From db4632f4ba8d8023f536e1d4a69a398b7b1c3d7c Mon Sep 17 00:00:00 2001 From: Alex He Date: Fri, 2 Feb 2024 13:48:42 +0800 Subject: [PATCH 001/496] 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 --- webui.sh | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/webui.sh b/webui.sh index cff43327..91174d5b 100755 --- a/webui.sh +++ b/webui.sh @@ -158,7 +158,7 @@ if ! echo "$gpu_info" | grep -q "NVIDIA"; then if echo "$gpu_info" | grep -q "AMD" && [[ -z "${TORCH_COMMAND}" ]] then - export TORCH_COMMAND="pip install torch==2.0.1+rocm5.4.2 torchvision==0.15.2+rocm5.4.2 --index-url https://download.pytorch.org/whl/rocm5.4.2" + export TORCH_COMMAND="pip install torch torchvision torchaudio --index-url https://download.pytorch.org/whl/rocm5.7" fi fi From 9588721197bc3c61354811eca5aff6f470b0b2f8 Mon Sep 17 00:00:00 2001 From: v0xie <28695009+v0xie@users.noreply.github.com> Date: Wed, 7 Feb 2024 04:49:17 -0800 Subject: [PATCH 002/496] feat: support LyCORIS BOFT --- extensions-builtin/Lora/network_oft.py | 44 ++++++++++++++++++++------ 1 file changed, 35 insertions(+), 9 deletions(-) diff --git a/extensions-builtin/Lora/network_oft.py b/extensions-builtin/Lora/network_oft.py index d1c46a4b..8a37828c 100644 --- a/extensions-builtin/Lora/network_oft.py +++ b/extensions-builtin/Lora/network_oft.py @@ -1,6 +1,6 @@ import torch import network -from lyco_helpers import factorization +from lyco_helpers import factorization, butterfly_factor from einops import rearrange @@ -36,6 +36,12 @@ class NetworkModuleOFT(network.NetworkModule): # self.alpha is unused self.dim = self.oft_blocks.shape[1] # (num_blocks, block_size, block_size) + self.is_boft = False + if "boft" in weights.w.keys(): + self.is_boft = True + self.boft_b = weights.w["boft_b"] + self.boft_m = weights.w["boft_m"] + 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_other_linear = type(self.sd_module) in [torch.nn.MultiheadAttention] # unsupported @@ -68,14 +74,34 @@ class NetworkModuleOFT(network.NetworkModule): R = oft_blocks.to(orig_weight.device) - # This errors out for MultiheadAttention, might need to be handled up-stream - merged_weight = rearrange(orig_weight, '(k n) ... -> k n ...', k=self.num_blocks, n=self.block_size) - merged_weight = torch.einsum( - 'k n m, k n ... -> k m ...', - R, - merged_weight - ) - merged_weight = rearrange(merged_weight, 'k m ... -> (k m) ...') + if not self.is_boft: + # This errors out for MultiheadAttention, might need to be handled up-stream + merged_weight = rearrange(orig_weight, '(k n) ... -> k n ...', k=self.num_blocks, n=self.block_size) + merged_weight = torch.einsum( + 'k n m, k n ... -> k m ...', + R, + merged_weight + ) + merged_weight = rearrange(merged_weight, 'k m ... -> (k m) ...') + else: + scale = 1.0 + m = self.boft_m.to(device=oft_blocks.device, dtype=oft_blocks.dtype) + b = self.boft_b.to(device=oft_blocks.device, dtype=oft_blocks.dtype) + r_b = b // 2 + inp = orig_weight + for i in range(m): + bi = R[i] # b_num, b_size, b_size + if i == 0: + # Apply multiplier/scale and rescale into first weight + bi = bi * scale + (1 - scale) * eye + #if self.rescaled: + # bi = bi * self.rescale + inp = rearrange(inp, "(c g k) ... -> (c k g) ...", g=2, k=2**i * r_b) + inp = rearrange(inp, "(d b) ... -> d b ...", b=b) + inp = torch.einsum("b i j, b j ... -> b i ...", bi, inp) + inp = rearrange(inp, "d b ... -> (d b) ...") + inp = rearrange(inp, "(c k g) ... -> (c g k) ...", g=2, k=2**i * r_b) + merged_weight = inp updown = merged_weight.to(orig_weight.device) - orig_weight.to(merged_weight.dtype) output_shape = orig_weight.shape From a4668a16b6f8e98bc6e1553aa754735f9148770f Mon Sep 17 00:00:00 2001 From: v0xie <28695009+v0xie@users.noreply.github.com> Date: Wed, 7 Feb 2024 04:51:22 -0800 Subject: [PATCH 003/496] fix: calculate butterfly factor --- extensions-builtin/Lora/network_oft.py | 3 +++ 1 file changed, 3 insertions(+) diff --git a/extensions-builtin/Lora/network_oft.py b/extensions-builtin/Lora/network_oft.py index 8a37828c..0f20d701 100644 --- a/extensions-builtin/Lora/network_oft.py +++ b/extensions-builtin/Lora/network_oft.py @@ -57,6 +57,9 @@ class NetworkModuleOFT(network.NetworkModule): self.constraint = self.alpha * self.out_dim self.num_blocks = self.dim self.block_size = self.out_dim // self.dim + elif self.is_boft: + self.constraint = None + self.block_size, self.block_num = butterfly_factor(self.out_dim, self.dim) else: self.constraint = None self.block_size, self.num_blocks = factorization(self.out_dim, self.dim) From 81c16c965e532c6d86a969284c320ff8fcb0451d Mon Sep 17 00:00:00 2001 From: v0xie <28695009+v0xie@users.noreply.github.com> Date: Wed, 7 Feb 2024 04:54:14 -0800 Subject: [PATCH 004/496] fix: add butterfly_factor fn --- extensions-builtin/Lora/lyco_helpers.py | 26 +++++++++++++++++++++++++ 1 file changed, 26 insertions(+) diff --git a/extensions-builtin/Lora/lyco_helpers.py b/extensions-builtin/Lora/lyco_helpers.py index 1679a0ce..3c4f5bad 100644 --- a/extensions-builtin/Lora/lyco_helpers.py +++ b/extensions-builtin/Lora/lyco_helpers.py @@ -66,3 +66,29 @@ def factorization(dimension: int, factor:int=-1) -> tuple[int, int]: n, m = m, n return m, n +# from https://github.com/KohakuBlueleaf/LyCORIS/blob/dev/lycoris/modules/boft.py +def butterfly_factor(dimension: int, factor: int = -1) -> tuple[int, int]: + """ + m = 2k + n = 2**p + m*n = dim + """ + + # Find the first solution and check if it is even doable + m = n = 0 + while m <= factor: + m += 2 + while dimension % m != 0 and m < dimension: + m += 2 + if m > factor: + break + if sum(int(i) for i in f"{dimension//m:b}") == 1: + n = dimension // m + + if n == 0: + raise ValueError( + f"It is impossible to decompose {dimension} with factor {factor} under BOFT constrains." + ) + + #log_butterfly_factorize(dimension, factor, (dimension // n, n)) + return dimension // n, n From 2f1073dc6edf2d1388f6aee4af91cb354099a463 Mon Sep 17 00:00:00 2001 From: v0xie <28695009+v0xie@users.noreply.github.com> Date: Wed, 7 Feb 2024 04:55:11 -0800 Subject: [PATCH 005/496] style: fix lint --- extensions-builtin/Lora/network_oft.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/extensions-builtin/Lora/network_oft.py b/extensions-builtin/Lora/network_oft.py index 0f20d701..dc6db56f 100644 --- a/extensions-builtin/Lora/network_oft.py +++ b/extensions-builtin/Lora/network_oft.py @@ -96,7 +96,7 @@ class NetworkModuleOFT(network.NetworkModule): bi = R[i] # b_num, b_size, b_size if i == 0: # Apply multiplier/scale and rescale into first weight - bi = bi * scale + (1 - scale) * eye + bi = bi * scale + (1 - scale) * eye #if self.rescaled: # bi = bi * self.rescale inp = rearrange(inp, "(c g k) ... -> (c k g) ...", g=2, k=2**i * r_b) From 325eaeb584f8565d49ce73553165088f794d3d12 Mon Sep 17 00:00:00 2001 From: v0xie <28695009+v0xie@users.noreply.github.com> Date: Thu, 8 Feb 2024 11:55:05 -0800 Subject: [PATCH 006/496] fix: get boft params from weight shape --- extensions-builtin/Lora/network_oft.py | 16 +++++++++------- 1 file changed, 9 insertions(+), 7 deletions(-) diff --git a/extensions-builtin/Lora/network_oft.py b/extensions-builtin/Lora/network_oft.py index dc6db56f..fc713265 100644 --- a/extensions-builtin/Lora/network_oft.py +++ b/extensions-builtin/Lora/network_oft.py @@ -1,6 +1,6 @@ import torch import network -from lyco_helpers import factorization, butterfly_factor +from lyco_helpers import factorization from einops import rearrange @@ -37,10 +37,8 @@ class NetworkModuleOFT(network.NetworkModule): self.dim = self.oft_blocks.shape[1] # (num_blocks, block_size, block_size) self.is_boft = False - if "boft" in weights.w.keys(): + if weights.w["oft_diag"].dim() == 4: self.is_boft = True - self.boft_b = weights.w["boft_b"] - self.boft_m = weights.w["boft_m"] 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] @@ -59,7 +57,11 @@ class NetworkModuleOFT(network.NetworkModule): self.block_size = self.out_dim // self.dim elif self.is_boft: self.constraint = None - self.block_size, self.block_num = butterfly_factor(self.out_dim, self.dim) + self.boft_m = weights.w["oft_diag"].shape[0] + self.block_num = weights.w["oft_diag"].shape[1] + self.block_size = weights.w["oft_diag"].shape[2] + self.boft_b = self.block_size + #self.block_size, self.block_num = butterfly_factor(self.out_dim, self.dim) else: self.constraint = None self.block_size, self.num_blocks = factorization(self.out_dim, self.dim) @@ -88,8 +90,8 @@ class NetworkModuleOFT(network.NetworkModule): merged_weight = rearrange(merged_weight, 'k m ... -> (k m) ...') else: scale = 1.0 - m = self.boft_m.to(device=oft_blocks.device, dtype=oft_blocks.dtype) - b = self.boft_b.to(device=oft_blocks.device, dtype=oft_blocks.dtype) + m = self.boft_m + b = self.boft_b r_b = b // 2 inp = orig_weight for i in range(m): From 613b0d9548a859408433bff7a6dca7fd0f2eae7e Mon Sep 17 00:00:00 2001 From: v0xie <28695009+v0xie@users.noreply.github.com> Date: Thu, 8 Feb 2024 21:58:59 -0800 Subject: [PATCH 007/496] doc: add boft comment --- extensions-builtin/Lora/network_oft.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/extensions-builtin/Lora/network_oft.py b/extensions-builtin/Lora/network_oft.py index fc713265..d7b31702 100644 --- a/extensions-builtin/Lora/network_oft.py +++ b/extensions-builtin/Lora/network_oft.py @@ -29,13 +29,14 @@ class NetworkModuleOFT(network.NetworkModule): self.oft_blocks = weights.w["oft_blocks"] # (num_blocks, block_size, block_size) self.alpha = weights.w["alpha"] # alpha is constraint self.dim = self.oft_blocks.shape[0] # lora dim - # LyCORIS + # LyCORIS OFT elif "oft_diag" in weights.w.keys(): self.is_kohya = False self.oft_blocks = weights.w["oft_diag"] # self.alpha is unused self.dim = self.oft_blocks.shape[1] # (num_blocks, block_size, block_size) + # LyCORIS BOFT self.is_boft = False if weights.w["oft_diag"].dim() == 4: self.is_boft = True @@ -89,6 +90,7 @@ class NetworkModuleOFT(network.NetworkModule): ) merged_weight = rearrange(merged_weight, 'k m ... -> (k m) ...') else: + # TODO: determine correct value for scale scale = 1.0 m = self.boft_m b = self.boft_b @@ -99,8 +101,6 @@ class NetworkModuleOFT(network.NetworkModule): if i == 0: # Apply multiplier/scale and rescale into first weight bi = bi * scale + (1 - scale) * eye - #if self.rescaled: - # bi = bi * self.rescale inp = rearrange(inp, "(c g k) ... -> (c k g) ...", g=2, k=2**i * r_b) inp = rearrange(inp, "(d b) ... -> d b ...", b=b) inp = torch.einsum("b i j, b j ... -> b i ...", bi, inp) From eb6f2df826087fdc62f6680364a0e16f666eef64 Mon Sep 17 00:00:00 2001 From: v0xie <28695009+v0xie@users.noreply.github.com> Date: Thu, 8 Feb 2024 22:00:15 -0800 Subject: [PATCH 008/496] Revert "fix: add butterfly_factor fn" This reverts commit 81c16c965e532c6d86a969284c320ff8fcb0451d. --- extensions-builtin/Lora/lyco_helpers.py | 26 ------------------------- 1 file changed, 26 deletions(-) diff --git a/extensions-builtin/Lora/lyco_helpers.py b/extensions-builtin/Lora/lyco_helpers.py index 3c4f5bad..1679a0ce 100644 --- a/extensions-builtin/Lora/lyco_helpers.py +++ b/extensions-builtin/Lora/lyco_helpers.py @@ -66,29 +66,3 @@ def factorization(dimension: int, factor:int=-1) -> tuple[int, int]: n, m = m, n return m, n -# from https://github.com/KohakuBlueleaf/LyCORIS/blob/dev/lycoris/modules/boft.py -def butterfly_factor(dimension: int, factor: int = -1) -> tuple[int, int]: - """ - m = 2k - n = 2**p - m*n = dim - """ - - # Find the first solution and check if it is even doable - m = n = 0 - while m <= factor: - m += 2 - while dimension % m != 0 and m < dimension: - m += 2 - if m > factor: - break - if sum(int(i) for i in f"{dimension//m:b}") == 1: - n = dimension // m - - if n == 0: - raise ValueError( - f"It is impossible to decompose {dimension} with factor {factor} under BOFT constrains." - ) - - #log_butterfly_factorize(dimension, factor, (dimension // n, n)) - return dimension // n, n From 90441294db16383bce6f341e8a1f67fe422172d4 Mon Sep 17 00:00:00 2001 From: Kohaku-Blueleaf <59680068+KohakuBlueleaf@users.noreply.github.com> Date: Mon, 12 Feb 2024 14:25:09 +0800 Subject: [PATCH 009/496] 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. --- extensions-builtin/Lora/network_oft.py | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/extensions-builtin/Lora/network_oft.py b/extensions-builtin/Lora/network_oft.py index d7b31702..ed221d8f 100644 --- a/extensions-builtin/Lora/network_oft.py +++ b/extensions-builtin/Lora/network_oft.py @@ -40,6 +40,7 @@ class NetworkModuleOFT(network.NetworkModule): self.is_boft = False if weights.w["oft_diag"].dim() == 4: self.is_boft = True + self.rescale = weight.w.get('rescale', None) 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] @@ -108,6 +109,10 @@ class NetworkModuleOFT(network.NetworkModule): inp = rearrange(inp, "(c k g) ... -> (c g k) ...", g=2, k=2**i * r_b) merged_weight = inp + # Rescale mechanism + if self.rescale is not None: + merged_weight = self.rescale.to(merged_weight) * merged_weight + updown = merged_weight.to(orig_weight.device) - orig_weight.to(merged_weight.dtype) output_shape = orig_weight.shape return self.finalize_updown(updown, orig_weight, output_shape) From 4573195894fffeae08a94c015a94772c1a54a58d Mon Sep 17 00:00:00 2001 From: AUTOMATIC1111 <16777216c@gmail.com> Date: Sat, 17 Feb 2024 11:40:53 +0300 Subject: [PATCH 010/496] prevent escape button causing an interrupt when no generation has been made yet --- script.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/script.js b/script.js index 25cf0973..f069b1ef 100644 --- a/script.js +++ b/script.js @@ -167,7 +167,7 @@ document.addEventListener('keydown', function(e) { const lightboxModal = document.querySelector('#lightboxModal'); if (!globalPopup || globalPopup.style.display === 'none') { if (document.activeElement === lightboxModal) return; - if (interruptButton.style.display !== 'none') { + if (interruptButton.style.display === 'block') { interruptButton.click(); e.preventDefault(); } From 4ff1fabc86db927c45642704fda3472d399f3e19 Mon Sep 17 00:00:00 2001 From: AUTOMATIC1111 <16777216c@gmail.com> Date: Sat, 17 Feb 2024 13:21:08 +0300 Subject: [PATCH 011/496] Update comment for Pad prompt/negative prompt v0 to add a warning about truncation, make it override the v1 implementation --- modules/sd_samplers_cfg_denoiser.py | 6 +++--- modules/shared_options.py | 2 +- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/modules/sd_samplers_cfg_denoiser.py b/modules/sd_samplers_cfg_denoiser.py index 941dff4b..a73d3b03 100644 --- a/modules/sd_samplers_cfg_denoiser.py +++ b/modules/sd_samplers_cfg_denoiser.py @@ -220,10 +220,10 @@ class CFGDenoiser(torch.nn.Module): self.padded_cond_uncond = False self.padded_cond_uncond_v0 = False - if shared.opts.pad_cond_uncond and tensor.shape[1] != uncond.shape[1]: - tensor, uncond = self.pad_cond_uncond(tensor, uncond) - elif shared.opts.pad_cond_uncond_v0 and tensor.shape[1] != uncond.shape[1]: + if shared.opts.pad_cond_uncond_v0 and tensor.shape[1] != uncond.shape[1]: tensor, uncond = self.pad_cond_uncond_v0(tensor, uncond) + elif shared.opts.pad_cond_uncond and tensor.shape[1] != uncond.shape[1]: + tensor, uncond = self.pad_cond_uncond(tensor, uncond) if tensor.shape[1] == uncond.shape[1] or skip_uncond: if is_edit_model: diff --git a/modules/shared_options.py b/modules/shared_options.py index e1d11c8e..25b47aa1 100644 --- a/modules/shared_options.py +++ b/modules/shared_options.py @@ -211,7 +211,7 @@ options_templates.update(options_section(('optimizations', "Optimizations", "sd" "token_merging_ratio_img2img": OptionInfo(0.0, "Token merging ratio for img2img", gr.Slider, {"minimum": 0.0, "maximum": 0.9, "step": 0.1}).info("only applies if non-zero and overrides above"), "token_merging_ratio_hr": OptionInfo(0.0, "Token merging ratio for high-res pass", gr.Slider, {"minimum": 0.0, "maximum": 0.9, "step": 0.1}, infotext='Token merging ratio hr').info("only applies if non-zero and overrides above"), "pad_cond_uncond": OptionInfo(False, "Pad prompt/negative prompt", infotext='Pad conds').info("improves performance when prompt and negative prompt have different lengths; changes seeds"), - "pad_cond_uncond_v0": OptionInfo(False, "Pad prompt/negative prompt (v0)", infotext='Pad conds v0').info("alternative implementation for the above; used prior to 1.6.0 for DDIM sampler; ignored if the above is set; changes seeds"), + "pad_cond_uncond_v0": OptionInfo(False, "Pad prompt/negative prompt (v0)", infotext='Pad conds v0').info("alternative implementation for the above; used prior to 1.6.0 for DDIM sampler; overrides the above if set; WARNING: truncates negative prompt if it's too long; changes seeds"), "persistent_cond_cache": OptionInfo(True, "Persistent cond cache").info("do not recalculate conds from prompts if prompts have not changed since previous calculation"), "batch_cond_uncond": OptionInfo(True, "Batch cond/uncond").info("do both conditional and unconditional denoising in one batch; uses a bit more VRAM during sampling, but improves speed; previously this was controlled by --always-batch-cond-uncond comandline argument"), "fp8_storage": OptionInfo("Disable", "FP8 weight", gr.Radio, {"choices": ["Disable", "Enable for SDXL", "Enable"]}).info("Use FP8 to store Linear/Conv layers' weight. Require pytorch>=2.1.0."), From a18e54ecd756a4101e16e42fc313df259542e07b Mon Sep 17 00:00:00 2001 From: w-e-w <40751091+w-e-w@users.noreply.github.com> Date: Sun, 18 Feb 2024 00:38:05 +0900 Subject: [PATCH 012/496] option "open image button" open the actual dir --- modules/shared_options.py | 2 ++ modules/ui_common.py | 54 +++++++++++++++++++++++++++------------ modules/ui_tempdir.py | 15 +++++++++++ 3 files changed, 54 insertions(+), 17 deletions(-) diff --git a/modules/shared_options.py b/modules/shared_options.py index 25b47aa1..7571a7d1 100644 --- a/modules/shared_options.py +++ b/modules/shared_options.py @@ -284,6 +284,8 @@ options_templates.update(options_section(('ui_gallery', "Gallery", "ui"), { "sd_webui_modal_lightbox_icon_opacity": OptionInfo(1, "Full page image viewer: control icon unfocused opacity", gr.Slider, {"minimum": 0.0, "maximum": 1, "step": 0.01}, onchange=shared.reload_gradio_theme).info('for mouse only').needs_reload_ui(), "sd_webui_modal_lightbox_toolbar_opacity": OptionInfo(0.9, "Full page image viewer: tool bar opacity", gr.Slider, {"minimum": 0.0, "maximum": 1, "step": 0.01}, onchange=shared.reload_gradio_theme).info('for mouse only').needs_reload_ui(), "gallery_height": OptionInfo("", "Gallery height", gr.Textbox).info("can be any valid CSS value, for example 768px or 20em").needs_reload_ui(), + "button_open_image_actual_dir": OptionInfo(True, '"Open images output directory" button opens the actual directory of the image rather than the output root folder'), + "button_open_image_actual_dir_temp": OptionInfo(False, '"Open images output directory" button opens the actual directory even for temp images'), })) options_templates.update(options_section(('ui_alternatives', "UI alternatives", "ui"), { diff --git a/modules/ui_common.py b/modules/ui_common.py index 29fe7d0e..78481c6f 100644 --- a/modules/ui_common.py +++ b/modules/ui_common.py @@ -9,7 +9,7 @@ import sys import gradio as gr import subprocess as sp -from modules import call_queue, shared +from modules import call_queue, shared, ui_tempdir from modules.infotext_utils import image_from_url_text import modules.images from modules.ui_components import ToolButton @@ -164,29 +164,45 @@ class OutputPanel: def create_output_panel(tabname, outdir, toprow=None): res = OutputPanel() - def open_folder(f): + def open_folder(f, images=None, index=None): + if shared.cmd_opts.hide_ui_dir_config: + return + + try: + if shared.opts.button_open_image_actual_dir and 0 <= index < len(images): + image = images[index] + image_path = image["name"].rsplit('?', 1)[0] + image_dir = os.path.split(image_path)[0] + if shared.opts.button_open_image_actual_dir_temp or not ui_tempdir.is_gradio_temp_path(image_dir): + f = image_dir + except Exception: + pass + if not os.path.exists(f): - print(f'Folder "{f}" does not exist. After you create an image, the folder will be created.') + msg = f'Folder "{f}" does not exist. After you create an image, the folder will be created.' + print(msg) + gr.Info(msg) return elif not os.path.isdir(f): - print(f""" + msg = f""" WARNING An open_folder request was made with an argument that is not a folder. This could be an error or a malicious attempt to run code on your computer. Requested path was: {f} -""", file=sys.stderr) +""" + print(msg, file=sys.stderr) + gr.Warning(msg) return - if not shared.cmd_opts.hide_ui_dir_config: - path = os.path.normpath(f) - if platform.system() == "Windows": - os.startfile(path) - elif platform.system() == "Darwin": - sp.Popen(["open", path]) - elif "microsoft-standard-WSL2" in platform.uname().release: - sp.Popen(["wsl-open", path]) - else: - sp.Popen(["xdg-open", path]) + path = os.path.normpath(f) + if platform.system() == "Windows": + os.startfile(path) + elif platform.system() == "Darwin": + sp.Popen(["open", path]) + elif "microsoft-standard-WSL2" in platform.uname().release: + sp.Popen(["wsl-open", path]) + else: + sp.Popen(["xdg-open", path]) with gr.Column(elem_id=f"{tabname}_results"): if toprow: @@ -213,8 +229,12 @@ Requested path was: {f} res.button_upscale = ToolButton('✨', elem_id=f'{tabname}_upscale', tooltip="Create an upscaled version of the current image using hires fix settings.") open_folder_button.click( - fn=lambda: open_folder(shared.opts.outdir_samples or outdir), - inputs=[], + fn=lambda images, index: open_folder(shared.opts.outdir_samples or outdir, images, index), + _js="(y, w) => [y, selected_gallery_index()]", + inputs=[ + res.gallery, + open_folder_button, # placeholder for index + ], outputs=[], ) diff --git a/modules/ui_tempdir.py b/modules/ui_tempdir.py index 91f40ea4..621ed1ec 100644 --- a/modules/ui_tempdir.py +++ b/modules/ui_tempdir.py @@ -81,3 +81,18 @@ def cleanup_tmpdr(): filename = os.path.join(root, name) os.remove(filename) + + +def is_gradio_temp_path(path): + """ + Check if the path is a temp dir used by gradio + """ + path = Path(path) + if shared.opts.temp_dir and path.is_relative_to(shared.opts.temp_dir): + return True + if gradio_temp_dir := os.environ.get("GRADIO_TEMP_DIR"): + if path.is_relative_to(gradio_temp_dir): + return True + if path.is_relative_to(Path(tempfile.gettempdir()) / "gradio"): + return True + return False From 71072f56204c300fa294e15eb7d07592edacda16 Mon Sep 17 00:00:00 2001 From: w-e-w <40751091+w-e-w@users.noreply.github.com> Date: Sun, 18 Feb 2024 02:47:44 +0900 Subject: [PATCH 013/496] re-work open image button settings --- modules/shared_options.py | 3 +-- modules/ui_common.py | 8 +++----- 2 files changed, 4 insertions(+), 7 deletions(-) diff --git a/modules/shared_options.py b/modules/shared_options.py index 7571a7d1..bb3752ba 100644 --- a/modules/shared_options.py +++ b/modules/shared_options.py @@ -284,8 +284,7 @@ options_templates.update(options_section(('ui_gallery', "Gallery", "ui"), { "sd_webui_modal_lightbox_icon_opacity": OptionInfo(1, "Full page image viewer: control icon unfocused opacity", gr.Slider, {"minimum": 0.0, "maximum": 1, "step": 0.01}, onchange=shared.reload_gradio_theme).info('for mouse only').needs_reload_ui(), "sd_webui_modal_lightbox_toolbar_opacity": OptionInfo(0.9, "Full page image viewer: tool bar opacity", gr.Slider, {"minimum": 0.0, "maximum": 1, "step": 0.01}, onchange=shared.reload_gradio_theme).info('for mouse only').needs_reload_ui(), "gallery_height": OptionInfo("", "Gallery height", gr.Textbox).info("can be any valid CSS value, for example 768px or 20em").needs_reload_ui(), - "button_open_image_actual_dir": OptionInfo(True, '"Open images output directory" button opens the actual directory of the image rather than the output root folder'), - "button_open_image_actual_dir_temp": OptionInfo(False, '"Open images output directory" button opens the actual directory even for temp images'), + "open_dir_button_choice": OptionInfo("Subdirectory", "What directory the [📂] button opens", gr.Radio, {"choices": ["Output Root", "Subdirectory", "Subdirectory (even temp dir)"]}), })) options_templates.update(options_section(('ui_alternatives', "UI alternatives", "ui"), { diff --git a/modules/ui_common.py b/modules/ui_common.py index 78481c6f..cf1b8b32 100644 --- a/modules/ui_common.py +++ b/modules/ui_common.py @@ -169,11 +169,9 @@ def create_output_panel(tabname, outdir, toprow=None): return try: - if shared.opts.button_open_image_actual_dir and 0 <= index < len(images): - image = images[index] - image_path = image["name"].rsplit('?', 1)[0] - image_dir = os.path.split(image_path)[0] - if shared.opts.button_open_image_actual_dir_temp or not ui_tempdir.is_gradio_temp_path(image_dir): + if 'Sub' in shared.opts.open_dir_button_choice: + image_dir = os.path.split(images[index]["name"].rsplit('?', 1)[0])[0] + if 'temp' in shared.opts.open_dir_button_choice or not ui_tempdir.is_gradio_temp_path(image_dir): f = image_dir except Exception: pass From 5a8dd0c549c0221cd3ee1c53816aa52cf7b3b0ae Mon Sep 17 00:00:00 2001 From: Kohaku-Blueleaf <59680068+KohakuBlueleaf@users.noreply.github.com> Date: Sun, 18 Feb 2024 14:58:41 +0800 Subject: [PATCH 014/496] Fix rescale --- extensions-builtin/Lora/network_oft.py | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/extensions-builtin/Lora/network_oft.py b/extensions-builtin/Lora/network_oft.py index ed221d8f..f5e657b8 100644 --- a/extensions-builtin/Lora/network_oft.py +++ b/extensions-builtin/Lora/network_oft.py @@ -40,7 +40,9 @@ class NetworkModuleOFT(network.NetworkModule): self.is_boft = False if weights.w["oft_diag"].dim() == 4: self.is_boft = True - self.rescale = weight.w.get('rescale', None) + 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_conv = type(self.sd_module) in [torch.nn.Conv2d] From 9d5dc582be54031f3a2292105eb7dc540bcc8b0c Mon Sep 17 00:00:00 2001 From: HSIEH TSUNGYU Date: Sun, 18 Feb 2024 19:27:33 +0800 Subject: [PATCH 015/496] 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 --- modules/images.py | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/modules/images.py b/modules/images.py index b6f2358c..ebd3a901 100644 --- a/modules/images.py +++ b/modules/images.py @@ -548,6 +548,12 @@ def save_image_with_geninfo(image, geninfo, filename, extension=None, existing_p else: pnginfo_data = None + # Error handling for unsupported transparency in RGB mode + if (image.mode == "RGB" and + "transparency" in image.info and + isinstance(image.info["transparency"], bytes)): + del image.info["transparency"] + image.save(filename, format=image_format, quality=opts.jpeg_quality, pnginfo=pnginfo_data) elif extension.lower() in (".jpg", ".jpeg", ".webp"): From 4eb949625c8cc04ba579fc5486cc10acd541596b Mon Sep 17 00:00:00 2001 From: Kohaku-Blueleaf <59680068+KohakuBlueleaf@users.noreply.github.com> Date: Mon, 19 Feb 2024 14:43:07 +0800 Subject: [PATCH 016/496] prevent undefined variable --- extensions-builtin/Lora/network_oft.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/extensions-builtin/Lora/network_oft.py b/extensions-builtin/Lora/network_oft.py index f5e657b8..d658ad10 100644 --- a/extensions-builtin/Lora/network_oft.py +++ b/extensions-builtin/Lora/network_oft.py @@ -22,6 +22,8 @@ class NetworkModuleOFT(network.NetworkModule): self.org_module: list[torch.Module] = [self.sd_module] self.scale = 1.0 + self.is_kohya = False + self.is_boft = False # kohya-ss if "oft_blocks" in weights.w.keys(): @@ -31,13 +33,11 @@ class NetworkModuleOFT(network.NetworkModule): self.dim = self.oft_blocks.shape[0] # lora dim # LyCORIS OFT elif "oft_diag" in weights.w.keys(): - self.is_kohya = False self.oft_blocks = weights.w["oft_diag"] # self.alpha is unused self.dim = self.oft_blocks.shape[1] # (num_blocks, block_size, block_size) # LyCORIS BOFT - self.is_boft = False if weights.w["oft_diag"].dim() == 4: self.is_boft = True self.rescale = weights.w.get('rescale', None) From 33c8fe1221cdc53b9f00b7041b6e06cc9b0e037c Mon Sep 17 00:00:00 2001 From: Andray Date: Mon, 19 Feb 2024 16:57:49 +0400 Subject: [PATCH 017/496] avoid doble upscaling in inpaint --- modules/processing.py | 15 ++++++--------- 1 file changed, 6 insertions(+), 9 deletions(-) diff --git a/modules/processing.py b/modules/processing.py index f4aa165d..d208a922 100644 --- a/modules/processing.py +++ b/modules/processing.py @@ -74,16 +74,18 @@ def uncrop(image, dest_size, paste_loc): def apply_overlay(image, paste_loc, overlay): if overlay is None: - return image + return image, image.copy() if paste_loc is not None: image = uncrop(image, (overlay.width, overlay.height), paste_loc) + original_denoised_image = image.copy() + image = image.convert('RGBA') image.alpha_composite(overlay) image = image.convert('RGB') - return image + return image, original_denoised_image def create_binary_mask(image, round=True): if image.mode == 'RGBA' and image.getextrema()[-1] != (255, 255): @@ -1021,7 +1023,7 @@ def process_images_inner(p: StableDiffusionProcessing) -> Processed: if p.color_corrections is not None and i < len(p.color_corrections): if save_samples and opts.save_images_before_color_correction: - image_without_cc = apply_overlay(image, p.paste_to, overlay_image) + image_without_cc, _ = apply_overlay(image, p.paste_to, overlay_image) images.save_image(image_without_cc, p.outpath_samples, "", p.seeds[i], p.prompts[i], opts.samples_format, info=infotext(i), p=p, suffix="-before-color-correction") image = apply_color_correction(p.color_corrections[i], image) @@ -1029,12 +1031,7 @@ def process_images_inner(p: StableDiffusionProcessing) -> Processed: # that is being composited over the original image, # we need to keep the original image around # and use it in the composite step. - original_denoised_image = image.copy() - - if p.paste_to is not None: - original_denoised_image = uncrop(original_denoised_image, (overlay_image.width, overlay_image.height), p.paste_to) - - image = apply_overlay(image, p.paste_to, overlay_image) + image, original_denoised_image = apply_overlay(image, p.paste_to, overlay_image) if p.scripts is not None: pp = scripts.PostprocessImageArgs(image) From a5436a3ac0d0048a36f0652bde56ec2bc9aeb2ca Mon Sep 17 00:00:00 2001 From: Kohaku-Blueleaf <59680068+KohakuBlueleaf@users.noreply.github.com> Date: Tue, 20 Feb 2024 17:20:14 +0800 Subject: [PATCH 018/496] Update network_oft.py --- extensions-builtin/Lora/network_oft.py | 40 ++++++++++++-------------- 1 file changed, 19 insertions(+), 21 deletions(-) diff --git a/extensions-builtin/Lora/network_oft.py b/extensions-builtin/Lora/network_oft.py index d658ad10..5b899bd6 100644 --- a/extensions-builtin/Lora/network_oft.py +++ b/extensions-builtin/Lora/network_oft.py @@ -22,24 +22,24 @@ class NetworkModuleOFT(network.NetworkModule): self.org_module: list[torch.Module] = [self.sd_module] self.scale = 1.0 - self.is_kohya = False + self.is_R = False self.is_boft = False - # kohya-ss + # kohya-ss/New LyCORIS OFT/BOFT if "oft_blocks" in weights.w.keys(): - self.is_kohya = True self.oft_blocks = weights.w["oft_blocks"] # (num_blocks, block_size, block_size) - self.alpha = weights.w["alpha"] # alpha is constraint + self.alpha = weights.w.get("alpha", self.alpha) # alpha is constraint self.dim = self.oft_blocks.shape[0] # lora dim - # LyCORIS OFT + # Old LyCORIS OFT elif "oft_diag" in weights.w.keys(): + self.is_R = True self.oft_blocks = weights.w["oft_diag"] # self.alpha is unused self.dim = self.oft_blocks.shape[1] # (num_blocks, block_size, block_size) - # LyCORIS BOFT - if weights.w["oft_diag"].dim() == 4: - self.is_boft = True + # 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)) @@ -55,26 +55,24 @@ class NetworkModuleOFT(network.NetworkModule): elif is_other_linear: self.out_dim = self.sd_module.embed_dim - if self.is_kohya: - self.constraint = self.alpha * self.out_dim - self.num_blocks = self.dim - self.block_size = self.out_dim // self.dim + self.num_blocks = self.dim + self.block_size = self.out_dim // self.dim + self.constraint = (1 if self.alpha is None else self.alpha) * self.out_dim + if self.is_R: + self.constraint = None + self.block_size = self.dim + self.num_blocks = self.out_dim // self.dim elif self.is_boft: - self.constraint = None - self.boft_m = weights.w["oft_diag"].shape[0] - self.block_num = weights.w["oft_diag"].shape[1] - self.block_size = weights.w["oft_diag"].shape[2] + self.boft_m = self.oft_blocks.shape[0] + self.num_blocks = self.oft_blocks.shape[1] + self.block_size = self.oft_blocks.shape[2] self.boft_b = self.block_size - #self.block_size, self.block_num = butterfly_factor(self.out_dim, self.dim) - else: - self.constraint = None - self.block_size, self.num_blocks = factorization(self.out_dim, self.dim) def calc_updown(self, orig_weight): oft_blocks = self.oft_blocks.to(orig_weight.device) eye = torch.eye(self.block_size, device=oft_blocks.device) - if self.is_kohya: + if not self.is_R: block_Q = oft_blocks - oft_blocks.transpose(1, 2) # ensure skew-symmetric orthogonal matrix norm_Q = torch.norm(block_Q.flatten()) new_norm_Q = torch.clamp(norm_Q, max=self.constraint.to(oft_blocks.device)) From 591470d86d565559d79d14a66ff14ecea2bd7706 Mon Sep 17 00:00:00 2001 From: Kohaku-Blueleaf <59680068+KohakuBlueleaf@users.noreply.github.com> Date: Tue, 20 Feb 2024 17:21:34 +0800 Subject: [PATCH 019/496] linting --- extensions-builtin/Lora/network_oft.py | 1 - 1 file changed, 1 deletion(-) diff --git a/extensions-builtin/Lora/network_oft.py b/extensions-builtin/Lora/network_oft.py index 5b899bd6..f14c183a 100644 --- a/extensions-builtin/Lora/network_oft.py +++ b/extensions-builtin/Lora/network_oft.py @@ -1,6 +1,5 @@ import torch import network -from lyco_helpers import factorization from einops import rearrange From f4869f8de3ed76735ea331fe5463abc6190bd4cf Mon Sep 17 00:00:00 2001 From: drhead <1313496+drhead@users.noreply.github.com> Date: Tue, 20 Feb 2024 16:18:13 -0500 Subject: [PATCH 020/496] Add compatibility option for refiner switching --- modules/shared_options.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/modules/shared_options.py b/modules/shared_options.py index bb3752ba..e17eed51 100644 --- a/modules/shared_options.py +++ b/modules/shared_options.py @@ -227,7 +227,8 @@ options_templates.update(options_section(('compatibility', "Compatibility", "sd" "dont_fix_second_order_samplers_schedule": OptionInfo(False, "Do not fix prompt schedule for second order samplers."), "hires_fix_use_firstpass_conds": OptionInfo(False, "For hires fix, calculate conds of second pass using extra networks of first pass."), "use_old_scheduling": OptionInfo(False, "Use old prompt editing timelines.", infotext="Old prompt editing timelines").info("For [red:green:N]; old: If N < 1, it's a fraction of steps (and hires fix uses range from 0 to 1), if N >= 1, it's an absolute number of steps; new: If N has a decimal point in it, it's a fraction of steps (and hires fix uses range from 1 to 2), othewrwise it's an absolute number of steps"), - "use_downcasted_alpha_bar": OptionInfo(False, "Downcast model alphas_cumprod to fp16 before sampling. For reproducing old seeds.", infotext="Downcast alphas_cumprod") + "use_downcasted_alpha_bar": OptionInfo(False, "Downcast model alphas_cumprod to fp16 before sampling. For reproducing old seeds.", infotext="Downcast alphas_cumprod"), + "refiner_switch_by_sample_steps": OptionInfo(False, "Switch to refiner by sampling steps instead of model timesteps. Old behavior for refiner.", infotext="Refiner switch by sampling steps") })) options_templates.update(options_section(('interrogate', "Interrogate"), { From 09d2e5881120c4a51888633947062b40726c6fef Mon Sep 17 00:00:00 2001 From: drhead <1313496+drhead@users.noreply.github.com> Date: Tue, 20 Feb 2024 16:22:40 -0500 Subject: [PATCH 021/496] Pass sigma to apply_refiner --- modules/sd_samplers_cfg_denoiser.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/modules/sd_samplers_cfg_denoiser.py b/modules/sd_samplers_cfg_denoiser.py index a73d3b03..93581c9a 100644 --- a/modules/sd_samplers_cfg_denoiser.py +++ b/modules/sd_samplers_cfg_denoiser.py @@ -152,7 +152,7 @@ class CFGDenoiser(torch.nn.Module): if state.interrupted or state.skipped: raise sd_samplers_common.InterruptedException - if sd_samplers_common.apply_refiner(self): + if sd_samplers_common.apply_refiner(self, sigma): cond = self.sampler.sampler_extra_args['cond'] uncond = self.sampler.sampler_extra_args['uncond'] From 25eeeaa65f819bb40df427141b82b46d3fcf59e9 Mon Sep 17 00:00:00 2001 From: drhead <1313496+drhead@users.noreply.github.com> Date: Tue, 20 Feb 2024 16:37:29 -0500 Subject: [PATCH 022/496] Allow refiner to be triggered by model timestep instead of sampling --- modules/sd_samplers_common.py | 11 ++++++++++- 1 file changed, 10 insertions(+), 1 deletion(-) diff --git a/modules/sd_samplers_common.py b/modules/sd_samplers_common.py index 6bd38e12..8052b021 100644 --- a/modules/sd_samplers_common.py +++ b/modules/sd_samplers_common.py @@ -156,7 +156,16 @@ replace_torchsde_browinan() def apply_refiner(cfg_denoiser): - completed_ratio = cfg_denoiser.step / cfg_denoiser.total_steps + if opts.refiner_switch_by_sample_steps: + completed_ratio = cfg_denoiser.step / cfg_denoiser.total_steps + else: + # torch.max(sigma) only to handle rare case where we might have different sigmas in the same batch + try: + timestep = torch.argmin(torch.abs(cfg_denoiser.inner_model.sigmas - torch.max(sigma))) + except AttributeError: # for samplers that dont use sigmas (DDIM) sigma is actually the timestep + timestep = torch.max(sigma).to(dtype=int) + completed_ratio = (999 - timestep) / 1000 + refiner_switch_at = cfg_denoiser.p.refiner_switch_at refiner_checkpoint_info = cfg_denoiser.p.refiner_checkpoint_info From bf348032bc07d48ec0b4fbb5be1c4648ee8bd49b Mon Sep 17 00:00:00 2001 From: drhead <1313496+drhead@users.noreply.github.com> Date: Tue, 20 Feb 2024 16:59:28 -0500 Subject: [PATCH 023/496] fix missing arg --- modules/sd_samplers_common.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/modules/sd_samplers_common.py b/modules/sd_samplers_common.py index 8052b021..045b9e2f 100644 --- a/modules/sd_samplers_common.py +++ b/modules/sd_samplers_common.py @@ -155,7 +155,7 @@ def replace_torchsde_browinan(): replace_torchsde_browinan() -def apply_refiner(cfg_denoiser): +def apply_refiner(cfg_denoiser, sigma): if opts.refiner_switch_by_sample_steps: completed_ratio = cfg_denoiser.step / cfg_denoiser.total_steps else: From 9c1ece89784e36a86b19f371e3b6e60bb630394e Mon Sep 17 00:00:00 2001 From: drhead <1313496+drhead@users.noreply.github.com> Date: Tue, 20 Feb 2024 19:23:21 -0500 Subject: [PATCH 024/496] Protect alphas_cumprod during refiner switchover --- modules/sd_samplers_common.py | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/modules/sd_samplers_common.py b/modules/sd_samplers_common.py index 6bd38e12..c9578ffe 100644 --- a/modules/sd_samplers_common.py +++ b/modules/sd_samplers_common.py @@ -181,8 +181,12 @@ def apply_refiner(cfg_denoiser): cfg_denoiser.p.extra_generation_params['Refiner'] = refiner_checkpoint_info.short_title cfg_denoiser.p.extra_generation_params['Refiner switch at'] = refiner_switch_at + alphas_cumprod_original = cfg_denoiser.p.sd_model.alphas_cumprod_original + alphas_cumprod = cfg_denoiser.p.sd_model.alphas_cumprod with sd_models.SkipWritingToConfig(): sd_models.reload_model_weights(info=refiner_checkpoint_info) + cfg_denoiser.p.sd_model.alphas_cumprod_original = alphas_cumprod_original + cfg_denoiser.p.sd_model.alphas_cumprod = alphas_cumprod devices.torch_gc() cfg_denoiser.p.setup_conds() From b7aa425344ea4f598350e94c451cb7ffd3e6630c Mon Sep 17 00:00:00 2001 From: wangshuai09 <391746016@qq.com> Date: Wed, 21 Feb 2024 11:49:06 +0800 Subject: [PATCH 025/496] del gpu_info for npu --- webui.sh | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/webui.sh b/webui.sh index f116376f..be2b853b 100755 --- a/webui.sh +++ b/webui.sh @@ -158,9 +158,9 @@ then if echo "$gpu_info" | grep -q "AMD" && [[ -z "${TORCH_COMMAND}" ]] then export TORCH_COMMAND="pip install torch==2.0.1+rocm5.4.2 torchvision==0.15.2+rocm5.4.2 --index-url https://download.pytorch.org/whl/rocm5.4.2" - elif echo "$gpu_info" | grep -q "Huawei" && [[ -z "${TORCH_COMMAND}" ]] + elif eval "npu-smi info" then - export TORCH_COMMAND="pip install torch==2.1.0 torchvision torchaudio --index-url https://download.pytorch.org/whl/cpu; pip install torch_npu" + export TORCH_COMMAND="pip install torch==2.1.0 torchvision torchaudio --index-url https://download.pytorch.org/whl/cpu; pip install torch_npu==2.1.0" fi fi From 64179c32213f986d1378b2f414be6ef86af1a82f Mon Sep 17 00:00:00 2001 From: Kohaku-Blueleaf <59680068+KohakuBlueleaf@users.noreply.github.com> Date: Wed, 21 Feb 2024 22:50:43 +0800 Subject: [PATCH 026/496] Update network_oft.py --- extensions-builtin/Lora/network_oft.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/extensions-builtin/Lora/network_oft.py b/extensions-builtin/Lora/network_oft.py index f14c183a..ce931c62 100644 --- a/extensions-builtin/Lora/network_oft.py +++ b/extensions-builtin/Lora/network_oft.py @@ -72,7 +72,7 @@ class NetworkModuleOFT(network.NetworkModule): eye = torch.eye(self.block_size, device=oft_blocks.device) if not self.is_R: - block_Q = oft_blocks - oft_blocks.transpose(1, 2) # ensure skew-symmetric orthogonal matrix + block_Q = oft_blocks - oft_blocks.transpose(-1, -2) # ensure skew-symmetric orthogonal matrix norm_Q = torch.norm(block_Q.flatten()) new_norm_Q = torch.clamp(norm_Q, max=self.constraint.to(oft_blocks.device)) block_Q = block_Q * ((new_norm_Q + 1e-8) / (norm_Q + 1e-8)) From c4afdb7895a5a5224915b3c6f27f8e800e18ef41 Mon Sep 17 00:00:00 2001 From: Kohaku-Blueleaf <59680068+KohakuBlueleaf@users.noreply.github.com> Date: Thu, 22 Feb 2024 00:43:32 +0800 Subject: [PATCH 027/496] For no constraint --- extensions-builtin/Lora/network_oft.py | 11 ++++++----- 1 file changed, 6 insertions(+), 5 deletions(-) diff --git a/extensions-builtin/Lora/network_oft.py b/extensions-builtin/Lora/network_oft.py index ce931c62..7821a8a7 100644 --- a/extensions-builtin/Lora/network_oft.py +++ b/extensions-builtin/Lora/network_oft.py @@ -27,7 +27,7 @@ class NetworkModuleOFT(network.NetworkModule): # kohya-ss/New LyCORIS OFT/BOFT if "oft_blocks" in weights.w.keys(): self.oft_blocks = weights.w["oft_blocks"] # (num_blocks, block_size, block_size) - self.alpha = weights.w.get("alpha", self.alpha) # alpha is constraint + self.alpha = weights.w.get("alpha", None) # alpha is constraint self.dim = self.oft_blocks.shape[0] # lora dim # Old LyCORIS OFT elif "oft_diag" in weights.w.keys(): @@ -56,7 +56,7 @@ class NetworkModuleOFT(network.NetworkModule): self.num_blocks = self.dim self.block_size = self.out_dim // self.dim - self.constraint = (1 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 if self.is_R: self.constraint = None self.block_size = self.dim @@ -73,9 +73,10 @@ class NetworkModuleOFT(network.NetworkModule): if not self.is_R: block_Q = oft_blocks - oft_blocks.transpose(-1, -2) # ensure skew-symmetric orthogonal matrix - norm_Q = torch.norm(block_Q.flatten()) - new_norm_Q = torch.clamp(norm_Q, max=self.constraint.to(oft_blocks.device)) - block_Q = block_Q * ((new_norm_Q + 1e-8) / (norm_Q + 1e-8)) + if self.constraint != 0: + norm_Q = torch.norm(block_Q.flatten()) + new_norm_Q = torch.clamp(norm_Q, max=self.constraint.to(oft_blocks.device)) + block_Q = block_Q * ((new_norm_Q + 1e-8) / (norm_Q + 1e-8)) oft_blocks = torch.matmul(eye + block_Q, (eye - block_Q).float().inverse()) R = oft_blocks.to(orig_weight.device) From f537e5a519d080fd2b16d94d91e7fed8dd3fd680 Mon Sep 17 00:00:00 2001 From: dtlnor Date: Thu, 22 Feb 2024 12:26:57 +0900 Subject: [PATCH 028/496] fix #14591 - using translated content to do categories mapping --- javascript/settings.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/javascript/settings.js b/javascript/settings.js index e6009290..b2d981c2 100644 --- a/javascript/settings.js +++ b/javascript/settings.js @@ -55,8 +55,8 @@ onOptionsChanged(function() { }); opts._categories.forEach(function(x) { - var section = x[0]; - var category = x[1]; + var section = localization[x[0]] ?? x[0]; + var category = localization[x[1]] ?? x[1]; var span = document.createElement('SPAN'); span.textContent = category; From 1da05297ea1850c6df5ef1f3d6a487d4bb4c50dd Mon Sep 17 00:00:00 2001 From: AUTOMATIC1111 <16777216c@gmail.com> Date: Thu, 22 Feb 2024 10:27:38 +0300 Subject: [PATCH 029/496] possible fix for reload button not appearing in some cases for extra networks. --- modules/ui_extra_networks.py | 17 +++++++++++------ 1 file changed, 11 insertions(+), 6 deletions(-) diff --git a/modules/ui_extra_networks.py b/modules/ui_extra_networks.py index c03b9f08..6874a024 100644 --- a/modules/ui_extra_networks.py +++ b/modules/ui_extra_networks.py @@ -472,7 +472,7 @@ class ExtraNetworksPage: return f"
    {res}
" - def create_card_view_html(self, tabname: str) -> str: + def create_card_view_html(self, tabname: str, *, none_message) -> str: """Generates HTML for the network Card View section for a tab. This HTML goes into the `extra-networks-pane.html`
with @@ -480,6 +480,7 @@ class ExtraNetworksPage: Args: tabname: The name of the active tab. + none_message: HTML text to show when there are no cards. Returns: HTML formatted string. @@ -490,24 +491,28 @@ class ExtraNetworksPage: if res == "": dirs = "".join([f"
  • {x}
  • " for x in self.allowed_directories_for_previews()]) - res = shared.html("extra-networks-no-cards.html").format(dirs=dirs) + res = none_message or shared.html("extra-networks-no-cards.html").format(dirs=dirs) return res - def create_html(self, tabname): + def create_html(self, tabname, *, empty=False): """Generates an HTML string for the current pane. The generated HTML uses `extra-networks-pane.html` as a template. Args: tabname: The name of the active tab. + empty: create an empty HTML page with no items Returns: HTML formatted string. """ self.lister.reset() self.metadata = {} - self.items = {x["name"]: x for x in self.list_items()} + + items_list = [] if empty else self.list_items() + self.items = {x["name"]: x for x in items_list} + # Populate the instance metadata for each item. for item in self.items.values(): metadata = item.get("metadata") @@ -536,7 +541,7 @@ class ExtraNetworksPage: "tree_view_btn_extra_class": tree_view_btn_extra_class, "tree_view_div_extra_class": tree_view_div_extra_class, "tree_html": self.create_tree_view_html(tabname), - "items_html": self.create_card_view_html(tabname), + "items_html": self.create_card_view_html(tabname, none_message="Loading..." if empty else None), } ) @@ -655,7 +660,7 @@ def create_ui(interface: gr.Blocks, unrelated_tabs, tabname): pass elem_id = f"{tabname}_{page.extra_networks_tabname}_cards_html" - page_elem = gr.HTML('Loading...', elem_id=elem_id) + page_elem = gr.HTML(page.create_html(tabname, empty=True), elem_id=elem_id) ui.pages.append(page_elem) editor = page.create_user_metadata_editor(ui, tabname) editor.create_ui() From ba66cf8d69b770b171a42ae996a466aceaaf7ca3 Mon Sep 17 00:00:00 2001 From: wangshuai09 <391746016@qq.com> Date: Thu, 22 Feb 2024 20:17:10 +0800 Subject: [PATCH 030/496] update --- modules/hypernetworks/hypernetwork.py | 1 + modules/sd_hijack_clip.py | 2 +- 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/modules/hypernetworks/hypernetwork.py b/modules/hypernetworks/hypernetwork.py index be3e4648..6082d9cb 100644 --- a/modules/hypernetworks/hypernetwork.py +++ b/modules/hypernetworks/hypernetwork.py @@ -95,6 +95,7 @@ class HypernetworkModule(torch.nn.Module): zeros_(b) else: raise KeyError(f"Key {weight_init} is not defined as initialization!") + devices.torch_npu_set_device() self.to(devices.device) def fix_old_state_dict(self, state_dict): diff --git a/modules/sd_hijack_clip.py b/modules/sd_hijack_clip.py index 98350ac4..228969dc 100644 --- a/modules/sd_hijack_clip.py +++ b/modules/sd_hijack_clip.py @@ -230,7 +230,7 @@ class FrozenCLIPEmbedderWithCustomWordsBase(torch.nn.Module): for fixes in self.hijack.fixes: for _position, embedding in fixes: used_embeddings[embedding.name] = embedding - + devices.torch_npu_set_device() z = self.process_tokens(tokens, multipliers) zs.append(z) From 85abbbb8fa8f983222e7fffec1e686c06cf4deae Mon Sep 17 00:00:00 2001 From: Andray Date: Thu, 22 Feb 2024 17:04:56 +0400 Subject: [PATCH 031/496] support resizable columns for touch (tablets) --- javascript/resizeHandle.js | 86 +++++++++++++++++++++++++------------- 1 file changed, 56 insertions(+), 30 deletions(-) diff --git a/javascript/resizeHandle.js b/javascript/resizeHandle.js index 8c5c5169..13f2b371 100644 --- a/javascript/resizeHandle.js +++ b/javascript/resizeHandle.js @@ -65,21 +65,31 @@ resizeHandle.classList.add('resize-handle'); parent.insertBefore(resizeHandle, rightCol); - resizeHandle.addEventListener('mousedown', (evt) => { - if (evt.button !== 0) return; + ['mousedown', 'touchstart'].forEach((eventType) => { + resizeHandle.addEventListener(eventType, (evt) => { + if (eventType.startsWith('mouse')){ + if (evt.button !== 0) return; + } else { + if (evt.changedTouches.length !== 1) return; + } - evt.preventDefault(); - evt.stopPropagation(); + evt.preventDefault(); + evt.stopPropagation(); - document.body.classList.add('resizing'); + document.body.classList.add('resizing'); - R.tracking = true; - R.parent = parent; - R.parentWidth = parent.offsetWidth; - R.handle = resizeHandle; - R.leftCol = leftCol; - R.leftColStartWidth = leftCol.offsetWidth; - R.screenX = evt.screenX; + R.tracking = true; + R.parent = parent; + R.parentWidth = parent.offsetWidth; + R.handle = resizeHandle; + R.leftCol = leftCol; + R.leftColStartWidth = leftCol.offsetWidth; + if (eventType.startsWith('mouse')){ + R.screenX = evt.screenX; + } else { + R.screenX = evt.changedTouches[0].screenX; + } + }); }); resizeHandle.addEventListener('dblclick', (evt) => { @@ -92,30 +102,46 @@ afterResize(parent); } - window.addEventListener('mousemove', (evt) => { - if (evt.button !== 0) return; + ['mousemove', 'touchmove'].forEach((eventType) => { + window.addEventListener(eventType, (evt) => { + if (eventType.startsWith('mouse')){ + if (evt.button !== 0) return; + } else { + if (evt.changedTouches.length !== 1) return; + } - if (R.tracking) { - evt.preventDefault(); - evt.stopPropagation(); - - const delta = R.screenX - evt.screenX; - const leftColWidth = Math.max(Math.min(R.leftColStartWidth - delta, R.parent.offsetWidth - GRADIO_MIN_WIDTH - PAD), GRADIO_MIN_WIDTH); - setLeftColGridTemplate(R.parent, leftColWidth); - } + if (R.tracking) { + evt.preventDefault(); + evt.stopPropagation(); + + if (eventType.startsWith('mouse')){ + var delta = R.screenX - evt.screenX; + } else { + var 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); + setLeftColGridTemplate(R.parent, leftColWidth); + } + }); }); - window.addEventListener('mouseup', (evt) => { - if (evt.button !== 0) return; + ['mouseup', 'touchend'].forEach((eventType) => { + window.addEventListener(eventType, (evt) => { + if (eventType.startsWith('mouse')){ + if (evt.button !== 0) return; + } else { + if (evt.changedTouches.length !== 1) return; + } - if (R.tracking) { - evt.preventDefault(); - evt.stopPropagation(); + if (R.tracking) { + evt.preventDefault(); + evt.stopPropagation(); - R.tracking = false; + R.tracking = false; - document.body.classList.remove('resizing'); - } + document.body.classList.remove('resizing'); + } + }); }); From ab1e0fa9bff196b4fd6f4eef560218833e6bb387 Mon Sep 17 00:00:00 2001 From: Andray Date: Thu, 22 Feb 2024 17:16:16 +0400 Subject: [PATCH 032/496] fix lint and console warning --- javascript/resizeHandle.js | 19 +++++++++++-------- 1 file changed, 11 insertions(+), 8 deletions(-) diff --git a/javascript/resizeHandle.js b/javascript/resizeHandle.js index 13f2b371..038f4cb0 100644 --- a/javascript/resizeHandle.js +++ b/javascript/resizeHandle.js @@ -67,7 +67,7 @@ ['mousedown', 'touchstart'].forEach((eventType) => { resizeHandle.addEventListener(eventType, (evt) => { - if (eventType.startsWith('mouse')){ + if (eventType.startsWith('mouse')) { if (evt.button !== 0) return; } else { if (evt.changedTouches.length !== 1) return; @@ -84,7 +84,7 @@ R.handle = resizeHandle; R.leftCol = leftCol; R.leftColStartWidth = leftCol.offsetWidth; - if (eventType.startsWith('mouse')){ + if (eventType.startsWith('mouse')) { R.screenX = evt.screenX; } else { R.screenX = evt.changedTouches[0].screenX; @@ -104,20 +104,23 @@ ['mousemove', 'touchmove'].forEach((eventType) => { window.addEventListener(eventType, (evt) => { - if (eventType.startsWith('mouse')){ + if (eventType.startsWith('mouse')) { if (evt.button !== 0) return; } else { if (evt.changedTouches.length !== 1) return; } if (R.tracking) { - evt.preventDefault(); + if (eventType.startsWith('mouse')) { + evt.preventDefault(); + } evt.stopPropagation(); - if (eventType.startsWith('mouse')){ - var delta = R.screenX - evt.screenX; + let delta = 0; + if (eventType.startsWith('mouse')) { + delta = R.screenX - evt.screenX; } else { - var 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); setLeftColGridTemplate(R.parent, leftColWidth); @@ -127,7 +130,7 @@ ['mouseup', 'touchend'].forEach((eventType) => { window.addEventListener(eventType, (evt) => { - if (eventType.startsWith('mouse')){ + if (eventType.startsWith('mouse')) { if (evt.button !== 0) return; } else { if (evt.changedTouches.length !== 1) return; From 58985e6b372de408150fcd2dbcd6c6d5a17a3f58 Mon Sep 17 00:00:00 2001 From: Andray Date: Thu, 22 Feb 2024 17:22:00 +0400 Subject: [PATCH 033/496] fix lint 2 --- javascript/resizeHandle.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/javascript/resizeHandle.js b/javascript/resizeHandle.js index 038f4cb0..f22aa51d 100644 --- a/javascript/resizeHandle.js +++ b/javascript/resizeHandle.js @@ -115,7 +115,7 @@ evt.preventDefault(); } evt.stopPropagation(); - + let delta = 0; if (eventType.startsWith('mouse')) { delta = R.screenX - evt.screenX; From 3f18a09c8638cfd69848a9f39d1841848b57d036 Mon Sep 17 00:00:00 2001 From: AUTOMATIC1111 <16777216c@gmail.com> Date: Thu, 22 Feb 2024 21:27:10 +0300 Subject: [PATCH 034/496] make extra network card description plaintext by default, with an option to re-enable HTML as it was --- modules/shared_options.py | 1 + modules/ui_extra_networks.py | 6 +++++- 2 files changed, 6 insertions(+), 1 deletion(-) diff --git a/modules/shared_options.py b/modules/shared_options.py index bb3752ba..64f8f196 100644 --- a/modules/shared_options.py +++ b/modules/shared_options.py @@ -254,6 +254,7 @@ options_templates.update(options_section(('extra_networks', "Extra Networks", "s "extra_networks_card_height": OptionInfo(0, "Card height for Extra Networks").info("in pixels"), "extra_networks_card_text_scale": OptionInfo(1.0, "Card text scale", gr.Slider, {"minimum": 0.0, "maximum": 2.0, "step": 0.01}).info("1 = original size"), "extra_networks_card_show_desc": OptionInfo(True, "Show description on card"), + "extra_networks_card_description_is_html": OptionInfo(False, "Treat card description as HTML"), "extra_networks_card_order_field": OptionInfo("Path", "Default order field for Extra Networks cards", gr.Dropdown, {"choices": ['Path', 'Name', 'Date Created', 'Date Modified']}).needs_reload_ui(), "extra_networks_card_order": OptionInfo("Ascending", "Default order for Extra Networks cards", gr.Dropdown, {"choices": ['Ascending', 'Descending']}).needs_reload_ui(), "extra_networks_tree_view_default_enabled": OptionInfo(False, "Enables the Extra Networks directory tree view by default").needs_reload_ui(), diff --git a/modules/ui_extra_networks.py b/modules/ui_extra_networks.py index 6874a024..34c46ed4 100644 --- a/modules/ui_extra_networks.py +++ b/modules/ui_extra_networks.py @@ -289,12 +289,16 @@ class ExtraNetworksPage: } ) + description = (item.get("description", "") or "" if shared.opts.extra_networks_card_show_desc else "") + if not shared.opts.extra_networks_card_description_is_html: + description = html.escape(description) + # Some items here might not be used depending on HTML template used. args = { "background_image": background_image, "card_clicked": onclick, "copy_path_button": btn_copy_path, - "description": (item.get("description", "") or "" if shared.opts.extra_networks_card_show_desc else ""), + "description": description, "edit_button": btn_edit_item, "local_preview": quote_js(item["local_preview"]), "metadata_button": btn_metadata, From 9211febbfc9ce45bdd2dc33e73939d67924c3f1e Mon Sep 17 00:00:00 2001 From: Andray Date: Fri, 23 Feb 2024 02:20:42 +0400 Subject: [PATCH 035/496] ResizeHandleRow - allow overriden column scale parametr --- javascript/resizeHandle.js | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/javascript/resizeHandle.js b/javascript/resizeHandle.js index f22aa51d..cd3e68c6 100644 --- a/javascript/resizeHandle.js +++ b/javascript/resizeHandle.js @@ -1,6 +1,5 @@ (function() { const GRADIO_MIN_WIDTH = 320; - const GRID_TEMPLATE_COLUMNS = '1fr 16px 1fr'; const PAD = 16; const DEBOUNCE_TIME = 100; @@ -37,7 +36,7 @@ } function afterResize(parent) { - if (displayResizeHandle(parent) && parent.style.gridTemplateColumns != GRID_TEMPLATE_COLUMNS) { + if (displayResizeHandle(parent) && parent.style.gridTemplateColumns != parent.style.originalGridTemplateColumns) { const oldParentWidth = R.parentWidth; const newParentWidth = parent.offsetWidth; const widthL = parseInt(parent.style.gridTemplateColumns.split(' ')[0]); @@ -59,7 +58,9 @@ parent.style.display = 'grid'; parent.style.gap = '0'; - parent.style.gridTemplateColumns = GRID_TEMPLATE_COLUMNS; + const gridTemplateColumns = `${parent.children[0].style.flexGrow}fr ${PAD}px ${parent.children[1].style.flexGrow}fr`; + parent.style.gridTemplateColumns = gridTemplateColumns; + parent.style.originalGridTemplateColumns = gridTemplateColumns; const resizeHandle = document.createElement('div'); resizeHandle.classList.add('resize-handle'); @@ -96,7 +97,7 @@ evt.preventDefault(); evt.stopPropagation(); - parent.style.gridTemplateColumns = GRID_TEMPLATE_COLUMNS; + parent.style.gridTemplateColumns = parent.style.originalGridTemplateColumns; }); afterResize(parent); From ed594d7ba69cf065222348f5aabc0374525d8ad5 Mon Sep 17 00:00:00 2001 From: DB Eriospermum Date: Fri, 23 Feb 2024 13:37:37 +0800 Subject: [PATCH 036/496] fix: the `split_threshold` parameter does not work when running Split oversized images --- scripts/postprocessing_split_oversized.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/scripts/postprocessing_split_oversized.py b/scripts/postprocessing_split_oversized.py index c4a03160..133e199b 100644 --- a/scripts/postprocessing_split_oversized.py +++ b/scripts/postprocessing_split_oversized.py @@ -61,7 +61,7 @@ class ScriptPostprocessingSplitOversized(scripts_postprocessing.ScriptPostproces ratio = (pp.image.height * width) / (pp.image.width * height) inverse_xy = True - if ratio >= 1.0 and ratio > split_threshold: + if ratio >= 1.0 or ratio > split_threshold: return result, *others = split_pic(pp.image, inverse_xy, width, height, overlap_ratio) From bab918f049dd42f53eebc241ad27607ca63cc57b Mon Sep 17 00:00:00 2001 From: Andray Date: Fri, 23 Feb 2024 18:34:24 +0400 Subject: [PATCH 037/496] fix resize-handle for vertical layout --- javascript/resizeHandle.js | 2 ++ 1 file changed, 2 insertions(+) diff --git a/javascript/resizeHandle.js b/javascript/resizeHandle.js index f22aa51d..a3164b4f 100644 --- a/javascript/resizeHandle.js +++ b/javascript/resizeHandle.js @@ -23,12 +23,14 @@ function displayResizeHandle(parent) { if (window.innerWidth < GRADIO_MIN_WIDTH * 2 + PAD * 4) { parent.style.display = 'flex'; + parent.querySelector('.resize-handle').style.display = "none"; if (R.handle != null) { R.handle.style.opacity = '0'; } return false; } else { parent.style.display = 'grid'; + parent.querySelector('.resize-handle').style.display = 'block'; if (R.handle != null) { R.handle.style.opacity = '100'; } From 3a99824638027ff84cf6c4af3421741cc091e617 Mon Sep 17 00:00:00 2001 From: Andray Date: Fri, 23 Feb 2024 20:26:56 +0400 Subject: [PATCH 038/496] register_tmp_file also with mtime --- modules/ui_tempdir.py | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/modules/ui_tempdir.py b/modules/ui_tempdir.py index 621ed1ec..ecd6bdec 100644 --- a/modules/ui_tempdir.py +++ b/modules/ui_tempdir.py @@ -35,7 +35,9 @@ def save_pil_to_file(self, pil_image, dir=None, format="png"): already_saved_as = getattr(pil_image, 'already_saved_as', None) if already_saved_as and os.path.isfile(already_saved_as): register_tmp_file(shared.demo, already_saved_as) - return f'{already_saved_as}?{os.path.getmtime(already_saved_as)}' + filename_with_mtime = f'{already_saved_as}?{os.path.getmtime(already_saved_as)}' + register_tmp_file(shared.demo, filename_with_mtime) + return filename_with_mtime if shared.opts.temp_dir != "": dir = shared.opts.temp_dir From 648f6a8e0cdf5881cbec9697792e6294c54422d4 Mon Sep 17 00:00:00 2001 From: drhead <1313496+drhead@users.noreply.github.com> Date: Sun, 25 Feb 2024 23:28:36 -0500 Subject: [PATCH 039/496] dont need to preserve alphas_cumprod_original --- modules/sd_samplers_common.py | 2 -- 1 file changed, 2 deletions(-) diff --git a/modules/sd_samplers_common.py b/modules/sd_samplers_common.py index c9578ffe..7ab1bf65 100644 --- a/modules/sd_samplers_common.py +++ b/modules/sd_samplers_common.py @@ -181,11 +181,9 @@ def apply_refiner(cfg_denoiser): cfg_denoiser.p.extra_generation_params['Refiner'] = refiner_checkpoint_info.short_title cfg_denoiser.p.extra_generation_params['Refiner switch at'] = refiner_switch_at - alphas_cumprod_original = cfg_denoiser.p.sd_model.alphas_cumprod_original alphas_cumprod = cfg_denoiser.p.sd_model.alphas_cumprod with sd_models.SkipWritingToConfig(): sd_models.reload_model_weights(info=refiner_checkpoint_info) - cfg_denoiser.p.sd_model.alphas_cumprod_original = alphas_cumprod_original cfg_denoiser.p.sd_model.alphas_cumprod = alphas_cumprod devices.torch_gc() From 6e6cc2922d39fff4029d47c316c22a1c152680ce Mon Sep 17 00:00:00 2001 From: Andray Date: Mon, 26 Feb 2024 13:37:29 +0400 Subject: [PATCH 040/496] fix resize handle --- javascript/resizeHandle.js | 12 +++--------- 1 file changed, 3 insertions(+), 9 deletions(-) diff --git a/javascript/resizeHandle.js b/javascript/resizeHandle.js index a3164b4f..ce67ca67 100644 --- a/javascript/resizeHandle.js +++ b/javascript/resizeHandle.js @@ -23,17 +23,11 @@ function displayResizeHandle(parent) { if (window.innerWidth < GRADIO_MIN_WIDTH * 2 + PAD * 4) { parent.style.display = 'flex'; - parent.querySelector('.resize-handle').style.display = "none"; - if (R.handle != null) { - R.handle.style.opacity = '0'; - } + parent.resizeHandle.style.display = "none"; return false; } else { parent.style.display = 'grid'; - parent.querySelector('.resize-handle').style.display = 'block'; - if (R.handle != null) { - R.handle.style.opacity = '100'; - } + parent.resizeHandle.style.display = "block"; return true; } } @@ -66,6 +60,7 @@ const resizeHandle = document.createElement('div'); resizeHandle.classList.add('resize-handle'); parent.insertBefore(resizeHandle, rightCol); + parent.resizeHandle = resizeHandle; ['mousedown', 'touchstart'].forEach((eventType) => { resizeHandle.addEventListener(eventType, (evt) => { @@ -83,7 +78,6 @@ R.tracking = true; R.parent = parent; R.parentWidth = parent.offsetWidth; - R.handle = resizeHandle; R.leftCol = leftCol; R.leftColStartWidth = leftCol.offsetWidth; if (eventType.startsWith('mouse')) { From dd4b0b95d5a59fa96759e5eb3937c9d268ebc2b9 Mon Sep 17 00:00:00 2001 From: Andray Date: Mon, 26 Feb 2024 16:30:15 +0400 Subject: [PATCH 041/496] cmd args: allow unix filenames and filenames max length --- modules/cmd_args.py | 4 +++- modules/images.py | 7 +++++-- 2 files changed, 8 insertions(+), 3 deletions(-) diff --git a/modules/cmd_args.py b/modules/cmd_args.py index 312dabff..be7a5987 100644 --- a/modules/cmd_args.py +++ b/modules/cmd_args.py @@ -120,4 +120,6 @@ 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("--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("--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') diff --git a/modules/images.py b/modules/images.py index b6f2358c..e7d11172 100644 --- a/modules/images.py +++ b/modules/images.py @@ -321,13 +321,16 @@ def resize_image(resize_mode, im, width, height, upscaler_name=None): 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_postfix = ' .' re_nonletters = re.compile(r'[\s' + string.punctuation + ']+') re_pattern = 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() From 3a618e3d24394aef0f8682ded713ef1b6c265553 Mon Sep 17 00:00:00 2001 From: catboxanon <122327233+catboxanon@users.noreply.github.com> Date: Mon, 26 Feb 2024 12:44:57 -0500 Subject: [PATCH 042/496] 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 --- modules/paths_internal.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/modules/paths_internal.py b/modules/paths_internal.py index 2ed1392a..6058b0cd 100644 --- a/modules/paths_internal.py +++ b/modules/paths_internal.py @@ -7,7 +7,7 @@ import shlex from pathlib import Path -normalized_filepath = lambda filepath: str(Path(filepath).resolve()) +normalized_filepath = lambda filepath: str(Path(filepath).absolute()) commandline_args = os.environ.get('COMMANDLINE_ARGS', "") sys.argv += shlex.split(commandline_args) From e2cd92ea230801ecc5fc7ed90e14ab55c946fb4a Mon Sep 17 00:00:00 2001 From: drhead <1313496+drhead@users.noreply.github.com> Date: Mon, 26 Feb 2024 23:43:27 -0500 Subject: [PATCH 043/496] move refiner fix to sd_models.py --- modules/sd_models.py | 32 ++++++++++++++++++++++++++++++++ 1 file changed, 32 insertions(+) diff --git a/modules/sd_models.py b/modules/sd_models.py index 2c045771..fbd53adb 100644 --- a/modules/sd_models.py +++ b/modules/sd_models.py @@ -15,6 +15,7 @@ from ldm.util import instantiate_from_config from modules import paths, shared, modelloader, devices, script_callbacks, sd_vae, sd_disable_initialization, errors, hashes, sd_models_config, sd_unet, sd_models_xl, cache, extra_networks, processing, lowvram, sd_hijack, patches from modules.timer import Timer +from modules.shared import opts import tomesd import numpy as np @@ -549,6 +550,36 @@ def repair_config(sd_config): karlo_path = os.path.join(paths.models_path, 'karlo') sd_config.model.params.noise_aug_config.params.clip_stats_path = sd_config.model.params.noise_aug_config.params.clip_stats_path.replace("checkpoints/karlo_models", karlo_path) +def apply_alpha_schedule_override(sd_model, p=None): + def rescale_zero_terminal_snr_abar(alphas_cumprod): + alphas_bar_sqrt = alphas_cumprod.sqrt() + + # Store old values. + alphas_bar_sqrt_0 = alphas_bar_sqrt[0].clone() + alphas_bar_sqrt_T = alphas_bar_sqrt[-1].clone() + + # Shift so the last timestep is zero. + alphas_bar_sqrt -= (alphas_bar_sqrt_T) + + # Scale so the first timestep is back to the old value. + alphas_bar_sqrt *= alphas_bar_sqrt_0 / (alphas_bar_sqrt_0 - alphas_bar_sqrt_T) + + # Convert alphas_bar_sqrt to betas + alphas_bar = alphas_bar_sqrt**2 # Revert sqrt + alphas_bar[-1] = 4.8973451890853435e-08 + return alphas_bar + + if hasattr(sd_model, 'alphas_cumprod') and hasattr(sd_model, 'alphas_cumprod_original'): + sd_model.alphas_cumprod = sd_model.alphas_cumprod_original.to(shared.device) + + if opts.use_downcasted_alpha_bar: + if p is not None: + p.extra_generation_params['Downcast alphas_cumprod'] = opts.use_downcasted_alpha_bar + sd_model.alphas_cumprod = sd_model.alphas_cumprod.half().to(shared.device) + if opts.sd_noise_schedule == "Zero Terminal SNR": + if p is not None: + p.extra_generation_params['Noise Schedule'] = opts.sd_noise_schedule + sd_model.alphas_cumprod = rescale_zero_terminal_snr_abar(sd_model.alphas_cumprod).to(shared.device) sd1_clip_weight = 'cond_stage_model.transformer.text_model.embeddings.token_embedding.weight' sd2_clip_weight = 'cond_stage_model.model.transformer.resblocks.0.attn.in_proj_weight' @@ -812,6 +843,7 @@ def reload_model_weights(sd_model=None, info=None, forced_reload=False): sd_model = reuse_model_from_already_loaded(sd_model, checkpoint_info, timer) if not forced_reload and sd_model is not None and sd_model.sd_checkpoint_info.filename == checkpoint_info.filename: + apply_alpha_schedule_override(sd_model) return sd_model if sd_model is not None: From 94f23d00a76e7988f4b73ced1fa2922801e893fb Mon Sep 17 00:00:00 2001 From: drhead <1313496+drhead@users.noreply.github.com> Date: Mon, 26 Feb 2024 23:44:58 -0500 Subject: [PATCH 044/496] move alphas cumprod override out of processing --- modules/processing.py | 28 +--------------------------- 1 file changed, 1 insertion(+), 27 deletions(-) diff --git a/modules/processing.py b/modules/processing.py index d208a922..411c7c3f 100644 --- a/modules/processing.py +++ b/modules/processing.py @@ -915,33 +915,7 @@ def process_images_inner(p: StableDiffusionProcessing) -> Processed: if p.n_iter > 1: shared.state.job = f"Batch {n+1} out of {p.n_iter}" - def rescale_zero_terminal_snr_abar(alphas_cumprod): - alphas_bar_sqrt = alphas_cumprod.sqrt() - - # Store old values. - alphas_bar_sqrt_0 = alphas_bar_sqrt[0].clone() - alphas_bar_sqrt_T = alphas_bar_sqrt[-1].clone() - - # Shift so the last timestep is zero. - alphas_bar_sqrt -= (alphas_bar_sqrt_T) - - # Scale so the first timestep is back to the old value. - alphas_bar_sqrt *= alphas_bar_sqrt_0 / (alphas_bar_sqrt_0 - alphas_bar_sqrt_T) - - # Convert alphas_bar_sqrt to betas - alphas_bar = alphas_bar_sqrt**2 # Revert sqrt - alphas_bar[-1] = 4.8973451890853435e-08 - return alphas_bar - - if hasattr(p.sd_model, 'alphas_cumprod') and hasattr(p.sd_model, 'alphas_cumprod_original'): - p.sd_model.alphas_cumprod = p.sd_model.alphas_cumprod_original.to(shared.device) - - if opts.use_downcasted_alpha_bar: - p.extra_generation_params['Downcast alphas_cumprod'] = opts.use_downcasted_alpha_bar - p.sd_model.alphas_cumprod = p.sd_model.alphas_cumprod.half().to(shared.device) - if opts.sd_noise_schedule == "Zero Terminal SNR": - p.extra_generation_params['Noise Schedule'] = opts.sd_noise_schedule - p.sd_model.alphas_cumprod = rescale_zero_terminal_snr_abar(p.sd_model.alphas_cumprod).to(shared.device) + sd_models.apply_alpha_schedule_override(p.sd_model, p) with devices.without_autocast() if devices.unet_needs_upcast else devices.autocast(): samples_ddim = p.sample(conditioning=p.c, unconditional_conditioning=p.uc, seeds=p.seeds, subseeds=p.subseeds, subseed_strength=p.subseed_strength, prompts=p.prompts) From 4dae91a1fe960ad9a9774f8f5407ef67c1a109f9 Mon Sep 17 00:00:00 2001 From: drhead <1313496+drhead@users.noreply.github.com> Date: Mon, 26 Feb 2024 23:46:10 -0500 Subject: [PATCH 045/496] remove alphas cumprod fix from samplers_common --- modules/sd_samplers_common.py | 2 -- 1 file changed, 2 deletions(-) diff --git a/modules/sd_samplers_common.py b/modules/sd_samplers_common.py index 7ab1bf65..6bd38e12 100644 --- a/modules/sd_samplers_common.py +++ b/modules/sd_samplers_common.py @@ -181,10 +181,8 @@ def apply_refiner(cfg_denoiser): cfg_denoiser.p.extra_generation_params['Refiner'] = refiner_checkpoint_info.short_title cfg_denoiser.p.extra_generation_params['Refiner switch at'] = refiner_switch_at - alphas_cumprod = cfg_denoiser.p.sd_model.alphas_cumprod with sd_models.SkipWritingToConfig(): sd_models.reload_model_weights(info=refiner_checkpoint_info) - cfg_denoiser.p.sd_model.alphas_cumprod = alphas_cumprod devices.torch_gc() cfg_denoiser.p.setup_conds() From 3ba575216a8e7df307562ba8bc68a8717798daef Mon Sep 17 00:00:00 2001 From: Andray Date: Tue, 27 Feb 2024 15:10:51 +0400 Subject: [PATCH 046/496] dat cmd flag --- modules/cmd_args.py | 1 + 1 file changed, 1 insertion(+) diff --git a/modules/cmd_args.py b/modules/cmd_args.py index 312dabff..213cba98 100644 --- a/modules/cmd_args.py +++ b/modules/cmd_args.py @@ -53,6 +53,7 @@ parser.add_argument("--gfpgan-models-path", type=normalized_filepath, help="Path parser.add_argument("--esrgan-models-path", type=normalized_filepath, help="Path to directory with ESRGAN model file(s).", default=os.path.join(models_path, 'ESRGAN')) parser.add_argument("--bsrgan-models-path", type=normalized_filepath, help="Path to directory with BSRGAN model file(s).", default=os.path.join(models_path, 'BSRGAN')) parser.add_argument("--realesrgan-models-path", type=normalized_filepath, help="Path to directory with RealESRGAN model file(s).", default=os.path.join(models_path, 'RealESRGAN')) +parser.add_argument("--dat-models-path", type=normalized_filepath, help="Path to directory with DAT model file(s).", default=os.path.join(models_path, 'DAT')) parser.add_argument("--clip-models-path", type=normalized_filepath, help="Path to directory with CLIP model file(s).", default=None) parser.add_argument("--xformers", action='store_true', help="enable xformers for cross attention layers") parser.add_argument("--force-enable-xformers", action='store_true', help="enable xformers for cross attention layers regardless of whether the checking code thinks you can run it; do not make bug reports if this fails to work") From 44bce3c74ee745b9776d965e02ae006e6b4fe3fb Mon Sep 17 00:00:00 2001 From: Andray Date: Tue, 27 Feb 2024 18:31:36 +0400 Subject: [PATCH 047/496] resize handle for extra networks --- html/extra-networks-pane.html | 6 +++--- javascript/extraNetworks.js | 19 ++++++++++++++++++- javascript/resizeHandle.js | 22 +++++++++++++++++++--- modules/shared_options.py | 1 + modules/ui_extra_networks.py | 9 ++++++++- style.css | 3 ++- 6 files changed, 51 insertions(+), 9 deletions(-) diff --git a/html/extra-networks-pane.html b/html/extra-networks-pane.html index 0c763f71..f54344aa 100644 --- a/html/extra-networks-pane.html +++ b/html/extra-networks-pane.html @@ -44,11 +44,11 @@
    -
    -
    +
    +
    {tree_html}
    -
    +
    {items_html}
    diff --git a/javascript/extraNetworks.js b/javascript/extraNetworks.js index d5855fe9..ff808d7a 100644 --- a/javascript/extraNetworks.js +++ b/javascript/extraNetworks.js @@ -447,7 +447,24 @@ function extraNetworksControlTreeViewOnClick(event, tabname, extra_networks_tabn * @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. */ - gradioApp().getElementById(tabname + "_" + extra_networks_tabname + "_tree").classList.toggle("hidden"); + const tree = gradioApp().getElementById(tabname + "_" + extra_networks_tabname + "_tree"); + const parent = tree.parentElement; + let resizeHandle = parent.querySelector('.resize-handle'); + tree.classList.toggle("hidden"); + + if (tree.classList.contains("hidden")){ + tree.style.display = 'none'; + resizeHandle.style.display = 'none'; + parent.style.display = 'flex'; + } else { + tree.style.display = 'block'; + if (!resizeHandle) { + setupResizeHandle(parent); + resizeHandle = parent.querySelector('.resize-handle'); + } + resizeHandle.style.display = 'block'; + parent.style.display = 'grid'; + } event.currentTarget.classList.toggle("extra-network-control--enabled"); } diff --git a/javascript/resizeHandle.js b/javascript/resizeHandle.js index 6560372c..5fb5dd4f 100644 --- a/javascript/resizeHandle.js +++ b/javascript/resizeHandle.js @@ -39,7 +39,7 @@ 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); R.parentWidth = newParentWidth; @@ -54,7 +54,15 @@ parent.style.display = 'grid'; 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; + } else { + leftColTemplate = parent.children[0].style.flexBasis; + parent.minLeftColWidth = parent.children[0].style.flexBasis.slice(0, -2); + } + const gridTemplateColumns = `${leftColTemplate} ${PAD}px ${parent.children[1].style.flexGrow}fr`; parent.style.gridTemplateColumns = gridTemplateColumns; parent.style.originalGridTemplateColumns = gridTemplateColumns; @@ -119,7 +127,7 @@ } else { 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 - GRADIO_MIN_WIDTH - PAD), R.parent.minLeftColWidth); setLeftColGridTemplate(R.parent, leftColWidth); } }); @@ -165,3 +173,11 @@ onUiLoaded(function() { } } }); + +function setupExtraNetworksResizeHandle() { + for (var elem of document.body.querySelectorAll('.resize-handle-row')) { + if (!elem.querySelector('.resize-handle') && !elem.children[0].classList.contains("hidden")) { + setupResizeHandle(elem); + } + } +} \ No newline at end of file diff --git a/modules/shared_options.py b/modules/shared_options.py index 64f8f196..285c5415 100644 --- a/modules/shared_options.py +++ b/modules/shared_options.py @@ -258,6 +258,7 @@ options_templates.update(options_section(('extra_networks', "Extra Networks", "s "extra_networks_card_order_field": OptionInfo("Path", "Default order field for Extra Networks cards", gr.Dropdown, {"choices": ['Path', 'Name', 'Date Created', 'Date Modified']}).needs_reload_ui(), "extra_networks_card_order": OptionInfo("Ascending", "Default order for Extra Networks cards", gr.Dropdown, {"choices": ['Ascending', 'Descending']}).needs_reload_ui(), "extra_networks_tree_view_default_enabled": OptionInfo(False, "Enables the Extra Networks directory tree view by default").needs_reload_ui(), + "extra_networks_tree_view_min_width": OptionInfo(180, "Minimal width for the Extra Networks directory tree view", gr.Number).needs_reload_ui(), "extra_networks_add_text_separator": OptionInfo(" ", "Extra networks separator").info("extra text to add before <...> when adding extra network to prompt"), "ui_extra_networks_tab_reorder": OptionInfo("", "Extra networks tab order").needs_reload_ui(), "textual_inversion_print_at_load": OptionInfo(False, "Print a list of Textual Inversion embeddings when loading model"), diff --git a/modules/ui_extra_networks.py b/modules/ui_extra_networks.py index 34c46ed4..09705a98 100644 --- a/modules/ui_extra_networks.py +++ b/modules/ui_extra_networks.py @@ -531,9 +531,13 @@ class ExtraNetworksPage: data_sortkey = f"{data_sortmode}-{data_sortdir}-{len(self.items)}" tree_view_btn_extra_class = "" tree_view_div_extra_class = "hidden" + tree_view_div_default_display = "none" + extra_network_pane_content_default_display = "flex" if shared.opts.extra_networks_tree_view_default_enabled: tree_view_btn_extra_class = "extra-network-control--enabled" tree_view_div_extra_class = "" + tree_view_div_default_display = "block" + extra_network_pane_content_default_display = "grid" return self.pane_tpl.format( **{ @@ -546,6 +550,9 @@ class ExtraNetworksPage: "tree_view_div_extra_class": tree_view_div_extra_class, "tree_html": self.create_tree_view_html(tabname), "items_html": self.create_card_view_html(tabname, none_message="Loading..." if empty else None), + "extra_networks_tree_view_min_width": shared.opts.extra_networks_tree_view_min_width, + "tree_view_div_default_display": tree_view_div_default_display, + "extra_network_pane_content_default_display": extra_network_pane_content_default_display, } ) @@ -703,7 +710,7 @@ def create_ui(interface: gr.Blocks, unrelated_tabs, tabname): create_html() return ui.pages_contents - interface.load(fn=pages_html, inputs=[], outputs=ui.pages) + interface.load(fn=pages_html, inputs=[], outputs=ui.pages).then(fn=lambda: None, _js='setupExtraNetworksResizeHandle') return ui diff --git a/style.css b/style.css index 8ce78ff0..004038f8 100644 --- a/style.css +++ b/style.css @@ -1615,9 +1615,10 @@ body.resizing .resize-handle { display: inline-flex; visibility: hidden; color: var(--button-secondary-text-color); - + width: 0; } .extra-network-tree .tree-list-content:hover .button-row { visibility: visible; + width: auto; } From de7604fa77180ac8d51da4f8a59c5a27bbe25cdc Mon Sep 17 00:00:00 2001 From: Andray Date: Tue, 27 Feb 2024 18:38:38 +0400 Subject: [PATCH 048/496] lint --- javascript/extraNetworks.js | 2 +- javascript/resizeHandle.js | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/javascript/extraNetworks.js b/javascript/extraNetworks.js index ff808d7a..4e30261b 100644 --- a/javascript/extraNetworks.js +++ b/javascript/extraNetworks.js @@ -452,7 +452,7 @@ function extraNetworksControlTreeViewOnClick(event, tabname, extra_networks_tabn let resizeHandle = parent.querySelector('.resize-handle'); tree.classList.toggle("hidden"); - if (tree.classList.contains("hidden")){ + if (tree.classList.contains("hidden")) { tree.style.display = 'none'; resizeHandle.style.display = 'none'; parent.style.display = 'flex'; diff --git a/javascript/resizeHandle.js b/javascript/resizeHandle.js index 5fb5dd4f..cf2c778b 100644 --- a/javascript/resizeHandle.js +++ b/javascript/resizeHandle.js @@ -180,4 +180,4 @@ function setupExtraNetworksResizeHandle() { setupResizeHandle(elem); } } -} \ No newline at end of file +} From b4c44e659ba3931d4bee0a0061c674e594cc639f Mon Sep 17 00:00:00 2001 From: Andray Date: Tue, 27 Feb 2024 23:17:52 +0400 Subject: [PATCH 049/496] fix on reload with changed show all loras setting --- javascript/extraNetworks.js | 6 ++++-- javascript/resizeHandle.js | 15 ++++++--------- modules/ui_extra_networks.py | 4 ++-- 3 files changed, 12 insertions(+), 13 deletions(-) diff --git a/javascript/extraNetworks.js b/javascript/extraNetworks.js index 4e30261b..1610698b 100644 --- a/javascript/extraNetworks.js +++ b/javascript/extraNetworks.js @@ -454,16 +454,18 @@ function extraNetworksControlTreeViewOnClick(event, tabname, extra_networks_tabn if (tree.classList.contains("hidden")) { tree.style.display = 'none'; - resizeHandle.style.display = 'none'; parent.style.display = 'flex'; + if (resizeHandle) { + resizeHandle.style.display = 'none'; + } } else { tree.style.display = 'block'; + parent.style.display = 'grid'; if (!resizeHandle) { setupResizeHandle(parent); resizeHandle = parent.querySelector('.resize-handle'); } resizeHandle.style.display = 'block'; - parent.style.display = 'grid'; } event.currentTarget.classList.toggle("extra-network-control--enabled"); } diff --git a/javascript/resizeHandle.js b/javascript/resizeHandle.js index cf2c778b..94ae4aaa 100644 --- a/javascript/resizeHandle.js +++ b/javascript/resizeHandle.js @@ -166,18 +166,15 @@ setupResizeHandle = setup; })(); -onUiLoaded(function() { - for (var elem of gradioApp().querySelectorAll('.resize-handle-row')) { - if (!elem.querySelector('.resize-handle')) { - setupResizeHandle(elem); - } - } -}); -function setupExtraNetworksResizeHandle() { - for (var elem of document.body.querySelectorAll('.resize-handle-row')) { +function setupAllResizeHandles() { + for (var elem of gradioApp().querySelectorAll('.resize-handle-row')) { if (!elem.querySelector('.resize-handle') && !elem.children[0].classList.contains("hidden")) { setupResizeHandle(elem); } } } + + +onUiLoaded(setupAllResizeHandles); + diff --git a/modules/ui_extra_networks.py b/modules/ui_extra_networks.py index 09705a98..9d8f8b28 100644 --- a/modules/ui_extra_networks.py +++ b/modules/ui_extra_networks.py @@ -700,7 +700,7 @@ def create_ui(interface: gr.Blocks, unrelated_tabs, tabname): return ui.pages_contents button_refresh = gr.Button("Refresh", elem_id=f"{tabname}_{page.extra_networks_tabname}_extra_refresh_internal", visible=False) - button_refresh.click(fn=refresh, inputs=[], outputs=ui.pages).then(fn=lambda: None, _js="function(){ " + f"applyExtraNetworkFilter('{tabname}_{page.extra_networks_tabname}');" + " }") + button_refresh.click(fn=refresh, inputs=[], outputs=ui.pages).then(fn=lambda: None, _js="function(){ " + f"applyExtraNetworkFilter('{tabname}_{page.extra_networks_tabname}');" + " }").then(fn=lambda: None, _js='setupAllResizeHandles') def create_html(): ui.pages_contents = [pg.create_html(ui.tabname) for pg in ui.stored_extra_pages] @@ -710,7 +710,7 @@ def create_ui(interface: gr.Blocks, unrelated_tabs, tabname): create_html() return ui.pages_contents - interface.load(fn=pages_html, inputs=[], outputs=ui.pages).then(fn=lambda: None, _js='setupExtraNetworksResizeHandle') + interface.load(fn=pages_html, inputs=[], outputs=ui.pages).then(fn=lambda: None, _js='setupAllResizeHandles') return ui From 51cc1ff2c9d47e66221c7abfb244e4d058c8b279 Mon Sep 17 00:00:00 2001 From: Andray Date: Tue, 27 Feb 2024 23:31:47 +0400 Subject: [PATCH 050/496] fix for mobile and allow collapse right column --- javascript/resizeHandle.js | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/javascript/resizeHandle.js b/javascript/resizeHandle.js index 94ae4aaa..4fe9cbdf 100644 --- a/javascript/resizeHandle.js +++ b/javascript/resizeHandle.js @@ -20,6 +20,9 @@ } function displayResizeHandle(parent) { + if (!parent.needHideOnMoblie) { + return true; + } if (window.innerWidth < GRADIO_MIN_WIDTH * 2 + PAD * 4) { parent.style.display = 'flex'; parent.resizeHandle.style.display = "none"; @@ -58,9 +61,13 @@ 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); + parent.minRightColWidth = 0; + parent.needHideOnMoblie = false; } const gridTemplateColumns = `${leftColTemplate} ${PAD}px ${parent.children[1].style.flexGrow}fr`; parent.style.gridTemplateColumns = gridTemplateColumns; @@ -127,7 +134,7 @@ } else { delta = R.screenX - evt.changedTouches[0].screenX; } - const leftColWidth = Math.max(Math.min(R.leftColStartWidth - delta, R.parent.offsetWidth - GRADIO_MIN_WIDTH - PAD), R.parent.minLeftColWidth); + const leftColWidth = Math.max(Math.min(R.leftColStartWidth - delta, R.parent.offsetWidth - R.parent.minRightColWidth - PAD), R.parent.minLeftColWidth); setLeftColGridTemplate(R.parent, leftColWidth); } }); From bce09eb9871e08fda07b8d6ff78d4d19307574db Mon Sep 17 00:00:00 2001 From: Dalton Date: Thu, 29 Feb 2024 01:04:46 -0500 Subject: [PATCH 051/496] Add a direct link to the binary release --- modules/launch_utils.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/modules/launch_utils.py b/modules/launch_utils.py index 29506f24..d1a086ad 100644 --- a/modules/launch_utils.py +++ b/modules/launch_utils.py @@ -56,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/ -{"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. """) From bb99f5271241565bfd98d2a1fdba59350a5aeb39 Mon Sep 17 00:00:00 2001 From: Andray Date: Thu, 29 Feb 2024 15:40:15 +0400 Subject: [PATCH 052/496] resizeHandle handle double tap --- javascript/resizeHandle.js | 25 +++++++++++++++++++------ 1 file changed, 19 insertions(+), 6 deletions(-) diff --git a/javascript/resizeHandle.js b/javascript/resizeHandle.js index 6560372c..c4e9de58 100644 --- a/javascript/resizeHandle.js +++ b/javascript/resizeHandle.js @@ -2,6 +2,7 @@ const GRADIO_MIN_WIDTH = 320; const PAD = 16; const DEBOUNCE_TIME = 100; + const DOUBLE_TAP_DELAY = 200; //ms const R = { tracking: false, @@ -10,6 +11,7 @@ leftCol: null, leftColStartWidth: null, screenX: null, + lastTapTime: null, }; let resizeTimer; @@ -47,6 +49,14 @@ } function setup(parent) { + + function onDoubleClick(evt) { + evt.preventDefault(); + evt.stopPropagation(); + + parent.style.gridTemplateColumns = parent.style.originalGridTemplateColumns; + } + const leftCol = parent.firstElementChild; const rightCol = parent.lastElementChild; @@ -69,6 +79,14 @@ if (evt.button !== 0) return; } else { if (evt.changedTouches.length !== 1) return; + + const currentTime = new Date().getTime(); + if (R.lastTapTime && currentTime - R.lastTapTime <= DOUBLE_TAP_DELAY) { + onDoubleClick(evt); + return; + } + + R.lastTapTime = currentTime; } evt.preventDefault(); @@ -89,12 +107,7 @@ }); }); - resizeHandle.addEventListener('dblclick', (evt) => { - evt.preventDefault(); - evt.stopPropagation(); - - parent.style.gridTemplateColumns = parent.style.originalGridTemplateColumns; - }); + resizeHandle.addEventListener('dblclick', onDoubleClick); afterResize(parent); } From eb0b84c5643896385ba6dd242c6815b288618355 Mon Sep 17 00:00:00 2001 From: Andray Date: Thu, 29 Feb 2024 16:02:21 +0400 Subject: [PATCH 053/496] make minimal width 2 times smaller then default --- html/extra-networks-pane.html | 2 +- javascript/resizeHandle.js | 2 +- modules/shared_options.py | 2 +- modules/ui_extra_networks.py | 2 +- 4 files changed, 4 insertions(+), 4 deletions(-) diff --git a/html/extra-networks-pane.html b/html/extra-networks-pane.html index f54344aa..02a87108 100644 --- a/html/extra-networks-pane.html +++ b/html/extra-networks-pane.html @@ -45,7 +45,7 @@
    -
    +
    {tree_html}
    diff --git a/javascript/resizeHandle.js b/javascript/resizeHandle.js index 4fe9cbdf..513198f5 100644 --- a/javascript/resizeHandle.js +++ b/javascript/resizeHandle.js @@ -65,7 +65,7 @@ parent.needHideOnMoblie = true; } else { leftColTemplate = parent.children[0].style.flexBasis; - parent.minLeftColWidth = parent.children[0].style.flexBasis.slice(0, -2); + parent.minLeftColWidth = parent.children[0].style.flexBasis.slice(0, -2) / 2; parent.minRightColWidth = 0; parent.needHideOnMoblie = false; } diff --git a/modules/shared_options.py b/modules/shared_options.py index 285c5415..aa26588d 100644 --- a/modules/shared_options.py +++ b/modules/shared_options.py @@ -258,7 +258,7 @@ options_templates.update(options_section(('extra_networks', "Extra Networks", "s "extra_networks_card_order_field": OptionInfo("Path", "Default order field for Extra Networks cards", gr.Dropdown, {"choices": ['Path', 'Name', 'Date Created', 'Date Modified']}).needs_reload_ui(), "extra_networks_card_order": OptionInfo("Ascending", "Default order for Extra Networks cards", gr.Dropdown, {"choices": ['Ascending', 'Descending']}).needs_reload_ui(), "extra_networks_tree_view_default_enabled": OptionInfo(False, "Enables the Extra Networks directory tree view by default").needs_reload_ui(), - "extra_networks_tree_view_min_width": OptionInfo(180, "Minimal width for the Extra Networks directory tree view", gr.Number).needs_reload_ui(), + "extra_networks_tree_view_default_width": OptionInfo(180, "Default width for the Extra Networks directory tree view", gr.Number).needs_reload_ui(), "extra_networks_add_text_separator": OptionInfo(" ", "Extra networks separator").info("extra text to add before <...> when adding extra network to prompt"), "ui_extra_networks_tab_reorder": OptionInfo("", "Extra networks tab order").needs_reload_ui(), "textual_inversion_print_at_load": OptionInfo(False, "Print a list of Textual Inversion embeddings when loading model"), diff --git a/modules/ui_extra_networks.py b/modules/ui_extra_networks.py index 9d8f8b28..ad2c2305 100644 --- a/modules/ui_extra_networks.py +++ b/modules/ui_extra_networks.py @@ -550,7 +550,7 @@ class ExtraNetworksPage: "tree_view_div_extra_class": tree_view_div_extra_class, "tree_html": self.create_tree_view_html(tabname), "items_html": self.create_card_view_html(tabname, none_message="Loading..." if empty else None), - "extra_networks_tree_view_min_width": shared.opts.extra_networks_tree_view_min_width, + "extra_networks_tree_view_default_width": shared.opts.extra_networks_tree_view_default_width, "tree_view_div_default_display": tree_view_div_default_display, "extra_network_pane_content_default_display": extra_network_pane_content_default_display, } From 1a51b166a04245f5e2ccdfc1300be3be79345bc3 Mon Sep 17 00:00:00 2001 From: AUTOMATIC1111 <16777216c@gmail.com> Date: Sat, 2 Mar 2024 06:53:53 +0300 Subject: [PATCH 054/496] call apply_alpha_schedule_override in load_model_weights for #14979 --- modules/sd_models.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/modules/sd_models.py b/modules/sd_models.py index fbd53adb..db72e120 100644 --- a/modules/sd_models.py +++ b/modules/sd_models.py @@ -428,6 +428,8 @@ def load_model_weights(model, checkpoint_info: CheckpointInfo, state_dict, timer devices.dtype_unet = torch.float16 timer.record("apply half()") + apply_alpha_schedule_override(model) + for module in model.modules(): if hasattr(module, 'fp16_weight'): del module.fp16_weight @@ -843,7 +845,6 @@ def reload_model_weights(sd_model=None, info=None, forced_reload=False): sd_model = reuse_model_from_already_loaded(sd_model, checkpoint_info, timer) if not forced_reload and sd_model is not None and sd_model.sd_checkpoint_info.filename == checkpoint_info.filename: - apply_alpha_schedule_override(sd_model) return sd_model if sd_model is not None: From ee470cc6a32ae0c89ca32d71adac02b2d434f59a Mon Sep 17 00:00:00 2001 From: AUTOMATIC1111 <16777216c@gmail.com> Date: Sat, 2 Mar 2024 06:54:11 +0300 Subject: [PATCH 055/496] style changes for #14979 --- modules/sd_models.py | 60 ++++++++++++++++++++++++++------------------ 1 file changed, 36 insertions(+), 24 deletions(-) diff --git a/modules/sd_models.py b/modules/sd_models.py index db72e120..747fc39e 100644 --- a/modules/sd_models.py +++ b/modules/sd_models.py @@ -552,36 +552,48 @@ def repair_config(sd_config): karlo_path = os.path.join(paths.models_path, 'karlo') sd_config.model.params.noise_aug_config.params.clip_stats_path = sd_config.model.params.noise_aug_config.params.clip_stats_path.replace("checkpoints/karlo_models", karlo_path) + +def rescale_zero_terminal_snr_abar(alphas_cumprod): + alphas_bar_sqrt = alphas_cumprod.sqrt() + + # Store old values. + alphas_bar_sqrt_0 = alphas_bar_sqrt[0].clone() + alphas_bar_sqrt_T = alphas_bar_sqrt[-1].clone() + + # Shift so the last timestep is zero. + alphas_bar_sqrt -= (alphas_bar_sqrt_T) + + # Scale so the first timestep is back to the old value. + alphas_bar_sqrt *= alphas_bar_sqrt_0 / (alphas_bar_sqrt_0 - alphas_bar_sqrt_T) + + # Convert alphas_bar_sqrt to betas + alphas_bar = alphas_bar_sqrt ** 2 # Revert sqrt + alphas_bar[-1] = 4.8973451890853435e-08 + return alphas_bar + + def apply_alpha_schedule_override(sd_model, p=None): - def rescale_zero_terminal_snr_abar(alphas_cumprod): - alphas_bar_sqrt = alphas_cumprod.sqrt() + """ + Applies an override to the alpha schedule of the model according to settings. + - downcasts the alpha schedule to half precision + - rescales the alpha schedule to have zero terminal SNR + """ - # Store old values. - alphas_bar_sqrt_0 = alphas_bar_sqrt[0].clone() - alphas_bar_sqrt_T = alphas_bar_sqrt[-1].clone() + if not hasattr(sd_model, 'alphas_cumprod') or not hasattr(sd_model, 'alphas_cumprod_original'): + return - # Shift so the last timestep is zero. - alphas_bar_sqrt -= (alphas_bar_sqrt_T) + sd_model.alphas_cumprod = sd_model.alphas_cumprod_original.to(shared.device) - # Scale so the first timestep is back to the old value. - alphas_bar_sqrt *= alphas_bar_sqrt_0 / (alphas_bar_sqrt_0 - alphas_bar_sqrt_T) + if opts.use_downcasted_alpha_bar: + if p is not None: + p.extra_generation_params['Downcast alphas_cumprod'] = opts.use_downcasted_alpha_bar + sd_model.alphas_cumprod = sd_model.alphas_cumprod.half().to(shared.device) - # Convert alphas_bar_sqrt to betas - alphas_bar = alphas_bar_sqrt**2 # Revert sqrt - alphas_bar[-1] = 4.8973451890853435e-08 - return alphas_bar + if opts.sd_noise_schedule == "Zero Terminal SNR": + if p is not None: + p.extra_generation_params['Noise Schedule'] = opts.sd_noise_schedule + sd_model.alphas_cumprod = rescale_zero_terminal_snr_abar(sd_model.alphas_cumprod).to(shared.device) - if hasattr(sd_model, 'alphas_cumprod') and hasattr(sd_model, 'alphas_cumprod_original'): - sd_model.alphas_cumprod = sd_model.alphas_cumprod_original.to(shared.device) - - if opts.use_downcasted_alpha_bar: - if p is not None: - p.extra_generation_params['Downcast alphas_cumprod'] = opts.use_downcasted_alpha_bar - sd_model.alphas_cumprod = sd_model.alphas_cumprod.half().to(shared.device) - if opts.sd_noise_schedule == "Zero Terminal SNR": - if p is not None: - p.extra_generation_params['Noise Schedule'] = opts.sd_noise_schedule - sd_model.alphas_cumprod = rescale_zero_terminal_snr_abar(sd_model.alphas_cumprod).to(shared.device) sd1_clip_weight = 'cond_stage_model.transformer.text_model.embeddings.token_embedding.weight' sd2_clip_weight = 'cond_stage_model.model.transformer.resblocks.0.attn.in_proj_weight' From bb24c13ed7910e9e6255e3d7ff3d81ba40468fc0 Mon Sep 17 00:00:00 2001 From: AUTOMATIC1111 <16777216c@gmail.com> Date: Sat, 2 Mar 2024 07:39:59 +0300 Subject: [PATCH 056/496] infotext support for #14978 --- modules/infotext_utils.py | 3 +++ modules/infotext_versions.py | 3 +++ modules/sd_samplers_common.py | 8 +++++--- 3 files changed, 11 insertions(+), 3 deletions(-) diff --git a/modules/infotext_utils.py b/modules/infotext_utils.py index a938aa2a..e04a7bee 100644 --- a/modules/infotext_utils.py +++ b/modules/infotext_utils.py @@ -359,6 +359,9 @@ Steps: 20, Sampler: Euler a, CFG scale: 7, Seed: 965400086, Size: 512x512, Model if "Emphasis" not in res: res["Emphasis"] = "Original" + if "Refiner switch by sampling steps" not in res: + res["Refiner switch by sampling steps"] = False + infotext_versions.backcompat(res) for key in skip_fields: diff --git a/modules/infotext_versions.py b/modules/infotext_versions.py index 23b45c3f..b5552a31 100644 --- a/modules/infotext_versions.py +++ b/modules/infotext_versions.py @@ -5,6 +5,7 @@ import re v160 = version.parse("1.6.0") v170_tsnr = version.parse("v1.7.0-225") +v180 = version.parse("1.8.0") def parse_version(text): @@ -40,3 +41,5 @@ def backcompat(d): if ver < v170_tsnr: d["Downcast alphas_cumprod"] = True + if ver < v180 and d.get('Refiner'): + d["Refiner switch by sampling steps"] = True diff --git a/modules/sd_samplers_common.py b/modules/sd_samplers_common.py index 045b9e2f..6df42391 100644 --- a/modules/sd_samplers_common.py +++ b/modules/sd_samplers_common.py @@ -155,14 +155,16 @@ def replace_torchsde_browinan(): replace_torchsde_browinan() -def apply_refiner(cfg_denoiser, sigma): - if opts.refiner_switch_by_sample_steps: +def apply_refiner(cfg_denoiser, sigma=None): + if opts.refiner_switch_by_sample_steps or not sigma: completed_ratio = cfg_denoiser.step / cfg_denoiser.total_steps + cfg_denoiser.p.extra_generation_params["Refiner switch by sampling steps"] = True + else: # torch.max(sigma) only to handle rare case where we might have different sigmas in the same batch try: timestep = torch.argmin(torch.abs(cfg_denoiser.inner_model.sigmas - torch.max(sigma))) - except AttributeError: # for samplers that dont use sigmas (DDIM) sigma is actually the timestep + except AttributeError: # for samplers that don't use sigmas (DDIM) sigma is actually the timestep timestep = torch.max(sigma).to(dtype=int) completed_ratio = (999 - timestep) / 1000 From 45b8a499a7e6d732b1711a0016c211f2b3c19232 Mon Sep 17 00:00:00 2001 From: AUTOMATIC1111 <16777216c@gmail.com> Date: Sat, 2 Mar 2024 10:36:48 +0300 Subject: [PATCH 057/496] fix wrong condition --- modules/sd_samplers_common.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/modules/sd_samplers_common.py b/modules/sd_samplers_common.py index 6df42391..bda578cc 100644 --- a/modules/sd_samplers_common.py +++ b/modules/sd_samplers_common.py @@ -156,7 +156,7 @@ replace_torchsde_browinan() def apply_refiner(cfg_denoiser, sigma=None): - if opts.refiner_switch_by_sample_steps or not sigma: + if opts.refiner_switch_by_sample_steps or sigma is None: completed_ratio = cfg_denoiser.step / cfg_denoiser.total_steps cfg_denoiser.p.extra_generation_params["Refiner switch by sampling steps"] = True From 01033656975cd0622aa3711352101751dfa6b1c3 Mon Sep 17 00:00:00 2001 From: Andray Date: Sun, 3 Mar 2024 16:54:58 +0400 Subject: [PATCH 058/496] fix_jpeg_live_preview --- modules/shared_state.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/modules/shared_state.py b/modules/shared_state.py index 33996691..759a4748 100644 --- a/modules/shared_state.py +++ b/modules/shared_state.py @@ -162,5 +162,7 @@ class State: errors.record_exception() def assign_current_image(self, image): + if shared.opts.live_previews_image_format == 'jpeg' and image.mode == 'RGBA': + image = image.convert('RGB') self.current_image = image self.id_live_preview += 1 From 3c0177a24b496be5b643b76348afe6a5ff30a59f Mon Sep 17 00:00:00 2001 From: Christopher Layne Date: Sat, 2 Mar 2024 08:00:20 -0800 Subject: [PATCH 059/496] 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. --- modules/upscaler_utils.py | 2 -- 1 file changed, 2 deletions(-) diff --git a/modules/upscaler_utils.py b/modules/upscaler_utils.py index b5e5a80c..17223ca0 100644 --- a/modules/upscaler_utils.py +++ b/modules/upscaler_utils.py @@ -69,10 +69,8 @@ def upscale_with_model( for y, h, row in grid.tiles: newrow = [] for x, w, tile in row: - logger.debug("Tile (%d, %d) %s...", x, y, tile) output = upscale_pil_patch(model, tile) scale_factor = output.width // tile.width - logger.debug("=> %s (scale factor %s)", output, scale_factor) newrow.append([x * scale_factor, w * scale_factor, output]) p.update(1) newtiles.append([y * scale_factor, h * scale_factor, newrow]) From e3fa46f26f78c01969eaca2708be4e2b4928c5a2 Mon Sep 17 00:00:00 2001 From: Aarni Koskela Date: Mon, 4 Mar 2024 08:37:23 +0200 Subject: [PATCH 060/496] Fix various typos with crate-ci/typos --- CHANGELOG.md | 18 +++++++++--------- _typos.toml | 5 +++++ extensions-builtin/LDSR/sd_hijack_ddpm_v1.py | 8 ++++---- extensions-builtin/Lora/lyco_helpers.py | 2 +- extensions-builtin/Lora/networks.py | 2 +- .../canvas-zoom-and-pan/javascript/zoom.js | 4 ++-- .../scripts/hotkey_config.py | 4 ++-- .../soft-inpainting/scripts/soft_inpainting.py | 2 +- javascript/aspectRatioOverlay.js | 12 ++++++------ javascript/extraNetworks.js | 2 +- javascript/ui.js | 2 +- modules/api/api.py | 6 +++--- modules/call_queue.py | 4 ++-- modules/devices.py | 2 +- modules/extra_networks.py | 2 +- modules/initialize.py | 2 +- modules/mac_specific.py | 2 +- modules/modelloader.py | 6 +++--- modules/models/diffusion/ddpm_edit.py | 8 ++++---- modules/rng.py | 4 ++-- modules/scripts.py | 4 ++-- modules/sd_emphasis.py | 4 ++-- modules/sd_hijack_clip.py | 8 ++++---- modules/sd_models.py | 2 +- modules/shared.py | 2 +- modules/shared_options.py | 4 ++-- modules/shared_state.py | 2 +- modules/textual_inversion/autocrop.py | 4 ++-- modules/textual_inversion/image_embedding.py | 6 +++--- modules/textual_inversion/textual_inversion.py | 2 +- modules/ui_common.py | 2 +- modules/ui_components.py | 2 +- modules/ui_extensions.py | 2 +- modules/ui_prompt_styles.py | 2 +- scripts/outpainting_mk_2.py | 2 +- scripts/xyz_grid.py | 2 +- 36 files changed, 76 insertions(+), 71 deletions(-) create mode 100644 _typos.toml diff --git a/CHANGELOG.md b/CHANGELOG.md index f0c65981..0df47801 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -14,7 +14,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)) * 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)) -* Propmpt comments support +* Prompt comments support ### Minor: * Allow pasting in WIDTHxHEIGHT strings into the width/height fields ([#14296](https://github.com/AUTOMATIC1111/stable-diffusion-webui/pull/14296)) @@ -59,7 +59,7 @@ * modules/api/api.py: add api endpoint to refresh embeddings list ([#14715](https://github.com/AUTOMATIC1111/stable-diffusion-webui/pull/14715)) * set_named_arg ([#14773](https://github.com/AUTOMATIC1111/stable-diffusion-webui/pull/14773)) * add before_token_counter callback and use it for prompt comments -* ResizeHandleRow - allow 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 * 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)) @@ -101,7 +101,7 @@ * Gracefully handle mtime read exception from cache ([#14933](https://github.com/AUTOMATIC1111/stable-diffusion-webui/pull/14933)) * Only trigger interrupt on `Esc` when interrupt button visible ([#14932](https://github.com/AUTOMATIC1111/stable-diffusion-webui/pull/14932)) * Disable prompt token counters option actually disables token counting rather than just hiding results. -* avoid 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: 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)) @@ -171,7 +171,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 * 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)) -* allow use of mutiple styles csv files ([#14125](https://github.com/AUTOMATIC1111/stable-diffusion-webui/pull/14125)) +* allow use of multiple styles csv files ([#14125](https://github.com/AUTOMATIC1111/stable-diffusion-webui/pull/14125)) * make extra network card description plaintext by default, with an option (Treat card description as HTML) to re-enable HTML as it was (originally by [#13241](https://github.com/AUTOMATIC1111/stable-diffusion-webui/pull/13241)) ### Extensions and API: @@ -308,7 +308,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)) * rework DDIM, PLMS, UniPC to use CFG denoiser same as in k-diffusion samplers: * makes all of them work with img2img - * makes prompt composition posssible (AND) + * makes prompt composition possible (AND) * makes them available for SDXL * 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)) @@ -484,7 +484,7 @@ * user metadata system for custom networks * 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) - * show github stars for extenstions + * show github stars for extensions * img2img batch mode can read extra stuff from png info * img2img batch works with subdirectories * hotkeys to move prompt elements: alt+left/right @@ -703,7 +703,7 @@ * do not wait for Stable Diffusion model to load at startup * add filename patterns: `[denoising]` * 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: 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) @@ -733,7 +733,7 @@ * fix gamepad navigation * make the lightbox fullscreen image function properly * 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 bug with upscalers not working properly * fix MPS on PyTorch 2.0.1, Intel Macs @@ -751,7 +751,7 @@ * switch to PyTorch 2.0.0 (except for AMD GPUs) * visual improvements to custom code scripts * 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 * 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 diff --git a/_typos.toml b/_typos.toml new file mode 100644 index 00000000..1c63fe70 --- /dev/null +++ b/_typos.toml @@ -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" diff --git a/extensions-builtin/LDSR/sd_hijack_ddpm_v1.py b/extensions-builtin/LDSR/sd_hijack_ddpm_v1.py index 04adc5eb..9a1e0778 100644 --- a/extensions-builtin/LDSR/sd_hijack_ddpm_v1.py +++ b/extensions-builtin/LDSR/sd_hijack_ddpm_v1.py @@ -301,7 +301,7 @@ class DDPMV1(pl.LightningModule): elif self.parameterization == "x0": target = x_start 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]) @@ -880,7 +880,7 @@ class LatentDiffusionV1(DDPMV1): def apply_model(self, x_noisy, t, cond, return_ids=False): if isinstance(cond, dict): - # hybrid case, cond is exptected to be a dict + # hybrid case, cond is expected to be a dict pass else: 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])] 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 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 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) 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) diff --git a/extensions-builtin/Lora/lyco_helpers.py b/extensions-builtin/Lora/lyco_helpers.py index 1679a0ce..6f134d54 100644 --- a/extensions-builtin/Lora/lyco_helpers.py +++ b/extensions-builtin/Lora/lyco_helpers.py @@ -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. secon value is a value for weight. - Becuase of non-commutative property, A⊗B ≠ B⊗A. Meaning of two matrices is slightly different. + Because of non-commutative property, A⊗B ≠ B⊗A. Meaning of two matrices is slightly different. examples) factor diff --git a/extensions-builtin/Lora/networks.py b/extensions-builtin/Lora/networks.py index 83ea2802..04bd1911 100644 --- a/extensions-builtin/Lora/networks.py +++ b/extensions-builtin/Lora/networks.py @@ -355,7 +355,7 @@ def network_apply_weights(self: Union[torch.nn.Conv2d, torch.nn.Linear, torch.nn """ 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 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) diff --git a/extensions-builtin/canvas-zoom-and-pan/javascript/zoom.js b/extensions-builtin/canvas-zoom-and-pan/javascript/zoom.js index df60c1a1..64e7a638 100644 --- a/extensions-builtin/canvas-zoom-and-pan/javascript/zoom.js +++ b/extensions-builtin/canvas-zoom-and-pan/javascript/zoom.js @@ -292,7 +292,7 @@ onUiLoaded(async() => { // Create tooltip function createTooltip() { - const toolTipElemnt = + const toolTipElement = targetElement.querySelector(".image-container"); const tooltip = document.createElement("div"); tooltip.className = "canvas-tooltip"; @@ -355,7 +355,7 @@ onUiLoaded(async() => { tooltip.appendChild(tooltipContent); // Add a hint element to the target element - toolTipElemnt.appendChild(tooltip); + toolTipElement.appendChild(tooltip); } //Show tool tip if setting enable diff --git a/extensions-builtin/canvas-zoom-and-pan/scripts/hotkey_config.py b/extensions-builtin/canvas-zoom-and-pan/scripts/hotkey_config.py index 89b7c31f..17b27b27 100644 --- a/extensions-builtin/canvas-zoom-and-pan/scripts/hotkey_config.py +++ b/extensions-builtin/canvas-zoom-and-pan/scripts/hotkey_config.py @@ -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_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_reset": shared.OptionInfo("R", "Reset zoom and canvas positon"), - "canvas_hotkey_overlap": shared.OptionInfo("O", "Toggle overlap").info("Technical button, neededs for testing"), + "canvas_hotkey_reset": shared.OptionInfo("R", "Reset zoom and canvas position"), + "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_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"), diff --git a/extensions-builtin/soft-inpainting/scripts/soft_inpainting.py b/extensions-builtin/soft-inpainting/scripts/soft_inpainting.py index d9024344..d4cf3fda 100644 --- a/extensions-builtin/soft-inpainting/scripts/soft_inpainting.py +++ b/extensions-builtin/soft-inpainting/scripts/soft_inpainting.py @@ -104,7 +104,7 @@ def latent_blend(settings, a, b, t): 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. Where: diff --git a/javascript/aspectRatioOverlay.js b/javascript/aspectRatioOverlay.js index 2cf2d571..c8751fe4 100644 --- a/javascript/aspectRatioOverlay.js +++ b/javascript/aspectRatioOverlay.js @@ -50,17 +50,17 @@ function dimensionChange(e, is_width, is_height) { var scaledx = targetElement.naturalWidth * viewportscale; var scaledy = targetElement.naturalHeight * viewportscale; - var cleintRectTop = (viewportOffset.top + window.scrollY); - var cleintRectLeft = (viewportOffset.left + window.scrollX); - var cleintRectCentreY = cleintRectTop + (targetElement.clientHeight / 2); - var cleintRectCentreX = cleintRectLeft + (targetElement.clientWidth / 2); + var clientRectTop = (viewportOffset.top + window.scrollY); + var clientRectLeft = (viewportOffset.left + window.scrollX); + var clientRectCentreY = clientRectTop + (targetElement.clientHeight / 2); + var clientRectCentreX = clientRectLeft + (targetElement.clientWidth / 2); var arscale = Math.min(scaledx / currentWidth, scaledy / currentHeight); var arscaledx = currentWidth * arscale; var arscaledy = currentHeight * arscale; - var arRectTop = cleintRectCentreY - (arscaledy / 2); - var arRectLeft = cleintRectCentreX - (arscaledx / 2); + var arRectTop = clientRectCentreY - (arscaledy / 2); + var arRectLeft = clientRectCentreX - (arscaledx / 2); var arRectWidth = arscaledx; var arRectHeight = arscaledy; diff --git a/javascript/extraNetworks.js b/javascript/extraNetworks.js index 1610698b..c21433db 100644 --- a/javascript/extraNetworks.js +++ b/javascript/extraNetworks.js @@ -290,7 +290,7 @@ function extraNetworksTreeProcessDirectoryClick(event, btn, tabname, extra_netwo * Processes `onclick` events when user clicks on directories in tree. * * 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. * selected opened directory: Directory is collapsed and deselected. * chevron is clicked: Directory is expanded or collapsed. Selected state unchanged. diff --git a/javascript/ui.js b/javascript/ui.js index 3d079b3d..1eef6d33 100644 --- a/javascript/ui.js +++ b/javascript/ui.js @@ -411,7 +411,7 @@ function switchWidthHeight(tabname) { 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) { var edited = function() { var existingTimer = onEditTimers[editId]; diff --git a/modules/api/api.py b/modules/api/api.py index 4e656082..78ff70df 100644 --- a/modules/api/api.py +++ b/modules/api/api.py @@ -360,7 +360,7 @@ class Api: return script_args 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. @@ -409,8 +409,8 @@ class Api: if request.override_settings is None: request.override_settings = {} - overriden_settings = infotext_utils.get_override_settings(params) - for _, setting_name, value in overriden_settings: + overridden_settings = infotext_utils.get_override_settings(params) + for _, setting_name, value in overridden_settings: if setting_name not in request.override_settings: request.override_settings[setting_name] = value diff --git a/modules/call_queue.py b/modules/call_queue.py index bcd7c546..b50931bc 100644 --- a/modules/call_queue.py +++ b/modules/call_queue.py @@ -100,8 +100,8 @@ def wrap_gradio_call(func, extra_outputs=None, add_stats=False): sys_pct = sys_peak/max(sys_total, 1) * 100 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_sys = "System: peak amout of video memory allocated by all running programs, out of total capacity" + toltip_r = "Reserved: total amount of video memory allocated by the Torch library " + toltip_sys = "System: peak amount of video memory allocated by all running programs, out of total capacity" text_a = f"A: {active_peak/1024:.2f} GB" text_r = f"R: {reserved_peak/1024:.2f} GB" diff --git a/modules/devices.py b/modules/devices.py index 28c0c54d..e4f671ac 100644 --- a/modules/devices.py +++ b/modules/devices.py @@ -259,7 +259,7 @@ def test_for_nans(x, where): def first_time_calculation(): """ just do any calculation with pytorch layers - the first time this is done it allocaltes 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) diff --git a/modules/extra_networks.py b/modules/extra_networks.py index 04249dff..ae8d42d9 100644 --- a/modules/extra_networks.py +++ b/modules/extra_networks.py @@ -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 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. Can be called multiple times before deactivate() - each new call should override the previous call completely. diff --git a/modules/initialize.py b/modules/initialize.py index f7313ff4..08ad4c0b 100644 --- a/modules/initialize.py +++ b/modules/initialize.py @@ -139,7 +139,7 @@ def initialize_rest(*, reload_script_modules=False): """ Accesses shared.sd_model property to load model. 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. """ from modules import devices diff --git a/modules/mac_specific.py b/modules/mac_specific.py index d96d86d7..039689f3 100644 --- a/modules/mac_specific.py +++ b/modules/mac_specific.py @@ -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+, # 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 def check_for_mps() -> bool: if version.parse(torch.__version__) <= version.parse("2.0.1"): diff --git a/modules/modelloader.py b/modules/modelloader.py index e100bb24..115415c8 100644 --- a/modules/modelloader.py +++ b/modules/modelloader.py @@ -110,7 +110,7 @@ def load_upscalers(): except Exception: pass - datas = [] + data = [] commandline_options = vars(shared.cmd_opts) # some of upscaler classes will not go away after reloading their modules, and we'll end @@ -129,10 +129,10 @@ def load_upscalers(): scaler = cls(commandline_model_path) scaler.user_path = commandline_model_path scaler.model_download_path = commandline_model_path or scaler.model_path - datas += scaler.scalers + data += scaler.scalers shared.sd_upscalers = sorted( - datas, + data, # 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 "" ) diff --git a/modules/models/diffusion/ddpm_edit.py b/modules/models/diffusion/ddpm_edit.py index 6db340da..7b51c83c 100644 --- a/modules/models/diffusion/ddpm_edit.py +++ b/modules/models/diffusion/ddpm_edit.py @@ -341,7 +341,7 @@ class DDPM(pl.LightningModule): elif self.parameterization == "x0": target = x_start 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]) @@ -901,7 +901,7 @@ class LatentDiffusion(DDPM): def apply_model(self, x_noisy, t, cond, return_ids=False): if isinstance(cond, dict): - # hybrid case, cond is exptected to be a dict + # hybrid case, cond is expected to be a dict pass else: 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])] 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 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 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) 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) diff --git a/modules/rng.py b/modules/rng.py index 8934d39b..5390d1bb 100644 --- a/modules/rng.py +++ b/modules/rng.py @@ -34,7 +34,7 @@ def randn_local(seed, shape): 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.""" @@ -48,7 +48,7 @@ def randn_like(x): 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.""" diff --git a/modules/scripts.py b/modules/scripts.py index 94690a22..77f5e4f3 100644 --- a/modules/scripts.py +++ b/modules/scripts.py @@ -92,7 +92,7 @@ class Script: """If true, the script setup will only be run in Gradio UI, not in API""" controls = None - """A list of controls retured by the ui().""" + """A list of controls returned by the ui().""" def title(self): """this function should return the title of the script. This is what will be displayed in the dropdown menu.""" @@ -109,7 +109,7 @@ class Script: 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: - False if the script should not be shown in UI at all diff --git a/modules/sd_emphasis.py b/modules/sd_emphasis.py index 654817b6..49ef1a6a 100644 --- a/modules/sd_emphasis.py +++ b/modules/sd_emphasis.py @@ -35,7 +35,7 @@ class EmphasisIgnore(Emphasis): class EmphasisOriginal(Emphasis): name = "Original" - description = "the orginal emphasis implementation" + description = "the original emphasis implementation" def after_transformers(self): original_mean = self.z.mean() @@ -48,7 +48,7 @@ class EmphasisOriginal(Emphasis): class EmphasisOriginalNoNorm(EmphasisOriginal): 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): self.z = self.z * self.multipliers.reshape(self.multipliers.shape + (1,)).expand(self.z.shape) diff --git a/modules/sd_hijack_clip.py b/modules/sd_hijack_clip.py index 98350ac4..81c60f48 100644 --- a/modules/sd_hijack_clip.py +++ b/modules/sd_hijack_clip.py @@ -23,7 +23,7 @@ class PromptChunk: 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 -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.""" @@ -66,7 +66,7 @@ class FrozenCLIPEmbedderWithCustomWordsBase(torch.nn.Module): 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. 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. @@ -136,7 +136,7 @@ class FrozenCLIPEmbedderWithCustomWordsBase(torch.nn.Module): if token == self.comma_token: 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. 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 @@ -206,7 +206,7 @@ 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. 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. - 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" """ diff --git a/modules/sd_models.py b/modules/sd_models.py index 747fc39e..b35aecbc 100644 --- a/modules/sd_models.py +++ b/modules/sd_models.py @@ -784,7 +784,7 @@ def reuse_model_from_already_loaded(sd_model, checkpoint_info, timer): If it is loaded, returns that (moving it to GPU if necessary, and moving the currently loadded model to CPU if necessary). If not, returns the model that can be used to load weights from checkpoint_info's file. If no such model exists, returns None. - Additionaly deletes loaded models that are over the limit set in settings (sd_checkpoints_limit). + Additionally deletes loaded models that are over the limit set in settings (sd_checkpoints_limit). """ already_loaded = None diff --git a/modules/shared.py b/modules/shared.py index ccdca4e7..b4ba14ad 100644 --- a/modules/shared.py +++ b/modules/shared.py @@ -43,7 +43,7 @@ restricted_opts = None sd_model: sd_models_types.WebuiSdModel = None settings_components = None -"""assinged from ui.py, a mapping on setting names to gradio components repsponsible for those settings""" +"""assigned from ui.py, a mapping on setting names to gradio components repsponsible for those settings""" tab_names = [] diff --git a/modules/shared_options.py b/modules/shared_options.py index 073454c6..536766db 100644 --- a/modules/shared_options.py +++ b/modules/shared_options.py @@ -213,7 +213,7 @@ options_templates.update(options_section(('optimizations', "Optimizations", "sd" "pad_cond_uncond": OptionInfo(False, "Pad prompt/negative prompt", infotext='Pad conds').info("improves performance when prompt and negative prompt have different lengths; changes seeds"), "pad_cond_uncond_v0": OptionInfo(False, "Pad prompt/negative prompt (v0)", infotext='Pad conds v0').info("alternative implementation for the above; used prior to 1.6.0 for DDIM sampler; overrides the above if set; WARNING: truncates negative prompt if it's too long; changes seeds"), "persistent_cond_cache": OptionInfo(True, "Persistent cond cache").info("do not recalculate conds from prompts if prompts have not changed since previous calculation"), - "batch_cond_uncond": OptionInfo(True, "Batch cond/uncond").info("do both conditional and unconditional denoising in one batch; uses a bit more VRAM during sampling, but improves speed; previously this was controlled by --always-batch-cond-uncond comandline argument"), + "batch_cond_uncond": OptionInfo(True, "Batch cond/uncond").info("do both conditional and unconditional denoising in one batch; uses a bit more VRAM during sampling, but improves speed; previously this was controlled by --always-batch-cond-uncond commandline argument"), "fp8_storage": OptionInfo("Disable", "FP8 weight", gr.Radio, {"choices": ["Disable", "Enable for SDXL", "Enable"]}).info("Use FP8 to store Linear/Conv layers' weight. Require pytorch>=2.1.0."), "cache_fp16_weight": OptionInfo(False, "Cache FP16 weight for LoRA").info("Cache fp16 weight when enabling FP8, will increase the quality of LoRA. Use more system ram."), })) @@ -370,7 +370,7 @@ options_templates.update(options_section(('sampler-params', "Sampler parameters" 'rho': OptionInfo(0.0, "rho", gr.Number, infotext='Schedule rho').info("0 = default (7 for karras, 1 for polyexponential); higher values result in a steeper noise schedule (decreases faster)"), 'eta_noise_seed_delta': OptionInfo(0, "Eta noise seed delta", gr.Number, {"precision": 0}, infotext='ENSD').info("ENSD; does not improve anything, just produces different results for ancestral samplers - only useful for reproducing images"), 'always_discard_next_to_last_sigma': OptionInfo(False, "Always discard next-to-last sigma", infotext='Discard penultimate sigma').link("PR", "https://github.com/AUTOMATIC1111/stable-diffusion-webui/pull/6044"), - 'sgm_noise_multiplier': OptionInfo(False, "SGM noise multiplier", infotext='SGM noise multplier').link("PR", "https://github.com/AUTOMATIC1111/stable-diffusion-webui/pull/12818").info("Match initial noise to official SDXL implementation - only useful for reproducing images"), + 'sgm_noise_multiplier': OptionInfo(False, "SGM noise multiplier", infotext='SGM noise multiplier').link("PR", "https://github.com/AUTOMATIC1111/stable-diffusion-webui/pull/12818").info("Match initial noise to official SDXL implementation - only useful for reproducing images"), 'uni_pc_variant': OptionInfo("bh1", "UniPC variant", gr.Radio, {"choices": ["bh1", "bh2", "vary_coeff"]}, infotext='UniPC variant'), 'uni_pc_skip_type': OptionInfo("time_uniform", "UniPC skip type", gr.Radio, {"choices": ["time_uniform", "time_quadratic", "logSNR"]}, infotext='UniPC skip type'), 'uni_pc_order': OptionInfo(3, "UniPC order", gr.Slider, {"minimum": 1, "maximum": 50, "step": 1}, infotext='UniPC order').info("must be < sampling steps"), diff --git a/modules/shared_state.py b/modules/shared_state.py index 33996691..db20b763 100644 --- a/modules/shared_state.py +++ b/modules/shared_state.py @@ -157,7 +157,7 @@ class State: self.current_image_sampling_step = self.sampling_step except Exception: - # when switching models during genration, VAE would be on CPU, so creating an image will fail. + # when switching models during generation, VAE would be on CPU, so creating an image will fail. # we silently ignore this error errors.record_exception() diff --git a/modules/textual_inversion/autocrop.py b/modules/textual_inversion/autocrop.py index e223a2e0..ca858ef4 100644 --- a/modules/textual_inversion/autocrop.py +++ b/modules/textual_inversion/autocrop.py @@ -65,7 +65,7 @@ def crop_image(im, settings): rect[3] -= 1 d.rectangle(rect, outline=GREEN) results.append(im_debug) - if settings.destop_view_image: + if settings.desktop_view_image: im_debug.show() return results @@ -341,5 +341,5 @@ class Settings: self.entropy_points_weight = entropy_points_weight self.face_points_weight = face_points_weight self.annotate_image = annotate_image - self.destop_view_image = False + self.desktop_view_image = False self.dnn_model_path = dnn_model_path diff --git a/modules/textual_inversion/image_embedding.py b/modules/textual_inversion/image_embedding.py index 81cff7bf..ea4b8833 100644 --- a/modules/textual_inversion/image_embedding.py +++ b/modules/textual_inversion/image_embedding.py @@ -193,11 +193,11 @@ if __name__ == '__main__': embedded_image = insert_image_data_embed(cap_image, test_embed) - retrived_embed = extract_image_data_embed(embedded_image) + retrieved_embed = extract_image_data_embed(embedded_image) - assert str(retrived_embed) == str(test_embed) + assert str(retrieved_embed) == str(test_embed) - embedded_image2 = insert_image_data_embed(cap_image, retrived_embed) + embedded_image2 = insert_image_data_embed(cap_image, retrieved_embed) assert embedded_image == embedded_image2 diff --git a/modules/textual_inversion/textual_inversion.py b/modules/textual_inversion/textual_inversion.py index 6d815c0b..c206ef5f 100644 --- a/modules/textual_inversion/textual_inversion.py +++ b/modules/textual_inversion/textual_inversion.py @@ -172,7 +172,7 @@ class EmbeddingDatabase: if data: name = data.get('name', name) else: - # if data is None, means this is not an embeding, just a preview image + # if data is None, means this is not an embedding, just a preview image return elif ext in ['.BIN', '.PT']: data = torch.load(path, map_location="cpu") diff --git a/modules/ui_common.py b/modules/ui_common.py index cf1b8b32..31b5492e 100644 --- a/modules/ui_common.py +++ b/modules/ui_common.py @@ -105,7 +105,7 @@ def save_files(js_data, images, do_make_zip, index): logfile_path = os.path.join(shared.opts.outdir_save, "log.csv") # NOTE: ensure csv integrity when fields are added by - # updating headers and padding with delimeters where needed + # updating headers and padding with delimiters where needed if os.path.exists(logfile_path): update_logfile(logfile_path, fields) diff --git a/modules/ui_components.py b/modules/ui_components.py index 55979f62..9cf67722 100644 --- a/modules/ui_components.py +++ b/modules/ui_components.py @@ -88,7 +88,7 @@ class DropdownEditable(FormComponent, gr.Dropdown): class InputAccordion(gr.Checkbox): """A gr.Accordion that can be used as an input - returns True if open, False if closed. - Actaully just a hidden checkbox, but creates an accordion that follows and is followed by the state of the checkbox. + Actually just a hidden checkbox, but creates an accordion that follows and is followed by the state of the checkbox. """ global_index = 0 diff --git a/modules/ui_extensions.py b/modules/ui_extensions.py index a24ea32e..913e1444 100644 --- a/modules/ui_extensions.py +++ b/modules/ui_extensions.py @@ -380,7 +380,7 @@ def install_extension_from_url(dirname, url, branch_name=None): except OSError as err: if err.errno == errno.EXDEV: # Cross device link, typical in docker or when tmp/ and extensions/ are on different file systems - # Since we can't use a rename, do the slower but more versitile shutil.move() + # Since we can't use a rename, do the slower but more versatile shutil.move() shutil.move(tmpdir, target_dir) else: # Something else, not enough free space, permissions, etc. rethrow it so that it gets handled. diff --git a/modules/ui_prompt_styles.py b/modules/ui_prompt_styles.py index d67e3f17..f71b40c4 100644 --- a/modules/ui_prompt_styles.py +++ b/modules/ui_prompt_styles.py @@ -67,7 +67,7 @@ class UiPromptStyles: with gr.Row(): self.selection = gr.Dropdown(label="Styles", elem_id=f"{tabname}_styles_edit_select", choices=list(shared.prompt_styles.styles), value=[], allow_custom_value=True, info="Styles allow you to add custom text to prompt. Use the {prompt} token in style text, and it will be replaced with user's prompt when applying style. Otherwise, style's text will be added to the end of the prompt.") ui_common.create_refresh_button([self.dropdown, self.selection], shared.prompt_styles.reload, lambda: {"choices": list(shared.prompt_styles.styles)}, f"refresh_{tabname}_styles") - self.materialize = ui_components.ToolButton(value=styles_materialize_symbol, elem_id=f"{tabname}_style_apply_dialog", tooltip="Apply all selected styles from the style selction dropdown in main UI to the prompt.") + self.materialize = ui_components.ToolButton(value=styles_materialize_symbol, elem_id=f"{tabname}_style_apply_dialog", tooltip="Apply all selected styles from the style selection dropdown in main UI to the prompt.") self.copy = ui_components.ToolButton(value=styles_copy_symbol, elem_id=f"{tabname}_style_copy", tooltip="Copy main UI prompt to style.") with gr.Row(): diff --git a/scripts/outpainting_mk_2.py b/scripts/outpainting_mk_2.py index c98ab480..5df9dff9 100644 --- a/scripts/outpainting_mk_2.py +++ b/scripts/outpainting_mk_2.py @@ -102,7 +102,7 @@ def get_matched_noise(_np_src_image, np_mask_rgb, noise_q=1, color_variation=0.0 shaped_noise_fft = _fft2(noise_rgb) shaped_noise_fft[:, :, :] = np.absolute(shaped_noise_fft[:, :, :]) ** 2 * (src_dist ** noise_q) * src_phase # perform the actual shaping - brightness_variation = 0. # color_variation # todo: temporarily tieing brightness variation to color variation for now + brightness_variation = 0. # color_variation # todo: temporarily tying brightness variation to color variation for now contrast_adjusted_np_src = _np_src_image[:] * (brightness_variation + 1.) - brightness_variation * 2. # scikit-image is used for histogram matching, very convenient! diff --git a/scripts/xyz_grid.py b/scripts/xyz_grid.py index 6d3e42c0..57ee4708 100644 --- a/scripts/xyz_grid.py +++ b/scripts/xyz_grid.py @@ -45,7 +45,7 @@ def apply_prompt(p, x, xs): def apply_order(p, x, xs): token_order = [] - # Initally grab the tokens from the prompt, so they can be replaced in order of earliest seen + # Initially grab the tokens from the prompt, so they can be replaced in order of earliest seen for token in x: token_order.append((p.prompt.find(token), token)) From 3fb1c2e58d30ea378c49f7d0e10df916cef1473e Mon Sep 17 00:00:00 2001 From: wangshuai09 <391746016@qq.com> Date: Mon, 4 Mar 2024 17:19:37 +0800 Subject: [PATCH 061/496] fix npu-smi command --- webui.sh | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/webui.sh b/webui.sh index be2b853b..eb76bcab 100755 --- a/webui.sh +++ b/webui.sh @@ -158,7 +158,7 @@ then if echo "$gpu_info" | grep -q "AMD" && [[ -z "${TORCH_COMMAND}" ]] then export TORCH_COMMAND="pip install torch==2.0.1+rocm5.4.2 torchvision==0.15.2+rocm5.4.2 --index-url https://download.pytorch.org/whl/rocm5.4.2" - elif eval "npu-smi info" + elif npu-smi info 2>/dev/null then export TORCH_COMMAND="pip install torch==2.1.0 torchvision torchaudio --index-url https://download.pytorch.org/whl/cpu; pip install torch_npu==2.1.0" From 67d8dafe4474c8f63889630fe61075e2ad507085 Mon Sep 17 00:00:00 2001 From: Alon Burg Date: Thu, 29 Feb 2024 10:07:15 +0200 Subject: [PATCH 062/496] Fix EXIF orientation in API image loading --- modules/api/api.py | 2 ++ modules/images.py | 49 ++++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 51 insertions(+) diff --git a/modules/api/api.py b/modules/api/api.py index 4e656082..5742e6e6 100644 --- a/modules/api/api.py +++ b/modules/api/api.py @@ -86,6 +86,7 @@ def decode_base64_to_image(encoding): response = requests.get(encoding, timeout=30, headers=headers) try: image = Image.open(BytesIO(response.content)) + image = images.apply_exif_orientation(image) return image except Exception as e: raise HTTPException(status_code=500, detail="Invalid image url") from e @@ -94,6 +95,7 @@ def decode_base64_to_image(encoding): encoding = encoding.split(";")[1].split(",")[1] try: image = Image.open(BytesIO(base64.b64decode(encoding))) + image = images.apply_exif_orientation(image) return image except Exception as e: raise HTTPException(status_code=500, detail="Invalid encoded image") from e diff --git a/modules/images.py b/modules/images.py index b6f2358c..1728ebc3 100644 --- a/modules/images.py +++ b/modules/images.py @@ -797,3 +797,52 @@ def flatten(img, bgcolor): return img.convert('RGB') + +# https://www.exiv2.org/tags.html +_EXIF_ORIENT = 274 # exif 'Orientation' tag + +def apply_exif_orientation(image): + """ + Applies the exif orientation correctly. + + This code exists per the bug: + https://github.com/python-pillow/Pillow/issues/3973 + with the function `ImageOps.exif_transpose`. The Pillow source raises errors with + various methods, especially `tobytes` + + Function based on: + https://github.com/wkentaro/labelme/blob/v4.5.4/labelme/utils/image.py#L59 + https://github.com/python-pillow/Pillow/blob/7.1.2/src/PIL/ImageOps.py#L527 + + Args: + image (PIL.Image): a PIL image + + Returns: + (PIL.Image): the PIL image with exif orientation applied, if applicable + """ + if not hasattr(image, "getexif"): + return image + + try: + exif = image.getexif() + except Exception: # https://github.com/facebookresearch/detectron2/issues/1885 + exif = None + + if exif is None: + return image + + orientation = exif.get(_EXIF_ORIENT) + + method = { + 2: Image.FLIP_LEFT_RIGHT, + 3: Image.ROTATE_180, + 4: Image.FLIP_TOP_BOTTOM, + 5: Image.TRANSPOSE, + 6: Image.ROTATE_270, + 7: Image.TRANSVERSE, + 8: Image.ROTATE_90, + }.get(orientation) + + if method is not None: + return image.transpose(method) + return image From 0dc12861efee9d1e1eacb2d1903bf0fcd43fcfcc Mon Sep 17 00:00:00 2001 From: AUTOMATIC1111 <16777216c@gmail.com> Date: Mon, 4 Mar 2024 15:30:46 +0300 Subject: [PATCH 063/496] 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 --- .../scripts/extra_options_section.py | 8 ++++++-- modules/ui.py | 4 +++- modules/ui_settings.py | 4 +++- 3 files changed, 12 insertions(+), 4 deletions(-) diff --git a/extensions-builtin/extra-options-section/scripts/extra_options_section.py b/extensions-builtin/extra-options-section/scripts/extra_options_section.py index 4c10d9c7..a91bea4f 100644 --- a/extensions-builtin/extra-options-section/scripts/extra_options_section.py +++ b/extensions-builtin/extra-options-section/scripts/extra_options_section.py @@ -1,7 +1,7 @@ import math 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 @@ -42,7 +42,11 @@ class ExtraOptionsSection(scripts.Script): setting_name = extra_options[index] 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.setting_names.append(setting_name) diff --git a/modules/ui.py b/modules/ui.py index dcba8e88..7b434162 100644 --- a/modules/ui.py +++ b/modules/ui.py @@ -269,6 +269,9 @@ def create_ui(): parameters_copypaste.reset() + settings = ui_settings.UiSettings() + settings.register_settings() + scripts.scripts_current = scripts.scripts_txt2img scripts.scripts_txt2img.initialize_scripts(is_img2img=False) @@ -1116,7 +1119,6 @@ def create_ui(): loadsave = ui_loadsave.UiLoadsave(cmd_opts.ui_config_file) ui_settings_from_file = loadsave.ui_settings.copy() - settings = ui_settings.UiSettings() settings.create_ui(loadsave, dummy_component) interfaces = [ diff --git a/modules/ui_settings.py b/modules/ui_settings.py index e054d00a..d17ef1d9 100644 --- a/modules/ui_settings.py +++ b/modules/ui_settings.py @@ -98,6 +98,9 @@ class UiSettings: return get_value_for_setting(key), opts.dumpjson() + def register_settings(self): + script_callbacks.ui_settings_callback() + def create_ui(self, loadsave, dummy_component): self.components = [] self.component_dict = {} @@ -105,7 +108,6 @@ class UiSettings: shared.settings_components = self.component_dict - script_callbacks.ui_settings_callback() opts.reorder() with gr.Blocks(analytics_enabled=False) as settings_interface: From 09b5ce68a99da700dd5a63f4475b0ac2d2a959e2 Mon Sep 17 00:00:00 2001 From: AUTOMATIC1111 <16777216c@gmail.com> Date: Mon, 4 Mar 2024 19:14:53 +0300 Subject: [PATCH 064/496] add images.read to automatically fix all jpeg/png weirdness --- modules/api/api.py | 6 +-- modules/images.py | 66 ++++++++-------------------- modules/img2img.py | 25 +++++------ modules/infotext_utils.py | 6 +-- modules/postprocessing.py | 6 +-- modules/textual_inversion/dataset.py | 4 +- 6 files changed, 41 insertions(+), 72 deletions(-) diff --git a/modules/api/api.py b/modules/api/api.py index a0e70329..0630e77e 100644 --- a/modules/api/api.py +++ b/modules/api/api.py @@ -85,8 +85,7 @@ def decode_base64_to_image(encoding): headers = {'user-agent': opts.api_useragent} if opts.api_useragent else {} response = requests.get(encoding, timeout=30, headers=headers) try: - image = Image.open(BytesIO(response.content)) - image = images.apply_exif_orientation(image) + image = images.read(BytesIO(response.content)) return image except Exception as e: raise HTTPException(status_code=500, detail="Invalid image url") from e @@ -94,8 +93,7 @@ def decode_base64_to_image(encoding): if encoding.startswith("data:image/"): encoding = encoding.split(";")[1].split(",")[1] try: - image = Image.open(BytesIO(base64.b64decode(encoding))) - image = images.apply_exif_orientation(image) + image = images.read(BytesIO(base64.b64decode(encoding))) return image except Exception as e: raise HTTPException(status_code=500, detail="Invalid encoded image") from e diff --git a/modules/images.py b/modules/images.py index de90b403..c50b2455 100644 --- a/modules/images.py +++ b/modules/images.py @@ -12,7 +12,7 @@ import re import numpy as np import piexif import piexif.helper -from PIL import Image, ImageFont, ImageDraw, ImageColor, PngImagePlugin +from PIL import Image, ImageFont, ImageDraw, ImageColor, PngImagePlugin, ImageOps import string import json import hashlib @@ -551,12 +551,6 @@ def save_image_with_geninfo(image, geninfo, filename, extension=None, existing_p else: pnginfo_data = None - # Error handling for unsupported transparency in RGB mode - if (image.mode == "RGB" and - "transparency" in image.info and - isinstance(image.info["transparency"], bytes)): - del image.info["transparency"] - image.save(filename, format=image_format, quality=opts.jpeg_quality, pnginfo=pnginfo_data) elif extension.lower() in (".jpg", ".jpeg", ".webp"): @@ -779,7 +773,7 @@ def image_data(data): import gradio as gr try: - image = Image.open(io.BytesIO(data)) + image = read(io.BytesIO(data)) textinfo, _ = read_info_from_image(image) return textinfo, None except Exception: @@ -807,51 +801,29 @@ def flatten(img, bgcolor): return img.convert('RGB') -# https://www.exiv2.org/tags.html -_EXIF_ORIENT = 274 # exif 'Orientation' tag +def read(fp, **kwargs): + image = Image.open(fp, **kwargs) + image = fix_image(image) -def apply_exif_orientation(image): - """ - Applies the exif orientation correctly. + return image - This code exists per the bug: - https://github.com/python-pillow/Pillow/issues/3973 - with the function `ImageOps.exif_transpose`. The Pillow source raises errors with - various methods, especially `tobytes` - Function based on: - https://github.com/wkentaro/labelme/blob/v4.5.4/labelme/utils/image.py#L59 - https://github.com/python-pillow/Pillow/blob/7.1.2/src/PIL/ImageOps.py#L527 - - Args: - image (PIL.Image): a PIL image - - Returns: - (PIL.Image): the PIL image with exif orientation applied, if applicable - """ - if not hasattr(image, "getexif"): - return image +def fix_image(image: Image.Image): + if image is None: + return None try: - exif = image.getexif() - except Exception: # https://github.com/facebookresearch/detectron2/issues/1885 - exif = None + image = ImageOps.exif_transpose(image) + image = fix_png_transparency(image) + except Exception: + pass - if exif is None: + 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 - orientation = exif.get(_EXIF_ORIENT) - - method = { - 2: Image.FLIP_LEFT_RIGHT, - 3: Image.ROTATE_180, - 4: Image.FLIP_TOP_BOTTOM, - 5: Image.TRANSPOSE, - 6: Image.ROTATE_270, - 7: Image.TRANSVERSE, - 8: Image.ROTATE_90, - }.get(orientation) - - if method is not None: - return image.transpose(method) + image = image.convert("RGBA") return image diff --git a/modules/img2img.py b/modules/img2img.py index f81405df..e7fb3ea3 100644 --- a/modules/img2img.py +++ b/modules/img2img.py @@ -6,7 +6,7 @@ import numpy as np from PIL import Image, ImageOps, ImageFilter, ImageEnhance, UnidentifiedImageError 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.processing import Processed, StableDiffusionProcessingImg2Img, process_images from modules.shared import opts, state @@ -21,7 +21,7 @@ def process_batch(p, input_dir, output_dir, inpaint_mask_dir, args, to_scale=Fal output_dir = output_dir.strip() processing.fix_seed(p) - images = list(shared.walk_files(input_dir, allowed_extensions=(".png", ".jpg", ".jpeg", ".webp", ".tif", ".tiff"))) + batch_images = list(shared.walk_files(input_dir, allowed_extensions=(".png", ".jpg", ".jpeg", ".webp", ".tif", ".tiff"))) is_inpaint_batch = False if inpaint_mask_dir: @@ -31,9 +31,9 @@ def process_batch(p, input_dir, output_dir, inpaint_mask_dir, args, to_scale=Fal if is_inpaint_batch: 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 prompt = p.prompt @@ -46,8 +46,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)) batch_results = None discard_further_results = False - for i, image in enumerate(images): - state.job = f"{i+1} out of {len(images)}" + for i, image in enumerate(batch_images): + state.job = f"{i+1} out of {len(batch_images)}" if state.skipped: state.skipped = False @@ -55,7 +55,7 @@ def process_batch(p, input_dir, output_dir, inpaint_mask_dir, args, to_scale=Fal break try: - img = Image.open(image) + img = images.read(image) except UnidentifiedImageError as e: print(e) continue @@ -86,7 +86,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 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 if use_png_info: @@ -94,8 +94,8 @@ def process_batch(p, input_dir, output_dir, inpaint_mask_dir, args, to_scale=Fal info_img = img if png_info_dir: info_img_path = os.path.join(png_info_dir, os.path.basename(image)) - info_img = Image.open(info_img_path) - geninfo, _ = imgutil.read_info_from_image(info_img) + info_img = images.read(info_img_path) + geninfo, _ = images.read_info_from_image(info_img) parsed_parameters = parse_generation_parameters(geninfo) parsed_parameters = {k: v for k, v in parsed_parameters.items() if k in (png_info_props or {})} except Exception: @@ -175,9 +175,8 @@ def img2img(id_task: str, mode: int, prompt: str, negative_prompt: str, prompt_s image = None mask = None - # Use the EXIF orientation of photos taken by smartphones. - if image is not None: - image = ImageOps.exif_transpose(image) + image = images.fix_image(image) + mask = images.fix_image(mask) if selected_scale_tab == 1 and not is_batch: assert image, "Can't scale by because no image is selected" diff --git a/modules/infotext_utils.py b/modules/infotext_utils.py index e04a7bee..a6de9db9 100644 --- a/modules/infotext_utils.py +++ b/modules/infotext_utils.py @@ -8,7 +8,7 @@ import sys import gradio as gr 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 from PIL import Image 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' filename = filename.rsplit('?', 1)[0] - return Image.open(filename) + return images.read(filename) if type(filedata) == list: if len(filedata) == 0: @@ -95,7 +95,7 @@ def image_from_url_text(filedata): filedata = filedata[len("data:image/png;base64,"):] filedata = base64.decodebytes(filedata.encode('utf-8')) - image = Image.open(io.BytesIO(filedata)) + image = images.read(io.BytesIO(filedata)) return image diff --git a/modules/postprocessing.py b/modules/postprocessing.py index f1488232..754cc9e3 100644 --- a/modules/postprocessing.py +++ b/modules/postprocessing.py @@ -17,10 +17,10 @@ def run_postprocessing(extras_mode, image, image_folder, input_dir, output_dir, if extras_mode == 1: for img in image_folder: if isinstance(img, Image.Image): - image = img + image = images.fix_image(img) fn = '' 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] yield image, fn elif extras_mode == 2: @@ -56,7 +56,7 @@ def run_postprocessing(extras_mode, image, image_folder, input_dir, output_dir, if isinstance(image_placeholder, str): try: - image_data = Image.open(image_placeholder) + image_data = images.read(image_placeholder) except Exception: continue else: diff --git a/modules/textual_inversion/dataset.py b/modules/textual_inversion/dataset.py index 7ee05061..84fb5df0 100644 --- a/modules/textual_inversion/dataset.py +++ b/modules/textual_inversion/dataset.py @@ -10,7 +10,7 @@ from random import shuffle, choices import random import tqdm -from modules import devices, shared +from modules import devices, shared, images import re from ldm.modules.distributions.distributions import DiagonalGaussianDistribution @@ -61,7 +61,7 @@ class PersonalizedBase(Dataset): if shared.state.interrupted: raise Exception("interrupted") try: - image = Image.open(path) + image = images.read(path) #Currently does not work for single color transparency #We would need to read image.info['transparency'] for that if use_weight and 'A' in image.getbands(): From 801461eea209d166e1b06714ea7eebd76f9e10dd Mon Sep 17 00:00:00 2001 From: catboxanon <122327233+catboxanon@users.noreply.github.com> Date: Mon, 4 Mar 2024 18:33:22 -0500 Subject: [PATCH 065/496] Re-use profiler visualization for extra networks --- .eslintrc.js | 2 + javascript/extraNetworks.js | 62 +++++++++ javascript/profilerVisualization.js | 205 +++++++++++++++------------- 3 files changed, 177 insertions(+), 92 deletions(-) diff --git a/.eslintrc.js b/.eslintrc.js index 9c70eff8..2e7258f6 100644 --- a/.eslintrc.js +++ b/.eslintrc.js @@ -78,6 +78,8 @@ module.exports = { //extraNetworks.js requestGet: "readonly", popup: "readonly", + // profilerVisualization.js + createVisualizationTable: "readonly", // from python localization: "readonly", // progrssbar.js diff --git a/javascript/extraNetworks.js b/javascript/extraNetworks.js index c21433db..7b487af1 100644 --- a/javascript/extraNetworks.js +++ b/javascript/extraNetworks.js @@ -528,12 +528,74 @@ function popupId(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) { + 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.debug(error); } + var elem = document.createElement('pre'); elem.classList.add('popup-metadata'); elem.textContent = text; popup(elem); + return; } function requestGet(url, data, handler, errorHandler) { diff --git a/javascript/profilerVisualization.js b/javascript/profilerVisualization.js index 9d8e5f42..9822f4b2 100644 --- a/javascript/profilerVisualization.js +++ b/javascript/profilerVisualization.js @@ -33,120 +33,141 @@ function createRow(table, cellName, items) { return res; } -function showProfile(path, cutoff = 0.05) { - requestGet(path, {}, function(data) { - var table = document.createElement('table'); - table.className = 'popup-table'; +function createVisualizationTable(data, cutoff = 0, sort = "") { + var table = document.createElement('table'); + table.className = 'popup-table'; - data.records['total'] = data.total; - var keys = Object.keys(data.records).sort(function(a, b) { - return data.records[b] - data.records[a]; + var keys = Object.keys(data); + if (sort === "number") { + keys = keys.sort(function(a, b) { + return data[b] - data[a]; }); - var items = keys.map(function(x) { - return {key: x, parts: x.split('/'), time: data.records[x]}; + } else { + 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) { - return Math.max(a, b.parts.length); - }, 0); - - var cols = createRow(table, 'th', ['record', 'seconds']); - cols[0].colSpan = maxLength; - - function arraysEqual(a, b) { - return !(a < b || b < a); + if (sort === "number") { + matching = matching.sort(function(a, b) { + return b.value - a.value; + }); + } else { + matching = matching.sort(); } + 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 matching = items.filter(function(x) { - return x.parts[level] && !x.parts[level + 1] && arraysEqual(x.parts.slice(0, level), parent); - }); - var sorted = matching.sort(function(a, b) { - return b.time - a.time; - }); - var othersTime = 0; - var othersList = []; - var othersRows = []; - var childrenRows = []; - sorted.forEach(function(x) { - var visible = x.time >= cutoff && !hide; + var cells = []; + for (var i = 0; i < maxLength; i++) { + cells.push(x.parts[i]); + } + cells.push(cutoff === 0 ? x.value : x.value.toFixed(3)); + var cols = createRow(table, 'td', cells); + for (i = 0; i < level; i++) { + cols[i].className = 'muted'; + } - var cells = []; - for (var i = 0; i < maxLength; i++) { - cells.push(x.parts[i]); - } - 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 (!visible) { + tr.classList.add("hidden"); + } - var tr = cols[0].parentNode; - if (!visible) { - tr.classList.add("hidden"); - } - - if (x.time >= cutoff) { - 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'; - } + if (cutoff === 0 || x.value >= cutoff) { + childrenRows.push(tr); + } else { + othersTime += x.value; + 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 tr = cell.parentNode; var onclick = function() { - tr.classList.add("hidden"); cell.classList.remove("link"); cell.removeEventListener("click", onclick); - othersRows.forEach(function(x) { + children.forEach(function(x) { x.classList.remove("hidden"); }); }; - - cell.title = othersList.join(", "); cell.classList.add("link"); cell.addEventListener("click", onclick); + } + }); - if (hide) { - tr.classList.add("hidden"); - } - - childrenRows.push(tr); + 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'; } - 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); }); } From ecffe8513e8ff10c58365d9d7c7d4dcbd3dc750a Mon Sep 17 00:00:00 2001 From: catboxanon <122327233+catboxanon@users.noreply.github.com> Date: Mon, 4 Mar 2024 18:46:25 -0500 Subject: [PATCH 066/496] Lint --- javascript/extraNetworks.js | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/javascript/extraNetworks.js b/javascript/extraNetworks.js index 7b487af1..584fd6c7 100644 --- a/javascript/extraNetworks.js +++ b/javascript/extraNetworks.js @@ -588,7 +588,9 @@ function extraNetworksShowMetadata(text) { popup(table); return; } - } catch (error) { console.debug(error); } + } catch (error) { + console.eror(error); + } var elem = document.createElement('pre'); elem.classList.add('popup-metadata'); From 706f63adfaf3c5442d181c7979bf5fbd2219f760 Mon Sep 17 00:00:00 2001 From: w-e-w <40751091+w-e-w@users.noreply.github.com> Date: Tue, 5 Mar 2024 12:23:44 +0900 Subject: [PATCH 067/496] fix extract_style_text_from_prompt #15132 --- modules/styles.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/modules/styles.py b/modules/styles.py index 60bd8a7f..a9d8636a 100644 --- a/modules/styles.py +++ b/modules/styles.py @@ -42,7 +42,7 @@ def extract_style_text_from_prompt(style_text, prompt): stripped_style_text = style_text.strip() if "{prompt}" in stripped_style_text: - left, right = stripped_style_text.split("{prompt}", 2) + left, _, right = stripped_style_text.partition("{prompt}") if stripped_prompt.startswith(left) and stripped_prompt.endswith(right): prompt = stripped_prompt[len(left):len(stripped_prompt)-len(right)] return True, prompt From 7785d484ae8a2e987bf56119b99f93841ce96675 Mon Sep 17 00:00:00 2001 From: catboxanon <122327233+catboxanon@users.noreply.github.com> Date: Tue, 5 Mar 2024 11:50:53 -0500 Subject: [PATCH 068/496] Only override emphasis if actually used in prompt --- modules/infotext_utils.py | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/modules/infotext_utils.py b/modules/infotext_utils.py index a6de9db9..db186644 100644 --- a/modules/infotext_utils.py +++ b/modules/infotext_utils.py @@ -8,7 +8,7 @@ import sys import gradio as gr from modules.paths import data_path -from modules import shared, ui_tempdir, script_callbacks, processing, infotext_versions, images +from modules import shared, ui_tempdir, script_callbacks, processing, infotext_versions, images, prompt_parser from PIL import Image sys.modules['modules.generation_parameters_copypaste'] = sys.modules[__name__] # alias for old name @@ -356,7 +356,10 @@ 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": 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" if "Refiner switch by sampling steps" not in res: From ed386c84b63b49402c4feb90ab466f9fb0781e37 Mon Sep 17 00:00:00 2001 From: catboxanon <122327233+catboxanon@users.noreply.github.com> Date: Tue, 5 Mar 2024 11:53:36 -0500 Subject: [PATCH 069/496] Fix emphasis infotext missing from `params.txt` --- modules/processing.py | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/modules/processing.py b/modules/processing.py index 411c7c3f..93493f80 100644 --- a/modules/processing.py +++ b/modules/processing.py @@ -896,6 +896,10 @@ def process_images_inner(p: StableDiffusionProcessing) -> Processed: if p.scripts is not None: 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 # infotext could be modified by that callback # Example: a wildcard processed by process_batch sets an extra model @@ -905,13 +909,9 @@ def process_images_inner(p: StableDiffusionProcessing) -> Processed: processed = Processed(p, []) file.write(processed.infotext(p, 0)) - p.setup_conds() - for comment in model_hijack.comments: p.comment(comment) - p.extra_generation_params.update(model_hijack.extra_generation_params) - if p.n_iter > 1: shared.state.job = f"Batch {n+1} out of {p.n_iter}" From c1deec64cb89102f0efbb155845a6195fc696c89 Mon Sep 17 00:00:00 2001 From: AUTOMATIC1111 <16777216c@gmail.com> Date: Wed, 6 Mar 2024 13:04:58 +0300 Subject: [PATCH 070/496] lint --- modules/api/api.py | 2 +- modules/textual_inversion/dataset.py | 1 - 2 files changed, 1 insertion(+), 2 deletions(-) diff --git a/modules/api/api.py b/modules/api/api.py index 0630e77e..29fa0011 100644 --- a/modules/api/api.py +++ b/modules/api/api.py @@ -23,7 +23,7 @@ from modules.shared import opts from modules.processing import StableDiffusionProcessingTxt2Img, StableDiffusionProcessingImg2Img, process_images from modules.textual_inversion.textual_inversion import create_embedding, train_embedding 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.realesrgan_model import get_realesrgan_models from modules import devices diff --git a/modules/textual_inversion/dataset.py b/modules/textual_inversion/dataset.py index 84fb5df0..71c032df 100644 --- a/modules/textual_inversion/dataset.py +++ b/modules/textual_inversion/dataset.py @@ -2,7 +2,6 @@ import os import numpy as np import PIL import torch -from PIL import Image from torch.utils.data import Dataset, DataLoader, Sampler from torchvision import transforms from collections import defaultdict From 73e635ce6e0391da23cab9a849bb009181580057 Mon Sep 17 00:00:00 2001 From: continue-revolution Date: Wed, 6 Mar 2024 05:32:59 -0600 Subject: [PATCH 071/496] fix --- .../scripts/soft_inpainting.py | 26 ++++++++++++++----- 1 file changed, 20 insertions(+), 6 deletions(-) diff --git a/extensions-builtin/soft-inpainting/scripts/soft_inpainting.py b/extensions-builtin/soft-inpainting/scripts/soft_inpainting.py index d9024344..cc02a150 100644 --- a/extensions-builtin/soft-inpainting/scripts/soft_inpainting.py +++ b/extensions-builtin/soft-inpainting/scripts/soft_inpainting.py @@ -57,10 +57,14 @@ def latent_blend(settings, a, b, t): # NOTE: We use inplace operations wherever possible. - # [4][w][h] to [1][4][w][h] - t2 = t.unsqueeze(0) - # [4][w][h] to [1][1][w][h] - the [4] seem redundant. - t3 = t[0].unsqueeze(0).unsqueeze(0) + if len(t.shape) == 3: + # [4][w][h] to [1][4][w][h] + t2 = t.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_t3 = 1 - t3 @@ -135,7 +139,10 @@ def apply_adaptive_masks( from PIL import Image, ImageOps, ImageFilter # 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 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) @@ -157,7 +164,14 @@ def apply_adaptive_masks( percentile_min=0.25, percentile_max=0.75, min_width=1) # 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: # len(mask_scalar.shape) == 3: + half_weighted_distance = settings.composite_difference_threshold * mask_scalar + converted_mask = converted_mask / half_weighted_distance converted_mask = 1 / (1 + converted_mask ** settings.composite_difference_contrast) From 7d59b3b5643c5faf8e84541e4af58c91a9182978 Mon Sep 17 00:00:00 2001 From: continue-revolution Date: Wed, 6 Mar 2024 05:39:17 -0600 Subject: [PATCH 072/496] rm comment --- extensions-builtin/soft-inpainting/scripts/soft_inpainting.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/extensions-builtin/soft-inpainting/scripts/soft_inpainting.py b/extensions-builtin/soft-inpainting/scripts/soft_inpainting.py index 8f7b42a8..f56e1e22 100644 --- a/extensions-builtin/soft-inpainting/scripts/soft_inpainting.py +++ b/extensions-builtin/soft-inpainting/scripts/soft_inpainting.py @@ -169,7 +169,7 @@ def apply_adaptive_masks( half_weighted_distance = settings.composite_difference_threshold * mask_scalar[i] else: half_weighted_distance = settings.composite_difference_threshold * mask_scalar[0] - else: # len(mask_scalar.shape) == 3: + else: half_weighted_distance = settings.composite_difference_threshold * mask_scalar converted_mask = converted_mask / half_weighted_distance From 12bcacf41393ff9368836514af641d835b8a3b02 Mon Sep 17 00:00:00 2001 From: Kohaku-Blueleaf <59680068+KohakuBlueleaf@users.noreply.github.com> Date: Thu, 7 Mar 2024 13:29:40 +0800 Subject: [PATCH 073/496] Initial implementation --- extensions-builtin/Lora/network.py | 15 +++++++++++++++ 1 file changed, 15 insertions(+) diff --git a/extensions-builtin/Lora/network.py b/extensions-builtin/Lora/network.py index b8fd9194..2268b0f7 100644 --- a/extensions-builtin/Lora/network.py +++ b/extensions-builtin/Lora/network.py @@ -146,6 +146,9 @@ class NetworkModule: 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.dora_scale = weights.w["dora_scale"] if "dora_scale" in weights.w else None + self.dora_mean_dim = tuple(i for i in range(len(self.shape)) if i != 1) + def multiplier(self): if 'transformer' in self.sd_key[:20]: return self.network.te_multiplier @@ -160,6 +163,15 @@ class NetworkModule: return 1.0 + def apply_weight_decompose(self, updown, orig_weight): + orig_weight = orig_weight.to(updown) + merged_scale1 = updown + orig_weight + dora_merged = ( + merged_scale1 / merged_scale1(dim=self.dora_mean_dim, keepdim=True) * self.dora_scale + ) + final_updown = dora_merged - orig_weight + return final_updown + def finalize_updown(self, updown, orig_weight, output_shape, ex_bias=None): if self.bias is not None: updown = updown.reshape(self.bias.shape) @@ -175,6 +187,9 @@ class NetworkModule: if ex_bias is not None: ex_bias = ex_bias * self.multiplier() + if self.dora_scale is not None: + updown = self.apply_weight_decompose(updown, orig_weight) + return updown * self.calc_scale() * self.multiplier(), ex_bias def calc_updown(self, target): From 766f6e3eca38996bb291c6e891ce457671abc383 Mon Sep 17 00:00:00 2001 From: catboxanon <122327233+catboxanon@users.noreply.github.com> Date: Thu, 7 Mar 2024 18:30:36 -0500 Subject: [PATCH 074/496] edit-attention: deselect surrounding whitespace --- javascript/edit-attention.js | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/javascript/edit-attention.js b/javascript/edit-attention.js index 688c2f11..01069449 100644 --- a/javascript/edit-attention.js +++ b/javascript/edit-attention.js @@ -64,6 +64,14 @@ function keyupEditAttention(event) { selectionEnd++; } + // deselect surrounding whitespace + while (target.value.slice(selectionStart, selectionStart + 1) == " " && selectionStart < selectionEnd) { + selectionStart++; + } + while (target.value.slice(selectionEnd - 1, selectionEnd) == " " && selectionEnd > selectionStart) { + selectionEnd--; + } + target.setSelectionRange(selectionStart, selectionEnd); return true; } From 5ab5405b6f50ad0eae0cab32772cb32c5b4a4781 Mon Sep 17 00:00:00 2001 From: catboxanon <122327233+catboxanon@users.noreply.github.com> Date: Thu, 7 Mar 2024 21:30:05 -0500 Subject: [PATCH 075/496] Simpler comparison Co-authored-by: missionfloyd --- javascript/edit-attention.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/javascript/edit-attention.js b/javascript/edit-attention.js index 01069449..b07ba97c 100644 --- a/javascript/edit-attention.js +++ b/javascript/edit-attention.js @@ -65,10 +65,10 @@ function keyupEditAttention(event) { } // deselect surrounding whitespace - while (target.value.slice(selectionStart, selectionStart + 1) == " " && selectionStart < selectionEnd) { + while (text[selectionStart] == " " && selectionStart < selectionEnd) { selectionStart++; } - while (target.value.slice(selectionEnd - 1, selectionEnd) == " " && selectionEnd > selectionStart) { + while (text[selectionEnd - 1] == " " && selectionEnd > selectionStart) { selectionEnd--; } From e0c9361b7dd673cf28dfe8888cbd463eddbb8431 Mon Sep 17 00:00:00 2001 From: AUTOMATIC1111 <16777216c@gmail.com> Date: Fri, 8 Mar 2024 07:51:14 +0300 Subject: [PATCH 076/496] performance optimization for extra networks --- modules/ui_extra_networks.py | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/modules/ui_extra_networks.py b/modules/ui_extra_networks.py index ad2c2305..2cf91d36 100644 --- a/modules/ui_extra_networks.py +++ b/modules/ui_extra_networks.py @@ -489,15 +489,15 @@ class ExtraNetworksPage: Returns: HTML formatted string. """ - res = "" + res = [] for item in self.items.values(): - res += self.create_item_html(tabname, item, self.card_tpl) + res.append(self.create_item_html(tabname, item, self.card_tpl)) - if res == "": + if not res: dirs = "".join([f"
  • {x}
  • " for x in self.allowed_directories_for_previews()]) - res = none_message or shared.html("extra-networks-no-cards.html").format(dirs=dirs) + res = [none_message or shared.html("extra-networks-no-cards.html").format(dirs=dirs)] - return res + return "".join(res) def create_html(self, tabname, *, empty=False): """Generates an HTML string for the current pane. From a43ce7eabbdd58eb6ec1c75bdd238efc3a60fdd4 Mon Sep 17 00:00:00 2001 From: AUTOMATIC1111 <16777216c@gmail.com> Date: Fri, 8 Mar 2024 08:13:02 +0300 Subject: [PATCH 077/496] fix broken resize handle on the train tab --- javascript/resizeHandle.js | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/javascript/resizeHandle.js b/javascript/resizeHandle.js index 50251ffc..4aeb14b4 100644 --- a/javascript/resizeHandle.js +++ b/javascript/resizeHandle.js @@ -79,6 +79,11 @@ parent.minRightColWidth = 0; parent.needHideOnMoblie = false; } + + if (!leftColTemplate) { + leftColTemplate = '1fr'; + } + const gridTemplateColumns = `${leftColTemplate} ${PAD}px ${parent.children[1].style.flexGrow}fr`; parent.style.gridTemplateColumns = gridTemplateColumns; parent.style.originalGridTemplateColumns = gridTemplateColumns; From a551a43164b8baf1b5652a9ee73081cca54c612b Mon Sep 17 00:00:00 2001 From: AUTOMATIC1111 <16777216c@gmail.com> Date: Fri, 8 Mar 2024 09:52:25 +0300 Subject: [PATCH 078/496] add an option to have old-style directory view instead of tree view --- html/extra-networks-pane-dirs.html | 8 +++ html/extra-networks-pane-tree.html | 8 +++ html/extra-networks-pane.html | 13 +---- javascript/extraNetworks.js | 34 +++++------- modules/shared_options.py | 3 +- modules/ui_extra_networks.py | 88 +++++++++++++++++++++--------- style.css | 14 ++++- 7 files changed, 111 insertions(+), 57 deletions(-) create mode 100644 html/extra-networks-pane-dirs.html create mode 100644 html/extra-networks-pane-tree.html diff --git a/html/extra-networks-pane-dirs.html b/html/extra-networks-pane-dirs.html new file mode 100644 index 00000000..5ce04289 --- /dev/null +++ b/html/extra-networks-pane-dirs.html @@ -0,0 +1,8 @@ +
    +
    + {dirs_html} +
    +
    + {items_html} +
    +
    diff --git a/html/extra-networks-pane-tree.html b/html/extra-networks-pane-tree.html new file mode 100644 index 00000000..88561fcd --- /dev/null +++ b/html/extra-networks-pane-tree.html @@ -0,0 +1,8 @@ +
    +
    + {tree_html} +
    +
    + {items_html} +
    +
    \ No newline at end of file diff --git a/html/extra-networks-pane.html b/html/extra-networks-pane.html index 02a87108..ff8a73ad 100644 --- a/html/extra-networks-pane.html +++ b/html/extra-networks-pane.html @@ -1,4 +1,4 @@ -
    +
    -
    -
    - {tree_html} -
    -
    - {items_html} -
    -
    -
    \ No newline at end of file + {pane_content} +
    diff --git a/javascript/extraNetworks.js b/javascript/extraNetworks.js index 584fd6c7..6adf9ec0 100644 --- a/javascript/extraNetworks.js +++ b/javascript/extraNetworks.js @@ -272,6 +272,15 @@ function saveCardPreview(event, tabname, filename) { 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) { /** * Processes `onclick` events when user clicks on files in tree. @@ -447,27 +456,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 extra_networks_tabname The id of the active extraNetworks tab. Ex: lora, checkpoints, etc. */ - const tree = gradioApp().getElementById(tabname + "_" + extra_networks_tabname + "_tree"); - const parent = tree.parentElement; - let resizeHandle = parent.querySelector('.resize-handle'); - tree.classList.toggle("hidden"); + var button = event.currentTarget; + button.classList.toggle("extra-network-control--enabled"); + var show = ! button.classList.contains("extra-network-control--enabled"); - if (tree.classList.contains("hidden")) { - tree.style.display = 'none'; - parent.style.display = 'flex'; - if (resizeHandle) { - resizeHandle.style.display = 'none'; - } - } else { - tree.style.display = 'block'; - parent.style.display = 'grid'; - if (!resizeHandle) { - setupResizeHandle(parent); - resizeHandle = parent.querySelector('.resize-handle'); - } - resizeHandle.style.display = 'block'; - } - event.currentTarget.classList.toggle("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) { diff --git a/modules/shared_options.py b/modules/shared_options.py index 536766db..21643afe 100644 --- a/modules/shared_options.py +++ b/modules/shared_options.py @@ -258,7 +258,8 @@ options_templates.update(options_section(('extra_networks', "Extra Networks", "s "extra_networks_card_description_is_html": OptionInfo(False, "Treat card description as HTML"), "extra_networks_card_order_field": OptionInfo("Path", "Default order field for Extra Networks cards", gr.Dropdown, {"choices": ['Path', 'Name', 'Date Created', 'Date Modified']}).needs_reload_ui(), "extra_networks_card_order": OptionInfo("Ascending", "Default order for Extra Networks cards", gr.Dropdown, {"choices": ['Ascending', 'Descending']}).needs_reload_ui(), - "extra_networks_tree_view_default_enabled": OptionInfo(False, "Enables the Extra Networks directory tree view by default").needs_reload_ui(), + "extra_networks_tree_view_style": OptionInfo("Dirs", "Extra Networks directory view style", gr.Radio, {"choices": ["Tree", "Dirs"]}).needs_reload_ui(), + "extra_networks_tree_view_default_enabled": OptionInfo(True, "Show the Extra Networks directory view by default").needs_reload_ui(), "extra_networks_tree_view_default_width": OptionInfo(180, "Default width for the Extra Networks directory tree view", gr.Number).needs_reload_ui(), "extra_networks_add_text_separator": OptionInfo(" ", "Extra networks separator").info("extra text to add before <...> when adding extra network to prompt"), "ui_extra_networks_tab_reorder": OptionInfo("", "Extra networks tab order").needs_reload_ui(), diff --git a/modules/ui_extra_networks.py b/modules/ui_extra_networks.py index 2cf91d36..9a1cf913 100644 --- a/modules/ui_extra_networks.py +++ b/modules/ui_extra_networks.py @@ -164,6 +164,8 @@ class ExtraNetworksPage: self.lister = util.MassFileLister() # HTML Templates self.pane_tpl = shared.html("extra-networks-pane.html") + self.pane_content_tree_tpl = shared.html("extra-networks-pane-tree.html") + self.pane_content_dirs_tpl = shared.html("extra-networks-pane-dirs.html") self.card_tpl = shared.html("extra-networks-card.html") self.btn_tree_tpl = shared.html("extra-networks-tree-button.html") self.btn_copy_path_tpl = shared.html("extra-networks-copy-path-button.html") @@ -476,6 +478,47 @@ class ExtraNetworksPage: return f"
      {res}
    " + def create_dirs_view_html(self, tabname: str) -> str: + """Generates HTML for displaying folders.""" + + subdirs = {} + for parentdir in [os.path.abspath(x) for x in self.allowed_directories_for_previews()]: + for root, dirs, _ in sorted(os.walk(parentdir, followlinks=True), key=lambda x: shared.natural_sort_key(x[0])): + for dirname in sorted(dirs, key=shared.natural_sort_key): + x = os.path.join(root, dirname) + + if not os.path.isdir(x): + continue + + subdir = os.path.abspath(x)[len(parentdir):] + + if shared.opts.extra_networks_dir_button_function: + if not subdir.startswith(os.path.sep): + subdir = os.path.sep + subdir + else: + while subdir.startswith(os.path.sep): + subdir = subdir[1:] + + is_empty = len(os.listdir(x)) == 0 + if not is_empty and not subdir.endswith(os.path.sep): + subdir = subdir + os.path.sep + + if (os.path.sep + "." in subdir or subdir.startswith(".")) and not shared.opts.extra_networks_show_hidden_directories: + continue + + subdirs[subdir] = 1 + + if subdirs: + subdirs = {"": 1, **subdirs} + + subdirs_html = "".join([f""" + + """ for subdir in subdirs]) + + return subdirs_html + def create_card_view_html(self, tabname: str, *, none_message) -> str: """Generates HTML for the network Card View section for a tab. @@ -529,32 +572,27 @@ class ExtraNetworksPage: data_sortdir = shared.opts.extra_networks_card_order data_sortmode = shared.opts.extra_networks_card_order_field.lower().replace("sort", "").replace(" ", "_").rstrip("_").strip() data_sortkey = f"{data_sortmode}-{data_sortdir}-{len(self.items)}" - tree_view_btn_extra_class = "" - tree_view_div_extra_class = "hidden" - tree_view_div_default_display = "none" - extra_network_pane_content_default_display = "flex" - if shared.opts.extra_networks_tree_view_default_enabled: - tree_view_btn_extra_class = "extra-network-control--enabled" - tree_view_div_extra_class = "" - tree_view_div_default_display = "block" - extra_network_pane_content_default_display = "grid" - return self.pane_tpl.format( - **{ - "tabname": tabname, - "extra_networks_tabname": self.extra_networks_tabname, - "data_sortmode": data_sortmode, - "data_sortkey": data_sortkey, - "data_sortdir": data_sortdir, - "tree_view_btn_extra_class": tree_view_btn_extra_class, - "tree_view_div_extra_class": tree_view_div_extra_class, - "tree_html": self.create_tree_view_html(tabname), - "items_html": self.create_card_view_html(tabname, none_message="Loading..." if empty else None), - "extra_networks_tree_view_default_width": shared.opts.extra_networks_tree_view_default_width, - "tree_view_div_default_display": tree_view_div_default_display, - "extra_network_pane_content_default_display": extra_network_pane_content_default_display, - } - ) + show_tree = shared.opts.extra_networks_tree_view_default_enabled + + page_params = { + "tabname": tabname, + "extra_networks_tabname": self.extra_networks_tabname, + "data_sortmode": data_sortmode, + "data_sortkey": data_sortkey, + "data_sortdir": data_sortdir, + "tree_view_btn_extra_class": "extra-network-control--enabled" if show_tree else "", + "items_html": self.create_card_view_html(tabname, none_message="Loading..." if empty else None), + "extra_networks_tree_view_default_width": shared.opts.extra_networks_tree_view_default_width, + "tree_view_div_default_display_class": "" if show_tree else "extra-network-dirs-hidden", + } + + if shared.opts.extra_networks_tree_view_style == "Tree": + pane_content = self.pane_content_tree_tpl.format(**page_params, tree_html=self.create_tree_view_html(tabname)) + else: + pane_content = self.pane_content_dirs_tpl.format(**page_params, dirs_html=self.create_dirs_view_html(tabname)) + + return self.pane_tpl.format(**page_params, pane_content=pane_content) def create_item(self, name, index=None): raise NotImplementedError() diff --git a/style.css b/style.css index 004038f8..49978a77 100644 --- a/style.css +++ b/style.css @@ -1205,12 +1205,24 @@ body.resizing .resize-handle { overflow: hidden; } -.extra-network-pane .extra-network-pane-content { +.extra-network-pane .extra-network-pane-content-dirs { + display: flex; + flex: 1; + flex-direction: column; + overflow: hidden; +} + +.extra-network-pane .extra-network-pane-content-tree { display: flex; flex: 1; overflow: hidden; } +.extra-network-dirs-hidden .extra-network-dirs{ display: none; } +.extra-network-dirs-hidden .extra-network-tree{ display: none; } +.extra-network-dirs-hidden .resize-handle { display: none; } +.extra-network-dirs-hidden .resize-handle-row { display: flex !important; } + .extra-network-pane .extra-network-tree { flex: 1; font-size: 1rem; From 01f531e9b162be94601b9f3a451cef9a9dd7dd53 Mon Sep 17 00:00:00 2001 From: SunChaser Date: Fri, 8 Mar 2024 17:25:28 +0800 Subject: [PATCH 079/496] fix: fix syntax errors --- modules/upscaler.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/modules/upscaler.py b/modules/upscaler.py index 3aee69db..cc662fc9 100644 --- a/modules/upscaler.py +++ b/modules/upscaler.py @@ -20,7 +20,7 @@ class Upscaler: filter = None model = None user_path = None - scalers: [] + scalers = [] tile = True def __init__(self, create_dirs=False): From 3bd75adb1c5a704fcce60a44138cb42c0301d699 Mon Sep 17 00:00:00 2001 From: AUTOMATIC1111 <16777216c@gmail.com> Date: Fri, 8 Mar 2024 16:54:39 +0300 Subject: [PATCH 080/496] optimization for extra networks filtering --- javascript/extraNetworks.js | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/javascript/extraNetworks.js b/javascript/extraNetworks.js index 6adf9ec0..ec6da69d 100644 --- a/javascript/extraNetworks.js +++ b/javascript/extraNetworks.js @@ -106,7 +106,9 @@ function setupExtraNetworksForTab(tabname) { }); }; - search.addEventListener("input", applyFilter); + search.addEventListener("input", function() { + applyFilter(); + }); applySort(); applyFilter(); extraNetworksApplySort[tabname_full] = applySort; @@ -458,7 +460,7 @@ function extraNetworksControlTreeViewOnClick(event, tabname, extra_networks_tabn */ var button = event.currentTarget; button.classList.toggle("extra-network-control--enabled"); - var show = ! button.classList.contains("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); From 530fea2bc4a2ab3412c76521961dd256b005a38b Mon Sep 17 00:00:00 2001 From: AUTOMATIC1111 <16777216c@gmail.com> Date: Fri, 8 Mar 2024 17:09:11 +0300 Subject: [PATCH 081/496] optimization for extra networks sorting --- extensions-builtin/Lora/network.py | 3 ++- javascript/extraNetworks.js | 17 +++++++++-------- 2 files changed, 11 insertions(+), 9 deletions(-) diff --git a/extensions-builtin/Lora/network.py b/extensions-builtin/Lora/network.py index 2268b0f7..b1426c6f 100644 --- a/extensions-builtin/Lora/network.py +++ b/extensions-builtin/Lora/network.py @@ -37,7 +37,8 @@ class NetworkOnDisk: try: self.metadata = cache.cached_data_for_file('safetensors-metadata', "lora/" + self.name, filename, read_metadata) except Exception as e: - errors.display(e, f"reading lora {filename}") + #errors.display(e, f"reading lora {filename}") + pass if self.metadata: m = {} diff --git a/javascript/extraNetworks.js b/javascript/extraNetworks.js index ec6da69d..b557d20d 100644 --- a/javascript/extraNetworks.js +++ b/javascript/extraNetworks.js @@ -71,7 +71,8 @@ function setupExtraNetworksForTab(tabname) { }; 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 sortKey = sort_mode.dataset.sortmode.toLowerCase().replace("sort", "").replaceAll(" ", "_").replace(/_+$/, "").trim() || "name"; sortKey = "sort" + sortKey.charAt(0).toUpperCase() + sortKey.slice(1); @@ -82,9 +83,6 @@ function setupExtraNetworksForTab(tabname) { } sort_mode.dataset.sortkey = sortKeyStore; - cards.forEach(function(card) { - card.originalParentElement = card.parentElement; - }); var sortedCards = Array.from(cards); sortedCards.sort(function(cardA, cardB) { var a = cardA.dataset[sortKey]; @@ -95,15 +93,18 @@ function setupExtraNetworksForTab(tabname) { return (a < b ? -1 : (a > b ? 1 : 0)); }); + if (reverse) { sortedCards.reverse(); } - cards.forEach(function(card) { - card.remove(); - }); + + parent.innerHTML = ''; + + var frag = document.createDocumentFragment(); sortedCards.forEach(function(card) { - card.originalParentElement.appendChild(card); + frag.appendChild(card); }); + parent.appendChild(frag); }; search.addEventListener("input", function() { From 758e8d7b4157c73065a20ff23194f61a9fd0d3cb Mon Sep 17 00:00:00 2001 From: AUTOMATIC1111 <16777216c@gmail.com> Date: Fri, 8 Mar 2024 17:11:42 +0300 Subject: [PATCH 082/496] undo unwanted change for extra networks --- extensions-builtin/Lora/network.py | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/extensions-builtin/Lora/network.py b/extensions-builtin/Lora/network.py index b1426c6f..2268b0f7 100644 --- a/extensions-builtin/Lora/network.py +++ b/extensions-builtin/Lora/network.py @@ -37,8 +37,7 @@ class NetworkOnDisk: try: self.metadata = cache.cached_data_for_file('safetensors-metadata', "lora/" + self.name, filename, read_metadata) except Exception as e: - #errors.display(e, f"reading lora {filename}") - pass + errors.display(e, f"reading lora {filename}") if self.metadata: m = {} From 7d1368c51ca39c23f65a3a7431211cd0235bddbe Mon Sep 17 00:00:00 2001 From: AUTOMATIC1111 <16777216c@gmail.com> Date: Fri, 8 Mar 2024 17:11:56 +0300 Subject: [PATCH 083/496] lint --- javascript/extraNetworks.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/javascript/extraNetworks.js b/javascript/extraNetworks.js index b557d20d..4d891b24 100644 --- a/javascript/extraNetworks.js +++ b/javascript/extraNetworks.js @@ -72,7 +72,7 @@ function setupExtraNetworksForTab(tabname) { var applySort = function(force) { var cards = gradioApp().querySelectorAll('#' + tabname_full + ' div.card'); - var parent = gradioApp().querySelector('#' + tabname_full + "_cards" ); + var parent = gradioApp().querySelector('#' + tabname_full + "_cards"); var reverse = sort_dir.dataset.sortdir == "Descending"; var sortKey = sort_mode.dataset.sortmode.toLowerCase().replace("sort", "").replaceAll(" ", "_").replace(/_+$/, "").trim() || "name"; sortKey = "sort" + sortKey.charAt(0).toUpperCase() + sortKey.slice(1); From 02a4ceabddeab10be9d5a3afbb4ef66fee81fe4c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E9=99=B3=E9=88=9E?= Date: Sat, 9 Mar 2024 02:07:42 +0800 Subject: [PATCH 084/496] chore: fix font not loaded fix #15182 --- style.css | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/style.css b/style.css index 49978a77..fe74ec41 100644 --- a/style.css +++ b/style.css @@ -1,6 +1,6 @@ /* temporary fix to load default gradio font in frontend instead of backend */ -@import url('webui-assets/css/sourcesanspro.css'); +@import url('/webui-assets/css/sourcesanspro.css'); /* temporary fix to hide gradio crop tool until it's fixed https://github.com/gradio-app/gradio/issues/3810 */ From c50b7e4eff88f52b22cd6881b47f3abf3bff00de Mon Sep 17 00:00:00 2001 From: 10sa Date: Sat, 9 Mar 2024 11:33:45 +0900 Subject: [PATCH 085/496] Add '--no-prompt-history' cmd args for disable last generation prompt history --- modules/cmd_args.py | 1 + modules/infotext_utils.py | 2 +- modules/processing.py | 2 +- 3 files changed, 3 insertions(+), 2 deletions(-) diff --git a/modules/cmd_args.py b/modules/cmd_args.py index bf355303..016a33d1 100644 --- a/modules/cmd_args.py +++ b/modules/cmd_args.py @@ -124,3 +124,4 @@ parser.add_argument("--disable-extra-extensions", action='store_true', help="pre 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") diff --git a/modules/infotext_utils.py b/modules/infotext_utils.py index db186644..a1cbfb17 100644 --- a/modules/infotext_utils.py +++ b/modules/infotext_utils.py @@ -462,7 +462,7 @@ def get_override_settings(params, *, skip_fields=None): def connect_paste(button, paste_fields, input_comp, override_settings_component, tabname): 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") try: with open(filename, "r", encoding="utf8") as file: diff --git a/modules/processing.py b/modules/processing.py index 93493f80..86194b05 100644 --- a/modules/processing.py +++ b/modules/processing.py @@ -904,7 +904,7 @@ def process_images_inner(p: StableDiffusionProcessing) -> Processed: # infotext could be modified by that callback # Example: a wildcard processed by process_batch sets an extra model # 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: processed = Processed(p, []) file.write(processed.infotext(p, 0)) From 5251733c0d6939c8d5ba71168e124634872c8dcd Mon Sep 17 00:00:00 2001 From: AUTOMATIC1111 <16777216c@gmail.com> Date: Sat, 9 Mar 2024 07:24:25 +0300 Subject: [PATCH 086/496] use natural sort in extra networks when ordering by path --- javascript/extraNetworks.js | 12 ++++-------- style.css | 2 +- 2 files changed, 5 insertions(+), 9 deletions(-) diff --git a/javascript/extraNetworks.js b/javascript/extraNetworks.js index 4d891b24..a816f498 100644 --- a/javascript/extraNetworks.js +++ b/javascript/extraNetworks.js @@ -406,25 +406,21 @@ function extraNetworksControlSortOnClick(event, tabname, extra_networks_tabname) * @param extra_networks_tabname The id of the active extraNetworks tab. Ex: lora, checkpoints, etc. */ 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") { + + if (curr_mode == "default") { 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.dataset.sortmode = "default"; event.currentTarget.setAttribute("title", "Sort by path"); } + applyExtraNetworkSort(tabname + "_" + extra_networks_tabname); } diff --git a/style.css b/style.css index fe74ec41..c2637ec8 100644 --- a/style.css +++ b/style.css @@ -1468,7 +1468,7 @@ body.resizing .resize-handle { background-color: var(--input-placeholder-color); } -.extra-network-control .extra-network-control--sort[data-sortmode="path"] .extra-network-control--sort-icon { +.extra-network-control .extra-network-control--sort[data-sortmode="default"] .extra-network-control--sort-icon { mask-image: url('data:image/svg+xml,'); } From 851c3d51eda1857a14d21ff3eb2e07c44ba262bc Mon Sep 17 00:00:00 2001 From: Kohaku-Blueleaf <59680068+KohakuBlueleaf@users.noreply.github.com> Date: Sat, 9 Mar 2024 12:31:32 +0800 Subject: [PATCH 087/496] Fix bugs for torch.nn.MultiheadAttention --- extensions-builtin/Lora/network.py | 8 +++++++- extensions-builtin/Lora/networks.py | 9 ++++++--- 2 files changed, 13 insertions(+), 4 deletions(-) diff --git a/extensions-builtin/Lora/network.py b/extensions-builtin/Lora/network.py index 2268b0f7..183f8bd7 100644 --- a/extensions-builtin/Lora/network.py +++ b/extensions-builtin/Lora/network.py @@ -117,6 +117,12 @@ class NetworkModule: if hasattr(self.sd_module, 'weight'): 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.extra_kwargs = {} @@ -146,7 +152,7 @@ class NetworkModule: 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.dora_scale = weights.w["dora_scale"] if "dora_scale" in weights.w else None + self.dora_scale = weights.w.get("dora_scale", None) self.dora_mean_dim = tuple(i for i in range(len(self.shape)) if i != 1) def multiplier(self): diff --git a/extensions-builtin/Lora/networks.py b/extensions-builtin/Lora/networks.py index 04bd1911..42b14dc2 100644 --- a/extensions-builtin/Lora/networks.py +++ b/extensions-builtin/Lora/networks.py @@ -429,9 +429,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: try: with torch.no_grad(): - updown_q, _ = module_q.calc_updown(self.in_proj_weight) - updown_k, _ = module_k.calc_updown(self.in_proj_weight) - updown_v, _ = module_v.calc_updown(self.in_proj_weight) + # Send "real" orig_weight into MHA's lora module + qw, kw, vw = self.in_proj_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]) updown_out, ex_bias = module_out.calc_updown(self.out_proj.weight) From 18d801a13d71b9a9e66722dead8b2e4a7a5612a9 Mon Sep 17 00:00:00 2001 From: AUTOMATIC1111 <16777216c@gmail.com> Date: Sat, 9 Mar 2024 08:25:01 +0300 Subject: [PATCH 088/496] stylistic changes for extra network sorting/search controls --- html/extra-networks-pane.html | 51 ++++++++++++++++++++++++++++------ javascript/extraNetworks.js | 52 +++++++++++++---------------------- modules/ui_extra_networks.py | 12 ++++---- style.css | 29 +++++++++++++++---- 4 files changed, 89 insertions(+), 55 deletions(-) diff --git a/html/extra-networks-pane.html b/html/extra-networks-pane.html index ff8a73ad..9a67baea 100644 --- a/html/extra-networks-pane.html +++ b/html/extra-networks-pane.html @@ -5,19 +5,49 @@ id="{tabname}_{extra_networks_tabname}_extra_search" class="extra-network-control--search-text" type="search" - placeholder="Filter files" + placeholder="Search" >
    + + Sort:
    - +
    +
    + +
    +
    + +
    +
    + +
    + +
    - +
    + + +
    - +
    - +
    {pane_content} diff --git a/javascript/extraNetworks.js b/javascript/extraNetworks.js index a816f498..8c390ab8 100644 --- a/javascript/extraNetworks.js +++ b/javascript/extraNetworks.js @@ -39,12 +39,12 @@ function setupExtraNetworksForTab(tabname) { // tabname_full = {tabname}_{extra_networks_tabname} var tabname_full = elem.id; 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 refresh = gradioApp().querySelector("#" + tabname_full + "_extra_refresh"); + var currentSort = ''; // If any of the buttons above don't exist, we want to skip this iteration of the loop. - if (!search || !sort_mode || !sort_dir || !refresh) { + if (!search || !sort_dir || !refresh) { return; // `return` is equivalent of `continue` but for forEach loops. } @@ -74,19 +74,20 @@ function setupExtraNetworksForTab(tabname) { var cards = gradioApp().querySelectorAll('#' + tabname_full + ' div.card'); var parent = gradioApp().querySelector('#' + tabname_full + "_cards"); var reverse = sort_dir.dataset.sortdir == "Descending"; - var sortKey = sort_mode.dataset.sortmode.toLowerCase().replace("sort", "").replaceAll(" ", "_").replace(/_+$/, "").trim() || "name"; - sortKey = "sort" + sortKey.charAt(0).toUpperCase() + sortKey.slice(1); - var sortKeyStore = sortKey + "-" + (reverse ? "Descending" : "Ascending") + "-" + cards.length; + var activeSearchElem = gradioApp().querySelector('#' + tabname_full + "_controls .extra-network-control--sort.extra-network-control--enabled"); + var sortKey = activeSearchElem ? activeSearchElem.dataset.sortkey : "default"; + var sortKeyDataField = "sort" + sortKey.charAt(0).toUpperCase() + sortKey.slice(1); + var sortKeyStore = sortKey + "-" + sort_dir.dataset.sortdir + "-" + cards.length; - if (sortKeyStore == sort_mode.dataset.sortkey && !force) { + if (sortKeyStore == currentSort && !force) { return; } - sort_mode.dataset.sortkey = sortKeyStore; + currentSort = sortKeyStore; var sortedCards = Array.from(cards); sortedCards.sort(function(cardA, cardB) { - var a = cardA.dataset[sortKey]; - var b = cardB.dataset[sortKey]; + var a = cardA.dataset[sortKeyDataField]; + var b = cardB.dataset[sortKeyDataField]; if (!isNaN(a) && !isNaN(b)) { return parseInt(a) - parseInt(b); } @@ -395,31 +396,16 @@ function extraNetworksTreeOnClick(event, tabname, extra_networks_tabname) { } function extraNetworksControlSortOnClick(event, tabname, extra_networks_tabname) { - /** - * Handles `onclick` events for the Sort Mode button. - * - * Modifies the data attributes of the Sort Mode button to cycle between - * various sorting modes. - * - * @param event The generated event. - * @param tabname The name of the active tab in the sd webui. Ex: txt2img, img2img, etc. - * @param extra_networks_tabname The id of the active extraNetworks tab. Ex: lora, checkpoints, etc. - */ - var curr_mode = event.currentTarget.dataset.sortmode; + /** Handles `onclick` events for Sort Mode buttons. */ - if (curr_mode == "default") { - event.currentTarget.dataset.sortmode = "name"; - event.currentTarget.setAttribute("title", "Sort by filename"); - } else if (curr_mode == "name") { - event.currentTarget.dataset.sortmode = "date_created"; - event.currentTarget.setAttribute("title", "Sort by date created"); - } else if (curr_mode == "date_created") { - event.currentTarget.dataset.sortmode = "date_modified"; - event.currentTarget.setAttribute("title", "Sort by date modified"); - } else { - event.currentTarget.dataset.sortmode = "default"; - event.currentTarget.setAttribute("title", "Sort by path"); - } + var self = event.currentTarget; + var parent = event.currentTarget.parentElement; + + parent.querySelectorAll('.extra-network-control--sort').forEach(function(x){ + x.classList.remove('extra-network-control--enabled'); + }); + + self.classList.add('extra-network-control--enabled'); applyExtraNetworkSort(tabname + "_" + extra_networks_tabname); } diff --git a/modules/ui_extra_networks.py b/modules/ui_extra_networks.py index 9a1cf913..f4627ce8 100644 --- a/modules/ui_extra_networks.py +++ b/modules/ui_extra_networks.py @@ -569,18 +569,16 @@ class ExtraNetworksPage: if "user_metadata" not in item: self.read_user_metadata(item) - data_sortdir = shared.opts.extra_networks_card_order - data_sortmode = shared.opts.extra_networks_card_order_field.lower().replace("sort", "").replace(" ", "_").rstrip("_").strip() - data_sortkey = f"{data_sortmode}-{data_sortdir}-{len(self.items)}" - show_tree = shared.opts.extra_networks_tree_view_default_enabled page_params = { "tabname": tabname, "extra_networks_tabname": self.extra_networks_tabname, - "data_sortmode": data_sortmode, - "data_sortkey": data_sortkey, - "data_sortdir": data_sortdir, + "data_sortdir": shared.opts.extra_networks_card_order, + "sort_path_active": ' extra-network-control--enabled' if shared.opts.extra_networks_card_order_field == 'Path' else '', + "sort_name_active": ' extra-network-control--enabled' if shared.opts.extra_networks_card_order_field == 'Name' else '', + "sort_date_created_active": ' extra-network-control--enabled' if shared.opts.extra_networks_card_order_field == 'Date Created' else '', + "sort_date_modified_active": ' extra-network-control--enabled' if shared.opts.extra_networks_card_order_field == 'Date Modified' else '', "tree_view_btn_extra_class": "extra-network-control--enabled" if show_tree else "", "items_html": self.create_card_view_html(tabname, none_message="Loading..." if empty else None), "extra_networks_tree_view_default_width": shared.opts.extra_networks_tree_view_default_width, diff --git a/style.css b/style.css index c2637ec8..29eae412 100644 --- a/style.css +++ b/style.css @@ -1272,7 +1272,7 @@ body.resizing .resize-handle { .extra-network-control { position: relative; - display: grid; + display: flex; width: 100%; padding: 0 !important; margin-top: 0 !important; @@ -1289,6 +1289,12 @@ body.resizing .resize-handle { align-items: start; } +.extra-network-control small{ + color: var(--input-placeholder-color); + line-height: 2.2rem; + margin: 0 0.5rem 0 0.75rem; +} + .extra-network-tree .tree-list--tree {} /* Remove auto indentation from tree. Will be overridden later. */ @@ -1436,6 +1442,12 @@ body.resizing .resize-handle { line-height: 1rem; } + +.extra-network-control .extra-network-control--search .extra-network-control--search-text::placeholder { + color: var(--input-placeholder-color); +} + + /* clear button (x on right side) styling */ .extra-network-control .extra-network-control--search .extra-network-control--search-text::-webkit-search-cancel-button { -webkit-appearance: none; @@ -1468,19 +1480,19 @@ body.resizing .resize-handle { background-color: var(--input-placeholder-color); } -.extra-network-control .extra-network-control--sort[data-sortmode="default"] .extra-network-control--sort-icon { +.extra-network-control .extra-network-control--sort[data-sortkey="default"] .extra-network-control--sort-icon { mask-image: url('data:image/svg+xml,'); } -.extra-network-control .extra-network-control--sort[data-sortmode="name"] .extra-network-control--sort-icon { +.extra-network-control .extra-network-control--sort[data-sortkey="name"] .extra-network-control--sort-icon { mask-image: url('data:image/svg+xml,'); } -.extra-network-control .extra-network-control--sort[data-sortmode="date_created"] .extra-network-control--sort-icon { +.extra-network-control .extra-network-control--sort[data-sortkey="date_created"] .extra-network-control--sort-icon { mask-image: url('data:image/svg+xml,'); } -.extra-network-control .extra-network-control--sort[data-sortmode="date_modified"] .extra-network-control--sort-icon { +.extra-network-control .extra-network-control--sort[data-sortkey="date_modified"] .extra-network-control--sort-icon { mask-image: url('data:image/svg+xml,'); } @@ -1530,13 +1542,18 @@ body.resizing .resize-handle { } .extra-network-control .extra-network-control--enabled { - background-color: rgba(0, 0, 0, 0.15); + background-color: rgba(0, 0, 0, 0.1); + border-radius: 0.25rem; } .dark .extra-network-control .extra-network-control--enabled { background-color: rgba(255, 255, 255, 0.15); } +.extra-network-control .extra-network-control--enabled .extra-network-control--icon{ + background-color: var(--button-secondary-text-color); +} + /* ==== REFRESH ICON ACTIONS ==== */ .extra-network-control .extra-network-control--refresh { padding: 0.25rem; From 0dc179ee7256690db9e63864bd330f235911e5d1 Mon Sep 17 00:00:00 2001 From: Kohaku-Blueleaf <59680068+KohakuBlueleaf@users.noreply.github.com> Date: Sat, 9 Mar 2024 17:12:54 +0800 Subject: [PATCH 089/496] Avoid error from None --- modules/sd_models_xl.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/modules/sd_models_xl.py b/modules/sd_models_xl.py index 0de17af3..94ff973f 100644 --- a/modules/sd_models_xl.py +++ b/modules/sd_models_xl.py @@ -13,8 +13,8 @@ def get_learned_conditioning(self: sgm.models.diffusion.DiffusionEngine, batch: for embedder in self.conditioner.embedders: embedder.ucg_rate = 0.0 - width = getattr(batch, 'width', 1024) - height = getattr(batch, 'height', 1024) + width = getattr(batch, 'width', 1024) or 1024 + height = getattr(batch, 'height', 1024) or 1024 is_negative_prompt = getattr(batch, 'is_negative_prompt', False) aesthetic_score = shared.opts.sdxl_refiner_low_aesthetic_score if is_negative_prompt else shared.opts.sdxl_refiner_high_aesthetic_score From 6136db1409b1d9d4a01358a558602fec40562488 Mon Sep 17 00:00:00 2001 From: AUTOMATIC1111 <16777216c@gmail.com> Date: Sat, 9 Mar 2024 12:21:46 +0300 Subject: [PATCH 090/496] linter --- javascript/extraNetworks.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/javascript/extraNetworks.js b/javascript/extraNetworks.js index 8c390ab8..c0c53cc3 100644 --- a/javascript/extraNetworks.js +++ b/javascript/extraNetworks.js @@ -401,7 +401,7 @@ function extraNetworksControlSortOnClick(event, tabname, extra_networks_tabname) var self = event.currentTarget; var parent = event.currentTarget.parentElement; - parent.querySelectorAll('.extra-network-control--sort').forEach(function(x){ + parent.querySelectorAll('.extra-network-control--sort').forEach(function(x) { x.classList.remove('extra-network-control--enabled'); }); From 0085e719a91a480ad1297423f63554be23f2418f Mon Sep 17 00:00:00 2001 From: Alexandre Macabies Date: Sat, 9 Mar 2024 21:53:38 +0100 Subject: [PATCH 091/496] 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. --- javascript/extraNetworks.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/javascript/extraNetworks.js b/javascript/extraNetworks.js index c0c53cc3..be5f0f30 100644 --- a/javascript/extraNetworks.js +++ b/javascript/extraNetworks.js @@ -52,7 +52,7 @@ function setupExtraNetworksForTab(tabname) { var searchTerm = search.value.toLowerCase(); gradioApp().querySelectorAll('#' + tabname + '_extra_tabs div.card').forEach(function(elem) { 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(); }).join(" "); From fb62f1fb4090b900eba07c27cf7d394a770e7efd Mon Sep 17 00:00:00 2001 From: w-e-w <40751091+w-e-w@users.noreply.github.com> Date: Sun, 10 Mar 2024 06:07:16 +0900 Subject: [PATCH 092/496] add entry to MassFileLister after writing metadata fix #15184 --- modules/ui_extra_networks_user_metadata.py | 7 +++---- modules/util.py | 17 +++++++++++++++++ 2 files changed, 20 insertions(+), 4 deletions(-) diff --git a/modules/ui_extra_networks_user_metadata.py b/modules/ui_extra_networks_user_metadata.py index 2ca937fd..6bc25a4d 100644 --- a/modules/ui_extra_networks_user_metadata.py +++ b/modules/ui_extra_networks_user_metadata.py @@ -133,8 +133,10 @@ class UserMetadataEditor: filename = item.get("filename", None) basename, ext = os.path.splitext(filename) - with open(basename + '.json', "w", encoding="utf8") as file: + metadata_path = basename + '.json' + with open(metadata_path, "w", encoding="utf8") as file: json.dump(metadata, file, indent=4, ensure_ascii=False) + self.page.lister.update_file_entry(metadata_path) def save_user_metadata(self, name, desc, notes): user_metadata = self.get_user_metadata(name) @@ -200,6 +202,3 @@ class UserMetadataEditor: inputs=[self.edit_name_input], outputs=[] ) - - - diff --git a/modules/util.py b/modules/util.py index 8d1aea44..cb690e73 100644 --- a/modules/util.py +++ b/modules/util.py @@ -81,6 +81,17 @@ class MassFileListerCachedDir: self.files = {x[0].lower(): x for x in files} self.files_cased = {x[0]: x for x in files} + def update_entry(self, filename): + """Add a file to the cache""" + file_path = os.path.join(self.dirname, filename) + try: + stat = os.stat(file_path) + entry = (filename, stat.st_mtime, stat.st_ctime) + self.files[filename.lower()] = entry + self.files_cased[filename] = entry + except FileNotFoundError as e: + print(f'MassFileListerCachedDir.add_entry: "{file_path}" {e}') + class MassFileLister: """A class that provides a way to check for the existence and mtime/ctile of files without doing more than one stat call per file.""" @@ -136,3 +147,9 @@ class MassFileLister: def reset(self): """Clear the cache of all directories.""" self.cached_dirs.clear() + + def update_file_entry(self, path): + """Update the cache for a specific directory.""" + dirname, filename = os.path.split(path) + if cached_dir := self.cached_dirs.get(dirname): + cached_dir.update_entry(filename) From 0411eced89688f55aec356eea0c7d29377d37bc8 Mon Sep 17 00:00:00 2001 From: AUTOMATIC1111 <16777216c@gmail.com> Date: Sun, 10 Mar 2024 07:52:57 +0300 Subject: [PATCH 093/496] add names to callbacks --- modules/extensions.py | 17 +++++ modules/script_callbacks.py | 126 +++++++++++++++++++++--------------- 2 files changed, 91 insertions(+), 52 deletions(-) diff --git a/modules/extensions.py b/modules/extensions.py index 04bda297..ab835d3f 100644 --- a/modules/extensions.py +++ b/modules/extensions.py @@ -186,6 +186,7 @@ class Extension: def list_extensions(): extensions.clear() + extension_paths.clear() if shared.cmd_opts.disable_all_extensions: print("*** \"--disable-all-extensions\" arg was used, will not load any extensions ***") @@ -220,6 +221,7 @@ def list_extensions(): 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) extensions.append(extension) + extension_paths[extension.path] = extension loaded_extensions[canonical_name] = extension # check for requirements @@ -238,4 +240,19 @@ def list_extensions(): continue +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 + + extensions: list[Extension] = [] +extension_paths: dict[str, Extension] = {} diff --git a/modules/script_callbacks.py b/modules/script_callbacks.py index 08bc5256..98952ec7 100644 --- a/modules/script_callbacks.py +++ b/modules/script_callbacks.py @@ -1,13 +1,14 @@ +from __future__ import annotations + import dataclasses import inspect import os -from collections import namedtuple from typing import Optional, Any from fastapi import FastAPI from gradio import Blocks -from modules import errors, timer +from modules import errors, timer, extensions def report_exception(c, job): @@ -116,7 +117,35 @@ class BeforeTokenCounterParams: is_positive: bool = True -ScriptCallback = namedtuple("ScriptCallback", ["script", "callback"]) +@dataclasses.dataclass +class ScriptCallback: + script: str + callback: '' + name: str = None + + +def add_callback(callbacks, fun, *, name=None, category='unknown'): + 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)) + + callback_map = dict( callbacks_app_started=[], callbacks_model_loaded=[], @@ -328,13 +357,6 @@ def before_token_counter_callback(params: BeforeTokenCounterParams): 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(): stack = [x for x in inspect.stack() if x.filename != __file__] filename = stack[0].filename if stack else 'unknown file' @@ -351,24 +373,24 @@ def remove_callbacks_for_function(callback_func): 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 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.""" - 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 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. The function must either return a None, which means no new tabs to be added, or a list, where each element is a tuple: @@ -378,71 +400,71 @@ def on_ui_tabs(callback): title is tab text displayed to user in the UI 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. 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 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. The callback is called with one argument: - 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. The callback is called with one argument: - 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; The callback is called with one argument: - 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. The callback is called with one argument: - 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. The callback is called with one argument: - 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. The callback is called with one argument: - 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. The callback is called with arguments: - component - gradio component that is about to be created. @@ -451,61 +473,61 @@ def on_before_component(callback): 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. """ - 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.""" - 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. The callback is called with one argument: - 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. The callback is called with two arguments: - infotext: str - raw infotext. - 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 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.""" - 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. The function will be called with one argument, a list, and shall add objects of type modules.sd_hijack_optimizations.SdOptimization 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. 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. 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') From 9b842e9ec745000248d00c3ebef2d599e8f033fa Mon Sep 17 00:00:00 2001 From: SunChaser Date: Sun, 10 Mar 2024 16:19:59 +0800 Subject: [PATCH 094/496] fix: resolve type annotation warnings --- modules/upscaler.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/modules/upscaler.py b/modules/upscaler.py index cc662fc9..9d13ee99 100644 --- a/modules/upscaler.py +++ b/modules/upscaler.py @@ -20,7 +20,7 @@ class Upscaler: filter = None model = None user_path = None - scalers = [] + scalers: list = [] tile = True def __init__(self, create_dirs=False): From 9fd0cd6a805ee0a0a0651de41d4c6154407a045f Mon Sep 17 00:00:00 2001 From: w-e-w <40751091+w-e-w@users.noreply.github.com> Date: Sun, 10 Mar 2024 18:24:52 +0900 Subject: [PATCH 095/496] update preview on Replace Preview --- modules/ui_extra_networks_user_metadata.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/modules/ui_extra_networks_user_metadata.py b/modules/ui_extra_networks_user_metadata.py index 6bc25a4d..fde09370 100644 --- a/modules/ui_extra_networks_user_metadata.py +++ b/modules/ui_extra_networks_user_metadata.py @@ -187,7 +187,8 @@ class UserMetadataEditor: geninfo, items = images.read_info_from_image(image) images.save_image_with_geninfo(image, geninfo, item["local_preview"]) - + self.page.lister.update_file_entry(item["local_preview"]) + item['preview'] = self.page.find_preview(item["local_preview"]) return self.get_card_html(name), '' def setup_ui(self, gallery): From 7e5e67330b092a65a8dd9e04a18969c458cb05d1 Mon Sep 17 00:00:00 2001 From: AUTOMATIC1111 <16777216c@gmail.com> Date: Sun, 10 Mar 2024 14:07:51 +0300 Subject: [PATCH 096/496] add UI for reordering callbacks --- modules/script_callbacks.py | 92 +++++++++++++++++++++++++++---------- modules/scripts.py | 90 ++++++++++++++++++++++++++++-------- modules/shared_items.py | 42 +++++++++++++++++ modules/ui_settings.py | 8 +++- style.css | 4 ++ 5 files changed, 192 insertions(+), 44 deletions(-) diff --git a/modules/script_callbacks.py b/modules/script_callbacks.py index 98952ec7..a0ecf5a5 100644 --- a/modules/script_callbacks.py +++ b/modules/script_callbacks.py @@ -8,7 +8,7 @@ from typing import Optional, Any from fastapi import FastAPI from gradio import Blocks -from modules import errors, timer, extensions +from modules import errors, timer, extensions, shared def report_exception(c, job): @@ -124,9 +124,10 @@ class ScriptCallback: name: str = None -def add_callback(callbacks, fun, *, name=None, category='unknown'): - stack = [x for x in inspect.stack() if x.filename != __file__] - filename = stack[0].filename if stack else 'unknown file' +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' @@ -146,6 +147,43 @@ def add_callback(callbacks, fun, *, name=None, category='unknown'): callbacks.append(ScriptCallback(filename, fun, unique_callback_name)) +def sort_callbacks(category, unordered_callbacks, *, enable_user_sort=True): + callbacks = unordered_callbacks.copy() + + 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( callbacks_app_started=[], callbacks_model_loaded=[], @@ -170,14 +208,18 @@ callback_map = dict( callbacks_before_token_counter=[], ) +ordered_callbacks_map = {} + def clear_callbacks(): for callback_list in callback_map.values(): callback_list.clear() + ordered_callbacks_map.clear() + 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: c.callback(demo, app) timer.startup_timer.record(os.path.basename(c.script)) @@ -186,7 +228,7 @@ def app_started_callback(demo: Optional[Blocks], app: FastAPI): def app_reload_callback(): - for c in callback_map['callbacks_on_reload']: + for c in ordered_callbacks('on_reload'): try: c.callback() except Exception: @@ -194,7 +236,7 @@ def app_reload_callback(): def model_loaded_callback(sd_model): - for c in callback_map['callbacks_model_loaded']: + for c in ordered_callbacks('model_loaded'): try: c.callback(sd_model) except Exception: @@ -204,7 +246,7 @@ def model_loaded_callback(sd_model): def ui_tabs_callback(): res = [] - for c in callback_map['callbacks_ui_tabs']: + for c in ordered_callbacks('ui_tabs'): try: res += c.callback() or [] except Exception: @@ -214,7 +256,7 @@ def ui_tabs_callback(): 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: c.callback(params) except Exception: @@ -222,7 +264,7 @@ def ui_train_tabs_callback(params: UiTrainTabParams): def ui_settings_callback(): - for c in callback_map['callbacks_ui_settings']: + for c in ordered_callbacks('ui_settings'): try: c.callback() except Exception: @@ -230,7 +272,7 @@ def ui_settings_callback(): 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: c.callback(params) except Exception: @@ -238,7 +280,7 @@ def before_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: c.callback(params) except Exception: @@ -246,7 +288,7 @@ def image_saved_callback(params: ImageSaveParams): def extra_noise_callback(params: ExtraNoiseParams): - for c in callback_map['callbacks_extra_noise']: + for c in ordered_callbacks('extra_noise'): try: c.callback(params) except Exception: @@ -254,7 +296,7 @@ def extra_noise_callback(params: ExtraNoiseParams): def cfg_denoiser_callback(params: CFGDenoiserParams): - for c in callback_map['callbacks_cfg_denoiser']: + for c in ordered_callbacks('cfg_denoiser'): try: c.callback(params) except Exception: @@ -262,7 +304,7 @@ def cfg_denoiser_callback(params: CFGDenoiserParams): def cfg_denoised_callback(params: CFGDenoisedParams): - for c in callback_map['callbacks_cfg_denoised']: + for c in ordered_callbacks('cfg_denoised'): try: c.callback(params) except Exception: @@ -270,7 +312,7 @@ def cfg_denoised_callback(params: CFGDenoisedParams): 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: c.callback(params) except Exception: @@ -278,7 +320,7 @@ def cfg_after_cfg_callback(params: AfterCFGCallbackParams): def before_component_callback(component, **kwargs): - for c in callback_map['callbacks_before_component']: + for c in ordered_callbacks('before_component'): try: c.callback(component, **kwargs) except Exception: @@ -286,7 +328,7 @@ def before_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: c.callback(component, **kwargs) except Exception: @@ -294,7 +336,7 @@ def after_component_callback(component, **kwargs): def image_grid_callback(params: ImageGridLoopParams): - for c in callback_map['callbacks_image_grid']: + for c in ordered_callbacks('image_grid'): try: c.callback(params) except Exception: @@ -302,7 +344,7 @@ def image_grid_callback(params: ImageGridLoopParams): 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: c.callback(infotext, params) except Exception: @@ -310,7 +352,7 @@ def infotext_pasted_callback(infotext: str, params: dict[str, Any]): def script_unloaded_callback(): - for c in reversed(callback_map['callbacks_script_unloaded']): + for c in reversed(ordered_callbacks('script_unloaded')): try: c.callback() except Exception: @@ -318,7 +360,7 @@ def script_unloaded_callback(): def before_ui_callback(): - for c in reversed(callback_map['callbacks_before_ui']): + for c in reversed(ordered_callbacks('before_ui')): try: c.callback() except Exception: @@ -328,7 +370,7 @@ def before_ui_callback(): def list_optimizers_callback(): res = [] - for c in callback_map['callbacks_list_optimizers']: + for c in ordered_callbacks('list_optimizers'): try: c.callback(res) except Exception: @@ -340,7 +382,7 @@ def list_optimizers_callback(): def list_unets_callback(): res = [] - for c in callback_map['callbacks_list_unets']: + for c in ordered_callbacks('list_unets'): try: c.callback(res) except Exception: @@ -350,7 +392,7 @@ def list_unets_callback(): 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: c.callback(params) except Exception: diff --git a/modules/scripts.py b/modules/scripts.py index 77f5e4f3..e1a43582 100644 --- a/modules/scripts.py +++ b/modules/scripts.py @@ -138,7 +138,6 @@ class Script: """ pass - def before_process(self, p, *args): """ This function is called very early during processing begins for AlwaysVisible scripts. @@ -562,6 +561,25 @@ class ScriptRunner: self.paste_field_names = [] 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 = {} """dict of callbacks to be called before an element is created; key=elem_id, value=list of callbacks""" @@ -600,6 +618,8 @@ class ScriptRunner: self.scripts.append(script) self.selectable_scripts.append(script) + self.callback_map.clear() + self.apply_on_before_component_callbacks() def apply_on_before_component_callbacks(self): @@ -769,8 +789,42 @@ class ScriptRunner: 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): - for script in self.alwayson_scripts: + for script in self.ordered_scripts('before_process'): try: script_args = p.script_args[script.args_from:script.args_to] script.before_process(p, *script_args) @@ -778,7 +832,7 @@ class ScriptRunner: errors.report(f"Error running before_process: {script.filename}", exc_info=True) def process(self, p): - for script in self.alwayson_scripts: + for script in self.ordered_scripts('process'): try: script_args = p.script_args[script.args_from:script.args_to] script.process(p, *script_args) @@ -786,7 +840,7 @@ class ScriptRunner: errors.report(f"Error running process: {script.filename}", exc_info=True) def before_process_batch(self, p, **kwargs): - for script in self.alwayson_scripts: + for script in self.ordered_scripts('before_process_batch'): try: script_args = p.script_args[script.args_from:script.args_to] script.before_process_batch(p, *script_args, **kwargs) @@ -794,7 +848,7 @@ class ScriptRunner: errors.report(f"Error running before_process_batch: {script.filename}", exc_info=True) 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: script_args = p.script_args[script.args_from:script.args_to] script.after_extra_networks_activate(p, *script_args, **kwargs) @@ -802,7 +856,7 @@ class ScriptRunner: errors.report(f"Error running after_extra_networks_activate: {script.filename}", exc_info=True) def process_batch(self, p, **kwargs): - for script in self.alwayson_scripts: + for script in self.ordered_scripts('process_batch'): try: script_args = p.script_args[script.args_from:script.args_to] script.process_batch(p, *script_args, **kwargs) @@ -810,7 +864,7 @@ class ScriptRunner: errors.report(f"Error running process_batch: {script.filename}", exc_info=True) def postprocess(self, p, processed): - for script in self.alwayson_scripts: + for script in self.ordered_scripts('postprocess'): try: script_args = p.script_args[script.args_from:script.args_to] script.postprocess(p, processed, *script_args) @@ -818,7 +872,7 @@ class ScriptRunner: errors.report(f"Error running postprocess: {script.filename}", exc_info=True) def postprocess_batch(self, p, images, **kwargs): - for script in self.alwayson_scripts: + for script in self.ordered_scripts('postprocess_batch'): try: script_args = p.script_args[script.args_from:script.args_to] script.postprocess_batch(p, *script_args, images=images, **kwargs) @@ -826,7 +880,7 @@ class ScriptRunner: errors.report(f"Error running postprocess_batch: {script.filename}", exc_info=True) 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: script_args = p.script_args[script.args_from:script.args_to] script.postprocess_batch_list(p, pp, *script_args, **kwargs) @@ -834,7 +888,7 @@ class ScriptRunner: errors.report(f"Error running postprocess_batch_list: {script.filename}", exc_info=True) def post_sample(self, p, ps: PostSampleArgs): - for script in self.alwayson_scripts: + for script in self.ordered_scripts('post_sample'): try: script_args = p.script_args[script.args_from:script.args_to] script.post_sample(p, ps, *script_args) @@ -842,7 +896,7 @@ class ScriptRunner: errors.report(f"Error running post_sample: {script.filename}", exc_info=True) def on_mask_blend(self, p, mba: MaskBlendArgs): - for script in self.alwayson_scripts: + for script in self.ordered_scripts('on_mask_blend'): try: script_args = p.script_args[script.args_from:script.args_to] script.on_mask_blend(p, mba, *script_args) @@ -850,7 +904,7 @@ class ScriptRunner: errors.report(f"Error running post_sample: {script.filename}", exc_info=True) def postprocess_image(self, p, pp: PostprocessImageArgs): - for script in self.alwayson_scripts: + for script in self.ordered_scripts('postprocess_image'): try: script_args = p.script_args[script.args_from:script.args_to] script.postprocess_image(p, pp, *script_args) @@ -858,7 +912,7 @@ class ScriptRunner: errors.report(f"Error running postprocess_image: {script.filename}", exc_info=True) def postprocess_maskoverlay(self, p, ppmo: PostProcessMaskOverlayArgs): - for script in self.alwayson_scripts: + for script in self.ordered_scripts('postprocess_maskoverlay'): try: script_args = p.script_args[script.args_from:script.args_to] script.postprocess_maskoverlay(p, ppmo, *script_args) @@ -866,7 +920,7 @@ class ScriptRunner: errors.report(f"Error running postprocess_image: {script.filename}", exc_info=True) 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: script_args = p.script_args[script.args_from:script.args_to] script.postprocess_image_after_composite(p, pp, *script_args) @@ -880,7 +934,7 @@ class ScriptRunner: except Exception: 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: script.before_component(component, **kwargs) except Exception: @@ -893,7 +947,7 @@ class ScriptRunner: except Exception: 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: script.after_component(component, **kwargs) except Exception: @@ -921,7 +975,7 @@ class ScriptRunner: self.scripts[si].args_to = args_to def before_hr(self, p): - for script in self.alwayson_scripts: + for script in self.ordered_scripts('before_hr'): try: script_args = p.script_args[script.args_from:script.args_to] script.before_hr(p, *script_args) @@ -929,7 +983,7 @@ class ScriptRunner: errors.report(f"Error running before_hr: {script.filename}", exc_info=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: continue diff --git a/modules/shared_items.py b/modules/shared_items.py index 88f63645..11f10b3f 100644 --- a/modules/shared_items.py +++ b/modules/shared_items.py @@ -1,5 +1,8 @@ +import html import sys +from modules import script_callbacks, scripts, ui_components +from modules.options import OptionHTML, OptionInfo from modules.shared_cmd_options import cmd_opts @@ -118,6 +121,45 @@ def ui_reorder_categories(): yield "scripts" +def callbacks_order_settings(): + options = { + "sd_vae_explanation": OptionHTML(""" + For categories below, callbacks added to dropdowns happen before others, in order listed. + """), + + } + + callback_options = {} + + for category, _ in script_callbacks.enumerate_callbacks(): + callback_options[category] = script_callbacks.ordered_callbacks(category, enable_user_sort=False) + + for method_name in scripts.scripts_txt2img.callback_names: + callback_options["script_" + method_name] = scripts.scripts_txt2img.create_ordered_callbacks_list(method_name, enable_user_sort=False) + + for method_name in scripts.scripts_img2img.callback_names: + callbacks = callback_options.get("script_" + method_name, []) + + for addition in scripts.scripts_img2img.create_ordered_callbacks_list(method_name, enable_user_sort=False): + if any(x.name == addition.name for x in callbacks): + continue + + callbacks.append(addition) + + callback_options["script_" + method_name] = callbacks + + for category, callbacks in callback_options.items(): + if not callbacks: + continue + + option_info = OptionInfo([], f"{category} callback priority", ui_components.DropdownMulti, {"choices": [x.name for x in callbacks]}) + option_info.needs_restart() + option_info.html("
    Default order:
      " + "".join(f"
    1. {html.escape(x.name)}
    2. \n" for x in callbacks) + "
    ") + options['prioritized_callbacks_' + category] = option_info + + return options + + class Shared(sys.modules[__name__].__class__): """ this class is here to provide sd_model field as a property, so that it can be created and loaded on demand rather than diff --git a/modules/ui_settings.py b/modules/ui_settings.py index d17ef1d9..087b91f3 100644 --- a/modules/ui_settings.py +++ b/modules/ui_settings.py @@ -1,7 +1,8 @@ import gradio as gr -from modules import ui_common, shared, script_callbacks, scripts, sd_models, sysinfo, timer +from modules import ui_common, shared, script_callbacks, scripts, sd_models, sysinfo, timer, shared_items from modules.call_queue import wrap_gradio_call +from modules.options import options_section from modules.shared import opts from modules.ui_components import FormRow from modules.ui_gradio_extensions import reload_javascript @@ -108,6 +109,11 @@ class UiSettings: shared.settings_components = self.component_dict + # we add this as late as possible so that scripts have already registered their callbacks + opts.data_labels.update(options_section(('callbacks', "Callbacks", "system"), { + **shared_items.callbacks_order_settings(), + })) + opts.reorder() with gr.Blocks(analytics_enabled=False) as settings_interface: diff --git a/style.css b/style.css index 29eae412..f6a89b8f 100644 --- a/style.css +++ b/style.css @@ -528,6 +528,10 @@ table.popup-table .link{ opacity: 0.75; } +.settings-comment .info ol{ + margin: 0.4em 0 0.8em 1em; +} + #sysinfo_download a.sysinfo_big_link{ font-size: 24pt; } From 2f55d669a26ca2c785b92656b8f32b56839f3750 Mon Sep 17 00:00:00 2001 From: AUTOMATIC1111 <16777216c@gmail.com> Date: Sun, 10 Mar 2024 15:14:04 +0300 Subject: [PATCH 097/496] add support for specifying callback order in metadata --- modules/extensions.py | 24 ++++++++++++++++++++++++ modules/script_callbacks.py | 34 +++++++++++++++++++++++++++++++++- modules/scripts.py | 27 +++------------------------ modules/util.py | 24 ++++++++++++++++++++++++ 4 files changed, 84 insertions(+), 25 deletions(-) diff --git a/modules/extensions.py b/modules/extensions.py index ab835d3f..1f620ff1 100644 --- a/modules/extensions.py +++ b/modules/extensions.py @@ -1,6 +1,7 @@ from __future__ import annotations import configparser +import dataclasses import os import threading import re @@ -22,6 +23,13 @@ def active(): return [x for x in extensions if x.enabled] +@dataclasses.dataclass +class CallbackOrderInfo: + name: str + before: list + after: list + + class ExtensionMetadata: filename = "metadata.ini" config: configparser.ConfigParser @@ -65,6 +73,22 @@ class ExtensionMetadata: # both "," and " " are accepted as separator 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: lock = threading.Lock() diff --git a/modules/script_callbacks.py b/modules/script_callbacks.py index a0ecf5a5..4d80b433 100644 --- a/modules/script_callbacks.py +++ b/modules/script_callbacks.py @@ -8,7 +8,7 @@ from typing import Optional, Any from fastapi import FastAPI from gradio import Blocks -from modules import errors, timer, extensions, shared +from modules import errors, timer, extensions, shared, util def report_exception(c, job): @@ -149,6 +149,38 @@ def add_callback(callbacks, fun, *, name=None, category='unknown', filename=None 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, [])): diff --git a/modules/scripts.py b/modules/scripts.py index e1a43582..20710b37 100644 --- a/modules/scripts.py +++ b/modules/scripts.py @@ -7,7 +7,9 @@ from dataclasses import dataclass 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() @@ -368,29 +370,6 @@ scripts_data = [] postprocessing_scripts_data = [] 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 class ScriptWithDependencies: diff --git a/modules/util.py b/modules/util.py index 8d1aea44..48268f04 100644 --- a/modules/util.py +++ b/modules/util.py @@ -136,3 +136,27 @@ class MassFileLister: def reset(self): """Clear the cache of all directories.""" self.cached_dirs.clear() + + +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 From 3670b4f49e070e8b8da20c88f4d6bda9ba18895c Mon Sep 17 00:00:00 2001 From: AUTOMATIC1111 <16777216c@gmail.com> Date: Sun, 10 Mar 2024 15:16:12 +0300 Subject: [PATCH 098/496] lint --- modules/script_callbacks.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/modules/script_callbacks.py b/modules/script_callbacks.py index 4d80b433..2cd65e83 100644 --- a/modules/script_callbacks.py +++ b/modules/script_callbacks.py @@ -120,7 +120,7 @@ class BeforeTokenCounterParams: @dataclasses.dataclass class ScriptCallback: script: str - callback: '' + callback: any name: str = None From 3e0146f9bdd79ed13d1fed729c76b97f7ab91587 Mon Sep 17 00:00:00 2001 From: AUTOMATIC1111 <16777216c@gmail.com> Date: Sun, 10 Mar 2024 22:40:35 +0300 Subject: [PATCH 099/496] restore the lost Uncategorized options section --- modules/options.py | 3 +++ 1 file changed, 3 insertions(+) diff --git a/modules/options.py b/modules/options.py index 35ccade2..2a78a825 100644 --- a/modules/options.py +++ b/modules/options.py @@ -240,6 +240,9 @@ class Options: item_categories = {} for item in self.data_labels.values(): + if item.section[0] is None: + continue + category = categories.mapping.get(item.category_id) category = "Uncategorized" if category is None else category.label if category not in item_categories: From eb10da8bb74a0f664c01052f04168837387598b2 Mon Sep 17 00:00:00 2001 From: Andray Date: Mon, 11 Mar 2024 05:15:09 +0400 Subject: [PATCH 100/496] type hinting in shared.py --- modules/shared.py | 28 ++++++++++++++++------------ 1 file changed, 16 insertions(+), 12 deletions(-) diff --git a/modules/shared.py b/modules/shared.py index b4ba14ad..8d179153 100644 --- a/modules/shared.py +++ b/modules/shared.py @@ -7,6 +7,10 @@ from modules import shared_cmd_options, shared_gradio_themes, options, shared_it from modules.paths_internal import models_path, script_path, data_path, sd_configs_path, sd_default_config, sd_model_file, default_sd_model_file, extensions_dir, extensions_builtin_dir # noqa: F401 from modules import util +falseVar = False # avoid circular import for type hinting +if falseVar: + from modules import shared_state, styles, interrogate, shared_total_tqdm, memmon + cmd_opts = shared_cmd_options.cmd_opts parser = shared_cmd_options.parser @@ -16,11 +20,11 @@ styles_filename = cmd_opts.styles_file = cmd_opts.styles_file if len(cmd_opts.st config_filename = cmd_opts.ui_settings_file hide_dirs = {"visible": not cmd_opts.hide_ui_dir_config} -demo = None +demo: gr.Blocks = None -device = None +device: str = None -weight_load_location = None +weight_load_location: str = None xformers_available = False @@ -28,21 +32,21 @@ hypernetworks = {} loaded_hypernetworks = [] -state = None +state: 'shared_state.State' = None -prompt_styles = None +prompt_styles: 'styles.StyleDatabase' = None -interrogator = None +interrogator: 'interrogate.InterrogateModels' = None face_restorers = [] -options_templates = None -opts = None -restricted_opts = None +options_templates: dict = None +opts: options.Options = None +restricted_opts: set[str] = None sd_model: sd_models_types.WebuiSdModel = None -settings_components = None +settings_components: dict = None """assigned from ui.py, a mapping on setting names to gradio components repsponsible for those settings""" tab_names = [] @@ -65,9 +69,9 @@ progress_print_out = sys.stdout gradio_theme = gr.themes.Base() -total_tqdm = None +total_tqdm: 'shared_total_tqdm.TotalTQDM' = None -mem_mon = None +mem_mon: 'memmon.MemUsageMonitor' = None options_section = options.options_section OptionInfo = options.OptionInfo From 2d57a2df660ec096969c89eb1ec72ebaa9a34636 Mon Sep 17 00:00:00 2001 From: Andray <33491867+light-and-ray@users.noreply.github.com> Date: Mon, 11 Mar 2024 07:40:15 +0400 Subject: [PATCH 101/496] Update modules/shared.py Co-authored-by: catboxanon <122327233+catboxanon@users.noreply.github.com> --- modules/shared.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/modules/shared.py b/modules/shared.py index 8d179153..4cf7f6a8 100644 --- a/modules/shared.py +++ b/modules/shared.py @@ -6,9 +6,9 @@ import gradio as gr from modules import shared_cmd_options, shared_gradio_themes, options, shared_items, sd_models_types from modules.paths_internal import models_path, script_path, data_path, sd_configs_path, sd_default_config, sd_model_file, default_sd_model_file, extensions_dir, extensions_builtin_dir # noqa: F401 from modules import util +from typing import TYPE_CHECKING -falseVar = False # avoid circular import for type hinting -if falseVar: +if TYPE_CHECKING: from modules import shared_state, styles, interrogate, shared_total_tqdm, memmon cmd_opts = shared_cmd_options.cmd_opts From 1a1205f601d27e1ca5052e01cf4f877615f6a499 Mon Sep 17 00:00:00 2001 From: w-e-w <40751091+w-e-w@users.noreply.github.com> Date: Tue, 12 Mar 2024 03:26:50 +0900 Subject: [PATCH 102/496] fix Restore progress --- javascript/ui.js | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/javascript/ui.js b/javascript/ui.js index 1eef6d33..e0f5feeb 100644 --- a/javascript/ui.js +++ b/javascript/ui.js @@ -136,8 +136,7 @@ function showSubmitInterruptingPlaceholder(tabname) { function showRestoreProgressButton(tabname, show) { var button = gradioApp().getElementById(tabname + "_restore_progress"); if (!button) return; - - button.style.display = show ? "flex" : "none"; + button.style.setProperty('display', show ? 'flex' : 'none', 'important'); } function submit() { @@ -209,6 +208,7 @@ function restoreProgressTxt2img() { var id = localGet("txt2img_task_id"); if (id) { + showSubmitInterruptingPlaceholder('txt2img'); requestProgress(id, gradioApp().getElementById('txt2img_gallery_container'), gradioApp().getElementById('txt2img_gallery'), function() { showSubmitButtons('txt2img', true); }, null, 0); @@ -223,6 +223,7 @@ function restoreProgressImg2img() { var id = localGet("img2img_task_id"); if (id) { + showSubmitInterruptingPlaceholder('img2img'); requestProgress(id, gradioApp().getElementById('img2img_gallery_container'), gradioApp().getElementById('img2img_gallery'), function() { showSubmitButtons('img2img', true); }, null, 0); From 4079b17dd979564bd762b42b31162c7e3e2edb7b Mon Sep 17 00:00:00 2001 From: Andray Date: Tue, 12 Mar 2024 01:47:23 +0400 Subject: [PATCH 103/496] move postprocessing-for-training into builtin extensions --- .../scripts/postprocessing_autosized_crop.py | 0 .../scripts}/postprocessing_caption.py | 0 .../scripts}/postprocessing_create_flipped_copies.py | 0 .../scripts}/postprocessing_focal_crop.py | 0 .../scripts}/postprocessing_split_oversized.py | 0 5 files changed, 0 insertions(+), 0 deletions(-) rename scripts/processing_autosized_crop.py => extensions-builtin/postprocessing-for-training/scripts/postprocessing_autosized_crop.py (100%) rename {scripts => extensions-builtin/postprocessing-for-training/scripts}/postprocessing_caption.py (100%) rename {scripts => extensions-builtin/postprocessing-for-training/scripts}/postprocessing_create_flipped_copies.py (100%) rename {scripts => extensions-builtin/postprocessing-for-training/scripts}/postprocessing_focal_crop.py (100%) rename {scripts => extensions-builtin/postprocessing-for-training/scripts}/postprocessing_split_oversized.py (100%) diff --git a/scripts/processing_autosized_crop.py b/extensions-builtin/postprocessing-for-training/scripts/postprocessing_autosized_crop.py similarity index 100% rename from scripts/processing_autosized_crop.py rename to extensions-builtin/postprocessing-for-training/scripts/postprocessing_autosized_crop.py diff --git a/scripts/postprocessing_caption.py b/extensions-builtin/postprocessing-for-training/scripts/postprocessing_caption.py similarity index 100% rename from scripts/postprocessing_caption.py rename to extensions-builtin/postprocessing-for-training/scripts/postprocessing_caption.py diff --git a/scripts/postprocessing_create_flipped_copies.py b/extensions-builtin/postprocessing-for-training/scripts/postprocessing_create_flipped_copies.py similarity index 100% rename from scripts/postprocessing_create_flipped_copies.py rename to extensions-builtin/postprocessing-for-training/scripts/postprocessing_create_flipped_copies.py diff --git a/scripts/postprocessing_focal_crop.py b/extensions-builtin/postprocessing-for-training/scripts/postprocessing_focal_crop.py similarity index 100% rename from scripts/postprocessing_focal_crop.py rename to extensions-builtin/postprocessing-for-training/scripts/postprocessing_focal_crop.py diff --git a/scripts/postprocessing_split_oversized.py b/extensions-builtin/postprocessing-for-training/scripts/postprocessing_split_oversized.py similarity index 100% rename from scripts/postprocessing_split_oversized.py rename to extensions-builtin/postprocessing-for-training/scripts/postprocessing_split_oversized.py From 2e3a0f39f6bcbb53837e43341507ffda0bd36eec Mon Sep 17 00:00:00 2001 From: Andray Date: Tue, 12 Mar 2024 02:28:15 +0400 Subject: [PATCH 104/496] move upscale postprocessing under input accordion --- scripts/postprocessing_upscale.py | 26 +++++++++++++++----------- 1 file changed, 15 insertions(+), 11 deletions(-) diff --git a/scripts/postprocessing_upscale.py b/scripts/postprocessing_upscale.py index e269682d..80692e5c 100644 --- a/scripts/postprocessing_upscale.py +++ b/scripts/postprocessing_upscale.py @@ -4,7 +4,7 @@ import numpy as np from modules import scripts_postprocessing, shared import gradio as gr -from modules.ui_components import FormRow, ToolButton +from modules.ui_components import FormRow, ToolButton, InputAccordion from modules.ui import switch_values_symbol upscale_cache = {} @@ -17,7 +17,14 @@ class ScriptPostprocessingUpscale(scripts_postprocessing.ScriptPostprocessing): def ui(self): selected_tab = gr.Number(value=0, visible=False) - with gr.Column(): + with InputAccordion(True, label="Upscale", elem_id="extras_upscale") as upscale_enabled: + with FormRow(): + extras_upscaler_1 = gr.Dropdown(label='Upscaler 1', elem_id="extras_upscaler_1", choices=[x.name for x in shared.sd_upscalers], value=shared.sd_upscalers[0].name) + + with FormRow(): + extras_upscaler_2 = gr.Dropdown(label='Upscaler 2', elem_id="extras_upscaler_2", choices=[x.name for x in shared.sd_upscalers], value=shared.sd_upscalers[0].name) + extras_upscaler_2_visibility = gr.Slider(minimum=0.0, maximum=1.0, step=0.001, label="Upscaler 2 visibility", value=0.0, elem_id="extras_upscaler_2_visibility") + with FormRow(): with gr.Tabs(elem_id="extras_resize_mode"): with gr.TabItem('Scale by', elem_id="extras_scale_by_tab") as tab_scale_by: @@ -32,18 +39,12 @@ class ScriptPostprocessingUpscale(scripts_postprocessing.ScriptPostprocessing): upscaling_res_switch_btn = ToolButton(value=switch_values_symbol, elem_id="upscaling_res_switch_btn", tooltip="Switch width/height") upscaling_crop = gr.Checkbox(label='Crop to fit', value=True, elem_id="extras_upscaling_crop") - with FormRow(): - extras_upscaler_1 = gr.Dropdown(label='Upscaler 1', elem_id="extras_upscaler_1", choices=[x.name for x in shared.sd_upscalers], value=shared.sd_upscalers[0].name) - - with FormRow(): - extras_upscaler_2 = gr.Dropdown(label='Upscaler 2', elem_id="extras_upscaler_2", choices=[x.name for x in shared.sd_upscalers], value=shared.sd_upscalers[0].name) - extras_upscaler_2_visibility = gr.Slider(minimum=0.0, maximum=1.0, step=0.001, label="Upscaler 2 visibility", value=0.0, elem_id="extras_upscaler_2_visibility") - upscaling_res_switch_btn.click(lambda w, h: (h, w), inputs=[upscaling_resize_w, upscaling_resize_h], outputs=[upscaling_resize_w, upscaling_resize_h], show_progress=False) tab_scale_by.select(fn=lambda: 0, inputs=[], outputs=[selected_tab]) tab_scale_to.select(fn=lambda: 1, inputs=[], outputs=[selected_tab]) return { + "upscale_enabled": upscale_enabled, "upscale_mode": selected_tab, "upscale_by": upscaling_resize, "upscale_to_width": upscaling_resize_w, @@ -81,7 +82,7 @@ class ScriptPostprocessingUpscale(scripts_postprocessing.ScriptPostprocessing): return image - def process_firstpass(self, pp: scripts_postprocessing.PostprocessedImage, upscale_mode=1, upscale_by=2.0, upscale_to_width=None, upscale_to_height=None, upscale_crop=False, upscaler_1_name=None, upscaler_2_name=None, upscaler_2_visibility=0.0): + def process_firstpass(self, pp: scripts_postprocessing.PostprocessedImage, upscale_enabled=True, upscale_mode=1, upscale_by=2.0, upscale_to_width=None, upscale_to_height=None, upscale_crop=False, upscaler_1_name=None, upscaler_2_name=None, upscaler_2_visibility=0.0): if upscale_mode == 1: pp.shared.target_width = upscale_to_width pp.shared.target_height = upscale_to_height @@ -89,7 +90,10 @@ class ScriptPostprocessingUpscale(scripts_postprocessing.ScriptPostprocessing): pp.shared.target_width = int(pp.image.width * upscale_by) pp.shared.target_height = int(pp.image.height * upscale_by) - def process(self, pp: scripts_postprocessing.PostprocessedImage, upscale_mode=1, upscale_by=2.0, upscale_to_width=None, upscale_to_height=None, upscale_crop=False, upscaler_1_name=None, upscaler_2_name=None, upscaler_2_visibility=0.0): + def process(self, pp: scripts_postprocessing.PostprocessedImage, upscale_enabled=True, upscale_mode=1, upscale_by=2.0, upscale_to_width=None, upscale_to_height=None, upscale_crop=False, upscaler_1_name=None, upscaler_2_name=None, upscaler_2_visibility=0.0): + if not upscale_enabled: + return + if upscaler_1_name == "None": upscaler_1_name = None From 8262cd71c49e58a109ca8d5e57d6590831b5ead7 Mon Sep 17 00:00:00 2001 From: DGdev91 Date: Tue, 12 Mar 2024 00:09:07 +0100 Subject: [PATCH 105/496] Better workaround for Navi1, removing --pre for Navi3 --- webui.sh | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/webui.sh b/webui.sh index 361255f6..89c52b51 100755 --- a/webui.sh +++ b/webui.sh @@ -129,11 +129,11 @@ case "$gpu_info" in export HSA_OVERRIDE_GFX_VERSION=10.3.0 if [[ -z "${TORCH_COMMAND}" ]] then - pyv="$(${python_cmd} -c 'import sys; print(".".join(map(str, sys.version_info[0:2])))')" - if [[ $(bc <<< "$pyv <= 3.10") -eq 1 ]] + pyv="$(${python_cmd} -c 'import sys; print(float(".".join(map(str, sys.version_info[0:2]))) <= 3.10)')" + if [[ $pyv == "True" ]] then - # Navi users will still use torch 1.13 because 2.0 does not seem to work. - export TORCH_COMMAND="pip install --pre torch torchvision --index-url https://download.pytorch.org/whl/nightly/rocm5.6" + # Using an old nightly compiled against rocm 5.2 for Navi1, see https://github.com/pytorch/pytorch/issues/106728#issuecomment-1749511711 + export TORCH_COMMAND="pip install https://download.pytorch.org/whl/nightly/rocm5.2/torch-2.0.0.dev20230209%2Brocm5.2-cp310-cp310-linux_x86_64.whl https://download.pytorch.org/whl/nightly/rocm5.2/torchvision-0.15.0.dev20230209%2Brocm5.2-cp310-cp310-linux_x86_64.whl" else printf "\e[1m\e[31mERROR: RX 5000 series GPUs must be using at max python 3.10, aborting...\e[0m" exit 1 @@ -143,7 +143,7 @@ case "$gpu_info" in *"Navi 2"*) export HSA_OVERRIDE_GFX_VERSION=10.3.0 ;; *"Navi 3"*) [[ -z "${TORCH_COMMAND}" ]] && \ - export TORCH_COMMAND="pip install --pre torch torchvision --index-url https://download.pytorch.org/whl/nightly/rocm5.7" + export TORCH_COMMAND="pip install torch torchvision --index-url https://download.pytorch.org/whl/nightly/rocm5.7" ;; *"Renoir"*) export HSA_OVERRIDE_GFX_VERSION=9.0.0 printf "\n%s\n" "${delimiter}" From 994e08aac1f80932dbd87a59a75ca6d4411bfe3a Mon Sep 17 00:00:00 2001 From: wangshuai09 <391746016@qq.com> Date: Tue, 12 Mar 2024 18:41:44 +0800 Subject: [PATCH 106/496] ascend npu readme --- README.md | 1 + 1 file changed, 1 insertion(+) diff --git a/README.md b/README.md index f4cfcf29..bc08e7ad 100644 --- a/README.md +++ b/README.md @@ -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) - [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) +- [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): From b980c8140ba336b3151cef7b4d1c1d2a3bca9130 Mon Sep 17 00:00:00 2001 From: Andray Date: Tue, 12 Mar 2024 22:21:59 +0400 Subject: [PATCH 107/496] featch only active branch updates for extensions --- modules/extensions.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/modules/extensions.py b/modules/extensions.py index 04bda297..6542cb7a 100644 --- a/modules/extensions.py +++ b/modules/extensions.py @@ -156,6 +156,8 @@ class Extension: def check_updates(self): repo = Repo(self.path) for fetch in repo.remote().fetch(dry_run=True): + if self.branch and fetch.name != f'{repo.remote().name}/{self.branch}': + continue if fetch.flags != fetch.HEAD_UPTODATE: self.can_update = True self.status = "new commits" From 74e2e5279c6a4147a8a893a137c028fda0479d07 Mon Sep 17 00:00:00 2001 From: DGdev91 Date: Wed, 13 Mar 2024 00:17:24 +0100 Subject: [PATCH 108/496] Workaround for Navi1: pytorch nightly whl for 3.8 and 3.9 --- webui.sh | 14 ++++++++++---- 1 file changed, 10 insertions(+), 4 deletions(-) diff --git a/webui.sh b/webui.sh index 89c52b51..0b1d4d09 100755 --- a/webui.sh +++ b/webui.sh @@ -129,13 +129,19 @@ case "$gpu_info" in export HSA_OVERRIDE_GFX_VERSION=10.3.0 if [[ -z "${TORCH_COMMAND}" ]] then - pyv="$(${python_cmd} -c 'import sys; print(float(".".join(map(str, sys.version_info[0:2]))) <= 3.10)')" - if [[ $pyv == "True" ]] + pyv="$(${python_cmd} -c 'import sys; print(".".join(map(str, sys.version_info[0:2])))')" + # Using an old nightly compiled against rocm 5.2 for Navi1, see https://github.com/pytorch/pytorch/issues/106728#issuecomment-1749511711 + if [[ $pyv == "3.8" ]] + then + export TORCH_COMMAND="pip install https://download.pytorch.org/whl/nightly/rocm5.2/torch-2.0.0.dev20230209%2Brocm5.2-cp38-cp38-linux_x86_64.whl https://download.pytorch.org/whl/nightly/rocm5.2/torchvision-0.15.0.dev20230209%2Brocm5.2-cp38-cp38-linux_x86_64.whl" + if [[ $pyv == "3.9" ]] + then + export TORCH_COMMAND="pip install https://download.pytorch.org/whl/nightly/rocm5.2/torch-2.0.0.dev20230209%2Brocm5.2-cp39-cp39-linux_x86_64.whl https://download.pytorch.org/whl/nightly/rocm5.2/torchvision-0.15.0.dev20230209%2Brocm5.2-cp39-cp39-linux_x86_64.whl" + if [[ $pyv == "3.10" ]] then - # Using an old nightly compiled against rocm 5.2 for Navi1, see https://github.com/pytorch/pytorch/issues/106728#issuecomment-1749511711 export TORCH_COMMAND="pip install https://download.pytorch.org/whl/nightly/rocm5.2/torch-2.0.0.dev20230209%2Brocm5.2-cp310-cp310-linux_x86_64.whl https://download.pytorch.org/whl/nightly/rocm5.2/torchvision-0.15.0.dev20230209%2Brocm5.2-cp310-cp310-linux_x86_64.whl" else - printf "\e[1m\e[31mERROR: RX 5000 series GPUs must be using at max python 3.10, aborting...\e[0m" + printf "\e[1m\e[31mERROR: RX 5000 series GPUs python version must be between 3.8 and 3.10, aborting...\e[0m" exit 1 fi fi From 9fbfb8ad324415597765e3e63b1ce4a123f91539 Mon Sep 17 00:00:00 2001 From: DGdev91 Date: Wed, 13 Mar 2024 00:43:01 +0100 Subject: [PATCH 109/496] Better workaround for Navi1 - fix if --- webui.sh | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/webui.sh b/webui.sh index 0b1d4d09..b348c387 100755 --- a/webui.sh +++ b/webui.sh @@ -134,10 +134,10 @@ case "$gpu_info" in if [[ $pyv == "3.8" ]] then export TORCH_COMMAND="pip install https://download.pytorch.org/whl/nightly/rocm5.2/torch-2.0.0.dev20230209%2Brocm5.2-cp38-cp38-linux_x86_64.whl https://download.pytorch.org/whl/nightly/rocm5.2/torchvision-0.15.0.dev20230209%2Brocm5.2-cp38-cp38-linux_x86_64.whl" - if [[ $pyv == "3.9" ]] + elif [[ $pyv == "3.9" ]] then export TORCH_COMMAND="pip install https://download.pytorch.org/whl/nightly/rocm5.2/torch-2.0.0.dev20230209%2Brocm5.2-cp39-cp39-linux_x86_64.whl https://download.pytorch.org/whl/nightly/rocm5.2/torchvision-0.15.0.dev20230209%2Brocm5.2-cp39-cp39-linux_x86_64.whl" - if [[ $pyv == "3.10" ]] + elif [[ $pyv == "3.10" ]] then export TORCH_COMMAND="pip install https://download.pytorch.org/whl/nightly/rocm5.2/torch-2.0.0.dev20230209%2Brocm5.2-cp310-cp310-linux_x86_64.whl https://download.pytorch.org/whl/nightly/rocm5.2/torchvision-0.15.0.dev20230209%2Brocm5.2-cp310-cp310-linux_x86_64.whl" else From 2efc7c1b0535e65274844c2e554bcdfd2b29a1f0 Mon Sep 17 00:00:00 2001 From: DGdev91 Date: Wed, 13 Mar 2024 00:54:32 +0100 Subject: [PATCH 110/496] Better workaround for Navi1, removing --pre for Navi3 --- webui.sh | 16 +++++++++++----- 1 file changed, 11 insertions(+), 5 deletions(-) diff --git a/webui.sh b/webui.sh index 361255f6..b348c387 100755 --- a/webui.sh +++ b/webui.sh @@ -130,12 +130,18 @@ case "$gpu_info" in if [[ -z "${TORCH_COMMAND}" ]] then pyv="$(${python_cmd} -c 'import sys; print(".".join(map(str, sys.version_info[0:2])))')" - if [[ $(bc <<< "$pyv <= 3.10") -eq 1 ]] + # Using an old nightly compiled against rocm 5.2 for Navi1, see https://github.com/pytorch/pytorch/issues/106728#issuecomment-1749511711 + if [[ $pyv == "3.8" ]] then - # Navi users will still use torch 1.13 because 2.0 does not seem to work. - export TORCH_COMMAND="pip install --pre torch torchvision --index-url https://download.pytorch.org/whl/nightly/rocm5.6" + export TORCH_COMMAND="pip install https://download.pytorch.org/whl/nightly/rocm5.2/torch-2.0.0.dev20230209%2Brocm5.2-cp38-cp38-linux_x86_64.whl https://download.pytorch.org/whl/nightly/rocm5.2/torchvision-0.15.0.dev20230209%2Brocm5.2-cp38-cp38-linux_x86_64.whl" + elif [[ $pyv == "3.9" ]] + then + export TORCH_COMMAND="pip install https://download.pytorch.org/whl/nightly/rocm5.2/torch-2.0.0.dev20230209%2Brocm5.2-cp39-cp39-linux_x86_64.whl https://download.pytorch.org/whl/nightly/rocm5.2/torchvision-0.15.0.dev20230209%2Brocm5.2-cp39-cp39-linux_x86_64.whl" + elif [[ $pyv == "3.10" ]] + then + export TORCH_COMMAND="pip install https://download.pytorch.org/whl/nightly/rocm5.2/torch-2.0.0.dev20230209%2Brocm5.2-cp310-cp310-linux_x86_64.whl https://download.pytorch.org/whl/nightly/rocm5.2/torchvision-0.15.0.dev20230209%2Brocm5.2-cp310-cp310-linux_x86_64.whl" else - printf "\e[1m\e[31mERROR: RX 5000 series GPUs must be using at max python 3.10, aborting...\e[0m" + printf "\e[1m\e[31mERROR: RX 5000 series GPUs python version must be between 3.8 and 3.10, aborting...\e[0m" exit 1 fi fi @@ -143,7 +149,7 @@ case "$gpu_info" in *"Navi 2"*) export HSA_OVERRIDE_GFX_VERSION=10.3.0 ;; *"Navi 3"*) [[ -z "${TORCH_COMMAND}" ]] && \ - export TORCH_COMMAND="pip install --pre torch torchvision --index-url https://download.pytorch.org/whl/nightly/rocm5.7" + export TORCH_COMMAND="pip install torch torchvision --index-url https://download.pytorch.org/whl/nightly/rocm5.7" ;; *"Renoir"*) export HSA_OVERRIDE_GFX_VERSION=9.0.0 printf "\n%s\n" "${delimiter}" From 9f2ae1cb85015ee3cc79f1de92641032df154d5b Mon Sep 17 00:00:00 2001 From: KohakuBlueleaf Date: Wed, 13 Mar 2024 11:47:33 +0800 Subject: [PATCH 111/496] Add missing .mean --- extensions-builtin/Lora/network.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/extensions-builtin/Lora/network.py b/extensions-builtin/Lora/network.py index 183f8bd7..473ba29e 100644 --- a/extensions-builtin/Lora/network.py +++ b/extensions-builtin/Lora/network.py @@ -173,7 +173,7 @@ class NetworkModule: orig_weight = orig_weight.to(updown) merged_scale1 = updown + orig_weight dora_merged = ( - merged_scale1 / merged_scale1(dim=self.dora_mean_dim, keepdim=True) * self.dora_scale + merged_scale1 / merged_scale1.mean(dim=self.dora_mean_dim, keepdim=True) * self.dora_scale ) final_updown = dora_merged - orig_weight return final_updown From d18eb10ecdbf3690571d2d7ce0c0fe548d00d836 Mon Sep 17 00:00:00 2001 From: Haoming Date: Wed, 13 Mar 2024 21:15:52 +0800 Subject: [PATCH 112/496] add hook --- modules/ui_postprocessing.py | 25 +++++++++++++++++++++++++ 1 file changed, 25 insertions(+) diff --git a/modules/ui_postprocessing.py b/modules/ui_postprocessing.py index 7261c2df..e9d82d46 100644 --- a/modules/ui_postprocessing.py +++ b/modules/ui_postprocessing.py @@ -4,6 +4,30 @@ import modules.infotext_utils as parameters_copypaste from modules.ui_components import ResizeHandleRow +def hook_scale_update(inputs): + resize = upscaler = None + for script in inputs: + if script.label == "Resize": + resize = script + elif script.label == "Upscaler 1": + upscaler = script + elif resize and upscaler: + break + + def update_scale(upscaler: str, slider: float): + if upscaler[1] in ('x', 'X'): + try: + scale = int(upscaler[0]) + return gr.update(value=scale) + except ValueError: + return gr.update(value=slider) + + return gr.update(value=slider) + + if resize and upscaler: + upscaler.input(update_scale, inputs=[upscaler, resize], outputs=[resize]) + + def create_ui(): dummy_component = gr.Label(visible=False) tab_index = gr.Number(value=0, visible=False) @@ -23,6 +47,7 @@ def create_ui(): show_extras_results = gr.Checkbox(label='Show result images', value=True, elem_id="extras_show_extras_results") script_inputs = scripts.scripts_postproc.setup_ui() + hook_scale_update(script_inputs) with gr.Column(): toprow = ui_toprow.Toprow(is_compact=True, is_img2img=False, id_part="extras") From fd71b761ff6b6636204ffcd71212cdbb7bb5d658 Mon Sep 17 00:00:00 2001 From: Haoming Date: Thu, 14 Mar 2024 09:55:14 +0800 Subject: [PATCH 113/496] use re instead of hardcoding Now supports all natively provided upscaler as well --- modules/ui_postprocessing.py | 20 +++++++++++++------- 1 file changed, 13 insertions(+), 7 deletions(-) diff --git a/modules/ui_postprocessing.py b/modules/ui_postprocessing.py index e9d82d46..7fd88910 100644 --- a/modules/ui_postprocessing.py +++ b/modules/ui_postprocessing.py @@ -5,6 +5,9 @@ from modules.ui_components import ResizeHandleRow def hook_scale_update(inputs): + import re + pattern = r'(\d)[xX]|[xX](\d)' + resize = upscaler = None for script in inputs: if script.label == "Resize": @@ -15,14 +18,17 @@ def hook_scale_update(inputs): break def update_scale(upscaler: str, slider: float): - if upscaler[1] in ('x', 'X'): - try: - scale = int(upscaler[0]) - return gr.update(value=scale) - except ValueError: - return gr.update(value=slider) + match = re.search(pattern, upscaler) - return gr.update(value=slider) + if match: + if match.group(1): + return gr.update(value=int(match.group(1))) + + else: + return gr.update(value=int(match.group(2))) + + else: + return gr.update(value=slider) if resize and upscaler: upscaler.input(update_scale, inputs=[upscaler, resize], outputs=[resize]) From 4e17fc36d87a1d983c542dbfb606a93e70a68e2a Mon Sep 17 00:00:00 2001 From: Haoming Date: Thu, 14 Mar 2024 10:04:09 +0800 Subject: [PATCH 114/496] add user setting Now this is disabled by default --- modules/shared_options.py | 1 + modules/ui_postprocessing.py | 3 ++- 2 files changed, 3 insertions(+), 1 deletion(-) diff --git a/modules/shared_options.py b/modules/shared_options.py index 21643afe..99a051aa 100644 --- a/modules/shared_options.py +++ b/modules/shared_options.py @@ -101,6 +101,7 @@ options_templates.update(options_section(('upscaling', "Upscaling", "postprocess "DAT_tile": OptionInfo(192, "Tile size for DAT upscalers.", gr.Slider, {"minimum": 0, "maximum": 512, "step": 16}).info("0 = no tiling"), "DAT_tile_overlap": OptionInfo(8, "Tile overlap for DAT upscalers.", gr.Slider, {"minimum": 0, "maximum": 48, "step": 1}).info("Low values = visible seam"), "upscaler_for_img2img": OptionInfo(None, "Upscaler for img2img", gr.Dropdown, lambda: {"choices": [x.name for x in shared.sd_upscalers]}), + "scaleBy_from_upscaler": OptionInfo(False, "Automatically set the Scale by factor based on the name of the selected Upscaler.").info("Will not change the value when no matching pattern is found."), })) options_templates.update(options_section(('face-restoration', "Face restoration", "postprocessing"), { diff --git a/modules/ui_postprocessing.py b/modules/ui_postprocessing.py index 7fd88910..af44f619 100644 --- a/modules/ui_postprocessing.py +++ b/modules/ui_postprocessing.py @@ -53,7 +53,8 @@ def create_ui(): show_extras_results = gr.Checkbox(label='Show result images', value=True, elem_id="extras_show_extras_results") script_inputs = scripts.scripts_postproc.setup_ui() - hook_scale_update(script_inputs) + if getattr(shared.opts, 'scaleBy_from_upscaler', False): + hook_scale_update(script_inputs) with gr.Column(): toprow = ui_toprow.Toprow(is_compact=True, is_img2img=False, id_part="extras") From c40f33ca0475a39a44576bc32dba9f87b011e9b2 Mon Sep 17 00:00:00 2001 From: w-e-w <40751091+w-e-w@users.noreply.github.com> Date: Fri, 15 Mar 2024 08:22:36 +0900 Subject: [PATCH 115/496] PEP 604 annotations --- modules/styles.py | 1 + 1 file changed, 1 insertion(+) diff --git a/modules/styles.py b/modules/styles.py index a9d8636a..25f22d3d 100644 --- a/modules/styles.py +++ b/modules/styles.py @@ -1,3 +1,4 @@ +from __future__ import annotations from pathlib import Path from modules import errors import csv From 07805cbeee144fb112039fd114dba1479cf4ba63 Mon Sep 17 00:00:00 2001 From: v0xie <28695009+v0xie@users.noreply.github.com> Date: Thu, 14 Mar 2024 17:05:14 -0700 Subject: [PATCH 116/496] fix: AttributeError when attempting to reshape rescale by org_module weight --- extensions-builtin/Lora/network_oft.py | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/extensions-builtin/Lora/network_oft.py b/extensions-builtin/Lora/network_oft.py index 7821a8a7..1c515ebb 100644 --- a/extensions-builtin/Lora/network_oft.py +++ b/extensions-builtin/Lora/network_oft.py @@ -36,13 +36,6 @@ class NetworkModuleOFT(network.NetworkModule): # self.alpha is unused 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_conv = type(self.sd_module) in [torch.nn.Conv2d] is_other_linear = type(self.sd_module) in [torch.nn.MultiheadAttention] # unsupported @@ -54,6 +47,13 @@ class NetworkModuleOFT(network.NetworkModule): elif is_other_linear: 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.block_size = self.out_dim // self.dim self.constraint = (0 if self.alpha is None else self.alpha) * self.out_dim From 76fd487818b1801906830a218c41ac434f2bd43d Mon Sep 17 00:00:00 2001 From: catboxanon <122327233+catboxanon@users.noreply.github.com> Date: Thu, 14 Mar 2024 21:59:53 -0400 Subject: [PATCH 117/496] Make imageviewer event listeners browser consistent --- javascript/imageviewer.js | 12 ++++-------- 1 file changed, 4 insertions(+), 8 deletions(-) diff --git a/javascript/imageviewer.js b/javascript/imageviewer.js index 625c5d14..d4d4f016 100644 --- a/javascript/imageviewer.js +++ b/javascript/imageviewer.js @@ -131,19 +131,15 @@ function setupImageForLightbox(e) { e.style.cursor = 'pointer'; e.style.userSelect = 'none'; - var isFirefox = navigator.userAgent.toLowerCase().indexOf('firefox') > -1; - - // 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) { + e.addEventListener('mousedown', function(evt) { if (evt.button == 1) { open(evt.target.src); evt.preventDefault(); return; } + }, true); + + e.addEventListener('click', function(evt) { if (!opts.js_modal_lightbox || evt.button != 0) return; modalZoomSet(gradioApp().getElementById('modalImage'), opts.js_modal_lightbox_initially_zoomed); From 8eaa7e9f0446a386129afae19fa27cbac151d190 Mon Sep 17 00:00:00 2001 From: catboxanon <122327233+catboxanon@users.noreply.github.com> Date: Fri, 15 Mar 2024 04:05:04 +0000 Subject: [PATCH 118/496] Support dragdrop for URLs --- javascript/dragdrop.js | 21 +++++++++++++++++---- modules/images.py | 33 +++++++++++++++++++++++---------- modules/ui_toprow.py | 8 ++++++++ 3 files changed, 48 insertions(+), 14 deletions(-) diff --git a/javascript/dragdrop.js b/javascript/dragdrop.js index d680daf5..01ae6e4d 100644 --- a/javascript/dragdrop.js +++ b/javascript/dragdrop.js @@ -56,6 +56,10 @@ function eventHasFiles(e) { return false; } +function getEventUrl(e) { + return e?.dataTransfer?.getData('URL') || e?.dataTransfer?.getData('text/html')?.match(/(?:src|href)=["'](.*?)["']/)?.[1]; +} + function dragDropTargetIsPrompt(target) { if (target?.placeholder && target?.placeholder.indexOf("Prompt") >= 0) return true; if (target?.parentNode?.parentNode?.className?.indexOf("prompt") > 0) return true; @@ -76,21 +80,30 @@ window.document.addEventListener('dragover', e => { window.document.addEventListener('drop', e => { const target = e.composedPath()[0]; - if (!eventHasFiles(e)) return; + const url = getEventUrl(e); + if (!eventHasFiles(e) && !url) return; if (dragDropTargetIsPrompt(target)) { e.stopPropagation(); 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"; + let prompt_url_target = isImg2img ? "img2img_prompt_url" : "txt2img_prompt_url"; - const imgParent = gradioApp().getElementById(prompt_target); + const imgParent = gradioApp().getElementById(prompt_image_target); + const urlParent = gradioApp().getElementById(prompt_url_target); const files = e.dataTransfer.files; const fileInput = imgParent.querySelector('input[type="file"]'); - if (fileInput) { + const urlInput = urlParent.querySelector('textarea'); + if (files && fileInput) { fileInput.files = files; fileInput.dispatchEvent(new Event('change')); } + if (url && urlInput) { + urlInput.value = url; + urlInput.dispatchEvent(new Event('input')); + } } var targetImage = target.closest('[data-testid="image"]'); diff --git a/modules/images.py b/modules/images.py index c50b2455..665dbc37 100644 --- a/modules/images.py +++ b/modules/images.py @@ -772,18 +772,31 @@ Steps: {json_info["steps"]}, Sampler: {sampler}, CFG scale: {json_info["scale"]} def image_data(data): import gradio as gr - try: - image = read(io.BytesIO(data)) - textinfo, _ = read_info_from_image(image) - return textinfo, None - except Exception: - pass + if not data: + return gr.update(), None - try: - text = data.decode('utf8') - assert len(text) < 10000 - return text, None + if isinstance(data, bytes): + try: + image = Image.open(io.BytesIO(data)) + textinfo, _ = read_info_from_image(image) + return textinfo, None + except Exception: + pass + try: + text = data.decode('utf8') + assert len(text) < 10000 + return text, None + except Exception: + pass + + import requests + try: + r = requests.get(data, timeout=5) + if r.status_code == 200: + image = Image.open(io.BytesIO(r.content)) + textinfo, _ = read_info_from_image(image) + return textinfo, None except Exception: pass diff --git a/modules/ui_toprow.py b/modules/ui_toprow.py index dc3c3aa3..bce93da9 100644 --- a/modules/ui_toprow.py +++ b/modules/ui_toprow.py @@ -82,6 +82,7 @@ class Toprow: with gr.Row(elem_id=f"{self.id_part}_prompt_row", elem_classes=["prompt-row"]): self.prompt = gr.Textbox(label="Prompt", elem_id=f"{self.id_part}_prompt", show_label=False, lines=3, placeholder="Prompt\n(Press Ctrl+Enter to generate, Alt+Enter to skip, Esc to interrupt)", elem_classes=["prompt"]) self.prompt_img = gr.File(label="", elem_id=f"{self.id_part}_prompt_image", file_count="single", type="binary", visible=False) + self.prompt_url = gr.Textbox(label="", elem_id=f"{self.id_part}_prompt_url", visible=False) with gr.Row(elem_id=f"{self.id_part}_neg_prompt_row", elem_classes=["prompt-row"]): self.negative_prompt = gr.Textbox(label="Negative prompt", elem_id=f"{self.id_part}_neg_prompt", show_label=False, lines=3, placeholder="Negative prompt\n(Press Ctrl+Enter to generate, Alt+Enter to skip, Esc to interrupt)", elem_classes=["prompt"]) @@ -93,6 +94,13 @@ class Toprow: show_progress=False, ) + self.prompt_url.input( + fn=modules.images.image_data, + inputs=[self.prompt_url], + outputs=[self.prompt, self.prompt_url], + show_progress=False, + ) + def create_submit_box(self): with gr.Row(elem_id=f"{self.id_part}_generate_box", elem_classes=["generate-box"] + (["generate-box-compact"] if self.is_compact else []), render=not self.is_compact) as submit_box: self.submit_box = submit_box From 5f4203bf9b622a0eb3b6a1eb2c8ef8dbb56930a9 Mon Sep 17 00:00:00 2001 From: missionfloyd Date: Thu, 14 Mar 2024 22:21:08 -0600 Subject: [PATCH 119/496] Strip comments from hires fix prompt --- modules/processing_scripts/comments.py | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/modules/processing_scripts/comments.py b/modules/processing_scripts/comments.py index 638e39f2..cf81dfd8 100644 --- a/modules/processing_scripts/comments.py +++ b/modules/processing_scripts/comments.py @@ -26,6 +26,13 @@ class ScriptStripComments(scripts.Script): p.main_prompt = strip_comments(p.main_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): if not shared.opts.enable_prompt_comments: From 6f51e0555376cff9f185cb4f1dde8537bfec92af Mon Sep 17 00:00:00 2001 From: Andray Date: Fri, 15 Mar 2024 12:01:43 +0400 Subject: [PATCH 120/496] prevent alt menu for firefox --- .../canvas-zoom-and-pan/javascript/zoom.js | 23 +++++++++++++++++++ 1 file changed, 23 insertions(+) diff --git a/extensions-builtin/canvas-zoom-and-pan/javascript/zoom.js b/extensions-builtin/canvas-zoom-and-pan/javascript/zoom.js index 64e7a638..aa27ac15 100644 --- a/extensions-builtin/canvas-zoom-and-pan/javascript/zoom.js +++ b/extensions-builtin/canvas-zoom-and-pan/javascript/zoom.js @@ -966,3 +966,26 @@ onUiLoaded(async() => { // Add integration with Inpaint Anything // applyZoomAndPanIntegration("None", ["#ia_sam_image", "#ia_sel_mask"]); }); + + +onUiLoaded(function() { + let isAltPressed = false; + + function handleAltKeyDown(e) { + if (e.code === "AltLeft" || e.code === "AltRight") { + isAltPressed = true; + } else { + isAltPressed = false; + } + } + + function handleAltKeyUp(e) { + if (isAltPressed) { + e.preventDefault(); + } + isAltPressed = false; + } + + document.addEventListener("keydown", handleAltKeyDown); + document.addEventListener("keyup", handleAltKeyUp); +}); From 887a5122083d27fd819bfeb54524dbdc791961cc Mon Sep 17 00:00:00 2001 From: w-e-w <40751091+w-e-w@users.noreply.github.com> Date: Fri, 15 Mar 2024 21:06:54 +0900 Subject: [PATCH 121/496] fix issue with Styles when Hires prompt is used --- modules/infotext_utils.py | 31 ++++++++++++++++++++----------- modules/infotext_versions.py | 1 + modules/processing.py | 15 ++++++++------- 3 files changed, 29 insertions(+), 18 deletions(-) diff --git a/modules/infotext_utils.py b/modules/infotext_utils.py index a1cbfb17..723cb1f8 100644 --- a/modules/infotext_utils.py +++ b/modules/infotext_utils.py @@ -265,17 +265,6 @@ Steps: 20, Sampler: Euler a, CFG scale: 7, Seed: 965400086, Size: 512x512, Model else: 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): try: if v[0] == '"' and v[-1] == '"': @@ -290,6 +279,26 @@ Steps: 20, Sampler: Euler a, CFG scale: 7, Seed: 965400086, Size: 512x512, Model except Exception: 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) if "Clip skip" not in res: res["Clip skip"] = "1" diff --git a/modules/infotext_versions.py b/modules/infotext_versions.py index b5552a31..0d2d6282 100644 --- a/modules/infotext_versions.py +++ b/modules/infotext_versions.py @@ -6,6 +6,7 @@ import re v160 = version.parse("1.6.0") v170_tsnr = version.parse("v1.7.0-225") v180 = version.parse("1.8.0") +v180_hr_styles = version.parse("1.8.0-136") # todo: change to the actual version number after merge def parse_version(text): diff --git a/modules/processing.py b/modules/processing.py index 86194b05..d6873a51 100644 --- a/modules/processing.py +++ b/modules/processing.py @@ -702,7 +702,7 @@ def program_version(): return res -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, all_hr_prompts=None, all_hr_negative_prompts=None): if index is None: index = position_in_batch + iteration * p.batch_size @@ -745,11 +745,18 @@ def create_infotext(p, all_prompts, all_seeds, all_subseeds, comments=None, iter "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, + "Hires prompt": None, # This is set later, insert here to keep order + "Hires negative prompt": None, # This is set later, insert here to keep order **p.extra_generation_params, "Version": program_version() if opts.add_version_to_infotext else None, "User": p.user if opts.add_user_name_to_info else None, } + if all_hr_prompts := all_hr_prompts or getattr(p, 'all_hr_prompts', None): + generation_params['Hires prompt'] = all_hr_prompts[index] if all_hr_prompts[index] != all_prompts[index] else None + if all_hr_negative_prompts := all_hr_negative_prompts or getattr(p, 'all_hr_negative_prompts', None): + generation_params['Hires negative prompt'] = all_hr_negative_prompts[index] if all_hr_negative_prompts[index] != all_negative_prompts[index] else 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] @@ -1194,12 +1201,6 @@ class StableDiffusionProcessingTxt2Img(StableDiffusionProcessing): 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 - if tuple(self.hr_prompt) != tuple(self.prompt): - self.extra_generation_params["Hires prompt"] = self.hr_prompt - - if tuple(self.hr_negative_prompt) != tuple(self.negative_prompt): - self.extra_generation_params["Hires negative prompt"] = self.hr_negative_prompt - 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 not any(x.name == self.hr_upscaler for x in shared.sd_upscalers): From a3a648bf6bf49d3ec51de3cee74035ac71f1662d Mon Sep 17 00:00:00 2001 From: w-e-w <40751091+w-e-w@users.noreply.github.com> Date: Sat, 16 Mar 2024 05:57:23 +0900 Subject: [PATCH 122/496] bump action version --- .github/workflows/on_pull_request.yaml | 8 ++++---- .github/workflows/run_tests.yaml | 10 +++++----- 2 files changed, 9 insertions(+), 9 deletions(-) diff --git a/.github/workflows/on_pull_request.yaml b/.github/workflows/on_pull_request.yaml index 9e44c806..c595b80a 100644 --- a/.github/workflows/on_pull_request.yaml +++ b/.github/workflows/on_pull_request.yaml @@ -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 steps: - name: Checkout Code - uses: actions/checkout@v3 - - uses: actions/setup-python@v4 + uses: actions/checkout@v4 + - uses: actions/setup-python@v5 with: python-version: 3.11 # NB: there's no cache: pip here since we're not installing anything @@ -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 steps: - name: Checkout Code - uses: actions/checkout@v3 + uses: actions/checkout@v4 - name: Install Node.js - uses: actions/setup-node@v3 + uses: actions/setup-node@v4 with: node-version: 18 - run: npm i --ci diff --git a/.github/workflows/run_tests.yaml b/.github/workflows/run_tests.yaml index f42e4758..0610f4f5 100644 --- a/.github/workflows/run_tests.yaml +++ b/.github/workflows/run_tests.yaml @@ -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 steps: - name: Checkout Code - uses: actions/checkout@v3 + uses: actions/checkout@v4 - name: Set up Python 3.10 - uses: actions/setup-python@v4 + uses: actions/setup-python@v5 with: python-version: 3.10.6 cache: pip @@ -22,7 +22,7 @@ jobs: launch.py - name: Cache models id: cache-models - uses: actions/cache@v3 + uses: actions/cache@v4 with: path: models key: "2023-12-30" @@ -68,13 +68,13 @@ jobs: python -m coverage report -i python -m coverage html -i - name: Upload main app output - uses: actions/upload-artifact@v3 + uses: actions/upload-artifact@v4 if: always() with: name: output path: output.txt - name: Upload coverage HTML - uses: actions/upload-artifact@v3 + uses: actions/upload-artifact@v4 if: always() with: name: htmlcov From 63c3c4dbc357e1e400b40fe22521857eeaeb01b4 Mon Sep 17 00:00:00 2001 From: AUTOMATIC1111 <16777216c@gmail.com> Date: Sat, 16 Mar 2024 09:04:08 +0300 Subject: [PATCH 123/496] simplify code for #15244 --- modules/shared_options.py | 2 +- modules/ui_postprocessing.py | 32 ------------------------------- scripts/postprocessing_upscale.py | 14 ++++++++++++++ 3 files changed, 15 insertions(+), 33 deletions(-) diff --git a/modules/shared_options.py b/modules/shared_options.py index 99a051aa..fc9f13d6 100644 --- a/modules/shared_options.py +++ b/modules/shared_options.py @@ -101,7 +101,7 @@ options_templates.update(options_section(('upscaling', "Upscaling", "postprocess "DAT_tile": OptionInfo(192, "Tile size for DAT upscalers.", gr.Slider, {"minimum": 0, "maximum": 512, "step": 16}).info("0 = no tiling"), "DAT_tile_overlap": OptionInfo(8, "Tile overlap for DAT upscalers.", gr.Slider, {"minimum": 0, "maximum": 48, "step": 1}).info("Low values = visible seam"), "upscaler_for_img2img": OptionInfo(None, "Upscaler for img2img", gr.Dropdown, lambda: {"choices": [x.name for x in shared.sd_upscalers]}), - "scaleBy_from_upscaler": OptionInfo(False, "Automatically set the Scale by factor based on the name of the selected Upscaler.").info("Will not change the value when no matching pattern is found."), + "set_scale_by_when_changing_upscaler": OptionInfo(False, "Automatically set the Scale by factor based on the name of the selected Upscaler."), })) options_templates.update(options_section(('face-restoration', "Face restoration", "postprocessing"), { diff --git a/modules/ui_postprocessing.py b/modules/ui_postprocessing.py index af44f619..7261c2df 100644 --- a/modules/ui_postprocessing.py +++ b/modules/ui_postprocessing.py @@ -4,36 +4,6 @@ import modules.infotext_utils as parameters_copypaste from modules.ui_components import ResizeHandleRow -def hook_scale_update(inputs): - import re - pattern = r'(\d)[xX]|[xX](\d)' - - resize = upscaler = None - for script in inputs: - if script.label == "Resize": - resize = script - elif script.label == "Upscaler 1": - upscaler = script - elif resize and upscaler: - break - - def update_scale(upscaler: str, slider: float): - match = re.search(pattern, upscaler) - - if match: - if match.group(1): - return gr.update(value=int(match.group(1))) - - else: - return gr.update(value=int(match.group(2))) - - else: - return gr.update(value=slider) - - if resize and upscaler: - upscaler.input(update_scale, inputs=[upscaler, resize], outputs=[resize]) - - def create_ui(): dummy_component = gr.Label(visible=False) tab_index = gr.Number(value=0, visible=False) @@ -53,8 +23,6 @@ def create_ui(): show_extras_results = gr.Checkbox(label='Show result images', value=True, elem_id="extras_show_extras_results") script_inputs = scripts.scripts_postproc.setup_ui() - if getattr(shared.opts, 'scaleBy_from_upscaler', False): - hook_scale_update(script_inputs) with gr.Column(): toprow = ui_toprow.Toprow(is_compact=True, is_img2img=False, id_part="extras") diff --git a/scripts/postprocessing_upscale.py b/scripts/postprocessing_upscale.py index e269682d..d8ba70ed 100644 --- a/scripts/postprocessing_upscale.py +++ b/scripts/postprocessing_upscale.py @@ -1,3 +1,5 @@ +import re + from PIL import Image import numpy as np @@ -39,10 +41,22 @@ class ScriptPostprocessingUpscale(scripts_postprocessing.ScriptPostprocessing): extras_upscaler_2 = gr.Dropdown(label='Upscaler 2', elem_id="extras_upscaler_2", choices=[x.name for x in shared.sd_upscalers], value=shared.sd_upscalers[0].name) extras_upscaler_2_visibility = gr.Slider(minimum=0.0, maximum=1.0, step=0.001, label="Upscaler 2 visibility", value=0.0, elem_id="extras_upscaler_2_visibility") + def on_selected_upscale_method(upscale_method): + if not shared.opts.set_scale_by_when_changing_upscaler: + return gr.update() + + match = re.search(r'(\d)[xX]|[xX](\d)', upscale_method) + if not match: + return gr.update() + + return gr.update(value=int(match.group(1) or match.group(2))) + upscaling_res_switch_btn.click(lambda w, h: (h, w), inputs=[upscaling_resize_w, upscaling_resize_h], outputs=[upscaling_resize_w, upscaling_resize_h], show_progress=False) tab_scale_by.select(fn=lambda: 0, inputs=[], outputs=[selected_tab]) tab_scale_to.select(fn=lambda: 1, inputs=[], outputs=[selected_tab]) + extras_upscaler_1.change(on_selected_upscale_method, inputs=[extras_upscaler_1], outputs=[upscaling_resize], show_progress="hidden") + return { "upscale_mode": selected_tab, "upscale_by": upscaling_resize, From 38a7dc54885f9df098a3275d687d3b82fa54de1d Mon Sep 17 00:00:00 2001 From: w-e-w <40751091+w-e-w@users.noreply.github.com> Date: Sat, 16 Mar 2024 17:19:38 +0900 Subject: [PATCH 124/496] v180_hr_styles actual version number --- modules/infotext_versions.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/modules/infotext_versions.py b/modules/infotext_versions.py index 0d2d6282..cea676cd 100644 --- a/modules/infotext_versions.py +++ b/modules/infotext_versions.py @@ -6,7 +6,7 @@ import re v160 = version.parse("1.6.0") v170_tsnr = version.parse("v1.7.0-225") v180 = version.parse("1.8.0") -v180_hr_styles = version.parse("1.8.0-136") # todo: change to the actual version number after merge +v180_hr_styles = version.parse("1.8.0-139") def parse_version(text): From cc8ea32501e09787d73815d2a982071fd3a4686a Mon Sep 17 00:00:00 2001 From: Andray Date: Tue, 12 Mar 2024 21:29:57 +0400 Subject: [PATCH 125/496] fix ui-config for InputAccordion --- modules/ui_loadsave.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/modules/ui_loadsave.py b/modules/ui_loadsave.py index 2555cdb6..0cc1ab82 100644 --- a/modules/ui_loadsave.py +++ b/modules/ui_loadsave.py @@ -104,6 +104,8 @@ class UiLoadsave: apply_field(x, 'value', check_dropdown, getattr(x, 'init_field', None)) if type(x) == InputAccordion: + if hasattr(x, 'custom_script_source'): + x.accordion.custom_script_source = x.custom_script_source if x.accordion.visible: apply_field(x.accordion, 'visible') apply_field(x, 'value') From 79514e5b8e112c47128f3da506267e8540219097 Mon Sep 17 00:00:00 2001 From: Andray Date: Sat, 16 Mar 2024 16:06:21 +0400 Subject: [PATCH 126/496] prevent defaults for alt only if mouse inside image --- .../canvas-zoom-and-pan/javascript/zoom.js | 48 ++++++++++--------- 1 file changed, 25 insertions(+), 23 deletions(-) diff --git a/extensions-builtin/canvas-zoom-and-pan/javascript/zoom.js b/extensions-builtin/canvas-zoom-and-pan/javascript/zoom.js index aa27ac15..63900025 100644 --- a/extensions-builtin/canvas-zoom-and-pan/javascript/zoom.js +++ b/extensions-builtin/canvas-zoom-and-pan/javascript/zoom.js @@ -839,6 +839,31 @@ onUiLoaded(async() => { document.addEventListener("keydown", handleMoveKeyDown); document.addEventListener("keyup", handleMoveKeyUp); + // Prevent firefox to open toolbar on pressing alt + if (hotkeysConfig.canvas_hotkey_zoom === "Alt") { + let isAltPressed = false; + + function handleAltKeyDown(e) { + if (!activeElement) return; + if (e.code === "AltLeft" || e.code === "AltRight") { + isAltPressed = true; + } else { + isAltPressed = false; + } + } + + function handleAltKeyUp(e) { + if (isAltPressed) { + e.preventDefault(); + } + isAltPressed = false; + } + + document.addEventListener("keydown", handleAltKeyDown); + document.addEventListener("keyup", handleAltKeyUp); + } + + // Detect zoom level and update the pan speed. function updatePanPosition(movementX, movementY) { let panSpeed = 2; @@ -966,26 +991,3 @@ onUiLoaded(async() => { // Add integration with Inpaint Anything // applyZoomAndPanIntegration("None", ["#ia_sam_image", "#ia_sel_mask"]); }); - - -onUiLoaded(function() { - let isAltPressed = false; - - function handleAltKeyDown(e) { - if (e.code === "AltLeft" || e.code === "AltRight") { - isAltPressed = true; - } else { - isAltPressed = false; - } - } - - function handleAltKeyUp(e) { - if (isAltPressed) { - e.preventDefault(); - } - isAltPressed = false; - } - - document.addEventListener("keydown", handleAltKeyDown); - document.addEventListener("keyup", handleAltKeyUp); -}); From 9142ce8188cc0d7b8847df0c2f11ab47a0b13a50 Mon Sep 17 00:00:00 2001 From: Andray Date: Sat, 16 Mar 2024 16:14:57 +0400 Subject: [PATCH 127/496] fix linter and do not require reload page if option was changed --- .../canvas-zoom-and-pan/javascript/zoom.js | 37 ++++++++++--------- 1 file changed, 19 insertions(+), 18 deletions(-) diff --git a/extensions-builtin/canvas-zoom-and-pan/javascript/zoom.js b/extensions-builtin/canvas-zoom-and-pan/javascript/zoom.js index 63900025..a575862d 100644 --- a/extensions-builtin/canvas-zoom-and-pan/javascript/zoom.js +++ b/extensions-builtin/canvas-zoom-and-pan/javascript/zoom.js @@ -839,30 +839,31 @@ onUiLoaded(async() => { document.addEventListener("keydown", handleMoveKeyDown); document.addEventListener("keyup", handleMoveKeyUp); + // Prevent firefox to open toolbar on pressing alt - if (hotkeysConfig.canvas_hotkey_zoom === "Alt") { - let isAltPressed = false; + let isAltPressed = false; - function handleAltKeyDown(e) { - if (!activeElement) return; - if (e.code === "AltLeft" || e.code === "AltRight") { - isAltPressed = true; - } else { - isAltPressed = false; - } - } - - function handleAltKeyUp(e) { - if (isAltPressed) { - e.preventDefault(); - } + function handleAltKeyDown(e) { + if (!activeElement) return; + if (hotkeysConfig.canvas_hotkey_zoom !== "Alt") return; + if (e.code === "AltLeft" || e.code === "AltRight") { + isAltPressed = true; + } else { isAltPressed = false; } - - document.addEventListener("keydown", handleAltKeyDown); - document.addEventListener("keyup", handleAltKeyUp); } + function handleAltKeyUp(e) { + if (hotkeysConfig.canvas_hotkey_zoom !== "Alt") return; + if (isAltPressed) { + e.preventDefault(); + } + isAltPressed = false; + } + + document.addEventListener("keydown", handleAltKeyDown); + document.addEventListener("keyup", handleAltKeyUp); + // Detect zoom level and update the pan speed. function updatePanPosition(movementX, movementY) { From eb2ea8df1de2aaaab8a1ed069b34ca7385e30af2 Mon Sep 17 00:00:00 2001 From: Andray Date: Sat, 16 Mar 2024 17:42:25 +0400 Subject: [PATCH 128/496] check e.key in up event --- .../canvas-zoom-and-pan/javascript/zoom.js | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/extensions-builtin/canvas-zoom-and-pan/javascript/zoom.js b/extensions-builtin/canvas-zoom-and-pan/javascript/zoom.js index a575862d..dfd7b17a 100644 --- a/extensions-builtin/canvas-zoom-and-pan/javascript/zoom.js +++ b/extensions-builtin/canvas-zoom-and-pan/javascript/zoom.js @@ -841,24 +841,24 @@ onUiLoaded(async() => { // Prevent firefox to open toolbar on pressing alt - let isAltPressed = false; + let wasAltPressed = false; function handleAltKeyDown(e) { if (!activeElement) return; if (hotkeysConfig.canvas_hotkey_zoom !== "Alt") return; if (e.code === "AltLeft" || e.code === "AltRight") { - isAltPressed = true; + wasAltPressed = true; } else { - isAltPressed = false; + wasAltPressed = false; } } function handleAltKeyUp(e) { if (hotkeysConfig.canvas_hotkey_zoom !== "Alt") return; - if (isAltPressed) { + if (wasAltPressed || (activeElement && e.key === "Alt")) { e.preventDefault(); } - isAltPressed = false; + wasAltPressed = false; } document.addEventListener("keydown", handleAltKeyDown); From 7598a92436be66f5ba63a9234ba8cdbcbc3cc662 Mon Sep 17 00:00:00 2001 From: Andray Date: Sat, 16 Mar 2024 17:49:05 +0400 Subject: [PATCH 129/496] use e.key instead of e.code --- extensions-builtin/canvas-zoom-and-pan/javascript/zoom.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/extensions-builtin/canvas-zoom-and-pan/javascript/zoom.js b/extensions-builtin/canvas-zoom-and-pan/javascript/zoom.js index dfd7b17a..c6b463fd 100644 --- a/extensions-builtin/canvas-zoom-and-pan/javascript/zoom.js +++ b/extensions-builtin/canvas-zoom-and-pan/javascript/zoom.js @@ -846,7 +846,7 @@ onUiLoaded(async() => { function handleAltKeyDown(e) { if (!activeElement) return; if (hotkeysConfig.canvas_hotkey_zoom !== "Alt") return; - if (e.code === "AltLeft" || e.code === "AltRight") { + if (e.key === "Alt") { wasAltPressed = true; } else { wasAltPressed = false; From c364b607764047cab27887bb78122fc302e2041b Mon Sep 17 00:00:00 2001 From: Andray Date: Fri, 15 Mar 2024 12:07:11 +0400 Subject: [PATCH 130/496] handle 0 wheel deltaX --- extensions-builtin/canvas-zoom-and-pan/javascript/zoom.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/extensions-builtin/canvas-zoom-and-pan/javascript/zoom.js b/extensions-builtin/canvas-zoom-and-pan/javascript/zoom.js index 64e7a638..b0963f4f 100644 --- a/extensions-builtin/canvas-zoom-and-pan/javascript/zoom.js +++ b/extensions-builtin/canvas-zoom-and-pan/javascript/zoom.js @@ -793,7 +793,7 @@ onUiLoaded(async() => { targetElement.addEventListener("wheel", e => { // change zoom level - const operation = e.deltaY > 0 ? "-" : "+"; + const operation = (e.deltaY || -e.wheelDelta) > 0 ? "-" : "+"; changeZoomLevel(operation, e); // Handle brush size adjustment with ctrl key pressed From 0283826179cd3aec923053877f0742919c52e166 Mon Sep 17 00:00:00 2001 From: AUTOMATIC1111 <16777216c@gmail.com> Date: Sat, 16 Mar 2024 18:44:36 +0300 Subject: [PATCH 131/496] prevent make alt key from opening main menu if it's used for brush size also --- .../canvas-zoom-and-pan/javascript/zoom.js | 33 +++++++++---------- 1 file changed, 15 insertions(+), 18 deletions(-) diff --git a/extensions-builtin/canvas-zoom-and-pan/javascript/zoom.js b/extensions-builtin/canvas-zoom-and-pan/javascript/zoom.js index 6e61def2..ed2ef99b 100644 --- a/extensions-builtin/canvas-zoom-and-pan/javascript/zoom.js +++ b/extensions-builtin/canvas-zoom-and-pan/javascript/zoom.js @@ -252,6 +252,7 @@ onUiLoaded(async() => { let isMoving = false; let mouseX, mouseY; let activeElement; + let interactedWithAltKey = false; const elements = Object.fromEntries( Object.keys(elementIDs).map(id => [ @@ -508,6 +509,10 @@ onUiLoaded(async() => { if (isModifierKey(e, hotkeysConfig.canvas_hotkey_zoom)) { e.preventDefault(); + if(hotkeysConfig.canvas_hotkey_zoom === "Alt"){ + interactedWithAltKey = true; + } + let zoomPosX, zoomPosY; let delta = 0.2; if (elemData[elemId].zoomLevel > 7) { @@ -800,6 +805,10 @@ onUiLoaded(async() => { if (isModifierKey(e, hotkeysConfig.canvas_hotkey_adjust)) { e.preventDefault(); + if(hotkeysConfig.canvas_hotkey_adjust === "Alt"){ + interactedWithAltKey = true; + } + // Increase or decrease brush size based on scroll direction adjustBrushSize(elemId, e.deltaY); } @@ -840,28 +849,16 @@ onUiLoaded(async() => { document.addEventListener("keyup", handleMoveKeyUp); - // Prevent firefox to open toolbar on pressing alt - let wasAltPressed = false; - - function handleAltKeyDown(e) { - if (!activeElement) return; - if (hotkeysConfig.canvas_hotkey_zoom !== "Alt") return; - if (e.key === "Alt") { - wasAltPressed = true; - } else { - wasAltPressed = false; - } - } - + // Prevent firefox from opening main menu when alt is used as a hotkey for zoom or brush size function handleAltKeyUp(e) { - if (hotkeysConfig.canvas_hotkey_zoom !== "Alt") return; - if (wasAltPressed || (activeElement && e.key === "Alt")) { - e.preventDefault(); + if (e.key !== "Alt" || !interactedWithAltKey) { + return; } - wasAltPressed = false; + + e.preventDefault(); + interactedWithAltKey = false; } - document.addEventListener("keydown", handleAltKeyDown); document.addEventListener("keyup", handleAltKeyUp); From bf35c661834be65d10a1e77f6cecbf7e5e6a687e Mon Sep 17 00:00:00 2001 From: AUTOMATIC1111 <16777216c@gmail.com> Date: Sat, 16 Mar 2024 18:45:19 +0300 Subject: [PATCH 132/496] fix for #15179 --- modules/upscaler.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/modules/upscaler.py b/modules/upscaler.py index 9d13ee99..4ffd428c 100644 --- a/modules/upscaler.py +++ b/modules/upscaler.py @@ -20,7 +20,7 @@ class Upscaler: filter = None model = None user_path = None - scalers: list = [] + scalers: list tile = True def __init__(self, create_dirs=False): From 1792e193b1ad22727d9628dda9c5c6457fd9f294 Mon Sep 17 00:00:00 2001 From: Kohaku-Blueleaf <59680068+KohakuBlueleaf@users.noreply.github.com> Date: Sat, 16 Mar 2024 23:52:29 +0800 Subject: [PATCH 133/496] Use correct implementation, fix device error --- extensions-builtin/Lora/network.py | 18 +++++++++++++++--- 1 file changed, 15 insertions(+), 3 deletions(-) diff --git a/extensions-builtin/Lora/network.py b/extensions-builtin/Lora/network.py index 183f8bd7..30b979f5 100644 --- a/extensions-builtin/Lora/network.py +++ b/extensions-builtin/Lora/network.py @@ -153,7 +153,7 @@ class NetworkModule: self.scale = weights.w["scale"].item() if "scale" in weights.w else None self.dora_scale = weights.w.get("dora_scale", None) - self.dora_mean_dim = tuple(i for i in range(len(self.shape)) if i != 1) + self.dora_norm_dims = len(self.shape) - 1 def multiplier(self): if 'transformer' in self.sd_key[:20]: @@ -170,10 +170,22 @@ class NetworkModule: return 1.0 def apply_weight_decompose(self, updown, orig_weight): - orig_weight = orig_weight.to(updown) + # 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 / merged_scale1(dim=self.dora_mean_dim, keepdim=True) * self.dora_scale + merged_scale1 * (dora_scale / merged_scale1_norm) ) final_updown = dora_merged - orig_weight return final_updown From 199c51d688c5770037cdb1d3521461902e5857c3 Mon Sep 17 00:00:00 2001 From: Kohaku-Blueleaf <59680068+KohakuBlueleaf@users.noreply.github.com> Date: Sun, 17 Mar 2024 00:00:07 +0800 Subject: [PATCH 134/496] linter --- extensions-builtin/Lora/network.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/extensions-builtin/Lora/network.py b/extensions-builtin/Lora/network.py index 30b979f5..41286429 100644 --- a/extensions-builtin/Lora/network.py +++ b/extensions-builtin/Lora/network.py @@ -185,7 +185,7 @@ class NetworkModule: ) dora_merged = ( - merged_scale1 * (dora_scale / merged_scale1_norm) + merged_scale1 * (dora_scale / merged_scale1_norm) ) final_updown = dora_merged - orig_weight return final_updown From 3da13f0cc912acb37052ff4416f238e714bd2a57 Mon Sep 17 00:00:00 2001 From: missionfloyd Date: Sat, 16 Mar 2024 15:46:29 -0600 Subject: [PATCH 135/496] Fix dragging to/from firefox --- javascript/dragdrop.js | 9 ++++----- 1 file changed, 4 insertions(+), 5 deletions(-) diff --git a/javascript/dragdrop.js b/javascript/dragdrop.js index 01ae6e4d..bb7be89f 100644 --- a/javascript/dragdrop.js +++ b/javascript/dragdrop.js @@ -57,7 +57,7 @@ function eventHasFiles(e) { } function getEventUrl(e) { - return e?.dataTransfer?.getData('URL') || e?.dataTransfer?.getData('text/html')?.match(/(?:src|href)=["'](.*?)["']/)?.[1]; + return e.dataTransfer?.getData('text/uri-list') || e.dataTransfer?.getData('text/plain'); } function dragDropTargetIsPrompt(target) { @@ -96,13 +96,12 @@ window.document.addEventListener('drop', e => { const files = e.dataTransfer.files; const fileInput = imgParent.querySelector('input[type="file"]'); const urlInput = urlParent.querySelector('textarea'); - if (files && fileInput) { - fileInput.files = files; - fileInput.dispatchEvent(new Event('change')); - } if (url && urlInput) { urlInput.value = url; urlInput.dispatchEvent(new Event('input')); + } else if (files && fileInput) { + fileInput.files = files; + fileInput.dispatchEvent(new Event('change')); } } From 83a9dd82db511f8f7545d953d6c6baf2c2c9a7a3 Mon Sep 17 00:00:00 2001 From: missionfloyd Date: Sat, 16 Mar 2024 16:08:42 -0600 Subject: [PATCH 136/496] Download image client-side --- javascript/dragdrop.js | 22 +++++++++------------- modules/images.py | 35 +++++++++++------------------------ modules/ui_toprow.py | 8 -------- 3 files changed, 20 insertions(+), 45 deletions(-) diff --git a/javascript/dragdrop.js b/javascript/dragdrop.js index bb7be89f..86591aa2 100644 --- a/javascript/dragdrop.js +++ b/javascript/dragdrop.js @@ -56,10 +56,6 @@ function eventHasFiles(e) { return false; } -function getEventUrl(e) { - return e.dataTransfer?.getData('text/uri-list') || e.dataTransfer?.getData('text/plain'); -} - function dragDropTargetIsPrompt(target) { if (target?.placeholder && target?.placeholder.indexOf("Prompt") >= 0) return true; if (target?.parentNode?.parentNode?.className?.indexOf("prompt") > 0) return true; @@ -78,9 +74,9 @@ window.document.addEventListener('dragover', e => { e.dataTransfer.dropEffect = 'copy'; }); -window.document.addEventListener('drop', e => { +window.document.addEventListener('drop', async e => { const target = e.composedPath()[0]; - const url = getEventUrl(e); + const url = e.dataTransfer.getData('text/uri-list') || e.dataTransfer.getData('text/plain'); if (!eventHasFiles(e) && !url) return; if (dragDropTargetIsPrompt(target)) { @@ -89,19 +85,19 @@ window.document.addEventListener('drop', e => { const isImg2img = get_tab_index('tabs') == 1; let prompt_image_target = isImg2img ? "img2img_prompt_image" : "txt2img_prompt_image"; - let prompt_url_target = isImg2img ? "img2img_prompt_url" : "txt2img_prompt_url"; const imgParent = gradioApp().getElementById(prompt_image_target); - const urlParent = gradioApp().getElementById(prompt_url_target); const files = e.dataTransfer.files; const fileInput = imgParent.querySelector('input[type="file"]'); - const urlInput = urlParent.querySelector('textarea'); - if (url && urlInput) { - urlInput.value = url; - urlInput.dispatchEvent(new Event('input')); - } else if (files && fileInput) { + if (eventHasFiles(e) && fileInput) { fileInput.files = files; fileInput.dispatchEvent(new Event('change')); + } else if (url) { + const request = await fetch(url); + const data = new DataTransfer(); + data.items.add(new File([await request.blob()], 'image.png')); + fileInput.files = data.files; + fileInput.dispatchEvent(new Event('change')); } } diff --git a/modules/images.py b/modules/images.py index 665dbc37..c50b2455 100644 --- a/modules/images.py +++ b/modules/images.py @@ -772,31 +772,18 @@ Steps: {json_info["steps"]}, Sampler: {sampler}, CFG scale: {json_info["scale"]} def image_data(data): import gradio as gr - if not data: - return gr.update(), None - - if isinstance(data, bytes): - try: - image = Image.open(io.BytesIO(data)) - textinfo, _ = read_info_from_image(image) - return textinfo, None - except Exception: - pass - - try: - text = data.decode('utf8') - assert len(text) < 10000 - return text, None - except Exception: - pass - - import requests try: - r = requests.get(data, timeout=5) - if r.status_code == 200: - image = Image.open(io.BytesIO(r.content)) - textinfo, _ = read_info_from_image(image) - return textinfo, None + image = read(io.BytesIO(data)) + textinfo, _ = read_info_from_image(image) + return textinfo, None + except Exception: + pass + + try: + text = data.decode('utf8') + assert len(text) < 10000 + return text, None + except Exception: pass diff --git a/modules/ui_toprow.py b/modules/ui_toprow.py index bce93da9..dc3c3aa3 100644 --- a/modules/ui_toprow.py +++ b/modules/ui_toprow.py @@ -82,7 +82,6 @@ class Toprow: with gr.Row(elem_id=f"{self.id_part}_prompt_row", elem_classes=["prompt-row"]): self.prompt = gr.Textbox(label="Prompt", elem_id=f"{self.id_part}_prompt", show_label=False, lines=3, placeholder="Prompt\n(Press Ctrl+Enter to generate, Alt+Enter to skip, Esc to interrupt)", elem_classes=["prompt"]) self.prompt_img = gr.File(label="", elem_id=f"{self.id_part}_prompt_image", file_count="single", type="binary", visible=False) - self.prompt_url = gr.Textbox(label="", elem_id=f"{self.id_part}_prompt_url", visible=False) with gr.Row(elem_id=f"{self.id_part}_neg_prompt_row", elem_classes=["prompt-row"]): self.negative_prompt = gr.Textbox(label="Negative prompt", elem_id=f"{self.id_part}_neg_prompt", show_label=False, lines=3, placeholder="Negative prompt\n(Press Ctrl+Enter to generate, Alt+Enter to skip, Esc to interrupt)", elem_classes=["prompt"]) @@ -94,13 +93,6 @@ class Toprow: show_progress=False, ) - self.prompt_url.input( - fn=modules.images.image_data, - inputs=[self.prompt_url], - outputs=[self.prompt, self.prompt_url], - show_progress=False, - ) - def create_submit_box(self): with gr.Row(elem_id=f"{self.id_part}_generate_box", elem_classes=["generate-box"] + (["generate-box-compact"] if self.is_compact else []), render=not self.is_compact) as submit_box: self.submit_box = submit_box From 446cd5a58b22b6771a35696f4dfe4063f4998ebe Mon Sep 17 00:00:00 2001 From: catboxanon <122327233+catboxanon@users.noreply.github.com> Date: Sat, 16 Mar 2024 20:19:12 -0400 Subject: [PATCH 137/496] dragdrop: add error handling for URLs --- javascript/dragdrop.js | 19 ++++++++++++++----- 1 file changed, 14 insertions(+), 5 deletions(-) diff --git a/javascript/dragdrop.js b/javascript/dragdrop.js index 86591aa2..0c018356 100644 --- a/javascript/dragdrop.js +++ b/javascript/dragdrop.js @@ -93,11 +93,20 @@ window.document.addEventListener('drop', async e => { fileInput.files = files; fileInput.dispatchEvent(new Event('change')); } else if (url) { - const request = await fetch(url); - const data = new DataTransfer(); - data.items.add(new File([await request.blob()], 'image.png')); - fileInput.files = data.files; - fileInput.dispatchEvent(new Event('change')); + 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; + } } } From 93c7b9d7fc4555b20b7c4d06d8acf02c697d22cb Mon Sep 17 00:00:00 2001 From: AUTOMATIC1111 <16777216c@gmail.com> Date: Sun, 17 Mar 2024 07:02:31 +0300 Subject: [PATCH 138/496] linter for #15262 --- extensions-builtin/canvas-zoom-and-pan/javascript/zoom.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/extensions-builtin/canvas-zoom-and-pan/javascript/zoom.js b/extensions-builtin/canvas-zoom-and-pan/javascript/zoom.js index ed2ef99b..97421178 100644 --- a/extensions-builtin/canvas-zoom-and-pan/javascript/zoom.js +++ b/extensions-builtin/canvas-zoom-and-pan/javascript/zoom.js @@ -509,7 +509,7 @@ onUiLoaded(async() => { if (isModifierKey(e, hotkeysConfig.canvas_hotkey_zoom)) { e.preventDefault(); - if(hotkeysConfig.canvas_hotkey_zoom === "Alt"){ + if (hotkeysConfig.canvas_hotkey_zoom === "Alt") { interactedWithAltKey = true; } @@ -805,7 +805,7 @@ onUiLoaded(async() => { if (isModifierKey(e, hotkeysConfig.canvas_hotkey_adjust)) { e.preventDefault(); - if(hotkeysConfig.canvas_hotkey_adjust === "Alt"){ + if (hotkeysConfig.canvas_hotkey_adjust === "Alt") { interactedWithAltKey = true; } From e9b8a89b3c775b55ffdd4fc43acbeba02bb0c7cb Mon Sep 17 00:00:00 2001 From: Andray Date: Sun, 17 Mar 2024 09:29:11 +0400 Subject: [PATCH 139/496] allow use zoom.js outside webui context --- .../canvas-zoom-and-pan/javascript/zoom.js | 28 +++++++++++-------- 1 file changed, 16 insertions(+), 12 deletions(-) diff --git a/extensions-builtin/canvas-zoom-and-pan/javascript/zoom.js b/extensions-builtin/canvas-zoom-and-pan/javascript/zoom.js index 97421178..24b7793d 100644 --- a/extensions-builtin/canvas-zoom-and-pan/javascript/zoom.js +++ b/extensions-builtin/canvas-zoom-and-pan/javascript/zoom.js @@ -29,6 +29,7 @@ onUiLoaded(async() => { }); function getActiveTab(elements, all = false) { + if (!elements.img2imgTabs) return null; const tabs = elements.img2imgTabs.querySelectorAll("button"); if (all) return tabs; @@ -43,6 +44,7 @@ onUiLoaded(async() => { // Get tab ID function getTabId(elements) { const activeTab = getActiveTab(elements); + if (!activeTab) return null; return tabNameToElementId[activeTab.innerText]; } @@ -366,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. 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`); if (img && img.style.display !== "none") { @@ -788,13 +790,15 @@ onUiLoaded(async() => { targetElement.addEventListener("mouseleave", handleMouseLeave); // Reset zoom when click on another tab - elements.img2imgTabs.addEventListener("click", resetZoom); - elements.img2imgTabs.addEventListener("click", () => { - // targetElement.style.width = ""; - if (parseInt(targetElement.style.width) > 865) { - setTimeout(fitToElement, 0); - } - }); + if (elements.img2imgTabs) { + elements.img2imgTabs.addEventListener("click", resetZoom); + elements.img2imgTabs.addEventListener("click", () => { + // targetElement.style.width = ""; + if (parseInt(targetElement.style.width) > 865) { + setTimeout(fitToElement, 0); + } + }); + } targetElement.addEventListener("wheel", e => { // change zoom level @@ -935,9 +939,9 @@ onUiLoaded(async() => { } - applyZoomAndPan(elementIDs.sketch, false); - applyZoomAndPan(elementIDs.inpaint, false); - applyZoomAndPan(elementIDs.inpaintSketch, false); + elementIDs.sketch && applyZoomAndPan(elementIDs.sketch, false); + elementIDs.inpaint && applyZoomAndPan(elementIDs.inpaint, false); + elementIDs.inpaintSketch && applyZoomAndPan(elementIDs.inpaintSketch, false); // Make the function global so that other extensions can take advantage of this solution const applyZoomAndPanIntegration = async(id, elementIDs) => { From 66355b47756e1e6e7e1acc5d7f515fe59fbb7cc6 Mon Sep 17 00:00:00 2001 From: AUTOMATIC1111 <16777216c@gmail.com> Date: Sun, 17 Mar 2024 09:18:32 +0300 Subject: [PATCH 140/496] use diskcache library for caching --- .gitignore | 1 + modules/cache.py | 82 ++++++++++++++++++--------------------- requirements.txt | 1 + requirements_versions.txt | 1 + 4 files changed, 40 insertions(+), 45 deletions(-) diff --git a/.gitignore b/.gitignore index 6790e9ee..519b4a53 100644 --- a/.gitignore +++ b/.gitignore @@ -38,3 +38,4 @@ notification.mp3 /package-lock.json /.coverage* /test/test_outputs +/cache diff --git a/modules/cache.py b/modules/cache.py index a9822a0e..9df248d7 100644 --- a/modules/cache.py +++ b/modules/cache.py @@ -2,48 +2,47 @@ import json import os import os.path import threading -import time + +import diskcache +import tqdm 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_data = None +cache_dir = os.environ.get('SD_WEBUI_CACHE_DIR', os.path.join(data_path, "cache")) +caches = {} cache_lock = threading.Lock() -dump_cache_after = None -dump_cache_thread = None - def dump_cache(): - """ - Marks cache for writing to disk. 5 seconds after no one else flags the cache for writing, it is written. - """ + """old function for dumping cache to disk; does nothing since diskcache.""" - global dump_cache_after - global dump_cache_thread + pass - def thread_func(): - global dump_cache_after - global dump_cache_thread - while dump_cache_after is not None and time.time() < dump_cache_after: - time.sleep(1) +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 - 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) + total_count = sum(len(keyvalues) for keyvalues in data.values()) - os.replace(cache_filename_tmp, cache_filename) + with tqdm.tqdm(total=total_count, desc="converting cache") as progress: + for subsection, keyvalues in data.items(): + cache_obj = caches.get(subsection) + if cache_obj is None: + cache_obj = diskcache.Cache(os.path.join(cache_dir, subsection)) + caches[subsection] = cache_obj - dump_cache_after = None - dump_cache_thread = None - - with cache_lock: - dump_cache_after = time.time() + 5 - if dump_cache_thread is None: - dump_cache_thread = threading.Thread(name='cache-writer', target=thread_func) - dump_cache_thread.start() + for key, value in keyvalues.items(): + cache_obj[key] = value + progress.update(1) def cache(subsection): @@ -54,28 +53,21 @@ def cache(subsection): subsection (str): The subsection identifier for the cache. Returns: - dict: The cache data for the specified subsection. + diskcache.Cache: The cache data for the specified subsection. """ - global cache_data - - if cache_data is None: + cache_obj = caches.get(subsection) + if not cache_obj: with cache_lock: - if cache_data is None: - try: - 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 = {} + if not os.path.exists(cache_dir) and os.path.isfile(cache_filename): + convert_old_cached_data() - s = cache_data.get(subsection, {}) - cache_data[subsection] = s + cache_obj = caches.get(subsection) + if not cache_obj: + cache_obj = diskcache.Cache(os.path.join(cache_dir, subsection)) + caches[subsection] = cache_obj - return s + return cache_obj def cached_data_for_file(subsection, title, filename, func): diff --git a/requirements.txt b/requirements.txt index 731a1be7..8699c02b 100644 --- a/requirements.txt +++ b/requirements.txt @@ -4,6 +4,7 @@ accelerate blendmodes clean-fid +diskcache einops facexlib fastapi>=0.90.1 diff --git a/requirements_versions.txt b/requirements_versions.txt index 5e30b5ea..87aae913 100644 --- a/requirements_versions.txt +++ b/requirements_versions.txt @@ -3,6 +3,7 @@ Pillow==9.5.0 accelerate==0.21.0 blendmodes==2022 clean-fid==0.1.35 +diskcache==5.6.3 einops==0.4.1 facexlib==0.3.0 fastapi==0.94.0 From c3f75d1d854332c5ddb8524886e9c0ff22a28ea4 Mon Sep 17 00:00:00 2001 From: Andray Date: Sun, 17 Mar 2024 10:30:11 +0400 Subject: [PATCH 141/496] little fixes zoom.js --- extensions-builtin/canvas-zoom-and-pan/javascript/zoom.js | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/extensions-builtin/canvas-zoom-and-pan/javascript/zoom.js b/extensions-builtin/canvas-zoom-and-pan/javascript/zoom.js index 24b7793d..7807f7f6 100644 --- a/extensions-builtin/canvas-zoom-and-pan/javascript/zoom.js +++ b/extensions-builtin/canvas-zoom-and-pan/javascript/zoom.js @@ -280,7 +280,7 @@ onUiLoaded(async() => { const targetElement = gradioApp().querySelector(elemId); if (!targetElement) { - console.log("Element not found"); + console.log("Element not found", elemId); return; } @@ -939,9 +939,9 @@ onUiLoaded(async() => { } - elementIDs.sketch && applyZoomAndPan(elementIDs.sketch, false); - elementIDs.inpaint && applyZoomAndPan(elementIDs.inpaint, false); - elementIDs.inpaintSketch && applyZoomAndPan(elementIDs.inpaintSketch, false); + applyZoomAndPan(elementIDs.sketch, false); + applyZoomAndPan(elementIDs.inpaint, false); + applyZoomAndPan(elementIDs.inpaintSketch, false); // Make the function global so that other extensions can take advantage of this solution const applyZoomAndPanIntegration = async(id, elementIDs) => { From b1cd0189bc30ed7c8dca6fb7bc3d248a056f3e15 Mon Sep 17 00:00:00 2001 From: Andray Date: Sun, 17 Mar 2024 12:51:40 +0400 Subject: [PATCH 142/496] allow variants for extension name in metadata.ini --- modules/extensions.py | 42 ++++++++++++++++++++++++++++++++++++------ 1 file changed, 36 insertions(+), 6 deletions(-) diff --git a/modules/extensions.py b/modules/extensions.py index 88a38938..6a3c6c7e 100644 --- a/modules/extensions.py +++ b/modules/extensions.py @@ -10,6 +10,10 @@ from modules import shared, errors, cache, scripts from modules.gitpython_hack import Repo 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) @@ -50,7 +54,7 @@ class ExtensionMetadata: self.canonical_name = self.config.get("Extension", "Name", fallback=canonical_name) 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): """reads a list of requirements from the config; field is the name of the field in the ini file, @@ -62,7 +66,33 @@ class ExtensionMetadata: if extra_section: x = x + ', ' + self.config.get(extra_section, field, fallback='') - return self.parse_list(x.lower()) + tmp_list = self.parse_list(x.lower()) + + if len(tmp_list) >= 3: + names_variants = [] + i = 0 + while i < len(tmp_list) - 2: + if tmp_list[i] != "|": + names_variants.append([tmp_list[i]]) + i += 1 + else: + names_variants[-1].append(tmp_list[i + 1]) + i += 2 + while i < len(tmp_list): + names_variants.append([tmp_list[i]]) + i += 1 + + result_list = [] + + for name_variants in names_variants: + for variant in name_variants: + if variant in loaded_extensions.keys(): + break + result_list.append(variant) + else: + result_list = tmp_list + + return result_list def parse_list(self, text): """converts a line from config ("ext1 ext2, ext3 ") into a python list (["ext1", "ext2", "ext3"])""" @@ -213,6 +243,7 @@ class Extension: def list_extensions(): extensions.clear() extension_paths.clear() + loaded_extensions.clear() if shared.cmd_opts.disable_all_extensions: print("*** \"--disable-all-extensions\" arg was used, will not load any extensions ***") @@ -223,7 +254,6 @@ def list_extensions(): elif shared.opts.disable_all_extensions == "extra": print("*** \"Disable all extensions\" option was set, will only load built-in extensions ***") - loaded_extensions = {} # scan through extensions directory and load metadata for dirname in [extensions_builtin_dir, extensions_dir]: @@ -250,6 +280,9 @@ def list_extensions(): extension_paths[extension.path] = extension loaded_extensions[canonical_name] = extension + for extension in extensions: + extension.metadata.requires = extension.metadata.get_script_requirements("Requires", "Extension") + # check for requirements for extension in extensions: if not extension.enabled: @@ -279,6 +312,3 @@ def find_extension(filename): return None - -extensions: list[Extension] = [] -extension_paths: dict[str, Extension] = {} From ef356193255ddd2499759700596bbde69f6dd6ef Mon Sep 17 00:00:00 2001 From: Andray Date: Sun, 17 Mar 2024 14:14:12 +0400 Subject: [PATCH 143/496] Extras upscaler: option limit target resolution --- scripts/postprocessing_upscale.py | 56 +++++++++++++++++++++++-------- 1 file changed, 42 insertions(+), 14 deletions(-) diff --git a/scripts/postprocessing_upscale.py b/scripts/postprocessing_upscale.py index c2574346..6259017b 100644 --- a/scripts/postprocessing_upscale.py +++ b/scripts/postprocessing_upscale.py @@ -12,6 +12,19 @@ from modules.ui import switch_values_symbol upscale_cache = {} +def limitSizeByOneDemention(size: tuple, limit: int): + w, h = size + if h > w: + if h > limit: + w = limit / h * w + h = limit + else: + if w > limit: + h = limit / w * h + w = limit + return (int(w), int(h)) + + class ScriptPostprocessingUpscale(scripts_postprocessing.ScriptPostprocessing): name = "Upscale" order = 1000 @@ -31,6 +44,8 @@ class ScriptPostprocessingUpscale(scripts_postprocessing.ScriptPostprocessing): with gr.Tabs(elem_id="extras_resize_mode"): with gr.TabItem('Scale by', elem_id="extras_scale_by_tab") as tab_scale_by: upscaling_resize = gr.Slider(minimum=1.0, maximum=8.0, step=0.05, label="Resize", value=4, elem_id="extras_upscaling_resize") + limit_target_resolution = gr.Slider(minimum=0, maximum=10000, step=8, label="Limit target resolution", value=0, elem_id="extras_upscale_limit_target_resolution", + tooltip="0 = no limit. Limit target resolution by one demension. Useful for batches where can be big images.") with gr.TabItem('Scale to', elem_id="extras_scale_to_tab") as tab_scale_to: with FormRow(): @@ -61,6 +76,7 @@ class ScriptPostprocessingUpscale(scripts_postprocessing.ScriptPostprocessing): "upscale_enabled": upscale_enabled, "upscale_mode": selected_tab, "upscale_by": upscaling_resize, + "limit_target_resolution": limit_target_resolution, "upscale_to_width": upscaling_resize_w, "upscale_to_height": upscaling_resize_h, "upscale_crop": upscaling_crop, @@ -69,12 +85,18 @@ class ScriptPostprocessingUpscale(scripts_postprocessing.ScriptPostprocessing): "upscaler_2_visibility": extras_upscaler_2_visibility, } - def upscale(self, image, info, upscaler, upscale_mode, upscale_by, upscale_to_width, upscale_to_height, upscale_crop): + def upscale(self, image, info, upscaler, upscale_mode, upscale_by, limit_target_resolution, upscale_to_width, upscale_to_height, upscale_crop): if upscale_mode == 1: upscale_by = max(upscale_to_width/image.width, upscale_to_height/image.height) info["Postprocess upscale to"] = f"{upscale_to_width}x{upscale_to_height}" else: info["Postprocess upscale by"] = upscale_by + if limit_target_resolution != 0 and max(*image.size)*upscale_by > limit_target_resolution: + upscale_mode = 1 + upscale_crop = False + upscale_to_width, upscale_to_height = limitSizeByOneDemention((image.width*upscale_by, image.height*upscale_by), limit_target_resolution) + upscale_by = max(upscale_to_width/image.width, upscale_to_height/image.height) + info["Limit target resolution"] = limit_target_resolution cache_key = (hash(np.array(image.getdata()).tobytes()), upscaler.name, upscale_mode, upscale_by, upscale_to_width, upscale_to_height, upscale_crop) cached_image = upscale_cache.pop(cache_key, None) @@ -96,18 +118,21 @@ class ScriptPostprocessingUpscale(scripts_postprocessing.ScriptPostprocessing): return image - def process_firstpass(self, pp: scripts_postprocessing.PostprocessedImage, upscale_enabled=True, upscale_mode=1, upscale_by=2.0, upscale_to_width=None, upscale_to_height=None, upscale_crop=False, upscaler_1_name=None, upscaler_2_name=None, upscaler_2_visibility=0.0): - if upscale_mode == 1: - pp.shared.target_width = upscale_to_width - pp.shared.target_height = upscale_to_height + def process_firstpass(self, pp: scripts_postprocessing.PostprocessedImage, **args): + if args['upscale_mode'] == 1: + pp.shared.target_width = args['upscale_to_width'] + pp.shared.target_height = args['upscale_to_height'] else: - pp.shared.target_width = int(pp.image.width * upscale_by) - pp.shared.target_height = int(pp.image.height * upscale_by) + pp.shared.target_width = int(pp.image.width * args['upscale_by']) + pp.shared.target_height = int(pp.image.height * args['upscale_by']) + if args['limit_target_resolution'] != 0: + pp.shared.target_width, pp.shared.target_height = limitSizeByOneDemention((pp.shared.target_width, pp.shared.target_height), args['limit_target_resolution']) - def process(self, pp: scripts_postprocessing.PostprocessedImage, upscale_enabled=True, upscale_mode=1, upscale_by=2.0, upscale_to_width=None, upscale_to_height=None, upscale_crop=False, upscaler_1_name=None, upscaler_2_name=None, upscaler_2_visibility=0.0): - if not upscale_enabled: + def process(self, pp: scripts_postprocessing.PostprocessedImage, **args): + if not args['upscale_enabled']: return + upscaler_1_name = args['upscaler_1_name'] if upscaler_1_name == "None": upscaler_1_name = None @@ -117,18 +142,21 @@ class ScriptPostprocessingUpscale(scripts_postprocessing.ScriptPostprocessing): if not upscaler1: return + upscaler_2_name = args['upscaler_2_name'] if upscaler_2_name == "None": upscaler_2_name = None upscaler2 = next(iter([x for x in shared.sd_upscalers if x.name == upscaler_2_name and x.name != "None"]), None) assert upscaler2 or (upscaler_2_name is None), f'could not find upscaler named {upscaler_2_name}' - upscaled_image = self.upscale(pp.image, pp.info, upscaler1, upscale_mode, upscale_by, upscale_to_width, upscale_to_height, upscale_crop) + upscaled_image = self.upscale(pp.image, pp.info, upscaler1, args['upscale_mode'], args['upscale_by'], args['limit_target_resolution'], args['upscale_to_width'], + args['upscale_to_height'], args['upscale_crop']) pp.info["Postprocess upscaler"] = upscaler1.name - if upscaler2 and upscaler_2_visibility > 0: - second_upscale = self.upscale(pp.image, pp.info, upscaler2, upscale_mode, upscale_by, upscale_to_width, upscale_to_height, upscale_crop) - upscaled_image = Image.blend(upscaled_image, second_upscale, upscaler_2_visibility) + if upscaler2 and args['upscaler_2_visibility'] > 0: + second_upscale = self.upscale(pp.image, pp.info, upscaler2, args['upscale_mode'], args['upscale_by'], args['upscale_to_width'], + args['upscale_to_height'], args['upscale_crop']) + upscaled_image = Image.blend(upscaled_image, second_upscale, args['upscaler_2_visibility']) pp.info["Postprocess upscaler 2"] = upscaler2.name @@ -163,5 +191,5 @@ class ScriptPostprocessingUpscaleSimple(ScriptPostprocessingUpscale): upscaler1 = next(iter([x for x in shared.sd_upscalers if x.name == upscaler_name]), None) assert upscaler1, f'could not find upscaler named {upscaler_name}' - pp.image = self.upscale(pp.image, pp.info, upscaler1, 0, upscale_by, 0, 0, False) + pp.image = self.upscale(pp.image, pp.info, upscaler1, 0, upscale_by, 0, 0, 0, False) pp.info["Postprocess upscaler"] = upscaler1.name From 908d522057b9e72ea8b3350112142956d10a22cf Mon Sep 17 00:00:00 2001 From: AUTOMATIC1111 <16777216c@gmail.com> Date: Sun, 17 Mar 2024 11:12:37 +0300 Subject: [PATCH 144/496] update ruff to 0.3.3 --- .github/workflows/on_pull_request.yaml | 2 +- pyproject.toml | 6 ++++-- 2 files changed, 5 insertions(+), 3 deletions(-) diff --git a/.github/workflows/on_pull_request.yaml b/.github/workflows/on_pull_request.yaml index c595b80a..9326c6a4 100644 --- a/.github/workflows/on_pull_request.yaml +++ b/.github/workflows/on_pull_request.yaml @@ -20,7 +20,7 @@ jobs: # not to have GHA download an (at the time of writing) 4 GB cache # of PyTorch and other dependencies. - name: Install Ruff - run: pip install ruff==0.1.6 + run: pip install ruff==0.3.3 - name: Run Ruff run: ruff . lint-js: diff --git a/pyproject.toml b/pyproject.toml index d03036e7..10ebc84b 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -2,6 +2,8 @@ target-version = "py39" +[tool.ruff.lint] + extend-select = [ "B", "C", @@ -25,10 +27,10 @@ ignore = [ "W605", # invalid escape sequence, messes with some docstrings ] -[tool.ruff.per-file-ignores] +[tool.ruff.lint.per-file-ignores] "webui.py" = ["E402"] # Module level import not at top of file -[tool.ruff.flake8-bugbear] +[tool.ruff.lint.flake8-bugbear] # Allow default arguments like, e.g., `data: List[str] = fastapi.Query(None)`. extend-immutable-calls = ["fastapi.Depends", "fastapi.security.HTTPBasic"] From 06c5dd0907ab15a1ef608b73ec0541fc258712c8 Mon Sep 17 00:00:00 2001 From: Andray Date: Sun, 17 Mar 2024 14:28:26 +0400 Subject: [PATCH 145/496] maybe fix tests --- modules/postprocessing.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/modules/postprocessing.py b/modules/postprocessing.py index 754cc9e3..0818eeeb 100644 --- a/modules/postprocessing.py +++ b/modules/postprocessing.py @@ -133,13 +133,14 @@ def run_postprocessing_webui(id_task, *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, limit_target_resolution = 0): """old handler for API""" args = scripts.scripts_postproc.create_args_for_run({ "Upscale": { "upscale_mode": resize_mode, "upscale_by": upscaling_resize, + "limit_target_resolution": limit_target_resolution, "upscale_to_width": upscaling_resize_w, "upscale_to_height": upscaling_resize_h, "upscale_crop": upscaling_crop, From 79cbc92abfee5e2c8b9fcf8eab3e45e9523af310 Mon Sep 17 00:00:00 2001 From: AUTOMATIC1111 <16777216c@gmail.com> Date: Sun, 17 Mar 2024 13:30:20 +0300 Subject: [PATCH 146/496] change code for variant requirements in metadata.ini --- modules/extensions.py | 32 +++++++------------------------- 1 file changed, 7 insertions(+), 25 deletions(-) diff --git a/modules/extensions.py b/modules/extensions.py index 6a3c6c7e..6549af7f 100644 --- a/modules/extensions.py +++ b/modules/extensions.py @@ -66,33 +66,15 @@ class ExtensionMetadata: if extra_section: x = x + ', ' + self.config.get(extra_section, field, fallback='') - tmp_list = self.parse_list(x.lower()) + listed_requirements = self.parse_list(x.lower()) + res = [] - if len(tmp_list) >= 3: - names_variants = [] - i = 0 - while i < len(tmp_list) - 2: - if tmp_list[i] != "|": - names_variants.append([tmp_list[i]]) - i += 1 - else: - names_variants[-1].append(tmp_list[i + 1]) - i += 2 - while i < len(tmp_list): - names_variants.append([tmp_list[i]]) - i += 1 + for requirement in listed_requirements: + loaded_requirements = (x for x in requirement.split("|") if x in loaded_extensions) + relevant_requirement = next(loaded_requirements, listed_requirements[0]) + res.append(relevant_requirement) - result_list = [] - - for name_variants in names_variants: - for variant in name_variants: - if variant in loaded_extensions.keys(): - break - result_list.append(variant) - else: - result_list = tmp_list - - return result_list + return res def parse_list(self, text): """converts a line from config ("ext1 ext2, ext3 ") into a python list (["ext1", "ext2", "ext3"])""" From 81be357925241359e6d40dd603923182faa8a2da Mon Sep 17 00:00:00 2001 From: Andray Date: Sun, 17 Mar 2024 14:51:19 +0400 Subject: [PATCH 147/496] hide limit target resolution under option --- modules/shared_options.py | 1 + scripts/postprocessing_upscale.py | 7 +++++-- 2 files changed, 6 insertions(+), 2 deletions(-) diff --git a/modules/shared_options.py b/modules/shared_options.py index fc9f13d6..73ec93c2 100644 --- a/modules/shared_options.py +++ b/modules/shared_options.py @@ -102,6 +102,7 @@ options_templates.update(options_section(('upscaling', "Upscaling", "postprocess "DAT_tile_overlap": OptionInfo(8, "Tile overlap for DAT upscalers.", gr.Slider, {"minimum": 0, "maximum": 48, "step": 1}).info("Low values = visible seam"), "upscaler_for_img2img": OptionInfo(None, "Upscaler for img2img", gr.Dropdown, lambda: {"choices": [x.name for x in shared.sd_upscalers]}), "set_scale_by_when_changing_upscaler": OptionInfo(False, "Automatically set the Scale by factor based on the name of the selected Upscaler."), + "show_limit_target_resolution_in_extras_upscale": OptionInfo(False, 'Show "Limit target resolution" slider in "Upscale" extras script. Useful for batches where can be big images.'), })) options_templates.update(options_section(('face-restoration', "Face restoration", "postprocessing"), { diff --git a/scripts/postprocessing_upscale.py b/scripts/postprocessing_upscale.py index 6259017b..485ff784 100644 --- a/scripts/postprocessing_upscale.py +++ b/scripts/postprocessing_upscale.py @@ -44,8 +44,11 @@ class ScriptPostprocessingUpscale(scripts_postprocessing.ScriptPostprocessing): with gr.Tabs(elem_id="extras_resize_mode"): with gr.TabItem('Scale by', elem_id="extras_scale_by_tab") as tab_scale_by: upscaling_resize = gr.Slider(minimum=1.0, maximum=8.0, step=0.05, label="Resize", value=4, elem_id="extras_upscaling_resize") - limit_target_resolution = gr.Slider(minimum=0, maximum=10000, step=8, label="Limit target resolution", value=0, elem_id="extras_upscale_limit_target_resolution", - tooltip="0 = no limit. Limit target resolution by one demension. Useful for batches where can be big images.") + if shared.opts.show_limit_target_resolution_in_extras_upscale: + limit_target_resolution = gr.Slider(minimum=0, maximum=10000, step=8, label="Limit target resolution", value=8000, elem_id="extras_upscale_limit_target_resolution", + tooltip="0 = no limit. Limit target resolution by one demension. Useful for batches where can be big images.") + else: + limit_target_resolution = gr.Number(0, visible=False) with gr.TabItem('Scale to', elem_id="extras_scale_to_tab") as tab_scale_to: with FormRow(): From fd83d4eec3852bacbf69dbfb0486017a0bc4342f Mon Sep 17 00:00:00 2001 From: Andray Date: Sun, 17 Mar 2024 18:19:13 +0400 Subject: [PATCH 148/496] add .needs_reload_ui() --- modules/shared_options.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/modules/shared_options.py b/modules/shared_options.py index 73ec93c2..050ede18 100644 --- a/modules/shared_options.py +++ b/modules/shared_options.py @@ -102,7 +102,7 @@ options_templates.update(options_section(('upscaling', "Upscaling", "postprocess "DAT_tile_overlap": OptionInfo(8, "Tile overlap for DAT upscalers.", gr.Slider, {"minimum": 0, "maximum": 48, "step": 1}).info("Low values = visible seam"), "upscaler_for_img2img": OptionInfo(None, "Upscaler for img2img", gr.Dropdown, lambda: {"choices": [x.name for x in shared.sd_upscalers]}), "set_scale_by_when_changing_upscaler": OptionInfo(False, "Automatically set the Scale by factor based on the name of the selected Upscaler."), - "show_limit_target_resolution_in_extras_upscale": OptionInfo(False, 'Show "Limit target resolution" slider in "Upscale" extras script. Useful for batches where can be big images.'), + "show_limit_target_resolution_in_extras_upscale": OptionInfo(False, 'Show "Limit target resolution" slider in "Upscale" extras script. Useful for batches where can be big images.').needs_reload_ui(), })) options_templates.update(options_section(('face-restoration', "Face restoration", "postprocessing"), { From daa1b33247e58d775cea46ea13079d4fd14732d5 Mon Sep 17 00:00:00 2001 From: AUTOMATIC1111 <16777216c@gmail.com> Date: Sun, 17 Mar 2024 18:16:12 +0300 Subject: [PATCH 149/496] make reloading UI scripts optional when doing Reload UI, and off by default --- modules/initialize.py | 2 +- modules/shared_options.py | 2 ++ 2 files changed, 3 insertions(+), 1 deletion(-) diff --git a/modules/initialize.py b/modules/initialize.py index 08ad4c0b..6c9c2388 100644 --- a/modules/initialize.py +++ b/modules/initialize.py @@ -109,7 +109,7 @@ def initialize_rest(*, reload_script_modules=False): with startup_timer.subcategory("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")]: importlib.reload(module) startup_timer.record("reload script modules") diff --git a/modules/shared_options.py b/modules/shared_options.py index fc9f13d6..29f98de3 100644 --- a/modules/shared_options.py +++ b/modules/shared_options.py @@ -315,6 +315,8 @@ options_templates.update(options_section(('ui', "User interface", "ui"), { "show_progress_in_title": OptionInfo(True, "Show generation progress in window title."), "send_seed": OptionInfo(True, "Send seed when sending prompt or image to other interface"), "send_size": OptionInfo(True, "Send size when sending prompt or image to another interface"), + "enable_reloading_ui_scripts": OptionInfo(False, "Reload UI scripts when using Reload UI option").info("useful for developing: if you make changes to UI scripts code, it is applied when the UI is reloded."), + })) From 611faaddef098468e2c6daa06a9d1d81393f5641 Mon Sep 17 00:00:00 2001 From: AUTOMATIC1111 <16777216c@gmail.com> Date: Sun, 17 Mar 2024 23:19:24 +0300 Subject: [PATCH 150/496] change the default name for callback from None to "unnamed" --- modules/script_callbacks.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/modules/script_callbacks.py b/modules/script_callbacks.py index 2cd65e83..d5a97ecf 100644 --- a/modules/script_callbacks.py +++ b/modules/script_callbacks.py @@ -121,7 +121,7 @@ class BeforeTokenCounterParams: class ScriptCallback: script: str callback: any - name: str = None + name: str = "unnamed" def add_callback(callbacks, fun, *, name=None, category='unknown', filename=None): From df4da02ab00b24de0f3d1cd03665af1fc18984ca Mon Sep 17 00:00:00 2001 From: Aarni Koskela Date: Sun, 17 Mar 2024 20:25:25 +0000 Subject: [PATCH 151/496] Tweak diskcache limits --- modules/cache.py | 12 ++++++++++-- 1 file changed, 10 insertions(+), 2 deletions(-) diff --git a/modules/cache.py b/modules/cache.py index 9df248d7..f4e5f702 100644 --- a/modules/cache.py +++ b/modules/cache.py @@ -20,6 +20,14 @@ def dump_cache(): pass +def make_cache(subsection: str) -> diskcache.Cache: + 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 + ) + + def convert_old_cached_data(): try: with open(cache_filename, "r", encoding="utf8") as file: @@ -37,7 +45,7 @@ def convert_old_cached_data(): for subsection, keyvalues in data.items(): cache_obj = caches.get(subsection) if cache_obj is None: - cache_obj = diskcache.Cache(os.path.join(cache_dir, subsection)) + cache_obj = make_cache(subsection) caches[subsection] = cache_obj for key, value in keyvalues.items(): @@ -64,7 +72,7 @@ def cache(subsection): cache_obj = caches.get(subsection) if not cache_obj: - cache_obj = diskcache.Cache(os.path.join(cache_dir, subsection)) + cache_obj = make_cache(subsection) caches[subsection] = cache_obj return cache_obj From 2a6054f83657d5b55a9f2151cd9b71ef6491fc09 Mon Sep 17 00:00:00 2001 From: Dalton Date: Sun, 17 Mar 2024 22:37:19 -0400 Subject: [PATCH 152/496] Update sd_hijack_ddpm_v1.py --- extensions-builtin/LDSR/sd_hijack_ddpm_v1.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/extensions-builtin/LDSR/sd_hijack_ddpm_v1.py b/extensions-builtin/LDSR/sd_hijack_ddpm_v1.py index 04adc5eb..21e6c61c 100644 --- a/extensions-builtin/LDSR/sd_hijack_ddpm_v1.py +++ b/extensions-builtin/LDSR/sd_hijack_ddpm_v1.py @@ -14,7 +14,7 @@ from contextlib import contextmanager from functools import partial from tqdm import tqdm from torchvision.utils import make_grid -from pytorch_lightning.utilities.distributed import rank_zero_only +from pytorch_lightning.utilities.rank_zero import rank_zero_only from ldm.util import log_txt_as_img, exists, default, ismap, isimage, mean_flat, count_params, instantiate_from_config from ldm.modules.ema import LitEma From 51cb20ec39e1ecb40ee8f35bbacf4ecf5c42d1f4 Mon Sep 17 00:00:00 2001 From: Dalton Date: Sun, 17 Mar 2024 22:45:31 -0400 Subject: [PATCH 153/496] Update ddpm_edit.py --- modules/models/diffusion/ddpm_edit.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/modules/models/diffusion/ddpm_edit.py b/modules/models/diffusion/ddpm_edit.py index 6db340da..52656005 100644 --- a/modules/models/diffusion/ddpm_edit.py +++ b/modules/models/diffusion/ddpm_edit.py @@ -19,7 +19,7 @@ from contextlib import contextmanager from functools import partial from tqdm import tqdm from torchvision.utils import make_grid -from pytorch_lightning.utilities.distributed import rank_zero_only +from pytorch_lightning.utilities.rank_zero import rank_zero_only from ldm.util import log_txt_as_img, exists, default, ismap, isimage, mean_flat, count_params, instantiate_from_config from ldm.modules.ema import LitEma From 203afa39c4cf144e7f373800dbf866b2d74565cb Mon Sep 17 00:00:00 2001 From: Andray Date: Mon, 18 Mar 2024 06:52:46 +0400 Subject: [PATCH 154/496] update tooltip --- scripts/postprocessing_upscale.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/scripts/postprocessing_upscale.py b/scripts/postprocessing_upscale.py index 485ff784..3132e499 100644 --- a/scripts/postprocessing_upscale.py +++ b/scripts/postprocessing_upscale.py @@ -46,7 +46,7 @@ class ScriptPostprocessingUpscale(scripts_postprocessing.ScriptPostprocessing): upscaling_resize = gr.Slider(minimum=1.0, maximum=8.0, step=0.05, label="Resize", value=4, elem_id="extras_upscaling_resize") if shared.opts.show_limit_target_resolution_in_extras_upscale: limit_target_resolution = gr.Slider(minimum=0, maximum=10000, step=8, label="Limit target resolution", value=8000, elem_id="extras_upscale_limit_target_resolution", - tooltip="0 = no limit. Limit target resolution by one demension. Useful for batches where can be big images.") + tooltip="0 = no limit. Limit maximal target resolution by the biggest demension. Useful for batches where can be big images.") else: limit_target_resolution = gr.Number(0, visible=False) From c4664b5a9c2f2302dae8be1c1daab94ad8a80001 Mon Sep 17 00:00:00 2001 From: AUTOMATIC1111 <16777216c@gmail.com> Date: Mon, 18 Mar 2024 08:00:30 +0300 Subject: [PATCH 155/496] fix for listing wrong requirements for extensions --- modules/extensions.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/modules/extensions.py b/modules/extensions.py index 6549af7f..5ad934b4 100644 --- a/modules/extensions.py +++ b/modules/extensions.py @@ -71,7 +71,7 @@ class ExtensionMetadata: for requirement in listed_requirements: loaded_requirements = (x for x in requirement.split("|") if x in loaded_extensions) - relevant_requirement = next(loaded_requirements, listed_requirements[0]) + relevant_requirement = next(loaded_requirements, requirement) res.append(relevant_requirement) return res From e9d4da7b567b37f8ad0b5f1d1cc00b7f83aff744 Mon Sep 17 00:00:00 2001 From: w-e-w <40751091+w-e-w@users.noreply.github.com> Date: Tue, 19 Mar 2024 00:54:56 +0900 Subject: [PATCH 156/496] restore outputs path output -> outputs --- modules/paths_internal.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/modules/paths_internal.py b/modules/paths_internal.py index 6058b0cd..cf9da45a 100644 --- a/modules/paths_internal.py +++ b/modules/paths_internal.py @@ -32,6 +32,6 @@ models_path = os.path.join(data_path, "models") extensions_dir = os.path.join(data_path, "extensions") extensions_builtin_dir = os.path.join(script_path, "extensions-builtin") 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') From 3fa1ebed6295f95d56e0d4b7d4f83f4128e2d78a Mon Sep 17 00:00:00 2001 From: missionfloyd Date: Mon, 18 Mar 2024 21:31:14 -0600 Subject: [PATCH 157/496] Escape btn_copy_path filename --- modules/ui_extra_networks.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/modules/ui_extra_networks.py b/modules/ui_extra_networks.py index 34c46ed4..bfb66a70 100644 --- a/modules/ui_extra_networks.py +++ b/modules/ui_extra_networks.py @@ -10,6 +10,7 @@ from modules.images import read_info_from_image, save_image_with_geninfo import gradio as gr import json import html +import re from fastapi.exceptions import HTTPException from modules.infotext_utils import image_from_url_text @@ -236,7 +237,7 @@ class ExtraNetworksPage: ) onclick = html.escape(onclick) - btn_copy_path = self.btn_copy_path_tpl.format(**{"filename": item["filename"]}) + btn_copy_path = self.btn_copy_path_tpl.format(**{"filename": re.sub(r"[\\\"']", r"\\\g<0>", item["filename"])}) btn_metadata = "" metadata = item.get("metadata") if metadata: From c4a00affc501392677d53b76f282d8fae8e43fd1 Mon Sep 17 00:00:00 2001 From: AUTOMATIC1111 <16777216c@gmail.com> Date: Tue, 19 Mar 2024 08:10:27 +0300 Subject: [PATCH 158/496] use existing quote_js function for #15316 --- html/extra-networks-copy-path-button.html | 4 ++-- modules/ui_extra_networks.py | 4 ++-- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/html/extra-networks-copy-path-button.html b/html/extra-networks-copy-path-button.html index 8083bb03..8388e319 100644 --- a/html/extra-networks-copy-path-button.html +++ b/html/extra-networks-copy-path-button.html @@ -1,5 +1,5 @@
    + onclick='extraNetworksCopyCardPath(event, {filename})' + data-clipboard-text={filename}>
    \ No newline at end of file diff --git a/modules/ui_extra_networks.py b/modules/ui_extra_networks.py index f30ce004..5436d52c 100644 --- a/modules/ui_extra_networks.py +++ b/modules/ui_extra_networks.py @@ -10,7 +10,6 @@ from modules.images import read_info_from_image, save_image_with_geninfo import gradio as gr import json import html -import re from fastapi.exceptions import HTTPException from modules.infotext_utils import image_from_url_text @@ -152,6 +151,7 @@ def quote_js(s): s = s.replace('"', '\\"') return f'"{s}"' + class ExtraNetworksPage: def __init__(self, title): self.title = title @@ -239,7 +239,7 @@ class ExtraNetworksPage: ) onclick = html.escape(onclick) - btn_copy_path = self.btn_copy_path_tpl.format(**{"filename": re.sub(r"[\\\"']", r"\\\g<0>", item["filename"])}) + btn_copy_path = self.btn_copy_path_tpl.format(**{"filename": quote_js(item["filename"])}) btn_metadata = "" metadata = item.get("metadata") if metadata: From a6b5a513f9b84384b630cc26f5e7ceea4b64897d Mon Sep 17 00:00:00 2001 From: Kohaku-Blueleaf <59680068+KohakuBlueleaf@users.noreply.github.com> Date: Tue, 19 Mar 2024 20:05:54 +0800 Subject: [PATCH 159/496] Implementation for sgm_uniform branch --- modules/sd_samplers_custom_schedulers.py | 12 ++++++++++++ modules/sd_samplers_kdiffusion.py | 9 ++++++++- modules/shared_options.py | 2 +- 3 files changed, 21 insertions(+), 2 deletions(-) create mode 100644 modules/sd_samplers_custom_schedulers.py diff --git a/modules/sd_samplers_custom_schedulers.py b/modules/sd_samplers_custom_schedulers.py new file mode 100644 index 00000000..78d6a2cd --- /dev/null +++ b/modules/sd_samplers_custom_schedulers.py @@ -0,0 +1,12 @@ +import torch + + +def sgm_uniform(n, sigma_min, sigma_max, inner_model, device): + start = inner_model.sigma_to_t(torch.tensor(sigma_max)) + end = inner_model.sigma_to_t(torch.tensor(sigma_min)) + sigs = [ + inner_model.t_to_sigma(ts) + for ts in torch.linspace(start, end, n)[:-1] + ] + sigs += [0.0] + return torch.FloatTensor(sigs).to(device) diff --git a/modules/sd_samplers_kdiffusion.py b/modules/sd_samplers_kdiffusion.py index 337106c0..516552a1 100644 --- a/modules/sd_samplers_kdiffusion.py +++ b/modules/sd_samplers_kdiffusion.py @@ -3,6 +3,7 @@ import inspect import k_diffusion.sampling from modules import sd_samplers_common, sd_samplers_extra, sd_samplers_cfg_denoiser from modules.sd_samplers_cfg_denoiser import CFGDenoiser # noqa: F401 +from modules.sd_samplers_custom_schedulers import sgm_uniform from modules.script_callbacks import ExtraNoiseParams, extra_noise_callback from modules.shared import opts @@ -62,7 +63,8 @@ k_diffusion_scheduler = { 'Automatic': None, 'karras': k_diffusion.sampling.get_sigmas_karras, 'exponential': k_diffusion.sampling.get_sigmas_exponential, - 'polyexponential': k_diffusion.sampling.get_sigmas_polyexponential + 'polyexponential': k_diffusion.sampling.get_sigmas_polyexponential, + 'sgm_uniform' : sgm_uniform, } @@ -121,6 +123,11 @@ class KDiffusionSampler(sd_samplers_common.Sampler): if opts.k_sched_type != 'exponential' and opts.rho != 0 and opts.rho != default_rho: sigmas_kwargs['rho'] = opts.rho p.extra_generation_params["Schedule rho"] = opts.rho + if opts.k_sched_type == 'sgm_uniform': + # Ensure the "step" will be target step + 1 + steps += 1 if not discard_next_to_last_sigma else 0 + sigmas_kwargs['inner_model'] = self.model_wrap + sigmas_kwargs.pop('rho', None) sigmas = sigmas_func(n=steps, **sigmas_kwargs, device=shared.device) elif self.config is not None and self.config.options.get('scheduler', None) == 'karras': diff --git a/modules/shared_options.py b/modules/shared_options.py index 29f98de3..84c9b224 100644 --- a/modules/shared_options.py +++ b/modules/shared_options.py @@ -368,7 +368,7 @@ options_templates.update(options_section(('sampler-params', "Sampler parameters" 's_tmin': OptionInfo(0.0, "sigma tmin", gr.Slider, {"minimum": 0.0, "maximum": 10.0, "step": 0.01}, infotext='Sigma tmin').info('enable stochasticity; start value of the sigma range; only applies to Euler, Heun, and DPM2'), 's_tmax': OptionInfo(0.0, "sigma tmax", gr.Slider, {"minimum": 0.0, "maximum": 999.0, "step": 0.01}, infotext='Sigma tmax').info("0 = inf; end value of the sigma range; only applies to Euler, Heun, and DPM2"), 's_noise': OptionInfo(1.0, "sigma noise", gr.Slider, {"minimum": 0.0, "maximum": 1.1, "step": 0.001}, infotext='Sigma noise').info('amount of additional noise to counteract loss of detail during sampling'), - 'k_sched_type': OptionInfo("Automatic", "Scheduler type", gr.Dropdown, {"choices": ["Automatic", "karras", "exponential", "polyexponential"]}, infotext='Schedule type').info("lets you override the noise schedule for k-diffusion samplers; choosing Automatic disables the three parameters below"), + 'k_sched_type': OptionInfo("Automatic", "Scheduler type", gr.Dropdown, {"choices": ["Automatic", "karras", "exponential", "polyexponential", "sgm_uniform"]}, infotext='Schedule type').info("lets you override the noise schedule for k-diffusion samplers; choosing Automatic disables the three parameters below"), 'sigma_min': OptionInfo(0.0, "sigma min", gr.Number, infotext='Schedule min sigma').info("0 = default (~0.03); minimum noise strength for k-diffusion noise scheduler"), 'sigma_max': OptionInfo(0.0, "sigma max", gr.Number, infotext='Schedule max sigma').info("0 = default (~14.6); maximum noise strength for k-diffusion noise scheduler"), 'rho': OptionInfo(0.0, "rho", gr.Number, infotext='Schedule rho').info("0 = default (7 for karras, 1 for polyexponential); higher values result in a steeper noise schedule (decreases faster)"), From 61f321756fd3f0bb6310161a2022c9a86a71c2d1 Mon Sep 17 00:00:00 2001 From: Dalton Date: Tue, 19 Mar 2024 14:44:31 -0400 Subject: [PATCH 160/496] Update ddpm_edit.py --- modules/models/diffusion/ddpm_edit.py | 3 +++ 1 file changed, 3 insertions(+) diff --git a/modules/models/diffusion/ddpm_edit.py b/modules/models/diffusion/ddpm_edit.py index 52656005..12067423 100644 --- a/modules/models/diffusion/ddpm_edit.py +++ b/modules/models/diffusion/ddpm_edit.py @@ -9,6 +9,7 @@ https://github.com/CompVis/taming-transformers # File modified by authors of InstructPix2Pix from original (https://github.com/CompVis/stable-diffusion). # See more details in LICENSE. +import sys import torch import torch.nn as nn import numpy as np @@ -19,6 +20,8 @@ from contextlib import contextmanager from functools import partial from tqdm import tqdm from torchvision.utils import make_grid +import pytorch_lightning.utilities.rank_zero +sys.modules['pytorch_lightning.utilities.distributed'] = sys.modules['pytorch_lightning.utilities.rank_zero'] from pytorch_lightning.utilities.rank_zero import rank_zero_only from ldm.util import log_txt_as_img, exists, default, ismap, isimage, mean_flat, count_params, instantiate_from_config From 86276832e0204aebb6353b22dcce1c832a2ae865 Mon Sep 17 00:00:00 2001 From: Dalton Date: Tue, 19 Mar 2024 14:45:07 -0400 Subject: [PATCH 161/496] Update sd_hijack_ddpm_v1.py --- extensions-builtin/LDSR/sd_hijack_ddpm_v1.py | 3 +++ 1 file changed, 3 insertions(+) diff --git a/extensions-builtin/LDSR/sd_hijack_ddpm_v1.py b/extensions-builtin/LDSR/sd_hijack_ddpm_v1.py index 21e6c61c..594dbc77 100644 --- a/extensions-builtin/LDSR/sd_hijack_ddpm_v1.py +++ b/extensions-builtin/LDSR/sd_hijack_ddpm_v1.py @@ -4,6 +4,7 @@ # Some models such as LDSR require VQ to work correctly # The classes are suffixed with "V1" and added back to the "ldm.models.diffusion.ddpm" module +import sys import torch import torch.nn as nn import numpy as np @@ -14,6 +15,8 @@ from contextlib import contextmanager from functools import partial from tqdm import tqdm from torchvision.utils import make_grid +import pytorch_lightning.utilities.rank_zero +sys.modules['pytorch_lightning.utilities.distributed'] = sys.modules['pytorch_lightning.utilities.rank_zero'] from pytorch_lightning.utilities.rank_zero import rank_zero_only from ldm.util import log_txt_as_img, exists, default, ismap, isimage, mean_flat, count_params, instantiate_from_config From 8f450321fef86d904626a03a4e38f1719e225e11 Mon Sep 17 00:00:00 2001 From: Dalton Date: Tue, 19 Mar 2024 14:53:30 -0400 Subject: [PATCH 162/496] Formatting ddpm_edit --- modules/models/diffusion/ddpm_edit.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/modules/models/diffusion/ddpm_edit.py b/modules/models/diffusion/ddpm_edit.py index 12067423..935d3564 100644 --- a/modules/models/diffusion/ddpm_edit.py +++ b/modules/models/diffusion/ddpm_edit.py @@ -14,13 +14,13 @@ import torch import torch.nn as nn import numpy as np import pytorch_lightning as pl +import pytorch_lightning.utilities.rank_zero from torch.optim.lr_scheduler import LambdaLR from einops import rearrange, repeat from contextlib import contextmanager from functools import partial from tqdm import tqdm from torchvision.utils import make_grid -import pytorch_lightning.utilities.rank_zero sys.modules['pytorch_lightning.utilities.distributed'] = sys.modules['pytorch_lightning.utilities.rank_zero'] from pytorch_lightning.utilities.rank_zero import rank_zero_only From 49779413aafecea511cd01b3b253643ff42998ca Mon Sep 17 00:00:00 2001 From: Dalton Date: Tue, 19 Mar 2024 14:54:06 -0400 Subject: [PATCH 163/496] Formatting sd_hijack_ddpm_v1.py --- extensions-builtin/LDSR/sd_hijack_ddpm_v1.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/extensions-builtin/LDSR/sd_hijack_ddpm_v1.py b/extensions-builtin/LDSR/sd_hijack_ddpm_v1.py index 594dbc77..cd54ab4b 100644 --- a/extensions-builtin/LDSR/sd_hijack_ddpm_v1.py +++ b/extensions-builtin/LDSR/sd_hijack_ddpm_v1.py @@ -9,13 +9,13 @@ import torch import torch.nn as nn import numpy as np import pytorch_lightning as pl +import pytorch_lightning.utilities.rank_zero from torch.optim.lr_scheduler import LambdaLR from einops import rearrange, repeat from contextlib import contextmanager from functools import partial from tqdm import tqdm from torchvision.utils import make_grid -import pytorch_lightning.utilities.rank_zero sys.modules['pytorch_lightning.utilities.distributed'] = sys.modules['pytorch_lightning.utilities.rank_zero'] from pytorch_lightning.utilities.rank_zero import rank_zero_only From d7f48472ccb279c13c5f5d67144b888083feb978 Mon Sep 17 00:00:00 2001 From: missionfloyd Date: Tue, 19 Mar 2024 18:50:25 -0600 Subject: [PATCH 164/496] Fix extra networks buttons when filename contains an apostrophe --- html/extra-networks-copy-path-button.html | 2 +- html/extra-networks-edit-item-button.html | 2 +- html/extra-networks-metadata-button.html | 2 +- javascript/extraNetworks.js | 11 +++++++---- modules/ui_extra_networks.py | 2 -- 5 files changed, 10 insertions(+), 9 deletions(-) diff --git a/html/extra-networks-copy-path-button.html b/html/extra-networks-copy-path-button.html index 8083bb03..50304b42 100644 --- a/html/extra-networks-copy-path-button.html +++ b/html/extra-networks-copy-path-button.html @@ -1,5 +1,5 @@
    \ No newline at end of file diff --git a/html/extra-networks-edit-item-button.html b/html/extra-networks-edit-item-button.html index 0fe43082..fd728600 100644 --- a/html/extra-networks-edit-item-button.html +++ b/html/extra-networks-edit-item-button.html @@ -1,4 +1,4 @@
    + onclick="extraNetworksEditUserMetadata(event, '{tabname}', '{extra_networks_tabname}')">
    \ No newline at end of file diff --git a/html/extra-networks-metadata-button.html b/html/extra-networks-metadata-button.html index 285b5b3b..4ef013bc 100644 --- a/html/extra-networks-metadata-button.html +++ b/html/extra-networks-metadata-button.html @@ -1,4 +1,4 @@ \ No newline at end of file diff --git a/javascript/extraNetworks.js b/javascript/extraNetworks.js index d5855fe9..655193eb 100644 --- a/javascript/extraNetworks.js +++ b/javascript/extraNetworks.js @@ -543,16 +543,18 @@ function requestGet(url, data, handler, errorHandler) { xhr.send(js); } -function extraNetworksCopyCardPath(event, path) { - navigator.clipboard.writeText(path); +function extraNetworksCopyCardPath(event) { + navigator.clipboard.writeText(event.target.getAttribute("data-clipboard-text")); event.stopPropagation(); } -function extraNetworksRequestMetadata(event, extraPage, cardName) { +function extraNetworksRequestMetadata(event, extraPage) { var showError = function() { 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) { if (data && data.metadata) { extraNetworksShowMetadata(data.metadata); @@ -566,7 +568,7 @@ function extraNetworksRequestMetadata(event, extraPage, cardName) { var extraPageUserMetadataEditors = {}; -function extraNetworksEditUserMetadata(event, tabname, extraPage, cardName) { +function extraNetworksEditUserMetadata(event, tabname, extraPage) { var id = tabname + '_' + extraPage + '_edit_user_metadata'; var editor = extraPageUserMetadataEditors[id]; @@ -578,6 +580,7 @@ function extraNetworksEditUserMetadata(event, tabname, extraPage, cardName) { extraPageUserMetadataEditors[id] = editor; } + var cardName = event.target.parentElement.parentElement.getAttribute("data-name"); editor.nameTextarea.value = cardName; updateInput(editor.nameTextarea); diff --git a/modules/ui_extra_networks.py b/modules/ui_extra_networks.py index 34c46ed4..afb95b51 100644 --- a/modules/ui_extra_networks.py +++ b/modules/ui_extra_networks.py @@ -243,14 +243,12 @@ class ExtraNetworksPage: btn_metadata = self.btn_metadata_tpl.format( **{ "extra_networks_tabname": self.extra_networks_tabname, - "name": html.escape(item["name"]), } ) btn_edit_item = self.btn_edit_item_tpl.format( **{ "tabname": tabname, "extra_networks_tabname": self.extra_networks_tabname, - "name": html.escape(item["name"]), } ) From b5c33341a18f9dc3e20d06b56540af330fafa285 Mon Sep 17 00:00:00 2001 From: missionfloyd Date: Tue, 19 Mar 2024 19:06:56 -0600 Subject: [PATCH 165/496] Don't use quote_js on filename --- modules/ui_extra_networks.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/modules/ui_extra_networks.py b/modules/ui_extra_networks.py index 741a4e14..6a206af0 100644 --- a/modules/ui_extra_networks.py +++ b/modules/ui_extra_networks.py @@ -239,7 +239,7 @@ class ExtraNetworksPage: ) onclick = html.escape(onclick) - btn_copy_path = self.btn_copy_path_tpl.format(**{"filename": quote_js(item["filename"])}) + btn_copy_path = self.btn_copy_path_tpl.format(**{"filename": item["filename"]}) btn_metadata = "" metadata = item.get("metadata") if metadata: From 25cd53d77561fda3dbfd6c280f0d2852cb162bda Mon Sep 17 00:00:00 2001 From: AUTOMATIC1111 <16777216c@gmail.com> Date: Wed, 20 Mar 2024 09:17:11 +0300 Subject: [PATCH 166/496] scheduler selection in main UI --- modules/img2img.py | 4 +- modules/processing.py | 2 + modules/processing_scripts/sampler.py | 79 +++++++++++++++++++++ modules/scripts.py | 3 + modules/sd_samplers.py | 15 ++-- modules/sd_samplers_custom_schedulers.py | 12 ---- modules/sd_samplers_kdiffusion.py | 90 +++++++++--------------- modules/sd_schedulers.py | 38 ++++++++++ modules/shared_options.py | 1 - modules/txt2img.py | 4 +- modules/ui.py | 35 ++------- 11 files changed, 175 insertions(+), 108 deletions(-) create mode 100644 modules/processing_scripts/sampler.py delete mode 100644 modules/sd_samplers_custom_schedulers.py create mode 100644 modules/sd_schedulers.py diff --git a/modules/img2img.py b/modules/img2img.py index e7fb3ea3..9e316d45 100644 --- a/modules/img2img.py +++ b/modules/img2img.py @@ -146,7 +146,7 @@ def process_batch(p, input_dir, output_dir, inpaint_mask_dir, args, to_scale=Fal 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, 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, request: gr.Request, *args): override_settings = create_override_settings_dict(override_settings_texts) is_batch = mode == 5 @@ -193,10 +193,8 @@ def img2img(id_task: str, mode: int, prompt: str, negative_prompt: str, prompt_s prompt=prompt, negative_prompt=negative_prompt, styles=prompt_styles, - sampler_name=sampler_name, batch_size=batch_size, n_iter=n_iter, - steps=steps, cfg_scale=cfg_scale, width=width, height=height, diff --git a/modules/processing.py b/modules/processing.py index d6873a51..ac554141 100644 --- a/modules/processing.py +++ b/modules/processing.py @@ -152,6 +152,7 @@ class StableDiffusionProcessing: seed_resize_from_w: int = -1 seed_enable_extras: bool = True sampler_name: str = None + scheduler: str = None batch_size: int = 1 n_iter: int = 1 steps: int = 50 @@ -721,6 +722,7 @@ def create_infotext(p, all_prompts, all_seeds, all_subseeds, comments=None, iter generation_params = { "Steps": p.steps, "Sampler": p.sampler_name, + "Schedule type": p.scheduler, "CFG scale": p.cfg_scale, "Image CFG scale": getattr(p, 'image_cfg_scale', None), "Seed": p.all_seeds[0] if use_main_prompt else all_seeds[index], diff --git a/modules/processing_scripts/sampler.py b/modules/processing_scripts/sampler.py new file mode 100644 index 00000000..83b95255 --- /dev/null +++ b/modules/processing_scripts/sampler.py @@ -0,0 +1,79 @@ +import gradio as gr +import functools + +from modules import scripts, sd_samplers, sd_schedulers, shared +from modules.infotext_utils import PasteField +from modules.ui_components import FormRow, FormGroup + + +def get_sampler_from_infotext(d: dict): + return get_sampler_and_scheduler(d.get("Sampler"), d.get("Schedule type"))[0] + + +def get_scheduler_from_infotext(d: dict): + return get_sampler_and_scheduler(d.get("Sampler"), d.get("Schedule type"))[1] + + +@functools.cache +def get_sampler_and_scheduler(sampler_name, scheduler_name): + default_sampler = sd_samplers.samplers[0] + found_scheduler = sd_schedulers.schedulers_map.get(scheduler_name, sd_schedulers.schedulers[0]) + + name = sampler_name or default_sampler.name + + for scheduler in sd_schedulers.schedulers: + name_options = [scheduler.label, scheduler.name, *(scheduler.aliases or [])] + + for name_option in name_options: + if name.endswith(" " + name_option): + found_scheduler = scheduler + name = name[0:-(len(name_option) + 1)] + break + + sampler = sd_samplers.all_samplers_map.get(name, default_sampler) + + # revert back to Automatic if it's the default scheduler for the selected sampler + if sampler.options.get('scheduler', None) == found_scheduler.name: + found_scheduler = sd_schedulers.schedulers[0] + + return sampler.name, found_scheduler.label + + +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, get_sampler_from_infotext, api="sampler_name"), + PasteField(self.scheduler, 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 diff --git a/modules/scripts.py b/modules/scripts.py index 20710b37..264503ca 100644 --- a/modules/scripts.py +++ b/modules/scripts.py @@ -352,6 +352,9 @@ class ScriptBuiltinUI(Script): return f'{tabname}{item_id}' + def show(self, is_img2img): + return AlwaysVisible + current_basedir = paths.script_path diff --git a/modules/sd_samplers.py b/modules/sd_samplers.py index a58528a0..1f50297e 100644 --- a/modules/sd_samplers.py +++ b/modules/sd_samplers.py @@ -1,7 +1,10 @@ -from modules import sd_samplers_kdiffusion, sd_samplers_timesteps, sd_samplers_lcm, shared +from __future__ import annotations + +from modules import sd_samplers_kdiffusion, sd_samplers_timesteps, sd_samplers_lcm, shared, sd_samplers_common # imports for functions that previously were here and are used by other modules -from modules.sd_samplers_common import samples_to_image_grid, sample_to_image # noqa: F401 +samples_to_image_grid = sd_samplers_common.samples_to_image_grid +sample_to_image = sd_samplers_common.sample_to_image all_samplers = [ *sd_samplers_kdiffusion.samplers_data_k_diffusion, @@ -10,8 +13,8 @@ all_samplers = [ ] all_samplers_map = {x.name: x for x in all_samplers} -samplers = [] -samplers_for_img2img = [] +samplers: list[sd_samplers_common.SamplerData] = [] +samplers_for_img2img: list[sd_samplers_common.SamplerData] = [] samplers_map = {} samplers_hidden = {} @@ -57,4 +60,8 @@ def visible_sampler_names(): return [x.name for x in samplers if x.name not in samplers_hidden] +def visible_samplers(): + return [x for x in samplers if x.name not in samplers_hidden] + + set_samplers() diff --git a/modules/sd_samplers_custom_schedulers.py b/modules/sd_samplers_custom_schedulers.py deleted file mode 100644 index 78d6a2cd..00000000 --- a/modules/sd_samplers_custom_schedulers.py +++ /dev/null @@ -1,12 +0,0 @@ -import torch - - -def sgm_uniform(n, sigma_min, sigma_max, inner_model, device): - start = inner_model.sigma_to_t(torch.tensor(sigma_max)) - end = inner_model.sigma_to_t(torch.tensor(sigma_min)) - sigs = [ - inner_model.t_to_sigma(ts) - for ts in torch.linspace(start, end, n)[:-1] - ] - sigs += [0.0] - return torch.FloatTensor(sigs).to(device) diff --git a/modules/sd_samplers_kdiffusion.py b/modules/sd_samplers_kdiffusion.py index 516552a1..0db0e34a 100644 --- a/modules/sd_samplers_kdiffusion.py +++ b/modules/sd_samplers_kdiffusion.py @@ -1,41 +1,28 @@ import torch import inspect import k_diffusion.sampling -from modules import sd_samplers_common, sd_samplers_extra, sd_samplers_cfg_denoiser +from modules import sd_samplers_common, sd_samplers_extra, sd_samplers_cfg_denoiser, sd_schedulers from modules.sd_samplers_cfg_denoiser import CFGDenoiser # noqa: F401 -from modules.sd_samplers_custom_schedulers import sgm_uniform from modules.script_callbacks import ExtraNoiseParams, extra_noise_callback from modules.shared import opts import modules.shared as shared samplers_k_diffusion = [ - ('DPM++ 2M Karras', 'sample_dpmpp_2m', ['k_dpmpp_2m_ka'], {'scheduler': 'karras'}), - ('DPM++ SDE Karras', 'sample_dpmpp_sde', ['k_dpmpp_sde_ka'], {'scheduler': 'karras', "second_order": True, "brownian_noise": True}), - ('DPM++ 2M SDE Exponential', 'sample_dpmpp_2m_sde', ['k_dpmpp_2m_sde_exp'], {'scheduler': 'exponential', "brownian_noise": True}), - ('DPM++ 2M SDE Karras', 'sample_dpmpp_2m_sde', ['k_dpmpp_2m_sde_ka'], {'scheduler': 'karras', "brownian_noise": True}), + ('DPM++ 2M', 'sample_dpmpp_2m', ['k_dpmpp_2m'], {'scheduler': 'karras'}), + ('DPM++ SDE', 'sample_dpmpp_sde', ['k_dpmpp_sde'], {'scheduler': 'karras', "second_order": True, "brownian_noise": True}), + ('DPM++ 2M SDE', 'sample_dpmpp_2m_sde', ['k_dpmpp_2m_sde'], {'scheduler': 'exponential', "brownian_noise": True}), + ('DPM++ 2M SDE Heun', 'sample_dpmpp_2m_sde', ['k_dpmpp_2m_sde_heun'], {'scheduler': 'exponential', "brownian_noise": True, "solver_type": "heun"}), + ('DPM++ 2S a', 'sample_dpmpp_2s_ancestral', ['k_dpmpp_2s_a'], {'scheduler': 'karras', "uses_ensd": True, "second_order": True}), + ('DPM++ 3M SDE', 'sample_dpmpp_3m_sde', ['k_dpmpp_3m_sde'], {'scheduler': 'exponential', 'discard_next_to_last_sigma': True, "brownian_noise": True}), ('Euler a', 'sample_euler_ancestral', ['k_euler_a', 'k_euler_ancestral'], {"uses_ensd": True}), ('Euler', 'sample_euler', ['k_euler'], {}), ('LMS', 'sample_lms', ['k_lms'], {}), ('Heun', 'sample_heun', ['k_heun'], {"second_order": True}), - ('DPM2', 'sample_dpm_2', ['k_dpm_2'], {'discard_next_to_last_sigma': True, "second_order": True}), - ('DPM2 a', 'sample_dpm_2_ancestral', ['k_dpm_2_a'], {'discard_next_to_last_sigma': True, "uses_ensd": True, "second_order": True}), - ('DPM++ 2S a', 'sample_dpmpp_2s_ancestral', ['k_dpmpp_2s_a'], {"uses_ensd": True, "second_order": True}), - ('DPM++ 2M', 'sample_dpmpp_2m', ['k_dpmpp_2m'], {}), - ('DPM++ SDE', 'sample_dpmpp_sde', ['k_dpmpp_sde'], {"second_order": True, "brownian_noise": True}), - ('DPM++ 2M SDE', 'sample_dpmpp_2m_sde', ['k_dpmpp_2m_sde_ka'], {"brownian_noise": True}), - ('DPM++ 2M SDE Heun', 'sample_dpmpp_2m_sde', ['k_dpmpp_2m_sde_heun'], {"brownian_noise": True, "solver_type": "heun"}), - ('DPM++ 2M SDE Heun Karras', 'sample_dpmpp_2m_sde', ['k_dpmpp_2m_sde_heun_ka'], {'scheduler': 'karras', "brownian_noise": True, "solver_type": "heun"}), - ('DPM++ 2M SDE Heun Exponential', 'sample_dpmpp_2m_sde', ['k_dpmpp_2m_sde_heun_exp'], {'scheduler': 'exponential', "brownian_noise": True, "solver_type": "heun"}), - ('DPM++ 3M SDE', 'sample_dpmpp_3m_sde', ['k_dpmpp_3m_sde'], {'discard_next_to_last_sigma': True, "brownian_noise": True}), - ('DPM++ 3M SDE Karras', 'sample_dpmpp_3m_sde', ['k_dpmpp_3m_sde_ka'], {'scheduler': 'karras', 'discard_next_to_last_sigma': True, "brownian_noise": True}), - ('DPM++ 3M SDE Exponential', 'sample_dpmpp_3m_sde', ['k_dpmpp_3m_sde_exp'], {'scheduler': 'exponential', 'discard_next_to_last_sigma': True, "brownian_noise": True}), + ('DPM2', 'sample_dpm_2', ['k_dpm_2'], {'scheduler': 'karras', 'discard_next_to_last_sigma': True, "second_order": True}), + ('DPM2 a', 'sample_dpm_2_ancestral', ['k_dpm_2_a'], {'scheduler': 'karras', 'discard_next_to_last_sigma': True, "uses_ensd": True, "second_order": True}), ('DPM fast', 'sample_dpm_fast', ['k_dpm_fast'], {"uses_ensd": True}), ('DPM adaptive', 'sample_dpm_adaptive', ['k_dpm_ad'], {"uses_ensd": True}), - ('LMS Karras', 'sample_lms', ['k_lms_ka'], {'scheduler': 'karras'}), - ('DPM2 Karras', 'sample_dpm_2', ['k_dpm_2_ka'], {'scheduler': 'karras', 'discard_next_to_last_sigma': True, "uses_ensd": True, "second_order": True}), - ('DPM2 a Karras', 'sample_dpm_2_ancestral', ['k_dpm_2_a_ka'], {'scheduler': 'karras', 'discard_next_to_last_sigma': True, "uses_ensd": True, "second_order": True}), - ('DPM++ 2S a Karras', 'sample_dpmpp_2s_ancestral', ['k_dpmpp_2s_a_ka'], {'scheduler': 'karras', "uses_ensd": True, "second_order": True}), ('Restart', sd_samplers_extra.restart_sampler, ['restart'], {'scheduler': 'karras', "second_order": True}), ] @@ -59,13 +46,7 @@ sampler_extra_params = { } k_diffusion_samplers_map = {x.name: x for x in samplers_data_k_diffusion} -k_diffusion_scheduler = { - 'Automatic': None, - 'karras': k_diffusion.sampling.get_sigmas_karras, - 'exponential': k_diffusion.sampling.get_sigmas_exponential, - 'polyexponential': k_diffusion.sampling.get_sigmas_polyexponential, - 'sgm_uniform' : sgm_uniform, -} +k_diffusion_scheduler = {x.name: x.function for x in sd_schedulers.schedulers} class CFGDenoiserKDiffusion(sd_samplers_cfg_denoiser.CFGDenoiser): @@ -98,47 +79,44 @@ class KDiffusionSampler(sd_samplers_common.Sampler): steps += 1 if discard_next_to_last_sigma else 0 + scheduler_name = p.scheduler or 'Automatic' + if scheduler_name == 'Automatic': + scheduler_name = self.config.options.get('scheduler', None) + + scheduler = sd_schedulers.schedulers_map.get(scheduler_name) + + m_sigma_min, m_sigma_max = self.model_wrap.sigmas[0].item(), self.model_wrap.sigmas[-1].item() + sigma_min, sigma_max = (0.1, 10) if opts.use_old_karras_scheduler_sigmas else (m_sigma_min, m_sigma_max) + if p.sampler_noise_scheduler_override: sigmas = p.sampler_noise_scheduler_override(steps) - elif opts.k_sched_type != "Automatic": - m_sigma_min, m_sigma_max = (self.model_wrap.sigmas[0].item(), self.model_wrap.sigmas[-1].item()) - sigma_min, sigma_max = (0.1, 10) if opts.use_old_karras_scheduler_sigmas else (m_sigma_min, m_sigma_max) - sigmas_kwargs = { - 'sigma_min': sigma_min, - 'sigma_max': sigma_max, - } + elif scheduler is None or scheduler.function is None: + sigmas = self.model_wrap.get_sigmas(steps) + else: + sigmas_kwargs = {'sigma_min': sigma_min, 'sigma_max': sigma_max} - sigmas_func = k_diffusion_scheduler[opts.k_sched_type] - p.extra_generation_params["Schedule type"] = opts.k_sched_type + p.extra_generation_params["Schedule type"] = scheduler.label - if opts.sigma_min != m_sigma_min and opts.sigma_min != 0: + if opts.sigma_min != 0 and opts.sigma_min != m_sigma_min: sigmas_kwargs['sigma_min'] = opts.sigma_min p.extra_generation_params["Schedule min sigma"] = opts.sigma_min - if opts.sigma_max != m_sigma_max and opts.sigma_max != 0: + + if opts.sigma_max != 0 and opts.sigma_max != m_sigma_max: sigmas_kwargs['sigma_max'] = opts.sigma_max p.extra_generation_params["Schedule max sigma"] = opts.sigma_max - default_rho = 1. if opts.k_sched_type == "polyexponential" else 7. - - if opts.k_sched_type != 'exponential' and opts.rho != 0 and opts.rho != default_rho: + if scheduler.default_rho != -1 and opts.rho != 0 and opts.rho != scheduler.default_rho: sigmas_kwargs['rho'] = opts.rho p.extra_generation_params["Schedule rho"] = opts.rho - if opts.k_sched_type == 'sgm_uniform': + + if scheduler.need_inner_model: + sigmas_kwargs['inner_model'] = self.model_wrap + + if scheduler.name == "sgm_uniform": # XXX check this # Ensure the "step" will be target step + 1 steps += 1 if not discard_next_to_last_sigma else 0 - sigmas_kwargs['inner_model'] = self.model_wrap - sigmas_kwargs.pop('rho', None) - sigmas = sigmas_func(n=steps, **sigmas_kwargs, device=shared.device) - elif self.config is not None and self.config.options.get('scheduler', None) == 'karras': - sigma_min, sigma_max = (0.1, 10) if opts.use_old_karras_scheduler_sigmas else (self.model_wrap.sigmas[0].item(), self.model_wrap.sigmas[-1].item()) - - sigmas = k_diffusion.sampling.get_sigmas_karras(n=steps, sigma_min=sigma_min, sigma_max=sigma_max, device=shared.device) - elif self.config is not None and self.config.options.get('scheduler', None) == 'exponential': - m_sigma_min, m_sigma_max = (self.model_wrap.sigmas[0].item(), self.model_wrap.sigmas[-1].item()) - sigmas = k_diffusion.sampling.get_sigmas_exponential(n=steps, sigma_min=m_sigma_min, sigma_max=m_sigma_max, device=shared.device) - else: - sigmas = self.model_wrap.get_sigmas(steps) + sigmas = scheduler.function(n=steps, **sigmas_kwargs, device=shared.device) if discard_next_to_last_sigma: sigmas = torch.cat([sigmas[:-2], sigmas[-1:]]) diff --git a/modules/sd_schedulers.py b/modules/sd_schedulers.py new file mode 100644 index 00000000..c327c90b --- /dev/null +++ b/modules/sd_schedulers.py @@ -0,0 +1,38 @@ +import dataclasses + +import torch + +import k_diffusion + + +@dataclasses.dataclass +class Scheduler: + name: str + label: str + function: any + + default_rho: float = -1 + need_inner_model: bool = False + aliases: list = None + + +def sgm_uniform(n, sigma_min, sigma_max, inner_model, device): + start = inner_model.sigma_to_t(torch.tensor(sigma_max)) + end = inner_model.sigma_to_t(torch.tensor(sigma_min)) + sigs = [ + inner_model.t_to_sigma(ts) + for ts in torch.linspace(start, end, n)[:-1] + ] + sigs += [0.0] + return torch.FloatTensor(sigs).to(device) + + +schedulers = [ + Scheduler('automatic', 'Automatic', None), + Scheduler('karras', 'Karras', k_diffusion.sampling.get_sigmas_karras, default_rho=7.0), + Scheduler('exponential', 'Exponential', k_diffusion.sampling.get_sigmas_exponential), + Scheduler('polyexponential', 'Polyexponential', k_diffusion.sampling.get_sigmas_polyexponential, default_rho=1.0), + Scheduler('sgm_uniform', 'SGM Uniform', sgm_uniform, need_inner_model=True, aliases=["SGMUniform"]), +] + +schedulers_map = {**{x.name: x for x in schedulers}, **{x.label: x for x in schedulers}} diff --git a/modules/shared_options.py b/modules/shared_options.py index 84c9b224..590ae6a6 100644 --- a/modules/shared_options.py +++ b/modules/shared_options.py @@ -368,7 +368,6 @@ options_templates.update(options_section(('sampler-params', "Sampler parameters" 's_tmin': OptionInfo(0.0, "sigma tmin", gr.Slider, {"minimum": 0.0, "maximum": 10.0, "step": 0.01}, infotext='Sigma tmin').info('enable stochasticity; start value of the sigma range; only applies to Euler, Heun, and DPM2'), 's_tmax': OptionInfo(0.0, "sigma tmax", gr.Slider, {"minimum": 0.0, "maximum": 999.0, "step": 0.01}, infotext='Sigma tmax').info("0 = inf; end value of the sigma range; only applies to Euler, Heun, and DPM2"), 's_noise': OptionInfo(1.0, "sigma noise", gr.Slider, {"minimum": 0.0, "maximum": 1.1, "step": 0.001}, infotext='Sigma noise').info('amount of additional noise to counteract loss of detail during sampling'), - 'k_sched_type': OptionInfo("Automatic", "Scheduler type", gr.Dropdown, {"choices": ["Automatic", "karras", "exponential", "polyexponential", "sgm_uniform"]}, infotext='Schedule type').info("lets you override the noise schedule for k-diffusion samplers; choosing Automatic disables the three parameters below"), 'sigma_min': OptionInfo(0.0, "sigma min", gr.Number, infotext='Schedule min sigma').info("0 = default (~0.03); minimum noise strength for k-diffusion noise scheduler"), 'sigma_max': OptionInfo(0.0, "sigma max", gr.Number, infotext='Schedule max sigma').info("0 = default (~14.6); maximum noise strength for k-diffusion noise scheduler"), 'rho': OptionInfo(0.0, "rho", gr.Number, infotext='Schedule rho').info("0 = default (7 for karras, 1 for polyexponential); higher values result in a steeper noise schedule (decreases faster)"), diff --git a/modules/txt2img.py b/modules/txt2img.py index fc56b8a8..53fa0099 100644 --- a/modules/txt2img.py +++ b/modules/txt2img.py @@ -11,7 +11,7 @@ from PIL import Image import gradio as gr -def txt2img_create_processing(id_task: str, request: gr.Request, prompt: str, negative_prompt: str, prompt_styles, steps: int, sampler_name: str, n_iter: int, batch_size: int, cfg_scale: float, height: int, width: int, enable_hr: bool, denoising_strength: float, hr_scale: float, hr_upscaler: str, hr_second_pass_steps: int, hr_resize_x: int, hr_resize_y: int, hr_checkpoint_name: str, hr_sampler_name: str, hr_prompt: str, hr_negative_prompt, override_settings_texts, *args, force_enable_hr=False): +def txt2img_create_processing(id_task: str, request: gr.Request, prompt: str, negative_prompt: str, prompt_styles, n_iter: int, batch_size: int, cfg_scale: float, height: int, width: int, enable_hr: bool, denoising_strength: float, hr_scale: float, hr_upscaler: str, hr_second_pass_steps: int, hr_resize_x: int, hr_resize_y: int, hr_checkpoint_name: str, hr_sampler_name: str, hr_prompt: str, hr_negative_prompt, override_settings_texts, *args, force_enable_hr=False): override_settings = create_override_settings_dict(override_settings_texts) if force_enable_hr: @@ -24,10 +24,8 @@ def txt2img_create_processing(id_task: str, request: gr.Request, prompt: str, ne prompt=prompt, styles=prompt_styles, negative_prompt=negative_prompt, - sampler_name=sampler_name, batch_size=batch_size, n_iter=n_iter, - steps=steps, cfg_scale=cfg_scale, width=width, height=height, diff --git a/modules/ui.py b/modules/ui.py index 7b434162..c964d5e2 100644 --- a/modules/ui.py +++ b/modules/ui.py @@ -12,7 +12,7 @@ import numpy as np from PIL import Image, PngImagePlugin # noqa: F401 from modules.call_queue import wrap_gradio_gpu_call, wrap_queued_call, wrap_gradio_call -from modules import gradio_extensons # noqa: F401 +from modules import gradio_extensons, sd_schedulers # noqa: F401 from modules import sd_hijack, sd_models, script_callbacks, ui_extensions, deepbooru, extra_networks, ui_common, ui_postprocessing, progress, ui_loadsave, shared_items, ui_settings, timer, sysinfo, ui_checkpoint_merger, scripts, sd_samplers, processing, ui_extra_networks, ui_toprow, launch_utils from modules.ui_components import FormRow, FormGroup, ToolButton, FormHTML, InputAccordion, ResizeHandleRow from modules.paths import script_path @@ -229,19 +229,6 @@ def create_output_panel(tabname, outdir, toprow=None): return ui_common.create_output_panel(tabname, outdir, toprow) -def create_sampler_and_steps_selection(choices, tabname): - if opts.samplers_in_dropdown: - with FormRow(elem_id=f"sampler_selection_{tabname}"): - sampler_name = gr.Dropdown(label='Sampling method', elem_id=f"{tabname}_sampling", choices=choices, value=choices[0]) - steps = gr.Slider(minimum=1, maximum=150, step=1, elem_id=f"{tabname}_steps", label="Sampling steps", value=20) - else: - with FormGroup(elem_id=f"sampler_selection_{tabname}"): - steps = gr.Slider(minimum=1, maximum=150, step=1, elem_id=f"{tabname}_steps", label="Sampling steps", value=20) - sampler_name = gr.Radio(label='Sampling method', elem_id=f"{tabname}_sampling", choices=choices, value=choices[0]) - - return steps, sampler_name - - def ordered_ui_categories(): user_order = {x.strip(): i * 2 + 1 for i, x in enumerate(shared.opts.ui_reorder_list)} @@ -295,9 +282,6 @@ def create_ui(): if category == "prompt": toprow.create_inline_toprow_prompts() - if category == "sampler": - steps, sampler_name = create_sampler_and_steps_selection(sd_samplers.visible_sampler_names(), "txt2img") - elif category == "dimensions": with FormRow(): with gr.Column(elem_id="txt2img_column_size", scale=4): @@ -396,8 +380,6 @@ def create_ui(): toprow.prompt, toprow.negative_prompt, toprow.ui_styles.dropdown, - steps, - sampler_name, batch_count, batch_size, cfg_scale, @@ -461,8 +443,6 @@ def create_ui(): txt2img_paste_fields = [ PasteField(toprow.prompt, "Prompt", api="prompt"), PasteField(toprow.negative_prompt, "Negative prompt", api="negative_prompt"), - PasteField(steps, "Steps", api="steps"), - PasteField(sampler_name, "Sampler", api="sampler_name"), PasteField(cfg_scale, "CFG scale", api="cfg_scale"), PasteField(width, "Size-1", api="width"), PasteField(height, "Size-2", api="height"), @@ -488,11 +468,13 @@ def create_ui(): paste_button=toprow.paste, tabname="txt2img", source_text_component=toprow.prompt, source_image_component=None, )) + steps = scripts.scripts_txt2img.script('Sampler').steps + txt2img_preview_params = [ toprow.prompt, toprow.negative_prompt, steps, - sampler_name, + scripts.scripts_txt2img.script('Sampler').sampler_name, cfg_scale, scripts.scripts_txt2img.script('Seed').seed, width, @@ -623,9 +605,6 @@ def create_ui(): with FormRow(): resize_mode = gr.Radio(label="Resize mode", elem_id="resize_mode", choices=["Just resize", "Crop and resize", "Resize and fill", "Just resize (latent upscale)"], type="index", value="Just resize") - if category == "sampler": - steps, sampler_name = create_sampler_and_steps_selection(sd_samplers.visible_sampler_names(), "img2img") - elif category == "dimensions": with FormRow(): with gr.Column(elem_id="img2img_column_size", scale=4): @@ -754,8 +733,6 @@ def create_ui(): inpaint_color_sketch_orig, init_img_inpaint, init_mask_inpaint, - steps, - sampler_name, mask_blur, mask_alpha, inpainting_fill, @@ -840,6 +817,8 @@ def create_ui(): **interrogate_args, ) + steps = scripts.scripts_img2img.script('Sampler').steps + toprow.ui_styles.dropdown.change(fn=wrap_queued_call(update_token_counter), inputs=[toprow.prompt, steps, toprow.ui_styles.dropdown], outputs=[toprow.token_counter]) toprow.ui_styles.dropdown.change(fn=wrap_queued_call(update_negative_prompt_token_counter), inputs=[toprow.negative_prompt, steps, toprow.ui_styles.dropdown], outputs=[toprow.negative_token_counter]) toprow.token_button.click(fn=update_token_counter, inputs=[toprow.prompt, steps, toprow.ui_styles.dropdown], outputs=[toprow.token_counter]) @@ -848,8 +827,6 @@ def create_ui(): img2img_paste_fields = [ (toprow.prompt, "Prompt"), (toprow.negative_prompt, "Negative prompt"), - (steps, "Steps"), - (sampler_name, "Sampler"), (cfg_scale, "CFG scale"), (image_cfg_scale, "Image CFG scale"), (width, "Size-1"), From 61f488302f37ad959eb5bb2eebed7e106c40c8b7 Mon Sep 17 00:00:00 2001 From: Art Gourieff <85128026+Gourieff@users.noreply.github.com> Date: Wed, 20 Mar 2024 13:28:32 +0700 Subject: [PATCH 167/496] FIX: Allow PNG-RGBA for Extras Tab --- modules/postprocessing.py | 2 +- modules/ui_postprocessing.py | 2 +- scripts/postprocessing_codeformer.py | 2 +- scripts/postprocessing_gfpgan.py | 2 +- 4 files changed, 4 insertions(+), 4 deletions(-) diff --git a/modules/postprocessing.py b/modules/postprocessing.py index f1488232..9afcfef8 100644 --- a/modules/postprocessing.py +++ b/modules/postprocessing.py @@ -66,7 +66,7 @@ def run_postprocessing(extras_mode, image, image_folder, input_dir, output_dir, if 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) diff --git a/modules/ui_postprocessing.py b/modules/ui_postprocessing.py index 7261c2df..dc08350d 100644 --- a/modules/ui_postprocessing.py +++ b/modules/ui_postprocessing.py @@ -12,7 +12,7 @@ def create_ui(): with gr.Column(variant='compact'): with gr.Tabs(elem_id="mode_extras"): with gr.TabItem('Single Image', id="single_image", elem_id="extras_single_tab") as tab_single: - extras_image = gr.Image(label="Source", source="upload", interactive=True, type="pil", elem_id="extras_image") + extras_image = gr.Image(label="Source", source="upload", interactive=True, type="pil", elem_id="extras_image", image_mode="RGBA") with gr.TabItem('Batch Process', id="batch_process", elem_id="extras_batch_process_tab") as tab_batch: image_batch = gr.Files(label="Batch Process", interactive=True, elem_id="extras_image_batch") diff --git a/scripts/postprocessing_codeformer.py b/scripts/postprocessing_codeformer.py index e1e156dd..53a0cc44 100644 --- a/scripts/postprocessing_codeformer.py +++ b/scripts/postprocessing_codeformer.py @@ -25,7 +25,7 @@ class ScriptPostprocessingCodeFormer(scripts_postprocessing.ScriptPostprocessing if codeformer_visibility == 0 or not enable: return - restored_img = codeformer_model.codeformer.restore(np.array(pp.image, dtype=np.uint8), w=codeformer_weight) + restored_img = codeformer_model.codeformer.restore(np.array(pp.image.convert("RGB"), dtype=np.uint8), w=codeformer_weight) res = Image.fromarray(restored_img) if codeformer_visibility < 1.0: diff --git a/scripts/postprocessing_gfpgan.py b/scripts/postprocessing_gfpgan.py index 6e756605..57e36239 100644 --- a/scripts/postprocessing_gfpgan.py +++ b/scripts/postprocessing_gfpgan.py @@ -22,7 +22,7 @@ class ScriptPostprocessingGfpGan(scripts_postprocessing.ScriptPostprocessing): if gfpgan_visibility == 0 or not enable: return - restored_img = gfpgan_model.gfpgan_fix_faces(np.array(pp.image, dtype=np.uint8)) + restored_img = gfpgan_model.gfpgan_fix_faces(np.array(pp.image.convert("RGB"), dtype=np.uint8)) res = Image.fromarray(restored_img) if gfpgan_visibility < 1.0: From 76f8436bfab829c40f6776540ff14f428b9a3557 Mon Sep 17 00:00:00 2001 From: AUTOMATIC1111 <16777216c@gmail.com> Date: Wed, 20 Mar 2024 10:27:32 +0300 Subject: [PATCH 168/496] add Uniform scheduler --- modules/sd_schedulers.py | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/modules/sd_schedulers.py b/modules/sd_schedulers.py index c327c90b..2a7ab092 100644 --- a/modules/sd_schedulers.py +++ b/modules/sd_schedulers.py @@ -16,6 +16,10 @@ class Scheduler: aliases: list = None +def uniform(n, sigma_min, sigma_max, inner_model, device): + return inner_model.get_sigmas(n) + + def sgm_uniform(n, sigma_min, sigma_max, inner_model, device): start = inner_model.sigma_to_t(torch.tensor(sigma_max)) end = inner_model.sigma_to_t(torch.tensor(sigma_min)) @@ -29,6 +33,7 @@ def sgm_uniform(n, sigma_min, sigma_max, inner_model, device): schedulers = [ Scheduler('automatic', 'Automatic', None), + Scheduler('uniform', 'Uniform', uniform, need_inner_model=True), Scheduler('karras', 'Karras', k_diffusion.sampling.get_sigmas_karras, default_rho=7.0), Scheduler('exponential', 'Exponential', k_diffusion.sampling.get_sigmas_exponential), Scheduler('polyexponential', 'Polyexponential', k_diffusion.sampling.get_sigmas_polyexponential, default_rho=1.0), From ac9aa44cb8f91b18934f03c8ce8bb2061c61da6f Mon Sep 17 00:00:00 2001 From: AUTOMATIC1111 <16777216c@gmail.com> Date: Wed, 20 Mar 2024 10:27:53 +0300 Subject: [PATCH 169/496] do not add 'Automatic' to infotext --- modules/processing.py | 2 +- modules/sd_samplers_kdiffusion.py | 3 ++- 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/modules/processing.py b/modules/processing.py index ac554141..716329fa 100644 --- a/modules/processing.py +++ b/modules/processing.py @@ -722,7 +722,7 @@ def create_infotext(p, all_prompts, all_seeds, all_subseeds, comments=None, iter generation_params = { "Steps": p.steps, "Sampler": p.sampler_name, - "Schedule type": p.scheduler, + "Schedule type": None, "CFG scale": p.cfg_scale, "Image CFG scale": getattr(p, 'image_cfg_scale', None), "Seed": p.all_seeds[0] if use_main_prompt else all_seeds[index], diff --git a/modules/sd_samplers_kdiffusion.py b/modules/sd_samplers_kdiffusion.py index 0db0e34a..04b2f7f0 100644 --- a/modules/sd_samplers_kdiffusion.py +++ b/modules/sd_samplers_kdiffusion.py @@ -95,7 +95,8 @@ class KDiffusionSampler(sd_samplers_common.Sampler): else: sigmas_kwargs = {'sigma_min': sigma_min, 'sigma_max': sigma_max} - p.extra_generation_params["Schedule type"] = scheduler.label + if scheduler.label != 'Automatic': + p.extra_generation_params["Schedule type"] = scheduler.label if opts.sigma_min != 0 and opts.sigma_min != m_sigma_min: sigmas_kwargs['sigma_min'] = opts.sigma_min From 31306ce6721e6f1ae323da64767da91897b3a1c2 Mon Sep 17 00:00:00 2001 From: AUTOMATIC1111 <16777216c@gmail.com> Date: Wed, 20 Mar 2024 10:29:52 +0300 Subject: [PATCH 170/496] change the behavior of discard_next_to_last_sigma for sgm_uniform to match other schedulers --- modules/sd_samplers_kdiffusion.py | 4 ---- modules/sd_schedulers.py | 2 +- 2 files changed, 1 insertion(+), 5 deletions(-) diff --git a/modules/sd_samplers_kdiffusion.py b/modules/sd_samplers_kdiffusion.py index 04b2f7f0..d053e48b 100644 --- a/modules/sd_samplers_kdiffusion.py +++ b/modules/sd_samplers_kdiffusion.py @@ -113,10 +113,6 @@ class KDiffusionSampler(sd_samplers_common.Sampler): if scheduler.need_inner_model: sigmas_kwargs['inner_model'] = self.model_wrap - if scheduler.name == "sgm_uniform": # XXX check this - # Ensure the "step" will be target step + 1 - steps += 1 if not discard_next_to_last_sigma else 0 - sigmas = scheduler.function(n=steps, **sigmas_kwargs, device=shared.device) if discard_next_to_last_sigma: diff --git a/modules/sd_schedulers.py b/modules/sd_schedulers.py index 2a7ab092..75eb3ac0 100644 --- a/modules/sd_schedulers.py +++ b/modules/sd_schedulers.py @@ -25,7 +25,7 @@ def sgm_uniform(n, sigma_min, sigma_max, inner_model, device): end = inner_model.sigma_to_t(torch.tensor(sigma_min)) sigs = [ inner_model.t_to_sigma(ts) - for ts in torch.linspace(start, end, n)[:-1] + for ts in torch.linspace(start, end, n + 1)[:-1] ] sigs += [0.0] return torch.FloatTensor(sigs).to(device) From 702edb288e53b9cf9d1727e0ca89f95102907c04 Mon Sep 17 00:00:00 2001 From: Art Gourieff <85128026+Gourieff@users.noreply.github.com> Date: Wed, 20 Mar 2024 15:14:28 +0700 Subject: [PATCH 171/496] FIX: initial_pp RGBA right way --- modules/api/api.py | 2 +- modules/postprocessing.py | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/modules/api/api.py b/modules/api/api.py index 4e656082..3e42dd7b 100644 --- a/modules/api/api.py +++ b/modules/api/api.py @@ -99,7 +99,7 @@ def decode_base64_to_image(encoding): raise HTTPException(status_code=500, detail="Invalid encoded image") from e -def encode_pil_to_base64(image): +def encode_pil_to_base64(image: Image.Image): with io.BytesIO() as output_bytes: if isinstance(image, str): return image diff --git a/modules/postprocessing.py b/modules/postprocessing.py index 9afcfef8..40cf866a 100644 --- a/modules/postprocessing.py +++ b/modules/postprocessing.py @@ -66,7 +66,7 @@ def run_postprocessing(extras_mode, image, image_folder, input_dir, output_dir, if parameters: existing_pnginfo["parameters"] = parameters - initial_pp = scripts_postprocessing.PostprocessedImage(image_data) + initial_pp = scripts_postprocessing.PostprocessedImage(image_data.convert("RGBA")) if image_data.mode == "RGBA" else scripts_postprocessing.PostprocessedImage(image_data.convert("RGB")) scripts.scripts_postproc.run(initial_pp, args) From 8ec890192141fec26265dadc0e50f1525eb87007 Mon Sep 17 00:00:00 2001 From: Art Gourieff <85128026+Gourieff@users.noreply.github.com> Date: Wed, 20 Mar 2024 15:20:29 +0700 Subject: [PATCH 172/496] FIX: No specific type for 'image' arg Roll back --- modules/api/api.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/modules/api/api.py b/modules/api/api.py index 3e42dd7b..4e656082 100644 --- a/modules/api/api.py +++ b/modules/api/api.py @@ -99,7 +99,7 @@ def decode_base64_to_image(encoding): raise HTTPException(status_code=500, detail="Invalid encoded image") from e -def encode_pil_to_base64(image: Image.Image): +def encode_pil_to_base64(image): with io.BytesIO() as output_bytes: if isinstance(image, str): return image From 5fd9a40b92c572c357773018cd47b0a7d3b8f9c3 Mon Sep 17 00:00:00 2001 From: Dalton Date: Wed, 20 Mar 2024 23:01:50 -0400 Subject: [PATCH 173/496] Revert sd_hijack_ddpm_v1.py --- extensions-builtin/LDSR/sd_hijack_ddpm_v1.py | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/extensions-builtin/LDSR/sd_hijack_ddpm_v1.py b/extensions-builtin/LDSR/sd_hijack_ddpm_v1.py index cd54ab4b..7944fb13 100644 --- a/extensions-builtin/LDSR/sd_hijack_ddpm_v1.py +++ b/extensions-builtin/LDSR/sd_hijack_ddpm_v1.py @@ -16,8 +16,7 @@ from contextlib import contextmanager from functools import partial from tqdm import tqdm from torchvision.utils import make_grid -sys.modules['pytorch_lightning.utilities.distributed'] = sys.modules['pytorch_lightning.utilities.rank_zero'] -from pytorch_lightning.utilities.rank_zero import rank_zero_only +from pytorch_lightning.utilities.distributed import rank_zero_only from ldm.util import log_txt_as_img, exists, default, ismap, isimage, mean_flat, count_params, instantiate_from_config from ldm.modules.ema import LitEma From f010dfffb9b6021f8b4e4f03f22532f7edef7137 Mon Sep 17 00:00:00 2001 From: Dalton Date: Wed, 20 Mar 2024 23:02:30 -0400 Subject: [PATCH 174/496] Revert ddpm_edit.py --- modules/models/diffusion/ddpm_edit.py | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/modules/models/diffusion/ddpm_edit.py b/modules/models/diffusion/ddpm_edit.py index 935d3564..347117cb 100644 --- a/modules/models/diffusion/ddpm_edit.py +++ b/modules/models/diffusion/ddpm_edit.py @@ -21,8 +21,7 @@ from contextlib import contextmanager from functools import partial from tqdm import tqdm from torchvision.utils import make_grid -sys.modules['pytorch_lightning.utilities.distributed'] = sys.modules['pytorch_lightning.utilities.rank_zero'] -from pytorch_lightning.utilities.rank_zero import rank_zero_only +from pytorch_lightning.utilities.distributed import rank_zero_only from ldm.util import log_txt_as_img, exists, default, ismap, isimage, mean_flat, count_params, instantiate_from_config from ldm.modules.ema import LitEma From b5b04912b523cfa7fd7e93026b1ca6c0d6456c3f Mon Sep 17 00:00:00 2001 From: Dalton Date: Wed, 20 Mar 2024 23:06:00 -0400 Subject: [PATCH 175/496] Include running pytorch lightning check --- modules/initialize.py | 1 + 1 file changed, 1 insertion(+) diff --git a/modules/initialize.py b/modules/initialize.py index f7313ff4..de92863f 100644 --- a/modules/initialize.py +++ b/modules/initialize.py @@ -51,6 +51,7 @@ def check_versions(): def initialize(): from modules import initialize_util initialize_util.fix_torch_version() + initialize_util.fix_pytorch_lightning() initialize_util.fix_asyncio_event_loop_policy() initialize_util.validate_tls_options() initialize_util.configure_sigint_handler() From 4eb5e09873720c55003bea89d527f99e62f36cb0 Mon Sep 17 00:00:00 2001 From: Dalton Date: Wed, 20 Mar 2024 23:28:40 -0400 Subject: [PATCH 176/496] Update initialize_util.py --- modules/initialize_util.py | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/modules/initialize_util.py b/modules/initialize_util.py index b6767138..63cea137 100644 --- a/modules/initialize_util.py +++ b/modules/initialize_util.py @@ -24,6 +24,13 @@ def fix_torch_version(): torch.__long_version__ = torch.__version__ torch.__version__ = re.search(r'[\d.]+[\d]', torch.__version__).group(0) +def fix_pytorch_lightning(): + import pytorch_lightning + # Checks if pytorch_lightning.utilities.distributed already exists in the sys.modules cache + if 'pytorch_lightning.utilities.distributed' not in sys.modules: + # Lets the user know that the library was not found and then will set it to pytorch_lightning.utilities.rank_zero + print(f"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(): """ From 4bc296332021cb07ba1fe449d72049604efd7a02 Mon Sep 17 00:00:00 2001 From: Dalton Date: Wed, 20 Mar 2024 23:33:15 -0400 Subject: [PATCH 177/496] Remove unnecessary import --- modules/initialize_util.py | 1 - 1 file changed, 1 deletion(-) diff --git a/modules/initialize_util.py b/modules/initialize_util.py index 63cea137..7a661476 100644 --- a/modules/initialize_util.py +++ b/modules/initialize_util.py @@ -25,7 +25,6 @@ def fix_torch_version(): torch.__version__ = re.search(r'[\d.]+[\d]', torch.__version__).group(0) def fix_pytorch_lightning(): - import pytorch_lightning # Checks if pytorch_lightning.utilities.distributed already exists in the sys.modules cache if 'pytorch_lightning.utilities.distributed' not in sys.modules: # Lets the user know that the library was not found and then will set it to pytorch_lightning.utilities.rank_zero From 41907b25f0baa62f773f37a73dc4e656a710bc8a Mon Sep 17 00:00:00 2001 From: Dalton Date: Wed, 20 Mar 2024 23:35:32 -0400 Subject: [PATCH 178/496] Cleanup sd_hijack_ddpm_v1.py Forgot some things to revert --- extensions-builtin/LDSR/sd_hijack_ddpm_v1.py | 2 -- 1 file changed, 2 deletions(-) diff --git a/extensions-builtin/LDSR/sd_hijack_ddpm_v1.py b/extensions-builtin/LDSR/sd_hijack_ddpm_v1.py index 7944fb13..04adc5eb 100644 --- a/extensions-builtin/LDSR/sd_hijack_ddpm_v1.py +++ b/extensions-builtin/LDSR/sd_hijack_ddpm_v1.py @@ -4,12 +4,10 @@ # Some models such as LDSR require VQ to work correctly # The classes are suffixed with "V1" and added back to the "ldm.models.diffusion.ddpm" module -import sys import torch import torch.nn as nn import numpy as np import pytorch_lightning as pl -import pytorch_lightning.utilities.rank_zero from torch.optim.lr_scheduler import LambdaLR from einops import rearrange, repeat from contextlib import contextmanager From 4e6e2574aba19a2a1b248601e33fad82fb881523 Mon Sep 17 00:00:00 2001 From: Dalton Date: Wed, 20 Mar 2024 23:36:35 -0400 Subject: [PATCH 179/496] Cleanup ddpm_edit.py Fully reverts this time --- modules/models/diffusion/ddpm_edit.py | 2 -- 1 file changed, 2 deletions(-) diff --git a/modules/models/diffusion/ddpm_edit.py b/modules/models/diffusion/ddpm_edit.py index 347117cb..6db340da 100644 --- a/modules/models/diffusion/ddpm_edit.py +++ b/modules/models/diffusion/ddpm_edit.py @@ -9,12 +9,10 @@ https://github.com/CompVis/taming-transformers # File modified by authors of InstructPix2Pix from original (https://github.com/CompVis/stable-diffusion). # See more details in LICENSE. -import sys import torch import torch.nn as nn import numpy as np import pytorch_lightning as pl -import pytorch_lightning.utilities.rank_zero from torch.optim.lr_scheduler import LambdaLR from einops import rearrange, repeat from contextlib import contextmanager From 32ba7575010a3fdd024cc4058650d81988d126f1 Mon Sep 17 00:00:00 2001 From: Dalton Date: Wed, 20 Mar 2024 23:55:04 -0400 Subject: [PATCH 180/496] Re-add import but after if check --- modules/initialize_util.py | 1 + 1 file changed, 1 insertion(+) diff --git a/modules/initialize_util.py b/modules/initialize_util.py index 7a661476..8abe7881 100644 --- a/modules/initialize_util.py +++ b/modules/initialize_util.py @@ -27,6 +27,7 @@ def fix_torch_version(): 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(f"Pytorch_lightning.distributed not found, attempting pytorch_lightning.rank_zero") sys.modules["pytorch_lightning.utilities.distributed"] = pytorch_lightning.utilities.rank_zero From 5c5594ff1632ccab6c1f2ae7de3f6c69b1ab6f78 Mon Sep 17 00:00:00 2001 From: AUTOMATIC1111 <16777216c@gmail.com> Date: Thu, 21 Mar 2024 07:09:40 +0300 Subject: [PATCH 181/496] linter --- modules/initialize_util.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/modules/initialize_util.py b/modules/initialize_util.py index 8abe7881..79a72cb3 100644 --- a/modules/initialize_util.py +++ b/modules/initialize_util.py @@ -29,7 +29,7 @@ def fix_pytorch_lightning(): 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(f"Pytorch_lightning.distributed not found, attempting pytorch_lightning.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(): From 57727e554d8f87b4cf438390d6cb05a27d1734f5 Mon Sep 17 00:00:00 2001 From: AUTOMATIC1111 <16777216c@gmail.com> Date: Thu, 21 Mar 2024 07:22:27 +0300 Subject: [PATCH 182/496] make #15334 work without making copies of images --- modules/postprocessing.py | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/modules/postprocessing.py b/modules/postprocessing.py index 7685e925..5a4e693a 100644 --- a/modules/postprocessing.py +++ b/modules/postprocessing.py @@ -66,7 +66,7 @@ def run_postprocessing(extras_mode, image, image_folder, input_dir, output_dir, if parameters: existing_pnginfo["parameters"] = parameters - initial_pp = scripts_postprocessing.PostprocessedImage(image_data.convert("RGBA")) if image_data.mode == "RGBA" else scripts_postprocessing.PostprocessedImage(image_data.convert("RGB")) + initial_pp = scripts_postprocessing.PostprocessedImage(image_data if image_data.mode in ("RGBA", "RGB") else image_data.convert("RGB")) scripts.scripts_postproc.run(initial_pp, args) @@ -122,8 +122,6 @@ def run_postprocessing(extras_mode, image, image_folder, input_dir, output_dir, if extras_mode != 2 or show_extras_results: outputs.append(pp.image) - image_data.close() - devices.torch_gc() shared.state.end() return outputs, ui_common.plaintext_to_html(infotext), '' From 721c4309c26374983589bcaba2f401a4bf01e2c2 Mon Sep 17 00:00:00 2001 From: Andray Date: Thu, 21 Mar 2024 16:29:51 +0400 Subject: [PATCH 183/496] escape brackets in lora random prompt generator --- extensions-builtin/Lora/ui_edit_user_metadata.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/extensions-builtin/Lora/ui_edit_user_metadata.py b/extensions-builtin/Lora/ui_edit_user_metadata.py index 3160aecf..7a07a544 100644 --- a/extensions-builtin/Lora/ui_edit_user_metadata.py +++ b/extensions-builtin/Lora/ui_edit_user_metadata.py @@ -149,6 +149,8 @@ class LoraUserMetadataEditor(ui_extra_networks_user_metadata.UserMetadataEditor) v = random.random() * max_count if count > v: + for x in "({[]})": + tag = tag.replace(x, '\\' + x) res.append(tag) return ", ".join(sorted(res)) From 2941e1f1f3cffc8e2024b359a53c3226c7114d0b Mon Sep 17 00:00:00 2001 From: Aarni Koskela Date: Fri, 22 Mar 2024 11:12:59 +0200 Subject: [PATCH 184/496] Add Size as an XYZ Grid option --- scripts/xyz_grid.py | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/scripts/xyz_grid.py b/scripts/xyz_grid.py index 57ee4708..43965fc9 100644 --- a/scripts/xyz_grid.py +++ b/scripts/xyz_grid.py @@ -106,6 +106,17 @@ def apply_upscale_latent_space(p, x, xs): opts.data["use_scale_latent_for_hires_fix"] = False +def apply_size(p, x: str, xs) -> None: + try: + width, _, height = x.partition('x') + width = int(width.strip()) + height = int(height.strip()) + p.width = width + p.height = height + except ValueError: + print(f"Invalid size in XYZ plot: {x}") + + def find_vae(name: str): if name.lower() in ['auto', 'automatic']: return modules.sd_vae.unspecified @@ -271,6 +282,7 @@ axis_options = [ AxisOption("Refiner switch at", float, apply_field('refiner_switch_at')), AxisOption("RNG source", str, apply_override("randn_source"), choices=lambda: ["GPU", "CPU", "NV"]), AxisOption("FP8 mode", str, apply_override("fp8_storage"), cost=0.9, choices=lambda: ["Disable", "Enable for SDXL", "Enable"]), + AxisOption("Size", str, apply_size), ] From f3ca6a92adf4ffe37d1b6fbe4d9d365c8aee5d80 Mon Sep 17 00:00:00 2001 From: kaalibro Date: Sat, 23 Mar 2024 00:50:37 +0500 Subject: [PATCH 185/496] Fix for #15333 - Fix "X/Y/Z plot" not working with "Schedule type" - Fix "Schedule type" not being saved to "params.txt" --- modules/processing.py | 2 +- scripts/xyz_grid.py | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/modules/processing.py b/modules/processing.py index 716329fa..ac554141 100644 --- a/modules/processing.py +++ b/modules/processing.py @@ -722,7 +722,7 @@ def create_infotext(p, all_prompts, all_seeds, all_subseeds, comments=None, iter generation_params = { "Steps": p.steps, "Sampler": p.sampler_name, - "Schedule type": None, + "Schedule type": p.scheduler, "CFG scale": p.cfg_scale, "Image CFG scale": getattr(p, 'image_cfg_scale', None), "Seed": p.all_seeds[0] if use_main_prompt else all_seeds[index], diff --git a/scripts/xyz_grid.py b/scripts/xyz_grid.py index 57ee4708..6cc82b7f 100644 --- a/scripts/xyz_grid.py +++ b/scripts/xyz_grid.py @@ -11,7 +11,7 @@ import numpy as np import modules.scripts as scripts import gradio as gr -from modules import images, sd_samplers, processing, sd_models, sd_vae, sd_samplers_kdiffusion, errors +from modules import images, sd_samplers, processing, sd_models, sd_vae, sd_schedulers, errors from modules.processing import process_images, Processed, StableDiffusionProcessingTxt2Img from modules.shared import opts, state import modules.shared as shared @@ -248,7 +248,7 @@ axis_options = [ AxisOption("Sigma min", float, apply_field("s_tmin")), AxisOption("Sigma max", float, apply_field("s_tmax")), AxisOption("Sigma noise", float, apply_field("s_noise")), - AxisOption("Schedule type", str, apply_override("k_sched_type"), choices=lambda: list(sd_samplers_kdiffusion.k_diffusion_scheduler)), + AxisOption("Schedule type", str, apply_field("scheduler"), choices=lambda: [x.label for x in sd_schedulers.schedulers]), AxisOption("Schedule min sigma", float, apply_override("sigma_min")), AxisOption("Schedule max sigma", float, apply_override("sigma_max")), AxisOption("Schedule rho", float, apply_override("rho")), From c4402500c76ebad4a614c8167031b8d20a442b2f Mon Sep 17 00:00:00 2001 From: catboxanon <122327233+catboxanon@users.noreply.github.com> Date: Sun, 24 Mar 2024 02:33:10 -0400 Subject: [PATCH 186/496] Support `ssmd_cover_images` --- extensions-builtin/Lora/network.py | 1 - .../Lora/ui_extra_networks_lora.py | 2 +- modules/ui_extra_networks.py | 42 +++++++++++++++++++ 3 files changed, 43 insertions(+), 2 deletions(-) diff --git a/extensions-builtin/Lora/network.py b/extensions-builtin/Lora/network.py index 41286429..20f8df3d 100644 --- a/extensions-builtin/Lora/network.py +++ b/extensions-builtin/Lora/network.py @@ -29,7 +29,6 @@ class NetworkOnDisk: def read_metadata(): 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 diff --git a/extensions-builtin/Lora/ui_extra_networks_lora.py b/extensions-builtin/Lora/ui_extra_networks_lora.py index 66d15dd0..b627f7dc 100644 --- a/extensions-builtin/Lora/ui_extra_networks_lora.py +++ b/extensions-builtin/Lora/ui_extra_networks_lora.py @@ -31,7 +31,7 @@ class ExtraNetworksPageLora(ui_extra_networks.ExtraNetworksPage): "name": name, "filename": lora_on_disk.filename, "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), "search_terms": search_terms, "local_preview": f"{path}.{shared.opts.samples_format}", diff --git a/modules/ui_extra_networks.py b/modules/ui_extra_networks.py index f4627ce8..087cb8a0 100644 --- a/modules/ui_extra_networks.py +++ b/modules/ui_extra_networks.py @@ -1,6 +1,8 @@ import functools import os.path import urllib.parse +from base64 import b64decode +from io import BytesIO from pathlib import Path from typing import Optional, Union from dataclasses import dataclass @@ -11,6 +13,7 @@ import gradio as gr import json import html from fastapi.exceptions import HTTPException +from PIL import Image from modules.infotext_utils import image_from_url_text @@ -108,6 +111,31 @@ def fetch_file(filename: str = ""): return FileResponse(filename, headers={"Accept-Ranges": "bytes"}) +def fetch_cover_images(page: str = "", item: str = "", index: int = 0): + from starlette.responses import Response + + page = next(iter([x for x in extra_pages if x.name == page]), None) + if page is None: + raise HTTPException(status_code=404, detail="File not found") + + metadata = page.metadata.get(item) + if metadata is None: + raise HTTPException(status_code=404, detail="File not found") + + cover_images = json.loads(metadata.get('ssmd_cover_images', {})) + image = cover_images[index] if index < len(cover_images) else None + if not image: + raise HTTPException(status_code=404, detail="File not found") + + try: + image = Image.open(BytesIO(b64decode(image))) + buffer = BytesIO() + image.save(buffer, format=image.format) + return Response(content=buffer.getvalue(), media_type=image.get_format_mimetype()) + except Exception as err: + raise ValueError(f"File cannot be fetched: {item}. Failed to load cover image.") from err + + def get_metadata(page: str = "", item: str = ""): from starlette.responses import JSONResponse @@ -119,6 +147,8 @@ def get_metadata(page: str = "", item: str = ""): if metadata is None: return JSONResponse({}) + metadata = {i:metadata[i] for i in metadata if i != 'ssmd_cover_images'} # those are cover images, and they are too big to display in UI as text + return JSONResponse({"metadata": json.dumps(metadata, indent=4, ensure_ascii=False)}) @@ -142,6 +172,7 @@ def get_single_card(page: str = "", tabname: str = "", name: str = ""): def add_pages_to_demo(app): app.add_api_route("/sd_extra_networks/thumb", fetch_file, methods=["GET"]) + app.add_api_route("/sd_extra_networks/cover-images", fetch_cover_images, methods=["GET"]) app.add_api_route("/sd_extra_networks/metadata", get_metadata, methods=["GET"]) app.add_api_route("/sd_extra_networks/get-single-card", get_single_card, methods=["GET"]) @@ -627,6 +658,17 @@ class ExtraNetworksPage: return None + def find_embedded_preview(self, path, name, metadata): + """ + Find if embedded preview exists in safetensors metadata and return endpoint for it. + """ + + file = f"{path}.safetensors" + if self.lister.exists(file) and 'ssmd_cover_images' in metadata and len(list(filter(None, json.loads(metadata['ssmd_cover_images'])))) > 0: + return f"./sd_extra_networks/cover-images?page={self.extra_networks_tabname}&item={name}" + + return None + def find_description(self, path): """ Find and read a description file for a given path (without extension). From 9aa9e980a9a2846755b72482e8b83b12cbc3ca0f Mon Sep 17 00:00:00 2001 From: AUTOMATIC1111 <16777216c@gmail.com> Date: Sun, 24 Mar 2024 11:00:16 +0300 Subject: [PATCH 187/496] support scheduler selection in hires fix --- modules/infotext_utils.py | 3 ++ modules/processing.py | 6 +++ modules/processing_scripts/sampler.py | 38 +---------------- modules/sd_samplers.py | 60 ++++++++++++++++++++++++++- modules/sd_samplers_kdiffusion.py | 6 ++- modules/txt2img.py | 3 +- modules/ui.py | 11 +++-- 7 files changed, 83 insertions(+), 44 deletions(-) diff --git a/modules/infotext_utils.py b/modules/infotext_utils.py index 723cb1f8..1c91d076 100644 --- a/modules/infotext_utils.py +++ b/modules/infotext_utils.py @@ -314,6 +314,9 @@ Steps: 20, Sampler: Euler a, CFG scale: 7, Seed: 965400086, Size: 512x512, Model if "Hires sampler" not in res: 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: res["Hires checkpoint"] = "Use same checkpoint" diff --git a/modules/processing.py b/modules/processing.py index ac554141..2baca4f5 100644 --- a/modules/processing.py +++ b/modules/processing.py @@ -1115,6 +1115,7 @@ class StableDiffusionProcessingTxt2Img(StableDiffusionProcessing): hr_resize_y: int = 0 hr_checkpoint_name: str = None hr_sampler_name: str = None + hr_scheduler: str = None hr_prompt: str = '' hr_negative_prompt: str = '' force_task_id: str = None @@ -1203,6 +1204,11 @@ class StableDiffusionProcessingTxt2Img(StableDiffusionProcessing): 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 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") if self.enable_hr and self.latent_scale_mode is None: if not any(x.name == self.hr_upscaler for x in shared.sd_upscalers): diff --git a/modules/processing_scripts/sampler.py b/modules/processing_scripts/sampler.py index 83b95255..5d50a162 100644 --- a/modules/processing_scripts/sampler.py +++ b/modules/processing_scripts/sampler.py @@ -1,44 +1,10 @@ import gradio as gr -import functools from modules import scripts, sd_samplers, sd_schedulers, shared from modules.infotext_utils import PasteField from modules.ui_components import FormRow, FormGroup -def get_sampler_from_infotext(d: dict): - return get_sampler_and_scheduler(d.get("Sampler"), d.get("Schedule type"))[0] - - -def get_scheduler_from_infotext(d: dict): - return get_sampler_and_scheduler(d.get("Sampler"), d.get("Schedule type"))[1] - - -@functools.cache -def get_sampler_and_scheduler(sampler_name, scheduler_name): - default_sampler = sd_samplers.samplers[0] - found_scheduler = sd_schedulers.schedulers_map.get(scheduler_name, sd_schedulers.schedulers[0]) - - name = sampler_name or default_sampler.name - - for scheduler in sd_schedulers.schedulers: - name_options = [scheduler.label, scheduler.name, *(scheduler.aliases or [])] - - for name_option in name_options: - if name.endswith(" " + name_option): - found_scheduler = scheduler - name = name[0:-(len(name_option) + 1)] - break - - sampler = sd_samplers.all_samplers_map.get(name, default_sampler) - - # revert back to Automatic if it's the default scheduler for the selected sampler - if sampler.options.get('scheduler', None) == found_scheduler.name: - found_scheduler = sd_schedulers.schedulers[0] - - return sampler.name, found_scheduler.label - - class ScriptSampler(scripts.ScriptBuiltinUI): section = "sampler" @@ -67,8 +33,8 @@ class ScriptSampler(scripts.ScriptBuiltinUI): self.infotext_fields = [ PasteField(self.steps, "Steps", api="steps"), - PasteField(self.sampler_name, get_sampler_from_infotext, api="sampler_name"), - PasteField(self.scheduler, get_scheduler_from_infotext, api="scheduler"), + 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 diff --git a/modules/sd_samplers.py b/modules/sd_samplers.py index 1f50297e..6b7b84b6 100644 --- a/modules/sd_samplers.py +++ b/modules/sd_samplers.py @@ -1,6 +1,8 @@ from __future__ import annotations -from modules import sd_samplers_kdiffusion, sd_samplers_timesteps, sd_samplers_lcm, shared, sd_samplers_common +import functools + +from modules import sd_samplers_kdiffusion, sd_samplers_timesteps, sd_samplers_lcm, shared, sd_samplers_common, sd_schedulers # imports for functions that previously were here and are used by other modules samples_to_image_grid = sd_samplers_common.samples_to_image_grid @@ -64,4 +66,60 @@ def visible_samplers(): return [x for x in samplers if x.name not in samplers_hidden] +def get_sampler_from_infotext(d: dict): + return get_sampler_and_scheduler(d.get("Sampler"), d.get("Schedule type"))[0] + + +def get_scheduler_from_infotext(d: dict): + return get_sampler_and_scheduler(d.get("Sampler"), d.get("Schedule type"))[1] + + +def get_hr_sampler_and_scheduler(d: dict): + hr_sampler = d.get("Hires sampler", "Use same sampler") + sampler = d.get("Sampler") if hr_sampler == "Use same sampler" else hr_sampler + + hr_scheduler = d.get("Hires schedule type", "Use same scheduler") + scheduler = d.get("Schedule type") if hr_scheduler == "Use same scheduler" else hr_scheduler + + sampler, scheduler = get_sampler_and_scheduler(sampler, scheduler) + + sampler = sampler if sampler != d.get("Sampler") else "Use same sampler" + scheduler = scheduler if scheduler != d.get("Schedule type") else "Use same scheduler" + + return sampler, scheduler + + +def get_hr_sampler_from_infotext(d: dict): + return get_hr_sampler_and_scheduler(d)[0] + + +def get_hr_scheduler_from_infotext(d: dict): + return get_hr_sampler_and_scheduler(d)[1] + + +@functools.cache +def get_sampler_and_scheduler(sampler_name, scheduler_name): + default_sampler = samplers[0] + found_scheduler = sd_schedulers.schedulers_map.get(scheduler_name, sd_schedulers.schedulers[0]) + + name = sampler_name or default_sampler.name + + for scheduler in sd_schedulers.schedulers: + name_options = [scheduler.label, scheduler.name, *(scheduler.aliases or [])] + + for name_option in name_options: + if name.endswith(" " + name_option): + found_scheduler = scheduler + name = name[0:-(len(name_option) + 1)] + break + + sampler = all_samplers_map.get(name, default_sampler) + + # revert back to Automatic if it's the default scheduler for the selected sampler + if sampler.options.get('scheduler', None) == found_scheduler.name: + found_scheduler = sd_schedulers.schedulers[0] + + return sampler.name, found_scheduler.label + + set_samplers() diff --git a/modules/sd_samplers_kdiffusion.py b/modules/sd_samplers_kdiffusion.py index d053e48b..b45f85b0 100644 --- a/modules/sd_samplers_kdiffusion.py +++ b/modules/sd_samplers_kdiffusion.py @@ -79,7 +79,7 @@ class KDiffusionSampler(sd_samplers_common.Sampler): steps += 1 if discard_next_to_last_sigma else 0 - scheduler_name = p.scheduler or 'Automatic' + scheduler_name = (p.hr_scheduler if p.is_hr_pass else p.scheduler) or 'Automatic' if scheduler_name == 'Automatic': scheduler_name = self.config.options.get('scheduler', None) @@ -95,8 +95,10 @@ class KDiffusionSampler(sd_samplers_common.Sampler): else: sigmas_kwargs = {'sigma_min': sigma_min, 'sigma_max': sigma_max} - if scheduler.label != 'Automatic': + if scheduler.label != 'Automatic' and not p.is_hr_pass: p.extra_generation_params["Schedule type"] = scheduler.label + elif scheduler.label != p.extra_generation_params.get("Schedule type"): + p.extra_generation_params["Hires schedule type"] = scheduler.label if opts.sigma_min != 0 and opts.sigma_min != m_sigma_min: sigmas_kwargs['sigma_min'] = opts.sigma_min diff --git a/modules/txt2img.py b/modules/txt2img.py index 53fa0099..6f20253a 100644 --- a/modules/txt2img.py +++ b/modules/txt2img.py @@ -11,7 +11,7 @@ from PIL import Image import gradio as gr -def txt2img_create_processing(id_task: str, request: gr.Request, prompt: str, negative_prompt: str, prompt_styles, n_iter: int, batch_size: int, cfg_scale: float, height: int, width: int, enable_hr: bool, denoising_strength: float, hr_scale: float, hr_upscaler: str, hr_second_pass_steps: int, hr_resize_x: int, hr_resize_y: int, hr_checkpoint_name: str, hr_sampler_name: str, hr_prompt: str, hr_negative_prompt, override_settings_texts, *args, force_enable_hr=False): +def txt2img_create_processing(id_task: str, request: gr.Request, prompt: str, negative_prompt: str, prompt_styles, n_iter: int, batch_size: int, cfg_scale: float, height: int, width: int, enable_hr: bool, denoising_strength: float, hr_scale: float, hr_upscaler: str, hr_second_pass_steps: int, hr_resize_x: int, hr_resize_y: int, hr_checkpoint_name: str, hr_sampler_name: str, hr_scheduler: str, hr_prompt: str, hr_negative_prompt, override_settings_texts, *args, force_enable_hr=False): override_settings = create_override_settings_dict(override_settings_texts) if force_enable_hr: @@ -38,6 +38,7 @@ def txt2img_create_processing(id_task: str, request: gr.Request, prompt: str, ne hr_resize_y=hr_resize_y, hr_checkpoint_name=None if hr_checkpoint_name == 'Use same checkpoint' else hr_checkpoint_name, hr_sampler_name=None if hr_sampler_name == 'Use same sampler' else hr_sampler_name, + hr_scheduler=None if hr_scheduler == 'Use same scheduler' else hr_scheduler, hr_prompt=hr_prompt, hr_negative_prompt=hr_negative_prompt, override_settings=override_settings, diff --git a/modules/ui.py b/modules/ui.py index c964d5e2..9b138e0a 100644 --- a/modules/ui.py +++ b/modules/ui.py @@ -322,10 +322,11 @@ def create_ui(): with FormRow(elem_id="txt2img_hires_fix_row3", variant="compact", visible=opts.hires_fix_show_sampler) as hr_sampler_container: - hr_checkpoint_name = gr.Dropdown(label='Hires checkpoint', elem_id="hr_checkpoint", choices=["Use same checkpoint"] + modules.sd_models.checkpoint_tiles(use_short=True), value="Use same checkpoint") + hr_checkpoint_name = gr.Dropdown(label='Checkpoint', elem_id="hr_checkpoint", choices=["Use same checkpoint"] + modules.sd_models.checkpoint_tiles(use_short=True), value="Use same checkpoint") create_refresh_button(hr_checkpoint_name, modules.sd_models.list_models, lambda: {"choices": ["Use same checkpoint"] + modules.sd_models.checkpoint_tiles(use_short=True)}, "hr_checkpoint_refresh") - hr_sampler_name = gr.Dropdown(label='Hires sampling method', elem_id="hr_sampler", choices=["Use same sampler"] + sd_samplers.visible_sampler_names(), value="Use same sampler") + hr_sampler_name = gr.Dropdown(label='Sampling method', elem_id="hr_sampler", choices=["Use same sampler"] + sd_samplers.visible_sampler_names(), value="Use same sampler") + hr_scheduler = gr.Dropdown(label='Schedule type', elem_id="hr_scheduler", choices=["Use same scheduler"] + [x.label for x in sd_schedulers.schedulers], value="Use same scheduler") with FormRow(elem_id="txt2img_hires_fix_row4", variant="compact", visible=opts.hires_fix_show_prompts) as hr_prompts_container: with gr.Column(scale=80): @@ -394,6 +395,7 @@ def create_ui(): hr_resize_y, hr_checkpoint_name, hr_sampler_name, + hr_scheduler, hr_prompt, hr_negative_prompt, override_settings, @@ -456,8 +458,9 @@ def create_ui(): PasteField(hr_resize_x, "Hires resize-1", api="hr_resize_x"), PasteField(hr_resize_y, "Hires resize-2", api="hr_resize_y"), PasteField(hr_checkpoint_name, "Hires checkpoint", api="hr_checkpoint_name"), - PasteField(hr_sampler_name, "Hires sampler", api="hr_sampler_name"), - PasteField(hr_sampler_container, lambda d: gr.update(visible=True) if d.get("Hires sampler", "Use same sampler") != "Use same sampler" or d.get("Hires checkpoint", "Use same checkpoint") != "Use same checkpoint" else gr.update()), + PasteField(hr_sampler_name, sd_samplers.get_hr_sampler_from_infotext, api="hr_sampler_name"), + PasteField(hr_scheduler, sd_samplers.get_hr_scheduler_from_infotext, api="hr_scheduler"), + PasteField(hr_sampler_container, lambda d: gr.update(visible=True) if d.get("Hires sampler", "Use same sampler") != "Use same sampler" or d.get("Hires checkpoint", "Use same checkpoint") != "Use same checkpoint" or d.get("Hires schedule type", "Use same scheduler") != "Use same scheduler" else gr.update()), PasteField(hr_prompt, "Hires prompt", api="hr_prompt"), PasteField(hr_negative_prompt, "Hires negative prompt", api="hr_negative_prompt"), PasteField(hr_prompts_container, lambda d: gr.update(visible=True) if d.get("Hires prompt", "") != "" or d.get("Hires negative prompt", "") != "" else gr.update()), From dfbdb5a135b2170c0a2330e5cf052a00784dbf74 Mon Sep 17 00:00:00 2001 From: AUTOMATIC1111 <16777216c@gmail.com> Date: Mon, 25 Mar 2024 18:00:58 +0300 Subject: [PATCH 188/496] put request: gr.Request at start of img2img function similar to txt2img --- modules/img2img.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/modules/img2img.py b/modules/img2img.py index 9e316d45..a1d042c2 100644 --- a/modules/img2img.py +++ b/modules/img2img.py @@ -146,7 +146,7 @@ def process_batch(p, input_dir, output_dir, inpaint_mask_dir, args, to_scale=Fal 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, 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, *args): override_settings = create_override_settings_dict(override_settings_texts) is_batch = mode == 5 From f62217b65d9328e96d8c8f26d12df7f90a38a8b6 Mon Sep 17 00:00:00 2001 From: Boning Date: Thu, 21 Mar 2024 15:28:38 -0700 Subject: [PATCH 189/496] minor bug fix of sd model memory management --- modules/sd_models.py | 11 +++++++---- 1 file changed, 7 insertions(+), 4 deletions(-) diff --git a/modules/sd_models.py b/modules/sd_models.py index 747fc39e..d5cccd83 100644 --- a/modules/sd_models.py +++ b/modules/sd_models.py @@ -787,6 +787,13 @@ def reuse_model_from_already_loaded(sd_model, checkpoint_info, timer): Additionaly deletes loaded models that are over the limit set in settings (sd_checkpoints_limit). """ + if sd_model is not None and sd_model.sd_checkpoint_info.filename == checkpoint_info.filename: + return sd_model + + if shared.opts.sd_checkpoints_keep_in_cpu: + send_model_to_cpu(sd_model) + timer.record("send model to cpu") + already_loaded = None for i in reversed(range(len(model_data.loaded_sd_models))): loaded_model = model_data.loaded_sd_models[i] @@ -800,10 +807,6 @@ def reuse_model_from_already_loaded(sd_model, checkpoint_info, timer): send_model_to_trash(loaded_model) timer.record("send model to trash") - if shared.opts.sd_checkpoints_keep_in_cpu: - send_model_to_cpu(sd_model) - timer.record("send model to cpu") - if already_loaded is not None: send_model_to_device(already_loaded) timer.record("send model to device") From f4633cb9c03c2fc91e6862bf9bc2acab4f6ca762 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E7=8E=8B=E6=80=80=E5=AE=97?= Date: Tue, 26 Mar 2024 13:53:16 +0800 Subject: [PATCH 190/496] fix: when find already_loaded model, remove loaded by array index --- modules/sd_models.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/modules/sd_models.py b/modules/sd_models.py index b35aecbc..ba5bbea4 100644 --- a/modules/sd_models.py +++ b/modules/sd_models.py @@ -796,7 +796,7 @@ def reuse_model_from_already_loaded(sd_model, checkpoint_info, timer): if len(model_data.loaded_sd_models) > shared.opts.sd_checkpoints_limit > 0: print(f"Unloading model {len(model_data.loaded_sd_models)} over the limit of {shared.opts.sd_checkpoints_limit}: {loaded_model.sd_checkpoint_info.title}") - model_data.loaded_sd_models.pop() + del model_data.loaded_sd_models[i] send_model_to_trash(loaded_model) timer.record("send model to trash") From c321680b3d6ea13c12d5836a2803f04cd45dba83 Mon Sep 17 00:00:00 2001 From: Andray Date: Tue, 26 Mar 2024 14:53:38 +0400 Subject: [PATCH 191/496] interrupt upscale --- modules/upscaler.py | 3 +++ modules/upscaler_utils.py | 2 ++ 2 files changed, 5 insertions(+) diff --git a/modules/upscaler.py b/modules/upscaler.py index 4ffd428c..59f8fbbf 100644 --- a/modules/upscaler.py +++ b/modules/upscaler.py @@ -60,6 +60,9 @@ class Upscaler: if img.width >= dest_w and img.height >= dest_h: break + if shared.state.interrupted: + break + shape = (img.width, img.height) img = self.do_upscale(img, selected_model) diff --git a/modules/upscaler_utils.py b/modules/upscaler_utils.py index 17223ca0..5ecbbed9 100644 --- a/modules/upscaler_utils.py +++ b/modules/upscaler_utils.py @@ -69,6 +69,8 @@ def upscale_with_model( for y, h, row in grid.tiles: newrow = [] for x, w, tile in row: + if shared.state.interrupted: + return img output = upscale_pil_patch(model, tile) scale_factor = output.width // tile.width newrow.append([x * scale_factor, w * scale_factor, output]) From 16522cb0e3457c3b9dbbe961c982ca4f8e20baf4 Mon Sep 17 00:00:00 2001 From: Ikko Eltociear Ashimine Date: Wed, 27 Mar 2024 03:01:06 +0900 Subject: [PATCH 192/496] fix typo in call_queue.py amout -> amount --- modules/call_queue.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/modules/call_queue.py b/modules/call_queue.py index bcd7c546..b50931bc 100644 --- a/modules/call_queue.py +++ b/modules/call_queue.py @@ -100,8 +100,8 @@ def wrap_gradio_call(func, extra_outputs=None, add_stats=False): sys_pct = sys_peak/max(sys_total, 1) * 100 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_sys = "System: peak amout of video memory allocated by all running programs, out of total capacity" + toltip_r = "Reserved: total amount of video memory allocated by the Torch library " + toltip_sys = "System: peak amount of video memory allocated by all running programs, out of total capacity" text_a = f"A: {active_peak/1024:.2f} GB" text_r = f"R: {reserved_peak/1024:.2f} GB" From 5461b00e89eb4a8e4b66befab4be5f4103e274fd Mon Sep 17 00:00:00 2001 From: ochen1 Date: Tue, 26 Mar 2024 21:22:09 -0600 Subject: [PATCH 193/496] fix: Python version check for PyTorch installation compatibility --- webui.sh | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/webui.sh b/webui.sh index f116376f..5fc1eb2e 100755 --- a/webui.sh +++ b/webui.sh @@ -129,7 +129,7 @@ case "$gpu_info" in export HSA_OVERRIDE_GFX_VERSION=10.3.0 if [[ -z "${TORCH_COMMAND}" ]] then - pyv="$(${python_cmd} -c 'import sys; print(".".join(map(str, sys.version_info[0:2])))')" + pyv="$(${python_cmd} -c 'import sys; print(f"{sys.version_info[0]}.{sys.version_info[1]:02d}")')" if [[ $(bc <<< "$pyv <= 3.10") -eq 1 ]] then # Navi users will still use torch 1.13 because 2.0 does not seem to work. From 4e2bb7250fc959b41f0575c3f2bc23e108b4096f Mon Sep 17 00:00:00 2001 From: Andray Date: Wed, 27 Mar 2024 15:35:06 +0400 Subject: [PATCH 194/496] fix_ui_config_for_hires_sampler_and_scheduler --- modules/ui.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/modules/ui.py b/modules/ui.py index 9b138e0a..403425f2 100644 --- a/modules/ui.py +++ b/modules/ui.py @@ -325,8 +325,8 @@ def create_ui(): hr_checkpoint_name = gr.Dropdown(label='Checkpoint', elem_id="hr_checkpoint", choices=["Use same checkpoint"] + modules.sd_models.checkpoint_tiles(use_short=True), value="Use same checkpoint") create_refresh_button(hr_checkpoint_name, modules.sd_models.list_models, lambda: {"choices": ["Use same checkpoint"] + modules.sd_models.checkpoint_tiles(use_short=True)}, "hr_checkpoint_refresh") - hr_sampler_name = gr.Dropdown(label='Sampling method', elem_id="hr_sampler", choices=["Use same sampler"] + sd_samplers.visible_sampler_names(), value="Use same sampler") - hr_scheduler = gr.Dropdown(label='Schedule type', elem_id="hr_scheduler", choices=["Use same scheduler"] + [x.label for x in sd_schedulers.schedulers], value="Use same scheduler") + hr_sampler_name = gr.Dropdown(label='Hires sampling method', elem_id="hr_sampler", choices=["Use same sampler"] + sd_samplers.visible_sampler_names(), value="Use same sampler") + hr_scheduler = gr.Dropdown(label='Hires schedule type', elem_id="hr_scheduler", choices=["Use same scheduler"] + [x.label for x in sd_schedulers.schedulers], value="Use same scheduler") with FormRow(elem_id="txt2img_hires_fix_row4", variant="compact", visible=opts.hires_fix_show_prompts) as hr_prompts_container: with gr.Column(scale=80): From c4c8a641115b3c6c19ad02409deb80f10f2bf232 Mon Sep 17 00:00:00 2001 From: AUTOMATIC1111 <16777216c@gmail.com> Date: Sat, 30 Mar 2024 07:33:39 +0300 Subject: [PATCH 195/496] restore the line lost in the merge --- webui.sh | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/webui.sh b/webui.sh index b348c387..d28c7c19 100755 --- a/webui.sh +++ b/webui.sh @@ -129,7 +129,7 @@ case "$gpu_info" in export HSA_OVERRIDE_GFX_VERSION=10.3.0 if [[ -z "${TORCH_COMMAND}" ]] then - pyv="$(${python_cmd} -c 'import sys; print(".".join(map(str, sys.version_info[0:2])))')" + pyv="$(${python_cmd} -c 'import sys; print(f"{sys.version_info[0]}.{sys.version_info[1]:02d}")')" # Using an old nightly compiled against rocm 5.2 for Navi1, see https://github.com/pytorch/pytorch/issues/106728#issuecomment-1749511711 if [[ $pyv == "3.8" ]] then From dcd4f880a86e500ec88ddf7eafe65894a24b85a3 Mon Sep 17 00:00:00 2001 From: AUTOMATIC1111 <16777216c@gmail.com> Date: Sun, 31 Mar 2024 08:17:22 +0300 Subject: [PATCH 196/496] rework code/UI for #15293 --- modules/shared_options.py | 1 - scripts/postprocessing_upscale.py | 75 +++++++++++++++---------------- 2 files changed, 35 insertions(+), 41 deletions(-) diff --git a/modules/shared_options.py b/modules/shared_options.py index bb1d232e..590ae6a6 100644 --- a/modules/shared_options.py +++ b/modules/shared_options.py @@ -102,7 +102,6 @@ options_templates.update(options_section(('upscaling', "Upscaling", "postprocess "DAT_tile_overlap": OptionInfo(8, "Tile overlap for DAT upscalers.", gr.Slider, {"minimum": 0, "maximum": 48, "step": 1}).info("Low values = visible seam"), "upscaler_for_img2img": OptionInfo(None, "Upscaler for img2img", gr.Dropdown, lambda: {"choices": [x.name for x in shared.sd_upscalers]}), "set_scale_by_when_changing_upscaler": OptionInfo(False, "Automatically set the Scale by factor based on the name of the selected Upscaler."), - "show_limit_target_resolution_in_extras_upscale": OptionInfo(False, 'Show "Limit target resolution" slider in "Upscale" extras script. Useful for batches where can be big images.').needs_reload_ui(), })) options_templates.update(options_section(('face-restoration', "Face restoration", "postprocessing"), { diff --git a/scripts/postprocessing_upscale.py b/scripts/postprocessing_upscale.py index 3132e499..b9573a51 100644 --- a/scripts/postprocessing_upscale.py +++ b/scripts/postprocessing_upscale.py @@ -12,17 +12,15 @@ from modules.ui import switch_values_symbol upscale_cache = {} -def limitSizeByOneDemention(size: tuple, limit: int): - w, h = size - if h > w: - if h > limit: - w = limit / h * w - h = limit - else: - if w > limit: - h = limit / w * h - w = limit - return (int(w), int(h)) +def limit_size_by_one_dimention(w, h, limit): + if h > w and h > limit: + w = limit * w // h + h = limit + elif w > h and w > limit: + h = limit * h // w + w = limit + + return int(w), int(h) class ScriptPostprocessingUpscale(scripts_postprocessing.ScriptPostprocessing): @@ -43,12 +41,11 @@ class ScriptPostprocessingUpscale(scripts_postprocessing.ScriptPostprocessing): with FormRow(): with gr.Tabs(elem_id="extras_resize_mode"): with gr.TabItem('Scale by', elem_id="extras_scale_by_tab") as tab_scale_by: - upscaling_resize = gr.Slider(minimum=1.0, maximum=8.0, step=0.05, label="Resize", value=4, elem_id="extras_upscaling_resize") - if shared.opts.show_limit_target_resolution_in_extras_upscale: - limit_target_resolution = gr.Slider(minimum=0, maximum=10000, step=8, label="Limit target resolution", value=8000, elem_id="extras_upscale_limit_target_resolution", - tooltip="0 = no limit. Limit maximal target resolution by the biggest demension. Useful for batches where can be big images.") - else: - limit_target_resolution = gr.Number(0, visible=False) + with gr.Row(): + with gr.Column(scale=3): + upscaling_resize = gr.Slider(minimum=1.0, maximum=8.0, step=0.05, label="Resize", value=4, elem_id="extras_upscaling_resize") + with gr.Column(scale=1): + max_side_length = gr.Number(label="Max side length", value=0, elem_id="extras_upscale_max_side_length", tooltip="If any of two sides of the image ends up larger than specified, will downscale it to fit. 0 = no limit.") with gr.TabItem('Scale to', elem_id="extras_scale_to_tab") as tab_scale_to: with FormRow(): @@ -79,7 +76,7 @@ class ScriptPostprocessingUpscale(scripts_postprocessing.ScriptPostprocessing): "upscale_enabled": upscale_enabled, "upscale_mode": selected_tab, "upscale_by": upscaling_resize, - "limit_target_resolution": limit_target_resolution, + "max_side_length": max_side_length, "upscale_to_width": upscaling_resize_w, "upscale_to_height": upscaling_resize_h, "upscale_crop": upscaling_crop, @@ -88,18 +85,18 @@ class ScriptPostprocessingUpscale(scripts_postprocessing.ScriptPostprocessing): "upscaler_2_visibility": extras_upscaler_2_visibility, } - def upscale(self, image, info, upscaler, upscale_mode, upscale_by, limit_target_resolution, upscale_to_width, upscale_to_height, upscale_crop): + def upscale(self, image, info, upscaler, upscale_mode, upscale_by, max_side_length, upscale_to_width, upscale_to_height, upscale_crop): if upscale_mode == 1: upscale_by = max(upscale_to_width/image.width, upscale_to_height/image.height) info["Postprocess upscale to"] = f"{upscale_to_width}x{upscale_to_height}" else: info["Postprocess upscale by"] = upscale_by - if limit_target_resolution != 0 and max(*image.size)*upscale_by > limit_target_resolution: + if max_side_length != 0 and max(*image.size)*upscale_by > max_side_length: upscale_mode = 1 upscale_crop = False - upscale_to_width, upscale_to_height = limitSizeByOneDemention((image.width*upscale_by, image.height*upscale_by), limit_target_resolution) + upscale_to_width, upscale_to_height = limit_size_by_one_dimention(image.width*upscale_by, image.height*upscale_by, max_side_length) upscale_by = max(upscale_to_width/image.width, upscale_to_height/image.height) - info["Limit target resolution"] = limit_target_resolution + info["Max side length"] = max_side_length cache_key = (hash(np.array(image.getdata()).tobytes()), upscaler.name, upscale_mode, upscale_by, upscale_to_width, upscale_to_height, upscale_crop) cached_image = upscale_cache.pop(cache_key, None) @@ -121,21 +118,21 @@ class ScriptPostprocessingUpscale(scripts_postprocessing.ScriptPostprocessing): return image - def process_firstpass(self, pp: scripts_postprocessing.PostprocessedImage, **args): - if args['upscale_mode'] == 1: - pp.shared.target_width = args['upscale_to_width'] - pp.shared.target_height = args['upscale_to_height'] + def process_firstpass(self, pp: scripts_postprocessing.PostprocessedImage, upscale_enabled=True, upscale_mode=1, upscale_by=2.0, max_side_length=0, upscale_to_width=None, upscale_to_height=None, upscale_crop=False, upscaler_1_name=None, upscaler_2_name=None, upscaler_2_visibility=0.0): + if upscale_mode == 1: + pp.shared.target_width = upscale_to_width + pp.shared.target_height = upscale_to_height else: - pp.shared.target_width = int(pp.image.width * args['upscale_by']) - pp.shared.target_height = int(pp.image.height * args['upscale_by']) - if args['limit_target_resolution'] != 0: - pp.shared.target_width, pp.shared.target_height = limitSizeByOneDemention((pp.shared.target_width, pp.shared.target_height), args['limit_target_resolution']) + pp.shared.target_width = int(pp.image.width * upscale_by) + pp.shared.target_height = int(pp.image.height * upscale_by) - def process(self, pp: scripts_postprocessing.PostprocessedImage, **args): - if not args['upscale_enabled']: + pp.shared.target_width, pp.shared.target_height = limit_size_by_one_dimention(pp.shared.target_width, pp.shared.target_height, max_side_length) + + def process(self, pp: scripts_postprocessing.PostprocessedImage, upscale_enabled=True, upscale_mode=1, upscale_by=2.0, max_side_length=0, upscale_to_width=None, upscale_to_height=None, upscale_crop=False, upscaler_1_name=None, upscaler_2_name=None, upscaler_2_visibility=0.0): + if not upscale_enabled: return - upscaler_1_name = args['upscaler_1_name'] + upscaler_1_name = upscaler_1_name if upscaler_1_name == "None": upscaler_1_name = None @@ -145,21 +142,19 @@ class ScriptPostprocessingUpscale(scripts_postprocessing.ScriptPostprocessing): if not upscaler1: return - upscaler_2_name = args['upscaler_2_name'] + upscaler_2_name = upscaler_2_name if upscaler_2_name == "None": upscaler_2_name = None upscaler2 = next(iter([x for x in shared.sd_upscalers if x.name == upscaler_2_name and x.name != "None"]), None) assert upscaler2 or (upscaler_2_name is None), f'could not find upscaler named {upscaler_2_name}' - upscaled_image = self.upscale(pp.image, pp.info, upscaler1, args['upscale_mode'], args['upscale_by'], args['limit_target_resolution'], args['upscale_to_width'], - args['upscale_to_height'], args['upscale_crop']) + upscaled_image = self.upscale(pp.image, pp.info, upscaler1, upscale_mode, upscale_by, max_side_length, upscale_to_width, upscale_to_height, upscale_crop) pp.info["Postprocess upscaler"] = upscaler1.name - if upscaler2 and args['upscaler_2_visibility'] > 0: - second_upscale = self.upscale(pp.image, pp.info, upscaler2, args['upscale_mode'], args['upscale_by'], args['upscale_to_width'], - args['upscale_to_height'], args['upscale_crop']) - upscaled_image = Image.blend(upscaled_image, second_upscale, args['upscaler_2_visibility']) + if upscaler2 and upscaler_2_visibility > 0: + second_upscale = self.upscale(pp.image, pp.info, upscaler2, upscale_mode, upscale_by, upscale_to_width, upscale_to_height, upscale_crop) + upscaled_image = Image.blend(upscaled_image, second_upscale, upscaler_2_visibility) pp.info["Postprocess upscaler 2"] = upscaler2.name From bfa20d2758b654b8522cb4bcd6af08e1e36fd7cb Mon Sep 17 00:00:00 2001 From: AUTOMATIC1111 <16777216c@gmail.com> Date: Sun, 31 Mar 2024 08:20:19 +0300 Subject: [PATCH 197/496] resize Max side length field --- scripts/postprocessing_upscale.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/scripts/postprocessing_upscale.py b/scripts/postprocessing_upscale.py index b9573a51..9b5c8c5e 100644 --- a/scripts/postprocessing_upscale.py +++ b/scripts/postprocessing_upscale.py @@ -42,10 +42,10 @@ class ScriptPostprocessingUpscale(scripts_postprocessing.ScriptPostprocessing): with gr.Tabs(elem_id="extras_resize_mode"): with gr.TabItem('Scale by', elem_id="extras_scale_by_tab") as tab_scale_by: with gr.Row(): - with gr.Column(scale=3): + with gr.Column(scale=4): upscaling_resize = gr.Slider(minimum=1.0, maximum=8.0, step=0.05, label="Resize", value=4, elem_id="extras_upscaling_resize") - with gr.Column(scale=1): - max_side_length = gr.Number(label="Max side length", value=0, elem_id="extras_upscale_max_side_length", tooltip="If any of two sides of the image ends up larger than specified, will downscale it to fit. 0 = no limit.") + with gr.Column(scale=1, min_width=160): + max_side_length = gr.Number(label="Max side length", value=0, elem_id="extras_upscale_max_side_length", tooltip="If any of two sides of the image ends up larger than specified, will downscale it to fit. 0 = no limit.", min_width=160) with gr.TabItem('Scale to', elem_id="extras_scale_to_tab") as tab_scale_to: with FormRow(): From f1a6c5fe17ce316b3617b6e23c0e81c623089ccf Mon Sep 17 00:00:00 2001 From: AUTOMATIC1111 <16777216c@gmail.com> Date: Sun, 31 Mar 2024 08:30:00 +0300 Subject: [PATCH 198/496] add an option to hide postprocessing options in Extras tab --- modules/scripts_postprocessing.py | 6 ++++-- modules/shared_options.py | 1 + 2 files changed, 5 insertions(+), 2 deletions(-) diff --git a/modules/scripts_postprocessing.py b/modules/scripts_postprocessing.py index 901cad08..4b3b7afd 100644 --- a/modules/scripts_postprocessing.py +++ b/modules/scripts_postprocessing.py @@ -143,6 +143,7 @@ class ScriptPostprocessingRunner: self.initialize_scripts(modules.scripts.postprocessing_scripts_data) scripts_order = shared.opts.postprocessing_operation_order + scripts_filter_out = set(shared.opts.postprocessing_disable_in_extras) def script_score(name): for i, possible_match in enumerate(scripts_order): @@ -151,9 +152,10 @@ class ScriptPostprocessingRunner: 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): inputs = [] diff --git a/modules/shared_options.py b/modules/shared_options.py index 590ae6a6..a2b595ff 100644 --- a/modules/shared_options.py +++ b/modules/shared_options.py @@ -383,6 +383,7 @@ options_templates.update(options_section(('sampler-params', "Sampler parameters" options_templates.update(options_section(('postprocessing', "Postprocessing", "postprocessing"), { 'postprocessing_enable_in_main_ui': OptionInfo([], "Enable postprocessing operations in txt2img and img2img tabs", ui_components.DropdownMulti, lambda: {"choices": [x.name for x in shared_items.postprocessing_scripts()]}), + 'postprocessing_disable_in_extras': OptionInfo([], "Disable postprocessing operations in extras tab", ui_components.DropdownMulti, lambda: {"choices": [x.name for x in shared_items.postprocessing_scripts()]}), 'postprocessing_operation_order': OptionInfo([], "Postprocessing operation order", ui_components.DropdownMulti, lambda: {"choices": [x.name for x in shared_items.postprocessing_scripts()]}), 'upscaling_max_images_in_cache': OptionInfo(5, "Maximum number of images in upscaling cache", gr.Slider, {"minimum": 0, "maximum": 10, "step": 1}), 'postprocessing_existing_caption_action': OptionInfo("Ignore", "Action for existing captions", gr.Radio, {"choices": ["Ignore", "Keep", "Prepend", "Append"]}).info("when generating captions using postprocessing; Ignore = use generated; Keep = use original; Prepend/Append = combine both"), From ea83180761d450690cf590d4fd9b582241a9846d Mon Sep 17 00:00:00 2001 From: DrBiggusDickus Date: Sun, 31 Mar 2024 14:41:06 +0200 Subject: [PATCH 199/496] fix CodeFormer weight --- modules/codeformer_model.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/modules/codeformer_model.py b/modules/codeformer_model.py index 44b84618..0b353353 100644 --- a/modules/codeformer_model.py +++ b/modules/codeformer_model.py @@ -50,7 +50,7 @@ class FaceRestorerCodeFormer(face_restoration_utils.CommonFaceRestoration): def restore_face(cropped_face_t): 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) From 4ccbae320e0dddccd78edcb328f5ad160ec474af Mon Sep 17 00:00:00 2001 From: Andray Date: Sun, 31 Mar 2024 17:05:15 +0400 Subject: [PATCH 200/496] fix dcd4f880a86e500ec88ddf7eafe65894a24b85a3 --- scripts/postprocessing_upscale.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/scripts/postprocessing_upscale.py b/scripts/postprocessing_upscale.py index 9b5c8c5e..0d7a19c6 100644 --- a/scripts/postprocessing_upscale.py +++ b/scripts/postprocessing_upscale.py @@ -16,7 +16,7 @@ def limit_size_by_one_dimention(w, h, limit): if h > w and h > limit: w = limit * w // h h = limit - elif w > h and w > limit: + elif w > limit: h = limit * h // w w = limit From 0a7d1e756f335199f3a42f6ce6a7e88093a56c96 Mon Sep 17 00:00:00 2001 From: Andray Date: Sun, 31 Mar 2024 19:34:58 +0400 Subject: [PATCH 201/496] fix upscaler 2 --- scripts/postprocessing_upscale.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/scripts/postprocessing_upscale.py b/scripts/postprocessing_upscale.py index 0d7a19c6..9628c4e9 100644 --- a/scripts/postprocessing_upscale.py +++ b/scripts/postprocessing_upscale.py @@ -153,7 +153,7 @@ class ScriptPostprocessingUpscale(scripts_postprocessing.ScriptPostprocessing): pp.info["Postprocess upscaler"] = upscaler1.name if upscaler2 and upscaler_2_visibility > 0: - second_upscale = self.upscale(pp.image, pp.info, upscaler2, upscale_mode, upscale_by, upscale_to_width, upscale_to_height, upscale_crop) + second_upscale = self.upscale(pp.image, pp.info, upscaler2, upscale_mode, upscale_by, max_side_length, upscale_to_width, upscale_to_height, upscale_crop) upscaled_image = Image.blend(upscaled_image, second_upscale, upscaler_2_visibility) pp.info["Postprocess upscaler 2"] = upscaler2.name From e73a7e40067c3beb9aacc9d27129354446098edb Mon Sep 17 00:00:00 2001 From: storyicon Date: Mon, 1 Apr 2024 09:13:07 +0000 Subject: [PATCH 202/496] feat: ensure the indexability of dynamically imported packages Signed-off-by: storyicon --- modules/script_loading.py | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/modules/script_loading.py b/modules/script_loading.py index 0d55f193..2bd26f01 100644 --- a/modules/script_loading.py +++ b/modules/script_loading.py @@ -2,13 +2,18 @@ import os import importlib.util from modules import errors - +import sys def load_module(path): module_spec = importlib.util.spec_from_file_location(os.path.basename(path), path) module = importlib.util.module_from_spec(module_spec) module_spec.loader.exec_module(module) - + if os.path.isfile(path): + sp = os.path.splitext(path) + module_name = sp[0] + else: + module_name = os.path.basename(path) + sys.modules[module_name] = module return module From 86861f8379e92a778f886ecf49f0d28380df2933 Mon Sep 17 00:00:00 2001 From: Andray Date: Mon, 1 Apr 2024 13:58:45 +0400 Subject: [PATCH 203/496] fix upscaler 2 images do not match --- scripts/postprocessing_upscale.py | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/scripts/postprocessing_upscale.py b/scripts/postprocessing_upscale.py index 9628c4e9..2409fd20 100644 --- a/scripts/postprocessing_upscale.py +++ b/scripts/postprocessing_upscale.py @@ -45,7 +45,7 @@ class ScriptPostprocessingUpscale(scripts_postprocessing.ScriptPostprocessing): with gr.Column(scale=4): upscaling_resize = gr.Slider(minimum=1.0, maximum=8.0, step=0.05, label="Resize", value=4, elem_id="extras_upscaling_resize") with gr.Column(scale=1, min_width=160): - max_side_length = gr.Number(label="Max side length", value=0, elem_id="extras_upscale_max_side_length", tooltip="If any of two sides of the image ends up larger than specified, will downscale it to fit. 0 = no limit.", min_width=160) + max_side_length = gr.Number(label="Max side length", value=0, elem_id="extras_upscale_max_side_length", tooltip="If any of two sides of the image ends up larger than specified, will downscale it to fit. 0 = no limit.", min_width=160, step=8, minimum=0) with gr.TabItem('Scale to', elem_id="extras_scale_to_tab") as tab_scale_to: with FormRow(): @@ -154,6 +154,8 @@ class ScriptPostprocessingUpscale(scripts_postprocessing.ScriptPostprocessing): if upscaler2 and upscaler_2_visibility > 0: second_upscale = self.upscale(pp.image, pp.info, upscaler2, upscale_mode, upscale_by, max_side_length, upscale_to_width, upscale_to_height, upscale_crop) + if upscaled_image.mode != second_upscale.mode: + second_upscale = second_upscale.convert(upscaled_image.mode) upscaled_image = Image.blend(upscaled_image, second_upscale, upscaler_2_visibility) pp.info["Postprocess upscaler 2"] = upscaler2.name From a669b8a6bcf0cf13e70ded68f8c35dbd8133c068 Mon Sep 17 00:00:00 2001 From: v0xie <28695009+v0xie@users.noreply.github.com> Date: Mon, 1 Apr 2024 12:51:09 -0700 Subject: [PATCH 204/496] fix: remove script callbacks in ordered_callbacks_map --- modules/script_callbacks.py | 3 +++ 1 file changed, 3 insertions(+) diff --git a/modules/script_callbacks.py b/modules/script_callbacks.py index d5a97ecf..74f41f09 100644 --- a/modules/script_callbacks.py +++ b/modules/script_callbacks.py @@ -439,6 +439,9 @@ def remove_current_script_callbacks(): for callback_list in callback_map.values(): for callback_to_remove in [cb for cb in callback_list if cb.script == filename]: 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): From b372fb6165c2b0f3990d9a3ff06245d25358c0b8 Mon Sep 17 00:00:00 2001 From: AUTOMATIC1111 <16777216c@gmail.com> Date: Mon, 1 Apr 2024 23:33:45 +0300 Subject: [PATCH 205/496] fix API upscale --- modules/postprocessing.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/modules/postprocessing.py b/modules/postprocessing.py index 29933232..ab3274df 100644 --- a/modules/postprocessing.py +++ b/modules/postprocessing.py @@ -131,14 +131,14 @@ def run_postprocessing_webui(id_task, *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, limit_target_resolution = 0): +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""" args = scripts.scripts_postproc.create_args_for_run({ "Upscale": { "upscale_mode": resize_mode, "upscale_by": upscaling_resize, - "limit_target_resolution": limit_target_resolution, + "max_side_length": max_side_length, "upscale_to_width": upscaling_resize_w, "upscale_to_height": upscaling_resize_h, "upscale_crop": upscaling_crop, From 92e6aa36537120a4b27944181b4b1cb1c1b80a1d Mon Sep 17 00:00:00 2001 From: w-e-w <40751091+w-e-w@users.noreply.github.com> Date: Fri, 5 Apr 2024 15:51:42 +0900 Subject: [PATCH 206/496] open_folder as util --- modules/ui_common.py | 31 ++----------------------------- modules/util.py | 33 +++++++++++++++++++++++++++++++++ 2 files changed, 35 insertions(+), 29 deletions(-) diff --git a/modules/ui_common.py b/modules/ui_common.py index cf1b8b32..dcd2d371 100644 --- a/modules/ui_common.py +++ b/modules/ui_common.py @@ -3,13 +3,10 @@ import dataclasses import json import html import os -import platform -import sys import gradio as gr -import subprocess as sp -from modules import call_queue, shared, ui_tempdir +from modules import call_queue, shared, ui_tempdir, util from modules.infotext_utils import image_from_url_text import modules.images from modules.ui_components import ToolButton @@ -176,31 +173,7 @@ def create_output_panel(tabname, outdir, toprow=None): except Exception: pass - if not os.path.exists(f): - msg = f'Folder "{f}" does not exist. After you create an image, the folder will be created.' - print(msg) - gr.Info(msg) - return - elif not os.path.isdir(f): - msg = f""" -WARNING -An open_folder request was made with an argument that is not a folder. -This could be an error or a malicious attempt to run code on your computer. -Requested path was: {f} -""" - print(msg, file=sys.stderr) - gr.Warning(msg) - return - - path = os.path.normpath(f) - if platform.system() == "Windows": - os.startfile(path) - elif platform.system() == "Darwin": - sp.Popen(["open", path]) - elif "microsoft-standard-WSL2" in platform.uname().release: - sp.Popen(["wsl-open", path]) - else: - sp.Popen(["xdg-open", path]) + util.open_folder(f) with gr.Column(elem_id=f"{tabname}_results"): if toprow: diff --git a/modules/util.py b/modules/util.py index 8d1aea44..19685a8e 100644 --- a/modules/util.py +++ b/modules/util.py @@ -136,3 +136,36 @@ class MassFileLister: def reset(self): """Clear the cache of all directories.""" self.cached_dirs.clear() + +def open_folder(path): + # import at function level to avoid potential issues + import gradio as gr + import platform + import sys + import subprocess + + if not os.path.exists(path): + msg = f'Folder "{path}" does not exist. after you save an image, the folder will be created.' + print(msg) + gr.Info(msg) + return + elif not os.path.isdir(path): + msg = f""" +WARNING +An open_folder request was made with an path that is not a folder. +This could be an error or a malicious attempt to run code on your computer. +Requested path was: {path} +""" + print(msg, file=sys.stderr) + gr.Warning(msg) + return + + path = os.path.normpath(path) + if platform.system() == "Windows": + os.startfile(path) + elif platform.system() == "Darwin": + subprocess.Popen(["open", path]) + elif "microsoft-standard-WSL2" in platform.uname().release: + subprocess.Popen(["wsl-open", path]) + else: + subprocess.Popen(["xdg-open", path]) From 20123d427b09901396133643be78f6b692393b0c Mon Sep 17 00:00:00 2001 From: w-e-w <40751091+w-e-w@users.noreply.github.com> Date: Fri, 5 Apr 2024 16:19:20 +0900 Subject: [PATCH 207/496] open_folder docstring --- modules/util.py | 1 + 1 file changed, 1 insertion(+) diff --git a/modules/util.py b/modules/util.py index b0da1990..b6e6010c 100644 --- a/modules/util.py +++ b/modules/util.py @@ -174,6 +174,7 @@ def topological_sort(dependencies): def open_folder(path): + """Open a folder in the file manager of the respect OS.""" # import at function level to avoid potential issues import gradio as gr import platform From 989b89b12a8c4255f91254bb32da8cfa72f72122 Mon Sep 17 00:00:00 2001 From: Marsel Markhabulin Date: Fri, 5 Apr 2024 12:42:08 +0300 Subject: [PATCH 208/496] 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. --- modules/sd_models.py | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/modules/sd_models.py b/modules/sd_models.py index 747fc39e..61f2b2ac 100644 --- a/modules/sd_models.py +++ b/modules/sd_models.py @@ -8,6 +8,7 @@ import re import safetensors.torch from omegaconf import OmegaConf, ListConfig from os import mkdir +from os import getenv from urllib import request import ldm.modules.midas as midas @@ -151,7 +152,8 @@ def list_models(): if shared.cmd_opts.no_download_sd_model or cmd_ckpt != shared.sd_model_file or os.path.exists(cmd_ckpt): model_url = None else: - model_url = "https://huggingface.co/runwayml/stable-diffusion-v1-5/resolve/main/v1-5-pruned-emaonly.safetensors" + hugging_host = getenv('HF_ENDPOINT', 'https://huggingface.co') + model_url = f"{hugging_host}/runwayml/stable-diffusion-v1-5/resolve/main/v1-5-pruned-emaonly.safetensors" model_list = modelloader.load_models(model_path=model_path, model_url=model_url, command_path=shared.cmd_opts.ckpt_dir, ext_filter=[".ckpt", ".safetensors"], download_name="v1-5-pruned-emaonly.safetensors", ext_blacklist=[".vae.ckpt", ".vae.safetensors"]) From acb20338b1c7e93927c8456e04cb2842d5798aff Mon Sep 17 00:00:00 2001 From: AUTOMATIC1111 <16777216c@gmail.com> Date: Sat, 6 Apr 2024 08:53:21 +0300 Subject: [PATCH 209/496] put HF_ENDPOINT into shared for #15443 --- modules/sd_models.py | 9 +++------ modules/shared.py | 2 ++ 2 files changed, 5 insertions(+), 6 deletions(-) diff --git a/modules/sd_models.py b/modules/sd_models.py index 1e0003ec..ff245b7a 100644 --- a/modules/sd_models.py +++ b/modules/sd_models.py @@ -1,5 +1,5 @@ import collections -import os.path +import os import sys import threading @@ -7,8 +7,6 @@ import torch import re import safetensors.torch from omegaconf import OmegaConf, ListConfig -from os import mkdir -from os import getenv from urllib import request import ldm.modules.midas as midas @@ -152,8 +150,7 @@ def list_models(): if shared.cmd_opts.no_download_sd_model or cmd_ckpt != shared.sd_model_file or os.path.exists(cmd_ckpt): model_url = None else: - hugging_host = getenv('HF_ENDPOINT', 'https://huggingface.co') - model_url = f"{hugging_host}/runwayml/stable-diffusion-v1-5/resolve/main/v1-5-pruned-emaonly.safetensors" + model_url = f"{shared.hf_endpoint}/runwayml/stable-diffusion-v1-5/resolve/main/v1-5-pruned-emaonly.safetensors" model_list = modelloader.load_models(model_path=model_path, model_url=model_url, command_path=shared.cmd_opts.ckpt_dir, ext_filter=[".ckpt", ".safetensors"], download_name="v1-5-pruned-emaonly.safetensors", ext_blacklist=[".vae.ckpt", ".vae.safetensors"]) @@ -510,7 +507,7 @@ def enable_midas_autodownload(): path = midas.api.ISL_PATHS[model_type] if not os.path.exists(path): if not os.path.exists(midas_path): - mkdir(midas_path) + os.mkdir(midas_path) print(f"Downloading midas model weights for {model_type} to {path}") request.urlretrieve(midas_urls[model_type], path) diff --git a/modules/shared.py b/modules/shared.py index 4cf7f6a8..a41cd457 100644 --- a/modules/shared.py +++ b/modules/shared.py @@ -90,3 +90,5 @@ list_checkpoint_tiles = shared_items.list_checkpoint_tiles refresh_checkpoints = shared_items.refresh_checkpoints list_samplers = shared_items.list_samplers reload_hypernetworks = shared_items.reload_hypernetworks + +hf_endpoint = os.getenv('HF_ENDPOINT', 'https://huggingface.co') From 23c06a51ccc54d76db465f8f66a5dcb0f7ca33e5 Mon Sep 17 00:00:00 2001 From: AUTOMATIC1111 <16777216c@gmail.com> Date: Sat, 6 Apr 2024 09:05:04 +0300 Subject: [PATCH 210/496] use 'scripts.' prefix for names of dynamically loaded modules --- modules/script_loading.py | 16 ++++++++++------ 1 file changed, 10 insertions(+), 6 deletions(-) diff --git a/modules/script_loading.py b/modules/script_loading.py index 2bd26f01..17f658b1 100644 --- a/modules/script_loading.py +++ b/modules/script_loading.py @@ -4,16 +4,20 @@ import importlib.util from modules import errors import sys + +loaded_scripts = {} + + def load_module(path): module_spec = importlib.util.spec_from_file_location(os.path.basename(path), path) module = importlib.util.module_from_spec(module_spec) module_spec.loader.exec_module(module) - if os.path.isfile(path): - sp = os.path.splitext(path) - module_name = sp[0] - else: - module_name = os.path.basename(path) - sys.modules[module_name] = module + + loaded_scripts[path] = module + + module_name, _ = os.path.splitext(os.path.basename(path)) + sys.modules["scripts." + module_name] = module + return module From 2ad17a6100be887616e91a84bd5ecb39d0771155 Mon Sep 17 00:00:00 2001 From: w-e-w <40751091+w-e-w@users.noreply.github.com> Date: Sat, 6 Apr 2024 15:56:57 +0900 Subject: [PATCH 211/496] 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 --- modules/util.py | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/modules/util.py b/modules/util.py index b6e6010c..0db13736 100644 --- a/modules/util.py +++ b/modules/util.py @@ -148,6 +148,11 @@ class MassFileLister: """Clear the cache of all directories.""" self.cached_dirs.clear() + def update_file_entry(self, path): + """Update the cache for a specific directory.""" + dirname, filename = os.path.split(path) + if cached_dir := self.cached_dirs.get(dirname): + cached_dir.update_entry(filename) def topological_sort(dependencies): """Accepts a dictionary mapping name to its dependencies, returns a list of names ordered according to dependencies. From e1640314df34b4cc8ed0422ab04a2886a3d9beb3 Mon Sep 17 00:00:00 2001 From: AUTOMATIC1111 <16777216c@gmail.com> Date: Sat, 6 Apr 2024 21:46:56 +0300 Subject: [PATCH 212/496] 1.9.0 changelog --- CHANGELOG.md | 120 ++++++++++++++++++++++++++++++++++++++++++++++++++- 1 file changed, 118 insertions(+), 2 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 0df47801..362b4861 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,4 +1,120 @@ -## 1.8.0-RC +## 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.` ([#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)) + +### 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)) + + +## 1.8.0 ### Features: * Update torch to version 2.1.2 @@ -61,7 +177,7 @@ * add before_token_counter callback and use it for prompt comments * ResizeHandleRow - allow overridden column scale parameter ([#15004](https://github.com/AUTOMATIC1111/stable-diffusion-webui/pull/15004)) -### Performance +### Performance: * Massive performance improvement for extra networks directories with a huge number of files in them in an attempt to tackle #14507 ([#14528](https://github.com/AUTOMATIC1111/stable-diffusion-webui/pull/14528)) * Reduce unnecessary re-indexing extra networks directory ([#14512](https://github.com/AUTOMATIC1111/stable-diffusion-webui/pull/14512)) * Avoid unnecessary `isfile`/`exists` calls ([#14527](https://github.com/AUTOMATIC1111/stable-diffusion-webui/pull/14527)) From 6efdfe3234d0510a6fd522c31dc62366d363ae73 Mon Sep 17 00:00:00 2001 From: w-e-w <40751091+w-e-w@users.noreply.github.com> Date: Sun, 7 Apr 2024 22:58:12 +0900 Subject: [PATCH 213/496] if use use_main_prompt index = 0 --- modules/processing.py | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/modules/processing.py b/modules/processing.py index 2baca4f5..c1e689c3 100644 --- a/modules/processing.py +++ b/modules/processing.py @@ -704,7 +704,9 @@ 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, all_hr_prompts=None, all_hr_negative_prompts=None): - if index is None: + if use_main_prompt: + index = 0 + elif index is None: index = position_in_batch + iteration * p.batch_size if all_negative_prompts is None: From 47ed9b2d398287f14a6dcab6fa4fe8b78bccf1c8 Mon Sep 17 00:00:00 2001 From: w-e-w <40751091+w-e-w@users.noreply.github.com> Date: Mon, 8 Apr 2024 01:39:31 +0900 Subject: [PATCH 214/496] allow list or callables in generation_params --- modules/processing.py | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/modules/processing.py b/modules/processing.py index c1e689c3..50570c49 100644 --- a/modules/processing.py +++ b/modules/processing.py @@ -756,6 +756,16 @@ def create_infotext(p, all_prompts, all_seeds, all_subseeds, comments=None, iter "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 + if all_hr_prompts := all_hr_prompts or getattr(p, 'all_hr_prompts', None): generation_params['Hires prompt'] = all_hr_prompts[index] if all_hr_prompts[index] != all_prompts[index] else None if all_hr_negative_prompts := all_hr_negative_prompts or getattr(p, 'all_hr_negative_prompts', None): From 219e64489c9c2a712dc31fe254d1a32ce3caf7c2 Mon Sep 17 00:00:00 2001 From: w-e-w <40751091+w-e-w@users.noreply.github.com> Date: Mon, 8 Apr 2024 01:41:52 +0900 Subject: [PATCH 215/496] re-work extra_generation_params for Hires prompt --- modules/processing.py | 26 ++++++++++++++++---------- 1 file changed, 16 insertions(+), 10 deletions(-) diff --git a/modules/processing.py b/modules/processing.py index 50570c49..97739fcb 100644 --- a/modules/processing.py +++ b/modules/processing.py @@ -703,7 +703,7 @@ def program_version(): return res -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, all_hr_prompts=None, all_hr_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 use_main_prompt: index = 0 elif index is None: @@ -717,6 +717,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_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 if uses_ensd: uses_ensd = sd_samplers_common.is_sampler_using_eta_noise_seed_delta(p) @@ -749,8 +752,6 @@ def create_infotext(p, all_prompts, all_seeds, all_subseeds, comments=None, iter "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, - "Hires prompt": None, # This is set later, insert here to keep order - "Hires negative prompt": None, # This is set later, insert here to keep order **p.extra_generation_params, "Version": program_version() if opts.add_version_to_infotext else None, "User": p.user if opts.add_user_name_to_info else None, @@ -766,15 +767,9 @@ def create_infotext(p, all_prompts, all_seeds, all_subseeds, comments=None, iter errors.report(f'Error creating infotext for key "{key}"', exc_info=True) generation_params[key] = None - if all_hr_prompts := all_hr_prompts or getattr(p, 'all_hr_prompts', None): - generation_params['Hires prompt'] = all_hr_prompts[index] if all_hr_prompts[index] != all_prompts[index] else None - if all_hr_negative_prompts := all_hr_negative_prompts or getattr(p, 'all_hr_negative_prompts', None): - generation_params['Hires negative prompt'] = all_hr_negative_prompts[index] if all_hr_negative_prompts[index] != all_negative_prompts[index] else 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: {p.main_negative_prompt if use_main_prompt else all_negative_prompts[index]}" if all_negative_prompts[index] else "" + negative_prompt_text = f"\nNegative prompt: {negative_prompt}" if negative_prompt else "" return f"{prompt_text}{negative_prompt_text}\n{generation_params_text}".strip() @@ -1216,6 +1211,17 @@ class StableDiffusionProcessingTxt2Img(StableDiffusionProcessing): 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 + def get_hr_prompt(p, index, prompt_text, **kwargs): + hr_prompt = p.all_hr_prompts[index] + return hr_prompt if hr_prompt != prompt_text else None + + def get_hr_negative_prompt(p, index, negative_prompt, **kwargs): + 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: From 1e1176b6eb4b0054ad44b29787cb8e9217da5daf Mon Sep 17 00:00:00 2001 From: w-e-w <40751091+w-e-w@users.noreply.github.com> Date: Mon, 8 Apr 2024 18:18:33 +0900 Subject: [PATCH 216/496] non-serializable as None --- modules/processing.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/modules/processing.py b/modules/processing.py index 97739fcb..bae00d74 100644 --- a/modules/processing.py +++ b/modules/processing.py @@ -608,7 +608,7 @@ class Processed: "version": self.version, } - return json.dumps(obj) + return json.dumps(obj, default=lambda o: None) 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) From e3aabe6959c2088f6b6e917b4f84185ae95a3af6 Mon Sep 17 00:00:00 2001 From: w-e-w <40751091+w-e-w@users.noreply.github.com> Date: Mon, 8 Apr 2024 19:48:38 +0900 Subject: [PATCH 217/496] add documentation for create_infotext --- modules/processing.py | 44 +++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 44 insertions(+) diff --git a/modules/processing.py b/modules/processing.py index bae00d74..d8ba5ca4 100644 --- a/modules/processing.py +++ b/modules/processing.py @@ -704,6 +704,50 @@ 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): + """ + 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: From d9708c92b444894bce8070e4dcfaa093f8eb8d43 Mon Sep 17 00:00:00 2001 From: AUTOMATIC1111 <16777216c@gmail.com> Date: Mon, 8 Apr 2024 16:15:25 +0300 Subject: [PATCH 218/496] fix limited file write (thanks, Sylwia) --- modules/ui_extensions.py | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/modules/ui_extensions.py b/modules/ui_extensions.py index 913e1444..d822c0b8 100644 --- a/modules/ui_extensions.py +++ b/modules/ui_extensions.py @@ -58,8 +58,9 @@ def apply_and_restart(disable_list, update_list, disable_all): def save_config_state(name): current_config_state = config_states.get_config() - if not name: - name = "Config" + + name = os.path.basename(name or "Config") + current_config_state["name"] = name timestamp = datetime.now().strftime('%Y_%m_%d-%H_%M_%S') filename = os.path.join(config_states_dir, f"{timestamp}_{name}.json") From 3786f3742fdada13de9adcaabd4c6150fdffc15c Mon Sep 17 00:00:00 2001 From: AUTOMATIC1111 <16777216c@gmail.com> Date: Mon, 8 Apr 2024 16:15:25 +0300 Subject: [PATCH 219/496] fix limited file write (thanks, Sylwia) --- modules/ui_extensions.py | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/modules/ui_extensions.py b/modules/ui_extensions.py index 913e1444..d822c0b8 100644 --- a/modules/ui_extensions.py +++ b/modules/ui_extensions.py @@ -58,8 +58,9 @@ def apply_and_restart(disable_list, update_list, disable_all): def save_config_state(name): current_config_state = config_states.get_config() - if not name: - name = "Config" + + name = os.path.basename(name or "Config") + current_config_state["name"] = name timestamp = datetime.now().strftime('%Y_%m_%d-%H_%M_%S') filename = os.path.join(config_states_dir, f"{timestamp}_{name}.json") From 2580235c72b0cde49519678756c26417248348f5 Mon Sep 17 00:00:00 2001 From: Jorden Tse Date: Tue, 9 Apr 2024 11:13:47 +0800 Subject: [PATCH 220/496] Fix extra-single-image API not doing upscale failed --- modules/postprocessing.py | 1 + 1 file changed, 1 insertion(+) diff --git a/modules/postprocessing.py b/modules/postprocessing.py index ab3274df..812cbcca 100644 --- a/modules/postprocessing.py +++ b/modules/postprocessing.py @@ -136,6 +136,7 @@ def run_extras(extras_mode, resize_mode, image, image_folder, input_dir, output_ args = scripts.scripts_postproc.create_args_for_run({ "Upscale": { + "upscale_enabled": True, "upscale_mode": resize_mode, "upscale_by": upscaling_resize, "max_side_length": max_side_length, From 696d6813e06179b05aa835ca1959d570b28bacb0 Mon Sep 17 00:00:00 2001 From: AUTOMATIC1111 <16777216c@gmail.com> Date: Tue, 9 Apr 2024 11:00:30 +0300 Subject: [PATCH 221/496] Merge pull request #15465 from jordenyt/fix-extras-api-upscale-enabled Fix extra-single-image API not doing upscale failed --- modules/postprocessing.py | 1 + 1 file changed, 1 insertion(+) diff --git a/modules/postprocessing.py b/modules/postprocessing.py index ab3274df..812cbcca 100644 --- a/modules/postprocessing.py +++ b/modules/postprocessing.py @@ -136,6 +136,7 @@ def run_extras(extras_mode, resize_mode, image, image_folder, input_dir, output_ args = scripts.scripts_postproc.create_args_for_run({ "Upscale": { + "upscale_enabled": True, "upscale_mode": resize_mode, "upscale_by": upscaling_resize, "max_side_length": max_side_length, From 7f691612caf65eec7dfccb12f813eac46293563e Mon Sep 17 00:00:00 2001 From: AUTOMATIC1111 <16777216c@gmail.com> Date: Tue, 9 Apr 2024 12:05:02 +0300 Subject: [PATCH 222/496] Merge pull request #15460 from AUTOMATIC1111/create_infotext-index-and-callable create_infotext allow index and callable, re-work Hires prompt infotext --- modules/processing.py | 84 +++++++++++++++++++++++++++++++++++++------ 1 file changed, 73 insertions(+), 11 deletions(-) diff --git a/modules/processing.py b/modules/processing.py index 2baca4f5..d8ba5ca4 100644 --- a/modules/processing.py +++ b/modules/processing.py @@ -608,7 +608,7 @@ class Processed: "version": self.version, } - return json.dumps(obj) + return json.dumps(obj, default=lambda o: None) 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) @@ -703,8 +703,54 @@ def program_version(): return res -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, all_hr_prompts=None, all_hr_negative_prompts=None): - if index is 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): + """ + 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 if all_negative_prompts is None: @@ -715,6 +761,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_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 if uses_ensd: uses_ensd = sd_samplers_common.is_sampler_using_eta_noise_seed_delta(p) @@ -747,22 +796,24 @@ def create_infotext(p, all_prompts, all_seeds, all_subseeds, comments=None, iter "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, - "Hires prompt": None, # This is set later, insert here to keep order - "Hires negative prompt": None, # This is set later, insert here to keep order **p.extra_generation_params, "Version": program_version() if opts.add_version_to_infotext else None, "User": p.user if opts.add_user_name_to_info else None, } - if all_hr_prompts := all_hr_prompts or getattr(p, 'all_hr_prompts', None): - generation_params['Hires prompt'] = all_hr_prompts[index] if all_hr_prompts[index] != all_prompts[index] else None - if all_hr_negative_prompts := all_hr_negative_prompts or getattr(p, 'all_hr_negative_prompts', None): - generation_params['Hires negative prompt'] = all_hr_negative_prompts[index] if all_hr_negative_prompts[index] != all_negative_prompts[index] 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]) - prompt_text = p.main_prompt if use_main_prompt else all_prompts[index] - 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 "" + negative_prompt_text = f"\nNegative prompt: {negative_prompt}" if negative_prompt else "" return f"{prompt_text}{negative_prompt_text}\n{generation_params_text}".strip() @@ -1204,6 +1255,17 @@ class StableDiffusionProcessingTxt2Img(StableDiffusionProcessing): 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 + def get_hr_prompt(p, index, prompt_text, **kwargs): + hr_prompt = p.all_hr_prompts[index] + return hr_prompt if hr_prompt != prompt_text else None + + def get_hr_negative_prompt(p, index, negative_prompt, **kwargs): + 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: From 600f339c4cf1829c4a52bf83a71544ba7fe7bb5b Mon Sep 17 00:00:00 2001 From: w-e-w <40751091+w-e-w@users.noreply.github.com> Date: Tue, 9 Apr 2024 20:59:04 +0900 Subject: [PATCH 223/496] Warning when Script is not found --- modules/scripts.py | 17 +++++++++++------ 1 file changed, 11 insertions(+), 6 deletions(-) diff --git a/modules/scripts.py b/modules/scripts.py index 264503ca..70ccfbe4 100644 --- a/modules/scripts.py +++ b/modules/scripts.py @@ -739,12 +739,17 @@ class ScriptRunner: def onload_script_visibility(params): title = params.get('Script', None) if title: - title_index = self.titles.index(title) - visibility = title_index == self.script_load_ctr - self.script_load_ctr = (self.script_load_ctr + 1) % len(self.titles) - return gr.update(visible=visibility) - else: - return gr.update(visible=False) + try: + title_index = self.titles.index(title) + visibility = title_index == self.script_load_ctr + self.script_load_ctr = (self.script_load_ctr + 1) % len(self.titles) + return gr.update(visible=visibility) + 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.extend([(script.group, onload_script_visibility) for script in self.selectable_scripts]) From ef83f6831fd747fea4d0fa638fe5ffb71aaa5b0f Mon Sep 17 00:00:00 2001 From: w-e-w <40751091+w-e-w@users.noreply.github.com> Date: Tue, 9 Apr 2024 21:28:44 +0900 Subject: [PATCH 224/496] catch exception for all paste_fields callable --- modules/infotext_utils.py | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/modules/infotext_utils.py b/modules/infotext_utils.py index 1c91d076..f1e8f54b 100644 --- a/modules/infotext_utils.py +++ b/modules/infotext_utils.py @@ -8,7 +8,7 @@ import sys import gradio as gr from modules.paths import data_path -from modules import shared, ui_tempdir, script_callbacks, processing, infotext_versions, images, prompt_parser +from modules import shared, ui_tempdir, script_callbacks, processing, infotext_versions, images, prompt_parser, errors from PIL import Image sys.modules['modules.generation_parameters_copypaste'] = sys.modules[__name__] # alias for old name @@ -488,7 +488,11 @@ def connect_paste(button, paste_fields, input_comp, override_settings_component, for output, key in paste_fields: if callable(key): - v = key(params) + try: + v = key(params) + except Exception: + errors.report(f"Error executing {key}", exc_info=True) + v = None else: v = params.get(key, None) From 88f70ce63cb9bfee1e0ff9ab7c409a03ac631396 Mon Sep 17 00:00:00 2001 From: AUTOMATIC1111 <16777216c@gmail.com> Date: Tue, 9 Apr 2024 16:00:56 +0300 Subject: [PATCH 225/496] Merge pull request #15470 from AUTOMATIC1111/read-infotext-Script-not-found error handling paste_field callables --- modules/infotext_utils.py | 8 ++++++-- modules/scripts.py | 17 +++++++++++------ 2 files changed, 17 insertions(+), 8 deletions(-) diff --git a/modules/infotext_utils.py b/modules/infotext_utils.py index 1c91d076..f1e8f54b 100644 --- a/modules/infotext_utils.py +++ b/modules/infotext_utils.py @@ -8,7 +8,7 @@ import sys import gradio as gr from modules.paths import data_path -from modules import shared, ui_tempdir, script_callbacks, processing, infotext_versions, images, prompt_parser +from modules import shared, ui_tempdir, script_callbacks, processing, infotext_versions, images, prompt_parser, errors from PIL import Image sys.modules['modules.generation_parameters_copypaste'] = sys.modules[__name__] # alias for old name @@ -488,7 +488,11 @@ def connect_paste(button, paste_fields, input_comp, override_settings_component, for output, key in paste_fields: if callable(key): - v = key(params) + try: + v = key(params) + except Exception: + errors.report(f"Error executing {key}", exc_info=True) + v = None else: v = params.get(key, None) diff --git a/modules/scripts.py b/modules/scripts.py index 264503ca..70ccfbe4 100644 --- a/modules/scripts.py +++ b/modules/scripts.py @@ -739,12 +739,17 @@ class ScriptRunner: def onload_script_visibility(params): title = params.get('Script', None) if title: - title_index = self.titles.index(title) - visibility = title_index == self.script_load_ctr - self.script_load_ctr = (self.script_load_ctr + 1) % len(self.titles) - return gr.update(visible=visibility) - else: - return gr.update(visible=False) + try: + title_index = self.titles.index(title) + visibility = title_index == self.script_load_ctr + self.script_load_ctr = (self.script_load_ctr + 1) % len(self.titles) + return gr.update(visible=visibility) + 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.extend([(script.group, onload_script_visibility) for script in self.selectable_scripts]) From 4068429ac72cd83e03abe779148bbe1d6a141a09 Mon Sep 17 00:00:00 2001 From: storyicon Date: Wed, 10 Apr 2024 10:53:25 +0000 Subject: [PATCH 226/496] fix: Coordinate 'right' is less than 'left' Signed-off-by: storyicon --- modules/masking.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/modules/masking.py b/modules/masking.py index 29a39452..9f5b0cd0 100644 --- a/modules/masking.py +++ b/modules/masking.py @@ -9,8 +9,8 @@ def get_crop_region(mask, pad=0): if box: x1, y1, x2, y2 = box else: # when no box is found - x1, y1 = mask_img.size - x2 = y2 = 0 + x1 = y1 = 0 + x2, y2 = mask_img.size return max(x1 - pad, 0), max(y1 - pad, 0), min(x2 + pad, mask_img.size[0]), min(y2 + pad, mask_img.size[1]) From 592e40ebe937cea6325412fe3650f4b693e0ab95 Mon Sep 17 00:00:00 2001 From: w-e-w <40751091+w-e-w@users.noreply.github.com> Date: Thu, 11 Apr 2024 22:51:29 +0900 Subject: [PATCH 227/496] update restricted_opts --- modules/shared_options.py | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/modules/shared_options.py b/modules/shared_options.py index a2b595ff..326a317e 100644 --- a/modules/shared_options.py +++ b/modules/shared_options.py @@ -19,7 +19,9 @@ restricted_opts = { "outdir_grids", "outdir_txt2img_grids", "outdir_save", - "outdir_init_images" + "outdir_init_images", + "temp_dir", + "clean_temp_dir_at_start", } categories.register_category("saving", "Saving images") From a196319edf55f5454bd0ad4f466bb19db757e0e0 Mon Sep 17 00:00:00 2001 From: AUTOMATIC1111 <16777216c@gmail.com> Date: Thu, 11 Apr 2024 19:33:55 +0300 Subject: [PATCH 228/496] Merge pull request #15492 from w-e-w/update-restricted_opts update restricted_opts --- modules/shared_options.py | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/modules/shared_options.py b/modules/shared_options.py index a2b595ff..326a317e 100644 --- a/modules/shared_options.py +++ b/modules/shared_options.py @@ -19,7 +19,9 @@ restricted_opts = { "outdir_grids", "outdir_txt2img_grids", "outdir_save", - "outdir_init_images" + "outdir_init_images", + "temp_dir", + "clean_temp_dir_at_start", } categories.register_category("saving", "Saving images") From d282d248000a40611f1b40f55bb4b3a4af5fb17b Mon Sep 17 00:00:00 2001 From: AUTOMATIC1111 <16777216c@gmail.com> Date: Sat, 13 Apr 2024 06:37:03 +0300 Subject: [PATCH 229/496] update changelog --- CHANGELOG.md | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 362b4861..5bb816cc 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -81,6 +81,10 @@ * 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)) @@ -112,6 +116,8 @@ * 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 From 8e1c3561beb33fa58e39c6fb2594ece02aca188a Mon Sep 17 00:00:00 2001 From: thatfuckingbird <67429906+thatfuckingbird@users.noreply.github.com> Date: Mon, 15 Apr 2024 21:17:24 +0200 Subject: [PATCH 230/496] fix typo in function call (eror -> error) --- javascript/extraNetworks.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/javascript/extraNetworks.js b/javascript/extraNetworks.js index 358ecd36..c5cced97 100644 --- a/javascript/extraNetworks.js +++ b/javascript/extraNetworks.js @@ -568,7 +568,7 @@ function extraNetworksShowMetadata(text) { return; } } catch (error) { - console.eror(error); + console.error(error); } var elem = document.createElement('pre'); From 0f82948e4f46ca27acbf3ffb817cabec402c6438 Mon Sep 17 00:00:00 2001 From: huchenlei Date: Mon, 15 Apr 2024 22:14:19 -0400 Subject: [PATCH 231/496] Fix cls.__module__ --- .../hypertile/scripts/hypertile_xyz.py | 2 +- modules/script_loading.py | 12 +++++------- 2 files changed, 6 insertions(+), 8 deletions(-) diff --git a/extensions-builtin/hypertile/scripts/hypertile_xyz.py b/extensions-builtin/hypertile/scripts/hypertile_xyz.py index 9e96ae3c..386c6b2d 100644 --- a/extensions-builtin/hypertile/scripts/hypertile_xyz.py +++ b/extensions-builtin/hypertile/scripts/hypertile_xyz.py @@ -1,7 +1,7 @@ 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 +xyz_grid = [x for x in scripts.scripts_data if x.script_class.__module__ == "scripts.xyz_grid"][0].module def int_applier(value_name:str, min_range:int = -1, max_range:int = -1): """ diff --git a/modules/script_loading.py b/modules/script_loading.py index 17f658b1..c505c0b8 100644 --- a/modules/script_loading.py +++ b/modules/script_loading.py @@ -9,15 +9,13 @@ loaded_scripts = {} def load_module(path): - module_spec = importlib.util.spec_from_file_location(os.path.basename(path), path) + module_name, _ = os.path.splitext(os.path.basename(path)) + full_module_name = "scripts." + module_name + module_spec = importlib.util.spec_from_file_location(full_module_name, path) module = importlib.util.module_from_spec(module_spec) module_spec.loader.exec_module(module) - - loaded_scripts[path] = module - - module_name, _ = os.path.splitext(os.path.basename(path)) - sys.modules["scripts." + module_name] = module - + loaded_scripts[full_module_name] = module + sys.modules[full_module_name] = module return module From a95326bec434da5d0a2aeb943d35cfded75e3afa Mon Sep 17 00:00:00 2001 From: huchenlei Date: Mon, 15 Apr 2024 22:34:01 -0400 Subject: [PATCH 232/496] nit --- modules/script_loading.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/modules/script_loading.py b/modules/script_loading.py index c505c0b8..20c7998a 100644 --- a/modules/script_loading.py +++ b/modules/script_loading.py @@ -14,7 +14,7 @@ def load_module(path): module_spec = importlib.util.spec_from_file_location(full_module_name, path) module = importlib.util.module_from_spec(module_spec) module_spec.loader.exec_module(module) - loaded_scripts[full_module_name] = module + loaded_scripts[path] = module sys.modules[full_module_name] = module return module From bba306d414f1e81c979ae53271c022df08ef7388 Mon Sep 17 00:00:00 2001 From: Travis Geiselbrecht Date: Mon, 15 Apr 2024 21:10:11 -0700 Subject: [PATCH 233/496] 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. --- modules/script_callbacks.py | 3 +++ 1 file changed, 3 insertions(+) diff --git a/modules/script_callbacks.py b/modules/script_callbacks.py index 74f41f09..9059d4d9 100644 --- a/modules/script_callbacks.py +++ b/modules/script_callbacks.py @@ -448,6 +448,9 @@ def remove_callbacks_for_function(callback_func): for callback_list in callback_map.values(): for callback_to_remove in [cb for cb in callback_list if cb.callback == callback_func]: 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, *, name=None): From 0980fdfe8c85bb4ff915bab100a383674a6a0171 Mon Sep 17 00:00:00 2001 From: storyicon Date: Tue, 16 Apr 2024 07:35:33 +0000 Subject: [PATCH 234/496] fix: images do not match Signed-off-by: storyicon --- modules/processing.py | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/modules/processing.py b/modules/processing.py index 411c7c3f..c14b6896 100644 --- a/modules/processing.py +++ b/modules/processing.py @@ -1537,6 +1537,9 @@ class StableDiffusionProcessingImg2Img(StableDiffusionProcessing): if self.mask_blur_x > 0 or self.mask_blur_y > 0: self.extra_generation_params["Mask blur"] = self.mask_blur + if image_mask.size != (self.width, self.height): + image_mask = images.resize_image(self.resize_mode, image_mask, self.width, self.height) + if self.inpaint_full_res: self.mask_for_overlay = image_mask mask = image_mask.convert('L') @@ -1551,7 +1554,6 @@ class StableDiffusionProcessingImg2Img(StableDiffusionProcessing): self.extra_generation_params["Inpaint area"] = "Only masked" self.extra_generation_params["Masked area padding"] = self.inpaint_full_res_padding else: - image_mask = images.resize_image(self.resize_mode, image_mask, self.width, self.height) np_mask = np.array(image_mask) np_mask = np.clip((np_mask.astype(np.float32)) * 2, 0, 255).astype(np.uint8) self.mask_for_overlay = Image.fromarray(np_mask) From 50190ca669fde082cd45a5030127813617a7f777 Mon Sep 17 00:00:00 2001 From: "Alessandro de Oliveira Faria (A.K.A. CABELO)" Date: Wed, 17 Apr 2024 00:01:56 -0300 Subject: [PATCH 235/496] Compatibility with Debian 11, Fedora 34+ and openSUSE 15.4+ --- webui.sh | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/webui.sh b/webui.sh index d28c7c19..fee5e118 100755 --- a/webui.sh +++ b/webui.sh @@ -243,7 +243,7 @@ prepare_tcmalloc() { for lib in "${TCMALLOC_LIBS[@]}" do # Determine which type of tcmalloc library the library supports - TCMALLOC="$(PATH=/usr/sbin:$PATH ldconfig -p | grep -P $lib | head -n 1)" + TCMALLOC="$(PATH=/sbin:/usr/sbin:$PATH ldconfig -p | grep -P $lib | head -n 1)" TC_INFO=(${TCMALLOC//=>/}) if [[ ! -z "${TC_INFO}" ]]; then echo "Check TCMalloc: ${TC_INFO}" From 63fd38a04f91ed97bf0f51c9b63f134a2ed81d59 Mon Sep 17 00:00:00 2001 From: w-e-w <40751091+w-e-w@users.noreply.github.com> Date: Wed, 17 Apr 2024 15:44:49 +0900 Subject: [PATCH 236/496] numpy DeprecationWarning product -> prod --- modules/textual_inversion/image_embedding.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/modules/textual_inversion/image_embedding.py b/modules/textual_inversion/image_embedding.py index ea4b8833..0898d8b7 100644 --- a/modules/textual_inversion/image_embedding.py +++ b/modules/textual_inversion/image_embedding.py @@ -43,7 +43,7 @@ def lcg(m=2**32, a=1664525, c=1013904223, seed=0): def xor_block(block): g = lcg() - randblock = np.array([next(g) for _ in range(np.product(block.shape))]).astype(np.uint8).reshape(block.shape) + randblock = np.array([next(g) for _ in range(np.prod(block.shape))]).astype(np.uint8).reshape(block.shape) return np.bitwise_xor(block.astype(np.uint8), randblock & 0x0F) From 9d4fdc45d3c4b9431551fd53de64a67726dcbd64 Mon Sep 17 00:00:00 2001 From: Andray Date: Thu, 18 Apr 2024 01:53:23 +0400 Subject: [PATCH 237/496] fix x1 upscalers --- modules/upscaler.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/modules/upscaler.py b/modules/upscaler.py index 59f8fbbf..28c60cdc 100644 --- a/modules/upscaler.py +++ b/modules/upscaler.py @@ -57,7 +57,7 @@ class Upscaler: dest_h = int((img.height * scale) // 8 * 8) for _ in range(3): - if img.width >= dest_w and img.height >= dest_h: + if img.width >= dest_w and img.height >= dest_h and scale != 1: break if shared.state.interrupted: From 909c3dfe83ac8c55edebb8c23e265dbfe5532081 Mon Sep 17 00:00:00 2001 From: missionfloyd Date: Wed, 17 Apr 2024 21:20:03 -0600 Subject: [PATCH 238/496] Remove API upscaling factor limits --- modules/api/models.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/modules/api/models.py b/modules/api/models.py index 16edf11c..ff377713 100644 --- a/modules/api/models.py +++ b/modules/api/models.py @@ -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.") 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.") - 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_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?") From ba2a737cceced2355f10e2f645e6794762858d12 Mon Sep 17 00:00:00 2001 From: Speculative Moonstone <167392122+speculativemoonstone@users.noreply.github.com> Date: Thu, 18 Apr 2024 04:25:32 +0000 Subject: [PATCH 239/496] Allow webui.sh to be runnable from directories containing a .git file --- webui.sh | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/webui.sh b/webui.sh index d28c7c19..c22a6822 100755 --- a/webui.sh +++ b/webui.sh @@ -113,13 +113,13 @@ then exit 1 fi -if [[ -d .git ]] +if [[ -d "$SCRIPT_DIR/.git" ]] then printf "\n%s\n" "${delimiter}" printf "Repo already cloned, using it as install directory" printf "\n%s\n" "${delimiter}" - install_dir="${PWD}/../" - clone_dir="${PWD##*/}" + install_dir="${SCRIPT_DIR}/../" + clone_dir="${SCRIPT_DIR##*/}" fi # Check prerequisites From 71314e47b1e505744f5ec8336fea0f7e45e0b4fb Mon Sep 17 00:00:00 2001 From: storyicon Date: Thu, 18 Apr 2024 11:59:25 +0000 Subject: [PATCH 240/496] feat:compatible with inconsistent/empty mask Signed-off-by: storyicon --- modules/masking.py | 4 ++-- modules/processing.py | 23 +++++++++++++---------- 2 files changed, 15 insertions(+), 12 deletions(-) diff --git a/modules/masking.py b/modules/masking.py index 9f5b0cd0..29a39452 100644 --- a/modules/masking.py +++ b/modules/masking.py @@ -9,8 +9,8 @@ def get_crop_region(mask, pad=0): if box: x1, y1, x2, y2 = box else: # when no box is found - x1 = y1 = 0 - x2, y2 = mask_img.size + 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]) diff --git a/modules/processing.py b/modules/processing.py index c14b6896..1ee4c047 100644 --- a/modules/processing.py +++ b/modules/processing.py @@ -1537,23 +1537,24 @@ class StableDiffusionProcessingImg2Img(StableDiffusionProcessing): if self.mask_blur_x > 0 or self.mask_blur_y > 0: self.extra_generation_params["Mask blur"] = self.mask_blur - if image_mask.size != (self.width, self.height): - image_mask = images.resize_image(self.resize_mode, image_mask, self.width, self.height) - if self.inpaint_full_res: self.mask_for_overlay = image_mask mask = image_mask.convert('L') crop_region = masking.get_crop_region(mask, self.inpaint_full_res_padding) - 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) - image_mask = images.resize_image(2, mask, self.width, self.height) - self.paste_to = (x1, y1, x2-x1, y2-y1) - + if crop_region[0] >= crop_region[2] and crop_region[1] >= crop_region[3]: + crop_region = None + image_mask = None + self.mask_for_overlay = None + else: + 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) + image_mask = images.resize_image(2, mask, self.width, self.height) + self.paste_to = (x1, y1, x2-x1, y2-y1) self.extra_generation_params["Inpaint area"] = "Only masked" self.extra_generation_params["Masked area padding"] = self.inpaint_full_res_padding else: + image_mask = images.resize_image(self.resize_mode, image_mask, self.width, self.height) np_mask = np.array(image_mask) np_mask = np.clip((np_mask.astype(np.float32)) * 2, 0, 255).astype(np.uint8) self.mask_for_overlay = Image.fromarray(np_mask) @@ -1579,6 +1580,8 @@ class StableDiffusionProcessingImg2Img(StableDiffusionProcessing): image = images.resize_image(self.resize_mode, image, self.width, self.height) 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.paste(image.convert("RGBA").convert("RGBa"), mask=ImageOps.invert(self.mask_for_overlay.convert('L'))) From d212fb59fe897327142c9b5049460689148be7a7 Mon Sep 17 00:00:00 2001 From: missionfloyd Date: Thu, 18 Apr 2024 20:56:51 -0600 Subject: [PATCH 241/496] Hide 'No Image data blocks found.' message --- modules/textual_inversion/image_embedding.py | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/modules/textual_inversion/image_embedding.py b/modules/textual_inversion/image_embedding.py index ea4b8833..d6a6a260 100644 --- a/modules/textual_inversion/image_embedding.py +++ b/modules/textual_inversion/image_embedding.py @@ -1,12 +1,15 @@ import base64 import json import warnings +import logging import numpy as np import zlib from PIL import Image, ImageDraw import torch +logger = logging.getLogger(__name__) + class EmbeddingEncoder(json.JSONEncoder): def default(self, obj): @@ -114,7 +117,7 @@ def extract_image_data_embed(image): outarr = crop_black(np.array(image.convert('RGB').getdata()).reshape(image.size[1], image.size[0], d).astype(np.uint8)) & 0x0F black_cols = np.where(np.sum(outarr, axis=(0, 2)) == 0) if black_cols[0].shape[0] < 2: - print('No Image data blocks found.') + logger.debug('No Image data blocks found.') return None data_block_lower = outarr[:, :black_cols[0].min(), :].astype(np.uint8) From 5cb567c1384a019e5ba534b023b0f5d2dfff931f Mon Sep 17 00:00:00 2001 From: missionfloyd Date: Fri, 19 Apr 2024 20:29:22 -0600 Subject: [PATCH 242/496] Add schedulers API endpoint --- modules/api/api.py | 14 +++++++++++++- modules/api/models.py | 7 +++++++ 2 files changed, 20 insertions(+), 1 deletion(-) diff --git a/modules/api/api.py b/modules/api/api.py index 29fa0011..f468c385 100644 --- a/modules/api/api.py +++ b/modules/api/api.py @@ -17,7 +17,7 @@ from fastapi.encoders import jsonable_encoder from secrets import compare_digest 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.shared import opts from modules.processing import StableDiffusionProcessingTxt2Img, StableDiffusionProcessingImg2Img, process_images @@ -221,6 +221,7 @@ class Api: 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/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/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]) @@ -683,6 +684,17 @@ class Api: def get_samplers(self): 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): return [ { diff --git a/modules/api/models.py b/modules/api/models.py index 16edf11c..758da631 100644 --- a/modules/api/models.py +++ b/modules/api/models.py @@ -235,6 +235,13 @@ class SamplerItem(BaseModel): aliases: list[str] = Field(title="Aliases") 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): name: str = Field(title="Name") model_name: Optional[str] = Field(title="Model Name") From b5b1487f6a7ccc9c80251f100a92b004f727bee7 Mon Sep 17 00:00:00 2001 From: w-e-w <40751091+w-e-w@users.noreply.github.com> Date: Sun, 21 Apr 2024 02:26:50 +0900 Subject: [PATCH 243/496] FilenameGenerator Sampler Scheduler --- modules/images.py | 30 +++++++++++++++++++++++++++++- 1 file changed, 29 insertions(+), 1 deletion(-) diff --git a/modules/images.py b/modules/images.py index c50b2455..eda02836 100644 --- a/modules/images.py +++ b/modules/images.py @@ -1,7 +1,7 @@ from __future__ import annotations import datetime - +import functools import pytz import io import math @@ -347,6 +347,32 @@ def sanitize_filename_part(text, replace_spaces=True): 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: replacements = { 'seed': lambda self: self.seed if self.seed is not None else '', @@ -358,6 +384,8 @@ class FilenameGenerator: '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), '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_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'), From 49fee7c8dbb7f17e3903c2a238c53ddd53bc503f Mon Sep 17 00:00:00 2001 From: kaanyalova <76952012+kaanyalova@users.noreply.github.com> Date: Sat, 20 Apr 2024 23:18:54 +0300 Subject: [PATCH 244/496] Add avif support --- modules/images.py | 13 ++++++++++++- requirements.txt | 1 + requirements_versions.txt | 1 + 3 files changed, 14 insertions(+), 1 deletion(-) diff --git a/modules/images.py b/modules/images.py index c50b2455..09d3523e 100644 --- a/modules/images.py +++ b/modules/images.py @@ -13,6 +13,8 @@ import numpy as np import piexif import piexif.helper 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 json import hashlib @@ -569,6 +571,16 @@ def save_image_with_geninfo(image, geninfo, filename, extension=None, existing_p }) 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") + }, + }) + + + image.save(filename,format=image_format, exif=exif_bytes) elif extension.lower() == ".gif": image.save(filename, format=image_format, comment=geninfo) else: @@ -747,7 +759,6 @@ def read_info_from_image(image: Image.Image) -> tuple[str | None, dict]: exif_comment = exif_comment.decode('utf8', errors="ignore") if exif_comment: - items['exif comment'] = exif_comment geninfo = exif_comment elif "comment" in items: # for gif geninfo = items["comment"].decode('utf8', errors="ignore") diff --git a/requirements.txt b/requirements.txt index 8699c02b..9e2ecfe4 100644 --- a/requirements.txt +++ b/requirements.txt @@ -30,3 +30,4 @@ torch torchdiffeq torchsde transformers==4.30.2 +pillow-avif-plugin==1.4.3 \ No newline at end of file diff --git a/requirements_versions.txt b/requirements_versions.txt index 87aae913..3df74f3d 100644 --- a/requirements_versions.txt +++ b/requirements_versions.txt @@ -29,3 +29,4 @@ torchdiffeq==0.2.3 torchsde==0.2.6 transformers==4.30.2 httpx==0.24.1 +pillow-avif-plugin==1.4.3 From 6f4f6bff6b4af4e65264618a7aebe8a6435d1350 Mon Sep 17 00:00:00 2001 From: AUTOMATIC1111 <16777216c@gmail.com> Date: Sun, 21 Apr 2024 07:18:58 +0300 Subject: [PATCH 245/496] add more info to the error message for #15567 --- modules/textual_inversion/image_embedding.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/modules/textual_inversion/image_embedding.py b/modules/textual_inversion/image_embedding.py index d6a6a260..644e1152 100644 --- a/modules/textual_inversion/image_embedding.py +++ b/modules/textual_inversion/image_embedding.py @@ -1,5 +1,6 @@ import base64 import json +import os.path import warnings import logging @@ -117,7 +118,7 @@ def extract_image_data_embed(image): outarr = crop_black(np.array(image.convert('RGB').getdata()).reshape(image.size[1], image.size[0], d).astype(np.uint8)) & 0x0F black_cols = np.where(np.sum(outarr, axis=(0, 2)) == 0) if black_cols[0].shape[0] < 2: - logger.debug('No Image data blocks found.') + logger.debug(f'{os.path.basename(getattr(image, "filename", "unknown image file"))}: no embedded information found.') return None data_block_lower = outarr[:, :black_cols[0].min(), :].astype(np.uint8) From 9bcfb92a00df2ff217359be68ea2b21b3260f341 Mon Sep 17 00:00:00 2001 From: AUTOMATIC1111 <16777216c@gmail.com> Date: Sun, 21 Apr 2024 07:41:28 +0300 Subject: [PATCH 246/496] rename logging from textual inversion to not confuse it with global logging module --- modules/hypernetworks/hypernetwork.py | 4 ++-- modules/textual_inversion/{logging.py => saving_settings.py} | 0 modules/textual_inversion/textual_inversion.py | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) rename modules/textual_inversion/{logging.py => saving_settings.py} (100%) diff --git a/modules/hypernetworks/hypernetwork.py b/modules/hypernetworks/hypernetwork.py index 6082d9cb..17454665 100644 --- a/modules/hypernetworks/hypernetwork.py +++ b/modules/hypernetworks/hypernetwork.py @@ -11,7 +11,7 @@ import tqdm from einops import rearrange, repeat from ldm.util import default 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 torch import einsum from torch.nn.init import normal_, xavier_normal_, xavier_uniform_, kaiming_normal_, kaiming_uniform_, zeros_ @@ -533,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), **{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 diff --git a/modules/textual_inversion/logging.py b/modules/textual_inversion/saving_settings.py similarity index 100% rename from modules/textual_inversion/logging.py rename to modules/textual_inversion/saving_settings.py diff --git a/modules/textual_inversion/textual_inversion.py b/modules/textual_inversion/textual_inversion.py index c206ef5f..253f219c 100644 --- a/modules/textual_inversion/textual_inversion.py +++ b/modules/textual_inversion/textual_inversion.py @@ -17,7 +17,7 @@ import modules.textual_inversion.dataset from modules.textual_inversion.learn_schedule import LearnRateScheduler from modules.textual_inversion.image_embedding import embedding_to_b64, embedding_from_b64, insert_image_data_embed, extract_image_data_embed, caption_image_overlay -from modules.textual_inversion.logging import save_settings_to_file +from modules.textual_inversion.saving_settings import save_settings_to_file TextualInversionTemplate = namedtuple("TextualInversionTemplate", ["name", "path"]) From db263df5d5c1ba6d56b277a155186b80d24ac5bd Mon Sep 17 00:00:00 2001 From: w-e-w <40751091+w-e-w@users.noreply.github.com> Date: Sun, 21 Apr 2024 19:34:11 +0900 Subject: [PATCH 247/496] get_crop_region_v2 --- modules/masking.py | 42 ++++++++++++++++++++++++++++++++---------- modules/processing.py | 20 ++++++++++++-------- 2 files changed, 44 insertions(+), 18 deletions(-) diff --git a/modules/masking.py b/modules/masking.py index 29a39452..8e869d1b 100644 --- a/modules/masking.py +++ b/modules/masking.py @@ -1,17 +1,39 @@ from PIL import Image, ImageFilter, ImageOps -def get_crop_region(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)""" - mask_img = mask if isinstance(mask, Image.Image) else Image.fromarray(mask) - box = mask_img.getbbox() - if box: +def get_crop_region_v2(mask, pad=0): + """ + Finds a rectangular region that contains all masked ares in a mask. + Returns None if mask is completely black mask (all 0) + + 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 - else: # when no box is found - 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]) + 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 + + +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): diff --git a/modules/processing.py b/modules/processing.py index b5a04634..f77123b9 100644 --- a/modules/processing.py +++ b/modules/processing.py @@ -1611,19 +1611,23 @@ class StableDiffusionProcessingImg2Img(StableDiffusionProcessing): if self.inpaint_full_res: self.mask_for_overlay = image_mask mask = image_mask.convert('L') - crop_region = masking.get_crop_region(mask, self.inpaint_full_res_padding) - if crop_region[0] >= crop_region[2] and crop_region[1] >= crop_region[3]: - crop_region = None - image_mask = None - self.mask_for_overlay = None - else: + crop_region = masking.get_crop_region_v2(mask, self.inpaint_full_res_padding) + if 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) image_mask = images.resize_image(2, mask, self.width, self.height) + self.inpaint_full_res = False self.paste_to = (x1, y1, x2-x1, y2-y1) - self.extra_generation_params["Inpaint area"] = "Only masked" - self.extra_generation_params["Masked area padding"] = self.inpaint_full_res_padding + self.extra_generation_params["Inpaint area"] = "Only masked" + self.extra_generation_params["Masked area padding"] = self.inpaint_full_res_padding + else: + crop_region = None + image_mask = None + self.mask_for_overlay = None + massage = 'Unable to perform "Inpaint Only mask" because mask is blank, switch to img2img mode.' + model_hijack.comments.append(massage) + logging.info(massage) else: image_mask = images.resize_image(self.resize_mode, image_mask, self.width, self.height) np_mask = np.array(image_mask) From 6c7c176dc9b2808fa897a8f52f445b48312b61bd Mon Sep 17 00:00:00 2001 From: w-e-w <40751091+w-e-w@users.noreply.github.com> Date: Mon, 22 Apr 2024 00:10:49 +0900 Subject: [PATCH 248/496] fix mistake in #15583 --- modules/processing.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/modules/processing.py b/modules/processing.py index f77123b9..76557dd7 100644 --- a/modules/processing.py +++ b/modules/processing.py @@ -1617,7 +1617,6 @@ class StableDiffusionProcessingImg2Img(StableDiffusionProcessing): x1, y1, x2, y2 = crop_region mask = mask.crop(crop_region) image_mask = images.resize_image(2, mask, self.width, self.height) - self.inpaint_full_res = False self.paste_to = (x1, y1, x2-x1, y2-y1) self.extra_generation_params["Inpaint area"] = "Only masked" self.extra_generation_params["Masked area padding"] = self.inpaint_full_res_padding @@ -1625,6 +1624,7 @@ class StableDiffusionProcessingImg2Img(StableDiffusionProcessing): 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) From a183ea4ba773da5144e9f6b99fa48bff13078d2a Mon Sep 17 00:00:00 2001 From: AUTOMATIC1111 <16777216c@gmail.com> Date: Mon, 22 Apr 2024 11:49:55 +0300 Subject: [PATCH 249/496] undo adding scripts to sys.modules --- modules/script_loading.py | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/modules/script_loading.py b/modules/script_loading.py index 20c7998a..7cbba71d 100644 --- a/modules/script_loading.py +++ b/modules/script_loading.py @@ -2,7 +2,6 @@ import os import importlib.util from modules import errors -import sys loaded_scripts = {} @@ -14,8 +13,8 @@ def load_module(path): module_spec = importlib.util.spec_from_file_location(full_module_name, path) module = importlib.util.module_from_spec(module_spec) module_spec.loader.exec_module(module) + loaded_scripts[path] = module - sys.modules[full_module_name] = module return module From e84703b2539c21d68881852f7e9738d76e7de26f Mon Sep 17 00:00:00 2001 From: AUTOMATIC1111 <16777216c@gmail.com> Date: Mon, 22 Apr 2024 11:59:54 +0300 Subject: [PATCH 250/496] update changelog --- CHANGELOG.md | 27 ++++++++++++++++++++++++++- 1 file changed, 26 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 5bb816cc..f0137f38 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,29 @@ +## 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: @@ -85,7 +111,6 @@ * 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)) From 61f6479ea9a85fd12b830be2dfe71a51db34b121 Mon Sep 17 00:00:00 2001 From: AUTOMATIC1111 <16777216c@gmail.com> Date: Mon, 22 Apr 2024 12:19:30 +0300 Subject: [PATCH 251/496] restore 1.8.0-style naming of scripts --- modules/script_loading.py | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/modules/script_loading.py b/modules/script_loading.py index 7cbba71d..cccb3096 100644 --- a/modules/script_loading.py +++ b/modules/script_loading.py @@ -8,9 +8,7 @@ loaded_scripts = {} def load_module(path): - module_name, _ = os.path.splitext(os.path.basename(path)) - full_module_name = "scripts." + module_name - module_spec = importlib.util.spec_from_file_location(full_module_name, path) + module_spec = importlib.util.spec_from_file_location(os.path.basename(path), path) module = importlib.util.module_from_spec(module_spec) module_spec.loader.exec_module(module) From e9809de6512160501e34c54e9c8c5e5e493e306f Mon Sep 17 00:00:00 2001 From: w-e-w <40751091+w-e-w@users.noreply.github.com> Date: Mon, 22 Apr 2024 18:21:48 +0900 Subject: [PATCH 252/496] restore 1.8.0-style naming of scripts --- extensions-builtin/hypertile/scripts/hypertile_xyz.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/extensions-builtin/hypertile/scripts/hypertile_xyz.py b/extensions-builtin/hypertile/scripts/hypertile_xyz.py index 386c6b2d..9e96ae3c 100644 --- a/extensions-builtin/hypertile/scripts/hypertile_xyz.py +++ b/extensions-builtin/hypertile/scripts/hypertile_xyz.py @@ -1,7 +1,7 @@ from modules import scripts from modules.shared import opts -xyz_grid = [x for x in scripts.scripts_data if x.script_class.__module__ == "scripts.xyz_grid"][0].module +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): """ From e837124f4b1b9dcf3caccd8d2fa5730cf3df099a Mon Sep 17 00:00:00 2001 From: AUTOMATIC1111 <16777216c@gmail.com> Date: Mon, 22 Apr 2024 12:26:05 +0300 Subject: [PATCH 253/496] changelog --- CHANGELOG.md | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index f0137f38..d7bcef0b 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,8 @@ +## 1.9.2 + +### Extensions and API: +* restore 1.8.0-style naming of scripts + ## 1.9.1 ### Minor: From 821adc3041b504b47b6e55bb8e8b451a90ee1833 Mon Sep 17 00:00:00 2001 From: w-e-w <40751091+w-e-w@users.noreply.github.com> Date: Mon, 22 Apr 2024 23:03:27 +0900 Subject: [PATCH 254/496] fix get_crop_region_v2 Co-Authored-By: Dowon --- modules/masking.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/modules/masking.py b/modules/masking.py index 8e869d1b..2fc83031 100644 --- a/modules/masking.py +++ b/modules/masking.py @@ -16,7 +16,7 @@ def get_crop_region_v2(mask, pad=0): mask = mask if isinstance(mask, Image.Image) else Image.fromarray(mask) if box := mask.getbbox(): x1, y1, x2, y2 = box - 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 + 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 def get_crop_region(mask, pad=0): From 7dfe959f4b9fbc1662def8a086e48932dff6c1b1 Mon Sep 17 00:00:00 2001 From: AUTOMATIC1111 <16777216c@gmail.com> Date: Mon, 22 Apr 2024 18:00:23 +0300 Subject: [PATCH 255/496] update changelog --- CHANGELOG.md | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index d7bcef0b..295d26c8 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,8 @@ +## 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: From 2b717bb195a3034853ed45a52c5752f010e1302b Mon Sep 17 00:00:00 2001 From: w-e-w <40751091+w-e-w@users.noreply.github.com> Date: Tue, 23 Apr 2024 02:35:25 +0900 Subject: [PATCH 256/496] 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 --- modules/sd_models.py | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/modules/sd_models.py b/modules/sd_models.py index ff245b7a..1747ca62 100644 --- a/modules/sd_models.py +++ b/modules/sd_models.py @@ -659,10 +659,11 @@ def get_empty_cond(sd_model): def send_model_to_cpu(m): - if m.lowvram: - lowvram.send_everything_to_cpu() - else: - m.to(devices.cpu) + if m is not None: + if m.lowvram: + lowvram.send_everything_to_cpu() + else: + m.to(devices.cpu) devices.torch_gc() From 4bc39d234d6535e3d8f8531d0c0f4e049261c922 Mon Sep 17 00:00:00 2001 From: w-e-w <40751091+w-e-w@users.noreply.github.com> Date: Tue, 23 Apr 2024 02:39:45 +0900 Subject: [PATCH 257/496] Show LoRA if model is None --- .../Lora/ui_extra_networks_lora.py | 23 ++++++++++--------- 1 file changed, 12 insertions(+), 11 deletions(-) diff --git a/extensions-builtin/Lora/ui_extra_networks_lora.py b/extensions-builtin/Lora/ui_extra_networks_lora.py index b627f7dc..e35d90c6 100644 --- a/extensions-builtin/Lora/ui_extra_networks_lora.py +++ b/extensions-builtin/Lora/ui_extra_networks_lora.py @@ -60,18 +60,19 @@ class ExtraNetworksPageLora(ui_extra_networks.ExtraNetworksPage): else: sd_version = lora_on_disk.sd_version - if shared.opts.lora_show_all or not enable_filter: - pass - 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 - if model_version.name in shared.opts.lora_hide_unknown_for_versions: + if shared.sd_model is not None: # still show LoRA in case an error occurs during initial model loading + if shared.opts.lora_show_all or not enable_filter: + pass + 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 + if model_version.name in shared.opts.lora_hide_unknown_for_versions: + return None + elif shared.sd_model.is_sdxl and sd_version != network.SdVersion.SDXL: + return None + elif shared.sd_model.is_sd2 and sd_version != network.SdVersion.SD2: + return None + elif shared.sd_model.is_sd1 and sd_version != network.SdVersion.SD1: return None - elif shared.sd_model.is_sdxl and sd_version != network.SdVersion.SDXL: - return None - elif shared.sd_model.is_sd2 and sd_version != network.SdVersion.SD2: - return None - elif shared.sd_model.is_sd1 and sd_version != network.SdVersion.SD1: - return None return item From 246c269af87757998f57bb27ddda59fdc7cff976 Mon Sep 17 00:00:00 2001 From: w-e-w <40751091+w-e-w@users.noreply.github.com> Date: Tue, 23 Apr 2024 03:08:09 +0900 Subject: [PATCH 258/496] add option to check file hash after download if the sha256 hash does not match it will be automatically deleted --- modules/modelloader.py | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/modules/modelloader.py b/modules/modelloader.py index 115415c8..5421e59b 100644 --- a/modules/modelloader.py +++ b/modules/modelloader.py @@ -23,6 +23,7 @@ def load_file_from_url( model_dir: str, progress: bool = True, file_name: str | None = None, + hash_prefix: str | None = None, ) -> str: """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): print(f'Downloading: "{url}" to {cached_file}\n') from torch.hub import download_url_to_file - download_url_to_file(url, cached_file, progress=progress) + download_url_to_file(url, cached_file, progress=progress, hash_prefix=hash_prefix) 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. @@ -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 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 hash_prefix: the expected sha256 of the model_url @return: A list of paths containing the desired model(s) """ 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 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: output.append(model_url) From c69773d7e8f23f8b6c46a8e177b50386e1f1b8e8 Mon Sep 17 00:00:00 2001 From: w-e-w <40751091+w-e-w@users.noreply.github.com> Date: Tue, 23 Apr 2024 03:08:57 +0900 Subject: [PATCH 259/496] ensure integrity for initial sd model download --- modules/sd_models.py | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/modules/sd_models.py b/modules/sd_models.py index ff245b7a..35d5952a 100644 --- a/modules/sd_models.py +++ b/modules/sd_models.py @@ -149,10 +149,12 @@ def list_models(): cmd_ckpt = shared.cmd_opts.ckpt if shared.cmd_opts.no_download_sd_model or cmd_ckpt != shared.sd_model_file or os.path.exists(cmd_ckpt): model_url = None + expected_sha256 = None else: model_url = f"{shared.hf_endpoint}/runwayml/stable-diffusion-v1-5/resolve/main/v1-5-pruned-emaonly.safetensors" + expected_sha256 = '6ce0161689b3853acaa03779ec93eafe75a02f4ced659bee03f50797806fa2fa' - model_list = modelloader.load_models(model_path=model_path, model_url=model_url, command_path=shared.cmd_opts.ckpt_dir, ext_filter=[".ckpt", ".safetensors"], download_name="v1-5-pruned-emaonly.safetensors", ext_blacklist=[".vae.ckpt", ".vae.safetensors"]) + model_list = modelloader.load_models(model_path=model_path, model_url=model_url, command_path=shared.cmd_opts.ckpt_dir, ext_filter=[".ckpt", ".safetensors"], download_name="v1-5-pruned-emaonly.safetensors", ext_blacklist=[".vae.ckpt", ".vae.safetensors"], hash_prefix=expected_sha256) if os.path.exists(cmd_ckpt): checkpoint_info = CheckpointInfo(cmd_ckpt) From a1aa0af8a45f4c30f1d3fce5635c090d64d4e55b Mon Sep 17 00:00:00 2001 From: drhead <1313496+drhead@users.noreply.github.com> Date: Mon, 22 Apr 2024 23:38:44 -0400 Subject: [PATCH 260/496] add code for skipping CFG on early steps --- modules/sd_samplers_cfg_denoiser.py | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/modules/sd_samplers_cfg_denoiser.py b/modules/sd_samplers_cfg_denoiser.py index 93581c9a..8ccc837a 100644 --- a/modules/sd_samplers_cfg_denoiser.py +++ b/modules/sd_samplers_cfg_denoiser.py @@ -212,6 +212,11 @@ class CFGDenoiser(torch.nn.Module): uncond = denoiser_params.text_uncond skip_uncond = False + if self.step < shared.opts.skip_cond_steps: + skip_uncond = True + x_in = x_in[:-batch_size] + sigma_in = sigma_in[:-batch_size] + # alternating uncond allows for higher thresholds without the quality loss normally expected from raising it if self.step % 2 and s_min_uncond > 0 and sigma[0] < s_min_uncond and not is_edit_model: skip_uncond = True From 8016d78a4b9c8bdd02b0031694ad56553f89161e Mon Sep 17 00:00:00 2001 From: drhead <1313496+drhead@users.noreply.github.com> Date: Mon, 22 Apr 2024 23:42:24 -0400 Subject: [PATCH 261/496] add option for early cfg skip --- modules/shared_options.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/modules/shared_options.py b/modules/shared_options.py index 326a317e..2f70ef65 100644 --- a/modules/shared_options.py +++ b/modules/shared_options.py @@ -380,7 +380,8 @@ options_templates.update(options_section(('sampler-params', "Sampler parameters" 'uni_pc_skip_type': OptionInfo("time_uniform", "UniPC skip type", gr.Radio, {"choices": ["time_uniform", "time_quadratic", "logSNR"]}, infotext='UniPC skip type'), 'uni_pc_order': OptionInfo(3, "UniPC order", gr.Slider, {"minimum": 1, "maximum": 50, "step": 1}, infotext='UniPC order').info("must be < sampling steps"), 'uni_pc_lower_order_final': OptionInfo(True, "UniPC lower order final", infotext='UniPC lower order final'), - 'sd_noise_schedule': OptionInfo("Default", "Noise schedule for sampling", gr.Radio, {"choices": ["Default", "Zero Terminal SNR"]}, infotext="Noise Schedule").info("for use with zero terminal SNR trained models") + 'sd_noise_schedule': OptionInfo("Default", "Noise schedule for sampling", gr.Radio, {"choices": ["Default", "Zero Terminal SNR"]}, infotext="Noise Schedule").info("for use with zero terminal SNR trained models"), + 'skip_cond_steps': OptionInfo(0, "Skip CFG on first N steps of sampling", gr.Slider, {"minimum": 0, "maximum": 50, "step": 1}, infotext="Skip CFG first steps"), })) options_templates.update(options_section(('postprocessing', "Postprocessing", "postprocessing"), { From 83266205d0b55ddbff34ea36b47f69c5ea11cc28 Mon Sep 17 00:00:00 2001 From: drhead <1313496+drhead@users.noreply.github.com> Date: Tue, 23 Apr 2024 00:09:43 -0400 Subject: [PATCH 262/496] Add KL Optimal scheduler --- modules/sd_schedulers.py | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/modules/sd_schedulers.py b/modules/sd_schedulers.py index 75eb3ac0..10ae4e08 100644 --- a/modules/sd_schedulers.py +++ b/modules/sd_schedulers.py @@ -31,6 +31,15 @@ def sgm_uniform(n, sigma_min, sigma_max, inner_model, device): return torch.FloatTensor(sigs).to(device) +def kl_optimal(n, sigma_min, sigma_max, device): + alpha_min = torch.arctan(torch.tensor(sigma_min, device=device)) + alpha_max = torch.arctan(torch.tensor(sigma_max, device=device)) + sigmas = torch.empty((n+1,), device=device) + for i in range(n+1): + sigmas[i] = torch.tan((i/n) * alpha_min + (1.0-i/n) * alpha_max) + return sigmas + + schedulers = [ Scheduler('automatic', 'Automatic', None), Scheduler('uniform', 'Uniform', uniform, need_inner_model=True), @@ -38,6 +47,7 @@ schedulers = [ Scheduler('exponential', 'Exponential', k_diffusion.sampling.get_sigmas_exponential), Scheduler('polyexponential', 'Polyexponential', k_diffusion.sampling.get_sigmas_polyexponential, default_rho=1.0), Scheduler('sgm_uniform', 'SGM Uniform', sgm_uniform, need_inner_model=True, aliases=["SGMUniform"]), + Scheduler('kl_optimal', 'KL Optimal', kl_optimal), ] schedulers_map = {**{x.name: x for x in schedulers}, **{x.label: x for x in schedulers}} From 83182d2799f12ee2b5e5425d750db062ad67eb90 Mon Sep 17 00:00:00 2001 From: drhead <1313496+drhead@users.noreply.github.com> Date: Tue, 23 Apr 2024 03:07:25 -0400 Subject: [PATCH 263/496] change skip early cond option name and to float --- modules/shared_options.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/modules/shared_options.py b/modules/shared_options.py index 2f70ef65..91ba72b5 100644 --- a/modules/shared_options.py +++ b/modules/shared_options.py @@ -381,7 +381,7 @@ options_templates.update(options_section(('sampler-params', "Sampler parameters" 'uni_pc_order': OptionInfo(3, "UniPC order", gr.Slider, {"minimum": 1, "maximum": 50, "step": 1}, infotext='UniPC order').info("must be < sampling steps"), 'uni_pc_lower_order_final': OptionInfo(True, "UniPC lower order final", infotext='UniPC lower order final'), 'sd_noise_schedule': OptionInfo("Default", "Noise schedule for sampling", gr.Radio, {"choices": ["Default", "Zero Terminal SNR"]}, infotext="Noise Schedule").info("for use with zero terminal SNR trained models"), - 'skip_cond_steps': OptionInfo(0, "Skip CFG on first N steps of sampling", gr.Slider, {"minimum": 0, "maximum": 50, "step": 1}, infotext="Skip CFG first steps"), + 'skip_early_cond': OptionInfo(0, "Skip CFG during early sampling", gr.Slider, {"minimum": 0.0, "maximum": 1.0, "step": 0.01}, infotext="Skip Early CFG").info("CFG will be disabled (set to 1) on early steps, can both improve sample diversity/quality and speed up sampling"), })) options_templates.update(options_section(('postprocessing', "Postprocessing", "postprocessing"), { From 6e9b69a33853e1bcee81cea6f01cf13de612fef7 Mon Sep 17 00:00:00 2001 From: drhead <1313496+drhead@users.noreply.github.com> Date: Tue, 23 Apr 2024 03:08:28 -0400 Subject: [PATCH 264/496] change skip_early_cond code to use float --- modules/sd_samplers_cfg_denoiser.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/modules/sd_samplers_cfg_denoiser.py b/modules/sd_samplers_cfg_denoiser.py index 8ccc837a..fba5c48c 100644 --- a/modules/sd_samplers_cfg_denoiser.py +++ b/modules/sd_samplers_cfg_denoiser.py @@ -212,7 +212,7 @@ class CFGDenoiser(torch.nn.Module): uncond = denoiser_params.text_uncond skip_uncond = False - if self.step < shared.opts.skip_cond_steps: + if self.step / self.total_steps <= shared.opts.skip_early_cond: skip_uncond = True x_in = x_in[:-batch_size] sigma_in = sigma_in[:-batch_size] From 33cbbf9f8b46666a2325c98b723b6cb2ec192ef7 Mon Sep 17 00:00:00 2001 From: drhead <1313496+drhead@users.noreply.github.com> Date: Tue, 23 Apr 2024 03:15:00 -0400 Subject: [PATCH 265/496] add s_min_uncond_all option --- modules/shared_options.py | 1 + 1 file changed, 1 insertion(+) diff --git a/modules/shared_options.py b/modules/shared_options.py index 91ba72b5..c711fa5f 100644 --- a/modules/shared_options.py +++ b/modules/shared_options.py @@ -210,6 +210,7 @@ options_templates.update(options_section(('img2img', "img2img", "sd"), { options_templates.update(options_section(('optimizations', "Optimizations", "sd"), { "cross_attention_optimization": OptionInfo("Automatic", "Cross attention optimization", gr.Dropdown, lambda: {"choices": shared_items.cross_attention_optimizations()}), "s_min_uncond": OptionInfo(0.0, "Negative Guidance minimum sigma", gr.Slider, {"minimum": 0.0, "maximum": 15.0, "step": 0.01}).link("PR", "https://github.com/AUTOMATIC1111/stable-diffusion-webui/pull/9177").info("skip negative prompt for some steps when the image is almost ready; 0=disable, higher=faster"), + "s_min_uncond_all": OptionInfo(False, "NGMS: Skip every step").info("makes Negative Guidance minimum sigma skip negative guidance on every step instead of only half"), "token_merging_ratio": OptionInfo(0.0, "Token merging ratio", gr.Slider, {"minimum": 0.0, "maximum": 0.9, "step": 0.1}, infotext='Token merging ratio').link("PR", "https://github.com/AUTOMATIC1111/stable-diffusion-webui/pull/9256").info("0=disable, higher=faster"), "token_merging_ratio_img2img": OptionInfo(0.0, "Token merging ratio for img2img", gr.Slider, {"minimum": 0.0, "maximum": 0.9, "step": 0.1}).info("only applies if non-zero and overrides above"), "token_merging_ratio_hr": OptionInfo(0.0, "Token merging ratio for high-res pass", gr.Slider, {"minimum": 0.0, "maximum": 0.9, "step": 0.1}, infotext='Token merging ratio hr').info("only applies if non-zero and overrides above"), From 029adbe5318b57c04dbc0d92273cce38e1ecf457 Mon Sep 17 00:00:00 2001 From: drhead <1313496+drhead@users.noreply.github.com> Date: Tue, 23 Apr 2024 03:15:56 -0400 Subject: [PATCH 266/496] implement option to skip uncond on all steps below ngms --- modules/sd_samplers_cfg_denoiser.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/modules/sd_samplers_cfg_denoiser.py b/modules/sd_samplers_cfg_denoiser.py index fba5c48c..082a4f63 100644 --- a/modules/sd_samplers_cfg_denoiser.py +++ b/modules/sd_samplers_cfg_denoiser.py @@ -218,7 +218,7 @@ class CFGDenoiser(torch.nn.Module): sigma_in = sigma_in[:-batch_size] # alternating uncond allows for higher thresholds without the quality loss normally expected from raising it - if self.step % 2 and s_min_uncond > 0 and sigma[0] < s_min_uncond and not is_edit_model: + if (self.step % 2 or shared.opts.s_min_uncond_all) and s_min_uncond > 0 and sigma[0] < s_min_uncond and not is_edit_model: skip_uncond = True x_in = x_in[:-batch_size] sigma_in = sigma_in[:-batch_size] From 50bb6e1179745799038b26a228b8acd8cacfffc5 Mon Sep 17 00:00:00 2001 From: pinanew <851673+pinanew@users.noreply.github.com> Date: Tue, 23 Apr 2024 18:45:42 +0300 Subject: [PATCH 267/496] AVIF has quality setting too --- modules/images.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/modules/images.py b/modules/images.py index c0ff8a63..f4eb6f71 100644 --- a/modules/images.py +++ b/modules/images.py @@ -608,7 +608,7 @@ def save_image_with_geninfo(image, geninfo, filename, extension=None, existing_p }) - image.save(filename,format=image_format, exif=exif_bytes) + image.save(filename,format=image_format, quality=opts.jpeg_quality, exif=exif_bytes) elif extension.lower() == ".gif": image.save(filename, format=image_format, comment=geninfo) else: From 8fa3fa76c39200e2af63ab86926c0c20cf02eb25 Mon Sep 17 00:00:00 2001 From: w-e-w <40751091+w-e-w@users.noreply.github.com> Date: Wed, 24 Apr 2024 02:41:31 +0900 Subject: [PATCH 268/496] fix exif_bytes referenced before assignment --- modules/images.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/modules/images.py b/modules/images.py index f4eb6f71..36b61032 100644 --- a/modules/images.py +++ b/modules/images.py @@ -606,7 +606,8 @@ def save_image_with_geninfo(image, geninfo, filename, extension=None, existing_p 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": From 1091e3a37eb363d6ac5f4d3eb596526a85dea551 Mon Sep 17 00:00:00 2001 From: w-e-w <40751091+w-e-w@users.noreply.github.com> Date: Wed, 24 Apr 2024 02:54:26 +0900 Subject: [PATCH 269/496] update jpeg_quality description --- modules/shared_options.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/modules/shared_options.py b/modules/shared_options.py index 326a317e..98d477f5 100644 --- a/modules/shared_options.py +++ b/modules/shared_options.py @@ -54,7 +54,7 @@ options_templates.update(options_section(('saving-images', "Saving images/grids" "save_images_before_color_correction": OptionInfo(False, "Save a copy of image before applying color correction to img2img results"), "save_mask": OptionInfo(False, "For inpainting, save a copy of the greyscale mask"), "save_mask_composite": OptionInfo(False, "For inpainting, save a masked composite"), - "jpeg_quality": OptionInfo(80, "Quality for saved jpeg images", gr.Slider, {"minimum": 1, "maximum": 100, "step": 1}), + "jpeg_quality": OptionInfo(80, "Quality for saved jpeg and avif images", gr.Slider, {"minimum": 1, "maximum": 100, "step": 1}), "webp_lossless": OptionInfo(False, "Use lossless compression for webp images"), "export_for_4chan": OptionInfo(True, "Save copy of large images as JPG").info("if the file size is above the limit, or either width or height are above the limit"), "img_downscale_threshold": OptionInfo(4.0, "File size limit for the above option, MB", gr.Number), From e85e327ae0409a6c7e6f98011465f07290b78567 Mon Sep 17 00:00:00 2001 From: Andray Date: Thu, 25 Apr 2024 13:26:26 +0400 Subject: [PATCH 270/496] more extension tag filtering options --- modules/ui_extensions.py | 68 +++++++++++++++++++++++++++------------- 1 file changed, 47 insertions(+), 21 deletions(-) diff --git a/modules/ui_extensions.py b/modules/ui_extensions.py index d822c0b8..9bfd5f3b 100644 --- a/modules/ui_extensions.py +++ b/modules/ui_extensions.py @@ -396,15 +396,15 @@ def install_extension_from_url(dirname, url, branch_name=None): shutil.rmtree(tmpdir, True) -def install_extension_from_index(url, hide_tags, sort_column, filter_text): +def install_extension_from_index(url, selected_tags, showing_type, filtering_type, sort_column, filter_text): ext_table, message = install_extension_from_url(None, url) - code, _ = refresh_available_extensions_from_data(hide_tags, sort_column, filter_text) + code, _ = refresh_available_extensions_from_data(selected_tags, showing_type, filtering_type, sort_column, filter_text) return code, ext_table, message, '' -def refresh_available_extensions(url, hide_tags, sort_column): +def refresh_available_extensions(url, selected_tags, showing_type, filtering_type, sort_column): global available_extensions import urllib.request @@ -413,19 +413,19 @@ def refresh_available_extensions(url, hide_tags, sort_column): available_extensions = json.loads(text) - code, tags = refresh_available_extensions_from_data(hide_tags, sort_column) + code, tags = refresh_available_extensions_from_data(selected_tags, showing_type, filtering_type, sort_column) return url, code, gr.CheckboxGroup.update(choices=tags), '', '' -def refresh_available_extensions_for_tags(hide_tags, sort_column, filter_text): - code, _ = refresh_available_extensions_from_data(hide_tags, sort_column, filter_text) +def refresh_available_extensions_for_tags(selected_tags, showing_type, filtering_type, sort_column, filter_text): + code, _ = refresh_available_extensions_from_data(selected_tags, showing_type, filtering_type, sort_column, filter_text) return code, '' -def search_extensions(filter_text, hide_tags, sort_column): - code, _ = refresh_available_extensions_from_data(hide_tags, sort_column, filter_text) +def search_extensions(filter_text, selected_tags, showing_type, filtering_type, sort_column): + code, _ = refresh_available_extensions_from_data(selected_tags, showing_type, filtering_type, sort_column, filter_text) return code, '' @@ -450,13 +450,13 @@ def get_date(info: dict, key): return '' -def refresh_available_extensions_from_data(hide_tags, sort_column, filter_text=""): +def refresh_available_extensions_from_data(selected_tags, showing_type, filtering_type, sort_column, filter_text=""): extlist = available_extensions["extensions"] installed_extensions = {extension.name for extension in extensions.extensions} installed_extension_urls = {normalize_git_url(extension.remote) for extension in extensions.extensions if extension.remote is not None} tags = available_extensions.get("tags", {}) - tags_to_hide = set(hide_tags) + selected_tags = set(selected_tags) hidden = 0 code = f""" @@ -489,9 +489,19 @@ def refresh_available_extensions_from_data(hide_tags, sort_column, filter_text=" existing = get_extension_dirname_from_url(url) in installed_extensions or normalize_git_url(url) in installed_extension_urls extension_tags = extension_tags + ["installed"] if existing else extension_tags - if any(x for x in extension_tags if x in tags_to_hide): - hidden += 1 - continue + if len(selected_tags) > 0: + matched_tags = [x for x in extension_tags if x in selected_tags] + if filtering_type == 'or': + need_hide = len(matched_tags) > 0 + else: + need_hide = len(matched_tags) == len(selected_tags) + + if showing_type == 'show': + need_hide = not need_hide + + if need_hide: + hidden += 1 + continue if filter_text and filter_text.strip(): if filter_text.lower() not in html.escape(name).lower() and filter_text.lower() not in html.escape(description).lower(): @@ -594,9 +604,13 @@ def create_ui(): install_extension_button = gr.Button(elem_id="install_extension_button", visible=False) with gr.Row(): - hide_tags = gr.CheckboxGroup(value=["ads", "localization", "installed"], label="Hide extensions with tags", choices=["script", "ads", "localization", "installed"]) + selected_tags = gr.CheckboxGroup(value=["ads", "localization", "installed"], label="Extension tags", choices=["script", "ads", "localization", "installed"]) sort_column = gr.Radio(value="newest first", label="Order", choices=["newest first", "oldest first", "a-z", "z-a", "internal order",'update time', 'create time', "stars"], type="index") + with gr.Row(): + showing_type = gr.Radio(value="hide", label="Showing type", choices=["hide", "show"]) + filtering_type = gr.Radio(value="or", label="Filtering type", choices=["or", "and"]) + with gr.Row(): search_extensions_text = gr.Text(label="Search", container=False) @@ -605,31 +619,43 @@ def create_ui(): refresh_available_extensions_button.click( fn=modules.ui.wrap_gradio_call(refresh_available_extensions, extra_outputs=[gr.update(), gr.update(), gr.update(), gr.update()]), - inputs=[available_extensions_index, hide_tags, sort_column], - outputs=[available_extensions_index, available_extensions_table, hide_tags, search_extensions_text, install_result], + inputs=[available_extensions_index, selected_tags, showing_type, filtering_type, sort_column], + outputs=[available_extensions_index, available_extensions_table, selected_tags, search_extensions_text, install_result], ) install_extension_button.click( fn=modules.ui.wrap_gradio_call(install_extension_from_index, extra_outputs=[gr.update(), gr.update()]), - inputs=[extension_to_install, hide_tags, sort_column, search_extensions_text], + inputs=[extension_to_install, selected_tags, showing_type, filtering_type, sort_column, search_extensions_text], outputs=[available_extensions_table, extensions_table, install_result], ) search_extensions_text.change( fn=modules.ui.wrap_gradio_call(search_extensions, extra_outputs=[gr.update()]), - inputs=[search_extensions_text, hide_tags, sort_column], + inputs=[search_extensions_text, selected_tags, showing_type, filtering_type, sort_column], outputs=[available_extensions_table, install_result], ) - hide_tags.change( + selected_tags.change( fn=modules.ui.wrap_gradio_call(refresh_available_extensions_for_tags, extra_outputs=[gr.update()]), - inputs=[hide_tags, sort_column, search_extensions_text], + inputs=[selected_tags, showing_type, filtering_type, sort_column, search_extensions_text], + outputs=[available_extensions_table, install_result] + ) + + showing_type.change( + fn=modules.ui.wrap_gradio_call(refresh_available_extensions_for_tags, extra_outputs=[gr.update()]), + inputs=[selected_tags, showing_type, filtering_type, sort_column, search_extensions_text], + outputs=[available_extensions_table, install_result] + ) + + filtering_type.change( + fn=modules.ui.wrap_gradio_call(refresh_available_extensions_for_tags, extra_outputs=[gr.update()]), + inputs=[selected_tags, showing_type, filtering_type, sort_column, search_extensions_text], outputs=[available_extensions_table, install_result] ) sort_column.change( fn=modules.ui.wrap_gradio_call(refresh_available_extensions_for_tags, extra_outputs=[gr.update()]), - inputs=[hide_tags, sort_column, search_extensions_text], + inputs=[selected_tags, showing_type, filtering_type, sort_column, search_extensions_text], outputs=[available_extensions_table, install_result] ) From d5f6fdb3c44204495067d4166a6a980a9f1165ed Mon Sep 17 00:00:00 2001 From: w-e-w <40751091+w-e-w@users.noreply.github.com> Date: Fri, 26 Apr 2024 18:47:04 +0900 Subject: [PATCH 271/496] compact-checkbox-group --- modules/ui_extensions.py | 8 ++++---- style.css | 4 ++++ 2 files changed, 8 insertions(+), 4 deletions(-) diff --git a/modules/ui_extensions.py b/modules/ui_extensions.py index 9bfd5f3b..6b6403f2 100644 --- a/modules/ui_extensions.py +++ b/modules/ui_extensions.py @@ -604,12 +604,12 @@ def create_ui(): install_extension_button = gr.Button(elem_id="install_extension_button", visible=False) with gr.Row(): - selected_tags = gr.CheckboxGroup(value=["ads", "localization", "installed"], label="Extension tags", choices=["script", "ads", "localization", "installed"]) - sort_column = gr.Radio(value="newest first", label="Order", choices=["newest first", "oldest first", "a-z", "z-a", "internal order",'update time', 'create time', "stars"], type="index") + selected_tags = gr.CheckboxGroup(value=["ads", "localization", "installed"], label="Extension tags", choices=["script", "ads", "localization", "installed"], elem_classes=['compact-checkbox-group']) + sort_column = gr.Radio(value="newest first", label="Order", choices=["newest first", "oldest first", "a-z", "z-a", "internal order",'update time', 'create time', "stars"], type="index", elem_classes=['compact-checkbox-group']) with gr.Row(): - showing_type = gr.Radio(value="hide", label="Showing type", choices=["hide", "show"]) - filtering_type = gr.Radio(value="or", label="Filtering type", choices=["or", "and"]) + showing_type = gr.Radio(value="hide", label="Showing type", choices=["hide", "show"], elem_classes=['compact-checkbox-group']) + filtering_type = gr.Radio(value="or", label="Filtering type", choices=["or", "and"], elem_classes=['compact-checkbox-group']) with gr.Row(): search_extensions_text = gr.Text(label="Search", container=False) diff --git a/style.css b/style.css index f6a89b8f..cca5456c 100644 --- a/style.css +++ b/style.css @@ -854,6 +854,10 @@ table.popup-table .link{ display: inline-block; } +.compact-checkbox-group div label { + padding: 0.1em 0.3em !important; +} + /* extensions tab table row hover highlight */ #extensions tr:hover td, From 3902aa222b00a24f2d7b7158b79efaac9f318923 Mon Sep 17 00:00:00 2001 From: Brendan Hoar Date: Fri, 26 Apr 2024 06:44:41 -0400 Subject: [PATCH 272/496] Better error handling to skip non-standard ss_tag_frequency content --- extensions-builtin/Lora/ui_edit_user_metadata.py | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/extensions-builtin/Lora/ui_edit_user_metadata.py b/extensions-builtin/Lora/ui_edit_user_metadata.py index 7a07a544..b6c4d1c6 100644 --- a/extensions-builtin/Lora/ui_edit_user_metadata.py +++ b/extensions-builtin/Lora/ui_edit_user_metadata.py @@ -21,10 +21,12 @@ re_comma = re.compile(r" *, *") def build_tags(metadata): tags = {} - for _, tags_dict in metadata.get("ss_tag_frequency", {}).items(): - for tag, tag_count in tags_dict.items(): - tag = tag.strip() - tags[tag] = tags.get(tag, 0) + int(tag_count) + ss_tag_frequency = metadata.get("ss_tag_frequency", {}) + if ss_tag_frequency is not None and hasattr(ss_tag_frequency, 'items'): + for _, tags_dict in ss_tag_frequency.items(): + for tag, tag_count in tags_dict.items(): + tag = tag.strip() + tags[tag] = tags.get(tag, 0) + int(tag_count) if tags and is_non_comma_tagset(tags): new_tags = {} From 8dc920228e7c5181cc990845f0febd2ac4b42d87 Mon Sep 17 00:00:00 2001 From: Brendan Hoar Date: Fri, 26 Apr 2024 06:52:21 -0400 Subject: [PATCH 273/496] Better error handling when unable to read metadata from safetensors file --- modules/sd_models.py | 22 +++++++++++++--------- 1 file changed, 13 insertions(+), 9 deletions(-) diff --git a/modules/sd_models.py b/modules/sd_models.py index ff245b7a..59742d31 100644 --- a/modules/sd_models.py +++ b/modules/sd_models.py @@ -280,18 +280,22 @@ def read_metadata_from_safetensors(filename): json_start = file.read(2) assert metadata_len > 2 and json_start in (b'{"', b"{'"), f"{filename} is not a safetensors file" - json_data = json_start + file.read(metadata_len-2) - json_obj = json.loads(json_data) res = {} - for k, v in json_obj.get("__metadata__", {}).items(): - res[k] = v - if isinstance(v, str) and v[0:1] == '{': - try: - res[k] = json.loads(v) - except Exception: - pass + try: + json_data = json_start + file.read(metadata_len-2) + json_obj = json.loads(json_data) + for k, v in json_obj.get("__metadata__", {}).items(): + res[k] = v + if isinstance(v, str) and v[0:1] == '{': + try: + res[k] = json.loads(v) + except Exception: + pass + except: + errors.report(f"Error reading metadata from file: {filename}", exc_info=True) + return res From c5b7559856c5f64792c2425d11890a121497e6bc Mon Sep 17 00:00:00 2001 From: Brendan Hoar Date: Fri, 26 Apr 2024 06:57:32 -0400 Subject: [PATCH 274/496] Better error handling when unable to extract contents of embedding/TI file --- modules/textual_inversion/textual_inversion.py | 12 ++++++++---- 1 file changed, 8 insertions(+), 4 deletions(-) diff --git a/modules/textual_inversion/textual_inversion.py b/modules/textual_inversion/textual_inversion.py index 253f219c..dc7833e9 100644 --- a/modules/textual_inversion/textual_inversion.py +++ b/modules/textual_inversion/textual_inversion.py @@ -181,12 +181,16 @@ class EmbeddingDatabase: else: return - embedding = create_embedding_from_data(data, name, filename=filename, filepath=path) + if data is not None: + embedding = create_embedding_from_data(data, name, filename=filename, filepath=path) - if self.expected_shape == -1 or self.expected_shape == embedding.shape: - self.register_embedding(embedding, shared.sd_model) + if self.expected_shape == -1 or self.expected_shape == embedding.shape: + self.register_embedding(embedding, shared.sd_model) + else: + self.skipped_embeddings[name] = embedding else: - self.skipped_embeddings[name] = embedding + print(f"Unable to load Textual inversion embedding due to data issue: '{name}'.") + def load_from_dir(self, embdir): if not os.path.isdir(embdir.path): From c5ae2254182b803618a4b01c12fa88c42642e806 Mon Sep 17 00:00:00 2001 From: Brendan Hoar Date: Fri, 26 Apr 2024 07:55:39 -0400 Subject: [PATCH 275/496] 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. --- modules/safe.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/modules/safe.py b/modules/safe.py index b1d08a79..ee878926 100644 --- a/modules/safe.py +++ b/modules/safe.py @@ -65,7 +65,7 @@ class RestrictedUnpickler(pickle.Unpickler): # Regular expression that accepts 'dirname/version', 'dirname/data.pkl', and 'dirname/data/' -allowed_zip_names_re = re.compile(r"^([^/]+)/((data/\d+)|version|(data\.pkl))$") +allowed_zip_names_re = re.compile(r"^([^/]+)/((data/\d+)|byteorder|(\.data\/serialization_id)|version|(data\.pkl))$") data_pkl_re = re.compile(r"^([^/]+)/data\.pkl$") def check_zip_filenames(filename, names): From 44afb48447c2ef40f8546fe704bd817881da5a14 Mon Sep 17 00:00:00 2001 From: Brendan Hoar Date: Fri, 26 Apr 2024 08:17:37 -0400 Subject: [PATCH 276/496] Linter fix - extraneous whitespace --- modules/sd_models.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/modules/sd_models.py b/modules/sd_models.py index 59742d31..06e88120 100644 --- a/modules/sd_models.py +++ b/modules/sd_models.py @@ -295,7 +295,7 @@ def read_metadata_from_safetensors(filename): pass except: errors.report(f"Error reading metadata from file: {filename}", exc_info=True) - + return res From 60c079995824ebe861029839ee12ca0df6a26e8d Mon Sep 17 00:00:00 2001 From: Brendan Hoar Date: Fri, 26 Apr 2024 08:21:12 -0400 Subject: [PATCH 277/496] Linter - except must not be bare. --- modules/sd_models.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/modules/sd_models.py b/modules/sd_models.py index 06e88120..06a7cf3f 100644 --- a/modules/sd_models.py +++ b/modules/sd_models.py @@ -293,7 +293,7 @@ def read_metadata_from_safetensors(filename): res[k] = json.loads(v) except Exception: pass - except: + except Exception: errors.report(f"Error reading metadata from file: {filename}", exc_info=True) return res From 9d964d3fc3285b3df877479081968ebf6dbccce4 Mon Sep 17 00:00:00 2001 From: w-e-w <40751091+w-e-w@users.noreply.github.com> Date: Sat, 27 Apr 2024 19:21:34 +0900 Subject: [PATCH 278/496] no-referrer --- modules/ui_gradio_extensions.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/modules/ui_gradio_extensions.py b/modules/ui_gradio_extensions.py index f5278d22..18fbd677 100644 --- a/modules/ui_gradio_extensions.py +++ b/modules/ui_gradio_extensions.py @@ -50,7 +50,7 @@ def reload_javascript(): def template_response(*args, **kwargs): res = shared.GradioTemplateResponseOriginal(*args, **kwargs) - res.body = res.body.replace(b'', f'{js}'.encode("utf8")) + res.body = res.body.replace(b'', f'{js}'.encode("utf8")) res.body = res.body.replace(b'', f'{css}'.encode("utf8")) res.init_headers() return res From 3a215deff23d28c06c8de98423c12628b8ce6326 Mon Sep 17 00:00:00 2001 From: drhead <1313496+drhead@users.noreply.github.com> Date: Sun, 28 Apr 2024 00:15:58 -0400 Subject: [PATCH 279/496] vectorize kl-optimal sigma calculation Co-authored-by: mamei16 --- modules/sd_schedulers.py | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/modules/sd_schedulers.py b/modules/sd_schedulers.py index 10ae4e08..99a6f7be 100644 --- a/modules/sd_schedulers.py +++ b/modules/sd_schedulers.py @@ -34,9 +34,8 @@ def sgm_uniform(n, sigma_min, sigma_max, inner_model, device): def kl_optimal(n, sigma_min, sigma_max, device): alpha_min = torch.arctan(torch.tensor(sigma_min, device=device)) alpha_max = torch.arctan(torch.tensor(sigma_max, device=device)) - sigmas = torch.empty((n+1,), device=device) - for i in range(n+1): - sigmas[i] = torch.tan((i/n) * alpha_min + (1.0-i/n) * alpha_max) + step_indices = torch.arange(n + 1, device=device) + sigmas = torch.tan(step_indices / n * alpha_min + (1.0 - step_indices / n) * alpha_max) return sigmas From 3d3fc81f4858cae75fa33e55e7b88ede853d28ae Mon Sep 17 00:00:00 2001 From: huchenlei Date: Sun, 28 Apr 2024 16:14:12 -0400 Subject: [PATCH 280/496] Add correct mimetype for .mjs files --- modules/ui.py | 1 + 1 file changed, 1 insertion(+) diff --git a/modules/ui.py b/modules/ui.py index 403425f2..c6c058fe 100644 --- a/modules/ui.py +++ b/modules/ui.py @@ -38,6 +38,7 @@ warnings.filterwarnings("default" if opts.show_gradio_deprecation_warnings else # this is a fix for Windows users. Without it, javascript files will be served with text/html content-type and the browser will not show any UI mimetypes.init() mimetypes.add_type('application/javascript', '.js') +mimetypes.add_type('application/javascript', '.mjs') # Likewise, add explicit content-type header for certain missing image types mimetypes.add_type('image/webp', '.webp') From 579f1ef278080ff7545be3a42c5fe36fc2890887 Mon Sep 17 00:00:00 2001 From: missionfloyd Date: Sun, 28 Apr 2024 22:36:43 -0600 Subject: [PATCH 281/496] Allow old sampler names in API --- modules/api/api.py | 21 +++++++++++++++++++-- 1 file changed, 19 insertions(+), 2 deletions(-) diff --git a/modules/api/api.py b/modules/api/api.py index f468c385..b1201fe7 100644 --- a/modules/api/api.py +++ b/modules/api/api.py @@ -48,6 +48,15 @@ def validate_sampler_name(name): return name +def parse_old_sampler_name(name): + for scheduler in sd_schedulers.schedulers: + for scheduler_name in [scheduler.label, scheduler.name, *(scheduler.aliases or [])]: + if name.endswith(" " + scheduler_name): + return name[0:-(len(scheduler_name) + 1)], scheduler_name + + return name, "Automatic" + + def setUpscalers(req: dict): reqDict = vars(req) reqDict['extras_upscaler_1'] = reqDict.pop('upscaler_1', None) @@ -438,15 +447,19 @@ class Api: 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) + sampler, scheduler = parse_old_sampler_name(txt2imgreq.sampler_name or txt2imgreq.sampler_index) 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_grid": not txt2imgreq.save_images, }) if populate.sampler_name: populate.sampler_index = None # prevent a warning later on + if not populate.scheduler: + populate.scheduler = scheduler + args = vars(populate) args.pop('script_name', None) args.pop('script_args', None) # will refeed them to the pipeline directly after initializing them @@ -502,9 +515,10 @@ class Api: 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) + sampler, scheduler = parse_old_sampler_name(img2imgreq.sampler_name or img2imgreq.sampler_index) 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_grid": not img2imgreq.save_images, "mask": mask, @@ -512,6 +526,9 @@ class Api: if populate.sampler_name: populate.sampler_index = None # prevent a warning later on + if not populate.scheduler: + populate.scheduler = scheduler + 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('script_name', None) From 4c7b22d37d14c8469b4510a11710f162940cdaa6 Mon Sep 17 00:00:00 2001 From: missionfloyd Date: Sun, 28 Apr 2024 22:46:11 -0600 Subject: [PATCH 282/496] Fix dragging text within prompt input --- javascript/dragdrop.js | 11 ++++++++++- 1 file changed, 10 insertions(+), 1 deletion(-) diff --git a/javascript/dragdrop.js b/javascript/dragdrop.js index 0c018356..882562d7 100644 --- a/javascript/dragdrop.js +++ b/javascript/dragdrop.js @@ -56,6 +56,15 @@ function eventHasFiles(e) { return false; } +function isURL(url) { + try { + const _ = new URL(url); + return true; + } catch { + return false; + } +} + function dragDropTargetIsPrompt(target) { if (target?.placeholder && target?.placeholder.indexOf("Prompt") >= 0) return true; if (target?.parentNode?.parentNode?.className?.indexOf("prompt") > 0) return true; @@ -77,7 +86,7 @@ window.document.addEventListener('dragover', e => { window.document.addEventListener('drop', async e => { const target = e.composedPath()[0]; const url = e.dataTransfer.getData('text/uri-list') || e.dataTransfer.getData('text/plain'); - if (!eventHasFiles(e) && !url) return; + if (!eventHasFiles(e) && !isURL(url)) return; if (dragDropTargetIsPrompt(target)) { e.stopPropagation(); From c8336c45b98c2226923503e17b1d7f9170af0f8a Mon Sep 17 00:00:00 2001 From: missionfloyd Date: Tue, 30 Apr 2024 01:53:41 -0600 Subject: [PATCH 283/496] Use existing function for old sampler names --- modules/api/api.py | 17 ++++------------- 1 file changed, 4 insertions(+), 13 deletions(-) diff --git a/modules/api/api.py b/modules/api/api.py index b1201fe7..d8e54529 100644 --- a/modules/api/api.py +++ b/modules/api/api.py @@ -48,15 +48,6 @@ def validate_sampler_name(name): return name -def parse_old_sampler_name(name): - for scheduler in sd_schedulers.schedulers: - for scheduler_name in [scheduler.label, scheduler.name, *(scheduler.aliases or [])]: - if name.endswith(" " + scheduler_name): - return name[0:-(len(scheduler_name) + 1)], scheduler_name - - return name, "Automatic" - - def setUpscalers(req: dict): reqDict = vars(req) reqDict['extras_upscaler_1'] = reqDict.pop('upscaler_1', None) @@ -447,7 +438,7 @@ class Api: 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) - sampler, scheduler = parse_old_sampler_name(txt2imgreq.sampler_name or txt2imgreq.sampler_index) + sampler, scheduler = sd_samplers.get_sampler_and_scheduler(txt2imgreq.sampler_name or txt2imgreq.sampler_index, txt2imgreq.scheduler) populate = txt2imgreq.copy(update={ # Override __init__ params "sampler_name": validate_sampler_name(sampler), @@ -457,7 +448,7 @@ class Api: if populate.sampler_name: populate.sampler_index = None # prevent a warning later on - if not populate.scheduler: + if not populate.scheduler and scheduler != "Automatic": populate.scheduler = scheduler args = vars(populate) @@ -515,7 +506,7 @@ class Api: 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) - sampler, scheduler = parse_old_sampler_name(img2imgreq.sampler_name or img2imgreq.sampler_index) + sampler, scheduler = sd_samplers.get_sampler_and_scheduler(img2imgreq.sampler_name or img2imgreq.sampler_index, img2imgreq.scheduler) populate = img2imgreq.copy(update={ # Override __init__ params "sampler_name": validate_sampler_name(sampler), @@ -526,7 +517,7 @@ class Api: if populate.sampler_name: populate.sampler_index = None # prevent a warning later on - if not populate.scheduler: + if not populate.scheduler and scheduler != "Automatic": populate.scheduler = scheduler args = vars(populate) From 9d393807056199deade14154d885fcd07dee24b7 Mon Sep 17 00:00:00 2001 From: w-e-w <40751091+w-e-w@users.noreply.github.com> Date: Tue, 30 Apr 2024 19:17:53 +0900 Subject: [PATCH 284/496] fix extra batch mode P Transparency red, green, blue = transparency TypeError: cannot unpack non-iterable int object --- modules/postprocessing.py | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/modules/postprocessing.py b/modules/postprocessing.py index 812cbcca..8ec122b7 100644 --- a/modules/postprocessing.py +++ b/modules/postprocessing.py @@ -62,11 +62,13 @@ def run_postprocessing(extras_mode, image, image_folder, input_dir, output_dir, else: 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) if parameters: existing_pnginfo["parameters"] = parameters - initial_pp = scripts_postprocessing.PostprocessedImage(image_data if image_data.mode in ("RGBA", "RGB") else image_data.convert("RGB")) + initial_pp = scripts_postprocessing.PostprocessedImage(image_data) scripts.scripts_postproc.run(initial_pp, args) From 89103b47475ba7bb8b9c4b36f8078c6416132ab0 Mon Sep 17 00:00:00 2001 From: w-e-w <40751091+w-e-w@users.noreply.github.com> Date: Wed, 1 May 2024 19:41:02 +0900 Subject: [PATCH 285/496] lora bundled TI infotext Co-Authored-By: Morgon Kanter <9632805+mx@users.noreply.github.com> --- extensions-builtin/Lora/networks.py | 9 +++++++++ extensions-builtin/Lora/scripts/lora_script.py | 1 + 2 files changed, 10 insertions(+) diff --git a/extensions-builtin/Lora/networks.py b/extensions-builtin/Lora/networks.py index 42b14dc2..aa55fe24 100644 --- a/extensions-builtin/Lora/networks.py +++ b/extensions-builtin/Lora/networks.py @@ -143,6 +143,14 @@ def assign_network_names_to_compvis_modules(sd_model): 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): net = network.Network(name, network_on_disk) net.mtime = os.path.getmtime(network_on_disk.filename) @@ -229,6 +237,7 @@ def load_network(name, network_on_disk): 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.loaded = None + embedding.shorthash = BundledTIHash(name) embeddings[emb_name] = embedding net.bundle_embeddings = embeddings diff --git a/extensions-builtin/Lora/scripts/lora_script.py b/extensions-builtin/Lora/scripts/lora_script.py index 1518f7e5..d3ea369a 100644 --- a/extensions-builtin/Lora/scripts/lora_script.py +++ b/extensions-builtin/Lora/scripts/lora_script.py @@ -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), "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_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_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}), From 0e0e41eabc5753034091e7c673100df66b3640ab Mon Sep 17 00:00:00 2001 From: Andray Date: Wed, 1 May 2024 16:54:47 +0400 Subject: [PATCH 286/496] use gradio theme colors in css --- style.css | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/style.css b/style.css index f6a89b8f..df4aca02 100644 --- a/style.css +++ b/style.css @@ -780,9 +780,9 @@ table.popup-table .link{ position:absolute; display:block; padding:0px 0; - border:2px solid #a55000; + border:2px solid var(--primary-800); border-radius:8px; - box-shadow:1px 1px 2px #CE6400; + box-shadow:1px 1px 2px var(--primary-500); width: 200px; } @@ -799,7 +799,7 @@ table.popup-table .link{ } .context-menu-items a:hover{ - background: #a55000; + background: var(--primary-700); } From 5d5224b322e8dbd817469a32d6c5578faff2df2f Mon Sep 17 00:00:00 2001 From: w-e-w <40751091+w-e-w@users.noreply.github.com> Date: Thu, 2 May 2024 02:25:16 +0900 Subject: [PATCH 287/496] fix_p_invalid_sampler_and_scheduler --- modules/processing.py | 3 +++ modules/sd_samplers.py | 9 ++++++++- 2 files changed, 11 insertions(+), 1 deletion(-) diff --git a/modules/processing.py b/modules/processing.py index 76557dd7..cb646e2b 100644 --- a/modules/processing.py +++ b/modules/processing.py @@ -842,6 +842,9 @@ def process_images(p: StableDiffusionProcessing) -> Processed: sd_models.apply_token_merging(p.sd_model, p.get_token_merging_ratio()) + # backwards compatibility, fix sampler and scheduler if invalid + sd_samplers.fix_p_invalid_sampler_and_scheduler(p) + res = process_images_inner(p) finally: diff --git a/modules/sd_samplers.py b/modules/sd_samplers.py index 6b7b84b6..b8abac4a 100644 --- a/modules/sd_samplers.py +++ b/modules/sd_samplers.py @@ -1,7 +1,7 @@ from __future__ import annotations import functools - +import logging from modules import sd_samplers_kdiffusion, sd_samplers_timesteps, sd_samplers_lcm, shared, sd_samplers_common, sd_schedulers # imports for functions that previously were here and are used by other modules @@ -122,4 +122,11 @@ def get_sampler_and_scheduler(sampler_name, scheduler_name): return sampler.name, found_scheduler.label +def fix_p_invalid_sampler_and_scheduler(p): + i_sampler_name, i_scheduler = p.sampler_name, p.scheduler + p.sampler_name, p.scheduler = get_sampler_and_scheduler(p.sampler_name, p.scheduler) + if p.sampler_name != i_sampler_name or i_scheduler != p.scheduler: + logging.warning(f'Sampler Scheduler autocorrection: "{i_sampler_name}" -> "{p.sampler_name}", "{i_scheduler}" -> "{p.scheduler}"') + + set_samplers() From 7195c4d42cf410c53d4d2f7a74d7059715d357a7 Mon Sep 17 00:00:00 2001 From: Andray Date: Wed, 1 May 2024 22:50:46 +0400 Subject: [PATCH 288/496] two fingers press to open context menu --- javascript/contextMenus.js | 25 ++++++++++++++++--------- 1 file changed, 16 insertions(+), 9 deletions(-) diff --git a/javascript/contextMenus.js b/javascript/contextMenus.js index ccae242f..a00c3de9 100644 --- a/javascript/contextMenus.js +++ b/javascript/contextMenus.js @@ -107,16 +107,23 @@ var contextMenuInit = function() { oldMenu.remove(); } }); - gradioApp().addEventListener("contextmenu", function(e) { - let oldMenu = gradioApp().querySelector('#context-menu'); - if (oldMenu) { - oldMenu.remove(); - } - menuSpecs.forEach(function(v, k) { - if (e.composedPath()[0].matches(k)) { - showContextMenu(e, e.composedPath()[0], v); - e.preventDefault(); + ['contextmenu', 'touchstart'].forEach((eventType) => { + gradioApp().addEventListener(eventType, function(e) { + let ev = e; + if (eventType.startsWith('touch')) { + if (e.touches.length !== 2) return; + ev = e.touches[0]; } + let oldMenu = gradioApp().querySelector('#context-menu'); + 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; From f12886aefa4f2ac5d8e64a206a6b4d6df9d85b6b Mon Sep 17 00:00:00 2001 From: w-e-w <40751091+w-e-w@users.noreply.github.com> Date: Sat, 4 May 2024 23:42:37 +0900 Subject: [PATCH 289/496] use script_path for webui root in launch_utils --- modules/launch_utils.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/modules/launch_utils.py b/modules/launch_utils.py index 5812b0e5..e22da4ec 100644 --- a/modules/launch_utils.py +++ b/modules/launch_utils.py @@ -76,7 +76,7 @@ def git_tag(): except Exception: 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: line = next((line.strip() for line in file if line.strip()), "") line = line.replace("## ", "") @@ -231,7 +231,7 @@ def run_extension_installer(extension_dir): try: 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() if stdout: From dd93c47abfd9ed357f5d5827311d836ea399a236 Mon Sep 17 00:00:00 2001 From: bluelovers Date: Tue, 7 May 2024 19:53:18 +0800 Subject: [PATCH 290/496] Update imageviewer.js --- javascript/imageviewer.js | 9 +-------- 1 file changed, 1 insertion(+), 8 deletions(-) diff --git a/javascript/imageviewer.js b/javascript/imageviewer.js index d4d4f016..a3f08ad1 100644 --- a/javascript/imageviewer.js +++ b/javascript/imageviewer.js @@ -51,14 +51,7 @@ function modalImageSwitch(offset) { var galleryButtons = all_gallery_buttons(); if (galleryButtons.length > 1) { - var currentButton = selected_gallery_button(); - - var result = -1; - galleryButtons.forEach(function(v, i) { - if (v == currentButton) { - result = i; - } - }); + var result = selected_gallery_index(); if (result != -1) { var nextButton = galleryButtons[negmod((result + offset), galleryButtons.length)]; From dbda59e58a7c90752ab9911a779dd1381ae530e1 Mon Sep 17 00:00:00 2001 From: Andray Date: Tue, 7 May 2024 19:26:16 +0400 Subject: [PATCH 291/496] fix context menu position --- javascript/contextMenus.js | 24 ++---------------------- 1 file changed, 2 insertions(+), 22 deletions(-) diff --git a/javascript/contextMenus.js b/javascript/contextMenus.js index a00c3de9..e01fd67e 100644 --- a/javascript/contextMenus.js +++ b/javascript/contextMenus.js @@ -8,9 +8,6 @@ var contextMenuInit = function() { }; 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'); if (oldMenu) { oldMenu.remove(); @@ -23,10 +20,8 @@ var contextMenuInit = function() { contextMenu.style.background = baseStyle.background; contextMenu.style.color = baseStyle.color; contextMenu.style.fontFamily = baseStyle.fontFamily; - contextMenu.style.top = posy + 'px'; - contextMenu.style.left = posx + 'px'; - - + contextMenu.style.top = event.pageY + 'px'; + contextMenu.style.left = event.pageX + 'px'; const contextMenuList = document.createElement('ul'); contextMenuList.className = 'context-menu-items'; @@ -43,21 +38,6 @@ var contextMenuInit = function() { }); 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) { From e736c3b36b5e450c3883719d1b73acf84bdf29f7 Mon Sep 17 00:00:00 2001 From: JLipnerPitt <122459494+JLipnerPitt@users.noreply.github.com> Date: Wed, 8 May 2024 05:22:12 -0400 Subject: [PATCH 292/496] 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. --- modules/images.py | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/modules/images.py b/modules/images.py index c0ff8a63..0f49caf7 100644 --- a/modules/images.py +++ b/modules/images.py @@ -789,7 +789,10 @@ def read_info_from_image(image: Image.Image) -> tuple[str | None, dict]: if exif_comment: geninfo = exif_comment 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: items.pop(field, None) From f7e349cea49731b0e57cc2a2c1eb4904f1aea9b9 Mon Sep 17 00:00:00 2001 From: LoganBooker Date: Wed, 8 May 2024 21:23:18 +1000 Subject: [PATCH 293/496] Add AVIF MIME type support to mimetype definitions AVIF images will open, rather than download, as the default behaviour. --- modules/ui.py | 1 + 1 file changed, 1 insertion(+) diff --git a/modules/ui.py b/modules/ui.py index 403425f2..cface500 100644 --- a/modules/ui.py +++ b/modules/ui.py @@ -41,6 +41,7 @@ mimetypes.add_type('application/javascript', '.js') # Likewise, add explicit content-type header for certain missing image types mimetypes.add_type('image/webp', '.webp') +mimetypes.add_type('image/avif', '.avif') if not cmd_opts.share and not cmd_opts.listen: # fix gradio phoning home From 5fbac49791d9a4a6af85c8236ba9179d7415e0f9 Mon Sep 17 00:00:00 2001 From: MarcusNyne <69087098+MarcusNyne@users.noreply.github.com> Date: Wed, 8 May 2024 16:48:10 -0400 Subject: [PATCH 294/496] 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. --- modules/cmd_args.py | 1 + modules/paths_internal.py | 4 +++- 2 files changed, 4 insertions(+), 1 deletion(-) diff --git a/modules/cmd_args.py b/modules/cmd_args.py index 016a33d1..a683c99e 100644 --- a/modules/cmd_args.py +++ b/modules/cmd_args.py @@ -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("--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("--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("--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") diff --git a/modules/paths_internal.py b/modules/paths_internal.py index cf9da45a..884984c9 100644 --- a/modules/paths_internal.py +++ b/modules/paths_internal.py @@ -24,11 +24,13 @@ 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 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("--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] data_path = cmd_opts_pre.data_dir +models_override = cmd_opts_pre.models_dir -models_path = os.path.join(data_path, "models") +models_path = models_override if models_override else os.path.join(data_path, "models") extensions_dir = os.path.join(data_path, "extensions") extensions_builtin_dir = os.path.join(script_path, "extensions-builtin") config_states_dir = os.path.join(script_path, "config_states") From d2cc8ccb11558f1dbdb27a2351e34155c3a24ccf Mon Sep 17 00:00:00 2001 From: MarcusNyne <69087098+MarcusNyne@users.noreply.github.com> Date: Thu, 9 May 2024 17:16:53 -0400 Subject: [PATCH 295/496] 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. --- webui.bat | 7 ++++++- webui.sh | 1 + 2 files changed, 7 insertions(+), 1 deletion(-) diff --git a/webui.bat b/webui.bat index e2c9079d..a8d479b0 100644 --- a/webui.bat +++ b/webui.bat @@ -37,10 +37,15 @@ if %ERRORLEVEL% == 0 goto :activate_venv for /f "delims=" %%i in ('CALL %PYTHON% -c "import sys; print(sys.executable)"') do set PYTHON_FULLNAME="%%i" echo Creating venv in directory %VENV_DIR% using python %PYTHON_FULLNAME% %PYTHON_FULLNAME% -m venv "%VENV_DIR%" >tmp/stdout.txt 2>tmp/stderr.txt -if %ERRORLEVEL% == 0 goto :activate_venv +if %ERRORLEVEL% == 0 goto :upgrade_pip echo Unable to create venv in directory "%VENV_DIR%" goto :show_stdout_stderr +:upgrade_pip +"%VENV_DIR%\Scripts\Python.exe" -m pip install --upgrade pip +if %ERRORLEVEL% == 0 goto :activate_venv +echo Warning: Failed to upgrade PIP version + :activate_venv set PYTHON="%VENV_DIR%\Scripts\Python.exe" echo venv %PYTHON% diff --git a/webui.sh b/webui.sh index c7c4bee9..7acea902 100755 --- a/webui.sh +++ b/webui.sh @@ -210,6 +210,7 @@ then if [[ ! -d "${venv_dir}" ]] then "${python_cmd}" -m venv "${venv_dir}" + "${venv_dir}"/bin/python -m pip install --upgrade pip first_launch=1 fi # shellcheck source=/dev/null From 73d1caf8f28a387f2db5a77a8892edad8ed505a0 Mon Sep 17 00:00:00 2001 From: Logan Date: Fri, 10 May 2024 12:38:10 +1000 Subject: [PATCH 296/496] 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) --- modules/sd_schedulers.py | 32 ++++++++++++++++++++++++++++++++ 1 file changed, 32 insertions(+) diff --git a/modules/sd_schedulers.py b/modules/sd_schedulers.py index 75eb3ac0..2131eae4 100644 --- a/modules/sd_schedulers.py +++ b/modules/sd_schedulers.py @@ -4,6 +4,7 @@ import torch import k_diffusion +import numpy as np @dataclasses.dataclass class Scheduler: @@ -30,6 +31,35 @@ def sgm_uniform(n, sigma_min, sigma_max, inner_model, device): sigs += [0.0] return torch.FloatTensor(sigs).to(device) +def get_align_your_steps_sigmas(n, device, sigma_id): + # https://research.nvidia.com/labs/toronto-ai/AlignYourSteps/howto.html + def loglinear_interp(t_steps, num_steps): + """ + Performs log-linear interpolation of a given array of decreasing numbers. + """ + xs = np.linspace(0, 1, len(t_steps)) + ys = np.log(t_steps[::-1]) + + new_xs = np.linspace(0, 1, num_steps) + new_ys = np.interp(new_xs, xs, ys) + + interped_ys = np.exp(new_ys)[::-1].copy() + return interped_ys + + if sigma_id == "sdxl": + sigmas = [14.615, 6.315, 3.771, 2.181, 1.342, 0.862, 0.555, 0.380, 0.234, 0.113, 0.029] + elif sigma_id == "sd15": + sigmas = [14.615, 6.475, 3.861, 2.697, 1.886, 1.396, 0.963, 0.652, 0.399, 0.152, 0.029] + else: + print(f'Align Your Steps sigma identifier "{sigma_id}" not recognized, defaulting to SD 1.5.') + sigmas = [14.615, 6.475, 3.861, 2.697, 1.886, 1.396, 0.963, 0.652, 0.399, 0.152, 0.029] + + if n != len(sigmas): + sigmas = np.append(loglinear_interp(sigmas, n), [0.0]) + else: + sigmas.append(0.0) + + return torch.FloatTensor(sigmas).to(device) schedulers = [ Scheduler('automatic', 'Automatic', None), @@ -38,6 +68,8 @@ schedulers = [ Scheduler('exponential', 'Exponential', k_diffusion.sampling.get_sigmas_exponential), Scheduler('polyexponential', 'Polyexponential', k_diffusion.sampling.get_sigmas_polyexponential, default_rho=1.0), Scheduler('sgm_uniform', 'SGM Uniform', sgm_uniform, need_inner_model=True, aliases=["SGMUniform"]), + Scheduler('align_your_steps_sdxl', 'Align Your Steps (SDXL)', lambda n, sigma_min, sigma_max, device: get_align_your_steps_sigmas(n, device, "sdxl")), + Scheduler('align_your_steps_sd15', 'Align Your Steps (SD 1.5)', lambda n, sigma_min, sigma_max, device: get_align_your_steps_sigmas(n, device, "sd15")), ] schedulers_map = {**{x.name: x for x in schedulers}, **{x.label: x for x in schedulers}} From d6b4444069d36cf7554eb9932061ecf43e9b1335 Mon Sep 17 00:00:00 2001 From: Logan Date: Fri, 10 May 2024 18:05:45 +1000 Subject: [PATCH 297/496] Use shared.sd_model.is_sdxl to determine base AYS sigmas --- modules/sd_schedulers.py | 13 ++++++------- 1 file changed, 6 insertions(+), 7 deletions(-) diff --git a/modules/sd_schedulers.py b/modules/sd_schedulers.py index 2131eae4..0ac1f7a2 100644 --- a/modules/sd_schedulers.py +++ b/modules/sd_schedulers.py @@ -6,6 +6,8 @@ import k_diffusion import numpy as np +from modules import shared + @dataclasses.dataclass class Scheduler: name: str @@ -31,7 +33,7 @@ def sgm_uniform(n, sigma_min, sigma_max, inner_model, device): sigs += [0.0] return torch.FloatTensor(sigs).to(device) -def get_align_your_steps_sigmas(n, device, sigma_id): +def get_align_your_steps_sigmas(n, sigma_min, sigma_max, device): # https://research.nvidia.com/labs/toronto-ai/AlignYourSteps/howto.html def loglinear_interp(t_steps, num_steps): """ @@ -46,12 +48,10 @@ def get_align_your_steps_sigmas(n, device, sigma_id): interped_ys = np.exp(new_ys)[::-1].copy() return interped_ys - if sigma_id == "sdxl": + if shared.sd_model.is_sdxl: sigmas = [14.615, 6.315, 3.771, 2.181, 1.342, 0.862, 0.555, 0.380, 0.234, 0.113, 0.029] - elif sigma_id == "sd15": - sigmas = [14.615, 6.475, 3.861, 2.697, 1.886, 1.396, 0.963, 0.652, 0.399, 0.152, 0.029] else: - print(f'Align Your Steps sigma identifier "{sigma_id}" not recognized, defaulting to SD 1.5.') + # Default to SD 1.5 sigmas. sigmas = [14.615, 6.475, 3.861, 2.697, 1.886, 1.396, 0.963, 0.652, 0.399, 0.152, 0.029] if n != len(sigmas): @@ -68,8 +68,7 @@ schedulers = [ Scheduler('exponential', 'Exponential', k_diffusion.sampling.get_sigmas_exponential), Scheduler('polyexponential', 'Polyexponential', k_diffusion.sampling.get_sigmas_polyexponential, default_rho=1.0), Scheduler('sgm_uniform', 'SGM Uniform', sgm_uniform, need_inner_model=True, aliases=["SGMUniform"]), - Scheduler('align_your_steps_sdxl', 'Align Your Steps (SDXL)', lambda n, sigma_min, sigma_max, device: get_align_your_steps_sigmas(n, device, "sdxl")), - Scheduler('align_your_steps_sd15', 'Align Your Steps (SD 1.5)', lambda n, sigma_min, sigma_max, device: get_align_your_steps_sigmas(n, device, "sd15")), + Scheduler('align_your_steps', 'Align Your Steps', get_align_your_steps_sigmas), ] schedulers_map = {**{x.name: x for x in schedulers}, **{x.label: x for x in schedulers}} From d44f241317d63095176543839bc111b731069629 Mon Sep 17 00:00:00 2001 From: w-e-w <40751091+w-e-w@users.noreply.github.com> Date: Sat, 11 May 2024 13:13:39 +0900 Subject: [PATCH 298/496] use relative path for webui-assets css --- style.css | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/style.css b/style.css index f6a89b8f..8eefda56 100644 --- a/style.css +++ b/style.css @@ -1,6 +1,6 @@ /* temporary fix to load default gradio font in frontend instead of backend */ -@import url('/webui-assets/css/sourcesanspro.css'); +@import url('webui-assets/css/sourcesanspro.css'); /* temporary fix to hide gradio crop tool until it's fixed https://github.com/gradio-app/gradio/issues/3810 */ From ef7713fbb29fed183d669a5a081cda9ac1a8b629 Mon Sep 17 00:00:00 2001 From: elf-mouse Date: Tue, 14 May 2024 15:39:05 +0800 Subject: [PATCH 299/496] chore: sync v1.8.0 packages according to changelog, fix warning --- webui-macos-env.sh | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/webui-macos-env.sh b/webui-macos-env.sh index db7e8b1a..4126005a 100644 --- a/webui-macos-env.sh +++ b/webui-macos-env.sh @@ -11,7 +11,7 @@ fi export install_dir="$HOME" export COMMANDLINE_ARGS="--skip-torch-cuda-test --upcast-sampling --no-half-vae --use-cpu interrogate" -export TORCH_COMMAND="pip install torch==2.1.0 torchvision==0.16.0" +export TORCH_COMMAND="pip install torch==2.1.2 torchvision==0.16.2" export PYTORCH_ENABLE_MPS_FALLBACK=1 #################################################################### From 5ab7d08a0a99c88a60a13885e564fd7d2d05cfc1 Mon Sep 17 00:00:00 2001 From: w-e-w <40751091+w-e-w@users.noreply.github.com> Date: Wed, 15 May 2024 17:27:05 +0900 Subject: [PATCH 300/496] fix extention update when not on main branch --- modules/extensions.py | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/modules/extensions.py b/modules/extensions.py index 5ad934b4..24de766e 100644 --- a/modules/extensions.py +++ b/modules/extensions.py @@ -191,8 +191,9 @@ class Extension: def check_updates(self): repo = Repo(self.path) + branch_name = f'{repo.remote().name}/{self.branch}' for fetch in repo.remote().fetch(dry_run=True): - if self.branch and fetch.name != f'{repo.remote().name}/{self.branch}': + if self.branch and fetch.name != branch_name: continue if fetch.flags != fetch.HEAD_UPTODATE: self.can_update = True @@ -200,7 +201,7 @@ class Extension: return try: - origin = repo.rev_parse('origin') + origin = repo.rev_parse(branch_name) if repo.head.commit != origin: self.can_update = True self.status = "behind HEAD" @@ -213,8 +214,10 @@ class Extension: self.can_update = False self.status = "latest" - def fetch_and_reset_hard(self, commit='origin'): + def fetch_and_reset_hard(self, commit=None): 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`, # because WSL2 Docker set 755 file permissions instead of 644, this results to the error. repo.git.fetch(all=True) From 022d835565f253841f7f9272ba320bb0cec4770d Mon Sep 17 00:00:00 2001 From: huchenlei Date: Wed, 15 May 2024 15:20:40 -0400 Subject: [PATCH 301/496] use_checkpoint = False --- configs/alt-diffusion-inference.yaml | 2 +- configs/alt-diffusion-m18-inference.yaml | 2 +- configs/instruct-pix2pix.yaml | 2 +- configs/sd_xl_inpaint.yaml | 2 +- configs/v1-inference.yaml | 2 +- configs/v1-inpainting-inference.yaml | 2 +- modules/sd_hijack_checkpoint.py | 9 ++++++--- modules/sd_models_config.py | 2 +- 8 files changed, 13 insertions(+), 10 deletions(-) diff --git a/configs/alt-diffusion-inference.yaml b/configs/alt-diffusion-inference.yaml index cfbee72d..4944ab5c 100644 --- a/configs/alt-diffusion-inference.yaml +++ b/configs/alt-diffusion-inference.yaml @@ -40,7 +40,7 @@ model: use_spatial_transformer: True transformer_depth: 1 context_dim: 768 - use_checkpoint: True + use_checkpoint: False legacy: False first_stage_config: diff --git a/configs/alt-diffusion-m18-inference.yaml b/configs/alt-diffusion-m18-inference.yaml index 41a031d5..c60dca8c 100644 --- a/configs/alt-diffusion-m18-inference.yaml +++ b/configs/alt-diffusion-m18-inference.yaml @@ -41,7 +41,7 @@ model: use_linear_in_transformer: True transformer_depth: 1 context_dim: 1024 - use_checkpoint: True + use_checkpoint: False legacy: False first_stage_config: diff --git a/configs/instruct-pix2pix.yaml b/configs/instruct-pix2pix.yaml index 4e896879..564e50ae 100644 --- a/configs/instruct-pix2pix.yaml +++ b/configs/instruct-pix2pix.yaml @@ -45,7 +45,7 @@ model: use_spatial_transformer: True transformer_depth: 1 context_dim: 768 - use_checkpoint: True + use_checkpoint: False legacy: False first_stage_config: diff --git a/configs/sd_xl_inpaint.yaml b/configs/sd_xl_inpaint.yaml index 3bad3721..f40f45e3 100644 --- a/configs/sd_xl_inpaint.yaml +++ b/configs/sd_xl_inpaint.yaml @@ -21,7 +21,7 @@ model: params: adm_in_channels: 2816 num_classes: sequential - use_checkpoint: True + use_checkpoint: False in_channels: 9 out_channels: 4 model_channels: 320 diff --git a/configs/v1-inference.yaml b/configs/v1-inference.yaml index d4effe56..25c4d9ed 100644 --- a/configs/v1-inference.yaml +++ b/configs/v1-inference.yaml @@ -40,7 +40,7 @@ model: use_spatial_transformer: True transformer_depth: 1 context_dim: 768 - use_checkpoint: True + use_checkpoint: False legacy: False first_stage_config: diff --git a/configs/v1-inpainting-inference.yaml b/configs/v1-inpainting-inference.yaml index f9eec37d..68c199f9 100644 --- a/configs/v1-inpainting-inference.yaml +++ b/configs/v1-inpainting-inference.yaml @@ -40,7 +40,7 @@ model: use_spatial_transformer: True transformer_depth: 1 context_dim: 768 - use_checkpoint: True + use_checkpoint: False legacy: False first_stage_config: diff --git a/modules/sd_hijack_checkpoint.py b/modules/sd_hijack_checkpoint.py index 2604d969..b2f05bbd 100644 --- a/modules/sd_hijack_checkpoint.py +++ b/modules/sd_hijack_checkpoint.py @@ -4,16 +4,19 @@ import ldm.modules.attention import ldm.modules.diffusionmodules.openaimodel +# Setting flag=False so that torch skips checking parameters. +# parameters checking is expensive in frequent operations. + def BasicTransformerBlock_forward(self, x, context=None): - return checkpoint(self._forward, x, context) + return checkpoint(self._forward, x, context, flag=False) def AttentionBlock_forward(self, x): - return checkpoint(self._forward, x) + return checkpoint(self._forward, x, flag=False) def ResBlock_forward(self, x, emb): - return checkpoint(self._forward, x, emb) + return checkpoint(self._forward, x, emb, flag=False) stored = [] diff --git a/modules/sd_models_config.py b/modules/sd_models_config.py index b38137eb..9cec4f13 100644 --- a/modules/sd_models_config.py +++ b/modules/sd_models_config.py @@ -35,7 +35,7 @@ def is_using_v_parameterization_for_sd2(state_dict): with sd_disable_initialization.DisableInitialization(): unet = ldm.modules.diffusionmodules.openaimodel.UNetModel( - use_checkpoint=True, + use_checkpoint=False, use_fp16=False, image_size=32, in_channels=4, From 0e98529365477a4f240b2ac67d94ff59235144c5 Mon Sep 17 00:00:00 2001 From: huchenlei Date: Wed, 15 May 2024 15:46:53 -0400 Subject: [PATCH 302/496] Replace einops.rearrange with torch native --- modules/sd_hijack_optimizations.py | 18 ++++++++++++++++-- 1 file changed, 16 insertions(+), 2 deletions(-) diff --git a/modules/sd_hijack_optimizations.py b/modules/sd_hijack_optimizations.py index 7f9e328d..4c2dc56d 100644 --- a/modules/sd_hijack_optimizations.py +++ b/modules/sd_hijack_optimizations.py @@ -486,7 +486,19 @@ def xformers_attention_forward(self, x, context=None, mask=None, **kwargs): k_in = self.to_k(context_k) 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)) + def _reshape(t): + """rearrange(t, 'b n (h d) -> b n h d', h=h). + Using torch native operations to avoid overhead as this function is + called frequently. (70 times/it for SDXL) + """ + b, n, _ = t.shape # Get the batch size (b) and sequence length (n) + d = t.shape[2] // h # Determine the depth per head + return t.reshape(b, n, h, d) + + q = _reshape(q_in) + k = _reshape(k_in) + v = _reshape(v_in) + del q_in, k_in, v_in dtype = q.dtype @@ -497,7 +509,9 @@ def xformers_attention_forward(self, x, context=None, mask=None, **kwargs): out = out.to(dtype) - out = rearrange(out, 'b n h d -> b n (h d)', h=h) + # 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) From 9eb2f786316c0f7e94c3df5f5e8bda203e6b875d Mon Sep 17 00:00:00 2001 From: huchenlei Date: Wed, 15 May 2024 16:32:29 -0400 Subject: [PATCH 303/496] Precompute is_sdxl_inpaint flag --- modules/processing.py | 28 +++++++++++----------------- modules/sd_models.py | 7 +++++++ modules/sd_models_xl.py | 9 ++++----- 3 files changed, 22 insertions(+), 22 deletions(-) diff --git a/modules/processing.py b/modules/processing.py index 76557dd7..d82cb24f 100644 --- a/modules/processing.py +++ b/modules/processing.py @@ -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) else: - sd = sd_model.model.state_dict() - diffusion_model_input = sd.get('diffusion_model.input_blocks.0.0.weight', None) - if diffusion_model_input is not None: - if diffusion_model_input.shape[1] == 9: - # The "masked-image" in this case will just be all 0.5 since the entire image is masked. - 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)) + if sd_model.model.is_sdxl_inpaint: + # The "masked-image" in this case will just be all 0.5 since the entire image is masked. + 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. - image_conditioning = torch.nn.functional.pad(image_conditioning, (0, 0, 0, 0, 1, 0), value=1.0) - image_conditioning = image_conditioning.to(x.dtype) + # 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 = image_conditioning.to(x.dtype) - return image_conditioning + return image_conditioning # Dummy zero conditioning if we're not using inpainting or unclip models. # Still takes up a bit of memory, but no encoder call. @@ -390,11 +387,8 @@ class StableDiffusionProcessing: if self.sampler.conditioning_key == "crossattn-adm": return self.unclip_image_conditioning(source_image) - sd = self.sampler.model_wrap.inner_model.model.state_dict() - diffusion_model_input = sd.get('diffusion_model.input_blocks.0.0.weight', None) - 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) + if self.sampler.model_wrap.inner_model.model.is_sdxl_inpaint: + 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. return latent_image.new_zeros(latent_image.shape[0], 5, 1, 1) diff --git a/modules/sd_models.py b/modules/sd_models.py index ff245b7a..62e74d27 100644 --- a/modules/sd_models.py +++ b/modules/sd_models.py @@ -380,6 +380,13 @@ def load_model_weights(model, checkpoint_info: CheckpointInfo, state_dict, timer model.is_sd2 = not model.is_sdxl and hasattr(model.cond_stage_model, 'model') model.is_sd1 = not model.is_sdxl and not model.is_sd2 model.is_ssd = model.is_sdxl and 'model.diffusion_model.middle_block.1.transformer_blocks.0.attn1.to_q.weight' not in state_dict.keys() + # Set is_sdxl_inpaint flag. + diffusion_model_input = state_dict.get('diffusion_model.input_blocks.0.0.weight', None) + model.is_sdxl_inpaint = ( + model.is_sdxl and + diffusion_model_input is not None and + diffusion_model_input.shape[1] == 9 + ) if model.is_sdxl: sd_models_xl.extend_sdxl(model) diff --git a/modules/sd_models_xl.py b/modules/sd_models_xl.py index 94ff973f..35e21f6e 100644 --- a/modules/sd_models_xl.py +++ b/modules/sd_models_xl.py @@ -35,11 +35,10 @@ def get_learned_conditioning(self: sgm.models.diffusion.DiffusionEngine, batch: def apply_model(self: sgm.models.diffusion.DiffusionEngine, x, t, cond): - sd = self.model.state_dict() - diffusion_model_input = sd.get('diffusion_model.input_blocks.0.0.weight', None) - if diffusion_model_input is not None: - if diffusion_model_input.shape[1] == 9: - x = torch.cat([x] + cond['c_concat'], dim=1) + """WARNING: This function is called once per denoising iteration. DO NOT add + expensive functionc calls such as `model.state_dict`. """ + if self.model.is_sdxl_inpaint: + x = torch.cat([x] + cond['c_concat'], dim=1) return self.model(x, t, cond) From 6a48476502d6cdd19cb3d0c7f2a0b92aacd7c01f Mon Sep 17 00:00:00 2001 From: huchenlei Date: Wed, 15 May 2024 16:54:26 -0400 Subject: [PATCH 304/496] Fix flag check for SD15 --- modules/processing.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/modules/processing.py b/modules/processing.py index d82cb24f..fff2595e 100644 --- a/modules/processing.py +++ b/modules/processing.py @@ -115,7 +115,7 @@ 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) else: - if sd_model.model.is_sdxl_inpaint: + if getattr(sd_model.model, "is_sdxl_inpaint", False): # The "masked-image" in this case will just be all 0.5 since the entire image is masked. image_conditioning = torch.ones(x.shape[0], 3, height, width, device=x.device) * 0.5 image_conditioning = images_tensor_to_samples(image_conditioning, @@ -387,7 +387,7 @@ class StableDiffusionProcessing: if self.sampler.conditioning_key == "crossattn-adm": return self.unclip_image_conditioning(source_image) - if self.sampler.model_wrap.inner_model.model.is_sdxl_inpaint: + if getattr(self.sampler.model_wrap.inner_model.model, "is_sdxl_inpaint", False): 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. From 3e20b36e8f1b26f24db0c149732fb5479bff68bc Mon Sep 17 00:00:00 2001 From: huchenlei Date: Wed, 15 May 2024 17:27:01 -0400 Subject: [PATCH 305/496] Fix attr access --- modules/sd_models_xl.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/modules/sd_models_xl.py b/modules/sd_models_xl.py index 35e21f6e..1242a593 100644 --- a/modules/sd_models_xl.py +++ b/modules/sd_models_xl.py @@ -37,7 +37,7 @@ def get_learned_conditioning(self: sgm.models.diffusion.DiffusionEngine, batch: def apply_model(self: sgm.models.diffusion.DiffusionEngine, x, t, cond): """WARNING: This function is called once per denoising iteration. DO NOT add expensive functionc calls such as `model.state_dict`. """ - if self.model.is_sdxl_inpaint: + if self.is_sdxl_inpaint: x = torch.cat([x] + cond['c_concat'], dim=1) return self.model(x, t, cond) From 9c8075ba8e538f695ef25f85e6513227b58b71ce Mon Sep 17 00:00:00 2001 From: w-e-w <40751091+w-e-w@users.noreply.github.com> Date: Thu, 16 May 2024 23:16:50 +0900 Subject: [PATCH 306/496] torch_utils.float64 return torch.float64 if device is not mps or xpu, else return torch.float32 --- modules/torch_utils.py | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/modules/torch_utils.py b/modules/torch_utils.py index e5b52393..a07e0285 100644 --- a/modules/torch_utils.py +++ b/modules/torch_utils.py @@ -1,6 +1,7 @@ from __future__ import annotations import torch.nn +import torch def get_param(model) -> torch.nn.Parameter: @@ -15,3 +16,11 @@ def get_param(model) -> torch.nn.Parameter: return param raise ValueError(f"No parameters found in model {model!r}") + + +def float64(t: torch.Tensor): + """return torch.float64 if device is not mps or xpu, else return torch.float32""" + match t.device.type: + case 'mps', 'xpu': + return torch.float32 + return torch.float64 From 41f66849c7feac1efd0b9eb6884209be382e9e74 Mon Sep 17 00:00:00 2001 From: w-e-w <40751091+w-e-w@users.noreply.github.com> Date: Thu, 16 May 2024 23:18:20 +0900 Subject: [PATCH 307/496] mps, xpu compatibility --- .../soft-inpainting/scripts/soft_inpainting.py | 9 ++++----- 1 file changed, 4 insertions(+), 5 deletions(-) diff --git a/extensions-builtin/soft-inpainting/scripts/soft_inpainting.py b/extensions-builtin/soft-inpainting/scripts/soft_inpainting.py index f56e1e22..0e629963 100644 --- a/extensions-builtin/soft-inpainting/scripts/soft_inpainting.py +++ b/extensions-builtin/soft-inpainting/scripts/soft_inpainting.py @@ -3,6 +3,7 @@ import gradio as gr import math from modules.ui_components import InputAccordion import modules.scripts as scripts +from modules.torch_utils import float64 class SoftInpaintingSettings: @@ -79,13 +80,11 @@ def latent_blend(settings, a, b, t): # Calculate the magnitude of the interpolated vectors. (We will remove this magnitude.) # 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). - a_magnitude = torch.norm(a, p=2, dim=1, keepdim=True).to(torch.float64).pow_( - settings.inpaint_detail_preservation) * one_minus_t3 - b_magnitude = torch.norm(b, p=2, dim=1, keepdim=True).to(torch.float64).pow_( - settings.inpaint_detail_preservation) * t3 + a_magnitude = torch.norm(a, p=2, dim=1, keepdim=True).to(float64(a)).pow_(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 desired_magnitude = a_magnitude desired_magnitude.add_(b_magnitude).pow_(1 / settings.inpaint_detail_preservation) del a_magnitude, b_magnitude, t3, one_minus_t3 From f015b94176d6df372ce153eddc018cb3b08c03ba Mon Sep 17 00:00:00 2001 From: w-e-w <40751091+w-e-w@users.noreply.github.com> Date: Thu, 16 May 2024 23:19:06 +0900 Subject: [PATCH 308/496] use torch_utils.float64 --- modules/sd_samplers_timesteps_impl.py | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/modules/sd_samplers_timesteps_impl.py b/modules/sd_samplers_timesteps_impl.py index 930a64af..84867d6e 100644 --- a/modules/sd_samplers_timesteps_impl.py +++ b/modules/sd_samplers_timesteps_impl.py @@ -5,13 +5,14 @@ import numpy as np from modules import shared from modules.models.diffusion.uni_pc import uni_pc +from modules.torch_utils import float64 @torch.no_grad() def ddim(model, x, timesteps, extra_args=None, callback=None, disable=None, eta=0.0): alphas_cumprod = model.inner_model.inner_model.alphas_cumprod alphas = alphas_cumprod[timesteps] - alphas_prev = alphas_cumprod[torch.nn.functional.pad(timesteps[:-1], pad=(1, 0))].to(torch.float64 if x.device.type != 'mps' and x.device.type != 'xpu' else torch.float32) + alphas_prev = alphas_cumprod[torch.nn.functional.pad(timesteps[:-1], pad=(1, 0))].to(float64(x)) sqrt_one_minus_alphas = torch.sqrt(1 - alphas) sigmas = eta * np.sqrt((1 - alphas_prev.cpu().numpy()) / (1 - alphas.cpu()) * (1 - alphas.cpu() / alphas_prev.cpu().numpy())) @@ -43,7 +44,7 @@ def ddim(model, x, timesteps, extra_args=None, callback=None, disable=None, eta= def plms(model, x, timesteps, extra_args=None, callback=None, disable=None): alphas_cumprod = model.inner_model.inner_model.alphas_cumprod alphas = alphas_cumprod[timesteps] - alphas_prev = alphas_cumprod[torch.nn.functional.pad(timesteps[:-1], pad=(1, 0))].to(torch.float64 if x.device.type != 'mps' and x.device.type != 'xpu' else torch.float32) + alphas_prev = alphas_cumprod[torch.nn.functional.pad(timesteps[:-1], pad=(1, 0))].to(float64(x)) sqrt_one_minus_alphas = torch.sqrt(1 - alphas) extra_args = {} if extra_args is None else extra_args From 51b13a8c54854104f1510956b920399226a932f1 Mon Sep 17 00:00:00 2001 From: huchenlei Date: Thu, 16 May 2024 11:39:01 -0400 Subject: [PATCH 309/496] Prevent uncessary bias backup --- extensions-builtin/Lora/networks.py | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/extensions-builtin/Lora/networks.py b/extensions-builtin/Lora/networks.py index 42b14dc2..360455f8 100644 --- a/extensions-builtin/Lora/networks.py +++ b/extensions-builtin/Lora/networks.py @@ -378,7 +378,10 @@ def network_apply_weights(self: Union[torch.nn.Conv2d, torch.nn.Linear, torch.nn self.network_weights_backup = weights_backup bias_backup = getattr(self, "network_bias_backup", None) - if bias_backup is None: + if bias_backup is None and wanted_names != (): + if current_names != (): + raise RuntimeError("no backup bias found and current bias are not unchanged") + 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) elif getattr(self, 'bias', None) is not None: From b2ae4490b9c225ff020941bcbf36c8975760deba Mon Sep 17 00:00:00 2001 From: huchenlei Date: Thu, 16 May 2024 14:45:00 -0400 Subject: [PATCH 310/496] Fix LoRA bias error --- extensions-builtin/Lora/networks.py | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/extensions-builtin/Lora/networks.py b/extensions-builtin/Lora/networks.py index 360455f8..aee4e9d9 100644 --- a/extensions-builtin/Lora/networks.py +++ b/extensions-builtin/Lora/networks.py @@ -379,15 +379,17 @@ def network_apply_weights(self: Union[torch.nn.Conv2d, torch.nn.Linear, torch.nn bias_backup = getattr(self, "network_bias_backup", None) if bias_backup is None and wanted_names != (): - if current_names != (): - raise RuntimeError("no backup bias found and current bias are not unchanged") - 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) elif getattr(self, 'bias', None) is not None: bias_backup = self.bias.to(devices.cpu, copy=True) else: 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 if current_names != wanted_names: From 221ac0b9abd2e39ccc6f1969a434f05dcd72b29a Mon Sep 17 00:00:00 2001 From: Andray Date: Thu, 16 May 2024 23:08:24 +0400 Subject: [PATCH 311/496] img2img batch upload method --- modules/img2img.py | 20 +++++++++++++++----- modules/ui.py | 31 ++++++++++++++++++++----------- 2 files changed, 35 insertions(+), 16 deletions(-) diff --git a/modules/img2img.py b/modules/img2img.py index a1d042c2..24f869f5 100644 --- a/modules/img2img.py +++ b/modules/img2img.py @@ -17,11 +17,14 @@ from modules.ui import plaintext_to_html 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() processing.fix_seed(p) - batch_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 if inpaint_mask_dir: @@ -146,7 +149,7 @@ def process_batch(p, input_dir, output_dir, inpaint_mask_dir, args, to_scale=Fal return batch_results -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, *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) is_batch = mode == 5 @@ -221,8 +224,15 @@ def img2img(id_task: str, request: gr.Request, mode: int, prompt: str, negative_ with closing(p): if is_batch: - 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 img2img_batch_source_type == "upload": + assert isinstance(img2img_batch_upload, list) and img2img_batch_upload + output_dir = "" + inpaint_mask_dir = "" + png_info_dir = img2img_batch_png_info_dir if not shared.cmd_opts.hide_ui_dir_config else "" + processed = process_batch(p, img2img_batch_upload, output_dir, inpaint_mask_dir, args, to_scale=selected_scale_tab == 1, scale_by=scale_by, use_png_info=img2img_batch_use_png_info, png_info_props=img2img_batch_png_info_props, png_info_dir=png_info_dir) + else: # "from dir" + assert not shared.cmd_opts.hide_ui_dir_config, "Launched with --hide-ui-dir-config, batch img2img disabled" + 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: processed = Processed(p, [], p.seed, "") diff --git a/modules/ui.py b/modules/ui.py index 403425f2..f3ac4236 100644 --- a/modules/ui.py +++ b/modules/ui.py @@ -566,18 +566,25 @@ def create_ui(): init_mask_inpaint = gr.Image(label="Mask", source="upload", interactive=True, type="pil", image_mode="RGBA", elem_id="img_inpaint_mask") with gr.TabItem('Batch', id='batch', elem_id="img2img_batch_tab") as tab_batch: - hidden = '
    Disabled when launched with --hide-ui-dir-config.' if shared.cmd_opts.hide_ui_dir_config else '' - gr.HTML( - "

    Process images in a directory on the same machine where the server is running." + - "
    Use an empty output directory to save pictures normally instead of writing to the output directory." + - f"
    Add inpaint batch mask directory to enable inpaint batch processing." - f"{hidden}

    " - ) - img2img_batch_input_dir = gr.Textbox(label="Input directory", **shared.hide_dirs, elem_id="img2img_batch_input_dir") - img2img_batch_output_dir = gr.Textbox(label="Output directory", **shared.hide_dirs, elem_id="img2img_batch_output_dir") - img2img_batch_inpaint_mask_dir = gr.Textbox(label="Inpaint batch mask directory (required for inpaint batch processing only)", **shared.hide_dirs, elem_id="img2img_batch_inpaint_mask_dir") + with gr.Tabs(elem_id="img2img_batch_source"): + img2img_batch_source_type = gr.Textbox(visible=False, value="upload") + with gr.TabItem('Upload', id='batch_upload', elem_id="img2img_batch_upload_tab") as tab_batch_upload: + img2img_batch_upload = gr.Files(label="Files", interactive=True, elem_id="img2img_batch_upload") + with gr.TabItem('From directory', id='batch_from_dir', elem_id="img2img_batch_from_dir_tab") as tab_batch_from_dir: + hidden = '
    Disabled when launched with --hide-ui-dir-config.' if shared.cmd_opts.hide_ui_dir_config else '' + gr.HTML( + "

    Process images in a directory on the same machine where the server is running." + + "
    Use an empty output directory to save pictures normally instead of writing to the output directory." + + f"
    Add inpaint batch mask directory to enable inpaint batch processing." + f"{hidden}

    " + ) + img2img_batch_input_dir = gr.Textbox(label="Input directory", **shared.hide_dirs, elem_id="img2img_batch_input_dir") + img2img_batch_output_dir = gr.Textbox(label="Output directory", **shared.hide_dirs, elem_id="img2img_batch_output_dir") + img2img_batch_inpaint_mask_dir = gr.Textbox(label="Inpaint batch mask directory (required for inpaint batch processing only)", **shared.hide_dirs, elem_id="img2img_batch_inpaint_mask_dir") + tab_batch_upload.select(fn=lambda: "upload", inputs=[], outputs=[img2img_batch_source_type]) + tab_batch_from_dir.select(fn=lambda: "from dir", inputs=[], outputs=[img2img_batch_source_type]) with gr.Accordion("PNG info", open=False): - img2img_batch_use_png_info = gr.Checkbox(label="Append png info to prompts", **shared.hide_dirs, elem_id="img2img_batch_use_png_info") + img2img_batch_use_png_info = gr.Checkbox(label="Append png info to prompts", elem_id="img2img_batch_use_png_info") img2img_batch_png_info_dir = gr.Textbox(label="PNG info directory", **shared.hide_dirs, placeholder="Leave empty to use input directory", elem_id="img2img_batch_png_info_dir") img2img_batch_png_info_props = gr.CheckboxGroup(["Prompt", "Negative prompt", "Seed", "CFG scale", "Sampler", "Steps", "Model hash"], label="Parameters to take from png info", info="Prompts from png info will be appended to prompts set in ui.") @@ -759,6 +766,8 @@ def create_ui(): img2img_batch_use_png_info, img2img_batch_png_info_props, img2img_batch_png_info_dir, + img2img_batch_source_type, + img2img_batch_upload, ] + custom_inputs, outputs=[ output_panel.gallery, From 58eec83a546b8d61500c7b801cb0bdbe7650f6a6 Mon Sep 17 00:00:00 2001 From: huchenlei Date: Thu, 16 May 2024 16:39:02 -0400 Subject: [PATCH 312/496] Fully prevent use_checkpoint --- modules/sd_models.py | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/modules/sd_models.py b/modules/sd_models.py index ff245b7a..a33fa7c3 100644 --- a/modules/sd_models.py +++ b/modules/sd_models.py @@ -551,6 +551,11 @@ def repair_config(sd_config): karlo_path = os.path.join(paths.models_path, 'karlo') sd_config.model.params.noise_aug_config.params.clip_stats_path = sd_config.model.params.noise_aug_config.params.clip_stats_path.replace("checkpoints/karlo_models", karlo_path) + # Do not use checkpoint for inference. + # This helps prevent extra performance overhead on checking parameters. + # The perf overhead is about 100ms/it on 4090. + sd_config.model.params.network_config.params.use_checkpoint = False + def rescale_zero_terminal_snr_abar(alphas_cumprod): alphas_bar_sqrt = alphas_cumprod.sqrt() From 2a8a60c2c50473f0ece5804d4a2cde0d1ff3d35e Mon Sep 17 00:00:00 2001 From: huchenlei Date: Thu, 16 May 2024 19:50:06 -0400 Subject: [PATCH 313/496] Add --precision half cmd option --- modules/cmd_args.py | 2 +- modules/devices.py | 24 ++++++++++++++++++++++++ modules/sd_hijack_unet.py | 29 ++++++++++++++++++++++------- modules/sd_hijack_utils.py | 26 +++++++++++++++----------- modules/sd_models.py | 1 + modules/shared_init.py | 8 ++++++++ 6 files changed, 71 insertions(+), 19 deletions(-) diff --git a/modules/cmd_args.py b/modules/cmd_args.py index 016a33d1..58c5e5d5 100644 --- a/modules/cmd_args.py +++ b/modules/cmd_args.py @@ -41,7 +41,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("--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("--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("--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) diff --git a/modules/devices.py b/modules/devices.py index e4f671ac..7de34ac5 100644 --- a/modules/devices.py +++ b/modules/devices.py @@ -114,6 +114,9 @@ errors.run(enable_tf32, "Enabling TF32") cpu: torch.device = torch.device("cpu") 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_interrogate: torch.device = None device_gfpgan: torch.device = None @@ -127,6 +130,8 @@ unet_needs_upcast = False def cond_cast_unet(input): + if force_fp16: + return input.to(torch.float16) return input.to(dtype_unet) if unet_needs_upcast else input @@ -206,6 +211,11 @@ def autocast(disable=False): if disable: 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: return torch.autocast("cpu", dtype=torch.bfloat16, enabled=True) @@ -269,3 +279,17 @@ def first_time_calculation(): x = torch.zeros((1, 1, 3, 3)).to(device, dtype) conv2d = torch.nn.Conv2d(1, 1, (3, 3)).to(device, dtype) 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`.") diff --git a/modules/sd_hijack_unet.py b/modules/sd_hijack_unet.py index 2101f1a0..41955313 100644 --- a/modules/sd_hijack_unet.py +++ b/modules/sd_hijack_unet.py @@ -36,7 +36,7 @@ th = TorchHijackForUnet() # Below are monkey patches to enable upcasting a float16 UNet for float32 sampling def apply_model(orig_func, self, x_noisy, t, cond, **kwargs): - + """Always make sure inputs to unet are in correct dtype.""" if isinstance(cond, dict): for y in cond.keys(): if isinstance(cond[y], list): @@ -45,7 +45,11 @@ def apply_model(orig_func, self, x_noisy, t, cond, **kwargs): cond[y] = cond[y].to(devices.dtype_unet) if isinstance(cond[y], torch.Tensor) else cond[y] with devices.autocast(): - return orig_func(self, x_noisy.to(devices.dtype_unet), t.to(devices.dtype_unet), cond, **kwargs).float() + result = orig_func(self, x_noisy.to(devices.dtype_unet), t.to(devices.dtype_unet), cond, **kwargs) + if devices.unet_needs_upcast: + return result.float() + else: + return result class GELUHijack(torch.nn.GELU, torch.nn.Module): @@ -64,12 +68,11 @@ def hijack_ddpm_edit(): if not ddpm_edit_hijack: CondFunc('modules.models.diffusion.ddpm_edit.LatentDiffusion.decode_first_stage', first_stage_sub, first_stage_cond) CondFunc('modules.models.diffusion.ddpm_edit.LatentDiffusion.encode_first_stage', first_stage_sub, first_stage_cond) - ddpm_edit_hijack = CondFunc('modules.models.diffusion.ddpm_edit.LatentDiffusion.apply_model', apply_model, unet_needs_upcast) + ddpm_edit_hijack = CondFunc('modules.models.diffusion.ddpm_edit.LatentDiffusion.apply_model', apply_model) unet_needs_upcast = lambda *args, **kwargs: devices.unet_needs_upcast -CondFunc('ldm.models.diffusion.ddpm.LatentDiffusion.apply_model', apply_model, unet_needs_upcast) -CondFunc('ldm.modules.diffusionmodules.openaimodel.timestep_embedding', lambda orig_func, timesteps, *args, **kwargs: orig_func(timesteps, *args, **kwargs).to(torch.float32 if timesteps.dtype == torch.int64 else devices.dtype_unet), unet_needs_upcast) + if version.parse(torch.__version__) <= version.parse("1.13.2") or torch.cuda.is_available(): CondFunc('ldm.modules.diffusionmodules.util.GroupNorm32.forward', lambda orig_func, self, *args, **kwargs: orig_func(self.float(), *args, **kwargs), unet_needs_upcast) CondFunc('ldm.modules.attention.GEGLU.forward', lambda orig_func, self, x: orig_func(self.float(), x.float()).to(devices.dtype_unet), unet_needs_upcast) @@ -81,5 +84,17 @@ CondFunc('ldm.models.diffusion.ddpm.LatentDiffusion.decode_first_stage', first_s CondFunc('ldm.models.diffusion.ddpm.LatentDiffusion.encode_first_stage', first_stage_sub, first_stage_cond) CondFunc('ldm.models.diffusion.ddpm.LatentDiffusion.get_first_stage_encoding', lambda orig_func, *args, **kwargs: orig_func(*args, **kwargs).float(), first_stage_cond) -CondFunc('sgm.modules.diffusionmodules.wrappers.OpenAIWrapper.forward', apply_model, unet_needs_upcast) -CondFunc('sgm.modules.diffusionmodules.openaimodel.timestep_embedding', lambda orig_func, timesteps, *args, **kwargs: orig_func(timesteps, *args, **kwargs).to(torch.float32 if timesteps.dtype == torch.int64 else devices.dtype_unet), unet_needs_upcast) +CondFunc('ldm.models.diffusion.ddpm.LatentDiffusion.apply_model', apply_model) +CondFunc('sgm.modules.diffusionmodules.wrappers.OpenAIWrapper.forward', apply_model) + + +def timestep_embedding_cast_result(orig_func, timesteps, *args, **kwargs): + if devices.unet_needs_upcast and timesteps.dtype == torch.int64: + dtype = torch.float32 + else: + dtype = devices.dtype_unet + return orig_func(timesteps, *args, **kwargs).to(dtype=dtype) + + +CondFunc('ldm.modules.diffusionmodules.openaimodel.timestep_embedding', timestep_embedding_cast_result) +CondFunc('sgm.modules.diffusionmodules.openaimodel.timestep_embedding', timestep_embedding_cast_result) diff --git a/modules/sd_hijack_utils.py b/modules/sd_hijack_utils.py index 79bf6e46..546f2eda 100644 --- a/modules/sd_hijack_utils.py +++ b/modules/sd_hijack_utils.py @@ -1,7 +1,11 @@ import importlib + +always_true_func = lambda *args, **kwargs: True + + class CondFunc: - def __new__(cls, orig_func, sub_func, cond_func): + def __new__(cls, orig_func, sub_func, cond_func=always_true_func): self = super(CondFunc, cls).__new__(cls) if isinstance(orig_func, str): func_path = orig_func.split('.') @@ -20,13 +24,13 @@ class CondFunc: print(f"Warning: Failed to resolve {orig_func} for CondFunc hijack") pass self.__init__(orig_func, sub_func, cond_func) - return lambda *args, **kwargs: self(*args, **kwargs) - def __init__(self, orig_func, sub_func, cond_func): - self.__orig_func = orig_func - self.__sub_func = sub_func - self.__cond_func = cond_func - def __call__(self, *args, **kwargs): - if not self.__cond_func or self.__cond_func(self.__orig_func, *args, **kwargs): - return self.__sub_func(self.__orig_func, *args, **kwargs) - else: - return self.__orig_func(*args, **kwargs) + return lambda *args, **kwargs: self(*args, **kwargs) + def __init__(self, orig_func, sub_func, cond_func): + self.__orig_func = orig_func + self.__sub_func = sub_func + self.__cond_func = cond_func + def __call__(self, *args, **kwargs): + if not self.__cond_func or self.__cond_func(self.__orig_func, *args, **kwargs): + return self.__sub_func(self.__orig_func, *args, **kwargs) + else: + return self.__orig_func(*args, **kwargs) diff --git a/modules/sd_models.py b/modules/sd_models.py index ff245b7a..9c590916 100644 --- a/modules/sd_models.py +++ b/modules/sd_models.py @@ -403,6 +403,7 @@ def load_model_weights(model, checkpoint_info: CheckpointInfo, state_dict, timer model.float() model.alphas_cumprod_original = model.alphas_cumprod devices.dtype_unet = torch.float32 + assert shared.cmd_opts.precision != "half", "Cannot use --precision half with --no-half" timer.record("apply float()") else: vae = model.first_stage_model diff --git a/modules/shared_init.py b/modules/shared_init.py index 935e3a21..a6ad0433 100644 --- a/modules/shared_init.py +++ b/modules/shared_init.py @@ -31,6 +31,14 @@ def initialize(): devices.dtype_vae = torch.float32 if cmd_opts.no_half or cmd_opts.no_half_vae else torch.float16 devices.dtype_inference = torch.float32 if cmd_opts.precision == 'full' else devices.dtype + if cmd_opts.precision == "half": + msg = "--no-half and --no-half-vae conflict with --precision half" + assert devices.dtype == torch.float16, msg + assert devices.dtype_vae == torch.float16, msg + assert devices.dtype_inference == torch.float16, msg + devices.force_fp16 = True + devices.force_model_fp16() + shared.device = devices.device shared.weight_load_location = None if cmd_opts.lowram else "cpu" From 47f1d42a7e77259e2e7418ae8f941718c55cfd25 Mon Sep 17 00:00:00 2001 From: huchenlei Date: Thu, 16 May 2024 20:06:04 -0400 Subject: [PATCH 314/496] Fix for SD15 models --- modules/sd_models.py | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/modules/sd_models.py b/modules/sd_models.py index a33fa7c3..cda142bd 100644 --- a/modules/sd_models.py +++ b/modules/sd_models.py @@ -553,8 +553,11 @@ def repair_config(sd_config): # Do not use checkpoint for inference. # This helps prevent extra performance overhead on checking parameters. - # The perf overhead is about 100ms/it on 4090. - sd_config.model.params.network_config.params.use_checkpoint = False + # The perf overhead is about 100ms/it on 4090 for SDXL. + if hasattr(sd_config.model.params, "network_config"): + sd_config.model.params.network_config.params.use_checkpoint = False + if hasattr(sd_config.model.params, "unet_config"): + sd_config.model.params.unet_config.params.use_checkpoint = False def rescale_zero_terminal_snr_abar(alphas_cumprod): From 01491d303ce216820513c5cee998801359b8cbba Mon Sep 17 00:00:00 2001 From: drhead <1313496+drhead@users.noreply.github.com> Date: Fri, 17 May 2024 10:36:08 -0400 Subject: [PATCH 315/496] Keep sigmas on CPU --- modules/sd_samplers_kdiffusion.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/modules/sd_samplers_kdiffusion.py b/modules/sd_samplers_kdiffusion.py index b45f85b0..228de494 100644 --- a/modules/sd_samplers_kdiffusion.py +++ b/modules/sd_samplers_kdiffusion.py @@ -115,7 +115,7 @@ class KDiffusionSampler(sd_samplers_common.Sampler): if scheduler.need_inner_model: sigmas_kwargs['inner_model'] = self.model_wrap - sigmas = scheduler.function(n=steps, **sigmas_kwargs, device=shared.device) + sigmas = scheduler.function(n=steps, **sigmas_kwargs) if discard_next_to_last_sigma: sigmas = torch.cat([sigmas[:-2], sigmas[-1:]]) From 10f2407f48fa3a8bbd299068e5f67108f272b87d Mon Sep 17 00:00:00 2001 From: w-e-w <40751091+w-e-w@users.noreply.github.com> Date: Sat, 18 May 2024 00:44:02 +0900 Subject: [PATCH 316/496] xyz csv skipinitialspace --- scripts/xyz_grid.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/scripts/xyz_grid.py b/scripts/xyz_grid.py index b9fd66fe..d416e4c0 100644 --- a/scripts/xyz_grid.py +++ b/scripts/xyz_grid.py @@ -212,7 +212,7 @@ def list_to_csv_string(data_list): def csv_string_to_list_strip(data_str): - return list(map(str.strip, chain.from_iterable(csv.reader(StringIO(data_str))))) + return list(map(str.strip, chain.from_iterable(csv.reader(StringIO(data_str), skipinitialspace=True)))) class AxisOption: From 53d67088ee0fb190c3ae1330c2b876dedb16dd8b Mon Sep 17 00:00:00 2001 From: drhead <1313496+drhead@users.noreply.github.com> Date: Fri, 17 May 2024 12:12:57 -0400 Subject: [PATCH 317/496] Patch timestep embedding to create tensor on-device --- modules/sd_hijack_unet.py | 27 +++++++++++++++++++++++++++ 1 file changed, 27 insertions(+) diff --git a/modules/sd_hijack_unet.py b/modules/sd_hijack_unet.py index 2101f1a0..0dabbe0e 100644 --- a/modules/sd_hijack_unet.py +++ b/modules/sd_hijack_unet.py @@ -1,5 +1,7 @@ import torch from packaging import version +from einops import repeat +import math from modules import devices from modules.sd_hijack_utils import CondFunc @@ -48,6 +50,30 @@ def apply_model(orig_func, self, x_noisy, t, cond, **kwargs): return orig_func(self, x_noisy.to(devices.dtype_unet), t.to(devices.dtype_unet), cond, **kwargs).float() +# Monkey patch to create timestep embed tensor on device, avoiding a block. +def timestep_embedding(_, timesteps, dim, max_period=10000, repeat_only=False): + """ + Create sinusoidal timestep embeddings. + :param timesteps: 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 x dim] Tensor of positional embeddings. + """ + if not repeat_only: + half = dim // 2 + freqs = torch.exp( + -math.log(max_period) * torch.arange(start=0, end=half, dtype=torch.float32, device=timesteps.device) / half + ) + args = timesteps[:, 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) + else: + embedding = repeat(timesteps, 'b -> b d', d=dim) + return embedding + + class GELUHijack(torch.nn.GELU, torch.nn.Module): def __init__(self, *args, **kwargs): torch.nn.GELU.__init__(self, *args, **kwargs) @@ -69,6 +95,7 @@ def hijack_ddpm_edit(): unet_needs_upcast = lambda *args, **kwargs: devices.unet_needs_upcast CondFunc('ldm.models.diffusion.ddpm.LatentDiffusion.apply_model', apply_model, unet_needs_upcast) +CondFunc('ldm.modules.diffusionmodules.openaimodel.timestep_embedding', timestep_embedding) CondFunc('ldm.modules.diffusionmodules.openaimodel.timestep_embedding', lambda orig_func, timesteps, *args, **kwargs: orig_func(timesteps, *args, **kwargs).to(torch.float32 if timesteps.dtype == torch.int64 else devices.dtype_unet), unet_needs_upcast) if version.parse(torch.__version__) <= version.parse("1.13.2") or torch.cuda.is_available(): CondFunc('ldm.modules.diffusionmodules.util.GroupNorm32.forward', lambda orig_func, self, *args, **kwargs: orig_func(self.float(), *args, **kwargs), unet_needs_upcast) From cc9ca67664ef72931af9a4dced88a8434c5d4f16 Mon Sep 17 00:00:00 2001 From: drhead <1313496+drhead@users.noreply.github.com> Date: Fri, 17 May 2024 13:14:26 -0400 Subject: [PATCH 318/496] Add transformer forward patch --- modules/sd_hijack_unet.py | 27 ++++++++++++++++++++++++++- 1 file changed, 26 insertions(+), 1 deletion(-) diff --git a/modules/sd_hijack_unet.py b/modules/sd_hijack_unet.py index 0dabbe0e..c680367e 100644 --- a/modules/sd_hijack_unet.py +++ b/modules/sd_hijack_unet.py @@ -74,6 +74,30 @@ def timestep_embedding(_, timesteps, dim, max_period=10000, repeat_only=False): return embedding +# Monkey patch to SpatialTransformer removing unnecessary contiguous calls. +# Prevents a lot of unnecessary aten::copy_ calls +def spatial_transformer_forward(_, self, x: torch.Tensor, context=None): + # note: if no context is given, cross-attention defaults to self-attention + if not isinstance(context, list): + context = [context] + b, c, h, w = x.shape + x_in = x + x = self.norm(x) + if not self.use_linear: + x = self.proj_in(x) + x = x.permute(0, 2, 3, 1).reshape(b, h * w, c) + if self.use_linear: + x = self.proj_in(x) + for i, block in enumerate(self.transformer_blocks): + x = block(x, context=context[i]) + if self.use_linear: + x = self.proj_out(x) + x = x.view(b, h, w, c).permute(0, 3, 1, 2) + if not self.use_linear: + x = self.proj_out(x) + return x + x_in + + class GELUHijack(torch.nn.GELU, torch.nn.Module): def __init__(self, *args, **kwargs): torch.nn.GELU.__init__(self, *args, **kwargs) @@ -95,7 +119,8 @@ def hijack_ddpm_edit(): unet_needs_upcast = lambda *args, **kwargs: devices.unet_needs_upcast CondFunc('ldm.models.diffusion.ddpm.LatentDiffusion.apply_model', apply_model, unet_needs_upcast) -CondFunc('ldm.modules.diffusionmodules.openaimodel.timestep_embedding', timestep_embedding) +CondFunc('ldm.modules.diffusionmodules.openaimodel.timestep_embedding', timestep_embedding, lambda *args, **kwargs: True) +CondFunc('ldm.modules.attention.SpatialTransformer.forward', spatial_transformer_forward, lambda *args, **kwargs: True) CondFunc('ldm.modules.diffusionmodules.openaimodel.timestep_embedding', lambda orig_func, timesteps, *args, **kwargs: orig_func(timesteps, *args, **kwargs).to(torch.float32 if timesteps.dtype == torch.int64 else devices.dtype_unet), unet_needs_upcast) if version.parse(torch.__version__) <= version.parse("1.13.2") or torch.cuda.is_available(): CondFunc('ldm.modules.diffusionmodules.util.GroupNorm32.forward', lambda orig_func, self, *args, **kwargs: orig_func(self.float(), *args, **kwargs), unet_needs_upcast) From dca9007ac7a9852752d91d34d2ed1feaef6a03f2 Mon Sep 17 00:00:00 2001 From: huchenlei Date: Fri, 17 May 2024 13:23:12 -0400 Subject: [PATCH 319/496] Fix SD15 dtype --- modules/sd_models.py | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/modules/sd_models.py b/modules/sd_models.py index 9c590916..7d4ab0fd 100644 --- a/modules/sd_models.py +++ b/modules/sd_models.py @@ -733,6 +733,10 @@ def load_model(checkpoint_info=None, already_loaded_state_dict=None): sd_model = instantiate_from_config(sd_config.model) sd_model.used_config = checkpoint_config + # ldm's Unet is using self.dtype to cast input tensor. If we do not overwrite + # UnetModel.dtype, it will be the default dtype from config. + # sgm's Unet is not using dtype for casting. The value will be ignored. + sd_model.model.diffusion_model.dtype = devices.dtype_unet timer.record("create model") From b57a70f37322142939f7429f287599e027108bfc Mon Sep 17 00:00:00 2001 From: huchenlei Date: Fri, 17 May 2024 13:34:04 -0400 Subject: [PATCH 320/496] Proper fix of SD15 dtype --- modules/sd_models.py | 6 +----- 1 file changed, 1 insertion(+), 5 deletions(-) diff --git a/modules/sd_models.py b/modules/sd_models.py index 7d4ab0fd..26a5127c 100644 --- a/modules/sd_models.py +++ b/modules/sd_models.py @@ -541,7 +541,7 @@ def repair_config(sd_config): if hasattr(sd_config.model.params, 'unet_config'): if shared.cmd_opts.no_half: sd_config.model.params.unet_config.params.use_fp16 = False - elif shared.cmd_opts.upcast_sampling: + elif shared.cmd_opts.upcast_sampling or shared.cmd_opts.precision == "half": sd_config.model.params.unet_config.params.use_fp16 = True if getattr(sd_config.model.params.first_stage_config.params.ddconfig, "attn_type", None) == "vanilla-xformers" and not shared.xformers_available: @@ -733,10 +733,6 @@ def load_model(checkpoint_info=None, already_loaded_state_dict=None): sd_model = instantiate_from_config(sd_config.model) sd_model.used_config = checkpoint_config - # ldm's Unet is using self.dtype to cast input tensor. If we do not overwrite - # UnetModel.dtype, it will be the default dtype from config. - # sgm's Unet is not using dtype for casting. The value will be ignored. - sd_model.model.diffusion_model.dtype = devices.dtype_unet timer.record("create model") From 1d7448281751ea3223c681a82de8219a6fbe1d22 Mon Sep 17 00:00:00 2001 From: Logan Date: Sat, 18 May 2024 09:09:57 +1000 Subject: [PATCH 321/496] 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 --- modules/sd_schedulers.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/modules/sd_schedulers.py b/modules/sd_schedulers.py index 0ac1f7a2..4ddb7785 100644 --- a/modules/sd_schedulers.py +++ b/modules/sd_schedulers.py @@ -33,7 +33,7 @@ def sgm_uniform(n, sigma_min, sigma_max, inner_model, device): sigs += [0.0] return torch.FloatTensor(sigs).to(device) -def get_align_your_steps_sigmas(n, sigma_min, sigma_max, device): +def get_align_your_steps_sigmas(n, sigma_min, sigma_max, device='cpu'): # https://research.nvidia.com/labs/toronto-ai/AlignYourSteps/howto.html def loglinear_interp(t_steps, num_steps): """ From 281e0a007b102c7fc9f6150fb88c95470dc25a17 Mon Sep 17 00:00:00 2001 From: Andray Date: Sat, 18 May 2024 09:13:16 +0400 Subject: [PATCH 322/496] scroll extensions table on overflow --- style.css | 2 ++ 1 file changed, 2 insertions(+) diff --git a/style.css b/style.css index f6a89b8f..5ec803a0 100644 --- a/style.css +++ b/style.css @@ -807,6 +807,8 @@ table.popup-table .link{ #tab_extensions table{ border-collapse: collapse; + overflow-x: auto; + display: block; } #tab_extensions table td, #tab_extensions table th{ From feeb6802aa71fad190da2e051e50af84a94eda85 Mon Sep 17 00:00:00 2001 From: drhead <1313496+drhead@users.noreply.github.com> Date: Sat, 18 May 2024 01:22:31 -0400 Subject: [PATCH 323/496] fix case where first step skilled if skip early cond is 0 --- modules/sd_samplers_cfg_denoiser.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/modules/sd_samplers_cfg_denoiser.py b/modules/sd_samplers_cfg_denoiser.py index 082a4f63..d89ea2c8 100644 --- a/modules/sd_samplers_cfg_denoiser.py +++ b/modules/sd_samplers_cfg_denoiser.py @@ -212,7 +212,7 @@ class CFGDenoiser(torch.nn.Module): uncond = denoiser_params.text_uncond skip_uncond = False - if self.step / self.total_steps <= shared.opts.skip_early_cond: + if shared.opts.skip_early_cond != 0. and self.step / self.total_steps <= shared.opts.skip_early_cond: skip_uncond = True x_in = x_in[:-batch_size] sigma_in = sigma_in[:-batch_size] From 501ac016da8c28ff4778219f142f0622083237ce Mon Sep 17 00:00:00 2001 From: w-e-w <40751091+w-e-w@users.noreply.github.com> Date: Sat, 18 May 2024 18:37:37 +0900 Subject: [PATCH 324/496] Reformat --- scripts/xyz_grid.py | 16 +++++++++------- 1 file changed, 9 insertions(+), 7 deletions(-) diff --git a/scripts/xyz_grid.py b/scripts/xyz_grid.py index b9fd66fe..b23fd477 100644 --- a/scripts/xyz_grid.py +++ b/scripts/xyz_grid.py @@ -162,12 +162,14 @@ def apply_override(field, boolean: bool = False): if boolean: x = True if x.lower() == "true" else False p.override_settings[field] = x + return fun def boolean_choice(reverse: bool = False): def choice(): return ["False", "True"] if reverse else ["True", "False"] + return choice @@ -572,7 +574,7 @@ class Script(scripts.Script): mc = re_range_count.fullmatch(val) if m is not None: start = int(m.group(1)) - end = int(m.group(2))+1 + end = int(m.group(2)) + 1 step = int(m.group(3)) if m.group(3) is not None else 1 valslist_ext += list(range(start, end, step)) @@ -725,11 +727,11 @@ class Script(scripts.Script): ydim = len(ys) if vary_seeds_y else 1 if vary_seeds_x: - pc.seed += ix + pc.seed += ix if vary_seeds_y: - pc.seed += iy * xdim + pc.seed += iy * xdim if vary_seeds_z: - pc.seed += iz * xdim * ydim + pc.seed += iz * xdim * ydim try: res = process_images(pc) @@ -797,18 +799,18 @@ class Script(scripts.Script): z_count = len(zs) # Set the grid infotexts to the real ones with extra_generation_params (1 main grid + z_count sub-grids) - processed.infotexts[:1+z_count] = grid_infotext[:1+z_count] + processed.infotexts[:1 + z_count] = grid_infotext[:1 + z_count] if not include_lone_images: # Don't need sub-images anymore, drop from list: - processed.images = processed.images[:z_count+1] + processed.images = processed.images[:z_count + 1] if opts.grid_save: # Auto-save main and sub-grids: grid_count = z_count + 1 if z_count > 1 else 1 for g in range(grid_count): # TODO: See previous comment about intentional data misalignment. - adj_g = g-1 if g > 0 else g + adj_g = g - 1 if g > 0 else g images.save_image(processed.images[g], p.outpath_grids, "xyz_grid", info=processed.infotexts[g], extension=opts.grid_format, prompt=processed.all_prompts[adj_g], seed=processed.all_seeds[adj_g], grid=True, p=processed) if not include_sub_grids: # if not include_sub_grids then skip saving after the first grid break From 969a462ac9ea52eb61b8de9fd685cc477c8b8dac Mon Sep 17 00:00:00 2001 From: w-e-w <40751091+w-e-w@users.noreply.github.com> Date: Sat, 18 May 2024 18:27:34 +0900 Subject: [PATCH 325/496] xyz util confirm_range --- scripts/xyz_grid.py | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/scripts/xyz_grid.py b/scripts/xyz_grid.py index b23fd477..81c7abe9 100644 --- a/scripts/xyz_grid.py +++ b/scripts/xyz_grid.py @@ -95,6 +95,17 @@ def confirm_checkpoints_or_none(p, xs): raise RuntimeError(f"Unknown checkpoint: {x}") +def confirm_range(min_val, max_val, axis_label): + """Generates a AxisOption.confirm() function that checks all values are within the specified range.""" + + def confirm_range_fun(p, xs): + for x in xs: + if not (max_val >= x >= min_val): + raise ValueError(f'{axis_label} value "{x}" out of range [{min_val}, {max_val}]') + + return confirm_range_fun + + def apply_clip_skip(p, x, xs): opts.data["CLIP_stop_at_last_layers"] = x From 24a59ad3d2f9f44130746fdfe54f9f51ba74e77f Mon Sep 17 00:00:00 2001 From: w-e-w <40751091+w-e-w@users.noreply.github.com> Date: Sat, 18 May 2024 15:36:49 +0900 Subject: [PATCH 326/496] fix Hypertile xyz grid --- .../hypertile/scripts/hypertile_script.py | 17 ++++++- .../hypertile/scripts/hypertile_xyz.py | 51 ------------------- 2 files changed, 15 insertions(+), 53 deletions(-) delete mode 100644 extensions-builtin/hypertile/scripts/hypertile_xyz.py diff --git a/extensions-builtin/hypertile/scripts/hypertile_script.py b/extensions-builtin/hypertile/scripts/hypertile_script.py index 395d584b..59e7f990 100644 --- a/extensions-builtin/hypertile/scripts/hypertile_script.py +++ b/extensions-builtin/hypertile/scripts/hypertile_script.py @@ -1,6 +1,5 @@ import hypertile from modules import scripts, script_callbacks, shared -from scripts.hypertile_xyz import add_axis_options 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_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_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_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) +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_before_ui(add_axis_options) diff --git a/extensions-builtin/hypertile/scripts/hypertile_xyz.py b/extensions-builtin/hypertile/scripts/hypertile_xyz.py deleted file mode 100644 index 9e96ae3c..00000000 --- a/extensions-builtin/hypertile/scripts/hypertile_xyz.py +++ /dev/null @@ -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) From 82884da18c8f183c4ce0e7237953303f26610370 Mon Sep 17 00:00:00 2001 From: w-e-w <40751091+w-e-w@users.noreply.github.com> Date: Sun, 19 May 2024 04:55:45 +0900 Subject: [PATCH 327/496] use apply_override for Clip skip --- scripts/xyz_grid.py | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/scripts/xyz_grid.py b/scripts/xyz_grid.py index b9fd66fe..c7cb5133 100644 --- a/scripts/xyz_grid.py +++ b/scripts/xyz_grid.py @@ -264,7 +264,7 @@ axis_options = [ AxisOption("Schedule max sigma", float, apply_override("sigma_max")), AxisOption("Schedule rho", float, apply_override("rho")), AxisOption("Eta", float, apply_field("eta")), - AxisOption("Clip skip", int, apply_clip_skip), + AxisOption("Clip skip", int, apply_override('CLIP_stop_at_last_layers')), AxisOption("Denoising", float, apply_field("denoising_strength")), AxisOption("Initial noise multiplier", float, apply_field("initial_noise_multiplier")), AxisOption("Extra noise", float, apply_override("img2img_extra_noise")), @@ -399,7 +399,6 @@ def draw_xyz_grid(p, xs, ys, zs, x_labels, y_labels, z_labels, cell, draw_legend class SharedSettingsStackHelper(object): def __enter__(self): - self.CLIP_stop_at_last_layers = opts.CLIP_stop_at_last_layers self.vae = opts.sd_vae self.uni_pc_order = opts.uni_pc_order @@ -409,8 +408,6 @@ class SharedSettingsStackHelper(object): modules.sd_models.reload_model_weights() modules.sd_vae.reload_vae_weights() - opts.data["CLIP_stop_at_last_layers"] = self.CLIP_stop_at_last_layers - re_range = re.compile(r"\s*([+-]?\s*\d+)\s*-\s*([+-]?\s*\d+)(?:\s*\(([+-]\d+)\s*\))?\s*") re_range_float = re.compile(r"\s*([+-]?\s*\d+(?:.\d*)?)\s*-\s*([+-]?\s*\d+(?:.\d*)?)(?:\s*\(([+-]\d+(?:.\d*)?)\s*\))?\s*") From 1f392517f8938e0082e189fa0c28f4eb89fb0eb2 Mon Sep 17 00:00:00 2001 From: w-e-w <40751091+w-e-w@users.noreply.github.com> Date: Sun, 19 May 2024 04:59:05 +0900 Subject: [PATCH 328/496] use override for uni_pc_order --- scripts/xyz_grid.py | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/scripts/xyz_grid.py b/scripts/xyz_grid.py index c7cb5133..622cc43c 100644 --- a/scripts/xyz_grid.py +++ b/scripts/xyz_grid.py @@ -140,7 +140,7 @@ def apply_styles(p: StableDiffusionProcessingTxt2Img, x: str, _): def apply_uni_pc_order(p, x, xs): - opts.data["uni_pc_order"] = min(x, p.steps - 1) + p.override_settings['uni_pc_order'] = min(x, p.steps - 1) def apply_face_restore(p, opt, x): @@ -400,11 +400,9 @@ def draw_xyz_grid(p, xs, ys, zs, x_labels, y_labels, z_labels, cell, draw_legend class SharedSettingsStackHelper(object): def __enter__(self): self.vae = opts.sd_vae - self.uni_pc_order = opts.uni_pc_order def __exit__(self, exc_type, exc_value, tb): opts.data["sd_vae"] = self.vae - opts.data["uni_pc_order"] = self.uni_pc_order modules.sd_models.reload_model_weights() modules.sd_vae.reload_vae_weights() From 1e696b028adbd449df8c30ed760103b120ec5546 Mon Sep 17 00:00:00 2001 From: w-e-w <40751091+w-e-w@users.noreply.github.com> Date: Sun, 19 May 2024 05:14:32 +0900 Subject: [PATCH 329/496] use override of sd_vae --- scripts/xyz_grid.py | 24 +++++++++--------------- 1 file changed, 9 insertions(+), 15 deletions(-) diff --git a/scripts/xyz_grid.py b/scripts/xyz_grid.py index 622cc43c..4c83e92b 100644 --- a/scripts/xyz_grid.py +++ b/scripts/xyz_grid.py @@ -118,21 +118,16 @@ def apply_size(p, x: str, xs) -> None: def find_vae(name: str): - if name.lower() in ['auto', 'automatic']: - return modules.sd_vae.unspecified - if name.lower() == 'none': - return None - else: - choices = [x for x in sorted(modules.sd_vae.vae_dict, key=lambda x: len(x)) if name.lower().strip() in x.lower()] - if len(choices) == 0: - print(f"No VAE found for {name}; using automatic") - return modules.sd_vae.unspecified - else: - return modules.sd_vae.vae_dict[choices[0]] + match name := name.lower().strip(): + case 'auto', 'automatic': + return 'Automatic' + case 'none': + return 'None' + return next((k for k in modules.sd_vae.vae_dict if k.lower() == name), print(f'No VAE found for {name}; using Automatic') or 'Automatic') def apply_vae(p, x, xs): - modules.sd_vae.reload_vae_weights(shared.sd_model, vae_file=find_vae(x)) + p.override_settings['sd_vae'] = find_vae(x) def apply_styles(p: StableDiffusionProcessingTxt2Img, x: str, _): @@ -270,7 +265,7 @@ axis_options = [ AxisOption("Extra noise", float, apply_override("img2img_extra_noise")), AxisOptionTxt2Img("Hires upscaler", str, apply_field("hr_upscaler"), choices=lambda: [*shared.latent_upscale_modes, *[x.name for x in shared.sd_upscalers]]), AxisOptionImg2Img("Cond. Image Mask Weight", float, apply_field("inpainting_mask_weight")), - AxisOption("VAE", str, apply_vae, cost=0.7, choices=lambda: ['None'] + list(sd_vae.vae_dict)), + AxisOption("VAE", str, apply_vae, cost=0.7, choices=lambda: ['Automatic', 'None'] + list(sd_vae.vae_dict)), AxisOption("Styles", str, apply_styles, choices=lambda: list(shared.prompt_styles.styles)), AxisOption("UniPC Order", int, apply_uni_pc_order, cost=0.5), AxisOption("Face restore", str, apply_face_restore, format_value=format_value), @@ -399,10 +394,9 @@ def draw_xyz_grid(p, xs, ys, zs, x_labels, y_labels, z_labels, cell, draw_legend class SharedSettingsStackHelper(object): def __enter__(self): - self.vae = opts.sd_vae + pass def __exit__(self, exc_type, exc_value, tb): - opts.data["sd_vae"] = self.vae modules.sd_models.reload_model_weights() modules.sd_vae.reload_vae_weights() From 51e7122f25c276b258a8f55a64e60e5b2265287f Mon Sep 17 00:00:00 2001 From: w-e-w <40751091+w-e-w@users.noreply.github.com> Date: Sun, 19 May 2024 05:17:44 +0900 Subject: [PATCH 330/496] remove unused code --- scripts/xyz_grid.py | 11 ----------- 1 file changed, 11 deletions(-) diff --git a/scripts/xyz_grid.py b/scripts/xyz_grid.py index 4c83e92b..23dafd47 100644 --- a/scripts/xyz_grid.py +++ b/scripts/xyz_grid.py @@ -95,17 +95,6 @@ def confirm_checkpoints_or_none(p, xs): raise RuntimeError(f"Unknown checkpoint: {x}") -def apply_clip_skip(p, x, xs): - opts.data["CLIP_stop_at_last_layers"] = x - - -def apply_upscale_latent_space(p, x, xs): - if x.lower().strip() != '0': - opts.data["use_scale_latent_for_hires_fix"] = True - else: - opts.data["use_scale_latent_for_hires_fix"] = False - - def apply_size(p, x: str, xs) -> None: try: width, _, height = x.partition('x') From 5867be2914c303c2f8ba86ff23dba4b31aeafa79 Mon Sep 17 00:00:00 2001 From: viking1304 Date: Mon, 20 May 2024 23:44:17 +0200 Subject: [PATCH 331/496] Use different torch versions for Intel and ARM Macs --- webui-macos-env.sh | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/webui-macos-env.sh b/webui-macos-env.sh index db7e8b1a..ad073637 100644 --- a/webui-macos-env.sh +++ b/webui-macos-env.sh @@ -11,7 +11,12 @@ fi export install_dir="$HOME" export COMMANDLINE_ARGS="--skip-torch-cuda-test --upcast-sampling --no-half-vae --use-cpu interrogate" -export TORCH_COMMAND="pip install torch==2.1.0 torchvision==0.16.0" export PYTORCH_ENABLE_MPS_FALLBACK=1 +if [[ "$(sysctl -n machdep.cpu.brand_string)" =~ ^.*"Intel".*$ ]]; then + export TORCH_COMMAND="pip install torch==2.1.2 torchvision==0.16.2" +else + export TORCH_COMMAND="pip install torch==2.3.0 torchvision==0.18.0" +fi + #################################################################### From 344eda55d4550e91b1a3e95f8e669084a74c876f Mon Sep 17 00:00:00 2001 From: w-e-w <40751091+w-e-w@users.noreply.github.com> Date: Wed, 22 May 2024 23:06:07 +0900 Subject: [PATCH 332/496] ReloadUI backgroundColor --background-fill-primary --- javascript/ui.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/javascript/ui.js b/javascript/ui.js index e0f5feeb..16faaceb 100644 --- a/javascript/ui.js +++ b/javascript/ui.js @@ -337,8 +337,8 @@ onOptionsChanged(function() { let txt2img_textarea, img2img_textarea = undefined; function restart_reload() { + document.body.style.backgroundColor = "var(--background-fill-primary)"; document.body.innerHTML = '

    Reloading...

    '; - var requestPing = function() { requestGet("./internal/ping", {}, function(data) { location.reload(); From a63946233b71083f6726006b96fc16e3033ab844 Mon Sep 17 00:00:00 2001 From: w-e-w <40751091+w-e-w@users.noreply.github.com> Date: Sat, 25 May 2024 14:18:05 +0900 Subject: [PATCH 333/496] setuptools==69.5.1 --- requirements_versions.txt | 1 + 1 file changed, 1 insertion(+) diff --git a/requirements_versions.txt b/requirements_versions.txt index 3df74f3d..3037a395 100644 --- a/requirements_versions.txt +++ b/requirements_versions.txt @@ -1,3 +1,4 @@ +setuptools==69.5.1 # temp fix for compatibility with some old packages GitPython==3.1.32 Pillow==9.5.0 accelerate==0.21.0 From 6dd53ce63dc70b3fcf7f25402d40b48f50abdf74 Mon Sep 17 00:00:00 2001 From: alcacode Date: Sun, 26 May 2024 15:36:55 +0200 Subject: [PATCH 334/496] 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". --- modules/images.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/modules/images.py b/modules/images.py index c0ff8a63..1be176cd 100644 --- a/modules/images.py +++ b/modules/images.py @@ -653,7 +653,7 @@ def save_image(image, path, basename, seed=None, prompt=None, extension='png', i # 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": print('Image dimensions too large; saving as PNG') - extension = ".png" + extension = "png" 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) From 801b72b92b4f07e5d2fa9737b160762ea8f67088 Mon Sep 17 00:00:00 2001 From: AUTOMATIC1111 <16777216c@gmail.com> Date: Tue, 28 May 2024 21:20:23 +0300 Subject: [PATCH 335/496] update changelog --- CHANGELOG.md | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 295d26c8..5c16b561 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,8 @@ +## 1.9.4 + +### Bug Fixes: +* pin setuptools version to fix the startup error ([#15883](https://github.com/AUTOMATIC1111/stable-diffusion-webui/pull/15883)) + ## 1.9.3 ### Bug Fixes: From 8d6f7417385d1cacfd827800bdf02a0e8dd8f092 Mon Sep 17 00:00:00 2001 From: w-e-w <40751091+w-e-w@users.noreply.github.com> Date: Wed, 29 May 2024 03:33:32 +0900 Subject: [PATCH 336/496] #15883 -> #15882 --- CHANGELOG.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 5c16b561..596b1ec4 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,7 +1,7 @@ ## 1.9.4 ### Bug Fixes: -* pin setuptools version to fix the startup error ([#15883](https://github.com/AUTOMATIC1111/stable-diffusion-webui/pull/15883)) +* pin setuptools version to fix the startup error ([#15882](https://github.com/AUTOMATIC1111/stable-diffusion-webui/pull/15882)) ## 1.9.3 From 10f8d0f84216e3642e960ea7118a5acc8a79546f Mon Sep 17 00:00:00 2001 From: eatmoreapple Date: Tue, 4 Jun 2024 15:02:13 +0800 Subject: [PATCH 337/496] feat: lora partial update precede full update. --- extensions-builtin/Lora/networks.py | 40 +++++++++++++++++++++-------- 1 file changed, 30 insertions(+), 10 deletions(-) diff --git a/extensions-builtin/Lora/networks.py b/extensions-builtin/Lora/networks.py index 42b14dc2..18809364 100644 --- a/extensions-builtin/Lora/networks.py +++ b/extensions-builtin/Lora/networks.py @@ -260,6 +260,16 @@ def load_networks(names, te_multipliers=None, unet_multipliers=None, dyn_dims=No 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] if any(x is None for x in networks_on_disk): list_available_networks() @@ -566,22 +576,16 @@ def network_MultiheadAttention_load_state_dict(self, *args, **kwargs): return originals.MultiheadAttention_load_state_dict(self, *args, **kwargs) -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) - +def process_network_files(names: list[str] | None = None): 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"])) for filename in candidates: if os.path.isdir(filename): continue - 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: entry = network.NetworkOnDisk(name, filename) except OSError: # should catch FileNotFoundError and PermissionError etc. @@ -597,6 +601,22 @@ def list_available_networks(): 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]+\)") From 25bbf31f5701b85804908a54b2f6af38a1d50f1f Mon Sep 17 00:00:00 2001 From: NouberNou Date: Thu, 6 Jun 2024 16:22:49 -0700 Subject: [PATCH 338/496] 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. --- modules/processing.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/modules/processing.py b/modules/processing.py index 76557dd7..cb37a77d 100644 --- a/modules/processing.py +++ b/modules/processing.py @@ -569,7 +569,7 @@ class Processed: 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_subseeds = all_subseeds or p.all_subseeds or [self.subseed] - self.infotexts = infotexts or [info] + self.infotexts = infotexts or [info] * len(image_list) self.version = program_version() def js(self): From 53f62674ae55e84aff4d4c9ed104ba9dce8ae887 Mon Sep 17 00:00:00 2001 From: NouberNou Date: Thu, 6 Jun 2024 16:30:01 -0700 Subject: [PATCH 339/496] Typo on edit Edited in fix in Github editor and mistyped from local copy --- modules/processing.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/modules/processing.py b/modules/processing.py index cb37a77d..c22da416 100644 --- a/modules/processing.py +++ b/modules/processing.py @@ -569,7 +569,7 @@ class Processed: 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_subseeds = all_subseeds or p.all_subseeds or [self.subseed] - self.infotexts = infotexts or [info] * len(image_list) + self.infotexts = infotexts or [info] * len(images_list) self.version = program_version() def js(self): From 0769aa318a1896ccf74f57e6e943eb6b5fab5051 Mon Sep 17 00:00:00 2001 From: AUTOMATIC1111 <16777216c@gmail.com> Date: Sat, 8 Jun 2024 09:05:35 +0300 Subject: [PATCH 340/496] integrated edits as recommended in the PR #15804 --- modules/sd_hijack_optimizations.py | 14 +------------- 1 file changed, 1 insertion(+), 13 deletions(-) diff --git a/modules/sd_hijack_optimizations.py b/modules/sd_hijack_optimizations.py index 4c2dc56d..0269f1f5 100644 --- a/modules/sd_hijack_optimizations.py +++ b/modules/sd_hijack_optimizations.py @@ -486,18 +486,7 @@ def xformers_attention_forward(self, x, context=None, mask=None, **kwargs): k_in = self.to_k(context_k) v_in = self.to_v(context_v) - def _reshape(t): - """rearrange(t, 'b n (h d) -> b n h d', h=h). - Using torch native operations to avoid overhead as this function is - called frequently. (70 times/it for SDXL) - """ - b, n, _ = t.shape # Get the batch size (b) and sequence length (n) - d = t.shape[2] // h # Determine the depth per head - return t.reshape(b, n, h, d) - - q = _reshape(q_in) - k = _reshape(k_in) - v = _reshape(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 @@ -509,7 +498,6 @@ def xformers_attention_forward(self, x, context=None, mask=None, **kwargs): 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) From 5429e4cff514df2f4cab242212ba347741eadc08 Mon Sep 17 00:00:00 2001 From: AUTOMATIC1111 <16777216c@gmail.com> Date: Sat, 8 Jun 2024 09:56:09 +0300 Subject: [PATCH 341/496] add proper infotext support for #15607 fix settings override not working for NGMI, s_churn, etc... --- modules/processing.py | 14 ++++++++------ modules/sd_samplers_cfg_denoiser.py | 12 +++++++----- modules/shared_options.py | 6 +++--- 3 files changed, 18 insertions(+), 14 deletions(-) diff --git a/modules/processing.py b/modules/processing.py index c22da416..97a7162a 100644 --- a/modules/processing.py +++ b/modules/processing.py @@ -238,11 +238,6 @@ class StableDiffusionProcessing: self.styles = [] 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.override_settings = self.override_settings or {} @@ -259,6 +254,13 @@ class StableDiffusionProcessing: self.cached_uc = StableDiffusionProcessing.cached_uc 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 def sd_model(self): return shared.sd_model @@ -794,7 +796,6 @@ 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, "Init image hash": getattr(p, 'init_img_hash', 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, **p.extra_generation_params, "Version": program_version() if opts.add_version_to_infotext else None, @@ -890,6 +891,7 @@ def process_images_inner(p: StableDiffusionProcessing) -> Processed: modules.sd_hijack.model_hijack.apply_circular(p.tiling) modules.sd_hijack.model_hijack.clear_comments() + p.fill_fields_from_opts() p.setup_prompts() if isinstance(seed, list): diff --git a/modules/sd_samplers_cfg_denoiser.py b/modules/sd_samplers_cfg_denoiser.py index d89ea2c8..f48f58a5 100644 --- a/modules/sd_samplers_cfg_denoiser.py +++ b/modules/sd_samplers_cfg_denoiser.py @@ -214,12 +214,14 @@ class CFGDenoiser(torch.nn.Module): if shared.opts.skip_early_cond != 0. and self.step / self.total_steps <= shared.opts.skip_early_cond: skip_uncond = True - x_in = x_in[:-batch_size] - sigma_in = sigma_in[:-batch_size] - - # alternating uncond allows for higher thresholds without the quality loss normally expected from raising it - if (self.step % 2 or shared.opts.s_min_uncond_all) and s_min_uncond > 0 and sigma[0] < s_min_uncond and not is_edit_model: + self.p.extra_generation_params["Skip Early CFG"] = shared.opts.skip_early_cond + elif (self.step % 2 or shared.opts.s_min_uncond_all) and s_min_uncond > 0 and sigma[0] < s_min_uncond and not is_edit_model: skip_uncond = True + self.p.extra_generation_params["NGMS"] = s_min_uncond + if shared.opts.s_min_uncond_all: + self.p.extra_generation_params["NGMS all steps"] = shared.opts.s_min_uncond_all + + if skip_uncond: x_in = x_in[:-batch_size] sigma_in = sigma_in[:-batch_size] diff --git a/modules/shared_options.py b/modules/shared_options.py index c711fa5f..05c3d939 100644 --- a/modules/shared_options.py +++ b/modules/shared_options.py @@ -209,8 +209,8 @@ options_templates.update(options_section(('img2img', "img2img", "sd"), { options_templates.update(options_section(('optimizations', "Optimizations", "sd"), { "cross_attention_optimization": OptionInfo("Automatic", "Cross attention optimization", gr.Dropdown, lambda: {"choices": shared_items.cross_attention_optimizations()}), - "s_min_uncond": OptionInfo(0.0, "Negative Guidance minimum sigma", gr.Slider, {"minimum": 0.0, "maximum": 15.0, "step": 0.01}).link("PR", "https://github.com/AUTOMATIC1111/stable-diffusion-webui/pull/9177").info("skip negative prompt for some steps when the image is almost ready; 0=disable, higher=faster"), - "s_min_uncond_all": OptionInfo(False, "NGMS: Skip every step").info("makes Negative Guidance minimum sigma skip negative guidance on every step instead of only half"), + "s_min_uncond": OptionInfo(0.0, "Negative Guidance minimum sigma", gr.Slider, {"minimum": 0.0, "maximum": 15.0, "step": 0.01}, infotext='NGMS').link("PR", "https://github.com/AUTOMATIC1111/stablediffusion-webui/pull/9177").info("skip negative prompt for some steps when the image is almost ready; 0=disable, higher=faster"), + "s_min_uncond_all": OptionInfo(False, "Negative Guidance minimum sigma all steps", infotext='NGMS all steps').info("By default, NGMS above skips every other step; this makes it skip all steps"), "token_merging_ratio": OptionInfo(0.0, "Token merging ratio", gr.Slider, {"minimum": 0.0, "maximum": 0.9, "step": 0.1}, infotext='Token merging ratio').link("PR", "https://github.com/AUTOMATIC1111/stable-diffusion-webui/pull/9256").info("0=disable, higher=faster"), "token_merging_ratio_img2img": OptionInfo(0.0, "Token merging ratio for img2img", gr.Slider, {"minimum": 0.0, "maximum": 0.9, "step": 0.1}).info("only applies if non-zero and overrides above"), "token_merging_ratio_hr": OptionInfo(0.0, "Token merging ratio for high-res pass", gr.Slider, {"minimum": 0.0, "maximum": 0.9, "step": 0.1}, infotext='Token merging ratio hr').info("only applies if non-zero and overrides above"), @@ -382,7 +382,7 @@ options_templates.update(options_section(('sampler-params', "Sampler parameters" 'uni_pc_order': OptionInfo(3, "UniPC order", gr.Slider, {"minimum": 1, "maximum": 50, "step": 1}, infotext='UniPC order').info("must be < sampling steps"), 'uni_pc_lower_order_final': OptionInfo(True, "UniPC lower order final", infotext='UniPC lower order final'), 'sd_noise_schedule': OptionInfo("Default", "Noise schedule for sampling", gr.Radio, {"choices": ["Default", "Zero Terminal SNR"]}, infotext="Noise Schedule").info("for use with zero terminal SNR trained models"), - 'skip_early_cond': OptionInfo(0, "Skip CFG during early sampling", gr.Slider, {"minimum": 0.0, "maximum": 1.0, "step": 0.01}, infotext="Skip Early CFG").info("CFG will be disabled (set to 1) on early steps, can both improve sample diversity/quality and speed up sampling"), + 'skip_early_cond': OptionInfo(0.0, "Ignore negative prompt during early sampling", gr.Slider, {"minimum": 0.0, "maximum": 1.0, "step": 0.01}, infotext="Skip Early CFG").info("disables CFG on a proportion of steps at the beginning of generation; 0=skip none; 1=skip all; can both improve sample diversity/quality and speed up sampling"), })) options_templates.update(options_section(('postprocessing', "Postprocessing", "postprocessing"), { From cd9e9e404955df19a72c832d68888db44ab7b382 Mon Sep 17 00:00:00 2001 From: AUTOMATIC1111 <16777216c@gmail.com> Date: Sat, 8 Jun 2024 10:13:38 +0300 Subject: [PATCH 342/496] remove unneeded tabulation --- .../Lora/ui_extra_networks_lora.py | 23 +++++++++---------- 1 file changed, 11 insertions(+), 12 deletions(-) diff --git a/extensions-builtin/Lora/ui_extra_networks_lora.py b/extensions-builtin/Lora/ui_extra_networks_lora.py index e35d90c6..3e34d69d 100644 --- a/extensions-builtin/Lora/ui_extra_networks_lora.py +++ b/extensions-builtin/Lora/ui_extra_networks_lora.py @@ -60,19 +60,18 @@ class ExtraNetworksPageLora(ui_extra_networks.ExtraNetworksPage): else: sd_version = lora_on_disk.sd_version - if shared.sd_model is not None: # still show LoRA in case an error occurs during initial model loading - if shared.opts.lora_show_all or not enable_filter: - pass - 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 - if model_version.name in shared.opts.lora_hide_unknown_for_versions: - return None - elif shared.sd_model.is_sdxl and sd_version != network.SdVersion.SDXL: - return None - elif shared.sd_model.is_sd2 and sd_version != network.SdVersion.SD2: - return None - elif shared.sd_model.is_sd1 and sd_version != network.SdVersion.SD1: + if shared.opts.lora_show_all or not enable_filter or not shared.sd_model: + pass + 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 + if model_version.name in shared.opts.lora_hide_unknown_for_versions: return None + elif shared.sd_model.is_sdxl and sd_version != network.SdVersion.SDXL: + return None + elif shared.sd_model.is_sd2 and sd_version != network.SdVersion.SD2: + return None + elif shared.sd_model.is_sd1 and sd_version != network.SdVersion.SD1: + return None return item From 510f025a01733f20ebe3997c1c3d159e6ac50148 Mon Sep 17 00:00:00 2001 From: w-e-w <40751091+w-e-w@users.noreply.github.com> Date: Tue, 4 Jun 2024 02:23:43 +0900 Subject: [PATCH 343/496] replace wsl-open with wslpath and explorer.exe --- modules/util.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/modules/util.py b/modules/util.py index 0db13736..768bf32d 100644 --- a/modules/util.py +++ b/modules/util.py @@ -208,6 +208,6 @@ Requested path was: {path} elif platform.system() == "Darwin": subprocess.Popen(["open", path]) elif "microsoft-standard-WSL2" in platform.uname().release: - subprocess.Popen(["wsl-open", path]) + subprocess.Popen(["explorer.exe", subprocess.check_output(["wslpath", "-w", path])]) else: subprocess.Popen(["xdg-open", path]) From 603509ec905a9c9ac1011e9531a9da180828fcc0 Mon Sep 17 00:00:00 2001 From: AUTOMATIC1111 <16777216c@gmail.com> Date: Sat, 8 Jun 2024 10:54:41 +0300 Subject: [PATCH 344/496] as per wfjsw's suggestion, revert changes for sd_hijack_checkpoint.py --- modules/sd_hijack_checkpoint.py | 9 +++------ 1 file changed, 3 insertions(+), 6 deletions(-) diff --git a/modules/sd_hijack_checkpoint.py b/modules/sd_hijack_checkpoint.py index b2f05bbd..2604d969 100644 --- a/modules/sd_hijack_checkpoint.py +++ b/modules/sd_hijack_checkpoint.py @@ -4,19 +4,16 @@ import ldm.modules.attention import ldm.modules.diffusionmodules.openaimodel -# Setting flag=False so that torch skips checking parameters. -# parameters checking is expensive in frequent operations. - def BasicTransformerBlock_forward(self, x, context=None): - return checkpoint(self._forward, x, context, flag=False) + return checkpoint(self._forward, x, context) def AttentionBlock_forward(self, x): - return checkpoint(self._forward, x, flag=False) + return checkpoint(self._forward, x) def ResBlock_forward(self, x, emb): - return checkpoint(self._forward, x, emb, flag=False) + return checkpoint(self._forward, x, emb) stored = [] From 07cf95c76ef052c120fbf1cfb69e3018e1cb06f8 Mon Sep 17 00:00:00 2001 From: AUTOMATIC1111 <16777216c@gmail.com> Date: Sat, 8 Jun 2024 11:26:34 +0300 Subject: [PATCH 345/496] update pickle safe filenames --- modules/safe.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/modules/safe.py b/modules/safe.py index b1d08a79..af019ffd 100644 --- a/modules/safe.py +++ b/modules/safe.py @@ -64,8 +64,8 @@ class RestrictedUnpickler(pickle.Unpickler): raise Exception(f"global '{module}/{name}' is forbidden") -# Regular expression that accepts 'dirname/version', 'dirname/data.pkl', and 'dirname/data/' -allowed_zip_names_re = re.compile(r"^([^/]+)/((data/\d+)|version|(data\.pkl))$") +# Regular expression that accepts 'dirname/version', 'dirname/byteorder', 'dirname/data.pkl', '.data/serialization_id', and 'dirname/data/' +allowed_zip_names_re = re.compile(r"^([^/]+)/((data/\d+)|version|byteorder|.data/serialization_id|(data\.pkl))$") data_pkl_re = re.compile(r"^([^/]+)/data\.pkl$") def check_zip_filenames(filename, names): From 1a7ffa2c76b0e68cd647c1f7f07235bcf85c985d Mon Sep 17 00:00:00 2001 From: AUTOMATIC1111 <16777216c@gmail.com> Date: Sat, 8 Jun 2024 11:35:45 +0300 Subject: [PATCH 346/496] remove extra local variable --- modules/paths_internal.py | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/modules/paths_internal.py b/modules/paths_internal.py index 884984c9..67521f5c 100644 --- a/modules/paths_internal.py +++ b/modules/paths_internal.py @@ -28,9 +28,8 @@ parser_pre.add_argument("--models-dir", type=str, default=None, help="base path cmd_opts_pre = parser_pre.parse_known_args()[0] data_path = cmd_opts_pre.data_dir -models_override = cmd_opts_pre.models_dir -models_path = models_override if models_override else 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_builtin_dir = os.path.join(script_path, "extensions-builtin") config_states_dir = os.path.join(script_path, "config_states") From 547778b10f25def4e040b81942a2b23295567de3 Mon Sep 17 00:00:00 2001 From: AUTOMATIC1111 <16777216c@gmail.com> Date: Sat, 8 Jun 2024 12:41:28 +0300 Subject: [PATCH 347/496] possibly make NaN check cheaper --- modules/devices.py | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/modules/devices.py b/modules/devices.py index 7de34ac5..d574975e 100644 --- a/modules/devices.py +++ b/modules/devices.py @@ -243,22 +243,22 @@ def test_for_nans(x, where): if shared.cmd_opts.disable_nan_check: return - if not torch.all(torch.isnan(x)).item(): + if not torch.isnan(x[(0, ) * len(x.shape)]): return 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: 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": - 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: 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: - 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." From 39a6d5655f6c162e2b8da024a1719d79304332a2 Mon Sep 17 00:00:00 2001 From: drhead <1313496+drhead@users.noreply.github.com> Date: Sat, 8 Jun 2024 18:55:07 -0400 Subject: [PATCH 348/496] patch k_diffusion to_d and strip device from schedulers --- modules/sd_schedulers.py | 24 +++++++++++++++--------- 1 file changed, 15 insertions(+), 9 deletions(-) diff --git a/modules/sd_schedulers.py b/modules/sd_schedulers.py index 0c09af8d..a2b9eb29 100644 --- a/modules/sd_schedulers.py +++ b/modules/sd_schedulers.py @@ -4,6 +4,12 @@ import torch import k_diffusion +def to_d(x, sigma, denoised): + """Converts a denoiser output to a Karras ODE derivative.""" + return (x - denoised) / sigma + +k_diffusion.sampling.to_d = to_d + import numpy as np from modules import shared @@ -19,11 +25,11 @@ class Scheduler: aliases: list = None -def uniform(n, sigma_min, sigma_max, inner_model, device): +def uniform(n, sigma_min, sigma_max, inner_model): return inner_model.get_sigmas(n) -def sgm_uniform(n, sigma_min, sigma_max, inner_model, device): +def sgm_uniform(n, sigma_min, sigma_max, inner_model): start = inner_model.sigma_to_t(torch.tensor(sigma_max)) end = inner_model.sigma_to_t(torch.tensor(sigma_min)) sigs = [ @@ -31,9 +37,9 @@ def sgm_uniform(n, sigma_min, sigma_max, inner_model, device): for ts in torch.linspace(start, end, n + 1)[:-1] ] sigs += [0.0] - return torch.FloatTensor(sigs).to(device) + return torch.FloatTensor(sigs) -def get_align_your_steps_sigmas(n, sigma_min, sigma_max, device='cpu'): +def get_align_your_steps_sigmas(n, sigma_min, sigma_max): # https://research.nvidia.com/labs/toronto-ai/AlignYourSteps/howto.html def loglinear_interp(t_steps, num_steps): """ @@ -59,12 +65,12 @@ def get_align_your_steps_sigmas(n, sigma_min, sigma_max, device='cpu'): else: sigmas.append(0.0) - return torch.FloatTensor(sigmas).to(device) + return torch.FloatTensor(sigmas) -def kl_optimal(n, sigma_min, sigma_max, device): - alpha_min = torch.arctan(torch.tensor(sigma_min, device=device)) - alpha_max = torch.arctan(torch.tensor(sigma_max, device=device)) - step_indices = torch.arange(n + 1, device=device) +def kl_optimal(n, sigma_min, sigma_max): + alpha_min = torch.arctan(torch.tensor(sigma_min)) + alpha_max = torch.arctan(torch.tensor(sigma_max)) + step_indices = torch.arange(n + 1) sigmas = torch.tan(step_indices / n * alpha_min + (1.0 - step_indices / n) * alpha_max) return sigmas From d52a1e1a22f19c941d581b92904a99d4dd7b22c1 Mon Sep 17 00:00:00 2001 From: drhead <1313496+drhead@users.noreply.github.com> Date: Sat, 8 Jun 2024 18:56:23 -0400 Subject: [PATCH 349/496] lint --- modules/sd_schedulers.py | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/modules/sd_schedulers.py b/modules/sd_schedulers.py index a2b9eb29..9916cf05 100644 --- a/modules/sd_schedulers.py +++ b/modules/sd_schedulers.py @@ -4,16 +4,16 @@ import torch import k_diffusion +import numpy as np + +from modules import shared + def to_d(x, sigma, denoised): """Converts a denoiser output to a Karras ODE derivative.""" return (x - denoised) / sigma k_diffusion.sampling.to_d = to_d -import numpy as np - -from modules import shared - @dataclasses.dataclass class Scheduler: name: str From d875cda565171407e1e2dc087fb5c5140359c6ec Mon Sep 17 00:00:00 2001 From: huchenlei Date: Sat, 8 Jun 2024 22:11:11 -0400 Subject: [PATCH 350/496] Fix sdxl inpaint --- modules/processing.py | 4 ++-- modules/sd_models.py | 19 ++++++++++++------- 2 files changed, 14 insertions(+), 9 deletions(-) diff --git a/modules/processing.py b/modules/processing.py index 0ff6a45c..dc538272 100644 --- a/modules/processing.py +++ b/modules/processing.py @@ -115,7 +115,7 @@ 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) else: - if getattr(sd_model.model, "is_sdxl_inpaint", False): + if sd_model.is_sdxl_inpaint: # The "masked-image" in this case will just be all 0.5 since the entire image is masked. image_conditioning = torch.ones(x.shape[0], 3, height, width, device=x.device) * 0.5 image_conditioning = images_tensor_to_samples(image_conditioning, @@ -389,7 +389,7 @@ class StableDiffusionProcessing: if self.sampler.conditioning_key == "crossattn-adm": return self.unclip_image_conditioning(source_image) - if getattr(self.sampler.model_wrap.inner_model.model, "is_sdxl_inpaint", False): + if self.sampler.model_wrap.inner_model.is_sdxl_inpaint: 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. diff --git a/modules/sd_models.py b/modules/sd_models.py index 61bd15d8..93ff6c5f 100644 --- a/modules/sd_models.py +++ b/modules/sd_models.py @@ -386,13 +386,6 @@ def load_model_weights(model, checkpoint_info: CheckpointInfo, state_dict, timer model.is_sd2 = not model.is_sdxl and hasattr(model.cond_stage_model, 'model') model.is_sd1 = not model.is_sdxl and not model.is_sd2 model.is_ssd = model.is_sdxl and 'model.diffusion_model.middle_block.1.transformer_blocks.0.attn1.to_q.weight' not in state_dict.keys() - # Set is_sdxl_inpaint flag. - diffusion_model_input = state_dict.get('diffusion_model.input_blocks.0.0.weight', None) - model.is_sdxl_inpaint = ( - model.is_sdxl and - diffusion_model_input is not None and - diffusion_model_input.shape[1] == 9 - ) if model.is_sdxl: sd_models_xl.extend_sdxl(model) @@ -408,6 +401,18 @@ def load_model_weights(model, checkpoint_info: CheckpointInfo, state_dict, timer del state_dict + # Set is_sdxl_inpaint flag. + # Perform this check after model initialization to make sure state_dict + # structure is already known. + diffusion_model_input = model.model.state_dict().get( + 'diffusion_model.input_blocks.0.0.weight' + ) + model.is_sdxl_inpaint = ( + model.is_sdxl and + diffusion_model_input is not None and + diffusion_model_input.shape[1] == 9 + ) + if shared.cmd_opts.opt_channelslast: model.to(memory_format=torch.channels_last) timer.record("apply channels_last") From f89b5dbbd282091fd6b3318f3ef20cf23cf9ea3a Mon Sep 17 00:00:00 2001 From: huchenlei Date: Sat, 8 Jun 2024 22:15:37 -0400 Subject: [PATCH 351/496] nit --- modules/sd_models.py | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/modules/sd_models.py b/modules/sd_models.py index 93ff6c5f..af35187c 100644 --- a/modules/sd_models.py +++ b/modules/sd_models.py @@ -402,8 +402,9 @@ def load_model_weights(model, checkpoint_info: CheckpointInfo, state_dict, timer del state_dict # Set is_sdxl_inpaint flag. - # Perform this check after model initialization to make sure state_dict - # structure is already known. + # Checks Unet structure to detect inpaint model. The inpaint model's + # checkpoint state_dict does not contain the key + # 'diffusion_model.input_blocks.0.0.weight'. diffusion_model_input = model.model.state_dict().get( 'diffusion_model.input_blocks.0.0.weight' ) From 6447ff49d335edd7dccc4b75e262615ce13e76ac Mon Sep 17 00:00:00 2001 From: bluelovers Date: Sun, 9 Jun 2024 19:07:32 +0800 Subject: [PATCH 352/496] feat: save pattern add `basename` `grid` or `xyz_grid` or `img` ```py 'basename': lambda self: 'img' if self.basename == '' else self.basename, ``` --- modules/images.py | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/modules/images.py b/modules/images.py index 05432e3a..90c65b74 100644 --- a/modules/images.py +++ b/modules/images.py @@ -377,6 +377,7 @@ def get_sampler_scheduler(p, sampler): class FilenameGenerator: replacements = { + 'basename': lambda self: 'img' if self.basename == '' else self.basename, '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_last': lambda self: NOTHING_AND_SKIP_PREVIOUS_TEXT if self.p.batch_size == 1 else self.p.all_seeds[-1], @@ -413,12 +414,13 @@ class FilenameGenerator: } 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.seed = seed self.prompt = prompt self.image = image self.zip = zip + self.basename = basename def get_vae_filename(self): """Get the name of the VAE file.""" @@ -649,7 +651,7 @@ def save_image(image, path, basename, seed=None, prompt=None, extension='png', i txt_fullfn (`str` or 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, zip=False, basename=basename) # 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": From 6214aa7d2a84aa2a12962706579a2dba3470fb51 Mon Sep 17 00:00:00 2001 From: AUTOMATIC1111 <16777216c@gmail.com> Date: Sun, 9 Jun 2024 16:24:04 +0300 Subject: [PATCH 353/496] performance: check for nans in unet only once, after all steps have been completed --- modules/processing.py | 5 +++++ modules/sd_samplers_cfg_denoiser.py | 2 -- 2 files changed, 5 insertions(+), 2 deletions(-) diff --git a/modules/processing.py b/modules/processing.py index dc538272..65e37db0 100644 --- a/modules/processing.py +++ b/modules/processing.py @@ -625,6 +625,9 @@ class DecodedSamples(list): def decode_latent_batch(model, batch, target_device=None, check_for_nans=False): samples = DecodedSamples() + if check_for_nans: + devices.test_for_nans(batch, "unet") + for i in range(batch.shape[0]): sample = decode_first_stage(model, batch[i:i + 1])[0] @@ -987,6 +990,8 @@ def process_images_inner(p: StableDiffusionProcessing) -> Processed: if getattr(samples_ddim, 'already_decoded', False): x_samples_ddim = samples_ddim else: + devices.test_for_nans(samples_ddim, "unet") + if opts.sd_vae_decode_method != 'Full': 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) diff --git a/modules/sd_samplers_cfg_denoiser.py b/modules/sd_samplers_cfg_denoiser.py index f48f58a5..06d2661f 100644 --- a/modules/sd_samplers_cfg_denoiser.py +++ b/modules/sd_samplers_cfg_denoiser.py @@ -273,8 +273,6 @@ class CFGDenoiser(torch.nn.Module): denoised_params = CFGDenoisedParams(x_out, state.sampling_step, state.sampling_steps, self.inner_model) cfg_denoised_callback(denoised_params) - devices.test_for_nans(x_out, "unet") - if is_edit_model: denoised = self.combine_denoised_for_edit_model(x_out, cond_scale) elif skip_uncond: From e368cd2810af0c7a734c33b25549110beacdf53f Mon Sep 17 00:00:00 2001 From: AUTOMATIC1111 <16777216c@gmail.com> Date: Sun, 9 Jun 2024 16:46:08 +0300 Subject: [PATCH 354/496] stylistic changes for #15978 --- modules/images.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/modules/images.py b/modules/images.py index 90c65b74..3253847f 100644 --- a/modules/images.py +++ b/modules/images.py @@ -377,7 +377,7 @@ def get_sampler_scheduler(p, sampler): class FilenameGenerator: replacements = { - 'basename': lambda self: 'img' if self.basename == '' else self.basename, + 'basename': lambda self: self.basename or 'img', '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_last': lambda self: NOTHING_AND_SKIP_PREVIOUS_TEXT if self.p.batch_size == 1 else self.p.all_seeds[-1], @@ -651,7 +651,7 @@ def save_image(image, path, basename, seed=None, prompt=None, extension='png', i txt_fullfn (`str` or None): If a text file is saved for this image, this will be its full path. Otherwise None. """ - namegen = FilenameGenerator(p, seed, prompt, image, zip=False, basename=basename) + 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 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": From aafbb5b403e524b94367c5893f76f834b98de26d Mon Sep 17 00:00:00 2001 From: AUTOMATIC1111 <16777216c@gmail.com> Date: Sun, 9 Jun 2024 16:47:08 +0300 Subject: [PATCH 355/496] lint --- modules/sd_samplers_cfg_denoiser.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/modules/sd_samplers_cfg_denoiser.py b/modules/sd_samplers_cfg_denoiser.py index 06d2661f..a86fa88e 100644 --- a/modules/sd_samplers_cfg_denoiser.py +++ b/modules/sd_samplers_cfg_denoiser.py @@ -1,5 +1,5 @@ import torch -from modules import prompt_parser, devices, sd_samplers_common +from modules import prompt_parser, sd_samplers_common from modules.shared import opts, state import modules.shared as shared From 57e6d05a43e4bdf4575e520f1a04c17e80fe58cc Mon Sep 17 00:00:00 2001 From: AUTOMATIC1111 <16777216c@gmail.com> Date: Sun, 9 Jun 2024 21:18:36 +0300 Subject: [PATCH 356/496] added tool for profiling code --- modules/call_queue.py | 10 +++++++-- modules/processing.py | 5 +++-- modules/profiling.py | 46 +++++++++++++++++++++++++++++++++++++++ modules/shared_options.py | 16 ++++++++++++++ style.css | 6 ++++- 5 files changed, 78 insertions(+), 5 deletions(-) create mode 100644 modules/profiling.py diff --git a/modules/call_queue.py b/modules/call_queue.py index b50931bc..d22c23b3 100644 --- a/modules/call_queue.py +++ b/modules/call_queue.py @@ -1,8 +1,9 @@ +import os.path from functools import wraps import html 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() @@ -111,8 +112,13 @@ def wrap_gradio_call(func, extra_outputs=None, add_stats=False): else: vram_html = '' + if shared.opts.profiling_enable and os.path.exists(shared.opts.profiling_filename): + profiling_html = f"

    [ Profile ]

    " + else: + profiling_html = '' + # last item is always HTML - res[-1] += f"

    Time taken: {elapsed_text}

    {vram_html}
    " + res[-1] += f"

    Time taken: {elapsed_text}

    {vram_html}{profiling_html}
    " return tuple(res) diff --git a/modules/processing.py b/modules/processing.py index 65e37db0..91cb94db 100644 --- a/modules/processing.py +++ b/modules/processing.py @@ -16,7 +16,7 @@ from skimage import exposure from typing import Any 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.sd_hijack import model_hijack from modules.sd_samplers_common import images_tensor_to_samples, decode_first_stage, approximation_indexes @@ -843,7 +843,8 @@ def process_images(p: StableDiffusionProcessing) -> Processed: # backwards compatibility, fix sampler and scheduler if invalid sd_samplers.fix_p_invalid_sampler_and_scheduler(p) - res = process_images_inner(p) + with profiling.Profiler(): + res = process_images_inner(p) finally: sd_models.apply_token_merging(p.sd_model, 0) diff --git a/modules/profiling.py b/modules/profiling.py new file mode 100644 index 00000000..95b59f71 --- /dev/null +++ b/modules/profiling.py @@ -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) + diff --git a/modules/shared_options.py b/modules/shared_options.py index e2e02094..104d8a54 100644 --- a/modules/shared_options.py +++ b/modules/shared_options.py @@ -129,6 +129,22 @@ options_templates.update(options_section(('system', "System", "system"), { "dump_stacks_on_signal": OptionInfo(False, "Print stack traces before exiting the program with ctrl+c."), })) +options_templates.update(options_section(('profiler', "Profiler", "system"), { + "profiling_explanation": OptionHTML(""" +Those settings allow you to enable torch profiler when generating pictures. +Profiling allows you to see which code uses how much of computer's resources during generation. +Each generation writes its own profile to one file, overwriting previous. +The file can be viewed in Chrome, or on a Perfetto web site. +Warning: writing profile can take a lot of time, up to 30 seconds, and the file itelf can be around 500MB in size. +"""), + "profiling_enable": OptionInfo(False, "Enable profiling"), + "profiling_activities": OptionInfo(["CPU"], "Activities", gr.CheckboxGroup, {"choices": ["CPU", "CUDA"]}), + "profiling_record_shapes": OptionInfo(True, "Record shapes"), + "profiling_profile_memory": OptionInfo(True, "Profile memory"), + "profiling_with_stack": OptionInfo(True, "Include python stack"), + "profiling_filename": OptionInfo("trace.json", "Profile filename"), +})) + options_templates.update(options_section(('API', "API", "system"), { "api_enable_requests": OptionInfo(True, "Allow http:// and https:// URLs for input images in API", restrict_api=True), "api_forbid_local_requests": OptionInfo(True, "Forbid URLs to local resources", restrict_api=True), diff --git a/style.css b/style.css index 467c29cd..64ef61ba 100644 --- a/style.css +++ b/style.css @@ -279,7 +279,7 @@ input[type="checkbox"].input-accordion-checkbox{ display: inline-block; } -.html-log .performance p.time, .performance p.vram, .performance p.time abbr, .performance p.vram abbr { +.html-log .performance p.time, .performance p.vram, .performance p.profile, .performance p.time abbr, .performance p.vram abbr { margin-bottom: 0; color: var(--block-title-text-color); } @@ -291,6 +291,10 @@ input[type="checkbox"].input-accordion-checkbox{ margin-left: auto; } +.html-log .performance p.profile { + margin-left: 0.5em; +} + .html-log .performance .measurement{ color: var(--body-text-color); font-weight: bold; From 99e65ec6182c4e1201d16713f58c899bf26ba2ac Mon Sep 17 00:00:00 2001 From: AUTOMATIC1111 <16777216c@gmail.com> Date: Sun, 9 Jun 2024 21:23:53 +0300 Subject: [PATCH 357/496] undo some changes from #15823 and fix whitespace --- modules/sd_samplers_kdiffusion.py | 4 ++-- modules/sd_schedulers.py | 28 +++++++++++++++------------- 2 files changed, 17 insertions(+), 15 deletions(-) diff --git a/modules/sd_samplers_kdiffusion.py b/modules/sd_samplers_kdiffusion.py index 228de494..64e14e0c 100644 --- a/modules/sd_samplers_kdiffusion.py +++ b/modules/sd_samplers_kdiffusion.py @@ -1,7 +1,7 @@ import torch import inspect import k_diffusion.sampling -from modules import sd_samplers_common, sd_samplers_extra, sd_samplers_cfg_denoiser, sd_schedulers +from modules import sd_samplers_common, sd_samplers_extra, sd_samplers_cfg_denoiser, sd_schedulers, devices from modules.sd_samplers_cfg_denoiser import CFGDenoiser # noqa: F401 from modules.script_callbacks import ExtraNoiseParams, extra_noise_callback @@ -115,7 +115,7 @@ class KDiffusionSampler(sd_samplers_common.Sampler): if scheduler.need_inner_model: sigmas_kwargs['inner_model'] = self.model_wrap - sigmas = scheduler.function(n=steps, **sigmas_kwargs) + sigmas = scheduler.function(n=steps, **sigmas_kwargs, device=devices.cpu) if discard_next_to_last_sigma: sigmas = torch.cat([sigmas[:-2], sigmas[-1:]]) diff --git a/modules/sd_schedulers.py b/modules/sd_schedulers.py index 9916cf05..0165e6a0 100644 --- a/modules/sd_schedulers.py +++ b/modules/sd_schedulers.py @@ -1,19 +1,19 @@ import dataclasses - import torch - import k_diffusion - import numpy as np from modules import shared + def to_d(x, sigma, denoised): """Converts a denoiser output to a Karras ODE derivative.""" return (x - denoised) / sigma + k_diffusion.sampling.to_d = to_d + @dataclasses.dataclass class Scheduler: name: str @@ -25,11 +25,11 @@ class Scheduler: aliases: list = None -def uniform(n, sigma_min, sigma_max, inner_model): - return inner_model.get_sigmas(n) +def uniform(n, sigma_min, sigma_max, inner_model, device): + return inner_model.get_sigmas(n).to(device) -def sgm_uniform(n, sigma_min, sigma_max, inner_model): +def sgm_uniform(n, sigma_min, sigma_max, inner_model, device): start = inner_model.sigma_to_t(torch.tensor(sigma_max)) end = inner_model.sigma_to_t(torch.tensor(sigma_min)) sigs = [ @@ -37,9 +37,10 @@ def sgm_uniform(n, sigma_min, sigma_max, inner_model): for ts in torch.linspace(start, end, n + 1)[:-1] ] sigs += [0.0] - return torch.FloatTensor(sigs) + return torch.FloatTensor(sigs).to(device) -def get_align_your_steps_sigmas(n, sigma_min, sigma_max): + +def get_align_your_steps_sigmas(n, sigma_min, sigma_max, device): # https://research.nvidia.com/labs/toronto-ai/AlignYourSteps/howto.html def loglinear_interp(t_steps, num_steps): """ @@ -65,12 +66,13 @@ def get_align_your_steps_sigmas(n, sigma_min, sigma_max): else: sigmas.append(0.0) - return torch.FloatTensor(sigmas) + return torch.FloatTensor(sigmas).to(device) -def kl_optimal(n, sigma_min, sigma_max): - alpha_min = torch.arctan(torch.tensor(sigma_min)) - alpha_max = torch.arctan(torch.tensor(sigma_max)) - step_indices = torch.arange(n + 1) + +def kl_optimal(n, sigma_min, sigma_max, device): + alpha_min = torch.arctan(torch.tensor(sigma_min, device=device)) + alpha_max = torch.arctan(torch.tensor(sigma_max, device=device)) + step_indices = torch.arange(n + 1, device=device) sigmas = torch.tan(step_indices / n * alpha_min + (1.0 - step_indices / n) * alpha_max) return sigmas From d2097dbdd99aa528d8459ad7b62d3a2230a14e65 Mon Sep 17 00:00:00 2001 From: AUTOMATIC1111 <16777216c@gmail.com> Date: Sun, 9 Jun 2024 21:33:32 +0300 Subject: [PATCH 358/496] added onOptionsAvailable callback for javascript for --- javascript/ui.js | 1 + script.js | 15 +++++++++++++++ 2 files changed, 16 insertions(+) diff --git a/javascript/ui.js b/javascript/ui.js index 16faaceb..ff6f8974 100644 --- a/javascript/ui.js +++ b/javascript/ui.js @@ -299,6 +299,7 @@ onAfterUiUpdate(function() { var jsdata = textarea.value; opts = JSON.parse(jsdata); + executeCallbacks(optionsAvailableCallbacks); /*global optionsAvailableCallbacks*/ executeCallbacks(optionsChangedCallbacks); /*global optionsChangedCallbacks*/ Object.defineProperty(textarea, 'value', { diff --git a/script.js b/script.js index f069b1ef..de1a9000 100644 --- a/script.js +++ b/script.js @@ -29,6 +29,7 @@ var uiAfterUpdateCallbacks = []; var uiLoadedCallbacks = []; var uiTabChangeCallbacks = []; var optionsChangedCallbacks = []; +var optionsAvailableCallbacks = []; var uiAfterUpdateTimeout = null; var uiCurrentTab = null; @@ -77,6 +78,20 @@ function onOptionsChanged(callback) { optionsChangedCallbacks.push(callback); } +/** + * Register callback to be called when the options (in opts global variable) are available. + * The callback receives no arguments. + * If you register the callback after the options are available, it's just immediately called. + */ +function onOptionsAvailable(callback) { + if (Object.keys(opts).length != 0) { + callback(); + return; + } + + optionsAvailableCallbacks.push(callback); +} + function executeCallbacks(queue, arg) { for (const callback of queue) { try { From 74ee8fd1e32d06a45e669dd5cce6a2bff786d3a4 Mon Sep 17 00:00:00 2001 From: w-e-w <40751091+w-e-w@users.noreply.github.com> Date: Mon, 10 Jun 2024 04:35:11 +0900 Subject: [PATCH 359/496] .gitignore trace.json --- .gitignore | 1 + 1 file changed, 1 insertion(+) diff --git a/.gitignore b/.gitignore index 519b4a53..96cfe22d 100644 --- a/.gitignore +++ b/.gitignore @@ -39,3 +39,4 @@ notification.mp3 /.coverage* /test/test_outputs /cache +trace.json From 17e846150c49395e44550e18cbe5120fcf64c173 Mon Sep 17 00:00:00 2001 From: huchenlei Date: Tue, 28 May 2024 19:35:35 -0400 Subject: [PATCH 360/496] Add process_before_every_sampling hook --- modules/processing.py | 24 ++++++++++++++++++++++++ modules/scripts.py | 15 +++++++++++++++ 2 files changed, 39 insertions(+) diff --git a/modules/processing.py b/modules/processing.py index 91cb94db..79a3f0a7 100644 --- a/modules/processing.py +++ b/modules/processing.py @@ -1330,6 +1330,15 @@ class StableDiffusionProcessingTxt2Img(StableDiffusionProcessing): # here we generate an image normally 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)) del x @@ -1430,6 +1439,13 @@ class StableDiffusionProcessingTxt2Img(StableDiffusionProcessing): if self.scripts is not None: 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) @@ -1743,6 +1759,14 @@ class StableDiffusionProcessingImg2Img(StableDiffusionProcessing): self.extra_generation_params["Noise multiplier"] = 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) if self.mask is not None: diff --git a/modules/scripts.py b/modules/scripts.py index 70ccfbe4..8eca396b 100644 --- a/modules/scripts.py +++ b/modules/scripts.py @@ -187,6 +187,13 @@ class Script: """ 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): """ Same as process(), but called for every batch. @@ -826,6 +833,14 @@ class ScriptRunner: except Exception: 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): for script in self.ordered_scripts('before_process_batch'): try: From abacb735f4f20a301e11a36442950b55b19626fe Mon Sep 17 00:00:00 2001 From: w-e-w <40751091+w-e-w@users.noreply.github.com> Date: Mon, 10 Jun 2024 20:47:12 +0900 Subject: [PATCH 361/496] multi size grid --- modules/images.py | 9 ++++++--- scripts/xyz_grid.py | 8 +++++--- 2 files changed, 11 insertions(+), 6 deletions(-) diff --git a/modules/images.py b/modules/images.py index 3253847f..cfdfb338 100644 --- a/modules/images.py +++ b/modules/images.py @@ -54,11 +54,14 @@ def image_grid(imgs, batch_size=1, rows=None): params = script_callbacks.ImageGridLoopParams(imgs, cols, rows) script_callbacks.image_grid_callback(params) - w, h = imgs[0].size - grid = Image.new('RGB', size=(params.cols * w, params.rows * h), color='black') + w, h = map(max, zip(*(img.size for img in imgs))) + 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): - 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 diff --git a/scripts/xyz_grid.py b/scripts/xyz_grid.py index 52e343c4..606d72d4 100644 --- a/scripts/xyz_grid.py +++ b/scripts/xyz_grid.py @@ -375,16 +375,18 @@ def draw_xyz_grid(p, xs, ys, zs, x_labels, y_labels, z_labels, cell, draw_legend end_index = start_index + len(xs) * len(ys) grid = images.image_grid(processed_result.images[start_index:end_index], rows=len(ys)) if draw_legend: - grid = images.draw_grid_annotations(grid, processed_result.images[start_index].size[0], processed_result.images[start_index].size[1], hor_texts, ver_texts, margin_size) + grid_max_w, grid_max_h = map(max, zip(*(img.size for img in processed_result.images[start_index:end_index]))) + grid = images.draw_grid_annotations(grid, grid_max_w, grid_max_h, hor_texts, ver_texts, margin_size) processed_result.images.insert(i, grid) processed_result.all_prompts.insert(i, processed_result.all_prompts[start_index]) processed_result.all_seeds.insert(i, processed_result.all_seeds[start_index]) processed_result.infotexts.insert(i, processed_result.infotexts[start_index]) - sub_grid_size = processed_result.images[0].size + # sub_grid_size = processed_result.images[0].size z_grid = images.image_grid(processed_result.images[:z_count], rows=1) + z_sub_grid_max_w, z_sub_grid_max_h = map(max, zip(*(img.size for img in processed_result.images[:z_count]))) if draw_legend: - z_grid = images.draw_grid_annotations(z_grid, sub_grid_size[0], sub_grid_size[1], title_texts, [[images.GridAnnotation()]]) + z_grid = images.draw_grid_annotations(z_grid, z_sub_grid_max_w, z_sub_grid_max_h, title_texts, [[images.GridAnnotation()]]) processed_result.images.insert(0, z_grid) # TODO: Deeper aspects of the program rely on grid info being misaligned between metadata arrays, which is not ideal. # processed_result.all_prompts.insert(0, processed_result.all_prompts[0]) From 00e09382cd85c77ac35545c7264855823dfc2eb1 Mon Sep 17 00:00:00 2001 From: Silver Date: Mon, 10 Jun 2024 22:11:11 +0200 Subject: [PATCH 362/496] Add option to enable clip skip for clip L on SDXL --- modules/sd_hijack_clip.py | 2 ++ modules/shared_options.py | 1 + 2 files changed, 3 insertions(+) diff --git a/modules/sd_hijack_clip.py b/modules/sd_hijack_clip.py index 6ef10ac7..3db047df 100644 --- a/modules/sd_hijack_clip.py +++ b/modules/sd_hijack_clip.py @@ -355,6 +355,8 @@ class FrozenCLIPEmbedderForSDXLWithCustomWords(FrozenCLIPEmbedderWithCustomWords if self.wrapped.layer == "last": z = outputs.last_hidden_state + elif opts.sdxl_clip_l_skip is True: + z = outputs.hidden_states[-opts.CLIP_stop_at_last_layers] else: z = outputs.hidden_states[self.wrapped.layer_idx] diff --git a/modules/shared_options.py b/modules/shared_options.py index 104d8a54..51d51d8e 100644 --- a/modules/shared_options.py +++ b/modules/shared_options.py @@ -176,6 +176,7 @@ options_templates.update(options_section(('sd', "Stable Diffusion", "sd"), { "emphasis": OptionInfo("Original", "Emphasis mode", gr.Radio, lambda: {"choices": [x.name for x in sd_emphasis.options]}, infotext="Emphasis").info("makes it possible to make model to pay (more:1.1) or (less:0.9) attention to text when you use the syntax in prompt; " + sd_emphasis.get_options_descriptions()), "enable_batch_seeds": OptionInfo(True, "Make K-diffusion samplers produce same images in a batch as when making a single image"), "comma_padding_backtrack": OptionInfo(20, "Prompt word wrap length limit", gr.Slider, {"minimum": 0, "maximum": 74, "step": 1}).info("in tokens - for texts shorter than specified, if they don't fit into 75 token limit, move them to the next 75 token chunk"), + "sdxl_clip_l_skip": OptionInfo(False, "Clip skip SDXL", gr.Checkbox).info("Enable Clip skip for the secondary clip model in sdxl. Has no effect on SD 1.5 or SD 2.0/2.1."), "CLIP_stop_at_last_layers": OptionInfo(1, "Clip skip", gr.Slider, {"minimum": 1, "maximum": 12, "step": 1}, infotext="Clip skip").link("wiki", "https://github.com/AUTOMATIC1111/stable-diffusion-webui/wiki/Features#clip-skip").info("ignore last layers of CLIP network; 1 ignores none, 2 ignores one layer"), "upcast_attn": OptionInfo(False, "Upcast cross attention layer to float32"), "randn_source": OptionInfo("GPU", "Random number generator source.", gr.Radio, {"choices": ["GPU", "CPU", "NV"]}, infotext="RNG").info("changes seeds drastically; use CPU to produce the same picture across different videocard vendors; use NV to produce same picture as on NVidia videocards"), From 91ecc750bebcd25c5ad970a7bddc8f7603a136d7 Mon Sep 17 00:00:00 2001 From: Silver <65376327+silveroxides@users.noreply.github.com> Date: Tue, 11 Jun 2024 00:40:26 +0200 Subject: [PATCH 363/496] Update sd_hijack_clip.py --- modules/sd_hijack_clip.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/modules/sd_hijack_clip.py b/modules/sd_hijack_clip.py index 3db047df..355df3d3 100644 --- a/modules/sd_hijack_clip.py +++ b/modules/sd_hijack_clip.py @@ -353,10 +353,10 @@ class FrozenCLIPEmbedderForSDXLWithCustomWords(FrozenCLIPEmbedderWithCustomWords def encode_with_transformers(self, tokens): outputs = self.wrapped.transformer(input_ids=tokens, output_hidden_states=self.wrapped.layer == "hidden") - if self.wrapped.layer == "last": - z = outputs.last_hidden_state - elif opts.sdxl_clip_l_skip is True: + 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 else: z = outputs.hidden_states[self.wrapped.layer_idx] From 1f8f3a6e8bdbc687bc449aa3ce39bf4bb668f6f1 Mon Sep 17 00:00:00 2001 From: YSH Date: Tue, 11 Jun 2024 16:50:00 -0700 Subject: [PATCH 364/496] feat: prevent screen sleep during generation --- javascript/progressbar.js | 25 +++++++++++++++++++++++++ modules/shared_options.py | 1 + 2 files changed, 26 insertions(+) diff --git a/javascript/progressbar.js b/javascript/progressbar.js index f068bac6..00b0429b 100644 --- a/javascript/progressbar.js +++ b/javascript/progressbar.js @@ -76,6 +76,28 @@ function requestProgress(id_task, progressbarContainer, gallery, atEnd, onProgre var dateStart = new Date(); var wasEverActive = false; var parentProgressbar = progressbarContainer.parentNode; + var wakeLock = null; + + var requestWakeLock = async function() { + if (!opts.prevent_screen_sleep_during_generation) return; + try { + wakeLock = await navigator.wakeLock.request('screen'); + console.log('Wake Lock is active.'); + } catch (err) { + console.log('Wake Lock is not supported.'); + } + }; + + var releaseWakeLock = async function() { + if (!opts.prevent_screen_sleep_during_generation || !wakeLock) return; + try { + await wakeLock.release(); + console.log('Wake Lock is released.'); + wakeLock = null; + } catch (err) { + console.error('Wake Lock release failed', err); + } + }; var divProgress = document.createElement('div'); divProgress.className = 'progressDiv'; @@ -89,6 +111,7 @@ function requestProgress(id_task, progressbarContainer, gallery, atEnd, onProgre var livePreview = null; var removeProgressBar = function() { + releaseWakeLock(); if (!divProgress) return; setTitle(""); @@ -100,6 +123,8 @@ function requestProgress(id_task, progressbarContainer, gallery, atEnd, onProgre }; var funProgress = function(id_task) { + // Request the wake lock at the start of the progress + requestWakeLock(); request("./internal/progress", {id_task: id_task, live_preview: false}, function(res) { if (res.completed) { removeProgressBar(); diff --git a/modules/shared_options.py b/modules/shared_options.py index 326a317e..3741cf1f 100644 --- a/modules/shared_options.py +++ b/modules/shared_options.py @@ -359,6 +359,7 @@ options_templates.update(options_section(('ui', "Live previews", "ui"), { "live_preview_refresh_period": OptionInfo(1000, "Progressbar and preview update period").info("in milliseconds"), "live_preview_fast_interrupt": OptionInfo(False, "Return image with chosen live preview method on interrupt").info("makes interrupts faster"), "js_live_preview_in_modal_lightbox": OptionInfo(False, "Show Live preview in full page image viewer"), + "prevent_screen_sleep_during_generation": OptionInfo(True, "Prevent screen sleep during generation"), })) options_templates.update(options_section(('sampler-params', "Sampler parameters", "sd"), { From c803e11505cae54c7e8e467cd773b2053c2bfc38 Mon Sep 17 00:00:00 2001 From: YSH Date: Tue, 11 Jun 2024 18:14:32 -0700 Subject: [PATCH 365/496] fix: prevent create multiple wake lock --- javascript/progressbar.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/javascript/progressbar.js b/javascript/progressbar.js index 00b0429b..751fc096 100644 --- a/javascript/progressbar.js +++ b/javascript/progressbar.js @@ -79,7 +79,7 @@ function requestProgress(id_task, progressbarContainer, gallery, atEnd, onProgre var wakeLock = null; var requestWakeLock = async function() { - if (!opts.prevent_screen_sleep_during_generation) return; + if (!opts.prevent_screen_sleep_during_generation || wakeLock) return; try { wakeLock = await navigator.wakeLock.request('screen'); console.log('Wake Lock is active.'); From f1e0bfebfc9418f14f36ea255162ed1eaba3a62f Mon Sep 17 00:00:00 2001 From: YSH Date: Tue, 11 Jun 2024 22:33:11 -0700 Subject: [PATCH 366/496] ci: remove comments and console logs --- javascript/progressbar.js | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/javascript/progressbar.js b/javascript/progressbar.js index 751fc096..23dea64c 100644 --- a/javascript/progressbar.js +++ b/javascript/progressbar.js @@ -82,9 +82,8 @@ function requestProgress(id_task, progressbarContainer, gallery, atEnd, onProgre if (!opts.prevent_screen_sleep_during_generation || wakeLock) return; try { wakeLock = await navigator.wakeLock.request('screen'); - console.log('Wake Lock is active.'); } catch (err) { - console.log('Wake Lock is not supported.'); + console.error('Wake Lock is not supported.'); } }; @@ -92,7 +91,6 @@ function requestProgress(id_task, progressbarContainer, gallery, atEnd, onProgre if (!opts.prevent_screen_sleep_during_generation || !wakeLock) return; try { await wakeLock.release(); - console.log('Wake Lock is released.'); wakeLock = null; } catch (err) { console.error('Wake Lock release failed', err); @@ -123,7 +121,6 @@ function requestProgress(id_task, progressbarContainer, gallery, atEnd, onProgre }; var funProgress = function(id_task) { - // Request the wake lock at the start of the progress requestWakeLock(); request("./internal/progress", {id_task: id_task, live_preview: false}, function(res) { if (res.completed) { From 9e0f6d2012bfdd11c0b8a4bd42e176e034c4848c Mon Sep 17 00:00:00 2001 From: w-e-w <40751091+w-e-w@users.noreply.github.com> Date: Tue, 11 Jun 2024 21:06:36 +0900 Subject: [PATCH 367/496] remove commented code --- scripts/xyz_grid.py | 1 - 1 file changed, 1 deletion(-) diff --git a/scripts/xyz_grid.py b/scripts/xyz_grid.py index 606d72d4..b184721b 100644 --- a/scripts/xyz_grid.py +++ b/scripts/xyz_grid.py @@ -382,7 +382,6 @@ def draw_xyz_grid(p, xs, ys, zs, x_labels, y_labels, z_labels, cell, draw_legend processed_result.all_seeds.insert(i, processed_result.all_seeds[start_index]) processed_result.infotexts.insert(i, processed_result.infotexts[start_index]) - # sub_grid_size = processed_result.images[0].size z_grid = images.image_grid(processed_result.images[:z_count], rows=1) z_sub_grid_max_w, z_sub_grid_max_h = map(max, zip(*(img.size for img in processed_result.images[:z_count]))) if draw_legend: From a7116aa9a11c7d7d10df81aa52ad5ec33f4b6db6 Mon Sep 17 00:00:00 2001 From: "Alex \"mcmonkey\" Goodwin" Date: Sun, 16 Jun 2024 07:13:57 +0300 Subject: [PATCH 368/496] add SD3 reference implementation from https://github.com/mcmonkey4eva/sd3-ref/ --- modules/models/sd3/mmdit.py | 619 ++++++++++++++++++++++++++++++ modules/models/sd3/other_impls.py | 492 ++++++++++++++++++++++++ modules/models/sd3/sd3_impls.py | 371 ++++++++++++++++++ 3 files changed, 1482 insertions(+) create mode 100644 modules/models/sd3/mmdit.py create mode 100644 modules/models/sd3/other_impls.py create mode 100644 modules/models/sd3/sd3_impls.py diff --git a/modules/models/sd3/mmdit.py b/modules/models/sd3/mmdit.py new file mode 100644 index 00000000..6d8b65bd --- /dev/null +++ b/modules/models/sd3/mmdit.py @@ -0,0 +1,619 @@ +### 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 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 # +################################################################################# + + +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 = nn.Linear(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__() + print(f"mmdit initializing with: {input_size=}, {patch_size=}, {in_channels=}, {depth=}, {mlp_ratio=}, {learn_sigma=}, {adm_in_channels=}, {context_embedder_config=}, {register_length=}, {attn_mode=}, {rmsnorm=}, {scale_mod_only=}, {swiglu=}, {out_channels=}, {pos_embed_scaling_factor=}, {pos_embed_offset=}, {pos_embed_max_size=}, {num_patches=}, {qk_norm=}, {qkv_bias=}, {dtype=}, {device=}") + 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 diff --git a/modules/models/sd3/other_impls.py b/modules/models/sd3/other_impls.py new file mode 100644 index 00000000..2c76e1cb --- /dev/null +++ b/modules/models/sd3/other_impls.py @@ -0,0 +1,492 @@ +### This file contains impls for underlying related models (CLIP, T5, etc) + +import torch, math +from torch import nn +from transformers import CLIPTokenizer, T5TokenizerFast + + +################################################################################################# +### Core/Utility +################################################################################################# + + +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 = map(lambda t: t.view(b, -1, heads, dim_head).transpose(1, 2), (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, l in enumerate(self.layers): + x = l(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): + super().__init__() + self.token_embedding = torch.nn.Embedding(vocab_size, embed_dim, dtype=dtype, device=device) + 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) + 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 = list(map(lambda a: a[0], 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={"start": 49406, "end": 49407, "pad": 49407}, 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 + 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() + device = backup_embeds.weight.device + tokens = torch.LongTensor(tokens).to(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 = nn.Linear(model_dim, ff_dim, bias=False, dtype=dtype, device=device) + self.wi_1 = nn.Linear(model_dim, ff_dim, bias=False, dtype=dtype, device=device) + self.wo = nn.Linear(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 = nn.Linear(model_dim, inner_dim, bias=False, dtype=dtype, device=device) + self.k = nn.Linear(model_dim, inner_dim, bias=False, dtype=dtype, device=device) + self.v = nn.Linear(model_dim, inner_dim, bias=False, dtype=dtype, device=device) + self.o = nn.Linear(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 + out = attention(q, k * ((k.shape[-1] / self.num_heads) ** 0.5), v, self.num_heads, mask) + 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) + past_bias = None + for i, l in enumerate(self.block): + x, past_bias = l(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) diff --git a/modules/models/sd3/sd3_impls.py b/modules/models/sd3/sd3_impls.py new file mode 100644 index 00000000..6e9d0a4d --- /dev/null +++ b/modules/models/sd3/sd3_impls.py @@ -0,0 +1,371 @@ +### Impls of the SD3 core diffusion model and VAE + +import torch, math, einops +from 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, file=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 = file.get_tensor(f"{prefix}x_embedder.proj.weight").shape[2] + depth = file.get_tensor(f"{prefix}x_embedder.proj.weight").shape[0] // 64 + num_patches = file.get_tensor(f"{prefix}pos_embed").shape[1] + pos_embed_max_size = round(math.sqrt(num_patches)) + adm_in_channels = file.get_tensor(f"{prefix}y_embedder.mlp.0.weight").shape[1] + context_shape = file.get_tensor(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) + + 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 = map(lambda x: einops.rearrange(x, "b c h w -> b 1 (h w) c").contiguous(), (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 i_block 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 i_block 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) From 5b2a60b8e2b7fb1221359047cbe9bc1f6cf0c51d Mon Sep 17 00:00:00 2001 From: AUTOMATIC1111 <16777216c@gmail.com> Date: Sun, 16 Jun 2024 08:04:31 +0300 Subject: [PATCH 369/496] initial SD3 support --- README.md | 2 +- configs/sd3-inference.yaml | 5 + extensions-builtin/Lora/networks.py | 4 +- modules/models/sd3/mmdit.py | 3 +- modules/models/sd3/sd3_impls.py | 14 +-- modules/models/sd3/sd3_model.py | 166 ++++++++++++++++++++++++++++ modules/processing.py | 3 +- modules/sd_models.py | 87 ++++++++++++--- modules/sd_models_config.py | 7 +- modules/sd_models_types.py | 6 + modules/sd_samplers_common.py | 4 +- modules/sd_samplers_kdiffusion.py | 9 +- modules/sd_vae_approx.py | 27 ++++- modules/sd_vae_taesd.py | 40 +++++-- 14 files changed, 333 insertions(+), 44 deletions(-) create mode 100644 configs/sd3-inference.yaml create mode 100644 modules/models/sd3/sd3_model.py diff --git a/README.md b/README.md index bc08e7ad..fc582e15 100644 --- a/README.md +++ b/README.md @@ -150,7 +150,7 @@ For the purposes of getting Google and other search engines to crawl the wiki, h ## Credits 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 - Spandrel - https://github.com/chaiNNer-org/spandrel implementing - GFPGAN - https://github.com/TencentARC/GFPGAN.git diff --git a/configs/sd3-inference.yaml b/configs/sd3-inference.yaml new file mode 100644 index 00000000..bccb69d2 --- /dev/null +++ b/configs/sd3-inference.yaml @@ -0,0 +1,5 @@ +model: + target: modules.models.sd3.sd3_model.SD3Inferencer + params: + shift: 3 + state_dict: null diff --git a/extensions-builtin/Lora/networks.py b/extensions-builtin/Lora/networks.py index 8869d2c8..63e8c946 100644 --- a/extensions-builtin/Lora/networks.py +++ b/extensions-builtin/Lora/networks.py @@ -130,7 +130,9 @@ def assign_network_names_to_compvis_modules(sd_model): network_layer_mapping[network_name] = module module.network_layer_name = network_name 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_layer_mapping[network_name] = module module.network_layer_name = network_name diff --git a/modules/models/sd3/mmdit.py b/modules/models/sd3/mmdit.py index 6d8b65bd..5ec73c05 100644 --- a/modules/models/sd3/mmdit.py +++ b/modules/models/sd3/mmdit.py @@ -6,7 +6,8 @@ import numpy as np import torch import torch.nn as nn from einops import rearrange, repeat -from other_impls import attention, Mlp +from modules.models.sd3.other_impls import attention, Mlp + class PatchEmbed(nn.Module): """ 2D Image to Patch Embedding""" diff --git a/modules/models/sd3/sd3_impls.py b/modules/models/sd3/sd3_impls.py index 6e9d0a4d..91dad66d 100644 --- a/modules/models/sd3/sd3_impls.py +++ b/modules/models/sd3/sd3_impls.py @@ -1,7 +1,7 @@ ### Impls of the SD3 core diffusion model and VAE import torch, math, einops -from mmdit import MMDiT +from modules.models.sd3.mmdit import MMDiT from PIL import Image @@ -46,16 +46,16 @@ class ModelSamplingDiscreteFlow(torch.nn.Module): class BaseModel(torch.nn.Module): """Wrapper around the core MM-DiT model""" - def __init__(self, shift=1.0, device=None, dtype=torch.float32, file=None, prefix=""): + 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 = file.get_tensor(f"{prefix}x_embedder.proj.weight").shape[2] - depth = file.get_tensor(f"{prefix}x_embedder.proj.weight").shape[0] // 64 - num_patches = file.get_tensor(f"{prefix}pos_embed").shape[1] + 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 = file.get_tensor(f"{prefix}y_embedder.mlp.0.weight").shape[1] - context_shape = file.get_tensor(f"{prefix}context_embedder.weight").shape + 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": { diff --git a/modules/models/sd3/sd3_model.py b/modules/models/sd3/sd3_model.py new file mode 100644 index 00000000..8b828524 --- /dev/null +++ b/modules/models/sd3/sd3_model.py @@ -0,0 +1,166 @@ +import contextlib +import os +from typing import Mapping + +import safetensors +import torch + +import k_diffusion +from modules.models.sd3.other_impls import SDClipModel, SDXLClipG, T5XXLModel, SD3Tokenizer +from modules.models.sd3.sd3_impls import BaseModel, SDVAE, SD3LatentFormat + +from modules import shared, modelloader, devices + +CLIPG_URL = "https://huggingface.co/stabilityai/stable-diffusion-3-medium/resolve/main/text_encoders/clip_g.safetensors" +CLIPG_CONFIG = { + "hidden_act": "gelu", + "hidden_size": 1280, + "intermediate_size": 5120, + "num_attention_heads": 20, + "num_hidden_layers": 32, +} + +CLIPL_URL = "https://huggingface.co/stabilityai/stable-diffusion-3-medium/resolve/main/text_encoders/clip_l.safetensors" +CLIPL_CONFIG = { + "hidden_act": "quick_gelu", + "hidden_size": 768, + "intermediate_size": 3072, + "num_attention_heads": 12, + "num_hidden_layers": 12, +} + +T5_URL = "https://huggingface.co/stabilityai/stable-diffusion-3-medium/resolve/main/text_encoders/t5xxl_fp16.safetensors" +T5_CONFIG = { + "d_ff": 10240, + "d_model": 4096, + "num_heads": 64, + "num_layers": 24, + "vocab_size": 32128, +} + + +class SafetensorsMapping(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) + + +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=torch.float32) + self.clip_l = SDClipModel(layer="hidden", layer_idx=-2, device="cpu", dtype=torch.float32, layer_norm_hidden_state=False, return_projected_pooled=False, textmodel_json_config=CLIPL_CONFIG) + self.t5xxl = T5XXLModel(T5_CONFIG, device="cpu", dtype=torch.float32) + + self.weights_loaded = False + + def forward(self, prompts: list[str]): + res = [] + + for prompt in prompts: + tokens = self.tokenizer.tokenize_with_weights(prompt) + l_out, l_pooled = self.clip_l.encode_token_weights(tokens["l"]) + g_out, g_pooled = self.clip_g.encode_token_weights(tokens["g"]) + t5_out, t5_pooled = self.t5xxl.encode_token_weights(tokens["t5xxl"]) + lg_out = torch.cat([l_out, g_out], dim=-1) + lg_out = torch.nn.functional.pad(lg_out, (0, 4096 - lg_out.shape[-1])) + lgt_out = torch.cat([lg_out, t5_out], dim=-2) + vector_out = torch.cat((l_pooled, g_pooled), dim=-1) + + res.append({ + 'crossattn': lgt_out[0].to(devices.device), + 'vector': vector_out[0].to(devices.device), + }) + + return res + + def load_weights(self): + if self.weights_loaded: + return + + clip_path = os.path.join(shared.models_path, "CLIP") + + 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)) + + 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) + + 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) + + self.weights_loaded = True + + def encode_embedding_init_text(self, init_text, nvpt): + return torch.tensor([[0]], device=devices.device) # XXX + + +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.cond_stage_model = SD3Cond() + self.cond_stage_key = 'txt' + + self.parameterization = "eps" + self.model.conditioning_key = "crossattn" + + self.latent_format = SD3LatentFormat() + self.latent_channels = 16 + + def after_load_weights(self): + self.cond_stage_model.load_weights() + + 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.apply_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 create_denoiser(self): + return SD3Denoiser(self, self.model.model_sampling.sigmas) diff --git a/modules/processing.py b/modules/processing.py index 79a3f0a7..d32a1811 100644 --- a/modules/processing.py +++ b/modules/processing.py @@ -942,7 +942,8 @@ def process_images_inner(p: StableDiffusionProcessing) -> Processed: 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.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: p.scripts.before_process_batch(p, batch_number=n, prompts=p.prompts, seeds=p.seeds, subseeds=p.subseeds) diff --git a/modules/sd_models.py b/modules/sd_models.py index af35187c..21a98c1d 100644 --- a/modules/sd_models.py +++ b/modules/sd_models.py @@ -1,7 +1,9 @@ import collections +import importlib import os import sys import threading +import enum import torch import re @@ -10,8 +12,6 @@ from omegaconf import OmegaConf, ListConfig from urllib import request import ldm.modules.midas as midas -from ldm.util import instantiate_from_config - from modules import paths, shared, modelloader, devices, script_callbacks, sd_vae, sd_disable_initialization, errors, hashes, sd_models_config, sd_unet, sd_models_xl, cache, extra_networks, processing, lowvram, sd_hijack, patches from modules.timer import Timer from modules.shared import opts @@ -27,6 +27,14 @@ checkpoint_alisases = checkpoint_aliases # for compatibility with old name checkpoints_loaded = collections.OrderedDict() +class ModelType(enum.Enum): + SD1 = 1 + SD2 = 2 + SDXL = 3 + SSD = 4 + SD3 = 5 + + def replace_key(d, key, new_key, value): keys = list(d.keys()) @@ -368,6 +376,36 @@ def check_fp8(model): return enable_fp8 +def set_model_type(model, state_dict): + model.is_sd1 = False + model.is_sd2 = False + model.is_sdxl = False + model.is_ssd = False + model.is_ssd3 = False + + if "model.diffusion_model.x_embedder.proj.weight" in state_dict: + model.is_sd3 = True + model.model_type = ModelType.SD3 + elif hasattr(model, 'conditioner'): + model.is_sdxl = True + + if 'model.diffusion_model.middle_block.1.transformer_blocks.0.attn1.to_q.weight' not in state_dict.keys(): + model.is_ssd = True + model.model_type = ModelType.SSD + else: + model.model_type = ModelType.SDXL + elif hasattr(model.cond_stage_model, 'model'): + model.is_sd2 = True + model.model_type = ModelType.SD2 + else: + model.is_sd1 = True + model.model_type = ModelType.SD1 + + +def set_model_fields(model): + if not hasattr(model, 'latent_channels'): + model.latent_channels = 4 + def load_model_weights(model, checkpoint_info: CheckpointInfo, state_dict, timer): sd_model_hash = checkpoint_info.calculate_shorthash() timer.record("calculate hash") @@ -382,10 +420,9 @@ def load_model_weights(model, checkpoint_info: CheckpointInfo, state_dict, timer if state_dict is None: state_dict = get_checkpoint_state_dict(checkpoint_info, timer) - model.is_sdxl = hasattr(model, 'conditioner') - model.is_sd2 = not model.is_sdxl and hasattr(model.cond_stage_model, 'model') - model.is_sd1 = not model.is_sdxl and not model.is_sd2 - model.is_ssd = model.is_sdxl and 'model.diffusion_model.middle_block.1.transformer_blocks.0.attn1.to_q.weight' not in state_dict.keys() + set_model_type(model, state_dict) + set_model_fields(model) + if model.is_sdxl: sd_models_xl.extend_sdxl(model) @@ -552,8 +589,7 @@ def patch_given_betas(): original_register_schedule = patches.patch(__name__, ldm.models.diffusion.ddpm.DDPM, 'register_schedule', patched_register_schedule) -def repair_config(sd_config): - +def repair_config(sd_config, state_dict=None): if not hasattr(sd_config.model.params, "use_ema"): sd_config.model.params.use_ema = False @@ -563,8 +599,9 @@ def repair_config(sd_config): elif shared.cmd_opts.upcast_sampling or shared.cmd_opts.precision == "half": sd_config.model.params.unet_config.params.use_fp16 = True - if getattr(sd_config.model.params.first_stage_config.params.ddconfig, "attn_type", None) == "vanilla-xformers" and not shared.xformers_available: - sd_config.model.params.first_stage_config.params.ddconfig.attn_type = "vanilla" + if hasattr(sd_config.model.params, 'first_stage_config'): + if getattr(sd_config.model.params.first_stage_config.params.ddconfig, "attn_type", None) == "vanilla-xformers" and not shared.xformers_available: + sd_config.model.params.first_stage_config.params.ddconfig.attn_type = "vanilla" # For UnCLIP-L, override the hardcoded karlo directory if hasattr(sd_config.model.params, "noise_aug_config") and hasattr(sd_config.model.params.noise_aug_config.params, "clip_stats_path"): @@ -580,6 +617,7 @@ def repair_config(sd_config): sd_config.model.params.unet_config.params.use_checkpoint = False + def rescale_zero_terminal_snr_abar(alphas_cumprod): alphas_bar_sqrt = alphas_cumprod.sqrt() @@ -715,6 +753,25 @@ def send_model_to_trash(m): devices.torch_gc() +def instantiate_from_config(config, state_dict=None): + constructor = get_obj_from_str(config["target"]) + + params = {**config.get("params", {})} + + if state_dict and "state_dict" in params and params["state_dict"] is None: + params["state_dict"] = state_dict + + return constructor(**params) + + +def get_obj_from_str(string, reload=False): + module, cls = string.rsplit(".", 1) + if reload: + module_imp = importlib.import_module(module) + importlib.reload(module_imp) + return getattr(importlib.import_module(module, package=None), cls) + + def load_model(checkpoint_info=None, already_loaded_state_dict=None): from modules import sd_hijack checkpoint_info = checkpoint_info or select_checkpoint() @@ -739,7 +796,7 @@ def load_model(checkpoint_info=None, already_loaded_state_dict=None): timer.record("find config") sd_config = OmegaConf.load(checkpoint_config) - repair_config(sd_config) + repair_config(sd_config, state_dict) timer.record("load config") @@ -749,7 +806,7 @@ def load_model(checkpoint_info=None, already_loaded_state_dict=None): try: with sd_disable_initialization.DisableInitialization(disable_clip=clip_is_included_into_sd or shared.cmd_opts.do_not_download_clip): with sd_disable_initialization.InitializeOnMeta(): - sd_model = instantiate_from_config(sd_config.model) + sd_model = instantiate_from_config(sd_config.model, state_dict) except Exception as e: errors.display(e, "creating model quickly", full_traceback=True) @@ -758,7 +815,7 @@ def load_model(checkpoint_info=None, already_loaded_state_dict=None): print('Failed to create model quickly; will retry using slow method.', file=sys.stderr) with sd_disable_initialization.InitializeOnMeta(): - sd_model = instantiate_from_config(sd_config.model) + sd_model = instantiate_from_config(sd_config.model, state_dict) sd_model.used_config = checkpoint_config @@ -775,6 +832,10 @@ def load_model(checkpoint_info=None, already_loaded_state_dict=None): with sd_disable_initialization.LoadStateDictOnMeta(state_dict, device=model_target_device(sd_model), weight_dtype_conversion=weight_dtype_conversion): load_model_weights(sd_model, checkpoint_info, state_dict, timer) + + if hasattr(sd_model, "after_load_weights"): + sd_model.after_load_weights() + timer.record("load weights from state dict") send_model_to_device(sd_model) diff --git a/modules/sd_models_config.py b/modules/sd_models_config.py index 9cec4f13..7cfeca67 100644 --- a/modules/sd_models_config.py +++ b/modules/sd_models_config.py @@ -23,6 +23,8 @@ config_inpainting = os.path.join(sd_configs_path, "v1-inpainting-inference.yaml" config_instruct_pix2pix = os.path.join(sd_configs_path, "instruct-pix2pix.yaml") config_alt_diffusion = os.path.join(sd_configs_path, "alt-diffusion-inference.yaml") config_alt_diffusion_m18 = os.path.join(sd_configs_path, "alt-diffusion-m18-inference.yaml") +config_sd3 = os.path.join(sd_configs_path, "sd3-inference.yaml") + def is_using_v_parameterization_for_sd2(state_dict): """ @@ -71,11 +73,15 @@ def guess_model_config_from_state_dict(sd, filename): diffusion_model_input = sd.get('model.diffusion_model.input_blocks.0.0.weight', None) sd2_variations_weight = sd.get('embedder.model.ln_final.weight', None) + if "model.diffusion_model.x_embedder.proj.weight" in sd: + return config_sd3 + if sd.get('conditioner.embedders.1.model.ln_final.weight', None) is not None: if diffusion_model_input.shape[1] == 9: return config_sdxl_inpainting else: return config_sdxl + if sd.get('conditioner.embedders.0.model.ln_final.weight', None) is not None: return config_sdxl_refiner elif sd.get('depth_model.model.pretrained.act_postprocess3.0.project.0.bias', None) is not None: @@ -99,7 +105,6 @@ def guess_model_config_from_state_dict(sd, filename): if diffusion_model_input.shape[1] == 8: return config_instruct_pix2pix - if sd.get('cond_stage_model.roberta.embeddings.word_embeddings.weight', None) is not None: if sd.get('cond_stage_model.transformation.weight').size()[0] == 1024: return config_alt_diffusion_m18 diff --git a/modules/sd_models_types.py b/modules/sd_models_types.py index f911fbb6..2fce2777 100644 --- a/modules/sd_models_types.py +++ b/modules/sd_models_types.py @@ -32,3 +32,9 @@ class WebuiSdModel(LatentDiffusion): is_sd1: bool """True if the model's architecture is SD 1.x""" + + is_sd3: bool + """True if the model's architecture is SD 3""" + + latent_channels: int + """number of layer in latent image representation; will be 16 in SD3 and 4 in other version""" diff --git a/modules/sd_samplers_common.py b/modules/sd_samplers_common.py index bda578cc..b584b68a 100644 --- a/modules/sd_samplers_common.py +++ b/modules/sd_samplers_common.py @@ -54,7 +54,7 @@ def samples_to_images_tensor(sample, approximation=None, model=None): else: if model is None: model = shared.sd_model - with devices.without_autocast(): # fixes an issue with unstable VAEs that are flaky even in fp32 + with torch.no_grad(), devices.without_autocast(): # fixes an issue with unstable VAEs that are flaky even in fp32 x_sample = model.decode_first_stage(sample.to(model.first_stage_model.dtype)) return x_sample @@ -246,7 +246,7 @@ class Sampler: self.eta_infotext_field = 'Eta' self.eta_default = 1.0 - self.conditioning_key = shared.sd_model.model.conditioning_key + self.conditioning_key = getattr(shared.sd_model.model, 'conditioning_key', 'crossattn') self.p = None self.model_wrap_cfg = None diff --git a/modules/sd_samplers_kdiffusion.py b/modules/sd_samplers_kdiffusion.py index 64e14e0c..cede0760 100644 --- a/modules/sd_samplers_kdiffusion.py +++ b/modules/sd_samplers_kdiffusion.py @@ -53,8 +53,13 @@ class CFGDenoiserKDiffusion(sd_samplers_cfg_denoiser.CFGDenoiser): @property def inner_model(self): if self.model_wrap is None: - denoiser = k_diffusion.external.CompVisVDenoiser if shared.sd_model.parameterization == "v" else k_diffusion.external.CompVisDenoiser - self.model_wrap = denoiser(shared.sd_model, quantize=shared.opts.enable_quantization) + denoiser_constructor = getattr(shared.sd_model, 'create_denoiser', None) + + if denoiser_constructor is not None: + self.model_wrap = denoiser_constructor() + else: + denoiser = k_diffusion.external.CompVisVDenoiser if shared.sd_model.parameterization == "v" else k_diffusion.external.CompVisDenoiser + self.model_wrap = denoiser(shared.sd_model, quantize=shared.opts.enable_quantization) return self.model_wrap diff --git a/modules/sd_vae_approx.py b/modules/sd_vae_approx.py index 3965e223..c5dda743 100644 --- a/modules/sd_vae_approx.py +++ b/modules/sd_vae_approx.py @@ -8,9 +8,9 @@ sd_vae_approx_models = {} class VAEApprox(nn.Module): - def __init__(self): + def __init__(self, latent_channels=4): super(VAEApprox, self).__init__() - self.conv1 = nn.Conv2d(4, 8, (7, 7)) + self.conv1 = nn.Conv2d(latent_channels, 8, (7, 7)) self.conv2 = nn.Conv2d(8, 16, (5, 5)) self.conv3 = nn.Conv2d(16, 32, (3, 3)) self.conv4 = nn.Conv2d(32, 64, (3, 3)) @@ -40,7 +40,13 @@ def download_model(model_path, model_url): def model(): - model_name = "vaeapprox-sdxl.pt" if getattr(shared.sd_model, 'is_sdxl', False) else "model.pt" + if shared.sd_model.is_sd3: + model_name = "vaeapprox-sd3.pt" + elif shared.sd_model.is_sdxl: + model_name = "vaeapprox-sdxl.pt" + else: + model_name = "model.pt" + loaded_model = sd_vae_approx_models.get(model_name) if loaded_model is None: @@ -52,7 +58,7 @@ def model(): model_path = os.path.join(paths.models_path, "VAE-approx", model_name) download_model(model_path, 'https://github.com/AUTOMATIC1111/stable-diffusion-webui/releases/download/v1.0.0-pre/' + model_name) - loaded_model = VAEApprox() + loaded_model = VAEApprox(latent_channels=shared.sd_model.latent_channels) loaded_model.load_state_dict(torch.load(model_path, map_location='cpu' if devices.device.type != 'cuda' else None)) loaded_model.eval() loaded_model.to(devices.device, devices.dtype) @@ -64,7 +70,18 @@ def model(): def cheap_approximation(sample): # https://discuss.huggingface.co/t/decoding-latents-to-rgb-without-upscaling/23204/2 - if shared.sd_model.is_sdxl: + if shared.sd_model.is_sd3: + coeffs = [ + [-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], + ] + elif shared.sd_model.is_sdxl: coeffs = [ [ 0.3448, 0.4168, 0.4395], [-0.1953, -0.0290, 0.0250], diff --git a/modules/sd_vae_taesd.py b/modules/sd_vae_taesd.py index 808eb362..d06253d2 100644 --- a/modules/sd_vae_taesd.py +++ b/modules/sd_vae_taesd.py @@ -34,9 +34,9 @@ class Block(nn.Module): return self.fuse(self.conv(x) + self.skip(x)) -def decoder(): +def decoder(latent_channels=4): return nn.Sequential( - Clamp(), conv(4, 64), nn.ReLU(), + Clamp(), conv(latent_channels, 64), nn.ReLU(), Block(64, 64), Block(64, 64), Block(64, 64), nn.Upsample(scale_factor=2), conv(64, 64, bias=False), Block(64, 64), Block(64, 64), Block(64, 64), nn.Upsample(scale_factor=2), conv(64, 64, bias=False), Block(64, 64), Block(64, 64), Block(64, 64), nn.Upsample(scale_factor=2), conv(64, 64, bias=False), @@ -44,13 +44,13 @@ def decoder(): ) -def encoder(): +def encoder(latent_channels=4): return nn.Sequential( conv(3, 64), Block(64, 64), conv(64, 64, stride=2, bias=False), Block(64, 64), Block(64, 64), Block(64, 64), conv(64, 64, stride=2, bias=False), Block(64, 64), Block(64, 64), Block(64, 64), conv(64, 64, stride=2, bias=False), Block(64, 64), Block(64, 64), Block(64, 64), - conv(64, 4), + conv(64, latent_channels), ) @@ -58,10 +58,14 @@ class TAESDDecoder(nn.Module): latent_magnitude = 3 latent_shift = 0.5 - def __init__(self, decoder_path="taesd_decoder.pth"): + def __init__(self, decoder_path="taesd_decoder.pth", latent_channels=None): """Initialize pretrained TAESD on the given device from the given checkpoints.""" super().__init__() - self.decoder = decoder() + + if latent_channels is None: + latent_channels = 16 if "taesd3" in str(decoder_path) else 4 + + self.decoder = decoder(latent_channels) self.decoder.load_state_dict( torch.load(decoder_path, map_location='cpu' if devices.device.type != 'cuda' else None)) @@ -70,10 +74,14 @@ class TAESDEncoder(nn.Module): latent_magnitude = 3 latent_shift = 0.5 - def __init__(self, encoder_path="taesd_encoder.pth"): + def __init__(self, encoder_path="taesd_encoder.pth", latent_channels=None): """Initialize pretrained TAESD on the given device from the given checkpoints.""" super().__init__() - self.encoder = encoder() + + if latent_channels is None: + latent_channels = 16 if "taesd3" in str(encoder_path) else 4 + + self.encoder = encoder(latent_channels) self.encoder.load_state_dict( torch.load(encoder_path, map_location='cpu' if devices.device.type != 'cuda' else None)) @@ -87,7 +95,13 @@ def download_model(model_path, model_url): def decoder_model(): - model_name = "taesdxl_decoder.pth" if getattr(shared.sd_model, 'is_sdxl', False) else "taesd_decoder.pth" + if shared.sd_model.is_sd3: + model_name = "taesd3_decoder.pth" + elif shared.sd_model.is_sdxl: + model_name = "taesdxl_decoder.pth" + else: + model_name = "taesd_decoder.pth" + loaded_model = sd_vae_taesd_models.get(model_name) if loaded_model is None: @@ -106,7 +120,13 @@ def decoder_model(): def encoder_model(): - model_name = "taesdxl_encoder.pth" if getattr(shared.sd_model, 'is_sdxl', False) else "taesd_encoder.pth" + if shared.sd_model.is_sd3: + model_name = "taesd3_encoder.pth" + elif shared.sd_model.is_sdxl: + model_name = "taesdxl_encoder.pth" + else: + model_name = "taesd_encoder.pth" + loaded_model = sd_vae_taesd_models.get(model_name) if loaded_model is None: From 79de09c3df95a54723dbd0676444e9e4fa6f8990 Mon Sep 17 00:00:00 2001 From: AUTOMATIC1111 <16777216c@gmail.com> Date: Sun, 16 Jun 2024 08:13:23 +0300 Subject: [PATCH 370/496] linter --- modules/models/sd3/other_impls.py | 19 ++++++++++--------- modules/models/sd3/sd3_impls.py | 10 ++++++---- 2 files changed, 16 insertions(+), 13 deletions(-) diff --git a/modules/models/sd3/other_impls.py b/modules/models/sd3/other_impls.py index 2c76e1cb..cd10edc8 100644 --- a/modules/models/sd3/other_impls.py +++ b/modules/models/sd3/other_impls.py @@ -1,6 +1,7 @@ ### This file contains impls for underlying related models (CLIP, T5, etc) -import torch, math +import torch +import math from torch import nn from transformers import CLIPTokenizer, T5TokenizerFast @@ -14,7 +15,7 @@ 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 = map(lambda t: t.view(b, -1, heads, dim_head).transpose(1, 2), (q, k, v)) + 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) @@ -89,8 +90,8 @@ class CLIPEncoder(torch.nn.Module): if intermediate_output < 0: intermediate_output = len(self.layers) + intermediate_output intermediate = None - for i, l in enumerate(self.layers): - x = l(x, mask) + for i, layer in enumerate(self.layers): + x = layer(x, mask) if i == intermediate_output: intermediate = x.clone() return x, intermediate @@ -215,7 +216,7 @@ class SD3Tokenizer: class ClipTokenWeightEncoder: def encode_token_weights(self, token_weight_pairs): - tokens = list(map(lambda a: a[0], token_weight_pairs[0])) + 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() @@ -229,7 +230,7 @@ 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={"start": 49406, "end": 49407, "pad": 49407}, layer_norm_hidden_state=True, return_projected_pooled=True): + 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) @@ -240,7 +241,7 @@ class SDClipModel(torch.nn.Module, ClipTokenWeightEncoder): param.requires_grad = False self.layer = layer self.layer_idx = None - self.special_tokens = special_tokens + 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 @@ -465,8 +466,8 @@ class T5Stack(torch.nn.Module): intermediate = None x = self.embed_tokens(input_ids) past_bias = None - for i, l in enumerate(self.block): - x, past_bias = l(x, past_bias) + 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) diff --git a/modules/models/sd3/sd3_impls.py b/modules/models/sd3/sd3_impls.py index 91dad66d..e2f6cad5 100644 --- a/modules/models/sd3/sd3_impls.py +++ b/modules/models/sd3/sd3_impls.py @@ -1,6 +1,8 @@ ### Impls of the SD3 core diffusion model and VAE -import torch, math, einops +import torch +import math +import einops from modules.models.sd3.mmdit import MMDiT from PIL import Image @@ -214,7 +216,7 @@ class AttnBlock(torch.nn.Module): k = self.k(hidden) v = self.v(hidden) b, c, h, w = q.shape - q, k, v = map(lambda x: einops.rearrange(x, "b c h w -> b 1 (h w) c").contiguous(), (q, k, v)) + 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) @@ -259,7 +261,7 @@ class VAEEncoder(torch.nn.Module): attn = torch.nn.ModuleList() block_in = ch*in_ch_mult[i_level] block_out = ch*ch_mult[i_level] - for i_block in range(num_res_blocks): + 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() @@ -318,7 +320,7 @@ class VAEDecoder(torch.nn.Module): for i_level in reversed(range(self.num_resolutions)): block = torch.nn.ModuleList() block_out = ch * ch_mult[i_level] - for i_block in range(self.num_res_blocks + 1): + 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() From 7ee2114cd9c401ceb390c141e604661055e0aaf4 Mon Sep 17 00:00:00 2001 From: AUTOMATIC1111 <16777216c@gmail.com> Date: Sun, 16 Jun 2024 08:18:05 +0300 Subject: [PATCH 371/496] typo --- modules/sd_models.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/modules/sd_models.py b/modules/sd_models.py index 21a98c1d..da083f71 100644 --- a/modules/sd_models.py +++ b/modules/sd_models.py @@ -381,7 +381,7 @@ def set_model_type(model, state_dict): model.is_sd2 = False model.is_sdxl = False model.is_ssd = False - model.is_ssd3 = False + model.is_sd3 = False if "model.diffusion_model.x_embedder.proj.weight" in state_dict: model.is_sd3 = True From b443fdcf7670d2a8bbd02936b0770957838f5e1d Mon Sep 17 00:00:00 2001 From: AUTOMATIC1111 <16777216c@gmail.com> Date: Sun, 16 Jun 2024 11:04:19 +0300 Subject: [PATCH 372/496] prevent accidental creation of CLIP models in float32 type when user wants float16 --- modules/models/sd3/sd3_model.py | 6 +++--- modules/sd_models.py | 1 + 2 files changed, 4 insertions(+), 3 deletions(-) diff --git a/modules/models/sd3/sd3_model.py b/modules/models/sd3/sd3_model.py index 8b828524..d60b04e4 100644 --- a/modules/models/sd3/sd3_model.py +++ b/modules/models/sd3/sd3_model.py @@ -61,9 +61,9 @@ class SD3Cond(torch.nn.Module): self.tokenizer = SD3Tokenizer() with torch.no_grad(): - self.clip_g = SDXLClipG(CLIPG_CONFIG, device="cpu", dtype=torch.float32) - self.clip_l = SDClipModel(layer="hidden", layer_idx=-2, device="cpu", dtype=torch.float32, layer_norm_hidden_state=False, return_projected_pooled=False, textmodel_json_config=CLIPL_CONFIG) - self.t5xxl = T5XXLModel(T5_CONFIG, device="cpu", dtype=torch.float32) + 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) + self.t5xxl = T5XXLModel(T5_CONFIG, device="cpu", dtype=devices.dtype) self.weights_loaded = False diff --git a/modules/sd_models.py b/modules/sd_models.py index da083f71..61fb881b 100644 --- a/modules/sd_models.py +++ b/modules/sd_models.py @@ -406,6 +406,7 @@ def set_model_fields(model): if not hasattr(model, 'latent_channels'): model.latent_channels = 4 + def load_model_weights(model, checkpoint_info: CheckpointInfo, state_dict, timer): sd_model_hash = checkpoint_info.calculate_shorthash() timer.record("calculate hash") From 80f618ea95f1f833f415fc53733d45f7f3d452db Mon Sep 17 00:00:00 2001 From: AUTOMATIC1111 <16777216c@gmail.com> Date: Sun, 16 Jun 2024 12:52:03 +0300 Subject: [PATCH 373/496] add protobuf==3.20.0 to requirements --- requirements.txt | 1 + requirements_versions.txt | 1 + 2 files changed, 2 insertions(+) diff --git a/requirements.txt b/requirements.txt index 9e2ecfe4..0d6bac60 100644 --- a/requirements.txt +++ b/requirements.txt @@ -18,6 +18,7 @@ omegaconf open-clip-torch piexif +protobuf==3.20.0 psutil pytorch_lightning requests diff --git a/requirements_versions.txt b/requirements_versions.txt index 3037a395..d6b83e78 100644 --- a/requirements_versions.txt +++ b/requirements_versions.txt @@ -18,6 +18,7 @@ numpy==1.26.2 omegaconf==2.2.3 open-clip-torch==2.20.0 piexif==1.1.3 +protobuf==3.20.0 psutil==5.9.5 pytorch_lightning==1.9.4 resize-right==0.0.2 From 06d0a5ab4d44728943c799030b8a218f0af4f242 Mon Sep 17 00:00:00 2001 From: AUTOMATIC1111 <16777216c@gmail.com> Date: Sun, 16 Jun 2024 14:09:32 +0300 Subject: [PATCH 374/496] fix NaN issue when running without --precision half --- modules/models/sd3/other_impls.py | 3 +-- modules/models/sd3/sd3_model.py | 3 ++- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/modules/models/sd3/other_impls.py b/modules/models/sd3/other_impls.py index cd10edc8..6e4c5d10 100644 --- a/modules/models/sd3/other_impls.py +++ b/modules/models/sd3/other_impls.py @@ -262,8 +262,7 @@ class SDClipModel(torch.nn.Module, ClipTokenWeightEncoder): def forward(self, tokens): backup_embeds = self.transformer.get_input_embeddings() - device = backup_embeds.weight.device - tokens = torch.LongTensor(tokens).to(device) + 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": diff --git a/modules/models/sd3/sd3_model.py b/modules/models/sd3/sd3_model.py index d60b04e4..bb3e6a3d 100644 --- a/modules/models/sd3/sd3_model.py +++ b/modules/models/sd3/sd3_model.py @@ -149,7 +149,8 @@ class SD3Inferencer(torch.nn.Module): return contextlib.nullcontext() def get_learned_conditioning(self, batch: list[str]): - return self.cond_stage_model(batch) + with devices.without_autocast(): + return self.cond_stage_model(batch) def apply_model(self, x, t, cond): return self.model.apply_model(x, t, c_crossattn=cond['crossattn'], y=cond['vector']) From 58dc35a64ac3a5e172b77374c6fa3651fba5a70c Mon Sep 17 00:00:00 2001 From: AUTOMATIC1111 <16777216c@gmail.com> Date: Sun, 16 Jun 2024 14:31:43 +0300 Subject: [PATCH 375/496] change CLIP links to allow anonymous downloading --- modules/models/sd3/sd3_model.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/modules/models/sd3/sd3_model.py b/modules/models/sd3/sd3_model.py index bb3e6a3d..2b98b884 100644 --- a/modules/models/sd3/sd3_model.py +++ b/modules/models/sd3/sd3_model.py @@ -11,7 +11,7 @@ from modules.models.sd3.sd3_impls import BaseModel, SDVAE, SD3LatentFormat from modules import shared, modelloader, devices -CLIPG_URL = "https://huggingface.co/stabilityai/stable-diffusion-3-medium/resolve/main/text_encoders/clip_g.safetensors" +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, @@ -20,7 +20,7 @@ CLIPG_CONFIG = { "num_hidden_layers": 32, } -CLIPL_URL = "https://huggingface.co/stabilityai/stable-diffusion-3-medium/resolve/main/text_encoders/clip_l.safetensors" +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, @@ -29,7 +29,7 @@ CLIPL_CONFIG = { "num_hidden_layers": 12, } -T5_URL = "https://huggingface.co/stabilityai/stable-diffusion-3-medium/resolve/main/text_encoders/t5xxl_fp16.safetensors" +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, From d4b814aed609878513f5d0caf60204dda35a9e5a Mon Sep 17 00:00:00 2001 From: AUTOMATIC1111 <16777216c@gmail.com> Date: Sun, 16 Jun 2024 14:39:58 +0300 Subject: [PATCH 376/496] change t5xxl checkpoint to fp8 --- modules/models/sd3/sd3_model.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/modules/models/sd3/sd3_model.py b/modules/models/sd3/sd3_model.py index 2b98b884..2095f4d2 100644 --- a/modules/models/sd3/sd3_model.py +++ b/modules/models/sd3/sd3_model.py @@ -29,7 +29,7 @@ CLIPL_CONFIG = { "num_hidden_layers": 12, } -T5_URL = "https://huggingface.co/AUTOMATIC/stable-diffusion-3-medium-text-encoders/resolve/main/t5xxl_fp16.safetensors" +T5_URL = "https://huggingface.co/AUTOMATIC/stable-diffusion-3-medium-text-encoders/resolve/main/t5xxl_fp8_e4m3fn.safetensors" T5_CONFIG = { "d_ff": 10240, "d_model": 4096, @@ -101,7 +101,7 @@ class SD3Cond(torch.nn.Module): with safetensors.safe_open(clip_l_file, framework="pt") as file: self.clip_l.transformer.load_state_dict(SafetensorsMapping(file), strict=False) - t5_file = modelloader.load_file_from_url(T5_URL, model_dir=clip_path, file_name="t5xxl_fp16.safetensors") + t5_file = modelloader.load_file_from_url(T5_URL, model_dir=clip_path, file_name="t5xxl_fp8_e4m3fn.safetensors") with safetensors.safe_open(t5_file, framework="pt") as file: self.t5xxl.transformer.load_state_dict(SafetensorsMapping(file), strict=False) From 34b4443cc32de501d7035e5f2f171634a857e44e Mon Sep 17 00:00:00 2001 From: AUTOMATIC1111 <16777216c@gmail.com> Date: Sun, 16 Jun 2024 21:57:17 +0300 Subject: [PATCH 377/496] add an option (on by default) to disable T5 revert t5xxl back to fp16 --- modules/models/sd3/sd3_model.py | 22 ++++++++++++++++------ modules/shared_options.py | 4 ++++ 2 files changed, 20 insertions(+), 6 deletions(-) diff --git a/modules/models/sd3/sd3_model.py b/modules/models/sd3/sd3_model.py index 2095f4d2..146ddf2e 100644 --- a/modules/models/sd3/sd3_model.py +++ b/modules/models/sd3/sd3_model.py @@ -29,7 +29,7 @@ CLIPL_CONFIG = { "num_hidden_layers": 12, } -T5_URL = "https://huggingface.co/AUTOMATIC/stable-diffusion-3-medium-text-encoders/resolve/main/t5xxl_fp8_e4m3fn.safetensors" +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, @@ -63,7 +63,11 @@ class SD3Cond(torch.nn.Module): 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) - self.t5xxl = T5XXLModel(T5_CONFIG, device="cpu", dtype=devices.dtype) + + if shared.opts.sd3_enable_t5: + self.t5xxl = T5XXLModel(T5_CONFIG, device="cpu", dtype=devices.dtype) + else: + self.t5xxl = None self.weights_loaded = False @@ -74,7 +78,12 @@ class SD3Cond(torch.nn.Module): tokens = self.tokenizer.tokenize_with_weights(prompt) l_out, l_pooled = self.clip_l.encode_token_weights(tokens["l"]) g_out, g_pooled = self.clip_g.encode_token_weights(tokens["g"]) - t5_out, t5_pooled = self.t5xxl.encode_token_weights(tokens["t5xxl"]) + + if self.t5xxl and shared.opts.sd3_enable_t5: + t5_out, t5_pooled = self.t5xxl.encode_token_weights(tokens["t5xxl"]) + else: + t5_out = torch.zeros(l_out.shape[0:2] + (4096,), dtype=l_out.dtype, device=l_out.device) + lg_out = torch.cat([l_out, g_out], dim=-1) lg_out = torch.nn.functional.pad(lg_out, (0, 4096 - lg_out.shape[-1])) lgt_out = torch.cat([lg_out, t5_out], dim=-2) @@ -101,9 +110,10 @@ class SD3Cond(torch.nn.Module): with safetensors.safe_open(clip_l_file, framework="pt") as file: self.clip_l.transformer.load_state_dict(SafetensorsMapping(file), strict=False) - t5_file = modelloader.load_file_from_url(T5_URL, model_dir=clip_path, file_name="t5xxl_fp8_e4m3fn.safetensors") - with safetensors.safe_open(t5_file, framework="pt") as file: - self.t5xxl.transformer.load_state_dict(SafetensorsMapping(file), strict=False) + if self.t5xxl: + 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) self.weights_loaded = True diff --git a/modules/shared_options.py b/modules/shared_options.py index 7bce0468..f40832c4 100644 --- a/modules/shared_options.py +++ b/modules/shared_options.py @@ -191,6 +191,10 @@ options_templates.update(options_section(('sdxl', "Stable Diffusion XL", "sd"), "sdxl_refiner_high_aesthetic_score": OptionInfo(6.0, "SDXL high aesthetic score", gr.Number).info("used for refiner model prompt"), })) +options_templates.update(options_section(('sd3', "Stable Diffusion 3", "sd"), { + "sd3_enable_t5": OptionInfo(False, "Enable T5").info("load T5 text encoder; increases VRAM use by a lot, potentially improving quality of generation; requires model reload to apply"), +})) + options_templates.update(options_section(('vae', "VAE", "sd"), { "sd_vae_explanation": OptionHTML(""" VAE is a neural network that transforms a standard RGB From 663a4d80dfae5510257b362fd0015c8dc8b8bb5e Mon Sep 17 00:00:00 2001 From: v0xie <28695009+v0xie@users.noreply.github.com> Date: Sun, 16 Jun 2024 17:47:21 -0700 Subject: [PATCH 378/496] add new sampler DDIM CFG++ --- modules/sd_samplers_cfg_denoiser.py | 10 ++++++++ modules/sd_samplers_timesteps.py | 1 + modules/sd_samplers_timesteps_impl.py | 37 +++++++++++++++++++++++++++ 3 files changed, 48 insertions(+) diff --git a/modules/sd_samplers_cfg_denoiser.py b/modules/sd_samplers_cfg_denoiser.py index a86fa88e..c8eeedad 100644 --- a/modules/sd_samplers_cfg_denoiser.py +++ b/modules/sd_samplers_cfg_denoiser.py @@ -58,6 +58,8 @@ class CFGDenoiser(torch.nn.Module): self.model_wrap = None self.p = None + self.last_noise_uncond = None + # NOTE: masking before denoising can cause the original latents to be oversmoothed # as the original latents do not have noise self.mask_before_denoising = False @@ -160,6 +162,8 @@ class CFGDenoiser(torch.nn.Module): # so is_edit_model is set to False to support AND composition. is_edit_model = shared.sd_model.cond_stage_key == "edit" and self.image_cfg_scale is not None and self.image_cfg_scale != 1.0 + is_cfg_pp = 'CFG++' in self.sampler.config.name + conds_list, tensor = prompt_parser.reconstruct_multicond_batch(cond, self.step) uncond = prompt_parser.reconstruct_cond_batch(uncond, self.step) @@ -273,10 +277,16 @@ class CFGDenoiser(torch.nn.Module): denoised_params = CFGDenoisedParams(x_out, state.sampling_step, state.sampling_steps, self.inner_model) cfg_denoised_callback(denoised_params) + if is_cfg_pp: + self.last_noise_uncond = x_out[-uncond.shape[0]:] + self.last_noise_uncond = torch.clone(self.last_noise_uncond) + if is_edit_model: denoised = self.combine_denoised_for_edit_model(x_out, cond_scale) elif skip_uncond: denoised = self.combine_denoised(x_out, conds_list, uncond, 1.0) + elif is_cfg_pp: + denoised = self.combine_denoised(x_out, conds_list, uncond, cond_scale/12.5) # CFG++ scale of (0, 1) maps to (1.0, 12.5) else: denoised = self.combine_denoised(x_out, conds_list, uncond, cond_scale) diff --git a/modules/sd_samplers_timesteps.py b/modules/sd_samplers_timesteps.py index 8cc7d384..81edd67d 100644 --- a/modules/sd_samplers_timesteps.py +++ b/modules/sd_samplers_timesteps.py @@ -10,6 +10,7 @@ import modules.shared as shared samplers_timesteps = [ ('DDIM', sd_samplers_timesteps_impl.ddim, ['ddim'], {}), + ('DDIM CFG++', sd_samplers_timesteps_impl.ddim_cfgpp, ['ddim_cfgpp'], {}), ('PLMS', sd_samplers_timesteps_impl.plms, ['plms'], {}), ('UniPC', sd_samplers_timesteps_impl.unipc, ['unipc'], {}), ] diff --git a/modules/sd_samplers_timesteps_impl.py b/modules/sd_samplers_timesteps_impl.py index 84867d6e..8896cfc9 100644 --- a/modules/sd_samplers_timesteps_impl.py +++ b/modules/sd_samplers_timesteps_impl.py @@ -40,6 +40,43 @@ def ddim(model, x, timesteps, extra_args=None, callback=None, disable=None, eta= return x +@torch.no_grad() +def ddim_cfgpp(model, x, timesteps, extra_args=None, callback=None, disable=None, eta=0.0): + """ Implements CFG++: Manifold-constrained Classifier Free Guidance For Diffusion Models (2024). + Uses the unconditional noise prediction instead of the conditional noise to guide the denoising direction. + The CFG scale is divided by 12.5 to map CFG from [0.0, 12.5] to [0, 1.0]. + """ + alphas_cumprod = model.inner_model.inner_model.alphas_cumprod + alphas = alphas_cumprod[timesteps] + alphas_prev = alphas_cumprod[torch.nn.functional.pad(timesteps[:-1], pad=(1, 0))].to(float64(x)) + sqrt_one_minus_alphas = torch.sqrt(1 - alphas) + sigmas = eta * np.sqrt((1 - alphas_prev.cpu().numpy()) / (1 - alphas.cpu()) * (1 - alphas.cpu() / alphas_prev.cpu().numpy())) + + extra_args = {} if extra_args is None else extra_args + s_in = x.new_ones((x.shape[0])) + s_x = x.new_ones((x.shape[0], 1, 1, 1)) + for i in tqdm.trange(len(timesteps) - 1, disable=disable): + index = len(timesteps) - 1 - i + + e_t = model(x, timesteps[index].item() * s_in, **extra_args) + last_noise_uncond = model.last_noise_uncond + + a_t = alphas[index].item() * s_x + a_prev = alphas_prev[index].item() * s_x + sigma_t = sigmas[index].item() * s_x + sqrt_one_minus_at = sqrt_one_minus_alphas[index].item() * s_x + + pred_x0 = (x - sqrt_one_minus_at * e_t) / a_t.sqrt() + dir_xt = (1. - a_prev - sigma_t ** 2).sqrt() * last_noise_uncond + noise = sigma_t * k_diffusion.sampling.torch.randn_like(x) + x = a_prev.sqrt() * pred_x0 + dir_xt + noise + + if callback is not None: + callback({'x': x, 'i': i, 'sigma': 0, 'sigma_hat': 0, 'denoised': pred_x0}) + + return x + + @torch.no_grad() def plms(model, x, timesteps, extra_args=None, callback=None, disable=None): alphas_cumprod = model.inner_model.inner_model.alphas_cumprod From a772fd9804944cc19c4d6a03ccfbaa6066ce62a8 Mon Sep 17 00:00:00 2001 From: viking1304 Date: Thu, 20 Jun 2024 23:57:59 +0200 Subject: [PATCH 379/496] Update torch for ARM Macs to 2.3.1 --- webui-macos-env.sh | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/webui-macos-env.sh b/webui-macos-env.sh index ad073637..f390e4d3 100644 --- a/webui-macos-env.sh +++ b/webui-macos-env.sh @@ -16,7 +16,7 @@ export PYTORCH_ENABLE_MPS_FALLBACK=1 if [[ "$(sysctl -n machdep.cpu.brand_string)" =~ ^.*"Intel".*$ ]]; then export TORCH_COMMAND="pip install torch==2.1.2 torchvision==0.16.2" else - export TORCH_COMMAND="pip install torch==2.3.0 torchvision==0.18.0" + export TORCH_COMMAND="pip install torch==2.3.1 torchvision==0.18.1" fi #################################################################### From 13f22974a42df9f7a491fefe2912cdf72dcbebaf Mon Sep 17 00:00:00 2001 From: snoppy Date: Fri, 21 Jun 2024 09:52:02 +0800 Subject: [PATCH 380/496] chore: fix typos Signed-off-by: snoppy --- extensions-builtin/LDSR/sd_hijack_ddpm_v1.py | 2 +- modules/api/api.py | 2 +- modules/devices.py | 2 +- modules/models/diffusion/uni_pc/uni_pc.py | 2 +- modules/shared.py | 2 +- modules/util.py | 2 +- 6 files changed, 6 insertions(+), 6 deletions(-) diff --git a/extensions-builtin/LDSR/sd_hijack_ddpm_v1.py b/extensions-builtin/LDSR/sd_hijack_ddpm_v1.py index 9a1e0778..51ab1821 100644 --- a/extensions-builtin/LDSR/sd_hijack_ddpm_v1.py +++ b/extensions-builtin/LDSR/sd_hijack_ddpm_v1.py @@ -572,7 +572,7 @@ class LatentDiffusionV1(DDPMV1): :param h: height :param w: width :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) arr = self.meshgrid(h, w) / lower_right_corner diff --git a/modules/api/api.py b/modules/api/api.py index f468c385..3d208b71 100644 --- a/modules/api/api.py +++ b/modules/api/api.py @@ -372,7 +372,7 @@ class Api: return {} 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) def get_field_value(field, params): diff --git a/modules/devices.py b/modules/devices.py index e4f671ac..d4cf2bc6 100644 --- a/modules/devices.py +++ b/modules/devices.py @@ -258,7 +258,7 @@ def test_for_nans(x, where): @lru_cache 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 with NVidia. """ diff --git a/modules/models/diffusion/uni_pc/uni_pc.py b/modules/models/diffusion/uni_pc/uni_pc.py index d257a728..3333bc80 100644 --- a/modules/models/diffusion/uni_pc/uni_pc.py +++ b/modules/models/diffusion/uni_pc/uni_pc.py @@ -323,7 +323,7 @@ def model_wrapper( 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: t_continuous = t_continuous.expand((x.shape[0])) diff --git a/modules/shared.py b/modules/shared.py index a41cd457..2a3787f9 100644 --- a/modules/shared.py +++ b/modules/shared.py @@ -47,7 +47,7 @@ restricted_opts: set[str] = None sd_model: sd_models_types.WebuiSdModel = None settings_components: dict = None -"""assigned from ui.py, a mapping on setting names to gradio components repsponsible for those settings""" +"""assigned from ui.py, a mapping on setting names to gradio components responsible for those settings""" tab_names = [] diff --git a/modules/util.py b/modules/util.py index 0db13736..1fd736c7 100644 --- a/modules/util.py +++ b/modules/util.py @@ -156,7 +156,7 @@ class MassFileLister: 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 + Ignores errors relating to missing dependencies or circular dependencies """ visited = {} From bd85b3f19b3546ce12bcdf1c13a885f94c9e1b6c Mon Sep 17 00:00:00 2001 From: w-e-w <40751091+w-e-w@users.noreply.github.com> Date: Fri, 21 Jun 2024 10:53:44 +0900 Subject: [PATCH 381/496] remove dont_fix_second_order_samplers_schedule --- modules/shared_options.py | 1 - 1 file changed, 1 deletion(-) diff --git a/modules/shared_options.py b/modules/shared_options.py index 7bce0468..b1a484ad 100644 --- a/modules/shared_options.py +++ b/modules/shared_options.py @@ -245,7 +245,6 @@ options_templates.update(options_section(('compatibility', "Compatibility", "sd" "use_old_karras_scheduler_sigmas": OptionInfo(False, "Use old karras scheduler sigmas (0.1 to 10)."), "no_dpmpp_sde_batch_determinism": OptionInfo(False, "Do not make DPM++ SDE deterministic across different batch sizes."), "use_old_hires_fix_width_height": OptionInfo(False, "For hires fix, use width/height sliders to set final resolution rather than first pass (disables Upscale by, Resize width/height to)."), - "dont_fix_second_order_samplers_schedule": OptionInfo(False, "Do not fix prompt schedule for second order samplers."), "hires_fix_use_firstpass_conds": OptionInfo(False, "For hires fix, calculate conds of second pass using extra networks of first pass."), "use_old_scheduling": OptionInfo(False, "Use old prompt editing timelines.", infotext="Old prompt editing timelines").info("For [red:green:N]; old: If N < 1, it's a fraction of steps (and hires fix uses range from 0 to 1), if N >= 1, it's an absolute number of steps; new: If N has a decimal point in it, it's a fraction of steps (and hires fix uses range from 1 to 2), othewrwise it's an absolute number of steps"), "use_downcasted_alpha_bar": OptionInfo(False, "Downcast model alphas_cumprod to fp16 before sampling. For reproducing old seeds.", infotext="Downcast alphas_cumprod"), From 0f40c4b9b116a16b7e81e75a4e78b98a310e414d Mon Sep 17 00:00:00 2001 From: w-e-w <40751091+w-e-w@users.noreply.github.com> Date: Thu, 20 Jun 2024 20:02:59 +0900 Subject: [PATCH 382/496] fix Sampler Scheduler autocorrection warning --- modules/sd_samplers.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/modules/sd_samplers.py b/modules/sd_samplers.py index b8abac4a..963da5be 100644 --- a/modules/sd_samplers.py +++ b/modules/sd_samplers.py @@ -98,7 +98,7 @@ def get_hr_scheduler_from_infotext(d: dict): @functools.cache -def get_sampler_and_scheduler(sampler_name, scheduler_name): +def get_sampler_and_scheduler(sampler_name, scheduler_name, *, convert_automatic=True): default_sampler = samplers[0] found_scheduler = sd_schedulers.schedulers_map.get(scheduler_name, sd_schedulers.schedulers[0]) @@ -116,7 +116,7 @@ def get_sampler_and_scheduler(sampler_name, scheduler_name): sampler = all_samplers_map.get(name, default_sampler) # revert back to Automatic if it's the default scheduler for the selected sampler - if sampler.options.get('scheduler', None) == found_scheduler.name: + if convert_automatic and sampler.options.get('scheduler', None) == found_scheduler.name: found_scheduler = sd_schedulers.schedulers[0] return sampler.name, found_scheduler.label @@ -124,7 +124,7 @@ def get_sampler_and_scheduler(sampler_name, scheduler_name): def fix_p_invalid_sampler_and_scheduler(p): i_sampler_name, i_scheduler = p.sampler_name, p.scheduler - p.sampler_name, p.scheduler = get_sampler_and_scheduler(p.sampler_name, p.scheduler) + p.sampler_name, p.scheduler = get_sampler_and_scheduler(p.sampler_name, p.scheduler, convert_automatic=False) if p.sampler_name != i_sampler_name or i_scheduler != p.scheduler: logging.warning(f'Sampler Scheduler autocorrection: "{i_sampler_name}" -> "{p.sampler_name}", "{i_scheduler}" -> "{p.scheduler}"') From 109bbda70959c47d26d8a7f9e26bf61030dc162a Mon Sep 17 00:00:00 2001 From: w-e-w <40751091+w-e-w@users.noreply.github.com> Date: Fri, 21 Jun 2024 14:47:56 +0900 Subject: [PATCH 383/496] fix infotext Lora hashes fro hires fix different lora --- .../Lora/extra_networks_lora.py | 23 ++++++++----------- 1 file changed, 9 insertions(+), 14 deletions(-) diff --git a/extensions-builtin/Lora/extra_networks_lora.py b/extensions-builtin/Lora/extra_networks_lora.py index 005ff32c..17a620f7 100644 --- a/extensions-builtin/Lora/extra_networks_lora.py +++ b/extensions-builtin/Lora/extra_networks_lora.py @@ -9,6 +9,8 @@ class ExtraNetworkLora(extra_networks.ExtraNetwork): self.errors = {} """mapping of network names to the number of errors the network had during operation""" + remove_symbols = str.maketrans('', '', ":,") + def activate(self, p, params_list): additional = shared.opts.sd_lora @@ -43,22 +45,15 @@ class ExtraNetworkLora(extra_networks.ExtraNetwork): networks.load_networks(names, te_multipliers, unet_multipliers, dyn_dims) 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: - shorthash = item.network_on_disk.shorthash - if not shorthash: - continue + if item.network_on_disk.shorthash and item.mentioned_name: + p.lora_hashes[item.mentioned_name.translate(self.remove_symbols)] = item.network_on_disk.shorthash - alias = item.mentioned_name - if not alias: - continue - - alias = alias.replace(":", "").replace(",", "") - - network_hashes.append(f"{alias}: {shorthash}") - - if network_hashes: - p.extra_generation_params["Lora hashes"] = ", ".join(network_hashes) + if p.lora_hashes: + p.extra_generation_params["Lora hashes"] = ', '.join(f'{k}: {v}' for k, v in p.lora_hashes.items()) def deactivate(self, p): if self.errors: From 775fa7696b90f0a41852fee604655d4b19de3c4a Mon Sep 17 00:00:00 2001 From: w-e-w <40751091+w-e-w@users.noreply.github.com> Date: Fri, 21 Jun 2024 20:26:09 +0900 Subject: [PATCH 384/496] ToggleLivePriview in image viewer --- javascript/imageviewer.js | 17 +++++++++++++++++ 1 file changed, 17 insertions(+) diff --git a/javascript/imageviewer.js b/javascript/imageviewer.js index a3f08ad1..9b23f470 100644 --- a/javascript/imageviewer.js +++ b/javascript/imageviewer.js @@ -6,6 +6,8 @@ function closeModal() { function showModal(event) { const source = event.target || event.srcElement; const modalImage = gradioApp().getElementById("modalImage"); + const modalToggleLivePreviewBtn = gradioApp().getElementById("modal_toggle_live_preview"); + modalToggleLivePreviewBtn.innerHTML = opts.js_live_preview_in_modal_lightbox ? "🗇" : "🗆"; const lb = gradioApp().getElementById("lightboxModal"); modalImage.src = source.src; if (modalImage.style.display === 'none') { @@ -152,6 +154,13 @@ function modalZoomToggle(event) { 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 ? "🗇" : "🗆"; + event.stopPropagation(); +} + function modalTileImageToggle(event) { const modalImage = gradioApp().getElementById("modalImage"); const modal = gradioApp().getElementById("lightboxModal"); @@ -209,6 +218,14 @@ document.addEventListener("DOMContentLoaded", function() { modalSave.title = "Save Image(s)"; modalControls.appendChild(modalSave); + const modalToggleLivePreview = document.createElement('span'); + modalToggleLivePreview.className = 'modalToggleLivePreview cursor'; + modalToggleLivePreview.id = "modal_toggle_live_preview"; + modalToggleLivePreview.innerHTML = "🗆"; + modalToggleLivePreview.onclick = modalLivePreviewToggle; + modalToggleLivePreview.title = "Toggle live preview"; + modalControls.appendChild(modalToggleLivePreview); + const modalClose = document.createElement('span'); modalClose.className = 'modalClose cursor'; modalClose.innerHTML = '×'; From c3ef381cd8c85ff05eba7d1d1d6295bcb105137d Mon Sep 17 00:00:00 2001 From: huchenlei Date: Sun, 23 Jun 2024 11:19:04 -0400 Subject: [PATCH 385/496] Fix SD2 loading --- modules/sd_hijack_unet.py | 2 ++ modules/sd_models_config.py | 7 ++++++- 2 files changed, 8 insertions(+), 1 deletion(-) diff --git a/modules/sd_hijack_unet.py b/modules/sd_hijack_unet.py index b4f03b13..6d657511 100644 --- a/modules/sd_hijack_unet.py +++ b/modules/sd_hijack_unet.py @@ -138,6 +138,7 @@ CondFunc('ldm.models.diffusion.ddpm.LatentDiffusion.decode_first_stage', first_s CondFunc('ldm.models.diffusion.ddpm.LatentDiffusion.encode_first_stage', first_stage_sub, first_stage_cond) CondFunc('ldm.models.diffusion.ddpm.LatentDiffusion.get_first_stage_encoding', lambda orig_func, *args, **kwargs: orig_func(*args, **kwargs).float(), first_stage_cond) +# Always make sure inputs to unet are in correct dtype CondFunc('ldm.models.diffusion.ddpm.LatentDiffusion.apply_model', apply_model) CondFunc('sgm.modules.diffusionmodules.wrappers.OpenAIWrapper.forward', apply_model) @@ -150,5 +151,6 @@ def timestep_embedding_cast_result(orig_func, timesteps, *args, **kwargs): return orig_func(timesteps, *args, **kwargs).to(dtype=dtype) +# Always make sure timestep calculation is in correct dtype CondFunc('ldm.modules.diffusionmodules.openaimodel.timestep_embedding', timestep_embedding_cast_result) CondFunc('sgm.modules.diffusionmodules.openaimodel.timestep_embedding', timestep_embedding_cast_result) diff --git a/modules/sd_models_config.py b/modules/sd_models_config.py index 9cec4f13..928beb57 100644 --- a/modules/sd_models_config.py +++ b/modules/sd_models_config.py @@ -54,14 +54,19 @@ def is_using_v_parameterization_for_sd2(state_dict): unet.eval() with torch.no_grad(): + unet_dtype = torch.float + original_unet_dtype = devices.dtype_unet + unet_sd = {k.replace("model.diffusion_model.", ""): v for k, v in state_dict.items() if "model.diffusion_model." in k} unet.load_state_dict(unet_sd, strict=True) - unet.to(device=device, dtype=torch.float) + unet.to(device=device, dtype=unet_dtype) + devices.dtype_unet = unet_dtype test_cond = torch.ones((1, 2, 1024), device=device) * 0.5 x_test = torch.ones((1, 4, 8, 8), device=device) * 0.5 out = (unet(x_test, torch.asarray([999], device=device), context=test_cond) - x_test).mean().item() + devices.dtype_unet = original_unet_dtype return out < -1 From 731eb7277454fe16b094f54750e05d20e05a8be8 Mon Sep 17 00:00:00 2001 From: Andray Date: Sun, 23 Jun 2024 21:16:48 +0400 Subject: [PATCH 386/496] fix sd2 switching --- modules/sd_models_config.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/modules/sd_models_config.py b/modules/sd_models_config.py index 9cec4f13..733d70af 100644 --- a/modules/sd_models_config.py +++ b/modules/sd_models_config.py @@ -31,7 +31,7 @@ def is_using_v_parameterization_for_sd2(state_dict): import ldm.modules.diffusionmodules.openaimodel - device = devices.cpu + device = devices.device with sd_disable_initialization.DisableInitialization(): unet = ldm.modules.diffusionmodules.openaimodel.UNetModel( From a65dd315adcab0467d652160b26a95604573530c Mon Sep 17 00:00:00 2001 From: AUTOMATIC1111 <16777216c@gmail.com> Date: Mon, 24 Jun 2024 09:06:10 +0300 Subject: [PATCH 387/496] fix T5 --- modules/models/sd3/other_impls.py | 38 ++++++++++++++++++++++--------- 1 file changed, 27 insertions(+), 11 deletions(-) diff --git a/modules/models/sd3/other_impls.py b/modules/models/sd3/other_impls.py index 6e4c5d10..d7b9b262 100644 --- a/modules/models/sd3/other_impls.py +++ b/modules/models/sd3/other_impls.py @@ -11,6 +11,18 @@ from transformers import CLIPTokenizer, T5TokenizerFast ################################################################################################# +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 @@ -27,9 +39,9 @@ class Mlp(nn.Module): 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.fc1 = AutocastLinear(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) + self.fc2 = AutocastLinear(hidden_features, out_features, bias=bias, dtype=dtype, device=device) def forward(self, x): x = self.fc1(x) @@ -297,7 +309,6 @@ class T5XXLModel(SDClipModel): ### 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): @@ -319,9 +330,9 @@ class T5LayerNorm(torch.nn.Module): class T5DenseGatedActDense(torch.nn.Module): def __init__(self, model_dim, ff_dim, dtype, device): super().__init__() - self.wi_0 = nn.Linear(model_dim, ff_dim, bias=False, dtype=dtype, device=device) - self.wi_1 = nn.Linear(model_dim, ff_dim, bias=False, dtype=dtype, device=device) - self.wo = nn.Linear(ff_dim, model_dim, bias=False, dtype=dtype, device=device) + 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") @@ -348,10 +359,10 @@ 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 = nn.Linear(model_dim, inner_dim, bias=False, dtype=dtype, device=device) - self.k = nn.Linear(model_dim, inner_dim, bias=False, dtype=dtype, device=device) - self.v = nn.Linear(model_dim, inner_dim, bias=False, dtype=dtype, device=device) - self.o = nn.Linear(inner_dim, model_dim, bias=False, dtype=dtype, device=device) + 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: @@ -421,11 +432,16 @@ class T5Attention(torch.nn.Module): 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 - out = attention(q, k * ((k.shape[-1] / self.num_heads) ** 0.5), v, self.num_heads, mask) + 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 From a8fba9af35a081ada8c563fc6c76212dd131be01 Mon Sep 17 00:00:00 2001 From: AUTOMATIC1111 <16777216c@gmail.com> Date: Mon, 24 Jun 2024 10:15:46 +0300 Subject: [PATCH 388/496] medvram support for SD3 --- modules/lowvram.py | 28 +++++++++++++++++++++++----- modules/models/sd3/mmdit.py | 1 - modules/models/sd3/sd3_model.py | 12 +++++++++++- modules/sd_samplers_common.py | 2 +- 4 files changed, 35 insertions(+), 8 deletions(-) diff --git a/modules/lowvram.py b/modules/lowvram.py index 45701046..00aad477 100644 --- a/modules/lowvram.py +++ b/modules/lowvram.py @@ -1,9 +1,12 @@ +from collections import namedtuple + import torch from modules import devices, shared module_in_gpu = None cpu = torch.device("cpu") +ModuleWithParent = namedtuple('ModuleWithParent', ['module', 'parent'], defaults=['None']) def send_everything_to_cpu(): global module_in_gpu @@ -75,13 +78,14 @@ def setup_for_low_vram(sd_model, use_medvram): (sd_model, 'depth_model'), (sd_model, 'embedder'), (sd_model, 'model'), - (sd_model, 'embedder'), ] is_sdxl = hasattr(sd_model, 'conditioner') 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')) elif is_sd2: 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) # register hooks for those the first three models - if is_sdxl: + if 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) elif is_sd2: 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.encode = first_stage_model_encode_wrap sd_model.first_stage_model.decode = first_stage_model_decode_wrap - if sd_model.depth_model: + if hasattr(sd_model, 'depth_model'): sd_model.depth_model.register_forward_pre_hook(send_me_to_gpu) - if sd_model.embedder: + if hasattr(sd_model, 'embedder'): sd_model.embedder.register_forward_pre_hook(send_me_to_gpu) if use_medvram: diff --git a/modules/models/sd3/mmdit.py b/modules/models/sd3/mmdit.py index 5ec73c05..4d2b8555 100644 --- a/modules/models/sd3/mmdit.py +++ b/modules/models/sd3/mmdit.py @@ -492,7 +492,6 @@ class MMDiT(nn.Module): device = None, ): super().__init__() - print(f"mmdit initializing with: {input_size=}, {patch_size=}, {in_channels=}, {depth=}, {mlp_ratio=}, {learn_sigma=}, {adm_in_channels=}, {context_embedder_config=}, {register_length=}, {attn_mode=}, {rmsnorm=}, {scale_mod_only=}, {swiglu=}, {out_channels=}, {pos_embed_scaling_factor=}, {pos_embed_offset=}, {pos_embed_max_size=}, {num_patches=}, {qk_norm=}, {qkv_bias=}, {dtype=}, {device=}") self.dtype = dtype self.learn_sigma = learn_sigma self.in_channels = in_channels diff --git a/modules/models/sd3/sd3_model.py b/modules/models/sd3/sd3_model.py index 146ddf2e..309a7f86 100644 --- a/modules/models/sd3/sd3_model.py +++ b/modules/models/sd3/sd3_model.py @@ -120,6 +120,9 @@ class SD3Cond(torch.nn.Module): def encode_embedding_init_text(self, init_text, nvpt): return torch.tensor([[0]], device=devices.device) # XXX + def medvram_modules(self): + return [self.clip_g, self.clip_l, self.t5xxl] + class SD3Denoiser(k_diffusion.external.DiscreteSchedule): def __init__(self, inner_model, sigmas): @@ -163,7 +166,7 @@ class SD3Inferencer(torch.nn.Module): return self.cond_stage_model(batch) def apply_model(self, x, t, cond): - return self.model.apply_model(x, t, c_crossattn=cond['crossattn'], y=cond['vector']) + 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) @@ -175,3 +178,10 @@ class SD3Inferencer(torch.nn.Module): def create_denoiser(self): return SD3Denoiser(self, self.model.model_sampling.sigmas) + + def medvram_fields(self): + return [ + (self, 'first_stage_model'), + (self, 'cond_stage_model'), + (self, 'model'), + ] diff --git a/modules/sd_samplers_common.py b/modules/sd_samplers_common.py index b584b68a..c060cccb 100644 --- a/modules/sd_samplers_common.py +++ b/modules/sd_samplers_common.py @@ -163,7 +163,7 @@ def apply_refiner(cfg_denoiser, sigma=None): else: # torch.max(sigma) only to handle rare case where we might have different sigmas in the same batch try: - timestep = torch.argmin(torch.abs(cfg_denoiser.inner_model.sigmas - torch.max(sigma))) + timestep = torch.argmin(torch.abs(cfg_denoiser.inner_model.sigmas.to(sigma.device) - torch.max(sigma))) except AttributeError: # for samplers that don't use sigmas (DDIM) sigma is actually the timestep timestep = torch.max(sigma).to(dtype=int) completed_ratio = (999 - timestep) / 1000 From 5d9f1e6a431fb3396ee3bb9000188941a48801f2 Mon Sep 17 00:00:00 2001 From: Andray Date: Tue, 25 Jun 2024 05:33:07 +0400 Subject: [PATCH 389/496] stoping generation extras --- modules/postprocessing.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/modules/postprocessing.py b/modules/postprocessing.py index 8ec122b7..a413d102 100644 --- a/modules/postprocessing.py +++ b/modules/postprocessing.py @@ -51,7 +51,7 @@ def run_postprocessing(extras_mode, image, image_folder, input_dir, output_dir, shared.state.textinfo = name shared.state.skipped = False - if shared.state.interrupted: + if shared.state.interrupted or shared.state.stopping_generation: break if isinstance(image_placeholder, str): From 9e60cdbc3f392b39143ee11b53cfec37c563682b Mon Sep 17 00:00:00 2001 From: cuba3 Date: Tue, 25 Jun 2024 15:24:46 +0800 Subject: [PATCH 390/496] 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. --- modules/torch_utils.py | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/modules/torch_utils.py b/modules/torch_utils.py index a07e0285..5ea3da09 100644 --- a/modules/torch_utils.py +++ b/modules/torch_utils.py @@ -20,7 +20,6 @@ def get_param(model) -> torch.nn.Parameter: def float64(t: torch.Tensor): """return torch.float64 if device is not mps or xpu, else return torch.float32""" - match t.device.type: - case 'mps', 'xpu': - return torch.float32 + if t.device.type in ['mps', 'xpu']: + return torch.float32 return torch.float64 From ec3c31e7a19f3240bfba072787399eb02b88dc9e Mon Sep 17 00:00:00 2001 From: viking1304 Date: Tue, 25 Jun 2024 21:01:33 +0200 Subject: [PATCH 391/496] Try to use specified python version on linux and mac, with fallback --- webui-macos-env.sh | 5 ----- webui.sh | 6 +++++- 2 files changed, 5 insertions(+), 6 deletions(-) diff --git a/webui-macos-env.sh b/webui-macos-env.sh index ad073637..ae84f1e4 100644 --- a/webui-macos-env.sh +++ b/webui-macos-env.sh @@ -4,11 +4,6 @@ # Please modify webui-user.sh to change these instead of this file # #################################################################### -if [[ -x "$(command -v python3.10)" ]] -then - python_cmd="python3.10" -fi - export install_dir="$HOME" export COMMANDLINE_ARGS="--skip-torch-cuda-test --upcast-sampling --no-half-vae --use-cpu interrogate" export PYTORCH_ENABLE_MPS_FALLBACK=1 diff --git a/webui.sh b/webui.sh index 7acea902..4a1bebd0 100755 --- a/webui.sh +++ b/webui.sh @@ -44,7 +44,11 @@ fi # python3 executable if [[ -z "${python_cmd}" ]] then - python_cmd="python3" + python_cmd="python3.10" +fi +if [[ ! -x "$(command -v "${python_cmd}")" ]] +then + python_cmd="python3" fi # git executable From d686e73daa6cca399fe68976922cabde681f69f1 Mon Sep 17 00:00:00 2001 From: AUTOMATIC1111 <16777216c@gmail.com> Date: Wed, 26 Jun 2024 23:22:00 +0300 Subject: [PATCH 392/496] support for SD3: infinite prompt length, token counting --- modules/models/sd3/sd3_cond.py | 225 ++++++++++++++++++++++++++++++++ modules/models/sd3/sd3_model.py | 119 +---------------- modules/prompt_parser.py | 2 +- modules/sd_hijack.py | 5 +- modules/sd_hijack_clip.py | 59 ++++++--- modules/sd_models.py | 7 +- 6 files changed, 278 insertions(+), 139 deletions(-) create mode 100644 modules/models/sd3/sd3_cond.py diff --git a/modules/models/sd3/sd3_cond.py b/modules/models/sd3/sd3_cond.py new file mode 100644 index 00000000..c61ae0fe --- /dev/null +++ b/modules/models/sd3/sd3_cond.py @@ -0,0 +1,225 @@ +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, +} + +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) + + self.weights_loaded = False + + def forward(self, prompts: list[str]): + lg_out, vector_out = self.model_lg(prompts) + + token_count = lg_out.shape[1] + + t5_out = self.model_t5(prompts, token_count=token_count) + lgt_out = torch.cat([lg_out, t5_out], dim=-2) + + return { + 'crossattn': lgt_out, + 'vector': vector_out, + } + + def load_weights(self): + if self.weights_loaded: + return + + clip_path = os.path.join(shared.models_path, "CLIP") + + 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)) + + 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: + 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) + + self.weights_loaded = True + + def encode_embedding_init_text(self, init_text, nvpt): + return torch.tensor([[0]], device=devices.device) # XXX + + 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) diff --git a/modules/models/sd3/sd3_model.py b/modules/models/sd3/sd3_model.py index 309a7f86..10209f82 100644 --- a/modules/models/sd3/sd3_model.py +++ b/modules/models/sd3/sd3_model.py @@ -1,127 +1,12 @@ import contextlib -import os -from typing import Mapping -import safetensors import torch import k_diffusion -from modules.models.sd3.other_impls import SDClipModel, SDXLClipG, T5XXLModel, SD3Tokenizer from modules.models.sd3.sd3_impls import BaseModel, SDVAE, SD3LatentFormat +from modules.models.sd3.sd3_cond import SD3Cond -from modules import shared, modelloader, devices - -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, -} - -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, -} - -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 SafetensorsMapping(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) - - -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.weights_loaded = False - - def forward(self, prompts: list[str]): - res = [] - - for prompt in prompts: - tokens = self.tokenizer.tokenize_with_weights(prompt) - l_out, l_pooled = self.clip_l.encode_token_weights(tokens["l"]) - g_out, g_pooled = self.clip_g.encode_token_weights(tokens["g"]) - - if self.t5xxl and shared.opts.sd3_enable_t5: - t5_out, t5_pooled = self.t5xxl.encode_token_weights(tokens["t5xxl"]) - else: - t5_out = torch.zeros(l_out.shape[0:2] + (4096,), dtype=l_out.dtype, device=l_out.device) - - lg_out = torch.cat([l_out, g_out], dim=-1) - lg_out = torch.nn.functional.pad(lg_out, (0, 4096 - lg_out.shape[-1])) - lgt_out = torch.cat([lg_out, t5_out], dim=-2) - vector_out = torch.cat((l_pooled, g_pooled), dim=-1) - - res.append({ - 'crossattn': lgt_out[0].to(devices.device), - 'vector': vector_out[0].to(devices.device), - }) - - return res - - def load_weights(self): - if self.weights_loaded: - return - - clip_path = os.path.join(shared.models_path, "CLIP") - - 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)) - - 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: - 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) - - self.weights_loaded = True - - def encode_embedding_init_text(self, init_text, nvpt): - return torch.tensor([[0]], device=devices.device) # XXX - - def medvram_modules(self): - return [self.clip_g, self.clip_l, self.t5xxl] +from modules import shared, devices class SD3Denoiser(k_diffusion.external.DiscreteSchedule): diff --git a/modules/prompt_parser.py b/modules/prompt_parser.py index cba13455..4e393d28 100644 --- a/modules/prompt_parser.py +++ b/modules/prompt_parser.py @@ -268,7 +268,7 @@ def get_multicond_learned_conditioning(model, prompts, steps, hires_steps=None, class DictWithShape(dict): - def __init__(self, x, shape): + def __init__(self, x, shape=None): super().__init__() self.update(x) diff --git a/modules/sd_hijack.py b/modules/sd_hijack.py index e139d996..d5b2989f 100644 --- a/modules/sd_hijack.py +++ b/modules/sd_hijack.py @@ -325,7 +325,10 @@ class StableDiffusionModelHijack: if self.clip is None: 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) diff --git a/modules/sd_hijack_clip.py b/modules/sd_hijack_clip.py index 355df3d3..a479148f 100644 --- a/modules/sd_hijack_clip.py +++ b/modules/sd_hijack_clip.py @@ -27,24 +27,21 @@ chunk. Those objects are found in PromptChunk.fixes and, are placed into FrozenC are applied by sd_hijack.EmbeddingsWithFixes's forward function.""" -class FrozenCLIPEmbedderWithCustomWordsBase(torch.nn.Module): - """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): +class TextConditionalModel(torch.nn.Module): + def __init__(self): super().__init__() - self.wrapped = wrapped - """Original FrozenCLIPEmbedder module; can also be FrozenOpenCLIPEmbedder or xlmr.BertSeriesModelWithTransformation, - depending on model.""" - - self.hijack: sd_hijack.StableDiffusionModelHijack = hijack + self.hijack = sd_hijack.model_hijack self.chunk_length = 75 - self.is_trainable = getattr(wrapped, 'is_trainable', False) - self.input_key = getattr(wrapped, 'input_key', 'txt') - self.legacy_ucg_val = None + self.is_trainable = False + self.input_key = 'txt' + self.return_pooled = False + + self.comma_token = None + self.id_start = None + self.id_end = None + self.id_pad = None def empty_chunk(self): """creates an empty PromptChunk and returns it""" @@ -210,10 +207,6 @@ class FrozenCLIPEmbedderWithCustomWordsBase(torch.nn.Module): 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) used_embeddings = {} @@ -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": 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 else: return torch.hstack(zs) @@ -292,6 +285,34 @@ class FrozenCLIPEmbedderWithCustomWordsBase(torch.nn.Module): 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): def __init__(self, wrapped, hijack): super().__init__(wrapped, hijack) diff --git a/modules/sd_models.py b/modules/sd_models.py index 61fb881b..45575e44 100644 --- a/modules/sd_models.py +++ b/modules/sd_models.py @@ -722,7 +722,12 @@ def get_empty_cond(sd_model): d = sd_model.get_learned_conditioning([""]) return d['crossattn'] else: - return sd_model.cond_stage_model([""]) + d = sd_model.cond_stage_model([""]) + + if isinstance(d, dict): + d = d['crossattn'] + + return d def send_model_to_cpu(m): From 42ca30d6c1b7dff737f49ca20409281947b0b110 Mon Sep 17 00:00:00 2001 From: AUTOMATIC1111 <16777216c@gmail.com> Date: Thu, 27 Jun 2024 07:35:53 +0300 Subject: [PATCH 393/496] fix mdevram for SD1/SDXL --- modules/lowvram.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/modules/lowvram.py b/modules/lowvram.py index 00aad477..6728c337 100644 --- a/modules/lowvram.py +++ b/modules/lowvram.py @@ -107,7 +107,7 @@ def setup_for_low_vram(sd_model, use_medvram): setattr(obj, field, module) # register hooks for those the first three models - if hasattr(sd_model.cond_stage_model, "medvram_modules"): + 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 @@ -135,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.encode = first_stage_model_encode_wrap sd_model.first_stage_model.decode = first_stage_model_decode_wrap - if hasattr(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) - if hasattr(sd_model, 'embedder'): + if getattr(sd_model, 'embedder', None) is not None: sd_model.embedder.register_forward_pre_hook(send_me_to_gpu) if use_medvram: From afaf120bc2677110a8676ff183054d68393cf192 Mon Sep 17 00:00:00 2001 From: Ikko Eltociear Ashimine Date: Thu, 27 Jun 2024 17:44:12 +0900 Subject: [PATCH 394/496] docs: update bug_report.yml occured -> occurred --- .github/ISSUE_TEMPLATE/bug_report.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/ISSUE_TEMPLATE/bug_report.yml b/.github/ISSUE_TEMPLATE/bug_report.yml index 5876e941..c86bd8a6 100644 --- a/.github/ISSUE_TEMPLATE/bug_report.yml +++ b/.github/ISSUE_TEMPLATE/bug_report.yml @@ -91,7 +91,7 @@ body: id: logs attributes: 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 validations: required: true From 06fe174c74fe99093a20eba87aff2bd4e7edadf9 Mon Sep 17 00:00:00 2001 From: AUTOMATIC1111 <16777216c@gmail.com> Date: Fri, 28 Jun 2024 07:51:30 +0300 Subject: [PATCH 395/496] get deepbooru to run with --precision-half --- modules/deepbooru.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/modules/deepbooru.py b/modules/deepbooru.py index 547e1b4c..fb043feb 100644 --- a/modules/deepbooru.py +++ b/modules/deepbooru.py @@ -57,7 +57,7 @@ class DeepDanbooru: a = np.expand_dims(np.array(pic, dtype=np.float32), 0) / 255 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() probability_dict = {} From fc8b126673bdfed65b2b98ee747368d35c1b42ba Mon Sep 17 00:00:00 2001 From: AUTOMATIC1111 <16777216c@gmail.com> Date: Fri, 28 Jun 2024 08:10:19 +0300 Subject: [PATCH 396/496] get T5 to work both with and without --precision half --- modules/models/sd3/other_impls.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/modules/models/sd3/other_impls.py b/modules/models/sd3/other_impls.py index d7b9b262..002fe483 100644 --- a/modules/models/sd3/other_impls.py +++ b/modules/models/sd3/other_impls.py @@ -479,7 +479,7 @@ class T5Stack(torch.nn.Module): def forward(self, input_ids, intermediate_output=None, final_layer_norm_intermediate=True): intermediate = None - x = self.embed_tokens(input_ids) + 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) From 0c7bdcc1b130130bdaec425a9c3537ba098e0098 Mon Sep 17 00:00:00 2001 From: AUTOMATIC1111 <16777216c@gmail.com> Date: Fri, 28 Jun 2024 08:10:32 +0300 Subject: [PATCH 397/496] add the missing get_first_stage_encoding function --- modules/models/sd3/sd3_model.py | 3 +++ 1 file changed, 3 insertions(+) diff --git a/modules/models/sd3/sd3_model.py b/modules/models/sd3/sd3_model.py index 10209f82..f8a4c96c 100644 --- a/modules/models/sd3/sd3_model.py +++ b/modules/models/sd3/sd3_model.py @@ -61,6 +61,9 @@ class SD3Inferencer(torch.nn.Module): 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) From 0b64633584e95e94ea2023bbe5640250f9e23984 Mon Sep 17 00:00:00 2001 From: AUTOMATIC1111 <16777216c@gmail.com> Date: Fri, 28 Jun 2024 09:23:41 +0300 Subject: [PATCH 398/496] fix img2img --- modules/models/sd3/sd3_model.py | 3 +++ modules/processing.py | 6 +++--- modules/sd_samplers_kdiffusion.py | 5 ++++- 3 files changed, 10 insertions(+), 4 deletions(-) diff --git a/modules/models/sd3/sd3_model.py b/modules/models/sd3/sd3_model.py index f8a4c96c..c17fd97e 100644 --- a/modules/models/sd3/sd3_model.py +++ b/modules/models/sd3/sd3_model.py @@ -73,3 +73,6 @@ class SD3Inferencer(torch.nn.Module): (self, 'cond_stage_model'), (self, 'model'), ] + + def add_noise_to_latent(self, x, noise, amount): + return x * (1 - amount) + noise * amount diff --git a/modules/processing.py b/modules/processing.py index d32a1811..c3ce975e 100644 --- a/modules/processing.py +++ b/modules/processing.py @@ -1737,10 +1737,10 @@ class StableDiffusionProcessingImg2Img(StableDiffusionProcessing): latmask = latmask[0] if self.mask_round: 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.nmask = torch.asarray(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(devices.dtype) # this needs to be fixed to be done in sample() using actual seeds for batches if self.inpainting_fill == 2: diff --git a/modules/sd_samplers_kdiffusion.py b/modules/sd_samplers_kdiffusion.py index cede0760..8398299f 100644 --- a/modules/sd_samplers_kdiffusion.py +++ b/modules/sd_samplers_kdiffusion.py @@ -133,7 +133,10 @@ class KDiffusionSampler(sd_samplers_common.Sampler): sigmas = self.get_sigmas(p, steps) sigma_sched = sigmas[steps - t_enc - 1:] - xi = x + noise * sigma_sched[0] + if hasattr(shared.sd_model, 'add_noise_to_latent'): + xi = shared.sd_model.add_noise_to_latent(x, noise, sigma_sched[0]) + else: + xi = x + noise * sigma_sched[0] if opts.img2img_extra_noise > 0: p.extra_generation_params["Extra noise"] = opts.img2img_extra_noise From 179ae47d642d9d28184bc0a6cacda00d67b81744 Mon Sep 17 00:00:00 2001 From: AUTOMATIC1111 <16777216c@gmail.com> Date: Fri, 28 Jun 2024 11:15:34 +0300 Subject: [PATCH 399/496] fix the problem with infinite prompts where empty cond would be calculated incorrectly --- modules/models/sd3/sd3_cond.py | 9 +++++---- modules/models/sd3/sd3_model.py | 3 +-- modules/sd_models.py | 9 ++++----- 3 files changed, 10 insertions(+), 11 deletions(-) diff --git a/modules/models/sd3/sd3_cond.py b/modules/models/sd3/sd3_cond.py index c61ae0fe..e25ba1b6 100644 --- a/modules/models/sd3/sd3_cond.py +++ b/modules/models/sd3/sd3_cond.py @@ -177,12 +177,13 @@ class SD3Cond(torch.nn.Module): self.weights_loaded = False def forward(self, prompts: list[str]): - lg_out, vector_out = self.model_lg(prompts) + with devices.without_autocast(): + lg_out, vector_out = self.model_lg(prompts) - token_count = lg_out.shape[1] + token_count = lg_out.shape[1] - t5_out = self.model_t5(prompts, token_count=token_count) - lgt_out = torch.cat([lg_out, t5_out], dim=-2) + t5_out = self.model_t5(prompts, token_count=token_count) + lgt_out = torch.cat([lg_out, t5_out], dim=-2) return { 'crossattn': lgt_out, diff --git a/modules/models/sd3/sd3_model.py b/modules/models/sd3/sd3_model.py index c17fd97e..336e8d2d 100644 --- a/modules/models/sd3/sd3_model.py +++ b/modules/models/sd3/sd3_model.py @@ -47,8 +47,7 @@ class SD3Inferencer(torch.nn.Module): return contextlib.nullcontext() def get_learned_conditioning(self, batch: list[str]): - with devices.without_autocast(): - return self.cond_stage_model(batch) + 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']) diff --git a/modules/sd_models.py b/modules/sd_models.py index 45575e44..68103044 100644 --- a/modules/sd_models.py +++ b/modules/sd_models.py @@ -718,16 +718,15 @@ def get_empty_cond(sd_model): p = processing.StableDiffusionProcessingTxt2Img() extra_networks.activate(p, {}) - if hasattr(sd_model, 'conditioner'): + if hasattr(sd_model, 'get_learned_conditioning'): d = sd_model.get_learned_conditioning([""]) - return d['crossattn'] else: d = sd_model.cond_stage_model([""]) - if isinstance(d, dict): - d = d['crossattn'] + if isinstance(d, dict): + d = d['crossattn'] - return d + return d def send_model_to_cpu(m): From d67348a0a54a4d4c612673d3622ef0a617036646 Mon Sep 17 00:00:00 2001 From: AUTOMATIC1111 <16777216c@gmail.com> Date: Fri, 28 Jun 2024 18:06:49 +0300 Subject: [PATCH 400/496] allow generation to be started with any dimensions specified --- modules/models/sd3/sd3_model.py | 3 +++ modules/processing.py | 3 +++ 2 files changed, 6 insertions(+) diff --git a/modules/models/sd3/sd3_model.py b/modules/models/sd3/sd3_model.py index 336e8d2d..2d66b80f 100644 --- a/modules/models/sd3/sd3_model.py +++ b/modules/models/sd3/sd3_model.py @@ -75,3 +75,6 @@ class SD3Inferencer(torch.nn.Module): 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 diff --git a/modules/processing.py b/modules/processing.py index c3ce975e..7535b56e 100644 --- a/modules/processing.py +++ b/modules/processing.py @@ -884,6 +884,9 @@ def process_images_inner(p: StableDiffusionProcessing) -> Processed: if p.refiner_checkpoint_info is None: 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_hash = shared.sd_model.sd_model_hash p.sd_vae_name = sd_vae.get_loaded_vae_name() From 7e4b06fcd0b9b47fcc4f0a1261039358114bbacd Mon Sep 17 00:00:00 2001 From: AUTOMATIC1111 <16777216c@gmail.com> Date: Sat, 29 Jun 2024 00:38:52 +0300 Subject: [PATCH 401/496] support loading clip/t5 from the main model checkpoint --- modules/models/sd3/sd3_cond.py | 30 +++++++++++------------------- modules/models/sd3/sd3_model.py | 10 +++++++--- modules/sd_models.py | 9 ++++++--- 3 files changed, 24 insertions(+), 25 deletions(-) diff --git a/modules/models/sd3/sd3_cond.py b/modules/models/sd3/sd3_cond.py index e25ba1b6..bade90ba 100644 --- a/modules/models/sd3/sd3_cond.py +++ b/modules/models/sd3/sd3_cond.py @@ -174,15 +174,10 @@ class SD3Cond(torch.nn.Module): self.model_lg = Sd3ClipLG(self.clip_l, self.clip_g) self.model_t5 = Sd3T5(self.t5xxl) - self.weights_loaded = False - def forward(self, prompts: list[str]): with devices.without_autocast(): lg_out, vector_out = self.model_lg(prompts) - - token_count = lg_out.shape[1] - - t5_out = self.model_t5(prompts, token_count=token_count) + t5_out = self.model_t5(prompts, token_count=lg_out.shape[1]) lgt_out = torch.cat([lg_out, t5_out], dim=-2) return { @@ -190,27 +185,24 @@ class SD3Cond(torch.nn.Module): 'vector': vector_out, } - def load_weights(self): - if self.weights_loaded: - return - + def before_load_weights(self, state_dict): clip_path = os.path.join(shared.models_path, "CLIP") - 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_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)) - 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 '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: + 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) - self.weights_loaded = True - def encode_embedding_init_text(self, init_text, nvpt): return torch.tensor([[0]], device=devices.device) # XXX diff --git a/modules/models/sd3/sd3_model.py b/modules/models/sd3/sd3_model.py index 2d66b80f..98470cda 100644 --- a/modules/models/sd3/sd3_model.py +++ b/modules/models/sd3/sd3_model.py @@ -31,7 +31,7 @@ class SD3Inferencer(torch.nn.Module): self.alphas_cumprod = 1 / (self.model.model_sampling.sigmas ** 2 + 1) - self.cond_stage_model = SD3Cond() + self.text_encoders = SD3Cond() self.cond_stage_key = 'txt' self.parameterization = "eps" @@ -40,8 +40,12 @@ class SD3Inferencer(torch.nn.Module): self.latent_format = SD3LatentFormat() self.latent_channels = 16 - def after_load_weights(self): - self.cond_stage_model.load_weights() + @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() diff --git a/modules/sd_models.py b/modules/sd_models.py index 68103044..55bd9ca5 100644 --- a/modules/sd_models.py +++ b/modules/sd_models.py @@ -434,9 +434,15 @@ def load_model_weights(model, checkpoint_info: CheckpointInfo, state_dict, timer # cache newly loaded model checkpoints_loaded[checkpoint_info] = state_dict.copy() + if hasattr(model, "before_load_weights"): + model.before_load_weights(state_dict) + model.load_state_dict(state_dict, strict=False) timer.record("apply weights to model") + if hasattr(model, "after_load_weights"): + model.after_load_weights(state_dict) + del state_dict # Set is_sdxl_inpaint flag. @@ -838,9 +844,6 @@ def load_model(checkpoint_info=None, already_loaded_state_dict=None): with sd_disable_initialization.LoadStateDictOnMeta(state_dict, device=model_target_device(sd_model), weight_dtype_conversion=weight_dtype_conversion): load_model_weights(sd_model, checkpoint_info, state_dict, timer) - if hasattr(sd_model, "after_load_weights"): - sd_model.after_load_weights() - timer.record("load weights from state dict") send_model_to_device(sd_model) From 1394ecaf36da365e6bdd16ff20a4ef661e94623f Mon Sep 17 00:00:00 2001 From: AUTOMATIC1111 <16777216c@gmail.com> Date: Sat, 29 Jun 2024 08:05:35 +0300 Subject: [PATCH 402/496] do sampler calculations on CPU --- modules/sd_samplers_kdiffusion.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/modules/sd_samplers_kdiffusion.py b/modules/sd_samplers_kdiffusion.py index 8398299f..95a354da 100644 --- a/modules/sd_samplers_kdiffusion.py +++ b/modules/sd_samplers_kdiffusion.py @@ -125,7 +125,7 @@ class KDiffusionSampler(sd_samplers_common.Sampler): if discard_next_to_last_sigma: sigmas = torch.cat([sigmas[:-2], sigmas[-1:]]) - return sigmas + return sigmas.cpu() def sample_img2img(self, p, x, noise, conditioning, unconditional_conditioning, steps=None, image_conditioning=None): steps, t_enc = sd_samplers_common.setup_img2img_steps(p, steps) From ebe8be9028b1a01cc21ff1c49a7fce7bd5138f1b Mon Sep 17 00:00:00 2001 From: AUTOMATIC1111 <16777216c@gmail.com> Date: Sat, 29 Jun 2024 08:05:55 +0300 Subject: [PATCH 403/496] remove AutocastLinear from SD3's MLP --- modules/models/sd3/other_impls.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/modules/models/sd3/other_impls.py b/modules/models/sd3/other_impls.py index 002fe483..f992db9b 100644 --- a/modules/models/sd3/other_impls.py +++ b/modules/models/sd3/other_impls.py @@ -39,9 +39,9 @@ class Mlp(nn.Module): out_features = out_features or in_features hidden_features = hidden_features or in_features - self.fc1 = AutocastLinear(in_features, hidden_features, bias=bias, dtype=dtype, device=device) + self.fc1 = nn.Linear(in_features, hidden_features, bias=bias, dtype=dtype, device=device) self.act = act_layer - self.fc2 = AutocastLinear(hidden_features, out_features, bias=bias, dtype=dtype, device=device) + self.fc2 = nn.Linear(hidden_features, out_features, bias=bias, dtype=dtype, device=device) def forward(self, x): x = self.fc1(x) From 9e404c315432ca9aca2b9805e462c2360b19f5ae Mon Sep 17 00:00:00 2001 From: AUTOMATIC1111 <16777216c@gmail.com> Date: Sun, 30 Jun 2024 07:06:28 +0300 Subject: [PATCH 404/496] fix --medvram --- modules/models/sd3/sd3_model.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/modules/models/sd3/sd3_model.py b/modules/models/sd3/sd3_model.py index 98470cda..dbec8168 100644 --- a/modules/models/sd3/sd3_model.py +++ b/modules/models/sd3/sd3_model.py @@ -73,7 +73,7 @@ class SD3Inferencer(torch.nn.Module): def medvram_fields(self): return [ (self, 'first_stage_model'), - (self, 'cond_stage_model'), + (self, 'text_encoders'), (self, 'model'), ] From 6ddcd8914ba7bead27cc72964959076b4e24fc9b Mon Sep 17 00:00:00 2001 From: viking1304 Date: Sun, 30 Jun 2024 11:44:06 +0200 Subject: [PATCH 405/496] ensure use of python from venv --- webui.sh | 2 ++ 1 file changed, 2 insertions(+) diff --git a/webui.sh b/webui.sh index 7acea902..a101e80f 100755 --- a/webui.sh +++ b/webui.sh @@ -217,6 +217,8 @@ then if [[ -f "${venv_dir}"/bin/activate ]] then source "${venv_dir}"/bin/activate + # ensure use of python from venv + python_cmd="${venv_dir}"/bin/python else printf "\n%s\n" "${delimiter}" printf "\e[1m\e[31mERROR: Cannot activate python venv, aborting...\e[0m" From 957185f7eb5a9aecea5853517f30dfe8f9c4ca58 Mon Sep 17 00:00:00 2001 From: w-e-w <40751091+w-e-w@users.noreply.github.com> Date: Sun, 30 Jun 2024 20:01:58 +0900 Subject: [PATCH 406/496] fix Replace preview fix broken Replace preview for extra networks tabs edit metadata caused by #11808 --- javascript/ui.js | 8 ++++++++ modules/ui_extra_networks_user_metadata.py | 2 +- 2 files changed, 9 insertions(+), 1 deletion(-) diff --git a/javascript/ui.js b/javascript/ui.js index ff6f8974..20309634 100644 --- a/javascript/ui.js +++ b/javascript/ui.js @@ -26,6 +26,14 @@ function selected_gallery_index() { 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) { if (gallery.length == 0) { return [null]; diff --git a/modules/ui_extra_networks_user_metadata.py b/modules/ui_extra_networks_user_metadata.py index fde09370..3a07db10 100644 --- a/modules/ui_extra_networks_user_metadata.py +++ b/modules/ui_extra_networks_user_metadata.py @@ -194,7 +194,7 @@ class UserMetadataEditor: def setup_ui(self, gallery): self.button_replace_preview.click( fn=self.save_preview, - _js="function(x, y, z){return [selected_gallery_index(), y, z]}", + _js=f"function(x, y, z){{return [selected_gallery_index_id('{self.tabname + '_gallery_container'}'), y, z]}}", inputs=[self.edit_name_input, gallery, self.edit_name_input], outputs=[self.html_preview, self.html_status] ).then( From fd16393465847acc07ce49df51f23683d7c690ec Mon Sep 17 00:00:00 2001 From: w-e-w <40751091+w-e-w@users.noreply.github.com> Date: Sun, 30 Jun 2024 21:19:25 +0900 Subject: [PATCH 407/496] defunct --max-batch-count --- modules/cmd_args.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/modules/cmd_args.py b/modules/cmd_args.py index 61ec1866..d71982b2 100644 --- a/modules/cmd_args.py +++ b/modules/cmd_args.py @@ -30,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-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("--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("--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") From 3971c015624cff2de7ea74f98bf14c53797c0568 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?I=CC=87brahim=20Su=CC=88ren?= Date: Wed, 3 Jul 2024 17:33:25 +0300 Subject: [PATCH 408/496] Return http 400 instead of 404 on invalid sampler --- modules/api/api.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/modules/api/api.py b/modules/api/api.py index f468c385..06cc854f 100644 --- a/modules/api/api.py +++ b/modules/api/api.py @@ -43,7 +43,7 @@ def script_name_to_index(name, scripts): def validate_sampler_name(name): config = sd_samplers.all_samplers_map.get(name, 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 From 32fdf18203cee09b558aa3e299fa4f9c0c69c8e0 Mon Sep 17 00:00:00 2001 From: Andrey Efremov <50556416+AndreyRGW@users.noreply.github.com> Date: Thu, 4 Jul 2024 00:56:18 +0300 Subject: [PATCH 409/496] Add Simple Scheduler --- modules/sd_schedulers.py | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/modules/sd_schedulers.py b/modules/sd_schedulers.py index 0165e6a0..59098d62 100644 --- a/modules/sd_schedulers.py +++ b/modules/sd_schedulers.py @@ -76,6 +76,14 @@ def kl_optimal(n, sigma_min, sigma_max, device): sigmas = torch.tan(step_indices / n * alpha_min + (1.0 - step_indices / n) * alpha_max) return sigmas +def simple_scheduler(n, sigma_min, sigma_max, inner_model, device): + sigs = [] + ss = len(inner_model.sigmas) / n + for x in range(n): + sigs += [float(inner_model.sigmas[-(1 + int(x * ss))])] + sigs += [0.0] + return torch.FloatTensor(sigs).to(device) + schedulers = [ Scheduler('automatic', 'Automatic', None), @@ -86,6 +94,7 @@ schedulers = [ Scheduler('sgm_uniform', 'SGM Uniform', sgm_uniform, need_inner_model=True, aliases=["SGMUniform"]), Scheduler('kl_optimal', 'KL Optimal', kl_optimal), Scheduler('align_your_steps', 'Align Your Steps', get_align_your_steps_sigmas), + Scheduler('simple', 'Simple', simple_scheduler, need_inner_model=True), ] schedulers_map = {**{x.name: x for x in schedulers}, **{x.label: x for x in schedulers}} From f8fb74b93ab3f78dcec05e52e669b3b89b3a3b26 Mon Sep 17 00:00:00 2001 From: Aarni Koskela Date: Thu, 4 Jul 2024 08:43:48 +0300 Subject: [PATCH 410/496] Bump Spandrel to 0.3.4; add spandrel-extra-arches for CodeFormer --- modules/gfpgan_model.py | 4 +--- modules/modelloader.py | 32 +++++++++++++++++++++++++++++--- requirements_versions.txt | 3 ++- 3 files changed, 32 insertions(+), 7 deletions(-) diff --git a/modules/gfpgan_model.py b/modules/gfpgan_model.py index 445b0409..01ef899e 100644 --- a/modules/gfpgan_model.py +++ b/modules/gfpgan_model.py @@ -36,13 +36,11 @@ class FaceRestorerGFPGAN(face_restoration_utils.CommonFaceRestoration): ext_filter=['.pth'], ): if 'GFPGAN' in os.path.basename(model_path): - model = modelloader.load_spandrel_model( + return modelloader.load_spandrel_model( model_path, device=self.get_device(), expected_architecture='GFPGAN', ).model - model.different_w = True # see https://github.com/chaiNNer-org/spandrel/pull/81 - return model raise ValueError("No GFPGAN model found") def restore(self, np_image): diff --git a/modules/modelloader.py b/modules/modelloader.py index 5421e59b..36e7415a 100644 --- a/modules/modelloader.py +++ b/modules/modelloader.py @@ -139,6 +139,27 @@ def load_upscalers(): 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( path: str | os.PathLike, @@ -148,11 +169,16 @@ def load_spandrel_model( dtype: str | torch.dtype | None = None, expected_architecture: str | None = None, ) -> spandrel.ModelDescriptor: + global _spandrel_extra_init_state + import spandrel + _init_spandrel_extra_archs() + 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( - 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 if prefer_half: @@ -166,6 +192,6 @@ def load_spandrel_model( model_descriptor.model.eval() logger.debug( "Loaded %s from %s (device=%s, half=%s, dtype=%s)", - model_descriptor, path, device, half, dtype, + arch, path, device, half, dtype, ) return model_descriptor diff --git a/requirements_versions.txt b/requirements_versions.txt index 3037a395..050b6d1f 100644 --- a/requirements_versions.txt +++ b/requirements_versions.txt @@ -23,7 +23,8 @@ pytorch_lightning==1.9.4 resize-right==0.0.2 safetensors==0.4.2 scikit-image==0.21.0 -spandrel==0.1.6 +spandrel==0.3.4 +spandrel-extra-arches==0.1.1 tomesd==0.1.3 torch torchdiffeq==0.2.3 From f8640662c56db75d7c16fe9224ada4e2fa81981e Mon Sep 17 00:00:00 2001 From: Andrey Efremov <50556416+AndreyRGW@users.noreply.github.com> Date: Thu, 4 Jul 2024 19:27:08 +0300 Subject: [PATCH 411/496] Add Normal and DDIM Schedulers --- modules/sd_schedulers.py | 29 +++++++++++++++++++++++++++++ 1 file changed, 29 insertions(+) diff --git a/modules/sd_schedulers.py b/modules/sd_schedulers.py index 0165e6a0..118beea5 100644 --- a/modules/sd_schedulers.py +++ b/modules/sd_schedulers.py @@ -76,6 +76,33 @@ def kl_optimal(n, sigma_min, sigma_max, device): sigmas = torch.tan(step_indices / n * alpha_min + (1.0 - step_indices / n) * alpha_max) return sigmas +def normal_scheduler(n, sigma_min, sigma_max, inner_model, device, sgm=False, floor=False): + start = inner_model.sigma_to_t(torch.tensor(sigma_max)) + end = inner_model.sigma_to_t(torch.tensor(sigma_min)) + + if sgm: + timesteps = torch.linspace(start, end, n + 1)[:-1] + else: + timesteps = torch.linspace(start, end, n) + + sigs = [] + for x in range(len(timesteps)): + ts = timesteps[x] + sigs.append(inner_model.t_to_sigma(ts)) + sigs += [0.0] + return torch.FloatTensor(sigs).to(device) + +def ddim_scheduler(n, sigma_min, sigma_max, inner_model, device): + sigs = [] + ss = max(len(inner_model.sigmas) // n, 1) + x = 1 + while x < len(inner_model.sigmas): + sigs += [float(inner_model.sigmas[x])] + x += ss + sigs = sigs[::-1] + sigs += [0.0] + return torch.FloatTensor(sigs).to(device) + schedulers = [ Scheduler('automatic', 'Automatic', None), @@ -86,6 +113,8 @@ schedulers = [ Scheduler('sgm_uniform', 'SGM Uniform', sgm_uniform, need_inner_model=True, aliases=["SGMUniform"]), Scheduler('kl_optimal', 'KL Optimal', kl_optimal), Scheduler('align_your_steps', 'Align Your Steps', get_align_your_steps_sigmas), + Scheduler('normal', 'Normal', normal_scheduler, need_inner_model=True), + Scheduler('ddim', 'DDIM', ddim_scheduler, need_inner_model=True), ] schedulers_map = {**{x.name: x for x in schedulers}, **{x.label: x for x in schedulers}} From bfbca310744e29d502e56c11dc212323bc9158ab Mon Sep 17 00:00:00 2001 From: Kohaku-Blueleaf <59680068+KohakuBlueleaf@users.noreply.github.com> Date: Fri, 5 Jul 2024 18:56:39 +0800 Subject: [PATCH 412/496] possible fix of wrong scale https://github.com/comfyanonymous/ComfyUI/pull/3922 --- extensions-builtin/Lora/network.py | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/extensions-builtin/Lora/network.py b/extensions-builtin/Lora/network.py index 20f8df3d..3c39c49d 100644 --- a/extensions-builtin/Lora/network.py +++ b/extensions-builtin/Lora/network.py @@ -204,10 +204,12 @@ class NetworkModule: if ex_bias is not None: ex_bias = ex_bias * self.multiplier() + updown = updown * self.calc_scale() + if self.dora_scale is not None: updown = self.apply_weight_decompose(updown, orig_weight) - return updown * self.calc_scale() * self.multiplier(), ex_bias + return updown * self.multiplier(), ex_bias def calc_updown(self, target): raise NotImplementedError() From b82caf132274aa3cd9b087ed9dc671e8987a4686 Mon Sep 17 00:00:00 2001 From: Andray Date: Fri, 5 Jul 2024 19:28:16 +0400 Subject: [PATCH 413/496] fix ui flashing on reloading and fast scrollong --- modules/ui_gradio_extensions.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/modules/ui_gradio_extensions.py b/modules/ui_gradio_extensions.py index 18fbd677..c895b3b6 100644 --- a/modules/ui_gradio_extensions.py +++ b/modules/ui_gradio_extensions.py @@ -41,6 +41,8 @@ def css_html(): if os.path.exists(user_css): head += stylesheet(user_css) + head += '' + return head From 0a6628bad0615a640efd99937eb6d10d6d648975 Mon Sep 17 00:00:00 2001 From: AUTOMATIC1111 <16777216c@gmail.com> Date: Sat, 6 Jul 2024 10:31:08 +0300 Subject: [PATCH 414/496] remove mentions of specific samplers from CFG denoiser code --- modules/sd_samplers_cfg_denoiser.py | 16 +++++++--------- modules/sd_samplers_timesteps_impl.py | 3 +++ 2 files changed, 10 insertions(+), 9 deletions(-) diff --git a/modules/sd_samplers_cfg_denoiser.py b/modules/sd_samplers_cfg_denoiser.py index c8eeedad..b6fbf337 100644 --- a/modules/sd_samplers_cfg_denoiser.py +++ b/modules/sd_samplers_cfg_denoiser.py @@ -58,6 +58,9 @@ class CFGDenoiser(torch.nn.Module): self.model_wrap = None self.p = None + self.cond_scale_miltiplier = 1.0 + + self.need_last_noise_uncond = False self.last_noise_uncond = None # NOTE: masking before denoising can cause the original latents to be oversmoothed @@ -162,8 +165,6 @@ class CFGDenoiser(torch.nn.Module): # so is_edit_model is set to False to support AND composition. is_edit_model = shared.sd_model.cond_stage_key == "edit" and self.image_cfg_scale is not None and self.image_cfg_scale != 1.0 - is_cfg_pp = 'CFG++' in self.sampler.config.name - conds_list, tensor = prompt_parser.reconstruct_multicond_batch(cond, self.step) uncond = prompt_parser.reconstruct_cond_batch(uncond, self.step) @@ -277,18 +278,15 @@ class CFGDenoiser(torch.nn.Module): denoised_params = CFGDenoisedParams(x_out, state.sampling_step, state.sampling_steps, self.inner_model) cfg_denoised_callback(denoised_params) - if is_cfg_pp: - self.last_noise_uncond = x_out[-uncond.shape[0]:] - self.last_noise_uncond = torch.clone(self.last_noise_uncond) + if self.need_last_noise_uncond: + self.last_noise_uncond = torch.clone(x_out[-uncond.shape[0]:]) if is_edit_model: - denoised = self.combine_denoised_for_edit_model(x_out, cond_scale) + denoised = self.combine_denoised_for_edit_model(x_out, cond_scale * self.cond_scale_miltiplier) elif skip_uncond: denoised = self.combine_denoised(x_out, conds_list, uncond, 1.0) - elif is_cfg_pp: - denoised = self.combine_denoised(x_out, conds_list, uncond, cond_scale/12.5) # CFG++ scale of (0, 1) maps to (1.0, 12.5) else: - denoised = self.combine_denoised(x_out, conds_list, uncond, cond_scale) + denoised = self.combine_denoised(x_out, conds_list, uncond, cond_scale * self.cond_scale_miltiplier) # Blend in the original latents (after) if not self.mask_before_denoising and self.mask is not None: diff --git a/modules/sd_samplers_timesteps_impl.py b/modules/sd_samplers_timesteps_impl.py index 8896cfc9..180e4389 100644 --- a/modules/sd_samplers_timesteps_impl.py +++ b/modules/sd_samplers_timesteps_impl.py @@ -52,6 +52,9 @@ def ddim_cfgpp(model, x, timesteps, extra_args=None, callback=None, disable=None sqrt_one_minus_alphas = torch.sqrt(1 - alphas) sigmas = eta * np.sqrt((1 - alphas_prev.cpu().numpy()) / (1 - alphas.cpu()) * (1 - alphas.cpu() / alphas_prev.cpu().numpy())) + model.cond_scale_miltiplier = 1 / 12.5 + model.need_last_noise_uncond = True + extra_args = {} if extra_args is None else extra_args s_in = x.new_ones((x.shape[0])) s_x = x.new_ones((x.shape[0], 1, 1, 1)) From ffead92d4e36a5082fa6ac5dd54c88477c9b524e Mon Sep 17 00:00:00 2001 From: AUTOMATIC1111 <16777216c@gmail.com> Date: Sat, 6 Jul 2024 10:40:48 +0300 Subject: [PATCH 415/496] Revert "Merge pull request #16078 from huchenlei/fix_sd2" This reverts commit 4cc3add770b10cb8e8f7aa980c0d50e5b637ab2b, reversing changes made to 50514ce414ee4fad9aa4780ef0b97116c7d7c970. --- modules/sd_hijack_unet.py | 2 -- modules/sd_models_config.py | 7 +------ 2 files changed, 1 insertion(+), 8 deletions(-) diff --git a/modules/sd_hijack_unet.py b/modules/sd_hijack_unet.py index 6d657511..b4f03b13 100644 --- a/modules/sd_hijack_unet.py +++ b/modules/sd_hijack_unet.py @@ -138,7 +138,6 @@ CondFunc('ldm.models.diffusion.ddpm.LatentDiffusion.decode_first_stage', first_s CondFunc('ldm.models.diffusion.ddpm.LatentDiffusion.encode_first_stage', first_stage_sub, first_stage_cond) CondFunc('ldm.models.diffusion.ddpm.LatentDiffusion.get_first_stage_encoding', lambda orig_func, *args, **kwargs: orig_func(*args, **kwargs).float(), first_stage_cond) -# Always make sure inputs to unet are in correct dtype CondFunc('ldm.models.diffusion.ddpm.LatentDiffusion.apply_model', apply_model) CondFunc('sgm.modules.diffusionmodules.wrappers.OpenAIWrapper.forward', apply_model) @@ -151,6 +150,5 @@ def timestep_embedding_cast_result(orig_func, timesteps, *args, **kwargs): return orig_func(timesteps, *args, **kwargs).to(dtype=dtype) -# Always make sure timestep calculation is in correct dtype CondFunc('ldm.modules.diffusionmodules.openaimodel.timestep_embedding', timestep_embedding_cast_result) CondFunc('sgm.modules.diffusionmodules.openaimodel.timestep_embedding', timestep_embedding_cast_result) diff --git a/modules/sd_models_config.py b/modules/sd_models_config.py index e9a80eba..7cfeca67 100644 --- a/modules/sd_models_config.py +++ b/modules/sd_models_config.py @@ -56,19 +56,14 @@ def is_using_v_parameterization_for_sd2(state_dict): unet.eval() with torch.no_grad(): - unet_dtype = torch.float - original_unet_dtype = devices.dtype_unet - unet_sd = {k.replace("model.diffusion_model.", ""): v for k, v in state_dict.items() if "model.diffusion_model." in k} unet.load_state_dict(unet_sd, strict=True) - unet.to(device=device, dtype=unet_dtype) - devices.dtype_unet = unet_dtype + unet.to(device=device, dtype=torch.float) test_cond = torch.ones((1, 2, 1024), device=device) * 0.5 x_test = torch.ones((1, 4, 8, 8), device=device) * 0.5 out = (unet(x_test, torch.asarray([999], device=device), context=test_cond) - x_test).mean().item() - devices.dtype_unet = original_unet_dtype return out < -1 From 74069addc31e6cb24a5fb394419aef87b43a8b2c Mon Sep 17 00:00:00 2001 From: AUTOMATIC1111 <16777216c@gmail.com> Date: Sat, 6 Jul 2024 11:00:22 +0300 Subject: [PATCH 416/496] SD2 v autodetection fix --- modules/sd_models_config.py | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/modules/sd_models_config.py b/modules/sd_models_config.py index 599153c2..fb44c5a8 100644 --- a/modules/sd_models_config.py +++ b/modules/sd_models_config.py @@ -58,12 +58,13 @@ def is_using_v_parameterization_for_sd2(state_dict): with torch.no_grad(): unet_sd = {k.replace("model.diffusion_model.", ""): v for k, v in state_dict.items() if "model.diffusion_model." in k} unet.load_state_dict(unet_sd, strict=True) - unet.to(device=device, dtype=torch.float) + unet.to(device=device, dtype=devices.dtype_unet) test_cond = torch.ones((1, 2, 1024), device=device) * 0.5 x_test = torch.ones((1, 4, 8, 8), device=device) * 0.5 - out = (unet(x_test, torch.asarray([999], device=device), context=test_cond) - x_test).mean().item() + with devices.autocast(): + out = (unet(x_test, torch.asarray([999], device=device), context=test_cond) - x_test).mean().cpu().item() return out < -1 From 340a9108ca800774e274b61a70517d1938b39ead Mon Sep 17 00:00:00 2001 From: AUTOMATIC1111 <16777216c@gmail.com> Date: Sat, 6 Jul 2024 11:26:14 +0300 Subject: [PATCH 417/496] update changelog --- CHANGELOG.md | 97 ++++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 97 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 596b1ec4..368b2953 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,100 @@ +## 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)) + * 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)) +* 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)) + +### 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)) + +### 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)) +* 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)) + + ## 1.9.4 ### Bug Fixes: From ec580374e54cb45cce504b9c8455a748fa1f991d Mon Sep 17 00:00:00 2001 From: w-e-w <40751091+w-e-w@users.noreply.github.com> Date: Sun, 7 Jul 2024 00:22:27 +0900 Subject: [PATCH 418/496] background-color: background_fill_primary --- modules/shared_gradio_themes.py | 41 +++++++++++++++++++++++++++++++++ modules/ui_gradio_extensions.py | 5 +++- 2 files changed, 45 insertions(+), 1 deletion(-) diff --git a/modules/shared_gradio_themes.py b/modules/shared_gradio_themes.py index b6dc3145..b4e3f32b 100644 --- a/modules/shared_gradio_themes.py +++ b/modules/shared_gradio_themes.py @@ -69,3 +69,44 @@ def reload_gradio_theme(theme_name=None): # append additional values gradio_theme shared.gradio_theme.sd_webui_modal_lightbox_toolbar_opacity = shared.opts.sd_webui_modal_lightbox_toolbar_opacity shared.gradio_theme.sd_webui_modal_lightbox_icon_opacity = shared.opts.sd_webui_modal_lightbox_icon_opacity + + +def resolve_var(name: str, gradio_theme=None, history=None): + """ + Attempt to resolve a theme variable name to its value + + Parameters: + name (str): The name of the theme variable + ie "background_fill_primary", "background_fill_primary_dark" + spaces and asterisk (*) prefix is removed from name before lookup + gradio_theme (gradio.themes.ThemeClass): The theme object to resolve the variable from + blank to use the webui default shared.gradio_theme + history (list): A list of previously resolved variables to prevent circular references + for regular use leave blank + Returns: + str: The resolved value + + Error handling: + return either #000000 or #ffffff depending on initial name ending with "_dark" + """ + try: + if history is None: + history = [] + if gradio_theme is None: + gradio_theme = shared.gradio_theme + + name = name.strip() + name = name[1:] if name.startswith("*") else name + + if name in history: + raise ValueError(f'Circular references: name "{name}" in {history}') + + if value := getattr(gradio_theme, name, None): + return resolve_var(value, gradio_theme, history + [name]) + else: + return name + + except Exception: + name = history[0] if history else name + errors.report(f'resolve_color({name})', exc_info=True) + return '#000000' if name.endswith("_dark") else '#ffffff' diff --git a/modules/ui_gradio_extensions.py b/modules/ui_gradio_extensions.py index c895b3b6..ed57c1e9 100644 --- a/modules/ui_gradio_extensions.py +++ b/modules/ui_gradio_extensions.py @@ -41,7 +41,10 @@ def css_html(): if os.path.exists(user_css): head += stylesheet(user_css) - head += '' + from modules.shared_gradio_themes import resolve_var + light = resolve_var('background_fill_primary') + dark = resolve_var('background_fill_primary_dark') + head += f'' return head From b5481c619583bf2e6212234ab129a46a225229c5 Mon Sep 17 00:00:00 2001 From: AUTOMATIC1111 <16777216c@gmail.com> Date: Sun, 7 Jul 2024 08:37:58 +0300 Subject: [PATCH 419/496] Merge pull request #16153 from light-and-ray/fix_ui_flashing_on_reload_and_fast_scrollong fix ui flashing on reloading and fast scrollong --- modules/shared_gradio_themes.py | 41 +++++++++++++++++++++++++++++++++ modules/ui_gradio_extensions.py | 5 ++++ 2 files changed, 46 insertions(+) diff --git a/modules/shared_gradio_themes.py b/modules/shared_gradio_themes.py index b6dc3145..b4e3f32b 100644 --- a/modules/shared_gradio_themes.py +++ b/modules/shared_gradio_themes.py @@ -69,3 +69,44 @@ def reload_gradio_theme(theme_name=None): # append additional values gradio_theme shared.gradio_theme.sd_webui_modal_lightbox_toolbar_opacity = shared.opts.sd_webui_modal_lightbox_toolbar_opacity shared.gradio_theme.sd_webui_modal_lightbox_icon_opacity = shared.opts.sd_webui_modal_lightbox_icon_opacity + + +def resolve_var(name: str, gradio_theme=None, history=None): + """ + Attempt to resolve a theme variable name to its value + + Parameters: + name (str): The name of the theme variable + ie "background_fill_primary", "background_fill_primary_dark" + spaces and asterisk (*) prefix is removed from name before lookup + gradio_theme (gradio.themes.ThemeClass): The theme object to resolve the variable from + blank to use the webui default shared.gradio_theme + history (list): A list of previously resolved variables to prevent circular references + for regular use leave blank + Returns: + str: The resolved value + + Error handling: + return either #000000 or #ffffff depending on initial name ending with "_dark" + """ + try: + if history is None: + history = [] + if gradio_theme is None: + gradio_theme = shared.gradio_theme + + name = name.strip() + name = name[1:] if name.startswith("*") else name + + if name in history: + raise ValueError(f'Circular references: name "{name}" in {history}') + + if value := getattr(gradio_theme, name, None): + return resolve_var(value, gradio_theme, history + [name]) + else: + return name + + except Exception: + name = history[0] if history else name + errors.report(f'resolve_color({name})', exc_info=True) + return '#000000' if name.endswith("_dark") else '#ffffff' diff --git a/modules/ui_gradio_extensions.py b/modules/ui_gradio_extensions.py index 18fbd677..ed57c1e9 100644 --- a/modules/ui_gradio_extensions.py +++ b/modules/ui_gradio_extensions.py @@ -41,6 +41,11 @@ def css_html(): if os.path.exists(user_css): head += stylesheet(user_css) + from modules.shared_gradio_themes import resolve_var + light = resolve_var('background_fill_primary') + dark = resolve_var('background_fill_primary_dark') + head += f'' + return head From 780c70f6eadc4f0d466f00fe7def2023eec3c8cf Mon Sep 17 00:00:00 2001 From: AUTOMATIC1111 <16777216c@gmail.com> Date: Sun, 7 Jul 2024 08:40:19 +0300 Subject: [PATCH 420/496] update changelog --- CHANGELOG.md | 1 + 1 file changed, 1 insertion(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 368b2953..ac2b4ad6 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -25,6 +25,7 @@ * 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)) ### Extensions and API: * Add process_before_every_sampling hook ([#15984](https://github.com/AUTOMATIC1111/stable-diffusion-webui/pull/15984)) From 11cfe0dd054926b5df81632f9e2b2a78738ccf95 Mon Sep 17 00:00:00 2001 From: AUTOMATIC1111 <16777216c@gmail.com> Date: Sun, 7 Jul 2024 16:36:53 +0300 Subject: [PATCH 421/496] sd3 TI support --- modules/models/sd3/other_impls.py | 8 +++++--- modules/models/sd3/sd3_cond.py | 6 +++++- modules/sd_hijack.py | 17 ++++++++++++++++- 3 files changed, 26 insertions(+), 5 deletions(-) diff --git a/modules/models/sd3/other_impls.py b/modules/models/sd3/other_impls.py index f992db9b..78c1dc68 100644 --- a/modules/models/sd3/other_impls.py +++ b/modules/models/sd3/other_impls.py @@ -5,6 +5,8 @@ import math from torch import nn from transformers import CLIPTokenizer, T5TokenizerFast +from modules import sd_hijack + ################################################################################################# ### Core/Utility @@ -110,9 +112,9 @@ class CLIPEncoder(torch.nn.Module): class CLIPEmbeddings(torch.nn.Module): - def __init__(self, embed_dim, vocab_size=49408, num_positions=77, dtype=None, device=None): + 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 = torch.nn.Embedding(vocab_size, embed_dim, dtype=dtype, device=device) + 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): @@ -127,7 +129,7 @@ class CLIPTextModel_(torch.nn.Module): intermediate_size = config_dict["intermediate_size"] intermediate_activation = config_dict["hidden_act"] super().__init__() - self.embeddings = CLIPEmbeddings(embed_dim, dtype=torch.float32, device=device) + 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) diff --git a/modules/models/sd3/sd3_cond.py b/modules/models/sd3/sd3_cond.py index bade90ba..325c512d 100644 --- a/modules/models/sd3/sd3_cond.py +++ b/modules/models/sd3/sd3_cond.py @@ -40,6 +40,7 @@ CLIPG_CONFIG = { "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" @@ -204,7 +205,10 @@ class SD3Cond(torch.nn.Module): self.t5xxl.transformer.load_state_dict(SafetensorsMapping(file), strict=False) def encode_embedding_init_text(self, init_text, nvpt): - return torch.tensor([[0]], device=devices.device) # XXX + 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] diff --git a/modules/sd_hijack.py b/modules/sd_hijack.py index d5b2989f..0de83054 100644 --- a/modules/sd_hijack.py +++ b/modules/sd_hijack.py @@ -359,13 +359,28 @@ class EmbeddingsWithFixes(torch.nn.Module): vec = embedding.vec[self.textual_inversion_key] if isinstance(embedding.vec, dict) else embedding.vec emb = devices.cond_cast_unet(vec) 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) 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(): conv2d_constructor = torch.nn.Conv2d.__init__ From 7b2917255a7d2065e7c956eb263b59b8262e97f3 Mon Sep 17 00:00:00 2001 From: Richard Tallent Date: Sun, 7 Jul 2024 11:18:17 -0500 Subject: [PATCH 422/496] Fix noisy DS_Store files for MacOS --- .gitignore | 1 + 1 file changed, 1 insertion(+) diff --git a/.gitignore b/.gitignore index 96cfe22d..091f7796 100644 --- a/.gitignore +++ b/.gitignore @@ -2,6 +2,7 @@ __pycache__ *.ckpt *.safetensors *.pth +.DS_Store /ESRGAN/* /SwinIR/* /repositories From 21e72d1a5e6cac133c379cae62a4315fec81ee9d Mon Sep 17 00:00:00 2001 From: w-e-w <40751091+w-e-w@users.noreply.github.com> Date: Mon, 8 Jul 2024 14:07:26 +0900 Subject: [PATCH 423/496] py 3.9 find_vae() --- scripts/xyz_grid.py | 9 ++++----- 1 file changed, 4 insertions(+), 5 deletions(-) diff --git a/scripts/xyz_grid.py b/scripts/xyz_grid.py index b184721b..b702c74d 100644 --- a/scripts/xyz_grid.py +++ b/scripts/xyz_grid.py @@ -118,11 +118,10 @@ def apply_size(p, x: str, xs) -> None: def find_vae(name: str): - match name := name.lower().strip(): - case 'auto', 'automatic': - return 'Automatic' - case 'none': - return 'None' + if name := name.strip().lower() in ('auto', 'automatic'): + return 'Automatic' + elif name == 'none': + return 'None' return next((k for k in modules.sd_vae.vae_dict if k.lower() == name), print(f'No VAE found for {name}; using Automatic') or 'Automatic') From c3d8b78b47dddb03bb4558d62c6eaffc167cc51b Mon Sep 17 00:00:00 2001 From: w-e-w <40751091+w-e-w@users.noreply.github.com> Date: Mon, 8 Jul 2024 14:17:51 +0900 Subject: [PATCH 424/496] py 3.9 compatibility --- extensions-builtin/Lora/networks.py | 1 + 1 file changed, 1 insertion(+) diff --git a/extensions-builtin/Lora/networks.py b/extensions-builtin/Lora/networks.py index 63e8c946..9ed8fa43 100644 --- a/extensions-builtin/Lora/networks.py +++ b/extensions-builtin/Lora/networks.py @@ -1,3 +1,4 @@ +from __future__ import annotations import gradio as gr import logging import os From 6ca7a453d40120f5a213a88e2576a9265a784573 Mon Sep 17 00:00:00 2001 From: AUTOMATIC1111 <16777216c@gmail.com> Date: Mon, 8 Jul 2024 08:27:07 +0300 Subject: [PATCH 425/496] Merge pull request #16169 from AUTOMATIC1111/py-3.9-compatibility Py 3.9 compatibility --- extensions-builtin/Lora/networks.py | 1 + scripts/xyz_grid.py | 9 ++++----- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/extensions-builtin/Lora/networks.py b/extensions-builtin/Lora/networks.py index 63e8c946..9ed8fa43 100644 --- a/extensions-builtin/Lora/networks.py +++ b/extensions-builtin/Lora/networks.py @@ -1,3 +1,4 @@ +from __future__ import annotations import gradio as gr import logging import os diff --git a/scripts/xyz_grid.py b/scripts/xyz_grid.py index b184721b..b702c74d 100644 --- a/scripts/xyz_grid.py +++ b/scripts/xyz_grid.py @@ -118,11 +118,10 @@ def apply_size(p, x: str, xs) -> None: def find_vae(name: str): - match name := name.lower().strip(): - case 'auto', 'automatic': - return 'Automatic' - case 'none': - return 'None' + if name := name.strip().lower() in ('auto', 'automatic'): + return 'Automatic' + elif name == 'none': + return 'None' return next((k for k in modules.sd_vae.vae_dict if k.lower() == name), print(f'No VAE found for {name}; using Automatic') or 'Automatic') From 1b0823db94ab491924fd1dc57287ef9dc5bff234 Mon Sep 17 00:00:00 2001 From: w-e-w <40751091+w-e-w@users.noreply.github.com> Date: Mon, 8 Jul 2024 14:51:06 +0900 Subject: [PATCH 426/496] shlex.join launch args in console log --- modules/launch_utils.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/modules/launch_utils.py b/modules/launch_utils.py index e22da4ec..93890cd1 100644 --- a/modules/launch_utils.py +++ b/modules/launch_utils.py @@ -9,6 +9,7 @@ import importlib.util import importlib.metadata import platform import json +import shlex from functools import lru_cache from modules import cmd_args, errors @@ -461,7 +462,7 @@ def configure_for_tests(): 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 if '--nowebui' in sys.argv: webui.api_only() From 7d7f7f4b49cc4265b48ee08fe13b7e7b03cecf98 Mon Sep 17 00:00:00 2001 From: w-e-w <40751091+w-e-w@users.noreply.github.com> Date: Mon, 8 Jul 2024 15:45:45 +0900 Subject: [PATCH 427/496] sysinfo handle psutil not working --- modules/sysinfo.py | 34 +++++++++++++++++++++++----------- 1 file changed, 23 insertions(+), 11 deletions(-) diff --git a/modules/sysinfo.py b/modules/sysinfo.py index f336251e..61433466 100644 --- a/modules/sysinfo.py +++ b/modules/sysinfo.py @@ -5,7 +5,6 @@ import sys import platform import hashlib import pkg_resources -import psutil import re import launch @@ -69,9 +68,27 @@ def check(x): return h.hexdigest() == m.group(1) -def get_dict(): - ram = psutil.virtual_memory() +def get_cpu_info(): + cpu_info = {"model": platform.processor()} + try: + import psutil + cpu_info["count logical"] = psutil.cpu_count(logical=True) + cpu_info["count physical"] = psutil.cpu_count(logical=False) + except Exception as e: + cpu_info["error"] = str(e) + return cpu_info + +def get_ram_info(): + try: + import psutil + ram = psutil.virtual_memory() + return {x: pretty_bytes(getattr(ram, x, 0)) for x in ["total", "used", "free", "active", "inactive", "buffers", "cached", "shared"] if getattr(ram, x, 0) != 0} + except Exception as e: + return str(e) + + +def get_dict(): res = { "Platform": platform.platform(), "Python": platform.python_version(), @@ -84,14 +101,8 @@ def get_dict(): "Commandline": get_argv(), "Torch env info": get_torch_sysinfo(), "Exceptions": errors.get_exceptions(), - "CPU": { - "model": platform.processor(), - "count logical": psutil.cpu_count(logical=True), - "count physical": psutil.cpu_count(logical=False), - }, - "RAM": { - x: pretty_bytes(getattr(ram, x, 0)) for x in ["total", "used", "free", "active", "inactive", "buffers", "cached", "shared"] if getattr(ram, x, 0) != 0 - }, + "CPU": get_cpu_info(), + "RAM": get_ram_info(), "Extensions": get_extensions(enabled=True), "Inactive extensions": get_extensions(enabled=False), "Environment": get_environment(), @@ -123,6 +134,7 @@ def get_argv(): return res + re_newline = re.compile(r"\r*\n") From 48dd4d9eaeaeb600bfcd27a02a4070192c55b858 Mon Sep 17 00:00:00 2001 From: AUTOMATIC1111 <16777216c@gmail.com> Date: Mon, 8 Jul 2024 18:36:28 +0300 Subject: [PATCH 428/496] Merge pull request #16170 from AUTOMATIC1111/shlex.quote-launch-args-in-console-log shlex.join launch args in console log --- modules/launch_utils.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/modules/launch_utils.py b/modules/launch_utils.py index e22da4ec..93890cd1 100644 --- a/modules/launch_utils.py +++ b/modules/launch_utils.py @@ -9,6 +9,7 @@ import importlib.util import importlib.metadata import platform import json +import shlex from functools import lru_cache from modules import cmd_args, errors @@ -461,7 +462,7 @@ def configure_for_tests(): 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 if '--nowebui' in sys.argv: webui.api_only() From 11f827c58b276dff946dccf4167d8e11159eeba5 Mon Sep 17 00:00:00 2001 From: w-e-w <40751091+w-e-w@users.noreply.github.com> Date: Mon, 8 Jul 2024 16:33:02 +0900 Subject: [PATCH 429/496] use pip freeze --all to get packages --- modules/sysinfo.py | 16 ++++++++++++++-- 1 file changed, 14 insertions(+), 2 deletions(-) diff --git a/modules/sysinfo.py b/modules/sysinfo.py index 61433466..65d4e3c9 100644 --- a/modules/sysinfo.py +++ b/modules/sysinfo.py @@ -4,7 +4,6 @@ import sys import platform import hashlib -import pkg_resources import re import launch @@ -88,6 +87,19 @@ def get_ram_info(): return str(e) +def get_packages(): + try: + import subprocess + return subprocess.check_output([sys.executable, '-m', 'pip', 'freeze', '--all']).decode("utf8").splitlines() + except Exception as pip_error: + try: + import importlib.metadata + packages = importlib.metadata.distributions() + return sorted([f"{package.metadata['Name']}=={package.version}" for package in packages]) + except Exception as e2: + return {'error pip': pip_error, 'error importlib': str(e2)} + + def get_dict(): res = { "Platform": platform.platform(), @@ -108,7 +120,7 @@ def get_dict(): "Environment": get_environment(), "Config": get_config(), "Startup": timer.startup_record, - "Packages": sorted([f"{pkg.key}=={pkg.version}" for pkg in pkg_resources.working_set]), + "Packages": get_packages(), } return res From 27947a79d619eac5ce40b3f2db62d422313d12f6 Mon Sep 17 00:00:00 2001 From: w-e-w <40751091+w-e-w@users.noreply.github.com> Date: Mon, 8 Jul 2024 16:56:06 +0900 Subject: [PATCH 430/496] git status --- modules/launch_utils.py | 8 ++++++++ modules/sysinfo.py | 11 +++++------ 2 files changed, 13 insertions(+), 6 deletions(-) diff --git a/modules/launch_utils.py b/modules/launch_utils.py index e22da4ec..0688f482 100644 --- a/modules/launch_utils.py +++ b/modules/launch_utils.py @@ -85,6 +85,14 @@ def git_tag(): return "" +@lru_cache() +def git_status(): + try: + return subprocess.check_output([git, "-C", script_path, "status"], shell=False, encoding='utf8').strip() + except Exception as e: + return str(e) + + def run(command, desc=None, errdesc=None, custom_env=None, live: bool = default_command_live) -> str: if desc is not None: print(desc) diff --git a/modules/sysinfo.py b/modules/sysinfo.py index 65d4e3c9..52617573 100644 --- a/modules/sysinfo.py +++ b/modules/sysinfo.py @@ -1,13 +1,12 @@ import json import os import sys - +import subprocess import platform import hashlib import re -import launch -from modules import paths_internal, timer, shared, extensions, errors +from modules import paths_internal, timer, shared, extensions, errors, launch_utils checksum_token = "DontStealMyGamePlz__WINNERS_DONT_USE_DRUGS__DONT_COPY_THAT_FLOPPY" environment_whitelist = { @@ -89,7 +88,6 @@ def get_ram_info(): def get_packages(): try: - import subprocess return subprocess.check_output([sys.executable, '-m', 'pip', 'freeze', '--all']).decode("utf8").splitlines() except Exception as pip_error: try: @@ -104,8 +102,9 @@ def get_dict(): res = { "Platform": platform.platform(), "Python": platform.python_version(), - "Version": launch.git_tag(), - "Commit": launch.commit_hash(), + "Version": launch_utils.git_tag(), + "Commit": launch_utils.commit_hash(), + "Git status": launch_utils.git_status(), "Script path": paths_internal.script_path, "Data path": paths_internal.data_path, "Extensions dir": paths_internal.extensions_dir, From dd4f798b97de3e32d0a6a1a18816b4cc28c6008e Mon Sep 17 00:00:00 2001 From: w-e-w <40751091+w-e-w@users.noreply.github.com> Date: Mon, 8 Jul 2024 19:20:49 +0900 Subject: [PATCH 431/496] fallback get_config() --- modules/sysinfo.py | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/modules/sysinfo.py b/modules/sysinfo.py index 52617573..2c08dd22 100644 --- a/modules/sysinfo.py +++ b/modules/sysinfo.py @@ -179,5 +179,9 @@ def get_extensions(*, enabled): def get_config(): try: return shared.opts.data - except Exception as e: - return str(e) + except Exception as _: + try: + with open(shared.cmd_opts.ui_settings_file, 'r') as f: + return json.load(f) + except Exception as e: + return str(e) From 27d96fa608da81b334163835fe39c1bb32984a7c Mon Sep 17 00:00:00 2001 From: w-e-w <40751091+w-e-w@users.noreply.github.com> Date: Mon, 8 Jul 2024 20:31:50 +0900 Subject: [PATCH 432/496] fallback Extensions info --- modules/sysinfo.py | 56 ++++++++++++++++++++++++++++++++++------------ 1 file changed, 42 insertions(+), 14 deletions(-) diff --git a/modules/sysinfo.py b/modules/sysinfo.py index 2c08dd22..c2bb35cd 100644 --- a/modules/sysinfo.py +++ b/modules/sysinfo.py @@ -99,6 +99,7 @@ def get_packages(): def get_dict(): + config = get_config() res = { "Platform": platform.platform(), "Python": platform.python_version(), @@ -114,10 +115,10 @@ def get_dict(): "Exceptions": errors.get_exceptions(), "CPU": get_cpu_info(), "RAM": get_ram_info(), - "Extensions": get_extensions(enabled=True), - "Inactive extensions": get_extensions(enabled=False), + "Extensions": get_extensions(enabled=True, fallback_disabled_extensions=config.get('disabled_extensions', [])), + "Inactive extensions": get_extensions(enabled=False, fallback_disabled_extensions=config.get('disabled_extensions', [])), "Environment": get_environment(), - "Config": get_config(), + "Config": config, "Startup": timer.startup_record, "Packages": get_packages(), } @@ -159,19 +160,46 @@ def get_torch_sysinfo(): return str(e) -def get_extensions(*, enabled): - +def run_git(path, *args): try: - def to_json(x: extensions.Extension): - return { - "name": x.name, - "path": x.path, - "version": x.version, - "branch": x.branch, - "remote": x.remote, - } + if os.path.isdir(os.path.join(path, '.git')): + return subprocess.check_output([launch_utils.git, '-C', path, *args], shell=False, encoding='utf8').strip() + return None + except Exception as e: + return str(e) - return [to_json(x) for x in extensions.extensions if not x.is_builtin and x.enabled == enabled] + +def get_info_from_repo_path(path): + return { + 'name': os.path.basename(path), + 'path': path, + 'version': run_git(path, 'rev-parse', 'HEAD'), + 'branch': run_git(path, 'branch', '--show-current'), + 'remote': run_git(path, 'remote', 'get-url', 'origin') + } + + +def get_extensions(*, enabled, fallback_disabled_extensions=None): + try: + if extensions.extensions: + def to_json(x: extensions.Extension): + return { + "name": x.name, + "path": x.path, + "version": x.version, + "branch": x.branch, + "remote": x.remote, + } + return [to_json(x) for x in extensions.extensions if not x.is_builtin and x.enabled == enabled] + else: + extensions_list = [] + for extension_dirname in sorted(os.listdir(paths_internal.extensions_dir)): + path = os.path.join(paths_internal.extensions_dir, extension_dirname) + if enabled == (extension_dirname in fallback_disabled_extensions): + continue + if os.path.isdir(path): + extensions_list.append(get_info_from_repo_path(path)) + return extensions_list except Exception as e: return str(e) From 3f6dcda3e50594a6581bee68f482901cd9ba5d5b Mon Sep 17 00:00:00 2001 From: w-e-w <40751091+w-e-w@users.noreply.github.com> Date: Mon, 8 Jul 2024 20:33:15 +0900 Subject: [PATCH 433/496] Extensions info full commit hash --- modules/sysinfo.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/modules/sysinfo.py b/modules/sysinfo.py index c2bb35cd..13427af6 100644 --- a/modules/sysinfo.py +++ b/modules/sysinfo.py @@ -173,7 +173,7 @@ def get_info_from_repo_path(path): return { 'name': os.path.basename(path), 'path': path, - 'version': run_git(path, 'rev-parse', 'HEAD'), + 'commit': run_git(path, 'rev-parse', 'HEAD'), 'branch': run_git(path, 'branch', '--show-current'), 'remote': run_git(path, 'remote', 'get-url', 'origin') } @@ -186,7 +186,7 @@ def get_extensions(*, enabled, fallback_disabled_extensions=None): return { "name": x.name, "path": x.path, - "version": x.version, + "commit": x.commit_hash, "branch": x.branch, "remote": x.remote, } From 4debd4d3ef62449787d6d02943f82ead356bfe48 Mon Sep 17 00:00:00 2001 From: w-e-w <40751091+w-e-w@users.noreply.github.com> Date: Tue, 9 Jul 2024 01:02:11 +0900 Subject: [PATCH 434/496] compact get_info_from_repo_path --- modules/sysinfo.py | 27 ++++++++++----------------- 1 file changed, 10 insertions(+), 17 deletions(-) diff --git a/modules/sysinfo.py b/modules/sysinfo.py index 13427af6..aa4328ed 100644 --- a/modules/sysinfo.py +++ b/modules/sysinfo.py @@ -5,6 +5,7 @@ import subprocess import platform import hashlib import re +from pathlib import Path from modules import paths_internal, timer, shared, extensions, errors, launch_utils @@ -162,20 +163,19 @@ def get_torch_sysinfo(): def run_git(path, *args): try: - if os.path.isdir(os.path.join(path, '.git')): - return subprocess.check_output([launch_utils.git, '-C', path, *args], shell=False, encoding='utf8').strip() - return None + return subprocess.check_output([launch_utils.git, '-C', path, *args], shell=False, encoding='utf8').strip() except Exception as e: return str(e) -def get_info_from_repo_path(path): +def get_info_from_repo_path(path: Path): + is_repo = (path / '.git').is_dir() return { - 'name': os.path.basename(path), - 'path': path, - 'commit': run_git(path, 'rev-parse', 'HEAD'), - 'branch': run_git(path, 'branch', '--show-current'), - 'remote': run_git(path, 'remote', 'get-url', 'origin') + 'name': path.name, + 'path': str(path), + 'commit': run_git(path, 'rev-parse', 'HEAD') if is_repo else None, + 'branch': run_git(path, 'branch', '--show-current') if is_repo else None, + 'remote': run_git(path, 'remote', 'get-url', 'origin') if is_repo else None, } @@ -192,14 +192,7 @@ def get_extensions(*, enabled, fallback_disabled_extensions=None): } return [to_json(x) for x in extensions.extensions if not x.is_builtin and x.enabled == enabled] else: - extensions_list = [] - for extension_dirname in sorted(os.listdir(paths_internal.extensions_dir)): - path = os.path.join(paths_internal.extensions_dir, extension_dirname) - if enabled == (extension_dirname in fallback_disabled_extensions): - continue - if os.path.isdir(path): - extensions_list.append(get_info_from_repo_path(path)) - return extensions_list + return [get_info_from_repo_path(d) for d in Path(paths_internal.extensions_dir).iterdir() if d.is_dir() and enabled != (str(d.name) in fallback_disabled_extensions)] except Exception as e: return str(e) From 72cfa2829d9595b6c92d554035aec6e5d92a6602 Mon Sep 17 00:00:00 2001 From: w-e-w <40751091+w-e-w@users.noreply.github.com> Date: Tue, 9 Jul 2024 01:30:55 +0900 Subject: [PATCH 435/496] safer Imports --- modules/sysinfo.py | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/modules/sysinfo.py b/modules/sysinfo.py index aa4328ed..2faa5075 100644 --- a/modules/sysinfo.py +++ b/modules/sysinfo.py @@ -7,7 +7,7 @@ import hashlib import re from pathlib import Path -from modules import paths_internal, timer, shared, extensions, errors, launch_utils +from modules import paths_internal, timer, shared_cmd_options, errors, launch_utils checksum_token = "DontStealMyGamePlz__WINNERS_DONT_USE_DRUGS__DONT_COPY_THAT_FLOPPY" environment_whitelist = { @@ -135,11 +135,11 @@ def get_argv(): res = [] for v in sys.argv: - if shared.cmd_opts.gradio_auth and shared.cmd_opts.gradio_auth == v: + if shared_cmd_options.cmd_opts.gradio_auth and shared_cmd_options.cmd_opts.gradio_auth == v: res.append("") continue - if shared.cmd_opts.api_auth and shared.cmd_opts.api_auth == v: + if shared_cmd_options.cmd_opts.api_auth and shared_cmd_options.cmd_opts.api_auth == v: res.append("") continue @@ -181,6 +181,7 @@ def get_info_from_repo_path(path: Path): def get_extensions(*, enabled, fallback_disabled_extensions=None): try: + from modules import extensions if extensions.extensions: def to_json(x: extensions.Extension): return { @@ -199,10 +200,11 @@ def get_extensions(*, enabled, fallback_disabled_extensions=None): def get_config(): try: + from modules import shared return shared.opts.data except Exception as _: try: - with open(shared.cmd_opts.ui_settings_file, 'r') as f: + with open(shared_cmd_options.cmd_opts.ui_settings_file, 'r') as f: return json.load(f) except Exception as e: return str(e) From 6a7042fe2fe2974b61e7f6271bd8dad3fedd9dd1 Mon Sep 17 00:00:00 2001 From: w-e-w <40751091+w-e-w@users.noreply.github.com> Date: Tue, 9 Jul 2024 01:51:47 +0900 Subject: [PATCH 436/496] move git_status to sysinfo --- modules/launch_utils.py | 9 --------- modules/sysinfo.py | 7 ++++++- 2 files changed, 6 insertions(+), 10 deletions(-) diff --git a/modules/launch_utils.py b/modules/launch_utils.py index 0688f482..b2cc7127 100644 --- a/modules/launch_utils.py +++ b/modules/launch_utils.py @@ -85,14 +85,6 @@ def git_tag(): return "" -@lru_cache() -def git_status(): - try: - return subprocess.check_output([git, "-C", script_path, "status"], shell=False, encoding='utf8').strip() - except Exception as e: - return str(e) - - def run(command, desc=None, errdesc=None, custom_env=None, live: bool = default_command_live) -> str: if desc is not None: print(desc) @@ -453,7 +445,6 @@ def prepare_environment(): exit(0) - def configure_for_tests(): if "--api" not in sys.argv: sys.argv.append("--api") diff --git a/modules/sysinfo.py b/modules/sysinfo.py index 2faa5075..e9a83d74 100644 --- a/modules/sysinfo.py +++ b/modules/sysinfo.py @@ -106,7 +106,7 @@ def get_dict(): "Python": platform.python_version(), "Version": launch_utils.git_tag(), "Commit": launch_utils.commit_hash(), - "Git status": launch_utils.git_status(), + "Git status": git_status(paths_internal.script_path), "Script path": paths_internal.script_path, "Data path": paths_internal.data_path, "Extensions dir": paths_internal.extensions_dir, @@ -168,6 +168,11 @@ def run_git(path, *args): return str(e) +def git_status(path): + if (Path(path) / '.git').is_dir(): + return run_git(paths_internal.script_path, 'status') + + def get_info_from_repo_path(path: Path): is_repo = (path / '.git').is_dir() return { From 5a5fe7494ad6e4ace6b31bcb00f1e606c5755909 Mon Sep 17 00:00:00 2001 From: w-e-w <40751091+w-e-w@users.noreply.github.com> Date: Mon, 8 Jul 2024 16:32:20 +0900 Subject: [PATCH 437/496] .gitignore sysinfo.json --- .gitignore | 1 + 1 file changed, 1 insertion(+) diff --git a/.gitignore b/.gitignore index 96cfe22d..40f659d3 100644 --- a/.gitignore +++ b/.gitignore @@ -40,3 +40,4 @@ notification.mp3 /test/test_outputs /cache trace.json +/sysinfo-????-??-??-??-??.json From 9cc7142dd7c89cc0105e27ecdcf2125b43349bf3 Mon Sep 17 00:00:00 2001 From: Andray Date: Tue, 9 Jul 2024 14:07:12 +0400 Subject: [PATCH 438/496] update installation guide linux --- README.md | 23 +++++++++++++++++++++-- 1 file changed, 21 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index fc582e15..60d4d739 100644 --- a/README.md +++ b/README.md @@ -78,7 +78,7 @@ A web interface for Stable Diffusion, implemented using Gradio library. - Clip skip - Hypernetworks - Loras (same as Hypernetworks but more pretty) -- A separate UI where you can choose, with preview, which embeddings, hypernetworks or Loras to add to your prompt +- A separate UI where you can choose, with preview, which embeddings, hypernetworks or Loras to add to your prompt - Can select to load a different VAE from settings screen - Estimated completion time in progress bar - API @@ -122,16 +122,35 @@ Alternatively, use online services (like Google Colab): # Debian-based: sudo apt install wget git python3 python3-venv libgl1 libglib2.0-0 # Red Hat-based: -sudo dnf install wget git python3 gperftools-libs libglvnd-glx +sudo dnf install wget git python3 gperftools-libs libglvnd-glx # openSUSE-based: sudo zypper install wget git python3 libtcmalloc4 libglvnd # Arch-based: 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 + +# Then set up env variable in launch script (only for 3.11) +export python_cmd="python3.11" +``` 2. Navigate to the directory you would like the webui to be installed and execute the following command: ```bash 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`. 4. Check `webui-user.sh` for options. ### Installation on Apple Silicon From 26cccd8faab7ade582458611b083cd168993ade0 Mon Sep 17 00:00:00 2001 From: Andray Date: Tue, 9 Jul 2024 14:22:08 +0400 Subject: [PATCH 439/496] update --- README.md | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/README.md b/README.md index 60d4d739..bc62945c 100644 --- a/README.md +++ b/README.md @@ -139,8 +139,11 @@ sudo apt install python3.11 sudo pacman -S yay yay -S python311 # do not confuse with python3.11 package -# Then set up env variable in launch script (only for 3.11) +# 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: ```bash From d57ff884edd5fe3e813dbb65adb45a8966dd15b2 Mon Sep 17 00:00:00 2001 From: Andray Date: Tue, 9 Jul 2024 16:12:39 +0400 Subject: [PATCH 440/496] do not send image size on paste inpaint --- modules/infotext_utils.py | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/modules/infotext_utils.py b/modules/infotext_utils.py index f1e8f54b..32dbafa6 100644 --- a/modules/infotext_utils.py +++ b/modules/infotext_utils.py @@ -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) 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): - 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" 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 binding.paste_button.click( fn=func, _js=jsfunc, 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, ) From b1695c1b68f0e52cfe8dc4b9ed28228bd3710336 Mon Sep 17 00:00:00 2001 From: w-e-w <40751091+w-e-w@users.noreply.github.com> Date: Thu, 11 Jul 2024 18:45:13 +0900 Subject: [PATCH 441/496] fix #16169 Py 3.9 compatibility Co-Authored-By: SLAPaper Pang --- scripts/xyz_grid.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/scripts/xyz_grid.py b/scripts/xyz_grid.py index b702c74d..56649326 100644 --- a/scripts/xyz_grid.py +++ b/scripts/xyz_grid.py @@ -118,7 +118,7 @@ def apply_size(p, x: str, xs) -> None: def find_vae(name: str): - if name := name.strip().lower() in ('auto', 'automatic'): + if (name := name.strip().lower()) in ('auto', 'automatic'): return 'Automatic' elif name == 'none': return 'None' From 3d2dbefcde4091ce4e6d915b3eda16ca964097f2 Mon Sep 17 00:00:00 2001 From: Andray Date: Thu, 11 Jul 2024 23:54:25 +0400 Subject: [PATCH 442/496] fix OSError: cannot write mode P as JPEG --- modules/api/api.py | 2 +- modules/shared_state.py | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/modules/api/api.py b/modules/api/api.py index 307476bd..97ec7514 100644 --- a/modules/api/api.py +++ b/modules/api/api.py @@ -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) elif opts.samples_format.lower() in ("jpg", "jpeg", "webp"): - if image.mode == "RGBA": + if image.mode in ("RGBA", "P"): image = image.convert("RGB") parameters = image.info.get('parameters', None) exif_bytes = piexif.dump({ diff --git a/modules/shared_state.py b/modules/shared_state.py index f74eafc5..4cd53af6 100644 --- a/modules/shared_state.py +++ b/modules/shared_state.py @@ -162,7 +162,7 @@ class State: errors.record_exception() def assign_current_image(self, image): - if shared.opts.live_previews_image_format == 'jpeg' and image.mode == 'RGBA': + if shared.opts.live_previews_image_format == 'jpeg' and image.mode in ('RGBA', 'P'): image = image.convert('RGB') self.current_image = image self.id_live_preview += 1 From 589dda3cf2954beaeef65928c063e2bb5c680209 Mon Sep 17 00:00:00 2001 From: Andray Date: Fri, 12 Jul 2024 16:08:36 +0400 Subject: [PATCH 443/496] do not break progressbar on non-job actions --- modules/call_queue.py | 25 +++++++++++++++++-------- modules/ui.py | 4 ++-- modules/ui_common.py | 4 ++-- modules/ui_extensions.py | 14 +++++++------- modules/ui_settings.py | 4 ++-- 5 files changed, 30 insertions(+), 21 deletions(-) diff --git a/modules/call_queue.py b/modules/call_queue.py index d22c23b3..555c3531 100644 --- a/modules/call_queue.py +++ b/modules/call_queue.py @@ -47,6 +47,22 @@ def wrap_gradio_gpu_call(func, extra_outputs=None): 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) 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 @@ -66,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)" errors.report(f"{message}\n{arg_str}", exc_info=True) - shared.state.job = "" - shared.state.job_count = 0 - if extra_outputs_array is None: extra_outputs_array = [None, ''] @@ -77,11 +90,6 @@ def wrap_gradio_call(func, extra_outputs=None, add_stats=False): 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: return tuple(res) @@ -123,3 +131,4 @@ def wrap_gradio_call(func, extra_outputs=None, add_stats=False): return tuple(res) return f + diff --git a/modules/ui.py b/modules/ui.py index 5af34ecb..8edce620 100644 --- a/modules/ui.py +++ b/modules/ui.py @@ -10,7 +10,7 @@ import gradio as gr import gradio.utils import numpy as np from PIL import Image, PngImagePlugin # noqa: F401 -from modules.call_queue import wrap_gradio_gpu_call, wrap_queued_call, wrap_gradio_call +from modules.call_queue import wrap_gradio_gpu_call, wrap_queued_call, wrap_gradio_call, wrap_gradio_call_no_job # noqa: F401 from modules import gradio_extensons, sd_schedulers # noqa: F401 from modules import sd_hijack, sd_models, script_callbacks, ui_extensions, deepbooru, extra_networks, ui_common, ui_postprocessing, progress, ui_loadsave, shared_items, ui_settings, timer, sysinfo, ui_checkpoint_merger, scripts, sd_samplers, processing, ui_extra_networks, ui_toprow, launch_utils @@ -889,7 +889,7 @@ def create_ui(): )) image.change( - fn=wrap_gradio_call(modules.extras.run_pnginfo), + fn=wrap_gradio_call_no_job(modules.extras.run_pnginfo), inputs=[image], outputs=[html, generation_info, html2], ) diff --git a/modules/ui_common.py b/modules/ui_common.py index 48992a3c..fb396770 100644 --- a/modules/ui_common.py +++ b/modules/ui_common.py @@ -228,7 +228,7 @@ def create_output_panel(tabname, outdir, toprow=None): ) save.click( - fn=call_queue.wrap_gradio_call(save_files), + fn=call_queue.wrap_gradio_call_no_job(save_files), _js="(x, y, z, w) => [x, y, false, selected_gallery_index()]", inputs=[ res.generation_info, @@ -244,7 +244,7 @@ def create_output_panel(tabname, outdir, toprow=None): ) save_zip.click( - fn=call_queue.wrap_gradio_call(save_files), + fn=call_queue.wrap_gradio_call_no_job(save_files), _js="(x, y, z, w) => [x, y, true, selected_gallery_index()]", inputs=[ res.generation_info, diff --git a/modules/ui_extensions.py b/modules/ui_extensions.py index 6b6403f2..23aff709 100644 --- a/modules/ui_extensions.py +++ b/modules/ui_extensions.py @@ -624,37 +624,37 @@ def create_ui(): ) install_extension_button.click( - fn=modules.ui.wrap_gradio_call(install_extension_from_index, extra_outputs=[gr.update(), gr.update()]), + fn=modules.ui.wrap_gradio_call_no_job(install_extension_from_index, extra_outputs=[gr.update(), gr.update()]), inputs=[extension_to_install, selected_tags, showing_type, filtering_type, sort_column, search_extensions_text], outputs=[available_extensions_table, extensions_table, install_result], ) search_extensions_text.change( - fn=modules.ui.wrap_gradio_call(search_extensions, extra_outputs=[gr.update()]), + fn=modules.ui.wrap_gradio_call_no_job(search_extensions, extra_outputs=[gr.update()]), inputs=[search_extensions_text, selected_tags, showing_type, filtering_type, sort_column], outputs=[available_extensions_table, install_result], ) selected_tags.change( - fn=modules.ui.wrap_gradio_call(refresh_available_extensions_for_tags, extra_outputs=[gr.update()]), + fn=modules.ui.wrap_gradio_call_no_job(refresh_available_extensions_for_tags, extra_outputs=[gr.update()]), inputs=[selected_tags, showing_type, filtering_type, sort_column, search_extensions_text], outputs=[available_extensions_table, install_result] ) showing_type.change( - fn=modules.ui.wrap_gradio_call(refresh_available_extensions_for_tags, extra_outputs=[gr.update()]), + fn=modules.ui.wrap_gradio_call_no_job(refresh_available_extensions_for_tags, extra_outputs=[gr.update()]), inputs=[selected_tags, showing_type, filtering_type, sort_column, search_extensions_text], outputs=[available_extensions_table, install_result] ) filtering_type.change( - fn=modules.ui.wrap_gradio_call(refresh_available_extensions_for_tags, extra_outputs=[gr.update()]), + fn=modules.ui.wrap_gradio_call_no_job(refresh_available_extensions_for_tags, extra_outputs=[gr.update()]), inputs=[selected_tags, showing_type, filtering_type, sort_column, search_extensions_text], outputs=[available_extensions_table, install_result] ) sort_column.change( - fn=modules.ui.wrap_gradio_call(refresh_available_extensions_for_tags, extra_outputs=[gr.update()]), + fn=modules.ui.wrap_gradio_call_no_job(refresh_available_extensions_for_tags, extra_outputs=[gr.update()]), inputs=[selected_tags, showing_type, filtering_type, sort_column, search_extensions_text], outputs=[available_extensions_table, install_result] ) @@ -667,7 +667,7 @@ def create_ui(): install_result = gr.HTML(elem_id="extension_install_result") install_button.click( - fn=modules.ui.wrap_gradio_call(lambda *args: [gr.update(), *install_extension_from_url(*args)], extra_outputs=[gr.update(), gr.update()]), + fn=modules.ui.wrap_gradio_call_no_job(lambda *args: [gr.update(), *install_extension_from_url(*args)], extra_outputs=[gr.update(), gr.update()]), inputs=[install_dirname, install_url, install_branch], outputs=[install_url, extensions_table, install_result], ) diff --git a/modules/ui_settings.py b/modules/ui_settings.py index 087b91f3..e53ad50f 100644 --- a/modules/ui_settings.py +++ b/modules/ui_settings.py @@ -1,7 +1,7 @@ import gradio as gr from modules import ui_common, shared, script_callbacks, scripts, sd_models, sysinfo, timer, shared_items -from modules.call_queue import wrap_gradio_call +from modules.call_queue import wrap_gradio_call_no_job from modules.options import options_section from modules.shared import opts from modules.ui_components import FormRow @@ -295,7 +295,7 @@ class UiSettings: def add_functionality(self, demo): self.submit.click( - fn=wrap_gradio_call(lambda *args: self.run_settings(*args), extra_outputs=[gr.update()]), + fn=wrap_gradio_call_no_job(lambda *args: self.run_settings(*args), extra_outputs=[gr.update()]), inputs=self.components, outputs=[self.text_settings, self.result], ) From 7e5cdaab4b386621a186999e7348f6d0af7317a7 Mon Sep 17 00:00:00 2001 From: AUTOMATIC1111 <16777216c@gmail.com> Date: Mon, 15 Jul 2024 08:31:55 +0300 Subject: [PATCH 444/496] SD3 lora support --- extensions-builtin/Lora/network.py | 6 +- extensions-builtin/Lora/network_lora.py | 10 ++- extensions-builtin/Lora/networks.py | 96 +++++++++++++++++++------ modules/models/sd3/mmdit.py | 5 +- modules/models/sd3/sd3_impls.py | 1 + modules/models/sd3/sd3_model.py | 12 ++++ 6 files changed, 106 insertions(+), 24 deletions(-) diff --git a/extensions-builtin/Lora/network.py b/extensions-builtin/Lora/network.py index 3c39c49d..98ff367f 100644 --- a/extensions-builtin/Lora/network.py +++ b/extensions-builtin/Lora/network.py @@ -7,6 +7,7 @@ import torch.nn as nn import torch.nn.functional as F from modules import sd_models, cache, errors, hashes, shared +import modules.models.sd3.mmdit NetworkWeights = namedtuple('NetworkWeights', ['network_key', 'sd_key', 'w', 'sd_module']) @@ -114,7 +115,10 @@ class NetworkModule: self.sd_key = weights.sd_key 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 elif isinstance(self.sd_module, nn.MultiheadAttention): # For now, only self-attn use Pytorch's MHA diff --git a/extensions-builtin/Lora/network_lora.py b/extensions-builtin/Lora/network_lora.py index 4cc40295..a7a08894 100644 --- a/extensions-builtin/Lora/network_lora.py +++ b/extensions-builtin/Lora/network_lora.py @@ -1,6 +1,7 @@ import torch import lyco_helpers +import modules.models.sd3.mmdit import network 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"]): 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 @@ -29,7 +37,7 @@ class NetworkModuleLora(network.NetworkModule): if weight is None and none_ok: 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] if is_linear: diff --git a/extensions-builtin/Lora/networks.py b/extensions-builtin/Lora/networks.py index 9ed8fa43..4ad98714 100644 --- a/extensions-builtin/Lora/networks.py +++ b/extensions-builtin/Lora/networks.py @@ -20,6 +20,7 @@ from typing import Union from modules import shared, devices, sd_models, errors, scripts, sd_hijack import modules.textual_inversion.textual_inversion as textual_inversion +import modules.models.sd3.mmdit from lora_logger import logger @@ -166,12 +167,26 @@ def load_network(name, network_on_disk): keys_failed_to_match = {} 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 = {} bundle_embeddings = {} 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": emb_name, vec_name = network_part.split(".", 1) @@ -183,7 +198,11 @@ def load_network(name, network_on_disk): emb_dict[vec_name] = weight 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) if sd_module is None: @@ -347,6 +366,28 @@ def load_networks(names, te_multipliers=None, unet_multipliers=None, dyn_dims=No 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]): weights_backup = getattr(self, "network_weights_backup", None) bias_backup = getattr(self, "network_bias_backup", None) @@ -356,21 +397,15 @@ def network_restore_weights_from_backup(self: Union[torch.nn.Conv2d, torch.nn.Li if weights_backup is not None: if isinstance(self, torch.nn.MultiheadAttention): - self.in_proj_weight.copy_(weights_backup[0]) - self.out_proj.weight.copy_(weights_backup[1]) + restore_weights_backup(self, 'in_proj_weight', weights_backup[0]) + restore_weights_backup(self.out_proj, 'weight', weights_backup[0]) 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): - self.out_proj.bias.copy_(bias_backup) - else: - self.bias.copy_(bias_backup) + if isinstance(self, torch.nn.MultiheadAttention): + restore_weights_backup(self.out_proj, 'bias', bias_backup) else: - if isinstance(self, torch.nn.MultiheadAttention): - self.out_proj.bias = None - else: - self.bias = None + restore_weights_backup(self, 'bias', bias_backup) def network_apply_weights(self: Union[torch.nn.Conv2d, torch.nn.Linear, torch.nn.GroupNorm, torch.nn.LayerNorm, torch.nn.MultiheadAttention]): @@ -389,22 +424,22 @@ def network_apply_weights(self: Union[torch.nn.Conv2d, torch.nn.Linear, torch.nn weights_backup = getattr(self, "network_weights_backup", None) if weights_backup is None and wanted_names != (): - if current_names != (): - raise RuntimeError("no backup weights found and current weights are not unchanged") + if current_names != () and not allowed_layer_without_weight(self): + raise RuntimeError(f"{network_layer_name} - no backup weights found and current weights are not unchanged") 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: - weights_backup = self.weight.to(devices.cpu, copy=True) + weights_backup = store_weights_backup(self.weight) self.network_weights_backup = weights_backup bias_backup = getattr(self, "network_bias_backup", None) if bias_backup is None and wanted_names != (): 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) 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: bias_backup = None @@ -412,6 +447,7 @@ def network_apply_weights(self: Union[torch.nn.Conv2d, torch.nn.Linear, torch.nn # 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 if current_names != wanted_names: @@ -419,7 +455,7 @@ def network_apply_weights(self: Union[torch.nn.Conv2d, torch.nn.Linear, torch.nn for net in loaded_networks: 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: with torch.no_grad(): if getattr(self, 'fp16_weight', None) is None: @@ -479,6 +515,24 @@ def network_apply_weights(self: Union[torch.nn.Conv2d, torch.nn.Linear, torch.nn 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: continue diff --git a/modules/models/sd3/mmdit.py b/modules/models/sd3/mmdit.py index 4d2b8555..8ddf49a4 100644 --- a/modules/models/sd3/mmdit.py +++ b/modules/models/sd3/mmdit.py @@ -175,6 +175,9 @@ class VectorEmbedder(nn.Module): ################################################################################# +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] @@ -202,7 +205,7 @@ class SelfAttention(nn.Module): self.num_heads = num_heads self.head_dim = dim // num_heads - self.qkv = nn.Linear(dim, dim * 3, bias=qkv_bias, dtype=dtype, device=device) + 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 diff --git a/modules/models/sd3/sd3_impls.py b/modules/models/sd3/sd3_impls.py index e2f6cad5..59f11b2c 100644 --- a/modules/models/sd3/sd3_impls.py +++ b/modules/models/sd3/sd3_impls.py @@ -67,6 +67,7 @@ class BaseModel(torch.nn.Module): } 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() diff --git a/modules/models/sd3/sd3_model.py b/modules/models/sd3/sd3_model.py index dbec8168..37cf85eb 100644 --- a/modules/models/sd3/sd3_model.py +++ b/modules/models/sd3/sd3_model.py @@ -82,3 +82,15 @@ class SD3Inferencer(torch.nn.Module): 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" From f5866199c4787f905b229581a8adf765bc42da40 Mon Sep 17 00:00:00 2001 From: Haoming Date: Tue, 16 Jul 2024 11:07:22 +0800 Subject: [PATCH 445/496] add ids --- modules/ui.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/modules/ui.py b/modules/ui.py index 5af34ecb..47d01b4a 100644 --- a/modules/ui.py +++ b/modules/ui.py @@ -622,8 +622,8 @@ def create_ui(): with gr.Column(elem_id="img2img_column_size", scale=4): selected_scale_tab = gr.Number(value=0, visible=False) - with gr.Tabs(): - with gr.Tab(label="Resize to", elem_id="img2img_tab_resize_to") as tab_scale_to: + with gr.Tabs(elem_id="img2img_tabs_resize"): + with gr.Tab(label="Resize to", id="to", elem_id="img2img_tab_resize_to") as tab_scale_to: with FormRow(): with gr.Column(elem_id="img2img_column_size", scale=4): width = gr.Slider(minimum=64, maximum=2048, step=8, label="Width", value=512, elem_id="img2img_width") @@ -632,7 +632,7 @@ def create_ui(): res_switch_btn = ToolButton(value=switch_values_symbol, elem_id="img2img_res_switch_btn", tooltip="Switch width/height") detect_image_size_btn = ToolButton(value=detect_image_size_symbol, elem_id="img2img_detect_image_size_btn", tooltip="Auto detect size from img2img") - with gr.Tab(label="Resize by", elem_id="img2img_tab_resize_by") as tab_scale_by: + with gr.Tab(label="Resize by", id="by", elem_id="img2img_tab_resize_by") as tab_scale_by: scale_by = gr.Slider(minimum=0.05, maximum=4.0, step=0.05, label="Scale", value=1.0, elem_id="img2img_scale") with FormRow(): From 2b50233f3ffa522d5183bacaee3411b9382cbe2c Mon Sep 17 00:00:00 2001 From: AUTOMATIC1111 <16777216c@gmail.com> Date: Tue, 16 Jul 2024 20:50:25 +0300 Subject: [PATCH 446/496] fix bugs in lora support --- extensions-builtin/Lora/networks.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/extensions-builtin/Lora/networks.py b/extensions-builtin/Lora/networks.py index 4ad98714..67f9abe2 100644 --- a/extensions-builtin/Lora/networks.py +++ b/extensions-builtin/Lora/networks.py @@ -398,7 +398,7 @@ def network_restore_weights_from_backup(self: Union[torch.nn.Conv2d, torch.nn.Li if weights_backup is not None: if isinstance(self, torch.nn.MultiheadAttention): restore_weights_backup(self, 'in_proj_weight', weights_backup[0]) - restore_weights_backup(self.out_proj, 'weight', weights_backup[0]) + restore_weights_backup(self.out_proj, 'weight', weights_backup[1]) else: restore_weights_backup(self, 'weight', weights_backup) @@ -437,7 +437,7 @@ def network_apply_weights(self: Union[torch.nn.Conv2d, torch.nn.Linear, torch.nn bias_backup = getattr(self, "network_bias_backup", None) if bias_backup is None and wanted_names != (): if isinstance(self, torch.nn.MultiheadAttention) and self.out_proj.bias is not None: - bias_backup = store_weights_backup(self.out_proj) + bias_backup = store_weights_backup(self.out_proj.bias) elif getattr(self, 'bias', None) is not None: bias_backup = store_weights_backup(self.bias) else: From 2abc628899aaddb7e28840d7f7a40d516cb5073d Mon Sep 17 00:00:00 2001 From: w-e-w <40751091+w-e-w@users.noreply.github.com> Date: Thu, 18 Jul 2024 23:49:49 +0900 Subject: [PATCH 447/496] bat activate venv --- webui.bat | 1 + 1 file changed, 1 insertion(+) diff --git a/webui.bat b/webui.bat index a8d479b0..7b162ce2 100644 --- a/webui.bat +++ b/webui.bat @@ -48,6 +48,7 @@ echo Warning: Failed to upgrade PIP version :activate_venv set PYTHON="%VENV_DIR%\Scripts\Python.exe" +call "%VENV_DIR%\Scripts\activate.bat" echo venv %PYTHON% :skip_venv From a5f66b5003527508c2e2c49c79360ab20071fec2 Mon Sep 17 00:00:00 2001 From: v0xie <28695009+v0xie@users.noreply.github.com> Date: Thu, 18 Jul 2024 15:53:54 -0700 Subject: [PATCH 448/496] feature: beta scheduler --- modules/sd_schedulers.py | 13 +++++++++++++ 1 file changed, 13 insertions(+) diff --git a/modules/sd_schedulers.py b/modules/sd_schedulers.py index 84b0abb6..19d294c1 100644 --- a/modules/sd_schedulers.py +++ b/modules/sd_schedulers.py @@ -2,6 +2,7 @@ import dataclasses import torch import k_diffusion import numpy as np +from scipy import stats from modules import shared @@ -115,6 +116,17 @@ def ddim_scheduler(n, sigma_min, sigma_max, inner_model, device): return torch.FloatTensor(sigs).to(device) +def beta_scheduler(n, sigma_min, sigma_max, inner_model, device): + # From "Beta Sampling is All You Need" [arXiv:2407.12173] (Lee et. al, 2024) """ + alpha = 0.6 + beta = 0.6 + timesteps = 1 - np.linspace(0, 1, n) + timesteps = [stats.beta.ppf(x, alpha, beta) for x in timesteps] + sigmas = [sigma_min + ((x)*(sigma_max-sigma_min)) for x in timesteps] + [0.0] + sigmas = torch.FloatTensor(sigmas).to(device) + return sigmas + + schedulers = [ Scheduler('automatic', 'Automatic', None), Scheduler('uniform', 'Uniform', uniform, need_inner_model=True), @@ -127,6 +139,7 @@ schedulers = [ Scheduler('simple', 'Simple', simple_scheduler, need_inner_model=True), Scheduler('normal', 'Normal', normal_scheduler, need_inner_model=True), Scheduler('ddim', 'DDIM', ddim_scheduler, need_inner_model=True), + Scheduler('beta', 'Beta', beta_scheduler, need_inner_model=True), ] schedulers_map = {**{x.name: x for x in schedulers}, **{x.label: x for x in schedulers}} From 964fc13a99d47263d023f4e3116ac2c220acec88 Mon Sep 17 00:00:00 2001 From: w-e-w <40751091+w-e-w@users.noreply.github.com> Date: Sat, 20 Jul 2024 04:00:54 +0900 Subject: [PATCH 449/496] fix upscale logic --- modules/upscaler.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/modules/upscaler.py b/modules/upscaler.py index 28c60cdc..507881fe 100644 --- a/modules/upscaler.py +++ b/modules/upscaler.py @@ -56,8 +56,8 @@ class Upscaler: dest_w = int((img.width * scale) // 8 * 8) dest_h = int((img.height * scale) // 8 * 8) - for _ in range(3): - if img.width >= dest_w and img.height >= dest_h and scale != 1: + for i in range(3): + if img.width >= dest_w and img.height >= dest_h and (i > 0 or scale != 1): break if shared.state.interrupted: From 7e1bd3e3c30e1d7e95f719a925f11d9225251f7c Mon Sep 17 00:00:00 2001 From: v0xie <28695009+v0xie@users.noreply.github.com> Date: Fri, 19 Jul 2024 13:44:22 -0700 Subject: [PATCH 450/496] refactor: syntax and add 0.0 on new line --- modules/sd_schedulers.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/modules/sd_schedulers.py b/modules/sd_schedulers.py index 19d294c1..6f2dc630 100644 --- a/modules/sd_schedulers.py +++ b/modules/sd_schedulers.py @@ -122,9 +122,9 @@ def beta_scheduler(n, sigma_min, sigma_max, inner_model, device): beta = 0.6 timesteps = 1 - np.linspace(0, 1, n) timesteps = [stats.beta.ppf(x, alpha, beta) for x in timesteps] - sigmas = [sigma_min + ((x)*(sigma_max-sigma_min)) for x in timesteps] + [0.0] - sigmas = torch.FloatTensor(sigmas).to(device) - return sigmas + sigmas = [sigma_min + (x * (sigma_max-sigma_min)) for x in timesteps] + sigmas += [0.0] + return torch.FloatTensor(sigmas).to(device) schedulers = [ From 3a5a66775c4c9dd03ffbf3c1696ae0db64a71793 Mon Sep 17 00:00:00 2001 From: v0xie <28695009+v0xie@users.noreply.github.com> Date: Fri, 19 Jul 2024 14:08:08 -0700 Subject: [PATCH 451/496] add new options 'beta_dist_alpha', 'beta_dist_beta' --- modules/shared_options.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/modules/shared_options.py b/modules/shared_options.py index 096366e0..203f7a32 100644 --- a/modules/shared_options.py +++ b/modules/shared_options.py @@ -404,6 +404,8 @@ options_templates.update(options_section(('sampler-params', "Sampler parameters" 'uni_pc_lower_order_final': OptionInfo(True, "UniPC lower order final", infotext='UniPC lower order final'), 'sd_noise_schedule': OptionInfo("Default", "Noise schedule for sampling", gr.Radio, {"choices": ["Default", "Zero Terminal SNR"]}, infotext="Noise Schedule").info("for use with zero terminal SNR trained models"), 'skip_early_cond': OptionInfo(0.0, "Ignore negative prompt during early sampling", gr.Slider, {"minimum": 0.0, "maximum": 1.0, "step": 0.01}, infotext="Skip Early CFG").info("disables CFG on a proportion of steps at the beginning of generation; 0=skip none; 1=skip all; can both improve sample diversity/quality and speed up sampling"), + 'beta_dist_alpha': OptionInfo(0.6, "Beta scheduler - alpha", gr.Slider, {"minimum": 0.0, "maximum": 1.0, "step": 0.01}, infotext='Beta scheduler alpha').info('Default = 0.6; the alpha parameter of the beta distribution used in Beta sampling'), + 'beta_dist_beta': OptionInfo(0.6, "Beta scheduler - beta", gr.Slider, {"minimum": 0.0, "maximum": 1.0, "step": 0.01}, infotext='Beta scheduler beta').info('Default = 0.6; the beta parameter of the beta distribution used in Beta sampling'), })) options_templates.update(options_section(('postprocessing', "Postprocessing", "postprocessing"), { From f6f055a93df4fb1a59fa8f28e3b8f092fd7ba511 Mon Sep 17 00:00:00 2001 From: v0xie <28695009+v0xie@users.noreply.github.com> Date: Fri, 19 Jul 2024 14:08:44 -0700 Subject: [PATCH 452/496] use configured alpha/beta values in Beta scheduling --- modules/sd_schedulers.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/modules/sd_schedulers.py b/modules/sd_schedulers.py index 6f2dc630..f4d16e30 100644 --- a/modules/sd_schedulers.py +++ b/modules/sd_schedulers.py @@ -118,8 +118,8 @@ def ddim_scheduler(n, sigma_min, sigma_max, inner_model, device): def beta_scheduler(n, sigma_min, sigma_max, inner_model, device): # From "Beta Sampling is All You Need" [arXiv:2407.12173] (Lee et. al, 2024) """ - alpha = 0.6 - beta = 0.6 + alpha = shared.opts.beta_dist_alpha + beta = shared.opts.beta_dist_beta timesteps = 1 - np.linspace(0, 1, n) timesteps = [stats.beta.ppf(x, alpha, beta) for x in timesteps] sigmas = [sigma_min + (x * (sigma_max-sigma_min)) for x in timesteps] From e285af6e4817a34c86212d1b640aad7225f0c022 Mon Sep 17 00:00:00 2001 From: v0xie <28695009+v0xie@users.noreply.github.com> Date: Fri, 19 Jul 2024 14:15:10 -0700 Subject: [PATCH 453/496] add beta schedule opts to xyz options --- scripts/xyz_grid.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/scripts/xyz_grid.py b/scripts/xyz_grid.py index 56649326..6a42a04d 100644 --- a/scripts/xyz_grid.py +++ b/scripts/xyz_grid.py @@ -259,6 +259,8 @@ axis_options = [ AxisOption("Schedule min sigma", float, apply_override("sigma_min")), AxisOption("Schedule max sigma", float, apply_override("sigma_max")), AxisOption("Schedule rho", float, apply_override("rho")), + AxisOption("Beta schedule alpha", float, apply_override("beta_dist_alpha")), + AxisOption("Beta schedule beta", float, apply_override("beta_dist_beta")), AxisOption("Eta", float, apply_field("eta")), AxisOption("Clip skip", int, apply_override('CLIP_stop_at_last_layers')), AxisOption("Denoising", float, apply_field("denoising_strength")), From 94275b115c2a6c3c273baa92caf0b5f4ff3cc43f Mon Sep 17 00:00:00 2001 From: v0xie <28695009+v0xie@users.noreply.github.com> Date: Fri, 19 Jul 2024 14:15:55 -0700 Subject: [PATCH 454/496] enforce beta_dist_alpha / beta_dist_beta > 0 to avoid nan --- modules/shared_options.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/modules/shared_options.py b/modules/shared_options.py index 203f7a32..4ff7f51c 100644 --- a/modules/shared_options.py +++ b/modules/shared_options.py @@ -404,8 +404,8 @@ options_templates.update(options_section(('sampler-params', "Sampler parameters" 'uni_pc_lower_order_final': OptionInfo(True, "UniPC lower order final", infotext='UniPC lower order final'), 'sd_noise_schedule': OptionInfo("Default", "Noise schedule for sampling", gr.Radio, {"choices": ["Default", "Zero Terminal SNR"]}, infotext="Noise Schedule").info("for use with zero terminal SNR trained models"), 'skip_early_cond': OptionInfo(0.0, "Ignore negative prompt during early sampling", gr.Slider, {"minimum": 0.0, "maximum": 1.0, "step": 0.01}, infotext="Skip Early CFG").info("disables CFG on a proportion of steps at the beginning of generation; 0=skip none; 1=skip all; can both improve sample diversity/quality and speed up sampling"), - 'beta_dist_alpha': OptionInfo(0.6, "Beta scheduler - alpha", gr.Slider, {"minimum": 0.0, "maximum": 1.0, "step": 0.01}, infotext='Beta scheduler alpha').info('Default = 0.6; the alpha parameter of the beta distribution used in Beta sampling'), - 'beta_dist_beta': OptionInfo(0.6, "Beta scheduler - beta", gr.Slider, {"minimum": 0.0, "maximum": 1.0, "step": 0.01}, infotext='Beta scheduler beta').info('Default = 0.6; the beta parameter of the beta distribution used in Beta sampling'), + 'beta_dist_alpha': OptionInfo(0.6, "Beta scheduler - alpha", gr.Slider, {"minimum": 0.01, "maximum": 1.0, "step": 0.01}, infotext='Beta scheduler alpha').info('Default = 0.6; the alpha parameter of the beta distribution used in Beta sampling'), + 'beta_dist_beta': OptionInfo(0.6, "Beta scheduler - beta", gr.Slider, {"minimum": 0.01, "maximum": 1.0, "step": 0.01}, infotext='Beta scheduler beta').info('Default = 0.6; the beta parameter of the beta distribution used in Beta sampling'), })) options_templates.update(options_section(('postprocessing', "Postprocessing", "postprocessing"), { From 9de7084884f4266a19e4ed3f503687927c845c4d Mon Sep 17 00:00:00 2001 From: v0xie <28695009+v0xie@users.noreply.github.com> Date: Fri, 19 Jul 2024 14:54:24 -0700 Subject: [PATCH 455/496] always add alpha/beta to extra_generation_params when schedule is Beta --- modules/sd_samplers_kdiffusion.py | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/modules/sd_samplers_kdiffusion.py b/modules/sd_samplers_kdiffusion.py index 95a354da..4e5310bc 100644 --- a/modules/sd_samplers_kdiffusion.py +++ b/modules/sd_samplers_kdiffusion.py @@ -119,6 +119,10 @@ class KDiffusionSampler(sd_samplers_common.Sampler): if scheduler.need_inner_model: sigmas_kwargs['inner_model'] = self.model_wrap + + if scheduler.label == 'Beta': + p.extra_generation_params["Beta schedule alpha"] = opts.beta_dist_alpha + p.extra_generation_params["Beta schedule beta"] = opts.beta_dist_beta sigmas = scheduler.function(n=steps, **sigmas_kwargs, device=devices.cpu) From 874954060297d847bf30cc5d220effe80ac18968 Mon Sep 17 00:00:00 2001 From: v0xie <28695009+v0xie@users.noreply.github.com> Date: Fri, 19 Jul 2024 15:33:07 -0700 Subject: [PATCH 456/496] fix lint --- modules/sd_samplers_kdiffusion.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/modules/sd_samplers_kdiffusion.py b/modules/sd_samplers_kdiffusion.py index 4e5310bc..0c94d100 100644 --- a/modules/sd_samplers_kdiffusion.py +++ b/modules/sd_samplers_kdiffusion.py @@ -119,7 +119,7 @@ class KDiffusionSampler(sd_samplers_common.Sampler): if scheduler.need_inner_model: sigmas_kwargs['inner_model'] = self.model_wrap - + if scheduler.label == 'Beta': p.extra_generation_params["Beta schedule alpha"] = opts.beta_dist_alpha p.extra_generation_params["Beta schedule beta"] = opts.beta_dist_beta From 24a23e1225244a5ee92acad8b96077e0af858761 Mon Sep 17 00:00:00 2001 From: w-e-w <40751091+w-e-w@users.noreply.github.com> Date: Sat, 20 Jul 2024 15:59:28 +0900 Subject: [PATCH 457/496] option to disable save button log.csv --- modules/shared_options.py | 1 + modules/ui_common.py | 17 ++++++++++------- 2 files changed, 11 insertions(+), 7 deletions(-) diff --git a/modules/shared_options.py b/modules/shared_options.py index 096366e0..a482c7c6 100644 --- a/modules/shared_options.py +++ b/modules/shared_options.py @@ -64,6 +64,7 @@ options_templates.update(options_section(('saving-images', "Saving images/grids" "use_original_name_batch": OptionInfo(True, "Use original name for output filename during batch process in extras tab"), "use_upscaler_name_as_suffix": OptionInfo(False, "Use upscaler name as filename suffix in the extras tab"), "save_selected_only": OptionInfo(True, "When using 'Save' button, only save a single selected image"), + "save_write_log_csv": OptionInfo(True, "Write log.csv when saving images using 'Save' button"), "save_init_img": OptionInfo(False, "Save init images when using img2img"), "temp_dir": OptionInfo("", "Directory for temporary images; leave empty for default"), diff --git a/modules/ui_common.py b/modules/ui_common.py index 48992a3c..af5857e6 100644 --- a/modules/ui_common.py +++ b/modules/ui_common.py @@ -3,6 +3,7 @@ import dataclasses import json import html import os +from contextlib import nullcontext import gradio as gr @@ -103,14 +104,15 @@ def save_files(js_data, images, do_make_zip, index): # NOTE: ensure csv integrity when fields are added by # updating headers and padding with delimiters where needed - if os.path.exists(logfile_path): + if shared.opts.save_write_log_csv and os.path.exists(logfile_path): update_logfile(logfile_path, fields) - with open(logfile_path, "a", encoding="utf8", newline='') as file: - at_start = file.tell() == 0 - writer = csv.writer(file) - if at_start: - writer.writerow(fields) + with (open(logfile_path, "a", encoding="utf8", newline='') if shared.opts.save_write_log_csv else nullcontext()) as file: + if file: + at_start = file.tell() == 0 + writer = csv.writer(file) + if at_start: + writer.writerow(fields) for image_index, filedata in enumerate(images, start_index): image = image_from_url_text(filedata) @@ -130,7 +132,8 @@ def save_files(js_data, images, do_make_zip, index): filenames.append(os.path.basename(txt_fullfn)) fullfns.append(txt_fullfn) - writer.writerow([parsed_infotexts[0]['Prompt'], parsed_infotexts[0]['Seed'], data["width"], data["height"], data["sampler_name"], data["cfg_scale"], data["steps"], filenames[0], parsed_infotexts[0]['Negative prompt'], data["sd_model_name"], data["sd_model_hash"]]) + if file: + writer.writerow([parsed_infotexts[0]['Prompt'], parsed_infotexts[0]['Seed'], data["width"], data["height"], data["sampler_name"], data["cfg_scale"], data["steps"], filenames[0], parsed_infotexts[0]['Negative prompt'], data["sd_model_name"], data["sd_model_hash"]]) # Make Zip if do_make_zip: From 8b3d98c5a580c3c72e82d03fdab2b643bf9a8edd Mon Sep 17 00:00:00 2001 From: AUTOMATIC1111 <16777216c@gmail.com> Date: Sat, 20 Jul 2024 11:54:14 +0300 Subject: [PATCH 458/496] update CHANGELOG --- CHANGELOG.md | 17 +++++++++++++++-- 1 file changed, 15 insertions(+), 2 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index ac2b4ad6..301bfd06 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,7 +2,7 @@ ### 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)) +* 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: @@ -11,6 +11,7 @@ * 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: @@ -26,6 +27,7 @@ * 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)) @@ -74,6 +76,10 @@ * 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)) @@ -90,10 +96,17 @@ * 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)) +* 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 From 1d7e9eca098d3ae66bb1db184b88f80781891e51 Mon Sep 17 00:00:00 2001 From: AUTOMATIC1111 <16777216c@gmail.com> Date: Sat, 27 Jul 2024 15:47:49 +0300 Subject: [PATCH 459/496] Merge pull request #16275 from AUTOMATIC1111/fix-image-upscale-on-cpu fix image upscale on cpu --- modules/upscaler_utils.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/modules/upscaler_utils.py b/modules/upscaler_utils.py index 5ecbbed9..a8408f05 100644 --- a/modules/upscaler_utils.py +++ b/modules/upscaler_utils.py @@ -41,7 +41,7 @@ def upscale_pil_patch(model, img: Image.Image) -> Image.Image: """ param = torch_utils.get_param(model) - with torch.no_grad(): + with torch.inference_mode(): tensor = pil_image_to_torch_bgr(img).unsqueeze(0) # add batch dimension tensor = tensor.to(device=param.device, dtype=param.dtype) with devices.without_autocast(): From 82a973c04367123ae98bd9abdf80d9eda9b910e2 Mon Sep 17 00:00:00 2001 From: AUTOMATIC1111 <16777216c@gmail.com> Date: Sat, 27 Jul 2024 15:49:39 +0300 Subject: [PATCH 460/496] changelog --- CHANGELOG.md | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 301bfd06..5f7e5935 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,9 @@ +## 1.10.1 + +### Bug Fixes: +* fix image upscale on cpu ([#16275](https://github.com/AUTOMATIC1111/stable-diffusion-webui/pull/16275)) + + ## 1.10.0 ### Features: From 95fa1e81b8a0ab7b5f807c507c62166df4b65569 Mon Sep 17 00:00:00 2001 From: anapnoe <124302297+anapnoe@users.noreply.github.com> Date: Wed, 18 Sep 2024 20:24:49 +0300 Subject: [PATCH 461/496] update 1.10.0 --- .../html/templates/template-app-root.html | 46 +------------------ .../templates/template-img2img-params.html | 4 +- .../templates/template-txt2img-params.html | 4 +- .../javascript/anapnoe_sd_uiux_core.js | 4 +- extensions-builtin/anapnoe-sd-uiux/style.css | 3 ++ 5 files changed, 12 insertions(+), 49 deletions(-) diff --git a/extensions-builtin/anapnoe-sd-uiux/html/templates/template-app-root.html b/extensions-builtin/anapnoe-sd-uiux/html/templates/template-app-root.html index 35437406..da44b88d 100644 --- a/extensions-builtin/anapnoe-sd-uiux/html/templates/template-app-root.html +++ b/extensions-builtin/anapnoe-sd-uiux/html/templates/template-app-root.html @@ -235,17 +235,9 @@
    -
    -
    Close -
    - +
    - -
    diff --git a/extensions-builtin/anapnoe-sd-uiux/html/templates/template-img2img-params.html b/extensions-builtin/anapnoe-sd-uiux/html/templates/template-img2img-params.html index 29f7c439..9f6a011a 100644 --- a/extensions-builtin/anapnoe-sd-uiux/html/templates/template-img2img-params.html +++ b/extensions-builtin/anapnoe-sd-uiux/html/templates/template-img2img-params.html @@ -127,7 +127,7 @@
    @@ -142,7 +142,7 @@
    diff --git a/extensions-builtin/anapnoe-sd-uiux/html/templates/template-txt2img-params.html b/extensions-builtin/anapnoe-sd-uiux/html/templates/template-txt2img-params.html index 7b00e573..e589f671 100644 --- a/extensions-builtin/anapnoe-sd-uiux/html/templates/template-txt2img-params.html +++ b/extensions-builtin/anapnoe-sd-uiux/html/templates/template-txt2img-params.html @@ -105,7 +105,7 @@ @@ -120,7 +120,7 @@ diff --git a/extensions-builtin/anapnoe-sd-uiux/javascript/anapnoe_sd_uiux_core.js b/extensions-builtin/anapnoe-sd-uiux/javascript/anapnoe_sd_uiux_core.js index f337ec86..6d5c05f3 100644 --- a/extensions-builtin/anapnoe-sd-uiux/javascript/anapnoe_sd_uiux_core.js +++ b/extensions-builtin/anapnoe-sd-uiux/javascript/anapnoe_sd_uiux_core.js @@ -855,10 +855,12 @@ function initDefaultComponents(content_div) { //console.log(txt, pid) if (txt && pid) { document.querySelectorAll(`${pid} .tab-nav button, [data-parent-selector="${pid}"] .tab-nav button`).forEach(function (elm) { - /* console.log(elm.innerHTML, txt) */ + console.log(elm.innerHTML, txt); + /* if (elm.innerHTML.toLowerCase().indexOf(txt) !== -1) { elm.click(); } + */ }); } diff --git a/extensions-builtin/anapnoe-sd-uiux/style.css b/extensions-builtin/anapnoe-sd-uiux/style.css index b4e58e9d..6a64b093 100644 --- a/extensions-builtin/anapnoe-sd-uiux/style.css +++ b/extensions-builtin/anapnoe-sd-uiux/style.css @@ -3411,6 +3411,9 @@ span.settings-category { -webkit-mask-image: url(./html/svg/clipboard-fill.svg); } +.token_counter * { + display: block !important; +} @-webkit-keyframes fade-out { 0% { From 09892a05a5d363bb189bf3c71dffe5fd8fda08ff Mon Sep 17 00:00:00 2001 From: anapnoe <124302297+anapnoe@users.noreply.github.com> Date: Wed, 18 Sep 2024 22:23:57 +0300 Subject: [PATCH 462/496] update 1.10.0 --- .../templates/template-extra-networks.html | 4 +-- .../templates/template-txt2img-results.html | 8 +++++ .../javascript/anapnoe_sd_uiux_core.js | 33 +++++++++---------- 3 files changed, 24 insertions(+), 21 deletions(-) diff --git a/extensions-builtin/anapnoe-sd-uiux/html/templates/template-extra-networks.html b/extensions-builtin/anapnoe-sd-uiux/html/templates/template-extra-networks.html index abd9b615..7ecc1d53 100644 --- a/extensions-builtin/anapnoe-sd-uiux/html/templates/template-extra-networks.html +++ b/extensions-builtin/anapnoe-sd-uiux/html/templates/template-extra-networks.html @@ -26,9 +26,7 @@
    - -
    +
    diff --git a/extensions-builtin/anapnoe-sd-uiux/html/templates/template-txt2img-results.html b/extensions-builtin/anapnoe-sd-uiux/html/templates/template-txt2img-results.html index 541f9ffe..56faf6de 100644 --- a/extensions-builtin/anapnoe-sd-uiux/html/templates/template-txt2img-results.html +++ b/extensions-builtin/anapnoe-sd-uiux/html/templates/template-txt2img-results.html @@ -49,6 +49,14 @@
    +
    diff --git a/extensions-builtin/anapnoe-sd-uiux/javascript/anapnoe_sd_uiux_core.js b/extensions-builtin/anapnoe-sd-uiux/javascript/anapnoe_sd_uiux_core.js index 6d5c05f3..56b496ab 100644 --- a/extensions-builtin/anapnoe-sd-uiux/javascript/anapnoe_sd_uiux_core.js +++ b/extensions-builtin/anapnoe-sd-uiux/javascript/anapnoe_sd_uiux_core.js @@ -15,22 +15,21 @@ window.all_gallery_buttons = function(){ return visibleGalleryButtons; } -window.extraNetworksSearchButton = function(tabs_id, e) { - var button = e.target; - const attr = e.target.parentElement.parentElement.getAttribute("search-field"); - var searchTextarea = gradioApp().querySelector(attr); - //console.log(button, searchTextarea) - var text = button.classList.contains("search-all") ? "" : button.textContent.trim(); - searchTextarea.value = text; - window.updateInput(searchTextarea); +window.extraNetworksSearchButton = function(tabname, extra_networks_tabname, event) { + var attr = event.target.parentElement.parentElement.getAttribute("search-field"); + var searchTextarea = gradioApp().querySelector(attr); + var button = event.target; + var text = button.classList.contains("search-all") ? "" : button.textContent.trim(); + + searchTextarea.value = text; + window.updateInput(searchTextarea); } window.imageMaskResize = function(){ // override function empty we dont need any fix here } -window.extraNetworksEditUserMetadata = function(event, tabname, extraPage, cardName) { - //var id = tabname + '_' + extraPage + '_edit_user_metadata'; +window.extraNetworksEditUserMetadata = function(event, tabname, extraPage) { var tid = 'txt2img_' + extraPage + '_edit_user_metadata'; var editor = extraPageUserMetadataEditors[tid]; if (!editor) { @@ -40,15 +39,12 @@ window.extraNetworksEditUserMetadata = function(event, tabname, extraPage, cardN editor.button = gradioApp().querySelector("#" + tid + "_button"); extraPageUserMetadataEditors[tid] = editor; } - + var cardName = event.target.parentElement.parentElement.getAttribute("data-name"); editor.nameTextarea.value = cardName; updateInput(editor.nameTextarea); - editor.button.click(); - popup(editor.page); - - //event.stopPropagation(); + event.stopPropagation(); } window.get_uiCurrentTabContent = function(){ @@ -853,16 +849,17 @@ function initDefaultComponents(content_div) { const txt = el.querySelector('span')?.innerHTML.toLowerCase(); //console.log(txt, pid) + //window.alert(txt, pid); + /* if (txt && pid) { document.querySelectorAll(`${pid} .tab-nav button, [data-parent-selector="${pid}"] .tab-nav button`).forEach(function (elm) { - console.log(elm.innerHTML, txt); - /* + //console.log(elm.innerHTML, txt); if (elm.innerHTML.toLowerCase().indexOf(txt) !== -1) { elm.click(); } - */ }); } + */ } From 567dd4c929182c80f728f885ef0b374219e130cb Mon Sep 17 00:00:00 2001 From: anapnoe <124302297+anapnoe@users.noreply.github.com> Date: Thu, 19 Sep 2024 11:01:26 +0300 Subject: [PATCH 463/496] update 1.10.0 --- .../templates/template-extras-params.html | 105 ++++++++++-------- .../javascript/anapnoe_sd_uiux_core.js | 12 +- 2 files changed, 68 insertions(+), 49 deletions(-) diff --git a/extensions-builtin/anapnoe-sd-uiux/html/templates/template-extras-params.html b/extensions-builtin/anapnoe-sd-uiux/html/templates/template-extras-params.html index b4893360..d083b4c7 100644 --- a/extensions-builtin/anapnoe-sd-uiux/html/templates/template-extras-params.html +++ b/extensions-builtin/anapnoe-sd-uiux/html/templates/template-extras-params.html @@ -29,49 +29,54 @@
    -
    +
    -
    -
    -
    -
    - - -
    -
    -
    -
    -
    -
    -
    -
    -
    -
    -
    -
    -
    -
    -
    -