From c055132606e084fc69887357da5dd7c1bac1f590 Mon Sep 17 00:00:00 2001 From: Dave Bauman Date: Tue, 27 Sep 2022 09:50:06 -0400 Subject: Fix downssample typo in options Updated `downssample` to `down-sample`, matching spelling in other places. --- modules/shared.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'modules/shared.py') diff --git a/modules/shared.py b/modules/shared.py index 870fb3b9..84302438 100644 --- a/modules/shared.py +++ b/modules/shared.py @@ -170,7 +170,7 @@ options_templates.update(options_section(('upscaling', "Upscaling"), { "SWIN_tile": OptionInfo(192, "Tile size for all SwinIR.", gr.Slider, {"minimum": 16, "maximum": 512, "step": 16}), "SWIN_tile_overlap": OptionInfo(8, "Tile overlap, in pixels for SwinIR. Low values = visible seam.", gr.Slider, {"minimum": 0, "maximum": 48, "step": 1}), "ldsr_steps": OptionInfo(100, "LDSR processing steps. Lower = faster", gr.Slider, {"minimum": 1, "maximum": 200, "step": 1}), - "ldsr_pre_down": OptionInfo(1, "LDSR Pre-process downssample scale. 1 = no down-sampling, 4 = 1/4 scale.", gr.Slider, {"minimum": 1, "maximum": 4, "step": 1}), + "ldsr_pre_down": OptionInfo(1, "LDSR Pre-process down-sample scale. 1 = no down-sampling, 4 = 1/4 scale.", gr.Slider, {"minimum": 1, "maximum": 4, "step": 1}), "ldsr_post_down": OptionInfo(1, "LDSR Post-process down-sample scale. 1 = no down-sampling, 4 = 1/4 scale.", gr.Slider, {"minimum": 1, "maximum": 4, "step": 1}), "upscaler_for_img2img": OptionInfo(None, "Upscaler for img2img", gr.Radio, lambda: {"choices": [x.name for x in sd_upscalers]}), -- cgit v1.2.3 From 047ca73388dbfbd5986639678170715ff220bf5b Mon Sep 17 00:00:00 2001 From: C43H66N12O12S2 <36072735+C43H66N12O12S2@users.noreply.github.com> Date: Wed, 28 Sep 2022 05:11:40 +0300 Subject: Update shared.py --- modules/shared.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'modules/shared.py') diff --git a/modules/shared.py b/modules/shared.py index 84302438..2502fe2d 100644 --- a/modules/shared.py +++ b/modules/shared.py @@ -221,7 +221,7 @@ options_templates.update(options_section(('ui', "User interface"), { })) options_templates.update(options_section(('sampler-params', "Sampler parameters"), { - "ddim_eta": OptionInfo(0.0, "DDIM eta", gr.Slider, {"minimum": 0.0, "maximum": 1.0, "step": 0.01}), + "eta": OptionInfo(0.0, "DDIM and K Ancestral eta", gr.Slider, {"minimum": 0.0, "maximum": 1.0, "step": 0.01}), "ddim_discretize": OptionInfo('uniform', "img2img DDIM discretize", gr.Radio, {"choices": ['uniform','quad']}), 's_churn': OptionInfo(0.0, "sigma churn", gr.Slider, {"minimum": 0.0, "maximum": 1.0, "step": 0.01}), 's_tmin': OptionInfo(0.0, "sigma tmin", gr.Slider, {"minimum": 0.0, "maximum": 1.0, "step": 0.01}), -- cgit v1.2.3 From aea5b2510ed4bd9150cea67b6036c837f7df2750 Mon Sep 17 00:00:00 2001 From: AUTOMATIC <16777216c@gmail.com> Date: Wed, 28 Sep 2022 17:05:23 +0300 Subject: save parameters for images when using the Save button. --- modules/processing.py | 9 +++++++-- modules/shared.py | 2 +- modules/ui.py | 16 ++++++++++------ 3 files changed, 18 insertions(+), 9 deletions(-) (limited to 'modules/shared.py') diff --git a/modules/processing.py b/modules/processing.py index 90e00bf8..e6b84684 100644 --- a/modules/processing.py +++ b/modules/processing.py @@ -100,7 +100,7 @@ class StableDiffusionProcessing: class Processed: - def __init__(self, p: StableDiffusionProcessing, images_list, seed=-1, info="", subseed=None, all_prompts=None, all_seeds=None, all_subseeds=None, index_of_first_image=0): + def __init__(self, p: StableDiffusionProcessing, images_list, seed=-1, info="", subseed=None, all_prompts=None, all_seeds=None, all_subseeds=None, index_of_first_image=0, infotexts=None): self.images = images_list self.prompt = p.prompt self.negative_prompt = p.negative_prompt @@ -139,6 +139,7 @@ class Processed: self.all_prompts = all_prompts or [self.prompt] self.all_seeds = all_seeds or [self.seed] self.all_subseeds = all_subseeds or [self.subseed] + self.infotexts = infotexts or [info] def js(self): obj = { @@ -165,6 +166,7 @@ class Processed: "denoising_strength": self.denoising_strength, "extra_generation_params": self.extra_generation_params, "index_of_first_image": self.index_of_first_image, + "infotexts": self.infotexts, } return json.dumps(obj) @@ -322,6 +324,7 @@ def process_images(p: StableDiffusionProcessing) -> Processed: if os.path.exists(cmd_opts.embeddings_dir): model_hijack.load_textual_inversion_embeddings(cmd_opts.embeddings_dir, p.sd_model) + infotexts = [] output_images = [] precision_scope = torch.autocast if cmd_opts.precision == "autocast" else contextlib.nullcontext ema_scope = (contextlib.nullcontext if cmd_opts.lowvram else p.sd_model.ema_scope) @@ -404,6 +407,7 @@ def process_images(p: StableDiffusionProcessing) -> Processed: if opts.samples_save and not p.do_not_save_samples: images.save_image(image, p.outpath_samples, "", seeds[i], prompts[i], opts.samples_format, info=infotext(n, i), p=p) + infotexts.append(infotext(n, i)) output_images.append(image) state.nextjob() @@ -416,6 +420,7 @@ def process_images(p: StableDiffusionProcessing) -> Processed: grid = images.image_grid(output_images, p.batch_size) if opts.return_grid: + infotexts.insert(0, infotext()) output_images.insert(0, grid) index_of_first_image = 1 @@ -423,7 +428,7 @@ def process_images(p: StableDiffusionProcessing) -> Processed: images.save_image(grid, p.outpath_grids, "grid", all_seeds[0], all_prompts[0], opts.grid_format, info=infotext(), short_filename=not opts.grid_extended_filename, p=p, grid=True) devices.torch_gc() - return Processed(p, output_images, all_seeds[0], infotext(), subseed=all_subseeds[0], all_prompts=all_prompts, all_seeds=all_seeds, all_subseeds=all_subseeds, index_of_first_image=index_of_first_image) + return Processed(p, output_images, all_seeds[0], infotext(), subseed=all_subseeds[0], all_prompts=all_prompts, all_seeds=all_seeds, all_subseeds=all_subseeds, index_of_first_image=index_of_first_image, infotexts=infotexts) class StableDiffusionProcessingTxt2Img(StableDiffusionProcessing): diff --git a/modules/shared.py b/modules/shared.py index 2502fe2d..ae459e14 100644 --- a/modules/shared.py +++ b/modules/shared.py @@ -143,6 +143,7 @@ options_templates.update(options_section(('saving-images', "Saving images/grids" "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"), "use_original_name_batch": OptionInfo(False, "Use original name for output filename during batch process in extras tab"), + "save_selected_only": OptionInfo(True, "When using 'Save' button, only save a single selected image"), })) options_templates.update(options_section(('saving-paths', "Paths for saving"), { @@ -180,7 +181,6 @@ 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]}), "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"), - "save_selected_only": OptionInfo(False, "When using 'Save' button, only save a single selected image"), })) options_templates.update(options_section(('system', "System"), { diff --git a/modules/ui.py b/modules/ui.py index 7db8edbd..484be762 100644 --- a/modules/ui.py +++ b/modules/ui.py @@ -12,7 +12,7 @@ import traceback import numpy as np import torch -from PIL import Image +from PIL import Image, PngImagePlugin import gradio as gr import gradio.utils @@ -97,10 +97,11 @@ def save_files(js_data, images, index): filenames = [] data = json.loads(js_data) - - if index > -1 and opts.save_selected_only and (index > 0 or not opts.return_grid): # ensures we are looking at a specific non-grid picture, and we have save_selected_only + if index > -1 and opts.save_selected_only and (index >= data["index_of_first_image"]): # ensures we are looking at a specific non-grid picture, and we have save_selected_only images = [images[index]] - data["seed"] += (index - 1 if opts.return_grid else index) + infotexts = [data["infotexts"][index]] + else: + infotexts = data["infotexts"] with open(os.path.join(opts.outdir_save, "log.csv"), "a", encoding="utf8", newline='') as file: at_start = file.tell() == 0 @@ -116,8 +117,11 @@ def save_files(js_data, images, index): if filedata.startswith("data:image/png;base64,"): filedata = filedata[len("data:image/png;base64,"):] - with open(filepath, "wb") as imgfile: - imgfile.write(base64.decodebytes(filedata.encode('utf-8'))) + pnginfo = PngImagePlugin.PngInfo() + pnginfo.add_text('parameters', infotexts[i]) + + image = Image.open(io.BytesIO(base64.decodebytes(filedata.encode('utf-8')))) + image.save(filepath, quality=opts.jpeg_quality, pnginfo=pnginfo) filenames.append(filename) -- cgit v1.2.3 From d64b451681bdba5453723d3fe0b0681a470d8045 Mon Sep 17 00:00:00 2001 From: AUTOMATIC <16777216c@gmail.com> Date: Wed, 28 Sep 2022 18:09:06 +0300 Subject: added support for automatically installing latest k-diffusion added eta parameter to parameters output for generated images split eta settings into ancestral and ddim (because they have different default values) --- launch.py | 7 +++++ modules/processing.py | 9 +++--- modules/sd_samplers.py | 83 ++++++++++++++++++++++++++------------------------ modules/shared.py | 5 +-- scripts/xy_grid.py | 12 ++++---- 5 files changed, 65 insertions(+), 51 deletions(-) (limited to 'modules/shared.py') diff --git a/launch.py b/launch.py index 7958c6e9..c3e99afe 100644 --- a/launch.py +++ b/launch.py @@ -113,6 +113,13 @@ if not skip_torch_cuda_test: if not is_installed("k_diffusion.sampling"): run_pip(f"install {k_diffusion_package}", "k-diffusion") +if not check_run_python("import k_diffusion; import inspect; assert 'eta' in inspect.signature(k_diffusion.sampling.sample_euler_ancestral).parameters"): + print(f"k-diffusion does not have 'eta' parameter; reinstalling latest version") + try: + run_pip(f"install --upgrade --force-reinstall {k_diffusion_package}", "k-diffusion") + except RuntimeError as e: + print(str(e)) + if not is_installed("gfpgan"): run_pip(f"install {gfpgan_package}", "gfpgan") diff --git a/modules/processing.py b/modules/processing.py index e6b84684..358a1b11 100644 --- a/modules/processing.py +++ b/modules/processing.py @@ -49,7 +49,7 @@ def apply_color_correction(correction, image): class StableDiffusionProcessing: - def __init__(self, sd_model=None, outpath_samples=None, outpath_grids=None, prompt="", styles=None, seed=-1, subseed=-1, subseed_strength=0, seed_resize_from_h=-1, seed_resize_from_w=-1, seed_enable_extras=True, sampler_index=0, batch_size=1, n_iter=1, steps=50, cfg_scale=7.0, width=512, height=512, restore_faces=False, tiling=False, do_not_save_samples=False, do_not_save_grid=False, extra_generation_params=None, overlay_images=None, negative_prompt=None): + def __init__(self, sd_model=None, outpath_samples=None, outpath_grids=None, prompt="", styles=None, seed=-1, subseed=-1, subseed_strength=0, seed_resize_from_h=-1, seed_resize_from_w=-1, seed_enable_extras=True, sampler_index=0, batch_size=1, n_iter=1, steps=50, cfg_scale=7.0, width=512, height=512, restore_faces=False, tiling=False, do_not_save_samples=False, do_not_save_grid=False, extra_generation_params=None, overlay_images=None, negative_prompt=None, eta=None): self.sd_model = sd_model self.outpath_samples: str = outpath_samples self.outpath_grids: str = outpath_grids @@ -75,15 +75,15 @@ class StableDiffusionProcessing: self.do_not_save_grid: bool = do_not_save_grid self.extra_generation_params: dict = extra_generation_params or {} self.overlay_images = overlay_images + self.eta = eta self.paste_to = None self.color_corrections = None self.denoising_strength: float = 0 - - self.eta = opts.eta + self.ddim_discretize = opts.ddim_discretize self.s_churn = opts.s_churn self.s_tmin = opts.s_tmin - self.s_tmax = float('inf') # not representable as a standard ui option + self.s_tmax = float('inf') # not representable as a standard ui option self.s_noise = opts.s_noise if not seed_enable_extras: @@ -271,6 +271,7 @@ def create_infotext(p, all_prompts, all_seeds, all_subseeds, comments, iteration "Variation seed strength": (None if p.subseed_strength == 0 else p.subseed_strength), "Seed resize from": (None if p.seed_resize_from_w == 0 or p.seed_resize_from_h == 0 else f"{p.seed_resize_from_w}x{p.seed_resize_from_h}"), "Denoising strength": getattr(p, 'denoising_strength', None), + "Eta": (None if p.sampler.eta == p.sampler.default_eta else p.sampler.eta), } generation_params.update(p.extra_generation_params) diff --git a/modules/sd_samplers.py b/modules/sd_samplers.py index a1183997..3588aae6 100644 --- a/modules/sd_samplers.py +++ b/modules/sd_samplers.py @@ -40,10 +40,8 @@ samplers_for_img2img = [x for x in samplers if x.name != 'PLMS'] sampler_extra_params = { 'sample_euler': ['s_churn', 's_tmin', 's_tmax', 's_noise'], - 'sample_euler_ancestral': ['eta'], 'sample_heun': ['s_churn', 's_tmin', 's_tmax', 's_noise'], 'sample_dpm_2': ['s_churn', 's_tmin', 's_tmax', 's_noise'], - 'sample_dpm_2_ancestral': ['eta'], } def setup_img2img_steps(p, steps=None): @@ -101,6 +99,8 @@ class VanillaStableDiffusionSampler: self.init_latent = None self.sampler_noises = None self.step = 0 + self.eta = None + self.default_eta = 0.0 def number_of_needed_noises(self, p): return 0 @@ -123,20 +123,29 @@ class VanillaStableDiffusionSampler: self.step += 1 return res + def initialize(self, p): + self.eta = p.eta or opts.eta_ddim + + for fieldname in ['p_sample_ddim', 'p_sample_plms']: + if hasattr(self.sampler, fieldname): + setattr(self.sampler, fieldname, self.p_sample_ddim_hook) + + self.mask = p.mask if hasattr(p, 'mask') else None + self.nmask = p.nmask if hasattr(p, 'nmask') else None + def sample_img2img(self, p, x, noise, conditioning, unconditional_conditioning, steps=None): steps, t_enc = setup_img2img_steps(p, steps) # existing code fails with cetain step counts, like 9 try: - self.sampler.make_schedule(ddim_num_steps=steps, ddim_eta=p.ddim_eta, ddim_discretize=p.ddim_discretize, verbose=False) + self.sampler.make_schedule(ddim_num_steps=steps, ddim_eta=self.eta, ddim_discretize=p.ddim_discretize, verbose=False) except Exception: - self.sampler.make_schedule(ddim_num_steps=steps+1,ddim_eta=p.ddim_eta, ddim_discretize=p.ddim_discretize, verbose=False) + self.sampler.make_schedule(ddim_num_steps=steps+1, ddim_eta=self.eta, ddim_discretize=p.ddim_discretize, verbose=False) x1 = self.sampler.stochastic_encode(x, torch.tensor([t_enc] * int(x.shape[0])).to(shared.device), noise=noise) - self.sampler.p_sample_ddim = self.p_sample_ddim_hook - self.mask = p.mask if hasattr(p, 'mask') else None - self.nmask = p.nmask if hasattr(p, 'nmask') else None + self.initialize(p) + self.init_latent = x self.step = 0 @@ -145,11 +154,8 @@ class VanillaStableDiffusionSampler: return samples def sample(self, p, x, conditioning, unconditional_conditioning, steps=None): - for fieldname in ['p_sample_ddim', 'p_sample_plms']: - if hasattr(self.sampler, fieldname): - setattr(self.sampler, fieldname, self.p_sample_ddim_hook) - self.mask = None - self.nmask = None + self.initialize(p) + self.init_latent = None self.step = 0 @@ -157,9 +163,9 @@ class VanillaStableDiffusionSampler: # existing code fails with cetin step counts, like 9 try: - samples_ddim, _ = self.sampler.sample(S=steps, conditioning=conditioning, batch_size=int(x.shape[0]), shape=x[0].shape, verbose=False, unconditional_guidance_scale=p.cfg_scale, unconditional_conditioning=unconditional_conditioning, x_T=x, eta=p.eta) + samples_ddim, _ = self.sampler.sample(S=steps, conditioning=conditioning, batch_size=int(x.shape[0]), shape=x[0].shape, verbose=False, unconditional_guidance_scale=p.cfg_scale, unconditional_conditioning=unconditional_conditioning, x_T=x, eta=self.eta) except Exception: - samples_ddim, _ = self.sampler.sample(S=steps+1, conditioning=conditioning, batch_size=int(x.shape[0]), shape=x[0].shape, verbose=False, unconditional_guidance_scale=p.cfg_scale, unconditional_conditioning=unconditional_conditioning, x_T=x, eta=p.eta) + samples_ddim, _ = self.sampler.sample(S=steps+1, conditioning=conditioning, batch_size=int(x.shape[0]), shape=x[0].shape, verbose=False, unconditional_guidance_scale=p.cfg_scale, unconditional_conditioning=unconditional_conditioning, x_T=x, eta=self.eta) return samples_ddim @@ -237,6 +243,8 @@ class KDiffusionSampler: self.sampler_noises = None self.sampler_noise_index = 0 self.stop_at = None + self.eta = None + self.default_eta = 1.0 def callback_state(self, d): store_latent(d["denoised"]) @@ -255,22 +263,12 @@ class KDiffusionSampler: self.sampler_noise_index += 1 return res - def sample_img2img(self, p, x, noise, conditioning, unconditional_conditioning, steps=None): - steps, t_enc = setup_img2img_steps(p, steps) - - sigmas = self.model_wrap.get_sigmas(steps) - - noise = noise * sigmas[steps - t_enc - 1] - - xi = x + noise - - sigma_sched = sigmas[steps - t_enc - 1:] - + def initialize(self, p): self.model_wrap_cfg.mask = p.mask if hasattr(p, 'mask') else None self.model_wrap_cfg.nmask = p.nmask if hasattr(p, 'nmask') else None - self.model_wrap_cfg.init_latent = x self.model_wrap.step = 0 self.sampler_noise_index = 0 + self.eta = p.eta or opts.eta_ancestral if hasattr(k_diffusion.sampling, 'trange'): k_diffusion.sampling.trange = lambda *args, **kwargs: extended_trange(self, *args, **kwargs) @@ -283,6 +281,25 @@ class KDiffusionSampler: if hasattr(p, param_name) and param_name in inspect.signature(self.func).parameters: extra_params_kwargs[param_name] = getattr(p, param_name) + if 'eta' in inspect.signature(self.func).parameters: + extra_params_kwargs['eta'] = self.eta + + return extra_params_kwargs + + def sample_img2img(self, p, x, noise, conditioning, unconditional_conditioning, steps=None): + steps, t_enc = setup_img2img_steps(p, steps) + + sigmas = self.model_wrap.get_sigmas(steps) + + noise = noise * sigmas[steps - t_enc - 1] + xi = x + noise + + extra_params_kwargs = self.initialize(p) + + sigma_sched = sigmas[steps - t_enc - 1:] + + self.model_wrap_cfg.init_latent = x + return self.func(self.model_wrap_cfg, xi, sigma_sched, extra_args={'cond': conditioning, 'uncond': unconditional_conditioning, 'cond_scale': p.cfg_scale}, disable=False, callback=self.callback_state, **extra_params_kwargs) def sample(self, p, x, conditioning, unconditional_conditioning, steps=None): @@ -291,19 +308,7 @@ class KDiffusionSampler: sigmas = self.model_wrap.get_sigmas(steps) x = x * sigmas[0] - self.model_wrap_cfg.step = 0 - self.sampler_noise_index = 0 - - if hasattr(k_diffusion.sampling, 'trange'): - k_diffusion.sampling.trange = lambda *args, **kwargs: extended_trange(self, *args, **kwargs) - - if self.sampler_noises is not None: - k_diffusion.sampling.torch = TorchHijack(self) - - extra_params_kwargs = {} - for param_name in self.extra_params: - if hasattr(p, param_name) and param_name in inspect.signature(self.func).parameters: - extra_params_kwargs[param_name] = getattr(p, param_name) + extra_params_kwargs = self.initialize(p) samples = self.func(self.model_wrap_cfg, x, sigmas, extra_args={'cond': conditioning, 'uncond': unconditional_conditioning, 'cond_scale': p.cfg_scale}, disable=False, callback=self.callback_state, **extra_params_kwargs) diff --git a/modules/shared.py b/modules/shared.py index ae459e14..39cf89bc 100644 --- a/modules/shared.py +++ b/modules/shared.py @@ -221,8 +221,9 @@ options_templates.update(options_section(('ui', "User interface"), { })) options_templates.update(options_section(('sampler-params', "Sampler parameters"), { - "eta": OptionInfo(0.0, "DDIM and K Ancestral eta", gr.Slider, {"minimum": 0.0, "maximum": 1.0, "step": 0.01}), - "ddim_discretize": OptionInfo('uniform', "img2img DDIM discretize", gr.Radio, {"choices": ['uniform','quad']}), + "eta_ddim": OptionInfo(0.0, "eta (noise multiplier) for DDIM", gr.Slider, {"minimum": 0.0, "maximum": 1.0, "step": 0.01}), + "eta_ancestral": OptionInfo(1.0, "eta (noise multiplier) for ancestral samplers", gr.Slider, {"minimum": 0.0, "maximum": 1.0, "step": 0.01}), + "ddim_discretize": OptionInfo('uniform', "img2img DDIM discretize", gr.Radio, {"choices": ['uniform', 'quad']}), 's_churn': OptionInfo(0.0, "sigma churn", gr.Slider, {"minimum": 0.0, "maximum": 1.0, "step": 0.01}), 's_tmin': OptionInfo(0.0, "sigma tmin", gr.Slider, {"minimum": 0.0, "maximum": 1.0, "step": 0.01}), 's_noise': OptionInfo(1.0, "sigma noise", gr.Slider, {"minimum": 0.0, "maximum": 1.0, "step": 0.01}), diff --git a/scripts/xy_grid.py b/scripts/xy_grid.py index 7c01231f..24fa5a0a 100644 --- a/scripts/xy_grid.py +++ b/scripts/xy_grid.py @@ -87,12 +87,12 @@ axis_options = [ AxisOption("Prompt S/R", str, apply_prompt, format_value), AxisOption("Sampler", str, apply_sampler, format_value), AxisOption("Checkpoint name", str, apply_checkpoint, format_value), - AxisOption("Sigma Churn", float, apply_field("s_churn"), format_value_add_label), - AxisOption("Sigma min", float, apply_field("s_tmin"), format_value_add_label), - AxisOption("Sigma max", float, apply_field("s_tmax"), format_value_add_label), - AxisOption("Sigma noise", float, apply_field("s_noise"), format_value_add_label), - AxisOption("DDIM Eta", float, apply_field("ddim_eta"), format_value_add_label), - AxisOptionImg2Img("Denoising", float, apply_field("denoising_strength"), format_value_add_label),# as it is now all AxisOptionImg2Img items must go after AxisOption ones + AxisOption("Sigma Churn", float, apply_field("s_churn"), format_value_add_label), + AxisOption("Sigma min", float, apply_field("s_tmin"), format_value_add_label), + AxisOption("Sigma max", float, apply_field("s_tmax"), format_value_add_label), + AxisOption("Sigma noise", float, apply_field("s_noise"), format_value_add_label), + AxisOption("Eta", float, apply_field("eta"), format_value_add_label), + AxisOptionImg2Img("Denoising", float, apply_field("denoising_strength"), format_value_add_label), # as it is now all AxisOptionImg2Img items must go after AxisOption ones ] -- cgit v1.2.3 From 7acfaca05a13352a7d86d281db6ad64dfd9350e0 Mon Sep 17 00:00:00 2001 From: AUTOMATIC <16777216c@gmail.com> Date: Thu, 29 Sep 2022 00:59:44 +0300 Subject: update lists of models after merging them in checkpoints tab support saving as half --- modules/extras.py | 27 +++++++++++++++++---------- modules/sd_models.py | 15 ++++++++++----- modules/shared.py | 2 +- modules/ui.py | 42 ++++++++++++++++++++++++------------------ 4 files changed, 52 insertions(+), 34 deletions(-) (limited to 'modules/shared.py') diff --git a/modules/extras.py b/modules/extras.py index dcc0148c..9a825530 100644 --- a/modules/extras.py +++ b/modules/extras.py @@ -13,6 +13,7 @@ from modules.ui import plaintext_to_html import modules.codeformer_model import piexif import piexif.helper +import gradio as gr cached_images = {} @@ -140,7 +141,7 @@ def run_pnginfo(image): return '', geninfo, info -def run_modelmerger(primary_model_name, secondary_model_name, interp_method, interp_amount): +def run_modelmerger(primary_model_name, secondary_model_name, interp_method, interp_amount, save_as_half): # Linear interpolation (https://en.wikipedia.org/wiki/Linear_interpolation) def weighted_sum(theta0, theta1, alpha): return ((1 - alpha) * theta0) + (alpha * theta1) @@ -156,14 +157,14 @@ def run_modelmerger(primary_model_name, secondary_model_name, interp_method, int alpha = 0.5 - math.sin(math.asin(1.0 - 2.0 * alpha) / 3.0) return theta0 + ((theta1 - theta0) * alpha) - primary_model_filename = sd_models.checkpoints_list[primary_model_name].filename - secondary_model_filename = sd_models.checkpoints_list[secondary_model_name].filename + primary_model_info = sd_models.checkpoints_list[primary_model_name] + secondary_model_info = sd_models.checkpoints_list[secondary_model_name] - print(f"Loading {primary_model_filename}...") - primary_model = torch.load(primary_model_filename, map_location='cpu') + print(f"Loading {primary_model_info.filename}...") + primary_model = torch.load(primary_model_info.filename, map_location='cpu') - print(f"Loading {secondary_model_filename}...") - secondary_model = torch.load(secondary_model_filename, map_location='cpu') + print(f"Loading {secondary_model_info.filename}...") + secondary_model = torch.load(secondary_model_info.filename, map_location='cpu') theta_0 = primary_model['state_dict'] theta_1 = secondary_model['state_dict'] @@ -178,17 +179,23 @@ def run_modelmerger(primary_model_name, secondary_model_name, interp_method, int print(f"Merging...") for key in tqdm.tqdm(theta_0.keys()): if 'model' in key and key in theta_1: - theta_0[key] = theta_func(theta_0[key], theta_1[key], (float(1.0) - interp_amount)) # Need to reverse the interp_amount to match the desired mix ration in the merged checkpoint + theta_0[key] = theta_func(theta_0[key], theta_1[key], (float(1.0) - interp_amount)) # Need to reverse the interp_amount to match the desired mix ration in the merged checkpoint + if save_as_half: + theta_0[key] = theta_0[key].half() for key in theta_1.keys(): if 'model' in key and key not in theta_0: theta_0[key] = theta_1[key] + if save_as_half: + theta_0[key] = theta_0[key].half() - filename = primary_model_name + '_' + str(round(interp_amount,2)) + '-' + secondary_model_name + '_' + str(round((float(1.0) - interp_amount),2)) + '-' + interp_method.replace(" ", "_") + '-merged.ckpt' + filename = primary_model_info.model_name + '_' + str(round(interp_amount, 2)) + '-' + secondary_model_info.model_name + '_' + str(round((float(1.0) - interp_amount), 2)) + '-' + interp_method.replace(" ", "_") + '-merged.ckpt' output_modelname = os.path.join(shared.cmd_opts.ckpt_dir, filename) print(f"Saving to {output_modelname}...") torch.save(primary_model, output_modelname) + sd_models.list_models() + print(f"Checkpoint saved.") - return "Checkpoint saved to " + output_modelname + return ["Checkpoint saved to " + output_modelname] + [gr.Dropdown.update(choices=sd_models.checkpoint_tiles()) for _ in range(3)] diff --git a/modules/sd_models.py b/modules/sd_models.py index 9decc911..dd47dffb 100644 --- a/modules/sd_models.py +++ b/modules/sd_models.py @@ -23,6 +23,11 @@ except Exception: pass +def checkpoint_tiles(): + print(sorted([x.title for x in checkpoints_list.values()])) + return sorted([x.title for x in checkpoints_list.values()]) + + def list_models(): checkpoints_list.clear() @@ -39,13 +44,14 @@ def list_models(): if name.startswith("\\") or name.startswith("/"): name = name[1:] - return f'{name} [{h}]' + shortname = os.path.splitext(name.replace("/", "_").replace("\\", "_"))[0] + + return f'{name} [{h}]', shortname cmd_ckpt = shared.cmd_opts.ckpt if os.path.exists(cmd_ckpt): h = model_hash(cmd_ckpt) - title = modeltitle(cmd_ckpt, h) - model_name = title.rsplit(".",1)[0] # remove extension if present + title, model_name = modeltitle(cmd_ckpt, h) checkpoints_list[title] = CheckpointInfo(cmd_ckpt, title, h, model_name) elif cmd_ckpt is not None and cmd_ckpt != shared.default_sd_model_file: print(f"Checkpoint in --ckpt argument not found: {cmd_ckpt}", file=sys.stderr) @@ -53,8 +59,7 @@ def list_models(): if os.path.exists(model_dir): for filename in glob.glob(model_dir + '/**/*.ckpt', recursive=True): h = model_hash(filename) - title = modeltitle(filename, h) - model_name = title.rsplit(".",1)[0] # remove extension if present + title, model_name = modeltitle(filename, h) checkpoints_list[title] = CheckpointInfo(filename, title, h, model_name) diff --git a/modules/shared.py b/modules/shared.py index 39cf89bc..ec1e569b 100644 --- a/modules/shared.py +++ b/modules/shared.py @@ -190,7 +190,7 @@ options_templates.update(options_section(('system', "System"), { })) options_templates.update(options_section(('sd', "Stable Diffusion"), { - "sd_model_checkpoint": OptionInfo(None, "Stable Diffusion checkpoint", gr.Radio, lambda: {"choices": [x.title for x in modules.sd_models.checkpoints_list.values()]}), + "sd_model_checkpoint": OptionInfo(None, "Stable Diffusion checkpoint", gr.Radio, lambda: {"choices": modules.sd_models.checkpoint_tiles()}), "img2img_color_correction": OptionInfo(False, "Apply color correction to img2img results to match original colors."), "save_images_before_color_correction": OptionInfo(False, "Save a copy of image before applying color correction to img2img results"), "img2img_fix_steps": OptionInfo(False, "With img2img, do exactly the amount of steps the slider specifies (normally you'd do less with less denoising)."), diff --git a/modules/ui.py b/modules/ui.py index d51f7a08..4958036a 100644 --- a/modules/ui.py +++ b/modules/ui.py @@ -872,29 +872,16 @@ def create_ui(txt2img, img2img, run_extras, run_pnginfo, run_modelmerger): gr.HTML(value="

A merger of the two checkpoints will be generated in your /models directory.

") with gr.Row(): - ckpt_name_list = sorted([x.title for x in modules.sd_models.checkpoints_list.values()]) - primary_model_name = gr.Dropdown(ckpt_name_list, elem_id="modelmerger_primary_model_name", label="Primary Model Name") - secondary_model_name = gr.Dropdown(ckpt_name_list, elem_id="modelmerger_secondary_model_name", label="Secondary Model Name") + primary_model_name = gr.Dropdown(modules.sd_models.checkpoint_tiles(), elem_id="modelmerger_primary_model_name", label="Primary Model Name") + secondary_model_name = gr.Dropdown(modules.sd_models.checkpoint_tiles(), elem_id="modelmerger_secondary_model_name", label="Secondary Model Name") interp_amount = gr.Slider(minimum=0.0, maximum=1.0, step=0.05, label='Interpolation Amount', value=0.3) interp_method = gr.Radio(choices=["Weighted Sum", "Sigmoid", "Inverse Sigmoid"], value="Weighted Sum", label="Interpolation Method") - submit = gr.Button(elem_id="modelmerger_merge", label="Merge", variant='primary') + save_as_half = gr.Checkbox(value=False, label="Safe as float16") + modelmerger_merge = gr.Button(elem_id="modelmerger_merge", label="Merge", variant='primary') with gr.Column(variant='panel'): submit_result = gr.Textbox(elem_id="modelmerger_result", show_label=False) - submit.click( - fn=run_modelmerger, - inputs=[ - primary_model_name, - secondary_model_name, - interp_method, - interp_amount - ], - outputs=[ - submit_result, - ] - ) - def create_setting_component(key): def fun(): return opts.data[key] if key in opts.data else opts.data_labels[key].default @@ -918,6 +905,7 @@ def create_ui(txt2img, img2img, run_extras, run_pnginfo, run_modelmerger): return comp(label=info.label, value=fun, **(args or {})) components = [] + component_dict = {} def run_settings(*args): changed = 0 @@ -973,7 +961,9 @@ def create_ui(txt2img, img2img, run_extras, run_pnginfo, run_modelmerger): gr.HTML(elem_id="settings_header_text_{}".format(item.section[0]), value='

{}

'.format(item.section[1])) - components.append(create_setting_component(k)) + component = create_setting_component(k) + component_dict[k] = component + components.append(component) items_displayed += 1 request_notifications = gr.Button(value='Request browser notifications', elem_id="request_notifications") @@ -1024,6 +1014,22 @@ def create_ui(txt2img, img2img, run_extras, run_pnginfo, run_modelmerger): outputs=[result, text_settings], ) + modelmerger_merge.click( + fn=run_modelmerger, + inputs=[ + primary_model_name, + secondary_model_name, + interp_method, + interp_amount, + save_as_half, + ], + outputs=[ + submit_result, + primary_model_name, + secondary_model_name, + component_dict['sd_model_checkpoint'], + ] + ) paste_field_names = ['Prompt', 'Negative prompt', 'Steps', 'Face restoration', 'Seed', 'Size-1', 'Size-2'] txt2img_fields = [field for field,name in txt2img_paste_fields if name in paste_field_names] img2img_fields = [field for field,name in img2img_paste_fields if name in paste_field_names] -- cgit v1.2.3 From c1c27dad3ba371a5ae344b267c760aa51e77f193 Mon Sep 17 00:00:00 2001 From: AUTOMATIC <16777216c@gmail.com> Date: Thu, 29 Sep 2022 11:31:48 +0300 Subject: new implementation for attention/emphasis --- modules/prompt_parser.py | 87 +++++++++++++++++++++++++++++++++++++++- modules/sd_hijack.py | 102 +++++++++++++++++++++++++++++++++++++++++++++-- modules/shared.py | 3 +- 3 files changed, 186 insertions(+), 6 deletions(-) (limited to 'modules/shared.py') diff --git a/modules/prompt_parser.py b/modules/prompt_parser.py index a6a25b28..f3c50adf 100644 --- a/modules/prompt_parser.py +++ b/modules/prompt_parser.py @@ -126,5 +126,90 @@ def reconstruct_cond_batch(c: ScheduledPromptBatch, current_step): return res +re_attention = re.compile(r""" +\\\(| +\\\)| +\\\[| +\\]| +\\\\| +\\| +\(| +\[| +:([+-]?[.\d]+)\)| +\)| +]| +[^\\()\[\]:]+| +: +""", re.X) + + +def parse_prompt_attention(text): + """ + Parses a string with attention tokens and returns a list of pairs: text and its assoicated weight. + Accepted tokens are: + (abc) - increases attention to abc by a multiplier of 1.1 + (abc:3.12) - increases attention to abc by a multiplier of 3.12 + [abc] - decreases attention to abc by a multiplier of 1.1 + \( - literal character '(' + \[ - literal character '[' + \) - literal character ')' + \] - literal character ']' + \\ - literal character '\' + anything else - just text + + Example: + + 'a (((house:1.3)) [on] a (hill:0.5), sun, (((sky))).' + + produces: + + [ + ['a ', 1.0], + ['house', 1.5730000000000004], + [' ', 1.1], + ['on', 1.0], + [' a ', 1.1], + ['hill', 0.55], + [', sun, ', 1.1], + ['sky', 1.4641000000000006], + ['.', 1.1] + ] + """ -#get_learned_conditioning_prompt_schedules(["fantasy landscape with a [mountain:lake:0.25] and [an oak:a christmas tree:0.75][ in foreground::0.6][ in background:0.25] [shoddy:masterful:0.5]"], 100) + res = [] + round_brackets = [] + square_brackets = [] + + round_bracket_multiplier = 1.1 + square_bracket_multiplier = 1 / 1.1 + + def multiply_range(start_position, multiplier): + for p in range(start_position, len(res)): + res[p][1] *= multiplier + + for m in re_attention.finditer(text): + text = m.group(0) + weight = m.group(1) + + if text.startswith('\\'): + res.append([text[1:], 1.0]) + elif text == '(': + round_brackets.append(len(res)) + elif text == '[': + square_brackets.append(len(res)) + elif weight is not None and len(round_brackets) > 0: + multiply_range(round_brackets.pop(), float(weight)) + elif text == ')' and len(round_brackets) > 0: + multiply_range(round_brackets.pop(), round_bracket_multiplier) + elif text == ']' and len(square_brackets) > 0: + multiply_range(square_brackets.pop(), square_bracket_multiplier) + else: + res.append([text, 1.0]) + + for pos in round_brackets: + multiply_range(pos, round_bracket_multiplier) + + for pos in square_brackets: + multiply_range(pos, square_bracket_multiplier) + + return res diff --git a/modules/sd_hijack.py b/modules/sd_hijack.py index bfbd07f9..2848a251 100644 --- a/modules/sd_hijack.py +++ b/modules/sd_hijack.py @@ -6,6 +6,7 @@ import torch import numpy as np from torch import einsum +from modules import prompt_parser from modules.shared import opts, device, cmd_opts from ldm.util import default @@ -211,6 +212,7 @@ class StableDiffusionModelHijack: param_dict = getattr(param_dict, '_parameters') # fix for torch 1.12.1 loading saved file from torch 1.11 assert len(param_dict) == 1, 'embedding file has multiple terms in it' emb = next(iter(param_dict.items()))[1] + # diffuser concepts elif type(data) == dict and type(next(iter(data.values()))) == torch.Tensor: assert len(data.keys()) == 1, 'embedding file has multiple terms in it' @@ -236,7 +238,7 @@ class StableDiffusionModelHijack: print(traceback.format_exc(), file=sys.stderr) continue - print(f"Loaded a total of {len(self.word_embeddings)} text inversion embeddings.") + print(f"Loaded a total of {len(self.word_embeddings)} textual inversion embeddings.") def hijack(self, m): model_embeddings = m.cond_stage_model.transformer.text_model.embeddings @@ -275,6 +277,7 @@ class StableDiffusionModelHijack: _, remade_batch_tokens, _, _, _, token_count = self.clip.process_text([text]) return remade_batch_tokens[0], token_count, max_length + class FrozenCLIPEmbedderWithCustomWords(torch.nn.Module): def __init__(self, wrapped, hijack): super().__init__() @@ -300,7 +303,92 @@ class FrozenCLIPEmbedderWithCustomWords(torch.nn.Module): if mult != 1.0: self.token_mults[ident] = mult - def process_text(self, text): + + def tokenize_line(self, line, used_custom_terms, hijack_comments): + id_start = self.wrapped.tokenizer.bos_token_id + id_end = self.wrapped.tokenizer.eos_token_id + maxlen = self.wrapped.max_length + + if opts.enable_emphasis: + parsed = prompt_parser.parse_prompt_attention(line) + else: + parsed = [[line, 1.0]] + + tokenized = self.wrapped.tokenizer([text for text, _ in parsed], truncation=False, add_special_tokens=False)["input_ids"] + + fixes = [] + remade_tokens = [] + multipliers = [] + + for tokens, (text, weight) in zip(tokenized, parsed): + i = 0 + while i < len(tokens): + token = tokens[i] + + possible_matches = self.hijack.ids_lookup.get(token, None) + + if possible_matches is None: + remade_tokens.append(token) + multipliers.append(weight) + else: + found = False + for ids, word in possible_matches: + if tokens[i:i + len(ids)] == ids: + emb_len = int(self.hijack.word_embeddings[word].shape[0]) + fixes.append((len(remade_tokens), word)) + remade_tokens += [0] * emb_len + multipliers += [weight] * emb_len + i += len(ids) - 1 + found = True + used_custom_terms.append((word, self.hijack.word_embeddings_checksums[word])) + break + + if not found: + remade_tokens.append(token) + multipliers.append(weight) + i += 1 + + if len(remade_tokens) > maxlen - 2: + vocab = {v: k for k, v in self.wrapped.tokenizer.get_vocab().items()} + ovf = remade_tokens[maxlen - 2:] + overflowing_words = [vocab.get(int(x), "") for x in ovf] + overflowing_text = self.wrapped.tokenizer.convert_tokens_to_string(''.join(overflowing_words)) + hijack_comments.append(f"Warning: too many input tokens; some ({len(overflowing_words)}) have been truncated:\n{overflowing_text}\n") + + token_count = len(remade_tokens) + remade_tokens = remade_tokens + [id_end] * (maxlen - 2 - len(remade_tokens)) + remade_tokens = [id_start] + remade_tokens[0:maxlen - 2] + [id_end] + + multipliers = multipliers + [1.0] * (maxlen - 2 - len(multipliers)) + multipliers = [1.0] + multipliers[0:maxlen - 2] + [1.0] + + return remade_tokens, fixes, multipliers, token_count + + def process_text(self, texts): + used_custom_terms = [] + remade_batch_tokens = [] + hijack_comments = [] + hijack_fixes = [] + token_count = 0 + + cache = {} + batch_multipliers = [] + for line in texts: + if line in cache: + remade_tokens, fixes, multipliers = cache[line] + else: + remade_tokens, fixes, multipliers, token_count = self.tokenize_line(line, used_custom_terms, hijack_comments) + + cache[line] = (remade_tokens, fixes, multipliers) + + remade_batch_tokens.append(remade_tokens) + hijack_fixes.append(fixes) + batch_multipliers.append(multipliers) + + return batch_multipliers, remade_batch_tokens, used_custom_terms, hijack_comments, hijack_fixes, token_count + + + def process_text_old(self, text): id_start = self.wrapped.tokenizer.bos_token_id id_end = self.wrapped.tokenizer.eos_token_id maxlen = self.wrapped.max_length @@ -376,12 +464,18 @@ class FrozenCLIPEmbedderWithCustomWords(torch.nn.Module): return batch_multipliers, remade_batch_tokens, used_custom_terms, hijack_comments, hijack_fixes, token_count def forward(self, text): - batch_multipliers, remade_batch_tokens, used_custom_terms, hijack_comments, hijack_fixes, token_count = self.process_text(text) + + if opts.use_old_emphasis_implementation: + batch_multipliers, remade_batch_tokens, used_custom_terms, hijack_comments, hijack_fixes, token_count = self.process_text_old(text) + else: + batch_multipliers, remade_batch_tokens, used_custom_terms, hijack_comments, hijack_fixes, token_count = self.process_text(text) + + self.hijack.fixes = hijack_fixes self.hijack.comments = hijack_comments if len(used_custom_terms) > 0: - self.hijack.comments.append("Used custom terms: " + ", ".join([f'{word} [{checksum}]' for word, checksum in used_custom_terms])) + self.hijack.comments.append("Used embeddings: " + ", ".join([f'{word} [{checksum}]' for word, checksum in used_custom_terms])) tokens = torch.asarray(remade_batch_tokens).to(device) outputs = self.wrapped.transformer(input_ids=tokens) diff --git a/modules/shared.py b/modules/shared.py index ec1e569b..f88c2b02 100644 --- a/modules/shared.py +++ b/modules/shared.py @@ -195,7 +195,8 @@ options_templates.update(options_section(('sd', "Stable Diffusion"), { "save_images_before_color_correction": OptionInfo(False, "Save a copy of image before applying color correction to img2img results"), "img2img_fix_steps": OptionInfo(False, "With img2img, do exactly the amount of steps the slider specifies (normally you'd do less with less denoising)."), "enable_quantization": OptionInfo(False, "Enable quantization in K samplers for sharper and cleaner results. This may change existing seeds. Requires restart to apply."), - "enable_emphasis": OptionInfo(True, "Use (text) to make model pay more attention to text and [text] to make it pay less attention"), + "enable_emphasis": OptionInfo(True, "Eemphasis: use (text) to make model pay more attention to text and [text] to make it pay less attention"), + "use_old_emphasis_implementation": OptionInfo(False, "Use old emphasis implementation. Can be useful to reproduce old seeds."), "enable_batch_seeds": OptionInfo(True, "Make K-diffusion samplers produce same images in a batch as when making a single image"), "filter_nsfw": OptionInfo(False, "Filter NSFW content"), "random_artist_categories": OptionInfo([], "Allowed categories for random artists selection when using the Roll button", gr.CheckboxGroup, {"choices": artist_db.categories()}), -- cgit v1.2.3