vLLM系统拆解-14-部署调优:从资源预算到线上问题诊断
vLLM系统拆解-14-部署调优:从资源预算到线上问题诊断
前几篇把 vLLM 的核心机制讲完了:PagedAttention、scheduler、prefix caching、chunked prefill、分布式并行……但这些理解停留在"设计层面",还不够。
推理系统真正的问题从来不是"这个设计对不对",而是:在当前这张 GPU、这个模型、这类请求、这个 QPS 下,哪里是瓶颈?应该调哪个参数?为什么改了之后反而更慢?
这篇文章的出发点是:vLLM 调优本质上是在给系统设置资源预算,而不是调几个孤立参数。围绕这个视角,会讲清楚关键指标、核心参数的系统含义、OOM 和 preemption 的诊断思路,以及不同 workload 对应的策略差异。
一次请求消耗哪些资源
调优之前,先要搞清楚一次推理请求到底会消耗什么。
1 | ┌──────────────────────────────────────────────────────────┐ |
线上性能不好,不能条件反射地说"GPU 不够"。常见的真实原因包括:GPU 显存不够导致 KV Cache 撑不住;decode 太多,GPU 不缺 FLOPS,缺的是显存带宽;batch 开得太大,吞吐上去了但 TTFT 飙升;CPU 调度跟不上,GPU 有余量但服务就是慢。
部署 vLLM,本质上是在给系统设置几个预算:KV Cache 可以用多少显存、每轮最多处理多少 token、每轮最多并发多少条序列、prefill 和 decode 如何共享批处理预算。这些预算通过几个关键参数体现出来。
先看指标,再动参数
调优最常见的低效做法是:不清楚瓶颈在哪,上来就改参数,结果越调越乱。
先要稳定监控四类指标。
延迟:TTFT 与 ITL
首 token 延迟(Time To First Token,TTFT) 反映用户发请求后多久看到第一个 token。主要影响因素:prompt 长度、prefill 负载、当前排队情况、max_num_batched_tokens 是否过大挤压 decode、prefix caching 命中率。低延迟交互场景(chat、Copilot)TTFT 是核心指标。
token 间延迟(Inter-Token Latency,ITL) 反映相邻输出 token 的平均间隔,直接决定用户感知的输出流畅度。主要影响因素:decode batch 是否过大、chunked prefill 是否给 decode 足够优先级、speculative decoding 有效性、GPU 是否 memory-bound。
TTFT 和 ITL 经常相互制约——为了降 TTFT 可能需要更快进入 prefill,但这可能挤占 decode,反而拉高 ITL。
吞吐:request throughput vs token throughput
做 infra 更常用 token throughput(单位时间处理多少输入/输出 token),因为它更直接反映系统真实工作量。
要注意:高 token throughput 不代表用户体验好。很多提吞吐的设置会把 TTFT/ITL 拉坏;吞吐优化往往意味着更激进的 batch、更高的资源利用率、更强的尾延迟风险。评估吞吐提升时,要同时说清楚代价是什么,适用于什么场景。
KV Cache 与显存
重点关注:GPU 显存总体使用率、KV Cache 占用情况、是否发生 preemption / recomputation、当前 block 使用率是否过高。
频繁 preemption 通常意味着:KV Cache 空间不够、并发太高、请求太长、相关参数设置过激进,或 gpu_memory_utilization 给 KV Cache 的预算太保守。这类问题靠"继续加 batch"解决不了,往往要先收缩预算。
CPU 指标
这一点最容易被忽视。vLLM 不是纯 GPU 程序,它是 API server + engine core + worker 的多进程服务系统。CPU 负责 HTTP 收发、tokenization/detokenization、engine core 调度循环、请求状态维护。
CPU 不足时的典型现象:GPU 利用率不稳定,延迟抖动大,QPS 上不去,明明 GPU 还有余量但服务就是跑不快。面试里提到这一点会很加分,因为很多人把 vLLM 理解成"纯 GPU 优化框架",忽视了它是多进程服务系统这个基本事实。
五个核心参数的系统含义
不需要背所有启动参数,但以下五个参数必须真正理解——它们控制的是整体资源格局。
| 参数 | 控制的本质 | 调高效果 | 调低效果 | 风险 |
|---|---|---|---|---|
gpu_memory_utilization |
实例级显存预算 | KV Cache 空间增加,可承载更多并发/长上下文 | KV Cache 收紧,preemption 风险上升 | 过高会挤压其他必要显存,多实例共卡时尤危险 |
kv_cache_memory_bytes |
直接指定 KV Cache 字节数 | 更精细的缓存预算控制 | — | 需要准确估算权重和 buffer 占用 |
max_num_batched_tokens |
每轮调度的 token 预算上限 | 吞吐更高,prefill 更容易吃满 | decode 优先级更高,ITL 更好 | 过大会恶化 TTFT/ITL |
max_num_seqs |
单轮并发请求条数上限 | 并发能力更强 | 调度和管理开销降低 | 太大会增加状态维护开销 |
max_model_len |
最大上下文长度上界 | 功能上支持更长输入 | KV Cache 压力降低,并发能力上升 | 按模型理论上限设会大幅压缩可服务并发 |
几点补充说明:
max_num_batched_tokens 不是"batch 大小"的同义词。它约束的是每轮总 token 工作量,而不是请求条数。在长 prompt 场景下,一个请求就可能消耗大量预算。max_num_seqs 和它是不同维度:前者更像限制 batch 的宽度(请求条数),后者更像限制 batch 的体积(token 总量)。
max_model_len 是容量规划参数,不是功能参数。把它设为模型理论上限(如 128K)会按最坏情况分配 KV Cache 预算,显著压缩可服务并发。生产环境里应按真实 workload 的 P99 长度来设,不是追求理论最大上下文。
kv_cache_memory_bytes 是 gpu_memory_utilization 的精细化版本。后者是比例配置,前者直接指定字节数。多实例同卡、固定化部署、容量规划压测等场景,精确字节配置比比例配置更可控。
OOM 三分类
很多人把 OOM 理解为"显存不够",这太粗了。把 OOM 分成三类,诊断思路会清晰很多。
模型权重层面 OOM
模型太大、TP 不够、量化没做、单卡装不下。这种 OOM 通常在启动或初始化时就出现,属于静态问题。解决方向:更小模型、量化、增加 tensor parallel、换更大显存 GPU。
KV Cache 容量层面 OOM
模型已经起来,但随着请求积累——prompt 很长、输出也长、并发多、prefix cache 和 active requests 同时占空间——KV Cache 被撑爆。这是 vLLM 更常见也更关键的 OOM 来源。
处理方向:提高 gpu_memory_utilization / 精确设置 kv_cache_memory_bytes、降低 max_num_batched_tokens / max_num_seqs、降低 max_model_len、控制请求长度和并发规模。本质是调整"缓存预算"。
运行时峰值 OOM
不是常驻内存不够,而是某轮执行时的瞬时峰值太高——某次 prefill 特别大、某个 batch 请求组合极端、某些 kernel / buffer 临时分配冲高。这种 OOM 可能不是每次都复现,比静态 OOM 更难排查。
处理方向:降低单轮 token 预算、限制异常长请求、缩小 batch 规模、保留足够的显存安全余量。
Preemption 意味着什么
在 vLLM 日志或监控里看到 preemption,不要只把它当成一条 warning。
它的本质信号是:当前配置下,系统已经无法同时优雅容纳所有活跃请求的 KV Cache 需求,开始用代价更高的方式维持运行(V1 默认是 RECOMPUTE,即丢弃并重算)。
Preemption 往往伴随:recomputation 带来的时延上升、吞吐波动、尾延迟恶化。偶发 preemption 可以接受,但高频 preemption 通常说明配置不健康,workload 和资源预算之间出现了张力。
看到频繁 preemption 时,排查顺序:
- KV Cache 预算是否太紧(
gpu_memory_utilization过低) - 并发是否压得太高(
max_num_seqs过激进) - 请求长度分布是否超出预期
max_num_batched_tokens是否开太大max_model_len是否定得远超真实 workload
CPU 瓶颈:容易被忽视的性能天花板
GPU 看起来有余量,但服务跑不快——这个现象在 vLLM 里不少见,根因往往在 CPU。
vLLM 的 engine core 本身是一个高频 busy loop 型调度核心,持续在 CPU 上跑请求调度、block 管理、batch 组装。同时 API server 还要处理 HTTP 请求、做 tokenization 和 detokenization,这些全都是 CPU 密集操作。
如果物理 CPU 核数不够、被其他进程抢占严重、或者 NUMA 绑定不合理,调度效率就会下降,最终表现为:
1 | GPU 利用率忽高忽低 → 调度循环不及时,GPU 被饿着 |
部署时 CPU 核数与进程数的匹配和 GPU 的容量规划同样重要,不能只看 GPU 规格。
不同 Workload 的调优策略
这是调优里最容易忽视的地方:不同业务目标,最优配置完全不同,甚至可能相反。
| Workload 类型 | 核心目标 | 关键参数方向 | 特别注意 |
|---|---|---|---|
| 低并发交互型(chat / Copilot) | TTFT 低、ITL 低、输出流畅 | max_num_batched_tokens 偏保守,不追求峰值吞吐 |
prefix caching 在系统 prompt 高复用时价值大;speculative decoding 值得试验 |
| 高并发吞吐型(API 平台 / 批处理) | token throughput 高、GPU 利用率高、单位成本低 | max_num_batched_tokens 可更激进,接受 TTFT 上升 |
严密监控 preemption 和尾延迟;吞吐和交互体验不是一套最优配置 |
| 长上下文型(文档 QA / 代码仓库分析) | 支撑长上下文、KV Cache 不爆、系统稳定 | max_model_len 按真实需求设,提高 KV Cache 预算,不追求高并发 |
长上下文最先打到的是缓存容量和访存压力,而不是算力 |
| 多租户 / 多实例同卡 | 资源隔离、峰值不互相干扰、稳定性 | 不要把 gpu_memory_utilization 拉满,精确显存预算,严格容量规划 |
平均可用 ≠ 峰值安全;多实例峰值叠加是主要风险 |
调优顺序:先稳,再快
乱试参数是最低效的调优方式。一套有效的调优顺序应该是:
flowchart TD
A[第一步:定义目标\n优化 TTFT?ITL?吞吐?稳定性?] --> B
B[第二步:测量 workload 特征\n平均/P99 prompt 长度、输出长度\n峰值并发、前缀重复率、请求类型] --> C
C[第三步:容量安全\n模型稳定起来\n不频繁 OOM\npreemption 不失控\nCPU 核数与进程匹配] --> D
D[第四步:调主干参数\ngpu_memory_utilization / kv_cache_memory_bytes\nmax_num_batched_tokens\nmax_num_seqs\nmax_model_len] --> E
E[第五步:叠高级优化\nprefix caching 命中率\nspeculative decoding 有效性\n量化引入\n分布式并行策略]
核心原则:先定义目标,再看指标,先求稳,再求快。用别人的 benchmark 参数直接照搬在自己场景往往无效,因为短 prompt 高频 chat 和长上下文文档问答,最优配置完全可能相反。
五问诊断框架
遇到线上性能问题,强制自己按这五个问题想,大多数问题都会变得有条理:
1. 计算瓶颈还是缓存/访存瓶颈?
Prefill 更可能算力敏感;decode 更可能 memory-bound。不同瓶颈对应完全不同的优化方向。
2. GPU 瓶颈还是 CPU 瓶颈?
GPU 很闲但延迟很高,不一定是 GPU 问题。先看 engine core 和 tokenization 的 CPU 占用。
3. Steady-state 容量问题还是峰值问题?
平时都撑不住,还是只有长尾请求冲高时才出问题?前者要调基础容量,后者要留安全余量。
4. 参数问题还是 workload 问题?
当前配置不合理,还是用户请求长度/并发分布本身太极端?后者靠调参解决不了,可能需要限流或容量扩充。
5. 想更快还是想更稳?
提吞吐时通常不能同时保延迟;保稳定时往往要留 buffer。目标不清晰,调优没有终点。
线上高频问题的诊断要点
把几个常见面试问题的分析思路整理在这里,可以直接对照实际场景使用。
线上频繁 preemption,怎么分析?
先把它当成 KV Cache 压力信号,依次排查:KV Cache 预算是否过小、并发是否过高、请求长度分布是否超出预期、max_num_batched_tokens/max_num_seqs 是否过激进、max_model_len 是否定得太大。高频 preemption 说明 workload 和资源预算出现了张力。
GPU 没打满但服务很慢?
先排查 CPU:engine core 调度循环是否饥饿、tokenization 是否是瓶颈、CPU 核数是否和进程数匹配。其次看是否有 preemption 导致 recomputation,以及是否有异常长请求拖慢整个 batch。
OOM 如何定位是哪类?
看 OOM 出现时机:启动/初始化时出现 → 模型权重层面;运行一段时间后随请求积累出现 → KV Cache 容量层面;偶发不规律 → 运行时峰值层面。不同类型的处理方向完全不同。
为什么不能把 max_model_len 开到模型上限?
因为 max_model_len 决定系统按多坏的情况预留 KV Cache。把它设到模型理论上限(如 128K)意味着系统要为每个请求预留极大的最坏情况缓存,会显著压缩可服务并发。生产环境里应按真实 workload 的 P99 长度来设。
核心结论
调优思维可以收束为四句话:
- vLLM 调优是在调资源预算,不是在调几个孤立参数——每个参数背后都有它影响的资源格局和机制链路。
- 吞吐、TTFT、ITL、显存容量、并发能力之间天然存在张力,不可能同时极致,调优本质上是在给定业务目标下找平衡点。
- 线上问题很多不是 GPU 算不动,而是 KV Cache 容量不足、CPU 调度跟不上、或 workload 形态和参数设置不匹配。
- 成熟的调优方式是先定义目标、看指标、做容量安全,再逐层优化——先稳,再快。
