理解 KV Cache:LLM 推理为什么能越写越快

理解 KV Cache:LLM 推理为什么能越写越快 只要你和现在的 AI 工具交流无论是 Codex、Claude还是 ChatGPT、DeepSeek、豆包你应该都注意过一件事它并不是一次性把完整答案吐出来而是一个字接着一个字慢慢形成一行字再逐渐生成一整段话直到所有结果都返回完毕。而你看到的第一个 token往往要等一会儿才出现比后面出现的字慢得多。可一旦第一个 token 出现AI 开始输出之后后面的内容就会几乎连续地流式生成。为什么 LLM 的生成结果一开始比较慢后面却能连续输出这就是本文要科普的小知识。在这个现象背后有一个有意思的工程机制KVCacheKV 缓存。它的目的很直接让 LLM 推理更快。KV Cache 的效果在进入技术细节之前先来看一个直观对比可以看出有 KV Cache 和没有 KV Cache 的 LLM 推理速度差异非常明显。接下来我们从最基础的原理开始理解它。LLM 是如何生成 token 的现在大多数 LLM 底层使用的都是 Transformer 架构。你可以先简单理解成Transformer 是一种专门处理序列数据的模型结构它擅长根据上下文关系判断每个 token 应该如何理解。当一段文本被输入模型后Transformer 会处理所有输入 token并为每个 token 生成一个hidden state。你可以把 hidden state 理解成模型在结合上下文之后对这个 token 形成的一组内部表示。随后这些 hidden state 会被投影到词表空间得到logits也就是词表中每个词对应的分数。但真正重要的是只有最后一个 token 的 logits 会被用来预测下一个 token。模型会从这些 logits 中采样得到下一个 token然后把这个 token 追加到输入序列后面再重复同样的过程。这里有一个关键点为了生成下一个 token我们只需要最近那个 token 的 hidden state。其他 token 的 hidden state更多是中间计算结果。Attention 实际上在计算什么Attention 你可以理解为是一种「信息选择机制」它会判断当前 token 应该关注上下文里的哪些 token并从这些位置取回信息。在 Transformer 的每一层里每个 token 都会生成三个向量Query简称 Q表示「我现在想找什么信息」Key简称 K表示「我这里有什么信息可被匹配」Value简称 V表示「如果你关注我真正拿走的内容是什么」。Attention 会用 Query 和 Key 做乘法得到注意力分数再用这些分数对 Value 做加权求和。现在我们只关注最后一个 token。在计算QK^T时T 表示转置。简单来说就是让每个 Query 都能和所有 Key 做一次乘法算出注意力分数。对最后一个 token 来说QK^T 的最后一行会用到最后一个 token 的 Query 向量序列中所有 token 的 Key 向量而这一行最终的 Attention 输出会用到同一个 Query 向量所有 token 的 Key 和 Value 向量所以为了计算我们真正需要的那个 hidden state每一层 Attention 都需要最新 token 的 Q以及整个序列中所有 token 的 K 和 V。存在的冗余假设生成第 50 个 token 时需要 token 1 到 token 50 的 K 和 V。生成第 51 个 token 时又需要 token 1 到 token 51 的 K 和 V。但问题来了token 1 到 token 49 的 K 和 V其实之前已经计算过了。它们不会改变。同样的输入同样的模型参数输出也会相同。如果每一步都重新从头计算这些 K 和 V模型就在重复做大量已经做过的工作。这就是每一步里的O(n)冗余计算。放到整个生成过程里就会变成O(n²)级别的浪费。KV Cache 如何解决冗余KV Cache 的做法很简单不要在每一步重新计算所有 K 和 V而是把它们存起来。对于每一个新 token模型只需要只为最新 token 计算 Q、K 和 V把新的 K 和 V 追加到缓存中从缓存中读取所有历史 K 和 V用新的 Q 去和完整的 K/V 缓存做 Attention这就是 KV Cache。每一层、每一步只新增一个 K 和一个 V。其他历史 token 的 K 和 V都直接从缓存里读取。需要注意的是Attention 计算本身仍然会随着序列长度增长。因为最新 token 的 Query 仍然要和所有历史 Key 计算注意力分数并根据这些分数从对应的 Value 中取回信息。但昂贵的 K/V 投影计算只需要对每个 token 做一次而不是每一步都重复做一次。Time-to-First-Token现在就能理解为什么第一个 token 通常会比较慢。当你发送一个 prompt 时模型需要先处理完整输入。它会对整个 prompt 做一次前向计算并为每个 token 计算和缓存 K/V。这个阶段叫做prefill。prefill 通常是整个请求中计算最重的阶段。因为模型要一次性处理完整的输入上下文。一旦 cache 建好后续每生成一个 token就只需要对最新 token 做一次前向计算。这也解释了为什么第一个 token 前的等待时间更长而后续 token 会连续流式输出。这个初始延迟通常被称为 Time-to-First-Token也就是 TTFT。prompt 越长prefill 越重TTFT 也就越长。优化 TTFT 本身也是一个很大的话题比如 chunked prefill、speculative decoding、prompt caching 等。但核心动态始终是一样的构建 cache 很贵读取 cache 很便宜。KV Cache 的代价KV Cache 本质上是在用内存换计算。每一层都要为每个 token 存储 K 和 V 向量。以 Qwen 2.5 72B 为例如果是 80 层、32K context、hidden dim 8192那么单个请求的 KV Cache 就可能消耗数 GB 的 GPU 显存。当并发请求达到几百个时KV Cache 的显存占用甚至可能超过模型权重本身。这也是为什么 GQA 和 MQA 会出现。在标准 Multi-Head Attention 里每个 Query head 通常都有对应的 Key / Value head。head 越多需要缓存的 K 和 V 也越多。Multi-Query Attention简称MQA做法更激进多个 Query head 共享同一组 Key / Value因此 KV Cache 会明显变小。Grouped-Query Attention简称GQA则是折中方案把多个 Query head 分成若干组每组共享一组 Key / Value。它牺牲少量结构自由度换来更低的 KV Cache 显存占用同时尽量保持模型质量。它们的核心思路是让多个 Query head 共享更少的 Key / Value head从而减少 KV Cache 的显存占用同时尽量保持模型质量不受明显影响。这也是为什么加长上下文窗口并不简单。如果 context length 翻倍那么单个请求需要的 KV Cache 也会翻倍。这意味着同样的 GPU 显存下可以同时服务的用户数量会减少。还有一个相关思路叫 PagedAttention它主要解决的是大规模推理服务中 KV Cache 的内存管理问题。这里就不展开讲了后面我们再来科普。小结KV Cache 消除了自回归生成过程中的大量重复计算。历史 token 产生的 K 和 V 向量不会变化所以只需要计算一次然后存起来。之后每生成一个新 token只需要计算这个新 token 自己的 Q、K 和 V再让新的 Q 基于完整的 K/V 缓存完成 Attention 计算。这就是为什么 LLM 的第一个 token 往往更慢因为模型要先完成 prefill建立完整的 KV Cache。而一旦 cache 建好后续 token 就可以逐步、连续地生成。KV Cache 在实践中能显著提升推理速度。代价是 GPU 显存占用增加而在大规模 LLM 服务中显存往往会成为比计算更关键的瓶颈。今天几乎所有主流 LLM serving stack比如 vLLM、TGI、TensorRT-LLM都会围绕 KV Cache 做进一步优化。参考资料Avi ChawlaKV Caching in LLMs, Explained Visually. https://blog.dailydoseofds.com/p/kv-caching-in-llms-explained-visually