Compare commits

...

696 Commits

Author SHA1 Message Date
AUTOMATIC1111
82a973c043 changelog 2024-07-27 15:49:39 +03:00
AUTOMATIC1111
1d7e9eca09 Merge pull request #16275 from AUTOMATIC1111/fix-image-upscale-on-cpu
fix image upscale on cpu
2024-07-27 15:48:22 +03:00
AUTOMATIC1111
c19d044364 Merge branch 'release_candidate' 2024-07-27 06:53:05 +03:00
AUTOMATIC1111
8b3d98c5a5 update CHANGELOG 2024-07-20 11:54:14 +03:00
AUTOMATIC1111
5bbbda473f Merge branch 'dev' into release_candidate 2024-07-20 11:51:12 +03:00
AUTOMATIC1111
9f5a98d576
Merge pull request #16166 from richardtallent/dev
Fix noisy DS_Store files for MacOS
2024-07-20 11:47:50 +03:00
AUTOMATIC1111
986c31dcfe
Merge pull request #16180 from light-and-ray/do_not_send_image_size_on_paste_inpaint
do not send image size on paste inpaint
2024-07-20 11:47:27 +03:00
AUTOMATIC1111
5096c163c1
Merge pull request #16173 from AUTOMATIC1111/robust-sysinfo
Robust sysinfo
2024-07-20 11:46:45 +03:00
AUTOMATIC1111
7b99e14ab1
Merge pull request #16194 from light-and-ray/fix_cannot_write_mode_P_as_jpeg
fix OSError: cannot write mode P as JPEG
2024-07-20 11:46:24 +03:00
AUTOMATIC1111
7c8a4ccecb
Merge pull request #16202 from light-and-ray/do_not_break_progressbar_on_non-job_actions
[bug] do not break progressbar on non-job actions (add wrap_gradio_call_no_job)
2024-07-20 11:45:57 +03:00
AUTOMATIC1111
5d26c6ae89
Merge pull request #16178 from light-and-ray/update_installation_guide_linux
update installation guide linux
2024-07-20 11:44:59 +03:00
AUTOMATIC1111
5a10bb9aa6
Merge pull request #16235 from v0xie/beta-sampling
Feature: Beta scheduler
2024-07-20 11:43:39 +03:00
AUTOMATIC1111
fa0ba939a7
Merge pull request #16218 from Haoming02/resize-tabs-id
add ids to the resize tabs in img2img
2024-07-20 11:43:03 +03:00
AUTOMATIC1111
fc7b25ac67
Merge pull request #16231 from AUTOMATIC1111/bat-activate-venv
activate venv .bat
2024-07-20 11:42:29 +03:00
AUTOMATIC1111
e09104a126
Merge pull request #16239 from AUTOMATIC1111/fix-upscale-logic
fix upscale logic
2024-07-20 11:41:34 +03:00
AUTOMATIC1111
141d4b71b1
Merge pull request #16242 from AUTOMATIC1111/option-to-disable-save-button-log.csv-
option to disable save button log.csv
2024-07-20 11:40:39 +03:00
AUTOMATIC1111
ea903819cb
Merge pull request #16212 from AUTOMATIC1111/sd3_lora
SD3 Lora support
2024-07-20 11:39:53 +03:00
w-e-w
24a23e1225 option to disable save button log.csv 2024-07-20 15:59:44 +09:00
v0xie
8749540602 fix lint 2024-07-19 15:33:07 -07:00
v0xie
9de7084884 always add alpha/beta to extra_generation_params when schedule is Beta 2024-07-19 14:54:24 -07:00
v0xie
94275b115c enforce beta_dist_alpha / beta_dist_beta > 0 to avoid nan 2024-07-19 14:15:55 -07:00
v0xie
e285af6e48 add beta schedule opts to xyz options 2024-07-19 14:15:10 -07:00
v0xie
f6f055a93d use configured alpha/beta values in Beta scheduling 2024-07-19 14:08:44 -07:00
v0xie
3a5a66775c add new options 'beta_dist_alpha', 'beta_dist_beta' 2024-07-19 14:08:08 -07:00
v0xie
7e1bd3e3c3 refactor: syntax and add 0.0 on new line 2024-07-19 13:44:22 -07:00
w-e-w
964fc13a99 fix upscale logic 2024-07-20 04:01:13 +09:00
v0xie
a5f66b5003 feature: beta scheduler 2024-07-18 15:53:54 -07:00
w-e-w
2abc628899 bat activate venv 2024-07-18 23:51:46 +09:00
AUTOMATIC1111
2b50233f3f fix bugs in lora support 2024-07-16 20:50:25 +03:00
Haoming
f5866199c4 add ids 2024-07-16 11:07:22 +08:00
AUTOMATIC1111
7e5cdaab4b SD3 lora support 2024-07-15 08:31:55 +03:00
AUTOMATIC1111
b2453d280a
Merge pull request #16192 from AUTOMATIC1111/patch-#16169
fix #16169 Py 3.9 compatibility
2024-07-13 09:29:47 +03:00
AUTOMATIC1111
b4d62a05af
Merge pull request #16164 from AUTOMATIC1111/sd3_textual_inversion
sd3 TI support
2024-07-13 09:27:46 +03:00
Andray
589dda3cf2 do not break progressbar on non-job actions 2024-07-12 16:08:36 +04:00
Andray
3d2dbefcde fix OSError: cannot write mode P as JPEG 2024-07-11 23:54:25 +04:00
w-e-w
b1695c1b68 fix #16169 Py 3.9 compatibility
Co-Authored-By: SLAPaper Pang <slapaper.pku@gmail.com>
2024-07-11 18:46:54 +09:00
Andray
d57ff884ed do not send image size on paste inpaint 2024-07-09 16:12:39 +04:00
Andray
26cccd8faa update 2024-07-09 14:22:08 +04:00
Andray
9cc7142dd7 update installation guide linux 2024-07-09 14:07:12 +04:00
w-e-w
5a5fe7494a .gitignore sysinfo.json 2024-07-09 02:27:22 +09:00
w-e-w
6a7042fe2f move git_status to sysinfo 2024-07-09 02:27:22 +09:00
w-e-w
72cfa2829d safer Imports 2024-07-09 02:27:22 +09:00
w-e-w
4debd4d3ef compact get_info_from_repo_path 2024-07-09 02:27:22 +09:00
w-e-w
3f6dcda3e5 Extensions info full commit hash 2024-07-09 02:23:23 +09:00
w-e-w
27d96fa608 fallback Extensions info 2024-07-09 02:23:23 +09:00
w-e-w
dd4f798b97 fallback get_config() 2024-07-09 02:23:23 +09:00
w-e-w
27947a79d6 git status 2024-07-09 02:23:23 +09:00
w-e-w
11f827c58b use pip freeze --all to get packages 2024-07-09 02:23:23 +09:00
AUTOMATIC1111
48dd4d9eae Merge pull request #16170 from AUTOMATIC1111/shlex.quote-launch-args-in-console-log
shlex.join launch args in console log
2024-07-08 18:36:56 +03:00
AUTOMATIC1111
93c00b2af7
Merge pull request #16170 from AUTOMATIC1111/shlex.quote-launch-args-in-console-log
shlex.join launch args in console log
2024-07-08 18:36:28 +03:00
w-e-w
7d7f7f4b49 sysinfo handle psutil not working 2024-07-08 16:40:20 +09:00
w-e-w
1b0823db94 shlex.join launch args in console log 2024-07-08 15:32:45 +09:00
AUTOMATIC1111
6ca7a453d4 Merge pull request #16169 from AUTOMATIC1111/py-3.9-compatibility
Py 3.9 compatibility
2024-07-08 08:27:40 +03:00
AUTOMATIC1111
bad47dcfeb
Merge pull request #16169 from AUTOMATIC1111/py-3.9-compatibility
Py 3.9 compatibility
2024-07-08 08:27:07 +03:00
w-e-w
c3d8b78b47 py 3.9 compatibility 2024-07-08 14:17:51 +09:00
w-e-w
21e72d1a5e py 3.9 find_vae() 2024-07-08 14:07:26 +09:00
Richard Tallent
7b2917255a Fix noisy DS_Store files for MacOS 2024-07-07 11:18:17 -05:00
AUTOMATIC1111
11cfe0dd05 sd3 TI support 2024-07-07 16:36:53 +03:00
AUTOMATIC1111
780c70f6ea update changelog 2024-07-07 08:40:19 +03:00
AUTOMATIC1111
b5481c6195 Merge pull request #16153 from light-and-ray/fix_ui_flashing_on_reload_and_fast_scrollong
fix ui flashing on reloading and fast scrollong
2024-07-07 08:38:26 +03:00
AUTOMATIC1111
1da4907927
Merge pull request #16153 from light-and-ray/fix_ui_flashing_on_reload_and_fast_scrollong
fix ui flashing on reloading and fast scrollong
2024-07-07 08:37:58 +03:00
w-e-w
ec580374e5 background-color: background_fill_primary 2024-07-07 00:22:27 +09:00
AUTOMATIC1111
340a9108ca update changelog 2024-07-06 11:26:14 +03:00
AUTOMATIC1111
74069addc3 SD2 v autodetection fix 2024-07-06 11:00:22 +03:00
AUTOMATIC1111
477869c044
Merge pull request #16079 from light-and-ray/fix_sd2_switching
fix sd2 switching
2024-07-06 10:41:16 +03:00
AUTOMATIC1111
ffead92d4e Revert "Merge pull request #16078 from huchenlei/fix_sd2"
This reverts commit 4cc3add770b10cb8e8f7aa980c0d50e5b637ab2b, reversing
changes made to 50514ce414ee4fad9aa4780ef0b97116c7d7c970.
2024-07-06 10:40:48 +03:00
AUTOMATIC1111
0a6628bad0 remove mentions of specific samplers from CFG denoiser code 2024-07-06 10:31:08 +03:00
AUTOMATIC1111
eb112c6f88
Merge pull request #16035 from v0xie/cfgpp
Add new sampler DDIM CFG++
2024-07-06 10:18:49 +03:00
AUTOMATIC1111
ace00a1fe8
Merge pull request #16054 from AUTOMATIC1111/fix-Sampler-Scheduler-autocorrection-warning
Fix sampler scheduler autocorrection warning
2024-07-06 10:11:33 +03:00
AUTOMATIC1111
2fec94710b
Merge pull request #16060 from xiaoxianBoy/fix-typos
chore: fix typos
2024-07-06 10:06:35 +03:00
AUTOMATIC1111
74b56fef3d
Merge pull request #16061 from AUTOMATIC1111/remove-dont_fix_second_order_samplers_schedule
remove deprecated setting dont_fix_second_order_samplers_schedule
2024-07-06 10:06:01 +03:00
AUTOMATIC1111
8058eed6a5
Merge pull request #16059 from viking1304/mac-torch-231
Update torch for ARM Macs to 2.3.1
2024-07-06 10:04:26 +03:00
AUTOMATIC1111
3bd4a08119
Merge pull request #16062 from AUTOMATIC1111/fix-infotext-Lora-hashes-fro-hires-fix-different-lora
fix infotext Lora hashes fro hires fix different lora
2024-07-06 10:04:06 +03:00
AUTOMATIC1111
68df28176c
Merge pull request #16065 from AUTOMATIC1111/ToggleLivePriview-in-image-viewer
ToggleLivePriview button in image viewer
2024-07-06 10:02:01 +03:00
AUTOMATIC1111
4cc3add770
Merge pull request #16078 from huchenlei/fix_sd2
Fix SD2 loading
2024-07-06 09:59:29 +03:00
AUTOMATIC1111
50514ce414
Merge pull request #16085 from light-and-ray/stoping_generation_extras
stoping generation extras
2024-07-06 09:53:03 +03:00
AUTOMATIC1111
edebe4d4de
Merge pull request #16088 from cuba3/dev_cuba3
Maintaining Project Compatibility for Python 3.9 Users Without Upgrade Requirements.
2024-07-06 09:52:40 +03:00
AUTOMATIC1111
b9c3f4ec2c
Merge pull request #16092 from viking1304/bash-python-version
Prioritize python3.10 over python3 if both are available on Linux and Mac (with fallback)
2024-07-06 09:52:14 +03:00
AUTOMATIC1111
41ac13996e
Merge pull request #16102 from eltociear/patch-4
docs: update bug_report.yml
2024-07-06 09:51:44 +03:00
AUTOMATIC1111
76a19c089a
Merge pull request #16116 from viking1304/ensure-venv-use
Ensure use of python from venv on Mac and Linux
2024-07-06 09:49:34 +03:00
AUTOMATIC1111
372a8e0658
Merge pull request #16151 from AUTOMATIC1111/dora-fix
Possible fix of wrong scale in weight decomposition
2024-07-06 09:48:53 +03:00
AUTOMATIC1111
b8c3664934
Merge pull request #16142 from AndreyRGW/dev
Add Simple Scheduler
2024-07-06 09:47:15 +03:00
AUTOMATIC1111
9414309f15
Merge branch 'dev' into dev 2024-07-06 09:46:57 +03:00
AUTOMATIC1111
9cbde7938a
Merge pull request #16118 from AUTOMATIC1111/fix-Replace-preview
fix Replace preview
2024-07-06 09:44:17 +03:00
AUTOMATIC1111
019df53a31
Merge pull request #16119 from AUTOMATIC1111/defunct---max-batch-count
Defunct --max-batch-count
2024-07-06 09:43:09 +03:00
AUTOMATIC1111
af3ccee5c8
Merge pull request #16140 from ibrahimsn98/master
Return HTTP 400 instead of 404 on invalid sampler error
2024-07-06 09:42:44 +03:00
AUTOMATIC1111
a6c384b9f7
Merge pull request #16144 from akx/bump-spandrel
Bump spandrel to 0.3.4
2024-07-06 09:42:14 +03:00
AUTOMATIC1111
b282b47b85
Merge pull request #16149 from AndreyRGW/devpatch1
Add Normal and DDIM Schedulers
2024-07-06 09:41:21 +03:00
AUTOMATIC1111
c02e3a5549
Merge pull request #16030 from AUTOMATIC1111/sd3
Stable Diffusion 3 support
2024-07-06 09:39:01 +03:00
Andray
b82caf1322 fix ui flashing on reloading and fast scrollong 2024-07-05 19:28:16 +04:00
Kohaku-Blueleaf
bfbca31074 possible fix of wrong scale
https://github.com/comfyanonymous/ComfyUI/pull/3922
2024-07-05 18:56:39 +08:00
Andrey Efremov
f8640662c5
Add Normal and DDIM Schedulers 2024-07-04 19:27:08 +03:00
Aarni Koskela
f8fb74b93a Bump Spandrel to 0.3.4; add spandrel-extra-arches for CodeFormer 2024-07-04 09:14:04 +03:00
Andrey Efremov
32fdf18203
Add Simple Scheduler 2024-07-04 00:56:18 +03:00
İbrahim Süren
3971c01562 Return http 400 instead of 404 on invalid sampler 2024-07-03 17:33:25 +03:00
w-e-w
fd16393465 defunct --max-batch-count 2024-06-30 21:19:25 +09:00
w-e-w
957185f7eb fix Replace preview
fix broken Replace preview for extra networks tabs edit metadata
caused by #11808
2024-06-30 20:20:29 +09:00
viking1304
6ddcd8914b ensure use of python from venv 2024-06-30 11:44:06 +02:00
AUTOMATIC1111
9e404c3154 fix --medvram 2024-06-30 07:06:28 +03:00
AUTOMATIC1111
ebe8be9028 remove AutocastLinear from SD3's MLP 2024-06-29 08:05:55 +03:00
AUTOMATIC1111
1394ecaf36 do sampler calculations on CPU 2024-06-29 08:05:35 +03:00
AUTOMATIC1111
7e4b06fcd0 support loading clip/t5 from the main model checkpoint 2024-06-29 00:38:52 +03:00
AUTOMATIC1111
d67348a0a5 allow generation to be started with any dimensions specified 2024-06-28 18:06:49 +03:00
AUTOMATIC1111
179ae47d64 fix the problem with infinite prompts where empty cond would be calculated incorrectly 2024-06-28 11:15:34 +03:00
AUTOMATIC1111
0b64633584 fix img2img 2024-06-28 09:23:41 +03:00
AUTOMATIC1111
0c7bdcc1b1 add the missing get_first_stage_encoding function 2024-06-28 08:10:32 +03:00
AUTOMATIC1111
fc8b126673 get T5 to work both with and without --precision half 2024-06-28 08:10:19 +03:00
AUTOMATIC1111
06fe174c74 get deepbooru to run with --precision-half 2024-06-28 07:51:30 +03:00
Ikko Eltociear Ashimine
afaf120bc2
docs: update bug_report.yml
occured -> occurred
2024-06-27 17:44:12 +09:00
AUTOMATIC1111
42ca30d6c1 fix mdevram for SD1/SDXL 2024-06-27 07:35:53 +03:00
AUTOMATIC1111
d686e73daa support for SD3: infinite prompt length, token counting 2024-06-26 23:22:00 +03:00
viking1304
ec3c31e7a1 Try to use specified python version on linux and mac, with fallback 2024-06-25 21:01:33 +02:00
cuba3
9e60cdbc3f Maintaining Project Compatibility for Python 3.9 Users Without Upgrade Requirements.
Sole usage of Python 3.10's match-case in the project hinders quick-start for beginners; consider replacing with if-else for improved accessibility.
2024-06-25 15:24:46 +08:00
Andray
5d9f1e6a43 stoping generation extras 2024-06-25 05:33:07 +04:00
AUTOMATIC1111
a8fba9af35 medvram support for SD3 2024-06-24 10:15:46 +03:00
AUTOMATIC1111
a65dd315ad fix T5 2024-06-24 09:06:10 +03:00
Andray
731eb72774 fix sd2 switching 2024-06-23 21:16:48 +04:00
huchenlei
c3ef381cd8 Fix SD2 loading 2024-06-23 11:19:04 -04:00
w-e-w
775fa7696b ToggleLivePriview in image viewer 2024-06-21 20:38:40 +09:00
w-e-w
109bbda709 fix infotext Lora hashes fro hires fix different lora 2024-06-21 15:11:41 +09:00
w-e-w
0f40c4b9b1 fix Sampler Scheduler autocorrection warning 2024-06-21 12:14:33 +09:00
w-e-w
bd85b3f19b remove dont_fix_second_order_samplers_schedule 2024-06-21 10:53:44 +09:00
snoppy
13f22974a4
chore: fix typos
Signed-off-by: snoppy <michaleli@foxmail.com>
2024-06-21 09:52:02 +08:00
viking1304
a772fd9804 Update torch for ARM Macs to 2.3.1 2024-06-20 23:57:59 +02:00
v0xie
663a4d80df add new sampler DDIM CFG++ 2024-06-16 17:47:21 -07:00
AUTOMATIC1111
34b4443cc3 add an option (on by default) to disable T5
revert t5xxl back to fp16
2024-06-16 21:57:17 +03:00
AUTOMATIC1111
d4b814aed6 change t5xxl checkpoint to fp8 2024-06-16 14:39:58 +03:00
AUTOMATIC1111
58dc35a64a change CLIP links to allow anonymous downloading 2024-06-16 14:31:43 +03:00
AUTOMATIC1111
06d0a5ab4d fix NaN issue when running without --precision half 2024-06-16 14:09:32 +03:00
AUTOMATIC1111
80f618ea95 add protobuf==3.20.0 to requirements 2024-06-16 12:52:03 +03:00
AUTOMATIC1111
b443fdcf76 prevent accidental creation of CLIP models in float32 type when user wants float16 2024-06-16 11:04:19 +03:00
AUTOMATIC1111
7ee2114cd9 typo 2024-06-16 08:18:05 +03:00
AUTOMATIC1111
79de09c3df linter 2024-06-16 08:13:23 +03:00
AUTOMATIC1111
5b2a60b8e2 initial SD3 support 2024-06-16 08:04:31 +03:00
Alex "mcmonkey" Goodwin
a7116aa9a1 add SD3 reference implementation from https://github.com/mcmonkey4eva/sd3-ref/ 2024-06-16 07:13:57 +03:00
w-e-w
9e0f6d2012 remove commented code 2024-06-13 09:31:37 +09:00
AUTOMATIC1111
a30b19dd55
Merge pull request #16001 from zero41120/feat-prevent-screen-lock
feat: Prevent screen sleep during generation
2024-06-12 08:33:28 +03:00
YSH
f1e0bfebfc ci: remove comments and console logs 2024-06-11 22:33:11 -07:00
YSH
c803e11505 fix: prevent create multiple wake lock 2024-06-11 18:14:32 -07:00
YSH
1f8f3a6e8b feat: prevent screen sleep during generation 2024-06-11 16:50:00 -07:00
AUTOMATIC1111
d5e26274c8
Merge pull request #15992 from silveroxides/dev
Add option to enable clip skip for clip L on SDXL
2024-06-11 07:25:47 +03:00
Silver
91ecc750be
Update sd_hijack_clip.py 2024-06-11 00:40:26 +02:00
Silver
00e09382cd Add option to enable clip skip for clip L on SDXL 2024-06-10 22:11:11 +02:00
AUTOMATIC1111
123582b00f
Merge pull request #15988 from AUTOMATIC1111/multi-size-grid
multi size grid
2024-06-10 16:44:11 +03:00
w-e-w
abacb735f4 multi size grid 2024-06-10 20:47:12 +09:00
AUTOMATIC1111
e33bb8febe
Merge pull request #15984 from huchenlei/before_every_sampling
Add process_before_every_sampling hook
2024-06-10 07:25:17 +03:00
huchenlei
17e846150c Add process_before_every_sampling hook 2024-06-09 23:06:28 -04:00
AUTOMATIC1111
a84000c285
Merge pull request #15980 from AUTOMATIC1111/git-ignore-trace.json
.gitignore trace.json
2024-06-09 23:23:23 +03:00
w-e-w
74ee8fd1e3 .gitignore trace.json 2024-06-10 04:36:58 +09:00
AUTOMATIC1111
d2097dbdd9 added onOptionsAvailable callback for javascript for 2024-06-09 21:33:32 +03:00
AUTOMATIC1111
99e65ec618 undo some changes from #15823 and fix whitespace 2024-06-09 21:23:53 +03:00
AUTOMATIC1111
1d0bb39797
Merge pull request #15823 from drhead/patch-3
[Performance] Keep sigmas on CPU
2024-06-09 21:18:48 +03:00
AUTOMATIC1111
57e6d05a43 added tool for profiling code 2024-06-09 21:18:36 +03:00
AUTOMATIC1111
aafbb5b403 lint 2024-06-09 16:47:08 +03:00
AUTOMATIC1111
e368cd2810 stylistic changes for #15978 2024-06-09 16:46:08 +03:00
AUTOMATIC1111
981abbb1f2
Merge pull request #15978 from bluelovers/pr/pattern-001
feat: save pattern add `basename`
2024-06-09 16:45:06 +03:00
AUTOMATIC1111
6214aa7d2a performance: check for nans in unet only once, after all steps have been completed 2024-06-09 16:24:04 +03:00
bluelovers
6447ff49d3 feat: save pattern add basename
`grid` or `xyz_grid` or `img`

```py
'basename': lambda self: 'img' if self.basename == '' else self.basename,
```
2024-06-09 19:07:32 +08:00
AUTOMATIC1111
41ee2db5a8
Merge pull request #15976 from huchenlei/fix_sdxl_inpaint
Fix SDXL Inpaint
2024-06-09 07:44:46 +03:00
huchenlei
f89b5dbbd2 nit 2024-06-08 22:15:37 -04:00
huchenlei
d875cda565 Fix sdxl inpaint 2024-06-08 22:11:11 -04:00
drhead
d52a1e1a22
lint 2024-06-08 18:56:23 -04:00
drhead
39a6d5655f
patch k_diffusion to_d and strip device from schedulers 2024-06-08 18:55:07 -04:00
drhead
428975e1d3
Merge pull request #1 from AUTOMATIC1111/dev
Dev
2024-06-08 18:49:44 -04:00
drhead
58b24eae30
Merge branch 'AUTOMATIC1111:master' into patch-3 2024-06-08 18:44:46 -04:00
AUTOMATIC1111
547778b10f possibly make NaN check cheaper 2024-06-08 12:41:38 +03:00
AUTOMATIC1111
194c2620d6
Merge pull request #15968 from AUTOMATIC1111/wsl-open
replace wsl-open with wslpath and explorer.exe
2024-06-08 12:30:11 +03:00
AUTOMATIC1111
5ecfc20d97
Merge pull request #15610 from pinanew/pinanew-patch-1
AVIF has quality setting too
2024-06-08 12:27:54 +03:00
AUTOMATIC1111
2dbc7aa688
Merge pull request #15627 from light-and-ray/more_extension_tag_filtering_options
more extension tag filtering options
2024-06-08 12:24:17 +03:00
AUTOMATIC1111
6d8d2723a0
Merge pull request #15632 from brendanhoar/bgh-handle-metadata-issues-more-cleanly
QOL Items - handle metadata issues more cleanly for SD models, Loras and embeddings
2024-06-08 12:17:01 +03:00
AUTOMATIC1111
3ef9f2748d
Merge branch 'dev' into bgh-handle-metadata-issues-more-cleanly 2024-06-08 12:16:55 +03:00
AUTOMATIC1111
30461bef98
Merge pull request #15602 from AUTOMATIC1111/initial-model-download-integrity
Initial model download integrity
2024-06-08 12:13:24 +03:00
AUTOMATIC1111
569f17c6c6
Merge pull request #15654 from huchenlei/mime
Add correct mimetype for .mjs files
2024-06-08 12:10:42 +03:00
AUTOMATIC1111
a184e5dd87
Merge pull request #15657 from AUTOMATIC1111/drag-text-fix
Fix dragging text within prompt input
2024-06-08 11:58:05 +03:00
AUTOMATIC1111
9e5103124a
Merge pull request #15641 from AUTOMATIC1111/no-referrer
no-referrer
2024-06-08 11:56:53 +03:00
AUTOMATIC1111
41b24d350f
Merge pull request #15680 from light-and-ray/use_gradio_theme_colors_in_css
use gradio theme colors in css
2024-06-08 11:54:55 +03:00
AUTOMATIC1111
742bfbe0ec
Merge pull request #15679 from AUTOMATIC1111/lora-bundled-TI-infotext
LoRA bundled TI infotext
2024-06-08 11:54:29 +03:00
AUTOMATIC1111
a1130c26e5
Merge pull request #15664 from AUTOMATIC1111/fix-extra-batch-mode-P-Transparency
fix extra batch mode P Transparency
2024-06-08 11:53:08 +03:00
AUTOMATIC1111
b9dfc50a1b
Merge pull request #15705 from AUTOMATIC1111/use-script_path-for-webui-root-in-launch_utils
use script_path for webui root in launch_utils
2024-06-08 11:51:48 +03:00
AUTOMATIC1111
74b1fc6256
Merge pull request #15682 from light-and-ray/two_fingers_press_to_open_context_menu
two fingers press to open context menu
2024-06-08 11:43:06 +03:00
AUTOMATIC1111
4aebfe9844
Merge pull request #15730 from bluelovers/patch-2
Update imageviewer.js
2024-06-08 11:42:27 +03:00
AUTOMATIC1111
debc6dddeb
Merge pull request #15739 from LoganBooker/LoganBooker-AVIF-mimetype-patch
Add AVIF MIME type support to mimetype definitions
2024-06-08 11:36:21 +03:00
AUTOMATIC1111
1a7ffa2c76 remove extra local variable 2024-06-08 11:35:45 +03:00
AUTOMATIC1111
64783dd9cc
Merge pull request #15742 from MarcusNyne/m9-240508-model-dir
Added --models-dir option
2024-06-08 11:35:03 +03:00
AUTOMATIC1111
c1c4b3fb34
Merge pull request #15738 from JLipnerPitt/JLipnerPitt-patch-1
Fix AttributeError
2024-06-08 11:32:38 +03:00
AUTOMATIC1111
5abdf5191d
Merge pull request #15750 from MarcusNyne/m9-240509-pip-upgrade
When creating a virtual environment, upgrade pip in webui.bat/webui.sh
2024-06-08 11:31:26 +03:00
AUTOMATIC1111
64ebb245ad
Merge pull request #15757 from AUTOMATIC1111/fix-fonts-with-subpath-
use relative path for webui-assets css
2024-06-08 11:28:06 +03:00
AUTOMATIC1111
07cf95c76e update pickle safe filenames 2024-06-08 11:26:41 +03:00
AUTOMATIC1111
9905341602
Merge pull request #15783 from elf-mouse/dev
chore: sync v1.8.0 packages according to changelog
2024-06-08 11:20:43 +03:00
AUTOMATIC1111
88a5001e06
Merge branch 'dev' into dev 2024-06-08 11:20:35 +03:00
AUTOMATIC1111
7b940e3879
Merge pull request #15797 from AUTOMATIC1111/fix-extention-update-when-not-on-main-branch
fix extention update when not on main branch
2024-06-08 11:19:26 +03:00
AUTOMATIC1111
b4723bb8c1
Merge pull request #15815 from AUTOMATIC1111/torch-float64-or-float32
fix soft inpainting on mps and xpu, torch_utils.float64
2024-06-08 11:07:29 +03:00
AUTOMATIC1111
6450d24afe
Merge pull request #15806 from huchenlei/inpaint_fix
[Performance 4/6] Precompute is_sdxl_inpaint flag
2024-06-08 11:06:39 +03:00
AUTOMATIC1111
64bf57b5ea
Merge pull request #15817 from light-and-ray/img2img_batch_upload
img2img batch upload method
2024-06-08 11:00:21 +03:00
AUTOMATIC1111
816bc424c4
Merge pull request #15816 from huchenlei/bias_backup
[Performance 5/6] Prevent unnecessary extra networks bias backup
2024-06-08 10:56:40 +03:00
AUTOMATIC1111
371cb60945
Merge pull request #15830 from light-and-ray/scroll_extensions_table_on_overflow
scroll extensions table on overflow
2024-06-08 10:55:18 +03:00
AUTOMATIC1111
603509ec90 as per wfjsw's suggestion, revert changes for sd_hijack_checkpoint.py 2024-06-08 10:54:41 +03:00
AUTOMATIC1111
ad229fae43
Merge pull request #15803 from huchenlei/checkpoint_false
[Performance 1/6] use_checkpoint = False
2024-06-08 10:52:40 +03:00
w-e-w
510f025a01 replace wsl-open with wslpath and explorer.exe 2024-06-08 16:52:12 +09:00
AUTOMATIC1111
5977cb0946
Merge pull request #15832 from AUTOMATIC1111/xyz-csv-skipinitialspace
XYZ CSV skipinitialspace
2024-06-08 10:46:26 +03:00
AUTOMATIC1111
04164a83c4
Merge pull request #15831 from AUTOMATIC1111/fix-Hypertile-xyz
Fix Hypertile xyz
2024-06-08 10:45:49 +03:00
AUTOMATIC1111
96f907ee09
Merge branch 'dev' into fix-Hypertile-xyz 2024-06-08 10:45:36 +03:00
AUTOMATIC1111
c3c90deec0
Merge pull request #15681 from AUTOMATIC1111/fix_p_invalid_sampler_and_scheduler
more old sampler scheduler compatibility
2024-06-08 10:42:42 +03:00
AUTOMATIC1111
cbac72d8ee
Merge pull request #15836 from AUTOMATIC1111/xyz-override-rework
XYZ override rework
2024-06-08 10:40:14 +03:00
AUTOMATIC1111
616013fd7a
Merge pull request #15851 from viking1304/torch-on-mac
Use different torch versions for Intel and ARM Macs
2024-06-08 10:39:33 +03:00
AUTOMATIC1111
93b53dc116
Merge pull request #15824 from drhead/patch-4
[Performance] LDM optimization patches
2024-06-08 10:35:39 +03:00
AUTOMATIC1111
ebfc9f6d09
Merge branch 'dev' into patch-4 2024-06-08 10:35:09 +03:00
AUTOMATIC1111
33b73c473c
Merge pull request #15820 from huchenlei/force_half
[Performance 6/6] Add --precision half option to avoid casting during inference
2024-06-08 10:26:23 +03:00
AUTOMATIC1111
ba54c747e2
Merge pull request #15656 from AUTOMATIC1111/api-old-sampler-names
Allow old sampler names in API
2024-06-08 10:18:20 +03:00
AUTOMATIC1111
cd9e9e4049 remove unneeded tabulation 2024-06-08 10:13:38 +03:00
AUTOMATIC1111
15245d9d5e
Merge pull request #15600 from AUTOMATIC1111/fix-corrupt-model-loop
Fix corrupt model initial load loop
2024-06-08 10:12:45 +03:00
AUTOMATIC1111
5429e4cff5 add proper infotext support for #15607
fix settings override not working for NGMI, s_churn, etc...
2024-06-08 09:56:09 +03:00
AUTOMATIC1111
b150b3a3a4
Merge pull request #15607 from drhead/patch-1
add code for skipping CFG on early steps
2024-06-08 09:20:08 +03:00
AUTOMATIC1111
9e1fc80c48
Merge pull request #15608 from drhead/patch-2
Add KL Optimal scheduler
2024-06-08 09:10:57 +03:00
AUTOMATIC1111
0edc04d126
Merge branch 'dev' into patch-2 2024-06-08 09:10:51 +03:00
AUTOMATIC1111
e21b1e3716
Merge pull request #15864 from AUTOMATIC1111/ReloadUI-backgroundColor---background-fill-primary
ReloadUI backgroundColor --background-fill-primary
2024-06-08 09:06:39 +03:00
AUTOMATIC1111
00f37ad73f
Merge pull request #15893 from alcacode/dev
Fix bug where file extension had an extra '.' under some circumstances
2024-06-08 09:06:03 +03:00
AUTOMATIC1111
0769aa318a integrated edits as recommended in the PR #15804 2024-06-08 09:05:35 +03:00
AUTOMATIC1111
de7f5cdc62
Merge pull request #15804 from huchenlei/rearrange_fix
[Performance 2/6] Replace einops.rearrange with torch native ops
2024-06-08 08:55:51 +03:00
AUTOMATIC1111
0c0d71a4e4
Merge pull request #15907 from AUTOMATIC1111/fix-change-log
fix changelog #15883 -> #15882
2024-06-08 08:53:56 +03:00
AUTOMATIC1111
6de733c91e
Merge pull request #15943 from eatmoreapple/update-lora-load
feat: lora partial update precede full update
2024-06-08 08:51:42 +03:00
AUTOMATIC1111
46bcfbe37c
Merge pull request #15751 from LoganBooker/LoganBooker-Add-AlignYourSteps-Scheduler
Add Align Your Steps to available schedulers
2024-06-08 08:43:02 +03:00
AUTOMATIC1111
3c7384ab60
Merge pull request #15958 from NouberNou/Fix-Grids-Without-Infotexts
Fix for grids without comprehensive infotexts
2024-06-08 08:10:18 +03:00
NouberNou
53f62674ae
Typo on edit
Edited in fix in Github editor and mistyped from local copy
2024-06-06 16:30:01 -07:00
NouberNou
25bbf31f57
Fix for grids without comprehensive infotexts
When generating grids, some scripts such as img2img loopback and ultimate SD upscale do not pass infotexts for each image since they are the same prompt.

