跳转到主要内容

KV Cache:推理性能的命根子

理解 KV Cache 的复用语义、容量公式、分页与共享机制,以及从分配到驱逐的生命周期

· 约 7 分钟阅读

自回归 decode 每步都会再次读取历史上下文。KV Cache 把各层已经计算出的 Key/Value 保存下来,使模型只需为新 token 生成新的 K/V;代价是常驻显存和每步历史读取量随序列增长。

30 秒复习
  • 一句话:KV Cache 用容量和带宽换掉历史 K/V 的重复投影计算,是自回归推理的基础状态。
  • 三个判断:先按模型 cache schema 算 bytes/token;PagedAttention 管碎片和映射而不改变语义 payload;prefix cache 命中、页共享与 offload/驱逐属于生命周期问题。
  • 核心模型:MHA/GQA 的逻辑容量为 2 × layers × kv_heads × head_dim × tokens × bytes/element,再乘请求数并按并行分片与 allocator 开销修正。
  • 边界:公式不是所有架构的统一答案;MLA、稀疏/滑窗、量化、TP 分片、Scale、页尾浪费和运行时 workspace 都会改变物理占用。

1. KV Cache 到底复用了什么

Attention 的完整坐标与 MHA/GQA/MLA 差异见 Attention 架构演化。这里只保留 KV 语义:

step t
  current hidden state -> Q_t, K_t, V_t
  Q_t reads cached K_1...K_t and V_1...V_t
  K_t, V_t append to cache
  attention output -> next model sublayer

没有 cache 时,每个 decode step 都要重新从历史 hidden states 投影出 K/V;有 cache 后只投影新增 token,但当前 Query 仍需读取可见历史。它减少的是重复生成历史 K/V,不是取消历史 attention。

每层投影参数不同,因此每层都有自己的 cache。下图把 QKV 投影、decode 追加与跨请求 prefix 复用放在一起:

QKV 与 Prefix KV Cache 复习图 打开全屏图

还要区分两个容易混淆的复用:

  • 请求内 KV Cache:同一序列 decode 时复用自己的历史状态。
  • 跨请求 Prefix Cache:另一个请求在匹配前缀上复用此前保存的状态。

二者使用相同类型的 payload,但命中判定、所有权和淘汰策略不同。

2. 容量账:先算逻辑 payload

对 MHA/GQA/MQA,单个 token 的全模型逻辑 payload 为:

Skv/token=2×L×nkv-heads×dhead×belemS_{\text{kv/token}} =2 \times L \times n_{\text{kv-heads}}\times d_{\text{head}}\times b_{\text{elem}}

BB 条请求都缓存 SS 个 token:

Mlogical=B×S×Skv/tokenM_{\text{logical}} =B\times S\times S_{\text{kv/token}}
变量含义
22K、V 两份
LLTransformer 层数
nkv-headsn_{\text{kv-heads}}KV heads;不要误用 Query heads
dheadd_{\text{head}}每个 KV head 的维度
belemb_{\text{elem}}cache payload 每元素字节数
SSBB每请求已缓存 token 与并发请求数

例如 L=32L=32nkv-heads=8n_{\text{kv-heads}}=8dhead=128d_{\text{head}}=128、BF16 时:

Skv/token=2×32×8×128×2=131,072 bytes=128 KiBS_{\text{kv/token}} =2\times32\times8\times128\times2 =131{,}072\ \text{bytes}=128\ \text{KiB}

一条 8K-token 请求的逻辑 payload 约为 1 GiB;32 条同长度请求约为 32 GiB。这里尚未计页尾浪费、Scale、对齐、元数据和 workspace。

全局量、per-rank 量与物理量

模型级公式得到的是全局逻辑 payload。若 KV heads 沿 TP ranks 分片,理想均分下:

Skv/token/rankSkv/tokenntpS_{\text{kv/token/rank}} \approx \frac{S_{\text{kv/token}}}{n_{\text{tp}}}

但实际 layout 取决于 num_kv_heads、TP 映射、复制策略和 kernel。不能在未确认分片合同前机械除以 TP。

运行时真正占用通常是:

physical allocation
  = live payload
  + block rounding / fragmentation
  + quantization scales and alignment
  + allocator metadata
  + implementation-specific workspace

因此模型可容纳的理论 tokens、allocator 可分配 tokens 和线上稳定容量是三个不同口径。

3. Attention 架构如何改变 cache schema

