Coming from TransformerLens¶
This page is for people who read activations with HookedTransformer.run_with_cache, name sites
with hook-point strings, or patch with run_with_hooks. The examples were written against
TransformerLens 4.x (the version line in landscape.md) and the TorchLens version on
this branch. Checked 2026-11-03.
TorchLens labels come from eager PyTorch execution, not from TransformerLens hook-point names, so
the migration is a capture plus site discovery, or a name lookup through
torchlens.mechinterp.resolve_alias.
The same task in both¶
The task: read the residual stream after the first transformer block of a tiny model.
In TransformerLens¶
# migration-test: tool=transformer_lens expected=[1, 4, 16]
import torch
from transformer_lens import HookedTransformer, HookedTransformerConfig
torch.manual_seed(0)
cfg = HookedTransformerConfig(
n_layers=1, d_model=16, n_ctx=8, d_head=8, n_heads=2, d_vocab=64, act_fn="relu"
)
model = HookedTransformer(cfg)
tokens = torch.tensor([[1, 2, 3, 4]])
_, cache = model.run_with_cache(tokens)
RESULT = list(cache["blocks.0.hook_resid_post"].shape)
In TorchLens¶
# migration-test: tool=torchlens expected=[1, 4, 16]
import torch
import transformers
import torchlens as tl
torch.manual_seed(0)
config = transformers.GPT2Config(n_layer=2, n_embd=16, n_head=2, vocab_size=64, n_positions=16)
model = transformers.GPT2LMHeadModel(config).eval()
tokens = torch.tensor([[1, 2, 3, 4]])
trace = tl.trace(model, tokens, save=tl.module("transformer.h.0"))
RESULT = list(trace["transformer.h.0"].out.shape)
trace.cleanup()
Translation table¶
| TransformerLens operation | TorchLens idiom | Parity |
|---|---|---|
Cache activations with run_with_cache |
log = tl.trace(model, x, save=tl.in_module("block")) |
TorchLens records a broader PyTorch op DAG, not only transformer hook points. |
| Inspect hook names | log.find_sites(tl.label_contains("...")) or log.summary() |
Equivalent discovery workflow, different names. |
| Target a hook point by TransformerLens name | torchlens.mechinterp.resolve_alias(log, "blocks.5.attn.hook_pattern") (2.x names, 3.x bridge-cache names, 4.x canonical names such as blocks.5.attn.o.hook_in, plus tuple and compact "k6" forms), or discover once and use tl.label(label) |
TransformerLens spellings resolve against the trace; exact labels remain the reproducible form. |
run_with_hooks |
tl.trace(model, x, intervene=tl.when(site, hook), save=site) |
Capture-time intervene= is the direct live-execution equivalent when the site is known. |
| Patch from a clean cache into a corrupted run | Capture clean and corrupted traces, then corrupted.fork().do(site, tl.patch_from(clean)) |
Equivalent for graph-stable patching. |
act_patch and patching heatmaps |
torchlens.mechinterp.patch_heads_grid / patch_residual_grid (turnkey PatchGrid results); gradient-based attribution through torchlens.attribution |
Turnkey grids ship; checked against TransformerLens as the oracle on the cases the test suite covers. |
| ActivationCache analysis verbs (accumulated residual, decomposition, head stacking, logit attributions) | torchlens.mechinterp.residual_accumulation / residual_decomposition / direct_logit_contributions / attention_head_contributions / head_scores / test_prompt |
The analysis kit follows the ActivationCache design, with credit; the LayerNorm linearization is checked against TransformerLens's cached-scale convention. |
| Activation ablation helpers | tl.zero_ablate, tl.mean_ablate, torchlens.intervention.scramble_elements (the rename of the removed resample_ablate) |
Built-in helpers cover the common cases. |
| Residual stream steering | tl.steer(direction, strength=...) at the discovered site |
Equivalent if the relevant residual op is visible. |
| Attention-head Q/K/V internals under fused SDPA | Attention facets (scores/pattern/z/result menus, head-sliceable views) read head-level semantics with provenance disclosed (captured or reconstructed); torchlens.mechinterp.patch_head patches one head on a fused model through an implementation-independent lowering |
Head-level addressing works on fused models; raw fused-kernel intermediates are still not op-DAG sites, and the facet provenance says which basis you got. |
| Accumulate many prompt variants | tl.Bundle({...}) plus map, compare, at |
Equivalent comparison container. |
| Transformer pictures (attention heads, logit lens, token strips) | torchlens.transformer (attention_views, prediction_trajectory, causal receipt grids) |
Native renderers; the module also carries a CircuitsVis hand-off. |
Every torchlens.mechinterp and facet name above is experimental and may change in any
release: the workflows are stable, the exact names may be ratified differently.
What TorchLens adds¶
- A trace of the stock model.
tl.trace(model, x)records every executed tensor operation of any PyTorch model, including a stock Hugging Face checkpoint, with no model-family list and no re-implementation of the architecture. - Replay verification:
tl.validatechecks the recording against the model's own output, so a mismatch between what ran and what was recorded is reported rather than silent. - Source lines per operation, loop and pass identity (
attn_1_1:2is the second call), and containment per module. - Hook-name resolution on the real model:
torchlens.mechinterp.resolve_alias(trace, name)reports whether a name has a counterpart (real,reconstructed_read_only,needs_capture,structurally_absent,ambiguous) and gives the remedy when it does not. - Interventions as declared, replayable specs (
tl.when,trace.fork().do(...)), and.tlspecpersistence of the recipe.
What TransformerLens does that TorchLens does not¶
- TransformerLens is more ergonomic when your whole workflow lives inside its supported model families and hook-point vocabulary: everything is pre-named, and its teaching corpus (ARENA notebooks, tutorials) speaks those names natively.
- TransformerLens 4.x adds vLLM and Inspect drivers, the backward lens, sparse probing and SVD
circuit tools, and makes
TransformerBridgeits main model class. TorchLens has none of these drivers; for serving-scale capture see landscape.md. - Where an earlier version of this page said TorchLens lacked a transformer component vocabulary
and turnkey patching helpers:
torchlens.mechinterpnow ships the ActivationCache-style analysis kit, name-alias resolution and turnkey patch grids, so that concession is withdrawn. The remaining gap is the curated pre-named vocabulary, not the analysis verbs.
Using both¶
TransformerLens feeds TorchLens¶
There is no bridge module for TransformerLens. Two routes work. Trace a HookedTransformer
directly (it captures fully and is the subject TorchLens's own oracle tests compare against), or
reload the stock Hugging Face model fresh and trace that, which keeps working as HookedTransformer
is retired. TorchLens refuses to trace a transformer_lens.TransformerBridge (3.x or 4.x); see
Semantic differences.
TorchLens feeds TransformerLens¶
There is no bridge. trace[label].out is a plain torch.Tensor, so a tensor or a patching value
captured in TorchLens can be returned from a TransformerLens hook function by hand.
torchlens.mechinterp.resolve_alias goes the other way for names: it accepts TransformerLens
spellings and returns the native TorchLens coordinates.
Semantic differences¶
- A
TransformerBridgecannot be traced. TransformerLens 4.0 (September 2026) makesTransformerBridgeits main model class and starts deprecatingHookedTransformer. TorchLens refuses to capture atransformer_lens.TransformerBridgewith a typedCompatibilityErrornaming the remedy. The bridge redirects attribute assignment to the HF modules it wraps, so TorchLens's forward instrumentation can never land on it; without the refusal, capture died partway through with an internalAttributeError. Trace a pristine model instead. - The bridge and boot machinery (
boot_transformers, bridge construction) mutates the HF model it wraps in place. A model instance that has already passed through the bridge is no longer the pristine model you loaded; reload it fresh before tracing rather than reusing it. - TransformerLens hook names still work on the traced Hugging Face model, and an unknown name is reported rather than guessed:
# migration-test: tool=torchlens expected=('real', 'transformer.h.1.attn', 'structurally_absent')
import torch
import transformers
import torchlens as tl
import torchlens.mechinterp as mi
torch.manual_seed(0)
config = transformers.GPT2Config(n_layer=2, n_embd=32, n_head=2, vocab_size=64, n_positions=16)
config._attn_implementation = "eager"
model = transformers.GPT2LMHeadModel(config).eval()
tokens = torch.randint(0, 64, (2, 8))
trace = tl.trace(model, tokens, save="all")
RESULT = (
mi.resolve_alias(trace, "blocks.0.attn.hook_z").status,
mi.resolve_alias(trace, ("pattern", 1)).module_address,
mi.resolve_alias(trace, "nonsense.hook_foo").status,
)
trace.cleanup()