The Feed-Forward Network: Where a Transformer Keeps What It Knows

The Feed-Forward Network: Where a Transformer Keeps What It Knows

A transformer block holds seven weight matrices. Three of them belong to the feed-forward network, and between them those three carry about 82 percent of the block’s parameters. Attention gets the papers, the talks and the diagrams. The feed-forward network gets the weights.

Most explanations of it fit in one sentence: widen, apply a nonlinearity, narrow. That sentence is accurate and it tells you nothing. It does not tell you why the widening helps, why there are three matrices in a modern model instead of two, or why the people who edit facts into a language model go straight for these weights and ignore attention entirely.

Part 5 closed out the attention half of the block with W_O. This part takes the other half. By the end you will be able to trace six channels of a real gated FFN by hand: raw gate score, SiLU, elementwise multiply, weighted sum of output rows, final vector. Every number is checkable with a calculator. You will also know why the width is 28,672 and not 32,768, why widening d_model instead would cost roughly five times as much for the same gain, and why this is the one slot in the block that Mixture of Experts can replace.

The running model is the same as everywhere else in this series: 80 layers, d_model 8,192, 64 query heads, 8 KV heads, FFN width 28,672.

Inside the Inference Stack · Part 6 of 11
  1. Part 1. How an LLM Answers a Question: The Complete Inference Path
  2. Part 2. Byte-Pair Encoding Explained: How LLMs Turn Text Into Tokens
  3. Part 3. Inside One Transformer Block: The Residual Stream and Its Seven Matrices
  4. Part 4. Multi-Head Attention Explained: Why 64 Heads Instead of One
  5. Part 5. The Output Projection: How 64 Attention Heads Become One Thought
  6. Part 6. The Feed-Forward Network: Where a Transformer Keeps What It Knows (you are here)
  7. Part 7. Mixture of Experts Explained: Conditional Computation From Zerocoming 15 Aug
  8. Part 8. Attention Is All You Need, Dissected: The 2017 Figure, Box by Boxcoming 16 Aug
  9. Part 9. The Roofline Model: Why LLM Decode Is Memory-Boundcoming 17 Aug
  10. Part 10. Continuous Batching and PagedAttention: How vLLM Keeps a GPU Busycoming 18 Aug
  11. Part 11. Benchmark Your Own LLM Serving Stack: Two Measurements, One Afternooncoming 19 Aug

Orientation: W_gate and W_up are siblings

Definition

Feed-forward network (FFN)

The half of a transformer block that processes each token on its own, with no reference to any other position. In a modern model it is three matrices: W_gate and W_up widening 8,192 to 28,672 in parallel, and W_down bringing the result back to 8,192.

Origin: the 2017 transformer paper called it the position-wise feed-forward network and built it from two matrices with a ReLU between them, at four times the model width. It was there to give the block some per-token computation, since attention on its own only moves existing information around and never transforms it.

Why it matters: it is 704,643,072 parameters per block, 82.4 percent of the total, and 112.7 GB across the 80 layers of the running model.

W_gate and W_up are siblings. Both receive the same normalised vector, both run at the same time, and both widen 8,192 to 28,672. They are not a chain, and nothing flows from one into the other. They meet later, at an elementwise multiply, and only W_down is downstream of anything. If your mental picture is x into W_gate into W_up, throw it away now, because every later step will confuse you.

x′ W_gate W_up → 28,672 numbers → 28,672 numbers this lesson × they meet here, not before same input · two matrices · simultaneous ✗ x′ → W_gate → W_up is the wrong picture

Two matrices, one input, running side by side. The multiply is the first point at which either branch knows the other exists.

On a GPU the two are often fused into a single matmul against a concatenated weight tensor, which is a performance detail rather than a change of structure. It works precisely because they are independent.

Section takeaways

  • The FFN is three matrices, and it processes every token in complete isolation from every other token.
  • W_gate and W_up read the same input at the same time and meet only at an elementwise multiply.
  • Only W_down is downstream of anything, making the gate-and-up pair the single chained step in the block.
  • Kernels fuse the two parallel matrices into one matmul, which is possible only because neither depends on the other.

