卷 III · 肚子里CH 12深度 12/20

现学现卖,是一个电路

大模型最像「智能」的本事之一,是在上下文里现学:你在提示词里给它看一个它从没见过的格式、一个新造的词、一张临时的对照表,它马上就能照着用。这看起来需要理解。这一章做全书最干净的一个实验:给 GPT-2 一串毫无意义的乱码,看它怎么在第二遍「学会」它——然后找到干这件事的那几个注意力头,把它们关掉。

17.36 → 0.63 比特关掉 4 个头97.1% → 9.1%
◉ 这一章在 GPT-2 的哪一段

▷ 先猜一下

从 GPT-2 的词表里随机抽 50 个 token,拼成一串乱码(比如 ␣press ␣Constitution ␣coffee gency ␣Book who rop ␣Care…),然后原样再接一遍,一共 100 个。这样的乱码做 30 串,交给 GPT-2,逐个 token 量它的意外。

第一遍它当然猜不中——这些组合它从来没见过。问:第二遍,平均每个 token 的意外是多少?

A 和第一遍差不多,17 比特左右。乱码就是乱码,见过一次也没用 B 10 比特左右。好了一些,但只看过一遍,记不住 C 4 比特左右。记住了一部分 D 不到 1 比特。几乎每个都猜中

答案在本章末尾。

一串乱码,读两遍

30 串乱码,每串 50 个随机 token 重复两遍,开头加一个「文档开始」符。GPT-2 的成绩:

平均意外第一名猜中率
闭眼瞎猜(50257 选 1)15.62 比特—
第一遍17.36 比特—
第二遍0.63 比特97.1%

两件事都值得停下来。第一遍比瞎猜还差:它不是一台「不知道就平均分配」的机器,它满心期待着正常的英文,把概率押在了 ␣the、. 这些词上,于是每个乱码 token 拿到的概率比 1/50257 还低。第二遍几乎全中:97.1% 的位置,它排第一的候选就是乱码里的下一个。看逐个位置的平均意外:

30 串乱码平均:每个位置的意外。绿 = 正常,红 = 关掉 4 个归纳头 0 5 10 15 20 1 25 50 75 100 第几个 token 比特 瞎猜 15.62 重复从这里开始

第二遍开头那一个 token(第 51 个)仍然是 15.13 比特——它没法预知乱码要重复。但只要读到重复开始后的第一个 token,下一个就降到 3.68 比特,再下一个 1.83,然后一路贴着 0 走。看一个具体位置的候选表:

随机乱码的第二遍:这个 token 第一遍出现时花了 14.05 比特 … gency ␣Book who rop 下一个 token 概率 意外 ' Care' 70.42% 0.51 比特 ' care' 2.40% 5.38 比特 '\n' 0.66% 7.25 比特 ' H' 0.47% 7.74 比特 ' Cont' 0.35% 8.17 比特 第二遍:照抄上一遍

同一个 token ␣Care,第一遍出现时花了 14.05 比特;第二遍,GPT-2 给它 70.42%,只花 0.51 比特。它没有「理解」这串乱码,它只是看了一眼,就会照抄了。

谁在抄:归纳头

照抄的规则用一句话就能写下来:「我现在读到的是 A;上一次 A 出现的时候,后面跟的是 B;所以下一个大概是 B。」写成序列:

… [A] [B] … … … [A] → ⟪B⟫ ?

如果有哪个注意力头在执行这条规则,它的注意力应该有一个非常具体的形状:在第二遍的每个位置上,它看向的是「上一次同一个 token 出现的位置的下一格」——也就是 B 所在的地方。按这个形状给 144 个头打分(在第二遍的位置上,平均有多少注意力落在那一格):

头(层.编号)注意力落在「上次那个词的下一格」
5.593.4%
6.991.9%
7.1091.2%
5.190.5%
7.284.2%
10.151.2%
9.948.0%

有四个头干净利落地做着这件事:九成以上的注意力,准确地落在那一格上。这种头有一个名字,叫归纳头(induction head)——「看过 A 后面跟 B,就归纳出 A 后面跟 B」。

一个头做不到,要两个头接力

这里有个难题。归纳头站在第二个 A 的位置上,它要找的是「B 所在的那一格」。可是 B 那一格的向量里,原本只写着「我是 B」——它不知道自己前面是 A。按内容查找的注意力,凭什么认出那一格?

答案是:要有人提前在 B 那一格上写一句「我前面那个是 A」。这正是第 9 章那个只看上一个 token 的 4.11 号头干的活。于是整个电路是两步接力:

两个头接力:先写下「我前面是谁」,再据此去找「上次后面跟的是谁」 A B … … A B ① 4.11 号头:在 B 的位置上写下「我前面是 A」 ② 归纳头(5.5 / 6.9 / 7.10 / 5.1):找「前面是 A」的那一格,取回 B ③ 写下 B 第一遍第二遍
  1. 第 4 层,上一个 token 头:在每一个位置上,把「上一个 token 是谁」写进这个位置的总线。B 那一格于是带上了「我前面是 A」。
  2. 第 5 到 7 层,归纳头:在第二个 A 的位置上,它的查询说「我要找前面是 A 的那一格」,B 那一格的键恰好匹配;它把 B 的内容(「我是 B」)取回来,写进总线,最后被读成「下一个是 B」。

两个头分在不同的层里,后一个的查询要用到前一个写下的东西——这是 Anthropic 在 2022 年的论文《In-context Learning and Induction Heads》里拆出来的一个电路:几个部件按固定方式接起来,完成一个算法。那篇论文还发现,归纳头是在训练中某个时刻突然形成的,而模型「在上下文里学习」的能力,恰好在同一时刻跳了一大截。

把它关掉

说一个部件在干某件事,最有说服力的证据是:把它拿掉,那件事就做不成了;而别的事照常。这里用均值消融来「关掉」一个头:把它的输出,换成它在一段正常文字(第 4 章那篇文章)上的平均输出。这个头照常发声,只是说的永远是同一句平均的废话,不再携带当下的任何信息。

关掉归纳头之后,第二遍每个 token 的意外(比特) 都不关 0.63 关前 1 个 0.76 关前 2 个 1.16 关前 3 个 1.31 关前 4 个 10.78 关前 5 个 12.32 关前 6 个 12.49 关前 8 个 12.88 关前 10 个 14.30 对照:随便关 4 个 0.68
关掉哪些头第一遍第二遍第二遍猜中率
都不关17.360.6397.1%
5.517.300.7696.3%
5.5、6.917.271.1694.2%
5.5、6.9、7.1017.261.3192.9%
5.5、6.9、7.10、5.117.2210.789.1%
再加 7.217.2112.323.4%
前 10 名全关17.2114.300.3%
对照:随便关 4 个别的头(5 次平均)—0.6896.7%

144 个头里关掉 4 个,第二遍的意外从 0.63 比特回到 10.78 比特,猜中率从 97.1% 掉到 9.1%。而第一遍纹丝不动(17.36 → 17.22):这几个头不管别的事,只管照抄。随便关掉 4 个别的头当对照,第二遍几乎不受影响(0.68 比特)。

还有一个细节:关掉 3 个时,猜中率还有 92.9%;关掉第 4 个才断崖式崩塌。这四个头互为备份,剩下任何一个都能把照抄的活大致干完。同样,第一步那个「上一个 token 头」也有备份:单独关掉 4.11,第二遍只涨到 1.04 比特;把最像它的 5 个头一起关掉(4.11、3.7、6.8、2.2、5.6),第二遍涨到 4.03 比特,猜中率降到 71.1%——电路的前半截断了,后半截就拿不到「前面是谁」的信息。

为什么这件事比乱码重要

拿乱码做实验,是为了排除一切「理解」的可能:这串东西没有意思,GPT-2 也从没见过它,它能做的只有照抄。可同一个电路在正常文字里时时刻刻都在工作:

  • 文章里再次出现的人名、专有名词、变量名——第 4 章那篇文章里,「␣ChatGPT」(三个 token)在句中出现了 113 次:第一次花了 11.16 比特,之后平均只要 5.14 比特,便宜了一半多。
  • 提示词里的例子——第 16 章会看到,给 GPT-2 看一个「问:…答:…」的例子,它答对的概率从 1.77% 跳到 63.71%。照抄格式,是这类「少样本学习」的一个核心零件。
  • 复读——第 14 章那些原地打转的输出,也有这个电路的功劳:它太擅长「前面出现过什么,后面就再来一遍」了。

更大的模型里,有「模糊」版本的归纳头:不是照抄完全相同的 token,而是照抄相似的模式(A′ 后面跟 B′)。Anthropic 那篇论文认为,这类机制可能是大模型上下文学习能力的主体。这是一个有分量的猜想,还没有被完全证实——但至少在这一章的实验里,「现学现卖」被一个可以指着、可以关掉的电路完整地解释了。

⌨ 自己跑一遍

同样的 30 串乱码(用同一个随机种子生成),同样的四个头,用 PyTorch 的钩子在 c_proj 的输入上做均值消融:

import math, numpy as np, torch
from transformers import AutoTokenizer, AutoModelForCausalLM

tok = AutoTokenizer.from_pretrained("gpt2")
model = AutoModelForCausalLM.from_pretrained("gpt2").eval()

rng = np.random.default_rng(42)
seq = torch.tensor(np.stack([rng.integers(1000, 10000, 50) for _ in range(30)]))
x = torch.cat([torch.full((30, 1), 50256), seq, seq], 1)    # 开头符 + 50 个随机 token + 原样再来一遍