If you attempt to save those images using the saved button in the UI it will fail because it will look for the selected image info text. This fixes those errors by replicating the infotext for as many images are passed into the image list if the infotext parameter is none.
2024-06-06 16:22:49 -07:00
eatmoreapple
10f8d0f842 feat: lora partial update precede full update. 2024-06-04 15:02:13 +08:00
w-e-w
8d6f741738 #15883 -> #15882 2024-05-29 03:41:57 +09:00
AUTOMATIC1111
feee37d75f Merge branch 'dev' 2024-05-28 21:20:40 +03:00
AUTOMATIC1111
801b72b92b update changelog 2024-05-28 21:20:23 +03:00
AUTOMATIC1111
759f396a2e
Merge pull request #15882 from AUTOMATIC1111/setuptools==69.5.1
Fix method 1 : pin Setuptools==69.5.1
2024-05-28 21:16:28 +03:00
alcacode
6dd53ce63d
Fix bug where file extension had an extra '.' under some circumstances
Fix bug where under some circumstances an extra "." was inserted between the file base name and the file extension.
The bug is triggered when the extension argument is one of "jpg", "jpeg", or "webp", and the image exceeds the format's dimension limit. Then the extension variable is set to ".png", resulting in the fullfn variable to evaluate to a string ending with "..png".
2024-05-26 15:36:55 +02:00
w-e-w
a63946233b setuptools==69.5.1 2024-05-25 14:19:19 +09:00
w-e-w
344eda55d4 ReloadUI backgroundColor --background-fill-primary 2024-05-22 23:06:07 +09:00
viking1304
5867be2914
Use different torch versions for Intel and ARM Macs 2024-05-20 23:44:17 +02:00
w-e-w
51e7122f25 remove unused code 2024-05-19 15:39:29 +09:00
w-e-w
1e696b028a use override of sd_vae 2024-05-19 15:39:29 +09:00
w-e-w
1f392517f8 use override for uni_pc_order 2024-05-19 05:54:42 +09:00
w-e-w
82884da18c use apply_override for Clip skip 2024-05-19 05:54:42 +09:00
w-e-w
24a59ad3d2 fix Hypertile xyz grid 2024-05-18 18:38:24 +09:00
w-e-w
969a462ac9 xyz util confirm_range 2024-05-18 18:38:24 +09:00
w-e-w
501ac016da Reformat 2024-05-18 18:38:24 +09:00
drhead
feeb6802aa
fix case where first step skilled if skip early cond is 0 2024-05-18 01:22:31 -04:00
Andray
281e0a007b scroll extensions table on overflow 2024-05-18 09:13:16 +04:00
Logan
1d74482817 Default device for sigma tensor to CPU
* Consistent with implementations in k-diffusion.
* Makes this compatible with https://github.com/AUTOMATIC1111/stable-diffusion-webui/pull/15823
2024-05-18 09:09:57 +10:00
huchenlei
b57a70f373 Proper fix of SD15 dtype 2024-05-17 13:34:04 -04:00
huchenlei
dca9007ac7 Fix SD15 dtype 2024-05-17 13:23:12 -04:00
drhead
cc9ca67664
Add transformer forward patch 2024-05-17 13:14:26 -04:00
drhead
53d67088ee
Patch timestep embedding to create tensor on-device 2024-05-17 12:12:57 -04:00
w-e-w
10f2407f48 xyz csv skipinitialspace 2024-05-18 01:00:48 +09:00
drhead
01491d303c
Keep sigmas on CPU 2024-05-17 10:36:08 -04:00
huchenlei
47f1d42a7e Fix for SD15 models 2024-05-16 20:06:04 -04:00
huchenlei
2a8a60c2c5 Add --precision half cmd option 2024-05-16 19:50:06 -04:00
huchenlei
58eec83a54 Fully prevent use_checkpoint 2024-05-16 16:39:02 -04:00
Andray
221ac0b9ab img2img batch upload method 2024-05-16 23:08:24 +04:00
huchenlei
b2ae4490b9 Fix LoRA bias error 2024-05-16 14:45:00 -04:00
huchenlei
51b13a8c54 Prevent uncessary bias backup 2024-05-16 11:39:01 -04:00
w-e-w
f015b94176 use torch_utils.float64 2024-05-16 23:19:06 +09:00
w-e-w
41f66849c7 mps, xpu compatibility 2024-05-16 23:18:20 +09:00
w-e-w
9c8075ba8e torch_utils.float64
return torch.float64 if device is not mps or xpu, else return torch.float32
2024-05-16 23:16:50 +09:00
huchenlei
3e20b36e8f Fix attr access 2024-05-15 17:27:01 -04:00
huchenlei
6a48476502 Fix flag check for SD15 2024-05-15 16:54:26 -04:00
huchenlei
9eb2f78631 Precompute is_sdxl_inpaint flag 2024-05-15 16:32:29 -04:00
huchenlei
0e98529365 Replace einops.rearrange with torch native 2024-05-15 15:46:53 -04:00
huchenlei
022d835565 use_checkpoint = False 2024-05-15 15:20:40 -04:00
w-e-w
5ab7d08a0a fix extention update when not on main branch 2024-05-15 17:27:05 +09:00
elf-mouse
ef7713fbb2 chore: sync v1.8.0 packages according to changelog, fix warning 2024-05-14 15:39:05 +08:00
w-e-w
d44f241317 use relative path for webui-assets css 2024-05-11 13:13:39 +09:00
Logan
d6b4444069 Use shared.sd_model.is_sdxl to determine base AYS sigmas 2024-05-10 18:05:45 +10:00
Logan
73d1caf8f2 Add Align Your Steps to available schedulers
* Include both SDXL and SD 1.5 variants (https://research.nvidia.com/labs/toronto-ai/AlignYourSteps/howto.html)
2024-05-10 12:38:10 +10:00
MarcusNyne
d2cc8ccb11 When creating a virtual environment, upgrade pip
Pip will be upgraded upon immediately creating the virtual environment.  If the pip upgrade fails, this should not cause the script to fail (treat as a warning).  After the environment is created, it will not attempt further updates to pip.
2024-05-09 17:16:53 -04:00
MarcusNyne
5fbac49791 Added --models-dir option
The --model-dir option overrides the location of the models directory for stable diffusion, so that models can be shared across multiple installations.  When --data-dir is specified alone, both the extensions and models folders are present in this folder.  --models-dir can be used independently, but when used with --data-dir, then the models folder is specified by --models-dir, and extensions are found in the --data-dir.
2024-05-08 16:48:10 -04:00
LoganBooker
f7e349cea4
Add AVIF MIME type support to mimetype definitions
AVIF images will open, rather than download, as the default behaviour.
2024-05-08 21:23:18 +10:00
JLipnerPitt
e736c3b36b
Add files via upload
Fixed an error (AttributeError: 'str' object has no attribute 'decode') coming from line 792 in images.py when trying to upscale certain images.
2024-05-08 05:22:12 -04:00
Andray
dbda59e58a fix context menu position 2024-05-07 19:26:16 +04:00
bluelovers
dd93c47abf
Update imageviewer.js 2024-05-07 19:53:18 +08:00
w-e-w
f12886aefa use script_path for webui root in launch_utils 2024-05-04 23:42:37 +09:00
Andray
7195c4d42c two fingers press to open context menu 2024-05-01 22:50:46 +04:00
w-e-w
5d5224b322 fix_p_invalid_sampler_and_scheduler 2024-05-02 02:25:16 +09:00
Andray
0e0e41eabc use gradio theme colors in css 2024-05-01 16:54:47 +04:00
w-e-w
89103b4747 lora bundled TI infotext
Co-Authored-By: Morgon Kanter <9632805+mx@users.noreply.github.com>
2024-05-01 19:41:02 +09:00
w-e-w
9d39380705 fix extra batch mode P Transparency
red, green, blue = transparency TypeError: cannot unpack non-iterable int object
2024-04-30 19:17:53 +09:00
missionfloyd
c8336c45b9 Use existing function for old sampler names 2024-04-30 01:53:41 -06:00
missionfloyd
4c7b22d37d Fix dragging text within prompt input 2024-04-28 22:46:11 -06:00
missionfloyd
579f1ef278 Allow old sampler names in API 2024-04-28 22:36:43 -06:00
huchenlei
3d3fc81f48 Add correct mimetype for .mjs files 2024-04-28 16:14:12 -04:00
drhead
3a215deff2
vectorize kl-optimal sigma calculation
Co-authored-by: mamei16 <marcel.1710@live.de>
2024-04-28 00:15:58 -04:00
w-e-w
9d964d3fc3 no-referrer 2024-04-27 19:21:34 +09:00
Brendan Hoar
60c0799958
Linter - except must not be bare. 2024-04-26 08:21:12 -04:00
Brendan Hoar
44afb48447
Linter fix - extraneous whitespace 2024-04-26 08:17:37 -04:00
Brendan Hoar
c5ae225418
Better handling of embeddings with two rare, but not unusual, files in them
I have encountered pickled embeddings with a short byteorder file at the top-level, as well as a .data/serialization_id file.

Both load fine after allowing these files in the dataset.

I do not think it is likely adding them to the safe unpickle regular expression would be a security risk, but that's for the maintainers to decide.
2024-04-26 07:55:39 -04:00
Brendan Hoar
c5b7559856
Better error handling when unable to extract contents of embedding/TI file 2024-04-26 06:57:32 -04:00
Brendan Hoar
8dc920228e
Better error handling when unable to read metadata from safetensors file 2024-04-26 06:52:21 -04:00
Brendan Hoar
3902aa222b
Better error handling to skip non-standard ss_tag_frequency content 2024-04-26 06:44:41 -04:00
w-e-w
d5f6fdb3c4 compact-checkbox-group 2024-04-26 18:47:04 +09:00
Andray
e85e327ae0 more extension tag filtering options 2024-04-25 13:26:26 +04:00
w-e-w
1091e3a37e update jpeg_quality description 2024-04-24 02:54:26 +09:00
w-e-w
8fa3fa76c3 fix exif_bytes referenced before assignment 2024-04-24 02:41:31 +09:00
pinanew
50bb6e1179
AVIF has quality setting too 2024-04-23 18:45:42 +03:00
drhead
029adbe531
implement option to skip uncond on all steps below ngms 2024-04-23 03:15:56 -04:00
drhead
33cbbf9f8b
add s_min_uncond_all option 2024-04-23 03:15:00 -04:00
drhead
6e9b69a338
change skip_early_cond code to use float 2024-04-23 03:08:28 -04:00
drhead
83182d2799
change skip early cond option name and to float 2024-04-23 03:07:25 -04:00
drhead
83266205d0
Add KL Optimal scheduler 2024-04-23 00:09:43 -04:00
drhead
8016d78a4b
add option for early cfg skip 2024-04-22 23:42:24 -04:00
drhead
a1aa0af8a4
add code for skipping CFG on early steps 2024-04-22 23:38:44 -04:00
w-e-w
c69773d7e8 ensure integrity for initial sd model download 2024-04-23 03:09:45 +09:00
w-e-w
246c269af8 add option to check file hash after download
if the sha256 hash does not match it will be automatically deleted
2024-04-23 03:09:45 +09:00
w-e-w
4bc39d234d Show LoRA if model is None 2024-04-23 02:39:45 +09:00
w-e-w
2b717bb195 fix initial corrupt model loop
if for some reason the initial loading model at loading phase of webui  is corrupted
after entering this state the user will not be able to load even a good model is selected, due the the unload_model_weights  > send_model_to_cpu > m.lowvram attribute check will fail becaules m is None
webui will be stuck in the loop unable to recover without manual intervention
2024-04-23 02:35:25 +09:00
AUTOMATIC1111
ddb28b33a3 Merge branch 'master' into dev 2024-04-22 18:01:16 +03:00
AUTOMATIC1111
1c0a0c4c26 Merge branch 'dev' 2024-04-22 18:00:36 +03:00
AUTOMATIC1111
7dfe959f4b update changelog 2024-04-22 18:00:23 +03:00
AUTOMATIC1111
8f64dad282
Merge pull request #15594 from AUTOMATIC1111/fix-get_crop_region_v2
fix get_crop_region_v2
2024-04-22 17:57:39 +03:00
w-e-w
821adc3041 fix get_crop_region_v2
Co-Authored-By: Dowon <ks2515@naver.com>
2024-04-22 23:10:19 +09:00
AUTOMATIC1111
e2b177c508 Merge branch 'dev' 2024-04-22 12:26:24 +03:00
AUTOMATIC1111
e837124f4b changelog 2024-04-22 12:26:05 +03:00
AUTOMATIC1111
3fdc3cfbbf
Merge pull request #15591 from AUTOMATIC1111/restore-1.8.0-style-naming-of-scripts
Restore 1.8.0 style naming of scripts
2024-04-22 12:24:06 +03:00
w-e-w
e9809de651 restore 1.8.0-style naming of scripts 2024-04-22 18:23:58 +09:00
AUTOMATIC1111
61f6479ea9 restore 1.8.0-style naming of scripts 2024-04-22 12:19:30 +03:00
AUTOMATIC1111
e84703b253 update changelog 2024-04-22 11:59:54 +03:00
AUTOMATIC1111
e4aa0c362e
Merge pull request #15587 from AUTOMATIC1111/fix-mistake-in-#15583
fix mistake in #15583
2024-04-22 11:50:34 +03:00
AUTOMATIC1111
a183ea4ba7 undo adding scripts to sys.modules 2024-04-22 11:49:55 +03:00
w-e-w
6c7c176dc9 fix mistake in #15583 2024-04-22 00:10:49 +09:00
AUTOMATIC1111
e6a8d0b4e6
Merge pull request #15583 from AUTOMATIC1111/get_crop_region_v2
get_crop_region_v2
2024-04-21 18:06:40 +03:00
w-e-w
db263df5d5 get_crop_region_v2 2024-04-21 19:34:11 +09:00
AUTOMATIC1111
d1998d747d
Merge pull request #15531 from thatfuckingbird/fix-mistyped-function-name
fix typo in function call (eror -> error)
2024-04-21 07:43:19 +03:00
AUTOMATIC1111
c0eaeb15af
Merge pull request #15532 from huchenlei/fix_module
Fix cls.__module__ value in extension script
2024-04-21 07:42:57 +03:00
AUTOMATIC1111
9bcfb92a00 rename logging from textual inversion to not confuse it with global logging module 2024-04-21 07:41:28 +03:00
AUTOMATIC1111
d74fc56fa5
Merge pull request #15547 from AUTOMATIC1111/numpy-DeprecationWarning-product---prod
numpy DeprecationWarning product -> prod
2024-04-21 07:23:38 +03:00
AUTOMATIC1111
a44ed231c2
Merge pull request #15555 from light-and-ray/fix_x1_upscalers
fix x1 upscalers
2024-04-21 07:22:58 +03:00
AUTOMATIC1111
daae17851a
Merge pull request #15560 from AUTOMATIC1111/api-downscale
Remove API upscaling factor limits
2024-04-21 07:22:30 +03:00
AUTOMATIC1111
ce19a7baef
Merge pull request #15544 from cabelo/master
Compatibility with Debian 11, Fedora 34+ and openSUSE 15.4+
2024-04-21 07:22:04 +03:00
AUTOMATIC1111
8d6e72dbfa
Merge pull request #15561 from speculativemoonstone/fix-launch-git-directories
Allow webui.sh to be runnable from arbitrary directories containing a .git file
2024-04-21 07:21:21 +03:00
AUTOMATIC1111
6f4f6bff6b add more info to the error message for #15567 2024-04-21 07:18:58 +03:00
AUTOMATIC1111
367b823466
Merge pull request #15567 from AUTOMATIC1111/no-image-data-blocks-debug
Hide 'No Image data blocks found.' message
2024-04-21 07:09:27 +03:00
AUTOMATIC1111
c8ac42aad1
Merge pull request #15533 from travisg/callback-fix
fix: remove_callbacks_for_function should also remove from the ordered map
2024-04-21 07:07:58 +03:00
AUTOMATIC1111
449bc7bcf3
Merge pull request #15534 from storyicon/fix-masking
Fix images do not match / Coordinate 'right' is less than 'left'
2024-04-21 07:06:45 +03:00
AUTOMATIC1111
3810413c00
Merge pull request #15581 from AUTOMATIC1111/FilenameGenerator-sampler-scheduler
FilenameGenerator Sampler Scheduler
2024-04-21 07:00:28 +03:00
AUTOMATIC1111
f8f5d6cea2
Merge pull request #15577 from AUTOMATIC1111/api-get-schedulers
Add schedulers API endpoint
2024-04-21 06:59:56 +03:00
AUTOMATIC1111
cde35bebe9
Merge pull request #15582 from kaanyalova/avif-support
Add avif support
2024-04-21 06:59:38 +03:00
kaanyalova
49fee7c8db Add avif support 2024-04-20 23:26:04 +03:00
w-e-w
b5b1487f6a FilenameGenerator Sampler Scheduler 2024-04-21 04:52:03 +09:00
missionfloyd
5cb567c138 Add schedulers API endpoint 2024-04-19 20:32:09 -06:00
missionfloyd
d212fb59fe Hide 'No Image data blocks found.' message 2024-04-18 20:56:51 -06:00
storyicon
71314e47b1 feat:compatible with inconsistent/empty mask
Signed-off-by: storyicon <storyicon@foxmail.com>
2024-04-18 11:59:25 +00:00
Speculative Moonstone
ba2a737cce
Allow webui.sh to be runnable from directories containing a .git file 2024-04-18 04:25:32 +00:00
missionfloyd
909c3dfe83 Remove API upscaling factor limits 2024-04-17 21:20:03 -06:00
Andray
9d4fdc45d3 fix x1 upscalers 2024-04-18 01:53:23 +04:00
w-e-w
63fd38a04f numpy DeprecationWarning product -> prod 2024-04-17 15:54:45 +09:00
Alessandro de Oliveira Faria (A.K.A. CABELO)
50190ca669 Compatibility with Debian 11, Fedora 34+ and openSUSE 15.4+ 2024-04-17 00:01:56 -03:00
storyicon
0980fdfe8c fix: images do not match
Signed-off-by: storyicon <storyicon@foxmail.com>
2024-04-16 07:35:33 +00:00
Travis Geiselbrecht
bba306d414 fix: remove callbacks properly in remove_callbacks_for_function()
Like remove_current_script_callback just before, also remove from the
ordered_callbacks_map to keep the callback map and ordered callback map
in sync.
2024-04-15 21:10:11 -07:00
huchenlei
a95326bec4 nit 2024-04-15 22:34:01 -04:00
huchenlei
0f82948e4f Fix cls.__module__ 2024-04-15 22:14:19 -04:00
thatfuckingbird
8e1c3561be
fix typo in function call (eror -> error) 2024-04-15 21:17:24 +02:00
AUTOMATIC1111
ff6f4680c4 Merge branch 'master' into dev 2024-04-13 06:38:58 +03:00
AUTOMATIC1111
adadb4e3c7 Merge branch 'release_candidate' 2024-04-13 06:37:28 +03:00
AUTOMATIC1111
d282d24800 update changelog 2024-04-13 06:37:03 +03:00
AUTOMATIC1111
a196319edf Merge pull request #15492 from w-e-w/update-restricted_opts
update restricted_opts
2024-04-11 19:34:10 +03:00
AUTOMATIC1111
3fadb4fc85
Merge pull request #15492 from w-e-w/update-restricted_opts
update restricted_opts
2024-04-11 19:33:55 +03:00
w-e-w
592e40ebe9 update restricted_opts 2024-04-11 23:10:25 +09:00
storyicon
4068429ac7 fix: Coordinate 'right' is less than 'left'
Signed-off-by: storyicon <storyicon@foxmail.com>
2024-04-10 10:53:25 +00:00
AUTOMATIC1111
88f70ce63c Merge pull request #15470 from AUTOMATIC1111/read-infotext-Script-not-found
error handling paste_field callables
2024-04-09 16:01:13 +03:00
AUTOMATIC1111
ac8ffb34e3
Merge pull request #15470 from AUTOMATIC1111/read-infotext-Script-not-found
error handling paste_field callables
2024-04-09 16:00:56 +03:00
w-e-w
ef83f6831f catch exception for all paste_fields callable 2024-04-09 21:32:58 +09:00
w-e-w
600f339c4c Warning when Script is not found 2024-04-09 21:32:41 +09:00
AUTOMATIC1111
7f691612ca Merge pull request #15460 from AUTOMATIC1111/create_infotext-index-and-callable
create_infotext allow index and callable, re-work Hires prompt infotext
2024-04-09 12:05:15 +03:00
AUTOMATIC1111
a976f4dff9
Merge pull request #15460 from AUTOMATIC1111/create_infotext-index-and-callable
create_infotext allow index and callable, re-work Hires prompt infotext
2024-04-09 12:05:02 +03:00
AUTOMATIC1111
696d6813e0 Merge pull request #15465 from jordenyt/fix-extras-api-upscale-enabled
Fix extra-single-image API not doing upscale failed
2024-04-09 11:00:49 +03:00
AUTOMATIC1111
c48b6bf6bd
Merge pull request #15465 from jordenyt/fix-extras-api-upscale-enabled
Fix extra-single-image API not doing upscale failed
2024-04-09 11:00:30 +03:00
Jorden Tse
2580235c72 Fix extra-single-image API not doing upscale failed 2024-04-09 11:13:47 +08:00
AUTOMATIC1111
3786f3742f fix limited file write (thanks, Sylwia) 2024-04-08 16:15:55 +03:00
AUTOMATIC1111
d9708c92b4 fix limited file write (thanks, Sylwia) 2024-04-08 16:15:25 +03:00
w-e-w
e3aabe6959 add documentation for create_infotext 2024-04-08 20:14:02 +09:00
w-e-w
1e1176b6eb non-serializable as None 2024-04-08 18:18:33 +09:00
w-e-w
219e64489c re-work extra_generation_params for Hires prompt 2024-04-08 18:18:13 +09:00
w-e-w
47ed9b2d39 allow list or callables in generation_params 2024-04-08 01:39:31 +09:00
w-e-w
6efdfe3234 if use use_main_prompt index = 0 2024-04-07 22:58:12 +09:00
AUTOMATIC1111
e1640314df 1.9.0 changelog 2024-04-06 21:46:56 +03:00
AUTOMATIC1111
c16a27caa9
Merge pull request #15446 from AUTOMATIC1111/re-add-update_file_entry
re-add update_file_entry
2024-04-06 10:02:45 +03:00
w-e-w
2ad17a6100 re-add update_file_entry
MassFileLister.update_file_entry was accidentally removed in https://github.com/AUTOMATIC1111/stable-diffusion-webui/pull/15205/files#diff-c39b942d8f8620d46d314db8301189b8d6195fc97aedbeb124a33694b738d69cL151-R173
2024-04-06 15:56:57 +09:00
AUTOMATIC1111
23c06a51cc use 'scripts.' prefix for names of dynamically loaded modules 2024-04-06 09:05:04 +03:00
AUTOMATIC1111
badb70da48
Merge pull request #15423 from storyicon/master
feat: ensure the indexability of dynamically imported packages
2024-04-06 09:00:35 +03:00
AUTOMATIC1111
447198f21b
Merge pull request #15442 from AUTOMATIC1111/open_folder-as-util
open_folder as util
2024-04-06 08:54:20 +03:00
AUTOMATIC1111
acb20338b1 put HF_ENDPOINT into shared for #15443 2024-04-06 08:53:21 +03:00
AUTOMATIC1111
73f7812045
Merge pull request #15443 from Satariall/add-hf_endpoint-variable
Use HF_ENDPOINT variable for HuggingFace domain with default
2024-04-06 08:48:55 +03:00
Marsel Markhabulin
989b89b12a
Use HF_ENDPOINT variable for HuggingFace domain with default
Modified the list_models function to dynamically construct the model URL
by using an environment variable for the HuggingFace domain. This allows
for greater flexibility in specifying the domain and ensures that the
modification is also compatible with the Hub client library. By
supporting different environments or requirements without hardcoding the
domain name, this change facilitates the use of custom HuggingFace
domains not only within our code but also when interacting with the Hub
client library.
2024-04-05 13:02:49 +03:00
w-e-w
20123d427b open_folder docstring 2024-04-05 16:19:20 +09:00
w-e-w
a05d89b1e5
Merge branch 'dev' into open_folder-as-util 2024-04-05 15:14:38 +08:00
w-e-w
92e6aa3653 open_folder as util 2024-04-05 16:08:45 +09:00
AUTOMATIC1111
b372fb6165 fix API upscale 2024-04-01 23:33:45 +03:00
AUTOMATIC1111
0cb2bbd01a
Merge pull request #15428 from v0xie/fix/remove-callbacks
Fix: Remove script callbacks in ordered_callbacks_map
2024-04-01 23:25:03 +03:00
v0xie
a669b8a6bc fix: remove script callbacks in ordered_callbacks_map 2024-04-01 12:51:09 -07:00
AUTOMATIC1111
719296133d
Merge pull request #15425 from light-and-ray/fix_upscaler_2_images_do_not_match
fix upscaler 2 images do not match
2024-04-01 13:40:09 +03:00
Andray
86861f8379 fix upscaler 2 images do not match 2024-04-01 13:58:45 +04:00
storyicon
e73a7e4006 feat: ensure the indexability of dynamically imported packages
Signed-off-by: storyicon <storyicon@foxmail.com>
2024-04-01 09:13:07 +00:00
AUTOMATIC1111
aa4a45187e
Merge pull request #15417 from light-and-ray/fix_upscaler_2
Fix upscaler 2: add missed max_side_length
2024-03-31 18:49:21 +03:00
Andray
0a7d1e756f fix upscaler 2 2024-03-31 19:34:58 +04:00
AUTOMATIC1111
859f0f6b19
Merge pull request #15415 from light-and-ray/fix_dcd4f880a86e500ec88ddf7eafe65894a24b85a3
fix dcd4f880a86e500ec88ddf7eafe65894a24b85a3
2024-03-31 16:55:47 +03:00
AUTOMATIC1111
23ef5027c6
Merge pull request #15414 from DrBiggusDickus/dev2
Fix CodeFormer weight
2024-03-31 16:53:23 +03:00
Andray
4ccbae320e fix dcd4f880a86e500ec88ddf7eafe65894a24b85a3 2024-03-31 17:05:15 +04:00
DrBiggusDickus
ea83180761 fix CodeFormer weight 2024-03-31 14:41:06 +02:00
AUTOMATIC1111
f1a6c5fe17 add an option to hide postprocessing options in Extras tab 2024-03-31 08:30:00 +03:00
AUTOMATIC1111
bfa20d2758 resize Max side length field 2024-03-31 08:20:19 +03:00
AUTOMATIC1111
dcd4f880a8 rework code/UI for #15293 2024-03-31 08:17:22 +03:00
AUTOMATIC1111
7f3ce06de9
Merge pull request #15293 from light-and-ray/extras_upscaler_limit_target_resolution
Extras upscaler: option limit target resolution
2024-03-31 08:02:35 +03:00
AUTOMATIC1111
8bebfde701
Merge pull request #15350 from baseco/memory-bug-fix
minor bug fix of sd model memory management
2024-03-30 07:37:10 +03:00
AUTOMATIC1111
98096195dd
Merge pull request #15382 from huaizong/fix/whz/model-loaded-remove-v3
fix: when find already_loaded model, remove loaded by array index
2024-03-30 07:36:33 +03:00
AUTOMATIC1111
642bca4c3d
Merge pull request #15380 from light-and-ray/interrupt_upscale
interrupt upscale
2024-03-30 07:34:34 +03:00
AUTOMATIC1111
80b87107de
Merge pull request #15386 from eltociear/patch-3
fix typo in call_queue.py
2024-03-30 07:34:10 +03:00
AUTOMATIC1111
c4c8a64111 restore the line lost in the merge 2024-03-30 07:33:39 +03:00
AUTOMATIC1111
470d402b17
Merge pull request #15390 from ochen1/patch-1
fix: Python version check for PyTorch installation compatibility
2024-03-30 07:32:29 +03:00
AUTOMATIC1111
1dc8cc1bce
Merge branch 'dev' into patch-1 2024-03-30 07:31:08 +03:00
AUTOMATIC1111
8687163f7f
Merge pull request #15394 from light-and-ray/fix_ui_config_for_hires_sampler_and_scheduler
fix ui_config for hires sampler and scheduler
2024-03-27 16:42:09 +03:00
Andray
4e2bb7250f fix_ui_config_for_hires_sampler_and_scheduler 2024-03-27 15:35:06 +04:00
ochen1
5461b00e89
fix: Python version check for PyTorch installation compatibility 2024-03-26 21:22:09 -06:00
Ikko Eltociear Ashimine
16522cb0e3
fix typo in call_queue.py
amout -> amount
2024-03-27 03:01:06 +09:00
Andray
c321680b3d interrupt upscale 2024-03-26 14:53:38 +04:00
王怀宗
f4633cb9c0 fix: when find already_loaded model, remove loaded by array index 2024-03-26 18:29:51 +08:00
Boning
f62217b65d minor bug fix of sd model memory management 2024-03-25 10:38:15 -07:00
AUTOMATIC1111
dfbdb5a135 put request: gr.Request at start of img2img function similar to txt2img 2024-03-25 18:00:58 +03:00
AUTOMATIC1111
b0b90dc0d7
Merge pull request #15319 from catboxanon/feat/ssmd_cover_images
Support cover images embedded in safetensors metadata
2024-03-24 13:43:37 +03:00
AUTOMATIC1111
9aa9e980a9 support scheduler selection in hires fix 2024-03-24 11:00:16 +03:00
catboxanon
c4402500c7 Support ssmd_cover_images 2024-03-24 02:33:10 -04:00
AUTOMATIC1111
755d2cb2e5
Merge pull request #15343 from light-and-ray/escape_brackets_in_lora_random_prompt
escape brackets in lora random prompt generator
2024-03-24 05:31:33 +03:00
AUTOMATIC1111
0affa24ce2
Merge pull request #15354 from akx/xyz-script-size
Add Size as an XYZ Grid option
2024-03-24 05:27:00 +03:00
AUTOMATIC1111
bf2f7b3af4
Merge pull request #15333 from AUTOMATIC1111/scheduler_selection
Scheduler selection in main UI
2024-03-24 05:21:56 +03:00
AUTOMATIC1111
db61b876d6
Merge pull request #15361 from kaalibro/fix/scheduler_selection
Fix for "Scheduler selection" #15333
2024-03-24 05:21:40 +03:00
kaalibro
f3ca6a92ad
Fix for #15333
- Fix "X/Y/Z plot" not working with "Schedule type"
- Fix "Schedule type" not being saved to "params.txt"
2024-03-23 00:50:37 +05:00
Aarni Koskela
2941e1f1f3 Add Size as an XYZ Grid option 2024-03-22 12:04:03 +02:00
Andray
721c4309c2 escape brackets in lora random prompt generator 2024-03-21 16:29:51 +04:00
AUTOMATIC1111
57727e554d make #15334 work without making copies of images 2024-03-21 07:22:27 +03:00
AUTOMATIC1111
b80b1cf92c
Merge pull request #15334 from Gourieff/extras--allow-png-rgba--dev
Allow PNG-RGBA for Extras Tab
2024-03-21 07:21:15 +03:00
AUTOMATIC1111
5c5594ff16 linter 2024-03-21 07:09:40 +03:00
AUTOMATIC1111
65075896f2
Merge pull request #15310 from Dalton-Murray/update-pytorch-lightning-utilities
Update pytorch lightning utilities
2024-03-21 07:08:43 +03:00
Dalton
32ba757501
Re-add import but after if check 2024-03-20 23:55:04 -04:00
Dalton
4e6e2574ab
Cleanup ddpm_edit.py
Fully reverts this time
2024-03-20 23:36:35 -04:00
Dalton
41907b25f0
Cleanup sd_hijack_ddpm_v1.py
Forgot some things to revert
2024-03-20 23:35:32 -04:00
Dalton
4bc2963320
Remove unnecessary import 2024-03-20 23:33:15 -04:00
Dalton
4eb5e09873
Update initialize_util.py 2024-03-20 23:28:40 -04:00
Dalton
b5b04912b5
Include running pytorch lightning check 2024-03-20 23:06:00 -04:00
Dalton
f010dfffb9
Revert ddpm_edit.py 2024-03-20 23:02:30 -04:00
Dalton
5fd9a40b92
Revert sd_hijack_ddpm_v1.py 2024-03-20 23:01:50 -04:00
Art Gourieff
e0cad0f87a Merge remote-tracking branch 'upstream/dev' into extras--allow-png-rgba--dev 2024-03-20 15:28:17 +07:00
Art Gourieff
8ec8901921 FIX: No specific type for 'image' arg
Roll back
2024-03-20 15:20:29 +07:00
Art Gourieff
702edb288e FIX: initial_pp RGBA right way 2024-03-20 15:14:28 +07:00
AUTOMATIC1111
31306ce672 change the behavior of discard_next_to_last_sigma for sgm_uniform to match other schedulers 2024-03-20 10:29:52 +03:00
AUTOMATIC1111
ac9aa44cb8 do not add 'Automatic' to infotext 2024-03-20 10:27:53 +03:00
AUTOMATIC1111
76f8436bfa add Uniform scheduler 2024-03-20 10:27:32 +03:00
Art Gourieff
61f488302f FIX: Allow PNG-RGBA for Extras Tab 2024-03-20 13:28:32 +07:00
AUTOMATIC1111
25cd53d775 scheduler selection in main UI 2024-03-20 09:17:11 +03:00
AUTOMATIC1111
060e55dfe3
Merge pull request #15331 from AUTOMATIC1111/extra-networks-buttons
Fix extra networks buttons when filename contains an apostrophe
2024-03-20 06:53:55 +03:00
missionfloyd
b5c33341a1
Don't use quote_js on filename 2024-03-19 19:06:56 -06:00
missionfloyd
6e420c7be2
Merge branch 'dev' into extra-networks-buttons 2024-03-19 19:03:53 -06:00
missionfloyd
d7f48472cc Fix extra networks buttons when filename contains an apostrophe 2024-03-19 18:50:25 -06:00
Dalton
49779413aa
Formatting sd_hijack_ddpm_v1.py 2024-03-19 14:54:06 -04:00
Dalton
8f450321fe
Formatting ddpm_edit 2024-03-19 14:53:30 -04:00
Dalton
86276832e0
Update sd_hijack_ddpm_v1.py 2024-03-19 14:45:07 -04:00
Dalton
61f321756f
Update ddpm_edit.py 2024-03-19 14:44:31 -04:00
AUTOMATIC1111
d44b8aa8c1
Merge pull request #15325 from AUTOMATIC1111/sgm_uniform
Sgm uniform scheduler for SDXL-Lightning models
2024-03-19 21:37:16 +03:00
Kohaku-Blueleaf
a6b5a513f9 Implementation for sgm_uniform branch 2024-03-19 20:05:54 +08:00
AUTOMATIC1111
c4a00affc5 use existing quote_js function for #15316 2024-03-19 08:10:27 +03:00
AUTOMATIC1111
522121be7e
Merge pull request #15316 from AUTOMATIC1111/escape-filename
Escape btn_copy_path filename
2024-03-19 08:02:36 +03:00
missionfloyd
3fa1ebed62 Escape btn_copy_path filename 2024-03-18 21:47:52 -06:00
AUTOMATIC1111
7ac7600dc3
Merge pull request #15307 from AUTOMATIC1111/restore-outputs-path
restore outputs path
2024-03-18 19:33:48 +03:00
w-e-w
e9d4da7b56 restore outputs path
output -> outputs
2024-03-19 00:54:56 +09:00
AUTOMATIC1111
c4664b5a9c fix for listing wrong requirements for extensions 2024-03-18 08:00:42 +03:00
Andray
203afa39c4 update tooltip 2024-03-18 06:52:46 +04:00
Dalton
51cb20ec39
Update ddpm_edit.py 2024-03-17 22:45:31 -04:00
Dalton
2a6054f836
Update sd_hijack_ddpm_v1.py 2024-03-17 22:37:19 -04:00
AUTOMATIC1111
8ac4a207f3
Merge pull request #15299 from AUTOMATIC1111/diskcache-bett
Tweak diskcache limits
2024-03-17 23:59:12 +03:00
Aarni Koskela
df4da02ab0 Tweak diskcache limits 2024-03-17 20:25:25 +00:00
AUTOMATIC1111
f1b090e9e0
Merge pull request #15287 from AUTOMATIC1111/diskcache
use diskcache library for caching
2024-03-17 23:20:00 +03:00
AUTOMATIC1111
611faaddef change the default name for callback from None to "unnamed" 2024-03-17 23:19:24 +03:00
AUTOMATIC1111
daa1b33247 make reloading UI scripts optional when doing Reload UI, and off by default 2024-03-17 18:16:12 +03:00
Andray
fd83d4eec3 add .needs_reload_ui() 2024-03-17 18:19:13 +04:00
Andray
81be357925 hide limit target resolution under option 2024-03-17 14:51:19 +04:00
AUTOMATIC1111
79cbc92abf change code for variant requirements in metadata.ini 2024-03-17 13:30:20 +03:00
Andray
06c5dd0907 maybe fix tests 2024-03-17 14:28:26 +04:00
AUTOMATIC1111
908d522057 update ruff to 0.3.3 2024-03-17 13:19:44 +03:00
AUTOMATIC1111
4ce2e25c0b
Merge pull request #15290 from light-and-ray/allow_variants_for_extension_name_in_metadata.ini
allow variants for extension name in metadata.ini
2024-03-17 13:19:23 +03:00
Andray
ef35619325 Extras upscaler: option limit target resolution 2024-03-17 14:14:12 +04:00
Andray
b1cd0189bc allow variants for extension name in metadata.ini 2024-03-17 13:05:35 +04:00
AUTOMATIC1111
c95c46004a
Merge pull request #15288 from light-and-ray/allow_use_zoom.js_outside_webui_context
little fixes zoom.js
2024-03-17 09:48:33 +03:00
Andray
c3f75d1d85 little fixes zoom.js 2024-03-17 10:30:11 +04:00
AUTOMATIC1111
c12ba58433
Merge pull request #15286 from light-and-ray/allow_use_zoom.js_outside_webui_context
allow use zoom.js outside webui context [for extensions]
2024-03-17 09:20:51 +03:00
AUTOMATIC1111
66355b4775 use diskcache library for caching 2024-03-17 09:18:32 +03:00
Andray
e9b8a89b3c allow use zoom.js outside webui context 2024-03-17 09:29:11 +04:00
AUTOMATIC1111
93c7b9d7fc linter for #15262 2024-03-17 07:02:31 +03:00
AUTOMATIC1111
6d8b7ec188
Merge pull request #15262 from catboxanon/feat/dragdrop-urls
Support dragdrop for URLs to read infotext
2024-03-17 07:02:08 +03:00
catboxanon
446cd5a58b
dragdrop: add error handling for URLs 2024-03-16 20:19:12 -04:00
missionfloyd
83a9dd82db Download image client-side 2024-03-16 17:10:26 -06:00
missionfloyd
3da13f0cc9 Fix dragging to/from firefox 2024-03-16 15:46:29 -06:00
AUTOMATIC1111
df8c09bcb3
Merge pull request #15283 from AUTOMATIC1111/dora-weight-decompose
Use correct DoRA implementation
2024-03-16 20:20:08 +03:00
AUTOMATIC1111
8dcb8faf5d
Merge branch 'dev' into dora-weight-decompose 2024-03-16 20:20:02 +03:00
Kohaku-Blueleaf
199c51d688 linter 2024-03-17 00:00:07 +08:00
Kohaku-Blueleaf
1792e193b1 Use correct implementation, fix device error 2024-03-16 23:52:29 +08:00
AUTOMATIC1111
bf35c66183 fix for #15179 2024-03-16 18:45:19 +03:00
AUTOMATIC1111
cb09e1ef7d
Merge pull request #15179 from llnancy/master
fix: fix syntax errors
2024-03-16 18:45:01 +03:00
AUTOMATIC1111
0283826179 prevent make alt key from opening main menu if it's used for brush size also 2024-03-16 18:44:36 +03:00
AUTOMATIC1111
2f9d1c33e2
Merge pull request #15267 from light-and-ray/prevent_alt_menu_on_firefox
prevent alt menu for firefox
2024-03-16 18:31:55 +03:00
AUTOMATIC1111
874809e0ca
Merge pull request #15268 from light-and-ray/handle_0_wheel_deltaX
handle 0 wheel deltaY
2024-03-16 18:25:00 +03:00
Andray
c364b60776 handle 0 wheel deltaX 2024-03-16 18:08:02 +04:00
Andray
7598a92436 use e.key instead of e.code 2024-03-16 17:49:05 +04:00
Andray
eb2ea8df1d check e.key in up event 2024-03-16 17:42:25 +04:00
Andray
9142ce8188 fix linter and do not require reload page if option was changed 2024-03-16 16:14:57 +04:00
Andray
79514e5b8e prevent defaults for alt only if mouse inside image 2024-03-16 16:06:21 +04:00
AUTOMATIC1111
bb9df5cdc9
Merge pull request #15276 from AUTOMATIC1111/v180_hr_styles-actual-version-number
v180_hr_styles actual version number
2024-03-16 12:40:24 +03:00
AUTOMATIC1111
e8613dbc93
Merge pull request #15231 from light-and-ray/fix_ui-config_for_InputAccordion
fix ui-config for InputAccordion [custom_script_source]
2024-03-16 12:35:43 +03:00
Andray
cc8ea32501 fix ui-config for InputAccordion 2024-03-16 12:32:39 +04:00
w-e-w
38a7dc5488 v180_hr_styles actual version number 2024-03-16 17:19:38 +09:00
AUTOMATIC1111
5bd2724765
Merge pull request #15205 from AUTOMATIC1111/callback_order
Callback order
2024-03-16 09:45:41 +03:00
AUTOMATIC1111
9fd693272f
Merge pull request #15211 from light-and-ray/type_hintinh_in_shared.py
type hinting in shared.py
2024-03-16 09:45:30 +03:00
AUTOMATIC1111
f7bad19e00
Merge pull request #15221 from AUTOMATIC1111/fix-Restore-progress
fix "Restore progress" button
2024-03-16 09:44:50 +03:00
AUTOMATIC1111
03ea0f3bfc
Merge pull request #15222 from light-and-ray/move_postprocessing-for-training_into_builtin_extensions
move postprocessing-for-training into builtin extensions
2024-03-16 09:43:01 +03:00
AUTOMATIC1111
2fc47b44c2
Merge pull request #15223 from light-and-ray/move_upscale_postprocessing_under_input_accordion
move upscale postprocessing under input accordion
2024-03-16 09:41:40 +03:00
AUTOMATIC1111
446e49d6db
Merge branch 'dev' into move_upscale_postprocessing_under_input_accordion 2024-03-16 09:41:16 +03:00
AUTOMATIC1111
8bc9978909
Merge pull request #15228 from wangshuai09/ascend_npu_readme
Ascend NPU wiki page
2024-03-16 09:39:34 +03:00
AUTOMATIC1111
1282bceeba
Merge pull request #15233 from light-and-ray/featch_only_active_branch_updates_for_extensions
featch only active branch updates for extensions
2024-03-16 09:06:33 +03:00
AUTOMATIC1111
d38b390ed4
Merge pull request #15239 from AUTOMATIC1111/Fix-lora-bugs
Add missing .mean() back
2024-03-16 09:06:05 +03:00
AUTOMATIC1111
63c3c4dbc3 simplify code for #15244 2024-03-16 09:04:08 +03:00
AUTOMATIC1111
afb9296e0d
Merge pull request #15244 from Haoming02/auto-scale-by
Automatically Set the Scale by value when user selects an Upscale Model
2024-03-16 08:49:32 +03:00
AUTOMATIC1111
c9244ef83a
Merge pull request #15224 from DGdev91/dev
Better workaround for Navi1, removing --pre for Navi3
2024-03-16 08:45:02 +03:00
AUTOMATIC1111
a072c1997d
Merge pull request #15259 from AUTOMATIC1111/PEP-604-annotations
PEP 604 annotations
2024-03-16 08:43:02 +03:00
AUTOMATIC1111
3cb698ac15
Merge pull request #15260 from v0xie/fix-OFT-MhA-AttributeError
Fix AttributeError in OFT when trying to get MultiheadAttention weight
2024-03-16 08:42:45 +03:00
AUTOMATIC1111
0cc3647c1c
Merge pull request #15261 from catboxanon/fix/imageviewer-click
Make imageviewer event listeners browser consistent
2024-03-16 08:41:11 +03:00
AUTOMATIC1111
3ffe47c6b7
Merge pull request #15263 from AUTOMATIC1111/fix-hr-comments
Strip comments from hires fix prompt
2024-03-16 08:30:16 +03:00
AUTOMATIC1111
c5aa7b65f7
Merge pull request #15269 from AUTOMATIC1111/fix-Hires-prompt-Styles
fix issue with Styles when Hires prompt is used
2024-03-16 08:25:39 +03:00
AUTOMATIC1111
01ba5ad213
Merge pull request #15272 from AUTOMATIC1111/bump-action-version
bump action version
2024-03-16 08:22:48 +03:00
w-e-w
a3a648bf6b bump action version 2024-03-16 05:57:23 +09:00
w-e-w
887a512208 fix issue with Styles when Hires prompt is used 2024-03-15 21:06:54 +09:00
Andray
6f51e05553 prevent alt menu for firefox 2024-03-15 12:12:37 +04:00
missionfloyd
5f4203bf9b Strip comments from hires fix prompt 2024-03-14 22:23:06 -06:00
catboxanon
8eaa7e9f04 Support dragdrop for URLs 2024-03-15 04:06:17 +00:00
catboxanon
76fd487818
Make imageviewer event listeners browser consistent 2024-03-14 21:59:53 -04:00
v0xie
07805cbeee fix: AttributeError when attempting to reshape rescale by org_module weight 2024-03-14 17:05:14 -07:00
w-e-w
c40f33ca04 PEP 604 annotations 2024-03-15 08:22:36 +09:00
Haoming
4e17fc36d8 add user setting
Now this is disabled by default
2024-03-14 10:04:09 +08:00
Haoming
fd71b761ff use re instead of hardcoding
Now supports all natively provided upscaler as well
2024-03-14 09:55:14 +08:00
Haoming
d18eb10ecd add hook 2024-03-13 21:15:52 +08:00
KohakuBlueleaf
9f2ae1cb85 Add missing .mean 2024-03-13 11:47:33 +08:00
DGdev91
32f0b5dbaf Merge branch 'dev' of https://github.com/DGdev91/stable-diffusion-webui into dev 2024-03-13 00:56:42 +01:00
DGdev91
2efc7c1b05 Better workaround for Navi1, removing --pre for Navi3 2024-03-13 00:54:32 +01:00
DGdev91
9fbfb8ad32 Better workaround for Navi1 - fix if 2024-03-13 00:43:01 +01:00
DGdev91
74e2e5279c Workaround for Navi1: pytorch nightly whl for 3.8 and 3.9 2024-03-13 00:17:24 +01:00
Andray
b980c8140b featch only active branch updates for extensions 2024-03-12 22:21:59 +04:00
wangshuai09
994e08aac1 ascend npu readme 2024-03-12 18:45:26 +08:00
DGdev91
8262cd71c4 Better workaround for Navi1, removing --pre for Navi3 2024-03-12 00:09:07 +01:00
Andray
2e3a0f39f6 move upscale postprocessing under input accordion 2024-03-12 02:28:15 +04:00
Andray
4079b17dd9 move postprocessing-for-training into builtin extensions 2024-03-12 01:50:57 +04:00
w-e-w
1a1205f601 fix Restore progress 2024-03-12 03:26:50 +09:00
Andray
2d57a2df66
Update modules/shared.py
Co-authored-by: catboxanon <122327233+catboxanon@users.noreply.github.com>
2024-03-11 07:40:15 +04:00
Andray
eb10da8bb7 type hinting in shared.py 2024-03-11 05:15:09 +04:00
AUTOMATIC1111
3e0146f9bd restore the lost Uncategorized options section 2024-03-10 22:40:35 +03:00
AUTOMATIC1111
1bbc8a153b Merge branch 'dev' into callback_order 2024-03-10 16:15:09 +03:00
AUTOMATIC1111
3670b4f49e lint 2024-03-10 15:16:12 +03:00
AUTOMATIC1111
2f55d669a2 add support for specifying callback order in metadata 2024-03-10 15:14:04 +03:00
AUTOMATIC1111
edc56202c1
Merge pull request #15201 from AUTOMATIC1111/update-preview-on-Replace-Preview
update preview on Replace Preview
2024-03-10 14:11:26 +03:00
AUTOMATIC1111
7e5e67330b add UI for reordering callbacks 2024-03-10 14:09:48 +03:00
w-e-w
9fd0cd6a80 update preview on Replace Preview 2024-03-10 18:24:52 +09:00
SunChaser
9b842e9ec7
fix: resolve type annotation warnings 2024-03-10 16:19:59 +08:00
AUTOMATIC1111
0411eced89 add names to callbacks 2024-03-10 07:52:57 +03:00
AUTOMATIC1111
2e93bdce0c
Merge pull request #15198 from zopieux/search-desc
Add model description to searched terms
2024-03-10 07:03:16 +03:00
AUTOMATIC1111
8076100e14
Merge pull request #15199 from AUTOMATIC1111/add-entry-to-MassFileLister-after-writing-metadata
Add entry to MassFileLister  after writing metadata
2024-03-10 07:01:46 +03:00
w-e-w
fb62f1fb40 add entry to MassFileLister after writing metadata
fix #15184
2024-03-10 06:07:16 +09:00
Alexandre Macabies
0085e719a9 Add model description to searched terms.
This adds the model description to the searchable terms.
This is particularly useful since the description can be used to store
arbitrary tags, independently from the filename, which is imposed by the
model publisher.
2024-03-09 21:53:38 +01:00
AUTOMATIC1111
6136db1409 linter 2024-03-09 12:21:46 +03:00
AUTOMATIC1111
110e3d7033
Merge pull request #15191 from AUTOMATIC1111/fix-default-in-get_learned_conditioning
Avoid error from None in get_learned_conditioning
2024-03-09 12:21:23 +03:00
Kohaku-Blueleaf
0dc179ee72 Avoid error from None 2024-03-09 17:12:54 +08:00
AUTOMATIC1111
4c9a7b8a75
Merge pull request #15190 from AUTOMATIC1111/dora-weight-decompose
Fix built-in lora system bugs caused by torch.nn.MultiheadAttention
2024-03-09 08:29:51 +03:00
AUTOMATIC1111
1770b887ec
Merge pull request #15189 from 10sa/dev
Add '--no-prompt-history' cmd args for disable last generation prompt history
2024-03-09 08:28:56 +03:00
AUTOMATIC1111
18d801a13d stylistic changes for extra network sorting/search controls 2024-03-09 08:25:01 +03:00
Kohaku-Blueleaf
851c3d51ed Fix bugs for torch.nn.MultiheadAttention 2024-03-09 12:31:32 +08:00
AUTOMATIC1111
5251733c0d use natural sort in extra networks when ordering by path 2024-03-09 07:24:51 +03:00
10sa
c50b7e4eff Add '--no-prompt-history' cmd args for disable last generation prompt history 2024-03-09 11:43:49 +09:00
AUTOMATIC1111
d318f1b5e1
Merge pull request #15183 from jim60105/master
chore: fix font not loaded
2024-03-08 21:58:41 +03:00
陳鈞
02a4ceabdd
chore: fix font not loaded
fix #15182
2024-03-09 02:13:35 +08:00
AUTOMATIC1111
7d1368c51c lint 2024-03-08 17:11:56 +03:00
AUTOMATIC1111
758e8d7b41 undo unwanted change for extra networks 2024-03-08 17:11:42 +03:00
AUTOMATIC1111
530fea2bc4 optimization for extra networks sorting 2024-03-08 17:09:11 +03:00
AUTOMATIC1111
3bd75adb1c optimization for extra networks filtering 2024-03-08 16:54:39 +03:00
SunChaser
01f531e9b1
fix: fix syntax errors 2024-03-08 17:25:28 +08:00
AUTOMATIC1111
a551a43164 add an option to have old-style directory view instead of tree view 2024-03-08 09:52:25 +03:00
AUTOMATIC1111
a43ce7eabb fix broken resize handle on the train tab 2024-03-08 08:13:08 +03:00
AUTOMATIC1111
9409419afb
Merge pull request #15160 from AUTOMATIC1111/dora-weight-decompose
Add DoRA (weight-decompose) support for LoRA/LoHa/LoKr
2024-03-08 08:02:17 +03:00
AUTOMATIC1111
e0c9361b7d performance optimization for extra networks 2024-03-08 07:51:31 +03:00
AUTOMATIC1111
8b96f3d036
Merge pull request #15178 from catboxanon/feat/edit-attention-whitespace-trim
edit-attention: deselect surrounding whitespace
2024-03-08 06:21:24 +03:00
catboxanon
5ab5405b6f
Simpler comparison
Co-authored-by: missionfloyd <missionfloyd@users.noreply.github.com>
2024-03-07 21:30:05 -05:00
catboxanon
766f6e3eca
edit-attention: deselect surrounding whitespace 2024-03-07 18:30:36 -05:00
Kohaku-Blueleaf
12bcacf413 Initial implementation 2024-03-07 13:29:40 +08:00
AUTOMATIC1111
58f7410c9d
Merge pull request #14820 from alexhegit/master
Update to ROCm5.7 and PyTorch
2024-03-06 15:45:39 +03:00
AUTOMATIC1111
ea3aae9c39
Merge branch 'dev' into master 2024-03-06 15:44:55 +03:00
AUTOMATIC1111
8904e00842
Merge pull request #15148 from continue-revolution/conrevo/fix-soft-inpaint
Fix Soft Inpaint for AnimateDiff
2024-03-06 15:36:01 +03:00
continue-revolution
7d59b3b564 rm comment 2024-03-06 05:39:17 -06:00
continue-revolution
7f766cd762 Merge branch 'dev' into conrevo/fix-soft-inpaint 2024-03-06 05:33:30 -06:00
continue-revolution
73e635ce6e fix 2024-03-06 05:32:59 -06:00
AUTOMATIC1111
ecd5fa9c42
Merge pull request #15131 from catboxanon/feat/extra-network-metadata
Re-use profiler visualization for extra networks
2024-03-06 13:08:43 +03:00
AUTOMATIC1111
14215beb48
Merge pull request #15135 from AUTOMATIC1111/fix-extract_style_text_from_prompt
fix extract_style_text_from_prompt #15132
2024-03-06 13:07:43 +03:00
AUTOMATIC1111
11ef1a9302
Merge pull request #15142 from catboxanon/fix/emphasis-prompt-txt-write-order
Fix emphasis infotext missing from `params.txt`
2024-03-06 13:07:02 +03:00
AUTOMATIC1111
c1deec64cb lint 2024-03-06 13:06:13 +03:00
AUTOMATIC1111
2bb296531d
Merge pull request #15141 from catboxanon/feat/emphasis-infotext-parse
Only override emphasis if actually used in prompt
2024-03-06 13:05:56 +03:00
catboxanon
ed386c84b6
Fix emphasis infotext missing from params.txt 2024-03-05 11:53:36 -05:00
catboxanon
7785d484ae
Only override emphasis if actually used in prompt 2024-03-05 11:50:53 -05:00
w-e-w
706f63adfa fix extract_style_text_from_prompt #15132 2024-03-05 12:35:46 +09:00
catboxanon
ecffe8513e
Lint 2024-03-04 18:46:25 -05:00
catboxanon
801461eea2 Re-use profiler visualization for extra networks 2024-03-04 18:33:22 -05:00
AUTOMATIC1111
eee46a5094
Merge pull request #14981 from wangshuai09/gpu_info_for_ascend
Add training support and change lspci for Ascend NPU
2024-03-04 20:06:54 +03:00
AUTOMATIC1111
09b5ce68a9 add images.read to automatically fix all jpeg/png weirdness 2024-03-04 19:14:53 +03:00
AUTOMATIC1111
5625ce1b1a
Merge pull request #14958 from HTYISABUG/dev
Error handling for unsupported transparency
2024-03-04 18:40:16 +03:00
AUTOMATIC1111
58278aa71c
Merge pull request #15121 from AUTOMATIC1111/fix-settings-in-ui
[alternative fix] can't load webui if selected wrong extra option in ui
2024-03-04 18:24:09 +03:00
AUTOMATIC1111
33fbe943e2
Merge pull request #15062 from astriaai/fix-exif-orientation-api
Fix EXIF orientation in API image loading
2024-03-04 16:26:53 +03:00
AUTOMATIC1111
0dc12861ef 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 2024-03-04 15:30:46 +03:00
Alon Burg
67d8dafe44 Fix EXIF orientation in API image loading 2024-03-04 12:23:14 +02:00
wangshuai09
3fb1c2e58d fix npu-smi command 2024-03-04 17:19:37 +08:00
AUTOMATIC1111
92d77e3fa8
Merge pull request #15102 from light-and-ray/fix_jpeg_live_preview
fix_jpeg_live_preview
2024-03-04 10:30:24 +03:00
AUTOMATIC1111
48a677c4ac
Merge pull request #15116 from akx/typos
Fix various typos with crate-ci/typos
2024-03-04 10:28:42 +03:00
Aarni Koskela
e3fa46f26f Fix various typos with crate-ci/typos 2024-03-04 08:42:07 +02:00
AUTOMATIC1111
e2a8745abc
Merge pull request #15084 from clayne/1709395892-upscale-logging-reduce
upscaler_utils: Reduce logging
2024-03-04 06:50:03 +03:00
Christopher Layne
3c0177a24b upscaler_utils: Reduce logging
* upscale_with_model: Remove debugging logging occurring in loop as
  it's an excessive amount of noise when running w/ DEBUG log levels.
2024-03-03 11:41:32 -08:00
Andray
0103365697 fix_jpeg_live_preview 2024-03-03 16:54:58 +04:00
AUTOMATIC1111
45b8a499a7 fix wrong condition 2024-03-02 10:36:48 +03:00
AUTOMATIC1111
bb24c13ed7 infotext support for #14978 2024-03-02 07:39:59 +03:00
AUTOMATIC1111
aabedcbcc7
Merge pull request #14978 from drhead/refiner_fix
Make refiner switchover based on model timesteps instead of sampling steps
2024-03-02 07:24:44 +03:00
AUTOMATIC1111
f4cb21bb8a
Merge pull request #15031 from light-and-ray/unix_filenames
cmd args: `--unix-filenames-sanitization` and `--filenames-max-length`
2024-03-02 07:16:06 +03:00
AUTOMATIC1111
6044a9723a
Merge pull request #15059 from Dalton-Murray/direct-binary-release-link
Add a direct link to the binary release
2024-03-02 07:15:32 +03:00
AUTOMATIC1111
9189ea20b0
Merge pull request #15041 from light-and-ray/resize_handle_for_extra_networks
resize handle for extra networks
2024-03-02 07:13:00 +03:00
AUTOMATIC1111
95d143eafe Merge branch 'master' into dev 2024-03-02 07:04:33 +03:00
AUTOMATIC1111
ee470cc6a3 style changes for #14979 2024-03-02 06:54:11 +03:00
AUTOMATIC1111
1a51b166a0 call apply_alpha_schedule_override in load_model_weights for #14979 2024-03-02 06:53:53 +03:00
AUTOMATIC1111
06b9200e91
Merge pull request #14979 from drhead/refiner_cumprod_fix
Protect alphas_cumprod during refiner switchover
2024-03-02 06:40:32 +03:00
AUTOMATIC1111
f04e76811a
Merge pull request #15039 from light-and-ray/dat_cmd_flag
dat cmd flag
2024-03-02 06:38:50 +03:00
AUTOMATIC1111
817d9b15f7
Merge pull request #15065 from light-and-ray/resizeHandle_handle_double_tap
resizeHandle handle double tap
2024-03-02 06:37:19 +03:00
AUTOMATIC1111
150b603770
Merge pull request #15035 from AUTOMATIC1111/fix/normalized-filepath-absolute
Use `absolute` path for normalized filepath
2024-03-02 06:35:46 +03:00
Andray
eb0b84c564 make minimal width 2 times smaller then default 2024-02-29 16:02:21 +04:00
Andray
bb99f52712 resizeHandle handle double tap 2024-02-29 15:40:15 +04:00
Dalton
bce09eb987
Add a direct link to the binary release 2024-02-29 01:04:46 -05:00
Andray
51cc1ff2c9 fix for mobile and allow collapse right column 2024-02-27 23:31:47 +04:00
Andray
b4c44e659b fix on reload with changed show all loras setting 2024-02-27 23:17:52 +04:00
Andray
de7604fa77 lint 2024-02-27 18:38:38 +04:00
Andray
44bce3c74e resize handle for extra networks 2024-02-27 18:31:36 +04:00
Andray
3ba575216a dat cmd flag 2024-02-27 15:10:51 +04:00
drhead
4dae91a1fe
remove alphas cumprod fix from samplers_common 2024-02-26 23:46:10 -05:00
drhead
94f23d00a7
move alphas cumprod override out of processing 2024-02-26 23:44:58 -05:00
drhead
e2cd92ea23
move refiner fix to sd_models.py 2024-02-26 23:43:27 -05:00
catboxanon
3a618e3d24
Fix normalized filepath, resolve -> absolute
https://github.com/lllyasviel/stable-diffusion-webui-forge/issues/313
https://github.com/AUTOMATIC1111/stable-diffusion-webui/discussions/14942#discussioncomment-8550050
2024-02-26 12:44:57 -05:00
Andray
dd4b0b95d5 cmd args: allow unix filenames and filenames max length 2024-02-26 16:30:15 +04:00
AUTOMATIC1111
c8a5322d1f
Merge pull request #15012 from light-and-ray/register_tmp_file-also-with-mtime
register_tmp_file also for mtime
2024-02-26 12:53:21 +03:00
AUTOMATIC1111
ca0308b60d
Merge pull request #15010 from light-and-ray/fix_resize-handle_for_vertical_layout
Fix resize-handle visability for vertical layout (mobile)
2024-02-26 12:52:34 +03:00
Andray
6e6cc2922d fix resize handle 2024-02-26 13:37:29 +04:00
drhead
648f6a8e0c
dont need to preserve alphas_cumprod_original 2024-02-25 23:28:36 -05:00
AUTOMATIC1111
2b7ddcbb5c
Merge pull request #15006 from imnodb/master
fix: the `split_threshold` parameter does not work when running Split oversized images
2024-02-26 07:16:42 +03:00
AUTOMATIC1111
e3a8dc6e23
Merge pull request #15004 from light-and-ray/ResizeHandleRow_-_allow_overriden_column_scale_parametr
ResizeHandleRow - allow overriden column scale parametr
2024-02-26 07:16:24 +03:00
AUTOMATIC1111
ca8dc2bde2
Merge pull request #14995 from dtlnor/14591-bug-the-categories-layout-is-different-when-localization-is-on
Fix #14591 using translated content to do categories mapping
2024-02-26 07:12:31 +03:00
AUTOMATIC1111
900419e85e
Merge pull request #14973 from AUTOMATIC1111/Fix-new-oft-boft
Fix the OFT/BOFT bugs when using new LyCORIS implementation
2024-02-26 07:12:12 +03:00
Andray
3a99824638 register_tmp_file also with mtime 2024-02-23 20:26:56 +04:00
Andray
bab918f049 fix resize-handle for vertical layout 2024-02-23 18:34:24 +04:00
DB Eriospermum
ed594d7ba6
fix: the split_threshold parameter does not work when running Split oversized images 2024-02-23 13:37:37 +08:00
Andray
9211febbfc ResizeHandleRow - allow overriden column scale parametr 2024-02-23 02:32:12 +04:00
AUTOMATIC1111
18819723c1
Merge pull request #15002 from light-and-ray/support_resizable_columns_for_touch_(tablets)
support resizable columns for touch (tablets)
2024-02-22 22:59:26 +03:00
AUTOMATIC1111
3f18a09c86 make extra network card description plaintext by default, with an option to re-enable HTML as it was 2024-02-22 21:27:10 +03:00
Andray
58985e6b37 fix lint 2 2024-02-22 17:22:00 +04:00
Andray
ab1e0fa9bf fix lint and console warning 2024-02-22 17:16:16 +04:00
Andray
85abbbb8fa support resizable columns for touch (tablets) 2024-02-22 17:04:56 +04:00
wangshuai09
ba66cf8d69 update 2024-02-22 20:17:10 +08:00
AUTOMATIC1111
1da05297ea possible fix for reload button not appearing in some cases for extra networks. 2024-02-22 10:28:03 +03:00
dtlnor
f537e5a519 fix #14591 - using translated content to do categories mapping 2024-02-22 12:26:57 +09:00
Kohaku-Blueleaf
c4afdb7895
For no constraint 2024-02-22 00:43:32 +08:00
Kohaku-Blueleaf
64179c3221
Update network_oft.py 2024-02-21 22:50:43 +08:00
wangshuai09
b7aa425344 del gpu_info for npu 2024-02-21 11:49:06 +08:00
drhead
9c1ece8978
Protect alphas_cumprod during refiner switchover 2024-02-20 19:23:21 -05:00
drhead
bf348032bc
fix missing arg 2024-02-20 16:59:28 -05:00
drhead
25eeeaa65f
Allow refiner to be triggered by model timestep instead of sampling 2024-02-20 16:37:29 -05:00
drhead
09d2e58811
Pass sigma to apply_refiner 2024-02-20 16:22:40 -05:00
drhead
f4869f8de3
Add compatibility option for refiner switching 2024-02-20 16:18:13 -05:00
Kohaku-Blueleaf
591470d86d linting 2024-02-20 17:21:34 +08:00
Kohaku-Blueleaf
a5436a3ac0 Update network_oft.py 2024-02-20 17:20:14 +08:00
AUTOMATIC1111
0a271938d8
Merge pull request #14966 from light-and-ray/avoid_doble_upscaling_in_inpaint
[bug] avoid doble upscaling in inpaint
2024-02-19 18:06:05 +03:00
Andray
33c8fe1221 avoid doble upscaling in inpaint 2024-02-19 16:57:49 +04:00
AUTOMATIC1111
6e4fc5e1a8
Merge pull request #14871 from v0xie/boft
Support inference with LyCORIS BOFT networks
2024-02-19 10:05:30 +03:00
Kohaku-Blueleaf
4eb949625c
prevent undefined variable 2024-02-19 14:43:07 +08:00
HSIEH TSUNGYU
9d5dc582be Error handling for unsupported transparency
When input images (palette mode) have transparency (bytes) in info,
the output images (RGB mode) will inherit it,
causing ValueError in Pillow:PIL/PngImagePlugin.py#1364
when trying to unpack this bytes.

This commit check the PNG mode and transparency info,
removing transparency if it's RGB mode and transparency is bytes
2024-02-18 19:27:33 +08:00
Kohaku-Blueleaf
5a8dd0c549
Fix rescale 2024-02-18 14:58:41 +08:00
AUTOMATIC1111
9d5becb4de
Merge pull request #14947 from AUTOMATIC1111/open-button
option "open image button" open the actual dir
2024-02-17 21:30:21 +03:00
w-e-w
71072f5620 re-work open image button settings 2024-02-18 02:47:44 +09:00
w-e-w
a18e54ecd7 option "open image button" open the actual dir 2024-02-18 00:38:05 +09:00
AUTOMATIC1111
4ff1fabc86 Update comment for Pad prompt/negative prompt v0 to add a warning about truncation, make it override the v1 implementation 2024-02-17 13:21:08 +03:00
AUTOMATIC1111
4573195894 prevent escape button causing an interrupt when no generation has been made yet 2024-02-17 11:40:53 +03:00
Kohaku-Blueleaf
90441294db
Add rescale mechanism
LyCORIS will support save oft_blocks instead of oft_diag in the near future (for both OFT and BOFT)

But this means we need to store the rescale if user enable it.
2024-02-12 14:25:09 +08:00
v0xie
eb6f2df826 Revert "fix: add butterfly_factor fn"
This reverts commit 81c16c965e532c6d86a969284c320ff8fcb0451d.
2024-02-08 22:00:15 -08:00
v0xie
613b0d9548 doc: add boft comment 2024-02-08 21:58:59 -08:00
v0xie
325eaeb584 fix: get boft params from weight shape 2024-02-08 11:55:05 -08:00
v0xie
2f1073dc6e style: fix lint 2024-02-07 04:55:11 -08:00
v0xie
81c16c965e fix: add butterfly_factor fn 2024-02-07 04:54:14 -08:00
v0xie
a4668a16b6 fix: calculate butterfly factor 2024-02-07 04:51:22 -08:00
v0xie
9588721197 feat: support LyCORIS BOFT 2024-02-07 04:49:17 -08:00
Alex He
db4632f4ba Update to ROCm5.7 and PyTorch
The webui.sh installs ROCm5.4.2 as default. The webui run failed with AMD
Radeon Pro W7900 with **Segmentation Fault** at Ubuntu22.04 maybe the ABI
compatibility issue.

ROCm5.7 is the latest version supported by PyTorch (https://pytorch.org/)
at now. I test it with AMD Radeon Pro W7900 by PyTorch+ROCm5.7 with PASS.

Signed-off-by: Alex He <heye_dev@163.com>
2024-02-02 13:48:42 +08:00
157 changed files with 5583 additions and 1330 deletions

View File

@ -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

View File

@ -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

View File

@ -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,7 +20,7 @@ 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 .
lint-js: lint-js:
@ -29,9 +29,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: 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

View File

@ -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

4
.gitignore vendored
View File

@ -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

View File

@ -1,4 +1,283 @@
## 1.8.0-RC ## 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: ### Features:
* Update torch to version 2.1.2 * Update torch to version 2.1.2
@ -14,7 +293,7 @@
* 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)) * 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)) * 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)) * NPU Support ([#14801](https://github.com/AUTOMATIC1111/stable-diffusion-webui/pull/14801))
* Propmpt comments support * Prompt comments support
### Minor: ### Minor:
* Allow pasting in WIDTHxHEIGHT strings into the width/height fields ([#14296](https://github.com/AUTOMATIC1111/stable-diffusion-webui/pull/14296)) * Allow pasting in WIDTHxHEIGHT strings into the width/height fields ([#14296](https://github.com/AUTOMATIC1111/stable-diffusion-webui/pull/14296))
@ -59,9 +338,9 @@
* modules/api/api.py: add api endpoint to refresh embeddings list ([#14715](https://github.com/AUTOMATIC1111/stable-diffusion-webui/pull/14715)) * 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)) * set_named_arg ([#14773](https://github.com/AUTOMATIC1111/stable-diffusion-webui/pull/14773))
* add before_token_counter callback and use it for prompt comments * add before_token_counter callback and use it for prompt comments
* ResizeHandleRow - allow overriden column scale parameter ([#15004](https://github.com/AUTOMATIC1111/stable-diffusion-webui/pull/15004)) * ResizeHandleRow - allow overridden column scale parameter ([#15004](https://github.com/AUTOMATIC1111/stable-diffusion-webui/pull/15004))
### Performance ### 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)) * 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)) * 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)) * Avoid unnecessary `isfile`/`exists` calls ([#14527](https://github.com/AUTOMATIC1111/stable-diffusion-webui/pull/14527))
@ -101,7 +380,7 @@
* Gracefully handle mtime read exception from cache ([#14933](https://github.com/AUTOMATIC1111/stable-diffusion-webui/pull/14933)) * 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)) * 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. * Disable prompt token counters option actually disables token counting rather than just hiding results.
* avoid doble upscaling in inpaint ([#14966](https://github.com/AUTOMATIC1111/stable-diffusion-webui/pull/14966)) * 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 #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: 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)) * 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))
@ -171,7 +450,7 @@
* 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)) * 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:
@ -308,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))
@ -484,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
@ -703,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)
@ -733,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
@ -751,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

View File

@ -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,32 @@ 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
# 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
``` ```
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 +172,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

5
_typos.toml Normal file
View File

@ -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"

View File

@ -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:

View File

@ -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:

View File

@ -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:

View File

@ -0,0 +1,5 @@
model:
target: modules.models.sd3.sd3_model.SD3Inferencer
params:
shift: 3
state_dict: null

View File

@ -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

View File

@ -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:

View File

@ -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:

View File

@ -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)

View File

@ -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:

View File

@ -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, AB BA. Meaning of two matrices is slightly different. Because of non-commutative property, AB BA. Meaning of two matrices is slightly different.
examples) examples)
factor factor

View File

@ -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()

View File

@ -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:

View File

@ -36,13 +36,6 @@ class NetworkModuleOFT(network.NetworkModule):
# 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)
# LyCORIS BOFT
if self.oft_blocks.dim() == 4:
self.is_boft = True
self.rescale = weights.w.get('rescale', None)
if self.rescale is not None:
self.rescale = self.rescale.reshape(-1, *[1]*(self.org_module[0].weight.dim() - 1))
is_linear = type(self.sd_module) in [torch.nn.Linear, torch.nn.modules.linear.NonDynamicallyQuantizableLinear] is_linear = type(self.sd_module) in [torch.nn.Linear, torch.nn.modules.linear.NonDynamicallyQuantizableLinear]
is_conv = type(self.sd_module) in [torch.nn.Conv2d] is_conv = type(self.sd_module) in [torch.nn.Conv2d]
is_other_linear = type(self.sd_module) in [torch.nn.MultiheadAttention] # unsupported is_other_linear = type(self.sd_module) in [torch.nn.MultiheadAttention] # unsupported
@ -54,6 +47,13 @@ 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
# LyCORIS BOFT
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
self.constraint = (0 if self.alpha is None else self.alpha) * self.out_dim self.constraint = (0 if self.alpha is None else self.alpha) * self.out_dim

View File

@ -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,12 +167,26 @@ 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():
key_network_without_network_parts, _, network_part = key_network.partition(".")
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(".")
if key_network_without_network_parts == "bundle_emb": if key_network_without_network_parts == "bundle_emb":
emb_name, vec_name = network_part.split(".", 1) emb_name, vec_name = network_part.split(".", 1)
@ -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
key = convert_diffusers_name_to_compvis(key_network_without_network_parts, is_sd2) 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)
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): restore_weights_backup(self.out_proj, 'bias', bias_backup)
self.out_proj.bias.copy_(bias_backup)
else:
self.bias.copy_(bias_backup)
else: else:
if isinstance(self, torch.nn.MultiheadAttention): restore_weights_backup(self, 'bias', bias_backup)
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]+\)")

