第2章:推理引擎核心技术
掌握 PagedAttention、Continuous Batching、Prefix Cache 和 Chunked Prefill 四大推理引擎核心技术
现代 LLM 推理引擎(vLLM、SGLang、TensorRT-LLM)能把 GPU 利用率从 20% 拉到 80%+,靠的是四个组合拳:PagedAttention 解决 KV 碎片、Continuous Batching 让 GPU 永不空转、Prefix Cache 复用前缀、Chunked Prefill 解 Prefill/Decode 互扰。本章逐个深入,理解为什么这套技术栈能把吞吐做到 20× 提升。
📑 目录
- 1. PagedAttention:KV 的虚拟内存
- 2. Continuous Batching:动态拼车
- 3. Prefix Cache:复用前缀 KV
- 4. RadixAttention:更聪明的前缀树
- 5. Chunked Prefill:解 PD 互扰
- 6. 四大技术的协同
- 自我检验清单
- 参考资料
1. PagedAttention:KV 的虚拟内存
1.1 传统 KV 分配的问题
每个请求一来就预留 max_seq_len 大小的连续 KV Cache:
Request A (实际 100 token): ████____________(预留 4096 个 slot)
Request B (实际 4000 token): ██████████████__(几乎用满)
Request C (300 token): ████__________(浪费严重)
问题:
- 内部碎片:实际用不到 max_len,大量空间浪费
- 外部碎片:请求结束释放后,空闲块大小不一,新请求难以放入
- 预留过大:典型场景 GPU 内存利用率只有 20-40%
1.2 PagedAttention 思想
借鉴操作系统虚拟内存:把 KV Cache 切成固定大小的 page(典型 16 token / page),用 Page Table 映射逻辑序列到物理页。
Request A 的逻辑序列:
Logical block 0 (token 0-15) → Physical page 5
Logical block 1 (token 16-31) → Physical page 12
Logical block 2 (token 32-47) → Physical page 3
Physical pool:
Page 0 Page 1 Page 2 Page 3 ... Page N
[free] [used] [used] [A.2] [A.0]
1.3 收益
- 几乎零内部碎片:每页 16 token,粒度很细
- 无外部碎片:页大小固定,任何空闲页都能用
- 内存利用率 80-90%(vs 传统 ~40%)
vLLM 论文实测:比 HuggingFace 快 20×,比 FasterTransformer 快 2-4×。
1.4 GPU 实现挑战
间接寻址(通过 page table)破坏了合并访存模式,需要专门的 paged attention CUDA kernel。
// 简化伪代码
__global__ void paged_attn(...) {
for (int page_idx = 0; page_idx < num_pages; page_idx++) {
int physical_page = page_table[req_id][page_idx];
// 从 kv_pool[physical_page] 读 K, V
// 用 online softmax 累加
}
}
实际工业级实现见 vLLM 的 csrc/attention/paged_attention.cu 或 FlashInfer 的 paged decode kernel。
1.5 Copy-on-Write:Beam Search 优化
Beam Search 需要从一个父序列分裂多个候选——传统做法复制整个 KV,浪费严重。PagedAttention 用 CoW:多个 beam 共享前缀页,只在分歧处复制。
2. Continuous Batching:动态拼车
2.1 Static Batching 的问题
传统做法:等齐一个 batch 才开始,所有请求同时结束才能换新 batch。
Time →
Batch 1: ████████████████████ (8 个请求,长度 200-2000 不等)
↑ 必须等最长的完成
Batch 2: ████████____
问题:
- 短请求等长请求结束,latency 高
- batch 内部分位置已生成完毕,GPU 空算
- 新请求要等到下个 batch,排队等待长
2.2 Continuous Batching(Iteration-level Scheduling)
每个 iteration 重新组 batch:已完成的请求立刻退出,新请求随时插入。
Time →
slot 0: ████ ___ ████████ ████ ███ (短-空-长-短-短)
slot 1: ██████████ ____ █████ ██ (长-空-短-短)
slot 2: ██ █████████████ ███████ (短-长-中)
slot 3: ████████ █████ ████ ████ ███ (中-中-短-短-短)
↑ 每一列都是一次 forward
GPU 始终满载,实测 throughput 提升 5-23×(vLLM 数据)。
2.3 Iteration-level Scheduler 伪代码
class Scheduler:
waiting = [] # 排队的新请求
running = [] # 正在 decode 的请求
def step(self):
# 1. 检查 running 中已完成的,移除
finished = [r for r in self.running if r.is_done()]
self.running = [r for r in self.running if not r.is_done()]
for r in finished: self.return_to_user(r)
# 2. 从 waiting 中取新请求(KV 空间够就加入)
while self.waiting and self.has_kv_space():
r = self.waiting.pop(0)
self.run_prefill(r)
self.running.append(r)
# 3. 当前 running 集合一起 decode 一步
self.run_decode_step(self.running)
2.4 与 PagedAttention 的协同
Continuous Batching 需要频繁分配/释放 KV——而 PagedAttention 的页粒度恰好解决了这个动态分配问题。两者是天作之合,vLLM 的核心设计就是把它们组合起来。
3. Prefix Cache:复用前缀 KV
3.1 场景
很多应用中,多个请求共享同一段 system prompt 或 few-shot 例子:
请求 A: [SYSTEM PROMPT][用户问题 A][...]
请求 B: [SYSTEM PROMPT][用户问题 B][...]
请求 C: [SYSTEM PROMPT][用户问题 C][...]
如果每次都重新 Prefill SYSTEM PROMPT,浪费了 90% 的算力。
3.2 Prefix Cache 机制
对 prompt 的前缀做 hash,如果命中已有的 KV Cache,直接复用,跳过 Prefill 这部分。
新请求来:
1. 把 prompt 按 page 大小切分
2. 对每个 page 的 token 做 hash
3. 在 hash table 中查询是否有命中
4. 命中的页直接共享物理页(增加引用计数)
5. 未命中的部分才走 Prefill
3.3 收益
System prompt 1000 token,问题 100 token,共享率 90%:
- 朴素 Prefill:1100 token,~50 ms
- Prefix Cache 命中:只 Prefill 100 token,~5 ms——TTFT 降低 10×
3.4 vLLM 用法
from vllm import LLM, SamplingParams
llm = LLM(model="meta-llama/Llama-2-7b", enable_prefix_caching=True)
# 第一次请求,正常 Prefill
out1 = llm.generate(["[SYSTEM]...[USER]问题 A"], sampling)
# 第二次,前缀命中,只算用户问题部分
out2 = llm.generate(["[SYSTEM]...[USER]问题 B"], sampling)
4. RadixAttention:更聪明的前缀树
vLLM 的 Prefix Cache 是按 page hash 匹配,只能复用”完全相同的”前缀。RadixAttention(SGLang)用 Radix Tree(基数树),可以复用任意公共前缀。
4.1 直觉
Tree:
root → [SYSTEM 你是助手]
├── [USER 计算...]
│ ├── [对 1+1]
│ └── [对 2+2]
└── [USER 翻译...]
└── [hello]
每个节点是一段 token,节点间共享路径。新请求来,沿树匹配最长公共前缀,后续才 Prefill。
4.2 收益
复杂多轮对话场景,RadixAttention 的命中率比 Prefix Cache 高 30-50%。
4.3 SGLang 用法
import sglang as sgl
sgl.set_default_backend(sgl.RuntimeEndpoint("http://localhost:30000"))
@sgl.function
def multi_turn(s):
s += "[SYSTEM] 你是助手\n"
s += "[USER] 1+1=?\n"
s += "[ASSISTANT]" + sgl.gen("ans1", max_tokens=10)
s += "[USER] 2+2=?\n"
s += "[ASSISTANT]" + sgl.gen("ans2", max_tokens=10)
第二个 USER 后的 prefill 会复用第一轮的 KV——多轮场景加速尤其明显。
5. Chunked Prefill:解 PD 互扰
5.1 问题
Continuous Batching 让 Prefill 和 Decode 请求混在一个 batch 里。但 Prefill 是 compute bound 的大算子(几千 token 一次),会严重拖慢同 batch 的 Decode——Decode 请求被迫等 Prefill 算完才能拿到下一 token。
5.2 Chunked Prefill
把长 Prefill 切成多个 chunk(典型 512 token),每个 iteration 只算一个 chunk:
Iteration 1: [Decode A][Decode B][Prefill C 的 chunk 1]
Iteration 2: [Decode A][Decode B][Prefill C 的 chunk 2]
Iteration 3: [Decode A][Decode B][Prefill C 的 chunk 3] ← C 完成,转 Decode
TPOT 显著改善——Decode 请求每次只被一个 chunk 阻塞,而非整个 Prefill。
5.3 vLLM / SGLang 配置
llm = LLM(
model=...,
max_num_batched_tokens=4096, # 一个 iteration 总 token 上限
enable_chunked_prefill=True,
)
5.4 与 PD 解耦的关系
Chunked Prefill 是”温和”方案:仍在同 GPU 池处理 P/D。 PD 解耦(第 6 章)是”激进”方案:分到不同 GPU 池。
两者互补:小集群用 Chunked Prefill,超大集群上 PD 解耦。
6. 四大技术的协同
vLLM 的整体架构:
请求到达
↓
Scheduler(Iteration-level)
├── 查 Prefix Cache:命中部分跳过 Prefill
├── 长 Prefill 切 Chunk
└── 组合 Prefill chunks + Decode 请求成一个 batch
↓
PagedAttention 分配 KV 物理页
↓
执行一个 iteration
↓
完成的请求退出,新请求加入,循环
四个技术任一缺失,整体性能就会降一个档:
| 缺失 | 后果 |
|---|---|
| 无 PagedAttention | KV 碎片严重,并发上不去 |
| 无 Continuous Batching | GPU 利用率仅 20-30% |
| 无 Prefix Cache | 重复 Prefill,TTFT 高 |
| 无 Chunked Prefill | 长 Prefill 拖垮 Decode P95 |
🌟 vLLM、SGLang、TensorRT-LLM 都实现了这四件套——它们的差异在 RadixAttention(SGLang)、量化集成(TensorRT-LLM)、生态成熟度(vLLM)等方面。
✅ 自我检验清单
- PagedAttention 类比:能用操作系统虚拟内存类比解释 PagedAttention 设计
- 碎片问题:能解释传统 KV 分配的内部碎片和外部碎片来源
- Continuous Batching:能用图示对比 Static Batching 和 Continuous Batching 的时间利用率
- Iteration-level Scheduler:能默写 scheduler 一个 step 的伪代码(回收完成 / 加入新请求 / 一起 decode)
- Prefix Cache 命中:能算出 system prompt 1000 token、问题 100 token 时的 TTFT 提升倍数
- Prefix Cache vs RadixAttention:能解释 RadixAttention 在多轮对话中为什么命中率更高
- Chunked Prefill 收益:能解释为什么不切 chunk 会让 Decode P95 翻倍
- 四件套协同:能说出每个技术缺失会导致什么具体性能问题
- vLLM 配置:能正确启用 prefix caching + chunked prefill + max_num_batched_tokens
📚 参考资料
- vLLM Paper (Kwon et al., 2023) —— PagedAttention:https://arxiv.org/abs/2309.06180
- Orca Paper (Yu et al., 2022) —— Continuous Batching 鼻祖:https://www.usenix.org/conference/osdi22/presentation/yu
- SGLang Paper (Zheng et al., 2023) —— RadixAttention:https://arxiv.org/abs/2312.07104
- DeepSpeed-FastGen Paper —— Dynamic SplitFuse:https://arxiv.org/abs/2401.08671
- vLLM 官方文档:https://docs.vllm.ai/
- 猛猿:vLLM 源码解析系列 —— 知乎深度长文
- DefTruth:vLLM Prefix Cache 原理图解
- Anyscale Blog:Continuous batching enables 23x throughput