系列:推理系统基础设施手记

推理系统基础设施手记(九):KV Cache 全景——它为什么同时决定你的吞吐、延迟和能开多少并发

0. 一句话主线

KV Cache 是用空间换时间的产物:它把自回归生成里的重复计算一次性做完并缓存下来,代价是显存随 batch × 序列长度线性膨胀,最终反过来卡住并发。本篇讲清三件事——它为什么能缓存、它到底有多大、以及业界到底从哪几个方向在削它。

1. 先看清敌人:没有 KV Cache 时,我们在重复算什么

LLM 推理是自回归的:生成第 n 个 token 时,需要用到前 n−1 个 token 的信息。朴素实现下,每生成一个 token 就要把全部历史 token 重新走一遍前向:

模块冗余在哪
Embedding历史 token 的 embedding 反复生成
K / V 生成历史 token 的 K、V 反复计算
QKᵀ注意力分数矩阵反复计算,复杂度随序列平方增长
Softmax × V加权求和反复计算

结果是生成 N 个 token 的总复杂度约为 O(N²),序列越长越慢,而且慢得非常难看。

图 1:prefill 与 decode 阶段的 KV Cache 变化 prefill:整段 prompt 一次算完,生成第一个 token 新 年 快 KV Cache · 3 slots 乐 decode:每步只算 1 个 token,但注意力要读全部历史 KV 乐 KV Cache · 4 slots(+1) 万 绿色 = 本步新增。decode 每步计算量恒定(1 个 token),但要读的 KV 线性增长——这就是"访存密集"的来源。
图 1:prefill 一次性为整段 prompt 建立 KV Cache;decode 每步只算 1 个 token,缓存 +1,但注意力要读全部历史。

2. 为什么可以缓存:逐模块拆一遍

缓存能成立,靠的是两个性质:因果掩码和位置无关性。

Attention:因果掩码让”过去”不再变化

FFN / LayerNorm / Linear:位置之间不交互

一句话的数学支撑

矩阵乘法可以分块:A 拆成 [:s] 和 [s:] 分别与 B 相乘,结果直接拼接,与整体相乘一致。注意力和 FFN 都是矩阵乘,所以缓存 [:s] 部分的结果、只算新增的 [s:],结果完全等价——这是一次无损优化。

3. prefill 与 decode:两个阶段的性格完全不同

阶段做什么计算特征关键指标
prefill一次性处理整个 prompt,建立 KV Cache,产出第 1 个 token算力密集(大矩阵乘,并行度高)TTFT
decode逐个生成后续 token,每步读全部历史 KV访存密集(计算量小,但要搬 KV)TPOT

这个”一个要算力、一个要带宽”的分裂,正是后面 PD 分离(sys8)存在的全部理由。

4. KV Cache 到底多大

公式(标准 MHA/GQA):

KV Cache = 2 (K & V) × layers × batch × seq_len × kv_dim × dtype_bytes

其中 kv_dim = num_kv_heads × head_size。

实测案例(Qwen3-32B):

结论:KV Cache 随 batch 和 seq_len 线性增长,在高并发 + 长上下文场景下,它才是限制服务并发能力的那个瓶颈,而不是模型权重。

5. 它如何吃掉性能:三个指标与三段显存

指标含义被什么影响
TTFT首 token 延迟主要是 prefill 耗时;命中前缀缓存可大幅下降
TPOT单 token 生成时间decode 每步耗时;随 KV 变长而上升(访存量增大)
吞吐单位时间输出 token 数(服务成本指标)batch 越大吞吐越高,但被显存上限卡住

显存视角:

峰值显存 = 模型权重(固定) + KV Cache(随 batch × seq 增长) + 激活值
总延迟   = TTFT + TPOT × 生成 token 数

所以优化 KV Cache 有双重收益:同样的卡能开更高并发(省显存)、同样的请求更快(省带宽)。

6. 优化全景:三层分类

论文 A Survey on Large Language Model Acceleration based on KV Cache Management 把优化分为三层:

6.1 token 级优化(不动模型结构、不动并行)

在 token 粒度上做选择、组织、压缩:

方向做法代表
选择只存最重要的 tokenH2O(Heavy-Hitter)、Keyformer、SnapKV、Quest
预算分配层间/头间动态分配缓存额度PyramidKV、PyramidInfer、AdaKV、DuoAttention
合并合并相似/重叠的 KV 对层内/层间合并、Prompt Cache
量化降低存储精度(FP16→INT8/INT4/FP8)KV Cache 量化,见 op2
低秩分解把 KV 矩阵压成低维表示LoRA 式压缩、MLA 的思路之一

6.2 模型级优化(改架构)

6.3 系统级优化(改调度与内存管理)

7. 五个旋钮:把”减小 KV Cache”拆成可量化的动作

回到公式,KV Cache 大小由五个因子相乘决定,每个因子都是一个独立的优化入口:

旋钮怎么拧代价
序列长度稀疏化(静态窗口 / 动态淘汰)、前缀复用可能丢关键信息,长距离依赖任务掉点
注意力头数MQA / GQA(减少 KV head 数)表达能力略降,但已是主流默认
key_bits量化到 INT8 / INT4 / FP8精度损失,需校准(见 op2)
头维度MLA 把 KV 压成低秩 latent;Double Sparsity 做 channel 稀疏结构改动大
层数只缓存部分层(YOCO / CLA / MLKV / LayerSkip)需训练配合或接受近似

理解方式:这五个旋钮不是互斥的,工程上通常叠加使用——比如”GQA(头数)+ FP8 KV(bits)+ 前缀复用(序列长度)+ PagedAttention(系统级)“是当下最常见的组合。

8. 与已有文章的衔接

觉得有用?欢迎点赞、收藏,或请作者喝咖啡 ☕️

支付宝收款码

支付宝

微信收款码

微信

💬 留言

评论由 Giscus 驱动(基于 GitHub Discussions)。 当前仓库 NaphJohn/LLM-blog 尚未启用 Discussions:请在 GitHub 仓库 Settings → General → Features 勾选 Discussions 后刷新本页,评论区即自动显示。