Mech Interp · ARENA 1.2

Week 4: ARENA 1.2 (Intro to Mech Interp)

Oct 20 – Oct 27

ARENA 1.2 exercises

This week’s exercises

All of Parts 1 and 3. For Part 2, we’ll just do the setup that Part 3 needs (see below).

Total ~2.5 hours.

SectionExerciseImportanceTime
1.2 Part 1Inspect your model3/55 min
1.2 Part 1How many tokens does your model guess correctly?3/510 min
1.2 Part 1Verify activations3/515 min
1.2 Part 2Setup only (see below)—10 min
1.2 Part 2Visualise & inspect attention patterns (optional)3/510 min
1.2 Part 3Calculate induction scores with hooks4/520 min
1.2 Part 3Find induction heads in GPT2-small4/515 min
1.2 Part 3Build logit attribution tool4/515 min
1.2 Part 3Interpret logit attribution for induction heads4/515 min
1.2 Part 3Induction head ablation4/530 min
1.2 Part 3Mean ablation3/510 min

Part 3 uses a good chunk of stuff from Part 2, so you’ll need to do the following in order for Part 3 to work correctly:

  1. Run the setup cells that build the HookedTransformerConfig and load the 2-layer attention-only model (model).
  2. Read the explanations of induction heads and the repeated-token experiment. The notes below should cover most of this, too.
  3. For generate_repeated_tokens, run_and_cache_model_repeated_tokens, and get_log_probs, just paste the solution into your notebook and run it (unless you want to write them yourself).
  4. Run the cell that creates rep_tokens and rep_cache. Part 3 uses both.
  5. Skip the detector exercises and the per-token loss plot.

Background and Overview

Mechanistic interpretability (mech interp) is the attempt to reverse engineer neural networks to better understand how they work. Two words that will come up all the time:

TransformerLens is a Python library (created by Neel Nanda) for doing mech interp on transformer-based language models, and this week’s exercises are centered around learning how to use it. There are a number of other libraries that do similar things (and that are maybe used more frequently today), and later in the program we’ll see how various methods (SAEs, steering) can be run using any of them. That said, TransformerLens is the best place to start and is especially good for learning the underlying/shared thinking behind all of these libraries and methodologies.

The week’s content has four parts:

  1. TransformerLens basics: load a model, run it, and read its activations.
  2. Finding induction heads: look at attention patterns and find heads that work together towards induction tasks.
  3. Hooks: run functions inside the model’s forward pass to measure and change things.
  4. Reverse-engineering the induction circuit: use the weights to explain how two heads work together (optional, not covered this week).

TransformerLens

HookedTransformer

A HookedTransformer is just a transformer (like the ones we built last week) with a HookPoint attached to every internal activation. We’ll get more into these later, but they’re essentially identity functions wrapped around internal activations that provide access points to our transformer’s internals and allow us to manipulate these individual activations using hooks.

Loading a pretrained model:

from transformer_lens import HookedTransformer, utils

gpt2_small = HookedTransformer.from_pretrained("gpt2-small")
gpt2_small.cfg   # n_layers=12, n_heads=12, d_model=768, n_ctx=1024, ...

TransformerLens supports 40+ open models this way. In Part 2 you will build a tiny (2-layer, attention-only) toy model from a HookedTransformerConfig and load its weights from HuggingFace to analyze.

Running the model

To run the model, you can give the model a string or a tensor of token IDs with shape [batch, seq]:

# `model` is any HookedTransformer, e.g. gpt2_small from above
logits = model("I often wish I had a pet monkey", return_type="logits")  # [batch, seq, d_vocab]
loss   = model("I often wish I had a pet monkey", return_type="loss")    # scalar

return_type can also be "both" or None.

Moving between text and tokens

model.to_tokens(text)      # tensor of token IDs, shape [1, seq]
model.to_str_tokens(text)  # list of strings, one per token
model.to_string(tokens)    # back to text

One thing to note: by default, TransformerLens puts a BOS token (<|endoftext|>) at the start of every input. Many heads end up using this as a “resting place” when they have nothing useful to attend to (we will see this in the attention patterns). These helper functions can come in handy for debugging common positional/dimensionality errors.

Parameters vs. activations

run_with_cache and ActivationCache

An activation cache, as the name suggests, is just a cache of the model’s activations! Very helpful for doing mech interp. To save them, just run the model with run_with_cache:

logits, cache = model.run_with_cache(tokens)

cache is an ActivationCache that then is like a dictionary from hook names to tensors consisting of the activations. There are two ways to index it:

cache["blocks.0.attn.hook_pattern"]   # full hook name
cache["pattern", 0]                   # shorthand: (short name, layer)

utils.get_act_name("pattern", 0)      # -> "blocks.0.attn.hook_pattern"

If you pass remove_batch_dim=True to run_with_cache, the batch dimension is removed from every tensor in the cache. This is convenient when you run one prompt. cache.keys() lists every hook name it holds.

Hook point names

Hook names follow the pattern blocks.{layer}.{sublayer}.hook_{name}. Below are some of the main ones we’ll use the most:

Full name (layer 0)Short nameWhat it isShape
hook_embedembedToken embeddings[batch, seq, d_model]
hook_pos_embedpos_embedPositional embeddings[batch, seq, d_model]
blocks.0.hook_resid_preresid_preResidual stream into the block[batch, seq, d_model]
blocks.0.attn.hook_q (also _k, _v)q, k, vQueries, keys, values per head[batch, seq, n_heads, d_head]
blocks.0.attn.hook_attn_scoresattn_scoresScores after scale and mask, before softmax[batch, n_heads, seqQ, seqK]
blocks.0.attn.hook_patternpatternAttention probabilities[batch, n_heads, seqQ, seqK]
blocks.0.attn.hook_zzWeighted sum of values, per head[batch, seq, n_heads, d_head]
blocks.0.hook_attn_outattn_outAttention output, summed over heads[batch, seq, d_model]
blocks.0.hook_resid_midresid_midResidual stream after attention, before MLP[batch, seq, d_model]
blocks.0.mlp.hook_pre / hook_postpre / postMLP neurons before / after GELU[batch, seq, d_mlp]
blocks.0.hook_mlp_outmlp_outMLP output[batch, seq, d_model]
blocks.0.hook_resid_postresid_postResidual stream out of the block[batch, seq, d_model]

Here is a useful diagram from ARENA of one attention-only block. Each box is an activation with its hook name, and the red text is its shape (without the batch dimension).

ARENA diagram of an attention-only TransformerBlock with hook names and shapes

Source: ARENA 1.2. See also the full block with MLP and LayerNorm.

Looking at Attention Patterns

One of the easiest activations to interpret is the attention pattern. It tells us, for each head, which earlier tokens each token is attending to.

Three simple head types

In the 2-layer model, many heads fall into one of three simple types. You can find each type by where its attention mass sits in the attention visualization matrix.

Three small attention patterns showing where current-token, previous-token, and first-token heads put their attention

Induction Heads

What they are

Text repeats itself a lot. Many things like names and phrases show up multiple times in the same document. If you read “Barack Obama” earlier in a story and now you see “Barack”, a very good guess for the next token is “Obama”.

This is what an induction head does! In a more general form:

If the current token is A, and earlier in the context A was followed by B, then predict B.

We can write this as [A][B] ... [A] -> [B]. The head does it in three steps:

  1. Look back for an earlier copy of the current token A.
  2. Attend to the token just after that copy (B).
  3. Copy B into the output, so the model predicts B.
An induction head sees Barack again, attends to the token after the earlier copy, and predicts Obama

Some terminological clarifications:

Original findings

Induction heads were studied in depth by Anthropic in In-context Learning and Induction Heads (Olsson et al., 2022). Some main findings:

Why not just one layer?

This is a pretty important intuition to build. First, we recall from week 2 that an attention head decides where to attend using the query from the current token and the keys from the earlier tokens. In layer 0, each token’s residual stream only contains its own token and position.

But if the current token is A and our attention head wants to attend to B, there’s a problem: B’s residual stream only says “I am B, at position k,” and nothing says “the token before me was A.” So, in layer 0, A has no way to find B.

An induction circuit thus needs two layers:

  1. In the first layer, a previous-token head at B’s position can attend to the token before and write “the previous token was A” into B’s residual stream.
  2. In the second layer, the induction head at the second instance of token A can use a query like “I am A, find a token whose previous token was A.” B’s key can now match this and allow the head to attend to B and copy its value.

The intuition here is that there are a number of tasks like this that require a kind of sequential processing of heads working together across layers.

The repeated-token experiment

To test for induction cleanly, the notebook uses a sequence of random tokens, repeated twice: [BOS] r1 r2 ... rN r1 r2 ... rN. In the first half, the tokens are random, so the model can’t predict them and the loss is high. In the second half, every token has appeared before, so a model with induction heads can just copy, and the loss drops a lot. Since this input looks nothing like training data, doing well on it means the model is using a general copying algorithm.

Where does an induction head attend on this input? Each second-half token attends to the token right after its earlier copy, which is always N − 1 positions back. In the attention pattern, this shows up as a diagonal line N − 1 below the main diagonal, called the induction stripe.

Attention pattern of an induction head on a repeated random sequence, with a stripe N minus 1 below the diagonal

A head’s induction score is just its average attention on this stripe. In Part 3, you compute this score for every head in GPT-2 Small using hooks.

Hooks

run_with_cache lets us read activations after the fact, but hooks let us then intervene on these activations during a forward pass.

One way to think about hooks

(Credit to Claude for the original idea behind this metaphor)

We can think of a forward pass like a flow of water (information/representations) through piping (layers/components). Every HookPoint is like a short section of pipe with an empty slot to insert a filter that alters the composition of the water. The water always flows through it, and by default, the slot is empty, so the water passes straight through as if it isn’t there (an identity function). But, we can also slot in a filter, aka plug a hook function into the HookPoint. With this, every drop of water then has to pass through that function on its way to the rest of the model.

A transformer block drawn as pipes, with one empty hook point and one hook point with a hook function plugged in that changes the color of the water

The hook function

def my_hook(
    pattern: Float[Tensor, "batch n_heads seqQ seqK"],
    hook: HookPoint,
) -> Float[Tensor, "batch n_heads seqQ seqK"] | None:
    ...

The type hints are not required, but they help a lot. Writing down expected shapes is also super helpful for keeping track of things and preventing mistakes.

Running the model with hooks

loss = model.run_with_hooks(
    tokens,
    return_type="loss",
    fwd_hooks=[("blocks.1.attn.hook_pattern", my_hook)],
)

Hook functions take exactly two arguments. When you need more (for example, which head to ablate), use functools.partial to fix the extra arguments first:

from functools import partial

def head_ablation_hook(z, hook, head_index):
    ...

hook_fn = partial(head_ablation_hook, head_index=4)   # now hook_fn(z, hook) works

What you’ll use hooks for in Part 3

Reverse-Engineering Induction Circuits

We’re not going to cover part 4 this week for time-constraint reasons. It’s definitely on the more difficult side of things, but if it’s interesting to you and you have time, I’d highly recommend at least reading through the sections of the notebook!

Further Resources

TransformerLens and Hooks

  1. ARENA 1.2: TransformerLens Introduction
  2. ARENA Lecture: Introduction to Mechanistic Interpretability
  3. TransformerLens Documentation
  4. TransformerLens Main Demo

Induction Heads and Circuits

  1. Induction Heads Illustrated
  2. In-context Learning and Induction Heads
  3. A Mathematical Framework for Transformer Circuits
  4. A Walkthrough of A Mathematical Framework for Transformer Circuits

Getting Into Mech Interp

  1. Neel Nanda’s Mech Interp Explainer & Glossary
  2. Neel Nanda: Concrete Steps to Get Started in Transformer Mechanistic Interpretability
  3. Neel Nanda: Mechanistic Interpretability Quickstart Guide