Opening case

The answer that appeared between two additions

A tiny transformer receives five tokens. Its task is to repeat the first token when it reaches a fixed query marker at the end. For one test sequence, the correct token is 7. A readout from the query position gives token 0 immediately after embedding, token 7 with probability 0.86 after attention, and token 7 with probability almost 1 after the feed-forward sublayer.

The tempting account is simple. Attention found the answer; the feed-forward network merely polished it. Then we intervene. Zero the attention output and the model fails. Zero the feed-forward output and it still predicts 7, but less confidently. Corrupt the first token, then copy only the clean attention write into the corrupted run, and the correct answer returns.

That sounds decisive until a second possibility appears. Another block can write strongly towards 7 in attention and then erase that direction in its feed-forward sublayer. The final output may be identical to a block that never wrote 7 at all. A sensor at the end sees the same result. A sensor between sublayers tells two different causal stories.

The hidden assumption is that each module owns a representation and hands it to the next module. In a residual architecture, modules do not take turns owning the state. They read from and write into a shared stream whose later consumers can reinterpret every earlier contribution. The block becomes intelligible only when we separate the continuity of the stream from the updates written into it.

The representation nobody owns

The residual stream is not another learned module. It is the path along which the current token states persist while sublayers propose changes.

Begin with a river, not a pipeline

A pipeline suggests replacement. Stage one emits a representation; stage two consumes it and emits another. A transformer block is easier to understand as a river. The token embeddings enter as the upstream current. Attention adds a tributary whose content depends on relations among token positions. The feed-forward sublayer adds another tributary whose content depends on the current state at each position. The MLP is position-wise, but that state already contains context routed by attention. Empirical analysis of trained transformers has shown feed-forward updates promoting output-token distributions, which is one reason their residual writes deserve separate inspection.[7] The water downstream contains all three contributions, though later currents may mix or cancel them.

Computation as a dye stream

A residual stream receives attention and feed-forward writes A broad indigo river representing the prior residual state flows left to right. A sky-coloured attention tributary and a teal feed-forward tributary enter it. Normalisation gates sit before the tributaries, and readout markers sample the stream before and after each write. prior state rₗ attention write aₗ feed-forward write mₗ updated state rₗ₊₁ read rₗread after attentionread after MLP normalise normalise the skip path carries prior state around both operators
The useful mental model is additive continuity. Attention and the feed-forward network alter a state that already exists; they do not need to reconstruct it. This makes every downstream reading a mixture of earlier writes, not the property of one module. Illustrative conceptual figure.

One state, three causal views

“Shared state” does not mean that every component sees the same object in the same way. A block exposes three related but distinct views. The read view is the normalised state presented to a sublayer. The write view is the vector that sublayer returns in residual-stream coordinates. The use view is the effect of that changed state on the frozen computation that follows. Treating these as one view is the source of many mistaken explanations.

A direction can persist on the skip path while being invisible to the next normalised read. A component can make a large write that lands in a direction to which the suffix is insensitive. A very small write can matter when the current state sits close to a decision boundary. Conversely, a write that looks decisive under the final unembedding can be cancelled before the model acts on it. Scale, readability and leverage are therefore different properties.

Mechanism · Read, write, use

The block’s causal contract can be stated as three questions: what state did the component condition on, what delta did it add, and which downstream operation changed because of that delta? The residual-stream framework makes the first two questions easy to instrument.[6] The third still requires an intervention. This is why an activation trace is evidence about computation, but not yet a complete explanation of it.

Thought experiment 1 · The silent bypass

Replace both learned sublayers with functions that always output zero. Keep the skip paths. What leaves the block? Exactly what entered. Now remove the skip paths and keep the zero sublayers. Nothing useful survives. One variable changed: the existence of the identity route.

The result reveals the hidden contract. With a skip, the learned function can ask, “What correction should I add?” Without it, the function must ask, “What complete state should I rebuild?” Residual learning was introduced in deep vision networks to make such residual functions easier to optimise than unreferenced mappings.[2] The transformer inherits the same structural advantage, then uses it twice per block.

The smallest honest equation

For a common pre-normalised decoder block, let rl be the matrix of token states entering layer l. It has one row per token position and one column per residual dimension. Let N be a normaliser, A the attention sublayer and M the feed-forward sublayer. The block computes:

ul = rl + Al(Nl,a(rl))
rl+1 = ul + Ml(Nl,m(ul)) Here, ul is the state after attention. Each sublayer reads a normalised view and writes an update in the residual stream’s original width.

Across a stack, the final pre-output state can be written as the embedding contribution plus every attention and feed-forward write observed during that forward pass:

rL = r0 + Σl=0L-1 al + Σl=0L-1 ml The equality is exact for the realised vectors. It does not make the terms independent. Every al and ml is a nonlinear function of the state created by earlier terms.

This distinction matters. The sum tells us where to place readouts and interventions. It does not tell us that a capability belongs to one addend. A late MLP may respond to a feature routed by early attention. An attention head may suppress a direction written by another head. A final normaliser can rescale the whole mixture before unembedding. Additivity of state is not additivity of responsibility.

Addition commutes; conditional writing does not

Vector addition itself is commutative. If two already-computed writes are a and m, then r + a + m equals r + m + a. It is tempting to conclude that the attention and feed-forward branches could be reordered without changing the block. The hidden assumption is that the writes were computed independently of the evolving state.