架构每 token 每层主要缓存变化点
MHA2 × q_heads × head_dim每个 Q head 有独立 K/V
GQA2 × kv_heads × head_dim多个 Q heads 共享一组 K/V
MQA2 × head_dim所有 Q heads 共享一组 K/V
MLAlatent KV + RoPE branch缓存低维表示,不按 KV head 直接计数
Sliding/Sparse由可保留/可访问区域决定token 数不再等于完整历史
Recurrent固定或分层状态不保存同形态的逐 token KV

这张表只用于选公式。GQA 的训练与表达折中属于 Attention 主责页;MLA 的低秩、RoPE 解耦和矩阵吸收见 DeepSeek MLA

4. PagedAttention 管的是什么

连续预留 max_seq_len 会产生页内浪费,也要求请求增长时寻找更大的连续空间。PagedAttention 借用虚拟内存思路,把逻辑 token blocks 映射到不连续的物理 cache blocks:

request block table
  logical block 0 -> physical block 7
  logical block 1 -> physical block 2
  logical block 2 -> physical block 15

运行时按需分配新 block,attention kernel 通过 block table 找到历史 K/V。它带来三个系统能力:

  1. 请求无需预留一整段连续 max_seq_len 空间。
  2. 活跃序列可独立增长、结束和回收物理 blocks。
  3. 多个逻辑序列可以引用同一物理 prefix blocks。

PagedAttention 不会自动减少同一批 live tokens 的语义 payload。它主要减少预留与碎片损失,并让共享、抢占和迁移更易实现;具体 block size 与 allocator 策略仍会影响页尾浪费和 kernel locality。

5. Prefix Cache 与 Copy-on-Write

跨请求 prefix 命中必须满足运行时定义的等价条件,通常至少包括 token 序列、模型/权重或 adapter、位置语义,以及会影响 hidden state 的其他输入。字符串相同不一定代表 token 与执行上下文相同。

命中后,多个请求可以让 block table 指向同一组只读物理 blocks:

request A: [shared 0, shared 1, A-private 2]
request B: [shared 0, shared 1, B-private 2]

当请求要修改共享尾块时,Copy-on-Write 分配私有副本;完整只读 blocks 继续共享。由此必须分别观测:

  • prefix hit 的 token 比例;
  • 实际复用的物理 bytes;
  • CoW 复制与未满尾块的浪费;
  • 命中查找和加载引入的延迟。

“命中率高”并不自动等于“TTFT 必然按同比例下降”,端到端修正见 KV Cache Hit Ratio 修正模型

6. 一条 cache 的生命周期

从请求进入到资源回收,可以按以下状态审计:

  1. Lookup:查询可复用 prefix;miss 不是零字节,而是需要新建的状态。
  2. Allocate:为 miss tokens 预留逻辑 blocks 与物理 blocks。
  3. Populate:prefill 计算并写入各层 K/V。
  4. Append:decode 每步追加当前 token 的 K/V。
  5. Share / CoW:只读 prefix 被其他请求引用,写入时拆分尾块。
  6. Preempt / Offload:容量压力下暂停请求,或把部分 pages 迁到 Host/远端层。
  7. Evict / Free:策略淘汰可复用 prefix,完成请求释放私有 blocks。

排障时应把“没有命中”“命中了但未驻留 GPU”“驻留但正在迁移”“被淘汰”分成不同状态,不能都折算为 cache miss 或零成本。

7. 长上下文的四类控制手段

手段控制对象收益边界
GQA/MLAbytes/token降低常驻容量与读取量需要模型原生结构与匹配 kernel
KV 量化bytes/element进一步缩小 payload要计 Scale/对齐并验证质量与实际 kernel
Sliding/Sparse/Compression保留或访问的 token限制长上下文增长改变可见区域或近似语义
Offload/Remote Cache驻留层级用 Host/网络容量换 HBM增加传输和调度延迟

这些手段作用点不同,可以组合,但不能把理论位宽比、逻辑容量和校准后线上容量混成一个数字。

8. 容量与性能检查表

  1. 模型保存的是 MHA/GQA KV、MLA latent,还是其他状态?
  2. 公式是全局量还是 per-rank,TP 下是否复制或均分?
  3. dtype 是否包含 Scale、对齐与 metadata?
  4. 统计的是 live payload、已分配 blocks,还是最大可分配容量?
  5. prefix hit 是否需要从 Host/远端加载?
  6. OOM 来自 KV、本轮 activation、graph pool,还是其他 workspace?
  7. 结论是配置估算、allocator 实测,还是业务负载校准?

只有先统一这些口径,KV 容量、并发和 TTFT 才能比较。

相关页面