← All tutorials

Mixture of Experts (MoE)

Top-k routing and even load

On this page

1 What a mixture of experts changes 2 The router picks k experts for each token 3 Without pressure, a few experts take every token 4 Each expert has a fixed number of slots 5 The z-loss keeps router scores small 6 A complete MoE layer in PyTorch 7 References

A dense transformer block sends every token through the same feed-forward network (FFN). A mixture of experts (MoE) block keeps attention and the residual, and swaps that FFN for N FFNs, called experts, plus a small router that picks k of them for each token. The tokens are the pieces a tokenizer cuts text into, as in Tokenizers.

dense blockMoE blockattentionattentionone FFNevery tokenrouterE1E2E3E4++output+outputresidual: x + FFN(x) in both
Figure 1. Only the FFN changes: 1 score, 2 run the chosen experts, 3 add by weight.

1 What a mixture of experts changes

Stored weights grow with N, the number of experts. The work per token grows with k, the number of experts each token uses (Figure 2). The two illustrative toys use the sizes of section 6’s code: token width d = 512 and hidden width 2,048, so one FFN holds 2.1M weights and costs 4.19M floating-point operations (FLOPs) per token. In the Switch Transformer paper (Fedus et al., 2021), Switch-Base with 64 experts matched T5-Base, a dense model with the same compute per token, in one-seventh of the training time. Mixtral (Jiang et al., 2024) is called 8x7B but stores 47B parameters, not 56B, because only the FFNs are copied; attention and the embeddings are shared. DeepSeek-V3 (DeepSeek-AI, 2024) stores 671B and uses 37B per token.

① dense toy FFN100%2.1M of 2.1M used; 4.19M FFN FLOPs per token② MoE toy, 8 experts, top-225%4.2M of 16.8M used; 8.39M FFN FLOPs per token③ Mixtral 8x7B27.7%13B of 47B used; matches or beats Llama 2 70B④ DeepSeek-V35.5%37B of 671B used
Figure 2. Stored (full bar) against used per token (filled); 1 and 2 are illustrative.

Training optimizes the usual next-token loss of The Transformer Block, section 7, plus the two small terms of sections 3 and 5. The router runs at every MoE layer, for every token, on every forward pass, in training and at inference, where that means every generated token (the generate loop). Nothing is carried between steps except the learned weights. Per token, the router costs 2·d·N FLOPs: 8,192 for the illustrative d = 512 and N = 8. The k expert FFNs cost the rest.

2 The router picks k experts for each token

2.1 Scores and softmax

The running example is the prompt “the cat has 3”, four tokens an LLM has read while predicting the next one; the digit is its own token (Tokenizers, section 5.1). Four experts keep the grids readable; Mixtral has 8 per layer. The scores are illustrative values, set up as the start of collapse: E1 already wins the article, the noun and the verb, and only the digit pulls hard to its own expert, as counting tokens did for encoder experts in ST-MoE (Zoph et al., 2022).

Trained routers follow token type and syntax more than topic. Mixtral’s router shows no clear expert per topic, while tokens like “self” in Python and indentation keep returning to the same experts. ST-MoE’s encoder experts specialize in token groups, and its decoder experts far less.

scores zshares pE1E2E3E4E1E2E3E4the2.00.690.50.150.10.10−0.50.06cat1.20.421.00.35−0.20.100.00.13has1.50.540.20.150.80.27−1.00.0430.30.11−0.40.060.10.092.20.74each row of p adds to 1; bold is the top share
Figure 3. Illustrative scores for four tokens; each row of shares adds to 1.

In the “cat” row, the scores 1.2, 1.0, −0.2 and 0.0 become shares 0.42, 0.35, 0.10 and 0.13. The router gives each expert a score for this token with one matrix multiply; a higher score means a bigger share.

The router scores every expert, then softmax turns the scores into shares.

z = x Wr
pi = eziΣj ezj
x is the token’s vector after attention, the input the dense FFN would get. Wr, d by N, is the only new weight matrix: 2,048 numbers for the illustrative d = 512 and 4 experts, against 2,097,152 in one expert.
zi is expert i’s score; scores can be negative, so they are not shares yet. ezi makes each one positive, and dividing by the sum Σj over all N experts makes the shares pi add to 1.
Back to the whole: p is the router’s vote over experts for this one token, computed fresh for every token.
Source: Switch Transformer eq. 1, written there as Wr · x; Shazeer et al. (2017) eq. 2.

2.2 Top-k and the weighted sum

Keep the k largest shares, run only those experts, and blend their outputs by weights called gates. The sparsely gated layer with noisy top-k comes from Shazeer et al. (2017), and the renormalized top-2 from GShard (Lepikhin et al., 2020).

tokenk = 1k = 2theE10.69E10.82E20.18catE10.42E10.55E20.45hasE10.54E10.67E30.333E40.74E40.87E10.13ring: first pick; dot: second pick; orange: gate
Figure 4. Illustrative gates; with k = 2 each token’s two gates add to 1.

The Switch Transformer paper draws the same step for one token.

From the Switch Transformer paper: a router box draws a bar chart of four scores and sends the token to FFN 2 of four FFN experts, FFN 1 to FFN 4. A dotted line carries the gate value p = 0.65 to a multiply sign above FFN 2.
Figure 5. From the Switch paper (their Fig. 2): “…the output of the selected FFN multiplied by the router gate value (dotted-line).”

With the illustrative scores and k = 1, “the”, “cat” and “has” go to E1 and “3” goes to E4, with gates 0.69, 0.42, 0.54 and 0.74. With k = 2, “cat” keeps E1 and E2 with gates 0.55 and 0.45, and E3 and E4 do no work for it. Pick the k experts the router likes best for this token, run only those, and add their outputs weighted by their gates.

Keep the top k shares as gates and add the chosen experts’ outputs.

S = top-k indices of p
gi = pi (k = 1)
gi = piΣj∈S pj (k ≥ 2)
y = Σi∈S gi Ei(x)
S is the chosen set; ∈ S means “one of the chosen”.
Line 2: with one expert, Switch keeps the raw share as the gate, so an unsure router shrinks the output; that is how the task loss reaches the router. A gate fixed at 1 would give it no gradient.
Line 3: with two or more, the kept shares are divided by their sum so the gates add to 1; Mixtral’s softmax over only the kept scores gives the same numbers.
Line 4: Ei(x) is expert i’s FFN on this token. Unchosen experts are never computed: “Wherever G(x)i = 0, we need not compute Ei(x)”.
Back to the whole: y replaces FFN(x), and the residual still adds x + y.
In training, Switch multiplies x by random numbers between 1 − ε and 1 + ε (ε is a small spread). This jitter was the best of four methods it tried; noisy top-k gating adds noise to the scores. Both let idle experts win near ties.
Source: Switch eq. 2, section 2.1, Table 11; Mixtral section 2.1; GShard Algorithm 1; Shazeer et al. section 2.
experts per token

Figure 6. Illustrative scores of Figure 3; arrow width is the gate.

3 Without pressure, a few experts take every token

An expert that wins a few more tokens gets trained more and wins even more. The noisy top-k gating paper saw it: “This imbalance is self-reinforcing, as the favored experts are trained more rapidly and thus are selected even more by the gating network.” In the running example E1 already takes 3 of 4 tokens.

f: where tokens wentP: router wanted0.750.44E100.18E200.14E30.250.24E4dashed line: even share 1/N = 0.25outlines: P after one push; cat now picks E2Laux1.0 even1.56 here4.0 = N, all on E1
Figure 7. Illustrative f and P; arrows show one exaggerated push of the loss.

Count where tokens went, average where the router wanted them to go, and charge a penalty that is smallest when both are even. In this illustrative example the loss is 1.56, against 1.0 for an even split and at most 4.0, which is N, when every token and all the share sit on E1.

The balance loss multiplies where tokens went by where the router wanted them, per expert.

fi = 1T Σx∈B 1{argmaxj pj(x) = i}
Pi = 1T Σx∈B pi(x)
Laux = N Σi fi Pi
L = Ltask + α Laux
B is the batch, all sequences together, and T its number of tokens. 1{…} is 1 when the token’s top choice, argmaxj, is expert i, so fi is the fraction of tokens sent to i; the count is the load ℓi = T fi.
A count has no gradient, so f alone cannot teach the router; Pi averages the smooth shares of the router card, and can.
Line 3: the push on each Pi grows with fi, so the busiest expert’s share is pulled down hardest and the idle ones rise. N in front makes an even split cost 1.0 for any N.
Line 4 is the training loss: Ltask is the next-token loss of section 1, and α sets how hard the balance term pulls against it; α is the knob of Figure 8. Laux never changes the forward pass, and the z-loss card adds β Lz to this same total.
Switch counts only top-1 picks in f; with k = 2 some implementations count both, so f adds to k. Switch puts α inside line 3; this page applies it once, in line 4.
Source: Switch eqs. 4 to 6, section 2.2, α = 10−2; Shazeer et al. section 4.

Repeated every training step, the push spreads the load; its weight α decides how hard it pulls against the task loss, and α = 0 turns it off. Switch picked α = 10−2 as “sufficiently large to ensure load balancing while small enough to not to overwhelm the primary cross-entropy objective”. Token types come in uneven amounts, and each type has an expert that handles it best, its own expert, so perfectly even loads must move some tokens off their own expert. Mixture of Experts Designs, section 1, removes this dilemma with a bias. Take a toy top-1 layer with 8 experts, 8 token types and 512 tokens per step: type i fits Ei best, and the types shrink from 28% of tokens for type 1 to 6% for types 7 and 8; its α is in toy units, not Switch’s.

Figure 8. Raising α pulls bars to 1; past α = 1 it shrinks 2.

4 Each expert has a fixed number of slots

Accelerators run compiled code that needs tensor sizes fixed in advance (Switch, section 2.2), so each expert gets C slots per batch, its capacity. A token past C skips this layer’s experts and rides the residual unchanged.

CF 1.0, C = 1 slot per expertE1theE2emptyE3emptyE43cathas← dropped,ride the residual2 of 4 tokens dropped, 2 of 4 slots emptyCF 2.0, C = 2 slots per expertE1thecatE2emptyemptyE3emptyemptyE43emptyhas← dropped,ride the residual1 of 4 tokens dropped, 5 of 8 slots empty
Figure 9. Illustrative routing: a small capacity factor drops tokens, a large one pads.

With T = 4 tokens, N = 4 experts and k = 1, a fair share is one token per expert, and E1 receives “the”, “cat” and “has” in that order. Give each expert room for its fair share of the batch, times a safety margin.

Capacity is the fair share of the batch times a margin.

C = CF × k TN
kT/N is the fair share: T tokens, k slots each, spread over N experts. CF, the capacity factor, is the margin: 1.0 is none, above 1 adds buffer, below 1 always drops. Code rounds C down.
Back to the whole: too small a C drops tokens; too large leaves empty slots that still cost memory and compute.
Source: GShard section 2.2 for the k form (k = 2); Switch eq. 3 for k = 1; ST-MoE section 4.1. Arrival order follows the cumulative sum (Switch, their Fig. 15).

Even at α = 1 in Figure 8 the busiest expert holds 28% of tokens, 2.25 times its fair share, so CF 1.0 would drop more than half of them. A bigger CF drops fewer tokens and pads more empty slots. Switch typically drops under 1% of tokens and found CF 1.0 and 1.25 best; ST-MoE trains at 1.25 and evaluates at 2.0.

5 The z-loss keeps router scores small

Softmax ignores a shift of all scores (Softmax, Temperature and Decoding, section 1), so scores can drift large without changing any choice. Large scores round badly in bfloat16, the 16-bit format big models train in.

near 2: step 0.0156error of ez up to 0.78%22.5one tick per value bfloat16 can storenear 20: step 0.125error of ez up to 6.45%2020.5one tick per value bfloat16 can storefloat32 has 65,536 times more ticks in each span
Figure 10. bfloat16 keeps 8 significant bits, so its step near 20 is 8 times its step near 2.
shares p for the token “the”scores × 1E1E2E3E40.69z: 2.0, 0.5,0.1, −0.5Lz = 5.15scores × 10E1E2E3E41.00z: 20, 5,1, −5Lz = 314centeredE1E2E3E40.69z: 1.48, −0.03,−0.43, −1.03Lz = 3.17Lz averages all four tokens of the example
Figure 11. Illustrative scores: times 10 raises the z-loss; centering lowers it.

Penalize big router scores, because softmax does not need them and big numbers round badly.

The z-loss charges the size of each token’s scores, and joins the total loss.

a(x) = log Σi ezi
Lz = 1T Σx∈B a(x)2
L = Ltask + α Laux + β Lz
Line 1: a is the log of the router card’s softmax denominator, about the largest score when one dominates.
Line 2: squaring makes large values cost far more; the average runs over the T tokens.
Line 3 is the whole training loss, the balance card’s line 4 plus the z-loss: Ltask is the next-token loss of section 1, and α and β weight the two router terms. β is 0.001 in ST-MoE; at 0.01 and above the z-loss is driven near zero, and 0 removes the term.
bfloat16 keeps 8 significant bits against float32’s 24, so its rounding step grows with the number (Figure 10).
Back to the whole: Lz leaves p unchanged and keeps z where bfloat16 is precise. In ST-MoE, 3 of 3 runs with it trained stably, against 4 of 6 for the baseline.
Source: ST-MoE eqs. 5 and 6 with cz = 0.001, section 3.4, Table 4 and their Fig. 7.

6 A complete MoE layer in PyTorch

6.1 The layer

Router, top-k, dispatch and combine in one module.

import torch
import torch.nn as nn
import torch.nn.functional as F

class MoELayer(nn.Module):
    def __init__(self, d_model, d_ff, n_experts, k=1):
        super().__init__()
        self.k = k
        self.router = nn.Linear(d_model, n_experts, bias=False)
        self.experts = nn.ModuleList(
            nn.Sequential(nn.Linear(d_model, d_ff), nn.ReLU(),
                          nn.Linear(d_ff, d_model))
            for _ in range(n_experts))

    def forward(self, x):             # x: (tokens, d_model)
        xf = x.float()                # router in float32
        z = F.linear(xf, self.router.weight.float())
        p = z.softmax(dim=-1)
        g, idx = p.topk(self.k, dim=-1)
        if self.k > 1:
            g = g / g.sum(dim=-1, keepdim=True)
        y = torch.zeros_like(x)
        for e, expert in enumerate(self.experts):
            tok, slot = (idx == e).nonzero(as_tuple=True)
            if tok.numel() == 0:
                continue
            out = expert(x[tok]) * g[tok, slot, None]
            y = y.index_add(0, tok, out.to(x.dtype))
        return y, z, p, idx
  1. 1The router Wr (router card, line 1) and the N experts Ei, each the dense FFN’s Linear, ReLU, Linear (gate card, line 4).
  2. 2Scores z and shares p, router card lines 1 and 2, computed from float32 copies of x and Wr, as Switch section 2.4 casts the router input; under autocast, turn it off for these lines.
  3. 3The chosen set S and gates g, gate card lines 1 to 3; renormalized only when k > 1.
  4. 4Each expert runs only on its tokens, scaled by their gates, and the results are added back in place, gate card line 4 and Figure 6, step 5.

6.2 Losses and one training step

The two extra losses and one optimizer step with them.

def load_balance_loss(p, idx, n):
    f = F.one_hot(idx[:, 0], n).float().mean(0)  # top-1 counts
    P = p.mean(0)
    return n * (f * P).sum()

def router_z_loss(z):
    return torch.logsumexp(z, dim=-1).pow(2).mean()

ALPHA, BETA = 1e-2, 1e-3
moe = MoELayer(512, 2048, 8, k=1)
opt = torch.optim.AdamW(moe.parameters())
x, target = torch.randn(64, 512), torch.randn(64, 512)
y, z, p, idx = moe(x)
# stand-in for the model's next-token loss
task = F.mse_loss(y, target)
lb = load_balance_loss(p, idx, 8)
zl = router_z_loss(z)
loss = task + ALPHA * lb + BETA * zl
opt.zero_grad()
loss.backward()
opt.step()
  1. 1f, P and Laux, balance card lines 1 to 3, with the top-1 pick in column 0.
  2. 2Lz, z-loss card lines 1 and 2.
  3. 3The total of z-loss card line 3, with α from Switch and β from ST-MoE, then one step.

6.3 Dropping tokens past capacity

A mask of the tokens each expert keeps, in arrival order, for k = 1.

def capacity_mask(top1, n, cf):
    T = top1.numel()
    C = int(cf * T / n)                  # slots per expert
    onehot = F.one_hot(top1, n)
    pos = onehot.cumsum(0) * onehot      # arrival order
    keep = pos.sum(-1) <= C
    return keep
  1. 1The capacity card with k = 1, and Figure 9: top-1 picks 0, 0, 0, 3 at CF 1.0 give keep True, False, False, True.

6.4 Where it plugs in and what the settings do

The layer replaces the FFN call inside each MoE block, and the two losses join the task loss before backward. The router runs in float32 even when the rest runs in bfloat16 (Switch, section 2.4).

knobentersraise itlower itdefault
kgate cardmore compute and communication, more expert combinationsless computeSwitch 1; GShard and Mixtral 2
Nevery cardmore stored weights at the same compute, fewer tokens per expertN = 1 is the dense FFNMixtral 8
αz-loss card, line 3spreads load faster but fights the task losscollapse can startSwitch 10−2, swept 10−1 to 10−5; Hugging Face’s 0.001 is a default, not a published value
CFcapacity cardfewer drops, more paddingbelow 1 always dropsSwitch 1.0 to 1.25; ST-MoE 1.25 train, 2.0 eval
βz-loss card, line 3scores pushed toward 00 removes the termST-MoE 0.001

The Hugging Face Transformers version is modeling_mixtral.py, with MixtralSparseMoeBlock, MixtralTopKRouter and load_balancing_loss_func. A collapsing router does not show in the task loss, only in the load shares; what goes wrong is in Mixture of Experts Designs, section 9.

Bias balancing, the real designs from Switch to DeepSeek-V3, experts across GPUs and fine-tuning continue in Mixture of Experts Designs.

7 References

  1. Shazeer, N., et al. (2017). Outrageously Large Neural Networks: The Sparsely-Gated Mixture-of-Experts Layer. ICLR 2017. arXiv:1701.06538.
  2. Lepikhin, D., et al. (2020). GShard: Scaling Giant Models with Conditional Computation and Automatic Sharding. arXiv:2006.16668.
  3. Fedus, W., Zoph, B., Shazeer, N. (2021). Switch Transformers: Scaling to Trillion Parameter Models with Simple and Efficient Sparsity. JMLR 2022. arXiv:2101.03961.
  4. Zoph, B., et al. (2022). ST-MoE: Designing Stable and Transferable Sparse Expert Models. arXiv:2202.08906.
  5. Jiang, A. Q., et al. (2024). Mixtral of Experts. arXiv:2401.04088.
  6. DeepSeek-AI (2024). DeepSeek-V3 Technical Report. arXiv:2412.19437.
  7. Hugging Face Transformers. modeling_mixtral.py, the Mixtral router, experts and load-balancing loss.