A full 15-second MiniMax H3 clip, with generated audio, on a single RTX 3090 (24 GB) in a box with only 31 GB of system RAM.
At the time of writing, every published local H3 result we could find was 5 seconds. The one detailed RTX 4090 write-up used ~90 GB of system RAM on a 128 GB machine to do less. This repo documents how to do 15 seconds on one 24 GB card and 31 GB of RAM, and — more usefully — why the obvious approaches fail.
The whole thing came down to one launch flag.
python3 main.py --listen 0.0.0.0 --port 8188 --disable-pinned-memory --fp16-intermediates| host RAM | outcome | |
|---|---|---|
| ComfyUI defaults | 29 866 MB / 31 997 | killed by the kernel OOM-killer |
--disable-pinned-memory |
7 508 MB / 31 997 | completed, 23 min 17 s |
Same model, same 362 frames, same single GPU. Only the flag changed.
ComfyUI page-locks ("pins") up to 90 % of system RAM on Linux — comfy/model_management.py:
MAX_PINNED_MEMORY = ram * 0.90 # LinuxOn a 31 997 MB box that is a 28.8 GB ceiling. Pinned pages are unswappable and unreclaimable.
When memory gets tight the kernel cannot page them out, so its only remaining option is to kill the
process. That is why the failure is an abrupt SIGKILL rather than a slowdown — and why adding swap
alone changes nothing.
Pinning is a legitimate optimisation (page-locked memory DMAs to the GPU faster). It is simply a bad trade on a RAM-constrained box.
| Lane | Status | Doc |
|---|---|---|
| RTX 3090 + ComfyUI | ✅ Verified end-to-end, 15 s | docs/3090-comfyui.md |
| DGX Spark + diffusers | docs/dgx-spark-diffusers.md |
The 3090 lane is the recommended path. The Spark lane is documented for completeness and is explicitly
not a finished recipe: we reached it by quantising at load time with torchao rather than starting
from the pre-quantised weights, which is almost certainly the wrong order of operations. It is written
up as-is rather than cleaned up to look finished.
duration 15.083333 s
nb_frames 362 (24 fps, 832x480)
video h264
audio aac, stereo, 32 kHz, 15.075 s (generated by the model, not dubbed)
render 23 min 17 s
peak VRAM ~19.8 GB of 24 GB
new OOM-kills 0
Checked beyond "a file exists": duration and frame count from ffprobe, per-frame luma (YAVG 87–108,
i.e. not a black clip), differing frame hashes (i.e. actually moving), and audio levels
(mean −16.5 dB, peak 0.0 dB, i.e. not silence). See scripts/verify_output.sh.
Verifying content matters more than usual here: there is an open ComfyUI-MultiGPU issue where pinning a text encoder to a second GPU yields a completely black video. That failure passes any "did a file get written" check.
These were each tried and measured, not assumed.
- Adding a second GPU. ComfyUI has no tensor-parallelism for diffusion.
ComfyUI-MultiGPU'sCLIPLoaderMultiGPU device=cuda:1really does move the 15.7 GB text encoder onto the idle card (verified: 306 MiB → 15 422 MiB) — and host RAM went up, to 29 866 MB, because ComfyUI still stages a CPU copy (load device: cuda:1, offload device: cpu). Pinning is a policy; it does not care how many GPUs you own. VAEDecodeTiled. A literal no-op for H3 —comfy/ldm/minimax/vae.py:The H3 VAE already tiles internally (256 px spatial / 17-frame temporal) anddef decode_tiled(self, z, **kwargs): return self.decode(z)
comfy/sd.pysetshandles_tiling = True.- Adding swap, on its own. You cannot page out pinned memory. Swap only becomes useful after
--disable-pinned-memory— then it is a genuine safety net. --disable-smart-memory. Its own help text says it forces aggressive offload to regular RAM. It makes this failure mode strictly worse. Same for--high-ram,--reserve-vram,--cache-lru.
docker inspectlies about kernel OOM-kills. A container killed by the kernel's OOM-killer reportsExitCode=0andOOMKilled=false, then silently restarts. The truth is only insudo dmesg -T | grep -i oom-kill.- VRAM is a red herring. The 15 s run peaked lower in VRAM (18 884 MiB) than the 5 s run (23 716 MiB) and still died. More frames → larger requested decode allocation → ComfyUI evicts more weight out of VRAM and into host RAM. Watching VRAM tells you nothing about this failure.
- Sampling dominates, and it scales superlinearly. 124 frames samples at ~12 s/step; 362 frames samples at ~67 s/step. On a 362-frame clip sampling is ~95 % of wall clock (22 min of ~23 min); decode is about a minute. 2.9× the frames costs ~5.6× per step, consistent with attention being quadratic in sequence length. Extrapolate by frames superlinearly, never by a flat per-step figure. (Corrected 2026-08-04 — an earlier version of this file claimed the opposite.)
- 362 frames is not overreach.
comfy_extras/nodes_minimax_h3.pydocuments the trained range as ~124–362 frames, in steps of 17 (17n + 5). 15 s is the top of what the model was built for.
- NVFP4 on Ampere is fine.
supports_nvfp4_compute()returns false below compute capability 10.0 (a 3090 is 8.6), butpick_operations()then addsnvfp4to adisabledset and emulates it rather than refusing. Weights stay packed at their quantised size; only speed suffers. The log line to look for:Native ops: convrot_w4a4, int8_tensorwise , emulated ops: float8_e5m2, float8_e4m3fn, nvfp4, mxfp8 - Keep the NVFP4 text encoder (~15.7 GB), not the int8 one (~26 GB), if system RAM is your constraint. Native execution is not worth 10 GB of extra RAM pressure on a small box.
- Practical canvas ceiling is 1344x768 (0.98 MP;
BASE_SHORT_EDGE = 768). The official templates ship at 864x480. Everything above ~1.0 MP in the resolution table is above the model's native canvas.
docs/3090-comfyui.md the recommended lane, full recipe
docs/dgx-spark-diffusers.md the incomplete lane, documented honestly
docs/troubleshooting.md symptom → cause → fix
workflows/ API-format graphs (t2v and i2v)
scripts/verify_output.sh prove the output is real, not black/silent/static
scripts/queue.sh POST a workflow to a running ComfyUI
MIT. See LICENSE.
Model weights are governed by MiniMax's own licence (minimax-h3-community) and are not distributed here.