LLM Infer Infra 学习资料库

公共概念:推理、KV Cache、内存和性能指标

本篇定位:第 2 篇,必读

把后面反复用到的概念一次讲清:KV 缓存什么、Prefill/Decode、容量公式、复用条件、四个“完成”时刻、指标口径。

需要数学推导读推理基础;需要 KV 收益模型读KV 容量与性能模型。

返回主题 · 上一篇:总体介绍 · 下一步:推理运行时

这一章介绍推理系统的共同基础。第一次先读本章;需要更完整的数学推导,再进入 Attention 与推理教材或 KV 容量与复用。

1. KV Cache 缓存的是什么

在一层 attention 中,输入隐藏状态经过投影,得到 Q、K、V。可以粗略理解为:Q 用来提出“现在要关注什么”,K 用来匹配历史位置,V 提供匹配后要汇总的信息。

Q = X Wq,K = X Wk,V = X Wv
Attention(Q, K, V) = softmax(Q Kᵀ / √d + causal mask) V

自回归生成一次向后推进一个或一组 token。历史 token 在固定 causal 前缀下的 K/V 可以保留,新 token 只需生成自己的 Q/K/V,再读取历史 K/V 做 attention,避免重新跑一遍完整历史前向。

为什么通常不缓存 Q?未来 token 用的是自己的 Q;历史 Q 已经完成当时的 attention,普通 Decode 不需要再用历史 Q 查询一次。KV Cache 仍然没有消除每轮读取历史状态的成本,长上下文 Decode 的 attention 成本仍会增长。

它与另外两种东西不同:

名字 存的内容 命中意味着什么
模型权重 学到的参数 模型可以执行,不表示请求已经算过
KV Cache 各层的中间状态 可以省掉适用前缀的一部分重复前向
输出/答案缓存 完整回答文本 在业务允许时直接返回答案

2. Prefill 与 Decode 为什么要分开看

Prefill 一次处理输入中的多个 token,通常能形成更大的矩阵运算。长输入时计算量很大,attention 的访存与实现也会影响瓶颈。

Decode 在常规自回归路径中,每个活跃请求每轮处理少量新 token。低 batch 时,读取权重和 KV 的带宽、kernel 启动、通信常常更突出。batch 增大、模型改变或使用投机解码后,瓶颈也会改变。

因此“Prefill 计算受限、Decode 带宽受限”是常见观察,不是所有设备和负载都成立的定律。

如果一个长 Prefill 独占 GPU 太久,正在 Decode 的用户会感觉输出卡顿。Chunked Prefill 把长输入分几轮算,让调度器在轮次之间服务其他请求。块太小则可能增加开销、降低计算效率。

3. 容量公式:面试中要真的会算

对普通、每层形状一致的 full-attention KV:

每 token KV 字节数 = 2 × L × Hkv × d × b
单请求 KV 字节数 = 每 token KV 字节数 × 已保留 token 数

2 表示 K 和 V,L 是层数,Hkv 是 KV heads 数,d 是 head dimension,b 是每元素字节数。不要把 Query heads 数 Hq 直接代入 Hkv。

教学模型取 L=32, Hkv=8, d=128, BF16 b=2:

每 token = 2 × 32 × 8 × 128 × 2 = 131,072 B = 128 KiB
8,192 token = 1 GiB
6,144 token = 0.75 GiB
再增加并缓存 256 token,额外 KV = 32 MiB

生成出的最后一个 token 通常要到下一次 forward 才会建立自己的 KV;请求若直接结束,这一步可能不再执行。因此容量预算可以按完整计划长度保守估算,精确测量要看实际已计算位置。

16 条互不共享、各保留 8,192 token 的活跃请求,需要约 16 GiB KV payload。这还没算权重、页尾浪费、工作空间、CUDA Graph 相关内存和在途副本。

变体与并行边界

