TAI BUI
Back to curriculum
BuildPython

Mixture of Experts (MoE)

A dense 70B transformer runs all of its parameters on each token. A 671B MoE runs 37B per token and beats it on almost every benchmark. Sparsity decouples how much a model knows from how much compute each token costs.

TIME: ~45 minutesPREREQUISITES: Phase 7 · 05 (Full Transformer), Phase 7 · 07 (GPT)

Learning Objectives

  • Explain how an MoE layer decouples total parameters from per-token compute using E experts and top-k routing.
  • Implement a top-k router with softmax gating and a per-expert selection bias in pure Python.
  • Compare auxiliary loss, capacity dropping, and auxiliary-loss-free balancing, and say what each one costs.
  • Decide when MoE is the right choice given memory budget, workload shape, and active-parameter scale.

The Problem

Mixture of Experts (MoE) is a transformer design that replaces each FFN with E independent experts and a router that picks k of them per token. Total parameters are E × FFN_size. Active parameters per token are k × FFN_size. Storage scales with E, compute scales with k.

That split matters because a dense transformer's FLOPs at inference equal its parameter count (times 2 for the forward pass). Scale a dense model up and each token pays the full bill. By 2024 the frontier was hitting a compute wall: to be meaningfully smarter, you needed exponentially more FLOPs per token. MoE breaks the link between parameter count and per-token compute. A typical 2026 configuration is E=256, k=8.

The 2026 frontier is almost entirely MoE: DeepSeek-V3 (671B total / 37B active), Mixtral 8×22B, Qwen2.5-MoE, Llama 4, Kimi K2, gpt-oss. On Artificial Analysis's independent leaderboard, the top 10 open-source models are all MoE.

Warm-up0/3
1. In the dense GPT block you built, every token passes through one FFN. If you double the FFN's hidden width, what happens to per-token FFN compute?Warm-up
A token vector passing through a single dense FFN box with up-projection, activation and down-projection, then an output vector, with a note that the hidden width goes from h to 2h and the per-token FFN compute goes from C to an unknown.
Schematic of the stem's dense block. C is a placeholder for the current compute.
2. Which part of a transformer block mixes information across token positions?Warm-up
Four token positions t1 to t4 entering a transformer block drawn as two rows of three boxes: layer norm, self-attention, residual add, then layer norm, FFN, residual add.
Block layout as in the dense GPT block of the lesson.
3. You have scores for 8 options and want to turn the 2 you pick into mixing weights that sum to 1. Which function from the full transformer do you reach for?Warm-up
A bar chart of eight scores for options 1 to 8, with options 2 and 8 highlighted as the two picked, and a note asking for two mixing weights that sum to 1.
Scores are typed for illustration.
Check: The Problem0/1
1. A MoE layer has E=64 experts of 1B parameters each and routes each token to k=2. How many FFN parameters are stored and how many run per token?Apply
An 8 by 8 grid of 64 expert boxes each labeled 1B, a token box, a note that it picks k equals 2 experts, and two unknowns for FFN parameters stored and FFN parameters run per token.
The stem's layer: 64 experts of 1B parameters, k = 2.

The Concept

MoE layer: router selects k of E experts per token

The FFN swap

Dense transformer block:

h = x + attn(norm(x))
h = h + FFN(norm(h))

MoE block:

h = x + attn(norm(x))
scores = router(norm(h))              # (N_tokens, E)
top_k = argmax_k(scores)              # pick k of E per token
h = h + sum_{e in top_k}(
        gate(scores[e]) * Expert_e(norm(h))
    )

Each expert is an independent FFN, typically SwiGLU. The router is a single linear layer. A token picks its own k experts and gets a gated mixture of their outputs.

The load-balancing problem

Suppose the router sends 90% of tokens through expert 3. The other experts starve. Three fixes have been tried:

  1. Auxiliary load-balancing loss (Switch Transformer, Mixtral). Add a penalty proportional to the variance in expert usage. Works, but adds a hyperparameter and a second gradient signal.
  2. Expert capacity + token dropping (early Switch). Each expert processes at most C × N/E tokens, and overflow tokens skip the layer. Hurts quality.
  3. Auxiliary-loss-free balancing (DeepSeek-V3). Add a learned per-expert bias that shifts the router's top-k selection. The bias is updated outside the training loss, so the main objective carries no penalty. That was 2024's big unlock.

