Skip to content
kefan.life
Go back

为什么KV缓存没有Q

Edit page

最近在和大模型讨论 Attention 计算过程中 QKV 矩阵的角色时,我发现了一个非常普遍且顽固的误解。

关于“为什么模型在推理(Decode)阶段只缓存 K 和 V,而不缓存 Q”,很多科普文章给出的解释是:“因为 Q 的信息被融合进 KV 里了,所以不需要缓存 Q。”

初听上去,这个说法似乎很合理。但如果沿着公式往深里挖,你会发现它其实是不准确的。更精确的解释应该回到最基础的因果生成逻辑里:旧 Q 不缓存的直接原因是未来步骤的计算根本不需要它;而 KV 需要缓存,是因为未来的每一个新输入,都需要重新与整个历史的 K 和 V 计算注意力。

我们将从最直白的感性认知起步,接着用严谨的数学推导来证明这个结论。

感性认知:独立的新任务与静态上下文

在自回归生成中,模型每前进一步,都会以上一步生成的 Token 为输入,向历史发问,并预测下一个 Token。

我们用一个简单的造句过程来辅助理解。假设模型正在生成一句话,目前已经生成了前三个词,并在上一步刚刚输出了第四个词“开源 (x4x_4)”:

团队 (x1x_1) 昨天 (x2x_2) 发布的 (x3x_3) 开源 (x4x_4)……

现在,为了预测下一个词(第 5 个词),刚刚生成的“开源 (x4x_4)”会作为当前步的唯一输入进入模型。此时,输入向量投影出了代表当前生成任务的查询向量:q4q_4。为了推断接下来该生成什么词,这个 q4q_4 可能会去寻找它关心的核心名词,比如前面的主语“团队”。

如果在此时,我们试图把前 3 步“过去的注意力结果”存下来复用,会发生什么?我们会拿到

  1. “昨天 (x2x_2)”生成时重点关注了“团队 (x1x_1)”的结果
  2. “发布的 (x3x_3)”生成时重点关注了“昨天 (x2x_2)”的结果。

冲突就在这里: 过去词元之间的注意力分布,对于预测当前词毫无帮助。“开源 (x4x_4)”作为一个全新的词,它的句法关注项、语义依赖,与“发布的 (x3x_3)”或“昨天 (x2x_2)”一定完全不同。 新的计算任务需要一套全新的注意力分布,它无法继承旧词元的计算结果。

因此,如果 q4q_4 想要知道自己和“团队”的关系,它必须亲自用 q4q_4 去和“团队”对应的特征 k1k_1 进行交叉计算。如果在显存里没有把“团队”的 k1k_1 像图书馆里的藏书一样静态地码放好,这新的一步计算根本无法起步。

所以,K 和 V 必须随着时间一步步积累(这就是 KV Cache)。而旧的 Q 只是前一个词元计算时的中间变量,一旦它拿到了结果、生成了当年的 Token,它就不会再被需要,自然没有缓存的价值。

公式推导:从维度到矩阵结构

感性认知能够建立直觉,但必须符合数学推导。在进入公式之前,先做一个重要区分——很多初学者之所以对 KV Cache 感到困惑,可能是把训练和推理两种截然不同的数据流混在了一起。

训练 / Prefill 阶段(并行前向传播)

整个序列一次性喂入,输入 XX 的维度是 (n,dmodel)(n, d_{\text{model}})。模型同时算出完整的 Q(n,dk)Q(n, d_k)K(n,dk)K(n, d_k)V(n,dk)V(n, d_k),并通过因果掩码(Causal Mask)确保位置 ii 只能看到 i\le i 的 Token。所有位置之间的注意力在一次前向传播中并行计算完毕。推理时的 Prefill 阶段(处理用户输入的 prompt)采用相同的并行计算方式,并将算出的 KV 写入缓存,供后续 Decode 阶段复用。

Decode 阶段(逐词自回归生成)