They are not. In the usual sequential block, attention writes a = A(N(r)). The feed-forward network then writes m = M(N(r + a)). If the order is reversed, the MLP receives r, while attention receives a state already altered by the MLP. Both writes can change. Parallel block variants deliberately change this dependency structure; they are more than another drawing of the same sum.

Worked mechanism · The order test

Ignore normalisation and take a one-number state r = 1. Let attention write A(x) = x², and let the MLP write M(x) = x + 1. Attention first gives 1 + 1 = 2; the MLP then writes 3, so the block ends at 5. Reverse the order: the MLP first writes 2, producing 3; attention then writes 9, so the block ends at 12. The additions did not become non-commutative. The functions generated different addends because the read state changed.

This tiny example removes one common ambiguity. A residual decomposition is a record of the realised forward pass, not a bag of context-free causes that can be rearranged after the fact.

Anatomical cutaway of a pre-LN block

A pre-layer-normalised transformer block with read and write sites The residual state travels along a thick horizontal line. Two branches read a normalised copy, pass it through attention or an MLP, and add their writes back. Five hook points mark the input, each normalised read, each write, and the output. LayerNorm Attention LayerNorm Feed-forward rₗnormalised readaₗuₗnormalised readmₗrₗ₊₁ READWRITEREADWRITE
A block has distinct read and write sites. Instrumenting only the block output hides whether attention routed a feature, the MLP transformed it, or one update cancelled another. The minimum useful trace records r_l, attn_write, u_l, mlp_write and r_l+1. Illustrative architecture view, based on the pre-LN equations.
Worked example 1 · A two-dimensional block

Suppose one token begins at r0 = [2, 0]. An attention head writes a = [0, 1], producing [2, 1]. The MLP then writes m = [-1, 2], producing [1, 3]. An unembedding compares two token directions: token A reads the first coordinate; token B reads the second.

At the input, A wins 2 to 0. After attention, A still wins 2 to 1. After the MLP, B wins 3 to 1. Nothing in the MLP had to reproduce the first two coordinates. It only had to add the update that changed the decision. The example assumes a linear readout and no final normalisation. Add a nonlinear normaliser and the exact logits change, though the additive state path remains.

One token’s path through residual space

A two-dimensional residual trajectory crosses a decision boundary Axes show token A and token B logit directions. The input point at 2,0 moves after attention to 2,1, then after the MLP to 1,3. The final segment crosses the diagonal decision boundary where token B becomes preferred. A = B boundary r₀ = [2,0]after attention [2,1]after MLP [1,3] token A logit token B logit
The decision changes because the MLP writes a correction into a state that already carries attention’s update. A layer-wise trace should therefore show the trajectory, rather than only label one point “the representation”. Illustrative numerical example.
The argument in one pass
Chart · flow
flowchart TD
  A["The answer that appeared<br/>between two additions"] --> B["The representation nobody owns"]
  B --> C["Normalisation decides what a<br/>write can see"]
  C --> D["A visible write is not a<br/>causal contribution"]
  D -. "re-test" .-> A
  classDef first fill:#0a1424,stroke:#5B5CF6,stroke-width:2px,color:#e6f3ff
  classDef step fill:#0a1424,stroke:#3f7da0,stroke-width:1px,color:#e6f3ff
  class A first
  class B,C,D step
A structural route through The block and the residual stream: from The answer that appeared between two… to Treat the block as an auditable update…. Reading use: follow the relation, then return to the surrounding argument for evidence and limits.

Normalisation decides what a write can see

Normalisation is not decorative housekeeping. It changes the coordinates presented to a sublayer and the route through which gradients cross the block.

Change one variable: move the gate

The original Transformer placed layer normalisation after each residual addition: LayerNorm(x + Sublayer(x)).[1] Many later language-model architectures moved normalisation before the sublayer. The forward functions still contain attention, an MLP, addition and normalisation, yet the optimisation problem changes because the identity route is no longer passed through a normaliser at every step.

Thought experiment 2 · Move the measuring gate

A factory conveyor carries a part past two machines. A measuring gate can sit before each machine or after each machine-plus-bypass merge. The same machines and bypasses remain. In the first layout, each machine receives a standardised view while the original part continues along a clean route. In the second, every merged part is standardised before it continues.

Would the factory learn the same adjustments under noisy calibration? Not necessarily. Moving the gate changes which transformations lie on the direct path from early state to late loss. This is the architectural difference between common pre-LN and post-LN blocks.

What layer normalisation actually removes

For one token vector x with d features, LayerNorm subtracts the feature mean, divides by the feature standard deviation, then applies learned gain and bias. Ba, Kiros and Hinton introduced it as a per-case alternative to batch-dependent normalisation, with the same computation at training and test time.[3]

LN(x)i = γi · (xi - μ(x)) / √(σ²(x) + ε) + βi μ and σ² are computed across the features of this token. γ and β are learned per-feature scale and shift parameters.
Worked example 2 · Shift the whole vector

Take x = [3, 1, -1]. Its mean is 1, so the centred vector is [2, 0, -2]. Now add 5 to every coordinate, giving [8, 6, 4]. The centred vector is still [2, 0, -2]. Ignoring learned gain, bias and the small numerical constant, LayerNorm presents the same direction to the next sublayer.

RMSNorm removes the mean subtraction and divides by root mean square instead. It preserves invariance to positive global rescaling but not to uniform shifts, reducing the operation while changing the symmetry it enforces.[5] The choice therefore says which global changes a sublayer should treat as irrelevant.

Normalisation creates equivalence classes

Ignoring the small stabilising constant, LayerNorm maps every vector of the form αx + c1, for positive α, to the same centred and standardised pattern before learned gain and bias. The sublayer cannot distinguish a uniform shift c1, and it treats a positive global rescaling as the same direction. RMSNorm identifies positive rescalings but keeps sensitivity to uniform shifts. These are not philosophical claims about what the model “knows”. They are exact or approximate symmetries of the read operation.

Pre-LN then creates a useful separation. The raw residual state, including components that the normaliser suppresses, continues along the identity route. The branch reads only the equivalence class selected by the normaliser. A fact can therefore be present in the stream yet unavailable to a particular branch under its read map. Whether it later affects output depends on later normalisers, projections and writes. Presence in state and addressability by a sublayer are not the same condition.

The gradient path exposes the same architectural distinction. For a simplified pre-LN update y = x + F(N(x)), the Jacobian is I + JFJN. It contains an explicit identity term. For post-LN, y = N(x + F(x)), the Jacobian is JN(I + JF); the direct route is filtered by the normaliser’s Jacobian. Xiong and colleagues used this difference to analyse why pre-LN and post-LN transformers exhibit different gradient behaviour at initialisation.[4] The identity term does not guarantee good optimisation. It explains which route exists before data and training reshape it.

Pre-LN and post-LN put different operators on the identity route

Comparison of pre-layer and post-layer normalised blocks The left panel shows a clean identity path with normalisation inside the residual branch. The right panel shows normalisation after the branch merges into the identity path. Backward gradient arrows follow the direct path in opposite directions. Pre-LNPost-LN Norm Sublayer direct gradient route includes an identity term Sublayer + Norm gradient repeatedly crosses normalisation xyxy
The diagrams contain the same named ingredients but different computational paths. Xiong and colleagues analysed this placement difference and found well-behaved gradients at initialisation for their pre-LN setting, while their post-LN setting required warm-up to avoid instability near the output.[4] That result is scoped to their assumptions and experiments; it is not a universal ranking of every normalisation layout. Architecture synthesis from published formulations.

A small experiment makes the path visible

To expose the mechanism without claiming a new benchmark, we initialised matched 18-block synthetic networks. Each block contained two small GELU feed-forward operators standing in for attention and the MLP. One stack used pre-LN; the other used post-LN. We measured the median parameter-gradient norm at each depth across 30 random seeds under the same mean-squared-error objective.

Normalisation placement changes the gradient profile

Median gradient norms across synthetic pre-LN and post-LN stacks A teal pre-LN curve remains near 0.10 to 0.12 across 18 layers. A coral post-LN curve starts near 0.056 and rises towards 0.077 near the output. The chart is a synthetic illustration, not a performance benchmark. 0.040.060.080.100.120.14 161218block depth, input to outputmedian parameter-gradient norm Pre-LNPost-LN
The experiment does not prove that one layout always trains better. It shows the causal point: moving normalisation changes the depth profile even when the operator family, width, data and objective are matched. The full generator is included in the executable appendix. Synthetic measurement: 18 blocks, width 48, 30 seeds, median gradient norm. Fixed code and data-generating process included below.
Evidence status · Established architecture, active interpretation

The ingredients and equations are established. Their best placement remains an active design problem. Peri-LN adds output normalisation around sublayers and reported lower variance growth and improved stability in its tested pre-training settings.[11] Other work has replaced statistical normalisation with bounded pointwise functions such as Dynamic Tanh, and a later Derf variant reported stronger results in its tested domains.[12][13]

The safe conclusion is narrower than “normalisation is essential”. Deep blocks need some workable control of scale, sensitivity and signal propagation. LayerNorm, RMSNorm and newer alternatives implement different versions of that control.

Boundary condition

Do not infer semantic importance from normalised magnitude alone. LayerNorm couples coordinates through a shared mean and variance; RMSNorm couples them through shared root mean square. A large raw write can be damped, and a small directional write can matter if it aligns with a sensitive downstream readout. Norms describe scale, not meaning.

A visible write is not a causal contribution

Residual additivity makes intermediate inspection convenient. It also creates a trap: direct readability can be mistaken for responsibility.

The logit lens is a question, not an answer

A decoder-only language model usually applies a final normaliser and an unembedding matrix to the last residual state. If we reuse that output machinery at an earlier layer, we obtain an intermediate vocabulary distribution. This “logit lens” asks which tokens are readable from the current state under the final decoder. A tuned lens learns a separate affine translator at each layer and has been shown to improve reliability over the raw logit lens in tested models.[8]

The readout is useful because it turns an opaque vector into a prediction trajectory. Its assumption is strong: the intermediate representation is sufficiently aligned with the final decoder. Earlier layers may use a different basis or encode information for later nonlinear processing. Final normalisation also means that raw component projections do not always compose as a simple sum unless the normalisation effect is handled explicitly.

Thought experiment 3 · The same mouth, different tributaries

System A begins with zero support for token P. Attention writes +4 units towards P. The MLP writes -1, leaving +3. System B has attention write 0 and MLP write +3. Both finish at +3, so their final logits match.

