From a85009ad3609431c9abb75054fdbcd3533a64764 Mon Sep 17 00:00:00 2001 From: Vladimir Mandic Date: Thu, 14 May 2026 06:37:15 +0200 Subject: [PATCH 01/16] fix hidream prequant loader Signed-off-by: Vladimir Mandic --- pipelines/model_hidream.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/pipelines/model_hidream.py b/pipelines/model_hidream.py index 7d1fcd5e4..a0875674d 100644 --- a/pipelines/model_hidream.py +++ b/pipelines/model_hidream.py @@ -51,6 +51,7 @@ def load_hidream_o1(checkpoint_info, diffusers_load_config=None): repo_id, cache_dir=shared.opts.hfcache_dir, trust_remote_code=True, + subfolder='transformer' if 'vladmandic' in repo_id.lower() else None, **load_args, **quant_args, ) @@ -59,6 +60,7 @@ def load_hidream_o1(checkpoint_info, diffusers_load_config=None): processor = transformers.AutoProcessor.from_pretrained( repo_id, + subfolder='processor' if 'vladmandic' in repo_id.lower() else None, cache_dir=shared.opts.hfcache_dir, trust_remote_code=True, ) From dedb130b2c15ef9d91f9593213ccffcb70d98be4 Mon Sep 17 00:00:00 2001 From: Vladimir Mandic Date: Thu, 14 May 2026 21:08:08 +0200 Subject: [PATCH 02/16] fix hidream-o1 loader Signed-off-by: Vladimir Mandic --- pipelines/model_hidream.py | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/pipelines/model_hidream.py b/pipelines/model_hidream.py index a0875674d..8301f4f0c 100644 --- a/pipelines/model_hidream.py +++ b/pipelines/model_hidream.py @@ -47,20 +47,25 @@ def load_hidream_o1(checkpoint_info, diffusers_load_config=None): o1_load_config = diffusers_load_config.copy() o1_load_config['trust_remote_code'] = True + path_args = {} + if 'vladmandic' in repo_id.lower(): + path_args['subfolder'] = 'transformer' transformer = HiDreamO1Qwen3VLTransformer.from_pretrained( repo_id, cache_dir=shared.opts.hfcache_dir, trust_remote_code=True, - subfolder='transformer' if 'vladmandic' in repo_id.lower() else None, + **path_args, **load_args, **quant_args, ) if shared.opts.diffusers_offload_mode != 'none' and transformer is not None: sd_models.move_model(transformer, devices.cpu) + if 'vladmandic' in repo_id.lower(): + path_args['subfolder'] = 'processor' processor = transformers.AutoProcessor.from_pretrained( repo_id, - subfolder='processor' if 'vladmandic' in repo_id.lower() else None, + **path_args, cache_dir=shared.opts.hfcache_dir, trust_remote_code=True, ) From a6b4614e25dff01ebfb87e780afa94cb9775276f Mon Sep 17 00:00:00 2001 From: QualiaRain <44004657+QualiaRain@users.noreply.github.com> Date: Sat, 13 Jun 2026 00:12:51 -0400 Subject: [PATCH 03/16] fix(control): advance video frames via cap, not the always-None video var The VideoCapture is stored in cap, but the per-frame read and skip/yield guards referenced video, which is set to None at function entry and never reassigned. As a result only the first frame of a video input was processed. Co-Authored-By: Claude --- modules/control/run.py | 11 +++++------ 1 file changed, 5 insertions(+), 6 deletions(-) diff --git a/modules/control/run.py b/modules/control/run.py index fa021b92e..086c7eac5 100644 --- a/modules/control/run.py +++ b/modules/control/run.py @@ -644,7 +644,6 @@ def control_run(state: str = '', # pylint: disable=keyword-arg-before-vararg debug_log(f'Control pipeline: class={pipe.__class__.__name__} args={vars(p)}') status = True frame = None - video = None output_filename = None index = 0 frames = 0 @@ -697,7 +696,7 @@ def control_run(state: str = '', # pylint: disable=keyword-arg-before-vararg inputs = [Image.fromarray(frame)] # cv2 to pil for i, input_image in enumerate(inputs): # loop per-input, but with early-break if pipe is None: # pipe may have been reset externally - if video is None: + if cap is None: break # non-video: pipeline was consumed, no need to re-process remaining inputs pipe = set_pipe(p, has_models, unit_type, selected_models, active_model, active_strength, active_units, control_conditioning, control_guidance_start, control_guidance_end, inits) debug_log(f'Control pipeline reinit: class={pipe.__class__.__name__}') @@ -744,7 +743,7 @@ def control_run(state: str = '', # pylint: disable=keyword-arg-before-vararg init_image = inits[i % len(inits)] else: init_image = None - if video is not None and index % (video_skip_frames + 1) != 0: + if cap is not None and index % (video_skip_frames + 1) != 0: index += 1 continue index += 1 @@ -806,13 +805,13 @@ def control_run(state: str = '', # pylint: disable=keyword-arg-before-vararg if processed_image is not None and isinstance(processed_image, Image.Image): output_images.append(processed_image) - if is_generator and frame is not None and video is not None: + if is_generator and frame is not None and cap is not None: image_txt = f'{output_image.width}x{output_image.height}' if output_image is not None else 'None' msg = f'Control output | {index} of {frames} skip {video_skip_frames} | Frame {image_txt}' yield (output_image, blended_image, msg) # result is control_output, proces_output - if video is not None and frame is not None: - status, frame = video.read() + if cap is not None and frame is not None: + status, frame = cap.read() if status: frame = cv2.cvtColor(frame, cv2.COLOR_BGR2RGB) debug_log(f'Control: video frame={index} frames={frames} status={status} skip={index % (video_skip_frames + 1)} progress={index/frames:.2f}') From ca730fa3e608b61309cd9493b5b846454fe5aa63 Mon Sep 17 00:00:00 2001 From: QualiaRain <44004657+QualiaRain@users.noreply.github.com> Date: Sat, 13 Jun 2026 00:17:17 -0400 Subject: [PATCH 04/16] fix(lora-extract): stop overwriting the module selection list; fix dead no-LoRA guard make_lora reassigned the 'modules' selection arg to a named_modules() generator, so the subsequent 'te'/'unet' in modules checks tested an exhausted generator and silently skipped TE2 + UNet extraction. Also 'loaded_lora() == ""' never matched a loaded model (returns a list), so the no-LoRA-detected guard never fired. Co-Authored-By: Claude --- modules/lora/lora_extract.py | 11 ++++------- 1 file changed, 4 insertions(+), 7 deletions(-) diff --git a/modules/lora/lora_extract.py b/modules/lora/lora_extract.py index 1eb8a69bd..22c6019fc 100644 --- a/modules/lora/lora_extract.py +++ b/modules/lora/lora_extract.py @@ -122,7 +122,7 @@ def make_lora(fn, maxrank, auto_rank, rank_ratio, modules, overwrite): log.warning(msg) yield msg return - if loaded_lora() == "": + if not loaded_lora(): msg = "LoRA extract: no LoRA detected" log.warning(msg) yield msg @@ -141,8 +141,7 @@ def make_lora(fn, maxrank, auto_rank, rank_ratio, modules, overwrite): with rp.Progress(rp.TextColumn('[cyan]LoRA extract'), rp.BarColumn(), rp.TaskProgressColumn(), rp.TimeRemainingColumn(), rp.TimeElapsedColumn(), rp.TextColumn('[cyan]{task.description}'), console=console) as progress: if 'te' in modules and getattr(shared.sd_model, 'text_encoder', None) is not None: - modules = shared.sd_model.text_encoder.named_modules() - task = progress.add_task(description="te1 decompose", total=len(list(modules))) + task = progress.add_task(description="te1 decompose", total=len(list(shared.sd_model.text_encoder.named_modules()))) for name, module in shared.sd_model.text_encoder.named_modules(): progress.update(task, advance=1) weights_backup = getattr(module, "network_weights_backup", None) @@ -157,8 +156,7 @@ def make_lora(fn, maxrank, auto_rank, rank_ratio, modules, overwrite): t1 = time.time() if 'te' in modules and getattr(shared.sd_model, 'text_encoder_2', None) is not None: - modules = shared.sd_model.text_encoder_2.named_modules() - task = progress.add_task(description="te2 decompose", total=len(list(modules))) + task = progress.add_task(description="te2 decompose", total=len(list(shared.sd_model.text_encoder_2.named_modules()))) for name, module in shared.sd_model.text_encoder_2.named_modules(): progress.update(task, advance=1) weights_backup = getattr(module, "network_weights_backup", None) @@ -172,8 +170,7 @@ def make_lora(fn, maxrank, auto_rank, rank_ratio, modules, overwrite): t2 = time.time() if 'unet' in modules and getattr(shared.sd_model, 'unet', None) is not None: - modules = shared.sd_model.unet.named_modules() - task = progress.add_task(description="unet decompose", total=len(list(modules))) + task = progress.add_task(description="unet decompose", total=len(list(shared.sd_model.unet.named_modules()))) for name, module in shared.sd_model.unet.named_modules(): progress.update(task, advance=1) weights_backup = getattr(module, "network_weights_backup", None) From 711d6018bfca61bedb28f4db0df4537ae33056c1 Mon Sep 17 00:00:00 2001 From: QualiaRain <44004657+QualiaRain@users.noreply.github.com> Date: Sat, 13 Jun 2026 00:21:25 -0400 Subject: [PATCH 05/16] fix(api): IP-adapter mask accumulation, null params, colon-in-password, raw allowed path generate.py: p.ip_adapter_masks was reinitialized inside the per-adapter loop, discarding all but the last adapter's masks; move it beside the other accumulators. process.py: req.params is dict|None, so a null params body crashed .items() in post_preprocess/post_mask. api.py: split(':') without maxsplit broke auth/auth-file entries whose password contains a colon. gallery.py: allowed_paths stored quote(path) but the membership check and path guards use the raw path, causing duplicate accumulation and an ineffective whitelist; also drop the unused FastAPI import (pylint W0611 surfaced when this file is linted). Co-Authored-By: Claude --- modules/api/api.py | 4 ++-- modules/api/gallery.py | 3 +-- modules/api/generate.py | 2 +- modules/api/process.py | 4 ++-- 4 files changed, 6 insertions(+), 7 deletions(-) diff --git a/modules/api/api.py b/modules/api/api.py index bb7d78bd6..86c67ae84 100644 --- a/modules/api/api.py +++ b/modules/api/api.py @@ -17,12 +17,12 @@ class Api: self.credentials = {} if shared.cmd_opts.auth: for auth in shared.cmd_opts.auth.split(","): - user, password = auth.split(":") + user, password = auth.split(":", 1) self.credentials[user.replace('"', '').strip()] = password.replace('"', '').strip() if shared.cmd_opts.auth_file: with open(shared.cmd_opts.auth_file, encoding="utf8") as file: for line in file.readlines(): - user, password = line.split(":") + user, password = line.split(":", 1) self.credentials[user.replace('"', '').strip()] = password.replace('"', '').strip() self.router = APIRouter() if shared.cmd_opts.docs: diff --git a/modules/api/gallery.py b/modules/api/gallery.py index b585ad896..5c38ea8a3 100644 --- a/modules/api/gallery.py +++ b/modules/api/gallery.py @@ -3,7 +3,6 @@ import os import time import base64 from urllib.parse import quote, unquote -from fastapi import FastAPI from fastapi.responses import JSONResponse from starlette.websockets import WebSocket, WebSocketState from pydantic import BaseModel, Field # pylint: disable=no-name-in-module @@ -173,7 +172,7 @@ def register_api(api): # register api unique_folders.append(f) if shared.demo is not None and path not in shared.demo.allowed_paths: debug(f'Browser folders allow: {path}') - shared.demo.allowed_paths.append(quote(path)) + shared.demo.allowed_paths.append(path) debug(f'Browser folders: {unique_folders}') return JSONResponse(content=unique_folders) diff --git a/modules/api/generate.py b/modules/api/generate.py index 678292922..783a78523 100644 --- a/modules/api/generate.py +++ b/modules/api/generate.py @@ -70,6 +70,7 @@ class APIGenerate: p.ip_adapter_starts = [] p.ip_adapter_ends = [] p.ip_adapter_images = [] + p.ip_adapter_masks = [] for ipadapter in request.ip_adapter: if not ipadapter.images or len(ipadapter.images) == 0: continue @@ -79,7 +80,6 @@ class APIGenerate: p.ip_adapter_starts.append(ipadapter.start) p.ip_adapter_ends.append(ipadapter.end) p.ip_adapter_images.append([helpers.decode_base64_to_image(x) for x in ipadapter.images]) - p.ip_adapter_masks = [] if ipadapter.masks: p.ip_adapter_masks.append([helpers.decode_base64_to_image(x) for x in ipadapter.masks]) del request.ip_adapter diff --git a/modules/api/process.py b/modules/api/process.py index 9c3925a51..1abf3950b 100644 --- a/modules/api/process.py +++ b/modules/api/process.py @@ -76,7 +76,7 @@ class APIProcess: if processor is None or processor.processor_id != req.model: with self.queue_lock: processor = processors.Processor(req.model) - for k, v in req.params.items(): + for k, v in (req.params or {}).items(): if k not in processors.config[processor.processor_id]['params']: return JSONResponse(status_code=400, content={"error": f"Processor invalid parameter: id={req.model} {k}={v}"}) jobid = shared.state.begin('API-PRE', api=True) @@ -102,7 +102,7 @@ class APIProcess: return JSONResponse(status_code=400, content={"error": f"Mask type not found: id={req.type}"}) image = decode_base64_to_image(req.image) mask = decode_base64_to_image(req.mask) if req.mask else None - for k, v in req.params.items(): + for k, v in (req.params or {}).items(): if not hasattr(masking.opts, k): return JSONResponse(status_code=400, content={"error": f"Mask invalid parameter: {k}={v}"}) else: From a68da643f139d01b1b76c0de0bcfb4a011253959 Mon Sep 17 00:00:00 2001 From: QualiaRain <44004657+QualiaRain@users.noreply.github.com> Date: Sat, 13 Jun 2026 00:22:24 -0400 Subject: [PATCH 06/16] fix(runtime): capture bias dtype cast, coerce env seq-len to int, init pipeline before use sd_hijack_accelerate.py: bias.to(weight.dtype) discarded its result (Tensor.to is not in-place), so the conv still received the mismatched bias. sd_hijack_te.py: os.environ.get returns a str when MAX_SEQUENCE_LENGTH is set, so max(int, str) raised TypeError (uncaught, before the try); coerce with int(). sd_detect.py: pipeline was referenced in 'if callable(pipeline)' but only assigned when cls is not None, raising UnboundLocalError for a model_index.json without _class_name. Co-Authored-By: Claude --- modules/sd_detect.py | 1 + modules/sd_hijack_accelerate.py | 2 +- modules/sd_hijack_te.py | 2 +- 3 files changed, 3 insertions(+), 2 deletions(-) diff --git a/modules/sd_detect.py b/modules/sd_detect.py index f87d7ae25..045d46a28 100644 --- a/modules/sd_detect.py +++ b/modules/sd_detect.py @@ -187,6 +187,7 @@ def guess_by_diffusers(fn, current_guess): cls = index.get('_class_name', None) if isinstance(cls, list): cls = cls[-1] + pipeline = None if cls is not None: pipeline = getattr(diffusers, cls, None) if pipeline is None: diff --git a/modules/sd_hijack_accelerate.py b/modules/sd_hijack_accelerate.py index 0467c536b..45e892d1e 100644 --- a/modules/sd_hijack_accelerate.py +++ b/modules/sd_hijack_accelerate.py @@ -84,7 +84,7 @@ def torch_conv_forward(self, input, weight, bias): # pylint: disable=redefined-b if self.padding_mode != 'zeros': return F.conv2d(F.pad(input, self._reversed_padding_repeated_twice, mode=self.padding_mode), weight, bias, self.stride, _pair(0), self.dilation, self.groups) # pylint: disable=protected-access if weight.dtype != bias.dtype: - bias.to(weight.dtype) + bias = bias.to(weight.dtype) return F.conv2d(input, weight, bias, self.stride, self.padding, self.dilation, self.groups) def hijack_torch_conv(): diff --git a/modules/sd_hijack_te.py b/modules/sd_hijack_te.py index e947ec4f2..c1163eabb 100644 --- a/modules/sd_hijack_te.py +++ b/modules/sd_hijack_te.py @@ -8,7 +8,7 @@ def hijack_encode_prompt(*args, **kwargs): jobid = shared.state.begin('TE Encode') t0 = time.time() if 'max_sequence_length' in kwargs and kwargs['max_sequence_length'] is not None: - kwargs['max_sequence_length'] = max(kwargs['max_sequence_length'], os.environ.get('MAX_SEQUENCE_LENGTH', 256)) + kwargs['max_sequence_length'] = max(kwargs['max_sequence_length'], int(os.environ.get('MAX_SEQUENCE_LENGTH', 256))) res = None try: args_copy = list(args) From bbbb4508843cc24cd361edb7fdb84ee4c81c66a2 Mon Sep 17 00:00:00 2001 From: QualiaRain <44004657+QualiaRain@users.noreply.github.com> Date: Mon, 15 Jun 2026 00:04:41 -0400 Subject: [PATCH 07/16] fix(control-units): list zimage controlnets under 'all'; fix LLLite output-block fall-through api_list_models omitted the 'or model_type == all' clause for zimage, so ZImage ControlNets never appeared in the all listing - added it to match every other family. lite_model used pass for the unimplemented root==output branch, which fell through to b = getattr(b, attn_name) with b unbound or stale from a prior iteration (UnboundLocalError or wrong-block patch); use continue to skip it. Co-Authored-By: Claude --- modules/control/units/controlnet.py | 2 +- modules/control/units/lite_model.py | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/modules/control/units/controlnet.py b/modules/control/units/controlnet.py index 8efbce5a5..96175a33b 100644 --- a/modules/control/units/controlnet.py +++ b/modules/control/units/controlnet.py @@ -179,7 +179,7 @@ def api_list_models(model_type: str | None = None): model_list += list(predefined_qwen) if model_type == 'hunyuandit' or model_type == 'all': model_list += list(predefined_hunyuandit) - if model_type == 'zimage': + if model_type == 'zimage' or model_type == 'all': model_list += list(predefined_zimage) model_list += sorted(find_models()) return model_list diff --git a/modules/control/units/lite_model.py b/modules/control/units/lite_model.py index ef6d444f9..3095d7cfe 100644 --- a/modules/control/units/lite_model.py +++ b/modules/control/units/lite_model.py @@ -171,7 +171,7 @@ class ControlNetLLLite(torch.nn.Module): # pylint: disable=abstract-method mapped_block, mapped_number = map_down_lllite_to_unet[int(block)] b = model.down_blocks[mapped_block].attentions[int(mapped_number)].transformer_blocks[int(block_number)] elif root == 'output': - pass # not implemented + continue # not implemented else: b = model.mid_block.attentions[0].transformer_blocks[int(block_number)] b = getattr(b, attn_name, None) From 14a8eebbb8fe559f4231a5b9ae4c7d9d7018b35c Mon Sep 17 00:00:00 2001 From: QualiaRain <44004657+QualiaRain@users.noreply.github.com> Date: Sat, 13 Jun 2026 00:11:25 -0400 Subject: [PATCH 08/16] fix(hypertile): correct width/height mapping in hires set_resolution hr_upscale_to_x is the width dimension and hr_upscale_to_y the height (see processing_class.py); set_resolution assigned them transposed, producing swapped HyperTile geometry on non-square hires passes. Co-Authored-By: Claude --- modules/sd_hijack_hypertile.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/modules/sd_hijack_hypertile.py b/modules/sd_hijack_hypertile.py index b072df7ad..eead36266 100644 --- a/modules/sd_hijack_hypertile.py +++ b/modules/sd_hijack_hypertile.py @@ -259,8 +259,8 @@ def set_resolution(p, hr=False): if hr: x = getattr(p, 'hr_upscale_to_x', 0) y = getattr(p, 'hr_upscale_to_y', 0) - width = y if y > 0 else p.width - height = x if x > 0 else p.height + width = x if x > 0 else p.width + height = y if y > 0 else p.height else: width = p.width height = p.height From 42a4b82c8e3217982ab68924552da1c067596a7f Mon Sep 17 00:00:00 2001 From: QualiaRain <44004657+QualiaRain@users.noreply.github.com> Date: Sat, 13 Jun 2026 00:19:56 -0400 Subject: [PATCH 09/16] fix(lora): guard self.shape for modules without a weight attribute NetworkModule.__init__ set self.shape only inside 'if hasattr(sd_module, weight)' but then used len(self.shape) unconditionally, raising AttributeError when a LoRA targets a weightless module. Default shape to None and skip the dora_norm_dims computation when absent. Co-Authored-By: Claude --- modules/lora/network.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/modules/lora/network.py b/modules/lora/network.py index 576f5831c..5926cd4fa 100644 --- a/modules/lora/network.py +++ b/modules/lora/network.py @@ -166,6 +166,7 @@ class NetworkModule: self.network_key = weights.network_key self.sd_key = weights.sd_key self.sd_module = weights.sd_module + self.shape = None if hasattr(self.sd_module, 'weight'): if hasattr(self.sd_module, "sdnq_dequantizer"): self.shape = self.sd_module.sdnq_dequantizer.original_shape @@ -176,7 +177,7 @@ class NetworkModule: self.alpha = weights.w["alpha"].item() if "alpha" in weights.w else None self.scale = weights.w["scale"].item() if "scale" in weights.w else None self.dora_scale = weights.w.get("dora_scale", None) - self.dora_norm_dims = len(self.shape) - 1 + self.dora_norm_dims = (len(self.shape) - 1) if self.shape is not None else None def multiplier(self): unet_multiplier = 3 * [self.network.unet_multiplier] if not isinstance(self.network.unet_multiplier, list) else self.network.unet_multiplier From 8f5759ac018fce4c7eeea446256f832bffd5263e Mon Sep 17 00:00:00 2001 From: Vladimir Mandic Date: Tue, 16 Jun 2026 08:47:17 +0200 Subject: [PATCH 10/16] fix video prompt Signed-off-by: Vladimir Mandic --- modules/openai/test.py | 7 +++++++ modules/video_models/video_prompt.py | 7 +++++-- modules/video_models/video_run.py | 3 +-- modules/video_models/video_utils.py | 9 --------- 4 files changed, 13 insertions(+), 13 deletions(-) diff --git a/modules/openai/test.py b/modules/openai/test.py index 8c37104bb..34a3a06a8 100644 --- a/modules/openai/test.py +++ b/modules/openai/test.py @@ -20,7 +20,13 @@ model = AutoModelForCausalLM.from_pretrained( dtype=torch.bfloat16, trust_remote_code=True, attn_implementation="sdpa", + # attn_implementation="eager", + # attn_implementation="flash_attention_2", ) +model.config.use_flash_attention = True + +logger.log.info("OpenAI: eval model...") +model.eval() tokenizer = AutoTokenizer.from_pretrained( "Qwen/Qwen3-0.6B", trust_remote_code=True @@ -42,3 +48,4 @@ while True: except KeyboardInterrupt: server.stop() break + diff --git a/modules/video_models/video_prompt.py b/modules/video_models/video_prompt.py index bbb29c409..963813e9d 100644 --- a/modules/video_models/video_prompt.py +++ b/modules/video_models/video_prompt.py @@ -1,7 +1,7 @@ from modules import shared, extra_networks, ui_video_vlm -def prepare_prompt(p, init_image, prompt:str, vlm_enhance:bool, vlm_model:str, vlm_system_prompt:str): +def prepare_prompts(p, init_image, prompt:str, vlm_enhance:bool, vlm_model:str, vlm_system_prompt:str): p.prompt = shared.prompt_styles.apply_styles_to_prompt(p.prompt, p.styles) p.negative_prompt = shared.prompt_styles.apply_negative_styles_to_prompt(p.negative_prompt, p.styles) shared.prompt_styles.apply_styles_to_extra(p) @@ -18,4 +18,7 @@ def prepare_prompt(p, init_image, prompt:str, vlm_enhance:bool, vlm_model:str, v ) if new_prompt is not None and len(new_prompt) > 0: prompt = new_prompt - return prompt + + p.styles = [] + p.task_args['prompt'] = p.prompt + p.task_args['negative_prompt'] = p.negative_prompt diff --git a/modules/video_models/video_run.py b/modules/video_models/video_run.py index b23fd98e3..569220a45 100644 --- a/modules/video_models/video_run.py +++ b/modules/video_models/video_run.py @@ -95,12 +95,11 @@ def generate(*args, **kwargs): shared.sd_model = sd_models.apply_balanced_offload(shared.sd_model) devices.torch_gc(force=True, reason='video') - prompt = video_prompt.prepare_prompt(p, init_image, prompt, vlm_enhance, vlm_model, vlm_system_prompt) # set args + video_prompt.prepare_prompts(p, init_image, prompt, vlm_enhance, vlm_model, vlm_system_prompt) processing.fix_seed(p) video_vae.set_vae_params(p) - video_utils.set_prompt(p) p.task_args['num_inference_steps'] = p.steps p.task_args['width'] = p.width p.task_args['height'] = p.height diff --git a/modules/video_models/video_utils.py b/modules/video_models/video_utils.py index 054486648..18ae1249d 100644 --- a/modules/video_models/video_utils.py +++ b/modules/video_models/video_utils.py @@ -30,15 +30,6 @@ def check_av(): return av -def set_prompt(p): - p.prompt = shared.prompt_styles.apply_styles_to_prompt(p.prompt, p.styles) - p.negative_prompt = shared.prompt_styles.apply_negative_styles_to_prompt(p.negative_prompt, p.styles) - shared.prompt_styles.apply_styles_to_extra(p) - p.styles = [] - p.task_args['prompt'] = p.prompt - p.task_args['negative_prompt'] = p.negative_prompt - - def hijack_encode_image(*args, **kwargs): t0 = time.time() try: From 25262146fbe1213728a46b4d0ef7cf7b771023d0 Mon Sep 17 00:00:00 2001 From: Vladimir Mandic Date: Tue, 16 Jun 2026 10:16:13 +0200 Subject: [PATCH 11/16] lint fix Signed-off-by: Vladimir Mandic --- CHANGELOG.md | 19 ++++++++++++++++--- modules/openai/test.py | 1 - modules/ui_common.py | 2 ++ 3 files changed, 18 insertions(+), 4 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 5403ef860..4193eed8b 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,8 +1,8 @@ # Change Log for SD.Next -## Update for 2026-06-14 +## Update for 2026-06-16 -### Highlights for 2026-06-14 +### Highlights for 2026-06-16 *What's New?* - **Ideogram-4** released, Microsoft joins the game with **Lens** and **Anima** made it to release version @@ -16,9 +16,11 @@ And we have new [Home page](https://vladmandic.github.io/sdnext/) with heavily u Plus continued work on modernization of codebase: UI is now fully TypeScript based And we have a new modular LoRA loader, new native Transformers loader and improved 3rd party finetunes support! +*Note*: This is a major update due to sheer size of the changes: over 400 commits! + [ReadMe](https://github.com/vladmandic/automatic/blob/master/README.md) | [ChangeLog](https://github.com/vladmandic/automatic/blob/master/CHANGELOG.md) | [Docs](https://vladmandic.github.io/sdnext-docs/) | [WiKi](https://github.com/vladmandic/automatic/wiki) | [Discord](https://discord.com/invite/sd-next-federal-batch-inspectors-1101998836328697867) | [Sponsor](https://github.com/sponsors/vladmandic) -### Details for 2026-06-14 +### Details for 2026-06-16 - **Models** - [CircleStone Anima 1.0](https://huggingface.co/circlestone-labs/Anima) in *Base* and *Turbo* (distilled) variants @@ -150,8 +152,19 @@ And we have a new modular LoRA loader, new native Transformers loader and improv - `samplers` ui sigma methods - `xpu` generator on non-cpu - `compel` compatibility with *transformers==5* + - `gallery` open folder - `mixture-tiling` fix for non-square images, thanks @QualiaRain - `prompts-from-file` fix metadata handling, thanks @QualiaRain + - `hypertile` correct width/height assignment, thanks @QualiaRain + - custom allowed-paths, thanks @QualiaRain + - bias dtype, thanks @QualiaRain + - `ipadapter` mask accumulation, thanks @QualiaRain + - `lora` no-lora check, thanks @QualiaRain + - control `video` processing, thanks @QualiaRain + - `freescale` correct width/height assignment, thanks @QualiaRain + - noise `lerp` inversion, thanks @QualiaRain + - additional safety checks, thanks @QualiaRain + - `remote vae` shadowing, thanks @QualiaRain ## Update for 2026-05-13 diff --git a/modules/openai/test.py b/modules/openai/test.py index 34a3a06a8..9c9d662b2 100644 --- a/modules/openai/test.py +++ b/modules/openai/test.py @@ -48,4 +48,3 @@ while True: except KeyboardInterrupt: server.stop() break - diff --git a/modules/ui_common.py b/modules/ui_common.py index b190046fe..6e4201824 100644 --- a/modules/ui_common.py +++ b/modules/ui_common.py @@ -264,6 +264,8 @@ def save_files(js_data, files, html_info, index): def open_folder(result_gallery, gallery_index = 0): try: + if gallery_index >= len(result_gallery): + gallery_index = 0 folder = os.path.dirname(result_gallery[gallery_index]['name']) except Exception: folder = shared.opts.outdir_samples From ccae78ca661c95936b05d3bd0a78fb3892f54fda Mon Sep 17 00:00:00 2001 From: Vladimir Mandic Date: Tue, 16 Jun 2026 10:17:13 +0200 Subject: [PATCH 12/16] Potential fix for pull request finding 'CodeQL / Workflow does not contain permissions' Co-authored-by: Copilot Autofix powered by AI <62310815+github-advanced-security[bot]@users.noreply.github.com> --- .github/workflows/lint.yaml | 3 +++ 1 file changed, 3 insertions(+) diff --git a/.github/workflows/lint.yaml b/.github/workflows/lint.yaml index ff6275405..643758b4c 100644 --- a/.github/workflows/lint.yaml +++ b/.github/workflows/lint.yaml @@ -4,6 +4,9 @@ on: - push - pull_request +permissions: + contents: read + jobs: lint: runs-on: ubuntu-24.04 From 0769d423a716bfbd5bdca0eeab439f4482b0daea Mon Sep 17 00:00:00 2001 From: CalamitousFelicitousness Date: Tue, 16 Jun 2026 10:18:21 +0100 Subject: [PATCH 13/16] fix(upscale): make SeedVR2 generation_step patch idempotent UpscalerSeedVR.load_model() rebinds the module-global generation.generation_step (called by name inside generation_loop) to the instance's model_step wrapper, keeping the previous value to call back into. That global was never restored, so the second pass through load_model() saved the wrapper itself as the "original", making model_step() call itself -> RecursionError. The second pass is reached on any model (re)load: with upscaler_unload enabled (self.model reset to None after each run) every subsequent run recurses, and switching SeedVR variants (self.model_loaded != model_name) triggers it even without unload. Stash the pristine generation_step on the module once and have the wrapper call that, so repeated loads never wrap the wrapper. Co-Authored-By: Claude Opus 4.8 (1M context) --- modules/postprocess/seedvr_model.py | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/modules/postprocess/seedvr_model.py b/modules/postprocess/seedvr_model.py index fb7c94c79..9e1e5aebe 100644 --- a/modules/postprocess/seedvr_model.py +++ b/modules/postprocess/seedvr_model.py @@ -47,7 +47,10 @@ class UpscalerSeedVR(Upscaler): self.model.dit.dtype = devices.dtype self.model.vae_encode = self.vae_encode self.model.vae_decode = self.vae_decode - self.model.model_step = generation.generation_step + # Patch generation_loop's generation_step() with our wrapper; stash the original once + # so reloads don't re-wrap the wrapper itself (infinite recursion). + if not hasattr(generation, "generation_step_original"): + generation.generation_step_original = generation.generation_step generation.generation_step = self.model_step self.model._internal_dict = { 'dit': self.model.dit, @@ -119,6 +122,7 @@ class UpscalerSeedVR(Upscaler): return samples def model_step(self, *args, **kwargs): + from modules.seedvr.src.core import generation from modules.seedvr.src.optimization import memory_manager self.model.vae = self.model.vae.to(device="cpu") self.model.dit = self.model.dit.to(device=self.device) @@ -126,7 +130,7 @@ class UpscalerSeedVR(Upscaler): log.debug(f'Upscaler inference: args={len(args)} kwargs={list(kwargs.keys())}') memory_manager.preinitialize_rope_cache(self.model) with devices.inference_context(): - result = self.model.model_step(*args, **kwargs) + result = generation.generation_step_original(*args, **kwargs) self.model.dit = self.model.dit.to(device="cpu") devices.torch_gc() return result From aba915ff447dd508a02c61c5706f49144a044394 Mon Sep 17 00:00:00 2001 From: Vladimir Mandic Date: Tue, 16 Jun 2026 11:25:56 +0200 Subject: [PATCH 14/16] update changelog Signed-off-by: Vladimir Mandic --- CHANGELOG.md | 1 + 1 file changed, 1 insertion(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 4193eed8b..ccee88dd0 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -153,6 +153,7 @@ And we have a new modular LoRA loader, new native Transformers loader and improv - `xpu` generator on non-cpu - `compel` compatibility with *transformers==5* - `gallery` open folder + - `seedvr` unload after upscale - `mixture-tiling` fix for non-square images, thanks @QualiaRain - `prompts-from-file` fix metadata handling, thanks @QualiaRain - `hypertile` correct width/height assignment, thanks @QualiaRain From b0dd94ab8a7a3bda3eb9138b47d56f1925a5c297 Mon Sep 17 00:00:00 2001 From: Vladimir Mandic Date: Tue, 16 Jun 2026 11:58:36 +0200 Subject: [PATCH 15/16] fix tinyvae with anima Signed-off-by: Vladimir Mandic --- CHANGELOG.md | 1 + modules/processing_vae.py | 18 ++++++++++++++++-- 2 files changed, 17 insertions(+), 2 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index ccee88dd0..1c2997a6a 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -154,6 +154,7 @@ And we have a new modular LoRA loader, new native Transformers loader and improv - `compel` compatibility with *transformers==5* - `gallery` open folder - `seedvr` unload after upscale + - `tinyvae` with anima - `mixture-tiling` fix for non-square images, thanks @QualiaRain - `prompts-from-file` fix metadata handling, thanks @QualiaRain - `hypertile` correct width/height assignment, thanks @QualiaRain diff --git a/modules/processing_vae.py b/modules/processing_vae.py index b793140b5..a889b1748 100644 --- a/modules/processing_vae.py +++ b/modules/processing_vae.py @@ -246,13 +246,21 @@ def vae_postprocess(tensor, model, output_type='np'): if hasattr(model, 'video_processor'): if tensor.ndim == 6 and tensor.shape[1] == 1: tensor = tensor.squeeze(0) - images = model.video_processor.postprocess_video(tensor, output_type='pil') + try: + images = model.video_processor.postprocess_video(tensor, output_type='pil') + except Exception as e: + log.warning(f'VAE postprocess: type=video {e}') + images = tensor if isinstance(images, list) and len(images) > 0 and isinstance(images[0], list): images = [frame for batch in images for frame in batch] elif hasattr(model, 'image_processor'): if tensor.ndim == 5 and tensor.shape[1] == 3: # Qwen Image tensor = tensor[:, :, 0] - images = model.image_processor.postprocess(tensor, output_type=output_type) + try: + images = model.image_processor.postprocess(tensor, output_type=output_type) + except Exception as e: + log.warning(f'VAE postprocess: type=image {e}') + images = tensor elif hasattr(model, "vqgan"): images = tensor.permute(0, 2, 3, 1).cpu().float().numpy() if output_type == "pil": @@ -263,6 +271,12 @@ def vae_postprocess(tensor, model, output_type='np'): if tensor.ndim == 5 and tensor.shape[1] == 3: # Qwen Image tensor = tensor[:, :, 0] images = model.image_processor.postprocess(tensor, output_type=output_type) + if torch.is_tensor(images): # failed to postprocess, do naive conversion + images = images.permute(0, 2, 3, 1).cpu().float().numpy() + if images.min() < 0 or images.max() > 1: + images = (images - images.min()) / (images.max() - images.min()) # naive normalization + if output_type == "pil": + images = model.numpy_to_pil(images) else: images = tensor if isinstance(tensor, list) or isinstance(tensor, np.ndarray) else [tensor] except Exception as e: From 37fc1da9baa70585fd8faba5e33c55a413ce6fc3 Mon Sep 17 00:00:00 2001 From: Vladimir Mandic Date: Tue, 16 Jun 2026 12:13:24 +0200 Subject: [PATCH 16/16] update changelog Signed-off-by: Vladimir Mandic --- CHANGELOG.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 1c2997a6a..103549298 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -18,7 +18,7 @@ And we have a new modular LoRA loader, new native Transformers loader and improv *Note*: This is a major update due to sheer size of the changes: over 400 commits! -[ReadMe](https://github.com/vladmandic/automatic/blob/master/README.md) | [ChangeLog](https://github.com/vladmandic/automatic/blob/master/CHANGELOG.md) | [Docs](https://vladmandic.github.io/sdnext-docs/) | [WiKi](https://github.com/vladmandic/automatic/wiki) | [Discord](https://discord.com/invite/sd-next-federal-batch-inspectors-1101998836328697867) | [Sponsor](https://github.com/sponsors/vladmandic) +[Home](https://vladmandic.github.io/sdnext/) | [ChangeLog](https://github.com/vladmandic/automatic/blob/master/CHANGELOG.md) | [Docs](https://vladmandic.github.io/sdnext-docs/) | [Discord](https://discord.com/invite/sd-next-federal-batch-inspectors-1101998836328697867) | [Sponsor](https://github.com/sponsors/vladmandic) ### Details for 2026-06-16