真实面经题目 · 原创解析
Transformer 推理阶段为什么 KV Cache 只缓存 K 和 V,而通常不缓存当前步的 Q?
这题考的是自回归推理中 attention 计算复用的本质:历史 token 的 K/V 会在未来每一步被反复访问,而 Q 只属于当前查询 token,用完即可丢弃,所以缓存 K/V 能省重复计算,缓存 Q 通常没有收益。
真实面经题目 · 原创解析
这题考的是自回归推理中 attention 计算复用的本质:历史 token 的 K/V 会在未来每一步被反复访问,而 Q 只属于当前查询 token,用完即可丢弃,所以缓存 K/V 能省重复计算,缓存 Q 通常没有收益。
在自回归解码里,第 t 步生成下一个 token 时,模型只新输入当前 token 或当前增量 token。attention 的计算可以写成当前步的 Q 去和从第 1 到第 t 步所有 token 的 K 做相似度,再用这些权重加权对应的 V。历史 token 的 K 和 V 在后续每一步都会被再次用到:第 t+1 步、第 t+2 步都要拿新的 Q 去看同一批历史 K/V,所以把历史 K/V 缓存起来可以避免反复对历史 token 做投影和计算。Q 不一样,它表示“当前 token 要查询什么”。每个解码步只有当前 token 的 Q 会参与本步 attention,下一步会产生一个新的 Q,历史 Q 不再被后续 token 直接使用;因此缓存历史 Q 通常不能减少未来计算,反而浪费显存。prefill 阶段会一次性算出整段 prompt 的 Q/K/V,但进入 decode 后,只需要保留 prompt 和已生成 token 的 K/V。例外是训练或全序列并行计算时会保留 Q/K/V 的中间结果用于反向传播,但那不是推理 KV Cache 的问题。
单头注意力可以写成 Attention(Q, K, V) = softmax(QK^T / sqrt(d))V。自回归解码第 t 步只需要当前 token 的 Q_t 与所有历史 K_1...K_t 做相似度,再加权历史 V_1...V_t。未来每一步都会继续使用已有的 K/V,但不会再使用旧的 Q_t 作为查询。
K 可以理解为每个历史 token 暴露给后来 token 匹配的索引,V 是匹配后要读取的内容。只要后续 token 还可能关注历史 token,历史 K/V 就是可复用的记忆。缓存它们后,新步只需算当前 token 的 K/V 并 append 到 cache,而不用把整个前缀重新过一遍投影。
Q 表示当前 token 在这一层、这一头里想从上下文中查询什么。它只在本步用于计算注意力权重。下一步 token 的语义状态不同,会生成新的 Q;旧 Q 不会作为被查询对象,也不会参与未来的 softmax。因此缓存历史 Q 对未来 decode 基本没有计算复用价值。
prefill 阶段处理完整 prompt,可以并行计算所有位置的 Q/K/V,并建立初始 KV Cache。decode 阶段每次追加一个或一小批 token,只计算新增位置的 Q/K/V,然后把新增 K/V 追加到 cache。缓存的核心收益发生在 decode 阶段,因为它把每步复杂度从重复处理全部前缀降为只处理新增 token 加读取历史 K/V。
KV Cache 已经是推理显存的大头,大小与层数、batch、序列长度、head 数和 head_dim 成正比。如果再缓存 Q,显存几乎会额外增加一份同量级张量,但历史 Q 未来不用,无法换来主要计算节省。因此主流实现选择缓存 K/V,而让 Q 在每个新增 token 上即时计算、即时消费。
第 t 步当前 token 的 K_t 在本步可能被自己或同一步内其他新增 token 用到,更重要的是它会成为未来第 t+1、t+2 步的历史 K。V_t 同理会被未来读取。所以当前步生成出的 K/V 会写入 cache,成为后续步骤的上下文记忆。
训练时通常对整段序列并行做 attention,并需要保存激活用于反向传播,框架可能保留 Q/K/V 或重计算它们来节省显存。那是训练的 activation 管理问题。KV Cache 特指自回归推理中的前缀复用,目标是减少重复前向计算和降低首 token 后的增量解码成本。
Multi-Query Attention 和 Grouped-Query Attention 减少 K/V head 数,让多个 query head 共享一组或一部分 K/V,从而降低 KV Cache 显存和带宽压力。它们并不改变“缓存 K/V 而不是 Q”的基本原因,只是让被缓存的历史记忆更小。
每生成一个新 token 都要重新对整个历史前缀做投影和 attention 相关计算,前缀越长,重复计算越多。KV Cache 复用历史 K/V 后,每步主要只计算新增 token 的投影,并读取已有 K/V 做注意力。
会。prefill 处理整段 prompt 时会计算所有位置的 Q/K/V 来完成前向 attention。但进入 decode 后,只保留 K/V 作为 cache;Q 是为了当下这次 attention 计算服务,不需要作为长期缓存保存。
当前 token 的 K/V 会在未来步骤成为历史上下文,被后续 token 查询和读取;当前 Q 只表示当前 token 查询历史上下文的需求,未来 token 会有自己的 Q,因此旧 Q 没有复用价值。
理想情况下不会,它只是复用历史 K/V 的计算结果,等价于每步重新计算前缀。实际系统里如果位置编码、cache 索引、mask、batch 拼接或精度处理有 bug,才可能导致输出差异。
普通 MHA 每个 query head 都有各自 K/V head;MQA 让所有 query head 共享一组 K/V,GQA 让一组 query head 共享一组 K/V。缓存的 K/V head 数减少,显存和读取带宽就下降。