Mixture of Experts: the Sparsely-Gated Layer
Explained as a load balancer in front of eight microservices, because that is what it is.
Download moe_from_scratch.py — everything below runs from it. NumPy only.
I'm an AI Engineer. I've served Mixtral. I read "47B parameters, 13B active" on the model card, nodded, and could not have told you what "active" meant or where the other 34B went while a token was being processed. Then I looked at the paper's one diagram and recognised it. It's a load balancer.
An MoE layer is a load balancer in front of N services
You've deployed the first one, and when it got too big, the second:
def handle(request):
return monolith(request) # every request runs all of the code
def handle(request):
svc = router.pick(request) # the load balancer chooses ONE backend
return svc(request) # the other N-1 backends do nothing
That's the layer. The "experts" are N copies of the transformer's feed-forward block — the two-layer MLP I called "wrapping" on the attention page — each with its own weights. The router is one matrix and a softmax. The paper's one twist: pick the top k backends, not one, and blend their answers by confidence.
def moe(x):
scores = softmax(x @ W_g) # how well does x fit each expert?
top = argsort(-scores)[:k] # keep the k best
gates = scores[top] / scores[top].sum() # renormalise: they sum to 1 again
return sum(g * expert[i](x) for g, i in zip(gates, top))
Four lines. Everything else in both papers is what goes wrong in production.
| Term | Means | Ops analogy |
|---|---|---|
| Expert | A plain 2-layer MLP. N of them, same shape, different weights | A backend service. Same code, different config |
| Router (gate) | softmax(x @ Wg): one score per expert, per token | The load balancer's routing rule, except it's learned |
| Top-k | Keep the k best scores, zero the rest. The kept ones are the gate values | The k backends actually called, and their weights |
| Auxiliary loss | A penalty for uneven routing | The alert that fires on a hot shard |
| Capacity factor | Max tokens per expert per batch (Switch) | A rate limit per backend. Overflow is dropped |
Shazeer's paper used this to train 137 billion parameters — between LSTM layers, months before Attention Is All You Need — and beat the state of the art on language modelling at lower compute. GShard (2020) and Switch (2021) moved it into the transformer in place of the FFN; Mixtral 8x7B is a LLaMA-shaped model with exactly that swap.
The one formula
G(x) = softmax(KeepTopK(x·Wg + noise, k))
Read the second line inside-out, as pipeline stages:
| Step | Shape | What it does |
|---|---|---|
x·Wg | (n, N) | Score matrix. n tokens in, one score per expert out. Cell [t][i] = how well token t fits expert i |
+ noise | (n, N) | Training only. N(0,1) × softplus(x·Wnoise): jitter, so the same k don't win every time |
KeepTopK | (n, N) | Keep the k largest per row, set the rest to −∞ |
softmax | row-wise | The k kept become percentages summing to 1; −∞ becomes exactly 0, so no call. Softmax first and renormalise after, as my script does, gives the same numbers |
Ei(x) | (n, d) | Expert i's MLP, run only on the tokens that chose it |
Σ G·E | (n, d) | Blend the k outputs. Same shape in and out: a drop-in for the FFN |
The router: a load balancer whose rules are learned
Sixteen tokens, eight experts, top-2. Token 0 step by step, then the first six rows of the routing table (the script prints all sixteen).
Don't take my word for it. Here's the actual output:
token 0, router probabilities over all 8 experts:
[0.012 0.052 0.093 0.232 0.021 0.036 0.39 0.163] sum 1.000
top-2 = experts [6, 3], probs [0.39 0.232], sum 0.622
renormalised gates [0.627 0.373], sum 1.000 ✓
routing table:
token 1st gate 2nd gate
0 E6 0.627 E3 0.373
1 E6 0.545 E0 0.455
2 E0 0.615 E1 0.385
3 E4 0.650 E1 0.350
4 E0 0.532 E5 0.468
5 E4 0.728 E2 0.272
tokens per expert: E0:5 E1:3 E2:4 E3:2 E4:2 E5:5 E6:8 E7:3
total assignments: 32 = 16 tokens x k=2
Every token got exactly two experts; the other six got a gate of exactly 0 for it. Zero gate, no call; no call, no compute. That's the whole trick. Now look at E6: eight of 32 assignments at random init, before any training, while E3 and E4 got two each. Hold that thought.
The + noise in the formula is Shazeer's fix for a ranking that
freezes: Gaussian jitter on each logit, with a learned scale, so the same k don't
win every time. Like dropout, it's a training-time switch. Leave it on and:
noise off, two runs: outputs identical? True
noise on, two runs: outputs identical? False
max |y1 - y2| = 2.199; 10 of 16 tokens changed at least one expert
Eight times the parameters, twice the compute
A dense FFN is two matrices of d×h. Eight experts is eight times that, plus a d×N router that rounds to nothing. But a token only visits k experts, so its FLOPs are k FFNs, not N. Decoupling how much the model knows from how much work each token does is the entire reason the paper exists.
d_model=1024, ffn hidden=4096, experts=8, top-2
dense FFN params: 8,388,608
MoE params (8 experts): 67,117,056 8.00x
FLOPs per token as k grows (params stay at 8.00x):
top-1: 16,793,600 1.00x
top-2: 33,570,816 2.00x
top-4: 67,125,248 4.00x
top-8: 134,234,112 8.00x <- k = N: a dense layer, 8x the compute
The question you will be asked: "Mixtral is 8x7B, so why is it 47B and not 56B?" Because only the FFN is replicated. Attention, embeddings and norms are shared, and every token pays for them once. Counting Mixtral's expert weights from its config (SwiGLU experts, three matrices each, 32 layers):
Mixtral 8x7B config, expert weights only (SwiGLU: 3 matrices each):
one expert: 176,160,768
all experts, all layers: 45,097,156,608 ~45.1B
active per token (top-2): 11,274,289,152 ~11.3B
Mistral quotes roughly 47B total and 13B active; the missing ≈1.6B in each column
is the shared attention, embeddings and norms. So a token does the work of a 13B
model but needs the memory of a 47B one: every expert is loaded whether
it runs or not. You pay for eight in VRAM and use two. That's the MLOps question
hiding behind the interview one. The script also checks the layer's output shape,
(16, 8), against the dense FFN's, and token 0's row against
0.627 × E6(x) + 0.373 × E3(x) done by hand: True.
Hot shards: the load-balancing loss
The failure every ops person has met. One expert is slightly better at random init, so the router sends it more; it gets more gradient, gets better, gets sent more. The others get no tokens, no gradient, no improvement. One hot shard, seven idle machines you're still paying for. The paper calls it self-reinforcing.
The fix is a second loss term. Shazeer's paper uses two: importance, the squared coefficient of variation (std ÷ mean) of each expert's summed gates over the batch, and load, the same on a smoothed estimate of token counts. Switch replaces both with one line, N × Σi fi · Pi: fi the fraction of tokens sent to expert i, Pi the router's mean probability for it. A count has no gradient; a probability does; the product pushes P down wherever the count is high. Sixty-four tokens, top-1, an untouched router and then one I biased by hand towards E3:
random-init router (nobody touched it):
tokens per expert: E0:12 E1:6 E2:1 E3:10 E4:8 E5:11 E6:14 E7:2
importance loss (Shazeer, CV^2): 0.301
balance loss (Switch, N * sum f*P): 1.078
collapsed router (logit bias +8 on E3, nothing to stop it):
tokens per expert: E0:0 E1:0 E2:0 E3:64 E4:0 E5:0 E6:0 E7:0
importance loss (Shazeer, CV^2): 7.000
balance loss (Switch, N * sum f*P): 7.956
The Switch loss is 1.0 when every fi and Pi is 1/N and climbs towards N when one expert takes everything. Even the untouched router sits at 1.078: random init is not balanced — E2 got one token, E6 got fourteen. The paper scales it by α = 0.01 before adding it. And why idle experts stay idle, with no autograd: add 1.0 to every weight in E5 and ask whether the output noticed.
E5 received 0 tokens. Add 1.0 to every weight in E5:
output changed? False -> dLoss/dW for E5 is exactly 0. E5 never trains.
bias argument I used to break the router, driven by a
controller rather than a gradient.
Switch: top-1, one loss, and a rate limit
Shazeer's paper used k between 2 and 4 and argued the router needs two experts per token to compare. The Switch Transformer (Fedus, Zoph and Shazeer, 2021) tried k=1 anyway, found quality held, and kept it: half the expert compute per token, simpler dispatch, the single loss above. With the same FLOPs per token as T5-Base it reaches the same quality up to 7× faster, and the recipe scaled to 1.6 trillion parameters. Every modern MoE LLM descends from this version, with k back at 2 for Mixtral.
Its second contribution is the rate limit. Experts run as fixed-size batches on fixed hardware, so each gets a capacity: (tokens ÷ N) × a capacity factor. Tokens beyond it are dropped from the layer — 0 from the experts; the residual connection carries them through. Nothing raises. Nothing logs.
64 tokens, 8 experts, top-1. capacity = floor(factor * 64 / 8)
random-init router: factor 1.0 capacity 8 -> dropped 15 of 64
random-init router: factor 1.25 capacity 10 -> dropped 7 of 64
random-init router: factor 2.0 capacity 16 -> dropped 0 of 64
collapsed router: factor 1.0 capacity 8 -> dropped 56 of 64
collapsed router: factor 1.25 capacity 10 -> dropped 54 of 64
collapsed router: factor 2.0 capacity 16 -> dropped 48 of 64
Fifteen of 64 tokens silently skipped the layer at factor 1.0, on a router nobody had biased. That's the trade-off in the paper's capacity table: a higher factor drops fewer tokens but pads every expert's batch to the cap, so you pay compute for empty slots. Switch settles on 1.0 to 1.25; "dropless" MoE (MegaBlocks) came later.
நான் ஒரு AI Engineer. Mixtral-ஐ serve செய்திருக்கிறேன். Model card-ல் "47B parameters, 13B active" என்று படித்து, தலையாட்டினேன் — ஆனால் "active" என்றால் என்ன, ஒரு token process ஆகும்போது மீதி 34B எங்கே போகிறது என்று என்னால் சொல்ல முடியவில்லை. பிறகு paper-ன் ஒரே diagram-ஐப் பார்த்தேன். அது எனக்குத் தெரிந்த ஒன்றுதான். அது ஒரு load balancer.
ஒரு MoE layer என்பது N services-க்கு முன்னால் இருக்கும் load balancer
முதலாவதை நீங்கள் deploy செய்திருக்கிறீர்கள்; அது பெரிதானபோது இரண்டாவதை:
def handle(request):
return monolith(request) # ஒவ்வொரு request-ம் முழு code-ஐயும் ஓட்டும்
def handle(request):
svc = router.pick(request) # load balancer ஒரே ஒரு backend-ஐத் தேர்கிறது
return svc(request) # மீதி N-1 backends ஒன்றும் செய்வதில்லை
அவ்வளவுதான் அந்த layer. "Experts" என்பவை transformer-ன் feed-forward block-ன் N நகல்கள் — attention page-ல் நான் "wrapping" என்று சொன்ன இரண்டு-layer MLP — ஒவ்வொன்றுக்கும் தனித்தனி weights. Router என்பது ஒரே ஒரு matrix-ம் ஒரு softmax-ம். Paper-ன் ஒரே திருப்பம்: ஒன்றல்ல, top k backends-ஐத் தேர்ந்து, அவற்றின் விடைகளை நம்பிக்கை அளவின்படி கலக்குங்கள்.
def moe(x):
scores = softmax(x @ W_g) # x எந்த expert-க்குப் பொருந்தும்?
top = argsort(-scores)[:k] # சிறந்த k-ஐ வைத்துக்கொள்
gates = scores[top] / scores[top].sum() # renormalise: மீண்டும் கூட்டினால் 1
return sum(g * expert[i](x) for g, i in zip(gates, top))
நான்கு வரிகள். இரு papers-லும் மீதி எல்லாம் production-ல் என்ன கெடுகிறது என்பதுதான்.
MoE layer-nu solradhu N backend services-kku munnadi oru load balancer. Router thaan traffic-a route pannudhu; oru token top-k experts-a mattum visit pannudhu, meethi ellaam idle-a irukku.
| சொல் | அர்த்தம் | Ops உதாரணம் |
|---|---|---|
| Expert | சாதாரண 2-layer MLP. N எண்ணிக்கை, ஒரே shape, வெவ்வேறு weights | ஒரு backend service. ஒரே code, வெவ்வேறு config |
| Router (gate) | softmax(x @ Wg): ஒவ்வொரு token-க்கும், ஒவ்வொரு expert-க்கும் ஒரு score | Load balancer-ன் routing rule — ஆனால் இது கற்றுக்கொள்ளப்படுகிறது |
| Top-k | சிறந்த k scores-ஐ வைத்து மீதியை 0 ஆக்கு. வைத்தவையே gate values | உண்மையில் அழைக்கப்படும் k backends, அவற்றின் weights |
| Auxiliary loss | சமமில்லாத routing-க்கு ஒரு அபராதம் | Hot shard-ல் வெடிக்கும் alert |
| Capacity factor | ஒரு batch-ல் ஒரு expert-க்கு அதிகபட்ச tokens (Switch) | ஒவ்வொரு backend-க்கும் rate limit. Overflow drop ஆகும் |
Shazeer-ன் paper இதைக் கொண்டு 137 billion parameters-ஐ train செய்தது — LSTM layers-க்கு இடையில், Attention Is All You Need வருவதற்குச் சில மாதங்கள் முன்பு — குறைந்த compute-ல் language modelling-ல் அப்போதைய சிறந்த முடிவை வென்றது. GShard (2020) மற்றும் Switch (2021) இதை transformer-க்குள் FFN-ன் இடத்தில் கொண்டு வந்தன; Mixtral 8x7B என்பது சரியாக அந்த மாற்றத்துடன் கூடிய ஒரு LLaMA-வடிவ model.
அந்த ஒரு சூத்திரம்
G(x) = softmax(KeepTopK(x·Wg + noise, k))
இரண்டாவது வரியை உள்ளிருந்து வெளியே, pipeline stages போலப் படியுங்கள்:
| படி | Shape | என்ன செய்கிறது |
|---|---|---|
x·Wg | (n, N) | Score matrix. உள்ளே n tokens, வெளியே ஒவ்வொரு expert-க்கும் ஒரு score. Cell [t][i] = token t, expert i-க்கு எவ்வளவு பொருந்தும் |
+ noise | (n, N) | Training-ல் மட்டும். N(0,1) × softplus(x·Wnoise): ஒரு அசைவு, ஒரே k எப்போதும் வெல்லாமல் இருக்க |
KeepTopK | (n, N) | ஒவ்வொரு row-லும் பெரிய k-ஐ வைத்து மீதியை −∞ ஆக்கு |
softmax | row-வாரியாக | வைத்த k சதவீதங்களாகி 1-ஆகக் கூடும்; −∞ சரியாக 0 ஆகும், அதனால் அழைப்பே இல்லை. என் script செய்வது போல முதலில் softmax, பிறகு renormalise — அதே எண்கள்தான் |
Ei(x) | (n, d) | Expert i-ன் MLP, அதைத் தேர்ந்தெடுத்த tokens-க்கு மட்டும் ஓடும் |
Σ G·E | (n, d) | k outputs-ஐக் கலக்கு. உள்ளேயும் வெளியேயும் ஒரே shape: FFN-க்கு நேரடி மாற்று |
Router: rules கற்றுக்கொள்ளப்படும் ஒரு load balancer
பதினாறு tokens, எட்டு experts, top-2. முதலில் token 0-ஐ படிப்படியாக, பிறகு routing table-ன் முதல் ஆறு rows (script பதினாறையும் print செய்யும்).
என் வார்த்தையை நம்ப வேண்டாம். இதோ உண்மையான output:
token 0, router probabilities over all 8 experts:
[0.012 0.052 0.093 0.232 0.021 0.036 0.39 0.163] sum 1.000
top-2 = experts [6, 3], probs [0.39 0.232], sum 0.622
renormalised gates [0.627 0.373], sum 1.000 ✓
routing table:
token 1st gate 2nd gate
0 E6 0.627 E3 0.373
1 E6 0.545 E0 0.455
2 E0 0.615 E1 0.385
3 E4 0.650 E1 0.350
4 E0 0.532 E5 0.468
5 E4 0.728 E2 0.272
tokens per expert: E0:5 E1:3 E2:4 E3:2 E4:2 E5:5 E6:8 E7:3
total assignments: 32 = 16 tokens x k=2
ஒவ்வொரு token-க்கும் சரியாக இரண்டு experts; மீதி ஆறுக்கும் அந்த token-க்கான gate சரியாக 0. Gate 0 என்றால் அழைப்பு இல்லை; அழைப்பு இல்லை என்றால் compute இல்லை. அதுதான் முழு தந்திரம். இப்போது E6-ஐப் பாருங்கள்: எந்த training-ம் இல்லாத random init-லேயே 32 assignments-ல் எட்டு, அதே சமயம் E3-க்கும் E4-க்கும் தலா இரண்டு. இதை மனதில் வையுங்கள்.
சூத்திரத்தில் உள்ள + noise என்பது ranking உறைந்து போவதற்கு
Shazeer கொடுக்கும் தீர்வு: ஒவ்வொரு logit-ன் மேலும் Gaussian அசைவு, ஒரு
கற்ற scale-உடன், அதனால் ஒரே k எப்போதும் வெல்லாது. Dropout போல, இது
training-நேர switch. அதை on-ல் விட்டுவிட்டால்:
noise off, two runs: outputs identical? True
noise on, two runs: outputs identical? False
max |y1 - y2| = 2.199; 10 of 16 tokens changed at least one expert
எட்டு மடங்கு parameters, இரு மடங்கு compute
ஒரு dense FFN என்பது d×h அளவுள்ள இரு matrices. எட்டு experts என்றால் அதன் எட்டு மடங்கு, கூடவே ஒரு d×N router — அது கணக்கிலேயே வராது. ஆனால் ஒரு token k experts-ஐ மட்டுமே சந்திக்கிறது, அதனால் அதன் FLOPs N FFNs அல்ல, k FFNs. Model எவ்வளவு அறிகிறது என்பதையும், ஒவ்வொரு token எவ்வளவு வேலை செய்கிறது என்பதையும் பிரிப்பதுதான் இந்த paper இருப்பதற்கான முழுக் காரணம்.
d_model=1024, ffn hidden=4096, experts=8, top-2
dense FFN params: 8,388,608
MoE params (8 experts): 67,117,056 8.00x
FLOPs per token as k grows (params stay at 8.00x):
top-1: 16,793,600 1.00x
top-2: 33,570,816 2.00x
top-4: 67,125,248 4.00x
top-8: 134,234,112 8.00x <- k = N: a dense layer, 8x the compute
உங்களிடம் கேட்கப்படும் கேள்வி: "Mixtral 8x7B தானே, பிறகு ஏன் 56B இல்லாமல் 47B?" ஏனென்றால் FFN மட்டுமே நகலெடுக்கப்படுகிறது. Attention, embeddings, norms எல்லாம் பகிரப்படுகின்றன, ஒவ்வொரு token-ம் அவற்றுக்கு ஒரு முறை மட்டும் செலவழிக்கிறது. Mixtral-ன் config-லிருந்து அதன் expert weights-ஐக் கணக்கிட்டால் (SwiGLU experts, தலா மூன்று matrices, 32 layers):
Mixtral 8x7B config, expert weights only (SwiGLU: 3 matrices each):
one expert: 176,160,768
all experts, all layers: 45,097,156,608 ~45.1B
active per token (top-2): 11,274,289,152 ~11.3B
Mistral சுமார் 47B total, 13B active என்று சொல்கிறது; ஒவ்வொரு column-லும்
விடுபடும் ≈1.6B என்பது பகிரப்பட்ட attention, embeddings, norms. அதாவது ஒரு
token 13B model-ன் வேலையைச் செய்கிறது, ஆனால் அதற்கு 47B model-ன்
memory வேண்டும்: ஓடினாலும் ஓடாவிட்டாலும் ஒவ்வொரு expert-ம்
load ஆகியிருக்கும். VRAM-ல் எட்டுக்குக் காசு கொடுத்து இரண்டைப்
பயன்படுத்துகிறீர்கள். Interview கேள்விக்குப் பின்னால் ஒளிந்திருக்கும் MLOps
கேள்வி அதுதான். Script layer-ன் output shape (16, 8)-ஐ dense FFN-னுடையதோடும்,
token 0-ன் row-ஐ கையால் செய்த
0.627 × E6(x) + 0.373 × E3(x)-உடனும் சரிபார்க்கிறது: True.
Params 8x aana FLOPs per token 2x mattum dhaan — token k experts-a mattum visit pannudhu. Aana memory-la ellaa experts-um load aagum: 13B velai, 47B VRAM. Adhu dhaan MLOps question.
Hot shards: load-balancing loss
ஒவ்வொரு ops நபரும் சந்தித்த தோல்வி. Random init-ல் ஒரு expert சற்றே சிறப்பாக இருக்கிறது, அதனால் router அதற்கு அதிகம் அனுப்புகிறது; அதிக gradient கிடைக்கிறது, மேலும் சிறப்படைகிறது, மேலும் அனுப்பப்படுகிறது. மற்றவற்றுக்கு tokens இல்லை, gradient இல்லை, முன்னேற்றம் இல்லை. ஒரு hot shard, நீங்கள் காசு கொடுத்துக்கொண்டிருக்கும் ஏழு சும்மா machines. இதை paper "self-reinforcing" என்கிறது.
தீர்வு இரண்டாவது loss term. Shazeer-ன் paper இரண்டைப் பயன்படுத்துகிறது: importance — batch முழுவதும் ஒவ்வொரு expert-ன் gates கூட்டுத் தொகையின் coefficient of variation-ன் (std ÷ mean) வர்க்கம்; மற்றும் load — token counts-ன் ஒரு மென்மையாக்கப்பட்ட மதிப்பீட்டின் மேல் அதே கணக்கு. Switch இரண்டையும் ஒரே வரியால் மாற்றுகிறது, N × Σi fi · Pi: fi என்பது expert i-க்கு அனுப்பப்பட்ட tokens-ன் விகிதம், Pi என்பது அதற்கான router-ன் சராசரி probability. Count-க்கு gradient இல்லை; probability-க்கு உண்டு; count அதிகமாக இருக்கும் இடத்தில் இந்தப் பெருக்கல் P-ஐக் கீழே தள்ளுகிறது. அறுபத்து நான்கு tokens, top-1, தொடாத ஒரு router, பிறகு நான் கையால் E3 பக்கம் சாய்த்த ஒன்று:
random-init router (nobody touched it):
tokens per expert: E0:12 E1:6 E2:1 E3:10 E4:8 E5:11 E6:14 E7:2
importance loss (Shazeer, CV^2): 0.301
balance loss (Switch, N * sum f*P): 1.078
collapsed router (logit bias +8 on E3, nothing to stop it):
tokens per expert: E0:0 E1:0 E2:0 E3:64 E4:0 E5:0 E6:0 E7:0
importance loss (Shazeer, CV^2): 7.000
balance loss (Switch, N * sum f*P): 7.956
ஒவ்வொரு fi-ம் Pi-ம் 1/N ஆக இருக்கும்போது Switch loss 1.0; ஒரு expert எல்லாவற்றையும் எடுத்துக்கொள்ளும்போது அது N-ஐ நோக்கி ஏறுகிறது. தொடாத router கூட 1.078-ல் உட்கார்ந்திருக்கிறது: random init சமநிலை அல்ல — E2-க்கு ஒரு token, E6-க்கு பதினான்கு. Paper இதைக் கூட்டுவதற்கு முன் α = 0.01-ஆல் பெருக்குகிறது. சும்மா இருக்கும் experts ஏன் சும்மாவே இருக்கின்றன என்பதை autograd இல்லாமல் பார்க்க: E5-ன் ஒவ்வொரு weight-ஓடும் 1.0-ஐக் கூட்டி, output கவனித்ததா என்று கேளுங்கள்.
E5 received 0 tokens. Add 1.0 to every weight in E5:
output changed? False -> dLoss/dW for E5 is exactly 0. E5 never trains.
bias argument, gradient-க்குப்
பதிலாக ஒரு controller-ஆல் இயக்கப்படுகிறது.
Switch: top-1, ஒரே loss, ஒரு rate limit
Shazeer-ன் paper k-ஐ 2-க்கும் 4-க்கும் இடையில் வைத்து, ஒப்பிடுவதற்கு router-க்கு ஒரு token-க்கு இரு experts தேவை என்று வாதிட்டது. Switch Transformer (Fedus, Zoph, Shazeer, 2021) k=1-ஐ முயற்சித்தது, தரம் குறையவில்லை என்று கண்டு, அதையே வைத்துக்கொண்டது: ஒரு token-க்கு பாதி expert compute, எளிய dispatch, மேலே சொன்ன ஒரே loss. T5-Base-ன் அதே FLOPs per token-உடன் அதே தரத்தை 7× வேகத்தில் அடைகிறது, அந்த recipe 1.6 trillion parameters வரை scale ஆனது. இன்றைய ஒவ்வொரு MoE LLM-ம் இந்தப் பதிப்பிலிருந்து வந்ததுதான் — Mixtral-ல் k மீண்டும் 2-ஆக.
அதன் இரண்டாவது பங்களிப்பு rate limit. Experts நிலையான hardware-ல் நிலையான அளவு batches-ஆக ஓடுகின்றன, அதனால் ஒவ்வொன்றுக்கும் ஒரு capacity: (tokens ÷ N) × ஒரு capacity factor. அதற்கு மேல் வரும் tokens layer-லிருந்து drop ஆகின்றன — experts-லிருந்து 0; residual connection அவற்றைத் தூக்கிச் செல்கிறது. எதுவும் raise ஆகாது. எதுவும் log ஆகாது.
64 tokens, 8 experts, top-1. capacity = floor(factor * 64 / 8)
random-init router: factor 1.0 capacity 8 -> dropped 15 of 64
random-init router: factor 1.25 capacity 10 -> dropped 7 of 64
random-init router: factor 2.0 capacity 16 -> dropped 0 of 64
collapsed router: factor 1.0 capacity 8 -> dropped 56 of 64
collapsed router: factor 1.25 capacity 10 -> dropped 54 of 64
collapsed router: factor 2.0 capacity 16 -> dropped 48 of 64
யாரும் bias செய்யாத ஒரு router-ல், factor 1.0-ல் 64 tokens-ல் பதினைந்து சத்தமில்லாமல் layer-ஐத் தவிர்த்துச் சென்றன. Paper-ன் capacity table-ல் இருக்கும் trade-off அதுதான்: அதிக factor குறைவான tokens-ஐ drop செய்யும், ஆனால் ஒவ்வொரு expert-ன் batch-ஐயும் cap வரை நிரப்பும், அதனால் காலி slots-க்கும் compute கொடுக்கிறீர்கள். Switch 1.0-க்கும் 1.25-க்கும் இடையில் நிற்கிறது; "dropless" MoE (MegaBlocks) அதற்குப் பிறகு வந்தது.
मैं एक AI Engineer हूँ। मैंने Mixtral serve किया है। Model card पर "47B parameters, 13B active" पढ़ा, सिर हिलाया, और यह नहीं बता सकता था कि "active" का मतलब क्या है या जब एक token process हो रहा होता है तब बाक़ी 34B कहाँ रहते हैं। फिर मैंने paper का इकलौता diagram देखा और पहचान गया। यह एक load balancer है।
MoE layer यानी N services के आगे लगा load balancer
पहला आपने deploy किया है, और जब वह बहुत बड़ा हो गया, तब दूसरा:
def handle(request):
return monolith(request) # हर request पूरा code चलाता है
def handle(request):
svc = router.pick(request) # load balancer एक ही backend चुनता है
return svc(request) # बाक़ी N-1 backends कुछ नहीं करते
बस यही है वह layer। "Experts" transformer के feed-forward block की N copies हैं — वही दो-layer MLP जिसे मैंने attention page पर "wrapping" कहा था — हर एक के अपने weights। Router एक matrix और एक softmax है। Paper का इकलौता twist: एक नहीं, top k backends चुनो और उनके जवाबों को confidence के हिसाब से mix करो।
def moe(x):
scores = softmax(x @ W_g) # x हर expert पर कितना fit बैठता है?
top = argsort(-scores)[:k] # सबसे अच्छे k रखो
gates = scores[top] / scores[top].sum() # renormalise: जोड़ फिर से 1
return sum(g * expert[i](x) for g, i in zip(gates, top))
चार लाइनें। दोनों papers में बाक़ी सब यही है कि production में क्या बिगड़ता है।
| शब्द | मतलब | Ops उदाहरण |
|---|---|---|
| Expert | सादा 2-layer MLP। ऐसे N, एक ही shape, अलग weights | एक backend service। एक ही code, अलग config |
| Router (gate) | softmax(x @ Wg): हर token के लिए, हर expert का एक score | Load balancer का routing rule, फ़र्क़ बस यह कि यह सीखा जाता है |
| Top-k | सबसे अच्छे k scores रखो, बाक़ी शून्य। जो रखे गए वही gate values हैं | असल में जिन k backends को call किया गया, और उनके weights |
| Auxiliary loss | असमान routing पर जुर्माना | Hot shard पर बजने वाला alert |
| Capacity factor | प्रति batch, प्रति expert अधिकतम tokens (Switch) | हर backend पर rate limit। Overflow drop हो जाता है |
Shazeer के paper ने इससे 137 billion parameters train किए — LSTM layers के बीच, Attention Is All You Need से कुछ महीने पहले — और कम compute पर language modelling का तत्कालीन best result हराया। GShard (2020) और Switch (2021) इसे transformer के अंदर FFN की जगह ले आए; Mixtral 8x7B ठीक इसी अदला-बदली वाला एक LLaMA-आकार का model है।
वह एक formula
G(x) = softmax(KeepTopK(x·Wg + noise, k))
दूसरी लाइन को अंदर से बाहर, pipeline stages की तरह पढ़िए:
| चरण | Shape | यह क्या करता है |
|---|---|---|
x·Wg | (n, N) | Score matrix. अंदर n tokens, बाहर हर expert का एक score. Cell [t][i] = token t, expert i पर कितना fit बैठता है |
+ noise | (n, N) | सिर्फ़ training में। N(0,1) × softplus(x·Wnoise): थोड़ी हलचल, ताकि हर बार वही k न जीतें |
KeepTopK | (n, N) | हर row में सबसे बड़े k रखो, बाक़ी को −∞ कर दो |
softmax | row-wise | रखे हुए k प्रतिशत बन जाते हैं जिनका जोड़ 1 है; −∞ ठीक 0 हो जाता है, यानी कोई call नहीं। पहले softmax फिर renormalise — जैसा मेरा script करता है — वही संख्याएँ देता है |
Ei(x) | (n, d) | Expert i का MLP, सिर्फ़ उन्हीं tokens पर चलता है जिन्होंने उसे चुना |
Σ G·E | (n, d) | k outputs को mix करो। अंदर-बाहर एक ही shape: FFN के लिए drop-in |
Router: वह load balancer जिसके rules सीखे जाते हैं
सोलह tokens, आठ experts, top-2। पहले token 0 कदम-दर-कदम, फिर routing table की पहली छह rows (script सोलहों print करता है)।
मेरी बात पर मत जाइए। यह रहा असली output:
token 0, router probabilities over all 8 experts:
[0.012 0.052 0.093 0.232 0.021 0.036 0.39 0.163] sum 1.000
top-2 = experts [6, 3], probs [0.39 0.232], sum 0.622
renormalised gates [0.627 0.373], sum 1.000 ✓
routing table:
token 1st gate 2nd gate
0 E6 0.627 E3 0.373
1 E6 0.545 E0 0.455
2 E0 0.615 E1 0.385
3 E4 0.650 E1 0.350
4 E0 0.532 E5 0.468
5 E4 0.728 E2 0.272
tokens per expert: E0:5 E1:3 E2:4 E3:2 E4:2 E5:5 E6:8 E7:3
total assignments: 32 = 16 tokens x k=2
हर token को ठीक दो experts मिले; बाक़ी छह के लिए उसका gate ठीक 0 था। Gate शून्य तो call नहीं; call नहीं तो compute नहीं। पूरा खेल यही है। अब E6 देखिए: बिना किसी training के, random init पर ही, 32 assignments में से आठ — जबकि E3 और E4 को दो-दो मिले। यह बात याद रखिए।
Formula में जो + noise है, वह जमी हुई ranking के लिए Shazeer
का इलाज है: हर logit पर Gaussian हलचल, एक सीखे हुए scale के साथ, ताकि हर
बार वही k न जीतें। Dropout की तरह यह training-time switch है। इसे चालू
छोड़ दीजिए और:
noise off, two runs: outputs identical? True
noise on, two runs: outputs identical? False
max |y1 - y2| = 2.199; 10 of 16 tokens changed at least one expert
आठ गुना parameters, दो गुना compute
Dense FFN यानी d×h की दो matrices। आठ experts यानी उसका आठ गुना, साथ में एक d×N router जो गिनती में ही नहीं आता। पर एक token सिर्फ़ k experts तक जाता है, इसलिए उसके FLOPs N FFNs नहीं, k FFNs हैं। Model कितना जानता है और हर token कितना काम करता है — इन दोनों को अलग कर देना ही इस paper के होने की पूरी वजह है।
d_model=1024, ffn hidden=4096, experts=8, top-2
dense FFN params: 8,388,608
MoE params (8 experts): 67,117,056 8.00x
FLOPs per token as k grows (params stay at 8.00x):
top-1: 16,793,600 1.00x
top-2: 33,570,816 2.00x
top-4: 67,125,248 4.00x
top-8: 134,234,112 8.00x <- k = N: a dense layer, 8x the compute
आपसे यह पूछा जाएगा: "Mixtral 8x7B है, तो 56B क्यों नहीं, 47B क्यों?" क्योंकि सिर्फ़ FFN की copies बनती हैं। Attention, embeddings और norms साझा हैं, और हर token उनकी क़ीमत एक ही बार चुकाता है। Mixtral के config से उसके expert weights गिनें (SwiGLU experts, हर एक में तीन matrices, 32 layers):
Mixtral 8x7B config, expert weights only (SwiGLU: 3 matrices each):
one expert: 176,160,768
all experts, all layers: 45,097,156,608 ~45.1B
active per token (top-2): 11,274,289,152 ~11.3B
Mistral क़रीब 47B total और 13B active बताता है; हर column में जो ≈1.6B
छूट रहा है वह साझा attention, embeddings और norms हैं। यानी एक token काम
तो 13B model जितना करता है, पर उसे memory 47B वाली चाहिए: हर
expert load रहता है, चले या न चले। VRAM में आठ के पैसे देकर आप दो
इस्तेमाल करते हैं। Interview वाले सवाल के पीछे छिपा MLOps सवाल यही है।
Script layer के output की shape (16, 8) को dense FFN वाली से, और token 0
की row को हाथ से किए गए
0.627 × E6(x) + 0.373 × E3(x) से भी मिलाता है: True।
Hot shards: load-balancing loss
वह failure जिससे हर ops वाला मिल चुका है। Random init पर एक expert ज़रा-सा बेहतर निकलता है, तो router उसे ज़्यादा भेजता है; उसे ज़्यादा gradient मिलता है, वह और बेहतर होता है, और ज़्यादा भेजा जाता है। बाक़ियों को न tokens, न gradient, न सुधार। एक hot shard, और सात खाली machines जिनका किराया आप फिर भी दे रहे हैं। Paper इसे self-reinforcing कहता है।
इलाज है एक दूसरा loss term। Shazeer का paper दो इस्तेमाल करता है: importance — पूरे batch में हर expert के जोड़े हुए gates के coefficient of variation (std ÷ mean) का वर्ग; और load — token counts के एक smooth अनुमान पर वही गणना। Switch दोनों की जगह एक लाइन रखता है, N × Σi fi · Pi: fi यानी expert i को भेजे गए tokens का हिस्सा, Pi यानी उसके लिए router की औसत probability। Count का gradient नहीं होता; probability का होता है; यह गुणा वहाँ P को नीचे धकेलता है जहाँ count ज़्यादा है। चौंसठ tokens, top-1, एक अनछुआ router और फिर एक जिसे मैंने हाथ से E3 की तरफ़ झुका दिया:
random-init router (nobody touched it):
tokens per expert: E0:12 E1:6 E2:1 E3:10 E4:8 E5:11 E6:14 E7:2
importance loss (Shazeer, CV^2): 0.301
balance loss (Switch, N * sum f*P): 1.078
collapsed router (logit bias +8 on E3, nothing to stop it):
tokens per expert: E0:0 E1:0 E2:0 E3:64 E4:0 E5:0 E6:0 E7:0
importance loss (Shazeer, CV^2): 7.000
balance loss (Switch, N * sum f*P): 7.956
जब हर fi और Pi 1/N हो तो Switch loss 1.0 रहता है, और जब एक expert सब कुछ ले ले तो N की तरफ़ चढ़ता है। अनछुआ router भी 1.078 पर बैठा है: random init संतुलित नहीं होता — E2 को एक token मिला, E6 को चौदह। Paper इसे जोड़ने से पहले α = 0.01 से गुणा करता है। और खाली experts खाली ही क्यों रह जाते हैं, वह बिना autograd के देखिए: E5 के हर weight में 1.0 जोड़ दीजिए और पूछिए कि output ने कुछ महसूस किया या नहीं।
E5 received 0 tokens. Add 1.0 to every weight in E5:
output changed? False -> dLoss/dW for E5 is exactly 0. E5 never trains.
bias argument जिससे मैंने
router तोड़ा था, बस gradient की जगह एक controller उसे चलाता है।
Switch: top-1, एक loss, और एक rate limit
Shazeer के paper ने k को 2 और 4 के बीच रखा और तर्क दिया कि तुलना करने के लिए router को हर token पर दो experts चाहिए। Switch Transformer (Fedus, Zoph और Shazeer, 2021) ने k=1 आज़मा ही लिया, पाया कि quality बनी रहती है, और उसी को रख लिया: प्रति token आधा expert compute, आसान dispatch, और ऊपर वाला अकेला loss। T5-Base जितने ही FLOPs per token के साथ यह वही quality 7× तक तेज़ी से छू लेता है, और यह recipe 1.6 trillion parameters तक scale हुआ। आज का हर MoE LLM इसी version का वंशज है, Mixtral में k फिर से 2 पर।
इसका दूसरा योगदान rate limit है। Experts तय hardware पर तय आकार के batches की तरह चलते हैं, इसलिए हर एक को एक capacity मिलती है: (tokens ÷ N) × एक capacity factor। उससे आगे के tokens layer से drop कर दिए जाते हैं — experts से 0; residual connection उन्हें आगे ले जाता है। न कोई exception उठता है। न कोई log आता है।
64 tokens, 8 experts, top-1. capacity = floor(factor * 64 / 8)
random-init router: factor 1.0 capacity 8 -> dropped 15 of 64
random-init router: factor 1.25 capacity 10 -> dropped 7 of 64
random-init router: factor 2.0 capacity 16 -> dropped 0 of 64
collapsed router: factor 1.0 capacity 8 -> dropped 56 of 64
collapsed router: factor 1.25 capacity 10 -> dropped 54 of 64
collapsed router: factor 2.0 capacity 16 -> dropped 48 of 64
जिस router को किसी ने छुआ तक नहीं था, उस पर factor 1.0 पर 64 में से पंद्रह tokens चुपचाप layer छोड़कर निकल गए। Paper की capacity table वाला trade-off यही है: ज़्यादा factor कम tokens drop करता है, पर हर expert का batch cap तक भर देता है, यानी खाली slots का compute भी आप ही चुकाते हैं। Switch 1.0 से 1.25 पर ठहरता है; "dropless" MoE (MegaBlocks) बाद में आया।
The code
Pure NumPy. No torch, no autograd, no training — the forward pass, the routing, the FLOP count and the two balance losses. Every function under 20 lines.
Download the full script — twelve short functions plus six runnable demos. Then:
python3 moe_from_scratch.py
def expert_forward(x, W1, W2):
return np.maximum(0, x @ W1) @ W2 # (n, d) -> (n, h) -> (n, d)
def router(x, W_g, k, W_noise=None, rng=None, bias=None, renormalise=True):
# (1) SCORE: one logit per expert. (n, d) @ (d, N) -> (n, N)
logits = x @ W_g
if bias is not None: # only used to fake a hot shard
logits = logits + bias
# (2) NOISE (training only): jitter the scores so the same k experts do
# not win every time. Learned scale, kept positive by softplus.
if W_noise is not None:
scale = softplus(x @ W_noise)
logits = logits + rng.standard_normal(logits.shape) * scale
# (3) PERCENTAGES over all N experts. The balance loss reads these.
probs = softmax(logits)
# (4) TOP-K: sort descending, keep the first k expert ids per token.
top = np.argsort(-logits, axis=-1)[:, :k] # (n, k), best first
gates = np.zeros_like(probs)
np.put_along_axis(gates, top, np.take_along_axis(probs, top, -1), -1)
# (5) RENORMALISE the kept gates so they sum to 1 again. Identical to the
# paper's softmax-after-KeepTopK; this order is easier to debug.
if renormalise:
gates = gates / gates.sum(axis=-1, keepdims=True)
return gates, top, probs
def moe_forward(x, gates, experts, capacity=None):
y = np.zeros_like(x)
dropped = 0
for i, (W1, W2) in enumerate(experts):
sent = np.flatnonzero(gates[:, i] > 0) # tokens routed to i
if capacity is not None and len(sent) > capacity:
dropped += len(sent) - capacity
sent = sent[:capacity] # first come, first served
if len(sent) == 0:
continue # idle expert: no FLOPs
y[sent] += gates[sent, i][:, None] * expert_forward(x[sent], W1, W2)
return y, dropped
def importance_loss(gates):
importance = gates.sum(axis=0) # (N,) load in gate units
cv = importance.std() / importance.mean()
return cv ** 2
def switch_balance_loss(gates, probs):
N = gates.shape[1]
f = (gates > 0).mean(axis=0) # share of tokens per expert
P = probs.mean(axis=0) # mean probability per expert
return N * np.sum(f * P)
Now break it
This is the part that actually makes it stick. Reading won't do it. You debug for a
living — reverse-engineering from broken behaviour is your native mode, and every
row below is one argument to router() or moe_forward().
| Break this | Watch for |
|---|---|
Ask for moe_flops_per_token(d, h, 8, k=8) — k equal to N | 134,234,112 FLOPs per token, 8.00×. You've built a dense layer that costs eight FFNs and called it sparse. |
Call router(x, W_g, k, renormalise=False) | Mean |y| climbs 0.728 → 1.495 as k goes 1 → 8. Renormalised, it's 1.495 at every k. Change k after training and every layer downstream sees a different scale. |
Pass bias with bias[3] = 8.0 and add no balance loss | E3 takes all 64 tokens. Add 1.0 to all of E5's weights: output changed? False. Its gradient is exactly zero; it will never train. |
Set capacity = int(1.0 * 64 / N) in moe_forward | 15 of 64 tokens dropped on the untouched router, 56 on the collapsed one. No exception, no log line — they just skip the layer. |
Keep W_noise=W_noise when you call router() at inference | The same batch twice: max |y1 − y2| = 2.199, and 10 of 16 tokens change an expert. Your endpoint is now nondeterministic. |
On the maths anxiety
Honestly: this one is easier than attention. Matrix shapes, a softmax you already
own, an argsort, an integer count per expert, and a standard deviation
divided by a mean. That's both papers' method sections.
The one place it looks harder than it is: Shazeer's "load" loss, in the appendix, uses the normal CDF. All it asks is "if I re-rolled the noise, would this expert still make the top k?" — a smooth stand-in for a count, because a count has no gradient. Switch does the same with less machinery: count × probability.
What an interview actually probes is the systems side: why active parameters aren't memory, why a hot expert is a latency problem on a multi-GPU box, what a dropped token costs you. All of it falls out of the counts you just printed.
Where to go next
This page gave you one thing: the layer as a load balancer, in code you can break. It skipped the history, the fine-tuning quirks and the all-to-all plumbing that runs it across GPUs. These fill those in.
-
Outrageously Large Neural Networks: The Sparsely-Gated Mixture-of-Experts Layer
§2 is
router()andmoe_forward()with a diagram; §4 is the two losses. §3 is the part I called plumbing — keeping each expert's batch large enough, and the network cost — worth a skim if you serve these. -
Mixture of Experts Explained
Everything I skipped on purpose: the 1991 origins, GShard, what Mixtral does per layer, why MoEs are awkward to fine-tune, and the serving and quantisation trade-offs. Read this for the map; this page was one road on it.
-
How to Train Really Large Models on Many GPUs?
The plumbing. My script loops over experts in one process; on real hardware each expert lives on a different device and tokens cross the network twice per layer. Her MoE section covers expert parallelism and the all-to-all behind it.