diff --git a/CHANGELOG.md b/CHANGELOG.md index 8af1d23a5..2917625ab 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,13 @@ # Change Log for SD.Next +## Update for 2023-09-18 + +Downgrade of `diffusers` to 0.20.2 due to critical issue with model offloading +This means that new model **Wuerstchen** is not supported until diffusers issue is resolved + +- Added **change log** to UI, see *System -> Changelog* +- **Extra networks**: faster search, ability to show/hide/sort networks + ## Update for 2023-09-13 Started as a mostly a service release with quite a few fixes, but then... @@ -22,11 +30,11 @@ Major changes how **hires** works as well as support for a very interesting new - diffusers: - allow loading of sd/sdxl models from safetensors without online connectivity - support for new model: [wuerstchen](https://huggingface.co/warp-ai/wuerstchen) - its a high-resolution model (1024px+) that nearly doubls performance of sd-xl with much lower resource requirements + its a high-resolution model (1024px+) thats ~40% faster than sd-xl with a bit lower resource requirements go to *models -> huggingface -> search "warp-ai/wuerstchen" -> download* its nearly 12gb in size, so be patient :) - minor re-layout of the main ui -- update **ui hints** +- updated **ui hints** - updated **models -> civitai** - search and download loras - find previews for already downloaded models or loras diff --git a/extensions-builtin/LDSR/scripts/ldsr_model.py b/extensions-builtin/LDSR/scripts/ldsr_model.py index 9c40740a5..e2c671b15 100644 --- a/extensions-builtin/LDSR/scripts/ldsr_model.py +++ b/extensions-builtin/LDSR/scripts/ldsr_model.py @@ -2,8 +2,6 @@ import os import sys import traceback -from basicsr.utils.download_util import load_file_from_url - from modules.upscaler import Upscaler, UpscalerData from ldsr_model_arch import LDSR from modules import shared, script_callbacks @@ -42,6 +40,7 @@ class UpscalerLDSR(Upscaler): print("Renaming model from model.pth to model.ckpt") os.rename(old_model_path, new_model_path) + from modules.modelloader import load_file_from_url if local_safetensors_path is not None and os.path.exists(local_safetensors_path): model = local_safetensors_path else: diff --git a/extensions-builtin/Lora/lora.py b/extensions-builtin/Lora/lora.py index fbac7e8fc..f45595cf6 100644 --- a/extensions-builtin/Lora/lora.py +++ b/extensions-builtin/Lora/lora.py @@ -145,11 +145,11 @@ def assign_lora_names_to_compvis_modules(sd_model): sd_model.lora_layer_mapping = lora_layer_mapping -def load_diffuser_lora(name, lora_on_disk, multiplier): +def load_diffuser_lora(name, lora_on_disk, multiplier, num_loras): lora = LoraModule(name, lora_on_disk) lora.mtime = os.path.getmtime(lora_on_disk.filename) from modules.lora_diffusers import load_diffusers_lora - load_diffusers_lora(name, lora_on_disk, multiplier) + load_diffusers_lora(name, lora_on_disk, multiplier, num_loras) return lora @@ -239,24 +239,36 @@ def load_loras(names, multipliers=None): failed_to_load_loras = [] + recompile_model = False + if shared.opts.cuda_compile and shared.opts.cuda_compile_backend == "openvino_fx": + if len(names) == len(shared.compiled_model_state.lora_model): + for i, name in enumerate(names): + if shared.compiled_model_state.lora_model[i] != f"{name}:{multipliers[i]}": + recompile_model = True + break + else: + recompile_model = True + shared.compiled_model_state.lora_model = [] + if recompile_model: + sd_models.unload_model_weights(op='model') + shared.opts.cuda_compile = False + sd_models.reload_model_weights(op='model') + shared.opts.cuda_compile = True + for i, name in enumerate(names): lora = already_loaded.get(name, None) if shared.backend == shared.Backend.ORIGINAL else None - lora_on_disk = loras_on_disk[i] - if lora_on_disk is not None: if lora is None or os.path.getmtime(lora_on_disk.filename) > lora.mtime: try: if shared.backend == shared.Backend.DIFFUSERS: - lora = load_diffuser_lora(name, lora_on_disk, multipliers[i] if multipliers else 1.0) + lora = load_diffuser_lora(name, lora_on_disk, multipliers[i] if multipliers else 1.0, len(names)) else: lora = load_lora(name, lora_on_disk) except Exception as e: errors.display(e, f"loading Lora {lora_on_disk.filename}") continue - lora.mentioned_name = name - lora_on_disk.read_hash() if lora is None: @@ -270,6 +282,10 @@ def load_loras(names, multipliers=None): if len(failed_to_load_loras) > 0: sd_hijack.model_hijack.comments.append("Failed to find Loras: " + ", ".join(failed_to_load_loras)) + if recompile_model: + shared.log.info("Lora: Recompiling model") + sd_models.compile_diffusers(shared.sd_model) + def lora_calc_updown(lora, module, target): with torch.no_grad(): diff --git a/extensions-builtin/ScuNET/scripts/scunet_model.py b/extensions-builtin/ScuNET/scripts/scunet_model.py index c1aa25230..120ec0ffc 100644 --- a/extensions-builtin/ScuNET/scripts/scunet_model.py +++ b/extensions-builtin/ScuNET/scripts/scunet_model.py @@ -7,8 +7,6 @@ import numpy as np import torch from tqdm import tqdm -from basicsr.utils.download_util import load_file_from_url - import modules.upscaler from modules import devices, modelloader, script_callbacks from scunet_model_arch import SCUNet as net @@ -121,6 +119,7 @@ class UpscalerScuNET(modules.upscaler.Upscaler): def load_model(self, path: str): device = devices.get_device_for('scunet') if "http" in path: + from modules.modelloader import load_file_from_url filename = load_file_from_url(url=self.model_url, model_dir=self.model_download_path, file_name="%s.pth" % self.name, progress=True) else: filename = path diff --git a/extensions-builtin/SwinIR/scripts/swinir_model.py b/extensions-builtin/SwinIR/scripts/swinir_model.py index e470db874..4527b4c79 100644 --- a/extensions-builtin/SwinIR/scripts/swinir_model.py +++ b/extensions-builtin/SwinIR/scripts/swinir_model.py @@ -2,8 +2,7 @@ import os import numpy as np import torch from PIL import Image -from basicsr.utils.download_util import load_file_from_url -from tqdm.rich import tqdm +from rich.progress import Progress, TextColumn, BarColumn, TaskProgressColumn, TimeRemainingColumn, TimeElapsedColumn from swinir_model_arch import SwinIR as net from swinir_model_arch_v2 import Swin2SR as net2 from modules import modelloader, devices, script_callbacks, shared @@ -45,11 +44,13 @@ class UpscalerSwinIR(Upscaler): def load_model(self, path, scale=4): if "http" in path: - dl_name = "%s%s" % (self.model_name.replace(" ", "_"), ".pth") + from modules.modelloader import load_file_from_url + dl_name = "%s%s" % (self.model_name.replace(" ", "_"), ".pth") # pylint: disable=consider-using-f-string filename = load_file_from_url(url=path, model_dir=self.model_download_path, file_name=dl_name, progress=True) else: filename = path if filename is None or not os.path.exists(filename): + shared.log.error(f"Model failed loading: type=SwinIR model={filename}") return None model_v2 = net2( upscale=scale, @@ -78,6 +79,8 @@ class UpscalerSwinIR(Upscaler): resi_connection="3conv", ) pretrained_model = torch.load(filename) + shared.log.info(f"Model loaded: type=SwinIR model={filename}") + for model in [model_v1, model_v2]: for param in ["params_ema", "params", None]: try: @@ -140,7 +143,8 @@ def inference(img, model, tile, tile_overlap, window_size, scale): E = torch.zeros(b, c, h * sf, w * sf, dtype=devices.dtype, device=device_swinir).type_as(img) W = torch.zeros_like(E, dtype=devices.dtype, device=device_swinir) - with tqdm(total=len(h_idx_list) * len(w_idx_list), desc="Upscaling SwinIR") as pbar: + with Progress(TextColumn('[cyan]{task.description}'), BarColumn(), TaskProgressColumn(), TimeRemainingColumn(), TimeElapsedColumn(), console=shared.console) as progress: + task = progress.add_task(description="Upscaling Initializing", total=len(h_idx_list) * len(w_idx_list)) for h_idx in h_idx_list: if state.interrupted or state.skipped: break @@ -159,7 +163,7 @@ def inference(img, model, tile, tile_overlap, window_size, scale): W[ ..., h_idx * sf: (h_idx + tile) * sf, w_idx * sf: (w_idx + tile) * sf ].add_(out_patch_mask) - pbar.update(1) + progress.update(task, advance=1, description="Upscaling") output = E.div_(W) return output diff --git a/html/locale_en.json b/html/locale_en.json index 2c6e77e49..692a82929 100644 --- a/html/locale_en.json +++ b/html/locale_en.json @@ -63,7 +63,7 @@ {"id":"","label":"UI position","localized":"","hint":"Location of extra networks"}, {"id":"","label":"cover","localized":"","hint":"cover full area"}, {"id":"","label":"inline","localized":"","hint":"inline with all additional elelemtns (scrollable)"}, - {"id":"","label":"sidebar","localized":"","hint":"sidebar on the right side of the screen"}, + {"id":"","label":"sidebar","localized":"","hint":"sidebar on the right side of the screen"}, {"id":"","label":"UI height (%)","localized":"","hint":""}, {"id":"","label":"UI sidebar width (%)","localized":"","hint":""}, {"id":"","label":"UI card preview lazy loading","localized":"","hint":""}, @@ -431,7 +431,7 @@ {"id":"","label":"Create text file next to every image with generation parameters","localized":"","hint":""}, {"id":"","label":"Create JSON log file for each saved image","localized":"","hint":"Save image information to a JSON file"}, {"id":"","label":"Save copy of image before doing face restoration","localized":"","hint":""}, - {"id":"","label":"Save copy of image before applying highres fix","localized":"","hint":""}, + {"id":"","label":"Save copy of image before applying hires","localized":"","hint":""}, {"id":"","label":"Save copy of image before applying color correction","localized":"","hint":""}, {"id":"","label":"Save copy of the inpainting greyscale mask","localized":"","hint":""}, {"id":"","label":"Save copy of inpainting masked composite","localized":"","hint":""}, @@ -585,7 +585,7 @@ {"id":"","label":"Enable attention slicing","localized":"","hint":"Performs attention computation in steps instead of all at once. Slower inference times, but greatly reduced memory usage"}, {"id":"","label":"Diffusers model loading variant","localized":"","hint":""}, {"id":"","label":"Diffusers VAE loading variant","localized":"","hint":""}, - {"id":"","label":"Diffusers LoRA loading variant","localized":"","hint":"'sequential apply' loads and applies each LoRA in order of appearance, 'merge and apply' loads all LoRAs and merges them in-memory before applying to model, 'diffusers default' uses single LoRA loading method"}, + {"id":"","label":"Diffusers LoRA loading variant","localized":"","hint":"'sequential apply' loads and applies each LoRA in order of appearance, 'merge and apply' loads all LoRAs and merges them in-memory before applying to model, 'diffusers' uses diffusers default LoRA loading method"}, {"id":"","label":"Torch inference mode","localized":"","hint":"Use torch inference mode"}, {"id":"","label":"inference-mode","localized":"","hint":"Use torch.inference_mode"}, {"id":"","label":"no-grad","localized":"","hint":"Use torch.no_grad"}, diff --git a/installer.py b/installer.py index 1f853cc24..ae227723d 100644 --- a/installer.py +++ b/installer.py @@ -429,8 +429,8 @@ def check_torch(): torch_command = os.environ.get('TORCH_COMMAND', 'torch==2.0.0a0 intel_extension_for_pytorch==2.0.110+gitba7f6c1 -f https://developer.intel.com/ipex-whl-stable-xpu') elif allow_openvino and args.use_openvino: #Remove this after 2.1.0 releases - log.info('Using OpenVINO with Torch Nightly CPU') - torch_command = os.environ.get('TORCH_COMMAND', '--pre torch==2.1.0.dev20230713+cpu torchvision==0.16.0.dev20230713+cpu -f https://download.pytorch.org/whl/nightly/cpu/torch_nightly.html') + log.info('Using OpenVINO') + torch_command = os.environ.get('TORCH_COMMAND', '--pre torch==2.1.0.dev20230726+cpu torchvision==0.16.0.dev20230726+cpu -f https://download.pytorch.org/whl/nightly/cpu/torch_nightly.html') else: machine = platform.machine() if sys.platform == 'darwin': diff --git a/javascript/extraNetworks.js b/javascript/extraNetworks.js index b59129103..43ef498a8 100644 --- a/javascript/extraNetworks.js +++ b/javascript/extraNetworks.js @@ -136,6 +136,31 @@ function saveCardDescription(event) { event.preventDefault(); } +async function filterExtraNetworksForTab(tabname, searchTerm) { + let found = 0; + let items = 0; + const t0 = performance.now(); + const cards = Array.from(gradioApp().querySelectorAll(`#${tabname}_extra_tabs div.card`)); + cards.forEach((elem) => { + items += 1; + if (searchTerm === '') { + elem.style.display = ''; + } else { + let text = `${elem.querySelector('.name').textContent.toLowerCase()} ${elem.querySelector('.search_term').textContent}`; + text = text.toLowerCase().replace('models--', 'Diffusers').replace('\\', '/'); + if (text.indexOf(searchTerm) === -1) { + elem.style.display = 'none'; + } else { + elem.style.display = ''; + found += 1; + } + } + }); + const t1 = performance.now(); + if (found > 0) log(`filterExtraNetworks: text=${searchTerm} items=${items} match=${found} time=${Math.round(1000 * (t1 - t0)) / 1000000}`); + else log(`filterExtraNetworks: text=all items=${items} time=${Math.round(1000 * (t1 - t0)) / 1000000}`); +} + function setupExtraNetworksForTab(tabname) { gradioApp().querySelector(`#${tabname}_extra_tabs`).classList.add('extra-networks'); const tabs = gradioApp().querySelector(`#${tabname}_extra_tabs > div`); @@ -157,14 +182,9 @@ function setupExtraNetworksForTab(tabname) { search.addEventListener('input', (evt) => { if (searchTimer) clearTimeout(searchTimer); searchTimer = setTimeout(() => { - const searchTerm = search.value.toLowerCase(); - gradioApp().querySelectorAll(`#${tabname}_extra_tabs div.card`).forEach((elem) => { - let text = `${elem.querySelector('.name').textContent.toLowerCase()} ${elem.querySelector('.search_term').textContent.toLowerCase()}`; - text = text.replace('models--', 'Diffusers'); - elem.style.display = text.indexOf(searchTerm) === -1 ? 'none' : ''; - }); + filterExtraNetworksForTab(tabname, search.value.toLowerCase()); searchTimer = null; - }, 100); + }, 150); }); let hoverTimer = null; @@ -186,7 +206,10 @@ function setupExtraNetworksForTab(tabname) { const intersectionObserver = new IntersectionObserver((entries) => { if (!en) return; - for (const el of Array.from(gradioApp().querySelectorAll('.extra-networks-page'))) el.style.height = `${window.opts.extra_networks_height}vh`; + for (const el of Array.from(gradioApp().querySelectorAll('.extra-networks-page'))) { + el.style.height = `${window.opts.extra_networks_height}vh`; + el.parentElement.style.width = '-webkit-fill-available'; + } if (entries[0].intersectionRatio > 0) { if (window.opts.extra_networks_card_cover === 'cover') { en.style.transition = ''; @@ -269,6 +292,7 @@ function tryToRemoveExtraNetworkFromPrompt(textarea, text) { } function refreshExtraNetworks(tabname) { + console.log('refreshExtraNetworks', tabname, gradioApp().querySelector(`#${tabname}_extra_networks textarea`)?.value); gradioApp().querySelector(`#${tabname}_extra_networks textarea`)?.dispatchEvent(new Event('input')); } diff --git a/javascript/midnight-barbie.css b/javascript/midnight-barbie.css index a52980c27..1ee4d7f10 100644 --- a/javascript/midnight-barbie.css +++ b/javascript/midnight-barbie.css @@ -314,4 +314,4 @@ svg.feather.feather-image, .feather .feather-image { display: none } --size-9: 64px; --size-14: 64px; } -/*Midnight-Barbie, By Nyxxia*/ \ No newline at end of file +/*Midnight-Barbie, By Nyxxia*/ diff --git a/javascript/style.css b/javascript/style.css index e06d61642..29c7b7cb2 100644 --- a/javascript/style.css +++ b/javascript/style.css @@ -249,10 +249,10 @@ table.settings-value-table td { padding: 0.4em; border: 1px solid #ccc; max-widt .extra-network-cards .card:hover .overlay .tags { display: block; } .extra-network-cards .card:hover .overlay .description { display: block; } .extra-network-cards .card:hover .preview { box-shadow: none; filter: grayscale(100%); } -#txt2img_description, #img2img_description { max-height: 63px; overflow-y: auto !important; } -#txt2img_description > label > textarea, #img2img_description > label > textarea { font-size: 0.9em } +#txt2img_description, #img2img_description { max-height: 63px; overflow-y: auto !important; } +#txt2img_description > label > textarea, #img2img_description > label > textarea { font-size: 0.9em } -/* controlnet */ +/* controlnet .controlnet_control_type .controlnet_control_type_filter_group .wrap:last-of-type { display: grid; grid-auto-flow: row; grid-template-columns: repeat(4, minmax(0, 1fr)); } fieldset.controlnet_resize_mode_radio .wrap:last-of-type, fieldset.controlnet_control_mode_radio .wrap:last-of-type { flex-direction: column; } div.controlnet_preprocessor_model { display: grid; grid-auto-flow: row; grid-template-columns: 1fr max-content; } @@ -263,6 +263,7 @@ div.controlnet_image_controls { display: grid; grid-template-columns: repeat(4, div.controlnet_image_controls .controlnet_invert_warning { grid-column: 1 / -1; } div.controlnet_image_controls button { justify-self: center; } div.controlnet_main_options { display: grid; grid-template-columns: 1fr 1fr; grid-auto-flow: row; } + */ /* specific elements */ #modelmerger_interp_description { margin-top: 1em; margin-bottom: 1em; } @@ -281,6 +282,12 @@ div.controlnet_main_options { display: grid; grid-template-columns: 1fr 1fr; gri .log-monitor { display: none; justify-content: unset !important; overflow: hidden; padding: 0; margin-top: auto; font-family: monospace; font-size: 0.85em; } .log-monitor td, .log-monitor th { padding-left: 1em; } +/* changelog */ +.md h2 { background-color: var(--background-fill-primary); padding: 0.5em; } +.md ul { list-style-type: square !important; text-indent: 1em; margin-left: 4em; } +.md li { list-style-position: outside !important; text-indent: 0; } +.md p { margin-left: 2em; } + /* custom component */ .folder-selector textarea { height: 2em !important; padding: 6px !important; } @@ -295,6 +302,7 @@ div.controlnet_main_options { display: grid; grid-template-columns: 1fr 1fr; gri .loader::before { border-top-color: var(--primary-900); animation: 3s spin linear infinite; } .loader::after { border-top-color: var(--primary-300); animation: spin 1.5s linear infinite; } + @keyframes move { from { background-position-x: 0, -40px; } to { background-position-x: 0, 40px; } diff --git a/modules/api/api.py b/modules/api/api.py index cf721d445..e750fccb4 100644 --- a/modules/api/api.py +++ b/modules/api/api.py @@ -2,7 +2,7 @@ import io import time import base64 from io import BytesIO -from typing import List, Dict, Any +from typing import List, Dict, Any, Optional from threading import Lock from secrets import compare_digest from fastapi import FastAPI, APIRouter, Depends @@ -148,6 +148,7 @@ class Api: self.add_api_route("/sdapi/v1/scripts", self.get_scripts_list, methods=["GET"], response_model=models.ScriptsList) self.add_api_route("/sdapi/v1/script-info", self.get_script_info, methods=["GET"], response_model=List[models.ScriptInfo]) self.add_api_route("/sdapi/v1/log", self.get_log_buffer, methods=["GET"], response_model=List) # bypass auth + self.add_api_route("/sdapi/v1/extra-networks", self.get_extra_networks, methods=["GET"], response_model=List[models.ExtraNetworkItem]) self.default_script_arg_txt2img = [] self.default_script_arg_img2img = [] @@ -180,10 +181,12 @@ class Api: i2ilist = [script.name for script in scripts.scripts_img2img.scripts if script.name is not None] return models.ScriptsList(txt2img = t2ilist, img2img = i2ilist) - def get_script_info(self): + def get_script_info(self, script_name: Optional[str] = None): res = [] for script_list in [scripts.scripts_txt2img.scripts, scripts.scripts_img2img.scripts]: - res += [script.api_info for script in script_list if script.api_info is not None] + for script in script_list: + if script.api_info is not None and (script_name is None or script_name == script.api_info.name): + res.append(script.api_info) return res def get_script(self, script_name, script_runner): @@ -500,6 +503,33 @@ class Api: "skipped": convert_embeddings(db.skipped_embeddings), } + def get_extra_networks(self, page: Optional[str] = None, name: Optional[str] = None, filename: Optional[str] = None, title: Optional[str] = None, fullname: Optional[str] = None, hash: Optional[str] = None): # pylint: disable=redefined-builtin + res = [] + for pg in shared.extra_networks: + if page is not None and pg.name != page.lower(): + continue + for item in pg.items: + if name is not None and item.get('name', '') != name: + continue + if title is not None and item.get('title', '') != title: + continue + if filename is not None and item.get('filename', '') != filename: + continue + if fullname is not None and item.get('fullname', '') != fullname: + continue + if hash is not None and (item.get('shorthash', None) or item.get('hash')) != hash: + continue + res.append({ + 'name': item.get('name', ''), + 'type': pg.name, + 'title': item.get('title', None), + 'fullname': item.get('fullname', None), + 'filename': item.get('filename', None), + 'hash': item.get('shorthash', None) or item.get('hash'), + "preview": item.get('preview', None), + }) + return res + def refresh_checkpoints(self): return shared.refresh_checkpoints() diff --git a/modules/api/models.py b/modules/api/models.py index da4158dcd..9241592eb 100644 --- a/modules/api/models.py +++ b/modules/api/models.py @@ -272,6 +272,20 @@ class StyleItem(BaseModel): filename: Optional[str] = Field(title="Filename") preview: Optional[str] = Field(title="Preview") +class ExtraNetworkItem(BaseModel): + name: str = Field(title="Name") + type: str = Field(title="Type") + title: Optional[str] = Field(title="Title") + fullname: Optional[str] = Field(title="Fullname") + filename: Optional[str] = Field(title="Filename") + hash: Optional[str] = Field(title="Hash") + preview: Optional[str] = Field(title="Preview image URL") + # description: Optional[str] = Field(title="Description") + # info: Optional[str] = Field(title="Information") + # metadata: Optional[Any] = Field(title="Metadata") + # local: Optional[str] = Field(title="Local") + + class ArtistItem(BaseModel): name: str = Field(title="Name") score: float = Field(title="Score") @@ -303,7 +317,7 @@ class ScriptArg(BaseModel): minimum: Optional[Any] = Field(default=None, title="Minimum", description="Minimum allowed value for the argumentin UI") maximum: Optional[Any] = Field(default=None, title="Minimum", description="Maximum allowed value for the argumentin UI") step: Optional[Any] = Field(default=None, title="Minimum", description="Step for changing value of the argumentin UI") - choices: Optional[List[str]] = Field(default=None, title="Choices", description="Possible values for the argument") + choices: Optional[Any] = Field(default=None, title="Choices", description="Possible values for the argument") class ScriptInfo(BaseModel): diff --git a/modules/devices.py b/modules/devices.py index 8a7db4244..2808760b5 100644 --- a/modules/devices.py +++ b/modules/devices.py @@ -115,7 +115,7 @@ def torch_gc(force=False): if oom > previous_oom: previous_oom = oom shared.log.warning(f'GPU out-of-memory error: {mem}') - if used > 95: + if used > 90: shared.log.info(f'GPU high memory utilization: {used}% {mem}') force = True if not force: diff --git a/modules/dml/__init__.py b/modules/dml/__init__.py index f14f479a2..3661559ec 100644 --- a/modules/dml/__init__.py +++ b/modules/dml/__init__.py @@ -9,7 +9,7 @@ default_memory_provider = "None" if platform.system() == "Windows": memory_providers.append("Performance Counter") default_memory_provider = "Performance Counter" -do_nothing = lambda: None +do_nothing = lambda: None # pylint: disable=unnecessary-lambda-assignment def _set_memory_provider(): from modules.shared import opts, cmd_opts, log @@ -63,7 +63,7 @@ def directml_init(): return True, None def directml_do_hijack(): - import modules.dml.hijack + import modules.dml.hijack # pylint: disable=unused-import from modules.devices import device if not torch.dml.has_float64_support(device): @@ -79,9 +79,9 @@ class OverrideItem(NamedTuple): message: Optional[str] opts_override_table = { - "diffusers_generator_device": OverrideItem("cpu", None, "DirectML does not support torch Generator API."), - "diffusers_model_cpu_offload": OverrideItem(False, None, "Diffusers' model CPU offloading does not support DirectML devices."), - "diffusers_seq_cpu_offload": OverrideItem(False, lambda opts: opts.diffusers_pipeline != "Stable Diffusion XL", "Diffusers' sequential CPU offloading is available only on StableDiffusionXLPipeline with DirectML devices."), + "diffusers_generator_device": OverrideItem("cpu", None, "DirectML does not support torch Generator API"), + "diffusers_model_cpu_offload": OverrideItem(False, None, "Diffusers model CPU offloading does not support DirectML devices"), + "diffusers_seq_cpu_offload": OverrideItem(False, lambda opts: opts.diffusers_pipeline != "Stable Diffusion XL", "Diffusers sequential CPU offloading is available only on StableDiffusionXLPipeline with DirectML devices"), } def directml_override_opts(): @@ -96,11 +96,9 @@ def directml_override_opts(): if getattr(shared.opts, key) != item.value and (item.condition is None or item.condition(shared.opts)): count += 1 setattr(shared.opts, key, item.value) - if item.message is not None: - shared.log.warning(item.message) - shared.log.warning(f'{key} is automatically overriden to {item.value}.') + shared.log.warning(f'Overriding: {key}={item.value} {item.message if item.message is not None else ""}') if count > 0: - shared.log.info(f'{count} options are automatically overriden. If you want to keep them from overriding, run with --experimental argument.') + shared.log.info(f'Options override: count={count}. If you want to keep them from overriding, run with --experimental argument.') _set_memory_provider() diff --git a/modules/dml/hijack/realesrgan_model.py b/modules/dml/hijack/realesrgan_model.py index ad3b01cce..b55e14647 100644 --- a/modules/dml/hijack/realesrgan_model.py +++ b/modules/dml/hijack/realesrgan_model.py @@ -1,6 +1,6 @@ import math import torch -from realesrgan import RealESRGANer +from modules.realesrgan_model_arch import RealESRGANer # DML Solution: Some of contents of output tensor turn to 0 after Extended Slices. Move it to cpu. diff --git a/modules/esrgan_model.py b/modules/esrgan_model.py index 7ed5c33ec..cbede232f 100644 --- a/modules/esrgan_model.py +++ b/modules/esrgan_model.py @@ -3,12 +3,12 @@ import os import numpy as np import torch from PIL import Image -from basicsr.utils.download_util import load_file_from_url +from rich.progress import Progress, TextColumn, BarColumn, TaskProgressColumn, TimeRemainingColumn, TimeElapsedColumn import modules.esrgan_model_arch as arch from modules import modelloader, images, devices from modules.upscaler import Upscaler, UpscalerData -from modules.shared import opts +from modules.shared import opts, log, console @@ -152,6 +152,7 @@ class UpscalerESRGAN(Upscaler): def load_model(self, path: str): if "http" in path: + from modules.modelloader import load_file_from_url filename = load_file_from_url( url=self.model_url, model_dir=self.model_download_path, @@ -161,10 +162,11 @@ class UpscalerESRGAN(Upscaler): else: filename = path if not os.path.exists(filename) or filename is None: - print(f"Unable to load {self.model_path} from {filename}") + log.error(f"Model failed loading: type=ESRGAN model={filename}") return None state_dict = torch.load(filename, map_location='cpu' if devices.device_esrgan.type == 'mps' else None) + log.info(f"Model loaded: type=ESRGAN model={filename}") if "params_ema" in state_dict: state_dict = state_dict["params_ema"] @@ -216,16 +218,20 @@ def esrgan_upscale(model, img): newtiles = [] scale_factor = 1 - for y, h, row in grid.tiles: - newrow = [] - for tiledata in row: - x, w, tile = tiledata - - output = upscale_without_tiling(model, tile) - scale_factor = output.width // tile.width - - newrow.append([x * scale_factor, w * scale_factor, output]) - newtiles.append([y * scale_factor, h * scale_factor, newrow]) + with Progress(TextColumn('[cyan]{task.description}'), BarColumn(), TaskProgressColumn(), TimeRemainingColumn(), TimeElapsedColumn(), console=console) as progress: + total = 0 + for y, h, row in grid.tiles: + total += len(row) + task = progress.add_task(description="Upscaling", total=total) + for y, h, row in grid.tiles: + newrow = [] + for tiledata in row: + x, w, tile = tiledata + output = upscale_without_tiling(model, tile) + scale_factor = output.width // tile.width + newrow.append([x * scale_factor, w * scale_factor, output]) + progress.update(task, advance=1, description="Upscaling") + newtiles.append([y * scale_factor, h * scale_factor, newrow]) newgrid = images.Grid(newtiles, grid.tile_w * scale_factor, grid.tile_h * scale_factor, grid.image_w * scale_factor, grid.image_h * scale_factor, grid.overlap * scale_factor) output = images.combine_grid(newgrid) diff --git a/modules/gfpgan_model.py b/modules/gfpgan_model.py index 728df70bf..2988cd407 100644 --- a/modules/gfpgan_model.py +++ b/modules/gfpgan_model.py @@ -29,12 +29,13 @@ def gfpgann(): latest_file = max(models, key=os.path.getctime) model_file = latest_file else: - print("Unable to load gfpgan model!") + shared.log.error(f"Model failed loading: type=GFPGAN model={model_file}") return None if hasattr(facexlib.detection.retinaface, 'device'): facexlib.detection.retinaface.device = devices.device_gfpgan model = gfpgan_constructor(model_path=model_file, upscale=1, arch='clean', channel_multiplier=2, bg_upsampler=None, device=devices.device_gfpgan) loaded_gfpgan_model = model + shared.log.info(f"Model loaded: type=GFPGAN model={model_file}") return model diff --git a/modules/hypernetworks/hypernetwork.py b/modules/hypernetworks/hypernetwork.py index d9cc95e86..7b319b1b7 100644 --- a/modules/hypernetworks/hypernetwork.py +++ b/modules/hypernetworks/hypernetwork.py @@ -221,10 +221,10 @@ class Hypernetwork: torch.save(optimizer_saved_dict, f"{filename}.optim") def load(self, filename): - self.filename = filename + self.filename = filename if os.path.exists(filename) else os.path.join(shared.opts.hypernetwork_dir, filename) if self.name is None: - self.name = os.path.splitext(os.path.basename(filename))[0] - with progress.open(filename, 'rb', description=f'Loading hypernetwork: [cyan]{filename}', auto_refresh=True, console=shared.console) as f: + self.name = os.path.splitext(os.path.basename(self.filename))[0] + with progress.open(self.filename, 'rb', description=f'Loading hypernetwork: [cyan]{self.filename}', auto_refresh=True, console=shared.console) as f: state_dict = torch.load(f, map_location='cpu') self.layer_structure = state_dict.get('layer_structure', [1, 2, 1]) self.optional_info = state_dict.get('optional_info', None) diff --git a/modules/images.py b/modules/images.py index 49f83669f..3a2e8ded0 100644 --- a/modules/images.py +++ b/modules/images.py @@ -273,7 +273,7 @@ re_nonletters = re.compile(r'[\s' + string.punctuation + ']+') re_pattern = re.compile(r"(.*?)(?:\[([^\[\]]+)\]|$)") re_pattern_arg = re.compile(r"(.*)<([^>]*)>$") max_filename_part_length = 128 -NOTHING_AND_SKIP_PREVIOUS_TEXT = object() +NOTHING = object() def sanitize_filename_part(text, replace_spaces=True): @@ -291,13 +291,13 @@ def sanitize_filename_part(text, replace_spaces=True): class FilenameGenerator: replacements = { - 'batch_number': lambda self: NOTHING_AND_SKIP_PREVIOUS_TEXT if self.p is None or self.p.batch_size == 1 else self.p.batch_index + 1, + 'batch_number': lambda self: NOTHING if self.index <= 1 else self.index, 'cfg': lambda self: self.p and self.p.cfg_scale, 'clip_skip': lambda self: self.p and self.p.clip_skip, 'date': lambda self: datetime.datetime.now().strftime('%Y-%m-%d'), 'datetime': lambda self, *args: self.datetime(*args), # accepts formats: [datetime], [datetime], [datetime