Fix carry_i2v_guides: prepend guide instead of append to avoid temporal reset

Appending the guide (even offset to chunk-end) caused the model to generate
content converging toward the reference at the end of each scene. Scene 3's
overlap started from that converged tail and compounded into a full visual reset.

Prepending the guide places it at temporal position 0 naturally (causal patchifier
assigns it [0,1]). Overlap frames follow at [1,9], [9,17]... — no temporal slot
conflict, no forward-pulling toward the reference. The guide acts as a clean
i2v anchor at the start. Crop now removes from the head after sampling.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
digital-garbage
2026-05-26 14:36:44 +03:00
co-authored by Claude Sonnet 4.6
parent f01b53dfcb
commit 3dbdd55568
+19 -21
View File
@@ -1027,7 +1027,7 @@ class FunPackLTXAVSceneChainSampler:
"guide_attention_entries": entries,
})
def _append_i2v_guides(self, chunk, template, positive, negative, vae):
def _append_i2v_guides(self, chunk, template, positive, negative):
chunk_tensors = self._latent_tensors(chunk)
template_tensors = self._latent_tensors(template)
template_masks = self._latent_masks(template, len(template_tensors))
@@ -1050,11 +1050,11 @@ class FunPackLTXAVSceneChainSampler:
out_masks = self._latent_masks(chunk, len(out_tensors))
if out_masks[0] is None:
out_masks[0] = torch.ones_like(out_tensors[0])
chunk_latent_frames = self._tensor_frames(out_tensors[0])
out_tensors[0] = torch.cat([out_tensors[0], guide], dim=2)
target_mask = self._time_slice(out_masks[0], 0, protected).to(guide_mask.device, guide_mask.dtype)
guide_mask = self._expand_mask_like(guide_mask, target_mask)
out_masks[0] = torch.cat([out_masks[0].to(guide_mask.device, guide_mask.dtype), guide_mask], dim=2)
# Prepend so guide is temporal pos 0, overlap frames follow at 1, 9, 17... — no conflict.
out_tensors[0] = torch.cat([guide, out_tensors[0]], dim=2)
out_masks[0] = torch.cat([guide_mask, out_masks[0].to(guide_mask.device, guide_mask.dtype)], dim=2)
if self._is_nested(chunk.get("samples")):
chunk["samples"] = comfy.nested_tensor.NestedTensor(out_tensors)
@@ -1063,21 +1063,7 @@ class FunPackLTXAVSceneChainSampler:
chunk["samples"] = out_tensors[0]
chunk["noise_mask"] = out_masks[0]
scale_factors = getattr(vae, "downscale_index_formula", (1, 1, 1))
keyframe_idxs = self._guide_keyframe_idxs(guide, chunk_latent_frames, scale_factors)
guide_strength = max(0.0, min(1.0, 1.0 - float(guide_mask.float().mean().item())))
guide_entry = {
"pre_filter_count": guide.shape[2] * guide.shape[3] * guide.shape[4],
"strength": guide_strength,
"pixel_mask": None,
"latent_shape": list(guide.shape[2:]),
}
return (
chunk,
self._append_guide_conditioning(positive, keyframe_idxs, guide_entry),
self._append_guide_conditioning(negative, keyframe_idxs, guide_entry),
protected,
)
return chunk, positive, negative, protected
def _crop_video_tail(self, latent, count):
if count <= 0:
@@ -1091,6 +1077,18 @@ class FunPackLTXAVSceneChainSampler:
result["samples"] = tensors[0]
return result
def _crop_video_head(self, latent, count):
if count <= 0:
return latent
result = self._clone_latent(latent)
tensors = self._latent_tensors(result)
tensors[0] = tensors[0][:, :, count:]
if self._is_nested(result.get("samples")):
result["samples"] = comfy.nested_tensor.NestedTensor(tensors)
else:
result["samples"] = tensors[0]
return result
def _blend_tensors(self, left, right, overlap):
if overlap <= 0:
return torch.cat([left, right], dim=2)
@@ -1293,14 +1291,14 @@ class FunPackLTXAVSceneChainSampler:
chunk = self._build_continuation_chunk(latent_template, output, video_overlap)
if carry_i2v_guides:
chunk, scene_positive, scene_negative, carried = self._append_i2v_guides(
chunk, latent_template, scene_positive, scene_negative, vae,
chunk, latent_template, scene_positive, scene_negative,
)
carried_guide_frames = max(carried_guide_frames, carried)
sampled = self._sample_chunk(
model, sampler, sigmas, scene_seed, cfg, scene_positive, scene_negative, chunk,
)
if carry_i2v_guides and carried > 0:
sampled = self._crop_video_tail(sampled, carried)
sampled = self._crop_video_head(sampled, carried)
output = sampled if output is None else self._blend_latents(output, sampled, video_overlap)
cumulative_latent_frames = self._tensor_frames(self._latent_tensors(output)[0])
report_lines.append(f"Scene {scene_index + 1}: seed={scene_seed}, text={self._scene_text(scene_cond, scene_index)}")