~/satyajit

Neuron lens: what a neuron's weights say, and what steering it does, checked on a 0.6B model

mdjsonmcp

2026-10-31 · 22 min · interpretability · qwen · transformers · small-models

Why read this

Hightop 30%

Explains the J, R, K and S lenses from the methods notes and re-runs the steering and input-side checks on Qwen3-0.6B with held-out documents and controls.

  • Interactive explanations
  • Original analysis
  • Runs on a laptop CPU

InterpretabilityCC-BY-4.0Research dataset

How this was scored
Is it new?
2 of 3: A real new idea, method or capability
Can I trust it?
2 of 3: Measures key facts from files, code or configs
Can I run it?
2 of 3: Open code or weights with real limits
Will I understand it?
3 of 3: Mechanism carried by interactives built from real code or data
Can I act on it?
2 of 3: A concrete recipe, numbers or comparison
Will it last?
1 of 3: Relevant for months
Does it affect many?
1 of 3: A specialist community
Only here?
2 of 3: A teardown or measurement few others did

Score 68 of 100, ranked 173 of 666 rated articles. Each question is answered 0–3 by hand, and a 3 is rare. How articles are scored

On 30 October 2026 two researchers, a9lim and treetown, published Neuron Lens: a public catalog of what every MLP neuron and every attention head in two open models responds to, and what it makes the model say. Qwen3-8B has 36 layers of 12,288 neurons and 32 heads, which is 442,368 neurons and 1,152 heads. Gemma 4 12B has 48 layers of 15,360 neurons and 16 heads, which is 737,280 neurons and 768 heads. Each unit is read three ways: from its weights through two Jacobian lenses, from its activations over 15 million tokens of web, Chinese, Python and chat text, and from the exact tokens that drive it. Claude Haiku 5.5 then wrote a label for every pole, at a reported cost of $105 for the 884,736 poles of Qwen3-8B (reported; the sums are mine). The data is CC BY 4.0, the site is AGPL-3.0.

I wrote about finding a neuron and steering it a few days ago, on a small model. That article asks what happens when you push a neuron. This catalog asks the earlier question: what does the neuron say it is for, and can you tell from the weights alone? The authors report that you often can. I wanted to see how much of that survives on a model I can run on a laptop CPU, so this page explains the lenses from the authors' methods notes, summarises what they claim and how they checked, and re-runs two of their checks on Qwen3-0.6B with my own code, on held-out documents with controls.

stdlogits/neuron-lens@07fdf45 · snapshot 2026-10-31
tracked files
190
license
MIT
branch
main
tests
4 files
source
738.3 kB
commit date
2026-10-30
source by language
Python736.2 kB(60)Shell2.1 kB(1)

by size of tracked source at this commit, file counts in brackets; docs, data and vendored trees excluded

local clone, 2026-10-31 at 07fdf45 — branch, commit, commitDate, fileCount, hasTests, languages, license, licenseFile, shallow, testFileCount

shallow clone: counts describe the pinned tree, not the history

Qwen/Qwen3-0.6B@c1899de · snapshot 2026-10-26
parameters
751.6M
repo size
4.53 GB
architecture
Qwen3ForCausalLM
task
text-generation
library
transformers
license
apache-2.0
safetensors
1 shard
largest file
1.50 GB
files
10
downloads
23.7M
likes
1.9K
parameters by dtype
BF16751.6M

repo last modified 2025-07-26

A neuron has two poles, a read and a write

A transformer block's MLP in Qwen3 is a SwiGLU. Neuron nn of layer LL is coordinate nn of the input to down_proj. It looks at the normalised MLP input x^\hat x through two rows, one from the gate projection and one from the up projection, with the norm's weight γ\gamma folded in:

a=γ⊙Wgate[n],b=γ⊙Wup[n],act=silu⁡(a⋅x^) (b⋅x^),a = \gamma \odot W_{\text{gate}}[n], \qquad b = \gamma \odot W_{\text{up}}[n], \qquad \text{act} = \operatorname{silu}(a\cdot\hat x)\,(b\cdot\hat x),

and it adds act⋅Wdown[:,n]\text{act}\cdot W_{\text{down}}[:,n] back into the residual stream. That last vector is the neuron's write. The pair of rows is its read.