W_gate supplies permission

Definition

Gating

Multiplying a signal by a separately computed number that decides how much of it survives. The gate contributes no content of its own, and it is computed from the same input as the content it controls.

Origin: gating goes back to the LSTM in 1997, where input, output and forget gates were introduced to control what a recurrent network kept and discarded. Gated linear units brought the idea into feed-forward language models in 2016, and it reached the transformer FFN in 2020.

Why it matters: without a gate the only way to suppress a channel is for its content to be small. With a gate, suppression becomes an independent decision, so a channel can hold something large and still be silenced.

Analogy

A mixing desk with 28,672 channels. W_up produces the audio on every channel and W_gate sets every fader. The faders play no music of their own.

Where it breaks: a sound engineer sets the faders once and leaves them. These are recomputed from scratch for every single token, from the same input that produced the audio, so the desk reconfigures itself thousands of times a second.

content arriving on each channel … 28,666 more channels … W_gate sets every fader high fader → that channel’s content passes at full strength low fader → that channel is turned down, or silenced entirely the faders carry no music of their own. they only set levels.

Faders set levels and contribute no signal. That distinction is the whole reason a gated FFN behaves differently from an ungated one.

One raw score per channel

Mechanically it is an ordinary matrix multiply: 8,192 numbers in, 28,672 numbers out, one per channel of the wide space. W_gate is 8,192 x 28,672, which is 234.9 million parameters, or about 470 MB at bf16.

Those raw numbers are not fader positions yet, and they can be large, small or negative. Our toy has six channels instead of 28,672 so the arithmetic stays visible, and the raw scores are [2.1, -3.0, 0.4, -0.2, 4.0, 1.1].

x′ W_gate 8192 × 28672 · 470 MB raw gate scores — our toy has 6 channels, the real thing has 28,672 ch 12 34 56 g 2.1−3.0 0.4−0.2 4.01.1

Six raw gate scores standing in for 28,672. Note that two of them are negative, which matters at the next step.

SiLU turns scores into fader positions

Definition

SiLU (also called swish)

The function SiLU(g) = g * sigmoid(g). Strongly negative inputs come out near zero, large positive inputs come out near themselves, and everything between is smooth. It dips to about -0.278 at an input near -1.28.

Origin: proposed in 2017, in one case found by an automated search over candidate activation functions rather than derived by hand. It was adopted over ReLU because ReLU has a flat dead region below zero where a unit receives no gradient and can stay dead permanently, while SiLU has gradient everywhere.

Why it matters: the smoothness lets a channel the model has shut be reopened later in training, and it lets the gate express “a bit of this one”, which an on/off switch cannot.

Run our six raw scores through it and you get [1.87, -0.14, 0.24, -0.09, 3.93, 0.83]. Check one: sigmoid(2.1) is 0.8909, and 2.1 * 0.8909 = 1.871. The score of -3.0 collapses to -0.14, so that channel is now effectively shut. The score of 4.0 passes through almost intact at 3.93.

out in 0 flat near zero → channel closed rises like x → channel open small dip below zero — deliberate, and useful our six, mapped through 2.11.87 −3.0−0.14 0.40.24 −0.2−0.09 4.03.93 1.10.83 −3.0 collapsed to −0.14 that channel is now shut 4.0 passed through almost intact

A strongly negative score is crushed toward zero. A large positive score is left roughly where it was. The curve is the entire mechanism.

One honest point about SiLU. Because it goes slightly negative, a closed channel is not exactly zero, and it can push a small amount of inverted signal into the output. That is a real property of the function and not a rounding artefact, and it shows up in the worked arithmetic later in this article.

The multiply is where the gate acts

Now W_up‘s content arrives and the two vectors are multiplied position by position. Channel 1’s gate multiplies channel 1’s content, and there is no mixing across channels at all. Say the content values are [1.0, 5.0, 2.0, 3.0, 1.5, 2.0]. The gated result is [1.87, -0.70, 0.48, -0.27, 5.90, 1.66].

