feat(parallelism): expert parallelism for the Nemotron-3 Nano MoE layers - #463
Open
le1nux wants to merge 1 commit into
Open
feat(parallelism): expert parallelism for the Nemotron-3 Nano MoE layers#463le1nux wants to merge 1 commit into
le1nux wants to merge 1 commit into
Conversation
…other improvements
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
Adds expert parallelism (EP) for the mixture-of-experts layers of the Nemotron-3 Nano architecture, plus a few improvements that came out of running the full 30B-A3B model.
Expert parallelism
Under EP the routed experts of each MoE layer are partitioned across an
epmesh dimension, so each rank stores and evaluates onlynum_experts / ep_degreeof them. Every rank still routes its own tokens, so tokens travel to the rank owning their expert (dispatch all-to-all) and results travel back (combine all-to-all).The alternative — what modalities does today — is to let FSDP2 all-gather the full expert stack on every rank. For the 16-layer Nemotron config that is 2.38 GiB of bf16 expert weights per MoE layer per forward, and activation checkpointing makes the forward run twice.
Design
src/modalities/models/parallelism/expert_parallelism.py—ExpertParallelGroupedExperts(dispatch/combine wrapper, drop-in forGroupedExperts, soMoEneeds no knowledge of EP) andshard_experts_over_ep_mesh(re-createsw1/w2asShard(0)DTensors on the meta-device model, so checkpoints still see the full global expert stack).torchtitan/models/common/moe.py,torchtitan/distributed/expert_parallel.py, BSD 3-Clause), attributed in the module docstring. Two things are done deliberately differently:(sender, local_expert)pairs and calls.item(), costing2 * ep_degree * num_local_expertsdevice-to-host syncs per MoE layer per forward — several thousand per step for the 52-layer model._build_permute_indicesconstructs the index tensor with vectorized ops instead.all_to_all_singleneeds on the host; both split vectors are copied in a single transfer.all_to_all_single_autogradand through theindex_select/index_copypermutation pair.Device mesh
EP is carved out of
data_parallel_shard_degreerather than multiplying into the world size, so the number of distinct data shards (and therefore the dataloader'sdp_degree) is unchanged. Withexpert_parallel_degree > 1,dp_shardis materialized asdp_shard_mod_ep×epand then re-exposed as a flatteneddp_shardalias, so callers that only care about data parallelism need no changes.epis the inner (fastest-varying) dimension so an all-to-all group spans consecutive global ranks, i.e. stays inside a node forep_degree <= devices per node.Because the flattened
dp_shardis addressable but absent frommesh_dim_names, mesh lookups now resolve throughdevice_mesh[name](_resolve_sub_mesh) instead of scanningmesh_dim_names. Config validation rejectsep_degree > dp_shard_degree, non-divisible degrees, and (for now) any combination with TP/PP/CP.FSDP2 interaction
Expert-parallel stacks must not be sharded on the full
dp_sharddimension — ranks differing in theirepcoordinate hold different experts, so all-gathering across them would mix unrelated weights. Each expert stack therefore gets its own FSDP unit ondp_shard_mod_ep, the part of the data-parallel dimension EP did not consume.Gradient clipping
Routed expert gradients are sharded over
(dp_shard_mod_ep, ep)while all other gradients are sharded overdp_shard. Both the norm reduction (torch.stack) and the in-place rescaling (aten._foreach_mul_) are batched, and neither has a sharding rule for operands from different meshes. Gradients are now grouped by mesh, reduced per group, and the group norms combined —(Σ_g norm_g^p)^(1/p), ormax_gfor the infinity norm. With a single mesh this is bit-identical to the previous computation.Measured, 4× A100-SXM4-80GB (NVSwitch), torch 2.9.1+cu128
config_fineweb_nemotron_nano_ep_fsdp2.yamlvs.config_fineweb_nemotron_nano_fsdp2.yaml(byte-identical apart from the EP degree, the new component and the header), median over steps 4+ of a ~5 minute run:expert_parallel_degreePer-step ranges were
[5.30, 5.60],[5.80, 6.00]and[7.00, 7.30]— non-overlapping.Two things worth reading off this table. Degree 2 is the worst of both worlds on memory: the experts are still FSDP-sharded over the 2 remaining data-parallel ranks so the all-gather buffer is still allocated, and the all-to-all buffers are added on top. And the speedup is not the MoE math getting faster — benchmarked in isolation without FSDP, one EP MoE layer is 0.89× on the forward and 1.06× on forward+backward versus a replicated one, roughly break-even. The entire end-to-end win comes from no longer all-gathering expert weights.
Other improvements
ChunkedCLMCrossEntropyLoss— moves thelm_headout of the model's forward and applies it chunk-by-chunk insidetorch.utils.checkpoint, so the[batch, seq_len, vocab_size]logits (the single largest activation at a 131k vocabulary, plus the fp32 up-cast inside CE) are never fully materialized. Same goal as TorchTitan'sChunkedLossWrapper, but torch-native via checkpoint recomputation rather than a manual per-chunk backward. Enabled byGPT2LLM.lm_head/set_skip_lm_headand byseparate_lm_head_fsdp_unitonFSDP2WrappedModelConfig(the head needs its own gather hook when it is called outside the model forward). Overlaps with feat: Add torchtitan-style chunked lm_head cross-entropy loss (ChunkedLMHeadCrossEntropyLoss) #458 — happy to drop this part if that PR lands first.clm_cross_entropy_loss,clm_cross_entropy_loss_sum) so it can be handed totorch.compilewithoutselfin the graph, plus aLossFactory.get_compiled_loss/loss: compiledcomponent mirroringModelFactory.get_compiled_model. Only the numeric core is compiled, never the batch unpacking.GroupedExpertsaccepts DTensor weights (_local_weights), and itstokens_per_expertis now documented as local counts under EP. No-op without EP.torch.compile'd. Both headers document the measured parameter counts (31.563B total / 3.565B active) and the deviations from the lorem-ipsum config. The compiled variant's header states plainly that compiling this architecture is close to a no-op — the two block types holding 97% of the parameters fragment into 12 and 4 graphs — and that the uncompiled sibling is the recommended default.New components
modelexpert_parallelizedExpertParallelizedModelConfiglosschunked_clm_cross_entropy_lossChunkedCLMCrossEntropyLossConfiglosscompiledCompiledLossConfigTests
tests/config/test_device_mesh_config.py— the EP degree constraints, including that EP does not consume world size, pinned down without a process group.tests/models/nemotron/test_expert_parallelism.py— the dispatch permutation against a loop reference, that it is a permutation, that it groups by expert, and (viamonkeypatch) that it performs no device-to-host readback.tests/fsdp2_parallelization/test_expert_parallelism_fsdp2.py(2 GPUs) — three workers: expert weights end up as DTensors on(dp_shard_mod_ep, ep)holding only this rank's share while router and shared experts stay data-parallel; a full forward/backward/optimizer step including the cross-mesh gradient clipping; and an EP MoE layer matching a replicated one bit-for-bit.Reviewer notes
docs/components/nemotron.mdstill says "Expert parallelism — Not supported. The device mesh has no expert dimension." (line 197) and describes the full 30B config as needing EP for real throughput (line 11). Both are stale as of this PR; I can push the doc update on request, or fold it into the feat(model): add Nemotron-3 Nano hybrid Mamba-Transformer MoE architecture #459 doc..gitignorechange addsdata/experiments/*andsoofi/*. The latter is local scratch and can be dropped if you'd rather not carry it upstream.