View File

@ -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}),

View File

@ -21,10 +21,12 @@ 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", {})
for tag, tag_count in tags_dict.items(): if ss_tag_frequency is not None and hasattr(ss_tag_frequency, 'items'):
tag = tag.strip() for _, tags_dict in ss_tag_frequency.items():
tags[tag] = tags.get(tag, 0) + int(tag_count) for tag, tag_count in tags_dict.items():
tag = tag.strip()
tags[tag] = tags.get(tag, 0) + int(tag_count)
if tags and is_non_comma_tagset(tags): if tags and is_non_comma_tagset(tags):
new_tags = {} new_tags = {}
@ -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))

View File

@ -31,7 +31,7 @@ class ExtraNetworksPageLora(ui_extra_networks.ExtraNetworksPage):
"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_terms": search_terms, "search_terms": search_terms,
"local_preview": f"{path}.{shared.opts.samples_format}", "local_preview": f"{path}.{shared.opts.samples_format}",
@ -60,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

View File

@ -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,23 +790,29 @@ onUiLoaded(async() => {
targetElement.addEventListener("mouseleave", handleMouseLeave); targetElement.addEventListener("mouseleave", handleMouseLeave);
// Reset zoom when click on another tab // Reset zoom when click on another tab
elements.img2imgTabs.addEventListener("click", resetZoom); if (elements.img2imgTabs) {
elements.img2imgTabs.addEventListener("click", () => { elements.img2imgTabs.addEventListener("click", resetZoom);
// targetElement.style.width = ""; elements.img2imgTabs.addEventListener("click", () => {
if (parseInt(targetElement.style.width) > 865) { // targetElement.style.width = "";
setTimeout(fitToElement, 0); if (parseInt(targetElement.style.width) > 865) {
} 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);
} }
@ -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;

View File

@ -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"),

View File

@ -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():
comp = ui_settings.create_setting_component(setting_name) try:
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)

View File

@ -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)

View File

@ -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)

