2026-08-03 14:10:40 +08:00
# MiniMax H3
2026-08-05 15:12:01 +08:00
> Joint video and audio generation with text, first/last keyframes, and
> mixed image/video/audio references
2026-08-03 14:10:40 +08:00
## Summary
- Vendor: MiniMaxAI
- Model: [`MiniMaxAI/MiniMax-H3` ](https://huggingface.co/MiniMaxAI/MiniMax-H3 )
- Tasks: T2VA, FL2VA, and Ref2VA
- Mode: OpenAI-compatible `/v1/videos` HTTP serving
- Maintainer: Community
MiniMax H3 is a CFG-distilled joint video/audio diffusion transformer. Its
2026-08-06 19:43:23 +08:00
checkpoint has two task-specific DiT partitions:
2026-08-03 14:10:40 +08:00
- `FL2VA` : text-to-video+audio (`t2va` ) and first-frame-to-video+audio
(`fl2va` )
2026-08-05 15:12:01 +08:00
- `Ref2VA` : up to 9 images, 3 videos, and 3 audio references in supported
image/video/audio combinations (`ref2va` ; audio-only is rejected)
2026-08-03 14:10:40 +08:00
2026-08-06 19:43:23 +08:00
One vLLM-Omni diffusion stage can load both DiTs while instantiating the
tokenizer, processor, Qwen3-VL text encoder, video VAE, and audio VAE only
once. Requests select the DiT with `extra_params.task` .
2026-09-16 16:52:35 +08:00
This single-stage recipe uses the default `model_loaded.text_encoder: true` and
`model_loaded.vae_encoder: true` . Precomputed text conditioning can replace the
local Qwen call. Setting `model_loaded.text_encoder: false` while leaving
`model_loaded.vae_encoder: true` requires precomputed text conditioning;
reference media is still encoded locally.
2026-08-03 14:10:40 +08:00
The generated MP4 contains H.264 video and synchronized stereo audio.
## Prerequisites
2026-08-06 19:43:23 +08:00
The checkpoint requires Hugging Face access approval. Authenticate once;
`vllm serve` downloads the required components automatically:
2026-08-03 14:10:40 +08:00
```bash
hf auth login
2026-08-06 19:43:23 +08:00
export MODEL = MiniMaxAI/MiniMax-H3
2026-08-03 14:10:40 +08:00
```
2026-09-16 16:52:35 +08:00
By default, the pipeline downloads `FL2VA/**` , `Ref2VA/model_index.json` , and
2026-08-06 19:43:23 +08:00
`Ref2VA/transformer/**` . It does not download or load the diffusers-format
`transformer` , `transformer_ref` , or `vae` weights at the repository root, nor
duplicate Ref2VA copies of shared components.
2026-08-06 09:02:29 +08:00
Install vLLM-Omni from the checkout containing MiniMax H3 support. The
two-GPU RTX 5090/4090 profiles use cuDNN attention and do not need
FlashAttention-4. Install the optional dependency only for the four-GPU
B300/GB200 `FLASH_ATTN` profile:
2026-08-03 14:10:40 +08:00
```bash
uv venv
source .venv/bin/activate
2026-08-06 17:38:17 +08:00
uv pip install -e .
```
To keep FA4 available as an explicit option on Blackwell, install the
FlashAttention-4 extra:
```bash
2026-08-03 14:10:40 +08:00
uv pip install -e '.[fa4]'
```
2026-08-09 10:18:24 -04:00
On AMD ROCm, install without the `[fa4]` extra (FA4 is CUDA-only) and use
`--diffusion-attention-backend FLASH_ATTN` ; see
[AMD ROCm (gfx942 / gfx950) ](#amd-rocm-gfx942--gfx950 ).
2026-08-03 14:10:40 +08:00
`ffmpeg` and `ffprobe` must be available on `PATH` . They are used for
reference-video preparation and MP4 output.
2026-08-22 12:04:14 +08:00
## CPU MP4 response encoding
For CUDA and ROCm deployments, non-streaming MP4 responses are encoded on the
host CPU through PyAV/libx264 after generation. The response encoder selects the
2026-08-31 07:04:27 +08:00
path automatically at runtime. The server-owned parallel converter accepts
supported frame shapes and dtypes with either per-channel-contiguous or strided
RGB planes, including interleaved arrays materialized by output transport.
Standalone callers without a parallel converter retain the legacy fallback for
strided planes. No CLI flag, model declaration, or user configuration is
required. Streaming fMP4 output is unchanged.
2026-08-22 12:04:14 +08:00
A community benchmark on 2x Xeon 8480C reported the following comparison
between the legacy and direct planar paths
([full result ](https://github.com/vllm-project/vllm-omni/pull/6288#issuecomment-5337546499 )):
| Metric | Legacy | Direct planar | Change |
2026-08-24 06:21:42 +08:00
| --- | ---: | ---: | ---: |
2026-08-22 12:04:14 +08:00
| Median wall time | 1.805 s | 1.394 s | -22.8% |
| Median process CPU time | 3.613 s | 3.207 s | -11.2% |
| Peak RSS | 3182 MiB | 2794 MiB | -387 MiB (-12.2%) |
Across the 1.0-8.7 s sweep, wall-time improvement was approximately 21.8-22.6%;
outputs were byte-identical and full decode passed. This is evidence for host
CPU response encoding. Actual gains depend on the CPU and runtime, and should
not be interpreted as GPU, DiT, or stage 0 speedups.
2026-08-03 14:10:40 +08:00
## Start a server
2026-08-06 19:43:23 +08:00
Pass the repository ID directly. The pipeline uses `FL2VA` for model discovery
and shared components, and loads the second DiT from `Ref2VA/transformer` .
2026-08-03 14:10:40 +08:00
2026-08-06 09:02:29 +08:00
### Memory and storage requirements
Treat GPU HBM, host RAM, and checkpoint storage as separate requirements. Each
H3 checkpoint partition (`FL2VA` or `Ref2VA` ) contains about **134 GiB** of
2026-08-06 19:43:23 +08:00
BF16 safetensors (about **135 GiB** on disk). Keeping both partitions
locally therefore needs roughly **270 GiB** of model storage. A combined
service downloads both; `--task-type fl2va` or `--task-type ref2va` downloads
only the selected partition.
2026-08-06 09:02:29 +08:00
CPU offload and distributed layerwise offload reduce GPU residency; they do
not make the model weights disappear. With `--dlo-no-use-allgather` , each
worker retains its standard-loader rank-local weights in host memory, including
pinned CPU buffers used for H2D streaming. Use at least **200 GiB available
system RAM** before starting the two-GPU recipe; a **384 GiB host is
recommended** to leave room for the OS, CUDA/PyTorch allocations, request
inputs, and filesystem cache. Do not run the FL2VA and Ref2VA servers at the
same time on a host sized for this minimum.
The consumer-GPU profiles below are HBM budgets only. They still require the
host-RAM budget above.
2026-09-06 05:40:09 +08:00
### Single GPU: blockwise capacity path
2026-08-03 14:10:40 +08:00
2026-09-06 05:40:09 +08:00
Use ordinary layerwise offload when one GPU cannot keep the Qwen3-VL encoder
and DiT resident together. The component list below streams the active DiT,
the Qwen vision blocks, and the first 50 Qwen text layers. Encoder blocks stay
rank-local; the video/audio VAEs remain resident.
2026-08-03 14:10:40 +08:00
```bash
2026-08-06 19:43:23 +08:00
export MODEL = MiniMaxAI/MiniMax-H3
2026-08-03 14:10:40 +08:00
export PORT = 8091
CUDA_VISIBLE_DEVICES = 0 \
VLLM_WORKER_MULTIPROC_METHOD = spawn \
2026-09-06 05:40:09 +08:00
VLLM_OMNI_VIDEO_SYNC_TIMEOUT = 14400 \
2026-08-03 14:10:40 +08:00
vllm serve " ${ MODEL } " \
--omni \
--host 0.0.0.0 \
--port " ${ PORT } " \
--trust-remote-code \
2026-09-06 05:40:09 +08:00
--task-type fl2va \
2026-08-03 14:10:40 +08:00
--num-gpus 1 \
2026-09-06 05:40:09 +08:00
--diffusion-offload-config \
'{"mode":"layer","components":["dit","text_encoder"]}' \
--enforce-eager \
2026-08-03 14:10:40 +08:00
--diffusion-attention-backend FLASH_ATTN
```
2026-09-06 05:40:09 +08:00
This is a capacity profile, not a latency profile: every denoising step streams
DiT blocks over the host link, while encoder blocks are streamed only during
the short conditioning phase. It requires host memory for the complete
checkpoint plus pinned transfer buffers. Re-measure peak HBM on the target
shape; this command does not claim a particular GPU model as validated.
A one-B300 correctness smoke selected only `text_encoder` (keeping the DiT and
VAEs resident) and completed a 384x672, 5-second T2VA request with two denoise
steps. It installed 77 encoder hooks across the vision and text stacks, used a
77,728 MiB worker peak, and measured 2.803 seconds encode, 2.706 seconds
denoise, and 4.503 seconds decode. These reduced-step numbers validate the
execution path; they are not a quality or production-latency benchmark.
To use whole-component behavior, change the config to
`{"mode":"module","components":["dit","text_encoder"]}` . Module
offload swaps the complete encoder and DiT and therefore has a higher
encode-phase peak than encoder blockwise offload.
2026-08-03 14:10:40 +08:00
2026-08-06 09:02:29 +08:00
### Two 24/32 GB GPUs: TP2 distributed layerwise offload
For two PCIe consumer GPUs, combine TP2 with distributed layerwise offload
(DLO). The standard loader first creates the rank-local TP shard. DLO keeps
that shard in pinned host memory and streams the 30 tail DiT blocks through a
shared two-buffer window without a DP AllGather. The first 20 DiT blocks are
copied to the GPUs once per denoise stage, reused by every sampling step, and
released before VAE decode so the decoder can reuse their HBM.
```bash
2026-08-06 19:43:23 +08:00
export MODEL = MiniMaxAI/MiniMax-H3
2026-08-06 09:02:29 +08:00
export PORT = 8091
CUDA_VISIBLE_DEVICES = 0,1 \
VLLM_WORKER_MULTIPROC_METHOD = spawn \
VLLM_OMNI_VIDEO_SYNC_TIMEOUT = 14400 \
vllm serve " ${ MODEL } " \
--omni \
--host 0.0.0.0 \
--port " ${ PORT } " \
--trust-remote-code \
2026-08-06 19:43:23 +08:00
--task-type fl2va \
2026-08-06 09:02:29 +08:00
--num-gpus 2 \
--tensor-parallel-size 2 \
--usp 1 \
--ring 1 \
--text-encoder-tp-size 2 \
--vae-patch-parallel-size 2 \
--vae-parallel-mode tile \
--vae-use-tiling \
--enable-distributed-layerwise-offload \
--dlo-no-use-allgather \
--dlo-resident-layers 20 \
--enforce-eager \
--diffusion-attention-backend CUDNN_ATTN
```
Use the profile that matches the per-GPU memory capacity:
| Profile | GPUs | Starting shape | Resident DiT blocks | Attention | Execution | Status |
2026-08-24 06:21:42 +08:00
| --- | ---: | ---: | ---: | --- | --- | --- |
2026-08-06 09:02:29 +08:00
| `rtx5090` | 2 x 32 GB | 1344x768 | 20 | cuDNN attention | eager | Target-hardware validated |
| `rtx4090` | 2 x 24 GB | 1024x576 | 12 | cuDNN attention | eager | Capacity-proxy starting point |
This topology uses all available parallel capacity: TP2 shards both the DiT
and text encoder, `--dlo-no-use-allgather` streams each rank's local TP shard
without reconstructing full blocks, and VAE patch parallelism splits tiled
decode across both GPUs. cuDNN attention is selected explicitly for the RTX
consumer path; the server stays eager to avoid an unqualified compile path.
The resident count changes placement and transfer frequency only; it does not
quantize or change the BF16/FP32 denoise math. Re-measure peak memory before
increasing it on a different request shape.
### RTX 5090 target-hardware validation
At vLLM-Omni commit `ae6577ea` , one full 50-step T2VA request completed on
2 x RTX 5090 without OOM:
2026-08-24 06:21:42 +08:00
| Shape | Frames | Client E2E | Sampled peak/GPU | Output validation |
| -------: | ------------: | ---------: | ---------------------: | -----------------------------------------------------------: |
2026-08-06 09:02:29 +08:00
| 1344x768 | 124 at 24 FPS | 8 min 38 s | approximately 22.6 GiB | H.264 video + 32 kHz stereo AAC; full `ffmpeg` decode passed |
This is a single end-to-end validation run, not a warmed multi-run latency
benchmark. The sampled `nvidia-smi` peak is also not a CUDA allocator
high-water mark. The environment used vLLM 0.26.0, vLLM-Omni
`0.26.1.dev14+gae6577ea` , and PyTorch 2.11.0+cu130. The
[run record ](https://github.com/lishunyang12/vllm-omni-rankings/blob/dcd06d7e83cb069842535918c0169ee9f3f29ba0/scripts/%E5%BE%AE%E4%BF%A1%E5%9B%BE%E7%89%87_20260805000034_86_237.png )
captures the environment, output contract, elapsed time, and sampled peak.
Before the target run, both profiles were exercised on two B300 ranks as an
allocation and correctness proxy. At 1344x768, 124 frames, and 50 steps, the
20-layer profile peaked at 27,726 MiB per rank. At 1024x576, the 12-layer
profile peaked at 18,888 MiB per rank in a 5-step capacity run. The resident
and fully streamed placements produced identical decoded video-frame and audio
hashes for the same shape, step count, prompt, and seed. The B300 result does
not establish RTX 4090 PCIe latency; treat the 4090 profile as a conservative
starting point until it is measured on that GPU.
To run T2VA, FL2VA, image+audio Ref2VA, and two-video Ref2VA in order, validate
every MP4's H.264/AAC streams, and retain live server and GPU-memory logs:
```bash
RUN_ROOT = /path/to/run-root \
MODEL_ROOT = /path/to/MiniMax-H3 \
GPU_IDS = 0,1 \
PROFILE = rtx5090 \
bash examples/offline_inference/minimax_h3/run_h3_2gpu_all_tasks.sh
```
The script selects 20 resident layers for `PROFILE=rtx5090` and 12 for
`PROFILE=rtx4090` ; `DLO_RESIDENT_LAYERS=N` overrides either default.
2026-08-06 19:43:23 +08:00
### Four GPUs: throughput-oriented combined service
2026-08-03 14:10:40 +08:00
2026-08-06 19:43:23 +08:00
For a combined service on four high-memory GPUs, use:
2026-08-03 14:10:40 +08:00
- no CPU or layerwise offload;
- Ulysses sequence parallelism degree 4;
- native tiled VAE patch parallelism degree 4;
- regional `torch.compile` for the repeated DiT blocks;
2026-08-06 17:38:17 +08:00
- dense BF16 `TRTLLM_ATTN` , with Ring and TP left at 1.
2026-08-03 14:10:40 +08:00
2026-08-06 19:43:23 +08:00
Both DiTs remain resident in this no-offload configuration. If they do not fit,
use model-level CPU offload.
2026-08-03 14:10:40 +08:00
```bash
2026-08-06 19:43:23 +08:00
export MODEL = MiniMaxAI/MiniMax-H3
2026-08-03 14:10:40 +08:00
export PORT = 8091
CUDA_VISIBLE_DEVICES = 0,1,2,3 \
VLLM_WORKER_MULTIPROC_METHOD = spawn \
VLLM_OMNI_VIDEO_SYNC_TIMEOUT = 1800 \
vllm serve " ${ MODEL } " \
--omni \
--host 0.0.0.0 \
--port " ${ PORT } " \
--trust-remote-code \
--num-gpus 4 \
--usp 4 \
--ring 1 \
--vae-patch-parallel-size 4 \
--vae-parallel-mode tile \
2026-08-06 17:38:17 +08:00
--vae-use-tiling
2026-08-03 14:10:40 +08:00
```
Do not add `--enforce-eager` to this performance configuration. The first
request includes regional compilation; warm the server once before measuring
steady-state latency. H3 is CFG-distilled, so `--cfg-parallel-size` must remain
2026-08-24 06:21:42 +08:00
`1` . The H3 VAE supports its native `tile` mode, not
2026-08-03 14:10:40 +08:00
`spatial_shard_height` or `spatial_shard_width` .
2026-08-06 17:38:17 +08:00
### Attention Backends
2026-09-02 19:42:37 +08:00
On supported datacenter Blackwell systems, MiniMax H3 defaults to dense BF16
2026-08-06 17:38:17 +08:00
`TRTLLM_ATTN` ; no attention backend flag is required. To select it explicitly,
use:
```bash
--diffusion-attention-backend TRTLLM_ATTN
```
Stable measurements with the four-GPU profile above put dense `TRTLLM_ATTN`
and FA4 within 2% of each other. `TRTLLM_ATTN` remains the datacenter Blackwell
default and enables the optional optimizations below. Confirm the server log
contains `Defaulting to diffusion attention backend TRTLLM_ATTN` before
recording measurements when using the default selection.
FA4 remains available by explicitly selecting the `FLASH_ATTN` backend:
```bash
--diffusion-attention-backend FLASH_ATTN
```
2026-09-02 19:42:37 +08:00
With the optional `[fa4]` dependency installed, `FLASH_ATTN` prefers FA4 on
Blackwell. Confirm the server log contains `Using CuTe FlashAttention-4 on
Blackwell` before recording FA4 measurements.
2026-08-06 17:38:17 +08:00
2026-09-02 19:42:37 +08:00
`TRTLLM_ATTN` additionally offers two **lossy** optimizations for the long main
DiT attention sequence: SAGE attention quantization and Skip-Softmax sparse
attention. Both work under the pure Ulysses parallelism of the profile above
(`--usp 4 --ring 1` ). The example below enables both:
- SAGE with `fp8_e4m3` Q/K; P and V are always FP8 in this kernel. B200
additionally supports `int8` Q/K, which preserves accuracy better than FP8.
- Skip-Softmax with a direct `threshold=0.05` (the calibrated
`target_sparsity` control needs ModelOpt metadata that the official H3
checkpoint does not include). Together with the cutoff below this is a
**conservative** setting: a low threshold skips only clearly negligible tiles,
and a cutoff close to `1.0` leaves a substantial dense prefix. Raise
`threshold` or lower `disabled_until_timestep` for more speedup once quality
is verified.
- `disabled_until_timestep=0.97` keeps the early high-noise steps dense. The
gate compares against the video sigma, which H3's default flow shift of 12
keeps high for much of the run: at 50 steps, `0.99` , `0.97` , and `0.95`
leave the first 6, 14, and 19 of 49 denoiser forwards dense. See the
[Skip-Softmax design ](https://github.com/vllm-project/vllm-omni/blob/main/docs/design/feature/skip_softmax.md#timestep-gating )
for how the cutoff maps to steps.
- A `per_role` entry that keeps the token refiner, a short attention path,
dense. A per-role spec does not inherit `quant` or `skip_softmax` from
`default` .
2026-08-06 17:38:17 +08:00
```bash
--diffusion-attention-config '{
"default": {
"backend": "TRTLLM_ATTN",
"quant": {
"dtype_qk": "fp8_e4m3",
"q_block_size": 1,
"k_block_size": 16
},
"skip_softmax": {
"threshold": 0.05,
"disabled_until_timestep": 0.97
}
},
"per_role": {
"minimax_h3.token_refiner": {
"backend": "TRTLLM_ATTN"
}
}
}'
```
2026-09-02 19:42:37 +08:00
Both optimizations trade fidelity for speed and their effects compound.
Compare against dense output on the same prompt and seed before adopting them.
For the full key reference, see
[Skip-Softmax ](https://github.com/vllm-project/vllm-omni/blob/main/docs/user_guide/diffusion/attention_backends/trtllm.md#skip-softmax )
2026-08-06 17:38:17 +08:00
and
2026-09-02 19:42:37 +08:00
[SAGE quantization ](https://github.com/vllm-project/vllm-omni/blob/main/docs/user_guide/diffusion/attention_backends/trtllm.md#sage-quantization )
in the TRTLLM attention guide.
2026-08-06 17:38:17 +08:00
2026-08-03 14:10:40 +08:00
### Text encoder tensor parallelism
The Qwen3-VL text encoder (~51.5 GB in BF16 for the retained 50 layers) is by
default fully resident on the DiT main rank. On multi-GPU no-offload runs that
rank becomes the peak-memory hotspot. Add `--text-encoder-tp-size N` to shard
the encoder across the first `N` DiT ranks (the encoder is implemented with
vLLM-style tensor-parallel layers and runs with distributed collectives over
its own encoder process group):
```bash
2026-08-06 19:43:23 +08:00
export MODEL = MiniMaxAI/MiniMax-H3
2026-08-03 14:10:40 +08:00
export PORT = 8091
CUDA_VISIBLE_DEVICES = 0,1,2,3 \
VLLM_WORKER_MULTIPROC_METHOD = spawn \
VLLM_OMNI_VIDEO_SYNC_TIMEOUT = 1800 \
vllm serve " ${ MODEL } " \
--omni \
--host 0.0.0.0 \
--port " ${ PORT } " \
--trust-remote-code \
--num-gpus 4 \
--usp 4 \
--ring 1 \
--text-encoder-tp-size 4 \
--vae-patch-parallel-size 4 \
--vae-parallel-mode tile \
2026-08-06 17:38:17 +08:00
--vae-use-tiling
2026-08-03 14:10:40 +08:00
```
`N` must divide the Qwen3-VL head counts (64 attention heads / 8 KV heads), so
valid values on a 4-GPU server are 1, 2, and 4 (1, 2, 4, 8 on 8 GPUs). The
encoder TP rank set is the first `N` DiT ranks; on 4 GPUs
`--text-encoder-tp-size 4` shards the encoder 4-way, dropping the DiT main
rank's no-offload peak by roughly `(N-1)/N` of the ~51.5 GB encoder while the
other ranks each gain `~51.5/N` GB. The encoder output remains identical to
the reference path within bf16 rounding: every encoder rank all-reduces the
row-parallel projections, so the full `[seq, 5120]` layer-50 hidden state is
replicated on every rank.
2026-08-06 19:43:23 +08:00
No restart is needed: `task=fl2va` routes to `FL2VA/transformer` , while
`task=ref2va` routes to
`Ref2VA/transformer` . T2VA uses the FL2VA DiT.
2026-08-03 14:10:40 +08:00
2026-08-24 21:08:53 +08:00
### Step execution and continuous batching
H3 implements the step-wise execution contract, so the scheduler can admit and
retire requests between denoise steps instead of running one request end to
end. Add the feature gate, then raise `--max-num-seqs` to co-batch:
```bash
--step-execution --max-num-seqs 4
```
Co-batched requests are packed into a single sequence that keeps one attention
document per request, so a batch costs one DiT forward. That packing requires
`--diffusion-attention-backend FLASH_ATTN` ; other backends fall back to one
forward per request. `--max-num-seqs 1` keeps the conservative single-request
step path. Cache acceleration (`--cache-backend` ) is not available in step mode.
!!! warning "Co-batching does not improve H3 throughput for large simultaneous requests"
Keep `--max-num-seqs 1` unless you specifically need scheduler-level control
(admitting and retiring requests between denoise steps). Measured on two
H100s (TP2, BF16, 672x384, 209 frames, 30 steps, 4 requests at concurrency
4 with `--request-rate inf` , i.e. all four submitted at time 0; one packed
request is 16384 rows):
| Configuration | Wall time | Mean latency | Peak memory |
|---------------|-----------|--------------|-------------|
| request mode | 174.8 s | 111.5 s | 72.4 GB |
| `--step-execution --max-num-seqs 1` | 179.0 s | 113.8 s | 72.4 GB |
| `--step-execution --max-num-seqs 4` | 182.1 s | 175.7 s | 78.3 GB |
A single H3 denoise step is a compute-bound dense GEMM over an already long
packed sequence, so fusing N requests costs N times the FLOPs and buys almost
no amortization — unlike LLM decoding, which is memory-bandwidth bound.
Going from one request per step to four cuts the per-request denoise cost
only from 1.323 s to 1.291 s (2.4%), which the step-mode bookkeeping then
spends. Mean latency degrades further because co-batched requests finish
together instead of staggered. Quantization moves the absolute numbers
without changing this: with online `int8` the same workload runs in 153.3 s
at 56.9 GB (request mode), and `--max-num-seqs 4` is still 5.0% slower than
request mode.
Two workloads outside this table are unmeasured and may behave differently:
- **Staggered arrivals (admission latency).** A single 16384-row H3 request
already saturates the GPUs, so submitting all requests at time 0 is the
one arrival pattern where co-batching cannot win: it can only bunch
completions. In request mode a new request queues behind the whole
in-flight generation (~45 s at this size); step mode admits at the next
denoise-step boundary (~1.3 s). Whether that shows up as a wall-clock
benefit under Poisson arrivals is not yet measured for H3. To reproduce,
run request mode vs `--step-execution --max-num-seqs 4` with
`diffusion_benchmark_serving.py --request-rate 0.05` (roughly one request
every 20 s) and report mean / p95 latency, which then includes queueing.
- **Small requests.** The numbers above are drawn from 672x384 / 209-frame
requests. A short clip at lower resolution packs a few thousand rows and
may not saturate the hardware; co-batching may amortize better there.
This is also unmeasured.
2026-08-04 19:35:01 +08:00
### Online FP8 quantization
2026-08-15 23:01:26 +08:00
MiniMax H3 supports online FP8 quantization of both the DiT and the Qwen3-VL
text decoder. The checkpoint remains BF16 on disk; vLLM-Omni creates FP8
weights at runtime and uses dynamic activation scaling during inference. By
default, `--quantization fp8` quantizes eligible attention and MLP linears in
the text decoder, token refiner, and main DiT blocks, as well as the DiT
condition and AdaLN projections. The Qwen vision tower, embeddings, norms,
RoPE, both VAEs, and the model's FP32 patch, timestep, and output projections
keep checkpoint precision.
To select a component, use `--diffusion-quantization-config` with
`{"transformer":{"method":"fp8"}}` for DiT-only FP8 or
`{"text_encoder":{"method":"fp8"}}` for text-decoder-only FP8. The two
entries can be combined. The shorthand below enables both components.
2026-08-04 19:35:01 +08:00
Add this option to an existing H3 server command:
```bash
--quantization fp8
```
2026-08-15 23:01:26 +08:00
#### Single 96 GB GPU, no-offload capacity check
Use the FL2VA-only partition for this capacity test. Loading the combined
service would also load the Ref2VA DiT and would test a different memory
2026-09-06 05:40:09 +08:00
budget. A no-offload capacity check should omit
`--diffusion-offload-config` and all legacy `--enable-*-offload` aliases. VAE
tiling changes decode placement but does not offload model weights to the CPU.
2026-08-15 23:01:26 +08:00
The run passes the capacity check when the server initializes, the request
finishes without CUDA OOM or Xid errors, `peak_used_mib` remains below the
card's reported `memory_total_mib` , and `ffprobe` reports H.264 video plus
32 kHz stereo AAC audio. Report the measured headroom rather than assuming
that every nominal 96 GB SKU exposes the same MiB total.
As a capacity proxy only, the same five-second case on one B300 measured a
92,946 MiB whole-device first-request peak and a 92,146 MiB worker peak. Its
encode, diffuse, decode, and client wall times were 8.664 s, 134.504 s,
6.016 s, and 151.583 s. These numbers suggest that a 96 GB card may fit, but
they are not an RTX PRO 6000 validation: kernels, allocator behavior, and
reported device capacity differ across GPUs.
2026-08-04 19:35:01 +08:00
Use `ignored_layers` to keep any otherwise eligible linear in BF16. H3
resolves the `transformer` component before constructing the DiT, so names do
not start with `transformer.` . Entries are exact runtime linear prefixes; a
parent name such as `blocks.0.attn` does not exclude its children.
Eligible names are the `attn.qkv_proj` , `attn.out_proj` , `mlp.fc1` , and
`mlp.fc2` children under `token_refiner.blocks.<0-1>` or `blocks.<0-49>` .
The other eligible names are `condition_proj` ,
`blocks.<0-49>.adaln_proj.linear` , and `final_layer.adaln_proj.linear` .
For example, keep the first main block's attention projections in BF16 with:
```bash
--diffusion-quantization-config \
'{"transformer":{"method":"fp8","ignored_layers":["blocks.0.attn.qkv_proj","blocks.0.attn.out_proj"]}}'
```
2026-08-15 23:01:26 +08:00
The structured option replaces `--quantization fp8` . Online FP8 can be used
2026-09-06 05:40:09 +08:00
with H3 layerwise offload and with either DiT DLO transfer. DiT `allgather`
uses the ordinary loader to finalize FP8 weights and scales before sharding
them across ranks. DiT `rank-local` instead retains complete loader-produced
tensors and avoids the synchronized request-wave contract. H3's TP-sharded
text encoder uses `rank-local` .
2026-08-04 19:35:01 +08:00
2026-08-09 10:18:24 -04:00
## AMD ROCm (gfx942 / gfx950)
The sections above describe the NVIDIA CUDA path. This section covers the AMD
ROCm path; use it instead of the CUDA commands when running on AMD Instinct
GPUs.
MiniMax H3 runs on AMD Instinct GPUs (gfx942 / gfx950) in BF16. Use
`--diffusion-attention-backend FLASH_ATTN` , which resolves to AITER packed varlen
attention on both architectures.
Select the device with `HIP_VISIBLE_DEVICES` (not `CUDA_VISIBLE_DEVICES` ), drop the
CUDA-only `FLASHINFER_DISABLE_VERSION_CHECK` , and install without the `[fa4]` extra
(FA4 is CUDA-only). The VAE uses AITER GroupNorm on ROCm.
Install (ROCm wheel + source vLLM-Omni):
```bash
pip install "vllm==0.26.0+rocm723" \
--extra-index-url https://wheels.vllm.ai/rocm/0.26.0/rocm723
VLLM_OMNI_TARGET_DEVICE = rocm pip install -e . --no-build-isolation
```
Prebuilt image: `vllm/vllm-omni-rocm:minimax-h3` . All tasks work out of the box:
the image bundles TorchCodec (for image+audio Ref2VA) and `ffmpeg` (for
video-reference Ref2VA).
### ROCm single GPU
Single GPU with model-level CPU offload keeps the Qwen3-VL encoder and DiT from
being co-resident:
```bash
export MODEL = " ${ MODEL_ROOT } /FL2VA"
export PORT = 8091
HIP_VISIBLE_DEVICES = 0 \
VLLM_WORKER_MULTIPROC_METHOD = spawn \
VLLM_OMNI_VIDEO_SYNC_TIMEOUT = 1800 \
vllm serve " ${ MODEL } " \
--omni --host 0.0.0.0 --port " ${ PORT } " --trust-remote-code \
--num-gpus 1 --enable-cpu-offload \
--diffusion-attention-backend FLASH_ATTN
```
2026-09-06 05:40:09 +08:00
This validated ROCm capacity recipe intentionally retains the compatibility
full-topology alias because MiniMax-H3 also stages its VAEs on that path. The
compact API in this release selects only `dit` and `text_encoder` , so replacing
the flag would change residency rather than perform a mechanical migration.
No removal deadline is assigned until the compact API offers equivalent
component coverage.
2026-08-09 10:18:24 -04:00
### ROCm four GPUs
The best-practice CUDA four-GPU configuration works on ROCm with the changes above.
It mirrors the CUDA command including Ulysses sequence parallelism, VAE patch
parallelism, and text-encoder tensor parallelism, with no CPU offload when the model
shards across the GPUs:
```bash
export MODEL = " ${ MODEL_ROOT } /FL2VA"
export PORT = 8091
HIP_VISIBLE_DEVICES = 0,1,2,3 \
VLLM_WORKER_MULTIPROC_METHOD = spawn \
VLLM_OMNI_VIDEO_SYNC_TIMEOUT = 1800 \
vllm serve " ${ MODEL } " \
--omni \
--host 0.0.0.0 \
--port " ${ PORT } " \
--trust-remote-code \
--num-gpus 4 \
--usp 4 \
--ring 1 \
--text-encoder-tp-size 4 \
--vae-patch-parallel-size 4 \
--vae-parallel-mode tile \
--vae-use-tiling \
--diffusion-attention-backend FLASH_ATTN
```
As on CUDA, H3 is CFG-distilled, so keep `--cfg-parallel-size` at 1, and the H3 VAE
supports only its native `tile` mode. `--text-encoder-tp-size` is validated on
gfx942; on gfx950 it was not exercised.
### Validated ROCm evidence
vLLM-Omni with MiniMax H3 support, BF16. gfx942 rows measured with the
`vllm/vllm-omni-rocm:minimax-h3` image; gfx950 rows measured with the
`0.26.0+rocm723` wheel (HIP 7.2).
| Workload | Configuration | Observed result |
2026-08-24 06:21:42 +08:00
| ---------- | --------------- | ----------------- |
2026-08-09 10:18:24 -04:00
| T2VA, 1344x768, 209 frames, 50 steps | 4x gfx942 (MI300X), FLASH_ATTN, USP4, text-enc TP4, VAE PP4 tile | encode 0.09 s, denoise 244.04 s, decode 4.15 s, 267.42 s client E2E; H.264 24 FPS + 32 kHz stereo AAC |
| FL2VA, 1344x768, 209 frames, 50 steps | 4x gfx942 (MI300X), FLASH_ATTN, USP4, text-enc TP4, VAE PP4 tile | encode 13.98 s, denoise 257.58 s, decode 4.11 s, 287.07 s client E2E; H.264 24 FPS + 32 kHz stereo AAC |
| T2VA, 832x480, ~4 s, 40 steps | 1x gfx950 (MI350), FLASH_ATTN, CPU offload | valid MP4 (H.264 + synced audio); ~0.73 s/denoise-step (~1.37 it/s), ~55 s client E2E incl. warmup |
gfx942 figures are the mean of three requests after one excluded warmup
(external evidence: vllm-project/recipes#732 ). gfx950 figures are
functional-correctness validations, not tuned throughput; the first request
includes lazy regional compilation. MI325X (gfx942) and other MI355X SKUs are not
listed until their own evidence is added.
2026-08-03 14:10:40 +08:00
## HTTP API examples
The following requests use the synchronous endpoint so the returned body can
be saved directly as an MP4. The asynchronous `POST /v1/videos` endpoint can
also be used when job polling is preferred.
All four tasks use 24 FPS, 50 sigma points, seed values from the validated
workloads, and the checkpoint-reference video/audio flow shifts of 12 and 3.
Decimal durations are passed through `extra_params` .
Set the endpoint once:
```bash
export API_URL = "http://127.0.0.1: ${ PORT } /v1/videos/sync"
```
### 1. T2VA: text to video and audio
2026-08-06 19:43:23 +08:00
Run this request against the combined service:
2026-08-03 14:10:40 +08:00
```bash
curl -sS -X POST " ${ API_URL } " \
-F 'prompt=In a snowy blue-purple forest, Ori carefully walks past a sleeping giant; footsteps crunch in the snow while the creature breathes and softly snorts.' \
-F 'width=1344' \
-F 'height=768' \
2026-08-05 15:12:01 +08:00
-F 'aspect_ratio=16:9' \
2026-08-03 14:10:40 +08:00
-F 'fps=24' \
-F 'num_inference_steps=50' \
-F 'flow_shift=12' \
-F 'seed=1101' \
-F 'extra_params={"task":"t2va","duration":8.7,"audio_flow_shift":3.0}' \
-o t2va.mp4
```
### 2. FL2VA: first frame to video and audio
2026-08-06 19:43:23 +08:00
Run this request against the combined service. When width and height are
2026-08-03 14:10:40 +08:00
omitted, H3 preserves the first-frame aspect ratio and uses a 768-pixel short
edge.
```bash
export FIRST_FRAME = /path/to/fl2va_first_frame.png
curl -sS -X POST " ${ API_URL } " \
-F 'prompt=A man stands beside a yellow car at night. The car drives away; he follows it with his eyes and begins singing sadly, with synchronized voice and city ambience.' \
-F 'fps=24' \
-F 'num_inference_steps=50' \
-F 'flow_shift=12' \
-F 'seed=2101' \
-F 'extra_params={"task":"fl2va","duration":8.7,"audio_flow_shift":3.0}' \
-F "input_reference=@ ${ FIRST_FRAME } ;type=image/png" \
-o fl2va.mp4
```
2026-08-05 15:12:01 +08:00
Use the same `FL2VA` partition for the official tail-keyframe forms. A single
image with `frame_indices=[-1]` conditions the last frame; two ordered images
with `frame_indices=[0,-1]` condition the first and last frames:
2026-08-03 14:10:40 +08:00
2026-08-05 15:12:01 +08:00
```bash
export LAST_FRAME = /path/to/fl2va_last_frame.png
export FIRST_FRAME = /path/to/fl2va_first_frame.png
curl -sS -X POST " ${ API_URL } " \
-F 'prompt=The subject moves naturally from the first image to the last image.' \
-F 'num_inference_steps=50' \
-F 'flow_shift=12' \
-F 'seed=2102' \
-F 'extra_params={"task":"fl2va","duration":8.7,"frame_indices":[0,-1],"audio_flow_shift":3.0}' \
-F "input_references=@ ${ FIRST_FRAME } ;type=image/png" \
-F "input_references=@ ${ LAST_FRAME } ;type=image/png" \
-o fl2va_first_last.mp4
```
### 3. Ref2VA: image-only, image/audio, or mixed references
2026-08-06 19:43:23 +08:00
Run these requests against the combined service or a Ref2VA-only service.
2026-09-29 04:12:53 +08:00
Reference images keep their original resolution, with dimensions aligned to
32 pixels. No additional request or server setting is needed.
2026-08-06 19:43:23 +08:00
Image-only Ref2VA omits `audio_reference` ; adding one or more audio references
is optional. The typed fields accept one object or an ordered JSON list.
`audio_reference` accepts an HTTP(S) URL or a `data:` URL. In one terminal,
expose the local reference assets to the serving host:
2026-08-03 14:10:40 +08:00
```bash
python -m http.server 8092 \
--bind 127.0.0.1 \
--directory /path/to/reference_assets
```
2026-08-05 15:12:01 +08:00
Then submit an image-only request from another terminal:
```bash
export REF_IMAGE = /path/to/reference_assets/ref2va_image.png
curl -sS -X POST " ${ API_URL } " \
-F 'prompt=A white cat sits on a beige couch and slowly looks toward the camera.' \
-F 'aspect_ratio=adaptive' \
-F 'short_edge=768' \
-F 'num_inference_steps=50' \
-F 'flow_shift=12' \
-F 'seed=3100' \
-F 'extra_params={"task":"ref2va","duration":8.0,"audio_flow_shift":3.0}' \
-F "input_reference=@ ${ REF_IMAGE } ;type=image/png" \
-o ref2va_image_only.mp4
```
An image-plus-audio request is:
2026-08-03 14:10:40 +08:00
```bash
export REF_IMAGE = /path/to/reference_assets/ref2va_image.png
export AUDIO_URL = http://127.0.0.1:8092/ref2va_audio.mp3
curl -sS -X POST " ${ API_URL } " \
-F 'prompt=A white cat with black mustache and eyebrow markings sits on a beige couch, lip-syncing precisely to the complete reference audio before shifting from confusion to deadpan speechlessness.' \
-F 'width=1344' \
-F 'height=768' \
-F 'fps=24' \
-F 'num_inference_steps=50' \
-F 'flow_shift=12' \
-F 'seed=3101' \
-F 'extra_params={"task":"ref2va","duration":15.0,"audio_flow_shift":3.0}' \
-F "input_reference=@ ${ REF_IMAGE } ;type=image/png" \
-F "audio_reference={\"audio_url\":\" ${ AUDIO_URL } \"}" \
-o ref2va_image_audio.mp4
```
The requested duration should cover the complete audio. If `duration` is
shorter, the reference soundtrack is truncated to the generated clip.
2026-08-05 15:12:01 +08:00
### 4. Ref2VA: video, separate audio, and mixed references
2026-08-03 14:10:40 +08:00
2026-08-06 19:43:23 +08:00
Run this request against the combined service. Repeat the
2026-08-03 14:10:40 +08:00
`input_references` multipart field once per source video. H3 consumes the
videos in form order and preserves their original soundtracks during
conditioning.
2026-09-29 04:12:53 +08:00
Small reference videos retain their original size; larger videos are scaled
down to the reference canvas. Dimensions are aligned to 32 pixels. No
additional request or server setting is needed.
2026-08-03 14:10:40 +08:00
```bash
export SUBJECT_VIDEO = /path/to/green_screen_subject.mp4
export BACKGROUND_VIDEO = /path/to/fairytale_background.mov
curl -sS -X POST " ${ API_URL } " \
-F 'prompt=Remove the green screen background of Video 1 and replace it with the fairytale environment from Video 2. Match the background motion to the character actions and relight the character to fit the scene.' \
-F 'width=1344' \
-F 'height=768' \
-F 'fps=24' \
-F 'num_inference_steps=50' \
-F 'flow_shift=12' \
-F 'seed=3101' \
-F 'extra_params={"task":"ref2va","duration":15.0,"audio_flow_shift":3.0}' \
-F "input_references=@ ${ SUBJECT_VIDEO } ;type=video/mp4" \
-F "input_references=@ ${ BACKGROUND_VIDEO } ;type=video/quicktime" \
-o ref2va_video_video.mp4
```
2026-08-05 15:12:01 +08:00
The server stores uploaded references only for the lifetime of the request and
deletes temporary files after generation. A video may use its embedded
soundtrack, a separate `audio_reference` , or both. To send a mixed multipart
request, repeat `input_references` for each image, video, or audio file; the
server classifies them by MIME type and preserves the per-type order.
Reference videos must be MP4/MOV with H.264/H.265 video, optional AAC/MP3
audio, 2– 15 seconds each, and at most 15 seconds combined. They may still be
longer than the generated clip. Use `start_time_seconds` to select a
synchronized segment; for multiple typed video references, pass one value per
video in `extra_params.start_time_seconds` .
Reference images accept JPG/JPEG, PNG, WEBP, HEIC, or HEIF up to 30 MiB. Standalone
audio references accept WAV or MP3 up to 15 MiB, with 2– 15 seconds per file and
at most 15 seconds combined.
## Official input matrix and limits
| Task | Supported references | Limits |
2026-08-24 06:21:42 +08:00
| ------ | ---------------------- | -------- |
2026-08-05 15:12:01 +08:00
| T2VA | text only | prompt must be non-empty |
| FL2VA | first image, last image, or ordered first+last images | at most 2 images; `frame_indices` is `[0]` , `[-1]` , or `[0,-1]` |
| Ref2VA | image-only, image+image, image+video, video+audio, and mixed image/video/audio | images ≤9, videos ≤3, audios ≤3, total references ≤12; audio requires a visual reference |
2026-09-20 23:44:33 +08:00
The default H3 output contract is 4– 15 seconds at 24 FPS, stereo 32 kHz audio, and a
2026-08-05 15:12:01 +08:00
32-pixel canvas multiple. T2VA requires one named output ratio from `21:9` ,
`16:9` , `4:3` , `1:1` , `3:4` , or `9:16` . FL2VA always follows the first input
image's ratio and ignores a generic `aspect_ratio` override. Ref2VA defaults to
`16:9` ; `adaptive` and SGLang's `auto` spelling are accepted aliases for that
default. `short_edge` controls the 768-pixel canvas and must be `768` .
`num_outputs_per_prompt` accepts 1– 10 and derives each output seed as
`seed + output_index` . The asynchronous endpoint returns all
outputs; the synchronous raw-MP4 endpoint returns the first output when more
than one is requested.
2026-08-03 14:10:40 +08:00
2026-09-20 23:44:33 +08:00
## Long video and driving audio
Set `extra_params.long_video=true` to opt into durations above 15 seconds.
Ref2VA long-video requests now default to latent continuation with global
temporal RoPE positions (A+B), described below. Set `long_video_mode=full`
explicitly to sample the entire target latent at once for comparison. Both modes
retain the native `17n+5` video-frame grid, `5n+2` video-latent grid, and 40 Hz
audio latents. A request for 75 seconds becomes 1,807 frames (75.292 seconds at
24 FPS). In full mode, attention cost and activation memory grow with the full
sequence length. Reference-video and reference-audio limits remain unchanged.
Other task types retain full-mode behavior. Requests are limited to 30 seconds
in full mode and 300 seconds in continuation mode; without `long_video=true` ,
the limit remains 15 seconds. The limits cover duration aliases and explicit
`num_frames` , and externally encoded requests are checked again before diffusion.
Native frame-grid alignment can round the output slightly above the requested
limit. These are request sanity bounds, not a guarantee that a given resolution
fits GPU memory. Full mode still allocates the whole sequence, and continuation
retains cumulative latents and reference inputs.
Use `audio_mode=lock_source` with one `audio_reference` to drive generation
with a soundtrack, rather than treating it as a short reference. The encoder
places that waveform in the target audio latent, crops or zero-pads its two
channels independently to the output duration, and keeps those rows clean
(timestep 1) throughout sampling. This mode does not insert an `<Audio 1>`
reference tag. It supports request and step execution; the default `native`
mode continues generating audio normally. Returned audio is the audio VAE's
reconstruction, not a byte-for-byte copy of the input recording.
Against a Ref2VA server, using the same asset-server setup as above:
```bash
curl --fail-with-body -sS -X POST " ${ API_URL } " \
-F 'prompt=<prompt.txt' \
-F 'width=960' -F 'height=544' -F 'fps=24' \
-F 'num_inference_steps=50' -F 'flow_shift=12' -F 'seed=19960422' \
-F 'extra_params={"task":"ref2va","duration":75,"long_video":true,"audio_mode":"lock_source","audio_flow_shift":3,"preencode_mp4":true,"preencode_batch_frames":17}' \
-F "input_reference=@ ${ REF_IMAGE } ;type=image/png" \
-F "audio_reference={\"audio_url\":\" ${ AUDIO_URL } \"}" \
-o long-video.mp4
```
`preencode_mp4` decodes and muxes temporal chunks on the worker, avoiding a
full decoded long video in the API process. It does not reduce the denoiser's
sequence length. For Turbo adapters, use the matching adapter's step count
and flow shift from the Turbo table below.
The [RunningHub workflow ](https://www.runninghub.cn/post/2100530217365364737/ )
uses T8's `MiniMaxH3AudioConditioningT8` , a full-length latent, and
`MiniMaxH3DualClockSamplerT8` ; its `remix_source` strength is zero, equivalent
to source locking. This differs from rolling-overlap continuation nodes.
See [T8 conditioning ](https://github.com/T8mars/comfyui-minimax-h3-audio-T8 )
and [ComfyUI H3 masking ](https://github.com/Comfy-Org/ComfyUI/blob/master/comfy/ldm/minimax/model.py ).
The workflow's custom Dasiwa weights, Sol attention, and T8-specific LoRA are
not supplied by this feature.
### Latent-tail continuation with global temporal positions (A+B)
`long_video=true` selects this mode by default for Ref2VA. It can also be selected
explicitly with `long_video_mode=continuation` . This experimental mode supports
Ref2VA request execution with uncached denoising (`quality=lossless` ); step
execution is rejected. Use `long_video_mode=full` explicitly for whole-target
sampling, including requests up to 30 seconds that require step execution.
Continuation currently rejects latent-mask editing (`video_noise_mask` or
`audio_noise_mask` with retained source regions). Full mode supports editing;
`audio_mode=lock_source` cannot be combined with audio latent-mask editing.
```json
{
"task" : "ref2va" ,
"duration" : 75 ,
"long_video" : true ,
"long_video_mode" : "continuation" ,
"continuation_window_frames" : 277 ,
"continuation_overlap_frames" : 22 ,
"audio_mode" : "lock_source" ,
"audio_flow_shift" : 3 ,
"preencode_mp4" : true ,
"preencode_batch_frames" : 17
}
```
These defaults sample seven windows for a 1,807-frame output. Each continuation
uses the preceding AV latent tail as fixed condition rows at the new target's
time origin, samples a fresh unmasked target, discards its hidden overlap, and
appends only the new suffix. The reference image and full prompt remain present
in every window. Both window and overlap use the `17n+5` frame grid; the window
must be 107..345 frames and larger than the overlap. A final window may be shorter.
Audio boundaries are rounded on the cumulative 24 FPS / 40 Hz timeline to avoid
drift; a locked driving track is sliced separately for each stereo channel.
Before RoPE evaluation, every window adds `start_frame * 40 / 24` to the temporal
position IDs of its target audio/video, reference audio/video, and latent-tail
guides. The start includes the overlap (not just the newly appended frames).
Text, static image references, spatial coordinates, and padding remain unchanged.
Offsets retain fractional precision independently of rounded audio slice indices.
For the default windows starting at frames 0, 255, 510, the added positions are
0, 425, 850. This follows the media-only global-offset convention in
[LongMedia temporal positioning ](https://github.com/vizart-vj/ComfyUI-MiniMax-H3-LongMedia/blob/main/temporal_positioning.py ).
The shifted layout is constructed once per window and then used by all denoising
steps and sequence-parallel ranks; no RoPE-frequency scaling is applied.
For a narrative with different scenes, pass `continuation_prompts` as a JSON
list containing exactly one non-empty prompt string per planned window (seven
strings for the default 75-second request). These prompts are independently
encoded with the same reference assets and selected in window order. Shot times
inside each prompt describe that local window, including its hidden overlap.
This option requires a local text encoder; external-encoder stage execution is
not supported. Media positions use one shared origin after the longest encoded
text prefix, so different prompt lengths do not shift the global AV clock.
Without this list, the request's single prompt is reused as before.
The cumulative latent is decoded once after all windows. Denoising memory is
bounded by the window, while cumulative latent storage still grows with duration.
This follows the [ComfyUI latent-tail continuation algorithm ](https://github.com/ttulttul/ComfyUI-Minimax-H3-Continuation ).
Global positions can still exceed the model's trained temporal range. This
mode does not guarantee seamless cuts or adherence to absolute shot timestamps in
a repeated prompt. For comparison, keep the model, seed, prompt, source media,
steps, shifts, and output size fixed, changing only `long_video_mode` .
For a reproducible approximately 20-second comparison, use a request-mode
Ref2VA server with caching disabled and the same `prompt.txt` and `REF_IMAGE`
for both requests. Use a single prompt without `continuation_prompts` so that
both modes receive identical text. The commands below generate native audio:
```bash
for mode in full continuation; do
curl --fail-with-body -sS --max-time 14400 " ${ API_URL } " \
-F 'prompt=<prompt.txt' \
-F "input_reference=@ ${ REF_IMAGE } ;type=image/png" \
-F 'width=960' -F 'height=544' -F 'fps=24' \
-F 'num_inference_steps=50' -F 'flow_shift=12' -F 'seed=19960422' \
-F 'quality=lossless' \
-F "extra_params={\"task\":\"ref2va\",\"duration\":20,\"long_video\":true,\"long_video_mode\":\" ${ mode } \",\"audio_flow_shift\":3,\"preencode_mp4\":true}" \
-o "h3-20s- ${ mode } .mp4" || break
done
```
Both outputs should contain 481 frames (20.042 seconds at 24 FPS).
Continuation uses windows `[0,277)` and `[255,481)` ; inspect frames 276/277
(about 11.54 seconds) for visual and audio discontinuities. Compare the complete
clips for motion, identity, prompt adherence, and sound, not just frame similarity:
the trajectories differ by design. Report the tested commit, checkpoint, hardware,
settings, and both videos with any comparison; the command alone is not quality
evidence. A matching seed does not imply matching noise over differently sized
full and windowed latents.
2026-10-04 15:26:19 +08:00
## Request-scoped sampler
H3 supports `euler` (the default) and `res_multistep` . Select the sampler per
request; it applies to both video and audio, using their separate sigma schedules.
For the HTTP examples above, set `num_inference_steps=20` and add
`"sampler":"res_multistep"` to the existing `extra_params` object. For example,
the T2VA fields become:
```bash
-F 'num_inference_steps=20' \
-F 'extra_params={"task":"t2va","duration":8.7,"audio_flow_shift":3.0,"sampler":"res_multistep"}'
```
For the offline Python API, use `extra_args` :
```python
from vllm_omni.inputs.data import OmniDiffusionSamplingParams
sampling_params = OmniDiffusionSamplingParams (
num_inference_steps = 20 ,
seed = 42 ,
extra_args = { "task" : "t2va" , "sampler" : "res_multistep" },
)
```
Both samplers use one DiT evaluation per step in request mode and step execution.
`res_multistep` retains the previous denoised prediction independently for each
request and each stream; its first and final updates use Euler. Omitting `sampler`
or selecting `euler` preserves the existing Euler update. Reducing the step count
reduces model evaluations, but quality depends on the prompt and configuration;
20 RES steps are a candidate to compare against an Euler-50 reference.
Requests using a fixed distilled `base_schedule` from a checkpoint or adapter
must keep `sampler="euler"` . RES sampling with those schedules has not been
validated and is rejected with `ValueError` .
2026-08-07 17:07:23 +08:00
## Request-scoped quality
Add one of these fields to any HTTP request above. No Cache-DiT startup option
is required; H3 installs its conservative profile when a `high` request
arrives and removes it for a `lossless` request.
```bash
-F 'quality=lossless' # Native reference path for this request.
-F 'quality=high' # H3's conservative Cache-DiT profile.
```
Omitting `quality` preserves the startup default: it uses the reference path
normally, or the server-configured profile when the server was started with
`--cache-backend cache_dit` .
2026-08-24 06:21:42 +08:00
Turbo is independent of this quality switch: `quality` selects a Cache-DiT
policy, while Turbo changes the active LoRA weights and sampling schedule. See
[LoRA ](#lora ) below.
2026-08-07 17:07:23 +08:00
The following result was measured on 4× NVIDIA H200 with SP4, text-encoder
TP4, 1344× 768, 124 frames, 24 FPS, and 50 inference steps. One full
`lossless` warmup was excluded, followed by three fixed prompt/seed pairs in
balanced switch order.
| `quality` | Median inference latency | Speedup | SSIM vs `lossless` | PSNR vs `lossless` | Expected trade-off |
2026-08-24 06:21:42 +08:00
| --- | ---: | ---: | ---: | ---: | --- |
2026-08-07 17:07:23 +08:00
| `lossless` | 85.49 s | 1.00× | 1.0000 | exact | Native reference path |
| `high` | 63.36 s | 1.35× | 0.9709 | 34.98 dB | Faster with measured same-seed deviation |
> [!NOTE]
> `high` selects a fixed Cache-DiT profile, but its cache hit rate and
> resulting latency/quality trade-off may vary by hardware, topology, and
> workload. The values above apply to this deployment and are not universal
> guarantees. `lossless` remains the exact reference path.
2026-09-24 23:30:42 +08:00
## Exact AdaLN reuse
H3 enables exact AdaLN projection reuse by default, independently of the task,
hardware, sampling schedule, quantization setting, and request `quality` .
This applies to both original H3 (including its default 50-point schedule) and
FastH3; no distilled adapter or explicit few-step ladder is required.
The first occurrence of an input runs the existing projection; later identical
time embeddings reuse its output only while the projection weights and numerical
settings remain unchanged. This does not approximate neighboring timesteps or
change attention, precision, sampling, or the generated audio/video contract.
Each DiT keeps at most 256 MiB of runtime projection outputs. If a long schedule
exceeds that budget, it retains a reusable subset and computes other entries
normally, avoiding cyclic eviction on repeated original-H3 requests. All original weights
remain loaded so new schedules and adapters can compute normally. Adapter changes,
weight reloads, and model device moves invalidate the cache; parameter versions
also guard individual entries. TP ranks coordinate hits before skipping a
projection collective. Gradient-enabled execution, compilation, custom linear
hooks, and tensors without weight version counters use the original computation.
Offload paths that replace weight storage can therefore reduce the hit rate.
The runtime cache uses the shared `ExactProjectionCache` implementation; H3 keeps
only its optional sidecar adaptation. Other models can integrate the same
[projection cache interface ](../../docs/design/module/diffusion/diffusion_model_integration.md#exact-conditioning-projection-reuse ).
This cache retains projection outputs, not offloaded weights, and does not skip
block weight prefetch.
For an A/B comparison, disable only this reuse at server startup:
```bash
--cache-config '{"minimax_h3_adaln_cache": false}'
```
### Optional offline sidecar
An offline sidecar can seed the first projection results; it is not required to
enable the default cache. Sidecars require `--enforce-eager` , native BF16 TP1
math, and the same numerical environment as the builder. With the default
compiled execution, sidecars are rejected before reading their payloads: compiled
H3 blocks bypass cached projections, so retaining those payloads would waste GPU
memory. This applies to both the main and Ref2VA sidecars. Other serving
configurations retain the default runtime-cache behavior described above.
From the repository root, for a fixed FastH3 adapter and its own four-step schedule:
```bash
PYTHONPATH = . python tools/minimax_h3/build_adaln_cache.py \
--transformer-path " ${ MODEL_ROOT } /FL2VA/transformer" \
--model-variant fl2va --mode t2va \
--fasth3-adapter " ${ FASTH3_ADAPTER } " \
--num-inference-steps 4 --flow-shift 12 --audio-flow-shift 3 \
--device cuda --output /path/to/h3-adaln.safetensors
```
Omit `--fasth3-adapter` for base weights and use the serving step count and shifts.
The builder accepts a native transformer directory with `config.json` and indexed
or single-file safetensors. It streams the required inputs and refuses to overwrite
an existing output. It does not instantiate the full DiT.
Pass the resulting local artifact at eager server startup:
```bash
--enforce-eager \
--cache-config '{"minimax_h3_adaln_cache_path": "/path/to/h3-adaln.safetensors"}'
```
Combined servers can also use `minimax_h3_ref_adaln_cache_path` for their Ref2VA
transformer. Each artifact binds the model architecture, task, schedule, shifts,
fixed adapter file, effective time-embedding/AdaLN weights, payload checksums, and
numerical environment. A missing, corrupt, incompatible, or outdated sidecar is
rejected with a warning; the model still loads every weight and computes through
the runtime path. A request with different settings similarly falls back.
Build sidecars from trusted local inputs: checksums verify identity and integrity,
not the correctness of an untrusted generator.
This implementation saves repeated projection work. It does not remove AdaLN
weights or claim a GPU memory reduction. End-to-end gains depend on the workload,
offload behavior, embedding fingerprint cost, and TP coordination overhead.
2026-08-24 06:21:42 +08:00
## LoRA
### Turbo LoRA
2026-09-06 20:23:36 +08:00
The eight Diffusers-layout LightX2V Turbo artifacts are supported. The
filename records the contract, so the server reads the step count, task family
and flow shift from it and validates each request against the artifact that is
loaded. It does not rewrite request or deploy-config sampling values: the
request must carry that artifact's own settings, listed here, or it is
rejected.
2026-08-24 06:21:42 +08:00
2026-09-06 20:23:36 +08:00
| Artifact | Task | Forwards | `num_inference_steps` | `flow_shift` | declared `alpha` |
| --- | --- | ---: | ---: | ---: | ---: |
| `minimax_h3_fl2v_turbo_4step_v0.1.safetensors` | T2VA / FL2VA | 4 | 5 | 12 | none -> 8 |
| `minimax_h3_fl2v_turbo_4step_v1.0_768p_bf16.safetensors` | T2VA / FL2VA | 4 | 5 | 6 | 128 |
| `minimax_h3_fl2v_turbo_4step_v1.1_768p_bf16.safetensors` | T2VA / FL2VA | 4 | 5 | 6 | 128 |
| `minimax_h3_fl2v_turbo_4step_v1.2_768p_bf16.safetensors` | T2VA / FL2VA | 4 | 5 | 6 | 8 |
| `minimax_h3_fl2v_turbo_8step_v1.0_bf16.safetensors` | T2VA / FL2VA | 8 | 9 | 12 | 8 |
| `minimax_h3_fl2v_turbo_8step_v1.0_768p_bf16.safetensors` | T2VA / FL2VA | 8 | 9 | 6 | 8 |
| `minimax_h3_ref2v_turbo_4step_v0.1_bf16.safetensors` | Ref2VA | 4 | 5 | 12 | 8 |
| `minimax_h3_ref2v_turbo_8step_v1.0_768p_bf16.safetensors` | Ref2VA | 8 | 9 | 6 | 8 |
2026-08-24 06:21:42 +08:00
2026-09-06 20:23:36 +08:00
`audio_flow_shift` is `3.0` across the family. Each row is the complete
published filename; use it verbatim as `TURBO_FILE` below.
Alpha needs no manual compensation: the server reads it from the artifact's
metadata, falling back to 8 with a warning for
`minimax_h3_fl2v_turbo_4step_v0.1` , the one artifact that declares none. The
request-level `scale` is a further multiplier on top of it.
> [!NOTE]
> Every artifact is rank 128 and the delta is applied at `scale * alpha /
> rank`, so the `alpha=8` rows drive at 1/16 the strength of the `alpha=128`
> rows. 8 is the default of LightX2V's reference script, which never reads the
> metadata; its documented `v0.1` command does not override that default.
Every artifact except `minimax_h3_fl2v_turbo_4step_v0.1` also ships a
`_comfyui_` export of the same weights. Those fuse Q/K/V into one projection
and are **not** supported; downloading one is refused by name. Take the
Diffusers file. The filename is the contract, so do not rename an artifact
either -- a renamed file is rejected rather than served on a guess.
FL2VA artifacts serve `t2va` and `fl2va` on any FL2VA or combined server.
Ref2VA artifacts require
`--task-type ref2va` : a combined server serves `ref2va` from a second DiT that
the adapter cannot bind to, so loading one there is refused rather than silently
running an undistilled model on the few-step schedule.
Download the artifact you want:
2026-08-24 06:21:42 +08:00
```bash
export TURBO_DIR = /path/to/minimax-h3-turbo
export TURBO_FILE = minimax_h3_fl2v_turbo_4step_v1.0_768p_bf16.safetensors
hf download lightx2v/Minimax-h3-Turbo " ${ TURBO_FILE } " --local-dir " ${ TURBO_DIR } "
export TURBO_LORA = " ${ TURBO_DIR } / ${ TURBO_FILE } "
```
2026-09-06 20:23:36 +08:00
`--lora-path` accepts one artifact, or a directory holding exactly one.
> [!IMPORTANT]
> This changes earlier behaviour. `--lora-path /path/to/minimax-h3-turbo`
> pointing at a full clone of the Turbo repository used to select the v1.0 768p
> file implicitly; a directory holding several recognized artifacts is now
> rejected as ambiguous. Name the artifact instead:
> `--lora-path /path/to/minimax-h3-turbo/minimax_h3_fl2v_turbo_4step_v1.0_768p_bf16.safetensors`.
2026-08-26 16:15:47 +08:00
Start from a non-offloaded or DLO FL2VA server command and add
2026-08-24 06:21:42 +08:00
`--task-type fl2va --lora-backend peft --lora-path "${TURBO_LORA}"` .
2026-09-06 20:23:36 +08:00
`--lora-path` preloads the adapter; each request still activates it and must
carry that artifact's sampling settings:
2026-08-24 06:21:42 +08:00
```bash
-F 'num_inference_steps=5' \
-F 'flow_shift=6' \
-F 'extra_params={"task":"t2va","duration":4.4,"audio_flow_shift":3.0}' \
-F "lora={\"name\":\"h3-turbo-v1.0\",\"path\":\" ${ TURBO_LORA } \",\"scale\":1.0}"
```
2026-09-06 20:23:36 +08:00
Switching to another FL2VA artifact means repointing `TURBO_FILE` , which moves
both `--lora-path` and the request's `lora.path` , and carrying that row's
`num_inference_steps` and `flow_shift` : `9` and `6` for `8step_v1.0_768p` , `9`
and `12` for the 544p `8step_v1.0` . A request that does not match the loaded
artifact is rejected, so a mismatch cannot silently degrade output.
The two `ref2v` rows are not served by this FL2VA command. Start a
`--task-type ref2va` server, take a request from
[Ref2VA ](#3-ref2va-image-only-imageaudio-or-mixed-references ) and override
`num_inference_steps` and `flow_shift` with that row's values; those examples
already send `audio_flow_shift=3.0` .
For FL2VA, change `task` and add `input_reference` as shown above. This
integration is dynamic-only and does not support prefusion or LoRA composition. DLO is
2026-08-26 16:15:47 +08:00
supported by keeping the request-switchable LoRA A/B buffers resident on the
accelerator while DLO streams only the base blocks; budget for this additional
fixed HBM usage. Model-level and standard layerwise offload remain unsupported.
2026-09-06 20:23:36 +08:00
The requested sigma points always number one more than the artifact's denoiser
evaluations.
2026-08-24 06:21:42 +08:00
2026-08-29 14:52:19 +08:00
### FlashGen native LoRA
The FlashGen 4-step T2VA artifact uses the native MiniMax-H3 module layout and
declares its distilled sigma schedule in safetensors metadata. It is published on
[ModelScope ](https://modelscope.cn/models/FlashGen/Minimax-H3-4step-lora-flashgen ):
```text
FlashGen/Minimax-H3-4step-lora-flashgen/minimax_h3_t2va_flashgen_4step_v1.0_768p_bf16.safetensors
```
Download only that file:
```bash
python -m pip install modelscope
export FLASHGEN_DIR = /path/to/minimax-h3-flashgen-lora
export FLASHGEN_FILE = minimax_h3_t2va_flashgen_4step_v1.0_768p_bf16.safetensors
modelscope download FlashGen/Minimax-H3-4step-lora-flashgen \
--local_dir " ${ FLASHGEN_DIR } " \
--include " ${ FLASHGEN_FILE } "
export FLASHGEN_LORA = " ${ FLASHGEN_DIR } / ${ FLASHGEN_FILE } "
```
Start from a non-offloaded or DLO FL2VA server command and add
`--task-type fl2va --lora-backend peft --lora-path "${FLASHGEN_LORA}"` .
Each request must use T2VA and the distilled interval-count contract:
```bash
-F 'num_inference_steps=4' \
-F 'extra_params={"task":"t2va","duration":5.2}' \
-F "lora={\"name\":\"h3-flashgen-v1.0\",\"path\":\" ${ FLASHGEN_LORA } \",\"scale\":1.0}"
```
This path rejects Ref2VA and checkpoints that already pin `base_schedule` in
`model_index.json` . The adapter metadata carries
`base_schedule=1.0,0.7,0.4,0.15,0.0` , so `num_inference_steps=4` means four
denoiser evaluations, not five sigma points. Request-mode generation may omit
the field and take the count from the adapter schedule; `--step-execution`
requires it explicitly, because the step scheduler reads the total step count
off the request at admission, before the adapter schedule is known.
DLO is supported in request-mode generation on the same terms as the Turbo
adapter: the request-switchable LoRA A/B buffers stay resident on the
accelerator while DLO streams only the base blocks, so budget for that
additional fixed HBM usage. The native artifact is rank 64 over 259 target
modules, and its packed `qkv_proj` and `fc1` layers reuse the full-input A
tensor per slice while B carries slice-local output rows, so the resident
footprint exceeds the on-disk payload; measure it for your parallel layout
rather than assuming the checkpoint size. Pure Ulysses replicates the adapter
on every rank, while DiT tensor parallelism shards the B buffers. Model-level
and standard layerwise offload remain unsupported, and `--step-execution`
cannot be combined with `--enable-distributed-layerwise-offload` .
To validate a deployment, post the same fixed-seed T2VA request twice with the
adapter and twice without it, then compare the four output digests. The adapter
is bound and deterministic when each pair matches internally and the two pairs
differ from each other.
2026-08-31 12:04:58 +08:00
### FastH3 adapter
[FastH3 ](https://haoailab.com/blogs/fasth3-preview/ ) is FastVideo's four-step
DMD2 student of H3-Base. It reuses H3's text encoder, VAEs, tokenizers, and
schedulers unchanged, replacing 49 denoiser evaluations with four. It is fused
into the checkpoint at load time rather than switched per request, because it
carries full-rank deltas that no LoRA layer can express.
The bundle publishes four variants, so download one and point `--lora-path` at
it; the repository root is refused rather than guessed at:
```bash
export FASTH3_DIR = /path/to/fasth3
hf download FastVideo/FastVideo-FastH3-4-step-Preview-v1-LoRA \
dense-datafree/adapter_model.safetensors --local-dir " ${ FASTH3_DIR } "
export FASTH3_LORA = " ${ FASTH3_DIR } /dense-datafree/adapter_model.safetensors"
```
Add `--task-type fl2va --lora-path "${FASTH3_LORA}"` to a non-offloaded server
command. T2VA is served by the FL2VA partition, so `--task-type fl2va` is
correct even though FastH3 preview v1 distills T2VA only. Because the adapter is
fused, `--lora-backend` does not apply and a request carrying a `lora=` field is
rejected rather than served without the adapter it asked for.
```bash
-F 'num_inference_steps=4' \
-F 'extra_params={"task":"t2va","duration":4.4}'
```
Requests must ask for `num_inference_steps=4` and `task=t2va` : the release's five
sigma points bound four denoiser evaluations, and that count is what the step
scheduler admits a request on. The server denoises on the release's own ladder,
keeping H3's per-modality shifts at the checkpoint values, so a request that
overrides `flow_shift` or `audio_flow_shift` is rejected - it would sample the
student at noise levels it was never distilled at.
Only a release that identifies itself as FastH3 is fused; any other
`fastvideo-lora-v2` adapter stays on the dynamic LoRA route. A claimed artifact
is then held to its own metadata: one that misdeclares its tensor counts or
leaves a transformer block unedited is refused at startup instead of serving
mostly base H3 weights on a four-step schedule. Offload is refused for the same
reason - `--enable-cpu-offload` , `--enable-layerwise-offload` and
`--enable-distributed-layerwise-offload` all bypass the fusion, so they fail fast.
2026-09-02 19:19:21 +08:00
The VSA variants are supported through FastVideo's external kernel. Install a
`fastvideo-kernel` build that provides the `fastvideo_kernel` Python module,
then add the following flags to the same command:
```bash
--diffusion-attention-backend FASTVIDEO_VSA \
--fastvideo-vsa-topk 64
```
FastH3 VSA applies its learned `.set_weight` compression gates to the complete
packed `[text | cond | audio | video]` document using the official H3 geometry:
text/condition/audio prefix tiles never cross segment boundaries, and target
video rows use `(4, 4, 4)` 3-D tiles (64 tokens). Prefix queries remain dense;
video queries select all prefix tiles plus the configured top-k video tiles.
Pure Ulysses sequence parallelism is supported: the learned gate follows the
same sequence-to-head all-to-all as Q/K/V before VSA runs. Ring and all-gather
SP remain unsupported because they do not present a complete packed sequence
to each block-sparse kernel rank.
The Dense / Data-Free variant does not require `fastvideo-kernel` and should be
served with a dense attention backend.
2026-08-31 12:04:58 +08:00
Measured on 8x NVIDIA B300 with USP8, VAE patch-parallel 8, `TRTLLM_ATTN` , at
1344x768, 4.4 s, seed 1101, one warmup excluded and two runs recorded:
| Adapter | Steps | End-to-end | Diffusion engine |
| --- | ---: | ---: | ---: |
| none (base H3) | 50 | 25.8 / 26.4 s | 16.22 / 16.28 s |
| FastH3 Dense | 4 (5 sigma points) | 11.7 / 11.8 s | 2.37 / 2.36 s |
The denoising speedup is 6.9x. End-to-end is 2.2x because text encoding, VAE
decoding and muxing are a fixed cost that dominates a clip this short; longer
generations move the end-to-end figure toward the denoising one. Fusing the
adapter does not measurably change startup: weight loading took 77.3 s with it
against 85.8 s without.
2026-09-17 16:41:52 +08:00
### FastH3 8-Step V2 full checkpoint
[FastH3 8-Step V2 ](https://huggingface.co/FastVideo/FastVideo-FastH3-8-Step-V2 )
ships a full Diffusers-format transformer, including learned VSA compression
gates. Serve the Hugging Face model ID or a downloaded snapshot directly:
```bash
export FASTH3_V2_MODEL = FastVideo/FastVideo-FastH3-8-Step-V2
```
Use a Hugging Face account with access to the release, and pin `--revision`
when reproducing a result. The existing component loader reads its safetensors
shards; the H3 weight loader maps names and loads Q/K/V and MLP projections into
native parameters in memory. No conversion command or second transformer
checkpoint is needed.
The transformer and text components come from the V2 release. To retain Omni's
native tiled and parallel VAE execution, the loader fetches only the video/audio
VAEs from the `MiniMaxAI/MiniMax-H3` revision pinned in V2's `provenance.json` .
Those components use the normal Hugging Face cache. No separate base-model
argument or full base-transformer download is required. Download time is a
preparation cost, separate from warmup and request latency.
Install the optional `fastvideo-kernel` build required by the
`FASTVIDEO_VSA` backend, including its H3 block-map entry point. A four-GPU
configuration is:
```bash
CUDA_VISIBLE_DEVICES = 0,1,2,3 vllm serve " ${ FASTH3_V2_MODEL } " --omni \
--host 127.0.0.1 --port 8095 --trust-remote-code --task-type fl2va \
--served-model-name FastH3-V2 \
--num-gpus 4 --usp 4 --ring 1 \
--vae-patch-parallel-size 4 --vae-parallel-mode tile --vae-use-tiling \
--diffusion-attention-backend FASTVIDEO_VSA
curl --fail-with-body -sS http://127.0.0.1:8095/v1/videos/sync \
-F 'model=FastH3-V2' \
-F 'prompt=A red fox runs through fresh snow at dawn, with fast pawsteps, winter wind and distant birds.' \
-F 'width=1344' -F 'height=768' -F 'aspect_ratio=16:9' -F 'fps=24' \
-F 'num_inference_steps=8' -F 'guidance_scale=1' -F 'flow_shift=10' \
-F 'seed=1101' \
-F 'extra_params={"task":"t2va","duration":4.4,"audio_flow_shift":3.0}' \
-o fasth3_v2.mp4
```
V2 pins video/audio shifts **10/3** , guidance **1** , and the unshifted ladder
`[0.999, 0.874, 0.749, 0.624, 0.5, 0.375, 0.25, 0.125, 0.0]` .
Use ** `num_inference_steps=8` ** in Omni: these nine nodes bound eight model
evaluations. FastVideo's `--steps 9` counts nodes instead. The existing H3 ODE
update is reused without stochastic re-noising.
The trained attention policy is **80% sparsity with 64-token video tiles** .
Omni derives the number of selected video tiles from each request's geometry;
do not supply a fixed `--fastvideo-vsa-topk` . Text/condition/audio prefix keys
remain available to every query, and prefix queries remain dense. Missing VSA
geometry or a failed H3 kernel raises an error instead of serving the student
with dense attention.
This release supports T2VA only, with local or pure Ulysses attention. Additional
LoRA adapters, Ref2VA and FL2VA conditioning are unsupported. The legacy
four-step adapter keeps its original sampling and fixed-top-k behavior.
2026-08-03 14:10:40 +08:00
## Key parameters
| Parameter | Recommended value | Notes |
2026-08-24 06:21:42 +08:00
| ----------- | ------------------- | ------- |
2026-08-07 17:07:23 +08:00
| `quality` | omitted or `lossless` | Request-level quality intent; `high` dynamically installs H3's conservative Cache-DiT profile |
| `extra_params.force_refresh_step_hint` | omitted | Optional positive 1-based denoising-step hint for an active Cache-DiT request; pair with `extra_params.force_refresh_step_policy` =`once` or `repeat` |
2026-08-06 19:43:23 +08:00
| `task` | `t2va` , `fl2va` , or `ref2va` | Passed in `extra_params` ; selects the task-specific DiT |
2026-08-03 14:10:40 +08:00
| `duration` | Workload-specific | Decimal seconds in `extra_params` ; converted to H3-compatible frame count |
| `fps` | `24` | H3 output FPS is fixed |
| `num_inference_steps` | `50` | Matches the reference accuracy workloads |
2026-10-04 15:26:19 +08:00
| `extra_params.sampler` | `euler` | Per-request `euler` or `res_multistep` for both streams; fixed distilled schedules require Euler |
2026-08-03 14:10:40 +08:00
| `flow_shift` | `12` | Video sigma shift |
| `audio_flow_shift` | `3` | Audio sigma shift, passed in `extra_params` |
| `seed` | Task-specific | Use a fixed value for reproducibility |
2026-08-05 15:12:01 +08:00
| `aspect_ratio` | Task-specific | T2VA requires a named ratio; FL2VA follows the input image; Ref2VA defaults to `16:9` |
| `short_edge` | `768` | H3 shape policy requires exactly 768 when `width` /`height` are omitted |
| `num_outputs_per_prompt` | `1` | 1– 10; async API returns every output |
| `start_time_seconds` | `0` | Reference-video segment start; use a list in `extra_params` for multiple videos |
| `width` , `height` | Multiples of 32 | Output aspect ratio must be between 1:4 and 4:1 |
2026-08-03 14:10:40 +08:00
2026-08-05 19:12:06 +08:00
## ComfyUI Frontend
Users can also use a ComfyUI frontend to interact with a hosted MiniMax-H3 service. The ComfyUI frontend can run in a separate environment or machine. Refer to [vLLM-Omni ComfyUI Integration ](../../docs/features/comfyui.md ) for details.
2026-08-03 14:10:40 +08:00
## Validated four-GPU evidence
The four-GPU recommendation was measured on four NVIDIA B300 GPUs with one
excluded warmup followed by three requests.
2026-08-24 06:21:42 +08:00
| Workload | Configuration | Observed result |
| -------------------------------------- | ------------------------------------------- | ------------------------------------ |
| FL2VA, 209 frames, 1248x768 | no offload, U4, VPP4 tile, regional compile | 86.964 s mean HTTP client latency |
2026-08-03 14:10:40 +08:00
| Two-video Ref2VA, 362 frames, 1344x768 | no offload, U4, VPP4 tile, regional compile | 784.394 s accounted model-stage mean |
These measurements describe the validated shapes rather than a general
throughput guarantee. Multi-video Ref2VA is much slower because the two
reference videos expand both the Qwen3-VL vision sequence and the packed DiT
attention sequence.
2026-08-04 19:35:01 +08:00
## Validated FP8 evidence
With eager DiT/text-encoder TP2 and VAE tiling, the 384x672, 107-frame,
10-step quality case measured LPIPS 0.1156 (limit 0.20), PSNR 23.6316 dB,
audio spectral cosine 0.9589 (minimum 0.80), and audio RMS ratio 0.9342. The
resident per-GPU peak was 68.52 GiB for BF16 and 53.51 GiB for FP8, a 22%
reduction.
2026-08-15 23:01:26 +08:00
For direct human inspection, see the external
[BF16 versus global-FP8 comparison ](https://lishunyang12.github.io/vllm-omni-rankings/scripts/minimax_h3_global_fp8_vs_bf16/ ),
which includes matched five-second T2VA, I2VA, and Ref2VA videos. The
[comparison sources ](https://github.com/lishunyang12/vllm-omni-rankings/tree/main/scripts/minimax_h3_global_fp8_vs_bf16 )
also record per-task fidelity metrics and provenance without storing generated
media in this repository.
2026-08-09 10:07:21 -05:00
## TeaCache acceleration
TeaCache reuses DiT block residuals across denoising steps when consecutive
timestep embeddings are similar. MiniMax-H3 TeaCache is currently calibrated
only for the FL2VA partition. In combined serving, FL2VA requests use TeaCache
while Ref2VA requests run uncached; Ref2VA-only serving rejects TeaCache.
TeaCache and Cache-DiT are mutually exclusive; pick one cache backend per server.
The model-specific default and examples use `rel_l1_thresh=0.17` , which
provided the best conservative speed/quality balance in the validated 107-frame
T2VA workload. Lower values
may produce few or no cache hits, while higher values can improve performance
at the cost of output quality. Validate the threshold on representative
prompts and generation settings before changing it.
### Offline (Python API)
Export the directory containing the `FL2VA` and `Ref2VA` subdirectories before
running the Python example, for example `export MODEL_ROOT=/models/MiniMax-H3` .
```python
import os
from vllm_omni import Omni
from vllm_omni.inputs.data import OmniDiffusionSamplingParams
omni = Omni (
model = os . path . join ( os . environ [ "MODEL_ROOT" ], "FL2VA" ),
cache_backend = "tea_cache" ,
cache_config = { "rel_l1_thresh" : 0.17 },
trust_remote_code = True ,
enable_cpu_offload = True ,
)
outputs = omni . generate (
"A quiet cinematic night scene with matching ambient sound." ,
OmniDiffusionSamplingParams (
height = 256 ,
width = 448 ,
num_frames = 29 ,
fps = 24 ,
num_inference_steps = 50 ,
seed = 42 ,
extra_args = {
"task" : "t2va" ,
"duration" : 4.0 ,
"aspect_ratio" : "16:9" ,
"flow_shift" : 12.0 ,
"audio_flow_shift" : 3.0 ,
},
),
)
```
### Online serving
```bash
vllm serve " ${ MODEL_ROOT } /FL2VA" \
--omni \
--trust-remote-code \
--cache-backend tea_cache \
--cache-config '{"rel_l1_thresh":0.17}'
```
2026-10-01 14:24:40 +08:00
## Latent super-resolution
H3's video VAE is a ~5B-parameter model, so the usual way to raise output
resolution -- decode, upscale in pixel space, re-encode -- pays for that VAE
twice. The community
[Minimax H3 latent upscaler ](https://huggingface.co/LBH-123-AI/Minimax_h3_latent_Upscaler )
is a learned 3D-convolutional resizer trained on H3's 24-channel latents: it
runs between the denoise loop and the VAE, so the clip is decoded once, at the
larger size. Unlike bilinear or bicubic interpolation of a latent, it does not
introduce ghosting or double-image artifacts.
The network runs one normalization below the pipeline latent: an H3 latent in
vLLM-Omni is already normalized, and the upscaler was trained on that tensor
normalized again by the VAE's `latents_mean` / `latents_std` . The stage applies
and undoes that itself, so nothing about a request changes -- but a port that
skips it inflates the latent about 5x and decodes as a magenta grid at one tile
per latent cell.
The checkpoint is optional and is not part of the H3 release. Download one file
from that repository (`minimax_h3_latent_upscaler_3d_bf16.safetensors` , 691 MB)
and name it at startup:
```bash
vllm serve " ${ MODEL_ROOT } /FL2VA" \
--omni \
--trust-remote-code \
--additional-config '{"latent_upscaler_path":"/models/minimax_h3_latent_upscaler_3d_bf16.safetensors"}'
```
A request then asks for a size. The three modes are mutually exclusive and all
resolve to the same 16-pixel latent grid, so the three requests below are the
same 960x544 -> 1920x1088 upscale:
```bash
curl -sS -X POST " ${ API_URL } " \
-F 'prompt=...' \
-F 'width=960' -F 'height=544' -F 'fps=24' \
-F 'extra_params={"task":"t2va","duration":8.7,"latent_upscale":2.0}' \
-o t2va_1080p.mp4
# or name the output size in pixels
# "latent_upscale":{"width":1920,"height":1088}
# or name a pixel budget and keep the aspect ratio
# "latent_upscale":{"megapixels":2.0}
```
Over HTTP the field is `extra_params` : the videos request model declares that
name and nothing else, so an `extra_args` form field is dropped without an
error and the request silently returns the un-upscaled size. Offline, the same
value goes in `extra_args` :
```python
omni = Omni (
model = os . path . join ( os . environ [ "MODEL_ROOT" ], "FL2VA" ),
trust_remote_code = True ,
additional_config = { "latent_upscaler_path" : "/models/minimax_h3_latent_upscaler_3d_bf16.safetensors" },
)
outputs = omni . generate (
"A quiet cinematic night scene with matching ambient sound." ,
OmniDiffusionSamplingParams (
height = 544 ,
width = 960 ,
fps = 24 ,
num_inference_steps = 50 ,
seed = 42 ,
extra_args = { "task" : "t2va" , "duration" : 8.7 , "latent_upscale" : 2.0 },
),
)
```
### The hi-res route: generate small, refine large
Upscaling alone decodes the enlarged latent directly, which is fast but can
only interpolate detail the first pass never generated. Adding `latent_refine`
resumes the denoise schedule at the new size, so the DiT puts real detail back:
1. generate at a cheap size -- far fewer DiT tokens,
2. upscale the latent,
3. re-noise it and run the tail of the schedule at the target size.
```bash
curl -sS -X POST " ${ API_URL } " \
-F 'prompt=...' \
-F 'width=960' -F 'height=544' -F 'fps=24' \
-F 'num_inference_steps=50' \
-F 'extra_params={"task":"t2va","duration":8.7,"latent_upscale":2.0,"latent_refine":0.4}' \
-o t2va_1080p.mp4
```
`latent_refine` is the fraction of the request's steps the second pass re-runs,
the img2img convention, so it sets both how much the pass may change and what
it costs: `0.4` of `num_inference_steps=50` is 50 cheap steps followed by 20
expensive ones. Low values stay close to the first pass and mostly sharpen;
high values re-generate and may drift from it. `1.0` re-runs the whole schedule
from noise, which discards the first pass entirely.
Detail does not keep climbing with strength. Measured on a 15s 2688x1536 clip,
six frames apart, against the same seed and prompt: 0.5 returned 0.98x the
high-frequency energy of 0.35 and 0.65 returned 0.92x, while the mean per-pixel
drift from the 0.35 result rose from 6.3 to 12.6 (0-255). What caps detail is
what the DiT can resolve at the target size, not how noisy a start it is given;
past that, a higher strength only erases more of the first pass and re-imagines
it from less. On this checkpoint 0.35 was the best of the three -- treat higher
values as "re-generate", not as "sharpen".
Why this is faster than generating at the target size: the DiT cost follows the
video token count, which is quadratic in the latent area for attention. The
example above is 31,620 video tokens at 960x544 against 126,480 at 1920x1088 --
4x the tokens, so roughly 4x the linear cost and up to 16x the attention cost
per step. Paying that on 20 steps instead of 50 is where the saving comes from.
The gain therefore depends on the resolutions, the strength, and how
attention-bound the shape is; measure it on your own workload.
Audio is re-noised and refined alongside the video at the same schedule
position. The two modalities are shifted apart (`flow_shift` 12.0 against
`audio_flow_shift` 3.0), so each resumes at its own sigma for that position --
the state the first pass actually passed through, which is what keeps the pass
in distribution.
`latent_refine` works without `latent_upscale` too, as a plain detail pass at
the generated size. For FL2VA, the keyframe condition is pinned to the output
latent size, so a refine after an upscale re-encodes the keyframes from the
originals at the new size; REF2VA references carry their own shapes and are
reused as they are.
### Options
| `--additional-config` key | Default | Meaning |
| --- | --- | --- |
| `latent_upscaler_path` | unset | Checkpoint file, or a directory holding exactly one. Unset disables the stage and loads no extra weights. |
| `latent_upscaler_dtype` | the engine dtype | `bf16` , `fp16` or `fp32` . |
| `latent_upscaler_chunk_frames` | `0` | `0` runs the whole clip in one pass; a positive value bounds activation memory with approximate temporal chunking. |
| `latent_upscaler_resident` | `false` | Keep the ~700 MB of weights on the GPU between requests instead of in host memory. |
| `latent_refine_max_tokens_per_rank` | `65536` | Reject larger refine layouts before denoising. Set `0` only after validating a larger deployment. |
| `latent_upscale` | unset | A default target applied to every request, in the request's own format. A request overrides it, and `false` opts out. |
| `latent_refine` | unset | A default refine strength applied to every request. A request overrides it, and `false` opts out. |
The `latent_upscale` request value is a multiplier (`2.0` ), `false` , or an
object naming one mode: `{"scale": 2.0}` , `{"width": …, "height": …}` or
`{"megapixels": …}` , each accepting an `align` (default `32` pixels). The
checkpoint was trained between 1.0x and 4.0x and only upscales; a smaller
target is rejected, and `scale` 1.0 is a no-op that skips the stage. The
`latent_refine` value is a strength in `(0, 1]` , `false` , or
`{"strength": 0.4}` ; it needs no checkpoint of its own.
When `latent_refine` is enabled, both resolved output dimensions must be
divisible by 32 pixels for the DiT's spatial patches. A custom `align` that
produces an incompatible target is rejected before denoising; upscale-only
requests can still use the VAE's 16-pixel grid. Numeric upscale fields must be
positive and finite, and `width` , `height` , and `align` must be integers.
Temporal chunking can bound activation memory on long clips -- a 15-second clip is
102 latent frames, more than three 32-frame chunks. It is an approximation rather than a
partition: the network's GroupNorms pool statistics over whatever clip they are
handed, so a chunked pass can differ from a single pass across the whole output
and not only at the chunk seams. How much that costs in practice was not
measured here; the default single pass buys the exact result for the
memory a full-length activation volume takes (10.1 GiB against 5.6 GiB at
2688x1536 for 15 seconds, measured on one B300). Set
`latent_upscaler_chunk_frames: 32` explicitly if that memory cost is too high.
2026-08-03 14:10:40 +08:00
## Known limitations
2026-08-09 10:07:21 -05:00
- TeaCache is calibrated for FL2VA only; Ref2VA requests run uncached.
2026-10-01 14:24:40 +08:00
- The latent upscaler is a community checkpoint, not part of the H3 release,
and is unavailable unless `latent_upscaler_path` names one. It runs
replicated on every DiT rank rather than sharded.
- `latent_refine` is request-mode only. It is a second denoise loop with its
own resolution and its own truncated schedule, and the step contract carries
one schedule per request, so step execution rejects it. `latent_upscale`
without `latent_refine` works in both modes.
- Large refine layouts can abort workers with `CUDA error: an illegal memory
access was encountered`. On B300, observed video token counts of 54,144 per
rank complete while 108,288 and 109,360 abort. The preflight limit of 65,536
counts the full padded layout, including text, audio, and visual references,
per Ulysses rank. It rejects the observed failing sizes before the base pass.
This is an empirical B300 guard, not a proven kernel limit on every device;
deployments with different hardware can adjust the limit after validation.
2026-08-06 19:43:23 +08:00
- Combined serving requires sibling ` FL2VA` and ` Ref2VA` directories, loads
both task-specific DiTs, and loads shared components once from ` FL2VA`.
2026-08-24 21:08:53 +08:00
- Request mode executes one generation request per diffusion batch. Use
` --step-execution` with ` --max-num-seqs N` to admit several requests at once
(see
[Execution modes](https://github.com/vllm-project/vllm-omni/blob/main/docs/user_guide/diffusion/execution_modes.md));
step
mode does not support ` cache_backend`.
2026-08-03 14:10:40 +08:00
- The first regional-compile request is a warmup and should not be included in
steady-state performance measurements.
2026-09-16 01:30:16 -07:00
- Ref2VA accepts up to 9 images, 3 video clips, and 3 audio clips, with at most
12 references in total. At least one image or video is required; audio-only
requests are rejected.
2026-08-09 10:18:24 -04:00
- The 768 px short-edge mode is available for T2VA and FL2VA; 1344x768 is the
documented 16:9 request shape.
- ` --cfg-parallel-size > 1` is rejected by design (CFG-distilled, no negative branch).
- VAE patch parallelism requires size 1 or the full DiT group size and supports the
H3 native ` tile` mode only.
2026-09-15 01:02:30 +08:00
- Ulysses x Ring hybrid attention supports H3's single-request contiguous suffix
padding. Arbitrary attention masks and multi-request packed batches remain
unsupported on the Ring path.
2026-08-18 23:48:07 +08:00
- Online FP8 with DLO AllGather temporarily materializes the complete FP8 model
in host memory on every rank during startup before retaining only each rank's
shard. Size startup host memory for that transient peak.
2026-08-09 10:07:21 -05:00
- TeaCache and Cache-DiT cannot be enabled on the same server.
2026-08-09 10:18:24 -04:00
- Pure Ulysses still replicates the full DiT on every rank, so smaller-memory GPUs
cannot use ` --usp N --tp 1` as a resident capacity path. Use DiT tensor parallelism
or model-level CPU offload; text-encoder TP alone is not sufficient.
2026-08-03 14:10:40 +08:00
## Additional resources
- [Supported models ](../../docs/models/supported_models.md )
- [Video API ](../../docs/serving/videos_api.md )
- [Diffusion parallelism ](../../docs/user_guide/diffusion/parallelism/overview.md )