Chapter 11

Mixture of Experts

Mixture of Experts (MoE), the architectural pattern that decouples parameters from compute. Replace the dense feed-forward block with a sparse mixture of expert sub-networks plus a router that picks which experts process each token. The result is dramatically more parameters at roughly the same FLOPs per token. Used by Mixtral, DeepSeek-V3, GLaM, Switch Transformer, and reportedly GPT-4. This chapter covers the MoE block, top-k routing, load balancing, expert capacity, modern variants, and why MoE is harder to train and serve than dense models.

Chapter 10 closed the training-side arc. We now know how to train a dense transformer at any scale, what hardware to use, what frameworks to reach for, what numerical precision to keep, and roughly how many dollars it costs. Chapters 11 and 12 cover the major architectural alternatives, variants that change the building block itself rather than how it is trained.

Mixture of Experts is the most consequential architectural change since the original transformer. The intuition is short. A dense feed-forward layer uses all its parameters for every token. That couples parameter count to FLOPs per token, doubling parameters doubles per-token compute, which means inference cost scales linearly with model size. MoE breaks the coupling. It replaces the dense FFN with many small “expert” sub-networks plus a router that picks which experts handle each token. Total parameters can grow large while compute per token stays small.

The pattern was first proposed in deep learning by Shazeer et al. in 2017 and gradually became dominant: Switch Transformer (2021), GLaM (2021), Mixtral 8x7B (2024), DeepSeek-V3 (late 2024). GPT-4 is reportedly MoE. Mixtral 8x7B has 46.746.7B total parameters but uses only about 12.912.9B per token, outperforming Llama-2 70B at a fraction of the inference cost. This chapter explains how MoE works, why it is hard to train, and the modern variants that have made it production-viable.

The setup: scaling parameters without scaling FLOPs

The fundamental scaling problem of dense models can be stated in one sentence: parameters and FLOPs per token are coupled. A 7070B-parameter dense model uses 7070B parameters’ worth of compute on every single token it processes. A hypothetical 700700B dense model would use 700700B per token. There is no way to “scale up the brain” without scaling up the per-token bill. Frontier-scale inference costs grow linearly with total parameters, and that linear scaling is the most uncomfortable economic fact about deploying ever-larger models.

MoE’s idea is that this coupling is not actually necessary. Not every parameter has to contribute to every token. The architecture builds sparse activation in directly: a model can have NN “experts” but only the top kk of them are computed for any given token. Total parameters scale with NN times the per-expert size; active parameters per token scale with kk times the per-expert size. Choosing N=8N = 8 and k=2k = 2 already gives a 4×4\times multiplier on capacity per dollar of inference compute. Capacity is decoupled from compute.

The intuition is roughly biological. A human brain does not activate every neuron for every thought: different cortical regions handle different tasks, and only the relevant ones engage. MoE applies this principle to transformers. Different experts learn to specialize through training; the router learns which expert to send each token to. After training, code tokens tend to route to one set of experts, factual tokens to another, syntax tokens to a third. Specialization emerges; it is not built in.

The economic argument is what has made MoE the dominant frontier-architecture choice for open-weights models. At inference time, active parameters drive cost, not total parameters. Mixtral 8x7B with about 12.912.9B active parameters per token routinely beats Llama-2 70B with all 7070B active. Quality-per-inference-dollar is the metric that matters in production, and on that metric MoE wins.

The MoE block: router + experts

What changes

The standard transformer block, from Chapter 5, is: attention, then layer norm, then a dense feed-forward network, then another layer norm. Residual connections wrap each sub-block. The MoE block is: attention, then layer norm, then an MoE layer, then another layer norm. The only difference is the middle component. Attention is unchanged. Residuals are unchanged. Layer norms are unchanged. One sub-block out of the block, one sub-block in.

The MoE layer itself has two components. The first is a small router (sometimes called the gating network), a single linear layer that produces one logit per expert. The second is a collection of NN separate experts, each itself a full FFN with its own parameter set. The router decides; the experts compute.

The minimalism of the swap matters. Everything that took Chapters 4–6 to build (multi-head attention, causal masking, positional encoding, residual scaffolding) keeps working without modification. MoE is not a redesigned architecture; it is a targeted replacement of one component.

The mathematics

For a standard FFN, the equation is:

FFN(x)=W2σ(W1x)\text{FFN}(x) = W_2 \cdot \sigma(W_1 x)

where σ\sigma is a nonlinearity (GeLU in most production transformers; the runnable demo below uses ReLU for simplicity). One pass through, all parameters used.

For the MoE layer, the equation generalizes to a weighted sum of expert outputs, where most weights are zero:

MoE(x)=itopk()gi(x)FFNi(x),gi(x)=exp(i)jtopk()exp(j)\text{MoE}(x) = \sum_{i \in \text{top}_k(\ell)} g_i(x) \cdot \text{FFN}_i(x), \quad g_i(x) = \frac{\exp(\ell_i)}{\sum_{j \in \text{top}_k(\ell)} \exp(\ell_j)}

(11.moe)

The router weights WrRN×dW_r \in \mathbb{R}^{N \times d} produce logits =WrxRN\ell = W_r x \in \mathbb{R}^N, one per expert. The topk()\text{top}_k(\ell) operator selects the indices of the kk largest logits; softmax is then taken over only those kk values, producing gate values gi(x)g_i(x) that sum to 11 across the selected experts. Each selected expert FFNi\text{FFN}_i runs on xx; the outputs are weighted-summed with the gate values to produce the layer’s output.

The critical observation is that only kk of the NN FFNs are ever actually computed. The remaining NkN - k have gi=0g_i = 0 and contribute nothing. They do not need to be invoked at all. FLOPs per token scale with kk, independent of NN.

A runnable forward pass makes the mechanics concrete. The implementation below is a naive numpy version, clear, not fast, but it shows how routing, selection, and weighted combination compose into a single MoE layer.

Top-k routing: the gating function

The routing decision

The router is the smallest piece of the MoE layer and the most consequential. It is a single linear layer WrRN×dW_r \in \mathbb{R}^{N \times d}, tiny compared to the per-expert FFNs that dominate parameter count. For each token xx, the router produces logits =WrxRN\ell = W_r x \in \mathbb{R}^N, one logit per expert, summarizing how well-suited each expert is for this token.

The selection step is discrete. Top-kk picks the indices of the kk largest logits. Only the experts at those indices will be invoked. The remaining NkN - k experts are not run at all: there is no zero output from them; they simply do not appear in the forward pass.

After selection, softmax over only the selected kk logits produces gate values that sum to 11. These gates modulate the expert outputs in the weighted sum. Critically, softmax is not applied over the full NN-logit vector and then masked; it is applied over the kk selected logits, so gate values for the chosen experts are a proper probability distribution among themselves.

The hybrid nature of this, discrete on top, continuous underneath, is what makes MoE trainable. The top-kk operation is non-differentiable, but gradients still flow through the soft gate values gig_i that modulate expert outputs in the weighted sum. The router learns from those gradients which experts to favor for which kinds of input.

Top-1 vs top-2

Top-1 routing (Switch Transformer) is the simplest possible MoE: one expert per token. The gate value is just the softmax probability of the selected expert (or, in some variants, simply 1.01.0). No convex combination is needed. Switch Transformer demonstrated that top-1 is sufficient for trillion-parameter-scale training, and its simplicity makes it pedagogically clean.

Top-2 routing (GLaM, Mixtral) sends each token through two experts and combines their outputs with the softmax-normalized gates. It is more expressive: a token that is “mostly code, partly math” can blend a code expert with a math expert at appropriate weights. The cost is roughly 2×2\times the FFN FLOPs per token relative to top-1.

The trade-off has converged on top-2 as the modern standard. GLaM showed that the extra FFN cost is well worth the quality gain; Mixtral confirmed it at open-weights production scale. Higher kk values (top-4, top-8) have been tried but tend to degrade the parameter-decoupling benefit: past k=2k = 2, you are buying more compute without much extra capacity.

Active vs total parameters

The bookkeeping for an MoE model has two distinct numbers that matter for completely different things.

Total parameters = NN experts ×\times per-expert FFN size, plus the shared attention, embedding, output, and router parameters that every layer has anyway. For Mixtral 8x7B: about 46.746.7B parameters total, with 88 experts contributing roughly 5.65.6B each to the FFN portion of every MoE layer.

Active parameters per token = kk experts ×\times per-expert FFN size, plus the shared attention/embedding/output/router parameters. For Mixtral: about 12.912.9B. The other 5.65.6B ×633.6\times 6 \approx 33.6B of expert parameters are loaded in memory but not invoked for that token.

Sparsity ratio = k/Nk / N. Mixtral is 2/8=25%2/8 = 25\%, only a quarter of expert parameters are active per token.

The inference economics fall directly out of this split. GPU memory needs the total parameter count, because every expert must be resident in memory to be ready to run on any token. Per-token compute is set by the active count. Memory bandwidth and FFN FLOPs are decoupled: a Mixtral inference deployment must hold 46.746.7B parameters but pays only 12.912.9B parameters’ worth of compute per token, which is cheaper than serving Llama-2 70B even though Mixtral has more total capacity.

MoE routing visualizer

Interactive
Top-k routing:
"the""def""Paris""return""and""=""2024""function""Einstein""if"",""sum""France""x""import""}"Expert 0punctuation / boundariesExpert 1function words / syntaxExpert 2code keywords (secondary)Expert 3code (primary)Expert 4under-utilized, see widget captionExpert 5named entitiesExpert 6numeric / mathExpert 7proper nouns / capitalizedTokensExperts
Load distribution (tokens routed per expert at top-2)
E0
4
E1
4
E2
4
E3
9
E4
0✗ under-utilized
E5
3
E6
4
E7
4
Selected token: "Paris"(named entity)
Top-2 routing: experts [5, 7] with gates [0.73, 0.27]
Expert 5 (named entities) is primary. Expert 7 (proper nouns / capitalized) is secondary. Expert specialization emerged from training, not explicit assignment.

16 hand-tuned tokens flow into an MoE layer with 8 experts. Curves show top-k routing; thicker curves = higher gate values. Hover any token to highlight its routing; hover any expert to see all routed tokens. Toggle top-k between 1, 2, and 4 to see how routing decisions change. Expert 4 is deliberately under-utilized, a real failure mode the auxiliary loss is designed to prevent. Note: real expert specialization is messier than shown; this widget hand-tunes the routing for pedagogical clarity.

Routing decides who does the work. But what happens when the router gets it wrong, when nothing forces all experts to be used and the router collapses onto a favorite few?

Load balancing: the practical hard problem

Router collapse

Nothing in the architecture forces all experts to be used. The router is a learned linear layer; in early training it produces near-random logits, but the moment small differences appear, positive feedback kicks in. Expert 00 happens to get slightly more tokens. With more training signal it improves slightly faster. The router, learning from the loss, learns that expert 00 tends to be useful and routes more tokens there. Other experts under-train, get further ignored, fall further behind.

The failure mode this produces is router collapse. In its extreme form, seven of eight experts are effectively dead: the router sends almost all tokens to one or two favored experts, and the rest of the model’s parameters are wasted. The model uses only about 1/N1/N of its capacity. This is catastrophic for MoE: the whole architectural promise is that all NN experts contribute total capacity, and if only one is used, MoE is just a slow dense FFN with a lot of unused weights.

The reason this matters more for MoE than for dense models is structural. A dense model has no “wasted” parameters: all of them are always active, so they all get gradient signal and all train. MoE’s value depends on all experts being trained and useful. If routing is uneven, training signal is uneven, expert quality is uneven, and the model degenerates.

The auxiliary loss

The standard fix is an auxiliary loss added to the main training objective. Switch Transformer’s formulation defines two per-expert quantities: fif_i, the fraction of tokens routed to expert ii in the batch; and PiP_i, the average router probability assigned to expert ii across the batch. The auxiliary loss is:

Laux=αNi=1NfiPi\mathcal{L}_{\text{aux}} = \alpha \cdot N \cdot \sum_{i=1}^{N} f_i \cdot P_i

This expression is minimized when both fif_i and PiP_i are uniform at 1/N1/N, exactly the balanced routing pattern we want. Any imbalance, in either how often experts are picked or in the routing probabilities themselves, pushes the loss up and creates a gradient signal that the router uses to rebalance.

The coefficient α0.01\alpha \approx 0.01 balances main task vs balance. Too small: collapse risk returns. Too large: the auxiliary signal distorts language modeling because the router is being pushed harder toward uniformity than toward picking actually-useful experts. Modern training tunes α\alpha carefully and sometimes anneals it through training.

Even with the auxiliary loss, imbalance can persist. Frontier MoE training adds further techniques: router z-loss (ST-MoE, 20222022) to penalize extreme router logit magnitudes, careful initialization to prevent any expert from getting a head start, expert dropout to force the model to use alternatives. DeepSeek-V3 (December 20242024) introduced auxiliary-loss-free load balancing using bias adjustment instead of a separate loss term, with reportedly better results. The auxiliary loss is the foundational technique, not the last word.

The demo below compares the loss value for a balanced routing pattern against a collapsed one. The collapsed case produces a larger loss, which is exactly the gradient signal that pushes the router back toward balance.

Even with balanced routing on average, individual batches can be uneven, raising the question of what happens when an expert is asked to do more work than it can fit in its compute budget.

Expert capacity and dropped tokens

The implementation reality is that experts are not infinitely flexible. Each expert is realized as a fixed-shape matmul that processes a fixed number of tokens per batch, the compute budget allocated to that expert. It cannot dynamically resize. So each expert has a maximum number of tokens it can process in a given step. That maximum is called its capacity.

The capacity formula is

C=ρTNC = \rho \cdot \frac{T}{N}

where TT is the total tokens in the batch, NN is the number of experts, and ρ\rho is the capacity factor (typically 1.01.0 to 1.251.25). At ρ=1.0\rho = 1.0, each expert is sized for exactly its fair share, the number of tokens it would receive if routing were perfectly balanced. At ρ=1.25\rho = 1.25, each expert has 25%25\% headroom for batches that happen to skew toward it.

When more than CC tokens get routed to an expert in a batch, the excess is dropped. Dropped does not mean errored. The tokens skip the expert and pass through via the residual connection: their hidden state continues unmodified by the MoE layer. From the model’s perspective, those tokens experienced an identity transformation in that layer. Some semantic content goes through “untransformed,” and that has a real quality cost: too many drops degrade model performance because too much of the model’s representational work is being skipped.

The trade-off across ρ\rho is straightforward. High ρ\rho (say, 2.02.0) reduces drops to near-zero but allocates extra compute slots that are mostly empty for typical batches, wasted FLOPs. Low ρ\rho (say, 0.50.5) is efficient with compute but drops aggressively, hurting quality. Typical settings split the difference: ρ=1.0\rho = 1.0 at inference (where compute efficiency matters more) and ρ=1.25\rho = 1.25 at training (where higher variance in routing means more capacity headroom is needed).

The auxiliary loss interacts with capacity in a useful way. The more balanced the routing, the less expert overflow. Balanced routing minimizes both wasted capacity (no empty slots) and dropped tokens (no overflow). Capacity factor tuning is downstream of routing balance; the cleaner the routing distribution, the smaller the capacity factor needs to be.

With the architectural pieces established (block structure, routing, balance, capacity), the modern variants that have shaped MoE practice follow.

Modern MoE variants

Switch Transformer (2021)

Switch Transformer introduced top-1 routing: one expert per token. This is the simplest MoE, no convex combination, no gate-weighting between two experts, just a hard assignment. Switch demonstrated trillion-parameter MoE training and formalized expert capacity as a concept. It remains pedagogically the cleanest case for explaining the architecture, and several systems still use top-1 in production for its simplicity.

GLaM (2021)

GLaM (Generalist Language Model) used top-2 routing at 1.21.2T total parameters, exceeding the quality of contemporary dense models while using about one-third the training energy and half the per-token inference FLOPs. The headline architectural choice, top-2 instead of top-1, became the de facto standard after GLaM. The extra expressiveness of weighted-combining two experts turned out to be worth the doubled FFN cost.

Mixtral 8x7B (2024)

Mixtral 8x7B was the open-weights breakthrough. Eight experts per MoE layer, top-2 routing, 46.746.7B total parameters and about 12.912.9B active per token. Mistral released the weights (via a magnet-link torrent) in December 20232023, with the paper following in January 20242024, and the model performed comparably to Llama-2 70B at a fraction of the inference cost. The release made MoE serving infrastructure (vLLM, Mistral.rs, DeepSpeed-MII) a first-class concern in the open-source community and established that production-quality MoE was deployable, not just trainable in research papers.

DeepSeek-MoE (2024)

DeepSeek-MoE pushed the architecture in two directions. First, fine-grained experts: many smaller experts (64 routed experts per layer, versus Mixtral’s 8) rather than fewer large ones. Specialization is finer, and the increased granularity gives the router more freedom to combine expertise. Second, shared experts: a couple of experts always run for every token, capturing features that are common across all inputs, while the routed experts handle specialization. The combination outperforms standard MoE at the same total parameter count and has become the template for modern Chinese-frontier MoE training; DeepSeek-V3 later pushed the fine-grained-expert count further still, to 256 routed experts per layer.

DeepSeek-V3 (December 2024)

DeepSeek-V3 took the DeepSeek-MoE recipe to the frontier: 671671B total parameters, 3737B active per token. It was, as of late 20242024, the open-weights state of the art on most benchmarks. Notably, it dispenses with the explicit auxiliary loss in favor of auxiliary-loss-free load balancing, a bias-adjustment scheme that nudges router logits toward balance without adding a separate loss term. Reports indicate it works better than the classic Switch Transformer auxiliary loss, suggesting that the load-balancing problem is genuinely still being refined.

The frontier kept moving on exactly this recipe. DeepSeek itself shipped DeepSeek-V4 in April 20262026 (open weights, MIT-licensed): a V4-Pro variant at 1.61.6T total / 4949B active parameters and a smaller V4-Flash at 284284B total / 1313B active, both with a 11M-token context window, continuing the fine-grained-expert, loss-free-balancing template set by V3. By mid-20262026, independent benchmarks (Artificial Analysis’s Intelligence Index) put Z.ai’s GLM-5.2 (753753B total, about 4040B active, MIT-licensed, released June 20262026) ahead of DeepSeek-V4-Pro, with Moonshot’s Kimi K2.6 (roughly 11T total, 3232B active) also in the same tier. As of 20262026-0707-1313, “the open-weights frontier” names a cluster of Chinese-lab MoE releases rather than any single model, and whichever one leads will likely change again within months; DeepSeek-V3 remains the right teaching example for the loss-free-balancing technique, but it is a 20242024 snapshot, not the current state of the art.

The parameter-counting math for an MoE configuration is short enough to write out. The function below computes total and active parameters for a given config and applies it to Mixtral 8x7B and Llama-2 70B for comparison.

Active vs total parameters

Interactive
Sort by:
Llama-2 7B
6.7B
Llama-2 13B
13.0B
Mixtral 8x7B
46.7B/12.9B
Llama-2 70B
69.0B
Mixtral 8x22B
141.0B/39.0B
DeepSeek-V2
236.0B/21.0B
Llama-3 405B
405.0B
DeepSeek-V3
671.0B/37.0B
Your custom MoE
Custom MoE
48.3B/14.5B
active (compute cost per token) total – active (memory cost only)
Custom MoE configuration
Total params48.3B
Active params14.5B
Sparsity30%
Memory (BF16)97 GB
Compute/token87 GFLOPs

The inference economics of MoE made concrete. Each model shown as a bar, light region is total parameters (memory cost), filled region is active parameters per token (compute cost). Dense models (Llama-2, Llama-3) have total = active. MoE models (Mixtral, DeepSeek) show a visible gap between memory and compute. The custom configurator below lets you design your own MoE and see where it lands.

The progression from Switch (top-1, 20212021) to GLaM (top-2, 20212021) to Mixtral (top-2 open-weights, 20242024) to DeepSeek-V3 (loss-free balancing, late 20242024) is a clean four-step story. Each step solved a different practical problem: Switch made sparse scaling work at all, GLaM established top-2 as standard, Mixtral made MoE production-ready in open weights, DeepSeek-V3 pushed it to frontier scale with refined balancing. What remains is the honest accounting of why MoE is harder to use than dense models.

Why MoE is hard

Training instability

MoE training is notoriously harder to keep stable than dense training, and the root causes are specific to the architecture. Router gradients are noisy: each token sees only kk of the NN experts, the routing decision is discrete, and the gradient that reaches the router has to flow through the soft gate values rather than the hard top-kk selection. The signal is thin.

Router z-loss (introduced in ST-MoE, 20222022) adds a penalty on the magnitude of router logits, preventing them from drifting to extreme values that destabilize the softmax. Numerical precision matters more than for dense training: router computations must be in higher precision (FP32) even when the rest of the model is in BF16, because the softmax over a small number of values amplifies any precision loss into a routing decision change. Auxiliary loss tuning is delicate: the α\alpha coefficient must be neither too large (which dominates the language-modeling signal) nor too small (which permits collapse).

Serving complexity

MoE serving is genuinely different from dense serving. All experts must be resident in GPU memory: total parameters drive memory requirements, even though only a fraction of them are computed per token. A Mixtral inference cluster needs 46.746.7B parameters’ worth of HBM whether it is processing one token per second or a million.

Per-batch routing decisions mean that every forward pass has unique expert assignments. Batching efficiency is harder to maintain because the experts a batch needs are not known until the router runs. All-to-all collectives appear in distributed MoE serving: when experts are sharded across GPUs, tokens must be sent to the GPU hosting their assigned expert. Network bandwidth matters for inference, not just for training.

Specialized inference engines (vLLM, Mistral.rs, DeepSpeed-MII) have evolved with MoE-specific optimizations: continuous batching that handles expert load imbalance, KV cache management aware of expert parallelism, fused routing kernels. Running an MoE model with a stock dense inference path is technically possible but leaves substantial throughput on the table.

Fine-tuning is harder

Post-training MoE models is harder than post-training dense models for a structural reason: each expert sees roughly k/Nk/N of the data. A fine-tuning corpus of 1010K examples becomes effectively 2500\sim 2500 examples per expert in an 88-expert model with top-2 routing. Gradient signal per expert is thinner, training is noisier, and overfitting risk is higher.

Expert routing can also shift during fine-tuning. A pre-trained router that has learned to specialize on natural-text patterns may re-route in unexpected ways when shown fine-tuning data, breaking the specialization that made the model good in the first place. The modern remedy is to freeze the router during fine-tuning and train only the experts, or to use parameter-efficient methods (Chapter 15) that touch only a small fraction of expert parameters.

What we’ve covered: and what’s next

This chapter introduced the MoE block, router plus NN experts replacing the dense FFN, and worked through the math of top-kk routing, the central practical problem of load balancing via auxiliary loss, the bookkeeping of expert capacity and dropped tokens, the modern variants from Switch through DeepSeek-V3, and the honest accounting of why MoE training, serving, and fine-tuning are all harder than their dense counterparts. The pedagogical claim is one sentence: MoE is the architectural pattern that decouples capacity from compute, scaling total parameters with the number of experts while keeping per-token FLOPs proportional only to the top-kk active subset.

MoE wins where very large total capacity and manageable inference cost both matter. The “sweet spot” for frontier open-weights models in 20242024 (Mixtral, DeepSeek-V3) was exactly that combination, and the pattern has held as the frontier moved to larger fine-grained MoEs (DeepSeek-V4, GLM-5.2, Kimi K2.6, as of mid-20262026). MoE loses on simplicity. If your use case does not need more than roughly 5050B effective capacity, dense models are easier to train, easier to serve, and very nearly as good.

Exercises

Four exercises that build on the chapter’s machinery. Each is a self-contained problem with a starting template; hints are collapsed by default; try the problem first.

Exercise 1 (easy): MoE forward pass at top-1 vs top-2

Implement the MoE forward pass. Verify that top-1 routing uses exactly one expert per token; top-2 uses two with normalized gates summing to 11.

Hint

For each token:

  1. Compute router logits: =Wrx\ell = W_r x.
  2. Find the top-kk expert indices (by argsort of logits).
  3. Softmax over only the selected logits to get gate values summing to 11.
  4. Run only the top-kk FFNs and combine: MoE(x)=itopkgiFFNi(x)\text{MoE}(x) = \sum_{i \in \text{top}_k} g_i \cdot \text{FFN}_i(x).

For top-1, the gate is the softmax of a single logit, which is just 1.01.0 (a softmax over one value always returns 11).

Solution

Route by the top-kk logits, softmax only over those (not the full vector), and combine each selected expert’s FFN output weighted by its gate:

import numpy as np

def softmax(x, axis=-1):
    x = x - x.max(axis=axis, keepdims=True)
    return np.exp(x) / np.exp(x).sum(axis=axis, keepdims=True)

def moe_forward(x, router_W, expert_W1s, expert_W2s, k=2):
    logits = x @ router_W.T
    idx = np.argsort(-logits)[:k]              # top-k indices, descending
    gates = softmax(logits[idx])                # softmax over selected logits only
    out = np.zeros_like(x)
    for i, g in zip(idx, gates):
        h = np.maximum(x @ expert_W1s[i].T, 0)   # ReLU
        out += g * (h @ expert_W2s[i].T)
    return out, idx, gates

With the seeded setup: top-1 selects expert [3] with gate [1.]; top-2 selects experts [3, 2] with gates [0.548, 0.452], which sum to 1.

Exercise 2 (medium): Parameter counting (Mixtral 8x7B verification)

Compute total and active parameters for an MoE transformer with a given config. Verify against Mixtral 8x7B’s published numbers (46.746.7B total, 12.912.9B active).

Hint

Per MoE layer:

  • Attention: 4d24 d^2 (Q, K, V, O projections)
  • LayerNorms: 4d\approx 4d
  • Per-expert FFN (SwiGLU: gate, up, down projections): 3ddffn3 d \cdot d_{\text{ffn}}
  • Router: NdN \cdot d

Total layer params (all experts) = attn ++ LN +NFFNper-expert+ N \cdot \text{FFN}_{\text{per-expert}} ++ router. Active layer params (top-kk only) = attn ++ LN +kFFNper-expert+ k \cdot \text{FFN}_{\text{per-expert}} ++ router.

Plus embeddings: 2Vd\approx 2 \cdot V \cdot d (input embedding + output projection, often tied).

For Mixtral 8x7B: d=4096d = 4096, dffn=14336d_{\text{ffn}} = 14336, N=8N = 8, k=2k = 2, L=32L = 32, V32000V \approx 32000.

Solution

Total counts every expert’s FFN; active counts only the top-kk FFNs actually run per token. Everything else (attention, layernorms, router, embeddings) is dense and counted once either way:

def count_moe_params(num_layers, d_model, d_ffn, num_experts, top_k, vocab_size=32000):
    attn = 4 * d_model * d_model
    ln = 4 * d_model
    ffn_per_expert = 3 * d_model * d_ffn
    router = num_experts * d_model

    layer_total = attn + ln + num_experts * ffn_per_expert + router
    layer_active = attn + ln + top_k * ffn_per_expert + router

    total = num_layers * layer_total + 2 * vocab_size * d_model
    active = num_layers * layer_active + 2 * vocab_size * d_model
    return total, active

Mixtral 8x7B (computed): total = 47.5B (published: 46.7B), active = 13.7B (published: 12.9B), sparsity = 29%. Llama-2 70B dense: total = active = 78.4B. So Mixtral has ~61% of Llama-2 70B’s total params and ~17% of its active params, for quality closer to the 70B model: the payoff of sparsity.

Exercise 3 (medium): Auxiliary load balance loss

Implement the Switch Transformer auxiliary load balancing loss. Verify that it is lower for balanced routing than for collapsed routing, providing the gradient signal that prevents collapse.

Hint

The auxiliary loss is: Laux=αNi=1NfiPi\mathcal{L}_{\text{aux}} = \alpha \cdot N \cdot \sum_{i=1}^{N} f_i \cdot P_i

where:

  • fif_i = fraction of tokens routed to expert ii (from argmax assignments).
  • PiP_i = average router probability for expert ii (from softmax outputs).
  • α\alpha = balance coefficient, typically 0.010.01.

The loss is minimized when both fif_i and PiP_i are uniform at 1/N1/N. Imbalance pushes the loss up.

Solution

fif_i is a hard count (from argmax assignments), and PiP_i is a soft average (from the full softmax); mixing a hard and soft signal is what makes this loss differentiable despite argmax being involved:

import numpy as np

def aux_load_balance_loss(router_probs, expert_assignments, num_experts, alpha=0.01):
    f = np.bincount(expert_assignments, minlength=num_experts) / len(expert_assignments)
    P = router_probs.mean(axis=0)
    return alpha * num_experts * np.sum(f * P)

With the seeded setup: balanced routing loss = 0.01001 (close to the theoretical minimum αNN(1/N)2=α=0.01\alpha \cdot N \cdot N \cdot (1/N)^2 = \alpha = 0.01), collapsed routing loss = 0.03841, a 3.84x ratio: collapse is heavily penalized.

Exercise 4 (hard): Expert capacity and dropped tokens

Simulate expert capacity. Given a batch of routing decisions, count how many tokens are dropped at each capacity factor. Sweep ρ\rho from 0.50.5 to 2.02.0 and report the drop rate at each value.

Hint

Expert capacity formula: C=ρ(T/N)C = \rho \cdot (T / N) where TT is total tokens, NN is number of experts, ρ\rho is the capacity factor.

For each expert:

  • Count how many tokens were assigned to it (from top-1 argmax).
  • If count >C> C, drop (countC)(\text{count} - C) tokens.

Total drops =imax(0,countiC)= \sum_i \max(0, \text{count}_i - C). Drop rate == total drops // total tokens.

At ρ=1.0\rho = 1.0: perfectly balanced routing has zero drops; imperfect routing has some. At ρ=2.0\rho = 2.0: very generous; most batches have zero drops but compute is wasted.

Solution

Count tokens per expert with np.bincount, then any excess over capacity is dropped:

import numpy as np

def simulate_drops(expert_assignments, num_experts, capacity_factor):
    num_tokens = len(expert_assignments)
    capacity = int(capacity_factor * num_tokens / num_experts)
    counts = np.bincount(expert_assignments, minlength=num_experts)
    drops = sum(max(0, c - capacity) for c in counts)
    return drops, drops / num_tokens

With the seeded Zipf-like weights ([3.0, 2.0, 1.5, 1.0, 1.0, 0.8, 0.5, 0.3], normalized), tokens per expert come out to [318, 179, 167, 85, 92, 80, 57, 22] against a perfect balance of 125:

rhodropsrate
0.554954.9%
0.836436.4%
1.028928.9%
1.2519619.6%
1.513113.1%
2.0686.8%

Even at ρ=2.0\rho = 2.0 drops don’t hit zero, because this synthetic skew is severe (the busiest expert gets 2.5x the fair share); real trained routers, kept balanced by the auxiliary loss, don’t skew this hard.

Chapter 12 takes the other architectural path. MoE keeps the attention mechanism intact and changes only what happens after it; state-space models like Mamba replace attention itself, trading attention’s O(N2)O(N^2) memory and compute for O(N)O(N) throughout. A different bet on what is actually holding transformers back. Together, Chapters 11 and 12 cover the two major architectural alternatives to standard dense transformers that have emerged in the last few years.

After Chapter 12 closes Part IV, the tutorial enters post-training (Chapters 13–16), taking a pre-trained model (dense or MoE) and turning it into something useful through SFT, RLHF, DPO, and the rest of the post-training stack.