Now ablate attention. System A falls to -1; System B stays at +3. The output alone underdetermines the route. Direct attribution to the final token direction reveals that A’s attention promoted P, but only an intervention reveals that downstream computation depended on that write in this context.

Promotion, erasure and the misleading snapshot

Two transformer routes reach the same final logit through different writes The upper route has an attention write of plus four and an MLP write of minus one. The lower route has no attention write and an MLP write of plus three. Both finish at plus three, but attention ablation affects only the upper route. Route A: write, then partial erasure Route B: late construction 0 Attention+4 MLP-1 +3 0 Attention0 MLP+3 +3 later computation can erase an earlier direct contribution same final readout, different intervention response
Direct logit attribution can identify a component that promotes or suppresses a token direction, but later components may erase that effect. Janiak and colleagues demonstrated a concrete erasure case in a four-layer transformer where direct logit attribution was misleading unless later memory-management behaviour was considered.[10] Illustrative routes; empirical failure mode supported by the cited study.

Direct projection is local accounting

Let dp,q be the difference between the unembedding directions for tokens p and q. For a component write w, the quantity dp,qTw is its direct contribution to the p-versus-q logit difference under a linear readout. This is a clean accounting identity when no final normaliser intervenes. With a final LayerNorm or RMSNorm, the exact output change depends on the whole residual state, so analysts either apply the full readout to counterfactual states or use a stated local approximation.

Even when computed exactly, direct projection answers only what the write points towards at that site. It does not include how the write changes a later attention pattern, opens an MLP feature, alters normalisation statistics or triggers a compensating write. For that reason, three numbers must remain separate:

QuantityQuestion answeredWhat it can miss
Direct writeWhich output direction does this delta favour immediately?Later routing, nonlinear use, erasure and final normalisation.
Realised effectHow does the chosen metric change when the branch is replaced?Redundancy, off-manifold damage and dependence on the replacement rule.
Patch recoveryCan a clean internal difference restore behaviour in a matched corrupted run?Collateral information carried by a broad patch and incompatibility with the receiving state.

No one scalar is “the contribution”. Each is an answer to a different counterfactual. Agreement strengthens a mechanism claim. Disagreement reveals interaction structure that a component leaderboard would hide.

Separate three questions

The central practical contribution of this paper is a Residual Contribution Audit. It separates three properties that are often collapsed:

Write asks whether a component changed the residual state in a specified direction or subspace. Survival asks whether that change remains available after later layers, rather than being rotated, overwritten or cancelled. Use asks whether changing that write changes the tested behaviour through the model’s own downstream computation.

A fourth diagnostic, recovery, asks whether restoring the clean write into a corrupted run recovers the clean result. Recovery is especially informative because it tests a counterfactual carrier: can this state difference transmit the relevant information when placed back into the original computation?

The Residual Contribution Audit

Diagnostic

1. Write

Measure the component output, its norm, direction and direct readout. Ask what changed at the write site.

Trajectory

2. Survival

Track the candidate direction through later residual states. Test rotation, amplification, cancellation and erasure.

Intervention

3. Use

Ablate, resample or replace the write and measure the behavioural effect with non-target controls.

Counterfactual

4. Recovery

Patch the clean write into a matched corrupted run. Ask whether the predicted output and intermediate route return.

A component should not be called responsible merely because one cell is positive. Write without use is an unused or redundant signal. Use without a clean direct readout may indicate a nonlinear or distributed route. Recovery without selectivity may reflect a broad state transplant rather than the intended mechanism. Practitioner synthesis introduced in this paper.
Worked example 3 · A cancellation index

Suppose attention writes a vector whose projection onto the “Paris minus Lyon” logit direction is +4.0. The next MLP writes -3.2 on the same direction. The final net support attributable to those two direct projections is +0.8. A simple cancellation index is 1 - |net| / (|positive| + |negative|). Here it equals 0.89.

The number does not prove that the components implement one semantic feature. It flags an interaction that a snapshot can miss. A high index tells the analyst to test erasure and compensation before ranking modules by direct contribution.

Intervene on matched runs

Causal tracing and activation patching compare a clean run with a matched corrupted run. An internal activation from one run is inserted into the other, and the effect on a defined metric is measured. Meng and colleagues used a causal intervention of this kind to identify states that mediated factual predictions in the GPT models they studied.[9]

Ablation and patching answer different questions. Ablation asks whether the current route is necessary under a chosen replacement, often zero or a resampled activation. Clean-to-corrupt patching asks whether a state is sufficient to restore some behaviour in the corrupted context. Neither establishes unique responsibility. Redundant routes can hide necessity. A large patch can carry collateral information. Off-manifold replacements can create damage that the endogenous system never encounters.

Build the rival generator before trusting the result

A causal-looking pattern can be produced by several mechanisms. The practical defence is to name the strongest rival before running the intervention, then add the control that would separate it from the preferred explanation.

Observed resultRival generatorDiscriminating control
Ablation has little effectA redundant route carries the same function.Test paired or path-specific ablations and look for compensatory writes.
Ablation has a large effectZeroing created an off-distribution state.Compare zero, mean, resampled and reconstruction-preserving replacements.
A patch restores the targetThe patch transplanted broad correlated information.Measure non-target drift and patch a narrower subspace or token site.
An answer appears earlyThe final decoder happens to align with an unfinished state.Compare a tuned translator, suffix compatibility and downstream intervention.
A component ranks highlyNormalisation or basis choice inflated its direct projection.Recompute through the exact output path and test stable effects across bases and prompts.

This discipline changes a negative result as well. A failed patch does not mean the information was absent. It may mean the patch site was too broad, too narrow, mistimed or incompatible with the corrupted context. The result lowers one specified mechanism claim, not every possible account of the behaviour.

Thought experiment 4 · The early answer mirage

At layer six, a logit lens already predicts “Paris”. Freeze that residual state and attach the model’s final unembedding. The answer looks complete. Now replace layers seven to twelve with a separately trained suffix that expects a rotated representation. The same layer-six state no longer yields Paris through the suffix, even though the direct lens still does.

What changed? Not the information visible to the external decoder. What changed was compatibility with the model’s downstream consumers. Linear readability is a relation between a state and a decoder; causal use is a relation between a state and the computation that actually follows it.

Open research hypothesis · Interaction-aware attribution

For behaviours with strong cancellation or redundancy, a component ranking that combines patch recovery, survival and pairwise interaction will predict out-of-sample behavioural effects better than direct logit attribution alone. The smallest useful test is a synthetic task with known circuits and deliberately trained eraser components.

The hypothesis gains support if the combined audit predicts held-out intervention effects across prompts and seeds. It weakens if direct readout performs equally well once normalisation is handled correctly. A rival explanation is that any gain comes from using more measurements rather than from the write-survival-use distinction itself.

How the claim changes under pressure
Chart · state
stateDiagram-v2
  [*] --> Claim
  state "Begin with a river, not a pipeline" as Claim
  state "Computation as a dye stream" as Test
  state "One state, three causal views" as Revision
  Claim --> Test: expose mechanism
  Test --> Decision: evidence survives
  Test --> Revision: rival survives
  Revision --> Claim: narrow or rebuild
  Decision --> [*]
The revision loop begins with Begin with a river, not a pipeline and keeps counterexamples inside the reasoning path. Reading use: follow the relation, then return to the surrounding argument for evidence and limits.

Build the block so it can answer for itself

The most useful implementation exposes intermediate state, component writes and counterfactual controls without changing the model’s normal forward path.

Five hook points are enough to begin

For each block, record the incoming residual state, the attention write, the post-attention state, the feed-forward write and the outgoing state. Record normalised reads when the question concerns what a sublayer detected. Bind every trace to the exact model weights, input tokens, mask, normalisation configuration and decoding metric.

Do not log only attention weights. They show how value vectors were mixed, not the full vector written after value and output projections. Do not log only hidden states. Their differences conflate all writes between two sites. A trace becomes causally useful when it preserves both the state and the delta that changed it.

The smallest valid audit packet

A convincing residual experiment needs more than tensors. It needs a contract that makes the counterfactual reproducible. The smallest useful packet contains four linked records.

Experimental protocol · Four records

Baseline pair. Store a clean input and a minimally corrupted input that differ in one declared causal property. Verify that the output metric changes before touching the model internally.

Intervention contract. Name the model version, layer, token positions, component, tensor shape, replacement source and timing. “Ablate head 3” is incomplete until zeroing, mean replacement, resampling or another rule is specified.

Metric bundle. Record the target metric, at least one non-target behaviour, the full output distribution where feasible, and intermediate survival measures. A patch that recovers one logit while scrambling everything else is not selective evidence.

Scope receipt. Repeat across prompts, seeds and matched controls, then state the population to which the result applies. Version normalisation, tokenizer, mask, precision and decoding because each changes the receiving computation.

Two invariants make the packet auditable. The clean and corrupted runs must use the same frozen parameters and execution settings. The intervention must alter only the declared tensor sites. Hashes or deterministic checks can verify both. A positive result then permits a narrow statement about causal leverage for that metric and context. A negative result lowers that statement, but cannot establish global absence or lack of representation.

This packet is intentionally smaller than a full interpretability platform. It can be implemented with forward hooks, saved activations and a metric function. Its value lies in forcing the analyst to expose assumptions that prose otherwise hides.

A hooked block inside a controlled experiment

System boundary for a residual stream experiment A clean input and corrupted input enter the same frozen model. A trace collector records five sites in each block. An intervention controller can ablate or patch one write. A metric compares output recovery and collateral drift. Frozen transformer under test Instrumented blockr₀ · attention write · r₁ · MLP write · r₂ remaining frozen suffix final norm + unembedding Clean inputtarget relation intact Corrupted inputone causal property changed Trace collectorversioned states + writes Interventionablate · replace · patch Metricrecovery + collateral drift Solid lines: executed model path. Dotted lines: experimental control and evidence flow.
The candidate mechanism is tested inside a matched experiment, not inferred from one activation dump. Clean and corrupted inputs use the same frozen model; only the declared intervention differs. The metric must include both target recovery and non-target drift. Practitioner system design.

A realistic configured example

The executable lab trains a one-block, one-task decoder on synthetic sequences. The last position contains a fixed query token; the target is the first token. Causal attention permits the query position to read earlier positions. The model is small enough to train on a CPU in seconds, yet it contains token and positional embeddings, pre-LN multi-head attention, a GELU MLP, residual additions, a final normaliser and tied unembedding.

