Skip to content

[Diffusion] MiniMax-H3 fuse the video VAE decoder's RMSNorm and QK RoPE - #41906

Merged
mickqian merged 8 commits into
mainfrom
h3-vae-fused-norm-kernel
Oct 5, 2026
Merged

mickqian merged 8 commits into
mainfrom
h3-vae-fused-norm-kernel

Conversation

@niehen6174

@niehen6174 niehen6174 commented Sep 30, 2026 •

Copy link
Copy Markdown
Collaborator

Motivation

Each block of the H3 video VAE decoder runs eager RMSNorm, a scaled residual add, and per-head Q/K RMSNorm plus partial NeoX RoPE. The decoder is a 36-block ViT and runs on every H3 request. Decode is still one tile per call.

Modifications

  • h3_vae_rmsnorm, h3_vae_scale_add_rmsnorm, h3_vae_qk_rmsnorm_rope (kernels/ops/diffusion/norm/h3_vae_fused_norm.py): FP32 RMSNorm, the scaled residual add fused into the next RMSNorm, and affine-free Q/K RMSNorm plus partial NeoX RoPE (head 64 / rotary 48). A contract mismatch stays on the eager path. MINIMAX_H3_VAE_DECODER_FUSED_NORM=1 by default.
  • The decoder broadcasts one rotary table across the tiles in a batch, so the Q/K kernel also runs for B > 1.

Accuracy

Fused matches eager to a max absolute error of about 5e-5 on a 28-tile output. test_h3_vae_fused_norm.py checks each kernel. test_fused_decoder_matches_unfused_forward checks the decoder and that the fused kernels actually ran.

Speed

One H200, fp16 autocast, released decoder shape, one tile per call. A 1344×768, 10 s, 24 fps clip is 14 temporal units, 28 tiles each. The unit row is measured. The 10 s row is that unit times 14: same decoder, no extra run.

main This PR Change
One temporal unit 656.3 ms 589.5 ms -66.8 ms (-10.2%)
10 s VAE decode, 1×H200 9.19 s 8.25 s -0.94 s (-10.2%)

4×H200, FastH3 4-step, ulysses 4, VSA sparsity 0.9. One request is 1344×768, 10.125 s, 24 fps, 5 sampling steps, seed 1001. Each number is the mean of 8 requests after one warmup. The decoding stage includes the audio VAE.

main This PR Change
End to end 11.00 s 10.75 s -0.25 s (-2.3%)
VAE decode 2.50 s 2.27 s -0.23 s (-9.4%)

Checklist


CI States

Latest PR Test (Base): 🚫 Run #37256954420
Latest PR Test (Extra): ❌ Run #37256954215
Latest PR Test (AMD ROCm 10): ❌ Run #37256954368

Fuse the FP32 residual add with the following RMSNorm, and fuse affine-free Q/K RMSNorm with partial NeoX RoPE, so the decoder can drop the extra eager launches.
Check FP32 RMSNorm, the rounded scale-add residual, and partial NeoX Q/K RoPE so the fused launches stay aligned with the unfused decoder.
Stacked tiles share coordinates, so replay a single rotary cache per tile and keep the single-sequence NeoX kernel on the batched path.
Run the head-64 norm and the first 48 rotary channels in one kernel when the stacked tiles share one rotary table.
Keep the FP32 residual in registers for the following norm, and check the fused decoder still matches the eager forward.
@mickqian

Copy link
Copy Markdown
Collaborator

/tag-and-rerun-ci

@github-actions github-actions Bot added the run-ci CI: run the baseline test suite on this PR label Sep 30, 2026
@niehen6174

Copy link
Copy Markdown
Collaborator Author

/rerun-failed-ci

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

diffusion SGLang Diffusion jit-kernel run-ci CI: run the baseline test suite on this PR

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants