Index Ai Academy
Phase 6 · Modern LLM engineering · Systems & safety

Interpretability

Looking inside models to see what they compute.

About 40 minutes · A free account opens the whole lesson

Create a free account Try part of it here

Already have an account? Sign in to open this lesson

Why it matters

Nobody writes the rules inside a trained model. Training finds them, and then we trust the model with real decisions. Interpretability is the work of opening a model up. It asks what each part computes, why the model gave an answer, and whether it's doing something we'd object to. The field is young. Still, it has already found readable "features" inside frontier models, and AI labs use it for debugging and safety. The course's tiny GPT is small enough to take apart completely, and that's what you'll do here.

What you'll be able to do

  • Read attention patterns to form a hypothesis about what a head does
  • Test the hypothesis causally by ablating heads and measuring the change
  • Use the logit lens to watch a prediction form layer by layer
  • Describe modern methods (probes, sparse autoencoders, circuits) and what they're used for

What it covers

  1. Look, guess, test
  2. Step 1: look, and form a hypothesis
  3. Step 2: test it by switching heads off
  4. Worked example: looking a lot vs. mattering a lot
  5. Step 3: watch the prediction form
  6. Interpretability at scale

Look, guess, test

Say a dish you cook tastes too salty. First you look at the recipe and spot a likely cause: the soy sauce. That's only a guess. To be sure, you cook it again without the soy sauce and taste. If the salt is gone, your guess was right.

Taking a model apart works the same way, in three moves:

  1. Look inside while it runs.
  2. Guess what a part does. A guess you plan to test is called a hypothesis.
  3. Test the guess by changing that part and watching what happens.

You'll use the base tiny GPT from Phase 5. It has 2 layers, and each layer has 4 attention heads, so 8 heads in all. Recall that a head is one small attention unit. From each position, it decides which earlier words to look at, then copies in information from them.

Try it now

A picture of attention is only a hypothesis. Switching parts off is what tests it.

Step 1: look, and form a hypothesis

Give the model the prompt "Our Kestrel tent is made from". Its next word must be a material. But the right material depends on the product: a tent is made of different stuff than a mug. So the model has to bring the word "tent" forward to the end of the sentence somehow.

That suggests a hypothesis: some head looks back at the product word. The code below checks where each head looks from the position of "from". That's the moment the material gets predicted. For each head it prints the two words that get the most attention, with their share of it:

import math
import torch
from tinygpt import load_pretrained

model, tok = load_pretrained("tiny-gpt")
words = tok.tokenize("Our Kestrel tent is made from")
idx = torch.tensor([tok.encode("Our Kestrel tent is made from")])

with torch.no_grad():
    h = model.tok(idx) + model.pos(torch.arange(idx.shape[1]))
    for layer, blk in enumerate(model.blocks):
        a = blk.ln1(h)
        B, T, d = a.shape
        q, k = blk.qkv(a).split(d, dim=-1)[:2]
        q, k = (t.view(B, T, blk.h, d // blk.h).transpose(1, 2) for t in (q, k))
        scores = (q @ k.transpose(-2, -1) / math.sqrt(d // blk.h)).masked_fill(~torch.tril(torch.ones(T, T)).bool(), float("-inf"))
        attention = torch.softmax(scores, dim=-1)[0, :, -1]          # every head, from the last position
        for head in range(blk.h):
            top = attention[head].topk(2)
            looks_at = ", ".join(f"{words[j]} {w:.2f}" for w, j in zip(top.values.tolist(), top.indices.tolist()))
            print(f"layer {layer} head {head}: {looks_at}")
        h = blk(h)
It runs right here in your browser, with nothing to install.

In layer 1, heads 1, 2 and 3 put almost all their attention on "tent". The layer-0 heads spread theirs out. That's a hypothesis, not a conclusion. Attention shows where information could flow. It doesn't show whether that information matters.

How focused is a head?

"Almost all" and "spread out" are words. You can also put a single number on how focused a head is. The number is the entropy you met in Phase 2, applied to one row of attention weights.

Write wiw_i for the share of attention on word number ii. The weights add up to 1. The sign ∑\sum means "add these up over every word". Here log⁡\log is the natural log, as in NumPy's np.log, so the answer comes out in nats, not bits. HH stands for the entropy:

H=−∑iwilog⁡wiH = -\sum_i w_i \log w_i

In plain words: for each word, multiply its share of attention by the log of that share. Add those up and flip the sign. All attention on one word gives 0, a fully focused head. The more evenly the attention spreads, the bigger the number.

A worked example with two words:

  • Split 50/50: each word gives 0.5 × log 0.5 = 0.5 × (−0.693) = −0.347. The sum is −0.693, so H=0.693H = 0.693.
  • Split 90/10: the terms are 0.9 × (−0.105) = −0.095 and 0.1 × (−2.303) = −0.230. So H=0.325H = 0.325, a more focused head.
  • All on one word: log 1 is 0, so H=0H = 0. Words with no attention at all are skipped.

The same sum as a loop:

import math
for weights in ([0.5, 0.5], [0.9, 0.1], [1.0, 0.0]):
    h = sum(-w * math.log(w) for w in weights if w > 0)
    print(weights, round(h, 3))

It prints 0.693, 0.325 and 0.0. The heads in layer 1 that stare at "tent" would score low. The spread-out layer-0 heads would score higher.

Quick check
A head puts 90% of its attention on a word. Why isn't that enough to conclude the head matters for the prediction?
  1. Heads never matter individually
  2. Attention weights are random
  3. Attention is only used during training
  4. Attention shows where information could flow; only a causal test like ablation shows the output depends on it
Show the answer

Attention shows where information could flow; only a causal test like ablation shows the output depends on it

The head might carry very little information from that word. Or later parts of the model might ignore what it brings. You have to change the model to find out.

Step 2: test it by switching heads off

Think of a band playing a song. To hear what the bass adds, you mute the bass and listen again. The standard test inside a model is the same idea, and it's called an ablation: switch one part off and see what breaks.

The code below runs the model's forward pass by hand. (The forward pass is the trip from the input words to the next-word chances.) There's one change. Each head's output gets multiplied by 1 to keep it, or by 0 to switch it off. The test uses four products and three setups:

  • normal: every head on;
  • layer-1 heads 1-3 off: the three heads that stared at the product;
  • all layer-0 heads off: a comparison that silences the other layer.

If the three layer-1 heads carry "which product is this?", switching them off should leave the model guessing materials blindly. For each run the code prints the top three next words and their chances:

import math
import torch
from tinygpt import load_pretrained

model, tok = load_pretrained("tiny-gpt")

def next_token_probs(idx, ablate=()):
    h = model.tok(idx) + model.pos(torch.arange(idx.shape[1]))
    for layer, blk in enumerate(model.blocks):
        a = blk.ln1(h)
        B, T, d = a.shape
        q, k, v = blk.qkv(a).split(d, dim=-1)
        q, k, v = (t.view(B, T, blk.h, d // blk.h).transpose(1, 2) for t in (q, k, v))
        scores = (q @ k.transpose(-2, -1) / math.sqrt(d // blk.h)).masked_fill(~torch.tril(torch.ones(T, T)).bool(), float("-inf"))
        heads = torch.softmax(scores, dim=-1) @ v                       # (B, heads, T, head size)
        keep = torch.tensor([0.0 if (layer, hd) in ablate else 1.0 for hd in range(blk.h)])
        heads = heads * keep.view(1, -1, 1, 1)                          # switch chosen heads off
        h = h + blk.proj(heads.transpose(1, 2).reshape(B, T, d))
        h = h + blk.mlp(blk.ln2(h))
    return torch.softmax(model.head(model.ln_f(h))[0, -1], dim=-1)

experiments = {"normal": (), "layer-1 heads 1-3 off": ((1, 1), (1, 2), (1, 3)),
               "all layer-0 heads off": ((0, 0), (0, 1), (0, 2), (0, 3))}
for product in ["tent", "camp mug", "headlamp", "fleece"]:
    idx = torch.tensor([tok.encode(f"Our Kestrel {product} is made from")])
    for name, heads in experiments.items():
        with torch.no_grad():
            top = next_token_probs(idx, heads).topk(3)
        print(f"{product:<9} {name:<22} " + ", ".join(f"{tok.vocab[i]} {p:.2f}" for p, i in zip(top.values.tolist(), top.indices.tolist())))

Normally each product gets its own materials. The tent gets silnylon, and the mug gets enamel or titanium. The headlamp gets "a", as in "a recycled aluminum body".

With the three layer-1 heads off, all four products get nearly the same generic guess: "responsibly", as in "responsibly sourced down". The model no longer knows what it's describing.

Switching off all of layer 0's heads shifts the chances, and for the tent it's enough to put "recycled" (0.62) just ahead of "silnylon" (0.35). But every product still gets its own materials near the top, with no sign of the generic "responsibly". So the hypothesis is supported: those three layer-1 heads move the product's identity to the place where the material is predicted. (torch here is the course's in-browser PyTorch subset.)

Go deeper What the switch looks like in the code

Each head produces its own block of numbers for every position. The line with keep builds a list like [1, 0, 0, 0]: a 1 for each head to keep and a 0 for each head to switch off. Each head's output is multiplied by its entry, so a 0 turns that head's whole contribution into zeros. The rest of the block then runs as usual. Setting a part to zero is the simplest kind of ablation. Researchers also replace a part's output with its average value, or with its output on a different prompt, which can disturb the model less.

Quick check
What does this print?
with_head = 0.92
without_head = 0.91
print(round(with_head - without_head, 2))
  1. 0.91
  2. 1.83
  3. 0.01
  4. 0.92
Show the answer

0.01

0.92 − 0.91 rounds to 0.01. Switching this head off changed almost nothing, so a story about it being essential doesn't hold up.

Quick check
You switch off all four layer-0 heads, and each product still gets its own materials. What does that suggest?
  1. Layer 0's heads aren't the ones that bring the product's identity to the material prediction
  2. The ablation code is broken
  3. Layer-1 heads 1-3 don't matter either
  4. Layer 0 does nothing at all in this model
Show the answer

Layer 0's heads aren't the ones that bring the product's identity to the material prediction

The chances shift a little, so layer 0 does contribute something. But the product-specific materials survive, so the product's identity must reach "from" another way: through the layer-1 heads.

Worked example

Worked example: looking a lot vs. mattering a lot

Here are made-up results for three heads, to show why you test instead of trusting attention. With every head on, the model gives "silnylon" a 64% chance. For each head you know two things: how much of its attention lands on "tent", and the chance of "silnylon" when that head is switched off. The effect of a head is how far the chance drops when you switch it off: the normal chance minus the chance with the head off. For head A that's 0.64 − 0.61 = 0.03.

normal = 0.64                      # chance of "silnylon" with every head on
heads = {                          # head: (attention on "tent", chance with that head off)
    "head A": (0.95, 0.61),
    "head B": (0.30, 0.12),
    "head C": (0.90, 0.20),
}
for name, (attention, chance_off) in heads.items():
    drop = normal - chance_off
    print(f"{name}: looks at tent {attention:.2f}, drop when off {drop:.2f}")
In the lesson you can change this and run it.

It prints drops of 0.03 for head A, 0.52 for head B and 0.44 for head C.

Head A looks hardest at "tent", yet switching it off barely matters. Maybe it brings little useful information, or later parts ignore it. Head B looks at "tent" least, yet matters most. It might pick up the product from a position where an earlier layer already copied it. Head C both looks and matters.

Ranked by effect, the order is B, C, A. Ranked by attention, it's A, C, B. Only the first ranking tells you what the model depends on.

Step 3: watch the prediction form

Picture a friend reading a mystery novel. You stop them after each chapter and ask, "Who did it?" Their answers show how their thinking built up. The logit lens does this to a model.

Recall two ideas from Phase 5. Every position carries a running list of numbers called the residual stream. Each block reads that list and adds its own update to it. At the very end, the model's final layer norm and output layer turn the list into a score for every word in the vocabulary. Those scores are the logits, and a softmax turns them into chances.

The logit lens applies that same final layer norm and output layer partway through. It asks, "What would the model predict if it stopped here?" The code reads the prediction three times: from the embeddings alone, after block 0, and after block 1. It prints the top four words each time:

import torch
from tinygpt import load_pretrained

model, tok = load_pretrained("tiny-gpt")
idx = torch.tensor([tok.encode("Our Kestrel tent is made from")])
with torch.no_grad():
    h = model.tok(idx) + model.pos(torch.arange(idx.shape[1]))
    stages = [("embeddings only", h)]
    for i, blk in enumerate(model.blocks):
        h = blk(h)
        stages.append((f"after block {i}", h))
    for name, hidden in stages:
        p = torch.softmax(model.head(model.ln_f(hidden))[0, -1], dim=-1)
        top = p.topk(4)
        print(f"{name:<16} " + ", ".join(f"{tok.vocab[i]} {v:.2f}" for v, i in zip(top.values.tolist(), top.indices.tolist())))

From the embedding of "from" alone, the best guess is "a", a common word after "from". After the first block, it makes the generic guess you saw in the ablation: "responsibly" (0.46), then "recycled" (0.38). After the second, it predicts materials for tents. The picture matches the ablation: the product-specific information arrives in layer 1.

Go deeper Why you can read the middle of a model at all

Each block adds its update to the same running list instead of replacing it. So the list keeps the same layout all the way through, and the output layer can read it at any point. The reading is rough, though. The output layer was trained on the final list only, so readings from early layers can be noisy or misleading. Later research methods train a small separate reader for each layer to fix this.

Quick check
In the logit lens run, when do materials for tents first show up at the top?
  1. After block 0, the first block
  2. From the embeddings alone
  3. Never: the lens only shows attention weights
  4. After block 1, the second block
Show the answer

After block 1, the second block

The embeddings alone give "a". Block 0 brings a generic guess: responsibly or recycled. Tent materials appear only after block 1, the layer whose heads the ablation showed carry the product.

Interpretability at scale

Large models are much harder. They have billions of parameters. Worse, a single neuron usually responds to many unrelated things.

The reason is that models pack in more concepts than they have neurons. Think of an old phone keypad: the 2 key stands for A, B and C at once. Each key does several jobs, and you can only tell which letter is meant from context. Neurons share concepts in a similar way. This is called superposition.

Researchers have several tools for this:

  • Probes: train a small classifier on a layer's hidden numbers. If it can read off a topic, a sentiment, or "is this statement true?", that information is present at that layer.
  • Sparse autoencoders (SAEs): learn a dictionary of directions in the hidden numbers, where each direction switches on only rarely. These directions turn out to be far easier to read than single neurons. Anthropic's 2024 "Scaling Monosemanticity" work pulled out millions of these features from Claude 3 Sonnet. ("Monosemantic" means "one meaning each".) They ranged from the Golden Gate Bridge to code bugs to sycophantic praise. Turning up the Golden Gate Bridge feature gave a model that steered every conversation toward the bridge.
  • Circuit tracing: follow how features connect across layers to explain one behavior. One example is how a model plans a rhyme before it writes the line. Another is how it does arithmetic along several paths at once.

What people use these tools for:

  • debugging failures;
  • detecting hidden goals or deception in safety audits;
  • steering: adding or removing a feature direction to change behavior, with no retraining.

Be honest about the limits. Today's methods explain small pieces of big models. Their explanations can be incomplete or misleading. Interpretability is more a research field than a product feature, though its tools show up more and more in model audits.

For marketers: interpretability is why "the model has a hidden bias toward X" can sometimes be checked and fixed directly. Without it, you could only guess from the outputs. Expect it to show up in model safety reports and audits.

Quick check
A probe reads a model's layer 10 and tells true statements from false ones with 95% accuracy. What can you conclude?
  1. Nothing, since probes can't be trained on hidden numbers
  2. The information is present at layer 10, but only an intervention would show the model uses it
  3. The model always tells the truth
  4. Layer 10 is where the model decides what to say
Show the answer

The information is present at layer 10, but only an intervention would show the model uses it

A probe finds a correlation: the information can be read from that layer. Like attention, that's a starting point. To show the model relies on it, change it and see if the behavior changes.

Practice what you just read

  • 5 code examples you can edit and run
  • a practice exercise that checks your code
  • a 5-question quiz
  • 5 review cards for spaced repetition

Create a free account

Try a quiz question

What does the logit lens show?

  1. What the model would predict if it stopped at an intermediate layer
  2. Which training examples influenced a given answer the most
  3. The model's attention weights, layer by layer, for each token
  4. How many parameters each layer has, and how many are in use

Answer it at the end of the lesson to see if you're right, and why.

Before this lesson