vLLM系统拆解-14-部署调优:从资源预算到线上问题诊断

前几篇把 vLLM 的核心机制讲完了:PagedAttention、scheduler、prefix caching、chunked prefill、分布式并行……但这些理解停留在"设计层面",还不够。

推理系统真正的问题从来不是"这个设计对不对",而是:在当前这张 GPU、这个模型、这类请求、这个 QPS 下,哪里是瓶颈?应该调哪个参数?为什么改了之后反而更慢?

这篇文章的出发点是:vLLM 调优本质上是在给系统设置资源预算,而不是调几个孤立参数。围绕这个视角,会讲清楚关键指标、核心参数的系统含义、OOM 和 preemption 的诊断思路,以及不同 workload 对应的策略差异。


一次请求消耗哪些资源

调优之前,先要搞清楚一次推理请求到底会消耗什么。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
┌──────────────────────────────────────────────────────────┐
│ 一次在线推理请求的资源消耗 │
│ │
│ GPU 计算资源 prefill 的大规模矩阵乘法 │
│ decode 在高并发时也有计算压力 │
│ │
│ GPU 显存 模型权重常驻(静态) │
│ KV Cache 随历史长度增长(动态) │
│ 中间激活 / workspace / 采样开销(临时) │
│ │
│ GPU 访存带宽 decode 反复读 KV Cache │
│ → decode 往往是 memory-bound │
│ │
│ CPU 资源 HTTP 收发 / tokenization / detokenization │
│ engine core 调度循环(高频 busy loop) │
│ worker 进程状态管理 │
└──────────────────────────────────────────────────────────┘

线上性能不好,不能条件反射地说"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_bytesgpu_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 时,排查顺序:

  1. KV Cache 预算是否太紧(gpu_memory_utilization 过低)
  2. 并发是否压得太高(max_num_seqs 过激进)
  3. 请求长度分布是否超出预期
  4. max_num_batched_tokens 是否开太大
  5. 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
2
3
4
GPU 利用率忽高忽低  → 调度循环不及时,GPU 被饿着
延迟抖动大 → 调度响应不稳定
QPS 上限低 → tokenization / detokenization 是瓶颈
GPU 有余量但慢 → CPU 本身是天花板

部署时 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 长度来设。


核心结论

调优思维可以收束为四句话:

  1. vLLM 调优是在调资源预算,不是在调几个孤立参数——每个参数背后都有它影响的资源格局和机制链路。
  2. 吞吐、TTFT、ITL、显存容量、并发能力之间天然存在张力,不可能同时极致,调优本质上是在给定业务目标下找平衡点。
  3. 线上问题很多不是 GPU 算不动,而是 KV Cache 容量不足、CPU 调度跟不上、或 workload 形态和参数设置不匹配。
  4. 成熟的调优方式是先定义目标、看指标、做容量安全,再逐层优化——先稳,再快。

系列导航