Compare commits
936 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 1937682a20 | |||
| fd0f475aaf | |||
| 76759a1853 | |||
| fd68e0c384 | |||
| 6685e532df | |||
| d4f02b8916 | |||
| e0be7a8427 | |||
| 2174ce5afe | |||
| 82bf9a3730 | |||
| ebbc56b4ec | |||
| 6d664438ba | |||
| 3064b21e23 | |||
| 374bb6cc38 | |||
| e7edad6fe9 | |||
| d8688def65 | |||
| 435b1df2db | |||
| 7fd7fc67f7 | |||
| 9d1accfea0 | |||
| c59a2badd2 | |||
| 9f670bc7e8 | |||
| afbd3da2fa | |||
| 32595360f2 | |||
| 8c7bc08f60 | |||
| 57e15ec9b5 | |||
| 021154d8b1 | |||
| b82ba9b0be | |||
| 45493949cd | |||
| 8c6c973614 | |||
| a037918748 | |||
| 4fa673a68a | |||
| 813c3912fc | |||
| 078d04ef23 | |||
| 1a773bf2c8 | |||
| f113474a6e | |||
| 6577e063d1 | |||
| 7953c570d9 | |||
| f25c3fc9cb | |||
| fc0952abb9 | |||
| b414c62ce4 | |||
| 04903af798 | |||
| e8c3b1f2a0 | |||
| 8bf30e3c42 | |||
| fbc51fa210 | |||
| 7025a2c4a5 | |||
| 0120768f63 | |||
| b425b97ad6 | |||
| 539ea3982d | |||
| 65bd61e87c | |||
| 023454b49e | |||
| cd869bb7a3 | |||
| 957888a100 | |||
| d2c9efb1f3 | |||
| 7799859f9f | |||
| ca3bedbd02 | |||
| 1b16c62608 | |||
| 91de919451 | |||
| aa52408aab | |||
| e6f36d9cdc | |||
| 28323cf8ee | |||
| 533c7b7b7f | |||
| ac28cad998 | |||
| 5206b938ef | |||
| 59481435f4 | |||
| f31faf6f14 | |||
| 14c6d6cb3a | |||
| 4ec10bcdc1 | |||
| 0bf36cf2ad | |||
| 820fe8d2b5 | |||
| deb3803a3a | |||
| 95686227bd | |||
| df74c3c638 | |||
| d88a3c15f7 | |||
| 38c8043229 | |||
| ee0ad5cceb | |||
| 984b952eb3 | |||
| 5865da28d1 | |||
| bb1f39196e | |||
| 6a59766313 | |||
| 65423d2b33 | |||
| c2bc187ce7 | |||
| d0b27dc906 | |||
| c2ce1d3b9c | |||
| bb4cbaf0cf | |||
| c462e5a575 | |||
| 8b19b75270 | |||
| 907bfb5ef0 | |||
| 1ae073c052 | |||
| c9a06d1093 | |||
| d8ad364617 | |||
| f57ec2b53b | |||
| 9677b09b7c | |||
| cbaaf0af0e | |||
| 48239090f1 | |||
| 82a973c043 | |||
| 1d7e9eca09 | |||
| 850e14923e | |||
| 8e0881d9ab | |||
| 834297b13d | |||
| c19d044364 | |||
| 8b3d98c5a5 | |||
| 5bbbda473f | |||
| 9f5a98d576 | |||
| 986c31dcfe | |||
| 5096c163c1 | |||
| 7b99e14ab1 | |||
| 7c8a4ccecb | |||
| 5d26c6ae89 | |||
| 5a10bb9aa6 | |||
| fa0ba939a7 | |||
| fc7b25ac67 | |||
| e09104a126 | |||
| 141d4b71b1 | |||
| ea903819cb | |||
| 24a23e1225 | |||
| 8749540602 | |||
| 9de7084884 | |||
| 94275b115c | |||
| e285af6e48 | |||
| f6f055a93d | |||
| 3a5a66775c | |||
| 7e1bd3e3c3 | |||
| 964fc13a99 | |||
| a5f66b5003 | |||
| 2abc628899 | |||
| 2b50233f3f | |||
| f5866199c4 | |||
| 7e5cdaab4b | |||
| b2453d280a | |||
| b4d62a05af | |||
| 589dda3cf2 | |||
| 3d2dbefcde | |||
| b1695c1b68 | |||
| d57ff884ed | |||
| 26cccd8faa | |||
| 9cc7142dd7 | |||
| 5a5fe7494a | |||
| 6a7042fe2f | |||
| 72cfa2829d | |||
| 4debd4d3ef | |||
| 3f6dcda3e5 | |||
| 27d96fa608 | |||
| dd4f798b97 | |||
| 27947a79d6 | |||
| 11f827c58b | |||
| 48dd4d9eae | |||
| 93c00b2af7 | |||
| 7d7f7f4b49 | |||
| 1b0823db94 | |||
| 6ca7a453d4 | |||
| bad47dcfeb | |||
| c3d8b78b47 | |||
| 21e72d1a5e | |||
| 7b2917255a | |||
| 11cfe0dd05 | |||
| 780c70f6ea | |||
| b5481c6195 | |||
| 1da4907927 | |||
| ec580374e5 | |||
| 340a9108ca | |||
| 74069addc3 | |||
| 477869c044 | |||
| ffead92d4e | |||
| 0a6628bad0 | |||
| eb112c6f88 | |||
| ace00a1fe8 | |||
| 2fec94710b | |||
| 74b56fef3d | |||
| 8058eed6a5 | |||
| 3bd4a08119 | |||
| 68df28176c | |||
| 4cc3add770 | |||
| 50514ce414 | |||
| edebe4d4de | |||
| b9c3f4ec2c | |||
| 41ac13996e | |||
| 76a19c089a | |||
| 372a8e0658 | |||
| b8c3664934 | |||
| 9414309f15 | |||
| 9cbde7938a | |||
| 019df53a31 | |||
| af3ccee5c8 | |||
| a6c384b9f7 | |||
| b282b47b85 | |||
| c02e3a5549 | |||
| b82caf1322 | |||
| bfbca31074 | |||
| f8640662c5 | |||
| f8fb74b93a | |||
| 32fdf18203 | |||
| 3971c01562 | |||
| fd16393465 | |||
| 957185f7eb | |||
| 6ddcd8914b | |||
| 9e404c3154 | |||
| ebe8be9028 | |||
| 1394ecaf36 | |||
| 7e4b06fcd0 | |||
| d67348a0a5 | |||
| 179ae47d64 | |||
| 0b64633584 | |||
| 0c7bdcc1b1 | |||
| fc8b126673 | |||
| 06fe174c74 | |||
| afaf120bc2 | |||
| 42ca30d6c1 | |||
| d686e73daa | |||
| ec3c31e7a1 | |||
| 9e60cdbc3f | |||
| 5d9f1e6a43 | |||
| a8fba9af35 | |||
| a65dd315ad | |||
| 731eb72774 | |||
| c3ef381cd8 | |||
| 775fa7696b | |||
| 109bbda709 | |||
| 0f40c4b9b1 | |||
| bd85b3f19b | |||
| 13f22974a4 | |||
| a772fd9804 | |||
| 663a4d80df | |||
| 34b4443cc3 | |||
| d4b814aed6 | |||
| 58dc35a64a | |||
| 06d0a5ab4d | |||
| 80f618ea95 | |||
| b443fdcf76 | |||
| 7ee2114cd9 | |||
| 79de09c3df | |||
| 5b2a60b8e2 | |||
| a7116aa9a1 | |||
| 9e0f6d2012 | |||
| a30b19dd55 | |||
| f1e0bfebfc | |||
| c803e11505 | |||
| 1f8f3a6e8b | |||
| d5e26274c8 | |||
| 91ecc750be | |||
| 00e09382cd | |||
| 123582b00f | |||
| abacb735f4 | |||
| e33bb8febe | |||
| 17e846150c | |||
| a84000c285 | |||
| 74ee8fd1e3 | |||
| d2097dbdd9 | |||
| 99e65ec618 | |||
| 1d0bb39797 | |||
| 57e6d05a43 | |||
| aafbb5b403 | |||
| e368cd2810 | |||
| 981abbb1f2 | |||
| 6214aa7d2a | |||
| 6447ff49d3 | |||
| 41ee2db5a8 | |||
| f89b5dbbd2 | |||
| d875cda565 | |||
| d52a1e1a22 | |||
| 39a6d5655f | |||
| 428975e1d3 | |||
| 58b24eae30 | |||
| 547778b10f | |||
| 194c2620d6 | |||
| 5ecfc20d97 | |||
| 2dbc7aa688 | |||
| 6d8d2723a0 | |||
| 3ef9f2748d | |||
| 30461bef98 | |||
| 569f17c6c6 | |||
| a184e5dd87 | |||
| 9e5103124a | |||
| 41b24d350f | |||
| 742bfbe0ec | |||
| a1130c26e5 | |||
| b9dfc50a1b | |||
| 74b1fc6256 | |||
| 4aebfe9844 | |||
| debc6dddeb | |||
| 1a7ffa2c76 | |||
| 64783dd9cc | |||
| c1c4b3fb34 | |||
| 5abdf5191d | |||
| 64ebb245ad | |||
| 07cf95c76e | |||
| 9905341602 | |||
| 88a5001e06 | |||
| 7b940e3879 | |||
| b4723bb8c1 | |||
| 6450d24afe | |||
| 64bf57b5ea | |||
| 816bc424c4 | |||
| 371cb60945 | |||
| 603509ec90 | |||
| ad229fae43 | |||
| 510f025a01 | |||
| 5977cb0946 | |||
| 04164a83c4 | |||
| 96f907ee09 | |||
| c3c90deec0 | |||
| cbac72d8ee | |||
| 616013fd7a | |||
| 93b53dc116 | |||
| ebfc9f6d09 | |||
| 33b73c473c | |||
| ba54c747e2 | |||
| cd9e9e4049 | |||
| 15245d9d5e | |||
| 5429e4cff5 | |||
| b150b3a3a4 | |||
| 9e1fc80c48 | |||
| 0edc04d126 | |||
| e21b1e3716 | |||
| 00f37ad73f | |||
| 0769aa318a | |||
| de7f5cdc62 | |||
| 0c0d71a4e4 | |||
| 6de733c91e | |||
| 46bcfbe37c | |||
| 3c7384ab60 | |||
| 53f62674ae | |||
| 25bbf31f57 | |||
| 10f8d0f842 | |||
| 8d6f741738 | |||
| feee37d75f | |||
| 801b72b92b | |||
| 759f396a2e | |||
| 6dd53ce63d | |||
| a63946233b | |||
| 344eda55d4 | |||
| 5867be2914 | |||
| 51e7122f25 | |||
| 1e696b028a | |||
| 1f392517f8 | |||
| 82884da18c | |||
| 24a59ad3d2 | |||
| 969a462ac9 | |||
| 501ac016da | |||
| feeb6802aa | |||
| 281e0a007b | |||
| 1d74482817 | |||
| b57a70f373 | |||
| dca9007ac7 | |||
| cc9ca67664 | |||
| 53d67088ee | |||
| 10f2407f48 | |||
| 01491d303c | |||
| 47f1d42a7e | |||
| 2a8a60c2c5 | |||
| 58eec83a54 | |||
| 221ac0b9ab | |||
| b2ae4490b9 | |||
| 51b13a8c54 | |||
| f015b94176 | |||
| 41f66849c7 | |||
| 9c8075ba8e | |||
| 3e20b36e8f | |||
| 6a48476502 | |||
| 9eb2f78631 | |||
| 0e98529365 | |||
| 022d835565 | |||
| 5ab7d08a0a | |||
| ef7713fbb2 | |||
| d44f241317 | |||
| d6b4444069 | |||
| 73d1caf8f2 | |||
| d2cc8ccb11 | |||
| 5fbac49791 | |||
| f7e349cea4 | |||
| e736c3b36b | |||
| dbda59e58a | |||
| dd93c47abf | |||
| f12886aefa | |||
| 7195c4d42c | |||
| 5d5224b322 | |||
| 0e0e41eabc | |||
| 89103b4747 | |||
| 9d39380705 | |||
| c8336c45b9 | |||
| 4c7b22d37d | |||
| 579f1ef278 | |||
| 3d3fc81f48 | |||
| 3a215deff2 | |||
| 9d964d3fc3 | |||
| 60c0799958 | |||
| 44afb48447 | |||
| c5ae225418 | |||
| c5b7559856 | |||
| 8dc920228e | |||
| 3902aa222b | |||
| d5f6fdb3c4 | |||
| e85e327ae0 | |||
| 1091e3a37e | |||
| 8fa3fa76c3 | |||
| 50bb6e1179 | |||
| 029adbe531 | |||
| 33cbbf9f8b | |||
| 6e9b69a338 | |||
| 83182d2799 | |||
| 83266205d0 | |||
| 8016d78a4b | |||
| a1aa0af8a4 | |||
| c69773d7e8 | |||
| 246c269af8 | |||
| 4bc39d234d | |||
| 2b717bb195 | |||
| ddb28b33a3 | |||
| 1c0a0c4c26 | |||
| 7dfe959f4b | |||
| 8f64dad282 | |||
| 821adc3041 | |||
| e2b177c508 | |||
| e837124f4b | |||
| 3fdc3cfbbf | |||
| e9809de651 | |||
| 61f6479ea9 | |||
| e84703b253 | |||
| e4aa0c362e | |||
| a183ea4ba7 | |||
| 6c7c176dc9 | |||
| e6a8d0b4e6 | |||
| db263df5d5 | |||
| d1998d747d | |||
| c0eaeb15af | |||
| 9bcfb92a00 | |||
| d74fc56fa5 | |||
| a44ed231c2 | |||
| daae17851a | |||
| ce19a7baef | |||
| 8d6e72dbfa | |||
| 6f4f6bff6b | |||
| 367b823466 | |||
| c8ac42aad1 | |||
| 449bc7bcf3 | |||
| 3810413c00 | |||
| f8f5d6cea2 | |||
| cde35bebe9 | |||
| 49fee7c8db | |||
| b5b1487f6a | |||
| 5cb567c138 | |||
| d212fb59fe | |||
| 71314e47b1 | |||
| ba2a737cce | |||
| 909c3dfe83 | |||
| 9d4fdc45d3 | |||
| 63fd38a04f | |||
| 50190ca669 | |||
| 0980fdfe8c | |||
| bba306d414 | |||
| a95326bec4 | |||
| 0f82948e4f | |||
| 8e1c3561be | |||
| ff6f4680c4 | |||
| adadb4e3c7 | |||
| d282d24800 | |||
| a196319edf | |||
| 3fadb4fc85 | |||
| 592e40ebe9 | |||
| 4068429ac7 | |||
| 88f70ce63c | |||
| ac8ffb34e3 | |||
| ef83f6831f | |||
| 600f339c4c | |||
| 7f691612ca | |||
| a976f4dff9 | |||
| 696d6813e0 | |||
| c48b6bf6bd | |||
| 2580235c72 | |||
| 3786f3742f | |||
| d9708c92b4 | |||
| e3aabe6959 | |||
| 1e1176b6eb | |||
| 219e64489c | |||
| 47ed9b2d39 | |||
| 6efdfe3234 | |||
| e1640314df | |||
| c16a27caa9 | |||
| 2ad17a6100 | |||
| 23c06a51cc | |||
| badb70da48 | |||
| 447198f21b | |||
| acb20338b1 | |||
| 73f7812045 | |||
| 989b89b12a | |||
| 20123d427b | |||
| a05d89b1e5 | |||
| 92e6aa3653 | |||
| b372fb6165 | |||
| 0cb2bbd01a | |||
| a669b8a6bc | |||
| 719296133d | |||
| 86861f8379 | |||
| e73a7e4006 | |||
| aa4a45187e | |||
| 0a7d1e756f | |||
| 859f0f6b19 | |||
| 23ef5027c6 | |||
| 4ccbae320e | |||
| ea83180761 | |||
| f1a6c5fe17 | |||
| bfa20d2758 | |||
| dcd4f880a8 | |||
| 7f3ce06de9 | |||
| 8bebfde701 | |||
| 98096195dd | |||
| 642bca4c3d | |||
| 80b87107de | |||
| c4c8a64111 | |||
| 470d402b17 | |||
| 1dc8cc1bce | |||
| 8687163f7f | |||
| 4e2bb7250f | |||
| 5461b00e89 | |||
| 16522cb0e3 | |||
| c321680b3d | |||
| f4633cb9c0 | |||
| f62217b65d | |||
| dfbdb5a135 | |||
| b0b90dc0d7 | |||
| 9aa9e980a9 | |||
| c4402500c7 | |||
| 755d2cb2e5 | |||
| 0affa24ce2 | |||
| bf2f7b3af4 | |||
| db61b876d6 | |||
| f3ca6a92ad | |||
| 2941e1f1f3 | |||
| 721c4309c2 | |||
| 57727e554d | |||
| b80b1cf92c | |||
| 5c5594ff16 | |||
| 65075896f2 | |||
| 32ba757501 | |||
| 4e6e2574ab | |||
| 41907b25f0 | |||
| 4bc2963320 | |||
| 4eb5e09873 | |||
| b5b04912b5 | |||
| f010dfffb9 | |||
| 5fd9a40b92 | |||
| e0cad0f87a | |||
| 8ec8901921 | |||
| 702edb288e | |||
| 31306ce672 | |||
| ac9aa44cb8 | |||
| 76f8436bfa | |||
| 61f488302f | |||
| 25cd53d775 | |||
| 060e55dfe3 | |||
| b5c33341a1 | |||
| 6e420c7be2 | |||
| d7f48472cc | |||
| 49779413aa | |||
| 8f450321fe | |||
| 86276832e0 | |||
| 61f321756f | |||
| d44b8aa8c1 | |||
| a6b5a513f9 | |||
| c4a00affc5 | |||
| 522121be7e | |||
| 3fa1ebed62 | |||
| 7ac7600dc3 | |||
| e9d4da7b56 | |||
| c4664b5a9c | |||
| 203afa39c4 | |||
| 51cb20ec39 | |||
| 2a6054f836 | |||
| 8ac4a207f3 | |||
| df4da02ab0 | |||
| f1b090e9e0 | |||
| 611faaddef | |||
| daa1b33247 | |||
| fd83d4eec3 | |||
| 81be357925 | |||
| 79cbc92abf | |||
| 06c5dd0907 | |||
| 908d522057 | |||
| 4ce2e25c0b | |||
| ef35619325 | |||
| b1cd0189bc | |||
| c95c46004a | |||
| c3f75d1d85 | |||
| c12ba58433 | |||
| 66355b4775 | |||
| e9b8a89b3c | |||
| 93c7b9d7fc | |||
| 6d8b7ec188 | |||
| 446cd5a58b | |||
| 83a9dd82db | |||
| 3da13f0cc9 | |||
| df8c09bcb3 | |||
| 8dcb8faf5d | |||
| 199c51d688 | |||
| 1792e193b1 | |||
| bf35c66183 | |||
| cb09e1ef7d | |||
| 0283826179 | |||
| 2f9d1c33e2 | |||
| 874809e0ca | |||
| c364b60776 | |||
| 7598a92436 | |||
| eb2ea8df1d | |||
| 9142ce8188 | |||
| 79514e5b8e | |||
| bb9df5cdc9 | |||
| e8613dbc93 | |||
| cc8ea32501 | |||
| 38a7dc5488 | |||
| 5bd2724765 | |||
| 9fd693272f | |||
| f7bad19e00 | |||
| 03ea0f3bfc | |||
| 2fc47b44c2 | |||
| 446e49d6db | |||
| 8bc9978909 | |||
| 1282bceeba | |||
| d38b390ed4 | |||
| 63c3c4dbc3 | |||
| afb9296e0d | |||
| c9244ef83a | |||
| a072c1997d | |||
| 3cb698ac15 | |||
| 0cc3647c1c | |||
| 3ffe47c6b7 | |||
| c5aa7b65f7 | |||
| 01ba5ad213 | |||
| a3a648bf6b | |||
| 887a512208 | |||
| 6f51e05553 | |||
| 5f4203bf9b | |||
| 8eaa7e9f04 | |||
| 76fd487818 | |||
| 07805cbeee | |||
| c40f33ca04 | |||
| 4e17fc36d8 | |||
| fd71b761ff | |||
| d18eb10ecd | |||
| 9f2ae1cb85 | |||
| 32f0b5dbaf | |||
| 2efc7c1b05 | |||
| 9fbfb8ad32 | |||
| 74e2e5279c | |||
| b980c8140b | |||
| 994e08aac1 | |||
| 8262cd71c4 | |||
| 2e3a0f39f6 | |||
| 4079b17dd9 | |||
| 1a1205f601 | |||
| 2d57a2df66 | |||
| eb10da8bb7 | |||
| 3e0146f9bd | |||
| 1bbc8a153b | |||
| 3670b4f49e | |||
| 2f55d669a2 | |||
| edc56202c1 | |||
| 7e5e67330b | |||
| 9fd0cd6a80 | |||
| 9b842e9ec7 | |||
| 0411eced89 | |||
| 2e93bdce0c | |||
| 8076100e14 | |||
| fb62f1fb40 | |||
| 0085e719a9 | |||
| 6136db1409 | |||
| 110e3d7033 | |||
| 0dc179ee72 | |||
| 4c9a7b8a75 | |||
| 1770b887ec | |||
| 18d801a13d | |||
| 851c3d51ed | |||
| 5251733c0d | |||
| c50b7e4eff | |||
| d318f1b5e1 | |||
| 02a4ceabdd | |||
| 7d1368c51c | |||
| 758e8d7b41 | |||
| 530fea2bc4 | |||
| 3bd75adb1c | |||
| 01f531e9b1 | |||
| a551a43164 | |||
| a43ce7eabb | |||
| 9409419afb | |||
| e0c9361b7d | |||
| 8b96f3d036 | |||
| 5ab5405b6f | |||
| 766f6e3eca | |||
| 12bcacf413 | |||
| 58f7410c9d | |||
| ea3aae9c39 | |||
| 8904e00842 | |||
| 7d59b3b564 | |||
| 7f766cd762 | |||
| 73e635ce6e | |||
| ecd5fa9c42 | |||
| 14215beb48 | |||
| 11ef1a9302 | |||
| c1deec64cb | |||
| 2bb296531d | |||
| ed386c84b6 | |||
| 7785d484ae | |||
| 706f63adfa | |||
| ecffe8513e | |||
| 801461eea2 | |||
| eee46a5094 | |||
| 09b5ce68a9 | |||
| 5625ce1b1a | |||
| 58278aa71c | |||
| 33fbe943e2 | |||
| 0dc12861ef | |||
| 67d8dafe44 | |||
| 3fb1c2e58d | |||
| 92d77e3fa8 | |||
| 48a677c4ac | |||
| e3fa46f26f | |||
| e2a8745abc | |||
| 3c0177a24b | |||
| 0103365697 | |||
| 45b8a499a7 | |||
| bb24c13ed7 | |||
| aabedcbcc7 | |||
| f4cb21bb8a | |||
| 6044a9723a | |||
| 9189ea20b0 | |||
| 95d143eafe | |||
| bef51aed03 | |||
| 1398485789 | |||
| 141a17e969 | |||
| da67afe5f6 | |||
| 28bc85a20a | |||
| 978c7fadb3 | |||
| d558cb69b0 | |||
| 0b07a6cf26 | |||
| 024a32a09b | |||
| b47756385d | |||
| ee470cc6a3 | |||
| 1a51b166a0 | |||
| 06b9200e91 | |||
| f04e76811a | |||
| 817d9b15f7 | |||
| 150b603770 | |||
| eb0b84c564 | |||
| bb99f52712 | |||
| bce09eb987 | |||
| 51cc1ff2c9 | |||
| b4c44e659b | |||
| de7604fa77 | |||
| 44bce3c74e | |||
| 3ba575216a | |||
| 4dae91a1fe | |||
| 94f23d00a7 | |||
| e2cd92ea23 | |||
| 3a618e3d24 | |||
| dd4b0b95d5 | |||
| c8a5322d1f | |||
| ca0308b60d | |||
| 6e6cc2922d | |||
| eaf5e0289a | |||
| f97e3548e5 | |||
| e651ca8adb | |||
| 78e421e4ea | |||
| a10c8df876 | |||
| 648f6a8e0c | |||
| 2b7ddcbb5c | |||
| e3a8dc6e23 | |||
| ca8dc2bde2 | |||
| 900419e85e | |||
| 3a99824638 | |||
| bab918f049 | |||
| ed594d7ba6 | |||
| 9211febbfc | |||
| 3069716510 | |||
| dfab42c949 | |||
| 18819723c1 | |||
| 6bc35be10a | |||
| b53989b195 | |||
| 726aaea0fe | |||
| 3f18a09c86 | |||
| 58985e6b37 | |||
| ab1e0fa9bf | |||
| 85abbbb8fa | |||
| ba66cf8d69 | |||
| 052fbde3ac | |||
| 1da05297ea | |||
| f537e5a519 | |||
| c4afdb7895 | |||
| 64179c3221 | |||
| b7aa425344 | |||
| 9c1ece8978 | |||
| bf348032bc | |||
| 25eeeaa65f | |||
| 09d2e58811 | |||
| f4869f8de3 | |||
| 591470d86d | |||
| a5436a3ac0 | |||
| 140d58b512 | |||
| 0a271938d8 | |||
| 33c8fe1221 | |||
| 532c340829 | |||
| 92ab0ef7d6 | |||
| 6e4fc5e1a8 | |||
| 4eb949625c | |||
| 9d5dc582be | |||
| 5a8dd0c549 | |||
| c7808825b1 | |||
| cb52279c3e | |||
| 9d5becb4de | |||
| 71072f5620 | |||
| a18e54ecd7 | |||
| 3345218439 | |||
| 4ff1fabc86 | |||
| 4652fc5ac3 | |||
| 4573195894 | |||
| 310d6b9075 | |||
| db19c46d6d | |||
| 1466daeafc | |||
| dd1641ecc4 | |||
| 7dae6bb3b5 | |||
| 2e1b61e590 | |||
| f293dbbf97 | |||
| bf08a5b75e | |||
| 48ce0379bc | |||
| d235aa068d | |||
| ce57a6c6db | |||
| d70632a7cf | |||
| 4333ecc43f | |||
| a56125b0a8 | |||
| 23f03d4796 | |||
| 06ab10a1be | |||
| 6ee4012c0a | |||
| 46988af636 | |||
| 18ec22bffe | |||
| 1142201a3a | |||
| 69f9564a6d | |||
| 90441294db | |||
| 13fd466c18 | |||
| b7f45e67dc | |||
| 02ab75b86a | |||
| f6e476d7a8 | |||
| b531b0bbef | |||
| e2b19900ec | |||
| 3732cf2f97 | |||
| 2f1e2c492f | |||
| 860534399b | |||
| 4d46f8c25c | |||
| 5ddd5d29e5 | |||
| 440fff64a2 | |||
| d2246df160 | |||
| c04c4b95de | |||
| 7583351760 | |||
| 82e2e25325 | |||
| 2ba0277b52 | |||
| 542611cce4 | |||
| c3c88ca8b4 | |||
| 6b3f7039b6 | |||
| 6b8458eb9f | |||
| 0bc7867ccd | |||
| d69a7944c9 | |||
| eb6f2df826 | |||
| 613b0d9548 | |||
| 325eaeb584 | |||
| 2f1073dc6e | |||
| 81c16c965e | |||
| a4668a16b6 | |||
| 9588721197 | |||
| 99c6c4a51b | |||
| 321b2db067 | |||
| 1ff1c5be64 | |||
| 5084b39ea5 | |||
| 5904e3f6b3 | |||
| db4632f4ba | |||
| 2600370659 | |||
| 9f3ba38314 | |||
| b594f518b7 | |||
| bbe8e02d74 | |||
| 652a7bbf80 | |||
| 1b0931fd92 | |||
| 96b550430a | |||
| 74b214a92a | |||
| cc3f604310 | |||
| c4255d12f7 | |||
| 74ff85a1a1 | |||
| ce168ab5db | |||
| f9ba7e648a | |||
| d243e24f53 | |||
| 6e7f0860f7 | |||
| 750dd6014a | |||
| 6484053037 | |||
| ec124607f4 | |||
| baaf39b6f9 | |||
| 2cb1b65309 | |||
| 757dda9ade | |||
| e717eaff86 | |||
| eae0bb89fd | |||
| 0a3a83382f | |||
| 486aeda3a7 | |||
| 7ea0859362 | |||
| 2996e43ff7 | |||
| 36d1fefc19 | |||
| 4d5db58a3e | |||
| 47cf92039b | |||
| c6c9d12275 | |||
| 0466ee2a83 | |||
| 19c95de8eb | |||
| 358e9e2847 | |||
| c17f7ee694 | |||
| de5a8c5cb4 | |||
| bac30eb3e7 | |||
| 695e24dbce | |||
| 94d4b3c8e7 | |||
| f4e931f18f | |||
| 569dc1919c | |||
| fd383140cf | |||
| 26e1cd7ec4 | |||
| d7d3166a27 | |||
| 2974b9cee9 | |||
| 25e8273d2f | |||
| b67a49441f | |||
| 2310cd66e5 | |||
| 69f4f148dc | |||
| 50e444fa1d | |||
| f25c81a744 | |||
| ccee26b065 | |||
| 4f96267033 | |||
| 1fdc18e6a0 | |||
| f49c220c03 | |||
| d88424ef2a | |||
| 019f6e3c5a | |||
| 261972f929 | |||
| 02e6963325 | |||
| 036500223d | |||
| 0726a6e12e | |||
| 3db6938caa | |||
| 34fc215249 | |||
| 67a70ad112 | |||
| df8aa69a99 | |||
| 816096e642 | |||
| 6b9795849d |
+3
-2
@@ -78,6 +78,8 @@ module.exports = {
|
|||||||
//extraNetworks.js
|
//extraNetworks.js
|
||||||
requestGet: "readonly",
|
requestGet: "readonly",
|
||||||
popup: "readonly",
|
popup: "readonly",
|
||||||
|
// profilerVisualization.js
|
||||||
|
createVisualizationTable: "readonly",
|
||||||
// from python
|
// from python
|
||||||
localization: "readonly",
|
localization: "readonly",
|
||||||
// progrssbar.js
|
// progrssbar.js
|
||||||
@@ -86,8 +88,7 @@ module.exports = {
|
|||||||
// imageviewer.js
|
// imageviewer.js
|
||||||
modalPrevImage: "readonly",
|
modalPrevImage: "readonly",
|
||||||
modalNextImage: "readonly",
|
modalNextImage: "readonly",
|
||||||
// token-counters.js
|
updateModalImageIfVisible: "readonly",
|
||||||
setupTokenCounters: "readonly",
|
|
||||||
// localStorage.js
|
// localStorage.js
|
||||||
localSet: "readonly",
|
localSet: "readonly",
|
||||||
localGet: "readonly",
|
localGet: "readonly",
|
||||||
|
|||||||
@@ -91,7 +91,7 @@ body:
|
|||||||
id: logs
|
id: logs
|
||||||
attributes:
|
attributes:
|
||||||
label: Console logs
|
label: Console logs
|
||||||
description: Please provide **full** cmd/terminal logs from the moment you started UI to the end of it, after the bug occured. If it's very long, provide a link to pastebin or similar service.
|
description: Please provide **full** cmd/terminal logs from the moment you started UI to the end of it, after the bug occurred. If it's very long, provide a link to pastebin or similar service.
|
||||||
render: Shell
|
render: Shell
|
||||||
validations:
|
validations:
|
||||||
required: true
|
required: true
|
||||||
|
|||||||
@@ -11,8 +11,8 @@ jobs:
|
|||||||
if: github.event_name != 'pull_request' || github.event.pull_request.head.repo.full_name != github.event.pull_request.base.repo.full_name
|
if: github.event_name != 'pull_request' || github.event.pull_request.head.repo.full_name != github.event.pull_request.base.repo.full_name
|
||||||
steps:
|
steps:
|
||||||
- name: Checkout Code
|
- name: Checkout Code
|
||||||
uses: actions/checkout@v3
|
uses: actions/checkout@v4
|
||||||
- uses: actions/setup-python@v4
|
- uses: actions/setup-python@v5
|
||||||
with:
|
with:
|
||||||
python-version: 3.11
|
python-version: 3.11
|
||||||
# NB: there's no cache: pip here since we're not installing anything
|
# NB: there's no cache: pip here since we're not installing anything
|
||||||
@@ -20,18 +20,18 @@ jobs:
|
|||||||
# not to have GHA download an (at the time of writing) 4 GB cache
|
# not to have GHA download an (at the time of writing) 4 GB cache
|
||||||
# of PyTorch and other dependencies.
|
# of PyTorch and other dependencies.
|
||||||
- name: Install Ruff
|
- name: Install Ruff
|
||||||
run: pip install ruff==0.1.6
|
run: pip install ruff==0.3.3
|
||||||
- name: Run Ruff
|
- name: Run Ruff
|
||||||
run: ruff .
|
run: ruff check .
|
||||||
lint-js:
|
lint-js:
|
||||||
name: eslint
|
name: eslint
|
||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
if: github.event_name != 'pull_request' || github.event.pull_request.head.repo.full_name != github.event.pull_request.base.repo.full_name
|
if: github.event_name != 'pull_request' || github.event.pull_request.head.repo.full_name != github.event.pull_request.base.repo.full_name
|
||||||
steps:
|
steps:
|
||||||
- name: Checkout Code
|
- name: Checkout Code
|
||||||
uses: actions/checkout@v3
|
uses: actions/checkout@v4
|
||||||
- name: Install Node.js
|
- name: Install Node.js
|
||||||
uses: actions/setup-node@v3
|
uses: actions/setup-node@v4
|
||||||
with:
|
with:
|
||||||
node-version: 18
|
node-version: 18
|
||||||
- run: npm i --ci
|
- run: npm i --ci
|
||||||
|
|||||||
@@ -11,9 +11,9 @@ jobs:
|
|||||||
if: github.event_name != 'pull_request' || github.event.pull_request.head.repo.full_name != github.event.pull_request.base.repo.full_name
|
if: github.event_name != 'pull_request' || github.event.pull_request.head.repo.full_name != github.event.pull_request.base.repo.full_name
|
||||||
steps:
|
steps:
|
||||||
- name: Checkout Code
|
- name: Checkout Code
|
||||||
uses: actions/checkout@v3
|
uses: actions/checkout@v4
|
||||||
- name: Set up Python 3.10
|
- name: Set up Python 3.10
|
||||||
uses: actions/setup-python@v4
|
uses: actions/setup-python@v5
|
||||||
with:
|
with:
|
||||||
python-version: 3.10.6
|
python-version: 3.10.6
|
||||||
cache: pip
|
cache: pip
|
||||||
@@ -22,7 +22,7 @@ jobs:
|
|||||||
launch.py
|
launch.py
|
||||||
- name: Cache models
|
- name: Cache models
|
||||||
id: cache-models
|
id: cache-models
|
||||||
uses: actions/cache@v3
|
uses: actions/cache@v4
|
||||||
with:
|
with:
|
||||||
path: models
|
path: models
|
||||||
key: "2023-12-30"
|
key: "2023-12-30"
|
||||||
@@ -68,13 +68,13 @@ jobs:
|
|||||||
python -m coverage report -i
|
python -m coverage report -i
|
||||||
python -m coverage html -i
|
python -m coverage html -i
|
||||||
- name: Upload main app output
|
- name: Upload main app output
|
||||||
uses: actions/upload-artifact@v3
|
uses: actions/upload-artifact@v4
|
||||||
if: always()
|
if: always()
|
||||||
with:
|
with:
|
||||||
name: output
|
name: output
|
||||||
path: output.txt
|
path: output.txt
|
||||||
- name: Upload coverage HTML
|
- name: Upload coverage HTML
|
||||||
uses: actions/upload-artifact@v3
|
uses: actions/upload-artifact@v4
|
||||||
if: always()
|
if: always()
|
||||||
with:
|
with:
|
||||||
name: htmlcov
|
name: htmlcov
|
||||||
|
|||||||
@@ -2,6 +2,7 @@ __pycache__
|
|||||||
*.ckpt
|
*.ckpt
|
||||||
*.safetensors
|
*.safetensors
|
||||||
*.pth
|
*.pth
|
||||||
|
.DS_Store
|
||||||
/ESRGAN/*
|
/ESRGAN/*
|
||||||
/SwinIR/*
|
/SwinIR/*
|
||||||
/repositories
|
/repositories
|
||||||
@@ -38,3 +39,6 @@ notification.mp3
|
|||||||
/package-lock.json
|
/package-lock.json
|
||||||
/.coverage*
|
/.coverage*
|
||||||
/test/test_outputs
|
/test/test_outputs
|
||||||
|
/cache
|
||||||
|
trace.json
|
||||||
|
/sysinfo-????-??-??-??-??.json
|
||||||
|
|||||||
+417
-6
@@ -1,3 +1,413 @@
|
|||||||
|
## 1.10.1
|
||||||
|
|
||||||
|
### Bug Fixes:
|
||||||
|
* fix image upscale on cpu ([#16275](https://github.com/AUTOMATIC1111/stable-diffusion-webui/pull/16275))
|
||||||
|
|
||||||
|
|
||||||
|
## 1.10.0
|
||||||
|
|
||||||
|
### Features:
|
||||||
|
* A lot of performance improvements (see below in Performance section)
|
||||||
|
* Stable Diffusion 3 support ([#16030](https://github.com/AUTOMATIC1111/stable-diffusion-webui/pull/16030), [#16164](https://github.com/AUTOMATIC1111/stable-diffusion-webui/pull/16164), [#16212](https://github.com/AUTOMATIC1111/stable-diffusion-webui/pull/16212))
|
||||||
|
* Recommended Euler sampler; DDIM and other timestamp samplers currently not supported
|
||||||
|
* T5 text model is disabled by default, enable it in settings
|
||||||
|
* New schedulers:
|
||||||
|
* Align Your Steps ([#15751](https://github.com/AUTOMATIC1111/stable-diffusion-webui/pull/15751))
|
||||||
|
* KL Optimal ([#15608](https://github.com/AUTOMATIC1111/stable-diffusion-webui/pull/15608))
|
||||||
|
* Normal ([#16149](https://github.com/AUTOMATIC1111/stable-diffusion-webui/pull/16149))
|
||||||
|
* DDIM ([#16149](https://github.com/AUTOMATIC1111/stable-diffusion-webui/pull/16149))
|
||||||
|
* Simple ([#16142](https://github.com/AUTOMATIC1111/stable-diffusion-webui/pull/16142))
|
||||||
|
* Beta ([#16235](https://github.com/AUTOMATIC1111/stable-diffusion-webui/pull/16235))
|
||||||
|
* New sampler: DDIM CFG++ ([#16035](https://github.com/AUTOMATIC1111/stable-diffusion-webui/pull/16035))
|
||||||
|
|
||||||
|
### Minor:
|
||||||
|
* Option to skip CFG on early steps ([#15607](https://github.com/AUTOMATIC1111/stable-diffusion-webui/pull/15607))
|
||||||
|
* Add --models-dir option ([#15742](https://github.com/AUTOMATIC1111/stable-diffusion-webui/pull/15742))
|
||||||
|
* Allow mobile users to open context menu by using two fingers press ([#15682](https://github.com/AUTOMATIC1111/stable-diffusion-webui/pull/15682))
|
||||||
|
* Infotext: add Lora name as TI hashes for bundled Textual Inversion ([#15679](https://github.com/AUTOMATIC1111/stable-diffusion-webui/pull/15679))
|
||||||
|
* Check model's hash after downloading it to prevent corruped downloads ([#15602](https://github.com/AUTOMATIC1111/stable-diffusion-webui/pull/15602))
|
||||||
|
* More extension tag filtering options ([#15627](https://github.com/AUTOMATIC1111/stable-diffusion-webui/pull/15627))
|
||||||
|
* When saving AVIF, use JPEG's quality setting ([#15610](https://github.com/AUTOMATIC1111/stable-diffusion-webui/pull/15610))
|
||||||
|
* Add filename pattern: `[basename]` ([#15978](https://github.com/AUTOMATIC1111/stable-diffusion-webui/pull/15978))
|
||||||
|
* Add option to enable clip skip for clip L on SDXL ([#15992](https://github.com/AUTOMATIC1111/stable-diffusion-webui/pull/15992))
|
||||||
|
* Option to prevent screen sleep during generation ([#16001](https://github.com/AUTOMATIC1111/stable-diffusion-webui/pull/16001))
|
||||||
|
* ToggleLivePriview button in image viewer ([#16065](https://github.com/AUTOMATIC1111/stable-diffusion-webui/pull/16065))
|
||||||
|
* Remove ui flashing on reloading and fast scrollong ([#16153](https://github.com/AUTOMATIC1111/stable-diffusion-webui/pull/16153))
|
||||||
|
* option to disable save button log.csv ([#16242](https://github.com/AUTOMATIC1111/stable-diffusion-webui/pull/16242))
|
||||||
|
|
||||||
|
### Extensions and API:
|
||||||
|
* Add process_before_every_sampling hook ([#15984](https://github.com/AUTOMATIC1111/stable-diffusion-webui/pull/15984))
|
||||||
|
* Return HTTP 400 instead of 404 on invalid sampler error ([#16140](https://github.com/AUTOMATIC1111/stable-diffusion-webui/pull/16140))
|
||||||
|
|
||||||
|
### Performance:
|
||||||
|
* [Performance 1/6] use_checkpoint = False ([#15803](https://github.com/AUTOMATIC1111/stable-diffusion-webui/pull/15803))
|
||||||
|
* [Performance 2/6] Replace einops.rearrange with torch native ops ([#15804](https://github.com/AUTOMATIC1111/stable-diffusion-webui/pull/15804))
|
||||||
|
* [Performance 4/6] Precompute is_sdxl_inpaint flag ([#15806](https://github.com/AUTOMATIC1111/stable-diffusion-webui/pull/15806))
|
||||||
|
* [Performance 5/6] Prevent unnecessary extra networks bias backup ([#15816](https://github.com/AUTOMATIC1111/stable-diffusion-webui/pull/15816))
|
||||||
|
* [Performance 6/6] Add --precision half option to avoid casting during inference ([#15820](https://github.com/AUTOMATIC1111/stable-diffusion-webui/pull/15820))
|
||||||
|
* [Performance] LDM optimization patches ([#15824](https://github.com/AUTOMATIC1111/stable-diffusion-webui/pull/15824))
|
||||||
|
* [Performance] Keep sigmas on CPU ([#15823](https://github.com/AUTOMATIC1111/stable-diffusion-webui/pull/15823))
|
||||||
|
* Check for nans in unet only once, after all steps have been completed
|
||||||
|
* Added pption to run torch profiler for image generation
|
||||||
|
|
||||||
|
### Bug Fixes:
|
||||||
|
* Fix for grids without comprehensive infotexts ([#15958](https://github.com/AUTOMATIC1111/stable-diffusion-webui/pull/15958))
|
||||||
|
* feat: lora partial update precede full update ([#15943](https://github.com/AUTOMATIC1111/stable-diffusion-webui/pull/15943))
|
||||||
|
* Fix bug where file extension had an extra '.' under some circumstances ([#15893](https://github.com/AUTOMATIC1111/stable-diffusion-webui/pull/15893))
|
||||||
|
* Fix corrupt model initial load loop ([#15600](https://github.com/AUTOMATIC1111/stable-diffusion-webui/pull/15600))
|
||||||
|
* Allow old sampler names in API ([#15656](https://github.com/AUTOMATIC1111/stable-diffusion-webui/pull/15656))
|
||||||
|
* more old sampler scheduler compatibility ([#15681](https://github.com/AUTOMATIC1111/stable-diffusion-webui/pull/15681))
|
||||||
|
* Fix Hypertile xyz ([#15831](https://github.com/AUTOMATIC1111/stable-diffusion-webui/pull/15831))
|
||||||
|
* XYZ CSV skipinitialspace ([#15832](https://github.com/AUTOMATIC1111/stable-diffusion-webui/pull/15832))
|
||||||
|
* fix soft inpainting on mps and xpu, torch_utils.float64 ([#15815](https://github.com/AUTOMATIC1111/stable-diffusion-webui/pull/15815))
|
||||||
|
* fix extention update when not on main branch ([#15797](https://github.com/AUTOMATIC1111/stable-diffusion-webui/pull/15797))
|
||||||
|
* update pickle safe filenames
|
||||||
|
* use relative path for webui-assets css ([#15757](https://github.com/AUTOMATIC1111/stable-diffusion-webui/pull/15757))
|
||||||
|
* When creating a virtual environment, upgrade pip in webui.bat/webui.sh ([#15750](https://github.com/AUTOMATIC1111/stable-diffusion-webui/pull/15750))
|
||||||
|
* Fix AttributeError ([#15738](https://github.com/AUTOMATIC1111/stable-diffusion-webui/pull/15738))
|
||||||
|
* use script_path for webui root in launch_utils ([#15705](https://github.com/AUTOMATIC1111/stable-diffusion-webui/pull/15705))
|
||||||
|
* fix extra batch mode P Transparency ([#15664](https://github.com/AUTOMATIC1111/stable-diffusion-webui/pull/15664))
|
||||||
|
* use gradio theme colors in css ([#15680](https://github.com/AUTOMATIC1111/stable-diffusion-webui/pull/15680))
|
||||||
|
* Fix dragging text within prompt input ([#15657](https://github.com/AUTOMATIC1111/stable-diffusion-webui/pull/15657))
|
||||||
|
* Add correct mimetype for .mjs files ([#15654](https://github.com/AUTOMATIC1111/stable-diffusion-webui/pull/15654))
|
||||||
|
* QOL Items - handle metadata issues more cleanly for SD models, Loras and embeddings ([#15632](https://github.com/AUTOMATIC1111/stable-diffusion-webui/pull/15632))
|
||||||
|
* replace wsl-open with wslpath and explorer.exe ([#15968](https://github.com/AUTOMATIC1111/stable-diffusion-webui/pull/15968))
|
||||||
|
* Fix SDXL Inpaint ([#15976](https://github.com/AUTOMATIC1111/stable-diffusion-webui/pull/15976))
|
||||||
|
* multi size grid ([#15988](https://github.com/AUTOMATIC1111/stable-diffusion-webui/pull/15988))
|
||||||
|
* fix Replace preview ([#16118](https://github.com/AUTOMATIC1111/stable-diffusion-webui/pull/16118))
|
||||||
|
* Possible fix of wrong scale in weight decomposition ([#16151](https://github.com/AUTOMATIC1111/stable-diffusion-webui/pull/16151))
|
||||||
|
* Ensure use of python from venv on Mac and Linux ([#16116](https://github.com/AUTOMATIC1111/stable-diffusion-webui/pull/16116))
|
||||||
|
* Prioritize python3.10 over python3 if both are available on Linux and Mac (with fallback) ([#16092](https://github.com/AUTOMATIC1111/stable-diffusion-webui/pull/16092))
|
||||||
|
* stoping generation extras ([#16085](https://github.com/AUTOMATIC1111/stable-diffusion-webui/pull/16085))
|
||||||
|
* Fix SD2 loading ([#16078](https://github.com/AUTOMATIC1111/stable-diffusion-webui/pull/16078), [#16079](https://github.com/AUTOMATIC1111/stable-diffusion-webui/pull/16079))
|
||||||
|
* fix infotext Lora hashes for hires fix different lora ([#16062](https://github.com/AUTOMATIC1111/stable-diffusion-webui/pull/16062))
|
||||||
|
* Fix sampler scheduler autocorrection warning ([#16054](https://github.com/AUTOMATIC1111/stable-diffusion-webui/pull/16054))
|
||||||
|
* fix ui flashing on reloading and fast scrollong ([#16153](https://github.com/AUTOMATIC1111/stable-diffusion-webui/pull/16153))
|
||||||
|
* fix upscale logic ([#16239](https://github.com/AUTOMATIC1111/stable-diffusion-webui/pull/16239))
|
||||||
|
* [bug] do not break progressbar on non-job actions (add wrap_gradio_call_no_job) ([#16202](https://github.com/AUTOMATIC1111/stable-diffusion-webui/pull/16202))
|
||||||
|
* fix OSError: cannot write mode P as JPEG ([#16194](https://github.com/AUTOMATIC1111/stable-diffusion-webui/pull/16194))
|
||||||
|
|
||||||
|
### Other:
|
||||||
|
* fix changelog #15883 -> #15882 ([#15907](https://github.com/AUTOMATIC1111/stable-diffusion-webui/pull/15907))
|
||||||
|
* ReloadUI backgroundColor --background-fill-primary ([#15864](https://github.com/AUTOMATIC1111/stable-diffusion-webui/pull/15864))
|
||||||
|
* Use different torch versions for Intel and ARM Macs ([#15851](https://github.com/AUTOMATIC1111/stable-diffusion-webui/pull/15851))
|
||||||
|
* XYZ override rework ([#15836](https://github.com/AUTOMATIC1111/stable-diffusion-webui/pull/15836))
|
||||||
|
* scroll extensions table on overflow ([#15830](https://github.com/AUTOMATIC1111/stable-diffusion-webui/pull/15830))
|
||||||
|
* img2img batch upload method ([#15817](https://github.com/AUTOMATIC1111/stable-diffusion-webui/pull/15817))
|
||||||
|
* chore: sync v1.8.0 packages according to changelog ([#15783](https://github.com/AUTOMATIC1111/stable-diffusion-webui/pull/15783))
|
||||||
|
* Add AVIF MIME type support to mimetype definitions ([#15739](https://github.com/AUTOMATIC1111/stable-diffusion-webui/pull/15739))
|
||||||
|
* Update imageviewer.js ([#15730](https://github.com/AUTOMATIC1111/stable-diffusion-webui/pull/15730))
|
||||||
|
* no-referrer ([#15641](https://github.com/AUTOMATIC1111/stable-diffusion-webui/pull/15641))
|
||||||
|
* .gitignore trace.json ([#15980](https://github.com/AUTOMATIC1111/stable-diffusion-webui/pull/15980))
|
||||||
|
* Bump spandrel to 0.3.4 ([#16144](https://github.com/AUTOMATIC1111/stable-diffusion-webui/pull/16144))
|
||||||
|
* Defunct --max-batch-count ([#16119](https://github.com/AUTOMATIC1111/stable-diffusion-webui/pull/16119))
|
||||||
|
* docs: update bug_report.yml ([#16102](https://github.com/AUTOMATIC1111/stable-diffusion-webui/pull/16102))
|
||||||
|
* Maintaining Project Compatibility for Python 3.9 Users Without Upgrade Requirements. ([#16088](https://github.com/AUTOMATIC1111/stable-diffusion-webui/pull/16088), [#16169](https://github.com/AUTOMATIC1111/stable-diffusion-webui/pull/16169), [#16192](https://github.com/AUTOMATIC1111/stable-diffusion-webui/pull/16192))
|
||||||
|
* Update torch for ARM Macs to 2.3.1 ([#16059](https://github.com/AUTOMATIC1111/stable-diffusion-webui/pull/16059))
|
||||||
|
* remove deprecated setting dont_fix_second_order_samplers_schedule ([#16061](https://github.com/AUTOMATIC1111/stable-diffusion-webui/pull/16061))
|
||||||
|
* chore: fix typos ([#16060](https://github.com/AUTOMATIC1111/stable-diffusion-webui/pull/16060))
|
||||||
|
* shlex.join launch args in console log ([#16170](https://github.com/AUTOMATIC1111/stable-diffusion-webui/pull/16170))
|
||||||
|
* activate venv .bat ([#16231](https://github.com/AUTOMATIC1111/stable-diffusion-webui/pull/16231))
|
||||||
|
* add ids to the resize tabs in img2img ([#16218](https://github.com/AUTOMATIC1111/stable-diffusion-webui/pull/16218))
|
||||||
|
* update installation guide linux ([#16178](https://github.com/AUTOMATIC1111/stable-diffusion-webui/pull/16178))
|
||||||
|
* Robust sysinfo ([#16173](https://github.com/AUTOMATIC1111/stable-diffusion-webui/pull/16173))
|
||||||
|
* do not send image size on paste inpaint ([#16180](https://github.com/AUTOMATIC1111/stable-diffusion-webui/pull/16180))
|
||||||
|
* Fix noisy DS_Store files for MacOS ([#16166](https://github.com/AUTOMATIC1111/stable-diffusion-webui/pull/16166))
|
||||||
|
|
||||||
|
|
||||||
|
## 1.9.4
|
||||||
|
|
||||||
|
### Bug Fixes:
|
||||||
|
* pin setuptools version to fix the startup error ([#15882](https://github.com/AUTOMATIC1111/stable-diffusion-webui/pull/15882))
|
||||||
|
|
||||||
|
## 1.9.3
|
||||||
|
|
||||||
|
### Bug Fixes:
|
||||||
|
* fix get_crop_region_v2 ([#15594](https://github.com/AUTOMATIC1111/stable-diffusion-webui/pull/15594))
|
||||||
|
|
||||||
|
## 1.9.2
|
||||||
|
|
||||||
|
### Extensions and API:
|
||||||
|
* restore 1.8.0-style naming of scripts
|
||||||
|
|
||||||
|
## 1.9.1
|
||||||
|
|
||||||
|
### Minor:
|
||||||
|
* Add avif support ([#15582](https://github.com/AUTOMATIC1111/stable-diffusion-webui/pull/15582))
|
||||||
|
* Add filename patterns: `[sampler_scheduler]` and `[scheduler]` ([#15581](https://github.com/AUTOMATIC1111/stable-diffusion-webui/pull/15581))
|
||||||
|
|
||||||
|
### Extensions and API:
|
||||||
|
* undo adding scripts to sys.modules
|
||||||
|
* Add schedulers API endpoint ([#15577](https://github.com/AUTOMATIC1111/stable-diffusion-webui/pull/15577))
|
||||||
|
* Remove API upscaling factor limits ([#15560](https://github.com/AUTOMATIC1111/stable-diffusion-webui/pull/15560))
|
||||||
|
|
||||||
|
### Bug Fixes:
|
||||||
|
* Fix images do not match / Coordinate 'right' is less than 'left' ([#15534](https://github.com/AUTOMATIC1111/stable-diffusion-webui/pull/15534))
|
||||||
|
* fix: remove_callbacks_for_function should also remove from the ordered map ([#15533](https://github.com/AUTOMATIC1111/stable-diffusion-webui/pull/15533))
|
||||||
|
* fix x1 upscalers ([#15555](https://github.com/AUTOMATIC1111/stable-diffusion-webui/pull/15555))
|
||||||
|
* Fix cls.__module__ value in extension script ([#15532](https://github.com/AUTOMATIC1111/stable-diffusion-webui/pull/15532))
|
||||||
|
* fix typo in function call (eror -> error) ([#15531](https://github.com/AUTOMATIC1111/stable-diffusion-webui/pull/15531))
|
||||||
|
|
||||||
|
### Other:
|
||||||
|
* Hide 'No Image data blocks found.' message ([#15567](https://github.com/AUTOMATIC1111/stable-diffusion-webui/pull/15567))
|
||||||
|
* Allow webui.sh to be runnable from arbitrary directories containing a .git file ([#15561](https://github.com/AUTOMATIC1111/stable-diffusion-webui/pull/15561))
|
||||||
|
* Compatibility with Debian 11, Fedora 34+ and openSUSE 15.4+ ([#15544](https://github.com/AUTOMATIC1111/stable-diffusion-webui/pull/15544))
|
||||||
|
* numpy DeprecationWarning product -> prod ([#15547](https://github.com/AUTOMATIC1111/stable-diffusion-webui/pull/15547))
|
||||||
|
* get_crop_region_v2 ([#15583](https://github.com/AUTOMATIC1111/stable-diffusion-webui/pull/15583), [#15587](https://github.com/AUTOMATIC1111/stable-diffusion-webui/pull/15587))
|
||||||
|
|
||||||
|
|
||||||
|
## 1.9.0
|
||||||
|
|
||||||
|
### Features:
|
||||||
|
* Make refiner switchover based on model timesteps instead of sampling steps ([#14978](https://github.com/AUTOMATIC1111/stable-diffusion-webui/pull/14978))
|
||||||
|
* add an option to have old-style directory view instead of tree view; stylistic changes for extra network sorting/search controls
|
||||||
|
* add UI for reordering callbacks, support for specifying callback order in extension metadata ([#15205](https://github.com/AUTOMATIC1111/stable-diffusion-webui/pull/15205))
|
||||||
|
* Sgm uniform scheduler for SDXL-Lightning models ([#15325](https://github.com/AUTOMATIC1111/stable-diffusion-webui/pull/15325))
|
||||||
|
* Scheduler selection in main UI ([#15333](https://github.com/AUTOMATIC1111/stable-diffusion-webui/pull/15333), [#15361](https://github.com/AUTOMATIC1111/stable-diffusion-webui/pull/15361), [#15394](https://github.com/AUTOMATIC1111/stable-diffusion-webui/pull/15394))
|
||||||
|
|
||||||
|
### Minor:
|
||||||
|
* "open images directory" button now opens the actual dir ([#14947](https://github.com/AUTOMATIC1111/stable-diffusion-webui/pull/14947))
|
||||||
|
* Support inference with LyCORIS BOFT networks ([#14871](https://github.com/AUTOMATIC1111/stable-diffusion-webui/pull/14871), [#14973](https://github.com/AUTOMATIC1111/stable-diffusion-webui/pull/14973))
|
||||||
|
* make extra network card description plaintext by default, with an option to re-enable HTML as it was
|
||||||
|
* resize handle for extra networks ([#15041](https://github.com/AUTOMATIC1111/stable-diffusion-webui/pull/15041))
|
||||||
|
* cmd args: `--unix-filenames-sanitization` and `--filenames-max-length` ([#15031](https://github.com/AUTOMATIC1111/stable-diffusion-webui/pull/15031))
|
||||||
|
* show extra networks parameters in HTML table rather than raw JSON ([#15131](https://github.com/AUTOMATIC1111/stable-diffusion-webui/pull/15131))
|
||||||
|
* Add DoRA (weight-decompose) support for LoRA/LoHa/LoKr ([#15160](https://github.com/AUTOMATIC1111/stable-diffusion-webui/pull/15160), [#15283](https://github.com/AUTOMATIC1111/stable-diffusion-webui/pull/15283))
|
||||||
|
* Add '--no-prompt-history' cmd args for disable last generation prompt history ([#15189](https://github.com/AUTOMATIC1111/stable-diffusion-webui/pull/15189))
|
||||||
|
* update preview on Replace Preview ([#15201](https://github.com/AUTOMATIC1111/stable-diffusion-webui/pull/15201))
|
||||||
|
* only fetch updates for extensions' active git branches ([#15233](https://github.com/AUTOMATIC1111/stable-diffusion-webui/pull/15233))
|
||||||
|
* put upscale postprocessing UI into an accordion ([#15223](https://github.com/AUTOMATIC1111/stable-diffusion-webui/pull/15223))
|
||||||
|
* Support dragdrop for URLs to read infotext ([#15262](https://github.com/AUTOMATIC1111/stable-diffusion-webui/pull/15262))
|
||||||
|
* use diskcache library for caching ([#15287](https://github.com/AUTOMATIC1111/stable-diffusion-webui/pull/15287), [#15299](https://github.com/AUTOMATIC1111/stable-diffusion-webui/pull/15299))
|
||||||
|
* Allow PNG-RGBA for Extras Tab ([#15334](https://github.com/AUTOMATIC1111/stable-diffusion-webui/pull/15334))
|
||||||
|
* Support cover images embedded in safetensors metadata ([#15319](https://github.com/AUTOMATIC1111/stable-diffusion-webui/pull/15319))
|
||||||
|
* faster interrupt when using NN upscale ([#15380](https://github.com/AUTOMATIC1111/stable-diffusion-webui/pull/15380))
|
||||||
|
* Extras upscaler: an input field to limit maximul side length for the output image ([#15293](https://github.com/AUTOMATIC1111/stable-diffusion-webui/pull/15293), [#15415](https://github.com/AUTOMATIC1111/stable-diffusion-webui/pull/15415), [#15417](https://github.com/AUTOMATIC1111/stable-diffusion-webui/pull/15417), [#15425](https://github.com/AUTOMATIC1111/stable-diffusion-webui/pull/15425))
|
||||||
|
* add an option to hide postprocessing options in Extras tab
|
||||||
|
|
||||||
|
### Extensions and API:
|
||||||
|
* ResizeHandleRow - allow overriden column scale parametr ([#15004](https://github.com/AUTOMATIC1111/stable-diffusion-webui/pull/15004))
|
||||||
|
* call script_callbacks.ui_settings_callback earlier; fix extra-options-section built-in extension killing the ui if using a setting that doesn't exist
|
||||||
|
* make it possible to use zoom.js outside webui context ([#15286](https://github.com/AUTOMATIC1111/stable-diffusion-webui/pull/15286), [#15288](https://github.com/AUTOMATIC1111/stable-diffusion-webui/pull/15288))
|
||||||
|
* allow variants for extension name in metadata.ini ([#15290](https://github.com/AUTOMATIC1111/stable-diffusion-webui/pull/15290))
|
||||||
|
* make reloading UI scripts optional when doing Reload UI, and off by default
|
||||||
|
* put request: gr.Request at start of img2img function similar to txt2img
|
||||||
|
* open_folder as util ([#15442](https://github.com/AUTOMATIC1111/stable-diffusion-webui/pull/15442))
|
||||||
|
* make it possible to import extensions' script files as `import scripts.<filename>` ([#15423](https://github.com/AUTOMATIC1111/stable-diffusion-webui/pull/15423))
|
||||||
|
|
||||||
|
### Performance:
|
||||||
|
* performance optimization for extra networks HTML pages
|
||||||
|
* optimization for extra networks filtering
|
||||||
|
* optimization for extra networks sorting
|
||||||
|
|
||||||
|
### Bug Fixes:
|
||||||
|
* prevent escape button causing an interrupt when no generation has been made yet
|
||||||
|
* [bug] avoid doble upscaling in inpaint ([#14966](https://github.com/AUTOMATIC1111/stable-diffusion-webui/pull/14966))
|
||||||
|
* possible fix for reload button not appearing in some cases for extra networks.
|
||||||
|
* fix: the `split_threshold` parameter does not work when running Split oversized images ([#15006](https://github.com/AUTOMATIC1111/stable-diffusion-webui/pull/15006))
|
||||||
|
* Fix resize-handle visability for vertical layout (mobile) ([#15010](https://github.com/AUTOMATIC1111/stable-diffusion-webui/pull/15010))
|
||||||
|
* register_tmp_file also for mtime ([#15012](https://github.com/AUTOMATIC1111/stable-diffusion-webui/pull/15012))
|
||||||
|
* Protect alphas_cumprod during refiner switchover ([#14979](https://github.com/AUTOMATIC1111/stable-diffusion-webui/pull/14979))
|
||||||
|
* Fix EXIF orientation in API image loading ([#15062](https://github.com/AUTOMATIC1111/stable-diffusion-webui/pull/15062))
|
||||||
|
* Only override emphasis if actually used in prompt ([#15141](https://github.com/AUTOMATIC1111/stable-diffusion-webui/pull/15141))
|
||||||
|
* Fix emphasis infotext missing from `params.txt` ([#15142](https://github.com/AUTOMATIC1111/stable-diffusion-webui/pull/15142))
|
||||||
|
* fix extract_style_text_from_prompt #15132 ([#15135](https://github.com/AUTOMATIC1111/stable-diffusion-webui/pull/15135))
|
||||||
|
* Fix Soft Inpaint for AnimateDiff ([#15148](https://github.com/AUTOMATIC1111/stable-diffusion-webui/pull/15148))
|
||||||
|
* edit-attention: deselect surrounding whitespace ([#15178](https://github.com/AUTOMATIC1111/stable-diffusion-webui/pull/15178))
|
||||||
|
* chore: fix font not loaded ([#15183](https://github.com/AUTOMATIC1111/stable-diffusion-webui/pull/15183))
|
||||||
|
* use natural sort in extra networks when ordering by path
|
||||||
|
* Fix built-in lora system bugs caused by torch.nn.MultiheadAttention ([#15190](https://github.com/AUTOMATIC1111/stable-diffusion-webui/pull/15190))
|
||||||
|
* Avoid error from None in get_learned_conditioning ([#15191](https://github.com/AUTOMATIC1111/stable-diffusion-webui/pull/15191))
|
||||||
|
* Add entry to MassFileLister after writing metadata ([#15199](https://github.com/AUTOMATIC1111/stable-diffusion-webui/pull/15199))
|
||||||
|
* fix issue with Styles when Hires prompt is used ([#15269](https://github.com/AUTOMATIC1111/stable-diffusion-webui/pull/15269), [#15276](https://github.com/AUTOMATIC1111/stable-diffusion-webui/pull/15276))
|
||||||
|
* Strip comments from hires fix prompt ([#15263](https://github.com/AUTOMATIC1111/stable-diffusion-webui/pull/15263))
|
||||||
|
* Make imageviewer event listeners browser consistent ([#15261](https://github.com/AUTOMATIC1111/stable-diffusion-webui/pull/15261))
|
||||||
|
* Fix AttributeError in OFT when trying to get MultiheadAttention weight ([#15260](https://github.com/AUTOMATIC1111/stable-diffusion-webui/pull/15260))
|
||||||
|
* Add missing .mean() back ([#15239](https://github.com/AUTOMATIC1111/stable-diffusion-webui/pull/15239))
|
||||||
|
* fix "Restore progress" button ([#15221](https://github.com/AUTOMATIC1111/stable-diffusion-webui/pull/15221))
|
||||||
|
* fix ui-config for InputAccordion [custom_script_source] ([#15231](https://github.com/AUTOMATIC1111/stable-diffusion-webui/pull/15231))
|
||||||
|
* handle 0 wheel deltaY ([#15268](https://github.com/AUTOMATIC1111/stable-diffusion-webui/pull/15268))
|
||||||
|
* prevent alt menu for firefox ([#15267](https://github.com/AUTOMATIC1111/stable-diffusion-webui/pull/15267))
|
||||||
|
* fix: fix syntax errors ([#15179](https://github.com/AUTOMATIC1111/stable-diffusion-webui/pull/15179))
|
||||||
|
* restore outputs path ([#15307](https://github.com/AUTOMATIC1111/stable-diffusion-webui/pull/15307))
|
||||||
|
* Escape btn_copy_path filename ([#15316](https://github.com/AUTOMATIC1111/stable-diffusion-webui/pull/15316))
|
||||||
|
* Fix extra networks buttons when filename contains an apostrophe ([#15331](https://github.com/AUTOMATIC1111/stable-diffusion-webui/pull/15331))
|
||||||
|
* escape brackets in lora random prompt generator ([#15343](https://github.com/AUTOMATIC1111/stable-diffusion-webui/pull/15343))
|
||||||
|
* fix: Python version check for PyTorch installation compatibility ([#15390](https://github.com/AUTOMATIC1111/stable-diffusion-webui/pull/15390))
|
||||||
|
* fix typo in call_queue.py ([#15386](https://github.com/AUTOMATIC1111/stable-diffusion-webui/pull/15386))
|
||||||
|
* fix: when find already_loaded model, remove loaded by array index ([#15382](https://github.com/AUTOMATIC1111/stable-diffusion-webui/pull/15382))
|
||||||
|
* minor bug fix of sd model memory management ([#15350](https://github.com/AUTOMATIC1111/stable-diffusion-webui/pull/15350))
|
||||||
|
* Fix CodeFormer weight ([#15414](https://github.com/AUTOMATIC1111/stable-diffusion-webui/pull/15414))
|
||||||
|
* Fix: Remove script callbacks in ordered_callbacks_map ([#15428](https://github.com/AUTOMATIC1111/stable-diffusion-webui/pull/15428))
|
||||||
|
* fix limited file write (thanks, Sylwia)
|
||||||
|
* Fix extra-single-image API not doing upscale failed ([#15465](https://github.com/AUTOMATIC1111/stable-diffusion-webui/pull/15465))
|
||||||
|
* error handling paste_field callables ([#15470](https://github.com/AUTOMATIC1111/stable-diffusion-webui/pull/15470))
|
||||||
|
|
||||||
|
### Hardware:
|
||||||
|
* Add training support and change lspci for Ascend NPU ([#14981](https://github.com/AUTOMATIC1111/stable-diffusion-webui/pull/14981))
|
||||||
|
* Update to ROCm5.7 and PyTorch ([#14820](https://github.com/AUTOMATIC1111/stable-diffusion-webui/pull/14820))
|
||||||
|
* Better workaround for Navi1, removing --pre for Navi3 ([#15224](https://github.com/AUTOMATIC1111/stable-diffusion-webui/pull/15224))
|
||||||
|
* Ascend NPU wiki page ([#15228](https://github.com/AUTOMATIC1111/stable-diffusion-webui/pull/15228))
|
||||||
|
|
||||||
|
### Other:
|
||||||
|
* Update comment for Pad prompt/negative prompt v0 to add a warning about truncation, make it override the v1 implementation
|
||||||
|
* support resizable columns for touch (tablets) ([#15002](https://github.com/AUTOMATIC1111/stable-diffusion-webui/pull/15002))
|
||||||
|
* Fix #14591 using translated content to do categories mapping ([#14995](https://github.com/AUTOMATIC1111/stable-diffusion-webui/pull/14995))
|
||||||
|
* Use `absolute` path for normalized filepath ([#15035](https://github.com/AUTOMATIC1111/stable-diffusion-webui/pull/15035))
|
||||||
|
* resizeHandle handle double tap ([#15065](https://github.com/AUTOMATIC1111/stable-diffusion-webui/pull/15065))
|
||||||
|
* --dat-models-path cmd flag ([#15039](https://github.com/AUTOMATIC1111/stable-diffusion-webui/pull/15039))
|
||||||
|
* Add a direct link to the binary release ([#15059](https://github.com/AUTOMATIC1111/stable-diffusion-webui/pull/15059))
|
||||||
|
* upscaler_utils: Reduce logging ([#15084](https://github.com/AUTOMATIC1111/stable-diffusion-webui/pull/15084))
|
||||||
|
* Fix various typos with crate-ci/typos ([#15116](https://github.com/AUTOMATIC1111/stable-diffusion-webui/pull/15116))
|
||||||
|
* fix_jpeg_live_preview ([#15102](https://github.com/AUTOMATIC1111/stable-diffusion-webui/pull/15102))
|
||||||
|
* [alternative fix] can't load webui if selected wrong extra option in ui ([#15121](https://github.com/AUTOMATIC1111/stable-diffusion-webui/pull/15121))
|
||||||
|
* Error handling for unsupported transparency ([#14958](https://github.com/AUTOMATIC1111/stable-diffusion-webui/pull/14958))
|
||||||
|
* Add model description to searched terms ([#15198](https://github.com/AUTOMATIC1111/stable-diffusion-webui/pull/15198))
|
||||||
|
* bump action version ([#15272](https://github.com/AUTOMATIC1111/stable-diffusion-webui/pull/15272))
|
||||||
|
* PEP 604 annotations ([#15259](https://github.com/AUTOMATIC1111/stable-diffusion-webui/pull/15259))
|
||||||
|
* Automatically Set the Scale by value when user selects an Upscale Model ([#15244](https://github.com/AUTOMATIC1111/stable-diffusion-webui/pull/15244))
|
||||||
|
* move postprocessing-for-training into builtin extensions ([#15222](https://github.com/AUTOMATIC1111/stable-diffusion-webui/pull/15222))
|
||||||
|
* type hinting in shared.py ([#15211](https://github.com/AUTOMATIC1111/stable-diffusion-webui/pull/15211))
|
||||||
|
* update ruff to 0.3.3
|
||||||
|
* Update pytorch lightning utilities ([#15310](https://github.com/AUTOMATIC1111/stable-diffusion-webui/pull/15310))
|
||||||
|
* Add Size as an XYZ Grid option ([#15354](https://github.com/AUTOMATIC1111/stable-diffusion-webui/pull/15354))
|
||||||
|
* Use HF_ENDPOINT variable for HuggingFace domain with default ([#15443](https://github.com/AUTOMATIC1111/stable-diffusion-webui/pull/15443))
|
||||||
|
* re-add update_file_entry ([#15446](https://github.com/AUTOMATIC1111/stable-diffusion-webui/pull/15446))
|
||||||
|
* create_infotext allow index and callable, re-work Hires prompt infotext ([#15460](https://github.com/AUTOMATIC1111/stable-diffusion-webui/pull/15460))
|
||||||
|
* update restricted_opts to include more options for --hide-ui-dir-config ([#15492](https://github.com/AUTOMATIC1111/stable-diffusion-webui/pull/15492))
|
||||||
|
|
||||||
|
|
||||||
|
## 1.8.0
|
||||||
|
|
||||||
|
### Features:
|
||||||
|
* Update torch to version 2.1.2
|
||||||
|
* Soft Inpainting ([#14208](https://github.com/AUTOMATIC1111/stable-diffusion-webui/pull/14208))
|
||||||
|
* FP8 support ([#14031](https://github.com/AUTOMATIC1111/stable-diffusion-webui/pull/14031), [#14327](https://github.com/AUTOMATIC1111/stable-diffusion-webui/pull/14327))
|
||||||
|
* Support for SDXL-Inpaint Model ([#14390](https://github.com/AUTOMATIC1111/stable-diffusion-webui/pull/14390))
|
||||||
|
* Use Spandrel for upscaling and face restoration architectures ([#14425](https://github.com/AUTOMATIC1111/stable-diffusion-webui/pull/14425), [#14467](https://github.com/AUTOMATIC1111/stable-diffusion-webui/pull/14467), [#14473](https://github.com/AUTOMATIC1111/stable-diffusion-webui/pull/14473), [#14474](https://github.com/AUTOMATIC1111/stable-diffusion-webui/pull/14474), [#14477](https://github.com/AUTOMATIC1111/stable-diffusion-webui/pull/14477), [#14476](https://github.com/AUTOMATIC1111/stable-diffusion-webui/pull/14476), [#14484](https://github.com/AUTOMATIC1111/stable-diffusion-webui/pull/14484), [#14500](https://github.com/AUTOMATIC1111/stable-difusion-webui/pull/14500), [#14501](https://github.com/AUTOMATIC1111/stable-diffusion-webui/pull/14501), [#14504](https://github.com/AUTOMATIC1111/stable-diffusion-webui/pull/14504), [#14524](https://github.com/AUTOMATIC1111/stable-diffusion-webui/pull/14524), [#14809](https://github.com/AUTOMATIC1111/stable-diffusion-webui/pull/14809))
|
||||||
|
* Automatic backwards version compatibility (when loading infotexts from old images with program version specified, will add compatibility settings)
|
||||||
|
* Implement zero terminal SNR noise schedule option (**[SEED BREAKING CHANGE](https://github.com/AUTOMATIC1111/stable-diffusion-webui/wiki/Seed-breaking-changes#180-dev-170-225-2024-01-01---zero-terminal-snr-noise-schedule-option)**, [#14145](https://github.com/AUTOMATIC1111/stable-diffusion-webui/pull/14145), [#14979](https://github.com/AUTOMATIC1111/stable-diffusion-webui/pull/14979))
|
||||||
|
* Add a [✨] button to run hires fix on selected image in the gallery (with help from [#14598](https://github.com/AUTOMATIC1111/stable-diffusion-webui/pull/14598), [#14626](https://github.com/AUTOMATIC1111/stable-diffusion-webui/pull/14626), [#14728](https://github.com/AUTOMATIC1111/stable-diffusion-webui/pull/14728))
|
||||||
|
* [Separate assets repository](https://github.com/AUTOMATIC1111/stable-diffusion-webui-assets); serve fonts locally rather than from google's servers
|
||||||
|
* Official LCM Sampler Support ([#14583](https://github.com/AUTOMATIC1111/stable-diffusion-webui/pull/14583))
|
||||||
|
* Add support for DAT upscaler models ([#14690](https://github.com/AUTOMATIC1111/stable-diffusion-webui/pull/14690), [#15039](https://github.com/AUTOMATIC1111/stable-diffusion-webui/pull/15039))
|
||||||
|
* Extra Networks Tree View ([#14588](https://github.com/AUTOMATIC1111/stable-diffusion-webui/pull/14588), [#14900](https://github.com/AUTOMATIC1111/stable-diffusion-webui/pull/14900))
|
||||||
|
* NPU Support ([#14801](https://github.com/AUTOMATIC1111/stable-diffusion-webui/pull/14801))
|
||||||
|
* Prompt comments support
|
||||||
|
|
||||||
|
### Minor:
|
||||||
|
* Allow pasting in WIDTHxHEIGHT strings into the width/height fields ([#14296](https://github.com/AUTOMATIC1111/stable-diffusion-webui/pull/14296))
|
||||||
|
* add option: Live preview in full page image viewer ([#14230](https://github.com/AUTOMATIC1111/stable-diffusion-webui/pull/14230), [#14307](https://github.com/AUTOMATIC1111/stable-diffusion-webui/pull/14307))
|
||||||
|
* Add keyboard shortcuts for generate/skip/interrupt ([#14269](https://github.com/AUTOMATIC1111/stable-diffusion-webui/pull/14269))
|
||||||
|
* Better TCMALLOC support on different platforms ([#14227](https://github.com/AUTOMATIC1111/stable-diffusion-webui/pull/14227), [#14883](https://github.com/AUTOMATIC1111/stable-diffusion-webui/pull/14883), [#14910](https://github.com/AUTOMATIC1111/stable-diffusion-webui/pull/14910))
|
||||||
|
* Lora not found warning ([#14464](https://github.com/AUTOMATIC1111/stable-diffusion-webui/pull/14464))
|
||||||
|
* Adding negative prompts to Loras in extra networks ([#14475](https://github.com/AUTOMATIC1111/stable-diffusion-webui/pull/14475))
|
||||||
|
* xyz_grid: allow varying the seed along an axis separate from axis options ([#12180](https://github.com/AUTOMATIC1111/stable-diffusion-webui/pull/12180))
|
||||||
|
* option to convert VAE to bfloat16 (implementation of [#9295](https://github.com/AUTOMATIC1111/stable-diffusion-webui/pull/9295))
|
||||||
|
* Better IPEX support ([#14229](https://github.com/AUTOMATIC1111/stable-diffusion-webui/pull/14229), [#14353](https://github.com/AUTOMATIC1111/stable-diffusion-webui/pull/14353), [#14559](https://github.com/AUTOMATIC1111/stable-diffusion-webui/pull/14559), [#14562](https://github.com/AUTOMATIC1111/stable-diffusion-webui/pull/14562), [#14597](https://github.com/AUTOMATIC1111/stable-diffusion-webui/pull/14597))
|
||||||
|
* Option to interrupt after current generation rather than immediately ([#13653](https://github.com/AUTOMATIC1111/stable-diffusion-webui/pull/13653), [#14659](https://github.com/AUTOMATIC1111/stable-diffusion-webui/pull/14659))
|
||||||
|
* Fullscreen Preview control fading/disable ([#14291](https://github.com/AUTOMATIC1111/stable-diffusion-webui/pull/14291))
|
||||||
|
* Finer settings freezing control ([#13789](https://github.com/AUTOMATIC1111/stable-diffusion-webui/pull/13789))
|
||||||
|
* Increase Upscaler Limits ([#14589](https://github.com/AUTOMATIC1111/stable-diffusion-webui/pull/14589))
|
||||||
|
* Adjust brush size with hotkeys ([#14638](https://github.com/AUTOMATIC1111/stable-diffusion-webui/pull/14638))
|
||||||
|
* Add checkpoint info to csv log file when saving images ([#14663](https://github.com/AUTOMATIC1111/stable-diffusion-webui/pull/14663))
|
||||||
|
* Make more columns resizable ([#14740](https://github.com/AUTOMATIC1111/stable-diffusion-webui/pull/14740), [#14884](https://github.com/AUTOMATIC1111/stable-diffusion-webui/pull/14884))
|
||||||
|
* Add an option to not overlay original image for inpainting for #14727
|
||||||
|
* Add Pad conds v0 option to support same generation with DDIM as before 1.6.0
|
||||||
|
* Add "Interrupting..." placeholder.
|
||||||
|
* Button for refresh extensions list ([#14857](https://github.com/AUTOMATIC1111/stable-diffusion-webui/pull/14857))
|
||||||
|
* Add an option to disable normalization after calculating emphasis. ([#14874](https://github.com/AUTOMATIC1111/stable-diffusion-webui/pull/14874))
|
||||||
|
* When counting tokens, also include enabled styles (can be disabled in settings to revert to previous behavior)
|
||||||
|
* Configuration for the [📂] button for image gallery ([#14947](https://github.com/AUTOMATIC1111/stable-diffusion-webui/pull/14947))
|
||||||
|
* Support inference with LyCORIS BOFT networks ([#14871](https://github.com/AUTOMATIC1111/stable-diffusion-webui/pull/14871), [#14973](https://github.com/AUTOMATIC1111/stable-diffusion-webui/pull/14973))
|
||||||
|
* support resizable columns for touch (tablets) ([#15002](https://github.com/AUTOMATIC1111/stable-diffusion-webui/pull/15002))
|
||||||
|
|
||||||
|
### Extensions and API:
|
||||||
|
* Removed packages from requirements: basicsr, gfpgan, realesrgan; as well as their dependencies: absl-py, addict, beautifulsoup4, future, gdown, grpcio, importlib-metadata, lmdb, lpips, Markdown, platformdirs, PySocks, soupsieve, tb-nightly, tensorboard-data-server, tomli, Werkzeug, yapf, zipp, soupsieve
|
||||||
|
* Enable task ids for API ([#14314](https://github.com/AUTOMATIC1111/stable-diffusion-webui/pull/14314))
|
||||||
|
* add override_settings support for infotext API
|
||||||
|
* rename generation_parameters_copypaste module to infotext_utils
|
||||||
|
* prevent crash due to Script __init__ exception ([#14407](https://github.com/AUTOMATIC1111/stable-diffusion-webui/pull/14407))
|
||||||
|
* Bump numpy to 1.26.2 ([#14471](https://github.com/AUTOMATIC1111/stable-diffusion-webui/pull/14471))
|
||||||
|
* Add utility to inspect a model's dtype/device ([#14478](https://github.com/AUTOMATIC1111/stable-diffusion-webui/pull/14478))
|
||||||
|
* Implement general forward method for all method in built-in lora ext ([#14547](https://github.com/AUTOMATIC1111/stable-diffusion-webui/pull/14547))
|
||||||
|
* Execute model_loaded_callback after moving to target device ([#14563](https://github.com/AUTOMATIC1111/stable-diffusion-webui/pull/14563))
|
||||||
|
* Add self to CFGDenoiserParams ([#14573](https://github.com/AUTOMATIC1111/stable-diffusion-webui/pull/14573))
|
||||||
|
* Allow TLS with API only mode (--nowebui) ([#14593](https://github.com/AUTOMATIC1111/stable-diffusion-webui/pull/14593))
|
||||||
|
* New callback: postprocess_image_after_composite ([#14657](https://github.com/AUTOMATIC1111/stable-diffusion-webui/pull/14657))
|
||||||
|
* modules/api/api.py: add api endpoint to refresh embeddings list ([#14715](https://github.com/AUTOMATIC1111/stable-diffusion-webui/pull/14715))
|
||||||
|
* set_named_arg ([#14773](https://github.com/AUTOMATIC1111/stable-diffusion-webui/pull/14773))
|
||||||
|
* add before_token_counter callback and use it for prompt comments
|
||||||
|
* ResizeHandleRow - allow overridden column scale parameter ([#15004](https://github.com/AUTOMATIC1111/stable-diffusion-webui/pull/15004))
|
||||||
|
|
||||||
|
### Performance:
|
||||||
|
* Massive performance improvement for extra networks directories with a huge number of files in them in an attempt to tackle #14507 ([#14528](https://github.com/AUTOMATIC1111/stable-diffusion-webui/pull/14528))
|
||||||
|
* Reduce unnecessary re-indexing extra networks directory ([#14512](https://github.com/AUTOMATIC1111/stable-diffusion-webui/pull/14512))
|
||||||
|
* Avoid unnecessary `isfile`/`exists` calls ([#14527](https://github.com/AUTOMATIC1111/stable-diffusion-webui/pull/14527))
|
||||||
|
|
||||||
|
### Bug Fixes:
|
||||||
|
* fix multiple bugs related to styles multi-file support ([#14203](https://github.com/AUTOMATIC1111/stable-diffusion-webui/pull/14203), [#14276](https://github.com/AUTOMATIC1111/stable-diffusion-webui/pull/14276), [#14707](https://github.com/AUTOMATIC1111/stable-diffusion-webui/pull/14707))
|
||||||
|
* Lora fixes ([#14300](https://github.com/AUTOMATIC1111/stable-diffusion-webui/pull/14300), [#14237](https://github.com/AUTOMATIC1111/stable-diffusion-webui/pull/14237), [#14546](https://github.com/AUTOMATIC1111/stable-diffusion-webui/pull/14546), [#14726](https://github.com/AUTOMATIC1111/stable-diffusion-webui/pull/14726))
|
||||||
|
* Re-add setting lost as part of e294e46 ([#14266](https://github.com/AUTOMATIC1111/stable-diffusion-webui/pull/14266))
|
||||||
|
* fix extras caption BLIP ([#14330](https://github.com/AUTOMATIC1111/stable-diffusion-webui/pull/14330))
|
||||||
|
* include infotext into saved init image for img2img ([#14452](https://github.com/AUTOMATIC1111/stable-diffusion-webui/pull/14452))
|
||||||
|
* xyz grid handle axis_type is None ([#14394](https://github.com/AUTOMATIC1111/stable-diffusion-webui/pull/14394))
|
||||||
|
* Update Added (Fixed) IPV6 Functionality When there is No Webui Argument Passed webui.py ([#14354](https://github.com/AUTOMATIC1111/stable-diffusion-webui/pull/14354))
|
||||||
|
* fix API thread safe issues of txt2img and img2img ([#14421](https://github.com/AUTOMATIC1111/stable-diffusion-webui/pull/14421))
|
||||||
|
* handle selectable script_index is None ([#14487](https://github.com/AUTOMATIC1111/stable-diffusion-webui/pull/14487))
|
||||||
|
* handle config.json failed to load ([#14525](https://github.com/AUTOMATIC1111/stable-diffusion-webui/pull/14525), [#14767](https://github.com/AUTOMATIC1111/stable-diffusion-webui/pull/14767))
|
||||||
|
* paste infotext cast int as float ([#14523](https://github.com/AUTOMATIC1111/stable-diffusion-webui/pull/14523))
|
||||||
|
* Ensure GRADIO_ANALYTICS_ENABLED is set early enough ([#14537](https://github.com/AUTOMATIC1111/stable-diffusion-webui/pull/14537))
|
||||||
|
* Fix logging configuration again ([#14538](https://github.com/AUTOMATIC1111/stable-diffusion-webui/pull/14538))
|
||||||
|
* Handle CondFunc exception when resolving attributes ([#14560](https://github.com/AUTOMATIC1111/stable-diffusion-webui/pull/14560))
|
||||||
|
* Fix extras big batch crashes ([#14699](https://github.com/AUTOMATIC1111/stable-diffusion-webui/pull/14699))
|
||||||
|
* Fix using wrong model caused by alias ([#14655](https://github.com/AUTOMATIC1111/stable-diffusion-webui/pull/14655))
|
||||||
|
* Add # to the invalid_filename_chars list ([#14640](https://github.com/AUTOMATIC1111/stable-diffusion-webui/pull/14640))
|
||||||
|
* Fix extension check for requirements ([#14639](https://github.com/AUTOMATIC1111/stable-diffusion-webui/pull/14639))
|
||||||
|
* Fix tab indexes are reset after restart UI ([#14637](https://github.com/AUTOMATIC1111/stable-diffusion-webui/pull/14637))
|
||||||
|
* Fix nested manual cast ([#14689](https://github.com/AUTOMATIC1111/stable-diffusion-webui/pull/14689))
|
||||||
|
* Keep postprocessing upscale selected tab after restart ([#14702](https://github.com/AUTOMATIC1111/stable-diffusion-webui/pull/14702))
|
||||||
|
* XYZ grid: filter out blank vals when axis is int or float type (like int axis seed) ([#14754](https://github.com/AUTOMATIC1111/stable-diffusion-webui/pull/14754))
|
||||||
|
* fix CLIP Interrogator topN regex ([#14775](https://github.com/AUTOMATIC1111/stable-diffusion-webui/pull/14775))
|
||||||
|
* Fix dtype error in MHA layer/change dtype checking mechanism for manual cast ([#14791](https://github.com/AUTOMATIC1111/stable-diffusion-webui/pull/14791))
|
||||||
|
* catch load style.csv error ([#14814](https://github.com/AUTOMATIC1111/stable-diffusion-webui/pull/14814))
|
||||||
|
* fix error when editing extra networks card
|
||||||
|
* fix extra networks metadata failing to work properly when you create the .json file with metadata for the first time.
|
||||||
|
* util.walk_files extensions case insensitive ([#14879](https://github.com/AUTOMATIC1111/stable-diffusion-webui/pull/14879))
|
||||||
|
* if extensions page not loaded, prevent apply ([#14873](https://github.com/AUTOMATIC1111/stable-diffusion-webui/pull/14873))
|
||||||
|
* call the right function for token counter in img2img
|
||||||
|
* Fix the bugs that search/reload will disappear when using other ExtraNetworks extensions ([#14939](https://github.com/AUTOMATIC1111/stable-diffusion-webui/pull/14939))
|
||||||
|
* Gracefully handle mtime read exception from cache ([#14933](https://github.com/AUTOMATIC1111/stable-diffusion-webui/pull/14933))
|
||||||
|
* Only trigger interrupt on `Esc` when interrupt button visible ([#14932](https://github.com/AUTOMATIC1111/stable-diffusion-webui/pull/14932))
|
||||||
|
* Disable prompt token counters option actually disables token counting rather than just hiding results.
|
||||||
|
* avoid double upscaling in inpaint ([#14966](https://github.com/AUTOMATIC1111/stable-diffusion-webui/pull/14966))
|
||||||
|
* Fix #14591 using translated content to do categories mapping ([#14995](https://github.com/AUTOMATIC1111/stable-diffusion-webui/pull/14995))
|
||||||
|
* fix: the `split_threshold` parameter does not work when running Split oversized images ([#15006](https://github.com/AUTOMATIC1111/stable-diffusion-webui/pull/15006))
|
||||||
|
* Fix resize-handle for mobile ([#15010](https://github.com/AUTOMATIC1111/stable-diffusion-webui/pull/15010), [#15065](https://github.com/AUTOMATIC1111/stable-diffusion-webui/pull/15065))
|
||||||
|
|
||||||
|
### Other:
|
||||||
|
* Assign id for "extra_options". Replace numeric field with slider. ([#14270](https://github.com/AUTOMATIC1111/stable-diffusion-webui/pull/14270))
|
||||||
|
* change state dict comparison to ref compare ([#14216](https://github.com/AUTOMATIC1111/stable-diffusion-webui/pull/14216))
|
||||||
|
* Bump torch-rocm to 5.6/5.7 ([#14293](https://github.com/AUTOMATIC1111/stable-diffusion-webui/pull/14293))
|
||||||
|
* Base output path off data path ([#14446](https://github.com/AUTOMATIC1111/stable-diffusion-webui/pull/14446))
|
||||||
|
* reorder training preprocessing modules in extras tab ([#14367](https://github.com/AUTOMATIC1111/stable-diffusion-webui/pull/14367))
|
||||||
|
* Remove `cleanup_models` code ([#14472](https://github.com/AUTOMATIC1111/stable-diffusion-webui/pull/14472))
|
||||||
|
* only rewrite ui-config when there is change ([#14352](https://github.com/AUTOMATIC1111/stable-diffusion-webui/pull/14352))
|
||||||
|
* Fix lint issue from 501993eb ([#14495](https://github.com/AUTOMATIC1111/stable-diffusion-webui/pull/14495))
|
||||||
|
* Update README.md ([#14548](https://github.com/AUTOMATIC1111/stable-diffusion-webui/pull/14548))
|
||||||
|
* hires button, fix seeds ()
|
||||||
|
* Logging: set formatter correctly for fallback logger too ([#14618](https://github.com/AUTOMATIC1111/stable-diffusion-webui/pull/14618))
|
||||||
|
* Read generation info from infotexts rather than json for internal needs (save, extract seed from generated pic) ([#14645](https://github.com/AUTOMATIC1111/stable-diffusion-webui/pull/14645))
|
||||||
|
* improve get_crop_region ([#14709](https://github.com/AUTOMATIC1111/stable-diffusion-webui/pull/14709))
|
||||||
|
* Bump safetensors' version to 0.4.2 ([#14782](https://github.com/AUTOMATIC1111/stable-diffusion-webui/pull/14782))
|
||||||
|
* add tooltip create_submit_box ([#14803](https://github.com/AUTOMATIC1111/stable-diffusion-webui/pull/14803))
|
||||||
|
* extensions tab table row hover highlight ([#14885](https://github.com/AUTOMATIC1111/stable-diffusion-webui/pull/14885))
|
||||||
|
* Always add timestamp to displayed image ([#14890](https://github.com/AUTOMATIC1111/stable-diffusion-webui/pull/14890))
|
||||||
|
* Added core.filemode=false so doesn't track changes in file permission… ([#14930](https://github.com/AUTOMATIC1111/stable-diffusion-webui/pull/14930))
|
||||||
|
* Normalize command-line argument paths ([#14934](https://github.com/AUTOMATIC1111/stable-diffusion-webui/pull/14934), [#15035](https://github.com/AUTOMATIC1111/stable-diffusion-webui/pull/15035))
|
||||||
|
* Use original App Title in progress bar ([#14916](https://github.com/AUTOMATIC1111/stable-diffusion-webui/pull/14916))
|
||||||
|
* register_tmp_file also for mtime ([#15012](https://github.com/AUTOMATIC1111/stable-diffusion-webui/pull/15012))
|
||||||
|
|
||||||
## 1.7.0
|
## 1.7.0
|
||||||
|
|
||||||
### Features:
|
### Features:
|
||||||
@@ -40,7 +450,8 @@
|
|||||||
* infotext updates: add option to disregard certain infotext fields, add option to not include VAE in infotext, add explanation to infotext settings page, move some options to infotext settings page
|
* infotext updates: add option to disregard certain infotext fields, add option to not include VAE in infotext, add explanation to infotext settings page, move some options to infotext settings page
|
||||||
* add FP32 fallback support on sd_vae_approx ([#14046](https://github.com/AUTOMATIC1111/stable-diffusion-webui/pull/14046))
|
* add FP32 fallback support on sd_vae_approx ([#14046](https://github.com/AUTOMATIC1111/stable-diffusion-webui/pull/14046))
|
||||||
* support XYZ scripts / split hires path from unet ([#14126](https://github.com/AUTOMATIC1111/stable-diffusion-webui/pull/14126))
|
* support XYZ scripts / split hires path from unet ([#14126](https://github.com/AUTOMATIC1111/stable-diffusion-webui/pull/14126))
|
||||||
* allow use of mutiple styles csv files ([#14125](https://github.com/AUTOMATIC1111/stable-diffusion-webui/pull/14125))
|
* allow use of multiple styles csv files ([#14125](https://github.com/AUTOMATIC1111/stable-diffusion-webui/pull/14125))
|
||||||
|
* make extra network card description plaintext by default, with an option (Treat card description as HTML) to re-enable HTML as it was (originally by [#13241](https://github.com/AUTOMATIC1111/stable-diffusion-webui/pull/13241))
|
||||||
|
|
||||||
### Extensions and API:
|
### Extensions and API:
|
||||||
* update gradio to 3.41.2
|
* update gradio to 3.41.2
|
||||||
@@ -176,7 +587,7 @@
|
|||||||
* new samplers: Restart, DPM++ 2M SDE Exponential, DPM++ 2M SDE Heun, DPM++ 2M SDE Heun Karras, DPM++ 2M SDE Heun Exponential, DPM++ 3M SDE, DPM++ 3M SDE Karras, DPM++ 3M SDE Exponential ([#12300](https://github.com/AUTOMATIC1111/stable-diffusion-webui/pull/12300), [#12519](https://github.com/AUTOMATIC1111/stable-diffusion-webui/pull/12519), [#12542](https://github.com/AUTOMATIC1111/stable-diffusion-webui/pull/12542))
|
* new samplers: Restart, DPM++ 2M SDE Exponential, DPM++ 2M SDE Heun, DPM++ 2M SDE Heun Karras, DPM++ 2M SDE Heun Exponential, DPM++ 3M SDE, DPM++ 3M SDE Karras, DPM++ 3M SDE Exponential ([#12300](https://github.com/AUTOMATIC1111/stable-diffusion-webui/pull/12300), [#12519](https://github.com/AUTOMATIC1111/stable-diffusion-webui/pull/12519), [#12542](https://github.com/AUTOMATIC1111/stable-diffusion-webui/pull/12542))
|
||||||
* rework DDIM, PLMS, UniPC to use CFG denoiser same as in k-diffusion samplers:
|
* rework DDIM, PLMS, UniPC to use CFG denoiser same as in k-diffusion samplers:
|
||||||
* makes all of them work with img2img
|
* makes all of them work with img2img
|
||||||
* makes prompt composition posssible (AND)
|
* makes prompt composition possible (AND)
|
||||||
* makes them available for SDXL
|
* makes them available for SDXL
|
||||||
* always show extra networks tabs in the UI ([#11808](https://github.com/AUTOMATIC1111/stable-diffusion-webui/pull/11808))
|
* always show extra networks tabs in the UI ([#11808](https://github.com/AUTOMATIC1111/stable-diffusion-webui/pull/11808))
|
||||||
* use less RAM when creating models ([#11958](https://github.com/AUTOMATIC1111/stable-diffusion-webui/pull/11958), [#12599](https://github.com/AUTOMATIC1111/stable-diffusion-webui/pull/12599))
|
* use less RAM when creating models ([#11958](https://github.com/AUTOMATIC1111/stable-diffusion-webui/pull/11958), [#12599](https://github.com/AUTOMATIC1111/stable-diffusion-webui/pull/12599))
|
||||||
@@ -352,7 +763,7 @@
|
|||||||
* user metadata system for custom networks
|
* user metadata system for custom networks
|
||||||
* extended Lora metadata editor: set activation text, default weight, view tags, training info
|
* extended Lora metadata editor: set activation text, default weight, view tags, training info
|
||||||
* Lora extension rework to include other types of networks (all that were previously handled by LyCORIS extension)
|
* Lora extension rework to include other types of networks (all that were previously handled by LyCORIS extension)
|
||||||
* show github stars for extenstions
|
* show github stars for extensions
|
||||||
* img2img batch mode can read extra stuff from png info
|
* img2img batch mode can read extra stuff from png info
|
||||||
* img2img batch works with subdirectories
|
* img2img batch works with subdirectories
|
||||||
* hotkeys to move prompt elements: alt+left/right
|
* hotkeys to move prompt elements: alt+left/right
|
||||||
@@ -571,7 +982,7 @@
|
|||||||
* do not wait for Stable Diffusion model to load at startup
|
* do not wait for Stable Diffusion model to load at startup
|
||||||
* add filename patterns: `[denoising]`
|
* add filename patterns: `[denoising]`
|
||||||
* directory hiding for extra networks: dirs starting with `.` will hide their cards on extra network tabs unless specifically searched for
|
* directory hiding for extra networks: dirs starting with `.` will hide their cards on extra network tabs unless specifically searched for
|
||||||
* LoRA: for the `<...>` text in prompt, use name of LoRA that is in the metdata of the file, if present, instead of filename (both can be used to activate LoRA)
|
* LoRA: for the `<...>` text in prompt, use name of LoRA that is in the metadata of the file, if present, instead of filename (both can be used to activate LoRA)
|
||||||
* LoRA: read infotext params from kohya-ss's extension parameters if they are present and if his extension is not active
|
* LoRA: read infotext params from kohya-ss's extension parameters if they are present and if his extension is not active
|
||||||
* LoRA: fix some LoRAs not working (ones that have 3x3 convolution layer)
|
* LoRA: fix some LoRAs not working (ones that have 3x3 convolution layer)
|
||||||
* LoRA: add an option to use old method of applying LoRAs (producing same results as with kohya-ss)
|
* LoRA: add an option to use old method of applying LoRAs (producing same results as with kohya-ss)
|
||||||
@@ -601,7 +1012,7 @@
|
|||||||
* fix gamepad navigation
|
* fix gamepad navigation
|
||||||
* make the lightbox fullscreen image function properly
|
* make the lightbox fullscreen image function properly
|
||||||
* fix squished thumbnails in extras tab
|
* fix squished thumbnails in extras tab
|
||||||
* keep "search" filter for extra networks when user refreshes the tab (previously it showed everthing after you refreshed)
|
* keep "search" filter for extra networks when user refreshes the tab (previously it showed everything after you refreshed)
|
||||||
* fix webui showing the same image if you configure the generation to always save results into same file
|
* fix webui showing the same image if you configure the generation to always save results into same file
|
||||||
* fix bug with upscalers not working properly
|
* fix bug with upscalers not working properly
|
||||||
* fix MPS on PyTorch 2.0.1, Intel Macs
|
* fix MPS on PyTorch 2.0.1, Intel Macs
|
||||||
@@ -619,7 +1030,7 @@
|
|||||||
* switch to PyTorch 2.0.0 (except for AMD GPUs)
|
* switch to PyTorch 2.0.0 (except for AMD GPUs)
|
||||||
* visual improvements to custom code scripts
|
* visual improvements to custom code scripts
|
||||||
* add filename patterns: `[clip_skip]`, `[hasprompt<>]`, `[batch_number]`, `[generation_number]`
|
* add filename patterns: `[clip_skip]`, `[hasprompt<>]`, `[batch_number]`, `[generation_number]`
|
||||||
* add support for saving init images in img2img, and record their hashes in infotext for reproducability
|
* add support for saving init images in img2img, and record their hashes in infotext for reproducibility
|
||||||
* automatically select current word when adjusting weight with ctrl+up/down
|
* automatically select current word when adjusting weight with ctrl+up/down
|
||||||
* add dropdowns for X/Y/Z plot
|
* add dropdowns for X/Y/Z plot
|
||||||
* add setting: Stable Diffusion/Random number generator source: makes it possible to make images generated from a given manual seed consistent across different GPUs
|
* add setting: Stable Diffusion/Random number generator source: makes it possible to make images generated from a given manual seed consistent across different GPUs
|
||||||
|
|||||||
+1
-12
@@ -1,12 +1 @@
|
|||||||
* @AUTOMATIC1111
|
* @AUTOMATIC1111 @w-e-w @catboxanon
|
||||||
|
|
||||||
# if you were managing a localization and were removed from this file, this is because
|
|
||||||
# the intended way to do localizations now is via extensions. See:
|
|
||||||
# https://github.com/AUTOMATIC1111/stable-diffusion-webui/wiki/Developing-extensions
|
|
||||||
# Make a repo with your localization and since you are still listed as a collaborator
|
|
||||||
# you can add it to the wiki page yourself. This change is because some people complained
|
|
||||||
# the git commit log is cluttered with things unrelated to almost everyone and
|
|
||||||
# because I believe this is the best overall for the project to handle localizations almost
|
|
||||||
# entirely without my oversight.
|
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@@ -98,6 +98,7 @@ Make sure the required [dependencies](https://github.com/AUTOMATIC1111/stable-di
|
|||||||
- [NVidia](https://github.com/AUTOMATIC1111/stable-diffusion-webui/wiki/Install-and-Run-on-NVidia-GPUs) (recommended)
|
- [NVidia](https://github.com/AUTOMATIC1111/stable-diffusion-webui/wiki/Install-and-Run-on-NVidia-GPUs) (recommended)
|
||||||
- [AMD](https://github.com/AUTOMATIC1111/stable-diffusion-webui/wiki/Install-and-Run-on-AMD-GPUs) GPUs.
|
- [AMD](https://github.com/AUTOMATIC1111/stable-diffusion-webui/wiki/Install-and-Run-on-AMD-GPUs) GPUs.
|
||||||
- [Intel CPUs, Intel GPUs (both integrated and discrete)](https://github.com/openvinotoolkit/stable-diffusion-webui/wiki/Installation-on-Intel-Silicon) (external wiki page)
|
- [Intel CPUs, Intel GPUs (both integrated and discrete)](https://github.com/openvinotoolkit/stable-diffusion-webui/wiki/Installation-on-Intel-Silicon) (external wiki page)
|
||||||
|
- [Ascend NPUs](https://github.com/wangshuai09/stable-diffusion-webui/wiki/Install-and-run-on-Ascend-NPUs) (external wiki page)
|
||||||
|
|
||||||
Alternatively, use online services (like Google Colab):
|
Alternatively, use online services (like Google Colab):
|
||||||
|
|
||||||
@@ -127,10 +128,33 @@ sudo zypper install wget git python3 libtcmalloc4 libglvnd
|
|||||||
# Arch-based:
|
# Arch-based:
|
||||||
sudo pacman -S wget git python3
|
sudo pacman -S wget git python3
|
||||||
```
|
```
|
||||||
|
If your system is very new, you need to install python3.11 or python3.10:
|
||||||
|
```bash
|
||||||
|
# Ubuntu 24.04
|
||||||
|
sudo add-apt-repository ppa:deadsnakes/ppa
|
||||||
|
sudo apt update
|
||||||
|
sudo apt install python3.11 python3.11-venv
|
||||||
|
|
||||||
|
# Manjaro/Arch
|
||||||
|
sudo pacman -S yay
|
||||||
|
yay -S python311 # do not confuse with python3.11 package
|
||||||
|
|
||||||
|
# Only for 3.11
|
||||||
|
# Then set up env variable in launch script
|
||||||
|
export python_cmd="python3.11"
|
||||||
|
# or in webui-user.sh
|
||||||
|
python_cmd="python3.11"
|
||||||
|
```
|
||||||
2. Navigate to the directory you would like the webui to be installed and execute the following command:
|
2. Navigate to the directory you would like the webui to be installed and execute the following command:
|
||||||
```bash
|
```bash
|
||||||
wget -q https://raw.githubusercontent.com/AUTOMATIC1111/stable-diffusion-webui/master/webui.sh
|
wget -q https://raw.githubusercontent.com/AUTOMATIC1111/stable-diffusion-webui/master/webui.sh
|
||||||
|
chmod +x webui.sh
|
||||||
```
|
```
|
||||||
|
Or just clone the repo wherever you want:
|
||||||
|
```bash
|
||||||
|
git clone https://github.com/AUTOMATIC1111/stable-diffusion-webui
|
||||||
|
```
|
||||||
|
|
||||||
3. Run `webui.sh`.
|
3. Run `webui.sh`.
|
||||||
4. Check `webui-user.sh` for options.
|
4. Check `webui-user.sh` for options.
|
||||||
### Installation on Apple Silicon
|
### Installation on Apple Silicon
|
||||||
@@ -149,7 +173,7 @@ For the purposes of getting Google and other search engines to crawl the wiki, h
|
|||||||
## Credits
|
## Credits
|
||||||
Licenses for borrowed code can be found in `Settings -> Licenses` screen, and also in `html/licenses.html` file.
|
Licenses for borrowed code can be found in `Settings -> Licenses` screen, and also in `html/licenses.html` file.
|
||||||
|
|
||||||
- Stable Diffusion - https://github.com/Stability-AI/stablediffusion, https://github.com/CompVis/taming-transformers
|
- Stable Diffusion - https://github.com/Stability-AI/stablediffusion, https://github.com/CompVis/taming-transformers, https://github.com/mcmonkey4eva/sd3-ref
|
||||||
- k-diffusion - https://github.com/crowsonkb/k-diffusion.git
|
- k-diffusion - https://github.com/crowsonkb/k-diffusion.git
|
||||||
- Spandrel - https://github.com/chaiNNer-org/spandrel implementing
|
- Spandrel - https://github.com/chaiNNer-org/spandrel implementing
|
||||||
- GFPGAN - https://github.com/TencentARC/GFPGAN.git
|
- GFPGAN - https://github.com/TencentARC/GFPGAN.git
|
||||||
|
|||||||
@@ -0,0 +1,5 @@
|
|||||||
|
[default.extend-words]
|
||||||
|
# Part of "RGBa" (Pillow's pre-multiplied alpha RGB mode)
|
||||||
|
Ba = "Ba"
|
||||||
|
# HSA is something AMD uses for their GPUs
|
||||||
|
HSA = "HSA"
|
||||||
@@ -40,7 +40,7 @@ model:
|
|||||||
use_spatial_transformer: True
|
use_spatial_transformer: True
|
||||||
transformer_depth: 1
|
transformer_depth: 1
|
||||||
context_dim: 768
|
context_dim: 768
|
||||||
use_checkpoint: True
|
use_checkpoint: False
|
||||||
legacy: False
|
legacy: False
|
||||||
|
|
||||||
first_stage_config:
|
first_stage_config:
|
||||||
|
|||||||
@@ -41,7 +41,7 @@ model:
|
|||||||
use_linear_in_transformer: True
|
use_linear_in_transformer: True
|
||||||
transformer_depth: 1
|
transformer_depth: 1
|
||||||
context_dim: 1024
|
context_dim: 1024
|
||||||
use_checkpoint: True
|
use_checkpoint: False
|
||||||
legacy: False
|
legacy: False
|
||||||
|
|
||||||
first_stage_config:
|
first_stage_config:
|
||||||
|
|||||||
@@ -45,7 +45,7 @@ model:
|
|||||||
use_spatial_transformer: True
|
use_spatial_transformer: True
|
||||||
transformer_depth: 1
|
transformer_depth: 1
|
||||||
context_dim: 768
|
context_dim: 768
|
||||||
use_checkpoint: True
|
use_checkpoint: False
|
||||||
legacy: False
|
legacy: False
|
||||||
|
|
||||||
first_stage_config:
|
first_stage_config:
|
||||||
|
|||||||
@@ -0,0 +1,5 @@
|
|||||||
|
model:
|
||||||
|
target: modules.models.sd3.sd3_model.SD3Inferencer
|
||||||
|
params:
|
||||||
|
shift: 3
|
||||||
|
state_dict: null
|
||||||
@@ -21,7 +21,7 @@ model:
|
|||||||
params:
|
params:
|
||||||
adm_in_channels: 2816
|
adm_in_channels: 2816
|
||||||
num_classes: sequential
|
num_classes: sequential
|
||||||
use_checkpoint: True
|
use_checkpoint: False
|
||||||
in_channels: 9
|
in_channels: 9
|
||||||
out_channels: 4
|
out_channels: 4
|
||||||
model_channels: 320
|
model_channels: 320
|
||||||
|
|||||||
@@ -0,0 +1,98 @@
|
|||||||
|
model:
|
||||||
|
target: sgm.models.diffusion.DiffusionEngine
|
||||||
|
params:
|
||||||
|
scale_factor: 0.13025
|
||||||
|
disable_first_stage_autocast: True
|
||||||
|
|
||||||
|
denoiser_config:
|
||||||
|
target: sgm.modules.diffusionmodules.denoiser.DiscreteDenoiser
|
||||||
|
params:
|
||||||
|
num_idx: 1000
|
||||||
|
|
||||||
|
weighting_config:
|
||||||
|
target: sgm.modules.diffusionmodules.denoiser_weighting.VWeighting
|
||||||
|
scaling_config:
|
||||||
|
target: sgm.modules.diffusionmodules.denoiser_scaling.VScaling
|
||||||
|
discretization_config:
|
||||||
|
target: sgm.modules.diffusionmodules.discretizer.LegacyDDPMDiscretization
|
||||||
|
|
||||||
|
network_config:
|
||||||
|
target: sgm.modules.diffusionmodules.openaimodel.UNetModel
|
||||||
|
params:
|
||||||
|
adm_in_channels: 2816
|
||||||
|
num_classes: sequential
|
||||||
|
use_checkpoint: False
|
||||||
|
in_channels: 4
|
||||||
|
out_channels: 4
|
||||||
|
model_channels: 320
|
||||||
|
attention_resolutions: [4, 2]
|
||||||
|
num_res_blocks: 2
|
||||||
|
channel_mult: [1, 2, 4]
|
||||||
|
num_head_channels: 64
|
||||||
|
use_spatial_transformer: True
|
||||||
|
use_linear_in_transformer: True
|
||||||
|
transformer_depth: [1, 2, 10] # note: the first is unused (due to attn_res starting at 2) 32, 16, 8 --> 64, 32, 16
|
||||||
|
context_dim: 2048
|
||||||
|
spatial_transformer_attn_type: softmax-xformers
|
||||||
|
legacy: False
|
||||||
|
|
||||||
|
conditioner_config:
|
||||||
|
target: sgm.modules.GeneralConditioner
|
||||||
|
params:
|
||||||
|
emb_models:
|
||||||
|
# crossattn cond
|
||||||
|
- is_trainable: False
|
||||||
|
input_key: txt
|
||||||
|
target: sgm.modules.encoders.modules.FrozenCLIPEmbedder
|
||||||
|
params:
|
||||||
|
layer: hidden
|
||||||
|
layer_idx: 11
|
||||||
|
# crossattn and vector cond
|
||||||
|
- is_trainable: False
|
||||||
|
input_key: txt
|
||||||
|
target: sgm.modules.encoders.modules.FrozenOpenCLIPEmbedder2
|
||||||
|
params:
|
||||||
|
arch: ViT-bigG-14
|
||||||
|
version: laion2b_s39b_b160k
|
||||||
|
freeze: True
|
||||||
|
layer: penultimate
|
||||||
|
always_return_pooled: True
|
||||||
|
legacy: False
|
||||||
|
# vector cond
|
||||||
|
- is_trainable: False
|
||||||
|
input_key: original_size_as_tuple
|
||||||
|
target: sgm.modules.encoders.modules.ConcatTimestepEmbedderND
|
||||||
|
params:
|
||||||
|
outdim: 256 # multiplied by two
|
||||||
|
# vector cond
|
||||||
|
- is_trainable: False
|
||||||
|
input_key: crop_coords_top_left
|
||||||
|
target: sgm.modules.encoders.modules.ConcatTimestepEmbedderND
|
||||||
|
params:
|
||||||
|
outdim: 256 # multiplied by two
|
||||||
|
# vector cond
|
||||||
|
- is_trainable: False
|
||||||
|
input_key: target_size_as_tuple
|
||||||
|
target: sgm.modules.encoders.modules.ConcatTimestepEmbedderND
|
||||||
|
params:
|
||||||
|
outdim: 256 # multiplied by two
|
||||||
|
|
||||||
|
first_stage_config:
|
||||||
|
target: sgm.models.autoencoder.AutoencoderKLInferenceWrapper
|
||||||
|
params:
|
||||||
|
embed_dim: 4
|
||||||
|
monitor: val/rec_loss
|
||||||
|
ddconfig:
|
||||||
|
attn_type: vanilla-xformers
|
||||||
|
double_z: true
|
||||||
|
z_channels: 4
|
||||||
|
resolution: 256
|
||||||
|
in_channels: 3
|
||||||
|
out_ch: 3
|
||||||
|
ch: 128
|
||||||
|
ch_mult: [1, 2, 4, 4]
|
||||||
|
num_res_blocks: 2
|
||||||
|
attn_resolutions: []
|
||||||
|
dropout: 0.0
|
||||||
|
lossconfig:
|
||||||
|
target: torch.nn.Identity
|
||||||
@@ -40,7 +40,7 @@ model:
|
|||||||
use_spatial_transformer: True
|
use_spatial_transformer: True
|
||||||
transformer_depth: 1
|
transformer_depth: 1
|
||||||
context_dim: 768
|
context_dim: 768
|
||||||
use_checkpoint: True
|
use_checkpoint: False
|
||||||
legacy: False
|
legacy: False
|
||||||
|
|
||||||
first_stage_config:
|
first_stage_config:
|
||||||
|
|||||||
@@ -40,7 +40,7 @@ model:
|
|||||||
use_spatial_transformer: True
|
use_spatial_transformer: True
|
||||||
transformer_depth: 1
|
transformer_depth: 1
|
||||||
context_dim: 768
|
context_dim: 768
|
||||||
use_checkpoint: True
|
use_checkpoint: False
|
||||||
legacy: False
|
legacy: False
|
||||||
|
|
||||||
first_stage_config:
|
first_stage_config:
|
||||||
|
|||||||
@@ -301,7 +301,7 @@ class DDPMV1(pl.LightningModule):
|
|||||||
elif self.parameterization == "x0":
|
elif self.parameterization == "x0":
|
||||||
target = x_start
|
target = x_start
|
||||||
else:
|
else:
|
||||||
raise NotImplementedError(f"Paramterization {self.parameterization} not yet supported")
|
raise NotImplementedError(f"Parameterization {self.parameterization} not yet supported")
|
||||||
|
|
||||||
loss = self.get_loss(model_out, target, mean=False).mean(dim=[1, 2, 3])
|
loss = self.get_loss(model_out, target, mean=False).mean(dim=[1, 2, 3])
|
||||||
|
|
||||||
@@ -572,7 +572,7 @@ class LatentDiffusionV1(DDPMV1):
|
|||||||
:param h: height
|
:param h: height
|
||||||
:param w: width
|
:param w: width
|
||||||
:return: normalized distance to image border,
|
:return: normalized distance to image border,
|
||||||
wtith min distance = 0 at border and max dist = 0.5 at image center
|
with min distance = 0 at border and max dist = 0.5 at image center
|
||||||
"""
|
"""
|
||||||
lower_right_corner = torch.tensor([h - 1, w - 1]).view(1, 1, 2)
|
lower_right_corner = torch.tensor([h - 1, w - 1]).view(1, 1, 2)
|
||||||
arr = self.meshgrid(h, w) / lower_right_corner
|
arr = self.meshgrid(h, w) / lower_right_corner
|
||||||
@@ -880,7 +880,7 @@ class LatentDiffusionV1(DDPMV1):
|
|||||||
def apply_model(self, x_noisy, t, cond, return_ids=False):
|
def apply_model(self, x_noisy, t, cond, return_ids=False):
|
||||||
|
|
||||||
if isinstance(cond, dict):
|
if isinstance(cond, dict):
|
||||||
# hybrid case, cond is exptected to be a dict
|
# hybrid case, cond is expected to be a dict
|
||||||
pass
|
pass
|
||||||
else:
|
else:
|
||||||
if not isinstance(cond, list):
|
if not isinstance(cond, list):
|
||||||
@@ -916,7 +916,7 @@ class LatentDiffusionV1(DDPMV1):
|
|||||||
cond_list = [{c_key: [c[:, :, :, :, i]]} for i in range(c.shape[-1])]
|
cond_list = [{c_key: [c[:, :, :, :, i]]} for i in range(c.shape[-1])]
|
||||||
|
|
||||||
elif self.cond_stage_key == 'coordinates_bbox':
|
elif self.cond_stage_key == 'coordinates_bbox':
|
||||||
assert 'original_image_size' in self.split_input_params, 'BoudingBoxRescaling is missing original_image_size'
|
assert 'original_image_size' in self.split_input_params, 'BoundingBoxRescaling is missing original_image_size'
|
||||||
|
|
||||||
# assuming padding of unfold is always 0 and its dilation is always 1
|
# assuming padding of unfold is always 0 and its dilation is always 1
|
||||||
n_patches_per_row = int((w - ks[0]) / stride[0] + 1)
|
n_patches_per_row = int((w - ks[0]) / stride[0] + 1)
|
||||||
@@ -926,7 +926,7 @@ class LatentDiffusionV1(DDPMV1):
|
|||||||
num_downs = self.first_stage_model.encoder.num_resolutions - 1
|
num_downs = self.first_stage_model.encoder.num_resolutions - 1
|
||||||
rescale_latent = 2 ** (num_downs)
|
rescale_latent = 2 ** (num_downs)
|
||||||
|
|
||||||
# get top left postions of patches as conforming for the bbbox tokenizer, therefore we
|
# get top left positions of patches as conforming for the bbbox tokenizer, therefore we
|
||||||
# need to rescale the tl patch coordinates to be in between (0,1)
|
# need to rescale the tl patch coordinates to be in between (0,1)
|
||||||
tl_patch_coordinates = [(rescale_latent * stride[0] * (patch_nr % n_patches_per_row) / full_img_w,
|
tl_patch_coordinates = [(rescale_latent * stride[0] * (patch_nr % n_patches_per_row) / full_img_w,
|
||||||
rescale_latent * stride[1] * (patch_nr // n_patches_per_row) / full_img_h)
|
rescale_latent * stride[1] * (patch_nr // n_patches_per_row) / full_img_h)
|
||||||
|
|||||||
@@ -9,6 +9,8 @@ class ExtraNetworkLora(extra_networks.ExtraNetwork):
|
|||||||
self.errors = {}
|
self.errors = {}
|
||||||
"""mapping of network names to the number of errors the network had during operation"""
|
"""mapping of network names to the number of errors the network had during operation"""
|
||||||
|
|
||||||
|
remove_symbols = str.maketrans('', '', ":,")
|
||||||
|
|
||||||
def activate(self, p, params_list):
|
def activate(self, p, params_list):
|
||||||
additional = shared.opts.sd_lora
|
additional = shared.opts.sd_lora
|
||||||
|
|
||||||
@@ -43,22 +45,15 @@ class ExtraNetworkLora(extra_networks.ExtraNetwork):
|
|||||||
networks.load_networks(names, te_multipliers, unet_multipliers, dyn_dims)
|
networks.load_networks(names, te_multipliers, unet_multipliers, dyn_dims)
|
||||||
|
|
||||||
if shared.opts.lora_add_hashes_to_infotext:
|
if shared.opts.lora_add_hashes_to_infotext:
|
||||||
network_hashes = []
|
if not getattr(p, "is_hr_pass", False) or not hasattr(p, "lora_hashes"):
|
||||||
|
p.lora_hashes = {}
|
||||||
|
|
||||||
for item in networks.loaded_networks:
|
for item in networks.loaded_networks:
|
||||||
shorthash = item.network_on_disk.shorthash
|
if item.network_on_disk.shorthash and item.mentioned_name:
|
||||||
if not shorthash:
|
p.lora_hashes[item.mentioned_name.translate(self.remove_symbols)] = item.network_on_disk.shorthash
|
||||||
continue
|
|
||||||
|
|
||||||
alias = item.mentioned_name
|
if p.lora_hashes:
|
||||||
if not alias:
|
p.extra_generation_params["Lora hashes"] = ', '.join(f'{k}: {v}' for k, v in p.lora_hashes.items())
|
||||||
continue
|
|
||||||
|
|
||||||
alias = alias.replace(":", "").replace(",", "")
|
|
||||||
|
|
||||||
network_hashes.append(f"{alias}: {shorthash}")
|
|
||||||
|
|
||||||
if network_hashes:
|
|
||||||
p.extra_generation_params["Lora hashes"] = ", ".join(network_hashes)
|
|
||||||
|
|
||||||
def deactivate(self, p):
|
def deactivate(self, p):
|
||||||
if self.errors:
|
if self.errors:
|
||||||
|
|||||||
@@ -30,7 +30,7 @@ def factorization(dimension: int, factor:int=-1) -> tuple[int, int]:
|
|||||||
In LoRA with Kroneckor Product, first value is a value for weight scale.
|
In LoRA with Kroneckor Product, first value is a value for weight scale.
|
||||||
secon value is a value for weight.
|
secon value is a value for weight.
|
||||||
|
|
||||||
Becuase of non-commutative property, A⊗B ≠ B⊗A. Meaning of two matrices is slightly different.
|
Because of non-commutative property, A⊗B ≠ B⊗A. Meaning of two matrices is slightly different.
|
||||||
|
|
||||||
examples)
|
examples)
|
||||||
factor
|
factor
|
||||||
|
|||||||
@@ -7,6 +7,7 @@ import torch.nn as nn
|
|||||||
import torch.nn.functional as F
|
import torch.nn.functional as F
|
||||||
|
|
||||||
from modules import sd_models, cache, errors, hashes, shared
|
from modules import sd_models, cache, errors, hashes, shared
|
||||||
|
import modules.models.sd3.mmdit
|
||||||
|
|
||||||
NetworkWeights = namedtuple('NetworkWeights', ['network_key', 'sd_key', 'w', 'sd_module'])
|
NetworkWeights = namedtuple('NetworkWeights', ['network_key', 'sd_key', 'w', 'sd_module'])
|
||||||
|
|
||||||
@@ -29,7 +30,6 @@ class NetworkOnDisk:
|
|||||||
|
|
||||||
def read_metadata():
|
def read_metadata():
|
||||||
metadata = sd_models.read_metadata_from_safetensors(filename)
|
metadata = sd_models.read_metadata_from_safetensors(filename)
|
||||||
metadata.pop('ssmd_cover_images', None) # those are cover images, and they are too big to display in UI as text
|
|
||||||
|
|
||||||
return metadata
|
return metadata
|
||||||
|
|
||||||
@@ -115,8 +115,17 @@ class NetworkModule:
|
|||||||
self.sd_key = weights.sd_key
|
self.sd_key = weights.sd_key
|
||||||
self.sd_module = weights.sd_module
|
self.sd_module = weights.sd_module
|
||||||
|
|
||||||
if hasattr(self.sd_module, 'weight'):
|
if isinstance(self.sd_module, modules.models.sd3.mmdit.QkvLinear):
|
||||||
|
s = self.sd_module.weight.shape
|
||||||
|
self.shape = (s[0] // 3, s[1])
|
||||||
|
elif hasattr(self.sd_module, 'weight'):
|
||||||
self.shape = self.sd_module.weight.shape
|
self.shape = self.sd_module.weight.shape
|
||||||
|
elif isinstance(self.sd_module, nn.MultiheadAttention):
|
||||||
|
# For now, only self-attn use Pytorch's MHA
|
||||||
|
# So assume all qkvo proj have same shape
|
||||||
|
self.shape = self.sd_module.out_proj.weight.shape
|
||||||
|
else:
|
||||||
|
self.shape = None
|
||||||
|
|
||||||
self.ops = None
|
self.ops = None
|
||||||
self.extra_kwargs = {}
|
self.extra_kwargs = {}
|
||||||
@@ -146,6 +155,9 @@ class NetworkModule:
|
|||||||
self.alpha = weights.w["alpha"].item() if "alpha" in weights.w else None
|
self.alpha = weights.w["alpha"].item() if "alpha" in weights.w else None
|
||||||
self.scale = weights.w["scale"].item() if "scale" in weights.w else None
|
self.scale = weights.w["scale"].item() if "scale" in weights.w else None
|
||||||
|
|
||||||
|
self.dora_scale = weights.w.get("dora_scale", None)
|
||||||
|
self.dora_norm_dims = len(self.shape) - 1
|
||||||
|
|
||||||
def multiplier(self):
|
def multiplier(self):
|
||||||
if 'transformer' in self.sd_key[:20]:
|
if 'transformer' in self.sd_key[:20]:
|
||||||
return self.network.te_multiplier
|
return self.network.te_multiplier
|
||||||
@@ -160,6 +172,27 @@ class NetworkModule:
|
|||||||
|
|
||||||
return 1.0
|
return 1.0
|
||||||
|
|
||||||
|
def apply_weight_decompose(self, updown, orig_weight):
|
||||||
|
# Match the device/dtype
|
||||||
|
orig_weight = orig_weight.to(updown.dtype)
|
||||||
|
dora_scale = self.dora_scale.to(device=orig_weight.device, dtype=updown.dtype)
|
||||||
|
updown = updown.to(orig_weight.device)
|
||||||
|
|
||||||
|
merged_scale1 = updown + orig_weight
|
||||||
|
merged_scale1_norm = (
|
||||||
|
merged_scale1.transpose(0, 1)
|
||||||
|
.reshape(merged_scale1.shape[1], -1)
|
||||||
|
.norm(dim=1, keepdim=True)
|
||||||
|
.reshape(merged_scale1.shape[1], *[1] * self.dora_norm_dims)
|
||||||
|
.transpose(0, 1)
|
||||||
|
)
|
||||||
|
|
||||||
|
dora_merged = (
|
||||||
|
merged_scale1 * (dora_scale / merged_scale1_norm)
|
||||||
|
)
|
||||||
|
final_updown = dora_merged - orig_weight
|
||||||
|
return final_updown
|
||||||
|
|
||||||
def finalize_updown(self, updown, orig_weight, output_shape, ex_bias=None):
|
def finalize_updown(self, updown, orig_weight, output_shape, ex_bias=None):
|
||||||
if self.bias is not None:
|
if self.bias is not None:
|
||||||
updown = updown.reshape(self.bias.shape)
|
updown = updown.reshape(self.bias.shape)
|
||||||
@@ -175,7 +208,12 @@ class NetworkModule:
|
|||||||
if ex_bias is not None:
|
if ex_bias is not None:
|
||||||
ex_bias = ex_bias * self.multiplier()
|
ex_bias = ex_bias * self.multiplier()
|
||||||
|
|
||||||
return updown * self.calc_scale() * self.multiplier(), ex_bias
|
updown = updown * self.calc_scale()
|
||||||
|
|
||||||
|
if self.dora_scale is not None:
|
||||||
|
updown = self.apply_weight_decompose(updown, orig_weight)
|
||||||
|
|
||||||
|
return updown * self.multiplier(), ex_bias
|
||||||
|
|
||||||
def calc_updown(self, target):
|
def calc_updown(self, target):
|
||||||
raise NotImplementedError()
|
raise NotImplementedError()
|
||||||
|
|||||||
@@ -1,6 +1,7 @@
|
|||||||
import torch
|
import torch
|
||||||
|
|
||||||
import lyco_helpers
|
import lyco_helpers
|
||||||
|
import modules.models.sd3.mmdit
|
||||||
import network
|
import network
|
||||||
from modules import devices
|
from modules import devices
|
||||||
|
|
||||||
@@ -10,6 +11,13 @@ class ModuleTypeLora(network.ModuleType):
|
|||||||
if all(x in weights.w for x in ["lora_up.weight", "lora_down.weight"]):
|
if all(x in weights.w for x in ["lora_up.weight", "lora_down.weight"]):
|
||||||
return NetworkModuleLora(net, weights)
|
return NetworkModuleLora(net, weights)
|
||||||
|
|
||||||
|
if all(x in weights.w for x in ["lora_A.weight", "lora_B.weight"]):
|
||||||
|
w = weights.w.copy()
|
||||||
|
weights.w.clear()
|
||||||
|
weights.w.update({"lora_up.weight": w["lora_B.weight"], "lora_down.weight": w["lora_A.weight"]})
|
||||||
|
|
||||||
|
return NetworkModuleLora(net, weights)
|
||||||
|
|
||||||
return None
|
return None
|
||||||
|
|
||||||
|
|
||||||
@@ -29,7 +37,7 @@ class NetworkModuleLora(network.NetworkModule):
|
|||||||
if weight is None and none_ok:
|
if weight is None and none_ok:
|
||||||
return None
|
return None
|
||||||
|
|
||||||
is_linear = type(self.sd_module) in [torch.nn.Linear, torch.nn.modules.linear.NonDynamicallyQuantizableLinear, torch.nn.MultiheadAttention]
|
is_linear = type(self.sd_module) in [torch.nn.Linear, torch.nn.modules.linear.NonDynamicallyQuantizableLinear, torch.nn.MultiheadAttention, modules.models.sd3.mmdit.QkvLinear]
|
||||||
is_conv = type(self.sd_module) in [torch.nn.Conv2d]
|
is_conv = type(self.sd_module) in [torch.nn.Conv2d]
|
||||||
|
|
||||||
if is_linear:
|
if is_linear:
|
||||||
|
|||||||
@@ -1,6 +1,5 @@
|
|||||||
import torch
|
import torch
|
||||||
import network
|
import network
|
||||||
from lyco_helpers import factorization
|
|
||||||
from einops import rearrange
|
from einops import rearrange
|
||||||
|
|
||||||
|
|
||||||
@@ -22,16 +21,17 @@ class NetworkModuleOFT(network.NetworkModule):
|
|||||||
self.org_module: list[torch.Module] = [self.sd_module]
|
self.org_module: list[torch.Module] = [self.sd_module]
|
||||||
|
|
||||||
self.scale = 1.0
|
self.scale = 1.0
|
||||||
|
self.is_R = False
|
||||||
|
self.is_boft = False
|
||||||
|
|
||||||
# kohya-ss
|
# kohya-ss/New LyCORIS OFT/BOFT
|
||||||
if "oft_blocks" in weights.w.keys():
|
if "oft_blocks" in weights.w.keys():
|
||||||
self.is_kohya = True
|
|
||||||
self.oft_blocks = weights.w["oft_blocks"] # (num_blocks, block_size, block_size)
|
self.oft_blocks = weights.w["oft_blocks"] # (num_blocks, block_size, block_size)
|
||||||
self.alpha = weights.w["alpha"] # alpha is constraint
|
self.alpha = weights.w.get("alpha", None) # alpha is constraint
|
||||||
self.dim = self.oft_blocks.shape[0] # lora dim
|
self.dim = self.oft_blocks.shape[0] # lora dim
|
||||||
# LyCORIS
|
# Old LyCORIS OFT
|
||||||
elif "oft_diag" in weights.w.keys():
|
elif "oft_diag" in weights.w.keys():
|
||||||
self.is_kohya = False
|
self.is_R = True
|
||||||
self.oft_blocks = weights.w["oft_diag"]
|
self.oft_blocks = weights.w["oft_diag"]
|
||||||
# self.alpha is unused
|
# self.alpha is unused
|
||||||
self.dim = self.oft_blocks.shape[1] # (num_blocks, block_size, block_size)
|
self.dim = self.oft_blocks.shape[1] # (num_blocks, block_size, block_size)
|
||||||
@@ -47,27 +47,41 @@ class NetworkModuleOFT(network.NetworkModule):
|
|||||||
elif is_other_linear:
|
elif is_other_linear:
|
||||||
self.out_dim = self.sd_module.embed_dim
|
self.out_dim = self.sd_module.embed_dim
|
||||||
|
|
||||||
if self.is_kohya:
|
# LyCORIS BOFT
|
||||||
self.constraint = self.alpha * self.out_dim
|
if self.oft_blocks.dim() == 4:
|
||||||
|
self.is_boft = True
|
||||||
|
self.rescale = weights.w.get('rescale', None)
|
||||||
|
if self.rescale is not None and not is_other_linear:
|
||||||
|
self.rescale = self.rescale.reshape(-1, *[1]*(self.org_module[0].weight.dim() - 1))
|
||||||
|
|
||||||
self.num_blocks = self.dim
|
self.num_blocks = self.dim
|
||||||
self.block_size = self.out_dim // self.dim
|
self.block_size = self.out_dim // self.dim
|
||||||
else:
|
self.constraint = (0 if self.alpha is None else self.alpha) * self.out_dim
|
||||||
|
if self.is_R:
|
||||||
self.constraint = None
|
self.constraint = None
|
||||||
self.block_size, self.num_blocks = factorization(self.out_dim, self.dim)
|
self.block_size = self.dim
|
||||||
|
self.num_blocks = self.out_dim // self.dim
|
||||||
|
elif self.is_boft:
|
||||||
|
self.boft_m = self.oft_blocks.shape[0]
|
||||||
|
self.num_blocks = self.oft_blocks.shape[1]
|
||||||
|
self.block_size = self.oft_blocks.shape[2]
|
||||||
|
self.boft_b = self.block_size
|
||||||
|
|
||||||
def calc_updown(self, orig_weight):
|
def calc_updown(self, orig_weight):
|
||||||
oft_blocks = self.oft_blocks.to(orig_weight.device)
|
oft_blocks = self.oft_blocks.to(orig_weight.device)
|
||||||
eye = torch.eye(self.block_size, device=self.oft_blocks.device)
|
eye = torch.eye(self.block_size, device=oft_blocks.device)
|
||||||
|
|
||||||
if self.is_kohya:
|
if not self.is_R:
|
||||||
block_Q = oft_blocks - oft_blocks.transpose(1, 2) # ensure skew-symmetric orthogonal matrix
|
block_Q = oft_blocks - oft_blocks.transpose(-1, -2) # ensure skew-symmetric orthogonal matrix
|
||||||
|
if self.constraint != 0:
|
||||||
norm_Q = torch.norm(block_Q.flatten())
|
norm_Q = torch.norm(block_Q.flatten())
|
||||||
new_norm_Q = torch.clamp(norm_Q, max=self.constraint)
|
new_norm_Q = torch.clamp(norm_Q, max=self.constraint.to(oft_blocks.device))
|
||||||
block_Q = block_Q * ((new_norm_Q + 1e-8) / (norm_Q + 1e-8))
|
block_Q = block_Q * ((new_norm_Q + 1e-8) / (norm_Q + 1e-8))
|
||||||
oft_blocks = torch.matmul(eye + block_Q, (eye - block_Q).float().inverse())
|
oft_blocks = torch.matmul(eye + block_Q, (eye - block_Q).float().inverse())
|
||||||
|
|
||||||
R = oft_blocks.to(orig_weight.device)
|
R = oft_blocks.to(orig_weight.device)
|
||||||
|
|
||||||
|
if not self.is_boft:
|
||||||
# This errors out for MultiheadAttention, might need to be handled up-stream
|
# This errors out for MultiheadAttention, might need to be handled up-stream
|
||||||
merged_weight = rearrange(orig_weight, '(k n) ... -> k n ...', k=self.num_blocks, n=self.block_size)
|
merged_weight = rearrange(orig_weight, '(k n) ... -> k n ...', k=self.num_blocks, n=self.block_size)
|
||||||
merged_weight = torch.einsum(
|
merged_weight = torch.einsum(
|
||||||
@@ -76,6 +90,28 @@ class NetworkModuleOFT(network.NetworkModule):
|
|||||||
merged_weight
|
merged_weight
|
||||||
)
|
)
|
||||||
merged_weight = rearrange(merged_weight, 'k m ... -> (k m) ...')
|
merged_weight = rearrange(merged_weight, 'k m ... -> (k m) ...')
|
||||||
|
else:
|
||||||
|
# TODO: determine correct value for scale
|
||||||
|
scale = 1.0
|
||||||
|
m = self.boft_m
|
||||||
|
b = self.boft_b
|
||||||
|
r_b = b // 2
|
||||||
|
inp = orig_weight
|
||||||
|
for i in range(m):
|
||||||
|
bi = R[i] # b_num, b_size, b_size
|
||||||
|
if i == 0:
|
||||||
|
# Apply multiplier/scale and rescale into first weight
|
||||||
|
bi = bi * scale + (1 - scale) * eye
|
||||||
|
inp = rearrange(inp, "(c g k) ... -> (c k g) ...", g=2, k=2**i * r_b)
|
||||||
|
inp = rearrange(inp, "(d b) ... -> d b ...", b=b)
|
||||||
|
inp = torch.einsum("b i j, b j ... -> b i ...", bi, inp)
|
||||||
|
inp = rearrange(inp, "d b ... -> (d b) ...")
|
||||||
|
inp = rearrange(inp, "(c k g) ... -> (c g k) ...", g=2, k=2**i * r_b)
|
||||||
|
merged_weight = inp
|
||||||
|
|
||||||
|
# Rescale mechanism
|
||||||
|
if self.rescale is not None:
|
||||||
|
merged_weight = self.rescale.to(merged_weight) * merged_weight
|
||||||
|
|
||||||
updown = merged_weight.to(orig_weight.device) - orig_weight.to(merged_weight.dtype)
|
updown = merged_weight.to(orig_weight.device) - orig_weight.to(merged_weight.dtype)
|
||||||
output_shape = orig_weight.shape
|
output_shape = orig_weight.shape
|
||||||
|
|||||||
@@ -1,3 +1,4 @@
|
|||||||
|
from __future__ import annotations
|
||||||
import gradio as gr
|
import gradio as gr
|
||||||
import logging
|
import logging
|
||||||
import os
|
import os
|
||||||
@@ -19,6 +20,7 @@ from typing import Union
|
|||||||
|
|
||||||
from modules import shared, devices, sd_models, errors, scripts, sd_hijack
|
from modules import shared, devices, sd_models, errors, scripts, sd_hijack
|
||||||
import modules.textual_inversion.textual_inversion as textual_inversion
|
import modules.textual_inversion.textual_inversion as textual_inversion
|
||||||
|
import modules.models.sd3.mmdit
|
||||||
|
|
||||||
from lora_logger import logger
|
from lora_logger import logger
|
||||||
|
|
||||||
@@ -130,7 +132,9 @@ def assign_network_names_to_compvis_modules(sd_model):
|
|||||||
network_layer_mapping[network_name] = module
|
network_layer_mapping[network_name] = module
|
||||||
module.network_layer_name = network_name
|
module.network_layer_name = network_name
|
||||||
else:
|
else:
|
||||||
for name, module in shared.sd_model.cond_stage_model.wrapped.named_modules():
|
cond_stage_model = getattr(shared.sd_model.cond_stage_model, 'wrapped', shared.sd_model.cond_stage_model)
|
||||||
|
|
||||||
|
for name, module in cond_stage_model.named_modules():
|
||||||
network_name = name.replace(".", "_")
|
network_name = name.replace(".", "_")
|
||||||
network_layer_mapping[network_name] = module
|
network_layer_mapping[network_name] = module
|
||||||
module.network_layer_name = network_name
|
module.network_layer_name = network_name
|
||||||
@@ -143,6 +147,14 @@ def assign_network_names_to_compvis_modules(sd_model):
|
|||||||
sd_model.network_layer_mapping = network_layer_mapping
|
sd_model.network_layer_mapping = network_layer_mapping
|
||||||
|
|
||||||
|
|
||||||
|
class BundledTIHash(str):
|
||||||
|
def __init__(self, hash_str):
|
||||||
|
self.hash = hash_str
|
||||||
|
|
||||||
|
def __str__(self):
|
||||||
|
return self.hash if shared.opts.lora_bundled_ti_to_infotext else ''
|
||||||
|
|
||||||
|
|
||||||
def load_network(name, network_on_disk):
|
def load_network(name, network_on_disk):
|
||||||
net = network.Network(name, network_on_disk)
|
net = network.Network(name, network_on_disk)
|
||||||
net.mtime = os.path.getmtime(network_on_disk.filename)
|
net.mtime = os.path.getmtime(network_on_disk.filename)
|
||||||
@@ -155,11 +167,25 @@ def load_network(name, network_on_disk):
|
|||||||
|
|
||||||
keys_failed_to_match = {}
|
keys_failed_to_match = {}
|
||||||
is_sd2 = 'model_transformer_resblocks' in shared.sd_model.network_layer_mapping
|
is_sd2 = 'model_transformer_resblocks' in shared.sd_model.network_layer_mapping
|
||||||
|
if hasattr(shared.sd_model, 'diffusers_weight_map'):
|
||||||
|
diffusers_weight_map = shared.sd_model.diffusers_weight_map
|
||||||
|
elif hasattr(shared.sd_model, 'diffusers_weight_mapping'):
|
||||||
|
diffusers_weight_map = {}
|
||||||
|
for k, v in shared.sd_model.diffusers_weight_mapping():
|
||||||
|
diffusers_weight_map[k] = v
|
||||||
|
shared.sd_model.diffusers_weight_map = diffusers_weight_map
|
||||||
|
else:
|
||||||
|
diffusers_weight_map = None
|
||||||
|
|
||||||
matched_networks = {}
|
matched_networks = {}
|
||||||
bundle_embeddings = {}
|
bundle_embeddings = {}
|
||||||
|
|
||||||
for key_network, weight in sd.items():
|
for key_network, weight in sd.items():
|
||||||
|
|
||||||
|
if diffusers_weight_map:
|
||||||
|
key_network_without_network_parts, network_name, network_weight = key_network.rsplit(".", 2)
|
||||||
|
network_part = network_name + '.' + network_weight
|
||||||
|
else:
|
||||||
key_network_without_network_parts, _, network_part = key_network.partition(".")
|
key_network_without_network_parts, _, network_part = key_network.partition(".")
|
||||||
|
|
||||||
if key_network_without_network_parts == "bundle_emb":
|
if key_network_without_network_parts == "bundle_emb":
|
||||||
@@ -172,7 +198,11 @@ def load_network(name, network_on_disk):
|
|||||||
emb_dict[vec_name] = weight
|
emb_dict[vec_name] = weight
|
||||||
bundle_embeddings[emb_name] = emb_dict
|
bundle_embeddings[emb_name] = emb_dict
|
||||||
|
|
||||||
|
if diffusers_weight_map:
|
||||||
|
key = diffusers_weight_map.get(key_network_without_network_parts, key_network_without_network_parts)
|
||||||
|
else:
|
||||||
key = convert_diffusers_name_to_compvis(key_network_without_network_parts, is_sd2)
|
key = convert_diffusers_name_to_compvis(key_network_without_network_parts, is_sd2)
|
||||||
|
|
||||||
sd_module = shared.sd_model.network_layer_mapping.get(key, None)
|
sd_module = shared.sd_model.network_layer_mapping.get(key, None)
|
||||||
|
|
||||||
if sd_module is None:
|
if sd_module is None:
|
||||||
@@ -229,6 +259,7 @@ def load_network(name, network_on_disk):
|
|||||||
for emb_name, data in bundle_embeddings.items():
|
for emb_name, data in bundle_embeddings.items():
|
||||||
embedding = textual_inversion.create_embedding_from_data(data, emb_name, filename=network_on_disk.filename + "/" + emb_name)
|
embedding = textual_inversion.create_embedding_from_data(data, emb_name, filename=network_on_disk.filename + "/" + emb_name)
|
||||||
embedding.loaded = None
|
embedding.loaded = None
|
||||||
|
embedding.shorthash = BundledTIHash(name)
|
||||||
embeddings[emb_name] = embedding
|
embeddings[emb_name] = embedding
|
||||||
|
|
||||||
net.bundle_embeddings = embeddings
|
net.bundle_embeddings = embeddings
|
||||||
@@ -260,6 +291,16 @@ def load_networks(names, te_multipliers=None, unet_multipliers=None, dyn_dims=No
|
|||||||
|
|
||||||
loaded_networks.clear()
|
loaded_networks.clear()
|
||||||
|
|
||||||
|
unavailable_networks = []
|
||||||
|
for name in names:
|
||||||
|
if name.lower() in forbidden_network_aliases and available_networks.get(name) is None:
|
||||||
|
unavailable_networks.append(name)
|
||||||
|
elif available_network_aliases.get(name) is None:
|
||||||
|
unavailable_networks.append(name)
|
||||||
|
|
||||||
|
if unavailable_networks:
|
||||||
|
update_available_networks_by_names(unavailable_networks)
|
||||||
|
|
||||||
networks_on_disk = [available_networks.get(name, None) if name.lower() in forbidden_network_aliases else available_network_aliases.get(name, None) for name in names]
|
networks_on_disk = [available_networks.get(name, None) if name.lower() in forbidden_network_aliases else available_network_aliases.get(name, None) for name in names]
|
||||||
if any(x is None for x in networks_on_disk):
|
if any(x is None for x in networks_on_disk):
|
||||||
list_available_networks()
|
list_available_networks()
|
||||||
@@ -325,6 +366,28 @@ def load_networks(names, te_multipliers=None, unet_multipliers=None, dyn_dims=No
|
|||||||
purge_networks_from_memory()
|
purge_networks_from_memory()
|
||||||
|
|
||||||
|
|
||||||
|
def allowed_layer_without_weight(layer):
|
||||||
|
if isinstance(layer, torch.nn.LayerNorm) and not layer.elementwise_affine:
|
||||||
|
return True
|
||||||
|
|
||||||
|
return False
|
||||||
|
|
||||||
|
|
||||||
|
def store_weights_backup(weight):
|
||||||
|
if weight is None:
|
||||||
|
return None
|
||||||
|
|
||||||
|
return weight.to(devices.cpu, copy=True)
|
||||||
|
|
||||||
|
|
||||||
|
def restore_weights_backup(obj, field, weight):
|
||||||
|
if weight is None:
|
||||||
|
setattr(obj, field, None)
|
||||||
|
return
|
||||||
|
|
||||||
|
getattr(obj, field).copy_(weight)
|
||||||
|
|
||||||
|
|
||||||
def network_restore_weights_from_backup(self: Union[torch.nn.Conv2d, torch.nn.Linear, torch.nn.GroupNorm, torch.nn.LayerNorm, torch.nn.MultiheadAttention]):
|
def network_restore_weights_from_backup(self: Union[torch.nn.Conv2d, torch.nn.Linear, torch.nn.GroupNorm, torch.nn.LayerNorm, torch.nn.MultiheadAttention]):
|
||||||
weights_backup = getattr(self, "network_weights_backup", None)
|
weights_backup = getattr(self, "network_weights_backup", None)
|
||||||
bias_backup = getattr(self, "network_bias_backup", None)
|
bias_backup = getattr(self, "network_bias_backup", None)
|
||||||
@@ -334,28 +397,22 @@ def network_restore_weights_from_backup(self: Union[torch.nn.Conv2d, torch.nn.Li
|
|||||||
|
|
||||||
if weights_backup is not None:
|
if weights_backup is not None:
|
||||||
if isinstance(self, torch.nn.MultiheadAttention):
|
if isinstance(self, torch.nn.MultiheadAttention):
|
||||||
self.in_proj_weight.copy_(weights_backup[0])
|
restore_weights_backup(self, 'in_proj_weight', weights_backup[0])
|
||||||
self.out_proj.weight.copy_(weights_backup[1])
|
restore_weights_backup(self.out_proj, 'weight', weights_backup[1])
|
||||||
else:
|
else:
|
||||||
self.weight.copy_(weights_backup)
|
restore_weights_backup(self, 'weight', weights_backup)
|
||||||
|
|
||||||
if bias_backup is not None:
|
|
||||||
if isinstance(self, torch.nn.MultiheadAttention):
|
if isinstance(self, torch.nn.MultiheadAttention):
|
||||||
self.out_proj.bias.copy_(bias_backup)
|
restore_weights_backup(self.out_proj, 'bias', bias_backup)
|
||||||
else:
|
else:
|
||||||
self.bias.copy_(bias_backup)
|
restore_weights_backup(self, 'bias', bias_backup)
|
||||||
else:
|
|
||||||
if isinstance(self, torch.nn.MultiheadAttention):
|
|
||||||
self.out_proj.bias = None
|
|
||||||
else:
|
|
||||||
self.bias = None
|
|
||||||
|
|
||||||
|
|
||||||
def network_apply_weights(self: Union[torch.nn.Conv2d, torch.nn.Linear, torch.nn.GroupNorm, torch.nn.LayerNorm, torch.nn.MultiheadAttention]):
|
def network_apply_weights(self: Union[torch.nn.Conv2d, torch.nn.Linear, torch.nn.GroupNorm, torch.nn.LayerNorm, torch.nn.MultiheadAttention]):
|
||||||
"""
|
"""
|
||||||
Applies the currently selected set of networks to the weights of torch layer self.
|
Applies the currently selected set of networks to the weights of torch layer self.
|
||||||
If weights already have this particular set of networks applied, does nothing.
|
If weights already have this particular set of networks applied, does nothing.
|
||||||
If not, restores orginal weights from backup and alters weights according to networks.
|
If not, restores original weights from backup and alters weights according to networks.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
network_layer_name = getattr(self, 'network_layer_name', None)
|
network_layer_name = getattr(self, 'network_layer_name', None)
|
||||||
@@ -367,24 +424,30 @@ def network_apply_weights(self: Union[torch.nn.Conv2d, torch.nn.Linear, torch.nn
|
|||||||
|
|
||||||
weights_backup = getattr(self, "network_weights_backup", None)
|
weights_backup = getattr(self, "network_weights_backup", None)
|
||||||
if weights_backup is None and wanted_names != ():
|
if weights_backup is None and wanted_names != ():
|
||||||
if current_names != ():
|
if current_names != () and not allowed_layer_without_weight(self):
|
||||||
raise RuntimeError("no backup weights found and current weights are not unchanged")
|
raise RuntimeError(f"{network_layer_name} - no backup weights found and current weights are not unchanged")
|
||||||
|
|
||||||
if isinstance(self, torch.nn.MultiheadAttention):
|
if isinstance(self, torch.nn.MultiheadAttention):
|
||||||
weights_backup = (self.in_proj_weight.to(devices.cpu, copy=True), self.out_proj.weight.to(devices.cpu, copy=True))
|
weights_backup = (store_weights_backup(self.in_proj_weight), store_weights_backup(self.out_proj.weight))
|
||||||
else:
|
else:
|
||||||
weights_backup = self.weight.to(devices.cpu, copy=True)
|
weights_backup = store_weights_backup(self.weight)
|
||||||
|
|
||||||
self.network_weights_backup = weights_backup
|
self.network_weights_backup = weights_backup
|
||||||
|
|
||||||
bias_backup = getattr(self, "network_bias_backup", None)
|
bias_backup = getattr(self, "network_bias_backup", None)
|
||||||
if bias_backup is None:
|
if bias_backup is None and wanted_names != ():
|
||||||
if isinstance(self, torch.nn.MultiheadAttention) and self.out_proj.bias is not None:
|
if isinstance(self, torch.nn.MultiheadAttention) and self.out_proj.bias is not None:
|
||||||
bias_backup = self.out_proj.bias.to(devices.cpu, copy=True)
|
bias_backup = store_weights_backup(self.out_proj.bias)
|
||||||
elif getattr(self, 'bias', None) is not None:
|
elif getattr(self, 'bias', None) is not None:
|
||||||
bias_backup = self.bias.to(devices.cpu, copy=True)
|
bias_backup = store_weights_backup(self.bias)
|
||||||
else:
|
else:
|
||||||
bias_backup = None
|
bias_backup = None
|
||||||
|
|
||||||
|
# Unlike weight which always has value, some modules don't have bias.
|
||||||
|
# Only report if bias is not None and current bias are not unchanged.
|
||||||
|
if bias_backup is not None and current_names != ():
|
||||||
|
raise RuntimeError("no backup bias found and current bias are not unchanged")
|
||||||
|
|
||||||
self.network_bias_backup = bias_backup
|
self.network_bias_backup = bias_backup
|
||||||
|
|
||||||
if current_names != wanted_names:
|
if current_names != wanted_names:
|
||||||
@@ -392,7 +455,7 @@ def network_apply_weights(self: Union[torch.nn.Conv2d, torch.nn.Linear, torch.nn
|
|||||||
|
|
||||||
for net in loaded_networks:
|
for net in loaded_networks:
|
||||||
module = net.modules.get(network_layer_name, None)
|
module = net.modules.get(network_layer_name, None)
|
||||||
if module is not None and hasattr(self, 'weight'):
|
if module is not None and hasattr(self, 'weight') and not isinstance(module, modules.models.sd3.mmdit.QkvLinear):
|
||||||
try:
|
try:
|
||||||
with torch.no_grad():
|
with torch.no_grad():
|
||||||
if getattr(self, 'fp16_weight', None) is None:
|
if getattr(self, 'fp16_weight', None) is None:
|
||||||
@@ -429,9 +492,12 @@ def network_apply_weights(self: Union[torch.nn.Conv2d, torch.nn.Linear, torch.nn
|
|||||||
if isinstance(self, torch.nn.MultiheadAttention) and module_q and module_k and module_v and module_out:
|
if isinstance(self, torch.nn.MultiheadAttention) and module_q and module_k and module_v and module_out:
|
||||||
try:
|
try:
|
||||||
with torch.no_grad():
|
with torch.no_grad():
|
||||||
updown_q, _ = module_q.calc_updown(self.in_proj_weight)
|
# Send "real" orig_weight into MHA's lora module
|
||||||
updown_k, _ = module_k.calc_updown(self.in_proj_weight)
|
qw, kw, vw = self.in_proj_weight.chunk(3, 0)
|
||||||
updown_v, _ = module_v.calc_updown(self.in_proj_weight)
|
updown_q, _ = module_q.calc_updown(qw)
|
||||||
|
updown_k, _ = module_k.calc_updown(kw)
|
||||||
|
updown_v, _ = module_v.calc_updown(vw)
|
||||||
|
del qw, kw, vw
|
||||||
updown_qkv = torch.vstack([updown_q, updown_k, updown_v])
|
updown_qkv = torch.vstack([updown_q, updown_k, updown_v])
|
||||||
updown_out, ex_bias = module_out.calc_updown(self.out_proj.weight)
|
updown_out, ex_bias = module_out.calc_updown(self.out_proj.weight)
|
||||||
|
|
||||||
@@ -449,6 +515,24 @@ def network_apply_weights(self: Union[torch.nn.Conv2d, torch.nn.Linear, torch.nn
|
|||||||
|
|
||||||
continue
|
continue
|
||||||
|
|
||||||
|
if isinstance(self, modules.models.sd3.mmdit.QkvLinear) and module_q and module_k and module_v:
|
||||||
|
try:
|
||||||
|
with torch.no_grad():
|
||||||
|
# Send "real" orig_weight into MHA's lora module
|
||||||
|
qw, kw, vw = self.weight.chunk(3, 0)
|
||||||
|
updown_q, _ = module_q.calc_updown(qw)
|
||||||
|
updown_k, _ = module_k.calc_updown(kw)
|
||||||
|
updown_v, _ = module_v.calc_updown(vw)
|
||||||
|
del qw, kw, vw
|
||||||
|
updown_qkv = torch.vstack([updown_q, updown_k, updown_v])
|
||||||
|
self.weight += updown_qkv
|
||||||
|
|
||||||
|
except RuntimeError as e:
|
||||||
|
logging.debug(f"Network {net.name} layer {network_layer_name}: {e}")
|
||||||
|
extra_network_lora.errors[net.name] = extra_network_lora.errors.get(net.name, 0) + 1
|
||||||
|
|
||||||
|
continue
|
||||||
|
|
||||||
if module is None:
|
if module is None:
|
||||||
continue
|
continue
|
||||||
|
|
||||||
@@ -563,22 +647,16 @@ def network_MultiheadAttention_load_state_dict(self, *args, **kwargs):
|
|||||||
return originals.MultiheadAttention_load_state_dict(self, *args, **kwargs)
|
return originals.MultiheadAttention_load_state_dict(self, *args, **kwargs)
|
||||||
|
|
||||||
|
|
||||||
def list_available_networks():
|
def process_network_files(names: list[str] | None = None):
|
||||||
available_networks.clear()
|
|
||||||
available_network_aliases.clear()
|
|
||||||
forbidden_network_aliases.clear()
|
|
||||||
available_network_hash_lookup.clear()
|
|
||||||
forbidden_network_aliases.update({"none": 1, "Addams": 1})
|
|
||||||
|
|
||||||
os.makedirs(shared.cmd_opts.lora_dir, exist_ok=True)
|
|
||||||
|
|
||||||
candidates = list(shared.walk_files(shared.cmd_opts.lora_dir, allowed_extensions=[".pt", ".ckpt", ".safetensors"]))
|
candidates = list(shared.walk_files(shared.cmd_opts.lora_dir, allowed_extensions=[".pt", ".ckpt", ".safetensors"]))
|
||||||
candidates += list(shared.walk_files(shared.cmd_opts.lyco_dir_backcompat, allowed_extensions=[".pt", ".ckpt", ".safetensors"]))
|
candidates += list(shared.walk_files(shared.cmd_opts.lyco_dir_backcompat, allowed_extensions=[".pt", ".ckpt", ".safetensors"]))
|
||||||
for filename in candidates:
|
for filename in candidates:
|
||||||
if os.path.isdir(filename):
|
if os.path.isdir(filename):
|
||||||
continue
|
continue
|
||||||
|
|
||||||
name = os.path.splitext(os.path.basename(filename))[0]
|
name = os.path.splitext(os.path.basename(filename))[0]
|
||||||
|
# if names is provided, only load networks with names in the list
|
||||||
|
if names and name not in names:
|
||||||
|
continue
|
||||||
try:
|
try:
|
||||||
entry = network.NetworkOnDisk(name, filename)
|
entry = network.NetworkOnDisk(name, filename)
|
||||||
except OSError: # should catch FileNotFoundError and PermissionError etc.
|
except OSError: # should catch FileNotFoundError and PermissionError etc.
|
||||||
@@ -594,6 +672,22 @@ def list_available_networks():
|
|||||||
available_network_aliases[entry.alias] = entry
|
available_network_aliases[entry.alias] = entry
|
||||||
|
|
||||||
|
|
||||||
|
def update_available_networks_by_names(names: list[str]):
|
||||||
|
process_network_files(names)
|
||||||
|
|
||||||
|
|
||||||
|
def list_available_networks():
|
||||||
|
available_networks.clear()
|
||||||
|
available_network_aliases.clear()
|
||||||
|
forbidden_network_aliases.clear()
|
||||||
|
available_network_hash_lookup.clear()
|
||||||
|
forbidden_network_aliases.update({"none": 1, "Addams": 1})
|
||||||
|
|
||||||
|
os.makedirs(shared.cmd_opts.lora_dir, exist_ok=True)
|
||||||
|
|
||||||
|
process_network_files()
|
||||||
|
|
||||||
|
|
||||||
re_network_name = re.compile(r"(.*)\s*\([0-9a-fA-F]+\)")
|
re_network_name = re.compile(r"(.*)\s*\([0-9a-fA-F]+\)")
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@@ -1,7 +1,8 @@
|
|||||||
import os
|
import os
|
||||||
from modules import paths
|
from modules import paths
|
||||||
|
from modules.paths_internal import normalized_filepath
|
||||||
|
|
||||||
|
|
||||||
def preload(parser):
|
def preload(parser):
|
||||||
parser.add_argument("--lora-dir", type=str, help="Path to directory with Lora networks.", default=os.path.join(paths.models_path, 'Lora'))
|
parser.add_argument("--lora-dir", type=normalized_filepath, help="Path to directory with Lora networks.", default=os.path.join(paths.models_path, 'Lora'))
|
||||||
parser.add_argument("--lyco-dir-backcompat", type=str, help="Path to directory with LyCORIS networks (for backawards compatibility; can also use --lyco-dir).", default=os.path.join(paths.models_path, 'LyCORIS'))
|
parser.add_argument("--lyco-dir-backcompat", type=normalized_filepath, help="Path to directory with LyCORIS networks (for backawards compatibility; can also use --lyco-dir).", default=os.path.join(paths.models_path, 'LyCORIS'))
|
||||||
|
|||||||
@@ -36,6 +36,7 @@ shared.options_templates.update(shared.options_section(('extra_networks', "Extra
|
|||||||
"sd_lora": shared.OptionInfo("None", "Add network to prompt", gr.Dropdown, lambda: {"choices": ["None", *networks.available_networks]}, refresh=networks.list_available_networks),
|
"sd_lora": shared.OptionInfo("None", "Add network to prompt", gr.Dropdown, lambda: {"choices": ["None", *networks.available_networks]}, refresh=networks.list_available_networks),
|
||||||
"lora_preferred_name": shared.OptionInfo("Alias from file", "When adding to prompt, refer to Lora by", gr.Radio, {"choices": ["Alias from file", "Filename"]}),
|
"lora_preferred_name": shared.OptionInfo("Alias from file", "When adding to prompt, refer to Lora by", gr.Radio, {"choices": ["Alias from file", "Filename"]}),
|
||||||
"lora_add_hashes_to_infotext": shared.OptionInfo(True, "Add Lora hashes to infotext"),
|
"lora_add_hashes_to_infotext": shared.OptionInfo(True, "Add Lora hashes to infotext"),
|
||||||
|
"lora_bundled_ti_to_infotext": shared.OptionInfo(True, "Add Lora name as TI hashes for bundled Textual Inversion").info('"Add Textual Inversion hashes to infotext" needs to be enabled'),
|
||||||
"lora_show_all": shared.OptionInfo(False, "Always show all networks on the Lora page").info("otherwise, those detected as for incompatible version of Stable Diffusion will be hidden"),
|
"lora_show_all": shared.OptionInfo(False, "Always show all networks on the Lora page").info("otherwise, those detected as for incompatible version of Stable Diffusion will be hidden"),
|
||||||
"lora_hide_unknown_for_versions": shared.OptionInfo([], "Hide networks of unknown versions for model versions", gr.CheckboxGroup, {"choices": ["SD1", "SD2", "SDXL"]}),
|
"lora_hide_unknown_for_versions": shared.OptionInfo([], "Hide networks of unknown versions for model versions", gr.CheckboxGroup, {"choices": ["SD1", "SD2", "SDXL"]}),
|
||||||
"lora_in_memory_limit": shared.OptionInfo(0, "Number of Lora networks to keep cached in memory", gr.Number, {"precision": 0}),
|
"lora_in_memory_limit": shared.OptionInfo(0, "Number of Lora networks to keep cached in memory", gr.Number, {"precision": 0}),
|
||||||
|
|||||||
@@ -21,7 +21,9 @@ re_comma = re.compile(r" *, *")
|
|||||||
def build_tags(metadata):
|
def build_tags(metadata):
|
||||||
tags = {}
|
tags = {}
|
||||||
|
|
||||||
for _, tags_dict in metadata.get("ss_tag_frequency", {}).items():
|
ss_tag_frequency = metadata.get("ss_tag_frequency", {})
|
||||||
|
if ss_tag_frequency is not None and hasattr(ss_tag_frequency, 'items'):
|
||||||
|
for _, tags_dict in ss_tag_frequency.items():
|
||||||
for tag, tag_count in tags_dict.items():
|
for tag, tag_count in tags_dict.items():
|
||||||
tag = tag.strip()
|
tag = tag.strip()
|
||||||
tags[tag] = tags.get(tag, 0) + int(tag_count)
|
tags[tag] = tags.get(tag, 0) + int(tag_count)
|
||||||
@@ -149,6 +151,8 @@ class LoraUserMetadataEditor(ui_extra_networks_user_metadata.UserMetadataEditor)
|
|||||||
|
|
||||||
v = random.random() * max_count
|
v = random.random() * max_count
|
||||||
if count > v:
|
if count > v:
|
||||||
|
for x in "({[]})":
|
||||||
|
tag = tag.replace(x, '\\' + x)
|
||||||
res.append(tag)
|
res.append(tag)
|
||||||
|
|
||||||
return ", ".join(sorted(res))
|
return ", ".join(sorted(res))
|
||||||
|
|||||||
@@ -24,13 +24,16 @@ class ExtraNetworksPageLora(ui_extra_networks.ExtraNetworksPage):
|
|||||||
|
|
||||||
alias = lora_on_disk.get_alias()
|
alias = lora_on_disk.get_alias()
|
||||||
|
|
||||||
|
search_terms = [self.search_terms_from_path(lora_on_disk.filename)]
|
||||||
|
if lora_on_disk.hash:
|
||||||
|
search_terms.append(lora_on_disk.hash)
|
||||||
item = {
|
item = {
|
||||||
"name": name,
|
"name": name,
|
||||||
"filename": lora_on_disk.filename,
|
"filename": lora_on_disk.filename,
|
||||||
"shorthash": lora_on_disk.shorthash,
|
"shorthash": lora_on_disk.shorthash,
|
||||||
"preview": self.find_preview(path),
|
"preview": self.find_preview(path) or self.find_embedded_preview(path, name, lora_on_disk.metadata),
|
||||||
"description": self.find_description(path),
|
"description": self.find_description(path),
|
||||||
"search_term": self.search_terms_from_path(lora_on_disk.filename) + " " + (lora_on_disk.hash or ""),
|
"search_terms": search_terms,
|
||||||
"local_preview": f"{path}.{shared.opts.samples_format}",
|
"local_preview": f"{path}.{shared.opts.samples_format}",
|
||||||
"metadata": lora_on_disk.metadata,
|
"metadata": lora_on_disk.metadata,
|
||||||
"sort_keys": {'default': index, **self.get_sort_keys(lora_on_disk.filename)},
|
"sort_keys": {'default': index, **self.get_sort_keys(lora_on_disk.filename)},
|
||||||
@@ -57,7 +60,7 @@ class ExtraNetworksPageLora(ui_extra_networks.ExtraNetworksPage):
|
|||||||
else:
|
else:
|
||||||
sd_version = lora_on_disk.sd_version
|
sd_version = lora_on_disk.sd_version
|
||||||
|
|
||||||
if shared.opts.lora_show_all or not enable_filter:
|
if shared.opts.lora_show_all or not enable_filter or not shared.sd_model:
|
||||||
pass
|
pass
|
||||||
elif sd_version == network.SdVersion.Unknown:
|
elif sd_version == network.SdVersion.Unknown:
|
||||||
model_version = network.SdVersion.SDXL if shared.sd_model.is_sdxl else network.SdVersion.SD2 if shared.sd_model.is_sd2 else network.SdVersion.SD1
|
model_version = network.SdVersion.SDXL if shared.sd_model.is_sdxl else network.SdVersion.SD2 if shared.sd_model.is_sd2 else network.SdVersion.SD1
|
||||||
|
|||||||
@@ -29,6 +29,7 @@ onUiLoaded(async() => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
function getActiveTab(elements, all = false) {
|
function getActiveTab(elements, all = false) {
|
||||||
|
if (!elements.img2imgTabs) return null;
|
||||||
const tabs = elements.img2imgTabs.querySelectorAll("button");
|
const tabs = elements.img2imgTabs.querySelectorAll("button");
|
||||||
|
|
||||||
if (all) return tabs;
|
if (all) return tabs;
|
||||||
@@ -43,6 +44,7 @@ onUiLoaded(async() => {
|
|||||||
// Get tab ID
|
// Get tab ID
|
||||||
function getTabId(elements) {
|
function getTabId(elements) {
|
||||||
const activeTab = getActiveTab(elements);
|
const activeTab = getActiveTab(elements);
|
||||||
|
if (!activeTab) return null;
|
||||||
return tabNameToElementId[activeTab.innerText];
|
return tabNameToElementId[activeTab.innerText];
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -252,6 +254,7 @@ onUiLoaded(async() => {
|
|||||||
let isMoving = false;
|
let isMoving = false;
|
||||||
let mouseX, mouseY;
|
let mouseX, mouseY;
|
||||||
let activeElement;
|
let activeElement;
|
||||||
|
let interactedWithAltKey = false;
|
||||||
|
|
||||||
const elements = Object.fromEntries(
|
const elements = Object.fromEntries(
|
||||||
Object.keys(elementIDs).map(id => [
|
Object.keys(elementIDs).map(id => [
|
||||||
@@ -277,7 +280,7 @@ onUiLoaded(async() => {
|
|||||||
const targetElement = gradioApp().querySelector(elemId);
|
const targetElement = gradioApp().querySelector(elemId);
|
||||||
|
|
||||||
if (!targetElement) {
|
if (!targetElement) {
|
||||||
console.log("Element not found");
|
console.log("Element not found", elemId);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -292,7 +295,7 @@ onUiLoaded(async() => {
|
|||||||
|
|
||||||
// Create tooltip
|
// Create tooltip
|
||||||
function createTooltip() {
|
function createTooltip() {
|
||||||
const toolTipElemnt =
|
const toolTipElement =
|
||||||
targetElement.querySelector(".image-container");
|
targetElement.querySelector(".image-container");
|
||||||
const tooltip = document.createElement("div");
|
const tooltip = document.createElement("div");
|
||||||
tooltip.className = "canvas-tooltip";
|
tooltip.className = "canvas-tooltip";
|
||||||
@@ -355,7 +358,7 @@ onUiLoaded(async() => {
|
|||||||
tooltip.appendChild(tooltipContent);
|
tooltip.appendChild(tooltipContent);
|
||||||
|
|
||||||
// Add a hint element to the target element
|
// Add a hint element to the target element
|
||||||
toolTipElemnt.appendChild(tooltip);
|
toolTipElement.appendChild(tooltip);
|
||||||
}
|
}
|
||||||
|
|
||||||
//Show tool tip if setting enable
|
//Show tool tip if setting enable
|
||||||
@@ -365,9 +368,9 @@ onUiLoaded(async() => {
|
|||||||
|
|
||||||
// In the course of research, it was found that the tag img is very harmful when zooming and creates white canvases. This hack allows you to almost never think about this problem, it has no effect on webui.
|
// In the course of research, it was found that the tag img is very harmful when zooming and creates white canvases. This hack allows you to almost never think about this problem, it has no effect on webui.
|
||||||
function fixCanvas() {
|
function fixCanvas() {
|
||||||
const activeTab = getActiveTab(elements).textContent.trim();
|
const activeTab = getActiveTab(elements)?.textContent.trim();
|
||||||
|
|
||||||
if (activeTab !== "img2img") {
|
if (activeTab && activeTab !== "img2img") {
|
||||||
const img = targetElement.querySelector(`${elemId} img`);
|
const img = targetElement.querySelector(`${elemId} img`);
|
||||||
|
|
||||||
if (img && img.style.display !== "none") {
|
if (img && img.style.display !== "none") {
|
||||||
@@ -508,6 +511,10 @@ onUiLoaded(async() => {
|
|||||||
if (isModifierKey(e, hotkeysConfig.canvas_hotkey_zoom)) {
|
if (isModifierKey(e, hotkeysConfig.canvas_hotkey_zoom)) {
|
||||||
e.preventDefault();
|
e.preventDefault();
|
||||||
|
|
||||||
|
if (hotkeysConfig.canvas_hotkey_zoom === "Alt") {
|
||||||
|
interactedWithAltKey = true;
|
||||||
|
}
|
||||||
|
|
||||||
let zoomPosX, zoomPosY;
|
let zoomPosX, zoomPosY;
|
||||||
let delta = 0.2;
|
let delta = 0.2;
|
||||||
if (elemData[elemId].zoomLevel > 7) {
|
if (elemData[elemId].zoomLevel > 7) {
|
||||||
@@ -783,6 +790,7 @@ onUiLoaded(async() => {
|
|||||||
targetElement.addEventListener("mouseleave", handleMouseLeave);
|
targetElement.addEventListener("mouseleave", handleMouseLeave);
|
||||||
|
|
||||||
// Reset zoom when click on another tab
|
// Reset zoom when click on another tab
|
||||||
|
if (elements.img2imgTabs) {
|
||||||
elements.img2imgTabs.addEventListener("click", resetZoom);
|
elements.img2imgTabs.addEventListener("click", resetZoom);
|
||||||
elements.img2imgTabs.addEventListener("click", () => {
|
elements.img2imgTabs.addEventListener("click", () => {
|
||||||
// targetElement.style.width = "";
|
// targetElement.style.width = "";
|
||||||
@@ -790,20 +798,25 @@ onUiLoaded(async() => {
|
|||||||
setTimeout(fitToElement, 0);
|
setTimeout(fitToElement, 0);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
}
|
||||||
|
|
||||||
targetElement.addEventListener("wheel", e => {
|
targetElement.addEventListener("wheel", e => {
|
||||||
// change zoom level
|
// change zoom level
|
||||||
const operation = e.deltaY > 0 ? "-" : "+";
|
const operation = (e.deltaY || -e.wheelDelta) > 0 ? "-" : "+";
|
||||||
changeZoomLevel(operation, e);
|
changeZoomLevel(operation, e);
|
||||||
|
|
||||||
// Handle brush size adjustment with ctrl key pressed
|
// Handle brush size adjustment with ctrl key pressed
|
||||||
if (isModifierKey(e, hotkeysConfig.canvas_hotkey_adjust)) {
|
if (isModifierKey(e, hotkeysConfig.canvas_hotkey_adjust)) {
|
||||||
e.preventDefault();
|
e.preventDefault();
|
||||||
|
|
||||||
|
if (hotkeysConfig.canvas_hotkey_adjust === "Alt") {
|
||||||
|
interactedWithAltKey = true;
|
||||||
|
}
|
||||||
|
|
||||||
// Increase or decrease brush size based on scroll direction
|
// Increase or decrease brush size based on scroll direction
|
||||||
adjustBrushSize(elemId, e.deltaY);
|
adjustBrushSize(elemId, e.deltaY);
|
||||||
}
|
}
|
||||||
});
|
}, {passive: false});
|
||||||
|
|
||||||
// Handle the move event for pan functionality. Updates the panX and panY variables and applies the new transform to the target element.
|
// Handle the move event for pan functionality. Updates the panX and panY variables and applies the new transform to the target element.
|
||||||
function handleMoveKeyDown(e) {
|
function handleMoveKeyDown(e) {
|
||||||
@@ -839,6 +852,20 @@ onUiLoaded(async() => {
|
|||||||
document.addEventListener("keydown", handleMoveKeyDown);
|
document.addEventListener("keydown", handleMoveKeyDown);
|
||||||
document.addEventListener("keyup", handleMoveKeyUp);
|
document.addEventListener("keyup", handleMoveKeyUp);
|
||||||
|
|
||||||
|
|
||||||
|
// Prevent firefox from opening main menu when alt is used as a hotkey for zoom or brush size
|
||||||
|
function handleAltKeyUp(e) {
|
||||||
|
if (e.key !== "Alt" || !interactedWithAltKey) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
e.preventDefault();
|
||||||
|
interactedWithAltKey = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
document.addEventListener("keyup", handleAltKeyUp);
|
||||||
|
|
||||||
|
|
||||||
// Detect zoom level and update the pan speed.
|
// Detect zoom level and update the pan speed.
|
||||||
function updatePanPosition(movementX, movementY) {
|
function updatePanPosition(movementX, movementY) {
|
||||||
let panSpeed = 2;
|
let panSpeed = 2;
|
||||||
|
|||||||
@@ -8,8 +8,8 @@ shared.options_templates.update(shared.options_section(('canvas_hotkey', "Canvas
|
|||||||
"canvas_hotkey_grow_brush": shared.OptionInfo("W", "Enlarge the brush size"),
|
"canvas_hotkey_grow_brush": shared.OptionInfo("W", "Enlarge the brush size"),
|
||||||
"canvas_hotkey_move": shared.OptionInfo("F", "Moving the canvas").info("To work correctly in firefox, turn off 'Automatically search the page text when typing' in the browser settings"),
|
"canvas_hotkey_move": shared.OptionInfo("F", "Moving the canvas").info("To work correctly in firefox, turn off 'Automatically search the page text when typing' in the browser settings"),
|
||||||
"canvas_hotkey_fullscreen": shared.OptionInfo("S", "Fullscreen Mode, maximizes the picture so that it fits into the screen and stretches it to its full width "),
|
"canvas_hotkey_fullscreen": shared.OptionInfo("S", "Fullscreen Mode, maximizes the picture so that it fits into the screen and stretches it to its full width "),
|
||||||
"canvas_hotkey_reset": shared.OptionInfo("R", "Reset zoom and canvas positon"),
|
"canvas_hotkey_reset": shared.OptionInfo("R", "Reset zoom and canvas position"),
|
||||||
"canvas_hotkey_overlap": shared.OptionInfo("O", "Toggle overlap").info("Technical button, neededs for testing"),
|
"canvas_hotkey_overlap": shared.OptionInfo("O", "Toggle overlap").info("Technical button, needed for testing"),
|
||||||
"canvas_show_tooltip": shared.OptionInfo(True, "Enable tooltip on the canvas"),
|
"canvas_show_tooltip": shared.OptionInfo(True, "Enable tooltip on the canvas"),
|
||||||
"canvas_auto_expand": shared.OptionInfo(True, "Automatically expands an image that does not fit completely in the canvas area, similar to manually pressing the S and R buttons"),
|
"canvas_auto_expand": shared.OptionInfo(True, "Automatically expands an image that does not fit completely in the canvas area, similar to manually pressing the S and R buttons"),
|
||||||
"canvas_blur_prompt": shared.OptionInfo(False, "Take the focus off the prompt when working with a canvas"),
|
"canvas_blur_prompt": shared.OptionInfo(False, "Take the focus off the prompt when working with a canvas"),
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
import math
|
import math
|
||||||
|
|
||||||
import gradio as gr
|
import gradio as gr
|
||||||
from modules import scripts, shared, ui_components, ui_settings, infotext_utils
|
from modules import scripts, shared, ui_components, ui_settings, infotext_utils, errors
|
||||||
from modules.ui_components import FormColumn
|
from modules.ui_components import FormColumn
|
||||||
|
|
||||||
|
|
||||||
@@ -42,7 +42,11 @@ class ExtraOptionsSection(scripts.Script):
|
|||||||
setting_name = extra_options[index]
|
setting_name = extra_options[index]
|
||||||
|
|
||||||
with FormColumn():
|
with FormColumn():
|
||||||
|
try:
|
||||||
comp = ui_settings.create_setting_component(setting_name)
|
comp = ui_settings.create_setting_component(setting_name)
|
||||||
|
except KeyError:
|
||||||
|
errors.report(f"Can't add extra options for {setting_name} in ui")
|
||||||
|
continue
|
||||||
|
|
||||||
self.comps.append(comp)
|
self.comps.append(comp)
|
||||||
self.setting_names.append(setting_name)
|
self.setting_names.append(setting_name)
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
"""
|
"""
|
||||||
Hypertile module for splitting attention layers in SD-1.5 U-Net and SD-1.5 VAE
|
Hypertile module for splitting attention layers in SD-1.5 U-Net and SD-1.5 VAE
|
||||||
Warn: The patch works well only if the input image has a width and height that are multiples of 128
|
Warn: The patch works well only if the input image has a width and height that are multiples of 128
|
||||||
Original author: @tfernd Github: https://github.com/tfernd/HyperTile
|
Original author: @tfernd GitHub: https://github.com/tfernd/HyperTile
|
||||||
"""
|
"""
|
||||||
|
|
||||||
from __future__ import annotations
|
from __future__ import annotations
|
||||||
|
|||||||
@@ -1,6 +1,5 @@
|
|||||||
import hypertile
|
import hypertile
|
||||||
from modules import scripts, script_callbacks, shared
|
from modules import scripts, script_callbacks, shared
|
||||||
from scripts.hypertile_xyz import add_axis_options
|
|
||||||
|
|
||||||
|
|
||||||
class ScriptHypertile(scripts.Script):
|
class ScriptHypertile(scripts.Script):
|
||||||
@@ -93,7 +92,6 @@ def on_ui_settings():
|
|||||||
"hypertile_max_depth_unet": shared.OptionInfo(3, "Hypertile U-Net max depth", gr.Slider, {"minimum": 0, "maximum": 3, "step": 1}, infotext="Hypertile U-Net max depth").info("larger = more neural network layers affected; minor effect on performance"),
|
"hypertile_max_depth_unet": shared.OptionInfo(3, "Hypertile U-Net max depth", gr.Slider, {"minimum": 0, "maximum": 3, "step": 1}, infotext="Hypertile U-Net max depth").info("larger = more neural network layers affected; minor effect on performance"),
|
||||||
"hypertile_max_tile_unet": shared.OptionInfo(256, "Hypertile U-Net max tile size", gr.Slider, {"minimum": 0, "maximum": 512, "step": 16}, infotext="Hypertile U-Net max tile size").info("larger = worse performance"),
|
"hypertile_max_tile_unet": shared.OptionInfo(256, "Hypertile U-Net max tile size", gr.Slider, {"minimum": 0, "maximum": 512, "step": 16}, infotext="Hypertile U-Net max tile size").info("larger = worse performance"),
|
||||||
"hypertile_swap_size_unet": shared.OptionInfo(3, "Hypertile U-Net swap size", gr.Slider, {"minimum": 0, "maximum": 64, "step": 1}, infotext="Hypertile U-Net swap size"),
|
"hypertile_swap_size_unet": shared.OptionInfo(3, "Hypertile U-Net swap size", gr.Slider, {"minimum": 0, "maximum": 64, "step": 1}, infotext="Hypertile U-Net swap size"),
|
||||||
|
|
||||||
"hypertile_enable_vae": shared.OptionInfo(False, "Enable Hypertile VAE", infotext="Hypertile VAE").info("minimal change in the generated picture"),
|
"hypertile_enable_vae": shared.OptionInfo(False, "Enable Hypertile VAE", infotext="Hypertile VAE").info("minimal change in the generated picture"),
|
||||||
"hypertile_max_depth_vae": shared.OptionInfo(3, "Hypertile VAE max depth", gr.Slider, {"minimum": 0, "maximum": 3, "step": 1}, infotext="Hypertile VAE max depth"),
|
"hypertile_max_depth_vae": shared.OptionInfo(3, "Hypertile VAE max depth", gr.Slider, {"minimum": 0, "maximum": 3, "step": 1}, infotext="Hypertile VAE max depth"),
|
||||||
"hypertile_max_tile_vae": shared.OptionInfo(128, "Hypertile VAE max tile size", gr.Slider, {"minimum": 0, "maximum": 512, "step": 16}, infotext="Hypertile VAE max tile size"),
|
"hypertile_max_tile_vae": shared.OptionInfo(128, "Hypertile VAE max tile size", gr.Slider, {"minimum": 0, "maximum": 512, "step": 16}, infotext="Hypertile VAE max tile size"),
|
||||||
@@ -105,5 +103,20 @@ def on_ui_settings():
|
|||||||
shared.opts.add_option(name, opt)
|
shared.opts.add_option(name, opt)
|
||||||
|
|
||||||
|
|
||||||
|
def add_axis_options():
|
||||||
|
xyz_grid = [x for x in scripts.scripts_data if x.script_class.__module__ == "xyz_grid.py"][0].module
|
||||||
|
xyz_grid.axis_options.extend([
|
||||||
|
xyz_grid.AxisOption("[Hypertile] Unet First pass Enabled", str, xyz_grid.apply_override('hypertile_enable_unet', boolean=True), choices=xyz_grid.boolean_choice(reverse=True)),
|
||||||
|
xyz_grid.AxisOption("[Hypertile] Unet Second pass Enabled", str, xyz_grid.apply_override('hypertile_enable_unet_secondpass', boolean=True), choices=xyz_grid.boolean_choice(reverse=True)),
|
||||||
|
xyz_grid.AxisOption("[Hypertile] Unet Max Depth", int, xyz_grid.apply_override("hypertile_max_depth_unet"), confirm=xyz_grid.confirm_range(0, 3, '[Hypertile] Unet Max Depth'), choices=lambda: [str(x) for x in range(4)]),
|
||||||
|
xyz_grid.AxisOption("[Hypertile] Unet Max Tile Size", int, xyz_grid.apply_override("hypertile_max_tile_unet"), confirm=xyz_grid.confirm_range(0, 512, '[Hypertile] Unet Max Tile Size')),
|
||||||
|
xyz_grid.AxisOption("[Hypertile] Unet Swap Size", int, xyz_grid.apply_override("hypertile_swap_size_unet"), confirm=xyz_grid.confirm_range(0, 64, '[Hypertile] Unet Swap Size')),
|
||||||
|
xyz_grid.AxisOption("[Hypertile] VAE Enabled", str, xyz_grid.apply_override('hypertile_enable_vae', boolean=True), choices=xyz_grid.boolean_choice(reverse=True)),
|
||||||
|
xyz_grid.AxisOption("[Hypertile] VAE Max Depth", int, xyz_grid.apply_override("hypertile_max_depth_vae"), confirm=xyz_grid.confirm_range(0, 3, '[Hypertile] VAE Max Depth'), choices=lambda: [str(x) for x in range(4)]),
|
||||||
|
xyz_grid.AxisOption("[Hypertile] VAE Max Tile Size", int, xyz_grid.apply_override("hypertile_max_tile_vae"), confirm=xyz_grid.confirm_range(0, 512, '[Hypertile] VAE Max Tile Size')),
|
||||||
|
xyz_grid.AxisOption("[Hypertile] VAE Swap Size", int, xyz_grid.apply_override("hypertile_swap_size_vae"), confirm=xyz_grid.confirm_range(0, 64, '[Hypertile] VAE Swap Size')),
|
||||||
|
])
|
||||||
|
|
||||||
|
|
||||||
script_callbacks.on_ui_settings(on_ui_settings)
|
script_callbacks.on_ui_settings(on_ui_settings)
|
||||||
script_callbacks.on_before_ui(add_axis_options)
|
script_callbacks.on_before_ui(add_axis_options)
|
||||||
|
|||||||
@@ -1,51 +0,0 @@
|
|||||||
from modules import scripts
|
|
||||||
from modules.shared import opts
|
|
||||||
|
|
||||||
xyz_grid = [x for x in scripts.scripts_data if x.script_class.__module__ == "xyz_grid.py"][0].module
|
|
||||||
|
|
||||||
def int_applier(value_name:str, min_range:int = -1, max_range:int = -1):
|
|
||||||
"""
|
|
||||||
Returns a function that applies the given value to the given value_name in opts.data.
|
|
||||||
"""
|
|
||||||
def validate(value_name:str, value:str):
|
|
||||||
value = int(value)
|
|
||||||
# validate value
|
|
||||||
if not min_range == -1:
|
|
||||||
assert value >= min_range, f"Value {value} for {value_name} must be greater than or equal to {min_range}"
|
|
||||||
if not max_range == -1:
|
|
||||||
assert value <= max_range, f"Value {value} for {value_name} must be less than or equal to {max_range}"
|
|
||||||
def apply_int(p, x, xs):
|
|
||||||
validate(value_name, x)
|
|
||||||
opts.data[value_name] = int(x)
|
|
||||||
return apply_int
|
|
||||||
|
|
||||||
def bool_applier(value_name:str):
|
|
||||||
"""
|
|
||||||
Returns a function that applies the given value to the given value_name in opts.data.
|
|
||||||
"""
|
|
||||||
def validate(value_name:str, value:str):
|
|
||||||
assert value.lower() in ["true", "false"], f"Value {value} for {value_name} must be either true or false"
|
|
||||||
def apply_bool(p, x, xs):
|
|
||||||
validate(value_name, x)
|
|
||||||
value_boolean = x.lower() == "true"
|
|
||||||
opts.data[value_name] = value_boolean
|
|
||||||
return apply_bool
|
|
||||||
|
|
||||||
def add_axis_options():
|
|
||||||
extra_axis_options = [
|
|
||||||
xyz_grid.AxisOption("[Hypertile] Unet First pass Enabled", str, bool_applier("hypertile_enable_unet"), choices=xyz_grid.boolean_choice(reverse=True)),
|
|
||||||
xyz_grid.AxisOption("[Hypertile] Unet Second pass Enabled", str, bool_applier("hypertile_enable_unet_secondpass"), choices=xyz_grid.boolean_choice(reverse=True)),
|
|
||||||
xyz_grid.AxisOption("[Hypertile] Unet Max Depth", int, int_applier("hypertile_max_depth_unet", 0, 3), choices=lambda: [str(x) for x in range(4)]),
|
|
||||||
xyz_grid.AxisOption("[Hypertile] Unet Max Tile Size", int, int_applier("hypertile_max_tile_unet", 0, 512)),
|
|
||||||
xyz_grid.AxisOption("[Hypertile] Unet Swap Size", int, int_applier("hypertile_swap_size_unet", 0, 64)),
|
|
||||||
xyz_grid.AxisOption("[Hypertile] VAE Enabled", str, bool_applier("hypertile_enable_vae"), choices=xyz_grid.boolean_choice(reverse=True)),
|
|
||||||
xyz_grid.AxisOption("[Hypertile] VAE Max Depth", int, int_applier("hypertile_max_depth_vae", 0, 3), choices=lambda: [str(x) for x in range(4)]),
|
|
||||||
xyz_grid.AxisOption("[Hypertile] VAE Max Tile Size", int, int_applier("hypertile_max_tile_vae", 0, 512)),
|
|
||||||
xyz_grid.AxisOption("[Hypertile] VAE Swap Size", int, int_applier("hypertile_swap_size_vae", 0, 64)),
|
|
||||||
]
|
|
||||||
set_a = {opt.label for opt in xyz_grid.axis_options}
|
|
||||||
set_b = {opt.label for opt in extra_axis_options}
|
|
||||||
if set_a.intersection(set_b):
|
|
||||||
return
|
|
||||||
|
|
||||||
xyz_grid.axis_options.extend(extra_axis_options)
|
|
||||||
+6
-6
@@ -34,14 +34,14 @@ class ScriptPostprocessingAutosizedCrop(scripts_postprocessing.ScriptPostprocess
|
|||||||
with ui_components.InputAccordion(False, label="Auto-sized crop") as enable:
|
with ui_components.InputAccordion(False, label="Auto-sized crop") as enable:
|
||||||
gr.Markdown('Each image is center-cropped with an automatically chosen width and height.')
|
gr.Markdown('Each image is center-cropped with an automatically chosen width and height.')
|
||||||
with gr.Row():
|
with gr.Row():
|
||||||
mindim = gr.Slider(minimum=64, maximum=2048, step=8, label="Dimension lower bound", value=384, elem_id="postprocess_multicrop_mindim")
|
mindim = gr.Slider(minimum=64, maximum=2048, step=8, label="Dimension lower bound", value=384, elem_id=self.elem_id_suffix("postprocess_multicrop_mindim"))
|
||||||
maxdim = gr.Slider(minimum=64, maximum=2048, step=8, label="Dimension upper bound", value=768, elem_id="postprocess_multicrop_maxdim")
|
maxdim = gr.Slider(minimum=64, maximum=2048, step=8, label="Dimension upper bound", value=768, elem_id=self.elem_id_suffix("postprocess_multicrop_maxdim"))
|
||||||
with gr.Row():
|
with gr.Row():
|
||||||
minarea = gr.Slider(minimum=64 * 64, maximum=2048 * 2048, step=1, label="Area lower bound", value=64 * 64, elem_id="postprocess_multicrop_minarea")
|
minarea = gr.Slider(minimum=64 * 64, maximum=2048 * 2048, step=1, label="Area lower bound", value=64 * 64, elem_id=self.elem_id_suffix("postprocess_multicrop_minarea"))
|
||||||
maxarea = gr.Slider(minimum=64 * 64, maximum=2048 * 2048, step=1, label="Area upper bound", value=640 * 640, elem_id="postprocess_multicrop_maxarea")
|
maxarea = gr.Slider(minimum=64 * 64, maximum=2048 * 2048, step=1, label="Area upper bound", value=640 * 640, elem_id=self.elem_id_suffix("postprocess_multicrop_maxarea"))
|
||||||
with gr.Row():
|
with gr.Row():
|
||||||
objective = gr.Radio(["Maximize area", "Minimize error"], value="Maximize area", label="Resizing objective", elem_id="postprocess_multicrop_objective")
|
objective = gr.Radio(["Maximize area", "Minimize error"], value="Maximize area", label="Resizing objective", elem_id=self.elem_id_suffix("postprocess_multicrop_objective"))
|
||||||
threshold = gr.Slider(minimum=0, maximum=1, step=0.01, label="Error threshold", value=0.1, elem_id="postprocess_multicrop_threshold")
|
threshold = gr.Slider(minimum=0, maximum=1, step=0.01, label="Error threshold", value=0.1, elem_id=self.elem_id_suffix("postprocess_multicrop_threshold"))
|
||||||
|
|
||||||
return {
|
return {
|
||||||
"enable": enable,
|
"enable": enable,
|
||||||
+4
-4
@@ -11,10 +11,10 @@ class ScriptPostprocessingFocalCrop(scripts_postprocessing.ScriptPostprocessing)
|
|||||||
|
|
||||||
def ui(self):
|
def ui(self):
|
||||||
with ui_components.InputAccordion(False, label="Auto focal point crop") as enable:
|
with ui_components.InputAccordion(False, label="Auto focal point crop") as enable:
|
||||||
face_weight = gr.Slider(label='Focal point face weight', value=0.9, minimum=0.0, maximum=1.0, step=0.05, elem_id="postprocess_focal_crop_face_weight")
|
face_weight = gr.Slider(label='Focal point face weight', value=0.9, minimum=0.0, maximum=1.0, step=0.05, elem_id=self.elem_id_suffix("postprocess_focal_crop_face_weight"))
|
||||||
entropy_weight = gr.Slider(label='Focal point entropy weight', value=0.15, minimum=0.0, maximum=1.0, step=0.05, elem_id="postprocess_focal_crop_entropy_weight")
|
entropy_weight = gr.Slider(label='Focal point entropy weight', value=0.15, minimum=0.0, maximum=1.0, step=0.05, elem_id=self.elem_id_suffix("postprocess_focal_crop_entropy_weight"))
|
||||||
edges_weight = gr.Slider(label='Focal point edges weight', value=0.5, minimum=0.0, maximum=1.0, step=0.05, elem_id="postprocess_focal_crop_edges_weight")
|
edges_weight = gr.Slider(label='Focal point edges weight', value=0.5, minimum=0.0, maximum=1.0, step=0.05, elem_id=self.elem_id_suffix("postprocess_focal_crop_edges_weight"))
|
||||||
debug = gr.Checkbox(label='Create debug image', elem_id="train_process_focal_crop_debug")
|
debug = gr.Checkbox(label='Create debug image', elem_id=self.elem_id_suffix("train_process_focal_crop_debug"))
|
||||||
|
|
||||||
return {
|
return {
|
||||||
"enable": enable,
|
"enable": enable,
|
||||||
+3
-3
@@ -35,8 +35,8 @@ class ScriptPostprocessingSplitOversized(scripts_postprocessing.ScriptPostproces
|
|||||||
def ui(self):
|
def ui(self):
|
||||||
with ui_components.InputAccordion(False, label="Split oversized images") as enable:
|
with ui_components.InputAccordion(False, label="Split oversized images") as enable:
|
||||||
with gr.Row():
|
with gr.Row():
|
||||||
split_threshold = gr.Slider(label='Threshold', value=0.5, minimum=0.0, maximum=1.0, step=0.05, elem_id="postprocess_split_threshold")
|
split_threshold = gr.Slider(label='Threshold', value=0.5, minimum=0.0, maximum=1.0, step=0.05, elem_id=self.elem_id_suffix("postprocess_split_threshold"))
|
||||||
overlap_ratio = gr.Slider(label='Overlap ratio', value=0.2, minimum=0.0, maximum=0.9, step=0.05, elem_id="postprocess_overlap_ratio")
|
overlap_ratio = gr.Slider(label='Overlap ratio', value=0.2, minimum=0.0, maximum=0.9, step=0.05, elem_id=self.elem_id_suffix("postprocess_overlap_ratio"))
|
||||||
|
|
||||||
return {
|
return {
|
||||||
"enable": enable,
|
"enable": enable,
|
||||||
@@ -61,7 +61,7 @@ class ScriptPostprocessingSplitOversized(scripts_postprocessing.ScriptPostproces
|
|||||||
ratio = (pp.image.height * width) / (pp.image.width * height)
|
ratio = (pp.image.height * width) / (pp.image.width * height)
|
||||||
inverse_xy = True
|
inverse_xy = True
|
||||||
|
|
||||||
if ratio >= 1.0 and ratio > split_threshold:
|
if ratio >= 1.0 or ratio > split_threshold:
|
||||||
return
|
return
|
||||||
|
|
||||||
result, *others = split_pic(pp.image, inverse_xy, width, height, overlap_ratio)
|
result, *others = split_pic(pp.image, inverse_xy, width, height, overlap_ratio)
|
||||||
@@ -1,36 +1,69 @@
|
|||||||
// Stable Diffusion WebUI - Bracket checker
|
// Stable Diffusion WebUI - Bracket Checker
|
||||||
// By Hingashi no Florin/Bwin4L & @akx
|
// By @Bwin4L, @akx, @w-e-w, @Haoming02
|
||||||
// Counts open and closed brackets (round, square, curly) in the prompt and negative prompt text boxes in the txt2img and img2img tabs.
|
// Counts open and closed brackets (round, square, curly) in the prompt and negative prompt text boxes in the txt2img and img2img tabs.
|
||||||
// If there's a mismatch, the keyword counter turns red and if you hover on it, a tooltip tells you what's wrong.
|
// If there's a mismatch, the keyword counter turns red, and if you hover on it, a tooltip tells you what's wrong.
|
||||||
|
|
||||||
function checkBrackets(textArea, counterElt) {
|
function checkBrackets(textArea, counterElem) {
|
||||||
var counts = {};
|
const pairs = [
|
||||||
(textArea.value.match(/[(){}[\]]/g) || []).forEach(bracket => {
|
['(', ')', 'round brackets'],
|
||||||
counts[bracket] = (counts[bracket] || 0) + 1;
|
['[', ']', 'square brackets'],
|
||||||
});
|
['{', '}', 'curly brackets']
|
||||||
var errors = [];
|
];
|
||||||
|
|
||||||
function checkPair(open, close, kind) {
|
const counts = {};
|
||||||
if (counts[open] !== counts[close]) {
|
const errors = new Set();
|
||||||
errors.push(
|
let i = 0;
|
||||||
`${open}...${close} - Detected ${counts[open] || 0} opening and ${counts[close] || 0} closing ${kind}.`
|
|
||||||
);
|
while (i < textArea.value.length) {
|
||||||
|
let char = textArea.value[i];
|
||||||
|
let escaped = false;
|
||||||
|
while (char === '\\' && i + 1 < textArea.value.length) {
|
||||||
|
escaped = !escaped;
|
||||||
|
i++;
|
||||||
|
char = textArea.value[i];
|
||||||
|
}
|
||||||
|
|
||||||
|
if (escaped) {
|
||||||
|
i++;
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
for (const [open, close, label] of pairs) {
|
||||||
|
if (char === open) {
|
||||||
|
counts[label] = (counts[label] || 0) + 1;
|
||||||
|
} else if (char === close) {
|
||||||
|
counts[label] = (counts[label] || 0) - 1;
|
||||||
|
if (counts[label] < 0) {
|
||||||
|
errors.add(`Incorrect order of ${label}.`);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
checkPair('(', ')', 'round brackets');
|
i++;
|
||||||
checkPair('[', ']', 'square brackets');
|
}
|
||||||
checkPair('{', '}', 'curly brackets');
|
|
||||||
counterElt.title = errors.join('\n');
|
for (const [open, close, label] of pairs) {
|
||||||
counterElt.classList.toggle('error', errors.length !== 0);
|
if (counts[label] == undefined) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (counts[label] > 0) {
|
||||||
|
errors.add(`${open} ... ${close} - Detected ${counts[label]} more opening than closing ${label}.`);
|
||||||
|
} else if (counts[label] < 0) {
|
||||||
|
errors.add(`${open} ... ${close} - Detected ${-counts[label]} more closing than opening ${label}.`);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
counterElem.title = [...errors].join('\n');
|
||||||
|
counterElem.classList.toggle('error', errors.size !== 0);
|
||||||
}
|
}
|
||||||
|
|
||||||
function setupBracketChecking(id_prompt, id_counter) {
|
function setupBracketChecking(id_prompt, id_counter) {
|
||||||
var textarea = gradioApp().querySelector("#" + id_prompt + " > label > textarea");
|
const textarea = gradioApp().querySelector(`#${id_prompt} > label > textarea`);
|
||||||
var counter = gradioApp().getElementById(id_counter);
|
const counter = gradioApp().getElementById(id_counter);
|
||||||
|
|
||||||
if (textarea && counter) {
|
if (textarea && counter) {
|
||||||
textarea.addEventListener("input", () => checkBrackets(textarea, counter));
|
onEdit(`${id_prompt}_BracketChecking`, textarea, 400, () => checkBrackets(textarea, counter));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -3,6 +3,7 @@ import gradio as gr
|
|||||||
import math
|
import math
|
||||||
from modules.ui_components import InputAccordion
|
from modules.ui_components import InputAccordion
|
||||||
import modules.scripts as scripts
|
import modules.scripts as scripts
|
||||||
|
from modules.torch_utils import float64
|
||||||
|
|
||||||
|
|
||||||
class SoftInpaintingSettings:
|
class SoftInpaintingSettings:
|
||||||
@@ -57,10 +58,14 @@ def latent_blend(settings, a, b, t):
|
|||||||
|
|
||||||
# NOTE: We use inplace operations wherever possible.
|
# NOTE: We use inplace operations wherever possible.
|
||||||
|
|
||||||
|
if len(t.shape) == 3:
|
||||||
# [4][w][h] to [1][4][w][h]
|
# [4][w][h] to [1][4][w][h]
|
||||||
t2 = t.unsqueeze(0)
|
t2 = t.unsqueeze(0)
|
||||||
# [4][w][h] to [1][1][w][h] - the [4] seem redundant.
|
# [4][w][h] to [1][1][w][h] - the [4] seem redundant.
|
||||||
t3 = t[0].unsqueeze(0).unsqueeze(0)
|
t3 = t[0].unsqueeze(0).unsqueeze(0)
|
||||||
|
else:
|
||||||
|
t2 = t
|
||||||
|
t3 = t[:, 0][:, None]
|
||||||
|
|
||||||
one_minus_t2 = 1 - t2
|
one_minus_t2 = 1 - t2
|
||||||
one_minus_t3 = 1 - t3
|
one_minus_t3 = 1 - t3
|
||||||
@@ -75,13 +80,11 @@ def latent_blend(settings, a, b, t):
|
|||||||
|
|
||||||
# Calculate the magnitude of the interpolated vectors. (We will remove this magnitude.)
|
# Calculate the magnitude of the interpolated vectors. (We will remove this magnitude.)
|
||||||
# 64-bit operations are used here to allow large exponents.
|
# 64-bit operations are used here to allow large exponents.
|
||||||
current_magnitude = torch.norm(image_interp, p=2, dim=1, keepdim=True).to(torch.float64).add_(0.00001)
|
current_magnitude = torch.norm(image_interp, p=2, dim=1, keepdim=True).to(float64(image_interp)).add_(0.00001)
|
||||||
|
|
||||||
# Interpolate the powered magnitudes, then un-power them (bring them back to a power of 1).
|
# Interpolate the powered magnitudes, then un-power them (bring them back to a power of 1).
|
||||||
a_magnitude = torch.norm(a, p=2, dim=1, keepdim=True).to(torch.float64).pow_(
|
a_magnitude = torch.norm(a, p=2, dim=1, keepdim=True).to(float64(a)).pow_(settings.inpaint_detail_preservation) * one_minus_t3
|
||||||
settings.inpaint_detail_preservation) * one_minus_t3
|
b_magnitude = torch.norm(b, p=2, dim=1, keepdim=True).to(float64(b)).pow_(settings.inpaint_detail_preservation) * t3
|
||||||
b_magnitude = torch.norm(b, p=2, dim=1, keepdim=True).to(torch.float64).pow_(
|
|
||||||
settings.inpaint_detail_preservation) * t3
|
|
||||||
desired_magnitude = a_magnitude
|
desired_magnitude = a_magnitude
|
||||||
desired_magnitude.add_(b_magnitude).pow_(1 / settings.inpaint_detail_preservation)
|
desired_magnitude.add_(b_magnitude).pow_(1 / settings.inpaint_detail_preservation)
|
||||||
del a_magnitude, b_magnitude, t3, one_minus_t3
|
del a_magnitude, b_magnitude, t3, one_minus_t3
|
||||||
@@ -104,7 +107,7 @@ def latent_blend(settings, a, b, t):
|
|||||||
|
|
||||||
def get_modified_nmask(settings, nmask, sigma):
|
def get_modified_nmask(settings, nmask, sigma):
|
||||||
"""
|
"""
|
||||||
Converts a negative mask representing the transparency of the original latent vectors being overlayed
|
Converts a negative mask representing the transparency of the original latent vectors being overlaid
|
||||||
to a mask that is scaled according to the denoising strength for this step.
|
to a mask that is scaled according to the denoising strength for this step.
|
||||||
|
|
||||||
Where:
|
Where:
|
||||||
@@ -135,7 +138,10 @@ def apply_adaptive_masks(
|
|||||||
from PIL import Image, ImageOps, ImageFilter
|
from PIL import Image, ImageOps, ImageFilter
|
||||||
|
|
||||||
# TODO: Bias the blending according to the latent mask, add adjustable parameter for bias control.
|
# TODO: Bias the blending according to the latent mask, add adjustable parameter for bias control.
|
||||||
|
if len(nmask.shape) == 3:
|
||||||
latent_mask = nmask[0].float()
|
latent_mask = nmask[0].float()
|
||||||
|
else:
|
||||||
|
latent_mask = nmask[:, 0].float()
|
||||||
# convert the original mask into a form we use to scale distances for thresholding
|
# convert the original mask into a form we use to scale distances for thresholding
|
||||||
mask_scalar = 1 - (torch.clamp(latent_mask, min=0, max=1) ** (settings.mask_blend_scale / 2))
|
mask_scalar = 1 - (torch.clamp(latent_mask, min=0, max=1) ** (settings.mask_blend_scale / 2))
|
||||||
mask_scalar = (0.5 * (1 - settings.composite_mask_influence)
|
mask_scalar = (0.5 * (1 - settings.composite_mask_influence)
|
||||||
@@ -157,7 +163,14 @@ def apply_adaptive_masks(
|
|||||||
percentile_min=0.25, percentile_max=0.75, min_width=1)
|
percentile_min=0.25, percentile_max=0.75, min_width=1)
|
||||||
|
|
||||||
# The distance at which opacity of original decreases to 50%
|
# The distance at which opacity of original decreases to 50%
|
||||||
|
if len(mask_scalar.shape) == 3:
|
||||||
|
if mask_scalar.shape[0] > i:
|
||||||
|
half_weighted_distance = settings.composite_difference_threshold * mask_scalar[i]
|
||||||
|
else:
|
||||||
|
half_weighted_distance = settings.composite_difference_threshold * mask_scalar[0]
|
||||||
|
else:
|
||||||
half_weighted_distance = settings.composite_difference_threshold * mask_scalar
|
half_weighted_distance = settings.composite_difference_threshold * mask_scalar
|
||||||
|
|
||||||
converted_mask = converted_mask / half_weighted_distance
|
converted_mask = converted_mask / half_weighted_distance
|
||||||
|
|
||||||
converted_mask = 1 / (1 + converted_mask ** settings.composite_difference_contrast)
|
converted_mask = 1 / (1 + converted_mask ** settings.composite_difference_contrast)
|
||||||
|
|||||||
@@ -1,14 +1,9 @@
|
|||||||
<div class='card' style={style} onclick={card_clicked} data-name="{name}" {sort_keys}>
|
<div class="card" style="{style}" onclick="{card_clicked}" data-name="{name}" {sort_keys}>
|
||||||
{background_image}
|
{background_image}
|
||||||
<div class="button-row">
|
<div class="button-row">{copy_path_button}{metadata_button}{edit_button}</div>
|
||||||
{metadata_button}
|
<div class="actions">
|
||||||
{edit_button}
|
<div class="additional">{search_terms}</div>
|
||||||
</div>
|
<span class="name">{name}</span>
|
||||||
<div class='actions'>
|
<span class="description">{description}</span>
|
||||||
<div class='additional'>
|
|
||||||
<span style="display:none" class='search_term{search_only}'>{search_term}</span>
|
|
||||||
</div>
|
|
||||||
<span class='name'>{name}</span>
|
|
||||||
<span class='description'>{description}</span>
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -0,0 +1,5 @@
|
|||||||
|
<div class="copy-path-button card-button"
|
||||||
|
title="Copy path to clipboard"
|
||||||
|
onclick="extraNetworksCopyCardPath(event)"
|
||||||
|
data-clipboard-text="{filename}">
|
||||||
|
</div>
|
||||||
@@ -0,0 +1,4 @@
|
|||||||
|
<div class="edit-button card-button"
|
||||||
|
title="Edit metadata"
|
||||||
|
onclick="extraNetworksEditUserMetadata(event, '{tabname}', '{extra_networks_tabname}')">
|
||||||
|
</div>
|
||||||
@@ -0,0 +1,4 @@
|
|||||||
|
<div class="metadata-button card-button"
|
||||||
|
title="Show internal metadata"
|
||||||
|
onclick="extraNetworksRequestMetadata(event, '{extra_networks_tabname}')">
|
||||||
|
</div>
|
||||||
@@ -0,0 +1,8 @@
|
|||||||
|
<div class="extra-network-pane-content-dirs">
|
||||||
|
<div id='{tabname}_{extra_networks_tabname}_dirs' class='extra-network-dirs'>
|
||||||
|
{dirs_html}
|
||||||
|
</div>
|
||||||
|
<div id='{tabname}_{extra_networks_tabname}_cards' class='extra-network-cards'>
|
||||||
|
{items_html}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
@@ -0,0 +1,8 @@
|
|||||||
|
<div class="extra-network-pane-content-tree resize-handle-row">
|
||||||
|
<div id='{tabname}_{extra_networks_tabname}_tree' class='extra-network-tree' style='flex-basis: {extra_networks_tree_view_default_width}px'>
|
||||||
|
{tree_html}
|
||||||
|
</div>
|
||||||
|
<div id='{tabname}_{extra_networks_tabname}_cards' class='extra-network-cards' style='flex-grow: 1;'>
|
||||||
|
{items_html}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
@@ -0,0 +1,81 @@
|
|||||||
|
<div id='{tabname}_{extra_networks_tabname}_pane' class='extra-network-pane {tree_view_div_default_display_class}'>
|
||||||
|
<div class="extra-network-control" id="{tabname}_{extra_networks_tabname}_controls" style="display:none" >
|
||||||
|
<div class="extra-network-control--search">
|
||||||
|
<input
|
||||||
|
id="{tabname}_{extra_networks_tabname}_extra_search"
|
||||||
|
class="extra-network-control--search-text"
|
||||||
|
type="search"
|
||||||
|
placeholder="Search"
|
||||||
|
>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<small>Sort: </small>
|
||||||
|
<div
|
||||||
|
id="{tabname}_{extra_networks_tabname}_extra_sort_path"
|
||||||
|
class="extra-network-control--sort{sort_path_active}"
|
||||||
|
data-sortkey="default"
|
||||||
|
title="Sort by path"
|
||||||
|
onclick="extraNetworksControlSortOnClick(event, '{tabname}', '{extra_networks_tabname}');"
|
||||||
|
>
|
||||||
|
<i class="extra-network-control--icon extra-network-control--sort-icon"></i>
|
||||||
|
</div>
|
||||||
|
<div
|
||||||
|
id="{tabname}_{extra_networks_tabname}_extra_sort_name"
|
||||||
|
class="extra-network-control--sort{sort_name_active}"
|
||||||
|
data-sortkey="name"
|
||||||
|
title="Sort by name"
|
||||||
|
onclick="extraNetworksControlSortOnClick(event, '{tabname}', '{extra_networks_tabname}');"
|
||||||
|
>
|
||||||
|
<i class="extra-network-control--icon extra-network-control--sort-icon"></i>
|
||||||
|
</div>
|
||||||
|
<div
|
||||||
|
id="{tabname}_{extra_networks_tabname}_extra_sort_date_created"
|
||||||
|
class="extra-network-control--sort{sort_date_created_active}"
|
||||||
|
data-sortkey="date_created"
|
||||||
|
title="Sort by date created"
|
||||||
|
onclick="extraNetworksControlSortOnClick(event, '{tabname}', '{extra_networks_tabname}');"
|
||||||
|
>
|
||||||
|
<i class="extra-network-control--icon extra-network-control--sort-icon"></i>
|
||||||
|
</div>
|
||||||
|
<div
|
||||||
|
id="{tabname}_{extra_networks_tabname}_extra_sort_date_modified"
|
||||||
|
class="extra-network-control--sort{sort_date_modified_active}"
|
||||||
|
data-sortkey="date_modified"
|
||||||
|
title="Sort by date modified"
|
||||||
|
onclick="extraNetworksControlSortOnClick(event, '{tabname}', '{extra_networks_tabname}');"
|
||||||
|
>
|
||||||
|
<i class="extra-network-control--icon extra-network-control--sort-icon"></i>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<small> </small>
|
||||||
|
<div
|
||||||
|
id="{tabname}_{extra_networks_tabname}_extra_sort_dir"
|
||||||
|
class="extra-network-control--sort-dir"
|
||||||
|
data-sortdir="{data_sortdir}"
|
||||||
|
title="Sort ascending"
|
||||||
|
onclick="extraNetworksControlSortDirOnClick(event, '{tabname}', '{extra_networks_tabname}');"
|
||||||
|
>
|
||||||
|
<i class="extra-network-control--icon extra-network-control--sort-dir-icon"></i>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
|
||||||
|
<small> </small>
|
||||||
|
<div
|
||||||
|
id="{tabname}_{extra_networks_tabname}_extra_tree_view"
|
||||||
|
class="extra-network-control--tree-view {tree_view_btn_extra_class}"
|
||||||
|
title="Enable Tree View"
|
||||||
|
onclick="extraNetworksControlTreeViewOnClick(event, '{tabname}', '{extra_networks_tabname}');"
|
||||||
|
>
|
||||||
|
<i class="extra-network-control--icon extra-network-control--tree-view-icon"></i>
|
||||||
|
</div>
|
||||||
|
<div
|
||||||
|
id="{tabname}_{extra_networks_tabname}_extra_refresh"
|
||||||
|
class="extra-network-control--refresh"
|
||||||
|
title="Refresh page"
|
||||||
|
onclick="extraNetworksControlRefreshOnClick(event, '{tabname}', '{extra_networks_tabname}');"
|
||||||
|
>
|
||||||
|
<i class="extra-network-control--icon extra-network-control--refresh-icon"></i>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
{pane_content}
|
||||||
|
</div>
|
||||||
@@ -0,0 +1,23 @@
|
|||||||
|
<span data-filterable-item-text hidden>{search_terms}</span>
|
||||||
|
<div class="tree-list-content {subclass}"
|
||||||
|
type="button"
|
||||||
|
onclick="extraNetworksTreeOnClick(event, '{tabname}', '{extra_networks_tabname}');{onclick_extra}"
|
||||||
|
data-path="{data_path}"
|
||||||
|
data-hash="{data_hash}"
|
||||||
|
>
|
||||||
|
<span class='tree-list-item-action tree-list-item-action--leading'>
|
||||||
|
{action_list_item_action_leading}
|
||||||
|
</span>
|
||||||
|
<span class="tree-list-item-visual tree-list-item-visual--leading">
|
||||||
|
{action_list_item_visual_leading}
|
||||||
|
</span>
|
||||||
|
<span class="tree-list-item-label tree-list-item-label--truncate">
|
||||||
|
{action_list_item_label}
|
||||||
|
</span>
|
||||||
|
<span class="tree-list-item-visual tree-list-item-visual--trailing">
|
||||||
|
{action_list_item_visual_trailing}
|
||||||
|
</span>
|
||||||
|
<span class="tree-list-item-action tree-list-item-action--trailing">
|
||||||
|
{action_list_item_action_trailing}
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
+2
-2
@@ -1,7 +1,7 @@
|
|||||||
<div>
|
<div>
|
||||||
<a href="{api_docs}">API</a>
|
<a href="{api_docs}" target="_blank">API</a>
|
||||||
•
|
•
|
||||||
<a href="https://github.com/AUTOMATIC1111/stable-diffusion-webui">Github</a>
|
<a href="https://github.com/AUTOMATIC1111/stable-diffusion-webui">GitHub</a>
|
||||||
•
|
•
|
||||||
<a href="https://gradio.app">Gradio</a>
|
<a href="https://gradio.app">Gradio</a>
|
||||||
•
|
•
|
||||||
|
|||||||
@@ -50,17 +50,17 @@ function dimensionChange(e, is_width, is_height) {
|
|||||||
var scaledx = targetElement.naturalWidth * viewportscale;
|
var scaledx = targetElement.naturalWidth * viewportscale;
|
||||||
var scaledy = targetElement.naturalHeight * viewportscale;
|
var scaledy = targetElement.naturalHeight * viewportscale;
|
||||||
|
|
||||||
var cleintRectTop = (viewportOffset.top + window.scrollY);
|
var clientRectTop = (viewportOffset.top + window.scrollY);
|
||||||
var cleintRectLeft = (viewportOffset.left + window.scrollX);
|
var clientRectLeft = (viewportOffset.left + window.scrollX);
|
||||||
var cleintRectCentreY = cleintRectTop + (targetElement.clientHeight / 2);
|
var clientRectCentreY = clientRectTop + (targetElement.clientHeight / 2);
|
||||||
var cleintRectCentreX = cleintRectLeft + (targetElement.clientWidth / 2);
|
var clientRectCentreX = clientRectLeft + (targetElement.clientWidth / 2);
|
||||||
|
|
||||||
var arscale = Math.min(scaledx / currentWidth, scaledy / currentHeight);
|
var arscale = Math.min(scaledx / currentWidth, scaledy / currentHeight);
|
||||||
var arscaledx = currentWidth * arscale;
|
var arscaledx = currentWidth * arscale;
|
||||||
var arscaledy = currentHeight * arscale;
|
var arscaledy = currentHeight * arscale;
|
||||||
|
|
||||||
var arRectTop = cleintRectCentreY - (arscaledy / 2);
|
var arRectTop = clientRectCentreY - (arscaledy / 2);
|
||||||
var arRectLeft = cleintRectCentreX - (arscaledx / 2);
|
var arRectLeft = clientRectCentreX - (arscaledx / 2);
|
||||||
var arRectWidth = arscaledx;
|
var arRectWidth = arscaledx;
|
||||||
var arRectHeight = arscaledy;
|
var arRectHeight = arscaledy;
|
||||||
|
|
||||||
|
|||||||
+11
-24
@@ -8,9 +8,6 @@ var contextMenuInit = function() {
|
|||||||
};
|
};
|
||||||
|
|
||||||
function showContextMenu(event, element, menuEntries) {
|
function showContextMenu(event, element, menuEntries) {
|
||||||
let posx = event.clientX + document.body.scrollLeft + document.documentElement.scrollLeft;
|
|
||||||
let posy = event.clientY + document.body.scrollTop + document.documentElement.scrollTop;
|
|
||||||
|
|
||||||
let oldMenu = gradioApp().querySelector('#context-menu');
|
let oldMenu = gradioApp().querySelector('#context-menu');
|
||||||
if (oldMenu) {
|
if (oldMenu) {
|
||||||
oldMenu.remove();
|
oldMenu.remove();
|
||||||
@@ -23,10 +20,8 @@ var contextMenuInit = function() {
|
|||||||
contextMenu.style.background = baseStyle.background;
|
contextMenu.style.background = baseStyle.background;
|
||||||
contextMenu.style.color = baseStyle.color;
|
contextMenu.style.color = baseStyle.color;
|
||||||
contextMenu.style.fontFamily = baseStyle.fontFamily;
|
contextMenu.style.fontFamily = baseStyle.fontFamily;
|
||||||
contextMenu.style.top = posy + 'px';
|
contextMenu.style.top = event.pageY + 'px';
|
||||||
contextMenu.style.left = posx + 'px';
|
contextMenu.style.left = event.pageX + 'px';
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
const contextMenuList = document.createElement('ul');
|
const contextMenuList = document.createElement('ul');
|
||||||
contextMenuList.className = 'context-menu-items';
|
contextMenuList.className = 'context-menu-items';
|
||||||
@@ -43,21 +38,6 @@ var contextMenuInit = function() {
|
|||||||
});
|
});
|
||||||
|
|
||||||
gradioApp().appendChild(contextMenu);
|
gradioApp().appendChild(contextMenu);
|
||||||
|
|
||||||
let menuWidth = contextMenu.offsetWidth + 4;
|
|
||||||
let menuHeight = contextMenu.offsetHeight + 4;
|
|
||||||
|
|
||||||
let windowWidth = window.innerWidth;
|
|
||||||
let windowHeight = window.innerHeight;
|
|
||||||
|
|
||||||
if ((windowWidth - posx) < menuWidth) {
|
|
||||||
contextMenu.style.left = windowWidth - menuWidth + "px";
|
|
||||||
}
|
|
||||||
|
|
||||||
if ((windowHeight - posy) < menuHeight) {
|
|
||||||
contextMenu.style.top = windowHeight - menuHeight + "px";
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
function appendContextMenuOption(targetElementSelector, entryName, entryFunction) {
|
function appendContextMenuOption(targetElementSelector, entryName, entryFunction) {
|
||||||
@@ -107,17 +87,24 @@ var contextMenuInit = function() {
|
|||||||
oldMenu.remove();
|
oldMenu.remove();
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
gradioApp().addEventListener("contextmenu", function(e) {
|
['contextmenu', 'touchstart'].forEach((eventType) => {
|
||||||
|
gradioApp().addEventListener(eventType, function(e) {
|
||||||
|
let ev = e;
|
||||||
|
if (eventType.startsWith('touch')) {
|
||||||
|
if (e.touches.length !== 2) return;
|
||||||
|
ev = e.touches[0];
|
||||||
|
}
|
||||||
let oldMenu = gradioApp().querySelector('#context-menu');
|
let oldMenu = gradioApp().querySelector('#context-menu');
|
||||||
if (oldMenu) {
|
if (oldMenu) {
|
||||||
oldMenu.remove();
|
oldMenu.remove();
|
||||||
}
|
}
|
||||||
menuSpecs.forEach(function(v, k) {
|
menuSpecs.forEach(function(v, k) {
|
||||||
if (e.composedPath()[0].matches(k)) {
|
if (e.composedPath()[0].matches(k)) {
|
||||||
showContextMenu(e, e.composedPath()[0], v);
|
showContextMenu(ev, e.composedPath()[0], v);
|
||||||
e.preventDefault();
|
e.preventDefault();
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
}, {passive: false});
|
||||||
});
|
});
|
||||||
eventListenerApplied = true;
|
eventListenerApplied = true;
|
||||||
|
|
||||||
|
|||||||
Vendored
+31
-5
@@ -56,6 +56,15 @@ function eventHasFiles(e) {
|
|||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function isURL(url) {
|
||||||
|
try {
|
||||||
|
const _ = new URL(url);
|
||||||
|
return true;
|
||||||
|
} catch {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
function dragDropTargetIsPrompt(target) {
|
function dragDropTargetIsPrompt(target) {
|
||||||
if (target?.placeholder && target?.placeholder.indexOf("Prompt") >= 0) return true;
|
if (target?.placeholder && target?.placeholder.indexOf("Prompt") >= 0) return true;
|
||||||
if (target?.parentNode?.parentNode?.className?.indexOf("prompt") > 0) return true;
|
if (target?.parentNode?.parentNode?.className?.indexOf("prompt") > 0) return true;
|
||||||
@@ -74,22 +83,39 @@ window.document.addEventListener('dragover', e => {
|
|||||||
e.dataTransfer.dropEffect = 'copy';
|
e.dataTransfer.dropEffect = 'copy';
|
||||||
});
|
});
|
||||||
|
|
||||||
window.document.addEventListener('drop', e => {
|
window.document.addEventListener('drop', async e => {
|
||||||
const target = e.composedPath()[0];
|
const target = e.composedPath()[0];
|
||||||
if (!eventHasFiles(e)) return;
|
const url = e.dataTransfer.getData('text/uri-list') || e.dataTransfer.getData('text/plain');
|
||||||
|
if (!eventHasFiles(e) && !isURL(url)) return;
|
||||||
|
|
||||||
if (dragDropTargetIsPrompt(target)) {
|
if (dragDropTargetIsPrompt(target)) {
|
||||||
e.stopPropagation();
|
e.stopPropagation();
|
||||||
e.preventDefault();
|
e.preventDefault();
|
||||||
|
|
||||||
let prompt_target = get_tab_index('tabs') == 1 ? "img2img_prompt_image" : "txt2img_prompt_image";
|
const isImg2img = get_tab_index('tabs') == 1;
|
||||||
|
let prompt_image_target = isImg2img ? "img2img_prompt_image" : "txt2img_prompt_image";
|
||||||
|
|
||||||
const imgParent = gradioApp().getElementById(prompt_target);
|
const imgParent = gradioApp().getElementById(prompt_image_target);
|
||||||
const files = e.dataTransfer.files;
|
const files = e.dataTransfer.files;
|
||||||
const fileInput = imgParent.querySelector('input[type="file"]');
|
const fileInput = imgParent.querySelector('input[type="file"]');
|
||||||
if (fileInput) {
|
if (eventHasFiles(e) && fileInput) {
|
||||||
fileInput.files = files;
|
fileInput.files = files;
|
||||||
fileInput.dispatchEvent(new Event('change'));
|
fileInput.dispatchEvent(new Event('change'));
|
||||||
|
} else if (url) {
|
||||||
|
try {
|
||||||
|
const request = await fetch(url);
|
||||||
|
if (!request.ok) {
|
||||||
|
console.error('Error fetching URL:', url, request.status);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
const data = new DataTransfer();
|
||||||
|
data.items.add(new File([await request.blob()], 'image.png'));
|
||||||
|
fileInput.files = data.files;
|
||||||
|
fileInput.dispatchEvent(new Event('change'));
|
||||||
|
} catch (error) {
|
||||||
|
console.error('Error fetching URL:', url, error);
|
||||||
|
return;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -64,6 +64,14 @@ function keyupEditAttention(event) {
|
|||||||
selectionEnd++;
|
selectionEnd++;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// deselect surrounding whitespace
|
||||||
|
while (text[selectionStart] == " " && selectionStart < selectionEnd) {
|
||||||
|
selectionStart++;
|
||||||
|
}
|
||||||
|
while (text[selectionEnd - 1] == " " && selectionEnd > selectionStart) {
|
||||||
|
selectionEnd--;
|
||||||
|
}
|
||||||
|
|
||||||
target.setSelectionRange(selectionStart, selectionEnd);
|
target.setSelectionRange(selectionStart, selectionEnd);
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -2,8 +2,11 @@
|
|||||||
function extensions_apply(_disabled_list, _update_list, disable_all) {
|
function extensions_apply(_disabled_list, _update_list, disable_all) {
|
||||||
var disable = [];
|
var disable = [];
|
||||||
var update = [];
|
var update = [];
|
||||||
|
const extensions_input = gradioApp().querySelectorAll('#extensions input[type="checkbox"]');
|
||||||
gradioApp().querySelectorAll('#extensions input[type="checkbox"]').forEach(function(x) {
|
if (extensions_input.length == 0) {
|
||||||
|
throw Error("Extensions page not yet loaded.");
|
||||||
|
}
|
||||||
|
extensions_input.forEach(function(x) {
|
||||||
if (x.name.startsWith("enable_") && !x.checked) {
|
if (x.name.startsWith("enable_") && !x.checked) {
|
||||||
disable.push(x.name.substring(7));
|
disable.push(x.name.substring(7));
|
||||||
}
|
}
|
||||||
|
|||||||
+398
-97
@@ -16,99 +16,116 @@ function toggleCss(key, css, enable) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
function setupExtraNetworksForTab(tabname) {
|
function setupExtraNetworksForTab(tabname) {
|
||||||
gradioApp().querySelector('#' + tabname + '_extra_tabs').classList.add('extra-networks');
|
function registerPrompt(tabname, id) {
|
||||||
|
var textarea = gradioApp().querySelector("#" + id + " > label > textarea");
|
||||||
|
|
||||||
var tabs = gradioApp().querySelector('#' + tabname + '_extra_tabs > div');
|
if (!activePromptTextarea[tabname]) {
|
||||||
var searchDiv = gradioApp().getElementById(tabname + '_extra_search');
|
activePromptTextarea[tabname] = textarea;
|
||||||
var search = searchDiv.querySelector('textarea');
|
}
|
||||||
var sort = gradioApp().getElementById(tabname + '_extra_sort');
|
|
||||||
var sortOrder = gradioApp().getElementById(tabname + '_extra_sortorder');
|
|
||||||
var refresh = gradioApp().getElementById(tabname + '_extra_refresh');
|
|
||||||
var showDirsDiv = gradioApp().getElementById(tabname + '_extra_show_dirs');
|
|
||||||
var showDirs = gradioApp().querySelector('#' + tabname + '_extra_show_dirs input');
|
|
||||||
var promptContainer = gradioApp().querySelector('.prompt-container-compact#' + tabname + '_prompt_container');
|
|
||||||
var negativePrompt = gradioApp().querySelector('#' + tabname + '_neg_prompt');
|
|
||||||
|
|
||||||
tabs.appendChild(searchDiv);
|
textarea.addEventListener("focus", function() {
|
||||||
tabs.appendChild(sort);
|
activePromptTextarea[tabname] = textarea;
|
||||||
tabs.appendChild(sortOrder);
|
});
|
||||||
tabs.appendChild(refresh);
|
}
|
||||||
tabs.appendChild(showDirsDiv);
|
|
||||||
|
|
||||||
var applyFilter = function() {
|
var tabnav = gradioApp().querySelector('#' + tabname + '_extra_tabs > div.tab-nav');
|
||||||
|
var controlsDiv = document.createElement('DIV');
|
||||||
|
controlsDiv.classList.add('extra-networks-controls-div');
|
||||||
|
tabnav.appendChild(controlsDiv);
|
||||||
|
tabnav.insertBefore(controlsDiv, null);
|
||||||
|
|
||||||
|
var this_tab = gradioApp().querySelector('#' + tabname + '_extra_tabs');
|
||||||
|
this_tab.querySelectorAll(":scope > [id^='" + tabname + "_']").forEach(function(elem) {
|
||||||
|
// tabname_full = {tabname}_{extra_networks_tabname}
|
||||||
|
var tabname_full = elem.id;
|
||||||
|
var search = gradioApp().querySelector("#" + tabname_full + "_extra_search");
|
||||||
|
var sort_dir = gradioApp().querySelector("#" + tabname_full + "_extra_sort_dir");
|
||||||
|
var refresh = gradioApp().querySelector("#" + tabname_full + "_extra_refresh");
|
||||||
|
var currentSort = '';
|
||||||
|
|
||||||
|
// If any of the buttons above don't exist, we want to skip this iteration of the loop.
|
||||||
|
if (!search || !sort_dir || !refresh) {
|
||||||
|
return; // `return` is equivalent of `continue` but for forEach loops.
|
||||||
|
}
|
||||||
|
|
||||||
|
var applyFilter = function(force) {
|
||||||
var searchTerm = search.value.toLowerCase();
|
var searchTerm = search.value.toLowerCase();
|
||||||
|
|
||||||
gradioApp().querySelectorAll('#' + tabname + '_extra_tabs div.card').forEach(function(elem) {
|
gradioApp().querySelectorAll('#' + tabname + '_extra_tabs div.card').forEach(function(elem) {
|
||||||
var searchOnly = elem.querySelector('.search_only');
|
var searchOnly = elem.querySelector('.search_only');
|
||||||
var text = elem.querySelector('.name').textContent.toLowerCase() + " " + elem.querySelector('.search_term').textContent.toLowerCase();
|
var text = Array.prototype.map.call(elem.querySelectorAll('.search_terms, .description'), function(t) {
|
||||||
|
return t.textContent.toLowerCase();
|
||||||
|
}).join(" ");
|
||||||
|
|
||||||
var visible = text.indexOf(searchTerm) != -1;
|
var visible = text.indexOf(searchTerm) != -1;
|
||||||
|
|
||||||
if (searchOnly && searchTerm.length < 4) {
|
if (searchOnly && searchTerm.length < 4) {
|
||||||
visible = false;
|
visible = false;
|
||||||
}
|
}
|
||||||
|
if (visible) {
|
||||||
elem.style.display = visible ? "" : "none";
|
elem.classList.remove("hidden");
|
||||||
|
} else {
|
||||||
|
elem.classList.add("hidden");
|
||||||
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
applySort();
|
applySort(force);
|
||||||
};
|
};
|
||||||
|
|
||||||
var applySort = function() {
|
var applySort = function(force) {
|
||||||
var cards = gradioApp().querySelectorAll('#' + tabname + '_extra_tabs div.card');
|
var cards = gradioApp().querySelectorAll('#' + tabname_full + ' div.card');
|
||||||
|
var parent = gradioApp().querySelector('#' + tabname_full + "_cards");
|
||||||
|
var reverse = sort_dir.dataset.sortdir == "Descending";
|
||||||
|
var activeSearchElem = gradioApp().querySelector('#' + tabname_full + "_controls .extra-network-control--sort.extra-network-control--enabled");
|
||||||
|
var sortKey = activeSearchElem ? activeSearchElem.dataset.sortkey : "default";
|
||||||
|
var sortKeyDataField = "sort" + sortKey.charAt(0).toUpperCase() + sortKey.slice(1);
|
||||||
|
var sortKeyStore = sortKey + "-" + sort_dir.dataset.sortdir + "-" + cards.length;
|
||||||
|
|
||||||
var reverse = sortOrder.classList.contains("sortReverse");
|
if (sortKeyStore == currentSort && !force) {
|
||||||
var sortKey = sort.querySelector("input").value.toLowerCase().replace("sort", "").replaceAll(" ", "_").replace(/_+$/, "").trim() || "name";
|
|
||||||
sortKey = "sort" + sortKey.charAt(0).toUpperCase() + sortKey.slice(1);
|
|
||||||
var sortKeyStore = sortKey + "-" + (reverse ? "Descending" : "Ascending") + "-" + cards.length;
|
|
||||||
|
|
||||||
if (sortKeyStore == sort.dataset.sortkey) {
|
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
sort.dataset.sortkey = sortKeyStore;
|
currentSort = sortKeyStore;
|
||||||
|
|
||||||
cards.forEach(function(card) {
|
|
||||||
card.originalParentElement = card.parentElement;
|
|
||||||
});
|
|
||||||
var sortedCards = Array.from(cards);
|
var sortedCards = Array.from(cards);
|
||||||
sortedCards.sort(function(cardA, cardB) {
|
sortedCards.sort(function(cardA, cardB) {
|
||||||
var a = cardA.dataset[sortKey];
|
var a = cardA.dataset[sortKeyDataField];
|
||||||
var b = cardB.dataset[sortKey];
|
var b = cardB.dataset[sortKeyDataField];
|
||||||
if (!isNaN(a) && !isNaN(b)) {
|
if (!isNaN(a) && !isNaN(b)) {
|
||||||
return parseInt(a) - parseInt(b);
|
return parseInt(a) - parseInt(b);
|
||||||
}
|
}
|
||||||
|
|
||||||
return (a < b ? -1 : (a > b ? 1 : 0));
|
return (a < b ? -1 : (a > b ? 1 : 0));
|
||||||
});
|
});
|
||||||
|
|
||||||
if (reverse) {
|
if (reverse) {
|
||||||
sortedCards.reverse();
|
sortedCards.reverse();
|
||||||
}
|
}
|
||||||
cards.forEach(function(card) {
|
|
||||||
card.remove();
|
parent.innerHTML = '';
|
||||||
});
|
|
||||||
|
var frag = document.createDocumentFragment();
|
||||||
sortedCards.forEach(function(card) {
|
sortedCards.forEach(function(card) {
|
||||||
card.originalParentElement.appendChild(card);
|
frag.appendChild(card);
|
||||||
});
|
});
|
||||||
|
parent.appendChild(frag);
|
||||||
};
|
};
|
||||||
|
|
||||||
search.addEventListener("input", applyFilter);
|
search.addEventListener("input", function() {
|
||||||
sortOrder.addEventListener("click", function() {
|
|
||||||
sortOrder.classList.toggle("sortReverse");
|
|
||||||
applySort();
|
|
||||||
});
|
|
||||||
applyFilter();
|
applyFilter();
|
||||||
|
});
|
||||||
|
applySort();
|
||||||
|
applyFilter();
|
||||||
|
extraNetworksApplySort[tabname_full] = applySort;
|
||||||
|
extraNetworksApplyFilter[tabname_full] = applyFilter;
|
||||||
|
|
||||||
extraNetworksApplySort[tabname] = applySort;
|
var controls = gradioApp().querySelector("#" + tabname_full + "_controls");
|
||||||
extraNetworksApplyFilter[tabname] = applyFilter;
|
controlsDiv.insertBefore(controls, null);
|
||||||
|
|
||||||
var showDirsUpdate = function() {
|
if (elem.style.display != "none") {
|
||||||
var css = '#' + tabname + '_extra_tabs .extra-network-subdirs { display: none; }';
|
extraNetworksShowControlsForPage(tabname, tabname_full);
|
||||||
toggleCss(tabname + '_extra_show_dirs_style', css, !showDirs.checked);
|
}
|
||||||
localSet('extra-networks-show-dirs', showDirs.checked ? 1 : 0);
|
});
|
||||||
};
|
|
||||||
showDirs.checked = localGet('extra-networks-show-dirs', 1) == 1;
|
registerPrompt(tabname, tabname + "_prompt");
|
||||||
showDirs.addEventListener("change", showDirsUpdate);
|
registerPrompt(tabname, tabname + "_neg_prompt");
|
||||||
showDirsUpdate();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
function extraNetworksMovePromptToTab(tabname, id, showPrompt, showNegativePrompt) {
|
function extraNetworksMovePromptToTab(tabname, id, showPrompt, showNegativePrompt) {
|
||||||
@@ -137,21 +154,42 @@ function extraNetworksMovePromptToTab(tabname, id, showPrompt, showNegativePromp
|
|||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
function extraNetworksUrelatedTabSelected(tabname) { // called from python when user selects an unrelated tab (generate)
|
function extraNetworksShowControlsForPage(tabname, tabname_full) {
|
||||||
extraNetworksMovePromptToTab(tabname, '', false, false);
|
gradioApp().querySelectorAll('#' + tabname + '_extra_tabs .extra-networks-controls-div > div').forEach(function(elem) {
|
||||||
|
var targetId = tabname_full + "_controls";
|
||||||
|
elem.style.display = elem.id == targetId ? "" : "none";
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
function extraNetworksTabSelected(tabname, id, showPrompt, showNegativePrompt) { // called from python when user selects an extra networks tab
|
|
||||||
|
function extraNetworksUnrelatedTabSelected(tabname) { // called from python when user selects an unrelated tab (generate)
|
||||||
|
extraNetworksMovePromptToTab(tabname, '', false, false);
|
||||||
|
|
||||||
|
extraNetworksShowControlsForPage(tabname, null);
|
||||||
|
}
|
||||||
|
|
||||||
|
function extraNetworksTabSelected(tabname, id, showPrompt, showNegativePrompt, tabname_full) { // called from python when user selects an extra networks tab
|
||||||
extraNetworksMovePromptToTab(tabname, id, showPrompt, showNegativePrompt);
|
extraNetworksMovePromptToTab(tabname, id, showPrompt, showNegativePrompt);
|
||||||
|
|
||||||
|
extraNetworksShowControlsForPage(tabname, tabname_full);
|
||||||
}
|
}
|
||||||
|
|
||||||
function applyExtraNetworkFilter(tabname) {
|
function applyExtraNetworkFilter(tabname_full) {
|
||||||
setTimeout(extraNetworksApplyFilter[tabname], 1);
|
var doFilter = function() {
|
||||||
|
var applyFunction = extraNetworksApplyFilter[tabname_full];
|
||||||
|
|
||||||
|
if (applyFunction) {
|
||||||
|
applyFunction(true);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
setTimeout(doFilter, 1);
|
||||||
}
|
}
|
||||||
|
|
||||||
function applyExtraNetworkSort(tabname) {
|
function applyExtraNetworkSort(tabname_full) {
|
||||||
setTimeout(extraNetworksApplySort[tabname], 1);
|
var doSort = function() {
|
||||||
|
extraNetworksApplySort[tabname_full](true);
|
||||||
|
};
|
||||||
|
setTimeout(doSort, 1);
|
||||||
}
|
}
|
||||||
|
|
||||||
var extraNetworksApplyFilter = {};
|
var extraNetworksApplyFilter = {};
|
||||||
@@ -161,28 +199,9 @@ var activePromptTextarea = {};
|
|||||||
function setupExtraNetworks() {
|
function setupExtraNetworks() {
|
||||||
setupExtraNetworksForTab('txt2img');
|
setupExtraNetworksForTab('txt2img');
|
||||||
setupExtraNetworksForTab('img2img');
|
setupExtraNetworksForTab('img2img');
|
||||||
|
|
||||||
function registerPrompt(tabname, id) {
|
|
||||||
var textarea = gradioApp().querySelector("#" + id + " > label > textarea");
|
|
||||||
|
|
||||||
if (!activePromptTextarea[tabname]) {
|
|
||||||
activePromptTextarea[tabname] = textarea;
|
|
||||||
}
|
|
||||||
|
|
||||||
textarea.addEventListener("focus", function() {
|
|
||||||
activePromptTextarea[tabname] = textarea;
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
registerPrompt('txt2img', 'txt2img_prompt');
|
|
||||||
registerPrompt('txt2img', 'txt2img_neg_prompt');
|
|
||||||
registerPrompt('img2img', 'img2img_prompt');
|
|
||||||
registerPrompt('img2img', 'img2img_neg_prompt');
|
|
||||||
}
|
}
|
||||||
|
|
||||||
onUiLoaded(setupExtraNetworks);
|
var re_extranet = /<([^:^>]+:[^:]+):[\d.]+>(.*)/s;
|
||||||
|
|
||||||
var re_extranet = /<([^:^>]+:[^:]+):[\d.]+>(.*)/;
|
|
||||||
var re_extranet_g = /<([^:^>]+:[^:]+):[\d.]+>/g;
|
var re_extranet_g = /<([^:^>]+:[^:]+):[\d.]+>/g;
|
||||||
|
|
||||||
var re_extranet_neg = /\(([^:^>]+:[\d.]+)\)/;
|
var re_extranet_neg = /\(([^:^>]+:[\d.]+)\)/;
|
||||||
@@ -191,8 +210,8 @@ function tryToRemoveExtraNetworkFromPrompt(textarea, text, isNeg) {
|
|||||||
var m = text.match(isNeg ? re_extranet_neg : re_extranet);
|
var m = text.match(isNeg ? re_extranet_neg : re_extranet);
|
||||||
var replaced = false;
|
var replaced = false;
|
||||||
var newTextareaText;
|
var newTextareaText;
|
||||||
if (m) {
|
|
||||||
var extraTextBeforeNet = opts.extra_networks_add_text_separator;
|
var extraTextBeforeNet = opts.extra_networks_add_text_separator;
|
||||||
|
if (m) {
|
||||||
var extraTextAfterNet = m[2];
|
var extraTextAfterNet = m[2];
|
||||||
var partToSearch = m[1];
|
var partToSearch = m[1];
|
||||||
var foundAtPosition = -1;
|
var foundAtPosition = -1;
|
||||||
@@ -205,7 +224,6 @@ function tryToRemoveExtraNetworkFromPrompt(textarea, text, isNeg) {
|
|||||||
}
|
}
|
||||||
return found;
|
return found;
|
||||||
});
|
});
|
||||||
|
|
||||||
if (foundAtPosition >= 0) {
|
if (foundAtPosition >= 0) {
|
||||||
if (extraTextAfterNet && newTextareaText.substr(foundAtPosition, extraTextAfterNet.length) == extraTextAfterNet) {
|
if (extraTextAfterNet && newTextareaText.substr(foundAtPosition, extraTextAfterNet.length) == extraTextAfterNet) {
|
||||||
newTextareaText = newTextareaText.substr(0, foundAtPosition) + newTextareaText.substr(foundAtPosition + extraTextAfterNet.length);
|
newTextareaText = newTextareaText.substr(0, foundAtPosition) + newTextareaText.substr(foundAtPosition + extraTextAfterNet.length);
|
||||||
@@ -215,13 +233,8 @@ function tryToRemoveExtraNetworkFromPrompt(textarea, text, isNeg) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
newTextareaText = textarea.value.replaceAll(new RegExp(text, "g"), function(found) {
|
newTextareaText = textarea.value.replaceAll(new RegExp(`((?:${extraTextBeforeNet})?${text})`, "g"), "");
|
||||||
if (found == text) {
|
replaced = (newTextareaText != textarea.value);
|
||||||
replaced = true;
|
|
||||||
return "";
|
|
||||||
}
|
|
||||||
return found;
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if (replaced) {
|
if (replaced) {
|
||||||
@@ -233,7 +246,6 @@ function tryToRemoveExtraNetworkFromPrompt(textarea, text, isNeg) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
function updatePromptArea(text, textArea, isNeg) {
|
function updatePromptArea(text, textArea, isNeg) {
|
||||||
|
|
||||||
if (!tryToRemoveExtraNetworkFromPrompt(textArea, text, isNeg)) {
|
if (!tryToRemoveExtraNetworkFromPrompt(textArea, text, isNeg)) {
|
||||||
textArea.value = textArea.value + opts.extra_networks_add_text_separator + text;
|
textArea.value = textArea.value + opts.extra_networks_add_text_separator + text;
|
||||||
}
|
}
|
||||||
@@ -264,8 +276,8 @@ function saveCardPreview(event, tabname, filename) {
|
|||||||
event.preventDefault();
|
event.preventDefault();
|
||||||
}
|
}
|
||||||
|
|
||||||
function extraNetworksSearchButton(tabs_id, event) {
|
function extraNetworksSearchButton(tabname, extra_networks_tabname, event) {
|
||||||
var searchTextarea = gradioApp().querySelector("#" + tabs_id + ' > label > textarea');
|
var searchTextarea = gradioApp().querySelector("#" + tabname + "_" + extra_networks_tabname + "_extra_search");
|
||||||
var button = event.target;
|
var button = event.target;
|
||||||
var text = button.classList.contains("search-all") ? "" : button.textContent.trim();
|
var text = button.classList.contains("search-all") ? "" : button.textContent.trim();
|
||||||
|
|
||||||
@@ -273,6 +285,187 @@ function extraNetworksSearchButton(tabs_id, event) {
|
|||||||
updateInput(searchTextarea);
|
updateInput(searchTextarea);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function extraNetworksTreeProcessFileClick(event, btn, tabname, extra_networks_tabname) {
|
||||||
|
/**
|
||||||
|
* Processes `onclick` events when user clicks on files in tree.
|
||||||
|
*
|
||||||
|
* @param event The generated event.
|
||||||
|
* @param btn The clicked `tree-list-item` button.
|
||||||
|
* @param tabname The name of the active tab in the sd webui. Ex: txt2img, img2img, etc.
|
||||||
|
* @param extra_networks_tabname The id of the active extraNetworks tab. Ex: lora, checkpoints, etc.
|
||||||
|
*/
|
||||||
|
// NOTE: Currently unused.
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
function extraNetworksTreeProcessDirectoryClick(event, btn, tabname, extra_networks_tabname) {
|
||||||
|
/**
|
||||||
|
* Processes `onclick` events when user clicks on directories in tree.
|
||||||
|
*
|
||||||
|
* Here is how the tree reacts to clicks for various states:
|
||||||
|
* unselected unopened directory: Directory is selected and expanded.
|
||||||
|
* unselected opened directory: Directory is selected.
|
||||||
|
* selected opened directory: Directory is collapsed and deselected.
|
||||||
|
* chevron is clicked: Directory is expanded or collapsed. Selected state unchanged.
|
||||||
|
*
|
||||||
|
* @param event The generated event.
|
||||||
|
* @param btn The clicked `tree-list-item` button.
|
||||||
|
* @param tabname The name of the active tab in the sd webui. Ex: txt2img, img2img, etc.
|
||||||
|
* @param extra_networks_tabname The id of the active extraNetworks tab. Ex: lora, checkpoints, etc.
|
||||||
|
*/
|
||||||
|
var ul = btn.nextElementSibling;
|
||||||
|
// This is the actual target that the user clicked on within the target button.
|
||||||
|
// We use this to detect if the chevron was clicked.
|
||||||
|
var true_targ = event.target;
|
||||||
|
|
||||||
|
function _expand_or_collapse(_ul, _btn) {
|
||||||
|
// Expands <ul> if it is collapsed, collapses otherwise. Updates button attributes.
|
||||||
|
if (_ul.hasAttribute("hidden")) {
|
||||||
|
_ul.removeAttribute("hidden");
|
||||||
|
_btn.dataset.expanded = "";
|
||||||
|
} else {
|
||||||
|
_ul.setAttribute("hidden", "");
|
||||||
|
delete _btn.dataset.expanded;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function _remove_selected_from_all() {
|
||||||
|
// Removes the `selected` attribute from all buttons.
|
||||||
|
var sels = document.querySelectorAll("div.tree-list-content");
|
||||||
|
[...sels].forEach(el => {
|
||||||
|
delete el.dataset.selected;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
function _select_button(_btn) {
|
||||||
|
// Removes `data-selected` attribute from all buttons then adds to passed button.
|
||||||
|
_remove_selected_from_all();
|
||||||
|
_btn.dataset.selected = "";
|
||||||
|
}
|
||||||
|
|
||||||
|
function _update_search(_tabname, _extra_networks_tabname, _search_text) {
|
||||||
|
// Update search input with select button's path.
|
||||||
|
var search_input_elem = gradioApp().querySelector("#" + tabname + "_" + extra_networks_tabname + "_extra_search");
|
||||||
|
search_input_elem.value = _search_text;
|
||||||
|
updateInput(search_input_elem);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
// If user clicks on the chevron, then we do not select the folder.
|
||||||
|
if (true_targ.matches(".tree-list-item-action--leading, .tree-list-item-action-chevron")) {
|
||||||
|
_expand_or_collapse(ul, btn);
|
||||||
|
} else {
|
||||||
|
// User clicked anywhere else on the button.
|
||||||
|
if ("selected" in btn.dataset && !(ul.hasAttribute("hidden"))) {
|
||||||
|
// If folder is select and open, collapse and deselect button.
|
||||||
|
_expand_or_collapse(ul, btn);
|
||||||
|
delete btn.dataset.selected;
|
||||||
|
_update_search(tabname, extra_networks_tabname, "");
|
||||||
|
} else if (!(!("selected" in btn.dataset) && !(ul.hasAttribute("hidden")))) {
|
||||||
|
// If folder is open and not selected, then we don't collapse; just select.
|
||||||
|
// NOTE: Double inversion sucks but it is the clearest way to show the branching here.
|
||||||
|
_expand_or_collapse(ul, btn);
|
||||||
|
_select_button(btn, tabname, extra_networks_tabname);
|
||||||
|
_update_search(tabname, extra_networks_tabname, btn.dataset.path);
|
||||||
|
} else {
|
||||||
|
// All other cases, just select the button.
|
||||||
|
_select_button(btn, tabname, extra_networks_tabname);
|
||||||
|
_update_search(tabname, extra_networks_tabname, btn.dataset.path);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function extraNetworksTreeOnClick(event, tabname, extra_networks_tabname) {
|
||||||
|
/**
|
||||||
|
* Handles `onclick` events for buttons within an `extra-network-tree .tree-list--tree`.
|
||||||
|
*
|
||||||
|
* Determines whether the clicked button in the tree is for a file entry or a directory
|
||||||
|
* then calls the appropriate function.
|
||||||
|
*
|
||||||
|
* @param event The generated event.
|
||||||
|
* @param tabname The name of the active tab in the sd webui. Ex: txt2img, img2img, etc.
|
||||||
|
* @param extra_networks_tabname The id of the active extraNetworks tab. Ex: lora, checkpoints, etc.
|
||||||
|
*/
|
||||||
|
var btn = event.currentTarget;
|
||||||
|
var par = btn.parentElement;
|
||||||
|
if (par.dataset.treeEntryType === "file") {
|
||||||
|
extraNetworksTreeProcessFileClick(event, btn, tabname, extra_networks_tabname);
|
||||||
|
} else {
|
||||||
|
extraNetworksTreeProcessDirectoryClick(event, btn, tabname, extra_networks_tabname);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function extraNetworksControlSortOnClick(event, tabname, extra_networks_tabname) {
|
||||||
|
/** Handles `onclick` events for Sort Mode buttons. */
|
||||||
|
|
||||||
|
var self = event.currentTarget;
|
||||||
|
var parent = event.currentTarget.parentElement;
|
||||||
|
|
||||||
|
parent.querySelectorAll('.extra-network-control--sort').forEach(function(x) {
|
||||||
|
x.classList.remove('extra-network-control--enabled');
|
||||||
|
});
|
||||||
|
|
||||||
|
self.classList.add('extra-network-control--enabled');
|
||||||
|
|
||||||
|
applyExtraNetworkSort(tabname + "_" + extra_networks_tabname);
|
||||||
|
}
|
||||||
|
|
||||||
|
function extraNetworksControlSortDirOnClick(event, tabname, extra_networks_tabname) {
|
||||||
|
/**
|
||||||
|
* Handles `onclick` events for the Sort Direction button.
|
||||||
|
*
|
||||||
|
* Modifies the data attributes of the Sort Direction button to cycle between
|
||||||
|
* ascending and descending sort directions.
|
||||||
|
*
|
||||||
|
* @param event The generated event.
|
||||||
|
* @param tabname The name of the active tab in the sd webui. Ex: txt2img, img2img, etc.
|
||||||
|
* @param extra_networks_tabname The id of the active extraNetworks tab. Ex: lora, checkpoints, etc.
|
||||||
|
*/
|
||||||
|
if (event.currentTarget.dataset.sortdir == "Ascending") {
|
||||||
|
event.currentTarget.dataset.sortdir = "Descending";
|
||||||
|
event.currentTarget.setAttribute("title", "Sort descending");
|
||||||
|
} else {
|
||||||
|
event.currentTarget.dataset.sortdir = "Ascending";
|
||||||
|
event.currentTarget.setAttribute("title", "Sort ascending");
|
||||||
|
}
|
||||||
|
applyExtraNetworkSort(tabname + "_" + extra_networks_tabname);
|
||||||
|
}
|
||||||
|
|
||||||
|
function extraNetworksControlTreeViewOnClick(event, tabname, extra_networks_tabname) {
|
||||||
|
/**
|
||||||
|
* Handles `onclick` events for the Tree View button.
|
||||||
|
*
|
||||||
|
* Toggles the tree view in the extra networks pane.
|
||||||
|
*
|
||||||
|
* @param event The generated event.
|
||||||
|
* @param tabname The name of the active tab in the sd webui. Ex: txt2img, img2img, etc.
|
||||||
|
* @param extra_networks_tabname The id of the active extraNetworks tab. Ex: lora, checkpoints, etc.
|
||||||
|
*/
|
||||||
|
var button = event.currentTarget;
|
||||||
|
button.classList.toggle("extra-network-control--enabled");
|
||||||
|
var show = !button.classList.contains("extra-network-control--enabled");
|
||||||
|
|
||||||
|
var pane = gradioApp().getElementById(tabname + "_" + extra_networks_tabname + "_pane");
|
||||||
|
pane.classList.toggle("extra-network-dirs-hidden", show);
|
||||||
|
}
|
||||||
|
|
||||||
|
function extraNetworksControlRefreshOnClick(event, tabname, extra_networks_tabname) {
|
||||||
|
/**
|
||||||
|
* Handles `onclick` events for the Refresh Page button.
|
||||||
|
*
|
||||||
|
* In order to actually call the python functions in `ui_extra_networks.py`
|
||||||
|
* to refresh the page, we created an empty gradio button in that file with an
|
||||||
|
* event handler that refreshes the page. So what this function here does
|
||||||
|
* is it manually raises a `click` event on that button.
|
||||||
|
*
|
||||||
|
* @param event The generated event.
|
||||||
|
* @param tabname The name of the active tab in the sd webui. Ex: txt2img, img2img, etc.
|
||||||
|
* @param extra_networks_tabname The id of the active extraNetworks tab. Ex: lora, checkpoints, etc.
|
||||||
|
*/
|
||||||
|
var btn_refresh_internal = gradioApp().getElementById(tabname + "_" + extra_networks_tabname + "_extra_refresh_internal");
|
||||||
|
btn_refresh_internal.dispatchEvent(new Event("click"));
|
||||||
|
}
|
||||||
|
|
||||||
var globalPopup = null;
|
var globalPopup = null;
|
||||||
var globalPopupInner = null;
|
var globalPopupInner = null;
|
||||||
|
|
||||||
@@ -314,12 +507,76 @@ function popupId(id) {
|
|||||||
popup(storedPopupIds[id]);
|
popup(storedPopupIds[id]);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function extraNetworksFlattenMetadata(obj) {
|
||||||
|
const result = {};
|
||||||
|
|
||||||
|
// Convert any stringified JSON objects to actual objects
|
||||||
|
for (const key of Object.keys(obj)) {
|
||||||
|
if (typeof obj[key] === 'string') {
|
||||||
|
try {
|
||||||
|
const parsed = JSON.parse(obj[key]);
|
||||||
|
if (parsed && typeof parsed === 'object') {
|
||||||
|
obj[key] = parsed;
|
||||||
|
}
|
||||||
|
} catch (error) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Flatten the object
|
||||||
|
for (const key of Object.keys(obj)) {
|
||||||
|
if (typeof obj[key] === 'object' && obj[key] !== null) {
|
||||||
|
const nested = extraNetworksFlattenMetadata(obj[key]);
|
||||||
|
for (const nestedKey of Object.keys(nested)) {
|
||||||
|
result[`${key}/${nestedKey}`] = nested[nestedKey];
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
result[key] = obj[key];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Special case for handling modelspec keys
|
||||||
|
for (const key of Object.keys(result)) {
|
||||||
|
if (key.startsWith("modelspec.")) {
|
||||||
|
result[key.replaceAll(".", "/")] = result[key];
|
||||||
|
delete result[key];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Add empty keys to designate hierarchy
|
||||||
|
for (const key of Object.keys(result)) {
|
||||||
|
const parts = key.split("/");
|
||||||
|
for (let i = 1; i < parts.length; i++) {
|
||||||
|
const parent = parts.slice(0, i).join("/");
|
||||||
|
if (!result[parent]) {
|
||||||
|
result[parent] = "";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
function extraNetworksShowMetadata(text) {
|
function extraNetworksShowMetadata(text) {
|
||||||
|
try {
|
||||||
|
let parsed = JSON.parse(text);
|
||||||
|
if (parsed && typeof parsed === 'object') {
|
||||||
|
parsed = extraNetworksFlattenMetadata(parsed);
|
||||||
|
const table = createVisualizationTable(parsed, 0);
|
||||||
|
popup(table);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
} catch (error) {
|
||||||
|
console.error(error);
|
||||||
|
}
|
||||||
|
|
||||||
var elem = document.createElement('pre');
|
var elem = document.createElement('pre');
|
||||||
elem.classList.add('popup-metadata');
|
elem.classList.add('popup-metadata');
|
||||||
elem.textContent = text;
|
elem.textContent = text;
|
||||||
|
|
||||||
popup(elem);
|
popup(elem);
|
||||||
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
function requestGet(url, data, handler, errorHandler) {
|
function requestGet(url, data, handler, errorHandler) {
|
||||||
@@ -348,11 +605,18 @@ function requestGet(url, data, handler, errorHandler) {
|
|||||||
xhr.send(js);
|
xhr.send(js);
|
||||||
}
|
}
|
||||||
|
|
||||||
function extraNetworksRequestMetadata(event, extraPage, cardName) {
|
function extraNetworksCopyCardPath(event) {
|
||||||
|
navigator.clipboard.writeText(event.target.getAttribute("data-clipboard-text"));
|
||||||
|
event.stopPropagation();
|
||||||
|
}
|
||||||
|
|
||||||
|
function extraNetworksRequestMetadata(event, extraPage) {
|
||||||
var showError = function() {
|
var showError = function() {
|
||||||
extraNetworksShowMetadata("there was an error getting metadata");
|
extraNetworksShowMetadata("there was an error getting metadata");
|
||||||
};
|
};
|
||||||
|
|
||||||
|
var cardName = event.target.parentElement.parentElement.getAttribute("data-name");
|
||||||
|
|
||||||
requestGet("./sd_extra_networks/metadata", {page: extraPage, item: cardName}, function(data) {
|
requestGet("./sd_extra_networks/metadata", {page: extraPage, item: cardName}, function(data) {
|
||||||
if (data && data.metadata) {
|
if (data && data.metadata) {
|
||||||
extraNetworksShowMetadata(data.metadata);
|
extraNetworksShowMetadata(data.metadata);
|
||||||
@@ -366,7 +630,7 @@ function extraNetworksRequestMetadata(event, extraPage, cardName) {
|
|||||||
|
|
||||||
var extraPageUserMetadataEditors = {};
|
var extraPageUserMetadataEditors = {};
|
||||||
|
|
||||||
function extraNetworksEditUserMetadata(event, tabname, extraPage, cardName) {
|
function extraNetworksEditUserMetadata(event, tabname, extraPage) {
|
||||||
var id = tabname + '_' + extraPage + '_edit_user_metadata';
|
var id = tabname + '_' + extraPage + '_edit_user_metadata';
|
||||||
|
|
||||||
var editor = extraPageUserMetadataEditors[id];
|
var editor = extraPageUserMetadataEditors[id];
|
||||||
@@ -378,6 +642,7 @@ function extraNetworksEditUserMetadata(event, tabname, extraPage, cardName) {
|
|||||||
extraPageUserMetadataEditors[id] = editor;
|
extraPageUserMetadataEditors[id] = editor;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
var cardName = event.target.parentElement.parentElement.getAttribute("data-name");
|
||||||
editor.nameTextarea.value = cardName;
|
editor.nameTextarea.value = cardName;
|
||||||
updateInput(editor.nameTextarea);
|
updateInput(editor.nameTextarea);
|
||||||
|
|
||||||
@@ -409,3 +674,39 @@ window.addEventListener("keydown", function(event) {
|
|||||||
closePopup();
|
closePopup();
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Setup custom loading for this script.
|
||||||
|
* We need to wait for all of our HTML to be generated in the extra networks tabs
|
||||||
|
* before we can actually run the `setupExtraNetworks` function.
|
||||||
|
* The `onUiLoaded` function actually runs before all of our extra network tabs are
|
||||||
|
* finished generating. Thus we needed this new method.
|
||||||
|
*
|
||||||
|
*/
|
||||||
|
|
||||||
|
var uiAfterScriptsCallbacks = [];
|
||||||
|
var uiAfterScriptsTimeout = null;
|
||||||
|
var executedAfterScripts = false;
|
||||||
|
|
||||||
|
function scheduleAfterScriptsCallbacks() {
|
||||||
|
clearTimeout(uiAfterScriptsTimeout);
|
||||||
|
uiAfterScriptsTimeout = setTimeout(function() {
|
||||||
|
executeCallbacks(uiAfterScriptsCallbacks);
|
||||||
|
}, 200);
|
||||||
|
}
|
||||||
|
|
||||||
|
onUiLoaded(function() {
|
||||||
|
var mutationObserver = new MutationObserver(function(m) {
|
||||||
|
let existingSearchfields = gradioApp().querySelectorAll("[id$='_extra_search']").length;
|
||||||
|
let neededSearchfields = gradioApp().querySelectorAll("[id$='_extra_tabs'] > .tab-nav > button").length - 2;
|
||||||
|
|
||||||
|
if (!executedAfterScripts && existingSearchfields >= neededSearchfields) {
|
||||||
|
mutationObserver.disconnect();
|
||||||
|
executedAfterScripts = true;
|
||||||
|
scheduleAfterScriptsCallbacks();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
mutationObserver.observe(gradioApp(), {childList: true, subtree: true});
|
||||||
|
});
|
||||||
|
|
||||||
|
uiAfterScriptsCallbacks.push(setupExtraNetworks);
|
||||||
|
|||||||
+32
-18
@@ -6,11 +6,14 @@ function closeModal() {
|
|||||||
function showModal(event) {
|
function showModal(event) {
|
||||||
const source = event.target || event.srcElement;
|
const source = event.target || event.srcElement;
|
||||||
const modalImage = gradioApp().getElementById("modalImage");
|
const modalImage = gradioApp().getElementById("modalImage");
|
||||||
|
const modalToggleLivePreviewBtn = gradioApp().getElementById("modal_toggle_live_preview");
|
||||||
|
modalToggleLivePreviewBtn.innerHTML = opts.js_live_preview_in_modal_lightbox ? "🗇" : "🗆";
|
||||||
const lb = gradioApp().getElementById("lightboxModal");
|
const lb = gradioApp().getElementById("lightboxModal");
|
||||||
modalImage.src = source.src;
|
modalImage.src = source.src;
|
||||||
if (modalImage.style.display === 'none') {
|
if (modalImage.style.display === 'none') {
|
||||||
lb.style.setProperty('background-image', 'url(' + source.src + ')');
|
lb.style.setProperty('background-image', 'url(' + source.src + ')');
|
||||||
}
|
}
|
||||||
|
updateModalImage();
|
||||||
lb.style.display = "flex";
|
lb.style.display = "flex";
|
||||||
lb.focus();
|
lb.focus();
|
||||||
|
|
||||||
@@ -29,9 +32,8 @@ function negmod(n, m) {
|
|||||||
return ((n % m) + m) % m;
|
return ((n % m) + m) % m;
|
||||||
}
|
}
|
||||||
|
|
||||||
function updateOnBackgroundChange() {
|
function updateModalImage() {
|
||||||
const modalImage = gradioApp().getElementById("modalImage");
|
const modalImage = gradioApp().getElementById("modalImage");
|
||||||
if (modalImage && modalImage.offsetParent) {
|
|
||||||
let currentButton = selected_gallery_button();
|
let currentButton = selected_gallery_button();
|
||||||
let preview = gradioApp().querySelectorAll('.livePreview > img');
|
let preview = gradioApp().querySelectorAll('.livePreview > img');
|
||||||
if (opts.js_live_preview_in_modal_lightbox && preview.length > 0) {
|
if (opts.js_live_preview_in_modal_lightbox && preview.length > 0) {
|
||||||
@@ -44,21 +46,21 @@ function updateOnBackgroundChange() {
|
|||||||
modal.style.setProperty('background-image', `url(${modalImage.src})`);
|
modal.style.setProperty('background-image', `url(${modalImage.src})`);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function updateOnBackgroundChange() {
|
||||||
|
const modalImage = gradioApp().getElementById("modalImage");
|
||||||
|
if (modalImage && modalImage.offsetParent) {
|
||||||
|
updateModalImage();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
const updateModalImageIfVisible = updateOnBackgroundChange;
|
||||||
|
|
||||||
function modalImageSwitch(offset) {
|
function modalImageSwitch(offset) {
|
||||||
var galleryButtons = all_gallery_buttons();
|
var galleryButtons = all_gallery_buttons();
|
||||||
|
|
||||||
if (galleryButtons.length > 1) {
|
if (galleryButtons.length > 1) {
|
||||||
var currentButton = selected_gallery_button();
|
var result = selected_gallery_index();
|
||||||
|
|
||||||
var result = -1;
|
|
||||||
galleryButtons.forEach(function(v, i) {
|
|
||||||
if (v == currentButton) {
|
|
||||||
result = i;
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
if (result != -1) {
|
if (result != -1) {
|
||||||
var nextButton = galleryButtons[negmod((result + offset), galleryButtons.length)];
|
var nextButton = galleryButtons[negmod((result + offset), galleryButtons.length)];
|
||||||
@@ -131,19 +133,15 @@ function setupImageForLightbox(e) {
|
|||||||
e.style.cursor = 'pointer';
|
e.style.cursor = 'pointer';
|
||||||
e.style.userSelect = 'none';
|
e.style.userSelect = 'none';
|
||||||
|
|
||||||
var isFirefox = navigator.userAgent.toLowerCase().indexOf('firefox') > -1;
|
e.addEventListener('mousedown', function(evt) {
|
||||||
|
|
||||||
// For Firefox, listening on click first switched to next image then shows the lightbox.
|
|
||||||
// If you know how to fix this without switching to mousedown event, please.
|
|
||||||
// For other browsers the event is click to make it possiblr to drag picture.
|
|
||||||
var event = isFirefox ? 'mousedown' : 'click';
|
|
||||||
|
|
||||||
e.addEventListener(event, function(evt) {
|
|
||||||
if (evt.button == 1) {
|
if (evt.button == 1) {
|
||||||
open(evt.target.src);
|
open(evt.target.src);
|
||||||
evt.preventDefault();
|
evt.preventDefault();
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
}, true);
|
||||||
|
|
||||||
|
e.addEventListener('click', function(evt) {
|
||||||
if (!opts.js_modal_lightbox || evt.button != 0) return;
|
if (!opts.js_modal_lightbox || evt.button != 0) return;
|
||||||
|
|
||||||
modalZoomSet(gradioApp().getElementById('modalImage'), opts.js_modal_lightbox_initially_zoomed);
|
modalZoomSet(gradioApp().getElementById('modalImage'), opts.js_modal_lightbox_initially_zoomed);
|
||||||
@@ -163,6 +161,14 @@ function modalZoomToggle(event) {
|
|||||||
event.stopPropagation();
|
event.stopPropagation();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function modalLivePreviewToggle(event) {
|
||||||
|
const modalToggleLivePreview = gradioApp().getElementById("modal_toggle_live_preview");
|
||||||
|
opts.js_live_preview_in_modal_lightbox = !opts.js_live_preview_in_modal_lightbox;
|
||||||
|
modalToggleLivePreview.innerHTML = opts.js_live_preview_in_modal_lightbox ? "🗇" : "🗆";
|
||||||
|
updateModalImageIfVisible();
|
||||||
|
event.stopPropagation();
|
||||||
|
}
|
||||||
|
|
||||||
function modalTileImageToggle(event) {
|
function modalTileImageToggle(event) {
|
||||||
const modalImage = gradioApp().getElementById("modalImage");
|
const modalImage = gradioApp().getElementById("modalImage");
|
||||||
const modal = gradioApp().getElementById("lightboxModal");
|
const modal = gradioApp().getElementById("lightboxModal");
|
||||||
@@ -220,6 +226,14 @@ document.addEventListener("DOMContentLoaded", function() {
|
|||||||
modalSave.title = "Save Image(s)";
|
modalSave.title = "Save Image(s)";
|
||||||
modalControls.appendChild(modalSave);
|
modalControls.appendChild(modalSave);
|
||||||
|
|
||||||
|
const modalToggleLivePreview = document.createElement('span');
|
||||||
|
modalToggleLivePreview.className = 'modalToggleLivePreview cursor';
|
||||||
|
modalToggleLivePreview.id = "modal_toggle_live_preview";
|
||||||
|
modalToggleLivePreview.innerHTML = "🗆";
|
||||||
|
modalToggleLivePreview.onclick = modalLivePreviewToggle;
|
||||||
|
modalToggleLivePreview.title = "Toggle live preview";
|
||||||
|
modalControls.appendChild(modalToggleLivePreview);
|
||||||
|
|
||||||
const modalClose = document.createElement('span');
|
const modalClose = document.createElement('span');
|
||||||
modalClose.className = 'modalClose cursor';
|
modalClose.className = 'modalClose cursor';
|
||||||
modalClose.innerHTML = '×';
|
modalClose.innerHTML = '×';
|
||||||
|
|||||||
@@ -33,23 +33,33 @@ function createRow(table, cellName, items) {
|
|||||||
return res;
|
return res;
|
||||||
}
|
}
|
||||||
|
|
||||||
function showProfile(path, cutoff = 0.05) {
|
function createVisualizationTable(data, cutoff = 0, sort = "") {
|
||||||
requestGet(path, {}, function(data) {
|
|
||||||
var table = document.createElement('table');
|
var table = document.createElement('table');
|
||||||
table.className = 'popup-table';
|
table.className = 'popup-table';
|
||||||
|
|
||||||
data.records['total'] = data.total;
|
var keys = Object.keys(data);
|
||||||
var keys = Object.keys(data.records).sort(function(a, b) {
|
if (sort === "number") {
|
||||||
return data.records[b] - data.records[a];
|
keys = keys.sort(function(a, b) {
|
||||||
|
return data[b] - data[a];
|
||||||
});
|
});
|
||||||
|
} else {
|
||||||
|
keys = keys.sort();
|
||||||
|
}
|
||||||
var items = keys.map(function(x) {
|
var items = keys.map(function(x) {
|
||||||
return {key: x, parts: x.split('/'), time: data.records[x]};
|
return {key: x, parts: x.split('/'), value: data[x]};
|
||||||
});
|
});
|
||||||
var maxLength = items.reduce(function(a, b) {
|
var maxLength = items.reduce(function(a, b) {
|
||||||
return Math.max(a, b.parts.length);
|
return Math.max(a, b.parts.length);
|
||||||
}, 0);
|
}, 0);
|
||||||
|
|
||||||
var cols = createRow(table, 'th', ['record', 'seconds']);
|
var cols = createRow(
|
||||||
|
table,
|
||||||
|
'th',
|
||||||
|
[
|
||||||
|
cutoff === 0 ? 'key' : 'record',
|
||||||
|
cutoff === 0 ? 'value' : 'seconds'
|
||||||
|
]
|
||||||
|
);
|
||||||
cols[0].colSpan = maxLength;
|
cols[0].colSpan = maxLength;
|
||||||
|
|
||||||
function arraysEqual(a, b) {
|
function arraysEqual(a, b) {
|
||||||
@@ -60,21 +70,25 @@ function showProfile(path, cutoff = 0.05) {
|
|||||||
var matching = items.filter(function(x) {
|
var matching = items.filter(function(x) {
|
||||||
return x.parts[level] && !x.parts[level + 1] && arraysEqual(x.parts.slice(0, level), parent);
|
return x.parts[level] && !x.parts[level + 1] && arraysEqual(x.parts.slice(0, level), parent);
|
||||||
});
|
});
|
||||||
var sorted = matching.sort(function(a, b) {
|
if (sort === "number") {
|
||||||
return b.time - a.time;
|
matching = matching.sort(function(a, b) {
|
||||||
|
return b.value - a.value;
|
||||||
});
|
});
|
||||||
|
} else {
|
||||||
|
matching = matching.sort();
|
||||||
|
}
|
||||||
var othersTime = 0;
|
var othersTime = 0;
|
||||||
var othersList = [];
|
var othersList = [];
|
||||||
var othersRows = [];
|
var othersRows = [];
|
||||||
var childrenRows = [];
|
var childrenRows = [];
|
||||||
sorted.forEach(function(x) {
|
matching.forEach(function(x) {
|
||||||
var visible = x.time >= cutoff && !hide;
|
var visible = (cutoff === 0 && !hide) || (x.value >= cutoff && !hide);
|
||||||
|
|
||||||
var cells = [];
|
var cells = [];
|
||||||
for (var i = 0; i < maxLength; i++) {
|
for (var i = 0; i < maxLength; i++) {
|
||||||
cells.push(x.parts[i]);
|
cells.push(x.parts[i]);
|
||||||
}
|
}
|
||||||
cells.push(x.time.toFixed(3));
|
cells.push(cutoff === 0 ? x.value : x.value.toFixed(3));
|
||||||
var cols = createRow(table, 'td', cells);
|
var cols = createRow(table, 'td', cells);
|
||||||
for (i = 0; i < level; i++) {
|
for (i = 0; i < level; i++) {
|
||||||
cols[i].className = 'muted';
|
cols[i].className = 'muted';
|
||||||
@@ -85,10 +99,10 @@ function showProfile(path, cutoff = 0.05) {
|
|||||||
tr.classList.add("hidden");
|
tr.classList.add("hidden");
|
||||||
}
|
}
|
||||||
|
|
||||||
if (x.time >= cutoff) {
|
if (cutoff === 0 || x.value >= cutoff) {
|
||||||
childrenRows.push(tr);
|
childrenRows.push(tr);
|
||||||
} else {
|
} else {
|
||||||
othersTime += x.time;
|
othersTime += x.value;
|
||||||
othersList.push(x.parts[level]);
|
othersList.push(x.parts[level]);
|
||||||
othersRows.push(tr);
|
othersRows.push(tr);
|
||||||
}
|
}
|
||||||
@@ -147,6 +161,13 @@ function showProfile(path, cutoff = 0.05) {
|
|||||||
|
|
||||||
addLevel(0, []);
|
addLevel(0, []);
|
||||||
|
|
||||||
|
return table;
|
||||||
|
}
|
||||||
|
|
||||||
|
function showProfile(path, cutoff = 0.05) {
|
||||||
|
requestGet(path, {}, function(data) {
|
||||||
|
data.records['total'] = data.total;
|
||||||
|
const table = createVisualizationTable(data.records, cutoff, "number");
|
||||||
popup(table);
|
popup(table);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -45,8 +45,15 @@ function formatTime(secs) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
var originalAppTitle = undefined;
|
||||||
|
|
||||||
|
onUiLoaded(function() {
|
||||||
|
originalAppTitle = document.title;
|
||||||
|
});
|
||||||
|
|
||||||
function setTitle(progress) {
|
function setTitle(progress) {
|
||||||
var title = 'Stable Diffusion';
|
var title = originalAppTitle;
|
||||||
|
|
||||||
if (opts.show_progress_in_title && progress) {
|
if (opts.show_progress_in_title && progress) {
|
||||||
title = '[' + progress.trim() + '] ' + title;
|
title = '[' + progress.trim() + '] ' + title;
|
||||||
@@ -69,6 +76,27 @@ function requestProgress(id_task, progressbarContainer, gallery, atEnd, onProgre
|
|||||||
var dateStart = new Date();
|
var dateStart = new Date();
|
||||||
var wasEverActive = false;
|
var wasEverActive = false;
|
||||||
var parentProgressbar = progressbarContainer.parentNode;
|
var parentProgressbar = progressbarContainer.parentNode;
|
||||||
|
var wakeLock = null;
|
||||||
|
|
||||||
|
var requestWakeLock = async function() {
|
||||||
|
if (!opts.prevent_screen_sleep_during_generation || wakeLock !== null) return;
|
||||||
|
try {
|
||||||
|
wakeLock = await navigator.wakeLock.request('screen');
|
||||||
|
} catch (err) {
|
||||||
|
console.error('Wake Lock is not supported.');
|
||||||
|
wakeLock = false;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
var releaseWakeLock = async function() {
|
||||||
|
if (!opts.prevent_screen_sleep_during_generation || !wakeLock) return;
|
||||||
|
try {
|
||||||
|
await wakeLock.release();
|
||||||
|
wakeLock = null;
|
||||||
|
} catch (err) {
|
||||||
|
console.error('Wake Lock release failed', err);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
var divProgress = document.createElement('div');
|
var divProgress = document.createElement('div');
|
||||||
divProgress.className = 'progressDiv';
|
divProgress.className = 'progressDiv';
|
||||||
@@ -82,6 +110,7 @@ function requestProgress(id_task, progressbarContainer, gallery, atEnd, onProgre
|
|||||||
var livePreview = null;
|
var livePreview = null;
|
||||||
|
|
||||||
var removeProgressBar = function() {
|
var removeProgressBar = function() {
|
||||||
|
releaseWakeLock();
|
||||||
if (!divProgress) return;
|
if (!divProgress) return;
|
||||||
|
|
||||||
setTitle("");
|
setTitle("");
|
||||||
@@ -93,6 +122,7 @@ function requestProgress(id_task, progressbarContainer, gallery, atEnd, onProgre
|
|||||||
};
|
};
|
||||||
|
|
||||||
var funProgress = function(id_task) {
|
var funProgress = function(id_task) {
|
||||||
|
requestWakeLock();
|
||||||
request("./internal/progress", {id_task: id_task, live_preview: false}, function(res) {
|
request("./internal/progress", {id_task: id_task, live_preview: false}, function(res) {
|
||||||
if (res.completed) {
|
if (res.completed) {
|
||||||
removeProgressBar();
|
removeProgressBar();
|
||||||
@@ -160,7 +190,7 @@ function requestProgress(id_task, progressbarContainer, gallery, atEnd, onProgre
|
|||||||
livePreview.className = 'livePreview';
|
livePreview.className = 'livePreview';
|
||||||
gallery.insertBefore(livePreview, gallery.firstElementChild);
|
gallery.insertBefore(livePreview, gallery.firstElementChild);
|
||||||
}
|
}
|
||||||
|
updateModalImageIfVisible();
|
||||||
livePreview.appendChild(img);
|
livePreview.appendChild(img);
|
||||||
if (livePreview.childElementCount > 2) {
|
if (livePreview.childElementCount > 2) {
|
||||||
livePreview.removeChild(livePreview.firstElementChild);
|
livePreview.removeChild(livePreview.firstElementChild);
|
||||||
|
|||||||
+89
-25
@@ -1,8 +1,8 @@
|
|||||||
(function() {
|
(function() {
|
||||||
const GRADIO_MIN_WIDTH = 320;
|
const GRADIO_MIN_WIDTH = 320;
|
||||||
const GRID_TEMPLATE_COLUMNS = '1fr 16px 1fr';
|
|
||||||
const PAD = 16;
|
const PAD = 16;
|
||||||
const DEBOUNCE_TIME = 100;
|
const DEBOUNCE_TIME = 100;
|
||||||
|
const DOUBLE_TAP_DELAY = 200; //ms
|
||||||
|
|
||||||
const R = {
|
const R = {
|
||||||
tracking: false,
|
tracking: false,
|
||||||
@@ -11,6 +11,7 @@
|
|||||||
leftCol: null,
|
leftCol: null,
|
||||||
leftColStartWidth: null,
|
leftColStartWidth: null,
|
||||||
screenX: null,
|
screenX: null,
|
||||||
|
lastTapTime: null,
|
||||||
};
|
};
|
||||||
|
|
||||||
let resizeTimer;
|
let resizeTimer;
|
||||||
@@ -21,30 +22,29 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
function displayResizeHandle(parent) {
|
function displayResizeHandle(parent) {
|
||||||
|
if (!parent.needHideOnMoblie) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
if (window.innerWidth < GRADIO_MIN_WIDTH * 2 + PAD * 4) {
|
if (window.innerWidth < GRADIO_MIN_WIDTH * 2 + PAD * 4) {
|
||||||
parent.style.display = 'flex';
|
parent.style.display = 'flex';
|
||||||
if (R.handle != null) {
|
parent.resizeHandle.style.display = "none";
|
||||||
R.handle.style.opacity = '0';
|
|
||||||
}
|
|
||||||
return false;
|
return false;
|
||||||
} else {
|
} else {
|
||||||
parent.style.display = 'grid';
|
parent.style.display = 'grid';
|
||||||
if (R.handle != null) {
|
parent.resizeHandle.style.display = "block";
|
||||||
R.handle.style.opacity = '100';
|
|
||||||
}
|
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
function afterResize(parent) {
|
function afterResize(parent) {
|
||||||
if (displayResizeHandle(parent) && parent.style.gridTemplateColumns != GRID_TEMPLATE_COLUMNS) {
|
if (displayResizeHandle(parent) && parent.style.gridTemplateColumns != parent.style.originalGridTemplateColumns) {
|
||||||
const oldParentWidth = R.parentWidth;
|
const oldParentWidth = R.parentWidth;
|
||||||
const newParentWidth = parent.offsetWidth;
|
const newParentWidth = parent.offsetWidth;
|
||||||
const widthL = parseInt(parent.style.gridTemplateColumns.split(' ')[0]);
|
const widthL = parseInt(parent.style.gridTemplateColumns.split(' ')[0]);
|
||||||
|
|
||||||
const ratio = newParentWidth / oldParentWidth;
|
const ratio = newParentWidth / oldParentWidth;
|
||||||
|
|
||||||
const newWidthL = Math.max(Math.floor(ratio * widthL), GRADIO_MIN_WIDTH);
|
const newWidthL = Math.max(Math.floor(ratio * widthL), parent.minLeftColWidth);
|
||||||
setLeftColGridTemplate(parent, newWidthL);
|
setLeftColGridTemplate(parent, newWidthL);
|
||||||
|
|
||||||
R.parentWidth = newParentWidth;
|
R.parentWidth = newParentWidth;
|
||||||
@@ -52,6 +52,14 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
function setup(parent) {
|
function setup(parent) {
|
||||||
|
|
||||||
|
function onDoubleClick(evt) {
|
||||||
|
evt.preventDefault();
|
||||||
|
evt.stopPropagation();
|
||||||
|
|
||||||
|
parent.style.gridTemplateColumns = parent.style.originalGridTemplateColumns;
|
||||||
|
}
|
||||||
|
|
||||||
const leftCol = parent.firstElementChild;
|
const leftCol = parent.firstElementChild;
|
||||||
const rightCol = parent.lastElementChild;
|
const rightCol = parent.lastElementChild;
|
||||||
|
|
||||||
@@ -59,14 +67,47 @@
|
|||||||
|
|
||||||
parent.style.display = 'grid';
|
parent.style.display = 'grid';
|
||||||
parent.style.gap = '0';
|
parent.style.gap = '0';
|
||||||
parent.style.gridTemplateColumns = GRID_TEMPLATE_COLUMNS;
|
let leftColTemplate = "";
|
||||||
|
if (parent.children[0].style.flexGrow) {
|
||||||
|
leftColTemplate = `${parent.children[0].style.flexGrow}fr`;
|
||||||
|
parent.minLeftColWidth = GRADIO_MIN_WIDTH;
|
||||||
|
parent.minRightColWidth = GRADIO_MIN_WIDTH;
|
||||||
|
parent.needHideOnMoblie = true;
|
||||||
|
} else {
|
||||||
|
leftColTemplate = parent.children[0].style.flexBasis;
|
||||||
|
parent.minLeftColWidth = parent.children[0].style.flexBasis.slice(0, -2) / 2;
|
||||||
|
parent.minRightColWidth = 0;
|
||||||
|
parent.needHideOnMoblie = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!leftColTemplate) {
|
||||||
|
leftColTemplate = '1fr';
|
||||||
|
}
|
||||||
|
|
||||||
|
const gridTemplateColumns = `${leftColTemplate} ${PAD}px ${parent.children[1].style.flexGrow}fr`;
|
||||||
|
parent.style.gridTemplateColumns = gridTemplateColumns;
|
||||||
|
parent.style.originalGridTemplateColumns = gridTemplateColumns;
|
||||||
|
|
||||||
const resizeHandle = document.createElement('div');
|
const resizeHandle = document.createElement('div');
|
||||||
resizeHandle.classList.add('resize-handle');
|
resizeHandle.classList.add('resize-handle');
|
||||||
parent.insertBefore(resizeHandle, rightCol);
|
parent.insertBefore(resizeHandle, rightCol);
|
||||||
|
parent.resizeHandle = resizeHandle;
|
||||||
|
|
||||||
resizeHandle.addEventListener('mousedown', (evt) => {
|
['mousedown', 'touchstart'].forEach((eventType) => {
|
||||||
|
resizeHandle.addEventListener(eventType, (evt) => {
|
||||||
|
if (eventType.startsWith('mouse')) {
|
||||||
if (evt.button !== 0) return;
|
if (evt.button !== 0) return;
|
||||||
|
} else {
|
||||||
|
if (evt.changedTouches.length !== 1) return;
|
||||||
|
|
||||||
|
const currentTime = new Date().getTime();
|
||||||
|
if (R.lastTapTime && currentTime - R.lastTapTime <= DOUBLE_TAP_DELAY) {
|
||||||
|
onDoubleClick(evt);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
R.lastTapTime = currentTime;
|
||||||
|
}
|
||||||
|
|
||||||
evt.preventDefault();
|
evt.preventDefault();
|
||||||
evt.stopPropagation();
|
evt.stopPropagation();
|
||||||
@@ -76,37 +117,54 @@
|
|||||||
R.tracking = true;
|
R.tracking = true;
|
||||||
R.parent = parent;
|
R.parent = parent;
|
||||||
R.parentWidth = parent.offsetWidth;
|
R.parentWidth = parent.offsetWidth;
|
||||||
R.handle = resizeHandle;
|
|
||||||
R.leftCol = leftCol;
|
R.leftCol = leftCol;
|
||||||
R.leftColStartWidth = leftCol.offsetWidth;
|
R.leftColStartWidth = leftCol.offsetWidth;
|
||||||
|
if (eventType.startsWith('mouse')) {
|
||||||
R.screenX = evt.screenX;
|
R.screenX = evt.screenX;
|
||||||
|
} else {
|
||||||
|
R.screenX = evt.changedTouches[0].screenX;
|
||||||
|
}
|
||||||
|
}, {passive: false});
|
||||||
});
|
});
|
||||||
|
|
||||||
resizeHandle.addEventListener('dblclick', (evt) => {
|
resizeHandle.addEventListener('dblclick', onDoubleClick);
|
||||||
evt.preventDefault();
|
|
||||||
evt.stopPropagation();
|
|
||||||
|
|
||||||
parent.style.gridTemplateColumns = GRID_TEMPLATE_COLUMNS;
|
|
||||||
});
|
|
||||||
|
|
||||||
afterResize(parent);
|
afterResize(parent);
|
||||||
}
|
}
|
||||||
|
|
||||||
window.addEventListener('mousemove', (evt) => {
|
['mousemove', 'touchmove'].forEach((eventType) => {
|
||||||
|
window.addEventListener(eventType, (evt) => {
|
||||||
|
if (eventType.startsWith('mouse')) {
|
||||||
if (evt.button !== 0) return;
|
if (evt.button !== 0) return;
|
||||||
|
} else {
|
||||||
|
if (evt.changedTouches.length !== 1) return;
|
||||||
|
}
|
||||||
|
|
||||||
if (R.tracking) {
|
if (R.tracking) {
|
||||||
|
if (eventType.startsWith('mouse')) {
|
||||||
evt.preventDefault();
|
evt.preventDefault();
|
||||||
|
}
|
||||||
evt.stopPropagation();
|
evt.stopPropagation();
|
||||||
|
|
||||||
const delta = R.screenX - evt.screenX;
|
let delta = 0;
|
||||||
const leftColWidth = Math.max(Math.min(R.leftColStartWidth - delta, R.parent.offsetWidth - GRADIO_MIN_WIDTH - PAD), GRADIO_MIN_WIDTH);
|
if (eventType.startsWith('mouse')) {
|
||||||
|
delta = R.screenX - evt.screenX;
|
||||||
|
} else {
|
||||||
|
delta = R.screenX - evt.changedTouches[0].screenX;
|
||||||
|
}
|
||||||
|
const leftColWidth = Math.max(Math.min(R.leftColStartWidth - delta, R.parent.offsetWidth - R.parent.minRightColWidth - PAD), R.parent.minLeftColWidth);
|
||||||
setLeftColGridTemplate(R.parent, leftColWidth);
|
setLeftColGridTemplate(R.parent, leftColWidth);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
});
|
||||||
|
|
||||||
window.addEventListener('mouseup', (evt) => {
|
['mouseup', 'touchend'].forEach((eventType) => {
|
||||||
|
window.addEventListener(eventType, (evt) => {
|
||||||
|
if (eventType.startsWith('mouse')) {
|
||||||
if (evt.button !== 0) return;
|
if (evt.button !== 0) return;
|
||||||
|
} else {
|
||||||
|
if (evt.changedTouches.length !== 1) return;
|
||||||
|
}
|
||||||
|
|
||||||
if (R.tracking) {
|
if (R.tracking) {
|
||||||
evt.preventDefault();
|
evt.preventDefault();
|
||||||
@@ -117,6 +175,7 @@
|
|||||||
document.body.classList.remove('resizing');
|
document.body.classList.remove('resizing');
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
|
||||||
window.addEventListener('resize', () => {
|
window.addEventListener('resize', () => {
|
||||||
@@ -132,10 +191,15 @@
|
|||||||
setupResizeHandle = setup;
|
setupResizeHandle = setup;
|
||||||
})();
|
})();
|
||||||
|
|
||||||
onUiLoaded(function() {
|
|
||||||
|
function setupAllResizeHandles() {
|
||||||
for (var elem of gradioApp().querySelectorAll('.resize-handle-row')) {
|
for (var elem of gradioApp().querySelectorAll('.resize-handle-row')) {
|
||||||
if (!elem.querySelector('.resize-handle')) {
|
if (!elem.querySelector('.resize-handle') && !elem.children[0].classList.contains("hidden")) {
|
||||||
setupResizeHandle(elem);
|
setupResizeHandle(elem);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
});
|
}
|
||||||
|
|
||||||
|
|
||||||
|
onUiLoaded(setupAllResizeHandles);
|
||||||
|
|
||||||
|
|||||||
@@ -55,8 +55,8 @@ onOptionsChanged(function() {
|
|||||||
});
|
});
|
||||||
|
|
||||||
opts._categories.forEach(function(x) {
|
opts._categories.forEach(function(x) {
|
||||||
var section = x[0];
|
var section = localization[x[0]] ?? x[0];
|
||||||
var category = x[1];
|
var category = localization[x[1]] ?? x[1];
|
||||||
|
|
||||||
var span = document.createElement('SPAN');
|
var span = document.createElement('SPAN');
|
||||||
span.textContent = category;
|
span.textContent = category;
|
||||||
|
|||||||
@@ -48,11 +48,6 @@ function setupTokenCounting(id, id_counter, id_button) {
|
|||||||
var counter = gradioApp().getElementById(id_counter);
|
var counter = gradioApp().getElementById(id_counter);
|
||||||
var textarea = gradioApp().querySelector(`#${id} > label > textarea`);
|
var textarea = gradioApp().querySelector(`#${id} > label > textarea`);
|
||||||
|
|
||||||
if (opts.disable_token_counters) {
|
|
||||||
counter.style.display = "none";
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (counter.parentElement == prompt.parentElement) {
|
if (counter.parentElement == prompt.parentElement) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
@@ -61,15 +56,32 @@ function setupTokenCounting(id, id_counter, id_button) {
|
|||||||
prompt.parentElement.style.position = "relative";
|
prompt.parentElement.style.position = "relative";
|
||||||
|
|
||||||
var func = onEdit(id, textarea, 800, function() {
|
var func = onEdit(id, textarea, 800, function() {
|
||||||
|
if (counter.classList.contains("token-counter-visible")) {
|
||||||
gradioApp().getElementById(id_button)?.click();
|
gradioApp().getElementById(id_button)?.click();
|
||||||
|
}
|
||||||
});
|
});
|
||||||
promptTokenCountUpdateFunctions[id] = func;
|
promptTokenCountUpdateFunctions[id] = func;
|
||||||
promptTokenCountUpdateFunctions[id_button] = func;
|
promptTokenCountUpdateFunctions[id_button] = func;
|
||||||
}
|
}
|
||||||
|
|
||||||
function setupTokenCounters() {
|
function toggleTokenCountingVisibility(id, id_counter, id_button) {
|
||||||
setupTokenCounting('txt2img_prompt', 'txt2img_token_counter', 'txt2img_token_button');
|
var counter = gradioApp().getElementById(id_counter);
|
||||||
setupTokenCounting('txt2img_neg_prompt', 'txt2img_negative_token_counter', 'txt2img_negative_token_button');
|
|
||||||
setupTokenCounting('img2img_prompt', 'img2img_token_counter', 'img2img_token_button');
|
counter.style.display = opts.disable_token_counters ? "none" : "block";
|
||||||
setupTokenCounting('img2img_neg_prompt', 'img2img_negative_token_counter', 'img2img_negative_token_button');
|
counter.classList.toggle("token-counter-visible", !opts.disable_token_counters);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function runCodeForTokenCounters(fun) {
|
||||||
|
fun('txt2img_prompt', 'txt2img_token_counter', 'txt2img_token_button');
|
||||||
|
fun('txt2img_neg_prompt', 'txt2img_negative_token_counter', 'txt2img_negative_token_button');
|
||||||
|
fun('img2img_prompt', 'img2img_token_counter', 'img2img_token_button');
|
||||||
|
fun('img2img_neg_prompt', 'img2img_negative_token_counter', 'img2img_negative_token_button');
|
||||||
|
}
|
||||||
|
|
||||||
|
onUiLoaded(function() {
|
||||||
|
runCodeForTokenCounters(setupTokenCounting);
|
||||||
|
});
|
||||||
|
|
||||||
|
onOptionsChanged(function() {
|
||||||
|
runCodeForTokenCounters(toggleTokenCountingVisibility);
|
||||||
|
});
|
||||||
|
|||||||
+25
-8
@@ -26,6 +26,14 @@ function selected_gallery_index() {
|
|||||||
return all_gallery_buttons().findIndex(elem => elem.classList.contains('selected'));
|
return all_gallery_buttons().findIndex(elem => elem.classList.contains('selected'));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function gallery_container_buttons(gallery_container) {
|
||||||
|
return gradioApp().querySelectorAll(`#${gallery_container} .thumbnail-item.thumbnail-small`);
|
||||||
|
}
|
||||||
|
|
||||||
|
function selected_gallery_index_id(gallery_container) {
|
||||||
|
return Array.from(gallery_container_buttons(gallery_container)).findIndex(elem => elem.classList.contains('selected'));
|
||||||
|
}
|
||||||
|
|
||||||
function extract_image_from_gallery(gallery) {
|
function extract_image_from_gallery(gallery) {
|
||||||
if (gallery.length == 0) {
|
if (gallery.length == 0) {
|
||||||
return [null];
|
return [null];
|
||||||
@@ -119,16 +127,24 @@ function create_submit_args(args) {
|
|||||||
return res;
|
return res;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function setSubmitButtonsVisibility(tabname, showInterrupt, showSkip, showInterrupting) {
|
||||||
|
gradioApp().getElementById(tabname + '_interrupt').style.display = showInterrupt ? "block" : "none";
|
||||||
|
gradioApp().getElementById(tabname + '_skip').style.display = showSkip ? "block" : "none";
|
||||||
|
gradioApp().getElementById(tabname + '_interrupting').style.display = showInterrupting ? "block" : "none";
|
||||||
|
}
|
||||||
|
|
||||||
function showSubmitButtons(tabname, show) {
|
function showSubmitButtons(tabname, show) {
|
||||||
gradioApp().getElementById(tabname + '_interrupt').style.display = show ? "none" : "block";
|
setSubmitButtonsVisibility(tabname, !show, !show, false);
|
||||||
gradioApp().getElementById(tabname + '_skip').style.display = show ? "none" : "block";
|
}
|
||||||
|
|
||||||
|
function showSubmitInterruptingPlaceholder(tabname) {
|
||||||
|
setSubmitButtonsVisibility(tabname, false, true, true);
|
||||||
}
|
}
|
||||||
|
|
||||||
function showRestoreProgressButton(tabname, show) {
|
function showRestoreProgressButton(tabname, show) {
|
||||||
var button = gradioApp().getElementById(tabname + "_restore_progress");
|
var button = gradioApp().getElementById(tabname + "_restore_progress");
|
||||||
if (!button) return;
|
if (!button) return;
|
||||||
|
button.style.setProperty('display', show ? 'flex' : 'none', 'important');
|
||||||
button.style.display = show ? "flex" : "none";
|
|
||||||
}
|
}
|
||||||
|
|
||||||
function submit() {
|
function submit() {
|
||||||
@@ -200,6 +216,7 @@ function restoreProgressTxt2img() {
|
|||||||
var id = localGet("txt2img_task_id");
|
var id = localGet("txt2img_task_id");
|
||||||
|
|
||||||
if (id) {
|
if (id) {
|
||||||
|
showSubmitInterruptingPlaceholder('txt2img');
|
||||||
requestProgress(id, gradioApp().getElementById('txt2img_gallery_container'), gradioApp().getElementById('txt2img_gallery'), function() {
|
requestProgress(id, gradioApp().getElementById('txt2img_gallery_container'), gradioApp().getElementById('txt2img_gallery'), function() {
|
||||||
showSubmitButtons('txt2img', true);
|
showSubmitButtons('txt2img', true);
|
||||||
}, null, 0);
|
}, null, 0);
|
||||||
@@ -214,6 +231,7 @@ function restoreProgressImg2img() {
|
|||||||
var id = localGet("img2img_task_id");
|
var id = localGet("img2img_task_id");
|
||||||
|
|
||||||
if (id) {
|
if (id) {
|
||||||
|
showSubmitInterruptingPlaceholder('img2img');
|
||||||
requestProgress(id, gradioApp().getElementById('img2img_gallery_container'), gradioApp().getElementById('img2img_gallery'), function() {
|
requestProgress(id, gradioApp().getElementById('img2img_gallery_container'), gradioApp().getElementById('img2img_gallery'), function() {
|
||||||
showSubmitButtons('img2img', true);
|
showSubmitButtons('img2img', true);
|
||||||
}, null, 0);
|
}, null, 0);
|
||||||
@@ -289,6 +307,7 @@ onAfterUiUpdate(function() {
|
|||||||
var jsdata = textarea.value;
|
var jsdata = textarea.value;
|
||||||
opts = JSON.parse(jsdata);
|
opts = JSON.parse(jsdata);
|
||||||
|
|
||||||
|
executeCallbacks(optionsAvailableCallbacks); /*global optionsAvailableCallbacks*/
|
||||||
executeCallbacks(optionsChangedCallbacks); /*global optionsChangedCallbacks*/
|
executeCallbacks(optionsChangedCallbacks); /*global optionsChangedCallbacks*/
|
||||||
|
|
||||||
Object.defineProperty(textarea, 'value', {
|
Object.defineProperty(textarea, 'value', {
|
||||||
@@ -310,8 +329,6 @@ onAfterUiUpdate(function() {
|
|||||||
});
|
});
|
||||||
|
|
||||||
json_elem.parentElement.style.display = "none";
|
json_elem.parentElement.style.display = "none";
|
||||||
|
|
||||||
setupTokenCounters();
|
|
||||||
});
|
});
|
||||||
|
|
||||||
onOptionsChanged(function() {
|
onOptionsChanged(function() {
|
||||||
@@ -329,8 +346,8 @@ onOptionsChanged(function() {
|
|||||||
let txt2img_textarea, img2img_textarea = undefined;
|
let txt2img_textarea, img2img_textarea = undefined;
|
||||||
|
|
||||||
function restart_reload() {
|
function restart_reload() {
|
||||||
|
document.body.style.backgroundColor = "var(--background-fill-primary)";
|
||||||
document.body.innerHTML = '<h1 style="font-family:monospace;margin-top:20%;color:lightgray;text-align:center;">Reloading...</h1>';
|
document.body.innerHTML = '<h1 style="font-family:monospace;margin-top:20%;color:lightgray;text-align:center;">Reloading...</h1>';
|
||||||
|
|
||||||
var requestPing = function() {
|
var requestPing = function() {
|
||||||
requestGet("./internal/ping", {}, function(data) {
|
requestGet("./internal/ping", {}, function(data) {
|
||||||
location.reload();
|
location.reload();
|
||||||
@@ -404,7 +421,7 @@ function switchWidthHeight(tabname) {
|
|||||||
|
|
||||||
var onEditTimers = {};
|
var onEditTimers = {};
|
||||||
|
|
||||||
// calls func after afterMs milliseconds has passed since the input elem has beed enited by user
|
// calls func after afterMs milliseconds has passed since the input elem has been edited by user
|
||||||
function onEdit(editId, elem, afterMs, func) {
|
function onEdit(editId, elem, afterMs, func) {
|
||||||
var edited = function() {
|
var edited = function() {
|
||||||
var existingTimer = onEditTimers[editId];
|
var existingTimer = onEditTimers[editId];
|
||||||
|
|||||||
@@ -6,6 +6,11 @@ git = launch_utils.git
|
|||||||
index_url = launch_utils.index_url
|
index_url = launch_utils.index_url
|
||||||
dir_repos = launch_utils.dir_repos
|
dir_repos = launch_utils.dir_repos
|
||||||
|
|
||||||
|
if args.uv:
|
||||||
|
from modules.uv_hook import patch
|
||||||
|
patch()
|
||||||
|
|
||||||
|
|
||||||
commit_hash = launch_utils.commit_hash
|
commit_hash = launch_utils.commit_hash
|
||||||
git_tag = launch_utils.git_tag
|
git_tag = launch_utils.git_tag
|
||||||
|
|
||||||
|
|||||||
+38
-13
@@ -17,13 +17,13 @@ from fastapi.encoders import jsonable_encoder
|
|||||||
from secrets import compare_digest
|
from secrets import compare_digest
|
||||||
|
|
||||||
import modules.shared as shared
|
import modules.shared as shared
|
||||||
from modules import sd_samplers, deepbooru, sd_hijack, images, scripts, ui, postprocessing, errors, restart, shared_items, script_callbacks, infotext_utils, sd_models
|
from modules import sd_samplers, deepbooru, sd_hijack, images, scripts, ui, postprocessing, errors, restart, shared_items, script_callbacks, infotext_utils, sd_models, sd_schedulers
|
||||||
from modules.api import models
|
from modules.api import models
|
||||||
from modules.shared import opts
|
from modules.shared import opts
|
||||||
from modules.processing import StableDiffusionProcessingTxt2Img, StableDiffusionProcessingImg2Img, process_images
|
from modules.processing import StableDiffusionProcessingTxt2Img, StableDiffusionProcessingImg2Img, process_images
|
||||||
from modules.textual_inversion.textual_inversion import create_embedding, train_embedding
|
from modules.textual_inversion.textual_inversion import create_embedding, train_embedding
|
||||||
from modules.hypernetworks.hypernetwork import create_hypernetwork, train_hypernetwork
|
from modules.hypernetworks.hypernetwork import create_hypernetwork, train_hypernetwork
|
||||||
from PIL import PngImagePlugin, Image
|
from PIL import PngImagePlugin
|
||||||
from modules.sd_models_config import find_checkpoint_config_near_filename
|
from modules.sd_models_config import find_checkpoint_config_near_filename
|
||||||
from modules.realesrgan_model import get_realesrgan_models
|
from modules.realesrgan_model import get_realesrgan_models
|
||||||
from modules import devices
|
from modules import devices
|
||||||
@@ -43,7 +43,7 @@ def script_name_to_index(name, scripts):
|
|||||||
def validate_sampler_name(name):
|
def validate_sampler_name(name):
|
||||||
config = sd_samplers.all_samplers_map.get(name, None)
|
config = sd_samplers.all_samplers_map.get(name, None)
|
||||||
if config is None:
|
if config is None:
|
||||||
raise HTTPException(status_code=404, detail="Sampler not found")
|
raise HTTPException(status_code=400, detail="Sampler not found")
|
||||||
|
|
||||||
return name
|
return name
|
||||||
|
|
||||||
@@ -85,7 +85,7 @@ def decode_base64_to_image(encoding):
|
|||||||
headers = {'user-agent': opts.api_useragent} if opts.api_useragent else {}
|
headers = {'user-agent': opts.api_useragent} if opts.api_useragent else {}
|
||||||
response = requests.get(encoding, timeout=30, headers=headers)
|
response = requests.get(encoding, timeout=30, headers=headers)
|
||||||
try:
|
try:
|
||||||
image = Image.open(BytesIO(response.content))
|
image = images.read(BytesIO(response.content))
|
||||||
return image
|
return image
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
raise HTTPException(status_code=500, detail="Invalid image url") from e
|
raise HTTPException(status_code=500, detail="Invalid image url") from e
|
||||||
@@ -93,7 +93,7 @@ def decode_base64_to_image(encoding):
|
|||||||
if encoding.startswith("data:image/"):
|
if encoding.startswith("data:image/"):
|
||||||
encoding = encoding.split(";")[1].split(",")[1]
|
encoding = encoding.split(";")[1].split(",")[1]
|
||||||
try:
|
try:
|
||||||
image = Image.open(BytesIO(base64.b64decode(encoding)))
|
image = images.read(BytesIO(base64.b64decode(encoding)))
|
||||||
return image
|
return image
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
raise HTTPException(status_code=500, detail="Invalid encoded image") from e
|
raise HTTPException(status_code=500, detail="Invalid encoded image") from e
|
||||||
@@ -113,7 +113,7 @@ def encode_pil_to_base64(image):
|
|||||||
image.save(output_bytes, format="PNG", pnginfo=(metadata if use_metadata else None), quality=opts.jpeg_quality)
|
image.save(output_bytes, format="PNG", pnginfo=(metadata if use_metadata else None), quality=opts.jpeg_quality)
|
||||||
|
|
||||||
elif opts.samples_format.lower() in ("jpg", "jpeg", "webp"):
|
elif opts.samples_format.lower() in ("jpg", "jpeg", "webp"):
|
||||||
if image.mode == "RGBA":
|
if image.mode in ("RGBA", "P"):
|
||||||
image = image.convert("RGB")
|
image = image.convert("RGB")
|
||||||
parameters = image.info.get('parameters', None)
|
parameters = image.info.get('parameters', None)
|
||||||
exif_bytes = piexif.dump({
|
exif_bytes = piexif.dump({
|
||||||
@@ -122,7 +122,7 @@ def encode_pil_to_base64(image):
|
|||||||
if opts.samples_format.lower() in ("jpg", "jpeg"):
|
if opts.samples_format.lower() in ("jpg", "jpeg"):
|
||||||
image.save(output_bytes, format="JPEG", exif = exif_bytes, quality=opts.jpeg_quality)
|
image.save(output_bytes, format="JPEG", exif = exif_bytes, quality=opts.jpeg_quality)
|
||||||
else:
|
else:
|
||||||
image.save(output_bytes, format="WEBP", exif = exif_bytes, quality=opts.jpeg_quality)
|
image.save(output_bytes, format="WEBP", exif = exif_bytes, quality=opts.jpeg_quality, lossless=opts.webp_lossless)
|
||||||
|
|
||||||
else:
|
else:
|
||||||
raise HTTPException(status_code=500, detail="Invalid image format")
|
raise HTTPException(status_code=500, detail="Invalid image format")
|
||||||
@@ -221,6 +221,7 @@ class Api:
|
|||||||
self.add_api_route("/sdapi/v1/options", self.set_config, methods=["POST"])
|
self.add_api_route("/sdapi/v1/options", self.set_config, methods=["POST"])
|
||||||
self.add_api_route("/sdapi/v1/cmd-flags", self.get_cmd_flags, methods=["GET"], response_model=models.FlagsModel)
|
self.add_api_route("/sdapi/v1/cmd-flags", self.get_cmd_flags, methods=["GET"], response_model=models.FlagsModel)
|
||||||
self.add_api_route("/sdapi/v1/samplers", self.get_samplers, methods=["GET"], response_model=list[models.SamplerItem])
|
self.add_api_route("/sdapi/v1/samplers", self.get_samplers, methods=["GET"], response_model=list[models.SamplerItem])
|
||||||
|
self.add_api_route("/sdapi/v1/schedulers", self.get_schedulers, methods=["GET"], response_model=list[models.SchedulerItem])
|
||||||
self.add_api_route("/sdapi/v1/upscalers", self.get_upscalers, methods=["GET"], response_model=list[models.UpscalerItem])
|
self.add_api_route("/sdapi/v1/upscalers", self.get_upscalers, methods=["GET"], response_model=list[models.UpscalerItem])
|
||||||
self.add_api_route("/sdapi/v1/latent-upscale-modes", self.get_latent_upscale_modes, methods=["GET"], response_model=list[models.LatentUpscalerModeItem])
|
self.add_api_route("/sdapi/v1/latent-upscale-modes", self.get_latent_upscale_modes, methods=["GET"], response_model=list[models.LatentUpscalerModeItem])
|
||||||
self.add_api_route("/sdapi/v1/sd-models", self.get_sd_models, methods=["GET"], response_model=list[models.SDModelItem])
|
self.add_api_route("/sdapi/v1/sd-models", self.get_sd_models, methods=["GET"], response_model=list[models.SDModelItem])
|
||||||
@@ -230,6 +231,7 @@ class Api:
|
|||||||
self.add_api_route("/sdapi/v1/realesrgan-models", self.get_realesrgan_models, methods=["GET"], response_model=list[models.RealesrganItem])
|
self.add_api_route("/sdapi/v1/realesrgan-models", self.get_realesrgan_models, methods=["GET"], response_model=list[models.RealesrganItem])
|
||||||
self.add_api_route("/sdapi/v1/prompt-styles", self.get_prompt_styles, methods=["GET"], response_model=list[models.PromptStyleItem])
|
self.add_api_route("/sdapi/v1/prompt-styles", self.get_prompt_styles, methods=["GET"], response_model=list[models.PromptStyleItem])
|
||||||
self.add_api_route("/sdapi/v1/embeddings", self.get_embeddings, methods=["GET"], response_model=models.EmbeddingsResponse)
|
self.add_api_route("/sdapi/v1/embeddings", self.get_embeddings, methods=["GET"], response_model=models.EmbeddingsResponse)
|
||||||
|
self.add_api_route("/sdapi/v1/refresh-embeddings", self.refresh_embeddings, methods=["POST"])
|
||||||
self.add_api_route("/sdapi/v1/refresh-checkpoints", self.refresh_checkpoints, methods=["POST"])
|
self.add_api_route("/sdapi/v1/refresh-checkpoints", self.refresh_checkpoints, methods=["POST"])
|
||||||
self.add_api_route("/sdapi/v1/refresh-vae", self.refresh_vae, methods=["POST"])
|
self.add_api_route("/sdapi/v1/refresh-vae", self.refresh_vae, methods=["POST"])
|
||||||
self.add_api_route("/sdapi/v1/create/embedding", self.create_embedding, methods=["POST"], response_model=models.CreateResponse)
|
self.add_api_route("/sdapi/v1/create/embedding", self.create_embedding, methods=["POST"], response_model=models.CreateResponse)
|
||||||
@@ -359,7 +361,7 @@ class Api:
|
|||||||
return script_args
|
return script_args
|
||||||
|
|
||||||
def apply_infotext(self, request, tabname, *, script_runner=None, mentioned_script_args=None):
|
def apply_infotext(self, request, tabname, *, script_runner=None, mentioned_script_args=None):
|
||||||
"""Processes `infotext` field from the `request`, and sets other fields of the `request` accoring to what's in infotext.
|
"""Processes `infotext` field from the `request`, and sets other fields of the `request` according to what's in infotext.
|
||||||
|
|
||||||
If request already has a field set, and that field is encountered in infotext too, the value from infotext is ignored.
|
If request already has a field set, and that field is encountered in infotext too, the value from infotext is ignored.
|
||||||
|
|
||||||
@@ -370,7 +372,7 @@ class Api:
|
|||||||
return {}
|
return {}
|
||||||
|
|
||||||
possible_fields = infotext_utils.paste_fields[tabname]["fields"]
|
possible_fields = infotext_utils.paste_fields[tabname]["fields"]
|
||||||
set_fields = request.model_dump(exclude_unset=True) if hasattr(request, "request") else request.dict(exclude_unset=True) # pydantic v1/v2 have differenrt names for this
|
set_fields = request.model_dump(exclude_unset=True) if hasattr(request, "request") else request.dict(exclude_unset=True) # pydantic v1/v2 have different names for this
|
||||||
params = infotext_utils.parse_generation_parameters(request.infotext)
|
params = infotext_utils.parse_generation_parameters(request.infotext)
|
||||||
|
|
||||||
def get_field_value(field, params):
|
def get_field_value(field, params):
|
||||||
@@ -408,8 +410,8 @@ class Api:
|
|||||||
if request.override_settings is None:
|
if request.override_settings is None:
|
||||||
request.override_settings = {}
|
request.override_settings = {}
|
||||||
|
|
||||||
overriden_settings = infotext_utils.get_override_settings(params)
|
overridden_settings = infotext_utils.get_override_settings(params)
|
||||||
for _, setting_name, value in overriden_settings:
|
for _, setting_name, value in overridden_settings:
|
||||||
if setting_name not in request.override_settings:
|
if setting_name not in request.override_settings:
|
||||||
request.override_settings[setting_name] = value
|
request.override_settings[setting_name] = value
|
||||||
|
|
||||||
@@ -436,15 +438,19 @@ class Api:
|
|||||||
self.apply_infotext(txt2imgreq, "txt2img", script_runner=script_runner, mentioned_script_args=infotext_script_args)
|
self.apply_infotext(txt2imgreq, "txt2img", script_runner=script_runner, mentioned_script_args=infotext_script_args)
|
||||||
|
|
||||||
selectable_scripts, selectable_script_idx = self.get_selectable_script(txt2imgreq.script_name, script_runner)
|
selectable_scripts, selectable_script_idx = self.get_selectable_script(txt2imgreq.script_name, script_runner)
|
||||||
|
sampler, scheduler = sd_samplers.get_sampler_and_scheduler(txt2imgreq.sampler_name or txt2imgreq.sampler_index, txt2imgreq.scheduler)
|
||||||
|
|
||||||
populate = txt2imgreq.copy(update={ # Override __init__ params
|
populate = txt2imgreq.copy(update={ # Override __init__ params
|
||||||
"sampler_name": validate_sampler_name(txt2imgreq.sampler_name or txt2imgreq.sampler_index),
|
"sampler_name": validate_sampler_name(sampler),
|
||||||
"do_not_save_samples": not txt2imgreq.save_images,
|
"do_not_save_samples": not txt2imgreq.save_images,
|
||||||
"do_not_save_grid": not txt2imgreq.save_images,
|
"do_not_save_grid": not txt2imgreq.save_images,
|
||||||
})
|
})
|
||||||
if populate.sampler_name:
|
if populate.sampler_name:
|
||||||
populate.sampler_index = None # prevent a warning later on
|
populate.sampler_index = None # prevent a warning later on
|
||||||
|
|
||||||
|
if not populate.scheduler and scheduler != "Automatic":
|
||||||
|
populate.scheduler = scheduler
|
||||||
|
|
||||||
args = vars(populate)
|
args = vars(populate)
|
||||||
args.pop('script_name', None)
|
args.pop('script_name', None)
|
||||||
args.pop('script_args', None) # will refeed them to the pipeline directly after initializing them
|
args.pop('script_args', None) # will refeed them to the pipeline directly after initializing them
|
||||||
@@ -500,9 +506,10 @@ class Api:
|
|||||||
self.apply_infotext(img2imgreq, "img2img", script_runner=script_runner, mentioned_script_args=infotext_script_args)
|
self.apply_infotext(img2imgreq, "img2img", script_runner=script_runner, mentioned_script_args=infotext_script_args)
|
||||||
|
|
||||||
selectable_scripts, selectable_script_idx = self.get_selectable_script(img2imgreq.script_name, script_runner)
|
selectable_scripts, selectable_script_idx = self.get_selectable_script(img2imgreq.script_name, script_runner)
|
||||||
|
sampler, scheduler = sd_samplers.get_sampler_and_scheduler(img2imgreq.sampler_name or img2imgreq.sampler_index, img2imgreq.scheduler)
|
||||||
|
|
||||||
populate = img2imgreq.copy(update={ # Override __init__ params
|
populate = img2imgreq.copy(update={ # Override __init__ params
|
||||||
"sampler_name": validate_sampler_name(img2imgreq.sampler_name or img2imgreq.sampler_index),
|
"sampler_name": validate_sampler_name(sampler),
|
||||||
"do_not_save_samples": not img2imgreq.save_images,
|
"do_not_save_samples": not img2imgreq.save_images,
|
||||||
"do_not_save_grid": not img2imgreq.save_images,
|
"do_not_save_grid": not img2imgreq.save_images,
|
||||||
"mask": mask,
|
"mask": mask,
|
||||||
@@ -510,6 +517,9 @@ class Api:
|
|||||||
if populate.sampler_name:
|
if populate.sampler_name:
|
||||||
populate.sampler_index = None # prevent a warning later on
|
populate.sampler_index = None # prevent a warning later on
|
||||||
|
|
||||||
|
if not populate.scheduler and scheduler != "Automatic":
|
||||||
|
populate.scheduler = scheduler
|
||||||
|
|
||||||
args = vars(populate)
|
args = vars(populate)
|
||||||
args.pop('include_init_images', None) # this is meant to be done by "exclude": True in model, but it's for a reason that I cannot determine.
|
args.pop('include_init_images', None) # this is meant to be done by "exclude": True in model, but it's for a reason that I cannot determine.
|
||||||
args.pop('script_name', None)
|
args.pop('script_name', None)
|
||||||
@@ -682,6 +692,17 @@ class Api:
|
|||||||
def get_samplers(self):
|
def get_samplers(self):
|
||||||
return [{"name": sampler[0], "aliases":sampler[2], "options":sampler[3]} for sampler in sd_samplers.all_samplers]
|
return [{"name": sampler[0], "aliases":sampler[2], "options":sampler[3]} for sampler in sd_samplers.all_samplers]
|
||||||
|
|
||||||
|
def get_schedulers(self):
|
||||||
|
return [
|
||||||
|
{
|
||||||
|
"name": scheduler.name,
|
||||||
|
"label": scheduler.label,
|
||||||
|
"aliases": scheduler.aliases,
|
||||||
|
"default_rho": scheduler.default_rho,
|
||||||
|
"need_inner_model": scheduler.need_inner_model,
|
||||||
|
}
|
||||||
|
for scheduler in sd_schedulers.schedulers]
|
||||||
|
|
||||||
def get_upscalers(self):
|
def get_upscalers(self):
|
||||||
return [
|
return [
|
||||||
{
|
{
|
||||||
@@ -747,6 +768,10 @@ class Api:
|
|||||||
"skipped": convert_embeddings(db.skipped_embeddings),
|
"skipped": convert_embeddings(db.skipped_embeddings),
|
||||||
}
|
}
|
||||||
|
|
||||||
|
def refresh_embeddings(self):
|
||||||
|
with self.queue_lock:
|
||||||
|
sd_hijack.model_hijack.embedding_db.load_textual_inversion_embeddings(force_reload=True)
|
||||||
|
|
||||||
def refresh_checkpoints(self):
|
def refresh_checkpoints(self):
|
||||||
with self.queue_lock:
|
with self.queue_lock:
|
||||||
shared.refresh_checkpoints()
|
shared.refresh_checkpoints()
|
||||||
|
|||||||
@@ -147,7 +147,7 @@ class ExtrasBaseRequest(BaseModel):
|
|||||||
gfpgan_visibility: float = Field(default=0, title="GFPGAN Visibility", ge=0, le=1, allow_inf_nan=False, description="Sets the visibility of GFPGAN, values should be between 0 and 1.")
|
gfpgan_visibility: float = Field(default=0, title="GFPGAN Visibility", ge=0, le=1, allow_inf_nan=False, description="Sets the visibility of GFPGAN, values should be between 0 and 1.")
|
||||||
codeformer_visibility: float = Field(default=0, title="CodeFormer Visibility", ge=0, le=1, allow_inf_nan=False, description="Sets the visibility of CodeFormer, values should be between 0 and 1.")
|
codeformer_visibility: float = Field(default=0, title="CodeFormer Visibility", ge=0, le=1, allow_inf_nan=False, description="Sets the visibility of CodeFormer, values should be between 0 and 1.")
|
||||||
codeformer_weight: float = Field(default=0, title="CodeFormer Weight", ge=0, le=1, allow_inf_nan=False, description="Sets the weight of CodeFormer, values should be between 0 and 1.")
|
codeformer_weight: float = Field(default=0, title="CodeFormer Weight", ge=0, le=1, allow_inf_nan=False, description="Sets the weight of CodeFormer, values should be between 0 and 1.")
|
||||||
upscaling_resize: float = Field(default=2, title="Upscaling Factor", ge=1, le=8, description="By how much to upscale the image, only used when resize_mode=0.")
|
upscaling_resize: float = Field(default=2, title="Upscaling Factor", gt=0, description="By how much to upscale the image, only used when resize_mode=0.")
|
||||||
upscaling_resize_w: int = Field(default=512, title="Target Width", ge=1, description="Target width for the upscaler to hit. Only used when resize_mode=1.")
|
upscaling_resize_w: int = Field(default=512, title="Target Width", ge=1, description="Target width for the upscaler to hit. Only used when resize_mode=1.")
|
||||||
upscaling_resize_h: int = Field(default=512, title="Target Height", ge=1, description="Target height for the upscaler to hit. Only used when resize_mode=1.")
|
upscaling_resize_h: int = Field(default=512, title="Target Height", ge=1, description="Target height for the upscaler to hit. Only used when resize_mode=1.")
|
||||||
upscaling_crop: bool = Field(default=True, title="Crop to fit", description="Should the upscaler crop the image to fit in the chosen size?")
|
upscaling_crop: bool = Field(default=True, title="Crop to fit", description="Should the upscaler crop the image to fit in the chosen size?")
|
||||||
@@ -235,6 +235,13 @@ class SamplerItem(BaseModel):
|
|||||||
aliases: list[str] = Field(title="Aliases")
|
aliases: list[str] = Field(title="Aliases")
|
||||||
options: dict[str, str] = Field(title="Options")
|
options: dict[str, str] = Field(title="Options")
|
||||||
|
|
||||||
|
class SchedulerItem(BaseModel):
|
||||||
|
name: str = Field(title="Name")
|
||||||
|
label: str = Field(title="Label")
|
||||||
|
aliases: Optional[list[str]] = Field(title="Aliases")
|
||||||
|
default_rho: Optional[float] = Field(title="Default Rho")
|
||||||
|
need_inner_model: Optional[bool] = Field(title="Needs Inner Model")
|
||||||
|
|
||||||
class UpscalerItem(BaseModel):
|
class UpscalerItem(BaseModel):
|
||||||
name: str = Field(title="Name")
|
name: str = Field(title="Name")
|
||||||
model_name: Optional[str] = Field(title="Model Name")
|
model_name: Optional[str] = Field(title="Model Name")
|
||||||
|
|||||||
+44
-44
@@ -2,48 +2,55 @@ import json
|
|||||||
import os
|
import os
|
||||||
import os.path
|
import os.path
|
||||||
import threading
|
import threading
|
||||||
import time
|
|
||||||
|
import diskcache
|
||||||
|
import tqdm
|
||||||
|
|
||||||
from modules.paths import data_path, script_path
|
from modules.paths import data_path, script_path
|
||||||
|
|
||||||
cache_filename = os.environ.get('SD_WEBUI_CACHE_FILE', os.path.join(data_path, "cache.json"))
|
cache_filename = os.environ.get('SD_WEBUI_CACHE_FILE', os.path.join(data_path, "cache.json"))
|
||||||
cache_data = None
|
cache_dir = os.environ.get('SD_WEBUI_CACHE_DIR', os.path.join(data_path, "cache"))
|
||||||
|
caches = {}
|
||||||
cache_lock = threading.Lock()
|
cache_lock = threading.Lock()
|
||||||
|
|
||||||
dump_cache_after = None
|
|
||||||
dump_cache_thread = None
|
|
||||||
|
|
||||||
|
|
||||||
def dump_cache():
|
def dump_cache():
|
||||||
"""
|
"""old function for dumping cache to disk; does nothing since diskcache."""
|
||||||
Marks cache for writing to disk. 5 seconds after no one else flags the cache for writing, it is written.
|
|
||||||
"""
|
|
||||||
|
|
||||||
global dump_cache_after
|
pass
|
||||||
global dump_cache_thread
|
|
||||||
|
|
||||||
def thread_func():
|
|
||||||
global dump_cache_after
|
|
||||||
global dump_cache_thread
|
|
||||||
|
|
||||||
while dump_cache_after is not None and time.time() < dump_cache_after:
|
def make_cache(subsection: str) -> diskcache.Cache:
|
||||||
time.sleep(1)
|
return diskcache.Cache(
|
||||||
|
os.path.join(cache_dir, subsection),
|
||||||
|
size_limit=2**32, # 4 GB, culling oldest first
|
||||||
|
disk_min_file_size=2**18, # keep up to 256KB in Sqlite
|
||||||
|
)
|
||||||
|
|
||||||
with cache_lock:
|
|
||||||
cache_filename_tmp = cache_filename + "-"
|
|
||||||
with open(cache_filename_tmp, "w", encoding="utf8") as file:
|
|
||||||
json.dump(cache_data, file, indent=4, ensure_ascii=False)
|
|
||||||
|
|
||||||
os.replace(cache_filename_tmp, cache_filename)
|
def convert_old_cached_data():
|
||||||
|
try:
|
||||||
|
with open(cache_filename, "r", encoding="utf8") as file:
|
||||||
|
data = json.load(file)
|
||||||
|
except FileNotFoundError:
|
||||||
|
return
|
||||||
|
except Exception:
|
||||||
|
os.replace(cache_filename, os.path.join(script_path, "tmp", "cache.json"))
|
||||||
|
print('[ERROR] issue occurred while trying to read cache.json; old cache has been moved to tmp/cache.json')
|
||||||
|
return
|
||||||
|
|
||||||
dump_cache_after = None
|
total_count = sum(len(keyvalues) for keyvalues in data.values())
|
||||||
dump_cache_thread = None
|
|
||||||
|
|
||||||
with cache_lock:
|
with tqdm.tqdm(total=total_count, desc="converting cache") as progress:
|
||||||
dump_cache_after = time.time() + 5
|
for subsection, keyvalues in data.items():
|
||||||
if dump_cache_thread is None:
|
cache_obj = caches.get(subsection)
|
||||||
dump_cache_thread = threading.Thread(name='cache-writer', target=thread_func)
|
if cache_obj is None:
|
||||||
dump_cache_thread.start()
|
cache_obj = make_cache(subsection)
|
||||||
|
caches[subsection] = cache_obj
|
||||||
|
|
||||||
|
for key, value in keyvalues.items():
|
||||||
|
cache_obj[key] = value
|
||||||
|
progress.update(1)
|
||||||
|
|
||||||
|
|
||||||
def cache(subsection):
|
def cache(subsection):
|
||||||
@@ -54,28 +61,21 @@ def cache(subsection):
|
|||||||
subsection (str): The subsection identifier for the cache.
|
subsection (str): The subsection identifier for the cache.
|
||||||
|
|
||||||
Returns:
|
Returns:
|
||||||
dict: The cache data for the specified subsection.
|
diskcache.Cache: The cache data for the specified subsection.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
global cache_data
|
cache_obj = caches.get(subsection)
|
||||||
|
if not cache_obj:
|
||||||
if cache_data is None:
|
|
||||||
with cache_lock:
|
with cache_lock:
|
||||||
if cache_data is None:
|
if not os.path.exists(cache_dir) and os.path.isfile(cache_filename):
|
||||||
try:
|
convert_old_cached_data()
|
||||||
with open(cache_filename, "r", encoding="utf8") as file:
|
|
||||||
cache_data = json.load(file)
|
|
||||||
except FileNotFoundError:
|
|
||||||
cache_data = {}
|
|
||||||
except Exception:
|
|
||||||
os.replace(cache_filename, os.path.join(script_path, "tmp", "cache.json"))
|
|
||||||
print('[ERROR] issue occurred while trying to read cache.json, move current cache to tmp/cache.json and create new cache')
|
|
||||||
cache_data = {}
|
|
||||||
|
|
||||||
s = cache_data.get(subsection, {})
|
cache_obj = caches.get(subsection)
|
||||||
cache_data[subsection] = s
|
if not cache_obj:
|
||||||
|
cache_obj = make_cache(subsection)
|
||||||
|
caches[subsection] = cache_obj
|
||||||
|
|
||||||
return s
|
return cache_obj
|
||||||
|
|
||||||
|
|
||||||
def cached_data_for_file(subsection, title, filename, func):
|
def cached_data_for_file(subsection, title, filename, func):
|
||||||
|
|||||||
+27
-12
@@ -1,8 +1,9 @@
|
|||||||
|
import os.path
|
||||||
from functools import wraps
|
from functools import wraps
|
||||||
import html
|
import html
|
||||||
import time
|
import time
|
||||||
|
|
||||||
from modules import shared, progress, errors, devices, fifo_lock
|
from modules import shared, progress, errors, devices, fifo_lock, profiling
|
||||||
|
|
||||||
queue_lock = fifo_lock.FIFOLock()
|
queue_lock = fifo_lock.FIFOLock()
|
||||||
|
|
||||||
@@ -46,6 +47,22 @@ def wrap_gradio_gpu_call(func, extra_outputs=None):
|
|||||||
|
|
||||||
|
|
||||||
def wrap_gradio_call(func, extra_outputs=None, add_stats=False):
|
def wrap_gradio_call(func, extra_outputs=None, add_stats=False):
|
||||||
|
@wraps(func)
|
||||||
|
def f(*args, **kwargs):
|
||||||
|
try:
|
||||||
|
res = func(*args, **kwargs)
|
||||||
|
finally:
|
||||||
|
shared.state.skipped = False
|
||||||
|
shared.state.interrupted = False
|
||||||
|
shared.state.stopping_generation = False
|
||||||
|
shared.state.job_count = 0
|
||||||
|
shared.state.job = ""
|
||||||
|
return res
|
||||||
|
|
||||||
|
return wrap_gradio_call_no_job(f, extra_outputs, add_stats)
|
||||||
|
|
||||||
|
|
||||||
|
def wrap_gradio_call_no_job(func, extra_outputs=None, add_stats=False):
|
||||||
@wraps(func)
|
@wraps(func)
|
||||||
def f(*args, extra_outputs_array=extra_outputs, **kwargs):
|
def f(*args, extra_outputs_array=extra_outputs, **kwargs):
|
||||||
run_memmon = shared.opts.memmon_poll_rate > 0 and not shared.mem_mon.disabled and add_stats
|
run_memmon = shared.opts.memmon_poll_rate > 0 and not shared.mem_mon.disabled and add_stats
|
||||||
@@ -65,9 +82,6 @@ def wrap_gradio_call(func, extra_outputs=None, add_stats=False):
|
|||||||
arg_str += f" (Argument list truncated at {max_debug_str_len}/{len(arg_str)} characters)"
|
arg_str += f" (Argument list truncated at {max_debug_str_len}/{len(arg_str)} characters)"
|
||||||
errors.report(f"{message}\n{arg_str}", exc_info=True)
|
errors.report(f"{message}\n{arg_str}", exc_info=True)
|
||||||
|
|
||||||
shared.state.job = ""
|
|
||||||
shared.state.job_count = 0
|
|
||||||
|
|
||||||
if extra_outputs_array is None:
|
if extra_outputs_array is None:
|
||||||
extra_outputs_array = [None, '']
|
extra_outputs_array = [None, '']
|
||||||
|
|
||||||
@@ -76,11 +90,6 @@ def wrap_gradio_call(func, extra_outputs=None, add_stats=False):
|
|||||||
|
|
||||||
devices.torch_gc()
|
devices.torch_gc()
|
||||||
|
|
||||||
shared.state.skipped = False
|
|
||||||
shared.state.interrupted = False
|
|
||||||
shared.state.stopping_generation = False
|
|
||||||
shared.state.job_count = 0
|
|
||||||
|
|
||||||
if not add_stats:
|
if not add_stats:
|
||||||
return tuple(res)
|
return tuple(res)
|
||||||
|
|
||||||
@@ -100,8 +109,8 @@ def wrap_gradio_call(func, extra_outputs=None, add_stats=False):
|
|||||||
sys_pct = sys_peak/max(sys_total, 1) * 100
|
sys_pct = sys_peak/max(sys_total, 1) * 100
|
||||||
|
|
||||||
toltip_a = "Active: peak amount of video memory used during generation (excluding cached data)"
|
toltip_a = "Active: peak amount of video memory used during generation (excluding cached data)"
|
||||||
toltip_r = "Reserved: total amout of video memory allocated by the Torch library "
|
toltip_r = "Reserved: total amount of video memory allocated by the Torch library "
|
||||||
toltip_sys = "System: peak amout of video memory allocated by all running programs, out of total capacity"
|
toltip_sys = "System: peak amount of video memory allocated by all running programs, out of total capacity"
|
||||||
|
|
||||||
text_a = f"<abbr title='{toltip_a}'>A</abbr>: <span class='measurement'>{active_peak/1024:.2f} GB</span>"
|
text_a = f"<abbr title='{toltip_a}'>A</abbr>: <span class='measurement'>{active_peak/1024:.2f} GB</span>"
|
||||||
text_r = f"<abbr title='{toltip_r}'>R</abbr>: <span class='measurement'>{reserved_peak/1024:.2f} GB</span>"
|
text_r = f"<abbr title='{toltip_r}'>R</abbr>: <span class='measurement'>{reserved_peak/1024:.2f} GB</span>"
|
||||||
@@ -111,9 +120,15 @@ def wrap_gradio_call(func, extra_outputs=None, add_stats=False):
|
|||||||
else:
|
else:
|
||||||
vram_html = ''
|
vram_html = ''
|
||||||
|
|
||||||
|
if shared.opts.profiling_enable and os.path.exists(shared.opts.profiling_filename):
|
||||||
|
profiling_html = f"<p class='profile'> [ <a href='{profiling.webpath()}' download>Profile</a> ] </p>"
|
||||||
|
else:
|
||||||
|
profiling_html = ''
|
||||||
|
|
||||||
# last item is always HTML
|
# last item is always HTML
|
||||||
res[-1] += f"<div class='performance'><p class='time'>Time taken: <wbr><span class='measurement'>{elapsed_text}</span></p>{vram_html}</div>"
|
res[-1] += f"<div class='performance'><p class='time'>Time taken: <wbr><span class='measurement'>{elapsed_text}</span></p>{vram_html}{profiling_html}</div>"
|
||||||
|
|
||||||
return tuple(res)
|
return tuple(res)
|
||||||
|
|
||||||
return f
|
return f
|
||||||
|
|
||||||
|
|||||||
+30
-24
@@ -1,7 +1,7 @@
|
|||||||
import argparse
|
import argparse
|
||||||
import json
|
import json
|
||||||
import os
|
import os
|
||||||
from modules.paths_internal import models_path, script_path, data_path, extensions_dir, extensions_builtin_dir, sd_default_config, sd_model_file # noqa: F401
|
from modules.paths_internal import normalized_filepath, models_path, script_path, data_path, extensions_dir, extensions_builtin_dir, sd_default_config, sd_model_file # noqa: F401
|
||||||
|
|
||||||
parser = argparse.ArgumentParser()
|
parser = argparse.ArgumentParser()
|
||||||
|
|
||||||
@@ -19,21 +19,22 @@ parser.add_argument("--skip-install", action='store_true', help="launch.py argum
|
|||||||
parser.add_argument("--dump-sysinfo", action='store_true', help="launch.py argument: dump limited sysinfo file (without information about extensions, options) to disk and quit")
|
parser.add_argument("--dump-sysinfo", action='store_true', help="launch.py argument: dump limited sysinfo file (without information about extensions, options) to disk and quit")
|
||||||
parser.add_argument("--loglevel", type=str, help="log level; one of: CRITICAL, ERROR, WARNING, INFO, DEBUG", default=None)
|
parser.add_argument("--loglevel", type=str, help="log level; one of: CRITICAL, ERROR, WARNING, INFO, DEBUG", default=None)
|
||||||
parser.add_argument("--do-not-download-clip", action='store_true', help="do not download CLIP model even if it's not included in the checkpoint")
|
parser.add_argument("--do-not-download-clip", action='store_true', help="do not download CLIP model even if it's not included in the checkpoint")
|
||||||
parser.add_argument("--data-dir", type=str, default=os.path.dirname(os.path.dirname(os.path.realpath(__file__))), help="base path where all user data is stored")
|
parser.add_argument("--data-dir", type=normalized_filepath, default=os.path.dirname(os.path.dirname(os.path.realpath(__file__))), help="base path where all user data is stored")
|
||||||
parser.add_argument("--config", type=str, default=sd_default_config, help="path to config which constructs model",)
|
parser.add_argument("--models-dir", type=normalized_filepath, default=None, help="base path where models are stored; overrides --data-dir")
|
||||||
parser.add_argument("--ckpt", type=str, default=sd_model_file, help="path to checkpoint of stable diffusion model; if specified, this checkpoint will be added to the list of checkpoints and loaded",)
|
parser.add_argument("--config", type=normalized_filepath, default=sd_default_config, help="path to config which constructs model",)
|
||||||
parser.add_argument("--ckpt-dir", type=str, default=None, help="Path to directory with stable diffusion checkpoints")
|
parser.add_argument("--ckpt", type=normalized_filepath, default=sd_model_file, help="path to checkpoint of stable diffusion model; if specified, this checkpoint will be added to the list of checkpoints and loaded",)
|
||||||
parser.add_argument("--vae-dir", type=str, default=None, help="Path to directory with VAE files")
|
parser.add_argument("--ckpt-dir", type=normalized_filepath, default=None, help="Path to directory with stable diffusion checkpoints")
|
||||||
parser.add_argument("--gfpgan-dir", type=str, help="GFPGAN directory", default=('./src/gfpgan' if os.path.exists('./src/gfpgan') else './GFPGAN'))
|
parser.add_argument("--vae-dir", type=normalized_filepath, default=None, help="Path to directory with VAE files")
|
||||||
parser.add_argument("--gfpgan-model", type=str, help="GFPGAN model file name", default=None)
|
parser.add_argument("--gfpgan-dir", type=normalized_filepath, help="GFPGAN directory", default=('./src/gfpgan' if os.path.exists('./src/gfpgan') else './GFPGAN'))
|
||||||
|
parser.add_argument("--gfpgan-model", type=normalized_filepath, help="GFPGAN model file name", default=None)
|
||||||
parser.add_argument("--no-half", action='store_true', help="do not switch the model to 16-bit floats")
|
parser.add_argument("--no-half", action='store_true', help="do not switch the model to 16-bit floats")
|
||||||
parser.add_argument("--no-half-vae", action='store_true', help="do not switch the VAE model to 16-bit floats")
|
parser.add_argument("--no-half-vae", action='store_true', help="do not switch the VAE model to 16-bit floats")
|
||||||
parser.add_argument("--no-progressbar-hiding", action='store_true', help="do not hide progressbar in gradio UI (we hide it because it slows down ML if you have hardware acceleration in browser)")
|
parser.add_argument("--no-progressbar-hiding", action='store_true', help="do not hide progressbar in gradio UI (we hide it because it slows down ML if you have hardware acceleration in browser)")
|
||||||
parser.add_argument("--max-batch-count", type=int, default=16, help="maximum batch count value for the UI")
|
parser.add_argument("--max-batch-count", type=int, default=16, help="does not do anything")
|
||||||
parser.add_argument("--embeddings-dir", type=str, default=os.path.join(data_path, 'embeddings'), help="embeddings directory for textual inversion (default: embeddings)")
|
parser.add_argument("--embeddings-dir", type=normalized_filepath, default=os.path.join(data_path, 'embeddings'), help="embeddings directory for textual inversion (default: embeddings)")
|
||||||
parser.add_argument("--textual-inversion-templates-dir", type=str, default=os.path.join(script_path, 'textual_inversion_templates'), help="directory with textual inversion templates")
|
parser.add_argument("--textual-inversion-templates-dir", type=normalized_filepath, default=os.path.join(script_path, 'textual_inversion_templates'), help="directory with textual inversion templates")
|
||||||
parser.add_argument("--hypernetwork-dir", type=str, default=os.path.join(models_path, 'hypernetworks'), help="hypernetwork directory")
|
parser.add_argument("--hypernetwork-dir", type=normalized_filepath, default=os.path.join(models_path, 'hypernetworks'), help="hypernetwork directory")
|
||||||
parser.add_argument("--localizations-dir", type=str, default=os.path.join(script_path, 'localizations'), help="localizations directory")
|
parser.add_argument("--localizations-dir", type=normalized_filepath, default=os.path.join(script_path, 'localizations'), help="localizations directory")
|
||||||
parser.add_argument("--allow-code", action='store_true', help="allow custom script execution from webui")
|
parser.add_argument("--allow-code", action='store_true', help="allow custom script execution from webui")
|
||||||
parser.add_argument("--medvram", action='store_true', help="enable stable diffusion model optimizations for sacrificing a little speed for low VRM usage")
|
parser.add_argument("--medvram", action='store_true', help="enable stable diffusion model optimizations for sacrificing a little speed for low VRM usage")
|
||||||
parser.add_argument("--medvram-sdxl", action='store_true', help="enable --medvram optimization just for SDXL models")
|
parser.add_argument("--medvram-sdxl", action='store_true', help="enable --medvram optimization just for SDXL models")
|
||||||
@@ -41,19 +42,20 @@ parser.add_argument("--lowvram", action='store_true', help="enable stable diffus
|
|||||||
parser.add_argument("--lowram", action='store_true', help="load stable diffusion checkpoint weights to VRAM instead of RAM")
|
parser.add_argument("--lowram", action='store_true', help="load stable diffusion checkpoint weights to VRAM instead of RAM")
|
||||||
parser.add_argument("--always-batch-cond-uncond", action='store_true', help="does not do anything")
|
parser.add_argument("--always-batch-cond-uncond", action='store_true', help="does not do anything")
|
||||||
parser.add_argument("--unload-gfpgan", action='store_true', help="does not do anything.")
|
parser.add_argument("--unload-gfpgan", action='store_true', help="does not do anything.")
|
||||||
parser.add_argument("--precision", type=str, help="evaluate at this precision", choices=["full", "autocast"], default="autocast")
|
parser.add_argument("--precision", type=str, help="evaluate at this precision", choices=["full", "half", "autocast"], default="autocast")
|
||||||
parser.add_argument("--upcast-sampling", action='store_true', help="upcast sampling. No effect with --no-half. Usually produces similar results to --no-half with better performance while using less memory.")
|
parser.add_argument("--upcast-sampling", action='store_true', help="upcast sampling. No effect with --no-half. Usually produces similar results to --no-half with better performance while using less memory.")
|
||||||
parser.add_argument("--share", action='store_true', help="use share=True for gradio and make the UI accessible through their site")
|
parser.add_argument("--share", action='store_true', help="use share=True for gradio and make the UI accessible through their site")
|
||||||
parser.add_argument("--ngrok", type=str, help="ngrok authtoken, alternative to gradio --share", default=None)
|
parser.add_argument("--ngrok", type=str, help="ngrok authtoken, alternative to gradio --share", default=None)
|
||||||
parser.add_argument("--ngrok-region", type=str, help="does not do anything.", default="")
|
parser.add_argument("--ngrok-region", type=str, help="does not do anything.", default="")
|
||||||
parser.add_argument("--ngrok-options", type=json.loads, help='The options to pass to ngrok in JSON format, e.g.: \'{"authtoken_from_env":true, "basic_auth":"user:password", "oauth_provider":"google", "oauth_allow_emails":"user@asdf.com"}\'', default=dict())
|
parser.add_argument("--ngrok-options", type=json.loads, help='The options to pass to ngrok in JSON format, e.g.: \'{"authtoken_from_env":true, "basic_auth":"user:password", "oauth_provider":"google", "oauth_allow_emails":"user@asdf.com"}\'', default=dict())
|
||||||
parser.add_argument("--enable-insecure-extension-access", action='store_true', help="enable extensions tab regardless of other options")
|
parser.add_argument("--enable-insecure-extension-access", action='store_true', help="enable extensions tab regardless of other options")
|
||||||
parser.add_argument("--codeformer-models-path", type=str, help="Path to directory with codeformer model file(s).", default=os.path.join(models_path, 'Codeformer'))
|
parser.add_argument("--codeformer-models-path", type=normalized_filepath, help="Path to directory with codeformer model file(s).", default=os.path.join(models_path, 'Codeformer'))
|
||||||
parser.add_argument("--gfpgan-models-path", type=str, help="Path to directory with GFPGAN model file(s).", default=os.path.join(models_path, 'GFPGAN'))
|
parser.add_argument("--gfpgan-models-path", type=normalized_filepath, help="Path to directory with GFPGAN model file(s).", default=os.path.join(models_path, 'GFPGAN'))
|
||||||
parser.add_argument("--esrgan-models-path", type=str, help="Path to directory with ESRGAN model file(s).", default=os.path.join(models_path, 'ESRGAN'))
|
parser.add_argument("--esrgan-models-path", type=normalized_filepath, help="Path to directory with ESRGAN model file(s).", default=os.path.join(models_path, 'ESRGAN'))
|
||||||
parser.add_argument("--bsrgan-models-path", type=str, help="Path to directory with BSRGAN model file(s).", default=os.path.join(models_path, 'BSRGAN'))
|
parser.add_argument("--bsrgan-models-path", type=normalized_filepath, help="Path to directory with BSRGAN model file(s).", default=os.path.join(models_path, 'BSRGAN'))
|
||||||
parser.add_argument("--realesrgan-models-path", type=str, help="Path to directory with RealESRGAN model file(s).", default=os.path.join(models_path, 'RealESRGAN'))
|
parser.add_argument("--realesrgan-models-path", type=normalized_filepath, help="Path to directory with RealESRGAN model file(s).", default=os.path.join(models_path, 'RealESRGAN'))
|
||||||
parser.add_argument("--clip-models-path", type=str, help="Path to directory with CLIP model file(s).", default=None)
|
parser.add_argument("--dat-models-path", type=normalized_filepath, help="Path to directory with DAT model file(s).", default=os.path.join(models_path, 'DAT'))
|
||||||
|
parser.add_argument("--clip-models-path", type=normalized_filepath, help="Path to directory with CLIP model file(s).", default=None)
|
||||||
parser.add_argument("--xformers", action='store_true', help="enable xformers for cross attention layers")
|
parser.add_argument("--xformers", action='store_true', help="enable xformers for cross attention layers")
|
||||||
parser.add_argument("--force-enable-xformers", action='store_true', help="enable xformers for cross attention layers regardless of whether the checking code thinks you can run it; do not make bug reports if this fails to work")
|
parser.add_argument("--force-enable-xformers", action='store_true', help="enable xformers for cross attention layers regardless of whether the checking code thinks you can run it; do not make bug reports if this fails to work")
|
||||||
parser.add_argument("--xformers-flash-attention", action='store_true', help="enable xformers with Flash Attention to improve reproducibility (supported for SD2.x or variant only)")
|
parser.add_argument("--xformers-flash-attention", action='store_true', help="enable xformers with Flash Attention to improve reproducibility (supported for SD2.x or variant only)")
|
||||||
@@ -83,18 +85,18 @@ parser.add_argument("--freeze-specific-settings", type=str, help='disable editin
|
|||||||
parser.add_argument("--ui-settings-file", type=str, help="filename to use for ui settings", default=os.path.join(data_path, 'config.json'))
|
parser.add_argument("--ui-settings-file", type=str, help="filename to use for ui settings", default=os.path.join(data_path, 'config.json'))
|
||||||
parser.add_argument("--gradio-debug", action='store_true', help="launch gradio with --debug option")
|
parser.add_argument("--gradio-debug", action='store_true', help="launch gradio with --debug option")
|
||||||
parser.add_argument("--gradio-auth", type=str, help='set gradio authentication like "username:password"; or comma-delimit multiple like "u1:p1,u2:p2,u3:p3"', default=None)
|
parser.add_argument("--gradio-auth", type=str, help='set gradio authentication like "username:password"; or comma-delimit multiple like "u1:p1,u2:p2,u3:p3"', default=None)
|
||||||
parser.add_argument("--gradio-auth-path", type=str, help='set gradio authentication file path ex. "/path/to/auth/file" same auth format as --gradio-auth', default=None)
|
parser.add_argument("--gradio-auth-path", type=normalized_filepath, help='set gradio authentication file path ex. "/path/to/auth/file" same auth format as --gradio-auth', default=None)
|
||||||
parser.add_argument("--gradio-img2img-tool", type=str, help='does not do anything')
|
parser.add_argument("--gradio-img2img-tool", type=str, help='does not do anything')
|
||||||
parser.add_argument("--gradio-inpaint-tool", type=str, help="does not do anything")
|
parser.add_argument("--gradio-inpaint-tool", type=str, help="does not do anything")
|
||||||
parser.add_argument("--gradio-allowed-path", action='append', help="add path to gradio's allowed_paths, make it possible to serve files from it", default=[data_path])
|
parser.add_argument("--gradio-allowed-path", action='append', help="add path to gradio's allowed_paths, make it possible to serve files from it", default=[data_path])
|
||||||
parser.add_argument("--opt-channelslast", action='store_true', help="change memory type for stable diffusion to channels last")
|
parser.add_argument("--opt-channelslast", action='store_true', help="change memory type for stable diffusion to channels last")
|
||||||
parser.add_argument("--styles-file", type=str, help="filename to use for styles", default=os.path.join(data_path, 'styles.csv'))
|
parser.add_argument("--styles-file", type=str, action='append', help="path or wildcard path of styles files, allow multiple entries.", default=[])
|
||||||
parser.add_argument("--autolaunch", action='store_true', help="open the webui URL in the system's default browser upon launch", default=False)
|
parser.add_argument("--autolaunch", action='store_true', help="open the webui URL in the system's default browser upon launch", default=False)
|
||||||
parser.add_argument("--theme", type=str, help="launches the UI with light or dark theme", default=None)
|
parser.add_argument("--theme", type=str, help="launches the UI with light or dark theme", default=None)
|
||||||
parser.add_argument("--use-textbox-seed", action='store_true', help="use textbox for seeds in UI (no up/down, but possible to input long seeds)", default=False)
|
parser.add_argument("--use-textbox-seed", action='store_true', help="use textbox for seeds in UI (no up/down, but possible to input long seeds)", default=False)
|
||||||
parser.add_argument("--disable-console-progressbars", action='store_true', help="do not output progressbars to console", default=False)
|
parser.add_argument("--disable-console-progressbars", action='store_true', help="do not output progressbars to console", default=False)
|
||||||
parser.add_argument("--enable-console-prompts", action='store_true', help="does not do anything", default=False) # Legacy compatibility, use as default value shared.opts.enable_console_prompts
|
parser.add_argument("--enable-console-prompts", action='store_true', help="does not do anything", default=False) # Legacy compatibility, use as default value shared.opts.enable_console_prompts
|
||||||
parser.add_argument('--vae-path', type=str, help='Checkpoint to use as VAE; setting this argument disables all settings related to VAE', default=None)
|
parser.add_argument('--vae-path', type=normalized_filepath, help='Checkpoint to use as VAE; setting this argument disables all settings related to VAE', default=None)
|
||||||
parser.add_argument("--disable-safe-unpickle", action='store_true', help="disable checking pytorch models for malicious code", default=False)
|
parser.add_argument("--disable-safe-unpickle", action='store_true', help="disable checking pytorch models for malicious code", default=False)
|
||||||
parser.add_argument("--api", action='store_true', help="use api=True to launch the API together with the webui (use --nowebui instead for only the API)")
|
parser.add_argument("--api", action='store_true', help="use api=True to launch the API together with the webui (use --nowebui instead for only the API)")
|
||||||
parser.add_argument("--api-auth", type=str, help='Set authentication for API like "username:password"; or comma-delimit multiple like "u1:p1,u2:p2,u3:p3"', default=None)
|
parser.add_argument("--api-auth", type=str, help='Set authentication for API like "username:password"; or comma-delimit multiple like "u1:p1,u2:p2,u3:p3"', default=None)
|
||||||
@@ -120,4 +122,8 @@ parser.add_argument('--api-server-stop', action='store_true', help='enable serve
|
|||||||
parser.add_argument('--timeout-keep-alive', type=int, default=30, help='set timeout_keep_alive for uvicorn')
|
parser.add_argument('--timeout-keep-alive', type=int, default=30, help='set timeout_keep_alive for uvicorn')
|
||||||
parser.add_argument("--disable-all-extensions", action='store_true', help="prevent all extensions from running regardless of any other settings", default=False)
|
parser.add_argument("--disable-all-extensions", action='store_true', help="prevent all extensions from running regardless of any other settings", default=False)
|
||||||
parser.add_argument("--disable-extra-extensions", action='store_true', help="prevent all extensions except built-in from running regardless of any other settings", default=False)
|
parser.add_argument("--disable-extra-extensions", action='store_true', help="prevent all extensions except built-in from running regardless of any other settings", default=False)
|
||||||
parser.add_argument("--skip-load-model-at-start", action='store_true', help="if load a model at web start, only take effect when --nowebui", )
|
parser.add_argument("--skip-load-model-at-start", action='store_true', help="if load a model at web start, only take effect when --nowebui")
|
||||||
|
parser.add_argument("--unix-filenames-sanitization", action='store_true', help="allow any symbols except '/' in filenames. May conflict with your browser and file system")
|
||||||
|
parser.add_argument("--filenames-max-length", type=int, default=128, help='maximal length of filenames of saved images. If you override it, it can conflict with your file system')
|
||||||
|
parser.add_argument("--no-prompt-history", action='store_true', help="disable read prompt from last generation feature; settings this argument will not create '--data_path/params.txt' file")
|
||||||
|
parser.add_argument("--uv", action='store_true', help="use the uv package manager")
|
||||||
|
|||||||
@@ -50,7 +50,7 @@ class FaceRestorerCodeFormer(face_restoration_utils.CommonFaceRestoration):
|
|||||||
|
|
||||||
def restore_face(cropped_face_t):
|
def restore_face(cropped_face_t):
|
||||||
assert self.net is not None
|
assert self.net is not None
|
||||||
return self.net(cropped_face_t, w=w, adain=True)[0]
|
return self.net(cropped_face_t, weight=w, adain=True)[0]
|
||||||
|
|
||||||
return self.restore_with_helper(np_image, restore_face)
|
return self.restore_with_helper(np_image, restore_face)
|
||||||
|
|
||||||
|
|||||||
+18
-4
@@ -1,7 +1,7 @@
|
|||||||
import os
|
import os
|
||||||
|
|
||||||
from modules import modelloader, errors
|
from modules import modelloader, errors
|
||||||
from modules.shared import cmd_opts, opts
|
from modules.shared import cmd_opts, opts, hf_endpoint
|
||||||
from modules.upscaler import Upscaler, UpscalerData
|
from modules.upscaler import Upscaler, UpscalerData
|
||||||
from modules.upscaler_utils import upscale_with_model
|
from modules.upscaler_utils import upscale_with_model
|
||||||
|
|
||||||
@@ -49,7 +49,18 @@ class UpscalerDAT(Upscaler):
|
|||||||
scaler.local_data_path = modelloader.load_file_from_url(
|
scaler.local_data_path = modelloader.load_file_from_url(
|
||||||
scaler.data_path,
|
scaler.data_path,
|
||||||
model_dir=self.model_download_path,
|
model_dir=self.model_download_path,
|
||||||
|
hash_prefix=scaler.sha256,
|
||||||
)
|
)
|
||||||
|
|
||||||
|
if os.path.getsize(scaler.local_data_path) < 200:
|
||||||
|
# Re-download if the file is too small, probably an LFS pointer
|
||||||
|
scaler.local_data_path = modelloader.load_file_from_url(
|
||||||
|
scaler.data_path,
|
||||||
|
model_dir=self.model_download_path,
|
||||||
|
hash_prefix=scaler.sha256,
|
||||||
|
re_download=True,
|
||||||
|
)
|
||||||
|
|
||||||
if not os.path.exists(scaler.local_data_path):
|
if not os.path.exists(scaler.local_data_path):
|
||||||
raise FileNotFoundError(f"DAT data missing: {scaler.local_data_path}")
|
raise FileNotFoundError(f"DAT data missing: {scaler.local_data_path}")
|
||||||
return scaler
|
return scaler
|
||||||
@@ -60,20 +71,23 @@ def get_dat_models(scaler):
|
|||||||
return [
|
return [
|
||||||
UpscalerData(
|
UpscalerData(
|
||||||
name="DAT x2",
|
name="DAT x2",
|
||||||
path="https://github.com/n0kovo/dat_upscaler_models/raw/main/DAT/DAT_x2.pth",
|
path=f"{hf_endpoint}/w-e-w/DAT/resolve/main/experiments/pretrained_models/DAT/DAT_x2.pth",
|
||||||
scale=2,
|
scale=2,
|
||||||
upscaler=scaler,
|
upscaler=scaler,
|
||||||
|
sha256='7760aa96e4ee77e29d4f89c3a4486200042e019461fdb8aa286f49aa00b89b51',
|
||||||
),
|
),
|
||||||
UpscalerData(
|
UpscalerData(
|
||||||
name="DAT x3",
|
name="DAT x3",
|
||||||
path="https://github.com/n0kovo/dat_upscaler_models/raw/main/DAT/DAT_x3.pth",
|
path=f"{hf_endpoint}/w-e-w/DAT/resolve/main/experiments/pretrained_models/DAT/DAT_x3.pth",
|
||||||
scale=3,
|
scale=3,
|
||||||
upscaler=scaler,
|
upscaler=scaler,
|
||||||
|
sha256='581973e02c06f90d4eb90acf743ec9604f56f3c2c6f9e1e2c2b38ded1f80d197',
|
||||||
),
|
),
|
||||||
UpscalerData(
|
UpscalerData(
|
||||||
name="DAT x4",
|
name="DAT x4",
|
||||||
path="https://github.com/n0kovo/dat_upscaler_models/raw/main/DAT/DAT_x4.pth",
|
path=f"{hf_endpoint}/w-e-w/DAT/resolve/main/experiments/pretrained_models/DAT/DAT_x4.pth",
|
||||||
scale=4,
|
scale=4,
|
||||||
upscaler=scaler,
|
upscaler=scaler,
|
||||||
|
sha256='391a6ce69899dff5ea3214557e9d585608254579217169faf3d4c353caff049e',
|
||||||
),
|
),
|
||||||
]
|
]
|
||||||
|
|||||||
@@ -57,7 +57,7 @@ class DeepDanbooru:
|
|||||||
a = np.expand_dims(np.array(pic, dtype=np.float32), 0) / 255
|
a = np.expand_dims(np.array(pic, dtype=np.float32), 0) / 255
|
||||||
|
|
||||||
with torch.no_grad(), devices.autocast():
|
with torch.no_grad(), devices.autocast():
|
||||||
x = torch.from_numpy(a).to(devices.device)
|
x = torch.from_numpy(a).to(devices.device, devices.dtype)
|
||||||
y = self.model(x)[0].detach().cpu().numpy()
|
y = self.model(x)[0].detach().cpu().numpy()
|
||||||
|
|
||||||
probability_dict = {}
|
probability_dict = {}
|
||||||
|
|||||||
+50
-10
@@ -3,8 +3,7 @@ import contextlib
|
|||||||
from functools import lru_cache
|
from functools import lru_cache
|
||||||
|
|
||||||
import torch
|
import torch
|
||||||
from modules import errors, shared
|
from modules import errors, shared, npu_specific
|
||||||
from modules import torch_utils
|
|
||||||
|
|
||||||
if sys.platform == "darwin":
|
if sys.platform == "darwin":
|
||||||
from modules import mac_specific
|
from modules import mac_specific
|
||||||
@@ -58,6 +57,9 @@ def get_optimal_device_name():
|
|||||||
if has_xpu():
|
if has_xpu():
|
||||||
return xpu_specific.get_xpu_device_string()
|
return xpu_specific.get_xpu_device_string()
|
||||||
|
|
||||||
|
if npu_specific.has_npu:
|
||||||
|
return npu_specific.get_npu_device_string()
|
||||||
|
|
||||||
return "cpu"
|
return "cpu"
|
||||||
|
|
||||||
|
|
||||||
@@ -85,6 +87,16 @@ def torch_gc():
|
|||||||
if has_xpu():
|
if has_xpu():
|
||||||
xpu_specific.torch_xpu_gc()
|
xpu_specific.torch_xpu_gc()
|
||||||
|
|
||||||
|
if npu_specific.has_npu:
|
||||||
|
torch_npu_set_device()
|
||||||
|
npu_specific.torch_npu_gc()
|
||||||
|
|
||||||
|
|
||||||
|
def torch_npu_set_device():
|
||||||
|
# Work around due to bug in torch_npu, revert me after fixed, @see https://gitee.com/ascend/pytorch/issues/I8KECW?from=project-issue
|
||||||
|
if npu_specific.has_npu:
|
||||||
|
torch.npu.set_device(0)
|
||||||
|
|
||||||
|
|
||||||
def enable_tf32():
|
def enable_tf32():
|
||||||
if torch.cuda.is_available():
|
if torch.cuda.is_available():
|
||||||
@@ -102,6 +114,9 @@ errors.run(enable_tf32, "Enabling TF32")
|
|||||||
|
|
||||||
cpu: torch.device = torch.device("cpu")
|
cpu: torch.device = torch.device("cpu")
|
||||||
fp8: bool = False
|
fp8: bool = False
|
||||||
|
# Force fp16 for all models in inference. No casting during inference.
|
||||||
|
# This flag is controlled by "--precision half" command line arg.
|
||||||
|
force_fp16: bool = False
|
||||||
device: torch.device = None
|
device: torch.device = None
|
||||||
device_interrogate: torch.device = None
|
device_interrogate: torch.device = None
|
||||||
device_gfpgan: torch.device = None
|
device_gfpgan: torch.device = None
|
||||||
@@ -115,6 +130,8 @@ unet_needs_upcast = False
|
|||||||
|
|
||||||
|
|
||||||
def cond_cast_unet(input):
|
def cond_cast_unet(input):
|
||||||
|
if force_fp16:
|
||||||
|
return input.to(torch.float16)
|
||||||
return input.to(dtype_unet) if unet_needs_upcast else input
|
return input.to(dtype_unet) if unet_needs_upcast else input
|
||||||
|
|
||||||
|
|
||||||
@@ -141,7 +158,12 @@ def manual_cast_forward(target_dtype):
|
|||||||
args = [arg.to(target_dtype) if isinstance(arg, torch.Tensor) else arg for arg in args]
|
args = [arg.to(target_dtype) if isinstance(arg, torch.Tensor) else arg for arg in args]
|
||||||
kwargs = {k: v.to(target_dtype) if isinstance(v, torch.Tensor) else v for k, v in kwargs.items()}
|
kwargs = {k: v.to(target_dtype) if isinstance(v, torch.Tensor) else v for k, v in kwargs.items()}
|
||||||
|
|
||||||
org_dtype = torch_utils.get_param(self).dtype
|
org_dtype = target_dtype
|
||||||
|
for param in self.parameters():
|
||||||
|
if param.dtype != target_dtype:
|
||||||
|
org_dtype = param.dtype
|
||||||
|
break
|
||||||
|
|
||||||
if org_dtype != target_dtype:
|
if org_dtype != target_dtype:
|
||||||
self.to(target_dtype)
|
self.to(target_dtype)
|
||||||
result = self.org_forward(*args, **kwargs)
|
result = self.org_forward(*args, **kwargs)
|
||||||
@@ -170,7 +192,7 @@ def manual_cast(target_dtype):
|
|||||||
continue
|
continue
|
||||||
applied = True
|
applied = True
|
||||||
org_forward = module_type.forward
|
org_forward = module_type.forward
|
||||||
if module_type == torch.nn.MultiheadAttention and has_xpu():
|
if module_type == torch.nn.MultiheadAttention:
|
||||||
module_type.forward = manual_cast_forward(torch.float32)
|
module_type.forward = manual_cast_forward(torch.float32)
|
||||||
else:
|
else:
|
||||||
module_type.forward = manual_cast_forward(target_dtype)
|
module_type.forward = manual_cast_forward(target_dtype)
|
||||||
@@ -189,6 +211,11 @@ def autocast(disable=False):
|
|||||||
if disable:
|
if disable:
|
||||||
return contextlib.nullcontext()
|
return contextlib.nullcontext()
|
||||||
|
|
||||||
|
if force_fp16:
|
||||||
|
# No casting during inference if force_fp16 is enabled.
|
||||||
|
# All tensor dtype conversion happens before inference.
|
||||||
|
return contextlib.nullcontext()
|
||||||
|
|
||||||
if fp8 and device==cpu:
|
if fp8 and device==cpu:
|
||||||
return torch.autocast("cpu", dtype=torch.bfloat16, enabled=True)
|
return torch.autocast("cpu", dtype=torch.bfloat16, enabled=True)
|
||||||
|
|
||||||
@@ -216,22 +243,22 @@ def test_for_nans(x, where):
|
|||||||
if shared.cmd_opts.disable_nan_check:
|
if shared.cmd_opts.disable_nan_check:
|
||||||
return
|
return
|
||||||
|
|
||||||
if not torch.all(torch.isnan(x)).item():
|
if not torch.isnan(x[(0, ) * len(x.shape)]):
|
||||||
return
|
return
|
||||||
|
|
||||||
if where == "unet":
|
if where == "unet":
|
||||||
message = "A tensor with all NaNs was produced in Unet."
|
message = "A tensor with NaNs was produced in Unet."
|
||||||
|
|
||||||
if not shared.cmd_opts.no_half:
|
if not shared.cmd_opts.no_half:
|
||||||
message += " This could be either because there's not enough precision to represent the picture, or because your video card does not support half type. Try setting the \"Upcast cross attention layer to float32\" option in Settings > Stable Diffusion or using the --no-half commandline argument to fix this."
|
message += " This could be either because there's not enough precision to represent the picture, or because your video card does not support half type. Try setting the \"Upcast cross attention layer to float32\" option in Settings > Stable Diffusion or using the --no-half commandline argument to fix this."
|
||||||
|
|
||||||
elif where == "vae":
|
elif where == "vae":
|
||||||
message = "A tensor with all NaNs was produced in VAE."
|
message = "A tensor with NaNs was produced in VAE."
|
||||||
|
|
||||||
if not shared.cmd_opts.no_half and not shared.cmd_opts.no_half_vae:
|
if not shared.cmd_opts.no_half and not shared.cmd_opts.no_half_vae:
|
||||||
message += " This could be because there's not enough precision to represent the picture. Try adding --no-half-vae commandline argument to fix this."
|
message += " This could be because there's not enough precision to represent the picture. Try adding --no-half-vae commandline argument to fix this."
|
||||||
else:
|
else:
|
||||||
message = "A tensor with all NaNs was produced."
|
message = "A tensor with NaNs was produced."
|
||||||
|
|
||||||
message += " Use --disable-nan-check commandline argument to disable this check."
|
message += " Use --disable-nan-check commandline argument to disable this check."
|
||||||
|
|
||||||
@@ -241,8 +268,8 @@ def test_for_nans(x, where):
|
|||||||
@lru_cache
|
@lru_cache
|
||||||
def first_time_calculation():
|
def first_time_calculation():
|
||||||
"""
|
"""
|
||||||
just do any calculation with pytorch layers - the first time this is done it allocaltes about 700MB of memory and
|
just do any calculation with pytorch layers - the first time this is done it allocates about 700MB of memory and
|
||||||
spends about 2.7 seconds doing that, at least wih NVidia.
|
spends about 2.7 seconds doing that, at least with NVidia.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
x = torch.zeros((1, 1)).to(device, dtype)
|
x = torch.zeros((1, 1)).to(device, dtype)
|
||||||
@@ -253,3 +280,16 @@ def first_time_calculation():
|
|||||||
conv2d = torch.nn.Conv2d(1, 1, (3, 3)).to(device, dtype)
|
conv2d = torch.nn.Conv2d(1, 1, (3, 3)).to(device, dtype)
|
||||||
conv2d(x)
|
conv2d(x)
|
||||||
|
|
||||||
|
|
||||||
|
def force_model_fp16():
|
||||||
|
"""
|
||||||
|
ldm and sgm has modules.diffusionmodules.util.GroupNorm32.forward, which
|
||||||
|
force conversion of input to float32. If force_fp16 is enabled, we need to
|
||||||
|
prevent this casting.
|
||||||
|
"""
|
||||||
|
assert force_fp16
|
||||||
|
import sgm.modules.diffusionmodules.util as sgm_util
|
||||||
|
import ldm.modules.diffusionmodules.util as ldm_util
|
||||||
|
sgm_util.GroupNorm32 = torch.nn.GroupNorm
|
||||||
|
ldm_util.GroupNorm32 = torch.nn.GroupNorm
|
||||||
|
print("ldm/sgm GroupNorm32 replaced with normal torch.nn.GroupNorm due to `--precision half`.")
|
||||||
|
|||||||
+64
-6
@@ -1,6 +1,7 @@
|
|||||||
from __future__ import annotations
|
from __future__ import annotations
|
||||||
|
|
||||||
import configparser
|
import configparser
|
||||||
|
import dataclasses
|
||||||
import os
|
import os
|
||||||
import threading
|
import threading
|
||||||
import re
|
import re
|
||||||
@@ -9,6 +10,10 @@ from modules import shared, errors, cache, scripts
|
|||||||
from modules.gitpython_hack import Repo
|
from modules.gitpython_hack import Repo
|
||||||
from modules.paths_internal import extensions_dir, extensions_builtin_dir, script_path # noqa: F401
|
from modules.paths_internal import extensions_dir, extensions_builtin_dir, script_path # noqa: F401
|
||||||
|
|
||||||
|
extensions: list[Extension] = []
|
||||||
|
extension_paths: dict[str, Extension] = {}
|
||||||
|
loaded_extensions: dict[str, Exception] = {}
|
||||||
|
|
||||||
|
|
||||||
os.makedirs(extensions_dir, exist_ok=True)
|
os.makedirs(extensions_dir, exist_ok=True)
|
||||||
|
|
||||||
@@ -22,6 +27,13 @@ def active():
|
|||||||
return [x for x in extensions if x.enabled]
|
return [x for x in extensions if x.enabled]
|
||||||
|
|
||||||
|
|
||||||
|
@dataclasses.dataclass
|
||||||
|
class CallbackOrderInfo:
|
||||||
|
name: str
|
||||||
|
before: list
|
||||||
|
after: list
|
||||||
|
|
||||||
|
|
||||||
class ExtensionMetadata:
|
class ExtensionMetadata:
|
||||||
filename = "metadata.ini"
|
filename = "metadata.ini"
|
||||||
config: configparser.ConfigParser
|
config: configparser.ConfigParser
|
||||||
@@ -42,7 +54,7 @@ class ExtensionMetadata:
|
|||||||
self.canonical_name = self.config.get("Extension", "Name", fallback=canonical_name)
|
self.canonical_name = self.config.get("Extension", "Name", fallback=canonical_name)
|
||||||
self.canonical_name = canonical_name.lower().strip()
|
self.canonical_name = canonical_name.lower().strip()
|
||||||
|
|
||||||
self.requires = self.get_script_requirements("Requires", "Extension")
|
self.requires = None
|
||||||
|
|
||||||
def get_script_requirements(self, field, section, extra_section=None):
|
def get_script_requirements(self, field, section, extra_section=None):
|
||||||
"""reads a list of requirements from the config; field is the name of the field in the ini file,
|
"""reads a list of requirements from the config; field is the name of the field in the ini file,
|
||||||
@@ -54,7 +66,15 @@ class ExtensionMetadata:
|
|||||||
if extra_section:
|
if extra_section:
|
||||||
x = x + ', ' + self.config.get(extra_section, field, fallback='')
|
x = x + ', ' + self.config.get(extra_section, field, fallback='')
|
||||||
|
|
||||||
return self.parse_list(x.lower())
|
listed_requirements = self.parse_list(x.lower())
|
||||||
|
res = []
|
||||||
|
|
||||||
|
for requirement in listed_requirements:
|
||||||
|
loaded_requirements = (x for x in requirement.split("|") if x in loaded_extensions)
|
||||||
|
relevant_requirement = next(loaded_requirements, requirement)
|
||||||
|
res.append(relevant_requirement)
|
||||||
|
|
||||||
|
return res
|
||||||
|
|
||||||
def parse_list(self, text):
|
def parse_list(self, text):
|
||||||
"""converts a line from config ("ext1 ext2, ext3 ") into a python list (["ext1", "ext2", "ext3"])"""
|
"""converts a line from config ("ext1 ext2, ext3 ") into a python list (["ext1", "ext2", "ext3"])"""
|
||||||
@@ -65,6 +85,22 @@ class ExtensionMetadata:
|
|||||||
# both "," and " " are accepted as separator
|
# both "," and " " are accepted as separator
|
||||||
return [x for x in re.split(r"[,\s]+", text.strip()) if x]
|
return [x for x in re.split(r"[,\s]+", text.strip()) if x]
|
||||||
|
|
||||||
|
def list_callback_order_instructions(self):
|
||||||
|
for section in self.config.sections():
|
||||||
|
if not section.startswith("callbacks/"):
|
||||||
|
continue
|
||||||
|
|
||||||
|
callback_name = section[10:]
|
||||||
|
|
||||||
|
if not callback_name.startswith(self.canonical_name):
|
||||||
|
errors.report(f"Callback order section for extension {self.canonical_name} is referencing the wrong extension: {section}")
|
||||||
|
continue
|
||||||
|
|
||||||
|
before = self.parse_list(self.config.get(section, 'Before', fallback=''))
|
||||||
|
after = self.parse_list(self.config.get(section, 'After', fallback=''))
|
||||||
|
|
||||||
|
yield CallbackOrderInfo(callback_name, before, after)
|
||||||
|
|
||||||
|
|
||||||
class Extension:
|
class Extension:
|
||||||
lock = threading.Lock()
|
lock = threading.Lock()
|
||||||
@@ -155,14 +191,17 @@ class Extension:
|
|||||||
|
|
||||||
def check_updates(self):
|
def check_updates(self):
|
||||||
repo = Repo(self.path)
|
repo = Repo(self.path)
|
||||||
|
branch_name = f'{repo.remote().name}/{self.branch}'
|
||||||
for fetch in repo.remote().fetch(dry_run=True):
|
for fetch in repo.remote().fetch(dry_run=True):
|
||||||
|
if self.branch and fetch.name != branch_name:
|
||||||
|
continue
|
||||||
if fetch.flags != fetch.HEAD_UPTODATE:
|
if fetch.flags != fetch.HEAD_UPTODATE:
|
||||||
self.can_update = True
|
self.can_update = True
|
||||||
self.status = "new commits"
|
self.status = "new commits"
|
||||||
return
|
return
|
||||||
|
|
||||||
try:
|
try:
|
||||||
origin = repo.rev_parse('origin')
|
origin = repo.rev_parse(branch_name)
|
||||||
if repo.head.commit != origin:
|
if repo.head.commit != origin:
|
||||||
self.can_update = True
|
self.can_update = True
|
||||||
self.status = "behind HEAD"
|
self.status = "behind HEAD"
|
||||||
@@ -175,8 +214,10 @@ class Extension:
|
|||||||
self.can_update = False
|
self.can_update = False
|
||||||
self.status = "latest"
|
self.status = "latest"
|
||||||
|
|
||||||
def fetch_and_reset_hard(self, commit='origin'):
|
def fetch_and_reset_hard(self, commit=None):
|
||||||
repo = Repo(self.path)
|
repo = Repo(self.path)
|
||||||
|
if commit is None:
|
||||||
|
commit = f'{repo.remote().name}/{self.branch}'
|
||||||
# Fix: `error: Your local changes to the following files would be overwritten by merge`,
|
# Fix: `error: Your local changes to the following files would be overwritten by merge`,
|
||||||
# because WSL2 Docker set 755 file permissions instead of 644, this results to the error.
|
# because WSL2 Docker set 755 file permissions instead of 644, this results to the error.
|
||||||
repo.git.fetch(all=True)
|
repo.git.fetch(all=True)
|
||||||
@@ -186,6 +227,8 @@ class Extension:
|
|||||||
|
|
||||||
def list_extensions():
|
def list_extensions():
|
||||||
extensions.clear()
|
extensions.clear()
|
||||||
|
extension_paths.clear()
|
||||||
|
loaded_extensions.clear()
|
||||||
|
|
||||||
if shared.cmd_opts.disable_all_extensions:
|
if shared.cmd_opts.disable_all_extensions:
|
||||||
print("*** \"--disable-all-extensions\" arg was used, will not load any extensions ***")
|
print("*** \"--disable-all-extensions\" arg was used, will not load any extensions ***")
|
||||||
@@ -196,7 +239,6 @@ def list_extensions():
|
|||||||
elif shared.opts.disable_all_extensions == "extra":
|
elif shared.opts.disable_all_extensions == "extra":
|
||||||
print("*** \"Disable all extensions\" option was set, will only load built-in extensions ***")
|
print("*** \"Disable all extensions\" option was set, will only load built-in extensions ***")
|
||||||
|
|
||||||
loaded_extensions = {}
|
|
||||||
|
|
||||||
# scan through extensions directory and load metadata
|
# scan through extensions directory and load metadata
|
||||||
for dirname in [extensions_builtin_dir, extensions_dir]:
|
for dirname in [extensions_builtin_dir, extensions_dir]:
|
||||||
@@ -220,8 +262,12 @@ def list_extensions():
|
|||||||
is_builtin = dirname == extensions_builtin_dir
|
is_builtin = dirname == extensions_builtin_dir
|
||||||
extension = Extension(name=extension_dirname, path=path, enabled=extension_dirname not in shared.opts.disabled_extensions, is_builtin=is_builtin, metadata=metadata)
|
extension = Extension(name=extension_dirname, path=path, enabled=extension_dirname not in shared.opts.disabled_extensions, is_builtin=is_builtin, metadata=metadata)
|
||||||
extensions.append(extension)
|
extensions.append(extension)
|
||||||
|
extension_paths[extension.path] = extension
|
||||||
loaded_extensions[canonical_name] = extension
|
loaded_extensions[canonical_name] = extension
|
||||||
|
|
||||||
|
for extension in extensions:
|
||||||
|
extension.metadata.requires = extension.metadata.get_script_requirements("Requires", "Extension")
|
||||||
|
|
||||||
# check for requirements
|
# check for requirements
|
||||||
for extension in extensions:
|
for extension in extensions:
|
||||||
if not extension.enabled:
|
if not extension.enabled:
|
||||||
@@ -238,4 +284,16 @@ def list_extensions():
|
|||||||
continue
|
continue
|
||||||
|
|
||||||
|
|
||||||
extensions: list[Extension] = []
|
def find_extension(filename):
|
||||||
|
parentdir = os.path.dirname(os.path.realpath(filename))
|
||||||
|
|
||||||
|
while parentdir != filename:
|
||||||
|
extension = extension_paths.get(parentdir)
|
||||||
|
if extension is not None:
|
||||||
|
return extension
|
||||||
|
|
||||||
|
filename = parentdir
|
||||||
|
parentdir = os.path.dirname(filename)
|
||||||
|
|
||||||
|
return None
|
||||||
|
|
||||||
|
|||||||
@@ -60,7 +60,7 @@ class ExtraNetwork:
|
|||||||
Where name matches the name of this ExtraNetwork object, and arg1:arg2:arg3 are any natural number of text arguments
|
Where name matches the name of this ExtraNetwork object, and arg1:arg2:arg3 are any natural number of text arguments
|
||||||
separated by colon.
|
separated by colon.
|
||||||
|
|
||||||
Even if the user does not mention this ExtraNetwork in his prompt, the call will stil be made, with empty params_list -
|
Even if the user does not mention this ExtraNetwork in his prompt, the call will still be made, with empty params_list -
|
||||||
in this case, all effects of this extra networks should be disabled.
|
in this case, all effects of this extra networks should be disabled.
|
||||||
|
|
||||||
Can be called multiple times before deactivate() - each new call should override the previous call completely.
|
Can be called multiple times before deactivate() - each new call should override the previous call completely.
|
||||||
|
|||||||
+1
-1
@@ -23,7 +23,7 @@ def run_pnginfo(image):
|
|||||||
info = ''
|
info = ''
|
||||||
for key, text in items.items():
|
for key, text in items.items():
|
||||||
info += f"""
|
info += f"""
|
||||||
<div>
|
<div class="infotext">
|
||||||
<p><b>{plaintext_to_html(str(key))}</b></p>
|
<p><b>{plaintext_to_html(str(key))}</b></p>
|
||||||
<p>{plaintext_to_html(str(text))}</p>
|
<p>{plaintext_to_html(str(text))}</p>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -36,13 +36,11 @@ class FaceRestorerGFPGAN(face_restoration_utils.CommonFaceRestoration):
|
|||||||
ext_filter=['.pth'],
|
ext_filter=['.pth'],
|
||||||
):
|
):
|
||||||
if 'GFPGAN' in os.path.basename(model_path):
|
if 'GFPGAN' in os.path.basename(model_path):
|
||||||
model = modelloader.load_spandrel_model(
|
return modelloader.load_spandrel_model(
|
||||||
model_path,
|
model_path,
|
||||||
device=self.get_device(),
|
device=self.get_device(),
|
||||||
expected_architecture='GFPGAN',
|
expected_architecture='GFPGAN',
|
||||||
).model
|
).model
|
||||||
model.different_w = True # see https://github.com/chaiNNer-org/spandrel/pull/81
|
|
||||||
return model
|
|
||||||
raise ValueError("No GFPGAN model found")
|
raise ValueError("No GFPGAN model found")
|
||||||
|
|
||||||
def restore(self, np_image):
|
def restore(self, np_image):
|
||||||
|
|||||||
+33
-2
@@ -1,7 +1,7 @@
|
|||||||
import hashlib
|
import hashlib
|
||||||
import os.path
|
import os.path
|
||||||
|
|
||||||
from modules import shared
|
from modules import shared, errors
|
||||||
import modules.cache
|
import modules.cache
|
||||||
|
|
||||||
dump_cache = modules.cache.dump_cache
|
dump_cache = modules.cache.dump_cache
|
||||||
@@ -21,7 +21,10 @@ def calculate_sha256(filename):
|
|||||||
|
|
||||||
def sha256_from_cache(filename, title, use_addnet_hash=False):
|
def sha256_from_cache(filename, title, use_addnet_hash=False):
|
||||||
hashes = cache("hashes-addnet") if use_addnet_hash else cache("hashes")
|
hashes = cache("hashes-addnet") if use_addnet_hash else cache("hashes")
|
||||||
|
try:
|
||||||
ondisk_mtime = os.path.getmtime(filename)
|
ondisk_mtime = os.path.getmtime(filename)
|
||||||
|
except FileNotFoundError:
|
||||||
|
return None
|
||||||
|
|
||||||
if title not in hashes:
|
if title not in hashes:
|
||||||
return None
|
return None
|
||||||
@@ -29,7 +32,7 @@ def sha256_from_cache(filename, title, use_addnet_hash=False):
|
|||||||
cached_sha256 = hashes[title].get("sha256", None)
|
cached_sha256 = hashes[title].get("sha256", None)
|
||||||
cached_mtime = hashes[title].get("mtime", 0)
|
cached_mtime = hashes[title].get("mtime", 0)
|
||||||
|
|
||||||
if ondisk_mtime > cached_mtime or cached_sha256 is None:
|
if ondisk_mtime != cached_mtime or cached_sha256 is None:
|
||||||
return None
|
return None
|
||||||
|
|
||||||
return cached_sha256
|
return cached_sha256
|
||||||
@@ -79,3 +82,31 @@ def addnet_hash_safetensors(b):
|
|||||||
|
|
||||||
return hash_sha256.hexdigest()
|
return hash_sha256.hexdigest()
|
||||||
|
|
||||||
|
|
||||||
|
def partial_hash_from_cache(filename, *, ignore_cache: bool = False, digits: int = 8):
|
||||||
|
"""old hash that only looks at a small part of the file and is prone to collisions
|
||||||
|
kept for compatibility, don't use this for new things
|
||||||
|
"""
|
||||||
|
try:
|
||||||
|
filename = str(filename)
|
||||||
|
mtime = os.path.getmtime(filename)
|
||||||
|
hashes = cache('partial-hash')
|
||||||
|
cache_entry = hashes.get(filename, {})
|
||||||
|
cache_mtime = cache_entry.get("mtime", 0)
|
||||||
|
cache_hash = cache_entry.get("hash", None)
|
||||||
|
if mtime == cache_mtime and cache_hash and not ignore_cache:
|
||||||
|
return cache_hash[0:digits]
|
||||||
|
|
||||||
|
with open(filename, 'rb') as file:
|
||||||
|
m = hashlib.sha256()
|
||||||
|
file.seek(0x100000)
|
||||||
|
m.update(file.read(0x10000))
|
||||||
|
partial_hash = m.hexdigest()
|
||||||
|
hashes[filename] = {'mtime': mtime, 'hash': partial_hash}
|
||||||
|
return partial_hash[0:digits]
|
||||||
|
|
||||||
|
except FileNotFoundError:
|
||||||
|
pass
|
||||||
|
except Exception:
|
||||||
|
errors.report(f'Error calculating partial hash for {filename}', exc_info=True)
|
||||||
|
return 'NOFILE'
|
||||||
|
|||||||
@@ -11,7 +11,7 @@ import tqdm
|
|||||||
from einops import rearrange, repeat
|
from einops import rearrange, repeat
|
||||||
from ldm.util import default
|
from ldm.util import default
|
||||||
from modules import devices, sd_models, shared, sd_samplers, hashes, sd_hijack_checkpoint, errors
|
from modules import devices, sd_models, shared, sd_samplers, hashes, sd_hijack_checkpoint, errors
|
||||||
from modules.textual_inversion import textual_inversion, logging
|
from modules.textual_inversion import textual_inversion, saving_settings
|
||||||
from modules.textual_inversion.learn_schedule import LearnRateScheduler
|
from modules.textual_inversion.learn_schedule import LearnRateScheduler
|
||||||
from torch import einsum
|
from torch import einsum
|
||||||
from torch.nn.init import normal_, xavier_normal_, xavier_uniform_, kaiming_normal_, kaiming_uniform_, zeros_
|
from torch.nn.init import normal_, xavier_normal_, xavier_uniform_, kaiming_normal_, kaiming_uniform_, zeros_
|
||||||
@@ -95,6 +95,7 @@ class HypernetworkModule(torch.nn.Module):
|
|||||||
zeros_(b)
|
zeros_(b)
|
||||||
else:
|
else:
|
||||||
raise KeyError(f"Key {weight_init} is not defined as initialization!")
|
raise KeyError(f"Key {weight_init} is not defined as initialization!")
|
||||||
|
devices.torch_npu_set_device()
|
||||||
self.to(devices.device)
|
self.to(devices.device)
|
||||||
|
|
||||||
def fix_old_state_dict(self, state_dict):
|
def fix_old_state_dict(self, state_dict):
|
||||||
@@ -532,7 +533,7 @@ def train_hypernetwork(id_task, hypernetwork_name: str, learn_rate: float, batch
|
|||||||
model_name=checkpoint.model_name, model_hash=checkpoint.shorthash, num_of_dataset_images=len(ds),
|
model_name=checkpoint.model_name, model_hash=checkpoint.shorthash, num_of_dataset_images=len(ds),
|
||||||
**{field: getattr(hypernetwork, field) for field in ['layer_structure', 'activation_func', 'weight_init', 'add_layer_norm', 'use_dropout', ]}
|
**{field: getattr(hypernetwork, field) for field in ['layer_structure', 'activation_func', 'weight_init', 'add_layer_norm', 'use_dropout', ]}
|
||||||
)
|
)
|
||||||
logging.save_settings_to_file(log_directory, {**saved_params, **locals()})
|
saving_settings.save_settings_to_file(log_directory, {**saved_params, **locals()})
|
||||||
|
|
||||||
latent_sampling_method = ds.latent_sampling_method
|
latent_sampling_method = ds.latent_sampling_method
|
||||||
|
|
||||||
|
|||||||
+91
-12
@@ -1,7 +1,7 @@
|
|||||||
from __future__ import annotations
|
from __future__ import annotations
|
||||||
|
|
||||||
import datetime
|
import datetime
|
||||||
|
import functools
|
||||||
import pytz
|
import pytz
|
||||||
import io
|
import io
|
||||||
import math
|
import math
|
||||||
@@ -12,7 +12,9 @@ import re
|
|||||||
import numpy as np
|
import numpy as np
|
||||||
import piexif
|
import piexif
|
||||||
import piexif.helper
|
import piexif.helper
|
||||||
from PIL import Image, ImageFont, ImageDraw, ImageColor, PngImagePlugin
|
from PIL import Image, ImageFont, ImageDraw, ImageColor, PngImagePlugin, ImageOps
|
||||||
|
# pillow_avif needs to be imported somewhere in code for it to work
|
||||||
|
import pillow_avif # noqa: F401
|
||||||
import string
|
import string
|
||||||
import json
|
import json
|
||||||
import hashlib
|
import hashlib
|
||||||
@@ -52,11 +54,14 @@ def image_grid(imgs, batch_size=1, rows=None):
|
|||||||
params = script_callbacks.ImageGridLoopParams(imgs, cols, rows)
|
params = script_callbacks.ImageGridLoopParams(imgs, cols, rows)
|
||||||
script_callbacks.image_grid_callback(params)
|
script_callbacks.image_grid_callback(params)
|
||||||
|
|
||||||
w, h = imgs[0].size
|
w, h = map(max, zip(*(img.size for img in imgs)))
|
||||||
grid = Image.new('RGB', size=(params.cols * w, params.rows * h), color='black')
|
grid_background_color = ImageColor.getcolor(opts.grid_background_color, 'RGB')
|
||||||
|
grid = Image.new('RGB', size=(params.cols * w, params.rows * h), color=grid_background_color)
|
||||||
|
|
||||||
for i, img in enumerate(params.imgs):
|
for i, img in enumerate(params.imgs):
|
||||||
grid.paste(img, box=(i % params.cols * w, i // params.cols * h))
|
img_w, img_h = img.size
|
||||||
|
w_offset, h_offset = 0 if img_w == w else (w - img_w) // 2, 0 if img_h == h else (h - img_h) // 2
|
||||||
|
grid.paste(img, box=(i % params.cols * w + w_offset, i // params.cols * h + h_offset))
|
||||||
|
|
||||||
return grid
|
return grid
|
||||||
|
|
||||||
@@ -321,13 +326,16 @@ def resize_image(resize_mode, im, width, height, upscaler_name=None):
|
|||||||
return res
|
return res
|
||||||
|
|
||||||
|
|
||||||
invalid_filename_chars = '#<>:"/\\|?*\n\r\t'
|
if not shared.cmd_opts.unix_filenames_sanitization:
|
||||||
|
invalid_filename_chars = '#<>:"/\\|?*\n\r\t'
|
||||||
|
else:
|
||||||
|
invalid_filename_chars = '/'
|
||||||
invalid_filename_prefix = ' '
|
invalid_filename_prefix = ' '
|
||||||
invalid_filename_postfix = ' .'
|
invalid_filename_postfix = ' .'
|
||||||
re_nonletters = re.compile(r'[\s' + string.punctuation + ']+')
|
re_nonletters = re.compile(r'[\s' + string.punctuation + ']+')
|
||||||
re_pattern = re.compile(r"(.*?)(?:\[([^\[\]]+)\]|$)")
|
re_pattern = re.compile(r"(.*?)(?:\[([^\[\]]+)\]|$)")
|
||||||
re_pattern_arg = re.compile(r"(.*)<([^>]*)>$")
|
re_pattern_arg = re.compile(r"(.*)<([^>]*)>$")
|
||||||
max_filename_part_length = 128
|
max_filename_part_length = shared.cmd_opts.filenames_max_length
|
||||||
NOTHING_AND_SKIP_PREVIOUS_TEXT = object()
|
NOTHING_AND_SKIP_PREVIOUS_TEXT = object()
|
||||||
|
|
||||||
|
|
||||||
@@ -344,8 +352,35 @@ def sanitize_filename_part(text, replace_spaces=True):
|
|||||||
return text
|
return text
|
||||||
|
|
||||||
|
|
||||||
|
@functools.cache
|
||||||
|
def get_scheduler_str(sampler_name, scheduler_name):
|
||||||
|
"""Returns {Scheduler} if the scheduler is applicable to the sampler"""
|
||||||
|
if scheduler_name == 'Automatic':
|
||||||
|
config = sd_samplers.find_sampler_config(sampler_name)
|
||||||
|
scheduler_name = config.options.get('scheduler', 'Automatic')
|
||||||
|
return scheduler_name.capitalize()
|
||||||
|
|
||||||
|
|
||||||
|
@functools.cache
|
||||||
|
def get_sampler_scheduler_str(sampler_name, scheduler_name):
|
||||||
|
"""Returns the '{Sampler} {Scheduler}' if the scheduler is applicable to the sampler"""
|
||||||
|
return f'{sampler_name} {get_scheduler_str(sampler_name, scheduler_name)}'
|
||||||
|
|
||||||
|
|
||||||
|
def get_sampler_scheduler(p, sampler):
|
||||||
|
"""Returns '{Sampler} {Scheduler}' / '{Scheduler}' / 'NOTHING_AND_SKIP_PREVIOUS_TEXT'"""
|
||||||
|
if hasattr(p, 'scheduler') and hasattr(p, 'sampler_name'):
|
||||||
|
if sampler:
|
||||||
|
sampler_scheduler = get_sampler_scheduler_str(p.sampler_name, p.scheduler)
|
||||||
|
else:
|
||||||
|
sampler_scheduler = get_scheduler_str(p.sampler_name, p.scheduler)
|
||||||
|
return sanitize_filename_part(sampler_scheduler, replace_spaces=False)
|
||||||
|
return NOTHING_AND_SKIP_PREVIOUS_TEXT
|
||||||
|
|
||||||
|
|
||||||
class FilenameGenerator:
|
class FilenameGenerator:
|
||||||
replacements = {
|
replacements = {
|
||||||
|
'basename': lambda self: self.basename or 'img',
|
||||||
'seed': lambda self: self.seed if self.seed is not None else '',
|
'seed': lambda self: self.seed if self.seed is not None else '',
|
||||||
'seed_first': lambda self: self.seed if self.p.batch_size == 1 else self.p.all_seeds[0],
|
'seed_first': lambda self: self.seed if self.p.batch_size == 1 else self.p.all_seeds[0],
|
||||||
'seed_last': lambda self: NOTHING_AND_SKIP_PREVIOUS_TEXT if self.p.batch_size == 1 else self.p.all_seeds[-1],
|
'seed_last': lambda self: NOTHING_AND_SKIP_PREVIOUS_TEXT if self.p.batch_size == 1 else self.p.all_seeds[-1],
|
||||||
@@ -355,6 +390,8 @@ class FilenameGenerator:
|
|||||||
'height': lambda self: self.image.height,
|
'height': lambda self: self.image.height,
|
||||||
'styles': lambda self: self.p and sanitize_filename_part(", ".join([style for style in self.p.styles if not style == "None"]) or "None", replace_spaces=False),
|
'styles': lambda self: self.p and sanitize_filename_part(", ".join([style for style in self.p.styles if not style == "None"]) or "None", replace_spaces=False),
|
||||||
'sampler': lambda self: self.p and sanitize_filename_part(self.p.sampler_name, replace_spaces=False),
|
'sampler': lambda self: self.p and sanitize_filename_part(self.p.sampler_name, replace_spaces=False),
|
||||||
|
'sampler_scheduler': lambda self: self.p and get_sampler_scheduler(self.p, True),
|
||||||
|
'scheduler': lambda self: self.p and get_sampler_scheduler(self.p, False),
|
||||||
'model_hash': lambda self: getattr(self.p, "sd_model_hash", shared.sd_model.sd_model_hash),
|
'model_hash': lambda self: getattr(self.p, "sd_model_hash", shared.sd_model.sd_model_hash),
|
||||||
'model_name': lambda self: sanitize_filename_part(shared.sd_model.sd_checkpoint_info.name_for_extra, replace_spaces=False),
|
'model_name': lambda self: sanitize_filename_part(shared.sd_model.sd_checkpoint_info.name_for_extra, replace_spaces=False),
|
||||||
'date': lambda self: datetime.datetime.now().strftime('%Y-%m-%d'),
|
'date': lambda self: datetime.datetime.now().strftime('%Y-%m-%d'),
|
||||||
@@ -372,6 +409,7 @@ class FilenameGenerator:
|
|||||||
'generation_number': lambda self: NOTHING_AND_SKIP_PREVIOUS_TEXT if (self.p.n_iter == 1 and self.p.batch_size == 1) or self.zip else self.p.iteration * self.p.batch_size + self.p.batch_index + 1,
|
'generation_number': lambda self: NOTHING_AND_SKIP_PREVIOUS_TEXT if (self.p.n_iter == 1 and self.p.batch_size == 1) or self.zip else self.p.iteration * self.p.batch_size + self.p.batch_index + 1,
|
||||||
'hasprompt': lambda self, *args: self.hasprompt(*args), # accepts formats:[hasprompt<prompt1|default><prompt2>..]
|
'hasprompt': lambda self, *args: self.hasprompt(*args), # accepts formats:[hasprompt<prompt1|default><prompt2>..]
|
||||||
'clip_skip': lambda self: opts.data["CLIP_stop_at_last_layers"],
|
'clip_skip': lambda self: opts.data["CLIP_stop_at_last_layers"],
|
||||||
|
'randn_source': lambda self: opts.data["randn_source"],
|
||||||
'denoising': lambda self: self.p.denoising_strength if self.p and self.p.denoising_strength else NOTHING_AND_SKIP_PREVIOUS_TEXT,
|
'denoising': lambda self: self.p.denoising_strength if self.p and self.p.denoising_strength else NOTHING_AND_SKIP_PREVIOUS_TEXT,
|
||||||
'user': lambda self: self.p.user,
|
'user': lambda self: self.p.user,
|
||||||
'vae_filename': lambda self: self.get_vae_filename(),
|
'vae_filename': lambda self: self.get_vae_filename(),
|
||||||
@@ -380,12 +418,13 @@ class FilenameGenerator:
|
|||||||
}
|
}
|
||||||
default_time_format = '%Y%m%d%H%M%S'
|
default_time_format = '%Y%m%d%H%M%S'
|
||||||
|
|
||||||
def __init__(self, p, seed, prompt, image, zip=False):
|
def __init__(self, p, seed, prompt, image, zip=False, basename=""):
|
||||||
self.p = p
|
self.p = p
|
||||||
self.seed = seed
|
self.seed = seed
|
||||||
self.prompt = prompt
|
self.prompt = prompt
|
||||||
self.image = image
|
self.image = image
|
||||||
self.zip = zip
|
self.zip = zip
|
||||||
|
self.basename = basename
|
||||||
|
|
||||||
def get_vae_filename(self):
|
def get_vae_filename(self):
|
||||||
"""Get the name of the VAE file."""
|
"""Get the name of the VAE file."""
|
||||||
@@ -566,6 +605,17 @@ def save_image_with_geninfo(image, geninfo, filename, extension=None, existing_p
|
|||||||
})
|
})
|
||||||
|
|
||||||
piexif.insert(exif_bytes, filename)
|
piexif.insert(exif_bytes, filename)
|
||||||
|
elif extension.lower() == '.avif':
|
||||||
|
if opts.enable_pnginfo and geninfo is not None:
|
||||||
|
exif_bytes = piexif.dump({
|
||||||
|
"Exif": {
|
||||||
|
piexif.ExifIFD.UserComment: piexif.helper.UserComment.dump(geninfo or "", encoding="unicode")
|
||||||
|
},
|
||||||
|
})
|
||||||
|
else:
|
||||||
|
exif_bytes = None
|
||||||
|
|
||||||
|
image.save(filename,format=image_format, quality=opts.jpeg_quality, exif=exif_bytes)
|
||||||
elif extension.lower() == ".gif":
|
elif extension.lower() == ".gif":
|
||||||
image.save(filename, format=image_format, comment=geninfo)
|
image.save(filename, format=image_format, comment=geninfo)
|
||||||
else:
|
else:
|
||||||
@@ -605,12 +655,12 @@ def save_image(image, path, basename, seed=None, prompt=None, extension='png', i
|
|||||||
txt_fullfn (`str` or None):
|
txt_fullfn (`str` or None):
|
||||||
If a text file is saved for this image, this will be its full path. Otherwise None.
|
If a text file is saved for this image, this will be its full path. Otherwise None.
|
||||||
"""
|
"""
|
||||||
namegen = FilenameGenerator(p, seed, prompt, image)
|
namegen = FilenameGenerator(p, seed, prompt, image, basename=basename)
|
||||||
|
|
||||||
# WebP and JPG formats have maximum dimension limits of 16383 and 65535 respectively. switch to PNG which has a much higher limit
|
# WebP and JPG formats have maximum dimension limits of 16383 and 65535 respectively. switch to PNG which has a much higher limit
|
||||||
if (image.height > 65535 or image.width > 65535) and extension.lower() in ("jpg", "jpeg") or (image.height > 16383 or image.width > 16383) and extension.lower() == "webp":
|
if (image.height > 65535 or image.width > 65535) and extension.lower() in ("jpg", "jpeg") or (image.height > 16383 or image.width > 16383) and extension.lower() == "webp":
|
||||||
print('Image dimensions too large; saving as PNG')
|
print('Image dimensions too large; saving as PNG')
|
||||||
extension = ".png"
|
extension = "png"
|
||||||
|
|
||||||
if save_to_dirs is None:
|
if save_to_dirs is None:
|
||||||
save_to_dirs = (grid and opts.grid_save_to_dirs) or (not grid and opts.save_to_dirs and not no_prompt)
|
save_to_dirs = (grid and opts.grid_save_to_dirs) or (not grid and opts.save_to_dirs and not no_prompt)
|
||||||
@@ -744,10 +794,12 @@ def read_info_from_image(image: Image.Image) -> tuple[str | None, dict]:
|
|||||||
exif_comment = exif_comment.decode('utf8', errors="ignore")
|
exif_comment = exif_comment.decode('utf8', errors="ignore")
|
||||||
|
|
||||||
if exif_comment:
|
if exif_comment:
|
||||||
items['exif comment'] = exif_comment
|
|
||||||
geninfo = exif_comment
|
geninfo = exif_comment
|
||||||
elif "comment" in items: # for gif
|
elif "comment" in items: # for gif
|
||||||
|
if isinstance(items["comment"], bytes):
|
||||||
geninfo = items["comment"].decode('utf8', errors="ignore")
|
geninfo = items["comment"].decode('utf8', errors="ignore")
|
||||||
|
else:
|
||||||
|
geninfo = items["comment"]
|
||||||
|
|
||||||
for field in IGNORED_INFO_KEYS:
|
for field in IGNORED_INFO_KEYS:
|
||||||
items.pop(field, None)
|
items.pop(field, None)
|
||||||
@@ -770,7 +822,7 @@ def image_data(data):
|
|||||||
import gradio as gr
|
import gradio as gr
|
||||||
|
|
||||||
try:
|
try:
|
||||||
image = Image.open(io.BytesIO(data))
|
image = read(io.BytesIO(data))
|
||||||
textinfo, _ = read_info_from_image(image)
|
textinfo, _ = read_info_from_image(image)
|
||||||
return textinfo, None
|
return textinfo, None
|
||||||
except Exception:
|
except Exception:
|
||||||
@@ -797,3 +849,30 @@ def flatten(img, bgcolor):
|
|||||||
|
|
||||||
return img.convert('RGB')
|
return img.convert('RGB')
|
||||||
|
|
||||||
|
|
||||||
|
def read(fp, **kwargs):
|
||||||
|
image = Image.open(fp, **kwargs)
|
||||||
|
image = fix_image(image)
|
||||||
|
|
||||||
|
return image
|
||||||
|
|
||||||
|
|
||||||
|
def fix_image(image: Image.Image):
|
||||||
|
if image is None:
|
||||||
|
return None
|
||||||
|
|
||||||
|
try:
|
||||||
|
image = ImageOps.exif_transpose(image)
|
||||||
|
image = fix_png_transparency(image)
|
||||||
|
except Exception:
|
||||||
|
pass
|
||||||
|
|
||||||
|
return image
|
||||||
|
|
||||||
|
|
||||||
|
def fix_png_transparency(image: Image.Image):
|
||||||
|
if image.mode not in ("RGB", "P") or not isinstance(image.info.get("transparency"), bytes):
|
||||||
|
return image
|
||||||
|
|
||||||
|
image = image.convert("RGBA")
|
||||||
|
return image
|
||||||
|
|||||||
+24
-17
@@ -6,7 +6,7 @@ import numpy as np
|
|||||||
from PIL import Image, ImageOps, ImageFilter, ImageEnhance, UnidentifiedImageError
|
from PIL import Image, ImageOps, ImageFilter, ImageEnhance, UnidentifiedImageError
|
||||||
import gradio as gr
|
import gradio as gr
|
||||||
|
|
||||||
from modules import images as imgutil
|
from modules import images
|
||||||
from modules.infotext_utils import create_override_settings_dict, parse_generation_parameters
|
from modules.infotext_utils import create_override_settings_dict, parse_generation_parameters
|
||||||
from modules.processing import Processed, StableDiffusionProcessingImg2Img, process_images
|
from modules.processing import Processed, StableDiffusionProcessingImg2Img, process_images
|
||||||
from modules.shared import opts, state
|
from modules.shared import opts, state
|
||||||
@@ -17,11 +17,14 @@ from modules.ui import plaintext_to_html
|
|||||||
import modules.scripts
|
import modules.scripts
|
||||||
|
|
||||||
|
|
||||||
def process_batch(p, input_dir, output_dir, inpaint_mask_dir, args, to_scale=False, scale_by=1.0, use_png_info=False, png_info_props=None, png_info_dir=None):
|
def process_batch(p, input, output_dir, inpaint_mask_dir, args, to_scale=False, scale_by=1.0, use_png_info=False, png_info_props=None, png_info_dir=None):
|
||||||
output_dir = output_dir.strip()
|
output_dir = output_dir.strip()
|
||||||
processing.fix_seed(p)
|
processing.fix_seed(p)
|
||||||
|
|
||||||
images = list(shared.walk_files(input_dir, allowed_extensions=(".png", ".jpg", ".jpeg", ".webp", ".tif", ".tiff")))
|
if isinstance(input, str):
|
||||||
|
batch_images = list(shared.walk_files(input, allowed_extensions=(".png", ".jpg", ".jpeg", ".webp", ".tif", ".tiff")))
|
||||||
|
else:
|
||||||
|
batch_images = [os.path.abspath(x.name) for x in input]
|
||||||
|
|
||||||
is_inpaint_batch = False
|
is_inpaint_batch = False
|
||||||
if inpaint_mask_dir:
|
if inpaint_mask_dir:
|
||||||
@@ -31,9 +34,9 @@ def process_batch(p, input_dir, output_dir, inpaint_mask_dir, args, to_scale=Fal
|
|||||||
if is_inpaint_batch:
|
if is_inpaint_batch:
|
||||||
print(f"\nInpaint batch is enabled. {len(inpaint_masks)} masks found.")
|
print(f"\nInpaint batch is enabled. {len(inpaint_masks)} masks found.")
|
||||||
|
|
||||||
print(f"Will process {len(images)} images, creating {p.n_iter * p.batch_size} new images for each.")
|
print(f"Will process {len(batch_images)} images, creating {p.n_iter * p.batch_size} new images for each.")
|
||||||
|
|
||||||
state.job_count = len(images) * p.n_iter
|
state.job_count = len(batch_images) * p.n_iter
|
||||||
|
|
||||||
# extract "default" params to use in case getting png info fails
|
# extract "default" params to use in case getting png info fails
|
||||||
prompt = p.prompt
|
prompt = p.prompt
|
||||||
@@ -46,8 +49,8 @@ def process_batch(p, input_dir, output_dir, inpaint_mask_dir, args, to_scale=Fal
|
|||||||
sd_model_checkpoint_override = get_closet_checkpoint_match(override_settings.get("sd_model_checkpoint", None))
|
sd_model_checkpoint_override = get_closet_checkpoint_match(override_settings.get("sd_model_checkpoint", None))
|
||||||
batch_results = None
|
batch_results = None
|
||||||
discard_further_results = False
|
discard_further_results = False
|
||||||
for i, image in enumerate(images):
|
for i, image in enumerate(batch_images):
|
||||||
state.job = f"{i+1} out of {len(images)}"
|
state.job = f"{i+1} out of {len(batch_images)}"
|
||||||
if state.skipped:
|
if state.skipped:
|
||||||
state.skipped = False
|
state.skipped = False
|
||||||
|
|
||||||
@@ -55,7 +58,7 @@ def process_batch(p, input_dir, output_dir, inpaint_mask_dir, args, to_scale=Fal
|
|||||||
break
|
break
|
||||||
|
|
||||||
try:
|
try:
|
||||||
img = Image.open(image)
|
img = images.read(image)
|
||||||
except UnidentifiedImageError as e:
|
except UnidentifiedImageError as e:
|
||||||
print(e)
|
print(e)
|
||||||
continue
|
continue
|
||||||
@@ -86,7 +89,7 @@ def process_batch(p, input_dir, output_dir, inpaint_mask_dir, args, to_scale=Fal
|
|||||||
# otherwise user has many masks with the same name but different extensions
|
# otherwise user has many masks with the same name but different extensions
|
||||||
mask_image_path = masks_found[0]
|
mask_image_path = masks_found[0]
|
||||||
|
|
||||||
mask_image = Image.open(mask_image_path)
|
mask_image = images.read(mask_image_path)
|
||||||
p.image_mask = mask_image
|
p.image_mask = mask_image
|
||||||
|
|
||||||
if use_png_info:
|
if use_png_info:
|
||||||
@@ -94,8 +97,8 @@ def process_batch(p, input_dir, output_dir, inpaint_mask_dir, args, to_scale=Fal
|
|||||||
info_img = img
|
info_img = img
|
||||||
if png_info_dir:
|
if png_info_dir:
|
||||||
info_img_path = os.path.join(png_info_dir, os.path.basename(image))
|
info_img_path = os.path.join(png_info_dir, os.path.basename(image))
|
||||||
info_img = Image.open(info_img_path)
|
info_img = images.read(info_img_path)
|
||||||
geninfo, _ = imgutil.read_info_from_image(info_img)
|
geninfo, _ = images.read_info_from_image(info_img)
|
||||||
parsed_parameters = parse_generation_parameters(geninfo)
|
parsed_parameters = parse_generation_parameters(geninfo)
|
||||||
parsed_parameters = {k: v for k, v in parsed_parameters.items() if k in (png_info_props or {})}
|
parsed_parameters = {k: v for k, v in parsed_parameters.items() if k in (png_info_props or {})}
|
||||||
except Exception:
|
except Exception:
|
||||||
@@ -146,7 +149,7 @@ def process_batch(p, input_dir, output_dir, inpaint_mask_dir, args, to_scale=Fal
|
|||||||
return batch_results
|
return batch_results
|
||||||
|
|
||||||
|
|
||||||
def img2img(id_task: str, mode: int, prompt: str, negative_prompt: str, prompt_styles, init_img, sketch, init_img_with_mask, inpaint_color_sketch, inpaint_color_sketch_orig, init_img_inpaint, init_mask_inpaint, steps: int, sampler_name: str, mask_blur: int, mask_alpha: float, inpainting_fill: int, n_iter: int, batch_size: int, cfg_scale: float, image_cfg_scale: float, denoising_strength: float, selected_scale_tab: int, height: int, width: int, scale_by: float, resize_mode: int, inpaint_full_res: bool, inpaint_full_res_padding: int, inpainting_mask_invert: int, img2img_batch_input_dir: str, img2img_batch_output_dir: str, img2img_batch_inpaint_mask_dir: str, override_settings_texts, img2img_batch_use_png_info: bool, img2img_batch_png_info_props: list, img2img_batch_png_info_dir: str, request: gr.Request, *args):
|
def img2img(id_task: str, request: gr.Request, mode: int, prompt: str, negative_prompt: str, prompt_styles, init_img, sketch, init_img_with_mask, inpaint_color_sketch, inpaint_color_sketch_orig, init_img_inpaint, init_mask_inpaint, mask_blur: int, mask_alpha: float, inpainting_fill: int, n_iter: int, batch_size: int, cfg_scale: float, image_cfg_scale: float, denoising_strength: float, selected_scale_tab: int, height: int, width: int, scale_by: float, resize_mode: int, inpaint_full_res: bool, inpaint_full_res_padding: int, inpainting_mask_invert: int, img2img_batch_input_dir: str, img2img_batch_output_dir: str, img2img_batch_inpaint_mask_dir: str, override_settings_texts, img2img_batch_use_png_info: bool, img2img_batch_png_info_props: list, img2img_batch_png_info_dir: str, img2img_batch_source_type: str, img2img_batch_upload: list, *args):
|
||||||
override_settings = create_override_settings_dict(override_settings_texts)
|
override_settings = create_override_settings_dict(override_settings_texts)
|
||||||
|
|
||||||
is_batch = mode == 5
|
is_batch = mode == 5
|
||||||
@@ -175,9 +178,8 @@ def img2img(id_task: str, mode: int, prompt: str, negative_prompt: str, prompt_s
|
|||||||
image = None
|
image = None
|
||||||
mask = None
|
mask = None
|
||||||
|
|
||||||
# Use the EXIF orientation of photos taken by smartphones.
|
image = images.fix_image(image)
|
||||||
if image is not None:
|
mask = images.fix_image(mask)
|
||||||
image = ImageOps.exif_transpose(image)
|
|
||||||
|
|
||||||
if selected_scale_tab == 1 and not is_batch:
|
if selected_scale_tab == 1 and not is_batch:
|
||||||
assert image, "Can't scale by because no image is selected"
|
assert image, "Can't scale by because no image is selected"
|
||||||
@@ -194,10 +196,8 @@ def img2img(id_task: str, mode: int, prompt: str, negative_prompt: str, prompt_s
|
|||||||
prompt=prompt,
|
prompt=prompt,
|
||||||
negative_prompt=negative_prompt,
|
negative_prompt=negative_prompt,
|
||||||
styles=prompt_styles,
|
styles=prompt_styles,
|
||||||
sampler_name=sampler_name,
|
|
||||||
batch_size=batch_size,
|
batch_size=batch_size,
|
||||||
n_iter=n_iter,
|
n_iter=n_iter,
|
||||||
steps=steps,
|
|
||||||
cfg_scale=cfg_scale,
|
cfg_scale=cfg_scale,
|
||||||
width=width,
|
width=width,
|
||||||
height=height,
|
height=height,
|
||||||
@@ -224,6 +224,13 @@ def img2img(id_task: str, mode: int, prompt: str, negative_prompt: str, prompt_s
|
|||||||
|
|
||||||
with closing(p):
|
with closing(p):
|
||||||
if is_batch:
|
if is_batch:
|
||||||
|
if img2img_batch_source_type == "upload":
|
||||||
|
assert isinstance(img2img_batch_upload, list) and img2img_batch_upload
|
||||||
|
output_dir = ""
|
||||||
|
inpaint_mask_dir = ""
|
||||||
|
png_info_dir = img2img_batch_png_info_dir if not shared.cmd_opts.hide_ui_dir_config else ""
|
||||||
|
processed = process_batch(p, img2img_batch_upload, output_dir, inpaint_mask_dir, args, to_scale=selected_scale_tab == 1, scale_by=scale_by, use_png_info=img2img_batch_use_png_info, png_info_props=img2img_batch_png_info_props, png_info_dir=png_info_dir)
|
||||||
|
else: # "from dir"
|
||||||
assert not shared.cmd_opts.hide_ui_dir_config, "Launched with --hide-ui-dir-config, batch img2img disabled"
|
assert not shared.cmd_opts.hide_ui_dir_config, "Launched with --hide-ui-dir-config, batch img2img disabled"
|
||||||
processed = process_batch(p, img2img_batch_input_dir, img2img_batch_output_dir, img2img_batch_inpaint_mask_dir, args, to_scale=selected_scale_tab == 1, scale_by=scale_by, use_png_info=img2img_batch_use_png_info, png_info_props=img2img_batch_png_info_props, png_info_dir=img2img_batch_png_info_dir)
|
processed = process_batch(p, img2img_batch_input_dir, img2img_batch_output_dir, img2img_batch_inpaint_mask_dir, args, to_scale=selected_scale_tab == 1, scale_by=scale_by, use_png_info=img2img_batch_use_png_info, png_info_props=img2img_batch_png_info_props, png_info_dir=img2img_batch_png_info_dir)
|
||||||
|
|
||||||
|
|||||||
+44
-18
@@ -8,7 +8,7 @@ import sys
|
|||||||
|
|
||||||
import gradio as gr
|
import gradio as gr
|
||||||
from modules.paths import data_path
|
from modules.paths import data_path
|
||||||
from modules import shared, ui_tempdir, script_callbacks, processing, infotext_versions
|
from modules import shared, ui_tempdir, script_callbacks, processing, infotext_versions, images, prompt_parser, errors
|
||||||
from PIL import Image
|
from PIL import Image
|
||||||
|
|
||||||
sys.modules['modules.generation_parameters_copypaste'] = sys.modules[__name__] # alias for old name
|
sys.modules['modules.generation_parameters_copypaste'] = sys.modules[__name__] # alias for old name
|
||||||
@@ -83,7 +83,7 @@ def image_from_url_text(filedata):
|
|||||||
assert is_in_right_dir, 'trying to open image file outside of allowed directories'
|
assert is_in_right_dir, 'trying to open image file outside of allowed directories'
|
||||||
|
|
||||||
filename = filename.rsplit('?', 1)[0]
|
filename = filename.rsplit('?', 1)[0]
|
||||||
return Image.open(filename)
|
return images.read(filename)
|
||||||
|
|
||||||
if type(filedata) == list:
|
if type(filedata) == list:
|
||||||
if len(filedata) == 0:
|
if len(filedata) == 0:
|
||||||
@@ -95,7 +95,7 @@ def image_from_url_text(filedata):
|
|||||||
filedata = filedata[len("data:image/png;base64,"):]
|
filedata = filedata[len("data:image/png;base64,"):]
|
||||||
|
|
||||||
filedata = base64.decodebytes(filedata.encode('utf-8'))
|
filedata = base64.decodebytes(filedata.encode('utf-8'))
|
||||||
image = Image.open(io.BytesIO(filedata))
|
image = images.read(io.BytesIO(filedata))
|
||||||
return image
|
return image
|
||||||
|
|
||||||
|
|
||||||
@@ -146,18 +146,19 @@ def connect_paste_params_buttons():
|
|||||||
destination_height_component = next(iter([field for field, name in fields if name == "Size-2"] if fields else []), None)
|
destination_height_component = next(iter([field for field, name in fields if name == "Size-2"] if fields else []), None)
|
||||||
|
|
||||||
if binding.source_image_component and destination_image_component:
|
if binding.source_image_component and destination_image_component:
|
||||||
|
need_send_dementions = destination_width_component and binding.tabname != 'inpaint'
|
||||||
if isinstance(binding.source_image_component, gr.Gallery):
|
if isinstance(binding.source_image_component, gr.Gallery):
|
||||||
func = send_image_and_dimensions if destination_width_component else image_from_url_text
|
func = send_image_and_dimensions if need_send_dementions else image_from_url_text
|
||||||
jsfunc = "extract_image_from_gallery"
|
jsfunc = "extract_image_from_gallery"
|
||||||
else:
|
else:
|
||||||
func = send_image_and_dimensions if destination_width_component else lambda x: x
|
func = send_image_and_dimensions if need_send_dementions else lambda x: x
|
||||||
jsfunc = None
|
jsfunc = None
|
||||||
|
|
||||||
binding.paste_button.click(
|
binding.paste_button.click(
|
||||||
fn=func,
|
fn=func,
|
||||||
_js=jsfunc,
|
_js=jsfunc,
|
||||||
inputs=[binding.source_image_component],
|
inputs=[binding.source_image_component],
|
||||||
outputs=[destination_image_component, destination_width_component, destination_height_component] if destination_width_component else [destination_image_component],
|
outputs=[destination_image_component, destination_width_component, destination_height_component] if need_send_dementions else [destination_image_component],
|
||||||
show_progress=False,
|
show_progress=False,
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -265,17 +266,6 @@ Steps: 20, Sampler: Euler a, CFG scale: 7, Seed: 965400086, Size: 512x512, Model
|
|||||||
else:
|
else:
|
||||||
prompt += ("" if prompt == "" else "\n") + line
|
prompt += ("" if prompt == "" else "\n") + line
|
||||||
|
|
||||||
if shared.opts.infotext_styles != "Ignore":
|
|
||||||
found_styles, prompt, negative_prompt = shared.prompt_styles.extract_styles_from_prompt(prompt, negative_prompt)
|
|
||||||
|
|
||||||
if shared.opts.infotext_styles == "Apply":
|
|
||||||
res["Styles array"] = found_styles
|
|
||||||
elif shared.opts.infotext_styles == "Apply if any" and found_styles:
|
|
||||||
res["Styles array"] = found_styles
|
|
||||||
|
|
||||||
res["Prompt"] = prompt
|
|
||||||
res["Negative prompt"] = negative_prompt
|
|
||||||
|
|
||||||
for k, v in re_param.findall(lastline):
|
for k, v in re_param.findall(lastline):
|
||||||
try:
|
try:
|
||||||
if v[0] == '"' and v[-1] == '"':
|
if v[0] == '"' and v[-1] == '"':
|
||||||
@@ -290,6 +280,26 @@ Steps: 20, Sampler: Euler a, CFG scale: 7, Seed: 965400086, Size: 512x512, Model
|
|||||||
except Exception:
|
except Exception:
|
||||||
print(f"Error parsing \"{k}: {v}\"")
|
print(f"Error parsing \"{k}: {v}\"")
|
||||||
|
|
||||||
|
# Extract styles from prompt
|
||||||
|
if shared.opts.infotext_styles != "Ignore":
|
||||||
|
found_styles, prompt_no_styles, negative_prompt_no_styles = shared.prompt_styles.extract_styles_from_prompt(prompt, negative_prompt)
|
||||||
|
|
||||||
|
same_hr_styles = True
|
||||||
|
if ("Hires prompt" in res or "Hires negative prompt" in res) and (infotext_ver > infotext_versions.v180_hr_styles if (infotext_ver := infotext_versions.parse_version(res.get("Version"))) else True):
|
||||||
|
hr_prompt, hr_negative_prompt = res.get("Hires prompt", prompt), res.get("Hires negative prompt", negative_prompt)
|
||||||
|
hr_found_styles, hr_prompt_no_styles, hr_negative_prompt_no_styles = shared.prompt_styles.extract_styles_from_prompt(hr_prompt, hr_negative_prompt)
|
||||||
|
if same_hr_styles := found_styles == hr_found_styles:
|
||||||
|
res["Hires prompt"] = '' if hr_prompt_no_styles == prompt_no_styles else hr_prompt_no_styles
|
||||||
|
res['Hires negative prompt'] = '' if hr_negative_prompt_no_styles == negative_prompt_no_styles else hr_negative_prompt_no_styles
|
||||||
|
|
||||||
|
if same_hr_styles:
|
||||||
|
prompt, negative_prompt = prompt_no_styles, negative_prompt_no_styles
|
||||||
|
if (shared.opts.infotext_styles == "Apply if any" and found_styles) or shared.opts.infotext_styles == "Apply":
|
||||||
|
res['Styles array'] = found_styles
|
||||||
|
|
||||||
|
res["Prompt"] = prompt
|
||||||
|
res["Negative prompt"] = negative_prompt
|
||||||
|
|
||||||
# Missing CLIP skip means it was set to 1 (the default)
|
# Missing CLIP skip means it was set to 1 (the default)
|
||||||
if "Clip skip" not in res:
|
if "Clip skip" not in res:
|
||||||
res["Clip skip"] = "1"
|
res["Clip skip"] = "1"
|
||||||
@@ -305,6 +315,9 @@ Steps: 20, Sampler: Euler a, CFG scale: 7, Seed: 965400086, Size: 512x512, Model
|
|||||||
if "Hires sampler" not in res:
|
if "Hires sampler" not in res:
|
||||||
res["Hires sampler"] = "Use same sampler"
|
res["Hires sampler"] = "Use same sampler"
|
||||||
|
|
||||||
|
if "Hires schedule type" not in res:
|
||||||
|
res["Hires schedule type"] = "Use same scheduler"
|
||||||
|
|
||||||
if "Hires checkpoint" not in res:
|
if "Hires checkpoint" not in res:
|
||||||
res["Hires checkpoint"] = "Use same checkpoint"
|
res["Hires checkpoint"] = "Use same checkpoint"
|
||||||
|
|
||||||
@@ -356,6 +369,15 @@ Steps: 20, Sampler: Euler a, CFG scale: 7, Seed: 965400086, Size: 512x512, Model
|
|||||||
if "Cache FP16 weight for LoRA" not in res and res["FP8 weight"] != "Disable":
|
if "Cache FP16 weight for LoRA" not in res and res["FP8 weight"] != "Disable":
|
||||||
res["Cache FP16 weight for LoRA"] = False
|
res["Cache FP16 weight for LoRA"] = False
|
||||||
|
|
||||||
|
prompt_attention = prompt_parser.parse_prompt_attention(prompt)
|
||||||
|
prompt_attention += prompt_parser.parse_prompt_attention(negative_prompt)
|
||||||
|
prompt_uses_emphasis = len(prompt_attention) != len([p for p in prompt_attention if p[1] == 1.0 or p[0] == 'BREAK'])
|
||||||
|
if "Emphasis" not in res and prompt_uses_emphasis:
|
||||||
|
res["Emphasis"] = "Original"
|
||||||
|
|
||||||
|
if "Refiner switch by sampling steps" not in res:
|
||||||
|
res["Refiner switch by sampling steps"] = False
|
||||||
|
|
||||||
infotext_versions.backcompat(res)
|
infotext_versions.backcompat(res)
|
||||||
|
|
||||||
for key in skip_fields:
|
for key in skip_fields:
|
||||||
@@ -453,7 +475,7 @@ def get_override_settings(params, *, skip_fields=None):
|
|||||||
|
|
||||||
def connect_paste(button, paste_fields, input_comp, override_settings_component, tabname):
|
def connect_paste(button, paste_fields, input_comp, override_settings_component, tabname):
|
||||||
def paste_func(prompt):
|
def paste_func(prompt):
|
||||||
if not prompt and not shared.cmd_opts.hide_ui_dir_config:
|
if not prompt and not shared.cmd_opts.hide_ui_dir_config and not shared.cmd_opts.no_prompt_history:
|
||||||
filename = os.path.join(data_path, "params.txt")
|
filename = os.path.join(data_path, "params.txt")
|
||||||
try:
|
try:
|
||||||
with open(filename, "r", encoding="utf8") as file:
|
with open(filename, "r", encoding="utf8") as file:
|
||||||
@@ -467,7 +489,11 @@ def connect_paste(button, paste_fields, input_comp, override_settings_component,
|
|||||||
|
|
||||||
for output, key in paste_fields:
|
for output, key in paste_fields:
|
||||||
if callable(key):
|
if callable(key):
|
||||||
|
try:
|
||||||
v = key(params)
|
v = key(params)
|
||||||
|
except Exception:
|
||||||
|
errors.report(f"Error executing {key}", exc_info=True)
|
||||||
|
v = None
|
||||||
else:
|
else:
|
||||||
v = params.get(key, None)
|
v = params.get(key, None)
|
||||||
|
|
||||||
|
|||||||
@@ -5,6 +5,8 @@ import re
|
|||||||
|
|
||||||
v160 = version.parse("1.6.0")
|
v160 = version.parse("1.6.0")
|
||||||
v170_tsnr = version.parse("v1.7.0-225")
|
v170_tsnr = version.parse("v1.7.0-225")
|
||||||
|
v180 = version.parse("1.8.0")
|
||||||
|
v180_hr_styles = version.parse("1.8.0-139")
|
||||||
|
|
||||||
|
|
||||||
def parse_version(text):
|
def parse_version(text):
|
||||||
@@ -31,9 +33,14 @@ def backcompat(d):
|
|||||||
if ver is None:
|
if ver is None:
|
||||||
return
|
return
|
||||||
|
|
||||||
if ver < v160:
|
if ver < v160 and '[' in d.get('Prompt', ''):
|
||||||
d["Old prompt editing timelines"] = True
|
d["Old prompt editing timelines"] = True
|
||||||
|
|
||||||
|
if ver < v160 and d.get('Sampler', '') in ('DDIM', 'PLMS'):
|
||||||
|
d["Pad conds v0"] = True
|
||||||
|
|
||||||
if ver < v170_tsnr:
|
if ver < v170_tsnr:
|
||||||
d["Downcast alphas_cumprod"] = True
|
d["Downcast alphas_cumprod"] = True
|
||||||
|
|
||||||
|
if ver < v180 and d.get('Refiner'):
|
||||||
|
d["Refiner switch by sampling steps"] = True
|
||||||
|
|||||||
@@ -51,6 +51,7 @@ def check_versions():
|
|||||||
def initialize():
|
def initialize():
|
||||||
from modules import initialize_util
|
from modules import initialize_util
|
||||||
initialize_util.fix_torch_version()
|
initialize_util.fix_torch_version()
|
||||||
|
initialize_util.fix_pytorch_lightning()
|
||||||
initialize_util.fix_asyncio_event_loop_policy()
|
initialize_util.fix_asyncio_event_loop_policy()
|
||||||
initialize_util.validate_tls_options()
|
initialize_util.validate_tls_options()
|
||||||
initialize_util.configure_sigint_handler()
|
initialize_util.configure_sigint_handler()
|
||||||
@@ -109,7 +110,7 @@ def initialize_rest(*, reload_script_modules=False):
|
|||||||
with startup_timer.subcategory("load scripts"):
|
with startup_timer.subcategory("load scripts"):
|
||||||
scripts.load_scripts()
|
scripts.load_scripts()
|
||||||
|
|
||||||
if reload_script_modules:
|
if reload_script_modules and shared.opts.enable_reloading_ui_scripts:
|
||||||
for module in [module for name, module in sys.modules.items() if name.startswith("modules.ui")]:
|
for module in [module for name, module in sys.modules.items() if name.startswith("modules.ui")]:
|
||||||
importlib.reload(module)
|
importlib.reload(module)
|
||||||
startup_timer.record("reload script modules")
|
startup_timer.record("reload script modules")
|
||||||
@@ -139,16 +140,17 @@ def initialize_rest(*, reload_script_modules=False):
|
|||||||
"""
|
"""
|
||||||
Accesses shared.sd_model property to load model.
|
Accesses shared.sd_model property to load model.
|
||||||
After it's available, if it has been loaded before this access by some extension,
|
After it's available, if it has been loaded before this access by some extension,
|
||||||
its optimization may be None because the list of optimizaers has neet been filled
|
its optimization may be None because the list of optimizers has not been filled
|
||||||
by that time, so we apply optimization again.
|
by that time, so we apply optimization again.
|
||||||
"""
|
"""
|
||||||
|
from modules import devices
|
||||||
|
devices.torch_npu_set_device()
|
||||||
|
|
||||||
shared.sd_model # noqa: B018
|
shared.sd_model # noqa: B018
|
||||||
|
|
||||||
if sd_hijack.current_optimizer is None:
|
if sd_hijack.current_optimizer is None:
|
||||||
sd_hijack.apply_optimizations()
|
sd_hijack.apply_optimizations()
|
||||||
|
|
||||||
from modules import devices
|
|
||||||
devices.first_time_calculation()
|
devices.first_time_calculation()
|
||||||
if not shared.cmd_opts.skip_load_model_at_start:
|
if not shared.cmd_opts.skip_load_model_at_start:
|
||||||
Thread(target=load_model).start()
|
Thread(target=load_model).start()
|
||||||
|
|||||||
@@ -24,6 +24,13 @@ def fix_torch_version():
|
|||||||
torch.__long_version__ = torch.__version__
|
torch.__long_version__ = torch.__version__
|
||||||
torch.__version__ = re.search(r'[\d.]+[\d]', torch.__version__).group(0)
|
torch.__version__ = re.search(r'[\d.]+[\d]', torch.__version__).group(0)
|
||||||
|
|
||||||
|
def fix_pytorch_lightning():
|
||||||
|
# Checks if pytorch_lightning.utilities.distributed already exists in the sys.modules cache
|
||||||
|
if 'pytorch_lightning.utilities.distributed' not in sys.modules:
|
||||||
|
import pytorch_lightning
|
||||||
|
# Lets the user know that the library was not found and then will set it to pytorch_lightning.utilities.rank_zero
|
||||||
|
print("Pytorch_lightning.distributed not found, attempting pytorch_lightning.rank_zero")
|
||||||
|
sys.modules["pytorch_lightning.utilities.distributed"] = pytorch_lightning.utilities.rank_zero
|
||||||
|
|
||||||
def fix_asyncio_event_loop_policy():
|
def fix_asyncio_event_loop_policy():
|
||||||
"""
|
"""
|
||||||
|
|||||||
@@ -17,7 +17,7 @@ clip_model_name = 'ViT-L/14'
|
|||||||
|
|
||||||
Category = namedtuple("Category", ["name", "topn", "items"])
|
Category = namedtuple("Category", ["name", "topn", "items"])
|
||||||
|
|
||||||
re_topn = re.compile(r"\.top(\d+)\.")
|
re_topn = re.compile(r"\.top(\d+)$")
|
||||||
|
|
||||||
def category_types():
|
def category_types():
|
||||||
return [f.stem for f in Path(shared.interrogator.content_dir).glob('*.txt')]
|
return [f.stem for f in Path(shared.interrogator.content_dir).glob('*.txt')]
|
||||||
|
|||||||
+70
-15
@@ -9,6 +9,7 @@ import importlib.util
|
|||||||
import importlib.metadata
|
import importlib.metadata
|
||||||
import platform
|
import platform
|
||||||
import json
|
import json
|
||||||
|
import shlex
|
||||||
from functools import lru_cache
|
from functools import lru_cache
|
||||||
|
|
||||||
from modules import cmd_args, errors
|
from modules import cmd_args, errors
|
||||||
@@ -42,9 +43,7 @@ def check_python_version():
|
|||||||
supported_minors = [7, 8, 9, 10, 11]
|
supported_minors = [7, 8, 9, 10, 11]
|
||||||
|
|
||||||
if not (major == 3 and minor in supported_minors):
|
if not (major == 3 and minor in supported_minors):
|
||||||
import modules.errors
|
errors.print_error_explanation(f"""
|
||||||
|
|
||||||
modules.errors.print_error_explanation(f"""
|
|
||||||
INCOMPATIBLE PYTHON VERSION
|
INCOMPATIBLE PYTHON VERSION
|
||||||
|
|
||||||
This program is tested with 3.10.6 Python, but you have {major}.{minor}.{micro}.
|
This program is tested with 3.10.6 Python, but you have {major}.{minor}.{micro}.
|
||||||
@@ -55,7 +54,7 @@ and delete current Python and "venv" folder in WebUI's directory.
|
|||||||
|
|
||||||
You can download 3.10 Python from here: https://www.python.org/downloads/release/python-3106/
|
You can download 3.10 Python from here: https://www.python.org/downloads/release/python-3106/
|
||||||
|
|
||||||
{"Alternatively, use a binary release of WebUI: https://github.com/AUTOMATIC1111/stable-diffusion-webui/releases" if is_windows else ""}
|
{"Alternatively, use a binary release of WebUI: https://github.com/AUTOMATIC1111/stable-diffusion-webui/releases/tag/v1.0.0-pre" if is_windows else ""}
|
||||||
|
|
||||||
Use --skip-python-version-check to suppress this warning.
|
Use --skip-python-version-check to suppress this warning.
|
||||||
""")
|
""")
|
||||||
@@ -76,7 +75,7 @@ def git_tag():
|
|||||||
except Exception:
|
except Exception:
|
||||||
try:
|
try:
|
||||||
|
|
||||||
changelog_md = os.path.join(os.path.dirname(os.path.dirname(__file__)), "CHANGELOG.md")
|
changelog_md = os.path.join(script_path, "CHANGELOG.md")
|
||||||
with open(changelog_md, "r", encoding="utf-8") as file:
|
with open(changelog_md, "r", encoding="utf-8") as file:
|
||||||
line = next((line.strip() for line in file if line.strip()), "<none>")
|
line = next((line.strip() for line in file if line.strip()), "<none>")
|
||||||
line = line.replace("## ", "")
|
line = line.replace("## ", "")
|
||||||
@@ -188,7 +187,7 @@ def git_clone(url, dir, name, commithash=None):
|
|||||||
return
|
return
|
||||||
|
|
||||||
try:
|
try:
|
||||||
run(f'"{git}" clone "{url}" "{dir}"', f"Cloning {name} into {dir}...", f"Couldn't clone {name}", live=True)
|
run(f'"{git}" clone --config core.filemode=false "{url}" "{dir}"', f"Cloning {name} into {dir}...", f"Couldn't clone {name}", live=True)
|
||||||
except RuntimeError:
|
except RuntimeError:
|
||||||
shutil.rmtree(dir, ignore_errors=True)
|
shutil.rmtree(dir, ignore_errors=True)
|
||||||
raise
|
raise
|
||||||
@@ -231,7 +230,7 @@ def run_extension_installer(extension_dir):
|
|||||||
|
|
||||||
try:
|
try:
|
||||||
env = os.environ.copy()
|
env = os.environ.copy()
|
||||||
env['PYTHONPATH'] = f"{os.path.abspath('.')}{os.pathsep}{env.get('PYTHONPATH', '')}"
|
env['PYTHONPATH'] = f"{script_path}{os.pathsep}{env.get('PYTHONPATH', '')}"
|
||||||
|
|
||||||
stdout = run(f'"{python}" "{path_installer}"', errdesc=f"Error running install.py for extension {extension_dir}", custom_env=env).strip()
|
stdout = run(f'"{python}" "{path_installer}"', errdesc=f"Error running install.py for extension {extension_dir}", custom_env=env).strip()
|
||||||
if stdout:
|
if stdout:
|
||||||
@@ -251,7 +250,6 @@ def list_extensions(settings_file):
|
|||||||
except Exception:
|
except Exception:
|
||||||
errors.report(f'\nCould not load settings\nThe config file "{settings_file}" is likely corrupted\nIt has been moved to the "tmp/config.json"\nReverting config to default\n\n''', exc_info=True)
|
errors.report(f'\nCould not load settings\nThe config file "{settings_file}" is likely corrupted\nIt has been moved to the "tmp/config.json"\nReverting config to default\n\n''', exc_info=True)
|
||||||
os.replace(settings_file, os.path.join(script_path, "tmp", "config.json"))
|
os.replace(settings_file, os.path.join(script_path, "tmp", "config.json"))
|
||||||
settings = {}
|
|
||||||
|
|
||||||
disabled_extensions = set(settings.get('disabled_extensions', []))
|
disabled_extensions = set(settings.get('disabled_extensions', []))
|
||||||
disable_all_extensions = settings.get('disable_all_extensions', 'none')
|
disable_all_extensions = settings.get('disable_all_extensions', 'none')
|
||||||
@@ -315,9 +313,43 @@ def requirements_met(requirements_file):
|
|||||||
return True
|
return True
|
||||||
|
|
||||||
|
|
||||||
|
def get_cuda_comp_cap():
|
||||||
|
"""
|
||||||
|
Returns float of CUDA Compute Capability using nvidia-smi
|
||||||
|
Returns 0.0 on error
|
||||||
|
CUDA Compute Capability
|
||||||
|
ref https://developer.nvidia.com/cuda-gpus
|
||||||
|
ref https://en.wikipedia.org/wiki/CUDA
|
||||||
|
Blackwell consumer GPUs should return 12.0 data-center GPUs should return 10.0
|
||||||
|
"""
|
||||||
|
try:
|
||||||
|
return max(map(float, subprocess.check_output(['nvidia-smi', '--query-gpu=compute_cap', '--format=noheader,csv'], text=True).splitlines()))
|
||||||
|
except Exception as _:
|
||||||
|
return 0.0
|
||||||
|
|
||||||
|
|
||||||
|
def early_access_blackwell_wheels():
|
||||||
|
"""For Blackwell GPUs, use Early Access PyTorch Wheels provided by Nvidia"""
|
||||||
|
print('deprecated early_access_blackwell_wheels')
|
||||||
|
if all([
|
||||||
|
os.environ.get('TORCH_INDEX_URL') is None,
|
||||||
|
sys.version_info.major == 3,
|
||||||
|
sys.version_info.minor in (10, 11, 12),
|
||||||
|
platform.system() == "Windows",
|
||||||
|
get_cuda_comp_cap() >= 10, # Blackwell
|
||||||
|
]):
|
||||||
|
base_repo = 'https://huggingface.co/w-e-w/torch-2.6.0-cu128.nv/resolve/main/'
|
||||||
|
ea_whl = {
|
||||||
|
10: f'{base_repo}torch-2.6.0+cu128.nv-cp310-cp310-win_amd64.whl#sha256=fef3de7ce8f4642e405576008f384304ad0e44f7b06cc1aa45e0ab4b6e70490d {base_repo}torchvision-0.20.0a0+cu128.nv-cp310-cp310-win_amd64.whl#sha256=50841254f59f1db750e7348b90a8f4cd6befec217ab53cbb03780490b225abef',
|
||||||
|
11: f'{base_repo}torch-2.6.0+cu128.nv-cp311-cp311-win_amd64.whl#sha256=6665c36e6a7e79e7a2cb42bec190d376be9ca2859732ed29dd5b7b5a612d0d26 {base_repo}torchvision-0.20.0a0+cu128.nv-cp311-cp311-win_amd64.whl#sha256=bbc0ee4938e35fe5a30de3613bfcd2d8ef4eae334cf8d49db860668f0bb47083',
|
||||||
|
12: f'{base_repo}torch-2.6.0+cu128.nv-cp312-cp312-win_amd64.whl#sha256=a3197f72379d34b08c4a4bcf49ea262544a484e8702b8c46cbcd66356c89def6 {base_repo}torchvision-0.20.0a0+cu128.nv-cp312-cp312-win_amd64.whl#sha256=235e7be71ac4e75b0f8e817bae4796d7bac8a67146d2037ab96394f2bdc63e6c'
|
||||||
|
}
|
||||||
|
return f'pip install {ea_whl.get(sys.version_info.minor)}'
|
||||||
|
|
||||||
|
|
||||||
def prepare_environment():
|
def prepare_environment():
|
||||||
torch_index_url = os.environ.get('TORCH_INDEX_URL', "https://download.pytorch.org/whl/cu121")
|
torch_index_url = os.environ.get('TORCH_INDEX_URL', "https://download.pytorch.org/whl/cu128")
|
||||||
torch_command = os.environ.get('TORCH_COMMAND', f"pip install torch==2.1.2 torchvision==0.16.2 --extra-index-url {torch_index_url}")
|
torch_command = os.environ.get('TORCH_COMMAND', f"pip install torch==2.7.0 torchvision==0.22.0 --extra-index-url {torch_index_url}")
|
||||||
if args.use_ipex:
|
if args.use_ipex:
|
||||||
if platform.system() == "Windows":
|
if platform.system() == "Windows":
|
||||||
# The "Nuullll/intel-extension-for-pytorch" wheels were built from IPEX source for Intel Arc GPU: https://github.com/intel/intel-extension-for-pytorch/tree/xpu-main
|
# The "Nuullll/intel-extension-for-pytorch" wheels were built from IPEX source for Intel Arc GPU: https://github.com/intel/intel-extension-for-pytorch/tree/xpu-main
|
||||||
@@ -339,13 +371,14 @@ def prepare_environment():
|
|||||||
torch_index_url = os.environ.get('TORCH_INDEX_URL', "https://pytorch-extension.intel.com/release-whl/stable/xpu/us/")
|
torch_index_url = os.environ.get('TORCH_INDEX_URL', "https://pytorch-extension.intel.com/release-whl/stable/xpu/us/")
|
||||||
torch_command = os.environ.get('TORCH_COMMAND', f"pip install torch==2.0.0a0 intel-extension-for-pytorch==2.0.110+gitba7f6c1 --extra-index-url {torch_index_url}")
|
torch_command = os.environ.get('TORCH_COMMAND', f"pip install torch==2.0.0a0 intel-extension-for-pytorch==2.0.110+gitba7f6c1 --extra-index-url {torch_index_url}")
|
||||||
requirements_file = os.environ.get('REQS_FILE', "requirements_versions.txt")
|
requirements_file = os.environ.get('REQS_FILE', "requirements_versions.txt")
|
||||||
|
requirements_file_for_npu = os.environ.get('REQS_FILE_FOR_NPU', "requirements_npu.txt")
|
||||||
|
|
||||||
xformers_package = os.environ.get('XFORMERS_PACKAGE', 'xformers==0.0.23.post1')
|
xformers_package = os.environ.get('XFORMERS_PACKAGE', 'xformers==0.0.30')
|
||||||
clip_package = os.environ.get('CLIP_PACKAGE', "https://github.com/openai/CLIP/archive/d50d76daa670286dd6cacf3bcd80b5e4823fc8e1.zip")
|
clip_package = os.environ.get('CLIP_PACKAGE', "https://github.com/openai/CLIP/archive/d50d76daa670286dd6cacf3bcd80b5e4823fc8e1.zip")
|
||||||
openclip_package = os.environ.get('OPENCLIP_PACKAGE', "https://github.com/mlfoundations/open_clip/archive/bb6e834e9c70d9c27d0dc3ecedeebeaeb1ffad6b.zip")
|
openclip_package = os.environ.get('OPENCLIP_PACKAGE', "https://github.com/mlfoundations/open_clip/archive/bb6e834e9c70d9c27d0dc3ecedeebeaeb1ffad6b.zip")
|
||||||
|
|
||||||
assets_repo = os.environ.get('ASSETS_REPO', "https://github.com/AUTOMATIC1111/stable-diffusion-webui-assets.git")
|
assets_repo = os.environ.get('ASSETS_REPO', "https://github.com/AUTOMATIC1111/stable-diffusion-webui-assets.git")
|
||||||
stable_diffusion_repo = os.environ.get('STABLE_DIFFUSION_REPO', "https://github.com/Stability-AI/stablediffusion.git")
|
stable_diffusion_repo = os.environ.get('STABLE_DIFFUSION_REPO', "https://github.com/w-e-w/stablediffusion.git")
|
||||||
stable_diffusion_xl_repo = os.environ.get('STABLE_DIFFUSION_XL_REPO', "https://github.com/Stability-AI/generative-models.git")
|
stable_diffusion_xl_repo = os.environ.get('STABLE_DIFFUSION_XL_REPO', "https://github.com/Stability-AI/generative-models.git")
|
||||||
k_diffusion_repo = os.environ.get('K_DIFFUSION_REPO', 'https://github.com/crowsonkb/k-diffusion.git')
|
k_diffusion_repo = os.environ.get('K_DIFFUSION_REPO', 'https://github.com/crowsonkb/k-diffusion.git')
|
||||||
blip_repo = os.environ.get('BLIP_REPO', 'https://github.com/salesforce/BLIP.git')
|
blip_repo = os.environ.get('BLIP_REPO', 'https://github.com/salesforce/BLIP.git')
|
||||||
@@ -389,8 +422,24 @@ def prepare_environment():
|
|||||||
)
|
)
|
||||||
startup_timer.record("torch GPU test")
|
startup_timer.record("torch GPU test")
|
||||||
|
|
||||||
|
# Ensure build dependencies are installed before any package that might need them
|
||||||
|
def ensure_build_dependencies():
|
||||||
|
"""Ensure essential build tools are available"""
|
||||||
|
if not is_installed("wheel"):
|
||||||
|
run_pip("install wheel", "wheel")
|
||||||
|
# Check setuptools version compatibility
|
||||||
|
try:
|
||||||
|
setuptools_version = run(f'"{python}" -c "import setuptools; print(setuptools.__version__)"', None, None).strip()
|
||||||
|
if setuptools_version >= "70":
|
||||||
|
run_pip("install setuptools==69.5.1", "setuptools")
|
||||||
|
except Exception:
|
||||||
|
# If setuptools check fails, install compatible version
|
||||||
|
run_pip("install setuptools==69.5.1", "setuptools")
|
||||||
|
# Install build dependencies early
|
||||||
|
ensure_build_dependencies()
|
||||||
|
|
||||||
if not is_installed("clip"):
|
if not is_installed("clip"):
|
||||||
run_pip(f"install {clip_package}", "clip")
|
run_pip(f"install --no-build-isolation {clip_package}", "clip")
|
||||||
startup_timer.record("install clip")
|
startup_timer.record("install clip")
|
||||||
|
|
||||||
if not is_installed("open_clip"):
|
if not is_installed("open_clip"):
|
||||||
@@ -422,6 +471,13 @@ def prepare_environment():
|
|||||||
run_pip(f"install -r \"{requirements_file}\"", "requirements")
|
run_pip(f"install -r \"{requirements_file}\"", "requirements")
|
||||||
startup_timer.record("install requirements")
|
startup_timer.record("install requirements")
|
||||||
|
|
||||||
|
if not os.path.isfile(requirements_file_for_npu):
|
||||||
|
requirements_file_for_npu = os.path.join(script_path, requirements_file_for_npu)
|
||||||
|
|
||||||
|
if "torch_npu" in torch_command and not requirements_met(requirements_file_for_npu):
|
||||||
|
run_pip(f"install -r \"{requirements_file_for_npu}\"", "requirements_for_npu")
|
||||||
|
startup_timer.record("install requirements_for_npu")
|
||||||
|
|
||||||
if not args.skip_install:
|
if not args.skip_install:
|
||||||
run_extensions_installers(settings_file=args.ui_settings_file)
|
run_extensions_installers(settings_file=args.ui_settings_file)
|
||||||
|
|
||||||
@@ -438,7 +494,6 @@ def prepare_environment():
|
|||||||
exit(0)
|
exit(0)
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
def configure_for_tests():
|
def configure_for_tests():
|
||||||
if "--api" not in sys.argv:
|
if "--api" not in sys.argv:
|
||||||
sys.argv.append("--api")
|
sys.argv.append("--api")
|
||||||
@@ -454,7 +509,7 @@ def configure_for_tests():
|
|||||||
|
|
||||||
|
|
||||||
def start():
|
def start():
|
||||||
print(f"Launching {'API server' if '--nowebui' in sys.argv else 'Web UI'} with arguments: {' '.join(sys.argv[1:])}")
|
print(f"Launching {'API server' if '--nowebui' in sys.argv else 'Web UI'} with arguments: {shlex.join(sys.argv[1:])}")
|
||||||
import webui
|
import webui
|
||||||
if '--nowebui' in sys.argv:
|
if '--nowebui' in sys.argv:
|
||||||
webui.api_only()
|
webui.api_only()
|
||||||
|
|||||||
+23
-5
@@ -1,9 +1,12 @@
|
|||||||
|
from collections import namedtuple
|
||||||
|
|
||||||
import torch
|
import torch
|
||||||
from modules import devices, shared
|
from modules import devices, shared
|
||||||
|
|
||||||
module_in_gpu = None
|
module_in_gpu = None
|
||||||
cpu = torch.device("cpu")
|
cpu = torch.device("cpu")
|
||||||
|
|
||||||
|
ModuleWithParent = namedtuple('ModuleWithParent', ['module', 'parent'], defaults=['None'])
|
||||||
|
|
||||||
def send_everything_to_cpu():
|
def send_everything_to_cpu():
|
||||||
global module_in_gpu
|
global module_in_gpu
|
||||||
@@ -75,13 +78,14 @@ def setup_for_low_vram(sd_model, use_medvram):
|
|||||||
(sd_model, 'depth_model'),
|
(sd_model, 'depth_model'),
|
||||||
(sd_model, 'embedder'),
|
(sd_model, 'embedder'),
|
||||||
(sd_model, 'model'),
|
(sd_model, 'model'),
|
||||||
(sd_model, 'embedder'),
|
|
||||||
]
|
]
|
||||||
|
|
||||||
is_sdxl = hasattr(sd_model, 'conditioner')
|
is_sdxl = hasattr(sd_model, 'conditioner')
|
||||||
is_sd2 = not is_sdxl and hasattr(sd_model.cond_stage_model, 'model')
|
is_sd2 = not is_sdxl and hasattr(sd_model.cond_stage_model, 'model')
|
||||||
|
|
||||||
if is_sdxl:
|
if hasattr(sd_model, 'medvram_fields'):
|
||||||
|
to_remain_in_cpu = sd_model.medvram_fields()
|
||||||
|
elif is_sdxl:
|
||||||
to_remain_in_cpu.append((sd_model, 'conditioner'))
|
to_remain_in_cpu.append((sd_model, 'conditioner'))
|
||||||
elif is_sd2:
|
elif is_sd2:
|
||||||
to_remain_in_cpu.append((sd_model.cond_stage_model, 'model'))
|
to_remain_in_cpu.append((sd_model.cond_stage_model, 'model'))
|
||||||
@@ -103,7 +107,21 @@ def setup_for_low_vram(sd_model, use_medvram):
|
|||||||
setattr(obj, field, module)
|
setattr(obj, field, module)
|
||||||
|
|
||||||
# register hooks for those the first three models
|
# register hooks for those the first three models
|
||||||
if is_sdxl:
|
if hasattr(sd_model, "cond_stage_model") and hasattr(sd_model.cond_stage_model, "medvram_modules"):
|
||||||
|
for module in sd_model.cond_stage_model.medvram_modules():
|
||||||
|
if isinstance(module, ModuleWithParent):
|
||||||
|
parent = module.parent
|
||||||
|
module = module.module
|
||||||
|
else:
|
||||||
|
parent = None
|
||||||
|
|
||||||
|
if module:
|
||||||
|
module.register_forward_pre_hook(send_me_to_gpu)
|
||||||
|
|
||||||
|
if parent:
|
||||||
|
parents[module] = parent
|
||||||
|
|
||||||
|
elif is_sdxl:
|
||||||
sd_model.conditioner.register_forward_pre_hook(send_me_to_gpu)
|
sd_model.conditioner.register_forward_pre_hook(send_me_to_gpu)
|
||||||
elif is_sd2:
|
elif is_sd2:
|
||||||
sd_model.cond_stage_model.model.register_forward_pre_hook(send_me_to_gpu)
|
sd_model.cond_stage_model.model.register_forward_pre_hook(send_me_to_gpu)
|
||||||
@@ -117,9 +135,9 @@ def setup_for_low_vram(sd_model, use_medvram):
|
|||||||
sd_model.first_stage_model.register_forward_pre_hook(send_me_to_gpu)
|
sd_model.first_stage_model.register_forward_pre_hook(send_me_to_gpu)
|
||||||
sd_model.first_stage_model.encode = first_stage_model_encode_wrap
|
sd_model.first_stage_model.encode = first_stage_model_encode_wrap
|
||||||
sd_model.first_stage_model.decode = first_stage_model_decode_wrap
|
sd_model.first_stage_model.decode = first_stage_model_decode_wrap
|
||||||
if sd_model.depth_model:
|
if getattr(sd_model, 'depth_model', None) is not None:
|
||||||
sd_model.depth_model.register_forward_pre_hook(send_me_to_gpu)
|
sd_model.depth_model.register_forward_pre_hook(send_me_to_gpu)
|
||||||
if sd_model.embedder:
|
if getattr(sd_model, 'embedder', None) is not None:
|
||||||
sd_model.embedder.register_forward_pre_hook(send_me_to_gpu)
|
sd_model.embedder.register_forward_pre_hook(send_me_to_gpu)
|
||||||
|
|
||||||
if use_medvram:
|
if use_medvram:
|
||||||
|
|||||||
@@ -12,7 +12,7 @@ log = logging.getLogger(__name__)
|
|||||||
|
|
||||||
# before torch version 1.13, has_mps is only available in nightly pytorch and macOS 12.3+,
|
# before torch version 1.13, has_mps is only available in nightly pytorch and macOS 12.3+,
|
||||||
# use check `getattr` and try it for compatibility.
|
# use check `getattr` and try it for compatibility.
|
||||||
# in torch version 1.13, backends.mps.is_available() and backends.mps.is_built() are introduced in to check mps availabilty,
|
# in torch version 1.13, backends.mps.is_available() and backends.mps.is_built() are introduced in to check mps availability,
|
||||||
# since torch 2.0.1+ nightly build, getattr(torch, 'has_mps', False) was deprecated, see https://github.com/pytorch/pytorch/pull/103279
|
# since torch 2.0.1+ nightly build, getattr(torch, 'has_mps', False) was deprecated, see https://github.com/pytorch/pytorch/pull/103279
|
||||||
def check_for_mps() -> bool:
|
def check_for_mps() -> bool:
|
||||||
if version.parse(torch.__version__) <= version.parse("2.0.1"):
|
if version.parse(torch.__version__) <= version.parse("2.0.1"):
|
||||||
|
|||||||
+31
-9
@@ -1,17 +1,39 @@
|
|||||||
from PIL import Image, ImageFilter, ImageOps
|
from PIL import Image, ImageFilter, ImageOps
|
||||||
|
|
||||||
|
|
||||||
def get_crop_region(mask, pad=0):
|
def get_crop_region_v2(mask, pad=0):
|
||||||
"""finds a rectangular region that contains all masked ares in an image. Returns (x1, y1, x2, y2) coordinates of the rectangle.
|
"""
|
||||||
For example, if a user has painted the top-right part of a 512x512 image, the result may be (256, 0, 512, 256)"""
|
Finds a rectangular region that contains all masked ares in a mask.
|
||||||
mask_img = mask if isinstance(mask, Image.Image) else Image.fromarray(mask)
|
Returns None if mask is completely black mask (all 0)
|
||||||
box = mask_img.getbbox()
|
|
||||||
if box:
|
Parameters:
|
||||||
|
mask: PIL.Image.Image L mode or numpy 1d array
|
||||||
|
pad: int number of pixels that the region will be extended on all sides
|
||||||
|
Returns: (x1, y1, x2, y2) | None
|
||||||
|
|
||||||
|
Introduced post 1.9.0
|
||||||
|
"""
|
||||||
|
mask = mask if isinstance(mask, Image.Image) else Image.fromarray(mask)
|
||||||
|
if box := mask.getbbox():
|
||||||
x1, y1, x2, y2 = box
|
x1, y1, x2, y2 = box
|
||||||
else: # when no box is found
|
return (max(x1 - pad, 0), max(y1 - pad, 0), min(x2 + pad, mask.size[0]), min(y2 + pad, mask.size[1])) if pad else box
|
||||||
x1, y1 = mask_img.size
|
|
||||||
|
|
||||||
|
def get_crop_region(mask, pad=0):
|
||||||
|
"""
|
||||||
|
Same function as get_crop_region_v2 but handles completely black mask (all 0) differently
|
||||||
|
when mask all black still return coordinates but the coordinates may be invalid ie x2>x1 or y2>y1
|
||||||
|
Notes: it is possible for the coordinates to be "valid" again if pad size is sufficiently large
|
||||||
|
(mask_size.x-pad, mask_size.y-pad, pad, pad)
|
||||||
|
|
||||||
|
Extension developer should use get_crop_region_v2 instead unless for compatibility considerations.
|
||||||
|
"""
|
||||||
|
mask = mask if isinstance(mask, Image.Image) else Image.fromarray(mask)
|
||||||
|
if box := get_crop_region_v2(mask, pad):
|
||||||
|
return box
|
||||||
|
x1, y1 = mask.size
|
||||||
x2 = y2 = 0
|
x2 = y2 = 0
|
||||||
return max(x1 - pad, 0), max(y1 - pad, 0), min(x2 + pad, mask_img.size[0]), min(y2 + pad, mask_img.size[1])
|
return max(x1 - pad, 0), max(y1 - pad, 0), min(x2 + pad, mask.size[0]), min(y2 + pad, mask.size[1])
|
||||||
|
|
||||||
|
|
||||||
def expand_crop_region(crop_region, processing_width, processing_height, image_width, image_height):
|
def expand_crop_region(crop_region, processing_width, processing_height, image_width, image_height):
|
||||||
|
|||||||
+36
-31
@@ -10,6 +10,7 @@ import torch
|
|||||||
|
|
||||||
from modules import shared
|
from modules import shared
|
||||||
from modules.upscaler import Upscaler, UpscalerLanczos, UpscalerNearest, UpscalerNone
|
from modules.upscaler import Upscaler, UpscalerLanczos, UpscalerNearest, UpscalerNone
|
||||||
|
from modules.util import load_file_from_url # noqa, backwards compatibility
|
||||||
|
|
||||||
if TYPE_CHECKING:
|
if TYPE_CHECKING:
|
||||||
import spandrel
|
import spandrel
|
||||||
@@ -17,30 +18,7 @@ if TYPE_CHECKING:
|
|||||||
logger = logging.getLogger(__name__)
|
logger = logging.getLogger(__name__)
|
||||||
|
|
||||||
|
|
||||||
def load_file_from_url(
|
def load_models(model_path: str, model_url: str = None, command_path: str = None, ext_filter=None, download_name=None, ext_blacklist=None, hash_prefix=None) -> list:
|
||||||
url: str,
|
|
||||||
*,
|
|
||||||
model_dir: str,
|
|
||||||
progress: bool = True,
|
|
||||||
file_name: str | None = None,
|
|
||||||
) -> str:
|
|
||||||
"""Download a file from `url` into `model_dir`, using the file present if possible.
|
|
||||||
|
|
||||||
Returns the path to the downloaded file.
|
|
||||||
"""
|
|
||||||
os.makedirs(model_dir, exist_ok=True)
|
|
||||||
if not file_name:
|
|
||||||
parts = urlparse(url)
|
|
||||||
file_name = os.path.basename(parts.path)
|
|
||||||
cached_file = os.path.abspath(os.path.join(model_dir, file_name))
|
|
||||||
if not os.path.exists(cached_file):
|
|
||||||
print(f'Downloading: "{url}" to {cached_file}\n')
|
|
||||||
from torch.hub import download_url_to_file
|
|
||||||
download_url_to_file(url, cached_file, progress=progress)
|
|
||||||
return cached_file
|
|
||||||
|
|
||||||
|
|
||||||
def load_models(model_path: str, model_url: str = None, command_path: str = None, ext_filter=None, download_name=None, ext_blacklist=None) -> list:
|
|
||||||
"""
|
"""
|
||||||
A one-and done loader to try finding the desired models in specified directories.
|
A one-and done loader to try finding the desired models in specified directories.
|
||||||
|
|
||||||
@@ -49,6 +27,7 @@ def load_models(model_path: str, model_url: str = None, command_path: str = None
|
|||||||
@param model_path: The location to store/find models in.
|
@param model_path: The location to store/find models in.
|
||||||
@param command_path: A command-line argument to search for models in first.
|
@param command_path: A command-line argument to search for models in first.
|
||||||
@param ext_filter: An optional list of filename extensions to filter by
|
@param ext_filter: An optional list of filename extensions to filter by
|
||||||
|
@param hash_prefix: the expected sha256 of the model_url
|
||||||
@return: A list of paths containing the desired model(s)
|
@return: A list of paths containing the desired model(s)
|
||||||
"""
|
"""
|
||||||
output = []
|
output = []
|
||||||
@@ -78,7 +57,7 @@ def load_models(model_path: str, model_url: str = None, command_path: str = None
|
|||||||
|
|
||||||
if model_url is not None and len(output) == 0:
|
if model_url is not None and len(output) == 0:
|
||||||
if download_name is not None:
|
if download_name is not None:
|
||||||
output.append(load_file_from_url(model_url, model_dir=places[0], file_name=download_name))
|
output.append(load_file_from_url(model_url, model_dir=places[0], file_name=download_name, hash_prefix=hash_prefix))
|
||||||
else:
|
else:
|
||||||
output.append(model_url)
|
output.append(model_url)
|
||||||
|
|
||||||
@@ -110,7 +89,7 @@ def load_upscalers():
|
|||||||
except Exception:
|
except Exception:
|
||||||
pass
|
pass
|
||||||
|
|
||||||
datas = []
|
data = []
|
||||||
commandline_options = vars(shared.cmd_opts)
|
commandline_options = vars(shared.cmd_opts)
|
||||||
|
|
||||||
# some of upscaler classes will not go away after reloading their modules, and we'll end
|
# some of upscaler classes will not go away after reloading their modules, and we'll end
|
||||||
@@ -129,14 +108,35 @@ def load_upscalers():
|
|||||||
scaler = cls(commandline_model_path)
|
scaler = cls(commandline_model_path)
|
||||||
scaler.user_path = commandline_model_path
|
scaler.user_path = commandline_model_path
|
||||||
scaler.model_download_path = commandline_model_path or scaler.model_path
|
scaler.model_download_path = commandline_model_path or scaler.model_path
|
||||||
datas += scaler.scalers
|
data += scaler.scalers
|
||||||
|
|
||||||
shared.sd_upscalers = sorted(
|
shared.sd_upscalers = sorted(
|
||||||
datas,
|
data,
|
||||||
# Special case for UpscalerNone keeps it at the beginning of the list.
|
# Special case for UpscalerNone keeps it at the beginning of the list.
|
||||||
key=lambda x: x.name.lower() if not isinstance(x.scaler, (UpscalerNone, UpscalerLanczos, UpscalerNearest)) else ""
|
key=lambda x: x.name.lower() if not isinstance(x.scaler, (UpscalerNone, UpscalerLanczos, UpscalerNearest)) else ""
|
||||||
)
|
)
|
||||||
|
|
||||||
|
# None: not loaded, False: failed to load, True: loaded
|
||||||
|
_spandrel_extra_init_state = None
|
||||||
|
|
||||||
|
|
||||||
|
def _init_spandrel_extra_archs() -> None:
|
||||||
|
"""
|
||||||
|
Try to initialize `spandrel_extra_archs` (exactly once).
|
||||||
|
"""
|
||||||
|
global _spandrel_extra_init_state
|
||||||
|
if _spandrel_extra_init_state is not None:
|
||||||
|
return
|
||||||
|
|
||||||
|
try:
|
||||||
|
import spandrel
|
||||||
|
import spandrel_extra_arches
|
||||||
|
spandrel.MAIN_REGISTRY.add(*spandrel_extra_arches.EXTRA_REGISTRY)
|
||||||
|
_spandrel_extra_init_state = True
|
||||||
|
except Exception:
|
||||||
|
logger.warning("Failed to load spandrel_extra_arches", exc_info=True)
|
||||||
|
_spandrel_extra_init_state = False
|
||||||
|
|
||||||
|
|
||||||
def load_spandrel_model(
|
def load_spandrel_model(
|
||||||
path: str | os.PathLike,
|
path: str | os.PathLike,
|
||||||
@@ -146,11 +146,16 @@ def load_spandrel_model(
|
|||||||
dtype: str | torch.dtype | None = None,
|
dtype: str | torch.dtype | None = None,
|
||||||
expected_architecture: str | None = None,
|
expected_architecture: str | None = None,
|
||||||
) -> spandrel.ModelDescriptor:
|
) -> spandrel.ModelDescriptor:
|
||||||
|
global _spandrel_extra_init_state
|
||||||
|
|
||||||
import spandrel
|
import spandrel
|
||||||
|
_init_spandrel_extra_archs()
|
||||||
|
|
||||||
model_descriptor = spandrel.ModelLoader(device=device).load_from_file(str(path))
|
model_descriptor = spandrel.ModelLoader(device=device).load_from_file(str(path))
|
||||||
if expected_architecture and model_descriptor.architecture != expected_architecture:
|
arch = model_descriptor.architecture
|
||||||
|
if expected_architecture and arch.name != expected_architecture:
|
||||||
logger.warning(
|
logger.warning(
|
||||||
f"Model {path!r} is not a {expected_architecture!r} model (got {model_descriptor.architecture!r})",
|
f"Model {path!r} is not a {expected_architecture!r} model (got {arch.name!r})",
|
||||||
)
|
)
|
||||||
half = False
|
half = False
|
||||||
if prefer_half:
|
if prefer_half:
|
||||||
@@ -164,6 +169,6 @@ def load_spandrel_model(
|
|||||||
model_descriptor.model.eval()
|
model_descriptor.model.eval()
|
||||||
logger.debug(
|
logger.debug(
|
||||||
"Loaded %s from %s (device=%s, half=%s, dtype=%s)",
|
"Loaded %s from %s (device=%s, half=%s, dtype=%s)",
|
||||||
model_descriptor, path, device, half, dtype,
|
arch, path, device, half, dtype,
|
||||||
)
|
)
|
||||||
return model_descriptor
|
return model_descriptor
|
||||||
|
|||||||
@@ -341,7 +341,7 @@ class DDPM(pl.LightningModule):
|
|||||||
elif self.parameterization == "x0":
|
elif self.parameterization == "x0":
|
||||||
target = x_start
|
target = x_start
|
||||||
else:
|
else:
|
||||||
raise NotImplementedError(f"Paramterization {self.parameterization} not yet supported")
|
raise NotImplementedError(f"Parameterization {self.parameterization} not yet supported")
|
||||||
|
|
||||||
loss = self.get_loss(model_out, target, mean=False).mean(dim=[1, 2, 3])
|
loss = self.get_loss(model_out, target, mean=False).mean(dim=[1, 2, 3])
|
||||||
|
|
||||||
@@ -901,7 +901,7 @@ class LatentDiffusion(DDPM):
|
|||||||
def apply_model(self, x_noisy, t, cond, return_ids=False):
|
def apply_model(self, x_noisy, t, cond, return_ids=False):
|
||||||
|
|
||||||
if isinstance(cond, dict):
|
if isinstance(cond, dict):
|
||||||
# hybrid case, cond is exptected to be a dict
|
# hybrid case, cond is expected to be a dict
|
||||||
pass
|
pass
|
||||||
else:
|
else:
|
||||||
if not isinstance(cond, list):
|
if not isinstance(cond, list):
|
||||||
@@ -937,7 +937,7 @@ class LatentDiffusion(DDPM):
|
|||||||
cond_list = [{c_key: [c[:, :, :, :, i]]} for i in range(c.shape[-1])]
|
cond_list = [{c_key: [c[:, :, :, :, i]]} for i in range(c.shape[-1])]
|
||||||
|
|
||||||
elif self.cond_stage_key == 'coordinates_bbox':
|
elif self.cond_stage_key == 'coordinates_bbox':
|
||||||
assert 'original_image_size' in self.split_input_params, 'BoudingBoxRescaling is missing original_image_size'
|
assert 'original_image_size' in self.split_input_params, 'BoundingBoxRescaling is missing original_image_size'
|
||||||
|
|
||||||
# assuming padding of unfold is always 0 and its dilation is always 1
|
# assuming padding of unfold is always 0 and its dilation is always 1
|
||||||
n_patches_per_row = int((w - ks[0]) / stride[0] + 1)
|
n_patches_per_row = int((w - ks[0]) / stride[0] + 1)
|
||||||
@@ -947,7 +947,7 @@ class LatentDiffusion(DDPM):
|
|||||||
num_downs = self.first_stage_model.encoder.num_resolutions - 1
|
num_downs = self.first_stage_model.encoder.num_resolutions - 1
|
||||||
rescale_latent = 2 ** (num_downs)
|
rescale_latent = 2 ** (num_downs)
|
||||||
|
|
||||||
# get top left postions of patches as conforming for the bbbox tokenizer, therefore we
|
# get top left positions of patches as conforming for the bbbox tokenizer, therefore we
|
||||||
# need to rescale the tl patch coordinates to be in between (0,1)
|
# need to rescale the tl patch coordinates to be in between (0,1)
|
||||||
tl_patch_coordinates = [(rescale_latent * stride[0] * (patch_nr % n_patches_per_row) / full_img_w,
|
tl_patch_coordinates = [(rescale_latent * stride[0] * (patch_nr % n_patches_per_row) / full_img_w,
|
||||||
rescale_latent * stride[1] * (patch_nr // n_patches_per_row) / full_img_h)
|
rescale_latent * stride[1] * (patch_nr // n_patches_per_row) / full_img_h)
|
||||||
|
|||||||
@@ -323,7 +323,7 @@ def model_wrapper(
|
|||||||
|
|
||||||
def model_fn(x, t_continuous, condition, unconditional_condition):
|
def model_fn(x, t_continuous, condition, unconditional_condition):
|
||||||
"""
|
"""
|
||||||
The noise predicition model function that is used for DPM-Solver.
|
The noise prediction model function that is used for DPM-Solver.
|
||||||
"""
|
"""
|
||||||
if t_continuous.reshape((-1,)).shape[0] == 1:
|
if t_continuous.reshape((-1,)).shape[0] == 1:
|
||||||
t_continuous = t_continuous.expand((x.shape[0]))
|
t_continuous = t_continuous.expand((x.shape[0]))
|
||||||
|
|||||||
@@ -0,0 +1,622 @@
|
|||||||
|
### This file contains impls for MM-DiT, the core model component of SD3
|
||||||
|
|
||||||
|
import math
|
||||||
|
from typing import Dict, Optional
|
||||||
|
import numpy as np
|
||||||
|
import torch
|
||||||
|
import torch.nn as nn
|
||||||
|
from einops import rearrange, repeat
|
||||||
|
from modules.models.sd3.other_impls import attention, Mlp
|
||||||
|
|
||||||
|
|
||||||
|
class PatchEmbed(nn.Module):
|
||||||
|
""" 2D Image to Patch Embedding"""
|
||||||
|
def __init__(
|
||||||
|
self,
|
||||||
|
img_size: Optional[int] = 224,
|
||||||
|
patch_size: int = 16,
|
||||||
|
in_chans: int = 3,
|
||||||
|
embed_dim: int = 768,
|
||||||
|
flatten: bool = True,
|
||||||
|
bias: bool = True,
|
||||||
|
strict_img_size: bool = True,
|
||||||
|
dynamic_img_pad: bool = False,
|
||||||
|
dtype=None,
|
||||||
|
device=None,
|
||||||
|
):
|
||||||
|
super().__init__()
|
||||||
|
self.patch_size = (patch_size, patch_size)
|
||||||
|
if img_size is not None:
|
||||||
|
self.img_size = (img_size, img_size)
|
||||||
|
self.grid_size = tuple([s // p for s, p in zip(self.img_size, self.patch_size)])
|
||||||
|
self.num_patches = self.grid_size[0] * self.grid_size[1]
|
||||||
|
else:
|
||||||
|
self.img_size = None
|
||||||
|
self.grid_size = None
|
||||||
|
self.num_patches = None
|
||||||
|
|
||||||
|
# flatten spatial dim and transpose to channels last, kept for bwd compat
|
||||||
|
self.flatten = flatten
|
||||||
|
self.strict_img_size = strict_img_size
|
||||||
|
self.dynamic_img_pad = dynamic_img_pad
|
||||||
|
|
||||||
|
self.proj = nn.Conv2d(in_chans, embed_dim, kernel_size=patch_size, stride=patch_size, bias=bias, dtype=dtype, device=device)
|
||||||
|
|
||||||
|
def forward(self, x):
|
||||||
|
B, C, H, W = x.shape
|
||||||
|
x = self.proj(x)
|
||||||
|
if self.flatten:
|
||||||
|
x = x.flatten(2).transpose(1, 2) # NCHW -> NLC
|
||||||
|
return x
|
||||||
|
|
||||||
|
|
||||||
|
def modulate(x, shift, scale):
|
||||||
|
if shift is None:
|
||||||
|
shift = torch.zeros_like(scale)
|
||||||
|
return x * (1 + scale.unsqueeze(1)) + shift.unsqueeze(1)
|
||||||
|
|
||||||
|
|
||||||
|
#################################################################################
|
||||||
|
# Sine/Cosine Positional Embedding Functions #
|
||||||
|
#################################################################################
|
||||||
|
|
||||||
|
|
||||||
|
def get_2d_sincos_pos_embed(embed_dim, grid_size, cls_token=False, extra_tokens=0, scaling_factor=None, offset=None):
|
||||||
|
"""
|
||||||
|
grid_size: int of the grid height and width
|
||||||
|
return:
|
||||||
|
pos_embed: [grid_size*grid_size, embed_dim] or [1+grid_size*grid_size, embed_dim] (w/ or w/o cls_token)
|
||||||
|
"""
|
||||||
|
grid_h = np.arange(grid_size, dtype=np.float32)
|
||||||
|
grid_w = np.arange(grid_size, dtype=np.float32)
|
||||||
|
grid = np.meshgrid(grid_w, grid_h) # here w goes first
|
||||||
|
grid = np.stack(grid, axis=0)
|
||||||
|
if scaling_factor is not None:
|
||||||
|
grid = grid / scaling_factor
|
||||||
|
if offset is not None:
|
||||||
|
grid = grid - offset
|
||||||
|
grid = grid.reshape([2, 1, grid_size, grid_size])
|
||||||
|
pos_embed = get_2d_sincos_pos_embed_from_grid(embed_dim, grid)
|
||||||
|
if cls_token and extra_tokens > 0:
|
||||||
|
pos_embed = np.concatenate([np.zeros([extra_tokens, embed_dim]), pos_embed], axis=0)
|
||||||
|
return pos_embed
|
||||||
|
|
||||||
|
|
||||||
|
def get_2d_sincos_pos_embed_from_grid(embed_dim, grid):
|
||||||
|
assert embed_dim % 2 == 0
|
||||||
|
# use half of dimensions to encode grid_h
|
||||||
|
emb_h = get_1d_sincos_pos_embed_from_grid(embed_dim // 2, grid[0]) # (H*W, D/2)
|
||||||
|
emb_w = get_1d_sincos_pos_embed_from_grid(embed_dim // 2, grid[1]) # (H*W, D/2)
|
||||||
|
emb = np.concatenate([emb_h, emb_w], axis=1) # (H*W, D)
|
||||||
|
return emb
|
||||||
|
|
||||||
|
|
||||||
|
def get_1d_sincos_pos_embed_from_grid(embed_dim, pos):
|
||||||
|
"""
|
||||||
|
embed_dim: output dimension for each position
|
||||||
|
pos: a list of positions to be encoded: size (M,)
|
||||||
|
out: (M, D)
|
||||||
|
"""
|
||||||
|
assert embed_dim % 2 == 0
|
||||||
|
omega = np.arange(embed_dim // 2, dtype=np.float64)
|
||||||
|
omega /= embed_dim / 2.0
|
||||||
|
omega = 1.0 / 10000**omega # (D/2,)
|
||||||
|
pos = pos.reshape(-1) # (M,)
|
||||||
|
out = np.einsum("m,d->md", pos, omega) # (M, D/2), outer product
|
||||||
|
emb_sin = np.sin(out) # (M, D/2)
|
||||||
|
emb_cos = np.cos(out) # (M, D/2)
|
||||||
|
return np.concatenate([emb_sin, emb_cos], axis=1) # (M, D)
|
||||||
|
|
||||||
|
|
||||||
|
#################################################################################
|
||||||
|
# Embedding Layers for Timesteps and Class Labels #
|
||||||
|
#################################################################################
|
||||||
|
|
||||||
|
|
||||||
|
class TimestepEmbedder(nn.Module):
|
||||||
|
"""Embeds scalar timesteps into vector representations."""
|
||||||
|
|
||||||
|
def __init__(self, hidden_size, frequency_embedding_size=256, dtype=None, device=None):
|
||||||
|
super().__init__()
|
||||||
|
self.mlp = nn.Sequential(
|
||||||
|
nn.Linear(frequency_embedding_size, hidden_size, bias=True, dtype=dtype, device=device),
|
||||||
|
nn.SiLU(),
|
||||||
|
nn.Linear(hidden_size, hidden_size, bias=True, dtype=dtype, device=device),
|
||||||
|
)
|
||||||
|
self.frequency_embedding_size = frequency_embedding_size
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
def timestep_embedding(t, dim, max_period=10000):
|
||||||
|
"""
|
||||||
|
Create sinusoidal timestep embeddings.
|
||||||
|
:param t: a 1-D Tensor of N indices, one per batch element.
|
||||||
|
These may be fractional.
|
||||||
|
:param dim: the dimension of the output.
|
||||||
|
:param max_period: controls the minimum frequency of the embeddings.
|
||||||
|
:return: an (N, D) Tensor of positional embeddings.
|
||||||
|
"""
|
||||||
|
half = dim // 2
|
||||||
|
freqs = torch.exp(
|
||||||
|
-math.log(max_period)
|
||||||
|
* torch.arange(start=0, end=half, dtype=torch.float32)
|
||||||
|
/ half
|
||||||
|
).to(device=t.device)
|
||||||
|
args = t[:, None].float() * freqs[None]
|
||||||
|
embedding = torch.cat([torch.cos(args), torch.sin(args)], dim=-1)
|
||||||
|
if dim % 2:
|
||||||
|
embedding = torch.cat([embedding, torch.zeros_like(embedding[:, :1])], dim=-1)
|
||||||
|
if torch.is_floating_point(t):
|
||||||
|
embedding = embedding.to(dtype=t.dtype)
|
||||||
|
return embedding
|
||||||
|
|
||||||
|
def forward(self, t, dtype, **kwargs):
|
||||||
|
t_freq = self.timestep_embedding(t, self.frequency_embedding_size).to(dtype)
|
||||||
|
t_emb = self.mlp(t_freq)
|
||||||
|
return t_emb
|
||||||
|
|
||||||
|
|
||||||
|
class VectorEmbedder(nn.Module):
|
||||||
|
"""Embeds a flat vector of dimension input_dim"""
|
||||||
|
|
||||||
|
def __init__(self, input_dim: int, hidden_size: int, dtype=None, device=None):
|
||||||
|
super().__init__()
|
||||||
|
self.mlp = nn.Sequential(
|
||||||
|
nn.Linear(input_dim, hidden_size, bias=True, dtype=dtype, device=device),
|
||||||
|
nn.SiLU(),
|
||||||
|
nn.Linear(hidden_size, hidden_size, bias=True, dtype=dtype, device=device),
|
||||||
|
)
|
||||||
|
|
||||||
|
def forward(self, x: torch.Tensor) -> torch.Tensor:
|
||||||
|
return self.mlp(x)
|
||||||
|
|
||||||
|
|
||||||
|
#################################################################################
|
||||||
|
# Core DiT Model #
|
||||||
|
#################################################################################
|
||||||
|
|
||||||
|
|
||||||
|
class QkvLinear(torch.nn.Linear):
|
||||||
|
pass
|
||||||
|
|
||||||
|
def split_qkv(qkv, head_dim):
|
||||||
|
qkv = qkv.reshape(qkv.shape[0], qkv.shape[1], 3, -1, head_dim).movedim(2, 0)
|
||||||
|
return qkv[0], qkv[1], qkv[2]
|
||||||
|
|
||||||
|
def optimized_attention(qkv, num_heads):
|
||||||
|
return attention(qkv[0], qkv[1], qkv[2], num_heads)
|
||||||
|
|
||||||
|
class SelfAttention(nn.Module):
|
||||||
|
ATTENTION_MODES = ("xformers", "torch", "torch-hb", "math", "debug")
|
||||||
|
|
||||||
|
def __init__(
|
||||||
|
self,
|
||||||
|
dim: int,
|
||||||
|
num_heads: int = 8,
|
||||||
|
qkv_bias: bool = False,
|
||||||
|
qk_scale: Optional[float] = None,
|
||||||
|
attn_mode: str = "xformers",
|
||||||
|
pre_only: bool = False,
|
||||||
|
qk_norm: Optional[str] = None,
|
||||||
|
rmsnorm: bool = False,
|
||||||
|
dtype=None,
|
||||||
|
device=None,
|
||||||
|
):
|
||||||
|
super().__init__()
|
||||||
|
self.num_heads = num_heads
|
||||||
|
self.head_dim = dim // num_heads
|
||||||
|
|
||||||
|
self.qkv = QkvLinear(dim, dim * 3, bias=qkv_bias, dtype=dtype, device=device)
|
||||||
|
if not pre_only:
|
||||||
|
self.proj = nn.Linear(dim, dim, dtype=dtype, device=device)
|
||||||
|
assert attn_mode in self.ATTENTION_MODES
|
||||||
|
self.attn_mode = attn_mode
|
||||||
|
self.pre_only = pre_only
|
||||||
|
|
||||||
|
if qk_norm == "rms":
|
||||||
|
self.ln_q = RMSNorm(self.head_dim, elementwise_affine=True, eps=1.0e-6, dtype=dtype, device=device)
|
||||||
|
self.ln_k = RMSNorm(self.head_dim, elementwise_affine=True, eps=1.0e-6, dtype=dtype, device=device)
|
||||||
|
elif qk_norm == "ln":
|
||||||
|
self.ln_q = nn.LayerNorm(self.head_dim, elementwise_affine=True, eps=1.0e-6, dtype=dtype, device=device)
|
||||||
|
self.ln_k = nn.LayerNorm(self.head_dim, elementwise_affine=True, eps=1.0e-6, dtype=dtype, device=device)
|
||||||
|
elif qk_norm is None:
|
||||||
|
self.ln_q = nn.Identity()
|
||||||
|
self.ln_k = nn.Identity()
|
||||||
|
else:
|
||||||
|
raise ValueError(qk_norm)
|
||||||
|
|
||||||
|
def pre_attention(self, x: torch.Tensor):
|
||||||
|
B, L, C = x.shape
|
||||||
|
qkv = self.qkv(x)
|
||||||
|
q, k, v = split_qkv(qkv, self.head_dim)
|
||||||
|
q = self.ln_q(q).reshape(q.shape[0], q.shape[1], -1)
|
||||||
|
k = self.ln_k(k).reshape(q.shape[0], q.shape[1], -1)
|
||||||
|
return (q, k, v)
|
||||||
|
|
||||||
|
def post_attention(self, x: torch.Tensor) -> torch.Tensor:
|
||||||
|
assert not self.pre_only
|
||||||
|
x = self.proj(x)
|
||||||
|
return x
|
||||||
|
|
||||||
|
def forward(self, x: torch.Tensor) -> torch.Tensor:
|
||||||
|
(q, k, v) = self.pre_attention(x)
|
||||||
|
x = attention(q, k, v, self.num_heads)
|
||||||
|
x = self.post_attention(x)
|
||||||
|
return x
|
||||||
|
|
||||||
|
|
||||||
|
class RMSNorm(torch.nn.Module):
|
||||||
|
def __init__(
|
||||||
|
self, dim: int, elementwise_affine: bool = False, eps: float = 1e-6, device=None, dtype=None
|
||||||
|
):
|
||||||
|
"""
|
||||||
|
Initialize the RMSNorm normalization layer.
|
||||||
|
Args:
|
||||||
|
dim (int): The dimension of the input tensor.
|
||||||
|
eps (float, optional): A small value added to the denominator for numerical stability. Default is 1e-6.
|
||||||
|
Attributes:
|
||||||
|
eps (float): A small value added to the denominator for numerical stability.
|
||||||
|
weight (nn.Parameter): Learnable scaling parameter.
|
||||||
|
"""
|
||||||
|
super().__init__()
|
||||||
|
self.eps = eps
|
||||||
|
self.learnable_scale = elementwise_affine
|
||||||
|
if self.learnable_scale:
|
||||||
|
self.weight = nn.Parameter(torch.empty(dim, device=device, dtype=dtype))
|
||||||
|
else:
|
||||||
|
self.register_parameter("weight", None)
|
||||||
|
|
||||||
|
def _norm(self, x):
|
||||||
|
"""
|
||||||
|
Apply the RMSNorm normalization to the input tensor.
|
||||||
|
Args:
|
||||||
|
x (torch.Tensor): The input tensor.
|
||||||
|
Returns:
|
||||||
|
torch.Tensor: The normalized tensor.
|
||||||
|
"""
|
||||||
|
return x * torch.rsqrt(x.pow(2).mean(-1, keepdim=True) + self.eps)
|
||||||
|
|
||||||
|
def forward(self, x):
|
||||||
|
"""
|
||||||
|
Forward pass through the RMSNorm layer.
|
||||||
|
Args:
|
||||||
|
x (torch.Tensor): The input tensor.
|
||||||
|
Returns:
|
||||||
|
torch.Tensor: The output tensor after applying RMSNorm.
|
||||||
|
"""
|
||||||
|
x = self._norm(x)
|
||||||
|
if self.learnable_scale:
|
||||||
|
return x * self.weight.to(device=x.device, dtype=x.dtype)
|
||||||
|
else:
|
||||||
|
return x
|
||||||
|
|
||||||
|
|
||||||
|
class SwiGLUFeedForward(nn.Module):
|
||||||
|
def __init__(
|
||||||
|
self,
|
||||||
|
dim: int,
|
||||||
|
hidden_dim: int,
|
||||||
|
multiple_of: int,
|
||||||
|
ffn_dim_multiplier: Optional[float] = None,
|
||||||
|
):
|
||||||
|
"""
|
||||||
|
Initialize the FeedForward module.
|
||||||
|
|
||||||
|
Args:
|
||||||
|
dim (int): Input dimension.
|
||||||
|
hidden_dim (int): Hidden dimension of the feedforward layer.
|
||||||
|
multiple_of (int): Value to ensure hidden dimension is a multiple of this value.
|
||||||
|
ffn_dim_multiplier (float, optional): Custom multiplier for hidden dimension. Defaults to None.
|
||||||
|
|
||||||
|
Attributes:
|
||||||
|
w1 (ColumnParallelLinear): Linear transformation for the first layer.
|
||||||
|
w2 (RowParallelLinear): Linear transformation for the second layer.
|
||||||
|
w3 (ColumnParallelLinear): Linear transformation for the third layer.
|
||||||
|
|
||||||
|
"""
|
||||||
|
super().__init__()
|
||||||
|
hidden_dim = int(2 * hidden_dim / 3)
|
||||||
|
# custom dim factor multiplier
|
||||||
|
if ffn_dim_multiplier is not None:
|
||||||
|
hidden_dim = int(ffn_dim_multiplier * hidden_dim)
|
||||||
|
hidden_dim = multiple_of * ((hidden_dim + multiple_of - 1) // multiple_of)
|
||||||
|
|
||||||
|
self.w1 = nn.Linear(dim, hidden_dim, bias=False)
|
||||||
|
self.w2 = nn.Linear(hidden_dim, dim, bias=False)
|
||||||
|
self.w3 = nn.Linear(dim, hidden_dim, bias=False)
|
||||||
|
|
||||||
|
def forward(self, x):
|
||||||
|
return self.w2(nn.functional.silu(self.w1(x)) * self.w3(x))
|
||||||
|
|
||||||
|
|
||||||
|
class DismantledBlock(nn.Module):
|
||||||
|
"""A DiT block with gated adaptive layer norm (adaLN) conditioning."""
|
||||||
|
|
||||||
|
ATTENTION_MODES = ("xformers", "torch", "torch-hb", "math", "debug")
|
||||||
|
|
||||||
|
def __init__(
|
||||||
|
self,
|
||||||
|
hidden_size: int,
|
||||||
|
num_heads: int,
|
||||||
|
mlp_ratio: float = 4.0,
|
||||||
|
attn_mode: str = "xformers",
|
||||||
|
qkv_bias: bool = False,
|
||||||
|
pre_only: bool = False,
|
||||||
|
rmsnorm: bool = False,
|
||||||
|
scale_mod_only: bool = False,
|
||||||
|
swiglu: bool = False,
|
||||||
|
qk_norm: Optional[str] = None,
|
||||||
|
dtype=None,
|
||||||
|
device=None,
|
||||||
|
**block_kwargs,
|
||||||
|
):
|
||||||
|
super().__init__()
|
||||||
|
assert attn_mode in self.ATTENTION_MODES
|
||||||
|
if not rmsnorm:
|
||||||
|
self.norm1 = nn.LayerNorm(hidden_size, elementwise_affine=False, eps=1e-6, dtype=dtype, device=device)
|
||||||
|
else:
|
||||||
|
self.norm1 = RMSNorm(hidden_size, elementwise_affine=False, eps=1e-6)
|
||||||
|
self.attn = SelfAttention(dim=hidden_size, num_heads=num_heads, qkv_bias=qkv_bias, attn_mode=attn_mode, pre_only=pre_only, qk_norm=qk_norm, rmsnorm=rmsnorm, dtype=dtype, device=device)
|
||||||
|
if not pre_only:
|
||||||
|
if not rmsnorm:
|
||||||
|
self.norm2 = nn.LayerNorm(hidden_size, elementwise_affine=False, eps=1e-6, dtype=dtype, device=device)
|
||||||
|
else:
|
||||||
|
self.norm2 = RMSNorm(hidden_size, elementwise_affine=False, eps=1e-6)
|
||||||
|
mlp_hidden_dim = int(hidden_size * mlp_ratio)
|
||||||
|
if not pre_only:
|
||||||
|
if not swiglu:
|
||||||
|
self.mlp = Mlp(in_features=hidden_size, hidden_features=mlp_hidden_dim, act_layer=nn.GELU(approximate="tanh"), dtype=dtype, device=device)
|
||||||
|
else:
|
||||||
|
self.mlp = SwiGLUFeedForward(dim=hidden_size, hidden_dim=mlp_hidden_dim, multiple_of=256)
|
||||||
|
self.scale_mod_only = scale_mod_only
|
||||||
|
if not scale_mod_only:
|
||||||
|
n_mods = 6 if not pre_only else 2
|
||||||
|
else:
|
||||||
|
n_mods = 4 if not pre_only else 1
|
||||||
|
self.adaLN_modulation = nn.Sequential(nn.SiLU(), nn.Linear(hidden_size, n_mods * hidden_size, bias=True, dtype=dtype, device=device))
|
||||||
|
self.pre_only = pre_only
|
||||||
|
|
||||||
|
def pre_attention(self, x: torch.Tensor, c: torch.Tensor):
|
||||||
|
assert x is not None, "pre_attention called with None input"
|
||||||
|
if not self.pre_only:
|
||||||
|
if not self.scale_mod_only:
|
||||||
|
shift_msa, scale_msa, gate_msa, shift_mlp, scale_mlp, gate_mlp = self.adaLN_modulation(c).chunk(6, dim=1)
|
||||||
|
else:
|
||||||
|
shift_msa = None
|
||||||
|
shift_mlp = None
|
||||||
|
scale_msa, gate_msa, scale_mlp, gate_mlp = self.adaLN_modulation(c).chunk(4, dim=1)
|
||||||
|
qkv = self.attn.pre_attention(modulate(self.norm1(x), shift_msa, scale_msa))
|
||||||
|
return qkv, (x, gate_msa, shift_mlp, scale_mlp, gate_mlp)
|
||||||
|
else:
|
||||||
|
if not self.scale_mod_only:
|
||||||
|
shift_msa, scale_msa = self.adaLN_modulation(c).chunk(2, dim=1)
|
||||||
|
else:
|
||||||
|
shift_msa = None
|
||||||
|
scale_msa = self.adaLN_modulation(c)
|
||||||
|
qkv = self.attn.pre_attention(modulate(self.norm1(x), shift_msa, scale_msa))
|
||||||
|
return qkv, None
|
||||||
|
|
||||||
|
def post_attention(self, attn, x, gate_msa, shift_mlp, scale_mlp, gate_mlp):
|
||||||
|
assert not self.pre_only
|
||||||
|
x = x + gate_msa.unsqueeze(1) * self.attn.post_attention(attn)
|
||||||
|
x = x + gate_mlp.unsqueeze(1) * self.mlp(modulate(self.norm2(x), shift_mlp, scale_mlp))
|
||||||
|
return x
|
||||||
|
|
||||||
|
def forward(self, x: torch.Tensor, c: torch.Tensor) -> torch.Tensor:
|
||||||
|
assert not self.pre_only
|
||||||
|
(q, k, v), intermediates = self.pre_attention(x, c)
|
||||||
|
attn = attention(q, k, v, self.attn.num_heads)
|
||||||
|
return self.post_attention(attn, *intermediates)
|
||||||
|
|
||||||
|
|
||||||
|
def block_mixing(context, x, context_block, x_block, c):
|
||||||
|
assert context is not None, "block_mixing called with None context"
|
||||||
|
context_qkv, context_intermediates = context_block.pre_attention(context, c)
|
||||||
|
|
||||||
|
x_qkv, x_intermediates = x_block.pre_attention(x, c)
|
||||||
|
|
||||||
|
o = []
|
||||||
|
for t in range(3):
|
||||||
|
o.append(torch.cat((context_qkv[t], x_qkv[t]), dim=1))
|
||||||
|
q, k, v = tuple(o)
|
||||||
|
|
||||||
|
attn = attention(q, k, v, x_block.attn.num_heads)
|
||||||
|
context_attn, x_attn = (attn[:, : context_qkv[0].shape[1]], attn[:, context_qkv[0].shape[1] :])
|
||||||
|
|
||||||
|
if not context_block.pre_only:
|
||||||
|
context = context_block.post_attention(context_attn, *context_intermediates)
|
||||||
|
else:
|
||||||
|
context = None
|
||||||
|
x = x_block.post_attention(x_attn, *x_intermediates)
|
||||||
|
return context, x
|
||||||
|
|
||||||
|
|
||||||
|
class JointBlock(nn.Module):
|
||||||
|
"""just a small wrapper to serve as a fsdp unit"""
|
||||||
|
|
||||||
|
def __init__(self, *args, **kwargs):
|
||||||
|
super().__init__()
|
||||||
|
pre_only = kwargs.pop("pre_only")
|
||||||
|
qk_norm = kwargs.pop("qk_norm", None)
|
||||||
|
self.context_block = DismantledBlock(*args, pre_only=pre_only, qk_norm=qk_norm, **kwargs)
|
||||||
|
self.x_block = DismantledBlock(*args, pre_only=False, qk_norm=qk_norm, **kwargs)
|
||||||
|
|
||||||
|
def forward(self, *args, **kwargs):
|
||||||
|
return block_mixing(*args, context_block=self.context_block, x_block=self.x_block, **kwargs)
|
||||||
|
|
||||||
|
|
||||||
|
class FinalLayer(nn.Module):
|
||||||
|
"""
|
||||||
|
The final layer of DiT.
|
||||||
|
"""
|
||||||
|
|
||||||
|
def __init__(self, hidden_size: int, patch_size: int, out_channels: int, total_out_channels: Optional[int] = None, dtype=None, device=None):
|
||||||
|
super().__init__()
|
||||||
|
self.norm_final = nn.LayerNorm(hidden_size, elementwise_affine=False, eps=1e-6, dtype=dtype, device=device)
|
||||||
|
self.linear = (
|
||||||
|
nn.Linear(hidden_size, patch_size * patch_size * out_channels, bias=True, dtype=dtype, device=device)
|
||||||
|
if (total_out_channels is None)
|
||||||
|
else nn.Linear(hidden_size, total_out_channels, bias=True, dtype=dtype, device=device)
|
||||||
|
)
|
||||||
|
self.adaLN_modulation = nn.Sequential(nn.SiLU(), nn.Linear(hidden_size, 2 * hidden_size, bias=True, dtype=dtype, device=device))
|
||||||
|
|
||||||
|
def forward(self, x: torch.Tensor, c: torch.Tensor) -> torch.Tensor:
|
||||||
|
shift, scale = self.adaLN_modulation(c).chunk(2, dim=1)
|
||||||
|
x = modulate(self.norm_final(x), shift, scale)
|
||||||
|
x = self.linear(x)
|
||||||
|
return x
|
||||||
|
|
||||||
|
|
||||||
|
class MMDiT(nn.Module):
|
||||||
|
"""Diffusion model with a Transformer backbone."""
|
||||||
|
|
||||||
|
def __init__(
|
||||||
|
self,
|
||||||
|
input_size: int = 32,
|
||||||
|
patch_size: int = 2,
|
||||||
|
in_channels: int = 4,
|
||||||
|
depth: int = 28,
|
||||||
|
mlp_ratio: float = 4.0,
|
||||||
|
learn_sigma: bool = False,
|
||||||
|
adm_in_channels: Optional[int] = None,
|
||||||
|
context_embedder_config: Optional[Dict] = None,
|
||||||
|
register_length: int = 0,
|
||||||
|
attn_mode: str = "torch",
|
||||||
|
rmsnorm: bool = False,
|
||||||
|
scale_mod_only: bool = False,
|
||||||
|
swiglu: bool = False,
|
||||||
|
out_channels: Optional[int] = None,
|
||||||
|
pos_embed_scaling_factor: Optional[float] = None,
|
||||||
|
pos_embed_offset: Optional[float] = None,
|
||||||
|
pos_embed_max_size: Optional[int] = None,
|
||||||
|
num_patches = None,
|
||||||
|
qk_norm: Optional[str] = None,
|
||||||
|
qkv_bias: bool = True,
|
||||||
|
dtype = None,
|
||||||
|
device = None,
|
||||||
|
):
|
||||||
|
super().__init__()
|
||||||
|
self.dtype = dtype
|
||||||
|
self.learn_sigma = learn_sigma
|
||||||
|
self.in_channels = in_channels
|
||||||
|
default_out_channels = in_channels * 2 if learn_sigma else in_channels
|
||||||
|
self.out_channels = out_channels if out_channels is not None else default_out_channels
|
||||||
|
self.patch_size = patch_size
|
||||||
|
self.pos_embed_scaling_factor = pos_embed_scaling_factor
|
||||||
|
self.pos_embed_offset = pos_embed_offset
|
||||||
|
self.pos_embed_max_size = pos_embed_max_size
|
||||||
|
|
||||||
|
# apply magic --> this defines a head_size of 64
|
||||||
|
hidden_size = 64 * depth
|
||||||
|
num_heads = depth
|
||||||
|
|
||||||
|
self.num_heads = num_heads
|
||||||
|
|
||||||
|
self.x_embedder = PatchEmbed(input_size, patch_size, in_channels, hidden_size, bias=True, strict_img_size=self.pos_embed_max_size is None, dtype=dtype, device=device)
|
||||||
|
self.t_embedder = TimestepEmbedder(hidden_size, dtype=dtype, device=device)
|
||||||
|
|
||||||
|
if adm_in_channels is not None:
|
||||||
|
assert isinstance(adm_in_channels, int)
|
||||||
|
self.y_embedder = VectorEmbedder(adm_in_channels, hidden_size, dtype=dtype, device=device)
|
||||||
|
|
||||||
|
self.context_embedder = nn.Identity()
|
||||||
|
if context_embedder_config is not None:
|
||||||
|
if context_embedder_config["target"] == "torch.nn.Linear":
|
||||||
|
self.context_embedder = nn.Linear(**context_embedder_config["params"], dtype=dtype, device=device)
|
||||||
|
|
||||||
|
self.register_length = register_length
|
||||||
|
if self.register_length > 0:
|
||||||
|
self.register = nn.Parameter(torch.randn(1, register_length, hidden_size, dtype=dtype, device=device))
|
||||||
|
|
||||||
|
# num_patches = self.x_embedder.num_patches
|
||||||
|
# Will use fixed sin-cos embedding:
|
||||||
|
# just use a buffer already
|
||||||
|
if num_patches is not None:
|
||||||
|
self.register_buffer(
|
||||||
|
"pos_embed",
|
||||||
|
torch.zeros(1, num_patches, hidden_size, dtype=dtype, device=device),
|
||||||
|
)
|
||||||
|
else:
|
||||||
|
self.pos_embed = None
|
||||||
|
|
||||||
|
self.joint_blocks = nn.ModuleList(
|
||||||
|
[
|
||||||
|
JointBlock(hidden_size, num_heads, mlp_ratio=mlp_ratio, qkv_bias=qkv_bias, attn_mode=attn_mode, pre_only=i == depth - 1, rmsnorm=rmsnorm, scale_mod_only=scale_mod_only, swiglu=swiglu, qk_norm=qk_norm, dtype=dtype, device=device)
|
||||||
|
for i in range(depth)
|
||||||
|
]
|
||||||
|
)
|
||||||
|
|
||||||
|
self.final_layer = FinalLayer(hidden_size, patch_size, self.out_channels, dtype=dtype, device=device)
|
||||||
|
|
||||||
|
def cropped_pos_embed(self, hw):
|
||||||
|
assert self.pos_embed_max_size is not None
|
||||||
|
p = self.x_embedder.patch_size[0]
|
||||||
|
h, w = hw
|
||||||
|
# patched size
|
||||||
|
h = h // p
|
||||||
|
w = w // p
|
||||||
|
assert h <= self.pos_embed_max_size, (h, self.pos_embed_max_size)
|
||||||
|
assert w <= self.pos_embed_max_size, (w, self.pos_embed_max_size)
|
||||||
|
top = (self.pos_embed_max_size - h) // 2
|
||||||
|
left = (self.pos_embed_max_size - w) // 2
|
||||||
|
spatial_pos_embed = rearrange(
|
||||||
|
self.pos_embed,
|
||||||
|
"1 (h w) c -> 1 h w c",
|
||||||
|
h=self.pos_embed_max_size,
|
||||||
|
w=self.pos_embed_max_size,
|
||||||
|
)
|
||||||
|
spatial_pos_embed = spatial_pos_embed[:, top : top + h, left : left + w, :]
|
||||||
|
spatial_pos_embed = rearrange(spatial_pos_embed, "1 h w c -> 1 (h w) c")
|
||||||
|
return spatial_pos_embed
|
||||||
|
|
||||||
|
def unpatchify(self, x, hw=None):
|
||||||
|
"""
|
||||||
|
x: (N, T, patch_size**2 * C)
|
||||||
|
imgs: (N, H, W, C)
|
||||||
|
"""
|
||||||
|
c = self.out_channels
|
||||||
|
p = self.x_embedder.patch_size[0]
|
||||||
|
if hw is None:
|
||||||
|
h = w = int(x.shape[1] ** 0.5)
|
||||||
|
else:
|
||||||
|
h, w = hw
|
||||||
|
h = h // p
|
||||||
|
w = w // p
|
||||||
|
assert h * w == x.shape[1]
|
||||||
|
|
||||||
|
x = x.reshape(shape=(x.shape[0], h, w, p, p, c))
|
||||||
|
x = torch.einsum("nhwpqc->nchpwq", x)
|
||||||
|
imgs = x.reshape(shape=(x.shape[0], c, h * p, w * p))
|
||||||
|
return imgs
|
||||||
|
|
||||||
|
def forward_core_with_concat(self, x: torch.Tensor, c_mod: torch.Tensor, context: Optional[torch.Tensor] = None) -> torch.Tensor:
|
||||||
|
if self.register_length > 0:
|
||||||
|
context = torch.cat((repeat(self.register, "1 ... -> b ...", b=x.shape[0]), context if context is not None else torch.Tensor([]).type_as(x)), 1)
|
||||||
|
|
||||||
|
# context is B, L', D
|
||||||
|
# x is B, L, D
|
||||||
|
for block in self.joint_blocks:
|
||||||
|
context, x = block(context, x, c=c_mod)
|
||||||
|
|
||||||
|
x = self.final_layer(x, c_mod) # (N, T, patch_size ** 2 * out_channels)
|
||||||
|
return x
|
||||||
|
|
||||||
|
def forward(self, x: torch.Tensor, t: torch.Tensor, y: Optional[torch.Tensor] = None, context: Optional[torch.Tensor] = None) -> torch.Tensor:
|
||||||
|
"""
|
||||||
|
Forward pass of DiT.
|
||||||
|
x: (N, C, H, W) tensor of spatial inputs (images or latent representations of images)
|
||||||
|
t: (N,) tensor of diffusion timesteps
|
||||||
|
y: (N,) tensor of class labels
|
||||||
|
"""
|
||||||
|
hw = x.shape[-2:]
|
||||||
|
x = self.x_embedder(x) + self.cropped_pos_embed(hw)
|
||||||
|
c = self.t_embedder(t, dtype=x.dtype) # (N, D)
|
||||||
|
if y is not None:
|
||||||
|
y = self.y_embedder(y) # (N, D)
|
||||||
|
c = c + y # (N, D)
|
||||||
|
|
||||||
|
context = self.context_embedder(context)
|
||||||
|
|
||||||
|
x = self.forward_core_with_concat(x, c, context)
|
||||||
|
|
||||||
|
x = self.unpatchify(x, hw=hw) # (N, out_channels, H, W)
|
||||||
|
return x
|
||||||
@@ -0,0 +1,510 @@
|
|||||||
|
### This file contains impls for underlying related models (CLIP, T5, etc)
|
||||||
|
|
||||||
|
import torch
|
||||||
|
import math
|
||||||
|
from torch import nn
|
||||||
|
from transformers import CLIPTokenizer, T5TokenizerFast
|
||||||
|
|
||||||
|
from modules import sd_hijack
|
||||||
|
|
||||||
|
|
||||||
|
#################################################################################################
|
||||||
|
### Core/Utility
|
||||||
|
#################################################################################################
|
||||||
|
|
||||||
|
|
||||||
|
class AutocastLinear(nn.Linear):
|
||||||
|
"""Same as usual linear layer, but casts its weights to whatever the parameter type is.
|
||||||
|
|
||||||
|
This is different from torch.autocast in a way that float16 layer processing float32 input
|
||||||
|
will return float16 with autocast on, and float32 with this. T5 seems to be fucked
|
||||||
|
if you do it in full float16 (returning almost all zeros in the final output).
|
||||||
|
"""
|
||||||
|
|
||||||
|
def forward(self, x):
|
||||||
|
return torch.nn.functional.linear(x, self.weight.to(x.dtype), self.bias.to(x.dtype) if self.bias is not None else None)
|
||||||
|
|
||||||
|
|
||||||
|
def attention(q, k, v, heads, mask=None):
|
||||||
|
"""Convenience wrapper around a basic attention operation"""
|
||||||
|
b, _, dim_head = q.shape
|
||||||
|
dim_head //= heads
|
||||||
|
q, k, v = [t.view(b, -1, heads, dim_head).transpose(1, 2) for t in (q, k, v)]
|
||||||
|
out = torch.nn.functional.scaled_dot_product_attention(q, k, v, attn_mask=mask, dropout_p=0.0, is_causal=False)
|
||||||
|
return out.transpose(1, 2).reshape(b, -1, heads * dim_head)
|
||||||
|
|
||||||
|
|
||||||
|
class Mlp(nn.Module):
|
||||||
|
""" MLP as used in Vision Transformer, MLP-Mixer and related networks"""
|
||||||
|
def __init__(self, in_features, hidden_features=None, out_features=None, act_layer=nn.GELU, bias=True, dtype=None, device=None):
|
||||||
|
super().__init__()
|
||||||
|
out_features = out_features or in_features
|
||||||
|
hidden_features = hidden_features or in_features
|
||||||
|
|
||||||
|
self.fc1 = nn.Linear(in_features, hidden_features, bias=bias, dtype=dtype, device=device)
|
||||||
|
self.act = act_layer
|
||||||
|
self.fc2 = nn.Linear(hidden_features, out_features, bias=bias, dtype=dtype, device=device)
|
||||||
|
|
||||||
|
def forward(self, x):
|
||||||
|
x = self.fc1(x)
|
||||||
|
x = self.act(x)
|
||||||
|
x = self.fc2(x)
|
||||||
|
return x
|
||||||
|
|
||||||
|
|
||||||
|
#################################################################################################
|
||||||
|
### CLIP
|
||||||
|
#################################################################################################
|
||||||
|
|
||||||
|
|
||||||
|
class CLIPAttention(torch.nn.Module):
|
||||||
|
def __init__(self, embed_dim, heads, dtype, device):
|
||||||
|
super().__init__()
|
||||||
|
self.heads = heads
|
||||||
|
self.q_proj = nn.Linear(embed_dim, embed_dim, bias=True, dtype=dtype, device=device)
|
||||||
|
self.k_proj = nn.Linear(embed_dim, embed_dim, bias=True, dtype=dtype, device=device)
|
||||||
|
self.v_proj = nn.Linear(embed_dim, embed_dim, bias=True, dtype=dtype, device=device)
|
||||||
|
self.out_proj = nn.Linear(embed_dim, embed_dim, bias=True, dtype=dtype, device=device)
|
||||||
|
|
||||||
|
def forward(self, x, mask=None):
|
||||||
|
q = self.q_proj(x)
|
||||||
|
k = self.k_proj(x)
|
||||||
|
v = self.v_proj(x)
|
||||||
|
out = attention(q, k, v, self.heads, mask)
|
||||||
|
return self.out_proj(out)
|
||||||
|
|
||||||
|
|
||||||
|
ACTIVATIONS = {
|
||||||
|
"quick_gelu": lambda a: a * torch.sigmoid(1.702 * a),
|
||||||
|
"gelu": torch.nn.functional.gelu,
|
||||||
|
}
|
||||||
|
|
||||||
|
class CLIPLayer(torch.nn.Module):
|
||||||
|
def __init__(self, embed_dim, heads, intermediate_size, intermediate_activation, dtype, device):
|
||||||
|
super().__init__()
|
||||||
|
self.layer_norm1 = nn.LayerNorm(embed_dim, dtype=dtype, device=device)
|
||||||
|
self.self_attn = CLIPAttention(embed_dim, heads, dtype, device)
|
||||||
|
self.layer_norm2 = nn.LayerNorm(embed_dim, dtype=dtype, device=device)
|
||||||
|
#self.mlp = CLIPMLP(embed_dim, intermediate_size, intermediate_activation, dtype, device)
|
||||||
|
self.mlp = Mlp(embed_dim, intermediate_size, embed_dim, act_layer=ACTIVATIONS[intermediate_activation], dtype=dtype, device=device)
|
||||||
|
|
||||||
|
def forward(self, x, mask=None):
|
||||||
|
x += self.self_attn(self.layer_norm1(x), mask)
|
||||||
|
x += self.mlp(self.layer_norm2(x))
|
||||||
|
return x
|
||||||
|
|
||||||
|
|
||||||
|
class CLIPEncoder(torch.nn.Module):
|
||||||
|
def __init__(self, num_layers, embed_dim, heads, intermediate_size, intermediate_activation, dtype, device):
|
||||||
|
super().__init__()
|
||||||
|
self.layers = torch.nn.ModuleList([CLIPLayer(embed_dim, heads, intermediate_size, intermediate_activation, dtype, device) for i in range(num_layers)])
|
||||||
|
|
||||||
|
def forward(self, x, mask=None, intermediate_output=None):
|
||||||
|
if intermediate_output is not None:
|
||||||
|
if intermediate_output < 0:
|
||||||
|
intermediate_output = len(self.layers) + intermediate_output
|
||||||
|
intermediate = None
|
||||||
|
for i, layer in enumerate(self.layers):
|
||||||
|
x = layer(x, mask)
|
||||||
|
if i == intermediate_output:
|
||||||
|
intermediate = x.clone()
|
||||||
|
return x, intermediate
|
||||||
|
|
||||||
|
|
||||||
|
class CLIPEmbeddings(torch.nn.Module):
|
||||||
|
def __init__(self, embed_dim, vocab_size=49408, num_positions=77, dtype=None, device=None, textual_inversion_key="clip_l"):
|
||||||
|
super().__init__()
|
||||||
|
self.token_embedding = sd_hijack.TextualInversionEmbeddings(vocab_size, embed_dim, dtype=dtype, device=device, textual_inversion_key=textual_inversion_key)
|
||||||
|
self.position_embedding = torch.nn.Embedding(num_positions, embed_dim, dtype=dtype, device=device)
|
||||||
|
|
||||||
|
def forward(self, input_tokens):
|
||||||
|
return self.token_embedding(input_tokens) + self.position_embedding.weight
|
||||||
|
|
||||||
|
|
||||||
|
class CLIPTextModel_(torch.nn.Module):
|
||||||
|
def __init__(self, config_dict, dtype, device):
|
||||||
|
num_layers = config_dict["num_hidden_layers"]
|
||||||
|
embed_dim = config_dict["hidden_size"]
|
||||||
|
heads = config_dict["num_attention_heads"]
|
||||||
|
intermediate_size = config_dict["intermediate_size"]
|
||||||
|
intermediate_activation = config_dict["hidden_act"]
|
||||||
|
super().__init__()
|
||||||
|
self.embeddings = CLIPEmbeddings(embed_dim, dtype=torch.float32, device=device, textual_inversion_key=config_dict.get('textual_inversion_key', 'clip_l'))
|
||||||
|
self.encoder = CLIPEncoder(num_layers, embed_dim, heads, intermediate_size, intermediate_activation, dtype, device)
|
||||||
|
self.final_layer_norm = nn.LayerNorm(embed_dim, dtype=dtype, device=device)
|
||||||
|
|
||||||
|
def forward(self, input_tokens, intermediate_output=None, final_layer_norm_intermediate=True):
|
||||||
|
x = self.embeddings(input_tokens)
|
||||||
|
causal_mask = torch.empty(x.shape[1], x.shape[1], dtype=x.dtype, device=x.device).fill_(float("-inf")).triu_(1)
|
||||||
|
x, i = self.encoder(x, mask=causal_mask, intermediate_output=intermediate_output)
|
||||||
|
x = self.final_layer_norm(x)
|
||||||
|
if i is not None and final_layer_norm_intermediate:
|
||||||
|
i = self.final_layer_norm(i)
|
||||||
|
pooled_output = x[torch.arange(x.shape[0], device=x.device), input_tokens.to(dtype=torch.int, device=x.device).argmax(dim=-1),]
|
||||||
|
return x, i, pooled_output
|
||||||
|
|
||||||
|
|
||||||
|
class CLIPTextModel(torch.nn.Module):
|
||||||
|
def __init__(self, config_dict, dtype, device):
|
||||||
|
super().__init__()
|
||||||
|
self.num_layers = config_dict["num_hidden_layers"]
|
||||||
|
self.text_model = CLIPTextModel_(config_dict, dtype, device)
|
||||||
|
embed_dim = config_dict["hidden_size"]
|
||||||
|
self.text_projection = nn.Linear(embed_dim, embed_dim, bias=False, dtype=dtype, device=device)
|
||||||
|
self.text_projection.weight.copy_(torch.eye(embed_dim))
|
||||||
|
self.dtype = dtype
|
||||||
|
|
||||||
|
def get_input_embeddings(self):
|
||||||
|
return self.text_model.embeddings.token_embedding
|
||||||
|
|
||||||
|
def set_input_embeddings(self, embeddings):
|
||||||
|
self.text_model.embeddings.token_embedding = embeddings
|
||||||
|
|
||||||
|
def forward(self, *args, **kwargs):
|
||||||
|
x = self.text_model(*args, **kwargs)
|
||||||
|
out = self.text_projection(x[2])
|
||||||
|
return (x[0], x[1], out, x[2])
|
||||||
|
|
||||||
|
|
||||||
|
class SDTokenizer:
|
||||||
|
def __init__(self, max_length=77, pad_with_end=True, tokenizer=None, has_start_token=True, pad_to_max_length=True, min_length=None):
|
||||||
|
self.tokenizer = tokenizer
|
||||||
|
self.max_length = max_length
|
||||||
|
self.min_length = min_length
|
||||||
|
empty = self.tokenizer('')["input_ids"]
|
||||||
|
if has_start_token:
|
||||||
|
self.tokens_start = 1
|
||||||
|
self.start_token = empty[0]
|
||||||
|
self.end_token = empty[1]
|
||||||
|
else:
|
||||||
|
self.tokens_start = 0
|
||||||
|
self.start_token = None
|
||||||
|
self.end_token = empty[0]
|
||||||
|
self.pad_with_end = pad_with_end
|
||||||
|
self.pad_to_max_length = pad_to_max_length
|
||||||
|
vocab = self.tokenizer.get_vocab()
|
||||||
|
self.inv_vocab = {v: k for k, v in vocab.items()}
|
||||||
|
self.max_word_length = 8
|
||||||
|
|
||||||
|
|
||||||
|
def tokenize_with_weights(self, text:str):
|
||||||
|
"""Tokenize the text, with weight values - presume 1.0 for all and ignore other features here. The details aren't relevant for a reference impl, and weights themselves has weak effect on SD3."""
|
||||||
|
if self.pad_with_end:
|
||||||
|
pad_token = self.end_token
|
||||||
|
else:
|
||||||
|
pad_token = 0
|
||||||
|
batch = []
|
||||||
|
if self.start_token is not None:
|
||||||
|
batch.append((self.start_token, 1.0))
|
||||||
|
to_tokenize = text.replace("\n", " ").split(' ')
|
||||||
|
to_tokenize = [x for x in to_tokenize if x != ""]
|
||||||
|
for word in to_tokenize:
|
||||||
|
batch.extend([(t, 1) for t in self.tokenizer(word)["input_ids"][self.tokens_start:-1]])
|
||||||
|
batch.append((self.end_token, 1.0))
|
||||||
|
if self.pad_to_max_length:
|
||||||
|
batch.extend([(pad_token, 1.0)] * (self.max_length - len(batch)))
|
||||||
|
if self.min_length is not None and len(batch) < self.min_length:
|
||||||
|
batch.extend([(pad_token, 1.0)] * (self.min_length - len(batch)))
|
||||||
|
return [batch]
|
||||||
|
|
||||||
|
|
||||||
|
class SDXLClipGTokenizer(SDTokenizer):
|
||||||
|
def __init__(self, tokenizer):
|
||||||
|
super().__init__(pad_with_end=False, tokenizer=tokenizer)
|
||||||
|
|
||||||
|
|
||||||
|
class SD3Tokenizer:
|
||||||
|
def __init__(self):
|
||||||
|
clip_tokenizer = CLIPTokenizer.from_pretrained("openai/clip-vit-large-patch14")
|
||||||
|
self.clip_l = SDTokenizer(tokenizer=clip_tokenizer)
|
||||||
|
self.clip_g = SDXLClipGTokenizer(clip_tokenizer)
|
||||||
|
self.t5xxl = T5XXLTokenizer()
|
||||||
|
|
||||||
|
def tokenize_with_weights(self, text:str):
|
||||||
|
out = {}
|
||||||
|
out["g"] = self.clip_g.tokenize_with_weights(text)
|
||||||
|
out["l"] = self.clip_l.tokenize_with_weights(text)
|
||||||
|
out["t5xxl"] = self.t5xxl.tokenize_with_weights(text)
|
||||||
|
return out
|
||||||
|
|
||||||
|
|
||||||
|
class ClipTokenWeightEncoder:
|
||||||
|
def encode_token_weights(self, token_weight_pairs):
|
||||||
|
tokens = [a[0] for a in token_weight_pairs[0]]
|
||||||
|
out, pooled = self([tokens])
|
||||||
|
if pooled is not None:
|
||||||
|
first_pooled = pooled[0:1].cpu()
|
||||||
|
else:
|
||||||
|
first_pooled = pooled
|
||||||
|
output = [out[0:1]]
|
||||||
|
return torch.cat(output, dim=-2).cpu(), first_pooled
|
||||||
|
|
||||||
|
|
||||||
|
class SDClipModel(torch.nn.Module, ClipTokenWeightEncoder):
|
||||||
|
"""Uses the CLIP transformer encoder for text (from huggingface)"""
|
||||||
|
LAYERS = ["last", "pooled", "hidden"]
|
||||||
|
def __init__(self, device="cpu", max_length=77, layer="last", layer_idx=None, textmodel_json_config=None, dtype=None, model_class=CLIPTextModel,
|
||||||
|
special_tokens=None, layer_norm_hidden_state=True, return_projected_pooled=True):
|
||||||
|
super().__init__()
|
||||||
|
assert layer in self.LAYERS
|
||||||
|
self.transformer = model_class(textmodel_json_config, dtype, device)
|
||||||
|
self.num_layers = self.transformer.num_layers
|
||||||
|
self.max_length = max_length
|
||||||
|
self.transformer = self.transformer.eval()
|
||||||
|
for param in self.parameters():
|
||||||
|
param.requires_grad = False
|
||||||
|
self.layer = layer
|
||||||
|
self.layer_idx = None
|
||||||
|
self.special_tokens = special_tokens if special_tokens is not None else {"start": 49406, "end": 49407, "pad": 49407}
|
||||||
|
self.logit_scale = torch.nn.Parameter(torch.tensor(4.6055))
|
||||||
|
self.layer_norm_hidden_state = layer_norm_hidden_state
|
||||||
|
self.return_projected_pooled = return_projected_pooled
|
||||||
|
if layer == "hidden":
|
||||||
|
assert layer_idx is not None
|
||||||
|
assert abs(layer_idx) < self.num_layers
|
||||||
|
self.set_clip_options({"layer": layer_idx})
|
||||||
|
self.options_default = (self.layer, self.layer_idx, self.return_projected_pooled)
|
||||||
|
|
||||||
|
def set_clip_options(self, options):
|
||||||
|
layer_idx = options.get("layer", self.layer_idx)
|
||||||
|
self.return_projected_pooled = options.get("projected_pooled", self.return_projected_pooled)
|
||||||
|
if layer_idx is None or abs(layer_idx) > self.num_layers:
|
||||||
|
self.layer = "last"
|
||||||
|
else:
|
||||||
|
self.layer = "hidden"
|
||||||
|
self.layer_idx = layer_idx
|
||||||
|
|
||||||
|
def forward(self, tokens):
|
||||||
|
backup_embeds = self.transformer.get_input_embeddings()
|
||||||
|
tokens = torch.asarray(tokens, dtype=torch.int64, device=backup_embeds.weight.device)
|
||||||
|
outputs = self.transformer(tokens, intermediate_output=self.layer_idx, final_layer_norm_intermediate=self.layer_norm_hidden_state)
|
||||||
|
self.transformer.set_input_embeddings(backup_embeds)
|
||||||
|
if self.layer == "last":
|
||||||
|
z = outputs[0]
|
||||||
|
else:
|
||||||
|
z = outputs[1]
|
||||||
|
pooled_output = None
|
||||||
|
if len(outputs) >= 3:
|
||||||
|
if not self.return_projected_pooled and len(outputs) >= 4 and outputs[3] is not None:
|
||||||
|
pooled_output = outputs[3].float()
|
||||||
|
elif outputs[2] is not None:
|
||||||
|
pooled_output = outputs[2].float()
|
||||||
|
return z.float(), pooled_output
|
||||||
|
|
||||||
|
|
||||||
|
class SDXLClipG(SDClipModel):
|
||||||
|
"""Wraps the CLIP-G model into the SD-CLIP-Model interface"""
|
||||||
|
def __init__(self, config, device="cpu", layer="penultimate", layer_idx=None, dtype=None):
|
||||||
|
if layer == "penultimate":
|
||||||
|
layer="hidden"
|
||||||
|
layer_idx=-2
|
||||||
|
super().__init__(device=device, layer=layer, layer_idx=layer_idx, textmodel_json_config=config, dtype=dtype, special_tokens={"start": 49406, "end": 49407, "pad": 0}, layer_norm_hidden_state=False)
|
||||||
|
|
||||||
|
|
||||||
|
class T5XXLModel(SDClipModel):
|
||||||
|
"""Wraps the T5-XXL model into the SD-CLIP-Model interface for convenience"""
|
||||||
|
def __init__(self, config, device="cpu", layer="last", layer_idx=None, dtype=None):
|
||||||
|
super().__init__(device=device, layer=layer, layer_idx=layer_idx, textmodel_json_config=config, dtype=dtype, special_tokens={"end": 1, "pad": 0}, model_class=T5)
|
||||||
|
|
||||||
|
|
||||||
|
#################################################################################################
|
||||||
|
### T5 implementation, for the T5-XXL text encoder portion, largely pulled from upstream impl
|
||||||
|
#################################################################################################
|
||||||
|
|
||||||
|
class T5XXLTokenizer(SDTokenizer):
|
||||||
|
"""Wraps the T5 Tokenizer from HF into the SDTokenizer interface"""
|
||||||
|
def __init__(self):
|
||||||
|
super().__init__(pad_with_end=False, tokenizer=T5TokenizerFast.from_pretrained("google/t5-v1_1-xxl"), has_start_token=False, pad_to_max_length=False, max_length=99999999, min_length=77)
|
||||||
|
|
||||||
|
|
||||||
|
class T5LayerNorm(torch.nn.Module):
|
||||||
|
def __init__(self, hidden_size, eps=1e-6, dtype=None, device=None):
|
||||||
|
super().__init__()
|
||||||
|
self.weight = torch.nn.Parameter(torch.ones(hidden_size, dtype=dtype, device=device))
|
||||||
|
self.variance_epsilon = eps
|
||||||
|
|
||||||
|
def forward(self, x):
|
||||||
|
variance = x.pow(2).mean(-1, keepdim=True)
|
||||||
|
x = x * torch.rsqrt(variance + self.variance_epsilon)
|
||||||
|
return self.weight.to(device=x.device, dtype=x.dtype) * x
|
||||||
|
|
||||||
|
|
||||||
|
class T5DenseGatedActDense(torch.nn.Module):
|
||||||
|
def __init__(self, model_dim, ff_dim, dtype, device):
|
||||||
|
super().__init__()
|
||||||
|
self.wi_0 = AutocastLinear(model_dim, ff_dim, bias=False, dtype=dtype, device=device)
|
||||||
|
self.wi_1 = AutocastLinear(model_dim, ff_dim, bias=False, dtype=dtype, device=device)
|
||||||
|
self.wo = AutocastLinear(ff_dim, model_dim, bias=False, dtype=dtype, device=device)
|
||||||
|
|
||||||
|
def forward(self, x):
|
||||||
|
hidden_gelu = torch.nn.functional.gelu(self.wi_0(x), approximate="tanh")
|
||||||
|
hidden_linear = self.wi_1(x)
|
||||||
|
x = hidden_gelu * hidden_linear
|
||||||
|
x = self.wo(x)
|
||||||
|
return x
|
||||||
|
|
||||||
|
|
||||||
|
class T5LayerFF(torch.nn.Module):
|
||||||
|
def __init__(self, model_dim, ff_dim, dtype, device):
|
||||||
|
super().__init__()
|
||||||
|
self.DenseReluDense = T5DenseGatedActDense(model_dim, ff_dim, dtype, device)
|
||||||
|
self.layer_norm = T5LayerNorm(model_dim, dtype=dtype, device=device)
|
||||||
|
|
||||||
|
def forward(self, x):
|
||||||
|
forwarded_states = self.layer_norm(x)
|
||||||
|
forwarded_states = self.DenseReluDense(forwarded_states)
|
||||||
|
x += forwarded_states
|
||||||
|
return x
|
||||||
|
|
||||||
|
|
||||||
|
class T5Attention(torch.nn.Module):
|
||||||
|
def __init__(self, model_dim, inner_dim, num_heads, relative_attention_bias, dtype, device):
|
||||||
|
super().__init__()
|
||||||
|
# Mesh TensorFlow initialization to avoid scaling before softmax
|
||||||
|
self.q = AutocastLinear(model_dim, inner_dim, bias=False, dtype=dtype, device=device)
|
||||||
|
self.k = AutocastLinear(model_dim, inner_dim, bias=False, dtype=dtype, device=device)
|
||||||
|
self.v = AutocastLinear(model_dim, inner_dim, bias=False, dtype=dtype, device=device)
|
||||||
|
self.o = AutocastLinear(inner_dim, model_dim, bias=False, dtype=dtype, device=device)
|
||||||
|
self.num_heads = num_heads
|
||||||
|
self.relative_attention_bias = None
|
||||||
|
if relative_attention_bias:
|
||||||
|
self.relative_attention_num_buckets = 32
|
||||||
|
self.relative_attention_max_distance = 128
|
||||||
|
self.relative_attention_bias = torch.nn.Embedding(self.relative_attention_num_buckets, self.num_heads, device=device)
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
def _relative_position_bucket(relative_position, bidirectional=True, num_buckets=32, max_distance=128):
|
||||||
|
"""
|
||||||
|
Adapted from Mesh Tensorflow:
|
||||||
|
https://github.com/tensorflow/mesh/blob/0cb87fe07da627bf0b7e60475d59f95ed6b5be3d/mesh_tensorflow/transformer/transformer_layers.py#L593
|
||||||
|
|
||||||
|
Translate relative position to a bucket number for relative attention. The relative position is defined as
|
||||||
|
memory_position - query_position, i.e. the distance in tokens from the attending position to the attended-to
|
||||||
|
position. If bidirectional=False, then positive relative positions are invalid. We use smaller buckets for
|
||||||
|
small absolute relative_position and larger buckets for larger absolute relative_positions. All relative
|
||||||
|
positions >=max_distance map to the same bucket. All relative positions <=-max_distance map to the same bucket.
|
||||||
|
This should allow for more graceful generalization to longer sequences than the model has been trained on
|
||||||
|
|
||||||
|
Args:
|
||||||
|
relative_position: an int32 Tensor
|
||||||
|
bidirectional: a boolean - whether the attention is bidirectional
|
||||||
|
num_buckets: an integer
|
||||||
|
max_distance: an integer
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
a Tensor with the same shape as relative_position, containing int32 values in the range [0, num_buckets)
|
||||||
|
"""
|
||||||
|
relative_buckets = 0
|
||||||
|
if bidirectional:
|
||||||
|
num_buckets //= 2
|
||||||
|
relative_buckets += (relative_position > 0).to(torch.long) * num_buckets
|
||||||
|
relative_position = torch.abs(relative_position)
|
||||||
|
else:
|
||||||
|
relative_position = -torch.min(relative_position, torch.zeros_like(relative_position))
|
||||||
|
# now relative_position is in the range [0, inf)
|
||||||
|
# half of the buckets are for exact increments in positions
|
||||||
|
max_exact = num_buckets // 2
|
||||||
|
is_small = relative_position < max_exact
|
||||||
|
# The other half of the buckets are for logarithmically bigger bins in positions up to max_distance
|
||||||
|
relative_position_if_large = max_exact + (
|
||||||
|
torch.log(relative_position.float() / max_exact)
|
||||||
|
/ math.log(max_distance / max_exact)
|
||||||
|
* (num_buckets - max_exact)
|
||||||
|
).to(torch.long)
|
||||||
|
relative_position_if_large = torch.min(relative_position_if_large, torch.full_like(relative_position_if_large, num_buckets - 1))
|
||||||
|
relative_buckets += torch.where(is_small, relative_position, relative_position_if_large)
|
||||||
|
return relative_buckets
|
||||||
|
|
||||||
|
def compute_bias(self, query_length, key_length, device):
|
||||||
|
"""Compute binned relative position bias"""
|
||||||
|
context_position = torch.arange(query_length, dtype=torch.long, device=device)[:, None]
|
||||||
|
memory_position = torch.arange(key_length, dtype=torch.long, device=device)[None, :]
|
||||||
|
relative_position = memory_position - context_position # shape (query_length, key_length)
|
||||||
|
relative_position_bucket = self._relative_position_bucket(
|
||||||
|
relative_position, # shape (query_length, key_length)
|
||||||
|
bidirectional=True,
|
||||||
|
num_buckets=self.relative_attention_num_buckets,
|
||||||
|
max_distance=self.relative_attention_max_distance,
|
||||||
|
)
|
||||||
|
values = self.relative_attention_bias(relative_position_bucket) # shape (query_length, key_length, num_heads)
|
||||||
|
values = values.permute([2, 0, 1]).unsqueeze(0) # shape (1, num_heads, query_length, key_length)
|
||||||
|
return values
|
||||||
|
|
||||||
|
def forward(self, x, past_bias=None):
|
||||||
|
q = self.q(x)
|
||||||
|
k = self.k(x)
|
||||||
|
v = self.v(x)
|
||||||
|
|
||||||
|
if self.relative_attention_bias is not None:
|
||||||
|
past_bias = self.compute_bias(x.shape[1], x.shape[1], x.device)
|
||||||
|
if past_bias is not None:
|
||||||
|
mask = past_bias
|
||||||
|
else:
|
||||||
|
mask = None
|
||||||
|
|
||||||
|
out = attention(q, k * ((k.shape[-1] / self.num_heads) ** 0.5), v, self.num_heads, mask.to(x.dtype) if mask is not None else None)
|
||||||
|
|
||||||
|
return self.o(out), past_bias
|
||||||
|
|
||||||
|
|
||||||
|
class T5LayerSelfAttention(torch.nn.Module):
|
||||||
|
def __init__(self, model_dim, inner_dim, ff_dim, num_heads, relative_attention_bias, dtype, device):
|
||||||
|
super().__init__()
|
||||||
|
self.SelfAttention = T5Attention(model_dim, inner_dim, num_heads, relative_attention_bias, dtype, device)
|
||||||
|
self.layer_norm = T5LayerNorm(model_dim, dtype=dtype, device=device)
|
||||||
|
|
||||||
|
def forward(self, x, past_bias=None):
|
||||||
|
output, past_bias = self.SelfAttention(self.layer_norm(x), past_bias=past_bias)
|
||||||
|
x += output
|
||||||
|
return x, past_bias
|
||||||
|
|
||||||
|
|
||||||
|
class T5Block(torch.nn.Module):
|
||||||
|
def __init__(self, model_dim, inner_dim, ff_dim, num_heads, relative_attention_bias, dtype, device):
|
||||||
|
super().__init__()
|
||||||
|
self.layer = torch.nn.ModuleList()
|
||||||
|
self.layer.append(T5LayerSelfAttention(model_dim, inner_dim, ff_dim, num_heads, relative_attention_bias, dtype, device))
|
||||||
|
self.layer.append(T5LayerFF(model_dim, ff_dim, dtype, device))
|
||||||
|
|
||||||
|
def forward(self, x, past_bias=None):
|
||||||
|
x, past_bias = self.layer[0](x, past_bias)
|
||||||
|
x = self.layer[-1](x)
|
||||||
|
return x, past_bias
|
||||||
|
|
||||||
|
|
||||||
|
class T5Stack(torch.nn.Module):
|
||||||
|
def __init__(self, num_layers, model_dim, inner_dim, ff_dim, num_heads, vocab_size, dtype, device):
|
||||||
|
super().__init__()
|
||||||
|
self.embed_tokens = torch.nn.Embedding(vocab_size, model_dim, device=device)
|
||||||
|
self.block = torch.nn.ModuleList([T5Block(model_dim, inner_dim, ff_dim, num_heads, relative_attention_bias=(i == 0), dtype=dtype, device=device) for i in range(num_layers)])
|
||||||
|
self.final_layer_norm = T5LayerNorm(model_dim, dtype=dtype, device=device)
|
||||||
|
|
||||||
|
def forward(self, input_ids, intermediate_output=None, final_layer_norm_intermediate=True):
|
||||||
|
intermediate = None
|
||||||
|
x = self.embed_tokens(input_ids).to(torch.float32) # needs float32 or else T5 returns all zeroes
|
||||||
|
past_bias = None
|
||||||
|
for i, layer in enumerate(self.block):
|
||||||
|
x, past_bias = layer(x, past_bias)
|
||||||
|
if i == intermediate_output:
|
||||||
|
intermediate = x.clone()
|
||||||
|
x = self.final_layer_norm(x)
|
||||||
|
if intermediate is not None and final_layer_norm_intermediate:
|
||||||
|
intermediate = self.final_layer_norm(intermediate)
|
||||||
|
return x, intermediate
|
||||||
|
|
||||||
|
|
||||||
|
class T5(torch.nn.Module):
|
||||||
|
def __init__(self, config_dict, dtype, device):
|
||||||
|
super().__init__()
|
||||||
|
self.num_layers = config_dict["num_layers"]
|
||||||
|
self.encoder = T5Stack(self.num_layers, config_dict["d_model"], config_dict["d_model"], config_dict["d_ff"], config_dict["num_heads"], config_dict["vocab_size"], dtype, device)
|
||||||
|
self.dtype = dtype
|
||||||
|
|
||||||
|
def get_input_embeddings(self):
|
||||||
|
return self.encoder.embed_tokens
|
||||||
|
|
||||||
|
def set_input_embeddings(self, embeddings):
|
||||||
|
self.encoder.embed_tokens = embeddings
|
||||||
|
|
||||||
|
def forward(self, *args, **kwargs):
|
||||||
|
return self.encoder(*args, **kwargs)
|
||||||
@@ -0,0 +1,222 @@
|
|||||||
|
import os
|
||||||
|
import safetensors
|
||||||
|
import torch
|
||||||
|
import typing
|
||||||
|
|
||||||
|
from transformers import CLIPTokenizer, T5TokenizerFast
|
||||||
|
|
||||||
|
from modules import shared, devices, modelloader, sd_hijack_clip, prompt_parser
|
||||||
|
from modules.models.sd3.other_impls import SDClipModel, SDXLClipG, T5XXLModel, SD3Tokenizer
|
||||||
|
|
||||||
|
|
||||||
|
class SafetensorsMapping(typing.Mapping):
|
||||||
|
def __init__(self, file):
|
||||||
|
self.file = file
|
||||||
|
|
||||||
|
def __len__(self):
|
||||||
|
return len(self.file.keys())
|
||||||
|
|
||||||
|
def __iter__(self):
|
||||||
|
for key in self.file.keys():
|
||||||
|
yield key
|
||||||
|
|
||||||
|
def __getitem__(self, key):
|
||||||
|
return self.file.get_tensor(key)
|
||||||
|
|
||||||
|
|
||||||
|
CLIPL_URL = f"{shared.hf_endpoint}/AUTOMATIC/stable-diffusion-3-medium-text-encoders/resolve/main/clip_l.safetensors"
|
||||||
|
CLIPL_CONFIG = {
|
||||||
|
"hidden_act": "quick_gelu",
|
||||||
|
"hidden_size": 768,
|
||||||
|
"intermediate_size": 3072,
|
||||||
|
"num_attention_heads": 12,
|
||||||
|
"num_hidden_layers": 12,
|
||||||
|
}
|
||||||
|
|
||||||
|
CLIPG_URL = f"{shared.hf_endpoint}/AUTOMATIC/stable-diffusion-3-medium-text-encoders/resolve/main/clip_g.safetensors"
|
||||||
|
CLIPG_CONFIG = {
|
||||||
|
"hidden_act": "gelu",
|
||||||
|
"hidden_size": 1280,
|
||||||
|
"intermediate_size": 5120,
|
||||||
|
"num_attention_heads": 20,
|
||||||
|
"num_hidden_layers": 32,
|
||||||
|
"textual_inversion_key": "clip_g",
|
||||||
|
}
|
||||||
|
|
||||||
|
T5_URL = f"{shared.hf_endpoint}/AUTOMATIC/stable-diffusion-3-medium-text-encoders/resolve/main/t5xxl_fp16.safetensors"
|
||||||
|
T5_CONFIG = {
|
||||||
|
"d_ff": 10240,
|
||||||
|
"d_model": 4096,
|
||||||
|
"num_heads": 64,
|
||||||
|
"num_layers": 24,
|
||||||
|
"vocab_size": 32128,
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
class Sd3ClipLG(sd_hijack_clip.TextConditionalModel):
|
||||||
|
def __init__(self, clip_l, clip_g):
|
||||||
|
super().__init__()
|
||||||
|
|
||||||
|
self.clip_l = clip_l
|
||||||
|
self.clip_g = clip_g
|
||||||
|
|
||||||
|
self.tokenizer = CLIPTokenizer.from_pretrained("openai/clip-vit-large-patch14")
|
||||||
|
|
||||||
|
empty = self.tokenizer('')["input_ids"]
|
||||||
|
self.id_start = empty[0]
|
||||||
|
self.id_end = empty[1]
|
||||||
|
self.id_pad = empty[1]
|
||||||
|
|
||||||
|
self.return_pooled = True
|
||||||
|
|
||||||
|
def tokenize(self, texts):
|
||||||
|
return self.tokenizer(texts, truncation=False, add_special_tokens=False)["input_ids"]
|
||||||
|
|
||||||
|
def encode_with_transformers(self, tokens):
|
||||||
|
tokens_g = tokens.clone()
|
||||||
|
|
||||||
|
for batch_pos in range(tokens_g.shape[0]):
|
||||||
|
index = tokens_g[batch_pos].cpu().tolist().index(self.id_end)
|
||||||
|
tokens_g[batch_pos, index+1:tokens_g.shape[1]] = 0
|
||||||
|
|
||||||
|
l_out, l_pooled = self.clip_l(tokens)
|
||||||
|
g_out, g_pooled = self.clip_g(tokens_g)
|
||||||
|
|
||||||
|
lg_out = torch.cat([l_out, g_out], dim=-1)
|
||||||
|
lg_out = torch.nn.functional.pad(lg_out, (0, 4096 - lg_out.shape[-1]))
|
||||||
|
|
||||||
|
vector_out = torch.cat((l_pooled, g_pooled), dim=-1)
|
||||||
|
|
||||||
|
lg_out.pooled = vector_out
|
||||||
|
return lg_out
|
||||||
|
|
||||||
|
def encode_embedding_init_text(self, init_text, nvpt):
|
||||||
|
return torch.zeros((nvpt, 768+1280), device=devices.device) # XXX
|
||||||
|
|
||||||
|
|
||||||
|
class Sd3T5(torch.nn.Module):
|
||||||
|
def __init__(self, t5xxl):
|
||||||
|
super().__init__()
|
||||||
|
|
||||||
|
self.t5xxl = t5xxl
|
||||||
|
self.tokenizer = T5TokenizerFast.from_pretrained("google/t5-v1_1-xxl")
|
||||||
|
|
||||||
|
empty = self.tokenizer('', padding='max_length', max_length=2)["input_ids"]
|
||||||
|
self.id_end = empty[0]
|
||||||
|
self.id_pad = empty[1]
|
||||||
|
|
||||||
|
def tokenize(self, texts):
|
||||||
|
return self.tokenizer(texts, truncation=False, add_special_tokens=False)["input_ids"]
|
||||||
|
|
||||||
|
def tokenize_line(self, line, *, target_token_count=None):
|
||||||
|
if shared.opts.emphasis != "None":
|
||||||
|
parsed = prompt_parser.parse_prompt_attention(line)
|
||||||
|
else:
|
||||||
|
parsed = [[line, 1.0]]
|
||||||
|
|
||||||
|
tokenized = self.tokenize([text for text, _ in parsed])
|
||||||
|
|
||||||
|
tokens = []
|
||||||
|
multipliers = []
|
||||||
|
|
||||||
|
for text_tokens, (text, weight) in zip(tokenized, parsed):
|
||||||
|
if text == 'BREAK' and weight == -1:
|
||||||
|
continue
|
||||||
|
|
||||||
|
tokens += text_tokens
|
||||||
|
multipliers += [weight] * len(text_tokens)
|
||||||
|
|
||||||
|
tokens += [self.id_end]
|
||||||
|
multipliers += [1.0]
|
||||||
|
|
||||||
|
if target_token_count is not None:
|
||||||
|
if len(tokens) < target_token_count:
|
||||||
|
tokens += [self.id_pad] * (target_token_count - len(tokens))
|
||||||
|
multipliers += [1.0] * (target_token_count - len(tokens))
|
||||||
|
else:
|
||||||
|
tokens = tokens[0:target_token_count]
|
||||||
|
multipliers = multipliers[0:target_token_count]
|
||||||
|
|
||||||
|
return tokens, multipliers
|
||||||
|
|
||||||
|
def forward(self, texts, *, token_count):
|
||||||
|
if not self.t5xxl or not shared.opts.sd3_enable_t5:
|
||||||
|
return torch.zeros((len(texts), token_count, 4096), device=devices.device, dtype=devices.dtype)
|
||||||
|
|
||||||
|
tokens_batch = []
|
||||||
|
|
||||||
|
for text in texts:
|
||||||
|
tokens, multipliers = self.tokenize_line(text, target_token_count=token_count)
|
||||||
|
tokens_batch.append(tokens)
|
||||||
|
|
||||||
|
t5_out, t5_pooled = self.t5xxl(tokens_batch)
|
||||||
|
|
||||||
|
return t5_out
|
||||||
|
|
||||||
|
def encode_embedding_init_text(self, init_text, nvpt):
|
||||||
|
return torch.zeros((nvpt, 4096), device=devices.device) # XXX
|
||||||
|
|
||||||
|
|
||||||
|
class SD3Cond(torch.nn.Module):
|
||||||
|
def __init__(self, *args, **kwargs):
|
||||||
|
super().__init__(*args, **kwargs)
|
||||||
|
|
||||||
|
self.tokenizer = SD3Tokenizer()
|
||||||
|
|
||||||
|
with torch.no_grad():
|
||||||
|
self.clip_g = SDXLClipG(CLIPG_CONFIG, device="cpu", dtype=devices.dtype)
|
||||||
|
self.clip_l = SDClipModel(layer="hidden", layer_idx=-2, device="cpu", dtype=devices.dtype, layer_norm_hidden_state=False, return_projected_pooled=False, textmodel_json_config=CLIPL_CONFIG)
|
||||||
|
|
||||||
|
if shared.opts.sd3_enable_t5:
|
||||||
|
self.t5xxl = T5XXLModel(T5_CONFIG, device="cpu", dtype=devices.dtype)
|
||||||
|
else:
|
||||||
|
self.t5xxl = None
|
||||||
|
|
||||||
|
self.model_lg = Sd3ClipLG(self.clip_l, self.clip_g)
|
||||||
|
self.model_t5 = Sd3T5(self.t5xxl)
|
||||||
|
|
||||||
|
def forward(self, prompts: list[str]):
|
||||||
|
with devices.without_autocast():
|
||||||
|
lg_out, vector_out = self.model_lg(prompts)
|
||||||
|
t5_out = self.model_t5(prompts, token_count=lg_out.shape[1])
|
||||||
|
lgt_out = torch.cat([lg_out, t5_out], dim=-2)
|
||||||
|
|
||||||
|
return {
|
||||||
|
'crossattn': lgt_out,
|
||||||
|
'vector': vector_out,
|
||||||
|
}
|
||||||
|
|
||||||
|
def before_load_weights(self, state_dict):
|
||||||
|
clip_path = os.path.join(shared.models_path, "CLIP")
|
||||||
|
|
||||||
|
if 'text_encoders.clip_g.transformer.text_model.embeddings.position_embedding.weight' not in state_dict:
|
||||||
|
clip_g_file = modelloader.load_file_from_url(CLIPG_URL, model_dir=clip_path, file_name="clip_g.safetensors")
|
||||||
|
with safetensors.safe_open(clip_g_file, framework="pt") as file:
|
||||||
|
self.clip_g.transformer.load_state_dict(SafetensorsMapping(file))
|
||||||
|
|
||||||
|
if 'text_encoders.clip_l.transformer.text_model.embeddings.position_embedding.weight' not in state_dict:
|
||||||
|
clip_l_file = modelloader.load_file_from_url(CLIPL_URL, model_dir=clip_path, file_name="clip_l.safetensors")
|
||||||
|
with safetensors.safe_open(clip_l_file, framework="pt") as file:
|
||||||
|
self.clip_l.transformer.load_state_dict(SafetensorsMapping(file), strict=False)
|
||||||
|
|
||||||
|
if self.t5xxl and 'text_encoders.t5xxl.transformer.encoder.embed_tokens.weight' not in state_dict:
|
||||||
|
t5_file = modelloader.load_file_from_url(T5_URL, model_dir=clip_path, file_name="t5xxl_fp16.safetensors")
|
||||||
|
with safetensors.safe_open(t5_file, framework="pt") as file:
|
||||||
|
self.t5xxl.transformer.load_state_dict(SafetensorsMapping(file), strict=False)
|
||||||
|
|
||||||
|
def encode_embedding_init_text(self, init_text, nvpt):
|
||||||
|
return self.model_lg.encode_embedding_init_text(init_text, nvpt)
|
||||||
|
|
||||||
|
def tokenize(self, texts):
|
||||||
|
return self.model_lg.tokenize(texts)
|
||||||
|
|
||||||
|
def medvram_modules(self):
|
||||||
|
return [self.clip_g, self.clip_l, self.t5xxl]
|
||||||
|
|
||||||
|
def get_token_count(self, text):
|
||||||
|
_, token_count = self.model_lg.process_texts([text])
|
||||||
|
|
||||||
|
return token_count
|
||||||
|
|
||||||
|
def get_target_prompt_token_count(self, token_count):
|
||||||
|
return self.model_lg.get_target_prompt_token_count(token_count)
|
||||||
@@ -0,0 +1,374 @@
|
|||||||
|
### Impls of the SD3 core diffusion model and VAE
|
||||||
|
|
||||||
|
import torch
|
||||||
|
import math
|
||||||
|
import einops
|
||||||
|
from modules.models.sd3.mmdit import MMDiT
|
||||||
|
from PIL import Image
|
||||||
|
|
||||||
|
|
||||||
|
#################################################################################################
|
||||||
|
### MMDiT Model Wrapping
|
||||||
|
#################################################################################################
|
||||||
|
|
||||||
|
|
||||||
|
class ModelSamplingDiscreteFlow(torch.nn.Module):
|
||||||
|
"""Helper for sampler scheduling (ie timestep/sigma calculations) for Discrete Flow models"""
|
||||||
|
def __init__(self, shift=1.0):
|
||||||
|
super().__init__()
|
||||||
|
self.shift = shift
|
||||||
|
timesteps = 1000
|
||||||
|
ts = self.sigma(torch.arange(1, timesteps + 1, 1))
|
||||||
|
self.register_buffer('sigmas', ts)
|
||||||
|
|
||||||
|
@property
|
||||||
|
def sigma_min(self):
|
||||||
|
return self.sigmas[0]
|
||||||
|
|
||||||
|
@property
|
||||||
|
def sigma_max(self):
|
||||||
|
return self.sigmas[-1]
|
||||||
|
|
||||||
|
def timestep(self, sigma):
|
||||||
|
return sigma * 1000
|
||||||
|
|
||||||
|
def sigma(self, timestep: torch.Tensor):
|
||||||
|
timestep = timestep / 1000.0
|
||||||
|
if self.shift == 1.0:
|
||||||
|
return timestep
|
||||||
|
return self.shift * timestep / (1 + (self.shift - 1) * timestep)
|
||||||
|
|
||||||
|
def calculate_denoised(self, sigma, model_output, model_input):
|
||||||
|
sigma = sigma.view(sigma.shape[:1] + (1,) * (model_output.ndim - 1))
|
||||||
|
return model_input - model_output * sigma
|
||||||
|
|
||||||
|
def noise_scaling(self, sigma, noise, latent_image, max_denoise=False):
|
||||||
|
return sigma * noise + (1.0 - sigma) * latent_image
|
||||||
|
|
||||||
|
|
||||||
|
class BaseModel(torch.nn.Module):
|
||||||
|
"""Wrapper around the core MM-DiT model"""
|
||||||
|
def __init__(self, shift=1.0, device=None, dtype=torch.float32, state_dict=None, prefix=""):
|
||||||
|
super().__init__()
|
||||||
|
# Important configuration values can be quickly determined by checking shapes in the source file
|
||||||
|
# Some of these will vary between models (eg 2B vs 8B primarily differ in their depth, but also other details change)
|
||||||
|
patch_size = state_dict[f"{prefix}x_embedder.proj.weight"].shape[2]
|
||||||
|
depth = state_dict[f"{prefix}x_embedder.proj.weight"].shape[0] // 64
|
||||||
|
num_patches = state_dict[f"{prefix}pos_embed"].shape[1]
|
||||||
|
pos_embed_max_size = round(math.sqrt(num_patches))
|
||||||
|
adm_in_channels = state_dict[f"{prefix}y_embedder.mlp.0.weight"].shape[1]
|
||||||
|
context_shape = state_dict[f"{prefix}context_embedder.weight"].shape
|
||||||
|
context_embedder_config = {
|
||||||
|
"target": "torch.nn.Linear",
|
||||||
|
"params": {
|
||||||
|
"in_features": context_shape[1],
|
||||||
|
"out_features": context_shape[0]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
self.diffusion_model = MMDiT(input_size=None, pos_embed_scaling_factor=None, pos_embed_offset=None, pos_embed_max_size=pos_embed_max_size, patch_size=patch_size, in_channels=16, depth=depth, num_patches=num_patches, adm_in_channels=adm_in_channels, context_embedder_config=context_embedder_config, device=device, dtype=dtype)
|
||||||
|
self.model_sampling = ModelSamplingDiscreteFlow(shift=shift)
|
||||||
|
self.depth = depth
|
||||||
|
|
||||||
|
def apply_model(self, x, sigma, c_crossattn=None, y=None):
|
||||||
|
dtype = self.get_dtype()
|
||||||
|
timestep = self.model_sampling.timestep(sigma).float()
|
||||||
|
model_output = self.diffusion_model(x.to(dtype), timestep, context=c_crossattn.to(dtype), y=y.to(dtype)).float()
|
||||||
|
return self.model_sampling.calculate_denoised(sigma, model_output, x)
|
||||||
|
|
||||||
|
def forward(self, *args, **kwargs):
|
||||||
|
return self.apply_model(*args, **kwargs)
|
||||||
|
|
||||||
|
def get_dtype(self):
|
||||||
|
return self.diffusion_model.dtype
|
||||||
|
|
||||||
|
|
||||||
|
class CFGDenoiser(torch.nn.Module):
|
||||||
|
"""Helper for applying CFG Scaling to diffusion outputs"""
|
||||||
|
def __init__(self, model):
|
||||||
|
super().__init__()
|
||||||
|
self.model = model
|
||||||
|
|
||||||
|
def forward(self, x, timestep, cond, uncond, cond_scale):
|
||||||
|
# Run cond and uncond in a batch together
|
||||||
|
batched = self.model.apply_model(torch.cat([x, x]), torch.cat([timestep, timestep]), c_crossattn=torch.cat([cond["c_crossattn"], uncond["c_crossattn"]]), y=torch.cat([cond["y"], uncond["y"]]))
|
||||||
|
# Then split and apply CFG Scaling
|
||||||
|
pos_out, neg_out = batched.chunk(2)
|
||||||
|
scaled = neg_out + (pos_out - neg_out) * cond_scale
|
||||||
|
return scaled
|
||||||
|
|
||||||
|
|
||||||
|
class SD3LatentFormat:
|
||||||
|
"""Latents are slightly shifted from center - this class must be called after VAE Decode to correct for the shift"""
|
||||||
|
def __init__(self):
|
||||||
|
self.scale_factor = 1.5305
|
||||||
|
self.shift_factor = 0.0609
|
||||||
|
|
||||||
|
def process_in(self, latent):
|
||||||
|
return (latent - self.shift_factor) * self.scale_factor
|
||||||
|
|
||||||
|
def process_out(self, latent):
|
||||||
|
return (latent / self.scale_factor) + self.shift_factor
|
||||||
|
|
||||||
|
def decode_latent_to_preview(self, x0):
|
||||||
|
"""Quick RGB approximate preview of sd3 latents"""
|
||||||
|
factors = torch.tensor([
|
||||||
|
[-0.0645, 0.0177, 0.1052], [ 0.0028, 0.0312, 0.0650],
|
||||||
|
[ 0.1848, 0.0762, 0.0360], [ 0.0944, 0.0360, 0.0889],
|
||||||
|
[ 0.0897, 0.0506, -0.0364], [-0.0020, 0.1203, 0.0284],
|
||||||
|
[ 0.0855, 0.0118, 0.0283], [-0.0539, 0.0658, 0.1047],
|
||||||
|
[-0.0057, 0.0116, 0.0700], [-0.0412, 0.0281, -0.0039],
|
||||||
|
[ 0.1106, 0.1171, 0.1220], [-0.0248, 0.0682, -0.0481],
|
||||||
|
[ 0.0815, 0.0846, 0.1207], [-0.0120, -0.0055, -0.0867],
|
||||||
|
[-0.0749, -0.0634, -0.0456], [-0.1418, -0.1457, -0.1259]
|
||||||
|
], device="cpu")
|
||||||
|
latent_image = x0[0].permute(1, 2, 0).cpu() @ factors
|
||||||
|
|
||||||
|
latents_ubyte = (((latent_image + 1) / 2)
|
||||||
|
.clamp(0, 1) # change scale from -1..1 to 0..1
|
||||||
|
.mul(0xFF) # to 0..255
|
||||||
|
.byte()).cpu()
|
||||||
|
|
||||||
|
return Image.fromarray(latents_ubyte.numpy())
|
||||||
|
|
||||||
|
|
||||||
|
#################################################################################################
|
||||||
|
### K-Diffusion Sampling
|
||||||
|
#################################################################################################
|
||||||
|
|
||||||
|
|
||||||
|
def append_dims(x, target_dims):
|
||||||
|
"""Appends dimensions to the end of a tensor until it has target_dims dimensions."""
|
||||||
|
dims_to_append = target_dims - x.ndim
|
||||||
|
return x[(...,) + (None,) * dims_to_append]
|
||||||
|
|
||||||
|
|
||||||
|
def to_d(x, sigma, denoised):
|
||||||
|
"""Converts a denoiser output to a Karras ODE derivative."""
|
||||||
|
return (x - denoised) / append_dims(sigma, x.ndim)
|
||||||
|
|
||||||
|
|
||||||
|
@torch.no_grad()
|
||||||
|
@torch.autocast("cuda", dtype=torch.float16)
|
||||||
|
def sample_euler(model, x, sigmas, extra_args=None):
|
||||||
|
"""Implements Algorithm 2 (Euler steps) from Karras et al. (2022)."""
|
||||||
|
extra_args = {} if extra_args is None else extra_args
|
||||||
|
s_in = x.new_ones([x.shape[0]])
|
||||||
|
for i in range(len(sigmas) - 1):
|
||||||
|
sigma_hat = sigmas[i]
|
||||||
|
denoised = model(x, sigma_hat * s_in, **extra_args)
|
||||||
|
d = to_d(x, sigma_hat, denoised)
|
||||||
|
dt = sigmas[i + 1] - sigma_hat
|
||||||
|
# Euler method
|
||||||
|
x = x + d * dt
|
||||||
|
return x
|
||||||
|
|
||||||
|
|
||||||
|
#################################################################################################
|
||||||
|
### VAE
|
||||||
|
#################################################################################################
|
||||||
|
|
||||||
|
|
||||||
|
def Normalize(in_channels, num_groups=32, dtype=torch.float32, device=None):
|
||||||
|
return torch.nn.GroupNorm(num_groups=num_groups, num_channels=in_channels, eps=1e-6, affine=True, dtype=dtype, device=device)
|
||||||
|
|
||||||
|
|
||||||
|
class ResnetBlock(torch.nn.Module):
|
||||||
|
def __init__(self, *, in_channels, out_channels=None, dtype=torch.float32, device=None):
|
||||||
|
super().__init__()
|
||||||
|
self.in_channels = in_channels
|
||||||
|
out_channels = in_channels if out_channels is None else out_channels
|
||||||
|
self.out_channels = out_channels
|
||||||
|
|
||||||
|
self.norm1 = Normalize(in_channels, dtype=dtype, device=device)
|
||||||
|
self.conv1 = torch.nn.Conv2d(in_channels, out_channels, kernel_size=3, stride=1, padding=1, dtype=dtype, device=device)
|
||||||
|
self.norm2 = Normalize(out_channels, dtype=dtype, device=device)
|
||||||
|
self.conv2 = torch.nn.Conv2d(out_channels, out_channels, kernel_size=3, stride=1, padding=1, dtype=dtype, device=device)
|
||||||
|
if self.in_channels != self.out_channels:
|
||||||
|
self.nin_shortcut = torch.nn.Conv2d(in_channels, out_channels, kernel_size=1, stride=1, padding=0, dtype=dtype, device=device)
|
||||||
|
else:
|
||||||
|
self.nin_shortcut = None
|
||||||
|
self.swish = torch.nn.SiLU(inplace=True)
|
||||||
|
|
||||||
|
def forward(self, x):
|
||||||
|
hidden = x
|
||||||
|
hidden = self.norm1(hidden)
|
||||||
|
hidden = self.swish(hidden)
|
||||||
|
hidden = self.conv1(hidden)
|
||||||
|
hidden = self.norm2(hidden)
|
||||||
|
hidden = self.swish(hidden)
|
||||||
|
hidden = self.conv2(hidden)
|
||||||
|
if self.in_channels != self.out_channels:
|
||||||
|
x = self.nin_shortcut(x)
|
||||||
|
return x + hidden
|
||||||
|
|
||||||
|
|
||||||
|
class AttnBlock(torch.nn.Module):
|
||||||
|
def __init__(self, in_channels, dtype=torch.float32, device=None):
|
||||||
|
super().__init__()
|
||||||
|
self.norm = Normalize(in_channels, dtype=dtype, device=device)
|
||||||
|
self.q = torch.nn.Conv2d(in_channels, in_channels, kernel_size=1, stride=1, padding=0, dtype=dtype, device=device)
|
||||||
|
self.k = torch.nn.Conv2d(in_channels, in_channels, kernel_size=1, stride=1, padding=0, dtype=dtype, device=device)
|
||||||
|
self.v = torch.nn.Conv2d(in_channels, in_channels, kernel_size=1, stride=1, padding=0, dtype=dtype, device=device)
|
||||||
|
self.proj_out = torch.nn.Conv2d(in_channels, in_channels, kernel_size=1, stride=1, padding=0, dtype=dtype, device=device)
|
||||||
|
|
||||||
|
def forward(self, x):
|
||||||
|
hidden = self.norm(x)
|
||||||
|
q = self.q(hidden)
|
||||||
|
k = self.k(hidden)
|
||||||
|
v = self.v(hidden)
|
||||||
|
b, c, h, w = q.shape
|
||||||
|
q, k, v = [einops.rearrange(x, "b c h w -> b 1 (h w) c").contiguous() for x in (q, k, v)]
|
||||||
|
hidden = torch.nn.functional.scaled_dot_product_attention(q, k, v) # scale is dim ** -0.5 per default
|
||||||
|
hidden = einops.rearrange(hidden, "b 1 (h w) c -> b c h w", h=h, w=w, c=c, b=b)
|
||||||
|
hidden = self.proj_out(hidden)
|
||||||
|
return x + hidden
|
||||||
|
|
||||||
|
|
||||||
|
class Downsample(torch.nn.Module):
|
||||||
|
def __init__(self, in_channels, dtype=torch.float32, device=None):
|
||||||
|
super().__init__()
|
||||||
|
self.conv = torch.nn.Conv2d(in_channels, in_channels, kernel_size=3, stride=2, padding=0, dtype=dtype, device=device)
|
||||||
|
|
||||||
|
def forward(self, x):
|
||||||
|
pad = (0,1,0,1)
|
||||||
|
x = torch.nn.functional.pad(x, pad, mode="constant", value=0)
|
||||||
|
x = self.conv(x)
|
||||||
|
return x
|
||||||
|
|
||||||
|
|
||||||
|
class Upsample(torch.nn.Module):
|
||||||
|
def __init__(self, in_channels, dtype=torch.float32, device=None):
|
||||||
|
super().__init__()
|
||||||
|
self.conv = torch.nn.Conv2d(in_channels, in_channels, kernel_size=3, stride=1, padding=1, dtype=dtype, device=device)
|
||||||
|
|
||||||
|
def forward(self, x):
|
||||||
|
x = torch.nn.functional.interpolate(x, scale_factor=2.0, mode="nearest")
|
||||||
|
x = self.conv(x)
|
||||||
|
return x
|
||||||
|
|
||||||
|
|
||||||
|
class VAEEncoder(torch.nn.Module):
|
||||||
|
def __init__(self, ch=128, ch_mult=(1,2,4,4), num_res_blocks=2, in_channels=3, z_channels=16, dtype=torch.float32, device=None):
|
||||||
|
super().__init__()
|
||||||
|
self.num_resolutions = len(ch_mult)
|
||||||
|
self.num_res_blocks = num_res_blocks
|
||||||
|
# downsampling
|
||||||
|
self.conv_in = torch.nn.Conv2d(in_channels, ch, kernel_size=3, stride=1, padding=1, dtype=dtype, device=device)
|
||||||
|
in_ch_mult = (1,) + tuple(ch_mult)
|
||||||
|
self.in_ch_mult = in_ch_mult
|
||||||
|
self.down = torch.nn.ModuleList()
|
||||||
|
for i_level in range(self.num_resolutions):
|
||||||
|
block = torch.nn.ModuleList()
|
||||||
|
attn = torch.nn.ModuleList()
|
||||||
|
block_in = ch*in_ch_mult[i_level]
|
||||||
|
block_out = ch*ch_mult[i_level]
|
||||||
|
for _ in range(num_res_blocks):
|
||||||
|
block.append(ResnetBlock(in_channels=block_in, out_channels=block_out, dtype=dtype, device=device))
|
||||||
|
block_in = block_out
|
||||||
|
down = torch.nn.Module()
|
||||||
|
down.block = block
|
||||||
|
down.attn = attn
|
||||||
|
if i_level != self.num_resolutions - 1:
|
||||||
|
down.downsample = Downsample(block_in, dtype=dtype, device=device)
|
||||||
|
self.down.append(down)
|
||||||
|
# middle
|
||||||
|
self.mid = torch.nn.Module()
|
||||||
|
self.mid.block_1 = ResnetBlock(in_channels=block_in, out_channels=block_in, dtype=dtype, device=device)
|
||||||
|
self.mid.attn_1 = AttnBlock(block_in, dtype=dtype, device=device)
|
||||||
|
self.mid.block_2 = ResnetBlock(in_channels=block_in, out_channels=block_in, dtype=dtype, device=device)
|
||||||
|
# end
|
||||||
|
self.norm_out = Normalize(block_in, dtype=dtype, device=device)
|
||||||
|
self.conv_out = torch.nn.Conv2d(block_in, 2 * z_channels, kernel_size=3, stride=1, padding=1, dtype=dtype, device=device)
|
||||||
|
self.swish = torch.nn.SiLU(inplace=True)
|
||||||
|
|
||||||
|
def forward(self, x):
|
||||||
|
# downsampling
|
||||||
|
hs = [self.conv_in(x)]
|
||||||
|
for i_level in range(self.num_resolutions):
|
||||||
|
for i_block in range(self.num_res_blocks):
|
||||||
|
h = self.down[i_level].block[i_block](hs[-1])
|
||||||
|
hs.append(h)
|
||||||
|
if i_level != self.num_resolutions-1:
|
||||||
|
hs.append(self.down[i_level].downsample(hs[-1]))
|
||||||
|
# middle
|
||||||
|
h = hs[-1]
|
||||||
|
h = self.mid.block_1(h)
|
||||||
|
h = self.mid.attn_1(h)
|
||||||
|
h = self.mid.block_2(h)
|
||||||
|
# end
|
||||||
|
h = self.norm_out(h)
|
||||||
|
h = self.swish(h)
|
||||||
|
h = self.conv_out(h)
|
||||||
|
return h
|
||||||
|
|
||||||
|
|
||||||
|
class VAEDecoder(torch.nn.Module):
|
||||||
|
def __init__(self, ch=128, out_ch=3, ch_mult=(1, 2, 4, 4), num_res_blocks=2, resolution=256, z_channels=16, dtype=torch.float32, device=None):
|
||||||
|
super().__init__()
|
||||||
|
self.num_resolutions = len(ch_mult)
|
||||||
|
self.num_res_blocks = num_res_blocks
|
||||||
|
block_in = ch * ch_mult[self.num_resolutions - 1]
|
||||||
|
curr_res = resolution // 2 ** (self.num_resolutions - 1)
|
||||||
|
# z to block_in
|
||||||
|
self.conv_in = torch.nn.Conv2d(z_channels, block_in, kernel_size=3, stride=1, padding=1, dtype=dtype, device=device)
|
||||||
|
# middle
|
||||||
|
self.mid = torch.nn.Module()
|
||||||
|
self.mid.block_1 = ResnetBlock(in_channels=block_in, out_channels=block_in, dtype=dtype, device=device)
|
||||||
|
self.mid.attn_1 = AttnBlock(block_in, dtype=dtype, device=device)
|
||||||
|
self.mid.block_2 = ResnetBlock(in_channels=block_in, out_channels=block_in, dtype=dtype, device=device)
|
||||||
|
# upsampling
|
||||||
|
self.up = torch.nn.ModuleList()
|
||||||
|
for i_level in reversed(range(self.num_resolutions)):
|
||||||
|
block = torch.nn.ModuleList()
|
||||||
|
block_out = ch * ch_mult[i_level]
|
||||||
|
for _ in range(self.num_res_blocks + 1):
|
||||||
|
block.append(ResnetBlock(in_channels=block_in, out_channels=block_out, dtype=dtype, device=device))
|
||||||
|
block_in = block_out
|
||||||
|
up = torch.nn.Module()
|
||||||
|
up.block = block
|
||||||
|
if i_level != 0:
|
||||||
|
up.upsample = Upsample(block_in, dtype=dtype, device=device)
|
||||||
|
curr_res = curr_res * 2
|
||||||
|
self.up.insert(0, up) # prepend to get consistent order
|
||||||
|
# end
|
||||||
|
self.norm_out = Normalize(block_in, dtype=dtype, device=device)
|
||||||
|
self.conv_out = torch.nn.Conv2d(block_in, out_ch, kernel_size=3, stride=1, padding=1, dtype=dtype, device=device)
|
||||||
|
self.swish = torch.nn.SiLU(inplace=True)
|
||||||
|
|
||||||
|
def forward(self, z):
|
||||||
|
# z to block_in
|
||||||
|
hidden = self.conv_in(z)
|
||||||
|
# middle
|
||||||
|
hidden = self.mid.block_1(hidden)
|
||||||
|
hidden = self.mid.attn_1(hidden)
|
||||||
|
hidden = self.mid.block_2(hidden)
|
||||||
|
# upsampling
|
||||||
|
for i_level in reversed(range(self.num_resolutions)):
|
||||||
|
for i_block in range(self.num_res_blocks + 1):
|
||||||
|
hidden = self.up[i_level].block[i_block](hidden)
|
||||||
|
if i_level != 0:
|
||||||
|
hidden = self.up[i_level].upsample(hidden)
|
||||||
|
# end
|
||||||
|
hidden = self.norm_out(hidden)
|
||||||
|
hidden = self.swish(hidden)
|
||||||
|
hidden = self.conv_out(hidden)
|
||||||
|
return hidden
|
||||||
|
|
||||||
|
|
||||||
|
class SDVAE(torch.nn.Module):
|
||||||
|
def __init__(self, dtype=torch.float32, device=None):
|
||||||
|
super().__init__()
|
||||||
|
self.encoder = VAEEncoder(dtype=dtype, device=device)
|
||||||
|
self.decoder = VAEDecoder(dtype=dtype, device=device)
|
||||||
|
|
||||||
|
@torch.autocast("cuda", dtype=torch.float16)
|
||||||
|
def decode(self, latent):
|
||||||
|
return self.decoder(latent)
|
||||||
|
|
||||||
|
@torch.autocast("cuda", dtype=torch.float16)
|
||||||
|
def encode(self, image):
|
||||||
|
hidden = self.encoder(image)
|
||||||
|
mean, logvar = torch.chunk(hidden, 2, dim=1)
|
||||||
|
logvar = torch.clamp(logvar, -30.0, 20.0)
|
||||||
|
std = torch.exp(0.5 * logvar)
|
||||||
|
return mean + std * torch.randn_like(mean)
|
||||||
@@ -0,0 +1,96 @@
|
|||||||
|
import contextlib
|
||||||
|
|
||||||
|
import torch
|
||||||
|
|
||||||
|
import k_diffusion
|
||||||
|
from modules.models.sd3.sd3_impls import BaseModel, SDVAE, SD3LatentFormat
|
||||||
|
from modules.models.sd3.sd3_cond import SD3Cond
|
||||||
|
|
||||||
|
from modules import shared, devices
|
||||||
|
|
||||||
|
|
||||||
|
class SD3Denoiser(k_diffusion.external.DiscreteSchedule):
|
||||||
|
def __init__(self, inner_model, sigmas):
|
||||||
|
super().__init__(sigmas, quantize=shared.opts.enable_quantization)
|
||||||
|
self.inner_model = inner_model
|
||||||
|
|
||||||
|
def forward(self, input, sigma, **kwargs):
|
||||||
|
return self.inner_model.apply_model(input, sigma, **kwargs)
|
||||||
|
|
||||||
|
|
||||||
|
class SD3Inferencer(torch.nn.Module):
|
||||||
|
def __init__(self, state_dict, shift=3, use_ema=False):
|
||||||
|
super().__init__()
|
||||||
|
|
||||||
|
self.shift = shift
|
||||||
|
|
||||||
|
with torch.no_grad():
|
||||||
|
self.model = BaseModel(shift=shift, state_dict=state_dict, prefix="model.diffusion_model.", device="cpu", dtype=devices.dtype)
|
||||||
|
self.first_stage_model = SDVAE(device="cpu", dtype=devices.dtype_vae)
|
||||||
|
self.first_stage_model.dtype = self.model.diffusion_model.dtype
|
||||||
|
|
||||||
|
self.alphas_cumprod = 1 / (self.model.model_sampling.sigmas ** 2 + 1)
|
||||||
|
|
||||||
|
self.text_encoders = SD3Cond()
|
||||||
|
self.cond_stage_key = 'txt'
|
||||||
|
|
||||||
|
self.parameterization = "eps"
|
||||||
|
self.model.conditioning_key = "crossattn"
|
||||||
|
|
||||||
|
self.latent_format = SD3LatentFormat()
|
||||||
|
self.latent_channels = 16
|
||||||
|
|
||||||
|
@property
|
||||||
|
def cond_stage_model(self):
|
||||||
|
return self.text_encoders
|
||||||
|
|
||||||
|
def before_load_weights(self, state_dict):
|
||||||
|
self.cond_stage_model.before_load_weights(state_dict)
|
||||||
|
|
||||||
|
def ema_scope(self):
|
||||||
|
return contextlib.nullcontext()
|
||||||
|
|
||||||
|
def get_learned_conditioning(self, batch: list[str]):
|
||||||
|
return self.cond_stage_model(batch)
|
||||||
|
|
||||||
|
def apply_model(self, x, t, cond):
|
||||||
|
return self.model(x, t, c_crossattn=cond['crossattn'], y=cond['vector'])
|
||||||
|
|
||||||
|
def decode_first_stage(self, latent):
|
||||||
|
latent = self.latent_format.process_out(latent)
|
||||||
|
return self.first_stage_model.decode(latent)
|
||||||
|
|
||||||
|
def encode_first_stage(self, image):
|
||||||
|
latent = self.first_stage_model.encode(image)
|
||||||
|
return self.latent_format.process_in(latent)
|
||||||
|
|
||||||
|
def get_first_stage_encoding(self, x):
|
||||||
|
return x
|
||||||
|
|
||||||
|
def create_denoiser(self):
|
||||||
|
return SD3Denoiser(self, self.model.model_sampling.sigmas)
|
||||||
|
|
||||||
|
def medvram_fields(self):
|
||||||
|
return [
|
||||||
|
(self, 'first_stage_model'),
|
||||||
|
(self, 'text_encoders'),
|
||||||
|
(self, 'model'),
|
||||||
|
]
|
||||||
|
|
||||||
|
def add_noise_to_latent(self, x, noise, amount):
|
||||||
|
return x * (1 - amount) + noise * amount
|
||||||
|
|
||||||
|
def fix_dimensions(self, width, height):
|
||||||
|
return width // 16 * 16, height // 16 * 16
|
||||||
|
|
||||||
|
def diffusers_weight_mapping(self):
|
||||||
|
for i in range(self.model.depth):
|
||||||
|
yield f"transformer.transformer_blocks.{i}.attn.to_q", f"diffusion_model_joint_blocks_{i}_x_block_attn_qkv_q_proj"
|
||||||
|
yield f"transformer.transformer_blocks.{i}.attn.to_k", f"diffusion_model_joint_blocks_{i}_x_block_attn_qkv_k_proj"
|
||||||
|
yield f"transformer.transformer_blocks.{i}.attn.to_v", f"diffusion_model_joint_blocks_{i}_x_block_attn_qkv_v_proj"
|
||||||
|
yield f"transformer.transformer_blocks.{i}.attn.to_out.0", f"diffusion_model_joint_blocks_{i}_x_block_attn_proj"
|
||||||
|
|
||||||
|
yield f"transformer.transformer_blocks.{i}.attn.add_q_proj", f"diffusion_model_joint_blocks_{i}_context_block.attn_qkv_q_proj"
|
||||||
|
yield f"transformer.transformer_blocks.{i}.attn.add_k_proj", f"diffusion_model_joint_blocks_{i}_context_block.attn_qkv_k_proj"
|
||||||
|
yield f"transformer.transformer_blocks.{i}.attn.add_v_proj", f"diffusion_model_joint_blocks_{i}_context_block.attn_qkv_v_proj"
|
||||||
|
yield f"transformer.transformer_blocks.{i}.attn.add_out_proj.0", f"diffusion_model_joint_blocks_{i}_context_block_attn_proj"
|
||||||
@@ -0,0 +1,31 @@
|
|||||||
|
import importlib
|
||||||
|
import torch
|
||||||
|
|
||||||
|
from modules import shared
|
||||||
|
|
||||||
|
|
||||||
|
def check_for_npu():
|
||||||
|
if importlib.util.find_spec("torch_npu") is None:
|
||||||
|
return False
|
||||||
|
import torch_npu
|
||||||
|
|
||||||
|
try:
|
||||||
|
# Will raise a RuntimeError if no NPU is found
|
||||||
|
_ = torch_npu.npu.device_count()
|
||||||
|
return torch.npu.is_available()
|
||||||
|
except RuntimeError:
|
||||||
|
return False
|
||||||
|
|
||||||
|
|
||||||
|
def get_npu_device_string():
|
||||||
|
if shared.cmd_opts.device_id is not None:
|
||||||
|
return f"npu:{shared.cmd_opts.device_id}"
|
||||||
|
return "npu:0"
|
||||||
|
|
||||||
|
|
||||||
|
def torch_npu_gc():
|
||||||
|
with torch.npu.device(get_npu_device_string()):
|
||||||
|
torch.npu.empty_cache()
|
||||||
|
|
||||||
|
|
||||||
|
has_npu = check_for_npu()
|
||||||
@@ -198,6 +198,8 @@ class Options:
|
|||||||
try:
|
try:
|
||||||
with open(filename, "r", encoding="utf8") as file:
|
with open(filename, "r", encoding="utf8") as file:
|
||||||
self.data = json.load(file)
|
self.data = json.load(file)
|
||||||
|
except FileNotFoundError:
|
||||||
|
self.data = {}
|
||||||
except Exception:
|
except Exception:
|
||||||
errors.report(f'\nCould not load settings\nThe config file "{filename}" is likely corrupted\nIt has been moved to the "tmp/config.json"\nReverting config to default\n\n''', exc_info=True)
|
errors.report(f'\nCould not load settings\nThe config file "{filename}" is likely corrupted\nIt has been moved to the "tmp/config.json"\nReverting config to default\n\n''', exc_info=True)
|
||||||
os.replace(filename, os.path.join(script_path, "tmp", "config.json"))
|
os.replace(filename, os.path.join(script_path, "tmp", "config.json"))
|
||||||
@@ -238,6 +240,9 @@ class Options:
|
|||||||
|
|
||||||
item_categories = {}
|
item_categories = {}
|
||||||
for item in self.data_labels.values():
|
for item in self.data_labels.values():
|
||||||
|
if item.section[0] is None:
|
||||||
|
continue
|
||||||
|
|
||||||
category = categories.mapping.get(item.category_id)
|
category = categories.mapping.get(item.category_id)
|
||||||
category = "Uncategorized" if category is None else category.label
|
category = "Uncategorized" if category is None else category.label
|
||||||
if category not in item_categories:
|
if category not in item_categories:
|
||||||
|
|||||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user