Because the up projection is linear, act takes both signs. The gate has to be open, so a⋅x^a\cdot\hat x has to be positive, and then the sign of the activation is the sign of b⋅x^b\cdot\hat x. The authors treat the two signs as two poles of one neuron and catalogue them separately. The positive pole reads along a^+b^\hat a + \hat b and writes +Wdown[:,n]+W_{\text{down}}[:,n]. The negative pole reads along a^−b^\hat a - \hat b and writes −Wdown[:,n]-W_{\text{down}}[:,n] (hats are unit vectors). The notes call the read direction a heuristic: the gate and up rows are nearly perpendicular, with a median cosine of 0.00 at every layer of Qwen3-8B except the third, so the two poles really do read different directions. The activations decide which pole fires in practice (reported).

This is also why my steering article adds a signed multiple of one column of down_proj: the write of a pole is a fixed direction, and steering a pole just supplies the activation by hand.

Three ways to read a vector inside a model

A lens turns a vector inside the model into a ranking over tokens. The oldest is the logit lens: pretend the vector is already at the output and read it as softmax⁡(WU norm⁡(h))\operatorname{softmax}(W_U\,\operatorname{norm}(h)), where norm⁡\operatorname{norm} is the final RMSNorm and WUW_U the unembedding. It works near the top and reads noise early, because the layers above transform the vector heavily.

The J-lens of Gurnee et al. keeps those layers by linearising them. Let hL(t)h_L(t) be the residual at position tt after layer LL, and hfinalh_{\text{final}} the last layer's output before the final norm. The matrix

JL=mean⁡t ∑t′≥t∂hfinal(t′)∂hL(t)J_L = \operatorname*{mean}_{t}\ \sum_{t' \ge t} \frac{\partial h_{\text{final}}(t')}{\partial h_L(t)}

is the Jacobian of the final residual with respect to layer LL's, summed over the current and later positions (so effects carried by attention count) and averaged over a corpus. The read is softmax⁡(WUnorm⁡(JLh))\operatorname{softmax}(W_U\operatorname{norm}(J_L h)): what hh makes the model say, on average, once everything above has acted on it.

The K-lens, from Theia Vogel, takes the Jacobian the other way, back to the input embeddings e(s)e(s):

KL=mean⁡t ∑s≤t∂hL(t)∂e(s),score(v)=h⋅KL (WE[v]−WE‾),K_L = \operatorname*{mean}_{t}\ \sum_{s \le t} \frac{\partial h_L(t)}{\partial e(s)}, \qquad \text{score}(v) = h\cdot K_L\,(W_E[v] - \overline{W_E}),

which answers a different question: which input tokens, at this position or earlier, put this direction there. The R-lens and S-lens are the J-lens and K-lens computed under RelP's propagation rules instead of the true gradient: the norm scales are held constant, silu⁡(g)\operatorname{silu}(g) is linearised as σ(g) dg\sigma(g)\,dg, and the product of gate and up passes half of each factor's change. The authors fit both on 1,000 documents of 128 tokens, at about 11 seconds a document per lens for Qwen3-8B on one H100 (reported). One pass per basis direction of the fixed end covers every layer at the other, so either lens costs dmodeld_{\text{model}} passes per document.

A diagram of the residual stream from input tokens through the embedding matrix, layers, final norm and unembedding to the logits, with coloured arcs showing where the logit lens, J-lens, R-lens, K-lens and S-lens tap layer L and in which direction each one runs.
The lenses side by side: forward lenses read a direction through the layers above it, backward lenses through the layers below it (neuron-lens research notes, schematic of the lens family, CC BY 4.0).

One more rule matters for what a read means. Each lens is fitted at two points of every layer, and the notes state a lens rule: the layer's output (out) reads MLP writes and attention inputs, and the MLP input (mid) reads MLP read directions and attention writes. A read anywhere else extrapolates, and the catalog ships none. Pick a lens and a tap below to see which columns it feeds.

A first-order preview of steering

There is a consequence of the definitions that I worked out while setting up the check below. I have not seen the authors state it, so I label it reasoned. Add a vector ww at layer LL at every seed position at once and measure the change in the final residual. To first order the change at position t′t' is ∑t≤t′∂hfinal(t′)/∂hL(t) w\sum_{t\le t'} \partial h_{\text{final}}(t')/\partial h_L(t)\,w, because attention is causal. Sum that over t′t' and divide by the number of seeds and you get JLwJ_L w for that document. So the J-lens read of a neuron's write is a corpus-averaged, first-order preview of steering the neuron at every position, read through the unembedding. It also needs no fitted matrix: a central finite difference of the final residual gives it in two forward passes. That is how I compute it below.

It also says where a lens must fail. Steering adds a push of a particular size, and the response of the model to a large push is not linear. A weight-only read is a statement about the direction of the push for small pushes.

What the catalog contains

Every neuron has a ++ and a −- pole, so Qwen3-8B has 884,736 neuron entries and 1,152 heads, and Gemma 4 12B 1,474,560 neuron entries and 768 heads. Each gets five tables: reads (the weight-only lens reads, each keeping its top 64 tokens), acts (how hard and how often it fires, and its top contexts), drivers (the tokens that make it fire, found by substituting every common token at one position of 8 real documents), legible (which reads hold at the unit's own top firings) and labels (Haiku's labels in words with a confidence). Gemma 4 12B, which also takes images and sound, adds image and audio tables, with its neurons read through 49,694 labelled images and 46,932 labelled sounds, and a media table for heads (reported).

The explorer addresses every view as a path, for example /qwen3-8b/L8/n344 for neuron 344 of layer 8, and draws it in the browser. It serves no unit's data as text, so to look something up you read the Parquet tables in the dataset on Hugging Face, and the lenses are in stdlogits/rs-lenses. The README shows what a row says. L8 n344 + fires on "the sound of the⟦ ow⟧ls", its exact drivers are owl, Owl, OWL and Athena, the R-lens read of its input is owl, 星空 (starry sky) and Owl, and the R-lens read of its write is owl, noct, bedtime and night. L35 n9510 + fires on hashes and IDs, and the notes say it writes no token at all: it sets the temperature of the output (reported).

Which read to trust where. The authors measured that no single read works at every depth, and the map is the most useful part of the write-up for a reader who wants to use the catalog. For Qwen3-8B (reported):

layerswhat drives a polewhat a pole writes
L0–L7the S-lens read of its read direction: literal tokensthe S-lens read (early neurons write back the tokens they detect); the R-lens from about L4
L8both lenses, agreeing mostboth
L9–L12the R-lens read: concepts; and exact driversthe R-lens read: what steering does, rarely what follows a firing
L13–L20only exact drivers and activations: 82% of poles pass no weight-only test herethe R-lens read, for steering
L21–L35the R-lens read, often the word about to comethe next token from about L23, suppression from L32

What the authors report, and how they checked it

The findings document gives each result its setup, controls and limits, which is more than most releases do. The ones that matter here, all reported:

Two line charts of median correlation against layer for Qwen3-8B. Left: the correlation between each lens's read of a neuron's write and the measured steering effect, for the R-lens, J-lens, logit lens and a control. Right: the same for ablation where the neuron fires.
The authors' own plot of the steering check on Qwen3-8B: the R-lens and J-lens read of a write predict what steering does far better than the logit lens until the late layers, and a control stays at zero (neuron-lens, lens card, steering figure, CC BY 4.0).

Re-running two checks on Qwen3-0.6B

I wrote scripts/neuron-lens/lens_check.py (it is in the repo with the other files for this article) to re-run the idea of two checks on Qwen3-0.6B, which has 28 layers, a 1,024-dimensional residual stream and 3,072 neurons a layer. I sampled 4 random neurons at each of 9 depths (L0, 2, 4, 8, 12, 16, 20, 24 and 27), which is 36 neurons and 72 poles. All documents are windows of this site's own article text, in three disjoint sets: 100 documents of 96 tokens (9,600 tokens) for activations and top firings; 4 documents of 64 tokens, where the lens reads are taken; and 8 other documents of 64 tokens, held out, where steering is measured. As in the authors' setup, seeds sit at positions 16 to 62, so the first 16 positions (the attention sink) are left out. The candidate vocabulary is the tokens seen at least three times across the text of every article on this site, 29,636 of them (the authors keep 103,830 of 151,669).

The differences from the authors' pipeline are large, and every one of them makes my numbers noisier or less comparable:

The weights stay in bf16, which is how the checkpoint is stored, and each matrix is upcast to fp32 inside its own matmul, because the shared machine had little memory to spare. That reproduces the fp32 model exactly. As a check, its logits correlate 0.9999 with the plain bf16 model's on a test prompt and the top token agrees at every position (measured).

The script runs in four stages, acts, reads, dose and summarize, each resumable:

QWEN_DIR=<Qwen3-0.6B snapshot> NLENS_OUT=<dir> nice -n 19 python scripts/neuron-lens/lens_check.py acts

The per-neuron records, the dose runs, the summary and the list of documents used (file names and token counts only) are in results.json, reads.jsonl, dose.jsonl and docs-provenance.json.

Check 1: does a weight-only read of a write predict what steering does?

For each pole I add its write, ±Wdown[:,n]\pm W_{\text{down}}[:,n] times the pole's typical top activation (the median of its 30 strongest firings over the 9,600 tokens), at the output of layer LL at the seed positions of the 8 held-out documents. I record the mean change in log-probability of each of the 29,636 tokens and take the Pearson correlation with each lens's read of the same write. The control correlates a neuron's steering effect with another sampled neuron's read from the same layer. The authors run the same test, on Qwen3-8B, with 16 documents and doses of 1× and 4× a pilot estimate of the typical activation (reported).

layerpush at 1× (× residual)logit lens rJ-style rcontrol rJ-style r at 4×J-style r at 16× (push × residual)
L00.040.100.530.030.520.33 (0.71)
L20.030.100.570.010.570.46 (0.52)
L40.020.170.67−0.060.660.61 (0.35)
L80.020.280.800.100.800.72 (0.35)
L120.020.310.840.000.830.77 (0.25)
L160.020.550.91−0.030.900.84 (0.31)
L200.020.690.97−0.020.970.95 (0.38)
L240.020.880.99−0.010.990.98 (0.35)
L270.031.001.00−0.011.001.00 (0.45)

Table: median over 8 poles a layer, measured on Qwen3-0.6B. At L27 the last layer's output is the final residual, so the J-style read is the logit lens and r is 1.00 for both. That row checks my code and is not a result.

The shape is the authors' shape. The J-style read predicts steering from the first layer, with median r of 0.53 at L0, 0.67 at L4, 0.80 at L8, 0.91 at L16 and 0.99 at L24. The plain logit lens starts at 0.10, is still at 0.28 at L8 and 0.55 at L16, and only catches up in the last quarter of the network (0.88 at L24). Another neuron's read stays between −0.06 and 0.10. On Qwen3-8B the authors report 0.09, 0.12 and 0.36 for the logit lens at L0, L8 and L16, and 0.38, 0.42 and 0.65 for the J-lens (reported), so the ordering and the gap agree. My values are higher early on, probably because my lens and test documents are the same kind of text and my pushes at 1× are only 0.02 to 0.04 of the residual stream, which keeps the response close to linear. I did not test either explanation.

The authors' plot of median correlation against layer for Qwen3-8B, repeated here for comparison with the measured curves below.
The same check on Qwen3-8B: the R-lens and J-lens curves rise from about 0.35 to 1, the logit lens stays low until the late layers, and the control sits at zero (neuron-lens, lens card, steering figure, CC BY 4.0).

The dose columns show where the first-order read stops working. At 4× the J-style correlation is the same to two digits at every layer. At 16×, a push of 0.25 to 0.71 of the residual stream, it falls at the early layers: 0.33 at L0, 0.46 at L2, 0.61 at L4, 0.72 at L8 and 0.77 at L12, and it stays at 0.84 or above from L16. That is what a linearisation should do: a read of the direction is a statement about small pushes, and the early layers have the smallest residual norms and the most layers above them to bend the response. At the same small dose the read is still weaker at L0 than at L8, and a bigger dose makes it worse.

Noise is the other limit, and it is large here. Splitting my four lens documents into two halves of two, the two J-style reads of the same write share on average 3% of their top-10 tokens at L0, 25% at L4, 18% at L12 and 42% at L20, and reach 82% at L24 and 100% at L27. The authors report that the R-lens read of a write has the same top token across halves of 500 documents for 89 to 99% of neurons (reported). Pearson r over 29,636 tokens survives a noisy read because it is dominated by the broad shape of the change, and the authors say so too. The top-10 lists are another matter: the logit lens and the J-style read share 0% of their top 10 at L0 and 5% at L16, and only 40% at L24.

Check 2: does an input-side read find the tokens that fire a pole?

A pole reads along a^±b^\hat a \pm \hat b. For each pole I take the tokens at its 20 strongest firings over the 9,600-token activation corpus (those in the candidate vocabulary), and count how many land in the top 10 or top 50 of four reads of that direction: the K-style read (one backward pass from the layer to the embeddings), the plain projection on the token embeddings, the J-style read of the direction, and a control, another neuron's K-style read from the same layer.

layerK-style top 10K-style top 50embedding top 50J-style top 50control top 50
L02%11%6%0%6%
L22%9%5%0%0%
L45%7%3%0%0%
L80%0%0%6%0%
L120%0%3%1%0%
L161%1%1%3%0%
L200%3%2%4%0%
L240%0%0%0%0%
L271%1%1%1%0%

Table: share of a pole's firing tokens found in the read's top 10 or top 50, mean over 8 poles a layer, measured.

These are small numbers from small samples. A layer is 8 poles and about 20 firings each, so one pole moves a layer's rate by several points, and I would not call this a replication. What it shows is the sign. The K-style read is above both the embedding shortcut and the control at L2 and L4 (9% and 7% in the top 50, against 5% and 3%, and 0% and 0%), at L0 it gets 11% against 6% for the shortcut and for the control, and from L8 on it finds at most 3%, against 0% for the control. That fits the authors' report that the S-lens read works at the lowest layers and fades after L8 (reported). My absolute rates are far below theirs, who find 40% of firing tokens in the top 10 at L0 and 2 to 9% at L1 to L8. I would blame the 29,636-token vocabulary, the four lens documents and the narrow text, in that order of confidence, but I did not separate them.

What the reads look like

The table below is built from the same files. It shows, for the + pole of one sampled neuron per depth, the tokens it fires on and what each read says. The neuron at each depth is the one whose input reads found the most of its firing tokens, so these are the best cases, not typical ones. Most neurons look like the one at L2 or L8.

At L4 the pole fires on spend and time. The K-style read of its input is investment, budget, spending, spend. The J-style read of its write includes investment, Spending, spent and money, and measured steering raises investment, pricing, budget, market, while the plain logit lens of the same write gives rices, carefully and protect. At L12 the J-style read of the write ( consumers, testers) is the measured steering effect, and the logit lens gives word fragments. At L27 the pole fires on − and delta, and the K-style read of its input is Delta, Δ and offset. The L8 neuron is the honest counter-example: the J-style read of its write and the measured steering share cybersecurity and little else.

What I take from it

A weight-only read of a write is a usable preview of steering in the second half of the network, a noisy one in the first, and the plain logit lens is a poor stand-in until the last quarter, which agrees with the authors. The finite-difference read I used is not cheaper than steering: it costs two forward passes a candidate, where measuring the steering effect costs one. What makes the authors' version cheap is the fitted lens. Once JLJ_L is fitted, reading a write is a matrix-vector product, so all 86,016 neurons of a model this size cost almost nothing, and steering has to be run only for a shortlist. I did not test that shortlist, and the authors' own point stands that a good steering preview says nothing about what a neuron does when it fires, because the effect of a real firing depends on context before about L21.

What I did not run

I did not run the authors' pipeline, their explorer or their code, and I did not load Qwen3-8B or Gemma 4 12B, so none of the catalog's own numbers is re-checked here. I used true gradients where they use RelP's rules, so nothing below says anything about the R-lens or S-lens themselves. I did not fit a lens matrix, I did not re-label anything, and I did not test the ablation check or the head circuits. The documents come from this site's own articles, which are narrow, technical English with some code.

Sources

Cite this article

For attribution, please use the following reference or BibTeX:

Satyajit Ghana, "Neuron lens: what a neuron's weights say, and what steering it does, checked on a 0.6B model", ai.thesatyajit.com, October 2026.

bibtex
@misc{ghana2026neuronlens,
  author = {Satyajit Ghana},
  title  = {Neuron lens: what a neuron's weights say, and what steering it does, checked on a 0.6B model},
  url    = {https://ai.thesatyajit.com/articles/neuron-lens},
  year   = {2026}
}
share