Skip to content

A Jacobian lens across two architectures

The Jacobian lens transports residual vectors into the final layer's basis. This entry fits the lens from live-state captures and compares it with the paper's reference implementation on GPT-2 small and with exact scalar autograd on Mamba-130m, an architecture the reference repository cannot run. The entry passes: the fitted Jacobians match both independent estimators at every pre-final layer, the held-out readouts agree exactly, both portable artifacts reload and replay, and the two controls fail their bars as declared.

Field Value
Claim Verbalizable Representations Form a Global Workspace in Language Models, Transformer Circuits, 6 July 2026, arXiv 2607.15495 v1
Script and run line python examples/gallery/jacobian_lens.py --out outputs/jacobian_lens --reference-repo URL (supply the paper's reference repository URL)
Pinned inputs GPT-2 small 607a30d783dfa663caf39e06633721c8d4cfcd7e; Mamba-130m 1e76775f628fbf1350fbe4dbb3d971ba64af25a1; reference commit 581d398613e5602a5af361e1c34d3a92ea82ba8e; embedded two-sentence CC0 corpus (SHA-256 5ddd996b8358a88420bf5076c4166c1beba68c003e68fb42f1c212616923624e); seed 0
Verdict Passed; CPU Linux runner, 5 November 2026, TorchLens 2.38.1, commit 138a568085d7acb4dc485d362462c1cd339fbbd3
Cost 2 CPU threads, 1,509.8 s script wall time (25 min 10 s), 6,985,384 KiB peak process RSS

Oracles

For every pre-final block, the relative Frobenius error of its mean Jacobian must be below 1e-3, and top-ten token overlap must be at least 0.9 on every held-out prompt. The fitting corpus is two original sentences embedded in the script, written verbatim as corpus.txt and hashed. This small demonstration corpus is not a fit of the paper's full dataset. Both estimators sum cotangents across target positions and average gradients across source positions, excluding the first and final token. Prompt Jacobians receive equal weight.

The GPT-2 oracle is the paper's reference implementation at the pinned commit, checked out inside the output directory. Mamba is outside that reference's recognized layouts; its oracle computes every exact row with scalar reverse-mode autograd and ordinary block hooks. The TorchLens estimator instead reads live outputs from save_mode="view" traces and batches the vector-Jacobian products. Final normalization and unembedding belong to the readout, outside the fitted Jacobian.

Oracle Rule Measured
GPT-2 Jacobian relative Frobenius error below 1e-3 at every pre-final layer 0.0 at all 11 layers
GPT-2 readout top-ten overlap at least 0.9 at every layer on every held-out prompt 1.0 at all 11 layers on all 3 prompts
GPT-2 replay portable trace and companion matrices reproduce the held-out readout matched
Mamba Jacobian relative Frobenius error below 1e-3 at every pre-final layer between 4.8e-7 (layer 0) and 4.8e-8 (layer 22) over 23 layers
Mamba readout top-ten overlap at least 0.9 at every layer on every held-out prompt 1.0 at all 23 layers on all 3 prompts
Mamba replay portable trace and companion matrices reproduce the held-out readout matched
Coverage both pretrained GPT-2 small and Mamba-130m measured both measured

The GPT-2 errors are exactly zero because the reference implementation and the TorchLens estimator take the same autograd path over the same residual tensors; the Mamba errors are float32 round-off between batched and scalar reverse mode over the sequential scan.

Controls

Transposing a fitted Jacobian must fail the relative-error bar (error at least 1e-3). Applying the Jacobian from a half-stack-distant layer must lower mean held-out top-ten overlap below 0.9. These rules are fixed before running. A control that stays above its bar fails the entry.

Control Measured
GPT-2 transpose, layer 5 relative error 1.1439
GPT-2 wrong layer, layer 10's Jacobian applied at layer 5 overlaps 0.1, 0.1, 0.1 (mean 0.1)
Mamba transpose, layer 5 relative error 1.3322
Mamba wrong layer, layer 16's Jacobian applied at layer 5 overlaps 0.2, 0.4, 0.4 (mean 0.333)

Artifact and replay

For each architecture, the script saves the final fitting trace as a portable .tlspec and all fitted Jacobians as a tensor-only .pt sidecar. The trace records the sidecar's SHA-256 and a held-out residual. Replay checks the sidecar hash and recomputes the readout using the same pinned model's final norm and unembedding. It must reproduce the top-ten token ids exactly. Model weights are a required replay input. Both replays matched.

Each trace is a directory in the 2.38 saved format: a manifest, one JSON table per record kind (one column per key) under records/, and the tensors in safetensors files under blobs/; nothing in it is pickled. The Mamba trace is 13 files, 685,358,014 bytes in all, with gallery sorted-path/content SHA-256 ace33494aa5af7afc31dbcf7ec36842927417d39f8df3a44425923eb36c48702; 333,679,536 bytes of it are tensors and the rest are the record tables, the largest records/op.json (151,682,863 bytes) and records/grad_fn_call.json (107,458,331 bytes). Its matrix sidecar is 54,271,174 bytes, SHA-256 bd42f446eefc3c425075fc832462f4ddfcad7b963223d4fb5f4d9802dd7dc870. The GPT-2 trace is 12 files, 84,346,612 bytes, SHA-256 6718e62577e90447d9f3b68093778ee858a7b1d032ecece206da8a2246747e5c; its sidecar is 25,956,473 bytes, SHA-256 ece32eff20c8dcbd9c6fab7f7babeecc445eadfc15fee4f1bed5a933e80e8112.

An earlier attempt at this entry, on TorchLens 2.36, stopped here: that release saved a trace's metadata as one pickle, the Mamba trace (about 24,000 ops, each holding one gradient record per backward pass of the fit) wrote 123 MB of it, and tl.load refused it at the loader's 24,000,000-opcode allocation ceiling for pickled metadata. The 2.38 format has no pickle to scan: the gradient-function calls of every backward pass are rows of records/grad_fn_call.json, and tl.load reads a 2.38 directory with the format's own JSON and safetensors reader, which never reaches the pickle path or its ceiling. The same fit now saves and reloads. A rerun in between exceeded its 90-minute budget because backward-pass recording had become about six times slower (every grad_fn resolved its op by scanning the op list); the lookup now reads the label index, and this run's fit took its expected time.

The script also writes jacobian_lens.html, a side-by-side layer-by-position top-token grid for the ASCII-face prompt, and one *_readout_grid.json per architecture containing the exact displayed data. The grid keeps token whitespace and full cell text. It shows this miniature-corpus fit's readouts and does not assume that the paper's full-corpus semantic example will reappear.

Basis-grid row

Core verbs: capture, read, save. The TorchLens region counts fitting and held-out capture code; the plain region counts the exact autograd estimator, decoder and overlap function. Model loading, reference checkout and reporting are outside both regions. The pinned reference's line count is not measured. Marked source counts are 41 TorchLens lines and 33 plain lines. Architectures: GPT-2 small and Mamba-130m.

The stamp

{"commit": "138a568085d7acb4dc485d362462c1cd339fbbd3", "date_utc": "2026-11-05T01:39:20Z",
 "device": "cpu", "libraries": {"numpy": "2.2.6", "torchvision": "0.28.0+cpu",
 "transformers": "5.18.0"}, "machine": "x86_64",
 "platform": "Linux-6.8.0-138-generic-x86_64-with-glibc2.39", "python": "3.10.18",
 "torch": "2.13.0+cpu", "torch_threads": 2, "torchlens": "2.38.1", "wall_seconds": 1509.8}

Vary it

--layer selects the layer used in the controls and saved replay. --prompt supplies a held-out readout prompt. --arch chooses gpt2, mamba or both; the gallery coverage oracle requires both pretrained architectures. --quick uses a randomly initialized GPT-2 to exercise the supported-model path and cannot pass the coverage oracle.

The executed stand-in below compares the live-state Jacobian with ordinary hooks and scalar autograd. It downloads nothing.

# recipe-test: tool=transformers expected=True
import torch
from transformers import GPT2Config, GPT2LMHeadModel

import torchlens as tl

torch.manual_seed(0)
config = GPT2Config(n_layer=2, n_head=2, n_embd=8, vocab_size=32,
                   n_positions=16, use_cache=False)
config._attn_implementation = "eager"
model = GPT2LMHeadModel(config).eval()
ids = torch.tensor([[3, 7, 1, 9, 4]])
seen = {}

def retain(index):
    def hook(module, args, output):
        seen[index] = output[0] if isinstance(output, tuple) else output
    return hook

handles = [block.register_forward_hook(retain(i))
           for i, block in enumerate(model.transformer.h)]
try:
    model(ids)
finally:
    for handle in handles:
        handle.remove()
target = seen[1][0, 1:-1].sum(0)
plain = torch.stack([
    torch.autograd.grad(value, seen[0], retain_graph=True)[0][0, 1:-1].mean(0)
    for value in target
])
trace = tl.trace(model, ids, capture=tl.options.CaptureOptions(save_mode="view"))
source = trace["transformer.h.0"].out
target = trace["transformer.h.1"].out[0, 1:-1].sum(0)
captured = torch.autograd.grad(target, source, grad_outputs=torch.eye(8),
                               is_grads_batched=True)[0][:, 0, 1:-1].mean(1)
RESULT = torch.allclose(captured, plain, rtol=1e-3, atol=1e-6)