序列中的前 nn 个 Token 早已吐出。当前步只有刚生成的最新一个 Token 输入 Transformer,XX 维度仅为 (1,dmodel)(1, d_{\text{model}})

假设模型隐层维度 dmodel=4096d_{\text{model}} = 4096,单头维度 dk=512d_k = 512,历史序列长度 n=100n = 100(已有 100 个 Token 被生成)。当第 101 个 Token x101x_{101} 进入模型时:

第一步:线性投影。 当前 Token 经过投影:

q101=x101WQ(1,512)q_{101} = x_{101} W_Q \rightarrow (1, 512) k101=x101WK(1,512)k_{101} = x_{101} W_K \rightarrow (1, 512) v101=x101WV(1,512)v_{101} = x_{101} W_V \rightarrow (1, 512)

注意:新 Token 必须同时计算 Q、K、V 三者。q101q_{101} 用于当前步检索历史;k101k_{101}v101v_{101} 则立刻被追加进 KV Cache,供后续注意力计算使用。

第二步:结合 KV Cache。 新的 k101k_{101}v101v_{101} 被追加到缓存末尾:

Kcache=[k1;k2;;k101](101,512)K_{\text{cache}} = [k_1; k_2; \dots; k_{101}] \rightarrow (101, 512) Vcache=[v1;v2;;v101](101,512)V_{\text{cache}} = [v_1; v_2; \dots; v_{101}] \rightarrow (101, 512)

第三步:计算注意力得分。

Scores101=q101×Kcache(1,512)×(512,101)=(1,101)\text{Scores}_{101} = q_{101} \times K_{\text{cache}}^\top \rightarrow (1, 512) \times (512, 101) = \mathbf{(1, 101)}

这个长度为 101 的行向量,代表当前 Token 对序列中全部 101 个位置(前 100 个历史位置 + 自身)的关注度。

第四步:加权聚合注意力输出。

o101=softmax(Scores101)×Vcache(1,101)×(101,512)=(1,512)o_{101} = \text{softmax}(\text{Scores}_{101}) \times V_{\text{cache}} \rightarrow (1, 101) \times (101, 512) = \mathbf{(1, 512)}

跟踪完整个计算路径就会发现:历史的 q1,q2,,q100q_1, q_2, \dots, q_{100} 没有出现在任何一步计算里。 公式天然闭合。

从矩阵结构看:为什么连“历史注意力得分”也不值得缓存?

上面的推演证明了旧 Q 不参与计算。但有人可能会追问:既然自回归的每一步都在算 Q×KQ \times K^\top,那我们何不把历史步骤算出来的 Attention Scores 缓存下来,省掉重复计算?

答案是:历史的 Scores 对新 Token 来说也完全没有复用价值。

在解释之前,先明确 Attention 计算中两个关键中间量的含义:

我们可以把自回归生成各步的 Attention Scores 按时序排列成一个下三角矩阵来观察。在第 3 步时:

Scores(3)=[q1k100q2k1q2k20q3k1q3k2q3k3]\text{Scores}^{(3)} = \begin{bmatrix} q_1 k_1^\top & 0 & 0 \\ q_2 k_1^\top & q_2 k_2^\top & 0 \\ q_3 k_1^\top & q_3 k_2^\top & q_3 k_3^\top \end{bmatrix}

当第 4 步 x4x_4 进入模型并投影出 q4q_4 时,它需要的是这一矩阵下方全新的一行

Scoresnew=[q4k1,q4k2,q4k3,q4k4]\text{Scores}_{new} = \begin{bmatrix} q_4 k_1^\top, & q_4 k_2^\top, & q_4 k_3^\top, & q_4 k_4^\top \end{bmatrix}

这 4 个新分数,与上面 3×33 \times 3 历史矩阵里的任何一个元素,在数学上没有任何线性组合关系。因为 q4q3q2q_4 \neq q_3 \neq q_2,你不可能通过对历史分数的任何矩阵变换,凭空计算出 q4q_4 与历史 K 的内积。

