MoE Routing & Experts
Mixture-of-Experts is where the Atlas supported-model matrix gets most of its diversity. Qwen3.5 routes 128 experts top-10. MiniMax-M2.7 routes 256 experts top-8 with sigmoid gating (not softmax). Gemma-4 has shared experts. Nemotron-H has an MoE-only layer variant (no mixer, just routing + FFN). The engineering problem is making all these shapes share one kernel pipeline without losing per-shape efficiency.
The generic MoE block
residual ─► gate (linear) ─► softmax/sigmoid ─► topk ──► dispatch ──► experts ──► gather ──► weighted sum ──► residual
│ │
│ └─ (N different FFN weights, one per expert)
│
└─ (tokens routed to ≤ k experts each)
Five kernels contribute to one MoE block:
- Gate — linear projection. Uses the same GEMM kernels as attention projections.
- Top-k + softmax/sigmoid — per-token expert selection.
- Dispatch — token scatter to expert-local arrangement. Conceptually a permutation.
- Expert FFN —
kcopies ofsilu_mul_quant(up(x)) → down(...). Grouped GEMM with one batch per expert. - Gather + weighted sum — reduce expert outputs back to the residual shape.
Prefill: grouped GEMM
For prefill (many tokens per step), the expert FFN is a grouped GEMM: M tokens split across N expert buckets, each bucket does its own (B_e, K) × (K, N_out) matmul.
Atlas's moe_prefill.cu kernel takes the "sort tokens by expert, then one GEMM per expert group" approach. The sort is parallel — dispatch tables are computed in a single pre-kernel pass. The GEMM itself uses the same 16×8×16 BF16 MMAs as attention, one CTA per expert-bucket.
This is the kernel that hits the headline MoE W4A16 256-expert 80-token: 3.87× PyTorch number in the benchmark table.
Decode: token-level MoE
For decode (one token per sequence per step), grouped GEMM is the wrong shape — each "group" has one or two tokens, and the launch overhead dominates. Atlas's decode MoE kernels (moe_expert_relu2_down_shared.cu and moe_shared_expert_fused_fp8.cu) take the opposite approach: one warp per (token, expert-in-topk) pair, compute the expert's up-proj and down-proj in a single kernel.
The shared-expert path for models like Gemma-4-26B fuses the shared expert's compute with the per-token dispatch, avoiding a round-trip through memory.
The 256-expert case: MiniMax-M2.7
MiniMax-M2.7 is the extreme end of the MoE support matrix: 256 experts, top-8, sigmoid routing (not softmax). The sigmoid variant lets multiple experts contribute independently without the softmax normalisation; the tradeoff is that topk has to pick from a longer tail of meaningful scores.
Atlas's minimax_moe layer uses:
norm_topk_prob = truesemantics (topk weights normalised by their sum, not softmax).- A dedicated
topk_sigmoidkernel path that reads sigmoid scores rather than post-softmax probabilities. - An EP=2 token-dispatch pipeline — with 256 experts, a single node is impractical; MiniMax ships only as EP=2.
The wave-17 bug fix that landed the "M2.7-NVFP4 EP=2 full PASS" milestone addressed four subtle issues:
rms_normplacement — the rms_norm output was being reused after the MoE dispatch modified it, causing subtle coherence drift.norm_topk_prob— initial implementation used softmax-normalised weights; corrected to sum-normalised sigmoid.- FP8-free path — MiniMax weights are NVFP4 end-to-end; early code had an accidental FP8 upcast in the shared-expert path.
- Template-forced thinking detection — MiniMax's chat template seeds
<think>differently from Qwen; the detector needed to distinguish.
Shared experts
Several models (Gemma-4, Qwen3-VL) have a "shared expert" FFN that runs for every token in addition to the k routed experts:
moe_out = topk_weighted_sum(expert_out) + shared_expert(x)
The shared expert is a standard dense FFN run alongside the routed experts. Atlas fuses the shared FFN with the gather step in the decode kernel (moe_shared_expert_fused_fp8.cu) to save a memory round-trip.
Expert parallelism (EP=2)
For 122B, 119B, 229B models, the experts don't fit on one GB10. Split them across two ranks:
- Gate runs on every rank (replicated).
- Token top-k assigns each token to
kexperts; tokens destined for remote experts are sent viareduce_scatter. - Expert FFN runs locally on the owning rank.
- Results come back via
all_gather.
The collective ops go through spark-comm::CommBackend; the EP dispatch logic in crates/spark-model/src/layers/moe/forward_ep.rs handles the local-vs-remote bucketing. See spark-comm and Multi-GPU & EP=2.
Routing edge cases
A handful of bug-sweep wave findings landed in the MoE code:
- Sibling stride bugs — the attention and SSM layer code assumed
K=2MTP strides in the expert outputs; corrected to supportK=1/2/3uniformly. - Slot-keyed
verify*_graphcaches — CUDA graph instances for MTP-verify were keyed by batch size alone; needed(batch, k)to avoid replaying a K=1 graph on a K=2 step. - MoE topk bounds — a weight-loader edge case where
num_experts_per_tokenexceedednum_expertsfor a few experimental checkpoints; added an assertion at load. - MoE topk weights == 0 guard — a numerical edge case where all-zero gate logits produced NaN after normalisation; now clamped.
Why MoE is fast on GB10
Three design choices stack:
- Grouped-GEMM prefill dispatches fewer kernels than per-token — the fixed overhead per expert group is tiny compared to the math.
- Fragment-time dequant — NVFP4 expert weights are never materialised to BF16; they unpack at the MMA boundary, same as attention (see NVFP4 deep dive).
- Fused SiLU×Mul + quant — the up-proj output is never written to memory in BF16; it's SwiGLU'd and re-quantized in one kernel before the down-proj reads it back. Saves the entire BF16 intermediate (a major BW win on MoE, where the intermediate is
batch × topk × moe_intermediate_size).
The end result on a 256-expert MoE at batch=80: Atlas at 8.43 ms vs PyTorch at 32.65 ms — 3.87×. That's the biggest single-kernel win in the benchmark table.
Files to read
kernels/gb10/<model>/<quant>/moe_prefill.cu— grouped-GEMM prefill.kernels/gb10/<model>/<quant>/moe_expert_relu2_down_shared.cu— token-level decode MoE.kernels/gb10/<model>/<quant>/moe_shared_expert_fused_fp8.cu— fused shared-expert path.kernels/gb10/minimax-m2-229b/nvfp4/moe_w4a16_grouped_gemm.cu— routed grouped-GEMM kernel.crates/spark-model/src/layers/moe/(forward.rs,forward_prefill.rs,forward_ep.rs, …) — Rust side;forward_ep.rsholds the EP=2 token dispatch.docs/adr/0007-tp-ep-composition.md— TP/EP composition design record.docs/adr/0011-ep-batched-decode-optimization.md— EP batched-decode optimization.