def two_passes():
    with torch.no_grad():
        logp = torch.log_softmax(model(x).logits[:, :-1], -1)
    b = -logp.gather(-1, x[:, 1:, None])[..., 0] / math.log(2)   # 每个位置的意外(比特)
    return b[:, 1:50].mean().item(), b[:, 50:100].mean().item()

print("第一遍 %.2f 比特/token,第二遍 %.2f 比特/token" % two_passes())

# 均值消融:把四个头的输出,换成它们在一段正常文字上的平均值
heads = [(5, 5), (6, 9), (7, 10), (5, 1)]
text = torch.tensor([tok.encode(open("chatgpt-2026-10-21.txt").read())[:1024]])
mean = {}
grab = lambda L: (lambda mod, args: mean.__setitem__(L, args[0][0].mean(0)))
hooks = [model.transformer.h[L].attn.c_proj.register_forward_pre_hook(grab(L)) for L in {5, 6, 7}]
with torch.no_grad():
    model(text)                                    # c_proj 的输入 = 12 个头的输出拼在一起
for hk in hooks:
    hk.remove()

def patch(L):
    def hook(mod, args):
        z = args[0].clone()
        for l, h in heads:
            if l == L:
                z[..., h * 64:(h + 1) * 64] = mean[L][h * 64:(h + 1) * 64]
        return (z,)
    return hook

for L in {5, 6, 7}:
    model.transformer.h[L].attn.c_proj.register_forward_pre_hook(patch(L))
print("关掉 4 个头:第一遍 %.2f,第二遍 %.2f" % two_passes())

本机输出(和正文用 numpy 版跑出来的数逐位一致):

第一遍 17.36 比特/token,第二遍 0.63 比特/token
关掉 4 个头:第一遍 17.22,第二遍 10.78

试试只关 3 个、或者换成第 9 章的上一个 token 头 (4, 11),看电路在哪一步断。

pip install torch transformers numpy && python 12.py

Colab 里几秒钟跑完。想深入:Anthropic 的 transformer-circuits.pub 上有归纳头的原始论文和交互图;Neel Nanda 的 TransformerLens 教程从这个例子开始。

▸ 在现实里
  • 代码补全特别擅长沿用你文件里的变量名、函数名,哪怕那些名字是你刚刚才起的、世上独一份——这就是归纳头的日常工作。
  • 「照下面的格式输出:……」这种提示特别管用,因为照抄格式是模型里最便宜、最可靠的一个机制。与其用文字描述格式,不如直接给它看一个例子。
  • 提示注入之所以难防,部分原因也在这里:模型天生倾向于延续上下文里出现过的模式,不管那段上下文来自你,还是来自它读到的一个网页。
✗ 这个直觉是错的

「模型能从提示词里现学一个新规律,说明它真正理解了这个规律。」

这一章的乱码实验里根本没有规律可理解,GPT-2 照样在第二遍做到了 97.1%。「现学现卖」至少有一大块,是一个两步的机械电路:一个头记下「我前面是谁」,后面的头据此找到「上次这个词后面跟了什么」,然后照抄。关掉 144 个头里的 4 个,这个本事就没了,别的能力一点不受影响。

这不是在贬低它——照抄恰好是学习里最有用的一个零件。但下次看到模型「一看就会」时,值得先问一句:它是理解了,还是找到了可以照抄的东西?

◇ 揭晓

正确答案是 D:第二遍平均 0.63 比特,97.1% 的位置第一名就猜中。
第一遍是 17.36 比特——比 50257 选 1 闭眼瞎猜的 15.62 比特还差。

A 「乱码就是乱码」——这是把模型想成了一张只会查「全世界的统计规律」的表。它还有一个专门处理「这一段上下文里的规律」的电路,而照抄恰好不需要任何世界知识。 B 「只看过一遍,记不住」——人读一遍乱码确实记不住。模型不需要「记」:注意力每一步都能直接回看前文的每一格,第一遍就在那里,抄就是了。关掉归纳头之后(10.78 比特),它才变成这个选项描述的样子。 C 「记住了一部分」——如果只关掉 5 个最像「上一个 token 头」的头,结果就在这附近(4.03 比特):电路的前半截坏了,后半截只能勉强工作。

这一章的一句话

「在上下文里现学」至少有一大块是一个两步电路:一个头记下「我前面是谁」,后面几层的归纳头据此找到「上次这个词后面跟了什么」并照抄。它在毫无意义的乱码上也能做到 97.1%;关掉 144 个头里的 4 个,这个本事就没了。

拆完这些零件,还剩一个让可解释性研究头疼的问题:为什么大多数神经元都说不清自己代表什么?下一章用一个只有 2 维的玩具模型给出答案:当 5 个特征都很少同时出现时,模型会把 5 个全塞进 2 维里,排成一个相邻夹角约 72 度的五角星——概念比维度多,每一维都身兼数职。