NOTE / 2026.04.17
Attention机制:从"挑重点"到"算重点"
不压缩,按需聚焦——用服务端开发者能懂的方式,拆解 Attention 机制的直觉与数学。
开场:代码审查的启示
想象你在做代码审查:一个 PR 有 50 个文件改动,你要判断其中某个函数有没有 bug。你不会把 50 个文件从头到尾逐行读一遍,而是先盯住那个函数,再去翻和它相关的几个文件:它调用了谁的接口、谁把参数传进来、依赖了哪个配置。重点看几处,其余扫一眼就过。这是人的处理方式:当前任务需要什么,就去源信息里找什么,重点看相关的部分。
传统模型怎么做?它把这 50 个文件的内容全部压缩成一段摘要,然后只凭这段摘要来回答问题。文件少的时候还行,文件一多,摘要就丢细节了。你明明需要看第 37 个文件里那个边界条件的处理,但摘要里只留了一句“代码逻辑基本正确”,关键信息没了。这就是信息瓶颈:把任意长度的信息硬塞进一个固定大小的容器,装不下就丢,丢了就回不来。
Attention 的思路完全不同:不再压缩,保留所有原始信息,让模型自己决定”看哪里、看多重”。 一句话总结:Attention = 不压缩,按需聚焦。 但”按需聚焦”说起来容易,模型怎么知道”需”是什么?怎么知道哪里该”聚焦”?这就需要一个可计算的流程。这个流程,就是 Attention 的核心公式。
从直觉到公式
人的”挑重点”是直觉,模型需要一个可计算的流程来实现它:
\[\text{Attention}(Q, K, V) = \text{softmax}\left(\frac{QK^T}{\sqrt{d_k}}\right)V\]先别被符号吓到。对照代码审查的场景,Q、K、V 可以粗略理解为:
- Q(Query):你在找什么——”我要查这个函数的依赖”
- K(Key):每条信息的标签——”这个文件导出了哪些接口”
- V(Value):每条信息的实际内容——”文件的具体实现”
Q 和 K 匹配一下,就知道哪些信息和你当前的需求相关;再用相关程度去加权 V,就得到了”重点看过之后”的输出。这个公式看起来是一行,实际上做了四件事。接下来我们把每一步拆开,用向量走一遍。
Q、K、V 各是什么
在拆公式之前,先把 Q、K、V 讲透。用一个更直觉的例子:图书馆检索。你想找一本关于“分布式系统”的书。你带着一个查询意图走进图书馆——这就是 Q;每本书的书脊上有一个标签,标明它属于什么领域——这就是 K;书的具体内容——这就是 V。你不会逐本翻阅所有书,而是拿你的 Q 去和每本书的 K 做比对:匹配度高的多看几眼(高权重),匹配度低的忽略(低权重),然后从高匹配度的书里提取你需要的信息(加权 V)。这就是 Q、K、V 的协作逻辑:Q 提问,K 应答,V 交付内容。
再换一个场景:你在搜索引擎里输入“Go 并发模型”。搜索框里的关键词就是 Q,每个网页的标题和摘要就是 K,网页的实际内容就是 V。搜索引擎做的事,本质上就是拿你的 Q 和所有网页的 K 做匹配,然后把最相关的 V 排在前面。在数学上,Q、K、V 都是向量。Q 是一个查询向量,K 是一组键向量,V 是一组值向量。它们的维度可以不同,但 K 和 V 的数量必须相同——每个键对应一个值,就像每本书的标签对应那本书的内容。
那为什么只需要 Q、K、V 这三个向量就够了?
因为 Attention 要做的事,本质上只有三步:
- 先表达“我想找什么”,这需要一个查询角色,所以有了 Q。
- 再判断“谁和我相关”,这需要每条信息都有一个可匹配的标识,所以有了 K。
- 最后把真正有用的内容取出来并加权汇总,这需要每条信息携带实际内容,所以有了 V。
也就是说,Attention 的核心任务无非是:提问、匹配、取值。Q 负责提问,K 负责匹配,V 负责取值。只要这三件事能完成,整个注意力机制就已经闭环了,不需要再额外引入第四种基础角色。换个更直白的说法:如果没有 Q,你就不知道“当前到底想找什么”;如果没有 K,你就没法衡量“谁和当前需求最相关”;如果没有 V,你就算找到了相关位置,也拿不到真正要汇总的信息。三者缺一不可,但三者合起来也刚好够用。
还有一个关键点:Q、K、V 不是凭空来的。在原始的 Attention 机制中,输入是一组向量(比如一个句子中每个词的词向量),然后通过三个不同的线性变换(也就是乘以三个不同的权重矩阵 W_Q、W_K、W_V),分别生成 Q、K、V。这意味着模型可以学习如何从同一个输入中提取出不同的角色——什么时候该提问、什么时候该应答、什么时候该交付内容。
逐步拆解计算流程
现在我们用向量把整个计算走一遍。假设输入序列有 N 个位置,每个位置已经生成了自己的 Q、K、V。
第一步:Q 和 K 做点积,得到原始注意力分数
\[Q \cdot K^T\]Q 是一个查询向量,K 是一组键向量。Q 和每个 K 做点积,得到一组标量——这就是“原始注意力分数”。分数越高,说明 Q 和那个 K 越相关。为什么用点积?因为点积衡量的是两个向量的方向一致性:方向越接近,点积越大,意味着“查询意图”和“信息标签”越匹配。这是一个天然的相似度度量。
举个具体数字。假设 Q 是一个 4 维向量 $[1, 0, 1, 0]$,有三个 K 向量:
- K₁ = $[1, 0, 1, 0]$(和 Q 方向完全一致)→ 点积 = 2
- K₂ = $[0, 1, 0, 1]$(和 Q 正交)→ 点积 = 0
- K₃ = $[1, 0, 0, 0]$(和 Q 部分一致)→ 点积 = 1
点积的结果:K₁ 最相关,K₃ 次之,K₂ 不相关。这和直觉完全吻合。
第二步:除以 √dₖ,缩放
\[\frac{QK^T}{\sqrt{d_k}}\]dₖ 是 K 向量的维度。为什么要除以 √dₖ?补一句:dₖ 不是“算出来”的结果,而是一个维度超参数,由你怎么构造 K 向量决定。更具体地说,输入 token 的隐藏向量 $x \in \mathbb{R}^{d_{model}}$ 会经过线性映射得到 K:
\[k = x W_K\]如果 $W_K \in \mathbb{R}^{d_{model} \times d_k}$,那么输出 $k \in \mathbb{R}^{d_k}$,因此 $d_k$ 就是 K 向量的长度。在单头注意力里,通常直接取 $d_k = d_{model}$。因为当维度很高时,点积的数值会变得很大。这里的关键关系是:方差会随着维度 dₖ 线性增长。直观推一下:把 $q, k \in \mathbb{R}^{d_k}$ 展开,点积是
\[s = q \cdot k = \sum_{i=1}^{d_k} q_i k_i\]如果我们做一个近似假设:每个分量 $q_i, k_i$ 相互独立,且都满足
\[\mathbb{E}[q_i] = \mathbb{E}[k_i] = 0, \qquad \operatorname{Var}(q_i) = \operatorname{Var}(k_i) = 1\]那么单项 $q_i k_i$ 的期望是:
\[\mathbb{E}[q_i k_i] = \mathbb{E}[q_i]\mathbb{E}[k_i] = 0\]单项 $q_i k_i$ 的方差是:
\[\begin{aligned} \operatorname{Var}(q_i k_i) &= \mathbb{E}[q_i^2 k_i^2] - \mathbb{E}[q_i k_i]^2 \\ &= \mathbb{E}[q_i^2]\mathbb{E}[k_i^2] - 0 \\ &= 1 \end{aligned}\]于是,整个点积
\[s = \sum_{i=1}^{d_k} q_i k_i\]的方差就是:
\[\begin{aligned} \operatorname{Var}(s) &= \operatorname{Var}\left(\sum_{i=1}^{d_k} q_i k_i\right) \\ &= \sum_{i=1}^{d_k} \operatorname{Var}(q_i k_i) \\ &= d_k \end{aligned}\]所以 dₖ = 64 时,$\operatorname{Var}(s)=64$,标准差是 $\sqrt{64}=8$;dₖ = 512 时,标准差是 $\sqrt{512}\approx 22.6$。这会把 softmax 的输入“拉得很开”,更容易变成近似 one-hot。方差大意味着什么?点积之间的差距会很大。送进 softmax 之后,大的更大、小的更小,输出趋近 one-hot——几乎全部权重集中在一个位置上,其余接近零。梯度也跟着消失,模型很难学习。
用数字感受一下:假设三个原始分数是 $[2, 1, 0]$,softmax 输出是 $[0.67, 0.24, 0.09]$,还有区分度。但如果分数是 $[20, 10, 0]$,softmax 输出就变成了 $[0.9999, 0.0001, 0.0000]$,几乎变成了 one-hot。除以 √dₖ,就是把方差拉回 1,让 softmax 的输入维持在一个合理的范围内。这一步叫缩放点积(Scaled Dot-Product),也是”Scaled Attention”这个名字的由来。
第三步:softmax 归一化
\[\text{softmax}\left(\frac{QK^T}{\sqrt{d_k}}\right)\]softmax 把一组实数转换成一组概率值:全部为正,加和为 1。具体来说,对输入向量 $[x_1, x_2, \ldots, x_n]$,softmax 的计算方式是:
\[\text{softmax}(x_i) = \frac{e^{x_i}}{\sum_j e^{x_j}}\]这里为什么要用 $e^{x}$?
- 先把任意分数变成“可当权重”的正数:原始分数 $x_i$ 可能为负,直接归一化会出现负权重;指数函数保证 $e^{x_i} > 0$,自然就能当权重。
- 可微且“强调差异”:指数是单调且平滑的,$x_i$ 大一点,$e^{x_i}$ 会放大得更多,模型更容易把注意力集中到更相关的位置。
- 对整体平移不敏感(数值更稳):softmax 满足 $\mathrm{softmax}(x) = \mathrm{softmax}(x - c)$。实现里通常取 $c = \max(x)$,写成 $e^{x_i - \max(x)}$,避免指数爆炸/下溢(这也是工程上常用的稳定写法)。
这一步的作用是把原始分数变成权重分布。不是”选一个最重要的”,而是”按相关程度分配注意力”——高度相关的拿 0.6,一般相关的拿 0.3,不太相关的拿 0.1。每个信息源都保留,只是权重不同。用前面的例子继续算。缩放后的分数假设是 $[1.0, 0.5, 0.0]$:
- e¹·⁰ = 2.718
- e⁰·⁵ = 1.649
- e⁰·⁰ = 1.000
- 总和 = 5.367
softmax 输出 = $[0.506, 0.307, 0.186]$。K₁ 拿到了一半的权重,K₂ 和 K₃ 也各有份额。有区分度,但不极端。
第四步:乘以 V,加权求和
\[\text{softmax}\left(\frac{QK^T}{\sqrt{d_k}}\right) V\]用上一步得到的权重,对 V 做加权求和。高权重的 V 贡献大,低权重的 V 贡献小。
假设三个 V 向量分别是:
- V₁ = $[0.8, 0.2]$
- V₂ = $[0.3, 0.7]$
- V₃ = $[0.1, 0.9]$
用权重 $[0.506, 0.307, 0.186]$ 加权求和:
\[\begin{aligned} &0.506 \times [0.8, 0.2] + 0.307 \times [0.3, 0.7] + 0.186 \times [0.1, 0.9] \\ =\;& [0.405, 0.101] + [0.092, 0.215] + [0.019, 0.167] \\ =\;& [0.516, 0.483] \end{aligned}\]最终输出 $[0.516, 0.483]$——它融合了所有信息,但重点突出了与当前查询最相关的 V₁ 的内容。这就是 Attention 的输出:一个向量,既保留了全局视野,又聚焦了局部重点。
关键细节深挖
为什么用点积衡量相似度?
两个向量做点积,几何含义是:方向越一致,点积越大。如果两个向量方向完全相同,点积最大;如果正交,点积为零;如果方向相反,点积为负。这恰好符合我们对”相似度”的直觉:查询意图和信息标签越对路,匹配度越高。点积就是用最简洁的数学操作实现了这个直觉。
当然,衡量相似度不只有点积这一种方式。加性注意力(Additive Attention)用一个小型神经网络来计算相似度,理论上表达力更强,但计算成本也更高。点积注意力的优势在于:一次矩阵乘法就能算完所有位置的相似度,计算效率极高,且在 dₖ 不太大时效果和加性注意力相当。 这也是 Transformer 选择缩放点积注意力的原因:简单、高效、够用。
为什么要缩放?
再强调一遍:不是点积本身有问题,是点积送进 softmax 之后有问题。softmax 对输入的大小非常敏感。输入差距大时,输出趋近 one-hot;输入差距小时,输出趋近均匀分布。我们希望的是中间态——有区分度,但不极端。缩放就是把这个”区分度”控制在合理范围。可以把它类比为一个”音量旋钮”:原始点积是音量开得太大,信号失真(one-hot);缩放就是把音量调回正常范围,让信号清晰可辨。
softmax 的作用:不是选择,是分配
一个常见误解:softmax 是在”选最重要的那个”。其实不是,softmax 是在按比例分配。所有位置都有权重,只是大小不同。这正是 Attention 和 hard attention(只选一个)的区别——它允许模型同时关注多个位置,只是关注程度不同。这也是 Attention 效果好的关键原因之一,因为真实世界的信息关联往往不是”只跟一个东西有关”,而是”跟几个东西都有关,只是程度不同”。
回到代码审查的例子:你审查一个函数时,可能 60% 的注意力放在直接调用链上,30% 放在共享状态上,10% 放在配置文件上。你不会只看一个文件,也不会所有文件等权重地看——你是在分配注意力,而不是选择注意力。
自注意力的视角
到目前为止,Q、K、V 是三组独立的输入:Q 来自一个序列,K 和 V 来自另一个序列。这种场景叫交叉注意力(Cross-Attention),常见于翻译任务:Q 来自目标语言,K 和 V 来自源语言。
但在 Transformer 中,更常用的是自注意力(Self-Attention):Q、K、V 都来自同一个序列。什么意思?每个位置同时扮演三个角色:
- 作为 Q:“我需要了解上下文的哪些信息?”
- 作为 K:“我能为别人提供什么信息?”
- 作为 V:“我实际携带的内容是什么?”
用一个简短的例子走一遍。假设输入序列是”我 爱 北京”,每个词已经被映射成一个向量。
- “爱”作为 Q,和”我”“爱”“北京”三个 K 做点积,算出注意力权重
- 用权重对三个 V 加权求和,得到”爱”的输出表示
- 这个输出融合了上下文信息——”爱”不再只是一个孤立的词向量,而是一个”知道前后文在说什么”的表示
同理,“我”和“北京”也会各自算一遍,得到各自的上下文感知表示。每个位置都这样算一遍,整个序列就完成了一轮信息交换:每个位置都“看”了其他位置,并根据相关性聚合了信息。这就是自注意力的力量:不需要递归、不需要卷积,一次矩阵运算就能捕获序列中任意两个位置的关系。
RNN 需要一步一步往后传,远距离依赖要传很多步才能到达,信息沿途衰减。自注意力没有这个问题——“我”和“北京”之间只隔一次点积,无论它们在序列中相距多远。但这也意味着自注意力的计算复杂度是 O(n²)——每个位置都要和所有位置算一次相似度。这是 Attention 机制的代价,也是后续各种高效注意力变体(如线性注意力、稀疏注意力)试图解决的问题。
数学补课:转置与点积
读到这里,你可能有两个疑问一直没解开:Q 为什么要乘 K 的转置?点积凭什么能表示相似度?这一节把这两件事讲透。
什么是转置,为什么 Q 乘的是 K 的转置
转置就是把矩阵的行和列互换。一个 m×n 的矩阵 A,转置之后变成 n×m 的矩阵 Aᵀ,其中 Aᵀ 的第 i 行第 j 列 = A 的第 j 行第 i 列。
\[A = \begin{bmatrix} 1 & 2 & 3 \\ 4 & 5 & 6 \end{bmatrix}, \quad A^T = \begin{bmatrix} 1 & 4 \\ 2 & 5 \\ 3 & 6 \end{bmatrix}\]那为什么 Attention 公式里是 QKᵀ 而不是 QK?
因为矩阵乘法的维度必须匹配,而且我们需要的恰好是”Q 的每一行和 K 的每一行做点积”。具体来说,假设序列长度为 N,每个位置的 Q 和 K 都是 dₖ 维向量,那么 Q 是 N×dₖ 的矩阵,K 也是 N×dₖ 的矩阵。
- QK:N×dₖ 乘 N×dₖ,维度不匹配,无法相乘
- QKᵀ:N×dₖ 乘 dₖ×N = N×N,完美
QKᵀ 的结果是一个 N×N 的矩阵,其中第 i 行第 j 列的值,恰好是 Q 的第 i 行(第 i 个位置的查询向量)和 K 的第 j 行(第 j 个位置的键向量)的点积。也就是说,转置是为了让 K 的列变成行,从而和 Q 的每一行对齐做点积。这不是什么高深的技巧,纯粹是矩阵乘法的维度规则决定的——你需要什么形状的输出,就得配什么形状的输入。
用数字走一遍。假设 N=3,dₖ=2:
\[Q = \begin{bmatrix} q_{11} & q_{12} \\ q_{21} & q_{22} \\ q_{31} & q_{32} \end{bmatrix}, \quad K = \begin{bmatrix} k_{11} & k_{12} \\ k_{21} & k_{22} \\ k_{31} & k_{32} \end{bmatrix}\] \[K^T = \begin{bmatrix} k_{11} & k_{21} & k_{31} \\ k_{12} & k_{22} & k_{32} \end{bmatrix}\] \[QK^T = \begin{bmatrix} q_{11}k_{11}+q_{12}k_{12} & q_{11}k_{21}+q_{12}k_{22} & q_{11}k_{31}+q_{12}k_{32} \\ q_{21}k_{11}+q_{22}k_{12} & q_{21}k_{21}+q_{22}k_{22} & q_{21}k_{31}+q_{22}k_{32} \\ q_{31}k_{11}+q_{32}k_{12} & q_{31}k_{21}+q_{32}k_{22} & q_{31}k_{31}+q_{32}k_{32} \end{bmatrix}\]看第 (1,2) 位置:q₁₁k₂₁ + q₁₂k₂₂,这就是 Q 的第 1 行和 K 的第 2 行的点积——第 1 个位置对第 2 个位置的注意力分数。
整个 N×N 矩阵,就是所有位置两两之间的注意力分数,一次矩阵乘法全部算完。
为什么点积可以表示相似度
点积的定义很简单:
\[\vec{a} \cdot \vec{b} = \sum_{i=1}^{n} a_i b_i\]但这个定义本身看不出”相似度”的含义。要理解它,需要把点积拆开:
\[\vec{a} \cdot \vec{b} = |\vec{a}| \cdot |\vec{b}| \cdot \cos\theta\]其中 $|\vec{a}|$ 和 $|\vec{b}|$ 是向量的长度(模),$\theta$ 是两个向量之间的夹角。这个等式怎么来的?推导如下。
推导过程:
从余弦定理出发。对于向量 a 和 b 构成的三角形,第三条边是 a - b,根据余弦定理:
\[|\vec{a} - \vec{b}|^2 = |\vec{a}|^2 + |\vec{b}|^2 - 2|\vec{a}||\vec{b}|\cos\theta\]左边展开:
\[\begin{aligned} |\vec{a} - \vec{b}|^2 &= (\vec{a} - \vec{b}) \cdot (\vec{a} - \vec{b}) \\ &= \vec{a} \cdot \vec{a} - 2\vec{a} \cdot \vec{b} + \vec{b} \cdot \vec{b} \\ &= |\vec{a}|^2 - 2(\vec{a} \cdot \vec{b}) + |\vec{b}|^2 \end{aligned}\]代入余弦定理:
\[|\vec{a}|^2 - 2(\vec{a} \cdot \vec{b}) + |\vec{b}|^2 = |\vec{a}|^2 + |\vec{b}|^2 - 2|\vec{a}||\vec{b}|\cos\theta\]两边消去 $|\vec{a}|^2 + |\vec{b}|^2$,得到:
\[-2(\vec{a} \cdot \vec{b}) = -2|\vec{a}||\vec{b}|\cos\theta\] \[\vec{a} \cdot \vec{b} = |\vec{a}||\vec{b}|\cos\theta\]这就是点积的几何含义。
现在看 cosθ 这一项:
- θ = 0°(方向相同)→ cosθ = 1 → 点积最大
- θ = 90°(正交)→ cosθ = 0 → 点积为零
- θ = 180°(方向相反)→ cosθ = -1 → 点积最小(负值)
如果我们在 Attention 的语境下,假设 Q 和 K 的模长大致稳定(缩放之后确实如此),那么点积的大小就主要由 cosθ 决定——方向越一致,点积越大,相似度越高。
在二维坐标系中演示:
左侧拖动 K 向量旋转,右侧对照推导步骤。观察点积值随夹角的变化:
- 0°(同向):cosθ = 1,点积最大 → 最相似
- 90°(正交):cosθ = 0,点积为零 → 无关
- 180°(反向):cosθ = -1,点积最小(负值)→ 最不相似
点积的大小完美对应了”方向一致性”的程度。这就是为什么点积可以用来衡量相似度——它本质上是把”方向接近程度”量化成了一个数字。
总结
Attention 的本质用一句话说:加权聚合,权重由 Q 和 K 的相似度决定。
计算流程用四步概括:
- Q 和 K 点积 → 算相似度
- 除以 √dₖ → 控制数值范围
- softmax → 变成权重分布
- 乘以 V → 加权求和得输出
它解决的核心问题是:如何从大量信息中,按需提取重点。 不是压缩,不是选择,而是按比例分配注意力——这就是 Attention。从代码审查到图书馆检索,从搜索引擎到语言理解,”按需聚焦”的思路无处不在。Attention 机制只是把这个人类直觉变成了一个可计算、可学习的流程。
下一篇预告
这篇文章里,我们反复说”Q、K、V 都是向量”,但有一个关键问题一直悬着:这些向量从哪来? 一个词——比如”爱”——是怎么变成一个数字向量的?变成向量之后,又是怎么通过 W_Q、W_K、W_V 三个矩阵,分别映射成查询向量、键向量和值向量的?
下一篇将拆解这个过程:从词嵌入(Word Embedding)到 Q/K/V 的线性映射,把”词 → 向量 → 角色”这条链路讲透。
DISCUSSION