channel 12 34 56 gate 1.87−0.14 0.24−0.09 3.930.83 content 1.05.0 2.03.0 1.52.0 result 1.87−0.70 0.48−0.27 5.901.66 channel 2 — loud content, gate shut content was 5.0, the largest on the row. gate −0.14 crushed it to −0.70. channel 5 — quiet content, gate wide open content was only 1.5. gate 3.93 lifted it to 5.90, the largest result.

Channel 2 had the loudest content on the row and finished near the bottom. Channel 5 had the quietest and finished on top.

Two channels are worth pausing on. Channel 2 carried content of 5.0, the largest value on the row, and a gate of -0.14 crushed it to -0.70. Channel 5 carried only 1.5, and a gate of 3.93 lifted it to 5.90, the largest result. The loudest content lost and the quietest content won, which is the point of the whole mechanism: what survives is decided by the gate and not by the content’s own magnitude.

Two questions, asked by two matrices

Notice what the split bought. W_up answers “what could this channel contribute?” and W_gate answers “should it, for this token?”. Two different questions, two different matrices, combined only at the end, so neither matrix has to compromise to do the other’s job.

W_up asks “what could this channel say?” CONTENT W_gate asks “should it, for this token?” PERMISSION × multiplied per channel learned independently, applied together

Content and permission are learned independently and applied together. Neither matrix has to compromise to do the other’s job.

W_gate is fixed after training, and it is multiplied by a different normalised vector for every token, so it produces a different set of 28,672 fader positions every time. One matrix, an unlimited number of filters.

W_gate — fixed token “bank” finance channels open token “river” geography channels open token “the” almost everything damped same matrix, three different tokens, three completely different filters illustrative — real channels are not this interpretable

The same fixed matrix yields a different filter per token. The channel labels here are illustrative; real channels are rarely this interpretable.

Without a gate you get the 2017 arrangement: widen, apply a nonlinearity, narrow. Every token receives the same treatment, and the only way to suppress a channel is for the content itself to be small.

no gate — one fixed treatment token A same filter token B same filter the model must find one transformation that works acceptably for everything with a gate — a filter per token token A filter for A token B filter for B no compromise needed

An ungated FFN has to find one transformation that is acceptable for everything. A gated one computes a fresh filter per token.
SHAPE — 8,192 × 28,672 · 470 MB · one of three FFN matrices INPUT — the normalised residual stream, x′. same input as W_up. OUTPUT — one raw score per channel, then SiLU, then a fader position JOB — decide how much of W_up’s content each channel may contribute KEY PROPERTY — the filter is recomputed for every token W_O mixes across heads. W_gate modulates across channels. both are 8,192-wide matrices; their jobs have nothing in common

Shape, input, output, job and key property on one page. Worth keeping next to you for the rest of this article.

Section takeaways

  • W_gate is 8,192 x 28,672, which is 234.9 million parameters and about 470 MB in bf16.
  • SiLU converts raw gate scores into fader positions, crushing -3.0 to -0.14 and passing 4.0 through as 3.93.
  • SiLU dips to about -0.278, so a closed channel leaks a small inverted signal rather than contributing exactly zero.
  • The multiply is elementwise, so channel 1’s gate only ever touches channel 1’s content.
  • In the worked row the loudest content (5.0) finished at -0.70 and the quietest (1.5) finished on top at 5.90.

W_up supplies content, and every channel is a detector

W_up sits beside W_gate, takes the same input, and produces the same shape. Its numbers mean something completely different: the gate produced levels, and W_up produces the material those levels act on.

x′ W_gate W_up fader positions — done last lesson content values — this lesson 8192 × 28672 · 470 MB · identical shape to W_gate same input, same output shape, entirely different meaning

Identical shape, identical input, opposite meaning. Same 470 MB, same 234.9 million parameters.

Look at a single column of W_up. It is a vector of 8,192 numbers, and computing channel 5 means taking the dot product of that column with the token’s vector, summed over 8,192 terms. A dot product measures alignment, so the column is a learned pattern and the channel’s value says how strongly this token matches it. Large means a strong match, near zero means no match, and negative means the token looks like the opposite of the pattern.

W_up, viewed as 28,672 columns … 28,666 more columns … column 5 — 8,192 numbers channel 5’s value = column 5 · x′ (a dot product over 8,192 terms) large → the token strongly matches this column’s pattern near zero → no match · negative → the opposite of the pattern

A column of W_up is a pattern and the channel value is a match score. There are 28,672 of these running at once.

That reframes the wide space. It is a bank of 28,672 independent pattern detectors, all evaluated on every token, rather than a bigger version of the residual stream.

Why widen at all

The token arrives as 8,192 numbers, so why blow it out to 28,672? Because you cannot have more clearly distinguishable detectors than you have dimensions. An 8,192-dimensional space gives you room for roughly 8,192 patterns that do not sit on top of each other, and widening to 28,672 gives you room for about 3.5 times as many, with space left between them.

The nonlinearity then runs in the wide space, where the patterns are already separated. Patterns that overlap and interfere at 8,192 dimensions can be pulled apart at 28,672. The width here buys capacity to recognise rather than capacity to represent.

8,192 dimensions room for ~8,192 distinguishable patterns 28,672 dimensions room for ~3.5× as many, with space between them the nonlinearity then runs in the wide space, where things are separated patterns that overlap and interfere at 8,192 can be pulled apart at 28,672 width here is capacity to RECOGNISE, not capacity to represent

More room means more detectors that do not collide. This is the only reason the widening exists.

Why not just make d_model bigger instead

If width helps, the obvious move is to widen everything. The answer is that d_model is paid for in every matrix in every block, plus the embedding table, plus the LM head, plus the KV cache. FFN width is paid for in three matrices, and the wide vector never leaves the block, so the cache never sees it.

Make it concrete. Add 1,024 to each width and count what it costs, keeping head_dim at 128 and the GQA ratio at 8 query heads per KV head.

What you widen FFN inner width, 28,672 to 29,696 d_model, 8,192 to 9,216
Matrices affected per block 3 all 7
New parameters per layer 25.2M 128.2M
New parameters, 80 layers 2.01B 10.26B
Embeddings and LM head unchanged +0.26B
Total added weight, bf16 4.0 GB 21.0 GB
KV cache per token unchanged, 320 KiB 360 KiB, +12.5 percent
KV cache per 8k-token sequence 2.50 GiB 2.81 GiB

Same 1,024 of extra width, roughly five times the weight cost, and 320 MiB more cache for every concurrent sequence you want to serve at 8k context.

In practice

On a fixed GPU budget the cache line is the one that hurts, because it caps concurrency directly. Recall the cache formula: 2 * layers * kv_heads * head_dim * tokens * dtype_bytes. Nothing in it mentions the FFN’s inner width, which is why buying capacity there is cheap and buying it in d_model is not. If you are weighing up running models on your own machines, the split between static weights and per-sequence cache decides how many users one card can hold.

widen d_model — everything grows W_Q W_K W_V W_O embeddings LM head KV CACHE the FFN ↑ this one caps your concurrency widen only the FFN’s inner width unchanged UNCHANGED grows the wide vector never leaves the block, so it never touches the cache that is why the FFN is where models buy extra capacity cheaply

Widening d_model touches the embedding table, both attention projections, the LM head and the cache. Widening the FFN touches three matrices.

Section takeaways

  • Each column of W_up is a learned pattern, and the channel value is a match score against the current token.
  • The wide space is a bank of 28,672 pattern detectors evaluated on every token, all at once.
  • Widening exists because a space cannot hold more clearly separated detectors than it has dimensions.
  • Adding 1,024 to the FFN width costs 4.0 GB. Adding it to d_model costs 21.0 GB, roughly five times as much.
  • FFN width costs nothing in KV cache. Widening d_model raises the cache by 12.5 percent per token, which is what caps concurrency.

W_down and the forced return to 8,192

The block’s last act is x + FFN_output, and addition demands matching shapes. You cannot add a 28,672-wide vector to an 8,192-wide one, so whatever the FFN produces must be 8,192 wide. The widening was always temporary, and W_down is a structural obligation imposed by the residual stream rather than a design preference. Part 3 walked through the residual stream and why every sublayer has to hand back the same shape it was given.

