From dc25a31d1a3816a7fb0cd5ef186559b3c085db43 Mon Sep 17 00:00:00 2001 From: Ju1-js <40339350+Ju1-js@users.noreply.github.com> Date: Fri, 27 Jan 2023 22:43:10 -0800 Subject: [PATCH 001/278] Gradio Auth Read from External File Usage: `--gradio-auth-path {PATH}` It adds the credentials to the already existing `--gradio-auth` credentials. It can also handle line breaks. The file should look like: `{u1}:{p1},{u2}:{p2}` or ``` {u1}:{p1}, {u2}:{p2} ``` Will gradio handle duplicate credentials if it happens? --- modules/shared.py | 1 + webui.py | 2 +- 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/modules/shared.py b/modules/shared.py index 474fcc42..36e9762f 100644 --- a/modules/shared.py +++ b/modules/shared.py @@ -81,6 +81,7 @@ parser.add_argument("--freeze-settings", action='store_true', help="disable edit parser.add_argument("--ui-settings-file", type=str, help="filename to use for ui settings", default=os.path.join(data_path, 'config.json')) parser.add_argument("--gradio-debug", action='store_true', help="launch gradio with --debug option") parser.add_argument("--gradio-auth", type=str, help='set gradio authentication like "username:password"; or comma-delimit multiple like "u1:p1,u2:p2,u3:p3"', default=None) +parser.add_argument("--gradio-auth-path", type=str, help='set gradio authentication file path ex. "/path/to/auth/file" same auth format as --gradio-auth', default=None) parser.add_argument("--gradio-img2img-tool", type=str, help='does not do anything') parser.add_argument("--gradio-inpaint-tool", type=str, help="does not do anything") parser.add_argument("--opt-channelslast", action='store_true', help="change memory type for stable diffusion to channels last") diff --git a/webui.py b/webui.py index 41f32f5c..0e2b28b9 100644 --- a/webui.py +++ b/webui.py @@ -205,7 +205,7 @@ def webui(): ssl_keyfile=cmd_opts.tls_keyfile, ssl_certfile=cmd_opts.tls_certfile, debug=cmd_opts.gradio_debug, - auth=[tuple(cred.split(':')) for cred in cmd_opts.gradio_auth.strip('"').split(',')] if cmd_opts.gradio_auth else None, + auth=[tuple(cred.split(':')) for cred in (cmd_opts.gradio_auth.strip('"').replace('\n','').split(',') + (open(cmd_opts.gradio_auth_path, 'r').read().strip().replace('\n','').split(',') if cmd_opts.gradio_auth_path and os.path.exists(cmd_opts.gradio_auth_path) else None))] if cmd_opts.gradio_auth or (cmd_opts.gradio_auth_path and os.path.exists(cmd_opts.gradio_auth_path)) else None, inbrowser=cmd_opts.autolaunch, prevent_thread_lock=True ) From 2abd89acc66419abf2eee9b03fd093f2737670de Mon Sep 17 00:00:00 2001 From: AUTOMATIC <16777216c@gmail.com> Date: Sat, 28 Jan 2023 20:04:35 +0300 Subject: [PATCH 002/278] index on master: 91c8d0d Merge pull request #7231 from EllangoK/master --- extensions-builtin/Lora/lora.py | 21 +++++++++++++++++-- .../Lora/scripts/lora_script.py | 5 +++++ 2 files changed, 24 insertions(+), 2 deletions(-) diff --git a/extensions-builtin/Lora/lora.py b/extensions-builtin/Lora/lora.py index cb8f1d36..568a7675 100644 --- a/extensions-builtin/Lora/lora.py +++ b/extensions-builtin/Lora/lora.py @@ -12,7 +12,7 @@ re_unet_up_blocks = re.compile(r"lora_unet_up_blocks_(\d+)_attentions_(\d+)_(.+) re_text_block = re.compile(r"lora_te_text_model_encoder_layers_(\d+)_(.+)") -def convert_diffusers_name_to_compvis(key): +def convert_diffusers_name_to_compvis(key, is_sd2): def match(match_list, regex): r = re.match(regex, key) if not r: @@ -34,6 +34,14 @@ def convert_diffusers_name_to_compvis(key): return f"diffusion_model_output_blocks_{m[0] * 3 + m[1]}_1_{m[2]}" if match(m, re_text_block): + if is_sd2: + if 'mlp_fc1' in m[1]: + return f"model_transformer_resblocks_{m[0]}_{m[1].replace('mlp_fc1', 'mlp_c_fc')}" + elif 'mlp_fc2' in m[1]: + return f"model_transformer_resblocks_{m[0]}_{m[1].replace('mlp_fc2', 'mlp_c_proj')}" + elif 'self_attn': + return f"model_transformer_resblocks_{m[0]}_{m[1].replace('self_attn', 'attn')}" + return f"transformer_text_model_encoder_layers_{m[0]}_{m[1]}" return key @@ -83,9 +91,10 @@ def load_lora(name, filename): sd = sd_models.read_state_dict(filename) keys_failed_to_match = [] + is_sd2 = 'model_transformer_resblocks' in shared.sd_model.lora_layer_mapping for key_diffusers, weight in sd.items(): - fullkey = convert_diffusers_name_to_compvis(key_diffusers) + fullkey = convert_diffusers_name_to_compvis(key_diffusers, is_sd2) key, lora_key = fullkey.split(".", 1) sd_module = shared.sd_model.lora_layer_mapping.get(key, None) @@ -104,9 +113,13 @@ def load_lora(name, filename): if type(sd_module) == torch.nn.Linear: module = torch.nn.Linear(weight.shape[1], weight.shape[0], bias=False) + elif type(sd_module) == torch.nn.modules.linear.NonDynamicallyQuantizableLinear: + module = torch.nn.modules.linear.NonDynamicallyQuantizableLinear(weight.shape[1], weight.shape[0], bias=False) elif type(sd_module) == torch.nn.Conv2d: module = torch.nn.Conv2d(weight.shape[1], weight.shape[0], (1, 1), bias=False) else: + print(f'Lora layer {key_diffusers} matched a layer with unsupported type: {type(sd_module).__name__}') + continue assert False, f'Lora layer {key_diffusers} matched a layer with unsupported type: {type(sd_module).__name__}' with torch.no_grad(): @@ -182,6 +195,10 @@ def lora_Conv2d_forward(self, input): return lora_forward(self, input, torch.nn.Conv2d_forward_before_lora(self, input)) +def lora_NonDynamicallyQuantizableLinear_forward(self, input): + return lora_forward(self, input, torch.nn.NonDynamicallyQuantizableLinear_forward_before_lora(self, input)) + + def list_available_loras(): available_loras.clear() diff --git a/extensions-builtin/Lora/scripts/lora_script.py b/extensions-builtin/Lora/scripts/lora_script.py index 2e860160..a385ae94 100644 --- a/extensions-builtin/Lora/scripts/lora_script.py +++ b/extensions-builtin/Lora/scripts/lora_script.py @@ -10,6 +10,7 @@ from modules import script_callbacks, ui_extra_networks, extra_networks, shared def unload(): torch.nn.Linear.forward = torch.nn.Linear_forward_before_lora torch.nn.Conv2d.forward = torch.nn.Conv2d_forward_before_lora + torch.nn.modules.linear.NonDynamicallyQuantizableLinear.forward = torch.nn.NonDynamicallyQuantizableLinear_forward_before_lora def before_ui(): @@ -23,8 +24,12 @@ if not hasattr(torch.nn, 'Linear_forward_before_lora'): if not hasattr(torch.nn, 'Conv2d_forward_before_lora'): torch.nn.Conv2d_forward_before_lora = torch.nn.Conv2d.forward +if not hasattr(torch.nn, 'NonDynamicallyQuantizableLinear_forward_before_lora'): + torch.nn.NonDynamicallyQuantizableLinear_forward_before_lora = torch.nn.modules.linear.NonDynamicallyQuantizableLinear.forward + torch.nn.Linear.forward = lora.lora_Linear_forward torch.nn.Conv2d.forward = lora.lora_Conv2d_forward +torch.nn.modules.linear.NonDynamicallyQuantizableLinear.forward = lora.lora_NonDynamicallyQuantizableLinear_forward script_callbacks.on_model_loaded(lora.assign_lora_names_to_compvis_modules) script_callbacks.on_script_unloaded(unload) From 17b24e45e8839d889af35ee0b2fb0825306ddafe Mon Sep 17 00:00:00 2001 From: Francesco Manzali Date: Tue, 31 Jan 2023 18:58:36 +0100 Subject: [PATCH 003/278] Fix prompt matrix #rows/#cols when using hires - images.draw_prompt_matrix() should be called with the final width/height of the generated images, after upscaling. Otherwise, the number of rows/cols computed in images.draw_grid_annotations will increase by the upscaling factor. - Round the number of cols/rows in images.draw_grid_annotations, since the final images width may be a bit less than the required hr_upscale_to_x/y --- modules/images.py | 4 ++-- scripts/prompt_matrix.py | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/modules/images.py b/modules/images.py index ae3cdaf4..4be0e74d 100644 --- a/modules/images.py +++ b/modules/images.py @@ -171,8 +171,8 @@ def draw_grid_annotations(im, width, height, hor_texts, ver_texts): pad_left = 0 if sum([sum([len(line.text) for line in lines]) for lines in ver_texts]) == 0 else width * 3 // 4 - cols = im.width // width - rows = im.height // height + cols = round(im.width / width) + rows = round(im.height / height) assert cols == len(hor_texts), f'bad number of horizontal texts: {len(hor_texts)}; must be {cols}' assert rows == len(ver_texts), f'bad number of vertical texts: {len(ver_texts)}; must be {rows}' diff --git a/scripts/prompt_matrix.py b/scripts/prompt_matrix.py index dd95e588..f6575b6b 100644 --- a/scripts/prompt_matrix.py +++ b/scripts/prompt_matrix.py @@ -79,7 +79,7 @@ class Script(scripts.Script): processed = process_images(p) grid = images.image_grid(processed.images, p.batch_size, rows=1 << ((len(prompt_matrix_parts) - 1) // 2)) - grid = images.draw_prompt_matrix(grid, p.width, p.height, prompt_matrix_parts) + grid = images.draw_prompt_matrix(grid, max(p.width, p.hr_upscale_to_x), max(p.height, p.hr_upscale_to_y), prompt_matrix_parts) processed.images.insert(0, grid) processed.index_of_first_image = 1 processed.infotexts.insert(0, processed.infotexts[0]) From 5afd9e82c3829348c58803cd85b02c87308fffae Mon Sep 17 00:00:00 2001 From: Francesco Manzali Date: Wed, 1 Feb 2023 21:16:52 +0100 Subject: [PATCH 004/278] Use the real images size, not the process - Use the width/height of the first image in processed.images - No more need for rounding in prompt_matrix --- modules/images.py | 4 ++-- scripts/prompt_matrix.py | 4 ++-- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/modules/images.py b/modules/images.py index 4be0e74d..ae3cdaf4 100644 --- a/modules/images.py +++ b/modules/images.py @@ -171,8 +171,8 @@ def draw_grid_annotations(im, width, height, hor_texts, ver_texts): pad_left = 0 if sum([sum([len(line.text) for line in lines]) for lines in ver_texts]) == 0 else width * 3 // 4 - cols = round(im.width / width) - rows = round(im.height / height) + cols = im.width // width + rows = im.height // height assert cols == len(hor_texts), f'bad number of horizontal texts: {len(hor_texts)}; must be {cols}' assert rows == len(ver_texts), f'bad number of vertical texts: {len(ver_texts)}; must be {rows}' diff --git a/scripts/prompt_matrix.py b/scripts/prompt_matrix.py index f6575b6b..50c7f3cb 100644 --- a/scripts/prompt_matrix.py +++ b/scripts/prompt_matrix.py @@ -78,8 +78,8 @@ class Script(scripts.Script): p.prompt_for_display = original_prompt processed = process_images(p) - grid = images.image_grid(processed.images, p.batch_size, rows=1 << ((len(prompt_matrix_parts) - 1) // 2)) - grid = images.draw_prompt_matrix(grid, max(p.width, p.hr_upscale_to_x), max(p.height, p.hr_upscale_to_y), prompt_matrix_parts) + grid = images.image_grid(processed.images, p.batch_size, rows=1 << ((len(prompt_matrix_parts) - 1) // 2)) + grid = images.draw_prompt_matrix(grid, processed.images[0].width, processed.images[1].height, prompt_matrix_parts) processed.images.insert(0, grid) processed.index_of_first_image = 1 processed.infotexts.insert(0, processed.infotexts[0]) From dd20fc0fda5ac7cbb93af0b93222ee5a17f7705e Mon Sep 17 00:00:00 2001 From: w-e-w <40751091+w-e-w@users.noreply.github.com> Date: Sat, 4 Feb 2023 23:23:20 +0900 Subject: [PATCH 005/278] fix --help show correct help message --- launch.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/launch.py b/launch.py index 9fd766d1..a68bb3a9 100644 --- a/launch.py +++ b/launch.py @@ -242,7 +242,7 @@ def prepare_environment(): sys.argv += shlex.split(commandline_args) - parser = argparse.ArgumentParser() + parser = argparse.ArgumentParser(add_help=False) parser.add_argument("--ui-settings-file", type=str, help="filename to use for ui settings", default='config.json') args, _ = parser.parse_known_args(sys.argv) From 6d11cda4188633ba19f4d8948139e510d5678059 Mon Sep 17 00:00:00 2001 From: w-e-w <40751091+w-e-w@users.noreply.github.com> Date: Sun, 5 Feb 2023 23:12:42 +0900 Subject: [PATCH 006/278] configurable image downscale allowing the user to configure the image downscale parameters in setting --- modules/images.py | 4 ++-- modules/shared.py | 4 +++- 2 files changed, 5 insertions(+), 3 deletions(-) diff --git a/modules/images.py b/modules/images.py index c2ca8849..cf4aea22 100644 --- a/modules/images.py +++ b/modules/images.py @@ -575,9 +575,9 @@ def save_image(image, path, basename, seed=None, prompt=None, extension='png', i image.already_saved_as = fullfn - target_side_length = 4000 + target_side_length = int(opts.target_side_length) oversize = image.width > target_side_length or image.height > target_side_length - if opts.export_for_4chan and (oversize or os.stat(fullfn).st_size > 4 * 1024 * 1024): + if opts.export_for_4chan and (oversize or os.stat(fullfn).st_size > opts.img_downscale_threshold * 1024 * 1024): ratio = image.width / image.height if oversize and ratio > 1: diff --git a/modules/shared.py b/modules/shared.py index 79fbf724..a93a0299 100644 --- a/modules/shared.py +++ b/modules/shared.py @@ -325,7 +325,9 @@ options_templates.update(options_section(('saving-images', "Saving images/grids" "save_images_before_highres_fix": OptionInfo(False, "Save a copy of image before applying highres fix."), "save_images_before_color_correction": OptionInfo(False, "Save a copy of image before applying color correction to img2img results"), "jpeg_quality": OptionInfo(80, "Quality for saved jpeg images", gr.Slider, {"minimum": 1, "maximum": 100, "step": 1}), - "export_for_4chan": OptionInfo(True, "If PNG image is larger than 4MB or any dimension is larger than 4000, downscale and save copy as JPG"), + "export_for_4chan": OptionInfo(True, "If PNG image is larger than Downscale threshold or any dimension is larger than Target length, downscale the image to dimensions and save a copy as JPG"), + "img_downscale_threshold": OptionInfo(4, "Downscale threshold (MB)"), + "target_side_length": OptionInfo(4000, "Target length"), "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"), From fe33be6cac140ff83b481029106968f39209cd90 Mon Sep 17 00:00:00 2001 From: w-e-w <40751091+w-e-w@users.noreply.github.com> Date: Sun, 5 Feb 2023 23:33:05 +0900 Subject: [PATCH 007/278] use Default if ValueError --- modules/images.py | 12 ++++++++++-- 1 file changed, 10 insertions(+), 2 deletions(-) diff --git a/modules/images.py b/modules/images.py index cf4aea22..34d08b73 100644 --- a/modules/images.py +++ b/modules/images.py @@ -575,9 +575,17 @@ def save_image(image, path, basename, seed=None, prompt=None, extension='png', i image.already_saved_as = fullfn - target_side_length = int(opts.target_side_length) + try: + target_side_length = int(opts.target_side_length) + except ValueError: + target_side_length = 4000 + try: + img_downscale_threshold = float(opts.img_downscale_threshold) + except ValueError: + img_downscale_threshold = 4 + oversize = image.width > target_side_length or image.height > target_side_length - if opts.export_for_4chan and (oversize or os.stat(fullfn).st_size > opts.img_downscale_threshold * 1024 * 1024): + if opts.export_for_4chan and (oversize or os.stat(fullfn).st_size > img_downscale_threshold * 1024 * 1024): ratio = image.width / image.height if oversize and ratio > 1: From c8109f0dea0af10336597fecc200ff1e53b701d0 Mon Sep 17 00:00:00 2001 From: Kyle Date: Sun, 5 Feb 2023 15:18:18 -0500 Subject: [PATCH 008/278] Add Image CFG Scale to XYZ Grid --- scripts/xyz_grid.py | 1 + 1 file changed, 1 insertion(+) diff --git a/scripts/xyz_grid.py b/scripts/xyz_grid.py index 5982cfba..db4396b4 100644 --- a/scripts/xyz_grid.py +++ b/scripts/xyz_grid.py @@ -186,6 +186,7 @@ axis_options = [ AxisOption("Steps", int, apply_field("steps")), AxisOptionTxt2Img("Hires steps", int, apply_field("hr_second_pass_steps")), AxisOption("CFG Scale", float, apply_field("cfg_scale")), + AxisOption("Image CFG Scale", float, apply_field("image_cfg_scale")), AxisOption("Prompt S/R", str, apply_prompt, format_value=format_value), AxisOption("Prompt order", str_permutations, apply_order, format_value=format_value_join_list), AxisOptionTxt2Img("Sampler", str, apply_sampler, format_value=format_value, confirm=confirm_samplers, choices=lambda: [x.name for x in sd_samplers.samplers]), From 67303fd5fc7b1970d509e4afa576a905ed664955 Mon Sep 17 00:00:00 2001 From: Kyle Date: Sun, 5 Feb 2023 15:34:26 -0500 Subject: [PATCH 009/278] Img2Img Only Will still show up as an option with regular img2img models, but outputs no changes. --- 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 db4396b4..1f29bf69 100644 --- a/scripts/xyz_grid.py +++ b/scripts/xyz_grid.py @@ -186,7 +186,7 @@ axis_options = [ AxisOption("Steps", int, apply_field("steps")), AxisOptionTxt2Img("Hires steps", int, apply_field("hr_second_pass_steps")), AxisOption("CFG Scale", float, apply_field("cfg_scale")), - AxisOption("Image CFG Scale", float, apply_field("image_cfg_scale")), + AxisOptionImg2Img("Image CFG Scale", float, apply_field("image_cfg_scale")), AxisOption("Prompt S/R", str, apply_prompt, format_value=format_value), AxisOption("Prompt order", str_permutations, apply_order, format_value=format_value_join_list), AxisOptionTxt2Img("Sampler", str, apply_sampler, format_value=format_value, confirm=confirm_samplers, choices=lambda: [x.name for x in sd_samplers.samplers]), From 7dd23973f7e7e3b116ce1a2ba427d409914bd921 Mon Sep 17 00:00:00 2001 From: Vladimir Repin <32306715+mezotaken@users.noreply.github.com> Date: Mon, 6 Feb 2023 00:28:31 +0300 Subject: [PATCH 010/278] Optionally append interrogated prompt in loopback script --- scripts/loopback.py | 14 ++++++++++++-- 1 file changed, 12 insertions(+), 2 deletions(-) diff --git a/scripts/loopback.py b/scripts/loopback.py index 1dab9476..ec1f85e5 100644 --- a/scripts/loopback.py +++ b/scripts/loopback.py @@ -8,6 +8,7 @@ from modules import processing, shared, sd_samplers, images from modules.processing import Processed from modules.sd_samplers import samplers from modules.shared import opts, cmd_opts, state +from modules import deepbooru class Script(scripts.Script): @@ -20,10 +21,11 @@ class Script(scripts.Script): def ui(self, is_img2img): loops = gr.Slider(minimum=1, maximum=32, step=1, label='Loops', value=4, elem_id=self.elem_id("loops")) denoising_strength_change_factor = gr.Slider(minimum=0.9, maximum=1.1, step=0.01, label='Denoising strength change factor', value=1, elem_id=self.elem_id("denoising_strength_change_factor")) + append_interrogation = gr.Dropdown(label="Append interrogated prompt at each iteration", choices=["None", "CLIP", "DeepBooru"], value="None") - return [loops, denoising_strength_change_factor] + return [loops, denoising_strength_change_factor, append_interrogation] - def run(self, p, loops, denoising_strength_change_factor): + def run(self, p, loops, denoising_strength_change_factor, append_interrogation): processing.fix_seed(p) batch_count = p.n_iter p.extra_generation_params = { @@ -40,6 +42,7 @@ class Script(scripts.Script): grids = [] all_images = [] original_init_image = p.init_images + original_prompt = p.prompt state.job_count = loops * batch_count initial_color_corrections = [processing.setup_color_correction(p.init_images[0])] @@ -58,6 +61,13 @@ class Script(scripts.Script): if opts.img2img_color_correction: p.color_corrections = initial_color_corrections + if append_interrogation != "None": + p.prompt = original_prompt + ", " if original_prompt != "" else "" + if append_interrogation == "CLIP": + p.prompt += shared.interrogator.interrogate(p.init_images[0]) + elif append_interrogation == "DeepBooru": + p.prompt += deepbooru.model.tag(p.init_images[0]) + state.job = f"Iteration {i + 1}/{loops}, batch {n + 1}/{batch_count}" processed = processing.process_images(p) From 584f782391b42c182385a56ae46d1674649713e6 Mon Sep 17 00:00:00 2001 From: CurtisDS <20732674+CurtisDS@users.noreply.github.com> Date: Sun, 5 Feb 2023 16:42:45 -0500 Subject: [PATCH 011/278] Update ui_extra_networks.py update the string used to build the ID handle to replace spaces with underscore --- modules/ui_extra_networks.py | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/modules/ui_extra_networks.py b/modules/ui_extra_networks.py index 90abec0a..fb7f2d6c 100644 --- a/modules/ui_extra_networks.py +++ b/modules/ui_extra_networks.py @@ -94,11 +94,13 @@ class ExtraNetworksPage: dirs = "".join([f"
  • {x}
  • " for x in self.allowed_directories_for_previews()]) items_html = shared.html("extra-networks-no-cards.html").format(dirs=dirs) + self_name_id = self.name.replace(" ", "_") + res = f""" -
    +
    {subdirs_html}
    -
    +
    {items_html}
    """ From df8ee5f6b059b81bce35db05c27209df9009646e Mon Sep 17 00:00:00 2001 From: Vladimir Repin <32306715+mezotaken@users.noreply.github.com> Date: Mon, 6 Feb 2023 00:52:57 +0300 Subject: [PATCH 012/278] Update batch count/size hints --- javascript/hints.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/javascript/hints.js b/javascript/hints.js index 9aa82f24..f1199009 100644 --- a/javascript/hints.js +++ b/javascript/hints.js @@ -8,8 +8,8 @@ titles = { "DDIM": "Denoising Diffusion Implicit Models - best at inpainting", "DPM adaptive": "Ignores step count - uses a number of steps determined by the CFG and resolution", - "Batch count": "How many batches of images to create", - "Batch size": "How many image to create in a single batch", + "Batch count": "How many batches of images to create (has no impact on generation performance or VRAM usage)", + "Batch size": "How many image to create in a single batch (increases generation performance at cost of higher VRAM usage)", "CFG Scale": "Classifier Free Guidance Scale - how strongly the image should conform to prompt - lower values produce more creative results", "Seed": "A value that determines the output of random number generator - if you create an image with same parameters and seed as another image, you'll get the same result", "\u{1f3b2}\ufe0f": "Set seed to -1, which will cause a new random number to be used every time", From 9a22c63f47e895cfa17704ae18dd62fd3a831e9f Mon Sep 17 00:00:00 2001 From: EllangoK Date: Mon, 6 Feb 2023 00:52:31 -0500 Subject: [PATCH 013/278] call modules.sd_vae.refresh_vae_list() --- modules/shared_items.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/modules/shared_items.py b/modules/shared_items.py index 8b5ec96d..b72b2bae 100644 --- a/modules/shared_items.py +++ b/modules/shared_items.py @@ -20,4 +20,4 @@ def sd_vae_items(): def refresh_vae_list(): import modules.sd_vae - return modules.sd_vae.refresh_vae_list + return modules.sd_vae.refresh_vae_list() From 5d483bf307c766aee97caec857d414946fad47db Mon Sep 17 00:00:00 2001 From: Gerschel Date: Mon, 6 Feb 2023 08:18:04 -0800 Subject: [PATCH 014/278] aspect ratio for dim's; sliders adjust by ratio MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Default choices added to settings in user interface section Choices are editable by user User selects from dropdown. When you move one slider, the other adjusts according to the ratio chosen. Vice versa for the other slider. Number fields for changes work as well. For disabling ratio, an unlock pad "🔓" is available as a default. This string can be changed to anything to serve as a disable, as long as there is no colon ":". Ratios are entered in this format, floats or ints with a colon "1:1". The string is split at the colon, parses left and right as floats to perform the math. --- javascript/ComponentControllers.js | 257 +++++++++++++++++++++++++++++ javascript/aspectRatioSliders.js | 41 +++++ modules/shared.py | 14 ++ modules/ui.py | 6 + 4 files changed, 318 insertions(+) create mode 100644 javascript/ComponentControllers.js create mode 100644 javascript/aspectRatioSliders.js diff --git a/javascript/ComponentControllers.js b/javascript/ComponentControllers.js new file mode 100644 index 00000000..194589c7 --- /dev/null +++ b/javascript/ComponentControllers.js @@ -0,0 +1,257 @@ +/* This is a basic library that allows controlling elements that take some form of user input. + +This was previously written in typescript, where all controllers implemented an interface. Not +all methods were needed in all the controllers, but it was done to keep a common interface, so +your main app can serve as a controller of controllers. + +These controllers were built to work on the shapes of html elements that gradio components use. + +There may be some notes in it that only applied to my use case, but I left them to help others +along. + +You will need the parent element for these to work. +The parent element can be defined as the element (div) that gets the element id when assigning +an element id to a gradio component. + +Example: + gr.TextBox(value="...", elem_id="THISID") + +Basic usage, grab an element that is the parent container for the component. + +Send it in to the class, like a function, don't forget the "new" keyword so it calls the constructor +and sends back a new object. + +Example: + +let txt2imgPrompt = new TextComponentController(gradioApp().querySelector("#txt2img_prompt")) + +Then use the getVal() method to get the value, or use the setVal(myValue) method to set the value. + +Input types that are groups, like Checkbox groups (not individual checkboxes), take in an array of values. + +Checkbox group has to reset all values to False (unchecked), then set the values in your array to true (checked). +If you don't hold a reference to the values (the labels in string format), you can acquire them using the getVal() method. +*/ +class DropdownComponentController { + constructor(element) { + this.element = element; + this.childSelector = this.element.querySelector('select'); + this.children = new Map(); + Array.from(this.childSelector.querySelectorAll('option')).forEach(opt => this.children.set(opt.value, opt)); + } + getVal() { + return this.childSelector.value; + } + updateVal(optionElement) { + optionElement.selected = true; + } + setVal(name) { + this.updateVal(this.children.get(name)); + this.eventHandler(); + } + eventHandler() { + this.childSelector.dispatchEvent(new Event("change")); + } +} +class CheckboxComponentController { + constructor(element) { + this.element = element; + this.child = this.element.querySelector('input'); + } + getVal() { + return this.child.checked; + } + updateVal(checked) { + this.child.checked = checked; + } + setVal(checked) { + this.updateVal(checked); + this.eventHandler(); + } + eventHandler() { + this.child.dispatchEvent(new Event("change")); + } +} +class CheckboxGroupComponentController { + constructor(element) { + this.element = element; + //this.checkBoxes = new Object; + this.children = new Map(); + Array.from(this.element.querySelectorAll('input')).forEach(input => this.children.set(input.nextElementSibling.innerText, input)); + /* element id gets use fieldset, grab all inputs (the bool val) get the userfriendly label, use as key, put bool value in mapping */ + //Array.from(this.component.querySelectorAll("input")).forEach( _input => this.checkBoxes[_input.nextElementSibling.innerText] = _input) + /*Checkboxgroup structure +
    +
    css makes translucent + + serves as label for component + +
    container for checkboxes + + ... +
    +
    + */ + } + updateVal(label) { + /********* + calls updates using a throttle or else the backend does not get updated properly + * ********/ + setTimeout(() => this.conditionalToggle(true, this.children.get(label)), 2); + } + setVal(labels) { + /* Handles reset and updates all in array to true */ + this.reupdateVals(); + labels.forEach(l => this.updateVal(l)); + } + getVal() { + //return the list of values that are true + return [...this.children].filter(([k, v]) => v.checked).map(arr => arr[0]); + } + reupdateVals() { + /************** + * for reupdating all vals, first set to false + **************/ + this.children.forEach(inputChild => this.conditionalToggle(false, inputChild)); + } + conditionalToggle(desiredVal, inputChild) { + //This method behaves like 'set this value to this' + //Using element.checked = true/false, does not register the change, even if you called change afterwards, + // it only sets what it looks like in our case, because there is no form submit, a person then has to click on it twice. + //Options are to use .click() or dispatch an event + if (desiredVal != inputChild.checked) { + inputChild.dispatchEvent(new Event("change")); //using change event instead of click, in case browser ad-blockers blocks the click method + } + } + eventHandler(checkbox) { + checkbox.dispatchEvent(new Event("change")); + } +} +class RadioComponentController { + constructor(element) { + this.element = element; + this.children = new Map(); + Array.from(this.element.querySelectorAll("input")).forEach(input => this.children.set(input.value, input)); + } + getVal() { + //radio groups have a single element that's checked is true + // as array arr k,v pair element.checked ) -> array of len(1) with [k,v] so either [0] [1].value + return [...this.children].filter(([l, e]) => e.checked)[0][0]; + //return Array.from(this.children).filter( ([label, input]) => input.checked)[0][1].value + } + updateVal(child) { + this.eventHandler(child); + } + setVal(name) { + //radio will trigger all false except the one that get the event change + //to keep the api similar, other methods are still called + this.updateVal(this.children.get(name)); + } + eventHandler(child) { + child.dispatchEvent(new Event("change")); + } +} +class NumberComponentController { + constructor(element) { + this.element = element; + this.childNumField = element.querySelector('input[type=number]'); + } + getVal() { + return this.childNumField.value; + } + updateVal(text) { + this.childNumField.value = text; + } + eventHandler() { + this.element.dispatchEvent(new Event("input")); + } + setVal(text) { + this.updateVal(text); + this.eventHandler(); + } +} +class SliderComponentController { + constructor(element) { + this.element = element; + this.childNumField = this.element.querySelector('input[type=number]'); + this.childRangeField = this.element.querySelector('input[type=range]'); + } + getVal() { + return this.childNumField.value; + } + updateVal(text) { + //both are not needed, either works, both are left in so one is a fallback in case of gradio changes + this.childNumField.value = text; + this.childRangeField.value = text; + } + eventHandler() { + this.element.dispatchEvent(new Event("input")); + } + setVal(text) { + this.updateVal(text); + this.eventHandler(); + } +} +class TextComponentController { + constructor(element) { + this.element = element; + this.child = element.querySelector('textarea'); + } + getVal() { + return this.child.value; + } + eventHandler() { + this.element.dispatchEvent(new Event("input")); + this.child.dispatchEvent(new Event("change")); + //Workaround to solve no target with v(o) on eventhandler, define my own target + let ne = new Event("input"); + Object.defineProperty(ne, "target", { value: this.child }); + this.child.dispatchEvent(ne); + } + updateVal(text) { + this.child.value = text; + } + appendValue(text) { + //might add delimiter option + this.child.value += ` ${text}`; + } + setVal(text, append = false) { + if (append) { + this.appendValue(text); + } + else { + this.updateVal(text); + } + this.eventHandler(); + } +} +class JsonComponentController extends TextComponentController { + constructor(element) { + super(element); + } + getVal() { + return JSON.parse(this.child.value); + } +} +class ColorComponentController { + constructor(element) { + this.element = element; + this.child = this.element.querySelector('input[type=color]'); + } + updateVal(text) { + this.child.value = text; + } + getVal() { + return this.child.value; + } + setVal(text) { + this.updateVal(text); + this.eventHandler(); + } + eventHandler() { + this.child.dispatchEvent(new Event("input")); + } +} diff --git a/javascript/aspectRatioSliders.js b/javascript/aspectRatioSliders.js new file mode 100644 index 00000000..f577750a --- /dev/null +++ b/javascript/aspectRatioSliders.js @@ -0,0 +1,41 @@ +class AspectRatioSliderController { + constructor(widthSlider, heightSlider, ratioSource) { + this.widthSlider = new SliderComponentController(widthSlider); + this.heightSlider = new SliderComponentController(heightSlider); + this.ratioSource = new DropdownComponentController(ratioSource); + this.widthSlider.childRangeField.addEventListener("change", () => this.resize("width")); + this.widthSlider.childNumField.addEventListener("change", () => this.resize("width")); + this.heightSlider.childRangeField.addEventListener("change", () => this.resize("height")); + this.heightSlider.childNumField.addEventListener("change", () => this.resize("height")); + } + resize(dimension) { + let val = this.ratioSource.getVal(); + if (!val.includes(":")) { + return; + } + let [width, height] = val.split(":").map(Number); + let ratio = width / height; + if (dimension == 'width') { + this.heightSlider.setVal(Math.round(parseFloat(this.widthSlider.getVal()) / ratio).toString()); + } + else if (dimension == "height") { + this.widthSlider.setVal(Math.round(parseFloat(this.heightSlider.getVal()) * ratio).toString()); + } + } + static observeStartup(widthSliderId, heightSliderId, ratioSourceId) { + let observer = new MutationObserver(() => { + let widthSlider = document.querySelector("gradio-app").shadowRoot.getElementById(widthSliderId); + let heightSlider = document.querySelector("gradio-app").shadowRoot.getElementById(heightSliderId); + let ratioSource = document.querySelector("gradio-app").shadowRoot.getElementById(ratioSourceId); + if (widthSlider && heightSlider && ratioSource) { + observer.disconnect(); + new AspectRatioSliderController(widthSlider, heightSlider, ratioSource); + } + }); + observer.observe(gradioApp(), { childList: true, subtree: true }); + } +} +document.addEventListener("DOMContentLoaded", () => { + AspectRatioSliderController.observeStartup("txt2img_width", "txt2img_height", "txt2img_ratio"); + AspectRatioSliderController.observeStartup("img2img_width", "img2img_height", "img2img_ratio"); +}); diff --git a/modules/shared.py b/modules/shared.py index 79fbf724..ead7be36 100644 --- a/modules/shared.py +++ b/modules/shared.py @@ -139,6 +139,19 @@ ui_reorder_categories = [ "scripts", ] +aspect_ratio_defaults = [ + "🔓" + "1:1", + "1:2", + "2:1", + "2:3", + "3:2", + "4:3", + "5:4", + "9:16", + "16:9", +] + cmd_opts.disable_extension_access = (cmd_opts.share or cmd_opts.listen or cmd_opts.server_name) and not cmd_opts.enable_insecure_extension_access devices.device, devices.device_interrogate, devices.device_gfpgan, devices.device_esrgan, devices.device_codeformer = \ @@ -456,6 +469,7 @@ options_templates.update(options_section(('ui', "User interface"), { "keyedit_precision_extra": OptionInfo(0.05, "Ctrl+up/down precision when editing ", gr.Slider, {"minimum": 0.01, "maximum": 0.2, "step": 0.001}), "quicksettings": OptionInfo("sd_model_checkpoint", "Quicksettings list"), "ui_reorder": OptionInfo(", ".join(ui_reorder_categories), "txt2img/img2img UI item order"), + "aspect_ratios": OptionInfo(", ".join(aspect_ratio_defaults), "txt2img/img2img aspect ratios"), "ui_extra_networks_tab_reorder": OptionInfo("", "Extra networks tab order"), "localization": OptionInfo("None", "Localization (requires restart)", gr.Dropdown, lambda: {"choices": ["None"] + list(localization.localizations.keys())}, refresh=lambda: localization.list_localizations(cmd_opts.localizations_dir)), })) diff --git a/modules/ui.py b/modules/ui.py index f5df1ffe..6853485c 100644 --- a/modules/ui.py +++ b/modules/ui.py @@ -424,6 +424,10 @@ def ordered_ui_categories(): yield category +def aspect_ratio_list(): + return [ratio.strip() for ratio in shared.opts.aspect_ratios.split(",")] + + def get_value_for_setting(key): value = getattr(opts, key) @@ -480,6 +484,7 @@ def create_ui(): height = gr.Slider(minimum=64, maximum=2048, step=8, label="Height", value=512, elem_id="txt2img_height") res_switch_btn = ToolButton(value=switch_values_symbol, elem_id="txt2img_res_switch_btn") + aspect_ratio_dropdown = gr.Dropdown(value="🔓", choices=aspect_ratio_list(), interactive=True, type="value", elem_id="txt2img_ratio", show_label=False, label="Aspect Ratio") if opts.dimensions_and_batch_together: with gr.Column(elem_id="txt2img_column_batch"): batch_count = gr.Slider(minimum=1, step=1, label='Batch count', value=1, elem_id="txt2img_batch_count") @@ -758,6 +763,7 @@ def create_ui(): height = gr.Slider(minimum=64, maximum=2048, step=8, label="Height", value=512, elem_id="img2img_height") res_switch_btn = ToolButton(value=switch_values_symbol, elem_id="img2img_res_switch_btn") + aspect_ratio_dropdown = gr.Dropdown(value="🔓", choices=aspect_ratio_list(), interactive=True, type="value", elem_id="img2img_ratio", show_label=False, label="Aspect Ratio") if opts.dimensions_and_batch_together: with gr.Column(elem_id="img2img_column_batch"): batch_count = gr.Slider(minimum=1, step=1, label='Batch count', value=1, elem_id="img2img_batch_count") From 4738486d8f528a98a525970ac06a109431fd7344 Mon Sep 17 00:00:00 2001 From: brkirch Date: Mon, 6 Feb 2023 18:10:55 -0500 Subject: [PATCH 015/278] Support for hypernetworks with --upcast-sampling --- modules/hypernetworks/hypernetwork.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/modules/hypernetworks/hypernetwork.py b/modules/hypernetworks/hypernetwork.py index 825a93b2..a15bae18 100644 --- a/modules/hypernetworks/hypernetwork.py +++ b/modules/hypernetworks/hypernetwork.py @@ -380,8 +380,8 @@ def apply_single_hypernetwork(hypernetwork, context_k, context_v, layer=None): layer.hyper_k = hypernetwork_layers[0] layer.hyper_v = hypernetwork_layers[1] - context_k = hypernetwork_layers[0](context_k) - context_v = hypernetwork_layers[1](context_v) + context_k = devices.cond_cast_unet(hypernetwork_layers[0](devices.cond_cast_float(context_k))) + context_v = devices.cond_cast_unet(hypernetwork_layers[1](devices.cond_cast_float(context_v))) return context_k, context_v From 2016733814433ca2b69d10764bfa0ab4c7088782 Mon Sep 17 00:00:00 2001 From: brkirch Date: Tue, 7 Feb 2023 00:05:54 -0500 Subject: [PATCH 016/278] Apply hijacks in ddpm_edit for upcast sampling To avoid import errors, ddpm_edit hijacks are done after an instruct pix2pix model is loaded. --- modules/sd_hijack.py | 3 +++ modules/sd_hijack_unet.py | 11 +++++++++++ 2 files changed, 14 insertions(+) diff --git a/modules/sd_hijack.py b/modules/sd_hijack.py index 8fdc5990..fca418cd 100644 --- a/modules/sd_hijack.py +++ b/modules/sd_hijack.py @@ -104,6 +104,9 @@ class StableDiffusionModelHijack: m.cond_stage_model.model.token_embedding = EmbeddingsWithFixes(m.cond_stage_model.model.token_embedding, self) m.cond_stage_model = sd_hijack_open_clip.FrozenOpenCLIPEmbedderWithCustomWords(m.cond_stage_model, self) + if m.cond_stage_key == "edit": + sd_hijack_unet.hijack_ddpm_edit() + self.optimization_method = apply_optimizations() self.clip = m.cond_stage_model diff --git a/modules/sd_hijack_unet.py b/modules/sd_hijack_unet.py index 45cf2b18..843ab66c 100644 --- a/modules/sd_hijack_unet.py +++ b/modules/sd_hijack_unet.py @@ -44,6 +44,7 @@ def apply_model(orig_func, self, x_noisy, t, cond, **kwargs): with devices.autocast(): return orig_func(self, x_noisy.to(devices.dtype_unet), t.to(devices.dtype_unet), cond, **kwargs).float() + class GELUHijack(torch.nn.GELU, torch.nn.Module): def __init__(self, *args, **kwargs): torch.nn.GELU.__init__(self, *args, **kwargs) @@ -53,6 +54,16 @@ class GELUHijack(torch.nn.GELU, torch.nn.Module): else: return torch.nn.GELU.forward(self, x) + +ddpm_edit_hijack = None +def hijack_ddpm_edit(): + global ddpm_edit_hijack + 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) + + 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) From 4c562a9832343f21ce86410aea6b26904cb13b2c Mon Sep 17 00:00:00 2001 From: Vladimir Mandic Date: Wed, 8 Feb 2023 07:03:36 -0500 Subject: [PATCH 017/278] convert rgba to rgb some image format (e.g. jpg) do not support rgba --- modules/img2img.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/modules/img2img.py b/modules/img2img.py index bcc158dc..c973b770 100644 --- a/modules/img2img.py +++ b/modules/img2img.py @@ -73,6 +73,8 @@ def process_batch(p, input_dir, output_dir, inpaint_mask_dir, args): if not save_normally: os.makedirs(output_dir, exist_ok=True) + if processed_image.mode == 'RGBA': + processed_image = processed_image.convert("RGB") processed_image.save(os.path.join(output_dir, filename)) From 3ee9ca5cb06bcb36a633aea0dbbc6fd2478921df Mon Sep 17 00:00:00 2001 From: Vladimir Mandic Date: Wed, 8 Feb 2023 07:08:09 -0500 Subject: [PATCH 018/278] add missing import used later in line 418 --- modules/esrgan_model_arch.py | 1 + 1 file changed, 1 insertion(+) diff --git a/modules/esrgan_model_arch.py b/modules/esrgan_model_arch.py index bc9ceb2a..1b52b0f5 100644 --- a/modules/esrgan_model_arch.py +++ b/modules/esrgan_model_arch.py @@ -1,5 +1,6 @@ # this file is adapted from https://github.com/victorca25/iNNfer +from collections import OrderedDict import math import functools import torch From 3ca41dbded119509bb66d7952d403091a5543257 Mon Sep 17 00:00:00 2001 From: Vladimir Mandic Date: Wed, 8 Feb 2023 07:10:13 -0500 Subject: [PATCH 019/278] add missing import used later in line 70 --- modules/sd_hijack_inpainting.py | 1 + 1 file changed, 1 insertion(+) diff --git a/modules/sd_hijack_inpainting.py b/modules/sd_hijack_inpainting.py index 478cd499..55a2ce4d 100644 --- a/modules/sd_hijack_inpainting.py +++ b/modules/sd_hijack_inpainting.py @@ -11,6 +11,7 @@ import ldm.models.diffusion.plms from ldm.models.diffusion.ddpm import LatentDiffusion from ldm.models.diffusion.plms import PLMSSampler from ldm.models.diffusion.ddim import DDIMSampler, noise_like +from ldm.models.diffusion.sampling_util import norm_thresholding @torch.no_grad() From 374fe636b80169c78b4b5f92013681d75fa2fad6 Mon Sep 17 00:00:00 2001 From: Gerschel Date: Wed, 8 Feb 2023 18:57:32 -0800 Subject: [PATCH 020/278] Squashed commit of the following: commit b030b67ad005bfe29bcda692238a00042dcae816 Author: Gerschel Date: Wed Feb 8 16:38:56 2023 -0800 styling adjustements commit 80a2acb0230dd77489b0eb466f2efe827a053f6d Author: Gerschel Date: Wed Feb 8 10:49:47 2023 -0800 badge indicator toggles visibility by selection commit 898922e025a6422ac947fb45c1fa4f1109882f0a Merge: 745382a0 31bbfa72 Author: Gerschel <9631031+Gerschel@users.noreply.github.com> Date: Wed Feb 8 08:35:26 2023 -0800 Merge pull request #1 from w-e-w/Rounding-Method Rounding Method commit 31bbfa729a15ef35fa1f905345d3ba2b17b26ab9 Author: w-e-w <40751091+w-e-w@users.noreply.github.com> Date: Wed Feb 8 19:41:45 2023 +0900 use switch commit 85dbe511c33521d3ac62224bf0e0f3a48194ce63 Author: w-e-w <40751091+w-e-w@users.noreply.github.com> Date: Wed Feb 8 16:47:52 2023 +0900 Rounding Method commit 745382a0f4b8d16241545a3460d5206915959255 Author: Gerschel Date: Tue Feb 7 21:19:20 2023 -0800 default set to round commit 728579c618af30ec98a5af0991bd3f28bdaca399 Author: Gerschel Date: Tue Feb 7 21:17:03 2023 -0800 cleaned some commented code out; added indicator commit 5b288c24a1edd8a5c2f35214b9634316d05b8dae Author: Gerschel Date: Tue Feb 7 18:19:00 2023 -0800 needs cleaning; attempt at rounding commit d9f18ae92b929576b0b8c5f1ef8b3b38e441e381 Author: Gerschel Date: Tue Feb 7 15:46:25 2023 -0800 add rounding option in setting for aspect ratio commit af22106802c9e42205649e4c71c23fcf5b8c62f6 Author: Gerschel Date: Tue Feb 7 13:18:45 2023 -0800 added some ratios, sorted ratios by commonality commit 11e2fba73cffe8cdbf4cd0860641b94428ca0e74 Author: Gerschel Date: Tue Feb 7 10:46:53 2023 -0800 snaps to mulitples of 8 and along ratio commit fa00387e07460b10ee82671a1bfa8687e00ee60b Author: Gerschel Date: Mon Feb 6 14:54:59 2023 -0800 updated slidercomponentcontroller commit 8059bc111c3e2d1edb3314e05ab21b65120fa1dd Author: Gerschel Date: Mon Feb 6 14:29:11 2023 -0800 added step size adjustment on number field commit 641157b9f27a874a24ee7b0a854a092e9eac3eec Author: Gerschel Date: Mon Feb 6 14:12:03 2023 -0800 added return step size to default when ratio is disabled commit 5fb75ad28f2476f36100ec93922a8199adbd2a68 Author: Gerschel Date: Mon Feb 6 14:09:34 2023 -0800 added step size adjustment commit e33532883bc4709cd41c3775cbb646d1d5ab0584 Author: Gerschel Date: Mon Feb 6 11:56:15 2023 -0800 adjusted dropdown size, padding, text-align commit 81937329cee77f466c5a5b23c268d0c810128f84 Author: Gerschel Date: Mon Feb 6 11:39:57 2023 -0800 added positioning and styling commit 86eb4583782d92880a9a113a54ffbac9d92f3753 Author: Gerschel Date: Mon Feb 6 08:54:45 2023 -0800 fix typo in defaults; added preventDefault in event --- javascript/ComponentControllers.js | 2 + javascript/aspectRatioSliders.js | 164 ++++++++++++++++++++++++++--- modules/shared.py | 17 ++- modules/ui.py | 10 +- style.css | 46 ++++++++ 5 files changed, 218 insertions(+), 21 deletions(-) diff --git a/javascript/ComponentControllers.js b/javascript/ComponentControllers.js index 194589c7..2888679b 100644 --- a/javascript/ComponentControllers.js +++ b/javascript/ComponentControllers.js @@ -189,6 +189,8 @@ class SliderComponentController { } eventHandler() { this.element.dispatchEvent(new Event("input")); + this.childNumField.dispatchEvent(new Event("input")); + this.childRangeField.dispatchEvent(new Event("input")); } setVal(text) { this.updateVal(text); diff --git a/javascript/aspectRatioSliders.js b/javascript/aspectRatioSliders.js index f577750a..d9c4f675 100644 --- a/javascript/aspectRatioSliders.js +++ b/javascript/aspectRatioSliders.js @@ -1,14 +1,61 @@ class AspectRatioSliderController { - constructor(widthSlider, heightSlider, ratioSource) { + constructor(widthSlider, heightSlider, ratioSource, roundingSource, roundingMethod) { + //References this.widthSlider = new SliderComponentController(widthSlider); this.heightSlider = new SliderComponentController(heightSlider); this.ratioSource = new DropdownComponentController(ratioSource); - this.widthSlider.childRangeField.addEventListener("change", () => this.resize("width")); - this.widthSlider.childNumField.addEventListener("change", () => this.resize("width")); - this.heightSlider.childRangeField.addEventListener("change", () => this.resize("height")); - this.heightSlider.childNumField.addEventListener("change", () => this.resize("height")); + this.roundingSource = new CheckboxComponentController(roundingSource); + this.roundingMethod = new RadioComponentController(roundingMethod); + this.roundingIndicatorBadge = document.createElement("div"); + // Badge implementation + this.roundingIndicatorBadge.innerText = "📐"; + this.roundingIndicatorBadge.classList.add("rounding-badge"); + this.ratioSource.element.appendChild(this.roundingIndicatorBadge); + // Check initial value of ratioSource to set badge visbility + let initialRatio = this.ratioSource.getVal(); + if (!initialRatio.includes(":")) { + this.roundingIndicatorBadge.style.display = "none"; + } + //Adjust badge icon if rounding is on + if (this.roundingSource.getVal()) { + this.roundingIndicatorBadge.classList.add("active"); + this.roundingIndicatorBadge.innerText = "⚠️"; + } + //Make badge clickable to toggle setting + this.roundingIndicatorBadge.addEventListener("click", () => { + this.roundingSource.setVal(!this.roundingSource.getVal()); + }); + //Make rounding setting toggle badge text and style if setting changes + this.roundingSource.child.addEventListener("change", () => { + if (this.roundingSource.getVal()) { + this.roundingIndicatorBadge.classList.add("active"); + this.roundingIndicatorBadge.innerText = "⚠️"; + } + else { + this.roundingIndicatorBadge.classList.remove("active"); + this.roundingIndicatorBadge.innerText = "📐"; + } + this.adjustStepSize(); + }); + //Other event listeners + this.widthSlider.childRangeField.addEventListener("change", (e) => { e.preventDefault(); this.resize("width"); }); + this.widthSlider.childNumField.addEventListener("change", (e) => { e.preventDefault(); this.resize("width"); }); + this.heightSlider.childRangeField.addEventListener("change", (e) => { e.preventDefault(); this.resize("height"); }); + this.heightSlider.childNumField.addEventListener("change", (e) => { e.preventDefault(); this.resize("height"); }); + this.ratioSource.childSelector.addEventListener("change", (e) => { + e.preventDefault(); + //Check and toggle display of badge conditionally on dropdown selection + if (!this.ratioSource.getVal().includes(":")) { + this.roundingIndicatorBadge.style.display = 'none'; + } + else { + this.roundingIndicatorBadge.style.display = 'block'; + } + this.adjustStepSize(); + }); } resize(dimension) { + //For moving slider or number field let val = this.ratioSource.getVal(); if (!val.includes(":")) { return; @@ -16,26 +63,119 @@ class AspectRatioSliderController { let [width, height] = val.split(":").map(Number); let ratio = width / height; if (dimension == 'width') { - this.heightSlider.setVal(Math.round(parseFloat(this.widthSlider.getVal()) / ratio).toString()); + let newHeight = parseInt(this.widthSlider.getVal()) / ratio; + if (this.roundingSource.getVal()) { + switch (this.roundingMethod.getVal()) { + case 'Round': + newHeight = Math.round(newHeight / 8) * 8; + break; + case 'Ceiling': + newHeight = Math.ceil(newHeight / 8) * 8; + break; + case 'Floor': + newHeight = Math.floor(newHeight / 8) * 8; + break; + } + } + this.heightSlider.setVal(newHeight.toString()); } else if (dimension == "height") { - this.widthSlider.setVal(Math.round(parseFloat(this.heightSlider.getVal()) * ratio).toString()); + let newWidth = parseInt(this.heightSlider.getVal()) * ratio; + if (this.roundingSource.getVal()) { + switch (this.roundingMethod.getVal()) { + case 'Round': + newWidth = Math.round(newWidth / 8) * 8; + break; + case 'Ceiling': + newWidth = Math.ceil(newWidth / 8) * 8; + break; + case 'Floor': + newWidth = Math.floor(newWidth / 8) * 8; + break; + } + } + this.widthSlider.setVal(newWidth.toString()); } } - static observeStartup(widthSliderId, heightSliderId, ratioSourceId) { + adjustStepSize() { + /* Sets scales/precision/rounding steps;*/ + let val = this.ratioSource.getVal(); + if (!val.includes(":")) { + //If ratio unlocked + this.widthSlider.childRangeField.step = "8"; + this.widthSlider.childRangeField.min = "64"; + this.widthSlider.childNumField.step = "8"; + this.widthSlider.childNumField.min = "64"; + this.heightSlider.childRangeField.step = "8"; + this.heightSlider.childRangeField.min = "64"; + this.heightSlider.childNumField.step = "8"; + this.heightSlider.childNumField.min = "64"; + return; + } + //Format string and calculate step sizes + let [width, height] = val.split(":").map(Number); + let decimalPlaces = (width.toString().split(".")[1] || []).length; + //keep upto 6 decimal points of precision of ratio + //euclidean gcd does not support floats, so we scale it up + decimalPlaces = decimalPlaces > 6 ? 6 : decimalPlaces; + let gcd = this.gcd(width * 10 ** decimalPlaces, height * 10 ** decimalPlaces) / 10 ** decimalPlaces; + let stepSize = 8 * height / gcd; + let stepSizeOther = 8 * width / gcd; + if (this.roundingSource.getVal()) { + //If rounding is on set/keep default stepsizes + this.widthSlider.childRangeField.step = "8"; + this.widthSlider.childRangeField.min = "64"; + this.widthSlider.childNumField.step = "8"; + this.widthSlider.childNumField.min = "64"; + this.heightSlider.childRangeField.step = "8"; + this.heightSlider.childRangeField.min = "64"; + this.heightSlider.childNumField.step = "8"; + this.heightSlider.childNumField.min = "64"; + } + else { + //if rounding is off, set step sizes so they enforce snapping + //min is changed, because it offsets snap positions + this.widthSlider.childRangeField.step = stepSizeOther.toString(); + this.widthSlider.childRangeField.min = stepSizeOther.toString(); + this.widthSlider.childNumField.step = stepSizeOther.toString(); + this.widthSlider.childNumField.min = stepSizeOther.toString(); + this.heightSlider.childRangeField.step = stepSize.toString(); + this.heightSlider.childRangeField.min = stepSize.toString(); + this.heightSlider.childNumField.step = stepSize.toString(); + this.heightSlider.childNumField.min = stepSize.toString(); + } + let currentWidth = parseInt(this.widthSlider.getVal()); + //Rounding treated kinda like pythons divmod + let stepsTaken = Math.round(currentWidth / stepSizeOther); + //this snaps it to closest rule matches (rules being html step points, and ratio) + let newWidth = stepsTaken * stepSizeOther; + this.widthSlider.setVal(newWidth.toString()); + this.heightSlider.setVal(Math.round(newWidth / (width / height)).toString()); + } + gcd(a, b) { + //euclidean gcd + if (b === 0) { + return a; + } + return this.gcd(b, a % b); + } + static observeStartup(widthSliderId, heightSliderId, ratioSourceId, roundingSourceId, roundingMethodId) { let observer = new MutationObserver(() => { let widthSlider = document.querySelector("gradio-app").shadowRoot.getElementById(widthSliderId); let heightSlider = document.querySelector("gradio-app").shadowRoot.getElementById(heightSliderId); let ratioSource = document.querySelector("gradio-app").shadowRoot.getElementById(ratioSourceId); - if (widthSlider && heightSlider && ratioSource) { + let roundingSource = document.querySelector("gradio-app").shadowRoot.getElementById(roundingSourceId); + let roundingMethod = document.querySelector("gradio-app").shadowRoot.getElementById(roundingMethodId); + if (widthSlider && heightSlider && ratioSource && roundingSource && roundingMethod) { observer.disconnect(); - new AspectRatioSliderController(widthSlider, heightSlider, ratioSource); + new AspectRatioSliderController(widthSlider, heightSlider, ratioSource, roundingSource, roundingMethod); } }); observer.observe(gradioApp(), { childList: true, subtree: true }); } } document.addEventListener("DOMContentLoaded", () => { - AspectRatioSliderController.observeStartup("txt2img_width", "txt2img_height", "txt2img_ratio"); - AspectRatioSliderController.observeStartup("img2img_width", "img2img_height", "img2img_ratio"); + //Register mutation observer for self start-up; + AspectRatioSliderController.observeStartup("txt2img_width", "txt2img_height", "txt2img_ratio", "setting_aspect_ratios_rounding", "setting_aspect_ratios_rounding_method"); + AspectRatioSliderController.observeStartup("img2img_width", "img2img_height", "img2img_ratio", "setting_aspect_ratios_rounding", "setting_aspect_ratios_rounding_method"); }); diff --git a/modules/shared.py b/modules/shared.py index ead7be36..fcd6eadf 100644 --- a/modules/shared.py +++ b/modules/shared.py @@ -140,16 +140,21 @@ ui_reorder_categories = [ ] aspect_ratio_defaults = [ - "🔓" + "🔓", "1:1", - "1:2", - "2:1", - "2:3", "3:2", "4:3", "5:4", - "9:16", "16:9", + "9:16", + "1.85:1", + "2.35:1", + "2.39:1", + "2.40:1", + "21:9", + "1.375:1", + "1.66:1", + "1.75:1" ] cmd_opts.disable_extension_access = (cmd_opts.share or cmd_opts.listen or cmd_opts.server_name) and not cmd_opts.enable_insecure_extension_access @@ -469,6 +474,8 @@ options_templates.update(options_section(('ui', "User interface"), { "keyedit_precision_extra": OptionInfo(0.05, "Ctrl+up/down precision when editing ", gr.Slider, {"minimum": 0.01, "maximum": 0.2, "step": 0.001}), "quicksettings": OptionInfo("sd_model_checkpoint", "Quicksettings list"), "ui_reorder": OptionInfo(", ".join(ui_reorder_categories), "txt2img/img2img UI item order"), + "aspect_ratios_rounding": OptionInfo(True, "Round aspect ratios for more flexibility?", gr.Checkbox), + "aspect_ratios_rounding_method": OptionInfo("Ceiling", "Aspect ratios rounding method", gr.Radio,{"choices": ["Round", "Ceiling", "Floor"]}), "aspect_ratios": OptionInfo(", ".join(aspect_ratio_defaults), "txt2img/img2img aspect ratios"), "ui_extra_networks_tab_reorder": OptionInfo("", "Extra networks tab order"), "localization": OptionInfo("None", "Localization (requires restart)", gr.Dropdown, lambda: {"choices": ["None"] + list(localization.localizations.keys())}, refresh=lambda: localization.list_localizations(cmd_opts.localizations_dir)), diff --git a/modules/ui.py b/modules/ui.py index 6853485c..873c857a 100644 --- a/modules/ui.py +++ b/modules/ui.py @@ -483,8 +483,9 @@ def create_ui(): width = gr.Slider(minimum=64, maximum=2048, step=8, label="Width", value=512, elem_id="txt2img_width") height = gr.Slider(minimum=64, maximum=2048, step=8, label="Height", value=512, elem_id="txt2img_height") - res_switch_btn = ToolButton(value=switch_values_symbol, elem_id="txt2img_res_switch_btn") - aspect_ratio_dropdown = gr.Dropdown(value="🔓", choices=aspect_ratio_list(), interactive=True, type="value", elem_id="txt2img_ratio", show_label=False, label="Aspect Ratio") + with gr.Column(elem_id="txt2img_size_toolbox", scale=0): + aspect_ratio_dropdown = gr.Dropdown(value="🔓", choices=aspect_ratio_list(), interactive=True, type="value", elem_id="txt2img_ratio", show_label=False, label="Aspect Ratio") + res_switch_btn = ToolButton(value=switch_values_symbol, elem_id="txt2img_res_switch_btn") if opts.dimensions_and_batch_together: with gr.Column(elem_id="txt2img_column_batch"): batch_count = gr.Slider(minimum=1, step=1, label='Batch count', value=1, elem_id="txt2img_batch_count") @@ -762,8 +763,9 @@ def create_ui(): width = gr.Slider(minimum=64, maximum=2048, step=8, label="Width", value=512, elem_id="img2img_width") height = gr.Slider(minimum=64, maximum=2048, step=8, label="Height", value=512, elem_id="img2img_height") - res_switch_btn = ToolButton(value=switch_values_symbol, elem_id="img2img_res_switch_btn") - aspect_ratio_dropdown = gr.Dropdown(value="🔓", choices=aspect_ratio_list(), interactive=True, type="value", elem_id="img2img_ratio", show_label=False, label="Aspect Ratio") + with gr.Column(elem_id="img2img_size_toolbox", scale=0): + aspect_ratio_dropdown = gr.Dropdown(value="🔓", choices=aspect_ratio_list(), interactive=True, type="value", elem_id="img2img_ratio", show_label=False, label="Aspect Ratio") + res_switch_btn = ToolButton(value=switch_values_symbol, elem_id="img2img_res_switch_btn") if opts.dimensions_and_batch_together: with gr.Column(elem_id="img2img_column_batch"): batch_count = gr.Slider(minimum=1, step=1, label='Batch count', value=1, elem_id="img2img_batch_count") diff --git a/style.css b/style.css index 05572f66..5b979841 100644 --- a/style.css +++ b/style.css @@ -747,6 +747,52 @@ footer { margin-left: 0em; } +#txt2img_size_toolbox, #img2img_size_toolbox{ + min-width: unset !important; + gap: 0; +} + +#txt2img_ratio, #img2img_ratio { + padding: 0px; + min-width: unset; + max-width: fit-content; +} +#txt2img_ratio select, #img2img_ratio select{ + -o-appearance: none; + -ms-appearance: none; + -webkit-appearance: none; + -moz-appearance: none; + appearance: none; + background-image: unset; + padding-right: unset; + min-width: 40px; + max-width: 40px; + min-height: 40px; + max-height: 40px; + line-height: 40px; + padding: 0; + text-align: center; +} +.rounding-badge { + display: inline-block; + border-radius: 0px; + background-color: #ccc; + cursor: pointer; + position: absolute; + top: -10px; + right: -10px; + width: 20px; + height: 20px; + padding: 1px; + line-height: 16px; + font-size: 14px; +} + +.rounding-badge.active { + background-color: #007bff; + border-radius: 50%; +} + .inactive{ opacity: 0.5; } From 463ab841803b45ea421ad7f9769b836f3000ef8c Mon Sep 17 00:00:00 2001 From: missionfloyd Date: Thu, 9 Feb 2023 02:13:49 -0700 Subject: [PATCH 021/278] Convert 16-bit greyscale to 8-bit when saving as JPEG --- modules/images.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/modules/images.py b/modules/images.py index c2ca8849..c0c68913 100644 --- a/modules/images.py +++ b/modules/images.py @@ -553,6 +553,8 @@ def save_image(image, path, basename, seed=None, prompt=None, extension='png', i elif extension.lower() in (".jpg", ".jpeg", ".webp"): if image_to_save.mode == 'RGBA': image_to_save = image_to_save.convert("RGB") + elif image_to_save.mode == 'I;16': + image_to_save = image_to_save.point(lambda p: p * 0.0038910505836576).convert("L") image_to_save.save(temp_file_path, format=image_format, quality=opts.jpeg_quality) From b313221ca6d12e441f2f5041490e2fc665ff5f60 Mon Sep 17 00:00:00 2001 From: w-e-w <40751091+w-e-w@users.noreply.github.com> Date: Fri, 10 Feb 2023 08:34:21 +0900 Subject: [PATCH 022/278] =?UTF-8?q?remove=20Badge=20color=20and=20?= =?UTF-8?q?=E2=9A=A0=EF=B8=8F=20->=F0=9F=93=8F?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- javascript/aspectRatioSliders.js | 10 +++++----- style.css | 2 +- 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/javascript/aspectRatioSliders.js b/javascript/aspectRatioSliders.js index d9c4f675..3def5158 100644 --- a/javascript/aspectRatioSliders.js +++ b/javascript/aspectRatioSliders.js @@ -18,8 +18,8 @@ class AspectRatioSliderController { } //Adjust badge icon if rounding is on if (this.roundingSource.getVal()) { - this.roundingIndicatorBadge.classList.add("active"); - this.roundingIndicatorBadge.innerText = "⚠️"; + //this.roundingIndicatorBadge.classList.add("active"); + this.roundingIndicatorBadge.innerText = "📏"; } //Make badge clickable to toggle setting this.roundingIndicatorBadge.addEventListener("click", () => { @@ -28,11 +28,11 @@ class AspectRatioSliderController { //Make rounding setting toggle badge text and style if setting changes this.roundingSource.child.addEventListener("change", () => { if (this.roundingSource.getVal()) { - this.roundingIndicatorBadge.classList.add("active"); - this.roundingIndicatorBadge.innerText = "⚠️"; + //this.roundingIndicatorBadge.classList.add("active"); + this.roundingIndicatorBadge.innerText = "📏"; } else { - this.roundingIndicatorBadge.classList.remove("active"); + //this.roundingIndicatorBadge.classList.remove("active"); this.roundingIndicatorBadge.innerText = "📐"; } this.adjustStepSize(); diff --git a/style.css b/style.css index 5b979841..55baefb7 100644 --- a/style.css +++ b/style.css @@ -776,7 +776,7 @@ footer { .rounding-badge { display: inline-block; border-radius: 0px; - background-color: #ccc; + /*background-color: #ccc;*/ cursor: pointer; position: absolute; top: -10px; From 73a97cac11456adcda05872364c605ebcd3982ad Mon Sep 17 00:00:00 2001 From: missionfloyd Date: Thu, 9 Feb 2023 17:04:55 -0700 Subject: [PATCH 023/278] Use RGB for webp Doesn't support greyscale (L) --- modules/images.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/modules/images.py b/modules/images.py index c0c68913..b335502b 100644 --- a/modules/images.py +++ b/modules/images.py @@ -554,7 +554,7 @@ def save_image(image, path, basename, seed=None, prompt=None, extension='png', i if image_to_save.mode == 'RGBA': image_to_save = image_to_save.convert("RGB") elif image_to_save.mode == 'I;16': - image_to_save = image_to_save.point(lambda p: p * 0.0038910505836576).convert("L") + image_to_save = image_to_save.point(lambda p: p * 0.0038910505836576).convert("RGB" if extension.lower() == ".webp" else "L") image_to_save.save(temp_file_path, format=image_format, quality=opts.jpeg_quality) From 33947a3c6631179bba02285f09b5853b9ecf5782 Mon Sep 17 00:00:00 2001 From: minux302 Date: Fri, 10 Feb 2023 17:58:35 +0900 Subject: [PATCH 024/278] fix arg for hypernetwork train api --- 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 eb7b1da5..5a9ac5f1 100644 --- a/modules/api/api.py +++ b/modules/api/api.py @@ -498,7 +498,7 @@ class Api: if not apply_optimizations: sd_hijack.undo_optimizations() try: - hypernetwork, filename = train_hypernetwork(*args) + hypernetwork, filename = train_hypernetwork(**args) except Exception as e: error = e finally: From 125319988984987801dc4b4ab1e5ed36e9b211c5 Mon Sep 17 00:00:00 2001 From: space-nuko <24979496+space-nuko@users.noreply.github.com> Date: Fri, 10 Feb 2023 03:30:20 -0800 Subject: [PATCH 025/278] Working UniPC (for batch size 1) --- javascript/hints.js | 1 + modules/models/diffusion/uni_pc/__init__.py | 1 + modules/models/diffusion/uni_pc/sampler.py | 85 ++ modules/models/diffusion/uni_pc/uni_pc.py | 858 ++++++++++++++++++++ modules/processing.py | 2 +- modules/sd_samplers_compvis.py | 35 +- test/basic_features/txt2img_test.py | 2 + 7 files changed, 978 insertions(+), 6 deletions(-) create mode 100644 modules/models/diffusion/uni_pc/__init__.py create mode 100644 modules/models/diffusion/uni_pc/sampler.py create mode 100644 modules/models/diffusion/uni_pc/uni_pc.py diff --git a/javascript/hints.js b/javascript/hints.js index 9aa82f24..0a0620e3 100644 --- a/javascript/hints.js +++ b/javascript/hints.js @@ -6,6 +6,7 @@ titles = { "GFPGAN": "Restore low quality faces using GFPGAN neural network", "Euler a": "Euler Ancestral - very creative, each can get a completely different picture depending on step count, setting steps higher than 30-40 does not help", "DDIM": "Denoising Diffusion Implicit Models - best at inpainting", + "UniPC": "Unified Predictor-Corrector Framework for Fast Sampling of Diffusion Models", "DPM adaptive": "Ignores step count - uses a number of steps determined by the CFG and resolution", "Batch count": "How many batches of images to create", diff --git a/modules/models/diffusion/uni_pc/__init__.py b/modules/models/diffusion/uni_pc/__init__.py new file mode 100644 index 00000000..e1265e3f --- /dev/null +++ b/modules/models/diffusion/uni_pc/__init__.py @@ -0,0 +1 @@ +from .sampler import UniPCSampler diff --git a/modules/models/diffusion/uni_pc/sampler.py b/modules/models/diffusion/uni_pc/sampler.py new file mode 100644 index 00000000..7cccd8a2 --- /dev/null +++ b/modules/models/diffusion/uni_pc/sampler.py @@ -0,0 +1,85 @@ +"""SAMPLING ONLY.""" + +import torch + +from .uni_pc import NoiseScheduleVP, model_wrapper, UniPC + +class UniPCSampler(object): + def __init__(self, model, **kwargs): + super().__init__() + self.model = model + to_torch = lambda x: x.clone().detach().to(torch.float32).to(model.device) + self.before_sample = None + self.after_sample = None + self.register_buffer('alphas_cumprod', to_torch(model.alphas_cumprod)) + + def register_buffer(self, name, attr): + if type(attr) == torch.Tensor: + if attr.device != torch.device("cuda"): + attr = attr.to(torch.device("cuda")) + setattr(self, name, attr) + + def set_hooks(self, before, after): + self.before_sample = before + self.after_sample = after + + @torch.no_grad() + def sample(self, + S, + batch_size, + shape, + conditioning=None, + callback=None, + normals_sequence=None, + img_callback=None, + quantize_x0=False, + eta=0., + mask=None, + x0=None, + temperature=1., + noise_dropout=0., + score_corrector=None, + corrector_kwargs=None, + verbose=True, + x_T=None, + log_every_t=100, + unconditional_guidance_scale=1., + unconditional_conditioning=None, + # this has to come in the same format as the conditioning, # e.g. as encoded tokens, ... + **kwargs + ): + if conditioning is not None: + if isinstance(conditioning, dict): + cbs = conditioning[list(conditioning.keys())[0]].shape[0] + if cbs != batch_size: + print(f"Warning: Got {cbs} conditionings but batch-size is {batch_size}") + else: + if conditioning.shape[0] != batch_size: + print(f"Warning: Got {conditioning.shape[0]} conditionings but batch-size is {batch_size}") + + # sampling + C, H, W = shape + size = (batch_size, C, H, W) + + device = self.model.betas.device + if x_T is None: + img = torch.randn(size, device=device) + else: + img = x_T + + ns = NoiseScheduleVP('discrete', alphas_cumprod=self.alphas_cumprod) + + model_fn = model_wrapper( + lambda x, t, c: self.model.apply_model(x, t, c), + ns, + model_type="noise", + guidance_type="classifier-free", + #condition=conditioning, + #unconditional_condition=unconditional_conditioning, + guidance_scale=unconditional_guidance_scale, + ) + + uni_pc = UniPC(model_fn, ns, predict_x0=True, thresholding=False, condition=conditioning, unconditional_condition=unconditional_conditioning, before_sample=self.before_sample, after_sample=self.after_sample) + x = uni_pc.sample(img, steps=S, skip_type="time_uniform", method="multistep", order=3, lower_order_final=True) + + return x.to(device), None diff --git a/modules/models/diffusion/uni_pc/uni_pc.py b/modules/models/diffusion/uni_pc/uni_pc.py new file mode 100644 index 00000000..ec6b37da --- /dev/null +++ b/modules/models/diffusion/uni_pc/uni_pc.py @@ -0,0 +1,858 @@ +import torch +import torch.nn.functional as F +import math + + +class NoiseScheduleVP: + def __init__( + self, + schedule='discrete', + betas=None, + alphas_cumprod=None, + continuous_beta_0=0.1, + continuous_beta_1=20., + ): + """Create a wrapper class for the forward SDE (VP type). + + *** + Update: We support discrete-time diffusion models by implementing a picewise linear interpolation for log_alpha_t. + We recommend to use schedule='discrete' for the discrete-time diffusion models, especially for high-resolution images. + *** + + The forward SDE ensures that the condition distribution q_{t|0}(x_t | x_0) = N ( alpha_t * x_0, sigma_t^2 * I ). + We further define lambda_t = log(alpha_t) - log(sigma_t), which is the half-logSNR (described in the DPM-Solver paper). + Therefore, we implement the functions for computing alpha_t, sigma_t and lambda_t. For t in [0, T], we have: + + log_alpha_t = self.marginal_log_mean_coeff(t) + sigma_t = self.marginal_std(t) + lambda_t = self.marginal_lambda(t) + + Moreover, as lambda(t) is an invertible function, we also support its inverse function: + + t = self.inverse_lambda(lambda_t) + + =============================================================== + + We support both discrete-time DPMs (trained on n = 0, 1, ..., N-1) and continuous-time DPMs (trained on t in [t_0, T]). + + 1. For discrete-time DPMs: + + For discrete-time DPMs trained on n = 0, 1, ..., N-1, we convert the discrete steps to continuous time steps by: + t_i = (i + 1) / N + e.g. for N = 1000, we have t_0 = 1e-3 and T = t_{N-1} = 1. + We solve the corresponding diffusion ODE from time T = 1 to time t_0 = 1e-3. + + Args: + betas: A `torch.Tensor`. The beta array for the discrete-time DPM. (See the original DDPM paper for details) + alphas_cumprod: A `torch.Tensor`. The cumprod alphas for the discrete-time DPM. (See the original DDPM paper for details) + + Note that we always have alphas_cumprod = cumprod(betas). Therefore, we only need to set one of `betas` and `alphas_cumprod`. + + **Important**: Please pay special attention for the args for `alphas_cumprod`: + The `alphas_cumprod` is the \hat{alpha_n} arrays in the notations of DDPM. Specifically, DDPMs assume that + q_{t_n | 0}(x_{t_n} | x_0) = N ( \sqrt{\hat{alpha_n}} * x_0, (1 - \hat{alpha_n}) * I ). + Therefore, the notation \hat{alpha_n} is different from the notation alpha_t in DPM-Solver. In fact, we have + alpha_{t_n} = \sqrt{\hat{alpha_n}}, + and + log(alpha_{t_n}) = 0.5 * log(\hat{alpha_n}). + + + 2. For continuous-time DPMs: + + We support two types of VPSDEs: linear (DDPM) and cosine (improved-DDPM). The hyperparameters for the noise + schedule are the default settings in DDPM and improved-DDPM: + + Args: + beta_min: A `float` number. The smallest beta for the linear schedule. + beta_max: A `float` number. The largest beta for the linear schedule. + cosine_s: A `float` number. The hyperparameter in the cosine schedule. + cosine_beta_max: A `float` number. The hyperparameter in the cosine schedule. + T: A `float` number. The ending time of the forward process. + + =============================================================== + + Args: + schedule: A `str`. The noise schedule of the forward SDE. 'discrete' for discrete-time DPMs, + 'linear' or 'cosine' for continuous-time DPMs. + Returns: + A wrapper object of the forward SDE (VP type). + + =============================================================== + + Example: + + # For discrete-time DPMs, given betas (the beta array for n = 0, 1, ..., N - 1): + >>> ns = NoiseScheduleVP('discrete', betas=betas) + + # For discrete-time DPMs, given alphas_cumprod (the \hat{alpha_n} array for n = 0, 1, ..., N - 1): + >>> ns = NoiseScheduleVP('discrete', alphas_cumprod=alphas_cumprod) + + # For continuous-time DPMs (VPSDE), linear schedule: + >>> ns = NoiseScheduleVP('linear', continuous_beta_0=0.1, continuous_beta_1=20.) + + """ + + if schedule not in ['discrete', 'linear', 'cosine']: + raise ValueError("Unsupported noise schedule {}. The schedule needs to be 'discrete' or 'linear' or 'cosine'".format(schedule)) + + self.schedule = schedule + if schedule == 'discrete': + if betas is not None: + log_alphas = 0.5 * torch.log(1 - betas).cumsum(dim=0) + else: + assert alphas_cumprod is not None + log_alphas = 0.5 * torch.log(alphas_cumprod) + self.total_N = len(log_alphas) + self.T = 1. + self.t_array = torch.linspace(0., 1., self.total_N + 1)[1:].reshape((1, -1)) + self.log_alpha_array = log_alphas.reshape((1, -1,)) + else: + self.total_N = 1000 + self.beta_0 = continuous_beta_0 + self.beta_1 = continuous_beta_1 + self.cosine_s = 0.008 + self.cosine_beta_max = 999. + self.cosine_t_max = math.atan(self.cosine_beta_max * (1. + self.cosine_s) / math.pi) * 2. * (1. + self.cosine_s) / math.pi - self.cosine_s + self.cosine_log_alpha_0 = math.log(math.cos(self.cosine_s / (1. + self.cosine_s) * math.pi / 2.)) + self.schedule = schedule + if schedule == 'cosine': + # For the cosine schedule, T = 1 will have numerical issues. So we manually set the ending time T. + # Note that T = 0.9946 may be not the optimal setting. However, we find it works well. + self.T = 0.9946 + else: + self.T = 1. + + def marginal_log_mean_coeff(self, t): + """ + Compute log(alpha_t) of a given continuous-time label t in [0, T]. + """ + if self.schedule == 'discrete': + return interpolate_fn(t.reshape((-1, 1)), self.t_array.to(t.device), self.log_alpha_array.to(t.device)).reshape((-1)) + elif self.schedule == 'linear': + return -0.25 * t ** 2 * (self.beta_1 - self.beta_0) - 0.5 * t * self.beta_0 + elif self.schedule == 'cosine': + log_alpha_fn = lambda s: torch.log(torch.cos((s + self.cosine_s) / (1. + self.cosine_s) * math.pi / 2.)) + log_alpha_t = log_alpha_fn(t) - self.cosine_log_alpha_0 + return log_alpha_t + + def marginal_alpha(self, t): + """ + Compute alpha_t of a given continuous-time label t in [0, T]. + """ + return torch.exp(self.marginal_log_mean_coeff(t)) + + def marginal_std(self, t): + """ + Compute sigma_t of a given continuous-time label t in [0, T]. + """ + return torch.sqrt(1. - torch.exp(2. * self.marginal_log_mean_coeff(t))) + + def marginal_lambda(self, t): + """ + Compute lambda_t = log(alpha_t) - log(sigma_t) of a given continuous-time label t in [0, T]. + """ + log_mean_coeff = self.marginal_log_mean_coeff(t) + log_std = 0.5 * torch.log(1. - torch.exp(2. * log_mean_coeff)) + return log_mean_coeff - log_std + + def inverse_lambda(self, lamb): + """ + Compute the continuous-time label t in [0, T] of a given half-logSNR lambda_t. + """ + if self.schedule == 'linear': + tmp = 2. * (self.beta_1 - self.beta_0) * torch.logaddexp(-2. * lamb, torch.zeros((1,)).to(lamb)) + Delta = self.beta_0**2 + tmp + return tmp / (torch.sqrt(Delta) + self.beta_0) / (self.beta_1 - self.beta_0) + elif self.schedule == 'discrete': + log_alpha = -0.5 * torch.logaddexp(torch.zeros((1,)).to(lamb.device), -2. * lamb) + t = interpolate_fn(log_alpha.reshape((-1, 1)), torch.flip(self.log_alpha_array.to(lamb.device), [1]), torch.flip(self.t_array.to(lamb.device), [1])) + return t.reshape((-1,)) + else: + log_alpha = -0.5 * torch.logaddexp(-2. * lamb, torch.zeros((1,)).to(lamb)) + t_fn = lambda log_alpha_t: torch.arccos(torch.exp(log_alpha_t + self.cosine_log_alpha_0)) * 2. * (1. + self.cosine_s) / math.pi - self.cosine_s + t = t_fn(log_alpha) + return t + + +def model_wrapper( + model, + noise_schedule, + model_type="noise", + model_kwargs={}, + guidance_type="uncond", + #condition=None, + #unconditional_condition=None, + guidance_scale=1., + classifier_fn=None, + classifier_kwargs={}, +): + """Create a wrapper function for the noise prediction model. + + DPM-Solver needs to solve the continuous-time diffusion ODEs. For DPMs trained on discrete-time labels, we need to + firstly wrap the model function to a noise prediction model that accepts the continuous time as the input. + + We support four types of the diffusion model by setting `model_type`: + + 1. "noise": noise prediction model. (Trained by predicting noise). + + 2. "x_start": data prediction model. (Trained by predicting the data x_0 at time 0). + + 3. "v": velocity prediction model. (Trained by predicting the velocity). + The "v" prediction is derivation detailed in Appendix D of [1], and is used in Imagen-Video [2]. + + [1] Salimans, Tim, and Jonathan Ho. "Progressive distillation for fast sampling of diffusion models." + arXiv preprint arXiv:2202.00512 (2022). + [2] Ho, Jonathan, et al. "Imagen Video: High Definition Video Generation with Diffusion Models." + arXiv preprint arXiv:2210.02303 (2022). + + 4. "score": marginal score function. (Trained by denoising score matching). + Note that the score function and the noise prediction model follows a simple relationship: + ``` + noise(x_t, t) = -sigma_t * score(x_t, t) + ``` + + We support three types of guided sampling by DPMs by setting `guidance_type`: + 1. "uncond": unconditional sampling by DPMs. + The input `model` has the following format: + `` + model(x, t_input, **model_kwargs) -> noise | x_start | v | score + `` + + 2. "classifier": classifier guidance sampling [3] by DPMs and another classifier. + The input `model` has the following format: + `` + model(x, t_input, **model_kwargs) -> noise | x_start | v | score + `` + + The input `classifier_fn` has the following format: + `` + classifier_fn(x, t_input, cond, **classifier_kwargs) -> logits(x, t_input, cond) + `` + + [3] P. Dhariwal and A. Q. Nichol, "Diffusion models beat GANs on image synthesis," + in Advances in Neural Information Processing Systems, vol. 34, 2021, pp. 8780-8794. + + 3. "classifier-free": classifier-free guidance sampling by conditional DPMs. + The input `model` has the following format: + `` + model(x, t_input, cond, **model_kwargs) -> noise | x_start | v | score + `` + And if cond == `unconditional_condition`, the model output is the unconditional DPM output. + + [4] Ho, Jonathan, and Tim Salimans. "Classifier-free diffusion guidance." + arXiv preprint arXiv:2207.12598 (2022). + + + The `t_input` is the time label of the model, which may be discrete-time labels (i.e. 0 to 999) + or continuous-time labels (i.e. epsilon to T). + + We wrap the model function to accept only `x` and `t_continuous` as inputs, and outputs the predicted noise: + `` + def model_fn(x, t_continuous) -> noise: + t_input = get_model_input_time(t_continuous) + return noise_pred(model, x, t_input, **model_kwargs) + `` + where `t_continuous` is the continuous time labels (i.e. epsilon to T). And we use `model_fn` for DPM-Solver. + + =============================================================== + + Args: + model: A diffusion model with the corresponding format described above. + noise_schedule: A noise schedule object, such as NoiseScheduleVP. + model_type: A `str`. The parameterization type of the diffusion model. + "noise" or "x_start" or "v" or "score". + model_kwargs: A `dict`. A dict for the other inputs of the model function. + guidance_type: A `str`. The type of the guidance for sampling. + "uncond" or "classifier" or "classifier-free". + condition: A pytorch tensor. The condition for the guided sampling. + Only used for "classifier" or "classifier-free" guidance type. + unconditional_condition: A pytorch tensor. The condition for the unconditional sampling. + Only used for "classifier-free" guidance type. + guidance_scale: A `float`. The scale for the guided sampling. + classifier_fn: A classifier function. Only used for the classifier guidance. + classifier_kwargs: A `dict`. A dict for the other inputs of the classifier function. + Returns: + A noise prediction model that accepts the noised data and the continuous time as the inputs. + """ + + def get_model_input_time(t_continuous): + """ + Convert the continuous-time `t_continuous` (in [epsilon, T]) to the model input time. + For discrete-time DPMs, we convert `t_continuous` in [1 / N, 1] to `t_input` in [0, 1000 * (N - 1) / N]. + For continuous-time DPMs, we just use `t_continuous`. + """ + if noise_schedule.schedule == 'discrete': + return (t_continuous - 1. / noise_schedule.total_N) * 1000. + else: + return t_continuous + + def noise_pred_fn(x, t_continuous, cond=None): + if t_continuous.reshape((-1,)).shape[0] == 1: + t_continuous = t_continuous.expand((x.shape[0])) + t_input = get_model_input_time(t_continuous) + if cond is None: + output = model(x, t_input, None, **model_kwargs) + else: + output = model(x, t_input, cond, **model_kwargs) + if model_type == "noise": + return output + elif model_type == "x_start": + alpha_t, sigma_t = noise_schedule.marginal_alpha(t_continuous), noise_schedule.marginal_std(t_continuous) + dims = x.dim() + return (x - expand_dims(alpha_t, dims) * output) / expand_dims(sigma_t, dims) + elif model_type == "v": + alpha_t, sigma_t = noise_schedule.marginal_alpha(t_continuous), noise_schedule.marginal_std(t_continuous) + dims = x.dim() + return expand_dims(alpha_t, dims) * output + expand_dims(sigma_t, dims) * x + elif model_type == "score": + sigma_t = noise_schedule.marginal_std(t_continuous) + dims = x.dim() + return -expand_dims(sigma_t, dims) * output + + def cond_grad_fn(x, t_input, condition): + """ + Compute the gradient of the classifier, i.e. nabla_{x} log p_t(cond | x_t). + """ + with torch.enable_grad(): + x_in = x.detach().requires_grad_(True) + log_prob = classifier_fn(x_in, t_input, condition, **classifier_kwargs) + return torch.autograd.grad(log_prob.sum(), x_in)[0] + + def model_fn(x, t_continuous, condition, unconditional_condition): + """ + The noise predicition model function that is used for DPM-Solver. + """ + if t_continuous.reshape((-1,)).shape[0] == 1: + t_continuous = t_continuous.expand((x.shape[0])) + if guidance_type == "uncond": + return noise_pred_fn(x, t_continuous) + elif guidance_type == "classifier": + assert classifier_fn is not None + t_input = get_model_input_time(t_continuous) + cond_grad = cond_grad_fn(x, t_input, condition) + sigma_t = noise_schedule.marginal_std(t_continuous) + noise = noise_pred_fn(x, t_continuous) + return noise - guidance_scale * expand_dims(sigma_t, dims=cond_grad.dim()) * cond_grad + elif guidance_type == "classifier-free": + if guidance_scale == 1. or unconditional_condition is None: + return noise_pred_fn(x, t_continuous, cond=condition) + else: + x_in = torch.cat([x] * 2) + t_in = torch.cat([t_continuous] * 2) + if isinstance(condition, dict): + assert isinstance(unconditional_condition, dict) + c_in = dict() + for k in condition: + if isinstance(condition[k], list): + c_in[k] = [torch.cat([ + unconditional_condition[k][i], + condition[k][i]]) for i in range(len(condition[k]))] + else: + c_in[k] = torch.cat([ + unconditional_condition[k], + condition[k]]) + elif isinstance(condition, list): + c_in = list() + assert isinstance(unconditional_condition, list) + for i in range(len(condition)): + c_in.append(torch.cat([unconditional_condition[i], condition[i]])) + else: + c_in = torch.cat([unconditional_condition, condition]) + noise_uncond, noise = noise_pred_fn(x_in, t_in, cond=c_in).chunk(2) + return noise_uncond + guidance_scale * (noise - noise_uncond) + + assert model_type in ["noise", "x_start", "v"] + assert guidance_type in ["uncond", "classifier", "classifier-free"] + return model_fn + + +class UniPC: + def __init__( + self, + model_fn, + noise_schedule, + predict_x0=True, + thresholding=False, + max_val=1., + variant='bh1', + condition=None, + unconditional_condition=None, + before_sample=None, + after_sample=None + ): + """Construct a UniPC. + + We support both data_prediction and noise_prediction. + """ + self.model_fn_ = model_fn + self.noise_schedule = noise_schedule + self.variant = variant + self.predict_x0 = predict_x0 + self.thresholding = thresholding + self.max_val = max_val + self.condition = condition + self.unconditional_condition = unconditional_condition + self.before_sample = before_sample + self.after_sample = after_sample + + def dynamic_thresholding_fn(self, x0, t=None): + """ + The dynamic thresholding method. + """ + dims = x0.dim() + p = self.dynamic_thresholding_ratio + s = torch.quantile(torch.abs(x0).reshape((x0.shape[0], -1)), p, dim=1) + s = expand_dims(torch.maximum(s, self.thresholding_max_val * torch.ones_like(s).to(s.device)), dims) + x0 = torch.clamp(x0, -s, s) / s + return x0 + + def model(self, x, t): + cond = self.condition + uncond = self.unconditional_condition + if self.before_sample is not None: + x, t, cond, uncond = self.before_sample(x, t, cond, uncond) + res = self.model_fn_(x, t, cond, uncond) + if self.after_sample is not None: + x, t, cond, uncond, res = self.after_sample(x, t, cond, uncond, res) + + if isinstance(res, tuple): + # (None, pred_x0) + res = res[1] + + return res + + def noise_prediction_fn(self, x, t): + """ + Return the noise prediction model. + """ + return self.model(x, t) + + def data_prediction_fn(self, x, t): + """ + Return the data prediction model (with thresholding). + """ + noise = self.noise_prediction_fn(x, t) + dims = x.dim() + alpha_t, sigma_t = self.noise_schedule.marginal_alpha(t), self.noise_schedule.marginal_std(t) + from pprint import pp + print("X:") + pp(x) + print("sigma_t:") + pp(sigma_t) + print("noise:") + pp(noise) + print("alpha_t:") + pp(alpha_t) + x0 = (x - expand_dims(sigma_t, dims) * noise) / expand_dims(alpha_t, dims) + if self.thresholding: + p = 0.995 # A hyperparameter in the paper of "Imagen" [1]. + s = torch.quantile(torch.abs(x0).reshape((x0.shape[0], -1)), p, dim=1) + s = expand_dims(torch.maximum(s, self.max_val * torch.ones_like(s).to(s.device)), dims) + x0 = torch.clamp(x0, -s, s) / s + return x0 + + def model_fn(self, x, t): + """ + Convert the model to the noise prediction model or the data prediction model. + """ + if self.predict_x0: + return self.data_prediction_fn(x, t) + else: + return self.noise_prediction_fn(x, t) + + def get_time_steps(self, skip_type, t_T, t_0, N, device): + """Compute the intermediate time steps for sampling. + """ + if skip_type == 'logSNR': + lambda_T = self.noise_schedule.marginal_lambda(torch.tensor(t_T).to(device)) + lambda_0 = self.noise_schedule.marginal_lambda(torch.tensor(t_0).to(device)) + logSNR_steps = torch.linspace(lambda_T.cpu().item(), lambda_0.cpu().item(), N + 1).to(device) + return self.noise_schedule.inverse_lambda(logSNR_steps) + elif skip_type == 'time_uniform': + return torch.linspace(t_T, t_0, N + 1).to(device) + elif skip_type == 'time_quadratic': + t_order = 2 + t = torch.linspace(t_T**(1. / t_order), t_0**(1. / t_order), N + 1).pow(t_order).to(device) + return t + else: + raise ValueError("Unsupported skip_type {}, need to be 'logSNR' or 'time_uniform' or 'time_quadratic'".format(skip_type)) + + def get_orders_and_timesteps_for_singlestep_solver(self, steps, order, skip_type, t_T, t_0, device): + """ + Get the order of each step for sampling by the singlestep DPM-Solver. + """ + if order == 3: + K = steps // 3 + 1 + if steps % 3 == 0: + orders = [3,] * (K - 2) + [2, 1] + elif steps % 3 == 1: + orders = [3,] * (K - 1) + [1] + else: + orders = [3,] * (K - 1) + [2] + elif order == 2: + if steps % 2 == 0: + K = steps // 2 + orders = [2,] * K + else: + K = steps // 2 + 1 + orders = [2,] * (K - 1) + [1] + elif order == 1: + K = steps + orders = [1,] * steps + else: + raise ValueError("'order' must be '1' or '2' or '3'.") + if skip_type == 'logSNR': + # To reproduce the results in DPM-Solver paper + timesteps_outer = self.get_time_steps(skip_type, t_T, t_0, K, device) + else: + timesteps_outer = self.get_time_steps(skip_type, t_T, t_0, steps, device)[torch.cumsum(torch.tensor([0,] + orders), 0).to(device)] + return timesteps_outer, orders + + def denoise_to_zero_fn(self, x, s): + """ + Denoise at the final step, which is equivalent to solve the ODE from lambda_s to infty by first-order discretization. + """ + return self.data_prediction_fn(x, s) + + def multistep_uni_pc_update(self, x, model_prev_list, t_prev_list, t, order, **kwargs): + if len(t.shape) == 0: + t = t.view(-1) + if 'bh' in self.variant: + return self.multistep_uni_pc_bh_update(x, model_prev_list, t_prev_list, t, order, **kwargs) + else: + assert self.variant == 'vary_coeff' + return self.multistep_uni_pc_vary_update(x, model_prev_list, t_prev_list, t, order, **kwargs) + + def multistep_uni_pc_vary_update(self, x, model_prev_list, t_prev_list, t, order, use_corrector=True): + print(f'using unified predictor-corrector with order {order} (solver type: vary coeff)') + ns = self.noise_schedule + assert order <= len(model_prev_list) + + # first compute rks + t_prev_0 = t_prev_list[-1] + lambda_prev_0 = ns.marginal_lambda(t_prev_0) + lambda_t = ns.marginal_lambda(t) + model_prev_0 = model_prev_list[-1] + sigma_prev_0, sigma_t = ns.marginal_std(t_prev_0), ns.marginal_std(t) + log_alpha_t = ns.marginal_log_mean_coeff(t) + alpha_t = torch.exp(log_alpha_t) + + h = lambda_t - lambda_prev_0 + + rks = [] + D1s = [] + for i in range(1, order): + t_prev_i = t_prev_list[-(i + 1)] + model_prev_i = model_prev_list[-(i + 1)] + lambda_prev_i = ns.marginal_lambda(t_prev_i) + rk = (lambda_prev_i - lambda_prev_0) / h + rks.append(rk) + D1s.append((model_prev_i - model_prev_0) / rk) + + rks.append(1.) + rks = torch.tensor(rks, device=x.device) + + K = len(rks) + # build C matrix + C = [] + + col = torch.ones_like(rks) + for k in range(1, K + 1): + C.append(col) + col = col * rks / (k + 1) + C = torch.stack(C, dim=1) + + if len(D1s) > 0: + D1s = torch.stack(D1s, dim=1) # (B, K) + C_inv_p = torch.linalg.inv(C[:-1, :-1]) + A_p = C_inv_p + + if use_corrector: + print('using corrector') + C_inv = torch.linalg.inv(C) + A_c = C_inv + + hh = -h if self.predict_x0 else h + h_phi_1 = torch.expm1(hh) + h_phi_ks = [] + factorial_k = 1 + h_phi_k = h_phi_1 + for k in range(1, K + 2): + h_phi_ks.append(h_phi_k) + h_phi_k = h_phi_k / hh - 1 / factorial_k + factorial_k *= (k + 1) + + model_t = None + if self.predict_x0: + x_t_ = ( + sigma_t / sigma_prev_0 * x + - alpha_t * h_phi_1 * model_prev_0 + ) + # now predictor + x_t = x_t_ + if len(D1s) > 0: + # compute the residuals for predictor + for k in range(K - 1): + x_t = x_t - alpha_t * h_phi_ks[k + 1] * torch.einsum('bkchw,k->bchw', D1s, A_p[k]) + # now corrector + if use_corrector: + model_t = self.model_fn(x_t, t) + D1_t = (model_t - model_prev_0) + x_t = x_t_ + k = 0 + for k in range(K - 1): + x_t = x_t - alpha_t * h_phi_ks[k + 1] * torch.einsum('bkchw,k->bchw', D1s, A_c[k][:-1]) + x_t = x_t - alpha_t * h_phi_ks[K] * (D1_t * A_c[k][-1]) + else: + log_alpha_prev_0, log_alpha_t = ns.marginal_log_mean_coeff(t_prev_0), ns.marginal_log_mean_coeff(t) + x_t_ = ( + (torch.exp(log_alpha_t - log_alpha_prev_0)) * x + - (sigma_t * h_phi_1) * model_prev_0 + ) + # now predictor + x_t = x_t_ + if len(D1s) > 0: + # compute the residuals for predictor + for k in range(K - 1): + x_t = x_t - sigma_t * h_phi_ks[k + 1] * torch.einsum('bkchw,k->bchw', D1s, A_p[k]) + # now corrector + if use_corrector: + model_t = self.model_fn(x_t, t) + D1_t = (model_t - model_prev_0) + x_t = x_t_ + k = 0 + for k in range(K - 1): + x_t = x_t - sigma_t * h_phi_ks[k + 1] * torch.einsum('bkchw,k->bchw', D1s, A_c[k][:-1]) + x_t = x_t - sigma_t * h_phi_ks[K] * (D1_t * A_c[k][-1]) + return x_t, model_t + + def multistep_uni_pc_bh_update(self, x, model_prev_list, t_prev_list, t, order, x_t=None, use_corrector=True): + print(f'using unified predictor-corrector with order {order} (solver type: B(h))') + ns = self.noise_schedule + assert order <= len(model_prev_list) + dims = x.dim() + + # first compute rks + t_prev_0 = t_prev_list[-1] + lambda_prev_0 = ns.marginal_lambda(t_prev_0) + lambda_t = ns.marginal_lambda(t) + model_prev_0 = model_prev_list[-1] + sigma_prev_0, sigma_t = ns.marginal_std(t_prev_0), ns.marginal_std(t) + log_alpha_prev_0, log_alpha_t = ns.marginal_log_mean_coeff(t_prev_0), ns.marginal_log_mean_coeff(t) + alpha_t = torch.exp(log_alpha_t) + + h = lambda_t - lambda_prev_0 + + rks = [] + D1s = [] + for i in range(1, order): + t_prev_i = t_prev_list[-(i + 1)] + model_prev_i = model_prev_list[-(i + 1)] + lambda_prev_i = ns.marginal_lambda(t_prev_i) + rk = ((lambda_prev_i - lambda_prev_0) / h)[0] + rks.append(rk) + D1s.append((model_prev_i - model_prev_0) / rk) + + rks.append(1.) + rks = torch.tensor(rks, device=x.device) + + R = [] + b = [] + + hh = -h[0] if self.predict_x0 else h[0] + h_phi_1 = torch.expm1(hh) # h\phi_1(h) = e^h - 1 + h_phi_k = h_phi_1 / hh - 1 + + factorial_i = 1 + + if self.variant == 'bh1': + B_h = hh + elif self.variant == 'bh2': + B_h = torch.expm1(hh) + else: + raise NotImplementedError() + + for i in range(1, order + 1): + R.append(torch.pow(rks, i - 1)) + b.append(h_phi_k * factorial_i / B_h) + factorial_i *= (i + 1) + h_phi_k = h_phi_k / hh - 1 / factorial_i + + R = torch.stack(R) + b = torch.tensor(b, device=x.device) + + # now predictor + use_predictor = len(D1s) > 0 and x_t is None + if len(D1s) > 0: + D1s = torch.stack(D1s, dim=1) # (B, K) + if x_t is None: + # for order 2, we use a simplified version + if order == 2: + rhos_p = torch.tensor([0.5], device=b.device) + else: + rhos_p = torch.linalg.solve(R[:-1, :-1], b[:-1]) + else: + D1s = None + + if use_corrector: + print('using corrector') + # for order 1, we use a simplified version + if order == 1: + rhos_c = torch.tensor([0.5], device=b.device) + else: + rhos_c = torch.linalg.solve(R, b) + + model_t = None + if self.predict_x0: + x_t_ = ( + expand_dims(sigma_t / sigma_prev_0, dims) * x + - expand_dims(alpha_t * h_phi_1, dims)* model_prev_0 + ) + + if x_t is None: + if use_predictor: + pred_res = torch.einsum('k,bkchw->bchw', rhos_p, D1s) + else: + pred_res = 0 + x_t = x_t_ - expand_dims(alpha_t * B_h, dims) * pred_res + + if use_corrector: + model_t = self.model_fn(x_t, t) + if D1s is not None: + corr_res = torch.einsum('k,bkchw->bchw', rhos_c[:-1], D1s) + else: + corr_res = 0 + D1_t = (model_t - model_prev_0) + x_t = x_t_ - expand_dims(alpha_t * B_h, dims) * (corr_res + rhos_c[-1] * D1_t) + else: + x_t_ = ( + expand_dims(torch.exp(log_alpha_t - log_alpha_prev_0), dimss) * x + - expand_dims(sigma_t * h_phi_1, dims) * model_prev_0 + ) + if x_t is None: + if use_predictor: + pred_res = torch.einsum('k,bkchw->bchw', rhos_p, D1s) + else: + pred_res = 0 + x_t = x_t_ - expand_dims(sigma_t * B_h, dims) * pred_res + + if use_corrector: + model_t = self.model_fn(x_t, t) + if D1s is not None: + corr_res = torch.einsum('k,bkchw->bchw', rhos_c[:-1], D1s) + else: + corr_res = 0 + D1_t = (model_t - model_prev_0) + x_t = x_t_ - expand_dims(sigma_t * B_h, dims) * (corr_res + rhos_c[-1] * D1_t) + return x_t, model_t + + + def sample(self, x, steps=20, t_start=None, t_end=None, order=3, skip_type='time_uniform', + method='singlestep', lower_order_final=True, denoise_to_zero=False, solver_type='dpm_solver', + atol=0.0078, rtol=0.05, corrector=False, + ): + t_0 = 1. / self.noise_schedule.total_N if t_end is None else t_end + t_T = self.noise_schedule.T if t_start is None else t_start + device = x.device + if method == 'multistep': + assert steps >= order + timesteps = self.get_time_steps(skip_type=skip_type, t_T=t_T, t_0=t_0, N=steps, device=device) + assert timesteps.shape[0] - 1 == steps + with torch.no_grad(): + vec_t = timesteps[0].expand((x.shape[0])) + model_prev_list = [self.model_fn(x, vec_t)] + t_prev_list = [vec_t] + # Init the first `order` values by lower order multistep DPM-Solver. + for init_order in range(1, order): + vec_t = timesteps[init_order].expand(x.shape[0]) + x, model_x = self.multistep_uni_pc_update(x, model_prev_list, t_prev_list, vec_t, init_order, use_corrector=True) + if model_x is None: + model_x = self.model_fn(x, vec_t) + model_prev_list.append(model_x) + t_prev_list.append(vec_t) + for step in range(order, steps + 1): + vec_t = timesteps[step].expand(x.shape[0]) + if lower_order_final: + step_order = min(order, steps + 1 - step) + else: + step_order = order + print('this step order:', step_order) + if step == steps: + print('do not run corrector at the last step') + use_corrector = False + else: + use_corrector = True + x, model_x = self.multistep_uni_pc_update(x, model_prev_list, t_prev_list, vec_t, step_order, use_corrector=use_corrector) + for i in range(order - 1): + t_prev_list[i] = t_prev_list[i + 1] + model_prev_list[i] = model_prev_list[i + 1] + t_prev_list[-1] = vec_t + # We do not need to evaluate the final model value. + if step < steps: + if model_x is None: + model_x = self.model_fn(x, vec_t) + model_prev_list[-1] = model_x + else: + raise NotImplementedError() + if denoise_to_zero: + x = self.denoise_to_zero_fn(x, torch.ones((x.shape[0],)).to(device) * t_0) + return x + + +############################################################# +# other utility functions +############################################################# + +def interpolate_fn(x, xp, yp): + """ + A piecewise linear function y = f(x), using xp and yp as keypoints. + We implement f(x) in a differentiable way (i.e. applicable for autograd). + The function f(x) is well-defined for all x-axis. (For x beyond the bounds of xp, we use the outmost points of xp to define the linear function.) + + Args: + x: PyTorch tensor with shape [N, C], where N is the batch size, C is the number of channels (we use C = 1 for DPM-Solver). + xp: PyTorch tensor with shape [C, K], where K is the number of keypoints. + yp: PyTorch tensor with shape [C, K]. + Returns: + The function values f(x), with shape [N, C]. + """ + N, K = x.shape[0], xp.shape[1] + all_x = torch.cat([x.unsqueeze(2), xp.unsqueeze(0).repeat((N, 1, 1))], dim=2) + sorted_all_x, x_indices = torch.sort(all_x, dim=2) + x_idx = torch.argmin(x_indices, dim=2) + cand_start_idx = x_idx - 1 + start_idx = torch.where( + torch.eq(x_idx, 0), + torch.tensor(1, device=x.device), + torch.where( + torch.eq(x_idx, K), torch.tensor(K - 2, device=x.device), cand_start_idx, + ), + ) + end_idx = torch.where(torch.eq(start_idx, cand_start_idx), start_idx + 2, start_idx + 1) + start_x = torch.gather(sorted_all_x, dim=2, index=start_idx.unsqueeze(2)).squeeze(2) + end_x = torch.gather(sorted_all_x, dim=2, index=end_idx.unsqueeze(2)).squeeze(2) + start_idx2 = torch.where( + torch.eq(x_idx, 0), + torch.tensor(0, device=x.device), + torch.where( + torch.eq(x_idx, K), torch.tensor(K - 2, device=x.device), cand_start_idx, + ), + ) + y_positions_expanded = yp.unsqueeze(0).expand(N, -1, -1) + start_y = torch.gather(y_positions_expanded, dim=2, index=start_idx2.unsqueeze(2)).squeeze(2) + end_y = torch.gather(y_positions_expanded, dim=2, index=(start_idx2 + 1).unsqueeze(2)).squeeze(2) + cand = start_y + (x - start_x) * (end_y - start_y) / (end_x - start_x) + return cand + + +def expand_dims(v, dims): + """ + Expand the tensor `v` to the dim `dims`. + + Args: + `v`: a PyTorch tensor with shape [N]. + `dim`: a `int`. + Returns: + a PyTorch tensor with shape [N, 1, 1, ..., 1] and the total dimension is `dims`. + """ + return v[(...,) + (None,)*(dims - 1)] diff --git a/modules/processing.py b/modules/processing.py index e1b53ac0..11e726df 100644 --- a/modules/processing.py +++ b/modules/processing.py @@ -884,7 +884,7 @@ class StableDiffusionProcessingTxt2Img(StableDiffusionProcessing): shared.state.nextjob() - img2img_sampler_name = self.sampler_name if self.sampler_name != 'PLMS' else 'DDIM' # PLMS does not support img2img so we just silently switch ot DDIM + img2img_sampler_name = 'DDIM' # PLMS does not support img2img so we just silently switch ot DDIM self.sampler = sd_samplers.create_sampler(img2img_sampler_name, self.sd_model) samples = samples[:, :, self.truncate_y//2:samples.shape[2]-(self.truncate_y+1)//2, self.truncate_x//2:samples.shape[3]-(self.truncate_x+1)//2] diff --git a/modules/sd_samplers_compvis.py b/modules/sd_samplers_compvis.py index d03131cd..86fa1c5b 100644 --- a/modules/sd_samplers_compvis.py +++ b/modules/sd_samplers_compvis.py @@ -7,19 +7,27 @@ import torch from modules.shared import state from modules import sd_samplers_common, prompt_parser, shared +import modules.models.diffusion.uni_pc samplers_data_compvis = [ sd_samplers_common.SamplerData('DDIM', lambda model: VanillaStableDiffusionSampler(ldm.models.diffusion.ddim.DDIMSampler, model), [], {}), sd_samplers_common.SamplerData('PLMS', lambda model: VanillaStableDiffusionSampler(ldm.models.diffusion.plms.PLMSSampler, model), [], {}), + sd_samplers_common.SamplerData('UniPC', lambda model: VanillaStableDiffusionSampler(modules.models.diffusion.uni_pc.UniPCSampler, model), [], {}), ] class VanillaStableDiffusionSampler: def __init__(self, constructor, sd_model): self.sampler = constructor(sd_model) + self.is_ddim = hasattr(self.sampler, 'p_sample_ddim') self.is_plms = hasattr(self.sampler, 'p_sample_plms') - self.orig_p_sample_ddim = self.sampler.p_sample_plms if self.is_plms else self.sampler.p_sample_ddim + self.is_unipc = isinstance(self.sampler, modules.models.diffusion.uni_pc.UniPCSampler) + self.orig_p_sample_ddim = None + if self.is_plms: + self.orig_p_sample_ddim = self.sampler.p_sample_plms + elif self.is_ddim: + self.orig_p_sample_ddim = self.sampler.p_sample_ddim self.mask = None self.nmask = None self.init_latent = None @@ -45,6 +53,15 @@ class VanillaStableDiffusionSampler: return self.last_latent def p_sample_ddim_hook(self, x_dec, cond, ts, unconditional_conditioning, *args, **kwargs): + x_dec, ts, cond, unconditional_conditioning = self.before_sample(x_dec, ts, cond, unconditional_conditioning) + + res = self.orig_p_sample_ddim(x_dec, cond, ts, unconditional_conditioning=unconditional_conditioning, *args, **kwargs) + + x_dec, ts, cond, unconditional_conditioning, res = self.after_sample(x_dec, ts, cond, unconditional_conditioning, res) + + return res + + def before_sample(self, x, ts, cond, unconditional_conditioning): if state.interrupted or state.skipped: raise sd_samplers_common.InterruptedException @@ -76,7 +93,7 @@ class VanillaStableDiffusionSampler: if self.mask is not None: img_orig = self.sampler.model.q_sample(self.init_latent, ts) - x_dec = img_orig * self.mask + self.nmask * x_dec + x = img_orig * self.mask + self.nmask * x # Wrap the image conditioning back up since the DDIM code can accept the dict directly. # Note that they need to be lists because it just concatenates them later. @@ -84,7 +101,13 @@ class VanillaStableDiffusionSampler: cond = {"c_concat": [image_conditioning], "c_crossattn": [cond]} unconditional_conditioning = {"c_concat": [image_conditioning], "c_crossattn": [unconditional_conditioning]} - res = self.orig_p_sample_ddim(x_dec, cond, ts, unconditional_conditioning=unconditional_conditioning, *args, **kwargs) + return x, ts, cond, unconditional_conditioning + + def after_sample(self, x, ts, cond, uncond, res): + if self.is_unipc: + # unipc model_fn returns (pred_x0) + # p_sample_ddim returns (x_prev, pred_x0) + res = (None, res[0]) if self.mask is not None: self.last_latent = self.init_latent * self.mask + self.nmask * res[1] @@ -97,7 +120,7 @@ class VanillaStableDiffusionSampler: state.sampling_step = self.step shared.total_tqdm.update() - return res + return x, ts, cond, uncond, res def initialize(self, p): self.eta = p.eta if p.eta is not None else shared.opts.eta_ddim @@ -107,12 +130,14 @@ class VanillaStableDiffusionSampler: for fieldname in ['p_sample_ddim', 'p_sample_plms']: if hasattr(self.sampler, fieldname): setattr(self.sampler, fieldname, self.p_sample_ddim_hook) + if self.is_unipc: + self.sampler.set_hooks(lambda x, t, c, u: self.before_sample(x, t, c, u), lambda x, t, c, u, r: self.after_sample(x, t, c, u, r)) self.mask = p.mask if hasattr(p, 'mask') else None self.nmask = p.nmask if hasattr(p, 'nmask') else None def adjust_steps_if_invalid(self, p, num_steps): - if (self.config.name == 'DDIM' and p.ddim_discretize == 'uniform') or (self.config.name == 'PLMS'): + if ((self.config.name == 'DDIM' or self.config.name == "UniPC") and p.ddim_discretize == 'uniform') or (self.config.name == 'PLMS'): valid_step = 999 / (1000 // num_steps) if valid_step == math.floor(valid_step): return int(valid_step) + 1 diff --git a/test/basic_features/txt2img_test.py b/test/basic_features/txt2img_test.py index 5aa43a44..cb525fbb 100644 --- a/test/basic_features/txt2img_test.py +++ b/test/basic_features/txt2img_test.py @@ -66,6 +66,8 @@ class TestTxt2ImgWorking(unittest.TestCase): self.assertEqual(requests.post(self.url_txt2img, json=self.simple_txt2img).status_code, 200) self.simple_txt2img["sampler_index"] = "DDIM" self.assertEqual(requests.post(self.url_txt2img, json=self.simple_txt2img).status_code, 200) + self.simple_txt2img["sampler_index"] = "UniPC" + self.assertEqual(requests.post(self.url_txt2img, json=self.simple_txt2img).status_code, 200) def test_txt2img_multiple_batches_performed(self): self.simple_txt2img["n_iter"] = 2 From 21880eb9e57b884635a07d2360831b4186afddf4 Mon Sep 17 00:00:00 2001 From: space-nuko <24979496+space-nuko@users.noreply.github.com> Date: Fri, 10 Feb 2023 04:47:08 -0800 Subject: [PATCH 026/278] Fix logspam and live previews --- modules/models/diffusion/uni_pc/sampler.py | 20 ++++++++++---- modules/models/diffusion/uni_pc/uni_pc.py | 32 ++++++++++------------ modules/sd_samplers_compvis.py | 20 ++++++++------ 3 files changed, 41 insertions(+), 31 deletions(-) diff --git a/modules/models/diffusion/uni_pc/sampler.py b/modules/models/diffusion/uni_pc/sampler.py index 7cccd8a2..219e9862 100644 --- a/modules/models/diffusion/uni_pc/sampler.py +++ b/modules/models/diffusion/uni_pc/sampler.py @@ -19,9 +19,10 @@ class UniPCSampler(object): attr = attr.to(torch.device("cuda")) setattr(self, name, attr) - def set_hooks(self, before, after): - self.before_sample = before - self.after_sample = after + def set_hooks(self, before_sample, after_sample, after_update): + self.before_sample = before_sample + self.after_sample = after_sample + self.after_update = after_update @torch.no_grad() def sample(self, @@ -50,9 +51,17 @@ class UniPCSampler(object): ): if conditioning is not None: if isinstance(conditioning, dict): - cbs = conditioning[list(conditioning.keys())[0]].shape[0] + ctmp = conditioning[list(conditioning.keys())[0]] + while isinstance(ctmp, list): ctmp = ctmp[0] + cbs = ctmp.shape[0] if cbs != batch_size: print(f"Warning: Got {cbs} conditionings but batch-size is {batch_size}") + + elif isinstance(conditioning, list): + for ctmp in conditioning: + if ctmp.shape[0] != batch_size: + print(f"Warning: Got {cbs} conditionings but batch-size is {batch_size}") + else: if conditioning.shape[0] != batch_size: print(f"Warning: Got {conditioning.shape[0]} conditionings but batch-size is {batch_size}") @@ -60,6 +69,7 @@ class UniPCSampler(object): # sampling C, H, W = shape size = (batch_size, C, H, W) + print(f'Data shape for UniPC sampling is {size}, eta {eta}') device = self.model.betas.device if x_T is None: @@ -79,7 +89,7 @@ class UniPCSampler(object): guidance_scale=unconditional_guidance_scale, ) - uni_pc = UniPC(model_fn, ns, predict_x0=True, thresholding=False, condition=conditioning, unconditional_condition=unconditional_conditioning, before_sample=self.before_sample, after_sample=self.after_sample) + uni_pc = UniPC(model_fn, ns, predict_x0=True, thresholding=False, condition=conditioning, unconditional_condition=unconditional_conditioning, before_sample=self.before_sample, after_sample=self.after_sample, after_update=self.after_update) x = uni_pc.sample(img, steps=S, skip_type="time_uniform", method="multistep", order=3, lower_order_final=True) return x.to(device), None diff --git a/modules/models/diffusion/uni_pc/uni_pc.py b/modules/models/diffusion/uni_pc/uni_pc.py index ec6b37da..31ee81a6 100644 --- a/modules/models/diffusion/uni_pc/uni_pc.py +++ b/modules/models/diffusion/uni_pc/uni_pc.py @@ -378,7 +378,8 @@ class UniPC: condition=None, unconditional_condition=None, before_sample=None, - after_sample=None + after_sample=None, + after_update=None ): """Construct a UniPC. @@ -394,6 +395,7 @@ class UniPC: self.unconditional_condition = unconditional_condition self.before_sample = before_sample self.after_sample = after_sample + self.after_update = after_update def dynamic_thresholding_fn(self, x0, t=None): """ @@ -434,15 +436,6 @@ class UniPC: noise = self.noise_prediction_fn(x, t) dims = x.dim() alpha_t, sigma_t = self.noise_schedule.marginal_alpha(t), self.noise_schedule.marginal_std(t) - from pprint import pp - print("X:") - pp(x) - print("sigma_t:") - pp(sigma_t) - print("noise:") - pp(noise) - print("alpha_t:") - pp(alpha_t) x0 = (x - expand_dims(sigma_t, dims) * noise) / expand_dims(alpha_t, dims) if self.thresholding: p = 0.995 # A hyperparameter in the paper of "Imagen" [1]. @@ -524,7 +517,7 @@ class UniPC: return self.multistep_uni_pc_vary_update(x, model_prev_list, t_prev_list, t, order, **kwargs) def multistep_uni_pc_vary_update(self, x, model_prev_list, t_prev_list, t, order, use_corrector=True): - print(f'using unified predictor-corrector with order {order} (solver type: vary coeff)') + #print(f'using unified predictor-corrector with order {order} (solver type: vary coeff)') ns = self.noise_schedule assert order <= len(model_prev_list) @@ -568,7 +561,7 @@ class UniPC: A_p = C_inv_p if use_corrector: - print('using corrector') + #print('using corrector') C_inv = torch.linalg.inv(C) A_c = C_inv @@ -627,7 +620,7 @@ class UniPC: return x_t, model_t def multistep_uni_pc_bh_update(self, x, model_prev_list, t_prev_list, t, order, x_t=None, use_corrector=True): - print(f'using unified predictor-corrector with order {order} (solver type: B(h))') + #print(f'using unified predictor-corrector with order {order} (solver type: B(h))') ns = self.noise_schedule assert order <= len(model_prev_list) dims = x.dim() @@ -695,7 +688,7 @@ class UniPC: D1s = None if use_corrector: - print('using corrector') + #print('using corrector') # for order 1, we use a simplified version if order == 1: rhos_c = torch.tensor([0.5], device=b.device) @@ -755,8 +748,9 @@ class UniPC: t_T = self.noise_schedule.T if t_start is None else t_start device = x.device if method == 'multistep': - assert steps >= order + assert steps >= order, "UniPC order must be < sampling steps" timesteps = self.get_time_steps(skip_type=skip_type, t_T=t_T, t_0=t_0, N=steps, device=device) + print(f"Running UniPC Sampling with {timesteps.shape[0]} timesteps") assert timesteps.shape[0] - 1 == steps with torch.no_grad(): vec_t = timesteps[0].expand((x.shape[0])) @@ -768,6 +762,8 @@ class UniPC: x, model_x = self.multistep_uni_pc_update(x, model_prev_list, t_prev_list, vec_t, init_order, use_corrector=True) if model_x is None: model_x = self.model_fn(x, vec_t) + if self.after_update is not None: + self.after_update(x, model_x) model_prev_list.append(model_x) t_prev_list.append(vec_t) for step in range(order, steps + 1): @@ -776,13 +772,15 @@ class UniPC: step_order = min(order, steps + 1 - step) else: step_order = order - print('this step order:', step_order) + #print('this step order:', step_order) if step == steps: - print('do not run corrector at the last step') + #print('do not run corrector at the last step') use_corrector = False else: use_corrector = True x, model_x = self.multistep_uni_pc_update(x, model_prev_list, t_prev_list, vec_t, step_order, use_corrector=use_corrector) + if self.after_update is not None: + self.after_update(x, model_x) for i in range(order - 1): t_prev_list[i] = t_prev_list[i + 1] model_prev_list[i] = model_prev_list[i + 1] diff --git a/modules/sd_samplers_compvis.py b/modules/sd_samplers_compvis.py index 86fa1c5b..946079ae 100644 --- a/modules/sd_samplers_compvis.py +++ b/modules/sd_samplers_compvis.py @@ -103,16 +103,11 @@ class VanillaStableDiffusionSampler: return x, ts, cond, unconditional_conditioning - def after_sample(self, x, ts, cond, uncond, res): - if self.is_unipc: - # unipc model_fn returns (pred_x0) - # p_sample_ddim returns (x_prev, pred_x0) - res = (None, res[0]) - + def update_step(self, last_latent): if self.mask is not None: - self.last_latent = self.init_latent * self.mask + self.nmask * res[1] + self.last_latent = self.init_latent * self.mask + self.nmask * last_latent else: - self.last_latent = res[1] + self.last_latent = last_latent sd_samplers_common.store_latent(self.last_latent) @@ -120,8 +115,15 @@ class VanillaStableDiffusionSampler: state.sampling_step = self.step shared.total_tqdm.update() + def after_sample(self, x, ts, cond, uncond, res): + if not self.is_unipc: + self.update_step(res[1]) + return x, ts, cond, uncond, res + def unipc_after_update(self, x, model_x): + self.update_step(x) + def initialize(self, p): self.eta = p.eta if p.eta is not None else shared.opts.eta_ddim if self.eta != 0.0: @@ -131,7 +133,7 @@ class VanillaStableDiffusionSampler: if hasattr(self.sampler, fieldname): setattr(self.sampler, fieldname, self.p_sample_ddim_hook) if self.is_unipc: - self.sampler.set_hooks(lambda x, t, c, u: self.before_sample(x, t, c, u), lambda x, t, c, u, r: self.after_sample(x, t, c, u, r)) + self.sampler.set_hooks(lambda x, t, c, u: self.before_sample(x, t, c, u), lambda x, t, c, u, r: self.after_sample(x, t, c, u, r), lambda x, mx: self.unipc_after_update(x, mx)) self.mask = p.mask if hasattr(p, 'mask') else None self.nmask = p.nmask if hasattr(p, 'nmask') else None From c88dcc20d495dab4be2692bdff30277112dbe416 Mon Sep 17 00:00:00 2001 From: space-nuko <24979496+space-nuko@users.noreply.github.com> Date: Fri, 10 Feb 2023 05:00:09 -0800 Subject: [PATCH 027/278] UniPC does not support img2img (for now) --- modules/processing.py | 2 +- modules/sd_samplers.py | 2 +- modules/sd_samplers_compvis.py | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/modules/processing.py b/modules/processing.py index 11e726df..b7cf5357 100644 --- a/modules/processing.py +++ b/modules/processing.py @@ -884,7 +884,7 @@ class StableDiffusionProcessingTxt2Img(StableDiffusionProcessing): shared.state.nextjob() - img2img_sampler_name = 'DDIM' # PLMS does not support img2img so we just silently switch ot DDIM + img2img_sampler_name = 'DDIM' # PLMS/UniPC does not support img2img so we just silently switch ot DDIM self.sampler = sd_samplers.create_sampler(img2img_sampler_name, self.sd_model) samples = samples[:, :, self.truncate_y//2:samples.shape[2]-(self.truncate_y+1)//2, self.truncate_x//2:samples.shape[3]-(self.truncate_x+1)//2] diff --git a/modules/sd_samplers.py b/modules/sd_samplers.py index 28c2136f..ff361f22 100644 --- a/modules/sd_samplers.py +++ b/modules/sd_samplers.py @@ -32,7 +32,7 @@ def set_samplers(): global samplers, samplers_for_img2img hidden = set(shared.opts.hide_samplers) - hidden_img2img = set(shared.opts.hide_samplers + ['PLMS']) + hidden_img2img = set(shared.opts.hide_samplers + ['PLMS', 'UniPC']) samplers = [x for x in all_samplers if x.name not in hidden] samplers_for_img2img = [x for x in all_samplers if x.name not in hidden_img2img] diff --git a/modules/sd_samplers_compvis.py b/modules/sd_samplers_compvis.py index 946079ae..ad39ab2b 100644 --- a/modules/sd_samplers_compvis.py +++ b/modules/sd_samplers_compvis.py @@ -139,7 +139,7 @@ class VanillaStableDiffusionSampler: self.nmask = p.nmask if hasattr(p, 'nmask') else None def adjust_steps_if_invalid(self, p, num_steps): - if ((self.config.name == 'DDIM' or self.config.name == "UniPC") and p.ddim_discretize == 'uniform') or (self.config.name == 'PLMS'): + if ((self.config.name == 'DDIM') and p.ddim_discretize == 'uniform') or (self.config.name == 'PLMS') or (self.config.name == 'UniPC'): valid_step = 999 / (1000 // num_steps) if valid_step == math.floor(valid_step): return int(valid_step) + 1 From 79ffb9453f8eddbdd4e316b9d9c75812b0eea4e1 Mon Sep 17 00:00:00 2001 From: space-nuko <24979496+space-nuko@users.noreply.github.com> Date: Fri, 10 Feb 2023 05:27:05 -0800 Subject: [PATCH 028/278] Add UniPC sampler settings --- modules/models/diffusion/uni_pc/sampler.py | 5 +++-- modules/models/diffusion/uni_pc/uni_pc.py | 2 +- modules/shared.py | 5 +++++ scripts/xyz_grid.py | 7 +++++++ 4 files changed, 16 insertions(+), 3 deletions(-) diff --git a/modules/models/diffusion/uni_pc/sampler.py b/modules/models/diffusion/uni_pc/sampler.py index 219e9862..e66a21e3 100644 --- a/modules/models/diffusion/uni_pc/sampler.py +++ b/modules/models/diffusion/uni_pc/sampler.py @@ -3,6 +3,7 @@ import torch from .uni_pc import NoiseScheduleVP, model_wrapper, UniPC +from modules import shared class UniPCSampler(object): def __init__(self, model, **kwargs): @@ -89,7 +90,7 @@ class UniPCSampler(object): guidance_scale=unconditional_guidance_scale, ) - uni_pc = UniPC(model_fn, ns, predict_x0=True, thresholding=False, condition=conditioning, unconditional_condition=unconditional_conditioning, before_sample=self.before_sample, after_sample=self.after_sample, after_update=self.after_update) - x = uni_pc.sample(img, steps=S, skip_type="time_uniform", method="multistep", order=3, lower_order_final=True) + uni_pc = UniPC(model_fn, ns, predict_x0=True, thresholding=shared.opts.uni_pc_thresholding, variant=shared.opts.uni_pc_variant, condition=conditioning, unconditional_condition=unconditional_conditioning, before_sample=self.before_sample, after_sample=self.after_sample, after_update=self.after_update) + x = uni_pc.sample(img, steps=S, skip_type=shared.opts.uni_pc_skip_type, method="multistep", order=shared.opts.uni_pc_order, lower_order_final=shared.opts.uni_pc_lower_order_final) return x.to(device), None diff --git a/modules/models/diffusion/uni_pc/uni_pc.py b/modules/models/diffusion/uni_pc/uni_pc.py index 31ee81a6..df63d1bc 100644 --- a/modules/models/diffusion/uni_pc/uni_pc.py +++ b/modules/models/diffusion/uni_pc/uni_pc.py @@ -750,7 +750,7 @@ class UniPC: if method == 'multistep': assert steps >= order, "UniPC order must be < sampling steps" timesteps = self.get_time_steps(skip_type=skip_type, t_T=t_T, t_0=t_0, N=steps, device=device) - print(f"Running UniPC Sampling with {timesteps.shape[0]} timesteps") + print(f"Running UniPC Sampling with {timesteps.shape[0]} timesteps, order {order}") assert timesteps.shape[0] - 1 == steps with torch.no_grad(): vec_t = timesteps[0].expand((x.shape[0])) diff --git a/modules/shared.py b/modules/shared.py index 79fbf724..34242073 100644 --- a/modules/shared.py +++ b/modules/shared.py @@ -480,6 +480,11 @@ options_templates.update(options_section(('sampler-params', "Sampler parameters" 's_noise': OptionInfo(1.0, "sigma noise", gr.Slider, {"minimum": 0.0, "maximum": 1.0, "step": 0.01}), 'eta_noise_seed_delta': OptionInfo(0, "Eta noise seed delta", gr.Number, {"precision": 0}), 'always_discard_next_to_last_sigma': OptionInfo(False, "Always discard next-to-last sigma"), + 'uni_pc_variant': OptionInfo("bh1", "UniPC variant", gr.Radio, {"choices": ["bh1", "vary_coeff"]}), + 'uni_pc_skip_type': OptionInfo("time_uniform", "UniPC skip type", gr.Radio, {"choices": ["time_uniform", "time_quadratic", "logSNR"]}), + 'uni_pc_order': OptionInfo(3, "UniPC order (must be < sampling steps)", gr.Slider, {"minimum": 1, "maximum": 150 - 1, "step": 1}), + 'uni_pc_thresholding': OptionInfo(False, "UniPC thresholding"), + 'uni_pc_lower_order_final': OptionInfo(True, "UniPC lower order final"), })) options_templates.update(options_section(('postprocessing', "Postprocessing"), { diff --git a/scripts/xyz_grid.py b/scripts/xyz_grid.py index 5982cfba..72421e0c 100644 --- a/scripts/xyz_grid.py +++ b/scripts/xyz_grid.py @@ -126,6 +126,10 @@ def apply_styles(p: StableDiffusionProcessingTxt2Img, x: str, _): p.styles.extend(x.split(',')) +def apply_uni_pc_order(p, x, xs): + opts.data["uni_pc_order"] = min(x, p.steps - 1) + + def format_value_add_label(p, opt, x): if type(x) == float: x = round(x, 8) @@ -202,6 +206,7 @@ axis_options = [ AxisOptionImg2Img("Cond. Image Mask Weight", float, apply_field("inpainting_mask_weight")), AxisOption("VAE", str, apply_vae, cost=0.7, choices=lambda: 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), ] @@ -310,9 +315,11 @@ 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 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 06cb0dc92095647e4856be10b4d7dc12f5e11fa1 Mon Sep 17 00:00:00 2001 From: space-nuko <24979496+space-nuko@users.noreply.github.com> Date: Fri, 10 Feb 2023 05:36:41 -0800 Subject: [PATCH 029/278] Fix UniPC order --- modules/shared.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/modules/shared.py b/modules/shared.py index 34242073..670d4954 100644 --- a/modules/shared.py +++ b/modules/shared.py @@ -482,7 +482,7 @@ options_templates.update(options_section(('sampler-params', "Sampler parameters" 'always_discard_next_to_last_sigma': OptionInfo(False, "Always discard next-to-last sigma"), 'uni_pc_variant': OptionInfo("bh1", "UniPC variant", gr.Radio, {"choices": ["bh1", "vary_coeff"]}), 'uni_pc_skip_type': OptionInfo("time_uniform", "UniPC skip type", gr.Radio, {"choices": ["time_uniform", "time_quadratic", "logSNR"]}), - 'uni_pc_order': OptionInfo(3, "UniPC order (must be < sampling steps)", gr.Slider, {"minimum": 1, "maximum": 150 - 1, "step": 1}), + 'uni_pc_order': OptionInfo(3, "UniPC order (must be < sampling steps)", gr.Slider, {"minimum": 1, "maximum": 50, "step": 1}), 'uni_pc_thresholding': OptionInfo(False, "UniPC thresholding"), 'uni_pc_lower_order_final': OptionInfo(True, "UniPC lower order final"), })) From bf9b1d64a3101b592713f584d5ef0533b6df1e0f Mon Sep 17 00:00:00 2001 From: missionfloyd Date: Fri, 10 Feb 2023 15:27:08 -0700 Subject: [PATCH 030/278] Fix face restorers setting --- modules/shared.py | 2 +- webui.py | 1 - 2 files changed, 1 insertion(+), 2 deletions(-) diff --git a/modules/shared.py b/modules/shared.py index 79fbf724..8bc6923a 100644 --- a/modules/shared.py +++ b/modules/shared.py @@ -364,7 +364,7 @@ options_templates.update(options_section(('upscaling', "Upscaling"), { })) options_templates.update(options_section(('face-restoration', "Face restoration"), { - "face_restoration_model": OptionInfo(None, "Face restoration model", gr.Radio, lambda: {"choices": [x.name() for x in face_restorers]}), + "face_restoration_model": OptionInfo("CodeFormer", "Face restoration model", gr.Radio, lambda: {"choices": [x.name() for x in face_restorers]}), "code_former_weight": OptionInfo(0.5, "CodeFormer weight parameter; 0 = maximum effect; 1 = minimum effect", gr.Slider, {"minimum": 0, "maximum": 1, "step": 0.01}), "face_restoration_unload": OptionInfo(False, "Move face restoration model from VRAM into RAM after processing"), })) diff --git a/webui.py b/webui.py index 5b5c2139..077c10be 100644 --- a/webui.py +++ b/webui.py @@ -97,7 +97,6 @@ def initialize(): modules.sd_models.setup_model() codeformer.setup_model(cmd_opts.codeformer_models_path) gfpgan.setup_model(cmd_opts.gfpgan_models_path) - shared.face_restorers.append(modules.face_restoration.FaceRestoration()) modelloader.list_builtin_upscalers() modules.scripts.load_scripts() From fb274229b2c5c1a89dac0b3da28c08c92d71fd95 Mon Sep 17 00:00:00 2001 From: space-nuko <24979496+space-nuko@users.noreply.github.com> Date: Fri, 10 Feb 2023 14:30:35 -0800 Subject: [PATCH 031/278] bug fix --- modules/models/diffusion/uni_pc/sampler.py | 2 +- modules/processing.py | 4 +++- 2 files changed, 4 insertions(+), 2 deletions(-) diff --git a/modules/models/diffusion/uni_pc/sampler.py b/modules/models/diffusion/uni_pc/sampler.py index e66a21e3..0bef6eed 100644 --- a/modules/models/diffusion/uni_pc/sampler.py +++ b/modules/models/diffusion/uni_pc/sampler.py @@ -70,7 +70,7 @@ class UniPCSampler(object): # sampling C, H, W = shape size = (batch_size, C, H, W) - print(f'Data shape for UniPC sampling is {size}, eta {eta}') + print(f'Data shape for UniPC sampling is {size}') device = self.model.betas.device if x_T is None: diff --git a/modules/processing.py b/modules/processing.py index b7cf5357..0ca15491 100644 --- a/modules/processing.py +++ b/modules/processing.py @@ -884,7 +884,9 @@ class StableDiffusionProcessingTxt2Img(StableDiffusionProcessing): shared.state.nextjob() - img2img_sampler_name = 'DDIM' # PLMS/UniPC does not support img2img so we just silently switch ot DDIM + img2img_sampler_name = self.sampler_name + if self.sampler_name in ['PLMS', 'UniPC']: # PLMS/UniPC do not support img2img so we just silently switch to DDIM + img2img_sampler_name = 'DDIM' self.sampler = sd_samplers.create_sampler(img2img_sampler_name, self.sd_model) samples = samples[:, :, self.truncate_y//2:samples.shape[2]-(self.truncate_y+1)//2, self.truncate_x//2:samples.shape[3]-(self.truncate_x+1)//2] From 9e27af76d14dc6d8a5062ab9c0db128a917ada17 Mon Sep 17 00:00:00 2001 From: RcINS Date: Sat, 11 Feb 2023 10:12:16 +0800 Subject: [PATCH 032/278] Fix DPM++ SDE not deterministic across different batch sizes (#5210) --- modules/sd_samplers_kdiffusion.py | 37 ++++++++++++++++++++++++------- modules/shared.py | 1 + 2 files changed, 30 insertions(+), 8 deletions(-) diff --git a/modules/sd_samplers_kdiffusion.py b/modules/sd_samplers_kdiffusion.py index f076fc55..d143d41e 100644 --- a/modules/sd_samplers_kdiffusion.py +++ b/modules/sd_samplers_kdiffusion.py @@ -269,6 +269,15 @@ class KDiffusionSampler: return sigmas + def create_noise_sampler(self, x, sigmas, seeds): + """For DPM++ SDE: manually create noise sampler to enable deterministic results across different batch sizes""" + if shared.opts.no_dpmpp_sde_batch_determinism: + return None + + from k_diffusion.sampling import BrownianTreeNoiseSampler + sigma_min, sigma_max = sigmas[sigmas > 0].min(), sigmas.max() + return BrownianTreeNoiseSampler(x, sigma_min, sigma_max, seed=seeds) + 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) @@ -278,18 +287,24 @@ class KDiffusionSampler: xi = x + noise * sigma_sched[0] extra_params_kwargs = self.initialize(p) - if 'sigma_min' in inspect.signature(self.func).parameters: + parameters = inspect.signature(self.func).parameters + + if 'sigma_min' in parameters: ## last sigma is zero which isn't allowed by DPM Fast & Adaptive so taking value before last extra_params_kwargs['sigma_min'] = sigma_sched[-2] - if 'sigma_max' in inspect.signature(self.func).parameters: + if 'sigma_max' in parameters: extra_params_kwargs['sigma_max'] = sigma_sched[0] - if 'n' in inspect.signature(self.func).parameters: + if 'n' in parameters: extra_params_kwargs['n'] = len(sigma_sched) - 1 - if 'sigma_sched' in inspect.signature(self.func).parameters: + if 'sigma_sched' in parameters: extra_params_kwargs['sigma_sched'] = sigma_sched - if 'sigmas' in inspect.signature(self.func).parameters: + if 'sigmas' in parameters: extra_params_kwargs['sigmas'] = sigma_sched + if self.funcname == 'sample_dpmpp_sde': + noise_sampler = self.create_noise_sampler(x, sigmas, p.all_seeds) + extra_params_kwargs['noise_sampler'] = noise_sampler + self.model_wrap_cfg.init_latent = x self.last_latent = x extra_args={ @@ -303,7 +318,7 @@ class KDiffusionSampler: return samples - def sample(self, p, x, conditioning, unconditional_conditioning, steps=None, image_conditioning = None): + def sample(self, p, x, conditioning, unconditional_conditioning, steps=None, image_conditioning=None): steps = steps or p.steps sigmas = self.get_sigmas(p, steps) @@ -311,14 +326,20 @@ class KDiffusionSampler: x = x * sigmas[0] extra_params_kwargs = self.initialize(p) - if 'sigma_min' in inspect.signature(self.func).parameters: + parameters = inspect.signature(self.func).parameters + + if 'sigma_min' in parameters: extra_params_kwargs['sigma_min'] = self.model_wrap.sigmas[0].item() extra_params_kwargs['sigma_max'] = self.model_wrap.sigmas[-1].item() - if 'n' in inspect.signature(self.func).parameters: + if 'n' in parameters: extra_params_kwargs['n'] = steps else: extra_params_kwargs['sigmas'] = sigmas + if self.funcname == 'sample_dpmpp_sde': + noise_sampler = self.create_noise_sampler(x, sigmas, p.all_seeds) + extra_params_kwargs['noise_sampler'] = noise_sampler + self.last_latent = x samples = self.launch_sampling(steps, lambda: self.func(self.model_wrap_cfg, x, extra_args={ 'cond': conditioning, diff --git a/modules/shared.py b/modules/shared.py index 79fbf724..22344431 100644 --- a/modules/shared.py +++ b/modules/shared.py @@ -414,6 +414,7 @@ options_templates.update(options_section(('sd', "Stable Diffusion"), { options_templates.update(options_section(('compatibility', "Compatibility"), { "use_old_emphasis_implementation": OptionInfo(False, "Use old emphasis implementation. Can be useful to reproduce old seeds."), "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)."), })) From b78c5e87baaf8c88d039bf60082c3b5ae35ec4ff Mon Sep 17 00:00:00 2001 From: opparco Date: Sat, 11 Feb 2023 11:18:38 +0900 Subject: [PATCH 033/278] Add cfg_denoised_callback --- modules/script_callbacks.py | 29 +++++++++++++++++++++++++++++ modules/sd_samplers_kdiffusion.py | 4 ++++ 2 files changed, 33 insertions(+) diff --git a/modules/script_callbacks.py b/modules/script_callbacks.py index 4bb45ec7..edd0e2a7 100644 --- a/modules/script_callbacks.py +++ b/modules/script_callbacks.py @@ -46,6 +46,18 @@ class CFGDenoiserParams: """Total number of sampling steps planned""" +class CFGDenoisedParams: + def __init__(self, x, sampling_step, total_sampling_steps): + self.x = x + """Latent image representation in the process of being denoised""" + + self.sampling_step = sampling_step + """Current Sampling step number""" + + self.total_sampling_steps = total_sampling_steps + """Total number of sampling steps planned""" + + class UiTrainTabParams: def __init__(self, txt2img_preview_params): self.txt2img_preview_params = txt2img_preview_params @@ -68,6 +80,7 @@ callback_map = dict( callbacks_before_image_saved=[], callbacks_image_saved=[], callbacks_cfg_denoiser=[], + callbacks_cfg_denoised=[], callbacks_before_component=[], callbacks_after_component=[], callbacks_image_grid=[], @@ -150,6 +163,14 @@ def cfg_denoiser_callback(params: CFGDenoiserParams): report_exception(c, 'cfg_denoiser_callback') +def cfg_denoised_callback(params: CFGDenoisedParams): + for c in callback_map['callbacks_cfg_denoised']: + try: + c.callback(params) + except Exception: + report_exception(c, 'cfg_denoised_callback') + + def before_component_callback(component, **kwargs): for c in callback_map['callbacks_before_component']: try: @@ -283,6 +304,14 @@ def on_cfg_denoiser(callback): add_callback(callback_map['callbacks_cfg_denoiser'], callback) +def on_cfg_denoised(callback): + """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) + + def on_before_component(callback): """register a function to be called before a component is created. The callback is called with arguments: diff --git a/modules/sd_samplers_kdiffusion.py b/modules/sd_samplers_kdiffusion.py index f076fc55..28847397 100644 --- a/modules/sd_samplers_kdiffusion.py +++ b/modules/sd_samplers_kdiffusion.py @@ -8,6 +8,7 @@ from modules import prompt_parser, devices, sd_samplers_common from modules.shared import opts, state import modules.shared as shared from modules.script_callbacks import CFGDenoiserParams, cfg_denoiser_callback +from modules.script_callbacks import CFGDenoisedParams, cfg_denoised_callback samplers_k_diffusion = [ ('Euler a', 'sample_euler_ancestral', ['k_euler_a', 'k_euler_ancestral'], {}), @@ -136,6 +137,9 @@ class CFGDenoiser(torch.nn.Module): x_out[-uncond.shape[0]:] = self.inner_model(x_in[-uncond.shape[0]:], sigma_in[-uncond.shape[0]:], cond={"c_crossattn": [uncond], "c_concat": [image_cond_in[-uncond.shape[0]:]]}) + denoised_params = CFGDenoisedParams(x_out, state.sampling_step, state.sampling_steps) + cfg_denoised_callback(denoised_params) + devices.test_for_nans(x_out, "unet") if opts.live_preview_content == "Prompt": From 716a69237cefb385f71105dbbf50e92d664e0f42 Mon Sep 17 00:00:00 2001 From: space-nuko <24979496+space-nuko@users.noreply.github.com> Date: Sat, 11 Feb 2023 06:18:34 -0800 Subject: [PATCH 034/278] support SD2.X models --- modules/models/diffusion/uni_pc/sampler.py | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/modules/models/diffusion/uni_pc/sampler.py b/modules/models/diffusion/uni_pc/sampler.py index 0bef6eed..708a9b2b 100644 --- a/modules/models/diffusion/uni_pc/sampler.py +++ b/modules/models/diffusion/uni_pc/sampler.py @@ -80,10 +80,13 @@ class UniPCSampler(object): ns = NoiseScheduleVP('discrete', alphas_cumprod=self.alphas_cumprod) + # SD 1.X is "noise", SD 2.X is "v" + model_type = "v" if self.model.parameterization == "v" else "noise" + model_fn = model_wrapper( lambda x, t, c: self.model.apply_model(x, t, c), ns, - model_type="noise", + model_type=model_type, guidance_type="classifier-free", #condition=conditioning, #unconditional_condition=unconditional_conditioning, From 0a4917ac4021eb6cf0da27c060c13bdd5b5d2a9f Mon Sep 17 00:00:00 2001 From: space-nuko <24979496+space-nuko@users.noreply.github.com> Date: Mon, 13 Feb 2023 03:33:28 -0800 Subject: [PATCH 035/278] Apply extra networks per-batch instead of per-session (fixes wildcards) --- modules/processing.py | 11 +++++------ 1 file changed, 5 insertions(+), 6 deletions(-) diff --git a/modules/processing.py b/modules/processing.py index e1b53ac0..e4b989d4 100644 --- a/modules/processing.py +++ b/modules/processing.py @@ -543,8 +543,6 @@ def process_images_inner(p: StableDiffusionProcessing) -> Processed: if os.path.exists(cmd_opts.embeddings_dir) and not p.do_not_reload_embeddings: model_hijack.embedding_db.load_textual_inversion_embeddings() - _, extra_network_data = extra_networks.parse_prompts(p.all_prompts[0:1]) - if p.scripts is not None: p.scripts.process(p) @@ -582,9 +580,6 @@ def process_images_inner(p: StableDiffusionProcessing) -> Processed: if shared.opts.live_previews_enable and opts.show_progress_type == "Approx NN": sd_vae_approx.model() - if not p.disable_extra_networks: - extra_networks.activate(p, extra_network_data) - with open(os.path.join(paths.data_path, "params.txt"), "w", encoding="utf8") as file: processed = Processed(p, [], p.seed, "") file.write(processed.infotext(p, 0)) @@ -609,7 +604,11 @@ def process_images_inner(p: StableDiffusionProcessing) -> Processed: if len(prompts) == 0: break - prompts, _ = extra_networks.parse_prompts(prompts) + prompts, extra_network_data = extra_networks.parse_prompts(prompts) + + if not p.disable_extra_networks: + with devices.autocast(): + extra_networks.activate(p, extra_network_data) if p.scripts is not None: p.scripts.process_batch(p, batch_number=n, prompts=prompts, seeds=seeds, subseeds=subseeds) From 7893533674e37de258d647f22b06430e0f924bd7 Mon Sep 17 00:00:00 2001 From: Vladimir Mandic Date: Mon, 13 Feb 2023 11:04:34 -0500 Subject: [PATCH 036/278] add version to extensions table --- modules/extensions.py | 6 ++++++ modules/ui_extensions.py | 8 +++----- 2 files changed, 9 insertions(+), 5 deletions(-) diff --git a/modules/extensions.py b/modules/extensions.py index 5e12b1aa..1975fca1 100644 --- a/modules/extensions.py +++ b/modules/extensions.py @@ -2,6 +2,7 @@ import os import sys import traceback +import time import git from modules import paths, shared @@ -25,6 +26,7 @@ class Extension: self.status = '' self.can_update = False self.is_builtin = is_builtin + self.version = '' repo = None try: @@ -40,6 +42,10 @@ class Extension: try: self.remote = next(repo.remote().urls, None) self.status = 'unknown' + head = repo.head.commit + ts = time.asctime(time.gmtime(repo.head.commit.committed_date)) + self.version = f'{head.hexsha[:7]} ({ts})' + except Exception: self.remote = None diff --git a/modules/ui_extensions.py b/modules/ui_extensions.py index 37d30e1f..bd4308ef 100644 --- a/modules/ui_extensions.py +++ b/modules/ui_extensions.py @@ -80,6 +80,7 @@ def extension_table(): Extension URL + Version Update @@ -87,11 +88,7 @@ def extension_table(): """ for ext in extensions.extensions: - remote = "" - if ext.is_builtin: - remote = "built-in" - elif ext.remote: - remote = f"""{html.escape("built-in" if ext.is_builtin else ext.remote or '')}""" + remote = f"""{html.escape("built-in" if ext.is_builtin else ext.remote or '')}""" if ext.can_update: ext_status = f"""""" @@ -102,6 +99,7 @@ def extension_table(): {remote} + {ext.version} {ext_status} """ From a320d157ec0221fa4e9c756327e31d881b9921ae Mon Sep 17 00:00:00 2001 From: Vladimir Mandic Date: Mon, 13 Feb 2023 20:26:47 -0500 Subject: [PATCH 037/278] all hiding of ui tabs --- modules/shared.py | 1 + modules/ui.py | 3 +++ 2 files changed, 4 insertions(+) diff --git a/modules/shared.py b/modules/shared.py index 79fbf724..ded28925 100644 --- a/modules/shared.py +++ b/modules/shared.py @@ -455,6 +455,7 @@ options_templates.update(options_section(('ui', "User interface"), { "keyedit_precision_attention": OptionInfo(0.1, "Ctrl+up/down precision when editing (attention:1.1)", gr.Slider, {"minimum": 0.01, "maximum": 0.2, "step": 0.001}), "keyedit_precision_extra": OptionInfo(0.05, "Ctrl+up/down precision when editing ", gr.Slider, {"minimum": 0.01, "maximum": 0.2, "step": 0.001}), "quicksettings": OptionInfo("sd_model_checkpoint", "Quicksettings list"), + "hidden_tabs": OptionInfo("", "Hidden UI tabs"), "ui_reorder": OptionInfo(", ".join(ui_reorder_categories), "txt2img/img2img UI item order"), "ui_extra_networks_tab_reorder": OptionInfo("", "Extra networks tab order"), "localization": OptionInfo("None", "Localization (requires restart)", gr.Dropdown, lambda: {"choices": ["None"] + list(localization.localizations.keys())}, refresh=lambda: localization.list_localizations(cmd_opts.localizations_dir)), diff --git a/modules/ui.py b/modules/ui.py index f5df1ffe..c99e55ab 100644 --- a/modules/ui.py +++ b/modules/ui.py @@ -1568,7 +1568,10 @@ def create_ui(): parameters_copypaste.connect_paste_params_buttons() with gr.Tabs(elem_id="tabs") as tabs: + hidden_tabs = [x.lower().strip() for x in shared.opts.hidden_tabs.split(",")] for interface, label, ifid in interfaces: + if label.lower() in hidden_tabs: + continue with gr.TabItem(label, id=ifid, elem_id='tab_' + ifid): interface.render() From 7df7e4d22796fda11629463f2fcbe859b98b1d19 Mon Sep 17 00:00:00 2001 From: space-nuko <24979496+space-nuko@users.noreply.github.com> Date: Tue, 14 Feb 2023 03:55:42 -0800 Subject: [PATCH 038/278] Allow extensions to declare paste fields for "Send to X" buttons --- modules/generation_parameters_copypaste.py | 5 +++-- modules/scripts.py | 9 +++++++++ modules/ui_common.py | 9 ++++++++- 3 files changed, 20 insertions(+), 3 deletions(-) diff --git a/modules/generation_parameters_copypaste.py b/modules/generation_parameters_copypaste.py index fc9e17aa..93d955db 100644 --- a/modules/generation_parameters_copypaste.py +++ b/modules/generation_parameters_copypaste.py @@ -23,13 +23,14 @@ registered_param_bindings = [] class ParamBinding: - def __init__(self, paste_button, tabname, source_text_component=None, source_image_component=None, source_tabname=None, override_settings_component=None): + def __init__(self, paste_button, tabname, source_text_component=None, source_image_component=None, source_tabname=None, override_settings_component=None, paste_field_names=[]): self.paste_button = paste_button self.tabname = tabname self.source_text_component = source_text_component self.source_image_component = source_image_component self.source_tabname = source_tabname self.override_settings_component = override_settings_component + self.paste_field_names = paste_field_names def reset(): @@ -133,7 +134,7 @@ def connect_paste_params_buttons(): connect_paste(binding.paste_button, fields, binding.source_text_component, binding.override_settings_component, binding.tabname) if binding.source_tabname is not None and fields is not None: - paste_field_names = ['Prompt', 'Negative prompt', 'Steps', 'Face restoration'] + (["Seed"] if shared.opts.send_seed else []) + paste_field_names = ['Prompt', 'Negative prompt', 'Steps', 'Face restoration'] + (["Seed"] if shared.opts.send_seed else []) + binding.paste_field_names binding.paste_button.click( fn=lambda *x: x, inputs=[field for field, name in paste_fields[binding.source_tabname]["fields"] if name in paste_field_names], diff --git a/modules/scripts.py b/modules/scripts.py index 24056a12..ac0785ce 100644 --- a/modules/scripts.py +++ b/modules/scripts.py @@ -33,6 +33,11 @@ class Script: parsing infotext to set the value for the component; see ui.py's txt2img_paste_fields for an example """ + paste_field_names = None + """if set in ui(), this is a list of names of infotext fields; the fields will be sent through the + various "Send to " buttons when clicked + """ + def title(self): """this function should return the title of the script. This is what will be displayed in the dropdown menu.""" @@ -256,6 +261,7 @@ class ScriptRunner: self.alwayson_scripts = [] self.titles = [] self.infotext_fields = [] + self.paste_field_names = [] def initialize_scripts(self, is_img2img): from modules import scripts_auto_postprocessing @@ -304,6 +310,9 @@ class ScriptRunner: if script.infotext_fields is not None: self.infotext_fields += script.infotext_fields + if script.paste_field_names is not None: + self.paste_field_names += script.paste_field_names + inputs += controls inputs_alwayson += [script.alwayson for _ in controls] script.args_to = len(inputs) diff --git a/modules/ui_common.py b/modules/ui_common.py index fd047f31..a12433d2 100644 --- a/modules/ui_common.py +++ b/modules/ui_common.py @@ -198,9 +198,16 @@ Requested path was: {f} html_info = gr.HTML(elem_id=f'html_info_{tabname}') html_log = gr.HTML(elem_id=f'html_log_{tabname}') + paste_field_names = [] + if tabname == "txt2img": + paste_field_names = modules.scripts.scripts_txt2img.paste_field_names + elif tabname == "img2img": + paste_field_names = modules.scripts.scripts_img2img.paste_field_names + for paste_tabname, paste_button in buttons.items(): parameters_copypaste.register_paste_params_button(parameters_copypaste.ParamBinding( - paste_button=paste_button, tabname=paste_tabname, source_tabname="txt2img" if tabname == "txt2img" else None, source_image_component=result_gallery + paste_button=paste_button, tabname=paste_tabname, source_tabname="txt2img" if tabname == "txt2img" else None, source_image_component=result_gallery, + paste_field_names=paste_field_names )) return result_gallery, generation_info if tabname != "extras" else html_info_x, html_info, html_log From 1615f786eeb5e407f7414835fbb73e7b6f8337de Mon Sep 17 00:00:00 2001 From: missionfloyd Date: Tue, 14 Feb 2023 20:54:02 -0700 Subject: [PATCH 039/278] Download model if none are found --- README.md | 3 +-- modules/sd_models.py | 2 +- 2 files changed, 2 insertions(+), 3 deletions(-) diff --git a/README.md b/README.md index 2149dcc5..f0dcb104 100644 --- a/README.md +++ b/README.md @@ -104,8 +104,7 @@ Alternatively, use online services (like Google Colab): 1. Install [Python 3.10.6](https://www.python.org/downloads/windows/), checking "Add Python to PATH" 2. Install [git](https://git-scm.com/download/win). 3. Download the stable-diffusion-webui repository, for example by running `git clone https://github.com/AUTOMATIC1111/stable-diffusion-webui.git`. -4. Place stable diffusion checkpoint (`model.ckpt`) in the `models/Stable-diffusion` directory (see [dependencies](https://github.com/AUTOMATIC1111/stable-diffusion-webui/wiki/Dependencies) for where to get it). -5. Run `webui-user.bat` from Windows Explorer as normal, non-administrator, user. +4. Run `webui-user.bat` from Windows Explorer as normal, non-administrator, user. ### Automatic Installation on Linux 1. Install the dependencies: diff --git a/modules/sd_models.py b/modules/sd_models.py index d847d358..07072e5c 100644 --- a/modules/sd_models.py +++ b/modules/sd_models.py @@ -105,7 +105,7 @@ def checkpoint_tiles(): def list_models(): checkpoints_list.clear() checkpoint_alisases.clear() - model_list = modelloader.load_models(model_path=model_path, command_path=shared.cmd_opts.ckpt_dir, ext_filter=[".ckpt", ".safetensors"], ext_blacklist=[".vae.safetensors"]) + model_list = modelloader.load_models(model_path=model_path, model_url="https://huggingface.co/runwayml/stable-diffusion-v1-5/resolve/main/v1-5-pruned-emaonly.safetensors", command_path=shared.cmd_opts.ckpt_dir, ext_filter=[".ckpt", ".safetensors"], download_name="v1-5-pruned-emaonly.safetensors", ext_blacklist=[".vae.safetensors"]) cmd_ckpt = shared.cmd_opts.ckpt if os.path.exists(cmd_ckpt): From f55a7e04d812e8cb07d622efb321abbad54d2d4a Mon Sep 17 00:00:00 2001 From: RcINS Date: Wed, 15 Feb 2023 16:57:18 +0800 Subject: [PATCH 040/278] Fix error when batch count > 1 --- modules/sd_samplers_kdiffusion.py | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/modules/sd_samplers_kdiffusion.py b/modules/sd_samplers_kdiffusion.py index d143d41e..86d657e2 100644 --- a/modules/sd_samplers_kdiffusion.py +++ b/modules/sd_samplers_kdiffusion.py @@ -269,14 +269,15 @@ class KDiffusionSampler: return sigmas - def create_noise_sampler(self, x, sigmas, seeds): + def create_noise_sampler(self, x, sigmas, p): """For DPM++ SDE: manually create noise sampler to enable deterministic results across different batch sizes""" if shared.opts.no_dpmpp_sde_batch_determinism: return None from k_diffusion.sampling import BrownianTreeNoiseSampler sigma_min, sigma_max = sigmas[sigmas > 0].min(), sigmas.max() - return BrownianTreeNoiseSampler(x, sigma_min, sigma_max, seed=seeds) + current_iter_seeds = p.all_seeds[p.iteration * p.batch_size:(p.iteration + 1) * p.batch_size] + return BrownianTreeNoiseSampler(x, sigma_min, sigma_max, seed=current_iter_seeds) 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) @@ -302,7 +303,7 @@ class KDiffusionSampler: extra_params_kwargs['sigmas'] = sigma_sched if self.funcname == 'sample_dpmpp_sde': - noise_sampler = self.create_noise_sampler(x, sigmas, p.all_seeds) + noise_sampler = self.create_noise_sampler(x, sigmas, p) extra_params_kwargs['noise_sampler'] = noise_sampler self.model_wrap_cfg.init_latent = x @@ -337,7 +338,7 @@ class KDiffusionSampler: extra_params_kwargs['sigmas'] = sigmas if self.funcname == 'sample_dpmpp_sde': - noise_sampler = self.create_noise_sampler(x, sigmas, p.all_seeds) + noise_sampler = self.create_noise_sampler(x, sigmas, p) extra_params_kwargs['noise_sampler'] = noise_sampler self.last_latent = x From c4bfd20f317243d7ceac6e2fbf30b18bbebd3e6d Mon Sep 17 00:00:00 2001 From: Shondoit Date: Thu, 12 Jan 2023 15:03:46 +0100 Subject: [PATCH 041/278] Hijack to add weighted_forward to model: return loss * weight map --- modules/sd_hijack.py | 52 ++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 52 insertions(+) diff --git a/modules/sd_hijack.py b/modules/sd_hijack.py index 8fdc5990..57ed5635 100644 --- a/modules/sd_hijack.py +++ b/modules/sd_hijack.py @@ -1,5 +1,6 @@ import torch from torch.nn.functional import silu +from types import MethodType import modules.textual_inversion.textual_inversion from modules import devices, sd_hijack_optimizations, shared, sd_hijack_checkpoint @@ -76,6 +77,54 @@ def fix_checkpoint(): pass +def weighted_loss(sd_model, pred, target, mean=True): + #Calculate the weight normally, but ignore the mean + loss = sd_model._old_get_loss(pred, target, mean=False) + + #Check if we have weights available + weight = getattr(sd_model, '_custom_loss_weight', None) + if weight is not None: + loss *= weight + + #Return the loss, as mean if specified + return loss.mean() if mean else loss + +def weighted_forward(sd_model, x, c, w, *args, **kwargs): + try: + #Temporarily append weights to a place accessible during loss calc + sd_model._custom_loss_weight = w + + #Replace 'get_loss' with a weight-aware one. Otherwise we need to reimplement 'forward' completely + #Keep 'get_loss', but don't overwrite the previous old_get_loss if it's already set + if not hasattr(sd_model, '_old_get_loss'): + sd_model._old_get_loss = sd_model.get_loss + sd_model.get_loss = MethodType(weighted_loss, sd_model) + + #Run the standard forward function, but with the patched 'get_loss' + return sd_model.forward(x, c, *args, **kwargs) + finally: + try: + #Delete temporary weights if appended + del sd_model._custom_loss_weight + except AttributeError as e: + pass + + #If we have an old loss function, reset the loss function to the original one + if hasattr(sd_model, '_old_get_loss'): + sd_model.get_loss = sd_model._old_get_loss + del sd_model._old_get_loss + +def apply_weighted_forward(sd_model): + #Add new function 'weighted_forward' that can be called to calc weighted loss + sd_model.weighted_forward = MethodType(weighted_forward, sd_model) + +def undo_weighted_forward(sd_model): + try: + del sd_model.weighted_forward + except AttributeError as e: + pass + + class StableDiffusionModelHijack: fixes = None comments = [] @@ -104,6 +153,8 @@ class StableDiffusionModelHijack: m.cond_stage_model.model.token_embedding = EmbeddingsWithFixes(m.cond_stage_model.model.token_embedding, self) m.cond_stage_model = sd_hijack_open_clip.FrozenOpenCLIPEmbedderWithCustomWords(m.cond_stage_model, self) + apply_weighted_forward(m) + self.optimization_method = apply_optimizations() self.clip = m.cond_stage_model @@ -132,6 +183,7 @@ class StableDiffusionModelHijack: m.cond_stage_model = m.cond_stage_model.wrapped undo_optimizations() + undo_weighted_forward(m) self.apply_circular(False) self.layers = None From 21642000b33a3069e3408ea1a50239006176badb Mon Sep 17 00:00:00 2001 From: Shondoit Date: Thu, 12 Jan 2023 15:29:19 +0100 Subject: [PATCH 042/278] Add PNG alpha channel as weight maps to data entries --- modules/textual_inversion/dataset.py | 51 +++++++++++++++++++++------- 1 file changed, 38 insertions(+), 13 deletions(-) diff --git a/modules/textual_inversion/dataset.py b/modules/textual_inversion/dataset.py index d31963d4..f4ce4552 100644 --- a/modules/textual_inversion/dataset.py +++ b/modules/textual_inversion/dataset.py @@ -19,9 +19,10 @@ re_numbers_at_start = re.compile(r"^[-\d]+\s*") class DatasetEntry: - def __init__(self, filename=None, filename_text=None, latent_dist=None, latent_sample=None, cond=None, cond_text=None, pixel_values=None): + def __init__(self, filename=None, filename_text=None, latent_dist=None, latent_sample=None, cond=None, cond_text=None, pixel_values=None, weight=None): self.filename = filename self.filename_text = filename_text + self.weight = weight self.latent_dist = latent_dist self.latent_sample = latent_sample self.cond = cond @@ -56,10 +57,16 @@ class PersonalizedBase(Dataset): print("Preparing dataset...") for path in tqdm.tqdm(self.image_paths): + alpha_channel = None if shared.state.interrupted: raise Exception("interrupted") try: - image = Image.open(path).convert('RGB') + image = Image.open(path) + #Currently does not work for single color transparency + #We would need to read image.info['transparency'] for that + if 'A' in image.getbands(): + alpha_channel = image.getchannel('A') + image = image.convert('RGB') if not varsize: image = image.resize((width, height), PIL.Image.BICUBIC) except Exception: @@ -87,17 +94,33 @@ class PersonalizedBase(Dataset): with devices.autocast(): latent_dist = model.encode_first_stage(torchdata.unsqueeze(dim=0)) - if latent_sampling_method == "once" or (latent_sampling_method == "deterministic" and not isinstance(latent_dist, DiagonalGaussianDistribution)): - latent_sample = model.get_first_stage_encoding(latent_dist).squeeze().to(devices.cpu) - latent_sampling_method = "once" - entry = DatasetEntry(filename=path, filename_text=filename_text, latent_sample=latent_sample) - elif latent_sampling_method == "deterministic": - # Works only for DiagonalGaussianDistribution - latent_dist.std = 0 - latent_sample = model.get_first_stage_encoding(latent_dist).squeeze().to(devices.cpu) - entry = DatasetEntry(filename=path, filename_text=filename_text, latent_sample=latent_sample) - elif latent_sampling_method == "random": - entry = DatasetEntry(filename=path, filename_text=filename_text, latent_dist=latent_dist) + #Perform latent sampling, even for random sampling. + #We need the sample dimensions for the weights + if latent_sampling_method == "deterministic": + if isinstance(latent_dist, DiagonalGaussianDistribution): + # Works only for DiagonalGaussianDistribution + latent_dist.std = 0 + else: + latent_sampling_method = "once" + latent_sample = model.get_first_stage_encoding(latent_dist).squeeze().to(devices.cpu) + + if alpha_channel is not None: + channels, *latent_size = latent_sample.shape + weight_img = alpha_channel.resize(latent_size) + npweight = np.array(weight_img).astype(np.float32) + #Repeat for every channel in the latent sample + weight = torch.tensor([npweight] * channels).reshape([channels] + latent_size) + #Normalize the weight to a minimum of 0 and a mean of 1, that way the loss will be comparable to default. + weight -= weight.min() + weight /= weight.mean() + else: + #If an image does not have a alpha channel, add a ones weight map anyway so we can stack it later + weight = torch.ones([channels] + latent_size) + + if latent_sampling_method == "random": + entry = DatasetEntry(filename=path, filename_text=filename_text, latent_dist=latent_dist, weight=weight) + else: + entry = DatasetEntry(filename=path, filename_text=filename_text, latent_sample=latent_sample, weight=weight) if not (self.tag_drop_out != 0 or self.shuffle_tags): entry.cond_text = self.create_text(filename_text) @@ -110,6 +133,7 @@ class PersonalizedBase(Dataset): del torchdata del latent_dist del latent_sample + del weight self.length = len(self.dataset) self.groups = list(groups.values()) @@ -195,6 +219,7 @@ class BatchLoader: self.cond_text = [entry.cond_text for entry in data] self.cond = [entry.cond for entry in data] self.latent_sample = torch.stack([entry.latent_sample for entry in data]).squeeze(1) + self.weight = torch.stack([entry.weight for entry in data]).squeeze(1) #self.emb_index = [entry.emb_index for entry in data] #print(self.latent_sample.device) From bc50936745e1a349afdc28cf1540109ba20bc71a Mon Sep 17 00:00:00 2001 From: Shondoit Date: Thu, 12 Jan 2023 15:34:11 +0100 Subject: [PATCH 043/278] Call weighted_forward during training --- modules/hypernetworks/hypernetwork.py | 3 ++- modules/textual_inversion/textual_inversion.py | 3 ++- 2 files changed, 4 insertions(+), 2 deletions(-) diff --git a/modules/hypernetworks/hypernetwork.py b/modules/hypernetworks/hypernetwork.py index 825a93b2..9c79b7d0 100644 --- a/modules/hypernetworks/hypernetwork.py +++ b/modules/hypernetworks/hypernetwork.py @@ -640,13 +640,14 @@ def train_hypernetwork(id_task, hypernetwork_name, learn_rate, batch_size, gradi with devices.autocast(): x = batch.latent_sample.to(devices.device, non_blocking=pin_memory) + w = batch.weight.to(devices.device, non_blocking=pin_memory) if tag_drop_out != 0 or shuffle_tags: shared.sd_model.cond_stage_model.to(devices.device) c = shared.sd_model.cond_stage_model(batch.cond_text).to(devices.device, non_blocking=pin_memory) shared.sd_model.cond_stage_model.to(devices.cpu) else: c = stack_conds(batch.cond).to(devices.device, non_blocking=pin_memory) - loss = shared.sd_model(x, c)[0] / gradient_step + loss = shared.sd_model.weighted_forward(x, c, w)[0] / gradient_step del x del c diff --git a/modules/textual_inversion/textual_inversion.py b/modules/textual_inversion/textual_inversion.py index a1a406c2..8853c868 100644 --- a/modules/textual_inversion/textual_inversion.py +++ b/modules/textual_inversion/textual_inversion.py @@ -480,6 +480,7 @@ def train_embedding(id_task, embedding_name, learn_rate, batch_size, gradient_st with devices.autocast(): x = batch.latent_sample.to(devices.device, non_blocking=pin_memory) + w = batch.weight.to(devices.device, non_blocking=pin_memory) c = shared.sd_model.cond_stage_model(batch.cond_text) if is_training_inpainting_model: @@ -490,7 +491,7 @@ def train_embedding(id_task, embedding_name, learn_rate, batch_size, gradient_st else: cond = c - loss = shared.sd_model(x, cond)[0] / gradient_step + loss = shared.sd_model.weighted_forward(x, cond, w)[0] / gradient_step del x _loss_step += loss.item() From edb10092de516dda5271130ed53628387780a859 Mon Sep 17 00:00:00 2001 From: Shondoit Date: Thu, 12 Jan 2023 16:29:00 +0100 Subject: [PATCH 044/278] Add ability to choose using weighted loss or not --- modules/hypernetworks/hypernetwork.py | 13 +++++++++---- modules/textual_inversion/dataset.py | 15 ++++++++++----- modules/textual_inversion/textual_inversion.py | 13 +++++++++---- modules/ui.py | 4 ++++ 4 files changed, 32 insertions(+), 13 deletions(-) diff --git a/modules/hypernetworks/hypernetwork.py b/modules/hypernetworks/hypernetwork.py index 9c79b7d0..f4fb69e0 100644 --- a/modules/hypernetworks/hypernetwork.py +++ b/modules/hypernetworks/hypernetwork.py @@ -496,7 +496,7 @@ def create_hypernetwork(name, enable_sizes, overwrite_old, layer_structure=None, shared.reload_hypernetworks() -def train_hypernetwork(id_task, hypernetwork_name, learn_rate, batch_size, gradient_step, data_root, log_directory, training_width, training_height, varsize, steps, clip_grad_mode, clip_grad_value, shuffle_tags, tag_drop_out, latent_sampling_method, create_image_every, save_hypernetwork_every, template_filename, preview_from_txt2img, preview_prompt, preview_negative_prompt, preview_steps, preview_sampler_index, preview_cfg_scale, preview_seed, preview_width, preview_height): +def train_hypernetwork(id_task, hypernetwork_name, learn_rate, batch_size, gradient_step, data_root, log_directory, training_width, training_height, varsize, steps, clip_grad_mode, clip_grad_value, shuffle_tags, tag_drop_out, latent_sampling_method, use_weight, create_image_every, save_hypernetwork_every, template_filename, preview_from_txt2img, preview_prompt, preview_negative_prompt, preview_steps, preview_sampler_index, preview_cfg_scale, preview_seed, preview_width, preview_height): # images allows training previews to have infotext. Importing it at the top causes a circular import problem. from modules import images @@ -554,7 +554,7 @@ def train_hypernetwork(id_task, hypernetwork_name, learn_rate, batch_size, gradi pin_memory = shared.opts.pin_memory - ds = modules.textual_inversion.dataset.PersonalizedBase(data_root=data_root, width=training_width, height=training_height, repeats=shared.opts.training_image_repeats_per_epoch, placeholder_token=hypernetwork_name, model=shared.sd_model, cond_model=shared.sd_model.cond_stage_model, device=devices.device, template_file=template_file, include_cond=True, batch_size=batch_size, gradient_step=gradient_step, shuffle_tags=shuffle_tags, tag_drop_out=tag_drop_out, latent_sampling_method=latent_sampling_method, varsize=varsize) + ds = modules.textual_inversion.dataset.PersonalizedBase(data_root=data_root, width=training_width, height=training_height, repeats=shared.opts.training_image_repeats_per_epoch, placeholder_token=hypernetwork_name, model=shared.sd_model, cond_model=shared.sd_model.cond_stage_model, device=devices.device, template_file=template_file, include_cond=True, batch_size=batch_size, gradient_step=gradient_step, shuffle_tags=shuffle_tags, tag_drop_out=tag_drop_out, latent_sampling_method=latent_sampling_method, varsize=varsize, use_weight=use_weight) if shared.opts.save_training_settings_to_txt: saved_params = dict( @@ -640,14 +640,19 @@ def train_hypernetwork(id_task, hypernetwork_name, learn_rate, batch_size, gradi with devices.autocast(): x = batch.latent_sample.to(devices.device, non_blocking=pin_memory) - w = batch.weight.to(devices.device, non_blocking=pin_memory) + if use_weight: + w = batch.weight.to(devices.device, non_blocking=pin_memory) if tag_drop_out != 0 or shuffle_tags: shared.sd_model.cond_stage_model.to(devices.device) c = shared.sd_model.cond_stage_model(batch.cond_text).to(devices.device, non_blocking=pin_memory) shared.sd_model.cond_stage_model.to(devices.cpu) else: c = stack_conds(batch.cond).to(devices.device, non_blocking=pin_memory) - loss = shared.sd_model.weighted_forward(x, c, w)[0] / gradient_step + if use_weight: + loss = shared.sd_model.weighted_forward(x, c, w)[0] / gradient_step + del w + else: + loss = shared.sd_model.forward(x, c)[0] / gradient_step del x del c diff --git a/modules/textual_inversion/dataset.py b/modules/textual_inversion/dataset.py index f4ce4552..1568b2b8 100644 --- a/modules/textual_inversion/dataset.py +++ b/modules/textual_inversion/dataset.py @@ -31,7 +31,7 @@ class DatasetEntry: class PersonalizedBase(Dataset): - def __init__(self, data_root, width, height, repeats, flip_p=0.5, placeholder_token="*", model=None, cond_model=None, device=None, template_file=None, include_cond=False, batch_size=1, gradient_step=1, shuffle_tags=False, tag_drop_out=0, latent_sampling_method='once', varsize=False): + def __init__(self, data_root, width, height, repeats, flip_p=0.5, placeholder_token="*", model=None, cond_model=None, device=None, template_file=None, include_cond=False, batch_size=1, gradient_step=1, shuffle_tags=False, tag_drop_out=0, latent_sampling_method='once', varsize=False, use_weight=False): re_word = re.compile(shared.opts.dataset_filename_word_regex) if len(shared.opts.dataset_filename_word_regex) > 0 else None self.placeholder_token = placeholder_token @@ -64,7 +64,7 @@ class PersonalizedBase(Dataset): image = Image.open(path) #Currently does not work for single color transparency #We would need to read image.info['transparency'] for that - if 'A' in image.getbands(): + if use_weight and 'A' in image.getbands(): alpha_channel = image.getchannel('A') image = image.convert('RGB') if not varsize: @@ -104,7 +104,7 @@ class PersonalizedBase(Dataset): latent_sampling_method = "once" latent_sample = model.get_first_stage_encoding(latent_dist).squeeze().to(devices.cpu) - if alpha_channel is not None: + if use_weight and alpha_channel is not None: channels, *latent_size = latent_sample.shape weight_img = alpha_channel.resize(latent_size) npweight = np.array(weight_img).astype(np.float32) @@ -113,9 +113,11 @@ class PersonalizedBase(Dataset): #Normalize the weight to a minimum of 0 and a mean of 1, that way the loss will be comparable to default. weight -= weight.min() weight /= weight.mean() - else: + elif use_weight: #If an image does not have a alpha channel, add a ones weight map anyway so we can stack it later weight = torch.ones([channels] + latent_size) + else: + weight = None if latent_sampling_method == "random": entry = DatasetEntry(filename=path, filename_text=filename_text, latent_dist=latent_dist, weight=weight) @@ -219,7 +221,10 @@ class BatchLoader: self.cond_text = [entry.cond_text for entry in data] self.cond = [entry.cond for entry in data] self.latent_sample = torch.stack([entry.latent_sample for entry in data]).squeeze(1) - self.weight = torch.stack([entry.weight for entry in data]).squeeze(1) + if all(entry.weight is not None for entry in data): + self.weight = torch.stack([entry.weight for entry in data]).squeeze(1) + else: + self.weight = None #self.emb_index = [entry.emb_index for entry in data] #print(self.latent_sample.device) diff --git a/modules/textual_inversion/textual_inversion.py b/modules/textual_inversion/textual_inversion.py index 8853c868..c63c7d1d 100644 --- a/modules/textual_inversion/textual_inversion.py +++ b/modules/textual_inversion/textual_inversion.py @@ -351,7 +351,7 @@ def validate_train_inputs(model_name, learn_rate, batch_size, gradient_step, dat assert log_directory, "Log directory is empty" -def train_embedding(id_task, embedding_name, learn_rate, batch_size, gradient_step, data_root, log_directory, training_width, training_height, varsize, steps, clip_grad_mode, clip_grad_value, shuffle_tags, tag_drop_out, latent_sampling_method, create_image_every, save_embedding_every, template_filename, save_image_with_stored_embedding, preview_from_txt2img, preview_prompt, preview_negative_prompt, preview_steps, preview_sampler_index, preview_cfg_scale, preview_seed, preview_width, preview_height): +def train_embedding(id_task, embedding_name, learn_rate, batch_size, gradient_step, data_root, log_directory, training_width, training_height, varsize, steps, clip_grad_mode, clip_grad_value, shuffle_tags, tag_drop_out, latent_sampling_method, use_weight, create_image_every, save_embedding_every, template_filename, save_image_with_stored_embedding, preview_from_txt2img, preview_prompt, preview_negative_prompt, preview_steps, preview_sampler_index, preview_cfg_scale, preview_seed, preview_width, preview_height): save_embedding_every = save_embedding_every or 0 create_image_every = create_image_every or 0 template_file = textual_inversion_templates.get(template_filename, None) @@ -410,7 +410,7 @@ def train_embedding(id_task, embedding_name, learn_rate, batch_size, gradient_st pin_memory = shared.opts.pin_memory - ds = modules.textual_inversion.dataset.PersonalizedBase(data_root=data_root, width=training_width, height=training_height, repeats=shared.opts.training_image_repeats_per_epoch, placeholder_token=embedding_name, model=shared.sd_model, cond_model=shared.sd_model.cond_stage_model, device=devices.device, template_file=template_file, batch_size=batch_size, gradient_step=gradient_step, shuffle_tags=shuffle_tags, tag_drop_out=tag_drop_out, latent_sampling_method=latent_sampling_method, varsize=varsize) + ds = modules.textual_inversion.dataset.PersonalizedBase(data_root=data_root, width=training_width, height=training_height, repeats=shared.opts.training_image_repeats_per_epoch, placeholder_token=embedding_name, model=shared.sd_model, cond_model=shared.sd_model.cond_stage_model, device=devices.device, template_file=template_file, batch_size=batch_size, gradient_step=gradient_step, shuffle_tags=shuffle_tags, tag_drop_out=tag_drop_out, latent_sampling_method=latent_sampling_method, varsize=varsize, use_weight=use_weight) if shared.opts.save_training_settings_to_txt: save_settings_to_file(log_directory, {**dict(model_name=checkpoint.model_name, model_hash=checkpoint.shorthash, num_of_dataset_images=len(ds), num_vectors_per_token=len(embedding.vec)), **locals()}) @@ -480,7 +480,8 @@ def train_embedding(id_task, embedding_name, learn_rate, batch_size, gradient_st with devices.autocast(): x = batch.latent_sample.to(devices.device, non_blocking=pin_memory) - w = batch.weight.to(devices.device, non_blocking=pin_memory) + if use_weight: + w = batch.weight.to(devices.device, non_blocking=pin_memory) c = shared.sd_model.cond_stage_model(batch.cond_text) if is_training_inpainting_model: @@ -491,7 +492,11 @@ def train_embedding(id_task, embedding_name, learn_rate, batch_size, gradient_st else: cond = c - loss = shared.sd_model.weighted_forward(x, cond, w)[0] / gradient_step + if use_weight: + loss = shared.sd_model.weighted_forward(x, cond, w)[0] / gradient_step + del w + else: + loss = shared.sd_model.forward(x, cond)[0] / gradient_step del x _loss_step += loss.item() diff --git a/modules/ui.py b/modules/ui.py index f5df1ffe..efb87c23 100644 --- a/modules/ui.py +++ b/modules/ui.py @@ -1191,6 +1191,8 @@ def create_ui(): create_image_every = gr.Number(label='Save an image to log directory every N steps, 0 to disable', value=500, precision=0, elem_id="train_create_image_every") save_embedding_every = gr.Number(label='Save a copy of embedding to log directory every N steps, 0 to disable', value=500, precision=0, elem_id="train_save_embedding_every") + use_weight = gr.Checkbox(label="Use PNG alpha channel as loss weight", value=False, elem_id="use_weight") + save_image_with_stored_embedding = gr.Checkbox(label='Save images with embedding in PNG chunks', value=True, elem_id="train_save_image_with_stored_embedding") preview_from_txt2img = gr.Checkbox(label='Read parameters (prompt, etc...) from txt2img tab when making previews', value=False, elem_id="train_preview_from_txt2img") @@ -1304,6 +1306,7 @@ def create_ui(): shuffle_tags, tag_drop_out, latent_sampling_method, + use_weight, create_image_every, save_embedding_every, template_file, @@ -1337,6 +1340,7 @@ def create_ui(): shuffle_tags, tag_drop_out, latent_sampling_method, + use_weight, create_image_every, save_embedding_every, template_file, From c4ea16a03f8f9c9a9add4049ce5be1a8060464f5 Mon Sep 17 00:00:00 2001 From: missionfloyd Date: Wed, 15 Feb 2023 19:47:30 -0700 Subject: [PATCH 045/278] Add ".vae.ckpt" to ext_blacklist --- 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 07072e5c..127e9663 100644 --- a/modules/sd_models.py +++ b/modules/sd_models.py @@ -105,7 +105,7 @@ def checkpoint_tiles(): def list_models(): checkpoints_list.clear() checkpoint_alisases.clear() - model_list = modelloader.load_models(model_path=model_path, model_url="https://huggingface.co/runwayml/stable-diffusion-v1-5/resolve/main/v1-5-pruned-emaonly.safetensors", command_path=shared.cmd_opts.ckpt_dir, ext_filter=[".ckpt", ".safetensors"], download_name="v1-5-pruned-emaonly.safetensors", ext_blacklist=[".vae.safetensors"]) + model_list = modelloader.load_models(model_path=model_path, model_url="https://huggingface.co/runwayml/stable-diffusion-v1-5/resolve/main/v1-5-pruned-emaonly.safetensors", command_path=shared.cmd_opts.ckpt_dir, ext_filter=[".ckpt", ".safetensors"], download_name="v1-5-pruned-emaonly.safetensors", ext_blacklist=[".vae.ckpt", ".vae.safetensors"]) cmd_ckpt = shared.cmd_opts.ckpt if os.path.exists(cmd_ckpt): From 9691ca5f59d8a126dc6595b9a217b1c2e4e36776 Mon Sep 17 00:00:00 2001 From: asdfire1 <45483619+asdfire1@users.noreply.github.com> Date: Thu, 16 Feb 2023 11:59:14 +0100 Subject: [PATCH 046/278] Fixed the Linux installation instructions --- README.md | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/README.md b/README.md index 2149dcc5..a0371a21 100644 --- a/README.md +++ b/README.md @@ -121,7 +121,8 @@ sudo pacman -S wget git python3 ```bash bash <(wget -qO- https://raw.githubusercontent.com/AUTOMATIC1111/stable-diffusion-webui/master/webui.sh) ``` - +3. Place stable diffusion checkpoint (`model.ckpt`) in the `models/Stable-diffusion` directory (see [dependencies](https://github.com/AUTOMATIC1111/stable-diffusion-webui/wiki/Dependencies) for where to get it). +4. Run `webui.sh`. ### Installation on Apple Silicon Find the instructions [here](https://github.com/AUTOMATIC1111/stable-diffusion-webui/wiki/Installation-on-Apple-Silicon). From b20737815a55cd90cfab2a1a3d60d682a67b127a Mon Sep 17 00:00:00 2001 From: space-nuko <24979496+space-nuko@users.noreply.github.com> Date: Thu, 16 Feb 2023 21:44:46 -0800 Subject: [PATCH 047/278] Fix params.txt saving for infotexts modified by process_batch --- modules/processing.py | 13 +++++++++---- 1 file changed, 9 insertions(+), 4 deletions(-) diff --git a/modules/processing.py b/modules/processing.py index e1b53ac0..73894822 100644 --- a/modules/processing.py +++ b/modules/processing.py @@ -585,10 +585,6 @@ def process_images_inner(p: StableDiffusionProcessing) -> Processed: if not p.disable_extra_networks: extra_networks.activate(p, extra_network_data) - with open(os.path.join(paths.data_path, "params.txt"), "w", encoding="utf8") as file: - processed = Processed(p, [], p.seed, "") - file.write(processed.infotext(p, 0)) - if state.job_count == -1: state.job_count = p.n_iter @@ -614,6 +610,15 @@ def process_images_inner(p: StableDiffusionProcessing) -> Processed: if p.scripts is not None: p.scripts.process_batch(p, batch_number=n, prompts=prompts, seeds=seeds, subseeds=subseeds) + # 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 + # strength, which is saved as "Model Strength: 1.0" in the infotext + if n == 0: + with open(os.path.join(paths.data_path, "params.txt"), "w", encoding="utf8") as file: + processed = Processed(p, [], p.seed, "") + file.write(processed.infotext(p, 0)) + uc = get_conds_with_caching(prompt_parser.get_learned_conditioning, negative_prompts, p.steps, cached_uc) c = get_conds_with_caching(prompt_parser.get_multicond_learned_conditioning, prompts, p.steps, cached_c) From 9c7e6d5bbaa55205d0678369588c019108fb30a7 Mon Sep 17 00:00:00 2001 From: Vladimir Mandic Date: Sat, 18 Feb 2023 11:31:02 -0500 Subject: [PATCH 048/278] store and print real torch version --- modules/ui.py | 7 ++++++- webui.py | 1 + 2 files changed, 7 insertions(+), 1 deletion(-) diff --git a/modules/ui.py b/modules/ui.py index f5df1ffe..d9df3781 100644 --- a/modules/ui.py +++ b/modules/ui.py @@ -1779,10 +1779,15 @@ def versions_html(): else: xformers_version = "N/A" + try: + torch_version = torch.__long_version__ + except: + torch_version = torch.__version__ + return f""" python: {python_version}  •  -torch: {torch.__version__} +torch: {torch_version}  •  xformers: {xformers_version}  •  diff --git a/webui.py b/webui.py index 5b5c2139..2363ea4e 100644 --- a/webui.py +++ b/webui.py @@ -20,6 +20,7 @@ import torch # Truncate version number of nightly/local build of PyTorch to not cause exceptions with CodeFormer or Safetensors if ".dev" in torch.__version__ or "+git" in torch.__version__: + torch.__long_version__ = torch.__version__ torch.__version__ = re.search(r'[\d.]+[\d]', torch.__version__).group(0) from modules import shared, devices, sd_samplers, upscaler, extensions, localization, ui_tempdir, ui_extra_networks From b5f69ad6afcdb8ba718da636b4a3f8aad5bd7cbf Mon Sep 17 00:00:00 2001 From: AUTOMATIC <16777216c@gmail.com> Date: Sun, 19 Feb 2023 08:38:38 +0300 Subject: [PATCH 049/278] simply long version display for torch in UI --- modules/ui.py | 7 +------ 1 file changed, 1 insertion(+), 6 deletions(-) diff --git a/modules/ui.py b/modules/ui.py index d9df3781..54efb6a4 100644 --- a/modules/ui.py +++ b/modules/ui.py @@ -1779,15 +1779,10 @@ def versions_html(): else: xformers_version = "N/A" - try: - torch_version = torch.__long_version__ - except: - torch_version = torch.__version__ - return f""" python: {python_version}  •  -torch: {torch_version} +torch: {getattr(torch, '__long_version__',torch.__version__)}  •  xformers: {xformers_version}  •  From 75e03785fe1fb47c3b288105e2638ef06d81aef2 Mon Sep 17 00:00:00 2001 From: AUTOMATIC <16777216c@gmail.com> Date: Sun, 19 Feb 2023 09:12:01 +0300 Subject: [PATCH 050/278] remove download instruction --- README.md | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/README.md b/README.md index 5a726872..2ceb4d2d 100644 --- a/README.md +++ b/README.md @@ -120,8 +120,7 @@ sudo pacman -S wget git python3 ```bash bash <(wget -qO- https://raw.githubusercontent.com/AUTOMATIC1111/stable-diffusion-webui/master/webui.sh) ``` -3. Place stable diffusion checkpoint (`model.ckpt`) in the `models/Stable-diffusion` directory (see [dependencies](https://github.com/AUTOMATIC1111/stable-diffusion-webui/wiki/Dependencies) for where to get it). -4. Run `webui.sh`. +3. Run `webui.sh`. ### Installation on Apple Silicon Find the instructions [here](https://github.com/AUTOMATIC1111/stable-diffusion-webui/wiki/Installation-on-Apple-Silicon). From a742facd95189eb078087bce9cafbfad0723cff4 Mon Sep 17 00:00:00 2001 From: AUTOMATIC <16777216c@gmail.com> Date: Sun, 19 Feb 2023 09:30:49 +0300 Subject: [PATCH 051/278] make PNG info tab work properly with parameter overrides --- modules/generation_parameters_copypaste.py | 7 ++++--- modules/ui.py | 10 +++++----- 2 files changed, 9 insertions(+), 8 deletions(-) diff --git a/modules/generation_parameters_copypaste.py b/modules/generation_parameters_copypaste.py index fc9e17aa..89dc23bf 100644 --- a/modules/generation_parameters_copypaste.py +++ b/modules/generation_parameters_copypaste.py @@ -74,8 +74,8 @@ def image_from_url_text(filedata): return image -def add_paste_fields(tabname, init_img, fields): - paste_fields[tabname] = {"init_img": init_img, "fields": fields} +def add_paste_fields(tabname, init_img, fields, override_settings_component=None): + paste_fields[tabname] = {"init_img": init_img, "fields": fields, "override_settings_component": override_settings_component} # backwards compatibility for existing extensions import modules.ui @@ -110,6 +110,7 @@ def connect_paste_params_buttons(): for binding in registered_param_bindings: destination_image_component = paste_fields[binding.tabname]["init_img"] fields = paste_fields[binding.tabname]["fields"] + override_settings_component = binding.override_settings_component or paste_fields[binding.tabname]["override_settings_component"] destination_width_component = next(iter([field for field, name in fields if name == "Size-1"] if fields else []), None) destination_height_component = next(iter([field for field, name in fields if name == "Size-2"] if fields else []), None) @@ -130,7 +131,7 @@ def connect_paste_params_buttons(): ) if binding.source_text_component is not None and fields is not None: - connect_paste(binding.paste_button, fields, binding.source_text_component, binding.override_settings_component, binding.tabname) + connect_paste(binding.paste_button, fields, binding.source_text_component, override_settings_component, binding.tabname) if binding.source_tabname is not None and fields is not None: paste_field_names = ['Prompt', 'Negative prompt', 'Steps', 'Face restoration'] + (["Seed"] if shared.opts.send_seed else []) diff --git a/modules/ui.py b/modules/ui.py index 54efb6a4..2fdbda42 100644 --- a/modules/ui.py +++ b/modules/ui.py @@ -631,9 +631,9 @@ def create_ui(): (hr_resize_y, "Hires resize-2"), *modules.scripts.scripts_txt2img.infotext_fields ] - parameters_copypaste.add_paste_fields("txt2img", None, txt2img_paste_fields) + parameters_copypaste.add_paste_fields("txt2img", None, txt2img_paste_fields, override_settings) parameters_copypaste.register_paste_params_button(parameters_copypaste.ParamBinding( - paste_button=txt2img_paste, tabname="txt2img", source_text_component=txt2img_prompt, source_image_component=None, override_settings_component=override_settings, + paste_button=txt2img_paste, tabname="txt2img", source_text_component=txt2img_prompt, source_image_component=None, )) txt2img_preview_params = [ @@ -963,10 +963,10 @@ def create_ui(): (mask_blur, "Mask blur"), *modules.scripts.scripts_img2img.infotext_fields ] - parameters_copypaste.add_paste_fields("img2img", init_img, img2img_paste_fields) - parameters_copypaste.add_paste_fields("inpaint", init_img_with_mask, img2img_paste_fields) + parameters_copypaste.add_paste_fields("img2img", init_img, img2img_paste_fields, override_settings) + parameters_copypaste.add_paste_fields("inpaint", init_img_with_mask, img2img_paste_fields, override_settings) parameters_copypaste.register_paste_params_button(parameters_copypaste.ParamBinding( - paste_button=img2img_paste, tabname="img2img", source_text_component=img2img_prompt, source_image_component=None, override_settings_component=override_settings, + paste_button=img2img_paste, tabname="img2img", source_text_component=img2img_prompt, source_image_component=None, )) modules.scripts.scripts_current = None From 15f4b217b10448449ae211df24c86a7cb0e187f4 Mon Sep 17 00:00:00 2001 From: AUTOMATIC <16777216c@gmail.com> Date: Sun, 19 Feb 2023 09:50:14 +0300 Subject: [PATCH 052/278] fix the a merge conflict resolve i did that entirely breaks image generation --- modules/processing.py | 3 --- 1 file changed, 3 deletions(-) diff --git a/modules/processing.py b/modules/processing.py index 269a1a9f..2009d3bf 100644 --- a/modules/processing.py +++ b/modules/processing.py @@ -580,9 +580,6 @@ def process_images_inner(p: StableDiffusionProcessing) -> Processed: if shared.opts.live_previews_enable and opts.show_progress_type == "Approx NN": sd_vae_approx.model() - if not p.disable_extra_networks: - extra_networks.activate(p, extra_network_data) - if state.job_count == -1: state.job_count = p.n_iter From 164699163718a73a273b86f67a16d3807bccda0e Mon Sep 17 00:00:00 2001 From: AUTOMATIC <16777216c@gmail.com> Date: Sun, 19 Feb 2023 09:54:04 +0300 Subject: [PATCH 053/278] display 8 (rather than 7) characters of the extension commit hash in the installed extensions table --- modules/extensions.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/modules/extensions.py b/modules/extensions.py index 1975fca1..3eef9eaf 100644 --- a/modules/extensions.py +++ b/modules/extensions.py @@ -44,7 +44,7 @@ class Extension: self.status = 'unknown' head = repo.head.commit ts = time.asctime(time.gmtime(repo.head.commit.committed_date)) - self.version = f'{head.hexsha[:7]} ({ts})' + self.version = f'{head.hexsha[:8]} ({ts})' except Exception: self.remote = None From fb2354cb2ae47f9e9b70f0e04f34925bbb31b1ac Mon Sep 17 00:00:00 2001 From: AUTOMATIC <16777216c@gmail.com> Date: Sun, 19 Feb 2023 10:12:45 +0300 Subject: [PATCH 054/278] reword settings for 4chan export, remove unneded try/excepts, add try/except for actually saving JPG --- modules/images.py | 24 +++++++++--------------- modules/shared.py | 6 +++--- 2 files changed, 12 insertions(+), 18 deletions(-) diff --git a/modules/images.py b/modules/images.py index 34d08b73..dcf5d90c 100644 --- a/modules/images.py +++ b/modules/images.py @@ -18,7 +18,7 @@ import string import json import hashlib -from modules import sd_samplers, shared, script_callbacks +from modules import sd_samplers, shared, script_callbacks, errors from modules.shared import opts, cmd_opts LANCZOS = (Image.Resampling.LANCZOS if hasattr(Image, 'Resampling') else Image.LANCZOS) @@ -575,25 +575,19 @@ def save_image(image, path, basename, seed=None, prompt=None, extension='png', i image.already_saved_as = fullfn - try: - target_side_length = int(opts.target_side_length) - except ValueError: - target_side_length = 4000 - try: - img_downscale_threshold = float(opts.img_downscale_threshold) - except ValueError: - img_downscale_threshold = 4 - - oversize = image.width > target_side_length or image.height > target_side_length - if opts.export_for_4chan and (oversize or os.stat(fullfn).st_size > img_downscale_threshold * 1024 * 1024): + oversize = image.width > opts.target_side_length or image.height > opts.target_side_length + if opts.export_for_4chan and (oversize or os.stat(fullfn).st_size > opts.img_downscale_threshold * 1024 * 1024): ratio = image.width / image.height if oversize and ratio > 1: - image = image.resize((target_side_length, image.height * target_side_length // image.width), LANCZOS) + image = image.resize((opts.target_side_length, image.height * opts.target_side_length // image.width), LANCZOS) elif oversize: - image = image.resize((image.width * target_side_length // image.height, target_side_length), LANCZOS) + image = image.resize((image.width * opts.target_side_length // image.height, opts.target_side_length), LANCZOS) - _atomically_save_image(image, fullfn_without_extension, ".jpg") + try: + _atomically_save_image(image, fullfn_without_extension, ".jpg") + except Exception as e: + errors.display(e, "saving image as downscaled JPG") if opts.save_txt and info is not None: txt_fullfn = f"{fullfn_without_extension}.txt" diff --git a/modules/shared.py b/modules/shared.py index d68c366c..4f496533 100644 --- a/modules/shared.py +++ b/modules/shared.py @@ -325,9 +325,9 @@ options_templates.update(options_section(('saving-images', "Saving images/grids" "save_images_before_highres_fix": OptionInfo(False, "Save a copy of image before applying highres fix."), "save_images_before_color_correction": OptionInfo(False, "Save a copy of image before applying color correction to img2img results"), "jpeg_quality": OptionInfo(80, "Quality for saved jpeg images", gr.Slider, {"minimum": 1, "maximum": 100, "step": 1}), - "export_for_4chan": OptionInfo(True, "If PNG image is larger than Downscale threshold or any dimension is larger than Target length, downscale the image to dimensions and save a copy as JPG"), - "img_downscale_threshold": OptionInfo(4, "Downscale threshold (MB)"), - "target_side_length": OptionInfo(4000, "Target length"), + "export_for_4chan": OptionInfo(True, "If the saved image file size is above the limit, or its either width or height are above the limit, save a downscaled copy as JPG"), + "img_downscale_threshold": OptionInfo(4.0, "File size limit for the above option, MB", gr.Number), + "target_side_length": OptionInfo(4000, "Width/height limit for the above option, in pixels", gr.Number), "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"), From fd4ac5187a1ae42be3f131770ea21e2158f75dcd Mon Sep 17 00:00:00 2001 From: AUTOMATIC1111 <16777216c@gmail.com> Date: Sun, 19 Feb 2023 10:55:39 +0300 Subject: [PATCH 055/278] Revert "Aspect ratio sliders" --- javascript/ComponentControllers.js | 259 ----------------------------- javascript/aspectRatioSliders.js | 181 -------------------- modules/shared.py | 21 --- modules/ui.py | 12 +- style.css | 46 ----- 5 files changed, 2 insertions(+), 517 deletions(-) delete mode 100644 javascript/ComponentControllers.js delete mode 100644 javascript/aspectRatioSliders.js diff --git a/javascript/ComponentControllers.js b/javascript/ComponentControllers.js deleted file mode 100644 index 2888679b..00000000 --- a/javascript/ComponentControllers.js +++ /dev/null @@ -1,259 +0,0 @@ -/* This is a basic library that allows controlling elements that take some form of user input. - -This was previously written in typescript, where all controllers implemented an interface. Not -all methods were needed in all the controllers, but it was done to keep a common interface, so -your main app can serve as a controller of controllers. - -These controllers were built to work on the shapes of html elements that gradio components use. - -There may be some notes in it that only applied to my use case, but I left them to help others -along. - -You will need the parent element for these to work. -The parent element can be defined as the element (div) that gets the element id when assigning -an element id to a gradio component. - -Example: - gr.TextBox(value="...", elem_id="THISID") - -Basic usage, grab an element that is the parent container for the component. - -Send it in to the class, like a function, don't forget the "new" keyword so it calls the constructor -and sends back a new object. - -Example: - -let txt2imgPrompt = new TextComponentController(gradioApp().querySelector("#txt2img_prompt")) - -Then use the getVal() method to get the value, or use the setVal(myValue) method to set the value. - -Input types that are groups, like Checkbox groups (not individual checkboxes), take in an array of values. - -Checkbox group has to reset all values to False (unchecked), then set the values in your array to true (checked). -If you don't hold a reference to the values (the labels in string format), you can acquire them using the getVal() method. -*/ -class DropdownComponentController { - constructor(element) { - this.element = element; - this.childSelector = this.element.querySelector('select'); - this.children = new Map(); - Array.from(this.childSelector.querySelectorAll('option')).forEach(opt => this.children.set(opt.value, opt)); - } - getVal() { - return this.childSelector.value; - } - updateVal(optionElement) { - optionElement.selected = true; - } - setVal(name) { - this.updateVal(this.children.get(name)); - this.eventHandler(); - } - eventHandler() { - this.childSelector.dispatchEvent(new Event("change")); - } -} -class CheckboxComponentController { - constructor(element) { - this.element = element; - this.child = this.element.querySelector('input'); - } - getVal() { - return this.child.checked; - } - updateVal(checked) { - this.child.checked = checked; - } - setVal(checked) { - this.updateVal(checked); - this.eventHandler(); - } - eventHandler() { - this.child.dispatchEvent(new Event("change")); - } -} -class CheckboxGroupComponentController { - constructor(element) { - this.element = element; - //this.checkBoxes = new Object; - this.children = new Map(); - Array.from(this.element.querySelectorAll('input')).forEach(input => this.children.set(input.nextElementSibling.innerText, input)); - /* element id gets use fieldset, grab all inputs (the bool val) get the userfriendly label, use as key, put bool value in mapping */ - //Array.from(this.component.querySelectorAll("input")).forEach( _input => this.checkBoxes[_input.nextElementSibling.innerText] = _input) - /*Checkboxgroup structure -
    -
    css makes translucent - - serves as label for component - -
    container for checkboxes - - ... -
    -
    - */ - } - updateVal(label) { - /********* - calls updates using a throttle or else the backend does not get updated properly - * ********/ - setTimeout(() => this.conditionalToggle(true, this.children.get(label)), 2); - } - setVal(labels) { - /* Handles reset and updates all in array to true */ - this.reupdateVals(); - labels.forEach(l => this.updateVal(l)); - } - getVal() { - //return the list of values that are true - return [...this.children].filter(([k, v]) => v.checked).map(arr => arr[0]); - } - reupdateVals() { - /************** - * for reupdating all vals, first set to false - **************/ - this.children.forEach(inputChild => this.conditionalToggle(false, inputChild)); - } - conditionalToggle(desiredVal, inputChild) { - //This method behaves like 'set this value to this' - //Using element.checked = true/false, does not register the change, even if you called change afterwards, - // it only sets what it looks like in our case, because there is no form submit, a person then has to click on it twice. - //Options are to use .click() or dispatch an event - if (desiredVal != inputChild.checked) { - inputChild.dispatchEvent(new Event("change")); //using change event instead of click, in case browser ad-blockers blocks the click method - } - } - eventHandler(checkbox) { - checkbox.dispatchEvent(new Event("change")); - } -} -class RadioComponentController { - constructor(element) { - this.element = element; - this.children = new Map(); - Array.from(this.element.querySelectorAll("input")).forEach(input => this.children.set(input.value, input)); - } - getVal() { - //radio groups have a single element that's checked is true - // as array arr k,v pair element.checked ) -> array of len(1) with [k,v] so either [0] [1].value - return [...this.children].filter(([l, e]) => e.checked)[0][0]; - //return Array.from(this.children).filter( ([label, input]) => input.checked)[0][1].value - } - updateVal(child) { - this.eventHandler(child); - } - setVal(name) { - //radio will trigger all false except the one that get the event change - //to keep the api similar, other methods are still called - this.updateVal(this.children.get(name)); - } - eventHandler(child) { - child.dispatchEvent(new Event("change")); - } -} -class NumberComponentController { - constructor(element) { - this.element = element; - this.childNumField = element.querySelector('input[type=number]'); - } - getVal() { - return this.childNumField.value; - } - updateVal(text) { - this.childNumField.value = text; - } - eventHandler() { - this.element.dispatchEvent(new Event("input")); - } - setVal(text) { - this.updateVal(text); - this.eventHandler(); - } -} -class SliderComponentController { - constructor(element) { - this.element = element; - this.childNumField = this.element.querySelector('input[type=number]'); - this.childRangeField = this.element.querySelector('input[type=range]'); - } - getVal() { - return this.childNumField.value; - } - updateVal(text) { - //both are not needed, either works, both are left in so one is a fallback in case of gradio changes - this.childNumField.value = text; - this.childRangeField.value = text; - } - eventHandler() { - this.element.dispatchEvent(new Event("input")); - this.childNumField.dispatchEvent(new Event("input")); - this.childRangeField.dispatchEvent(new Event("input")); - } - setVal(text) { - this.updateVal(text); - this.eventHandler(); - } -} -class TextComponentController { - constructor(element) { - this.element = element; - this.child = element.querySelector('textarea'); - } - getVal() { - return this.child.value; - } - eventHandler() { - this.element.dispatchEvent(new Event("input")); - this.child.dispatchEvent(new Event("change")); - //Workaround to solve no target with v(o) on eventhandler, define my own target - let ne = new Event("input"); - Object.defineProperty(ne, "target", { value: this.child }); - this.child.dispatchEvent(ne); - } - updateVal(text) { - this.child.value = text; - } - appendValue(text) { - //might add delimiter option - this.child.value += ` ${text}`; - } - setVal(text, append = false) { - if (append) { - this.appendValue(text); - } - else { - this.updateVal(text); - } - this.eventHandler(); - } -} -class JsonComponentController extends TextComponentController { - constructor(element) { - super(element); - } - getVal() { - return JSON.parse(this.child.value); - } -} -class ColorComponentController { - constructor(element) { - this.element = element; - this.child = this.element.querySelector('input[type=color]'); - } - updateVal(text) { - this.child.value = text; - } - getVal() { - return this.child.value; - } - setVal(text) { - this.updateVal(text); - this.eventHandler(); - } - eventHandler() { - this.child.dispatchEvent(new Event("input")); - } -} diff --git a/javascript/aspectRatioSliders.js b/javascript/aspectRatioSliders.js deleted file mode 100644 index 3def5158..00000000 --- a/javascript/aspectRatioSliders.js +++ /dev/null @@ -1,181 +0,0 @@ -class AspectRatioSliderController { - constructor(widthSlider, heightSlider, ratioSource, roundingSource, roundingMethod) { - //References - this.widthSlider = new SliderComponentController(widthSlider); - this.heightSlider = new SliderComponentController(heightSlider); - this.ratioSource = new DropdownComponentController(ratioSource); - this.roundingSource = new CheckboxComponentController(roundingSource); - this.roundingMethod = new RadioComponentController(roundingMethod); - this.roundingIndicatorBadge = document.createElement("div"); - // Badge implementation - this.roundingIndicatorBadge.innerText = "📐"; - this.roundingIndicatorBadge.classList.add("rounding-badge"); - this.ratioSource.element.appendChild(this.roundingIndicatorBadge); - // Check initial value of ratioSource to set badge visbility - let initialRatio = this.ratioSource.getVal(); - if (!initialRatio.includes(":")) { - this.roundingIndicatorBadge.style.display = "none"; - } - //Adjust badge icon if rounding is on - if (this.roundingSource.getVal()) { - //this.roundingIndicatorBadge.classList.add("active"); - this.roundingIndicatorBadge.innerText = "📏"; - } - //Make badge clickable to toggle setting - this.roundingIndicatorBadge.addEventListener("click", () => { - this.roundingSource.setVal(!this.roundingSource.getVal()); - }); - //Make rounding setting toggle badge text and style if setting changes - this.roundingSource.child.addEventListener("change", () => { - if (this.roundingSource.getVal()) { - //this.roundingIndicatorBadge.classList.add("active"); - this.roundingIndicatorBadge.innerText = "📏"; - } - else { - //this.roundingIndicatorBadge.classList.remove("active"); - this.roundingIndicatorBadge.innerText = "📐"; - } - this.adjustStepSize(); - }); - //Other event listeners - this.widthSlider.childRangeField.addEventListener("change", (e) => { e.preventDefault(); this.resize("width"); }); - this.widthSlider.childNumField.addEventListener("change", (e) => { e.preventDefault(); this.resize("width"); }); - this.heightSlider.childRangeField.addEventListener("change", (e) => { e.preventDefault(); this.resize("height"); }); - this.heightSlider.childNumField.addEventListener("change", (e) => { e.preventDefault(); this.resize("height"); }); - this.ratioSource.childSelector.addEventListener("change", (e) => { - e.preventDefault(); - //Check and toggle display of badge conditionally on dropdown selection - if (!this.ratioSource.getVal().includes(":")) { - this.roundingIndicatorBadge.style.display = 'none'; - } - else { - this.roundingIndicatorBadge.style.display = 'block'; - } - this.adjustStepSize(); - }); - } - resize(dimension) { - //For moving slider or number field - let val = this.ratioSource.getVal(); - if (!val.includes(":")) { - return; - } - let [width, height] = val.split(":").map(Number); - let ratio = width / height; - if (dimension == 'width') { - let newHeight = parseInt(this.widthSlider.getVal()) / ratio; - if (this.roundingSource.getVal()) { - switch (this.roundingMethod.getVal()) { - case 'Round': - newHeight = Math.round(newHeight / 8) * 8; - break; - case 'Ceiling': - newHeight = Math.ceil(newHeight / 8) * 8; - break; - case 'Floor': - newHeight = Math.floor(newHeight / 8) * 8; - break; - } - } - this.heightSlider.setVal(newHeight.toString()); - } - else if (dimension == "height") { - let newWidth = parseInt(this.heightSlider.getVal()) * ratio; - if (this.roundingSource.getVal()) { - switch (this.roundingMethod.getVal()) { - case 'Round': - newWidth = Math.round(newWidth / 8) * 8; - break; - case 'Ceiling': - newWidth = Math.ceil(newWidth / 8) * 8; - break; - case 'Floor': - newWidth = Math.floor(newWidth / 8) * 8; - break; - } - } - this.widthSlider.setVal(newWidth.toString()); - } - } - adjustStepSize() { - /* Sets scales/precision/rounding steps;*/ - let val = this.ratioSource.getVal(); - if (!val.includes(":")) { - //If ratio unlocked - this.widthSlider.childRangeField.step = "8"; - this.widthSlider.childRangeField.min = "64"; - this.widthSlider.childNumField.step = "8"; - this.widthSlider.childNumField.min = "64"; - this.heightSlider.childRangeField.step = "8"; - this.heightSlider.childRangeField.min = "64"; - this.heightSlider.childNumField.step = "8"; - this.heightSlider.childNumField.min = "64"; - return; - } - //Format string and calculate step sizes - let [width, height] = val.split(":").map(Number); - let decimalPlaces = (width.toString().split(".")[1] || []).length; - //keep upto 6 decimal points of precision of ratio - //euclidean gcd does not support floats, so we scale it up - decimalPlaces = decimalPlaces > 6 ? 6 : decimalPlaces; - let gcd = this.gcd(width * 10 ** decimalPlaces, height * 10 ** decimalPlaces) / 10 ** decimalPlaces; - let stepSize = 8 * height / gcd; - let stepSizeOther = 8 * width / gcd; - if (this.roundingSource.getVal()) { - //If rounding is on set/keep default stepsizes - this.widthSlider.childRangeField.step = "8"; - this.widthSlider.childRangeField.min = "64"; - this.widthSlider.childNumField.step = "8"; - this.widthSlider.childNumField.min = "64"; - this.heightSlider.childRangeField.step = "8"; - this.heightSlider.childRangeField.min = "64"; - this.heightSlider.childNumField.step = "8"; - this.heightSlider.childNumField.min = "64"; - } - else { - //if rounding is off, set step sizes so they enforce snapping - //min is changed, because it offsets snap positions - this.widthSlider.childRangeField.step = stepSizeOther.toString(); - this.widthSlider.childRangeField.min = stepSizeOther.toString(); - this.widthSlider.childNumField.step = stepSizeOther.toString(); - this.widthSlider.childNumField.min = stepSizeOther.toString(); - this.heightSlider.childRangeField.step = stepSize.toString(); - this.heightSlider.childRangeField.min = stepSize.toString(); - this.heightSlider.childNumField.step = stepSize.toString(); - this.heightSlider.childNumField.min = stepSize.toString(); - } - let currentWidth = parseInt(this.widthSlider.getVal()); - //Rounding treated kinda like pythons divmod - let stepsTaken = Math.round(currentWidth / stepSizeOther); - //this snaps it to closest rule matches (rules being html step points, and ratio) - let newWidth = stepsTaken * stepSizeOther; - this.widthSlider.setVal(newWidth.toString()); - this.heightSlider.setVal(Math.round(newWidth / (width / height)).toString()); - } - gcd(a, b) { - //euclidean gcd - if (b === 0) { - return a; - } - return this.gcd(b, a % b); - } - static observeStartup(widthSliderId, heightSliderId, ratioSourceId, roundingSourceId, roundingMethodId) { - let observer = new MutationObserver(() => { - let widthSlider = document.querySelector("gradio-app").shadowRoot.getElementById(widthSliderId); - let heightSlider = document.querySelector("gradio-app").shadowRoot.getElementById(heightSliderId); - let ratioSource = document.querySelector("gradio-app").shadowRoot.getElementById(ratioSourceId); - let roundingSource = document.querySelector("gradio-app").shadowRoot.getElementById(roundingSourceId); - let roundingMethod = document.querySelector("gradio-app").shadowRoot.getElementById(roundingMethodId); - if (widthSlider && heightSlider && ratioSource && roundingSource && roundingMethod) { - observer.disconnect(); - new AspectRatioSliderController(widthSlider, heightSlider, ratioSource, roundingSource, roundingMethod); - } - }); - observer.observe(gradioApp(), { childList: true, subtree: true }); - } -} -document.addEventListener("DOMContentLoaded", () => { - //Register mutation observer for self start-up; - AspectRatioSliderController.observeStartup("txt2img_width", "txt2img_height", "txt2img_ratio", "setting_aspect_ratios_rounding", "setting_aspect_ratios_rounding_method"); - AspectRatioSliderController.observeStartup("img2img_width", "img2img_height", "img2img_ratio", "setting_aspect_ratios_rounding", "setting_aspect_ratios_rounding_method"); -}); diff --git a/modules/shared.py b/modules/shared.py index 2983ee44..e324a48a 100644 --- a/modules/shared.py +++ b/modules/shared.py @@ -139,24 +139,6 @@ ui_reorder_categories = [ "scripts", ] -aspect_ratio_defaults = [ - "🔓", - "1:1", - "3:2", - "4:3", - "5:4", - "16:9", - "9:16", - "1.85:1", - "2.35:1", - "2.39:1", - "2.40:1", - "21:9", - "1.375:1", - "1.66:1", - "1.75:1" -] - cmd_opts.disable_extension_access = (cmd_opts.share or cmd_opts.listen or cmd_opts.server_name) and not cmd_opts.enable_insecure_extension_access devices.device, devices.device_interrogate, devices.device_gfpgan, devices.device_esrgan, devices.device_codeformer = \ @@ -477,9 +459,6 @@ options_templates.update(options_section(('ui', "User interface"), { "keyedit_precision_extra": OptionInfo(0.05, "Ctrl+up/down precision when editing ", gr.Slider, {"minimum": 0.01, "maximum": 0.2, "step": 0.001}), "quicksettings": OptionInfo("sd_model_checkpoint", "Quicksettings list"), "ui_reorder": OptionInfo(", ".join(ui_reorder_categories), "txt2img/img2img UI item order"), - "aspect_ratios_rounding": OptionInfo(True, "Round aspect ratios for more flexibility?", gr.Checkbox), - "aspect_ratios_rounding_method": OptionInfo("Ceiling", "Aspect ratios rounding method", gr.Radio,{"choices": ["Round", "Ceiling", "Floor"]}), - "aspect_ratios": OptionInfo(", ".join(aspect_ratio_defaults), "txt2img/img2img aspect ratios"), "ui_extra_networks_tab_reorder": OptionInfo("", "Extra networks tab order"), "localization": OptionInfo("None", "Localization (requires restart)", gr.Dropdown, lambda: {"choices": ["None"] + list(localization.localizations.keys())}, refresh=lambda: localization.list_localizations(cmd_opts.localizations_dir)), })) diff --git a/modules/ui.py b/modules/ui.py index 2fc1fee5..2fdbda42 100644 --- a/modules/ui.py +++ b/modules/ui.py @@ -424,10 +424,6 @@ def ordered_ui_categories(): yield category -def aspect_ratio_list(): - return [ratio.strip() for ratio in shared.opts.aspect_ratios.split(",")] - - def get_value_for_setting(key): value = getattr(opts, key) @@ -483,9 +479,7 @@ def create_ui(): width = gr.Slider(minimum=64, maximum=2048, step=8, label="Width", value=512, elem_id="txt2img_width") height = gr.Slider(minimum=64, maximum=2048, step=8, label="Height", value=512, elem_id="txt2img_height") - with gr.Column(elem_id="txt2img_size_toolbox", scale=0): - aspect_ratio_dropdown = gr.Dropdown(value="🔓", choices=aspect_ratio_list(), interactive=True, type="value", elem_id="txt2img_ratio", show_label=False, label="Aspect Ratio") - res_switch_btn = ToolButton(value=switch_values_symbol, elem_id="txt2img_res_switch_btn") + res_switch_btn = ToolButton(value=switch_values_symbol, elem_id="txt2img_res_switch_btn") if opts.dimensions_and_batch_together: with gr.Column(elem_id="txt2img_column_batch"): batch_count = gr.Slider(minimum=1, step=1, label='Batch count', value=1, elem_id="txt2img_batch_count") @@ -763,9 +757,7 @@ def create_ui(): width = gr.Slider(minimum=64, maximum=2048, step=8, label="Width", value=512, elem_id="img2img_width") height = gr.Slider(minimum=64, maximum=2048, step=8, label="Height", value=512, elem_id="img2img_height") - with gr.Column(elem_id="img2img_size_toolbox", scale=0): - aspect_ratio_dropdown = gr.Dropdown(value="🔓", choices=aspect_ratio_list(), interactive=True, type="value", elem_id="img2img_ratio", show_label=False, label="Aspect Ratio") - res_switch_btn = ToolButton(value=switch_values_symbol, elem_id="img2img_res_switch_btn") + res_switch_btn = ToolButton(value=switch_values_symbol, elem_id="img2img_res_switch_btn") if opts.dimensions_and_batch_together: with gr.Column(elem_id="img2img_column_batch"): batch_count = gr.Slider(minimum=1, step=1, label='Batch count', value=1, elem_id="img2img_batch_count") diff --git a/style.css b/style.css index 55baefb7..05572f66 100644 --- a/style.css +++ b/style.css @@ -747,52 +747,6 @@ footer { margin-left: 0em; } -#txt2img_size_toolbox, #img2img_size_toolbox{ - min-width: unset !important; - gap: 0; -} - -#txt2img_ratio, #img2img_ratio { - padding: 0px; - min-width: unset; - max-width: fit-content; -} -#txt2img_ratio select, #img2img_ratio select{ - -o-appearance: none; - -ms-appearance: none; - -webkit-appearance: none; - -moz-appearance: none; - appearance: none; - background-image: unset; - padding-right: unset; - min-width: 40px; - max-width: 40px; - min-height: 40px; - max-height: 40px; - line-height: 40px; - padding: 0; - text-align: center; -} -.rounding-badge { - display: inline-block; - border-radius: 0px; - /*background-color: #ccc;*/ - cursor: pointer; - position: absolute; - top: -10px; - right: -10px; - width: 20px; - height: 20px; - padding: 1px; - line-height: 16px; - font-size: 14px; -} - -.rounding-badge.active { - background-color: #007bff; - border-radius: 50%; -} - .inactive{ opacity: 0.5; } From 66cfd1dcfc893a9051310c208a66890b86334118 Mon Sep 17 00:00:00 2001 From: AUTOMATIC <16777216c@gmail.com> Date: Sun, 19 Feb 2023 11:45:04 +0300 Subject: [PATCH 056/278] Expose xyz_grid's values to other extensions for #7721 --- scripts/xyz_grid.py | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/scripts/xyz_grid.py b/scripts/xyz_grid.py index 5982cfba..62e03d02 100644 --- a/scripts/xyz_grid.py +++ b/scripts/xyz_grid.py @@ -25,6 +25,8 @@ from modules.ui_components import ToolButton fill_values_symbol = "\U0001f4d2" # 📒 +AxisInfo = namedtuple('AxisInfo', ['axis', 'values']) + def apply_field(field): def fun(p, x, xs): @@ -520,6 +522,10 @@ class Script(scripts.Script): grid_infotext = [None] + state.xyz_plot_x = AxisInfo(x_opt, xs) + state.xyz_plot_y = AxisInfo(y_opt, ys) + state.xyz_plot_z = AxisInfo(z_opt, zs) + # If one of the axes is very slow to change between (like SD model # checkpoint), then make sure it is in the outer iteration of the nested # `for` loop. From fe46a08f52c36ca61446f657296802ab3ceb2529 Mon Sep 17 00:00:00 2001 From: AUTOMATIC <16777216c@gmail.com> Date: Sun, 19 Feb 2023 12:09:25 +0300 Subject: [PATCH 057/278] add slash to non-empty dirs in extra networks interface --- modules/ui_extra_networks.py | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/modules/ui_extra_networks.py b/modules/ui_extra_networks.py index 90abec0a..30ceab4e 100644 --- a/modules/ui_extra_networks.py +++ b/modules/ui_extra_networks.py @@ -76,6 +76,10 @@ class ExtraNetworksPage: while subdir.startswith("/"): subdir = subdir[1:] + is_empty = len(os.listdir(x)) == 0 + if not is_empty and not subdir.endswith("/"): + subdir = subdir + "/" + subdirs[subdir] = 1 if subdirs: From b908bed8838fae89e5e83e57ca91808cf2d68077 Mon Sep 17 00:00:00 2001 From: AUTOMATIC <16777216c@gmail.com> Date: Sun, 19 Feb 2023 12:23:40 +0300 Subject: [PATCH 058/278] remove unneeded return from #7583 --- modules/shared_items.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/modules/shared_items.py b/modules/shared_items.py index b72b2bae..e792a134 100644 --- a/modules/shared_items.py +++ b/modules/shared_items.py @@ -20,4 +20,4 @@ def sd_vae_items(): def refresh_vae_list(): import modules.sd_vae - return modules.sd_vae.refresh_vae_list() + modules.sd_vae.refresh_vae_list() From 48d171bbb373e3db9aef0776fe681b63056272b7 Mon Sep 17 00:00:00 2001 From: AUTOMATIC <16777216c@gmail.com> Date: Sun, 19 Feb 2023 12:25:05 +0300 Subject: [PATCH 059/278] fix incorrectly named args for gr.Slider in prompt matrix and xyz grid --- scripts/prompt_matrix.py | 2 +- scripts/xyz_grid.py | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/scripts/prompt_matrix.py b/scripts/prompt_matrix.py index 3ee3cbe4..6340a7d9 100644 --- a/scripts/prompt_matrix.py +++ b/scripts/prompt_matrix.py @@ -54,7 +54,7 @@ class Script(scripts.Script): prompt_type = gr.Radio(["positive", "negative"], label="Select prompt", elem_id=self.elem_id("prompt_type"), value="positive") variations_delimiter = gr.Radio(["comma", "space"], label="Select joining char", elem_id=self.elem_id("variations_delimiter"), value="comma") with gr.Column(): - margin_size = gr.Slider(label="Grid margins (px)", min=0, max=500, value=0, step=2, elem_id=self.elem_id("margin_size")) + margin_size = gr.Slider(label="Grid margins (px)", minimum=0, maximum=500, value=0, step=2, elem_id=self.elem_id("margin_size")) return [put_at_start, different_seeds, prompt_type, variations_delimiter, margin_size] diff --git a/scripts/xyz_grid.py b/scripts/xyz_grid.py index 62e03d02..c375d2c0 100644 --- a/scripts/xyz_grid.py +++ b/scripts/xyz_grid.py @@ -360,7 +360,7 @@ class Script(scripts.Script): include_lone_images = gr.Checkbox(label='Include Sub Images', value=False, elem_id=self.elem_id("include_lone_images")) include_sub_grids = gr.Checkbox(label='Include Sub Grids', value=False, elem_id=self.elem_id("include_sub_grids")) with gr.Column(): - margin_size = gr.Slider(label="Grid margins (px)", min=0, max=500, value=0, step=2, elem_id=self.elem_id("margin_size")) + margin_size = gr.Slider(label="Grid margins (px)", minimum=0, maximum=500, value=0, step=2, elem_id=self.elem_id("margin_size")) with gr.Row(variant="compact", elem_id="swap_axes"): swap_xy_axes_button = gr.Button(value="Swap X/Y axes", elem_id="xy_grid_swap_axes_button") From 11183b4d905d14c6a0164a4d13675b89b1bf4ceb Mon Sep 17 00:00:00 2001 From: AUTOMATIC <16777216c@gmail.com> Date: Sun, 19 Feb 2023 12:44:56 +0300 Subject: [PATCH 060/278] fix for #6700 --- modules/textual_inversion/dataset.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/modules/textual_inversion/dataset.py b/modules/textual_inversion/dataset.py index 1568b2b8..af9fbcf2 100644 --- a/modules/textual_inversion/dataset.py +++ b/modules/textual_inversion/dataset.py @@ -115,7 +115,7 @@ class PersonalizedBase(Dataset): weight /= weight.mean() elif use_weight: #If an image does not have a alpha channel, add a ones weight map anyway so we can stack it later - weight = torch.ones([channels] + latent_size) + weight = torch.ones(latent_sample.shape) else: weight = None From d84f3cf7a7743bc91cd5ba524c76cf859e021b49 Mon Sep 17 00:00:00 2001 From: AUTOMATIC <16777216c@gmail.com> Date: Sun, 19 Feb 2023 13:11:48 +0300 Subject: [PATCH 061/278] split #7300 into multiple lines --- webui.py | 10 +++++++++- 1 file changed, 9 insertions(+), 1 deletion(-) diff --git a/webui.py b/webui.py index 69312a19..9e8b486a 100644 --- a/webui.py +++ b/webui.py @@ -207,6 +207,14 @@ def webui(): if cmd_opts.gradio_queue: shared.demo.queue(64) + gradio_auth_creds = [] + if cmd_opts.gradio_auth: + gradio_auth_creds += cmd_opts.gradio_auth.strip('"').replace('\n', '').split(',') + if cmd_opts.gradio_auth_path: + with open(cmd_opts.gradio_auth_path, 'r', encoding="utf8") as file: + for line in file.readlines(): + gradio_auth_creds += [x.strip() for x in line.split(',')] + app, local_url, share_url = shared.demo.launch( share=cmd_opts.share, server_name=server_name, @@ -214,7 +222,7 @@ def webui(): ssl_keyfile=cmd_opts.tls_keyfile, ssl_certfile=cmd_opts.tls_certfile, debug=cmd_opts.gradio_debug, - auth=[tuple(cred.split(':')) for cred in (cmd_opts.gradio_auth.strip('"').replace('\n','').split(',') + (open(cmd_opts.gradio_auth_path, 'r').read().strip().replace('\n','').split(',') if cmd_opts.gradio_auth_path and os.path.exists(cmd_opts.gradio_auth_path) else None))] if cmd_opts.gradio_auth or (cmd_opts.gradio_auth_path and os.path.exists(cmd_opts.gradio_auth_path)) else None, + auth=[tuple(cred.split(':')) for cred in gradio_auth_creds] if gradio_auth_creds else None, inbrowser=cmd_opts.autolaunch, prevent_thread_lock=True ) From c77f01ff31072715afe80413ecaf6b3d00797d34 Mon Sep 17 00:00:00 2001 From: w-e-w <40751091+w-e-w@users.noreply.github.com> Date: Sun, 19 Feb 2023 20:37:40 +0900 Subject: [PATCH 062/278] fix auto sd download issue --- modules/sd_models.py | 8 +++++++- modules/shared.py | 1 + 2 files changed, 8 insertions(+), 1 deletion(-) diff --git a/modules/sd_models.py b/modules/sd_models.py index 127e9663..ac4903f4 100644 --- a/modules/sd_models.py +++ b/modules/sd_models.py @@ -105,9 +105,15 @@ def checkpoint_tiles(): def list_models(): checkpoints_list.clear() checkpoint_alisases.clear() - model_list = modelloader.load_models(model_path=model_path, model_url="https://huggingface.co/runwayml/stable-diffusion-v1-5/resolve/main/v1-5-pruned-emaonly.safetensors", command_path=shared.cmd_opts.ckpt_dir, ext_filter=[".ckpt", ".safetensors"], download_name="v1-5-pruned-emaonly.safetensors", ext_blacklist=[".vae.ckpt", ".vae.safetensors"]) cmd_ckpt = shared.cmd_opts.ckpt + if shared.cmd_opts.no_download_sd_model or cmd_ckpt != shared.sd_model_file: + model_url = None + else: + model_url = "https://huggingface.co/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"]) + if os.path.exists(cmd_ckpt): checkpoint_info = CheckpointInfo(cmd_ckpt) checkpoint_info.register() diff --git a/modules/shared.py b/modules/shared.py index 2c2edfbd..805f9cc1 100644 --- a/modules/shared.py +++ b/modules/shared.py @@ -108,6 +108,7 @@ parser.add_argument("--server-name", type=str, help="Sets hostname of server", d parser.add_argument("--gradio-queue", action='store_true', help="Uses gradio queue; experimental option; breaks restart UI button") parser.add_argument("--skip-version-check", action='store_true', help="Do not check versions of torch and xformers") parser.add_argument("--no-hashing", action='store_true', help="disable sha256 hashing of checkpoints to help loading performance", default=False) +parser.add_argument("--no-download-sd-model", action='store_true', help="don't download SD1.5 model even if no model is found in --ckpt-dir", default=False) script_loading.preload_extensions(extensions.extensions_dir, parser) From 014e7323f6f54a38183f8de52dc83f2248eee251 Mon Sep 17 00:00:00 2001 From: w-e-w <40751091+w-e-w@users.noreply.github.com> Date: Sun, 19 Feb 2023 20:49:07 +0900 Subject: [PATCH 063/278] when exists --- 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 ac4903f4..93959f55 100644 --- a/modules/sd_models.py +++ b/modules/sd_models.py @@ -107,7 +107,7 @@ def list_models(): checkpoint_alisases.clear() cmd_ckpt = shared.cmd_opts.ckpt - if shared.cmd_opts.no_download_sd_model or cmd_ckpt != shared.sd_model_file: + 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" From 83829471decbde64d335eb510d4a5670baf68773 Mon Sep 17 00:00:00 2001 From: Vladimir Mandic Date: Sun, 19 Feb 2023 09:21:44 -0500 Subject: [PATCH 064/278] make ui as multiselect instead of string list --- modules/shared.py | 3 ++- modules/ui.py | 7 +++++-- 2 files changed, 7 insertions(+), 3 deletions(-) diff --git a/modules/shared.py b/modules/shared.py index 1a1abeb2..a7c5f58e 100644 --- a/modules/shared.py +++ b/modules/shared.py @@ -305,6 +305,7 @@ def list_samplers(): hide_dirs = {"visible": not cmd_opts.hide_ui_dir_config} +tab_names = [] options_templates = {} @@ -460,7 +461,7 @@ options_templates.update(options_section(('ui', "User interface"), { "keyedit_precision_attention": OptionInfo(0.1, "Ctrl+up/down precision when editing (attention:1.1)", gr.Slider, {"minimum": 0.01, "maximum": 0.2, "step": 0.001}), "keyedit_precision_extra": OptionInfo(0.05, "Ctrl+up/down precision when editing ", gr.Slider, {"minimum": 0.01, "maximum": 0.2, "step": 0.001}), "quicksettings": OptionInfo("sd_model_checkpoint", "Quicksettings list"), - "hidden_tabs": OptionInfo("", "Hidden UI tabs"), + "hidden_tabs": OptionInfo([], "Hidden UI tabs (requires restart)", ui_components.DropdownMulti, lambda: {"choices": [x for x in tab_names]}), "ui_reorder": OptionInfo(", ".join(ui_reorder_categories), "txt2img/img2img UI item order"), "ui_extra_networks_tab_reorder": OptionInfo("", "Extra networks tab order"), "localization": OptionInfo("None", "Localization (requires restart)", gr.Dropdown, lambda: {"choices": ["None"] + list(localization.localizations.keys())}, refresh=lambda: localization.list_localizations(cmd_opts.localizations_dir)), diff --git a/modules/ui.py b/modules/ui.py index a4ecd41b..5ac249b2 100644 --- a/modules/ui.py +++ b/modules/ui.py @@ -1563,6 +1563,10 @@ def create_ui(): extensions_interface = ui_extensions.create_ui() interfaces += [(extensions_interface, "Extensions", "extensions")] + shared.tab_names = [] + for _interface, label, _ifid in interfaces: + shared.tab_names.append(label) + with gr.Blocks(css=css, analytics_enabled=False, title="Stable Diffusion") as demo: with gr.Row(elem_id="quicksettings", variant="compact"): for i, k, item in sorted(quicksettings_list, key=lambda x: quicksettings_names.get(x[1], x[0])): @@ -1572,9 +1576,8 @@ def create_ui(): parameters_copypaste.connect_paste_params_buttons() with gr.Tabs(elem_id="tabs") as tabs: - hidden_tabs = [x.lower().strip() for x in shared.opts.hidden_tabs.split(",")] for interface, label, ifid in interfaces: - if label.lower() in hidden_tabs: + if label in shared.opts.hidden_tabs: continue with gr.TabItem(label, id=ifid, elem_id='tab_' + ifid): interface.render() From 65995a2ea38a1a0afd06cb508a4f65fd0d3a1743 Mon Sep 17 00:00:00 2001 From: AUTOMATIC <16777216c@gmail.com> Date: Sun, 19 Feb 2023 18:31:07 +0300 Subject: [PATCH 065/278] possible fix for #7804 --- scripts/xyz_grid.py | 3 +++ 1 file changed, 3 insertions(+) diff --git a/scripts/xyz_grid.py b/scripts/xyz_grid.py index 4afcbbfd..53511b12 100644 --- a/scripts/xyz_grid.py +++ b/scripts/xyz_grid.py @@ -244,6 +244,9 @@ def draw_xyz_grid(p, xs, ys, zs, x_labels, y_labels, z_labels, cell, draw_legend cell_mode = processed_image.mode cell_size = processed_image.size processed_result.images = [Image.new(cell_mode, cell_size)] + processed_result.all_prompts = [processed.prompt] + processed_result.all_seeds = [processed.seed] + processed_result.infotexts = [processed.infotexts[0]] image_cache[index(ix, iy, iz)] = processed_image if include_lone_images: From ca2b8faa83076a21dd14c974f03f88eb6da57485 Mon Sep 17 00:00:00 2001 From: EllangoK Date: Sun, 19 Feb 2023 14:38:22 -0500 Subject: [PATCH 066/278] custom height, width settings for extra networks --- html/extra-networks-card.html | 2 +- modules/shared.py | 2 ++ modules/ui_extra_networks.py | 6 +++++- 3 files changed, 8 insertions(+), 2 deletions(-) diff --git a/html/extra-networks-card.html b/html/extra-networks-card.html index 8a5e2fbd..250bb60d 100644 --- a/html/extra-networks-card.html +++ b/html/extra-networks-card.html @@ -1,4 +1,4 @@ -
    +
      diff --git a/modules/shared.py b/modules/shared.py index e324a48a..4edcb5ef 100644 --- a/modules/shared.py +++ b/modules/shared.py @@ -438,6 +438,8 @@ options_templates.update(options_section(('interrogate', "Interrogate Options"), options_templates.update(options_section(('extra_networks', "Extra Networks"), { "extra_networks_default_view": OptionInfo("cards", "Default view for Extra Networks", gr.Dropdown, {"choices": ["cards", "thumbs"]}), "extra_networks_default_multiplier": OptionInfo(1.0, "Multiplier for extra networks", gr.Slider, {"minimum": 0.0, "maximum": 1.0, "step": 0.01}), + "extra_networks_card_width": OptionInfo(0, "Card width for Extra Networks (em)"), + "extra_networks_card_height": OptionInfo(0, "Card height for Extra Networks (em)"), "sd_hypernetwork": OptionInfo("None", "Add hypernetwork to prompt", gr.Dropdown, lambda: {"choices": [""] + [x for x in hypernetworks.keys()]}, refresh=reload_hypernetworks), })) diff --git a/modules/ui_extra_networks.py b/modules/ui_extra_networks.py index 71f1d81f..0c7ba173 100644 --- a/modules/ui_extra_networks.py +++ b/modules/ui_extra_networks.py @@ -124,8 +124,12 @@ class ExtraNetworksPage: if onclick is None: onclick = '"' + html.escape(f"""return cardClicked({json.dumps(tabname)}, {item["prompt"]}, {"true" if self.allow_negative_prompt else "false"})""") + '"' + height = f"height: {shared.opts.extra_networks_card_height}em;" if shared.opts.extra_networks_card_height else '' + width = f"width: {shared.opts.extra_networks_card_width}em;" if shared.opts.extra_networks_card_width else '' + background_image = f"background-image: url(\"{html.escape(preview)}\");" if preview else '' + args = { - "preview_html": "style='background-image: url(\"" + html.escape(preview) + "\")'" if preview else '', + "style": f"'{height}{width}{background_image}'", "prompt": item.get("prompt", None), "tabname": json.dumps(tabname), "local_preview": json.dumps(item["local_preview"]), From f71a3c9c3a8f6d9e5a6231f9f94900201da3e554 Mon Sep 17 00:00:00 2001 From: w-e-w <40751091+w-e-w@users.noreply.github.com> Date: Mon, 20 Feb 2023 17:30:13 +0900 Subject: [PATCH 067/278] convert resolution to int using round() --- modules/images.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/modules/images.py b/modules/images.py index 38404de3..5b80c23e 100644 --- a/modules/images.py +++ b/modules/images.py @@ -582,9 +582,9 @@ def save_image(image, path, basename, seed=None, prompt=None, extension='png', i ratio = image.width / image.height if oversize and ratio > 1: - image = image.resize((opts.target_side_length, image.height * opts.target_side_length // image.width), LANCZOS) + image = image.resize((round(opts.target_side_length), round(image.height * opts.target_side_length / image.width)), LANCZOS) elif oversize: - image = image.resize((image.width * opts.target_side_length // image.height, opts.target_side_length), LANCZOS) + image = image.resize((round(image.width * opts.target_side_length / image.height), round(opts.target_side_length)), LANCZOS) try: _atomically_save_image(image, fullfn_without_extension, ".jpg") From bab972ff8ab6be1132ca2b58a2c4fadac0a0685d Mon Sep 17 00:00:00 2001 From: EllangoK Date: Mon, 20 Feb 2023 10:16:55 -0500 Subject: [PATCH 068/278] fixes newline being detected as its own entry --- 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 53511b12..d0ff5cb8 100644 --- a/scripts/xyz_grid.py +++ b/scripts/xyz_grid.py @@ -418,7 +418,7 @@ class Script(scripts.Script): if opt.label == 'Nothing': return [0] - valslist = [x.strip() for x in chain.from_iterable(csv.reader(StringIO(vals)))] + valslist = [x.strip() for x in chain.from_iterable(csv.reader(StringIO(vals))) if x] if opt.type == int: valslist_ext = [] From b0f2653541219486c8b8cbdbf8ce80caf3f62ef8 Mon Sep 17 00:00:00 2001 From: xSinStarx <98231899+xSinStarx@users.noreply.github.com> Date: Mon, 20 Feb 2023 12:39:38 -0800 Subject: [PATCH 069/278] Fixes img2img Negative Token Counter The img2img negative token counter is counting the txt2img negative prompt. --- modules/ui.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/modules/ui.py b/modules/ui.py index 0516c643..a37b1739 100644 --- a/modules/ui.py +++ b/modules/ui.py @@ -939,7 +939,7 @@ def create_ui(): ) token_button.click(fn=update_token_counter, inputs=[img2img_prompt, steps], outputs=[token_counter]) - negative_token_button.click(fn=wrap_queued_call(update_token_counter), inputs=[txt2img_negative_prompt, steps], outputs=[negative_token_counter]) + negative_token_button.click(fn=wrap_queued_call(update_token_counter), inputs=[img2img_negative_prompt, steps], outputs=[negative_token_counter]) ui_extra_networks.setup_ui(extra_networks_ui_img2img, img2img_gallery) From 32a4c8d961df3da4534c98fd0573d854cff1bb91 Mon Sep 17 00:00:00 2001 From: Kilvoctu Date: Mon, 20 Feb 2023 15:14:06 -0600 Subject: [PATCH 070/278] use emojis for extra network buttons MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 🔄 for refresh ❌ for close --- modules/ui_extra_networks.py | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/modules/ui_extra_networks.py b/modules/ui_extra_networks.py index 71f1d81f..8786fde6 100644 --- a/modules/ui_extra_networks.py +++ b/modules/ui_extra_networks.py @@ -13,6 +13,10 @@ from modules.generation_parameters_copypaste import image_from_url_text extra_pages = [] allowed_dirs = set() +# Using constants for these since the variation selector isn't visible. +# Important that they exactly match script.js for tooltip to work. +refresh_symbol = '\U0001f504' # 🔄 +close_symbol = '\U0000274C' # ❌ def register_page(page): """registers extra networks page for the UI; recommend doing it in on_before_ui() callback for extensions""" @@ -182,8 +186,8 @@ def create_ui(container, button, tabname): ui.pages.append(page_elem) filter = gr.Textbox('', show_label=False, elem_id=tabname+"_extra_search", placeholder="Search...", visible=False) - button_refresh = gr.Button('Refresh', elem_id=tabname+"_extra_refresh") - button_close = gr.Button('Close', elem_id=tabname+"_extra_close") + button_refresh = gr.Button(refresh_symbol, elem_id=tabname+"_extra_refresh") + button_close = gr.Button(close_symbol, elem_id=tabname+"_extra_close") ui.button_save_preview = gr.Button('Save preview', elem_id=tabname+"_save_preview", visible=False) ui.preview_target_filename = gr.Textbox('Preview save filename', elem_id=tabname+"_preview_filename", visible=False) From a2d635ad135241a0a40f67f7e1638c9c8a4ded04 Mon Sep 17 00:00:00 2001 From: space-nuko <24979496+space-nuko@users.noreply.github.com> Date: Wed, 22 Feb 2023 01:52:53 -0800 Subject: [PATCH 071/278] Add before_process_batch script callback --- modules/processing.py | 3 +++ modules/scripts.py | 23 +++++++++++++++++++++++ 2 files changed, 26 insertions(+) diff --git a/modules/processing.py b/modules/processing.py index 2009d3bf..187e98fd 100644 --- a/modules/processing.py +++ b/modules/processing.py @@ -597,6 +597,9 @@ def process_images_inner(p: StableDiffusionProcessing) -> Processed: seeds = p.all_seeds[n * p.batch_size:(n + 1) * p.batch_size] subseeds = p.all_subseeds[n * p.batch_size:(n + 1) * p.batch_size] + if p.scripts is not None: + p.scripts.before_process_batch(p, batch_number=n, prompts=prompts, seeds=seeds, subseeds=subseeds) + if len(prompts) == 0: break diff --git a/modules/scripts.py b/modules/scripts.py index 24056a12..e6a505b3 100644 --- a/modules/scripts.py +++ b/modules/scripts.py @@ -80,6 +80,20 @@ class Script: pass + def before_process_batch(self, p, *args, **kwargs): + """ + Called before extra networks are parsed from the prompt, so you can add + new extra network keywords to the prompt with this callback. + + **kwargs will have those items: + - batch_number - index of current batch, from 0 to number of batches-1 + - prompts - list of prompts for current batch; you can change contents of this list but changing the number of entries will likely break things + - seeds - list of seeds for current batch + - subseeds - list of subseeds for current batch + """ + + pass + def process_batch(self, p, *args, **kwargs): """ Same as process(), but called for every batch. @@ -388,6 +402,15 @@ class ScriptRunner: print(f"Error running process: {script.filename}", file=sys.stderr) print(traceback.format_exc(), file=sys.stderr) + def before_process_batch(self, p, **kwargs): + for script in self.alwayson_scripts: + try: + script_args = p.script_args[script.args_from:script.args_to] + script.before_process_batch(p, *script_args, **kwargs) + except Exception: + print(f"Error running before_process_batch: {script.filename}", file=sys.stderr) + print(traceback.format_exc(), file=sys.stderr) + def process_batch(self, p, **kwargs): for script in self.alwayson_scripts: try: From 2c58d373dd408153dc126f6eba1525d32fbf92bb Mon Sep 17 00:00:00 2001 From: 112292454 <92578848+112292454@users.noreply.github.com> Date: Wed, 22 Feb 2023 21:40:42 +0800 Subject: [PATCH 072/278] Update prompt_matrix.py MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit this file last commit fixed common situation when using both prompts matrix and high-res。 but if we just open matrix option,but not use ‘|’,we will only get one pic,and `processed.images[0].width, processed.images[1].height` will cause a index out of bounds exception --- scripts/prompt_matrix.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/scripts/prompt_matrix.py b/scripts/prompt_matrix.py index b1c486d4..7790ac38 100644 --- a/scripts/prompt_matrix.py +++ b/scripts/prompt_matrix.py @@ -100,7 +100,7 @@ class Script(scripts.Script): processed = process_images(p) grid = images.image_grid(processed.images, p.batch_size, rows=1 << ((len(prompt_matrix_parts) - 1) // 2)) - grid = images.draw_prompt_matrix(grid, processed.images[0].width, processed.images[1].height, prompt_matrix_parts, margin_size) + grid = images.draw_prompt_matrix(grid, p.hr_upscale_to_x, p.hr_upscale_to_y,, prompt_matrix_parts, margin_size) processed.images.insert(0, grid) processed.index_of_first_image = 1 processed.infotexts.insert(0, processed.infotexts[0]) From 2fa91cbee65429e611861df1c32657c941f4acaf Mon Sep 17 00:00:00 2001 From: 112292454 <92578848+112292454@users.noreply.github.com> Date: Thu, 23 Feb 2023 01:55:07 +0800 Subject: [PATCH 073/278] Update prompt_matrix.py 1 --- scripts/prompt_matrix.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/scripts/prompt_matrix.py b/scripts/prompt_matrix.py index 7790ac38..e9b11517 100644 --- a/scripts/prompt_matrix.py +++ b/scripts/prompt_matrix.py @@ -100,7 +100,7 @@ class Script(scripts.Script): processed = process_images(p) grid = images.image_grid(processed.images, p.batch_size, rows=1 << ((len(prompt_matrix_parts) - 1) // 2)) - grid = images.draw_prompt_matrix(grid, p.hr_upscale_to_x, p.hr_upscale_to_y,, prompt_matrix_parts, margin_size) + grid = images.draw_prompt_matrix(grid, processed.images[0].width, processed.images[0].height, prompt_matrix_parts, margin_size) processed.images.insert(0, grid) processed.index_of_first_image = 1 processed.infotexts.insert(0, processed.infotexts[0]) From 6825de7bc811d777ff0d462e5668fa4fba73a889 Mon Sep 17 00:00:00 2001 From: Thomas Young <35073576+DrakeRichards@users.noreply.github.com> Date: Wed, 22 Feb 2023 15:31:49 -0600 Subject: [PATCH 074/278] Added results selector This causes the querySelectorAll function to only select images in a results div, ignoring images that might be in an extension's gallery. --- javascript/notification.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/javascript/notification.js b/javascript/notification.js index 040a3afa..5ae6df24 100644 --- a/javascript/notification.js +++ b/javascript/notification.js @@ -15,7 +15,7 @@ onUiUpdate(function(){ } } - const galleryPreviews = gradioApp().querySelectorAll('div[id^="tab_"][style*="display: block"] img.h-full.w-full.overflow-hidden'); + const galleryPreviews = gradioApp().querySelectorAll('div[id^="tab_"][style*="display: block"] div[id$="_results"] img.h-full.w-full.overflow-hidden'); if (galleryPreviews == null) return; From b90cad7f3136bbe04efeee2a00e95d0cc6ce1a4a Mon Sep 17 00:00:00 2001 From: "fkunn1326@users.noreply.github.com" Date: Thu, 23 Feb 2023 03:29:22 +0000 Subject: [PATCH 075/278] Add .mjs support for extensions --- modules/ui.py | 3 +++ 1 file changed, 3 insertions(+) diff --git a/modules/ui.py b/modules/ui.py index 0516c643..2509ce2d 100644 --- a/modules/ui.py +++ b/modules/ui.py @@ -1754,6 +1754,9 @@ def reload_javascript(): for script in modules.scripts.list_scripts("javascript", ".js"): head += f'\n' + for script in modules.scripts.list_scripts("javascript", ".mjs"): + head += f'\n' + head += f'\n' def template_response(*args, **kwargs): From ac4c7f05cd38dfa99cf64f7ddb9b1656e70a13c5 Mon Sep 17 00:00:00 2001 From: Tpinion Date: Fri, 24 Feb 2023 00:42:29 +0800 Subject: [PATCH 076/278] Filter out temporary files that will be generated if the download fails. --- 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 01fb7bd8..8d84bbc9 100644 --- a/modules/codeformer_model.py +++ b/modules/codeformer_model.py @@ -55,7 +55,7 @@ def setup_model(dirname): if self.net is not None and self.face_helper is not None: self.net.to(devices.device_codeformer) return self.net, self.face_helper - model_paths = modelloader.load_models(model_path, model_url, self.cmd_dir, download_name='codeformer-v0.1.0.pth') + model_paths = modelloader.load_models(model_path, model_url, self.cmd_dir, download_name='codeformer-v0.1.0.pth', ext_filter=['.pth']) if len(model_paths) != 0: ckpt_path = model_paths[0] else: From 327186b484c344598522a989a4b4859d6b90fb04 Mon Sep 17 00:00:00 2001 From: laksjdjf Date: Fri, 24 Feb 2023 14:03:46 +0900 Subject: [PATCH 077/278] Update script_callbacks.py --- modules/script_callbacks.py | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/modules/script_callbacks.py b/modules/script_callbacks.py index edd0e2a7..c5bb3e71 100644 --- a/modules/script_callbacks.py +++ b/modules/script_callbacks.py @@ -29,7 +29,7 @@ class ImageSaveParams: class CFGDenoiserParams: - def __init__(self, x, image_cond, sigma, sampling_step, total_sampling_steps): + def __init__(self, x, image_cond, sigma, sampling_step, total_sampling_steps, tensor, uncond): self.x = x """Latent image representation in the process of being denoised""" @@ -44,6 +44,12 @@ class CFGDenoiserParams: self.total_sampling_steps = total_sampling_steps """Total number of sampling steps planned""" + + self.tensor = tensor + """ Encoder hidden states of condtioning""" + + self.uncond = uncond + """ Encoder hidden states of unconditioning""" class CFGDenoisedParams: From 9a1435946ce7cc7f2cdaa0a312e1c0d296b8c276 Mon Sep 17 00:00:00 2001 From: laksjdjf Date: Fri, 24 Feb 2023 14:04:23 +0900 Subject: [PATCH 078/278] Update sd_samplers_kdiffusion.py --- modules/sd_samplers_kdiffusion.py | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/modules/sd_samplers_kdiffusion.py b/modules/sd_samplers_kdiffusion.py index 528f513f..ea974be0 100644 --- a/modules/sd_samplers_kdiffusion.py +++ b/modules/sd_samplers_kdiffusion.py @@ -101,11 +101,13 @@ class CFGDenoiser(torch.nn.Module): sigma_in = torch.cat([torch.stack([sigma[i] for _ in range(n)]) for i, n in enumerate(repeats)] + [sigma] + [sigma]) image_cond_in = torch.cat([torch.stack([image_cond[i] for _ in range(n)]) for i, n in enumerate(repeats)] + [image_cond] + [torch.zeros_like(self.init_latent)]) - denoiser_params = CFGDenoiserParams(x_in, image_cond_in, sigma_in, state.sampling_step, state.sampling_steps) + denoiser_params = CFGDenoiserParams(x_in, image_cond_in, sigma_in, state.sampling_step, state.sampling_steps, tensor, uncond) cfg_denoiser_callback(denoiser_params) x_in = denoiser_params.x image_cond_in = denoiser_params.image_cond sigma_in = denoiser_params.sigma + tensor = denoiser_params.tensor + uncond = denoiser_params.uncond if tensor.shape[1] == uncond.shape[1]: if not is_edit_model: From 534cf60afb547de72891e1e87b59a1433aadeee3 Mon Sep 17 00:00:00 2001 From: laksjdjf Date: Fri, 24 Feb 2023 14:26:55 +0900 Subject: [PATCH 079/278] Update script_callbacks.py --- 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 c5bb3e71..d1703135 100644 --- a/modules/script_callbacks.py +++ b/modules/script_callbacks.py @@ -46,7 +46,7 @@ class CFGDenoiserParams: """Total number of sampling steps planned""" self.tensor = tensor - """ Encoder hidden states of condtioning""" + """ Encoder hidden states of conditioning""" self.uncond = uncond """ Encoder hidden states of unconditioning""" From b15bc73c99e6fbbeffdbdbeab39ba30276021d4b Mon Sep 17 00:00:00 2001 From: Brad Smith Date: Fri, 24 Feb 2023 14:22:58 -0500 Subject: [PATCH 080/278] sort upscalers by name --- modules/modelloader.py | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/modules/modelloader.py b/modules/modelloader.py index fc3f6249..a7ac338c 100644 --- a/modules/modelloader.py +++ b/modules/modelloader.py @@ -6,7 +6,7 @@ from urllib.parse import urlparse from basicsr.utils.download_util import load_file_from_url from modules import shared -from modules.upscaler import Upscaler +from modules.upscaler import Upscaler, UpscalerNone from modules.paths import script_path, models_path @@ -169,4 +169,8 @@ def load_upscalers(): scaler = cls(commandline_options.get(cmd_name, None)) datas += scaler.scalers - shared.sd_upscalers = datas + shared.sd_upscalers = sorted( + datas, + # Special case for UpscalerNone keeps it at the beginning of the list. + key=lambda x: x.name if not isinstance(x.scaler, UpscalerNone) else "" + ) From aa108bd02a8282e8213fa6c5967e3c47e49bb43f Mon Sep 17 00:00:00 2001 From: missionfloyd Date: Fri, 24 Feb 2023 20:57:18 -0700 Subject: [PATCH 081/278] Add lossless webp option --- modules/images.py | 2 +- modules/shared.py | 1 + 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/modules/images.py b/modules/images.py index 5b80c23e..7df2b08c 100644 --- a/modules/images.py +++ b/modules/images.py @@ -556,7 +556,7 @@ def save_image(image, path, basename, seed=None, prompt=None, extension='png', i elif image_to_save.mode == 'I;16': image_to_save = image_to_save.point(lambda p: p * 0.0038910505836576).convert("RGB" if extension.lower() == ".webp" else "L") - image_to_save.save(temp_file_path, format=image_format, quality=opts.jpeg_quality) + image_to_save.save(temp_file_path, format=image_format, quality=opts.jpeg_quality, lossless=opts.webp_lossless) if opts.enable_pnginfo and info is not None: exif_bytes = piexif.dump({ diff --git a/modules/shared.py b/modules/shared.py index 805f9cc1..51101988 100644 --- a/modules/shared.py +++ b/modules/shared.py @@ -327,6 +327,7 @@ options_templates.update(options_section(('saving-images', "Saving images/grids" "save_images_before_highres_fix": OptionInfo(False, "Save a copy of image before applying highres fix."), "save_images_before_color_correction": OptionInfo(False, "Save a copy of image before applying color correction to img2img results"), "jpeg_quality": OptionInfo(80, "Quality for saved jpeg images", gr.Slider, {"minimum": 1, "maximum": 100, "step": 1}), + "webp_lossless": OptionInfo(False, "Use lossless compression for webp images"), "export_for_4chan": OptionInfo(True, "If the saved image file size is above the limit, or its either width or height are above the limit, save a downscaled copy as JPG"), "img_downscale_threshold": OptionInfo(4.0, "File size limit for the above option, MB", gr.Number), "target_side_length": OptionInfo(4000, "Width/height limit for the above option, in pixels", gr.Number), From ed43a822b2df601fd1d6d2a3b97ae5924c06ca98 Mon Sep 17 00:00:00 2001 From: Vladimir Mandic Date: Sat, 25 Feb 2023 12:56:03 -0500 Subject: [PATCH 082/278] fix progressbar --- javascript/progressbar.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/javascript/progressbar.js b/javascript/progressbar.js index ff6d757b..9ccc9da4 100644 --- a/javascript/progressbar.js +++ b/javascript/progressbar.js @@ -139,7 +139,7 @@ function requestProgress(id_task, progressbarContainer, gallery, atEnd, onProgre var divProgress = document.createElement('div') divProgress.className='progressDiv' - divProgress.style.display = opts.show_progressbar ? "" : "none" + divProgress.style.display = opts.show_progressbar ? "block" : "none" var divInner = document.createElement('div') divInner.className='progress' From 6d92d95a33e46aea7a7b8c136a76a621d7fc4f52 Mon Sep 17 00:00:00 2001 From: Adam Huganir Date: Sat, 25 Feb 2023 19:15:06 +0000 Subject: [PATCH 083/278] git 3.1.30 api change --- modules/extensions.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/modules/extensions.py b/modules/extensions.py index 3eef9eaf..ed4b58fe 100644 --- a/modules/extensions.py +++ b/modules/extensions.py @@ -66,7 +66,7 @@ class Extension: def check_updates(self): repo = git.Repo(self.path) - for fetch in repo.remote().fetch("--dry-run"): + for fetch in repo.remote().fetch(dry_run=True): if fetch.flags != fetch.HEAD_UPTODATE: self.can_update = True self.status = "behind" @@ -79,8 +79,8 @@ class Extension: repo = git.Repo(self.path) # 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') - repo.git.reset('--hard', 'origin') + repo.git.fetch(all=True) + repo.git.reset('origin', hard=True) def list_extensions(): From 3c6459154fb115ea7cf1a0c5f3f0761a192dfea3 Mon Sep 17 00:00:00 2001 From: Vladimir Mandic Date: Mon, 27 Feb 2023 17:28:04 -0500 Subject: [PATCH 084/278] add check for resulting image size --- modules/shared.py | 1 + scripts/xyz_grid.py | 6 ++++++ 2 files changed, 7 insertions(+) diff --git a/modules/shared.py b/modules/shared.py index 805f9cc1..ec08b7be 100644 --- a/modules/shared.py +++ b/modules/shared.py @@ -330,6 +330,7 @@ options_templates.update(options_section(('saving-images', "Saving images/grids" "export_for_4chan": OptionInfo(True, "If the saved image file size is above the limit, or its either width or height are above the limit, save a downscaled copy as JPG"), "img_downscale_threshold": OptionInfo(4.0, "File size limit for the above option, MB", gr.Number), "target_side_length": OptionInfo(4000, "Width/height limit for the above option, in pixels", gr.Number), + "img_max_size_mp": OptionInfo(200, "Maximum image size, in megapixels", gr.Number), "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"), diff --git a/scripts/xyz_grid.py b/scripts/xyz_grid.py index 53511b12..1ba954ac 100644 --- a/scripts/xyz_grid.py +++ b/scripts/xyz_grid.py @@ -484,6 +484,12 @@ class Script(scripts.Script): z_opt = self.current_axis_options[z_type] zs = process_axis(z_opt, z_values) + # this could be moved to common code, but unlikely to be ever triggered anywhere else + Image.MAX_IMAGE_PIXELS = opts.img_max_size_mp * 1.1 # allow 10% overhead for margins and legend + grid_mp = round(len(xs) * len(ys) * len(zs) * p.width * p.height / 1000000) + if grid_mp > opts.img_max_size_mp: + return Processed(p, [], p.seed, info=f'Error: Resulting grid would be too large ({grid_mp} MPixels) (max configured size is {opts.img_max_size_mp} MPixels)') + def fix_axis_seeds(axis_opt, axis_list): if axis_opt.label in ['Seed', 'Var. seed']: return [int(random.randrange(4294967294)) if val is None or val == '' or val == -1 else val for val in axis_list] From 3b6de96467b0ee6d59f3aaf4bafd1467633e14c6 Mon Sep 17 00:00:00 2001 From: Vespinian Date: Sun, 26 Feb 2023 19:17:58 -0500 Subject: [PATCH 085/278] Added alwayson_script_name and alwayson_script_args to api Added 2 additional possible entries in the api request: alwayson_script_name, a string list, and, alwayson_script_args, a list of list containing the args of each script. This allows us to send args to always on script and keep backwards compatibility with old script_name and script_arg api params --- modules/api/api.py | 111 +++++++++++++++++++++++++++++++++++++----- modules/api/models.py | 4 +- 2 files changed, 100 insertions(+), 15 deletions(-) diff --git a/modules/api/api.py b/modules/api/api.py index 5a9ac5f1..a1cdebb8 100644 --- a/modules/api/api.py +++ b/modules/api/api.py @@ -163,20 +163,26 @@ class Api: raise HTTPException(status_code=401, detail="Incorrect username or password", headers={"WWW-Authenticate": "Basic"}) - def get_script(self, script_name, script_runner): - if script_name is None: + def get_selectable_script(self, script_name, script_runner): + if script_name is None or script_name == "": return None, None - if not script_runner.scripts: - script_runner.initialize_scripts(False) - ui.create_ui() - script_idx = script_name_to_index(script_name, script_runner.selectable_scripts) script = script_runner.selectable_scripts[script_idx] return script, script_idx + def get_script(self, script_name, script_runner): + for script in script_runner.scripts: + if script_name.lower() == script.title().lower(): + return script + return None + def text2imgapi(self, txt2imgreq: StableDiffusionTxt2ImgProcessingAPI): - script, script_idx = self.get_script(txt2imgreq.script_name, scripts.scripts_txt2img) + script_runner = scripts.scripts_txt2img + if not script_runner.scripts: + script_runner.initialize_scripts(False) + ui.create_ui() + api_selectable_scripts, api_selectable_script_idx = self.get_selectable_script(txt2imgreq.script_name, script_runner) populate = txt2imgreq.copy(update={ # Override __init__ params "sampler_name": validate_sampler_name(txt2imgreq.sampler_name or txt2imgreq.sampler_index), @@ -184,22 +190,59 @@ class Api: "do_not_save_grid": True } ) + if populate.sampler_name: populate.sampler_index = None # prevent a warning later on args = vars(populate) args.pop('script_name', None) + args.pop('script_args', None) # will refeed them later with script_args + args.pop('alwayson_script_name', None) + args.pop('alwayson_script_args', None) + + #find max idx from the scripts in runner and generate a none array to init script_args + last_arg_index = 1 + for script in script_runner.scripts: + if last_arg_index < script.args_to: + last_arg_index = script.args_to + # None everywhere exepct position 0 to initialize script args + script_args = [None]*last_arg_index + # position 0 in script_arg is the idx+1 of the selectable script that is going to be run + if api_selectable_scripts: + script_args[api_selectable_scripts.args_from:api_selectable_scripts.args_to] = txt2imgreq.script_args + script_args[0] = api_selectable_script_idx + 1 + else: + # if 0 then none + script_args[0] = 0 + + # Now check for always on scripts + if len(txt2imgreq.alwayson_script_name) > 0: + # always on script with no arg should always run, but if you include their name in the api request, send an empty list for there args + if len(txt2imgreq.alwayson_script_name) != len(txt2imgreq.alwayson_script_args): + raise HTTPException(status_code=422, detail=f"Number of script names and number of script arg lists doesn't match") + + for alwayson_script_name, alwayson_script_args in zip(txt2imgreq.alwayson_script_name, txt2imgreq.alwayson_script_args): + alwayson_script = self.get_script(alwayson_script_name, script_runner) + if alwayson_script == None: + raise HTTPException(status_code=422, detail=f"always on script {alwayson_script_name} not found") + # Selectable script in always on script param check + if alwayson_script.alwayson == False: + raise HTTPException(status_code=422, detail=f"Cannot have a selectable script in the always on scripts params") + if alwayson_script_args != []: + script_args[alwayson_script.args_from:alwayson_script.args_to] = alwayson_script_args with self.queue_lock: p = StableDiffusionProcessingTxt2Img(sd_model=shared.sd_model, **args) + p.scripts = script_runner shared.state.begin() - if script is not None: + if api_selectable_scripts != None: + p.script_args = script_args p.outpath_grids = opts.outdir_txt2img_grids p.outpath_samples = opts.outdir_txt2img_samples - p.script_args = [script_idx + 1] + [None] * (script.args_from - 1) + p.script_args processed = scripts.scripts_txt2img.run(p, *p.script_args) else: + p.script_args = tuple(script_args) processed = process_images(p) shared.state.end() @@ -212,12 +255,16 @@ class Api: if init_images is None: raise HTTPException(status_code=404, detail="Init image not found") - script, script_idx = self.get_script(img2imgreq.script_name, scripts.scripts_img2img) - mask = img2imgreq.mask if mask: mask = decode_base64_to_image(mask) + script_runner = scripts.scripts_img2img + if not script_runner.scripts: + script_runner.initialize_scripts(True) + ui.create_ui() + api_selectable_scripts, api_selectable_script_idx = self.get_selectable_script(img2imgreq.script_name, script_runner) + populate = img2imgreq.copy(update={ # Override __init__ params "sampler_name": validate_sampler_name(img2imgreq.sampler_name or img2imgreq.sampler_index), "do_not_save_samples": True, @@ -225,24 +272,62 @@ class Api: "mask": mask } ) + if populate.sampler_name: populate.sampler_index = None # prevent a warning later on 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) + args.pop('script_args', None) # will refeed them later with script_args + args.pop('alwayson_script_name', None) + args.pop('alwayson_script_args', None) + + #find max idx from the scripts in runner and generate a none array to init script_args + last_arg_index = 1 + for script in script_runner.scripts: + if last_arg_index < script.args_to: + last_arg_index = script.args_to + # None everywhere exepct position 0 to initialize script args + script_args = [None]*last_arg_index + # position 0 in script_arg is the idx+1 of the selectable script that is going to be run + if api_selectable_scripts: + script_args[api_selectable_scripts.args_from:api_selectable_scripts.args_to] = img2imgreq.script_args + script_args[0] = api_selectable_script_idx + 1 + else: + # if 0 then none + script_args[0] = 0 + + # Now check for always on scripts + if len(img2imgreq.alwayson_script_name) > 0: + # always on script with no arg should always run, but if you include their name in the api request, send an empty list for there args + if len(img2imgreq.alwayson_script_name) != len(img2imgreq.alwayson_script_args): + raise HTTPException(status_code=422, detail=f"Number of script names and number of script arg lists doesn't match") + + for alwayson_script_name, alwayson_script_args in zip(img2imgreq.alwayson_script_name, img2imgreq.alwayson_script_args): + alwayson_script = self.get_script(alwayson_script_name, script_runner) + if alwayson_script == None: + raise HTTPException(status_code=422, detail=f"always on script {alwayson_script_name} not found") + # Selectable script in always on script param check + if alwayson_script.alwayson == False: + raise HTTPException(status_code=422, detail=f"Cannot have a selectable script in the always on scripts params") + if alwayson_script_args != []: + script_args[alwayson_script.args_from:alwayson_script.args_to] = alwayson_script_args + with self.queue_lock: p = StableDiffusionProcessingImg2Img(sd_model=shared.sd_model, **args) p.init_images = [decode_base64_to_image(x) for x in init_images] + p.scripts = script_runner shared.state.begin() - if script is not None: + if api_selectable_scripts != None: + p.script_args = script_args p.outpath_grids = opts.outdir_img2img_grids p.outpath_samples = opts.outdir_img2img_samples - p.script_args = [script_idx + 1] + [None] * (script.args_from - 1) + p.script_args processed = scripts.scripts_img2img.run(p, *p.script_args) else: + p.script_args = tuple(script_args) processed = process_images(p) shared.state.end() diff --git a/modules/api/models.py b/modules/api/models.py index cba43d3b..86c70178 100644 --- a/modules/api/models.py +++ b/modules/api/models.py @@ -100,13 +100,13 @@ class PydanticModelGenerator: StableDiffusionTxt2ImgProcessingAPI = PydanticModelGenerator( "StableDiffusionProcessingTxt2Img", StableDiffusionProcessingTxt2Img, - [{"key": "sampler_index", "type": str, "default": "Euler"}, {"key": "script_name", "type": str, "default": None}, {"key": "script_args", "type": list, "default": []}] + [{"key": "sampler_index", "type": str, "default": "Euler"}, {"key": "script_name", "type": str, "default": None}, {"key": "script_args", "type": list, "default": []}, {"key": "alwayson_script_name", "type": list, "default": []}, {"key": "alwayson_script_args", "type": list, "default": []}] ).generate_model() StableDiffusionImg2ImgProcessingAPI = PydanticModelGenerator( "StableDiffusionProcessingImg2Img", StableDiffusionProcessingImg2Img, - [{"key": "sampler_index", "type": str, "default": "Euler"}, {"key": "init_images", "type": list, "default": None}, {"key": "denoising_strength", "type": float, "default": 0.75}, {"key": "mask", "type": str, "default": None}, {"key": "include_init_images", "type": bool, "default": False, "exclude" : True}, {"key": "script_name", "type": str, "default": None}, {"key": "script_args", "type": list, "default": []}] + [{"key": "sampler_index", "type": str, "default": "Euler"}, {"key": "init_images", "type": list, "default": None}, {"key": "denoising_strength", "type": float, "default": 0.75}, {"key": "mask", "type": str, "default": None}, {"key": "include_init_images", "type": bool, "default": False, "exclude" : True}, {"key": "script_name", "type": str, "default": None}, {"key": "script_args", "type": list, "default": []}, {"key": "alwayson_script_name", "type": list, "default": []}, {"key": "alwayson_script_args", "type": list, "default": []}] ).generate_model() class TextToImageResponse(BaseModel): From a39c4cf766a9b0f18972fc52bcf5189173f434c6 Mon Sep 17 00:00:00 2001 From: Vespinian Date: Mon, 27 Feb 2023 23:27:33 -0500 Subject: [PATCH 086/278] small refactor of api.py --- modules/api/api.py | 125 ++++++++++++++++++--------------------------- 1 file changed, 51 insertions(+), 74 deletions(-) diff --git a/modules/api/api.py b/modules/api/api.py index a1cdebb8..d4c0c152 100644 --- a/modules/api/api.py +++ b/modules/api/api.py @@ -172,17 +172,53 @@ class Api: return script, script_idx def get_script(self, script_name, script_runner): + if script_name is None or script_name == "": + return None, None + + script_idx = script_name_to_index(script_name, script_runner.scripts) + return script_runner.scripts[script_idx] + + def init_script_args(self, request, selectable_scripts, selectable_idx, script_runner): + #find max idx from the scripts in runner and generate a none array to init script_args + last_arg_index = 1 for script in script_runner.scripts: - if script_name.lower() == script.title().lower(): - return script - return None + if last_arg_index < script.args_to: + last_arg_index = script.args_to + # None everywhere except position 0 to initialize script args + script_args = [None]*last_arg_index + # position 0 in script_arg is the idx+1 of the selectable script that is going to be run when using scripts.scripts_*2img.run() + if selectable_scripts: + script_args[selectable_scripts.args_from:selectable_scripts.args_to] = request.script_args + script_args[0] = selectable_idx + 1 + else: + # if 0 then none + script_args[0] = 0 + + # Now check for always on scripts + if request.alwayson_script_name and (len(request.alwayson_script_name) > 0): + # always on script with no arg should always run, but if you include their name in the api request, send an empty list for there args + if not request.alwayson_script_args: + raise HTTPException(status_code=422, detail=f"Script {request.alwayson_script_name} has no arg list") + if len(request.alwayson_script_name) != len(request.alwayson_script_args): + raise HTTPException(status_code=422, detail=f"Number of script names and number of script arg lists doesn't match") + + for alwayson_script_name, alwayson_script_args in zip(request.alwayson_script_name, request.alwayson_script_args): + alwayson_script = self.get_script(alwayson_script_name, script_runner) + if alwayson_script == None: + raise HTTPException(status_code=422, detail=f"always on script {alwayson_script_name} not found") + # Selectable script in always on script param check + if alwayson_script.alwayson == False: + raise HTTPException(status_code=422, detail=f"Cannot have a selectable script in the always on scripts params") + if alwayson_script_args != []: + script_args[alwayson_script.args_from:alwayson_script.args_to] = alwayson_script_args + return script_args def text2imgapi(self, txt2imgreq: StableDiffusionTxt2ImgProcessingAPI): script_runner = scripts.scripts_txt2img if not script_runner.scripts: script_runner.initialize_scripts(False) ui.create_ui() - api_selectable_scripts, api_selectable_script_idx = self.get_selectable_script(txt2imgreq.script_name, script_runner) + selectable_scripts, selectable_script_idx = self.get_selectable_script(txt2imgreq.script_name, script_runner) populate = txt2imgreq.copy(update={ # Override __init__ params "sampler_name": validate_sampler_name(txt2imgreq.sampler_name or txt2imgreq.sampler_index), @@ -196,53 +232,24 @@ class Api: args = vars(populate) args.pop('script_name', None) - args.pop('script_args', None) # will refeed them later with script_args + args.pop('script_args', None) # will refeed them to the pipeline directly after initializing them args.pop('alwayson_script_name', None) args.pop('alwayson_script_args', None) - #find max idx from the scripts in runner and generate a none array to init script_args - last_arg_index = 1 - for script in script_runner.scripts: - if last_arg_index < script.args_to: - last_arg_index = script.args_to - # None everywhere exepct position 0 to initialize script args - script_args = [None]*last_arg_index - # position 0 in script_arg is the idx+1 of the selectable script that is going to be run - if api_selectable_scripts: - script_args[api_selectable_scripts.args_from:api_selectable_scripts.args_to] = txt2imgreq.script_args - script_args[0] = api_selectable_script_idx + 1 - else: - # if 0 then none - script_args[0] = 0 - - # Now check for always on scripts - if len(txt2imgreq.alwayson_script_name) > 0: - # always on script with no arg should always run, but if you include their name in the api request, send an empty list for there args - if len(txt2imgreq.alwayson_script_name) != len(txt2imgreq.alwayson_script_args): - raise HTTPException(status_code=422, detail=f"Number of script names and number of script arg lists doesn't match") - - for alwayson_script_name, alwayson_script_args in zip(txt2imgreq.alwayson_script_name, txt2imgreq.alwayson_script_args): - alwayson_script = self.get_script(alwayson_script_name, script_runner) - if alwayson_script == None: - raise HTTPException(status_code=422, detail=f"always on script {alwayson_script_name} not found") - # Selectable script in always on script param check - if alwayson_script.alwayson == False: - raise HTTPException(status_code=422, detail=f"Cannot have a selectable script in the always on scripts params") - if alwayson_script_args != []: - script_args[alwayson_script.args_from:alwayson_script.args_to] = alwayson_script_args + script_args = self.init_script_args(txt2imgreq, selectable_scripts, selectable_script_idx, script_runner) with self.queue_lock: p = StableDiffusionProcessingTxt2Img(sd_model=shared.sd_model, **args) p.scripts = script_runner shared.state.begin() - if api_selectable_scripts != None: + if selectable_scripts != None: p.script_args = script_args p.outpath_grids = opts.outdir_txt2img_grids p.outpath_samples = opts.outdir_txt2img_samples - processed = scripts.scripts_txt2img.run(p, *p.script_args) + processed = scripts.scripts_txt2img.run(p, *p.script_args) # Need to pass args as list here else: - p.script_args = tuple(script_args) + p.script_args = tuple(script_args) # Need to pass args as tuple here processed = process_images(p) shared.state.end() @@ -263,7 +270,7 @@ class Api: if not script_runner.scripts: script_runner.initialize_scripts(True) ui.create_ui() - api_selectable_scripts, api_selectable_script_idx = self.get_selectable_script(img2imgreq.script_name, script_runner) + selectable_scripts, selectable_script_idx = self.get_selectable_script(img2imgreq.script_name, script_runner) populate = img2imgreq.copy(update={ # Override __init__ params "sampler_name": validate_sampler_name(img2imgreq.sampler_name or img2imgreq.sampler_index), @@ -279,41 +286,11 @@ class Api: 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) - args.pop('script_args', None) # will refeed them later with script_args + args.pop('script_args', None) # will refeed them to the pipeline directly after initializing them args.pop('alwayson_script_name', None) args.pop('alwayson_script_args', None) - #find max idx from the scripts in runner and generate a none array to init script_args - last_arg_index = 1 - for script in script_runner.scripts: - if last_arg_index < script.args_to: - last_arg_index = script.args_to - # None everywhere exepct position 0 to initialize script args - script_args = [None]*last_arg_index - # position 0 in script_arg is the idx+1 of the selectable script that is going to be run - if api_selectable_scripts: - script_args[api_selectable_scripts.args_from:api_selectable_scripts.args_to] = img2imgreq.script_args - script_args[0] = api_selectable_script_idx + 1 - else: - # if 0 then none - script_args[0] = 0 - - # Now check for always on scripts - if len(img2imgreq.alwayson_script_name) > 0: - # always on script with no arg should always run, but if you include their name in the api request, send an empty list for there args - if len(img2imgreq.alwayson_script_name) != len(img2imgreq.alwayson_script_args): - raise HTTPException(status_code=422, detail=f"Number of script names and number of script arg lists doesn't match") - - for alwayson_script_name, alwayson_script_args in zip(img2imgreq.alwayson_script_name, img2imgreq.alwayson_script_args): - alwayson_script = self.get_script(alwayson_script_name, script_runner) - if alwayson_script == None: - raise HTTPException(status_code=422, detail=f"always on script {alwayson_script_name} not found") - # Selectable script in always on script param check - if alwayson_script.alwayson == False: - raise HTTPException(status_code=422, detail=f"Cannot have a selectable script in the always on scripts params") - if alwayson_script_args != []: - script_args[alwayson_script.args_from:alwayson_script.args_to] = alwayson_script_args - + script_args = self.init_script_args(img2imgreq, selectable_scripts, selectable_script_idx, script_runner) with self.queue_lock: p = StableDiffusionProcessingImg2Img(sd_model=shared.sd_model, **args) @@ -321,13 +298,13 @@ class Api: p.scripts = script_runner shared.state.begin() - if api_selectable_scripts != None: + if selectable_scripts != None: p.script_args = script_args p.outpath_grids = opts.outdir_img2img_grids p.outpath_samples = opts.outdir_img2img_samples - processed = scripts.scripts_img2img.run(p, *p.script_args) + processed = scripts.scripts_img2img.run(p, *p.script_args) # Need to pass args as list here else: - p.script_args = tuple(script_args) + p.script_args = tuple(script_args) # Need to pass args as tuple here processed = process_images(p) shared.state.end() From c6c2a59333c77dffff49a748bfed8c54af6e2abd Mon Sep 17 00:00:00 2001 From: Vespinian Date: Mon, 27 Feb 2023 23:45:59 -0500 Subject: [PATCH 087/278] comment clarification --- 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 d4c0c152..248922d2 100644 --- a/modules/api/api.py +++ b/modules/api/api.py @@ -191,7 +191,7 @@ class Api: script_args[selectable_scripts.args_from:selectable_scripts.args_to] = request.script_args script_args[0] = selectable_idx + 1 else: - # if 0 then none + # when [0] = 0 no selectable script to run script_args[0] = 0 # Now check for always on scripts From 1e30e4d9ebd9c36ccee43ec0e61c6ab490171614 Mon Sep 17 00:00:00 2001 From: Ju1-js <40339350+Ju1-js@users.noreply.github.com> Date: Tue, 28 Feb 2023 15:55:12 -0800 Subject: [PATCH 088/278] Gradio auth logic fix - Handle empty/newlines When the massive one-liner was split into multiple lines, it lost the ability to handle newlines. This removes empty strings & newline characters from the logins. It also closes the file so it's more robust if the garbage collection function is ever changed. --- webui.py | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/webui.py b/webui.py index 9e8b486a..5e925fa7 100644 --- a/webui.py +++ b/webui.py @@ -209,11 +209,12 @@ def webui(): gradio_auth_creds = [] if cmd_opts.gradio_auth: - gradio_auth_creds += cmd_opts.gradio_auth.strip('"').replace('\n', '').split(',') + gradio_auth_creds += [x.strip() for x in cmd_opts.gradio_auth.strip('"').replace('/n', '').split(',') if x.strip()] if cmd_opts.gradio_auth_path: with open(cmd_opts.gradio_auth_path, 'r', encoding="utf8") as file: for line in file.readlines(): - gradio_auth_creds += [x.strip() for x in line.split(',')] + gradio_auth_creds += [x.strip() for x in line.split(',') if x.strip()] + file.close() app, local_url, share_url = shared.demo.launch( share=cmd_opts.share, From 7990ed92be7f34e609b441252ff97ae1504b0a3f Mon Sep 17 00:00:00 2001 From: Ju1-js <40339350+Ju1-js@users.noreply.github.com> Date: Tue, 28 Feb 2023 22:05:47 -0800 Subject: [PATCH 089/278] Slash was facing the wrong way --- webui.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/webui.py b/webui.py index 5e925fa7..d60c4e5d 100644 --- a/webui.py +++ b/webui.py @@ -209,7 +209,7 @@ def webui(): gradio_auth_creds = [] if cmd_opts.gradio_auth: - gradio_auth_creds += [x.strip() for x in cmd_opts.gradio_auth.strip('"').replace('/n', '').split(',') if x.strip()] + gradio_auth_creds += [x.strip() for x in cmd_opts.gradio_auth.strip('"').replace('\n', '').split(',') if x.strip()] if cmd_opts.gradio_auth_path: with open(cmd_opts.gradio_auth_path, 'r', encoding="utf8") as file: for line in file.readlines(): From b14d8b61bdc4cc2e6c0ea484a5ba8fd370231227 Mon Sep 17 00:00:00 2001 From: Adam Huganir Date: Wed, 1 Mar 2023 13:07:37 -0500 Subject: [PATCH 090/278] version bump for git python due to CVE-2022-24439 required version for CVE-2022-24439 is >= 3.130 --- requirements_versions.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/requirements_versions.txt b/requirements_versions.txt index 331d0fe8..41e0ccc5 100644 --- a/requirements_versions.txt +++ b/requirements_versions.txt @@ -23,7 +23,7 @@ torchdiffeq==0.2.3 kornia==0.6.7 lark==1.1.2 inflection==0.5.1 -GitPython==3.1.27 +GitPython==3.1.30 torchsde==0.2.5 safetensors==0.2.7 httpcore<=0.15 From fc3063d9b924c094b59229269f4afe722b120d88 Mon Sep 17 00:00:00 2001 From: Ju1-js <40339350+Ju1-js@users.noreply.github.com> Date: Wed, 1 Mar 2023 18:25:23 -0800 Subject: [PATCH 091/278] Remove unnecessary line --- webui.py | 1 - 1 file changed, 1 deletion(-) diff --git a/webui.py b/webui.py index d60c4e5d..be39fa8d 100644 --- a/webui.py +++ b/webui.py @@ -214,7 +214,6 @@ def webui(): with open(cmd_opts.gradio_auth_path, 'r', encoding="utf8") as file: for line in file.readlines(): gradio_auth_creds += [x.strip() for x in line.split(',') if x.strip()] - file.close() app, local_url, share_url = shared.demo.launch( share=cmd_opts.share, From 23d4fb5bf2400622d00ca5fe489fadb160ee7c47 Mon Sep 17 00:00:00 2001 From: Vladimir Mandic Date: Fri, 3 Mar 2023 08:29:10 -0500 Subject: [PATCH 092/278] allow saving of images via api --- modules/api/api.py | 8 ++++---- modules/api/models.py | 4 ++-- modules/images.py | 3 +++ 3 files changed, 9 insertions(+), 6 deletions(-) diff --git a/modules/api/api.py b/modules/api/api.py index 5a9ac5f1..6b939daa 100644 --- a/modules/api/api.py +++ b/modules/api/api.py @@ -180,8 +180,8 @@ class Api: populate = txt2imgreq.copy(update={ # Override __init__ params "sampler_name": validate_sampler_name(txt2imgreq.sampler_name or txt2imgreq.sampler_index), - "do_not_save_samples": True, - "do_not_save_grid": True + "do_not_save_samples": True if not 'do_not_save_samples' in vars(txt2imgreq) else txt2imgreq.do_not_save_samples, + "do_not_save_grid": True if not 'do_not_save_grid' in vars(txt2imgreq) else txt2imgreq.do_not_save_grid, } ) if populate.sampler_name: @@ -220,8 +220,8 @@ class Api: populate = img2imgreq.copy(update={ # Override __init__ params "sampler_name": validate_sampler_name(img2imgreq.sampler_name or img2imgreq.sampler_index), - "do_not_save_samples": True, - "do_not_save_grid": True, + "do_not_save_samples": True if not 'do_not_save_samples' in img2imgreq else img2imgreq.do_not_save_samples, + "do_not_save_grid": True if not 'do_not_save_grid' in img2imgreq else img2imgreq.do_not_save_grid, "mask": mask } ) diff --git a/modules/api/models.py b/modules/api/models.py index cba43d3b..a947e6ac 100644 --- a/modules/api/models.py +++ b/modules/api/models.py @@ -14,8 +14,8 @@ API_NOT_ALLOWED = [ "outpath_samples", "outpath_grids", "sampler_index", - "do_not_save_samples", - "do_not_save_grid", + # "do_not_save_samples", + # "do_not_save_grid", "extra_generation_params", "overlay_images", "do_not_reload_embeddings", diff --git a/modules/images.py b/modules/images.py index 5b80c23e..f8e62b71 100644 --- a/modules/images.py +++ b/modules/images.py @@ -489,6 +489,9 @@ def save_image(image, path, basename, seed=None, prompt=None, extension='png', i """ namegen = FilenameGenerator(p, seed, prompt, image) + if path is None: # set default path to avoid errors when functions are triggered manually or via api and param is not set + path = opts.outdir_save + 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 f8e219bad9f33cde94cd31fff3edd70946612541 Mon Sep 17 00:00:00 2001 From: Vladimir Mandic Date: Fri, 3 Mar 2023 09:00:52 -0500 Subject: [PATCH 093/278] allow api requests to specify do not send images in response --- modules/api/api.py | 10 ++++++++-- modules/api/models.py | 18 ++++++++++++++++-- 2 files changed, 24 insertions(+), 4 deletions(-) diff --git a/modules/api/api.py b/modules/api/api.py index 6b939daa..7da9081b 100644 --- a/modules/api/api.py +++ b/modules/api/api.py @@ -190,6 +190,9 @@ class Api: args = vars(populate) args.pop('script_name', None) + send_images = True if not 'do_not_send_images' in args else not args['do_not_send_images'] + args.pop('do_not_send_images', None) + with self.queue_lock: p = StableDiffusionProcessingTxt2Img(sd_model=shared.sd_model, **args) @@ -203,7 +206,7 @@ class Api: processed = process_images(p) shared.state.end() - b64images = list(map(encode_pil_to_base64, processed.images)) + b64images = list(map(encode_pil_to_base64, processed.images)) if send_images else [] return TextToImageResponse(images=b64images, parameters=vars(txt2imgreq), info=processed.js()) @@ -232,6 +235,9 @@ class Api: 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) + send_images = True if not 'do_not_send_images' in args else not args['do_not_send_images'] + args.pop('do_not_send_images', None) + with self.queue_lock: p = StableDiffusionProcessingImg2Img(sd_model=shared.sd_model, **args) p.init_images = [decode_base64_to_image(x) for x in init_images] @@ -246,7 +252,7 @@ class Api: processed = process_images(p) shared.state.end() - b64images = list(map(encode_pil_to_base64, processed.images)) + b64images = list(map(encode_pil_to_base64, processed.images)) if send_images else [] if not img2imgreq.include_init_images: img2imgreq.init_images = None diff --git a/modules/api/models.py b/modules/api/models.py index a947e6ac..aa4ea5d5 100644 --- a/modules/api/models.py +++ b/modules/api/models.py @@ -100,13 +100,27 @@ class PydanticModelGenerator: StableDiffusionTxt2ImgProcessingAPI = PydanticModelGenerator( "StableDiffusionProcessingTxt2Img", StableDiffusionProcessingTxt2Img, - [{"key": "sampler_index", "type": str, "default": "Euler"}, {"key": "script_name", "type": str, "default": None}, {"key": "script_args", "type": list, "default": []}] + [ + {"key": "sampler_index", "type": str, "default": "Euler"}, + {"key": "script_name", "type": str, "default": None}, + {"key": "script_args", "type": list, "default": []}, + {"key": "do_not_send_images", "type": bool, "default": False} + ] ).generate_model() StableDiffusionImg2ImgProcessingAPI = PydanticModelGenerator( "StableDiffusionProcessingImg2Img", StableDiffusionProcessingImg2Img, - [{"key": "sampler_index", "type": str, "default": "Euler"}, {"key": "init_images", "type": list, "default": None}, {"key": "denoising_strength", "type": float, "default": 0.75}, {"key": "mask", "type": str, "default": None}, {"key": "include_init_images", "type": bool, "default": False, "exclude" : True}, {"key": "script_name", "type": str, "default": None}, {"key": "script_args", "type": list, "default": []}] + [ + {"key": "sampler_index", "type": str, "default": "Euler"}, + {"key": "init_images", "type": list, "default": None}, + {"key": "denoising_strength", "type": float, "default": 0.75}, + {"key": "mask", "type": str, "default": None}, + {"key": "include_init_images", "type": bool, "default": False, "exclude" : True}, + {"key": "script_name", "type": str, "default": None}, + {"key": "script_args", "type": list, "default": []}, + {"key": "do_not_send_images", "type": bool, "default": False} + ] ).generate_model() class TextToImageResponse(BaseModel): From c48bbccf12f13cf309f532d70494ff04c27bcc2a Mon Sep 17 00:00:00 2001 From: Yea chen Date: Sat, 4 Mar 2023 11:46:07 +0800 Subject: [PATCH 094/278] add: /sdapi/v1/scripts in API API for get scripts list --- modules/api/api.py | 13 +++++++++++++ modules/api/models.py | 4 ++++ 2 files changed, 17 insertions(+) diff --git a/modules/api/api.py b/modules/api/api.py index 5a9ac5f1..46cb7c81 100644 --- a/modules/api/api.py +++ b/modules/api/api.py @@ -150,6 +150,7 @@ class Api: self.add_api_route("/sdapi/v1/train/embedding", self.train_embedding, methods=["POST"], response_model=TrainResponse) self.add_api_route("/sdapi/v1/train/hypernetwork", self.train_hypernetwork, methods=["POST"], response_model=TrainResponse) self.add_api_route("/sdapi/v1/memory", self.get_memory, methods=["GET"], response_model=MemoryResponse) + self.add_api_route("/sdapi/v1/scripts", self.get_scripts_list, methods=["GET"], response_model=ScriptsList) def add_api_route(self, path: str, endpoint, **kwargs): if shared.cmd_opts.api_auth: @@ -174,6 +175,18 @@ class Api: script_idx = script_name_to_index(script_name, script_runner.selectable_scripts) script = script_runner.selectable_scripts[script_idx] return script, script_idx + + def get_scripts_list(self): + t2ilist = [] + i2ilist = [] + + for a in scripts.scripts_txt2img.titles: + t2ilist.append(str(a.lower())) + + for b in scripts.scripts_img2img.titles: + i2ilist.append(str(b.lower())) + + return ScriptsList(txt2img = t2ilist, img2img = i2ilist) def text2imgapi(self, txt2imgreq: StableDiffusionTxt2ImgProcessingAPI): script, script_idx = self.get_script(txt2imgreq.script_name, scripts.scripts_txt2img) diff --git a/modules/api/models.py b/modules/api/models.py index cba43d3b..db739f2b 100644 --- a/modules/api/models.py +++ b/modules/api/models.py @@ -267,3 +267,7 @@ class EmbeddingsResponse(BaseModel): class MemoryResponse(BaseModel): ram: dict = Field(title="RAM", description="System memory stats") cuda: dict = Field(title="CUDA", description="nVidia CUDA memory stats") + +class ScriptsList(BaseModel): + txt2img: list = Field(default=None,title="Txt2img", description="Titles of scripts (txt2img)") + img2img: list = Field(default=None,title="Img2img", description="Titles of scripts (img2img)") \ No newline at end of file From 2d9635cce5d8123c53e5c8233bab7c9751ae03ba Mon Sep 17 00:00:00 2001 From: DejitaruJin Date: Sat, 4 Mar 2023 12:51:55 -0500 Subject: [PATCH 095/278] Fix display and save order for X/Y/Z Grid script --- scripts/xyz_grid.py | 125 ++++++++++++++++++++++++-------------------- 1 file changed, 69 insertions(+), 56 deletions(-) diff --git a/scripts/xyz_grid.py b/scripts/xyz_grid.py index 53511b12..8ede2aa0 100644 --- a/scripts/xyz_grid.py +++ b/scripts/xyz_grid.py @@ -25,8 +25,6 @@ from modules.ui_components import ToolButton fill_values_symbol = "\U0001f4d2" # 📒 -AxisInfo = namedtuple('AxisInfo', ['axis', 'values']) - def apply_field(field): def fun(p, x, xs): @@ -188,7 +186,6 @@ axis_options = [ AxisOption("Steps", int, apply_field("steps")), AxisOptionTxt2Img("Hires steps", int, apply_field("hr_second_pass_steps")), AxisOption("CFG Scale", float, apply_field("cfg_scale")), - AxisOptionImg2Img("Image CFG Scale", float, apply_field("image_cfg_scale")), AxisOption("Prompt S/R", str, apply_prompt, format_value=format_value), AxisOption("Prompt order", str_permutations, apply_order, format_value=format_value_join_list), AxisOptionTxt2Img("Sampler", str, apply_sampler, format_value=format_value, confirm=confirm_samplers, choices=lambda: [x.name for x in sd_samplers.samplers]), @@ -213,49 +210,47 @@ def draw_xyz_grid(p, xs, ys, zs, x_labels, y_labels, z_labels, cell, draw_legend ver_texts = [[images.GridAnnotation(y)] for y in y_labels] title_texts = [[images.GridAnnotation(z)] for z in z_labels] - # Temporary list of all the images that are generated to be populated into the grid. - # Will be filled with empty images for any individual step that fails to process properly - image_cache = [None] * (len(xs) * len(ys) * len(zs)) + list_size = (len(xs) * len(ys) * len(zs)) processed_result = None - cell_mode = "P" - cell_size = (1, 1) - state.job_count = len(xs) * len(ys) * len(zs) * p.n_iter + state.job_count = list_size * p.n_iter def process_cell(x, y, z, ix, iy, iz): - nonlocal image_cache, processed_result, cell_mode, cell_size + nonlocal processed_result def index(ix, iy, iz): return ix + iy * len(xs) + iz * len(xs) * len(ys) - state.job = f"{index(ix, iy, iz) + 1} out of {len(xs) * len(ys) * len(zs)}" + state.job = f"{index(ix, iy, iz) + 1} out of {list_size}" processed: Processed = cell(x, y, z) - try: - # this dereference will throw an exception if the image was not processed - # (this happens in cases such as if the user stops the process from the UI) - processed_image = processed.images[0] + if processed_result is None: + # Use our first processed result object as a template container to hold our full results + processed_result = copy(processed) + processed_result.images = [None] * list_size + processed_result.all_prompts = [None] * list_size + processed_result.all_seeds = [None] * list_size + processed_result.infotexts = [None] * list_size + processed_result.index_of_first_image = 0 - if processed_result is None: - # Use our first valid processed result as a template container to hold our full results - processed_result = copy(processed) - cell_mode = processed_image.mode - cell_size = processed_image.size - processed_result.images = [Image.new(cell_mode, cell_size)] - processed_result.all_prompts = [processed.prompt] - processed_result.all_seeds = [processed.seed] - processed_result.infotexts = [processed.infotexts[0]] + idx = index(ix, iy, iz) + if processed.images: + # Non-empty list indicates some degree of success. + processed_result.images[idx] = processed.images[0] + processed_result.all_prompts[idx] = processed.prompt + processed_result.all_seeds[idx] = processed.seed + processed_result.infotexts[idx] = processed.infotexts[0] + else: + cell_mode = "P" + cell_size = (processed_result.width, processed_result.height) + if processed_result.images[0] is not None: + cell_mode = processed_result.images[0].mode + #This corrects size in case of batches: + cell_size = processed_result.images[0].size + processed_result.images[idx] = Image.new(cell_mode, cell_size) - image_cache[index(ix, iy, iz)] = processed_image - if include_lone_images: - processed_result.images.append(processed_image) - processed_result.all_prompts.append(processed.prompt) - processed_result.all_seeds.append(processed.seed) - processed_result.infotexts.append(processed.infotexts[0]) - except: - image_cache[index(ix, iy, iz)] = Image.new(cell_mode, cell_size) if first_axes_processed == 'x': for ix, x in enumerate(xs): @@ -289,27 +284,36 @@ def draw_xyz_grid(p, xs, ys, zs, x_labels, y_labels, z_labels, cell, draw_legend process_cell(x, y, z, ix, iy, iz) if not processed_result: + # Should never happen, I've only seen it on one of four open tabs and it needed to refresh. + print("Unexpected error: Processing could not begin, you may need to refresh the tab or restart the service.") + return Processed(p, []) + elif not any(processed_result.images): print("Unexpected error: draw_xyz_grid failed to return even a single processed image") return Processed(p, []) - sub_grids = [None] * len(zs) - for i in range(len(zs)): - start_index = i * len(xs) * len(ys) + z_count = len(zs) + sub_grids = [None] * z_count + for i in range(z_count): + start_index = (i * len(xs) * len(ys)) + i end_index = start_index + len(xs) * len(ys) - grid = images.image_grid(image_cache[start_index:end_index], rows=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, cell_size[0], cell_size[1], hor_texts, ver_texts, margin_size) - sub_grids[i] = grid - if include_sub_grids and len(zs) > 1: - processed_result.images.insert(i+1, grid) + 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) + 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 = sub_grids[0].size - z_grid = images.image_grid(sub_grids, rows=1) + sub_grid_size = processed_result.images[0].size + z_grid = images.image_grid(processed_result.images[:z_count], rows=1) if draw_legend: z_grid = images.draw_grid_annotations(z_grid, sub_grid_size[0], sub_grid_size[1], title_texts, [[images.GridAnnotation()]]) - processed_result.images[0] = z_grid + processed_result.images.insert(0, z_grid) + processed_result.all_prompts.insert(0, processed_result.all_prompts[0]) + processed_result.all_seeds.insert(0, processed_result.all_seeds[0]) + processed_result.infotexts.insert(0, processed_result.infotexts[0]) - return processed_result, sub_grids + return processed_result class SharedSettingsStackHelper(object): @@ -364,7 +368,7 @@ class Script(scripts.Script): include_lone_images = gr.Checkbox(label='Include Sub Images', value=False, elem_id=self.elem_id("include_lone_images")) include_sub_grids = gr.Checkbox(label='Include Sub Grids', value=False, elem_id=self.elem_id("include_sub_grids")) with gr.Column(): - margin_size = gr.Slider(label="Grid margins (px)", minimum=0, maximum=500, value=0, step=2, elem_id=self.elem_id("margin_size")) + margin_size = gr.Slider(label="Grid margins (px)", min=0, max=500, value=0, step=2, elem_id=self.elem_id("margin_size")) with gr.Row(variant="compact", elem_id="swap_axes"): swap_xy_axes_button = gr.Button(value="Swap X/Y axes", elem_id="xy_grid_swap_axes_button") @@ -526,14 +530,10 @@ class Script(scripts.Script): grid_infotext = [None] - state.xyz_plot_x = AxisInfo(x_opt, xs) - state.xyz_plot_y = AxisInfo(y_opt, ys) - state.xyz_plot_z = AxisInfo(z_opt, zs) - # If one of the axes is very slow to change between (like SD model # checkpoint), then make sure it is in the outer iteration of the nested # `for` loop. - first_axes_processed = 'x' + first_axes_processed = 'z' second_axes_processed = 'y' if x_opt.cost > y_opt.cost and x_opt.cost > z_opt.cost: first_axes_processed = 'x' @@ -593,7 +593,7 @@ class Script(scripts.Script): return res with SharedSettingsStackHelper(): - processed, sub_grids = draw_xyz_grid( + processed = draw_xyz_grid( p, xs=xs, ys=ys, @@ -610,11 +610,24 @@ class Script(scripts.Script): margin_size=margin_size ) - if opts.grid_save and len(sub_grids) > 1: - for sub_grid in sub_grids: - images.save_image(sub_grid, p.outpath_grids, "xyz_grid", info=grid_infotext[0], extension=opts.grid_format, prompt=p.prompt, seed=processed.seed, grid=True, p=p) + z_count = len(zs) - if opts.grid_save: - images.save_image(processed.images[0], p.outpath_grids, "xyz_grid", info=grid_infotext[0], extension=opts.grid_format, prompt=p.prompt, seed=processed.seed, grid=True, p=p) + if not include_lone_images: + # Don't need sub-images anymore, drop from list: + processed.images = processed.images[:z_count+1] + + if opts.grid_save and processed.images: + # Auto-save main and sub-grids: + grid_count = z_count + 1 if z_count > 1 else 1 + for g in range(grid_count): + images.save_image(processed.images[g], p.outpath_grids, "xyz_grid", info=processed.infotexts[g], extension=opts.grid_format, prompt=processed.all_prompts[g], seed=processed.all_seeds[g], grid=True, p=processed) + + if not include_sub_grids: + # Done with sub-grids, drop all related information: + for sg in range(z_count): + del processed.images[1] + del processed.all_prompts[1] + del processed.all_seeds[1] + del processed.infotexts[1] return processed From 2ba880704b970f2870cbae1fe08cea77a21b9213 Mon Sep 17 00:00:00 2001 From: DejitaruJin Date: Sat, 4 Mar 2023 13:00:27 -0500 Subject: [PATCH 096/278] Add files via upload --- scripts/xyz_grid.py | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/scripts/xyz_grid.py b/scripts/xyz_grid.py index 8ede2aa0..e9010817 100644 --- a/scripts/xyz_grid.py +++ b/scripts/xyz_grid.py @@ -25,6 +25,8 @@ from modules.ui_components import ToolButton fill_values_symbol = "\U0001f4d2" # 📒 +AxisInfo = namedtuple('AxisInfo', ['axis', 'values']) + def apply_field(field): def fun(p, x, xs): @@ -186,6 +188,7 @@ axis_options = [ AxisOption("Steps", int, apply_field("steps")), AxisOptionTxt2Img("Hires steps", int, apply_field("hr_second_pass_steps")), AxisOption("CFG Scale", float, apply_field("cfg_scale")), + AxisOptionImg2Img("Image CFG Scale", float, apply_field("image_cfg_scale")), AxisOption("Prompt S/R", str, apply_prompt, format_value=format_value), AxisOption("Prompt order", str_permutations, apply_order, format_value=format_value_join_list), AxisOptionTxt2Img("Sampler", str, apply_sampler, format_value=format_value, confirm=confirm_samplers, choices=lambda: [x.name for x in sd_samplers.samplers]), @@ -368,7 +371,7 @@ class Script(scripts.Script): include_lone_images = gr.Checkbox(label='Include Sub Images', value=False, elem_id=self.elem_id("include_lone_images")) include_sub_grids = gr.Checkbox(label='Include Sub Grids', value=False, elem_id=self.elem_id("include_sub_grids")) with gr.Column(): - margin_size = gr.Slider(label="Grid margins (px)", min=0, max=500, value=0, step=2, elem_id=self.elem_id("margin_size")) + margin_size = gr.Slider(label="Grid margins (px)", minimum=0, maximum=500, value=0, step=2, elem_id=self.elem_id("margin_size")) with gr.Row(variant="compact", elem_id="swap_axes"): swap_xy_axes_button = gr.Button(value="Swap X/Y axes", elem_id="xy_grid_swap_axes_button") @@ -530,6 +533,10 @@ class Script(scripts.Script): grid_infotext = [None] + state.xyz_plot_x = AxisInfo(x_opt, xs) + state.xyz_plot_y = AxisInfo(y_opt, ys) + state.xyz_plot_z = AxisInfo(z_opt, zs) + # If one of the axes is very slow to change between (like SD model # checkpoint), then make sure it is in the outer iteration of the nested # `for` loop. From fe7d7dfd5ae9fdb09eea56af48c45ddc76fa3e28 Mon Sep 17 00:00:00 2001 From: DejitaruJin Date: Sat, 4 Mar 2023 15:40:35 -0500 Subject: [PATCH 097/278] Add files via upload --- scripts/xyz_grid.py | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/scripts/xyz_grid.py b/scripts/xyz_grid.py index e9010817..1cce87e1 100644 --- a/scripts/xyz_grid.py +++ b/scripts/xyz_grid.py @@ -236,7 +236,7 @@ def draw_xyz_grid(p, xs, ys, zs, x_labels, y_labels, z_labels, cell, draw_legend processed_result.all_prompts = [None] * list_size processed_result.all_seeds = [None] * list_size processed_result.infotexts = [None] * list_size - processed_result.index_of_first_image = 0 + processed_result.index_of_first_image = 1 idx = index(ix, iy, iz) if processed.images: @@ -312,8 +312,9 @@ def draw_xyz_grid(p, xs, ys, zs, x_labels, y_labels, z_labels, cell, draw_legend if draw_legend: z_grid = images.draw_grid_annotations(z_grid, sub_grid_size[0], sub_grid_size[1], title_texts, [[images.GridAnnotation()]]) processed_result.images.insert(0, z_grid) - processed_result.all_prompts.insert(0, processed_result.all_prompts[0]) - processed_result.all_seeds.insert(0, processed_result.all_seeds[0]) + #TODO: Deeper aspects of the program rely on index 0 "grid" images only having partial information, which is not ideal. + #processed_result.all_prompts.insert(0, processed_result.all_prompts[0]) + #processed_result.all_seeds.insert(0, processed_result.all_seeds[0]) processed_result.infotexts.insert(0, processed_result.infotexts[0]) return processed_result @@ -637,4 +638,6 @@ class Script(scripts.Script): del processed.all_seeds[1] del processed.infotexts[1] + print(processed.images) + return processed From eb29ff211af885a96cee3a97beb99194a6b22a3d Mon Sep 17 00:00:00 2001 From: DejitaruJin Date: Sat, 4 Mar 2023 16:06:40 -0500 Subject: [PATCH 098/278] Add files via upload --- scripts/xyz_grid.py | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/scripts/xyz_grid.py b/scripts/xyz_grid.py index 1cce87e1..7ed8a9da 100644 --- a/scripts/xyz_grid.py +++ b/scripts/xyz_grid.py @@ -312,7 +312,7 @@ def draw_xyz_grid(p, xs, ys, zs, x_labels, y_labels, z_labels, cell, draw_legend if draw_legend: z_grid = images.draw_grid_annotations(z_grid, sub_grid_size[0], sub_grid_size[1], title_texts, [[images.GridAnnotation()]]) processed_result.images.insert(0, z_grid) - #TODO: Deeper aspects of the program rely on index 0 "grid" images only having partial information, which is not ideal. + #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]) #processed_result.all_seeds.insert(0, processed_result.all_seeds[0]) processed_result.infotexts.insert(0, processed_result.infotexts[0]) @@ -628,7 +628,9 @@ class Script(scripts.Script): # Auto-save main and sub-grids: grid_count = z_count + 1 if z_count > 1 else 1 for g in range(grid_count): - images.save_image(processed.images[g], p.outpath_grids, "xyz_grid", info=processed.infotexts[g], extension=opts.grid_format, prompt=processed.all_prompts[g], seed=processed.all_seeds[g], grid=True, p=processed) + #TODO: See previous comment about intentional data misalignment. + 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: # Done with sub-grids, drop all related information: @@ -638,6 +640,4 @@ class Script(scripts.Script): del processed.all_seeds[1] del processed.infotexts[1] - print(processed.images) - return processed From b012d70f15641d6b85c9257b83cec892e941609c Mon Sep 17 00:00:00 2001 From: Vladimir Mandic Date: Sat, 4 Mar 2023 17:51:37 -0500 Subject: [PATCH 099/278] update using original defaults --- modules/api/api.py | 17 +++++++++++------ modules/api/models.py | 6 ++++-- 2 files changed, 15 insertions(+), 8 deletions(-) diff --git a/modules/api/api.py b/modules/api/api.py index 7da9081b..a6bb439c 100644 --- a/modules/api/api.py +++ b/modules/api/api.py @@ -180,8 +180,8 @@ class Api: populate = txt2imgreq.copy(update={ # Override __init__ params "sampler_name": validate_sampler_name(txt2imgreq.sampler_name or txt2imgreq.sampler_index), - "do_not_save_samples": True if not 'do_not_save_samples' in vars(txt2imgreq) else txt2imgreq.do_not_save_samples, - "do_not_save_grid": True if not 'do_not_save_grid' in vars(txt2imgreq) else txt2imgreq.do_not_save_grid, + "do_not_save_samples": txt2imgreq.do_not_save, + "do_not_save_grid": txt2imgreq.do_not_save, } ) if populate.sampler_name: @@ -190,8 +190,9 @@ class Api: args = vars(populate) args.pop('script_name', None) - send_images = True if not 'do_not_send_images' in args else not args['do_not_send_images'] - args.pop('do_not_send_images', None) + send_images = True if not 'do_not_send' in args else not args['do_not_send'] + args.pop('do_not_send', None) + args.pop('do_not_save', None) with self.queue_lock: p = StableDiffusionProcessingTxt2Img(sd_model=shared.sd_model, **args) @@ -223,8 +224,8 @@ class Api: populate = img2imgreq.copy(update={ # Override __init__ params "sampler_name": validate_sampler_name(img2imgreq.sampler_name or img2imgreq.sampler_index), - "do_not_save_samples": True if not 'do_not_save_samples' in img2imgreq else img2imgreq.do_not_save_samples, - "do_not_save_grid": True if not 'do_not_save_grid' in img2imgreq else img2imgreq.do_not_save_grid, + "do_not_save_samples": img2imgreq.do_not_save, + "do_not_save_grid": img2imgreq.do_not_save, "mask": mask } ) @@ -235,6 +236,10 @@ class Api: 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) + send_images = True if not 'do_not_send' in args else not args['do_not_send'] + args.pop('do_not_send', None) + args.pop('do_not_save', None) + send_images = True if not 'do_not_send_images' in args else not args['do_not_send_images'] args.pop('do_not_send_images', None) diff --git a/modules/api/models.py b/modules/api/models.py index aa4ea5d5..2b66e1f0 100644 --- a/modules/api/models.py +++ b/modules/api/models.py @@ -104,7 +104,8 @@ StableDiffusionTxt2ImgProcessingAPI = PydanticModelGenerator( {"key": "sampler_index", "type": str, "default": "Euler"}, {"key": "script_name", "type": str, "default": None}, {"key": "script_args", "type": list, "default": []}, - {"key": "do_not_send_images", "type": bool, "default": False} + {"key": "do_not_send", "type": bool, "default": False}, + {"key": "do_not_save", "type": bool, "default": True} ] ).generate_model() @@ -119,7 +120,8 @@ StableDiffusionImg2ImgProcessingAPI = PydanticModelGenerator( {"key": "include_init_images", "type": bool, "default": False, "exclude" : True}, {"key": "script_name", "type": str, "default": None}, {"key": "script_args", "type": list, "default": []}, - {"key": "do_not_send_images", "type": bool, "default": False} + {"key": "do_not_send", "type": bool, "default": False}, + {"key": "do_not_save", "type": bool, "default": True} ] ).generate_model() From c8b52c79755618736aec40a80d72043967274a59 Mon Sep 17 00:00:00 2001 From: DejitaruJin Date: Sat, 4 Mar 2023 19:32:09 -0500 Subject: [PATCH 100/278] Short-circuit error handling --- scripts/xyz_grid.py | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/scripts/xyz_grid.py b/scripts/xyz_grid.py index 7ed8a9da..f79c46f6 100644 --- a/scripts/xyz_grid.py +++ b/scripts/xyz_grid.py @@ -618,13 +618,17 @@ class Script(scripts.Script): margin_size=margin_size ) + if not processed.images: + # It broke, no further handling needed. + return processed + z_count = len(zs) if not include_lone_images: # Don't need sub-images anymore, drop from list: processed.images = processed.images[:z_count+1] - if opts.grid_save and processed.images: + 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): From d118cb6ea3f1a410b5e030519dc021eafc1d6b52 Mon Sep 17 00:00:00 2001 From: Brad Smith Date: Mon, 6 Mar 2023 13:18:35 -0500 Subject: [PATCH 101/278] use lowercase name for sorting; keep `UpscalerLanczos` and `UpscalerNearest` at the start of the list with `UpscalerNone` Co-authored-by: catboxanon <122327233+catboxanon@users.noreply.github.com> --- modules/modelloader.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/modules/modelloader.py b/modules/modelloader.py index a7ac338c..e351d808 100644 --- a/modules/modelloader.py +++ b/modules/modelloader.py @@ -6,7 +6,7 @@ from urllib.parse import urlparse from basicsr.utils.download_util import load_file_from_url from modules import shared -from modules.upscaler import Upscaler, UpscalerNone +from modules.upscaler import Upscaler, UpscalerLanczos, UpscalerNearest, UpscalerNone from modules.paths import script_path, models_path @@ -172,5 +172,5 @@ def load_upscalers(): shared.sd_upscalers = sorted( datas, # Special case for UpscalerNone keeps it at the beginning of the list. - key=lambda x: x.name if not isinstance(x.scaler, UpscalerNone) else "" + key=lambda x: x.name.lower() if not isinstance(x.scaler, (UpscalerNone, UpscalerLanczos, UpscalerNearest)) else "" ) From 49b1dc5e07825e76c85ac4ac078fd63aa835e8bd Mon Sep 17 00:00:00 2001 From: Aarni Koskela Date: Mon, 6 Mar 2023 21:00:34 +0200 Subject: [PATCH 102/278] Deduplicate extra network preview-search code --- extensions-builtin/Lora/ui_extra_networks_lora.py | 10 +--------- modules/ui_extra_networks.py | 10 ++++++++++ modules/ui_extra_networks_checkpoints.py | 11 +---------- modules/ui_extra_networks_hypernets.py | 9 +-------- modules/ui_extra_networks_textual_inversion.py | 8 +------- 5 files changed, 14 insertions(+), 34 deletions(-) diff --git a/extensions-builtin/Lora/ui_extra_networks_lora.py b/extensions-builtin/Lora/ui_extra_networks_lora.py index 22cabcb0..4c1549d7 100644 --- a/extensions-builtin/Lora/ui_extra_networks_lora.py +++ b/extensions-builtin/Lora/ui_extra_networks_lora.py @@ -15,18 +15,10 @@ class ExtraNetworksPageLora(ui_extra_networks.ExtraNetworksPage): def list_items(self): for name, lora_on_disk in lora.available_loras.items(): path, ext = os.path.splitext(lora_on_disk.filename) - previews = [path + ".png", path + ".preview.png"] - - preview = None - for file in previews: - if os.path.isfile(file): - preview = self.link_preview(file) - break - yield { "name": name, "filename": path, - "preview": preview, + "preview": self._find_preview(path), "search_term": self.search_terms_from_path(lora_on_disk.filename), "prompt": json.dumps(f""), "local_preview": path + ".png", diff --git a/modules/ui_extra_networks.py b/modules/ui_extra_networks.py index 71f1d81f..1a10a5df 100644 --- a/modules/ui_extra_networks.py +++ b/modules/ui_extra_networks.py @@ -2,6 +2,7 @@ import glob import os.path import urllib.parse from pathlib import Path +from typing import Optional from modules import shared import gradio as gr @@ -137,6 +138,15 @@ class ExtraNetworksPage: return self.card_page.format(**args) + def _find_preview(self, path: str) -> Optional[str]: + """ + Find a preview PNG for a given path (without extension) and call link_preview on it. + """ + for file in [path + ".png", path + ".preview.png"]: + if os.path.isfile(file): + return self.link_preview(file) + return None + def intialize(): extra_pages.clear() diff --git a/modules/ui_extra_networks_checkpoints.py b/modules/ui_extra_networks_checkpoints.py index 04097a79..b712d12b 100644 --- a/modules/ui_extra_networks_checkpoints.py +++ b/modules/ui_extra_networks_checkpoints.py @@ -1,7 +1,6 @@ import html import json import os -import urllib.parse from modules import shared, ui_extra_networks, sd_models @@ -17,18 +16,10 @@ class ExtraNetworksPageCheckpoints(ui_extra_networks.ExtraNetworksPage): checkpoint: sd_models.CheckpointInfo for name, checkpoint in sd_models.checkpoints_list.items(): path, ext = os.path.splitext(checkpoint.filename) - previews = [path + ".png", path + ".preview.png"] - - preview = None - for file in previews: - if os.path.isfile(file): - preview = self.link_preview(file) - break - yield { "name": checkpoint.name_for_extra, "filename": path, - "preview": preview, + "preview": self._find_preview(path), "search_term": self.search_terms_from_path(checkpoint.filename) + " " + (checkpoint.sha256 or ""), "onclick": '"' + html.escape(f"""return selectCheckpoint({json.dumps(name)})""") + '"', "local_preview": path + ".png", diff --git a/modules/ui_extra_networks_hypernets.py b/modules/ui_extra_networks_hypernets.py index 57851088..89f33242 100644 --- a/modules/ui_extra_networks_hypernets.py +++ b/modules/ui_extra_networks_hypernets.py @@ -14,18 +14,11 @@ class ExtraNetworksPageHypernetworks(ui_extra_networks.ExtraNetworksPage): def list_items(self): for name, path in shared.hypernetworks.items(): path, ext = os.path.splitext(path) - previews = [path + ".png", path + ".preview.png"] - - preview = None - for file in previews: - if os.path.isfile(file): - preview = self.link_preview(file) - break yield { "name": name, "filename": path, - "preview": preview, + "preview": self._find_preview(path), "search_term": self.search_terms_from_path(path), "prompt": json.dumps(f""), "local_preview": path + ".png", diff --git a/modules/ui_extra_networks_textual_inversion.py b/modules/ui_extra_networks_textual_inversion.py index bb64eb81..f7057390 100644 --- a/modules/ui_extra_networks_textual_inversion.py +++ b/modules/ui_extra_networks_textual_inversion.py @@ -15,16 +15,10 @@ class ExtraNetworksPageTextualInversion(ui_extra_networks.ExtraNetworksPage): def list_items(self): for embedding in sd_hijack.model_hijack.embedding_db.word_embeddings.values(): path, ext = os.path.splitext(embedding.filename) - preview_file = path + ".preview.png" - - preview = None - if os.path.isfile(preview_file): - preview = self.link_preview(preview_file) - yield { "name": embedding.name, "filename": embedding.filename, - "preview": preview, + "preview": self._find_preview(path), "search_term": self.search_terms_from_path(embedding.filename), "prompt": json.dumps(embedding.name), "local_preview": path + ".preview.png", From 06f167da37cd00ea8241bd2a6a3c12d8c5fb9eaf Mon Sep 17 00:00:00 2001 From: Aarni Koskela Date: Mon, 6 Mar 2023 21:14:06 +0200 Subject: [PATCH 103/278] Extra networks: support .txt description sidecar file --- extensions-builtin/Lora/ui_extra_networks_lora.py | 1 + html/extra-networks-card.html | 1 + modules/ui_extra_networks.py | 15 +++++++++++++++ modules/ui_extra_networks_checkpoints.py | 1 + modules/ui_extra_networks_hypernets.py | 1 + modules/ui_extra_networks_textual_inversion.py | 1 + style.css | 11 +++++++++++ 7 files changed, 31 insertions(+) diff --git a/extensions-builtin/Lora/ui_extra_networks_lora.py b/extensions-builtin/Lora/ui_extra_networks_lora.py index 4c1549d7..9da13a09 100644 --- a/extensions-builtin/Lora/ui_extra_networks_lora.py +++ b/extensions-builtin/Lora/ui_extra_networks_lora.py @@ -19,6 +19,7 @@ class ExtraNetworksPageLora(ui_extra_networks.ExtraNetworksPage): "name": name, "filename": path, "preview": self._find_preview(path), + "description": self._find_description(path), "search_term": self.search_terms_from_path(lora_on_disk.filename), "prompt": json.dumps(f""), "local_preview": path + ".png", diff --git a/html/extra-networks-card.html b/html/extra-networks-card.html index 8a5e2fbd..8612396d 100644 --- a/html/extra-networks-card.html +++ b/html/extra-networks-card.html @@ -7,6 +7,7 @@
    {name} + {description}
    diff --git a/modules/ui_extra_networks.py b/modules/ui_extra_networks.py index 1a10a5df..cd61a569 100644 --- a/modules/ui_extra_networks.py +++ b/modules/ui_extra_networks.py @@ -1,6 +1,7 @@ import glob import os.path import urllib.parse +from functools import lru_cache from pathlib import Path from typing import Optional @@ -131,6 +132,7 @@ class ExtraNetworksPage: "tabname": json.dumps(tabname), "local_preview": json.dumps(item["local_preview"]), "name": item["name"], + "description": (item.get("description") or ""), "card_clicked": onclick, "save_card_preview": '"' + html.escape(f"""return saveCardPreview(event, {json.dumps(tabname)}, {json.dumps(item["local_preview"])})""") + '"', "search_term": item.get("search_term", ""), @@ -147,6 +149,19 @@ class ExtraNetworksPage: return self.link_preview(file) return None + @lru_cache(maxsize=512) + def _find_description(self, path: str) -> Optional[str]: + """ + Find and read a description file for a given path (without extension). + """ + for file in [f"{path}.txt", f"{path}.description.txt"]: + try: + with open(file, "r", encoding="utf-8", errors="replace") as f: + return f.read() + except OSError: + pass + return None + def intialize(): extra_pages.clear() diff --git a/modules/ui_extra_networks_checkpoints.py b/modules/ui_extra_networks_checkpoints.py index b712d12b..1deb785a 100644 --- a/modules/ui_extra_networks_checkpoints.py +++ b/modules/ui_extra_networks_checkpoints.py @@ -20,6 +20,7 @@ class ExtraNetworksPageCheckpoints(ui_extra_networks.ExtraNetworksPage): "name": checkpoint.name_for_extra, "filename": path, "preview": self._find_preview(path), + "description": self._find_description(path), "search_term": self.search_terms_from_path(checkpoint.filename) + " " + (checkpoint.sha256 or ""), "onclick": '"' + html.escape(f"""return selectCheckpoint({json.dumps(name)})""") + '"', "local_preview": path + ".png", diff --git a/modules/ui_extra_networks_hypernets.py b/modules/ui_extra_networks_hypernets.py index 89f33242..80cc2a24 100644 --- a/modules/ui_extra_networks_hypernets.py +++ b/modules/ui_extra_networks_hypernets.py @@ -19,6 +19,7 @@ class ExtraNetworksPageHypernetworks(ui_extra_networks.ExtraNetworksPage): "name": name, "filename": path, "preview": self._find_preview(path), + "description": self._find_description(path), "search_term": self.search_terms_from_path(path), "prompt": json.dumps(f""), "local_preview": path + ".png", diff --git a/modules/ui_extra_networks_textual_inversion.py b/modules/ui_extra_networks_textual_inversion.py index f7057390..f3bae666 100644 --- a/modules/ui_extra_networks_textual_inversion.py +++ b/modules/ui_extra_networks_textual_inversion.py @@ -19,6 +19,7 @@ class ExtraNetworksPageTextualInversion(ui_extra_networks.ExtraNetworksPage): "name": embedding.name, "filename": embedding.filename, "preview": self._find_preview(path), + "description": self._find_description(path), "search_term": self.search_terms_from_path(embedding.filename), "prompt": json.dumps(embedding.name), "local_preview": path + ".preview.png", diff --git a/style.css b/style.css index 05572f66..9f2af263 100644 --- a/style.css +++ b/style.css @@ -939,6 +939,17 @@ footer { line-break: anywhere; } +.extra-network-cards .card .actions .description { + display: block; + max-height: 3em; + white-space: pre-wrap; + line-height: 1.1; +} + +.extra-network-cards .card .actions .description:hover { + max-height: none; +} + .extra-network-cards .card .actions:hover .additional{ display: block; } From fec0a895119a124a295e3dad5205de5766031dc7 Mon Sep 17 00:00:00 2001 From: Pam Date: Tue, 7 Mar 2023 00:33:13 +0500 Subject: [PATCH 104/278] scaled dot product attention --- html/licenses.html | 219 +++++++++++++++++++++++++++++ modules/sd_hijack.py | 4 + modules/sd_hijack_optimizations.py | 42 ++++++ modules/shared.py | 1 + 4 files changed, 266 insertions(+) diff --git a/html/licenses.html b/html/licenses.html index 570630eb..bddbf466 100644 --- a/html/licenses.html +++ b/html/licenses.html @@ -417,3 +417,222 @@ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. +

    Scaled Dot Product Attention

    +Some small amounts of code borrowed and reworked. +
    +   Copyright 2023 The HuggingFace Team. All rights reserved.
    +
    +   Licensed under the Apache License, Version 2.0 (the "License");
    +   you may not use this file except in compliance with the License.
    +   You may obtain a copy of the License at
    +
    +      http://www.apache.org/licenses/LICENSE-2.0
    +
    +   Unless required by applicable law or agreed to in writing, software
    +   distributed under the License is distributed on an "AS IS" BASIS,
    +   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
    +   See the License for the specific language governing permissions and
    +   limitations under the License.
    +
    +                                 Apache License
    +                           Version 2.0, January 2004
    +                        http://www.apache.org/licenses/
    +
    +   TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
    +
    +   1. Definitions.
    +
    +      "License" shall mean the terms and conditions for use, reproduction,
    +      and distribution as defined by Sections 1 through 9 of this document.
    +
    +      "Licensor" shall mean the copyright owner or entity authorized by
    +      the copyright owner that is granting the License.
    +
    +      "Legal Entity" shall mean the union of the acting entity and all
    +      other entities that control, are controlled by, or are under common
    +      control with that entity. For the purposes of this definition,
    +      "control" means (i) the power, direct or indirect, to cause the
    +      direction or management of such entity, whether by contract or
    +      otherwise, or (ii) ownership of fifty percent (50%) or more of the
    +      outstanding shares, or (iii) beneficial ownership of such entity.
    +
    +      "You" (or "Your") shall mean an individual or Legal Entity
    +      exercising permissions granted by this License.
    +
    +      "Source" form shall mean the preferred form for making modifications,
    +      including but not limited to software source code, documentation
    +      source, and configuration files.
    +
    +      "Object" form shall mean any form resulting from mechanical
    +      transformation or translation of a Source form, including but
    +      not limited to compiled object code, generated documentation,
    +      and conversions to other media types.
    +
    +      "Work" shall mean the work of authorship, whether in Source or
    +      Object form, made available under the License, as indicated by a
    +      copyright notice that is included in or attached to the work
    +      (an example is provided in the Appendix below).
    +
    +      "Derivative Works" shall mean any work, whether in Source or Object
    +      form, that is based on (or derived from) the Work and for which the
    +      editorial revisions, annotations, elaborations, or other modifications
    +      represent, as a whole, an original work of authorship. For the purposes
    +      of this License, Derivative Works shall not include works that remain
    +      separable from, or merely link (or bind by name) to the interfaces of,
    +      the Work and Derivative Works thereof.
    +
    +      "Contribution" shall mean any work of authorship, including
    +      the original version of the Work and any modifications or additions
    +      to that Work or Derivative Works thereof, that is intentionally
    +      submitted to Licensor for inclusion in the Work by the copyright owner
    +      or by an individual or Legal Entity authorized to submit on behalf of
    +      the copyright owner. For the purposes of this definition, "submitted"
    +      means any form of electronic, verbal, or written communication sent
    +      to the Licensor or its representatives, including but not limited to
    +      communication on electronic mailing lists, source code control systems,
    +      and issue tracking systems that are managed by, or on behalf of, the
    +      Licensor for the purpose of discussing and improving the Work, but
    +      excluding communication that is conspicuously marked or otherwise
    +      designated in writing by the copyright owner as "Not a Contribution."
    +
    +      "Contributor" shall mean Licensor and any individual or Legal Entity
    +      on behalf of whom a Contribution has been received by Licensor and
    +      subsequently incorporated within the Work.
    +
    +   2. Grant of Copyright License. Subject to the terms and conditions of
    +      this License, each Contributor hereby grants to You a perpetual,
    +      worldwide, non-exclusive, no-charge, royalty-free, irrevocable
    +      copyright license to reproduce, prepare Derivative Works of,
    +      publicly display, publicly perform, sublicense, and distribute the
    +      Work and such Derivative Works in Source or Object form.
    +
    +   3. Grant of Patent License. Subject to the terms and conditions of
    +      this License, each Contributor hereby grants to You a perpetual,
    +      worldwide, non-exclusive, no-charge, royalty-free, irrevocable
    +      (except as stated in this section) patent license to make, have made,
    +      use, offer to sell, sell, import, and otherwise transfer the Work,
    +      where such license applies only to those patent claims licensable
    +      by such Contributor that are necessarily infringed by their
    +      Contribution(s) alone or by combination of their Contribution(s)
    +      with the Work to which such Contribution(s) was submitted. If You
    +      institute patent litigation against any entity (including a
    +      cross-claim or counterclaim in a lawsuit) alleging that the Work
    +      or a Contribution incorporated within the Work constitutes direct
    +      or contributory patent infringement, then any patent licenses
    +      granted to You under this License for that Work shall terminate
    +      as of the date such litigation is filed.
    +
    +   4. Redistribution. You may reproduce and distribute copies of the
    +      Work or Derivative Works thereof in any medium, with or without
    +      modifications, and in Source or Object form, provided that You
    +      meet the following conditions:
    +
    +      (a) You must give any other recipients of the Work or
    +          Derivative Works a copy of this License; and
    +
    +      (b) You must cause any modified files to carry prominent notices
    +          stating that You changed the files; and
    +
    +      (c) You must retain, in the Source form of any Derivative Works
    +          that You distribute, all copyright, patent, trademark, and
    +          attribution notices from the Source form of the Work,
    +          excluding those notices that do not pertain to any part of
    +          the Derivative Works; and
    +
    +      (d) If the Work includes a "NOTICE" text file as part of its
    +          distribution, then any Derivative Works that You distribute must
    +          include a readable copy of the attribution notices contained
    +          within such NOTICE file, excluding those notices that do not
    +          pertain to any part of the Derivative Works, in at least one
    +          of the following places: within a NOTICE text file distributed
    +          as part of the Derivative Works; within the Source form or
    +          documentation, if provided along with the Derivative Works; or,
    +          within a display generated by the Derivative Works, if and
    +          wherever such third-party notices normally appear. The contents
    +          of the NOTICE file are for informational purposes only and
    +          do not modify the License. You may add Your own attribution
    +          notices within Derivative Works that You distribute, alongside
    +          or as an addendum to the NOTICE text from the Work, provided
    +          that such additional attribution notices cannot be construed
    +          as modifying the License.
    +
    +      You may add Your own copyright statement to Your modifications and
    +      may provide additional or different license terms and conditions
    +      for use, reproduction, or distribution of Your modifications, or
    +      for any such Derivative Works as a whole, provided Your use,
    +      reproduction, and distribution of the Work otherwise complies with
    +      the conditions stated in this License.
    +
    +   5. Submission of Contributions. Unless You explicitly state otherwise,
    +      any Contribution intentionally submitted for inclusion in the Work
    +      by You to the Licensor shall be under the terms and conditions of
    +      this License, without any additional terms or conditions.
    +      Notwithstanding the above, nothing herein shall supersede or modify
    +      the terms of any separate license agreement you may have executed
    +      with Licensor regarding such Contributions.
    +
    +   6. Trademarks. This License does not grant permission to use the trade
    +      names, trademarks, service marks, or product names of the Licensor,
    +      except as required for reasonable and customary use in describing the
    +      origin of the Work and reproducing the content of the NOTICE file.
    +
    +   7. Disclaimer of Warranty. Unless required by applicable law or
    +      agreed to in writing, Licensor provides the Work (and each
    +      Contributor provides its Contributions) on an "AS IS" BASIS,
    +      WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
    +      implied, including, without limitation, any warranties or conditions
    +      of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
    +      PARTICULAR PURPOSE. You are solely responsible for determining the
    +      appropriateness of using or redistributing the Work and assume any
    +      risks associated with Your exercise of permissions under this License.
    +
    +   8. Limitation of Liability. In no event and under no legal theory,
    +      whether in tort (including negligence), contract, or otherwise,
    +      unless required by applicable law (such as deliberate and grossly
    +      negligent acts) or agreed to in writing, shall any Contributor be
    +      liable to You for damages, including any direct, indirect, special,
    +      incidental, or consequential damages of any character arising as a
    +      result of this License or out of the use or inability to use the
    +      Work (including but not limited to damages for loss of goodwill,
    +      work stoppage, computer failure or malfunction, or any and all
    +      other commercial damages or losses), even if such Contributor
    +      has been advised of the possibility of such damages.
    +
    +   9. Accepting Warranty or Additional Liability. While redistributing
    +      the Work or Derivative Works thereof, You may choose to offer,
    +      and charge a fee for, acceptance of support, warranty, indemnity,
    +      or other liability obligations and/or rights consistent with this
    +      License. However, in accepting such obligations, You may act only
    +      on Your own behalf and on Your sole responsibility, not on behalf
    +      of any other Contributor, and only if You agree to indemnify,
    +      defend, and hold each Contributor harmless for any liability
    +      incurred by, or claims asserted against, such Contributor by reason
    +      of your accepting any such warranty or additional liability.
    +
    +   END OF TERMS AND CONDITIONS
    +
    +   APPENDIX: How to apply the Apache License to your work.
    +
    +      To apply the Apache License to your work, attach the following
    +      boilerplate notice, with the fields enclosed by brackets "[]"
    +      replaced with your own identifying information. (Don't include
    +      the brackets!)  The text should be enclosed in the appropriate
    +      comment syntax for the file format. We also recommend that a
    +      file or class name and description of purpose be included on the
    +      same "printed page" as the copyright notice for easier
    +      identification within third-party archives.
    +
    +   Copyright [yyyy] [name of copyright owner]
    +
    +   Licensed under the Apache License, Version 2.0 (the "License");
    +   you may not use this file except in compliance with the License.
    +   You may obtain a copy of the License at
    +
    +       http://www.apache.org/licenses/LICENSE-2.0
    +
    +   Unless required by applicable law or agreed to in writing, software
    +   distributed under the License is distributed on an "AS IS" BASIS,
    +   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
    +   See the License for the specific language governing permissions and
    +   limitations under the License.
    +
    \ No newline at end of file diff --git a/modules/sd_hijack.py b/modules/sd_hijack.py index 79476783..76cb9120 100644 --- a/modules/sd_hijack.py +++ b/modules/sd_hijack.py @@ -42,6 +42,10 @@ def apply_optimizations(): ldm.modules.attention.CrossAttention.forward = sd_hijack_optimizations.xformers_attention_forward ldm.modules.diffusionmodules.model.AttnBlock.forward = sd_hijack_optimizations.xformers_attnblock_forward optimization_method = 'xformers' + elif cmd_opts.opt_sdp_attention and (hasattr(torch.nn.functional, "scaled_dot_product_attention") and callable(getattr(torch.nn.functional, "scaled_dot_product_attention"))): + print("Applying scaled dot product cross attention optimization.") + ldm.modules.attention.CrossAttention.forward = sd_hijack_optimizations.scaled_dot_product_attention_forward + optimization_method = 'sdp' elif cmd_opts.opt_sub_quad_attention: print("Applying sub-quadratic cross attention optimization.") ldm.modules.attention.CrossAttention.forward = sd_hijack_optimizations.sub_quad_attention_forward diff --git a/modules/sd_hijack_optimizations.py b/modules/sd_hijack_optimizations.py index c02d954c..a324a592 100644 --- a/modules/sd_hijack_optimizations.py +++ b/modules/sd_hijack_optimizations.py @@ -346,6 +346,48 @@ def xformers_attention_forward(self, x, context=None, mask=None): out = rearrange(out, 'b n h d -> b n (h d)', h=h) return self.to_out(out) +# Based on Diffusers usage of scaled dot product attention from https://github.com/huggingface/diffusers/blob/c7da8fd23359a22d0df2741688b5b4f33c26df21/src/diffusers/models/cross_attention.py +# The scaled_dot_product_attention_forward function contains parts of code under Apache-2.0 license listed under Scaled Dot Product Attention in the Licenses section of the web UI interface +def scaled_dot_product_attention_forward(self, x, context=None, mask=None): + batch_size, sequence_length, inner_dim = x.shape + + if mask is not None: + mask = self.prepare_attention_mask(mask, sequence_length, batch_size) + mask = mask.view(batch_size, self.heads, -1, mask.shape[-1]) + + h = self.heads + q_in = self.to_q(x) + context = default(context, x) + + context_k, context_v = hypernetwork.apply_hypernetworks(shared.loaded_hypernetworks, context) + k_in = self.to_k(context_k) + v_in = self.to_v(context_v) + + head_dim = inner_dim // h + q = q_in.view(batch_size, -1, h, head_dim).transpose(1, 2) + k = k_in.view(batch_size, -1, h, head_dim).transpose(1, 2) + v = v_in.view(batch_size, -1, h, head_dim).transpose(1, 2) + + del q_in, k_in, v_in + + dtype = q.dtype + if shared.opts.upcast_attn: + q, k = q.float(), k.float() + + # the output of sdp = (batch, num_heads, seq_len, head_dim) + hidden_states = torch.nn.functional.scaled_dot_product_attention( + q, k, v, attn_mask=mask, dropout_p=0.0, is_causal=False + ) + + hidden_states = hidden_states.transpose(1, 2).reshape(batch_size, -1, h * head_dim) + hidden_states = hidden_states.to(dtype) + + # linear proj + hidden_states = self.to_out[0](hidden_states) + # dropout + hidden_states = self.to_out[1](hidden_states) + return hidden_states + def cross_attention_attnblock_forward(self, x): h_ = x h_ = self.norm(h_) diff --git a/modules/shared.py b/modules/shared.py index 805f9cc1..12d0756b 100644 --- a/modules/shared.py +++ b/modules/shared.py @@ -69,6 +69,7 @@ parser.add_argument("--sub-quad-kv-chunk-size", type=int, help="kv chunk size fo parser.add_argument("--sub-quad-chunk-threshold", type=int, help="the percentage of VRAM threshold for the sub-quadratic cross-attention layer optimization to use chunking", default=None) parser.add_argument("--opt-split-attention-invokeai", action='store_true', help="force-enables InvokeAI's cross-attention layer optimization. By default, it's on when cuda is unavailable.") parser.add_argument("--opt-split-attention-v1", action='store_true', help="enable older version of split attention optimization that does not consume all the VRAM it can find") +parser.add_argument("--opt-sdp-attention", action='store_true', help="enable scaled dot product cross-attention layer optimization; requires PyTorch 2.*") parser.add_argument("--disable-opt-split-attention", action='store_true', help="force-disables cross-attention layer optimization") parser.add_argument("--disable-nan-check", action='store_true', help="do not check if produced images/latent spaces have nans; useful for running without a checkpoint in CI") parser.add_argument("--use-cpu", nargs='+', help="use CPU as torch device for specified modules", default=[], type=str.lower) From f85a192f998dcc17d0a59d8755c76100e8e31bde Mon Sep 17 00:00:00 2001 From: Yea Chen Date: Tue, 7 Mar 2023 04:04:35 +0800 Subject: [PATCH 105/278] Update modules/api/api.py Suggested change by @akx Co-authored-by: Aarni Koskela --- modules/api/api.py | 10 ++-------- 1 file changed, 2 insertions(+), 8 deletions(-) diff --git a/modules/api/api.py b/modules/api/api.py index 46cb7c81..12b38386 100644 --- a/modules/api/api.py +++ b/modules/api/api.py @@ -177,14 +177,8 @@ class Api: return script, script_idx def get_scripts_list(self): - t2ilist = [] - i2ilist = [] - - for a in scripts.scripts_txt2img.titles: - t2ilist.append(str(a.lower())) - - for b in scripts.scripts_img2img.titles: - i2ilist.append(str(b.lower())) + t2ilist = [str(title.lower()) for title in scripts.scripts_txt2img.titles] + i2ilist = [str(title.lower()) for title in scripts.scripts_img2img.titles] return ScriptsList(txt2img = t2ilist, img2img = i2ilist) From 09c73710c9145afdd22bcdb6da68db8e346e35b6 Mon Sep 17 00:00:00 2001 From: vladlearns Date: Wed, 8 Mar 2023 23:00:55 +0200 Subject: [PATCH 106/278] chore: auto update all extensions using scripts --- extensions/update-all.bat | 1 + extensions/update-all.sh | 3 +++ 2 files changed, 4 insertions(+) create mode 100644 extensions/update-all.bat create mode 100644 extensions/update-all.sh diff --git a/extensions/update-all.bat b/extensions/update-all.bat new file mode 100644 index 00000000..75a17cbe --- /dev/null +++ b/extensions/update-all.bat @@ -0,0 +1 @@ +for /d %%i in (*) do @if exist "%%i\.git" (echo Pulling updates for %%i... & git -C "%%i" pull) \ No newline at end of file diff --git a/extensions/update-all.sh b/extensions/update-all.sh new file mode 100644 index 00000000..b00de927 --- /dev/null +++ b/extensions/update-all.sh @@ -0,0 +1,3 @@ +ls | while read dir; do if [ -d "$dir/.git" ]; +then echo "Pulling updates for $dir..."; +git -C "$dir" pull; fi; done \ No newline at end of file From b07b7057f0636aa142471ec27841a8001a85f98b Mon Sep 17 00:00:00 2001 From: vladlearns Date: Thu, 9 Mar 2023 16:25:18 +0200 Subject: [PATCH 107/278] chore: removed scripts and added a flag to launch.py --- extensions/update-all.bat | 1 - extensions/update-all.sh | 3 --- launch.py | 14 +++++++++++++- 3 files changed, 13 insertions(+), 5 deletions(-) delete mode 100644 extensions/update-all.bat delete mode 100644 extensions/update-all.sh diff --git a/extensions/update-all.bat b/extensions/update-all.bat deleted file mode 100644 index 75a17cbe..00000000 --- a/extensions/update-all.bat +++ /dev/null @@ -1 +0,0 @@ -for /d %%i in (*) do @if exist "%%i\.git" (echo Pulling updates for %%i... & git -C "%%i" pull) \ No newline at end of file diff --git a/extensions/update-all.sh b/extensions/update-all.sh deleted file mode 100644 index b00de927..00000000 --- a/extensions/update-all.sh +++ /dev/null @@ -1,3 +0,0 @@ -ls | while read dir; do if [ -d "$dir/.git" ]; -then echo "Pulling updates for $dir..."; -git -C "$dir" pull; fi; done \ No newline at end of file diff --git a/launch.py b/launch.py index a68bb3a9..ba306791 100644 --- a/launch.py +++ b/launch.py @@ -161,7 +161,15 @@ def git_clone(url, dir, name, commithash=None): if commithash is not None: run(f'"{git}" -C "{dir}" checkout {commithash}', None, "Couldn't checkout {name}'s hash: {commithash}") - +def git_pull_recursive(dir): + for subdir, _, _ in os.walk(dir): + if os.path.exists(os.path.join(subdir, '.git')): + try: + output = subprocess.check_output(['git', '-C', subdir, 'pull']) + print(f"Pulled changes for repository in '{subdir}':\n{output.decode('utf-8').strip()}\n") + except subprocess.CalledProcessError as e: + print(f"Couldn't perform 'git pull' on repository in '{subdir}':\n{e.output.decode('utf-8').strip()}\n") + def version_check(commit): try: import requests @@ -247,6 +255,7 @@ def prepare_environment(): args, _ = parser.parse_known_args(sys.argv) sys.argv, _ = extract_arg(sys.argv, '-f') + sys.argv, update_all_extensions = extract_arg(sys.argv, '--update-all-extensions') sys.argv, skip_torch_cuda_test = extract_arg(sys.argv, '--skip-torch-cuda-test') sys.argv, skip_python_version_check = extract_arg(sys.argv, '--skip-python-version-check') sys.argv, reinstall_xformers = extract_arg(sys.argv, '--reinstall-xformers') @@ -312,6 +321,9 @@ def prepare_environment(): if update_check: version_check(commit) + + if update_all_extensions: + git_pull_recursive(dir_extensions) if "--exit" in sys.argv: print("Exiting because of --exit argument") From 13081dd45ece33457f6cb2cad3a8e7840a0a6eaf Mon Sep 17 00:00:00 2001 From: vladlearns Date: Thu, 9 Mar 2023 16:56:06 +0200 Subject: [PATCH 108/278] chore: added autostash flag to pull --- launch.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/launch.py b/launch.py index ba306791..8cbf1ca5 100644 --- a/launch.py +++ b/launch.py @@ -165,7 +165,7 @@ def git_pull_recursive(dir): for subdir, _, _ in os.walk(dir): if os.path.exists(os.path.join(subdir, '.git')): try: - output = subprocess.check_output(['git', '-C', subdir, 'pull']) + output = subprocess.check_output(['git', '-C', subdir, 'pull', '--autostash']) print(f"Pulled changes for repository in '{subdir}':\n{output.decode('utf-8').strip()}\n") except subprocess.CalledProcessError as e: print(f"Couldn't perform 'git pull' on repository in '{subdir}':\n{e.output.decode('utf-8').strip()}\n") From 37acba263389e22bc46cfffc80b2ca8b76a85287 Mon Sep 17 00:00:00 2001 From: Pam Date: Fri, 10 Mar 2023 12:19:36 +0500 Subject: [PATCH 109/278] argument to disable memory efficient for sdp --- modules/sd_hijack.py | 11 ++++++++--- modules/sd_hijack_optimizations.py | 4 ++++ modules/shared.py | 1 + 3 files changed, 13 insertions(+), 3 deletions(-) diff --git a/modules/sd_hijack.py b/modules/sd_hijack.py index 76cb9120..f62e9adb 100644 --- a/modules/sd_hijack.py +++ b/modules/sd_hijack.py @@ -43,9 +43,14 @@ def apply_optimizations(): ldm.modules.diffusionmodules.model.AttnBlock.forward = sd_hijack_optimizations.xformers_attnblock_forward optimization_method = 'xformers' elif cmd_opts.opt_sdp_attention and (hasattr(torch.nn.functional, "scaled_dot_product_attention") and callable(getattr(torch.nn.functional, "scaled_dot_product_attention"))): - print("Applying scaled dot product cross attention optimization.") - ldm.modules.attention.CrossAttention.forward = sd_hijack_optimizations.scaled_dot_product_attention_forward - optimization_method = 'sdp' + if cmd_opts.opt_sdp_no_mem_attention: + print("Applying scaled dot product cross attention optimization (without memory efficient attention).") + ldm.modules.attention.CrossAttention.forward = sd_hijack_optimizations.scaled_dot_product_no_mem_attention_forward + optimization_method = 'sdp-no-mem' + else: + print("Applying scaled dot product cross attention optimization.") + ldm.modules.attention.CrossAttention.forward = sd_hijack_optimizations.scaled_dot_product_attention_forward + optimization_method = 'sdp' elif cmd_opts.opt_sub_quad_attention: print("Applying sub-quadratic cross attention optimization.") ldm.modules.attention.CrossAttention.forward = sd_hijack_optimizations.sub_quad_attention_forward diff --git a/modules/sd_hijack_optimizations.py b/modules/sd_hijack_optimizations.py index a324a592..68b1dd84 100644 --- a/modules/sd_hijack_optimizations.py +++ b/modules/sd_hijack_optimizations.py @@ -388,6 +388,10 @@ def scaled_dot_product_attention_forward(self, x, context=None, mask=None): hidden_states = self.to_out[1](hidden_states) return hidden_states +def scaled_dot_product_no_mem_attention_forward(self, x, context=None, mask=None): + with torch.backends.cuda.sdp_kernel(enable_flash=True, enable_math=True, enable_mem_efficient=False): + return scaled_dot_product_attention_forward(self, x, context, mask) + def cross_attention_attnblock_forward(self, x): h_ = x h_ = self.norm(h_) diff --git a/modules/shared.py b/modules/shared.py index 12d0756b..4b81c591 100644 --- a/modules/shared.py +++ b/modules/shared.py @@ -70,6 +70,7 @@ parser.add_argument("--sub-quad-chunk-threshold", type=int, help="the percentage parser.add_argument("--opt-split-attention-invokeai", action='store_true', help="force-enables InvokeAI's cross-attention layer optimization. By default, it's on when cuda is unavailable.") parser.add_argument("--opt-split-attention-v1", action='store_true', help="enable older version of split attention optimization that does not consume all the VRAM it can find") parser.add_argument("--opt-sdp-attention", action='store_true', help="enable scaled dot product cross-attention layer optimization; requires PyTorch 2.*") +parser.add_argument("--opt-sdp-no-mem-attention", action='store_true', help="disables memory efficient sdp, makes image generation deterministic; requires --opt-sdp-attention") parser.add_argument("--disable-opt-split-attention", action='store_true', help="force-disables cross-attention layer optimization") parser.add_argument("--disable-nan-check", action='store_true', help="do not check if produced images/latent spaces have nans; useful for running without a checkpoint in CI") parser.add_argument("--use-cpu", nargs='+', help="use CPU as torch device for specified modules", default=[], type=str.lower) From 0981dea94832f34d638b1aa8964cfaeffd223b47 Mon Sep 17 00:00:00 2001 From: Pam Date: Fri, 10 Mar 2023 12:58:10 +0500 Subject: [PATCH 110/278] sdp refactoring --- modules/sd_hijack.py | 19 ++++++++++--------- modules/shared.py | 2 +- 2 files changed, 11 insertions(+), 10 deletions(-) diff --git a/modules/sd_hijack.py b/modules/sd_hijack.py index f62e9adb..e98ae51a 100644 --- a/modules/sd_hijack.py +++ b/modules/sd_hijack.py @@ -37,20 +37,21 @@ def apply_optimizations(): optimization_method = None + can_use_sdp = hasattr(torch.nn.functional, "scaled_dot_product_attention") and callable(getattr(torch.nn.functional, "scaled_dot_product_attention")) # not everyone has torch 2.x to use sdp + if cmd_opts.force_enable_xformers or (cmd_opts.xformers and shared.xformers_available and torch.version.cuda and (6, 0) <= torch.cuda.get_device_capability(shared.device) <= (9, 0)): print("Applying xformers cross attention optimization.") ldm.modules.attention.CrossAttention.forward = sd_hijack_optimizations.xformers_attention_forward ldm.modules.diffusionmodules.model.AttnBlock.forward = sd_hijack_optimizations.xformers_attnblock_forward optimization_method = 'xformers' - elif cmd_opts.opt_sdp_attention and (hasattr(torch.nn.functional, "scaled_dot_product_attention") and callable(getattr(torch.nn.functional, "scaled_dot_product_attention"))): - if cmd_opts.opt_sdp_no_mem_attention: - print("Applying scaled dot product cross attention optimization (without memory efficient attention).") - ldm.modules.attention.CrossAttention.forward = sd_hijack_optimizations.scaled_dot_product_no_mem_attention_forward - optimization_method = 'sdp-no-mem' - else: - print("Applying scaled dot product cross attention optimization.") - ldm.modules.attention.CrossAttention.forward = sd_hijack_optimizations.scaled_dot_product_attention_forward - optimization_method = 'sdp' + elif cmd_opts.opt_sdp_no_mem_attention and can_use_sdp: + print("Applying scaled dot product cross attention optimization (without memory efficient attention).") + ldm.modules.attention.CrossAttention.forward = sd_hijack_optimizations.scaled_dot_product_no_mem_attention_forward + optimization_method = 'sdp-no-mem' + elif cmd_opts.opt_sdp_attention and can_use_sdp: + print("Applying scaled dot product cross attention optimization.") + ldm.modules.attention.CrossAttention.forward = sd_hijack_optimizations.scaled_dot_product_attention_forward + optimization_method = 'sdp' elif cmd_opts.opt_sub_quad_attention: print("Applying sub-quadratic cross attention optimization.") ldm.modules.attention.CrossAttention.forward = sd_hijack_optimizations.sub_quad_attention_forward diff --git a/modules/shared.py b/modules/shared.py index 4b81c591..66a6bfa5 100644 --- a/modules/shared.py +++ b/modules/shared.py @@ -70,7 +70,7 @@ parser.add_argument("--sub-quad-chunk-threshold", type=int, help="the percentage parser.add_argument("--opt-split-attention-invokeai", action='store_true', help="force-enables InvokeAI's cross-attention layer optimization. By default, it's on when cuda is unavailable.") parser.add_argument("--opt-split-attention-v1", action='store_true', help="enable older version of split attention optimization that does not consume all the VRAM it can find") parser.add_argument("--opt-sdp-attention", action='store_true', help="enable scaled dot product cross-attention layer optimization; requires PyTorch 2.*") -parser.add_argument("--opt-sdp-no-mem-attention", action='store_true', help="disables memory efficient sdp, makes image generation deterministic; requires --opt-sdp-attention") +parser.add_argument("--opt-sdp-no-mem-attention", action='store_true', help="enable scaled dot product cross-attention layer optimization without memory efficient attention, makes image generation deterministic; requires PyTorch 2.*") parser.add_argument("--disable-opt-split-attention", action='store_true', help="force-disables cross-attention layer optimization") parser.add_argument("--disable-nan-check", action='store_true', help="do not check if produced images/latent spaces have nans; useful for running without a checkpoint in CI") parser.add_argument("--use-cpu", nargs='+', help="use CPU as torch device for specified modules", default=[], type=str.lower) From 1226028b9c1b153b6ceef62d8678ecb84c9d4fcd Mon Sep 17 00:00:00 2001 From: Vladimir Mandic Date: Fri, 10 Mar 2023 11:21:48 -0500 Subject: [PATCH 111/278] fix silly math error --- 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 1ba954ac..8c816a73 100644 --- a/scripts/xyz_grid.py +++ b/scripts/xyz_grid.py @@ -485,7 +485,7 @@ class Script(scripts.Script): zs = process_axis(z_opt, z_values) # this could be moved to common code, but unlikely to be ever triggered anywhere else - Image.MAX_IMAGE_PIXELS = opts.img_max_size_mp * 1.1 # allow 10% overhead for margins and legend + Image.MAX_IMAGE_PIXELS = opts.img_max_size_mp * 1000000 * 1.1 # allow 10% overhead for margins and legend grid_mp = round(len(xs) * len(ys) * len(zs) * p.width * p.height / 1000000) if grid_mp > opts.img_max_size_mp: return Processed(p, [], p.seed, info=f'Error: Resulting grid would be too large ({grid_mp} MPixels) (max configured size is {opts.img_max_size_mp} MPixels)') From 8d7fa2f67cb0554d8902d5d407166876020e067e Mon Sep 17 00:00:00 2001 From: Pam Date: Fri, 10 Mar 2023 22:48:41 +0500 Subject: [PATCH 112/278] sdp_attnblock_forward hijack --- modules/sd_hijack.py | 2 ++ modules/sd_hijack_optimizations.py | 24 ++++++++++++++++++++++++ 2 files changed, 26 insertions(+) diff --git a/modules/sd_hijack.py b/modules/sd_hijack.py index e98ae51a..f4bb0266 100644 --- a/modules/sd_hijack.py +++ b/modules/sd_hijack.py @@ -47,10 +47,12 @@ def apply_optimizations(): elif cmd_opts.opt_sdp_no_mem_attention and can_use_sdp: print("Applying scaled dot product cross attention optimization (without memory efficient attention).") ldm.modules.attention.CrossAttention.forward = sd_hijack_optimizations.scaled_dot_product_no_mem_attention_forward + ldm.modules.diffusionmodules.model.AttnBlock.forward = sd_hijack_optimizations.sdp_no_mem_attnblock_forward optimization_method = 'sdp-no-mem' elif cmd_opts.opt_sdp_attention and can_use_sdp: print("Applying scaled dot product cross attention optimization.") ldm.modules.attention.CrossAttention.forward = sd_hijack_optimizations.scaled_dot_product_attention_forward + ldm.modules.diffusionmodules.model.AttnBlock.forward = sd_hijack_optimizations.sdp_attnblock_forward optimization_method = 'sdp' elif cmd_opts.opt_sub_quad_attention: print("Applying sub-quadratic cross attention optimization.") diff --git a/modules/sd_hijack_optimizations.py b/modules/sd_hijack_optimizations.py index 68b1dd84..2e307b5d 100644 --- a/modules/sd_hijack_optimizations.py +++ b/modules/sd_hijack_optimizations.py @@ -473,6 +473,30 @@ def xformers_attnblock_forward(self, x): except NotImplementedError: return cross_attention_attnblock_forward(self, x) +def sdp_attnblock_forward(self, x): + h_ = x + h_ = self.norm(h_) + q = self.q(h_) + k = self.k(h_) + v = self.v(h_) + b, c, h, w = q.shape + q, k, v = map(lambda t: rearrange(t, 'b c h w -> b (h w) c'), (q, k, v)) + dtype = q.dtype + if shared.opts.upcast_attn: + q, k = q.float(), k.float() + q = q.contiguous() + k = k.contiguous() + v = v.contiguous() + out = torch.nn.functional.scaled_dot_product_attention(q, k, v, dropout_p=0.0, is_causal=False) + out = out.to(dtype) + out = rearrange(out, 'b (h w) c -> b c h w', h=h) + out = self.proj_out(out) + return x + out + +def sdp_no_mem_attnblock_forward(self, x): + with torch.backends.cuda.sdp_kernel(enable_flash=True, enable_math=True, enable_mem_efficient=False): + return sdp_attnblock_forward(self, x) + def sub_quad_attnblock_forward(self, x): h_ = x h_ = self.norm(h_) From 5fef67f6ee949a61826a3a043ea8610fd89fc371 Mon Sep 17 00:00:00 2001 From: space-nuko <24979496+space-nuko@users.noreply.github.com> Date: Fri, 10 Mar 2023 19:56:14 -0500 Subject: [PATCH 113/278] Requested changes --- modules/models/diffusion/uni_pc/sampler.py | 2 +- modules/sd_samplers_compvis.py | 4 +++- modules/shared.py | 3 +-- 3 files changed, 5 insertions(+), 4 deletions(-) diff --git a/modules/models/diffusion/uni_pc/sampler.py b/modules/models/diffusion/uni_pc/sampler.py index 708a9b2b..6bb3bb21 100644 --- a/modules/models/diffusion/uni_pc/sampler.py +++ b/modules/models/diffusion/uni_pc/sampler.py @@ -93,7 +93,7 @@ class UniPCSampler(object): guidance_scale=unconditional_guidance_scale, ) - uni_pc = UniPC(model_fn, ns, predict_x0=True, thresholding=shared.opts.uni_pc_thresholding, variant=shared.opts.uni_pc_variant, condition=conditioning, unconditional_condition=unconditional_conditioning, before_sample=self.before_sample, after_sample=self.after_sample, after_update=self.after_update) + uni_pc = UniPC(model_fn, ns, predict_x0=True, thresholding=False, variant=shared.opts.uni_pc_variant, condition=conditioning, unconditional_condition=unconditional_conditioning, before_sample=self.before_sample, after_sample=self.after_sample, after_update=self.after_update) x = uni_pc.sample(img, steps=S, skip_type=shared.opts.uni_pc_skip_type, method="multistep", order=shared.opts.uni_pc_order, lower_order_final=shared.opts.uni_pc_lower_order_final) return x.to(device), None diff --git a/modules/sd_samplers_compvis.py b/modules/sd_samplers_compvis.py index ad39ab2b..7d07c4a5 100644 --- a/modules/sd_samplers_compvis.py +++ b/modules/sd_samplers_compvis.py @@ -140,10 +140,12 @@ class VanillaStableDiffusionSampler: def adjust_steps_if_invalid(self, p, num_steps): if ((self.config.name == 'DDIM') and p.ddim_discretize == 'uniform') or (self.config.name == 'PLMS') or (self.config.name == 'UniPC'): + if self.config.name == 'UniPC' and num_steps < shared.opts.uni_pc_order: + num_steps = shared.opts.uni_pc_order valid_step = 999 / (1000 // num_steps) if valid_step == math.floor(valid_step): return int(valid_step) + 1 - + return num_steps def sample_img2img(self, p, x, noise, conditioning, unconditional_conditioning, steps=None, image_conditioning=None): diff --git a/modules/shared.py b/modules/shared.py index 7c559fa4..29f8dccb 100644 --- a/modules/shared.py +++ b/modules/shared.py @@ -485,10 +485,9 @@ options_templates.update(options_section(('sampler-params', "Sampler parameters" 's_noise': OptionInfo(1.0, "sigma noise", gr.Slider, {"minimum": 0.0, "maximum": 1.0, "step": 0.01}), 'eta_noise_seed_delta': OptionInfo(0, "Eta noise seed delta", gr.Number, {"precision": 0}), 'always_discard_next_to_last_sigma': OptionInfo(False, "Always discard next-to-last sigma"), - 'uni_pc_variant': OptionInfo("bh1", "UniPC variant", gr.Radio, {"choices": ["bh1", "vary_coeff"]}), + 'uni_pc_variant': OptionInfo("bh1", "UniPC variant", gr.Radio, {"choices": ["bh1", "bh2", "vary_coeff"]}), 'uni_pc_skip_type': OptionInfo("time_uniform", "UniPC skip type", gr.Radio, {"choices": ["time_uniform", "time_quadratic", "logSNR"]}), 'uni_pc_order': OptionInfo(3, "UniPC order (must be < sampling steps)", gr.Slider, {"minimum": 1, "maximum": 50, "step": 1}), - 'uni_pc_thresholding': OptionInfo(False, "UniPC thresholding"), 'uni_pc_lower_order_final': OptionInfo(True, "UniPC lower order final"), })) From f261a4a53c153c630a506bc5282e9955c36b3ef2 Mon Sep 17 00:00:00 2001 From: AUTOMATIC <16777216c@gmail.com> Date: Sat, 11 Mar 2023 11:56:05 +0300 Subject: [PATCH 114/278] use selected device instead of always cuda for UniPC sampler --- modules/models/diffusion/uni_pc/sampler.py | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/modules/models/diffusion/uni_pc/sampler.py b/modules/models/diffusion/uni_pc/sampler.py index 6bb3bb21..bf346ff4 100644 --- a/modules/models/diffusion/uni_pc/sampler.py +++ b/modules/models/diffusion/uni_pc/sampler.py @@ -3,7 +3,8 @@ import torch from .uni_pc import NoiseScheduleVP, model_wrapper, UniPC -from modules import shared +from modules import shared, devices + class UniPCSampler(object): def __init__(self, model, **kwargs): @@ -16,8 +17,8 @@ class UniPCSampler(object): def register_buffer(self, name, attr): if type(attr) == torch.Tensor: - if attr.device != torch.device("cuda"): - attr = attr.to(torch.device("cuda")) + if attr.device != devices.device: + attr = attr.to(devices.device) setattr(self, name, attr) def set_hooks(self, before_sample, after_sample, after_update): From 58b5b7c2f1d3b843803c1fc7a0aae8b1d6be5763 Mon Sep 17 00:00:00 2001 From: AUTOMATIC <16777216c@gmail.com> Date: Sat, 11 Mar 2023 12:09:36 +0300 Subject: [PATCH 115/278] add UniPC options to infotext --- modules/generation_parameters_copypaste.py | 8 +++++++- modules/sd_samplers_compvis.py | 14 ++++++++++++++ modules/shared.py | 9 +++++++++ 3 files changed, 30 insertions(+), 1 deletion(-) diff --git a/modules/generation_parameters_copypaste.py b/modules/generation_parameters_copypaste.py index 89dc23bf..cb367655 100644 --- a/modules/generation_parameters_copypaste.py +++ b/modules/generation_parameters_copypaste.py @@ -288,6 +288,8 @@ Steps: 20, Sampler: Euler a, CFG scale: 7, Seed: 965400086, Size: 512x512, Model settings_map = {} + + infotext_to_setting_name_mapping = [ ('Clip skip', 'CLIP_stop_at_last_layers', ), ('Conditional mask weight', 'inpainting_mask_weight'), @@ -296,7 +298,11 @@ infotext_to_setting_name_mapping = [ ('Noise multiplier', 'initial_noise_multiplier'), ('Eta', 'eta_ancestral'), ('Eta DDIM', 'eta_ddim'), - ('Discard penultimate sigma', 'always_discard_next_to_last_sigma') + ('Discard penultimate sigma', 'always_discard_next_to_last_sigma'), + ('UniPC variant', 'uni_pc_variant'), + ('UniPC skip type', 'uni_pc_skip_type'), + ('UniPC order', 'uni_pc_order'), + ('UniPC lower order final', 'uni_pc_lower_order_final'), ] diff --git a/modules/sd_samplers_compvis.py b/modules/sd_samplers_compvis.py index 7d07c4a5..083da18c 100644 --- a/modules/sd_samplers_compvis.py +++ b/modules/sd_samplers_compvis.py @@ -129,6 +129,19 @@ class VanillaStableDiffusionSampler: if self.eta != 0.0: p.extra_generation_params["Eta DDIM"] = self.eta + if self.is_unipc: + keys = [ + ('UniPC variant', 'uni_pc_variant'), + ('UniPC skip type', 'uni_pc_skip_type'), + ('UniPC order', 'uni_pc_order'), + ('UniPC lower order final', 'uni_pc_lower_order_final'), + ] + + for name, key in keys: + v = getattr(shared.opts, key) + if v != shared.opts.get_default(key): + p.extra_generation_params[name] = v + for fieldname in ['p_sample_ddim', 'p_sample_plms']: if hasattr(self.sampler, fieldname): setattr(self.sampler, fieldname, self.p_sample_ddim_hook) @@ -138,6 +151,7 @@ class VanillaStableDiffusionSampler: self.mask = p.mask if hasattr(p, 'mask') else None self.nmask = p.nmask if hasattr(p, 'nmask') else None + def adjust_steps_if_invalid(self, p, num_steps): if ((self.config.name == 'DDIM') and p.ddim_discretize == 'uniform') or (self.config.name == 'PLMS') or (self.config.name == 'UniPC'): if self.config.name == 'UniPC' and num_steps < shared.opts.uni_pc_order: diff --git a/modules/shared.py b/modules/shared.py index 29f8dccb..d481c25b 100644 --- a/modules/shared.py +++ b/modules/shared.py @@ -563,6 +563,15 @@ class Options: return True + def get_default(self, key): + """returns the default value for the key""" + + data_label = self.data_labels.get(key) + if data_label is None: + return None + + return data_label.default + def save(self, filename): assert not cmd_opts.freeze_settings, "saving settings is disabled" From 1ace16e799c1ff43a6f67947be2506c2f83857a1 Mon Sep 17 00:00:00 2001 From: AUTOMATIC <16777216c@gmail.com> Date: Sat, 11 Mar 2023 12:21:53 +0300 Subject: [PATCH 116/278] use path to git from env variable for git_pull_recursive --- launch.py | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/launch.py b/launch.py index 8cbf1ca5..0868f8a9 100644 --- a/launch.py +++ b/launch.py @@ -161,15 +161,17 @@ def git_clone(url, dir, name, commithash=None): if commithash is not None: run(f'"{git}" -C "{dir}" checkout {commithash}', None, "Couldn't checkout {name}'s hash: {commithash}") + def git_pull_recursive(dir): for subdir, _, _ in os.walk(dir): if os.path.exists(os.path.join(subdir, '.git')): try: - output = subprocess.check_output(['git', '-C', subdir, 'pull', '--autostash']) + output = subprocess.check_output([git, '-C', subdir, 'pull', '--autostash']) print(f"Pulled changes for repository in '{subdir}':\n{output.decode('utf-8').strip()}\n") except subprocess.CalledProcessError as e: print(f"Couldn't perform 'git pull' on repository in '{subdir}':\n{e.output.decode('utf-8').strip()}\n") + def version_check(commit): try: import requests From 3531a50080e63197752dd4d9b49f0ac34a758e12 Mon Sep 17 00:00:00 2001 From: AUTOMATIC <16777216c@gmail.com> Date: Sat, 11 Mar 2023 13:22:59 +0300 Subject: [PATCH 117/278] rename fields for API for saving/sending images save images to correct directories --- modules/api/api.py | 41 +++++++++++++++++------------------------ modules/api/models.py | 8 ++++---- modules/images.py | 3 --- 3 files changed, 21 insertions(+), 31 deletions(-) diff --git a/modules/api/api.py b/modules/api/api.py index a6bb439c..fbd50552 100644 --- a/modules/api/api.py +++ b/modules/api/api.py @@ -178,29 +178,27 @@ class Api: def text2imgapi(self, txt2imgreq: StableDiffusionTxt2ImgProcessingAPI): script, script_idx = self.get_script(txt2imgreq.script_name, scripts.scripts_txt2img) - populate = txt2imgreq.copy(update={ # Override __init__ params + populate = txt2imgreq.copy(update={ # Override __init__ params "sampler_name": validate_sampler_name(txt2imgreq.sampler_name or txt2imgreq.sampler_index), - "do_not_save_samples": txt2imgreq.do_not_save, - "do_not_save_grid": txt2imgreq.do_not_save, - } - ) + "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 args = vars(populate) args.pop('script_name', None) - send_images = True if not 'do_not_send' in args else not args['do_not_send'] - args.pop('do_not_send', None) - args.pop('do_not_save', None) + send_images = args.pop('send_images', True) + args.pop('save_images', None) with self.queue_lock: p = StableDiffusionProcessingTxt2Img(sd_model=shared.sd_model, **args) + p.outpath_grids = opts.outdir_txt2img_grids + p.outpath_samples = opts.outdir_txt2img_samples shared.state.begin() if script is not None: - p.outpath_grids = opts.outdir_txt2img_grids - p.outpath_samples = opts.outdir_txt2img_samples p.script_args = [script_idx + 1] + [None] * (script.args_from - 1) + p.script_args processed = scripts.scripts_txt2img.run(p, *p.script_args) else: @@ -222,13 +220,12 @@ class Api: if mask: mask = decode_base64_to_image(mask) - populate = img2imgreq.copy(update={ # Override __init__ params + populate = img2imgreq.copy(update={ # Override __init__ params "sampler_name": validate_sampler_name(img2imgreq.sampler_name or img2imgreq.sampler_index), - "do_not_save_samples": img2imgreq.do_not_save, - "do_not_save_grid": img2imgreq.do_not_save, - "mask": mask - } - ) + "do_not_save_samples": not img2imgreq.save_images, + "do_not_save_grid": not img2imgreq.save_images, + "mask": mask, + }) if populate.sampler_name: populate.sampler_index = None # prevent a warning later on @@ -236,21 +233,17 @@ class Api: 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) - send_images = True if not 'do_not_send' in args else not args['do_not_send'] - args.pop('do_not_send', None) - args.pop('do_not_save', None) - - send_images = True if not 'do_not_send_images' in args else not args['do_not_send_images'] - args.pop('do_not_send_images', None) + send_images = args.pop('send_images', True) + args.pop('save_images', None) with self.queue_lock: p = StableDiffusionProcessingImg2Img(sd_model=shared.sd_model, **args) p.init_images = [decode_base64_to_image(x) for x in init_images] + p.outpath_grids = opts.outdir_img2img_grids + p.outpath_samples = opts.outdir_img2img_samples shared.state.begin() if script is not None: - p.outpath_grids = opts.outdir_img2img_grids - p.outpath_samples = opts.outdir_img2img_samples p.script_args = [script_idx + 1] + [None] * (script.args_from - 1) + p.script_args processed = scripts.scripts_img2img.run(p, *p.script_args) else: diff --git a/modules/api/models.py b/modules/api/models.py index 2b66e1f0..ff3fb344 100644 --- a/modules/api/models.py +++ b/modules/api/models.py @@ -104,8 +104,8 @@ StableDiffusionTxt2ImgProcessingAPI = PydanticModelGenerator( {"key": "sampler_index", "type": str, "default": "Euler"}, {"key": "script_name", "type": str, "default": None}, {"key": "script_args", "type": list, "default": []}, - {"key": "do_not_send", "type": bool, "default": False}, - {"key": "do_not_save", "type": bool, "default": True} + {"key": "send_images", "type": bool, "default": True}, + {"key": "save_images", "type": bool, "default": False}, ] ).generate_model() @@ -120,8 +120,8 @@ StableDiffusionImg2ImgProcessingAPI = PydanticModelGenerator( {"key": "include_init_images", "type": bool, "default": False, "exclude" : True}, {"key": "script_name", "type": str, "default": None}, {"key": "script_args", "type": list, "default": []}, - {"key": "do_not_send", "type": bool, "default": False}, - {"key": "do_not_save", "type": bool, "default": True} + {"key": "send_images", "type": bool, "default": True}, + {"key": "save_images", "type": bool, "default": False}, ] ).generate_model() diff --git a/modules/images.py b/modules/images.py index f8e62b71..5b80c23e 100644 --- a/modules/images.py +++ b/modules/images.py @@ -489,9 +489,6 @@ def save_image(image, path, basename, seed=None, prompt=None, extension='png', i """ namegen = FilenameGenerator(p, seed, prompt, image) - if path is None: # set default path to avoid errors when functions are triggered manually or via api and param is not set - path = opts.outdir_save - 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 946797b01de3671c9969f6f7d55e35ef1adaa6e6 Mon Sep 17 00:00:00 2001 From: butaixianran Date: Sat, 11 Mar 2023 18:42:14 +0800 Subject: [PATCH 118/278] update "replace preview" link button's css modify css `.extra-network-thumbs .card:hover .additional a` 's value from `block` to `inline-block`. So, extensions can add more buttons to extra network's thumbnail card. --- style.css | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/style.css b/style.css index 05572f66..f3fb6069 100644 --- a/style.css +++ b/style.css @@ -856,7 +856,7 @@ footer { } .extra-network-thumbs .card:hover .additional a { - display: block; + display: inline-block; } .extra-network-thumbs .actions .additional a { From aaa367e35ce4e823219c2954ca141ca1ed14800e Mon Sep 17 00:00:00 2001 From: AUTOMATIC <16777216c@gmail.com> Date: Sat, 11 Mar 2023 14:18:18 +0300 Subject: [PATCH 119/278] new setting: Extra text to add before <...> when adding extra network to prompt --- javascript/extraNetworks.js | 2 +- modules/shared.py | 1 + 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/javascript/extraNetworks.js b/javascript/extraNetworks.js index 17bf2000..5781df4f 100644 --- a/javascript/extraNetworks.js +++ b/javascript/extraNetworks.js @@ -78,7 +78,7 @@ function cardClicked(tabname, textToAdd, allowNegativePrompt){ var textarea = allowNegativePrompt ? activePromptTextarea[tabname] : gradioApp().querySelector("#" + tabname + "_prompt > label > textarea") if(! tryToRemoveExtraNetworkFromPrompt(textarea, textToAdd)){ - textarea.value = textarea.value + " " + textToAdd + textarea.value = textarea.value + opts.extra_networks_add_text_separator + textToAdd } updateInput(textarea) diff --git a/modules/shared.py b/modules/shared.py index dbab0018..28d952dd 100644 --- a/modules/shared.py +++ b/modules/shared.py @@ -442,6 +442,7 @@ options_templates.update(options_section(('interrogate', "Interrogate Options"), options_templates.update(options_section(('extra_networks', "Extra Networks"), { "extra_networks_default_view": OptionInfo("cards", "Default view for Extra Networks", gr.Dropdown, {"choices": ["cards", "thumbs"]}), "extra_networks_default_multiplier": OptionInfo(1.0, "Multiplier for extra networks", gr.Slider, {"minimum": 0.0, "maximum": 1.0, "step": 0.01}), + "extra_networks_add_text_separator": OptionInfo(" ", "Extra text to add before <...> when adding extra network to prompt"), "sd_hypernetwork": OptionInfo("None", "Add hypernetwork to prompt", gr.Dropdown, lambda: {"choices": [""] + [x for x in hypernetworks.keys()]}, refresh=reload_hypernetworks), })) From 7f2005127ff20170e2d92f353416c7f0705c593b Mon Sep 17 00:00:00 2001 From: AUTOMATIC <16777216c@gmail.com> Date: Sat, 11 Mar 2023 14:52:29 +0300 Subject: [PATCH 120/278] rename CFGDenoiserParams fields for #8064 --- modules/script_callbacks.py | 10 +++++----- modules/sd_samplers_kdiffusion.py | 4 ++-- 2 files changed, 7 insertions(+), 7 deletions(-) diff --git a/modules/script_callbacks.py b/modules/script_callbacks.py index d1703135..07911876 100644 --- a/modules/script_callbacks.py +++ b/modules/script_callbacks.py @@ -29,7 +29,7 @@ class ImageSaveParams: class CFGDenoiserParams: - def __init__(self, x, image_cond, sigma, sampling_step, total_sampling_steps, tensor, uncond): + def __init__(self, x, image_cond, sigma, sampling_step, total_sampling_steps, text_cond, text_uncond): self.x = x """Latent image representation in the process of being denoised""" @@ -45,11 +45,11 @@ class CFGDenoiserParams: self.total_sampling_steps = total_sampling_steps """Total number of sampling steps planned""" - self.tensor = tensor - """ Encoder hidden states of conditioning""" + self.text_cond = text_cond + """ Encoder hidden states of text conditioning from prompt""" - self.uncond = uncond - """ Encoder hidden states of unconditioning""" + self.text_uncond = text_uncond + """ Encoder hidden states of text conditioning from negative prompt""" class CFGDenoisedParams: diff --git a/modules/sd_samplers_kdiffusion.py b/modules/sd_samplers_kdiffusion.py index ea974be0..93f0e55a 100644 --- a/modules/sd_samplers_kdiffusion.py +++ b/modules/sd_samplers_kdiffusion.py @@ -106,8 +106,8 @@ class CFGDenoiser(torch.nn.Module): x_in = denoiser_params.x image_cond_in = denoiser_params.image_cond sigma_in = denoiser_params.sigma - tensor = denoiser_params.tensor - uncond = denoiser_params.uncond + tensor = denoiser_params.text_cond + uncond = denoiser_params.text_uncond if tensor.shape[1] == uncond.shape[1]: if not is_edit_model: From d006108d75d74b3237ccbb60a9373bf28c2b85d7 Mon Sep 17 00:00:00 2001 From: Zhang Hua Date: Fri, 10 Mar 2023 16:35:55 +0800 Subject: [PATCH 121/278] webui.sh: remove all `cd` related code This may be helpful for https://github.com/AUTOMATIC1111/stable-diffusion-webui/issues/7028, because we won't change working directory to the repo now, instead, we will use any working directory. If we set working directory to a path contains repo and the custom --data-dir, the problem in this issue should be solved. Howewer, this may be treated as an incompatible change if some code assume the working directory is always the repo. Also, there may be another solution that always let --data-dir be the subdirectory of the repo, but personally I think this may not be what we actually need. As this issue mainly influent on Docker and I am not familiar with .bat files, updating webui.bat is skipped. webui.sh: source env from repo instead $PWD --- webui.sh | 28 ++++++++++++++-------------- 1 file changed, 14 insertions(+), 14 deletions(-) diff --git a/webui.sh b/webui.sh index 8cdad22d..7b6e0568 100755 --- a/webui.sh +++ b/webui.sh @@ -6,19 +6,18 @@ # If run from macOS, load defaults from webui-macos-env.sh if [[ "$OSTYPE" == "darwin"* ]]; then - if [[ -f webui-macos-env.sh ]] + if [[ -f "$(dirname $0)/webui-macos-env.sh" ]] then - source ./webui-macos-env.sh + source "$(dirname $0)/webui-macos-env.sh" fi fi # Read variables from webui-user.sh # shellcheck source=/dev/null -if [[ -f webui-user.sh ]] +if [[ -f "$(dirname $0)/webui-user.sh" ]] then - source ./webui-user.sh + source "$(dirname $0)/webui-user.sh" fi - # Set defaults # Install directory without trailing slash if [[ -z "${install_dir}" ]] @@ -47,12 +46,12 @@ fi # python3 venv without trailing slash (defaults to ${install_dir}/${clone_dir}/venv) if [[ -z "${venv_dir}" ]] then - venv_dir="venv" + venv_dir="${install_dir}/${clone_dir}/venv" fi if [[ -z "${LAUNCH_SCRIPT}" ]] then - LAUNCH_SCRIPT="launch.py" + LAUNCH_SCRIPT="${install_dir}/${clone_dir}/launch.py" fi # this script cannot be run as root by default @@ -140,22 +139,23 @@ then exit 1 fi -cd "${install_dir}"/ || { printf "\e[1m\e[31mERROR: Can't cd to %s/, aborting...\e[0m" "${install_dir}"; exit 1; } -if [[ -d "${clone_dir}" ]] +if [[ ! -d "${install_dir}/${clone_dir}" ]] then - cd "${clone_dir}"/ || { printf "\e[1m\e[31mERROR: Can't cd to %s/%s/, aborting...\e[0m" "${install_dir}" "${clone_dir}"; exit 1; } -else printf "\n%s\n" "${delimiter}" printf "Clone stable-diffusion-webui" printf "\n%s\n" "${delimiter}" - "${GIT}" clone https://github.com/AUTOMATIC1111/stable-diffusion-webui.git "${clone_dir}" - cd "${clone_dir}"/ || { printf "\e[1m\e[31mERROR: Can't cd to %s/%s/, aborting...\e[0m" "${install_dir}" "${clone_dir}"; exit 1; } + mkdir -p "${install_dir}" + "${GIT}" clone https://github.com/AUTOMATIC1111/stable-diffusion-webui.git "${install_dir}/${clone_dir}" fi printf "\n%s\n" "${delimiter}" printf "Create and activate python venv" printf "\n%s\n" "${delimiter}" -cd "${install_dir}"/"${clone_dir}"/ || { printf "\e[1m\e[31mERROR: Can't cd to %s/%s/, aborting...\e[0m" "${install_dir}" "${clone_dir}"; exit 1; } +# Make venv_dir absolute +if [[ "${venv_dir}" != /* ]] +then + venv_dir="${install_dir}/${clone_dir}/${venv_dir}" +fi if [[ ! -d "${venv_dir}" ]] then "${python_cmd}" -m venv "${venv_dir}" From 1fa1ab5249ccc7acaafa5f3e11c6925f91543f8b Mon Sep 17 00:00:00 2001 From: Zhang Hua Date: Fri, 10 Mar 2023 21:38:35 +0800 Subject: [PATCH 122/278] launch.py: fix failure because webui.sh's changes launch.py: using getcwd() instead curdir launch.py: use absolute path for preparing also remove chdir() launch.py: use absolute path for test launch.py: add default script_path and data_path --- launch.py | 19 ++++++++++++------- 1 file changed, 12 insertions(+), 7 deletions(-) diff --git a/launch.py b/launch.py index 0868f8a9..e1908496 100644 --- a/launch.py +++ b/launch.py @@ -7,6 +7,11 @@ import shlex import platform import argparse import json +try: + from modules.paths import script_path, data_path +except ModuleNotFoundError: + script_path = os.path.dirname(__file__) + data_path = os.getcwd() dir_repos = "repositories" dir_extensions = "extensions" @@ -122,7 +127,7 @@ def is_installed(package): def repo_dir(name): - return os.path.join(dir_repos, name) + return os.path.join(script_path, dir_repos, name) def run_python(code, desc=None, errdesc=None): @@ -215,7 +220,7 @@ def list_extensions(settings_file): disabled_extensions = set(settings.get('disabled_extensions', [])) - return [x for x in os.listdir(dir_extensions) if x not in disabled_extensions] + return [x for x in os.listdir(os.path.join(data_path, dir_extensions)) if x not in disabled_extensions] def run_extensions_installers(settings_file): @@ -306,7 +311,7 @@ def prepare_environment(): if not is_installed("pyngrok") and ngrok: run_pip("install pyngrok", "ngrok") - os.makedirs(dir_repos, exist_ok=True) + os.makedirs(os.path.join(script_path, dir_repos), exist_ok=True) git_clone(stable_diffusion_repo, repo_dir('stable-diffusion-stability-ai'), "Stable Diffusion", stable_diffusion_commit_hash) git_clone(taming_transformers_repo, repo_dir('taming-transformers'), "Taming Transformers", taming_transformers_commit_hash) @@ -317,7 +322,7 @@ def prepare_environment(): if not is_installed("lpips"): run_pip(f"install -r {os.path.join(repo_dir('CodeFormer'), 'requirements.txt')}", "requirements for CodeFormer") - run_pip(f"install -r {requirements_file}", "requirements for Web UI") + run_pip(f"install -r {os.path.join(script_path, requirements_file)}", "requirements for Web UI") run_extensions_installers(settings_file=args.ui_settings_file) @@ -325,7 +330,7 @@ def prepare_environment(): version_check(commit) if update_all_extensions: - git_pull_recursive(dir_extensions) + git_pull_recursive(os.path.join(data_path, dir_extensions)) if "--exit" in sys.argv: print("Exiting because of --exit argument") @@ -341,7 +346,7 @@ def tests(test_dir): sys.argv.append("--api") if "--ckpt" not in sys.argv: sys.argv.append("--ckpt") - sys.argv.append("./test/test_files/empty.pt") + sys.argv.append(os.path.join(script_path, "test/test_files/empty.pt")) if "--skip-torch-cuda-test" not in sys.argv: sys.argv.append("--skip-torch-cuda-test") if "--disable-nan-check" not in sys.argv: @@ -350,7 +355,7 @@ def tests(test_dir): print(f"Launching Web UI in another process for testing with arguments: {' '.join(sys.argv[1:])}") os.environ['COMMANDLINE_ARGS'] = "" - with open('test/stdout.txt', "w", encoding="utf8") as stdout, open('test/stderr.txt', "w", encoding="utf8") as stderr: + with open(os.path.join(script_path, 'test/stdout.txt'), "w", encoding="utf8") as stdout, open(os.path.join(script_path, 'test/stderr.txt'), "w", encoding="utf8") as stderr: proc = subprocess.Popen([sys.executable, *sys.argv], stdout=stdout, stderr=stderr) import test.server_poll From 8106117a474a5e62dbd73687dadc6e7b7637267d Mon Sep 17 00:00:00 2001 From: Zhang Hua Date: Sat, 11 Mar 2023 09:18:08 +0800 Subject: [PATCH 123/278] models/ui.py: make the path of script.js absolute --- modules/ui.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/modules/ui.py b/modules/ui.py index 0516c643..56b55f29 100644 --- a/modules/ui.py +++ b/modules/ui.py @@ -1745,7 +1745,8 @@ def create_ui(): def reload_javascript(): - head = f'\n' + script_js = os.path.join(script_path, "script.js") + head = f'\n' inline = f"{localization.localization_js(shared.opts.localization)};" if cmd_opts.theme is not None: From 8e0d16e746759b1f9b4bf1b5abfc30f3d985415e Mon Sep 17 00:00:00 2001 From: Zhang Hua Date: Sat, 11 Mar 2023 12:22:59 +0800 Subject: [PATCH 124/278] modules/sd_vae_approx.py: fix VAE-approx path --- modules/sd_vae_approx.py | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/modules/sd_vae_approx.py b/modules/sd_vae_approx.py index 0027343a..e2f00468 100644 --- a/modules/sd_vae_approx.py +++ b/modules/sd_vae_approx.py @@ -35,8 +35,11 @@ def model(): global sd_vae_approx_model if sd_vae_approx_model is None: + model_path = os.path.join(paths.models_path, "VAE-approx", "model.pt") sd_vae_approx_model = VAEApprox() - sd_vae_approx_model.load_state_dict(torch.load(os.path.join(paths.models_path, "VAE-approx", "model.pt"), map_location='cpu' if devices.device.type != 'cuda' else None)) + if not os.path.exists(model_path): + model_path = os.path.join(paths.script_path, "models", "VAE-approx", "model.pt") + sd_vae_approx_model.load_state_dict(torch.load(model_path, map_location='cpu' if devices.device.type != 'cuda' else None)) sd_vae_approx_model.eval() sd_vae_approx_model.to(devices.device, devices.dtype) From 9abe2f5e74f02d4c8e47d2d4e03464179e9f0aa2 Mon Sep 17 00:00:00 2001 From: Zhang Hua Date: Sat, 11 Mar 2023 13:27:10 +0800 Subject: [PATCH 125/278] test/server_poll.py: use absolute path for test test/server_poll.py: fix absolute path --- test/server_poll.py | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/test/server_poll.py b/test/server_poll.py index 42d56a4c..c732630f 100644 --- a/test/server_poll.py +++ b/test/server_poll.py @@ -1,6 +1,8 @@ import unittest import requests import time +import os +from modules.paths import script_path def run_tests(proc, test_dir): @@ -15,8 +17,8 @@ def run_tests(proc, test_dir): break if proc.poll() is None: if test_dir is None: - test_dir = "test" - suite = unittest.TestLoader().discover(test_dir, pattern="*_test.py", top_level_dir="test") + test_dir = os.path.join(script_path, "test") + suite = unittest.TestLoader().discover(test_dir, pattern="*_test.py", top_level_dir=test_dir) result = unittest.TextTestRunner(verbosity=2).run(suite) return len(result.failures) + len(result.errors) else: From d25c4b13e4be0c89401637c769e3634e7ee456a7 Mon Sep 17 00:00:00 2001 From: Zhang Hua Date: Sat, 11 Mar 2023 14:42:31 +0800 Subject: [PATCH 126/278] test/basic_features/{extras,img2img}_test.py: use absolute path --- test/basic_features/extras_test.py | 8 +++++--- test/basic_features/img2img_test.py | 8 +++++--- 2 files changed, 10 insertions(+), 6 deletions(-) diff --git a/test/basic_features/extras_test.py b/test/basic_features/extras_test.py index 0170c511..8ed98747 100644 --- a/test/basic_features/extras_test.py +++ b/test/basic_features/extras_test.py @@ -1,7 +1,9 @@ +import os import unittest import requests from gradio.processing_utils import encode_pil_to_base64 from PIL import Image +from modules.paths import script_path class TestExtrasWorking(unittest.TestCase): def setUp(self): @@ -19,7 +21,7 @@ class TestExtrasWorking(unittest.TestCase): "upscaler_1": "None", "upscaler_2": "None", "extras_upscaler_2_visibility": 0, - "image": encode_pil_to_base64(Image.open(r"test/test_files/img2img_basic.png")) + "image": encode_pil_to_base64(Image.open(os.path.join(script_path, r"test/test_files/img2img_basic.png"))) } def test_simple_upscaling_performed(self): @@ -31,7 +33,7 @@ class TestPngInfoWorking(unittest.TestCase): def setUp(self): self.url_png_info = "http://localhost:7860/sdapi/v1/extra-single-image" self.png_info = { - "image": encode_pil_to_base64(Image.open(r"test/test_files/img2img_basic.png")) + "image": encode_pil_to_base64(Image.open(os.path.join(script_path, r"test/test_files/img2img_basic.png"))) } def test_png_info_performed(self): @@ -42,7 +44,7 @@ class TestInterrogateWorking(unittest.TestCase): def setUp(self): self.url_interrogate = "http://localhost:7860/sdapi/v1/extra-single-image" self.interrogate = { - "image": encode_pil_to_base64(Image.open(r"test/test_files/img2img_basic.png")), + "image": encode_pil_to_base64(Image.open(os.path.join(script_path, r"test/test_files/img2img_basic.png"))), "model": "clip" } diff --git a/test/basic_features/img2img_test.py b/test/basic_features/img2img_test.py index 08c5c903..5240ec36 100644 --- a/test/basic_features/img2img_test.py +++ b/test/basic_features/img2img_test.py @@ -1,14 +1,16 @@ +import os import unittest import requests from gradio.processing_utils import encode_pil_to_base64 from PIL import Image +from modules.paths import script_path class TestImg2ImgWorking(unittest.TestCase): def setUp(self): self.url_img2img = "http://localhost:7860/sdapi/v1/img2img" self.simple_img2img = { - "init_images": [encode_pil_to_base64(Image.open(r"test/test_files/img2img_basic.png"))], + "init_images": [encode_pil_to_base64(Image.open(os.path.join(script_path, r"test/test_files/img2img_basic.png")))], "resize_mode": 0, "denoising_strength": 0.75, "mask": None, @@ -47,11 +49,11 @@ class TestImg2ImgWorking(unittest.TestCase): self.assertEqual(requests.post(self.url_img2img, json=self.simple_img2img).status_code, 200) def test_inpainting_masked_performed(self): - self.simple_img2img["mask"] = encode_pil_to_base64(Image.open(r"test/test_files/mask_basic.png")) + self.simple_img2img["mask"] = encode_pil_to_base64(Image.open(os.path.join(script_path, r"test/test_files/img2img_basic.png"))) self.assertEqual(requests.post(self.url_img2img, json=self.simple_img2img).status_code, 200) def test_inpainting_with_inverted_masked_performed(self): - self.simple_img2img["mask"] = encode_pil_to_base64(Image.open(r"test/test_files/mask_basic.png")) + self.simple_img2img["mask"] = encode_pil_to_base64(Image.open(os.path.join(script_path, r"test/test_files/img2img_basic.png"))) self.simple_img2img["inpainting_mask_invert"] = True self.assertEqual(requests.post(self.url_img2img, json=self.simple_img2img).status_code, 200) From f36ba9949a64fd35a81369e4ec7107b1b87ef7fb Mon Sep 17 00:00:00 2001 From: AUTOMATIC <16777216c@gmail.com> Date: Sat, 11 Mar 2023 15:02:50 +0300 Subject: [PATCH 127/278] add credit for UniPC sampler into the readme --- README.md | 1 + 1 file changed, 1 insertion(+) diff --git a/README.md b/README.md index 2ceb4d2d..24f8e799 100644 --- a/README.md +++ b/README.md @@ -157,5 +157,6 @@ Licenses for borrowed code can be found in `Settings -> Licenses` screen, and al - Sampling in float32 precision from a float16 UNet - marunine for the idea, Birch-san for the example Diffusers implementation (https://github.com/Birch-san/diffusers-play/tree/92feee6) - Instruct pix2pix - Tim Brooks (star), Aleksander Holynski (star), Alexei A. Efros (no star) - https://github.com/timothybrooks/instruct-pix2pix - Security advice - RyotaK +- UniPC sampler - Wenliang Zhao - https://github.com/wl-zhao/UniPC - Initial Gradio script - posted on 4chan by an Anonymous user. Thank you Anonymous user. - (You) From ce68ab8d0dfdd25e820c58dbc9d3b0148a2022a4 Mon Sep 17 00:00:00 2001 From: AUTOMATIC <16777216c@gmail.com> Date: Sat, 11 Mar 2023 15:27:42 +0300 Subject: [PATCH 128/278] remove underscores from function names in #8366 remove LRU from #8366 because I don't know why it's there --- extensions-builtin/Lora/ui_extra_networks_lora.py | 4 ++-- modules/ui_extra_networks.py | 8 +++----- modules/ui_extra_networks_checkpoints.py | 4 ++-- modules/ui_extra_networks_hypernets.py | 4 ++-- modules/ui_extra_networks_textual_inversion.py | 4 ++-- 5 files changed, 11 insertions(+), 13 deletions(-) diff --git a/extensions-builtin/Lora/ui_extra_networks_lora.py b/extensions-builtin/Lora/ui_extra_networks_lora.py index 9da13a09..6815f6ef 100644 --- a/extensions-builtin/Lora/ui_extra_networks_lora.py +++ b/extensions-builtin/Lora/ui_extra_networks_lora.py @@ -18,8 +18,8 @@ class ExtraNetworksPageLora(ui_extra_networks.ExtraNetworksPage): yield { "name": name, "filename": path, - "preview": self._find_preview(path), - "description": self._find_description(path), + "preview": self.find_preview(path), + "description": self.find_description(path), "search_term": self.search_terms_from_path(lora_on_disk.filename), "prompt": json.dumps(f""), "local_preview": path + ".png", diff --git a/modules/ui_extra_networks.py b/modules/ui_extra_networks.py index cd61a569..21c52287 100644 --- a/modules/ui_extra_networks.py +++ b/modules/ui_extra_networks.py @@ -1,9 +1,7 @@ import glob import os.path import urllib.parse -from functools import lru_cache from pathlib import Path -from typing import Optional from modules import shared import gradio as gr @@ -140,17 +138,17 @@ class ExtraNetworksPage: return self.card_page.format(**args) - def _find_preview(self, path: str) -> Optional[str]: + def find_preview(self, path): """ Find a preview PNG for a given path (without extension) and call link_preview on it. """ for file in [path + ".png", path + ".preview.png"]: if os.path.isfile(file): return self.link_preview(file) + return None - @lru_cache(maxsize=512) - def _find_description(self, path: str) -> Optional[str]: + def find_description(self, path): """ Find and read a description file for a given path (without extension). """ diff --git a/modules/ui_extra_networks_checkpoints.py b/modules/ui_extra_networks_checkpoints.py index 1deb785a..7d1aa203 100644 --- a/modules/ui_extra_networks_checkpoints.py +++ b/modules/ui_extra_networks_checkpoints.py @@ -19,8 +19,8 @@ class ExtraNetworksPageCheckpoints(ui_extra_networks.ExtraNetworksPage): yield { "name": checkpoint.name_for_extra, "filename": path, - "preview": self._find_preview(path), - "description": self._find_description(path), + "preview": self.find_preview(path), + "description": self.find_description(path), "search_term": self.search_terms_from_path(checkpoint.filename) + " " + (checkpoint.sha256 or ""), "onclick": '"' + html.escape(f"""return selectCheckpoint({json.dumps(name)})""") + '"', "local_preview": path + ".png", diff --git a/modules/ui_extra_networks_hypernets.py b/modules/ui_extra_networks_hypernets.py index 80cc2a24..8e49e93b 100644 --- a/modules/ui_extra_networks_hypernets.py +++ b/modules/ui_extra_networks_hypernets.py @@ -18,8 +18,8 @@ class ExtraNetworksPageHypernetworks(ui_extra_networks.ExtraNetworksPage): yield { "name": name, "filename": path, - "preview": self._find_preview(path), - "description": self._find_description(path), + "preview": self.find_preview(path), + "description": self.find_description(path), "search_term": self.search_terms_from_path(path), "prompt": json.dumps(f""), "local_preview": path + ".png", diff --git a/modules/ui_extra_networks_textual_inversion.py b/modules/ui_extra_networks_textual_inversion.py index f3bae666..1ad806eb 100644 --- a/modules/ui_extra_networks_textual_inversion.py +++ b/modules/ui_extra_networks_textual_inversion.py @@ -18,8 +18,8 @@ class ExtraNetworksPageTextualInversion(ui_extra_networks.ExtraNetworksPage): yield { "name": embedding.name, "filename": embedding.filename, - "preview": self._find_preview(path), - "description": self._find_description(path), + "preview": self.find_preview(path), + "description": self.find_description(path), "search_term": self.search_terms_from_path(embedding.filename), "prompt": json.dumps(embedding.name), "local_preview": path + ".preview.png", From 9320139bd8340e8b12c178fe80411c8e25f78d9e Mon Sep 17 00:00:00 2001 From: AUTOMATIC <16777216c@gmail.com> Date: Sat, 11 Mar 2023 15:33:24 +0300 Subject: [PATCH 129/278] support three extensions for preview instead of one: png, jpg, webp --- modules/ui_extra_networks.py | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/modules/ui_extra_networks.py b/modules/ui_extra_networks.py index 21c52287..3b476f3a 100644 --- a/modules/ui_extra_networks.py +++ b/modules/ui_extra_networks.py @@ -142,7 +142,11 @@ class ExtraNetworksPage: """ Find a preview PNG for a given path (without extension) and call link_preview on it. """ - for file in [path + ".png", path + ".preview.png"]: + + preview_extensions = ["png", "jpg", "webp"] + potential_files = sum([[path + "." + ext, path + ".preview." + ext] for ext in preview_extensions], []) + + for file in potential_files: if os.path.isfile(file): return self.link_preview(file) From 6da2027213c3bf132c54489d34c48ec084f8dc11 Mon Sep 17 00:00:00 2001 From: AUTOMATIC <16777216c@gmail.com> Date: Sat, 11 Mar 2023 15:46:20 +0300 Subject: [PATCH 130/278] save previews for extra networks in the selected format --- extensions-builtin/Lora/ui_extra_networks_lora.py | 2 +- modules/ui_extra_networks.py | 3 +++ modules/ui_extra_networks_checkpoints.py | 2 +- modules/ui_extra_networks_hypernets.py | 2 +- modules/ui_extra_networks_textual_inversion.py | 4 ++-- 5 files changed, 8 insertions(+), 5 deletions(-) diff --git a/extensions-builtin/Lora/ui_extra_networks_lora.py b/extensions-builtin/Lora/ui_extra_networks_lora.py index 6815f6ef..8d32052e 100644 --- a/extensions-builtin/Lora/ui_extra_networks_lora.py +++ b/extensions-builtin/Lora/ui_extra_networks_lora.py @@ -22,7 +22,7 @@ class ExtraNetworksPageLora(ui_extra_networks.ExtraNetworksPage): "description": self.find_description(path), "search_term": self.search_terms_from_path(lora_on_disk.filename), "prompt": json.dumps(f""), - "local_preview": path + ".png", + "local_preview": f"{path}.{shared.opts.samples_format}", } def allowed_directories_for_previews(self): diff --git a/modules/ui_extra_networks.py b/modules/ui_extra_networks.py index 3b476f3a..85f0af4c 100644 --- a/modules/ui_extra_networks.py +++ b/modules/ui_extra_networks.py @@ -144,6 +144,9 @@ class ExtraNetworksPage: """ preview_extensions = ["png", "jpg", "webp"] + if shared.opts.samples_format not in preview_extensions: + preview_extensions.append(shared.opts.samples_format) + potential_files = sum([[path + "." + ext, path + ".preview." + ext] for ext in preview_extensions], []) for file in potential_files: diff --git a/modules/ui_extra_networks_checkpoints.py b/modules/ui_extra_networks_checkpoints.py index 7d1aa203..a17aa9c9 100644 --- a/modules/ui_extra_networks_checkpoints.py +++ b/modules/ui_extra_networks_checkpoints.py @@ -23,7 +23,7 @@ class ExtraNetworksPageCheckpoints(ui_extra_networks.ExtraNetworksPage): "description": self.find_description(path), "search_term": self.search_terms_from_path(checkpoint.filename) + " " + (checkpoint.sha256 or ""), "onclick": '"' + html.escape(f"""return selectCheckpoint({json.dumps(name)})""") + '"', - "local_preview": path + ".png", + "local_preview": f"{path}.{shared.opts.samples_format}", } def allowed_directories_for_previews(self): diff --git a/modules/ui_extra_networks_hypernets.py b/modules/ui_extra_networks_hypernets.py index 8e49e93b..6187e000 100644 --- a/modules/ui_extra_networks_hypernets.py +++ b/modules/ui_extra_networks_hypernets.py @@ -22,7 +22,7 @@ class ExtraNetworksPageHypernetworks(ui_extra_networks.ExtraNetworksPage): "description": self.find_description(path), "search_term": self.search_terms_from_path(path), "prompt": json.dumps(f""), - "local_preview": path + ".png", + "local_preview": f"{path}.preview.{shared.opts.samples_format}", } def allowed_directories_for_previews(self): diff --git a/modules/ui_extra_networks_textual_inversion.py b/modules/ui_extra_networks_textual_inversion.py index 1ad806eb..6944d559 100644 --- a/modules/ui_extra_networks_textual_inversion.py +++ b/modules/ui_extra_networks_textual_inversion.py @@ -1,7 +1,7 @@ import json import os -from modules import ui_extra_networks, sd_hijack +from modules import ui_extra_networks, sd_hijack, shared class ExtraNetworksPageTextualInversion(ui_extra_networks.ExtraNetworksPage): @@ -22,7 +22,7 @@ class ExtraNetworksPageTextualInversion(ui_extra_networks.ExtraNetworksPage): "description": self.find_description(path), "search_term": self.search_terms_from_path(embedding.filename), "prompt": json.dumps(embedding.name), - "local_preview": path + ".preview.png", + "local_preview": f"{path}.preview.{shared.opts.samples_format}", } def allowed_directories_for_previews(self): From 52dcf0f0c70f1edc4a04ef7bc905528fbc6cdbec Mon Sep 17 00:00:00 2001 From: AUTOMATIC <16777216c@gmail.com> Date: Sat, 11 Mar 2023 16:27:58 +0300 Subject: [PATCH 131/278] record startup time --- webui.py | 47 +++++++++++++++++++++++++++++++++++++++++++---- 1 file changed, 43 insertions(+), 4 deletions(-) diff --git a/webui.py b/webui.py index be39fa8d..32561877 100644 --- a/webui.py +++ b/webui.py @@ -12,11 +12,22 @@ from packaging import version import logging logging.getLogger("xformers").addFilter(lambda record: 'A matching Triton is not available' not in record.getMessage()) -from modules import import_hook, errors, extra_networks, ui_extra_networks_checkpoints -from modules import extra_networks_hypernet, ui_extra_networks_hypernets, ui_extra_networks_textual_inversion -from modules.call_queue import wrap_queued_call, queue_lock, wrap_gradio_gpu_call +from modules import paths, timer, import_hook, errors + +startup_timer = timer.Timer() import torch +startup_timer.record("import torch") + +import gradio +startup_timer.record("import gradio") + +import ldm.modules.encoders.modules +startup_timer.record("import ldm") + +from modules import extra_networks, ui_extra_networks_checkpoints +from modules import extra_networks_hypernet, ui_extra_networks_hypernets, ui_extra_networks_textual_inversion +from modules.call_queue import wrap_queued_call, queue_lock, wrap_gradio_gpu_call # Truncate version number of nightly/local build of PyTorch to not cause exceptions with CodeFormer or Safetensors if ".dev" in torch.__version__ or "+git" in torch.__version__: @@ -30,7 +41,6 @@ import modules.gfpgan_model as gfpgan import modules.img2img import modules.lowvram -import modules.paths import modules.scripts import modules.sd_hijack import modules.sd_models @@ -45,6 +55,8 @@ from modules import modelloader from modules.shared import cmd_opts import modules.hypernetworks.hypernetwork +startup_timer.record("other imports") + if cmd_opts.server_name: server_name = cmd_opts.server_name @@ -88,6 +100,7 @@ def initialize(): extensions.list_extensions() localization.list_localizations(cmd_opts.localizations_dir) + startup_timer.record("list extensions") if cmd_opts.ui_debug_mode: shared.sd_upscalers = upscaler.UpscalerLanczos().scalers @@ -96,16 +109,28 @@ def initialize(): modelloader.cleanup_models() modules.sd_models.setup_model() + startup_timer.record("list SD models") + codeformer.setup_model(cmd_opts.codeformer_models_path) + startup_timer.record("setup codeformer") + gfpgan.setup_model(cmd_opts.gfpgan_models_path) + startup_timer.record("setup gfpgan") modelloader.list_builtin_upscalers() + startup_timer.record("list builtin upscalers") + modules.scripts.load_scripts() + startup_timer.record("load scripts") + modelloader.load_upscalers() + startup_timer.record("load upscalers") modules.sd_vae.refresh_vae_list() + startup_timer.record("refresh VAE") modules.textual_inversion.textual_inversion.list_textual_inversion_templates() + startup_timer.record("refresh textual inversion templates") try: modules.sd_models.load_model() @@ -114,6 +139,7 @@ def initialize(): print("", file=sys.stderr) print("Stable diffusion model failed to load, exiting", file=sys.stderr) exit(1) + startup_timer.record("load SD checkpoint") shared.opts.data["sd_model_checkpoint"] = shared.sd_model.sd_checkpoint_info.title @@ -121,8 +147,10 @@ def initialize(): shared.opts.onchange("sd_vae", wrap_queued_call(lambda: modules.sd_vae.reload_vae_weights()), call=False) shared.opts.onchange("sd_vae_as_default", wrap_queued_call(lambda: modules.sd_vae.reload_vae_weights()), call=False) shared.opts.onchange("temp_dir", ui_tempdir.on_tmpdir_changed) + startup_timer.record("opts onchange") shared.reload_hypernetworks() + startup_timer.record("reload hypernets") ui_extra_networks.intialize() ui_extra_networks.register_page(ui_extra_networks_textual_inversion.ExtraNetworksPageTextualInversion()) @@ -131,6 +159,7 @@ def initialize(): extra_networks.initialize() extra_networks.register_extra_network(extra_networks_hypernet.ExtraNetworkHypernet()) + startup_timer.record("extra networks") if cmd_opts.tls_keyfile is not None and cmd_opts.tls_keyfile is not None: @@ -144,6 +173,7 @@ def initialize(): print("TLS setup invalid, running webui without TLS") else: print("Running with TLS") + startup_timer.record("TLS") # make the program just exit at ctrl+c without waiting for anything def sigint_handler(sig, frame): @@ -189,6 +219,7 @@ def api_only(): modules.script_callbacks.app_started_callback(None, app) + print(f"Startup time: {startup_timer.summary()}.") api.launch(server_name="0.0.0.0" if cmd_opts.listen else "127.0.0.1", port=cmd_opts.port if cmd_opts.port else 7861) @@ -199,10 +230,13 @@ def webui(): while 1: if shared.opts.clean_temp_dir_at_start: ui_tempdir.cleanup_tmpdr() + startup_timer.record("cleanup temp dir") modules.script_callbacks.before_ui_callback() + startup_timer.record("scripts before_ui_callback") shared.demo = modules.ui.create_ui() + startup_timer.record("create ui") if cmd_opts.gradio_queue: shared.demo.queue(64) @@ -229,6 +263,8 @@ def webui(): # after initial launch, disable --autolaunch for subsequent restarts cmd_opts.autolaunch = False + startup_timer.record("gradio launch") + # gradio uses a very open CORS policy via app.user_middleware, which makes it possible for # an attacker to trick the user into opening a malicious HTML page, which makes a request to the # running web ui and do whatever the attacker wants, including installing an extension and @@ -247,6 +283,9 @@ def webui(): ui_extra_networks.add_pages_to_demo(app) modules.script_callbacks.app_started_callback(shared.demo, app) + startup_timer.record("scripts app_started_callback") + + print(f"Startup time: {startup_timer.summary()}.") wait_on_server(shared.demo) print('Restarting UI...') From a47c18297e1611568c732e6e6922d5be9def7c47 Mon Sep 17 00:00:00 2001 From: Vladimir Mandic Date: Sat, 11 Mar 2023 08:33:55 -0500 Subject: [PATCH 132/278] use assert instead of return --- scripts/xyz_grid.py | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/scripts/xyz_grid.py b/scripts/xyz_grid.py index 8c816a73..44f7374c 100644 --- a/scripts/xyz_grid.py +++ b/scripts/xyz_grid.py @@ -485,10 +485,9 @@ class Script(scripts.Script): zs = process_axis(z_opt, z_values) # this could be moved to common code, but unlikely to be ever triggered anywhere else - Image.MAX_IMAGE_PIXELS = opts.img_max_size_mp * 1000000 * 1.1 # allow 10% overhead for margins and legend + Image.MAX_IMAGE_PIXELS = opts.img_max_size_mp * 1.1 # allow 10% overhead for margins and legend grid_mp = round(len(xs) * len(ys) * len(zs) * p.width * p.height / 1000000) - if grid_mp > opts.img_max_size_mp: - return Processed(p, [], p.seed, info=f'Error: Resulting grid would be too large ({grid_mp} MPixels) (max configured size is {opts.img_max_size_mp} MPixels)') + assert grid_mp < opts.img_max_size_mp, f'Error: Resulting grid would be too large ({grid_mp} MPixels) (max configured size is {opts.img_max_size_mp} MPixels)' def fix_axis_seeds(axis_opt, axis_list): if axis_opt.label in ['Seed', 'Var. seed']: From 1e1a32b130ea8088aeda976cd044544c33a659c3 Mon Sep 17 00:00:00 2001 From: Adam Huganir Date: Sat, 11 Mar 2023 09:34:17 -0500 Subject: [PATCH 133/278] Update requirements_versions.txt revert back to .27 --- requirements_versions.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/requirements_versions.txt b/requirements_versions.txt index 41e0ccc5..331d0fe8 100644 --- a/requirements_versions.txt +++ b/requirements_versions.txt @@ -23,7 +23,7 @@ torchdiffeq==0.2.3 kornia==0.6.7 lark==1.1.2 inflection==0.5.1 -GitPython==3.1.30 +GitPython==3.1.27 torchsde==0.2.5 safetensors==0.2.7 httpcore<=0.15 From 5cea278d3ad3a1c1a9b454387241a29bec11699b Mon Sep 17 00:00:00 2001 From: AUTOMATIC <16777216c@gmail.com> Date: Sat, 11 Mar 2023 17:51:55 +0300 Subject: [PATCH 134/278] bump GitPython to 3.1.30 because some people would be upset about it being below that version #8118 --- requirements_versions.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/requirements_versions.txt b/requirements_versions.txt index 331d0fe8..41e0ccc5 100644 --- a/requirements_versions.txt +++ b/requirements_versions.txt @@ -23,7 +23,7 @@ torchdiffeq==0.2.3 kornia==0.6.7 lark==1.1.2 inflection==0.5.1 -GitPython==3.1.27 +GitPython==3.1.30 torchsde==0.2.5 safetensors==0.2.7 httpcore<=0.15 From 7fd19fa4e7039746dc98990883acfa500b90b6c7 Mon Sep 17 00:00:00 2001 From: "Alex \"mcmonkey\" Goodwin" Date: Sat, 11 Mar 2023 07:22:22 -0800 Subject: [PATCH 135/278] initial fix for filename length limits on *nix systems --- modules/images.py | 3 +++ 1 file changed, 3 insertions(+) diff --git a/modules/images.py b/modules/images.py index 7df2b08c..4c204fca 100644 --- a/modules/images.py +++ b/modules/images.py @@ -512,6 +512,9 @@ def save_image(image, path, basename, seed=None, prompt=None, extension='png', i file_decoration = "-" + file_decoration file_decoration = namegen.apply(file_decoration) + suffix + if hasattr(os, 'statvfs'): + max_name_len = os.statvfs(path).f_namemax + file_decoration = file_decoration[:max_name_len - 5] if add_number: basecount = get_next_sequence_number(path, basename) From 94ffa9fc5386e51f20692ab46906135e8de33110 Mon Sep 17 00:00:00 2001 From: AUTOMATIC <16777216c@gmail.com> Date: Sat, 11 Mar 2023 18:55:48 +0300 Subject: [PATCH 136/278] emergency fix for xyz plot --- scripts/xyz_grid.py | 1 - 1 file changed, 1 deletion(-) diff --git a/scripts/xyz_grid.py b/scripts/xyz_grid.py index 84c73e28..9a0678fa 100644 --- a/scripts/xyz_grid.py +++ b/scripts/xyz_grid.py @@ -500,7 +500,6 @@ class Script(scripts.Script): zs = process_axis(z_opt, z_values) # this could be moved to common code, but unlikely to be ever triggered anywhere else - Image.MAX_IMAGE_PIXELS = opts.img_max_size_mp * 1.1 # allow 10% overhead for margins and legend grid_mp = round(len(xs) * len(ys) * len(zs) * p.width * p.height / 1000000) assert grid_mp < opts.img_max_size_mp, f'Error: Resulting grid would be too large ({grid_mp} MPixels) (max configured size is {opts.img_max_size_mp} MPixels)' From fb088bfb6409efa1562f811539998920ec0c0621 Mon Sep 17 00:00:00 2001 From: Vladimir Mandic Date: Sat, 11 Mar 2023 11:13:21 -0500 Subject: [PATCH 137/278] all usage of newer pytorch_lighning --- requirements_versions.txt | 2 +- webui.py | 2 ++ 2 files changed, 3 insertions(+), 1 deletion(-) diff --git a/requirements_versions.txt b/requirements_versions.txt index 41e0ccc5..b98081bf 100644 --- a/requirements_versions.txt +++ b/requirements_versions.txt @@ -9,7 +9,7 @@ Pillow==9.4.0 realesrgan==0.3.0 torch omegaconf==2.2.3 -pytorch_lightning==1.7.6 +pytorch_lightning==1.9.4 scikit-image==0.19.2 fonts font-roboto diff --git a/webui.py b/webui.py index 32561877..9baa7da5 100644 --- a/webui.py +++ b/webui.py @@ -17,6 +17,8 @@ from modules import paths, timer, import_hook, errors startup_timer = timer.Timer() import torch +import pytorch_lightning # pytorch_lightning should be imported after torch, but it re-enables warnings on import so import once to disable them +warnings.filterwarnings(action="ignore", category=DeprecationWarning) startup_timer.record("import torch") import gradio From 29ce0bf4f2e708cbd58ee4b9c89d6f27c2f36baa Mon Sep 17 00:00:00 2001 From: Vladimir Mandic Date: Sat, 11 Mar 2023 12:01:08 -0500 Subject: [PATCH 138/278] allow usage of latest fastapi --- requirements_versions.txt | 2 +- webui.py | 12 ++++++------ 2 files changed, 7 insertions(+), 7 deletions(-) diff --git a/requirements_versions.txt b/requirements_versions.txt index 41e0ccc5..0031c616 100644 --- a/requirements_versions.txt +++ b/requirements_versions.txt @@ -27,4 +27,4 @@ GitPython==3.1.30 torchsde==0.2.5 safetensors==0.2.7 httpcore<=0.15 -fastapi==0.90.1 +fastapi==0.94.0 diff --git a/webui.py b/webui.py index 32561877..1a4175af 100644 --- a/webui.py +++ b/webui.py @@ -183,13 +183,16 @@ def initialize(): signal.signal(signal.SIGINT, sigint_handler) -def setup_cors(app): +def setup_middleware(app): + app.middleware_stack = None # reset current middleware to allow modifying user provided list + app.add_middleware(GZipMiddleware, minimum_size=1000) if cmd_opts.cors_allow_origins and cmd_opts.cors_allow_origins_regex: app.add_middleware(CORSMiddleware, allow_origins=cmd_opts.cors_allow_origins.split(','), allow_origin_regex=cmd_opts.cors_allow_origins_regex, allow_methods=['*'], allow_credentials=True, allow_headers=['*']) elif cmd_opts.cors_allow_origins: app.add_middleware(CORSMiddleware, allow_origins=cmd_opts.cors_allow_origins.split(','), allow_methods=['*'], allow_credentials=True, allow_headers=['*']) elif cmd_opts.cors_allow_origins_regex: app.add_middleware(CORSMiddleware, allow_origin_regex=cmd_opts.cors_allow_origins_regex, allow_methods=['*'], allow_credentials=True, allow_headers=['*']) + app.build_middleware_stack() # rebuild middleware stack on-the-fly def create_api(app): @@ -213,8 +216,7 @@ def api_only(): initialize() app = FastAPI() - setup_cors(app) - app.add_middleware(GZipMiddleware, minimum_size=1000) + setup_middleware(app) api = create_api(app) modules.script_callbacks.app_started_callback(None, app) @@ -271,9 +273,7 @@ def webui(): # running its code. We disable this here. Suggested by RyotaK. app.user_middleware = [x for x in app.user_middleware if x.cls.__name__ != 'CORSMiddleware'] - setup_cors(app) - - app.add_middleware(GZipMiddleware, minimum_size=1000) + setup_middleware(app) modules.progress.setup_progress_api(app) From 2174f58daee1e077eec1125e196d34cc93dbaf23 Mon Sep 17 00:00:00 2001 From: Vespinian Date: Sat, 11 Mar 2023 12:21:33 -0500 Subject: [PATCH 139/278] Changed alwayson_script_name and alwayson_script_args api params to 1 alwayson_scripts param dict --- modules/api/api.py | 23 +++++++---------------- modules/api/models.py | 4 ++-- 2 files changed, 9 insertions(+), 18 deletions(-) diff --git a/modules/api/api.py b/modules/api/api.py index 248922d2..8a17017b 100644 --- a/modules/api/api.py +++ b/modules/api/api.py @@ -195,22 +195,17 @@ class Api: script_args[0] = 0 # Now check for always on scripts - if request.alwayson_script_name and (len(request.alwayson_script_name) > 0): - # always on script with no arg should always run, but if you include their name in the api request, send an empty list for there args - if not request.alwayson_script_args: - raise HTTPException(status_code=422, detail=f"Script {request.alwayson_script_name} has no arg list") - if len(request.alwayson_script_name) != len(request.alwayson_script_args): - raise HTTPException(status_code=422, detail=f"Number of script names and number of script arg lists doesn't match") - - for alwayson_script_name, alwayson_script_args in zip(request.alwayson_script_name, request.alwayson_script_args): + if request.alwayson_scripts and (len(request.alwayson_scripts) > 0): + for alwayson_script_name in request.alwayson_scripts.keys(): alwayson_script = self.get_script(alwayson_script_name, script_runner) if alwayson_script == None: raise HTTPException(status_code=422, detail=f"always on script {alwayson_script_name} not found") # Selectable script in always on script param check if alwayson_script.alwayson == False: raise HTTPException(status_code=422, detail=f"Cannot have a selectable script in the always on scripts params") - if alwayson_script_args != []: - script_args[alwayson_script.args_from:alwayson_script.args_to] = alwayson_script_args + # always on script with no arg should always run so you don't really need to add them to the requests + if "args" in request.alwayson_scripts[alwayson_script_name]: + script_args[alwayson_script.args_from:alwayson_script.args_to] = request.alwayson_scripts[alwayson_script_name]["args"] return script_args def text2imgapi(self, txt2imgreq: StableDiffusionTxt2ImgProcessingAPI): @@ -226,15 +221,13 @@ class Api: "do_not_save_grid": True } ) - if populate.sampler_name: populate.sampler_index = None # prevent a warning later on args = vars(populate) args.pop('script_name', None) args.pop('script_args', None) # will refeed them to the pipeline directly after initializing them - args.pop('alwayson_script_name', None) - args.pop('alwayson_script_args', None) + args.pop('alwayson_scripts', None) script_args = self.init_script_args(txt2imgreq, selectable_scripts, selectable_script_idx, script_runner) @@ -279,7 +272,6 @@ class Api: "mask": mask } ) - if populate.sampler_name: populate.sampler_index = None # prevent a warning later on @@ -287,8 +279,7 @@ class Api: args.pop('include_init_images', None) # this is meant to be done by "exclude": True in model, but it's for a reason that I cannot determine. args.pop('script_name', None) args.pop('script_args', None) # will refeed them to the pipeline directly after initializing them - args.pop('alwayson_script_name', None) - args.pop('alwayson_script_args', None) + args.pop('alwayson_scripts', None) script_args = self.init_script_args(img2imgreq, selectable_scripts, selectable_script_idx, script_runner) diff --git a/modules/api/models.py b/modules/api/models.py index 86c70178..e273469d 100644 --- a/modules/api/models.py +++ b/modules/api/models.py @@ -100,13 +100,13 @@ class PydanticModelGenerator: StableDiffusionTxt2ImgProcessingAPI = PydanticModelGenerator( "StableDiffusionProcessingTxt2Img", StableDiffusionProcessingTxt2Img, - [{"key": "sampler_index", "type": str, "default": "Euler"}, {"key": "script_name", "type": str, "default": None}, {"key": "script_args", "type": list, "default": []}, {"key": "alwayson_script_name", "type": list, "default": []}, {"key": "alwayson_script_args", "type": list, "default": []}] + [{"key": "sampler_index", "type": str, "default": "Euler"}, {"key": "script_name", "type": str, "default": None}, {"key": "script_args", "type": list, "default": []}, {"key": "alwayson_scripts", "type": dict, "default": {}}] ).generate_model() StableDiffusionImg2ImgProcessingAPI = PydanticModelGenerator( "StableDiffusionProcessingImg2Img", StableDiffusionProcessingImg2Img, - [{"key": "sampler_index", "type": str, "default": "Euler"}, {"key": "init_images", "type": list, "default": None}, {"key": "denoising_strength", "type": float, "default": 0.75}, {"key": "mask", "type": str, "default": None}, {"key": "include_init_images", "type": bool, "default": False, "exclude" : True}, {"key": "script_name", "type": str, "default": None}, {"key": "script_args", "type": list, "default": []}, {"key": "alwayson_script_name", "type": list, "default": []}, {"key": "alwayson_script_args", "type": list, "default": []}] + [{"key": "sampler_index", "type": str, "default": "Euler"}, {"key": "init_images", "type": list, "default": None}, {"key": "denoising_strength", "type": float, "default": 0.75}, {"key": "mask", "type": str, "default": None}, {"key": "include_init_images", "type": bool, "default": False, "exclude" : True}, {"key": "script_name", "type": str, "default": None}, {"key": "script_args", "type": list, "default": []}, {"key": "alwayson_scripts", "type": dict, "default": {}}] ).generate_model() class TextToImageResponse(BaseModel): From 5546e71a105033989adacc2df9dfb53f81a0534c Mon Sep 17 00:00:00 2001 From: Vespinian Date: Sat, 11 Mar 2023 12:35:20 -0500 Subject: [PATCH 140/278] Fixed whitespace --- modules/api/models.py | 1 - 1 file changed, 1 deletion(-) diff --git a/modules/api/models.py b/modules/api/models.py index 6c1c5546..4a70f440 100644 --- a/modules/api/models.py +++ b/modules/api/models.py @@ -113,7 +113,6 @@ StableDiffusionTxt2ImgProcessingAPI = PydanticModelGenerator( StableDiffusionImg2ImgProcessingAPI = PydanticModelGenerator( "StableDiffusionProcessingImg2Img", StableDiffusionProcessingImg2Img, - [ {"key": "sampler_index", "type": str, "default": "Euler"}, {"key": "init_images", "type": list, "default": None}, From 27e319dc4f09a2f040043948e5c52965976f8491 Mon Sep 17 00:00:00 2001 From: AUTOMATIC <16777216c@gmail.com> Date: Sat, 11 Mar 2023 21:22:52 +0300 Subject: [PATCH 141/278] alternative solution for #8089 --- modules/shared.py | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/modules/shared.py b/modules/shared.py index 4e229353..2fb9e3b5 100644 --- a/modules/shared.py +++ b/modules/shared.py @@ -116,7 +116,10 @@ parser.add_argument("--no-download-sd-model", action='store_true', help="don't d script_loading.preload_extensions(extensions.extensions_dir, parser) script_loading.preload_extensions(extensions.extensions_builtin_dir, parser) -cmd_opts = parser.parse_args() +if os.environ.get('IGNORE_CMD_ARGS_ERRORS', None) is None: + cmd_opts = parser.parse_args() +else: + cmd_opts, _ = parser.parse_known_args() restricted_opts = { "samples_filename_pattern", From 247a34498b337798a371d69483bbcab49b5c320c Mon Sep 17 00:00:00 2001 From: Kilvoctu Date: Sat, 11 Mar 2023 13:11:26 -0600 Subject: [PATCH 142/278] restore text, remove 'close' don't use emojis for extra network buttons; remove 'close' --- javascript/extraNetworks.js | 2 -- modules/ui_extra_networks.py | 8 +------- 2 files changed, 1 insertion(+), 9 deletions(-) diff --git a/javascript/extraNetworks.js b/javascript/extraNetworks.js index 17bf2000..8f7cee03 100644 --- a/javascript/extraNetworks.js +++ b/javascript/extraNetworks.js @@ -5,12 +5,10 @@ function setupExtraNetworksForTab(tabname){ var tabs = gradioApp().querySelector('#'+tabname+'_extra_tabs > div') var search = gradioApp().querySelector('#'+tabname+'_extra_search textarea') var refresh = gradioApp().getElementById(tabname+'_extra_refresh') - var close = gradioApp().getElementById(tabname+'_extra_close') search.classList.add('search') tabs.appendChild(search) tabs.appendChild(refresh) - tabs.appendChild(close) search.addEventListener("input", function(evt){ searchTerm = search.value.toLowerCase() diff --git a/modules/ui_extra_networks.py b/modules/ui_extra_networks.py index 8786fde6..3f56bf63 100644 --- a/modules/ui_extra_networks.py +++ b/modules/ui_extra_networks.py @@ -13,10 +13,6 @@ from modules.generation_parameters_copypaste import image_from_url_text extra_pages = [] allowed_dirs = set() -# Using constants for these since the variation selector isn't visible. -# Important that they exactly match script.js for tooltip to work. -refresh_symbol = '\U0001f504' # 🔄 -close_symbol = '\U0000274C' # ❌ def register_page(page): """registers extra networks page for the UI; recommend doing it in on_before_ui() callback for extensions""" @@ -186,8 +182,7 @@ def create_ui(container, button, tabname): ui.pages.append(page_elem) filter = gr.Textbox('', show_label=False, elem_id=tabname+"_extra_search", placeholder="Search...", visible=False) - button_refresh = gr.Button(refresh_symbol, elem_id=tabname+"_extra_refresh") - button_close = gr.Button(close_symbol, elem_id=tabname+"_extra_close") + button_refresh = gr.Button('Refresh', elem_id=tabname+"_extra_refresh") ui.button_save_preview = gr.Button('Save preview', elem_id=tabname+"_save_preview", visible=False) ui.preview_target_filename = gr.Textbox('Preview save filename', elem_id=tabname+"_preview_filename", visible=False) @@ -198,7 +193,6 @@ def create_ui(container, button, tabname): state_visible = gr.State(value=False) button.click(fn=toggle_visibility, inputs=[state_visible], outputs=[state_visible, container]) - button_close.click(fn=toggle_visibility, inputs=[state_visible], outputs=[state_visible, container]) def refresh(): res = [] From 49bbdbe4476a7325e184f3022166c3dc02d086ba Mon Sep 17 00:00:00 2001 From: Vespinian Date: Sat, 11 Mar 2023 14:34:56 -0500 Subject: [PATCH 143/278] small diff whitespace cleanup --- 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 abdbb6a7..35e17afc 100644 --- a/modules/api/api.py +++ b/modules/api/api.py @@ -274,7 +274,7 @@ class Api: ui.create_ui() selectable_scripts, selectable_script_idx = self.get_selectable_script(img2imgreq.script_name, script_runner) - populate = img2imgreq.copy(update={ # Override __init__ params + populate = img2imgreq.copy(update={ # Override __init__ params "sampler_name": validate_sampler_name(img2imgreq.sampler_name or img2imgreq.sampler_index), "do_not_save_samples": not img2imgreq.save_images, "do_not_save_grid": not img2imgreq.save_images, From 48f4abd2e61e545104f72eb50c9ab9b100726948 Mon Sep 17 00:00:00 2001 From: EllangoK Date: Sat, 11 Mar 2023 15:52:14 -0500 Subject: [PATCH 144/278] fix dims typo in unipc --- modules/models/diffusion/uni_pc/uni_pc.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/modules/models/diffusion/uni_pc/uni_pc.py b/modules/models/diffusion/uni_pc/uni_pc.py index df63d1bc..e9a093a2 100644 --- a/modules/models/diffusion/uni_pc/uni_pc.py +++ b/modules/models/diffusion/uni_pc/uni_pc.py @@ -719,7 +719,7 @@ class UniPC: x_t = x_t_ - expand_dims(alpha_t * B_h, dims) * (corr_res + rhos_c[-1] * D1_t) else: x_t_ = ( - expand_dims(torch.exp(log_alpha_t - log_alpha_prev_0), dimss) * x + expand_dims(torch.exp(log_alpha_t - log_alpha_prev_0), dims) * x - expand_dims(sigma_t * h_phi_1, dims) * model_prev_0 ) if x_t is None: From a4cb96d4ae82741be9f0d072a37af3ae39521379 Mon Sep 17 00:00:00 2001 From: brkirch Date: Sat, 11 Mar 2023 17:35:17 -0500 Subject: [PATCH 145/278] Remove test, use bool tensor fix by default The test isn't working correctly on macOS 13.3 and the bool tensor fix for cumsum is currently always needed anyway, so enable the fix by default. --- modules/mac_specific.py | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/modules/mac_specific.py b/modules/mac_specific.py index ddcea53b..18e6ff72 100644 --- a/modules/mac_specific.py +++ b/modules/mac_specific.py @@ -23,7 +23,7 @@ def cumsum_fix(input, cumsum_func, *args, **kwargs): output_dtype = kwargs.get('dtype', input.dtype) if output_dtype == torch.int64: return cumsum_func(input.cpu(), *args, **kwargs).to(input.device) - elif cumsum_needs_bool_fix and output_dtype == torch.bool or cumsum_needs_int_fix and (output_dtype == torch.int8 or output_dtype == torch.int16): + elif output_dtype == torch.bool or cumsum_needs_int_fix and (output_dtype == torch.int8 or output_dtype == torch.int16): return cumsum_func(input.to(torch.int32), *args, **kwargs).to(torch.int64) return cumsum_func(input, *args, **kwargs) @@ -45,7 +45,6 @@ if has_mps: CondFunc('torch.Tensor.numpy', lambda orig_func, self, *args, **kwargs: orig_func(self.detach(), *args, **kwargs), lambda _, self, *args, **kwargs: self.requires_grad) elif version.parse(torch.__version__) > version.parse("1.13.1"): cumsum_needs_int_fix = not torch.Tensor([1,2]).to(torch.device("mps")).equal(torch.ShortTensor([1,1]).to(torch.device("mps")).cumsum(0)) - cumsum_needs_bool_fix = not torch.BoolTensor([True,True]).to(device=torch.device("mps"), dtype=torch.int64).equal(torch.BoolTensor([True,False]).to(torch.device("mps")).cumsum(0)) cumsum_fix_func = lambda orig_func, input, *args, **kwargs: cumsum_fix(input, orig_func, *args, **kwargs) CondFunc('torch.cumsum', cumsum_fix_func, None) CondFunc('torch.Tensor.cumsum', cumsum_fix_func, None) From 5ed5e95fb8a0a4a3292eff22dd1b25e960b066a9 Mon Sep 17 00:00:00 2001 From: high_byte Date: Sun, 12 Mar 2023 03:29:07 +0200 Subject: [PATCH 146/278] add face restoration option to xyz_grid --- scripts/xyz_grid.py | 15 +++++++++++++++ 1 file changed, 15 insertions(+) diff --git a/scripts/xyz_grid.py b/scripts/xyz_grid.py index 9a0678fa..ce584981 100644 --- a/scripts/xyz_grid.py +++ b/scripts/xyz_grid.py @@ -132,6 +132,20 @@ def apply_uni_pc_order(p, x, xs): opts.data["uni_pc_order"] = min(x, p.steps - 1) +def apply_face_restore(p, opt, x): + opt = opt.lower() + if opt == 'codeformer': + is_active = True + p.face_restoration_model = 'CodeFormer' + elif opt == 'gfpgan': + is_active = True + p.face_restoration_model = 'GFPGAN' + else: + is_active = opt in ('true', 'yes', 'y', '1') + + p.restore_faces = is_active + + def format_value_add_label(p, opt, x): if type(x) == float: x = round(x, 8) @@ -210,6 +224,7 @@ axis_options = [ AxisOption("VAE", str, apply_vae, cost=0.7, choices=lambda: 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), ] From 04924241218bb51bee255bebc6c66ef1de449f4a Mon Sep 17 00:00:00 2001 From: bluelovers Date: Sun, 12 Mar 2023 10:18:33 +0800 Subject: [PATCH 147/278] feat: try sort as ignore-case https://github.com/AUTOMATIC1111/stable-diffusion-webui/issues/8368 --- extensions-builtin/Lora/lora.py | 2 +- modules/shared.py | 2 +- modules/textual_inversion/textual_inversion.py | 4 ++-- 3 files changed, 4 insertions(+), 4 deletions(-) diff --git a/extensions-builtin/Lora/lora.py b/extensions-builtin/Lora/lora.py index cb8f1d36..7d3c0f90 100644 --- a/extensions-builtin/Lora/lora.py +++ b/extensions-builtin/Lora/lora.py @@ -192,7 +192,7 @@ def list_available_loras(): glob.glob(os.path.join(shared.cmd_opts.lora_dir, '**/*.safetensors'), recursive=True) + \ glob.glob(os.path.join(shared.cmd_opts.lora_dir, '**/*.ckpt'), recursive=True) - for filename in sorted(candidates): + for filename in sorted(candidates, key=str.lower): if os.path.isdir(filename): continue diff --git a/modules/shared.py b/modules/shared.py index 805f9cc1..1322b96d 100644 --- a/modules/shared.py +++ b/modules/shared.py @@ -702,7 +702,7 @@ mem_mon.start() def listfiles(dirname): - filenames = [os.path.join(dirname, x) for x in sorted(os.listdir(dirname)) if not x.startswith(".")] + filenames = [os.path.join(dirname, x) for x in sorted(os.listdir(dirname), key=str.lower) if not x.startswith(".")] return [file for file in filenames if os.path.isfile(file)] diff --git a/modules/textual_inversion/textual_inversion.py b/modules/textual_inversion/textual_inversion.py index c63c7d1d..3d21b9fe 100644 --- a/modules/textual_inversion/textual_inversion.py +++ b/modules/textual_inversion/textual_inversion.py @@ -129,7 +129,7 @@ class EmbeddingDatabase: if first_id not in self.ids_lookup: self.ids_lookup[first_id] = [] - self.ids_lookup[first_id] = sorted(self.ids_lookup[first_id] + [(ids, embedding)], key=lambda x: len(x[0]), reverse=True) + self.ids_lookup[first_id] = sorted(self.ids_lookup[first_id] + [(ids, embedding)], key=lambda x: len(x[0]), reverse=True, cmp=lambda x, y: x.lower() > y.lower()) return embedding @@ -196,7 +196,7 @@ class EmbeddingDatabase: return for root, dirs, fns in os.walk(embdir.path, followlinks=True): - for fn in fns: + for fn in sorted(fns, key=str.lower): try: fullfn = os.path.join(root, fn) From db85421da18cfaaf1ed0361bc2a9ee40b5796344 Mon Sep 17 00:00:00 2001 From: bluelovers Date: Mon, 27 Feb 2023 09:52:55 +0800 Subject: [PATCH 148/278] feat: better lightbox when not enable zoom --- javascript/imageviewer.js | 2 +- style.css | 4 +--- 2 files changed, 2 insertions(+), 4 deletions(-) diff --git a/javascript/imageviewer.js b/javascript/imageviewer.js index aac2ee82..28e748b7 100644 --- a/javascript/imageviewer.js +++ b/javascript/imageviewer.js @@ -11,7 +11,7 @@ function showModal(event) { if (modalImage.style.display === 'none') { lb.style.setProperty('background-image', 'url(' + source.src + ')'); } - lb.style.display = "block"; + lb.style.display = "flex"; lb.focus() const tabTxt2Img = gradioApp().getElementById("tab_txt2img") diff --git a/style.css b/style.css index 05572f66..4695cc29 100644 --- a/style.css +++ b/style.css @@ -436,9 +436,7 @@ input[type="range"]{ #modalImage { display: block; - margin-left: auto; - margin-right: auto; - margin-top: auto; + margin: auto; width: auto; } From 5c9f2bbb7473c7085dc961bbf81d5248a4859e90 Mon Sep 17 00:00:00 2001 From: AUTOMATIC <16777216c@gmail.com> Date: Sun, 12 Mar 2023 08:58:58 +0300 Subject: [PATCH 149/278] do not import modules.paths in launch.py --- launch.py | 17 ++++++++--------- 1 file changed, 8 insertions(+), 9 deletions(-) diff --git a/launch.py b/launch.py index e1908496..4873da60 100644 --- a/launch.py +++ b/launch.py @@ -7,11 +7,14 @@ import shlex import platform import argparse import json -try: - from modules.paths import script_path, data_path -except ModuleNotFoundError: - script_path = os.path.dirname(__file__) - data_path = os.getcwd() + +parser = argparse.ArgumentParser(add_help=False) +parser.add_argument("--ui-settings-file", type=str, default='config.json') +parser.add_argument("--data-dir", type=str, default=os.path.dirname(os.path.realpath(__file__))) +args, _ = parser.parse_known_args(sys.argv) + +script_path = os.path.dirname(__file__) +data_path = os.getcwd() dir_repos = "repositories" dir_extensions = "extensions" @@ -257,10 +260,6 @@ def prepare_environment(): sys.argv += shlex.split(commandline_args) - parser = argparse.ArgumentParser(add_help=False) - parser.add_argument("--ui-settings-file", type=str, help="filename to use for ui settings", default='config.json') - args, _ = parser.parse_known_args(sys.argv) - sys.argv, _ = extract_arg(sys.argv, '-f') sys.argv, update_all_extensions = extract_arg(sys.argv, '--update-all-extensions') sys.argv, skip_torch_cuda_test = extract_arg(sys.argv, '--skip-torch-cuda-test') From 3c922d983bf60ba187b5422b3690e6b7fb07777e Mon Sep 17 00:00:00 2001 From: AUTOMATIC <16777216c@gmail.com> Date: Sun, 12 Mar 2023 12:11:51 +0300 Subject: [PATCH 150/278] fix #8492 breaking the program when the directory with code contains spaces. --- launch.py | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/launch.py b/launch.py index 4873da60..b943fed2 100644 --- a/launch.py +++ b/launch.py @@ -319,9 +319,11 @@ def prepare_environment(): git_clone(blip_repo, repo_dir('BLIP'), "BLIP", blip_commit_hash) if not is_installed("lpips"): - run_pip(f"install -r {os.path.join(repo_dir('CodeFormer'), 'requirements.txt')}", "requirements for CodeFormer") + run_pip(f"install -r \"{os.path.join(repo_dir('CodeFormer'), 'requirements.txt')}\"", "requirements for CodeFormer") - run_pip(f"install -r {os.path.join(script_path, requirements_file)}", "requirements for Web UI") + if not os.path.isfile(requirements_file): + requirements_file = os.path.join(script_path, requirements_file) + run_pip(f"install -r \"{requirements_file}\"", "requirements for Web UI") run_extensions_installers(settings_file=args.ui_settings_file) From fc4d593b4e069329a1014ddc3ddb9bb4757ab7d0 Mon Sep 17 00:00:00 2001 From: Vladimir Mandic Date: Sun, 12 Mar 2023 08:51:12 -0400 Subject: [PATCH 151/278] fix import --- webui.py | 1 + 1 file changed, 1 insertion(+) diff --git a/webui.py b/webui.py index 9baa7da5..36e18d37 100644 --- a/webui.py +++ b/webui.py @@ -4,6 +4,7 @@ import time import importlib import signal import re +import warnings from fastapi import FastAPI from fastapi.middleware.cors import CORSMiddleware from fastapi.middleware.gzip import GZipMiddleware From 8179901f9cc3fe7d2d4b24edc8fe59275a1332f8 Mon Sep 17 00:00:00 2001 From: Vladimir Mandic Date: Sun, 12 Mar 2023 09:12:54 -0400 Subject: [PATCH 152/278] disable pil checks --- scripts/xyz_grid.py | 1 + 1 file changed, 1 insertion(+) diff --git a/scripts/xyz_grid.py b/scripts/xyz_grid.py index ce584981..f6513dae 100644 --- a/scripts/xyz_grid.py +++ b/scripts/xyz_grid.py @@ -515,6 +515,7 @@ class Script(scripts.Script): zs = process_axis(z_opt, z_values) # this could be moved to common code, but unlikely to be ever triggered anywhere else + Image.MAX_IMAGE_PIXELS = None # disable check in Pillow and rely on check below to allow large custom image sizes grid_mp = round(len(xs) * len(ys) * len(zs) * p.width * p.height / 1000000) assert grid_mp < opts.img_max_size_mp, f'Error: Resulting grid would be too large ({grid_mp} MPixels) (max configured size is {opts.img_max_size_mp} MPixels)' From bd67c41f5415c4c42d2383f128fe9ee656153bd0 Mon Sep 17 00:00:00 2001 From: Vladimir Mandic Date: Sun, 12 Mar 2023 09:19:23 -0400 Subject: [PATCH 153/278] force refresh tqdm before close --- modules/shared.py | 1 + 1 file changed, 1 insertion(+) diff --git a/modules/shared.py b/modules/shared.py index 2fb9e3b5..f28a12cc 100644 --- a/modules/shared.py +++ b/modules/shared.py @@ -714,6 +714,7 @@ class TotalTQDM: def clear(self): if self._tqdm is not None: + self._tqdm.refresh() self._tqdm.close() self._tqdm = None From 27eedb696661d031b9a7b8641b50eaec8dabf64f Mon Sep 17 00:00:00 2001 From: AUTOMATIC <16777216c@gmail.com> Date: Sun, 12 Mar 2023 17:20:04 +0300 Subject: [PATCH 154/278] change extension index link to the new dedicated repo instead of wiki --- modules/ui_extensions.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/modules/ui_extensions.py b/modules/ui_extensions.py index bd4308ef..df75a925 100644 --- a/modules/ui_extensions.py +++ b/modules/ui_extensions.py @@ -304,7 +304,7 @@ def create_ui(): with gr.TabItem("Available"): with gr.Row(): refresh_available_extensions_button = gr.Button(value="Load from:", variant="primary") - available_extensions_index = gr.Text(value="https://raw.githubusercontent.com/wiki/AUTOMATIC1111/stable-diffusion-webui/Extensions-index.md", label="Extension index URL").style(container=False) + available_extensions_index = gr.Text(value="https://raw.githubusercontent.com/AUTOMATIC1111/stable-diffusion-webui-extensions/master/index.json", label="Extension index URL").style(container=False) extension_to_install = gr.Text(elem_id="extension_to_install", visible=False) install_extension_button = gr.Button(elem_id="install_extension_button", visible=False) From 6033de18bff6c1506879e5f3a645f98131c3f043 Mon Sep 17 00:00:00 2001 From: AUTOMATIC <16777216c@gmail.com> Date: Sun, 12 Mar 2023 20:50:02 +0300 Subject: [PATCH 155/278] revert webui.sh from #8492 --- webui.sh | 28 ++++++++++++++-------------- 1 file changed, 14 insertions(+), 14 deletions(-) diff --git a/webui.sh b/webui.sh index 7b6e0568..8cdad22d 100755 --- a/webui.sh +++ b/webui.sh @@ -6,18 +6,19 @@ # If run from macOS, load defaults from webui-macos-env.sh if [[ "$OSTYPE" == "darwin"* ]]; then - if [[ -f "$(dirname $0)/webui-macos-env.sh" ]] + if [[ -f webui-macos-env.sh ]] then - source "$(dirname $0)/webui-macos-env.sh" + source ./webui-macos-env.sh fi fi # Read variables from webui-user.sh # shellcheck source=/dev/null -if [[ -f "$(dirname $0)/webui-user.sh" ]] +if [[ -f webui-user.sh ]] then - source "$(dirname $0)/webui-user.sh" + source ./webui-user.sh fi + # Set defaults # Install directory without trailing slash if [[ -z "${install_dir}" ]] @@ -46,12 +47,12 @@ fi # python3 venv without trailing slash (defaults to ${install_dir}/${clone_dir}/venv) if [[ -z "${venv_dir}" ]] then - venv_dir="${install_dir}/${clone_dir}/venv" + venv_dir="venv" fi if [[ -z "${LAUNCH_SCRIPT}" ]] then - LAUNCH_SCRIPT="${install_dir}/${clone_dir}/launch.py" + LAUNCH_SCRIPT="launch.py" fi # this script cannot be run as root by default @@ -139,23 +140,22 @@ then exit 1 fi -if [[ ! -d "${install_dir}/${clone_dir}" ]] +cd "${install_dir}"/ || { printf "\e[1m\e[31mERROR: Can't cd to %s/, aborting...\e[0m" "${install_dir}"; exit 1; } +if [[ -d "${clone_dir}" ]] then + cd "${clone_dir}"/ || { printf "\e[1m\e[31mERROR: Can't cd to %s/%s/, aborting...\e[0m" "${install_dir}" "${clone_dir}"; exit 1; } +else printf "\n%s\n" "${delimiter}" printf "Clone stable-diffusion-webui" printf "\n%s\n" "${delimiter}" - mkdir -p "${install_dir}" - "${GIT}" clone https://github.com/AUTOMATIC1111/stable-diffusion-webui.git "${install_dir}/${clone_dir}" + "${GIT}" clone https://github.com/AUTOMATIC1111/stable-diffusion-webui.git "${clone_dir}" + cd "${clone_dir}"/ || { printf "\e[1m\e[31mERROR: Can't cd to %s/%s/, aborting...\e[0m" "${install_dir}" "${clone_dir}"; exit 1; } fi printf "\n%s\n" "${delimiter}" printf "Create and activate python venv" printf "\n%s\n" "${delimiter}" -# Make venv_dir absolute -if [[ "${venv_dir}" != /* ]] -then - venv_dir="${install_dir}/${clone_dir}/${venv_dir}" -fi +cd "${install_dir}"/"${clone_dir}"/ || { printf "\e[1m\e[31mERROR: Can't cd to %s/%s/, aborting...\e[0m" "${install_dir}" "${clone_dir}"; exit 1; } if [[ ! -d "${venv_dir}" ]] then "${python_cmd}" -m venv "${venv_dir}" From a00cd8b9c1e9866a58d135f3b64cc7e0f29c6d47 Mon Sep 17 00:00:00 2001 From: AUTOMATIC <16777216c@gmail.com> Date: Sun, 12 Mar 2023 21:04:17 +0300 Subject: [PATCH 156/278] attempt to fix memory monitor with multiple CUDA devices --- modules/memmon.py | 12 ++++++++---- 1 file changed, 8 insertions(+), 4 deletions(-) diff --git a/modules/memmon.py b/modules/memmon.py index a7060f58..4018edcc 100644 --- a/modules/memmon.py +++ b/modules/memmon.py @@ -23,12 +23,16 @@ class MemUsageMonitor(threading.Thread): self.data = defaultdict(int) try: - torch.cuda.mem_get_info() + self.cuda_mem_get_info() torch.cuda.memory_stats(self.device) except Exception as e: # AMD or whatever print(f"Warning: caught exception '{e}', memory monitor disabled") self.disabled = True + def cuda_mem_get_info(self): + index = self.device.index if self.device.index is not None else torch.cuda.current_device() + return torch.cuda.mem_get_info(index) + def run(self): if self.disabled: return @@ -43,10 +47,10 @@ class MemUsageMonitor(threading.Thread): self.run_flag.clear() continue - self.data["min_free"] = torch.cuda.mem_get_info()[0] + self.data["min_free"] = self.cuda_mem_get_info()[0] while self.run_flag.is_set(): - free, total = torch.cuda.mem_get_info() # calling with self.device errors, torch bug? + free, total = self.cuda_mem_get_info() self.data["min_free"] = min(self.data["min_free"], free) time.sleep(1 / self.opts.memmon_poll_rate) @@ -70,7 +74,7 @@ class MemUsageMonitor(threading.Thread): def read(self): if not self.disabled: - free, total = torch.cuda.mem_get_info() + free, total = self.cuda_mem_get_info() self.data["free"] = free self.data["total"] = total From dfeee786f903e392dbef1519c7c246b9856ebab3 Mon Sep 17 00:00:00 2001 From: AUTOMATIC <16777216c@gmail.com> Date: Sun, 12 Mar 2023 21:25:22 +0300 Subject: [PATCH 157/278] display correct timings after restarting UI --- modules/timer.py | 3 +++ webui.py | 12 ++++++++++++ 2 files changed, 15 insertions(+) diff --git a/modules/timer.py b/modules/timer.py index 57a4f17a..ba92be33 100644 --- a/modules/timer.py +++ b/modules/timer.py @@ -33,3 +33,6 @@ class Timer: res += ")" return res + + def reset(self): + self.__init__() diff --git a/webui.py b/webui.py index 1a4175af..aaec79fd 100644 --- a/webui.py +++ b/webui.py @@ -290,24 +290,35 @@ def webui(): wait_on_server(shared.demo) print('Restarting UI...') + startup_timer.reset() + sd_samplers.set_samplers() modules.script_callbacks.script_unloaded_callback() extensions.list_extensions() + startup_timer.record("list extensions") localization.list_localizations(cmd_opts.localizations_dir) modelloader.forbid_loaded_nonbuiltin_upscalers() modules.scripts.reload_scripts() + startup_timer.record("load scripts") + modules.script_callbacks.model_loaded_callback(shared.sd_model) + startup_timer.record("model loaded callback") + modelloader.load_upscalers() + startup_timer.record("load upscalers") 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") modules.sd_models.list_models() + startup_timer.record("list SD models") shared.reload_hypernetworks() + startup_timer.record("reload hypernetworks") ui_extra_networks.intialize() ui_extra_networks.register_page(ui_extra_networks_textual_inversion.ExtraNetworksPageTextualInversion()) @@ -316,6 +327,7 @@ def webui(): extra_networks.initialize() extra_networks.register_extra_network(extra_networks_hypernet.ExtraNetworkHypernet()) + startup_timer.record("initialize extra networks") if __name__ == "__main__": From a71b7b5ec09a24e8a9bb3385e32862da905af6f1 Mon Sep 17 00:00:00 2001 From: "Alex \"mcmonkey\" Goodwin" Date: Sun, 12 Mar 2023 12:30:31 -0700 Subject: [PATCH 158/278] relocate filename length limit to better spot --- modules/images.py | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/modules/images.py b/modules/images.py index 4c204fca..2ce5c67c 100644 --- a/modules/images.py +++ b/modules/images.py @@ -512,9 +512,6 @@ def save_image(image, path, basename, seed=None, prompt=None, extension='png', i file_decoration = "-" + file_decoration file_decoration = namegen.apply(file_decoration) + suffix - if hasattr(os, 'statvfs'): - max_name_len = os.statvfs(path).f_namemax - file_decoration = file_decoration[:max_name_len - 5] if add_number: basecount = get_next_sequence_number(path, basename) @@ -576,6 +573,10 @@ def save_image(image, path, basename, seed=None, prompt=None, extension='png', i os.replace(temp_file_path, filename_without_extension + extension) fullfn_without_extension, extension = os.path.splitext(params.filename) + if hasattr(os, 'statvfs'): + max_name_len = os.statvfs(path).f_namemax + fullfn_without_extension = fullfn_without_extension[:max_name_len - len(extension)] + params.filename = fullfn_without_extension + extension _atomically_save_image(image, fullfn_without_extension, extension) image.already_saved_as = fullfn From 48df6d66ea627a1c538aba4d37d9141798fff4d7 Mon Sep 17 00:00:00 2001 From: "Alex \"mcmonkey\" Goodwin" Date: Sun, 12 Mar 2023 12:33:29 -0700 Subject: [PATCH 159/278] add safety check in case of short extensions so eg if a two-letter or empty extension is used, `.txt` would break, this `max` call protects that. --- modules/images.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/modules/images.py b/modules/images.py index 2ce5c67c..18d1de2f 100644 --- a/modules/images.py +++ b/modules/images.py @@ -575,7 +575,7 @@ def save_image(image, path, basename, seed=None, prompt=None, extension='png', i fullfn_without_extension, extension = os.path.splitext(params.filename) if hasattr(os, 'statvfs'): max_name_len = os.statvfs(path).f_namemax - fullfn_without_extension = fullfn_without_extension[:max_name_len - len(extension)] + fullfn_without_extension = fullfn_without_extension[:max_name_len - max(4, len(extension))] params.filename = fullfn_without_extension + extension _atomically_save_image(image, fullfn_without_extension, extension) From af9158a8c708c2f710823b298708c51a4d8ba08f Mon Sep 17 00:00:00 2001 From: "Alex \"mcmonkey\" Goodwin" Date: Sun, 12 Mar 2023 12:36:04 -0700 Subject: [PATCH 160/278] update `fullfn` properly --- modules/images.py | 1 + 1 file changed, 1 insertion(+) diff --git a/modules/images.py b/modules/images.py index 18d1de2f..2da988ee 100644 --- a/modules/images.py +++ b/modules/images.py @@ -577,6 +577,7 @@ def save_image(image, path, basename, seed=None, prompt=None, extension='png', i max_name_len = os.statvfs(path).f_namemax fullfn_without_extension = fullfn_without_extension[:max_name_len - max(4, len(extension))] params.filename = fullfn_without_extension + extension + fullfn = params.filename _atomically_save_image(image, fullfn_without_extension, extension) image.already_saved_as = fullfn From 9e23bacfbcb35f46f28539f71b2bc917276634b8 Mon Sep 17 00:00:00 2001 From: missionfloyd Date: Sun, 12 Mar 2023 17:07:03 -0600 Subject: [PATCH 161/278] Make extra networks button togglable --- modules/ui_extra_networks.py | 4 ++-- style.css | 7 +++++++ 2 files changed, 9 insertions(+), 2 deletions(-) diff --git a/modules/ui_extra_networks.py b/modules/ui_extra_networks.py index 01df5e90..50e2093e 100644 --- a/modules/ui_extra_networks.py +++ b/modules/ui_extra_networks.py @@ -219,10 +219,10 @@ def create_ui(container, button, tabname): def toggle_visibility(is_visible): is_visible = not is_visible - return is_visible, gr.update(visible=is_visible) + return is_visible, gr.update(visible=is_visible), gr.update(variant=("primary" if is_visible else "tool")) state_visible = gr.State(value=False) - button.click(fn=toggle_visibility, inputs=[state_visible], outputs=[state_visible, container]) + button.click(fn=toggle_visibility, inputs=[state_visible], outputs=[state_visible, container, button]) def refresh(): res = [] diff --git a/style.css b/style.css index 2f26ad02..e82aebf3 100644 --- a/style.css +++ b/style.css @@ -968,3 +968,10 @@ footer { [id*='_prompt_container'] > div { margin: 0!important; } + +button[id$='_extra_networks'] { + margin: 0.6em 0em 0.55em 0; + max-width: 2.5em; + min-width: 2.5em !important; + height: 2.4em; +} From 4d26c7da57a621815f25929b35977bd6a3958711 Mon Sep 17 00:00:00 2001 From: high_byte Date: Mon, 13 Mar 2023 17:37:29 +0200 Subject: [PATCH 162/278] initialize extra_network_data before use --- modules/processing.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/modules/processing.py b/modules/processing.py index 06e7a440..59717b4c 100644 --- a/modules/processing.py +++ b/modules/processing.py @@ -583,6 +583,7 @@ def process_images_inner(p: StableDiffusionProcessing) -> Processed: if state.job_count == -1: state.job_count = p.n_iter + extra_network_data = None for n in range(p.n_iter): p.iteration = n @@ -712,7 +713,7 @@ def process_images_inner(p: StableDiffusionProcessing) -> Processed: if opts.grid_save: images.save_image(grid, p.outpath_grids, "grid", p.all_seeds[0], p.all_prompts[0], opts.grid_format, info=infotext(), short_filename=not opts.grid_extended_filename, p=p, grid=True) - if not p.disable_extra_networks: + if not p.disable_extra_networks and extra_network_data: extra_networks.deactivate(p, extra_network_data) devices.torch_gc() From 03a80f198e8cda66cb6206cb3ae505d66d9eed9d Mon Sep 17 00:00:00 2001 From: Vladimir Mandic Date: Mon, 13 Mar 2023 12:35:30 -0400 Subject: [PATCH 163/278] add pbar to unipc --- modules/models/diffusion/uni_pc/sampler.py | 2 +- modules/models/diffusion/uni_pc/uni_pc.py | 5 +++-- 2 files changed, 4 insertions(+), 3 deletions(-) diff --git a/modules/models/diffusion/uni_pc/sampler.py b/modules/models/diffusion/uni_pc/sampler.py index bf346ff4..a241c8a7 100644 --- a/modules/models/diffusion/uni_pc/sampler.py +++ b/modules/models/diffusion/uni_pc/sampler.py @@ -71,7 +71,7 @@ class UniPCSampler(object): # sampling C, H, W = shape size = (batch_size, C, H, W) - print(f'Data shape for UniPC sampling is {size}') + # print(f'Data shape for UniPC sampling is {size}') device = self.model.betas.device if x_T is None: diff --git a/modules/models/diffusion/uni_pc/uni_pc.py b/modules/models/diffusion/uni_pc/uni_pc.py index e9a093a2..eb5f4e76 100644 --- a/modules/models/diffusion/uni_pc/uni_pc.py +++ b/modules/models/diffusion/uni_pc/uni_pc.py @@ -1,6 +1,7 @@ import torch import torch.nn.functional as F import math +from tqdm.auto import trange class NoiseScheduleVP: @@ -750,7 +751,7 @@ class UniPC: if method == 'multistep': assert steps >= order, "UniPC order must be < sampling steps" timesteps = self.get_time_steps(skip_type=skip_type, t_T=t_T, t_0=t_0, N=steps, device=device) - print(f"Running UniPC Sampling with {timesteps.shape[0]} timesteps, order {order}") + #print(f"Running UniPC Sampling with {timesteps.shape[0]} timesteps, order {order}") assert timesteps.shape[0] - 1 == steps with torch.no_grad(): vec_t = timesteps[0].expand((x.shape[0])) @@ -766,7 +767,7 @@ class UniPC: self.after_update(x, model_x) model_prev_list.append(model_x) t_prev_list.append(vec_t) - for step in range(order, steps + 1): + for step in trange(order, steps + 1): vec_t = timesteps[step].expand(x.shape[0]) if lower_order_final: step_order = min(order, steps + 1 - step) From c19530f1a590d758463f84523dd4c48c34d723e6 Mon Sep 17 00:00:00 2001 From: AUTOMATIC <16777216c@gmail.com> Date: Tue, 14 Mar 2023 09:10:26 +0300 Subject: [PATCH 164/278] Add view metadata button for Lora cards. --- extensions-builtin/Lora/lora.py | 21 ++++++- .../Lora/ui_extra_networks_lora.py | 1 + html/extra-networks-card.html | 2 + javascript/extraNetworks.js | 38 +++++++++++- modules/sd_models.py | 24 ++++++++ modules/ui_extra_networks.py | 7 +++ style.css | 61 +++++++++++++++++++ 7 files changed, 152 insertions(+), 2 deletions(-) diff --git a/extensions-builtin/Lora/lora.py b/extensions-builtin/Lora/lora.py index cb8f1d36..8937b585 100644 --- a/extensions-builtin/Lora/lora.py +++ b/extensions-builtin/Lora/lora.py @@ -3,7 +3,9 @@ import os import re import torch -from modules import shared, devices, sd_models +from modules import shared, devices, sd_models, errors + +metadata_tags_order = {"ss_sd_model_name": 1, "ss_resolution": 2, "ss_clip_skip": 3, "ss_num_train_images": 10, "ss_tag_frequency": 20} re_digits = re.compile(r"\d+") re_unet_down_blocks = re.compile(r"lora_unet_down_blocks_(\d+)_attentions_(\d+)_(.+)") @@ -43,6 +45,23 @@ class LoraOnDisk: def __init__(self, name, filename): self.name = name self.filename = filename + self.metadata = {} + + _, ext = os.path.splitext(filename) + if ext.lower() == ".safetensors": + try: + self.metadata = sd_models.read_metadata_from_safetensors(filename) + except Exception as e: + errors.display(e, f"reading lora {filename}") + + if self.metadata: + m = {} + for k, v in sorted(self.metadata.items(), key=lambda x: metadata_tags_order.get(x[0], 999)): + m[k] = v + + self.metadata = m + + self.ssmd_cover_images = self.metadata.pop('ssmd_cover_images', None) # those are cover images and they are too big to display in UI as text class LoraModule: diff --git a/extensions-builtin/Lora/ui_extra_networks_lora.py b/extensions-builtin/Lora/ui_extra_networks_lora.py index 8d32052e..68b11332 100644 --- a/extensions-builtin/Lora/ui_extra_networks_lora.py +++ b/extensions-builtin/Lora/ui_extra_networks_lora.py @@ -23,6 +23,7 @@ class ExtraNetworksPageLora(ui_extra_networks.ExtraNetworksPage): "search_term": self.search_terms_from_path(lora_on_disk.filename), "prompt": json.dumps(f""), "local_preview": f"{path}.{shared.opts.samples_format}", + "metadata": json.dumps(lora_on_disk.metadata, indent=4) if lora_on_disk.metadata else None, } def allowed_directories_for_previews(self): diff --git a/html/extra-networks-card.html b/html/extra-networks-card.html index 8612396d..1bf3fc30 100644 --- a/html/extra-networks-card.html +++ b/html/extra-networks-card.html @@ -1,4 +1,6 @@
    + {metadata_button} +
      diff --git a/javascript/extraNetworks.js b/javascript/extraNetworks.js index d0177ad6..2fb87cd5 100644 --- a/javascript/extraNetworks.js +++ b/javascript/extraNetworks.js @@ -102,4 +102,40 @@ function extraNetworksSearchButton(tabs_id, event){ searchTextarea.value = text updateInput(searchTextarea) -} \ No newline at end of file +} + +var globalPopup = null; +var globalPopupInner = null; +function popup(contents){ + if(! globalPopup){ + globalPopup = document.createElement('div') + globalPopup.onclick = function(){ globalPopup.style.display = "none"; }; + globalPopup.classList.add('global-popup'); + + var close = document.createElement('div') + close.classList.add('global-popup-close'); + close.onclick = function(){ globalPopup.style.display = "none"; }; + close.title = "Close"; + globalPopup.appendChild(close) + + globalPopupInner = document.createElement('div') + globalPopupInner.onclick = function(event){ event.stopPropagation(); return false; }; + globalPopupInner.classList.add('global-popup-inner'); + globalPopup.appendChild(globalPopupInner) + + gradioApp().appendChild(globalPopup); + } + + globalPopupInner.innerHTML = ''; + globalPopupInner.appendChild(contents); + + globalPopup.style.display = "flex"; +} + +function extraNetworksShowMetadata(text){ + elem = document.createElement('pre') + elem.classList.add('popup-metadata'); + elem.textContent = text; + + popup(elem); +} diff --git a/modules/sd_models.py b/modules/sd_models.py index 93959f55..5f57ec0c 100644 --- a/modules/sd_models.py +++ b/modules/sd_models.py @@ -210,6 +210,30 @@ def get_state_dict_from_checkpoint(pl_sd): return pl_sd +def read_metadata_from_safetensors(filename): + import json + + with open(filename, mode="rb") as file: + metadata_len = file.read(8) + metadata_len = int.from_bytes(metadata_len, "little") + 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] == '{': + try: + res[k] = json.loads(v) + except Exception as e: + pass + + return res + + def read_state_dict(checkpoint_file, print_global_state=False, map_location=None): _, extension = os.path.splitext(checkpoint_file) if extension.lower() == ".safetensors": diff --git a/modules/ui_extra_networks.py b/modules/ui_extra_networks.py index 01df5e90..b5847299 100644 --- a/modules/ui_extra_networks.py +++ b/modules/ui_extra_networks.py @@ -124,6 +124,12 @@ class ExtraNetworksPage: if onclick is None: onclick = '"' + html.escape(f"""return cardClicked({json.dumps(tabname)}, {item["prompt"]}, {"true" if self.allow_negative_prompt else "false"})""") + '"' + metadata_button = "" + metadata = item.get("metadata") + if metadata: + metadata_onclick = '"' + html.escape(f"""extraNetworksShowMetadata({json.dumps(metadata)}); return false;""") + '"' + metadata_button = f"" + args = { "preview_html": "style='background-image: url(\"" + html.escape(preview) + "\")'" if preview else '', "prompt": item.get("prompt", None), @@ -134,6 +140,7 @@ class ExtraNetworksPage: "card_clicked": onclick, "save_card_preview": '"' + html.escape(f"""return saveCardPreview(event, {json.dumps(tabname)}, {json.dumps(item["local_preview"])})""") + '"', "search_term": item.get("search_term", ""), + "metadata_button": metadata_button, } return self.card_page.format(**args) diff --git a/style.css b/style.css index 2f26ad02..3eac2b17 100644 --- a/style.css +++ b/style.css @@ -362,6 +362,46 @@ input[type="range"]{ height: 100%; } +.popup-metadata{ + color: black; + background: white; + display: inline-block; + padding: 1em; + white-space: pre-wrap; +} + +.global-popup{ + display: flex; + position: fixed; + z-index: 1001; + left: 0; + top: 0; + width: 100%; + height: 100%; + overflow: auto; + background-color: rgba(20, 20, 20, 0.95); +} + + +.global-popup-close:before { + content: "×"; +} + +.global-popup-close{ + position: fixed; + right: 0.25em; + top: 0; + cursor: pointer; + color: white; + font-size: 32pt; +} + +.global-popup-inner{ + display: inline-block; + margin: auto; + padding: 2em; +} + #lightboxModal{ display: none; position: fixed; @@ -837,6 +877,27 @@ footer { margin-left: 0.5em; } + +.extra-network-cards .card .metadata-button:before, .extra-network-thumbs .card .metadata-button:before{ + content: "🛈"; +} +.extra-network-cards .card .metadata-button, .extra-network-thumbs .card .metadata-button{ + display: none; + position: absolute; + right: 0; + color: white; + text-shadow: 2px 2px 3px black; + padding: 0.25em; + font-size: 22pt; +} +.extra-network-cards .card:hover .metadata-button, .extra-network-thumbs .card:hover .metadata-button{ + display: inline-block; +} +.extra-network-cards .card .metadata-button:hover, .extra-network-thumbs .card .metadata-button:hover{ + color: red; +} + + .extra-network-thumbs { display: flex; flex-flow: row wrap; From 4281432594084bc846cc834c807ac57f59457eae Mon Sep 17 00:00:00 2001 From: willtakasan <126040162+willtakasan@users.noreply.github.com> Date: Tue, 14 Mar 2023 15:36:08 +0900 Subject: [PATCH 165/278] Update ui_extra_networks.py I updated it so that no error message is displayed when setting a webp for the preview image. --- modules/ui_extra_networks.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/modules/ui_extra_networks.py b/modules/ui_extra_networks.py index b5847299..cdfd6f2a 100644 --- a/modules/ui_extra_networks.py +++ b/modules/ui_extra_networks.py @@ -30,8 +30,8 @@ def add_pages_to_demo(app): raise ValueError(f"File cannot be fetched: {filename}. Must be in one of directories registered by extra pages.") ext = os.path.splitext(filename)[1].lower() - if ext not in (".png", ".jpg"): - raise ValueError(f"File cannot be fetched: {filename}. Only png and jpg.") + if ext not in (".png", ".jpg", ".webp"): + raise ValueError(f"File cannot be fetched: {filename}. Only png and jpg and webp.") # would profit from returning 304 return FileResponse(filename, headers={"Accept-Ranges": "bytes"}) From 6a04a7f20fcc4a992ae017b06723e9ceffe17b37 Mon Sep 17 00:00:00 2001 From: AUTOMATIC <16777216c@gmail.com> Date: Tue, 14 Mar 2023 11:22:29 +0300 Subject: [PATCH 166/278] fix an error loading Lora with empty values in metadata --- 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 5f57ec0c..f0cb1240 100644 --- a/modules/sd_models.py +++ b/modules/sd_models.py @@ -225,7 +225,7 @@ def read_metadata_from_safetensors(filename): res = {} for k, v in json_obj.get("__metadata__", {}).items(): res[k] = v - if isinstance(v, str) and v[0] == '{': + if isinstance(v, str) and v[0:1] == '{': try: res[k] = json.loads(v) except Exception as e: From 1823526c103ee1d2232dfa65f908636daa22a342 Mon Sep 17 00:00:00 2001 From: Mikhail Gribanov Date: Tue, 14 Mar 2023 13:05:45 +0200 Subject: [PATCH 167/278] Update README.md --- README.md | 21 ++++++++++----------- 1 file changed, 10 insertions(+), 11 deletions(-) diff --git a/README.md b/README.md index 24f8e799..b67e2296 100644 --- a/README.md +++ b/README.md @@ -13,9 +13,9 @@ A browser interface based on Gradio library for Stable Diffusion. - Prompt Matrix - Stable Diffusion Upscale - Attention, specify parts of text that the model should pay more attention to - - a man in a ((tuxedo)) - will pay more attention to tuxedo - - a man in a (tuxedo:1.21) - alternative syntax - - select text and press ctrl+up or ctrl+down to automatically adjust attention to selected text (code contributed by anonymous user) + - a man in a `((tuxedo))` - will pay more attention to tuxedo + - a man in a `(tuxedo:1.21)` - alternative syntax + - select text and press `Ctrl+Up` or `Ctrl+Down` to automatically adjust attention to selected text (code contributed by anonymous user) - Loopback, run img2img processing multiple times - X/Y/Z plot, a way to draw a 3 dimensional plot of images with different parameters - Textual Inversion @@ -28,7 +28,7 @@ A browser interface based on Gradio library for Stable Diffusion. - CodeFormer, face restoration tool as an alternative to GFPGAN - RealESRGAN, neural network upscaler - ESRGAN, neural network upscaler with a lot of third party models - - SwinIR and Swin2SR([see here](https://github.com/AUTOMATIC1111/stable-diffusion-webui/pull/2092)), neural network upscalers + - SwinIR and Swin2SR ([see here](https://github.com/AUTOMATIC1111/stable-diffusion-webui/pull/2092)), neural network upscalers - LDSR, Latent diffusion super resolution upscaling - Resizing aspect ratio options - Sampling method selection @@ -46,7 +46,7 @@ A browser interface based on Gradio library for Stable Diffusion. - drag and drop an image/text-parameters to promptbox - Read Generation Parameters Button, loads parameters in promptbox to UI - Settings page -- Running arbitrary python code from UI (must run with --allow-code to enable) +- Running arbitrary python code from UI (must run with `--allow-code` to enable) - Mouseover hints for most UI elements - Possible to change defaults/mix/max/step values for UI elements via text config - Tiling support, a checkbox to create images that can be tiled like textures @@ -69,7 +69,7 @@ A browser interface based on Gradio library for Stable Diffusion. - also supports weights for prompts: `a cat :1.2 AND a dog AND a penguin :2.2` - No token limit for prompts (original stable diffusion lets you use up to 75 tokens) - DeepDanbooru integration, creates danbooru style tags for anime prompts -- [xformers](https://github.com/AUTOMATIC1111/stable-diffusion-webui/wiki/Xformers), major speed increase for select cards: (add --xformers to commandline args) +- [xformers](https://github.com/AUTOMATIC1111/stable-diffusion-webui/wiki/Xformers), major speed increase for select cards: (add `--xformers` to commandline args) - via extension: [History tab](https://github.com/yfszzx/stable-diffusion-webui-images-browser): view, direct and delete images conveniently within the UI - Generate forever option - Training tab @@ -78,11 +78,11 @@ A browser interface based on Gradio library for Stable Diffusion. - Clip skip - Hypernetworks - Loras (same as Hypernetworks but more pretty) -- A sparate UI where you can choose, with preview, which embeddings, hypernetworks or Loras to add to your prompt. +- A sparate 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 -- Support for dedicated [inpainting model](https://github.com/runwayml/stable-diffusion#inpainting-with-stable-diffusion) by RunwayML. +- Support for dedicated [inpainting model](https://github.com/runwayml/stable-diffusion#inpainting-with-stable-diffusion) by RunwayML - via extension: [Aesthetic Gradients](https://github.com/AUTOMATIC1111/stable-diffusion-webui-aesthetic-gradients), a way to generate images with a specific aesthetic by using clip images embeds (implementation of [https://github.com/vicgalle/stable-diffusion-aesthetic-gradients](https://github.com/vicgalle/stable-diffusion-aesthetic-gradients)) - [Stable Diffusion 2.0](https://github.com/Stability-AI/stablediffusion) support - see [wiki](https://github.com/AUTOMATIC1111/stable-diffusion-webui/wiki/Features#stable-diffusion-20) for instructions - [Alt-Diffusion](https://arxiv.org/abs/2211.06679) support - see [wiki](https://github.com/AUTOMATIC1111/stable-diffusion-webui/wiki/Features#alt-diffusion) for instructions @@ -91,7 +91,6 @@ A browser interface based on Gradio library for Stable Diffusion. - Eased resolution restriction: generated image's domension must be a multiple of 8 rather than 64 - Now with a license! - Reorder elements in the UI from settings screen -- ## Installation and Running Make sure the required [dependencies](https://github.com/AUTOMATIC1111/stable-diffusion-webui/wiki/Dependencies) are met and follow the instructions available for both [NVidia](https://github.com/AUTOMATIC1111/stable-diffusion-webui/wiki/Install-and-Run-on-NVidia-GPUs) (recommended) and [AMD](https://github.com/AUTOMATIC1111/stable-diffusion-webui/wiki/Install-and-Run-on-AMD-GPUs) GPUs. @@ -101,7 +100,7 @@ Alternatively, use online services (like Google Colab): - [List of Online Services](https://github.com/AUTOMATIC1111/stable-diffusion-webui/wiki/Online-Services) ### Automatic Installation on Windows -1. Install [Python 3.10.6](https://www.python.org/downloads/windows/), checking "Add Python to PATH" +1. Install [Python 3.10.6](https://www.python.org/downloads/windows/), checking "Add Python to PATH". 2. Install [git](https://git-scm.com/download/win). 3. Download the stable-diffusion-webui repository, for example by running `git clone https://github.com/AUTOMATIC1111/stable-diffusion-webui.git`. 4. Run `webui-user.bat` from Windows Explorer as normal, non-administrator, user. @@ -159,4 +158,4 @@ Licenses for borrowed code can be found in `Settings -> Licenses` screen, and al - Security advice - RyotaK - UniPC sampler - Wenliang Zhao - https://github.com/wl-zhao/UniPC - Initial Gradio script - posted on 4chan by an Anonymous user. Thank you Anonymous user. -- (You) +- (You) \ No newline at end of file From f2ed6295b9041e217c50ad57ca2f609aa6f1bac9 Mon Sep 17 00:00:00 2001 From: Vladimir Mandic Date: Tue, 14 Mar 2023 07:46:09 -0400 Subject: [PATCH 168/278] make it module specific --- webui.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/webui.py b/webui.py index 36e18d37..d7bbc7b7 100644 --- a/webui.py +++ b/webui.py @@ -19,7 +19,7 @@ startup_timer = timer.Timer() import torch import pytorch_lightning # pytorch_lightning should be imported after torch, but it re-enables warnings on import so import once to disable them -warnings.filterwarnings(action="ignore", category=DeprecationWarning) +warnings.filterwarnings(action="ignore", category=DeprecationWarning, module="pytorch_lightning") startup_timer.record("import torch") import gradio From fd672a79afc912be46a2a01133269b8c6842a90d Mon Sep 17 00:00:00 2001 From: bluelovers Date: Wed, 15 Mar 2023 13:17:09 +0800 Subject: [PATCH 169/278] fix: remove cmp by ChatGPT --- modules/textual_inversion/textual_inversion.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/modules/textual_inversion/textual_inversion.py b/modules/textual_inversion/textual_inversion.py index 3d21b9fe..8b5bb6ce 100644 --- a/modules/textual_inversion/textual_inversion.py +++ b/modules/textual_inversion/textual_inversion.py @@ -129,7 +129,7 @@ class EmbeddingDatabase: if first_id not in self.ids_lookup: self.ids_lookup[first_id] = [] - self.ids_lookup[first_id] = sorted(self.ids_lookup[first_id] + [(ids, embedding)], key=lambda x: len(x[0]), reverse=True, cmp=lambda x, y: x.lower() > y.lower()) + self.ids_lookup[first_id] = sorted(self.ids_lookup[first_id] + [(ids, embedding)], key=lambda x: (len(x[0]), x[0].casefold()), reverse=True) return embedding From 4845db4e324dd5194ea82cc5f772237242a89a92 Mon Sep 17 00:00:00 2001 From: Ftps <63702646+Tps-F@users.noreply.github.com> Date: Wed, 15 Mar 2023 20:29:50 +0900 Subject: [PATCH 170/278] Update ui_extensions.py Add git submodule and Fix WinError --- modules/ui_extensions.py | 21 +++++---------------- 1 file changed, 5 insertions(+), 16 deletions(-) diff --git a/modules/ui_extensions.py b/modules/ui_extensions.py index df75a925..b24b67fc 100644 --- a/modules/ui_extensions.py +++ b/modules/ui_extensions.py @@ -141,22 +141,11 @@ def install_extension_from_url(dirname, url): try: shutil.rmtree(tmpdir, True) - - repo = git.Repo.clone_from(url, tmpdir) - repo.remote().fetch() - - try: - os.rename(tmpdir, target_dir) - except OSError as err: - # TODO what does this do on windows? I think it'll be a different error code but I don't have a system to check it - # Shouldn't cause any new issues at least but we probably want to handle it there too. - 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() - shutil.move(tmpdir, target_dir) - else: - # Something else, not enough free space, permissions, etc. rethrow it so that it gets handled. - raise(err) + with git.Repo.clone_from(url, tmpdir) as repo: + repo.remote().fetch() + for submodule in repo.submodules: + submodule.update() + os.rename(tmpdir, target_dir) import launch launch.run_extension_installer(target_dir) From 79ed567b12c73231b712eb97106a565330968e34 Mon Sep 17 00:00:00 2001 From: Ftps <63702646+Tps-F@users.noreply.github.com> Date: Wed, 15 Mar 2023 22:42:53 +0900 Subject: [PATCH 171/278] remove unused library I'm sorry I forgot. --- modules/ui_extensions.py | 2 -- 1 file changed, 2 deletions(-) diff --git a/modules/ui_extensions.py b/modules/ui_extensions.py index b24b67fc..3d9c4261 100644 --- a/modules/ui_extensions.py +++ b/modules/ui_extensions.py @@ -1,6 +1,5 @@ import json import os.path -import shutil import sys import time import traceback @@ -10,7 +9,6 @@ import git import gradio as gr import html import shutil -import errno from modules import extensions, shared, paths from modules.call_queue import wrap_gradio_gpu_call From 250193ee933eb4c45f07b910bc650e3b1a9a071e Mon Sep 17 00:00:00 2001 From: Vladimir Mandic Date: Wed, 15 Mar 2023 10:14:40 -0400 Subject: [PATCH 172/278] disable gradio css transitions --- webui.py | 3 +++ 1 file changed, 3 insertions(+) diff --git a/webui.py b/webui.py index aaec79fd..0e0ff105 100644 --- a/webui.py +++ b/webui.py @@ -262,6 +262,9 @@ def webui(): inbrowser=cmd_opts.autolaunch, prevent_thread_lock=True ) + for dep in shared.demo.dependencies: + dep['show_progress'] = False # disable gradio css animation on component update + # after initial launch, disable --autolaunch for subsequent restarts cmd_opts.autolaunch = False From 79d261b7d47e75a93f38e04608508102dded6a6c Mon Sep 17 00:00:00 2001 From: high_byte Date: Wed, 15 Mar 2023 19:44:30 +0200 Subject: [PATCH 173/278] disable gradio analytics globally --- launch.py | 1 + 1 file changed, 1 insertion(+) diff --git a/launch.py b/launch.py index 0868f8a9..2b2d3128 100644 --- a/launch.py +++ b/launch.py @@ -16,6 +16,7 @@ index_url = os.environ.get('INDEX_URL', "") stored_commit_hash = None skip_install = False +os.environ['GRADIO_ANALYTICS_ENABLED'] = 'False' def check_python_version(): is_windows = platform.system() == "Windows" From 5387576c59632f6bc85c81de4d19219f63437d0a Mon Sep 17 00:00:00 2001 From: Vladimir Mandic Date: Wed, 15 Mar 2023 15:11:04 -0400 Subject: [PATCH 174/278] api error handler --- modules/api/api.py | 45 ++++++++++++++++++++++++++++++++++++++++++++- requirements.txt | 1 + 2 files changed, 45 insertions(+), 1 deletion(-) diff --git a/modules/api/api.py b/modules/api/api.py index 35e17afc..8d4ff4e0 100644 --- a/modules/api/api.py +++ b/modules/api/api.py @@ -6,8 +6,11 @@ import uvicorn from threading import Lock from io import BytesIO from gradio.processing_utils import decode_base64_to_file -from fastapi import APIRouter, Depends, FastAPI, HTTPException, Request, Response +from fastapi import APIRouter, Depends, FastAPI, Request, Response from fastapi.security import HTTPBasic, HTTPBasicCredentials +from fastapi.exceptions import HTTPException +from fastapi.responses import JSONResponse +from fastapi.encoders import jsonable_encoder from secrets import compare_digest import modules.shared as shared @@ -90,6 +93,16 @@ def encode_pil_to_base64(image): return base64.b64encode(bytes_data) def api_middleware(app: FastAPI): + rich_available = True + try: + import anyio # importing just so it can be placed on silent list + import starlette # importing just so it can be placed on silent list + from rich.console import Console + console = Console() + except: + import traceback + rich_available = False + @app.middleware("http") async def log_and_time(req: Request, call_next): ts = time.time() @@ -110,6 +123,36 @@ def api_middleware(app: FastAPI): )) return res + def handle_exception(request: Request, e: Exception): + err = { + "error": type(e).__name__, + "detail": vars(e).get('detail', ''), + "body": vars(e).get('body', ''), + "errors": str(e), + } + print(f"API error: {request.method}: {request.url} {err}") + if not isinstance(e, HTTPException): # do not print backtrace on known httpexceptions + if rich_available: + console.print_exception(show_locals=True, max_frames=2, extra_lines=1, suppress=[anyio, starlette], word_wrap=False, width=min([console.width, 200])) + else: + traceback.print_exc() + return JSONResponse(status_code=vars(e).get('status_code', 500), content=jsonable_encoder(err)) + + @app.middleware("http") + async def exception_handling(request: Request, call_next): + try: + return await call_next(request) + except Exception as e: + return handle_exception(request, e) + + @app.exception_handler(Exception) + async def fastapi_exception_handler(request: Request, e: Exception): + return handle_exception(request, e) + + @app.exception_handler(HTTPException) + async def http_exception_handler(request: Request, e: HTTPException): + return handle_exception(request, e) + class Api: def __init__(self, app: FastAPI, queue_lock: Lock): diff --git a/requirements.txt b/requirements.txt index 6d53f089..69c1e7cc 100644 --- a/requirements.txt +++ b/requirements.txt @@ -30,3 +30,4 @@ GitPython torchsde safetensors psutil +rich From 575c17a8f9bc6471a7a0891b665ec42073a18049 Mon Sep 17 00:00:00 2001 From: missionfloyd Date: Wed, 15 Mar 2023 16:56:27 -0600 Subject: [PATCH 175/278] Update tooltip per Kilvoctu's suggestion --- javascript/hints.js | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/javascript/hints.js b/javascript/hints.js index 7f4101b2..83128497 100644 --- a/javascript/hints.js +++ b/javascript/hints.js @@ -21,8 +21,7 @@ titles = { "\u{1f5d1}": "Clear prompt", "\u{1f4cb}": "Apply selected styles to current prompt", "\u{1f4d2}": "Paste available values into the field", - "\u{1f3b4}": "Show extra networks", - + "\u{1f3b4}": "Show/hide extra networks", "Inpaint a part of image": "Draw a mask over an image, and the script will regenerate the masked area with content according to prompt", "SD upscale": "Upscale image normally, split result into tiles, improve each tile using img2img, merge whole image back", From dfa258de5f87ea7f5ff49e29ddf6e7b34ff8ebff Mon Sep 17 00:00:00 2001 From: Vespinian Date: Wed, 15 Mar 2023 22:17:32 -0400 Subject: [PATCH 176/278] Made copies of global scriptrunners, now we clear the copied scriptrunner of alwayson_scripts and only add back the ones that that were requested --- modules/api/api.py | 16 +++++++++++++--- 1 file changed, 13 insertions(+), 3 deletions(-) diff --git a/modules/api/api.py b/modules/api/api.py index 35e17afc..afbc202a 100644 --- a/modules/api/api.py +++ b/modules/api/api.py @@ -3,6 +3,7 @@ import io import time import datetime import uvicorn +import copy from threading import Lock from io import BytesIO from gradio.processing_utils import decode_base64_to_file @@ -202,6 +203,7 @@ class Api: script_args[0] = 0 # Now check for always on scripts + alwayson_script_to_run = [] # list to replace the one from the global ScriptRunner we copied if request.alwayson_scripts and (len(request.alwayson_scripts) > 0): for alwayson_script_name in request.alwayson_scripts.keys(): alwayson_script = self.get_script(alwayson_script_name, script_runner) @@ -210,13 +212,21 @@ class Api: # Selectable script in always on script param check if alwayson_script.alwayson == False: raise HTTPException(status_code=422, detail=f"Cannot have a selectable script in the always on scripts params") - # always on script with no arg should always run so you don't really need to add them to the requests + # all good, so add to run list and set its args if any + alwayson_script_to_run.append(alwayson_script) if "args" in request.alwayson_scripts[alwayson_script_name]: script_args[alwayson_script.args_from:alwayson_script.args_to] = request.alwayson_scripts[alwayson_script_name]["args"] + + # Remove always on scripts that were not included in the request by resetting the script list in out ScriptRunner + script_runner.alwayson_scripts.clear() + script_runner.alwayson_scripts = alwayson_script_to_run + script_runner.scripts.clear() + script_runner.scripts = alwayson_script_to_run + script_runner.selectable_scripts + return script_args def text2imgapi(self, txt2imgreq: StableDiffusionTxt2ImgProcessingAPI): - script_runner = scripts.scripts_txt2img + script_runner = copy.copy(scripts.scripts_txt2img) # copy so we don't overwrite our globals if not script_runner.scripts: script_runner.initialize_scripts(False) ui.create_ui() @@ -268,7 +278,7 @@ class Api: if mask: mask = decode_base64_to_image(mask) - script_runner = scripts.scripts_img2img + script_runner = copy.copy(scripts.scripts_img2img) # copy so we don't overwrite our globals if not script_runner.scripts: script_runner.initialize_scripts(True) ui.create_ui() From f04bd037a51de3c65072581d9a7dfed1d0d2887e Mon Sep 17 00:00:00 2001 From: Vespinian Date: Wed, 15 Mar 2023 22:27:54 -0400 Subject: [PATCH 177/278] Comment fix --- 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 afbc202a..8c06cf20 100644 --- a/modules/api/api.py +++ b/modules/api/api.py @@ -217,7 +217,7 @@ class Api: if "args" in request.alwayson_scripts[alwayson_script_name]: script_args[alwayson_script.args_from:alwayson_script.args_to] = request.alwayson_scripts[alwayson_script_name]["args"] - # Remove always on scripts that were not included in the request by resetting the script list in out ScriptRunner + # Remove always on scripts that were not included in the request by resetting the script list in our ScriptRunner script_runner.alwayson_scripts.clear() script_runner.alwayson_scripts = alwayson_script_to_run script_runner.scripts.clear() From 147d2922ff573f757b8940446b925c2e658e40ac Mon Sep 17 00:00:00 2001 From: Ftps <63702646+Tps-F@users.noreply.github.com> Date: Thu, 16 Mar 2023 12:35:48 +0900 Subject: [PATCH 178/278] Cross device link --- .fleet/settings.json | 0 modules/ui_extensions.py | 12 +++++++++++- 2 files changed, 11 insertions(+), 1 deletion(-) create mode 100644 .fleet/settings.json diff --git a/.fleet/settings.json b/.fleet/settings.json new file mode 100644 index 00000000..e69de29b diff --git a/modules/ui_extensions.py b/modules/ui_extensions.py index 3d9c4261..d9def96e 100644 --- a/modules/ui_extensions.py +++ b/modules/ui_extensions.py @@ -9,6 +9,7 @@ import git import gradio as gr import html import shutil +import errno from modules import extensions, shared, paths from modules.call_queue import wrap_gradio_gpu_call @@ -143,7 +144,16 @@ def install_extension_from_url(dirname, url): repo.remote().fetch() for submodule in repo.submodules: submodule.update() - os.rename(tmpdir, target_dir) + try: + os.rename(tmpdir, target_dir) + 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() + shutil.move(tmpdir, target_dir) + else: + # Something else, not enough free space, permissions, etc. rethrow it so that it gets handled. + raise err import launch launch.run_extension_installer(target_dir) From 6f5a5ad2057a2b2a1c94a5c3bf5bf0d8fefc187f Mon Sep 17 00:00:00 2001 From: Ftps <63702646+Tps-F@users.noreply.github.com> Date: Thu, 16 Mar 2023 12:36:11 +0900 Subject: [PATCH 179/278] Delete settings.json --- .fleet/settings.json | 0 1 file changed, 0 insertions(+), 0 deletions(-) delete mode 100644 .fleet/settings.json diff --git a/.fleet/settings.json b/.fleet/settings.json deleted file mode 100644 index e69de29b..00000000 From 4f415ad639e8ba5274b697bc95af18aae3b32039 Mon Sep 17 00:00:00 2001 From: Nicolas Patry Date: Fri, 17 Mar 2023 09:02:36 +0100 Subject: [PATCH 180/278] Updating safetensors version (fully backward compatible) - Main takeaway is that the newly created files should load better because pointer alignment is forced --- requirements_versions.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/requirements_versions.txt b/requirements_versions.txt index 0031c616..6045aa60 100644 --- a/requirements_versions.txt +++ b/requirements_versions.txt @@ -25,6 +25,6 @@ lark==1.1.2 inflection==0.5.1 GitPython==3.1.30 torchsde==0.2.5 -safetensors==0.2.7 +safetensors==0.3.0 httpcore<=0.15 fastapi==0.94.0 From b9a66b02d0b8ca1c3364d9410198530682fd2f6c Mon Sep 17 00:00:00 2001 From: nonnonstop <42905588+nonnonstop@users.noreply.github.com> Date: Sun, 19 Mar 2023 01:17:04 +0900 Subject: [PATCH 181/278] Fix problem of install.py when data-dir is specified --- launch.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/launch.py b/launch.py index b943fed2..95311d33 100644 --- a/launch.py +++ b/launch.py @@ -14,7 +14,7 @@ parser.add_argument("--data-dir", type=str, default=os.path.dirname(os.path.real args, _ = parser.parse_known_args(sys.argv) script_path = os.path.dirname(__file__) -data_path = os.getcwd() +data_path = args.data_dir dir_repos = "repositories" dir_extensions = "extensions" @@ -231,7 +231,7 @@ def run_extensions_installers(settings_file): return for dirname_extension in list_extensions(settings_file): - run_extension_installer(os.path.join(dir_extensions, dirname_extension)) + run_extension_installer(os.path.join(data_path, dir_extensions, dirname_extension)) def prepare_environment(): From e5dd5d73357715110c18c1ac31711f3a81b84a0c Mon Sep 17 00:00:00 2001 From: whw1sfb Date: Sun, 19 Mar 2023 14:05:01 +0800 Subject: [PATCH 182/278] fix output-html text overflow. --- style.css | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/style.css b/style.css index 3eac2b17..5ead7c5d 100644 --- a/style.css +++ b/style.css @@ -37,7 +37,10 @@ cursor: default; } -.output-html p {margin: 0 0.5em;} +.output-html p { + margin: 0 0.5em; + overflow-wrap: break-word; +} .row > *, .row > .gr-form > * { From cf17dfcd648e56fcfb44342fb057fca359c56127 Mon Sep 17 00:00:00 2001 From: Michael Bachmann Date: Sun, 19 Mar 2023 14:50:44 +0100 Subject: [PATCH 183/278] fixed typo in prompt-bracket-checker.js which leads to js error --- .../prompt-bracket-checker/javascript/prompt-bracket-checker.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/extensions-builtin/prompt-bracket-checker/javascript/prompt-bracket-checker.js b/extensions-builtin/prompt-bracket-checker/javascript/prompt-bracket-checker.js index 4a85c8eb..7bd3f84b 100644 --- a/extensions-builtin/prompt-bracket-checker/javascript/prompt-bracket-checker.js +++ b/extensions-builtin/prompt-bracket-checker/javascript/prompt-bracket-checker.js @@ -105,6 +105,6 @@ var shadowRootLoaded = setInterval(function() { setupBracketChecking('txt2img_prompt', 'txt2img_token_counter') setupBracketChecking('txt2img_neg_prompt', 'txt2img_negative_token_counter') - setupBracketChecking('img2img_prompt', 'imgimg_token_counter') + setupBracketChecking('img2img_prompt', 'img2img_token_counter') setupBracketChecking('img2img_neg_prompt', 'img2img_negative_token_counter') }, 1000); From 64fc936738d296f5eb2ff495006e298c2aeb51bf Mon Sep 17 00:00:00 2001 From: missionfloyd Date: Sun, 19 Mar 2023 19:30:28 -0600 Subject: [PATCH 184/278] Don't bubble when metadata_button is clicked --- 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 cdfd6f2a..10272dbb 100644 --- a/modules/ui_extra_networks.py +++ b/modules/ui_extra_networks.py @@ -127,7 +127,7 @@ class ExtraNetworksPage: metadata_button = "" metadata = item.get("metadata") if metadata: - metadata_onclick = '"' + html.escape(f"""extraNetworksShowMetadata({json.dumps(metadata)}); return false;""") + '"' + metadata_onclick = '"' + html.escape(f"""extraNetworksShowMetadata({json.dumps(metadata)}); event.stopPropagation(); return false;""") + '"' metadata_button = f"" args = { From 8ea8e712c43e493a9c96dcec7dfbc036a8630c97 Mon Sep 17 00:00:00 2001 From: AUTOMATIC <16777216c@gmail.com> Date: Mon, 20 Mar 2023 16:09:36 +0300 Subject: [PATCH 185/278] initial gradio 3.22 support --- .../javascript/prompt-bracket-checker.js | 15 +- javascript/hints.js | 2 +- javascript/imageviewer.js | 62 +- javascript/progressbar.js | 67 +- javascript/ui.js | 3 +- modules/scripts.py | 3 + modules/scripts_postprocessing.py | 2 +- modules/ui.py | 43 +- modules/ui_common.py | 3 +- modules/ui_components.py | 42 +- requirements.txt | 2 +- requirements_versions.txt | 2 +- script.js | 6 +- scripts/postprocessing_upscale.py | 28 +- style.css | 654 +++++------------- 15 files changed, 299 insertions(+), 635 deletions(-) diff --git a/extensions-builtin/prompt-bracket-checker/javascript/prompt-bracket-checker.js b/extensions-builtin/prompt-bracket-checker/javascript/prompt-bracket-checker.js index 4a85c8eb..f0918e26 100644 --- a/extensions-builtin/prompt-bracket-checker/javascript/prompt-bracket-checker.js +++ b/extensions-builtin/prompt-bracket-checker/javascript/prompt-bracket-checker.js @@ -89,22 +89,15 @@ function checkBrackets(evt, textArea, counterElt) { function setupBracketChecking(id_prompt, id_counter){ var textarea = gradioApp().querySelector("#" + id_prompt + " > label > textarea"); var counter = gradioApp().getElementById(id_counter) + textarea.addEventListener("input", function(evt){ checkBrackets(evt, textarea, counter) }); } -var shadowRootLoaded = setInterval(function() { - var shadowRoot = document.querySelector('gradio-app').shadowRoot; - if(! shadowRoot) return false; - - var shadowTextArea = shadowRoot.querySelectorAll('#txt2img_prompt > label > textarea'); - if(shadowTextArea.length < 1) return false; - - clearInterval(shadowRootLoaded); - +onUiLoaded(function(){ setupBracketChecking('txt2img_prompt', 'txt2img_token_counter') setupBracketChecking('txt2img_neg_prompt', 'txt2img_negative_token_counter') - setupBracketChecking('img2img_prompt', 'imgimg_token_counter') + setupBracketChecking('img2img_prompt', 'img2img_token_counter') setupBracketChecking('img2img_neg_prompt', 'img2img_negative_token_counter') -}, 1000); +}) \ No newline at end of file diff --git a/javascript/hints.js b/javascript/hints.js index 7f4101b2..61763e6b 100644 --- a/javascript/hints.js +++ b/javascript/hints.js @@ -18,7 +18,7 @@ titles = { "\u2199\ufe0f": "Read generation parameters from prompt or last generation if prompt is empty into user interface.", "\u{1f4c2}": "Open images output directory", "\u{1f4be}": "Save style", - "\u{1f5d1}": "Clear prompt", + "\u{1f5d1}\ufe0f": "Clear prompt", "\u{1f4cb}": "Apply selected styles to current prompt", "\u{1f4d2}": "Paste available values into the field", "\u{1f3b4}": "Show extra networks", diff --git a/javascript/imageviewer.js b/javascript/imageviewer.js index 28e748b7..7547e771 100644 --- a/javascript/imageviewer.js +++ b/javascript/imageviewer.js @@ -50,7 +50,7 @@ function updateOnBackgroundChange() { } function modalImageSwitch(offset) { - var allgalleryButtons = gradioApp().querySelectorAll(".gallery-item.transition-all") + var allgalleryButtons = gradioApp().querySelectorAll(".gradio-gallery .thumbnail-item") var galleryButtons = [] allgalleryButtons.forEach(function(elem) { if (elem.parentElement.offsetParent) { @@ -59,7 +59,7 @@ function modalImageSwitch(offset) { }) if (galleryButtons.length > 1) { - var allcurrentButtons = gradioApp().querySelectorAll(".gallery-item.transition-all.\\!ring-2") + var allcurrentButtons = gradioApp().querySelectorAll(".gradio-gallery .thumbnail-item.selected") var currentButton = null allcurrentButtons.forEach(function(elem) { if (elem.parentElement.offsetParent) { @@ -136,37 +136,29 @@ function modalKeyHandler(event) { } } -function showGalleryImage() { - setTimeout(function() { - fullImg_preview = gradioApp().querySelectorAll('img.w-full.object-contain') +function setupImageForLightbox(e) { + if (e.dataset.modded) + return; - if (fullImg_preview != null) { - fullImg_preview.forEach(function function_name(e) { - if (e.dataset.modded) - return; - e.dataset.modded = true; - if(e && e.parentElement.tagName == 'DIV'){ - e.style.cursor='pointer' - e.style.userSelect='none' + e.dataset.modded = true; + e.style.cursor='pointer' + e.style.userSelect='none' - var isFirefox = isFirefox = navigator.userAgent.toLowerCase().indexOf('firefox') > -1 + 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' + // For Firefox, listening on click first switched to next image then shows the lightbox. + // If you know how to fix this without switching to mousedown event, please. + // For other browsers the event is click to make it possiblr to drag picture. + var event = isFirefox ? 'mousedown' : 'click' - e.addEventListener(event, function (evt) { - if(!opts.js_modal_lightbox || evt.button != 0) return; - modalZoomSet(gradioApp().getElementById('modalImage'), opts.js_modal_lightbox_initially_zoomed) - evt.preventDefault() - showModal(evt) - }, true); - } - }); - } + e.addEventListener(event, function (evt) { + if(!opts.js_modal_lightbox || evt.button != 0) return; + + modalZoomSet(gradioApp().getElementById('modalImage'), opts.js_modal_lightbox_initially_zoomed) + evt.preventDefault() + showModal(evt) + }, true); - }, 100); } function modalZoomSet(modalImage, enable) { @@ -199,21 +191,21 @@ function modalTileImageToggle(event) { } function galleryImageHandler(e) { - if (e && e.parentElement.tagName == 'BUTTON') { + //if (e && e.parentElement.tagName == 'BUTTON') { e.onclick = showGalleryImage; - } + //} } onUiUpdate(function() { - fullImg_preview = gradioApp().querySelectorAll('img.w-full') + fullImg_preview = gradioApp().querySelectorAll('.gradio-gallery > div > img') if (fullImg_preview != null) { - fullImg_preview.forEach(galleryImageHandler); + fullImg_preview.forEach(setupImageForLightbox); } updateOnBackgroundChange(); }) document.addEventListener("DOMContentLoaded", function() { - const modalFragment = document.createDocumentFragment(); + //const modalFragment = document.createDocumentFragment(); const modal = document.createElement('div') modal.onclick = closeModal; modal.id = "lightboxModal"; @@ -277,9 +269,9 @@ document.addEventListener("DOMContentLoaded", function() { modal.appendChild(modalNext) + gradioApp().appendChild(modal) - gradioApp().getRootNode().appendChild(modal) - document.body.appendChild(modalFragment); + document.body.appendChild(modal); }); diff --git a/javascript/progressbar.js b/javascript/progressbar.js index 9ccc9da4..4ac9b8db 100644 --- a/javascript/progressbar.js +++ b/javascript/progressbar.js @@ -1,78 +1,13 @@ // code related to showing and updating progressbar shown as the image is being made - -galleries = {} -storedGallerySelections = {} -galleryObservers = {} - function rememberGallerySelection(id_gallery){ - storedGallerySelections[id_gallery] = getGallerySelectedIndex(id_gallery) + } function getGallerySelectedIndex(id_gallery){ - let galleryButtons = gradioApp().querySelectorAll('#'+id_gallery+' .gallery-item') - let galleryBtnSelected = gradioApp().querySelector('#'+id_gallery+' .gallery-item.\\!ring-2') - let currentlySelectedIndex = -1 - galleryButtons.forEach(function(v, i){ if(v==galleryBtnSelected) { currentlySelectedIndex = i } }) - - return currentlySelectedIndex } -// this is a workaround for https://github.com/gradio-app/gradio/issues/2984 -function check_gallery(id_gallery){ - let gallery = gradioApp().getElementById(id_gallery) - // if gallery has no change, no need to setting up observer again. - if (gallery && galleries[id_gallery] !== gallery){ - galleries[id_gallery] = gallery; - if(galleryObservers[id_gallery]){ - galleryObservers[id_gallery].disconnect(); - } - - storedGallerySelections[id_gallery] = -1 - - galleryObservers[id_gallery] = new MutationObserver(function (){ - let galleryButtons = gradioApp().querySelectorAll('#'+id_gallery+' .gallery-item') - let galleryBtnSelected = gradioApp().querySelector('#'+id_gallery+' .gallery-item.\\!ring-2') - let currentlySelectedIndex = getGallerySelectedIndex(id_gallery) - prevSelectedIndex = storedGallerySelections[id_gallery] - storedGallerySelections[id_gallery] = -1 - - if (prevSelectedIndex !== -1 && galleryButtons.length>prevSelectedIndex && !galleryBtnSelected) { - // automatically re-open previously selected index (if exists) - activeElement = gradioApp().activeElement; - let scrollX = window.scrollX; - let scrollY = window.scrollY; - - galleryButtons[prevSelectedIndex].click(); - showGalleryImage(); - - // When the gallery button is clicked, it gains focus and scrolls itself into view - // We need to scroll back to the previous position - setTimeout(function (){ - window.scrollTo(scrollX, scrollY); - }, 50); - - if(activeElement){ - // i fought this for about an hour; i don't know why the focus is lost or why this helps recover it - // if someone has a better solution please by all means - setTimeout(function (){ - activeElement.focus({ - preventScroll: true // Refocus the element that was focused before the gallery was opened without scrolling to it - }) - }, 1); - } - } - }) - galleryObservers[id_gallery].observe( gallery, { childList:true, subtree:false }) - } -} - -onUiUpdate(function(){ - check_gallery('txt2img_gallery') - check_gallery('img2img_gallery') -}) - function request(url, data, handler, errorHandler){ var xhr = new XMLHttpRequest(); var url = url; diff --git a/javascript/ui.js b/javascript/ui.js index b7a8268a..fcaf5608 100644 --- a/javascript/ui.js +++ b/javascript/ui.js @@ -86,7 +86,7 @@ function get_tab_index(tabId){ var res = 0 gradioApp().getElementById(tabId).querySelector('div').querySelectorAll('button').forEach(function(button, i){ - if(button.className.indexOf('bg-white') != -1) + if(button.className.indexOf('selected') != -1) res = i }) @@ -255,7 +255,6 @@ onUiUpdate(function(){ } prompt.parentElement.insertBefore(counter, prompt) - counter.classList.add("token-counter") prompt.parentElement.style.position = "relative" promptTokecountUpdateFuncs[id] = function(){ update_token_counter(id_button); } diff --git a/modules/scripts.py b/modules/scripts.py index 8de19884..40d8dcc6 100644 --- a/modules/scripts.py +++ b/modules/scripts.py @@ -521,6 +521,9 @@ def IOComponent_init(self, *args, **kwargs): res = original_IOComponent_init(self, *args, **kwargs) + # this adds gradio-* to every component for css styling (ie gradio-button to gr.Button) + self.elem_classes = ["gradio-" + self.get_block_name(), *(self.elem_classes or [])] + script_callbacks.after_component_callback(self, **kwargs) if scripts_current is not None: diff --git a/modules/scripts_postprocessing.py b/modules/scripts_postprocessing.py index ce0ebb61..b11568c0 100644 --- a/modules/scripts_postprocessing.py +++ b/modules/scripts_postprocessing.py @@ -109,7 +109,7 @@ class ScriptPostprocessingRunner: inputs = [] for script in self.scripts_in_preferred_order(): - with gr.Box() as group: + with gr.Row() as group: self.create_script_ui(script, inputs) script.group = group diff --git a/modules/ui.py b/modules/ui.py index 7e603332..80807ce3 100644 --- a/modules/ui.py +++ b/modules/ui.py @@ -20,7 +20,7 @@ from PIL import Image, PngImagePlugin from modules.call_queue import wrap_gradio_gpu_call, wrap_queued_call, wrap_gradio_call from modules import sd_hijack, sd_models, localization, script_callbacks, ui_extensions, deepbooru, sd_vae, extra_networks, postprocessing, ui_components, ui_common, ui_postprocessing -from modules.ui_components import FormRow, FormGroup, ToolButton, FormHTML +from modules.ui_components import FormRow, FormColumn, FormGroup, ToolButton, FormHTML from modules.paths import script_path, data_path from modules.shared import opts, cmd_opts, restricted_opts @@ -89,7 +89,7 @@ paste_symbol = '\u2199\ufe0f' # ↙ refresh_symbol = '\U0001f504' # 🔄 save_style_symbol = '\U0001f4be' # 💾 apply_style_symbol = '\U0001f4cb' # 📋 -clear_prompt_symbol = '\U0001F5D1' # 🗑️ +clear_prompt_symbol = '\U0001f5d1\ufe0f' # 🗑️ extra_networks_symbol = '\U0001F3B4' # 🎴 switch_values_symbol = '\U000021C5' # ⇅ @@ -179,14 +179,13 @@ def interrogate_deepbooru(image): def create_seed_inputs(target_interface): - with FormRow(elem_id=target_interface + '_seed_row'): + with FormRow(elem_id=target_interface + '_seed_row', variant="compact"): seed = (gr.Textbox if cmd_opts.use_textbox_seed else gr.Number)(label='Seed', value=-1, elem_id=target_interface + '_seed') seed.style(container=False) - random_seed = gr.Button(random_symbol, elem_id=target_interface + '_random_seed') - reuse_seed = gr.Button(reuse_symbol, elem_id=target_interface + '_reuse_seed') + random_seed = ToolButton(random_symbol, elem_id=target_interface + '_random_seed') + reuse_seed = ToolButton(reuse_symbol, elem_id=target_interface + '_reuse_seed') - with gr.Group(elem_id=target_interface + '_subseed_show_box'): - seed_checkbox = gr.Checkbox(label='Extra', elem_id=target_interface + '_subseed_show', value=False) + seed_checkbox = gr.Checkbox(label='Extra', elem_id=target_interface + '_subseed_show', value=False) # Components to show/hide based on the 'Extra' checkbox seed_extras = [] @@ -195,8 +194,8 @@ def create_seed_inputs(target_interface): seed_extras.append(seed_extra_row_1) subseed = gr.Number(label='Variation seed', value=-1, elem_id=target_interface + '_subseed') subseed.style(container=False) - random_subseed = gr.Button(random_symbol, elem_id=target_interface + '_random_subseed') - reuse_subseed = gr.Button(reuse_symbol, elem_id=target_interface + '_reuse_subseed') + random_subseed = ToolButton(random_symbol, elem_id=target_interface + '_random_subseed') + reuse_subseed = ToolButton(reuse_symbol, elem_id=target_interface + '_reuse_subseed') subseed_strength = gr.Slider(label='Variation strength', value=0.0, minimum=0, maximum=1, step=0.01, elem_id=target_interface + '_subseed_strength') with FormRow(visible=False) as seed_extra_row_2: @@ -291,19 +290,19 @@ def create_toprow(is_img2img): with gr.Row(): with gr.Column(scale=80): with gr.Row(): - negative_prompt = gr.Textbox(label="Negative prompt", elem_id=f"{id_part}_neg_prompt", show_label=False, lines=2, placeholder="Negative prompt (press Ctrl+Enter or Alt+Enter to generate)") + negative_prompt = gr.Textbox(label="Negative prompt", elem_id=f"{id_part}_neg_prompt", show_label=False, lines=3, placeholder="Negative prompt (press Ctrl+Enter or Alt+Enter to generate)") button_interrogate = None button_deepbooru = None if is_img2img: - with gr.Column(scale=1, elem_id="interrogate_col"): + with gr.Column(scale=1, elem_classes="interrogate-col"): button_interrogate = gr.Button('Interrogate\nCLIP', elem_id="interrogate") button_deepbooru = gr.Button('Interrogate\nDeepBooru', elem_id="deepbooru") with gr.Column(scale=1, elem_id=f"{id_part}_actions_column"): - with gr.Row(elem_id=f"{id_part}_generate_box"): - interrupt = gr.Button('Interrupt', elem_id=f"{id_part}_interrupt") - skip = gr.Button('Skip', elem_id=f"{id_part}_skip") + with gr.Row(elem_id=f"{id_part}_generate_box", elem_classes="generate-box"): + interrupt = gr.Button('Interrupt', elem_id=f"{id_part}_interrupt", elem_classes="generate-box-interrupt") + skip = gr.Button('Skip', elem_id=f"{id_part}_skip", elem_classes="generate-box-skip") submit = gr.Button('Generate', elem_id=f"{id_part}_generate", variant='primary') skip.click( @@ -325,9 +324,9 @@ def create_toprow(is_img2img): prompt_style_apply = ToolButton(value=apply_style_symbol, elem_id=f"{id_part}_style_apply") save_style = ToolButton(value=save_style_symbol, elem_id=f"{id_part}_style_create") - token_counter = gr.HTML(value="", elem_id=f"{id_part}_token_counter") + token_counter = gr.HTML(value="0/75", elem_id=f"{id_part}_token_counter", elem_classes=["token-counter"]) token_button = gr.Button(visible=False, elem_id=f"{id_part}_token_button") - negative_token_counter = gr.HTML(value="", elem_id=f"{id_part}_negative_token_counter") + negative_token_counter = gr.HTML(value="0/75", elem_id=f"{id_part}_negative_token_counter", elem_classes=["token-counter"]) negative_token_button = gr.Button(visible=False, elem_id=f"{id_part}_negative_token_button") clear_prompt_button.click( @@ -479,7 +478,9 @@ def create_ui(): width = gr.Slider(minimum=64, maximum=2048, step=8, label="Width", value=512, elem_id="txt2img_width") height = gr.Slider(minimum=64, maximum=2048, step=8, label="Height", value=512, elem_id="txt2img_height") - res_switch_btn = ToolButton(value=switch_values_symbol, elem_id="txt2img_res_switch_btn") + with gr.Column(elem_id="txt2img_dimensions_row", scale=1): + res_switch_btn = ToolButton(value=switch_values_symbol, elem_id="txt2img_res_switch_btn") + if opts.dimensions_and_batch_together: with gr.Column(elem_id="txt2img_column_batch"): batch_count = gr.Slider(minimum=1, step=1, label='Batch count', value=1, elem_id="txt2img_batch_count") @@ -492,7 +493,7 @@ def create_ui(): seed, reuse_seed, subseed, reuse_subseed, subseed_strength, seed_resize_from_h, seed_resize_from_w, seed_checkbox = create_seed_inputs('txt2img') elif category == "checkboxes": - with FormRow(elem_id="txt2img_checkboxes", variant="compact"): + with FormRow(elem_classes="checkboxes-row", variant="compact"): restore_faces = gr.Checkbox(label='Restore faces', value=False, visible=len(shared.face_restorers) > 1, elem_id="txt2img_restore_faces") tiling = gr.Checkbox(label='Tiling', value=False, elem_id="txt2img_tiling") enable_hr = gr.Checkbox(label='Hires. fix', value=False, elem_id="txt2img_enable_hr") @@ -757,7 +758,9 @@ def create_ui(): width = gr.Slider(minimum=64, maximum=2048, step=8, label="Width", value=512, elem_id="img2img_width") height = gr.Slider(minimum=64, maximum=2048, step=8, label="Height", value=512, elem_id="img2img_height") - res_switch_btn = ToolButton(value=switch_values_symbol, elem_id="img2img_res_switch_btn") + with gr.Column(elem_id="img2img_dimensions_row", scale=1): + res_switch_btn = ToolButton(value=switch_values_symbol, elem_id="img2img_res_switch_btn") + if opts.dimensions_and_batch_together: with gr.Column(elem_id="img2img_column_batch"): batch_count = gr.Slider(minimum=1, step=1, label='Batch count', value=1, elem_id="img2img_batch_count") @@ -774,7 +777,7 @@ def create_ui(): seed, reuse_seed, subseed, reuse_subseed, subseed_strength, seed_resize_from_h, seed_resize_from_w, seed_checkbox = create_seed_inputs('img2img') elif category == "checkboxes": - with FormRow(elem_id="img2img_checkboxes", variant="compact"): + with FormRow(elem_classes="checkboxes-row", variant="compact"): restore_faces = gr.Checkbox(label='Restore faces', value=False, visible=len(shared.face_restorers) > 1, elem_id="img2img_restore_faces") tiling = gr.Checkbox(label='Tiling', value=False, elem_id="img2img_tiling") diff --git a/modules/ui_common.py b/modules/ui_common.py index a12433d2..d4e00829 100644 --- a/modules/ui_common.py +++ b/modules/ui_common.py @@ -130,7 +130,7 @@ Requested path was: {f} generation_info = None with gr.Column(): with gr.Row(elem_id=f"image_buttons_{tabname}"): - open_folder_button = gr.Button(folder_symbol, elem_id="hidden_element" if shared.cmd_opts.hide_ui_dir_config else f'open_folder_{tabname}') + open_folder_button = gr.Button(folder_symbol, visible=not shared.cmd_opts.hide_ui_dir_config) if tabname != "extras": save = gr.Button('Save', elem_id=f'save_{tabname}') @@ -160,6 +160,7 @@ Requested path was: {f} _js="function(x, y, z){ return [x, y, selected_gallery_index()] }", inputs=[generation_info, html_info, html_info], outputs=[html_info, html_info], + show_progress=False, ) save.click( diff --git a/modules/ui_components.py b/modules/ui_components.py index 284ca0cf..2b1da2cb 100644 --- a/modules/ui_components.py +++ b/modules/ui_components.py @@ -1,55 +1,61 @@ import gradio as gr -class ToolButton(gr.Button, gr.components.FormComponent): +class FormComponent: + def get_expected_parent(self): + return gr.components.Form + + +gr.Dropdown.get_expected_parent = FormComponent.get_expected_parent + + +class ToolButton(FormComponent, gr.Button): """Small button with single emoji as text, fits inside gradio forms""" - def __init__(self, **kwargs): - super().__init__(variant="tool", **kwargs) + def __init__(self, *args, **kwargs): + classes = kwargs.pop("elem_classes", []) + super().__init__(*args, elem_classes=["tool", *classes], **kwargs) def get_block_name(self): return "button" -class ToolButtonTop(gr.Button, gr.components.FormComponent): - """Small button with single emoji as text, with extra margin at top, fits inside gradio forms""" - - def __init__(self, **kwargs): - super().__init__(variant="tool-top", **kwargs) - - def get_block_name(self): - return "button" - - -class FormRow(gr.Row, gr.components.FormComponent): +class FormRow(FormComponent, gr.Row): """Same as gr.Row but fits inside gradio forms""" def get_block_name(self): return "row" -class FormGroup(gr.Group, gr.components.FormComponent): +class FormColumn(FormComponent, gr.Column): + """Same as gr.Column but fits inside gradio forms""" + + def get_block_name(self): + return "column" + + +class FormGroup(FormComponent, gr.Group): """Same as gr.Row but fits inside gradio forms""" def get_block_name(self): return "group" -class FormHTML(gr.HTML, gr.components.FormComponent): +class FormHTML(FormComponent, gr.HTML): """Same as gr.HTML but fits inside gradio forms""" def get_block_name(self): return "html" -class FormColorPicker(gr.ColorPicker, gr.components.FormComponent): +class FormColorPicker(FormComponent, gr.ColorPicker): """Same as gr.ColorPicker but fits inside gradio forms""" def get_block_name(self): return "colorpicker" -class DropdownMulti(gr.Dropdown): +class DropdownMulti(FormComponent, gr.Dropdown): """Same as gr.Dropdown but always multiselect""" def __init__(self, **kwargs): super().__init__(multiselect=True, **kwargs) diff --git a/requirements.txt b/requirements.txt index 6d53f089..e71251c5 100644 --- a/requirements.txt +++ b/requirements.txt @@ -4,7 +4,7 @@ basicsr fonts font-roboto gfpgan -gradio==3.16.2 +gradio==3.22.1 invisible-watermark numpy omegaconf diff --git a/requirements_versions.txt b/requirements_versions.txt index 0031c616..ab16e4cc 100644 --- a/requirements_versions.txt +++ b/requirements_versions.txt @@ -3,7 +3,7 @@ transformers==4.25.1 accelerate==0.12.0 basicsr==1.4.2 gfpgan==1.3.8 -gradio==3.16.2 +gradio==3.22.1 numpy==1.23.3 Pillow==9.4.0 realesrgan==0.3.0 diff --git a/script.js b/script.js index 97e0bfcf..978b948f 100644 --- a/script.js +++ b/script.js @@ -1,7 +1,9 @@ function gradioApp() { const elems = document.getElementsByTagName('gradio-app') - const gradioShadowRoot = elems.length == 0 ? null : elems[0].shadowRoot - return !!gradioShadowRoot ? gradioShadowRoot : document; + const elem = elems.length == 0 ? document : elems[0] + + elem.getElementById = function(id){ return document.getElementById(id) } + return elem.shadowRoot ? elem.shadowRoot : elem } function get_uiCurrentTab() { diff --git a/scripts/postprocessing_upscale.py b/scripts/postprocessing_upscale.py index 8842bd91..11eab31a 100644 --- a/scripts/postprocessing_upscale.py +++ b/scripts/postprocessing_upscale.py @@ -17,22 +17,24 @@ class ScriptPostprocessingUpscale(scripts_postprocessing.ScriptPostprocessing): def ui(self): selected_tab = gr.State(value=0) - 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") + with gr.Column(): + 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") - with gr.TabItem('Scale to', elem_id="extras_scale_to_tab") as tab_scale_to: - with FormRow(): - upscaling_resize_w = gr.Number(label="Width", value=512, precision=0, elem_id="extras_upscaling_resize_w") - upscaling_resize_h = gr.Number(label="Height", value=512, precision=0, elem_id="extras_upscaling_resize_h") - upscaling_crop = gr.Checkbox(label='Crop to fit', value=True, elem_id="extras_upscaling_crop") + with gr.TabItem('Scale to', elem_id="extras_scale_to_tab") as tab_scale_to: + with FormRow(): + upscaling_resize_w = gr.Number(label="Width", value=512, precision=0, elem_id="extras_upscaling_resize_w") + upscaling_resize_h = gr.Number(label="Height", value=512, precision=0, elem_id="extras_upscaling_resize_h") + 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_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(): + 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") tab_scale_by.select(fn=lambda: 0, inputs=[], outputs=[selected_tab]) tab_scale_to.select(fn=lambda: 1, inputs=[], outputs=[selected_tab]) diff --git a/style.css b/style.css index 3eac2b17..936930fd 100644 --- a/style.css +++ b/style.css @@ -1,48 +1,139 @@ -.container { - max-width: 100%; + +/* general gradio fixes */ + +:root{ + --checkbox-label-gap: 0.25em 0.1em; + --section-header-text-size: 12pt; } -.token-counter{ +.block.padded{ + padding: 0.2em 0.5em !important; +} + +div.gradio-container{ + max-width: unset !important; +} + +.hidden{ + display: none; +} + +.compact{ + background: transparent !important; + padding: 0 !important; +} + +div.form{ + border-width: 0; + box-shadow: none; + background: transparent; + overflow: visible; + gap: 0.5em; +} + +.block.gradio-dropdown, +.block.gradio-slider, +.block.gradio-checkbox, +.block.gradio-textbox, +.block.gradio-radio, +.block.gradio-checkboxgroup, +.block.gradio-number +{ + border-width: 0 !important; + box-shadow: none !important; +} + +.gap.compact{ + padding: 0; + gap: 0; +} + +div.compact{ + gap: 0.5em; +} + +.gradio-dropdown ul.options{ + max-height: 35em; +} + +.gradio-dropdown label span:not(.has-info){ + margin-bottom: 0; +} + +.gradio-dropdown div.wrap.wrap.wrap.wrap{ + box-shadow: 0 1px 2px 0 rgba(0, 0, 0, 0.05); +} + +.gradio-slider input[type="number"]{ + width: 6em; +} + +.block.gradio-checkbox { + margin: 0.75em 1.5em 0 0; +} + +/* general styled components */ + +.gradio-button.tool{ + max-width: 2.2em; + min-width: 2.2em !important; + height: 2.4em; + align-self: end; + line-height: 1em; + border-radius: 0.5em; +} + +.checkboxes-row{ + margin-bottom: 0.5em; + margin-left: 0em; +} +.checkboxes-row > div{ + flex: 0; + white-space: nowrap; + min-width: auto; +} + + +/* txt2img/img2img specific */ + +.block.token-counter{ position: absolute; display: inline-block; - right: 2em; + right: 1em; min-width: 0 !important; width: auto; z-index: 100; } -.token-counter.error span{ +.block.token-counter span{ + background: var(--input-background-fill) !important; + box-shadow: 0 0 0.0 0.3em rgba(192,192,192,0.15), inset 0 0 0.6em rgba(192,192,192,0.075); + border: 2px solid rgba(192,192,192,0.4) !important; + border-radius: 0.4em; +} + +.block.token-counter.error span{ box-shadow: 0 0 0.0 0.3em rgba(255,0,0,0.15), inset 0 0 0.6em rgba(255,0,0,0.075); border: 2px solid rgba(255,0,0,0.4) !important; } -.token-counter div{ +.block.token-counter div{ display: inline; } -.token-counter span{ +.block.token-counter span{ padding: 0.1em 0.75em; } -#sh{ - min-width: 2em; - min-height: 2em; - max-width: 2em; - max-height: 2em; - flex-grow: 0; - padding-left: 0.25em; - padding-right: 0.25em; - margin: 0.1em 0; - opacity: 0%; - cursor: default; +[id$=_subseed_show]{ + min-width: auto !important; + flex-grow: 0 !important; + display: flex; } -.output-html p {margin: 0 0.5em;} - -.row > *, -.row > .gr-form > * { - min-width: min(120px, 100%); - flex: 1 1 0%; +[id$=_subseed_show] label{ + margin-bottom: 0.5em; + align-self: end; } .performance { @@ -75,196 +166,94 @@ object-fit: scale-down; } #txt2img_actions_column, #img2img_actions_column { - margin: 0.35rem 0.75rem 0.35rem 0; + gap: 0.5em; } -#script_list { - padding: .625rem .75rem 0 .625rem; -} -.justify-center.overflow-x-scroll { - justify-content: left; -} - -.justify-center.overflow-x-scroll button:first-of-type { - margin-left: auto; -} - -.justify-center.overflow-x-scroll button:last-of-type { - margin-right: auto; -} - -[id$=_random_seed], [id$=_random_subseed], [id$=_reuse_seed], [id$=_reuse_subseed], #open_folder{ - min-width: 2.3em; - height: 2.5em; - flex-grow: 0; - padding-left: 0.25em; - padding-right: 0.25em; -} - -#hidden_element{ - display: none; -} - -[id$=_seed_row], [id$=_subseed_row]{ - gap: 0.5rem; - padding: 0.6em; -} - -[id$=_subseed_show_box]{ - min-width: auto; - flex-grow: 0; -} - -[id$=_subseed_show_box] > div{ - border: 0; - height: 100%; -} - -[id$=_subseed_show]{ - min-width: auto; - flex-grow: 0; - padding: 0; -} - -[id$=_subseed_show] label{ - height: 100%; -} - -#txt2img_actions_column, #img2img_actions_column{ - gap: 0; - margin-right: .75rem; -} - #txt2img_tools, #img2img_tools{ gap: 0.4em; } -#interrogate_col{ +.interrogate-col{ min-width: 0 !important; - max-width: 8em !important; - margin-right: 1em; - gap: 0; + max-width: fit-content; + gap: 0.5em; } -#interrogate, #deepbooru{ - margin: 0em 0.25em 0.5em 0.25em; +.interrogate-col > button{ min-width: 8em; max-width: 8em; + height: 5.45em; } -#style_pos_col, #style_neg_col{ - min-width: 8em !important; +.generate-box{ + position: relative; } - -#txt2img_styles_row, #img2img_styles_row{ - gap: 0.25em; - margin-top: 0.3em; -} - -#txt2img_styles_row > button, #img2img_styles_row > button{ - margin: 0; -} - -#txt2img_styles, #img2img_styles{ - padding: 0; -} - -#txt2img_styles > label > div, #img2img_styles > label > div{ - min-height: 3.2em; -} - -ul.list-none{ - max-height: 35em; - z-index: 2000; -} - -.gr-form{ - background: transparent; -} - -.my-4{ - margin-top: 0; - margin-bottom: 0; -} - -#resize_mode{ - flex: 1.5; -} - -button{ - align-self: stretch !important; -} - -.overflow-hidden, .gr-panel{ - overflow: visible !important; -} - -#x_type, #y_type{ - max-width: 10em; -} - -#txt2img_preview, #img2img_preview, #ti_preview{ +.gradio-button.generate-box-skip, .gradio-button.generate-box-interrupt{ position: absolute; - width: 320px; - left: 0; - right: 0; - margin-left: auto; - margin-right: auto; - margin-top: 34px; - z-index: 100; - border: none; - border-top-left-radius: 0; - border-top-right-radius: 0; -} - -@media screen and (min-width: 768px) { - #txt2img_preview, #img2img_preview, #ti_preview { - position: absolute; - } -} - -@media screen and (max-width: 767px) { - #txt2img_preview, #img2img_preview, #ti_preview { - position: relative; - } -} - -#txt2img_preview div.left-0.top-0, #img2img_preview div.left-0.top-0, #ti_preview div.left-0.top-0{ + width: 50%; + height: 100%; display: none; } - -fieldset span.text-gray-500, .gr-block.gr-box span.text-gray-500, label.block span{ - position: absolute; - top: -0.7em; - line-height: 1.2em; - padding: 0; - margin: 0 0.5em; - - background-color: white; - box-shadow: 6px 0 6px 0px white, -6px 0 6px 0px white; - - z-index: 300; +.gradio-button.generate-box-interrupt{ + left: 0; + border-radius: 0.5rem 0 0 0.5rem; +} +.gradio-button.generate-box-skip{ + right: 0; + border-radius: 0 0.5rem 0.5rem 0; } -.dark fieldset span.text-gray-500, .dark .gr-block.gr-box span.text-gray-500, .dark label.block span{ - background-color: rgb(31, 41, 55); - box-shadow: none; - border: 1px solid rgba(128, 128, 128, 0.1); - border-radius: 6px; - padding: 0.1em 0.5em; +#txtimg_hr_finalres{ + min-height: 0 !important; + padding: .625rem .75rem; + margin-left: -0.75em } -#txt2img_column_batch, #img2img_column_batch{ +#txtimg_hr_finalres .resolution{ + font-weight: bold; +} + +.inactive{ + opacity: 0.5; +} + +[id$=_column_batch]{ min-width: min(13.5em, 100%) !important; } -#settings fieldset span.text-gray-500, #settings .gr-block.gr-box span.text-gray-500, #settings label.block span{ - position: relative; - border: none; - margin-right: 8em; +[id$=_dimensions_row]{ + min-width: 0 !important; + max-width: fit-content; + padding: 0 1em; } -#settings .gr-panel div.flex-col div.justify-between div{ - position: relative; - z-index: 200; +#mode_img2img .gradio-image > div.fixed-height, #mode_img2img .gradio-image > div.fixed-height img{ + height: 480px !important; + max-height: 480px !important; + min-height: 480px !important; +} + + +/* settings */ +#quicksettings { + width: fit-content; +} + +#quicksettings > div, #quicksettings > fieldset{ + max-width: 24em; + min-width: 24em; + padding: 0; + border: none; + box-shadow: none; + background: none; + margin-right: 10px; +} + +#quicksettings .gradio-dropdown .wrap-inner{ + flex-wrap: unset; +} + +#quicksettings .gradio-dropdown .single-select{ + white-space: nowrap; + overflow: hidden; } #settings{ @@ -276,14 +265,14 @@ fieldset span.text-gray-500, .gr-block.gr-box span.text-gray-500, label.block s margin-left: 10em; } -#settings > div.flex-wrap{ +#settings > div.tab-nav{ float: left; display: block; margin-left: 0; width: 10em; } -#settings > div.flex-wrap button{ +#settings > div.tab-nav button{ display: block; border: none; text-align: left; @@ -294,29 +283,8 @@ fieldset span.text-gray-500, .gr-block.gr-box span.text-gray-500, label.block s margin: 0 1.2em; } -input[type="range"]{ - margin: 0.5em 0 -0.3em 0; -} - -#mask_bug_info { - text-align: center; - display: block; - margin-top: -0.75em; - margin-bottom: -0.75em; -} - -#txt2img_negative_prompt, #img2img_negative_prompt{ -} - -/* gradio 3.8 adds opacity to progressbar which makes it blink; disable it here */ -.transition.opacity-20 { - opacity: 1 !important; -} - -/* more gradio's garbage cleanup */ -.min-h-\[4rem\] { min-height: unset !important; } -.min-h-\[6rem\] { min-height: unset !important; } +/* live preview */ .progressDiv{ position: relative; height: 20px; @@ -362,6 +330,8 @@ input[type="range"]{ height: 100%; } +/* fullscreen popup (ie in Lora's (i) button) */ + .popup-metadata{ color: black; background: white; @@ -402,6 +372,8 @@ input[type="range"]{ padding: 2em; } +/* fullpage image viewer */ + #lightboxModal{ display: none; position: fixed; @@ -512,45 +484,7 @@ input[type="range"]{ background-color: rgba(0, 0, 0, 0.8); } -#imageARPreview{ - position:absolute; - top:0px; - left:0px; - border:2px solid red; - background:rgba(255, 0, 0, 0.3); - z-index: 900; - pointer-events:none; - display:none -} - -#txt2img_generate_box, #img2img_generate_box{ - position: relative; -} - -#txt2img_interrupt, #img2img_interrupt, #txt2img_skip, #img2img_skip{ - position: absolute; - width: 50%; - height: 100%; - background: #b4c0cc; - display: none; -} - -#txt2img_interrupt, #img2img_interrupt{ - left: 0; - border-radius: 0.5rem 0 0 0.5rem; -} -#txt2img_skip, #img2img_skip{ - right: 0; - border-radius: 0 0.5rem 0.5rem 0; -} - -.red { - color: red; -} - -.gallery-item { - --tw-bg-opacity: 0 !important; -} +/* context menu (ie for the generate button) */ #context-menu{ z-index:9999; @@ -579,61 +513,8 @@ input[type="range"]{ background: #a55000; } -#quicksettings { - width: fit-content; -} -#quicksettings > div, #quicksettings > fieldset{ - max-width: 24em; - min-width: 24em; - padding: 0; - border: none; - box-shadow: none; - background: none; - margin-right: 10px; -} - -#quicksettings > div > div > div > label > span { - position: relative; - margin-right: 9em; - margin-bottom: -1em; -} - -canvas[key="mask"] { - z-index: 12 !important; - filter: invert(); - mix-blend-mode: multiply; - pointer-events: none; -} - - -/* gradio 3.4.1 stuff for editable scrollbar values */ -.gr-box > div > div > input.gr-text-input{ - position: absolute; - right: 0.5em; - top: -0.6em; - z-index: 400; - width: 6em; -} -#quicksettings .gr-box > div > div > input.gr-text-input { - top: -1.12em; -} - -.row.gr-compact{ - overflow: visible; -} - -#img2img_image, #img2img_image > .h-60, #img2img_image > .h-60 > div, #img2img_image > .h-60 > div > img, -#img2img_sketch, #img2img_sketch > .h-60, #img2img_sketch > .h-60 > div, #img2img_sketch > .h-60 > div > img, -#img2maskimg, #img2maskimg > .h-60, #img2maskimg > .h-60 > div, #img2maskimg > .h-60 > div > img, -#inpaint_sketch, #inpaint_sketch > .h-60, #inpaint_sketch > .h-60 > div, #inpaint_sketch > .h-60 > div > img -{ - height: 480px !important; - max-height: 480px !important; - min-height: 480px !important; -} - -/* Extensions */ +/* extensions */ #tab_extensions table{ border-collapse: collapse; @@ -646,6 +527,7 @@ canvas[key="mask"] { #tab_extensions table input[type="checkbox"]{ margin-right: 0.5em; + appearance: checkbox; } #tab_extensions button{ @@ -670,74 +552,7 @@ canvas[key="mask"] { font-size: 90%; } -#image_buttons_txt2img button, #image_buttons_img2img button, #image_buttons_extras button{ - min-width: auto; - padding-left: 0.5em; - padding-right: 0.5em; -} - -.gr-form{ - background-color: white; -} - -.dark .gr-form{ - background-color: rgb(31 41 55 / var(--tw-bg-opacity)); -} - -.gr-button-tool, .gr-button-tool-top{ - max-width: 2.5em; - min-width: 2.5em !important; - height: 2.4em; -} - -.gr-button-tool{ - margin: 0.6em 0em 0.55em 0; -} - -.gr-button-tool-top, #settings .gr-button-tool{ - margin: 1.6em 0.7em 0.55em 0; -} - - -#modelmerger_results_container{ - margin-top: 1em; - overflow: visible; -} - -#modelmerger_models{ - gap: 0; -} - - -#quicksettings .gr-button-tool{ - margin: 0; - border-color: unset; - background-color: unset; -} - -#modelmerger_interp_description>p { - margin: 0!important; - text-align: center; -} -#modelmerger_interp_description { - margin: 0.35rem 0.75rem 1.23rem; -} -#img2img_settings > div.gr-form, #txt2img_settings > div.gr-form { - padding-top: 0.9em; - padding-bottom: 0.9em; -} -#txt2img_settings { - padding-top: 1.16em; - padding-bottom: 0.9em; -} -#img2img_settings { - padding-bottom: 0.9em; -} - -#img2img_settings div.gr-form .gr-form, #txt2img_settings div.gr-form .gr-form, #train_tabs div.gr-form .gr-form{ - border: none; - padding-bottom: 0.5em; -} +/* replace original footer with ours */ footer { display: none !important; @@ -756,90 +571,7 @@ footer { opacity: 0.85; } -#txtimg_hr_finalres{ - min-height: 0 !important; - padding: .625rem .75rem; - margin-left: -0.75em - -} - -#txtimg_hr_finalres .resolution{ - font-weight: bold; -} - -#txt2img_checkboxes, #img2img_checkboxes{ - margin-bottom: 0.5em; - margin-left: 0em; -} -#txt2img_checkboxes > div, #img2img_checkboxes > div{ - flex: 0; - white-space: nowrap; - min-width: auto; -} - -#img2img_copy_to_img2img, #img2img_copy_to_sketch, #img2img_copy_to_inpaint, #img2img_copy_to_inpaint_sketch{ - margin-left: 0em; -} - -#axis_options { - margin-left: 0em; -} - -.inactive{ - opacity: 0.5; -} - -[id*='_prompt_container']{ - gap: 0; -} - -[id*='_prompt_container'] > div{ - margin: -0.4em 0 0 0; -} - -.gr-compact { - border: none; -} - -.dark .gr-compact{ - background-color: rgb(31 41 55 / var(--tw-bg-opacity)); - margin-left: 0; -} - -.gr-compact{ - overflow: visible; -} - -.gr-compact > *{ -} - -.gr-compact .gr-block, .gr-compact .gr-form{ - border: none; - box-shadow: none; -} - -.gr-compact .gr-box{ - border-radius: .5rem !important; - border-width: 1px !important; -} - -#mode_img2img > div > div{ - gap: 0 !important; -} - -[id*='img2img_copy_to_'] { - border: none; -} - -[id*='img2img_copy_to_'] > button { -} - -[id*='img2img_label_copy_to_'] { - font-size: 1.0em; - font-weight: bold; - text-align: center; - line-height: 2.4em; -} +/* extra networks UI */ .extra-networks > div > [id *= '_extra_']{ margin: 0.3em; @@ -1025,7 +757,3 @@ footer { .extra-network-cards .card ul a:hover{ color: red; } - -[id*='_prompt_container'] > div { - margin: 0!important; -} From 05ec128ca94fa8e72502a91a6db3dfae04917a08 Mon Sep 17 00:00:00 2001 From: "Alex \"mcmonkey\" Goodwin" Date: Mon, 20 Mar 2023 15:42:36 -0700 Subject: [PATCH 186/278] fix img2img alt for SD v2.x --- scripts/img2imgalt.py | 18 ++++++++++++++---- 1 file changed, 14 insertions(+), 4 deletions(-) diff --git a/scripts/img2imgalt.py b/scripts/img2imgalt.py index 2572443f..0b7ca633 100644 --- a/scripts/img2imgalt.py +++ b/scripts/img2imgalt.py @@ -22,7 +22,12 @@ def find_noise_for_image(p, cond, uncond, cfg_scale, steps): x = p.init_latent s_in = x.new_ones([x.shape[0]]) - dnw = K.external.CompVisDenoiser(shared.sd_model) + if shared.sd_model.parameterization == "v": + dnw = K.external.CompVisVDenoiser(shared.sd_model) + skip = 1 + else: + dnw = K.external.CompVisDenoiser(shared.sd_model) + skip = 0 sigmas = dnw.get_sigmas(steps).flip(0) shared.state.sampling_steps = steps @@ -37,7 +42,7 @@ def find_noise_for_image(p, cond, uncond, cfg_scale, steps): image_conditioning = torch.cat([p.image_conditioning] * 2) cond_in = {"c_concat": [image_conditioning], "c_crossattn": [cond_in]} - c_out, c_in = [K.utils.append_dims(k, x_in.ndim) for k in dnw.get_scalings(sigma_in)] + c_out, c_in = [K.utils.append_dims(k, x_in.ndim) for k in dnw.get_scalings(sigma_in)[skip:]] t = dnw.sigma_to_t(sigma_in) eps = shared.sd_model.apply_model(x_in * c_in, t, cond=cond_in) @@ -69,7 +74,12 @@ def find_noise_for_image_sigma_adjustment(p, cond, uncond, cfg_scale, steps): x = p.init_latent s_in = x.new_ones([x.shape[0]]) - dnw = K.external.CompVisDenoiser(shared.sd_model) + if shared.sd_model.parameterization == "v": + dnw = K.external.CompVisVDenoiser(shared.sd_model) + skip = 1 + else: + dnw = K.external.CompVisDenoiser(shared.sd_model) + skip = 0 sigmas = dnw.get_sigmas(steps).flip(0) shared.state.sampling_steps = steps @@ -84,7 +94,7 @@ def find_noise_for_image_sigma_adjustment(p, cond, uncond, cfg_scale, steps): image_conditioning = torch.cat([p.image_conditioning] * 2) cond_in = {"c_concat": [image_conditioning], "c_crossattn": [cond_in]} - c_out, c_in = [K.utils.append_dims(k, x_in.ndim) for k in dnw.get_scalings(sigma_in)] + c_out, c_in = [K.utils.append_dims(k, x_in.ndim) for k in dnw.get_scalings(sigma_in)[skip:]] if i == 1: t = dnw.sigma_to_t(torch.cat([sigmas[i] * s_in] * 2)) From c9c692c4d96a58d80b037d1fd3f3bf8bd27cc4c0 Mon Sep 17 00:00:00 2001 From: "Alex \"mcmonkey\" Goodwin" Date: Mon, 20 Mar 2023 15:43:01 -0700 Subject: [PATCH 187/278] cleanup the img2img alt file a bit --- scripts/img2imgalt.py | 12 ++---------- 1 file changed, 2 insertions(+), 10 deletions(-) diff --git a/scripts/img2imgalt.py b/scripts/img2imgalt.py index 0b7ca633..bb00fb3f 100644 --- a/scripts/img2imgalt.py +++ b/scripts/img2imgalt.py @@ -6,18 +6,11 @@ from tqdm import trange import modules.scripts as scripts import gradio as gr -from modules import processing, shared, sd_samplers, prompt_parser, sd_samplers_common -from modules.processing import Processed -from modules.shared import opts, cmd_opts, state +from modules import processing, shared, sd_samplers, sd_samplers_common import torch import k_diffusion as K -from PIL import Image -from torch import autocast -from einops import rearrange, repeat - - def find_noise_for_image(p, cond, uncond, cfg_scale, steps): x = p.init_latent @@ -135,7 +128,7 @@ class Script(scripts.Script): def show(self, is_img2img): return is_img2img - def ui(self, is_img2img): + def ui(self, is_img2img): info = gr.Markdown(''' * `CFG Scale` should be 2 or lower. ''') @@ -223,4 +216,3 @@ class Script(scripts.Script): processed = processing.process_images(p) return processed - From 8e3ced73a8c8f435809de544e0574da265177289 Mon Sep 17 00:00:00 2001 From: missionfloyd Date: Mon, 20 Mar 2023 18:04:22 -0600 Subject: [PATCH 188/278] Add event.stopPropagation() to extraNetworksShowMetadata() Prevent bubbling the same way "replace preview" does --- javascript/extraNetworks.js | 4 +++- modules/ui_extra_networks.py | 2 +- 2 files changed, 4 insertions(+), 2 deletions(-) diff --git a/javascript/extraNetworks.js b/javascript/extraNetworks.js index 2fb87cd5..c46ab1c6 100644 --- a/javascript/extraNetworks.js +++ b/javascript/extraNetworks.js @@ -132,10 +132,12 @@ function popup(contents){ globalPopup.style.display = "flex"; } -function extraNetworksShowMetadata(text){ +function extraNetworksShowMetadata(event, text){ elem = document.createElement('pre') elem.classList.add('popup-metadata'); elem.textContent = text; popup(elem); + + event.stopPropagation() } diff --git a/modules/ui_extra_networks.py b/modules/ui_extra_networks.py index 10272dbb..9b7e5e65 100644 --- a/modules/ui_extra_networks.py +++ b/modules/ui_extra_networks.py @@ -127,7 +127,7 @@ class ExtraNetworksPage: metadata_button = "" metadata = item.get("metadata") if metadata: - metadata_onclick = '"' + html.escape(f"""extraNetworksShowMetadata({json.dumps(metadata)}); event.stopPropagation(); return false;""") + '"' + metadata_onclick = '"' + html.escape(f"""return extraNetworksShowMetadata(event, {json.dumps(metadata)})""") + '"' metadata_button = f"" args = { From 46482decd5ec7c15811e1db5f992b0f616472cfe Mon Sep 17 00:00:00 2001 From: AUTOMATIC <16777216c@gmail.com> Date: Tue, 21 Mar 2023 06:49:19 +0300 Subject: [PATCH 189/278] fix ctrl+up/down attention edit fix dropdown obscured by live preview stylistic changes --- javascript/edit-attention.js | 2 +- modules/ui.py | 4 ++-- style.css | 12 +++++++++++- 3 files changed, 14 insertions(+), 4 deletions(-) diff --git a/javascript/edit-attention.js b/javascript/edit-attention.js index 619bb1fa..20a5aadf 100644 --- a/javascript/edit-attention.js +++ b/javascript/edit-attention.js @@ -1,6 +1,6 @@ function keyupEditAttention(event){ let target = event.originalTarget || event.composedPath()[0]; - if (!target.matches("[id*='_toprow'] textarea.gr-text-input[placeholder]")) return; + if (! target.matches("[id*='_toprow'] [id*='_prompt'] textarea")) return; if (! (event.metaKey || event.ctrlKey)) return; let isPlus = event.key == "ArrowUp" diff --git a/modules/ui.py b/modules/ui.py index 80807ce3..6e0ac89f 100644 --- a/modules/ui.py +++ b/modules/ui.py @@ -587,7 +587,7 @@ def create_ui(): txt2img_prompt.submit(**txt2img_args) submit.click(**txt2img_args) - res_switch_btn.click(lambda w, h: (h, w), inputs=[width, height], outputs=[width, height]) + res_switch_btn.click(lambda w, h: (h, w), inputs=[width, height], outputs=[width, height], show_progress=False) txt_prompt_img.change( fn=modules.images.image_data, @@ -907,7 +907,7 @@ def create_ui(): img2img_prompt.submit(**img2img_args) submit.click(**img2img_args) - res_switch_btn.click(lambda w, h: (h, w), inputs=[width, height], outputs=[width, height]) + res_switch_btn.click(lambda w, h: (h, w), inputs=[width, height], outputs=[width, height], show_progress=False) img2img_interrogate.click( fn=lambda *args: process_interrogate(interrogate, *args), diff --git a/style.css b/style.css index 936930fd..692833bd 100644 --- a/style.css +++ b/style.css @@ -1,9 +1,10 @@ /* general gradio fixes */ -:root{ +:root, .dark{ --checkbox-label-gap: 0.25em 0.1em; --section-header-text-size: 12pt; + --block-background-fill: transparent; } .block.padded{ @@ -54,6 +55,7 @@ div.compact{ .gradio-dropdown ul.options{ max-height: 35em; + z-index: 3000; } .gradio-dropdown label span:not(.has-info){ @@ -72,6 +74,13 @@ div.compact{ margin: 0.75em 1.5em 0 0; } +.gradio-html div.wrap{ + height: 100%; +} +div.gradio-html.min{ + min-height: 0; +} + /* general styled components */ .gradio-button.tool{ @@ -103,6 +112,7 @@ div.compact{ min-width: 0 !important; width: auto; z-index: 100; + top: -0.75em; } .block.token-counter span{ From f93547be18641039aca24e8166f0ed7d495da15e Mon Sep 17 00:00:00 2001 From: AUTOMATIC <16777216c@gmail.com> Date: Tue, 21 Mar 2023 08:18:14 +0300 Subject: [PATCH 190/278] hide delete button for single-item dropdown more stylistic changes --- modules/scripts.py | 15 +++++++++++++-- modules/ui.py | 2 ++ modules/ui_common.py | 2 +- style.css | 43 ++++++++++++++++++++++++++++++++++--------- 4 files changed, 50 insertions(+), 12 deletions(-) diff --git a/modules/scripts.py b/modules/scripts.py index 40d8dcc6..8f55cf24 100644 --- a/modules/scripts.py +++ b/modules/scripts.py @@ -513,6 +513,18 @@ def reload_scripts(): scripts_postproc = scripts_postprocessing.ScriptPostprocessingRunner() +def add_classes_to_gradio_component(comp): + """ + this adds gradio-* to the component for css styling (ie gradio-button to gr.Button), as well as some others + """ + + comp.elem_classes = ["gradio-" + comp.get_block_name(), *(comp.elem_classes or [])] + + if getattr(comp, 'multiselect', False): + comp.elem_classes.append('multiselect') + + + def IOComponent_init(self, *args, **kwargs): if scripts_current is not None: scripts_current.before_component(self, **kwargs) @@ -521,8 +533,7 @@ def IOComponent_init(self, *args, **kwargs): res = original_IOComponent_init(self, *args, **kwargs) - # this adds gradio-* to every component for css styling (ie gradio-button to gr.Button) - self.elem_classes = ["gradio-" + self.get_block_name(), *(self.elem_classes or [])] + add_classes_to_gradio_component(self) script_callbacks.after_component_callback(self, **kwargs) diff --git a/modules/ui.py b/modules/ui.py index 6e0ac89f..c5b0e876 100644 --- a/modules/ui.py +++ b/modules/ui.py @@ -1601,11 +1601,13 @@ def create_ui(): for i, k, item in quicksettings_list: component = component_dict[k] + info = opts.data_labels[k] component.change( fn=lambda value, k=k: run_settings_single(value, key=k), inputs=[component], outputs=[component, text_settings], + show_progress=info.refresh is not None, ) text_settings.change( diff --git a/modules/ui_common.py b/modules/ui_common.py index d4e00829..7b752b45 100644 --- a/modules/ui_common.py +++ b/modules/ui_common.py @@ -129,7 +129,7 @@ Requested path was: {f} generation_info = None with gr.Column(): - with gr.Row(elem_id=f"image_buttons_{tabname}"): + with gr.Row(elem_id=f"image_buttons_{tabname}", elem_classes="image-buttons"): open_folder_button = gr.Button(folder_symbol, visible=not shared.cmd_opts.hide_ui_dir_config) if tabname != "extras": diff --git a/style.css b/style.css index 692833bd..677f03cd 100644 --- a/style.css +++ b/style.css @@ -8,7 +8,7 @@ } .block.padded{ - padding: 0.2em 0.5em !important; + padding: 0 !important; } div.gradio-container{ @@ -38,7 +38,8 @@ div.form{ .block.gradio-textbox, .block.gradio-radio, .block.gradio-checkboxgroup, -.block.gradio-number +.block.gradio-number, +.block.gradio-colorpicker { border-width: 0 !important; box-shadow: none !important; @@ -46,11 +47,11 @@ div.form{ .gap.compact{ padding: 0; - gap: 0; + gap: 0.2em 0; } div.compact{ - gap: 0.5em; + gap: 1em; } .gradio-dropdown ul.options{ @@ -58,7 +59,10 @@ div.compact{ z-index: 3000; } -.gradio-dropdown label span:not(.has-info){ +.gradio-dropdown label span:not(.has-info), +.gradio-textbox label span:not(.has-info), +.gradio-number label span:not(.has-info) +{ margin-bottom: 0; } @@ -66,6 +70,14 @@ div.compact{ box-shadow: 0 1px 2px 0 rgba(0, 0, 0, 0.05); } +.gradio-dropdown .token-remove.remove-all.remove-all{ + display: none; +} + +.gradio-dropdown.multiselect .token-remove.remove-all.remove-all{ + display: flex; +} + .gradio-slider input[type="number"]{ width: 6em; } @@ -81,6 +93,17 @@ div.gradio-html.min{ min-height: 0; } +.block.gradio-gallery{ + background: var(--input-background-fill); +} + +.gradio-container .prose a, .gradio-container .prose a:visited{ + color: unset; + text-decoration: none; +} + + + /* general styled components */ .gradio-button.tool{ @@ -188,9 +211,7 @@ div.gradio-html.min{ gap: 0.5em; } .interrogate-col > button{ - min-width: 8em; - max-width: 8em; - height: 5.45em; + flex: 1; } .generate-box{ @@ -241,6 +262,10 @@ div.gradio-html.min{ min-height: 480px !important; } +.image-buttons button{ + min-width: auto; +} + /* settings */ #quicksettings { @@ -254,7 +279,6 @@ div.gradio-html.min{ border: none; box-shadow: none; background: none; - margin-right: 10px; } #quicksettings .gradio-dropdown .wrap-inner{ @@ -286,6 +310,7 @@ div.gradio-html.min{ display: block; border: none; text-align: left; + white-space: initial; } #settings_result{ From 6eacaad4a92f5c85eb9addd096a673bf3d5db5fe Mon Sep 17 00:00:00 2001 From: AUTOMATIC <16777216c@gmail.com> Date: Tue, 21 Mar 2023 08:49:08 +0300 Subject: [PATCH 191/278] enable queue by default more stylistic changes --- modules/shared.py | 3 ++- modules/ui.py | 4 ++-- modules/ui_extensions.py | 2 +- style.css | 39 ++++++++++++++++++++++++++++----------- webui.py | 2 +- 5 files changed, 34 insertions(+), 16 deletions(-) diff --git a/modules/shared.py b/modules/shared.py index f28a12cc..0b70d104 100644 --- a/modules/shared.py +++ b/modules/shared.py @@ -107,7 +107,8 @@ parser.add_argument("--cors-allow-origins-regex", type=str, help="Allowed CORS o parser.add_argument("--tls-keyfile", type=str, help="Partially enables TLS, requires --tls-certfile to fully function", default=None) parser.add_argument("--tls-certfile", type=str, help="Partially enables TLS, requires --tls-keyfile to fully function", default=None) parser.add_argument("--server-name", type=str, help="Sets hostname of server", default=None) -parser.add_argument("--gradio-queue", action='store_true', help="Uses gradio queue; experimental option; breaks restart UI button") +parser.add_argument("--gradio-queue", action='store_true', help="does not do anything", default=True) +parser.add_argument("--no-gradio-queue", action='store_true', help="Disables gradio queue; causes the webpage to use http requests instead of websockets; was the defaul in earlier versions") parser.add_argument("--skip-version-check", action='store_true', help="Do not check versions of torch and xformers") parser.add_argument("--no-hashing", action='store_true', help="disable sha256 hashing of checkpoints to help loading performance", default=False) parser.add_argument("--no-download-sd-model", action='store_true', help="don't download SD1.5 model even if no model is found in --ckpt-dir", default=False) diff --git a/modules/ui.py b/modules/ui.py index c5b0e876..9b9bfa8b 100644 --- a/modules/ui.py +++ b/modules/ui.py @@ -478,7 +478,7 @@ def create_ui(): width = gr.Slider(minimum=64, maximum=2048, step=8, label="Width", value=512, elem_id="txt2img_width") height = gr.Slider(minimum=64, maximum=2048, step=8, label="Height", value=512, elem_id="txt2img_height") - with gr.Column(elem_id="txt2img_dimensions_row", scale=1): + with gr.Column(elem_id="txt2img_dimensions_row", scale=1, elem_classes="dimensions-tools"): res_switch_btn = ToolButton(value=switch_values_symbol, elem_id="txt2img_res_switch_btn") if opts.dimensions_and_batch_together: @@ -758,7 +758,7 @@ def create_ui(): width = gr.Slider(minimum=64, maximum=2048, step=8, label="Width", value=512, elem_id="img2img_width") height = gr.Slider(minimum=64, maximum=2048, step=8, label="Height", value=512, elem_id="img2img_height") - with gr.Column(elem_id="img2img_dimensions_row", scale=1): + with gr.Column(elem_id="img2img_dimensions_row", scale=1, elem_classes="dimensions-tools"): res_switch_btn = ToolButton(value=switch_values_symbol, elem_id="img2img_res_switch_btn") if opts.dimensions_and_batch_together: diff --git a/modules/ui_extensions.py b/modules/ui_extensions.py index df75a925..50173e68 100644 --- a/modules/ui_extensions.py +++ b/modules/ui_extensions.py @@ -244,7 +244,7 @@ def refresh_available_extensions_from_data(hide_tags, sort_column): hidden += 1 continue - install_code = f"""""" + install_code = f"""""" tags_text = ", ".join([f"{x}" for x in extension_tags]) diff --git a/style.css b/style.css index 677f03cd..cee13cf7 100644 --- a/style.css +++ b/style.css @@ -70,6 +70,15 @@ div.compact{ box-shadow: 0 1px 2px 0 rgba(0, 0, 0, 0.05); } +.gradio-dropdown .wrap-inner.wrap-inner.wrap-inner{ + flex-wrap: unset; +} + +.gradio-dropdown .single-select{ + white-space: nowrap; + overflow: hidden; +} + .gradio-dropdown .token-remove.remove-all.remove-all{ display: none; } @@ -125,6 +134,22 @@ div.gradio-html.min{ min-width: auto; } +button.custom-button{ + border-radius: var(--button-large-radius); + padding: var(--button-large-padding); + font-weight: var(--button-large-text-weight); + border: var(--button-border-width) solid var(--button-secondary-border-color); + background: var(--button-secondary-background-fill); + color: var(--button-secondary-text-color); + font-size: var(--button-large-text-size); + display: inline-flex; + justify-content: center; + align-items: center; + transition: var(--button-transition); + box-shadow: var(--button-shadow); + text-align: center; +} + /* txt2img/img2img specific */ @@ -250,10 +275,11 @@ div.gradio-html.min{ min-width: min(13.5em, 100%) !important; } -[id$=_dimensions_row]{ +div.dimensions-tools{ min-width: 0 !important; max-width: fit-content; - padding: 0 1em; + flex-direction: row; + align-content: center; } #mode_img2img .gradio-image > div.fixed-height, #mode_img2img .gradio-image > div.fixed-height img{ @@ -281,15 +307,6 @@ div.gradio-html.min{ background: none; } -#quicksettings .gradio-dropdown .wrap-inner{ - flex-wrap: unset; -} - -#quicksettings .gradio-dropdown .single-select{ - white-space: nowrap; - overflow: hidden; -} - #settings{ display: block; } diff --git a/webui.py b/webui.py index aaec79fd..ca725b7d 100644 --- a/webui.py +++ b/webui.py @@ -240,7 +240,7 @@ def webui(): shared.demo = modules.ui.create_ui() startup_timer.record("create ui") - if cmd_opts.gradio_queue: + if not cmd_opts.no_gradio_queue: shared.demo.queue(64) gradio_auth_creds = [] From d3dcb05904de0a21cee27466da399263d21dbf43 Mon Sep 17 00:00:00 2001 From: AUTOMATIC <16777216c@gmail.com> Date: Tue, 21 Mar 2023 09:24:19 +0300 Subject: [PATCH 192/278] fix extra networks ui --- modules/ui_extra_networks.py | 2 +- style.css | 9 ++++++--- 2 files changed, 7 insertions(+), 4 deletions(-) diff --git a/modules/ui_extra_networks.py b/modules/ui_extra_networks.py index cdfd6f2a..8cd6d8cf 100644 --- a/modules/ui_extra_networks.py +++ b/modules/ui_extra_networks.py @@ -86,7 +86,7 @@ class ExtraNetworksPage: subdirs = {"": 1, **subdirs} subdirs_html = "".join([f""" - """ for subdir in subdirs]) diff --git a/style.css b/style.css index cee13cf7..b258552d 100644 --- a/style.css +++ b/style.css @@ -636,12 +636,12 @@ footer { .extra-network-subdirs button{ margin: 0 0.15em; } - -#txt2img_extra_networks .search, #img2img_extra_networks .search{ +.extra-networks .tab-nav .search{ display: inline-block; max-width: 16em; margin: 0.3em; align-self: center; + width: 16em; } #txt2img_extra_view, #img2img_extra_view { @@ -766,12 +766,15 @@ footer { left: 0; right: 0; padding: 0.5em; - color: white; background: rgba(0,0,0,0.5); box-shadow: 0 0 0.25em 0.25em rgba(0,0,0,0.5); text-shadow: 0 0 0.2em black; } +.extra-network-cards .card .actions *{ + color: white; +} + .extra-network-cards .card .actions:hover{ box-shadow: 0 0 0.75em 0.75em rgba(0,0,0,0.5) !important; } From 4cbbb881ee530d9b9ba18027e2b0057e6a2c4ee1 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=CE=A6=CF=86?= <42910943+Brawlence@users.noreply.github.com> Date: Thu, 9 Mar 2023 07:56:19 +0300 Subject: [PATCH 193/278] Unload checkpoints on Request MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit …to free VRAM. New Action buttons in the settings to manually free and reload checkpoints, essentially juggling models between RAM and VRAM. --- modules/api/api.py | 14 +++++++++++++- modules/sd_models.py | 22 +++++++++++++++++++++- modules/ui.py | 22 ++++++++++++++++++++++ 3 files changed, 56 insertions(+), 2 deletions(-) diff --git a/modules/api/api.py b/modules/api/api.py index 35e17afc..f52f7fef 100644 --- a/modules/api/api.py +++ b/modules/api/api.py @@ -18,7 +18,7 @@ from modules.textual_inversion.textual_inversion import create_embedding, train_ from modules.textual_inversion.preprocess import preprocess from modules.hypernetworks.hypernetwork import create_hypernetwork, train_hypernetwork from PIL import PngImagePlugin,Image -from modules.sd_models import checkpoints_list +from modules.sd_models import checkpoints_list, unload_model_weights, reload_model_weights from modules.sd_models_config import find_checkpoint_config_near_filename from modules.realesrgan_model import get_realesrgan_models from modules import devices @@ -150,6 +150,8 @@ class Api: self.add_api_route("/sdapi/v1/train/embedding", self.train_embedding, methods=["POST"], response_model=TrainResponse) self.add_api_route("/sdapi/v1/train/hypernetwork", self.train_hypernetwork, methods=["POST"], response_model=TrainResponse) self.add_api_route("/sdapi/v1/memory", self.get_memory, methods=["GET"], response_model=MemoryResponse) + self.add_api_route("/sdapi/v1/unload-checkpoint", self.unloadapi, methods=["POST"]) + self.add_api_route("/sdapi/v1/reload-checkpoint", self.reloadapi, methods=["POST"]) self.add_api_route("/sdapi/v1/scripts", self.get_scripts_list, methods=["GET"], response_model=ScriptsList) def add_api_route(self, path: str, endpoint, **kwargs): @@ -412,6 +414,16 @@ class Api: return {} + def unloadapi(self): + unload_model_weights() + + return {} + + def reloadapi(self): + reload_model_weights() + + return {} + def skip(self): shared.state.skip() diff --git a/modules/sd_models.py b/modules/sd_models.py index f0cb1240..f9dd0521 100644 --- a/modules/sd_models.py +++ b/modules/sd_models.py @@ -494,7 +494,7 @@ def reload_model_weights(sd_model=None, info=None): if sd_model is None or checkpoint_config != sd_model.used_config: del sd_model checkpoints_loaded.clear() - load_model(checkpoint_info, already_loaded_state_dict=state_dict, time_taken_to_load_state_dict=timer.records["load weights from disk"]) + load_model(checkpoint_info, already_loaded_state_dict=state_dict) return shared.sd_model try: @@ -517,3 +517,23 @@ def reload_model_weights(sd_model=None, info=None): print(f"Weights loaded in {timer.summary()}.") return sd_model + +def unload_model_weights(sd_model=None, info=None): + from modules import lowvram, devices, sd_hijack + timer = Timer() + + if shared.sd_model: + + # shared.sd_model.cond_stage_model.to(devices.cpu) + # shared.sd_model.first_stage_model.to(devices.cpu) + shared.sd_model.to(devices.cpu) + sd_hijack.model_hijack.undo_hijack(shared.sd_model) + shared.sd_model = None + sd_model = None + gc.collect() + devices.torch_gc() + torch.cuda.empty_cache() + + print(f"Unloaded weights {timer.summary()}.") + + return sd_model \ No newline at end of file diff --git a/modules/ui.py b/modules/ui.py index 7e603332..d93ef134 100644 --- a/modules/ui.py +++ b/modules/ui.py @@ -1491,11 +1491,33 @@ def create_ui(): request_notifications = gr.Button(value='Request browser notifications', elem_id="request_notifications") download_localization = gr.Button(value='Download localization template', elem_id="download_localization") reload_script_bodies = gr.Button(value='Reload custom script bodies (No ui updates, No restart)', variant='secondary', elem_id="settings_reload_script_bodies") + with gr.Row(): + unload_sd_model = gr.Button(value='Unload SD checkpoint to free VRAM', elem_id="sett_unload_sd_model") + reload_sd_model = gr.Button(value='Reload the last SD checkpoint back into VRAM', elem_id="sett_reload_sd_model") with gr.TabItem("Licenses"): gr.HTML(shared.html("licenses.html"), elem_id="licenses") gr.Button(value="Show all pages", elem_id="settings_show_all_pages") + + + def unload_sd_weights(): + modules.sd_models.unload_model_weights() + + def reload_sd_weights(): + modules.sd_models.reload_model_weights() + + unload_sd_model.click( + fn=unload_sd_weights, + inputs=[], + outputs=[] + ) + + reload_sd_model.click( + fn=reload_sd_weights, + inputs=[], + outputs=[] + ) request_notifications.click( fn=lambda: None, From 254d9946439be9b6266b671db68086eb68ef1e63 Mon Sep 17 00:00:00 2001 From: FNSpd <125805478+FNSpd@users.noreply.github.com> Date: Tue, 21 Mar 2023 14:45:39 +0400 Subject: [PATCH 194/278] Update devices.py --- modules/devices.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/modules/devices.py b/modules/devices.py index 52c3e7cd..6c6c7233 100644 --- a/modules/devices.py +++ b/modules/devices.py @@ -110,7 +110,7 @@ def autocast(disable=False): if disable: return contextlib.nullcontext() - if dtype == torch.float32 or shared.cmd_opts.precision == "full": + if dtype == torch.float32 or shared.cmd_opts.precision == "full" or shared.cmd_opts.upcast_sampling: return contextlib.nullcontext() return torch.autocast("cuda") From 91cfa9718cead1c9834d5fe46a3af54abeacc8e2 Mon Sep 17 00:00:00 2001 From: FNSpd <125805478+FNSpd@users.noreply.github.com> Date: Tue, 21 Mar 2023 14:47:43 +0400 Subject: [PATCH 195/278] Update sd_hijack_unet.py --- modules/sd_hijack_unet.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/modules/sd_hijack_unet.py b/modules/sd_hijack_unet.py index 843ab66c..d37ec316 100644 --- a/modules/sd_hijack_unet.py +++ b/modules/sd_hijack_unet.py @@ -67,7 +67,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', 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.1"): +if version.parse(torch.__version__) <= version.parse("1.13.2"): 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) CondFunc('open_clip.transformer.ResidualAttentionBlock.__init__', lambda orig_func, *args, **kwargs: kwargs.update({'act_layer': GELUHijack}) and False or orig_func(*args, **kwargs), lambda _, *args, **kwargs: kwargs.get('act_layer') is None or kwargs['act_layer'] == torch.nn.GELU) From c84c9df73799e173fcfafdc9548dbd043ba28682 Mon Sep 17 00:00:00 2001 From: FNSpd <125805478+FNSpd@users.noreply.github.com> Date: Tue, 21 Mar 2023 14:50:22 +0400 Subject: [PATCH 196/278] Update sd_hijack_optimizations.py --- modules/sd_hijack_optimizations.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/modules/sd_hijack_optimizations.py b/modules/sd_hijack_optimizations.py index 2e307b5d..eaff12f0 100644 --- a/modules/sd_hijack_optimizations.py +++ b/modules/sd_hijack_optimizations.py @@ -337,7 +337,7 @@ def xformers_attention_forward(self, x, context=None, mask=None): dtype = q.dtype if shared.opts.upcast_attn: - q, k = q.float(), k.float() + q, k, v = q.float(), k.float(), v.float() out = xformers.ops.memory_efficient_attention(q, k, v, attn_bias=None, op=get_xformers_flash_attention_op(q, k, v)) From 2f0181405f25e1448a55697081e380020fe8c68d Mon Sep 17 00:00:00 2001 From: FNSpd <125805478+FNSpd@users.noreply.github.com> Date: Tue, 21 Mar 2023 14:53:51 +0400 Subject: [PATCH 197/278] Update lora.py --- extensions-builtin/Lora/lora.py | 1 + 1 file changed, 1 insertion(+) diff --git a/extensions-builtin/Lora/lora.py b/extensions-builtin/Lora/lora.py index 8937b585..7c371deb 100644 --- a/extensions-builtin/Lora/lora.py +++ b/extensions-builtin/Lora/lora.py @@ -178,6 +178,7 @@ def load_loras(names, multipliers=None): def lora_forward(module, input, res): + input = devices.cond_cast_unet(input) if len(loaded_loras) == 0: return res From 33b85391474098b021946a5f1d8e07f8a6d27aa2 Mon Sep 17 00:00:00 2001 From: James Railton Date: Tue, 21 Mar 2023 21:07:33 -0400 Subject: [PATCH 198/278] Loopback Script Updates - Improved user experience. You can now pick the denoising strength of the final loop and one of three curves. Previously you picked a multiplier such as 0.98 or 1.03 to define the change to the denoising strength for each loop. You had to do a ton of math in your head to visualize what was happening. The new UX makes it very easy to understand what's going on and tweak. - For batch sizes over 1, intermediate images no longer returned. For a batch size of 1, intermediate images from each loop will continue to be returned. When more than 1 image is returned, a grid will also be generated. Previously for larger jobs, you'd get back a mess of many grids and potentially hundreds of images with no organization. To make large jobs usable, only final images are returned. - Added support for skipping current image. Fixed interrupt to cleanly end and return images. Previously these would throw. - Improved tooltip descriptions - Fix some edge cases --- javascript/hints.js | 9 +++-- scripts/loopback.py | 91 ++++++++++++++++++++++++++++++++------------- 2 files changed, 71 insertions(+), 29 deletions(-) diff --git a/javascript/hints.js b/javascript/hints.js index 7f4101b2..58e4a0a2 100644 --- a/javascript/hints.js +++ b/javascript/hints.js @@ -40,8 +40,7 @@ titles = { "Inpaint at full resolution": "Upscale masked region to target resolution, do inpainting, downscale back and paste into original image", "Denoising strength": "Determines how little respect the algorithm should have for image's content. At 0, nothing will change, and at 1 you'll get an unrelated image. With values below 1.0, processing will take less steps than the Sampling Steps slider specifies.", - "Denoising strength change factor": "In loopback mode, on each loop the denoising strength is multiplied by this value. <1 means decreasing variety so your sequence will converge on a fixed picture. >1 means increasing variety so your sequence will become more and more chaotic.", - + "Skip": "Stop processing current image and continue processing.", "Interrupt": "Stop processing images and return any results accumulated so far.", "Save": "Write image to a directory (default - log/images) and generation parameters into csv file.", @@ -71,8 +70,10 @@ titles = { "Directory name pattern": "Use following tags to define how subdirectories for images and grids are chosen: [steps], [cfg],[prompt_hash], [prompt], [prompt_no_styles], [prompt_spaces], [width], [height], [styles], [sampler], [seed], [model_hash], [model_name], [prompt_words], [date], [datetime], [datetime], [datetime