View File

@ -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.
# [4][w][h] to [1][4][w][h] if len(t.shape) == 3:
t2 = t.unsqueeze(0) # [4][w][h] to [1][4][w][h]
# [4][w][h] to [1][1][w][h] - the [4] seem redundant. t2 = t.unsqueeze(0)
t3 = t[0].unsqueeze(0).unsqueeze(0) # [4][w][h] to [1][1][w][h] - the [4] seem redundant.
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.
latent_mask = nmask[0].float() if len(nmask.shape) == 3:
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%
half_weighted_distance = settings.composite_difference_threshold * mask_scalar 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
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)

View File

@ -1,5 +1,5 @@
<div class="copy-path-button card-button" <div class="copy-path-button card-button"
title="Copy path to clipboard" title="Copy path to clipboard"
onclick="extraNetworksCopyCardPath(event, '{filename}')" onclick="extraNetworksCopyCardPath(event)"
data-clipboard-text="{filename}"> data-clipboard-text="{filename}">
</div> </div>

View File

@ -1,4 +1,4 @@
<div class="edit-button card-button" <div class="edit-button card-button"
title="Edit metadata" title="Edit metadata"
onclick="extraNetworksEditUserMetadata(event, '{tabname}', '{extra_networks_tabname}', '{name}')"> onclick="extraNetworksEditUserMetadata(event, '{tabname}', '{extra_networks_tabname}')">
</div> </div>

View File

@ -1,4 +1,4 @@
<div class="metadata-button card-button" <div class="metadata-button card-button"
title="Show internal metadata" title="Show internal metadata"
onclick="extraNetworksRequestMetadata(event, '{extra_networks_tabname}', '{name}')"> onclick="extraNetworksRequestMetadata(event, '{extra_networks_tabname}')">
</div> </div>

View File

@ -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>

View File

@ -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>

View File

@ -1,23 +1,53 @@
<div id='{tabname}_{extra_networks_tabname}_pane' class='extra-network-pane'> <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" id="{tabname}_{extra_networks_tabname}_controls" style="display:none" >
<div class="extra-network-control--search"> <div class="extra-network-control--search">
<input <input
id="{tabname}_{extra_networks_tabname}_extra_search" id="{tabname}_{extra_networks_tabname}_extra_search"
class="extra-network-control--search-text" class="extra-network-control--search-text"
type="search" type="search"
placeholder="Filter files" placeholder="Search"
> >
</div> </div>
<small>Sort: </small>
<div <div
id="{tabname}_{extra_networks_tabname}_extra_sort" id="{tabname}_{extra_networks_tabname}_extra_sort_path"
class="extra-network-control--sort" class="extra-network-control--sort{sort_path_active}"
data-sortmode="{data_sortmode}" data-sortkey="default"
data-sortkey="{data_sortkey}"
title="Sort by path" title="Sort by path"
onclick="extraNetworksControlSortOnClick(event, '{tabname}', '{extra_networks_tabname}');" onclick="extraNetworksControlSortOnClick(event, '{tabname}', '{extra_networks_tabname}');"
> >
<i class="extra-network-control--sort-icon"></i> <i class="extra-network-control--icon extra-network-control--sort-icon"></i>
</div> </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 <div
id="{tabname}_{extra_networks_tabname}_extra_sort_dir" id="{tabname}_{extra_networks_tabname}_extra_sort_dir"
class="extra-network-control--sort-dir" class="extra-network-control--sort-dir"
@ -25,15 +55,18 @@
title="Sort ascending" title="Sort ascending"
onclick="extraNetworksControlSortDirOnClick(event, '{tabname}', '{extra_networks_tabname}');" onclick="extraNetworksControlSortDirOnClick(event, '{tabname}', '{extra_networks_tabname}');"
> >
<i class="extra-network-control--sort-dir-icon"></i> <i class="extra-network-control--icon extra-network-control--sort-dir-icon"></i>
</div> </div>
<small> </small>
<div <div
id="{tabname}_{extra_networks_tabname}_extra_tree_view" id="{tabname}_{extra_networks_tabname}_extra_tree_view"
class="extra-network-control--tree-view {tree_view_btn_extra_class}" class="extra-network-control--tree-view {tree_view_btn_extra_class}"
title="Enable Tree View" title="Enable Tree View"
onclick="extraNetworksControlTreeViewOnClick(event, '{tabname}', '{extra_networks_tabname}');" onclick="extraNetworksControlTreeViewOnClick(event, '{tabname}', '{extra_networks_tabname}');"
> >
<i class="extra-network-control--tree-view-icon"></i> <i class="extra-network-control--icon extra-network-control--tree-view-icon"></i>
</div> </div>
<div <div
id="{tabname}_{extra_networks_tabname}_extra_refresh" id="{tabname}_{extra_networks_tabname}_extra_refresh"
@ -41,15 +74,8 @@
title="Refresh page" title="Refresh page"
onclick="extraNetworksControlRefreshOnClick(event, '{tabname}', '{extra_networks_tabname}');" onclick="extraNetworksControlRefreshOnClick(event, '{tabname}', '{extra_networks_tabname}');"
> >
<i class="extra-network-control--refresh-icon"></i> <i class="extra-network-control--icon extra-network-control--refresh-icon"></i>
</div>
</div>
<div class="extra-network-pane-content">
<div id='{tabname}_{extra_networks_tabname}_tree' class='extra-network-tree {tree_view_div_extra_class}'>
{tree_html}
</div>
<div id='{tabname}_{extra_networks_tabname}_cards' class='extra-network-cards'>
{items_html}
</div> </div>
</div> </div>
{pane_content}
</div> </div>

