Revert i2v anchor (DynamicConditioning): no-op on packed AV, not worth keeping

DynamicConditioning is an unpacked-video technique; on LTXAV (packed AV latent, 3D
mask) it can only no-op (comfy's process_timestep patchifies expecting 5D). Rather
than keep dead knobs in the Chain Sampler UI, remove i2v_anchor_power /
i2v_anchor_first_frame_only, the install block, and _install_i2v_anchor entirely.

The packed-safe path for cross-cut appearance carry (strengthen the Chain Sampler's
own carry/guide masks pre-pack) remains available as a future native feature.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
digital-garbage
2026-06-05 20:56:52 +03:00
co-authored by Claude Opus 4.8
parent 04d00fcecd
commit cf3f0b64a5
-83
View File
@@ -2070,14 +2070,6 @@ class FunPackLTXAVSceneChainSampler:
"default": 0.05, "min": 0.0, "max": 1.0, "step": 0.001,
"tooltip": "Decode timestep fed to the VAE decoder when decode_noise_scale > 0. ~0.05 adds subtle detail; higher gives the decoder more freedom (more deviation from the latent).",
}),
"i2v_anchor_power": ("FLOAT", {
"default": 1.0, "min": 1.0, "max": 2.0, "step": 0.01,
"tooltip": "Progressively strengthen i2v adherence to the conditioning frame as denoising proceeds (port of LTXV DynamicConditioning): the denoise mask is raised to power**step. 1.0 = off. NOTE: only works on UNPACKED 5D video masks; on PACKED audio+video (LTXAV) it safely no-ops (the mask is 3D and comfy's per-token-timestep patchify needs 5D). ~zero overhead.",
}),
"i2v_anchor_first_frame_only": ("BOOLEAN", {
"default": True,
"tooltip": "When i2v_anchor_power > 1.0: apply the ramp only to the first (conditioning) frame, leaving the rest free. Off ramps the whole mask.",
}),
"embed_guidance_source": (["relative", "absolute"], {
"default": "relative",
"tooltip": "Which learned direction embed_guidance steers toward. Relative: this prompt's liked direction (needs refinement_key_input). Absolute: the global, prompt-agnostic taste direction the Refiner accumulates across all prompts — works with no key.",
@@ -2785,75 +2777,6 @@ class FunPackLTXAVSceneChainSampler:
result.seed = seed
return result
def _install_i2v_anchor(self, model, power, first_frame_only):
"""Port of LTXV DynamicConditioning: progressively strengthen i2v adherence to the
conditioning frame as denoising proceeds, by raising the denoise mask to power**step.
Uses the stock comfy ModelPatcher API (set_model_denoise_mask_function) — no modified
model, no extra forward pass. Fully defensive: any missing mask / unexpected shape / error
falls through to the unmodified mask, so it's a safe no-op when it can't apply."""
p = float(power)
def _find_step(sigma, step_sigmas):
try:
for idx, s in enumerate(step_sigmas):
if s <= sigma:
return idx
return len(step_sigmas) - 1
except Exception:
return 0
def _denoise_mask_fn(sigma, denoise_mask, extra_options):
try:
step_sigmas = extra_options.get("sigmas")
inner = extra_options.get("model")
if denoise_mask is None or step_sigmas is None or inner is None:
return denoise_mask
exp = p ** _find_step(sigma, step_sigmas)
if abs(exp - 1.0) < 1e-6:
return denoise_mask
# This technique (LTXV DynamicConditioning) assumes an UNPACKED 5D video mask
# [B,C,F,H,W]: comfy's LTXAV process_timestep patchifies the mask expecting 5D.
# LTXAV PACKS the AV latent, so its denoise mask is 3D [B,C,N] — touching or
# propagating that crashes patchify (expected 5, got 3). So operate ONLY on genuine
# 5D masks; on packed AV (or anything else) this is a clean no-op.
if denoise_mask.dim() != 5:
if not getattr(self, "_i2v_anchor_packed_warned", False):
self._i2v_anchor_packed_warned = True
print("[FunPackSceneChain] i2v anchor: packed/non-5D denoise mask "
f"(ndim={denoise_mask.dim()}) -> no-op (technique needs unpacked video).")
return denoise_mask
mask = denoise_mask.clone()
if first_frame_only:
try:
num_channels = inner.model_patcher.model.diffusion_model.in_channels
except Exception:
num_channels = mask.shape[1]
mask[:, :num_channels, :1] = mask[:, :num_channels, :1] ** exp
else:
mask = mask ** exp
# Propagate to the conds so per-token timestep values stay consistent.
try:
for ck in inner.conds:
if "positive" in ck or "negative" in ck:
for cond in inner.conds[ck]:
mc = cond.get("model_conds") if isinstance(cond, dict) else None
if mc and "denoise_mask" in mc:
mc["denoise_mask"].cond = mask
except Exception:
pass
return mask
except Exception:
return denoise_mask
try:
patched = model.clone()
patched.set_model_denoise_mask_function(_denoise_mask_fn)
print(f"[FunPackSceneChain] i2v anchor ramp on (power={p}, first_frame_only={first_frame_only})")
return patched
except Exception as e:
print(f"[FunPackSceneChain] i2v anchor install failed ({e}); continuing without it.")
return model
def sample(self, model, vae, positive, negative, sampler, sigmas, seed, latent_template,
num_frames_per_scene, frame_overlap, cfg, max_scenes, use_same_seed=False,
carry_i2v_guides=False,
@@ -2862,7 +2785,6 @@ class FunPackLTXAVSceneChainSampler:
embed_guidance_source="relative",
transition_duration=16, decode_tile_size=0,
decode_noise_scale=0.0, decode_timestep=0.05,
i2v_anchor_power=1.0, i2v_anchor_first_frame_only=True,
refinement_key_input="", unique_id=None, prompt=None):
if not isinstance(positive, list) or not positive:
raise ValueError("positive conditioning must contain at least one scene entry.")
@@ -2875,11 +2797,6 @@ class FunPackLTXAVSceneChainSampler:
if decode_noise_scale and decode_noise_scale > 0:
vae = self._vae_with_decode_noise(vae, decode_timestep, decode_noise_scale, seed)
# i2v anchor ramp (LTXV DynamicConditioning): patch the model BEFORE the batch/main split
# so both paths inherit it. power<=1.0 = off.
if i2v_anchor_power and i2v_anchor_power > 1.0:
model = self._install_i2v_anchor(model, i2v_anchor_power, bool(i2v_anchor_first_frame_only))
# Batch Training: Studio (the hub) packs N conditionings into positive, each scene entry
# tagged 'funpack_batch_variant'. That marker is the only trigger — the sampler has no
# batch-count input. Sample one chain per packed entry, persist each for rating in Studio.