MLPs: The Other Half of Every Block

The MLP (multi-layer perceptron, or feed-forward network) is the second sublayer in every transformer block. It contains ~⅔ of the model’s parameters and operates position-wise: each token’s residual stream vector is processed independently.

This post defines the MLP, derives the key-value memory interpretation (Geva et al., 2021) that underlies most modern MLP interpretability, covers neuron archetypes and superposition, and connects the framework to factual editing (ROME/MEMIT) and sparse autoencoders.


Demo: neuron activations

Interactive · MLP neuron inspector

An MLP block has two linear layers with a non-linearity between them. The intermediate dimension is huge, typically 4× the hidden size. So in distilGPT2, each block has 3,072 MLP neurons. Each one is a tiny pattern detector. Click a neuron below to see what it fires on and what it writes back to the stream.

Residual
[T, 768]
comes in
Win
Up-projection
[T, 3072]
3072 neurons fire (or don't)
GELU
Activation
[T, 3072]
non-linear gate
Wout
Δ Residual
[T, 768]
added to belt
Pick a neuron Examples curated from published MLP-feature research and circuit studies.
,
,
Top-activating contexts ("keys", what triggers this neuron)
    Output direction ("values", what this neuron writes toward)
      Why MLPs as "key-value memories"

      Geva et al. (2021) showed that you can read the up-projection rows as keys (patterns the neuron looks for in the residual stream) and the down-projection columns as values (vectors the neuron writes into the stream when it fires). That decomposition turns each neuron into a key-value pair: "if you see X, write Y." It's the cleanest way to think about MLPs that exists. Bricken et al. (2023) extended this with sparse autoencoders to handle the messy fact that real neurons mix many features in superposition, but the key-value framing is still the foundation.

      Cycle through neurons. Some are monosemantic (Python keywords, capital letters, France-related contexts). Some are polysemantic, firing on multiple unrelated concepts. The polysemantic case is explained by superposition (below).

      Definition

      For input $x \in \mathbb{R}^{d_\text{model}}$ at one position, a transformer MLP computes:

      \[\text{MLP}(x) = W_\text{out}\, \sigma(W_\text{in}\, x + b_\text{in}) + b_\text{out}\]

      where:

      • $W_\text{in} \in \mathbb{R}^{d_\text{ffn} \times d_\text{model}}$
      • $W_\text{out} \in \mathbb{R}^{d_\text{model} \times d_\text{ffn}}$
      • $\sigma$ is a non-linearity (GeLU, ReLU, or in modern models SwiGLU)
      • $d_\text{ffn} = 4 \cdot d_\text{model}$ is the standard ratio

      Sizes:

      Model $d_\text{model}$ $d_\text{ffn}$ Neurons per block
      GPT-2 small 768 3,072 3,072
      GPT-2 XL 1,600 6,400 6,400
      Llama 3 8B 4,096 14,336 14,336
      GPT-3 175B 12,288 49,152 49,152

      Three properties:

      1. Position-wise. No mixing across token positions. Operates in parallel on each token’s residual vector.
      2. Up-projection then down-projection. Hidden width $4\times$ wider than the residual stream. Storage capacity scales with $d_\text{ffn}$.
      3. Non-linearity is essential. Without $\sigma$, two stacked linear layers collapse to one and the MLP cannot represent any nonlinear pattern.

      Key-value memory interpretation

      Decompose $W_\text{in}$ row-wise and $W_\text{out}$ column-wise:

      • Row $n$ of $W_\text{in}$, written $k_n^\top$, is a vector in $\mathbb{R}^{d_\text{model}}$: the key of neuron $n$.
      • Column $n$ of $W_\text{out}$, written $v_n$, is a vector in $\mathbb{R}^{d_\text{model}}$: the value of neuron $n$.

      Then:

      \[\text{MLP}(x) = \sum_{n=1}^{d_\text{ffn}} \sigma(k_n^\top x + b_n)\, v_n\]

      The MLP is a sum of $d_\text{ffn}$ scaled value-vectors, where each scaling coefficient is a non-linearly gated dot product of $x$ with the corresponding key.

      Equivalently:

      • The key $k_n$ tests whether $x$ matches a specific pattern (large $k_n^\top x$ ⇒ match).
      • The non-linearity gates: only neurons whose match exceeds threshold contribute.
      • Each contributing neuron writes its value $v_n$ to the residual stream, scaled by activation.

      This is a soft, sparse key-value lookup over a learned database of $d_\text{ffn}$ entries per block. (Geva et al., 2021)

      Neuron archetypes

      Empirically, MLP neurons fall into recurring categories:

      Surface-feature neurons (early layers)

      Fire on lexical patterns: capital letters, punctuation, specific morphemes, code-syntax tokens. Their values write tags downstream blocks consume.

      Syntactic neurons (mid layers)

      Fire after grammatical patterns: possessives, definite articles, sentence-initial positions. Values bias the next-token distribution toward syntactically valid continuations.

      Factual-recall neurons (mid-to-late layers)

      Encode specific facts. Meng et al. (2022, ROME) demonstrated that “the Eiffel Tower is in Paris” can be located to a small set of neurons in mid layers and surgically edited (so the model claims the Eiffel Tower is in Rome) by modifying $W_\text{out}$ columns at those positions.

      Abstract / semantic neurons (late layers)

      Fire on higher-level patterns: sentiment, sarcasm, discourse markers. Harder to characterize from top-activating examples alone.

      Uninterpretable from top examples

      A non-trivial fraction of neurons have no clean concept-level description. Often these are polysemantic.

      Polysemanticity and superposition

      Most real neurons are polysemantic: top-activating contexts span multiple unrelated concepts.

      Elhage et al. (2022, “Toy Models of Superposition”) explain why. When features are sparse (most off most of the time), a $d$-dimensional space can represent ~$d / \log d$ features by overlapping them at non-orthogonal angles. The non-linearity in the MLP allows partial recovery: only one feature in a superposed pair is typically active in any given input, so interference is bounded.

      Consequences:

      1. The “real” interpretable features are directions (linear combinations of neurons), not single neurons.
      2. Reading individual neuron activations gives a tangled, polysemantic picture.
      3. To recover monosemantic features, train an overcomplete dictionary on cached activations: a sparse autoencoder (SAE).

      Bricken et al. (2023, “Towards Monosemanticity”) and Templeton et al. (2024, “Scaling Monosemanticity”) trained SAEs on Claude 3 Sonnet and recovered millions of monosemantic features ranging from “the Golden Gate Bridge” to “code with security vulnerabilities.”

      Mini · Superposition in 2D

      A neural network has fewer dimensions than concepts. To survive, it packs many features into the same space at near-orthogonal angles. The trick: only a few features are active at once, so they barely interfere. Toggle features on and off and watch the residual vector form. Then read it back out.

      What you're seeing: 8 feature directions packed into 2 dimensions. Each active feature contributes its direction (faded amber arrow); the residual is their sum (dark amber). Below: dot products of the residual with each feature direction, the model's "readout." When few features are on the readout is clean. Turn on too many and they start to interfere. This is why neurons can be polysemantic and why sparse autoencoders work.

      Direct logit attribution for MLPs

      Because each neuron’s contribution to the residual stream is $\sigma(k_n^\top x) v_n$, its contribution to the final logit of token $w$ is:

      \[\Delta\text{logit}_n(w) = \sigma(k_n^\top x)\, v_n^\top W_U[:, w]\]
      Sort neurons by $ \Delta\text{logit}_n(w) $ to identify which neurons drove the prediction. This is MLP-level DLA.
      # in TransformerLens
      mlp_act = cache["post", layer, "mlp"]      # [seq, d_ffn]
      W_out = model.W_out[layer]                 # [d_ffn, d_model]
      W_U = model.W_U[:, answer_id]              # [d_model]
      neuron_dla = mlp_act[-1] * (W_out @ W_U)   # [d_ffn]
      top_neurons = neuron_dla.argsort(descending=True)[:10]
      

      Why MLPs hold the knowledge

      Three lines of evidence support the claim that factual knowledge lives in MLPs:

      1. Parameter share. MLPs are ~⅔ of total parameters. Most learned content is statistically there.
      2. Editing. ROME and MEMIT edit specific facts by modifying MLP weights at specific layers (typically mid-layers, around layer 5–8 in GPT-2 medium). Editing attention weights does not produce the same effect.
      3. Causal tracing. Meng et al. (2022) corrupt subject tokens, then restore individual layers’ activations one at a time and measure which restoration recovers the correct prediction. The signal localizes to mid-layer MLPs.

      A clean operational summary: attention moves information; MLPs add new information. Both contribute additively to the residual stream. Their roles are complementary.

      Mini · Edit a fact in the model (ROME-style)

      Pick a fact. Pick the new answer you want the model to insist on. Drag the edit slider from 0 to 1 to apply a rank-one update to one MLP layer's weights, simulating what the ROME paper does. Watch the prediction shift, and watch a related question to see if your edit generalizes correctly.

      fact:
      The Eiffel Tower is in
      new answer:
      Paris
      91%
      Rome
      0%

      What ROME actually does: the model encodes facts as (key, value) pairs in mid-layer MLPs. ROME locates the layer where the subject's representation peaks, then computes a rank-one update to W_out that swaps the value while preserving the key. The edit propagates to paraphrases of the same fact (the consistency check) without destroying unrelated knowledge. Numbers shown are illustrative; real ROME edits typically achieve 95%+ post-edit probability.

      Activation functions in modern models

      Model Non-linearity Form
      GPT-2 / GPT-3 GeLU $x \cdot \Phi(x)$
      Original Transformer ReLU $\max(0, x)$
      PaLM, Llama, Mistral SwiGLU $\text{Swish}(W_g x) \odot (W_\text{in} x)$

      SwiGLU adds a gating branch:

      \[\text{MLP}_\text{SwiGLU}(x) = W_\text{out}\, (\text{Swish}(W_g x) \odot W_\text{in} x)\]

      This requires three matrices instead of two, but the key-value interpretation extends: each neuron’s “key” is now a (gate, input) pair, and the value is still the corresponding $W_\text{out}$ column. Most MLP interpretability tooling generalizes with minor modification.

      What we have so far

      Component Role Reads Writes
      Embedding Token → vector token IDs residual stream
      Attention Cross-position routing residual stream (all positions) residual stream (current position)
      MLP Stored knowledge / transforms residual stream (current position) residual stream (current position)
      Unembedding Vector → logits residual stream (last position) output distribution

      All four components communicate exclusively via the residual stream. Every interpretability tool in this series operates on that interface.

      The next post runs a full forward pass through GPT-2 small, end-to-end, with concrete numbers at every stage.

      Resources

      Foundational papers

      Tools and code