feat(cuda-graphs): capture fixed-capacity MoE execution#2932
Draft
yaoyu-33 wants to merge 2 commits into
Draft
Conversation
Signed-off-by: yaoyu-33 <yaoyu.094@gmail.com>
Signed-off-by: yaoyu-33 <yaoyu.094@gmail.com>
462a569 to
e2088b4
Compare
7c13d12 to
9aa4a75
Compare
e2088b4 to
fd1a75f
Compare
9aa4a75 to
be567b9
Compare
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.
What does this PR do ?
Adds a fixed-capacity
moeCUDA Graph scope on top of #2917. The captured boundary starts after routing and includes token dispatch, local Transformer EngineGroupedLinearexperts, and token combine. Attention and MoE scopes can be enabled together withcuda_graph_modules: [attn, moe]under FSDP2.This is part of #1027. It is built on #2917, whose scoped partial-capture implementation credits @hemildesai as co-author.
Changelog
cuda_graph_modules: [moe]andcuda_graph_moe_capacity_factorconfiguration.source_capacity * ep_size * num_local_experts, avoidingtokens_per_expert.sum()and its host synchronization.Design notes
For
Nsource tokens, top-kK,Eglobal experts, and capacity factorF, source capacity is:ceil(N * K * F / E)Every source rank selects exactly that many token slots for every global expert. Real assignments are selected by routing probability; unused slots have zero routing probability. Therefore every local expert receives exactly
source_capacity * ep_sizerows and TE receives constantm_splits.This follows the Megatron Core
moe_pad_expert_input_to_capacity/ HybridEP path. Legacy DeepEP remains rejected: its current dispatch path waits on dynamic communication metadata on the CPU, and padding only after dispatch cannot make capture safe. A DeepEP v2 static-buffer integration would be separate work.CUDA experiments
Combined-scope attribution
Tiny Qwen3-MoE, 2 layers, hidden size 512, 8 experts, top-2, BF16, sequence length 512, local/global batch size 2/4, FSDP2 over 2 H100s. Timing is 8 measured forward/backward/Adam steps after 4 warmup steps.
attnmoeattn, moeAll four modes report the same 12-step loss sequence. This tiny-model run is useful for attribution, but launch overhead is overrepresented.
Qwen3-30B-A3B low-load sanity check
Qwen/Qwen3-30B-A3B, 48 layers, BF16, sequence length 1024, local/global batch size 1/8, FSDP2 + EP8 HybridEP over 8 H100s, real learned router, 25 forward/backward/Adam steps with 5 warmup steps. The order was eager 1, graph 1, graph 2, eager 2. Setup, TE compilation, and graph capture are excluded from steady-state iteration timing.attn, moeMean latency decreases by 33.8%; equivalent training throughput increases by 51.0%. Each graph run captured all 96 entries (attention + MoE for 48 layers), replayed 2,304 calls, and had zero fallback. This is a deliberately underloaded launch-overhead result (4.39% eager MFU), not a production-throughput claim.
The memory cost is material: +20.97 GB/GPU in this configuration. Capture is also a one-time startup cost and is not included in the steady-state speedup.
For a controlled replay parity check, the same 30B configuration was run with
lr=0for 6 steps. Eager and graph produced the same reported loss on every step:10.3457, 10.3424, 10.3292, 10.3191, 10.3709, 10.3198. The graph run captured 96 entries, replayed 480 calls, and had zero fallback. With nonzero learning rate, repeated distributed HybridEP/BF16 runs are not bitwise deterministic, so the controlled no-update run is the forward/replay parity check.Existing-recipe scaling check
The repository H100 recipe uses local batch 4, sequence length 4096, TE projections/norm/RoPE, DeepEP, and activation checkpointing. It cannot be used as a pure graph-on/graph-off comparison yet: whole-attention graph currently requires torch projections, torch RMSNorm, and unfused RoPE; full MoE graph requires HybridEP and rejects activation checkpointing.
Using the closest currently supported backend on 8 H100-80GB GPUs (
linear: torch,rms_norm: torch,rope_fusion: false, TE attention/experts, HybridEP, FSDP2 + EP8), the doubled-batch result is:attnmoeattn, moeAt local batch 2 x sequence 2048, eager runs at 539.834 ms (15.80% MFU, 53.61 GB), but combined capture OOMs earlier at layer 27 attention with 75.37 GiB process usage. At 2 x 1024, combined capture reaches layer 45 before OOM. This identifies cumulative graph-pool memory across 96 per-layer entries as the immediate scaling blocker. Both 48-entry single scopes fit and replay without fallback.
Validation
uv run ruff format .uv run ruff check --fix .uv run pytest -q tests/unit_tests/moe tests/unit_tests/recipes/test_partial_cuda_graphs.pylr=0replay parity.Known limitations
ep_shard > 1) is not validated.Before your PR is "Ready for review"
Additional Information