diff --git a/CHANGELOG.md b/CHANGELOG.md index dd92b2c7e..2917625ab 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,10 +1,13 @@ # Change Log for SD.Next -## Update for 2023-09-15 +## 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... diff --git a/README.md b/README.md index bc1f07692..19318a2eb 100644 --- a/README.md +++ b/README.md @@ -69,10 +69,10 @@ Additional models will be added as they become available and there is public int - *nVidia* GPUs using **CUDA** libraries on both *Windows and Linux* - *AMD* GPUs using **ROCm** libraries on *Linux*. Support will be extended to *Windows* once AMD releases ROCm for Windows -- Any GPU compatibile with *DirectX* on *Windows* using **DirectML** libraries. - This includes support for AMD GPUs that are not supported by native ROCm libraries - *Intel Arc* GPUs using **OneAPI** with *IPEX XPU* libraries on both *Windows and Linux* -- *Intel* GPUs using **OpenVINO** libraries on both *Windows and Linux* +- Any GPU compatible with *DirectX* on *Windows* using **DirectML** libraries. + This includes support for AMD GPUs that are not supported by native ROCm libraries +- Any GPU compatible with **OpenVINO** libraries on both *Windows and Linux* - *Apple M1/M2* on *OSX* using built-in support in Torch with **MPS** optimizations ## Install & Run @@ -175,7 +175,7 @@ General goals: ### **Sponsors**
-TillerzAllan GrantMichael HarrisBrent OzarToniXMatthew RunoHELLO WORLD SASSalad TechnologiesGym Dreams • GymDreams8 +Allan GrantMichael HarrisBrent OzarToniXMatthew RunoHELLO WORLD SASSalad TechnologiesGym Dreams • GymDreams8

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/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/extensions-builtin/sd-extension-system-info b/extensions-builtin/sd-extension-system-info index 83dd4d8f6..b73ffdcc8 160000 --- a/extensions-builtin/sd-extension-system-info +++ b/extensions-builtin/sd-extension-system-info @@ -1 +1 @@ -Subproject commit 83dd4d8f65511af720ddb034125fe4ac0a46fb18 +Subproject commit b73ffdcc8d6a9622d3d8074862b109b20726ac73 diff --git a/html/locale_en.json b/html/locale_en.json index 692a82929..f17742039 100644 --- a/html/locale_en.json +++ b/html/locale_en.json @@ -356,7 +356,7 @@ {"id":"","label":"Enable splitting of hires batch processing","localized":"","hint":"Reduces VRAM usage when using hires fix on batches of images"}, {"id":"","label":"Load models using stream loading method","localized":"","hint":"When loading models attempt stream loading optimized for slow or network storage"}, {"id":"","label":"When loading models attempt to reuse previous model dictionary","localized":"","hint":""}, - {"id":"","label":"Disable cross-attention layer optimization","localized":"","hint":"Disable the all cross-attention optimization. May result in higher VRAM usage and longer generation times"}, + {"id":"","label":"Disabled","localized":"","hint":""}, {"id":"","label":"xFormers","localized":"","hint":"Memory optimization. Non-Deterministic (different results each time)"}, {"id":"","label":"Scaled-Dot-Product","localized":"","hint":"Memory optimization. Non-Deterministic unless SDP memory attention is disabled."}, {"id":"","label":"Doggettx's","localized":"","hint":""}, @@ -365,9 +365,9 @@ {"id":"","label":"Split attention","localized":"","hint":""}, {"id":"","label":"xFormers enable flash Attention","localized":"","hint":""}, {"id":"","label":"SDP disable memory attention","localized":"","hint":""}, - {"id":"","label":"Sub-quadratic cross-attention query chunk size","localized":"","hint":""}, - {"id":"","label":"Sub-quadratic cross-attention kv chunk size","localized":"","hint":""}, - {"id":"","label":"Sub-quadratic cross-attention chunking threshold","localized":"","hint":""}, + {"id":"","label":"cross-attention query chunk size","localized":"","hint":""}, + {"id":"","label":"cross-attention kv chunk size","localized":"","hint":""}, + {"id":"","label":"cross-attention chunking threshold","localized":"","hint":""}, {"id":"","label":"Full parser","localized":"","hint":""}, {"id":"","label":"Compel parser","localized":"","hint":""}, {"id":"","label":"A1111 parser","localized":"","hint":""}, @@ -466,9 +466,9 @@ {"id":"","label":"Auto","localized":"","hint":""}, {"id":"","label":"Dark","localized":"","hint":""}, {"id":"","label":"Light","localized":"","hint":""}, - {"id":"","label":"Show grid in results for web","localized":"","hint":""}, - {"id":"","label":"For inpainting, include the greyscale mask in results for web","localized":"","hint":""}, - {"id":"","label":"For inpainting, include masked composite in results for web","localized":"","hint":""}, + {"id":"","label":"Show grid in results","localized":"","hint":""}, + {"id":"","label":"For inpainting, include the greyscale mask in results","localized":"","hint":""}, + {"id":"","label":"For inpainting, include masked composite in results","localized":"","hint":""}, {"id":"","label":"Do not change selected model when reading generation parameters","localized":"","hint":""}, {"id":"","label":"Send seed when sending prompt or image to other interface","localized":"","hint":""}, {"id":"","label":"Send size when sending prompt or image to another interface","localized":"","hint":""}, diff --git a/html/logo-bg-dark.jpg b/html/logo-bg-dark.jpg index 20392e1ae..40af4bbbc 100644 Binary files a/html/logo-bg-dark.jpg and b/html/logo-bg-dark.jpg differ diff --git a/installer.py b/installer.py index ae227723d..d7350654f 100644 --- a/installer.py +++ b/installer.py @@ -430,7 +430,7 @@ def check_torch(): elif allow_openvino and args.use_openvino: #Remove this after 2.1.0 releases 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') + torch_command = os.environ.get('TORCH_COMMAND', '--pre torch==2.1.0.dev20230820+cpu torchvision==0.16.0.dev20230820+cpu -f https://download.pytorch.org/whl/nightly/cpu/torch_nightly.html') else: machine = platform.machine() if sys.platform == 'darwin': @@ -496,7 +496,7 @@ def check_torch(): if opts.get('cuda_compile_backend', '') == 'hidet': install('hidet', 'hidet') if args.use_openvino or opts.get('cuda_compile_backend', '') == 'openvino_fx': - install('openvino==2023.1.0.dev20230811', 'openvino') + install('openvino==2023.1.0', 'openvino') os.environ.setdefault('PYTORCH_TRACING_MODE', 'TORCHFX') if args.profile: print_profile(pr, 'Torch') diff --git a/javascript/invokeai.css b/javascript/invokeai.css new file mode 100644 index 000000000..363aff97b --- /dev/null +++ b/javascript/invokeai.css @@ -0,0 +1,313 @@ +/* generic html tags */ +:root, .light, .dark { + --font: 'system-ui', 'ui-sans-serif', 'system-ui', "Roboto", sans-serif; + --font-mono: 'ui-monospace', 'Consolas', monospace; + --font-size: 16px; + --left-column: 490px; + --primary-100: #2b303b; + --primary-200: #15181e; + --primary-300: #0a0c0e; + --primary-400: #566176; + --primary-500: #483a90; + --primary-700: #6b7994; + --primary-800: #5b49b3; + --highlight-color: var(--primary-500); + --inactive-color: var(--primary--800); + --body-text-color: var(--neutral-100); + --body-text-color-subdued: var(--neutral-300); + --background-color: var(--primary-100); + --background-fill-primary: var(--input-background-fill); + --input-padding: 8px; + --input-background-fill: var(--primary-200); + --input-shadow: none; + --button-secondary-text-color: white; + --button-secondary-background-fill: var(--primary-400); + --button-secondary-background-fill-hover: var(--primary-700); + --block-title-text-color: var(--neutral-300); + --radius-sm: 1px; + --radius-lg: 6px; + --spacing-md: 4px; + --spacing-xxl: 8px; + --line-sm: 1.2em; + --line-md: 1.4em; +} + +html { font-size: var(--font-size); } +body, button, input, select, textarea { font-family: var(--font);} +button { font-size: 1.2rem; max-width: 400px; } +img { background-color: var(--background-color); } +input[type=range] { height: var(--line-sm); appearance: none; margin-top: 0; min-width: 160px; background-color: var(--background-color); width: 100%; background: transparent; } +input[type=range]::-webkit-slider-runnable-track, input[type=range]::-moz-range-track { width: 100%; height: 6px; cursor: pointer; background: var(--primary-400); border-radius: var(--radius-lg); border: 0px solid #222222; } +input[type=range]::-webkit-slider-thumb, input[type=range]::-moz-range-thumb { border: 0px solid #000000; height: var(--line-sm); width: 8px; border-radius: var(--radius-lg); background: white; cursor: pointer; appearance: none; margin-top: 0px; } +input[type=range]::-moz-range-progress { background-color: var(--primary-500); height: 6px; border-radius: var(--radius-lg); } +::-webkit-scrollbar-track { background: #333333; } +::-webkit-scrollbar-thumb { background-color: var(--highlight-color); border-radius: var(--radius-lg); border-width: 0; box-shadow: 2px 2px 3px #111111; } +div.form { border-width: 0; box-shadow: none; background: transparent; overflow: visible; margin-bottom: 6px; } +div.compact { gap: 1em; } + +/* gradio style classes */ +fieldset .gr-block.gr-box, label.block span { padding: 0; margin-top: -4px; } +.border-2 { border-width: 0; } +.border-b-2 { border-bottom-width: 2px; border-color: var(--highlight-color) !important; padding-bottom: 2px; margin-bottom: 8px; } +.bg-white { color: lightyellow; background-color: var(--inactive-color); } +.gr-box { border-radius: var(--radius-sm) !important; background-color: #111111 !important; box-shadow: 2px 2px 3px #111111; border-width: 0; padding: 4px; margin: 12px 0px 12px 0px } +.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: var(--radius-lg); box-shadow: 2px 2px 3px #111111; } +.gr-check-radio:checked { background-color: var(--highlight-color); } +.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: var(--background-color); } +.eta-bar { display: none !important } +svg.feather.feather-image, .feather .feather-image { display: none } +.gap-2 { padding-top: 8px; } +.gr-box > div > div > input.gr-text-input { right: 0; width: 4em; padding: 0; top: -12px; border: none; max-height: 20px; } +.output-html { line-height: 1.2rem; overflow-x: hidden; } +.output-html > div { margin-bottom: 8px; } +.overflow-hidden .flex .flex-col .relative col .gap-4 { min-width: var(--left-column); max-width: var(--left-column); } /* this is a problematic one */ +.p-2 { padding: 0; } +.px-4 { padding-lefT: 1rem; padding-right: 1rem; } +.py-6 { padding-bottom: 0; } +.tabs { background-color: var(--background-color); } +.block.token-counter span { background-color: var(--input-background-fill) !important; box-shadow: 2px 2px 2px #111; border: none !important; font-size: 0.8rem; } +.tab-nav { zoom: 120%; margin-top: 10px; margin-bottom: 10px; border-bottom: 2px solid var(--highlight-color) !important; padding-bottom: 2px; } +div.tab-nav button.selected {background-color: var(--button-primary-background-fill);} +#settings div.tab-nav button.selected {background-color: var(--background-color); color: var(--primary-800); font-weight: bold;} +.label-wrap { background-color: #363c4a; padding: 16px 8px 8px 8px; border-radius: var(--radius-lg);} +.gradio-slider input[type="number"] { width: 4.5em; font-size: 0.8rem; height: 20px; } +.gradio-button.tool { border: none; box-shadow: none; border-radius: var(--radius-lg);} +button.selected {background: var(--button-primary-background-fill);} +.center.boundedheight.flex {background-color: var(--input-background-fill);} +.compact {border-radius: var(--border-radius-lg);} +#logMonitorData {background-color: var(--input-background-fill);} +#tab_extensions table td, #tab_extensions table th, #tab_config table td, #tab_config table th { border: none; padding: 0.5em; background-color: var(--primary-200); } +#tab_extensions table, #tab_config table { width: 96vw; } +#tab_extensions table input[type=checkbox] {appearance: none; border-radius: 0px;} +#tab_extensions button:hover { background-color: var(--button-secondary-background-fill-hover);} + +/* automatic style classes */ +.progressDiv { border-radius: var(--radius-sm) !important; position: fixed; top: 44px; right: 26px; max-width: 262px; height: 48px; z-index: 99; box-shadow: var(--button-shadow); } +.progressDiv .progress { border-radius: var(--radius-lg) !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 var(--highlight-color) !important; padding-left: 4px; } +.image-buttons { gap: 10px !important; justify-content: center; } +.image-buttons > button { max-width: 160px; } +.tooltip { background: var(--primary-800); color: white; border: none; border-radius: var(--radius-lg) } +#system_row > button, #settings_row > button, #config_row > button { max-width: 190px; } + +/* 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: 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.1rem; } +#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; } +#quicksettings .gr-button-tool { font-size: 1.6rem; box-shadow: none; margin-top: -2px; height: 2.4em; } +#quicksettings button {padding: 0 0.5em 0.1em 0.5em;} +#open_folder_extras, #footer, #style_pos_col, #style_neg_col, #roll_col, #extras_upscaler_2, #extras_upscaler_2_visibility, #txt2img_seed_resize_from_w, #txt2img_seed_resize_from_h { display: none; } +#save-animation { border-radius: var(--radius-sm) !important; margin-bottom: 16px; background-color: #111111; } +#script_list { padding: 4px; margin-top: 20px; margin-bottom: 20px; } +#settings > div.flex-wrap { width: 15em; } +#txt2img_cfg_scale { min-width: 200px; } +#txt2img_checkboxes, #img2img_checkboxes { background-color: transparent; } +#txt2img_checkboxes, #img2img_checkboxes { margin-bottom: 0.2em; } +#txt2img_actions_column, #img2img_actions_column { flex-flow: wrap; justify-content: space-between; } +#txt2img_enqueue_wrapper, #img2img_enqueue_wrapper { min-width: unset; width: 48%; } +#txt2img_generate_box, #img2img_generate_box { min-width: unset; width: 48%; } + +#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: var(--background-color); padding: 0; } +#txt2img_settings { min-width: var(--left-column); max-width: var(--left-column); background-color: #111111; padding-top: 16px; } +#pnginfo_html2_info { margin-top: -18px; background-color: var(--input-background-fill); padding: var(--input-padding) } +#txt2img_tools, #img2img_tools { margin-top: -4px; margin-bottom: -4px; } +#txt2img_styles_row, #img2img_styles_row { margin-top: -6px; z-index: 200; } + +/* custom elements overrides */ +#steps-animation, #controlnet { border-width: 0; } + +/* based on gradio built-in dark theme */ +:root, .light, .dark { + --body-background-fill: var(--background-color); + --color-accent-soft: var(--neutral-700); + --background-fill-secondary: none; + --border-color-accent: var(--background-color); + --border-color-primary: var(--background-color); + --link-text-color-active: var(--primary-500); + --link-text-color: var(--secondary-500); + --link-text-color-hover: var(--secondary-400); + --link-text-color-visited: var(--secondary-600); + --shadow-spread: 1px; + --block-background-fill: None; + --block-border-color: var(--border-color-primary); + --block_border_width: None; + --block-info-text-color: var(--body-text-color-subdued); + --block-label-background-fill: var(--background-fill-secondary); + --block-label-border-color: var(--border-color-primary); + --block_label_border_width: None; + --block-label-text-color: var(--neutral-200); + --block_shadow: None; + --block_title_background_fill: None; + --block_title_border_color: None; + --block_title_border_width: None; + --panel-background-fill: var(--background-fill-secondary); + --panel-border-color: var(--border-color-primary); + --panel_border_width: None; + --checkbox-background-color: var(--primary-400); + --checkbox-background-color-focus: var(--primary-700); + --checkbox-background-color-hover: var(--primary-700); + --checkbox-background-color-selected: var(--primary-500); + --checkbox-border-color: transparent; + --checkbox-border-color-focus: var(--primary-800); + --checkbox-border-color-hover: var(--primary-800); + --checkbox-border-color-selected: var(--primary-800); + --checkbox-border-width: var(--input-border-width); + --checkbox-label-background-fill: None; + --checkbox-label-background-fill-hover: None; + --checkbox-label-background-fill-selected: var(--checkbox-label-background-fill); + --checkbox-label-border-color: var(--border-color-primary); + --checkbox-label-border-color-hover: var(--checkbox-label-border-color); + --checkbox-label-border-width: var(--input-border-width); + --checkbox-label-text-color: var(--body-text-color); + --checkbox-label-text-color-selected: var(--checkbox-label-text-color); + --error-background-fill: var(--background-fill-primary); + --error-border-color: var(--border-color-primary); + --error-text-color: #ef4444; + --input-background-fill-focus: var(--secondary-600); + --input-background-fill-hover: var(--input-background-fill); + --input-border-color: var(--background-color); + --input-border-color-focus: var(--primary-800); + --input-placeholder-color: var(--neutral-500); + --input-shadow-focus: None; + --loader_color: None; + --slider_color: None; + --stat-background-fill: linear-gradient(to right, var(--primary-400), var(--primary-800)); + --table-border-color: var(--neutral-700); + --table-even-background-fill: var(--primary-300); + --table-odd-background-fill: var(--primary-200); + --table-row-focus: var(--color-accent-soft); + --button-border-width: var(--input-border-width); + --button-cancel-background-fill: linear-gradient(to bottom right, #dc2626, #b91c1c); + --button-cancel-background-fill-hover: linear-gradient(to bottom right, #dc2626, #dc2626); + --button-cancel-border-color: #dc2626; + --button-cancel-border-color-hover: var(--button-cancel-border-color); + --button-cancel-text-color: white; + --button-cancel-text-color-hover: var(--button-cancel-text-color); + --button-primary-background-fill: var(--primary-500); + --button-primary-background-fill-hover: var(--primary-800); + --button-primary-border-color: var(--primary-500); + --button-primary-border-color-hover: var(--button-primary-border-color); + --button-primary-text-color: white; + --button-primary-text-color-hover: var(--button-primary-text-color); + --button-secondary-border-color: var(--neutral-600); + --button-secondary-border-color-hover: var(--button-secondary-border-color); + --button-secondary-text-color-hover: var(--button-secondary-text-color); + --secondary-50: #eff6ff; + --secondary-100: #dbeafe; + --secondary-200: #bfdbfe; + --secondary-300: #93c5fd; + --secondary-400: #60a5fa; + --secondary-500: #3b82f6; + --secondary-600: #2563eb; + --secondary-700: #1d4ed8; + --secondary-800: #1e40af; + --secondary-900: #1e3a8a; + --secondary-950: #1d3660; + --neutral-50: #f0f0f0; + --neutral-100: #e0e0e0; + --neutral-200: #d0d0d0; + --neutral-300: #b0b0b0; + --neutral-400: #909090; + --neutral-500: #707070; + --neutral-600: #606060; + --neutral-700: #404040; + --neutral-800: #333333; + --neutral-900: #111827; + --neutral-950: #0b0f19; + --spacing-xxs: 1px; + --spacing-xs: 2px; + --spacing-sm: 4px; + --spacing-lg: 8px; + --spacing-xl: 10px; + --radius-xxs: 0; + --radius-xs: 0; + --radius-md: 0; + --radius-xl: 0; + --radius-xxl: 0; + --text-xxs: 9px; + --text-xs: 10px; + --text-sm: 12px; + --text-md: 14px; + --text-lg: 16px; + --text-xl: 22px; + --text-xxl: 26px; + --body-text-size: var(--text-md); + --body-text-weight: 400; + --embed-radius: var(--radius-lg); + --color-accent: var(--primary-500); + --shadow-drop: 0; + --shadow-drop-lg: 0 1px 3px 0 rgb(0 0 0 / 0.1), 0 1px 2px -1px rgb(0 0 0 / 0.1); + --shadow-inset: rgba(0,0,0,0.05) 0px 2px 4px 0px inset; + --block-border-width: 1px; + --block-info-text-size: var(--text-sm); + --block-info-text-weight: 400; + --block-label-border-width: 1px; + --block-label-margin: 0; + --block-label-padding: var(--spacing-sm) var(--spacing-lg); + --block-label-radius: calc(var(--radius-lg) - 1px) 0 calc(var(--radius-lg) - 1px) 0; + --block-label-right-radius: 0 calc(var(--radius-lg) - 1px) 0 calc(var(--radius-lg) - 1px); + --block-label-text-size: var(--text-sm); + --block-label-text-weight: 400; + --block-padding: var(--spacing-xl) calc(var(--spacing-xl) + 2px); + --block-radius: var(--radius-lg); + --block-shadow: var(--shadow-drop); + --block-title-background-fill: none; + --block-title-border-color: none; + --block-title-border-width: 0; + --block-title-padding: 0; + --block-title-radius: none; + --block-title-text-size: var(--text-md); + --block-title-text-weight: 400; + --container-radius: var(--radius-lg); + --form-gap-width: 1px; + --layout-gap: var(--spacing-xxl); + --panel-border-width: 0; + --section-header-text-size: var(--text-md); + --section-header-text-weight: 400; + --checkbox-border-radius: var(--radius-sm); + --checkbox-label-gap: 2px; + --checkbox-label-padding: var(--spacing-md); + --checkbox-label-shadow: var(--shadow-drop); + --checkbox-label-text-size: var(--text-md); + --checkbox-label-text-weight: 400; + --checkbox-check: url("data:image/svg+xml,%3csvg viewBox='0 0 16 16' fill='white' xmlns='http://www.w3.org/2000/svg'%3e%3cpath d='M12.207 4.793a1 1 0 010 1.414l-5 5a1 1 0 01-1.414 0l-2-2a1 1 0 011.414-1.414L6.5 9.086l4.293-4.293a1 1 0 011.414 0z'/%3e%3c/svg%3e"); + --radio-circle: url("data:image/svg+xml,%3csvg viewBox='0 0 16 16' fill='white' xmlns='http://www.w3.org/2000/svg'%3e%3ccircle cx='8' cy='8' r='3'/%3e%3c/svg%3e"); + --checkbox-shadow: var(--input-shadow); + --error-border-width: 1px; + --input-border-width: 1px; + --input-radius: var(--radius-lg); + --input-text-size: var(--text-md); + --input-text-weight: 400; + --loader-color: var(--color-accent); + --prose-text-size: var(--text-md); + --prose-text-weight: 400; + --prose-header-text-weight: 600; + --slider-color: ; + --table-radius: var(--radius-lg); + --button-large-padding: 2px 10px; + --button-large-radius: var(--radius-lg); + --button-large-text-size: var(--text-lg); + --button-large-text-weight: 400; + --button-shadow: none; + --button-shadow-active: none; + --button-shadow-hover: none; + --button-small-padding: var(--spacing-sm) calc(2 * var(--spacing-sm)); + --button-small-radius: var(--radius-lg); + --button-small-text-size: var(--text-md); + --button-small-text-weight: 400; + --button-transition: none; + --size-9: 64px; + --size-14: 64px; +} diff --git a/javascript/loader.js b/javascript/loader.js index a280f3cf5..f32c7d294 100644 --- a/javascript/loader.js +++ b/javascript/loader.js @@ -34,9 +34,10 @@ async function createSplash() { `; document.body.insertAdjacentHTML('beforeend', splash); await preloadImages(); - const imgElement = `
`; + const imgElement = `
`; document.getElementById('splash').insertAdjacentHTML('afterbegin', imgElement); } + async function removeSplash() { const splash = document.getElementById('splash'); if (splash) splash.remove(); diff --git a/javascript/script.js b/javascript/script.js index 6ad77aa26..d9e35376f 100644 --- a/javascript/script.js +++ b/javascript/script.js @@ -75,7 +75,7 @@ let executedOnLoaded = false; document.addEventListener('DOMContentLoaded', () => { const mutationObserver = new MutationObserver((m) => { - if (!executedOnLoaded && gradioApp().querySelector('#txt2img_prompt')) { + if (!executedOnLoaded && gradioApp().getElementById('txt2img_prompt')) { executedOnLoaded = true; executeCallbacks(uiLoadedCallbacks); } diff --git a/javascript/setHints.js b/javascript/setHints.js index 7398c7a5e..1ce221723 100644 --- a/javascript/setHints.js +++ b/javascript/setHints.js @@ -51,7 +51,7 @@ async function setHints() { if (localeData.data.length === 0) { const res = await fetch('/file=html/locale_en.json'); const json = await res.json(); - localeData.data = Object.values(json).flat(); + localeData.data = Object.values(json).flat().filter((e) => e.hint.length > 0); for (const e of localeData.data) e.label = e.label.toLowerCase().trim(); } const elements = [ diff --git a/javascript/style.css b/javascript/style.css index 52554cca3..4c4950fe7 100644 --- a/javascript/style.css +++ b/javascript/style.css @@ -1,6 +1,7 @@ :root, .dark{ --checkbox-label-gap: 0.25em 0.1em; --section-header-text-size: 12pt; --block-background-fill: transparent;} a { font-weight: bold; cursor: pointer; } h2 { margin-top: 1em !important; font-size: 1.4em !important; } +table { overflow-x: auto !important; overflow-y: auto !important; } div.gradio-container{ max-width: unset !important; padding: 8px !important; } div.tabitem { padding: 0 !important; } div.form { border-width: 0; box-shadow: none; background: transparent; overflow: visible; gap: 0.5em 1em; flex-grow: 1 !important; } @@ -230,7 +231,7 @@ table.settings-value-table td { padding: 0.4em; border: 1px solid #ccc; max-widt .extra-networks .description { flex: 3; } .extra-networks .tab-nav > button { margin-right: 0; height: 24px; padding: 2px 4px 2px 4px; } .extra-networks-tab { padding: 0 !important; } -.extra-network-subdirs { background: var(--input-background-fill); overflow-x: hidden; overflow-y: auto; min-width: max(20%, 120px); padding-top: 0.5em; } +.extra-network-subdirs { background: var(--input-background-fill); overflow-x: hidden; overflow-y: auto; min-width: max(15%, 120px); padding-top: 0.5em; } .extra-networks-page { display: flex } .extra-networks .custom-button { width: 120px; width: 100%; background: none; justify-content: left; text-align: left; padding: 2px 8px 2px 16px; text-indent: -8px; box-shadow: none; line-break: auto; } .extra-networks .custom-button:hover { background: var(--button-primary-background-fill) } @@ -252,7 +253,7 @@ table.settings-value-table td { padding: 0.4em; border: 1px solid #ccc; max-widt #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 +264,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,20 +283,27 @@ 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; } /* Workaround for Gradio dropdowns capturing clicks during and after fadeout */ .gradio-dropdown > label > div > div:first-child:not(.showOptions) ~ ul.options { pointer-events: none; } -.splash { position: fixed; top: 0; left: 0; width: 100vw; height: 100vh; z-index: 100; display: block; text-align: center; } -.splash-img { margin: 10% auto 0 auto; width: 512px; background-repeat: no-repeat; height: 512px; animation: move 5s infinite alternate; } +.splash { position: fixed; top: 0; left: 0; width: 100vw; height: 100vh; z-index: 1000; display: block; text-align: center; } +.splash-img { margin: 10% auto 0 auto; width: 512px; background-repeat: no-repeat; height: 512px; animation: color 10s infinite alternate; } .loading { color: white; position: absolute; top: 20%; left: 50%; transform: translateX(-50%); } .loader { width: 300px; height: 300px; border: var(--spacing-md) solid transparent; border-radius: 50%; border-top: var(--spacing-md) solid var(--primary-600); animation: spin 4s linear infinite; position: relative; } .loader::before, .loader::after { content: ""; position: absolute; top: 6px; bottom: 6px; left: 6px; right: 6px; border-radius: 50%; border: var(--spacing-md) solid transparent; } .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; } @@ -303,3 +312,8 @@ div.controlnet_main_options { display: grid; grid-template-columns: 1fr 1fr; gri from { transform: rotate(0deg); } to { transform: rotate(360deg); } } + +@keyframes color { + from { filter: hue-rotate(0deg) } + to { filter: hue-rotate(360deg) } +} diff --git a/modules/api/api.py b/modules/api/api.py index 36a22e423..e750fccb4 100644 --- a/modules/api/api.py +++ b/modules/api/api.py @@ -504,9 +504,8 @@ class Api: } 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 - import modules.ui_extra_networks res = [] - for pg in modules.ui_extra_networks.extra_pages: + for pg in shared.extra_networks: if page is not None and pg.name != page.lower(): continue for item in pg.items: 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/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..a9682bceb 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/images.py b/modules/images.py index 39d9e7246..98b44c4d0 100644 --- a/modules/images.py +++ b/modules/images.py @@ -291,7 +291,8 @@ def sanitize_filename_part(text, replace_spaces=True): class FilenameGenerator: replacements = { - 'batch_number': lambda self: NOTHING if self.index <= 1 else self.index, + 'batch_number': lambda self: self.batch_number, + 'iter_number': lambda self: self.iter_number, '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'), @@ -320,12 +321,17 @@ class FilenameGenerator: } default_time_format = '%Y%m%d%H%M%S' - def __init__(self, p, seed, prompt, image, index): + def __init__(self, p, seed, prompt, image, grid=False): self.p = p self.seed = seed self.prompt = prompt self.image = image - self.index = index if self.p is None or self.p.batch_size == 1 else self.p.batch_index + 1 + if not grid: + self.batch_number = NOTHING if self.p is None or getattr(self.p, 'batch_size', 1) == 1 else (self.p.batch_index + 1 if hasattr(self.p, 'batch_index') else NOTHING) + self.iter_number = NOTHING if self.p is None or getattr(self.p, 'n_iter', 1) == 1 else (self.p.iteration + 1 if hasattr(self.p, 'iteration') else NOTHING) + else: + self.batch_number = NOTHING + self.iter_number = NOTHING def hasprompt(self, *args): lower = self.prompt.lower() @@ -449,14 +455,14 @@ def atomically_save_image(): image_format = 'JPEG' if shared.opts.image_watermark_enabled: image = set_watermark(image, shared.opts.image_watermark) - shared.log.debug(f'Saving: image={fn} type={image_format} size={image.width}x{image.height}') + shared.log.debug(f'Saving: image="{fn}" type={image_format} size={image.width}x{image.height}') # actual save exifinfo = (exifinfo or "") if shared.opts.image_metadata else "" if image_format == 'PNG': pnginfo_data = PngImagePlugin.PngInfo() for k, v in params.pnginfo.items(): pnginfo_data.add_text(k, str(v)) - image.save(fn, format=image_format, quality=shared.opts.jpeg_quality, pnginfo=pnginfo_data if shared.opts.image_metadata else None) + image.save(fn, format=image_format, optimize=True, compress_level=9, pnginfo=pnginfo_data if shared.opts.image_metadata else None) elif image_format == 'JPEG': if image.mode == 'RGBA': shared.log.warning('Saving RGBA image as JPEG: Alpha channel will be lost') @@ -464,7 +470,7 @@ def atomically_save_image(): elif image.mode == 'I;16': image = image.point(lambda p: p * 0.0038910505836576).convert("L") exif_bytes = piexif.dump({ "Exif": { piexif.ExifIFD.UserComment: piexif.helper.UserComment.dump(exifinfo, encoding="unicode") } }) - image.save(fn, format=image_format, quality=shared.opts.jpeg_quality, exif=exif_bytes) + image.save(fn, format=image_format, optimize=True, quality=shared.opts.jpeg_quality, exif=exif_bytes) elif image_format == 'WEBP': if image.mode == 'I;16': image = image.point(lambda p: p * 0.0038910505836576).convert("RGB") @@ -489,8 +495,14 @@ def atomically_save_image(): with open(os.path.join(paths.data_path, "params.txt"), "w", encoding="utf8") as file: file.write(exifinfo) if shared.opts.save_log_fn != '' and len(exifinfo) > 0: - entry = { 'filename': filename, 'time': datetime.datetime.now().isoformat(), 'info': exifinfo } - shared.writefile(entry, os.path.join(paths.data_path, shared.opts.save_log_fn), mode='a+') + fn = os.path.join(paths.data_path, shared.opts.save_log_fn) + entries = shared.readfile(fn) + idx = len(list(entries)) + if idx == 0: + entries = [] + entry = { 'id': idx, 'filename': filename, 'time': datetime.datetime.now().isoformat(), 'info': exifinfo } + entries.append(entry) + shared.writefile(entries, fn, mode='w') save_queue.task_done() @@ -499,7 +511,7 @@ save_thread = threading.Thread(target=atomically_save_image, daemon=True) save_thread.start() -def save_image(image, path, basename, seed=None, prompt=None, extension='jpg', info=None, short_filename=False, no_prompt=False, grid=False, pnginfo_section_name='parameters', p=None, existing_info=None, forced_filename=None, suffix="", save_to_dirs=None, index=0): +def save_image(image, path, basename, seed=None, prompt=None, extension='jpg', info=None, short_filename=False, no_prompt=False, grid=False, pnginfo_section_name='parameters', p=None, existing_info=None, forced_filename=None, suffix="", save_to_dirs=None): """Save an image. Args: image (`PIL.Image`): @@ -537,7 +549,7 @@ def save_image(image, path, basename, seed=None, prompt=None, extension='jpg', i return None, None if path is None or len(path) == 0: # set default path to avoid errors when functions are triggered manually or via api and param is not set path = shared.opts.outdir_save - namegen = FilenameGenerator(p, seed, prompt, image, index) + namegen = FilenameGenerator(p, seed, prompt, image, grid=grid) if save_to_dirs is None: save_to_dirs = (grid and shared.opts.grid_save_to_dirs) or (not grid and shared.opts.save_to_dirs and not no_prompt) if save_to_dirs: diff --git a/modules/intel/ipex/__init__.py b/modules/intel/ipex/__init__.py index bd2e8e142..03cee82c7 100644 --- a/modules/intel/ipex/__init__.py +++ b/modules/intel/ipex/__init__.py @@ -17,7 +17,11 @@ def ipex_init(): # pylint: disable=too-many-statements torch.cuda.device = torch.xpu.device torch.cuda.device_count = torch.xpu.device_count torch.cuda.device_of = torch.xpu.device_of - torch.cuda.getDeviceIdListForCard = torch.xpu.getDeviceIdListForCard + # getDeviceIdListForCard is renamed since https://github.com/intel/intel-extension-for-pytorch/commit/835b41fd5c8b6facf9efee8312f20699850ee592 + if hasattr(torch.xpu, 'getDeviceIdListForCard'): + torch.cuda.getDeviceIdListForCard = torch.xpu.getDeviceIdListForCard + else: + torch.cuda.getDeviceIdListForCard = torch.xpu.get_device_id_list_per_card torch.cuda.get_device_name = torch.xpu.get_device_name torch.cuda.get_device_properties = torch.xpu.get_device_properties torch.cuda.init = torch.xpu.init diff --git a/modules/intel/ipex/attention.py b/modules/intel/ipex/attention.py index fc4ab6e26..e38689f21 100644 --- a/modules/intel/ipex/attention.py +++ b/modules/intel/ipex/attention.py @@ -64,7 +64,13 @@ def torch_bmm(input, mat2, *, out=None): original_scaled_dot_product_attention = torch.nn.functional.scaled_dot_product_attention def scaled_dot_product_attention(query, key, value, attn_mask=None, dropout_p=0.0, is_causal=False): #ARC GPUs can't allocate more than 4GB to a single block, Slice it: - shape_one, batch_size_attention, query_tokens, shape_four = query.shape + if len(query.shape) == 3: + batch_size_attention, query_tokens, shape_four = query.shape + shape_one = 1 + no_shape_one = True + else: + shape_one, batch_size_attention, query_tokens, shape_four = query.shape + no_shape_one = False block_multiply = 3.6 if query.dtype == torch.float32 else 1.8 block_size = (shape_one * batch_size_attention * query_tokens * shape_four) / 1024 * block_multiply #MB split_slice_size = batch_size_attention @@ -101,21 +107,39 @@ def scaled_dot_product_attention(query, key, value, attn_mask=None, dropout_p=0. for i2 in range(query_tokens // split_2_slice_size): # pylint: disable=invalid-name start_idx_2 = i2 * split_2_slice_size end_idx_2 = (i2 + 1) * split_2_slice_size - hidden_states[:, start_idx:end_idx, start_idx_2:end_idx_2] = original_scaled_dot_product_attention( - query[:, start_idx:end_idx, start_idx_2:end_idx_2], - key[:, start_idx:end_idx, start_idx_2:end_idx_2], - value[:, start_idx:end_idx, start_idx_2:end_idx_2], - attn_mask=attn_mask[:, start_idx:end_idx, start_idx_2:end_idx_2] if attn_mask is not None else attn_mask, + if no_shape_one: + hidden_states[start_idx:end_idx, start_idx_2:end_idx_2] = original_scaled_dot_product_attention( + query[start_idx:end_idx, start_idx_2:end_idx_2], + key[start_idx:end_idx, start_idx_2:end_idx_2], + value[start_idx:end_idx, start_idx_2:end_idx_2], + attn_mask=attn_mask[start_idx:end_idx, start_idx_2:end_idx_2] if attn_mask is not None else attn_mask, + dropout_p=dropout_p, is_causal=is_causal + ) + else: + hidden_states[:, start_idx:end_idx, start_idx_2:end_idx_2] = original_scaled_dot_product_attention( + query[:, start_idx:end_idx, start_idx_2:end_idx_2], + key[:, start_idx:end_idx, start_idx_2:end_idx_2], + value[:, start_idx:end_idx, start_idx_2:end_idx_2], + attn_mask=attn_mask[:, start_idx:end_idx, start_idx_2:end_idx_2] if attn_mask is not None else attn_mask, + dropout_p=dropout_p, is_causal=is_causal + ) + else: + if no_shape_one: + hidden_states[start_idx:end_idx] = original_scaled_dot_product_attention( + query[start_idx:end_idx], + key[start_idx:end_idx], + value[start_idx:end_idx], + attn_mask=attn_mask[start_idx:end_idx] if attn_mask is not None else attn_mask, + dropout_p=dropout_p, is_causal=is_causal + ) + else: + hidden_states[:, start_idx:end_idx] = original_scaled_dot_product_attention( + query[:, start_idx:end_idx], + key[:, start_idx:end_idx], + value[:, start_idx:end_idx], + attn_mask=attn_mask[:, start_idx:end_idx] if attn_mask is not None else attn_mask, dropout_p=dropout_p, is_causal=is_causal ) - else: - hidden_states[:, start_idx:end_idx] = original_scaled_dot_product_attention( - query[:, start_idx:end_idx], - key[:, start_idx:end_idx], - value[:, start_idx:end_idx], - attn_mask=attn_mask[:, start_idx:end_idx] if attn_mask is not None else attn_mask, - dropout_p=dropout_p, is_causal=is_causal - ) else: return original_scaled_dot_product_attention( query, key, value, attn_mask=attn_mask, dropout_p=dropout_p, is_causal=is_causal diff --git a/modules/intel/openvino/__init__.py b/modules/intel/openvino/__init__.py index 460062a7b..698c162be 100644 --- a/modules/intel/openvino/__init__.py +++ b/modules/intel/openvino/__init__.py @@ -1,4 +1,5 @@ import os +import sys import torch from openvino.frontend import FrontEndManager from openvino.frontend.pytorch.fx_decoder import TorchFXPythonDecoder @@ -13,6 +14,25 @@ from hashlib import sha256 import functools from modules import shared, devices +def BUILD_MAP_UNPACK(self, inst): + items = self.popn(inst.argval) + # ensure everything is a dict + items = [BuiltinVariable(dict).call_function(self, [x], {}) for x in items] # noqa: F821 + result = dict() + for x in items: + assert isinstance(x, ConstDictVariable) # noqa: F821 + result.update(x.items) + self.push( + ConstDictVariable( # noqa: F821 + result, + dict, + mutable_local=MutableLocal(), # noqa: F821 + **VariableTracker.propagate(items), # noqa: F821 + ) + ) +tmp_torch = sys.modules["torch"] +tmp_torch.BUILD_MAP_UNPACK_WITH_CALL = BUILD_MAP_UNPACK + compiled_cache = {} max_openvino_partitions = 0 partitioned_modules = {} @@ -65,10 +85,6 @@ def get_device(): shared.log.warning("OpenVINO: No compatible GPU detected!") os.environ.setdefault('OPENVINO_TORCH_BACKEND_DEVICE', device) shared.log.debug(f"OpenVINO Device: {device}") - if shared.opts.cuda_compile_errors and device not in core.available_devices: - shared.log.error(f"OpenVINO: Specified device {device} is not in the list of OpenVINO Available Devices") - assert device in core.available_devices, f"OpenVINO: Specified device {device} is not in the list of OpenVINO Available Devices" - return device def cache_root_path(): diff --git a/modules/processing.py b/modules/processing.py index 772bbafba..f325e6000 100644 --- a/modules/processing.py +++ b/modules/processing.py @@ -472,6 +472,7 @@ def create_infotext(p: StableDiffusionProcessing, all_prompts, all_seeds, all_su "CFG scale": p.cfg_scale, "Size": f"{p.width}x{p.height}", "Batch": f'{p.n_iter}x{p.batch_size}' if p.n_iter > 1 or p.batch_size > 1 else None, + "Index": f'{p.iteration + 1}x{index + 1}' if (p.n_iter > 1 or p.batch_size > 1) and index >= 0 else None, "Parser": shared.opts.prompt_attention, "Model": None if (not shared.opts.add_model_name_to_info) or (not shared.sd_model.sd_checkpoint_info.model_name) else shared.sd_model.sd_checkpoint_info.model_name.replace(',', '').replace(':', ''), "Model hash": getattr(p, 'sd_model_hash', None if (not shared.opts.add_model_hash_to_info) or (not shared.sd_model.sd_model_hash) else shared.sd_model.sd_model_hash), @@ -798,7 +799,7 @@ def process_images_inner(p: StableDiffusionProcessing) -> Processed: p.scripts.postprocess_batch_list(p, batch_params, batch_number=n) x_samples_ddim = batch_params.images - def infotext(index=0): # pylint: disable=function-redefined # noqa: F811 + def infotext(index): # pylint: disable=function-redefined # noqa: F811 return create_infotext(p, p.prompts, p.seeds, p.subseeds, index=index, all_negative_prompts=p.negative_prompts) for i, x_sample in enumerate(x_samples_ddim): @@ -816,7 +817,7 @@ def process_images_inner(p: StableDiffusionProcessing) -> Processed: p.restore_faces = False info = infotext(i) p.restore_faces = orig - images.save_image(Image.fromarray(x_sample), path=p.outpath_samples, basename="", seed=p.seeds[i], prompt=p.prompts[i], extension=shared.opts.samples_format, info=info, p=p, suffix="-before-face-restoration") + images.save_image(Image.fromarray(x_sample), path=p.outpath_samples, basename="", seed=p.seeds[i], prompt=p.prompts[i], extension=shared.opts.samples_format, info=info, p=p, suffix="-before-face-restore") p.ops.append('face') x_sample = modules.face_restoration.restore_faces(x_sample) image = Image.fromarray(x_sample) @@ -831,7 +832,7 @@ def process_images_inner(p: StableDiffusionProcessing) -> Processed: info = infotext(i) p.color_corrections = orig image_without_cc = apply_overlay(image, p.paste_to, i, p.overlay_images) - images.save_image(image_without_cc, path=p.outpath_samples, basename="", seed=p.seeds[i], prompt=p.prompts[i], extension=shared.opts.samples_format, info=info, p=p, suffix="-before-color-correction") + images.save_image(image_without_cc, path=p.outpath_samples, basename="", seed=p.seeds[i], prompt=p.prompts[i], extension=shared.opts.samples_format, info=info, p=p, suffix="-before-color-correct") p.ops.append('color') image = apply_color_correction(p.color_corrections[i], image) image = apply_overlay(image, p.paste_to, i, p.overlay_images) @@ -840,7 +841,7 @@ def process_images_inner(p: StableDiffusionProcessing) -> Processed: image.info["parameters"] = text output_images.append(image) if shared.opts.samples_save and not p.do_not_save_samples: - images.save_image(image, p.outpath_samples, "", p.seeds[i], p.prompts[i], shared.opts.samples_format, info=text, p=p) + images.save_image(image, p.outpath_samples, "", p.seeds[i], p.prompts[i], shared.opts.samples_format, info=text, p=p) # main save image if hasattr(p, 'mask_for_overlay') and p.mask_for_overlay and any([shared.opts.save_mask, shared.opts.save_mask_composite, shared.opts.return_mask, shared.opts.return_mask_composite]): image_mask = p.mask_for_overlay.convert('RGB') image_mask_composite = Image.composite(image.convert('RGBA').convert('RGBa'), Image.new('RGBa', image.size), images.resize_image(3, p.mask_for_overlay, image.width, image.height).convert('L')).convert('RGBA') @@ -865,13 +866,13 @@ def process_images_inner(p: StableDiffusionProcessing) -> Processed: if images.check_grid_size(output_images): grid = images.image_grid(output_images, p.batch_size) if shared.opts.return_grid: - text = infotext() + text = infotext(-1) infotexts.insert(0, text) grid.info["parameters"] = text output_images.insert(0, grid) index_of_first_image = 1 if shared.opts.grid_save: - images.save_image(grid, p.outpath_grids, "grid", p.all_seeds[0], p.all_prompts[0], shared.opts.grid_format, info=infotext(), short_filename=not shared.opts.grid_extended_filename, p=p, grid=True) + images.save_image(grid, p.outpath_grids, "", p.all_seeds[0], p.all_prompts[0], shared.opts.grid_format, info=infotext(-1), short_filename=not shared.opts.grid_extended_filename, p=p, grid=True, suffix="-grid") # main save grid if not p.disable_extra_networks and extra_network_data: modules.extra_networks.deactivate(p, extra_network_data) @@ -880,7 +881,7 @@ def process_images_inner(p: StableDiffusionProcessing) -> Processed: p, images_list=output_images, seed=p.all_seeds[0], - info=infotext(), + info=infotext(0), comments="\n".join(comments), subseed=p.all_subseeds[0], index_of_first_image=index_of_first_image, @@ -964,6 +965,9 @@ class StableDiffusionProcessingTxt2Img(StableDiffusionProcessing): if (self.hr_upscale_to_x == self.width and self.hr_upscale_to_y == self.height) or self.hr_upscaler is None or self.hr_upscaler == 'None': self.is_hr_pass = False return + if self.denoising_strength == 0: + self.is_hr_pass = False + return self.is_hr_pass = True if not shared.state.processing_has_refined_job_count: if shared.state.job_count == -1: @@ -987,7 +991,7 @@ class StableDiffusionProcessingTxt2Img(StableDiffusionProcessing): 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], shared.opts.samples_format, info=info, suffix="-before-hires", index=index+1) + images.save_image(image, self.outpath_samples, "", seeds[index], prompts[index], shared.opts.samples_format, info=info, suffix="-before-hires") if shared.backend == shared.Backend.DIFFUSERS: modules.sd_models.set_diffuser_pipe(self.sd_model, modules.sd_models.DiffusersTaskType.TEXT_2_IMAGE) @@ -1140,7 +1144,7 @@ class StableDiffusionProcessingImg2Img(StableDiffusionProcessing): self.init_img_width = img.width # pylint: disable=attribute-defined-outside-init self.init_img_height = img.height # pylint: disable=attribute-defined-outside-init if shared.opts.save_init_img: - images.save_image(img, path=shared.opts.outdir_init_images, basename=None, forced_filename=self.init_img_hash, save_to_dirs=False) + images.save_image(img, path=shared.opts.outdir_init_images, basename=None, forced_filename=self.init_img_hash, save_to_dirs=False, suffix="-init-image") image = images.flatten(img, shared.opts.img2img_background_color) if crop_region is None and self.resize_mode != 4: image = images.resize_image(self.resize_mode, image, self.width, self.height) diff --git a/modules/processing_diffusers.py b/modules/processing_diffusers.py index 9cd74f162..56444d4ec 100644 --- a/modules/processing_diffusers.py +++ b/modules/processing_diffusers.py @@ -65,7 +65,7 @@ def process_diffusers(p: StableDiffusionProcessing, seeds, prompts, negative_pro def full_vae_decode(latents, model): t0 = time.time() - if shared.opts.diffusers_move_unet and not model.has_accelerate: + if shared.opts.diffusers_move_unet and not getattr(model, 'has_accelerate', False): shared.log.debug('Moving to CPU: model=UNet') unet_device = model.unet.device model.unet.to(devices.cpu) @@ -80,7 +80,7 @@ def process_diffusers(p: StableDiffusionProcessing, seeds, prompts, negative_pro latents = latents.to(next(iter(model.vae.post_quant_conv.parameters())).dtype) decoded = model.vae.decode(latents / model.vae.config.scaling_factor, return_dict=False)[0] - if shared.opts.diffusers_move_unet and not model.has_accelerate: + if shared.opts.diffusers_move_unet and not getattr(model, 'has_accelerate', False): model.unet.to(unet_device) t1 = time.time() shared.log.debug(f'VAE decode: name={sd_vae.loaded_vae_file if sd_vae.loaded_vae_file is not None else "baked"} dtype={model.vae.dtype} upcast={upcast} images={latents.shape[0]} latents={latents.shape} time={round(t1-t0, 3)}s') @@ -88,7 +88,7 @@ def process_diffusers(p: StableDiffusionProcessing, seeds, prompts, negative_pro def full_vae_encode(image, model): shared.log.debug(f'VAE encode: name={sd_vae.loaded_vae_file if sd_vae.loaded_vae_file is not None else "baked"} dtype={model.vae.dtype} upcast={model.vae.config.get("force_upcast", None)}') - if shared.opts.diffusers_move_unet and not model.has_accelerate: + if shared.opts.diffusers_move_unet and not getattr(model, 'has_accelerate', False): shared.log.debug('Moving to CPU: model=UNet') unet_device = model.unet.device model.unet.to(devices.cpu) @@ -96,7 +96,7 @@ def process_diffusers(p: StableDiffusionProcessing, seeds, prompts, negative_pro if not shared.cmd_opts.lowvram and not shared.opts.diffusers_seq_cpu_offload: model.vae.to(devices.device) encoded = model.vae.encode(image.to(model.vae.device, model.vae.dtype)) - if shared.opts.diffusers_move_unet and not model.has_accelerate: + if shared.opts.diffusers_move_unet and not getattr(model, 'has_accelerate', False): model.unet.to(unet_device) return encoded @@ -315,10 +315,11 @@ def process_diffusers(p: StableDiffusionProcessing, seeds, prompts, negative_pro task_specific_kwargs = {"image": p.init_images, "mask_image": p.mask, "strength": p.denoising_strength, "height": 8 * math.ceil(p.height / 8), "width": 8 * math.ceil(p.width / 8)} if shared.state.interrupted or shared.state.skipped: - unload_diffusers_lora() + if lora_state['active']: + unload_diffusers_lora() return results - if shared.opts.diffusers_move_base and not shared.sd_model.has_accelerate: + if shared.opts.diffusers_move_base and not getattr(shared.sd_model, 'has_accelerate', False): shared.sd_model.to(devices.device) is_img2img = bool(sd_models.get_diffusers_task(shared.sd_model) == sd_models.DiffusersTaskType.IMAGE_2_IMAGE or sd_models.get_diffusers_task(shared.sd_model) == sd_models.DiffusersTaskType.INPAINTING) @@ -357,15 +358,16 @@ def process_diffusers(p: StableDiffusionProcessing, seeds, prompts, negative_pro output = shared.sd_model(**base_args) # pylint: disable=not-callable except AssertionError as e: shared.log.info(e) + except ValueError as e: + shared.state.interrupted = True + shared.log.error(e) if hasattr(shared.sd_model, 'embedding_db') and len(shared.sd_model.embedding_db.embeddings_used) > 0: p.extra_generation_params['Embeddings'] = ', '.join(shared.sd_model.embedding_db.embeddings_used) - if lora_state['active']: - p.extra_generation_params['LoRA method'] = shared.opts.diffusers_lora_loader - unload_diffusers_lora() - if shared.state.interrupted or shared.state.skipped: + if lora_state['active']: + unload_diffusers_lora() return results # optional hires pass @@ -407,11 +409,15 @@ def process_diffusers(p: StableDiffusionProcessing, seeds, prompts, negative_pro except AssertionError as e: shared.log.info(e) + if lora_state['active']: + p.extra_generation_params['LoRA method'] = shared.opts.diffusers_lora_loader + unload_diffusers_lora() + # optional refiner pass or decode if is_refiner_enabled: if shared.opts.save and not p.do_not_save_samples and shared.opts.save_images_before_refiner and hasattr(shared.sd_model, 'vae'): save_intermediate(latents=output.images, suffix="-before-refiner") - if shared.opts.diffusers_move_base and not shared.sd_model.has_accelerate: + if shared.opts.diffusers_move_base and not getattr(shared.sd_model, 'has_accelerate', False): shared.log.debug('Moving to CPU: model=base') shared.sd_model.to(devices.cpu) devices.torch_gc() @@ -423,9 +429,11 @@ def process_diffusers(p: StableDiffusionProcessing, seeds, prompts, negative_pro sd_samplers.create_sampler(sampler.name, shared.sd_refiner) # TODO(Patrick): For wrapped pipelines this is currently a no-op if shared.state.interrupted or shared.state.skipped: + if lora_state['active']: + unload_diffusers_lora() return results - if shared.opts.diffusers_move_refiner and not shared.sd_refiner.has_accelerate: + if shared.opts.diffusers_move_refiner and not getattr(shared.sd_refiner, 'has_accelerate', False): shared.sd_refiner.to(devices.device) refiner_is_sdxl = bool("StableDiffusionXL" in shared.sd_refiner.__class__.__name__) p.ops.append('refine') @@ -461,7 +469,7 @@ def process_diffusers(p: StableDiffusionProcessing, seeds, prompts, negative_pro for refiner_image in refiner_images: results.append(refiner_image) - if shared.opts.diffusers_move_refiner and not shared.sd_refiner.has_accelerate: + if shared.opts.diffusers_move_refiner and not getattr(shared.sd_refiner, 'has_accelerate', False): shared.log.debug('Moving to CPU: model=refiner') shared.sd_refiner.to(devices.cpu) devices.torch_gc() diff --git a/modules/realesrgan_model.py b/modules/realesrgan_model.py index 5f5b132e5..5a88f2330 100644 --- a/modules/realesrgan_model.py +++ b/modules/realesrgan_model.py @@ -1,12 +1,9 @@ import os -import sys import numpy as np from PIL import Image -from basicsr.utils.download_util import load_file_from_url from modules.upscaler import Upscaler, UpscalerData -from modules.shared import opts, device +from modules.shared import opts, device, log from modules import modelloader -import modules.errors as errors class UpscalerRealESRGAN(Upscaler): @@ -16,8 +13,7 @@ class UpscalerRealESRGAN(Upscaler): super().__init__() try: from basicsr.archs.rrdbnet_arch import RRDBNet # pylint: disable=unused-import - from realesrgan import RealESRGANer # pylint: disable=unused-import - from realesrgan.archs.srvgg_arch import SRVGGNetCompact # pylint: disable=unused-import + from modules.realesrgan_model_arch import RealESRGANer, SRVGGNetCompact # pylint: disable=unused-import self.enable = True self.scalers = [] scalers = self.load_models(path) @@ -28,11 +24,9 @@ class UpscalerRealESRGAN(Upscaler): local_model_candidates = [local_model for local_model in local_model_paths if local_model.endswith(f"{filename}.pth")] if local_model_candidates: scaler.local_data_path = local_model_candidates[0] - if scaler.name in opts.realesrgan_enabled_models: - self.scalers.append(scaler) - + self.scalers.append(scaler) except Exception as e: - errors.display(e, 'real-esrgan') + log.error(f"Error loading Real-ESRGAN: model={path} {e}") self.enable = False self.scalers = [] @@ -41,14 +35,13 @@ class UpscalerRealESRGAN(Upscaler): return img try: - from realesrgan import RealESRGANer + from modules.realesrgan_model_arch import RealESRGANer except Exception: - print("Error importing Real-ESRGAN:", file=sys.stderr) + log.error("Error importing Real-ESRGAN:") return img info = self.load_model(selected_model) - if not os.path.exists(info.local_data_path): - print(f"Unable to load RealESRGAN model: {info.name}") + if info is None or not os.path.exists(info.local_data_path): return img upsampler = RealESRGANer( @@ -70,13 +63,15 @@ class UpscalerRealESRGAN(Upscaler): try: info = next(iter([scaler for scaler in self.scalers if scaler.data_path == path]), None) if info is None: - print(f"Unable to find model info: {path}") + log.error(f"Model failed loading: type=R-ESRGAN model={info.name}") return None if info.local_data_path.startswith("http"): + from modules.modelloader import load_file_from_url info.local_data_path = load_file_from_url(url=info.data_path, model_dir=self.model_download_path, progress=True) + log.info(f"Model loaded: type=R-ESRGAN model={info.name}") return info except Exception as e: - errors.display(e, 'real-esrgan model list') + log.error(f"Model failed loading: type=R-ESRGAN model={info.name} {e}") return None def load_models(self, _): @@ -86,7 +81,7 @@ class UpscalerRealESRGAN(Upscaler): def get_realesrgan_models(scaler): try: from basicsr.archs.rrdbnet_arch import RRDBNet - from realesrgan.archs.srvgg_arch import SRVGGNetCompact + from modules.realesrgan_model_arch import SRVGGNetCompact # pylint: disable=unused-import models = [ UpscalerData( name="R-ESRGAN General 4xV3", @@ -132,6 +127,6 @@ def get_realesrgan_models(scaler): ), ] return models - except Exception: - print("Error creating Real-ESRGAN models list", file=sys.stderr) + except Exception as e: + log.error(f'Error creating Real-ESRGAN models list: {e}') return [] diff --git a/modules/realesrgan_model_arch.py b/modules/realesrgan_model_arch.py new file mode 100644 index 000000000..50f085255 --- /dev/null +++ b/modules/realesrgan_model_arch.py @@ -0,0 +1,383 @@ +import os +import math +import queue +import threading +import cv2 +import numpy as np +import torch +from torch.nn import functional as F +from rich.progress import Progress, TextColumn, BarColumn, TaskProgressColumn, TimeRemainingColumn, TimeElapsedColumn +from modules.shared import log, console + +ROOT_DIR = os.path.dirname(os.path.dirname(os.path.abspath(__file__))) + + +class RealESRGANer(): + """A helper class for upsampling images with RealESRGAN. + + Args: + scale (int): Upsampling scale factor used in the networks. It is usually 2 or 4. + model_path (str): The path to the pretrained model. It can be urls (will first download it automatically). + model (nn.Module): The defined network. Default: None. + tile (int): As too large images result in the out of GPU memory issue, so this tile option will first crop + input images into tiles, and then process each of them. Finally, they will be merged into one image. + 0 denotes for do not use tile. Default: 0. + tile_pad (int): The pad size for each tile, to remove border artifacts. Default: 10. + pre_pad (int): Pad the input images to avoid border artifacts. Default: 10. + half (float): Whether to use half precision during inference. Default: False. + """ + + def __init__(self, + scale, + model_path, + dni_weight=None, + model=None, + tile=0, + tile_pad=10, + pre_pad=10, + half=False, + device=None, + gpu_id=None): + self.scale = scale + self.tile_size = tile + self.tile_pad = tile_pad + self.pre_pad = pre_pad + self.mod_scale = None + self.half = half + + # initialize model + if gpu_id: + self.device = torch.device( + f'cuda:{gpu_id}' if torch.cuda.is_available() else 'cpu') if device is None else device + else: + self.device = torch.device('cuda' if torch.cuda.is_available() else 'cpu') if device is None else device + + if isinstance(model_path, list): + # dni + assert len(model_path) == len(dni_weight), 'model_path and dni_weight should have the save length.' + loadnet = self.dni(model_path[0], model_path[1], dni_weight) + else: + # if the model_path starts with https, it will first download models to the folder: weights + if model_path.startswith('https://'): + from modules.modelloader import load_file_from_url + model_path = load_file_from_url(url=model_path, model_dir=os.path.join(ROOT_DIR, 'weights'), progress=True, file_name=None) + loadnet = torch.load(model_path, map_location=torch.device('cpu')) + + # prefer to use params_ema + if 'params_ema' in loadnet: + keyname = 'params_ema' + else: + keyname = 'params' + model.load_state_dict(loadnet[keyname], strict=True) + + model.eval() + self.model = model.to(self.device) + if self.half: + self.model = self.model.half() + + def dni(self, net_a, net_b, dni_weight, key='params', loc='cpu'): + """Deep network interpolation. + + ``Paper: Deep Network Interpolation for Continuous Imagery Effect Transition`` + """ + net_a = torch.load(net_a, map_location=torch.device(loc)) + net_b = torch.load(net_b, map_location=torch.device(loc)) + for k, v_a in net_a[key].items(): + net_a[key][k] = dni_weight[0] * v_a + dni_weight[1] * net_b[key][k] + return net_a + + def pre_process(self, img): + """Pre-process, such as pre-pad and mod pad, so that the images can be divisible + """ + img = torch.from_numpy(np.transpose(img, (2, 0, 1))).float() + self.img = img.unsqueeze(0).to(self.device) + if self.half: + self.img = self.img.half() + + # pre_pad + if self.pre_pad != 0: + self.img = F.pad(self.img, (0, self.pre_pad, 0, self.pre_pad), 'reflect') + # mod pad for divisible borders + if self.scale == 2: + self.mod_scale = 2 + elif self.scale == 1: + self.mod_scale = 4 + if self.mod_scale is not None: + self.mod_pad_h, self.mod_pad_w = 0, 0 + _, _, h, w = self.img.size() + if (h % self.mod_scale != 0): + self.mod_pad_h = (self.mod_scale - h % self.mod_scale) + if (w % self.mod_scale != 0): + self.mod_pad_w = (self.mod_scale - w % self.mod_scale) + self.img = F.pad(self.img, (0, self.mod_pad_w, 0, self.mod_pad_h), 'reflect') + + def process(self): + # model inference + self.output = self.model(self.img) + + def tile_process(self): + """It will first crop input images to tiles, and then process each tile. + Finally, all the processed tiles are merged into one images. + + Modified from: https://github.com/ata4/esrgan-launcher + """ + batch, channel, height, width = self.img.shape + output_height = height * self.scale + output_width = width * self.scale + output_shape = (batch, channel, output_height, output_width) + + # start with black image + self.output = self.img.new_zeros(output_shape) + tiles_x = math.ceil(width / self.tile_size) + tiles_y = math.ceil(height / self.tile_size) + + # loop over all tiles + with Progress(TextColumn('[cyan]{task.description}'), BarColumn(), TaskProgressColumn(), TimeRemainingColumn(), TimeElapsedColumn(), console=console) as progress: + task = progress.add_task(description="Upscaling", total=tiles_y * tiles_x) + with torch.no_grad(): + for y in range(tiles_y): + for x in range(tiles_x): + # extract tile from input image + ofs_x = x * self.tile_size + ofs_y = y * self.tile_size + # input tile area on total image + input_start_x = ofs_x + input_end_x = min(ofs_x + self.tile_size, width) + input_start_y = ofs_y + input_end_y = min(ofs_y + self.tile_size, height) + + # input tile area on total image with padding + input_start_x_pad = max(input_start_x - self.tile_pad, 0) + input_end_x_pad = min(input_end_x + self.tile_pad, width) + input_start_y_pad = max(input_start_y - self.tile_pad, 0) + input_end_y_pad = min(input_end_y + self.tile_pad, height) + + # input tile dimensions + input_tile_width = input_end_x - input_start_x + input_tile_height = input_end_y - input_start_y + tile_idx = y * tiles_x + x + 1 # noqa + input_tile = self.img[:, :, input_start_y_pad:input_end_y_pad, input_start_x_pad:input_end_x_pad] + + # upscale tile + try: + output_tile = self.model(input_tile) + except Exception as e: + log.error(f'Upscale error: type=R-ESRGAN {e}') + + # output tile area on total image + output_start_x = input_start_x * self.scale + output_end_x = input_end_x * self.scale + output_start_y = input_start_y * self.scale + output_end_y = input_end_y * self.scale + + # output tile area without padding + output_start_x_tile = (input_start_x - input_start_x_pad) * self.scale + output_end_x_tile = output_start_x_tile + input_tile_width * self.scale + output_start_y_tile = (input_start_y - input_start_y_pad) * self.scale + output_end_y_tile = output_start_y_tile + input_tile_height * self.scale + + # put tile into output image + self.output[:, :, output_start_y:output_end_y, + output_start_x:output_end_x] = output_tile[:, :, output_start_y_tile:output_end_y_tile, + output_start_x_tile:output_end_x_tile] + progress.update(task, advance=1, description="Upscaling") + + def post_process(self): + # remove extra pad + if self.mod_scale is not None: + _, _, h, w = self.output.size() + self.output = self.output[:, :, 0:h - self.mod_pad_h * self.scale, 0:w - self.mod_pad_w * self.scale] + # remove prepad + if self.pre_pad != 0: + _, _, h, w = self.output.size() + self.output = self.output[:, :, 0:h - self.pre_pad * self.scale, 0:w - self.pre_pad * self.scale] + return self.output + + @torch.no_grad() + def enhance(self, img, outscale=None, alpha_upsampler='realesrgan'): + h_input, w_input = img.shape[0:2] + # img: numpy + img = img.astype(np.float32) + if np.max(img) > 256: # 16-bit image + max_range = 65535 + else: + max_range = 255 + img = img / max_range + if len(img.shape) == 2: # gray image + img_mode = 'L' + img = cv2.cvtColor(img, cv2.COLOR_GRAY2RGB) + elif img.shape[2] == 4: # RGBA image with alpha channel + img_mode = 'RGBA' + alpha = img[:, :, 3] + img = img[:, :, 0:3] + img = cv2.cvtColor(img, cv2.COLOR_BGR2RGB) + if alpha_upsampler == 'realesrgan': + alpha = cv2.cvtColor(alpha, cv2.COLOR_GRAY2RGB) + else: + img_mode = 'RGB' + img = cv2.cvtColor(img, cv2.COLOR_BGR2RGB) + + # ------------------- process image (without the alpha channel) ------------------- # + self.pre_process(img) + if self.tile_size > 0: + self.tile_process() + else: + self.process() + output_img = self.post_process() + output_img = output_img.data.squeeze().float().cpu().clamp_(0, 1).numpy() + output_img = np.transpose(output_img[[2, 1, 0], :, :], (1, 2, 0)) + if img_mode == 'L': + output_img = cv2.cvtColor(output_img, cv2.COLOR_BGR2GRAY) + + # ------------------- process the alpha channel if necessary ------------------- # + if img_mode == 'RGBA': + if alpha_upsampler == 'realesrgan': + self.pre_process(alpha) + if self.tile_size > 0: + self.tile_process() + else: + self.process() + output_alpha = self.post_process() + output_alpha = output_alpha.data.squeeze().float().cpu().clamp_(0, 1).numpy() + output_alpha = np.transpose(output_alpha[[2, 1, 0], :, :], (1, 2, 0)) + output_alpha = cv2.cvtColor(output_alpha, cv2.COLOR_BGR2GRAY) + else: # use the cv2 resize for alpha channel + h, w = alpha.shape[0:2] + output_alpha = cv2.resize(alpha, (w * self.scale, h * self.scale), interpolation=cv2.INTER_LINEAR) + + # merge the alpha channel + output_img = cv2.cvtColor(output_img, cv2.COLOR_BGR2BGRA) + output_img[:, :, 3] = output_alpha + + # ------------------------------ return ------------------------------ # + if max_range == 65535: # 16-bit image + output = (output_img * 65535.0).round().astype(np.uint16) + else: + output = (output_img * 255.0).round().astype(np.uint8) + + if outscale is not None and outscale != float(self.scale): + output = cv2.resize( + output, ( + int(w_input * outscale), + int(h_input * outscale), + ), interpolation=cv2.INTER_LANCZOS4) + + return output, img_mode + + +class PrefetchReader(threading.Thread): + """Prefetch images. + + Args: + img_list (list[str]): A image list of image paths to be read. + num_prefetch_queue (int): Number of prefetch queue. + """ + + def __init__(self, img_list, num_prefetch_queue): + super().__init__() + self.que = queue.Queue(num_prefetch_queue) + self.img_list = img_list + + def run(self): + for img_path in self.img_list: + img = cv2.imread(img_path, cv2.IMREAD_UNCHANGED) + self.que.put(img) + + self.que.put(None) + + def __next__(self): + next_item = self.que.get() + if next_item is None: + raise StopIteration + return next_item + + def __iter__(self): + return self + + +class IOConsumer(threading.Thread): + + def __init__(self, opt, que, qid): + super().__init__() + self._queue = que + self.qid = qid + self.opt = opt + + def run(self): + while True: + msg = self._queue.get() + if isinstance(msg, str) and msg == 'quit': + break + + output = msg['output'] + save_path = msg['save_path'] + cv2.imwrite(save_path, output) + +from basicsr.utils.registry import ARCH_REGISTRY +from torch import nn as nn +from torch.nn import functional as F # noqa + + +class SRVGGNetCompact(nn.Module): + """A compact VGG-style network structure for super-resolution. + + It is a compact network structure, which performs upsampling in the last layer and no convolution is + conducted on the HR feature space. + + Args: + num_in_ch (int): Channel number of inputs. Default: 3. + num_out_ch (int): Channel number of outputs. Default: 3. + num_feat (int): Channel number of intermediate features. Default: 64. + num_conv (int): Number of convolution layers in the body network. Default: 16. + upscale (int): Upsampling factor. Default: 4. + act_type (str): Activation type, options: 'relu', 'prelu', 'leakyrelu'. Default: prelu. + """ + + def __init__(self, num_in_ch=3, num_out_ch=3, num_feat=64, num_conv=16, upscale=4, act_type='prelu'): + super(SRVGGNetCompact, self).__init__() + self.num_in_ch = num_in_ch + self.num_out_ch = num_out_ch + self.num_feat = num_feat + self.num_conv = num_conv + self.upscale = upscale + self.act_type = act_type + + self.body = nn.ModuleList() + # the first conv + self.body.append(nn.Conv2d(num_in_ch, num_feat, 3, 1, 1)) + # the first activation + if act_type == 'relu': + activation = nn.ReLU(inplace=True) + elif act_type == 'prelu': + activation = nn.PReLU(num_parameters=num_feat) + elif act_type == 'leakyrelu': + activation = nn.LeakyReLU(negative_slope=0.1, inplace=True) + self.body.append(activation) + + # the body structure + for _ in range(num_conv): + self.body.append(nn.Conv2d(num_feat, num_feat, 3, 1, 1)) + # activation + if act_type == 'relu': + activation = nn.ReLU(inplace=True) + elif act_type == 'prelu': + activation = nn.PReLU(num_parameters=num_feat) + elif act_type == 'leakyrelu': + activation = nn.LeakyReLU(negative_slope=0.1, inplace=True) + self.body.append(activation) + + # the last conv + self.body.append(nn.Conv2d(num_feat, num_out_ch * upscale * upscale, 3, 1, 1)) + # upsample + self.upsampler = nn.PixelShuffle(upscale) + + def forward(self, x): + out = x + for i in range(0, len(self.body)): + out = self.body[i](out) + + out = self.upsampler(out) + # add the nearest upsampled image, so that the network learns the residual + base = F.interpolate(x, scale_factor=self.upscale, mode='nearest') + out += base + return out diff --git a/modules/sd_hijack.py b/modules/sd_hijack.py index 5cd407e19..3f465a600 100644 --- a/modules/sd_hijack.py +++ b/modules/sd_hijack.py @@ -37,45 +37,38 @@ def apply_optimizations(): can_use_sdp = hasattr(torch.nn.functional, "scaled_dot_product_attention") and callable(torch.nn.functional.scaled_dot_product_attention) if devices.device == torch.device("cpu"): if opts.cross_attention_optimization == "Scaled-Dot-Product": - shared.log.warning("Scaled dot product cross attention is not available on CPU") + shared.log.warning("Cross-attention: Scaled dot product is not available on CPU") can_use_sdp = False if opts.cross_attention_optimization == "xFormers": - shared.log.warning("xFormers cross attention is not available on CPU") + shared.log.warning("Cross-attention: xFormers is not available on CPU") shared.xformers_available = False - if opts.cross_attention_optimization == "Disable cross-attention layer optimization": - shared.log.warning("Cross-attention optimization disabled") + shared.log.info(f"Cross-attention: optimization={opts.cross_attention_optimization} options={opts.cross_attention_options}") + if opts.cross_attention_optimization == "Disabled": optimization_method = 'none' if can_use_sdp and opts.cross_attention_optimization == "Scaled-Dot-Product" and 'SDP disable memory attention' in opts.cross_attention_options: - shared.log.info("Applying scaled dot product cross attention optimization (without memory efficient attention)") ldm.modules.attention.CrossAttention.forward = sd_hijack_optimizations.scaled_dot_product_no_mem_attention_forward ldm.modules.diffusionmodules.model.AttnBlock.forward = sd_hijack_optimizations.sdp_no_mem_attnblock_forward optimization_method = 'sdp-no-mem' elif can_use_sdp and opts.cross_attention_optimization == "Scaled-Dot-Product": - shared.log.info("Applying scaled dot product cross attention optimization") ldm.modules.attention.CrossAttention.forward = sd_hijack_optimizations.scaled_dot_product_attention_forward ldm.modules.diffusionmodules.model.AttnBlock.forward = sd_hijack_optimizations.sdp_attnblock_forward optimization_method = 'sdp' if shared.xformers_available and opts.cross_attention_optimization == "xFormers": - shared.log.info("Applying xformers cross attention optimization") ldm.modules.attention.CrossAttention.forward = sd_hijack_optimizations.xformers_attention_forward ldm.modules.diffusionmodules.model.AttnBlock.forward = sd_hijack_optimizations.xformers_attnblock_forward optimization_method = 'xformers' if opts.cross_attention_optimization == "Sub-quadratic": - shared.log.info("Applying sub-quadratic cross attention optimization") ldm.modules.attention.CrossAttention.forward = sd_hijack_optimizations.sub_quad_attention_forward ldm.modules.diffusionmodules.model.AttnBlock.forward = sd_hijack_optimizations.sub_quad_attnblock_forward optimization_method = 'sub-quadratic' if opts.cross_attention_optimization == "Split attention": - shared.log.info("Applying split attention optimization") ldm.modules.attention.CrossAttention.forward = sd_hijack_optimizations.split_cross_attention_forward_v1 optimization_method = 'v1' if opts.cross_attention_optimization == "InvokeAI's": - shared.log.info("Applying InvokeAI cross attention optimization") ldm.modules.attention.CrossAttention.forward = sd_hijack_optimizations.split_cross_attention_forward_invokeAI optimization_method = 'invokeai' if opts.cross_attention_optimization == "Doggettx's": - shared.log.info("Applying Doggettx cross attention optimization") ldm.modules.attention.CrossAttention.forward = sd_hijack_optimizations.split_cross_attention_forward ldm.modules.diffusionmodules.model.AttnBlock.forward = sd_hijack_optimizations.cross_attention_attnblock_forward optimization_method = 'doggettx' diff --git a/modules/sd_models.py b/modules/sd_models.py index 5278015e6..4ace52368 100644 --- a/modules/sd_models.py +++ b/modules/sd_models.py @@ -261,7 +261,7 @@ def select_checkpoint(op='model'): return None checkpoint_info = get_closet_checkpoint_match(model_checkpoint) if checkpoint_info is not None: - shared.log.debug(f'Select checkpoint: {op} {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 and not shared.cmd_opts.no_download: shared.log.error("Cannot run without a checkpoint") @@ -275,7 +275,7 @@ def select_checkpoint(op='model'): shared.log.info("Selecting first available checkpoint") # shared.log.warning(f"Loading fallback checkpoint: {checkpoint_info.title}") shared.opts.data['sd_model_checkpoint'] = checkpoint_info.title - 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 @@ -896,7 +896,7 @@ def load_diffuser(checkpoint_info=None, already_loaded_state_dict=None, timer=No base_sent_to_cpu=False if (shared.opts.cuda_compile and shared.opts.cuda_compile_backend != 'none') or shared.opts.ipex_optimize: - if op == 'refiner' and not sd_model.has_accelerate: + if op == 'refiner' and not getattr(sd_model, 'has_accelerate', False): gpu_vram = memory_stats().get('gpu', {}) free_vram = gpu_vram.get('total', 0) - gpu_vram.get('used', 0) refiner_enough_vram = free_vram >= 7 if "StableDiffusionXL" in sd_model.__class__.__name__ else 3 @@ -917,7 +917,7 @@ def load_diffuser(checkpoint_info=None, already_loaded_state_dict=None, timer=No devices.torch_gc(force=True) sd_model.to(devices.device) base_sent_to_cpu=True - elif not sd_model.has_accelerate: + elif not getattr(sd_model, 'has_accelerate', False): sd_model.to(devices.device) compile_diffusers(sd_model) @@ -931,10 +931,10 @@ def load_diffuser(checkpoint_info=None, already_loaded_state_dict=None, timer=No shared.opts.data["sd_checkpoint_hash"] = checkpoint_info.sha256 if hasattr(sd_model, "set_progress_bar_config"): sd_model.set_progress_bar_config(bar_format='Progress {rate_fmt}{postfix} {bar} {percentage:3.0f}% {n_fmt}/{total_fmt} {elapsed} {remaining}', ncols=80, colour='#327fba') - if op == 'refiner' and shared.opts.diffusers_move_refiner and not sd_model.has_accelerate: + if op == 'refiner' and shared.opts.diffusers_move_refiner and not getattr(sd_model, 'has_accelerate', False): shared.log.debug('Moving refiner model to CPU') sd_model.to(devices.cpu) - elif not sd_model.has_accelerate: # In offload modes, accelerate will move models around + elif not getattr(sd_model, 'has_accelerate', False): # In offload modes, accelerate will move models around sd_model.to(devices.device) if op == 'refiner' and base_sent_to_cpu: shared.log.debug('Moving base model back to GPU') @@ -965,10 +965,10 @@ class DiffusersTaskType(Enum): INPAINTING = 3 def set_diffuser_pipe(pipe, new_pipe_type): - sd_checkpoint_info = pipe.sd_checkpoint_info if hasattr(pipe, "sd_checkpoint_info") else None - sd_model_checkpoint = pipe.sd_model_checkpoint if hasattr(pipe, "sd_model_checkpoint") else None - sd_model_hash = pipe.sd_model_hash if hasattr(pipe, "sd_model_hash") else None - has_accelerate = pipe.has_accelerate if hasattr(pipe, "has_accelerate") else None + sd_checkpoint_info = getattr(pipe, "sd_checkpoint_info", None) + sd_model_checkpoint = getattr(pipe, "sd_model_checkpoint", None) + sd_model_hash = getattr(pipe, "sd_model_hash", None) + has_accelerate = getattr(pipe, "has_accelerate", None) if new_pipe_type == DiffusersTaskType.TEXT_2_IMAGE: new_pipe = diffusers.AutoPipelineForText2Image.from_pipe(pipe) @@ -1067,6 +1067,7 @@ def load_model(checkpoint_info=None, already_loaded_state_dict=None, timer=None, shared.log.info(f'LDM: {line.strip()}') shared.log.debug(f"Model created from config: {checkpoint_config}") sd_model.used_config = checkpoint_config + sd_model.has_accelerate = False timer.record("create") ok = load_model_weights(sd_model, checkpoint_info, state_dict, timer) if not ok: @@ -1090,7 +1091,6 @@ def load_model(checkpoint_info=None, already_loaded_state_dict=None, timer=None, sd_hijack.model_hijack.hijack(sd_model) timer.record("hijack") sd_model.eval() - sd_model.has_accelerate = False if op == 'refiner': model_data.sd_refiner = sd_model else: @@ -1126,12 +1126,12 @@ def reload_model_weights(sd_model=None, info=None, reuse_dict=False, op='model') current_checkpoint_info = getattr(sd_model, 'sd_checkpoint_info', None) if current_checkpoint_info is not None and checkpoint_info is not None and current_checkpoint_info.filename == checkpoint_info.filename: return - if not sd_model.has_accelerate: + if not getattr(sd_model, 'has_accelerate', False): if shared.cmd_opts.lowvram or shared.cmd_opts.medvram: lowvram.send_everything_to_cpu() else: sd_model.to(devices.cpu) - if (reuse_dict or shared.opts.model_reuse_dict) and not sd_model.has_accelerate: + if (reuse_dict or shared.opts.model_reuse_dict) and not getattr(sd_model, 'has_accelerate', False): shared.log.info('Reusing previous model dictionary') sd_hijack.model_hijack.undo_hijack(sd_model) else: @@ -1164,7 +1164,7 @@ def reload_model_weights(sd_model=None, info=None, reuse_dict=False, op='model') timer.record("hijack") script_callbacks.model_loaded_callback(sd_model) timer.record("callbacks") - if sd_model is not None and not shared.cmd_opts.lowvram and not shared.cmd_opts.medvram and not sd_model.has_accelerate: + if sd_model is not None and not shared.cmd_opts.lowvram and not shared.cmd_opts.medvram and not getattr(sd_model, 'has_accelerate', False): sd_model.to(devices.device) timer.record("device") shared.log.info(f"Weights loaded in {timer.summary()}") @@ -1172,7 +1172,7 @@ def reload_model_weights(sd_model=None, info=None, reuse_dict=False, op='model') def disable_offload(sd_model): from accelerate.hooks import remove_hook_from_module - if not sd_model.has_accelerate: + if not getattr(sd_model, 'has_accelerate', False): return for _name, model in sd_model.components.items(): if not isinstance(model, torch.nn.Module): diff --git a/modules/sd_samplers_common.py b/modules/sd_samplers_common.py index 4c2b3655a..b2e2daf36 100644 --- a/modules/sd_samplers_common.py +++ b/modules/sd_samplers_common.py @@ -55,6 +55,29 @@ def samples_to_image_grid(samples, approximation=None): return images.image_grid([single_sample_to_image(sample, approximation) for sample in samples]) +def images_tensor_to_samples(image, approximation=None, model=None): + '''image[0, 1] -> latent''' + if approximation is None: + approximation = approximation_indexes.get(shared.opts.show_progress_type, 0) + if approximation == 3: + image = image.to(devices.device, devices.dtype) + x_latent = sd_vae_taesd.encode(image) + else: + if model is None: + model = shared.sd_model + model.first_stage_model.to(devices.dtype_vae) + image = image.to(shared.device, dtype=devices.dtype_vae) + image = image * 2 - 1 + if len(image) > 1: + x_latent = torch.stack([ + model.get_first_stage_encoding(model.encode_first_stage(torch.unsqueeze(img, 0)))[0] + for img in image + ]) + else: + x_latent = model.get_first_stage_encoding(model.encode_first_stage(image)) + return x_latent + + def store_latent(decoded): shared.state.current_latent = decoded if shared.opts.live_previews_enable and shared.opts.show_progress_every_n_steps > 0 and shared.state.sampling_step % shared.opts.show_progress_every_n_steps == 0: diff --git a/modules/sd_vae.py b/modules/sd_vae.py index 0360d83b3..173f54337 100644 --- a/modules/sd_vae.py +++ b/modules/sd_vae.py @@ -243,7 +243,7 @@ def reload_vae_weights(sd_model=None, vae_file=unspecified): vae_source = "function-argument" if loaded_vae_file == vae_file: return - if not sd_model.has_accelerate: + if not getattr(sd_model, 'has_accelerate', False): if shared.cmd_opts.lowvram or shared.cmd_opts.medvram: lowvram.send_everything_to_cpu() else: @@ -265,6 +265,6 @@ def reload_vae_weights(sd_model=None, vae_file=unspecified): if vae is not None: sd_model.vae = vae - if not shared.cmd_opts.lowvram and not shared.cmd_opts.medvram and not sd_model.has_accelerate: + if not shared.cmd_opts.lowvram and not shared.cmd_opts.medvram and not getattr(sd_model, 'has_accelerate', False): sd_model.to(devices.device) return sd_model diff --git a/modules/shared.py b/modules/shared.py index 713074f45..d69f0ff3b 100644 --- a/modules/shared.py +++ b/modules/shared.py @@ -11,7 +11,7 @@ from enum import Enum import gradio as gr import fasteners from rich.console import Console -from modules import errors, ui_components, shared_items, cmd_args +from modules import errors, shared_items, cmd_args, ui_components from modules.paths_internal import models_path, script_path, data_path, sd_configs_path, sd_default_config, sd_model_file, default_sd_model_file, extensions_dir, extensions_builtin_dir # pylint: disable=W0611 from modules.dml import memory_providers, default_memory_provider, directml_do_hijack import modules.interrogate @@ -37,6 +37,7 @@ interrogator = modules.interrogate.InterrogateModels("interrogate") sd_upscalers = [] face_restorers = [] tab_names = [] +extra_networks = [] options_templates = {} hypernetworks = {} loaded_hypernetworks = [] @@ -71,21 +72,12 @@ restricted_opts = { compatibility_opts = ['clip_skip', 'uni_pc_lower_order_final', 'uni_pc_order'] console = Console(log_time=True, log_time_format='%H:%M:%S-%f') -def is_url(string): - parsed_url = urlparse(string) - return all([parsed_url.scheme, parsed_url.netloc]) - class Backend(Enum): ORIGINAL = 1 DIFFUSERS = 2 -def reload_hypernetworks(): - from modules.hypernetworks import hypernetwork - global hypernetworks # pylint: disable=W0603 - hypernetworks = hypernetwork.list_hypernetworks(opts.hypernetwork_dir) - class State: skipped = False @@ -192,6 +184,7 @@ class State: self.current_image = image self.id_live_preview += 1 + state = State() state.server_start = time.time() if not hasattr(cmd_opts, "use_openvino"): @@ -251,6 +244,17 @@ def list_checkpoint_tiles(): default_checkpoint = list_checkpoint_tiles()[0] if len(list_checkpoint_tiles()) > 0 else "model.ckpt" +def is_url(string): + parsed_url = urlparse(string) + return all([parsed_url.scheme, parsed_url.netloc]) + + +def reload_hypernetworks(): + from modules.hypernetworks import hypernetwork + global hypernetworks # pylint: disable=W0603 + hypernetworks = hypernetwork.list_hypernetworks(opts.hypernetwork_dir) + + def refresh_checkpoints(): import modules.sd_models # pylint: disable=W0621 return modules.sd_models.list_models() @@ -336,7 +340,7 @@ def readfile(filename, silent=False): return data -def writefile(data, filename, mode='w'): +def writefile(data, filename, mode='w', silent=False): def default(obj): log.error(f"Saving: {filename} not a valid object: {obj}") @@ -346,7 +350,8 @@ def writefile(data, filename, mode='w'): with fasteners.InterProcessLock(f"{filename}.lock"): # skipkeys=True, ensure_ascii=True, check_circular=True, allow_nan=True output = json.dumps(data, indent=2, default=default) - log.debug(f'Saving: {filename} len={len(output)}') + if not silent: + log.debug(f'Saving: {filename} len={len(output)}') with open(filename, mode, encoding="utf8") as file: file.write(output) except Exception as e: @@ -372,24 +377,26 @@ options_templates.update(options_section(('sd', "Execution & Models"), { "sd_checkpoint_autoload": OptionInfo(True, "Model autoload on server start"), "sd_model_checkpoint": OptionInfo(default_checkpoint, "Base model", gr.Dropdown, lambda: {"choices": list_checkpoint_tiles()}, refresh=refresh_checkpoints), "sd_model_refiner": OptionInfo('None', "Refiner model", gr.Dropdown, lambda: {"choices": ['None'] + list_checkpoint_tiles()}, refresh=refresh_checkpoints), - "sd_checkpoint_cache": OptionInfo(0, "Number of cached models", gr.Slider, {"minimum": 0, "maximum": 10, "step": 1}), - "sd_vae_checkpoint_cache": OptionInfo(0, "Number of cached VAEs", gr.Slider, {"minimum": 0, "maximum": 10, "step": 1}), "sd_vae": OptionInfo("Automatic", "VAE model", gr.Dropdown, lambda: {"choices": shared_items.sd_vae_items()}, refresh=shared_items.refresh_vae_list), "sd_model_dict": OptionInfo('None', "Use baseline data from a different model", gr.Dropdown, lambda: {"choices": ['None'] + list_checkpoint_tiles()}, refresh=refresh_checkpoints), "stream_load": OptionInfo(False, "Load models using stream loading method"), - "model_reuse_dict": OptionInfo(False, "When loading models attempt to reuse previous model dictionary"), + "model_reuse_dict": OptionInfo(False, "When loading models attempt to reuse previous model dictionary", gr.Checkbox, {"visible": False}), "prompt_attention": OptionInfo("Full parser", "Prompt attention parser", gr.Radio, lambda: {"choices": ["Full parser", "Compel parser", "A1111 parser", "Fixed attention"] }), "prompt_mean_norm": OptionInfo(True, "Prompt attention mean normalization"), "comma_padding_backtrack": OptionInfo(20, "Prompt padding for long prompts", gr.Slider, {"minimum": 0, "maximum": 74, "step": 1 }), + "sd_checkpoint_cache": OptionInfo(0, "Number of cached models", gr.Slider, {"minimum": 0, "maximum": 10, "step": 1}), + "sd_vae_checkpoint_cache": OptionInfo(0, "Number of cached VAEs", gr.Slider, {"minimum": 0, "maximum": 10, "step": 1}), "sd_disable_ckpt": OptionInfo(False, "Disallow usage of models in ckpt format"), })) options_templates.update(options_section(('optimizations', "Optimizations"), { "cross_attention_optimization": OptionInfo(cross_attention_optimization_default, "Cross-attention optimization method", gr.Radio, lambda: {"choices": shared_items.list_crossattention() }), "cross_attention_options": OptionInfo([], "Cross-attention advanced options", gr.CheckboxGroup, lambda: {"choices": ['xFormers enable flash Attention', 'SDP disable memory attention']}), - "sub_quad_q_chunk_size": OptionInfo(512, "Sub-quadratic cross-attention query chunk size", gr.Slider, {"minimum": 16, "maximum": 8192, "step": 8}), - "sub_quad_kv_chunk_size": OptionInfo(512, "Sub-quadratic cross-attention kv chunk size", gr.Slider, {"minimum": 0, "maximum": 8192, "step": 8}), - "sub_quad_chunk_threshold": OptionInfo(80, "Sub-quadratic cross-attention chunking threshold", gr.Slider, {"minimum": 0, "maximum": 100, "step": 1}), + "sub_quad_sep": OptionInfo("

Sub-quadratic options

", "", gr.HTML), + "sub_quad_q_chunk_size": OptionInfo(512, "cross-attention query chunk size", gr.Slider, {"minimum": 16, "maximum": 8192, "step": 8}), + "sub_quad_kv_chunk_size": OptionInfo(512, "cross-attention kv chunk size", gr.Slider, {"minimum": 0, "maximum": 8192, "step": 8}), + "sub_quad_chunk_threshold": OptionInfo(80, "cross-attention chunking threshold", gr.Slider, {"minimum": 0, "maximum": 100, "step": 1}), + "token_merging_sep": OptionInfo("

Token Merging

", "", gr.HTML), "token_merging_ratio": OptionInfo(0.0, "Token merging ratio", gr.Slider, {"minimum": 0.0, "maximum": 0.9, "step": 0.1}), "token_merging_ratio_img2img": OptionInfo(0.0, "Token merging ratio for img2img", gr.Slider, {"minimum": 0.0, "maximum": 0.9, "step": 0.1}), "token_merging_ratio_hr": OptionInfo(0.0, "Token merging ratio for hires pass", gr.Slider, {"minimum": 0.0, "maximum": 0.9, "step": 0.1}), @@ -437,7 +444,7 @@ options_templates.update(options_section(('diffusers', "Diffusers Settings"), { "diffusers_attention_slicing": OptionInfo(True if devices.backend == "ipex" else False, "Enable attention slicing"), "diffusers_model_load_variant": OptionInfo("default", "Diffusers model loading variant", gr.Radio, lambda: {"choices": ['default', 'fp32', 'fp16']}), "diffusers_vae_load_variant": OptionInfo("default", "Diffusers VAE loading variant", gr.Radio, lambda: {"choices": ['default', 'fp32', 'fp16']}), - "diffusers_lora_loader": OptionInfo("diffusers", "Diffusers LoRA loading variant", gr.Radio, lambda: {"choices": ['diffusers', 'sequential apply', 'merge and apply']}), + "diffusers_lora_loader": OptionInfo("diffusers" if cmd_opts.use_openvino else "sequential apply", "Diffusers LoRA loading variant", gr.Radio, lambda: {"choices": ['diffusers', 'sequential apply', 'merge and apply']}), "diffusers_force_zeros": OptionInfo(True, "Force zeros for prompts when empty"), "diffusers_aesthetics_score": OptionInfo(False, "Require aesthetics score"), })) @@ -525,19 +532,19 @@ options_templates.update(options_section(('saving-paths', "Image Naming & Paths" options_templates.update(options_section(('ui', "User Interface"), { "gradio_theme": OptionInfo("black-teal", "UI theme", gr.Dropdown, lambda: {"choices": list_themes()}, refresh=refresh_themes), "theme_style": OptionInfo("Auto", "Theme mode", gr.Radio, {"choices": ["Auto", "Dark", "Light"]}), - "tooltips": OptionInfo("UI Tooltips", "UI tooltips", gr.Radio, {"choices": ["None", "Browser default", "UI tooltips"]}), - "return_grid": OptionInfo(True, "Show grid in results for web"), - "return_mask": OptionInfo(False, "For inpainting, include the greyscale mask in results for web"), - "return_mask_composite": OptionInfo(False, "For inpainting, include masked composite in results for web"), + "tooltips": OptionInfo("UI Tooltips", "UI tooltips", gr.Radio, {"choices": ["None", "Browser default", "UI tooltips"], "visible": False}), + "return_grid": OptionInfo(True, "Show grid in results"), + "return_mask": OptionInfo(False, "For inpainting, include the greyscale mask in results"), + "return_mask_composite": OptionInfo(False, "For inpainting, include masked composite in results"), "disable_weights_auto_swap": OptionInfo(True, "Do not change selected model when reading generation parameters"), "send_seed": OptionInfo(True, "Send seed when sending prompt or image to other interface"), "send_size": OptionInfo(True, "Send size when sending prompt or image to another interface"), "font": OptionInfo("", "Font for image grids that have text"), - "keyedit_precision_attention": OptionInfo(0.1, "Ctrl+up/down precision when editing (attention:1.1)", gr.Slider, {"minimum": 0.01, "maximum": 0.2, "step": 0.001}), - "keyedit_precision_extra": OptionInfo(0.05, "Ctrl+up/down precision when editing ", gr.Slider, {"minimum": 0.01, "maximum": 0.2, "step": 0.001}), - "keyedit_delimiters": OptionInfo(".,\/!?%^*;:{}=`~()", "Ctrl+up/down word delimiters"), # pylint: disable=anomalous-backslash-in-string + "keyedit_precision_attention": OptionInfo(0.1, "Ctrl+up/down precision when editing (attention:1.1)", gr.Slider, {"minimum": 0.01, "maximum": 0.2, "step": 0.001, "visible": False}), + "keyedit_precision_extra": OptionInfo(0.05, "Ctrl+up/down precision when editing ", gr.Slider, {"minimum": 0.01, "maximum": 0.2, "step": 0.001, "visible": False}), + "keyedit_delimiters": OptionInfo(".,\/!?%^*;:{}=`~()", "Ctrl+up/down word delimiters", gr.Textbox, { "visible": False }), # pylint: disable=anomalous-backslash-in-string "quicksettings_list": OptionInfo(["sd_model_checkpoint"] if backend == Backend.ORIGINAL else ["sd_model_checkpoint", "sd_model_refiner"], "Quicksettings list", ui_components.DropdownMulti, lambda: {"choices": list(opts.data_labels.keys())}), - "ui_scripts_reorder": OptionInfo("", "UI scripts order"), + "ui_scripts_reorder": OptionInfo("", "UI scripts order", gr.Textbox, { "visible": False }), })) options_templates.update(options_section(('live-preview', "Live Previews"), { @@ -548,7 +555,7 @@ options_templates.update(options_section(('live-preview', "Live Previews"), { "notification_audio_path": OptionInfo("html/notification.mp3","Path to notification sound", component_args=hide_dirs, folder=True), "show_progress_every_n_steps": OptionInfo(1, "Live preview display period", gr.Slider, {"minimum": -1, "maximum": 32, "step": 1}), "show_progress_type": OptionInfo("Approximate NN", "Live preview method", gr.Radio, {"choices": ["Full VAE", "Approximate NN", "Approximate simple", "TAESD"]}), - "live_preview_content": OptionInfo("Combined", "Live preview subject", gr.Radio, {"choices": ["Combined", "Prompt", "Negative prompt"]}), + "live_preview_content": OptionInfo("Combined", "Live preview subject", gr.Radio, {"choices": ["Combined", "Prompt", "Negative prompt"], "visible": False}), "live_preview_refresh_period": OptionInfo(500, "Progress update period", gr.Slider, {"minimum": 0, "maximum": 5000, "step": 25}), "logmonitor_show": OptionInfo(True, "Show log view"), "logmonitor_refresh_period": OptionInfo(5000, "Log view update period", gr.Slider, {"minimum": 0, "maximum": 30000, "step": 25}), @@ -606,7 +613,7 @@ options_templates.update(options_section(('postprocessing', "Postprocessing"), { "postprocessing_sep_upscalers": OptionInfo("

Upscaling

", "", gr.HTML), 'upscaling_max_images_in_cache': OptionInfo(5, "Maximum number of images in upscaling cache", gr.Slider, {"minimum": 0, "maximum": 10, "step": 1}), "upscaler_for_img2img": OptionInfo("None", "Default upscaler for image resize operations", gr.Dropdown, lambda: {"choices": [x.name for x in sd_upscalers]}), - "realesrgan_enabled_models": OptionInfo(["R-ESRGAN 4x+", "R-ESRGAN 4x+ Anime6B"], "Real-ESRGAN available models", gr.CheckboxGroup, lambda: {"choices": shared_items.realesrgan_models_names()}), + # "realesrgan_enabled_models": OptionInfo(["R-ESRGAN 4x+", "R-ESRGAN 4x+ Anime6B"], "Real-ESRGAN available models", gr.CheckboxGroup, lambda: {"choices": shared_items.realesrgan_models_names()}), "ESRGAN_tile": OptionInfo(192, "Tile size for ESRGAN upscalers", gr.Slider, {"minimum": 0, "maximum": 512, "step": 16}), "ESRGAN_tile_overlap": OptionInfo(8, "Tile overlap in pixels for ESRGAN upscalers", gr.Slider, {"minimum": 0, "maximum": 48, "step": 1}), "SCUNET_tile": OptionInfo(256, "Tile size for SCUNET upscalers", gr.Slider, {"minimum": 0, "maximum": 512, "step": 16}), @@ -634,7 +641,7 @@ options_templates.update(options_section(('interrogate', "Interrogate"), { "interrogate_clip_num_beams": OptionInfo(1, "Interrogate: num_beams for BLIP", gr.Slider, {"minimum": 1, "maximum": 16, "step": 1}), "interrogate_clip_min_length": OptionInfo(32, "Interrogate: minimum description length", gr.Slider, {"minimum": 1, "maximum": 128, "step": 1}), "interrogate_clip_max_length": OptionInfo(192, "Interrogate: maximum description length", gr.Slider, {"minimum": 1, "maximum": 256, "step": 1}), - "interrogate_clip_dict_limit": OptionInfo(2048, "CLIP: maximum number of lines in text file"), + "interrogate_clip_dict_limit": OptionInfo(2048, "CLIP: maximum number of lines in text file", gr.Slider, { "visible": False }), "interrogate_clip_skip_categories": OptionInfo(["artists", "movements", "flavors"], "Interrogate: skip categories", gr.CheckboxGroup, lambda: {"choices": modules.interrogate.category_types()}, refresh=modules.interrogate.category_types), "interrogate_deepbooru_score_threshold": OptionInfo(0.65, "Interrogate: deepbooru score threshold", gr.Slider, {"minimum": 0, "maximum": 1, "step": 0.01}), "deepbooru_sort_alpha": OptionInfo(False, "Interrogate: deepbooru sort alphabetically"), @@ -644,17 +651,18 @@ options_templates.update(options_section(('interrogate', "Interrogate"), { })) options_templates.update(options_section(('extra_networks', "Extra Networks"), { + "extra_networks": OptionInfo(["All"], "Extra networks", ui_components.DropdownMulti, lambda: {"choices": ['All'] + [en.title for en in extra_networks]}), "extra_networks_card_cover": OptionInfo("sidebar", "UI position", gr.Radio, lambda: {"choices": ["cover", "inline", "sidebar"]}), "extra_networks_height": OptionInfo(53, "UI height (%)", gr.Slider, {"minimum": 10, "maximum": 100, "step": 1}), "extra_networks_sidebar_width": OptionInfo(35, "UI sidebar width (%)", gr.Slider, {"minimum": 10, "maximum": 80, "step": 1}), - "extra_networks_card_lazy": OptionInfo(True, "UI card preview lazy loading"), + "extra_networks_card_lazy": OptionInfo(True, "UI card preview lazy loading", gr.Checkbox, { "visible": False }), "extra_networks_card_size": OptionInfo(160, "UI card size (px)", gr.Slider, {"minimum": 20, "maximum": 2000, "step": 1}), "extra_networks_card_square": OptionInfo(True, "UI disable variable aspect ratio"), - "extra_networks_card_fit": OptionInfo("cover", "UI image contain method", gr.Radio, lambda: {"choices": ["contain", "cover", "fill"]}), + "extra_networks_card_fit": OptionInfo("cover", "UI image contain method", gr.Radio, lambda: {"choices": ["contain", "cover", "fill"], "visible": False}), "extra_network_skip_indexing": OptionInfo(False, "Do not automatically build extra network pages", gr.Checkbox), "lyco_patch_lora": OptionInfo(False, "Use LyCoris handler for all LoRA types", gr.Checkbox), - "lora_functional": OptionInfo(False, "Use Kohya method for handling multiple LoRA", gr.Checkbox), - "extra_networks_default_multiplier": OptionInfo(1.0, "Multiplier for extra networks", gr.Slider, {"minimum": 0.0, "maximum": 1.0, "step": 0.01}), + "lora_functional": OptionInfo(False, "Use Kohya method for handling multiple LoRA", gr.Checkbox, { "visible": False }), + "extra_networks_default_multiplier": OptionInfo(1.0, "Multiplier for extra networks", gr.Slider, {"minimum": 0.0, "maximum": 1.0, "step": 0.01, "visible": False}), "sd_hypernetwork": OptionInfo("None", "Add hypernetwork to prompt", gr.Dropdown, lambda: { "choices": ["None"] + list(hypernetworks.keys()), "visible": False }, refresh=reload_hypernetworks), })) @@ -829,7 +837,7 @@ else: opts.data['sd_backend'] = 'diffusers' if backend == Backend.DIFFUSERS else 'original' opts.data['uni_pc_lower_order_final'] = opts.schedulers_use_loworder opts.data['uni_pc_order'] = opts.schedulers_solver_order -opts.data['diffusers_lora_loader'] = 'diffusers' # TODO broken in diffusers=0.21 +# opts.data['diffusers_lora_loader'] = 'diffusers' # TODO broken in diffusers=0.21 log.info(f'Engine: backend={backend} compute={devices.backend} mode={devices.inference_context.__name__} device={devices.get_optimal_device_name()}') log.info(f'Device: {print_dict(devices.get_gpu_info())}') diff --git a/modules/shared_items.py b/modules/shared_items.py index 8bb05b064..061fe4929 100644 --- a/modules/shared_items.py +++ b/modules/shared_items.py @@ -20,7 +20,7 @@ def refresh_vae_list(): def list_crossattention(): return [ - "Disable cross-attention layer optimization", + "Disabled", "xFormers", "Scaled-Dot-Product", "Doggettx's", diff --git a/modules/ui.py b/modules/ui.py index d6f7bb62c..898da57d1 100644 --- a/modules/ui.py +++ b/modules/ui.py @@ -392,7 +392,7 @@ def create_ui(startup_timer = None): with FormGroup(): with FormRow(elem_id="sampler_selection_txt2img_alt_row1"): latent_index = gr.Dropdown(label='Secondary sampler', elem_id="txt2img_sampling_alt", choices=[x.name for x in modules.sd_samplers.samplers], value='Default', type="index") - denoising_strength = gr.Slider(minimum=0.05, maximum=1.0, step=0.01, label='Denoising strength', value=0.5, elem_id="txt2img_denoising_strength") + denoising_strength = gr.Slider(minimum=0.0, maximum=1.0, step=0.05, label='Denoising strength', value=0.5, elem_id="txt2img_denoising_strength") with FormRow(elem_id="txt2img_hires_finalres", variant="compact"): hr_final_resolution = FormHTML(value="", elem_id="txtimg_hr_finalres", label="Upscaled resolution", interactive=False) with FormRow(elem_id="txt2img_hires_fix_row1", variant="compact"): @@ -678,7 +678,7 @@ def create_ui(startup_timer = None): with FormGroup(visible=show_denoise.value, elem_id=f"{tab}_denoise_group") as denoise_group: with FormRow(): - denoising_strength = gr.Slider(minimum=0.05, maximum=1.0, step=0.01, label='Denoising strength', value=0.75, elem_id="img2img_denoising_strength") + denoising_strength = gr.Slider(minimum=0.0, maximum=1.0, step=0.05, label='Denoising strength', value=0.75, elem_id="img2img_denoising_strength") refiner_start = gr.Slider(minimum=0.0, maximum=1.0, step=0.05, label='Denoise start', value=0.0, elem_id="img2img_refiner_start") with FormGroup(visible=show_advanced.value, elem_id=f"{tab}_advanced_group") as advanced_group: @@ -1076,6 +1076,11 @@ def create_ui(startup_timer = None): loadsave.create_ui() create_dirty_indicator("tab_defaults", [], interactive=False) + with gr.TabItem("Change log", id="change_log", elem_id="system_tab_changelog"): + with open('CHANGELOG.md', 'r', encoding='utf-8') as f: + md = f.read() + gr.Markdown(md) + with gr.TabItem("Licenses", id="system_licenses", elem_id="system_tab_licenses"): gr.HTML(modules.shared.html("licenses.html"), elem_id="licenses", elem_classes="licenses") create_dirty_indicator("tab_licenses", [], interactive=False) diff --git a/modules/ui_common.py b/modules/ui_common.py index b4f31734c..8d1bb48fd 100644 --- a/modules/ui_common.py +++ b/modules/ui_common.py @@ -75,15 +75,21 @@ def save_files(js_data, images, html_info, index): class PObject: # pylint: disable=too-few-public-methods def __init__(self, d=None): if d is not None: - for key, value in d.items(): - setattr(self, key, value) - self.seed = getattr(self, 'seed', None) or getattr(self, 'Seed', None) + for k, v in d.items(): + setattr(self, k, v) self.prompt = getattr(self, 'prompt', None) or getattr(self, 'Prompt', None) - self.all_seeds = getattr(self, 'all_seeds', [self.seed]) self.all_prompts = getattr(self, 'all_prompts', [self.prompt]) + self.negative_prompt = getattr(self, 'negative_prompt', None) + self.all_negative_prompt = getattr(self, 'all_negative_prompts', [self.negative_prompt]) + self.seed = getattr(self, 'seed', None) or getattr(self, 'Seed', None) + self.all_seeds = getattr(self, 'all_seeds', [self.seed]) + self.subseed = getattr(self, 'subseed', None) + self.all_subseeds = getattr(self, 'all_subseeds', [self.subseed]) + self.width = getattr(self, 'width', None) + self.height = getattr(self, 'height', None) + self.index_of_first_image = getattr(self, 'index_of_first_image', 0) self.infotexts = getattr(self, 'infotexts', [html_info]) self.infotext = self.infotexts[0] if len(self.infotexts) > 0 else html_info - self.index_of_first_image = getattr(self, 'index_of_first_image', 0) try: data = json.loads(js_data) except Exception: @@ -115,7 +121,7 @@ def save_files(js_data, images, html_info, index): destination = os.path.join(destination, dirname) os.makedirs(destination, exist_ok = True) shutil.copy(fullfn, destination) - shared.log.info(f"Copying image: {fullfn} -> {destination}") + shared.log.info(f'Copying image: file="{fullfn}" folder="{destination}"') tgt_filename = os.path.join(destination, os.path.basename(fullfn)) modules.script_callbacks.image_save_btn_callback(tgt_filename) else: diff --git a/modules/ui_extra_networks.py b/modules/ui_extra_networks.py index 62662b9fb..bf70cf4d7 100644 --- a/modules/ui_extra_networks.py +++ b/modules/ui_extra_networks.py @@ -16,10 +16,10 @@ from modules.ui_components import ToolButton import modules.ui_symbols as symbols -extra_pages = [] allowed_dirs = [] dir_cache = {} # key=path, value=(mtime, listdir(path)) refresh_time = None +extra_pages = shared.extra_networks def listdir(path): @@ -37,9 +37,9 @@ def listdir(path): def register_page(page): # registers extra networks page for the UI; recommend doing it in on_before_ui() callback for extensions - extra_pages.append(page) + shared.extra_networks.append(page) allowed_dirs.clear() - for page in extra_pages: + for page in shared.extra_networks: for folder in page.allowed_directories_for_previews(): if folder not in allowed_dirs: allowed_dirs.append(os.path.abspath(folder)) @@ -58,7 +58,7 @@ def fetch_file(filename: str = ""): def get_metadata(page: str = "", item: str = ""): - page = next(iter([x for x in extra_pages if x.name == page]), None) + page = next(iter([x for x in shared.extra_networks if x.name == page]), None) if page is None: return JSONResponse({ 'metadata': 'none' }) metadata = page.metadata.get(item, 'none') @@ -69,7 +69,7 @@ def get_metadata(page: str = "", item: str = ""): def get_info(page: str = "", item: str = ""): - page = next(iter([x for x in extra_pages if x.name == page]), None) + page = next(iter([x for x in shared.extra_networks if x.name == page]), None) if page is None: return JSONResponse({ 'info': 'none' }) info = page.info.get(item, 'none') @@ -128,7 +128,7 @@ class ExtraNetworksPage: shared.log.error(f'Cannot evaluate extra network prompt: {item["prompt"]} {e}') if not any(self.title in x.label for x in xyz_grid.axis_options): - if self.title == 'Checkpoints': + if self.title == 'Model': return opt = xyz_grid.AxisOption(f"[Network] {self.title}", str, add_prompt, choices=lambda: [x["name"] for x in self.items]) xyz_grid.axis_options.append(opt) @@ -308,7 +308,7 @@ class ExtraNetworksPage: shared.log.error(f'Extra network save preview: {filename} {e}') return is_allowed = False - for page in extra_pages: + for page in shared.extra_networks: if any(path_is_parent(x, filename) for x in page.allowed_directories_for_previews()): is_allowed = True break @@ -339,7 +339,7 @@ class ExtraNetworksPage: def initialize(): - extra_pages.clear() + shared.extra_networks.clear() def register_pages(): @@ -353,6 +353,21 @@ def register_pages(): register_page(ExtraNetworksPageHypernetworks()) +def get_pages(): + pages = [] + if 'All' in shared.opts.extra_networks: + pages = shared.extra_networks + else: + titles = [page.title for page in shared.extra_networks] + for page in shared.opts.extra_networks: + try: + idx = titles.index(page) + except ValueError: + continue + pages.append(shared.extra_networks[idx]) + return pages + + class ExtraNetworksUi: def __init__(self): self.pages = None @@ -384,7 +399,7 @@ def create_ui(container, button, tabname, skip_indexing = False): if ui.tabname == 'txt2img': # refresh only once global refresh_time # pylint: disable=global-statement refresh_time = time.time() - for page in extra_pages: + for page in get_pages(): page.create_page(ui.tabname, skip_indexing) with gr.Tab(page.title, id=page.title.lower().replace(" ", "_"), elem_classes="extra-networks-tab"): hmtl = gr.HTML(page.html, elem_id=f'{tabname}{page.name}_extra_page', elem_classes="extra-networks-page") @@ -396,16 +411,16 @@ def create_ui(container, button, tabname, skip_indexing = False): return is_visible, gr.update(visible=is_visible), gr.update(variant=("secondary-down" if is_visible else "secondary")) def en_refresh(title): - res = [] - for page in extra_pages: + pages = [] + for page in get_pages(): if title is None or title == '' or title == page.title or len(page.html) == 0: page.refresh() page.refresh_time = None page.create_page(ui.tabname) shared.log.debug(f"Refreshing Extra networks: page='{page.title}' items={len(page.items)} tab={ui.tabname}") - res.append(page.html) + pages.append(page.html) ui.search.update(value = ui.search.value) - return res + return pages state_visible = gr.State(value=False) # pylint: disable=abstract-class-instantiated button.click(fn=toggle_visibility, inputs=[state_visible], outputs=[state_visible, container, button]) @@ -423,15 +438,13 @@ def path_is_parent(parent_path, child_path): def setup_ui(ui, gallery): def save_preview(pagename, index, images, filename): - res = [] - for page in extra_pages: + pages = [] + for page in get_pages(): if pagename is None or pagename == '' or pagename == page.title or len(page.html) == 0: page.save_preview(index, images, filename) - res.append(page.create_page(ui.tabname)) - else: - res.append(page.html) - return res - + page.create_page(ui.tabname) + pages.append(page.html) + return pages ui.button_save_preview.click( fn=save_preview, @@ -441,14 +454,13 @@ def setup_ui(ui, gallery): ) def save_description(pagename, filename, desc): - res = [] - for page in extra_pages: + pages = [] + for page in get_pages(): if pagename is None or pagename == '' or pagename == page.title or len(page.html) == 0: page.save_description(filename, desc) - res.append(page.create_page(ui.tabname)) - else: - res.append(page.html) - return res + page.create_page(ui.tabname) + pages.append(page.html) + return pages ui.button_save_description.click( fn=save_description, diff --git a/modules/ui_extra_networks_checkpoints.py b/modules/ui_extra_networks_checkpoints.py index 4a4a18e1d..39ce706be 100644 --- a/modules/ui_extra_networks_checkpoints.py +++ b/modules/ui_extra_networks_checkpoints.py @@ -7,7 +7,7 @@ from modules import shared, ui_extra_networks, sd_models class ExtraNetworksPageCheckpoints(ui_extra_networks.ExtraNetworksPage): def __init__(self): - super().__init__('Checkpoints') + super().__init__('Model') def refresh(self): shared.refresh_checkpoints() diff --git a/modules/ui_extra_networks_hypernets.py b/modules/ui_extra_networks_hypernets.py index 66ddca527..a61f5ffb6 100644 --- a/modules/ui_extra_networks_hypernets.py +++ b/modules/ui_extra_networks_hypernets.py @@ -5,7 +5,7 @@ from modules import shared, ui_extra_networks class ExtraNetworksPageHypernetworks(ui_extra_networks.ExtraNetworksPage): def __init__(self): - super().__init__('Hypernetworks') + super().__init__('Hypernetwork') def refresh(self): shared.reload_hypernetworks() diff --git a/modules/ui_extra_networks_styles.py b/modules/ui_extra_networks_styles.py index c3e402d70..db772efd1 100644 --- a/modules/ui_extra_networks_styles.py +++ b/modules/ui_extra_networks_styles.py @@ -6,7 +6,7 @@ from modules import shared, ui_extra_networks class ExtraNetworksPageStyles(ui_extra_networks.ExtraNetworksPage): def __init__(self): - super().__init__('Styles') + super().__init__('Style') def refresh(self): shared.prompt_styles.reload() diff --git a/modules/ui_extra_networks_textual_inversion.py b/modules/ui_extra_networks_textual_inversion.py index 438ce86f1..78e900858 100644 --- a/modules/ui_extra_networks_textual_inversion.py +++ b/modules/ui_extra_networks_textual_inversion.py @@ -7,7 +7,7 @@ from modules.textual_inversion.textual_inversion import Embedding class ExtraNetworksPageTextualInversion(ui_extra_networks.ExtraNetworksPage): def __init__(self): - super().__init__('Textual Inversion') + super().__init__('Embedding') self.allow_negative_prompt = True def refresh(self): diff --git a/modules/ui_tempdir.py b/modules/ui_tempdir.py index 945a004b6..38b4b5923 100644 --- a/modules/ui_tempdir.py +++ b/modules/ui_tempdir.py @@ -61,7 +61,7 @@ def pil_to_temp_file(self, img, dir: str, format="png") -> str: # pylint: disabl file_obj = tempfile.NamedTemporaryFile(delete=False, suffix=".png", dir=dir) img.save(file_obj, pnginfo=(metadata if use_metadata else None)) name = file_obj.name - shared.log.debug(f'Saving temp image: {name}') + shared.log.debug(f'Saving temp: image="{name}"') return name diff --git a/requirements.txt b/requirements.txt index d926ae426..59d4a3f55 100644 --- a/requirements.txt +++ b/requirements.txt @@ -27,7 +27,6 @@ opencv-contrib-python-headless piexif psutil pyyaml -realesrgan resize-right rich safetensors