On the fixed seed supplied below, the model reaches perfect batch accuracy on the synthetic task. For the held-out sequence [7, 3, 9, 5, 0], the target probability is near zero at the embedding readout, 0.86 after the attention write, and approximately 1 after the MLP. Attention ablation reduces target probability to about 0.007. MLP ablation leaves the correct top token with probability 0.86. Patching the clean attention write into a corrupted sequence restores the clean answer.

Those numbers establish only what the configured experiment tests: attention carries the first-token information to the query position in this trained model, and the MLP sharpens the readout. They do not establish a general role for attention or MLPs in language models.

Executable lab · Residual Contribution Audit

Move two writes and watch one decision change

The browser lab is a causal skeleton of one token position. The horizontal coordinate supports token A; the vertical coordinate supports token B. Change the attention and MLP writes, then ablate either branch. The readout updates immediately.

-0.4
1.2
-0.6
0.8
After attention
Final state
Preferred token
Cancellation index

Residual writes move a token decision

Interactive two-dimensional residual stream The input vector, attention-updated vector and final vector are shown as connected points against the token A versus token B decision boundary. inputafter attentionfinal token A supporttoken B support
The browser lab makes the audit’s causal skeleton visible. The input state persists, attention and the MLP add separate vectors, and ablation removes one realised write at a time. The diagonal marks equal support for the two candidate tokens. Interactive, synthetic two-dimensional example.

Positive result: a branch changes the chosen token or recovers it under a patch. Negative result: the tested branch lacks causal leverage under this replacement and context. The toy cannot establish how a trained language model represents any real concept.

Decision rule

Promote a component from “interesting” to “mechanistic” only after its readout, ablation and patch results agree under matched controls. When they disagree, report the disagreement. It is often the most informative result.

Run the full Python experiment

Save the code as residual_stream_lab.py, install PyTorch, and run it with Python 3.10 or later. It trains only on generated integer sequences. No external dataset or model download is required.

Full runnable PyTorch implementation
Python · residual_stream_lab.py
from __future__ import annotations

import statistics
import torch
from torch import nn
import torch.nn.functional as F

# Reproducible, synthetic experiment.
torch.manual_seed(7)
torch.set_num_threads(1)

VOCAB = 12
SEQ = 5
D_MODEL = 32
HEADS = 4


class Block(nn.Module):
    def __init__(self, d_model: int = D_MODEL) -> None:
        super().__init__()
        self.ln1 = nn.LayerNorm(d_model)
        self.attn = nn.MultiheadAttention(
            d_model, HEADS, batch_first=True
        )
        self.ln2 = nn.LayerNorm(d_model)
        self.mlp = nn.Sequential(
            nn.Linear(d_model, 4 * d_model),
            nn.GELU(),
            nn.Linear(4 * d_model, d_model),
        )

    def forward(
        self,
        x: torch.Tensor,
        *,
        ablate_attn: bool = False,
        ablate_mlp: bool = False,
        patch_attn: torch.Tensor | None = None,
        trace: dict[str, torch.Tensor] | None = None,
    ) -> torch.Tensor:
        r0 = x
        z = self.ln1(r0)
        causal_mask = torch.triu(
            torch.ones(SEQ, SEQ, dtype=torch.bool), diagonal=1
        )
        attn_write, _ = self.attn(
            z, z, z, attn_mask=causal_mask, need_weights=False
        )

        if ablate_attn:
            attn_write = torch.zeros_like(attn_write)
        if patch_attn is not None:
            attn_write = attn_write.clone()
            attn_write[:, -1, :] = patch_attn

        r1 = r0 + attn_write
        mlp_write = self.mlp(self.ln2(r1))
        if ablate_mlp:
            mlp_write = torch.zeros_like(mlp_write)
        r2 = r1 + mlp_write

        if trace is not None:
            trace.update(
                r0=r0.detach(),
                attn_write=attn_write.detach(),
                r1=r1.detach(),
                mlp_write=mlp_write.detach(),
                r2=r2.detach(),
            )
        return r2


class TinyCopyModel(nn.Module):
    """At the final query position, predict the first token."""

    def __init__(self) -> None:
        super().__init__()
        self.token_embedding = nn.Embedding(VOCAB, D_MODEL)
        self.position = nn.Parameter(torch.randn(SEQ, D_MODEL) * 0.02)
        self.block = Block()
        self.final_norm = nn.LayerNorm(D_MODEL)
        self.unembed = nn.Linear(D_MODEL, VOCAB, bias=False)
        self.unembed.weight = self.token_embedding.weight

    def forward(self, tokens: torch.Tensor, **block_kwargs) -> torch.Tensor:
        x = self.token_embedding(tokens) + self.position
        x = self.block(x, **block_kwargs)
        return self.unembed(self.final_norm(x[:, -1, :]))

    def readout(self, residual: torch.Tensor) -> torch.Tensor:
        return self.unembed(self.final_norm(residual[:, -1, :]))


def synthetic_batch(size: int = 128) -> tuple[torch.Tensor, torch.Tensor]:
    tokens = torch.randint(0, VOCAB, (size, SEQ))
    tokens[:, -1] = 0  # fixed query marker
    target = tokens[:, 0].clone()
    return tokens, target


def train_copy_model(steps: int = 700) -> TinyCopyModel:
    model = TinyCopyModel()
    optimiser = torch.optim.AdamW(model.parameters(), lr=2e-3)

    for step in range(steps):
        tokens, target = synthetic_batch()
        logits = model(tokens)
        loss = F.cross_entropy(logits, target)
        optimiser.zero_grad()
        loss.backward()
        optimiser.step()

        if step in {0, 99, 299, steps - 1}:
            accuracy = (logits.argmax(-1) == target).float().mean().item()
            print(
                f"step={step + 1:3d} loss={loss.item():.4f} "
                f"batch_accuracy={accuracy:.3f}"
            )
    return model.eval()


def audit(model: TinyCopyModel) -> None:
    clean = torch.tensor([[7, 3, 9, 5, 0]])
    corrupt = torch.tensor([[2, 3, 9, 5, 0]])
    trace: dict[str, torch.Tensor] = {}

    with torch.no_grad():
        clean_logits = model(clean, trace=trace)
        corrupt_logits = model(corrupt)
        no_attention = model(clean, ablate_attn=True)
        no_mlp = model(clean, ablate_mlp=True)
        patched = model(
            corrupt,
            patch_attn=trace["attn_write"][:, -1, :],
        )

    print("\nLayer-wise readouts for target token 7")
    stages = {
        "embedding": trace["r0"],
        "after attention": trace["r1"],
        "after MLP": trace["r2"],
    }
    for name, residual in stages.items():
        logits = model.readout(residual)
        probability = logits.softmax(-1)[0, 7].item()
        print(
            f"{name:16s} p(target)={probability:.3f} "
            f"top={logits.argmax(-1).item()}"
        )

    print("\nInterventions")
    cases = {
        "clean": clean_logits,
        "attention ablated": no_attention,
        "MLP ablated": no_mlp,
        "corrupt": corrupt_logits,
        "corrupt + clean attention patch": patched,
    }
    for name, logits in cases.items():
        probability = logits.softmax(-1)[0, 7].item()
        print(
            f"{name:34s} p(target)={probability:.3f} "
            f"top={logits.argmax(-1).item()}"
        )

    print("\nWrite norms at the query position")
    print("attention", trace["attn_write"][0, -1].norm().item())
    print("MLP      ", trace["mlp_write"][0, -1].norm().item())


# Optional: reproduce Figure 5 with matched synthetic stacks.
class FFOperator(nn.Module):
    def __init__(self, d_model: int) -> None:
        super().__init__()
        self.net = nn.Sequential(
            nn.Linear(d_model, 4 * d_model),
            nn.GELU(),
            nn.Linear(4 * d_model, d_model),
        )

    def forward(self, x: torch.Tensor) -> torch.Tensor:
        return self.net(x)


class PreLNBlock(nn.Module):
    def __init__(self, d_model: int) -> None:
        super().__init__()
        self.n1, self.n2 = nn.LayerNorm(d_model), nn.LayerNorm(d_model)
        self.a, self.m = FFOperator(d_model), FFOperator(d_model)

    def forward(self, x: torch.Tensor) -> torch.Tensor:
        x = x + self.a(self.n1(x))
        return x + self.m(self.n2(x))


class PostLNBlock(nn.Module):
    def __init__(self, d_model: int) -> None:
        super().__init__()
        self.n1, self.n2 = nn.LayerNorm(d_model), nn.LayerNorm(d_model)
        self.a, self.m = FFOperator(d_model), FFOperator(d_model)

    def forward(self, x: torch.Tensor) -> torch.Tensor:
        x = self.n1(x + self.a(x))
        return self.n2(x + self.m(x))


def gradient_profile(seeds: int = 30, depth: int = 18, d_model: int = 48):
    medians: dict[str, list[float]] = {}
    for name, block_type in {"pre": PreLNBlock, "post": PostLNBlock}.items():
        runs: list[list[float]] = []
        for seed in range(seeds):
            torch.manual_seed(seed)
            blocks = nn.ModuleList([block_type(d_model) for _ in range(depth)])
            output = nn.Linear(d_model, d_model)
            x = torch.randn(8, 6, d_model)
            target = torch.randn_like(x)
            y = x
            for block in blocks:
                y = block(y)
            loss = F.mse_loss(output(y), target)
            loss.backward()
            runs.append([
                statistics.mean(
                    p.grad.detach().norm().item()
                    for p in block.parameters()
                    if p.grad is not None
                )
                for block in blocks
            ])
        medians[name] = [
            statistics.median(run[layer] for run in runs)
            for layer in range(depth)
        ]
    print("\nMedian gradient profiles")
    for name, values in medians.items():
        print(name, [round(value, 5) for value in values])


if __name__ == "__main__":
    trained_model = train_copy_model()
    audit(trained_model)
    gradient_profile()

What it tests: layer-wise readout, attention and MLP ablation, clean-to-corrupt patch recovery, component write magnitude, and a matched pre-LN versus post-LN gradient illustration. What it cannot establish: a general decomposition of language-model cognition, semantic localisation, or unique causal responsibility.

Expected output and interpretation

Exact decimals can vary with PyTorch version, but the fixed seed should produce the same qualitative pattern: training reaches near-perfect accuracy; the target becomes readable after attention; attention ablation destroys the answer; MLP ablation reduces confidence; and patching the clean attention write into the corrupted run restores the target. The gradient profile should show distinct depth shapes for the two layouts.

A positive patch result permits a scoped statement: the patched state is a sufficient carrier for recovering the chosen metric in the corrupted context. A negative patch result means this site, replacement and metric did not restore the behaviour. It does not prove the information was absent, because it may be distributed, encoded elsewhere or incompatible with the receiving context.

The strongest alternative explanation

The residual-stream view can itself become too coarse. A component output is a vector that may superpose many features. One block may contain several circuits with opposing effects. Layer boundaries are engineering units, not guaranteed cognitive units. Gated residuals, mixture-of-experts MLPs, parallel attention-MLP layouts and normalisation-free variants change the exact contract.

The alternative is not to abandon the residual view. It is to treat it as a measurement coordinate. It tells us where state persists and where updates enter. Feature-level explanations still require finer decompositions, controlled interventions and stability across inputs. The residual stream is a causal workspace in the architectural sense, not a certificate that its axes correspond to human concepts.

The evidence conversation
Chart · sequence
sequenceDiagram
  participant Q as Question
  participant M as Mechanism
  participant E as Evidence
  participant D as Decision
  Q->>M: The answer that appeared between two additions
  M->>E: Begin with a river, not a pipeline
  E-->>M: compare rival explanations
  E->>D: Treat the block as an auditable update contract
  D-->>Q: revise scope or act
Question, mechanism, evidence and decision exchange the burdens developed in The block and the residual stream. Reading use: follow the relation, then return to the surrounding argument for evidence and limits.

Treat the block as an auditable update contract

When reading or designing a transformer, stop asking which module “contains the representation” as though attention and the MLP pass ownership of a single object. Ask which state persists, which normalised view each sublayer reads, what vector it writes, and which later operations consume that change.

For architecture, this changes the unit of comparison. A block variant is not defined by a shopping list of attention, feed-forward layers, skips and normalisers. It is defined by their placement on the read, write and gradient paths. Moving one normaliser can change optimisation even when the named ingredients remain. Replacing LayerNorm with RMSNorm, DyT or another bounded operator changes invariances and sensitivity control, rather than only speed.

For interpretability, this changes evidential standards. Intermediate logits are excellent hypothesis generators. Component norms and direct projections are useful diagnostics. Neither proves use. Apply the Residual Contribution Audit: establish the write, test its survival, intervene on its use, and seek recovery in a matched counterfactual. Report cancellation and collateral drift rather than hiding them behind one attribution score.

For implementation, this changes observability. Expose state and delta separately at every block boundary. Version the exact normalisation and decoding path. Preserve clean and corrupted traces. Make ablation and patching ordinary test operations rather than one-off research surgery.

The decision is therefore concrete: treat the transformer block as an auditable update contract over shared state. Design it by tracing read and write paths. Explain it by interventions on those paths. Release claims only at the level the evidence supports. The residual stream makes deep composition possible, but only a causal audit tells us which currents actually moved the answer.

Glossary

Residual stream
The evolving token-state matrix carried across blocks by residual additions.
Residual write
The vector or token-state update produced by an attention or feed-forward sublayer and added to the stream.
Skip path
The identity route that carries the prior state around a learned sublayer.
Pre-LN
A block layout in which normalisation is applied before the learned sublayer inside the residual branch.
Post-LN
A block layout in which normalisation follows the residual addition.
Direct readout
A projection of an intermediate state or component write into an output space, often vocabulary logits.
Survival
The extent to which a candidate write remains available after later layers transform the residual state.
Activation patching
Replacing an internal activation in one run with the corresponding activation from a matched run and measuring the effect.
Patch recovery
The restoration of a clean metric when a clean internal state is inserted into a corrupted computation.
Cancellation index
A diagnostic for opposing direct projections; it flags interaction but does not identify a semantic feature.

References

  1. Vaswani, A. et al. (2017). Attention Is All You Need. Advances in Neural Information Processing Systems 30.
  2. He, K., Zhang, X., Ren, S. and Sun, J. (2016). Deep Residual Learning for Image Recognition. CVPR.
  3. Ba, J. L., Kiros, J. R. and Hinton, G. E. (2016). Layer Normalization.
  4. Xiong, R. et al. (2020). On Layer Normalization in the Transformer Architecture. ICML.
  5. Zhang, B. and Sennrich, R. (2019). Root Mean Square Layer Normalization. NeurIPS.
  6. Elhage, N. et al. (2021). A Mathematical Framework for Transformer Circuits. Transformer Circuits Thread.
  7. Geva, M., Schuster, R., Berant, J. and Levy, O. (2021). Transformer Feed-Forward Layers Are Key-Value Memories. EMNLP.
  8. Belrose, N. et al. (2023). Eliciting Latent Predictions from Transformers with the Tuned Lens.
  9. Meng, K., Bau, D., Andonian, A. and Belinkov, Y. (2022). Locating and Editing Factual Associations in GPT. NeurIPS.
  10. Janiak, J., Rager, C., Dao, J. and Lau, Y. T. (2024). An Adversarial Example for Direct Logit Attribution: Memory Management in GELU-4L. BlackboxNLP.
  11. Kim, J. et al. (2025). Peri-LN: Revisiting Normalization Layer in the Transformer Architecture. ICML.
  12. Zhu, J., Chen, X., He, K., LeCun, Y. and Liu, Z. (2025). Transformers without Normalization. CVPR.
  13. Chen, M. et al. (2026). Stronger Normalization-Free Transformers. CVPR.