diff --git a/CHANGELOG.md b/CHANGELOG.md
index 286cf5776..9dfcab66b 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -1,5 +1,14 @@
# Change Log for SD.Next
+## Update for 2026-01-21
+
+- add `SD_DEVICE_DEBUG` env variable to trace rocm/xpu/directml init failures
+- fix lora load when using peft/diffusers loader
+- fix rocm hipblaslt detection
+- fix image delete
+- fix `all_seeds` error
+- improve `wrap_gradio` error handling
+
## Update for 2026-01-20
### Highlights for 2026-01-20
diff --git a/modules/call_queue.py b/modules/call_queue.py
index 7ed03e5b9..ebfe58f0f 100644
--- a/modules/call_queue.py
+++ b/modules/call_queue.py
@@ -37,7 +37,6 @@ def wrap_gradio_gpu_call(func, extra_outputs=None, name=None):
id_task = None
with get_lock():
progress.start_task(id_task)
- res = [None, '', '', '']
try:
res = func(*args, **kwargs)
progress.record_results(id_task, res)
@@ -45,7 +44,8 @@ def wrap_gradio_gpu_call(func, extra_outputs=None, name=None):
shared.log.error(f"Exception: {e}")
shared.log.error(f"Arguments: args={str(args)[:10240]} kwargs={str(kwargs)[:10240]}")
errors.display(e, 'gradio call')
- res[-1] = f"
{html.escape(str(e))}
"
+ res = extra_outputs or []
+ res.append(f"{html.escape(str(e))}
")
finally:
progress.finish_task(id_task)
return res
@@ -70,7 +70,8 @@ def wrap_gradio_call(func, extra_outputs=None, add_stats=False, name=None):
if res is None:
msg = "No result returned from function"
shared.log.warning(msg)
- res = [None, '', '', f"{html.escape(msg)}
"]
+ res = extra_outputs_array or []
+ res.append(f"{html.escape(msg)}
")
else:
res = list(res)
if shared.cmd_opts.profile:
@@ -78,9 +79,8 @@ def wrap_gradio_call(func, extra_outputs=None, add_stats=False, name=None):
errors.profile(pr, 'Wrap')
except Exception as e:
errors.display(e, 'gradio call')
- if extra_outputs_array is None:
- extra_outputs_array = [None, '']
- res = extra_outputs_array + [f"{html.escape(type(e).__name__+': '+str(e))}
"]
+ res = extra_outputs_array or []
+ res.append(f"{html.escape(type(e).__name__+': '+str(e))}
")
shared.state.end(jobid)
if not add_stats:
return tuple(res)
diff --git a/modules/lora/extra_networks_lora.py b/modules/lora/extra_networks_lora.py
index 882c0d91b..3db727d1a 100644
--- a/modules/lora/extra_networks_lora.py
+++ b/modules/lora/extra_networks_lora.py
@@ -173,7 +173,7 @@ class ExtraNetworkLora(extra_networks.ExtraNetwork):
def signature(self, names: List[str], te_multipliers: List, unet_multipliers: List):
return [f'{name}:{te}:{unet}' for name, te, unet in zip(names, te_multipliers, unet_multipliers)]
- def changed(self, requested: List[str], include: List[str], exclude: List[str]):
+ def changed(self, requested: List[str], include: List[str] = None, exclude: List[str] = None) -> bool:
if shared.opts.lora_force_reload:
return True
sd_model = shared.sd_model.pipe if hasattr(shared.sd_model, 'pipe') else shared.sd_model
@@ -213,15 +213,17 @@ class ExtraNetworkLora(extra_networks.ExtraNetwork):
debug_log(f'Network load: type=LoRA include={include} exclude={exclude} method={load_method} requested={requested} fn={fn}')
if load_method == 'diffusers':
- has_changed = False # diffusers handles its own loading
- if len(exclude) == 0:
+ has_changed = self.changed(requested)
+ if has_changed:
jobid = shared.state.begin('LoRA')
lora_load.network_load(names, te_multipliers, unet_multipliers, dyn_dims, lora_modules) # load only on first call
sd_models.set_diffuser_offload(shared.sd_model, op="model")
shared.state.end(jobid)
+
elif load_method == 'nunchaku':
from modules.lora import lora_nunchaku
has_changed = lora_nunchaku.load_nunchaku(names, unet_multipliers)
+
else: # native
lora_load.network_load(names, te_multipliers, unet_multipliers, dyn_dims) # load
has_changed = self.changed(requested, include, exclude)
diff --git a/modules/lora/lora_load.py b/modules/lora/lora_load.py
index 14ee012ad..77695ee3f 100644
--- a/modules/lora/lora_load.py
+++ b/modules/lora/lora_load.py
@@ -304,7 +304,7 @@ def network_load(names, te_multipliers=None, unet_multipliers=None, dyn_dims=Non
shared.log.error(f'Network load: type=LoRA action=fuse {str(e)}')
if l.debug:
errors.display(e, 'LoRA')
- shared.sd_model = sd_models.apply_balanced_offload(shared.sd_model, force=True) # some layers may end up on cpu without hook
+ shared.sd_model = sd_models.apply_balanced_offload(shared.sd_model, force=True, silent=True) # some layers may end up on cpu without hook
if len(l.loaded_networks) > 0 and l.debug:
shared.log.debug(f'Network load: type=LoRA loaded={[n.name for n in l.loaded_networks]} cache={list(lora_cache)} fuse={shared.opts.lora_fuse_native}:{shared.opts.lora_fuse_diffusers}')
diff --git a/modules/processing_helpers.py b/modules/processing_helpers.py
index 222409738..786156bf0 100644
--- a/modules/processing_helpers.py
+++ b/modules/processing_helpers.py
@@ -227,7 +227,7 @@ def decode_first_stage(model, x):
def get_fixed_seed(seed):
- if seed is None or seed == '' or seed == -1:
+ if (seed is None) or (seed == '') or (seed == -1):
random.seed()
seed = int(random.randrange(4294967294))
return seed
@@ -236,10 +236,16 @@ def get_fixed_seed(seed):
def fix_seed(p):
p.seed = get_fixed_seed(p.seed)
p.subseed = get_fixed_seed(p.subseed)
- for i in range(len(p.all_seeds)):
- p.all_seeds[i] = get_fixed_seed(p.all_seeds[i])
- for i in range(len(p.all_subseeds)):
- p.all_subseeds[i] = get_fixed_seed(p.all_subseeds[i])
+ if p.all_seeds is None or len(p.all_seeds) == 0:
+ p.all_seeds = [p.seed]
+ else:
+ for i in range(len(p.all_seeds)):
+ p.all_seeds[i] = get_fixed_seed(p.all_seeds[i])
+ if p.all_subseeds is None or len(p.all_subseeds) == 0:
+ p.all_subseeds = [p.subseed]
+ else:
+ for i in range(len(p.all_subseeds)):
+ p.all_subseeds[i] = get_fixed_seed(p.all_subseeds[i])
def old_hires_fix_first_pass_dimensions(width, height):
diff --git a/modules/rocm.py b/modules/rocm.py
index c4c205bcb..dd1c8d33a 100644
--- a/modules/rocm.py
+++ b/modules/rocm.py
@@ -110,7 +110,7 @@ class Agent:
else:
self.arch = MicroArchitecture.GCN
self.is_apu = (self.gfx_version & 0xFFF0 == 0x1150) or self.gfx_version in (0x801, 0x902, 0x90c, 0x1013, 0x1033, 0x1035, 0x1036, 0x1103,)
- self.blaslt_supported = False if blaslt_tensile_libpath is None else os.path.exists(os.path.join(blaslt_tensile_libpath, f"Kernels.so-000-{self.name}.hsaco" if sys.platform == "win32" else f"extop_{self.name}.co"))
+ self.blaslt_supported = False if blaslt_tensile_libpath is None else os.path.exists(os.path.join(blaslt_tensile_libpath, f"TensileLibrary_lazy_{self.name}.dat"))
def __str__(self) -> str:
return self.name
@@ -138,6 +138,8 @@ class Agent:
return None
def get_gfx_version(self) -> Union[str, None]:
+ if self.gfx_version is None:
+ return None
if self.gfx_version >= 0x1100 and self.gfx_version < 0x1200:
return "11.0.0"
elif self.gfx_version != 0x1030 and self.gfx_version >= 0x1000 and self.gfx_version < 0x1100:
@@ -293,12 +295,14 @@ if sys.platform == "win32":
build_targets = torch.cuda.get_arch_list()
agents = get_agents()
+ log.debug(f'ROCm: agents={agents}')
if all(available.name not in build_targets for available in agents):
- log.warning('ROCm: torch-rocm is installed, but none of build targets is available')
+ log.warning('ROCm: torch-rocm is installed, but none of build targets are available')
# use cpu instead of crashing
torch.cuda.is_available = lambda: False
agent = get_hip_agent()
+ log.debug(f'ROCm: selected={agents}')
if not agent.blaslt_supported:
log.warning(f'ROCm: hipBLASLt unavailable agent={agent}')
if (agent.gfx_version & 0xFFF0) == 0x1200:
diff --git a/modules/rocm_triton_windows.py b/modules/rocm_triton_windows.py
index 88c509b6d..4bcbaff18 100644
--- a/modules/rocm_triton_windows.py
+++ b/modules/rocm_triton_windows.py
@@ -59,7 +59,7 @@ if sys.platform == "win32":
return zluda.core.to_hip_stream(_cuda_getCurrentRawStream(device))
def get_default_agent() -> Union[Agent, None]:
- if shared.devices.backend == "rocm":
+ if shared.devices.has_rocm():
return devices.get_hip_agent()
else:
from modules import zluda
diff --git a/modules/sd_offload.py b/modules/sd_offload.py
index c7b2b0e66..d0efd5d47 100644
--- a/modules/sd_offload.py
+++ b/modules/sd_offload.py
@@ -466,7 +466,7 @@ def report_model_stats(module_name, module):
shared.log.error(f'Module stats: name={module_name} {e}')
-def apply_balanced_offload(sd_model=None, exclude=None, force=False):
+def apply_balanced_offload(sd_model=None, exclude:list[str]=None, force:bool=False, silent:bool=False):
global offload_hook_instance # pylint: disable=global-statement
if shared.opts.diffusers_offload_mode != "balanced":
return sd_model
@@ -499,7 +499,8 @@ def apply_balanced_offload(sd_model=None, exclude=None, force=False):
module.module_name = module_name
module.offload_dir = os.path.join(shared.opts.accelerate_offload_path, checkpoint_name, module_name)
apply_balanced_offload_to_module(module, op='apply')
- report_model_stats(module_name, module)
+ if not silent:
+ report_model_stats(module_name, module)
set_accelerate(sd_model)
t = time.time() - t0
diff --git a/modules/shared.py b/modules/shared.py
index 5deb96825..0ac6511a1 100644
--- a/modules/shared.py
+++ b/modules/shared.py
@@ -87,16 +87,22 @@ elif cmd_opts.use_ipex or devices.has_xpu():
ok, e = ipex_init()
if not ok:
log.error(f'IPEX initialization failed: {e}')
+ if os.environ.get('SD_DEVICE_DEBUG', None) is not None:
+ errors.display(e, 'IPEX')
elif cmd_opts.use_directml:
from modules.dml import directml_init
ok, e = directml_init()
if not ok:
log.error(f'DirectML initialization failed: {e}')
+ if os.environ.get('SD_DEVICE_DEBUG', None) is not None:
+ errors.display(e, 'DirectML')
elif cmd_opts.use_rocm or devices.has_rocm():
from modules.rocm import rocm_init
ok, e = rocm_init()
if not ok:
log.error(f'ROCm initialization failed: {e}')
+ if os.environ.get('SD_DEVICE_DEBUG', None) is not None:
+ errors.display(e, 'ROCm')
devices.backend = devices.get_backend(cmd_opts)
devices.device = devices.get_optimal_device()
mem_stat = memory_stats()
diff --git a/modules/ui_common.py b/modules/ui_common.py
index 4c0c6979e..b96e0daff 100644
--- a/modules/ui_common.py
+++ b/modules/ui_common.py
@@ -88,7 +88,7 @@ def delete_files(js_data, files, all_files, index):
continue
if os.path.exists(fn) and os.path.isfile(fn):
deleted.append(fn)
- # os.remove(fn)
+ os.remove(fn)
if fn in all_files:
all_files.remove(fn)
shared.log.info(f'Delete: image="{fn}"')
diff --git a/modules/video_models/video_run.py b/modules/video_models/video_run.py
index 8f478fa15..a8603679a 100644
--- a/modules/video_models/video_run.py
+++ b/modules/video_models/video_run.py
@@ -11,6 +11,7 @@ debug = shared.log.trace if os.environ.get('SD_VIDEO_DEBUG', None) is not None e
def generate(*args, **kwargs):
task_id, ui_state, engine, model, prompt, negative, styles, width, height, frames, steps, sampler_index, sampler_shift, dynamic_shift, seed, guidance_scale, guidance_true, init_image, init_strength, last_image, vae_type, vae_tile_frames, mp4_fps, mp4_interpolate, mp4_codec, mp4_ext, mp4_opt, mp4_video, mp4_frames, mp4_sf, vlm_enhance, vlm_model, vlm_system_prompt, override_settings = args
+
if engine is None or model is None or engine == 'None' or model == 'None':
return video_utils.queue_err('model not selected')
# videojob = shared.state.begin('Video')
diff --git a/modules/video_models/video_ui.py b/modules/video_models/video_ui.py
index de0511d64..fdcefec46 100644
--- a/modules/video_models/video_ui.py
+++ b/modules/video_models/video_ui.py
@@ -190,7 +190,7 @@ def create_ui(prompt, negative, styles, overrides, init_image, init_strength, la
]
video_dict = dict(
- fn=call_queue.wrap_gradio_gpu_call(video_run.generate, extra_outputs=[None, '', ''], name='Video'),
+ fn=call_queue.wrap_gradio_gpu_call(video_run.generate, extra_outputs=[gr.update(), gr.update(), gr.update(), gr.update()], name='Video'),
_js="submit_video",
inputs=state_inputs + video_inputs,
outputs=video_outputs,