8192 28,672 gate · content · multiply 8192 + 8192 the residual stream this width exists only inside the branch you cannot add a 28,672 vector to an 8,192 one — so W_down is mandatory

The wide space exists only inside the branch. The residual add is what forces the narrowing.

Each channel owns an output vector

Turn W_down on its side and it becomes readable. It has 28,672 rows, each 8,192 numbers long, and row 5 is what channel 5 writes into the residual stream when it fires. The output is those rows added up, each weighted by how strongly its channel fired, so a channel that fired hard contributes a lot of its row and a channel that was gated shut contributes almost nothing.

W_down, viewed as rows ch 1ch 2ch 3 ch 4ch 5ch 6 row 5 — 8,192 numbers what channel 5 contributes 28,672 rows in the real thing output = Σ (channel value) × (that channel’s row) a channel that fired hard contributes a lot of its row; a silent channel contributes nothing

W_down read as rows rather than as a matrix. Each row is a fixed vector that one channel is entitled to write.

The arithmetic, carried all the way through

Our gated values were [1.87, -0.70, 0.48, -0.27, 5.90, 1.66]. Give each channel a short output row of 4 numbers instead of 8,192, and add them up.

Channel Gated value Its row in W_down
1 1.87 [ 1, 0, 2, 0 ]
2 -0.70 [ 0, 3, 0, 1 ]
3 0.48 [ 2, 1, 0, 0 ]
4 -0.27 [ 0, 0, 1, 2 ]
5 5.90 [ 3, 0, 1, 1 ]
6 1.66 [ 1, 2, 0, 0 ]
Output [ 22.19, 1.70, 9.37, 4.66 ]

Check the first component yourself: 1.87(1) + 0.48(2) + 5.90(3) + 1.66(1) = 1.87 + 0.96 + 17.70 + 1.66 = 22.19. Channels 2 and 4 had zeros in the first position and drop out.

Check the second: -0.70(3) + 0.48(1) + 1.66(2) = -2.10 + 0.48 + 3.32 = 1.70. Here channel 2, the one the gate shut, does contribute, and it contributes with a flipped sign. That is the SiLU negative dip showing up in the output. It is small, and it is not zero.

ch gated its row in W_down 11.87 [ 1 0 2 0 ] 2−0.70 [ 0 3 0 1 ] 30.48 [ 2 1 0 0 ] 4−0.27 [ 0 0 1 2 ] 55.90 [ 3 0 1 1 ] 61.66 [ 1 2 0 0 ] out [ 22.19 1.70 9.37 4.66 ] channel 5 fired at 5.90 and dominates the result channels 2 and 4 were gated shut and barely register first component: 1.87(1) + 0.48(2) + 5.90(3) + 1.66(1) = 22.19 — check it yourself

Channel 5 fired at 5.90 and dominates every component. The two gated-shut channels barely register.

Scale that up to 28,672 rows of 8,192 numbers and you have the vector this block adds to the residual stream for this token. Nothing else about the computation changes.

Section takeaways

  • W_down exists because the residual add requires the branch to hand back exactly 8,192 numbers.
  • Read as rows, W_down gives every one of the 28,672 channels its own fixed output vector.
  • The FFN result is those rows summed, each scaled by how strongly its channel fired.
  • In the worked example channel 5 fired at 5.90 and dominates every component of the output.
  • The gated-shut channel still contributes, with a flipped sign, which is the SiLU dip appearing in real arithmetic.

The payoff: the FFN is a key-value memory

Definition

Key-value memory view of the FFN

Reading W_up‘s columns as keys, patterns to match against the token, and W_down‘s rows as values, content to return. Channel i pairs column i with row i, giving 28,672 entries per layer.

Origin: set out by Geva and colleagues in 2020, who showed that individual FFN keys correlate with human-interpretable input patterns and that the paired values shift the output distribution in matching ways. It was adopted because it made the FFN’s parameters interpretable at all, where previously they were treated as an undifferentiated blob.

