Tag: nvfp4

  • From 118GB to 12GB: Running MiniMax H3 Through Every Precision Tier on a 16GB GPU

    From 118GB to 12GB: Running MiniMax H3 Through Every Precision Tier on a 16GB GPU

    The Question Nobody Answers Directly

    When MiniMax H3 came out, the full-precision model was 118GB (66GB UNet + 51GB text encoder). Every tutorial said “you need a big GPU” and left it there. Nobody showed what actually happens when you try to squeeze that through a 16GB consumer card — and whether the smaller quantized versions are actually worth it.

    I ran all of them on the same machine: RTX 5060 Ti 16GB, AMD Ryzen 7, 30GB RAM + 43GB swap, Ubuntu 22.04. This is the full comparison: bf16 full precision (118GB), FP8 scaled (20GB), NVFP4 (12GB), and int8. Same keyframe, same prompt, same seed, same resolution — because anything less is not a comparison.


    Tier 1: The 118GB Full-Precision Model — It Runs. Barely.

    The Download

    118GB over a residential connection is its own project. Two files, serial downloads (parallel would blow the disk):

    File Size Time at 105 MiB/s
    minimax_h3_fl2va_bf16.safetensors 66.28 GB ~8 min
    qwen3vl_32b_minimax_h3_bf16.safetensors 51.5 GB ~12 min

    Two things that matter here:

    1. hf-mirror is fully usable for the official Comfy-Org repo at 105 MiB/s — but it’s a dead source for third-party NVFP4 repos (HEAD returns 200, GET times out). Test before you trust: curl -r 0-1048575 must return 206, not 000.
    2. Verify by byte count, not “it loads.” 66280487368 and 51506295256 — exactly. A file that loads with a corrupted tail will happily fail strict validation later.

    The Reality of 118GB on 16GB

    The model doesn’t fit in VRAM. It doesn’t fit in RAM (30GB). It fits in 43GB of swap, and ComfyUI’s offload machinery shuffles everything through it.

    The ComfyUI log tells the whole story:

    Model MiniMaxH3 prepared for dynamic VRAM loading. 63208MB Staged
    MiniMaxH3TEModel_ 49118MB Staged
    MiniMaxH3VideoVAE 4965MB Staged
    

    63GB UNet + 49GB text encoder + 5GB VAE, all staged, none resident. To make this work at all on 16GB VRAM, I used:
    – KJNodes chunked inference: chunk_ffn=4, head_chunks=4
    – TE-Speed (45.6% speedup)
    – Sage Attention in auto mode
    – 43GB of swap as the overflow tank

    Results — Three Tiers of Tests

    Test Resolution Frames Sample speed Swap swings VRAM peak OOM? End-to-end
    480p short 480×832 107 ~6.5 s/it 28–50 ~11.2 GB No 425 s
    768p short 768×1344 107 ~16 s/it 65–70 ~11.2 GB No 13 min 36 s
    480p 15s 480×832 362 ~20 s/it 84–93 ~12.9 GB No 17 min 28 s

    The full 118GB model runs on a 16GB card. All three tests, zero OOM.

    But look at the pattern: the more frames, the more swap churn, the slower it gets — and it periodically spikes to 65-93 s/it when the swap pages in. A 15-second clip takes 17 minutes. That’s not a workflow, that’s an endurance test.

    Surprise: the full-precision bf16 model was actually faster at 480p (~6.5 s/it) than the int8 production model we were running (16.28 s/it). Chunking + memory strategy differences trump raw precision. Full precision is not automatically “the slow one.”


    Tier 2: The Comparison Trap That Made bf16 Look Terrible

    My first “comparison” showed bf16 scoring 2/10 vs int8’s 7.9/10. I was about to write off the full model as useless.

    Then I realized the two videos weren’t the same input — different keyframe, different prompt, one was a front-facing shot, the other a profile. The “quality gap” was content difference, not precision difference.

    The fix: strictly same source. Same keyframe, same prompt, same resolution, same seed:

    Version Details Color Fog/haze Overall
    bf16 full 3/10 4/10 7/10 3.5/10
    int8 same-source ~2/10 ~2.5/10 8.5-9/10 ~2/10

    Same source, bf16 is slightly better (roughly +50% detail). But both are visibly hazy at 480p — that’s the input + short-clip + motion-prompt combination, not a precision difference.

    The lesson that applies to every model comparison you’ll ever do: same keyframe + same prompt + same resolution + same seed, or your “benchmark” is measuring content, not quality.


    Tier 3: FP8 Scaled (20GB) vs NVFP4 (12GB) — Bigger Is Not Better

    After the bf16 experiment, I benchmarked the two practical mid-tier quantizations against each other, same methodology:

    Metric NVFP4 FP8 scaled
    Model file 12 GB 20 GB
    Staged VRAM 11,944 MB 19,983 MB
    Sample speed (steady) 60.4 s/it 66.9 s/it
    20-step estimate ~20 min ~22 min

    FP8 scaled is bigger, slower, and hungrier — and buys nothing. On 16GB it’s strictly dominated by NVFP4. The interesting data point: at 768×1344, both models run at ~60 s/it, which is 10× slower than the 480p numbers — the bottleneck at high resolution is attention + offload bandwidth, not precision.


    The Final Stack (What I Actually Run)

    After testing every tier, the production config is:

    • UNet (production): minimax_h3_fl2va_pruned_int8_convrot.safetensors (20GB, int8 — this is what the T2 workstation runs as its default; ~16.28 s/it at 480p, quality 7.9/10, rock stable)
    • Text encoder: qwen3vl_32b_minimax_h3_nvfp4_awq.safetensors (14.6GB)
    • Video VAE: official minimax_h3_video_vae_fp16 — never the third-party int8 VAE (NaN black frames)
    • Audio VAE: minimax_h3_audio_vae_fp32

    The full bf16 model is reserved for single hero shots where 17 minutes per 15 seconds is acceptable and detail matters. Everything else runs int8_convrot (20GB), which remains the production default — it is the most battle-tested config on this card. NVFP4 (12GB) was benchmarked as the theoretical sweet spot (smaller file, competitive speed), but the working production setup stayed on int8.


    What You Should Take Away

    1. 118GB full precision runs on 16GB — swap + chunked inference + sage attention make it possible, but it’s slow and only worth it for one-off quality shots.
    2. FP8 scaled is a trap on small GPUs — bigger (20GB) and slower than both NVFP4 (12GB) and int8 (20GB) with no quality win. On 16GB, the practical winners are int8 (production default) and NVFP4 (smaller footprint); FP8 sits in the worst of both worlds.
    3. Compare models only with identical inputs — same keyframe, prompt, seed, resolution. Otherwise you’re measuring content, not precision.
    4. The bottleneck at high resolution is bandwidth, not precision — at 768×1344, every quantized tier converges to ~60 s/it because offload dominates.
    5. “Bigger model = better” is dead on small GPUs — 20GB int8 and 12GB NVFP4 both beat 20GB FP8 and 118GB bf16 on the only metric that matters for batch work: wall-clock time per usable frame.

    Hardware: RTX 5060 Ti 16GB / AMD Ryzen 7 / 30GB RAM + 43GB swap / Ubuntu 22.04 / ComfyUI-H3 on :18189. All benchmarks same-source (same keyframe, prompt, seed, resolution).


    More in the MiniMax H3 on 16GB series

  • Deploying MiniMax H3 on a 16GB GPU: 5 Fatal Mistakes I Made (with Full Debugging Notes)

    Deploying MiniMax H3 on a 16GB GPU: 5 Fatal Mistakes I Made (with Full Debugging Notes)

    Why This Article Exists

    This is the story of how I deployed MiniMax H3 — Google’s joint audio-video generation model, NVFP4 quantized — on a single RTX 5060 Ti 16GB card, and burned nearly two weeks on five fatal mistakes. Three of them produced zero error messages; they just silently emitted garbage output.

    Most H3 tutorials tell you how to succeed. This one tells you exactly why you might fail — symptom, root cause, and the debugging path I actually took.

    Environment: T2 workstation, AMD Ryzen 7 + RTX 5060 Ti 16GB, Ubuntu 22.04, ComfyUI (standalone instance :18189)


    Mistake 1: Mixing a Third-Party int8 VAE with the Official UNet → All-NaN Black Frames (2 days wasted)

    Symptom

    The workflow completed. 56 frames at 512×512. The output file was 52 bytes. Pure black. Every tensor was NaN.

    Root Cause

    I downloaded a 3GB minimax_h3_video_vae_int8_convrot.safetensors thinking it was the official VAE. It was not:

    • The official Comfy-Org repository has no int8 video VAE at all
    • That file came from a third party (Gluttony10/MiniMax-H3-INT8-CONVROT), built specifically for the author’s own 47GB full INT8 UNet
    • Used standalone it worked fine (chunk tiling, 23s, no NaN). But under ComfyUI’s offload path, the quantization error blew up into NaN.

    Debugging Path

    I spent a full day hunting for an offload bug: wrote a standalone decode script, studied force_full_load, dug through sd.py / quant_ops.py. All wasted.

    The real answer was: read the official workflow template and see that the VAE was wrong.

    Lesson

    When a quantized model pipeline misbehaves, your first move is to list the official repo’s files and read the official template — not to dig into the source.

    Switching back to the official minimax_h3_video_vae_fp16.safetensors (5.2G): 107s / 56 frames / zero NaN.


    Mistake 2: Skipping KSampler and Decoding Directly → 1-Frame 2KB Garbage

    Symptom

    Workflow completed, but the output was 1 frame WEBP, 2KB, pure noise.

    Root Cause

    I assumed H3 was an “end-to-end multimodal model” that outputs pixels directly. Wrong.

    H3 is a latent-space diffusion model — its output is noisy, un-denoised latents, not pixels. You must pass them through KSampler for flow-matching denoising, then VAEDecode to get valid frames.

    My Ref2VA → VAEDecode chain decoded noise directly. Garbage in, garbage out.

    The Mental Model That Would Have Saved Me

    Node Why it exists
    MiniMaxH3ReferenceToVideo Encodes reference image/video/audio into DiT conditioning tokens
    UNETLoader + CLIPLoader H3 DiT is a standalone model file; Qwen3-VL is a standalone text encoder
    SigmaShift Flow matching needs video/audio shift parameters for noise scheduling
    KSampler Core: denoises in latent space — latent → denoised latent
    VAEDecode Unavoidable: denoised latent → pixel video

    Analogy: Stable Diffusion 3 is also “multimodal” (image + text) but still needs KSampler + VAEDecode. Every latent diffusion model works this way.


    Mistake 3: The Audio Track Was Silently Discarded — Generated Every Run, Never Decoded (Most Insidious)

    Symptom

    Generated videos had no sound, even though the audio VAE was clearly loaded. No error whatsoever.

    Root Cause

    This is the most H3-specific trap. H3 is a joint audio-video model — every KSampler pass denoises two streams at once:

    • video latent [B, 24, latent_t, H//16, W//16]
    • audio latent [B, 32, 2, T40] (AUDIO_LATENT_FPS = 40)

    EmptyMiniMaxH3LatentAV returns NestedTensor((video, audio)). But VAEDecode only takes unbind()[0] (video). The audio latent is silently dropped — it was generated every single run, never decoded.

    Fix

    Add a second branch and merge with CreateVideo:

    KSampler ─┬─> VAEDecode      (video_vae fp16) ─> IMAGE ─┐
              └─> VAEDecodeAudio (audio_vae fp32) ─> AUDIO ─┴─> CreateVideo(fps=24) ─> SaveVideo(mp4)
    

    CreateVideo has a built-in optional audio input — no ffmpeg merging needed. Audio and video come from the same sampling pass, so they’re frame-aligned by construction.

    How to Verify Audio Isn’t Silence

    A .mp4 containing an aac stream ≠ has sound (a fully silent stream is still a valid track). Measure the level:

    ffmpeg -hide_banner -i out.mp4 -map 0:a -af volumedetect -f null /dev/null 2>&1 | grep volume
    # Expected: mean_volume around -20 to -30 dB
    # Silence:  mean_volume: -91.0 dB
    

    Don’t use -v error — volumedetect output is info-level and gets swallowed, so grep finds nothing and you wrongly conclude “no audio track.”


    Mistake 4: Encoding at 12fps → Playback at Half Speed (Looks Normal, So It’s Hard to Catch)

    Symptom

    Video rendered “fine,” but motion was half speed. No error. Everything looks correct.

    Root Cause

    H3’s native framerate FPS = 24 is hardcoded in nodes_minimax_h3.py. I encoded at 12fps, which doubled the duration — the result looks like slow motion, with no error anywhere.

    Fix

    Always encode at 24fps. For new work, prefer h3av-cli.py which outputs mp4 directly with fps=24 and an audio track built in.


    Mistake 5: Estimating OOM by Summing Model Sizes — Completely Wrong (with Real Benchmark)

    My Flawed Reasoning

    “UNet 12G + Qwen3-VL 14.6G + VAE 5.2G = 32GB of models. A 16GB card will obviously OOM.”

    Completely wrong.

    Reality

    ComfyUI loads models in phases: CLIP encode → unload CLIP → UNet sampling → unload UNet → VAE decode with only the VAE in VRAM. 32GB of models peaked at 15.1GB on a 16GB card.

    Canvas Frames Sample speed End-to-end VRAM peak Note
    512×512 56 2.0 s/it 75 s 15050 MiB smoke test
    768×1344 (native) 124 60 s/it 1364 s (23 min) 9900 MiB production

    Two counterintuitive conclusions:

    1. Time grows superlinearly: pixels grew only 12×, but sampling slowed 30× (2s/it → 60s/it). On larger canvases, attention is quadratic and offload starts thrashing PCIe — the bottleneck shifts from compute to bandwidth. Estimate time as 60 s/step × steps, never extrapolate from small canvases.

    2. The large canvas actually peaks LOWER (9.9G < 15.1G) — it triggers more aggressive offload, so the model is no longer fully resident. “Small works, large must OOM” is false — the real risk is time, not VRAM.

    Verification

    Don’t reason about it — run once and read nvidia-smi peak. Cheaper than any prediction.


    Bonus Trap: Downloader-Injected Garbage Tail (The 60-70 Byte Trap)

    After downloading from Hugging Face, 3 of my 5 model files had an ASCII marker appended by the downloader:

    \nL2P_bypass_<filename>_<unix timestamp>\n     ← 60-70 bytes
    

    This makes safetensors strict header validation fail (it requires exact file length), but ComfyUI’s lenient read path doesn’t care — so the model “runs, but fails validation,” and you’ll wrongly re-download it.

    Fix: truncate off the tail; data is intact. BUT — if the tail is \x00 zero-padding instead of an ASCII marker, that’s genuine truncation (interrupted download) and you need to re-download.

    Distinguish by tail content: ASCII marker = truncate-and-fix; zero padding = genuinely missing data.


    Summary: If You Want to Run H3 on a Small 16GB Card

    1. Read the official workflow template first to confirm the model combo — never mix third-party quantized models with official ones
    2. KSampler is not optional — H3 outputs noisy latents, not pixels
    3. Add the audio branch yourself — VAEDecode only decodes video; the audio latent is generated every run but never decoded
    4. Always use 24fps — H3’s native framerate is hardcoded
    5. Don’t estimate OOM by summing model sizes — phased loading is the truth; run once and watch the peak
    6. Verify model integrity: full safetensors read + NaN/all-zero scan after download, never trust file size

    If this was useful, I’m writing follow-ups on running Wan2.2 Animate 14B motion transfer on 16GB and production AI manhua pipeline pitfalls.


    Based on real deployment on a T2 workstation. Hardware: RTX 5060 Ti 16GB / AMD Ryzen 7 / Ubuntu 22.04.