Nodes/ComfyUI-ZeroCopy-Continuity/๐Ÿ’พ CUDA Zero-Copy Latent Store
ComfyUI Node

๐Ÿ’พ CUDA Zero-Copy Latent Store

A Node That Passes Data Through Untouched โ€” and That's the Point

By latentforge-devยทCreated 6 days agoยทUpdated 4 days agoยท 0
๐Ÿ’พ CUDA Zero-Copy Latent Store
  • samples
  • samples
โ—„buffer_tagshot_continuityโ–บ

Two inputs, one output, and the output is byte-for-byte the input. On its face CineFluxZeroCopyLatentStore looks like the most pointless node in your graph. Put it after a KSampler and it hands the same latent on to VAEDecode exactly as it arrived.

The work is the side effect: while passing it through, it files a reference to that latent in a process-wide buffer on the GPU, under a tag. That stored copy is what makes latent-level shot chaining possible at all - the hand-off node in shot 2 has no graph connection back to shot 1, so the only thing carrying state across the cut is this buffer.

If you're used to FLF chaining (decode โ†’ save last frame โ†’ reload as the next shot's start image), this is the cheap half of the replacement: no PNG, no colour shift, no round-trip. The other half is ZeroCopyLatentHandoff, and the two have to agree on one string.

What it keeps, and how

samples goes in, samples comes out, unmodified. Inside, the node calls the pack's singleton latent buffer and stores samples["samples"].detach() under buffer_tag. Two things follow from that detach(): it isn't a deep copy, so this really is "zero copy" - the buffer holds the same GPU memory the sampler produced - and because it's a reference, that VRAM can't be freed while the entry exists. A video latent is small next to a 20GB DiT, but it's not nothing, and nothing unloads it for you.

It handles both shapes the pack cares about: a plain tensor, or ComfyUI's nested (video, audio) tensor, which is what the MiniMax-H3 pipeline produces. Either is kept as-is.

buffer_tag is the only thing you actually set. Default shot_continuity, and it must match the buffer_tag on the hand-off node downstream. One tag per sequence - if you're running two concurrent shot chains, give them scene_a and scene_b rather than letting them stomp each other.

The output samples is a plain LATENT. Wire it to your VAEDecode / VHS video combine as usual; the graph after this node is unchanged from what you had before.

Install

Manager โ†’ search ComfyUI-ZeroCopy-Continuity, or:

cd ComfyUI/custom_nodes
git clone https://github.com/latentforge-dev/ComfyUI-ZeroCopy-Continuity

Restart ComfyUI and you're done - no model files, no extra Python packages beyond the torch you already run. (The README's clone line names the wrong org, latentforge/; the working repo is latentforge-dev/.)

Where it goes wrong

Order of execution is not guaranteed by your graph, and that's the trap. The Store node lives after shot 1's sampler; the Hand-off node in shot 2 doesn't depend on it, so it can't wait for it. In practice: queue shot 1, let it finish, then queue shot 2. Chain them inside one prompt and shot 2 will find an empty buffer and cold-start from the fallback image instead. Nothing warns you.

Everything after this node fails silently-ish. The store wraps its work in a try/except and logs the error to the console while still returning samples - the render completes, the shot looks fine, and the continuity hand-off quietly does nothing. When in doubt, watch the console for the stored-latent line rather than the image.

Image latents are a dead end. The store happily keeps a 4D [B, C, H, W] latent from an image model, but the hand-off's tail slice requires 5 dimensions. It stores fine, then the read side logs a warning and falls back. Latent chaining here is a video-model feature; the README's FLUX.1 mention doesn't survive contact with the code.

There is no clear button. The buffer exposes a clear() in Python, but no node calls it. Resident latents survive until you restart ComfyUI. If your VRAM headroom seems worse than it was at startup after a long session, that's part of the reason - and the docs' claim of a "16 slots per tag" ring buffer is generous; the shipped code is a plain dict keyed by tag, where storing again simply replaces the old entry.

Same class, two names. ZeroCopyLatentStore is the same object registered without the CineFlux prefix. Both appear in the menu under latent/cineflux, both behave identically, and either one satisfies the hand-off node - it only ever looks at the tag string.

Categorylatent/cineflux

Inputs (2)

NameTypeDefaultDescription
samplesLATENTOutput latent from KSampler / SamplerCustomAdvanced.
buffer_tagSTRINGshot_continuityIdentifier tag under which this latent is preserved for the next shot.

Outputs (1)

NameTypeDescription
samplesLATENTโ€”