Chapter 13 · Section 13.2 · 60 min

Routing tokens to experts

The router of §13.1 has a problem that sounds fatal. It makes a hard, discrete decision (these two experts, not the other six), and a hard decision has no gradient. Nudge a router weight by a tiny amount and, almost always, the same two experts win, so the derivative of "which experts" with respect to the router is zero almost everywhere. How does the router learn anything?

And a second problem waits on the hardware side. A GPU wants to know the shape of a matrix product before it runs it. Routing decides, token by token, how many rows each expert's product will have, and nothing stops the router from sending half the batch to one expert. This section solves both problems. It derives how gradients reach the router, defines the gating functions real models use, introduces expert capacity and the token dropping that comes with it, describes the alternative where experts choose tokens, and builds the sort, gather, and scatter implementation that every fast MoE kernel is organized around.

How a token chooses experts

For a batch we flatten all sequences into TT tokens, X∈RT×d\mathbf{X} \in \R^{T\times d} with row xt\mathbf{x}_t. The router is a matrix Wr∈Rd×E\mathbf{W}_r \in \R^{d\times E} for EE experts, and it produces one logit per token and expert,

R=XWr∈RT×E,rt=xtWr.\mathbf{R} = \mathbf{X}\mathbf{W}_r \in \R^{T\times E}, \qquad \mathbf{r}_t = \mathbf{x}_t\mathbf{W}_r .

Each token keeps the set Kt\mathcal{K}_t of its kk largest logits. Then the logits become gates gt,ig_{t,i}, the weights in yt=∑i∈Ktgt,iFFN⁡i(xt)\mathbf{y}_t = \sum_{i\in\mathcal{K}_t} g_{t,i}\operatorname{FFN}_i(\mathbf{x}_t). Three conventions are in use, and they are not interchangeable.

  • Top-k, then softmax. gt,i=ert,i/∑j∈Ktert,jg_{t,i} = e^{r_{t,i}}/\sum_{j\in\mathcal{K}_t} e^{r_{t,j}} for i∈Kti \in \mathcal{K}_t. Shazeer et al. (2017) and Mixtral use this. The gates of each token sum to 1.
  • Softmax, then top-k. pt=softmax⁡(rt)\mathbf{p}_t = \operatorname{softmax}(\mathbf{r}_t) over all EE experts and gt,i=pt,ig_{t,i} = p_{t,i} for i∈Kti\in\mathcal{K}_t, without renormalizing. The Switch Transformer (with k=1k = 1) and DeepSeek-V2 use this. The gates of a token sum to less than 1.
  • Sigmoid, then top-k, then normalize. st,i=σ(rt,i)s_{t,i} = \sigma(r_{t,i}), and the chosen ss values are divided by their sum. DeepSeek-V3 uses this, with a twist for load balancing that §13.3 and §13.5 explain.

The first two are closer than they look. Renormalizing the full softmax over the chosen set gives back the first convention exactly, because the full partition function cancels,

pt,i∑j∈Ktpt,j=ert,i/Zt∑j∈Ktert,j/Zt=ert,i∑j∈Ktert,j,Zt=∑j=1Eert,j.\frac{p_{t,i}}{\sum_{j\in\mathcal{K}_t} p_{t,j}} = \frac{e^{r_{t,i}}/Z_t}{\sum_{j\in\mathcal{K}_t} e^{r_{t,j}}/Z_t} = \frac{e^{r_{t,i}}}{\sum_{j\in\mathcal{K}_t} e^{r_{t,j}}}, \qquad Z_t = \sum_{j=1}^{E} e^{r_{t,j}} .

In plain words, "softmax over the chosen logits" and "softmax over everything, then renormalize the chosen ones" are the same function. The only real choice is whether to renormalize.

That choice matters most at k=1k = 1. With renormalization, a single chosen expert always gets gate 11, so the output yt=FFN⁡i(xt)\mathbf{y}_t = \operatorname{FFN}_i(\mathbf{x}_t) does not depend on the router's logits at all, and the router receives no gradient from the language-modeling loss. Shazeer et al. conjectured that routing to at least two experts was needed for useful router gradients. Fedus et al. (2022) showed that k=1k = 1 works if the gate is the unnormalized probability pt,ip_{t,i}, because then the output is scaled by how confident the router was, and that scale has a gradient.

Python · cell 1
Interview

A top-k router

Implement top_k_router(logits, k) for router logits of shape (T, E). Return (idx, gates), both of shape (T, k). idx[t] holds the indices of token tt's kk largest logits in descending order of logit (break ties by the smaller expert index), and gates[t] is the softmax over just those kk logits, in the same order. It must be numerically stable for logits in the thousands, and must not loop over tokens.

Hidden tests, hints, and AI help, from $1.99 a month.

How the router gets a gradient

The choice of experts has no gradient, but the gates do, and that is enough. Fix one token and drop its index. Let the chosen set be K\mathcal{K}, the gates gi=eri/∑j∈Kerjg_i = e^{r_i}/\sum_{j\in\mathcal{K}} e^{r_j}, the expert outputs fi=FFN⁡i(x)\mathbf{f}_i = \operatorname{FFN}_i(\mathbf{x}), and the upstream gradient G=∂L/∂y\mathbf{G} = \partial\mathcal{L}/\partial\mathbf{y}, a row vector the size of the output. Since y=∑i∈Kgifi\mathbf{y} = \sum_{i\in\mathcal{K}} g_i\mathbf{f}_i, each gate's gradient is a dot product,

ai=∂L∂gi=G⋅fi.a_i = \frac{\partial\mathcal{L}}{\partial g_i} = \mathbf{G}\cdot\mathbf{f}_i .

The gates are a softmax over the chosen logits, whose Jacobian (§0.2) is ∂gi/∂rj=gi(δij−gj)\partial g_i/\partial r_j = g_i(\delta_{ij} - g_j) for i,j∈Ki, j \in \mathcal{K}. A logit outside K\mathcal{K} does not appear in any gate, so as long as a small change does not alter the selection, its derivative is zero. The chain rule gives, for j∈Kj\in\mathcal{K},

∂L∂rj=∑i∈Kai gi(δij−gj)=gj(aj−∑i∈Kgi ai),∂L∂rj=0  for j∉K.\frac{\partial\mathcal{L}}{\partial r_j} = \sum_{i\in\mathcal{K}} a_i\, g_i(\delta_{ij} - g_j) = g_j\Big(a_j - \sum_{i\in\mathcal{K}} g_i\, a_i\Big), \qquad \frac{\partial\mathcal{L}}{\partial r_j} = 0 \ \text{ for } j \notin\mathcal{K} .

In plain words, among the experts it tried, the router turns up the ones whose outputs pointed more downhill than the gate-weighted average, and turns down the others. It learns nothing at all about the experts it did not try.

That second sentence is the heart of routing. The router is in the position of a gambler who only sees the payoff of the machines it plays. If one expert happens to be chosen often early on, it trains more, gets better, gets chosen even more, and the others never get a chance to show what they could do. This is why routers are given noise, auxiliary losses, or bias corrections to keep exploring, the subject of §13.3. The gradient into Wr\mathbf{W}_r follows as usual, ∂L/∂Wr=X⊤ ∂L/∂R\partial\mathcal{L}/\partial\mathbf{W}_r = \mathbf{X}^\top\,\partial\mathcal{L}/\partial\mathbf{R}, and the gradient into each expert's weights is the expert's ordinary backward pass with its upstream gradient scaled by the gate, giGg_i\mathbf{G}.

Python · cell 2

The two agree, and the four unchosen experts get exactly zero.

The unnormalized convention changes this picture in one instructive way. With gi=pig_i = p_i taken from the full softmax over all EE experts, every logit appears in every gate through the partition function, and the Jacobian ∂pi/∂rj=pi(δij−pj)\partial p_i/\partial r_j = p_i(\delta_{ij} - p_j) holds for all jj, so

∂L∂rj=∑i∈Kai pi(δij−pj)=pj(aj 1[j∈K]−∑i∈Kpi ai)for every j∈[E].\frac{\partial\mathcal{L}}{\partial r_j} = \sum_{i\in\mathcal{K}} a_i\, p_i(\delta_{ij} - p_j) = p_j\Big(a_j\,\mathbb{1}[j\in\mathcal{K}] - \sum_{i\in\mathcal{K}} p_i\, a_i\Big) \quad\text{for every } j\in[E].

An unchosen expert now receives a gradient, but it is −pj∑i∈Kpiai-p_j\sum_{i\in\mathcal{K}}p_ia_i, the same signal for every unchosen expert scaled only by its probability. It says how much the chosen experts helped, not whether the skipped ones would have. When the chosen experts helped (∑i∈Kpiai<0\sum_{i\in\mathcal{K}}p_ia_i < 0), gradient descent lowers every unchosen logit, which sharpens the router toward the choice it already made. In plain words, the full softmax lets the router learn how confident to be, which is what keeps k=1k = 1 trainable, but it still learns nothing about the quality of experts it did not try.

Core

The gradient of the loss with respect to the router logits

Implement router_logit_grad(r, F_out, G, k) for one token, with logits r of shape (E,), every expert's output F_out of shape (E, m), and upstream gradient G of shape (m,). The layer uses top-k then softmax gating. Return ∂L/∂r\partial\mathcal{L}/\partial\mathbf{r} of shape (E,), zero for experts that were not chosen.

Hidden tests, hints, and AI help, from $1.99 a month.

Noisy top-k gating

Shazeer et al. (2017) added noise to the logits before choosing, with a learned, input-dependent scale,

r~t,i=rt,i+ϵt,i softplus⁡(xtWnoise)i,ϵt,i∼N(0,1),\tilde r_{t,i} = r_{t,i} + \epsilon_{t,i}\,\operatorname{softplus}\big(\mathbf{x}_t\mathbf{W}_{\text{noise}}\big)_i, \qquad \epsilon_{t,i}\sim\mathcal{N}(0, 1),

with Wnoise∈Rd×E\mathbf{W}_{\text{noise}}\in\R^{d\times E} and softplus⁡(z)=log⁡(1+ez)\operatorname{softplus}(z) = \log(1 + e^z) to keep the scale positive. The top kk of r~t\tilde{\mathbf{r}}_t are chosen and softmaxed. In plain words, the router sometimes tries an expert just outside its current top kk, so that expert gets a chance to prove itself, and the model can learn how much exploration each token needs.

The noise had a second job. It makes "is expert ii in the top kk?" a random event with a smooth probability, computable with the normal CDF, so Shazeer et al. could write a differentiable estimate of each expert's load and penalize imbalance (§13.3). Later Transformer MoEs kept the exploration but made it lighter. Switch Transformers compared several options in an appendix and settled on input jitter, multiplying the router's input by random factors near 1, which scored slightly better than plain argmax and clearly better than sampling from the softmax. Balancing itself moved to auxiliary losses and bias terms. The cell shows the exploration effect on one token whose third-best expert is just behind the second.

Python · cell 3

Without noise the token would use experts 0 and 1 every time. With noise, expert 2 is in the top two about half the time and receives gradient on those steps, while experts 3 and 4, far behind, are almost never tried. The noise explores near the boundary, not at random.

Expert capacity

Now the hardware problem. On an accelerator each expert processes its tokens as one matrix product, and the simplest fast implementation gives every expert a buffer of fixed size, its capacity. With TT tokens, kk choices each, and EE experts, a perfectly balanced router would give each expert kT/EkT/E tokens. Capacity is that fair share times a slack factor, the capacity factor CF\text{CF},

cap⁡=⌈CF⋅kTE⌉.\operatorname{cap} = \Big\lceil \text{CF}\cdot\frac{kT}{E}\Big\rceil .

In plain words, each expert agrees to take at most CF\text{CF} times its fair share of tokens. (Implementations differ in rounding, and GShard computes it per group of tokens rather than per batch.)

A token that arrives at a full expert is dropped for that expert. It is not lost. Its contribution from that expert is zero, and because the MoE layer sits inside a residual connection, the token's vector passes to the next layer unchanged, as if the FFN had output zero. If a token's other choices had room, it still gets their (now smaller) sum. Every drop is computation the layer skipped for that token, so the capacity factor is a real trade-off. Larger CF\text{CF} drops fewer tokens but pads buffers with empty slots that cost memory, compute, and communication. Switch Transformers trained with CF=1.0\text{CF} = 1.0 or 1.251.25, found the lower factors gave the better speed and quality trade-off, and report that typically fewer than 1% of tokens were dropped. ST-MoE (Zoph et al. 2022) used 1.25 in training and 2.0 at evaluation.

Who gets dropped when an expert overflows? The usual rule is arrival order, the position of the token in the flattened batch, with first choices served before second choices. GShard places every token's first choice, in token order, and only then the second choices into whatever room is left. Riquelme et al. (2021) proposed batch prioritized routing, which serves tokens with larger gates first, so the tokens the router is most confident about are the last to be dropped.

A worked example

Eight tokens, E=4E = 4 experts, k=1k = 1, CF=1\text{CF} = 1, so cap⁡=⌈8/4⌉=2\operatorname{cap} = \lceil 8/4\rceil = 2. The router sends tokens 0 to 7 to experts (0,1,0,0,2,0,1,3)(0, 1, 0, 0, 2, 0, 1, 3). Expert 0 receives tokens 0 and 2 into its two slots, and tokens 3 and 5 find it full, so they are dropped. Experts 1, 2, and 3 receive two, one, and one tokens, all accepted, leaving two empty slots. Two of eight tokens (25%) are dropped and two of eight slots are wasted padding, even though only one expert was overloaded. The widget below runs the same bookkeeping on 24 tokens, with a router you can skew toward one expert.

Which tokens get dropped when experts have a fixed capacity?
24 tokens, 4 experts. Each expert has capacity ⌈CF · k · T / E⌉ slots. Lines are accepted assignments, pink marks are dropped ones, empty boxes are padding.
Loading interactive figure…

Even a perfectly fair router drops tokens, because randomness alone makes some experts busier than others. If each token picks one of EE experts uniformly and independently, an expert's load is binomial with mean T/ET/E and standard deviation about T/E\sqrt{T/E}, and the expected fraction dropped is

drop=ET E[max⁡(0, load−cap⁡)],load∼Binomial⁡(T,1/E).\text{drop} = \frac{E}{T}\,\mathbb{E}\big[\max(0,\ \text{load} - \operatorname{cap})\big], \qquad \text{load} \sim \operatorname{Binomial}(T, 1/E).

The fluctuations shrink relative to the mean as TT grows, so big routing groups drop less at the same capacity factor.

Python · cell 4

A uniform random router at CF=1\text{CF} = 1 still drops 13% of a 64-token group and 1.6% of a 4,096-token group. Real routers are less balanced than uniform, so their drop rates are higher, which is why balancing (§13.3) and capacity go together.

Interview

Capacity-limited dispatch with token dropping

Implement dispatch(idx, E, cap) for the router's choices idx of shape (T, k) (column 0 is each token's first choice). Return pos, an integer array of shape (T, k) holding the slot each assignment gets in its expert's buffer (0 to cap - 1), or -1 if it is dropped. Follow GShard's rule. All first choices are placed before any second choice, and so on, and within one choice rank tokens are served in arrival order (by token index). An expert accepts assignments until it holds cap of them.