这步 q4×K4q_4 \times K_{\le 4}^\top 的矩阵乘法,必须老老实实地从头算一遍。随后,再用这组新分数经过 Softmax 后对历史的 V4V_{\le 4} 进行加权求和,得到最终的 Attention Output。

因此,既然全新意图 Q 的提取无法避免,且完全依赖于完整的 KK 索引和 VV 信息,那历史 KV 就是唯一值得缓存的中间量:每轮只需把新的 ki+1k_{i+1}vi+1v_{i+1} 追加到末尾即可2。而过去的 q1,q2,q3q_1, q_2, q_3 仅存在于历史行中,不会出现在当前行的计算公式里——旧的注意力得分矩阵同理,它的使命在生成当期 Token 时就已经完成了。

因果掩码与跨层传播:为什么这不是马尔可夫链?

上面的推导引出了一个自然的追问:如果 Decode 阶段只输入了一个 Token xnx_n,那这岂不是退化成了“只看上一个词预测下一个词”的马尔可夫链?自回归生成的根本目标是“据所有已知词预测下一个词”——形式上写作 P(xn+1x1,,xn)P(x_{n+1} \mid x_1, \dots, x_n)。为什么输入只是一个单维向量就够了?

因果掩码带来的历史不变性

因果掩码(Causal Mask)是自回归模型的根基性约束:在注意力计算中,位置 ii 只能看到 i\le i 的 Token,永远看不到未来。这条规则带来了一个关键推论——历史不变性:当第 ii 个词被生成时,它在每一层产出的 kik_iviv_i 只依赖 x1,,xix_1, \dots, x_i。之后不管再来多少新词,都不会反向修改位置 ii 的计算结果。历史 KV 一旦算出就永久有效,可以被安全地复用。

这直接回答了“马尔可夫链”疑问的一半:虽然 Decode 阶段物理上只输入了一个新向量,但 qnq_n 通过对 KcacheK_{\text{cache}} 做矩阵乘,一次性遍历了完整前缀:

Scoresn=[qnk1,qnk2,,qnkn]\text{Scores}_n = \begin{bmatrix} q_n k_1^\top, & q_n k_2^\top, & \dots, & q_n k_n^\top \end{bmatrix}

这个长度为 nn 的向量,代表当前 Token 对每一个历史位置的关注度。模型并非只看上一个词——它通过缓存的 KV 看到了所有历史。

反观 RNN:它的隐状态更新 ht=f(ht1,xt)h_t = f(h_{t-1}, x_t) 是隐状态上的一阶马尔可夫过程——全部历史信息必须压缩进一个固定大小的向量 ht1h_{t-1},位置 1 的信号到达位置 tt 需要经过 t1t-1 次状态转移,路径长度为 O(n)O(n)。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 将任意两个位置之间的路径长度压缩到 O(1)O(1)——每个历史位置的 K/V 都可以被当前 qnq_n 直接触及,无需经过中间状态的逐步传递;而因果掩码又保证了这些已缓存的 K/V 不会因未来 Token 的到来而失效。两者结合,才使 KV Cache 既有用、又正确。

“Q 融进了 KV”:到底错在哪,又对在哪?

但这只回答了问题的一半。还有一个更深的层面:文章开头提到的那个说法——“Q 的信息融合进了 KV”——到底错在哪里,又对在哪里?它其实混淆了同层与跨层两个截然不同的机制。

同一层内,Q 不可能“融进” KV。三者都从同一个输入 hi()h_i^{(\ell)} 分别线性投影而来:

qi()=hi()WQ(),ki()=hi()WK(),vi()=hi()WV()q_i^{(\ell)} = h_i^{(\ell)} W_Q^{(\ell)}, \quad k_i^{(\ell)} = h_i^{(\ell)} W_K^{(\ell)}, \quad v_i^{(\ell)} = h_i^{(\ell)} W_V^{(\ell)}

当前层的 KV 在 QKQK^\top 计算之前就已经存在了,不可能说 Q“融进”了同层的 KV。