View File

@ -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;

View File

@ -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,16 +87,23 @@ var contextMenuInit = function() {
oldMenu.remove(); oldMenu.remove();
} }
}); });
gradioApp().addEventListener("contextmenu", function(e) { ['contextmenu', 'touchstart'].forEach((eventType) => {
let oldMenu = gradioApp().querySelector('#context-menu'); gradioApp().addEventListener(eventType, function(e) {
if (oldMenu) { let ev = e;
oldMenu.remove(); if (eventType.startsWith('touch')) {
} if (e.touches.length !== 2) return;
menuSpecs.forEach(function(v, k) { ev = e.touches[0];
if (e.composedPath()[0].matches(k)) {
showContextMenu(e, e.composedPath()[0], v);
e.preventDefault();
} }
let oldMenu = gradioApp().querySelector('#context-menu');
if (oldMenu) {
oldMenu.remove();
}
menuSpecs.forEach(function(v, k) {
if (e.composedPath()[0].matches(k)) {
showContextMenu(ev, e.composedPath()[0], v);
e.preventDefault();
}
});
}); });
}); });
eventListenerApplied = true; eventListenerApplied = true;

View File

@ -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;
}
} }
} }

View File

@ -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;
} }

View File

@ -39,12 +39,12 @@ function setupExtraNetworksForTab(tabname) {
// tabname_full = {tabname}_{extra_networks_tabname} // tabname_full = {tabname}_{extra_networks_tabname}
var tabname_full = elem.id; var tabname_full = elem.id;
var search = gradioApp().querySelector("#" + tabname_full + "_extra_search"); var search = gradioApp().querySelector("#" + tabname_full + "_extra_search");
var sort_mode = gradioApp().querySelector("#" + tabname_full + "_extra_sort");
var sort_dir = gradioApp().querySelector("#" + tabname_full + "_extra_sort_dir"); var sort_dir = gradioApp().querySelector("#" + tabname_full + "_extra_sort_dir");
var refresh = gradioApp().querySelector("#" + tabname_full + "_extra_refresh"); 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 any of the buttons above don't exist, we want to skip this iteration of the loop.
if (!search || !sort_mode || !sort_dir || !refresh) { if (!search || !sort_dir || !refresh) {
return; // `return` is equivalent of `continue` but for forEach loops. return; // `return` is equivalent of `continue` but for forEach loops.
} }
@ -52,7 +52,7 @@ function setupExtraNetworksForTab(tabname) {
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 = Array.prototype.map.call(elem.querySelectorAll('.search_terms'), function(t) { var text = Array.prototype.map.call(elem.querySelectorAll('.search_terms, .description'), function(t) {
return t.textContent.toLowerCase(); return t.textContent.toLowerCase();
}).join(" "); }).join(" ");
@ -71,42 +71,46 @@ function setupExtraNetworksForTab(tabname) {
}; };
var applySort = function(force) { 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 reverse = sort_dir.dataset.sortdir == "Descending";
var sortKey = sort_mode.dataset.sortmode.toLowerCase().replace("sort", "").replaceAll(" ", "_").replace(/_+$/, "").trim() || "name"; var activeSearchElem = gradioApp().querySelector('#' + tabname_full + "_controls .extra-network-control--sort.extra-network-control--enabled");
sortKey = "sort" + sortKey.charAt(0).toUpperCase() + sortKey.slice(1); var sortKey = activeSearchElem ? activeSearchElem.dataset.sortkey : "default";
var sortKeyStore = sortKey + "-" + (reverse ? "Descending" : "Ascending") + "-" + cards.length; var sortKeyDataField = "sort" + sortKey.charAt(0).toUpperCase() + sortKey.slice(1);
var sortKeyStore = sortKey + "-" + sort_dir.dataset.sortdir + "-" + cards.length;
if (sortKeyStore == sort_mode.dataset.sortkey && !force) { if (sortKeyStore == currentSort && !force) {
return; return;
} }
sort_mode.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() {
applyFilter();
});
applySort(); applySort();
applyFilter(); applyFilter();
extraNetworksApplySort[tabname_full] = applySort; extraNetworksApplySort[tabname_full] = applySort;
@ -272,6 +276,15 @@ function saveCardPreview(event, tabname, filename) {
event.preventDefault(); event.preventDefault();
} }
function extraNetworksSearchButton(tabname, extra_networks_tabname, event) {
var searchTextarea = gradioApp().querySelector("#" + tabname + "_" + extra_networks_tabname + "_extra_search");
var button = event.target;
var text = button.classList.contains("search-all") ? "" : button.textContent.trim();
searchTextarea.value = text;
updateInput(searchTextarea);
}
function extraNetworksTreeProcessFileClick(event, btn, tabname, extra_networks_tabname) { function extraNetworksTreeProcessFileClick(event, btn, tabname, extra_networks_tabname) {
/** /**
* Processes `onclick` events when user clicks on files in tree. * Processes `onclick` events when user clicks on files in tree.
@ -290,7 +303,7 @@ function extraNetworksTreeProcessDirectoryClick(event, btn, tabname, extra_netwo
* Processes `onclick` events when user clicks on directories in tree. * Processes `onclick` events when user clicks on directories in tree.
* *
* Here is how the tree reacts to clicks for various states: * Here is how the tree reacts to clicks for various states:
* unselected unopened directory: Diretory is selected and expanded. * unselected unopened directory: Directory is selected and expanded.
* unselected opened directory: Directory is selected. * unselected opened directory: Directory is selected.
* selected opened directory: Directory is collapsed and deselected. * selected opened directory: Directory is collapsed and deselected.
* chevron is clicked: Directory is expanded or collapsed. Selected state unchanged. * chevron is clicked: Directory is expanded or collapsed. Selected state unchanged.
@ -383,36 +396,17 @@ function extraNetworksTreeOnClick(event, tabname, extra_networks_tabname) {
} }
function extraNetworksControlSortOnClick(event, tabname, extra_networks_tabname) { function extraNetworksControlSortOnClick(event, tabname, extra_networks_tabname) {
/** /** Handles `onclick` events for Sort Mode buttons. */
* Handles `onclick` events for the Sort Mode button.
* var self = event.currentTarget;
* Modifies the data attributes of the Sort Mode button to cycle between var parent = event.currentTarget.parentElement;
* various sorting modes.
* parent.querySelectorAll('.extra-network-control--sort').forEach(function(x) {
* @param event The generated event. x.classList.remove('extra-network-control--enabled');
* @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.
*/ self.classList.add('extra-network-control--enabled');
var curr_mode = event.currentTarget.dataset.sortmode;
var el_sort_dir = gradioApp().querySelector("#" + tabname + "_" + extra_networks_tabname + "_extra_sort_dir");
var sort_dir = el_sort_dir.dataset.sortdir;
if (curr_mode == "path") {
event.currentTarget.dataset.sortmode = "name";
event.currentTarget.dataset.sortkey = "sortName-" + sort_dir + "-640";
event.currentTarget.setAttribute("title", "Sort by filename");
} else if (curr_mode == "name") {
event.currentTarget.dataset.sortmode = "date_created";
event.currentTarget.dataset.sortkey = "sortDate_created-" + sort_dir + "-640";
event.currentTarget.setAttribute("title", "Sort by date created");
} else if (curr_mode == "date_created") {
event.currentTarget.dataset.sortmode = "date_modified";
event.currentTarget.dataset.sortkey = "sortDate_modified-" + sort_dir + "-640";
event.currentTarget.setAttribute("title", "Sort by date modified");
} else {
event.currentTarget.dataset.sortmode = "path";
event.currentTarget.dataset.sortkey = "sortPath-" + sort_dir + "-640";
event.currentTarget.setAttribute("title", "Sort by path");
}
applyExtraNetworkSort(tabname + "_" + extra_networks_tabname); applyExtraNetworkSort(tabname + "_" + extra_networks_tabname);
} }
@ -447,8 +441,12 @@ function extraNetworksControlTreeViewOnClick(event, tabname, extra_networks_tabn
* @param tabname The name of the active tab in the sd webui. Ex: txt2img, img2img, etc. * @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. * @param extra_networks_tabname The id of the active extraNetworks tab. Ex: lora, checkpoints, etc.
*/ */
gradioApp().getElementById(tabname + "_" + extra_networks_tabname + "_tree").classList.toggle("hidden"); var button = event.currentTarget;
event.currentTarget.classList.toggle("extra-network-control--enabled"); 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) { function extraNetworksControlRefreshOnClick(event, tabname, extra_networks_tabname) {
@ -509,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) {
@ -543,16 +605,18 @@ function requestGet(url, data, handler, errorHandler) {
xhr.send(js); xhr.send(js);
} }
function extraNetworksCopyCardPath(event, path) { function extraNetworksCopyCardPath(event) {
navigator.clipboard.writeText(path); navigator.clipboard.writeText(event.target.getAttribute("data-clipboard-text"));
event.stopPropagation(); event.stopPropagation();
} }
function extraNetworksRequestMetadata(event, extraPage, cardName) { 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);
@ -566,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];
@ -578,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);

View File

@ -6,6 +6,8 @@ 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 ? "&#x1F5C7;" : "&#x1F5C6;";
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') {
@ -51,14 +53,7 @@ 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 +126,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 +154,13 @@ 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 ? "&#x1F5C7;" : "&#x1F5C6;";
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 +218,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 = "&#x1F5C6;";
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 = '&times;'; modalClose.innerHTML = '&times;';

View File

@ -33,120 +33,141 @@ 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];
}); });
var items = keys.map(function(x) { } else {
return {key: x, parts: x.split('/'), time: data.records[x]}; keys = keys.sort();
}
var items = keys.map(function(x) {
return {key: x, parts: x.split('/'), value: data[x]};
});
var maxLength = items.reduce(function(a, b) {
return Math.max(a, b.parts.length);
}, 0);
var cols = createRow(
table,
'th',
[
cutoff === 0 ? 'key' : 'record',
cutoff === 0 ? 'value' : 'seconds'
]
);
cols[0].colSpan = maxLength;
function arraysEqual(a, b) {
return !(a < b || b < a);
}
var addLevel = function(level, parent, hide) {
var matching = items.filter(function(x) {
return x.parts[level] && !x.parts[level + 1] && arraysEqual(x.parts.slice(0, level), parent);
}); });
var maxLength = items.reduce(function(a, b) { if (sort === "number") {
return Math.max(a, b.parts.length); matching = matching.sort(function(a, b) {
}, 0); return b.value - a.value;
});
var cols = createRow(table, 'th', ['record', 'seconds']); } else {
cols[0].colSpan = maxLength; matching = matching.sort();
function arraysEqual(a, b) {
return !(a < b || b < a);
} }
var othersTime = 0;
var othersList = [];
var othersRows = [];
var childrenRows = [];
matching.forEach(function(x) {
var visible = (cutoff === 0 && !hide) || (x.value >= cutoff && !hide);
var addLevel = function(level, parent, hide) { var cells = [];
var matching = items.filter(function(x) { for (var i = 0; i < maxLength; i++) {
return x.parts[level] && !x.parts[level + 1] && arraysEqual(x.parts.slice(0, level), parent); cells.push(x.parts[i]);
}); }
var sorted = matching.sort(function(a, b) { cells.push(cutoff === 0 ? x.value : x.value.toFixed(3));
return b.time - a.time; var cols = createRow(table, 'td', cells);
}); for (i = 0; i < level; i++) {
var othersTime = 0; cols[i].className = 'muted';
var othersList = []; }
var othersRows = [];
var childrenRows = [];
sorted.forEach(function(x) {
var visible = x.time >= cutoff && !hide;
var cells = []; var tr = cols[0].parentNode;
for (var i = 0; i < maxLength; i++) { if (!visible) {
cells.push(x.parts[i]); tr.classList.add("hidden");
} }
cells.push(x.time.toFixed(3));
var cols = createRow(table, 'td', cells);
for (i = 0; i < level; i++) {
cols[i].className = 'muted';
}
var tr = cols[0].parentNode; if (cutoff === 0 || x.value >= cutoff) {
if (!visible) { childrenRows.push(tr);
tr.classList.add("hidden"); } else {
} othersTime += x.value;
othersList.push(x.parts[level]);
if (x.time >= cutoff) { othersRows.push(tr);
childrenRows.push(tr); }
} else {
othersTime += x.time;
othersList.push(x.parts[level]);
othersRows.push(tr);
}
var children = addLevel(level + 1, parent.concat([x.parts[level]]), true);
if (children.length > 0) {
var cell = cols[level];
var onclick = function() {
cell.classList.remove("link");
cell.removeEventListener("click", onclick);
children.forEach(function(x) {
x.classList.remove("hidden");
});
};
cell.classList.add("link");
cell.addEventListener("click", onclick);
}
});
if (othersTime > 0) {
var cells = [];
for (var i = 0; i < maxLength; i++) {
cells.push(parent[i]);
}
cells.push(othersTime.toFixed(3));
cells[level] = 'others';
var cols = createRow(table, 'td', cells);
for (i = 0; i < level; i++) {
cols[i].className = 'muted';
}
var children = addLevel(level + 1, parent.concat([x.parts[level]]), true);
if (children.length > 0) {
var cell = cols[level]; var cell = cols[level];
var tr = cell.parentNode;
var onclick = function() { var onclick = function() {
tr.classList.add("hidden");
cell.classList.remove("link"); cell.classList.remove("link");
cell.removeEventListener("click", onclick); cell.removeEventListener("click", onclick);
othersRows.forEach(function(x) { children.forEach(function(x) {
x.classList.remove("hidden"); x.classList.remove("hidden");
}); });
}; };
cell.title = othersList.join(", ");
cell.classList.add("link"); cell.classList.add("link");
cell.addEventListener("click", onclick); cell.addEventListener("click", onclick);
}
});
if (hide) { if (othersTime > 0) {
tr.classList.add("hidden"); var cells = [];
} for (var i = 0; i < maxLength; i++) {
cells.push(parent[i]);
childrenRows.push(tr); }
cells.push(othersTime.toFixed(3));
cells[level] = 'others';
var cols = createRow(table, 'td', cells);
for (i = 0; i < level; i++) {
cols[i].className = 'muted';
} }
return childrenRows; var cell = cols[level];
}; var tr = cell.parentNode;
var onclick = function() {
tr.classList.add("hidden");
cell.classList.remove("link");
cell.removeEventListener("click", onclick);
othersRows.forEach(function(x) {
x.classList.remove("hidden");
});
};
addLevel(0, []); cell.title = othersList.join(", ");
cell.classList.add("link");
cell.addEventListener("click", onclick);
if (hide) {
tr.classList.add("hidden");
}
childrenRows.push(tr);
}
return childrenRows;
};
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);
}); });
} }

View File

@ -76,6 +76,26 @@ 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) return;
try {
wakeLock = await navigator.wakeLock.request('screen');
} catch (err) {
console.error('Wake Lock is not supported.');
}
};
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';
@ -89,6 +109,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("");
@ -100,6 +121,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();

View File

@ -22,6 +22,9 @@
} }
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';
parent.resizeHandle.style.display = "none"; parent.resizeHandle.style.display = "none";
@ -41,7 +44,7 @@
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;
@ -64,7 +67,24 @@
parent.style.display = 'grid'; parent.style.display = 'grid';
parent.style.gap = '0'; parent.style.gap = '0';
const gridTemplateColumns = `${parent.children[0].style.flexGrow}fr ${PAD}px ${parent.children[1].style.flexGrow}fr`; 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.gridTemplateColumns = gridTemplateColumns;
parent.style.originalGridTemplateColumns = gridTemplateColumns; parent.style.originalGridTemplateColumns = gridTemplateColumns;
@ -132,7 +152,7 @@
} else { } else {
delta = R.screenX - evt.changedTouches[0].screenX; delta = R.screenX - evt.changedTouches[0].screenX;
} }
const leftColWidth = Math.max(Math.min(R.leftColStartWidth - delta, R.parent.offsetWidth - GRADIO_MIN_WIDTH - PAD), GRADIO_MIN_WIDTH); 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);
} }
}); });
@ -171,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);

View File

@ -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];
@ -136,8 +144,7 @@ function showSubmitInterruptingPlaceholder(tabname) {
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() {
@ -209,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);
@ -223,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);
@ -298,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', {
@ -336,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();
@ -411,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];

View File

