cs336 / lessons/10 · moelesson 11 / 20

Part II — Systems

Mixture of Experts — parameters without the FLOPs

Lesson 09 gave you the last tool you need to train an arbitrarily large dense model: tensor, pipeline, and sequence parallelism, composed into a 3D mesh that splits any matmul, any layer stack, any sequence across hundreds of GPUs. So you can now build a 70B, a 400B, a model of any size the cluster can hold. But there is a tax you cannot escape by splitting harder: in a dense model every token flows through every parameter, so the FLOPs per token grow in lockstep with the parameter count, and the budget caps both at once. This lesson breaks that lockstep. The idea is sparsity — only fire a fraction of the network per token — and it lets total parameters grow while the FLOPs you pay stay flat.

The previous step left this broken
Parallelism (lesson 9) solved where the parameters live — shard them across the mesh and any size fits. It did nothing about how many of them each token must touch. A dense Transformer runs every token through every weight matrix, so by the 6N rule of lesson 5 the cost is 2N FLOPs/token forward, 6N to train — capability rises with N, but so does the compute bill, and a fixed budget C = 6ND caps them together. You cannot buy more capability without paying more FLOPs, and you cannot pay more FLOPs without shrinking D or extending the run. Capability is shackled to compute by the very same identity. Splitting the model across more devices does not loosen that knot — it only stores it more places.
Linear position
Forced by: in a dense model, parameters and FLOPs/token rise together, so the budget caps both at once — capability is shackled to compute.
New idea: sparsity — replace the per-block MLP with E experts plus a router that sends each token to only its top-k experts (k ≈ 1–2). Total parameters grow ≈ while FLOPs/token stay near constant. This rests on the course's third motif — an empirical bet, not a theorem: capability ~ total params, cost ~ active params.
Forces next: MoE finishes the question of what to train — any size, dense or sparse. But you still have to execute it: a weeks-long run on thousands of GPUs where hardware fails, the loss spikes, and a stalled job burns money is its own engineering discipline. That is running the job (lesson 11).
The plan
Five moves. (1) State the decoupling that makes MoE possible — capability tracks total params, cost tracks active params. (2) Build the router: top-k softmax gating that replaces one MLP with a choice over E experts. (3) Fix the failure that always appears — expert collapse — with a load-balancing loss or an aux-loss-free bias, and price capacity factor and dropped tokens. (4) Pay the systems bill: expert parallelism turns the MLP into an all-to-all, and memory must hold all experts. (5) Read the real models — Mixtral 8×7B, DeepSeek-MoE — and the dense-vs-MoE trade-table. Then drive the economics widget until E explodes the parameters with the FLOPs dead flat.

1 · The decoupling: capability tracks total params, cost tracks active params

The whole of MoE rests on prying apart two quantities that a dense model welds together. In a dense Transformer they are the same number N: the parameters that store knowledge are exactly the parameters each token multiplies through, so capability and per-token cost are one knob. MoE introduces a second number.

total params (Ntotal)
every weight in the model. This is the capacity to store knowledge and skills — and capability scales roughly with it. Memory must hold all of it.
active params (Nact)
the weights a single token actually touches in one forward pass. FLOPs/token ≈ 2·Nact — the compute bill, and what the budget 6ND prices.
the dense case
Nact = Ntotal. The two are locked, so capability and cost are the same knob — the wall lesson 9 left.

The whole approach rests on an empirical bet, not a theorem: that model quality is driven far more by Ntotal than by Nact. The intuition is that knowledge is sparse and localizable — any given token only needs a small, relevant slice of what the model knows (a coding token doesn't consult the French-poetry weights), so storing more in inactive experts adds capability the token never pays FLOPs to consult. A model can know a great deal as long as the knowledge is stored somewhere and the router can find the right sliver. So if you could grow Ntotal while holding Nact fixed, you would buy capability at constant FLOPs/token — a "free" parameter from the budget's point of view. That gap, Ntotal ≫ Nact, is the entire prize. But it is an empirical regularity that holds in practice, not a guarantee — it can break: if the router collapses onto a few experts, or experts learn redundant rather than specialized knowledge (capacity waste), the extra parameters stop buying capability and you are paying memory and comm for ballast. The rest of the lesson is how to build a network where the bet pays off, and what it costs you elsewhere (memory and communication — there is no actual free lunch, only a moved one).

dense: FLOPs/token ≈ 2N   ·   MoE: FLOPs/token ≈ 2·Nact  ≪  2·Ntotal

2 · The router: replace the MLP with E experts and a top-k gate

Where does the gap come from mechanically? Recall from lesson 2 that the per-block MLP holds about two-thirds of every block's parameters and is applied independently at each token position — it is the network's per-token feature store. MoE attacks exactly this sublayer. Replace the single shared MLP with E independent MLPs — the experts — each the same shape as the original. Then, per token, a small router (a single linear layer Wr of shape d × E) decides which experts that token visits.

# x: (tokens, d)   E experts, each an MLP(d -> d_ff -> d)
logits  = x @ W_r                 # (tokens, E)   router scores, W_r is d×E
probs   = softmax(logits, dim=-1) # (tokens, E)   gate weights over experts
top     = topk(probs, k)          # pick the k highest-scoring experts / token
top     = [(e, g/sum_of_topk_g) for (e,g) in top]  # renormalize gates over the chosen k (Mixtral)
y = 0
for (e, g) in top:                # k of them, k ≈ 1 or 2
    y += g * expert[e](x)         # run ONLY the chosen experts, weight by gate
# tokens not routed to expert e never touch its weights → its FLOPs aren't paid

Read what changed. The router runs for every token (it is tiny: d·E FLOPs, negligible against an expert MLP's ~16d²). But only the top-k experts actually execute. With E = 8 experts and k = 2, each token touches 2 of the 8 MLPs: the block stores 8× the MLP parameters but pays for only 2 of them per token. The gate weight g (the softmax probability, renormalized over the chosen k) scales each expert's contribution so the router is differentiable and learns which expert is good for which token — by backprop, with no explicit supervision of the assignment.

Why top-k and not all? Routing to all E would be a dense model with E parallel MLPs — capability up, but FLOPs up too, defeating the point. Routing to k = 1 (Switch Transformer) minimizes FLOPs but gives the router no way to blend specialties and makes training twitchy. k = 2 is the workhorse (Mixtral, GShard): enough to mix two experts and stabilize gradients, still only 2/E of the compute. The active MLP parameters are k/E of the total expert parameters — that ratio is the decoupling, made concrete.

token x (d,) router W_r softmax → top-k expert 1 ✓ expert 2 expert 3 ✓ … expert E Σ gₑ · expert(x) y (d,) Only the ✓ experts (top-k=2 of E) execute and pay FLOPs; the rest store parameters but stay idle for this token.

3 · The failure that needs fixing: expert collapse, load balancing, capacity

Train the router naively and it breaks in a specific, predictable way: expert collapse. Early on, a few experts get marginally better, so the router sends them slightly more tokens, so they get more gradient and improve faster, so the router sends them even more — a rich-get-richer loop. Within a few thousand steps the router funnels nearly all tokens to a handful of experts while the rest receive almost nothing, never train, and become dead weight. You have paid for parameters and are effectively running a dense model with the others as ballast. The router's argmax is a positive-feedback amplifier, and left alone it always collapses.

The fix is to make balance an explicit objective. Two approaches dominate:

load-balancing aux loss
Add α · E · Σe fe · pe to the loss, where fe = fraction of tokens routed to expert e and pe = mean router probability for e. It is minimized when both are uniform (1/E), so it pushes the router toward even usage. Tiny coefficient (α ≈ 0.01): too large hurts quality, too small lets collapse return.
aux-loss-free bias
DeepSeek-V3's trick: drop the auxiliary loss (which fights the language-model loss) and instead add a per-expert bias to the router logits, nudged up for under-used experts and down for over-used ones each step. Balances load without polluting the gradient — now the common choice.
capacity factor & dropped tokens
Each expert gets a fixed buffer = (tokens/E)·cap, with cap ≈ 1.0–1.5. If more tokens route to an expert than its buffer holds, the overflow is dropped — it skips the MLP (residual passes through unchanged). Higher cap = fewer drops but more compute/memory; lower cap = cheaper but lossy.

The capacity factor exists for a systems reason that section 4 makes clear: experts run as fixed-shape batched matmuls on hardware, so each must be given a statically known number of tokens. Perfect balance is impossible per batch, so you over-provision by cap and drop the rest. A drop is a small quality hit (that token's MLP update is skipped this layer); at cap = 1.25 with a healthy balancing term, drop rates sit at a few percent — the price of turning a dynamic routing decision into a static tensor shape.

4 · Systems: expert parallelism and the all-to-all bottleneck

Now the bill MoE actually moves the cost to. Two facts collide. First, memory holds all experts: Ntotal bytes of weights are resident even though each token uses k/E of them — the 16-bytes/param ledger of lesson 5 now applies to the full expanded parameter count, so an 8× expert expansion is an ~8× MLP-memory bill. Second, the experts are too many to replicate, so you shard them across GPUs: expert parallelism (EP) places different experts on different devices.

That sharding creates a new communication pattern the dense stack never had. A token arrives on the GPU that ran attention for it, but the expert it was routed to lives on some other GPU. So before the expert MLP you must ship every token to wherever its chosen expert sits, and after, ship the result back. Every GPU sends a different slice to every other GPU — an all-to-all collective, twice per MoE layer (dispatch and combine).

attentionruns locally on each GPU's token shard, exactly as in the dense 3D mesh (lesson 9).
all-to-all (dispatch)route every token to the GPU holding its top-k expert(s); each rank sends a custom-sized chunk to every other rank.
expert MLPeach GPU runs its local experts on the tokens it received — a fixed-shape batched matmul (hence capacity factor, §3).
all-to-all (combine)ship each result back to the token's home GPU and sum the k expert outputs with their gate weights.

All-to-all is the MoE analogue of the all-reduce/all-gather that defined Part II's earlier comm walls — and it is the new bottleneck. Its volume scales with token count and is acutely sensitive to network topology: cheap over NVLink inside a node, brutal across the slower inter-node fabric, which is why expert parallelism, like tensor parallelism (lesson 9), wants to stay within high-bandwidth islands. The arithmetic-intensity lens from lesson 6 returns here: all-to-all moves a lot of bytes for relatively little compute, so a poorly placed MoE layer is communication-bound and the saved FLOPs are eaten by stalled GPUs waiting on the network. The kernel-level view of these collectives — how the all-to-all and grouped-GEMM expert kernels are written, fused, and debugged — lives in gpu_kernels · 16 (debugging a kernel / system design), and the topology of placing experts across a cluster is treated in the reinforcement-learning track's distributed-topology lessons.

5 · Real models and the dense-vs-MoE trade

The decoupling is not theoretical — it ships in frontier models. The number that matters is the active/total ratio: how much of the model each token pays for.

Mixtral 8×7B
8 experts, top-k=2. The "8×7B" is loose marketing — shared attention means total is ≈47B, not 56B. Active per token ≈12.9B. So it has ~47B-class storage at ~13B compute cost: a ≈3.6× total/active ratio (46.7B total / 12.9B active).
DeepSeek-MoE / V3
Fine-grained experts (many small ones) for sharper specialization, plus shared experts always on (every token uses them) to hold common knowledge so the routed experts can specialize. DeepSeekMoE-16B used 64 routed + 2 shared (k=6); DeepSeek-V3 scaled this to 256 routed + 1 shared (k=8): ≈671B total, ≈37B active — an ~18× ratio.
the ratio
active/total runs ~1/4 (Mixtral) down to ~1/18 (DeepSeek-V3) and lower. That fraction is the FLOP discount; its inverse is the memory and comm premium you pay for it.
Dense 13BMoE (≈47B total, 13B active)
Total parameters13B≈47B — capability of a far bigger model
FLOPs / token (≈2·Nact)≈26 GFLOP≈26 GFLOP — same compute, despite 3.6× the params
Memory (16 B/param)≈208 GB state≈752 GB state — must hold all experts; the premium
Communicationall-reduce / all-gather (DP/TP)+ all-to-all ×2 per MoE layer — the new bottleneck
Training difficultystandard recipe (lesson 4)harder — balancing loss, capacity tuning, drop rates, router instability

The verdict is honest, not cheerleading: MoE buys capability-per-FLOP — invaluable when compute is the binding constraint and you have the memory and fast interconnect to absorb the premium. It is the wrong trade when memory is the binding constraint (it multiplies the very thing lesson 5 showed already overflows the device) or when your interconnect can't hide the all-to-all. You moved the cost off the FLOP axis; you did not delete it.

6 · Drive the economics

The widget makes the decoupling tangible. Move E (experts), k (top-k), the expert hidden size, and the number of layers. Watch the two bars: total params (blue, what you store, what drives capability) and active params/token (green, what each token touches, what sets FLOPs). The point to internalize: crank E with k fixed and the blue bar explodes while green — and FLOPs/token with it — stays dead flat. That flat green line at a rising blue ceiling is the free parameter, and the gap between them is exactly the capability you bought without paying compute. Then look at total memory climbing with the blue bar: that is where the bill landed.

MoE economics — total params explode, FLOPs stay flat
Blue = total parameters (storage, ≈ capability). Green = active parameters/token (= the FLOPs you pay). The experts strip shows k of E lit per token. Crank E with k fixed: total soars, active is pinned at k/E of the experts — the "free parameter" gap. Memory tracks total; FLOPs track active only.
k of E experts fire per token
total params
active / token
Total params
Active params / token
FLOPs / token (2·Nact)
Total memory (16 B/param)
Total / Active ratio
Active fraction (k/E)

Notice the asymmetry the widget exposes: raising k moves both bars (you pay for more experts/token), but raising E moves only the blue one. That is why frontier MoEs push E high and keep k small — they are buying parameters off the cheap axis. And it is why this lesson hands you a problem rather than a solution: you now have four knobs that all trade differently against the budget.

Failure modes & checklist

Failure modes

  • Expert collapse. Router funnels all tokens to a few experts; the rest never train. Signal: per-expert token counts wildly skewed, validation loss stalls, you're paying for E× params but getting dense-model quality.
  • Balancing coefficient mis-set. Signal: too large → quality regresses below a dense baseline (the aux loss is fighting the LM loss); too small → collapse returns a few thousand steps in.
  • Capacity too low. Signal: high drop rate (10%+), jagged loss, tokens silently skipping the MLP; raise the capacity factor or improve balancing.
  • Comm-bound MoE. Expert parallelism spans slow inter-node links so all-to-all dominates. Signal: MFU craters versus a dense model of the same active size; the saved FLOPs vanish into network stalls.
  • Pricing FLOPs but forgetting memory. "It's only 13B active, it'll fit." Signal: OOM — the 16 B/param ledger applies to Ntotal (47B+), not Nact.

Checklist

  • Separate Ntotal from Nact in every estimate — memory uses total, FLOPs use active.
  • Always add a balancing mechanism (aux loss α≈0.01, or an aux-loss-free router bias) — never train a raw top-k router.
  • Set capacity factor ≈1.25 and monitor drop rate; tune it against balance, not blindly up.
  • Keep expert parallelism inside high-bandwidth islands so the all-to-all stays cheap; measure it, don't assume it's free.
  • Reach for MoE when compute is the binding constraint, not when memory or interconnect is.

Checkpoint

Try it
Open the widget at the Mixtral-ish default (E=8, k=2, d_ff=14336, L=32). Read the total/active ratio (≈3.5 for this simplified config; real Mixtral is ≈3.6×) and the active fraction (k/E = 1/4). Now: (a) crank E to 64 with k pinned at 2 — watch the blue bar and total memory soar while the green bar and FLOPs/token do not move at all; record the new ratio. (b) From there, raise k from 2 to 4 — note that both bars now rise, and explain in one sentence why E and k move different bars. (c) Set E=64, k=6, the DeepSeek-style fine-grained regime, and compare the total/active ratio to part (a). Finally, by hand: at E=16, k=2, if a dense baseline of the same active size needs 2 GPUs to hold its state (lesson 5), roughly how many does the MoE need just for parameter storage, and which knob — E or k — drove that?

Where this points next

MoE just handed you the most powerful lever in the course — buy capability off the cheap (FLOP) axis by spending the expensive (memory + comm) one. With it, Part II is complete: you can now specify what to train at any scale — dense or sparse, any size the cluster can hold, with the FLOP/parameter trade tuned to your budget. But specifying the model and executing the run are different problems. That training run is not an afternoon job: it is weeks of wall-clock on thousands of GPUs, and at that scale the rare becomes routine — a GPU dies mid-step, the loss spikes and threatens to diverge, a node drops off the fabric, and every hour the job is stalled or silently corrupted is thousands of dollars and days of schedule burned. Keeping a run alive and healthy — checkpointing, monitoring, detecting and recovering from failures, reading the loss curve — is its own engineering discipline, and it is what stands between a good configuration and a finished model. Next: 11 · Running the job — executing a weeks-long distributed run.

Takeaway
A dense Transformer welds two numbers together: every parameter that stores knowledge is a parameter every token computes through, so capability (∝ params) and cost (∝ FLOPs/token = 2N) rise in lockstep and the budget 6ND caps both at once — the wall lesson 9 left. Mixture of Experts pries them apart: replace the per-block MLP with E experts and a top-k router (k≈1–2), and each token touches only k/E of the expert parameters. Total params — and capability — grow ≈E×, while active params and FLOPs/token stay near constant. This is the course's third motif made real: capability ~ total params, you pay FLOPs per active parameter. The price is moved, not removed: a naive router collapses onto a few experts, so you need a load-balancing aux loss (α≈0.01) or an aux-loss-free router bias plus a capacity factor (~1.25) that drops overflow tokens; memory must hold all experts (the 16 B/param ledger now applies to Ntotal); and expert parallelism introduces an all-to-all collective, twice per layer, that becomes the new bottleneck if it crosses slow links. Mixtral 8×7B (≈46.7B total, ≈12.9B active) and DeepSeek-V3 (≈671B total, ≈37B active) ship this gap at ratios of ≈3.6× to ~18×. With MoE, Part II is done — you can specify what to train at any scale. What remains is executing it: a weeks-long run on thousands of GPUs, where hardware fails and the loss spikes, is its own engineering discipline — running the job, lesson 11.

Interview prompts