得分与注意力

阅读 ≈ 10 mins · 推荐使用 Mac 阅读 · powered by ecznlai · 2026-05-27

Q/K/V 给人一种强烈的诱导,让人不得不去设想一个 “查-键-值” 的数据库搜索过程,似乎在暗示一种语言内在的语法规则,学得越多越困惑,靠字面的 query key value 的自然语言说法是理解不了 LLM 如何建模语言的,必须拆开实际的计算过程才能明白为什么。

Q/K/V 的诱导

任何一个初学者,都会被 Q/K/V 的命名所吸引,并会构想:Q 为什么是 query、K 为什么是 key、V 为什么是 value,然后会持续追问 QK^T 在查询什么?最后点乘一个 V 又代表什么?总之,这会陷入一个还原论的怪圈:这些计算是否代表了某种特定的 “语法过程” ?比如注意力是注意到其中的主谓宾语法关系?

虽然模型内部分注意力头确实具备某些语法结构的偏好,但那是梯度下降的结果,要想全面理解 QKV 必须要抛弃字面意思,只从纯粹的矩阵计算(张量计算)看看其过程细节,只有在这个层面上才能真正理解注意力机制如何实现 token 间俩俩互相看看并给出语义的建模过程,以下是今天要讨论的内容,首先 Q K V 三个由可学习的矩阵投影而来,然后是自注意力公式、及其构件:

\begin{aligned} \operatorname{Attention}(Q, K, V) &= \operatorname{softmax}(\frac{QK^T}{\sqrt{d}} + M)V \\[6pt] scores &= \frac{QK^T}{\sqrt{d}} \\[6pt] maskedScores &= scores + M \\[6pt] M \, 代表因果掩&码矩阵,\, 具体怎么个加法 ? \\[6pt] \operatorname{softmax}(masked&Scores) 具体计算是怎样的 ? \\[6pt] \operatorname{softmax}(masked&Scores) \cdot V 代表了什么 ? \end{aligned}

建模:token 间俩俩相互看看

LLM 是自回归输出的,每次生成 nextToken 都要回头看看当前的 “上下文”,怎么看呢?最直接的方式就是弄一个方阵,row 行代表 token 下标,col 列代表 row 对 col 的 “关注度得分” 或者说 “注意力得分”,为了让建模能实现 token 只关注历史 token,需要对每行未来的注意力得分掩盖为 -\infty 代表不需要关注,即需要盖住 i 行 > j 列的格子:

maskedScores = \begin{bmatrix} 1 & 2 & 3 & 4 \\ 1 & 2 & 3 & 4 \\ 1 & 2 & 3 & 4 \\ 1 & 2 & 3 & 4 \\ \end{bmatrix} + \underbrace{\begin{bmatrix} 0 & -\infty & -\infty & -\infty \\ 0 & 0 & -\infty & -\infty \\ 0 & 0 & 0 & -\infty \\ 0 & 0 & 0 & 0 \\ \end{bmatrix}}_{因果掩码矩阵 \,\, M} = \begin{bmatrix} 1 & -\infty & -\infty & -\infty \\ 1 & 2 & -\infty & -\infty \\ 1 & 2 & 3 & -\infty \\ 1 & 2 & 3 & 4 \\ \end{bmatrix}

正是因为要实现 token 间俩俩相互看看,最直接且覆盖最全面的方式就是通过一个 \mathbb{R}^{T \times T} 的方阵直接表达,T 代表总长度,每行代表对应 token 下标对其他 token 的 “注意力得分”,并通过设置 -\infty 的方式实现对后续 token 的屏蔽

好,那么:注意力得分方阵如何拿到?

最基础的方式,你不是要 \mathbb{R}^{T \times T} 方阵吗,直接用嵌入后的 X \in \mathbb{R}^{T \times d} 凑一个出来不就行了: (d 是词嵌入维度)

\begin{aligned} maskedScore &= X \cdot X^T + M \\[6pt] &= \underbrace{\begin{bmatrix} t_0 \\ t_1 \\ t_2 \\ t_3 \\ \end{bmatrix} \cdot \begin{bmatrix} t_0 & t_1 & t_2 & t_3 \\[6pt] \end{bmatrix}}_{ [T \times d] \cdot [d \times T] = [T \times T] } + M \\[6pt] &= \begin{bmatrix} t_0 \cdot t_0 & -\infty & -\infty & -\infty \\ t_1 \cdot t_0 & t_1 \cdot t_1 & -\infty & -\infty \\ t_2 \cdot t_0 & t_2 \cdot t_1 & t_2 \cdot t_2 & -\infty \\ t_3 \cdot t_0 & t_3 \cdot t_1 & t_3 \cdot t_2 & t_3 \cdot t_3 \\ \end{bmatrix} \in \mathbb{R}^{T \times T} \end{aligned}

这种方式可以是可以,但会有很多问题,比如点积运算的最大特点就是跟自己越像的值越大,这种方式会让模型更关注跟自己长得像的词,其次是语言是有序的:“我/爱/打/游戏” 中的 (我_{t_0} \to 爱_{t_1})(爱_{t_1} \to 我_{t_0}) 的语义截然不同,但点积运算满足交换律,导致这种方阵并不能准确理解位置和语义:

我_{t_0} \cdot 爱_{t_1} = 爱_{t_1} \cdot 我_{t_0}

虽然可以通过位置编码解决这类问题,但这只是给 token 下标做了建模,远不足以表达一个 token 看另外一个 token 的语言关系,因此需要给每个 token 配备两套不同的投影以适配它们在注意力看与被看的角色切换:分别对应 QK,它们通过可学习的投影矩阵 W_QW_K 得到。

\begin{aligned} Q = X \cdot W_Q = \begin{bmatrix} q_0 \\ q_1 \\ q_2 \\ q_3 \\ \end{bmatrix} \qquad & \text{—— “各 token 主动看什么” (发起注意)} \\[6pt] K = X \cdot W_K = \begin{bmatrix} k_0 \\ k_1 \\ k_2 \\ k_3 \\ \end{bmatrix} \qquad & \text{—— “各 token 能提供什么” (接受注意)} \end{aligned}

这样一来,(我_{t_0} \to 爱_{t_1}) 的注意力得分就变成了 q_{t_0} \cdot k_{t_1},而 (爱_{t_1} \to 我_{t_0}) 变成了 q_{t_1} \cdot k_{t_0}。因为 W_Q \neq W_K,token “主动看”和“被动看”被建模成两个可学习的投影矩阵,通过训练塑造成完全不同的角色模式(梯度)

QK 的形状都是 \mathbb{R}^{T \times d},因此乘积 QK^T 的形状为 \mathbb{R}^{T \times T},这种内积运算能让 Q 的每一行跟 K 的每一行直接算内积相似度作为注意力得分 最后加上因果掩码,得到的 masked scores(注意力得分,假设 T = 4)

maskedScores = \begin{bmatrix} 1 & 2 & 3 & 4 \\ 1 & 2 & 3 & 4 \\ 1 & 2 & 3 & 4 \\ 1 & 2 & 3 & 4 \\ \end{bmatrix} + \begin{bmatrix} 0 & -\infty & -\infty & -\infty \\ 0 & 0 & -\infty & -\infty \\ 0 & 0 & 0 & -\infty \\ 0 & 0 & 0 & 0 \\ \end{bmatrix} = \begin{bmatrix} 1 & -\infty & -\infty & -\infty \\ 1 & 2 & -\infty & -\infty \\ 1 & 2 & 3 & -\infty \\ 1 & 2 & 3 & 4 \\ \end{bmatrix}

最后,不同量级数值容易造成数值爆炸或消失的问题,因此需要对注意力得分做归一化处理,把一个词看其他词的总注意力当作单位 1,然后按其具体得分进行分配,最经典的方式是 softmax,一方面他永远大于 0 避免负值注意力得分的解释问题,另外一方面它使输出光滑限定在 [0, 1] 内:

\begin{aligned} \operatorname{softmax}( scores + M ) = \frac{e^{scores + M}}{\sum_{i=1}^T e^{scores + M}} \end{aligned}
\begin{aligned} \operatorname{softmax}( scores + M ) = \begin{bmatrix} 1.0 & 0.0 & 0.0 & 0.0 \\ 0.269 & 0.731 & 0.0 & 0.0 \\ 0.090 & 0.245 & 0.665 & 0.0 \\ 0.032 & 0.087 & 0.237 & 0.644 \\ \end{bmatrix} \end{aligned}

如何解释注意力得分方阵?

注意力得分只描述了 token 间的关注度,并没有指出这种关注度应该是怎样的语义,因此需要通过一个可学习的矩阵直接投影得到 V = X \cdot W_V 最后计算 scores \cdot V,行列扫过每一个 V 实现加权分配以表达:这样的注意力得分能代表什么信息:

\begin{aligned} \operatorname{softmax}( scores + M ) \cdot V &= \begin{bmatrix} \bbox[#FFF3E0,2pt]{1.0} & \bbox[#FFE0B2,2pt]{0.0} & \bbox[#FFCC80,2pt]{0.0} & \bbox[#FFB74D,2pt]{0.0} \\ \bbox[#E6F5E6,2pt]{0.269} & \bbox[#C8E6C9,2pt]{0.731} & \bbox[#A5D6A7,2pt]{0.0} & \bbox[#81C784,2pt]{0.0} \\ 0.090 & 0.245 & 0.665 & 0.0 \\ 0.032 & 0.087 & 0.237 & 0.644 \\ \end{bmatrix} \cdot \begin{bmatrix} \bbox[#FFF3E0,3pt]{\bbox[#E6F5E6,1pt]{v0}} \\ \bbox[#FFE0B2,3pt]{\bbox[#C8E6C9,1pt]{v1}} \\ \bbox[#FFE0B2,3pt]{\bbox[#A5D6A7,1pt]{v2}} \\ \bbox[#FFB74D,3pt]{\bbox[#81C784,1pt]{v3}} \\ \end{bmatrix} \\[6pt] \operatorname{Attention}(Q, K, V) &= \begin{bmatrix} \bbox[#FFE0B2,2pt]{1.0 * v0 + \underbrace{0 * v1 + 0 * v2 + 0 * v3}_{后面的看不到了}} \\ \bbox[#A5D6A7,2pt]{0.269 * v0 + 0.731 * v1 + \underbrace{0 * v2 + 0 * v3}_{后面的看不到了}} \\ 0.090 * v0 + 0.245 * v1 + 0.665 * v2 + 0 * v3 \\ 0.032 * v0 + 0.087 * v1 + 0.237 * v2 + 0.644 * v3 \\ \end{bmatrix} \end{aligned}

即用 scores 点乘扫一遍 V,依据 scores 内的行权重代表某 token 应该对其他 token 的关注量,0~1 的系数,在这个层面上实现了 token 对 token 的注意力分配,以及这样的注意力分配能得到什么,复杂度 O(n^2),比如第一行是第一个 token 通常是 bos,启动只需要关注它自己,因此只有 v0 有注意力分配,其他都是 0,第二行则关注自己和前一个,通过因果编码让它不要注意到未来的 token,依次类推,从这个角度实现了语义的捕捉和映射,达成了 token 间俩俩互相看看并依据看看的结果得到注意到的语义,以 scores 表达 token 间的注意力分配系数、以最后的点乘 V 得到注意到的语义,最后通过梯度塑造 W_Q \, W_K \, W_V

为什么一定要 V ?不可以直接 V = X 吗?

如果 V 是 X,那么 attention 就变成了:

\operatorname{Attention}(Q, K, X) = \operatorname{softmax}( maskedScores ) \cdot X

如果 V 还是原来输入的 X 那注意力得分方阵的含义就会变成加权调整输入的 X 了,这只能得到 X 的凸包,表达能力严重受限,即解空间太小了远比不上 V = X \cdot W_V 这里展开解释一下凸包:

V = X 时,我们只能得到这样的输出:

O_i = \sum_{j=1}^{n} a_{ij} \cdot X_j \quad , \qquad a_{ij} \in [0, 1] \,\,且\,\, \sum_{j=1}^{n} a_{ij} = 1

这个公式表达了 O_{i} 必定位于所有输入的 X_i 所围成的凸包之内,意味着:

所以引入 W_K 是必要且必须的,它将真正代表注意力分数代表的语义,而不是简单的“关注度”,关注度只是从这个语义里加权抽取出来,这也是它为啥叫做 Value 的原因。

不要神化 “注意力” 这个词,更应该关注数学与建模

“注意力” 这个词只是一个比喻,用于解释上面的加权分配是一种好理解的说法,它完全可以是 “对其他 token 的重视度” “语义关联” 等等什么别的都行,最重要的是,用数学表达建模才是最重要的:

由于注意力机制在设计上非常简洁,且其主要运算集中在 MatMul,非常适合 GPU 实现大规模 scale,能达到骇人的参数量和推理效果。(大概率几年内估计都没新的架构能直接替代了,资源虹吸太厉害了,其他结构的研究更难拿到经费和算力)

结语

从纯粹的语义过程来看:注意力机制通过加权求和的方式实现了 token 间互相看看的建模,这个看是在词向量空间内的看,不是自然语言的那种主谓宾的看,最后通过 V 将这份得分转为 hidden_state 代表高维语义。

从纯粹的计算角度来看:上下文越长,分配的注意力越容易稀疏,这个稀疏在数学上指的是 softmax 的项增多后更容易造成熵增,这会给模型聚焦关键信息带来困难,是“长度外推”和“长文本”能力的一个核心挑战;另外,历史计算过的 K V 未来可以不用在算,这是 KV-Cache 的优化原理