但在 跨层(Cross-layer) 的意义上,Transformer 的残差连接实现了逐层的信息积累。Transformer 由多个结构相同的 Decoder 层串联叠加,每一层包含 Attention、LayerNorm 和 FFN 子层,并拥有自己独立的权重参数2。为了看清信息如何流动,我们先忽略 LayerNorm 和 FFN(它们是非线性变换,不改变下面的定性结论),只保留 Attention 和残差连接来写出骨架:

A(l)=softmax(Q(l)(K(l))dk)A^{(l)} = \operatorname{softmax}(\frac{Q^{(l)}(K^{(l)})^\top}{\sqrt{d_k}}) H(l+1)A(l)V(l)+H(l)H^{(l+1)} \approx A^{(l)}V^{(l)} + H^{(l)}

当网络继续走到第 l+1l+1 层时,这一层的 K(l+1)K^{(l+1)}H(l+1)H^{(l+1)} 投影而来:

K(l+1)(A(l)V(l)+H(l))WK(l+1)K^{(l+1)} \propto \left( A^{(l)}V^{(l)} + H^{(l)} \right) W_K^{(l+1)}

关键在于: A(l)A^{(l)} 本身就是 Q(l)Q^{(l)}K(l)K^{(l)} 交互后经过 softmax 的产物。因此ll 层的 Q(l)Q^{(l)} 的计算结果,已经以非线性的方式嵌入了第 l+1l+1 层的 K(l+1)K^{(l+1)} 之中。 同理,V(l+1)V^{(l+1)} 也携带了 A(l)A^{(l)} 的痕迹。

到了网络的最后一层(比如 LLaMA 的第 32 层),VV 早已不是最初那个孤立的静态词向量,而是一座经过了 31 层“思考、重组、压缩”后沉淀下来的复杂语义向量,内部融合了过去所有“历史 Q”的交互结果。当 qnq_n 对这些顶层 VV 做加权求和时,它获得的不是 nn 个孤立单词的信息,而是全序列语义经过逐层蒸馏后的精要与细节被浓缩在一个向量中。

最终拿到的 hnh_n 经过 LM Head 投影回词表,即完整实现了:

P(xn+1x1,x2,,xn)P(x_{n+1} \mid x_1, x_2, \dots, x_n)

所以它绝非马尔可夫链。历史 Q 参与 Attention 计算后的结果,已经永久地改变了隐藏状态的几何分布,并妥善保存在下一层的 K 和 V 里——Q 矩阵本身自然可以被丢弃;历史对历史的注意力不需要重算,因为因果掩码保证了历史永远不变。

但再次强调:跨层的信息流动只是一个附带的观察,不是“不缓存 Q”的主因。 真正的原因始终是公式层面的——在 ot=softmax(qtKtdk)Vto_t = \text{softmax}(\frac{q_t K_{\le t}^\top}{\sqrt{d_k}}) V_{\le t} 中,旧 q<tq_{<t} 根本不作为因数出现。

延伸:GQA 架构的数学自由度论证

理解了 QKV 的角色,我们就能自然地看懂 GQA(Grouped-Query Attention)的底层数学逻辑。

问题起源:KV Cache 是推理瓶颈

在早期的 MHA 中,每一个 Q 头都有独立的 K 和 V 头。Decode 阶段极低的算力强度(FLOPs / Bytes)表明,模型大量时间在等待 KV Cache 从显存读取4。为了提升吞吐,必须在数学公式中砍掉 KV 的存储体积,但同时又要保证模型的表达能力不被破坏。

QQ 作为当前步 (1,dk)(1, d_k) 的向量,常驻开销极小。所以优化方向很明确:在不显著损害模型能力的前提下,砍掉 KV 的存储和传输体积。

GQA 的做法:具体维度

标准 MHA 中,HH 个 Q 头各自配备独立的 K 和 V 头。GQA 的改动是:保留全部 HQH_Q 个 Q 头,但只保留 HKVH_{KV} 个 KV 头,让多个 Q 头共享同一组 KV。

