KV Cache

KV Cache

Decode 阶段,每生成一个新 token 都需要拿它的 Q 去跟所有历史 token 的 K、V 做 attention。因为 causal mask,已有 token 的 K、V 永远不会因为新 token 的加入而改变——不缓存是纯粹的浪费。

KV Cache 就是把每步算出来的 K、V 存到显存里,下一步直接拿来用。每步只需要算新 token 的 Q、K、V,然后追加进 cache。

代价:显存占用随序列长度线性增长

LLaMA-2 7B 的估算:每个 token 的 KV Cache = 32层 × 2(K和V) × 4096维 × 2字节(fp16) = 0.5 MB

一个 2048 token 的请求 = 1 GB。A100 80GB 减去 14GB 模型权重,理论上最多放 66 个请求的 KV Cache——但这只是显存上限,每步 Decode 还要把整个 KV Cache 读一遍,序列越长每步越慢。

PagedAttention

早期系统给每个请求预分配连续显存存 KV Cache,按最大可能长度预留空间,导致两种浪费:

  • 内部碎片:分配了 2048 slots,实际只用 500,剩下的锁死占着
  • 外部碎片:请求结束后释放的空间不连续,新请求放不进去

PagedAttention 借鉴操作系统虚拟内存分页,把 KV Cache 切成固定大小的 block(如每 block 存 16 个 token),按需分配,用 block table 记录逻辑地址到物理地址的映射:

逻辑 block 0 → 物理地址 0x1000
逻辑 block 1 → 物理地址 0x5000   ← 物理不连续没关系
逻辑 block 2 → 物理地址 0x2000

每个请求维护自己的 block table,attention kernel 内部按映射取数据,不要求物理连续。内部碎片从"最大序列长度"降到"最多一个 block",外部碎片消失。

warning: 反直觉 前缀共享节省的不是 decode 时的带宽——多个请求共享同一组 prefix block,decode 时每个请求还是要各自读一遍(因为各自的 Q 不同,无法合并 attention 计算)。节省的是:① 显存只存一份;② 新请求进来不用重新做 prefill。

多个请求共享同一 prefix block 时,写新 token 才 copy-on-write,只复制需要修改的 block,不影响其他请求。这是 前缀缓存 的物理基础。

和前缀缓存的关系

前缀缓存(RadixAttention)是 KV Cache 的进一步延伸:不只是缓存同一个请求内的历史 token,还跨请求复用共享前缀的 KV Cache。本质上是把 PagedAttention 的 page 组织成 radix tree,实现共享前缀的零拷贝复用。

KV Cache 大小公式

每 token KV size = 2 × L × H_kv × D × dtype_bytes

  • 2:K 和 V 两份
  • L:Transformer 层数
  • H_kv:KV head 数(GQA 时远小于 Q head 数,MLA 时更小)
  • D:每个 head 的维度
  • dtype_bytes:fp16/bf16 = 2,fp8 = 1

代入 Llama-3-70B(L=80, H_kv=8, D=128, fp16):2×80×8×128×2 = 320 KB/token。一个 8K prefix ≈ 2.56 GB——这也是 KV Cache 分层存储 的出发点。

容量不够怎么办

当 KV cache 总量远超 HBM 时,KV Cache 分层存储 把冷数据 offload 到 host DRAM 甚至 SSD。代价是载入延迟,但仍然比重算 prefill 快 3~4 倍,且 context 越长优势越大。这是 Dynamo 的核心组件之一。

相关来源

  • llm-inference-tutorial:第3章
  • inference-experiments-2026-04:实验八、九验证了缓存命中的加速效果