情况 该怎么调整
MHA Hkv = Hq
GQA / MQA 多个 Q head 共享较少的 KV head,KV 容量通常更小
MLA 看实际缓存的是压缩 latent、位置部分还是展开形式,不能直接套 GQA 公式
滑窗 attention 按实际保留窗口及缓存实现算,不是所有层都保留完整历史
混合 attention / 递归状态 分层或按 group 算;部分状态按序列保存快照,而不是逐 token KV
TP 只有 KV heads 等实际均匀切分时,单 rank 才近似除以 TP;head 不足可能复制
PP 单 stage 按本 stage 的层数计算
DP / 存储副本 是多份资源占用,不能拿来抵消单份 KV 大小

4. 页、前缀与 chunk:三个粒度不要混淆

Page / block:解决内存怎么分

给每个请求预留最大长度的连续显存,会浪费很多空间,也难以扩容。分页把 KV 放进固定大小物理块,通过 block table 把逻辑 token 位置映射到物理地址。

假设每块放 16 个 token,一个 33-token 请求需要 3 块,最后一块只用 1 个位置。分页减少了连续大块分配的困难,但仍有尾部浪费和元数据开销。

Prefix cache:解决哪些计算可以复用

两个请求的前缀完全一致时,可以共享对应的只读 KV。共享块要有引用管理;分叉后新增 token 写各自尾部。若要修改共享尾块,必须按实现采用 copy-on-write 或其他隔离方案,不能直接覆盖。

PagedAttention 讨论地址与访存,前缀哈希或 Radix 树讨论匹配和共享;它们可以配合。

Chunk / object:解决存储怎么组织

外部缓存通常把一段 token 的 KV 组织成 chunk,再映射成一个或多个对象。若 chunk=256、page=16,在理想对齐条件下,一个 chunk 覆盖 16 个 page。遇到多 rank、多 group、按层存储时,一个 chunk 可能有多个对象。

小 chunk 复用更细,但产生更多 key、RPC、锁和完成事件;大 chunk 更容易跑满带宽,但部分前缀更难复用,也可能多搬无用数据。

5. 什么情况下才可以复用

“同样一段文字”不够。普通精确前缀复用至少要确认:

  1. 模型及实际计算语义兼容,包括权重 revision、adapter、位置编码等。
  2. 完整前缀的 token IDs 和位置关系相同。
  3. KV dtype、布局、层/group、rank 分片与目标一致,或者存在明确且正确的转换。
  4. 命中对象完整、仍有效、已成功加载到执行位置。
  5. 租户与访问策略允许共享。

例如“合同 A”放在不同 system prompt 后面,深层隐藏状态会受到不同前文影响,不能只按合同正文 hash 认定 KV 一样。位置变化、文档顺序变化也需要相应算法处理。

链式前缀 key 可以概念化为:

namespace = 模型与计算配置身份 + 允许共享的范围
chunk_hash[i] = H(namespace, chunk_hash[i-1], token_ids_of_chunk[i])
object_key = chunk_hash[i] + 必要的 rank/group/layout 标识

这是正确性设计示意,不是四个项目通用的序列化协议。某些字段在实际实现中由实例配置固定,某些需要显式编码;应检查数据从哪里传来。salt/namespace 能避免错误共享,但不等于鉴权。

普通精确前缀缓存不能跨过缺口:如果只有第 1、3 段 KV,第 2 段缺失,不能把第 3 段随意拼进去宣称整段免算。CacheBlend 等特殊机制另有位置处理、选择性重算和质量约束。

6. 多级缓存为什么可能更慢

常见存储层级是 GPU HBM、主机 DRAM、远端 DRAM、SSD。容量和单位成本通常更有利的层,访问延迟与数据路径也更长。

重算路径 = 排队 + 完整 Prefill + 首 token 其他开销
复用路径 = 排队 + 查询 + 加载关键路径 + 剩余 Prefill + 首 token 其他开销

只有当减少的计算和排队收益,超过查询、加载及其引入的额外排队时,缓存才改善该请求的延迟。加载关键路径指无法被计算掩盖的那部分时间。

