<?xml version="1.0" encoding="utf-8"?><feed xmlns="http://www.w3.org/2005/Atom" ><generator uri="https://jekyllrb.com/" version="4.4.1">Jekyll</generator><link href="https://vaibhav016.github.io/feed.xml" rel="self" type="application/atom+xml" /><link href="https://vaibhav016.github.io/" rel="alternate" type="text/html" /><updated>2026-10-06T01:16:26+00:00</updated><id>https://vaibhav016.github.io/feed.xml</id><title type="html">Vaibhav Singh</title><subtitle>PhD Student at Mila, Concordia University. Research on continual learning and efficient inference for LLMs.</subtitle><author><name>Vaibhav Singh</name><email>vaibhav.singh@mila.quebec</email></author><entry><title type="html">Interrogating Mixture of Experts, Part 1: Why Make a Model Sparse</title><link href="https://vaibhav016.github.io/blog/2026/09/09/interrogating-moe-part-1-why-make-a-model-sparse/" rel="alternate" type="text/html" title="Interrogating Mixture of Experts, Part 1: Why Make a Model Sparse" /><published>2026-09-09T00:00:00+00:00</published><updated>2026-09-09T00:00:00+00:00</updated><id>https://vaibhav016.github.io/blog/2026/09/09/interrogating-moe-part-1-why-make-a-model-sparse</id><content type="html" xml:base="https://vaibhav016.github.io/blog/2026/09/09/interrogating-moe-part-1-why-make-a-model-sparse/"><![CDATA[<h2 id="why-would-you-make-a-model-sparse">Why would you make a model sparse?</h2>

<p>The Transformer series ended with a block I could write from memory. The GPU series put a clock on it. Then an interviewer asked me something that sits across both: “DeepSeek-V3 is a 671 billion parameter model that trains for about the price of a 37 billion parameter one. How?”</p>

<p>The answer is one structural change, applied to one sublayer, and every consequence of it fills five parts. In a dense transformer, parameters and arithmetic per token are the same quantity. You cannot buy one without the other. A Mixture of Experts cuts that link.</p>

<p>Compare two versions of the same feed-forward sublayer. On the left, every token runs one wide FFN. On the right, a router selects two of four smaller FFNs and combines their outputs. Follow the hidden vector for “sat” in “the cat sat down” to see which weights are evaluated and how the mixing equation is built. This is a small teaching example with top-2 softmax routing and ReLU experts, not a simulation of DeepSeek-V3.</p>

<div class="visual-embed">
  <iframe src="/images/blog/moe-visuals/m1-comparison-preview.html" title="Dense FFN versus Mixture of Experts: side-by-side networks and highlighted equations" height="720" loading="lazy" allowfullscreen=""></iframe>
</div>

<details>
<summary>Explore the full DeepSeek-V3 block and parameter counters</summary>

<p>The detailed walkthrough puts one token through one layer of DeepSeek-V3 and writes the working as it goes: normalise, attend, add it back, normalise again, and then the single box a Mixture of Experts replaces. Step 2 opens that box in a dense layer, step 3 opens it in a sparse one, and the last step puts the two next to each other.</p>

<p>Watch the counters at the bottom of the first step, because they answer the question everyone asks next. The arithmetic per token is the same on both tracks, and that is deliberate: nine experts of 2,048 is 18,432, the dense width exactly. What differs is the pool it comes from. The dense token multiplies the same 396 million parameters every time. The MoE token multiplies a different 396 million, chosen out of 11.3 billion.</p>

<div class="visual-embed">
  <iframe src="/images/blog/moe-visuals/m1c-block-flow.html" title="Detailed DeepSeek-V3 block and parameter counters" height="700" loading="lazy"></iframe>
</div>
</details>

<script>
(function () {
  window.addEventListener('message', function (e) {
    var d = e.data;
    if (!d || d.type !== 'visualEmbedHeight' || !d.height) return;
    var frames = document.querySelectorAll('.visual-embed iframe');
    for (var i = 0; i < frames.length; i++) {
      if (frames[i].contentWindow === e.source) { frames[i].style.height = d.height + 'px'; }
    }
  });
})();
</script>

<p>What you get is capacity that costs storage but not FLOPs. What you pay is a routing decision on every token in every layer, a load-balancing problem that has no clean solution, and a network bill that turns a layer of matrix multiplies into a distributed systems problem.</p>

<p>This part is the trade in its simplest form: what the layer becomes, where the parameters go, and what the two columns of the ledger actually say.</p>

<div class="interview-q">
  <span class="interview-q-label"><i class="fa-solid fa-circle-question"></i> They will ask</span>
  <span class="interview-q-text">What is a Mixture of Experts, and why does every large model use one now?</span>
  <details>
    <summary>The 30-second version</summary>
    <span class="interview-q-answer">In a dense transformer the feed-forward network is one wide matrix triple, and every token multiplies against all of it, so FLOPs per token are about twice the parameter count. An MoE layer replaces that one network with a bank of narrower ones, the experts, plus a small router that scores every expert for the token and picks the top few. Only the chosen experts run. Total parameters are set by the size of the bank; arithmetic per token is set by how many you pick. DeepSeek-V3 stores 671 billion parameters and touches 37 billion per token, so it trains and serves on a 37B compute budget with a 671B capacity. That is the whole argument: at fixed compute you get more parameters, and more parameters is what scaling laws want. What you pay is memory, because every expert has to be resident somewhere; communication, because the experts a token wants are on other GPUs; and a routing problem, because nothing in the loss makes the router use the bank evenly.</span>
  </details>
</div>

<h2 id="why-are-parameters-and-flops-the-same-knob-in-a-dense-model">Why are parameters and FLOPs the same knob in a dense model?</h2>

<p>Take the feed-forward sublayer of a dense block. With SwiGLU it is three matrices of shape $d \times d_{ff}$, and a token passes through all three. The parameter count is $3 d\, d_{ff}$ and the arithmetic is one multiply and one add per parameter, so the FLOPs are $6 d\, d_{ff}$.</p>

<p>Twice the parameters, exactly. That relationship holds for every dense matrix in the model, which is where the familiar $6ND$ estimate for training compute comes from: six FLOPs per parameter per token, forward and backward.</p>

<p>So in a dense model there is one dial. Want more capacity? Pay more arithmetic on every token of every batch of the entire training run, and again on every token you ever serve. The scaling laws say capacity is what you want. The compute budget says you cannot have it.</p>

<p>Everything about Mixture of Experts follows from noticing that the token does not actually need all of that width.</p>

<h2 id="what-does-an-moe-layer-replace-the-feed-forward-network-with">What does an MoE layer replace the feed-forward network with?</h2>

<p>A bank of $N$ smaller feed-forward networks, called experts, and a router. The router is a single matrix of shape $d \times N$. It turns the token into $N$ scores, the top $k$ are selected, those $k$ experts run on the token, and their outputs are summed with the router’s scores as weights.</p>

\[y = \sum_{i \in \text{TopK}(s)} g_i \cdot \text{FFN}_i(x), \qquad s = W_r x\]

<p>That is the entire idea. Total parameters scale with $N$. Arithmetic per token scales with $k$. Two dials where a dense model has one.</p>

<p>The experts are usually much narrower than the layer they replace. DeepSeek-V3’s dense layers use $d_{ff} = 18432$; its experts use $d_{ff} = 2048$, a ninth as wide, and it keeps 256 of them plus one that always runs. That choice has a name, fine-grained expert segmentation, and Part 2 is about why it is the right one.</p>

<p>Which layers get a bank, then? Not all of them. The report is blunt about it: “We substitute all FFNs except for the first three layers with MoE layers.” So the first three blocks are ordinary transformer blocks, the other fifty-eight carry the experts, and attention is left alone in every one of the sixty-one.</p>

<p>That is the box the expandable DeepSeek-V3 walkthrough above stops on, and the two steps after it open the box both ways: as one wide network in a dense layer, and as a bank of narrow ones everywhere else.</p>

<h2 id="where-do-deepseek-v3s-671-billion-parameters-actually-go">Where do DeepSeek-V3’s 671 billion parameters actually go?</h2>

<p>Two counts, one model. <strong>Total parameters</strong> count the weights stored; <strong>active parameters</strong> count the weights used for one token. Start with one MoE layer below: choose a word and watch the selection change. The unused experts stay in the bank.</p>

<div class="visual-embed" id="total-parameters">
  <iframe src="/images/blog/moe-visuals/m1-parameter-map.html" title="Total versus active parameters: explore the stored expert bank and the weights used by one token" height="850" loading="lazy"></iframe>
</div>

<p>The two sliders separate the trade-off: <strong>a bigger bank stores more weights; a bigger selection evaluates more weights</strong>. At a fixed selection size, adding experts only increases the small router’s contribution to the active count. Across all 58 MoE layers and the rest of the model, the <a href="https://arxiv.org/abs/2412.19437">published totals</a> are about <strong>671B stored and 37B active per token</strong>. Open the breakdown above to see where they come from.</p>

<div class="key-idea"><span class="key-idea-label"><i class="fa-solid fa-lightbulb"></i> key idea</span>
<span class="key-idea-text">Total parameters tell you how many weights must be stored. Active parameters help estimate the matrix-multiplication work per token. They are not a speedup guarantee: attention, batching, communication and training state still have costs.</span></div>

<h2 id="what-does-the-sparsity-actually-buy">What does the sparsity actually buy?</h2>

<p>Compare at fixed compute, which is the comparison that matters, because compute is the budget. A dense model with DeepSeek-V3’s arithmetic per token has 37 billion parameters, by the identity from two sections ago. V3 has eighteen times that.</p>

<p>The claim that this is worth something is empirical, and the papers are specific. Google’s Switch Transformer reported up to a sevenfold speedup to a fixed quality against dense T5 baselines at equal compute, and about fourfold against T5-XXL. DeepSeek’s own MoE paper reported a 145B sparse model matching their dense 67B while doing a fraction of its arithmetic.</p>

<p>The intuition is the one from <a href="/blog/2026/09/06/interrogating-the-transformer-part-5-what-the-ffn-knows/">Part 5 of the Transformer series</a>: the feed-forward layer behaves like a key-value memory. More slots is more storage. A given token only needs to look up a few of them, and looking up the ones it does not need was never doing any work.</p>

<p>What sparsity does not buy is anything on the attention side. All 11.4 billion parameters of DeepSeek-V3’s attention run on every token. Sparsity lives entirely in the feed-forward half of the block.</p>

<h2 id="what-does-it-cost">What does it cost?</h2>

<p>Three bills, and the interview is usually about the second and third.</p>

<p>Memory first, because it is the one people get wrong. An MoE saves FLOPs, not bytes. Every expert has to be resident in HBM somewhere, because routing is decided per token in the middle of a forward pass and there is no time to fetch a weight from anywhere slower.</p>

<p>How many bytes that is depends on the format the weights are stored in. DeepSeek publish V3 only in FP8, with a conversion script for anyone who wants BF16, so a byte a parameter is the right count for serving it: about 671 GB, against the 640 GB that eight H100s hold.</p>

<p>Say that carefully in an interview, because the training claim is not the same claim. V3 was trained in FP8 mixed precision, not in FP8 throughout. The expensive GEMMs run in FP8. The embedding, the output head, the MoE gating, the normalisations and the attention operators are held at BF16 or FP32 on purpose, and the master weights, the gradients and the optimizer states are all kept higher.</p>

<p>Most checkpoints still ship in BF16, two bytes a parameter, which would put the same model at 1.34 TB. That single choice of format is the largest lever on this line, which is why the last step of the expandable DeepSeek-V3 walkthrough puts a toggle on it.</p>

<p>Training is the worse number either way. At the sixteen bytes per parameter the <a href="/blog/2026/09/07/interrogating-the-gpu-part-4-where-the-bytes-go/">GPU series counted</a> for Adam in mixed precision, 671 billion of them come to 10.7 TB before a single activation. V3’s own choices shave that, keeping optimizer states in BF16 and caching activations in FP8, and it is still a number no single node can hold.</p>

<p>Communication second. The experts a token wants live on other devices, so an MoE layer sends every token across the network and brings the results back, twice per layer, in a collective whose message sizes nobody knows until the router has run. Part 4 does that arithmetic.</p>

<p>Balance third. Nothing in the training objective cares whether the experts get used evenly, and a router left alone will not use them evenly. That is Part 3, and it is the failure mode that quietly deletes the capacity you paid for.</p>

<h2 id="why-the-feed-forward-layer-and-not-attention">Why the feed-forward layer and not attention?</h2>

<p>Two reasons, and having both is the difference between a memorised answer and an understood one.</p>

<p>The feed-forward layer is where the parameters are. In a dense block it is about two thirds of the weights, so it is the only place where sparsifying is worth the machinery.</p>

<p>And it is position-wise. Each token passes through the feed-forward layer independently of every other token, so sending different tokens to different experts changes nothing about what the layer means. Attention is the opposite: it is the one place where tokens interact, and an expert that only saw some of the tokens would be computing a different function, not a cheaper one.</p>

<p>There is research on sparsifying attention heads, and there is sparse attention, which is a different idea about which score entries to compute rather than which weights to use. Neither is what “MoE” means when an interviewer says it.</p>

<h2 id="is-a-sparse-model-just-a-small-model-wearing-a-big-number">Is a sparse model just a small model wearing a big number?</h2>

<p>This is the sharp version of the question and it deserves a real answer rather than a defensive one.</p>

<p>At fixed pretraining loss, the sparse model is often the worse one. There is measured evidence that at equal perplexity a sparse model underperforms a dense one on reasoning-heavy downstream tasks, which is what you would expect if reasoning consumes inference compute that a sparse model does not spend.</p>

<p>But nobody chooses at fixed loss. They choose at fixed compute, or fixed cost per served token, and on both of those axes the sparse model wins, which is why every frontier open-weight release since 2024 has been one.</p>

<p>So the honest framing is that the sparse model is not a 671B model with a 37B price tag. It is a 37B-compute model with 671B worth of storage behind it, and the reason that is a good trade is that storage got cheaper faster than arithmetic did.</p>

<h2 id="rapid-fire-can-you-do-these-from-memory">Rapid fire: can you do these from memory?</h2>

<div class="rapid-fire">
<ol>
<li>Why are parameters and FLOPs per token the same number, up to a factor of two, in a dense layer?</li>
<li>Write the MoE layer's forward equation, including where the gate value multiplies.</li>
<li>Given layers, hidden size, expert width, expert count and top-k, compute total and active parameters.</li>
<li>Does an MoE reduce the memory needed to serve a model? Say exactly which quantity you mean.</li>
<li>Which parts of a transformer block stay dense in every shipped MoE, and why?</li>
<li>Name the three bills that come with sparsity, and which part of the model each lands on.</li>
<li>At fixed pretraining loss, is a sparse model better or worse than a dense one? At fixed compute?</li>
</ol>
</div>

<p>Part 2 opens the router: what it computes, what the gate value is, and why every large model now has hundreds of small experts instead of eight big ones.</p>]]></content><author><name>Vaibhav Singh</name><email>vaibhav.singh@mila.quebec</email></author><category term="mixture-of-experts" /><category term="sparsity" /><category term="llm" /><category term="architecture" /><category term="interview-prep" /><category term="deep-learning" /><summary type="html"><![CDATA[]]></summary></entry><entry><title type="html">Interrogating Mixture of Experts, Part 2: How a Token Picks Its Experts</title><link href="https://vaibhav016.github.io/blog/2026/09/09/interrogating-moe-part-2-how-a-token-picks-its-experts/" rel="alternate" type="text/html" title="Interrogating Mixture of Experts, Part 2: How a Token Picks Its Experts" /><published>2026-09-09T00:00:00+00:00</published><updated>2026-09-09T00:00:00+00:00</updated><id>https://vaibhav016.github.io/blog/2026/09/09/interrogating-moe-part-2-how-a-token-picks-its-experts</id><content type="html" xml:base="https://vaibhav016.github.io/blog/2026/09/09/interrogating-moe-part-2-how-a-token-picks-its-experts/"><![CDATA[<h2 id="how-does-a-token-choose-its-experts">How does a token choose its experts?</h2>

<p>Part 1 established the trade. This part is the mechanism, and it is the part an interviewer will make you write on a whiteboard, because there is nowhere to hide: either you know what shape the router is and what its output multiplies, or you do not.</p>

<p>The whole routing apparatus is one matrix. There is no separate network, no attention over experts, no learned assignment table, and no communication between tokens. A vector goes in, $N$ scores come out, the top $k$ win.</p>

<p>What makes the mechanism interesting is not its complexity. It is that a component holding 0.016% of a layer’s parameters decides where 100% of that layer’s capacity goes, and it makes that decision for one token at a time with no knowledge of what the rest of the batch is doing.</p>

<p>Everything difficult in Parts 3 and 4 is a consequence of that last sentence.</p>

<div class="interview-q">
  <span class="interview-q-label"><i class="fa-solid fa-circle-question"></i> They will ask</span>
  <span class="interview-q-text">Walk me through a single token going through one MoE layer, from hidden state to output.</span>
  <details>
    <summary>The 30-second version</summary>
    <span class="interview-q-answer">The hidden state, a $d$-vector, is multiplied by a $d \times N$ router matrix to give one score per expert. Modern large models pass those through a sigmoid rather than a softmax, take the top $k$, and renormalise those $k$ scores so they sum to one, sometimes times a fixed scaling factor. Each chosen expert runs the full feed-forward computation on the token, and the outputs are summed with the renormalised scores as weights. Models with a shared expert run that one unconditionally and add it too. The gate is a single scalar per expert per token, and it is the only path by which the router's weights ever see the loss: the top-$k$ selection itself is a hard argmax with no gradient, so an expert that is never chosen for a token gets nothing from that token, and neither does the router's opinion about it. In a real implementation you do not run $k$ separate forward passes. You compute the routing for the whole batch, permute the tokens into expert order, run one grouped matrix multiply, and permute back.</span>
  </details>
</div>

<p>Step through the figure with the sliders. It runs on DeepSeek-V3’s actual shapes, and the two controls in the second step let you see the difference between a softmax gate and a sigmoid gate on identical logits.</p>

<div class="visual-embed">
  <iframe src="/images/blog/moe-visuals/m2-routing.html" height="760" loading="lazy"></iframe>
</div>

<script>
(function () {
  window.addEventListener('message', function (e) {
    var d = e.data;
    if (!d || d.type !== 'visualEmbedHeight' || !d.height) return;
    var frames = document.querySelectorAll('.visual-embed iframe');
    for (var i = 0; i < frames.length; i++) {
      if (frames[i].contentWindow === e.source) { frames[i].style.height = d.height + 'px'; }
    }
  });
})();
</script>

<h2 id="what-does-the-router-actually-compute">What does the router actually compute?</h2>

<p>One matrix-vector product. For DeepSeek-V3 the router is $7168 \times 256$, which is 1.84 million parameters and 3.67 megaflops per token.</p>

<p>Set that against what it commits the layer to. The eight chosen experts are 352 million parameters and 705 megaflops. The decision costs half a percent of the arithmetic it authorises.</p>

<p>The router sees one token. It does not see the sequence, it does not see the batch, and it certainly does not see what the other layers decided. Whatever coordination exists between routing decisions is emergent, not designed, and Part 3 is largely about the consequences.</p>

<h2 id="why-did-the-large-models-switch-from-softmax-to-sigmoid">Why did the large models switch from softmax to sigmoid?</h2>

<p>The 2017 formulation and everything through Mixtral used a softmax over the expert scores. DeepSeek-V3’s config says <code class="language-plaintext highlighter-rouge">scoring_func: sigmoid</code>, and Kimi K2 and several others followed.</p>

<p>The reason is the size of the bank. A softmax spreads one unit of probability across all $N$ experts, so as $N$ grows every individual score shrinks. On the token in the figure, a softmax over 256 experts gives the winner about 5%; a sigmoid gives it about 0.75. The sigmoid scores each expert on its own merits and does not care how many others exist.</p>

<p>That has two consequences. Adding experts to the bank does not rescale every existing gate, which matters if you ever want to grow a model. And the scores stay in a range where the numerics are comfortable, rather than drifting toward a regime where an exponential of a drifting logit becomes a precision problem.</p>

<p>After the top-$k$ renormalisation the two look similar, because both get divided by their own sum. The differences live in the gradients, in what happens when $N$ changes, and in the fact that the softmax’s exponential exaggerates whatever gap the logits had.</p>

<h2 id="what-is-the-gate-value-and-what-does-it-multiply">What is the gate value and what does it multiply?</h2>

\[g_i = \frac{s_i}{\sum_{j \in \text{TopK}} s_j} \cdot \lambda, \qquad y = \sum_{i \in \text{TopK}} g_i \, \text{FFN}_i(x)\]

<p>The gate is one scalar per chosen expert. It multiplies that expert’s entire $d$-dimensional output before the sum. DeepSeek-V3 renormalises over the chosen $k$ and then multiplies by a fixed routed scaling factor of 2.5, both of which are in the published config.</p>

<p>Being precise about the order matters, because it is a common interview trip. Some models take the softmax over all $N$ and use those probabilities directly as gates; others take the top $k$ first and normalise within them. The second gives gates that sum to one regardless of how confident the router was, which throws away a signal and gains a scale that does not drift.</p>

<p>Notice what the gate is not. It is not a probability that gets sampled. It is not a mask. It is a deterministic weight in a weighted sum, and its entire job during the backward pass is to be the road the router’s gradient travels down.</p>

<h2 id="why-does-the-router-get-so-little-gradient">Why does the router get so little gradient?</h2>

<p>Because the only differentiable thing in the whole selection is those $k$ scalars.</p>

<p>The top-$k$ operation is a hard argmax. It has no useful derivative, so no gradient flows through the choice itself. What does flow is $\partial L / \partial g_i$ for the $k$ experts that were chosen, and from there back into the router’s weights. The 248 experts that were not chosen contribute nothing to the output and receive nothing from the backward pass, and the router learns nothing about whether one of them would have been better.</p>

<p>This is the exact reason routing is hard, and it has a sharp corollary worth having ready. If you use top-1 and renormalise the gate over the chosen set, the gate is identically 1, a constant, and the router receives no gradient at all from the output. It would learn only from the balancing loss.</p>

<p>Switch Transformer, which is the model that made top-1 respectable, avoids this by keeping the raw softmax probability as the multiplier rather than renormalising. Shazeer’s original 2017 formulation had argued you needed $k \ge 2$ for the router to get a meaningful signal; Switch’s answer was that you need $k \ge 2$ only if you throw the magnitude away.</p>

<div class="key-idea"><span class="key-idea-label"><i class="fa-solid fa-lightbulb"></i> key idea</span>
<span class="key-idea-text">The router's only gradient path is the $k$ gate values that multiply the chosen experts' outputs. The selection itself is not differentiable, so a router that stops choosing an expert stops receiving any information about it, which is why a Mixture of Experts needs a balancing mechanism outside the loss it is trained on.</span></div>

<h2 id="why-256-tiny-experts-instead-of-eight-large-ones">Why 256 tiny experts instead of eight large ones?</h2>

<p>This is the design move that separates the 2024 generation from Mixtral, and it comes from the DeepSeekMoE paper, where it is called fine-grained expert segmentation.</p>

<p>Hold the arithmetic fixed and cut each expert into $m$ narrower pieces, then select $m$ times as many. Active parameters are unchanged to the digit. FLOPs are unchanged. What changes is the number of distinct expert teams a token can assemble, and it changes by a lot: $\binom{8}{2}$ is 28, while $\binom{256}{8}$ is about $4 \times 10^{14}$.</p>

<p>The bet is that expressiveness in the combination is worth something, and the published models all took it. Mixtral had 8 experts and used 2. DeepSeek-V3 has 256 and uses 8. Kimi K2’s config lists 384 and uses 8. Qwen3-235B lists 128 and uses 8.</p>

<p>What it costs is not FLOPs. It is a router matrix that grows with $N$, a longer sort in the kernel, and $mk$ network destinations per token instead of $k$, which is the whole subject of Part 4. Fine-grained routing is cheap in arithmetic and expensive in systems work.</p>

<h2 id="what-is-a-shared-expert-for-and-does-it-help">What is a shared expert for, and does it help?</h2>

<p>DeepSeekMoE’s other idea: hold out one or two experts that run on every token, outside the top-$k$. The argument is that whatever every token needs, common syntax, the shape of the residual stream, does not need to be learned redundantly in all 256 experts, so isolating it frees the routed experts to be different from each other.</p>

<p>DeepSeek, Moonshot and Z.ai all ship one. Their published configs list <code class="language-plaintext highlighter-rouge">n_shared_experts: 1</code> alongside 256, 384 and 160 routed experts respectively.</p>

<p>And it is genuinely contested. AI2 ran the matched-compute ablation for OLMoE and reported that sharing an expert performed slightly worse than using two routed ones, with a neat combinatorial argument: with 32 experts and 4 active, making one of them shared cuts the choices from $\binom{32}{4} = 35{,}960$ to $\binom{31}{3} = 4{,}495$, removing almost 90% of the combinations. Alibaba shipped shared experts in Qwen2.5-MoE and removed them in Qwen3.</p>

<p>If asked, the honest answer is that it is a small effect either way, that the labs disagree, and that it probably interacts with granularity, because the more experts you have the less a single always-on one costs you in combinations.</p>

<h2 id="what-happens-when-an-expert-runs-out-of-room">What happens when an expert runs out of room?</h2>

<p>To run the experts as batched matrix multiplies you need fixed-size buffers, and you have to size them before you know the routing. That is what the capacity factor is for. Switch Transformer defines expert capacity as tokens per batch divided by the number of experts, times a capacity factor, and a token arriving at a full expert is simply dropped: its computation is skipped and it passes to the next layer through the residual connection.</p>

<p>Note what that means. A dropped token is not an error and does not raise an exception. It is a token that silently did not get a feed-forward layer, and it happens more the more skewed the routing is.</p>

<p>The capacity factor is the knob and it is a bad one. Too low and you drop tokens; too high and your buffers sit half empty and the matrix multiply is mostly padding.</p>

<p>The modern answer is to stop doing it. Block-sparse kernels, of which MegaBlocks is the reference, run variable-size expert groups directly and remove the capacity factor as a hyperparameter entirely. DeepSeek state plainly that V3 does not drop any tokens during training, and describe a deployment strategy that avoids dropping at inference too. If you are asked about capacity factors in 2026, the useful answer explains what they were for and why the frontier stopped needing them.</p>

<h2 id="what-does-the-kernel-actually-run">What does the kernel actually run?</h2>

<p>Not $k$ forward passes. This is the misconception that a whiteboard question is designed to catch.</p>

<p>The sequence is: compute the router for the whole batch, take the top $k$, sort the token rows by expert index into one contiguous buffer, run a single grouped matrix multiply whose group boundaries are the expert offsets, then scatter the results back to token order and weight them by the gates.</p>

<p>The two permutations are pure data movement. For a batch of 8192 tokens with $k = 8$ and $d = 7168$ they move a few gigabytes per layer in BF16 and do zero arithmetic, which puts them at the far left of the roofline from <a href="/blog/2026/09/07/interrogating-the-gpu-part-2-the-roofline/">Part 2 of the GPU series</a>: bandwidth-bound, nothing else.</p>

<p>That is where a naive implementation loses most of its throughput, and it is why MegaBlocks, the Triton grouped-GEMM kernels and DeepSeek’s own DeepEP exist. It is also why the group sizes being unknown until the router has run is such a persistent problem: a kernel that wants static shapes has to wait for a number that arrives late, every layer, every step.</p>

<h2 id="rapid-fire-can-you-do-these-from-memory">Rapid fire: can you do these from memory?</h2>

<div class="rapid-fire">
<ol>
<li>Write the router, the top-k, the gate normalisation and the combine, in that order, with shapes.</li>
<li>Why did large-bank models move from a softmax gate to a sigmoid one?</li>
<li>Which quantity carries the router's gradient, and what receives no gradient at all?</li>
<li>Why does top-1 with a renormalised gate give the router no gradient from the output, and what did Switch do instead?</li>
<li>At fixed active parameters, what does splitting each expert into four change and what does it not?</li>
<li>State the combinatorial argument against a shared expert.</li>
<li>Define expert capacity, and say what happens to a token that overflows it.</li>
<li>Describe what one MoE layer's kernel does, and why the permutations are the expensive part.</li>
</ol>
</div>

<p>Part 3 is the failure mode: what happens to a router that nobody is supervising, and the three different mechanisms the field uses to stop it.</p>]]></content><author><name>Vaibhav Singh</name><email>vaibhav.singh@mila.quebec</email></author><category term="mixture-of-experts" /><category term="routing" /><category term="llm" /><category term="architecture" /><category term="interview-prep" /><category term="deep-learning" /><summary type="html"><![CDATA[]]></summary></entry><entry><title type="html">Interrogating Mixture of Experts, Part 3: Keeping the Experts Busy</title><link href="https://vaibhav016.github.io/blog/2026/09/09/interrogating-moe-part-3-keeping-the-experts-busy/" rel="alternate" type="text/html" title="Interrogating Mixture of Experts, Part 3: Keeping the Experts Busy" /><published>2026-09-09T00:00:00+00:00</published><updated>2026-09-09T00:00:00+00:00</updated><id>https://vaibhav016.github.io/blog/2026/09/09/interrogating-moe-part-3-keeping-the-experts-busy</id><content type="html" xml:base="https://vaibhav016.github.io/blog/2026/09/09/interrogating-moe-part-3-keeping-the-experts-busy/"><![CDATA[<h2 id="how-do-you-stop-one-expert-from-eating-everything">How do you stop one expert from eating everything?</h2>

<p>Part 2 ended on the sentence that makes this part necessary: the router’s only gradient is the gate values of the experts it already chose. An expert it stops choosing sends back nothing, and the router learns nothing about it.</p>

<p>That is a positive feedback loop with no damping term. An expert that wins a few extra tokens early gets a few extra gradient updates, becomes slightly better at the tokens it sees, and wins more of them. Left alone, a Mixture of Experts is a winner-take-all system.</p>

<p>The failure is quiet. The loss curve looks fine for a long time, because a bank in which four experts do all the work is still a perfectly good four-expert model. What is being destroyed is the capacity you paid the memory bill for, and nothing in the objective mentions it.</p>

<p>Three mechanisms exist to stop it, and a good answer distinguishes them by what they change: the loss, the selection, or the numerics.</p>

<div class="interview-q">
  <span class="interview-q-label"><i class="fa-solid fa-circle-question"></i> They will ask</span>
  <span class="interview-q-text">Your router is sending most of the tokens to a handful of experts. What is happening and what do you do?</span>
  <details>
    <summary>The 30-second version</summary>
    <span class="interview-q-answer">It is router collapse, and it is the default behaviour rather than a bug. The gradient only reaches experts that are selected, so an early lead compounds: a slightly favoured expert gets more tokens, improves faster, and gets more tokens. The classic fix is the auxiliary loss from GShard and Switch Transformer, $\alpha N \sum_i f_i P_i$, where $f_i$ is the fraction of tokens dispatched to expert $i$ and $P_i$ is the mean router probability it received. Both vectors sum to one, so the product is minimised when both are flat, and the $N$ in front makes that minimum exactly 1. The subtlety is that $f_i$ is a histogram of hard decisions and has no gradient, so all of the pressure goes through $P_i$. DeepSeek-V3 replaced it with a per-expert bias added to the score used for the top-$k$ comparison and nowhere else, nudged up for starved experts and down for crowded ones after every step, which balances the load without adding a term to the loss or distorting any gradient. Separately, ST-MoE's router z-loss penalises the squared log-sum-exp of the router logits, which is about numerical stability rather than balance. In practice you log the per-layer load histogram every step, because collapse usually starts in one layer.</span>
  </details>
</div>

<p>The figure runs the same sixteen-expert router three times on the same token stream: once with nothing, once with the auxiliary loss, once with the bias. Press play on the first step and watch it fall over.</p>

<div class="visual-embed">
  <iframe src="/images/blog/moe-visuals/m3-load-balancing.html" height="760" loading="lazy"></iframe>
</div>

<script>
(function () {
  window.addEventListener('message', function (e) {
    var d = e.data;
    if (!d || d.type !== 'visualEmbedHeight' || !d.height) return;
    var frames = document.querySelectorAll('.visual-embed iframe');
    for (var i = 0; i < frames.length; i++) {
      if (frames[i].contentWindow === e.source) { frames[i].style.height = d.height + 'px'; }
    }
  });
})();
</script>

<h2 id="why-does-a-router-collapse">Why does a router collapse?</h2>

<p>Two rules are enough, and they are both true of any Mixture of Experts trained by gradient descent.</p>

<p>Tokens improve an expert. An expert that receives more tokens takes more gradient steps on the task, so it gets better at it.</p>

<p>A better expert scores higher. The router is trained to send tokens where the loss goes down, so an expert that is better at a token attracts more of them.</p>

<p>Compose those and you have compounding. The figure’s first step models exactly this and nothing else, with the two rules written at the top of its source, and it collapses to a single expert in about a hundred steps. Nothing exotic is required: no bad data, no bad initialisation, no bug.</p>

<p>What makes it dangerous is the shape of the curve. The load imbalance sits flat for the first forty steps, drifts for thirty more, then falls off a cliff. If your only instrument is the training loss, the first sign of trouble arrives long after the mechanism has started.</p>

<h2 id="what-does-collapse-actually-cost-you">What does collapse actually cost you?</h2>

<p>Nothing you can see on a memory profile, which is the point.</p>

<p>The parameters are all still there. The checkpoint is the same size, the HBM bill from Part 1 has not moved by a byte, and the FLOPs per token are unchanged because $k$ experts still run. What has moved is how much of the bank is learning anything. An expert with no tokens gets no gradient, so it stops improving, and a sixteen-expert bank in which one expert does everything is a one-expert model with sixteen experts’ worth of storage.</p>

<p>Scale that to a frontier model. If a quarter of DeepSeek-V3’s routed experts went quiet, about 163 billion parameters would stop learning while continuing to occupy HBM and continuing to be sharded, checkpointed and shipped.</p>

<p>That is why the load histogram, logged per layer per step, is the cheapest instrument in an MoE run and the first thing to add. It costs an $N$-element reduction and it is the only place collapse is visible early.</p>

<h2 id="what-does-the-auxiliary-loss-actually-penalise">What does the auxiliary loss actually penalise?</h2>

<p>The formulation from GShard, made standard by Switch Transformer:</p>

\[L_{\text{bal}} = \alpha \, N \sum_{i=1}^{N} f_i \, P_i\]

<p>where $f_i$ is the fraction of tokens in the batch dispatched to expert $i$, and $P_i$ is the mean router probability assigned to expert $i$ over those tokens. Both vectors sum to one over the $N$ experts.</p>

<p>Their dot product is smallest when both are flat, and the $N$ in front normalises the minimum to exactly 1. Its maximum is $N$, when one expert takes everything. So the number is readable: 1 means perfectly even, 16 on a sixteen-expert bank means total collapse, and in practice it hovers a little above 1 because routing noise never lets it settle.</p>

<p>Switch used $\alpha = 10^{-2}$, having swept it from $10^{-1}$ down to $10^{-5}$ and found that value balanced the load quickly without interfering with the training loss. That sweep is worth remembering: too small and the mechanism does nothing, too large and you are optimising for uniformity rather than for the task.</p>

<h2 id="why-the-product-of-two-vectors-and-not-the-variance-of-one">Why the product of two vectors and not the variance of one?</h2>

<p>Because you need something differentiable, and $f$ is not.</p>

<p>$f_i$ is a histogram. It counts how many tokens the hard top-$k$ actually sent to expert $i$, so it is piecewise constant in the router’s weights and its gradient is zero almost everywhere. Penalising the variance of $f$ alone would give you a number that describes the problem and a gradient that does nothing about it.</p>

<p>$P_i$, the mean router probability, is smooth. Multiplying the two gives a surrogate that reads as “wherever a lot of tokens actually went, push the probability down”, and its gradient is</p>

\[\frac{\partial L_{\text{bal}}}{\partial s_j} = \alpha N P_j \Big( f_j - \sum_i f_i P_i \Big)\]

<p>which is positive for over-used experts and negative for under-used ones. Being able to say which factor carries the gradient is the standard follow-up question, and it separates people who have read the formula from people who have implemented it.</p>

<p>There is a second-order version of this worth knowing, because it is an implementation detail that changes the model rather than the speed. The batch you compute $f_i$ over is a choice.</p>

<p>Most frameworks compute it per micro-batch, which at frontier scale is a handful of sequences, so the term effectively demands that every individual sequence spread itself evenly over all the experts. A micro-batch of pure code is then forced to use the whole bank.</p>

<p>Qiu and colleagues showed in January 2025 that computing $f_i$ over the global batch instead, which costs one extra all-reduce of an $N$-element vector, improves both perplexity and the domain specialisation of the experts, at scales up to 42.8 billion parameters and 400 billion tokens.</p>

<h2 id="what-is-deepseeks-auxiliary-loss-free-balancing">What is DeepSeek’s auxiliary-loss-free balancing?</h2>

<p>The observation behind it is that the auxiliary loss is a foreign term. Its gradient is not trying to make the model better at the task; it is trying to make the histogram flat, and those two gradients are added together and pull in different directions.</p>

<p>So remove it. Keep a per-expert bias $b_i$, add it to the score used for the top-$k$ comparison, and leave the gate value alone:</p>

\[\text{selection: } \text{TopK}\big(s_i + b_i\big), \qquad \text{gate: } g_i \text{ from } s_i \text{ alone}\]

<p>After each step, decrease $b_i$ by a small $\gamma$ for experts that were over-subscribed and increase it for those that were under-subscribed. No term is added to the loss. No gradient is distorted. It is a controller bolted to the side of the router rather than an objective, and it works because it changes which experts are chosen without changing how much they count.</p>

<p>DeepSeek’s numbers for it, on models up to 3 billion parameters, are a perplexity of 9.50 against the auxiliary loss’s 9.56 at 1B parameters and 100B tokens, with a maximum load violation of 0.04 against 0.72. Better balance and slightly better perplexity, which is the claim.</p>

<p>Two footnotes, both worth having. In DeepSeek-V3 the bias update rate was $\gamma = 0.001$ for the first 14.3 trillion tokens and zero for the rest, and they kept a complementary sequence-wise auxiliary loss at $\alpha = 0.0001$ to stop any single sequence from piling onto one expert. So the “loss-free” method ships alongside a small loss, with its own controller mostly switched off.</p>

<p>And the paper was rejected at ICLR 2025 and shipped in V3 anyway. In the public reviews the authors conceded that the interference-gradient motivation was based on intuition and needed more rigorous validation. Knowing that is a better answer than either endorsing or dismissing the method.</p>

<div class="key-idea"><span class="key-idea-label"><i class="fa-solid fa-lightbulb"></i> key idea</span>
<span class="key-idea-text">Balancing is not one mechanism but three, and they act in different places: the auxiliary loss adds a gradient to the objective, the per-expert bias changes the selection without touching the loss, and the z-loss constrains the magnitude of the router logits without touching the ranking.</span></div>

<h2 id="what-is-the-z-loss-for-and-why-is-it-a-different-problem">What is the z-loss for, and why is it a different problem?</h2>

<p>The router z-loss, from the ST-MoE paper, is not about balance at all.</p>

\[L_z = \frac{1}{B} \sum_{b=1}^{B} \Big( \log \sum_{j=1}^{N} e^{x_j^{(b)}} \Big)^2\]

<p>It penalises the squared log-sum-exp of the router logits. Squaring means the penalty grows with the size of the logits and says nothing about which expert is winning, so it constrains magnitude and leaves the ranking alone.</p>

<p>The reason it exists is numerical. Router logits drift upward over a long run, the way attention logits do, and large numbers have large rounding errors in low precision, which an exponential then amplifies.</p>

<p>ST-MoE chose a coefficient of 0.001 by sweeping for the best model quality after pretraining, and they pair it with a second recommendation: cast the router’s input to float32 before the softmax and cast the dispatch tensors back to bfloat16 afterwards. The router tensor is tiny, so the fp32 cast costs nothing and removes the problem at the source.</p>

<p>This is the router’s version of the QK-norm story from the Transformer series. An exponential of a drifting logit is a stability hazard wherever it appears, and the fix is always to bound the logit rather than to fix the exponential.</p>

<h2 id="does-a-balanced-router-mean-specialised-experts">Does a balanced router mean specialised experts?</h2>

<p>No, and this is the question that separates a memorised answer from an understood one.</p>

<p>Every metric in this part counts how many tokens each expert received. None of them says anything about whether the assignment means anything. Two routings can have identical perfect balance, zero maximum violation and an auxiliary loss of exactly 1, with one assigning tokens by content and the other assigning them at random. The figure’s last step draws both.</p>

<p>What the evidence says is genuinely mixed, and the disagreement is informative. Mistral’s own routing analysis of Mixtral found no obvious pattern by topic: the expert assignment distribution for arXiv papers, PubMed abstracts and philosophy papers is very similar at every layer. What they did find is syntactic and positional structure. Python’s <code class="language-plaintext highlighter-rouge">self</code> and indentation tokens route consistently, and consecutive tokens pick the same first-choice expert about 28% of the time at layer 15 against a 12.5% random baseline.</p>

<p>ST-MoE found clear specialisation in their encoder experts, punctuation, verbs, proper nouns, and explicitly did not find it in the decoder. They also looked for language specialisation in a multilingual model and found none: experts handled English, Japanese, French and Chinese indiscriminately.</p>

<p>AI2’s OLMoE, trained from scratch rather than upcycled, does report domain and vocabulary specialisation, with one layer-0 expert nearly 100% specialised to arXiv, and they hypothesise that Mixtral shows less of it because it was upcycled from a dense model, so all its experts started from the same optimum.</p>

<p>So the honest position: specialisation at the level of token class and vocabulary is real and measurable; specialisation by subject matter is contested and may depend on how the model was initialised; specialisation by language does not appear. “Which expert handles medicine” is the wrong question. “Is any expert idle” is the right one.</p>

<h2 id="what-do-you-actually-watch-during-a-run">What do you actually watch during a run?</h2>

<p>Four things, and none of them is the training loss.</p>

<p>The per-layer load histogram, every step. Collapse starts in one layer and spreads, so a model-wide average hides it. The single number to derive from it is the maximum violation: the busiest expert’s load over the even load, minus one.</p>

<p>The number of experts receiving zero tokens. It is the metric that turns into lost capacity, and it is a step function: fine, fine, fine, then several.</p>

<p>The router logit magnitude, which is what the z-loss is defending, and which tells you whether the numerics are drifting before a spike appears.</p>

<p>And the auxiliary loss value itself, if you are using one, remembering that it never reaches its minimum and is not supposed to. What matters is the trend, not the level.</p>

<h2 id="rapid-fire-can-you-do-these-from-memory">Rapid fire: can you do these from memory?</h2>

<div class="rapid-fire">
<ol>
<li>Describe the feedback loop that makes a router collapse, in two sentences.</li>
<li>Write the auxiliary load-balancing loss, define both vectors, and say what its minimum and maximum are.</li>
<li>Which factor of the auxiliary loss carries the gradient, and why is the other one there at all?</li>
<li>Why does the batch you compute the dispatch fractions over change the model, not just the speed?</li>
<li>Where exactly does the per-expert bias enter, and where does it deliberately not enter?</li>
<li>Write the router z-loss and say what problem it is solving.</li>
<li>Give two routings with identical perfect load balance and completely different meaning.</li>
<li>Name four things you would log every step in an MoE run.</li>
</ol>
</div>

<p>Part 4 leaves the loss function and asks what it costs to actually run this: two all-to-alls a layer, and the reason a sparse model is harder to batch during decoding than a dense one.</p>]]></content><author><name>Vaibhav Singh</name><email>vaibhav.singh@mila.quebec</email></author><category term="mixture-of-experts" /><category term="load-balancing" /><category term="training" /><category term="llm" /><category term="interview-prep" /><category term="deep-learning" /><summary type="html"><![CDATA[]]></summary></entry><entry><title type="html">Interrogating Mixture of Experts, Part 4: The Bill for Sparsity</title><link href="https://vaibhav016.github.io/blog/2026/09/09/interrogating-moe-part-4-the-bill-for-sparsity/" rel="alternate" type="text/html" title="Interrogating Mixture of Experts, Part 4: The Bill for Sparsity" /><published>2026-09-09T00:00:00+00:00</published><updated>2026-09-09T00:00:00+00:00</updated><id>https://vaibhav016.github.io/blog/2026/09/09/interrogating-moe-part-4-the-bill-for-sparsity</id><content type="html" xml:base="https://vaibhav016.github.io/blog/2026/09/09/interrogating-moe-part-4-the-bill-for-sparsity/"><![CDATA[<h2 id="what-does-an-moe-cost-to-train-and-to-serve">What does an MoE cost to train and to serve?</h2>

<p>Parts 2 and 3 were about the router as a piece of mathematics. This part is about what happens when you actually have to run it, and the answer is that a Mixture of Experts layer stops being a matrix multiply and becomes a distributed systems problem.</p>

<p>The reason is simple to state. A dense layer’s weights are the same for every token, so you can shard them however you like and the tokens never move. An MoE layer’s weights are chosen per token, the chosen ones are on other devices, and so the tokens move instead.</p>

<p>That single change swaps an all-reduce for two all-to-alls, replaces static message sizes with sizes the router decides at runtime, and, during generation, quietly undoes most of the arithmetic-intensity argument that made sparsity attractive in the first place.</p>

<p>The GPU series drew expert parallelism as <a href="/blog/2026/09/07/interrogating-the-gpu-part-6-the-2026-toolkit/">one axis on the parallelism map</a>. This part is what happens along that axis.</p>

<div class="interview-q">
  <span class="interview-q-label"><i class="fa-solid fa-circle-question"></i> They will ask</span>
  <span class="interview-q-text">Why is serving a Mixture of Experts harder than serving a dense model with the same active parameter count?</span>
  <details>
    <summary>The 30-second version</summary>
    <span class="interview-q-answer">Three reasons, in increasing order of how often people miss them. Memory: the whole bank has to be resident because routing is decided per token mid-forward-pass, so you size HBM by total parameters and get throughput from active ones. Communication: the experts a token wants are on other GPUs, so every layer runs two all-to-alls whose payload sizes are unknown until the router has run, which fights static shapes and CUDA graphs. And arithmetic intensity, which is the one that decides throughput. Each decoding token picks $k$ of $N$ experts independently, so a batch of $B$ tokens touches about $N(1 - (1 - k/N)^B)$ distinct experts per layer. At batch one that is $k$. At batch 128 on DeepSeek-V3 it is 252 of 256. So the bytes you read stop scaling with $k$ and start scaling with $N$ while the FLOPs keep scaling with $k$, and the intensity settles at $N/k$ times worse than a dense model of the same weights. DeepSeek-V3 needs roughly thirty-two times the concurrent batch of a dense model to reach the same point on the roofline, which is exactly why the published serving setups run expert parallelism a hundred and forty-four ways during decode.</span>
  </details>
</div>

<p>The figure computes all of that from DeepSeek-V3’s shapes and H100-class hardware, with every assumption on the screen so you can disagree with it.</p>

<div class="visual-embed">
  <iframe src="/images/blog/moe-visuals/m4-systems.html" height="760" loading="lazy"></iframe>
</div>

<script>
(function () {
  window.addEventListener('message', function (e) {
    var d = e.data;
    if (!d || d.type !== 'visualEmbedHeight' || !d.height) return;
    var frames = document.querySelectorAll('.visual-embed iframe');
    for (var i = 0; i < frames.length; i++) {
      if (frames[i].contentWindow === e.source) { frames[i].style.height = d.height + 'px'; }
    }
  });
})();
</script>

<h2 id="why-is-expert-parallelism-the-natural-split">Why is expert parallelism the natural split?</h2>

<p>Because a bank of experts has a seam that a dense matrix does not. Expert parallelism sits alongside the other four axes in the <a href="/blog/2026/09/09/interrogating-parallelism-part-5-long-sequences-and-sparse-models/">Parallelism series</a>; here it is the sparse model’s own bill.</p>

<p>Tensor parallelism cuts every matrix by rows or columns, so each device computes a partial result and the partials have to be summed: an all-reduce, every layer, with a message size known at compile time. That works, and for the attention half of an MoE model it is still what you do.</p>

<p>Expert parallelism gives whole experts to whole devices. Nothing needs summing across devices, because each expert’s output is complete where it was computed. What has to move instead is the tokens, out to their experts and back with the results.</p>

<p>For DeepSeek-V3 the arithmetic is stark. One MoE layer’s experts are 11.3 billion parameters. Spread 257 experts over 64 GPUs and each holds four of them, 177 million parameters per layer instead of 11.3 billion. That is not an optimisation, it is the only reason 671 billion parameters fit anywhere.</p>

<h2 id="what-travels-and-how-much-of-it">What travels, and how much of it?</h2>

<p>Per token, per layer, in each direction: the hidden state out to each destination, and one result vector back per expert.</p>

<p>Dispatch sends $d$ values per destination. Combine brings back $d$ values per expert. DeepSeek’s DeepEP dispatches in FP8 and combines in BF16, so the return journey moves twice the bytes of the outward one. With $d = 7168$ and $k = 8$ that is about 57 kB out and 115 kB back per token per layer, before any optimisation.</p>

<p>Now multiply. Fifty-eight MoE layers, two collectives each in the forward pass and two more in the backward, is 232 all-to-alls per training step, each of which is a full every-GPU-to-every-GPU exchange with $P^2$ flows.</p>

<p>The lever that matters most is node-limited routing. Cap the number of distinct nodes a token is allowed to reach, send it across the slow network once per node rather than once per expert, and fan it out over NVLink inside. DeepSeek-V3 capped it at four nodes, which turns eight network destinations into at most four.</p>

<h2 id="is-an-moe-layer-a-flop-problem-or-a-network-problem">Is an MoE layer a FLOP problem or a network problem?</h2>

<p>Do the arithmetic and it is not close.</p>

<p>Take the figure’s default: expert parallelism 64 across eight nodes, 8192 tokens per GPU, node-limited routing on, an H100 at 989 TFLOP/s with 450 GB/s of NVLink and a 400-gigabit port that moves 50 GB/s. Each GPU computes about 65,000 token-expert pairs, which is 6.5 TFLOP and 6.6 milliseconds at peak. The traffic it has to move is 705 MB across the network, which is 14.1 milliseconds, plus 1.4 GB over NVLink, which is 3.1.</p>

<p>Communication is 2.6 times the arithmetic. That is the whole story of why MoE systems work exists, and it is why an interviewer asking “what is the bottleneck in an MoE layer” is not asking about matrix multiplies.</p>

<p>Change any input and the ratio moves, which is why the sliders are there. What does not move is the shape of the answer. The arithmetic and the traffic both grow linearly with tokens, so batching does not help the ratio. What helps is the interconnect, the precision of the payload, and how many nodes a token is allowed to touch.</p>

<div class="key-idea"><span class="key-idea-label"><i class="fa-solid fa-lightbulb"></i> key idea</span>
<span class="key-idea-text">An MoE layer's expert arithmetic and its all-to-all traffic both scale linearly with the number of tokens, so the ratio between them is a property of the architecture and the interconnect, not of the batch size. You cannot batch your way out of it. You can only overlap it, shrink the payload, or bound the fan-out.</span></div>

<h2 id="how-do-you-hide-the-all-to-all">How do you hide the all-to-all?</h2>

<p>A ratio above one only hurts if the two things happen in sequence, and essentially all the systems work since GShard is about making them happen at the same time.</p>

<p>Pipeline schedules put a different micro-batch’s arithmetic underneath this one’s collective. DeepSeek’s DualPipe runs the pipeline in both directions at once so there is always compute travelling the other way, which the GPU series covered as the reason their 64-way expert parallelism was affordable at all.</p>

<p>Kernel libraries attack it from below. DeepEP’s dispatch and combine kernels are written for zero or minimal streaming-multiprocessor occupation, which matters more than it sounds: an all-to-all implemented as an ordinary kernel steals the multiprocessors the expert matrix multiplies want, so a communication kernel that uses no SMs is not just faster, it stops competing.</p>

<p>DeepSeek’s own April 2026 report puts a number on how far this goes. They split the experts into waves, so that the computation of the current wave, the token transfer for the next, and the result-sending of the completed ones all proceed at once.</p>

<p>And they state the balance condition directly: because a token-expert pair costs $6hd$ FLOPs but only $3h$ bytes, each gigabyte per second of interconnect bandwidth suffices to hide the communication for 6.1 teraflop per second of compute. Above that threshold, more bandwidth buys nothing. They report 1.50 to 1.73 times speedups on general inference and up to 1.96 on latency-sensitive work, and they shipped the fused kernel as MegaMoE.</p>

<p>That result is also why V4 removed the node-limited routing constraint that V3 needed. Once the communication is genuinely hidden, the cap that bounded it stops earning its cost in routing quality.</p>

<h2 id="why-is-decoding-a-sparse-model-harder-than-decoding-a-dense-one">Why is decoding a sparse model harder than decoding a dense one?</h2>

<p>This is the part that surprises people, and it is the single best thing to have on your fingertips in this whole series.</p>

<p>Training moves thousands of tokens together. Generation moves one per sequence per step. Each decoding token picks its $k$ experts independently, so with $B$ tokens decoding together the expected number of distinct experts touched in a layer is</p>

\[\mathbb{E}[\text{experts touched}] = N \left( 1 - \left(1 - \tfrac{k}{N}\right)^{B} \right)\]

<p>For DeepSeek-V3, $N = 256$ and $k = 8$. At $B = 1$ that is 8 experts, 3% of the bank. At $B = 32$ it is 163. At $B = 128$ it is 252, which is 98% of the bank.</p>

<p>So the bytes you have to read stop scaling with $k$ and start scaling with $N$, while the FLOPs keep scaling with $k$. Arithmetic intensity is the ratio of exactly those two things, and it collapses.</p>

<p>At $B = 128$, one MoE layer reads about 11 GB of expert weights to do about 100 gigaflops: an intensity of 9 FLOP per byte, against the H100 ridge of 295 from <a href="/blog/2026/09/07/interrogating-the-gpu-part-2-the-roofline/">Part 2 of the GPU series</a>. A dense model holding the same weights would be at 256 at that batch, because it reads its weights once and every token uses all of them.</p>

<p>As $B$ grows the ratio settles at exactly $N/k$, which for V3 is 32. A sparse layer needs thirty-two times the concurrent batch of a dense one to sit at the same point on the roofline.</p>

<h2 id="what-does-that-do-to-how-you-serve-it">What does that do to how you serve it?</h2>

<p>It splits the machine in two, which is roughly what every 2026 inference stack converged on.</p>

<p>Prefill is compute-bound. The matrix multiplies are already large, so the all-to-all is pure overhead and narrow expert parallelism wins.</p>

<p>Decode is bandwidth-bound. Spreading the experts over more devices shrinks the weights each one has to read and frees the HBM that a larger batch needs, so wide expert parallelism wins.</p>

<p>DeepSeek published their own serving configuration and it is exactly this split: expert parallelism 32 across four nodes for prefilling, and expert parallelism 144 across eighteen nodes for decoding, with two routed experts and one shared expert per GPU. Their stated reason is the arithmetic above, in their words: only 8 of 256 experts are activated per layer, so the system needs an extremely large overall batch to give each expert a batch size worth having.</p>

<p>The two phases want opposite settings of the same knob. That is the systems argument for running them on separate machines, and it is the answer to “how would you deploy this” that shows you have thought past the model card.</p>

<p>The other levers are all about the skew from Part 3, which does not go away at inference. The same DeepSeek writeup runs 32 redundant routed experts in both phases and an expert-parallel load balancer whose objective is to minimise the maximum dispatch load across GPUs, because the all-to-all is synchronous and the slowest rank sets the step time for everyone.</p>

<h2 id="why-is-temperature-zero-not-determinism">Why is temperature zero not determinism?</h2>

<p>Because on a capacity-limited serving stack your token’s routing depends on the other tokens in the batch.</p>

<p>If expert capacity is enforced, an expert that fills up drops whatever arrives next, and what arrives next depends on which other requests happened to be batched with yours. Two identical prompts at temperature zero can take different paths through the model because someone else’s traffic changed at the same moment.</p>

<p>The dropless kernels remove that particular version of the problem, but a subtler one remains: routing is a top-$k$ over floating-point scores, so any numerical difference at all, a different kernel, a different batch shape, a different accumulation order, can flip which expert wins. Discrete selection turns a rounding difference into a completely different computation.</p>

<p>That is a good thing to raise unprompted in a systems interview, because it explains a class of production bug that looks impossible: the model gives different answers, the seed is fixed, and nothing in the request changed.</p>

<h2 id="what-is-the-memory-floor-really">What is the memory floor, really?</h2>

<p>Total parameters, always. There is no way around it, and this is the most common misconception about MoE in the whole subject.</p>

<p>The reason is timing. The router decides mid-forward-pass, per token, which experts are needed. There is no window in which to fetch a weight from host memory or from disk, so every expert has to be resident in HBM somewhere before the layer starts.</p>

<p>Offloading the cold experts to the host is a real technique and it works exactly where you would expect: at batch one, where a token touches eight of 256 experts, most of the bank genuinely is idle. As soon as you batch, the coverage formula above says you touch nearly all of them, and offloading turns into streaming the whole model over PCIe every step.</p>

<p>So the honest sentence is: an MoE saves FLOPs, not bytes. It is a way to buy capacity with memory instead of with arithmetic, and memory is the resource you have to be able to afford.</p>

<h2 id="rapid-fire-can-you-do-these-from-memory">Rapid fire: can you do these from memory?</h2>

<div class="rapid-fire">
<ol>
<li>Why does expert parallelism need an all-to-all where tensor parallelism needs an all-reduce?</li>
<li>Compute the dispatch and combine bytes per token per layer, given $d$, $k$ and the precisions.</li>
<li>What does node-limited routing bound, and what does it cost?</li>
<li>Given tokens per GPU, expert parallel width and an interconnect, compare the traffic time against the expert arithmetic time.</li>
<li>Derive the expected number of distinct experts touched by a batch of $B$ decoding tokens.</li>
<li>Why does an MoE layer's arithmetic intensity settle at $N/k$ times worse than a dense layer's?</li>
<li>Why do prefill and decode want opposite expert-parallel widths?</li>
<li>Give two reasons a Mixture of Experts is not deterministic at temperature zero.</li>
<li>Does an MoE let you serve a large model on less memory? Answer precisely.</li>
</ol>
</div>

<p>Part 5 leaves the mechanism behind and asks what is actually unsettled: which models are sparse in September 2026, what changed in the last year, and the list of questions where a confident answer is a bad sign.</p>]]></content><author><name>Vaibhav Singh</name><email>vaibhav.singh@mila.quebec</email></author><category term="mixture-of-experts" /><category term="systems" /><category term="inference" /><category term="distributed-training" /><category term="interview-prep" /><category term="deep-learning" /><summary type="html"><![CDATA[]]></summary></entry><entry><title type="html">Interrogating Mixture of Experts, Part 5: What Is Unsolved</title><link href="https://vaibhav016.github.io/blog/2026/09/09/interrogating-moe-part-5-what-is-unsolved/" rel="alternate" type="text/html" title="Interrogating Mixture of Experts, Part 5: What Is Unsolved" /><published>2026-09-09T00:00:00+00:00</published><updated>2026-09-09T00:00:00+00:00</updated><id>https://vaibhav016.github.io/blog/2026/09/09/interrogating-moe-part-5-what-is-unsolved</id><content type="html" xml:base="https://vaibhav016.github.io/blog/2026/09/09/interrogating-moe-part-5-what-is-unsolved/"><![CDATA[<h2 id="what-is-unsolved-about-mixture-of-experts-in-2026">What is unsolved about Mixture of Experts in 2026?</h2>

<p>The first four parts described a mechanism the field has largely converged on. A bank of narrow experts, sigmoid affinities, top-8, one shared expert or none, balanced by a bias rather than a loss, sharded by expert parallelism, dropless.</p>

<p>The interesting thing about September 2026 is that the convergence has started to come apart, and the lab that broke it first is the one that set most of it. DeepSeek-V4, published in April, changed the gate, removed the routing constraint that V3 needed, replaced the dense layers at the bottom of the stack with hash-routed experts, and moved the expert weights to four bits.</p>

<p>This part is the state of play, dated, with the questions where a confident answer is a bad sign. I have tried to keep the distinction visible throughout between what a lab measured, what a lab asserted, and what nobody has settled.</p>

<div class="interview-q">
  <span class="interview-q-label"><i class="fa-solid fa-circle-question"></i> They will ask</span>
  <span class="interview-q-text">What is the current state of the art in Mixture of Experts, and what would you be worried about if you had to build one?</span>
  <details>
    <summary>The 30-second version</summary>
    <span class="interview-q-answer">The 2025 recipe was fine-grained experts, sigmoid affinities, top-8, auxiliary-loss-free balancing with a per-expert bias, dropless routing, and expert parallelism with a fused all-to-all. Sparsity ratios went from about 4 with Mixtral to about 30 with Kimi K2 and DeepSeek-V4, while active parameters stayed between roughly 13 and 50 billion, so the growth all went into total parameters. What would worry me is four things. Hyperparameters do not transfer cleanly across sparsity: the September 2026 work on 1,800 pretraining runs finds optimal learning rate and batch size shift with the activation ratio in a way neither parameter count explains. Post-training is unstable: routing is a discrete function of numerics, so under reinforcement learning about 10% of activated experts flip after each gradient update on a 30B model, which breaks importance sampling. Expert specialisation is contested, and appears to depend on whether the model was upcycled. And the balancing mechanisms both have regimes where they misbehave, which is why DeepSeek ship a bias controller and a small auxiliary loss at the same time.</span>
  </details>
</div>

<p>The figure is the landscape: eleven open-weight models with published numbers, the sparsity trend, what routing alternatives exist, and the upcycling question.</p>

<div class="visual-embed">
  <iframe src="/images/blog/moe-visuals/m5-frontier.html" height="760" loading="lazy"></iframe>
</div>

<script>
(function () {
  window.addEventListener('message', function (e) {
    var d = e.data;
    if (!d || d.type !== 'visualEmbedHeight' || !d.height) return;
    var frames = document.querySelectorAll('.visual-embed iframe');
    for (var i = 0; i < frames.length; i++) {
      if (frames[i].contentWindow === e.source) { frames[i].style.height = d.height + 'px'; }
    }
  });
})();
</script>

<h2 id="which-models-are-sparse-and-how-sparse">Which models are sparse, and how sparse?</h2>

<p>Every open-weight frontier release since 2024 is a Mixture of Experts, and their configs are public, so this is one of the few places in the subject where you can be exact.</p>

<p>Mixtral 8x7B, in December 2023, stored 47 billion parameters and used 13. DeepSeek-V3, a year later, stored 671 and used 37. Meta’s Llama 4 Maverick stores 400 and uses 17, with 128 experts and an unusual routing scheme: each token goes to one routed expert plus a shared one, in layers that alternate with dense ones.</p>

<p>Qwen3-235B-A22B has 128 experts, top-8, no shared expert, across 94 layers. Kimi K2’s config lists 384 routed experts plus one shared. GLM-4.5 lists 160 plus one. OpenAI’s gpt-oss-120b has 128 experts with top-4 routing and the expert weights quantised to MXFP4.</p>

<p>DeepSeek-V4, in April 2026, comes in two sizes: V4-Pro at 1.6 trillion total and 49 billion active, and V4-Flash at 284 billion and 13.</p>

<p>Two trends fall out of that list. The sparsity ratio went from 3.6 to more than 30 in under three years. And the active-parameter axis barely moved: the frontier settled somewhere between 13 and 50 billion active parameters and spent all its growth on the other axis.</p>

<p>That second observation is the one worth being able to state, because it says what the binding constraint actually is. Compute per token is set by the inference budget and by what a serving fleet can afford. Total parameters are set by how much HBM you can put behind it. Sparsity is the only knob that moves capacity without moving the first number.</p>

<h2 id="what-changed-in-deepseek-v4">What changed in DeepSeek-V4?</h2>

<p>More than the parameter count, and the specific changes are the best available snapshot of where the recipe is going.</p>

<p>The affinity function moved. V3 computed expert scores with a sigmoid; V4 changed it to the square root of a softplus. That is a small edit to one line and it is the first time a frontier lab has publicly moved off the gate everyone had copied.</p>

<p>Node-limited routing is gone. V3 capped each token at four destination nodes to bound the network fan-out, which was the lever Part 4 spent a section on.</p>

<p>V4 removes the constraint, and the reason is in their own systems section: with the wave-based overlap they describe, the communication within a single MoE layer takes less time than the computation, so once it is fused into one pipeline the compute is the bottleneck and the system tolerates lower interconnect bandwidth. Their threshold is that each gigabyte per second of interconnect suffices to hide 6.1 teraflop per second of compute, and past it more bandwidth buys nothing.</p>

<p>The dense layers at the bottom are gone too. V3 kept plain feed-forward networks in its first three blocks. V4 replaces them with MoE layers using hash routing, where a predefined hash of the token id decides the experts. No router, no gradient, no balancing problem, in exactly the layers where the earlier literature found routing looks most like a partition of the vocabulary anyway.</p>

<p>And the routed expert parameters are stored in FP4. Balancing is still auxiliary-loss-free with a small sequence-wise loss alongside, which is the one part of the V3 recipe that survived untouched.</p>

<div class="key-idea"><span class="key-idea-label"><i class="fa-solid fa-lightbulb"></i> key idea</span>
<span class="key-idea-text">The constraints an architecture is designed around are the ones the systems layer has not yet solved. DeepSeek-V3 capped routing at four nodes because the all-to-all was exposed; V4 removed the cap because a fused kernel hid it. Read every architectural restriction as a statement about the hardware of its year.</span></div>

<h2 id="has-routing-moved-past-top-k">Has routing moved past top-k?</h2>

<p>Not in what ships, and that is itself the interesting answer.</p>

<p>The alternatives are all well known. Expert choice inverts the argmax: instead of every token picking $k$ experts, every expert picks its best $c$ tokens. Load is then balanced by construction, with no auxiliary loss and no bias controller at all. The catch is that an expert’s choice depends on the other tokens in the batch, including later ones, which in a causal decoder leaks information backwards in time, and that a token can end up chosen by nobody.</p>

<p>AI2 ran the comparison directly for OLMoE and reported that dropless token choice beat expert choice on every task at the same token budget, while expert choice ran about 20% faster per device. That is the trade in one line: expert choice buys throughput and balance and pays in quality and in causality.</p>

<p>Hash routing goes the other way and does not learn at all. The Hash Layers work in 2021 reported that routing tokens by a fixed hash was competitive with learned routing, which is an uncomfortable result if you believe the router is doing something clever. It sat as a curiosity for five years and then reappeared in DeepSeek-V4’s first blocks.</p>

<p>So the honest summary is that top-$k$ token choice is still the default, that nothing has beaten it convincingly at scale, and that the two most credible alternatives are one that cannot be used in a decoder and one that does not learn.</p>

<h2 id="should-you-upcycle-a-dense-checkpoint">Should you upcycle a dense checkpoint?</h2>

<p>You already have a good dense model. Copy its feed-forward layer $N$ times, perturb the copies, bolt on a router, and keep training. That is sparse upcycling, and it is the cheapest way to get an MoE.</p>

<p>Google’s paper on it put the crossover at roughly 120% of the original dense training budget: below that, upcycle, above it, start over.</p>

<p>AI2 tried it for OLMoE and got a different number. They upcycled their OLMo-1B checkpoint after 2 trillion tokens and trained for 610 billion more. An otherwise equivalent MoE trained from scratch caught up after 500 billion extra tokens and started beating it around 600 billion. That is 25% of the original budget, not 120%, and they rejected upcycling for their final model.</p>

<p>Both are right about their own setting, and the useful thing is the reason they differ. Beyond the compute bracket, AI2’s stated objection was that an upcycled model inherits hyperparameters tuned for the dense model it came from, and the previous section says those do not transfer. There is a second reason, and it connects to the specialisation question below: experts that all start from the same weights start correlated, and it takes training to pull them apart.</p>

<p>The practical reading is that upcycling is a way to spend a small budget well, not a way to reach a frontier model.</p>

<h2 id="do-the-hyperparameters-transfer">Do the hyperparameters transfer?</h2>

<p>This is the newest result on the list, dated 8 September 2026, and it is the one I would raise unprompted.</p>

<p>Muon, muP and the whole hyperparameter-transfer literature rest on the idea that you can tune a small proxy and scale the settings up along a known law. The paper “Hyperparameter Scaling Laws Across MoE Sparsity” tests that across sparsity levels with 1,800 pretraining runs, six activated-parameter scales, models up to 6 billion total non-embedding parameters, about 20 trillion tokens, and roughly 200,000 H800-hour equivalents.</p>

<p>The finding is that conventional hyperparameter scaling laws are insufficient for ultra-sparse models. Optimal learning rate and optimal batch size both shift with the activation ratio, and those shifts cannot be explained by either total or activated parameter count alone. Their resolution is that at fixed sparsity the optimal batch size is a power law in the token count and the optimal learning rate a power law in compute, and that across sparsity levels the activation ratio enters both as an additional multiplicative factor.</p>

<p>The interview version of this: if you tune on a dense proxy and transfer to a model activating a sixty-fourth of its parameters, you should expect the transfer to be wrong, and neither of the two parameter counts you might reach for will tell you by how much.</p>

<h2 id="do-experts-specialise">Do experts specialise?</h2>

<p>The popular claim is that a Mixture of Experts learns a maths expert, a code expert, a biology expert. The evidence does not support that, and being able to say why is a genuinely differentiating answer.</p>

<p>Mistral looked for it in Mixtral and did not find it. Their own routing analysis says the assignment distribution is very similar across arXiv papers, PubMed abstracts and philosophy papers at every layer. What they did find is structure of a different kind: Python’s <code class="language-plaintext highlighter-rouge">self</code> and indentation tokens route consistently, and consecutive tokens pick the same first-choice expert about 28% of the time at layer 15 against a 12.5% random baseline. That is syntax and temporal locality, not subject matter.</p>

<p>ST-MoE found clear specialisation in their encoder experts, on punctuation and verbs and proper nouns, and explicitly did not find it in the decoder. They also looked for language specialisation in a multilingual model and reported that experts handled English, Japanese, French and Chinese indiscriminately.</p>

<p>OLMoE, trained from scratch rather than upcycled, does report domain and vocabulary specialisation, with one layer-0 expert nearly 100% specialised to arXiv, and their hypothesis for the disagreement is that Mixtral was upcycled from Mistral, so its experts all started from the same optimum and had less room to diverge.</p>

<p>There is a mechanism underneath that makes the whole thing less mysterious. The balancing scope from Part 3 acts directly against specialisation: if you compute the dispatch fractions over a micro-batch of a few sequences, you are demanding that a batch of pure code spread itself over the whole bank.</p>

<p>Computing them over the global batch instead improves both perplexity and measured domain specialisation, at scales up to 42.8 billion parameters. Some of what looks like a fact about Mixture of Experts is a fact about a reduction in a framework.</p>

<p>So: token class and vocabulary, yes. Subject matter, contested and initialisation-dependent. Language, no.</p>

<h2 id="what-breaks-when-you-reinforcement-learn-a-sparse-model">What breaks when you reinforcement-learn a sparse model?</h2>

<p>The routing, and it is the sharpest modern failure mode.</p>

<p>Top-$k$ is a discrete function of continuous scores, so any numerical difference at all can flip which expert wins. In reinforcement learning that matters more than anywhere else, because the algorithms depend on comparing the probability a token had under the old policy against its probability under the new one, and if the two policies ran different experts you are comparing two different networks.</p>

<p>The Qwen team put a number on it while introducing GSPO. On the 48-layer Qwen3-30B-A3B base model, after each gradient update and for the same rollout sample, roughly 10% of the activated experts differ between the new policy and the old one, and the effect becomes more prominent in deeper models.</p>

<p>Their workaround was routing replay: cache the experts the old policy activated and replay those routing decisions when computing the importance ratios. They call it essential for normal convergence of GRPO on MoE models, and they note it costs memory, communication, and some of the model’s effective capacity.</p>

<p>GSPO’s own contribution is to sidestep it by working at the sequence level rather than the token level, so it is not sensitive to individual token likelihoods and does not need the replay at all.</p>

<p>If someone tells you their reinforcement learning run diverges on a sparse model and is fine on a dense one of the same size, this is the first thing to check, and it is not a bug in their code.</p>

<h2 id="does-sparsity-change-what-else-you-have-to-change">Does sparsity change what else you have to change?</h2>

<p>Yes, and the combinations are where the 2026 work is.</p>

<p>The obvious pairing is sparse attention. Parts 1 and 4 established that sparsity lives entirely in the feed-forward half of the block: all of DeepSeek-V3’s attention runs on every token, and the KV cache is untouched by any of it.</p>

<p>So a model that is sparse in the feed-forward layer and dense in attention has moved its bottleneck rather than removed it. That is why DeepSeek pairs MoE with multi-head latent attention to shrink the cache and, from V3.2 onward, with a sparse attention mechanism to cut the score computation.</p>

<p>Their natively trainable sparse attention work in February 2025 laid the groundwork. V4’s hybrid attention combines a compressed sparse mechanism with a heavily compressed one, and they report V4-Pro needing 27% of V3.2’s single-token inference FLOPs and 10% of its KV cache at million-token context.</p>

<p>The other pairing is quantisation, and it interacts with sparsity in a way dense models do not have to think about. Experts see wildly different amounts of data. A hot expert is calibrated on a diverse stream; a cold one is under-represented in whatever calibration set you use, and a shared expert sees everything.</p>

<p>Quantising them all identically is the obvious thing and the wrong one. gpt-oss quantises the MoE weights to MXFP4 and leaves attention, the router and the embeddings alone, which is the shape of the answer: the router is tiny and precision-sensitive, the experts are enormous and are where the bits are.</p>

<h2 id="what-would-i-actually-say-if-asked-what-comes-next">What would I actually say if asked what comes next?</h2>

<p>One prediction and one refusal.</p>

<p>The prediction: the active-parameter axis stays roughly where it is and the total-parameter axis keeps climbing. The binding constraints are inference FLOPs and HBM bandwidth, and sparsity is the only knob that moves capacity without moving either. Nothing in the public sweeps has found a loss-based ceiling on how sparse a model can be; the ceilings that have actually been hit are systems ceilings and numerical ones.</p>

<p>The refusal: I would not predict what the router looks like. In eighteen months the field has kept top-$k$ token choice, moved from softmax to sigmoid, moved from an auxiliary loss to a bias controller, moved from sigmoid to the square root of a softplus, and put hash routing back into production.</p>

<p>Every one of those changes was small and none was forced by a theory. The honest summary is that the router is the least understood component of the most successful architecture of the decade.</p>

<p>That is also the reason the questions in Part 6 exist. The mechanism is learnable in an afternoon. Knowing which of its parts are load-bearing and which are conventions nobody has re-examined is the thing that takes longer.</p>

<h2 id="rapid-fire-can-you-do-these-from-memory">Rapid fire: can you do these from memory?</h2>

<div class="rapid-fire">
<ol>
<li>Name four open-weight models from the last two years with their total and active parameter counts.</li>
<li>What did DeepSeek-V4 change from V3, and what does the removal of node-limited routing tell you about the systems layer?</li>
<li>Why can expert-choice routing not be used unmodified in a causal decoder?</li>
<li>State both published crossover points for upcycling versus training from scratch, and one reason they differ.</li>
<li>Why does hyperparameter transfer from a dense proxy break on an ultra-sparse model?</li>
<li>Summarise the evidence on expert specialisation at three different granularities.</li>
<li>Why does reinforcement learning destabilise a Mixture of Experts more than a dense model, and what is routing replay?</li>
<li>Why does a sparse feed-forward layer not help the KV cache, and what do the labs pair it with?</li>
</ol>
</div>

<p>Part 6 is the self-test: three levels of questions with answer sketches, built from everything above.</p>]]></content><author><name>Vaibhav Singh</name><email>vaibhav.singh@mila.quebec</email></author><category term="mixture-of-experts" /><category term="research" /><category term="scaling-laws" /><category term="llm" /><category term="interview-prep" /><category term="deep-learning" /><summary type="html"><![CDATA[]]></summary></entry><entry><title type="html">Interrogating Mixture of Experts, Part 6: The Question Set</title><link href="https://vaibhav016.github.io/blog/2026/09/09/interrogating-moe-part-6-the-question-set/" rel="alternate" type="text/html" title="Interrogating Mixture of Experts, Part 6: The Question Set" /><published>2026-09-09T00:00:00+00:00</published><updated>2026-09-09T00:00:00+00:00</updated><id>https://vaibhav016.github.io/blog/2026/09/09/interrogating-moe-part-6-the-question-set</id><content type="html" xml:base="https://vaibhav016.github.io/blog/2026/09/09/interrogating-moe-part-6-the-question-set/"><![CDATA[<h2 id="can-you-answer-these-without-looking-anything-up">Can you answer these without looking anything up?</h2>

<p>Five parts of prose is a good way to build an understanding and a bad way to test one. This part is the test.</p>

<p>Three levels. The first asks whether you can state the mechanism precisely: shapes, what is computed, what is stored, where the bytes and the FLOPs go. The second asks whether you can reason about the trade-offs, which is where most interviews actually live. The third is the frontier, where the useful answer is usually a question about what the interviewer means.</p>

<p>Every answer sketch is what a strong answer contains, not an essay. Nothing in them is a number this series has not already established, and if one of them surprises you, the part it came from is the one to reread.</p>

<div class="interview-q">
  <span class="interview-q-label"><i class="fa-solid fa-circle-question"></i> They will ask</span>
  <span class="interview-q-text">Tell me about Mixture of Experts.</span>
  <details>
    <summary>The 30-second version</summary>
    <span class="interview-q-answer">A prompt this open is asking you to choose the frame, so choose one and say why. The frame I would take: MoE is the one architectural change that separates parameter count from arithmetic per token, which lets you buy capacity with memory instead of with compute. Then the three consequences, in order: the router is a hard argmax so most of the bank receives no gradient from most tokens, which is why balancing is a mechanism rather than a loss term; the experts live on other devices so a layer becomes two all-to-alls with runtime-decided payloads; and at serving batch a batch touches nearly every expert per layer, so the arithmetic intensity is worse than a dense model's by the ratio of experts to active experts. Each of those has a number I can compute on the board if they want it.</span>
  </details>
</div>

<h2 id="level-1-can-you-state-the-mechanism">Level 1: can you state the mechanism?</h2>

<div class="qbank">
  <div class="qbank-head"><span class="qbank-level">level 1</span><span class="qbank-name">the mechanism</span></div>
  <ol class="qbank-list">
    <li>
      <p class="qbank-q">Mixtral 8x7B is eight seven-billion-parameter experts, and it has 47 billion parameters rather than 56. Where did the other nine billion go?</p>
      <details class="qbank-a"><summary>answer sketch</summary>
      <p>Nowhere: they were never there. Only the feed-forward sublayer is replicated across the eight experts. Attention, the norms, the embeddings and the output head exist once and are shared by every token regardless of routing, so counting eight copies of the whole 7B model double-counts everything outside the feed-forward layer. The name describes how the experts were sized, not how the parameters add up, which is exactly why the useful numbers are total and active rather than the name.</p>
      </details>
    </li>
    <li>
      <p class="qbank-q">A token arrives at an MoE layer as a vector of length $d$. Name every tensor computed on the way to the layer's output, with its shape.</p>
      <details class="qbank-a"><summary>answer sketch</summary>
      <p>Router scores, $N$ values, from one $d \times N$ matrix-vector product. A top-$k$ selection giving $k$ indices, which is not a tensor with a gradient. The $k$ gate values, scalars, from renormalising the chosen scores, possibly times a fixed scaling factor. Then $k$ expert outputs, each a $d$-vector, each produced by a full SwiGLU through a $d \times d_{ff}$ triple. Then a weighted sum of those $k$ vectors, plus the shared expert's $d$-vector if the model has one. The output is a $d$-vector, the same shape it arrived as.</p>
      </details>
    </li>
    <li>
      <p class="qbank-q">During the backward pass of an MoE layer, which quantities receive gradient and which receive none?</p>
      <details class="qbank-a"><summary>answer sketch</summary>
      <p>The $k$ chosen experts receive gradient through their outputs, scaled by their gate values. The router's weights receive gradient only through those $k$ gate values, because the top-$k$ selection is a hard argmax with no derivative. The experts that were not chosen receive nothing at all, and the router receives no information about whether one of them would have been better. That last sentence is the whole reason a Mixture of Experts needs a balancing mechanism that lives outside the loss.</p>
      </details>
    </li>
    <li>
      <p class="qbank-q">Given 61 layers of which the first 3 are dense, $d = 7168$, experts of width 2048, 256 routed plus 1 shared, top-8, and a 129k vocabulary with untied embeddings: compute total and active parameters.</p>
      <details class="qbank-a"><summary>answer sketch</summary>
      <p>One SwiGLU expert is $3 \times 7168 \times 2048$, about 44 million. A MoE layer holds 257 of those plus a $7168 \times 256$ router, so about 11.3 billion, of which 9 experts plus the router, about 398 million, are active. Fifty-eight MoE layers give 657 billion total and 23 billion active. Add the three dense feed-forward layers, the 61 attention blocks and the embeddings and you land on DeepSeek-V3's published 671 billion and roughly 37 billion. The point of doing it by hand once is that both columns come out of the same set of shapes.</p>
      </details>
    </li>
    <li>
      <p class="qbank-q">Does top-8 routing mean eight forward passes through the layer? Describe what a real kernel does instead.</p>
      <details class="qbank-a"><summary>answer sketch</summary>
      <p>No. You compute the routing for the whole batch, sort the token rows by expert index into one contiguous buffer, run a single grouped matrix multiply whose group boundaries are the expert offsets, then scatter the results back to token order and weight them by the gates. The two permutations do zero arithmetic and move gigabytes, so they sit at the far left of the roofline and run at memory bandwidth. That is where a naive implementation loses its throughput, and it is why block-sparse and grouped-GEMM kernels exist.</p>
      </details>
    </li>
    <li>
      <p class="qbank-q">Define expert capacity and the capacity factor, and say precisely what happens to a token that overflows.</p>
      <details class="qbank-a"><summary>answer sketch</summary>
      <p>Capacity is tokens per batch divided by the number of experts, times a capacity factor, and it is fixed before the routing is known so that the experts can run as batched matrix multiplies. A token arriving at a full expert is dropped: its computation is skipped and it passes to the next layer through the residual connection. It is not an error and nothing raises. Worth adding that the modern answer is to avoid the situation entirely, with block-sparse kernels that handle variable group sizes and remove the capacity factor as a hyperparameter.</p>
      </details>
    </li>
    <li>
      <p class="qbank-q">Write the auxiliary load-balancing loss, define both of its vectors, and give its value at perfect balance and at total collapse.</p>
      <details class="qbank-a"><summary>answer sketch</summary>
      <p>$L = \alpha N \sum_i f_i P_i$, where $f_i$ is the fraction of the batch's tokens dispatched to expert $i$ and $P_i$ is the mean router probability that expert received. Both sum to one over the experts, so the dot product is minimised when both are flat, and the factor of $N$ makes that minimum exactly 1. The maximum is $N$, when one expert takes everything. Switch Transformer used $\alpha = 0.01$ after sweeping five orders of magnitude around it.</p>
      </details>
    </li>
    <li>
      <p class="qbank-q">Where does DeepSeek's per-expert bias enter the computation, and where does it deliberately not?</p>
      <details class="qbank-a"><summary>answer sketch</summary>
      <p>It is added to the affinity score used for the top-$k$ comparison and nowhere else. The gate value that weights an expert's output is computed from the original affinity, without the bias. That separation is the whole design: the bias changes which experts are chosen without changing how much they count, so no term is added to the loss and no gradient is distorted. It is updated after each step by a small constant, down for over-subscribed experts and up for under-subscribed ones.</p>
      </details>
    </li>
  </ol>
</div>

<h2 id="level-2-can-you-reason-about-the-trade-offs">Level 2: can you reason about the trade-offs?</h2>

<div class="qbank">
  <div class="qbank-head"><span class="qbank-level">level 2</span><span class="qbank-name">the trade-offs</span></div>
  <ol class="qbank-list">
    <li>
      <p class="qbank-q">At fixed active parameters, would you rather have 8 experts of width $W$ or 64 of width $W/8$? What changes and what does not?</p>
      <details class="qbank-a"><summary>answer sketch</summary>
      <p>Active parameters and FLOPs per token are identical by construction; that is the premise. What changes is the number of distinct expert teams a token can assemble, which goes up combinatorially, and that is the argument the fine-grained models were built on. What it costs is not arithmetic: a router matrix eight times larger, a longer sort in the kernel, and eight times as many network destinations per token in the all-to-all. So the answer is that it is nearly free in FLOPs and expensive in systems work, which is why it took a systems paper to make it practical.</p>
      </details>
    </li>
    <li>
      <p class="qbank-q">Does a Mixture of Experts reduce the memory needed to serve a model of a given quality?</p>
      <details class="qbank-a"><summary>answer sketch</summary>
      <p>No, and this is the most common misconception in the subject. Every expert must be resident in HBM because routing is decided per token in the middle of a forward pass and there is no window in which to fetch a weight from anywhere slower. Memory tracks total parameters; only speed tracks active ones. Offloading cold experts works at batch one, where a token really does touch a small fraction of the bank, and collapses under batching for the reason in the next question.</p>
      </details>
    </li>
    <li>
      <p class="qbank-q">A 256-expert top-8 model decodes 128 tokens together. How many experts does one layer read, and what does that do to arithmetic intensity?</p>
      <details class="qbank-a"><summary>answer sketch</summary>
      <p>Each token picks independently, so the expected number of distinct experts is $N(1 - (1-k/N)^B)$, which at $B = 128$ is about 252 of 256. The bytes read scale with $N$ while the FLOPs still scale with $k$, so intensity is worse than a dense model's holding the same weights by a factor that settles at $N/k$, which here is 32. Concretely the layer sits around 9 FLOP per byte against an H100 ridge of 295, so it is memory-bound by a wide margin and needs roughly thirty-two times the batch of a dense model to reach the same point.</p>
      </details>
    </li>
    <li>
      <p class="qbank-q">Your training run is dropping 15% of tokens. A colleague proposes doubling the capacity factor. Why is that the wrong first move?</p>
      <details class="qbank-a"><summary>answer sketch</summary>
      <p>Because dropping is a symptom of skew, and the capacity factor only decides how much skew you buffer. Doubling it makes the buffers half empty, so the grouped matrix multiply becomes mostly padding, and it hides the underlying problem rather than fixing it. The first move is to look at the per-layer load histogram and the maximum violation to find out how skewed the routing actually is, and then to fix the balancing: check that the auxiliary loss or bias controller is on, that it is applied per layer, and what batch the dispatch fractions are being reduced over.</p>
      </details>
    </li>
    <li>
      <p class="qbank-q">At step 40,000 your MoE's loss curve looks healthy. What could already be badly wrong, and how would you know?</p>
      <details class="qbank-a"><summary>answer sketch</summary>
      <p>Router collapse, which is invisible in the loss for a long time because a bank in which a few experts do all the work is still a functioning model, just a much smaller one than you are paying for. The instruments are the per-layer load histogram and the count of experts receiving zero tokens, both logged every step. The shape to watch for is that the imbalance sits flat for a long stretch and then accelerates, so a healthy-looking recent history is not reassurance. Collapse also usually starts in one layer, so a model-wide average will hide it.</p>
      </details>
    </li>
    <li>
      <p class="qbank-q">You profile a training step and the all-to-all is 2.5 times the expert compute. List the levers, ordered by how much they buy.</p>
      <details class="qbank-a"><summary>answer sketch</summary>
      <p>Overlap first, because it is the only lever that changes the sum into a maximum: a pipeline schedule that puts another micro-batch's arithmetic under this one's collective, and kernels that occupy no streaming multiprocessors so the communication stops competing with the compute for them. Then shrink the payload: dispatch in FP8 and combine in BF16 halves the outward leg. Then bound the fan-out with node-limited routing, so a token crosses the network once per destination node rather than once per expert. Raising the batch does not help, because the traffic and the arithmetic both scale with tokens.</p>
      </details>
    </li>
    <li>
      <p class="qbank-q">Why does top-1 routing with a renormalised gate leave the router with no gradient from the output, and what are the two ways out?</p>
      <details class="qbank-a"><summary>answer sketch</summary>
      <p>Renormalising over the chosen set makes the single gate identically 1, a constant, so its derivative with respect to the router's weights is zero and the router learns nothing from the task loss. It would be driven entirely by the balancing term. One way out is to keep the raw gate value as the multiplier rather than renormalising, which is what Switch Transformer did to make top-1 work. The other is to use $k \ge 2$, which is what the original formulation argued for and what essentially everything at scale now does.</p>
      </details>
    </li>
    <li>
      <p class="qbank-q">A colleague computes the balance loss per micro-batch instead of over the global batch, to save an all-reduce. What does that change about the model, not just the speed?</p>
      <details class="qbank-a"><summary>answer sketch</summary>
      <p>The formula is identical, which is why the difference is easy to miss, but the reduction scope changes what is being demanded. A micro-batch at frontier scale is a handful of sequences, so a per-micro-batch term effectively insists that every individual sequence spread itself evenly across all the experts, which forces a batch of pure code to use the whole bank and actively works against specialisation. Reducing over the global batch instead costs one all-reduce of an $N$-element vector, and published work at tens of billions of parameters reports better perplexity and more domain specialisation for it.</p>
      </details>
    </li>
  </ol>
</div>

<h2 id="level-3-can-you-defend-a-design-decision">Level 3: can you defend a design decision?</h2>

<div class="qbank">
  <div class="qbank-head"><span class="qbank-level">level 3</span><span class="qbank-name">the frontier</span></div>
  <ol class="qbank-list">
    <li>
      <p class="qbank-q">Argue against Mixture of Experts. You have a fixed compute budget per token and you may build either a sparse model or a dense one with the same active parameters.</p>
      <details class="qbank-a"><summary>answer sketch</summary>
      <p>The dense model fits in a fraction of the memory, so it serves on fewer devices and its economics are simpler. It has no all-to-all, so its layers have static shapes, it captures cleanly into a CUDA graph, it is deterministic, and a slow rank does not stall a synchronous collective. It has no routing to collapse, no balancing mechanism to tune and no expert placement to rebalance in production. And at equal pretraining loss there is evidence that sparse models transfer worse on reasoning-heavy tasks, plausibly because reasoning wants inference compute the sparse model does not spend. If the deployment is memory-constrained rather than compute-constrained, the dense model is simply the right answer.</p>
      </details>
    </li>
    <li>
      <p class="qbank-q">Shared expert or no shared expert? What would change your mind?</p>
      <details class="qbank-a"><summary>answer sketch</summary>
      <p>The argument for is that whatever every token needs does not have to be relearned redundantly in every routed expert, so isolating it frees the rest to differ, and three labs ship one. The argument against is combinatorial: making one expert always-on removes it from the choice set and, at small expert counts, deletes most of the combinations a token could assemble, which is what AI2 measured for OLMoE when the matched-compute ablation came out slightly against it. What would change my mind is granularity, because with hundreds of experts one always-on expert costs almost nothing in combinations, which may be why the large-bank models keep them and Qwen3 dropped theirs. It is a small effect and the labs genuinely disagree.</p>
      </details>
    </li>
    <li>
      <p class="qbank-q">Auxiliary loss or the per-expert bias controller? What does the evidence actually say?</p>
      <details class="qbank-a"><summary>answer sketch</summary>
      <p>DeepSeek's own numbers show the bias controller giving both better balance and slightly better perplexity, and the argument is that the auxiliary loss adds a gradient that is not trying to make the model better at the task. But the honest version has three caveats. The perplexity gap is small. DeepSeek ship both, with a small sequence-wise auxiliary loss alongside the controller and the controller's update rate set to zero for the last part of training. And the paper was rejected at ICLR 2025, with the authors conceding in the public reviews that the interference-gradient motivation was intuition rather than a demonstrated effect. So: probably the better default, on evidence weaker than its adoption suggests.</p>
      </details>
    </li>
    <li>
      <p class="qbank-q">A product manager says your MoE should have a medical expert and a legal expert. What do you tell them?</p>
      <details class="qbank-a"><summary>answer sketch</summary>
      <p>That the name is misleading and the published evidence does not support subject-matter specialisation emerging on its own. Mistral looked for it in Mixtral and found the routing distribution nearly identical for arXiv, PubMed and philosophy; what they found instead was syntax and positional locality. ST-MoE found token-class specialisation in an encoder and essentially none in a decoder, and no language specialisation at all. OLMoE, trained from scratch, does report domain specialisation, and hypothesises that upcycled models show less of it because their experts start identical. And the balancing scope matters: reducing the load term over a small micro-batch actively suppresses whatever specialisation would have emerged. If they want a medical expert, that is a routing constraint you would have to impose, not one you can expect.</p>
      </details>
    </li>
    <li>
      <p class="qbank-q">You have a strong dense checkpoint and budget for more training. Upcycle it into an MoE, or start over?</p>
      <details class="qbank-a"><summary>answer sketch</summary>
      <p>It depends on the size of the remaining budget relative to the dense run, and the two published crossovers are far apart: Google's sparse upcycling paper put it near 120% of the original budget, AI2 measured 25% for OLMoE and rejected upcycling on that basis. Beyond compute there are two structural costs to name. The upcycled model inherits hyperparameters tuned for the dense model, and those do not transfer cleanly across sparsity. And the experts all start from the same weights, so they start correlated, which is the mechanism OLMoE proposes for why upcycled models specialise less. My default: upcycle for a small budget, start over if you are going to train for a long time anyway.</p>
      </details>
    </li>
    <li>
      <p class="qbank-q">Reinforcement learning on your MoE diverges. The dense model of the same active size is fine. Diagnose it.</p>
      <details class="qbank-a"><summary>answer sketch</summary>
      <p>Start with the routing. Top-$k$ is discrete, so any numerical difference between the rollout engine and the training engine, or between the old and new policy, flips which experts run, and the importance ratio then compares two genuinely different networks. Qwen measured roughly 10% of activated experts changing after each gradient update on a 30B model, worse in deeper ones. The two known fixes are routing replay, caching the rollout's expert choices and replaying them when computing the ratios, which costs memory and some effective capacity; and moving the objective to the sequence level so it is not sensitive to individual token likelihoods, which is what GSPO does. I would confirm the diagnosis first by logging the fraction of tokens whose routing differs between the two engines.</p>
      </details>
    </li>
    <li>
      <p class="qbank-q">DeepSeek-V4 removed the node-limited routing that V3 needed. What has to be true for that to be a good idea, and would it be a good idea on your cluster?</p>
      <details class="qbank-a"><summary>answer sketch</summary>
      <p>It is a good idea exactly when the all-to-all is genuinely hidden under compute, because the cap only ever existed to bound exposed communication and it costs routing freedom. DeepSeek's own argument is that with a fused wave-based overlap the communication in a MoE layer takes less time than the computation, so the compute is the bottleneck and the system tolerates lower bandwidth; they give the threshold as a compute-per-bandwidth ratio. On a different cluster the answer flips as soon as your interconnect falls below that ratio or your kernels do not overlap as well, and the general lesson is worth stating: an architectural constraint is usually a statement about the systems layer of its year, not about the model.</p>
      </details>
    </li>
    <li>
      <p class="qbank-q">You must tune a model that activates one parameter in sixty-four, and you can afford only a dense proxy sweep. What do you do, and what do you tell your manager to expect?</p>
      <details class="qbank-a"><summary>answer sketch</summary>
      <p>Expect the transfer to be wrong, and say so up front. The September 2026 work on hyperparameter scaling across sparsity ran 1,800 pretraining runs and found that optimal learning rate and batch size both shift with the activation ratio in a way that neither the total nor the activated parameter count predicts. So a dense proxy gives you a starting point, not a setting. What I would actually do is sweep at the target sparsity on the smallest model I can afford, use the dense sweep only to bracket the range, budget for a short learning-rate re-search at scale, and watch the load histogram from step one, because a sparse model with a badly transferred learning rate does not fail loudly, it fails by collapsing the router.</p>
      </details>
    </li>
  </ol>
</div>

<h2 id="what-do-you-do-with-a-list-like-this">What do you do with a list like this?</h2>

<p>Not memorise the answers. The sketches are deliberately short because the point of each one is a single load-bearing idea, and if you have that idea you can build the sentences around it in the room.</p>

<p>The pattern across all three levels is the same. Level 1 is arithmetic you should be able to do on a whiteboard from the config alone. Level 2 is a question about which quantity is binding, and almost every one of them is answered by asking “at fixed what?” before answering. Level 3 has no settled answer, and the strong response names the axis it turns on and then commits to a default anyway.</p>

<p>That last habit is worth practising specifically. “It depends” is a bad answer on its own and a good one when it is followed by what it depends on and what you would do today.</p>

<p>Six parts ago the question was how a 671 billion parameter model trains for the price of a 37 billion parameter one. The answer turned out to be one edit to one sublayer, and five parts of consequences: a router with almost no gradient, a bank that collapses without supervision, a layer that spends more time on the network than on arithmetic, and a decode path that gives back most of its sparsity as soon as you batch.</p>

<p>None of that is hard. It is just longer than the one sentence the architecture is usually described in, and the gap between the sentence and the consequences is where the interview happens.</p>]]></content><author><name>Vaibhav Singh</name><email>vaibhav.singh@mila.quebec</email></author><category term="mixture-of-experts" /><category term="interview-questions" /><category term="llm" /><category term="architecture" /><category term="interview-prep" /><category term="deep-learning" /><summary type="html"><![CDATA[]]></summary></entry><entry><title type="html">Interrogating Parallelism, Part 1: Why One GPU Cannot Do It</title><link href="https://vaibhav016.github.io/blog/2026/09/09/interrogating-parallelism-part-1-why-one-gpu-cannot-do-it/" rel="alternate" type="text/html" title="Interrogating Parallelism, Part 1: Why One GPU Cannot Do It" /><published>2026-09-09T00:00:00+00:00</published><updated>2026-09-09T00:00:00+00:00</updated><id>https://vaibhav016.github.io/blog/2026/09/09/interrogating-parallelism-part-1-why-one-gpu-cannot-do-it</id><content type="html" xml:base="https://vaibhav016.github.io/blog/2026/09/09/interrogating-parallelism-part-1-why-one-gpu-cannot-do-it/"><![CDATA[<h2 id="why-can-one-gpu-not-train-this-model">Why can one GPU not train this model?</h2>

<p>The GPU series ended with an inventory: sixteen bytes per parameter, an activation formula, a KV cache, and four clocks that decide which of them is the bottleneck. That inventory is for one GPU. The next question an interviewer asks is the one that follows from it, and it is almost always phrased as a task rather than a question: “you have 512 H100s and a 70 billion parameter model. Lay it out.”</p>

<p>There are five ways to cut a model across GPUs, and they are not interchangeable. Each one divides a different line of that inventory, each one buys the division with a different collective, and each one runs out of road for a different reason. Getting the list right is easy. Knowing which line each one divides, and what it costs, is the part that separates a candidate who has read about this from one who has run it.</p>

<p>This series derives the five, one at a time, on one model and one cluster. Everything is arithmetic you can do on a whiteboard, and every number in every figure is computed from the same constants.</p>

<div class="interview-q">
  <span class="interview-q-label"><i class="fa-solid fa-circle-question"></i> They will ask</span>
  <span class="interview-q-text">Why does a large model need more than one GPU, and what are the ways to split it across them?</span>
  <details>
    <summary>The 30-second version</summary>
    <span class="interview-q-answer">Two budgets force it, independently. Memory: Adam in mixed precision costs sixteen bytes per parameter, so a 70B model needs 1.13 TB of state before a single activation, and one 8192-token sequence adds another 182 GB of saved activations. That is sixteen H100s' worth on a machine that has 80 GB. Time: six FLOPs per parameter per token over fifteen trillion tokens is 6.3 times ten to the twenty-fourth, which one H100 at a realistic 40% of peak would finish in five centuries. Five axes split it. Data parallelism splits the batch and divides the optimizer states, gradients and weights through ZeRO, paying with an all-reduce or a reduce-scatter and all-gather per step. Tensor parallelism splits the matmuls inside each block and divides the activations too, paying with four collectives per layer, which is why it stays inside a node. Pipeline parallelism splits the layers, paying in idle time rather than bytes. Context parallelism splits the sequence, for long-context training. Expert parallelism splits a mixture of experts, paying with two all-to-alls per layer. Real runs use three or four of them at once, ordered so that the busiest collective gets the fastest link.</span>
  </details>
</div>

<h2 id="what-does-one-gpu-actually-have-to-hold">What does one GPU actually have to hold?</h2>

<p>Take Llama 3 70B, whose shape Meta published: 70.5 billion parameters, 80 layers, hidden size 8192, an FFN width of 28,672, 64 query heads and 8 key-value heads of dimension 128.</p>

<p>Training one parameter with Adam in mixed precision costs sixteen bytes: a BF16 weight, a BF16 gradient, an FP32 master copy, and two FP32 optimizer moments. That is 1.13 TB of model state, and none of it is optional while the optimizer is Adam.</p>

<p>Then the activations. Megatron’s accounting for one transformer layer, with FlashAttention removing the quadratic attention term, is $34\,s\,b\,h$ bytes. At a sequence of 8192 tokens and $h = 8192$ that is 2.28 GB for one layer, and 182.5 GB across all eighty.</p>

<p>Add them and one GPU would need 1.31 TB. An H100 has 80 GB. The first figure draws that as sixteen and a bit GPUs’ worth of boxes, and then empties them one axis at a time.</p>

<div class="visual-embed">
  <iframe src="/images/blog/parallel-visuals/p1-two-budgets.html" height="820" loading="lazy"></iframe>
</div>

<script>
(function () {
  window.addEventListener('message', function (e) {
    var d = e.data;
    if (!d || d.type !== 'visualEmbedHeight' || !d.height) return;
    var frames = document.querySelectorAll('.visual-embed iframe');
    for (var i = 0; i < frames.length; i++) {
      if (frames[i].contentWindow === e.source) { frames[i].style.height = d.height + 'px'; }
    }
  });
})();
</script>

<h2 id="how-long-would-one-gpu-take-anyway">How long would one GPU take anyway?</h2>

<p>The second budget is time, and it does not care about the first. A dense transformer costs about six FLOPs per parameter per token: two in the forward pass, four in the backward.</p>

\[\text{FLOPs} = 6 P D = 6 \times 70.5\times 10^{9} \times 15 \times 10^{12} \approx 6.3 \times 10^{24}\]

<p>An H100 does 989 TFLOP/s of dense BF16 at peak, and a well-tuned training run achieves about 40% of that. So one GPU delivers 396 TFLOP/s of useful arithmetic and finishes the run in roughly five hundred years.</p>

<p>Five hundred and twelve of them take 363 days. Sixteen thousand take eleven. That is the shape of the second budget: it is satisfied only by throwing hardware at it, and the hardware is only useful if the model is split in a way that keeps it busy.</p>

<p>Notice that the two budgets give different answers. Memory says you need at least seventeen GPUs. Time says you want thousands. The interesting engineering lives in the gap.</p>

<h2 id="what-are-the-five-things-you-can-split">What are the five things you can split?</h2>

<p>Five, and it helps to name them by what gets cut rather than by their acronyms.</p>

<p><strong>Split the batch.</strong> Every GPU holds the model and processes different tokens. Gradients are averaged at the end of the step. ZeRO and FSDP are refinements that stop replicating the states.</p>

<p><strong>Split the tensors.</strong> Each matrix multiply inside a block is cut across GPUs, so every GPU holds a slice of every layer and works on the same tokens.</p>

<p><strong>Split the layers.</strong> Each GPU holds a contiguous group of layers and passes activations to the next group, like a factory line.</p>

<p><strong>Split the sequence.</strong> Each GPU holds part of the token sequence, which only matters because attention couples the whole sequence together.</p>

<p><strong>Split the experts.</strong> In a mixture-of-experts model, the experts live on different GPUs and tokens travel to them.</p>

<p>Data parallelism is the only one of the five where the model is never cut. That is why it is the default, and why it is the first to run out.</p>

<h2 id="why-does-each-axis-divide-a-different-thing">Why does each axis divide a different thing?</h2>

<p>This is the table I would draw on the whiteboard before answering anything else, and the fourth step of the figure builds it.</p>

<p>Four things occupy memory during training: the weights, the gradients, the optimizer states, and the activations saved for the backward pass. Every axis divides some of them and leaves the rest alone.</p>

<p>Plain data parallelism divides nothing. ZeRO stage 1 divides the optimizer states, stage 2 the gradients too, stage 3 the weights as well. None of the three touches the activations, because each rank still has to keep the activations of its own tokens.</p>

<p>Tensor parallelism divides all four, which is what makes it valuable and expensive at the same time. Pipeline parallelism divides the three model-state lines by the number of stages, but keeps several micro-batches in flight, so the activations barely move. Context parallelism divides the activations and nothing else. Expert parallelism divides the expert weights and nothing else.</p>

<div class="key-idea">
  <span class="key-idea-label"><i class="fa-solid fa-lightbulb"></i> Key idea</span>
  <span class="key-idea-text">Every parallelism scheme is a choice of which line of the memory inventory to divide, and the collective it costs is exactly the information you decided not to keep a copy of.</span>
</div>

<h2 id="what-does-each-axis-cost-on-the-wire">What does each axis cost on the wire?</h2>

<p>Numbers, for the layout this series arrives at: tensor parallelism of 8 inside each node, fully sharded data parallelism across the 64 nodes, one micro-batch of 8192 tokens.</p>

<p>One GPU owns 8.81 billion parameters and does 0.44 seconds of arithmetic per micro-batch at peak. Against that:</p>

<p>The gradient all-reduce of plain data parallelism moves 34.7 GB per rank per optimizer step. Fully sharded data parallelism moves 52.9 GB, because stage 3 adds an all-gather in the backward pass. Both ride the 400 Gb/s network at 50 GB/s, so they take of order a second, and both can hide behind the backward pass if the framework overlaps them properly.</p>

<p>Tensor parallelism moves 75.2 GB per micro-batch, not per step, because it fires four times per layer. It gets away with that only because NVLink inside a node is 450 GB/s per direction, nine times the network. On the network the same traffic takes 1.5 seconds against 0.44 seconds of arithmetic.</p>

<p>Pipeline parallelism moves 33.6 MB per stage boundary per micro-batch, forward and backward. It is the cheapest axis on the wire by three orders of magnitude, and it is the only one whose cost is idle time rather than bytes.</p>

<h2 id="what-does-a-good-layout-look-like-on-this-cluster">What does a good layout look like on this cluster?</h2>

<p>The last step of the figure sets it out, and every line in it is derived rather than remembered.</p>

<p>Tensor parallelism of 8, filling one node, divides both the weights and the activations. Fully sharded data parallelism across the 64 nodes divides the states again, so no byte of the model is stored twice anywhere in the cluster. Micro-batch of one 8192-token sequence per tensor-parallel group. Gradient accumulation sets the global batch without changing a single collective.</p>

<p>That gives 2.2 GB of model state per GPU, 22.8 GB of activations, about 4 GB of context and buffers and workspace, and 29 GB of an 80 GB card in use. The arithmetic is 438 ms per micro-batch at peak, and the whole run is 363 days at 40% MFU.</p>

<p>There is a lot of headroom in that 29 GB, and the rest of the series is about what to spend it on: a larger micro-batch, a deeper pipeline, a longer sequence, or nothing at all.</p>

<h2 id="what-is-this-series-and-what-is-it-not">What is this series, and what is it not?</h2>

<p>It is the derivation, not the walkthrough. Every part asks the question an interviewer opens with, works out the mechanism from the shapes, and puts a number on it.</p>

<p>For the practical side of the same subject, running jobs on a real cluster, I wrote <a href="/blog/2026/03/10/distributed-training-part-1-batch-size-hierarchy/">Distributed Training from Scratch</a> earlier this year: the batch-size hierarchy, DDP against FSDP with real config, and what actually breaks at 256 GPUs. That series is the one to read for launch scripts and the shape of a bad night. This one is the one to read before someone asks you to derive a bubble formula on a whiteboard.</p>

<p>For the hardware underneath, the <a href="/blog/2026/09/07/interrogating-the-gpu-part-1-where-the-time-goes/">GPU series</a> has the four clocks, the roofline, the memory hierarchy and the byte-by-byte inventory this part starts from. I will point at it rather than repeat it.</p>

<p>Six parts of derivation, and then a graded question set to check whether it stuck.</p>

<div class="interview-q">
  <span class="interview-q-label"><i class="fa-solid fa-circle-question"></i> They will ask</span>
  <span class="interview-q-text">You have 512 GPUs and a 70B model. Where do you start?</span>
  <details>
    <summary>The 30-second version</summary>
    <span class="interview-q-answer">With the byte inventory, not with the parallelism. Sixteen bytes per parameter is 1.13 TB of state, and 34 times sequence times hidden per layer is 182 GB of activations for one 8192-token sequence. That is sixteen H100s of memory for one GPU's worth of work, so the model has to be cut before anything else is decided. Then pick the axes in the order the hardware allows: tensor parallelism up to the eight GPUs in a node, because it is the only axis that divides both weights and activations and it needs NVLink; fully sharded data parallelism across the nodes, because its collective runs once per step and hides behind the backward pass. Check the arithmetic: 2.2 GB of states plus 22.8 GB of activations plus overheads is under 30 GB, so it fits with room to raise the micro-batch. Only add a pipeline if the states still do not fit, because a pipeline costs a bubble that data parallelism does not. And say the number: about 363 days on 512 H100s at 40% MFU for a fifteen trillion token run, which is the real reason frontier labs use sixteen thousand.</span>
  </details>
</div>

<h2 id="rapid-fire-can-you-do-these-from-memory">Rapid fire: can you do these from memory?</h2>

<div class="rapid-fire">
<ol>
<li>Derive the model-state bytes for a 70B model with Adam in mixed precision, and the activation bytes for one 8192-token sequence.</li>
<li>Write the FLOP count for a full training run and evaluate it for 70B parameters and 15T tokens.</li>
<li>Name the five axes and say which line of the memory inventory each one divides.</li>
<li>Which axes leave the activations untouched, and why?</li>
<li>Why does tensor parallelism have to stay inside a node while data parallelism does not?</li>
<li>Estimate the gradient all-reduce volume per rank for 8.8 billion parameters in BF16.</li>
<li>What is the cheapest axis on the wire, and what does it cost instead?</li>
</ol>
</div>

<p>Part 2 takes the axis everyone starts with, splitting the batch, and follows it until it stops working.</p>]]></content><author><name>Vaibhav Singh</name><email>vaibhav.singh@mila.quebec</email></author><category term="distributed-training" /><category term="parallelism" /><category term="systems" /><category term="scaling" /><category term="interview-prep" /><category term="deep-learning" /><summary type="html"><![CDATA[]]></summary></entry><entry><title type="html">Interrogating Parallelism, Part 2: Splitting the Batch</title><link href="https://vaibhav016.github.io/blog/2026/09/09/interrogating-parallelism-part-2-splitting-the-batch/" rel="alternate" type="text/html" title="Interrogating Parallelism, Part 2: Splitting the Batch" /><published>2026-09-09T00:00:00+00:00</published><updated>2026-09-09T00:00:00+00:00</updated><id>https://vaibhav016.github.io/blog/2026/09/09/interrogating-parallelism-part-2-splitting-the-batch</id><content type="html" xml:base="https://vaibhav016.github.io/blog/2026/09/09/interrogating-parallelism-part-2-splitting-the-batch/"><![CDATA[<h2 id="how-far-does-splitting-the-batch-get-you">How far does splitting the batch get you?</h2>

<p>Data parallelism is the axis nobody has to be talked into. Every GPU runs the ordinary single-device code on its own slice of the batch, the framework averages the gradients behind your back, and the result is mathematically identical to one enormous GPU with one enormous batch. Nothing about the model is cut.</p>

<p>Which is exactly why it runs out. The model is not cut, so every GPU still needs room for all of it, and the activations of its own tokens on top. ZeRO fixes the first half of that sentence and cannot touch the second.</p>

<p>This part follows the axis from a plain all-reduce to fully sharded data parallelism, counts the bytes at every step, and then works out the three walls it hits.</p>

<div class="interview-q">
  <span class="interview-q-label"><i class="fa-solid fa-circle-question"></i> They will ask</span>
  <span class="interview-q-text">Walk me through what happens in one data-parallel training step, and what ZeRO changes about it.</span>
  <details>
    <summary>The 30-second version</summary>
    <span class="interview-q-answer">Each rank runs forward and backward on its own tokens with no communication at all, then the gradients are averaged with an all-reduce so every rank applies the identical update. A ring all-reduce is a reduce-scatter followed by an all-gather, which moves two times the message minus a bit per rank, independent of how many ranks there are, and frameworks bucket the gradients so the reduction of the last layer overlaps the backward pass of the one before it. ZeRO observes that the replicas are redundant. Stage 1 shards the optimizer states, stage 2 shards the gradients too, stage 3 shards the weights as well and gathers each layer just before it runs. Stages 1 and 2 move the same 2 psi elements per step as plain data parallelism; stage 3 moves 3 psi, a 50% increase, in exchange for memory that falls linearly in the number of ranks. What none of them touch is the activations, because each rank still processes its own tokens, and that is the wall.</span>
  </details>
</div>

<h2 id="what-actually-happens-in-a-data-parallel-step">What actually happens in a data-parallel step?</h2>

<p>Four phases, and only one of them talks to anyone.</p>

<p>Each rank takes a different slice of the global batch, runs the forward pass, runs the backward pass, and ends up with a gradient for every parameter. Those gradients disagree, because each rank saw different tokens, and the optimizer wants the average over the whole batch.</p>

<p>One collective fixes that. After it, every rank holds the identical averaged gradient, applies the identical update to its identical weights, and the invariant holds for the next step.</p>

<p>For our running layout, where tensor parallelism has already cut the weights eight ways, each data-parallel rank is responsible for $\Psi = 8.81$ billion parameters. In BF16 that is 17.6 GB of gradient, and the all-reduce moves about twice that per rank.</p>

<div class="visual-embed">
  <iframe src="/images/blog/parallel-visuals/p2-data-parallel.html" height="860" loading="lazy"></iframe>
</div>

<script>
(function () {
  window.addEventListener('message', function (e) {
    var d = e.data;
    if (!d || d.type !== 'visualEmbedHeight' || !d.height) return;
    var frames = document.querySelectorAll('.visual-embed iframe');
    for (var i = 0; i < frames.length; i++) {
      if (frames[i].contentWindow === e.source) { frames[i].style.height = d.height + 'px'; }
    }
  });
})();
</script>

<h2 id="why-does-the-all-reduce-not-get-more-expensive-with-more-ranks">Why does the all-reduce not get more expensive with more ranks?</h2>

<p>This is the first thing interviewers check, and the answer is a two-line derivation.</p>

<p>A ring all-reduce is two halves. In the reduce-scatter, the message of $M$ bytes is cut into $d$ chunks, and each chunk walks around the ring being summed as it goes. After $d-1$ hops, rank $i$ owns the completely reduced version of one chunk. In the all-gather, those finished chunks walk around again until everyone has all of them.</p>

<p>Every hop moves one chunk, $M/d$ bytes, and there are $2(d-1)$ hops:</p>

\[\text{bytes sent per rank} = \frac{2(d-1)M}{d} &lt; 2M\]

<p>So bandwidth cost is flat in $d$. What is not flat is latency: there are $2(d-1)$ sequential hops, so a ring across 64 ranks pays 126 link latencies before the last byte lands. That is why large clusters use tree or hierarchical algorithms for small messages and rings for large ones, and why NCCL picks between them at runtime.</p>

<p>The second step of the figure animates the chunk grid and counts the bytes as they move. At $d = 8$ with a 17.6 GB gradient, each rank sends 30.8 GB, and it would send less than 35.2 GB no matter how large the ring got.</p>

<h2 id="how-does-the-collective-hide-behind-the-backward-pass">How does the collective hide behind the backward pass?</h2>

<p>The reduction does not have to wait for the backward pass to finish, because gradients become final layer by layer, from the top of the network down.</p>

<p>So the framework buckets them. PyTorch’s DDP fills a bucket, 25 MB by default, and launches an asynchronous all-reduce the moment it is full. The reduction of the last layer overlaps the backward pass of the one before it, and only the tail sticks out past the end.</p>

<p>That works beautifully when there is a lot of compute per byte, and stops working when there is not. The collective is fixed by the parameter count. The backward pass shrinks with the tokens on the GPU.</p>

<p>At 8192 tokens per GPU the backward arithmetic is 292 ms and the all-reduce over 64 ranks is 694 ms, so more than half the step is exposed. The third step of the figure lets you drag the token count and watch the tail appear.</p>

<h2 id="what-is-redundant-about-holding-the-optimizer-state-512-times">What is redundant about holding the optimizer state 512 times?</h2>

<p>Nothing about a data-parallel step needs every rank to hold every optimizer moment. The ZeRO paper’s observation is that after the reduction, rank $i$ only ever updates its own slice of the parameters, so it only ever needs its own slice of the states.</p>

<p>Stage 1 shards the twelve bytes of FP32 master weight and Adam moments across the $d$ ranks. Stage 2 shards the two bytes of gradient too, which turns the all-reduce into a reduce-scatter. Stage 3 shards the two bytes of BF16 weight as well, which means gathering each layer’s parameters just before it runs and freeing them straight after.</p>

\[\text{DDP: } 16\Psi \quad \text{ZeRO-1: } 4\Psi + \frac{12\Psi}{d} \quad \text{ZeRO-2: } 2\Psi + \frac{14\Psi}{d} \quad \text{ZeRO-3: } \frac{16\Psi}{d}\]

<p>The floors matter more than the slopes. Stage 1 never gets below $4\Psi$ and stage 2 never below $2\Psi$, whatever $d$ is, because the BF16 weight and then the gradient stay replicated on every rank. For our $\Psi$, that is 35.3 GB and 17.6 GB of permanent floor. Only stage 3 goes to zero.</p>

<div class="key-idea">
  <span class="key-idea-label"><i class="fa-solid fa-lightbulb"></i> Key idea</span>
  <span class="key-idea-text">ZeRO does not change what the cluster computes or how many bytes cross the wire in stages 1 and 2. It changes how many copies of the same information exist, and copies are the only thing data parallelism was ever wasting.</span>
</div>

<h2 id="what-does-each-stage-cost-on-the-wire">What does each stage cost on the wire?</h2>

<p>The ZeRO paper counts communication in elements, which is worth copying because it makes the comparison clean.</p>

<p>Plain data parallelism moves $2\Psi$ elements per rank per step: an all-reduce is a reduce-scatter of $\Psi$ followed by an all-gather of $\Psi$. Stage 1 moves the same $2\Psi$. Stage 2 moves the same $2\Psi$, because the reduce-scatter it uses is exactly the first half of the all-reduce and the all-gather of updated parameters is the second half.</p>

<p>Stage 3 moves $3\Psi$. The extra $\Psi$ is a second all-gather: the weights have to be gathered once for the forward pass and once again for the backward, because they were freed in between.</p>

<p>In BF16 those are four and six bytes per parameter. For our rank, 35.3 GB and 52.9 GB per step. A 50% increase in traffic for memory that falls linearly in $d$ is the trade, and at any reasonable scale it is a good one.</p>

<p>There is a middle option that shows up in real runs. Meta’s Llama 3 used FSDP to shard the optimizer states and the gradients, but deliberately did not reshard the parameters after the forward pass, so the backward pass reuses the gathered weights instead of paying for a second all-gather. That is stage 3’s memory for the forward and stage 2’s traffic overall, and it is the right choice when memory is not the binding constraint.</p>

<h2 id="what-does-stage-3-actually-do-per-layer">What does stage 3 actually do per layer?</h2>

<p>Stage 3 is a promise that a layer’s weights will exist by the time the layer runs.</p>

<p>Before layer $i$ computes, its shard is all-gathered from the $d$ ranks into a full copy. The layer computes. The full copy is freed. In the backward pass the same gather happens again, the gradient is computed, a reduce-scatter sends each rank its own slice, and the copy is freed again.</p>

<p>Done naively, the GPU idles through every gather. Done with prefetch, the gather for layer $i+1$ is issued while layer $i$ is still computing, and the only exposed gather in the whole model is the first one. The fifth step of the figure has a prefetch toggle: turn it off and the compute row fills with holes.</p>

<p>The knob is wrapping granularity. Wrap too coarsely and the gathered chunk is enormous, which defeats the memory saving. Wrap too finely and each gather is too small to reach line rate on the network.</p>

<p>PyTorch’s FSDP2 shards each parameter on its own with DTensor rather than flattening a module’s parameters into one buffer, which is what turns wrapping into a per-module decision and makes it compose with tensor parallelism. In practice you wrap one transformer block per unit, and one block’s weights on our layout are 220 MB gathered.</p>

<h2 id="what-does-gradient-accumulation-actually-buy">What does gradient accumulation actually buy?</h2>

<p>It buys arithmetic to hide the collective behind, and nothing else.</p>

<p>Run $k$ micro-batches, accumulate the gradients locally, reduce once. The collective is unchanged, because it is sized by the parameter count. The compute per optimizer step is $k$ times larger. So the exposed fraction falls as $1/k$ without changing the global batch’s learning dynamics at all, as long as you were going to run those tokens anyway.</p>

<p>The catch is numerical. Accumulating $k$ BF16 gradients loses the small contributions, so the accumulator has to be FP32. Meta reported doing exactly that for Llama 3: FP32 gradient accumulation across micro-batches, and an FP32 reduce-scatter across data-parallel workers.</p>

<p>The other thing to know is what accumulation does not buy. It does not reduce memory, because the accumulator is the same size as the gradient. And it does not help the pipeline, which wants micro-batches for a completely different reason that Part 4 gets to.</p>

<h2 id="where-does-the-batch-axis-stop">Where does the batch axis stop?</h2>

<p>Three walls, and none of them is about bandwidth.</p>

<p>The activations never shard along this axis. After ZeRO-3 has taken the model states on our layout from 141 GB to 2.2 GB, the 182.5 GB of activations for a single 8192-token sequence is exactly where it was. That is the wall that sends you to tensor, pipeline or context parallelism, and it is the one that matters most.</p>

<p>The global batch has a ceiling. Global batch is tokens per rank times $d$, so at fixed tokens per rank, adding ranks grows the batch, and batch size is a hyperparameter that stops helping. Hold the batch fixed instead and the tokens per rank fall. Meta held Llama 3 405B at 16 million tokens per batch and halved the per-rank batch when the data-parallel degree went from 64 to 128; the reported MFU fell from 43% to 41%, and they attribute the drop to exactly that.</p>

<p>And falling tokens per rank means less arithmetic to hide the same fixed collective behind, which is the third wall and a consequence of the second.</p>

<p>All three say the same thing. Past a point you have to cut the model, not the batch.</p>

<div class="interview-q">
  <span class="interview-q-label"><i class="fa-solid fa-circle-question"></i> They will ask</span>
  <span class="interview-q-text">Your FSDP job is at 20% MFU and the profiler shows NCCL kernels that are not overlapped. What do you check?</span>
  <details>
    <summary>The 30-second version</summary>
    <span class="interview-q-answer">First the arithmetic: parameters times six bytes for stage 3 is the traffic per rank per step, and tokens per rank times six FLOPs per parameter is the compute it has to hide behind. If the second is smaller than the first, no amount of tuning fixes it and the answer is gradient accumulation or a larger micro-batch. If there is enough compute, then it is a prefetch or wrapping problem: check that the unit is a whole transformer block rather than the whole model or individual linears, check that forward prefetch and backward prefetch are enabled, and check that the first all-gather is not being issued inside the layer that needs it. Then check whether the job should be resharding after forward at all, because keeping the gathered parameters for the backward pass removes an entire all-gather at the cost of holding one unit longer. And confirm the data-parallel group is not spanning a slow tier of the network when a hybrid sharded arrangement would keep the all-gathers inside a node.</span>
  </details>
</div>

<h2 id="rapid-fire-can-you-do-these-from-memory">Rapid fire: can you do these from memory?</h2>

<div class="rapid-fire">
<ol>
<li>Derive the per-rank byte count of a ring all-reduce and say what does and does not grow with the ring size.</li>
<li>Write the per-rank memory for DDP and the three ZeRO stages, and name the two floors.</li>
<li>Give the communication volume in elements for each stage, and say where stage 3's extra volume comes from.</li>
<li>Why does bucketing make the reduction overlap the backward pass, and when does it stop helping?</li>
<li>Describe the lifecycle of one layer's weights under stage 3, in both passes.</li>
<li>What does gradient accumulation change, what does it not change, and what precision does the accumulator need?</li>
<li>Name the three reasons more data parallelism stops helping, and which of them is about memory.</li>
</ol>
</div>

<p>Part 3 cuts the model for the first time, and starts with the smallest unit worth cutting: a single matrix multiply.</p>]]></content><author><name>Vaibhav Singh</name><email>vaibhav.singh@mila.quebec</email></author><category term="distributed-training" /><category term="parallelism" /><category term="data-parallel" /><category term="fsdp" /><category term="interview-prep" /><category term="deep-learning" /><summary type="html"><![CDATA[]]></summary></entry><entry><title type="html">Interrogating Parallelism, Part 3: Splitting a Matrix Multiply</title><link href="https://vaibhav016.github.io/blog/2026/09/09/interrogating-parallelism-part-3-splitting-a-matrix-multiply/" rel="alternate" type="text/html" title="Interrogating Parallelism, Part 3: Splitting a Matrix Multiply" /><published>2026-09-09T00:00:00+00:00</published><updated>2026-09-09T00:00:00+00:00</updated><id>https://vaibhav016.github.io/blog/2026/09/09/interrogating-parallelism-part-3-splitting-a-matrix-multiply</id><content type="html" xml:base="https://vaibhav016.github.io/blog/2026/09/09/interrogating-parallelism-part-3-splitting-a-matrix-multiply/"><![CDATA[<h2 id="how-do-you-split-a-matrix-multiply-across-gpus">How do you split a matrix multiply across GPUs?</h2>

<p>Part 2 ended at a wall: the activations of a sequence do not shard along the batch axis, no matter which ZeRO stage you run. To divide them you have to cut the model itself, and the smallest thing worth cutting is one matrix multiply.</p>

<p>There are exactly two ways to cut a weight matrix, and which one you choose is forced by the nonlinearity that follows it, not by the bandwidth. That single observation is most of Megatron’s tensor parallelism, and it is the thing to be able to derive rather than recall.</p>

<p>The rest of the part is arithmetic: how many collectives per layer, how many bytes each one moves, what sequence parallelism adds for free, and why the degree stops at the number of GPUs in a node.</p>

<div class="interview-q">
  <span class="interview-q-label"><i class="fa-solid fa-circle-question"></i> They will ask</span>
  <span class="interview-q-text">How does tensor parallelism split a transformer block, and what does it cost?</span>
  <details>
    <summary>The 30-second version</summary>
    <span class="interview-q-answer">The first matrix of a pair is split along its columns, so each GPU owns whole output features and any elementwise nonlinearity applies locally with no communication. The second is split along its rows, so it consumes those features directly and produces a partial sum. One all-reduce turns the partial sums back into the residual stream. Attention works the same way with heads as the unit: query, key and value projections are column-parallel, the output projection is row-parallel, and every head's computation stays on one device. Two operators do the bookkeeping and are conjugates: f is identity forward and all-reduce backward, g is all-reduce forward and identity backward. That is two all-reduces per layer in the forward pass and two in the backward, each of sequence times batch times hidden elements. For Llama 3 70B at 8192 tokens and eight-way splitting, that is 134 MB a message and 75 GB per GPU per micro-batch, which is 38% of the arithmetic on NVLink and three and a half times the arithmetic on a 400 Gb/s network. That ratio is why the degree stays inside a node.</span>
  </details>
</div>

<h2 id="which-way-do-you-cut-the-weight-matrix">Which way do you cut the weight matrix?</h2>

<p>Take one GEMM followed by a nonlinearity, $Y = f(XA)$, and two GPUs.</p>

<p>Cut $A$ along its rows and you must cut $X$ along its columns to match. Each GPU computes a partial product, and $f$ cannot be applied to a partial product, because $f(u + v) \neq f(u) + f(v)$ for anything worth calling a nonlinearity. So the partials have to be summed first, which means a synchronisation in the middle of the block.</p>

<p>Cut $A$ along its columns instead and each GPU owns whole output features:</p>

\[[Y_1, Y_2] = [f(XA_1),\; f(XA_2)]\]

<p>The nonlinearity applies column by column, locally, and nothing is communicated at all. That is the asymmetry the whole scheme is built on.</p>

<p>Megatron pairs them. First GEMM column-parallel, second GEMM row-parallel so it consumes the sharded output directly, and one reduction at the end of the pair. The reduction sits exactly where the residual stream has to be whole anyway.</p>

<div class="visual-embed">
  <iframe src="/images/blog/parallel-visuals/p3-tensor-parallel.html" height="820" loading="lazy"></iframe>
</div>

<script>
(function () {
  window.addEventListener('message', function (e) {
    var d = e.data;
    if (!d || d.type !== 'visualEmbedHeight' || !d.height) return;
    var frames = document.querySelectorAll('.visual-embed iframe');
    for (var i = 0; i < frames.length; i++) {
      if (frames[i].contentWindow === e.source) { frames[i].style.height = d.height + 'px'; }
    }
  });
})();
</script>

<h2 id="what-does-that-look-like-in-the-mlp-block">What does that look like in the MLP block?</h2>

<p>Llama’s MLP is three matrices, not two: a gate projection and an up projection, both $8192 \times 28672$, and a down projection of $28672 \times 8192$. SwiGLU multiplies the gated half by the up half elementwise.</p>

<p>Split gate and up along their columns. At $t = 8$ each GPU owns 3584 of the 28,672 intermediate features, computes both halves for those features, and applies SwiGLU to its own slice with no communication, because the product is elementwise in the feature index.</p>

<p>Split down along its rows over the same partition. Each GPU multiplies its 3584 features by its 3584 rows and produces a full-width partial sum of the output. One all-reduce adds the eight partial sums.</p>

<p>Each GPU holds 176 MB of this block instead of 1.4 GB, and every activation inside the parallel region is a factor of eight smaller.</p>

<h2 id="what-are-f-and-g-exactly">What are f and g, exactly?</h2>

<p>Two autograd hooks, and they are conjugates of each other. This is the detail that tells an interviewer whether you have implemented it or only read about it.</p>

<p>$f$ sits at the entry to the parallel region. In the forward pass it does nothing, because the input was already broadcast to every GPU in the group. In the backward pass it all-reduces the gradient, because every GPU produced a gradient with respect to that same shared input and they have to be summed.</p>

<p>$g$ sits at the exit. In the forward pass it all-reduces, because the output was a partial sum. In the backward pass it does nothing, because the incoming gradient is already identical on every GPU.</p>

<p>Identity forward, all-reduce backward. All-reduce forward, identity backward. Two lines of PyTorch each.</p>

<h2 id="how-does-attention-split">How does attention split?</h2>

<p>More naturally than the MLP, because the heads are already independent.</p>

<p>Give each GPU $64/t$ query heads and the key-value heads that go with them. The query, key and value projections are column-parallel over the head dimension, so each GPU makes only its own heads. The scores, the softmax and the weighted sum for a head happen entirely on the device that owns it: no head ever needs another head.</p>

<p>The output projection is row-parallel over the same partition, so it consumes the local head outputs and emits a partial sum. Same shape as the MLP: column, row, one all-reduce.</p>

<p>There is a hard limit hiding in there, and it is grouped-query attention. Llama 3 70B has 8 key-value heads. At $t = 8$ each GPU owns exactly one, which is tidy. At $t = 16$ the key-value heads have to be duplicated across pairs of GPUs, and duplicated weights mean duplicated gradients and an extra reduction to keep them in step. The KV head count is a ceiling on the tensor-parallel degree that has nothing to do with bandwidth.</p>

<h2 id="how-many-collectives-is-that-and-how-many-bytes">How many collectives is that, and how many bytes?</h2>

<p>Two all-reduces per layer in the forward pass, one after attention and one after the MLP. Two more in the backward pass from the conjugate operators. Four per layer, and for 80 layers that is 320 collectives per micro-batch.</p>

<p>Each message is $s\,b\,h$ elements. At 8192 tokens and $h = 8192$ in BF16, 134.2 MB. A ring all-reduce over $t$ ranks moves $2(t-1)/t$ of that per rank, which at $t = 8$ is 234.9 MB.</p>

\[4 \times 80 \times 234.9\ \text{MB} = 75.2\ \text{GB per GPU per micro-batch}\]

<p>Against 438 ms of arithmetic on this GPU, that is 167 ms on NVLink at 450 GB/s per direction, so 38% overhead. On a 400 Gb/s network port at 50 GB/s it would be 1.50 seconds, three and a half times the arithmetic.</p>

<p>And note what it is per. Data parallelism’s collective fires once per optimizer step and amortises over gradient accumulation. This one fires per micro-batch, four times per layer, and amortises over nothing.</p>

<div class="key-idea">
  <span class="key-idea-label"><i class="fa-solid fa-lightbulb"></i> Key idea</span>
  <span class="key-idea-text">Tensor parallelism is the only axis that divides both the weights and the activations, and it pays for that with the highest-frequency collective in the job. Its degree is chosen by the interconnect topology, not by the model.</span>
</div>

<h2 id="what-is-sequence-parallelism-and-why-is-it-free">What is sequence parallelism, and why is it free?</h2>

<p>Tensor parallelism leaves two things replicated on every GPU in the group: the layer norms and the dropouts, and the residual stream they act on. They are cheap to compute and expensive to store.</p>

<p>In Megatron’s activation accounting they are the $10\,s\,b\,h$ that stubbornly refuses to divide:</p>

\[\text{tensor parallel: } s\,b\,h\left(10 + \frac{24}{t}\right) \qquad \text{tensor + sequence: } s\,b\,h\left(\frac{34}{t}\right)\]

<p>The fix is that those operations are independent along the sequence, so they can be split that way instead. The all-reduce at the end of a parallel region becomes a reduce-scatter, which lands each GPU with its own slice of the sequence, and the entry to the next region becomes an all-gather.</p>

<p>The bytes on the wire do not change, because a ring all-reduce was a reduce-scatter followed by an all-gather all along. Korthikanti and colleagues make that point explicitly: four all-reduces per forward and backward becomes four all-gathers and four reduce-scatters, at identical bandwidth.</p>

<p>What changes is memory. At $t = 8$ and our shape, a layer goes from 872 MB to 285 MB, a factor of three, and the model’s activations from 69.8 GB to 22.8 GB. It is the rare optimisation with no downside, which is why every framework bundles it with tensor parallelism rather than exposing it as a separate switch.</p>

<h2 id="what-does-tensor-parallelism-do-to-each-kernel">What does tensor parallelism do to each kernel?</h2>

<p>Two things, and they both get worse as $t$ grows.</p>

<p>The matmuls get narrower. The MLP GEMM on one GPU is $8192 \times 3584$ at $t = 8$, and $8192 \times 896$ at $t = 32$. A narrow GEMM has fewer tiles to spread over 132 streaming multiprocessors and a worse ratio of epilogue to arithmetic, so it sits further below the roofline. This is the <a href="/blog/2026/09/07/interrogating-the-gpu-part-2-the-roofline/">Part 2 of the GPU series</a> problem arriving through a different door.</p>

<p>And every all-reduce is a synchronisation. Whatever the fastest GPU in the group finishes early is wasted, four times per layer, so a straggler tax that would be invisible once per step becomes 320 taxes per micro-batch.</p>

<p>There is a live line of work on the first problem. Asynchronous tensor parallelism decomposes the GEMM and the collective into chunks so that a slice of the matmul overlaps the transfer of the previous slice, using symmetric memory to write directly into a peer’s buffer. It is in torchtitan under compile, and it is the main reason the practical ceiling on $t$ has moved at all since 2021.</p>

<h2 id="why-does-the-degree-stop-at-eight">Why does the degree stop at eight?</h2>

<p>Three limits arrive at almost the same place, which is unusual and convenient.</p>

<p>Bandwidth. Arithmetic per GPU falls as $1/t$, while communication climbs towards a ceiling, because $2(t-1)/t \to 2$. So the ratio of communication to computation grows roughly as $t$: 5% at $t=2$, 16% at $t=4$, 38% at $t=8$, 82% at $t=16$ on NVLink. Across a network it is nine times worse and hopeless everywhere.</p>

<p>Heads. Eight key-value heads means eight is the largest degree that does not duplicate them.</p>

<p>Tiles. At $t = 32$ the FFN matmul is 896 wide, which is a handful of tile columns.</p>

<p>Megatron’s own guidance from the other direction is the same conclusion: use tensor parallelism up to the number of GPUs in a server, then use pipeline parallelism to go further. Every published dense recipe in Part 6 has $t = 8$, and the reason is that servers have eight GPUs.</p>

<div class="interview-q">
  <span class="interview-q-label"><i class="fa-solid fa-circle-question"></i> They will ask</span>
  <span class="interview-q-text">You need to halve the activation memory. Do you raise the tensor-parallel degree from 8 to 16?</span>
  <details>
    <summary>The 30-second version</summary>
    <span class="interview-q-answer">Almost certainly not, and the reasons are worth listing in order. The group would straddle two nodes, so 320 collectives per micro-batch would move from a 450 GB/s link to a 50 GB/s one, and the arithmetic they overlap with has halved at the same time, so the ratio goes from about 38% to well over 100%. The model has eight key-value heads, so sixteen-way splitting duplicates them and adds a reduction. And the FFN matmul narrows to 1792 columns, which costs efficiency inside the kernel. Cheaper options first: turn on sequence parallelism if it is somehow off, since it is a factor of three on this shape for no extra bytes; enable selective activation recomputation, which drops the attention internals for a few percent of extra compute; halve the micro-batch and use gradient accumulation to keep the global batch; or split the sequence with context parallelism if the sequence is what is long. Raising the tensor degree past the node is the last thing on the list, not the first.</span>
  </details>
</div>

<h2 id="rapid-fire-can-you-do-these-from-memory">Rapid fire: can you do these from memory?</h2>

<div class="rapid-fire">
<ol>
<li>Explain why the first GEMM of a pair is column-parallel and the second is row-parallel.</li>
<li>Define f and g and say what each does in each pass.</li>
<li>Count the collectives per layer per micro-batch, and give the message size in elements.</li>
<li>Compute the per-GPU tensor-parallel traffic for 80 layers at 8192 tokens and eight-way splitting.</li>
<li>What does sequence parallelism split, what does it cost, and which term of the activation formula does it fix?</li>
<li>Why does grouped-query attention cap the tensor-parallel degree?</li>
<li>Name three separate reasons the degree stops at the size of a node.</li>
</ol>
</div>

<p>Part 4 takes the other way of cutting the model: not across each layer, but between them.</p>]]></content><author><name>Vaibhav Singh</name><email>vaibhav.singh@mila.quebec</email></author><category term="distributed-training" /><category term="parallelism" /><category term="tensor-parallel" /><category term="megatron" /><category term="interview-prep" /><category term="deep-learning" /><summary type="html"><![CDATA[]]></summary></entry><entry><title type="html">Interrogating Parallelism, Part 4: Splitting the Layers</title><link href="https://vaibhav016.github.io/blog/2026/09/09/interrogating-parallelism-part-4-splitting-the-layers/" rel="alternate" type="text/html" title="Interrogating Parallelism, Part 4: Splitting the Layers" /><published>2026-09-09T00:00:00+00:00</published><updated>2026-09-09T00:00:00+00:00</updated><id>https://vaibhav016.github.io/blog/2026/09/09/interrogating-parallelism-part-4-splitting-the-layers</id><content type="html" xml:base="https://vaibhav016.github.io/blog/2026/09/09/interrogating-parallelism-part-4-splitting-the-layers/"><![CDATA[<h2 id="how-do-you-split-the-layers">How do you split the layers?</h2>

<p>Tensor parallelism cut across every layer and paid in bandwidth. The other way to cut a model is between layers: give the first twenty to one group of GPUs, the next twenty to the next, and pass activations down the line.</p>

<p>It is the cheapest axis on the wire by a very wide margin. One activation tensor per stage boundary per micro-batch, point to point, no collective at all. It is also the only axis whose cost is idle time, and idle time does not overlap with anything.</p>

<p>Everything about pipeline parallelism is a fight with that idle time. There are four schedules worth being able to draw, and the difference between them is entirely in when the backward passes run.</p>

<div class="interview-q">
  <span class="interview-q-label"><i class="fa-solid fa-circle-question"></i> They will ask</span>
  <span class="interview-q-text">What is the pipeline bubble, how big is it, and how do you make it smaller?</span>
  <details>
    <summary>The 30-second version</summary>
    <span class="interview-q-answer">The bubble is the idle time at the start and end of every batch, while the pipeline fills and drains. It is p minus one slots of warm-up and the same again of drain, against m micro-batches of useful work, so the bubble is (p−1)/m of the ideal time and you want m much larger than p. GPipe runs all forwards then all backwards and holds all m micro-batches' activations; 1F1B alternates once warmed up, has exactly the same wall clock, and caps in-flight micro-batches at p minus the stage index, which is what makes large m affordable. Interleaving gives each device v non-contiguous chunks of the model so the slot is v times shorter, cutting the bubble to (p−1)/(vm) at the cost of v times as many point-to-point messages. Zero-bubble schedules split the backward pass into the input gradient, which the previous stage is waiting for, and the weight gradient, which nobody is waiting for, and use the second as filler; that takes the bubble to a third with no extra memory, or to nothing at all with about twice the activations in flight. And the memory floor that none of them fix: the first stage holds p micro-batches of L over p layers each, which is L layers' worth of activations whatever p is.</span>
  </details>
</div>

<h2 id="what-crosses-a-stage-boundary">What crosses a stage boundary?</h2>

<p>Almost nothing, which is the point.</p>

<p>The forward pass sends one activation tensor from the last layer of stage $i$ to the first layer of stage $i+1$. With tensor and sequence parallelism already applied, that tensor is $s/t \times h$ in BF16, which for our shape is 16.8 MB. The backward pass sends the same shape the other way.</p>

<p>Compare that with the 75.2 GB per micro-batch that tensor parallelism moves in the same pass. Pipeline traffic is three orders of magnitude smaller, and it is point to point rather than a collective, so it does not synchronise anyone who is not directly involved.</p>

<p>The weights, gradients and optimizer states all divide cleanly by $p$. At four stages our 70B model’s states go from 141 GB per tensor-parallel rank to 35.3 GB.</p>

<p>And then you run one batch through it and every stage but one is idle.</p>

<div class="visual-embed">
  <iframe src="/images/blog/parallel-visuals/p4-pipeline-bubble.html" height="860" loading="lazy"></iframe>
</div>

<script>
(function () {
  window.addEventListener('message', function (e) {
    var d = e.data;
    if (!d || d.type !== 'visualEmbedHeight' || !d.height) return;
    var frames = document.querySelectorAll('.visual-embed iframe');
    for (var i = 0; i < frames.length; i++) {
      if (frames[i].contentWindow === e.source) { frames[i].style.height = d.height + 'px'; }
    }
  });
})();
</script>

<h2 id="where-does-the-bubble-come-from">Where does the bubble come from?</h2>

<p>Cut the batch into $m$ micro-batches and push them in one after another. Stage 0 starts on micro-batch 1 at time zero, stage 1 starts on it one slot later, and stage $p-1$ cannot start until $p-1$ slots have passed.</p>

<p>The same thing happens in reverse at the end, when the last micro-batch drains back down the pipeline. Between those two, every stage is busy.</p>

\[t_{\text{bubble}} = (p-1)(t_F + t_B), \qquad t_{\text{ideal}} = m(t_F + t_B), \qquad \frac{t_{\text{bubble}}}{t_{\text{ideal}}} = \frac{p-1}{m}\]

<p>At $p = 4$ and $m = 16$ that is 18.8%, and the figure measures exactly that off the drawn schedule rather than asserting it. Drag $m$ and the number falls as $1/m$. Drag $p$ and it climbs.</p>

<p>A flush at the end of every batch is what forces this. Asynchronous and bounded-staleness pipelines avoid it by letting different micro-batches see different weight versions, and every production framework rejected that trade, because reproducing a training curve matters more than the last few percent.</p>

<h2 id="why-does-1f1b-not-make-it-faster">Why does 1F1B not make it faster?</h2>

<p>Because the bubble does not care what order the passes run in. It cares how deep the pipeline is.</p>

<p>In GPipe, each stage runs all $m$ forward passes, then all $m$ backward passes. In 1F1B, a stage warms up with a few forwards and then alternates one forward and one backward for the rest of the batch. Put the two schedules next to each other on the same axis, as the third step of the figure does, and the wall clocks are identical.</p>

<p>What changes is the number of micro-batches a stage has in flight, meaning micro-batches whose forward pass has run and whose backward pass has not, so whose activations must still be resident. GPipe holds all $m$. 1F1B holds at most $p - i$ on stage $i$.</p>

<p>At $m = 16$ and four stages, that is 91.3 GB of activations on stage 0 against 22.8 GB. The first number does not fit in an H100 and the second does.</p>

<p>So 1F1B does not make the pipeline faster. It makes a large $m$ affordable, and a large $m$ is what makes the pipeline fast. Megatron calls it PipeDream-Flush and it has been the default everywhere for years.</p>

<div class="key-idea">
  <span class="key-idea-label"><i class="fa-solid fa-lightbulb"></i> Key idea</span>
  <span class="key-idea-text">1F1B buys memory, not time. Interleaving and zero-bubble scheduling buy time. Knowing which schedule bought which is the whole of this subject.</span>
</div>

<h2 id="how-does-interleaving-cut-the-bubble">How does interleaving cut the bubble?</h2>

<p>The bubble is $p-1$ slots long, and a slot is however long one stage’s forward pass takes. So make the slot shorter.</p>

<p>Give each device $v$ non-contiguous chunks of the model instead of one contiguous block. With $v = 2$ and eight stages, device 0 owns the first sixteenth of the layers and the ninth sixteenth. The pipeline now has $vp$ virtual stages, each a $v$-th as long.</p>

<p>The last device still starts after $p-1$ forwards, because virtual stages 0 through $p-1$ are the first chunk on devices 0 through $p-1$. But each of those forwards is now $1/v$ of a full one. The warm-up costs $v$ times less time:</p>

\[\text{bubble} = \frac{1}{v}\cdot\frac{p-1}{m}\]

<p>The fourth step of the figure zooms into the warm-up of both schedules, which is where the entire difference lives, and measures the bubble off each.</p>

<p>The price is $v$ times as many point-to-point messages, over the slowest links in the job. At 16.8 MB a message that is a trade almost anyone would take. Meta used interleaving for Llama 3 and quoted the bubble in the paper as $(PP-1)/(V \cdot M)$.</p>

<h2 id="what-does-splitting-the-backward-pass-buy">What does splitting the backward pass buy?</h2>

<p>A backward pass is two computations glued together. One computes the gradient with respect to the layer’s input, which the previous stage is blocked on. The other computes the gradient with respect to the layer’s weights, which nobody is blocked on.</p>

<p>The Zero Bubble paper’s whole idea is to stop treating them as one unit. Call them $B$ and $W$. Now $B$ is half as long, so the critical path through the pipeline is shorter, and $W$ can be dropped into any gap after its own $B$.</p>

<p>Their ZB-H1 schedule keeps 1F1B’s peak memory exactly and takes the bubble from $(p-1)(F+B+W)$ to $(p-1)(F+B-W)$, which is a third of the size when the three passes take equal time. The fifth step of the figure shows the ochre $W$ blocks filling what were holes, and measures 18.8% falling to 6.3%.</p>

<p>ZB-H2 goes further, adding forwards to the warm-up and reordering the $W$s at the tail so the shape becomes a parallelogram rather than a trapezoid. That reaches $(p-1)(F+B-2W)$, which is zero when the three take the same time, and it costs about twice the activations in flight. Getting the last of the bubble also required removing the synchronisation in the optimizer step, which the paper handles with a post-hoc validation rather than an up-front all-reduce.</p>

<p>DeepSeek-V3 went further again with DualPipe: two pipelines running in opposite directions at once, with a bubble of $(p/2 - 1)(F\&amp;B + B - 3W)$, paid for with two copies of the parameters. They built it because their expert-parallel all-to-all needed something to hide behind, which is Part 5’s problem.</p>

<h2 id="how-much-memory-is-actually-in-flight">How much memory is actually in flight?</h2>

<p>This is the part people get wrong, and it is a good interview question because the naive answer is so tempting.</p>

<p>Deepening the pipeline divides the weights by $p$. It does not divide the activations, because with a bubble-minimising schedule the first stage has to keep $p$ micro-batches in flight to stay busy, and each of those carries $L/p$ layers of activations.</p>

\[p \times \frac{L}{p} = L \text{ layers' worth, for every } p\]

<p>Korthikanti and colleagues state it exactly that way: the total activation memory on the first stage is $\frac{s\,b\,h\,L}{t}\left(34 + \frac{5as}{h}\right)$, with no $p$ in it at all. On our shape that is 22.8 GB regardless of whether the pipeline is four stages deep or sixteen.</p>

<p>Interleaving makes it slightly worse, by a factor of $1 + \frac{p-1}{pm}$, because each device holds pieces of two places in the network. Nobody notices that term; everyone notices the $L$.</p>

<h2 id="how-do-you-choose-the-number-of-micro-batches">How do you choose the number of micro-batches?</h2>

<p>You do not choose it directly. It falls out of a batch identity:</p>

\[m = \frac{\text{global batch}}{\text{micro-batch} \times d}\]

<p>Larger $m$ shrinks the bubble as $1/m$. But at a fixed global batch, every GPU added to the data-parallel dimension takes micro-batches away from the pipeline. That is why the pipeline degree and the data-parallel degree cannot be chosen independently, and why frontier runs hold tokens per batch constant and accept a falling MFU rather than letting the bubble grow.</p>

<p>The last step of the figure plots the bubble against $m$ for one, two and four-way interleaving. At four stages you need about thirty micro-batches for a 10% bubble with plain 1F1B, or eight with four-way interleaving.</p>

<p>And a practical constraint that a lot of implementations impose and Meta had to remove: many schedules require the micro-batch count to be divisible by the number of stages. Their fix for Llama 3 was to make the number of consecutive micro-batches per stage a free parameter, so they could run fewer micro-batches than stages when the batch was tight, or more when they wanted to hide point-to-point latency.</p>

<h2 id="what-else-goes-wrong-in-a-real-pipeline">What else goes wrong in a real pipeline?</h2>

<p>Two imbalances that the clean picture does not show, and Meta documented both.</p>

<p>The first stage carries the embedding table on top of its layers, and carries the most warm-up micro-batches, so it uses the most memory. The last stage computes the output projection and the loss, so it is the slowest, and in a pipeline the slowest stage sets the pace for everyone. Their fix was to take one transformer layer away from each end: the first chunk holds only the embedding, the last only the projection and the loss.</p>

<p>Beyond that, the list is unglamorous. Asynchronous point-to-point so a stage does not block on a send. Proactive deallocation of stage input and output tensors that nothing will read again. And a document mask that makes different micro-batches cost different amounts, which is a straggler problem with a pipeline shape.</p>

<p>With all of that, Meta reported pre-training Llama 3 on 8192-token sequences with no activation checkpointing at all, which is the outcome the whole exercise is for.</p>

<div class="interview-q">
  <span class="interview-q-label"><i class="fa-solid fa-circle-question"></i> They will ask</span>
  <span class="interview-q-text">Your pipeline is 16 stages deep and the profiler shows long idle bands at the ends of every step. What do you do?</span>
  <details>
    <summary>The 30-second version</summary>
    <span class="interview-q-answer">First put a number on it: fifteen over the micro-batch count is the expected bubble, so at eight micro-batches it is 188% of the ideal time and the profile is behaving exactly as the formula says. Then check whether the micro-batch count is small because the global batch is small or because the data-parallel degree is large, since those have different fixes. If micro-batches are available, raise them, since 1F1B caps in-flight activations at the pipeline depth so it costs nothing in memory. If they are not, interleave: two or four chunks per device divides the bubble by that factor for the price of proportionally more point-to-point messages. If the schedule is already interleaved, split the backward pass and use a zero-bubble schedule, which takes another factor of three with no memory cost. And check the two imbalances before any of that, because a slow last stage with the loss on it, or a first stage carrying the embeddings and every warm-up micro-batch, looks like a bubble in a summary and is not one.</span>
  </details>
</div>

<h2 id="rapid-fire-can-you-do-these-from-memory">Rapid fire: can you do these from memory?</h2>

<div class="rapid-fire">
<ol>
<li>Derive the bubble fraction and say what it is at eight stages and sixteen micro-batches.</li>
<li>Say what 1F1B changes relative to GPipe, and what it does not.</li>
<li>Give the interleaved bubble formula and the cost that pays for it.</li>
<li>Explain what B and W are and why splitting them shortens the critical path.</li>
<li>State the peak activation memory of the first stage in layers, and why p does not appear in it.</li>
<li>Write the identity that determines the micro-batch count, and say what competes for it.</li>
<li>Name the two structural imbalances in a real pipeline and a fix for each.</li>
</ol>
</div>

<p>Part 5 takes the two axes people reach for last, and the two that interviews probe hardest: splitting the sequence, and splitting the experts.</p>]]></content><author><name>Vaibhav Singh</name><email>vaibhav.singh@mila.quebec</email></author><category term="distributed-training" /><category term="parallelism" /><category term="pipeline-parallel" /><category term="scheduling" /><category term="interview-prep" /><category term="deep-learning" /><summary type="html"><![CDATA[]]></summary></entry></feed>