Interviewers often ask for the loop first and then "now do it without a loop over tokens". The tests accept either, but try the vectorized version with a cumulative sum.

Hidden tests, hints, and AI help, from $1.99 a month.

Experts choose tokens

Token-choice routing lets every token pick its favorite experts and then deals with the crowding. Zhou et al. (2022) turned it around. In expert-choice routing every expert picks its favorite tokens. Compute the token-to-expert affinities S=softmax⁡(XWr)∈RT×E\mathbf{S} = \operatorname{softmax}(\mathbf{X}\mathbf{W}_r) \in \R^{T\times E} (a softmax over experts for each token), and let each expert ii take the cap⁡\operatorname{cap} tokens with the largest scores in column ii,

Ti=TopK⁡(S:,i, cap⁡),cap⁡=T⋅cE,yt=∑i : t∈TiSt,i FFN⁡i(xt),\mathcal{T}_i = \operatorname{TopK}\big(\mathbf{S}_{:,i},\ \operatorname{cap}\big), \qquad \operatorname{cap} = \frac{T\cdot c}{E}, \qquad \mathbf{y}_t = \sum_{i\,:\,t\in\mathcal{T}_i} S_{t,i}\,\operatorname{FFN}_i(\mathbf{x}_t),

where cc plays the role of the capacity factor (the paper's default is c=2c = 2, matching the compute of top-2 token choice). In plain words, each expert fills its buffer with the tokens that want it most.

Three consequences follow. Load is perfectly balanced by construction, since every expert processes exactly cap⁡\operatorname{cap} tokens, so no balancing loss is needed and no buffer slot is wasted. Tokens receive a variable number of experts, from zero to EE, which spends more compute on tokens many experts find relevant. Zhou et al. report that most tokens in their analysis went to one or two experts, 23% to three or four, and about 3% to more than four, and that expert choice improved training convergence by more than 2x over Switch and GShard routing at the same compute. And tokens can go unprocessed, if no expert ranks them in its top cap⁡\operatorname{cap}.

The catch is causality. Whether token tt is chosen depends on how the other tokens in the batch score, including tokens that come later in the same sequence. In training a decoder that leaks information about the future into the present (by counting the possible routing patterns, Wang et al. 2024 show that a 9-layer model with 16 experts and two per token could leak more than 50 bits per token, enough for a token to learn which token comes next), and at inference the future tokens do not exist yet, so the routing cannot be reproduced token by token. Zhou et al. note the method may not apply directly to autoregressive generation. That is why expert choice is used in encoders (Komatsuzaki et al. use it in the T5 encoder and ViT, §13.5), while decoder-only LLMs route by token choice.

A third design enforces balance exactly from the token side. BASE layers (Lewis et al. 2021) treat routing during training as a linear assignment problem, giving every expert exactly T/ET/E tokens of the batch while maximizing the total affinity, and route each token greedily to its best expert at inference. The training assignment has the same dependence on the rest of the batch as expert choice. Clark et al.'s S-BASE router (§13.5) approximates the assignment with a few Sinkhorn iterations, which rescale the rows and columns of the affinity matrix until every expert's column sums to its share.

Interview

Expert-choice routing

Implement expert_choice(S, cap) for affinities S of shape (T, E). Each expert ii takes the cap tokens with the largest S[:, i] (ties go to the smaller token index). Return (tok, gates, n_experts) where tok of shape (E, cap) lists expert ii's chosen tokens in descending order of score, gates of shape (E, cap) holds the matching scores, and n_experts of shape (T,) counts how many experts chose each token. No Python loop over tokens.

Hidden tests, hints, and AI help, from $1.99 a month.

Sort, gather, compute, scatter

A loop over tokens is hopeless on a GPU, and even the loop over experts in §13.1 hides a cost, because each nonzero call scans the whole batch. Fast MoE layers reorganize the batch so that each expert's tokens sit next to each other in memory, run each expert on its contiguous block, and then put the results back. Here are the steps, with shapes, for TT tokens and kk choices each.

  1. Route. Get idx and gates, both (T,k)(T, k). Flatten to TkTk assignments, with expert = idx.ravel() and token = repeat(arange(T), k).
  2. Sort. order = argsort(expert, stable) groups the assignments by expert. counts = bincount(expert, minlength=E) gives each expert's number of rows, and offsets = [0, cumsum(counts)] gives where each expert's block starts.
  3. Gather. Xs = X[token[order]], shape (Tk,d)(Tk, d). This is the permutation of tokens into expert order. Each token appears kk times.
  4. Compute. Expert ii processes rows offsets[i] to offsets[i+1] of Xs. On a GPU these EE products of different heights run as one grouped GEMM (§13.4).
  5. Scatter. Multiply each output row by its gate and add it back to its token's row, out[token[order]] += gates[order] * Ys. Because a token appears kk times, this must be a scatter-add, not an assignment.

The backward pass mirrors it. The gradient of a gather is a scatter-add, and the gradient of a scatter-add is a gather, so autograd handles both. With capacity, step 2 also discards assignments whose slot is −1-1 (or pads each block to cap⁡\operatorname{cap} rows), which is what turns this into the fixed-shape version.

Python · cell 5
Interview

A vectorized MoE layer

This is the MoE coding question in its most common form. Implement moe_forward(X, W_r, experts, k) for tokens X of shape (T, d), router W_r of shape (d, E), and a list of EE SwiGLU experts, each a tuple (W_g, W_u, W_d) with FFN⁡(x)=(SiLU⁡(xWg)⊙xWu)Wd\operatorname{FFN}(\mathbf{x}) = (\operatorname{SiLU}(\mathbf{x}\mathbf{W}_g)\odot\mathbf{x}\mathbf{W}_u)\mathbf{W}_d. Use top-k then softmax gating (ties to the smaller expert index) and no capacity limit. Return the output of shape (T, d). You may loop over experts but not over tokens, and an expert that receives no tokens must not break anything. The tests compare with a token-by-token reference and count the lines your function executes on 2,000 tokens.

Hidden tests, hints, and AI help, from $1.99 a month.

In PyTorch

The same algorithm in PyTorch uses argsort, bincount, split for the per-expert blocks, and index_add_ for the scatter. Autograd differentiates through the gather and scatter, and the router gets its gradient through gates, exactly as derived above.

🔥 In PyTorchfor a machine with PyTorch
routingwho choosesload balancetokens per expertexperts per tokencausal decodingused by
top-k token choice, softmax over chosentokenneeds help (§13.3)variable, cappedexactly kk (fewer if dropped)yesShazeer 2017, Mixtral
top-1, unnormalized softmaxtokenneeds helpvariable, capped1 or 0yesSwitch
sigmoid, bias-adjusted top-ktokenbias termsvariable, no droppingexactly kkyesDeepSeek-V3
expert choiceexpertperfect by constructionexactly cap⁡\operatorname{cap}0 to EEnoZhou 2022, upcycled T5 encoders
balanced assignmenta solver over the batchperfect in trainingexactly T/ET/E1only with greedy routingBASE, Lewis 2021
hash routingfixed function of the token idgood if the hash is balancedvariablekkyesRoller 2021

Read more

Watch next

🎬 A Visual Guide to Mixture of Experts (MoE) in LLMs · Maarten Grootendorst · starts at 3:05The router, KeepTopK, token-choice top-k routing, and expert capacity, drawn step by step.
🎬 Mixture of Experts (MoE), Visually Explained · Jia-Bin Huang · starts at 7:22Router design and token-to-expert assignment, with clear pictures of top-k selection.