@ -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({
@ -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])
@ -360,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.
@ -371,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):
@ -409,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
@ -437,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
@ -501,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,
@ -511,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)
@ -683,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 [
{ {

View File

@ -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")

View File

@ -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):

View File

@ -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

View File

@ -20,6 +20,7 @@ parser.add_argument("--dump-sysinfo", action='store_true', help="launch.py argum
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=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("--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("--models-dir", type=normalized_filepath, default=None, help="base path where models are stored; overrides --data-dir")
parser.add_argument("--config", type=normalized_filepath, default=sd_default_config, help="path to config which constructs model",) parser.add_argument("--config", type=normalized_filepath, default=sd_default_config, help="path to config which constructs model",)
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("--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("--ckpt-dir", type=normalized_filepath, default=None, help="Path to directory with stable diffusion checkpoints") parser.add_argument("--ckpt-dir", type=normalized_filepath, default=None, help="Path to directory with stable diffusion checkpoints")
@ -29,7 +30,7 @@ parser.add_argument("--gfpgan-model", type=normalized_filepath, help="GFPGAN mod
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=normalized_filepath, 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=normalized_filepath, 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=normalized_filepath, 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")
@ -41,7 +42,7 @@ 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)
@ -121,4 +122,7 @@ 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")

View File

@ -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)

View File

@ -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 = {}

View File

@ -114,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
@ -127,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
@ -206,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)
@ -233,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."
@ -258,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)
@ -269,3 +279,17 @@ def first_time_calculation():
x = torch.zeros((1, 1, 3, 3)).to(device, dtype) x = torch.zeros((1, 1, 3, 3)).to(device, dtype)
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`.")

View File

@ -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

View File

@ -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.

View File

@ -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):

View File

@ -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

View File

@ -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'),
@ -380,12 +417,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 +604,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 +654,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 +793,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
geninfo = items["comment"].decode('utf8', errors="ignore") if isinstance(items["comment"], bytes):
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 +821,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 +848,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

View File

@ -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,8 +224,15 @@ 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:
assert not shared.cmd_opts.hide_ui_dir_config, "Launched with --hide-ui-dir-config, batch img2img disabled" if img2img_batch_source_type == "upload":
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) 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"
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)
if processed is None: if processed is None:
processed = Processed(p, [], p.seed, "") processed = Processed(p, [], p.seed, "")

View File

@ -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,9 +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
if "Emphasis" not in res: 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" 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:
@ -456,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:
@ -470,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):
v = key(params) try:
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)

View File

@ -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):
@ -40,3 +42,5 @@ def backcompat(d):
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

View File

@ -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,7 +140,7 @@ 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 from modules import devices

View File

@ -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():
""" """

View File

@ -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
@ -55,7 +56,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 +77,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("## ", "")
@ -231,7 +232,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:
@ -445,7 +446,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")
@ -461,7 +461,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()

View File

@ -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:

View File

@ -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"):

View File

@ -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
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]) 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
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):

View File

@ -23,6 +23,7 @@ def load_file_from_url(
model_dir: str, model_dir: str,
progress: bool = True, progress: bool = True,
file_name: str | None = None, file_name: str | None = None,
hash_prefix: str | None = None,
) -> str: ) -> str:
"""Download a file from `url` into `model_dir`, using the file present if possible. """Download a file from `url` into `model_dir`, using the file present if possible.
@ -36,11 +37,11 @@ def load_file_from_url(
if not os.path.exists(cached_file): if not os.path.exists(cached_file):
print(f'Downloading: "{url}" to {cached_file}\n') print(f'Downloading: "{url}" to {cached_file}\n')
from torch.hub import download_url_to_file from torch.hub import download_url_to_file
download_url_to_file(url, cached_file, progress=progress) download_url_to_file(url, cached_file, progress=progress, hash_prefix=hash_prefix)
return cached_file 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: 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:
""" """
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 +50,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 +80,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 +112,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 +131,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 +169,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 +192,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

View File

@ -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)

View File

@ -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]))

622
modules/models/sd3/mmdit.py Normal file
View File

@ -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

View File

@ -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)

View File

@ -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 = "https://huggingface.co/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 = "https://huggingface.co/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 = "https://huggingface.co/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)

View File

@ -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)

View File

@ -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"

View File

@ -240,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:

View File

@ -24,14 +24,15 @@ default_sd_model_file = sd_model_file
# Parse the --data-dir flag first so we can use it as a base for our other argument default values # Parse the --data-dir flag first so we can use it as a base for our other argument default values
parser_pre = argparse.ArgumentParser(add_help=False) parser_pre = argparse.ArgumentParser(add_help=False)
parser_pre.add_argument("--data-dir", type=str, default=os.path.dirname(modules_path), help="base path where all user data is stored", ) parser_pre.add_argument("--data-dir", type=str, default=os.path.dirname(modules_path), help="base path where all user data is stored", )
parser_pre.add_argument("--models-dir", type=str, default=None, help="base path where models are stored; overrides --data-dir", )
cmd_opts_pre = parser_pre.parse_known_args()[0] cmd_opts_pre = parser_pre.parse_known_args()[0]
data_path = cmd_opts_pre.data_dir data_path = cmd_opts_pre.data_dir
models_path = os.path.join(data_path, "models") models_path = cmd_opts_pre.models_dir if cmd_opts_pre.models_dir else os.path.join(data_path, "models")
extensions_dir = os.path.join(data_path, "extensions") extensions_dir = os.path.join(data_path, "extensions")
extensions_builtin_dir = os.path.join(script_path, "extensions-builtin") extensions_builtin_dir = os.path.join(script_path, "extensions-builtin")
config_states_dir = os.path.join(script_path, "config_states") config_states_dir = os.path.join(script_path, "config_states")
default_output_dir = os.path.join(data_path, "output") default_output_dir = os.path.join(data_path, "outputs")
roboto_ttf_file = os.path.join(modules_path, 'Roboto-Regular.ttf') roboto_ttf_file = os.path.join(modules_path, 'Roboto-Regular.ttf')

View File

@ -17,10 +17,10 @@ def run_postprocessing(extras_mode, image, image_folder, input_dir, output_dir,
if extras_mode == 1: if extras_mode == 1:
for img in image_folder: for img in image_folder:
if isinstance(img, Image.Image): if isinstance(img, Image.Image):
image = img image = images.fix_image(img)
fn = '' fn = ''
else: else:
image = Image.open(os.path.abspath(img.name)) image = images.read(os.path.abspath(img.name))
fn = os.path.splitext(img.orig_name)[0] fn = os.path.splitext(img.orig_name)[0]
yield image, fn yield image, fn
elif extras_mode == 2: elif extras_mode == 2:
@ -51,22 +51,24 @@ def run_postprocessing(extras_mode, image, image_folder, input_dir, output_dir,
shared.state.textinfo = name shared.state.textinfo = name
shared.state.skipped = False shared.state.skipped = False
if shared.state.interrupted: if shared.state.interrupted or shared.state.stopping_generation:
break break
if isinstance(image_placeholder, str): if isinstance(image_placeholder, str):
try: try:
image_data = Image.open(image_placeholder) image_data = images.read(image_placeholder)
except Exception: except Exception:
continue continue
else: else:
image_data = image_placeholder image_data = image_placeholder
image_data = image_data if image_data.mode in ("RGBA", "RGB") else image_data.convert("RGB")
parameters, existing_pnginfo = images.read_info_from_image(image_data) parameters, existing_pnginfo = images.read_info_from_image(image_data)
if parameters: if parameters:
existing_pnginfo["parameters"] = parameters existing_pnginfo["parameters"] = parameters
initial_pp = scripts_postprocessing.PostprocessedImage(image_data.convert("RGB")) initial_pp = scripts_postprocessing.PostprocessedImage(image_data)
scripts.scripts_postproc.run(initial_pp, args) scripts.scripts_postproc.run(initial_pp, args)
@ -122,8 +124,6 @@ def run_postprocessing(extras_mode, image, image_folder, input_dir, output_dir,
if extras_mode != 2 or show_extras_results: if extras_mode != 2 or show_extras_results:
outputs.append(pp.image) outputs.append(pp.image)
image_data.close()
devices.torch_gc() devices.torch_gc()
shared.state.end() shared.state.end()
return outputs, ui_common.plaintext_to_html(infotext), '' return outputs, ui_common.plaintext_to_html(infotext), ''
@ -133,13 +133,15 @@ def run_postprocessing_webui(id_task, *args, **kwargs):
return run_postprocessing(*args, **kwargs) return run_postprocessing(*args, **kwargs)
def run_extras(extras_mode, resize_mode, image, image_folder, input_dir, output_dir, show_extras_results, gfpgan_visibility, codeformer_visibility, codeformer_weight, upscaling_resize, upscaling_resize_w, upscaling_resize_h, upscaling_crop, extras_upscaler_1, extras_upscaler_2, extras_upscaler_2_visibility, upscale_first: bool, save_output: bool = True): def run_extras(extras_mode, resize_mode, image, image_folder, input_dir, output_dir, show_extras_results, gfpgan_visibility, codeformer_visibility, codeformer_weight, upscaling_resize, upscaling_resize_w, upscaling_resize_h, upscaling_crop, extras_upscaler_1, extras_upscaler_2, extras_upscaler_2_visibility, upscale_first: bool, save_output: bool = True, max_side_length: int = 0):
"""old handler for API""" """old handler for API"""
args = scripts.scripts_postproc.create_args_for_run({ args = scripts.scripts_postproc.create_args_for_run({
"Upscale": { "Upscale": {
"upscale_enabled": True,
"upscale_mode": resize_mode, "upscale_mode": resize_mode,
"upscale_by": upscaling_resize, "upscale_by": upscaling_resize,
"max_side_length": max_side_length,
"upscale_to_width": upscaling_resize_w, "upscale_to_width": upscaling_resize_w,
"upscale_to_height": upscaling_resize_h, "upscale_to_height": upscaling_resize_h,
"upscale_crop": upscaling_crop, "upscale_crop": upscaling_crop,

View File

@ -16,7 +16,7 @@ from skimage import exposure
from typing import Any from typing import Any
import modules.sd_hijack import modules.sd_hijack
from modules import devices, prompt_parser, masking, sd_samplers, lowvram, infotext_utils, extra_networks, sd_vae_approx, scripts, sd_samplers_common, sd_unet, errors, rng from modules import devices, prompt_parser, masking, sd_samplers, lowvram, infotext_utils, extra_networks, sd_vae_approx, scripts, sd_samplers_common, sd_unet, errors, rng, profiling
from modules.rng import slerp # noqa: F401 from modules.rng import slerp # noqa: F401
from modules.sd_hijack import model_hijack from modules.sd_hijack import model_hijack
from modules.sd_samplers_common import images_tensor_to_samples, decode_first_stage, approximation_indexes from modules.sd_samplers_common import images_tensor_to_samples, decode_first_stage, approximation_indexes
@ -115,20 +115,17 @@ def txt2img_image_conditioning(sd_model, x, width, height):
return x.new_zeros(x.shape[0], 2*sd_model.noise_augmentor.time_embed.dim, dtype=x.dtype, device=x.device) return x.new_zeros(x.shape[0], 2*sd_model.noise_augmentor.time_embed.dim, dtype=x.dtype, device=x.device)
else: else:
sd = sd_model.model.state_dict() if sd_model.is_sdxl_inpaint:
diffusion_model_input = sd.get('diffusion_model.input_blocks.0.0.weight', None) # The "masked-image" in this case will just be all 0.5 since the entire image is masked.
if diffusion_model_input is not None: image_conditioning = torch.ones(x.shape[0], 3, height, width, device=x.device) * 0.5
if diffusion_model_input.shape[1] == 9: image_conditioning = images_tensor_to_samples(image_conditioning,
# The "masked-image" in this case will just be all 0.5 since the entire image is masked. approximation_indexes.get(opts.sd_vae_encode_method))
image_conditioning = torch.ones(x.shape[0], 3, height, width, device=x.device) * 0.5
image_conditioning = images_tensor_to_samples(image_conditioning,
approximation_indexes.get(opts.sd_vae_encode_method))
# Add the fake full 1s mask to the first dimension. # Add the fake full 1s mask to the first dimension.
image_conditioning = torch.nn.functional.pad(image_conditioning, (0, 0, 0, 0, 1, 0), value=1.0) image_conditioning = torch.nn.functional.pad(image_conditioning, (0, 0, 0, 0, 1, 0), value=1.0)
image_conditioning = image_conditioning.to(x.dtype) image_conditioning = image_conditioning.to(x.dtype)
return image_conditioning return image_conditioning
# Dummy zero conditioning if we're not using inpainting or unclip models. # Dummy zero conditioning if we're not using inpainting or unclip models.
# Still takes up a bit of memory, but no encoder call. # Still takes up a bit of memory, but no encoder call.
@ -152,6 +149,7 @@ class StableDiffusionProcessing:
seed_resize_from_w: int = -1 seed_resize_from_w: int = -1
seed_enable_extras: bool = True seed_enable_extras: bool = True
sampler_name: str = None sampler_name: str = None
scheduler: str = None
batch_size: int = 1 batch_size: int = 1
n_iter: int = 1 n_iter: int = 1
steps: int = 50 steps: int = 50
@ -237,11 +235,6 @@ class StableDiffusionProcessing:
self.styles = [] self.styles = []
self.sampler_noise_scheduler_override = None self.sampler_noise_scheduler_override = None
self.s_min_uncond = self.s_min_uncond if self.s_min_uncond is not None else opts.s_min_uncond
self.s_churn = self.s_churn if self.s_churn is not None else opts.s_churn
self.s_tmin = self.s_tmin if self.s_tmin is not None else opts.s_tmin
self.s_tmax = (self.s_tmax if self.s_tmax is not None else opts.s_tmax) or float('inf')
self.s_noise = self.s_noise if self.s_noise is not None else opts.s_noise
self.extra_generation_params = self.extra_generation_params or {} self.extra_generation_params = self.extra_generation_params or {}
self.override_settings = self.override_settings or {} self.override_settings = self.override_settings or {}
@ -258,6 +251,13 @@ class StableDiffusionProcessing:
self.cached_uc = StableDiffusionProcessing.cached_uc self.cached_uc = StableDiffusionProcessing.cached_uc
self.cached_c = StableDiffusionProcessing.cached_c self.cached_c = StableDiffusionProcessing.cached_c
def fill_fields_from_opts(self):
self.s_min_uncond = self.s_min_uncond if self.s_min_uncond is not None else opts.s_min_uncond
self.s_churn = self.s_churn if self.s_churn is not None else opts.s_churn
self.s_tmin = self.s_tmin if self.s_tmin is not None else opts.s_tmin
self.s_tmax = (self.s_tmax if self.s_tmax is not None else opts.s_tmax) or float('inf')
self.s_noise = self.s_noise if self.s_noise is not None else opts.s_noise
@property @property
def sd_model(self): def sd_model(self):
return shared.sd_model return shared.sd_model
@ -389,11 +389,8 @@ class StableDiffusionProcessing:
if self.sampler.conditioning_key == "crossattn-adm": if self.sampler.conditioning_key == "crossattn-adm":
return self.unclip_image_conditioning(source_image) return self.unclip_image_conditioning(source_image)
sd = self.sampler.model_wrap.inner_model.model.state_dict() if self.sampler.model_wrap.inner_model.is_sdxl_inpaint:
diffusion_model_input = sd.get('diffusion_model.input_blocks.0.0.weight', None) return self.inpainting_image_conditioning(source_image, latent_image, image_mask=image_mask)
if diffusion_model_input is not None:
if diffusion_model_input.shape[1] == 9:
return self.inpainting_image_conditioning(source_image, latent_image, image_mask=image_mask)
# Dummy zero conditioning if we're not using inpainting or depth model. # Dummy zero conditioning if we're not using inpainting or depth model.
return latent_image.new_zeros(latent_image.shape[0], 5, 1, 1) return latent_image.new_zeros(latent_image.shape[0], 5, 1, 1)
@ -568,7 +565,7 @@ class Processed:
self.all_negative_prompts = all_negative_prompts or p.all_negative_prompts or [self.negative_prompt] self.all_negative_prompts = all_negative_prompts or p.all_negative_prompts or [self.negative_prompt]
self.all_seeds = all_seeds or p.all_seeds or [self.seed] self.all_seeds = all_seeds or p.all_seeds or [self.seed]
self.all_subseeds = all_subseeds or p.all_subseeds or [self.subseed] self.all_subseeds = all_subseeds or p.all_subseeds or [self.subseed]
self.infotexts = infotexts or [info] self.infotexts = infotexts or [info] * len(images_list)
self.version = program_version() self.version = program_version()
def js(self): def js(self):
@ -607,7 +604,7 @@ class Processed:
"version": self.version, "version": self.version,
} }
return json.dumps(obj) return json.dumps(obj, default=lambda o: None)
def infotext(self, p: StableDiffusionProcessing, index): def infotext(self, p: StableDiffusionProcessing, index):
return create_infotext(p, self.all_prompts, self.all_seeds, self.all_subseeds, comments=[], position_in_batch=index % self.batch_size, iteration=index // self.batch_size) return create_infotext(p, self.all_prompts, self.all_seeds, self.all_subseeds, comments=[], position_in_batch=index % self.batch_size, iteration=index // self.batch_size)
@ -628,6 +625,9 @@ class DecodedSamples(list):
def decode_latent_batch(model, batch, target_device=None, check_for_nans=False): def decode_latent_batch(model, batch, target_device=None, check_for_nans=False):
samples = DecodedSamples() samples = DecodedSamples()
if check_for_nans:
devices.test_for_nans(batch, "unet")
for i in range(batch.shape[0]): for i in range(batch.shape[0]):
sample = decode_first_stage(model, batch[i:i + 1])[0] sample = decode_first_stage(model, batch[i:i + 1])[0]
@ -703,7 +703,53 @@ def program_version():
def create_infotext(p, all_prompts, all_seeds, all_subseeds, comments=None, iteration=0, position_in_batch=0, use_main_prompt=False, index=None, all_negative_prompts=None): def create_infotext(p, all_prompts, all_seeds, all_subseeds, comments=None, iteration=0, position_in_batch=0, use_main_prompt=False, index=None, all_negative_prompts=None):
if index is None: """
this function is used to generate the infotext that is stored in the generated images, it's contains the parameters that are required to generate the imagee
Args:
p: StableDiffusionProcessing
all_prompts: list[str]
all_seeds: list[int]
all_subseeds: list[int]
comments: list[str]
iteration: int
position_in_batch: int
use_main_prompt: bool
index: int
all_negative_prompts: list[str]
Returns: str
Extra generation params
p.extra_generation_params dictionary allows for additional parameters to be added to the infotext
this can be use by the base webui or extensions.
To add a new entry, add a new key value pair, the dictionary key will be used as the key of the parameter in the infotext
the value generation_params can be defined as:
- str | None
- List[str|None]
- callable func(**kwargs) -> str | None
When defined as a string, it will be used as without extra processing; this is this most common use case.
Defining as a list allows for parameter that changes across images in the job, for example, the 'Seed' parameter.
The list should have the same length as the total number of images in the entire job.
Defining as a callable function allows parameter cannot be generated earlier or when extra logic is required.
For example 'Hires prompt', due to reasons the hr_prompt might be changed by process in the pipeline or extensions
and may vary across different images, defining as a static string or list would not work.
The function takes locals() as **kwargs, as such will have access to variables like 'p' and 'index'.
the base signature of the function should be:
func(**kwargs) -> str | None
optionally it can have additional arguments that will be used in the function:
func(p, index, **kwargs) -> str | None
note: for better future compatibility even though this function will have access to all variables in the locals(),
it is recommended to only use the arguments present in the function signature of create_infotext.
For actual implementation examples, see StableDiffusionProcessingTxt2Img.init > get_hr_prompt.
"""
if use_main_prompt:
index = 0
elif index is None:
index = position_in_batch + iteration * p.batch_size index = position_in_batch + iteration * p.batch_size
if all_negative_prompts is None: if all_negative_prompts is None:
@ -714,6 +760,9 @@ def create_infotext(p, all_prompts, all_seeds, all_subseeds, comments=None, iter
token_merging_ratio = p.get_token_merging_ratio() token_merging_ratio = p.get_token_merging_ratio()
token_merging_ratio_hr = p.get_token_merging_ratio(for_hr=True) token_merging_ratio_hr = p.get_token_merging_ratio(for_hr=True)
prompt_text = p.main_prompt if use_main_prompt else all_prompts[index]
negative_prompt = p.main_negative_prompt if use_main_prompt else all_negative_prompts[index]
uses_ensd = opts.eta_noise_seed_delta != 0 uses_ensd = opts.eta_noise_seed_delta != 0
if uses_ensd: if uses_ensd:
uses_ensd = sd_samplers_common.is_sampler_using_eta_noise_seed_delta(p) uses_ensd = sd_samplers_common.is_sampler_using_eta_noise_seed_delta(p)
@ -721,6 +770,7 @@ def create_infotext(p, all_prompts, all_seeds, all_subseeds, comments=None, iter
generation_params = { generation_params = {
"Steps": p.steps, "Steps": p.steps,
"Sampler": p.sampler_name, "Sampler": p.sampler_name,
"Schedule type": p.scheduler,
"CFG scale": p.cfg_scale, "CFG scale": p.cfg_scale,
"Image CFG scale": getattr(p, 'image_cfg_scale', None), "Image CFG scale": getattr(p, 'image_cfg_scale', None),
"Seed": p.all_seeds[0] if use_main_prompt else all_seeds[index], "Seed": p.all_seeds[0] if use_main_prompt else all_seeds[index],
@ -743,17 +793,25 @@ def create_infotext(p, all_prompts, all_seeds, all_subseeds, comments=None, iter
"Token merging ratio hr": None if not enable_hr or token_merging_ratio_hr == 0 else token_merging_ratio_hr, "Token merging ratio hr": None if not enable_hr or token_merging_ratio_hr == 0 else token_merging_ratio_hr,
"Init image hash": getattr(p, 'init_img_hash', None), "Init image hash": getattr(p, 'init_img_hash', None),
"RNG": opts.randn_source if opts.randn_source != "GPU" else None, "RNG": opts.randn_source if opts.randn_source != "GPU" else None,
"NGMS": None if p.s_min_uncond == 0 else p.s_min_uncond,
"Tiling": "True" if p.tiling else None, "Tiling": "True" if p.tiling else None,
**p.extra_generation_params, **p.extra_generation_params,
"Version": program_version() if opts.add_version_to_infotext else None, "Version": program_version() if opts.add_version_to_infotext else None,
"User": p.user if opts.add_user_name_to_info else None, "User": p.user if opts.add_user_name_to_info else None,
} }
for key, value in generation_params.items():
try:
if isinstance(value, list):
generation_params[key] = value[index]
elif callable(value):
generation_params[key] = value(**locals())
except Exception:
errors.report(f'Error creating infotext for key "{key}"', exc_info=True)
generation_params[key] = None
generation_params_text = ", ".join([k if k == v else f'{k}: {infotext_utils.quote(v)}' for k, v in generation_params.items() if v is not None]) generation_params_text = ", ".join([k if k == v else f'{k}: {infotext_utils.quote(v)}' for k, v in generation_params.items() if v is not None])
prompt_text = p.main_prompt if use_main_prompt else all_prompts[index] negative_prompt_text = f"\nNegative prompt: {negative_prompt}" if negative_prompt else ""
negative_prompt_text = f"\nNegative prompt: {p.main_negative_prompt if use_main_prompt else all_negative_prompts[index]}" if all_negative_prompts[index] else ""
return f"{prompt_text}{negative_prompt_text}\n{generation_params_text}".strip() return f"{prompt_text}{negative_prompt_text}\n{generation_params_text}".strip()
@ -782,7 +840,11 @@ def process_images(p: StableDiffusionProcessing) -> Processed:
sd_models.apply_token_merging(p.sd_model, p.get_token_merging_ratio()) sd_models.apply_token_merging(p.sd_model, p.get_token_merging_ratio())
res = process_images_inner(p) # backwards compatibility, fix sampler and scheduler if invalid
sd_samplers.fix_p_invalid_sampler_and_scheduler(p)
with profiling.Profiler():
res = process_images_inner(p)
finally: finally:
sd_models.apply_token_merging(p.sd_model, 0) sd_models.apply_token_merging(p.sd_model, 0)
@ -822,6 +884,9 @@ def process_images_inner(p: StableDiffusionProcessing) -> Processed:
if p.refiner_checkpoint_info is None: if p.refiner_checkpoint_info is None:
raise Exception(f'Could not find checkpoint with name {p.refiner_checkpoint}') raise Exception(f'Could not find checkpoint with name {p.refiner_checkpoint}')
if hasattr(shared.sd_model, 'fix_dimensions'):
p.width, p.height = shared.sd_model.fix_dimensions(p.width, p.height)
p.sd_model_name = shared.sd_model.sd_checkpoint_info.name_for_extra p.sd_model_name = shared.sd_model.sd_checkpoint_info.name_for_extra
p.sd_model_hash = shared.sd_model.sd_model_hash p.sd_model_hash = shared.sd_model.sd_model_hash
p.sd_vae_name = sd_vae.get_loaded_vae_name() p.sd_vae_name = sd_vae.get_loaded_vae_name()
@ -830,6 +895,7 @@ def process_images_inner(p: StableDiffusionProcessing) -> Processed:
modules.sd_hijack.model_hijack.apply_circular(p.tiling) modules.sd_hijack.model_hijack.apply_circular(p.tiling)
modules.sd_hijack.model_hijack.clear_comments() modules.sd_hijack.model_hijack.clear_comments()
p.fill_fields_from_opts()
p.setup_prompts() p.setup_prompts()
if isinstance(seed, list): if isinstance(seed, list):
@ -879,7 +945,8 @@ def process_images_inner(p: StableDiffusionProcessing) -> Processed:
p.seeds = p.all_seeds[n * p.batch_size:(n + 1) * p.batch_size] p.seeds = p.all_seeds[n * p.batch_size:(n + 1) * p.batch_size]
p.subseeds = p.all_subseeds[n * p.batch_size:(n + 1) * p.batch_size] p.subseeds = p.all_subseeds[n * p.batch_size:(n + 1) * p.batch_size]
p.rng = rng.ImageRNG((opt_C, p.height // opt_f, p.width // opt_f), p.seeds, subseeds=p.subseeds, subseed_strength=p.subseed_strength, seed_resize_from_h=p.seed_resize_from_h, seed_resize_from_w=p.seed_resize_from_w) latent_channels = getattr(shared.sd_model, 'latent_channels', opt_C)
p.rng = rng.ImageRNG((latent_channels, p.height // opt_f, p.width // opt_f), p.seeds, subseeds=p.subseeds, subseed_strength=p.subseed_strength, seed_resize_from_h=p.seed_resize_from_h, seed_resize_from_w=p.seed_resize_from_w)
if p.scripts is not None: if p.scripts is not None:
p.scripts.before_process_batch(p, batch_number=n, prompts=p.prompts, seeds=p.seeds, subseeds=p.subseeds) p.scripts.before_process_batch(p, batch_number=n, prompts=p.prompts, seeds=p.seeds, subseeds=p.subseeds)
@ -896,22 +963,22 @@ def process_images_inner(p: StableDiffusionProcessing) -> Processed:
if p.scripts is not None: if p.scripts is not None:
p.scripts.process_batch(p, batch_number=n, prompts=p.prompts, seeds=p.seeds, subseeds=p.subseeds) p.scripts.process_batch(p, batch_number=n, prompts=p.prompts, seeds=p.seeds, subseeds=p.subseeds)
p.setup_conds()
p.extra_generation_params.update(model_hijack.extra_generation_params)
# params.txt should be saved after scripts.process_batch, since the # params.txt should be saved after scripts.process_batch, since the
# infotext could be modified by that callback # infotext could be modified by that callback
# Example: a wildcard processed by process_batch sets an extra model # Example: a wildcard processed by process_batch sets an extra model
# strength, which is saved as "Model Strength: 1.0" in the infotext # strength, which is saved as "Model Strength: 1.0" in the infotext
if n == 0: if n == 0 and not cmd_opts.no_prompt_history:
with open(os.path.join(paths.data_path, "params.txt"), "w", encoding="utf8") as file: with open(os.path.join(paths.data_path, "params.txt"), "w", encoding="utf8") as file:
processed = Processed(p, []) processed = Processed(p, [])
file.write(processed.infotext(p, 0)) file.write(processed.infotext(p, 0))
p.setup_conds()
for comment in model_hijack.comments: for comment in model_hijack.comments:
p.comment(comment) p.comment(comment)
p.extra_generation_params.update(model_hijack.extra_generation_params)
if p.n_iter > 1: if p.n_iter > 1:
shared.state.job = f"Batch {n+1} out of {p.n_iter}" shared.state.job = f"Batch {n+1} out of {p.n_iter}"
@ -928,6 +995,8 @@ def process_images_inner(p: StableDiffusionProcessing) -> Processed:
if getattr(samples_ddim, 'already_decoded', False): if getattr(samples_ddim, 'already_decoded', False):
x_samples_ddim = samples_ddim x_samples_ddim = samples_ddim
else: else:
devices.test_for_nans(samples_ddim, "unet")
if opts.sd_vae_decode_method != 'Full': if opts.sd_vae_decode_method != 'Full':
p.extra_generation_params['VAE Decoder'] = opts.sd_vae_decode_method p.extra_generation_params['VAE Decoder'] = opts.sd_vae_decode_method
x_samples_ddim = decode_latent_batch(p.sd_model, samples_ddim, target_device=devices.cpu, check_for_nans=True) x_samples_ddim = decode_latent_batch(p.sd_model, samples_ddim, target_device=devices.cpu, check_for_nans=True)
@ -1106,6 +1175,7 @@ class StableDiffusionProcessingTxt2Img(StableDiffusionProcessing):
hr_resize_y: int = 0 hr_resize_y: int = 0
hr_checkpoint_name: str = None hr_checkpoint_name: str = None
hr_sampler_name: str = None hr_sampler_name: str = None
hr_scheduler: str = None
hr_prompt: str = '' hr_prompt: str = ''
hr_negative_prompt: str = '' hr_negative_prompt: str = ''
force_task_id: str = None force_task_id: str = None
@ -1194,11 +1264,21 @@ class StableDiffusionProcessingTxt2Img(StableDiffusionProcessing):
if self.hr_sampler_name is not None and self.hr_sampler_name != self.sampler_name: if self.hr_sampler_name is not None and self.hr_sampler_name != self.sampler_name:
self.extra_generation_params["Hires sampler"] = self.hr_sampler_name self.extra_generation_params["Hires sampler"] = self.hr_sampler_name
if tuple(self.hr_prompt) != tuple(self.prompt): def get_hr_prompt(p, index, prompt_text, **kwargs):
self.extra_generation_params["Hires prompt"] = self.hr_prompt hr_prompt = p.all_hr_prompts[index]
return hr_prompt if hr_prompt != prompt_text else None
if tuple(self.hr_negative_prompt) != tuple(self.negative_prompt): def get_hr_negative_prompt(p, index, negative_prompt, **kwargs):
self.extra_generation_params["Hires negative prompt"] = self.hr_negative_prompt hr_negative_prompt = p.all_hr_negative_prompts[index]
return hr_negative_prompt if hr_negative_prompt != negative_prompt else None
self.extra_generation_params["Hires prompt"] = get_hr_prompt
self.extra_generation_params["Hires negative prompt"] = get_hr_negative_prompt
self.extra_generation_params["Hires schedule type"] = None # to be set in sd_samplers_kdiffusion.py
if self.hr_scheduler is None:
self.hr_scheduler = self.scheduler
self.latent_scale_mode = shared.latent_upscale_modes.get(self.hr_upscaler, None) if self.hr_upscaler is not None else shared.latent_upscale_modes.get(shared.latent_upscale_default_mode, "nearest") self.latent_scale_mode = shared.latent_upscale_modes.get(self.hr_upscaler, None) if self.hr_upscaler is not None else shared.latent_upscale_modes.get(shared.latent_upscale_default_mode, "nearest")
if self.enable_hr and self.latent_scale_mode is None: if self.enable_hr and self.latent_scale_mode is None:
@ -1254,6 +1334,15 @@ class StableDiffusionProcessingTxt2Img(StableDiffusionProcessing):
# here we generate an image normally # here we generate an image normally
x = self.rng.next() x = self.rng.next()
if self.scripts is not None:
self.scripts.process_before_every_sampling(
p=self,
x=x,
noise=x,
c=conditioning,
uc=unconditional_conditioning
)
samples = self.sampler.sample(self, x, conditioning, unconditional_conditioning, image_conditioning=self.txt2img_image_conditioning(x)) samples = self.sampler.sample(self, x, conditioning, unconditional_conditioning, image_conditioning=self.txt2img_image_conditioning(x))
del x del x
@ -1354,6 +1443,13 @@ class StableDiffusionProcessingTxt2Img(StableDiffusionProcessing):
if self.scripts is not None: if self.scripts is not None:
self.scripts.before_hr(self) self.scripts.before_hr(self)
self.scripts.process_before_every_sampling(
p=self,
x=samples,
noise=noise,
c=self.hr_c,
uc=self.hr_uc,
)
samples = self.sampler.sample_img2img(self, samples, noise, self.hr_c, self.hr_uc, steps=self.hr_second_pass_steps or self.steps, image_conditioning=image_conditioning) samples = self.sampler.sample_img2img(self, samples, noise, self.hr_c, self.hr_uc, steps=self.hr_second_pass_steps or self.steps, image_conditioning=image_conditioning)
@ -1540,16 +1636,23 @@ class StableDiffusionProcessingImg2Img(StableDiffusionProcessing):
if self.inpaint_full_res: if self.inpaint_full_res:
self.mask_for_overlay = image_mask self.mask_for_overlay = image_mask
mask = image_mask.convert('L') mask = image_mask.convert('L')
crop_region = masking.get_crop_region(mask, self.inpaint_full_res_padding) crop_region = masking.get_crop_region_v2(mask, self.inpaint_full_res_padding)
crop_region = masking.expand_crop_region(crop_region, self.width, self.height, mask.width, mask.height) if crop_region:
x1, y1, x2, y2 = crop_region crop_region = masking.expand_crop_region(crop_region, self.width, self.height, mask.width, mask.height)
x1, y1, x2, y2 = crop_region
mask = mask.crop(crop_region) mask = mask.crop(crop_region)
image_mask = images.resize_image(2, mask, self.width, self.height) image_mask = images.resize_image(2, mask, self.width, self.height)
self.paste_to = (x1, y1, x2-x1, y2-y1) self.paste_to = (x1, y1, x2-x1, y2-y1)
self.extra_generation_params["Inpaint area"] = "Only masked"
self.extra_generation_params["Inpaint area"] = "Only masked" self.extra_generation_params["Masked area padding"] = self.inpaint_full_res_padding
self.extra_generation_params["Masked area padding"] = self.inpaint_full_res_padding else:
crop_region = None
image_mask = None
self.mask_for_overlay = None
self.inpaint_full_res = False
massage = 'Unable to perform "Inpaint Only mask" because mask is blank, switch to img2img mode.'
model_hijack.comments.append(massage)
logging.info(massage)
else: else:
image_mask = images.resize_image(self.resize_mode, image_mask, self.width, self.height) image_mask = images.resize_image(self.resize_mode, image_mask, self.width, self.height)
np_mask = np.array(image_mask) np_mask = np.array(image_mask)
@ -1577,6 +1680,8 @@ class StableDiffusionProcessingImg2Img(StableDiffusionProcessing):
image = images.resize_image(self.resize_mode, image, self.width, self.height) image = images.resize_image(self.resize_mode, image, self.width, self.height)
if image_mask is not None: if image_mask is not None:
if self.mask_for_overlay.size != (image.width, image.height):
self.mask_for_overlay = images.resize_image(self.resize_mode, self.mask_for_overlay, image.width, image.height)
image_masked = Image.new('RGBa', (image.width, image.height)) image_masked = Image.new('RGBa', (image.width, image.height))
image_masked.paste(image.convert("RGBA").convert("RGBa"), mask=ImageOps.invert(self.mask_for_overlay.convert('L'))) image_masked.paste(image.convert("RGBA").convert("RGBa"), mask=ImageOps.invert(self.mask_for_overlay.convert('L')))
@ -1635,10 +1740,10 @@ class StableDiffusionProcessingImg2Img(StableDiffusionProcessing):
latmask = latmask[0] latmask = latmask[0]
if self.mask_round: if self.mask_round:
latmask = np.around(latmask) latmask = np.around(latmask)
latmask = np.tile(latmask[None], (4, 1, 1)) latmask = np.tile(latmask[None], (self.init_latent.shape[1], 1, 1))
self.mask = torch.asarray(1.0 - latmask).to(shared.device).type(self.sd_model.dtype) self.mask = torch.asarray(1.0 - latmask).to(shared.device).type(devices.dtype)
self.nmask = torch.asarray(latmask).to(shared.device).type(self.sd_model.dtype) self.nmask = torch.asarray(latmask).to(shared.device).type(devices.dtype)
# this needs to be fixed to be done in sample() using actual seeds for batches # this needs to be fixed to be done in sample() using actual seeds for batches
if self.inpainting_fill == 2: if self.inpainting_fill == 2:
@ -1658,6 +1763,14 @@ class StableDiffusionProcessingImg2Img(StableDiffusionProcessing):
self.extra_generation_params["Noise multiplier"] = self.initial_noise_multiplier self.extra_generation_params["Noise multiplier"] = self.initial_noise_multiplier
x *= self.initial_noise_multiplier x *= self.initial_noise_multiplier
if self.scripts is not None:
self.scripts.process_before_every_sampling(
p=self,
x=self.init_latent,
noise=x,
c=conditioning,
uc=unconditional_conditioning
)
samples = self.sampler.sample_img2img(self, self.init_latent, x, conditioning, unconditional_conditioning, image_conditioning=self.image_conditioning) samples = self.sampler.sample_img2img(self, self.init_latent, x, conditioning, unconditional_conditioning, image_conditioning=self.image_conditioning)
if self.mask is not None: if self.mask is not None:

View File

@ -26,6 +26,13 @@ class ScriptStripComments(scripts.Script):
p.main_prompt = strip_comments(p.main_prompt) p.main_prompt = strip_comments(p.main_prompt)
p.main_negative_prompt = strip_comments(p.main_negative_prompt) p.main_negative_prompt = strip_comments(p.main_negative_prompt)
if getattr(p, 'enable_hr', False):
p.all_hr_prompts = [strip_comments(x) for x in p.all_hr_prompts]
p.all_hr_negative_prompts = [strip_comments(x) for x in p.all_hr_negative_prompts]
p.hr_prompt = strip_comments(p.hr_prompt)
p.hr_negative_prompt = strip_comments(p.hr_negative_prompt)
def before_token_counter(params: script_callbacks.BeforeTokenCounterParams): def before_token_counter(params: script_callbacks.BeforeTokenCounterParams):
if not shared.opts.enable_prompt_comments: if not shared.opts.enable_prompt_comments:

View File

@ -0,0 +1,45 @@
import gradio as gr
from modules import scripts, sd_samplers, sd_schedulers, shared
from modules.infotext_utils import PasteField
from modules.ui_components import FormRow, FormGroup
class ScriptSampler(scripts.ScriptBuiltinUI):
section = "sampler"
def __init__(self):
self.steps = None
self.sampler_name = None
self.scheduler = None
def title(self):
return "Sampler"
def ui(self, is_img2img):
sampler_names = [x.name for x in sd_samplers.visible_samplers()]
scheduler_names = [x.label for x in sd_schedulers.schedulers]
if shared.opts.samplers_in_dropdown:
with FormRow(elem_id=f"sampler_selection_{self.tabname}"):
self.sampler_name = gr.Dropdown(label='Sampling method', elem_id=f"{self.tabname}_sampling", choices=sampler_names, value=sampler_names[0])
self.scheduler = gr.Dropdown(label='Schedule type', elem_id=f"{self.tabname}_scheduler", choices=scheduler_names, value=scheduler_names[0])
self.steps = gr.Slider(minimum=1, maximum=150, step=1, elem_id=f"{self.tabname}_steps", label="Sampling steps", value=20)
else:
with FormGroup(elem_id=f"sampler_selection_{self.tabname}"):
self.steps = gr.Slider(minimum=1, maximum=150, step=1, elem_id=f"{self.tabname}_steps", label="Sampling steps", value=20)
self.sampler_name = gr.Radio(label='Sampling method', elem_id=f"{self.tabname}_sampling", choices=sampler_names, value=sampler_names[0])
self.scheduler = gr.Dropdown(label='Schedule type', elem_id=f"{self.tabname}_scheduler", choices=scheduler_names, value=scheduler_names[0])
self.infotext_fields = [
PasteField(self.steps, "Steps", api="steps"),
PasteField(self.sampler_name, sd_samplers.get_sampler_from_infotext, api="sampler_name"),
PasteField(self.scheduler, sd_samplers.get_scheduler_from_infotext, api="scheduler"),
]
return self.steps, self.sampler_name, self.scheduler
def setup(self, p, steps, sampler_name, scheduler):
p.steps = steps
p.sampler_name = sampler_name
p.scheduler = scheduler

46
modules/profiling.py Normal file
View File

@ -0,0 +1,46 @@
import torch
from modules import shared, ui_gradio_extensions
class Profiler:
def __init__(self):
if not shared.opts.profiling_enable:
self.profiler = None
return
activities = []
if "CPU" in shared.opts.profiling_activities:
activities.append(torch.profiler.ProfilerActivity.CPU)
if "CUDA" in shared.opts.profiling_activities:
activities.append(torch.profiler.ProfilerActivity.CUDA)
if not activities:
self.profiler = None
return
self.profiler = torch.profiler.profile(
activities=activities,
record_shapes=shared.opts.profiling_record_shapes,
profile_memory=shared.opts.profiling_profile_memory,
with_stack=shared.opts.profiling_with_stack
)
def __enter__(self):
if self.profiler:
self.profiler.__enter__()
return self
def __exit__(self, exc_type, exc, exc_tb):
if self.profiler:
shared.state.textinfo = "Finishing profile..."
self.profiler.__exit__(exc_type, exc, exc_tb)
self.profiler.export_chrome_trace(shared.opts.profiling_filename)
def webpath():
return ui_gradio_extensions.webpath(shared.opts.profiling_filename)

View File

@ -268,7 +268,7 @@ def get_multicond_learned_conditioning(model, prompts, steps, hires_steps=None,
class DictWithShape(dict): class DictWithShape(dict):
def __init__(self, x, shape): def __init__(self, x, shape=None):
super().__init__() super().__init__()
self.update(x) self.update(x)

View File

@ -34,7 +34,7 @@ def randn_local(seed, shape):
def randn_like(x): def randn_like(x):
"""Generate a tensor with random numbers from a normal distribution using the previously initialized genrator. """Generate a tensor with random numbers from a normal distribution using the previously initialized generator.
Use either randn() or manual_seed() to initialize the generator.""" Use either randn() or manual_seed() to initialize the generator."""
@ -48,7 +48,7 @@ def randn_like(x):
def randn_without_seed(shape, generator=None): def randn_without_seed(shape, generator=None):
"""Generate a tensor with random numbers from a normal distribution using the previously initialized genrator. """Generate a tensor with random numbers from a normal distribution using the previously initialized generator.
Use either randn() or manual_seed() to initialize the generator.""" Use either randn() or manual_seed() to initialize the generator."""

View File

@ -64,8 +64,8 @@ class RestrictedUnpickler(pickle.Unpickler):
raise Exception(f"global '{module}/{name}' is forbidden") raise Exception(f"global '{module}/{name}' is forbidden")
# Regular expression that accepts 'dirname/version', 'dirname/data.pkl', and 'dirname/data/<number>' # Regular expression that accepts 'dirname/version', 'dirname/byteorder', 'dirname/data.pkl', '.data/serialization_id', and 'dirname/data/<number>'
allowed_zip_names_re = re.compile(r"^([^/]+)/((data/\d+)|version|(data\.pkl))$") allowed_zip_names_re = re.compile(r"^([^/]+)/((data/\d+)|version|byteorder|.data/serialization_id|(data\.pkl))$")
data_pkl_re = re.compile(r"^([^/]+)/data\.pkl$") data_pkl_re = re.compile(r"^([^/]+)/data\.pkl$")
def check_zip_filenames(filename, names): def check_zip_filenames(filename, names):

View File

@ -1,13 +1,14 @@
from __future__ import annotations
import dataclasses import dataclasses
import inspect import inspect
import os import os
from collections import namedtuple
from typing import Optional, Any from typing import Optional, Any
from fastapi import FastAPI from fastapi import FastAPI
from gradio import Blocks from gradio import Blocks
from modules import errors, timer from modules import errors, timer, extensions, shared, util
def report_exception(c, job): def report_exception(c, job):
@ -116,7 +117,105 @@ class BeforeTokenCounterParams:
is_positive: bool = True is_positive: bool = True
ScriptCallback = namedtuple("ScriptCallback", ["script", "callback"]) @dataclasses.dataclass
class ScriptCallback:
script: str
callback: any
name: str = "unnamed"
def add_callback(callbacks, fun, *, name=None, category='unknown', filename=None):
if filename is None:
stack = [x for x in inspect.stack() if x.filename != __file__]
filename = stack[0].filename if stack else 'unknown file'
extension = extensions.find_extension(filename)
extension_name = extension.canonical_name if extension else 'base'
callback_name = f"{extension_name}/{os.path.basename(filename)}/{category}"
if name is not None:
callback_name += f'/{name}'
unique_callback_name = callback_name
for index in range(1000):
existing = any(x.name == unique_callback_name for x in callbacks)
if not existing:
break
unique_callback_name = f'{callback_name}-{index+1}'
callbacks.append(ScriptCallback(filename, fun, unique_callback_name))
def sort_callbacks(category, unordered_callbacks, *, enable_user_sort=True):
callbacks = unordered_callbacks.copy()
callback_lookup = {x.name: x for x in callbacks}
dependencies = {}
order_instructions = {}
for extension in extensions.extensions:
for order_instruction in extension.metadata.list_callback_order_instructions():
if order_instruction.name in callback_lookup:
if order_instruction.name not in order_instructions:
order_instructions[order_instruction.name] = []
order_instructions[order_instruction.name].append(order_instruction)
if order_instructions:
for callback in callbacks:
dependencies[callback.name] = []
for callback in callbacks:
for order_instruction in order_instructions.get(callback.name, []):
for after in order_instruction.after:
if after not in callback_lookup:
continue
dependencies[callback.name].append(after)
for before in order_instruction.before:
if before not in callback_lookup:
continue
dependencies[before].append(callback.name)
sorted_names = util.topological_sort(dependencies)
callbacks = [callback_lookup[x] for x in sorted_names]
if enable_user_sort:
for name in reversed(getattr(shared.opts, 'prioritized_callbacks_' + category, [])):
index = next((i for i, callback in enumerate(callbacks) if callback.name == name), None)
if index is not None:
callbacks.insert(0, callbacks.pop(index))
return callbacks
def ordered_callbacks(category, unordered_callbacks=None, *, enable_user_sort=True):
if unordered_callbacks is None:
unordered_callbacks = callback_map.get('callbacks_' + category, [])
if not enable_user_sort:
return sort_callbacks(category, unordered_callbacks, enable_user_sort=False)
callbacks = ordered_callbacks_map.get(category)
if callbacks is not None and len(callbacks) == len(unordered_callbacks):
return callbacks
callbacks = sort_callbacks(category, unordered_callbacks)
ordered_callbacks_map[category] = callbacks
return callbacks
def enumerate_callbacks():
for category, callbacks in callback_map.items():
if category.startswith('callbacks_'):
category = category[10:]
yield category, callbacks
callback_map = dict( callback_map = dict(
callbacks_app_started=[], callbacks_app_started=[],
callbacks_model_loaded=[], callbacks_model_loaded=[],
@ -141,14 +240,18 @@ callback_map = dict(
callbacks_before_token_counter=[], callbacks_before_token_counter=[],
) )
ordered_callbacks_map = {}
def clear_callbacks(): def clear_callbacks():
for callback_list in callback_map.values(): for callback_list in callback_map.values():
callback_list.clear() callback_list.clear()
ordered_callbacks_map.clear()
def app_started_callback(demo: Optional[Blocks], app: FastAPI): def app_started_callback(demo: Optional[Blocks], app: FastAPI):
for c in callback_map['callbacks_app_started']: for c in ordered_callbacks('app_started'):
try: try:
c.callback(demo, app) c.callback(demo, app)
timer.startup_timer.record(os.path.basename(c.script)) timer.startup_timer.record(os.path.basename(c.script))
@ -157,7 +260,7 @@ def app_started_callback(demo: Optional[Blocks], app: FastAPI):
def app_reload_callback(): def app_reload_callback():
for c in callback_map['callbacks_on_reload']: for c in ordered_callbacks('on_reload'):
try: try:
c.callback() c.callback()
except Exception: except Exception:
@ -165,7 +268,7 @@ def app_reload_callback():
def model_loaded_callback(sd_model): def model_loaded_callback(sd_model):
for c in callback_map['callbacks_model_loaded']: for c in ordered_callbacks('model_loaded'):
try: try:
c.callback(sd_model) c.callback(sd_model)
except Exception: except Exception:
@ -175,7 +278,7 @@ def model_loaded_callback(sd_model):
def ui_tabs_callback(): def ui_tabs_callback():
res = [] res = []
for c in callback_map['callbacks_ui_tabs']: for c in ordered_callbacks('ui_tabs'):
try: try:
res += c.callback() or [] res += c.callback() or []
except Exception: except Exception:
@ -185,7 +288,7 @@ def ui_tabs_callback():
def ui_train_tabs_callback(params: UiTrainTabParams): def ui_train_tabs_callback(params: UiTrainTabParams):
for c in callback_map['callbacks_ui_train_tabs']: for c in ordered_callbacks('ui_train_tabs'):
try: try:
c.callback(params) c.callback(params)
except Exception: except Exception:
@ -193,7 +296,7 @@ def ui_train_tabs_callback(params: UiTrainTabParams):
def ui_settings_callback(): def ui_settings_callback():
for c in callback_map['callbacks_ui_settings']: for c in ordered_callbacks('ui_settings'):
try: try:
c.callback() c.callback()
except Exception: except Exception:
@ -201,7 +304,7 @@ def ui_settings_callback():
def before_image_saved_callback(params: ImageSaveParams): def before_image_saved_callback(params: ImageSaveParams):
for c in callback_map['callbacks_before_image_saved']: for c in ordered_callbacks('before_image_saved'):
try: try:
c.callback(params) c.callback(params)
except Exception: except Exception:
@ -209,7 +312,7 @@ def before_image_saved_callback(params: ImageSaveParams):
def image_saved_callback(params: ImageSaveParams): def image_saved_callback(params: ImageSaveParams):
for c in callback_map['callbacks_image_saved']: for c in ordered_callbacks('image_saved'):
try: try:
c.callback(params) c.callback(params)
except Exception: except Exception:
@ -217,7 +320,7 @@ def image_saved_callback(params: ImageSaveParams):
def extra_noise_callback(params: ExtraNoiseParams): def extra_noise_callback(params: ExtraNoiseParams):
for c in callback_map['callbacks_extra_noise']: for c in ordered_callbacks('extra_noise'):
try: try:
c.callback(params) c.callback(params)
except Exception: except Exception:
@ -225,7 +328,7 @@ def extra_noise_callback(params: ExtraNoiseParams):
def cfg_denoiser_callback(params: CFGDenoiserParams): def cfg_denoiser_callback(params: CFGDenoiserParams):
for c in callback_map['callbacks_cfg_denoiser']: for c in ordered_callbacks('cfg_denoiser'):
try: try:
c.callback(params) c.callback(params)
except Exception: except Exception:
@ -233,7 +336,7 @@ def cfg_denoiser_callback(params: CFGDenoiserParams):
def cfg_denoised_callback(params: CFGDenoisedParams): def cfg_denoised_callback(params: CFGDenoisedParams):
for c in callback_map['callbacks_cfg_denoised']: for c in ordered_callbacks('cfg_denoised'):
try: try:
c.callback(params) c.callback(params)
except Exception: except Exception:
@ -241,7 +344,7 @@ def cfg_denoised_callback(params: CFGDenoisedParams):
def cfg_after_cfg_callback(params: AfterCFGCallbackParams): def cfg_after_cfg_callback(params: AfterCFGCallbackParams):
for c in callback_map['callbacks_cfg_after_cfg']: for c in ordered_callbacks('cfg_after_cfg'):
try: try:
c.callback(params) c.callback(params)
except Exception: except Exception:
@ -249,7 +352,7 @@ def cfg_after_cfg_callback(params: AfterCFGCallbackParams):
def before_component_callback(component, **kwargs): def before_component_callback(component, **kwargs):
for c in callback_map['callbacks_before_component']: for c in ordered_callbacks('before_component'):
try: try:
c.callback(component, **kwargs) c.callback(component, **kwargs)
except Exception: except Exception:
@ -257,7 +360,7 @@ def before_component_callback(component, **kwargs):
def after_component_callback(component, **kwargs): def after_component_callback(component, **kwargs):
for c in callback_map['callbacks_after_component']: for c in ordered_callbacks('after_component'):
try: try:
c.callback(component, **kwargs) c.callback(component, **kwargs)
except Exception: except Exception:
@ -265,7 +368,7 @@ def after_component_callback(component, **kwargs):
def image_grid_callback(params: ImageGridLoopParams): def image_grid_callback(params: ImageGridLoopParams):
for c in callback_map['callbacks_image_grid']: for c in ordered_callbacks('image_grid'):
try: try:
c.callback(params) c.callback(params)
except Exception: except Exception:
@ -273,7 +376,7 @@ def image_grid_callback(params: ImageGridLoopParams):
def infotext_pasted_callback(infotext: str, params: dict[str, Any]): def infotext_pasted_callback(infotext: str, params: dict[str, Any]):
for c in callback_map['callbacks_infotext_pasted']: for c in ordered_callbacks('infotext_pasted'):
try: try:
c.callback(infotext, params) c.callback(infotext, params)
except Exception: except Exception:
@ -281,7 +384,7 @@ def infotext_pasted_callback(infotext: str, params: dict[str, Any]):
def script_unloaded_callback(): def script_unloaded_callback():
for c in reversed(callback_map['callbacks_script_unloaded']): for c in reversed(ordered_callbacks('script_unloaded')):
try: try:
c.callback() c.callback()
except Exception: except Exception:
@ -289,7 +392,7 @@ def script_unloaded_callback():
def before_ui_callback(): def before_ui_callback():
for c in reversed(callback_map['callbacks_before_ui']): for c in reversed(ordered_callbacks('before_ui')):
try: try:
c.callback() c.callback()
except Exception: except Exception:
@ -299,7 +402,7 @@ def before_ui_callback():
def list_optimizers_callback(): def list_optimizers_callback():
res = [] res = []
for c in callback_map['callbacks_list_optimizers']: for c in ordered_callbacks('list_optimizers'):
try: try:
c.callback(res) c.callback(res)
except Exception: except Exception:
@ -311,7 +414,7 @@ def list_optimizers_callback():
def list_unets_callback(): def list_unets_callback():
res = [] res = []
for c in callback_map['callbacks_list_unets']: for c in ordered_callbacks('list_unets'):
try: try:
c.callback(res) c.callback(res)
except Exception: except Exception:
@ -321,20 +424,13 @@ def list_unets_callback():
def before_token_counter_callback(params: BeforeTokenCounterParams): def before_token_counter_callback(params: BeforeTokenCounterParams):
for c in callback_map['callbacks_before_token_counter']: for c in ordered_callbacks('before_token_counter'):
try: try:
c.callback(params) c.callback(params)
except Exception: except Exception:
report_exception(c, 'before_token_counter') report_exception(c, 'before_token_counter')
def add_callback(callbacks, fun):
stack = [x for x in inspect.stack() if x.filename != __file__]
filename = stack[0].filename if stack else 'unknown file'
callbacks.append(ScriptCallback(filename, fun))
def remove_current_script_callbacks(): def remove_current_script_callbacks():
stack = [x for x in inspect.stack() if x.filename != __file__] stack = [x for x in inspect.stack() if x.filename != __file__]
filename = stack[0].filename if stack else 'unknown file' filename = stack[0].filename if stack else 'unknown file'
@ -343,32 +439,38 @@ def remove_current_script_callbacks():
for callback_list in callback_map.values(): for callback_list in callback_map.values():
for callback_to_remove in [cb for cb in callback_list if cb.script == filename]: for callback_to_remove in [cb for cb in callback_list if cb.script == filename]:
callback_list.remove(callback_to_remove) callback_list.remove(callback_to_remove)
for ordered_callbacks_list in ordered_callbacks_map.values():
for callback_to_remove in [cb for cb in ordered_callbacks_list if cb.script == filename]:
ordered_callbacks_list.remove(callback_to_remove)
def remove_callbacks_for_function(callback_func): def remove_callbacks_for_function(callback_func):
for callback_list in callback_map.values(): for callback_list in callback_map.values():
for callback_to_remove in [cb for cb in callback_list if cb.callback == callback_func]: for callback_to_remove in [cb for cb in callback_list if cb.callback == callback_func]:
callback_list.remove(callback_to_remove) callback_list.remove(callback_to_remove)
for ordered_callback_list in ordered_callbacks_map.values():
for callback_to_remove in [cb for cb in ordered_callback_list if cb.callback == callback_func]:
ordered_callback_list.remove(callback_to_remove)
def on_app_started(callback): def on_app_started(callback, *, name=None):
"""register a function to be called when the webui started, the gradio `Block` component and """register a function to be called when the webui started, the gradio `Block` component and
fastapi `FastAPI` object are passed as the arguments""" fastapi `FastAPI` object are passed as the arguments"""
add_callback(callback_map['callbacks_app_started'], callback) add_callback(callback_map['callbacks_app_started'], callback, name=name, category='app_started')
def on_before_reload(callback): def on_before_reload(callback, *, name=None):
"""register a function to be called just before the server reloads.""" """register a function to be called just before the server reloads."""
add_callback(callback_map['callbacks_on_reload'], callback) add_callback(callback_map['callbacks_on_reload'], callback, name=name, category='on_reload')
def on_model_loaded(callback): def on_model_loaded(callback, *, name=None):
"""register a function to be called when the stable diffusion model is created; the model is """register a function to be called when the stable diffusion model is created; the model is
passed as an argument; this function is also called when the script is reloaded. """ passed as an argument; this function is also called when the script is reloaded. """
add_callback(callback_map['callbacks_model_loaded'], callback) add_callback(callback_map['callbacks_model_loaded'], callback, name=name, category='model_loaded')
def on_ui_tabs(callback): def on_ui_tabs(callback, *, name=None):
"""register a function to be called when the UI is creating new tabs. """register a function to be called when the UI is creating new tabs.
The function must either return a None, which means no new tabs to be added, or a list, where The function must either return a None, which means no new tabs to be added, or a list, where
each element is a tuple: each element is a tuple:
@ -378,71 +480,71 @@ def on_ui_tabs(callback):
title is tab text displayed to user in the UI title is tab text displayed to user in the UI
elem_id is HTML id for the tab elem_id is HTML id for the tab
""" """
add_callback(callback_map['callbacks_ui_tabs'], callback) add_callback(callback_map['callbacks_ui_tabs'], callback, name=name, category='ui_tabs')
def on_ui_train_tabs(callback): def on_ui_train_tabs(callback, *, name=None):
"""register a function to be called when the UI is creating new tabs for the train tab. """register a function to be called when the UI is creating new tabs for the train tab.
Create your new tabs with gr.Tab. Create your new tabs with gr.Tab.
""" """
add_callback(callback_map['callbacks_ui_train_tabs'], callback) add_callback(callback_map['callbacks_ui_train_tabs'], callback, name=name, category='ui_train_tabs')
def on_ui_settings(callback): def on_ui_settings(callback, *, name=None):
"""register a function to be called before UI settings are populated; add your settings """register a function to be called before UI settings are populated; add your settings
by using shared.opts.add_option(shared.OptionInfo(...)) """ by using shared.opts.add_option(shared.OptionInfo(...)) """
add_callback(callback_map['callbacks_ui_settings'], callback) add_callback(callback_map['callbacks_ui_settings'], callback, name=name, category='ui_settings')
def on_before_image_saved(callback): def on_before_image_saved(callback, *, name=None):
"""register a function to be called before an image is saved to a file. """register a function to be called before an image is saved to a file.
The callback is called with one argument: The callback is called with one argument:
- params: ImageSaveParams - parameters the image is to be saved with. You can change fields in this object. - params: ImageSaveParams - parameters the image is to be saved with. You can change fields in this object.
""" """
add_callback(callback_map['callbacks_before_image_saved'], callback) add_callback(callback_map['callbacks_before_image_saved'], callback, name=name, category='before_image_saved')
def on_image_saved(callback): def on_image_saved(callback, *, name=None):
"""register a function to be called after an image is saved to a file. """register a function to be called after an image is saved to a file.
The callback is called with one argument: The callback is called with one argument:
- params: ImageSaveParams - parameters the image was saved with. Changing fields in this object does nothing. - params: ImageSaveParams - parameters the image was saved with. Changing fields in this object does nothing.
""" """
add_callback(callback_map['callbacks_image_saved'], callback) add_callback(callback_map['callbacks_image_saved'], callback, name=name, category='image_saved')
def on_extra_noise(callback): def on_extra_noise(callback, *, name=None):
"""register a function to be called before adding extra noise in img2img or hires fix; """register a function to be called before adding extra noise in img2img or hires fix;
The callback is called with one argument: The callback is called with one argument:
- params: ExtraNoiseParams - contains noise determined by seed and latent representation of image - params: ExtraNoiseParams - contains noise determined by seed and latent representation of image
""" """
add_callback(callback_map['callbacks_extra_noise'], callback) add_callback(callback_map['callbacks_extra_noise'], callback, name=name, category='extra_noise')
def on_cfg_denoiser(callback): def on_cfg_denoiser(callback, *, name=None):
"""register a function to be called in the kdiffussion cfg_denoiser method after building the inner model inputs. """register a function to be called in the kdiffussion cfg_denoiser method after building the inner model inputs.
The callback is called with one argument: The callback is called with one argument:
- params: CFGDenoiserParams - parameters to be passed to the inner model and sampling state details. - params: CFGDenoiserParams - parameters to be passed to the inner model and sampling state details.
""" """
add_callback(callback_map['callbacks_cfg_denoiser'], callback) add_callback(callback_map['callbacks_cfg_denoiser'], callback, name=name, category='cfg_denoiser')
def on_cfg_denoised(callback): def on_cfg_denoised(callback, *, name=None):
"""register a function to be called in the kdiffussion cfg_denoiser method after building the inner model inputs. """register a function to be called in the kdiffussion cfg_denoiser method after building the inner model inputs.
The callback is called with one argument: The callback is called with one argument:
- params: CFGDenoisedParams - parameters to be passed to the inner model and sampling state details. - params: CFGDenoisedParams - parameters to be passed to the inner model and sampling state details.
""" """
add_callback(callback_map['callbacks_cfg_denoised'], callback) add_callback(callback_map['callbacks_cfg_denoised'], callback, name=name, category='cfg_denoised')
def on_cfg_after_cfg(callback): def on_cfg_after_cfg(callback, *, name=None):
"""register a function to be called in the kdiffussion cfg_denoiser method after cfg calculations are completed. """register a function to be called in the kdiffussion cfg_denoiser method after cfg calculations are completed.
The callback is called with one argument: The callback is called with one argument:
- params: AfterCFGCallbackParams - parameters to be passed to the script for post-processing after cfg calculation. - params: AfterCFGCallbackParams - parameters to be passed to the script for post-processing after cfg calculation.
""" """
add_callback(callback_map['callbacks_cfg_after_cfg'], callback) add_callback(callback_map['callbacks_cfg_after_cfg'], callback, name=name, category='cfg_after_cfg')
def on_before_component(callback): def on_before_component(callback, *, name=None):
"""register a function to be called before a component is created. """register a function to be called before a component is created.
The callback is called with arguments: The callback is called with arguments:
- component - gradio component that is about to be created. - component - gradio component that is about to be created.
@ -451,61 +553,61 @@ def on_before_component(callback):
Use elem_id/label fields of kwargs to figure out which component it is. Use elem_id/label fields of kwargs to figure out which component it is.
This can be useful to inject your own components somewhere in the middle of vanilla UI. This can be useful to inject your own components somewhere in the middle of vanilla UI.
""" """
add_callback(callback_map['callbacks_before_component'], callback) add_callback(callback_map['callbacks_before_component'], callback, name=name, category='before_component')
def on_after_component(callback): def on_after_component(callback, *, name=None):
"""register a function to be called after a component is created. See on_before_component for more.""" """register a function to be called after a component is created. See on_before_component for more."""
add_callback(callback_map['callbacks_after_component'], callback) add_callback(callback_map['callbacks_after_component'], callback, name=name, category='after_component')
def on_image_grid(callback): def on_image_grid(callback, *, name=None):
"""register a function to be called before making an image grid. """register a function to be called before making an image grid.
The callback is called with one argument: The callback is called with one argument:
- params: ImageGridLoopParams - parameters to be used for grid creation. Can be modified. - params: ImageGridLoopParams - parameters to be used for grid creation. Can be modified.
""" """
add_callback(callback_map['callbacks_image_grid'], callback) add_callback(callback_map['callbacks_image_grid'], callback, name=name, category='image_grid')
def on_infotext_pasted(callback): def on_infotext_pasted(callback, *, name=None):
"""register a function to be called before applying an infotext. """register a function to be called before applying an infotext.
The callback is called with two arguments: The callback is called with two arguments:
- infotext: str - raw infotext. - infotext: str - raw infotext.
- result: dict[str, any] - parsed infotext parameters. - result: dict[str, any] - parsed infotext parameters.
""" """
add_callback(callback_map['callbacks_infotext_pasted'], callback) add_callback(callback_map['callbacks_infotext_pasted'], callback, name=name, category='infotext_pasted')
def on_script_unloaded(callback): def on_script_unloaded(callback, *, name=None):
"""register a function to be called before the script is unloaded. Any hooks/hijacks/monkeying about that """register a function to be called before the script is unloaded. Any hooks/hijacks/monkeying about that
the script did should be reverted here""" the script did should be reverted here"""
add_callback(callback_map['callbacks_script_unloaded'], callback) add_callback(callback_map['callbacks_script_unloaded'], callback, name=name, category='script_unloaded')
def on_before_ui(callback): def on_before_ui(callback, *, name=None):
"""register a function to be called before the UI is created.""" """register a function to be called before the UI is created."""
add_callback(callback_map['callbacks_before_ui'], callback) add_callback(callback_map['callbacks_before_ui'], callback, name=name, category='before_ui')
def on_list_optimizers(callback): def on_list_optimizers(callback, *, name=None):
"""register a function to be called when UI is making a list of cross attention optimization options. """register a function to be called when UI is making a list of cross attention optimization options.
The function will be called with one argument, a list, and shall add objects of type modules.sd_hijack_optimizations.SdOptimization The function will be called with one argument, a list, and shall add objects of type modules.sd_hijack_optimizations.SdOptimization
to it.""" to it."""
add_callback(callback_map['callbacks_list_optimizers'], callback) add_callback(callback_map['callbacks_list_optimizers'], callback, name=name, category='list_optimizers')
def on_list_unets(callback): def on_list_unets(callback, *, name=None):
"""register a function to be called when UI is making a list of alternative options for unet. """register a function to be called when UI is making a list of alternative options for unet.
The function will be called with one argument, a list, and shall add objects of type modules.sd_unet.SdUnetOption to it.""" The function will be called with one argument, a list, and shall add objects of type modules.sd_unet.SdUnetOption to it."""
add_callback(callback_map['callbacks_list_unets'], callback) add_callback(callback_map['callbacks_list_unets'], callback, name=name, category='list_unets')
def on_before_token_counter(callback): def on_before_token_counter(callback, *, name=None):
"""register a function to be called when UI is counting tokens for a prompt. """register a function to be called when UI is counting tokens for a prompt.
The function will be called with one argument of type BeforeTokenCounterParams, and should modify its fields if necessary.""" The function will be called with one argument of type BeforeTokenCounterParams, and should modify its fields if necessary."""
add_callback(callback_map['callbacks_before_token_counter'], callback) add_callback(callback_map['callbacks_before_token_counter'], callback, name=name, category='before_token_counter')

View File

@ -4,11 +4,15 @@ import importlib.util
from modules import errors from modules import errors
loaded_scripts = {}
def load_module(path): def load_module(path):
module_spec = importlib.util.spec_from_file_location(os.path.basename(path), path) module_spec = importlib.util.spec_from_file_location(os.path.basename(path), path)
module = importlib.util.module_from_spec(module_spec) module = importlib.util.module_from_spec(module_spec)
module_spec.loader.exec_module(module) module_spec.loader.exec_module(module)
loaded_scripts[path] = module
return module return module

View File

@ -7,7 +7,9 @@ from dataclasses import dataclass
import gradio as gr import gradio as gr
from modules import shared, paths, script_callbacks, extensions, script_loading, scripts_postprocessing, errors, timer from modules import shared, paths, script_callbacks, extensions, script_loading, scripts_postprocessing, errors, timer, util
topological_sort = util.topological_sort
AlwaysVisible = object() AlwaysVisible = object()
@ -92,7 +94,7 @@ class Script:
"""If true, the script setup will only be run in Gradio UI, not in API""" """If true, the script setup will only be run in Gradio UI, not in API"""
controls = None controls = None
"""A list of controls retured by the ui().""" """A list of controls returned by the ui()."""
def title(self): def title(self):
"""this function should return the title of the script. This is what will be displayed in the dropdown menu.""" """this function should return the title of the script. This is what will be displayed in the dropdown menu."""
@ -109,7 +111,7 @@ class Script:
def show(self, is_img2img): def show(self, is_img2img):
""" """
is_img2img is True if this function is called for the img2img interface, and Fasle otherwise is_img2img is True if this function is called for the img2img interface, and False otherwise
This function should return: This function should return:
- False if the script should not be shown in UI at all - False if the script should not be shown in UI at all
@ -138,7 +140,6 @@ class Script:
""" """
pass pass
def before_process(self, p, *args): def before_process(self, p, *args):
""" """
This function is called very early during processing begins for AlwaysVisible scripts. This function is called very early during processing begins for AlwaysVisible scripts.
@ -186,6 +187,13 @@ class Script:
""" """
pass pass
def process_before_every_sampling(self, p, *args, **kwargs):
"""
Similar to process(), called before every sampling.
If you use high-res fix, this will be called two times.
"""
pass
def process_batch(self, p, *args, **kwargs): def process_batch(self, p, *args, **kwargs):
""" """
Same as process(), but called for every batch. Same as process(), but called for every batch.
@ -351,6 +359,9 @@ class ScriptBuiltinUI(Script):
return f'{tabname}{item_id}' return f'{tabname}{item_id}'
def show(self, is_img2img):
return AlwaysVisible
current_basedir = paths.script_path current_basedir = paths.script_path
@ -369,29 +380,6 @@ scripts_data = []
postprocessing_scripts_data = [] postprocessing_scripts_data = []
ScriptClassData = namedtuple("ScriptClassData", ["script_class", "path", "basedir", "module"]) ScriptClassData = namedtuple("ScriptClassData", ["script_class", "path", "basedir", "module"])
def topological_sort(dependencies):
"""Accepts a dictionary mapping name to its dependencies, returns a list of names ordered according to dependencies.
Ignores errors relating to missing dependeencies or circular dependencies
"""
visited = {}
result = []
def inner(name):
visited[name] = True
for dep in dependencies.get(name, []):
if dep in dependencies and dep not in visited:
inner(dep)
result.append(name)
for depname in dependencies:
if depname not in visited:
inner(depname)
return result
@dataclass @dataclass
class ScriptWithDependencies: class ScriptWithDependencies:
@ -562,6 +550,25 @@ class ScriptRunner:
self.paste_field_names = [] self.paste_field_names = []
self.inputs = [None] self.inputs = [None]
self.callback_map = {}
self.callback_names = [
'before_process',
'process',
'before_process_batch',
'after_extra_networks_activate',
'process_batch',
'postprocess',
'postprocess_batch',
'postprocess_batch_list',
'post_sample',
'on_mask_blend',
'postprocess_image',
'postprocess_maskoverlay',
'postprocess_image_after_composite',
'before_component',
'after_component',
]
self.on_before_component_elem_id = {} self.on_before_component_elem_id = {}
"""dict of callbacks to be called before an element is created; key=elem_id, value=list of callbacks""" """dict of callbacks to be called before an element is created; key=elem_id, value=list of callbacks"""
@ -600,6 +607,8 @@ class ScriptRunner:
self.scripts.append(script) self.scripts.append(script)
self.selectable_scripts.append(script) self.selectable_scripts.append(script)
self.callback_map.clear()
self.apply_on_before_component_callbacks() self.apply_on_before_component_callbacks()
def apply_on_before_component_callbacks(self): def apply_on_before_component_callbacks(self):
@ -737,12 +746,17 @@ class ScriptRunner:
def onload_script_visibility(params): def onload_script_visibility(params):
title = params.get('Script', None) title = params.get('Script', None)
if title: if title:
title_index = self.titles.index(title) try:
visibility = title_index == self.script_load_ctr title_index = self.titles.index(title)
self.script_load_ctr = (self.script_load_ctr + 1) % len(self.titles) visibility = title_index == self.script_load_ctr
return gr.update(visible=visibility) self.script_load_ctr = (self.script_load_ctr + 1) % len(self.titles)
else: return gr.update(visible=visibility)
return gr.update(visible=False) except ValueError:
params['Script'] = None
massage = f'Cannot find Script: "{title}"'
print(massage)
gr.Warning(massage)
return gr.update(visible=False)
self.infotext_fields.append((dropdown, lambda x: gr.update(value=x.get('Script', 'None')))) self.infotext_fields.append((dropdown, lambda x: gr.update(value=x.get('Script', 'None'))))
self.infotext_fields.extend([(script.group, onload_script_visibility) for script in self.selectable_scripts]) self.infotext_fields.extend([(script.group, onload_script_visibility) for script in self.selectable_scripts])
@ -769,8 +783,42 @@ class ScriptRunner:
return processed return processed
def list_scripts_for_method(self, method_name):
if method_name in ('before_component', 'after_component'):
return self.scripts
else:
return self.alwayson_scripts
def create_ordered_callbacks_list(self, method_name, *, enable_user_sort=True):
script_list = self.list_scripts_for_method(method_name)
category = f'script_{method_name}'
callbacks = []
for script in script_list:
if getattr(script.__class__, method_name, None) == getattr(Script, method_name, None):
continue
script_callbacks.add_callback(callbacks, script, category=category, name=script.__class__.__name__, filename=script.filename)
return script_callbacks.sort_callbacks(category, callbacks, enable_user_sort=enable_user_sort)
def ordered_callbacks(self, method_name, *, enable_user_sort=True):
script_list = self.list_scripts_for_method(method_name)
category = f'script_{method_name}'
scrpts_len, callbacks = self.callback_map.get(category, (-1, None))
if callbacks is None or scrpts_len != len(script_list):
callbacks = self.create_ordered_callbacks_list(method_name, enable_user_sort=enable_user_sort)
self.callback_map[category] = len(script_list), callbacks
return callbacks
def ordered_scripts(self, method_name):
return [x.callback for x in self.ordered_callbacks(method_name)]
def before_process(self, p): def before_process(self, p):
for script in self.alwayson_scripts: for script in self.ordered_scripts('before_process'):
try: try:
script_args = p.script_args[script.args_from:script.args_to] script_args = p.script_args[script.args_from:script.args_to]
script.before_process(p, *script_args) script.before_process(p, *script_args)
@ -778,15 +826,23 @@ class ScriptRunner:
errors.report(f"Error running before_process: {script.filename}", exc_info=True) errors.report(f"Error running before_process: {script.filename}", exc_info=True)
def process(self, p): def process(self, p):
for script in self.alwayson_scripts: for script in self.ordered_scripts('process'):
try: try:
script_args = p.script_args[script.args_from:script.args_to] script_args = p.script_args[script.args_from:script.args_to]
script.process(p, *script_args) script.process(p, *script_args)
except Exception: except Exception:
errors.report(f"Error running process: {script.filename}", exc_info=True) errors.report(f"Error running process: {script.filename}", exc_info=True)
def process_before_every_sampling(self, p, **kwargs):
for script in self.ordered_scripts('process_before_every_sampling'):
try:
script_args = p.script_args[script.args_from:script.args_to]
script.process_before_every_sampling(p, *script_args, **kwargs)
except Exception:
errors.report(f"Error running process_before_every_sampling: {script.filename}", exc_info=True)
def before_process_batch(self, p, **kwargs): def before_process_batch(self, p, **kwargs):
for script in self.alwayson_scripts: for script in self.ordered_scripts('before_process_batch'):
try: try:
script_args = p.script_args[script.args_from:script.args_to] script_args = p.script_args[script.args_from:script.args_to]
script.before_process_batch(p, *script_args, **kwargs) script.before_process_batch(p, *script_args, **kwargs)
@ -794,7 +850,7 @@ class ScriptRunner:
errors.report(f"Error running before_process_batch: {script.filename}", exc_info=True) errors.report(f"Error running before_process_batch: {script.filename}", exc_info=True)
def after_extra_networks_activate(self, p, **kwargs): def after_extra_networks_activate(self, p, **kwargs):
for script in self.alwayson_scripts: for script in self.ordered_scripts('after_extra_networks_activate'):
try: try:
script_args = p.script_args[script.args_from:script.args_to] script_args = p.script_args[script.args_from:script.args_to]
script.after_extra_networks_activate(p, *script_args, **kwargs) script.after_extra_networks_activate(p, *script_args, **kwargs)
@ -802,7 +858,7 @@ class ScriptRunner:
errors.report(f"Error running after_extra_networks_activate: {script.filename}", exc_info=True) errors.report(f"Error running after_extra_networks_activate: {script.filename}", exc_info=True)
def process_batch(self, p, **kwargs): def process_batch(self, p, **kwargs):
for script in self.alwayson_scripts: for script in self.ordered_scripts('process_batch'):
try: try:
script_args = p.script_args[script.args_from:script.args_to] script_args = p.script_args[script.args_from:script.args_to]
script.process_batch(p, *script_args, **kwargs) script.process_batch(p, *script_args, **kwargs)
@ -810,7 +866,7 @@ class ScriptRunner:
errors.report(f"Error running process_batch: {script.filename}", exc_info=True) errors.report(f"Error running process_batch: {script.filename}", exc_info=True)
def postprocess(self, p, processed): def postprocess(self, p, processed):
for script in self.alwayson_scripts: for script in self.ordered_scripts('postprocess'):
try: try:
script_args = p.script_args[script.args_from:script.args_to] script_args = p.script_args[script.args_from:script.args_to]
script.postprocess(p, processed, *script_args) script.postprocess(p, processed, *script_args)
@ -818,7 +874,7 @@ class ScriptRunner:
errors.report(f"Error running postprocess: {script.filename}", exc_info=True) errors.report(f"Error running postprocess: {script.filename}", exc_info=True)
def postprocess_batch(self, p, images, **kwargs): def postprocess_batch(self, p, images, **kwargs):
for script in self.alwayson_scripts: for script in self.ordered_scripts('postprocess_batch'):
try: try:
script_args = p.script_args[script.args_from:script.args_to] script_args = p.script_args[script.args_from:script.args_to]
script.postprocess_batch(p, *script_args, images=images, **kwargs) script.postprocess_batch(p, *script_args, images=images, **kwargs)
@ -826,7 +882,7 @@ class ScriptRunner:
errors.report(f"Error running postprocess_batch: {script.filename}", exc_info=True) errors.report(f"Error running postprocess_batch: {script.filename}", exc_info=True)
def postprocess_batch_list(self, p, pp: PostprocessBatchListArgs, **kwargs): def postprocess_batch_list(self, p, pp: PostprocessBatchListArgs, **kwargs):
for script in self.alwayson_scripts: for script in self.ordered_scripts('postprocess_batch_list'):
try: try:
script_args = p.script_args[script.args_from:script.args_to] script_args = p.script_args[script.args_from:script.args_to]
script.postprocess_batch_list(p, pp, *script_args, **kwargs) script.postprocess_batch_list(p, pp, *script_args, **kwargs)
@ -834,7 +890,7 @@ class ScriptRunner:
errors.report(f"Error running postprocess_batch_list: {script.filename}", exc_info=True) errors.report(f"Error running postprocess_batch_list: {script.filename}", exc_info=True)
def post_sample(self, p, ps: PostSampleArgs): def post_sample(self, p, ps: PostSampleArgs):
for script in self.alwayson_scripts: for script in self.ordered_scripts('post_sample'):
try: try:
script_args = p.script_args[script.args_from:script.args_to] script_args = p.script_args[script.args_from:script.args_to]
script.post_sample(p, ps, *script_args) script.post_sample(p, ps, *script_args)
@ -842,7 +898,7 @@ class ScriptRunner:
errors.report(f"Error running post_sample: {script.filename}", exc_info=True) errors.report(f"Error running post_sample: {script.filename}", exc_info=True)
def on_mask_blend(self, p, mba: MaskBlendArgs): def on_mask_blend(self, p, mba: MaskBlendArgs):
for script in self.alwayson_scripts: for script in self.ordered_scripts('on_mask_blend'):
try: try:
script_args = p.script_args[script.args_from:script.args_to] script_args = p.script_args[script.args_from:script.args_to]
script.on_mask_blend(p, mba, *script_args) script.on_mask_blend(p, mba, *script_args)
@ -850,7 +906,7 @@ class ScriptRunner:
errors.report(f"Error running post_sample: {script.filename}", exc_info=True) errors.report(f"Error running post_sample: {script.filename}", exc_info=True)
def postprocess_image(self, p, pp: PostprocessImageArgs): def postprocess_image(self, p, pp: PostprocessImageArgs):
for script in self.alwayson_scripts: for script in self.ordered_scripts('postprocess_image'):
try: try:
script_args = p.script_args[script.args_from:script.args_to] script_args = p.script_args[script.args_from:script.args_to]
script.postprocess_image(p, pp, *script_args) script.postprocess_image(p, pp, *script_args)
@ -858,7 +914,7 @@ class ScriptRunner:
errors.report(f"Error running postprocess_image: {script.filename}", exc_info=True) errors.report(f"Error running postprocess_image: {script.filename}", exc_info=True)
def postprocess_maskoverlay(self, p, ppmo: PostProcessMaskOverlayArgs): def postprocess_maskoverlay(self, p, ppmo: PostProcessMaskOverlayArgs):
for script in self.alwayson_scripts: for script in self.ordered_scripts('postprocess_maskoverlay'):
try: try:
script_args = p.script_args[script.args_from:script.args_to] script_args = p.script_args[script.args_from:script.args_to]
script.postprocess_maskoverlay(p, ppmo, *script_args) script.postprocess_maskoverlay(p, ppmo, *script_args)
@ -866,7 +922,7 @@ class ScriptRunner:
errors.report(f"Error running postprocess_image: {script.filename}", exc_info=True) errors.report(f"Error running postprocess_image: {script.filename}", exc_info=True)
def postprocess_image_after_composite(self, p, pp: PostprocessImageArgs): def postprocess_image_after_composite(self, p, pp: PostprocessImageArgs):
for script in self.alwayson_scripts: for script in self.ordered_scripts('postprocess_image_after_composite'):
try: try:
script_args = p.script_args[script.args_from:script.args_to] script_args = p.script_args[script.args_from:script.args_to]
script.postprocess_image_after_composite(p, pp, *script_args) script.postprocess_image_after_composite(p, pp, *script_args)
@ -880,7 +936,7 @@ class ScriptRunner:
except Exception: except Exception:
errors.report(f"Error running on_before_component: {script.filename}", exc_info=True) errors.report(f"Error running on_before_component: {script.filename}", exc_info=True)
for script in self.scripts: for script in self.ordered_scripts('before_component'):
try: try:
script.before_component(component, **kwargs) script.before_component(component, **kwargs)
except Exception: except Exception:
@ -893,7 +949,7 @@ class ScriptRunner:
except Exception: except Exception:
errors.report(f"Error running on_after_component: {script.filename}", exc_info=True) errors.report(f"Error running on_after_component: {script.filename}", exc_info=True)
for script in self.scripts: for script in self.ordered_scripts('after_component'):
try: try:
script.after_component(component, **kwargs) script.after_component(component, **kwargs)
except Exception: except Exception:
@ -921,7 +977,7 @@ class ScriptRunner:
self.scripts[si].args_to = args_to self.scripts[si].args_to = args_to
def before_hr(self, p): def before_hr(self, p):
for script in self.alwayson_scripts: for script in self.ordered_scripts('before_hr'):
try: try:
script_args = p.script_args[script.args_from:script.args_to] script_args = p.script_args[script.args_from:script.args_to]
script.before_hr(p, *script_args) script.before_hr(p, *script_args)
@ -929,7 +985,7 @@ class ScriptRunner:
errors.report(f"Error running before_hr: {script.filename}", exc_info=True) errors.report(f"Error running before_hr: {script.filename}", exc_info=True)
def setup_scrips(self, p, *, is_ui=True): def setup_scrips(self, p, *, is_ui=True):
for script in self.alwayson_scripts: for script in self.ordered_scripts('setup'):
if not is_ui and script.setup_for_ui_only: if not is_ui and script.setup_for_ui_only:
continue continue

View File

@ -143,6 +143,7 @@ class ScriptPostprocessingRunner:
self.initialize_scripts(modules.scripts.postprocessing_scripts_data) self.initialize_scripts(modules.scripts.postprocessing_scripts_data)
scripts_order = shared.opts.postprocessing_operation_order scripts_order = shared.opts.postprocessing_operation_order
scripts_filter_out = set(shared.opts.postprocessing_disable_in_extras)
def script_score(name): def script_score(name):
for i, possible_match in enumerate(scripts_order): for i, possible_match in enumerate(scripts_order):
@ -151,9 +152,10 @@ class ScriptPostprocessingRunner:
return len(self.scripts) return len(self.scripts)
script_scores = {script.name: (script_score(script.name), script.order, script.name, original_index) for original_index, script in enumerate(self.scripts)} filtered_scripts = [script for script in self.scripts if script.name not in scripts_filter_out]
script_scores = {script.name: (script_score(script.name), script.order, script.name, original_index) for original_index, script in enumerate(filtered_scripts)}
return sorted(self.scripts, key=lambda x: script_scores[x.name]) return sorted(filtered_scripts, key=lambda x: script_scores[x.name])
def setup_ui(self): def setup_ui(self):
inputs = [] inputs = []

View File

@ -35,7 +35,7 @@ class EmphasisIgnore(Emphasis):
class EmphasisOriginal(Emphasis): class EmphasisOriginal(Emphasis):
name = "Original" name = "Original"
description = "the orginal emphasis implementation" description = "the original emphasis implementation"
def after_transformers(self): def after_transformers(self):
original_mean = self.z.mean() original_mean = self.z.mean()
@ -48,7 +48,7 @@ class EmphasisOriginal(Emphasis):
class EmphasisOriginalNoNorm(EmphasisOriginal): class EmphasisOriginalNoNorm(EmphasisOriginal):
name = "No norm" name = "No norm"
description = "same as orginal, but without normalization (seems to work better for SDXL)" description = "same as original, but without normalization (seems to work better for SDXL)"
def after_transformers(self): def after_transformers(self):
self.z = self.z * self.multipliers.reshape(self.multipliers.shape + (1,)).expand(self.z.shape) self.z = self.z * self.multipliers.reshape(self.multipliers.shape + (1,)).expand(self.z.shape)

View File

@ -325,7 +325,10 @@ class StableDiffusionModelHijack:
if self.clip is None: if self.clip is None:
return "-", "-" return "-", "-"
_, token_count = self.clip.process_texts([text]) if hasattr(self.clip, 'get_token_count'):
token_count = self.clip.get_token_count(text)
else:
_, token_count = self.clip.process_texts([text])
return token_count, self.clip.get_target_prompt_token_count(token_count) return token_count, self.clip.get_target_prompt_token_count(token_count)
@ -356,13 +359,28 @@ class EmbeddingsWithFixes(torch.nn.Module):
vec = embedding.vec[self.textual_inversion_key] if isinstance(embedding.vec, dict) else embedding.vec vec = embedding.vec[self.textual_inversion_key] if isinstance(embedding.vec, dict) else embedding.vec
emb = devices.cond_cast_unet(vec) emb = devices.cond_cast_unet(vec)
emb_len = min(tensor.shape[0] - offset - 1, emb.shape[0]) emb_len = min(tensor.shape[0] - offset - 1, emb.shape[0])
tensor = torch.cat([tensor[0:offset + 1], emb[0:emb_len], tensor[offset + 1 + emb_len:]]) tensor = torch.cat([tensor[0:offset + 1], emb[0:emb_len], tensor[offset + 1 + emb_len:]]).to(dtype=inputs_embeds.dtype)
vecs.append(tensor) vecs.append(tensor)
return torch.stack(vecs) return torch.stack(vecs)
class TextualInversionEmbeddings(torch.nn.Embedding):
def __init__(self, num_embeddings: int, embedding_dim: int, textual_inversion_key='clip_l', **kwargs):
super().__init__(num_embeddings, embedding_dim, **kwargs)
self.embeddings = model_hijack
self.textual_inversion_key = textual_inversion_key
@property
def wrapped(self):
return super().forward
def forward(self, input_ids):
return EmbeddingsWithFixes.forward(self, input_ids)
def add_circular_option_to_conv_2d(): def add_circular_option_to_conv_2d():
conv2d_constructor = torch.nn.Conv2d.__init__ conv2d_constructor = torch.nn.Conv2d.__init__

View File

@ -23,28 +23,25 @@ class PromptChunk:
PromptChunkFix = namedtuple('PromptChunkFix', ['offset', 'embedding']) PromptChunkFix = namedtuple('PromptChunkFix', ['offset', 'embedding'])
"""An object of this type is a marker showing that textual inversion embedding's vectors have to placed at offset in the prompt """An object of this type is a marker showing that textual inversion embedding's vectors have to placed at offset in the prompt
chunk. Thos objects are found in PromptChunk.fixes and, are placed into FrozenCLIPEmbedderWithCustomWordsBase.hijack.fixes, and finally chunk. Those objects are found in PromptChunk.fixes and, are placed into FrozenCLIPEmbedderWithCustomWordsBase.hijack.fixes, and finally
are applied by sd_hijack.EmbeddingsWithFixes's forward function.""" are applied by sd_hijack.EmbeddingsWithFixes's forward function."""
class FrozenCLIPEmbedderWithCustomWordsBase(torch.nn.Module): class TextConditionalModel(torch.nn.Module):
"""A pytorch module that is a wrapper for FrozenCLIPEmbedder module. it enhances FrozenCLIPEmbedder, making it possible to def __init__(self):
have unlimited prompt length and assign weights to tokens in prompt.
"""
def __init__(self, wrapped, hijack):
super().__init__() super().__init__()
self.wrapped = wrapped self.hijack = sd_hijack.model_hijack
"""Original FrozenCLIPEmbedder module; can also be FrozenOpenCLIPEmbedder or xlmr.BertSeriesModelWithTransformation,
depending on model."""
self.hijack: sd_hijack.StableDiffusionModelHijack = hijack
self.chunk_length = 75 self.chunk_length = 75
self.is_trainable = getattr(wrapped, 'is_trainable', False) self.is_trainable = False
self.input_key = getattr(wrapped, 'input_key', 'txt') self.input_key = 'txt'
self.legacy_ucg_val = None self.return_pooled = False
self.comma_token = None
self.id_start = None
self.id_end = None
self.id_pad = None
def empty_chunk(self): def empty_chunk(self):
"""creates an empty PromptChunk and returns it""" """creates an empty PromptChunk and returns it"""
@ -66,7 +63,7 @@ class FrozenCLIPEmbedderWithCustomWordsBase(torch.nn.Module):
def encode_with_transformers(self, tokens): def encode_with_transformers(self, tokens):
""" """
converts a batch of token ids (in python lists) into a single tensor with numeric respresentation of those tokens; converts a batch of token ids (in python lists) into a single tensor with numeric representation of those tokens;
All python lists with tokens are assumed to have same length, usually 77. All python lists with tokens are assumed to have same length, usually 77.
if input is a list with B elements and each element has T tokens, expected output shape is (B, T, C), where C depends on if input is a list with B elements and each element has T tokens, expected output shape is (B, T, C), where C depends on
model - can be 768 and 1024. model - can be 768 and 1024.
@ -136,7 +133,7 @@ class FrozenCLIPEmbedderWithCustomWordsBase(torch.nn.Module):
if token == self.comma_token: if token == self.comma_token:
last_comma = len(chunk.tokens) last_comma = len(chunk.tokens)
# this is when we are at the end of alloted 75 tokens for the current chunk, and the current token is not a comma. opts.comma_padding_backtrack # this is when we are at the end of allotted 75 tokens for the current chunk, and the current token is not a comma. opts.comma_padding_backtrack
# is a setting that specifies that if there is a comma nearby, the text after the comma should be moved out of this chunk and into the next. # is a setting that specifies that if there is a comma nearby, the text after the comma should be moved out of this chunk and into the next.
elif opts.comma_padding_backtrack != 0 and len(chunk.tokens) == self.chunk_length and last_comma != -1 and len(chunk.tokens) - last_comma <= opts.comma_padding_backtrack: elif opts.comma_padding_backtrack != 0 and len(chunk.tokens) == self.chunk_length and last_comma != -1 and len(chunk.tokens) - last_comma <= opts.comma_padding_backtrack:
break_location = last_comma + 1 break_location = last_comma + 1
@ -206,14 +203,10 @@ class FrozenCLIPEmbedderWithCustomWordsBase(torch.nn.Module):
be a multiple of 77; and C is dimensionality of each token - for SD1 it's 768, for SD2 it's 1024, and for SDXL it's 1280. be a multiple of 77; and C is dimensionality of each token - for SD1 it's 768, for SD2 it's 1024, and for SDXL it's 1280.
An example shape returned by this function can be: (2, 77, 768). An example shape returned by this function can be: (2, 77, 768).
For SDXL, instead of returning one tensor avobe, it returns a tuple with two: the other one with shape (B, 1280) with pooled values. For SDXL, instead of returning one tensor avobe, it returns a tuple with two: the other one with shape (B, 1280) with pooled values.
Webui usually sends just one text at a time through this function - the only time when texts is an array with more than one elemenet Webui usually sends just one text at a time through this function - the only time when texts is an array with more than one element
is when you do prompt editing: "a picture of a [cat:dog:0.4] eating ice cream" is when you do prompt editing: "a picture of a [cat:dog:0.4] eating ice cream"
""" """
if opts.use_old_emphasis_implementation:
import modules.sd_hijack_clip_old
return modules.sd_hijack_clip_old.forward_old(self, texts)
batch_chunks, token_count = self.process_texts(texts) batch_chunks, token_count = self.process_texts(texts)
used_embeddings = {} used_embeddings = {}
@ -230,7 +223,7 @@ class FrozenCLIPEmbedderWithCustomWordsBase(torch.nn.Module):
for fixes in self.hijack.fixes: for fixes in self.hijack.fixes:
for _position, embedding in fixes: for _position, embedding in fixes:
used_embeddings[embedding.name] = embedding used_embeddings[embedding.name] = embedding
devices.torch_npu_set_device()
z = self.process_tokens(tokens, multipliers) z = self.process_tokens(tokens, multipliers)
zs.append(z) zs.append(z)
@ -252,7 +245,7 @@ class FrozenCLIPEmbedderWithCustomWordsBase(torch.nn.Module):
if any(x for x in texts if "(" in x or "[" in x) and opts.emphasis != "Original": if any(x for x in texts if "(" in x or "[" in x) and opts.emphasis != "Original":
self.hijack.extra_generation_params["Emphasis"] = opts.emphasis self.hijack.extra_generation_params["Emphasis"] = opts.emphasis
if getattr(self.wrapped, 'return_pooled', False): if self.return_pooled:
return torch.hstack(zs), zs[0].pooled return torch.hstack(zs), zs[0].pooled
else: else:
return torch.hstack(zs) return torch.hstack(zs)
@ -292,6 +285,34 @@ class FrozenCLIPEmbedderWithCustomWordsBase(torch.nn.Module):
return z return z
class FrozenCLIPEmbedderWithCustomWordsBase(TextConditionalModel):
"""A pytorch module that is a wrapper for FrozenCLIPEmbedder module. it enhances FrozenCLIPEmbedder, making it possible to
have unlimited prompt length and assign weights to tokens in prompt.
"""
def __init__(self, wrapped, hijack):
super().__init__()
self.hijack = hijack
self.wrapped = wrapped
"""Original FrozenCLIPEmbedder module; can also be FrozenOpenCLIPEmbedder or xlmr.BertSeriesModelWithTransformation,
depending on model."""
self.is_trainable = getattr(wrapped, 'is_trainable', False)
self.input_key = getattr(wrapped, 'input_key', 'txt')
self.return_pooled = getattr(self.wrapped, 'return_pooled', False)
self.legacy_ucg_val = None # for sgm codebase
def forward(self, texts):
if opts.use_old_emphasis_implementation:
import modules.sd_hijack_clip_old
return modules.sd_hijack_clip_old.forward_old(self, texts)
return super().forward(texts)
class FrozenCLIPEmbedderWithCustomWords(FrozenCLIPEmbedderWithCustomWordsBase): class FrozenCLIPEmbedderWithCustomWords(FrozenCLIPEmbedderWithCustomWordsBase):
def __init__(self, wrapped, hijack): def __init__(self, wrapped, hijack):
super().__init__(wrapped, hijack) super().__init__(wrapped, hijack)
@ -353,7 +374,9 @@ class FrozenCLIPEmbedderForSDXLWithCustomWords(FrozenCLIPEmbedderWithCustomWords
def encode_with_transformers(self, tokens): def encode_with_transformers(self, tokens):
outputs = self.wrapped.transformer(input_ids=tokens, output_hidden_states=self.wrapped.layer == "hidden") outputs = self.wrapped.transformer(input_ids=tokens, output_hidden_states=self.wrapped.layer == "hidden")
if self.wrapped.layer == "last": if opts.sdxl_clip_l_skip is True:
z = outputs.hidden_states[-opts.CLIP_stop_at_last_layers]
elif self.wrapped.layer == "last":
z = outputs.last_hidden_state z = outputs.last_hidden_state
else: else:
z = outputs.hidden_states[self.wrapped.layer_idx] z = outputs.hidden_states[self.wrapped.layer_idx]

View File

@ -486,7 +486,8 @@ def xformers_attention_forward(self, x, context=None, mask=None, **kwargs):
k_in = self.to_k(context_k) k_in = self.to_k(context_k)
v_in = self.to_v(context_v) v_in = self.to_v(context_v)
q, k, v = (rearrange(t, 'b n (h d) -> b n h d', h=h) for t in (q_in, k_in, v_in)) q, k, v = (t.reshape(t.shape[0], t.shape[1], h, -1) for t in (q_in, k_in, v_in))
del q_in, k_in, v_in del q_in, k_in, v_in
dtype = q.dtype dtype = q.dtype
@ -497,7 +498,8 @@ def xformers_attention_forward(self, x, context=None, mask=None, **kwargs):
out = out.to(dtype) out = out.to(dtype)
out = rearrange(out, 'b n h d -> b n (h d)', h=h) b, n, h, d = out.shape
out = out.reshape(b, n, h * d)
return self.to_out(out) return self.to_out(out)

Some files were not shown because too many files have changed in this diff Show More