假设读取 1 GiB,网络有效带宽 20 GiB/s:

网络部分至少约 50 ms
另假设查询 2 ms,H2D 40 ms,布局/同步 8 ms
串行加载约 100 ms

如果原本能省下 700 ms Prefill,值得尝试;如果只能省 60 ms,串行加载反而更慢。流水线能重叠部分阶段,但有启动、尾部和依赖成本,要用 trace 验证。

单位也要说清:200 Gb/s = 25 GB/s ≈ 23.28 GiB/s 的物理线速;应用 payload 带宽更低。不能把 200G 当 200 GB/s。

7. 四个“完成”时刻

知道对象存在
→ 存储对象可以读取
→ 数据已到目标 buffer,相关传输完成
→ GPU 已满足布局和同步依赖,可以消费

前一个时刻不能替代后一个。异步调用返回往往只是“任务已提交”。同理,请求取消或上层超时,也不代表 NIC/GPU 已停止访问旧内存。

安全回收要回答:谁拥有内存、谁还在读写、哪个事件证明访问结束、失败时如何停止或排空在途任务。lease、对象引用、锁、CUDA event、传输完成状态分别覆盖不同边界。

8. 指标怎么说才不含糊

指标 白话解释 常见误用
TTFT 从发送/接收请求到第一个输出 token 的等待 不说明起点,漏掉排队或网络
ITL 相邻输出 token 的时间间隔分布 只报均值,掩盖某些轮次卡顿
TPOT 常用口径为首 token 后的平均每 token 时间 把平均 TPOT 当最坏 ITL;单输出 token 无此分母
E2E 完整请求耗时 不固定输出长度就比较
Throughput 每秒完成的请求/token 不区分输入与输出,不报请求失败
Goodput 满足指定成功和延迟标准的有效吞吐 不说明 SLO、统计窗口和通过条件
命中率 在指定分母下,有多少数据可复用 混用请求、token、字节命中率

报性能至少说明模型、输入/输出分布、到达率、并发、冷热状态、硬件、版本及 P50/P95/P99。相同并发的闭环测试和固定到达率的开环测试不是一回事:前者可能因为服务变慢而自动降低请求压力。

9. 后面经常遇到的术语

术语 白话解释 与 KV 的关系
HBM / DRAM 前者通常指 GPU 高带宽显存,后者在本文通常指主机内存 存在 host 的 KV 需要经适用路径送入 GPU 才能用于普通 GPU attention
H2D / D2H 主机到设备 / 设备到主机的拷贝 offload 和 restore 不能只算网络时间
Pinned memory 被锁定、不能随意换出的主机内存,常用于异步设备拷贝 锁页本身不等于完成 RDMA 注册,也不延长对象所有者寿命
TP,张量并行 把一层中的部分张量计算分给多张卡协作 KV 是否分片,要看实际 head 和布局
PP,流水并行 把不同模型层放在不同设备/stage 每个 stage 通常只持有相应层的状态
DP,数据并行副本 多份模型处理不同请求;MoE 的组织还可能与 EP 耦合 普通副本的本地 KV 不会自动共享
EP,专家并行 MoE 的专家分布在不同设备,token 需要路由过去再汇总 专家 dispatch/combine 通信与 attention KV 存储是两条问题线
IPC / RPC 进程间通信 / 远程调用 控制消息走某条通路,不意味着大块 KV 也走同一条
SLO / P99 约定的服务目标 / 99% 样本不超过的延迟值 需说明请求范围、统计窗口、失败和超时如何计入
Trace / profiler 记录各阶段或设备工作的时间线 用来判断瓶颈和是否真的发生计算/传输重叠

10. 这章需要达到的程度

闭卷算出一个模型的 KV 容量和传输下界;画出共享块与私有尾部;解释一个错误命中案例;给出一个远端命中不值得加载的例子;分清查询、加载、计算和回收的边界。

这些掌握以后,再读各项目的具体类和函数,才不会把实现细节记成互不相干的名词。

输入主题、术语或问题