fix: strip leaked Technique-5 block hooks across runs (quality drift)
build_enhancements registered forward/forward-pre hooks directly on the shared diffusion_model blocks but only removed them on scene-transition sigma jumps, never at end-of-sampling. Single-scene runs (and the final scene of multi-scene runs) thus left hooks installed; because ModelPatcher clones share the underlying blocks, every subsequent generation stacked another set, each lerp-ing block output toward a previous run's stale reference tensor -> progressive corruption that survived Session Reset and deleting refinements/ (the leak is in-process, not on disk) and only cleared on a full restart. Tag every installed hook (_FUNPACK_HOOK_TAG) and add strip_funpack_block_hooks to pop only tagged entries from each block's hook stores (idempotent, leaves foreign hooks intact). Strip at the top of build_enhancements and defensively at FunPackLTXAVSceneChainSampler.sample() so accumulation is bounded to zero. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
co-authored by
Claude Opus 4.8
parent
593267daa8
commit
9fa0d3d2ea
@@ -940,6 +940,53 @@ def _is_ltx_model(model):
|
||||
return False
|
||||
|
||||
|
||||
# ---------------------------------------------------------------------------
|
||||
# Block-hook leak guard
|
||||
# ---------------------------------------------------------------------------
|
||||
# Technique-5 forward hooks (see _register_hooks) attach directly to the SHARED
|
||||
# diffusion blocks. They are removed on a scene-transition sigma jump, but NOT at
|
||||
# end-of-sampling, so a single-scene run (or the final scene of a multi-scene run)
|
||||
# leaves them installed. Since the blocks are shared across ModelPatcher clones,
|
||||
# the next generation stacks another set — over many gens this compounds into
|
||||
# progressive output corruption that survives Session Reset and deleting
|
||||
# refinements/ (the hooks live in-process, not on disk). We tag every installed
|
||||
# hook and strip any leftovers at the start of each run so accumulation is bounded
|
||||
# to a single set and removed before the next sampling pass.
|
||||
_FUNPACK_HOOK_TAG = "_funpack_enh_hook"
|
||||
|
||||
|
||||
def _funpack_locate_blocks(model):
|
||||
diff = getattr(getattr(model, "model", None), "diffusion_model", None)
|
||||
if diff is None:
|
||||
return None
|
||||
for attr in ("transformer_blocks", "blocks", "joint_blocks", "layers"):
|
||||
candidate = getattr(diff, attr, None)
|
||||
if isinstance(candidate, torch.nn.ModuleList) and len(candidate) >= 28:
|
||||
return candidate
|
||||
return None
|
||||
|
||||
|
||||
def strip_funpack_block_hooks(model):
|
||||
"""Remove forward / forward-pre hooks this module installed on the shared
|
||||
diffusion blocks in a previous run. Idempotent; safe to call every run."""
|
||||
bl = _funpack_locate_blocks(model)
|
||||
if bl is None:
|
||||
return 0
|
||||
removed = 0
|
||||
for block in bl:
|
||||
for store in (getattr(block, "_forward_hooks", None),
|
||||
getattr(block, "_forward_pre_hooks", None)):
|
||||
if not store:
|
||||
continue
|
||||
for hid in [hid for hid, fn in list(store.items())
|
||||
if getattr(fn, _FUNPACK_HOOK_TAG, False)]:
|
||||
store.pop(hid, None)
|
||||
removed += 1
|
||||
if removed:
|
||||
print(f"[FunPackEnhancements] Stripped {removed} leaked block hook(s) from a previous run")
|
||||
return removed
|
||||
|
||||
|
||||
# ---------------------------------------------------------------------------
|
||||
# Main enhancement builder
|
||||
# ---------------------------------------------------------------------------
|
||||
@@ -965,6 +1012,10 @@ def build_enhancements(model, rating_profile, temporal_style, refinement_key, re
|
||||
|
||||
model = model.clone()
|
||||
|
||||
# Strip any block hooks leaked by a previous run before installing fresh ones,
|
||||
# so they cannot stack across generations (see _FUNPACK_HOOK_TAG note above).
|
||||
strip_funpack_block_hooks(model)
|
||||
|
||||
# --- VF score for temperature modulation ---
|
||||
vf_score = None
|
||||
if conditioning is not None and refinement_key:
|
||||
@@ -1221,6 +1272,7 @@ def build_enhancements(model, rating_profile, temporal_style, refinement_key, re
|
||||
return injected
|
||||
except Exception:
|
||||
return None
|
||||
setattr(_hook, _FUNPACK_HOOK_TAG, True)
|
||||
return _hook
|
||||
|
||||
def _make_incontext_hooks(block_idx, buf, lazy_ref, strength, gate, s_state):
|
||||
@@ -1272,6 +1324,8 @@ def build_enhancements(model, rating_profile, temporal_style, refinement_key, re
|
||||
except Exception:
|
||||
return None
|
||||
|
||||
setattr(_pre, _FUNPACK_HOOK_TAG, True)
|
||||
setattr(_post, _FUNPACK_HOOK_TAG, True)
|
||||
return _pre, _post
|
||||
|
||||
for idx, (buf, lazy_ref, strength, gate) in _hook_block_params.items():
|
||||
|
||||
+13
@@ -3472,6 +3472,19 @@ class FunPackLTXAVSceneChainSampler:
|
||||
if negative is None:
|
||||
negative = []
|
||||
|
||||
# Defensively strip any enhancement block hooks left on the shared diffusion
|
||||
# model by a previous run (build_enhancements only removes them on scene
|
||||
# transitions, not at end-of-sampling). This covers runs that don't go through
|
||||
# build_enhancements, so stale hooks can't fire on an unenhanced generation.
|
||||
try:
|
||||
try:
|
||||
from .ltx_enhancements import strip_funpack_block_hooks
|
||||
except ImportError:
|
||||
from ltx_enhancements import strip_funpack_block_hooks
|
||||
strip_funpack_block_hooks(model)
|
||||
except Exception as _e:
|
||||
print(f"[FunPackLTXAVSceneChainSampler] hook strip failed: {_e}")
|
||||
|
||||
# Decode-time noise (folded in from LTXV's Set VAE Decoder Noise per the boundary law:
|
||||
# the Chain Sampler owns IMAGES decode, so this lives here, not on a separate node).
|
||||
# Stamp settings onto a shallow copy of the VAE so we never mutate the shared input.
|
||||
|
||||
Reference in New Issue
Block a user