总成|Attention 到底在算什么

ClawLihai · · 共 2,751 字 · 约 9 分钟读完

总成 Attention 封面:每个 token 拿着 Query 去和别人 Key 配对,按相关性把 Value 加权拿过来

Attention 到底在算什么

前面十来篇探针,注意力(attention)这个词出现过无数次。

探针 4 讲 Claude Code 凭什么自己干活时,我说模型靠注意力才读得懂工具说明,但「具体在算什么,留到总成」。探针 5 讲 AI 为什么会忘事时,我说模型每生成一个字都要把整个窗口从头看一遍,这个「看」就是注意力,但「具体在算什么,留到总成」。

这篇就是那个总成。一口气把注意力从里到外挖透。

为什么值得专门拆?因为注意力是大模型理解语言的根。前几篇你看到的几乎所有本事——读工具说明、记上下文、看懂图、消歧义词——底下都是它在干活。但前面为了不跑题,每次都只用一句话带过。这篇不省了,把它三步流程、为什么平方爆炸、凭什么越答越快,一次讲清。

这篇只讲注意力这一个零件。它和 FFN、残差连接、LayerNorm、位置编码怎么拼成整个 Transformer,留给下一篇「总成 Transformer」。这里专注一件事:注意力本身在干什么。

三个角色:Q、K、V

注意力的全部秘密,藏在一个叫 Q/K/V 的设计里。

每个 token(对,又是 token,探针 2 讲过)进到注意力这一层,会拿自己的向量(一串数字,探针 3 讲过,模型只认数字)乘以三个不同的矩阵,投影出三样东西:

  • Q(Query,查询):这个 token 「在找什么样的信息」
  • K(Key,键):这个 token 「自称能提供什么」
  • V(Value,值):这个 token 「实际携带的信息」

打个比方,一场聚会上你想找人聊天。你心里揣着自己的提问(Q:「我在找懂 AI 的人」);每个人胸前挂着胸牌(K:「我是搞 AI 的」/「我是搞法律的」);找到对的人后,你从他那儿取走的是他的实际发言(V)。

注意力的核心动作,就是让每个 token 拿着自己的 Q,去和所有人的 K 配对,看谁匹配,匹配度高的就把对方的 V 多拿一点过来,加权混合到自己身上。

一句话先压住全局:Q·K 点积算相关性 → softmax 归一化 → 加权求和 V。 注意力就这三步,没别的。下面一步步拆。

想亲手走一遍这三步?打开 Attention 三步可视化:点「我想吃苹果」里的任一个词,看它的 Q·K 点积 → softmax 权重 → 加权求和,看「苹果」怎么被上下文钉成水果。

第一步:点积打分——Q 和 K 方向越一致,越相关

拿「我想吃苹果」举例。切完 token 是 [我][想][吃][苹果],我们聚焦到「苹果」。

「苹果」要搞清楚自己在当前语境下到底是水果还是公司,得看上下文。它拿着自己的 Q(在找「谁吃我、怎么描述我」),去挨个看其他 token 的 K(胸牌):

Q(苹果) · K(我)    = 0.5    ← 方向差得远,弱相关
Q(苹果) · K(想)    = 0.2    ← 方向差得远,弱相关
Q(苹果) · K(吃)    = 2.5    ← 方向接近,强相关 ★
Q(苹果) · K(苹果)  = 3.0    ← 自己看自己,最高

这里算相关性的操作叫点积(dot product),就是中学数学里那套:两串数字对应位相乘,再全部加起来,得一个数。这个数有个直观含义——在向量长度差不多时,它衡量两串向量的「方向一致程度」。方向越一致,点积越大;方向相反,点积小甚至为负。

「苹果」的 Q 和「吃」的 K 指向相近的方向(都和「进食」「水果」的语义沾边),点积就大;「苹果」的 Q 和「我」的 K 方向八竿子打不着,点积就小。

这一步算出来的数字,行话叫注意力分数(attention score)。它回答的是:「苹果」该多关注谁。

Q/K/V 三角色:「苹果」拿着 Query 去和别人的 Key 配对算相关性,分数高就把对方的 Value 多拿过来

