Running Netflix's ID-V2V video restylizer on GPUs I actually own

Eyeline Labs (Netflix's research arm) just released ID-V2V, their SIGGRAPH Asia 2026 "shoot first, restyle later" model. You give it a source video plus one restyled keyframe, and it regenerates the whole clip in the new look while the actor's identity and performance (expressions, eye gaze, lip sync) stay locked to the original plate. The README says "tested on 8× A100-80GB." The checkpoint alone is a 78GB fp32 .pth.
I don't have eight A100s. I have a single RTX 4090 in a box with 46GB of system RAM, where that checkpoint is bigger than RAM and VRAM combined, plus a Blackwell RTX Pro 6000 I can borrow when nobody else is on it. I got the model running on both without touching the model math. Most of the work turned out to be a weight repack and memory-mapping.
What ID-V2V is
ID-V2V is a finetuned Wan2.1-14B image-to-video DiT with a VACE control adapter, both fused into one checkpoint. For restylization the control signal is the source's person segmented onto gray, with SAM3 doing the masking. For pure relighting you skip preprocessing and feed the raw source video as the control. The part of the paper I keep thinking about is how they solved training data: paired relighting footage barely exists, so they relit the face region of ordinary single videos and trained the model to invert that, and suddenly they didn't need paired footage at all. Inference generates 81-frame clips and chains them with an anti-drift scheme borrowed from Stable-Video-Infinity.
The 78GB problem
The published checkpoint is fp32, but the pipeline computes in bf16. The very first thing the loader does is call .to(torch.bfloat16) on everything, which means half of that 78GB file exists only to be discarded at load time. So I did the same cast once, offline, and saved the result. The weights that reach the GPU are bit-identical to what the stock loader produces, because it is the same cast either way. 77.8GB became 38.9GB. I keep wanting to call it a quantization, but there was nothing to calibrate and nothing to evaluate afterward, so it barely qualifies.
Converting it was its own small problem. safetensors.torch.save_file wants the whole tensor dict in memory before it writes, and the whole tensor dict is 78GB on a 46GB machine. The safetensors format turns out to be simple enough to sidestep that: an 8-byte length, a JSON header, then raw tensor bytes back to back, and shapes alone tell you every offset in advance. So my converter walks the source checkpoint one tensor at a time (cast to bf16, write, free) and peak RSS stayed around 1GB. The run took 23 minutes. Nearly all of that was the spinning disk the checkpoint lived on.
Two things surprised me at the kernel level.
First, torch.load(mmap=True) on the 78GB file failed outright with Cannot allocate memory. Linux's default overcommit heuristic refuses any single private mapping larger than RAM plus swap, and 78GB is more than 46 + 2. The pages would have been copy-on-write and never dirtied, so nothing would ever have actually committed, but the heuristic doesn't know that. I set sysctl vm.overcommit_memory=1 for the duration of the conversion and put it back after.
Second, loading the finished bf16 safetensors really is zero-copy, and you can watch it happen in /proc/self/status. After all 1,539 tensors of the 39GB file were loaded, RssAnon sat at 330MB. The tensors are backed by the mapped file; pages fault in from disk on first touch, and since they are clean file pages the kernel can evict them whenever it wants the memory back. The loader's .to(bf16) becomes a no-op that keeps the mapping intact. A 39GB model "loads" instantly on a 46GB box and still leaves room for the T5 text encoder and CLIP.
Fitting a 14B video model into 24GB of VRAM
DiffSynth-Studio, the Wan runtime ID-V2V builds on, already has layer-streaming VRAM management. A budget of persistent weights lives on the GPU and everything else shuttles in per layer. It computes that budget from total VRAM rather than free VRAM, though, and my 4090 already had 6.7GB of daemons sitting on it. I patched the reserve into an env var (VRAM_BUFFER) and gave it 14GB of headroom. During generation the card holds steady at 19.3GB used, daemons included, with no OOMs at 832×480 across 81 frames.
The cost is bandwidth. Only about 10GB of weights stay resident, so every denoising step re-streams the other 29GB from page cache, and on this box the checkpoint lives on a spinning HDD, so a good chunk of each pass comes off physical disk. I measured roughly 15.5 minutes per denoising step, and the GPU sits at 0% utilization for most of it, just waiting. A 30-step clip is an overnight job, call it 8 hours. I treat the 4090 as the validation box and nothing more.
An fp8 or int8 quant would halve the streamed bytes again, and it is on my maybe-list. Unlike the bf16 repack, though, it changes the numbers the GPU computes, and I'd want A/B renders against known outputs before trusting it.
The Blackwell port
The fast box is an RTX Pro 6000 Blackwell with 96GB, borrowed when it's idle. Blackwell is sm_120. The repo pins torch==2.6+cu118 and a flash-attn wheel compiled against exactly that pin, and none of it can even launch a kernel on this GPU. I expected an afternoon of dependency hell and got a small port instead:
torch==2.7.1from the cu128 index, the first release with sm_120 kernels- flash-attn dropped entirely, because DiffSynth's attention falls back FA3 → FA2 → SageAttention →
F.scaled_dot_product_attention, and SDPA's cuDNN backend is fine on Blackwell - xfuser dropped too, since it's only imported inside the multi-GPU sequence-parallel path and this is one card
One landmine worth writing down for the next person: the box runs someone's CUDA MPS daemon, and any new CUDA process hangs forever trying to connect to a control pipe it can't use. Pointing CUDA_MPS_PIPE_DIRECTORY at an empty directory fixes it. CUDA finds no pipe, gives up on MPS, and talks to the GPU directly.
With 96GB the entire 39GB model stays resident and nothing streams. I haven't timed a full clip there yet; the same render that takes 8 hours on the 4090 should land in tens of minutes. I also wrapped every launch in a guard script that refuses to start if anyone else's compute process is on the card, because it's a borrowed machine and I'd like to keep borrowing it.
The first render
For the first real test I ran a 2-step debug sample of the bundled relighting scene. Two denoising steps is nowhere near production quality. Even so, the render carried the keyframe's cool blue grade across all 81 frames, and both actors' faces, expressions, and poses tracked the source plate the whole way. Identity holding up at a garbage step count is what convinced me the plumbing is right.
The repo's own A/B visualization makes the case better than stills. It plays the generated video and freezes periodically to flip against the source plate:
2-step debug render, 832×480. Green pill = generated (relit), red pill = source. Identity, gaze, and expressions hold; only the light changes.
Side by side: generated | source.
If you try something like this yourself, check what dtype the runtime computes in before assuming you need real quantization, because an fp32 checkpoint feeding a bf16 pipeline means half the file is dead weight. And expect version-pinned CUDA stacks to age fast. This repo came out this month and already couldn't run on current silicon without a torch bump; the SDPA fallback chain is the only reason the port stayed in config territory.
ID-V2V is Apache-2.0 by Eyeline Labs / Netflix. Wan2.1 by Alibaba.
This post was drafted with Claude from my build notes and terminal logs. The work, the numbers, and the mistakes are mine.