diff --git a/CHANGELOG.md b/CHANGELOG.md index 75d99a748..fda471333 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,26 @@ # Change Log for SD.Next +## Update for 2024-06-02 + +- fix textual inversion loading +- fix gallery mtime display +- fix extra network scrollable area when using modernui +- fix control prompts list handling +- fix restore variation seed and strength +- fix negative prompt parsing from metadata +- fix stable cascade progress monitoring +- fix variation seed with hires pass +- fix loading models trained with onetrainer +- add variation seed info to metadata +- workaround for scale-by when using modernui +- lock torch-directml version +- improve xformers installer +- improve ultralytics installer (face-hires) +- improve triton installer (compile) +- improve insightface installer (faceip) +- improve mim installer (dwpose) +- add dpm++ 1s and dpm++ 3m aliases for dpm++ 2m scheduler with different orders + ## Update for 2024-05-28 ### Highlights for 2024-05-28 @@ -11,10 +32,6 @@ For details on how to enable and use it, see [Home](https://github.com/BinaryQua **ModernUI** is still in early development and not all features are available yet, please report [issues and feedback](https://github.com/BinaryQuantumSoul/sdnext-modernui/issues) Thanks to @BinaryQuantumSoul for his hard work on this project! -![Screenshot-ModernUI](html/screenshot-modernui.jpg) -![Screenshot-ModernUI-Img](html/screenshot-modernui-img2img.jpg) -![Screenshot-ModernUI-Control](html/screenshot-modernui-control.jpg) - *What else?* #### New built-in features diff --git a/README.md b/README.md index de51559bf..c9280c410 100644 --- a/README.md +++ b/README.md @@ -142,6 +142,7 @@ Also supported are modifiers such as: - [Step-by-step install guide](https://github.com/vladmandic/automatic/wiki/Installation) - [Advanced install notes](https://github.com/vladmandic/automatic/wiki/Advanced-Install) +- [Video: install and use](https://www.youtube.com/watch?v=nWTnTyFTuAs) - [Common installation errors](https://github.com/vladmandic/automatic/discussions/1627) - [FAQ](https://github.com/vladmandic/automatic/discussions/1011) diff --git a/TODO.md b/TODO.md index 1f1838914..dfac1d65b 100644 --- a/TODO.md +++ b/TODO.md @@ -2,6 +2,10 @@ Main ToDo list can be found at [GitHub projects](https://github.com/users/vladmandic/projects) +## Fix + +- ultralytics package install + ## Future Candidates - stable diffusion 3.0: unreleased @@ -10,10 +14,25 @@ Main ToDo list can be found at [GitHub projects](https://github.com/users/vladma - async lowvram: - fp8: - profiling: +- kohya-hires-fix: +- hunyuan-dit: - init latents: variations, img2img - diffusers public callbacks - include reference styles - lora: sc lora, dora, etc +- controlnet: additional models +- resadapter: +- t-gate: + +## Experimental + +- [MuLan](https://github.com/mulanai/MuLan) Multi-langunage prompts - wirte your prompts in ~110 auto-detected languages! + Compatible with SD15 and SDXL + Enable in scripts -> MuLan and set encoder to `InternVL-14B-224px` encoder + (that is currently only supported encoder, but others will be added) + Note: Model will be auto-downloaded on first use: note its huge size of 27GB + Even executing it in FP16 context will require ~16GB of VRAM for text encoder alone + *Note*: Uses fixed prompt parser, so no prompt attention will be used - [SDXL Flash Mini](https://huggingface.co/sd-community/sdxl-flash-mini) SDXL type that weighs less, consumes less video memory, and the quality has not dropped much to use, simply select from *networks -> models -> reference -> SDXL Flash Mini* diff --git a/extensions-builtin/sdnext-modernui b/extensions-builtin/sdnext-modernui index c79be7ffe..0b56557c1 160000 --- a/extensions-builtin/sdnext-modernui +++ b/extensions-builtin/sdnext-modernui @@ -1 +1 @@ -Subproject commit c79be7ffebfe9e186655f08f21cdab183d005f6b +Subproject commit 0b56557c15467d6c86b9ef1d6cbfd55bd2f52928 diff --git a/installer.py b/installer.py index 157f98961..88f50e5d4 100644 --- a/installer.py +++ b/installer.py @@ -1,3 +1,4 @@ +from functools import lru_cache import os import sys import json @@ -171,6 +172,7 @@ def print_profile(profiler: cProfile.Profile, msg: str): # check if package is installed +@lru_cache() def installed(package, friendly: str = None, reload = False, quiet = False): ok = True try: @@ -201,12 +203,12 @@ def installed(package, friendly: str = None, reload = False, quiet = False): # log.debug(f"Package version found: {p[0]} {package_version}") if len(p) > 1: exact = package_version == p[1] - ok = ok and (exact or args.experimental) if not exact and not quiet: if args.experimental: log.warning(f"Package allowing experimental: {p[0]} {package_version} required {p[1]}") else: log.warning(f"Package version mismatch: {p[0]} {package_version} required {p[1]}") + ok = ok and (exact or args.experimental) else: if not quiet: log.debug(f"Package not found: {p[0]}") @@ -227,6 +229,7 @@ def uninstall(package, quiet = False): return res +@lru_cache() def pip(arg: str, ignore: bool = False, quiet: bool = False): arg = arg.replace('>=', '==') if not quiet: @@ -248,13 +251,15 @@ def pip(arg: str, ignore: bool = False, quiet: bool = False): # install package using pip if not already installed -def install(package, friendly: str = None, ignore: bool = False): +@lru_cache() +def install(package, friendly: str = None, ignore: bool = False, reinstall: bool = False, no_deps: bool = False): res = '' if args.reinstall or args.upgrade: global quick_allowed # pylint: disable=global-statement quick_allowed = False - if args.reinstall or not installed(package, friendly): - res = pip(f"install --upgrade {package}", ignore=ignore) + if args.reinstall or reinstall or not installed(package, friendly, quiet=False): + deps = '' if not no_deps else '--no-deps' + res = pip(f"install --upgrade {deps} {package}", ignore=ignore) try: import imp # pylint: disable=deprecated-module imp.reload(pkg_resources) @@ -264,6 +269,7 @@ def install(package, friendly: str = None, ignore: bool = False): # execute git command +@lru_cache() def git(arg: str, folder: str = None, ignore: bool = False): if args.skip_git: return '' @@ -434,12 +440,13 @@ def check_torch(): log.debug(f'Torch overrides: cuda={args.use_cuda} rocm={args.use_rocm} ipex={args.use_ipex} diml={args.use_directml} openvino={args.use_openvino}') log.debug(f'Torch allowed: cuda={allow_cuda} rocm={allow_rocm} ipex={allow_ipex} diml={allow_directml} openvino={allow_openvino}') torch_command = os.environ.get('TORCH_COMMAND', '') - xformers_package = os.environ.get('XFORMERS_PACKAGE', 'none') + xformers_package = os.environ.get('XFORMERS_PACKAGE', '--pre xformers') if opts.get('cross_attention_optimization', '') == 'xFormers' or args.use_xformers else 'none' + triton_command = os.environ.get('TRITON_COMMAND', 'triton') if sys.platform == 'linux' else None def is_rocm_available(): if not allow_rocm: return False - if installed('torch-directml'): + if installed('torch-directml', quiet=True): log.debug('DirectML installation is detected. Skipping HIP SDK check.') return False if platform.system() == 'Windows': @@ -452,14 +459,7 @@ def check_torch(): pass elif allow_cuda and (shutil.which('nvidia-smi') is not None or args.use_xformers or os.path.exists(os.path.join(os.environ.get('SystemRoot') or r'C:\Windows', 'System32', 'nvidia-smi.exe'))): log.info('nVidia CUDA toolkit detected: nvidia-smi present') - if not args.use_xformers: - torch_command = os.environ.get('TORCH_COMMAND', 'torch torchvision --index-url https://download.pytorch.org/whl/cu121') - xformers_package = os.environ.get('XFORMERS_PACKAGE', '--pre triton xformers --index-url https://download.pytorch.org/whl/cu121') - else: - torch_command = os.environ.get('TORCH_COMMAND', 'torch torchvision --index-url https://download.pytorch.org/whl/cu118') - xformers_package = os.environ.get('XFORMERS_PACKAGE', '--pre triton xformers --index-url https://download.pytorch.org/whl/cu118') - if opts.get('cross_attention_optimization', '') != 'xFormers': - xformers_package = 'none' + torch_command = os.environ.get('TORCH_COMMAND', 'torch torchvision --index-url https://download.pytorch.org/whl/cu121') install('onnxruntime-gpu', 'onnxruntime-gpu', ignore=True) elif is_rocm_available(): is_windows = platform.system() == 'Windows' @@ -555,7 +555,6 @@ def check_torch(): ort_version = os.environ.get('ONNXRUNTIME_VERSION', None) ort_package = os.environ.get('ONNXRUNTIME_PACKAGE', f"--pre onnxruntime-training{'' if ort_version is None else ('==' + ort_version)} --index-url https://pypi.lsh.sh/{rocm_ver[0]}{rocm_ver[2]} --extra-index-url https://pypi.org/simple") install(ort_package, 'onnxruntime-training') - xformers_package = os.environ.get('XFORMERS_PACKAGE', 'none') elif allow_ipex and (args.use_ipex or shutil.which('sycl-ls') is not None or shutil.which('sycl-ls.exe') is not None or os.environ.get('ONEAPI_ROOT') is not None or os.path.exists('/opt/intel/oneapi') or os.path.exists("C:/Program Files (x86)/Intel/oneAPI") or os.path.exists("C:/oneAPI")): args.use_ipex = True # pylint: disable=attribute-defined-outside-init log.info('Intel OneAPI Toolkit detected') @@ -623,6 +622,8 @@ def check_torch(): if not installed('torch', quiet=True): log.debug(f'Installing torch: {torch_command}') install(torch_command, 'torch torchvision') + if triton_command is not None: + install(triton_command, 'triton') else: try: import torch @@ -666,7 +667,7 @@ def check_torch(): install(f'--no-deps {xformers_package}', ignore=True) import torch import xformers # pylint: disable=unused-import - elif not args.experimental and not args.use_xformers: + elif not args.experimental and not args.use_xformers and opts.get('cross_attention_optimization', '') != 'xFormers': uninstall('xformers') except Exception as e: log.debug(f'Cannot install xformers package: {e}') @@ -863,7 +864,7 @@ def install_requirements(): with open('requirements.txt', 'r', encoding='utf8') as f: lines = [line.strip() for line in f.readlines() if line.strip() != '' and not line.startswith('#') and line is not None] for line in lines: - install(line) + _res = install(line) if args.profile: print_profile(pr, 'Requirements') diff --git a/javascript/gallery.js b/javascript/gallery.js index 90f50b1e8..545a65726 100644 --- a/javascript/gallery.js +++ b/javascript/gallery.js @@ -163,7 +163,7 @@ class GalleryFile extends HTMLElement { this.width = cache.width; this.height = cache.height; this.size = cache.size; - this.mtime = new Date(1000 * cache.mtime); + this.mtime = new Date(cache.mtime); } else { try { const json = await delayFetchThumb(this.src); @@ -175,7 +175,7 @@ class GalleryFile extends HTMLElement { this.width = json.width; this.height = json.height; this.size = json.size; - this.mtime = new Date(1000 * json.mtime); + this.mtime = new Date(json.mtime); await idbAdd({ hash: this.hash, folder: this.folder, diff --git a/modules/control/proc/dwpose/__init__.py b/modules/control/proc/dwpose/__init__.py index 45039e920..4ae62c133 100644 --- a/modules/control/proc/dwpose/__init__.py +++ b/modules/control/proc/dwpose/__init__.py @@ -9,6 +9,7 @@ os.environ["KMP_DUPLICATE_LIB_OK"]="TRUE" import cv2 import numpy as np from PIL import Image +from installer import installed, install, log from modules.control.util import HWC3, resize_image from .draw import draw_bodypose, draw_handpose, draw_facepose checked_ok = False @@ -16,11 +17,17 @@ checked_ok = False def check_dependencies(): global checked_ok # pylint: disable=global-statement - from installer import installed, install, log - packages = [('openmim', 'openmim'), ('mmengine', 'mmengine'), ('mmcv', 'mmcv'), ('mmpose', 'mmpose'), ('mmdet', 'mmdet')] + packages = [ + ('openmim==0.3.9', 'openmim'), + ('mmengine==0.10.4', 'mmengine'), + ('mmcv==2.1.0', 'mmcv'), + ('mmpose==1.3.1', 'mmpose'), + ('mmdet==3.3.0', 'mmdet'), + ] + packages = [] for pkg in packages: if not installed(pkg[1], reload=True, quiet=True): - install(pkg[0], pkg[1], ignore=False) + install(pkg[0], pkg[1], ignore=False, no_deps=True) try: import mmcv # pylint: disable=unused-import checked_ok = True @@ -46,6 +53,7 @@ def draw_pose(pose, H, W): class DWposeDetector: def __init__(self, det_config=None, det_ckpt=None, pose_config=None, pose_ckpt=None, device="cpu"): + self.pose_estimation = None if not checked_ok: if not check_dependencies(): return @@ -57,6 +65,8 @@ class DWposeDetector: return self def __call__(self, input_image, detect_resolution=512, image_resolution=512, output_type="pil", min_confidence=0.3, **kwargs): + if self.pose_estimation is None: + log.error("DWPose: not loaded") input_image = cv2.cvtColor(np.array(input_image, dtype=np.uint8), cv2.COLOR_RGB2BGR) input_image = HWC3(input_image) diff --git a/modules/control/run.py b/modules/control/run.py index 73eadeafb..1357be713 100644 --- a/modules/control/run.py +++ b/modules/control/run.py @@ -86,6 +86,13 @@ def control_run(units: List[unit.Unit] = [], inputs: List[Image.Image] = [], ini if mask is not None and input_type == 0: input_type = 1 # inpaint always requires control_image + if sampler_index is None: + shared.log.warning('Sampler: invalid') + sampler_index = 0 + if hr_sampler_index is None: + shared.log.warning('Sampler: invalid') + hr_sampler_index = 0 + p = StableDiffusionProcessingControl( prompt = prompt, negative_prompt = negative, @@ -128,7 +135,20 @@ def control_run(units: List[unit.Unit] = [], inputs: List[Image.Image] = [], ini outpath_samples=shared.opts.outdir_samples or shared.opts.outdir_control_samples, outpath_grids=shared.opts.outdir_grids or shared.opts.outdir_control_grids, ) - processing.process_init(p) + # processing.process_init(p) + resize_mode_before = resize_mode_before if resize_name_before != 'None' and inputs is not None and len(inputs) > 0 else 0 + + # TODO monkey-patch for modernui missing tabs.select event + if selected_scale_tab_before == 0 and resize_name_before != 'None' and scale_by_before != 1 and inputs is not None and len(inputs) > 0: + shared.log.debug('Control: override resize mode=before') + selected_scale_tab_before = 1 + if selected_scale_tab_after == 0 and resize_name_after != 'None' and scale_by_after != 1: + shared.log.debug('Control: override resize mode=after') + selected_scale_tab_after = 1 + if selected_scale_tab_mask == 0 and resize_name_mask != 'None' and scale_by_mask != 1: + shared.log.debug('Control: override resize mode=mask') + selected_scale_tab_mask = 1 + # set initial resolution if resize_mode_before != 0 or inputs is None or inputs == [None]: p.width, p.height = width_before, height_before # pylint: disable=attribute-defined-outside-init diff --git a/modules/face/__init__.py b/modules/face/__init__.py index 5b4c3a31c..289bdb8e2 100644 --- a/modules/face/__init__.py +++ b/modules/face/__init__.py @@ -112,7 +112,6 @@ class Script(scripts.Script): input_images[i] = Image.open(image['name']) processed = None - processing.process_init(p) if mode == 'FaceID': # faceid runs as ipadapter in its own pipeline from modules.face.insightface import get_app app = get_app('buffalo_l') diff --git a/modules/face/faceid.py b/modules/face/faceid.py index 283594f54..f400f038a 100644 --- a/modules/face/faceid.py +++ b/modules/face/faceid.py @@ -75,7 +75,6 @@ def face_id( script_callbacks.before_process_callback(p) with context_hypertile_vae(p), context_hypertile_unet(p), devices.inference_context(): - p.init(p.all_prompts, p.all_seeds, p.all_subseeds) ip_ckpt = FACEID_MODELS[model] folder, filename = os.path.split(ip_ckpt) basename, _ext = os.path.splitext(filename) @@ -83,23 +82,13 @@ def face_id( if model_path is None: shared.log.error(f"FaceID download failed: model={model} file={ip_ckpt}") return None - if override: - shared.sd_model.scheduler = diffusers.DDIMScheduler( - num_train_timesteps=1000, - beta_start=0.00085, - beta_end=0.012, - beta_schedule="scaled_linear", - clip_sample=False, - set_alpha_to_one=False, - steps_offset=1, - ) if faceid_model_weights is None or faceid_model_name != model or not cache: shared.log.debug(f"FaceID load: model={model} file={ip_ckpt}") faceid_model_weights = torch.load(model_path, map_location="cpu") else: shared.log.debug(f"FaceID cached: model={model} file={ip_ckpt}") - if "XL Plus" in model: + if "XL Plus" in model and shared.sd_model_type == 'sd': image_encoder_path = "laion/CLIP-ViT-H-14-laion2B-s32B-b79K" original_load_ip_adapter = IPAdapterFaceIDPlusXL.load_ip_adapter IPAdapterFaceIDPlusXL.load_ip_adapter = hijack_load_ip_adapter @@ -112,7 +101,7 @@ def face_id( device=devices.device, torch_dtype=devices.dtype, ) - elif "XL" in model: + elif "XL" in model and shared.sd_model_type == 'sdxl': original_load_ip_adapter = IPAdapterFaceIDXL.load_ip_adapter IPAdapterFaceIDXL.load_ip_adapter = hijack_load_ip_adapter faceid_model = IPAdapterFaceIDXL( @@ -123,7 +112,7 @@ def face_id( device=devices.device, torch_dtype=devices.dtype, ) - elif "Plus" in model: + elif "Plus" in model and shared.sd_model_type == 'sd': original_load_ip_adapter = IPAdapterFaceIDPlus.load_ip_adapter IPAdapterFaceIDPlus.load_ip_adapter = hijack_load_ip_adapter image_encoder_path = "laion/CLIP-ViT-H-14-laion2B-s32B-b79K" @@ -136,7 +125,7 @@ def face_id( device=devices.device, torch_dtype=devices.dtype, ) - elif "Portrait" in model: + elif "Portrait" in model and shared.sd_model_type == 'sd': original_load_ip_adapter = IPAdapterFaceIDPortrait.load_ip_adapter IPAdapterFaceIDPortrait.load_ip_adapter = hijack_load_ip_adapter faceid_model = IPAdapterFaceIDPortrait( @@ -147,7 +136,7 @@ def face_id( device=devices.device, torch_dtype=devices.dtype, ) - else: + elif "Base" in model and shared.sd_model_type == 'sd': original_load_ip_adapter = IPAdapterFaceID.load_ip_adapter IPAdapterFaceID.load_ip_adapter = hijack_load_ip_adapter faceid_model = IPAdapterFaceID( @@ -158,11 +147,26 @@ def face_id( device=devices.device, torch_dtype=devices.dtype, ) + else: + shared.log.error(f'FaceID model not supported: model="{model}" class={shared.sd_model.__class__.__name__}') + return None + + if override: + shared.sd_model.scheduler = diffusers.DDIMScheduler( + num_train_timesteps=1000, + beta_start=0.00085, + beta_end=0.012, + beta_schedule="scaled_linear", + clip_sample=False, + set_alpha_to_one=False, + steps_offset=1, + ) shortcut = "v2" in model faceid_model_name = model face_embeds = [] face_images = [] + for i, source_image in enumerate(source_images): np_image = cv2.cvtColor(np.array(source_image), cv2.COLOR_RGB2BGR) faces = app.get(np_image) @@ -201,19 +205,24 @@ def face_id( faceid_model.set_scale(scale) extra_network_data = None - for i in range(p.n_iter): - p.iteration = i - p.prompts = p.all_prompts[i * p.batch_size:(i + 1) * p.batch_size] - p.negative_prompts = p.all_negative_prompts[i * p.batch_size:(i + 1) * p.batch_size] + if p.all_prompts is None or len(p.all_prompts) == 0: + processing.process_init(p) + p.init(p.all_prompts, p.all_seeds, p.all_subseeds) + for n in range(p.n_iter): + p.iteration = n + p.prompts = p.all_prompts[n * p.batch_size:(n+1) * p.batch_size] + p.negative_prompts = p.all_negative_prompts[n * p.batch_size:(n+1) * p.batch_size] + p.seeds = p.all_seeds[n * p.batch_size:(n+1) * p.batch_size] + p.subseeds = p.all_subseeds[n * p.batch_size:(n+1) * p.batch_size] p.prompts, extra_network_data = extra_networks.parse_prompts(p.prompts) - p.seeds = p.all_seeds[i * p.batch_size:(i + 1) * p.batch_size] + if not p.disable_extra_networks: with devices.autocast(): extra_networks.activate(p, extra_network_data) ip_model_dict.update({ - "prompt": p.prompts, - "negative_prompt": p.negative_prompts, - "seed": int(p.seeds[0]), + "prompt": p.prompts[0], + "negative_prompt": p.negative_prompts[0], + "seed": p.seeds[0], }) debug(f"FaceID: {ip_model_dict}") res = faceid_model.generate(**ip_model_dict) diff --git a/modules/face/insightface.py b/modules/face/insightface.py index 655dd72e6..3eb7171bf 100644 --- a/modules/face/insightface.py +++ b/modules/face/insightface.py @@ -9,14 +9,15 @@ instightface_mp = None def get_app(mp_name): global insightface_app, instightface_mp # pylint: disable=global-statement - from installer import installed, install - packages = [ - ('insightface', 'insightface'), - ('git+https://github.com/tencent-ailab/IP-Adapter.git', 'ip_adapter'), - ] - for pkg in packages: - if not installed(pkg[1], reload=False, quiet=True): - install(pkg[0], pkg[1], ignore=False) + + from installer import install, installed + if not installed('insightface', reload=False, quiet=True): + install('insightface', 'insightface', ignore=False) + install('albumentations==1.4.3', 'albumentations', ignore=False, reinstall=True) + install('pydantic==1.10.15', 'pydantic', ignore=False, reinstall=True) + if not installed('ip_adapter', reload=False, quiet=True): + install('git+https://github.com/tencent-ailab/IP-Adapter.git', 'ip_adapter', ignore=False) + if insightface_app is None or mp_name != instightface_mp: from insightface.app import FaceAnalysis import huggingface_hub as hf diff --git a/modules/face/instantid.py b/modules/face/instantid.py index e519218df..9c7c16f61 100644 --- a/modules/face/instantid.py +++ b/modules/face/instantid.py @@ -43,8 +43,6 @@ def instant_id(p: processing.StableDiffusionProcessing, app, source_images, stre controlnet_model = ControlNetModel.from_pretrained(REPO_ID, subfolder="ControlNetModel", torch_dtype=devices.dtype, cache_dir=shared.opts.diffusers_dir) sd_models.move_model(controlnet_model, devices.device) - processing.process_init(p) - # create new pipeline orig_pipeline = shared.sd_model # backup current pipeline definition shared.sd_model = StableDiffusionXLInstantIDPipeline( @@ -66,6 +64,9 @@ def instant_id(p: processing.StableDiffusionProcessing, app, source_images, stre shared.sd_model.to(dtype=devices.dtype) # pipeline specific args + if p.all_prompts is None or len(p.all_prompts) == 0: + processing.process_init(p) + p.init(p.all_prompts, p.all_seeds, p.all_subseeds) orig_prompt_attention = shared.opts.prompt_attention shared.opts.data['prompt_attention'] = 'Fixed attention' # otherwise need to deal with class_tokens_mask p.task_args['image_embeds'] = face_embeds[0].shape # placeholder @@ -73,8 +74,8 @@ def instant_id(p: processing.StableDiffusionProcessing, app, source_images, stre p.task_args['controlnet_conditioning_scale'] = float(conditioning) p.task_args['ip_adapter_scale'] = float(strength) shared.log.debug(f"InstantID args: {p.task_args}") - p.task_args['prompt'] = p.all_prompts[0] # override all logic - p.task_args['negative_prompt'] = p.all_negative_prompts[0] + p.task_args['prompt'] = p.all_prompts[0] if p.all_prompts is not None else p.prompt + p.task_args['negative_prompt'] = p.all_negative_prompts[0] if p.all_negative_prompts is not None else p.negative_prompt p.task_args['image_embeds'] = face_embeds[0] # overwrite placeholder # run processing diff --git a/modules/face/photomaker.py b/modules/face/photomaker.py index 9e86316b0..c8f58b42a 100644 --- a/modules/face/photomaker.py +++ b/modules/face/photomaker.py @@ -17,6 +17,9 @@ def photo_maker(p: processing.StableDiffusionProcessing, input_images, trigger, return None # validate prompt + if p.all_prompts is None or len(p.all_prompts) == 0: + processing.process_init(p) + p.init(p.all_prompts, p.all_seeds, p.all_subseeds) trigger_ids = shared.sd_model.tokenizer.encode(trigger) + shared.sd_model.tokenizer_2.encode(trigger) prompt_ids1 = shared.sd_model.tokenizer.encode(p.all_prompts[0]) prompt_ids2 = shared.sd_model.tokenizer_2.encode(p.all_prompts[0]) @@ -49,7 +52,7 @@ def photo_maker(p: processing.StableDiffusionProcessing, input_images, trigger, shared.opts.data['prompt_attention'] = 'Fixed attention' # otherwise need to deal with class_tokens_mask p.task_args['input_id_images'] = input_images p.task_args['start_merge_step'] = int(start * p.steps) - p.task_args['prompt'] = p.all_prompts[0] # override all logic + p.task_args['prompt'] = p.all_prompts[0] if p.all_prompts is not None else p.prompt photomaker_path = hf.hf_hub_download(repo_id="TencentARC/PhotoMaker", filename="photomaker-v1.bin", repo_type="model", cache_dir=shared.opts.diffusers_dir) shared.log.debug(f'PhotoMaker: model={photomaker_path} images={len(input_images)} trigger={trigger} args={p.task_args}') diff --git a/modules/generation_parameters_copypaste.py b/modules/generation_parameters_copypaste.py index 93b5d3dd1..97cf6d38f 100644 --- a/modules/generation_parameters_copypaste.py +++ b/modules/generation_parameters_copypaste.py @@ -217,6 +217,8 @@ def parse_generation_parameters(infotext, no_prompt=False): params.pop(next(iter(params))) params_idx = sanitized.find(f'{first_param}:') if first_param else -1 negative_idx = infotext.find("Negative prompt:") + if 'Steps:' in sanitized: + params_idx = max(params_idx, sanitized.find('Steps:')) if negative_idx == -1: # prompt can be without negative prompt prompt = infotext[:params_idx] if params_idx > 0 else infotext diff --git a/modules/images.py b/modules/images.py index 77bdb6f10..caf3a7cc4 100644 --- a/modules/images.py +++ b/modules/images.py @@ -231,7 +231,7 @@ def resize_image(resize_mode, im, width, height, upscaler_name=None, output_type return im.resize((w, h), resample=Image.Resampling.LANCZOS) # force for mask scale = max(w / im.width, h / im.height) if scale > 1.0: - upscalers = [x for x in shared.sd_upscalers if x.name == upscaler_name] + upscalers = [x for x in shared.sd_upscalers if x.name.lower().replace('-', ' ') == upscaler_name.lower().replace('-', ' ')] if len(upscalers) > 0: upscaler = upscalers[0] im = upscaler.scaler.upscale(im, scale, upscaler.data_path) @@ -240,8 +240,9 @@ def resize_image(resize_mode, im, width, height, upscaler_name=None, output_type if upscaler is not None: im = latent(im, w, h, upscaler) else: - upscaler = upscalers[0] + upscaler = shared.sd_upscalers[0] shared.log.warning(f"Resize upscaler: invalid={upscaler_name} fallback={upscaler.name}") + shared.log.debug(f"Resize upscaler: available={[u.name for u in shared.sd_upscalers]}") if im.width != w or im.height != h: # probably downsample after upscaler created larger image im = im.resize((w, h), resample=Image.Resampling.LANCZOS) return im diff --git a/modules/img2img.py b/modules/img2img.py index 210ec002a..319d02171 100644 --- a/modules/img2img.py +++ b/modules/img2img.py @@ -152,6 +152,7 @@ def img2img(id_task: str, mode: int, shared.log.debug('Init image not set') if sampler_index is None: + shared.log.warning('Sampler: invalid') sampler_index = 0 override_settings = create_override_settings_dict(override_settings_texts) diff --git a/modules/processing.py b/modules/processing.py index ae81ce833..211f790b8 100644 --- a/modules/processing.py +++ b/modules/processing.py @@ -223,7 +223,7 @@ def process_init(p: StableDiffusionProcessing): if p.all_seeds is None: reset_prompts = True if type(seed) == list: - p.all_seeds = seed + p.all_seeds = [int(s) for s in seed] else: if shared.opts.sequential_seed: p.all_seeds = [int(seed) + (x if p.subseed_strength == 0 else 0) for x in range(len(p.all_prompts))] @@ -232,14 +232,14 @@ def process_init(p: StableDiffusionProcessing): for i in range(len(p.all_prompts)): seed = get_fixed_seed(p.seed) p.all_seeds.append(int(seed) + (i if p.subseed_strength == 0 else 0)) + if p.all_subseeds is None: if type(subseed) == list: - p.all_subseeds = subseed + p.all_subseeds = [int(s) for s in subseed] else: p.all_subseeds = [int(subseed) + x for x in range(len(p.all_prompts))] if reset_prompts: p.all_prompts, p.all_negative_prompts = shared.prompt_styles.apply_styles_to_prompts(p.all_prompts, p.all_negative_prompts, p.styles, p.all_seeds) - def process_images_inner(p: StableDiffusionProcessing) -> Processed: """this is the main loop that both txt2img and img2img use; it calls func_init once inside all the scopes and func_sample once per batch""" if type(p.prompt) == list: diff --git a/modules/processing_args.py b/modules/processing_args.py index b3bc69c35..d16a6a245 100644 --- a/modules/processing_args.py +++ b/modules/processing_args.py @@ -150,7 +150,8 @@ def set_pipeline_args(p, model, prompts: list, negative_prompts: list, prompts_2 if 'generator' in possible: args['generator'] = get_generator(p) if 'latents' in possible and getattr(p, "init_latent", None) is not None: - args['latents'] = p.init_latent + if sd_models.get_diffusers_task(model) == sd_models.DiffusersTaskType.TEXT_2_IMAGE: + args['latents'] = p.init_latent if 'output_type' in possible: if not hasattr(model, 'vae'): args['output_type'] = 'np' # only set latent if model has vae diff --git a/modules/processing_diffusers.py b/modules/processing_diffusers.py index 9eddf3fba..5b2361431 100644 --- a/modules/processing_diffusers.py +++ b/modules/processing_diffusers.py @@ -103,7 +103,7 @@ def process_diffusers(p: processing.StableDiffusionProcessing): clip_skip=p.clip_skip, desc='Base', ) - shared.state.sampling_steps = base_args.get('num_inference_steps', None) or p.steps + shared.state.sampling_steps = base_args.get('prior_num_inference_steps', None) or base_args.get('num_inference_steps', None) or p.steps p.extra_generation_params['Pipeline'] = shared.sd_model.__class__.__name__ if shared.opts.scheduler_eta is not None and shared.opts.scheduler_eta > 0 and shared.opts.scheduler_eta < 1: p.extra_generation_params["Sampler Eta"] = shared.opts.scheduler_eta @@ -211,7 +211,7 @@ def process_diffusers(p: processing.StableDiffusionProcessing): desc='Hires', ) shared.state.job = 'HiRes' - shared.state.sampling_steps = hires_args.get('num_inference_steps', None) or p.steps + shared.state.sampling_steps = hires_args.get('prior_num_inference_steps', None) or hires_args.get('num_inference_steps', None) or p.steps try: sd_models_compile.check_deepcache(enable=True) output = shared.sd_model(**hires_args) # pylint: disable=not-callable @@ -276,7 +276,7 @@ def process_diffusers(p: processing.StableDiffusionProcessing): clip_skip=p.clip_skip, desc='Refiner', ) - shared.state.sampling_steps = refiner_args.get('num_inference_steps', None) or p.steps + shared.state.sampling_steps = refiner_args.get('prior_num_inference_steps', None) or refiner_args.get('num_inference_steps', None) or p.steps try: if 'requires_aesthetics_score' in shared.sd_refiner.config: # sdxl-model needs false and sdxl-refiner needs true shared.sd_refiner.register_to_config(requires_aesthetics_score = getattr(shared.sd_refiner, 'tokenizer', None) is None) diff --git a/modules/processing_helpers.py b/modules/processing_helpers.py index 3d4ae2a43..bf86f1e24 100644 --- a/modules/processing_helpers.py +++ b/modules/processing_helpers.py @@ -216,7 +216,8 @@ def decode_first_stage(model, x, full_quality=True): def get_fixed_seed(seed): if seed is None or seed == '' or seed == -1: - return int(random.randrange(4294967294)) + random.seed() + seed = int(random.randrange(4294967294)) return seed @@ -526,6 +527,7 @@ def update_sampler(p, sd_model, second_pass=False): if hasattr(sd_model, 'scheduler') and sampler_selection != 'Default': sampler = sd_samplers.all_samplers_map.get(sampler_selection, None) if sampler is None: + shared.log.warning(f'Sampler: sampler="{sampler_selection}" not found') sampler = sd_samplers.all_samplers_map.get("UniPC") if len(getattr(p, 'timesteps', [])) > 0: if 'schedulers_use_karras' in shared.opts.data: diff --git a/modules/processing_info.py b/modules/processing_info.py index 5f60fb1a1..809d87115 100644 --- a/modules/processing_info.py +++ b/modules/processing_info.py @@ -64,8 +64,6 @@ def create_infotext(p: StableDiffusionProcessing, all_prompts=None, all_seeds=No "Operations": '; '.join(ops).replace('"', '') if len(p.ops) > 0 else 'none', } if 'txt2img' in p.ops: - pass - if shared.backend == shared.Backend.ORIGINAL: args["Variation seed"] = all_subseeds[index] if p.subseed_strength > 0 else None args["Variation strength"] = p.subseed_strength if p.subseed_strength > 0 else None if 'hires' in p.ops or 'upscale' in p.ops: diff --git a/modules/prompt_parser_diffusers.py b/modules/prompt_parser_diffusers.py index 4d242458f..bfe267e2e 100644 --- a/modules/prompt_parser_diffusers.py +++ b/modules/prompt_parser_diffusers.py @@ -230,6 +230,7 @@ def pad_to_same_length(pipe, embeds): try: if getattr(pipe, "prior_pipe", None) and getattr(pipe.prior_pipe, "text_encoder", None) is not None: # Cascade empty_embed = pipe.prior_pipe.encode_prompt(device, 1, 1, False, "") + empty_embed = [torch.zeros(empty_embed[0].shape, device=empty_embed[0].device, dtype=empty_embed[0].dtype)] else: # SDXL empty_embed = pipe.encode_prompt("") except TypeError: # SD1.5 diff --git a/modules/sd_models.py b/modules/sd_models.py index d78a5a6b9..607606a94 100644 --- a/modules/sd_models.py +++ b/modules/sd_models.py @@ -12,10 +12,11 @@ import os.path from os import mkdir from urllib import request from enum import Enum +import diffusers +import diffusers.loaders.single_file_utils from rich import progress # pylint: disable=redefined-builtin import torch import safetensors.torch -import diffusers from omegaconf import OmegaConf from transformers import logging as transformers_logging from ldm.util import instantiate_from_config @@ -1056,6 +1057,7 @@ def load_diffuser(checkpoint_info=None, already_loaded_state_dict=None, timer=No else: diffusers_load_config['config'] = get_load_config(checkpoint_info.path, model_type, config_type='json') if hasattr(pipeline, 'from_single_file'): + diffusers.loaders.single_file_utils.CHECKPOINT_KEY_NAMES["clip"] = "cond_stage_model.transformer.text_model.embeddings.position_embedding.weight" # TODO patch for diffusers==0.28.0 diffusers_load_config['use_safetensors'] = True diffusers_load_config['cache_dir'] = shared.opts.hfcache_dir # use hfcache instead of diffusers dir as this is for config only in case of single-file if shared.opts.disable_accelerate: diff --git a/modules/sd_models_compile.py b/modules/sd_models_compile.py index 113335931..c6006db25 100644 --- a/modules/sd_models_compile.py +++ b/modules/sd_models_compile.py @@ -27,7 +27,7 @@ class CompiledModelState: deepcache_worker = None -def apply_compile_to_model(sd_model, function, options): +def apply_compile_to_model(sd_model, function, options, op=None): if "Model" in options: if hasattr(sd_model, 'unet') and hasattr(sd_model.unet, 'config'): sd_model.unet = function(sd_model.unet) @@ -38,7 +38,11 @@ def apply_compile_to_model(sd_model, function, options): sd_model.decoder = sd_model.decoder_pipe.decoder = function(sd_model.decoder_pipe.decoder) if hasattr(sd_model, 'prior_pipe') and hasattr(sd_model, 'prior_prior'): sd_model.prior_prior = None + if op == "nncf" and "StableCascade" in sd_model.__class__.__name__: # fixes dtype errors + backup_clip_txt_pooled_mapper = copy.deepcopy(sd_model.prior_pipe.prior.clip_txt_pooled_mapper) sd_model.prior_prior = sd_model.prior_pipe.prior = function(sd_model.prior_pipe.prior) + if op == "nncf" and "StableCascade" in sd_model.__class__.__name__: + sd_model.prior_prior.clip_txt_pooled_mapper = sd_model.prior_pipe.prior.clip_txt_pooled_mapper = backup_clip_txt_pooled_mapper if "VAE" in options: if hasattr(sd_model, 'vae') and hasattr(sd_model.vae, 'decode'): sd_model.vae = function(sd_model.vae) @@ -88,7 +92,7 @@ def ipex_optimize(sd_model): devices.torch_gc() return model - sd_model = apply_compile_to_model(sd_model, ipex_optimize_model, shared.opts.ipex_optimize) + sd_model = apply_compile_to_model(sd_model, ipex_optimize_model, shared.opts.ipex_optimize, op="ipex") t1 = time.time() shared.log.info(f"IPEX Optimize: time={t1-t0:.2f}") @@ -120,7 +124,7 @@ def nncf_compress_weights(sd_model): shared.compiled_model_state = CompiledModelState() shared.compiled_model_state.is_compiled = True - sd_model = apply_compile_to_model(sd_model, nncf_compress_model, shared.opts.nncf_compress_weights) + sd_model = apply_compile_to_model(sd_model, nncf_compress_model, shared.opts.nncf_compress_weights, op="nncf") t1 = time.time() shared.log.info(f"Compress Weights: time={t1-t0:.2f}") @@ -267,7 +271,7 @@ def compile_torch(sd_model): except Exception as e: shared.log.error(f"Torch inductor config error: {e}") - sd_model = apply_compile_to_model(sd_model, torch_compile_model, shared.opts.cuda_compile) + sd_model = apply_compile_to_model(sd_model, torch_compile_model, shared.opts.cuda_compile, op="compile") setup_logging() # compile messes with logging so reset is needed if shared.opts.cuda_compile_precompile: diff --git a/modules/sd_samplers.py b/modules/sd_samplers.py index 6ae5a5fe2..e93998d9d 100644 --- a/modules/sd_samplers.py +++ b/modules/sd_samplers.py @@ -55,6 +55,7 @@ def create_sampler(name, model): return model.scheduler config = find_sampler_config(name) if config is None or config.constructor is None: + # shared.log.warning(f'Sampler: sampler="{name}" not found') return None if shared.backend == shared.Backend.ORIGINAL: sampler = config.constructor(model) diff --git a/modules/sd_samplers_diffusers.py b/modules/sd_samplers_diffusers.py index be6aedb42..d7c2e5d0f 100644 --- a/modules/sd_samplers_diffusers.py +++ b/modules/sd_samplers_diffusers.py @@ -46,7 +46,9 @@ config = { 'UniPC': { 'solver_order': 2, 'thresholding': False, 'sample_max_value': 1.0, 'predict_x0': 'bh2', 'lower_order_final': True, 'timestep_spacing': 'linspace' }, 'DEIS': { 'solver_order': 2, 'thresholding': False, 'sample_max_value': 1.0, 'algorithm_type': "deis", 'solver_type': "logrho", 'lower_order_final': True, 'timestep_spacing': 'linspace' }, 'DPM++': { 'solver_order': 2, 'thresholding': False, 'sample_max_value': 1.0, 'algorithm_type': "dpmsolver++", 'solver_type': "midpoint", 'lower_order_final': True, 'use_karras_sigmas': False, 'final_sigmas_type': 'sigma_min' }, - 'DPM++ 2M': { 'thresholding': False, 'sample_max_value': 1.0, 'algorithm_type': "dpmsolver++", 'solver_type': "midpoint", 'lower_order_final': True, 'use_karras_sigmas': False, 'final_sigmas_type': 'zero', 'timestep_spacing': 'linspace' }, + 'DPM++ 1S': { 'thresholding': False, 'sample_max_value': 1.0, 'algorithm_type': "dpmsolver++", 'solver_type': "midpoint", 'lower_order_final': True, 'use_karras_sigmas': False, 'final_sigmas_type': 'zero', 'timestep_spacing': 'linspace', 'solver_order': 1 }, + 'DPM++ 2M': { 'thresholding': False, 'sample_max_value': 1.0, 'algorithm_type': "dpmsolver++", 'solver_type': "midpoint", 'lower_order_final': True, 'use_karras_sigmas': False, 'final_sigmas_type': 'zero', 'timestep_spacing': 'linspace', 'solver_order': 2 }, + 'DPM++ 3M': { 'thresholding': False, 'sample_max_value': 1.0, 'algorithm_type': "dpmsolver++", 'solver_type': "midpoint", 'lower_order_final': True, 'use_karras_sigmas': False, 'final_sigmas_type': 'zero', 'timestep_spacing': 'linspace', 'solver_order': 3 }, 'DPM SDE': { 'use_karras_sigmas': False, 'noise_sampler_seed': None, 'timestep_spacing': 'linspace', 'steps_offset': 0 }, 'Euler a': { 'rescale_betas_zero_snr': False, 'timestep_spacing': 'linspace' }, 'Euler': { 'interpolation_type': "linear", 'use_karras_sigmas': False, 'rescale_betas_zero_snr': False, 'timestep_spacing': 'linspace' }, @@ -77,7 +79,9 @@ samplers_data_diffusers = [ sd_samplers_common.SamplerData('Euler', lambda model: DiffusionSampler('Euler', EulerDiscreteScheduler, model), [], {}), sd_samplers_common.SamplerData('Euler a', lambda model: DiffusionSampler('Euler a', EulerAncestralDiscreteScheduler, model), [], {}), sd_samplers_common.SamplerData('DPM++', lambda model: DiffusionSampler('DPM++', DPMSolverSinglestepScheduler, model), [], {}), + sd_samplers_common.SamplerData('DPM++ 1S', lambda model: DiffusionSampler('DPM++ 1S', DPMSolverMultistepScheduler, model), [], {}), sd_samplers_common.SamplerData('DPM++ 2M', lambda model: DiffusionSampler('DPM++ 2M', DPMSolverMultistepScheduler, model), [], {}), + sd_samplers_common.SamplerData('DPM++ 3M', lambda model: DiffusionSampler('DPM++ 3M', DPMSolverMultistepScheduler, model), [], {}), sd_samplers_common.SamplerData('DPM SDE', lambda model: DiffusionSampler('DPM SDE', DPMSolverSDEScheduler, model), [], {}), sd_samplers_common.SamplerData('PNDM', lambda model: DiffusionSampler('PNDM', PNDMScheduler, model), [], {}), @@ -107,10 +111,6 @@ class DiffusionSampler: return for key, value in config.get('All', {}).items(): # apply global defaults self.config[key] = value - # shared.log.debug(f'Sampler: name={name} type=all config={self.config}') - for key, value in config.get(name, {}).items(): # apply diffusers per-scheduler defaults - self.config[key] = value - # shared.log.debug(f'Sampler: name={name} type=scheduler config={self.config}') if hasattr(model.scheduler, 'scheduler_config'): # find model defaults orig_config = model.scheduler.scheduler_config else: @@ -118,11 +118,11 @@ class DiffusionSampler: for key, value in orig_config.items(): # apply model defaults if key in self.config: self.config[key] = value - # shared.log.debug(f'Sampler: name={name} type=model config={self.config}') + for key, value in config.get(name, {}).items(): # apply diffusers per-scheduler defaults + self.config[key] = value for key, value in kwargs.items(): # apply user args, if any if key in self.config: self.config[key] = value - # shared.log.debug(f'Sampler: name={name} type=user config={self.config}') # finally apply user preferences if shared.opts.schedulers_prediction_type != 'default': self.config['prediction_type'] = shared.opts.schedulers_prediction_type @@ -136,7 +136,7 @@ class DiffusionSampler: self.config['thresholding'] = shared.opts.schedulers_use_thresholding if 'lower_order_final' in self.config: self.config['lower_order_final'] = shared.opts.schedulers_use_loworder - if 'solver_order' in self.config: + if 'solver_order' in self.config and 'DPM' not in name: self.config['solver_order'] = shared.opts.schedulers_solver_order if 'predict_x0' in self.config: self.config['predict_x0'] = shared.opts.uni_pc_variant diff --git a/modules/shared.py b/modules/shared.py index 2608feea7..d71efd5df 100644 --- a/modules/shared.py +++ b/modules/shared.py @@ -754,7 +754,7 @@ options_templates.update(options_section(('postprocessing', "Postprocessing"), { "facehires_max_size": OptionInfo(0, "Max face size", gr.Slider, {"minimum": 0, "maximum": 1024, "step": 1}), "facehires_padding": OptionInfo(10, "Face padding", gr.Slider, {"minimum": 0, "maximum": 100, "step": 1}), "face_restoration_unload": OptionInfo(False, "Move model to CPU when complete"), - "facehires_strength": OptionInfo(0.0, "Face HiRes strength", gr.Slider, {"minimum": 0, "maximum": 1, "step": 0.01}), + "facehires_strength": OptionInfo(0.0, "Face restore strength", gr.Slider, {"minimum": 0, "maximum": 1, "step": 0.01}), "code_former_weight": OptionInfo(0.2, "CodeFormer weight parameter", gr.Slider, {"minimum": 0, "maximum": 1, "step": 0.01}), "postprocessing_sep_upscalers": OptionInfo("

