From 05abd99285001e63e0951cbba10693e349bf7f9d Mon Sep 17 00:00:00 2001 From: CalamitousFelicitousness Date: Sun, 19 Apr 2026 03:36:16 +0100 Subject: [PATCH] fix(video): invalidate ltx cache on pipe-class mismatch Move cache tracking from ltx_util into video_load where shared.sd_model lives, and invalidate the name-based hit when the cached class no longer matches the current pipeline (e.g. after Unload Models triggers an auto-reload of the default checkpoint). - Drop the duplicate module-level loaded_model cache in ltx_util - Add a pipe-class isinstance check around the cache hit in video_load --- modules/ltx/ltx_util.py | 16 ++++------------ modules/video_models/video_load.py | 6 ++++++ 2 files changed, 10 insertions(+), 12 deletions(-) diff --git a/modules/ltx/ltx_util.py b/modules/ltx/ltx_util.py index e31641bf9..310a865fb 100644 --- a/modules/ltx/ltx_util.py +++ b/modules/ltx/ltx_util.py @@ -5,11 +5,8 @@ from modules import devices, shared, sd_models, timer, extra_networks from modules.logger import log -loaded_model: str = None - - def get_bucket(size: int): - # LTX pipelines validate width/height divisible by 32 across all families + # LTX pipes validate width/height divisible by 32 across all families. ratio = getattr(shared.sd_model, 'vae_spatial_compression_ratio', None) if not isinstance(ratio, int) or ratio < 32: ratio = 32 @@ -22,21 +19,16 @@ def get_frames(frames: int): def load_model(engine: str, model: str): - global loaded_model # pylint: disable=global-statement - if not shared.sd_loaded: - loaded_model = None - if loaded_model == model: - return - if model is None or model == '' or model=='None': - loaded_model = None + if model is None or model == '' or model == 'None': shared.sd_model = None return t0 = time.time() from modules.video_models import models_def, video_load selected: models_def.Model = [m for m in models_def.models[engine] if m.name == model][0] + # video_load owns the cache; pipe-class mismatch inside it invalidates the name-based hit + # when Unload Models (or any external swap) silently replaced shared.sd_model. log.info(f'Video load: engine="{engine}" selected="{model}" {selected}') video_load.load_model(selected) - loaded_model = model t1 = time.time() shared.sd_model = sd_models.apply_balanced_offload(shared.sd_model) t2 = time.time() diff --git a/modules/video_models/video_load.py b/modules/video_models/video_load.py index 373751b7e..c09197a0f 100644 --- a/modules/video_models/video_load.py +++ b/modules/video_models/video_load.py @@ -36,6 +36,12 @@ def load_model(selected: models_def.Model): global loaded_model # pylint: disable=global-statement if not shared.sd_loaded: loaded_model = None + elif loaded_model == selected.name and selected.repo_cls is not None and not isinstance(shared.sd_model, selected.repo_cls): + # shared.sd_model auto-reloads the default checkpoint when model_data.sd_model is None, + # which silently swaps the pipe class behind the name-based cache. Pipe-class mismatch + # is the reliable signal that the cached name no longer maps to the cached object. + log.warning(f'Video load: cached model="{selected.name}" pipe class swapped to {type(shared.sd_model).__name__}; forcing reload') + loaded_model = None if loaded_model == selected.name: return '' if shared.sd_loaded: