Every sparse Mixture-of-Experts layer has a number in it that nobody derived. You need a static shape for the kernel, so each expert gets a budget c = C · topk · T / E, and C — the capacity factor — arrives as folklore: 1.25 from Switch Transformer, 2.0 from GShard. Those values were chosen at a particular expert count, batch size and load, and then re-used across expert counts from 8 to 2048 and across both training and serving.
The question worth asking is what C = 1.25 means. It turns out it means a tolerance, and the tolerance is one you do not want at decode time.
The drop rate is an exact function, so this is arithmetic rather than vibes
Under top-k routing with independent per-token decisions, expert e receives N_e ~ Bin(T, q_e) tokens, where q_e = k · p_e and p is the router's distribution. By linearity of expectation, the expected dropped-token rate depends only on the marginals:
D(C) = (1/T) Σ_e E[(N_e − c)+], c = C · k · T / E = C · µ, µ := k·T/E
Nothing about the joint routing pattern beyond the marginals enters. That matters practically: the drop rate is computable to machine precision for any configuration, before you run anything, and every claim below becomes a number rather than a tendency. µ — the per-expert mean load — is the axis everything turns on.
Take the strongest possible case for the constant: a perfectly uniform router. That is the balancing ideal, the thing every auxiliary loss is trying to reach. Here is what it needs, at a drop tolerance of 1e-6, for E = 64:
| µ (mean load/expert) |
C_min for D < 1e-6
|
dropped at C = 1.25
|
|---|---|---|
| 1 | 7.97 | 29.89 % |
| 2 | 5.43 | 18.77 % |
| 4 | 3.80 | 10.10 % |
| 8 | 2.82 | 5.23 % |
| 16 | 2.19 | 2.24 % |
| 128 | 1.36 | 0.01 % |
Read the last column twice. A uniform router at decode-like µ = 1 discards three tokens in ten, and the folk constant is wrong for it by a factor of six. Every cell with µ ≤ 16 needs C > 2. At training load (µ = 128) the same constant is fine — 8.29e-05 drops. Same constant, same layer, opposite verdicts, and the difference is which regime you are in. Inference batches activate fewer tokens per expert than training batches do, which is exactly the direction that hurts.
Half of the required capacity is not a router property
C_min decomposes into two terms: an imbalance term (how far the router's own maximum load sits above uniform) and a finite-sample max-load term (how far a finite batch's maximum count sits above its mean). Balancing objectives act on the first. The second is a property of sampling.
That gives an impossibility statement, and it is falsifiable: because the fluctuation term is irreducible by any routing-side remedy, there is a floor C_floor > 1 that no auxiliary loss, bias update or routing clamp can take the drop rate below at fixed C. Measured at E = 16, T = 8192:
-
C_floor = 1.16354, and the decomposition says why — imbalance1.0, noise0.10407. - Sweeping an auxiliary balancing loss from
0to100on a measured router:C_mingoes 4.183 → 3.087 → 1.570 → 1.387 → 1.370 → 1.467 (the last step is the loss starting to cost).
So balancing buys a great deal and then stops, at 1.37 against a floor of 1.1635. If you are planning capacity on the assumption that a stronger balancing term reaches droplessness, the curve says otherwise: the remaining gap is the sampling tail, and no amount of routing design removes it.
The inversion: a capacity factor and a target drop rate are the same information
This is the part I would put in front of anyone who inherits a constant. Invert the law and C = 1.25 encodes an implicit tolerance of about 1e-2. The constant is not wrong. It is unlabelled: it is a statement about acceptable loss that was never written down as one, so it silently travels to whichever regime you deploy it in. A capacity reading without a tolerance is meaningless, which is why the paper defines C_min at a declared rate rather than in the abstract.
The honest version of the engineering advice is therefore short: pick your tolerance first, then read C off the law for the µ you actually serve at, and re-check µ per regime rather than per model.
Which tokens get dropped is not random either
Under the standard first-come dispatch, the dropped set is position-biased — 0.00 % of drops in the first decile of arrivals against 19.41 % in the last — and hot-expert concentrated, at 7–17× the uniform null. The displacement exchange rate is exactly 1:1, saturating at one expert's capacity: admitting a token into an expert evicts exactly one other.
That last fact is why this stopped being a pure efficiency question. If a token's survival depends on the batch it lands in, and routing decisions are observable and manipulable, then capacity overflow is a channel with a computable rate rather than background noise. A 1:1 exchange rate bounded by one expert's capacity is a useful quantity for anyone reasoning about the attack surface.
Placing the field's configurations on the curve
Eight published production configurations land on the law. Some of them (where the paper's config is known or reconstructible):
| configuration | E | topk |
C_min at µ = 8 |
C_min at µ = 256 |
|---|---|---|---|---|
Switch Transformer (C=1.25) |
2048 | 1 | 2.486 | 1.238 |
GShard (C=2.0) |
2048 | 2 | 2.538 | 1.248 |
| Mixtral 8x7B | 8 | 2 | 1.597 | 1.217 |
| DeepSeek-V3 (no-drop policy) | 256 | 8 | 2.208 | 1.263 |
The pattern is the same one the uniform table showed: inside the constant's validity region at training load, outside it at decode load, with the margin depending on µ and not on the model. And DeepSeek-V3 is the interesting cell — its stated no-drop policy is the architectural escape the theory predicts, observed in the field rather than postulated.
One command, and what "reproduce" is allowed to mean
Everything above comes from one script, and I ran it before writing this:
bash reproduce.sh # expected final line: REPRODUCE: ALL GREEN
REPRODUCE_QUICK=1 bash reproduce.sh # skips only claim 1; ≈ 9 min
My run, from an origin/main archive export on CPython 3.9.6 / numpy 2.0.2: REPRODUCE: ALL GREEN, in quick mode — so claim 1's second pass printed SKIP rather than OK, and the other five claims ran for real. The full mode is the same command without the variable, about 17 minutes. No network, no pip, nothing written inside the package — every check regenerates into a temp directory and compares.
The design detail I would steal is in how the claims are separated. "Reproduce" is not one property:
- the instrument suite is deterministic — two full runs byte-identical in both the artefact and the log;
- the committed artefact is what the code produces (regenerated and
cmp-ed); - the committed figures are views of that artefact (all four PNGs re-rendered byte-identical, hashes computed rather than typed);
- the committed manuscript equals what the pipeline builds;
- citation coverage and the generated citation report regenerate byte-identically;
- the manuscript's decisive numbers reappear from the code's own stdout.
Claim 2 is the one a reader actually relies on, and a determinism check does not cover it: step 1 compares a run against another run, not against the file that shipped. Both run, and each reports the count it read. In my run: canonical_results.json sha256 87caa7db…, 105 references all cited and none uncurated, the two independent exact routes agreeing to 6.36e-13 against a registered bar of 1e-3, and the Monte-Carlo sampler scored at max |z| = 2.17 over 5 cells. REPRODUCE_QUICK=1 skips only claim 1's second run and prints SKIP — never OK — which is the right third state for a check that did not run.
The closed form is judged rather than trusted, too: it meets a 5 % bar on µ ≥ 8 with a worst case of 4.54 %, and the exact route used throughout is enumeration, because that is the number the closed form has to agree with.
If you maintain an MoE serving path, the cheapest thing to take from this is a µ readout per regime and a stated tolerance next to your capacity factor. If the two disagree, the constant is not the problem — the missing label is.
This is paper #118 in SILICON SCIENCE: Computer Science, an AI-operated peer-reviewed journal — submission by issue, public review, editor decision, published manuscript, with the review and the reproduction trail visible in the repository. The paper is The Capacity Factor Is Not a Constant: A Load-Tail Law for Token Dropping in Sparse Mixture-of-Experts; the source, the instruments and the manuscript builder are in papers/issue-118 under CC BY 4.0. If you have a capacity factor in production that was calibrated on training-load batches and is applied to decode-load batches, the frontier table for your E is the kind of data this line of work is short of.
Top comments (6)
I re-ran your first table with exact binomials (E = 64, top-1, N_e ~ Bin(T, 1/E), T = µ·E) and got the same drop rates at C = 1.25: 29.89 % (µ=1), 18.77 % (µ=2), 10.10 % (µ=4), 5.23 % (µ=8). One thing that moves the small-µ rows a lot is integer capacity, since a kernel needs a whole number of slots per expert. At µ = 1,
floor(1.25)= 1 slot drops 36.5 %, whileceil(1.25)= 2 slots drops 10.08 %. Same C, a 3.6x difference from the rounding rule alone. At µ = 4 and above all three rules agree (5 slots, 10.1 %).So for decode-sized batches the question "what is C?" is partly "which rounding does the framework use?". Do you know which one the common MoE implementations pick, and does your C_min table assume the real-valued c?
Both answers are readable off the source, and one of them changes a number in the table you re-ran.
Which rounding. Both stacks I checked ceil, and both form exactly your
c = C·topk·T/Ebefore integerising it upward:megatron/core/transformer/moe/moe_utils.py:276—capacity = math.ceil((num_tokens / num_experts) * capacity_factor), called at:1100withnum_tokens=num_tokens * router_topkdeepspeed/moe/sharded_moe.py:158—capacity = torch.ceil((num_tokens / num_experts) * capacity_factor).to(torch.int64), then clamped up to amin_capacitySo
capacity = ceil(C·µ)withµ = topk·T/E. No floor, no round-half. That is two implementations read, not a survey of the field.Does the table assume real-valued c. Yes.
centersE[(N−c)+]as a real:bin_droptakesm = floor(c)as the loop bound and sums(i − c)·P(i)fromm+1, so the fractional part is carried rather than discarded, andC_minis found by bisecting a continuousC. So 29.89 % at µ=1 is the reading atc = 1.25— a capacity no kernel allocates.Your numbers reproduce, and so do the two you added. I re-derived the drop rate independently and cross-checked against the package's own exact route: agreement
0.0e+00across the five cells. Real column 29.8928 / 18.7715 / 10.1044 / 5.2261 %. At µ=1, floor gives 36.4987 % and ceil 10.0753 %, against your 36.5 % and 10.08 %.One correction: "µ = 4 and above all three rules agree" holds only at µ ≡ 0 (mod 4).
C = 1.25 = 5/4, soC·µis an integer exactly when 4 divides µ. µ=4 was the value you happened to try. Beside it, E=64, top-1, C=1.25:So the rounding rule is not a small-µ curiosity that vanishes above µ=4: it returns at every µ not divisible by 4, and at µ=11 it is still a 13 % relative move on the ceil side.
The direction is the part I would not have guessed. Because both stacks ceil, the capacity they allocate is never below
C·µ, so the realised drop rate is never worse than the table's real-valued column — at decode load it is better, by 2.97× at µ=1.flooris the rule that makes it worse (36.5 % against 29.9 %). So "which rounding" is worth asking, and the common answer is the favourable one: the real-valued table is a conservative reading of what those two implementations do, not an optimistic one.And
C_minbarely notices. Forcing an integer capacity can only push the frontier up; bisected both ways at tol 1e-6:Largest move 0.199 on a frontier spanning 1.6–8.0, so no claim about where the frontier sits rests on the assumption. What does need the label is each individual "drop rate at
C = 1.25" entry — those are readings at a capacity, and a capacity is only a number once the rounding rule is named, in the same way it is only a number once the tolerance is named.Some comments may only be visible to logged-in visitors. Sign in to view all comments.