vLLM 推理优化实战:PagedAttention 与连续批处理如何把吞吐提升 20 倍
传统批处理在 LLM 推理上为什么效率极低
大模型推理的低效,往往不是"算力不够",而是"算力被浪费了"。自回归解码的生成特性与传统批处理的假设存在根本冲突——输出长度不可预测,请求到达时间不同步,KV Cache 按最大长度预分配——这三个冲突造成了三个典型的浪费:
浪费一:padding。批内 4 个请求的输出长度分别是 10、50、200、500 token,静态批处理要把全部 padding 到 500,前三个请求约 90% 的算力都花在了算 padding 上。
浪费二:等齐。请求 A 在 0 秒到达,请求 B 在 1.5 秒到达,静态批处理要等批满才开跑,A 白等了 1.5 秒。
浪费三:显存碎片。KV Cache 按最大可能长度预分配(例如每个请求 2048 token),实际平均只生成 200 token,90% 的预分配显存空着,而且这段连续空间无法给别的请求复用。
实测数据触目惊心:传统批处理下,A100 上的 GPU 利用率往往只有 30% 左右,显存利用率约 40%,其余全部浪费在 padding、等齐和碎片上。
理解 KV Cache:自回归解码的显存瓶颈
自回归解码第 t 步需要用到前 t-1 步所有 token 的 K、V 向量。不缓存就要重算前 t-1 步,复杂度是 O(t²);缓存后每步 O(1),但用显存换时间:
KV Cache 显存 = 2 × num_layers × num_heads × head_dim × seq_len × batch × dtype字节以 Llama-70B 为例:80 层、64 头、head_dim 128、seq_len 2048、batch 32、FP16,KV Cache 约需 80GB——比模型权重本身还大。推理过程中还有两个阶段之分:prefill 阶段一次性处理整个 prompt,计算密集(compute bound);decode 阶段逐 token 生成,显存密集(memory bound)。两个阶段对资源的需求完全不同,这也是传统单一批处理难以高效调度的深层原因。
PagedAttention:把 KV Cache 当虚拟内存管理
vLLM 提出的 PagedAttention 借鉴了操作系统虚拟内存分页的思想。传统做法为每个请求预分配一段连续的 KV Cache 空间(按 max_len),而 PagedAttention 把 KV Cache 切成固定大小的物理块(block,典型值 16 token/块),按需分配,用块表(block table)记录逻辑序列到物理块的映射。
这样做带来三个直接收益:消除了预分配浪费和外部碎片,显存按实际用量分配,利用率从约 40% 提升到 95% 以上;可并发请求数受限于总块数而非连续大块,同等显存下并发能力提升 2-3 倍;请求级抢占和优先级调度成为可能——长请求可以被挂起并恢复,不会阻塞短请求。
值得说明的是,PagedAttention 的实现并不简单——传统 attention 假设 KV Cache 连续,一次矩阵乘搞定;PagedAttention 需要按块表分块读取再算 attention。幸运的是 vLLM 已经用 CUDA kernel 优化好了这部分开销,使用者不需要关心底层实现。
Continuous Batching:请求随到随走
连续批处理(Continuous Batching)解决的是"等齐"问题。静态批处理等批满才开跑,一批内所有请求都完成才接新批,GPU 大量空转。连续批处理改为迭代级调度:
- 每个 iteration 结束后,移出已完成的请求(释放其 KV Cache block 回池)
- 从等待队列拉新请求填入空位
- 立即跑下一个 iteration
批的"成员"每个 iteration 都在变,但批的"大小"始终维持最大并发数,GPU 持续满载。更关键的是 prefill 与 decode 的混合调度:新请求入批先做 prefill(计算密集),老请求继续 decode(显存密集),同一个 iteration 里两类请求互相填补,避免了"prefill 阶段 GPU 满、decode 阶段 GPU 闲"的割裂。
- 立即跑下一个 iteration
实测效果(A100 80G 部署 Llama-3-70B,100 并发请求):
| 配置 | 吞吐 (token/s) | 显存利用率 | P99 延迟 | GPU 利用率 |
|---|---|---|---|---|
| 传统批处理 | 120 | 40% | 8.5s | 30% |
| + PagedAttention | 600 | 96% | 5.2s | 70% |
| + Continuous Batching | 1800 | 96% | 4.8s | 85% |
| + 推测解码 | 2400 | 96% | 3.1s | 88% |
一个值得深思的结论:PagedAttention 和连续批处理没有增加任何算力,只是把浪费掉的算力捡了回来——显存利用率从 40% 到 96%,意味着同一张卡能服务的并发量直接翻倍。这就是 vLLM 能"不换硬件就把吞吐提升 20 倍"的根本原因。
vLLM 部署与参数调优
vLLM 把 PagedAttention 和连续批处理封装成了开箱即用的能力,部署只需一条命令:
python-mvllm.entrypoints.openai.api_server\--modelmeta-llama/Llama-3-70b-hf\--quantizationawq\--tensor-parallel-size2\--max-model-len4096\--gpu-memory-utilization0.9```关键调优参数及决策逻辑: - **max_num_seqs**:单批最大并发请求数。显存够就调大(吞吐升),但 P99 延迟会升(排队久)。高吞吐离线批处理可以调大(256+),低延迟在线服务保持32-64 平衡。 - - **gpu_memory_utilization**:显存占用比例,默认0.9,A100 可调到0.95榨干显存。 - - **block_size**:每个 block 的 token 数,默认16是验证过的甜点,通常不动。 - - **max_model_len**:按业务最长请求设,而不是用默认最大值——预留过大的上下文窗口会浪费显存,挤占并发空间。 - - **enable_chunked_prefill**:长 prompt 场景必开。不开的话,单个长 prompt 的 prefill 会独占 GPU 一个 iteration,其他请求全部卡住。## 叠加优化:推测解码与量化的组合拳vLLM 的优化手段可以正交叠加。推测解码(Speculative Decoding)用小模型快速生成候选 token,大模型单次前向并行验证,接受的 token"免费"获得,单请求延迟可提升1.5-2 倍,且精度无损。它与连续批处理互不干扰——一个优化单请求延迟,一个优化系统吞吐,组合后两个维度同时受益。 量化则从显存维度切入:AWQ 或 GPTQ 的4-bit 量化能把 70B 模型从约 140GB 压到 40GB 以内,精度损失低于3%,让单卡 A100 就能跑 70B 模型。量化与 KV Cache 优化、批处理优化都是独立的维度,可以全部叠加。 配置示例:```python from vllmimportLLM, SamplingParams llm=LLM(model="meta-llama/Llama-3-70B",max_num_seqs=64,# 连续批处理:并发数gpu_memory_utilization=0.95,# 榨干显存enable_chunked_prefill=True,# 长 prompt 分块speculative_model="meta-llama/Llama-3-8B",# 推测解码num_speculative_tokens=5,quantization="awq",# INT4 量化)```## Prefix Caching 与语义缓存:省下重复计算的算力对于固定前缀占比高的在线服务,Prefix Caching 是又一个高性价比优化。典型场景:客服机器人每个请求都携带相同的系统提示词(角色设定 + 安全规则 + 业务规范,可能长达几百 token)。没有前缀缓存时,每次请求都要重新计算这些前缀 token 的 KV 值;启用前缀缓存后,相同前缀的 KV 直接从缓存复用,只需要计算新增的用户输入部分。 vLLM 启用前缀缓存只需一个参数:`--enable-prefix-caching`。它通过哈希匹配 token 前缀,命中时复用 KV,显著降低 prefill 阶段的计算量。实测中,固定系统提示词 + 短用户输入的场景,prefill 时间可以缩短一半以上。更进一步的语义缓存(Semantic Cache)思路是:相似问题(Embedding 相似度>0.95)直接返回缓存结果,避免重复生成。语义缓存的命中率在客服类场景可达15-20%,适合高重复、低波动的问题分布——但要注意缓存带来的时效性问题,涉及实时数据的回答不应走缓存。## 监控、成本治理与落地建议推理优化的效果最终要用监控数据说话。生产环境至少监控四类指标:硬件资源(GPU 显存使用率、算力利用率——显存过高触发 OOM,算力过低说明优化不到位);推理业务(单请求延迟、TTFT 首字延迟、token 生成速度、吞吐量、推理成功率);请求流量(QPS、超时数、限流数);异常日志(OOM、推理报错、参数异常)。配合 Prometheus + Grafana 搭建看板,设置阈值告警,才能做到"问题早发现、早定位、早修复"。 成本治理是另一个常被忽略的维度:单次对话平均成本、日/月成本趋势、按业务线分摊的成本归属,都应该有可视化看板。量化、缓存、批处理这些优化手段的收益,最终都要落到"单位成本的服务能力"这个指标上验证。推理优化没有终点,模型在升级、业务在变化、硬件在换代,优化的决策依据永远应该是当前负载下的实测数据。 落地建议按"用 vLLM → 调 max_num_seqs → 叠加推测解码 → 叠加量化"的递进路径实施。第一步直接换 vLLM 是 ROI 最高的动作——一天完成部署,立刻拿到 PagedAttention 和连续批处理带来的吞吐提升;第二步用实验找吞吐与延迟的甜点;第三步几行配置叠加推测解码;量化按显存需求按需添加。 三个核心权衡要牢记:max_num_seqs 是吞吐与延迟的跷跷板,在线服务偏小、离线批处理偏大;block_size 在碎片与调度开销之间平衡,16 是甜点;chunked_prefill 在多租户在线服务上必开,保证长 prompt 不饿死短请求。 最后提醒:生产环境要接监控——Prometheus 记录 gpu_cache_usage、num_requests_running、TTFT 分布等指标,配合自动扩缩容。推理优化的效果必须用生产负载下的实测数据说话,而不是看单个 benchmark 数字。