Skip to content

Re-register the backbone pre-hook on every apply - #2

Closed
jjdejong wants to merge 1 commit into
fix/video-latent-capturefrom
fix/backbone-hook-lifecycle
Closed

Re-register the backbone pre-hook on every apply#2
jjdejong wants to merge 1 commit into
fix/video-latent-capturefrom
fix/backbone-hook-lifecycle

Conversation

@jjdejong

Copy link
Copy Markdown
Owner

Stacked on #1 — base is fix/video-latent-capture, so the diff here is the lifecycle change alone.

Problem

The backbone pre-hook registration is gated on a sentinel attribute:

if not getattr(backbone, HOOK_ATTR_BACKBONE, False):
    backbone.register_forward_pre_hook(backbone_pre_hook, with_kwargs=True)
    setattr(backbone, HOOK_ATTR_BACKBONE, True)

That sentinel lives on the backbone module, which model.clone() shares by reference. So from the second apply onward in a single ComfyUI process the gate is already set: the new pre-hook is never attached, and the first apply's closure stays live — writing captured_latent_shape and current_sigma into a state dict that nothing else can read.

Consequences for every apply after the first, for the lifetime of the process:

  • latent_frame_0 silently returns unmodified output. captured_latent_shape stays None, so the hook bails at every block.
  • skip_when_sigma_above stops working in all modes, including guidecurrent_sigma comes from the same dead closure.

Only the first prompt after a restart ever worked, which makes this easy to mistake for a functioning node: you see it work once, then never notice it stop.

The attn1 hooks in this same node already remove-and-re-register correctly, and LTXLatentAnchorAware does so for both its hook types — this path was the outlier.

Fix

Track the handle, remove any prior registration, re-register unconditionally. The bypass path now detaches the pre-hook too, rather than only dropping the sentinel — which previously left the hook attached and firing.

Verification

Applying three times against a shared module:

apply 1: state='run1' live_hooks=1
apply 2: state='run2' live_hooks=1
apply 3: state='run3' live_hooks=1

Each apply's own state dict is the one populated, and hooks don't accumulate.

Confirmed on a live LTX-AV run: before the fix a second apply logged video latent shape was never captured; after it, the same second apply reports HOOK ACTIVE.

Registration was gated on a sentinel attribute living on the backbone,
which model.clone() shares by reference. From the second apply onward in a
single ComfyUI process the gate was already set, so the new pre-hook was
never attached and the first apply's closure stayed live, writing into a
state dict nothing else could read.

The effect was that captured_latent_shape and current_sigma stayed None for
the rest of the process: latent_frame_0 silently returned unmodified output
from the second prompt onward, and the skip_when_sigma_above gate stopped
working in every mode including guide. Only the first prompt after a
restart ever worked, which makes it easy to mistake for a working node.

Track the handle, remove any prior registration, and re-register
unconditionally -- the same lifecycle the attn1 hooks already use, and that
LTXLatentAnchorAware already applies to both its hook types. The bypass path
now detaches the pre-hook too instead of only dropping the sentinel, which
previously left it attached and firing.

Verified by applying three times against a shared module: each apply's state
dict is the one populated, and exactly one pre-hook stays live.
@jjdejong

Copy link
Copy Markdown
Owner Author

Superseded by TenStrip#15, which carries both commits upstream. Branch retained.

@jjdejong jjdejong closed this Jul 28, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant