Problem
Bigger transformers are better, but in a dense model every parameter takes part in every token's forward pass, so compute per token grows in step with parameter count. The feed-forward blocks hold about two thirds of a dense transformer's non-embedding parameters, and each token uses all of them.
Mixture of experts breaks that link. Keep E copies of the feed-forward block, the experts, and let a learned router pick k of them per token, with k usually 1 or 2. Parameters grow with E; compute per token grows with k. The cost moves elsewhere: memory to hold every expert, communication to send tokens to wherever their experts live, and a routing problem that has to be trained to stay balanced.
Intuition
Think of a help desk with many specialists and a receptionist. Each question goes to the one or two specialists the receptionist thinks fit best, and the answer is a blend of theirs, weighted by the receptionist's confidence. The desk as a whole knows a great deal; each question only costs two specialists' time. Two things can go wrong: the receptionist can send everyone to the same specialist, who then has a queue while the others sit idle, and a specialist with a full queue has to turn people away. The load-balancing loss and the capacity factor below deal with exactly those two problems.
Four experts hold four feed-forward blocks' worth of parameters; this token's forward pass runs two of them.
Mechanics
Replacing the feed-forward block
In a transformer block, attention mixes information across positions and the feed-forward network (FFN) then transforms each position on its own. Because the FFN is applied token by token, nothing stops different tokens from using different FFNs. A mixture-of-experts layer keeps the attention sublayer and swaps the FFN for E expert FFNs E₁ … EE (each the same shape as the original) plus a router matrix Wᵣ with E rows:
h = Wᵣ x, p = softmax(h), y = Σi ∈ TopK(h) gᵢ(x) · Eᵢ(x)
The router is one linear layer, so it costs E · d multiply-adds per token, tiny next to an expert. The gates gᵢ multiply each expert's output, which is how the router gets gradient: the choice of experts is discrete, but the gate values are differentiable, and making a gate larger for an expert whose output lowered the loss is what teaches the router. Not every block has to be sparse: GShard replaces every other FFN, Mixtral replaces all of them.
Top-k gating
The router keeps the k largest logits. Two gate conventions are common:
- Switch (k = 1). Each token goes to argmax h, and the gate is that expert's full-softmax probability pᵢ, which is below 1. Switch Transformers argues that one expert per token is enough and halves the routing computation and communication compared with k = 2.
- Renormalized top-k (Mixtral, k = 2). g = softmax over the k selected logits only, so the gates sum to 1. This equals taking the full softmax and dividing the chosen probabilities by their sum, because the shared normalizer cancels.
The original sparsely-gated layer (Shazeer et al.) added tunable Gaussian noise to the logits before the top-k, which spreads early routing decisions; GShard used top-2 with the second expert chosen at random in proportion to its weight. Later models mostly use the plain deterministic top-k above.
The load-balancing loss
Left alone, routing collapses: whichever experts start slightly better get more tokens, train faster and get more tokens still, while the rest never learn. The Switch Transformer auxiliary loss pushes back. Over a batch of T tokens, let fᵢ be the fraction of tokens whose top choice is expert i, and Pᵢ the mean router probability for expert i:
Laux = α · E · Σi fᵢ · Pᵢ, α = 0.01 in the paper
fᵢ is a count, so it has no gradient; Pᵢ does, and the gradient of fᵢ · Pᵢ with respect to Pᵢ is fᵢ, so the loss pushes probability away from the experts that are already busiest. When the routing is perfectly uniform, every fᵢ is 1/E and the sum is 1/E whatever the Pᵢ, so E · Σ fᵢ Pᵢ = 1; when every token goes to one expert with probability 1, it is E. The factor E keeps the value comparable as the number of experts changes. ST-MoE adds a router z-loss, the mean over tokens of (log Σⱼ exp hⱼ)², with a small coefficient (10⁻³ in the paper). It keeps the router's logits from growing large, which the authors found made training more stable, especially in low precision.
Capacity and dropped tokens
On accelerators, each expert processes a fixed-size buffer so that shapes are static and the work can be split evenly across devices. Switch Transformers sets
expert capacity = (tokens per batch / number of experts) × capacity factor
A factor of 1 leaves exactly enough slots for perfect balance; values above 1 leave slack for uneven routing, at the price of padding and memory. When more tokens choose an expert than it has slots, the overflow tokens are dropped: they skip the expert computation, and the residual connection carries their input forward unchanged. Which tokens are dropped depends on the order slots are filled; the simplest rule, used below, fills them in position order, so tokens late in the batch are the ones lost. Dropless implementations (MegaBlocks, and the loop-over-experts style in the implementation section) instead give each expert a variable-size batch and drop nothing, at the cost of irregular shapes.
Total versus active parameters
A sparse model is described by two numbers. Total parameters count every expert, and decide the memory needed to hold the model. Active parameters count what one token's forward pass touches: the shared parts (embeddings, attention, norms, routers) plus k experts per MoE layer, and decide the compute per token. Mixtral 8x7B, worked out below, has about 46.7B total and 12.9B active parameters, not 56B, because only the FFNs are replicated. DeepSeek-V3 takes the ratio further, with 671B total and 37B active, using many small experts plus shared experts every token visits.
Active parameters set the FLOPs but not the cost of serving. At small batch sizes decoding is limited by reading weights from memory, and different tokens in the batch touch different experts, so a batch can read most of the total. Across many devices, expert parallelism places different experts on different devices and exchanges tokens with two all-to-all communications per layer, one to dispatch tokens and one to bring the outputs back.
| Component | Shape/Params | Meaning | Source |
|---|---|---|---|
Router nn.Linear(d, E, bias=False) |
weight (E, d); logits (T, E) | One row per expert. Its logits feed both the top-k choice and the softmax used by the gates and the balancing loss. | linear.py 53-147, functional.py 2382-2403 |
Experts nn.ModuleList |
E modules, each an FFN with weights (d_ff, d) and (d, d_ff), three matrices for SwiGLU | Registers every expert so that their parameters are trained, moved and saved with the layer, while the forward pass calls only the chosen ones. | container.py 341-508 |
torch.topk |
(T, E) → values (T, k), indices (T, k) | Picks each token's k largest logits and their expert ids, sorted largest first. | _torch_docs.py 11878-11922 |
F.softmax |
dim −1 | Over all E logits for the probabilities pᵢ; over the k selected logits for renormalized gates. | functional.py 2176-2216 |
Tensor.index_add_ |
dim 0, index (n,), source (n, d) | Scatters each expert's gated outputs back to their token rows, summing where a token used two experts. scatter_add does the same with an index per element. |
_torch_docs.py 373-380, 10009-10016 |
F.one_hot, torch.bincount |
top-1 ids (T,) → (T, E) or counts (E,) | Turn routing decisions into the per-expert counts behind fᵢ and the capacity check. | functional.py 5974-6021, _torch_docs.py 1642-1684 |
F.silu |
elementwise | The activation inside a SwiGLU expert: w₂(silu(w₁x) ⊙ w₃x). | functional.py 2429-2451 |
The top-k selection is not differentiable. How does the router learn at all?
Through the gates. The layer's output is Σ gᵢ Eᵢ(x), and each gᵢ is a softmax of the router's logits, so the loss has a gradient with respect to the router weights through every selected expert's gate: an expert whose output helped gets a larger gate next time, which raises its logit relative to the others. Experts that were not selected get no gradient from that token, apart from the balancing loss, which acts through the mean probabilities Pᵢ.
Worked example
Routing eight tokens
Setup. 8 tokens with d = 4 ("The cat sat on a warm red mat"), 4 experts, and a router whose rows are (1.2, 0.3, −0.4, 0.5), (−0.2, 1.0, 0.3, −0.1), (0.1, −0.3, 1.1, 0.2), (−0.4, 0.2, 0.0, 1.0). Each token's logits are its dot products with the rows; the table shows the softmax, the top-1 (Switch) choice and gate, and the top-2 choice with renormalized gates.
| Token | Logits h | softmax p | Top-1: expert, gate | Top-2: experts, gates |
|---|---|---|---|---|
| 0 "The" | (1.61, −0.18, −0.45, −0.06) | (0.67, 0.11, 0.09, 0.13) | 0, 0.674 | 0, 3: 0.842, 0.158 |
| 1 "cat" | (0.97, 0.39, 0.00, −0.42) | (0.46, 0.26, 0.17, 0.11) | 0, 0.457 | 0, 1: 0.641, 0.359 |
| 2 "sat" | (−0.19, 1.28, 0.08, 0.34) | (0.12, 0.52, 0.16, 0.20) | 1, 0.520 | 1, 3: 0.719, 0.281 |
| 3 "on" | (1.43, −0.40, 0.02, 0.22) | (0.59, 0.09, 0.14, 0.18) | 0, 0.587 | 0, 3: 0.770, 0.230 |
| 4 "a" | (−0.47, 0.68, 1.16, −0.38) | (0.10, 0.30, 0.49, 0.11) | 2, 0.493 | 2, 1: 0.618, 0.382 |
| 5 "warm" | (1.61, 0.08, −0.32, −0.26) | (0.66, 0.14, 0.10, 0.10) | 0, 0.660 | 0, 1: 0.822, 0.178 |
| 6 "red" | (−0.28, 0.31, 0.41, 1.28) | (0.10, 0.19, 0.21, 0.50) | 3, 0.498 | 3, 2: 0.705, 0.295 |
| 7 "mat" | (1.21, 0.74, −0.16, 0.10) | (0.45, 0.28, 0.12, 0.15) | 0, 0.453 | 0, 1: 0.615, 0.385 |
For token 0, the full softmax gives experts 0 and 3 probabilities 0.674 and 0.127; dividing each by their sum 0.801 gives 0.842 and 0.158, the same as a softmax over just those two logits. The router already favours expert 0: 5 of 8 tokens pick it first.
The balancing loss
Top-1 counts per expert are 5, 1, 1, 1, so f = (0.625, 0.125, 0.125, 0.125). The mean probabilities are P = (0.394, 0.238, 0.184, 0.184). E · Σ fᵢ Pᵢ = 4 × (0.625 × 0.394 + 0.125 × 0.238 + 0.125 × 0.184 + 0.125 × 0.184) = 1.2880, and with α = 0.01 the auxiliary loss is 0.01288. For comparison, a perfectly balanced routing (two tokens per expert, uniform probabilities) gives 1.0, and sending every token to expert 0 with probability 1 gives 4.0. The gradient on Pᵢ is proportional to fᵢ, so expert 0's mean probability gets 5 times the push-down of each other expert's. The router z-loss over these tokens is 3.7734: the mean squared log-sum-exp of the logits.
Capacity and dropped tokens
With T = 8 and E = 4, perfect balance is 2 tokens per expert. Capacity is that times the factor, rounded down here. Slots fill in token order, so expert 0, chosen by tokens 0, 1, 3, 5, 7, overflows:
| Capacity factor | Capacity per expert | Total slots | Load per expert | Dropped tokens |
|---|---|---|---|---|
| 1.0 | 2 | 8 | 2, 1, 1, 1 | 3 "on", 5 "warm", 7 "mat" |
| 1.25 | 2 | 8 | 2, 1, 1, 1 | 3 "on", 5 "warm", 7 "mat" |
| 1.5 | 3 | 12 | 3, 1, 1, 1 | 5 "warm", 7 "mat" |
| 2.0 | 4 | 16 | 4, 1, 1, 1 | 7 "mat" |
Factors 1.0 and 1.25 give the same capacity, because 2 × 1.25 rounds down to 2; with real batches of thousands of tokens the rounding barely matters. Even at factor 2.0, with 16 slots for 8 tokens, token 7 is dropped, because the slack is spread evenly across experts while the load is not. More capacity is a poor fix for imbalance; the balancing loss is what makes a factor near 1 workable.
Red squares are dropped at capacity factor 1.0: they pass through the residual connection without any expert. Doubling the factor saves two of them and leaves experts 1 to 3 three-quarters empty.
One token's output
To keep the arithmetic visible, each toy expert here is a single 4 × 4 matrix followed by ReLU, not a full FFN. Token 1 "cat" is x = (0.8, 0.5, 0.1, −0.2). Its top-2 experts produce
- E0(x) = relu((0.8, 0.5, 0.1, −0.2) through rows (−0.2, 0.3, −0.1, −0.2), (−0.6, −0.1, 0.7, 0.3), (0.6, 0.1, 0.2, 0.1), (−1.0, 0.5, 0.3, 0.3)) = (0.02, 0.00, 0.53, 0.00)
- E1(x) = relu((0.8, 0.5, 0.1, −0.2) through rows (−1.0, −1.0, −0.5, −0.3), (0.2, 0.0, 0.3, −0.4), (0.2, 0.2, −0.4, 1.0), (0.3, 0.7, −0.4, −0.4)) = (0.00, 0.27, 0.02, 0.63)
With gates 0.641 and 0.359, the layer output is 0.641 · E0(x) + 0.359 · E1(x) = (0.01, 0.10, 0.35, 0.23), which the residual adds to x. Under Switch top-1 routing the token uses only expert 0 with gate 0.457, giving (0.01, 0.00, 0.24, 0.00): a smaller output, since a single full-softmax gate is below 1, and none of expert 1's contribution.
Mixtral 8x7B: total and active parameters
The Mixtral paper's architecture table gives d = 4096, 32 layers, 32 query heads and 8 key-value heads of size 128, SwiGLU experts with hidden size 14336, 8 experts with top-2 routing in every layer, and a 32000-token vocabulary. Counting weights, assuming untied input and output embeddings and no biases:
| Part | Formula | Parameters |
|---|---|---|
| Attention, per layer | 2 · d · heads · head_dim + 2 · d · kv_heads · head_dim | 41.9M |
| One expert | 3 · d · d_ff | 176.2M |
| Router, per layer | d · E | 32,768 |
| Norms, per layer | 2 · d | 8,192 |
| Embeddings, output head and final norm | 2 · vocab · d + d | 262.1M |
| Total | layers · (attention + 8 experts + router + norms) + embeddings | 46.70B |
| Active per token | the same with 2 experts | 12.88B |
That reproduces the paper's "47B total, 13B active". Experts are 96.6% of the total, which is why the name's eight 7B models add up to 47B, not 56B: only the FFNs are replicated, while attention and embeddings are shared. Per token the model does the work of a 13B dense model, but serving it needs memory for all 47B, about 93 GB in 16-bit weights.
Compute follows the lower bar, memory the upper one.
Implementation
import torch
import torch.nn as nn
import torch.nn.functional as F
class SwiGLU(nn.Module):
def __init__(self, d, d_ff):
super().__init__()
self.w1 = nn.Linear(d, d_ff, bias=False)
self.w3 = nn.Linear(d, d_ff, bias=False)
self.w2 = nn.Linear(d_ff, d, bias=False)
def forward(self, x):
return self.w2(F.silu(self.w1(x)) * self.w3(x))
class MoE(nn.Module):
def __init__(self, d, d_ff, num_experts, k=2, capacity=None):
super().__init__()
self.router = nn.Linear(d, num_experts, bias=False)
self.experts = nn.ModuleList(SwiGLU(d, d_ff) for _ in range(num_experts))
self.k, self.capacity = k, capacity
def forward(self, x): # x: (T, d), batch and sequence flattened
logits = self.router(x) # (T, E)
probs = logits.softmax(-1)
top_logits, top_idx = logits.topk(self.k, dim=-1) # (T, k), largest first
gates = top_logits.softmax(-1) # renormalized over the k chosen
out = torch.zeros_like(x)
for e, expert in enumerate(self.experts):
token, slot = (top_idx == e).nonzero(as_tuple=True) # in token order
if self.capacity is not None:
token, slot = token[:self.capacity], slot[:self.capacity] # the rest are dropped
if token.numel():
out.index_add_(0, token, gates[token, slot, None] * expert(x[token]))
E = len(self.experts)
f = F.one_hot(top_idx[:, 0], E).float().mean(0) # share of tokens whose first choice is e
aux = E * (f * probs.mean(0)).sum() # 1 when balanced, E when collapsed
z = torch.logsumexp(logits, -1).pow(2).mean()
return out, aux, z
# In the block: h = h + moe_out; in the loss: loss = task_loss + 0.01 * aux + 0.001 * z
The loop over experts gathers each expert's tokens, runs the expert once on that sub-batch and scatters the gated results back with index_add_, so a token with two experts gets the sum of both. With capacity=None nothing is dropped. Production kernels replace the Python loop with a sort of tokens by expert and a grouped matrix multiply, and across devices with an all-to-all exchange, but the arithmetic is the same.
Two details matter in practice: compute the router in float32 even under mixed precision, since small logit errors flip discrete choices, and count the auxiliary losses per MoE layer, summing them across layers.
Tradeoffs
| Design | Compute per token | Memory | Training | Use for |
|---|---|---|---|---|
| Dense FFN | all parameters | all parameters | simple and stable | small models, low-memory serving, fine-tuning on little data |
| Top-1 (Switch) | shared + 1 expert | shared + E experts | cheapest routing; needs balancing and capacity tuning | pretraining many parameters on a fixed compute budget |
| Top-2 (GShard, Mixtral) | shared + 2 experts | shared + E experts | smoother gradients to the router; twice the expert compute and communication of top-1 | the common default for open-weight MoE language models |
| Many small experts plus shared experts (DeepSeekMoE) | shared experts + k small ones | very large | finer specialization; more routing work per token | very high total-to-active ratios |
- Quality per FLOP improves, quality per byte does not. An MoE model usually beats a dense model with the same active parameters, and usually loses to a dense model with the same total parameters. Choose it when compute, not memory, is the limit.
- Serving is harder. All experts must be resident, batches spread across experts unevenly, and at low batch sizes memory bandwidth, which scales with total parameters read, dominates. Quantization and expert offloading help.
- Training is less stable. Routing can collapse or oscillate; the balancing and z losses, float32 routers and careful initialization are standard. Fine-tuning a sparse model has been reported to overfit more readily than its dense counterpart.
- Experts do not specialize the way the name suggests. Analyses of Mixtral found little specialization by topic and more by syntax and token type, with consecutive tokens often going to the same expert.
A team doubles the capacity factor to stop tokens being dropped, and training gets slower without getting better. What happened?
Capacity is reserved for every expert, so doubling the factor doubles every expert's buffer, padding included, and the memory and communication with it. If the drops come from imbalance, most of the new slots sit empty on the quiet experts while the busy ones still overflow. Fixing the routing, with a larger balancing coefficient or a check that the auxiliary loss is actually being added, attacks the cause.
Why can a model with 13B active parameters be slower to serve than a 13B dense model, even at the same FLOPs per token?
Decoding a small batch is limited by reading weights from memory, not by arithmetic. A dense 13B model reads 13B parameters per step. In the MoE model, each token reads its own two experts per layer, and the tokens in a batch choose different experts, so a step can read most of the 47B, and the model needs memory, or multiple devices with all-to-all communication, to hold it all.
Connections
Read first
- The Transformer core
Read next
No page builds on this one yet.
The transformer page builds the block whose feed-forward sublayer the experts replace, and decoder-only language models are where MoE is now most used. Expert parallelism is one more axis alongside the data, tensor and pipeline parallelism of distributed training, and the memory-bound serving problem is the one on latency and throughput. The idea of combining specialists by a learned weighting goes back to ensembles like random forests, with the difference that a router picks a few members per input instead of averaging all of them.
References
- Jacobs, Jordan, Nowlan and Hinton. Adaptive Mixtures of Local Experts. Neural Computation 1991. doi:10.1162/neco.1991.3.1.79
- Shazeer et al. Outrageously Large Neural Networks: The Sparsely-Gated Mixture-of-Experts Layer. ICLR 2017. arXiv:1701.06538
- Lepikhin et al. GShard: Scaling Giant Models with Conditional Computation and Automatic Sharding. 2020. arXiv:2006.16668
- Fedus, Zoph and Shazeer. Switch Transformers: Scaling to Trillion Parameter Models with Simple and Efficient Sparsity. JMLR 2022. arXiv:2101.03961
- Zoph et al. ST-MoE: Designing Stable and Transferable Sparse Expert Models. 2022. arXiv:2202.08906
- Gale et al. MegaBlocks: Efficient Sparse Training with Mixture-of-Experts. MLSys 2023. arXiv:2211.15841
- Jiang et al. Mixtral of Experts. 2024. arXiv:2401.04088
- Dai et al. DeepSeekMoE: Towards Ultimate Expert Specialization in Mixture-of-Experts Language Models. 2024. arXiv:2401.06066
- DeepSeek-AI. DeepSeek-V3 Technical Report. 2024. arXiv:2412.19437
- PyTorch v2.15.0:
nn.ModuleList,nn.Linear,torch.topk,index_add,F.softmax.