第二步:softmax 归一化——把分数变成权重

上一步算出来的是一批原始分数(0.5、0.2、2.5、3.0……),数字有大有小,加起来也不等于 1。直接拿来用不方便。

所以接一个叫 softmax 的操作,把这批分数归一化成一组「权重」——让它们全部变成 0 到 1 之间的小数,且加起来正好等于 1。

原始分数:        softmax 归一化后:
0.5       →     0.05   (≈ 5%)
0.2       →     0.03   (≈ 3%)
2.5       →     0.35   (≈ 35%)
3.0       →     0.57   (≈ 57%)
                  └──── 加起来 = 1.00

softmax 有个关键特性:它会让强相关的更突出、弱相关的更微弱。看上面这组:强相关的「吃」「苹果」原本点积就大,softmax 后占了 35% + 57% = 92% 的权重;弱相关的「我」「想」原本就小,softmax 后被压到 5% + 3% = 8%。归一化既把分数变成了好用的权重(加起来等于 1),又强化了「相关的多看、不相关的几乎不看」这个分布。

(工程上还有个细节:算点积之前会先除以一个数缩放一下,防止维度一高、softmax 分布太尖导致训练出问题。这不影响核心机制,知道有这回事就行。)

第三步:加权求和——把别人的 V 按权重拿过来

拿到归一化后的权重,「苹果」就按这组权重,把所有 token 的 V 加权混合到自己身上:

新向量(苹果) = 0.05 × V(我) + 0.03 × V(想) + 0.35 × V(吃) + 0.57 × V(苹果)

结果,「苹果」的向量被更新了:它大量吸收了「吃」的语义(权重 0.35)和自己原本的信息(0.57),只沾了一丁点「我」「想」的。更新后的「苹果」向量,现在「知道」自己在这个句子里是被吃的那个水果,而不是苹果公司。

这就是注意力最妙的本事:靠上下文,把一个词的多重含义收敛到正确的那一个。一个词在词典里有一堆意思,但放在具体句子里,注意力通过「看周围谁跟它相关」把它钉死成当下的含义。消歧义,就这么自动完成了。

到这里,标准注意力的全部三步就走完了:点积算相关性 → softmax 归一化 → 加权求和 V。一个 token 看完一圈上下文,把自己的向量更新一遍,注意力这一层就完事了。

softmax 加权求和:「苹果」按权重从所有人那拿信息,大量吸收「吃」的语义,钉死成「被吃的水果」

为什么是 N²——因为人人都得对一遍

现在能彻底看懂探针 5 没展开的那个 N² 了。

注意力的第一步(点积打分)是这样的:每个 token 都要拿自己的 Q,去和所有 token 的 K 算一次点积。 注意是「所有」——不是只看邻居,是全员对照。

N 个 token,每个都要算 N 次点积(跟所有人算一遍,含自己)
→ 总共 N × N = N² 次点积

序列长度一涨,计算量不是线性涨,是平方涨:

序列长度注意力点积次数
100 个 token100 × 100 = 1 万次
1 万个 token1 亿次
100 万 token1 万亿次

序列翻 10 倍,计算量翻 100 倍。这就是探针 5 讲过的那个让上下文窗口不能无限大的「原罪」——「100 个字两两算就是 1 万次」,记住这个口径,N 个 token 两两算 = N×N 次。

为什么必须全员对照、不能偷懒只看邻居?因为信息可能隔得很远。「小明去了很远的银行」里,「银行」要直接看到句首的「小明」才能判断这是金融机构还是河岸——中间隔多少 token 都得能直线关联。注意力能做到任意两个 token 直接对上眼,这正是它取代老一代 RNN(循环神经网络)的关键优势。代价就是人人都得对一遍,于是 N²。

注意力 N²:N 个 token 两两算关系,100 个字 = 1 万次,1 万字 = 1 亿次,平方增长(延续探针 5)

多头注意力——同时盯几种关系

光一套 Q/K/V 不够用。一句话里的关系不止一种:有语法关系(主谓宾)、有指代关系(「他」指代谁)、有长程依赖(句首设定、句末提问)。一套注意力头很难同时把这些都学好。

