Repository navigation
[Bugfix][MammothModa2] Route AR MoE experts graph-safely under cudagraph capture - #9764
Conversation
Codex Review SummaryThis comment shows the latest Codex review activity on this pull request.
ℹ️ About Codex in GitHubYour team has set up Codex to review pull requests in this repo. Reviews are triggered when you
Codex reacts with 👀 while any review is running, comments if it has suggestions, and reacts with 👍 once all reviews finish with no findings. |
|
Codex usage limits have been reached for code reviews. Please check with the admins of this repo to increase the limits by adding credits. |
|
This PR appears to belong to: docs/design/module/ar_runtime.md, docs/design/module/model_integration.md. Module owners: @tzhouam @fake0fan @Gaohan123 Routing: @tzhouam via module of the changed files, CODEOWNERS; @fake0fan via module of the changed files; @Gaohan123 via module of the changed files @BLANKETusers, please review your own changes and leave a short self-review comment describing what you checked. PRs without author self-review may not be assigned a reviewer. Please take a look when you have a chance. If you would like an automated review, mention @vllm-omni-review-bot in a comment. |
amy-why-3459
left a comment
There was a problem hiding this comment.
Thanks for splitting this out of #9527 and for the clear write-up of the cost. The capture-time dispatch reads well: _moe_blend never touches the mask on the host, the eager fast paths are untouched when not capturing, and the fail-closed guard turns the silent all-und_expert bake into a loud error. A few code-level notes:
-
Runner-level coverage for
requires_raw_input_tokens.test_ar_wrappers_keep_raw_input_tokensonly checks the class attribute. The whole fix depends on the runner actually passinginput_idsalongsideinputs_embedson the mm graph path (and the top-level module attribute set in__init__being the one the runner reads). A small test throughtests/worker/test_omni_gpu_model_runner.pythat assertsinput_idsis non-Nonein_model_forwardfor this model, both during dummy capture and on a normal step, would protect against a future runner refactor silently dropping it again, which is exactly the original defect. -
The fail-closed guard can now abort engine startup. Any capture path that still reaches
moe_forwardwithout a mask (for examplemodel_runner: v2, or a PP rank whose intermediate tensors don't carrygen_token_mask) will now raise during graph capture instead of running degraded. That's arguably the right outcome, but it would help to (a) confirm the PP path:make_empty_intermediate_tensorsseedsgen_token_mask, so please check that the runner's static intermediate buffers include that key and get it copied on replay, otherwise rank > 0 replays the zeros mask from capture; and (b) make the error message point users to the workaround (enforce_eager: trueor a non-FULLcudagraph_mode). -
Qwen3 variant.
MammothModa2Qwen3ARForConditionalGenerationgetsrequires_raw_input_tokens = True, but the tests and the H100 evidence only exercise the Qwen2.5-VL backbone. Could you confirm the Qwen3 language model derivesgen_token_maskfrominput_idsin the same way, so the new attribute isn't a no-op there and capture doesn't hit the fail-closed branch? -
Blend cost also applies to understanding-only traffic. Under FULL capture, every graphed batch now runs both experts on layers 14–27, including pure understanding/chat requests that never contain gen-vocab ids. It would be worth stating that in the PR description next to the +25% t2i number, and ideally measuring one understanding-only workload, so the cost is visible to whoever weighs this.
-
Nit: clamp comment. In
get_input_embeddings, the comment says the out-of-range side "maps to index 0", but the base lookup clamps gen ids tobase_vocab_size - 1, not 0. Only the gen side maps to 0. Harmless since those rows are discarded bywhere(), but the comment should match. -
Default-path numerics. As the description notes, the stock eager config now routes prompt gen tokens through
gen_mlp, so output changes for every existing deployment. A reference-output comparison (even a single prompt, hidden-state or logits diff against the reference implementation) before this lands would make that change much easier to sign off on.
|
@amy-why-3459 thanks for the split review — addressed in
Relationship to #9527: that PR is the #7318 refresh-only fix (stale |
amy-why-3459
left a comment
There was a problem hiding this comment.
Re-reviewed only cbbc503b on top of 6367c66e. The error message, clamp comment, Qwen3 docstring and the _prepare_mm_inputs runner test address notes 1, 2b, 3 and 5. Two small follow-ups on the new tests:
-
test_empty_intermediate_tensors_seed_gen_token_maskdoesn't exercise the model. It builds anIntermediateTensorsdict by hand with agen_token_maskkey and then asserts that the key is there, so it would still pass if_make_empty_intermediate_tensorsstopped seeding the mask. Building the tensors through the model's ownmake_empty_intermediate_tensorsfactory and asserting on its output would make the test protect the PP path it is named after. -
The
issubclassassertion only covers inheritance. It confirmsMammothModa2Qwen3ForCausalLMsubclasses the Qwen2 host, but not that the Qwen3 subclass doesn't overrideforward/get_input_embeddings. An assertion such asMammothModa2Qwen3ForCausalLM.forward is MammothModa2Qwen2ForCausalLM.forward(and the same forget_input_embeddings) would catch a future override that drops the mask derivation.
The remaining open items listed in the PR body (an understanding-only FULL workload measurement and a reference logits or hidden-state comparison for the default eager path) are still the main things worth seeing before this leaves draft.
|
Codex usage limits have been reached for code reviews. Please check with the admins of this repo to increase the limits by adding credits. |
9e012b0 to
534ffe8
Compare
BLANKETusers
left a comment
There was a problem hiding this comment.
Review follow-up (InferMatrixCopilot Direct @ 534ffe82f)
CI is green, but the PR is still CONFLICTING vs main after #9527 landed. The MoE graph-safe blend path itself looks sound; the remaining work is merge hygiene plus the two weak tests already flagged.
Must keep both flags when resolving the conflict in MammothModa2ForConditionalGeneration.__init__: requires_raw_input_tokens (this PR) and supports_omni_decode_step_metadata (#9527). The runner reads both on the top-level module — picking only one side re-breaks either MoE routing or the FULL-replay metadata refresh. Same for tests/worker/test_omni_gpu_model_runner.py: keep both suites.
Inline notes below cover the still-open test pins. Separately still open from the PR body (sign-off, not code blockers): reference logits/hidden vs upstream, understanding-only FULL TPOT, NPU/XPU real capture probe.
… FULL capture With compilation_config cudagraph_mode FULL on the AR stage, t2i decode was wrong from the first generated token (early EOS). The und/gen dual expert routing derives gen_token_mask from input_ids >= gen_vocab_start_index with host-side .any()/.all()/.item() branches, so capture with the dummy all-zeros input bakes the understanding-expert path into the graph; t2i decode replays the wrong expert. Route to a graph-safe torch.where blend (run both experts, select per token) when the current stream is capturing a device graph, and use the same blend for gen/base embedding lookup. Eager execution keeps the original dispatch paths unchanged, so non-capturing behavior is bit-for-bit identical. Fixes vllm-project#7318 Signed-off-by: zzh <943967662@qq.com>
The AR backbone routes tokens between the understanding FFN and `gen_mlp` (layers 14-27, `moe_type = "ffn-14:28"`) from `input_ids >= gen_vocab_start_index`. That dispatch reads the mask on the host (`.any()` / `.all()` / `.item()`), which cannot be recorded into a device graph: dummy capture inputs are all zeros, so the branch taken at capture time is replayed for real inputs. Two separate problems follow. 1. The multimodal graph path passes `inputs_embeds` only, so `input_ids` is `None` and `gen_token_mask` never exists. Every token then takes the `und_expert` path, in every mode - the generation expert never runs, even with `enforce_eager: true`, which is the stock deploy config. 2. Under capture there is no graph-safe dispatch at all. This change: - dispatches capturing streams through a `torch.where` blend that runs both experts and selects per token, leaving the eager fast paths untouched - keeps `input_ids` on the mm graph path via `requires_raw_input_tokens` on both AR wrappers and the top-level module - fails closed when capture runs without a mask, instead of silently baking `und_expert` into the graph - clamps both embedding lookups so the discarded side of the blend cannot index out of range - warns once when an NPU/XPU backend exposes no capture probe, instead of always reporting "not capturing" The reference implementation derives the same mask inside `model.forward` (`gen_token_mask = input_ids >= self.gen_vocab_start_index`) and routes gen-vocab tokens through `gen_mlp` for the whole sequence, decode included, so `main`'s behaviour is the deviation this restores. Cost: the blend runs both experts for every graphed token. Measured on H100 (SM90, FA3, `cudagraph_mode: FULL`), greedy seed 42, 1024 squared: 36.0 s of AR decode versus 28.7 s without the routing change, about +25%. This is split out of the vllm-project#7318 fix, which only needs the runtime metadata refresh. That fix alone reaches the full 4161-token AR grid; this routing change is a separate correctness issue and changes default-path numerics. Signed-off-by: zzh <943967662@qq.com>
Tighten the fail-closed capture error with an enforce_eager workaround, fix the embedding clamp comment, document Qwen3 mask inheritance and PP mask seeding, and add a runner test that requires_raw_input_tokens keeps input_ids on the mm path. Signed-off-by: zzh <943967662@qq.com>
534ffe8 to
1581fc6
Compare
|
Addressed the review follow-ups in
PR is MERGEABLE again (merge state may still show blocked on reviews/CI). |
Keep both requires_raw_input_tokens and supports_omni_decode_step_metadata on the top-level module when rebasing onto vllm-project#9527, pin Qwen3 forward/ embedding identity, and drive the PP empty-tensor assertion through the shared factory instead of a hand-built IntermediateTensors dict. Signed-off-by: zzh <943967662@qq.com> Co-authored-by: Cursor <cursoragent@cursor.com>
1581fc6 to
1ab722a
Compare
amy-why-3459
left a comment
There was a problem hiding this comment.
Re-reviewed the follow-up commits after the rebase onto main (0394f199 and 1ab722aa), on top of my earlier cbbc503b pass. Code paths only; I did not run this.
The Qwen3 identity asserts on forward / get_input_embeddings cover the earlier concern about a silent override, and pulling the seeding into make_empty_intermediate_tensors_with_gen_mask is a nice cleanup.
Two small follow-ups:
-
The PP test still doesn't go through the model's factory.
test_empty_intermediate_tensors_seed_gen_token_masknow assigns its own lambda tolm.make_empty_intermediate_tensorsthat callsmake_empty_intermediate_tensors_with_gen_maskdirectly, so it tests the helper, not what__init__installs. If__init__stopped wiring_make_empty_intermediate_tensorsto the helper (or went back to a dict without the mask), this test would still pass. Building the LM through__init__with a tiny config, or asserting on the closure the constructor actually installs, would close that gap. -
Workaround wording in the capture error. The new message suggests
FULL_DECODE_ONLYalongsidePIECEWISEas a fallback. SinceFULL_DECODE_ONLYstill captures the decode step as a full graph, I'd expect it to reach the samemoe_forwardcapture check whenever the mask is missing there (for example the model_runner v2 or PP cases the message mentions). If that's right, recommendingenforce_eagerorPIECEWISEonly would avoid pointing users at a mode that fails the same way.
I didn't re-read the rebased 4ad60f45 / a832b480 beyond these two commits.
Omni ReviewBot: supersededThe CI failure noted on |
amy-why-3459
left a comment
There was a problem hiding this comment.
Looked at the new commit f7edadd only (error-message wording and the PP empty-tensor test now going through __init__).
Dropping FULL_DECODE_ONLY from the fail-closed message is right: decode is still captured as a full graph there, so it wasn't a real workaround, and the test now pins that the message offers only enforce_eager and PIECEWISE. Building MammothModa2Qwen2ForCausalLM through its constructor is also a real improvement over the hand-installed lambda, since it now exercises the closure __init__ actually assigns.
Two small notes on the test:
-
factory.__code__.co_namescheck is brittle. It passes today because the closure references the module-level helper by name, but a harmless refactor (e.g.functools.partial(make_empty_intermediate_tensors_with_gen_mask, hidden_size=...), or a local alias) would break it without any behaviour change. The tensor assertions below already prove the mask is seeded; if you want to keep a wiring check, monkeypatchingm.make_empty_intermediate_tensors_with_gen_maskwith a spy and asserting it was called withhidden_size == 8would test the same thing by behaviour. -
Stub coverage of
__init__. The test stubs embeddings, LM head, logits processor, norm,make_layersandget_pp_group, withextra_gen_vocab=False. That's fine for this assertion, but it means a future dependency added to__init__will fail here with an unrelated-looking error. Consider a one-line comment listing what has to stay stubbed, or covering theis_last_rank=Falsebranch too, since that's the PP case the factory exists for.
The rest of the diff is line-length reflow and looks fine.
|
Pushed as |
amy-why-3459
left a comment
There was a problem hiding this comment.
Incremental look at 4a0c0e921 (changes since a8940f22, test file only).
Both test notes from my last pass are addressed:
- The
co_namescheck is gone. The spy onmake_empty_intermediate_tensors_with_gen_maskis installed before__init__runs, and since the closure looks the helper up as a module global at call time,spy_calls == [(batch_size, hidden)]now checks the wiring by behaviour. - The docstring lists what has to stay stubbed, and the new
(is_first_rank=True, is_last_rank=False)case exercises the non-last PP rank with realPPMissingLayerhead/norm.
No further comments on this change. I only reviewed a8940f22..4a0c0e92 and did not run the tests.
…resets Keep und-only on eager/PIECEWISE; add piecewise + FULL opt-in deploy YAMLs and recipe guidance. Mask-keyed split graphs tracked in vllm-project#9865. Signed-off-by: zzh <943967662@qq.com>
|
Short-term und-only guidance + follow-up issue:
|
…tree Keep A/B numbers in the PR/issue text; binary assets do not belong in the repo for this routing fix. Signed-off-by: zzh <943967662@qq.com>
Document PIECEWISE as an optional manual cudagraph_mode on the default AR yaml instead of shipping a separate deploy file. Signed-off-by: zzh <943967662@qq.com>
amy-why-3459
left a comment
There was a problem hiding this comment.
Incremental look at ac1764d8 and 5ff71e49 only (docs, deploy presets, and removal of the evidence JPGs). I read the diff; I did not run anything. The routing code is unchanged since my last pass.
Dropping the JPGs from the tree makes sense, and nothing in the recipe still links to them. A few questions on the new guidance:
-
Is PIECEWISE really free of the dual-expert cost? The new recipe section,
mammoth_moda2_ar.yaml, andmammoth_moda2_ar_piecewise.yamlall present PIECEWISE as the way to get graphs without FULL's dual-expert tax ("Eager MoE still runs a single expert; PIECEWISE does not require recording both..."). As far as I can tell frommoe_forward,_is_graph_capturing()is true for any capture on the current stream, and under PIECEWISE the MoE MLPs sit inside the captured pieces (only attention is split out by default). If so, PIECEWISE capture would also go through_moe_blendand pay the same cost. The other possibility is that torch.compile tracesmoe_forwardbefore capture and specializes the host-side.any()/.all()branch on the dummy inputs, which would be the original wrong-expert bug again. Could you add a PIECEWISE data point (und-only correctness plus AR decode throughput, compared with eager), or point me to where PIECEWISE avoids the capture path? If PIECEWISE also runs both experts, the docs, the two YAML comments, and the fail-closedRuntimeErrortext that recommends PIECEWISE should probably say so. -
Greedy sampling in a user-facing preset.
mammoth_moda2_ar_graph_full.yamlsetsdefault_sampling_params: {temperature: 0.0, top_k: 1}"to isolate routing / graph replay". The recipe table recommends this file for "Text-to-image with FULL AR graphs", so anyone following the table would quietly get greedy AR decoding for t2i, which changes outputs compared with the default preset. I'd suggest keeping the preset limited to the graph settings and moving the greedy override to the repro instructions (or a separate repro-only file). -
Minor: the FULL preset pins
compilation_config.mode: 0, while the PIECEWISE preset leavesmodeat the default. Since PIECEWISE needs the compile path, a short comment saying it relies on the default compilation mode would stop someone from copyingmode: 0across and ending up with an unsupported or silently downgraded combination.
Nothing blocking on the JPG removal itself.
amy-why-3459
left a comment
There was a problem hiding this comment.
Incremental look at 02b53a2f only (drops mammoth_moda2_ar_piecewise.yaml and turns PIECEWISE into a manual setting in the docs and YAML comments). I read the diff; I did not run anything.
Folding the extra preset into a comment is fine, and I don't see any remaining links to the deleted file in the recipe or the other presets. Two small things:
-
The manual instruction is incomplete as written. The recipe and the comments in
mammoth_moda2.yaml/mammoth_moda2_ar.yamlnow say "setcudagraph_mode: PIECEWISEon the AR stage yourself", but both files keep stage 0 atenforce_eager: true. With eager enforced, setting onlycudagraph_modewon't capture any graphs, so someone following the text literally would see no change. The deleted preset had the full recipe (enforce_eager: falsepluscompilation_config.cudagraph_mode: PIECEWISE); it would help to put that snippet in the recipe section so the manual path is copy-pasteable. -
My earlier question about whether PIECEWISE actually avoids the dual-expert path still applies, since PIECEWISE is still recommended in the recipe, both YAML comments, and the fail-closed
RuntimeErrortext (and asserted intest_mammoth_moda2_moe_graph_safety.py). Removing the dedicated preset makes this lower-stakes, but if PIECEWISE capture also goes through_moe_blend, the wording should say so. The greedydefault_sampling_paramsnote onmammoth_moda2_ar_graph_full.yamlis unchanged in this commit as well.
Nothing blocking in this commit itself.
_is_graph_capturing is mode-agnostic, so fail-closed and docs now point und-only at eager only; drop greedy sampling from the FULL user preset. Signed-off-by: zzh <943967662@qq.com>
|
@amy-why-3459 thanks — all three land in
|
amy-why-3459
left a comment
There was a problem hiding this comment.
Incremental look at d0dd1915 only (PIECEWISE wording, greedy sampling removed from the FULL preset, mode: 0 comment). I read the diff; I did not run anything.
This addresses the three points from my last pass:
- The recipe, both YAML headers, and the fail-closed
RuntimeErrornow say plainly that any capture going through MoE takes_moe_blend, and they point und-only traffic at eager only. That matches how_is_graph_capturing()works, and it also removes the incomplete "setcudagraph_mode: PIECEWISEyourself" instruction I flagged before. - Dropping
default_sampling_paramsfrommammoth_moda2_ar_graph_full.yamllooks right; the preset now inherits the base sampling defaults, and the repro note says to override at the client. - The
mode: 0comment makes the choice clear.
One small test nit: assert "cudagraph_mode: PIECEWISE" not in message only guards against that exact old phrasing. Since the new message deliberately mentions PIECEWISE ("PIECEWISE is not a workaround"), a positive check such as assert "PIECEWISE is not a workaround" in message would pin the intended wording more directly. Not blocking.
Nothing blocking in this commit.
vllm-project/vllm-omni@main:recipes 4b63581cf652 → bfe3d20c8ca4 [latest.vllm-omni] vllm-project/vllm-omni@4b63581...bfe3d20 Commits: - vllm-project/vllm-omni@bfe3d20c8ca4 [Bugfix][MammothModa2] Route AR MoE experts graph-safely under cudagraph capture (vllm-project/vllm-omni#9764) Signed-off-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
vllm-project/vllm-omni@main:recipes 809548323b15 → fc7ea43775b8 [latest.vllm-omni] vllm-project/vllm-omni@8095483...fc7ea43 Commits: - vllm-project/vllm-omni@bfe3d20c8ca4 [Bugfix][MammothModa2] Route AR MoE experts graph-safely under cudagraph capture (vllm-project/vllm-omni#9764) - vllm-project/vllm-omni@4b63581cf652 [Core] Align vLLM-Omni with vLLM 0.33.0 (vllm-project/vllm-omni#9648) - vllm-project/vllm-omni@670aa7ec6043 [Model] TaoMate-H3: full-request inference and streamed audio delivery (vllm-project/vllm-omni#8676) - vllm-project/vllm-omni@01c3f711a8d7 [Model] Add experimental Physis Same-Step Wan, LTX and MiniMax-H3 (vllm-project/vllm-omni#9154) Recipes: - MiniMaxAI/MiniMax-H3 (modified): https://recipes.vllm.ai/MiniMaxAI/MiniMax-H3 - Qwen/Qwen-Image-2.1 (modified): https://recipes.vllm.ai/Qwen/Qwen-Image-2.1 - Qwen/Qwen-Image (modified): https://recipes.vllm.ai/Qwen/Qwen-Image - fishaudio/drama-3 (modified): https://recipes.vllm.ai/fishaudio/drama-3 - meituan-longcat/LongCat-Image-Edit (modified): https://recipes.vllm.ai/meituan-longcat/LongCat-Image-Edit - nvidia/Cosmos3-Nano (modified): https://recipes.vllm.ai/nvidia/Cosmos3-Nano - nvidia/Cosmos3-Super-Image2Video (modified): https://recipes.vllm.ai/nvidia/Cosmos3-Super-Image2Video - nvidia/Cosmos3-Super-Text2Image (modified): https://recipes.vllm.ai/nvidia/Cosmos3-Super-Text2Image - nvidia/Cosmos3-Super (modified): https://recipes.vllm.ai/nvidia/Cosmos3-Super - stabilityai/stable-audio-open-1.0 (modified): https://recipes.vllm.ai/stabilityai/stable-audio-open-1.0 - stabilityai/stable-diffusion-3.5-medium (modified): https://recipes.vllm.ai/stabilityai/stable-diffusion-3.5-medium Signed-off-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
vllm-project/vllm-omni@main:recipes 809548323b15 → fc7ea43775b8 [latest.vllm-omni] vllm-project/vllm-omni@8095483...fc7ea43 Commits: - vllm-project/vllm-omni@bfe3d20c8ca4 [Bugfix][MammothModa2] Route AR MoE experts graph-safely under cudagraph capture (vllm-project/vllm-omni#9764) - vllm-project/vllm-omni@4b63581cf652 [Core] Align vLLM-Omni with vLLM 0.33.0 (vllm-project/vllm-omni#9648) - vllm-project/vllm-omni@670aa7ec6043 [Model] TaoMate-H3: full-request inference and streamed audio delivery (vllm-project/vllm-omni#8676) - vllm-project/vllm-omni@01c3f711a8d7 [Model] Add experimental Physis Same-Step Wan, LTX and MiniMax-H3 (vllm-project/vllm-omni#9154) Recipes: - MiniMaxAI/MiniMax-H3 (modified): https://recipes.vllm.ai/MiniMaxAI/MiniMax-H3 - Qwen/Qwen-Image-2.1 (modified): https://recipes.vllm.ai/Qwen/Qwen-Image-2.1 - Qwen/Qwen-Image (modified): https://recipes.vllm.ai/Qwen/Qwen-Image - fishaudio/drama-3 (modified): https://recipes.vllm.ai/fishaudio/drama-3 - meituan-longcat/LongCat-Image-Edit (modified): https://recipes.vllm.ai/meituan-longcat/LongCat-Image-Edit - nvidia/Cosmos3-Nano (modified): https://recipes.vllm.ai/nvidia/Cosmos3-Nano - nvidia/Cosmos3-Super-Image2Video (modified): https://recipes.vllm.ai/nvidia/Cosmos3-Super-Image2Video - nvidia/Cosmos3-Super-Text2Image (modified): https://recipes.vllm.ai/nvidia/Cosmos3-Super-Text2Image - nvidia/Cosmos3-Super (modified): https://recipes.vllm.ai/nvidia/Cosmos3-Super - stabilityai/stable-audio-open-1.0 (modified): https://recipes.vllm.ai/stabilityai/stable-audio-open-1.0 - stabilityai/stable-diffusion-3.5-medium (modified): https://recipes.vllm.ai/stabilityai/stable-diffusion-3.5-medium Signed-off-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
vllm-project/vllm-omni@main:recipes 4cbaec9d0146 → fc7ea43775b8 [latest.vllm-omni] vllm-project/vllm-omni@4cbaec9...fc7ea43 Commits: - vllm-project/vllm-omni@bfe3d20c8ca4 [Bugfix][MammothModa2] Route AR MoE experts graph-safely under cudagraph capture (vllm-project/vllm-omni#9764) - vllm-project/vllm-omni@4b63581cf652 [Core] Align vLLM-Omni with vLLM 0.33.0 (vllm-project/vllm-omni#9648) - vllm-project/vllm-omni@670aa7ec6043 [Model] TaoMate-H3: full-request inference and streamed audio delivery (vllm-project/vllm-omni#8676) - vllm-project/vllm-omni@01c3f711a8d7 [Model] Add experimental Physis Same-Step Wan, LTX and MiniMax-H3 (vllm-project/vllm-omni#9154) Recipes: - MiniMaxAI/MiniMax-H3 (modified): https://recipes.vllm.ai/MiniMaxAI/MiniMax-H3 - Qwen/Qwen-Image-2.1 (modified): https://recipes.vllm.ai/Qwen/Qwen-Image-2.1 - Qwen/Qwen-Image (modified): https://recipes.vllm.ai/Qwen/Qwen-Image - fishaudio/drama-3 (modified): https://recipes.vllm.ai/fishaudio/drama-3 - meituan-longcat/LongCat-Image-Edit (modified): https://recipes.vllm.ai/meituan-longcat/LongCat-Image-Edit - nvidia/Cosmos3-Nano (modified): https://recipes.vllm.ai/nvidia/Cosmos3-Nano - nvidia/Cosmos3-Super-Image2Video (modified): https://recipes.vllm.ai/nvidia/Cosmos3-Super-Image2Video - nvidia/Cosmos3-Super-Text2Image (modified): https://recipes.vllm.ai/nvidia/Cosmos3-Super-Text2Image - nvidia/Cosmos3-Super (modified): https://recipes.vllm.ai/nvidia/Cosmos3-Super - stabilityai/stable-audio-open-1.0 (modified): https://recipes.vllm.ai/stabilityai/stable-audio-open-1.0 - stabilityai/stable-diffusion-3.5-medium (modified): https://recipes.vllm.ai/stabilityai/stable-diffusion-3.5-medium Signed-off-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
vllm-project/vllm-omni@main:recipes 670aa7ec6043 → a8220b0352f1 [latest.vllm-omni] vllm-project/vllm-omni@670aa7e...a8220b0 Commits: - vllm-project/vllm-omni@a8220b0352f1 [Doc] Add Qwen-Image-2.1 Ascend 910B1 low-latency serving recipe (vllm-project/vllm-omni#9796) - vllm-project/vllm-omni@bfe3d20c8ca4 [Bugfix][MammothModa2] Route AR MoE experts graph-safely under cudagraph capture (vllm-project/vllm-omni#9764) - vllm-project/vllm-omni@4b63581cf652 [Core] Align vLLM-Omni with vLLM 0.33.0 (vllm-project/vllm-omni#9648) Recipes: - MiniMaxAI/MiniMax-H3 (modified): https://recipes.vllm.ai/MiniMaxAI/MiniMax-H3 - Qwen/Qwen-Image-2.1 (modified): https://recipes.vllm.ai/Qwen/Qwen-Image-2.1 - Qwen/Qwen-Image (modified): https://recipes.vllm.ai/Qwen/Qwen-Image - fishaudio/drama-3 (modified): https://recipes.vllm.ai/fishaudio/drama-3 - meituan-longcat/LongCat-Image-Edit (modified): https://recipes.vllm.ai/meituan-longcat/LongCat-Image-Edit - nvidia/Cosmos3-Nano (modified): https://recipes.vllm.ai/nvidia/Cosmos3-Nano - nvidia/Cosmos3-Super-Image2Video (modified): https://recipes.vllm.ai/nvidia/Cosmos3-Super-Image2Video - nvidia/Cosmos3-Super-Text2Image (modified): https://recipes.vllm.ai/nvidia/Cosmos3-Super-Text2Image - nvidia/Cosmos3-Super (modified): https://recipes.vllm.ai/nvidia/Cosmos3-Super - stabilityai/stable-audio-open-1.0 (modified): https://recipes.vllm.ai/stabilityai/stable-audio-open-1.0 - stabilityai/stable-diffusion-3.5-medium (modified): https://recipes.vllm.ai/stabilityai/stable-diffusion-3.5-medium Signed-off-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
vllm-project/vllm-omni@main:recipes 670aa7ec6043 → a8220b0352f1 [latest.vllm-omni] vllm-project/vllm-omni@670aa7e...a8220b0 Commits: - vllm-project/vllm-omni@a8220b0352f1 [Doc] Add Qwen-Image-2.1 Ascend 910B1 low-latency serving recipe (vllm-project/vllm-omni#9796) - vllm-project/vllm-omni@bfe3d20c8ca4 [Bugfix][MammothModa2] Route AR MoE experts graph-safely under cudagraph capture (vllm-project/vllm-omni#9764) - vllm-project/vllm-omni@4b63581cf652 [Core] Align vLLM-Omni with vLLM 0.33.0 (vllm-project/vllm-omni#9648) Recipes: - MiniMaxAI/MiniMax-H3 (modified): https://recipes.vllm.ai/MiniMaxAI/MiniMax-H3 - Qwen/Qwen-Image-2.1 (modified): https://recipes.vllm.ai/Qwen/Qwen-Image-2.1 - Qwen/Qwen-Image (modified): https://recipes.vllm.ai/Qwen/Qwen-Image - fishaudio/drama-3 (modified): https://recipes.vllm.ai/fishaudio/drama-3 - meituan-longcat/LongCat-Image-Edit (modified): https://recipes.vllm.ai/meituan-longcat/LongCat-Image-Edit - nvidia/Cosmos3-Nano (modified): https://recipes.vllm.ai/nvidia/Cosmos3-Nano - nvidia/Cosmos3-Super-Image2Video (modified): https://recipes.vllm.ai/nvidia/Cosmos3-Super-Image2Video - nvidia/Cosmos3-Super-Text2Image (modified): https://recipes.vllm.ai/nvidia/Cosmos3-Super-Text2Image - nvidia/Cosmos3-Super (modified): https://recipes.vllm.ai/nvidia/Cosmos3-Super - stabilityai/stable-audio-open-1.0 (modified): https://recipes.vllm.ai/stabilityai/stable-audio-open-1.0 - stabilityai/stable-diffusion-3.5-medium (modified): https://recipes.vllm.ai/stabilityai/stable-diffusion-3.5-medium Signed-off-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
…aph capture (vllm-project#9764) Signed-off-by: zzh <943967662@qq.com> Co-authored-by: Cursor <cursoragent@cursor.com> Co-authored-by: amy-why-3459 <wuhaiyan17@huawei.com>
…project#9865) Capture all-und / all-gen / blend graphs so uniform batches skip the unused expert. Host classifies gen_token_mask before replay; mixed still uses the vllm-project#9764 blend path. Extends BatchDescriptor with moe_routing_key and wires Omni capture/dispatch. Signed-off-by: zzh <943967662@qq.com>
…project#9865) Capture all-und / all-gen / blend graphs so uniform batches skip the unused expert. Host classifies gen_token_mask before replay; mixed still uses the vllm-project#9764 blend path. Extends BatchDescriptor with moe_routing_key and wires Omni capture/dispatch. Signed-off-by: zzh <943967662@qq.com>
Purpose
Splits the MoE routing half out of #9527. The #7318 symptom there (greedy decode stopping after ~6 tokens under
cudagraph_mode: FULL) is fixed by the runtime metadata refresh alone; this PR carries the separate routing defect.The AR backbone routes tokens between the understanding FFN and
gen_mlp(layers 14–27,moe_type = "ffn-14:28") frominput_ids >= gen_vocab_start_index. That dispatch reads the mask on the host (.any()/.all()/.item()), which cannot be recorded into a device graph: dummy capture inputs are all zeros, so the branch taken at capture time is replayed for real inputs.Two distinct problems:
inputs_embedsonly, soinput_idsisNoneandgen_token_maskdoes not exist. Every token takesund_expert, in every mode — includingenforce_eager: true, which is the stockmammoth_moda2.yaml. The checkpoint ships 42gen_mlp.*tensors covering exactly layers 14–27 that are loaded and then never used.What this changes
torch.whereblend that runs both experts and selects per token; eager fast paths untouchedinput_idson the mm graph path viarequires_raw_input_tokenson both AR wrappers and the top-level moduleund_expertWhy this is the correct behaviour
The reference implementation does the same thing, inside the model:
modeling_mammothmoda2_qwen2_5_vl.py(initial commitdf2154e, the arch this checkpoint declares —llm_config.model_type = mammothmoda2_qwen2_5_vl) andmodeling_mammothmoda2_qwen3_vl.pyboth derivegen_token_mask = input_ids >= self.gen_vocab_start_indexinmodel.forwardwheneverinputs_embedsis not supplied, then route every gen-vocab token throughgen_mlpfor the whole sequence.prepare_inputs_for_generation, so decode keeps routing on token ids — the same semantics as this PR.<|image start|>,<|image token|>), so prompt positions already route togen_mlpin the reference.main's all-und_expertbehaviour is therefore the deviation, not the baseline.Behaviour change and cost — needs to be weighed
requires_raw_input_tokens, the stock eager config stops being identical tomain: prompt gen tokens now go throughgen_mlp. The first differing hidden row is the first gen-vocab prompt token. A reference-output comparison is still the open question; this PR does not claim the new image is better, only that it matches the reference code path.cudagraph_mode: FULLover mixed prefill-decode), greedy seed 42, 1024²: 36.0 s vs 28.7 s with the same tree minus the routing change. Both experts run for every graphed token, since the captured graph must have no data-dependent control flow. That dual-expert tax also hits understanding-only traffic under FULL.mammoth_moda2_ar.yaml). PIECEWISE does not avoid the dual-expert MoE path when MoE is captured; opt into FULL only viamammoth_moda2_ar_graph_full.yaml(graph settings only — sampling stays at base defaults). See the CUDA graphs section inrecipes/MammothModa2/MammothModa2.md.Relationship to #7319
#7319 raises on capture with a non-None mask and relies on
input_ids=None. That is a different routing design and the two cannot land together — one of them has to be chosen. This PR is the graph-safe-blend option; direction should be settled there.Test plan
144 passed.
CPU regressions added: blend ≡ eager dispatch, per-token expert selection, capture without a mask fails closed, an all-false (dummy-capture) mask still runs both experts,
requires_raw_input_tokenspresent on both wrappers, out-of-range gen ids clamp, and the missing-probe warning fires exactly once.Evidence
H200, SM90, FlashAttention 3,
mode: 0+cudagraph_mode: FULL, 29 graphs over mixed prefill-decode:61a6ac259(no refresh, no routing)stoplengthlengthThe middle row is what #9527 now carries; it reaches the full grid without any of these changes. The table is the reason for the split and the source of the +25%.
Not covered here: NPU/XPU graph execution,
model_runner: v2, and any comparison against reference-implementation output.Cost note (understanding-only traffic)
Under FULL capture, layers 14–27 always run both experts via
_moe_blend, including pure understanding/chat batches that never contain gen-vocab ids. The +25% AR decode figure measured on H200 t2i therefore understates the cost for und-only traffic on the same path; measuring one understanding-only workload before merge is still open.Open before merge
gen_mlp, so default-path numerics change for existing deployments.is_current_stream_capturingprobe (missing-probe path now warns once).