以具体参数为例:

权重矩阵维度:

KV Cache 体积直接缩减为 MHA 的 HKV/HQ=1/4H_{KV} / H_Q = 1/4。在上面的 101 Token 例子中,MHA 需要维护 8 份 (101,512)(101, 512) 的 K 矩阵和 8 份 V 矩阵,而 GQA 只需维护 2 份——显存占用下降 75%。

计算过程(以第 1 组为例):Q1,Q2,Q3,Q4Q_1, Q_2, Q_3, Q_4 这四个头全部去和共享的 Kgroup1K_{\text{group1}}Vgroup1V_{\text{group1}} 计算。由于四个 Q 头的投影矩阵各自独立,它们算出的注意力分布完全不同,输出也保持独立。

为什么可以这样做:线性代数的自由度论证

在单头注意力得分计算中,抛开缩放因子,决定“关注什么”的核心算子是复合矩阵:

Mi=WQiWKiM_i = W_{Q_i} W_{K_i}^\top

MHA 中,每个头都拥有独立的 WQiW_{Q_i}WKiW_{K_i},即拥有完整的参数自由度来构建 MiM_i

如果强行让同组 Q 头共享同一个 WKW_K(即 GQA),组合算子变成:

Mi=WQiWKM'_i = W_{Q_i} W_K^\top

尽管 WKW_K 被固定了,每一个头依然拥有自己完全独立的 WQiW_{Q_i}。从线性空间映射的角度看,共享的 WKW_K^\top 将所有 Key 投影到了同一个 dkd_k 维子空间中——这确实是一种约束——但各个头依然可以通过调整 WQiW_{Q_i} 的参数,在这个共享子空间里选择不同的匹配方向。

GQA 论文5的实验表明,这种约束在实践中代价极小:MiM'_i 的有效自由度仍然主要由独立的 WQiW_{Q_i} 掌控,模型依然有足够的参数空间去产生差异化的注意力分布。

这正是 GQA 能够在砍掉大量 KV 头、缓解内存带宽瓶颈的同时,几乎做到模型精度无损的核心原因。总结成一句话就是:

提问的多样性(Q)在特征空间中被保住了,而事实的冗余性(KV)在物理显存中被干掉了。

GQA 的位置:在 MHA 与 MQA 之间

架构KV 头数特点
MHA=HQ= H_Q完全独立,精度最高,KV Cache 最大
GQA1<HKV<HQ1 < H_{KV} < H_Q分组共享,精度接近 MHA,Cache 显著缩减
MQA=1= 1全部共享,速度极快,大模型下可能存在轻微信息瓶颈

MQA6 把 KV 压到极致的 1 个头。随着模型规模增大,单一的 dkd_k 维子空间越来越难以承载高维隐空间的全部信息——WKW_K^\top 将所有 Key 压缩到一个过于狭窄的基底中,造成不可逆的信息丢失。GQA 正是为了在速度和容量之间找到帕累托最优:通过分组机制缓解了单一子空间的信息瓶颈,同时在硬件层面依然把访存吞吐量维持在接近 MQA 的水平。

结语

从“为什么不缓存 Q”到“跨层的信息压缩”,再到“GQA”,大模型架构演进的背后,是算力约束、工程现实与线性代数的综合应用。学习时千万不要过度依赖拟人化的比喻,把矩阵维度和变换过程在脑海里跑一遍,才能获得打好技术基本功。

参考资料

  1. Transformer 架构快速入门

  2. Transformer 全貌及代码实现 2

  3. Attention Is All You Need (Vaswani et al., 2017)

  4. 从 305 GB 到 7.4 GB:大模型 KVCache 架构演进全景

  5. GQA: Training Generalized Multi-Query Transformer Models from Multi-Head Checkpoints (Ainslie et al., 2023)

  6. Fast Transformer Decoding: One Write-Head is All You Need (Shazeer, 2019)


Edit page
Share this post on:

Next Post
Agent架构重构的心路历程:从造轮子到选轮子