所以实际的大模型用的是多头注意力(multi-head attention):把 Q/K/V 拆成好几套,每套叫一个「头」,各学一种关系模式,并行算各自的注意力,最后把结果拼起来。

打个比方,读一句话时,你脑子里同时有好几个「频道」在工作:一个频道盯语法、一个频道盯指代、一个频道盯语义关联。多头注意力就是把这个并行读句子的过程显式拆开。每个头独立算一遍,最后汇总。这属于注意力机制本身的扩展,不改三步流程,只是多跑几路。

头数具体多少是模型自己定的。比如 GLM-5.2 用的是 64 个头(这个数字来自它公开的 config,探针 5 的素材里拆过)。每个头独立地学一种「谁该关注谁」的模式,64 个头叠起来,模型就能同时捕捉几十种不同的语言关系。头数越多,模型「看一句话的角度」越丰富,代价是算力和参数也跟着涨。

多个头之间是并行的,各自算各自的注意力、互不干扰,最后把结果拼起来送回给模型继续处理。

KV Cache——为什么越答越快

最后接上探针 5 讲过、但没展开的 KV Cache。

探针 5 说:模型每生成一个字,都要把整个窗口从头「看」一遍。如果每次都真的从头算所有 token 的 K 和 V,那生成一句话要重算无数遍,慢到没法用。

所以有个工程优化叫 KV Cache:把每个位置算出来的 K 和 V 存起来,下一次直接拿来用,不重算。

具体是分两阶段跑的:

阶段 1 Prefill(预填充):处理你的输入
  一次性把输入的所有 token 算一遍
  → 每个位置的 K 和 V 算出来,存进 KV Cache

阶段 2 Decode(解码):一个字一个字蹦
  每生成一个新 token:
    - 用新 token 的 Q,和 Cache 里所有历史 K 算点积
    - 按权重混合历史 V,得到新 token 的更新向量
    - 新 token 自己的 K/V 也存进 Cache
    - Q 用完就扔

这里有个关键设计:K 和 V 要存,Q 要扔。 为什么?因为下一次生成时,是「新的 Q 来查历史的 K/V」——历史的 K 还要被未来的 Q 反复查,所以得留着;V 是别人要取走的信息,也得留。而 Q 是「当前这个 token 在找什么」,每个新 token 的 Q 都不一样,老 Q 不会再被任何人查,存着没复用价值,所以用完即弃。

这个设计把「重复计算」换成了「存储复用」。代价是 KV Cache 会占内存(探针 5 讲过,窗口越长,存的越多,撑爆显存就是「忘事」的根源),但换来的是生成速度——这也是为什么你发一大段话给 AI,第一句回得慢(prefill 在算所有 K/V 填缓存),后面越答越快(缓存放那儿了,decode 直接复用)。

KV Cache:Prefill 把 K/V 存起来,Decode 用新 Q 查历史 K、混合历史 V,新 K/V 入库、Q 用完即弃

这篇挖到了什么

一个「注意力到底在算什么」的疑问,底下连着这些:

  • Q/K/V 三角色:每个 token 拿 Query 找谁匹配、用 Key 自报家门、用 Value 携带实际信息
  • 三步流程:点积算相关性 → softmax 归一化 → 加权求和 V
  • :全员两两算,序列一长平方爆炸(100 字 1 万次,1 万字 1 亿次)
  • 多头注意力:拆成多套头并行,各学一种关系
  • KV Cache:K/V 存起来复用、Q 用完即弃,换来「越答越快」

记住一句话就够:注意力本质是「按相关性分配注意力」——和你问题相关的上下文,权重高、被认真读;无关的,权重低、几乎被忽略。模型不是逐字精读整段,而是在做相关性筛选。

下一篇是另一个总成:注意力只是 Transformer 里的一个零件,Transformer 整体长什么样、注意力怎么和 FFN、残差、LayerNorm 拼到一起,下回拆。

双击或滚轮缩放 · 拖动平移 · Esc 关闭