Skip to content

feat(minimax-h3): vLLM-Omni rollout backend for H3 FlowGRPO at 32-GPU scale - #378

Open
nussejzz wants to merge 13 commits into
Tencent-Hunyuan:mainfrom
nussejzz:feat/minimax-h3-vllmomni-32gpu
Open

feat(minimax-h3): vLLM-Omni rollout backend for H3 FlowGRPO at 32-GPU scale#378
nussejzz wants to merge 13 commits into
Tencent-Hunyuan:mainfrom
nussejzz:feat/minimax-h3-vllmomni-32gpu

Conversation

@nussejzz

@nussejzz nussejzz commented Aug 19, 2026

Copy link
Copy Markdown
Collaborator

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:

  • Rollout adapter and RL pipeline for H3 under vLLM-Omni: grouped four-GPU
    replicas (DiT TP2 x Ulysses2, text-encoder TP4), sparse FP32 trajectory
    capture, and behaviour-policy log-probs emitted per SDE transition.
  • Staged LoRA publication. LocalLoraWeightSync splits into
    extract() / push() so the adapter is extracted while the trainer is
    resident and pushed after trainer offload and rollout wake-up, which is what
    makes three roles time-share the same GPUs.
  • LoRA transport verification. add_lora() only registers CPU-side logical
    weights, 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], synthesized
    rope.inv_freq), every expected block/slice must carry both A and B, and
    per-layer checksums are compared against the awake engine.
  • Row-level reward dispatch and a resident reward service at DP32.
  • Numerical fixes. 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 real
    precision loss at the low-noise end.
  • Rollout contract fixes found at 32-GPU scale: variable-length prompt
    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.
  • Continuation guarantees. Resuming advanced the data source twice when both
    a checkpoint and data_source.start_batch were set; a checkpoint save that
    wrote no model state returned successfully.
  • Dependency floor moved to vLLM 0.27 / PyTorch 2.13 cu130 / Transformers 5.10+.

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 be
coordinated rather than merged independently.

Test Plan

Static, on this branch rebased onto main:

  • pytest tests/ — 16 passed (LoRA layout mapping and coverage, video PickScore
    frame sampling, T2AV composite failure propagation, stable SDE sample key).
  • ruff check + ruff format --check on 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.
  • Hydra composition:
    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:

  • Two-rollout smoke through the full lifecycle (LoRA sync -> rollout wake ->
    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-5 against a 1e-3 gate; PPO ratio 1.0000; LoRA verification
    reported 350 modules loaded per replica; no OOM.
  • Rollout topology comparison at K8 (8 prompts x 8 samples): HSDP4+UP4 median
    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:

  • Regression of the existing BAGEL / HI3 / Qwen3-Omni vLLM-Omni recipes under
    the vLLM 0.27 / Torch 2.13 dependency upgrade. This is the main outstanding
    risk and needs an owner with those recipes' checkpoints.
  • A clean CU13 environment rebuild from scratch.

Compatibility / Risk

  • Not mergeable as-is. pyproject.toml pins
    vllm-omni @ git+https://github.com/nussejzz/vllm-omni.git@a4759624, a fork
    commit, 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.
  • Global dependency floor moves to vLLM 0.27 / Torch 2.13 cu130 /
    Transformers 5.10+, which affects every vLLM-Omni recipe, not just H3. The
    SGLang environment stays independent.
  • LocalLoraWeightSync.sync() is preserved as extract() + push(), so
    existing callers are unaffected.
  • H3 geometry rejects anything off the released 768-short-edge distribution
    unless a recipe opts in with sampler_kwargs.allow_nonstandard_canvas: true.
  • Adds a top-level tests/ directory, which this repo does not currently have
    and 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.7201 over 20 rollouts), which
is 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 main hit a
semantic conflict in _validate_prompt_tree_dp_geometry: main now validates
reward DP against batch_size (prompt trees), while this branch validates
against batch_size * samples_per_prompt (independently scored rows). I kept
the 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_attach is
Dispatch.DP_SCATTER, so the tree rule may be the correct invariant for the
general 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

  • I reviewed the changed code and removed unrelated/generated artifacts.
  • I updated tests, docs, and configs where needed, or explained why not.

@github-actions github-actions Bot added the need review Ready and waiting for review label Aug 19, 2026
@nussejzz
nussejzz marked this pull request as draft August 19, 2026 05:09
@github-actions github-actions Bot added wip Draft / work in progress and removed need review Ready and waiting for review labels Aug 19, 2026
@nussejzz
nussejzz force-pushed the feat/minimax-h3-vllmomni-32gpu branch 2 times, most recently from 27d0e9a to b6d01ce Compare August 19, 2026 07:36
@nussejzz
nussejzz force-pushed the feat/minimax-h3-vllmomni-32gpu branch from eb4ad91 to 12e9c1a Compare August 26, 2026 09:13
@nussejzz nussejzz changed the title feat(minimax-h3): add vLLM-Omni rollout at 32-GPU scale feat(minimax-h3): vLLM-Omni rollout backend for H3 FlowGRPO at 32-GPU scale Aug 26, 2026
@nussejzz
nussejzz marked this pull request as ready for review August 26, 2026 09:27
@github-actions github-actions Bot added need review Ready and waiting for review and removed wip Draft / work in progress labels Aug 26, 2026
nussejzz and others added 6 commits August 28, 2026 16:08
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>
nussejzz and others added 6 commits August 28, 2026 16:09
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
CjhHa1 force-pushed the feat/minimax-h3-vllmomni-32gpu branch from 12e9c1a to 2a67fff Compare August 28, 2026 08:13
Keep the H3 backend change aligned with the repository policy of not carrying a standalone tests directory.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

need review Ready and waiting for review

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants