Install any skill in seconds. Free to start, no credit card required.
Get Started Free →Provides guidance for mechanistic interpretability research using TransformerLens to inspect and manipulate transformer internals via HookPoints and activation caching. Use when reverse-engineering model algorithms, studying attention patterns, or performing activation patching experiments.
.claude/skills/openlair-transformer-lens-interpretability/SKILL.md| Test case | Without → With | Effect | Δ tokens | Δ turns |
|---|---|---|---|---|
| case-02 | ✗→✓ | ▲ Improved | 72% | 0% |
| case-04 | ✗→✓ | ▲ Improved | 116% | 0% |
| case-06 | ✗→✓ | ▲ Improved | 172% | 0% |
| case-09 | ✗→✓ | ▲ Improved | 128% | 0% |
| case-14 | ✗→✓ | ▲ Improved | 240% | 0% |
TransformerLens is the de facto standard library for mechanistic interpretability research on GPT-style language models. Created by Neel Nanda and maintained by Bryce Meyer, it provides clean interfaces to inspect and manipulate model internals via HookPoints on every activation.
GitHub: TransformerLensOrg/TransformerLens (2,900+ stars)
Use TransformerLens when you need to:
Consider alternatives when:
bashpip install transformer-lens
For development version:
bashpip install git+https://github.com/TransformerLensOrg/TransformerLens
The main class that wraps transformer models with HookPoints on every activation:
pythonfrom transformer_lens import HookedTransformer # Load a model model = HookedTransformer.from_pretrained("gpt2-small") # For gated models (LLaMA, Mistral) import os os.environ["HF_TOKEN"] = "your_token" model = HookedTransformer.from_pretrained("meta-llama/Llama-2-7b-hf")
| Family | Models | |--------|--------| | GPT-2 | gpt2, gpt2-medium, gpt2-large, gpt2-xl | | LLaMA | llama-7b, llama-13b, llama-2-7b, llama-2-13b | | EleutherAI | pythia-70m to pythia-12b, gpt-neo, gpt-j-6b | | Mistral | mistral-7b, mixtral-8x7b | | Others | phi, qwen, opt, gemma |
Run the model and cache all intermediate activations:
python# Get all activations tokens = model.to_tokens("The Eiffel Tower is in") logits, cache = model.run_with_cache(tokens) # Access specific activations residual = cache["resid_post", 5] # Layer 5 residual stream attn_pattern = cache["pattern", 3] # Layer 3 attention pattern mlp_out = cache["mlp_out", 7] # Layer 7 MLP output # Filter which activations to cache (saves memory) logits, cache = model.run_with_cache( tokens, names_filter=lambda name: "resid_post" in name )
| Key Pattern | Shape | Description | |-------------|-------|-------------| | resid_pre, layer | batch, pos, d_model] | Residual before attention | | resid_mid, layer | batch, pos, d_model] | Residual after attention | | resid_post, layer | batch, pos, d_model] | Residual after MLP | | attn_out, layer | batch, pos, d_model] | Attention output | | mlp_out, layer | batch, pos, d_model] | MLP output | | pattern, layer | batch, head, q_pos, k_pos] | Attention pattern (post-softmax) | | q, layer | batch, pos, head, d_head] | Query vectors | | k, layer | batch, pos, head, d_head] | Key vectors | | v, layer | batch, pos, head, d_head] | Value vectors |
Identify which activations causally affect model output by patching clean activations into corrupted runs.
pythonfrom transformer_lens import HookedTransformer, patching import torch model = HookedTransformer.from_pretrained("gpt2-small") # 1. Define clean and corrupted prompts clean_prompt = "The Eiffel Tower is in the city of" corrupted_prompt = "The Colosseum is in the city of" clean_tokens = model.to_tokens(clean_prompt) corrupted_tokens = model.to_tokens(corrupted_prompt) # 2. Get clean activations _, clean_cache = model.run_with_cache(clean_tokens) # 3. Define metric (e.g., logit difference) paris_token = model.to_single_token(" Paris") rome_token = model.to_single_token(" Rome") def metric(logits): return logits[0, -1, paris_token] - logits[0, -1, rome_token] # 4. Patch each position and layer results = torch.zeros(model.cfg.n_layers, clean_tokens.shape[1]) for layer in range(model.cfg.n_layers): for pos in range(clean_tokens.shape[1]): def patch_hook(activation, hook): activation[0, pos] = clean_cache[hook.name][0, pos] return activation patched_logits = model.run_with_hooks( corrupted_tokens, fwd_hooks=[(f"blocks.{layer}.hook_resid_post", patch_hook)] ) results[layer, pos] = metric(patched_logits) # 5. Visualize results (layer x position heatmap)
Replicate the IOI circuit discovery from "Interpretability in the Wild".
pythonfrom transformer_lens import HookedTransformer import torch model = HookedTransformer.from_pretrained("gpt2-small") # IOI task: "When John and Mary went to the store, Mary gave a bottle to" # Model should predict "John" (indirect object) prompt = "When John and Mary went to the store, Mary gave a bottle to" tokens = model.to_tokens(prompt) # 1. Get baseline logits logits, cache = model.run_with_cache(tokens) john_token = model.to_single_token(" John") mary_token = model.to_single_token(" Mary") # 2. Compute logit difference (IO - S) logit_diff = logits[0, -1, john_token] - logits[0, -1, mary_token] print(f"Logit difference: {logit_diff.item():.3f}") # 3. Direct logit attribution by head def get_head_contribution(layer, head): # Project head output to logits head_out = cache["z", layer][0, :, head, :] # [pos, d_head] W_O = model.W_O[layer, head] # [d_head, d_model] W_U = model.W_U # [d_model, vocab] # Head contribution to logits at final position contribution = head_out[-1] @ W_O @ W_U return contribution[john_token] - contribution[mary_token] # 4. Map all heads head_contributions = torch.zeros(model.cfg.n_layers, model.cfg.n_heads) for layer in range(model.cfg.n_layers): for head in range(model.cfg.n_heads): head_contributions[layer, head] = get_head_contribution(layer, head) # 5. Identify top contributing heads (name movers, backup name movers)
Find induction heads that implement A]B]...A] → B] pattern.
pythonfrom transformer_lens import HookedTransformer import torch model = HookedTransformer.from_pretrained("gpt2-small") # Create repeated sequence: [A][B][A] should predict [B] repeated_tokens = torch.tensor([[1000, 2000, 1000]]) # Arbitrary tokens _, cache = model.run_with_cache(repeated_tokens) # Induction heads attend from final [A] back to first [B] # Check attention from position 2 to position 1 induction_scores = torch.zeros(model.cfg.n_layers, model.cfg.n_heads) for layer in range(model.cfg.n_layers): pattern = cache["pattern", layer][0] # [head, q_pos, k_pos] # Attention from pos 2 to pos 1 induction_scores[layer] = pattern[:, 2, 1] # Heads with high scores are induction heads top_heads = torch.topk(induction_scores.flatten(), k=5)
python# WRONG: Old hooks remain active model.run_with_hooks(tokens, fwd_hooks=[...]) # Debug, add new hooks model.run_with_hooks(tokens, fwd_hooks=[...]) # Old hooks still there! # RIGHT: Always reset hooks model.reset_hooks() model.run_with_hooks(tokens, fwd_hooks=[...])
python# WRONG: Assuming consistent tokenization model.to_tokens("Tim") # Single token model.to_tokens("Neel") # Becomes "Ne" + "el" (two tokens!) # RIGHT: Check tokenization explicitly tokens = model.to_tokens("Neel", prepend_bos=False) print(model.to_str_tokens(tokens)) # ['Ne', 'el']
python# WRONG: Ignoring LayerNorm pre_activation = residual @ model.W_in[layer] # RIGHT: Include LayerNorm ln_scale = model.blocks[layer].ln2.w ln_out = model.blocks[layer].ln2(residual) pre_activation = ln_out @ model.W_in[layer]
python# Use selective caching logits, cache = model.run_with_cache( tokens, names_filter=lambda n: "resid_post" in n or "pattern" in n, device="cpu" # Cache on CPU )
| Class | Purpose | |-------|---------| | HookedTransformer | Main model wrapper with hooks | | ActivationCache | Dictionary-like cache of activations | | HookedTransformerConfig | Model configuration | | FactoredMatrix | Efficient factored matrix operations |
TransformerLens integrates with SAELens for Sparse Autoencoder analysis:
pythonfrom transformer_lens import HookedTransformer from sae_lens import SAE model = HookedTransformer.from_pretrained("gpt2-small") sae = SAE.from_pretrained("gpt2-small-res-jb", "blocks.8.hook_resid_pre") # Run with SAE tokens = model.to_tokens("Hello world") _, cache = model.run_with_cache(tokens) sae_acts = sae.encode(cache["resid_pre", 8])
For detailed API documentation, tutorials, and advanced usage, see the references/ folder:
| File | Contents | |------|----------| | references/README.md | Overview and quick start guide | | references/api.md | Complete API reference for HookedTransformer, ActivationCache, HookPoints | | references/tutorials.md | Step-by-step tutorials for activation patching, circuit analysis, logit lens |
| Case | Status | Duration (ms) | Turns | Tokens | Tool calls | ||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
| Without | With | Δ | Without | With | Δ | Without | With | Δ | Without | With | Δ | ||
case-01 | pass→pass | 24,399 | 14,418 | -41% | 1 | 1 | 0% | 5,211 | 6,518 | +25% | 0 | 0 | — |
case-02 | fail→pass | 12,413 | 5,537 | -55% | 1 | 1 | 0% | 2,630 | 4,520 | +72% | 0 | 0 | — |
case-03 | pass→pass | 6,341 | 3,346 | -47% | 1 | 1 | 0% | 1,253 | 3,973 | +217% | 0 | 0 | — |
case-04 | fail→pass | 12,378 | 5,723 | -54% | 1 | 1 | 0% | 2,065 | 4,469 | +116% | 0 | 0 | — |
case-05 | pass→pass | 9,429 | 6,802 | -28% | 1 | 1 | 0% | 1,783 | 4,680 | +162% | 0 | 0 | — |
case-06 | fail→pass | 7,555 | 3,027 | -60% | 1 | 1 | 0% | 1,428 | 3,878 | +172% | 0 | 0 | — |
case-07 | pass→pass | 5,432 | 3,915 | -28% | 1 | 1 | 0% | 1,248 | 4,038 | +224% | 0 | 0 | — |
case-08 | pass→pass | 5,294 | 2,490 | -53% | 1 | 1 | 0% | 923 | 3,808 | +313% | 0 | 0 | — |
case-09 | fail→pass | 10,701 | 6,942 | -35% | 1 | 1 | 0% | 2,039 | 4,644 | +128% | 0 | 0 | — |
case-10 | pass→pass | 11,130 | 6,481 | -42% | 1 | 1 | 0% | 1,989 | 4,626 | +133% | 0 | 0 | — |
case-11 | pass→pass | 7,721 | 3,820 | -51% | 1 | 1 | 0% | 1,670 | 4,028 | +141% | 0 | 0 | — |
case-12 | pass→pass | 4,744 | 4,793 | +1% | 1 | 1 | 0% | 1,074 | 4,144 | +286% | 0 | 0 | — |
case-13 | pass→pass | 5,681 | 3,184 | -44% | 1 | 1 | 0% | 1,170 | 4,003 | +242% | 0 | 0 | — |
case-14 | fail→pass | 6,230 | 4,451 | -29% | 1 | 1 | 0% | 1,222 | 4,159 | +240% | 0 | 0 | — |
case-15 | pass→pass | 7,708 | 5,028 | -35% | 1 | 1 | 0% | 1,528 | 4,345 | +184% | 0 | 0 | — |
case-16 | pass→pass | 6,318 | 6,655 | +5% | 1 | 1 | 0% | 1,141 | 4,636 | +306% | 0 | 0 | — |
case-17 | pass→pass | 5,676 | 4,398 | -23% | 1 | 1 | 0% | 1,395 | 4,207 | +202% | 0 | 0 | — |
case-18 | pass→pass | 5,888 | 3,142 | -47% | 1 | 1 | 0% | 1,195 | 3,961 | +231% | 0 | 0 | — |
case-19 | pass→pass | 7,819 | 6,310 | -19% | 1 | 1 | 0% | 1,512 | 4,513 | +198% | 0 | 0 | — |
case-20 | pass→pass | 12,577 | 8,689 | -31% | 1 | 1 | 0% | 2,467 | 5,143 | +108% | 0 | 0 | — |
case-21 | pass→pass | 10,758 | 11,327 | +5% | 1 | 1 | 0% | 2,095 | 5,332 | +155% | 0 | 0 | — |
case-22 | pass→pass | 9,834 | 6,346 | -35% | 1 | 1 | 0% | 1,679 | 4,645 | +177% | 0 | 0 | — |
DecimalAI ran this skill against gemini-3.6-flash twice over the same eval suite — once with the skill loaded and once without — and compared the two runs case by case. 22 cases were attempted. The headline lift of +23 percentage points is the difference between those two pass rates over the 22 comparable cases.
Without the skill loaded, the model failed this case. With it loaded, the same prompt on the same model passed. This is one improved case from the latest verified run; every case, including any that regressed, is in the table above.
Other measured skills in the registry, with their headline benchmark lift.