diff --git a/CHANGELOG.md b/CHANGELOG.md index acd7fa139..ad851ae10 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,6 +2,17 @@ ## Update for 2026-09-04 +### Highlights for 2026-09-04 + +All-about-optimizations: +- improved LoRA performance and quality, especially with quantized models +- newly structured attention mechanisms +- modular pipelines with new guidance +- support for different caching stacks +- compute updates across the board + +### Details for 2026-09-04 + - **LoRA** - *TODO*: see [LoRA docs](https://vladmandic.github.io/sdnext-docs/LoRA) for all of the improvements and usage instructions *note*: lora now has its own settings section in *settings -> lora* @@ -59,6 +70,8 @@ - xyz grid: apply bool values - vdm scheduler: fix steps, thanks @zjn20030811 - openvino: optimize recompile checks and lora loading + - log: ansi color handling + - compile: keep model compiled state ## Update for 2026-08-26 diff --git a/modules/history.py b/modules/history.py index f98fea1b2..797c5a3b2 100644 --- a/modules/history.py +++ b/modules/history.py @@ -36,7 +36,7 @@ class Item: def __str__(self): if self.latent is not None: - return f'Item(ts="{self.name}" ops={self.ops} latent={self.latent.shape} size={self.size})' + return f'Item(ts="{self.name}" ops={self.ops} latent={list(self.latent.shape)} size={self.size})' elif self.images is not None: return f'Item(ts="{self.name}" ops={self.ops} images={len(self.images) if isinstance(self.images, list) else self.images})' else: diff --git a/modules/intel/openvino/__init__.py b/modules/intel/openvino/__init__.py index 85f46e78b..f779743ee 100644 --- a/modules/intel/openvino/__init__.py +++ b/modules/intel/openvino/__init__.py @@ -251,14 +251,14 @@ def openvino_compile(gm: GraphModule, *example_inputs, model_hash_str: str | Non hints = {} if shared.opts.openvino_accuracy == "performance": - hints[ov_hints.execution_mode] = ov_hints.ExecutionMode.PERFORMANCE + hints[ov_hints.execution_mode] = ov_hints.ExecutionMode.PERFORMANCE # pylint: disable=c-extension-no-member elif shared.opts.openvino_accuracy == "accuracy": - hints[ov_hints.execution_mode] = ov_hints.ExecutionMode.ACCURACY + hints[ov_hints.execution_mode] = ov_hints.ExecutionMode.ACCURACY # pylint: disable=c-extension-no-member if model_hash_str is not None: hints['CACHE_DIR'] = shared.opts.openvino_cache_path + '/blob' core.set_property(hints) - log.debug(f'OpenVINO compile: device={device} backend={shared.opts.cuda_compile_backend} hints={hints} file="{file_name}"') + log.debug(f'OpenVINO compile cache: device={device} backend={shared.opts.cuda_compile_backend} accuracy={shared.opts.openvino_accuracy} hints={hints} hash={model_hash_str} file="{file_name}"') compiled_model = core.compile_model(om, device) return compiled_model @@ -274,9 +274,9 @@ def openvino_compile_cached_model(cached_model_path, *example_inputs): hints = {'CACHE_DIR': shared.opts.openvino_cache_path + '/blob'} if shared.opts.openvino_accuracy == "performance": - hints[ov_hints.execution_mode] = ov_hints.ExecutionMode.PERFORMANCE + hints[ov_hints.execution_mode] = ov_hints.ExecutionMode.PERFORMANCE # pylint: disable=c-extension-no-member elif shared.opts.openvino_accuracy == "accuracy": - hints[ov_hints.execution_mode] = ov_hints.ExecutionMode.ACCURACY + hints[ov_hints.execution_mode] = ov_hints.ExecutionMode.ACCURACY # pylint: disable=c-extension-no-member core.set_property(hints) device = get_device() diff --git a/modules/logger.py b/modules/logger.py index a1826b571..91f9b2327 100644 --- a/modules/logger.py +++ b/modules/logger.py @@ -237,8 +237,15 @@ def setup_logging(debug=None, trace=None, filename=None): "inspect.value.border": "black", "traceback.border.syntax_error": "dark_red", "logging.level.info": "blue_violet", - "logging.level.debug": "purple4", + "logging.level.debug": "orchid", "logging.level.trace": "dark_blue", + "repr.attrib_name": "bright_cyan", + "repr.attrib_value": "orchid", + "repr.str": "sandy_brown", + "repr.number": "bright_green", + "repr.bool_true": "bright_green", + "repr.bool_false": "bright_red", + "repr.values": "bright_cyan", }) Padding.__rich_console__ = override_padding diff --git a/modules/lora/extra_networks_lora.py b/modules/lora/extra_networks_lora.py index 81015af01..e85771e3f 100644 --- a/modules/lora/extra_networks_lora.py +++ b/modules/lora/extra_networks_lora.py @@ -54,7 +54,7 @@ def prompt(p): all_tags = list(set(all_tags)) all_tags = [t for t in all_tags if t not in p.prompt] if len(all_tags) > 0: - log.debug(f"Network load: type=LoRA tags={all_tags} max={shared.opts.lora_apply_tags} apply") + log.debug(f"Network tags: type=LoRA tags={all_tags} max={shared.opts.lora_apply_tags} apply") all_tags = ', '.join(all_tags) p.extra_generation_params["LoRA tags"] = all_tags if '_tags_' in p.prompt: @@ -163,7 +163,7 @@ def unload_diffusers(): pass if hasattr(shared.sd_model, "unload_lora_weights"): try: - shared.sd_model.unload_lora_weights() # fails for non-CLIP models + shared.sd_model.unload_lora_weights() except Exception: pass @@ -248,7 +248,7 @@ class ExtraNetworkLora(extra_networks.ExtraNetwork): if hasattr(sd_model, 'disable_lora'): try: sd_model.disable_lora() - log.info('Network unload: type=LoRA mode=diffusers') + log.info('Network unload: type=LoRA method=diffusers disable') except Exception as e: log.error(f'Network unload: type=LoRA {e}') sd_models.set_diffuser_offload(shared.sd_model, op="model") @@ -275,13 +275,15 @@ class ExtraNetworkLora(extra_networks.ExtraNetwork): if len(l.loaded_networks) > 0 and (len(networks.applied_layers) > 0 or load_method=='diffusers' or load_method=='nunchaku') and step == 0: infotext(p) prompt(p) - if has_changed and len(include) == 0: # print only once + sd_model = shared.sd_model.pipe if hasattr(shared.sd_model, 'pipe') else shared.sd_model + if len(include) == 0: # print only once actual_method = 'native' if any(len(n.modules) > 0 for n in l.loaded_networks) else load_method stack = lora_stack.signature() if actual_method == 'native' else 'sum' # non-native paths always combine as sum - log.info(f'Network load: type=LoRA networks={[n.name for n in l.loaded_networks]} load={load_method}({load_reason}) method={actual_method} mode={networks.effective_mode()} stack={stack} te={te_multipliers} unet={unet_multipliers} time={l.timer.summary} reason="{reason}"') + log.info(f'Network status: type=LoRA networks={[n.name for n in l.loaded_networks]} method={actual_method}({load_reason}) mode={networks.effective_mode()} stack={stack} te={te_multipliers} unet={unet_multipliers} time={l.timer.summary} changed={has_changed} reason="{reason}"') - def deactivate(self, p, force=False): + def deactivate(self, p, force=False): # pylint: disable=unused-argument if len(lora_diffusers.diffuser_loaded) > 0 and (shared.opts.lora_force_reload or force): + log.debug(f'Network unload: type=LoRA method=diffusers loaded={len(lora_diffusers.diffuser_loaded)} opts={shared.opts.lora_force_reload} force={force}') unload_diffusers() if force: networks.network_deactivate() diff --git a/modules/lora/lora_load.py b/modules/lora/lora_load.py index eb867b326..7b9a0f918 100644 --- a/modules/lora/lora_load.py +++ b/modules/lora/lora_load.py @@ -177,18 +177,26 @@ def maybe_recompile_model(names, te_multipliers): else: recompile_model = True shared.compiled_model_state.lora_model = [] + if l.debug: + log.debug(f'Model recompile check: task={sd_models.get_diffusers_task(shared.sd_model)} recompile={recompile_model} load={skip_lora_load}') if recompile_model: current_task = sd_models.get_diffusers_task(shared.sd_model) log.debug(f'Compile: task={current_task} force model reload') backup_cuda_compile = shared.opts.cuda_compile backup_scheduler = getattr(sd_model, "scheduler", None) + backup_loaded_loras = getattr(sd_model, "loaded_loras", None) # reload below replaces shared.sd_model with a new pipe object sd_models.unload_model_weights(op='model') shared.opts.cuda_compile = ['LoRA'] # if its empty, it will be overridden by set_openvino_overrides() to ['Model'] which is not what we want sd_models.reload_model_weights(op='model') shared.sd_model = sd_models.set_diffuser_pipe(shared.sd_model, current_task) shared.opts.cuda_compile = backup_cuda_compile + new_sd_model = getattr(shared.sd_model, "pipe", shared.sd_model) # scheduler/cache must be reapplied to the new object, not the discarded one if backup_scheduler is not None: - sd_model.scheduler = backup_scheduler + new_sd_model.scheduler = backup_scheduler + if backup_loaded_loras is not None: + new_sd_model.loaded_loras = backup_loaded_loras + from modules import processing_diffusers # pylint: disable=import-outside-toplevel + processing_diffusers.orig_pipeline = shared.sd_model # otherwise process_diffusers() restores the pre-recompile pipeline once generation ends return recompile_model, skip_lora_load @@ -334,7 +342,7 @@ def network_load(names, te_multipliers=None, unet_multipliers=None, dyn_dims=Non try: if shared.opts.lora_fuse_diffusers and not lora_overrides.disable_fuse(): sd_model.fuse_lora(adapter_names=lora_diffusers.diffuser_loaded, lora_scale=1.0, fuse_unet=True, fuse_text_encoder=True) # diffusers with fuse uses fixed scale since later apply does the scaling - sd_model.unload_lora_weights() + # sd_model.unload_lora_weights() # optionally unload fused lora as we dont need it, but it may cause issues with some models l.timer.activate += time.time() - t1 except Exception as e: log.error(f'Network load: type=LoRA action=fuse {str(e)}') diff --git a/modules/processing_diffusers.py b/modules/processing_diffusers.py index e46aa785c..fcc33037a 100644 --- a/modules/processing_diffusers.py +++ b/modules/processing_diffusers.py @@ -79,7 +79,7 @@ def process_pre(p: processing.StableDiffusionProcessing, phase: str | None = Non modular_guiders.set_guider(p, phase) else: try: - log.info(f'Processing modifiers: phase={phase} apply') + log.info(f'Processing: modifiers=apply phase={phase}') from modules import ipadapter, sd_hijack_freeu, para_attention, teacache, hidiffusion, ras, pag, cfgzero, transformer_cache, token_merge, linfusion, cachedit # apply-with-unapply # sd_hijack_compile.install() @@ -112,7 +112,7 @@ def process_post(p: processing.StableDiffusionProcessing): else: try: from modules import ipadapter, hidiffusion, ras, pag, cfgzero, token_merge, linfusion, cachedit - log.info('Processing modifiers: unapply') + log.info('Processing: modifiers=unapply') sd_models_compile.check_deepcache(enable=False) ipadapter.unapply(shared.sd_model, unload=getattr(p, 'ip_adapter_unload', False)) token_merge.remove_token_merging(shared.sd_model) diff --git a/modules/processing_vae.py b/modules/processing_vae.py index 4b36e4788..34f13c405 100644 --- a/modules/processing_vae.py +++ b/modules/processing_vae.py @@ -396,7 +396,7 @@ def reprocess(gallery): latent, index = shared.history.selected if latent is None or gallery is None: return None - log.info(f'Reprocessing: latent={latent.shape}') + log.info(f'Reprocessing: latent={list(latent.shape)}') reprocessed = vae_decode(latent, shared.sd_model, output_type='pil') outputs = [] for i0, i1 in zip(gallery, reprocessed, strict=False): diff --git a/modules/sd_hijack_modular.py b/modules/sd_hijack_modular.py index cba1cf993..4f4b7ed72 100644 --- a/modules/sd_hijack_modular.py +++ b/modules/sd_hijack_modular.py @@ -27,7 +27,7 @@ def modular_step(components: diffusers.modular_pipelines.ModularPipeline, state: shared.state.current_latent = state.latents lora_stack.on_step(shared.state.sampling_step) if debug: - log.trace(f'Modular step: step={shared.state.sampling_step} latent={state.latents.shape}') + log.trace(f'Modular step: step={shared.state.sampling_step} latent={list(state.latents.shape)}') if shared.state.interrupted or shared.state.skipped: raise AssertionError('Interrupted...') if shared.state.paused: diff --git a/modules/sd_hijack_triton.py b/modules/sd_hijack_triton.py index 94aeba161..2fad5ba3c 100644 --- a/modules/sd_hijack_triton.py +++ b/modules/sd_hijack_triton.py @@ -1,3 +1,4 @@ +import os import time import math from typing import Any @@ -171,10 +172,8 @@ def run_hook(orig): if hasattr(arg, 'dtype'): key += (str(arg.dtype),) needs_benchmark = len(self.configs) > 1 and key not in self.cache - import os skip_autotune = os.environ.get('SD_SKIP_AUTOTUNE', None) is not None if needs_benchmark and skip_autotune: - log.trace('Autotune: skip') self.cache[key] = self.configs[0] # pre-seed the cache so orig() takes its cache-hit path and skips the sweep needs_benchmark = False if needs_benchmark: diff --git a/modules/sd_models_compile.py b/modules/sd_models_compile.py index 287006a61..594b77449 100644 --- a/modules/sd_models_compile.py +++ b/modules/sd_models_compile.py @@ -357,21 +357,28 @@ def compile_diffusers(sd_model, apply_to_components=True, op="Model"): def set_openvino_overrides(): + overrides = [] if "Model" not in shared.opts.cuda_compile: if 'LoRA' in shared.opts.cuda_compile: shared.opts.cuda_compile = [] else: shared.opts.cuda_compile.append("Model") - log.warning("OpenVINO: compile=Model setting override") + overrides.append("compile=model") if shared.opts.cuda_compile_backend != shared.opts.openvino_compile_backend: shared.opts.cuda_compile_backend = shared.opts.openvino_compile_backend - log.warning(f"OpenVINO: backend={shared.opts.openvino_compile_backend} setting override") + overrides.append(f"backend={shared.opts.openvino_compile_backend}") if shared.opts.diffusers_offload_mode != "none": shared.opts.diffusers_offload_mode = "none" - log.warning("OpenVINO: offload=None setting override") + overrides.append("offload=none") if not shared.opts.lora_force_diffusers: shared.opts.lora_force_diffusers = True - log.warning("OpenVINO: lora=diffusers setting override") + overrides.append("lora=diffusers") + if not shared.opts.lora_fuse_diffusers: + shared.opts.lora_fuse_native = False + shared.opts.lora_fuse_diffusers = True + overrides.append("lora=fuse") + if len(overrides) > 0: + log.warning(f"OpenVINO setting override: {overrides}") def openvino_recompile_model(p, hires=False, refiner=False): # recompile if a parameter changes # pylint: disable=unused-argument diff --git a/modules/shared_state.py b/modules/shared_state.py index 4c0a31e07..0eaa6c68b 100644 --- a/modules/shared_state.py +++ b/modules/shared_state.py @@ -63,7 +63,7 @@ class State: status += 'oom ' if self.oom else '' status += 'api ' if self.api else '' fn = f'{sys._getframe(3).f_code.co_name}:{sys._getframe(2).f_code.co_name}' # pylint: disable=protected-access - return f'State: ts={self.job_timestamp} job={self.job} jobs={self.job_no+1}/{self.job_count}/{self.total_jobs} step={self.sampling_step}/{self.sampling_steps} preview={self.preview_job}/{self.id_live_preview}/{self.current_image_sampling_step} status="{status.strip()}" image={self.current_image} latent={self.current_latent.shape if self.current_latent is not None else None} fn={fn}' + return f'State: ts={self.job_timestamp} job={self.job} jobs={self.job_no+1}/{self.job_count}/{self.total_jobs} step={self.sampling_step}/{self.sampling_steps} preview={self.preview_job}/{self.id_live_preview}/{self.current_image_sampling_step} status="{status.strip()}" image={self.current_image} latent={list(self.current_latent.shape) if self.current_latent is not None else None} fn={fn}' @property def sampling_step(self): diff --git a/modules/vae/sd_vae_remote.py b/modules/vae/sd_vae_remote.py index 835b37f69..ae98ac081 100644 --- a/modules/vae/sd_vae_remote.py +++ b/modules/vae/sd_vae_remote.py @@ -180,5 +180,5 @@ def remote_encode(images: list[Image.Image], model_type: str | None = None): else: return images t1 = time.time() - log.debug(f'Encode: type="remote" model={model_type} mode={shared.opts.remote_vae_type} image={images} latent={tensors.shape} time={t1-t0:.3f}s') + log.debug(f'Encode: type="remote" model={model_type} mode={shared.opts.remote_vae_type} image={images} latent={list(tensors.shape)} time={t1-t0:.3f}s') return tensors diff --git a/scripts/pulid/pulid_sdxl.py b/scripts/pulid/pulid_sdxl.py index a36283be3..6e3561bfb 100644 --- a/scripts/pulid/pulid_sdxl.py +++ b/scripts/pulid/pulid_sdxl.py @@ -327,7 +327,7 @@ class StableDiffusionXLPuLIDPipeline: return_image_latents=False, ) latents = latents[0] - debug(f'PulID noise: op=inpaint latent={latents.shape} image={image} mask={mask_image} dtype={latents.dtype}') + debug(f'PulID noise: op=inpaint latent={list(latents.shape)} image={image} mask={mask_image} dtype={latents.dtype}') else: # img2img latents = self.pipe.prepare_latents(image, None, # timestep (not needed) @@ -338,10 +338,10 @@ class StableDiffusionXLPuLIDPipeline: None, # generator False, # add_noise ) - debug(f'PulID noise: op=img2img latent={latents.shape} image={image} dtype={latents.dtype}') + debug(f'PulID noise: op=img2img latent={list(latents.shape)} image={image} dtype={latents.dtype}') else: latents = torch.zeros_like(noise) - debug(f'PulID noise: op=txt2img latent={latents.shape} dtype={latents.dtype}') + debug(f'PulID noise: op=txt2img latent={list(latents.shape)} dtype={latents.dtype}') return latents, noise def __call__( @@ -379,7 +379,7 @@ class StableDiffusionXLPuLIDPipeline: # latents latent, noise = self.init_latent(seed, size, image, mask_image, strength, width, height) noisy_latent = latent + noise * sigmas[0].to(noise) - debug(f'PulID noisy: latent={noisy_latent.shape} dtype={noisy_latent.dtype}') + debug(f'PulID noisy: latent={list(noisy_latent.shape)} dtype={noisy_latent.dtype}') ( prompt_embeds, @@ -425,7 +425,7 @@ class StableDiffusionXLPuLIDPipeline: # process output latents = latents.to(dtype=self.pipe.vae.dtype, device=self.device) - debug(f'PulID output: latent={latents.shape} dtype={latents.dtype}') + debug(f'PulID output: latent={list(latents.shape)} dtype={latents.dtype}') if output_type == 'latent': images = self.pipe.image_processor.postprocess(latents, output_type='latent') elif output_type == 'np':