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:
co-authored by
Claude Sonnet 4.6
parent
f01b53dfcb
commit
3dbdd55568
+19
-21
@@ -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)}")
|
||||
|
||||
Reference in New Issue
Block a user