Upscaling

", "", gr.HTML), diff --git a/modules/styles.py b/modules/styles.py index 4e3b55a41..511cfc425 100644 --- a/modules/styles.py +++ b/modules/styles.py @@ -81,8 +81,10 @@ def apply_file_wildcards(prompt, replaced = [], not_found = [], recursion=0, see def apply_wildcards_to_prompt(prompt, all_wildcards, seed=-1, silent=False): if len(prompt) == 0: return prompt - if seed > 0: + old_state = None + if seed > 0 and len(all_wildcards) > 0: random.seed(seed) + old_state = random.getstate() replaced = {} t0 = time.time() for style_wildcards in all_wildcards: @@ -104,6 +106,8 @@ def apply_wildcards_to_prompt(prompt, all_wildcards, seed=-1, silent=False): shared.log.debug(f'Wildcards applied: {replaced} path="{shared.opts.wildcards_dir}" type=style time={t1-t0:.2f}') if (len(replaced_file) > 0 or len(not_found) > 0) and not silent: shared.log.debug(f'Wildcards applied: {replaced_file} missing: {not_found} path="{shared.opts.wildcards_dir}" type=file time={t2-t2:.2f} ') + if old_state is not None: + random.setstate(old_state) return prompt diff --git a/modules/textual_inversion/textual_inversion.py b/modules/textual_inversion/textual_inversion.py index e2d720663..bf3e19a44 100644 --- a/modules/textual_inversion/textual_inversion.py +++ b/modules/textual_inversion/textual_inversion.py @@ -140,7 +140,7 @@ class EmbeddingDatabase: def get_expected_shape(self): if shared.backend == shared.Backend.DIFFUSERS: return 0 - if shared.sd_loaded: + if not shared.sd_loaded: shared.log.error('Model not loaded') return 0 vec = shared.sd_model.cond_stage_model.encode_embedding_init_text(",", 1) diff --git a/modules/txt2img.py b/modules/txt2img.py index d0d1d8b9a..56abf3de4 100644 --- a/modules/txt2img.py +++ b/modules/txt2img.py @@ -32,8 +32,10 @@ def txt2img(id_task, override_settings = create_override_settings_dict(override_settings_texts) if sampler_index is None: + shared.log.warning('Sampler: invalid') sampler_index = 0 if hr_sampler_index is None: + shared.log.warning('Sampler: invalid') hr_sampler_index = 0 p = processing.StableDiffusionProcessingTxt2Img( diff --git a/modules/ui_common.py b/modules/ui_common.py index 1b8aea378..ec1d7f505 100644 --- a/modules/ui_common.py +++ b/modules/ui_common.py @@ -348,31 +348,37 @@ def create_override_inputs(tab): # pylint: disable=unused-argument return override_settings -def connect_reuse_seed(seed: gr.Number, reuse_seed: gr.Button, generation_info: gr.Textbox, is_subseed): +def connect_reuse_seed(seed: gr.Number, reuse_seed: gr.Button, generation_info: gr.Textbox, is_subseed, subseed_strength=None): """ Connects a 'reuse (sub)seed' button's click event so that it copies last used (sub)seed value from generation info the to the seed field. If copying subseed and subseed strength was 0, i.e. no variation seed was used, it copies the normal seed value instead.""" def copy_seed(gen_info_string: str, index: int): - res = -1 + restore_seed = -1 + restore_strength = -1 try: gen_info = json.loads(gen_info_string) shared.log.debug(f'Reuse: info={gen_info}') index -= gen_info.get('index_of_first_image', 0) index = int(index) - - if is_subseed and gen_info.get('subseed_strength', 0) > 0: + if is_subseed: all_subseeds = gen_info.get('all_subseeds', [-1]) - res = all_subseeds[index if 0 <= index < len(all_subseeds) else 0] + restore_seed = all_subseeds[index if 0 <= index < len(all_subseeds) else 0] + restore_strength = gen_info.get('subseed_strength', 0) else: all_seeds = gen_info.get('all_seeds', [-1]) - res = all_seeds[index if 0 <= index < len(all_seeds) else 0] + restore_seed = all_seeds[index if 0 <= index < len(all_seeds) else 0] except json.decoder.JSONDecodeError: if gen_info_string != '': shared.log.error(f"Error parsing JSON generation info: {gen_info_string}") - return [res, gr_show(False)] - + if is_subseed is not None: + return [restore_seed, gr_show(False), restore_strength] + else: + return [restore_seed, gr_show(False)] dummy_component = gr.Number(visible=False, value=0) - reuse_seed.click(fn=copy_seed, _js="(x, y) => [x, selected_gallery_index()]", show_progress=False, inputs=[generation_info, dummy_component], outputs=[seed, dummy_component]) + if subseed_strength is None: + reuse_seed.click(fn=copy_seed, _js="(x, y) => [x, selected_gallery_index()]", show_progress=False, inputs=[generation_info, dummy_component], outputs=[seed, dummy_component]) + else: + reuse_seed.click(fn=copy_seed, _js="(x, y) => [x, selected_gallery_index()]", show_progress=False, inputs=[generation_info, dummy_component], outputs=[seed, dummy_component, subseed_strength]) def update_token_counter(text, steps): diff --git a/modules/ui_gallery.py b/modules/ui_gallery.py index 1170f1371..a1f317caa 100644 --- a/modules/ui_gallery.py +++ b/modules/ui_gallery.py @@ -1,12 +1,13 @@ import os from datetime import datetime +from urllib.parse import unquote import gradio as gr from PIL import Image from modules import shared, ui_symbols, ui_common, images, ui_control_helpers from modules.ui_components import ToolButton - def read_media(fn): + fn = unquote(fn).replace('%3A', ':') if not os.path.isfile(fn): shared.log.error(f'Gallery not found: file="{fn}"') return [[], None, '', '', f'Media not found: {fn}'] diff --git a/modules/ui_img2img.py b/modules/ui_img2img.py index c8d82358a..09d0c5022 100644 --- a/modules/ui_img2img.py +++ b/modules/ui_img2img.py @@ -157,7 +157,7 @@ def create_ui(): img2img_gallery, img2img_generation_info, img2img_html_info, _img2img_html_info_formatted, img2img_html_log = ui_common.create_output_panel("img2img", prompt=img2img_prompt) ui_common.connect_reuse_seed(seed, reuse_seed, img2img_generation_info, is_subseed=False) - ui_common.connect_reuse_seed(subseed, reuse_subseed, img2img_generation_info, is_subseed=True) + ui_common.connect_reuse_seed(subseed, reuse_subseed, img2img_generation_info, is_subseed=True, subseed_strength=subseed_strength) img2img_prompt_img.change(fn=modules.images.image_data, inputs=[img2img_prompt_img], outputs=[img2img_prompt, img2img_prompt_img]) dummy_component1 = gr.Textbox(visible=False, value='dummy') diff --git a/modules/ui_sections.py b/modules/ui_sections.py index d83a7472d..b6082e839 100644 --- a/modules/ui_sections.py +++ b/modules/ui_sections.py @@ -136,7 +136,7 @@ def create_seed_inputs(tab, reuse_visible=True): with gr.Row(visible=False): seed_resize_from_w = gr.Slider(minimum=0, maximum=4096, step=8, label="Resize seed from width", value=0, elem_id=f"{tab}_seed_resize_from_w") seed_resize_from_h = gr.Slider(minimum=0, maximum=4096, step=8, label="Resize seed from height", value=0, elem_id=f"{tab}_seed_resize_from_h") - random_seed.click(fn=lambda: [-1, -1], show_progress=False, inputs=[], outputs=[seed, subseed]) + random_seed.click(fn=lambda: -1, show_progress=False, inputs=[], outputs=[seed]) random_subseed.click(fn=lambda: -1, show_progress=False, inputs=[], outputs=[subseed]) return seed, reuse_seed, subseed, reuse_subseed, subseed_strength, seed_resize_from_h, seed_resize_from_w @@ -316,10 +316,10 @@ def create_resize_inputs(tab, images, accordion=True, latent=False): with gr.Row(visible=True) as _resize_group: with gr.Column(elem_id=f"{tab}_column_size"): selected_scale_tab = gr.State(value=0) # pylint: disable=abstract-class-instantiated - with gr.Tabs(): - with gr.Tab(label="Fixed") as tab_scale_to: + with gr.Tabs(elem_id=f"{tab}_scale_tabs"): + with gr.Tab(label="Fixed", elem_id=f"{tab}_scale_tab_fixed") as tab_scale_to: with gr.Row(): - with gr.Column(elem_id=f"{tab}_column_size"): + with gr.Column(elem_id=f"{tab}_column_size_fixed"): with gr.Row(): width = gr.Slider(minimum=64, maximum=8192, step=8, label="Width", value=512, elem_id=f"{tab}_width") height = gr.Slider(minimum=64, maximum=8192, step=8, label="Height", value=512, elem_id=f"{tab}_height") @@ -332,7 +332,7 @@ def create_resize_inputs(tab, images, accordion=True, latent=False): detect_image_size_btn = ToolButton(value=ui_symbols.detect, elem_id=f"{tab}_detect_image_size_btn") el = tab.split('_')[0] detect_image_size_btn.click(fn=lambda w, h, _: (w or gr.update(), h or gr.update()), _js=f'currentImageResolution{el}', inputs=[dummy_component, dummy_component, dummy_component], outputs=[width, height], show_progress=False) - with gr.Tab(label="Scale") as tab_scale_by: + with gr.Tab(label="Scale", elem_id=f"{tab}_scale_tab_scale") as tab_scale_by: scale_by = gr.Slider(minimum=0.05, maximum=8.0, step=0.05, label="Scale", value=1.0, elem_id=f"{tab}_scale") for component in images: component.change(fn=lambda: None, _js="updateImg2imgResizeToTextAfterChangingImage", inputs=[], outputs=[], show_progress=False) diff --git a/modules/ui_txt2img.py b/modules/ui_txt2img.py index ab6545b8c..27d93fb7b 100644 --- a/modules/ui_txt2img.py +++ b/modules/ui_txt2img.py @@ -56,7 +56,7 @@ def create_ui(): txt2img_gallery, txt2img_generation_info, txt2img_html_info, _txt2img_html_info_formatted, txt2img_html_log = ui_common.create_output_panel("txt2img", preview=True, prompt=txt2img_prompt) ui_common.connect_reuse_seed(seed, reuse_seed, txt2img_generation_info, is_subseed=False) - ui_common.connect_reuse_seed(subseed, reuse_subseed, txt2img_generation_info, is_subseed=True) + ui_common.connect_reuse_seed(subseed, reuse_subseed, txt2img_generation_info, is_subseed=True, subseed_strength=subseed_strength) dummy_component = gr.Textbox(visible=False, value='dummy') txt2img_args = [ diff --git a/modules/zluda.py b/modules/zluda.py index 71f870267..033b7c93a 100644 --- a/modules/zluda.py +++ b/modules/zluda.py @@ -5,6 +5,7 @@ import torch from torch._prims_common import DeviceLikeType import onnxruntime as ort from modules import shared, devices +from modules.onnx_impl.execution_providers import available_execution_providers, ExecutionProvider PLATFORM = sys.platform @@ -61,10 +62,10 @@ def initialize_zluda(): shared.opts.sdp_options = ['Math attention'] # ONNX Runtime is not supported - ort.capi._pybind_state.get_available_providers = lambda: [v for v in ort.get_available_providers() if v != 'CUDAExecutionProvider'] # pylint: disable=protected-access + ort.capi._pybind_state.get_available_providers = lambda: [v for v in available_execution_providers if v != ExecutionProvider.CUDA] # pylint: disable=protected-access ort.get_available_providers = ort.capi._pybind_state.get_available_providers # pylint: disable=protected-access - if shared.opts.onnx_execution_provider == 'CUDAExecutionProvider': - shared.opts.onnx_execution_provider = 'CPUExecutionProvider' + if shared.opts.onnx_execution_provider == ExecutionProvider.CUDA: + shared.opts.onnx_execution_provider = ExecutionProvider.CPU devices.device_codeformer = devices.cpu diff --git a/scripts/face-details.py b/scripts/face-details.py index 57fa0e361..849461c77 100644 --- a/scripts/face-details.py +++ b/scripts/face-details.py @@ -32,7 +32,7 @@ class FaceRestorerYolo(FaceRestoration): def dependencies(self): import installer - installer.install('ultralytics', ignore=False) + installer.install('ultralytics', ignore=True) def predict( self, diff --git a/scripts/xyz_grid.py b/scripts/xyz_grid.py index ea0494bc7..a7ff92532 100644 --- a/scripts/xyz_grid.py +++ b/scripts/xyz_grid.py @@ -554,6 +554,7 @@ class Script(scripts.Script): processing.fix_seed(p) if not shared.opts.return_grid: p.batch_size = 1 + def process_axis(opt, vals, vals_dropdown): if opt.label == 'Nothing': return [0] diff --git a/wiki b/wiki index f17d12033..709796f97 160000 --- a/wiki +++ b/wiki @@ -1 +1 @@ -Subproject commit f17d12033e99505865763e32ce9a87bc7b422043 +Subproject commit 709796f9753081ebe74d723118b2482bf633fae5