Why it matters: it turns “where does a model keep what it knows” into an answerable question, and it is why model editing methods go straight for these weights.

Analogy

A library where every book has a description on the spine. The token is compared against all 28,672 spines at once, and every book contributes its contents in proportion to how well its spine matched.

Where it breaks: a library returns the book you asked for. This returns a weighted blend of all 28,672 at once, most of them contributing something close to nothing, and there is no step at which one entry is selected.

W_up columns = KEYS W_down rows = VALUES key 1 value 1 key 2 value 2 key 5 value 5 — this one matched 28,672 pairs in total match every key · gate the matches · return a blend of the corresponding values a soft lookup — every entry contributes something, most of it near zero

Keys on the left, values on the right, paired by channel index. Every entry contributes something; most contributions are near zero.

Model-editing work followed the same road. ROME changes a specific factual association by making a rank-one edit to a middle-layer FFN weight matrix, and it works well enough to be a standard baseline.

Definition

Polysemanticity

The observation that one channel responds to several unrelated patterns rather than to a single clean concept.

Origin: named in interpretability research that found individual neurons in trained networks firing for mixtures of unrelated features. The leading explanation is that models pack more features than they have dimensions and tolerate the interference, which is exactly the pressure the widening in this article is trying to relieve.

Why it matters: it is the reason to distrust any claim that a specific neuron holds a specific fact, even though the key-value framing at the layer level holds up well.

Be careful how far you push the storage picture. “Facts live in the FFN” is a good working model and a bad literal claim. Hase and colleagues showed that you can successfully edit a fact by changing weights in a layer quite different from the one causal tracing points at, which means editability and localisation are not the same property. A given fact is usually spread across several layers and many channels.

The division of labour across a block is cleaner than the localisation question. Attention moves information between positions and the feed-forward network stores it. Edit attention weights and the model routes differently. Edit FFN weights and the model believes differently.

attention moves information between positions the FFN stores information 82% of the parameters edit attention weights → the model routes differently edit FFN weights → the model believes differently this is why “82% of parameters in the FFN” is less strange than it first sounds

Two jobs, two halves of the block. This is why 82 percent of the parameters sitting in the FFN is less strange than it first sounds.

Section takeaways

  • W_up‘s columns are keys and W_down‘s rows are values, giving 28,672 key-value entries per layer.
  • The FFN is a soft lookup: every entry contributes, weighted by match strength and by whether the gate let it through.
  • The reading is empirically supported and underpins model editing methods like ROME.
  • Localisation is weaker than editability, since a fact can be edited in a layer other than the one causal tracing identifies.
  • Attention moves information between positions and the FFN stores it, which is why the parameter split is 18 to 82.

Where the parameters actually go

Here is the whole block, counted. Every matrix in the feed-forward network is the same size, which follows from all three touching the same two widths.

Matrix Shape Parameters bf16 size Role
W_gate 8,192 x 28,672 234,881,024 470 MB permission: how much of each channel gets through
W_up 8,192 x 28,672 234,881,024 470 MB detection: the keys, 28,672 pattern matchers
W_down 28,672 x 8,192 234,881,024 470 MB retrieval: the values, one output row per channel
FFN total 704,643,072 1.41 GB 82.4 percent of the block
Attention (W_Q, W_K, W_V, W_O) 150,994,944 302 MB 17.6 percent of the block
Block total 855,638,016 1.71 GB times 80 layers

Across 80 layers that is 112.7 GB of FFN weights against 24.2 GB of attention weights, on a model whose bf16 checkpoint is roughly 140 GB. When you generate a token at batch size 1, the GPU reads essentially all of that from HBM to produce a single token, which is why decode is memory-bound. Part 9 puts numbers on thatcoming 17 Aug, and the same reasoning shows up whenever you are thinking about memory rather than arithmetic as the scarce resource.

x′ 8192 W_gate permission W_up detection · the keys SiLU × 28,672 wide W_down retrieval · the values match → filter → retrieve → add to the stream

Match, filter, retrieve, add to the stream. All three matrices and both widths in one picture.

Section takeaways

  • All three FFN matrices are 234,881,024 parameters and 470 MB each, because all three span the same two widths.
  • The FFN is 1.41 GB per block against attention’s 302 MB, an 82.4 to 17.6 split.
  • Across 80 layers that is 112.7 GB of FFN weights against 24.2 GB of attention weights.
  • Generating one token at batch size 1 reads essentially all 140 GB from HBM, which is the root of the decode bottleneck.

Classic FFN versus modern SwiGLU

Definition

SwiGLU

The three-matrix gated feed-forward network used in most modern models: SiLU applied to the gate branch, an elementwise multiply with the up branch, then the down projection.

Origin: introduced by Noam Shazeer in 2020 in a paper comparing gated variants of the transformer FFN, which found the gated versions consistently better on the same parameter budget. The paper is unusually candid that it offers no theoretical explanation for why they work, and it was adopted on the strength of the measurements alone.

Why it matters: the third matrix is what makes the FFN’s parameter count 82 percent of a block instead of roughly 75 percent, and its width is the number the next section explains.

The 2017 FFN had two matrices and a ReLU, with an inner width of four times d_model. SwiGLU adds a third matrix, so at the same width it would cost 50 percent more parameters for the same slot. The standard fix is to scale the width down by two-thirds, which makes the swap parameter-neutral: three matrices at two-thirds the width cost exactly what two matrices at full width cost.

Design Matrices Inner width Params per layer Relative
2017 ReLU FFN, width = 4 x d_model 2 32,768 536.9M 1.00x
SwiGLU at that same width 3 32,768 805.3M 1.50x
SwiGLU at strict two-thirds width 3 21,845 536.9M 1.00x
Llama-3-70B as shipped 3 28,672 704.6M 1.31x

So the honest answer to “why 28,672 and not 32,768” has two halves. The two-thirds rule is why the number is not simply 4 times 8,192, since adding a third matrix without shrinking the width would inflate the FFN by half for no structural reason. Early Llama models followed the rule closely, with Llama-2-7B using 11,008 against a d_model of 4,096, which is 2.69 times and near the 8/3 the rule prescribes.

In practice

Llama-3 chose 3.5 times d_model at every size, landing at 28,672 here, which takes most of the discount and spends part of it back on extra detectors. Do not repeat the common claim that 28,672 is the parameter-neutral number. It is 31 percent above it, and every figure in the table above is checkable in one line of arithmetic.

Section takeaways

  • SwiGLU adds a third matrix, so at unchanged width it would cost 50 percent more than the 2017 design.
  • The two-thirds width rule makes the swap parameter-neutral, at 21,845 for d_model 8,192.
  • Llama-3 ships 28,672, which is 3.5 times d_model and 31 percent above the parameter-neutral figure.
  • Gated variants were adopted on measured results, with the introducing paper explicitly offering no theory for why they win.

Why this is the slot Mixture of Experts replaces

One property has been quietly true throughout: the feed-forward network processes each token entirely independently. Token 4 knows nothing about token 3 inside the FFN, because all the cross-token work happened in attention, upstream.

Definition

Mixture of Experts (MoE)

Replacing the single FFN with several copies of its three matrices, plus a small router that sends each token to a couple of them. Parameter count rises several times while the work done per token stays roughly flat.

Origin: the idea dates to 1991 work on mixtures of local experts, and it was brought into large neural language models in 2017 as a sparsely gated layer, specifically to grow capacity without growing the compute spent on each example.

Why it matters: it only works where tokens are handled independently, which is true of the FFN and false of attention.

That independence is what makes the slot swappable. You cannot do the same to attention, because attention’s entire job is to mix across positions, so routing tokens to separate attention modules would sever the connections it exists to make. The FFN is the only slot in the block where conditional computation is structurally free. Part 7 builds an MoE layer from zerocoming 15 Aug on exactly this foundation, including what it does to your serving memory when all the experts have to be resident whether they fire or not.

Section takeaways

  • The FFN handles each token in isolation, which is what makes it safe to route different tokens to different copies.
  • Attention cannot be split that way, since mixing across positions is the whole of its job.
  • MoE grows parameter count several times while keeping per-token compute roughly flat.
  • The idea is from 1991 and reached large language models in 2017, adopted to buy capacity without buying compute.

Key takeaways

  • W_gate and W_up run in parallel from the same input and meet at an elementwise multiply. Only W_down is downstream.
  • W_gate supplies permission and no content. SiLU converts its raw scores into fader positions, dipping to about -0.278 at worst, so a closed channel leaks a small inverted signal rather than exactly zero.
  • Each column of W_up is a learned pattern and each channel value is a match score. Widening to 28,672 buys room for about 3.5 times as many distinguishable detectors.
  • Widening d_model by the same 1,024 would cost roughly five times the weights and 12.5 percent more KV cache per token. FFN width costs three matrices and nothing in the cache.
  • Each of the 28,672 rows of W_down is an output vector, and the FFN’s result is those rows summed with the gated activations as weights. The residual add is what forces the return to 8,192.
  • W_up holds keys and W_down holds values, so the FFN behaves like a soft key-value lookup. Attention moves information between positions; the FFN stores it.
  • Three matrices at 234.9M parameters each hold 82.4 percent of a block, and the feed-forward network’s per-token independence is what lets Mixture of Experts replace it.

Frequently asked questions

Is W_up applied after W_gate?

No. They are siblings. Both read the same normalised residual vector, both run at the same time, and both project 8,192 dimensions up to 28,672. They meet only at the elementwise multiply, after SiLU has been applied to the gate branch. Many inference kernels fuse the two into one matmul precisely because neither depends on the other.

What does SiLU do that ReLU does not?

SiLU is x * sigmoid(x). It is smooth everywhere, so it has gradient below zero, and it dips to about -0.278 near an input of -1.28 before flattening toward zero. ReLU has a hard corner and a completely flat dead region, so a channel that lands there gets no gradient and can stay dead for the rest of training. The smoothness also lets the gate express partial openness rather than a binary on or off.

Why is the FFN width 28,672 and not 4 x 8,192 = 32,768?

SwiGLU uses three matrices where the classic FFN used two, so keeping the 4x width would inflate the FFN by 50 percent. The standard fix is to scale the width by two-thirds, which for d_model 8,192 gives 21,845 and makes the swap parameter-neutral. Llama-3 chose 3.5 times d_model, or 28,672, which takes most of the discount and spends part of it back on extra width.

How many parameters are in the FFN compared to attention?

For a Llama-3-70B class block, the three FFN matrices hold 704,643,072 parameters and the four attention matrices hold 150,994,944. That is 82.4 percent of the block in the FFN. Across 80 layers it is about 112.7 GB of FFN weights against 24.2 GB of attention weights at bf16.

Does the FFN really store the model’s facts?

The key-value memory reading is well supported, and model-editing methods like ROME work by changing FFN weights. Be careful about stronger claims. Later work found you can edit a fact successfully in a layer other than the one causal tracing identifies, individual facts are usually spread across several layers, and channels are polysemantic. The FFN is where storage happens; clean per-neuron localisation of a given fact is not established.

Why can Mixture of Experts replace the FFN but not attention?

The FFN processes every token independently, with no cross-token dependency inside it. That means you can route different tokens to different copies of the three matrices without breaking anything. Attention exists to mix information across positions, so splitting tokens across separate attention modules would destroy the connections it is there to build.

Sources and further reading

Next in the seriesPart 7. Mixture of Experts Explained: Conditional Computation From Zerocoming 15 Aug

Check your understanding

Take the 10 question quiz on this article

It opens in a panel and takes you one question at a time. You get a full report at the end: your score, the correct answer to anything you missed, why it is correct, and a link straight back to the section it came from. Hints are there if you want them. Nothing is stored, and you can retake it as often as you like.

  • 10 questions
  • 3 select all that apply
  • hint on every question
  • timed, no limit

The Feed-Forward Network: Where a Transformer Keeps What It Knows

Question 1 of 10

Elapsed 0:00

1 How do W_gate and W_up relate to each other inside a modern gated feed-forward network?

0 of 10 answered

Previous