第19章:KV Cache
大语言模型在生成文本时,每产生一个新 token,都需要"回顾"之前所有的 token。如果每一步都从头重新计算,代价会随序列长度平方增长。KV Cache 是解决这个问题最核心的工程优化——理解它,才能真正理解推理系统的设计权衡。
19.1 原理:为什么可以缓存
朴素实现的浪费
回顾 Transformer 的自注意力(Self-Attention)机制。对于第 步生成,模型需要计算:
其中 是当前新 token 产生的 Query, 和 是所有历史 token(包含当前)产生的 Key 和 Value。
朴素实现的做法是:每生成一个新 token,就把所有历史 token 重新输入模型,重新计算整个序列的 K、V 矩阵。
这非常低效。生成第 个 token 时,需要计算 个位置的 K 和 V;生成第 个 token 时,又要重新计算前 个位置的 K 和 V——其中前 个位置的计算完全是重复劳动。
生成 个 token 的总计算量:。
关键洞察:K、V 与新 token 无关
Transformer 的因果掩码(Causal Mask)保证:位置 的 K 和 V,只依赖位置 本身及其之前的输入,与后续生成的任何新 token 完全无关。
更具体地说,对于解码器,第 个位置的 Key 和 Value 是:
其中 只取决于输入序列的第 个 token(及其位置编码)。一旦 确定, 和 就永远不会变化。
KV Cache 的做法
既然历史位置的 K、V 不会变,那就把每一层、每个位置的 K 和 V 存进显存,下次直接读取,不再重算。
生成第 个 token 时:
- 只计算新 token 的 、、;
- 将 、 追加到 KV Cache;
- 用 与 Cache 中所有 做 Attention。
每一步的计算量变为 (Attention 的 QK 乘积),但总计算量不再是 的求和,而是每步的增量计算。
:::info 加速效果 对比:
| 实现方式 | 生成第 步的计算 | 生成 步总计算 |
|---|---|---|
| 朴素重算 | (全序列 Attention) | |
| KV Cache | (仅新 token 的 Attention) |
KV Cache 把每步的瓶颈从"全序列计算"变成了"单步 Attention + 内存读取",实际推理速度可提升 数十倍。 :::
19.2 显存占用计算
KV Cache 是免费的吗?当然不是——它用显存换计算。
单 token 的 KV Cache 大小
对于一个 Transformer 模型,KV Cache 的大小由以下参数决定:
其中:
- :K 和 V 各一份;
- :Transformer 的层数(num_layers);
- :KV 头数(num_kv_heads,GQA 下小于 Q 的头数);
- :每个头的维度(head_dim);
- :每个元素的字节数(BF16 为 2 字节,INT8 为 1 字节)。
LLaMA-3 70B 实例计算
LLaMA-3 70B 的关键参数:
- 层;
- GQA(Grouped Query Attention)下 个 KV 头;
- ;
- BF16 精度, 字节。
代入公式:
规模化的显存压力
现在把这个数字放到实际场景中:
| 场景 | 计算 | 显存占用 |
|---|---|---|
| 单个 4096 token 对话 | ||
| 100 个并发请求 | ||
| LLaMA-3 70B 权重本身(BF16) | — |
:::warning 关键结论 100 个并发请求的 KV Cache(130 GB)已经接近模型权重本身(140 GB)的大小。这意味着:KV Cache 的显存管理,往往比模型本身更难。这也是为什么推理系统(如 vLLM)需要专门设计显存分配器。 :::
19.3 多轮对话中的 Cache 管理
多轮对话的天然复用
在多轮对话中,第一轮的历史文本在第二轮、第三轮都会出现在 context 的开头。如果把前几轮的 KV Cache 保留下来,后续轮次只需要计算新增内容的 K、V,大量计算可以省去。
第一轮:[系统提示词][用户消息1][助手回复1]
第二轮:[系统提示词][用户消息1][助手回复1][用户消息2][助手回复2]
^^^^^^^^^^^^^^^^^^^^^^^^
这部分的 KV Cache 可以直接复用
Prefix Caching(前缀缓存)
更进一步:许多请求共享相同的系统提示词(System Prompt)。例如,一个部署在生产环境的 AI 助手,每个用户请求都以同一段几千字的系统提示词开头。
Prefix Caching 的思路是:把相同前缀的 KV Cache 在多个请求之间共享。
请求 A:[系统提示词(2000 tokens)][用户 A 的消息]
请求 B:[系统提示词(2000 tokens)][用户 B 的消息]
^^^^^^^^^^^^^^^^^^^^^^^^
这 2000 个 token 的 KV Cache 只需计算一次
实现上,通常用前缀的哈希值作为 Cache key,命中时直接加载对应的 K、V 张量。vLLM、SGLang 等框架都支持这一特性,对于系统提示词较长的场景,首次请求之后的延迟可以大幅降低。
:::tip 实际效果 若系统提示词占 context 的 50%,Prefix Caching 理论上可将 TTFT(Time To First Token,首 token 延迟)减少约 50%。 :::
缓存淘汰策略
显存有限,KV Cache 不可能无限保留。常见的淘汰策略:
LRU(Least Recently Used,最近最少使用):淘汰最久没被访问的 Cache 块。适合请求分布较均匀的场景。
滑动窗口(Sliding Window):只保留最近 个 token 的 KV Cache,超出窗口的直接丢弃。这是 Mistral 等模型采用的策略,在牺牲超长上下文能力的前提下,把显存占用控制在固定上限。
基于 Radix Tree 的前缀共享(SGLang 的 RadixAttention):将所有历史请求的 KV Cache 组织成一棵前缀树,自动发现和共享公共前缀,同时按 LRU 淘汰叶节点。
19.4 KV Cache 量化
为什么 KV Cache 需要单独量化
模型权重量化(如 GPTQ、AWQ)是在模型加载时对静态参数做压缩,误差是固定的。而 KV Cache 是推理过程中动态生成的,每个新 token 都会往 Cache 里追加新的 K、V 值。
这个差异带来两个问题:
- 误差累积:量化 KV Cache 引入的误差,会在 Attention 计算中逐步传播,越长的序列误差越大;
- 动态范围不稳定:K、V 的数值分布因输入内容而异,离线校准统计出的量化参数不一定适合每一步的 Cache。
FP16 → INT8
将 KV Cache 从 BF16/FP16(2 字节)降为 INT8(1 字节),显存直接减半。
常见做法是逐 token 或逐 head 的动态量化:
反量化时乘回 scale 因子。这种方式对大多数任务的精度损失在可接受范围内(困惑度上升 < 1%),是生产环境中最常用的 KV Cache 压缩方案。
INT4 量化
INT4 将显存压缩至原来的 1/4(相对 FP16),但精度风险显著上升,需要额外处理:
- 分组量化(Group Quantization):每 (如 64)个元素共享一个 scale,减少 outlier 的影响;
- Key 和 Value 分开量化:实验发现 K 和 V 的数值分布差异较大,统一量化效果差;
- 保留注意力 sink(Attention Sink):序列开头几个 token 的 KV Cache 对精度影响不成比例地大,可以保留 FP16 精度。
量化效果对比
| 精度 | 显存(相对 FP16) | 典型精度损失 | 适用场景 |
|---|---|---|---|
| BF16 / FP16 | 1× | 基准 | 高精度要求 |
| INT8 | 0.5× | 极小(<1% PPL 上升) | 生产环境首选 |
| INT4 | 0.25× | 中等,需仔细调优 | 超长上下文、极端显存受限 |
:::warning KV 量化与权重量化的根本区别 权重量化:一次性压缩,推理时反量化为全精度再计算,误差不累积。
KV Cache 量化:量化后的 K、V 直接参与 Attention 计算,误差随序列长度累积。这意味着 KV 量化对长文本(> 8K tokens)的影响远大于短文本。 :::
本章小结
| 概念 | 核心要点 |
|---|---|
| KV Cache 原理 | K、V 只依赖历史 token,一次计算永久复用 |
| 计算复杂度 | 生成总计算量从 降为 |
| 显存公式 | 字节/token |
| LLaMA-3 70B | 约 320 KB/token,100 并发 4K 上下文需 130 GB |
| Prefix Caching | 共享相同前缀的 Cache,大幅降低首 token 延迟 |
| 淘汰策略 | LRU、滑动窗口、Radix Tree 前缀共享 |
| KV 量化 | INT8 是生产首选;INT4 需防范长序列误差累积 |
KV Cache 解决了"逐步生成时的重复计算"问题,但随之而来的显存压力催生了一个新挑战:当显存不足以容纳所有请求的 KV Cache 时,如何高效地分配和调度?下一章将介绍 vLLM 提出的 PagedAttention,它借鉴操作系统的虚拟内存思想,把 KV Cache 的碎片化管理问题彻底解决。