
在这篇文章里,我们会学习 KV Cache:K 代表 Key,V 代表 Value;也会理解它为什么会用在大语言模型(LLM)里,用来加速文本生成。
我们会先看 LLM 如何一次生成一个 token,理解 Key、Value 和 Query 在模型内部的作用;再通过一个例子看看重复计算的问题;最后一步步看 KV Cache 如何通过保存并复用过去的结果来解决这个问题。
我是 Amit Shekhar,Outcome School 创始人。我教过并辅导过很多开发者,他们靠自己的努力拿到了高薪技术岗位;我也帮助过许多科技公司解决各自独特的问题,并创建过很多被顶级公司使用的开源库。我热衷于通过开源、博客和视频分享知识。
我在 Outcome School 教 AI and Machine Learning。
我们开始吧。
LLM 如何生成文本
在理解 KV Cache 之前,我们先要理解 LLM 如何生成文本。
LLM,也就是大语言模型,是一种在海量文本数据上训练出来的模型。它可以理解并生成人类语言。当我们给它一句话时,它会预测接下来应该是什么。
LLM 生成文本是一次一个 token。token 是一小段文本,可以是一个词、词的一部分,甚至是单个字符。为了简单起见,我们先把每个词当作一个 token。
假设我们给模型这个输入:
"I love"
模型看到 “I” 和 “love”,预测下一个 token:“teaching”(教学)。
现在完整序列变成:
"I love teaching"
模型看到 “I”、“love” 和 “teaching”,预测下一个 token:“AI”。
现在完整序列变成:
"I love teaching AI"
这个过程会一次一个 token 地持续下去,直到模型决定停止。
这里最重要的一点是:模型每次预测一个新 token 时,都需要查看所有之前的 token,才能决定接下来是什么。
模型内部发生了什么
现在,我们来理解模型生成每个 token 时内部发生了什么。
模型里有一个组件叫 注意力层(attention layer),它是 Transformer 架构的核心。注意力层的任务,是帮助模型判断哪些之前的 token 对预测下一个 token 重要。不是所有之前的 token 都同样有用。有些更重要,有些没那么重要。
在注意力层内部,每个 token 都会被转换成三样东西。我们用一个简单类比来理解它们。
想象一个教室,新同学加入进来,想知道谁能帮他解决某个主题的问题:
- Query(Q):新同学的问题——“这里谁懂这个主题?”每个正在被预测的 token 都会有一个 Query。它表示这个 token 正在寻找什么。
- Key(K):每个现有学生戴着的名牌,上面写着他们知道什么——“我懂数学”或“我懂科学”。每个之前的 token 都会有一个 Key。它描述这个 token 持有什么信息。
- Value(V):每个学生真正拥有的笔记。新同学用名牌(Key)找到合适的人以后,真正会用到的是笔记(Value)。每个之前的 token 都会有一个 Value。它承载实际信息。
所以,当前 token 会用自己的 Query 去和所有之前 token 的 Key 比较。这个比较会产生 注意力分数(attention score),也就是一些数字,告诉模型应该给每个之前的 token 多少关注。分数越高,说明那个 token 越相关;分数越低,说明没那么相关。然后,模型会用这些分数从相关 token 中收集 Value。如果想深入理解 Q、K、V 是怎么计算出来的,我们有一篇详细文章:Math behind Attention: Q, K, V。
这就是注意力层的工作方式。
接下来看看问题在哪里。
如果想深入学习注意力层、Q/K/V 内部机制和 Transformer 架构,可以看看 Outcome School 的 AI and Machine Learning Program。
问题:重复计算
模型每次预测下一个 token 时,都会为序列中的所有 token 计算 Key、Value 和 Query,而不只是为新 token 计算。
我们用前面的例子一步步看会发生什么:
第 1 步: 输入是 “I love”
模型为下面这些 token 计算 Key、Value 和 Query:
- “I”
- “love”
它用这些结果预测下一个 token:“teaching”(教学)。
第 2 步: 输入是 “I love teaching”
模型为下面这些 token 计算 Key、Value 和 Query:
- “I”(第 1 步已经算过,但又算了一遍)
- “love”(第 1 步已经算过,但又算了一遍)
- “teaching”(新的)
它用这些结果预测下一个 token:“AI”。
第 3 步: 输入是 “I love teaching AI”
模型为下面这些 token 计算 Key、Value 和 Query:
- “I”(第 1 步和第 2 步已经算过,但又算了一遍)
- “love”(第 1 步和第 2 步已经算过,但又算了一遍)
- “teaching”(第 2 步已经算过,但又算了一遍)
- “AI”(新的)
你看到问题了吗?
“I” 的 Key 和 Value 在第 1 步已经算过。但模型在第 2 步又算了一遍,在第 3 步又算了一遍。“love” 和 “teaching” 也是一样。
模型在为已经见过的 token 重复做同样的工作。 这就是浪费的计算。
随着序列变长,这个问题会越来越严重。如果模型已经生成了 100 个 token,那么下一步为了预测 1 个新 token,它会重新计算前面所有 100 个 token 的 Key 和 Value。这会让文本生成变得很慢。
给你一个小提示
无论你在哪个技术领域工作,都应该熟悉这些主题:
- LLM
- RAG
- MCP
- Agent
- Fine-tuning
- Quantization
我们把它们整理进了一个视频:
AI Engineering Explained: LLM, RAG, MCP, Agent, Fine-Tuning, and Quantization
不用现在停下来去看——先收藏,等有时间再看。未来的你会感谢现在的你。
现在,我们回到主题。
解决方案:KV Cache
KV Cache 背后的思路很简单:每个 token 的 Key 和 Value 只计算一次,保存起来,并在之后每一步复用。
可以把它想成做笔记。假设你在开会。每次有新人发言时,你不需要让前面所有发言者把说过的话再重复一遍,只要读自己的笔记,然后只听新发言者说什么就行。笔记就是你的缓存。
同样,KV Cache 是一块内存,用来保存每个已经处理过的 token 的 Key 和 Value。这样模型就不必再次计算它们。
我们看看同一个例子在使用 KV Cache 时会怎么运行:
第 1 步: 输入是 “I love”
模型为下面这些 token 计算 Key、Value 和 Query:
- “I”
- “love”
它把 “I” 和 “love” 的 Key 和 Value 保存到 KV Cache 中。
它用这些结果预测下一个 token:“teaching”(教学)。
现在 KV Cache 包含: “I”、“love” 的 Key 和 Value
第 2 步: 输入只有 “teaching”(只有新的 token)
模型从 KV Cache 中取回 “I” 和 “love” 的 Key 和 Value。不需要重新计算。
它只为新的 token “teaching” 计算 Key、Value 和 Query。
它把 “teaching” 的 Key 和 Value 保存到 KV Cache 中。
它把所有这些一起用于预测下一个 token:“AI”。
现在 KV Cache 包含: “I”、“love”、“teaching” 的 Key 和 Value
第 3 步: 输入只有 “AI”(只有新的 token)
模型从 KV Cache 中取回 “I”、“love” 和 “teaching” 的 Key 和 Value。不需要重新计算。
它只为新的 token “AI” 计算 Key、Value 和 Query。
它把 “AI” 的 Key 和 Value 保存到 KV Cache 中。
现在 KV Cache 包含: “I”、“love”、“teaching”、“AI” 的 Key 和 Value
所以,模型不再每一步都为每个 token 重新计算 Key 和 Value,而是只为新的 token 计算,并复用所有之前 token 的缓存值。
这就是 KV Cache 避免重复计算的方式。
同样的思路还可以再进一步:在很多共享相同开头文本的请求之间复用已经缓存的 Key 和 Value。我们有一篇详细介绍 Prompt Caching 的文章,解释了这是怎么工作的。
为什么只缓存 Key 和 Value,而不缓存 Query
你自然会问:为什么我们只缓存 Key 和 Value,而不缓存 Query?
Query 只对当前 token 有用,也就是正在生成的那个 token。当前 token 会用自己的 Query 去和所有之前 token 的 Key 比较,找出哪些 token 相关。一旦预测完成,这个 Query 就不再需要了。
但每个过去 token 的 Key 和 Value 在之后每一步都会被用到,因为每个新 token 都必须查看所有之前的 token 才能完成预测。
所以,我们只需要保存 Key 和 Value。这就是为什么它叫 KV Cache:它缓存的是 Key 和 Value。
速度能快多少
我们把两种做法放在一起对比:
没有 KV Cache:
Step 1: Compute K, V, Q for 2 tokens
Step 2: Compute K, V, Q for 3 tokens (2 recomputed)
Step 3: Compute K, V, Q for 4 tokens (3 recomputed)
Step 4: Compute K, V, Q for 5 tokens (4 recomputed)
...
Step N: Compute K, V, Q for (N+1) tokens (N recomputed)
计算量会在每一步持续增长。
使用 KV Cache:
Step 1: Compute K, V, Q for 2 tokens → Save K, V for 2 tokens in cache
Step 2: Compute K, V, Q for 1 token → Reuse K, V for 2 tokens from cache
Step 3: Compute K, V, Q for 1 token → Reuse K, V for 3 tokens from cache
Step 4: Compute K, V, Q for 1 token → Reuse K, V for 4 tokens from cache
...
Step N: Compute K, V, Q for 1 token → Reuse K, V for N tokens from cache
第一步之后,模型每一步只为一个新 token 计算,而不是为整个序列重新计算。
换个角度看:如果模型要生成一个 100 个 token 的序列,没有 KV Cache 时,所有步骤里的 Key 和 Value 计算总数会是 2 + 3 + 4 + ... + 100 = 5,049 次计算。使用 KV Cache 时,会变成 2 + 1 + 1 + ... + 1 = 101 次计算。大约少了 50 倍计算量。序列越长,节省越大。
这就是 KV Cache 能有效加速文本生成的原因。
取舍:速度与内存
KV Cache 会让生成更快,但也有一个取舍:它需要额外内存来保存到目前为止已经生成的每个 token 的 Key 和 Value 信息。
序列越长,缓存就越大。对于包含数千个 token 的超长序列,缓存可能会占用相当多内存。
所以,KV Cache 是一种取舍:我们用更多内存换取计算时间。对大多数用例来说,这个取舍非常值得,因为速度提升很明显。
当我们同时为很多用户提供 LLM 服务时,高效管理 KV Cache 内存会变得非常关键。我们有一篇详细介绍 vLLM 的文章,解释了服务系统如何端到端处理这件事。
现在,我们已经理解了 LLM 中的 KV Cache。
在下一篇文章里,我们会学习 Paged Attention,它解决的是 KV Cache 的内存问题。
准备 AI 工程面试可以看这里:AI Engineering Interview Questions
这次就到这里。
谢谢
Amit Shekhar Outcome School 创始人