The feedforward layer
Two matrices with a squashing function between them, applied to each token on its own - and the place where roughly two thirds of a transformer's parameters actually live.
- 13 min read
- 3 reading levels
- Updated
Read these first
On this page 6
One lesson, three depths. Pick the one that fits you today — you can switch any time.
Beginner — No maths. Plain English.
The feedforward layer processes each token entirely on its own, and it holds most of the model's parameters.
Watch a good cook taste a dish. They take one spoonful and decide what it needs. Then they reach for exactly those things: a pinch of salt, a squeeze of lemon. A different dish gets a different set of additions.
Two things stand out. The cook is judging this one dish, not comparing it to the pot on the next burner. And the decision triggers specific, learned responses: salty-but-flat means lemon, every time.
That is the feedforward layer. One token at a time, with no reference to any other. It applies a large set of learned "if this, then add that" responses.
Why it comes after attention
The attention step is where tokens talk to each other. Once that is done, each token is carrying a bundle of collected information.
Now something has to be done with that bundle. That is this layer's job. Attention gathers; the feedforward layer digests.
Every transformer block alternates the two, dozens of times over.
What is inside
Three steps, and the middle one is where it gets interesting.
Step one: widen. Take the token's numbers and expand them into a much larger set. Four times as many is the standard choice. A token that arrived with four thousand numbers becomes sixteen thousand.
Step two: squash. Apply a rule that keeps strong values and flattens weak ones. Without this step the whole layer would collapse into a single multiplication and be capable of almost nothing.
Step three: narrow. Bring it back down to the original size. Now it can be added to the running total.
token in widen 4x squash narrow back
[4096] ───► [16384] ───► [16384] ───► [4096]
▲ ▲ ▲
first matrix keep the strong second matrix
flatten the weakWhy widening then narrowing is not a waste
It looks pointless. Go up, come back down, end where you started.
The wide middle is the whole point. Each of those sixteen thousand slots can act as a small detector. Each watches for one specific pattern in the incoming token. Most stay quiet. A handful fire.
When one fires, the second matrix decides what it writes back into the token. So the pair of matrices behaves like a huge set of learned rules: this pattern in, that adjustment out.
Sixteen thousand rules per layer, times dozens of layers, is a very large number of rules. There is good evidence that much of what a model "knows" is stored here. Capital cities, common phrasings, factual associations.
The number that surprises everyone
Attention gets all the attention. The feedforward layer holds about two thirds of every block's parameters.
Two matrices, each connecting the model width to four times the model width. That is eight units of weight. Attention's four matrices, each width by width, come to four units. Two thirds against one third.
So when someone says a model has eight billion parameters, roughly five billion sit here. In these plain, unglamorous layers.
Remember this
- Each token is processed alone here, with no reference to any other token.
- Widen, squash, narrow — and the wide middle is where learned patterns live.
- This layer holds around two thirds of the model's parameters.
What to learn next
- SwiGLU and gated feedforward layers — the version nearly every current model uses.
- Activation functions — the squashing step, in its own right.
- Counting a model's parameters by hand — putting the two-thirds share into real numbers.
Developer — Code and libraries.
Setup
pip install torchA feedforward layer built by hand as a lookup table
The clearest way to see what this layer does is to set its weights deliberately instead of randomly.
import torch
import torch.nn as nn
import torch.nn.functional as F
torch.set_printoptions(precision=3, sci_mode=False)
d, hidden = 4, 8
ffn = nn.Sequential(nn.Linear(d, hidden), nn.ReLU(), nn.Linear(hidden, d))
with torch.no_grad():
ffn[0].weight.zero_(); ffn[0].bias.fill_(-0.5)
ffn[2].weight.zero_(); ffn[2].bias.zero_()
# Neuron 0 is a detector: it fires only when slot 0 of the input is large.
ffn[0].weight[0, 0] = 1.0
# When neuron 0 fires, it writes into slot 2 of the output.
ffn[2].weight[2, 0] = 3.0
# Neuron 1 detects slot 1 and writes into slot 3.
ffn[0].weight[1, 1] = 1.0
ffn[2].weight[3, 1] = -2.0
def row(t):
return "[" + " ".join(f"{v:+5.2f}" for v in t) + "]"
print("a feedforward layer built by hand as a two-entry lookup table")
for x in (torch.tensor([[2., 0., 0., 0.]]),
torch.tensor([[0., 2., 0., 0.]]),
torch.tensor([[2., 2., 0., 0.]]),
torch.tensor([[0.1, 0., 0., 0.]])):
hid = F.relu(ffn[0](x))
print(f" in {row(x[0])} hidden {row(hid[0])} out {row(ffn(x)[0])}")
print(" -> the last row is below the -0.5 threshold, so nothing fires and nothing is written")
print("\nwidth of the hidden layer, and what it costs")
d_model = 4096
print(f"{'multiplier':>11} {'hidden width':>13} {'FFN params':>14} {'share of block':>15}")
for mult in (1, 2, 4, 8):
hid = mult * d_model
ffn_p = 2 * d_model * hid # two weight matrices, biases ignored
attn_p = 4 * d_model * d_model # Q, K, V and the output projection
print(f"{mult:>10}x {hid:>13,} {ffn_p:>14,} {ffn_p/(ffn_p+attn_p):>14.1%}")
print(" -> at the usual 4x, the feedforward layer holds two thirds of every block")
print("\nwhich activation, and does it matter here?")
z = torch.linspace(-3, 3, 7)
print(" z ", [f"{v:+.2f}" for v in z])
print(" ReLU ", [f"{v:+.2f}" for v in F.relu(z)])
print(" GELU ", [f"{v:+.2f}" for v in F.gelu(z)])
print(" SiLU ", [f"{v:+.2f}" for v in F.silu(z)])
print(" -> GELU and SiLU pass a little signal for small negatives; ReLU passes none")a feedforward layer built by hand as a two-entry lookup table
in [+2.00 +0.00 +0.00 +0.00] hidden [+1.50 +0.00 +0.00 +0.00 +0.00 +0.00 +0.00 +0.00] out [+0.00 +0.00 +4.50 +0.00]
in [+0.00 +2.00 +0.00 +0.00] hidden [+0.00 +1.50 +0.00 +0.00 +0.00 +0.00 +0.00 +0.00] out [+0.00 +0.00 +0.00 -3.00]
in [+2.00 +2.00 +0.00 +0.00] hidden [+1.50 +1.50 +0.00 +0.00 +0.00 +0.00 +0.00 +0.00] out [+0.00 +0.00 +4.50 -3.00]
in [+0.10 +0.00 +0.00 +0.00] hidden [+0.00 +0.00 +0.00 +0.00 +0.00 +0.00 +0.00 +0.00] out [+0.00 +0.00 +0.00 +0.00]
-> the last row is below the -0.5 threshold, so nothing fires and nothing is written
width of the hidden layer, and what it costs
multiplier hidden width FFN params share of block
1x 4,096 33,554,432 33.3%
2x 8,192 67,108,864 50.0%
4x 16,384 134,217,728 66.7%
8x 32,768 268,435,456 80.0%
-> at the usual 4x, the feedforward layer holds two thirds of every block
which activation, and does it matter here?
z ['-3.00', '-2.00', '-1.00', '+0.00', '+1.00', '+2.00', '+3.00']
ReLU ['+0.00', '+0.00', '+0.00', '+0.00', '+1.00', '+2.00', '+3.00']
GELU ['-0.00', '-0.05', '-0.16', '+0.00', '+0.84', '+1.95', '+3.00']
SiLU ['-0.14', '-0.24', '-0.27', '+0.00', '+0.73', '+1.76', '+2.86']
-> GELU and SiLU pass a little signal for small negatives; ReLU passes noneThe lookup table is the useful mental model
Each row of the first matrix is a detector; each column of the second is what gets written. Row 0 detects slot 0 with a threshold of -0.5. Column 0 of the second matrix writes 3.0 into output slot 2. That is one rule.
Rules compose by addition. Feed both patterns at once and both rules fire. The output is the sum of both writes: [0, 0, 4.50, -3.00]. Nothing arbitrates between them. This is why a wide hidden layer can hold many rules that mostly do not interfere.
A weak input triggers nothing. The 0.1 row produced all zeros. The bias sets a threshold, and inputs below it are ignored entirely. In a trained model most hidden units are near zero for most tokens. The layer is far sparser in practice than its parameter count suggests.
Geva et al. (2021) formalised exactly this. They showed the feedforward layers of a trained language model behave as key-value memories. First-layer rows act as pattern detectors over the input. Second-layer columns are the distributions those patterns write out.
Why the multiplier is 4
There is no derivation. The original 2017 transformer used d_ff = 4 * d_model and it stuck.
The table shows the consequence: at 4x, the feedforward layer holds exactly two thirds of the block's weights. Below that, the model is attention-heavy; above it, almost all feedforward. Models that use gated activations shrink the multiplier to keep the count matched — see SwiGLU and gated feedforward layers.
The activation
ReLU was the original. GELU took over with BERT and GPT-2. SiLU, also called Swish, is used inside gated variants.
The printed values show the real difference. For small negative inputs, ReLU gives exactly zero. GELU and SiLU give a small negative. A unit whose output is exactly zero has zero gradient, so it can stop learning permanently. The smooth alternatives avoid that.
Notice that ReLU is closest to the identity for positive values. GELU nearly matches it above +2, and SiLU sits slightly below throughout. None of them changes the layer's structure. See activation functions.
Common mistakes
Leaving out the activation. Two stacked linear layers are one linear layer. The model will train and be capable of almost nothing. This is a real bug people ship.
Putting a normalisation between the two matrices. The standard block normalises the layer's input, not its middle. An extra normalisation there rescales away exactly the magnitude information the detectors are producing.
Assuming the hidden layer is where attention happens. No token communication occurs here at all. The same weights are applied independently to every position.
Optimising attention while ignoring this layer. At short sequence lengths the feedforward layer is where most of the time goes. It is where most of the arithmetic is.
Try it yourself
Add a third rule: make neuron 2 detect slot 2 and write -1.0 into slot 0. Then feed a token that triggers all three at once. Confirm the output is the sum of all three writes. Then set the second matrix's [2, 0] entry to 0.0. That rule becomes a detector that fires and writes nothing. It is what a dead unit looks like from the outside.
What to learn next
- SwiGLU and gated feedforward layers — the version nearly every current model uses.
- Activation functions — the squashing step, in its own right.
- Counting a model's parameters by hand — putting the two-thirds share into real numbers.
Researcher — Mathematics and papers.
Definition
$$ \operatorname{FFN}(x) = W_2 \, \phi(W_1 x + b_1) + b_2 $$
with $W_1 \in \mathbb{R}^{d_{\text{ff}} \times d}$, $W_2 \in \mathbb{R}^{d \times d_{\text{ff}}}$ and $\phi$ an elementwise nonlinearity. Applied identically at every position, so it is a position-wise map $\mathbb{R}^d \to \mathbb{R}^d$ lifted over the sequence axis. Equivalently a $1 \times 1$ convolution over the token axis, which is how the original paper describes it.
Parameters: $2 d\, d_{\text{ff}} + d_{\text{ff}} + d$. At $d_{\text{ff}} = 4d$ that is $8d^2$, against $4d^2$ for attention, giving the two-thirds share.
FLOPs per token: $4 d\, d_{\text{ff}} = 16 d^2$, independent of sequence length. Attention's cost is $8d^2 + 4Td$, so the feedforward layer dominates until $T$ becomes comparable to $6d$.
The key-value memory reading
Geva et al. (2021), Transformer Feed-Forward Layers Are Key-Value Memories, arXiv:2012.14913, decompose the layer as
$$ \operatorname{FFN}(x) = \sum_{i=1}^{d_{\text{ff}}} \phi(k_i \cdot x + b_i) \, v_i $$
where $k_i$ is the $i$-th row of $W_1$ and $v_i$ the $i$-th column of $W_2$. The output is a coefficient-weighted sum of the $v_i$. Coefficients are set by how well $x$ matches each $k_i$.
Empirically, individual $k_i$ correspond to human-interpretable input patterns. Early layers hold shallow n-gram patterns, later layers semantic topics. The corresponding $v_i$, projected through the output head, induce interpretable shifts in the next-token distribution. Geva et al. (2022), arXiv:2203.14680, extend this to show the update is a sub-update in vocabulary space.
Follow-on work built on this reading:
- Meng et al. (2022), ROME, arXiv:2202.05262, localise factual associations to mid-layer feedforward modules via causal tracing. They edit them with a rank-one update to $W_2$.
- Dai et al. (2022), Knowledge Neurons, arXiv:2104.08696, attribute specific facts to individual hidden units.
The caution is the same one that applies throughout mechanistic interpretability. Locating where an intervention succeeds is not the same as locating where a fact is stored. Hase et al. (2023), arXiv:2301.04213, show editing succeeds at layers that causal tracing does not identify. That weakens the localisation claim considerably.
Sparsity
In trained ReLU transformers, the fraction of hidden units active per token is typically a few percent. Li et al. (2023), The Lazy Neuron Phenomenon, arXiv:2210.06313, document this across scales. It strengthens with size. Three practical consequences:
- Conditional computation is viable: skipping inactive units saves real work (Liu et al., 2023, Deja Vu, arXiv:2310.17157).
- Mixture-of-experts is a structured form of the same idea. Each token is routed to a few of many feedforward experts. See Shazeer et al. (2017) and Fedus et al. (2021).
- GELU and SiLU produce small nonzero values rather than exact zeros. The sparsity is approximate, and thresholding is required to exploit it.
Depth against width in the feedforward layer
The layer is a one-hidden-layer MLP, and one hidden layer is the minimum for universal approximation. Making it deeper is possible, and essentially nobody does it. The residual stack already provides depth. A wide shallow MLP maps far better onto matrix-multiply hardware.
Raising $d_{\text{ff}} / d$ above 4 is more common than adding depth. Kaplan et al. (2020), arXiv:2001.08361, report a robust finding. Performance depends far more on total non-embedding parameter count than on aspect ratios. That holds within a wide band. It reasonably justifies a ratio that has stayed at 4 for eight years without much scrutiny.
Papers
- Vaswani et al., Attention Is All You Need, 2017 — arxiv.org/abs/1706.03762
- Geva et al., Transformer Feed-Forward Layers Are Key-Value Memories, 2021 — arxiv.org/abs/2012.14913
- Meng et al., Locating and Editing Factual Associations in GPT, 2022 — arxiv.org/abs/2202.05262
- Hase et al., Does Localization Inform Editing?, 2023 — arxiv.org/abs/2301.04213
- Li et al., The Lazy Neuron Phenomenon, 2023 — arxiv.org/abs/2210.06313
What to learn next
- SwiGLU and gated feedforward layers — the version nearly every current model uses.
- Activation functions — the squashing step, in its own right.
- Counting a model's parameters by hand — putting the two-thirds share into real numbers.