The Complete Guide to Reading a Model’s Hidden Layers with Anthropic’s Jacobian Lens Anthropic's July 2026 paper 'Verbalizable Representations Form a Global Workspace in Language Models' introduces the Jacobian lens (J-lens), a tool that corrects the basis mismatch causing the older logit lens to fail in early layers, and demonstrates that a low-dimensional slice of hidden layers, J-space, acts as a global workspace. In tests, the model silently recognized a blackmail scenario in this space before writing a reply, and suppressing that space made it more likely to attempt the blackmail, providing causal evidence of its role. The companion library jlens, installable from GitHub, enables fitting lenses and visualizing results on a free Colab T4. When a language model answers a question, most of the computation happens in the middle of the network the hidden layers , long before you see the answer. And knowing exactly what happens in those hidden layers is one of the harder problems in mechanistic interpretability, largely because the standard techniques like the logit lens fail due to a basis mismatch. So, in July 2026, Anthropic published Verbalizable Representations Form a Global Workspace in Language Models , arguing that a small, low-dimensional slice of those hidden layers— what they call J-space — behaves like a global workspace. This global workspace means the concepts or “thoughts” a model is leaning toward saying, even if it never actually says them out loud. For example: In the paper, researchers gave the model an email-based blackmail scenario as a test. Before the model wrote a single word of its reply, this hidden space already contained words like “blackmail” and “fake” — meaning the model had silently recognized the setup and was already weighing that response, even though its final written reply never mentioned either idea. When the researchers then suppressed that hidden space and reran the same scenario, the model’s behaviour actually changed: it became more likely to attempt the blackmail it had previously only “considered” internally. That’s the causal evidence — not just correlation — that this hidden space is doing real work in the model’s decision. That’s a genuinely new kind of interpretability result, and it hinges on a new tool: the Jacobian lens J-lens , which corrects for the basis mismatch that causes the older logit lens to produce garbage in early layers. This article stays narrower than the paper. We’ll load a small open model, apply both the logit lens and the Jacobian lens to identical activations, watch where the correct answer to a simple factual question first becomes legible, and fit a lens of our own — all runnable on a free Colab T4. In this article, we’ll look into the hidden layers of a Qwen model using the Jacobian lens . We’ll compare it directly against the older logit lens on identical activations, plot where the correct answer emerges, and fit a lens from scratch. Note: The entire setup runs on a free Colab T4, so you can follow along without paying for compute. To understand the Jacobian Lens, we must first examine the limitations of the baseline Logit Lens . The logit lens is the original approach to this problem. It takes a hidden state from some intermediate layer and pushes it straight through the model’s output matrix, as if the layer you’re probing were the last one. The logit lens relies on a strong assumption: that downstream layers won’t significantly change the representation. Early in the network, this assumption fails. Because in a 30-layer model, layer 5 is still 25 layers away from the output space and hasn’t been rotated into the expected vocabulary basis. Forcing this unrefined state through the unembedding matrix produces garbled output such as punctuation marks, unrelated foreign characters, or strings of underscores. The Jacobian lens replaces that identity assumption with a learned correction. For each layer, it fits a matrix that approximates what the remaining layers actually do, then applies that matrix before unembedding. Because the correction is fit by averaging real gradients across many prompts, it captures what a given activation is generally disposed to push the model toward, rather than what happened in one specific context. jlens is the companion library released with the paper. It handles fitting lenses, applying them to prompts, and visualising the results. It also ships the interactive slice-stack viewer used in the paper's figures. Note: It isn’t published to PyPI, so it installs from GitHub at the time of this writing. Let h l be the hidden state at layer l a d-dimensional vector , and W U be the unembedding matrix mapping d hidden dimensions to vocabulary size V . The logit lens decodes h l by applying W U directly: P logit = softmax W U · LayerNorm h l The Jacobian lens fits a projection matrix J l dimension d × d , defined as the expected gradient of the final hidden state h L with respect to the probed layer state h l: J l ≈ E ∂h L / ∂h l The hidden state is transported through J l before unembedding: P jacobian = softmax W U · LayerNorm J l · h l That single matrix multiplication is the entire architectural difference between the two methods. The diagram below shows the three paths out of the same hidden state: the model’s real forward pass, the logit lens shortcut, and the Jacobian lens shortcut. Before starting, make sure your environment has the following: You can find the Colab notebook here https://colab.research.google.com/drive/1Tcq pKdaZMdCleaxacUuOJeD0uwrjT37?usp=sharing I used Qwen/Qwen3.5-4B in this tutorial, which is about 8GB in bfloat16 and ungated. Larger models cause two problems on a T4. meta-llama/Meta-Llama-3-8B is gated, so loading it without an accepted license and a token raises OSError: You are trying to access a gated repo. It is also roughly 16GB in bf16, which is the full capacity of a T4, so it raises an out-of-memory error while moving weights to the GPU even after authentication is sorted out. If you want to use a larger or gated model, you’ll need a bigger GPU, 4-bit quantization, or both. Install the required packages. jlens installs from its GitHub repository https://github.com/anthropics/jacobian-lens since it isn't on PyPI. pip install -q torch transformers pandas accelerate matplotlib pip install -q git+https://github.com/anthropics/jacobian-lens egg=jlens Next, log in to Hugging Face https://huggingface.co . This is required for gated models and avoids download rate limits on ungated ones. python from huggingface hub import loginlogin Load the base model and wrap it for jlens. The library works through its own wrapper rather than the raw Hugging Face model object. python import torchimport pandas as pdfrom transformers import AutoModelForCausalLM, AutoTokenizerimport jlens model id = "Qwen/Qwen3.5-4B"tokenizer = AutoTokenizer.from pretrained model id hf model = AutoModelForCausalLM.from pretrained model id, dtype=torch.bfloat16, .cuda model = jlens.from hf hf model, tokenizer Note that the keyword is dtype, not torch dtype. jlens pins a version of transformers that renamed this argument, and the older name will throw. A fitted lens is one matrix per layer, stored as a .pt file. Pre-fitted lenses for several models are hosted on the Hub, so there's no need to fit your own to get started. lens = jlens.JacobianLens.from pretrained "neuronpedia/jacobian-lens", filename="qwen3.5-4b/jlens/Salesforce-wikitext/Qwen3.5-4B jacobian lens n1000.pt", revision="qwen-n1000", Two things to keep in mind here. The function below reads the final token position at every layer the lens supports, running the same activations through both lenses. prompt = "The capital of the country where the Eiffel Tower is located is" python def probe trajectory model, lens, prompt, tokenizer : Restrict probing to layers the loaded lens actually covers layers = sorted set range 1, model.n layers & set lens.source layers logits jac, , = lens.apply model, prompt, layers=layers, positions= -1 , use jacobian=True logits std, , = lens.apply model, prompt, layers=layers, positions= -1 , use jacobian=False results = for l in layers: top std = tokenizer.decode logits std l 0 .argmax .item top jac = tokenizer.decode logits jac l 0 .argmax .item results.append {"Layer": l, "Logit Lens": top std, "Jacobian Lens": top jac} return pd.DataFrame results df = probe trajectory model, lens, prompt, tokenizer print df.iloc 1:30 Thepositions= -1 reads the last token, which is the point just before the model commits to an answer. The use jacobian flag is the whole comparison: same forward pass, same weights, same activations, with only the transport step changing. As you can see, the logit lens never recovers. Across all 29 layers shown, it produces no French word, no place name, nothing geography-adjacent, but the Jacobian lens gets it on the 26th layer. This is the failure the Jacobian lens was built to address, and it appears exactly as described. The Jacobian lens is also mostly noise. Its column is full of ... and , so this is not a method that makes every intermediate layer legible. At layers 26 and 30, though, it returns “Paris.” Those are the only two cells in the table, in either column, where the correct answer surfaces before the model’s final output. It’s worth being precise about what this supports. A single top-1 hit is not a picture of what the model was thinking at that instant, and most layers here remain unreadable. What the table does support is the comparison: on identical activations, one method surfaced the answer twice, and the other surfaced it zero times. Evaluating only Top-1 argmax tokens masks continuous probability shifts in intermediate layers. Tracking the absolute vocabulary rank of a target token on a logarithmic scale provides a clear metric of emerging confidence across network depth. python import matplotlib.pyplot as plt python def token rank trajectory model, lens, prompt, target token, tokenizer : layers = sorted set range 1, model.n layers & set lens.source layers target id = tokenizer.encode target token, add special tokens=False 0 logits jac, , = lens.apply model, prompt, layers=layers, positions= -1 , use jacobian=True logits std, , = lens.apply model, prompt, layers=layers, positions= -1 , use jacobian=False ranks jac, ranks std = , for l in layers: ranks jac.append logits jac l 0 .argsort descending=True == target id .nonzero .item ranks std.append logits std l 0 .argsort descending=True == target id .nonzero .item return layers, ranks jac, ranks stdlayers, ranks jac, ranks std = token rank trajectory model, lens, prompt, target token=" Paris", tokenizer=tokenizer plt.figure figsize= 9, 5 plt.plot layers, ranks std, marker="o", label="Logit lens", color="tab:gray" plt.plot layers, ranks jac, marker="o", label="Jacobian lens", color="tab:blue" plt.yscale "log" plt.gca .invert yaxis plt.xlabel "Layer" plt.ylabel 'Rank of " Paris" log scale ' plt.title "Rank of the correct answer across layers" plt.legend plt.grid alpha=0.3 plt.show Note the leading space in " Paris". This tokenizer, like most, encodes a space-prefixed word as a different token from the bare word. If a rank curve looks flat and wrong, check this first. Next, we can also visualise representations across the entire prompt sequence and layer stack, because it jlens integrates interactive visualizers and dictionary gloss mappings used in the Jacobian Lens paper. mkdir -p assets wget -q https://raw.githubusercontent.com/anthropics/jacobian-lens/main/assets/qwen gloss.json.gz -P assets/ python import urllib.request, gzip, json, os Download Qwen token gloss lookup tableURL = "https://raw.githubusercontent.com/anthropics/jacobian-lens/main/assets/qwen gloss.json.gz"os.makedirs "assets", exist ok=True urllib.request.urlretrieve URL, "assets/qwen gloss.json.gz" print os.path.getsize "assets/qwen gloss.json.gz" expect 655852gloss = {int k : v for k, v in json.load gzip.open "assets/qwen gloss.json.gz" .items }print len gloss , "glosses" expect 91695 slice data = compute slice model, lens, prompt, layer stride=2, mask display=True page, , = build page slice data, prompt, title="Eiffel Tower probe", description="Multi-hop factual recall, probed at the final token position.", alt token=gloss, notebook iframe page Setting mask display=True filters raw output tokens into human-readable subwords, removing noise such as standalone punctuation or formatting tokens. You can click on any cell pin to track its rank across every layer at once. A public copy of this viewer with pre-loaded examples is available at transformer-circuits.pub/2026/workspace/public/slice-stack/ http://transformer-circuits.pub/2026/workspace/public/slice-stack/ . It is a useful reference for what a strong readout looks like compared to the mixed one above. For longer prompts, switch to mode="fetch", which writes rank data to sidecar files instead of inlining it into the page. Two constraints are worth carrying forward, particularly given how mixed the Step 5 output looked. It’s a linear approximation of a nonlinear system. J l is a single matrix standing in for everything downstream — attention routing included — and it can't represent the sharp, discontinuous decisions attention makes. The expectation in E ∂h final/∂h l is taken across many contexts, which means it offers no guarantee for any one particular context. This is plausibly part of why most layers in the table were unreadable. A lens doesn’t transfer. Not across base models, and not across fine-tunes of the same base. Fine-tuning changes internal representations, so J l has to be recomputed. The fitting procedure in Step 8 is identical regardless of the model, but it costs compute every time. It only captures single-token concepts cleanly, and mid-network depths specifically. Multi-token ideas and very early- or very-late-layer content are harder for this method to surface — the useful readouts concentrate in the middle third to two-thirds of the network’s depth. The Jacobian lens doesn’t give you a clean window into a transformer’s intermediate reasoning — on the prompt tested here, most layers stayed unreadable under both methods. But what it does provide is a readout that can surface the correct answer at points where the logit lens structurally cannot, on identical activations. That’s also the mechanism behind the paper’s larger claim: that a specific, low-dimensional subspace of a model’s activations holds the concepts it’s currently poised to verbalise, separate from the much larger volume of automatic processing happening in parallel — and that this subspace can be read, and in some cases edited, before the model ever writes a word. For interpretability and safety work where the alternative is decoding noise, that difference is the point. The Complete Guide to Reading a Model’s Hidden Layers with Anthropic’s Jacobian Lens https://pub.towardsai.net/the-complete-guide-to-reading-a-models-hidden-layers-with-anthropic-s-jacobian-lens-c564c9ac4fcf was originally published in Towards AI https://pub.towardsai.net on Medium, where people are continuing the conversation by highlighting and responding to this story.