feat(minimax-h3): vLLM-Omni rollout backend for H3 FlowGRPO at 32-GPU scale - #378
Open
nussejzz wants to merge 13 commits into
Open
feat(minimax-h3): vLLM-Omni rollout backend for H3 FlowGRPO at 32-GPU scale#378nussejzz wants to merge 13 commits into
nussejzz wants to merge 13 commits into
Conversation
nussejzz
requested review from
CjhHa1,
Ideny42,
Jayce-Ping,
KemingWu,
Zcchill,
celve,
haonan3,
leviking98z-rgb and
zzhuoxin1508
as code owners
August 19, 2026 05:08
nussejzz
marked this pull request as draft
August 19, 2026 05:09
nussejzz
force-pushed
the
feat/minimax-h3-vllmomni-32gpu
branch
2 times, most recently
from
August 19, 2026 07:36
27d0e9a to
b6d01ce
Compare
2 tasks
nussejzz
force-pushed
the
feat/minimax-h3-vllmomni-32gpu
branch
from
August 26, 2026 09:13
eb4ad91 to
12e9c1a
Compare
nussejzz
marked this pull request as ready for review
August 26, 2026 09:27
Draft
2 tasks
10 tasks
Add grouped H3 rollout, staged LoRA publication, row-level reward dispatch, and FSDP/HSDP recipes so collocated training stays within memory while using all reward and training ranks. Co-authored-by: Cursor <cursoragent@cursor.com>
Move the CUDA stack to cu130, pin the rebased upstream integration, and replace legacy stage YAML boot with current direct diffusion-stage arguments while retaining grouped LoRA and row-level reward behavior. Signed-off-by: DingZuhao <e1583181@u.nus.edu> Co-authored-by: Cursor <cursoragent@cursor.com>
Allow the shared package range to cover both stacks while pinning SGLang to 5.6 and current vLLM-Omni to 5.10+, preserving independent solvability. Signed-off-by: DingZuhao <e1583181@u.nus.edu> Co-authored-by: Cursor <cursoragent@cursor.com>
Update the temporary source pin after replaying the upstream dependency PR onto the latest main branch. Signed-off-by: DingZuhao <e1583181@u.nus.edu> Co-authored-by: Cursor <cursoragent@cursor.com>
Apply current import formatting and collapse legacy multiline docstrings after rebasing the trainside model package onto the latest lint rules. Signed-off-by: DingZuhao <e1583181@u.nus.edu> Co-authored-by: Cursor <cursoragent@cursor.com>
Move the checkpoint, packing, replay, conditioner, and mixed-dtype invariants out of multiline docstrings into the model README. Signed-off-by: DingZuhao <e1583181@u.nus.edu> Co-authored-by: Cursor <cursoragent@cursor.com>
Target vLLM 0.27 and Torch 2.13 cu130, consume the public unique-reply RPC API, make the qualified four-GPU topology explicit, and restore VAE tile-parallel setup for the custom pipeline path. Signed-off-by: DingZuhao <e1583181@u.nus.edu> Co-authored-by: Cursor <cursoragent@cursor.com>
…t layout add_lora() only registers CPU-side logical weights, so a name or shape mismatch between the Diffusers adapter and the fused vllm DiT is silent: the engine keeps serving base weights and the only symptom is a reward curve that never moves. Translate the H3 adapter into the serving layout in one place (transfer/minimax_h3_lora.py: blocks/out_proj/mlp renames plus the GEGLU half-swap that Diffusers stores as [up, gate] and serving consumes as [gate, up]), require every expected block/slice to carry both A and B, and compare per-layer checksums against the awake engine rather than trusting the load call. Checksum collection now replies from a single rank instead of every TP rank. Co-authored-by: Cursor <cursoragent@cursor.com>
Four defects that only surface at 32-GPU scale with heterogeneous prompts: - Global CUDA autocast ran the fp32-pinned projections, timestep MLP and rope in bf16 despite their storage dtype. H3 conditions on t = 1 - sigma, where bf16 spacing near 1.0 is ~0.008, so this was a real precision loss on the low-noise end. Disable autocast around exactly those modules. - The output adapter concatenated per-request prompt embeddings directly, which raises on any batch whose prompts tokenize to different lengths (hit at 32 prompts spanning 9-58 tokens). Pad to the batch maximum, carry an attention mask, and trim back to the true length before the packed forward, so the DiT still sees varlen input and never attends to padding. - SDE exploration keys were derived from the engine request id, which contains a fresh UUID per attempt, so a retried or resumed sample explored a different trajectory than the one being replayed. Derive them from the logical sample id instead. - Sparse GRPO replay stores only SDE boundaries, so decode could be handed a segment without the terminal latent. Always store it. Geometry now requires an explicit sampler_kwargs.allow_nonstandard_canvas opt-in for anything off the released 768-short-edge distribution, and the FlowGRPO parity gate reports per-step log-prob, transition-mean and velocity-RMS drift so a cross-engine mismatch is attributable rather than a single scalar. Co-authored-by: Cursor <cursoragent@cursor.com>
…ewards VideoPickScore scored a single frame, which cannot separate a clip that is good throughout from one that is good only where it happened to sample. Add uniform multi-frame scoring with either a plain mean or a top-k/mean blend, so a recipe can choose how much of the score comes from the best moments. The T2AV composite previously let an inner scorer fail and still contributed its zero to the weighted sum without renormalizing, which silently trains against a fraction of the intended reward. Propagate inner failures and non-finite scores instead, and forward the shared frame-selection, CLAP id and ImageBind checkpoint settings so a reward can be pinned to a local path rather than resolved relative to the worker cwd. Co-authored-by: Cursor <cursoragent@cursor.com>
…rantees Recipes for the topology that passed the capacity, LoRA-sync, parity and throughput gates: trainer HSDP shard8 x replicate4 over 32 GPUs, eight four-GPU rollout replicas at DiT TP2 x Ulysses2 with text-encoder TP4, and a resident reward service at DP32, all time-sharing the same GPUs. The _tp4 and _hsdp4 variants exist so the comparison stays reproducible; TP2 x UP2 is the fastest verified arm (91.1s vs 112.7s median generate at K8), not a proven optimum. README states what is verified and, explicitly, that reward convergence is not. Supporting fixes for long runs: - Resuming advanced the data source twice when both a checkpoint and data_source.start_batch were set, so the run silently skipped prompts. Reconcile the two cursors and reject a start_batch ahead of the checkpoint. - A checkpoint save that wrote no model state returned successfully, leaving a directory that only fails on load. Assert an artifact exists, and wait for the writer on every save rather than only the last one. - Colocated store setup timed out at 30s during 32-rank NCCL bring-up. - Trainer master weights are fp32 for this mixed-dtype checkpoint. Co-authored-by: Cursor <cursoragent@cursor.com>
Keep the final Tencent-Hunyuan#347 geometry and terminal-trajectory behavior while retaining the vLLM-Omni deployment additions.
CjhHa1
force-pushed
the
feat/minimax-h3-vllmomni-32gpu
branch
from
August 28, 2026 08:13
12e9c1a to
2a67fff
Compare
Keep the H3 backend change aligned with the repository policy of not carrying a standalone tests directory.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
vLLM-Omni rollout backend for MiniMax-H3 T2VA flow-GRPO, plus the 32-GPU
deployment it runs on. H3 is a 33B dense omni-modal transformer that denoises
video and stereo audio in one packed sequence, so it does not fit the existing
single-latent rollout path.
What this adds:
replicas (DiT TP2 x Ulysses2, text-encoder TP4), sparse FP32 trajectory
capture, and behaviour-policy log-probs emitted per SDE transition.
LocalLoraWeightSyncsplits intoextract()/push()so the adapter is extracted while the trainer isresident and pushed after trainer offload and rollout wake-up, which is what
makes three roles time-share the same GPUs.
add_lora()only registers CPU-side logicalweights, so a name or shape mismatch against the fused vllm DiT is silent:
the engine keeps serving base weights and the only symptom is a flat reward
curve. The Diffusers-to-serving translation now lives in one place
(renames, the GEGLU half-swap Diffusers stores as
[up, gate], synthesizedrope.inv_freq), every expected block/slice must carry both A and B, andper-layer checksums are compared against the awake engine.
timestep MLP and rope in bf16 despite their storage dtype; H3 conditions on
t = 1 - sigma, where bf16 spacing near 1.0 is ~0.008, so this was realprecision loss at the low-noise end.
embeddings are padded with a mask and trimmed before the packed forward
(concatenating them directly raises on any batch whose prompts tokenize to
different lengths, hit at 32 prompts spanning 9-58 tokens); SDE exploration
keys come from the logical sample id rather than the engine request UUID, so a
retried or resumed sample replays the trajectory it actually generated; sparse
replay always stores the terminal latent for decode.
a checkpoint and
data_source.start_batchwere set; a checkpoint save thatwrote no model state returned successfully.
Related Issue
Depends on vllm-project/vllm-omni#6351 (open). Includes the H3 trainside commit
series from #347 rebased onto current
main, so #347 and this PR must becoordinated rather than merged independently.
Test Plan
Static, on this branch rebased onto
main:pytest tests/— 16 passed (LoRA layout mapping and coverage, video PickScoreframe sampling, T2AV composite failure propagation, stable SDE sample key).
ruff check+ruff format --checkon all 42 changed Python files — clean.python lint/check_docstring_lines.py(3284 docstrings),lint/check_recipe_targets.py(2517_target_paths resolve),lint/check_core_dependencies.py,lint/check_experimental_boundaries.py—all pass.
python -m unirl.train_diffusion --config-name diffusion/minimax_h3/minimax_h3_t2va_vllmomni_32c_quality100_tp2_up2 --cfg job.On hardware — 4 nodes x 8 x H20 96GB, real MiniMax-H3 33B, LoRA rank 64,
256x448x107, 24 transitions:
reward -> trainer replay/step -> sleep), then an 11-rollout run at 32 prompts
x 8 samples with CLAP + ImageBind. Rollout/replay parity held at
|dlogp| ~1e-5against a1e-3gate; PPO ratio1.0000; LoRA verificationreported 350 modules loaded per replica; no OOM.
generate 112.697s / 66,876 MiB peak; TP2 x UP2 91.087s / 77,436 MiB, parity
drift 3.73e-5 and 3.94e-5. A TP4 arm raced a stale GPU keep-alive and OOM'd,
so that number was discarded and TP2 x UP2 is the fastest verified arm, not
a proven optimum.
Not run:
the vLLM 0.27 / Torch 2.13 dependency upgrade. This is the main outstanding
risk and needs an owner with those recipes' checkpoints.
Compatibility / Risk
pyproject.tomlpinsvllm-omni @ git+https://github.com/nussejzz/vllm-omni.git@a4759624, a forkcommit, because [Core][Diffusion] Propagate RPC policy and support packed LoRA targets vllm-project/vllm-omni#6351 is still open. Replace with the
first upstream release containing #6351 before merge.
Transformers 5.10+, which affects every vLLM-Omni recipe, not just H3. The
SGLang environment stays independent.
LocalLoraWeightSync.sync()is preserved asextract()+push(), soexisting callers are unaffected.
unless a recipe opts in with
sampler_kwargs.allow_nonstandard_canvas: true.tests/directory, which this repo does not currently haveand which no CI workflow runs. Happy to relocate or drop it if that is not
the direction you want.
Reviewer Notes
Reward convergence is not solved and this PR does not claim it. The
topology, lifecycle, LoRA sync and parity gates pass, and the throughput
numbers above are reproducible, but no H3 recipe here produces a sustained rise
in held-out visual reward. A Wan2.1 positive control on the same trainer,
PickScore and GRPO path does rise (
0.6985 -> 0.7201over 20 rollouts), whichis what localizes the open problem to H3/reward rather than to the shared
training stack. Treat this as backend and deployment support, not as a tuned
recipe.
One merge resolution needs your judgement. The rebase onto current
mainhit asemantic conflict in
_validate_prompt_tree_dp_geometry:mainnow validatesreward DP against
batch_size(prompt trees), while this branch validatesagainst
batch_size * samples_per_prompt(independently scored rows). I keptthe row-based rule, because the H3 deployment runs 8 prompts x 16 samples over
reward DP32 and ran 11 rollouts that way, and because row-based is strictly more
permissive — anything the tree rule accepts, it also accepts, so no existing
recipe can start failing. But
RewardService.score_and_attachisDispatch.DP_SCATTER, so the tree rule may be the correct invariant for thegeneral path and I did not want to decide that unilaterally.
The flow-GRPO algorithm knobs and the experiment recipes are deliberately not
here; they are stacked on this branch in a follow-up so this PR stays reviewable
as backend support. A DiffusionNFT counterpart is planned separately.
AI assistance was used for this change.
Checklist