perf(dllm): use the DeepEP dispatcher in the DiffusionGemma recipes - #3654
Open
akoumpa wants to merge 1 commit into
Open
perf(dllm): use the DeepEP dispatcher in the DiffusionGemma recipes#3654akoumpa wants to merge 1 commit into
akoumpa wants to merge 1 commit into
Conversation
akoumpa
force-pushed
the
akoumparouli/dllm-diffusiongemma-dispatcher-default
branch
from
August 25, 2026 04:42
d54b68b to
d967043
Compare
akoumpa
force-pushed
the
akoumparouli/dllm-diffusiongemma-dispatcher-default
branch
from
August 26, 2026 05:19
b43c50a to
efe68da
Compare
akoumpa
requested review from
a team,
HuiyingLi,
athitten and
snowmanwwg
as code owners
August 26, 2026 05:19
akoumpa
changed the base branch from
akoumpa/feat/diffusion-gemma-te-cp
to
main
August 26, 2026 05:19
akoumpa
force-pushed
the
akoumparouli/dllm-diffusiongemma-dispatcher-default
branch
from
August 26, 2026 05:21
efe68da to
d51b314
Compare
diffusion_gemma_sft.yaml and diffusion_gemma_lora.yaml pinned `dispatcher: torch`. Both run ep_size=8 on a single node, so this forced the DTensor fallback even on DeepEP-capable images such as the CI container. At ep_size=8 that fallback runs, for every MoE layer on every forward pass: a token-count all_gather, an `[int(t.item()) ...]` host sync, and four variable-length all_gathers (activations, router weights, indices, token mask) that replicate every token to all 8 ranks, then an all-reduce to combine. DiffusionGemma runs the shared stack three times per step (causal encoder, no_grad self-conditioning decode, real decode) and activation-checkpoint recompute replays the dispatch again in backward. Measured with GPU kernel tracing on 8xH100 (google/diffusiongemma-26B-A4B-it), per step per rank: 59,482 kernel launches, 1,184 NCCL collectives holding 97.4% of GPU kernel time, 99.9% of that communication exposed (1,091 of the 1,184 are issued on the same CUDA stream as compute, so overlap is impossible), 2,175 blocking device-to-host copies, and only 528 ms of compute-kernel time of which 73 ms is GEMM. Benchmarked on eos, 8xH100, 8 steps, shipped recipe with only this change: | dispatcher | step | throughput | peak mem | |-----------------|---------|-------------|----------| | torch (before) | 15.7 s | 263 tok/s | 59.0 GiB | | deepep (after) | 10.6 s | 388 tok/s | 59.2 GiB | 1.47x faster steps at equal memory. Loss trajectories match to within floating-point reassociation (step 0: 4.9946 vs 4.9583), as expected for a change that alters only how tokens are exchanged. Set explicitly rather than left unset: the value is visible in the recipe, and BackendConfig's default would otherwise resolve differently per image. "hybridep" was benchmarked as the alternative and is not a drop-in for this recipe -- it fails with CheckpointError because the tokens routed to a rank differ between forward and activation-checkpoint recompute (481 vs 1087 rows), even though ignore_router_for_ac already defaults to True. The diffusion_gemma_te_cp_100k.yaml recipe added by #3651 is deliberately left on `dispatcher: torch`: it runs ep_size=1 across two nodes, and the dispatcher branch in moe/layers.py keys off world size rather than ep_size, so switching it would activate the DeepEP path on an untested internode configuration. Signed-off-by: Alexandros Koumparoulis <akoumparouli@nvidia.com>
akoumpa
force-pushed
the
akoumparouli/dllm-diffusiongemma-dispatcher-default
branch
from
August 26, 2026 06:47
d51b314 to
d12fa05
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
diffusion_gemma_sft.yamlanddiffusion_gemma_lora.yamlpinneddispatcher: torch. Both runep_size: 8on a single node, so this forced the DTensor fallback even on DeepEP-capable images such as the CI container. This setsdispatcher: deepepexplicitly.Verified in the CI container that DeepEP is genuinely usable, not just importable:
…and end-to-end: the patched recipe runs 8 clean steps at 10.47 s/step vs 15.7 s before,
TORCHRUN_EXIT=0, 59.17 GiB.Why
At
ep_size=8the fallback path inmoe/experts.pyruns, for every MoE layer on every forward pass: a token-countall_gather, an[int(t.item()) ...]host sync, and four variable-lengthall_gathers (activations, router weights, indices, token mask) that replicate every token to all 8 ranks — then an all-reduce to combine. DiffusionGemma runs the shared stack three times per step (causal encoder,no_gradself-conditioning decode, real decode), and activation-checkpoint recompute replays the dispatch again in backward.Profiled on 8×H100. Per step, per rank:
About 85 % of the collectives are launched from
experts.py, and 1,091 of the 1,184 are issued on the same CUDA stream as compute, so overlap is not merely missed — it is impossible.Measured
eos, 8×H100, 8 steps, shipped recipe with only this change:
torch(before)deepep(after)1.47× faster steps at equal memory. Loss trajectories match to within floating-point reassociation (step 0: 4.9946 vs 4.9583), as expected for a change that alters only how tokens are exchanged.
Why
deepepand nothybridephybridepwas benchmarked as the alternative and is not a drop-in for this recipe. It fails with:The tokens routed to a rank differ between forward and activation-checkpoint recompute, even though
ignore_router_for_acalready defaults toTrue— so the nondeterminism is beyond what that guard covers. Worth a separate look, sincehybridepis what would be needed to scale this recipe past one node (DeepEP's own internode path faults separately atinternode.cu:346).Why
diffusion_gemma_te_cp_100k.yamlis untouchedIt is deliberately left on
dispatcher: torch. It runsep_size: 1across two nodes, and the dispatcher branch inmoe/layers.py:770-778keys offget_world_size_safe()rather thanep_size— so changing it would constructGroupedExpertsDeepEPon a 16-GPU internode configuration I have not tested.Not in this PR
attn: sdpaandlinear: torchare also downgrades from the library default (te);diffusion_gemma_te_cp_100k.yamlalready usesattn: te. I have not verified TE attention against the block-diffusion additive mask.local_batch_sizeis worth more (4× the batch costs only ~4 % more step time — the step is dominated by fixed per-collective latency).deepep+local_batch_size: 2measured 3.08×. It changes the effective global batch, so it belongs in a separate, deliberate change.Update: making both dispatchers usable
Follow-up investigation into why
hybridepcould not simply be swapped in.Working configurations (8xH100, measured)
torch(before)deepepdeepephybridephybridepCheckpointErrorWhy
hybridep+ activation checkpointing failsIt dies in the first backward with:
That message points at the router — a shape change like this is documented as non-deterministic
re-routing, which
ignore_router_for_acexists to prevent. That is not the cause here.Instrumenting
Gemma4Gate(DiffusionGemma swaps in its own gate, so the standardGateis nevercalled) shows routing is reproduced exactly. Encoder pass vs its recompute, top-k index checksum:
All 30 decoder layers match too (0/30 mismatches on every rank). With bit-identical routing on all
8 ranks,
hybrid_ep_dispatchreturned 2791 rows in the forward and 2701 on replay. Thenon-determinism is inside the dispatch, so no router-side mitigation can prevent it.
Also ruled out along the way:
dispatcher_share_token_dispatcher=False(per-layer managers) still fails.pad_multiple— set once at construction, never reassigned.torch.ops.deepep.dispatch/hybridep.dispatchentries already inactivation_checkpointing.pyare inert: dispatch goesthrough
FusedDispatch.apply, anautograd.Function, which a__torch_dispatch__policy neverobserves. Those four entries have never matched anything. Worth fixing separately.
A warning for this combination, and the DeepEP dispatch-layout reuse
found while profiling this, are both in #3684. This PR is only the two recipe files.