Method: the Jacobian lens and the J-space
Archive — the original J-space replication study
This is what the repository started as: replicating the J-space / global-workspace readout claim on open weights. That readout claim did not survive our controls — an informative null, preserved here with full rigor — and the project pivoted to the operator/operand finding on the main page. These pages are kept as-is.
This project reproduces the method of Anthropic's
Verbalizable Representations Form a Global Workspace in Language Models
(Gurnee, Sofroniew, Lindsey et al., 2026) on open-weights Qwen3 models, using
the authors' reference implementation
anthropics/jacobian-lens.
The Jacobian lens (J-lens)
A lens reads out what an intermediate residual-stream activation is "poised to make the model say". The logit lens does this by decoding a mid-layer residual \(h_\ell\) directly with the model's unembedding \(W_U\). The Jacobian lens first transports \(h_\ell\) into the final-layer basis with the average input–output Jacobian of the network:
The expectation is taken over a corpus of pretraining-like prompts, over source positions, and over all current-and-future target positions. Intuitively, for every vocabulary token the J-lens finds the internal activity pattern that makes the model more likely to emit that token at some point in the future — it measures causal influence on the output, not mere presence.
Computing \(J_\ell\) requires backward passes through the model: for each of the \(d_{\text{model}}\) output dimensions a one-hot cotangent is injected at every valid target position and backpropagated to layer \(\ell\). This is why the whole pipeline needs autograd on the GPU (not just inference) — see the setup page.
From the logit lens to the Jacobian lens
It helps to build the J-lens up from the simplest readout.
The logit lens (nostalgebraist, 2020) decodes a mid-layer residual in place with the model's own output head:
It asks "if the model stopped at layer \(\ell\) and read out now, what would it say?" The catch is a basis mismatch: the residual stream at layer \(\ell\) is not yet in the coordinate system the unembedding expects — the remaining layers still rotate and rescale it. So the logit lens is reasonable in late layers (basis already close to final) but noisy and systematically under-reads content in the early and middle layers — exactly where the workspace lives.
The Jacobian lens fixes the basis by transporting \(h_\ell\) with the model's own average input–output Jacobian before decoding:
Because \(J_\ell\) is the model's first-order sensitivity — how a nudge to \(h_\ell\) actually propagates to the output, averaged over real text — three things follow:
- It reads in the right basis. Instead of assuming the mid-layer basis equals the final one, it carries \(h_\ell\) forward with the network's own derivative.
- It measures influence, not presence. A direction that exists in \(h_\ell\) but that the downstream network ignores contributes nothing to \(J_\ell h_\ell\); the lens filters for what actually reaches the output.
- It is derived, not trained. Unlike the tuned lens (a learned affine probe fit to predict the next token), \(J_\ell\) comes from the model itself, so it cannot hallucinate content that is not causally there.
| lens | transport into final basis | trained? | main failure mode |
|---|---|---|---|
| logit lens | none (\(J_\ell = I\)) | no | basis mismatch → under-reads mid layers |
| tuned lens | learned affine \(A_\ell h + b_\ell\) | yes (fit to next token) | can hallucinate / overfit |
| Jacobian lens | model's average Jacobian \(J_\ell\) | no (derived) | needs backward passes to build |
The logit lens is exactly the special case \(J_\ell = I\) — turn off the
transport and the J-lens is the logit lens. In this codebase that is a single
flag, lens.apply(..., use_jacobian=False), so every "J-lens vs logit-lens"
comparison in the results is controlled: same activations, same unembedding, the
only difference is whether \(h_\ell\) is carried through the model's Jacobian
first. If the J-lens beats the logit lens, the improvement is due to the
transport and nothing else.
The J-space
The J-space is a small, sparse subspace of the residual stream spanned by the J-lens vectors that are active at a given moment (typical sparsity \(k \le 25\)). Its defining properties, which we measure across scale:
- it accounts for a small fraction of activation variance (never more than ~10%, varying by layer);
- far more model components read from and write to it than to ordinary directions (up to ~100× in parts of the network);
- it is organised by depth into a sensory regime (early layers), a workspace regime (the middle band, where abstract content persists), and a motor regime (late layers, aligned with the output).
What we measure
Per-layer metrics (scripts/metrics.py), reindexed to a 0–100 depth scale:
| metric | what it captures |
|---|---|
jlens_top1/5, logitlens_top1 |
next-token accuracy of the readout; J-lens vs logit-lens baseline |
read_var_frac |
fraction of residual variance in the J-lens' top-\(k\) read directions (the "<10%" claim) |
eff_dim |
participation ratio of \(J_\ell\)'s singular values (effective dimensionality of the workspace) |
kurtosis |
excess kurtosis of the lens logits (a few concepts dominate → workspace signature) |
top1_autocorr |
persistence of the top-1 readout across adjacent positions |
Lens quality (scripts/lens_eval.py): on the paper's shipped two-hop set,
pass@\(k\) = the fraction of hidden bridge entities (e.g. Brazil in "the country
where the Amazon ends") that appear at lens rank \(\le k\) at their best layer.
Causal importance (scripts/causal_swap.py): using the shipped probe-swap
set, we move the residual component along a bridge entity's J-lens direction
onto a different entity's direction, across a layer band, and check whether the
model's answer flips accordingly — versus a matched-norm random control. If the
workspace band flips (while the control and the early/late bands do not), the
J-space is causally steering the computation.
The scale question
The paper studies Claude. Our contribution is to ask, on open weights, when these properties appear: we run the identical pipeline on Qwen3 1.7B, 8B, and 32B and compare the metrics above across model size — does the J-space exist already at 1.7B, or does it sharpen (lower variance fraction, cleaner workspace band, stronger causal flips) as models scale?