【学习笔记】推理加速三板斧:KV Cache、PagedAttention、Continuous Batching(11/35)

入门认知 5 篇 + 训练与微调 5 篇——前 10 篇我们走完了「模型怎么来」的全过程。
从这一篇开始,我们进入推理优化篇(第 11-15 篇)。这是整个系列里和后端工程师最贴近的部分,也是我个人最想认真讲透的部分。
为什么这么说?
很多团队的真实情况是这样的:
-
模型训完,丢给后端「请部署上线」
-
后端
pip install transformers,写个 FastAPI,跑起来 -
发现:单 H100 跑 70B,平均 30 tokens/s,并发 1
-
老板:「为什么这么慢?vLLM 一个请求能跑 1000 tokens/s 啊!」
-
后端:「……我也是按官方文档写的」
这 30 倍的性能差距,就藏在 vLLM 等推理框架里的"魔法"中。这些"魔法"不是什么不可言说的黑科技,本质就是三个核心技术:
KV Cache、PagedAttention、Continuous Batching。
任何一个想做大模型部署的工程师,必须把这三个东西理解到「能讲给别人听」的程度。这一篇我们就来做这件事。
读完本文你将能:
-
理解 Prefill vs Decode 两阶段的本质差异
-
算清 KV Cache 显存账,并知道量化怎么优化
-
理解 PagedAttention 如何让显存利用率从 30% 跃升到 90%+
-
理解 Continuous Batching 如何让吞吐量提升 5-10×
-
用 vLLM 跑通一个高性能推理服务,并能监控关键指标
我们开始。
一、为什么推理优化是另一个 90% 的工作
1.1 朴素推理 vs 优化推理的差距
我们先用一组真实数字震撼一下:
实验设置:Llama-3-70B INT8、单 H100 80G、prompt=2K、生成=512 token、batch=32
|
推理方式 |
单卡吞吐(tokens/s) |
相对性能 |
|
HuggingFace Transformers (朴素) |
~30 |
1× |
|
Transformers + KV Cache |
~120 |
4× |
|
TGI(HF 生产版) |
~600 |
20× |
|
vLLM (默认) |
~2000 |
67× |
|
vLLM + INT8 + FP8 KV Cache |
~3500 |
117× |
|
TensorRT-LLM (极致优化) |
~4500 |
150× |
150 倍的差距。这意味着什么?
-
不优化:你需要 150 张 H100 服务 1000 并发用户
-
优化好:1 张 H100 就够
每月成本差距:2K。这就是为什么推理优化是大模型部署的核心议题。
1.2 推理成本 > 训练成本
一个被反复验证的事实:
模型上线后,推理累积成本会在几个月内超过训练成本。
|
项目 |
训练成本 |
推理成本(月) |
多少月超过训练 |
|
Llama 3-70B |
$13M |
~$1M |
13 个月 |
|
DeepSeek V3 |
$5.6M |
~$0.6M |
9 个月 |
|
中型客服应用 |
~$200K |
~$30K |
6 个月 |
|
小型 API 包装 |
$0 |
~$5K |
N/A |
这就是为什么 OpenAI / Anthropic 都把推理优化看作头等技术战略——训练是一次性投入,推理是持续支出。
1.3 推理优化的三大维度
工业上把推理优化拆成三个独立维度:
显存优化 ── 让模型放得下
吞吐优化 ── 让单位时间处理的请求更多
延迟优化 ── 让单个请求更快返回
三者经常冲突——比如增加 batch size 提升吞吐,但会增加单请求延迟。
工程师的核心工作就是在三者之间找平衡。
本系列推理优化篇 5 篇会系统讲清这一切:
|
篇号 |
主题 |
核心优化维度 |
| 11(本篇) |
KV Cache / PagedAttention / Continuous Batching |
吞吐 + 显存 |
|
12 |
量化压缩 |
显存 |
|
13 |
Flash Attention |
延迟 + 显存 |
|
14 |
投机解码 |
延迟 |
|
15 |
长上下文优化 |
显存 + 延迟 |
我们开始第一板斧。
二、第一板斧:KV Cache
第 2 篇我们讲过 KV Cache 的基础原理。这里我们做工程视角的深度拆解——什么时候用、用多少、怎么压缩、怎么复用。
2.1 Prefill vs Decode:两阶段本质不同
大模型生成的工作流程其实是两个完全不同的阶段:
[Prefill 阶段] [Decode 阶段]
处理整个 prompt 每次生成 1 个 token
计算 n × n attention 计算 1 × (n+i) attention
计算密集 显存带宽密集
~1 次 ~512 次(生成 512 token)
单次耗时长 单次耗时短
关键洞察:
-
Prefill 是 compute-bound——GPU 算力是瓶颈
-
Decode 是 memory-bound——GPU 显存带宽是瓶颈
这两个阶段需要的优化完全不同:
|
阶段 |
主要瓶颈 |
优化方向 |
|
Prefill |
算力 |
Flash Attention、Chunked Prefill |
|
Decode |
带宽 |
KV Cache、量化、Batching |
生产环境的延迟构成:
TTFT(Time To First Token) = Prefill 耗时
TPOT(Time Per Output Token) = Decode 单 token 耗时
端到端延迟 = TTFT + N × TPOT
TTFT 和 TPOT 是两个互相独立的优化目标。
2.2 KV Cache 显存账(再算一次)
KV Cache 显存公式:
KV_cache = 2 × batch × seq_len × num_layers × num_kv_heads × head_dim × dtype_size
以 Llama-3-70B(80 layers, 8 KV heads with GQA, head_dim=128, FP16)为例:
|
上下文 |
单请求 KV Cache |
可并发数 (单 H100 80G, 模型占 70G 留 10G 给 KV) |
|
4K |
1.3 GB |
7 |
|
8K |
2.6 GB |
3 |
|
32K |
10.7 GB |
0(连一个都装不下) |
这就是短上下文 + 多并发 vs 长上下文 + 少并发的根本权衡。
2.3 KV Cache 量化
KV Cache 已经是显存大户,把它量化是最直接的优化。
主流方案:
|
方案 |
显存节省 |
精度损失 |
支持框架 |
|
FP16(基准) |
1× |
0% |
全部 |
| FP8 (E5M2 / E4M3) |
2× |
< 0.5% |
vLLM, TensorRT-LLM |
| INT8 |
2× |
1-2% |
vLLM, SGLang |
|
INT4 |
4× |
3-5%(部分任务掉点明显) |
vLLM 实验 |
生产推荐:
-
有 H100+ → FP8 KV Cache(性能精度兼顾)
-
老硬件 → INT8 KV Cache
-
极致显存 → INT4,但要测业务效果
vLLM 启用 FP8 KV Cache:
vllm serve Qwen/Qwen3-32B \
--kv-cache-dtype fp8 \
--quantization fp8
2.4 Prefix Caching:把"重复计算"省掉
很多业务场景的 prompt 有大量重复前缀:
-
多轮对话:每次新对话都带历史,前 N 轮的 KV 是重复的
-
RAG:每次请求都有相同的 system prompt + 检索文档
-
Few-shot:固定的 examples 占了大半 prompt
-
Code 助手:每次都带整个项目上下文
朴素做法:每次都重新做 Prefill 计算这些 KV。
Prefix Caching 做法:把高频前缀的 KV 缓存起来,下次直接复用。
收益惊人:
-
多轮对话场景:TTFT 降 50-80%
-
RAG 场景:TTFT 降 70-90%
-
极端情况(90% prompt 重复):TTFT 降 95%
vLLM 启用:
vllm serve ... --enable-prefix-caching
OpenAI / Anthropic 早已做这个——所以他们的 prompt cache 价格只有正常价的 10-50%。
高级:Tree-based Prefix Caching(SGLang)
SGLang 进一步做了 RadixAttention——用基数树管理多个 prompt 的前缀树,支持任意分叉共享:
[system prompt]
/ \
[user A] [user B]
| |
[response] [response]
A/B 用户共享 system prompt 的 KV,每个用户自己的对话单独存储——节省显存 + 加速 prefill。
三、第二板斧:PagedAttention
PagedAttention 是 vLLM 的核心创新(2023.6,Berkeley),目前已经被全行业采纳。它解决了一个所有人都忽视但极其严重的问题——KV Cache 显存碎片化。
3.1 传统 KV Cache 的「三宗罪」
罪 1:连续分配的浪费
传统 KV Cache 给每个请求预分配最大长度的显存。
假设你支持 32K 上下文,那即使一个请求只用了 1K,也得占 32K 的显存(万一它说话长呢)。
实际利用率:30-50%。
罪 2:碎片化
不同请求长度不同,释放后留下碎片——新请求无法利用。
[已用][已用][已用][空][已用][空][空][已用][空]
↑ ↑ ↑
小碎片,无法被大请求使用
罪 3:长短请求混合崩溃
短请求和长请求一起来时,短请求结束后留下的空间被长请求占满——后续短请求干等。
时刻 1:[长][短][短][短] ← 短的快结束
时刻 2:[长][空][空][空] ← 短的结束了,留下空
时刻 3:[长][新长][...] ← 新长请求占满了空间
时刻 4:[长][新长] ← 没法接新短请求了!
整体显存利用率:通常只有 30-50%。一张 80G 卡,实际有效利用 24-40G。
3.2 PagedAttention 的思路:借鉴 OS 虚拟内存
操作系统怎么处理类似的"碎片化 + 不知道用多少"问题?
分页(Paging)。
PagedAttention 直接借用了这个思想:
KV Cache 显存 → 切成固定大小的"页"(block)
默认 16 token / 页
每个请求的 KV → 按需申请页
不需要连续
用多少分多少
对比:
传统:每请求一块连续大显存
[████████████████][░░░░░░░░░░][████████████████]
占了用不到 小碎片
PagedAttention:所有请求共用一个页池
[█A][█B][█A][█C][░][█B][█A][█C][░][█A][░][█B]
↑ ↑ ↑ ↑ ↑ ↑ ↑ ↑
请求 A B A C B A C A
散落但满载
通过一张page table 把"虚拟的连续 KV"映射到"物理的离散页"——和 OS 完全一样。
3.3 PagedAttention 的实际收益
PagedAttention 让显存利用率从 30% 跃升到 90%+,直接带来:
-
单卡可并发请求数提升 2-4×
-
端到端吞吐提升 2-4×
-
长短请求混合也不慌
vLLM 官方论文实测数据:
|
模型 |
朴素 KV Cache |
PagedAttention |
提升 |
|
LLaMA-13B |
12 reqs/s |
28 reqs/s |
2.3× |
|
LLaMA-33B |
5 reqs/s |
16 reqs/s |
3.2× |
|
LLaMA-65B |
2 reqs/s |
8 reqs/s |
4× |
模型越大,PagedAttention 收益越明显。
3.4 进阶:Copy-on-Write(CoW)
PagedAttention 还能做一件更聪明的事——KV Cache 共享。
场景:beam search、并行 sampling、A/B 测试。多个序列共享相同的 prefix。
朴素做法:每个序列复制一份 prefix KV。
CoW 做法:
-
多个序列指向同一份 prefix KV 页
-
一旦某个序列要修改某页,复制一份再改(Copy on Write)
-
没有修改的页继续共享
效果:多采样场景显存省 50-80%。
3.5 PagedAttention 的代价
PagedAttention 不是没代价:
-
页索引开销:每次 attention 计算需要先查 page table,增加几个 % 的开销
-
CUDA Kernel 复杂度高:需要专门的 paged attention kernel
-
实现成本:vLLM 团队花了几个月才稳定
但相比 2-4× 的吞吐收益,这点代价几乎可以忽略。
现在主流推理框架都已经"PagedAttention 化":vLLM、SGLang、TensorRT-LLM、TGI 都支持。
四、第三板斧:Continuous Batching
第三板斧解决另一个看似简单但极其重要的问题——多请求怎么 batch。
4.1 静态 Batching 的悲剧
静态 Batching(朴素做法):
请求 1 到达 → 等其他请求 → 凑齐 batch=32 → 一起跑 → 等最长的结束 → 全部返回
这有几个致命问题:
问题 1:等待延迟
请求来的间隔不规律。要凑齐 32 个,可能要等几秒。用户体验灾难。
问题 2:木桶效应
batch 内 32 个请求,输出长度差异巨大:
请求 1: 输出 50 token → 99% 时间在等
请求 2: 输出 100 token → 95% 时间在等
请求 3: 输出 1000 token → 慢悠悠
...
请求 32: 输出 5000 token → 大家全部陪它跑完
短请求被长请求"绑架",GPU 利用率惨不忍睹。
问题 3:长尾恶化
业务场景中"长 tail" 很常见。99 percentile 的请求可能 10× 于 median。统计上必然出现极差。
结果:静态 batching 实际有效 GPU 利用率 ~ 20-30%。
4.2 Continuous Batching:迭代级调度
Continuous Batching 的核心改动:
不在请求级别 batch,而是在 iteration(每生成一个 token)级别 batch。
关键改动:
-
任意时刻可插入新请求——不需要等
-
任意时刻可移除完成的请求——立即返回
-
每个 iteration(生成一个 token)作为调度单位
举例:
iter 1: 请求 A 在 batch
iter 2: 请求 A 在 batch,请求 B 到达,B 也加入
iter 3: A, B 都在
iter 4: A 完成了!立即返回 A,B 继续
iter 5: 只有 B 在 batch,请求 C 到达加入
iter 6: B, C 都在
...
效果:
-
GPU 利用率从 20-30% 提升到 80%+
-
短请求不再被长请求绑架
-
吞吐量提升 5-10×
4.3 Continuous Batching 的实现挑战
实现这个看似简单的优化,工程上其实非常难:
-
变长 KV Cache 管理——不同请求不同长度,需要 PagedAttention 配合
-
变长 attention mask——每个 iteration 重新计算
-
请求优先级与公平性——长请求会不会饿死
-
预填充与解码混合——新请求要先 prefill,旧请求在 decode
这就是为什么 PagedAttention + Continuous Batching 必须一起做——分开不好实现。
4.4 PagedAttention + Continuous Batching = vLLM 的魔法
PagedAttention 解决"显存放得下",Continuous Batching 解决"GPU 跑得满"。
两者协同的效果:
-
单卡能放下的请求数:2-4×
-
每个请求平均跑得快:2-3×
-
综合吞吐:5-10×
这也是为什么 vLLM 相对朴素推理能快 50-100 倍。
4.5 进阶:Chunked Prefill
Continuous Batching 还有一个进化版——Chunked Prefill(vLLM 0.5+ 默认)。
问题:Prefill 很重(O(n²) 计算),如果一个长 prompt 来了,它会独占 GPU,把所有 decode 请求阻塞。
Chunked Prefill 的做法:把长 prompt 切成 chunk(如 512 token),逐 chunk 处理,和 decode 请求混合调度。
效果:
-
长 prompt 不再阻塞短请求
-
整体延迟尾部大幅改善
-
99% latency 降低 50%+
启用:
vllm serve ... --enable-chunked-prefill
五、实战 + 关键指标
5.1 vLLM 完整部署示例
# 部署 Qwen3-32B + 全套优化
vllm serve Qwen/Qwen3-32B-Instruct \
--max-model-len 32768 \
--tensor-parallel-size 2 \
--gpu-memory-utilization 0.9 \
\
--enable-prefix-caching \ # KV Cache 复用
--enable-chunked-prefill \ # 分块 prefill
--kv-cache-dtype fp8 \ # KV Cache 量化
\
--max-num-batched-tokens 8192 \
--max-num-seqs 256 \ # 最大并发数
--swap-space 16 \ # CPU swap 备用
\
--port 8000
关键参数解读:
|
参数 |
作用 |
推荐值 |
--gpu-memory-utilization |
显存使用上限 |
0.85-0.95 |
--enable-prefix-caching |
开启 prefix 复用 |
强烈推荐 |
--enable-chunked-prefill |
长 prompt 不阻塞 |
推荐 |
--kv-cache-dtype |
KV Cache 精度 |
fp8 (H100) / int8 |
--max-num-seqs |
最大并发 |
看显存 |
--max-num-batched-tokens |
单次 batch 总 token |
通常 = max_seq_len × 2-4 |
5.2 关键监控指标
部署后必须监控的 4 个指标:
|
指标 |
含义 |
目标值 |
| TTFT
(Time To First Token) |
首 token 延迟 |
< 500ms(短 prompt) |
| TPOT
(Time Per Output Token) |
单 token 生成时间 |
< 50ms |
| Throughput |
整体吞吐 (tokens/s) |
越高越好 |
| GPU Utilization |
GPU 利用率 |
80%+ |
vLLM 自带 Prometheus metrics endpoint:
curl http://localhost:8000/metrics | grep vllm
关键 metrics:
vllm:time_to_first_token_seconds_histogram
vllm:time_per_output_token_seconds_histogram
vllm:request_success_total
vllm:num_requests_running
vllm:num_requests_waiting
vllm:gpu_cache_usage_perc
vllm:cpu_cache_usage_perc
接到 Grafana 后能可视化看到效果。
5.3 性能调优 Checklist
部署后发现性能不达标,按这个顺序排查:
-
GPU 利用率低(< 60%)
-
是不是 batch 太小?提高
--max-num-seqs -
是不是请求太少?看
num_requests_waiting
-
-
TTFT 高
-
prompt 太长 → 启用 chunked prefill
-
没开 prefix caching → 启用
-
-
TPOT 高
-
显存带宽满了?看 GPU 利用率
-
模型太大?考虑量化
-
-
OOM
-
KV Cache 量化(fp8/int8)
-
减少 max num seqs
-
升级到张量并行
-
-
5. 长尾恶化
-
开 chunked prefill
-
设置 max tokens 上限
-
5.4 性能对比实测
我们用 Qwen3-32B 做对比实验(单 H100、prompt=1K、output=512):
|
配置 |
吞吐 (tokens/s) |
TTFT |
TPOT |
|
纯 transformers |
25 |
1200ms |
40ms |
|
+ KV Cache |
95 |
1100ms |
11ms |
|
vLLM 默认 |
1800 |
600ms |
28ms (batch 摊薄) |
|
vLLM + fp8 KV |
2400 |
550ms |
22ms |
|
vLLM + 全套优化 |
3200 |
280ms |
20ms |
127 倍 吞吐差距。这就是推理优化的威力。
六、扩展话题与下一篇预告
6.1 三板斧之外的优化
除了这三板斧,2024-2025 还有几个新进展:
|
技术 |
解决问题 |
状态 |
| Speculative Decoding |
串行解码慢 |
主流(第 14 篇) |
| Tensor Parallel + Pipeline Parallel |
单卡装不下 |
主流(第 20 篇) |
| Multi-LoRA Serving |
多业务复用 |
主流 |
| Disaggregated Prefill/Decode |
资源利用不均 |
新兴 |
| CPU/GPU 卸载 |
极致显存 |
实验 |
6.2 推理框架横向对比预告
下一篇我们会进入量化,第 17 篇会做完整的推理框架横评:
|
框架 |
优势 |
适合 |
|
vLLM |
易用、社区好 |
通用首选 |
|
SGLang |
复杂控制流、JSON |
Agent / 结构化 |
|
TensorRT-LLM |
极致性能 |
生产追求极限 |
|
TGI |
HF 生态融合 |
HF 用户 |
6.3 不要只看 benchmark
最后一个反直觉的提醒:
性能基准很重要,但不要只盯着 benchmark。
很多框架在标准 benchmark 上跑得飞快,但生产场景(长尾、突发、长上下文)下表现不一致。所以:
-
用真实业务数据做测试
-
监控 p99 / p999 而不只是平均值
-
测试长时间稳定性(运行 24 小时看看)
七、结语:理解三板斧,理解大模型推理
读完本文你应该明白:
-
KV Cache:解决重复计算,4-10× 加速。Prefill 与 Decode 是两个完全不同的阶段
-
PagedAttention:解决显存碎片,2-4× 提升并发能力。借鉴 OS 虚拟内存的智慧
-
Continuous Batching:解决静态 batch 的悲剧,5-10× 吞吐提升
-
三者协同 = vLLM 的"魔法",相比朴素推理 50-150× 加速
-
真正的生产部署还要看 TTFT/TPOT/Throughput/GPU 利用率 四大指标
-
Chunked Prefill / Prefix Caching / KV Cache 量化 是当下推荐的进阶配置
参考文献:
openEuler 是由开放原子开源基金会孵化的全场景开源操作系统项目,面向数字基础设施四大核心场景(服务器、云计算、边缘计算、嵌入式),全面支持 ARM、x86、RISC-V、loongArch、PowerPC、SW-64 等多样性计算架构
更多推荐


所有评论(0)