Skip to content

feat(neuron): MiniMax-H3 on the PyTorch-native neuron backend (SDK 2.33) - #154

Closed
shiyang-opensource wants to merge 8 commits into
mainfrom
neuron-minimax-h3
Closed

shiyang-opensource wants to merge 8 commits into
mainfrom
neuron-minimax-h3

Conversation

@shiyang-opensource

Copy link
Copy Markdown
Collaborator

Summary

MiniMax-H3 (T2VA, 33B DiT + Qwen3-VL conditioner + video/audio VAEs) on the PyTorch-native neuron backend (TorchNeuron, torch-neuronx 2.14 / SDK 2.33). The AOT trainium backend cannot move to SDK 2.33: neuronx-cc 2.28 removed neuronxcc.nki, which NxD 0.19 / NxDI 0.10 import. With this PR H3 runs on the current SDK without NxD/NxDI.

With DIFFLET_BACKEND=neuron (or --backend neuron), difflet generate runs H3 in one torchrun launch of --tp-degree ranks:

  1. Text: Qwen3-VL's language model to hidden_states[50], TP-sharded (NeuronMiniMaxH3TextEncoderApplication; first 50 layers only, 11.7 GiB/rank at TP4). Released before the DiT loads.
  2. DiT: NeuronMiniMaxH3TransformerApplication, per-block compiled. The AdaLN modulation is precomputed on the host, so the 13B adaln_proj branch is never loaded (9.4 GiB/rank).
  3. Video VAE: one decoder per rank (NeuronMiniMaxH3VideoVaeApplication, fp32 by default). The official 256 px tiles are spread over the ranks (decode_video_on_ranks).

The audio VAE and the AV mux stay on the host (audio_vae stage). --host-text-encoder and --host-vae bring back the host text and video_vae stages.

Commits

  1. refactor(minimax-h3): the DiT TP rewrite moves to models/minimax_h3/modeling_minimax_h3_tp.py. The Trainium module re-exports every name, so its graph is built from the same objects. This is the only change to the Trainium path, plus --model-path now being forwarded to every stage on both backends.
  2. feat(neuron): the H3 DiT as a TorchNeuron application.
    • Fused SwiGLU via CheckpointSlice.
    • fp32 patch projections, timestep MLP and output heads on fp32 storage.
    • int32 masks.
  3. feat: host CPU text encoder and decoders (host_components.py), used by the --host-* paths.
  4. feat(neuron): the staged CLI on the torchrun backend.
    • The registry lists neuron for minimax_h3.
    • The c10 refusal tests now restrict H3's entry to trainium themselves, because no registered model lacks neuron any more.
  5. feat(neuron): the video VAE on a NeuronCore.
  6. feat(neuron): the Qwen3-VL conditioner on the device.
  7. feat(neuron): text, DiT and video decode in one generate launch.

Evidence (trn3.48xlarge, device 7 = cores 28-31, TP4, 256x448x124, 30 steps, seed 42)

AOT trainium (SDK 2.26 stack) neuron (this PR)
DiT step (median of 20, same device) 782.6 ms 714.7 ms
difflet generate end to end ~150 s (generate only) 109 s
text / DiT 30 steps / video decode / audio 25 / ~29 / 18 / 53 s 8.9 / 21.1 / 6.6 / 2.9 s

Trainium path unchanged: after the TP-module move, the AOT DiT was recompiled from scratch (fresh NEFF cache, 438 s) and generated from the same text.pt. The video and audio latents are bit-identical to the pre-refactor run (max_abs 0.0).

Numerics vs references:

  • DiT: tp1/tp2/tp4, eager and compile, matches diffusers' MiniMaxH3Transformer3DModel (fp32, atol 2e-5), with the host AdaLN table and with the resident branch.
  • Text encoder: tp1/2/4 matches transformers' Qwen3VLForConditionalGeneration (fp32, 2e-5). On trn3, vs the AOT/NxDI encode: rel_l2 9.8e-4 (bf16); mask and token count equal.
  • Video VAE: device fp32 vs the host fp32 decode is 80 dB per clip; the rank-spread decode is equal to the one-process decode. bf16 (--vae-dtype bf16) is 57 dB and 5x faster.
  • End to end vs AOT: same scene (frames compared). Audio mean -58.6 vs -58.0 dB. The latents are not expected to be bit-equal across compilers and kernels after 30 bf16 steps.

Not done here:

  • The audio VAE stays on the host. Device fp32 hits a neuronx-cc 2.28 internal error on BigVGAN's depthwise conv1d (NCC_ITIN902); bf16 runs but gives 13 dB SNR and no speed-up over the host's 2.9 s.
  • Top-level difflet generate has no --model-path yet; this would be a shared cli/main.py change.

Test plan

  • tests/unit/backends/test_neuron_minimax_h3_{l1,text_encoder,vae}.py (CPU/gloo, run on the trn3 host; the Mac torch 2.11 lacks all_gather_single)
  • tests/unit/cli/test_orchestrator_minimax_h3.py, tests/unit/backends/test_neuron_launch_c10.py (109 passed), registry tests
  • tests/unit/models/minimax_h3 in the Trainium lock venv (32 passed, including the re-export identity test)
  • On-device: difflet compile + difflet generate (neuron), AOT recompile bit-identity (trainium)

