什么是 KV Cache?它为什么能加速大模型推理?
简化版
自回归生成时,历史 token 的 Key 和 Value 在每一步都不会改变。KV Cache 把各层历史 K、V 保存下来,新一步只计算新 token 的 Q、K、V,再让新 Q 查询缓存,从而避免重复计算全部前缀。
详细版
没有缓存时,生成第 t 个 token 会再次对前 t−1 个 token 计算各层投影,产生大量重复工作。有 KV Cache 后:
- Prefill 阶段一次计算输入提示的 K、V 并缓存;
- Decode 阶段只计算最新 token 的 K、V;
- 将新 K、V 追加到缓存;
- 新 Q 与全部缓存 K 计算注意力,再加权缓存 V。
它显著降低生成阶段计算量,但不会消除对历史 K、V 的读取,注意力长度仍随输出增长。缓存还会占用大量显存,大小大致与批量、层数、序列长度、KV 头数、头维度和数据精度成正比。
完整版教学
一、重复计算从哪里来
因果模型生成 token 时,历史前缀保持不变。对同一层而言,历史 token 的 K、V 已经确定;下一步只有最新 token 是新增信息。若每步把整个序列重新前向一次,就会反复计算相同结果。
Q 只服务于当前位置发起查询,历史 Q 不需要缓存;历史 K 用于与新 Q 匹配,历史 V 用于汇总内容,因此缓存叫 KV Cache。
二、Prefill 和 Decode
Prefill 一次处理完整提示,Q、K、V 可组织成大矩阵乘法,GPU 并行度较高;提示越长,首 token 延迟通常越明显。Decode 每步只处理一个新 token,却要读取所有层的历史缓存,算术强度较低,更容易受显存带宽和调度影响。两阶段瓶颈不同,服务端应分别观察 TTFT、TPOT 和并发下的尾延迟。
三、缓存为什么很占显存
每层都要为每个历史位置保存 K 和 V。批量越大、上下文越长、层数越多,缓存越大。多用户并发服务时,模型权重是共享的,但每条请求都有独立缓存,因此 KV Cache 经常成为并发量的真正限制。
四、MQA 与 GQA
标准多头注意力为每个查询头保留独立 K、V 头。Multi-Query Attention 让多个查询头共享一组 K、V;Grouped-Query Attention 让若干查询头共享一组。它们显著减少缓存和内存带宽,同时尽量保留多头表达能力。
五、Paged Attention 与前缀复用
连续显存分配容易产生碎片,且不同请求长度变化很大。Paged Attention 把缓存按块管理,类似虚拟内存分页,提高显存利用率。若多个请求共享相同系统提示,还可以复用前缀缓存,避免重复 Prefill。
其他优化还包括 KV Cache 量化、滑动窗口注意力和淘汰低价值 token,但都需要在速度、显存和质量之间权衡。
六、它没有解决什么
KV Cache 避免的是历史 K、V 投影和前层计算的重复,不会让每步注意力变成常数开销。新 Q 仍需读取并关注历史缓存,生成也仍是串行过程,所以输出越长,总延迟依然会上升。
训练时整段目标可以并行计算,通常不依赖推理式 KV Cache;缓存主要服务于自回归解码。
七、算一笔单请求缓存账
KV Cache 的大小可近似写成 2 × 层数 × KV头数 × 头维度 × token数 × 每元素字节数,其中 2 代表 K 和 V。对 32 层、32 个 KV 头、头维度 128、BF16、上下文 4096 的 MHA 模型,单请求缓存约为 2 GiB;如果改成 8 个 KV 头的 GQA,则降到约 512 MiB。
MHA: 2 * 32 * 32 * 128 * 4096 * 2 bytes = 2 GiB
GQA: 2 * 32 * 8 * 128 * 4096 * 2 bytes = 512 MiB
| 变量 | 增长关系 | 优化手段 |
|---|---|---|
| 上下文长度 | 线性增加缓存 | 滑窗、截断、分页管理 |
| KV 头数 | 线性增加缓存 | GQA 或 MQA |
| 存储精度 | 随字节数线性变化 | FP8/INT8 KV 量化 |
这解释了为什么在线服务的并发上限经常先受 KV Cache 而非权重限制。缓存只消除了历史 K/V 的重复投影,不会消除新 query 与全部历史 key 的注意力计算;上下文继续增长时,单步 decode 仍会越来越慢。
八、常见误区与追问
- 误区:KV Cache 会缓存历史 Q、K、V。 历史 Q 用完即可丢弃;后续新 query 只需读取历史 K 做匹配、读取历史 V 做聚合。
- 追问:KV Cache 为什么主要优化 Decode? Decode 若无缓存会反复计算整个前缀;缓存后只计算新 token 的投影,但 Prefill 本来就是一次并行处理输入。
- 误区:用了 KV Cache 后每个生成步就是 O(1)。 新 query 仍要扫描历史 K/V,单步注意力读写量随上下文长度增长。
- 追问:GQA 怎样减少缓存? 让多个 query 头共享同一组 K/V 头,缓存容量与 KV 头数近似线性下降。
- 追问:PagedAttention 改变注意力数学吗? 不改变;它改变 KV 块的物理存储和映射方式,以减少碎片并支持动态请求。
九、加强记忆
KV Cache 用显存换重复计算:历史 token 的 K/V 一旦生成便保持不变,Decode 只为新 token 做投影并读取缓存。容量由层数、KV 头数、头维度、token 数和精度共同决定,因此 GQA、分页缓存和 KV 量化是在削减不同乘数。它不会消除新 query 扫描历史 key 的成本;回答时要把 TTFT、TPOT 与并发显存分开说明。