π CUDA Zero-Copy Latent Handoff (Continuity)
Chain Video Shots Without the VAE Round-Trip
- clip
- vae
- fallback_first_frame
- positive
- latent
The standard way to keep two shots continuous is the last-frame-to-first-frame trick: decode the clip, grab the final frame as a PNG, feed it back in as an I2V start image. It works - people have built 30-second sequences that way - but every hand-off runs the picture through a VAE decode and a VAE encode, and that round-trip nudges the pixels. Do it ten times and the drift shows up as flicker, colour shift, and that slightly melting look at the cut.
CineFluxZeroCopyLatentHandoff skips the pixels entirely. It takes the latent the previous shot ended on, slices off the last frame, and feeds that straight into the next shot's conditioning. No decode, no disk, no re-encode.
What it's actually doing
Four steps, in order:
- Builds the empty latent for the new shot. If ComfyUI's MiniMax-H3 extras are present it calls
_empty_av_latent(width, height, length); otherwise it builds its own: a video tensor[1, 24, T, H/16, W/16]plus an audio tensor[1, 32, 2, T_audio], wrapped as a nested tensor. - Pulls the tail. It looks up
buffer_tagin the pack's resident latent buffer and slices[:, :, -1:, :, :]- the final latent frame of the previous shot. If the new canvas is a different resolution, it resamples the slice in latent space. - Encodes your prompt, with the fallback image attached as a visual reference when you connect one.
- Attaches the slice as keyframe 0 by writing a
minimax_keyframesentry into the returned conditioning vianode_helpers.conditioning_set_values.
That last point is the whole mechanism, and it's model-specific. minimax_keyframes is the key ComfyUI's MiniMax-H3 nodes look for. If you're on H3 (the 33B omni-modal model with native stereo audio), this is a first-class conditioning path. If you're on something else, see the sibling article - this key is not the one your model reads.
Inputs worth understanding
Required: clip (your text encoder), vae, prompt, plus width / height (defaults 1280Γ768, step 32) and length (default 192, step 17 - the tooltip says "frame count at 24 fps (192 = 8 seconds)", but treat the grid as the real constraint).
The vae input is required even though the pure latent path never touches it - it's only used in blend_with_fallback and fallback_only. You still have to wire something up, so connect your real video VAE.
Optional, and these are the ones you'll set:
buffer_tag(defaultshot_continuity) - must match the tag on the Store node from shot 1. Mismatch is the #1 silent failure: no latent found, no warning, straight to cold start.continuity_mode-zero_copy_latent_only(default, pure latent hand-off),blend_with_fallback(alpha-blend the prior latent with a VAE-encoded fallback frame),fallback_only(plain keyframe encoding, i.e. the old way).blend_ratio- 1.0 is all previous latent, 0.0 is all fallback image. This is your dial for a hard camera change: 0.7-ish keeps the colour match but lets the new frame win.fallback_first_frame- the reference image, used for visual tokens and for cold starts.
Outputs are positive (CONDITIONING β the KSampler's positive input) and latent (LATENT β the KSampler's latent input). Note the README calls them "conditioning" and "latent"; in the actual node the first port is named positive, and there is no negative output - pair it with your own text-encode node for the negative side, since the keyframe binding only exists on the positive conditioning.
Install
Manager β search ComfyUI-ZeroCopy-Continuity, or clone and restart:
cd ComfyUI/custom_nodes
git clone https://github.com/latentforge-dev/ComfyUI-ZeroCopy-Continuity
No models, no pip installs beyond the torch you have. The README's git URL says latentforge/ - the working one is latentforge-dev/.
Getting burned
First run of a fresh ComfyUI process always cold-starts. The buffer lives in the server process's memory. Nothing is stored until a Store node has executed, so run shot 1, then shot 2, and don't expect two runs queued from the same graph to come out continuous.
Resolution changes are resampled bilinearly, not bicubically, whatever the README says - the code passes mode="bilinear". Fine for a small crop change, visibly soft if you jump 1280Γ768 β 1920Γ1080.
length gets clamped when the H3 helpers are missing. The internal latent builder caps at 362 frames (min(362, max(5, length))) even though the widget allows 3600. If your 10-second request comes back short, that's why.
It's a homelab pack. No community footprint I could find, one author's 5090 benchmark, and a docs folder partly in German. The code is short enough to read before you trust it with a render.
Inputs (10)
| Name | Type | Default | Description |
|---|---|---|---|
| clip | CLIP | CLIP / Text-Encoder model. | |
| vae | VAE | Video VAE for fallback encoding. | |
| prompt | STRING | β | |
| width | INT | 128032β8192 | β |
| height | INT | 76832β8192 | β |
| length | INT | 1925β3600 | Frame count at 24 fps (192 = 8 seconds). |
| fallback_first_frameopt | IMAGE | Reference image used for visual tokens or cold start. | |
| buffer_tagopt | STRING | shot_continuity | Buffer tag from which the previous shot's tail latent is pulled. |
| continuity_modeopt | COMBO | zero_copy_latent_only | 'zero_copy_latent_only': Bit-identical latent handoff from prior shot (Zero VAE loss); 'blend_with_fallback': Smooth alpha-blend between prior latent and fallback image; 'fallback_only': Standard keyframe encoding. |
| blend_ratioopt | FLOAT | 1.000β1 | 1.0 = 100% prior shot latent; 0.0 = 100% fallback image. |
Outputs (2)
| Name | Type | Description |
|---|---|---|
| positive | CONDITIONING | β |
| latent | LATENT | β |