From d9bf41b6dd5ad35ca30d6f8eb4f195d03300565a Mon Sep 17 00:00:00 2001 From: digital-garbage <133010426+digital-garbage@users.noreply.github.com> Date: Tue, 26 May 2026 03:44:09 +0300 Subject: [PATCH] Fix transition flat zone and memory leak scene_cond ref MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Transition "bad" appearance: blend_half_pixels=64 caused 2+ seconds of forced full-black at every seam — the flat zone existed to hide latent blend artifacts, but the blend is a no-op (overlap frames are identical on both sides, mask=0). Removed blend_half entirely. Transitions are now a simple V-ramp: full effect at the seam center, linear ramp back to normal over transition_duration//2 pixel frames on each side. Memory leak: scene_cond (Python loop variable) stays alive after del scene_conditionings — still holds the conditioning tuple whose embedding tensor keeps LTXAVTEModel referenced during VAE decode. Added scene_cond to the del statement. Co-Authored-By: Claude Sonnet 4.6 --- samplers.py | 31 ++++++++++--------------------- 1 file changed, 10 insertions(+), 21 deletions(-) diff --git a/samplers.py b/samplers.py index ee4f62dd..c2bd3977 100644 --- a/samplers.py +++ b/samplers.py @@ -1166,21 +1166,17 @@ class FunPackLTXAVSceneChainSampler: x = torch.nn.functional.avg_pool2d(x, k, stride=1, padding=0) return x.squeeze(0).permute(1, 2, 0).to(dtype=frame.dtype) - def _apply_effect_on_pixels(self, frames, effect, center, half, blend_half=0): + def _apply_effect_on_pixels(self, frames, effect, center, half): """Apply one visual transition effect in-place on a [N, H, W, C] float tensor. - blend_half: number of frames on each side of center that are blended-latent - content and must be fully processed (zero brightness / max blur) regardless - of transition_duration. extra frames beyond blend_half fade/ramp back to - full brightness over the remaining (half - blend_half) range. + Simple V-ramp centered at the seam: full effect at center, linear ramp + back to no-effect at ±half frames. transition_duration controls half. """ n = frames.shape[0] center = max(half, min(n - half - 1, center)) - extra_half = max(1, half - blend_half) if effect == "fade_to_black": for i in range(max(0, center - half), min(n, center + half)): - dist = abs(i - center) - brightness = min(1.0, max(0.0, (dist - blend_half) / extra_half)) + brightness = abs(i - center) / max(1, half) frames[i] = frames[i] * brightness elif effect == "crossfade": orig = frames.clone() @@ -1192,8 +1188,7 @@ class FunPackLTXAVSceneChainSampler: frames[post_i] = (1 - alpha) * orig[post_i] + alpha * orig[pre_i] elif effect == "blur_out_in": for i in range(max(0, center - half), min(n, center + half)): - dist = abs(i - center) - ramp = max(0.0, (dist - blend_half) / extra_half) + ramp = abs(i - center) / max(1, half) sigma = 8.0 * (1.0 - ramp) if sigma > 0: frames[i] = self._blur_pixel_frame(frames[i], sigma) @@ -1243,11 +1238,9 @@ class FunPackLTXAVSceneChainSampler: if not active: return decoded frames = decoded.clone().float() + half = max(1, transition_duration // 2) for entry in active: - blend_half = entry.get("blend_half_pixels", 0) - extra = max(1, transition_duration // 2) - half = blend_half + extra - self._apply_effect_on_pixels(frames, entry["effect"], int(entry["pixel_frame"]), half, blend_half=blend_half) + self._apply_effect_on_pixels(frames, entry["effect"], int(entry["pixel_frame"]), half) return frames.clamp(0.0, 1.0).to(dtype=decoded.dtype) def _scene_text(self, scene_conditioning, index): @@ -1323,18 +1316,14 @@ class FunPackLTXAVSceneChainSampler: effect = self._scene_transition_effect(scene_cond) if effect and transition_duration > 0: # Seam is at cumulative_latent_frames: overlap frames are pinned - # (mask=0) so the latent blend is a no-op — the actual content - # boundary is exactly here, not offset by overlap//2. + # (mask=0) so the latent blend is a no-op — actual content + # boundary is exactly here. boundary_latent = cumulative_latent_frames boundary_pixel = int((boundary_latent - 1) * time_scale + 1) if time_scale > 1 else boundary_latent - blend_half_latent = max(1, video_overlap // 2) - blend_half_pixels = blend_half_latent * time_scale boundary_entries.append({ "boundary_latent": boundary_latent, "pixel_frame": max(0, boundary_pixel), "effect": effect, - "blend_half_latent": blend_half_latent, - "blend_half_pixels": blend_half_pixels, }) chunk = self._build_continuation_chunk(latent_template, output, video_overlap) if carry_i2v_guides: @@ -1358,7 +1347,7 @@ class FunPackLTXAVSceneChainSampler: # model and text encoder before VAE decode runs. scene_conditionings is a # slice of positive; scene_positive/negative are loop-scoped conditioning # copies — all keep the LTXAVTEModel alive through their embedding tensors. - del model, positive, negative, scene_conditionings, scene_positive, scene_negative, chunk, sampled + del model, positive, negative, scene_conditionings, scene_cond, scene_positive, scene_negative, chunk, sampled # Scene boundaries for per-scene decode: each entry's boundary_latent is # where the next scene's new content starts in the concatenated latent.