Jacobian Lens reads out what a mid-layer activation is about to make the model say
Companion code for the global workspace interpretability paper
At a glance
- What is it?
- anthropics/jacobian-lens is Apache-2.0 reference code for the global workspace interpretability paper. It transports a residual stream vector from any layer and position into the final layer basis using an average Jacobian, then decodes it with the model's own unembedding. The README states plainly that it is a reference implementation, is not maintained, and does not accept contributions.
- Who is it for?
- Reach for it if you work on interpretability and want to see, token by token, what a mid-layer residual stream is disposed to say, or if you want a readable reference for how the average Jacobian transport in the global workspace paper is actually estimated. Do not build a product on it: the README states it is a reference implementation, is not maintained, and is not accepting contributions, and there are no releases.
- Can I use it commercially?
- Yes. Apache-2.0 is a permissive licence: you can use, modify and sell software built on it, as long as you keep its copyright and licence notices.
- Is it still maintained?
- Yes. The repository last received commits 17 days ago.
- What is it written in?
- Mainly Python, according to GitHub's language statistics.
Answers come from the project's GitHub data, last synced on September 18, 2026, and from our analysis. They are not legal advice.
DEEP OPEN-SOURCE ANALYSIS
The question the lens answers
An interpretability tool earns its keep by answering a question you could not answer otherwise. The Jacobian lens answers this one: at layer l and position p, what is this residual stream vector disposed to make the model say?
The README gives the procedure in one line. Take the residual stream vector, transport it linearly into the final layer basis, then decode it with the model's own unembedding matrix to get a ranked list of vocabulary tokens. The transport operator is the average input to output Jacobian over a text corpus, so the readout is expressed in the same vocabulary the model actually emits rather than in an abstract feature space.
The demonstration in the repository makes the point better than the formula. In an ASCII-face prompt, selecting the caret character that forms the nose gives a lens readout of nose at the middle layers, even though the word nose never appears anywhere in the prompt. The model has computed something nose-shaped well before it has committed to saying it.
This is companion code for a paper, Verbalizable Representations Form a Global Workspace in Language Models, published on Transformer Circuits in 2026.
What the average is actually over
The estimator is where most of the substance sits, and the README is specific about it. The Jacobian at layer l is the expectation of the derivative of the final hidden state with respect to the hidden state at layer l, written as J_l = E[dh_final / dh_l].
That expectation is taken over three things at once: prompts, source positions, and all current and future target positions in a generic web text corpus. The precise estimator, cotangents summed over target positions and then averaged over source positions, is documented in the docstring of the jlens.fitting module, which is the file to read if you intend to reproduce the numbers rather than just use the tool.
Summing over future target positions is the detail that distinguishes this from a naive single-step derivative. It aggregates how a change at layer l propagates to everything the model will subsequently predict, not only to the immediately next token.
The consequence worth naming: the result is one linear map per layer, averaged across a corpus. It is not conditioned on your prompt. What you apply at inference is a fixed transport, and the input-specific part is the vector you feed it.
Installing it and applying a fitted lens
Install is an editable install of the package, which is named jlens and is at version 0.1.0:
pip install -e .The dependency set is small and the versions matter. Python 3.10 or newer is required, and the pinned stack is torch, huggingface_hub, numpy, and transformers at 5.5 or newer:
requires-python = ">=3.10"
"torch",
"huggingface_hub",
"transformers>=5.5",
"numpy",The repository bundles no model weights and no text corpora. Anything you download at run time carries its own licence, so the open-weights decoder you point this at is your choice and your responsibility.
Applying a lens that someone has already fitted is the shortest path to a result. Load the model and tokenizer, wrap them, load the lens, and apply it at the positions you care about:
import transformers, jlens
hf = transformers.AutoModelForCausalLM.from_pretrained("org/model").cuda()
tok = transformers.AutoTokenizer.from_pretrained("org/model")
model = jlens.from_hf(hf, tok)
lens = jlens.JacobianLens.from_pretrained("org/lens-repo", filename="model/lens.pt")
lens_logits, model_logits, _ = lens.apply(
model, "Fact: The currency used in the country shaped like a boot is",
positions=[-2])
for layer, logits in sorted(lens_logits.items()):
print(layer, [tok.decode([t]) for t in logits[0].topk(5).indices])What comes back is a dictionary keyed by layer, and the loop prints the top five decoded tokens per layer. Running that over a factual prompt is the fastest way to see the behaviour the paper describes: the answer often appears in the lens readout layers before the final layer commits to it.
Fitting your own, and what it costs
Fitting is two calls, and the README is unusually candid about the cost:
lens = jlens.fit(model, prompts=my_prompts, checkpoint_path="out/ckpt.pt")
lens.save("out/jacobian_lens.pt")The paper's own lenses use 1000 sequences of 128 tokens drawn from a pretraining-like corpus. Quality saturates quickly, the README cites section 9.3 of the paper, and around 100 prompts is described as usable. If you are experimenting rather than replicating, that is the number to plan around.
Cost is dominated by the model's own backward pass, and the implementation is explicitly not optimized. The parallelism story is manual: run fit on disjoint slices and combine the results with JacobianLens.merge. There is no scheduler, no cluster integration, and no checkpoint resume beyond the checkpoint path argument.
Examples in the repository use Qwen, and the README says other HuggingFace decoders adapt cleanly, which is plausible given that the method only needs residual stream access and the unembedding matrix.
Reading a slice page
The output that makes this tool legible is the interactive layer by position view, and the walkthrough notebook is the end-to-end path to one: load a model, load or fit a lens, apply it at a few layers, and render a slice page.
Each cell in a slice page shows the lens top-1 word at that position and layer, with a superscript giving its rank over the full vocabulary. That superscript is the part to watch, because a plausible-looking word at rank 4000 is a very different claim from the same word at rank 3.
Clicking a cell selects that position and layer and pins its top-1 token, which adds rank-tracking charts and a rank heatmap for the pinned token. The bottom row, layer index n_layers minus one, is the model's actual output, so you can read the lens columns against what the model really said.
The pages are rendered with d3, which is ISC licensed and loaded from the jsDelivr CDN with subresource integrity, or inlined into self-contained pages.
Where it stops, and what it is not
Three limits are stated in the repository rather than inferred, and they should shape how you use it.
It is a reference implementation. The README says so in the first line, adds that it is not maintained and not accepting contributions, and notes that the code is not optimized. There are no releases. The last push was on 2026-09-02. If you need this to keep working across transformers versions, you are maintaining a fork.
The transport is a single corpus-averaged linear map per layer. That is the design, and it is also the ceiling: anything the model does that is not well approximated by an average linear map from layer l to the final layer will not show up in the readout.
And the readout is not a causal claim. Seeing nose at a middle layer tells you what that vector is disposed to produce under this transport, not that ablating it would remove the behaviour. Establishing that requires a different method.
There is also a practical dependency on someone having fitted and published a lens for your model. The example code loads from an org/lens-repo placeholder, so unless a lens exists for the model you care about, fitting is step one.
Against the logit lens, and against patching
The nearest technique is the logit lens, which applies the unembedding matrix directly to an intermediate residual stream and reads out the result. It is simpler and needs no fitting.
The difference is exactly the transport. The logit lens implicitly assumes the residual stream at layer l is already expressed in the basis the unembedding expects. The Jacobian lens inserts an estimated linear map between the two, accounting on average for the computation that happens in between. That costs you a fitting run over a corpus and buys you a readout that respects the rest of the network. Which one you want depends on whether you believe the residual stream is already in final form at the layer you are probing.
Against activation patching, the contrast is in what kind of claim you get. Patching is causal: you intervene and observe the effect. This is a readout: you observe what a vector is disposed to say under a fixed transport. They answer different questions, and a careful interpretability workflow uses both rather than treating one as a substitute.
Licence, data and what you bring yourself
The code is Apache License 2.0. The replication and lens evaluation prompt sets under data are synthetic, authored by Anthropic, and released under the same Apache 2.0 licence, with per-set READMEs under data/experiments and data/evaluations describing what each contains. That is a cleaner position than most interpretability repositories manage, since the evaluation material is reproducible without any third-party data terms.
What you bring yourself is the model. Nothing is bundled, and anything downloaded at run time is subject to its own licence, which for open-weights models can restrict commercial use. The repository also ships uv.lock, so the environment is reproducible, and a tests directory plus a dev extra listing pytest, ruff and datasets.
Line length is set to 88 with ruff targeting Python 3.10 and a lint selection covering errors, warnings, import sorting, bugbear and pyupgrade rules. Small detail, but it tells you this was written as a tidy reference rather than as research scratch.
Editorial conclusion
Reach for it if you work on interpretability and want to see, token by token, what a mid-layer residual stream is disposed to say, or if you want a readable reference for how the average Jacobian transport in the global workspace paper is actually estimated. Do not build a product on it: the README states it is a reference implementation, is not maintained, and is not accepting contributions, and there are no releases. Before you start, check that you have an open-weights HuggingFace decoder and a GPU, since nothing is bundled and fitting cost is dominated by the model's own backward pass, then run walkthrough.ipynb end to end rather than reading the modules in isolation.
Frequently asked questions
What does the Jacobian Lens actually output?
For a chosen layer and position it returns a ranked list of vocabulary tokens, produced by transporting the residual stream vector into the final layer basis and decoding it with the model's own unembedding. Applying it returns a dictionary keyed by layer, and the slice view shows the top token per cell with its rank over the full vocabulary.
Is jacobian-lens still maintained?
No. The README states in its opening line that it is a reference implementation, is not maintained, and is not accepting contributions. There are no releases and the last push was on 2026-09-02.
How much data do I need to fit a lens?
The paper's lenses use 1000 sequences of 128 tokens from a pretraining-like corpus, but the README says quality saturates quickly and that around 100 prompts is usable. Fitting time is dominated by the model's own backward pass, and parallelism means running fit on disjoint slices then combining with JacobianLens.merge.
Are model weights or corpora included with jacobian-lens?
No. No weights or text corpora are bundled, so anything downloaded at run time carries its own licence. The prompt sets under data are synthetic, authored by Anthropic, and released under Apache 2.0 alongside the code.
Community notes