diff --git a/DIFFUSERS.md b/DIFFUSERS.md index d5a99b5db..61ff1c17b 100644 --- a/DIFFUSERS.md +++ b/DIFFUSERS.md @@ -1,4 +1,129 @@ -# Diffusers WiP +# Additional Models + +SD.Next includes *experimental* support for additional model pipelines +This includes support for additional models such as: + +- **Stable Diffusion XL** +- **Kandinsky** +- **Deep Floyd IF** +- **Shap-E** + +Note that support is *experimental*, do not open [GitHub issues](https://github.com/vladmandic/automatic/issues) for those models +and instead reach-out on [Discord](https://discord.gg/WqMzTUDC) using dedicated channels + +*This has been made possible by integration of [huggingface diffusers](https://huggingface.co/docs/diffusers/index) library with help of huggingface team!* + +## How to + +- Install **SD.Next** as usual +- Start with + `webui --backend diffusers` +- To go back to standard execution pipeline, start with + `webui --backend original` + +## Integration + +### Standard workflows + +- **txt2txt** +- **img2img** +- **process** + +### Model Access + +- For standard SD 1.5 and SD 2.1 models, you can use either + standard *safetensor* models or *diffusers* models +- For additional models, you can use *diffusers* models only +- You can download diffuser models directly from [Huggingface hub](https://huggingface.co/) + or use built-in model search & download in SD.Next: **UI -> Models -> Huggingface** +- Note that access to some models is gated + In which case, you need to accept model EULA and provide your huggingface token + +### Extra Networks + +- Lora networks +- Textual inversions (embeddings) + +Note that Lora and TI need are still model-specific, so you cannot use Lora trained on SD 1.5 on SD-XL +(just like you couldn't do it on SD 2.1 model) - it needs to be trained for a specific model + +Support for SD-XL training is expected shortly + +### Diffuser Settings + +- UI -> Settings -> Diffuser Settings + contains additional tunable parameters + +### Samplers + +- Samplers (schedulers) are pipeline specific, so when running with diffuser backend, you'll see a different list of samplers +- UI -> Settings -> Sampler Settings shows different configurable parameters depending on backend +- Recommended sampler for diffusers is **DEIS** + +### Other + +- Updated **System Info** tab with additional information +- Support for `lowvram` and `medvram` modes + Additional tunables are available in UI -> Settings -> Diffuser Settings +- Support for both default **SDP** and **xFormers** cross-optimizations + Other cross-optimization methods are not available +- **Extra Networks UI** will show available diffusers models +- **CUDA model compile** + UI Settings -> Compute settings + Requires GPU with high VRAM + Diffusers recommend `reduce overhead`, but other methods are available as well + Fullgraph is possible (with sufficient vram) when using diffusers + +## SD-XL Notes + +- SD-XL model is designed as two-stage model + You can run SD-XL pipeline using just `base` model, but for best results, load both `base` and `refiner` models + - `base`: Trained on images with variety of aspect ratios and uses OpenCLIP-ViT/G and CLIP-ViT/L for text encoding + - `refiner`: Trained to denoise small noise levels of high quality data and uses the OpenCLIP model +- If you want to use `refiner` model, it is advised to add `sd_model_refiner` to **quicksettings** + in UI Settings -> User Interface +- SD-XL model was trained on **1024px** images + You can use it with smaller sizes, but you will likely get better results with SD 1.5 models +- SD-XL model NSFW filter has been turned off + +## Limitations + +- Diffusers do not have callbacks per-step, so any functionality that relies on that will not be available + This includes trival but very visible **progress bar** +- Any extension that requires access to model internals will likely not work when using diffusers backend + This for example includes standard extensions such as `ControlNet`, `MultiDiffusion`, `LyCORIS` +- Second-pass workflows such as `hires fix` are not yet implemented (soon) +- Hypernetworks +- Explit VAE usage (soon) + +## Performance + +Comparison of original stable diffusion pipeline and diffusers pipeline + +| pipeline | performance it/s | memory cpu/gpu | +| --- | --- | --- | +| original | 7.99 / 7.93 / 8.83 / 9.14 / 9.2 | 6.7 / 7.2 | +| original medvram | 6.23 / 7.16 / 8.41 / 9.24 / 9.68 | 8.4 / 6.8 | +| original lowvram | 1.05 / 1.94 / 3.2 / 4.81 / 6.46 | 8.8 / 5.2 | +| diffusers | 9 / 7.4 / 8.2 / 8.4 / 7.0 | 4.3 / 9.0 | +| diffusers medvram | 7.5 / 6.7 / 7.5 / 7.8 / 7.2 | 6.6 / 8.2 | +| diffusers lowvram | 7.0 / 7.0 / 7.4 / 7.7 / 7.8 | 4.3 / 7.2 | +| diffusers with safetensors | 8.9 / 7.3 / 8.1 / 8.4 / 7.1 | 5.9 / 9.0 | + +Notes: + +- Performance is measured using standard SD 1.5 model +- Performance is measured for `batch-size` 1, 2, 4, 8 16 +- Test environment: + - nVidia RTX 3060 GPU + - Torch 2.1-nightly with CUDA 12.1 + - Cross-optimization: SDP +- All being equal, diffussers seem to: + - Use slightly less RAM and more VRAM + - Have highly efficient medvram/lowvram equivalents which don't loose a lot of performance + - Faster on smaller batch sizes, slower on larger batch sizes + +## TODO initial support merged into `dev` branch @@ -13,61 +138,6 @@ lora support is not compatible with setting `Use LyCoris handler for all Lora ty to update repo, do not use `--upgrade` flag, use manual `git pull` instead -## Test - -### Standard - -goal is to test standard workflows (so not diffusers) to ensure there are no regressions -so diffusers code can be merged into `master` and we can continue with development there - -- run with `webui --debug --backend original` - -### Diffusers - -whats implemented so far? - -- new scheduler: deis -- simple model downloader for huggingface models: tabs -> models -> hf hub -- use huggingface models -- extra networks ui -- use safetensor models with diffusers backend -- lowvram and medvram equivalents for diffusers -- standard workflows: - - txt2img, img2img, inpaint, outpaint, process - - hires fix, restore faces, etc? -- textual inversion - yes, this applies to standard embedddings, don't need ones from huggingface -- lora - yes, this applies to standard loras, don't need ones from huggingface - but seems that diffuser lora support is somewhat limited, so quite a few loras may not work - you should see which lora loads without issues in console log -- system info tab with updated information -- kandinsky model - works for me - -### Experimental - -- cuda model compile - in settings -> compute settings - diffusers recommend `reduce overhead`, but other methods are available as well - it seems that fullgraph is possible (with sufficient vram) when using diffusers -- deepfloyd - in theory it should work, but its 20gb model so cant test it just yet - note that access is gated, so you'll need to download using your huggingface credentials - (you can still do it from sdnext ui, just need access token) - -## Todo - -- sdxl model -- no idea if sd21 works out-of-the-box -- hires fix? -- vae support - -## Limitations - -even if extensions are not supported, runtime errors are never nice -will need to handle in the code before we get out of alpha - - lycoris `lyco_patch_lora` - controlnet @@ -76,28 +146,11 @@ will need to handle in the code before we get out of alpha > sd_model.first_stage_model?.encoder? - dynamic-thresholding > AttributeError: 'DiffusionSampler' object has no attribute 'model_wrap_cfg' -- no per-step callback -## Performance - -| pipeline | performance it/s | memory cpu/gpu | -| --- | --- | --- | -| original | 7.99 / 7.93 / 8.83 / 9.14 / 9.2 | 6.7 / 7.2 | -| original medvram | 6.23 / 7.16 / 8.41 / 9.24 / 9.68 | 8.4 / 6.8 | -| original lowvram | 1.05 / 1.94 / 3.2 / 4.81 / 6.46 | 8.8 / 5.2 | -| diffusers | 9 / 7.4 / 8.2 / 8.4 / 7.0 | 4.3 / 9.0 | -| diffusers medvram | 7.5 / 6.7 / 7.5 / 7.8 / 7.2 | 6.6 / 8.2 | -| diffusers lowvram | 7.0 / 7.0 / 7.4 / 7.7 / 7.8 | 4.3 / 7.2 | -| diffusers with safetensors | 8.9 / 7.3 / 8.1 / 8.4 / 7.1 | 5.9 / 9.0 | - -Notes: - -- Performance is measured for `batch-size` 1, 2, 4, 8 16 -- Test environment: - - nVidia RTX 3060 GPU - - Torch 2.1-nightly with CUDA 12.1 - - Cross-optimization: SDP -- All being equal, diffussers seem to: - - Use slightly less RAM and more VRAM - - Have highly efficient medvram/lowvram equivalents which don't loose a lot of performance - - Faster on smaller batch sizes, slower on larger batch sizes +- diffusers pipeline in general no sampler per-step callback, its completely opaque inside the pipeline + so i'm missing some very basic stuff like progress bar in the ui or ability to generate live preview based on intermediate latents +- StableDiffusionXLPipeline does not implement `from_ckpt` +- StableDiffusionXLPipeline has long delay after tqdm progress bar finishes and before it returns an image, i assume its vae, but its not a good user-experience +- VAE: + > vae = AutoencoderKL.from_pretrained("stabilityai/sdxl-vae") + > pipe = StableDiffusionPipeline.from_pretrained(model, vae=vae) diff --git a/installer.py b/installer.py index 0ebcc0680..d54e95eb8 100644 --- a/installer.py +++ b/installer.py @@ -283,6 +283,7 @@ def check_torch(): log.debug(f'Torch overrides: cuda={args.use_cuda} rocm={args.use_rocm} ipex={args.use_ipex} diml={args.use_directml}') log.debug(f'Torch allowed: cuda={allow_cuda} rocm={allow_rocm} ipex={allow_ipex} diml={allow_directml}') torch_command = os.environ.get('TORCH_COMMAND', '') + xformers_package = os.environ.get('XFORMERS_PACKAGE', 'none') if torch_command != '': pass elif allow_cuda and (shutil.which('nvidia-smi') is not None or os.path.exists(os.path.join(os.environ.get('SystemRoot') or r'C:\Windows', 'System32', 'nvidia-smi.exe'))): @@ -294,11 +295,9 @@ def check_torch(): os.environ.setdefault('HSA_OVERRIDE_GFX_VERSION', '10.3.0') os.environ.setdefault('PYTORCH_HIP_ALLOC_CONF', 'garbage_collection_threshold:0.8,max_split_size_mb:512') torch_command = os.environ.get('TORCH_COMMAND', 'torch==2.0.1 torchvision==0.15.2 --index-url https://download.pytorch.org/whl/rocm5.4.2') - xformers_package = os.environ.get('XFORMERS_PACKAGE', 'none') elif allow_ipex and args.use_ipex and shutil.which('sycl-ls') is not None: log.info('Intel OneAPI Toolkit detected') torch_command = os.environ.get('TORCH_COMMAND', 'torch==1.13.0a0 torchvision==0.14.1a0 intel_extension_for_pytorch==1.13.120+xpu -f https://developer.intel.com/ipex-whl-stable-xpu') - xformers_package = os.environ.get('XFORMERS_PACKAGE', 'none') else: machine = platform.machine() if sys.platform == 'darwin': @@ -306,13 +305,11 @@ def check_torch(): elif allow_directml and args.use_directml and ('arm' not in machine and 'aarch' not in machine): log.info('Using DirectML Backend') torch_command = os.environ.get('TORCH_COMMAND', 'torch-directml') - xformers_package = os.environ.get('XFORMERS_PACKAGE', 'none') if 'torch' in torch_command and not args.version: install(torch_command, 'torch torchvision') else: log.info('Using CPU-only Torch') torch_command = os.environ.get('TORCH_COMMAND', 'torch torchvision') - xformers_package = os.environ.get('XFORMERS_PACKAGE', 'none') if 'torch' in torch_command and not args.version: install(torch_command, 'torch torchvision') if args.skip_torch: diff --git a/javascript/black-orange.css b/javascript/black-orange.css index 22c0e57c6..09cd5bb0f 100644 --- a/javascript/black-orange.css +++ b/javascript/black-orange.css @@ -1,10 +1,28 @@ /* generic html tags */ -:root { --font: "Source Sans Pro", 'ui-sans-serif', 'system-ui', "Roboto", sans-serif; } -html { font-size: 16px; } +:root { + --font: "Source Sans Pro", 'ui-sans-serif', 'system-ui', "Roboto", sans-serif; + --font-size: 16px; + --left-column: 490px; + --highlight-color: #ce6400; + --inactive-color: #4e1400; + --background-color: #000000; + --primary-50: #fff7ed; + --primary-100: #ffedd5; + --primary-200: #fed7aa; + --primary-300: #fdba74; + --primary-400: #fb923c; + --primary-500: #f97316; + --primary-600: #ea580c; + --primary-700: #c2410c; + --primary-800: #9a3412; + --primary-900: #7c2d12; + --primary-950: #6c2e12; +} +html { font-size: var(--font-size); } body, button, input, select, textarea { font-family: var(--font);} button { font-size: 1.2rem; } -img { background-color: black; } -input[type=range] { height: 18px; appearance: none; margin-top: 0; min-width: 160px; background-color: black; width: 100%; background: transparent; } +img { background-color: var(--background-color); } +input[type=range] { height: 18px; appearance: none; margin-top: 0; min-width: 160px; background-color: var(--background-color); width: 100%; background: transparent; } input[type=range]::-webkit-slider-runnable-track { width: 100%; height: 18px; cursor: pointer; box-shadow: 2px 2px 3px #111111; background: #50555C; border-radius: 2px; border: 0px solid #222222; } input[type=range]::-moz-range-track { width: 100%; height: 18px; cursor: pointer; box-shadow: 2px 2px 3px #111111; background: #50555C; border-radius: 2px; border: 0px solid #222222; } input[type=range]::-webkit-slider-thumb { box-shadow: 2px 2px 3px #111111; border: 0px solid #000000; height: 18px; width: 40px; border-radius: 2px; background: var(--highlight-color); cursor: pointer; appearance: none; margin-top: 0px; } @@ -14,10 +32,6 @@ input[type=range]::-moz-range-thumb { box-shadow: 2px 2px 3px #111111; border: 0 ::-webkit-scrollbar-thumb { background-color: var(--highlight-color); border-radius: 2px; border-width: 0; box-shadow: 2px 2px 3px #111111; } div.form { border-width: 0; box-shadow: none; background: transparent; overflow: visible; gap: 0.5em; margin-bottom: 6px; } -/* gradio shadowroot */ -.gradio-container { font-family: var(--font); --left-column: 490px; --highlight-color: #CE6400; --inactive-color: #4E1400; } - - /* gradio style classes */ fieldset .gr-block.gr-box, label.block span { padding: 0; margin-top: -4px; } .border-2 { border-width: 0; } @@ -27,11 +41,11 @@ fieldset .gr-block.gr-box, label.block span { padding: 0; margin-top: -4px; } .gr-button { font-weight: normal; box-shadow: 2px 2px 3px #111111; font-size: 0.8rem; min-width: 32px; min-height: 32px; padding: 3px; margin: 3px; } .gr-check-radio { background-color: var(--inactive-color); border-width: 0; border-radius: 2px; box-shadow: 2px 2px 3px #111111; } .gr-check-radio:checked { background-color: var(--highlight-color); } -.gr-compact { background-color: black; } +.gr-compact { background-color: var(--background-color); } .gr-form { border-width: 0; } .gr-input { background-color: #333333 !important; padding: 4px; margin: 4px; } .gr-input-label { color: lightyellow; border-width: 0; background: transparent; padding: 2px !important; } -.gr-panel { background-color: black; } +.gr-panel { background-color: var(--background-color); } .eta-bar { display: none !important } svg.feather.feather-image, .feather .feather-image { display: none } .gap-2 { padding-top: 8px; } @@ -42,9 +56,9 @@ svg.feather.feather-image, .feather .feather-image { display: none } .p-2 { padding: 0; } .px-4 { padding-lefT: 1rem; padding-right: 1rem; } .py-6 { padding-bottom: 0; } -.tabs { background-color: black; } +.tabs { background-color: var(--background-color); } .block.token-counter span { background-color: #222 !important; box-shadow: 2px 2px 2px #111; border: none !important; font-size: 0.8rem; } -.tab-nav { zoom: 120%; margin-bottom: 10px; border-bottom: 2px solid #CE6400 !important; padding-bottom: 2px; } +.tab-nav { zoom: 120%; margin-bottom: 10px; border-bottom: 2px solid var(--highlight-color) !important; padding-bottom: 2px; } .label-wrap { margin: 16px 0px 8px 0px; } .gradio-slider input[type="number"] { width: 4.5em; font-size: 0.8rem; } #tab_extensions table td, #tab_extensions table th { border: none; padding: 0.5em; } @@ -56,13 +70,13 @@ svg.feather.feather-image, .feather .feather-image { display: none } .progressDiv .progress { border-radius: 0 !important; background: var(--highlight-color); line-height: 3rem; height: 48px; } .gallery-item { box-shadow: none !important; } .performance { color: #888; } -.extra-networks { border-left: 2px solid #CE6400 !important; padding-left: 4px; } +.extra-networks { border-left: 2px solid var(--highlight-color) !important; padding-left: 4px; } .image-buttons { gap: 10px !important} /* gradio elements overrides */ #div.gradio-container { overflow-x: hidden; } #img2img_label_copy_to_img2img { font-weight: normal; } -#txt2img_prompt, #txt2img_neg_prompt, #img2img_prompt, #img2img_neg_prompt { background-color: black; box-shadow: 4px 4px 4px 0px #333333 !important; } +#txt2img_prompt, #txt2img_neg_prompt, #img2img_prompt, #img2img_neg_prompt { background-color: var(--background-color); box-shadow: 4px 4px 4px 0px #333333 !important; } #txt2img_prompt > label > textarea, #txt2img_neg_prompt > label > textarea, #img2img_prompt > label > textarea, #img2img_neg_prompt > label > textarea { font-size: 1.2rem; } #img2img_settings { min-width: calc(2 * var(--left-column)); max-width: calc(2 * var(--left-column)); background-color: #111111; padding-top: 16px; } #interrogate, #deepbooru { margin: 0 0px 10px 0px; max-width: 80px; max-height: 80px; font-weight: normal; font-size: 0.95em; } @@ -82,7 +96,7 @@ svg.feather.feather-image, .feather .feather-image { display: none } #extras_upscale { margin-top: 10px } #txt2img_progress_row > div { min-width: var(--left-column); max-width: var(--left-column); } -#txt2img_results, #img2img_results, #extras_results { background-color: black; padding: 0; } +#txt2img_results, #img2img_results, #extras_results { background-color: var(--background-color); padding: 0; } #txt2img_seed_row { padding: 0; margin-top: 8px; } #txt2img_settings { min-width: var(--left-column); max-width: var(--left-column); background-color: #111111; padding-top: 16px; } #txt2img_subseed_row { padding: 0; margin-top: 16px; } @@ -97,13 +111,13 @@ svg.feather.feather-image, .feather .feather-image { display: none } /* based on gradio built-in dark theme */ :root, .light, .dark { - --body-background-fill: black; + --body-background-fill: var(--background-color); --body-text-color: var(--neutral-100); --color-accent-soft: var(--neutral-700); --background-fill-primary: #222222; --background-fill-secondary: none; - --border-color-accent: black; - --border-color-primary: black; + --border-color-accent: var(--background-color); + --border-color-primary: var(--background-color); --link-text-color-active: var(--secondary-500); --link-text-color: var(--secondary-500); --link-text-color-hover: var(--secondary-400); @@ -183,17 +197,6 @@ svg.feather.feather-image, .feather .feather-image { display: none } --button-secondary-border-color-hover: var(--button-secondary-border-color); --button-secondary-text-color: white; --button-secondary-text-color-hover: var(--button-secondary-text-color); - --primary-50: #fff7ed; - --primary-100: #ffedd5; - --primary-200: #fed7aa; - --primary-300: #fdba74; - --primary-400: #fb923c; - --primary-500: #f97316; - --primary-600: #ea580c; - --primary-700: #c2410c; - --primary-800: #9a3412; - --primary-900: #7c2d12; - --primary-950: #6c2e12; --secondary-50: #eff6ff; --secondary-100: #dbeafe; --secondary-200: #bfdbfe; diff --git a/modules/api/api.py b/modules/api/api.py index 144d5e82e..9664fc1a8 100644 --- a/modules/api/api.py +++ b/modules/api/api.py @@ -406,17 +406,15 @@ class Api: def interruptapi(self): shared.state.interrupt() - return {} def unloadapi(self): - unload_model_weights() - + unload_model_weights(op='model') + unload_model_weights(op='refiner') return {} def reloadapi(self): reload_model_weights() - return {} def skip(self): diff --git a/modules/processing.py b/modules/processing.py index f0e55acc2..6bf8b5eaf 100644 --- a/modules/processing.py +++ b/modules/processing.py @@ -223,7 +223,8 @@ class StableDiffusionProcessing: source_image = devices.cond_cast_float(source_image) # HACK: Using introspection as the Depth2Image model doesn't appear to uniquely # identify itself with a field common to all models. The conditioning_key is also hybrid. - if backend == Backend.DIFFUSERS: # TODO: Diffusers img2img_image_conditioning + if backend == Backend.DIFFUSERS: + log.warning('Diffusers not implemented: img2img_image_conditioning') return None if isinstance(self.sd_model, LatentDepth2ImageDiffusion): return self.depth2img_image_conditioning(source_image) @@ -682,11 +683,6 @@ def process_images_inner(p: StableDiffusionProcessing) -> Processed: x_samples_ddim = torch.stack(x_samples_ddim).float() x_samples_ddim = torch.clamp((x_samples_ddim + 1.0) / 2.0, min=0.0, max=1.0) del samples_ddim - if shared.cmd_opts.lowvram or shared.cmd_opts.medvram: - lowvram.send_everything_to_cpu() - devices.torch_gc() - if p.scripts is not None: - p.scripts.postprocess_batch(p, x_samples_ddim, batch_number=n) elif backend == Backend.DIFFUSERS: generator_device = 'cpu' if shared.opts.diffusers_generator_device == "cpu" else shared.device @@ -695,7 +691,7 @@ def process_images_inner(p: StableDiffusionProcessing) -> Processed: sampler = sd_samplers.all_samplers_map.get(p.sampler_name, None) if sampler is None: sampler = sd_samplers.all_samplers_map.get("UniPC") - shared.sd_model.scheduler = sd_samplers.create_sampler(sampler.name, shared.sd_model) # TODO(Patrick): For wrapped pipelines this is currently a no-op + # shared.sd_model.scheduler = sd_samplers.create_sampler(sampler.name, shared.sd_model) # TODO(Patrick): For wrapped pipelines this is currently a no-op cross_attention_kwargs={} if lora_state['active']: @@ -708,23 +704,48 @@ def process_images_inner(p: StableDiffusionProcessing) -> Processed: elif sd_models.get_diffusers_task(shared.sd_model) == sd_models.DiffusersTaskType.INPAINTING: # TODO(PVP): change out to latents once possible with `diffusers` task_specific_kwargs = {"image": p.init_images[0], "mask_image": p.image_mask, "strength": p.denoising_strength} - output = shared.sd_model( + + output = shared.sd_model( # pylint: disable=not-callable prompt=prompts, negative_prompt=negative_prompts, num_inference_steps=p.steps, guidance_scale=p.cfg_scale, generator=generator, - output_type="np", + output_type='np' if shared.sd_refiner is None else 'latent', cross_attention_kwargs=cross_attention_kwargs, **task_specific_kwargs ) + + if shared.sd_refiner is not None: + init_image = output.images[0] + output = shared.sd_refiner( # pylint: disable=not-callable + prompt=prompts, + negative_prompt=negative_prompts, + num_inference_steps=p.steps, + guidance_scale=p.cfg_scale, + generator=generator, + output_type='np', + cross_attention_kwargs=cross_attention_kwargs, + image=init_image + ) + x_samples_ddim = output.images + + if p.enable_hr: + log.warning('Diffusers not implemented: hires fix') + if lora_state['active']: unload_diffusers_lora() else: raise ValueError(f"Unknown backend {backend}") + if shared.cmd_opts.lowvram or shared.cmd_opts.medvram: + lowvram.send_everything_to_cpu() + devices.torch_gc() + if p.scripts is not None: + p.scripts.postprocess_batch(p, x_samples_ddim, batch_number=n) + for i, x_sample in enumerate(x_samples_ddim): p.batch_index = i if backend == Backend.ORIGINAL: @@ -898,7 +919,23 @@ class StableDiffusionProcessingTxt2Img(StableDiffusionProcessing): if self.hr_upscaler is not None: self.extra_generation_params["Hires upscaler"] = self.hr_upscaler - def sample(self, conditioning, unconditional_conditioning, seeds, subseeds, subseed_strength, prompts): # TODO this is majority of processing time + def sample(self, conditioning, unconditional_conditioning, seeds, subseeds, subseed_strength, prompts): + + def save_intermediate(image, index): + """saves image before applying hires fix, if enabled in options; takes as an argument either an image or batch with latent space images""" + if not opts.save or self.do_not_save_samples or not opts.save_images_before_highres_fix: + return + if not isinstance(image, Image.Image): + image = sd_samplers.sample_to_image(image, index, approximation=0) + orig1 = self.extra_generation_params + orig2 = self.restore_faces + self.extra_generation_params = {} + self.restore_faces = False + info = create_infotext(self, self.all_prompts, self.all_seeds, self.all_subseeds, [], iteration=self.iteration, position_in_batch=index) + self.extra_generation_params = orig1 + self.restore_faces = orig2 + images.save_image(image, self.outpath_samples, "", seeds[index], prompts[index], opts.samples_format, info=info, suffix="-before-highres-fix") + if backend == Backend.DIFFUSERS: sd_models.set_diffuser_pipe(self.sd_model, sd_models.DiffusersTaskType.TEXT_2_IMAGE) @@ -916,21 +953,6 @@ class StableDiffusionProcessingTxt2Img(StableDiffusionProcessing): target_width = self.hr_upscale_to_x target_height = self.hr_upscale_to_y - def save_intermediate(image, index): - """saves image before applying hires fix, if enabled in options; takes as an argument either an image or batch with latent space images""" - if not opts.save or self.do_not_save_samples or not opts.save_images_before_highres_fix: - return - if not isinstance(image, Image.Image): - image = sd_samplers.sample_to_image(image, index, approximation=0) - orig1 = self.extra_generation_params - orig2 = self.restore_faces - self.extra_generation_params = {} - self.restore_faces = False - info = create_infotext(self, self.all_prompts, self.all_seeds, self.all_subseeds, [], iteration=self.iteration, position_in_batch=index) - self.extra_generation_params = orig1 - self.restore_faces = orig2 - images.save_image(image, self.outpath_samples, "", seeds[index], prompts[index], opts.samples_format, info=info, suffix="-before-highres-fix") - if latent_scale_mode is not None: for i in range(samples.shape[0]): save_intermediate(samples, i) diff --git a/modules/sd_models.py b/modules/sd_models.py index aad51403b..68e7a5a26 100644 --- a/modules/sd_models.py +++ b/modules/sd_models.py @@ -23,6 +23,7 @@ from modules.timer import Timer from modules.memstats import memory_stats from modules.paths_internal import models_path + transformers_logging.set_verbosity_error() model_dir = "Stable-diffusion" model_path = os.path.abspath(os.path.join(paths.models_path, model_dir)) @@ -127,7 +128,9 @@ def list_models(): checkpoints_list.clear() checkpoint_aliases.clear() ext_filter=[".safetensors"] if shared.opts.sd_disable_ckpt else [".ckpt", ".safetensors"] - model_list = modelloader.load_models(model_path=model_path, model_url=None, command_path=shared.opts.ckpt_dir, ext_filter=ext_filter, download_name=None, ext_blacklist=[".vae.ckpt", ".vae.safetensors"]) + model_list = [] + if shared.backend == shared.Backend.ORIGINAL or shared.opts.diffusers_pipeline == shared.pipelines[0]: + model_list += modelloader.load_models(model_path=model_path, model_url=None, command_path=shared.opts.ckpt_dir, ext_filter=ext_filter, download_name=None, ext_blacklist=[".vae.ckpt", ".vae.safetensors"]) if shared.backend == shared.Backend.DIFFUSERS: model_list += modelloader.load_diffusers_models(model_path=os.path.join(models_path, 'Diffusers'), command_path=shared.opts.diffusers_dir) @@ -210,11 +213,18 @@ def model_hash(filename): return 'NOHASH' -def select_checkpoint(model=True): - model_checkpoint = shared.opts.sd_model_checkpoint if model else shared.opts.sd_model_dict +def select_checkpoint(op='model'): + if op == 'model': + model_checkpoint = shared.opts.sd_model_checkpoint + elif op == 'dict': + model_checkpoint = shared.opts.sd_model_dict + elif op == 'refiner': + model_checkpoint = shared.opts.data['sd_model_refiner'] + if model_checkpoint is None or model_checkpoint == 'None': + return None checkpoint_info = get_closet_checkpoint_match(model_checkpoint) if checkpoint_info is not None: - shared.log.debug(f'Select checkpoint: {checkpoint_info.title if checkpoint_info is not None else None}') + shared.log.debug(f'Select checkpoint: {op} {checkpoint_info.title if checkpoint_info is not None else None}') return checkpoint_info if len(checkpoints_list) == 0: shared.log.error("Cannot run without a checkpoint") @@ -458,9 +468,10 @@ sd1_clip_weight = 'cond_stage_model.transformer.text_model.embeddings.token_embe sd2_clip_weight = 'cond_stage_model.model.transformer.resblocks.0.attn.in_proj_weight' -class SdModelData: +class ModelData: def __init__(self): self.sd_model = None + self.sd_refiner = None self.sd_dict = 'None' self.initial = True self.lock = threading.Lock() @@ -470,9 +481,9 @@ class SdModelData: with self.lock: try: if shared.backend == shared.Backend.ORIGINAL: - reload_model_weights() + reload_model_weights(op='model') elif shared.backend == shared.Backend.DIFFUSERS: - load_diffuser() + load_diffuser(op='model') else: shared.log.error(f"Unknown Stable Diffusion backend: {shared.backend}") self.initial = False @@ -483,11 +494,31 @@ class SdModelData: return self.sd_model def set_sd_model(self, v): + shared.log.debug(f"Class model: {v}") self.sd_model = v + def get_sd_refiner(self): + if self.sd_model is None: + with self.lock: + try: + if shared.backend == shared.Backend.ORIGINAL: + reload_model_weights(op='refiner') + elif shared.backend == shared.Backend.DIFFUSERS: + load_diffuser(op='refiner') + else: + shared.log.error(f"Unknown Stable Diffusion backend: {shared.backend}") + self.initial = False + except Exception as e: + shared.log.error("Failed to load stable diffusion model") + errors.display(e, "loading stable diffusion model") + self.sd_refiner = None + return self.sd_refiner -model_data = SdModelData() + def set_sd_refiner(self, v): + shared.log.debug(f"Class refiner: {v}") + self.sd_refiner = v +model_data = ModelData() class PriorPipeline: def __init__(self, prior, main): @@ -531,7 +562,7 @@ class PriorPipeline: return result -def load_diffuser(checkpoint_info=None, already_loaded_state_dict=None, timer=None): # pylint: disable=unused-argument +def load_diffuser(checkpoint_info=None, already_loaded_state_dict=None, timer=None, op='model'): # pylint: disable=unused-argument if timer is None: timer = Timer() import logging @@ -548,27 +579,61 @@ def load_diffuser(checkpoint_info=None, already_loaded_state_dict=None, timer=No if shared.opts.data['sd_model_checkpoint'] == 'model.ckpt': shared.opts.data['sd_model_checkpoint'] = "runwayml/stable-diffusion-v1-5" + + if op == 'model' or op == 'dict': + if model_data.sd_model is not None and (checkpoint_info.hash == model_data.sd_model.sd_checkpoint_info.hash): # trying to load the same model + return + else: + if model_data.sd_refiner is not None and (checkpoint_info.hash == model_data.sd_refiner.sd_checkpoint_info.hash): # trying to load the same model + return + sd_model = None try: - devices.set_cuda_params() # todo + devices.set_cuda_params() if shared.cmd_opts.ckpt is not None and model_data.initial: # initial load model_name = modelloader.find_diffuser(shared.cmd_opts.ckpt) if model_name is not None: - shared.log.info(f'Loading diffuser model: {model_name}') + shared.log.info(f'Loading diffuser {op}: {model_name}') model_file = modelloader.download_diffusers_model(hub_id=model_name) sd_model = diffusers.DiffusionPipeline.from_pretrained(model_file, **diffusers_load_config) list_models() # rescan for downloaded model checkpoint_info = CheckpointInfo(model_name) if sd_model is None: - checkpoint_info = checkpoint_info or select_checkpoint() - shared.log.info(f'Loading diffuser model: {checkpoint_info.filename}') + checkpoint_info = checkpoint_info or select_checkpoint(op=op) + if checkpoint_info is None: + unload_model_weights(op=op) + return + shared.log.info(f'Loading diffuser {op}: {checkpoint_info.filename}') if not os.path.isfile(checkpoint_info.path): sd_model = diffusers.DiffusionPipeline.from_pretrained(checkpoint_info.path, **diffusers_load_config) else: diffusers_load_config["local_files_only "] = True diffusers_load_config["extract_ema"] = shared.opts.diffusers_extract_ema - sd_model = diffusers.StableDiffusionPipeline.from_ckpt(checkpoint_info.path, **diffusers_load_config) + try: + # pipelines = ['Stable Diffusion', 'Stable Diffusion XL', 'Kandinsky V1', 'Kandinsky V2', 'DeepFloyd IF', 'Shap-E'] + if shared.opts.diffusers_pipeline == shared.pipelines[0]: + pipeline = diffusers.StableDiffusionPipeline + elif shared.opts.diffusers_pipeline == shared.pipelines[1]: + pipeline = diffusers.StableDiffusionXLPipeline + elif shared.opts.diffusers_pipeline == shared.pipelines[2]: + pipeline = diffusers.KandinskyPipeline + elif shared.opts.diffusers_pipeline == shared.pipelines[3]: + pipeline = diffusers.KandinskyV22Pipeline + elif shared.opts.diffusers_pipeline == shared.pipelines[4]: + pipeline = diffusers.IFPipeline + elif shared.opts.diffusers_pipeline == shared.pipelines[5]: + pipeline = diffusers.ShapEPipeline + else: + shared.log.error(f'Diffusers unknown pipeline: {shared.opts.diffusers_pipeline}') + except Exception as e: + shared.log.error(f'Diffusers failed initializing pipeline: {shared.opts.diffusers_pipeline} {e}') + return + try: + sd_model = pipeline.from_ckpt(checkpoint_info.path, **diffusers_load_config) + except Exception as e: + shared.log.error(f'Diffusers failed loading model using pipeline: {checkpoint_info.path} {shared.opts.diffusers_pipeline} {e}') + return if "StableDiffusion" in sd_model.__class__.__name__: pass # scheduler is created on first use @@ -615,7 +680,7 @@ def load_diffuser(checkpoint_info=None, already_loaded_state_dict=None, timer=No sd_model.unet.to(memory_format=torch.channels_last) if shared.opts.cuda_compile and torch.cuda.is_available(): sd_model.to(devices.device) - import torch._dynamo # pylint: disable=unused-import + import torch._dynamo # pylint: disable=unused-import,redefined-outer-name log_level = logging.WARNING if shared.opts.cuda_compile_verbose else logging.CRITICAL # pylint: disable=protected-access torch._logging.set_logs(dynamo=log_level, aot=log_level, inductor=log_level) # pylint: disable=protected-access torch._dynamo.config.verbose = shared.opts.cuda_compile_verbose # pylint: disable=protected-access @@ -632,7 +697,11 @@ def load_diffuser(checkpoint_info=None, already_loaded_state_dict=None, timer=No except Exception as e: shared.log.error("Failed to load diffusers model") errors.display(e, "loading Diffusers model") - shared.sd_model = sd_model + + if op == 'refiner': + model_data.sd_refiner = sd_model + else: + model_data.sd_model = sd_model from modules.textual_inversion import textual_inversion embedding_db = textual_inversion.EmbeddingDatabase() @@ -685,7 +754,7 @@ def set_diffuser_pipe(pipe, new_pipe_type): new_pipe.sd_model_checkpoint = sd_model_checkpoint new_pipe.sd_model_hash = sd_model_hash - shared.sd_model = new_pipe + model_data.sd_model = new_pipe shared.log.info(f"Pipeline class changed from {pipe.__class__.__name__} to {new_pipe_cls.__name__}") @@ -700,21 +769,32 @@ def get_diffusers_task(pipe: diffusers.DiffusionPipeline) -> DiffusersTaskType: return DiffusersTaskType.TEXT_2_IMAGE -def load_model(checkpoint_info=None, already_loaded_state_dict=None, timer=None): +def load_model(checkpoint_info=None, already_loaded_state_dict=None, timer=None, op='model'): from modules import lowvram, sd_hijack - checkpoint_info = checkpoint_info or select_checkpoint() + checkpoint_info = checkpoint_info or select_checkpoint(op=op) if checkpoint_info is None: return - if model_data.sd_model is not None and (checkpoint_info.hash == model_data.sd_model.sd_checkpoint_info.hash): # trying to load the same model - return - shared.log.debug(f'Load model: name={checkpoint_info.filename} dict={already_loaded_state_dict is not None}') + if op == 'model' or op == 'dict': + if model_data.sd_model is not None and (checkpoint_info.hash == model_data.sd_model.sd_checkpoint_info.hash): # trying to load the same model + return + else: + if model_data.sd_refiner is not None and (checkpoint_info.hash == model_data.sd_refiner.sd_checkpoint_info.hash): # trying to load the same model + return + shared.log.debug(f'Load {op}: name={checkpoint_info.filename} dict={already_loaded_state_dict is not None}') if timer is None: timer = Timer() current_checkpoint_info = None - if model_data.sd_model is not None: - sd_hijack.model_hijack.undo_hijack(model_data.sd_model) - current_checkpoint_info = model_data.sd_model.sd_checkpoint_info - unload_model_weights() + if op == 'model' or op == 'dict': + if model_data.sd_model is not None: + sd_hijack.model_hijack.undo_hijack(model_data.sd_model) + current_checkpoint_info = model_data.sd_model.sd_checkpoint_info + unload_model_weights(op=op) + else: + if model_data.sd_refiner is not None: + sd_hijack.model_hijack.undo_hijack(model_data.sd_refiner) + current_checkpoint_info = model_data.sd_refiner.sd_checkpoint_info + unload_model_weights(op=op) + do_inpainting_hijack() devices.set_cuda_params() if already_loaded_state_dict is not None: @@ -760,7 +840,10 @@ def load_model(checkpoint_info=None, already_loaded_state_dict=None, timer=None) sd_model = torch.xpu.optimize(sd_model, dtype=devices.dtype, auto_kernel_selection=True, optimize_lstm=True, graph_mode=True if shared.opts.cuda_compile and shared.opts.cuda_compile_mode == 'ipex' else False) shared.log.info("Applied IPEX Optimize") - model_data.sd_model = sd_model + if op == 'refiner': + model_data.sd_refiner = sd_model + else: + model_data.sd_model = sd_model sd_hijack.model_hijack.embedding_db.load_textual_inversion_embeddings(force_reload=True) # Reload embeddings after model load as they may or may not fit the model timer.record("embeddings") script_callbacks.model_loaded_callback(sd_model) @@ -771,7 +854,7 @@ def load_model(checkpoint_info=None, already_loaded_state_dict=None, timer=None) shared.log.info(f'Model load finished: {memory_stats()} cached={len(checkpoints_loaded.keys())}') -def reload_model_weights(sd_model=None, info=None, reuse_dict=False): +def reload_model_weights(sd_model=None, info=None, reuse_dict=False, op='model'): load_dict = shared.opts.sd_model_dict != model_data.sd_dict global skip_next_load # pylint: disable=global-statement if skip_next_load: @@ -779,15 +862,18 @@ def reload_model_weights(sd_model=None, info=None, reuse_dict=False): skip_next_load = False return from modules import lowvram, sd_hijack - checkpoint_info = info or select_checkpoint(model=not load_dict) # are we selecting model or dictionary - next_checkpoint_info = info or select_checkpoint(model=load_dict) if load_dict else None + checkpoint_info = info or select_checkpoint(op=op) # are we selecting model or dictionary + next_checkpoint_info = info or select_checkpoint(op='dict' if load_dict else 'model') if load_dict else None + if checkpoint_info is None: + unload_model_weights(op=op) + return if load_dict: shared.log.debug(f'Model dict: existing={sd_model is not None} target={checkpoint_info.filename} info={info}') else: model_data.sd_dict = 'None' shared.log.debug(f'Load model weights: existing={sd_model is not None} target={checkpoint_info.filename} info={info}') if not sd_model: - sd_model = model_data.sd_model + sd_model = model_data.sd_model if op == 'model' or op == 'dict' else model_data.sd_refiner if sd_model is None: # previous model load failed current_checkpoint_info = None else: @@ -802,7 +888,7 @@ def reload_model_weights(sd_model=None, info=None, reuse_dict=False): shared.log.info('Reusing previous model dictionary') sd_hijack.model_hijack.undo_hijack(sd_model) else: - unload_model_weights() + unload_model_weights(op=op) sd_model = None timer = Timer() state_dict = get_checkpoint_state_dict(checkpoint_info, timer) @@ -811,14 +897,14 @@ def reload_model_weights(sd_model=None, info=None, reuse_dict=False): if sd_model is None or checkpoint_config != sd_model.used_config: del sd_model if shared.backend == shared.Backend.ORIGINAL: - load_model(checkpoint_info, already_loaded_state_dict=state_dict, timer=timer) + load_model(checkpoint_info, already_loaded_state_dict=state_dict, timer=timer, op=op) else: - load_diffuser(checkpoint_info, already_loaded_state_dict=state_dict, timer=timer) + load_diffuser(checkpoint_info, already_loaded_state_dict=state_dict, timer=timer, op=op) if load_dict and next_checkpoint_info is not None: model_data.sd_dict = shared.opts.sd_model_dict shared.opts.data["sd_model_checkpoint"] = next_checkpoint_info.title reload_model_weights(reuse_dict=True) # ok we loaded dict now lets redo and load model on top of it - return model_data.sd_model + return model_data.sd_model if op == 'model' or op == 'dict' else model_data.sd_refiner try: load_model_weights(sd_model, checkpoint_info, state_dict, timer) except Exception: @@ -835,17 +921,22 @@ def reload_model_weights(sd_model=None, info=None, reuse_dict=False): shared.log.info(f"Weights loaded in {timer.summary()}") -def unload_model_weights(sd_model=None, _info=None): +def unload_model_weights(op='model'): from modules import sd_hijack - if model_data.sd_model: - model_data.sd_model.to(devices.cpu) - if shared.backend == shared.Backend.ORIGINAL: - sd_hijack.model_hijack.undo_hijack(model_data.sd_model) - sd_model = None - model_data.sd_model = None - devices.torch_gc(force=True) - shared.log.debug(f'Model weights unloaded: {memory_stats()}') - return sd_model + if op == 'model' or op == 'dict': + if model_data.sd_model: + model_data.sd_model.to(devices.cpu) + if shared.backend == shared.Backend.ORIGINAL: + sd_hijack.model_hijack.undo_hijack(model_data.sd_model) + model_data.sd_model = None + else: + if model_data.sd_refiner: + model_data.sd_refiner.to(devices.cpu) + if shared.backend == shared.Backend.ORIGINAL: + sd_hijack.model_hijack.undo_hijack(model_data.sd_refiner) + model_data.sd_refiner = None + shared.log.debug(f'Weights unloaded {op}: {memory_stats()}') + devices.torch_gc(force=True) def apply_token_merging(sd_model, token_merging_ratio): diff --git a/modules/shared.py b/modules/shared.py index e02fadac3..b0514d278 100644 --- a/modules/shared.py +++ b/modules/shared.py @@ -38,6 +38,7 @@ hypernetworks = {} loaded_hypernetworks = [] gradio_theme = gr.themes.Base() settings_components = None +pipelines = ['Stable Diffusion', 'Stable Diffusion XL', 'Kandinsky V1', 'Kandinsky V2', 'DeepFloyd IF', 'Shap-E'] latent_upscale_default_mode = "Latent" latent_upscale_modes = { "Latent": {"mode": "bilinear", "antialias": False}, @@ -217,6 +218,10 @@ class OptionInfo: self.comment_after += f"({info})" return self + def html(self, info): + self.comment_after += f"{info}" + return self + def needs_restart(self): self.comment_after += " (requires restart)" return self @@ -295,8 +300,9 @@ else: # cuda cross_attention_optimization_default ="Scaled-Dot-Product" options_templates.update(options_section(('sd', "Stable Diffusion"), { - "sd_model_checkpoint": OptionInfo(default_checkpoint, "Stable Diffusion checkpoint", gr.Dropdown, lambda: {"choices": list_checkpoint_tiles()}, refresh=refresh_checkpoints), "sd_checkpoint_autoload": OptionInfo(True, "Stable Diffusion checkpoint autoload on server start"), + "sd_model_checkpoint": OptionInfo(default_checkpoint, "Stable Diffusion checkpoint", gr.Dropdown, lambda: {"choices": list_checkpoint_tiles()}, refresh=refresh_checkpoints), + "sd_model_refiner": OptionInfo('None', "Stable Diffusion refiner", gr.Dropdown, lambda: {"choices": ['None'] + list_checkpoint_tiles()}, refresh=refresh_checkpoints), "sd_checkpoint_cache": OptionInfo(0, "Number of cached model checkpoints", gr.Slider, {"minimum": 0, "maximum": 10, "step": 1}), "sd_vae_checkpoint_cache": OptionInfo(0, "Number of cached VAE checkpoints", gr.Slider, {"minimum": 0, "maximum": 10, "step": 1}), "sd_vae": OptionInfo("Automatic", "Select VAE", gr.Dropdown, lambda: {"choices": shared_items.sd_vae_items()}, refresh=shared_items.refresh_vae_list), @@ -341,10 +347,11 @@ options_templates.update(options_section(('cuda', "Compute Settings"), { "cuda_compile_fullgraph": OptionInfo(False, "Model compile fullgraph"), "cuda_compile_verbose": OptionInfo(False, "Model compile verbose mode"), "cuda_compile_errors": OptionInfo(True, "Model compile suppress errors"), - "disable_gc": OptionInfo(False, "Disable Torch memory garbage collection"), + "disable_gc": OptionInfo(True, "Disable Torch memory garbage collection on each generation"), })) options_templates.update(options_section(('diffusers', "Diffusers Settings"), { + "diffusers_pipeline": OptionInfo(pipelines[0], 'Diffuser Pipeline', gr.Dropdown, lambda: {"choices": pipelines}), "diffusers_extract_ema": OptionInfo(True, "Use model EMA weights when possible"), "diffusers_generator_device": OptionInfo("default", "Generator device", gr.Radio, lambda: {"choices": ["default", "cpu"]}), "diffusers_seq_cpu_offload": OptionInfo(False, "Enable sequential CPU offload"), @@ -905,7 +912,6 @@ class Shared(sys.modules[__name__].__class__): @property def sd_model(self): import modules.sd_models # pylint: disable=W0621 - # return modules.sd_models.model_data.sd_model return modules.sd_models.model_data.get_sd_model() @sd_model.setter @@ -913,6 +919,17 @@ class Shared(sys.modules[__name__].__class__): import modules.sd_models # pylint: disable=W0621 modules.sd_models.model_data.set_sd_model(value) -# sd_model: LatentDiffusion = None # this var is here just for IDE's type checking; it cannot be accessed because the class field above will be accessed instead + @property + def sd_refiner(self): + import modules.sd_models # pylint: disable=W0621 + return modules.sd_models.model_data.get_sd_refiner() + + @sd_refiner.setter + def sd_refiner(self, value): + import modules.sd_models # pylint: disable=W0621 + modules.sd_models.model_data.set_sd_refiner(value) + + sd_model = None +sd_refiner = None sys.modules[__name__].__class__ = Shared diff --git a/modules/textual_inversion/textual_inversion.py b/modules/textual_inversion/textual_inversion.py index 27170eb40..645eacdc9 100644 --- a/modules/textual_inversion/textual_inversion.py +++ b/modules/textual_inversion/textual_inversion.py @@ -145,8 +145,12 @@ class EmbeddingDatabase: self.word_embeddings[name] = embedding except Exception: self.skipped_embeddings[name] = embedding - text_inv_tokens = pipe.tokenizer.added_tokens_encoder.keys() - text_inv_tokens = [t for t in text_inv_tokens if not (len(t.split("_")) > 1 and t.split("_")[-1].isdigit())] + try: + text_inv_tokens = pipe.tokenizer.added_tokens_encoder.keys() + text_inv_tokens = [t for t in text_inv_tokens if not (len(t.split("_")) > 1 and t.split("_")[-1].isdigit())] + except Exception: + text_inv_tokens = [] + pass def load_from_file(self, path, filename): name, ext = os.path.splitext(filename) diff --git a/modules/ui.py b/modules/ui.py index 60ccc6cd1..37b6c0363 100644 --- a/modules/ui.py +++ b/modules/ui.py @@ -1031,7 +1031,8 @@ def create_ui(startup_timer = None): create_dirty_indicator("show_all_pages", [], interactive=False) def unload_sd_weights(): - modules.sd_models.unload_model_weights() + modules.sd_models.unload_model_weights(op='model') + modules.sd_models.unload_model_weights(op='refiner') def reload_sd_weights(): modules.sd_models.reload_model_weights() diff --git a/webui.py b/webui.py index b78919c47..930e4cd3a 100644 --- a/webui.py +++ b/webui.py @@ -167,14 +167,18 @@ def load_model(): if opts.sd_checkpoint_autoload: shared.state.begin() shared.state.job = 'load model' - thread = Thread(target=lambda: shared.sd_model) - thread.start() + thread_model = Thread(target=lambda: shared.sd_model) + thread_model.start() + thread_refiner = Thread(target=lambda: shared.sd_refiner) + thread_refiner.start() shared.state.end() - thread.join() + thread_model.join() + thread_refiner.join() else: log.debug('Model auto load disabled') - shared.opts.onchange("sd_model_checkpoint", wrap_queued_call(lambda: modules.sd_models.reload_model_weights()), call=False) - shared.opts.onchange("sd_model_dict", wrap_queued_call(lambda: modules.sd_models.reload_model_weights()), call=False) + shared.opts.onchange("sd_model_checkpoint", wrap_queued_call(lambda: modules.sd_models.reload_model_weights(op='model')), call=False) + shared.opts.onchange("sd_model_refiner", wrap_queued_call(lambda: modules.sd_models.reload_model_weights(op='refiner')), call=False) + shared.opts.onchange("sd_model_dict", wrap_queued_call(lambda: modules.sd_models.reload_model_weights(op='dict')), call=False) startup_timer.record("checkpoint") diff --git a/wiki b/wiki index f941746c0..28e3cc15e 160000 --- a/wiki +++ b/wiki @@ -1 +1 @@ -Subproject commit f941746c0ed2afcaa37c1ec77b86da4dee131bee +Subproject commit 28e3cc15ef4566564764fa73542ab6b00d2b0959