DeepSeek-V3's approach: after each training step, check whether each expert's usage is above or below the target, and nudge its bias by ±γ. Selection uses scores + bias. The expert probabilities used for gating are the raw scores, unchanged. Routing and expression stay decoupled.

Shared experts

DeepSeek-V2/V3 also split experts into shared and routed. All tokens pass through the shared experts, while routed experts are picked via top-k. Shared experts capture common knowledge and routed experts specialize. V3 runs 1 shared expert plus top-8 of 256 routed.

Fine-grained experts

Classic MoE (GShard, Switch) makes each expert as wide as a full FFN. E is small (8–64) and k is small (1–2).

Modern fine-grained MoE (DeepSeek-V3, Qwen-MoE) makes each expert narrower (1/8 FFN size). E is large (256+) and k is larger (8+). Total parameters stay the same, but the number of expert combinations grows much faster: C(256, 8) = 400 trillion possible "experts" per token. Quality goes up while latency stays flat.

The cost profile

Per token, per layer:

ConfigActive params / tokenTotal params
Mixtral 8×22B~39B141B
Llama 3 70B (dense)70B70B
DeepSeek-V337B671B
Kimi K2 (MoE)~32B1T

DeepSeek-V3 beats Llama 3 70B (dense) on almost every benchmark while doing fewer active FLOPs per token. More parameters means more knowledge, and more active FLOPs means more compute per token. MoE decouples the two.

The catch: memory

All experts live on GPU regardless of which ones fire. A 671B model needs ~1.3 TB of VRAM for fp16 weights. Frontier MoE deployment requires expert parallelism: shard experts across GPUs and route tokens across the network. Latency is dominated by the all-to-all communication, not the matmul.

Check: The Concept0/19
1. How does DeepSeek-V3's auxiliary-loss-free method fix load imbalance?Define
A bar chart over experts E1 to E8 where E3 receives 90 percent of tokens and each of the others about 1.4 percent, with a prompt asking how DeepSeek-V3 balances this load.
Illustrative: the 90% figure is the lesson's example, the other 10% is split evenly.
2. What separates shared experts from routed experts in DeepSeek-V2/V3?Define
A token box and two unconnected boxes, shared experts (V3 has 1) and routed experts (V3 has 256), with question marks where the links from the token would be.
Counts are those the lesson gives for V3.
3. In an MoE layer, what is an expert?Define
A token entering a router and eight boxes labeled Expert 1 to Expert 8, with Expert 3 highlighted, and a prompt asking what an expert is.
Layer layout from the lesson; the eight experts match the toy config in main.py.
4. What is the router in an MoE layer?Define
A token entering a highlighted box marked with a question mark, which has a line to each of eight boxes labeled Expert 1 to Expert 8.
Same eight-expert layout as the toy in main.py.
5. What does top-k routing mean?Define
A token entering a router with lines to eight experts, a note that k equals 2, and a prompt asking what top-k routing means.
Layout of the toy in main.py: eight experts, k = 2.
6. What is an auxiliary load-balancing loss?Define
A text card naming the auxiliary load-balancing loss as used by Switch Transformer and Mixtral, with a prompt asking what it is.
Names as given in the lesson.
7. What is expert parallelism?Define
Eight expert boxes on the left, four GPU boxes each marked holds: unknown, with a prompt asking what expert parallelism is.
Counts are illustrative.
8. In MoE terms, what does sparsity measure?Define
Eight expert boxes E1 to E8 with E2 and E8 highlighted, a note of 1536 parameters per expert with top-2 routing, and a table with stored 12288 and sparsity unknown.
Numbers from main.py's printout for the toy MoE (total 12288).
9. DeepSeek-V3 stores 671B parameters but runs 37B per token, against 70B for dense Llama 3 70B. Why can it hold more knowledge at lower per-token compute?Explain
Paired bars: DeepSeek-V3 stores 671B parameters and runs 37B per token; dense Llama 3 70B stores 70B and runs 70B, with a prompt asking why the first can hold more knowledge at lower compute.
Figures from the stem and the lesson's cost-profile table; bars to scale.
10. What do the first two load-balancing fixes (before auxiliary-loss-free balancing) do?Explain
A bar chart of skewed expert usage with E3 at 90 percent, and below it the names of the first two load-balancing fixes, auxiliary loss and expert capacity plus token dropping, each followed by a question.
Illustrative skew; the 90% figure is the lesson's example.
11. What does an auxiliary load-balancing loss cost you?Explain
A table of three load-balancing fixes with who used them: auxiliary loss for Switch and Mixtral, capacity plus dropping for early Switch, auxiliary-loss-free for DeepSeek-V3, each cost shown as unknown.
Names and users as listed in the lesson.
12. Why does capacity plus token dropping hurt model quality?Explain
A tall box for one expert with a dashed capacity line part way up, tokens arriving beyond the line, and a prompt asking what happens to the tokens over the cap.
Schematic of the lesson's expert capacity.
13. Why give every token a shared expert alongside the routed ones?Explain
A token going directly into a shared expert and also through a router to two highlighted routed experts among several, with a prompt asking about the role of the shared expert.
Schematic: four of the routed experts are drawn; the real V3 layer has 256.
14. A fine-grained MoE with E=256 and k=8 picks 8 experts per token. Roughly how many distinct expert combinations are possible?Explain
A 16 by 16 grid of 256 small expert cells with 8 highlighted as one token's choice, and a prompt for the number of distinct sets of 8 that could be chosen.
One arbitrary example choice.
15. A team wants to serve a 671B fp16 MoE. What is the main infrastructure consequence?Explain
A box for a 671B-parameter fp16 model that runs 37B per token, an arrow into a box for GPU memory needed for the weights marked unknown, and a prompt about the main infrastructure consequence.
Figures from the stem.
16. Using the cost-profile table, roughly what share of its stored parameters does Mixtral 8×22B activate per token?Apply
The Mixtral 8x22B row of the cost-profile table with about 39B active per token and 141B total, drawn as a long bar for total and a shorter bar for active, and a prompt for the share.
Values from the lesson's cost-profile table; bars drawn to scale.
17. Your router sends most tokens to two experts while the rest starve, and you do not want a penalty term in the loss. What do you change?Analyze
A bar chart over experts E1 to E8 in which E1 and E5 are very tall and the rest are short, with a note that the training loss is the main loss only, the constraint of no penalty term, and a prompt for the change.
Illustrative usage counts (sum 1000); the stem gives no numbers.
18. A large MoE has low active FLOPs per token, yet serving latency is high and profiling points at the network. What is the likely cause?Analyze
Four GPU boxes each marked compute low, above a wide network bar marked busy, with notes that active FLOPs per token are low yet latency is high, and a prompt for the likely cause.
Typed from the stem; no measured values.
19. You want more expert combinations per token without growing total parameters. Which design fits?Design
Eight full-width expert boxes with k equals 2, and a table: total parameters 8 FFN, combinations per token C(8,2) equals 28, goal more combinations with the same total.
Design as in the lesson's classic MoE description.

Build It

The full version is code/main.py, a compact MoE layer in pure stdlib with:

  • n_experts=8 toy experts, each one linear map followed by silu (only the parameter count in Step 3 uses a ×3 SwiGLU-like factor)
  • top-k=2 routing
  • softmax-normalized gating weights
  • auxiliary-loss-free balancing via per-expert bias

The forward pass has no shared expert. The shared expert from the Concept section only shows up in the Step 3 parameter count.

Step 1: the router

python
def route(hidden, W_router, top_k, bias):
    scores = [sum(h * w for h, w in zip(hidden, W_router[e])) for e in range(len(W_router))]
    biased = [s + b for s, b in zip(scores, bias)]
    top_idx = sorted(range(len(biased)), key=lambda i: -biased[i])[:top_k]
    # softmax over ORIGINAL scores of the chosen experts
    chosen = [scores[i] for i in top_idx]
    m = max(chosen)
    exps = [math.exp(c - m) for c in chosen]
    s = sum(exps)
    gates = [e / s for e in exps]
    return top_idx, gates

Bias affects selection, not gate weight. That is the DeepSeek-V3 trick: the bias corrects load imbalance without steering the model's predictions.

Step 2: run 1000 tokens through the router

Track how often each expert fires. The target is 250 per expert (1000 tokens × 2 / 8). With random router weights the toy starts close to uniform already: iteration 0 has entropy 2.072 against the ln(8) = 2.079 ceiling, so there is little imbalance to fix. Each iteration applies -γ to over-used experts and +γ to under-used ones. With seed 42 and γ=0.15 the usage does not settle. From iteration 1 it alternates between two fixed patterns (entropy 2.076, then 2.073), because each step overshoots. That is why γ needs tuning.

Step 3: param count comparison

Print the parameter counts for the toy MoE and for a DeepSeek-V3-shaped config: 256 routed + 1 shared experts, d_model=7168, 61 layers, each expert 1/8 of a full FFN. The printout counts the shared expert plus 8 routed experts, so 9 experts, as active. It reports about 806B FFN parameters in total (13.2B per layer × 61) and about 28B active. That is the toy's FFN-only arithmetic, not the published 671B total / 37B active, which comes from the real architecture and includes attention and a different expert layout. Use the published figures for the real model.

The next line prints a "llama-3-70b FFN total" of about 13B. It reuses the d=7168 FFN width for 32 layers and counts only FFN parameters, so it is not Llama 3 70B's real size. The closing "same active FLOPs" line is likewise just the toy's shorthand. Compare against published numbers instead: 37B active for DeepSeek-V3 versus 70B for dense Llama 3 70B, which is fewer active FLOPs per token.

Check: Build It0/9
1. In `route()`, does the bias change the gate weights used to combine expert outputs?Explain
A data-flow chart of route(): hidden state to raw scores, then a plus-bias box, then top_idx; a dashed gates box with softmax of an unknown input, with a note asking whether the bias changes the gate weights.
Flow follows route() in main.py; the gate input is left open.
2. Why does the toy's expert usage keep alternating between two patterns at gamma 0.15 instead of settling?Explain
Four small bar charts of usage across experts 1 to 8 for iterations 1 to 4 of main.py, each with a dashed target line at 250; the charts for iterations 1 and 3 look the same, as do those for 2 and 4.
Real run of main.py, seed 42, gamma 0.15.
3. You run `main.py` with seed 42 and `gamma=0.15`. What does expert usage do from iteration 1 onward?Apply
A terminal showing the config line and the iteration 0 usage line of main.py with entropy 2.072, followed by a prompt about iteration 1 onward.
Real output lines from main.py.
4. In route() with top_k=2, the raw scores are [1.0, 3.0, 2.0, 0.5] and the bias is [0, -2, 0, 2]. What does it return?Apply
A table of four experts 0 to 3 with raw scores 1, 3, 2, 0.5 and biases 0, -2, 0, 2, followed by top_idx and gates both unknown.
Inputs from the stem.
5. An expert fired 310 times against a target of 250 and has bias 0.30. With gamma 0.15, what is its bias after the update?Apply
A bar for one expert's usage of 310 against a dashed target line at 250, and a small table with bias before the update 0.30 and bias after the update unknown, with gamma 0.15.
The stem's numbers.
6. In the toy run, 1000 tokens go through 8 experts with top-k=2. What usage per expert counts as perfectly balanced?Apply
1000 tokens entering a router that feeds eight experts E1 to E8, each showing an unknown token count.
The toy run of main.py: 1000 tokens, 8 experts, top-k 2.
7. The Step 3 printout for the DeepSeek-V3-shaped config uses 256 routed experts plus 1 shared. How many experts does it count as active per token?Apply
A shared expert box marked x 1 beside a routed experts box marked x 256, with a prompt for how many experts the Step 3 printout counts as active per token.
Config from the lesson's Step 3 description.
8. A team sees expert usage flip between two patterns every iteration and never settle. What should they change first?Analyze
A line chart of tokens routed to experts 2, 3 and 7 over iterations 0 to 10 of main.py, zigzagging around a dashed target at 250 and never settling.
Real run of main.py, seed 42, gamma 0.15.
9. A reimplementation selects with scores plus bias, then runs the gate softmax over the biased scores of the chosen experts. What changed from the DeepSeek-V3 design?Analyze
A reimplementation drawn as raw scores, scores plus bias, top-k pick, with the note that the gate softmax runs over the biased scores of the chosen experts, and a prompt comparing it with the DeepSeek-V3 design whose gate input is unknown.
Setup from the stem.

Use It

HuggingFace loading:

python
from transformers import AutoModelForCausalLM, AutoTokenizer
model = AutoModelForCausalLM.from_pretrained("mistralai/Mixtral-8x22B-v0.1")

For 2026 production inference, vLLM supports MoE routing natively, and SGLang has the fastest expert-parallel path. Both handle top-k selection and expert parallelism for you.

When to pick MoE:

  • You want frontier quality at lower inference cost per token.
  • You have the VRAM / expert-parallel infrastructure.
  • Your workload is token-heavy (chat, code) not context-heavy (long docs).

When NOT to pick MoE:

  • Edge deployment. You pay full storage for any active FLOP.
  • Latency-critical single-user serving. Expert routing adds overhead.
  • Small models (<7B). MoE's quality advantage appears above a compute threshold (~6B active params).
Check: Use It0/3
1. In 2026 production, which inference stacks handle MoE routing and expert parallelism for you?Explain
A table with rows vLLM and SGLang and columns MoE routing and expert parallelism, every cell unknown, with a prompt about which stacks handle them for you.
Stack names from the lesson's Use It section.
2. A team plans to run a ~3B model on a laptop and is weighing a MoE version. Why is that a poor fit?Design
A laptop box containing a roughly 3B model with fixed memory and compute, and a note about considering a MoE version and asking why it is a poor fit.
Typed from the stem.
3. Which workload is the better match for MoE?Design
Two panels: paid in memory, where every expert stays stored, and paid in compute, where only the chosen experts run, with a prompt asking which workload fits the trade.
Cost split as described in the lesson.

Ship It

The skill in outputs/skill-moe-configurator.md picks E, k, and the shared-expert layout for a new MoE given a parameter budget, training tokens, and a deployment target.

Author's Note

I'd reach for MoE when the deployment has the VRAM and expert-parallel infrastructure to hold all the experts, and I'd skip it for edge serving or models under about 6B active parameters. The storage cost is full-size while the compute cost is the active slice, so the trade pays off when memory is the cheaper side of the ledger.

My bet is that the part worth internalizing is the split in route(). Bias is added to the scores to choose the top-k, and the gate softmax then runs over the raw scores of the chosen experts. Balance gets corrected without touching what the experts contribute. I'd also tune γ rather than trust it: with the seed fixed at 42, main.py at gamma=0.15 swings between two usage patterns from iteration 1 on, and entropy stays near 2.07 against the ln(8) = 2.079 ceiling.

I'd read the toy as a sketch of routing, not of DeepSeek-V3. The experts are one linear map plus silu, the forward pass has no shared expert, and the parameter printout counts the shared expert among the active ones. Treat its numbers as a shape check, and trust the published 37B / 671B figures over the printout.

Exercises

  1. Easy. Run code/main.py. Watch the auxiliary-loss-free bias update over its 10 iterations. Usage starts near uniform and then alternates between two patterns instead of settling. Try a smaller gamma and see whether the swings shrink.
  2. Medium. Replace the learned router with a hash-based router (deterministic, no learning). Compare quality and balance. Why is the learned router better?
  3. Hard. Implement GRPO-style "rollout-matched routing" (DeepSeek-V3.2 trick): log which experts fire during inference, force the same routing during gradient computation. Measure the effect on a toy policy-gradient setup.

Key Terms

TermWhat people sayWhat it actually means
Expert"One FFN among many"An independent feed-forward network; parameters dedicated to a sparse slice of the FFN computation.
Router"The gate"A tiny linear layer that scores each token against each expert; top-k selection.
Top-k routing"k active experts per token"Each token's FFN computation goes through exactly k experts, weighted by gate.
Auxiliary loss"Load-balance penalty"Extra loss term that penalizes skewed expert usage.
Auxiliary-loss-free"DeepSeek-V3's trick"Balance via per-expert bias on the router's selection only; no extra gradient.
Shared expert"Always on"Extra expert through which every token passes; captures common knowledge.
Expert parallelism"Shard by expert"Distribute different experts to different GPUs; route tokens across the network.
Sparsity"Active params < total params"The ratio k × expert_size / (E × expert_size); 37/671 ≈ 5.5% for DeepSeek-V3.

Further Reading

Code

Ships With