最近在和大模型讨论 Attention 计算过程中 QKV 矩阵的角色时,我发现了一个非常普遍且顽固的误解。
关于“为什么模型在推理(Decode)阶段只缓存 K 和 V,而不缓存 Q”,很多科普文章给出的解释是:“因为 Q 的信息被融合进 KV 里了,所以不需要缓存 Q。”
初听上去,这个说法似乎很合理。但如果沿着公式往深里挖,你会发现它其实是不准确的。更精确的解释应该回到最基础的因果生成逻辑里:旧 Q 不缓存的直接原因是未来步骤的计算根本不需要它;而 KV 需要缓存,是因为未来的每一个新输入,都需要重新与整个历史的 K 和 V 计算注意力。
我们将从最直白的感性认知起步,接着用严谨的数学推导来证明这个结论。
感性认知:独立的新任务与静态上下文
在自回归生成中,模型每前进一步,都会以上一步生成的 Token 为输入,向历史发问,并预测下一个 Token。
我们用一个简单的造句过程来辅助理解。假设模型正在生成一句话,目前已经生成了前三个词,并在上一步刚刚输出了第四个词“开源 ()”:
团队 () 昨天 () 发布的 () 开源 ()……
现在,为了预测下一个词(第 5 个词),刚刚生成的“开源 ()”会作为当前步的唯一输入进入模型。此时,输入向量投影出了代表当前生成任务的查询向量:。为了推断接下来该生成什么词,这个 可能会去寻找它关心的核心名词,比如前面的主语“团队”。
如果在此时,我们试图把前 3 步“过去的注意力结果”存下来复用,会发生什么?我们会拿到
- “昨天 ()”生成时重点关注了“团队 ()”的结果
- “发布的 ()”生成时重点关注了“昨天 ()”的结果。
冲突就在这里: 过去词元之间的注意力分布,对于预测当前词毫无帮助。“开源 ()”作为一个全新的词,它的句法关注项、语义依赖,与“发布的 ()”或“昨天 ()”一定完全不同。 新的计算任务需要一套全新的注意力分布,它无法继承旧词元的计算结果。
因此,如果 想要知道自己和“团队”的关系,它必须亲自用 去和“团队”对应的特征 进行交叉计算。如果在显存里没有把“团队”的 像图书馆里的藏书一样静态地码放好,这新的一步计算根本无法起步。
所以,K 和 V 必须随着时间一步步积累(这就是 KV Cache)。而旧的 Q 只是前一个词元计算时的中间变量,一旦它拿到了结果、生成了当年的 Token,它就不会再被需要,自然没有缓存的价值。
公式推导:从维度到矩阵结构
感性认知能够建立直觉,但必须符合数学推导。在进入公式之前,先做一个重要区分——很多初学者之所以对 KV Cache 感到困惑,可能是把训练和推理两种截然不同的数据流混在了一起。
训练 / Prefill 阶段(并行前向传播)
整个序列一次性喂入,输入 的维度是 。模型同时算出完整的 、 和 ,并通过因果掩码(Causal Mask)确保位置 只能看到 的 Token。所有位置之间的注意力在一次前向传播中并行计算完毕。推理时的 Prefill 阶段(处理用户输入的 prompt)采用相同的并行计算方式,并将算出的 KV 写入缓存,供后续 Decode 阶段复用。
Decode 阶段(逐词自回归生成)
序列中的前 个 Token 早已吐出。当前步只有刚生成的最新一个 Token 输入 Transformer, 维度仅为 。
假设模型隐层维度 ,单头维度 ,历史序列长度 (已有 100 个 Token 被生成)。当第 101 个 Token 进入模型时:
第一步:线性投影。 当前 Token 经过投影:
注意:新 Token 必须同时计算 Q、K、V 三者。 用于当前步检索历史; 和 则立刻被追加进 KV Cache,供后续注意力计算使用。
第二步:结合 KV Cache。 新的 和 被追加到缓存末尾:
第三步:计算注意力得分。
这个长度为 101 的行向量,代表当前 Token 对序列中全部 101 个位置(前 100 个历史位置 + 自身)的关注度。
第四步:加权聚合注意力输出。
跟踪完整个计算路径就会发现:历史的 没有出现在任何一步计算里。 公式天然闭合。
从矩阵结构看:为什么连“历史注意力得分”也不值得缓存?
上面的推演证明了旧 Q 不参与计算。但有人可能会追问:既然自回归的每一步都在算 ,那我们何不把历史步骤算出来的 Attention Scores 缓存下来,省掉重复计算?
答案是:历史的 Scores 对新 Token 来说也完全没有复用价值。
在解释之前,先明确 Attention 计算中两个关键中间量的含义:
- Attention Scores():提问意图与事实索引交叉匹配后,得出的一组关注度权重。
- Attention Output():系统基于这组关注度,从事实中提取出的最终信息1。
我们可以把自回归生成各步的 Attention Scores 按时序排列成一个下三角矩阵来观察。在第 3 步时:
当第 4 步 进入模型并投影出 时,它需要的是这一矩阵下方全新的一行:
这 4 个新分数,与上面 历史矩阵里的任何一个元素,在数学上没有任何线性组合关系。因为 ,你不可能通过对历史分数的任何矩阵变换,凭空计算出 与历史 K 的内积。
这步 的矩阵乘法,必须老老实实地从头算一遍。随后,再用这组新分数经过 Softmax 后对历史的 进行加权求和,得到最终的 Attention Output。
因此,既然全新意图 Q 的提取无法避免,且完全依赖于完整的 索引和 信息,那历史 KV 就是唯一值得缓存的中间量:每轮只需把新的 、 追加到末尾即可2。而过去的 仅存在于历史行中,不会出现在当前行的计算公式里——旧的注意力得分矩阵同理,它的使命在生成当期 Token 时就已经完成了。
因果掩码与跨层传播:为什么这不是马尔可夫链?
上面的推导引出了一个自然的追问:如果 Decode 阶段只输入了一个 Token ,那这岂不是退化成了“只看上一个词预测下一个词”的马尔可夫链?自回归生成的根本目标是“据所有已知词预测下一个词”——形式上写作 。为什么输入只是一个单维向量就够了?
因果掩码带来的历史不变性
因果掩码(Causal Mask)是自回归模型的根基性约束:在注意力计算中,位置 只能看到 的 Token,永远看不到未来。这条规则带来了一个关键推论——历史不变性:当第 个词被生成时,它在每一层产出的 和 只依赖 。之后不管再来多少新词,都不会反向修改位置 的计算结果。历史 KV 一旦算出就永久有效,可以被安全地复用。
这直接回答了“马尔可夫链”疑问的一半:虽然 Decode 阶段物理上只输入了一个新向量,但 通过对 做矩阵乘,一次性遍历了完整前缀:
这个长度为 的向量,代表当前 Token 对每一个历史位置的关注度。模型并非只看上一个词——它通过缓存的 KV 看到了所有历史。
反观 RNN:它的隐状态更新 是隐状态上的一阶马尔可夫过程——全部历史信息必须压缩进一个固定大小的向量 ,位置 1 的信号到达位置 需要经过 次状态转移,路径长度为 。Transformer 论文3的核心动机正是消除这个瓶颈:”the number of operations required to relate signals from two arbitrary input or output positions grows in the distance between positions… In the Transformer this is reduced to a constant number of operations.” Self-Attention 将任意两个位置之间的路径长度压缩到 ——每个历史位置的 K/V 都可以被当前 直接触及,无需经过中间状态的逐步传递;而因果掩码又保证了这些已缓存的 K/V 不会因未来 Token 的到来而失效。两者结合,才使 KV Cache 既有用、又正确。
“Q 融进了 KV”:到底错在哪,又对在哪?
但这只回答了问题的一半。还有一个更深的层面:文章开头提到的那个说法——“Q 的信息融合进了 KV”——到底错在哪里,又对在哪里?它其实混淆了同层与跨层两个截然不同的机制。
在同一层内,Q 不可能“融进” KV。三者都从同一个输入 分别线性投影而来:
当前层的 KV 在 计算之前就已经存在了,不可能说 Q“融进”了同层的 KV。
但在 跨层(Cross-layer) 的意义上,Transformer 的残差连接实现了逐层的信息积累。Transformer 由多个结构相同的 Decoder 层串联叠加,每一层包含 Attention、LayerNorm 和 FFN 子层,并拥有自己独立的权重参数2。为了看清信息如何流动,我们先忽略 LayerNorm 和 FFN(它们是非线性变换,不改变下面的定性结论),只保留 Attention 和残差连接来写出骨架:
当网络继续走到第 层时,这一层的 由 投影而来:
关键在于: 本身就是 与 交互后经过 softmax 的产物。因此第 层的 的计算结果,已经以非线性的方式嵌入了第 层的 之中。 同理, 也携带了 的痕迹。
到了网络的最后一层(比如 LLaMA 的第 32 层), 早已不是最初那个孤立的静态词向量,而是一座经过了 31 层“思考、重组、压缩”后沉淀下来的复杂语义向量,内部融合了过去所有“历史 Q”的交互结果。当 对这些顶层 做加权求和时,它获得的不是 个孤立单词的信息,而是全序列语义经过逐层蒸馏后的精要与细节被浓缩在一个向量中。
最终拿到的 经过 LM Head 投影回词表,即完整实现了:
所以它绝非马尔可夫链。历史 Q 参与 Attention 计算后的结果,已经永久地改变了隐藏状态的几何分布,并妥善保存在下一层的 K 和 V 里——Q 矩阵本身自然可以被丢弃;历史对历史的注意力不需要重算,因为因果掩码保证了历史永远不变。
但再次强调:跨层的信息流动只是一个附带的观察,不是“不缓存 Q”的主因。 真正的原因始终是公式层面的——在 中,旧 根本不作为因数出现。
延伸:GQA 架构的数学自由度论证
理解了 QKV 的角色,我们就能自然地看懂 GQA(Grouped-Query Attention)的底层数学逻辑。
问题起源:KV Cache 是推理瓶颈
在早期的 MHA 中,每一个 Q 头都有独立的 K 和 V 头。Decode 阶段极低的算力强度(FLOPs / Bytes)表明,模型大量时间在等待 KV Cache 从显存读取4。为了提升吞吐,必须在数学公式中砍掉 KV 的存储体积,但同时又要保证模型的表达能力不被破坏。
而 作为当前步 的向量,常驻开销极小。所以优化方向很明确:在不显著损害模型能力的前提下,砍掉 KV 的存储和传输体积。
GQA 的做法:具体维度
标准 MHA 中, 个 Q 头各自配备独立的 K 和 V 头。GQA 的改动是:保留全部 个 Q 头,但只保留 个 KV 头,让多个 Q 头共享同一组 KV。
以具体参数为例:
- 模型隐层维度
- Q 头数 ,KV 头数
- 组大小 (每 4 个 Q 头共享 1 组 KV 头)
- 每头维度
权重矩阵维度:
- (不变)
- (从 4096 降到 1024)
- (同上)
KV Cache 体积直接缩减为 MHA 的 。在上面的 101 Token 例子中,MHA 需要维护 8 份 的 K 矩阵和 8 份 V 矩阵,而 GQA 只需维护 2 份——显存占用下降 75%。
计算过程(以第 1 组为例): 这四个头全部去和共享的 、 计算。由于四个 Q 头的投影矩阵各自独立,它们算出的注意力分布完全不同,输出也保持独立。
为什么可以这样做:线性代数的自由度论证
在单头注意力得分计算中,抛开缩放因子,决定“关注什么”的核心算子是复合矩阵:
MHA 中,每个头都拥有独立的 和 ,即拥有完整的参数自由度来构建 。
如果强行让同组 Q 头共享同一个 (即 GQA),组合算子变成:
尽管 被固定了,每一个头依然拥有自己完全独立的 。从线性空间映射的角度看,共享的 将所有 Key 投影到了同一个 维子空间中——这确实是一种约束——但各个头依然可以通过调整 的参数,在这个共享子空间里选择不同的匹配方向。
GQA 论文5的实验表明,这种约束在实践中代价极小: 的有效自由度仍然主要由独立的 掌控,模型依然有足够的参数空间去产生差异化的注意力分布。
这正是 GQA 能够在砍掉大量 KV 头、缓解内存带宽瓶颈的同时,几乎做到模型精度无损的核心原因。总结成一句话就是:
提问的多样性(Q)在特征空间中被保住了,而事实的冗余性(KV)在物理显存中被干掉了。
GQA 的位置:在 MHA 与 MQA 之间
| 架构 | KV 头数 | 特点 |
|---|---|---|
| MHA | 完全独立,精度最高,KV Cache 最大 | |
| GQA | 分组共享,精度接近 MHA,Cache 显著缩减 | |
| MQA | 全部共享,速度极快,大模型下可能存在轻微信息瓶颈 |
MQA6 把 KV 压到极致的 1 个头。随着模型规模增大,单一的 维子空间越来越难以承载高维隐空间的全部信息—— 将所有 Key 压缩到一个过于狭窄的基底中,造成不可逆的信息丢失。GQA 正是为了在速度和容量之间找到帕累托最优:通过分组机制缓解了单一子空间的信息瓶颈,同时在硬件层面依然把访存吞吐量维持在接近 MQA 的水平。
结语
从“为什么不缓存 Q”到“跨层的信息压缩”,再到“GQA”,大模型架构演进的背后,是算力约束、工程现实与线性代数的综合应用。学习时千万不要过度依赖拟人化的比喻,把矩阵维度和变换过程在脑海里跑一遍,才能获得打好技术基本功。