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..9a322b677 100644 --- a/installer.py +++ b/installer.py @@ -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/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 29c7b7cb2..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) } @@ -294,8 +295,8 @@ div.controlnet_main_options { display: grid; grid-template-columns: 1fr 1fr; gri /* 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; } @@ -311,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/esrgan_model.py b/modules/esrgan_model.py index cbede232f..a9682bceb 100644 --- a/modules/esrgan_model.py +++ b/modules/esrgan_model.py @@ -220,7 +220,7 @@ def esrgan_upscale(model, img): with Progress(TextColumn('[cyan]{task.description}'), BarColumn(), TaskProgressColumn(), TimeRemainingColumn(), TimeElapsedColumn(), console=console) as progress: total = 0 - for y, h, row in grid.tiles: + 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: diff --git a/modules/images.py b/modules/images.py index 3a2e8ded0..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 = 0): + 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/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..9d8605d75 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 hasattr(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 hasattr(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 hasattr(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 hasattr(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_arch.py b/modules/realesrgan_model_arch.py index 8b991de6d..50f085255 100644 --- a/modules/realesrgan_model_arch.py +++ b/modules/realesrgan_model_arch.py @@ -155,7 +155,7 @@ class RealESRGANer(): # 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 + 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 @@ -315,7 +315,7 @@ class IOConsumer(threading.Thread): from basicsr.utils.registry import ARCH_REGISTRY from torch import nn as nn -from torch.nn import functional as F +from torch.nn import functional as F # noqa class SRVGGNetCompact(nn.Module): @@ -381,4 +381,3 @@ class SRVGGNetCompact(nn.Module): base = F.interpolate(x, scale_factor=self.upscale, mode='nearest') out += base return out - \ No newline at end of file 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 8dd49648d..d69f0ff3b 100644 --- a/modules/shared.py +++ b/modules/shared.py @@ -340,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}") @@ -350,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: @@ -376,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("