🤖 Generated with Claude Code

SHIYANG CHENG and others added 8 commits November 10, 2026 16:33
…ron backends

The TP rewrite (head-sharded attention, split SwiGLU, column-sharded text
embedder/norm_out/adaln_proj, the bounded-attention processor and the trace
module) only uses difflet.ops, so it moves to
difflet/models/minimax_h3/modeling_minimax_h3_tp.py. The Trainium module
re-exports every name it had, so its graph is built from the same objects.
The fused-SwiGLU key split becomes split_fused_swiglu_keys/ff_prefixes.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
NeuronMiniMaxH3TransformerApplication builds the shared H3 TP trace module on
meta, loads each rank's shards (the fused SwiGLU through CheckpointSlices, the
fp32 patch projections / timestep MLP / output heads on real fp32 storage) and
compiles the 50 blocks and the refiner blocks per block. precomputed_adaln is
on by default, so the 13B adaln_proj branch is never loaded. The masks travel
as int32; at tp1 the bounded-attention processor is installed too.

L1 tests (CPU, gloo): tp1/tp2/tp4 match diffusers' MiniMaxH3Transformer3DModel
(fp32, atol 2e-5) with the host modulation table and with the resident branch,
eager and per-block compiled.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
HostQwen3VLTextEncoder builds only the first 50 Qwen3-VL decoder layers
(no layers 50-63, final norm, lm_head or vision tower) and captures
hidden_states[50] with the Trainium text stage's tokenization and record.
load_host_video_decoder / load_host_audio_decoder build the two VAEs in fp32
with the reference attention and the folded weight norm.

On trn3 (fox prompt, vs the AOT stage outputs): text live rows rel_l2 9.9e-4
(bf16), mask and token count equal; video decode PSNR 73.4 dB.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
With DIFFLET_BACKEND=neuron the four H3 stages keep their work files: text
and both decoders run on the host as one rank, the DiT on --tp-degree torchrun
ranks with the AdaLN table built once on rank 0 under
<cache-dir>/minimax_h3_neuron/. compile only warms the DiT up. The registry
lists neuron for minimax_h3; the stage CLI forwards --model-path to every
stage on both backends. The c10 refusal tests now restrict H3's entry to
trainium themselves.

trn3 device 7, 256x448x124, 30 steps, seed 42: difflet generate end to end
242 s (text 23 s, DiT load+warmup 66 s, denoise 21.1 s = 716 ms/step,
video VAE 69 s, audio 2.6 s); same scene as the AOT output (video PSNR
22.5 dB, audio mean -58.5 vs -58.0 dB).

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
NeuronMiniMaxH3VideoVaeApplication runs the 36-block ViT decoder on one core,
one official 256 px tile per call (sequence padded to 128 with key bounds,
blocks compiled per block, block norms in fp32). The neuron video_vae stage
uses it by default (--vae-dtype fp32, or bf16); --host-vae keeps the host
decode. compile now warms up the DiT and the device decoder.

trn3 device 7, 256x448x124 (14 tiles): fp32 775 ms/tile, 11-14 s per clip,
PSNR vs the host fp32 decode 80 dB (106.9 dB end to end); bf16 130 ms/tile,
2.3 s, 57 dB. difflet generate end to end 242 s -> 144 s.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Qwen3VLTextEncoderModel (models/, backend-neutral) is Qwen3-VL's language
model to hidden_states[50]: the embedding and the first 50 layers with the
difflet.ops TP layers (GQA q/k/v without bias, per-head q/k RMSNorm,
head_dim 128, the text case of the interleaved mrope = 1-D RoPE, theta 5e6),
causal and unmasked on a right-padded prompt. NeuronMiniMaxH3TextEncoderApp
loads model.language_model.* (11.7 GiB/rank at tp4), skips the vision tower,
lm_head, the final norm and layers 50-63, and encodes with the Trainium
stage's tokenization and text.pt record.

L1 (CPU, gloo): tp1/tp2/tp4 eager and tp2 compile match transformers'
Qwen3VLForConditionalGeneration hidden_states[3] (fp32, 2e-5) on a tiny model.
trn3: hidden_states[50] vs the AOT/NxDI encode rel_l2 9.8e-4, mask equal.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…launch

On the neuron backend the generate stage now encodes the prompt on the
device (released before the DiT loads), denoises, and decodes the video with
a decoder on every rank, the 256 px tiles spread over the ranks
(decode_video_on_ranks: record the tile order, decode i % world, replay the
official blending on rank 0; equal to the one-process decode). The separate
device video_vae stage is gone; --host-text-encoder / --host-vae bring back
the host text and video_vae stages. compile warms up all three components.

trn3 device 7, 256x448x124, 30 steps: difflet generate 144 s -> 109 s
(text 8.9 s, DiT 21.1 s, video decode 14.2 s -> 6.6 s); compile 70 s warm.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The reduce-dtype coverage test exempted MiniMax-H3 by its Trainium path; the
TP rewrite now lives in models/minimax_h3/modeling_minimax_h3_tp.py and keeps
the same default on purpose (the Trainium graph is pinned bit-identical).

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant