Administrator
发布于 2026-07-07 / 3 阅读
0
0

vLLM 性能监控要看哪些指标

vLLM 性能监控要看哪些指标

English Version

vLLM 服务跑起来,只是第一步。

真正上线前,还有一些内容:首 token 慢不慢、生成过程稳不稳、并发上来以后吞吐能不能涨、P99 会不会突然炸、KV Cache 有没有逼近显存上限。

这些问题如果没有监控,只靠“我感觉还行”,上线以后一定会吃亏。

先把服务用可控参数启动

按场景分了四类:基础验证、高吞吐、低延迟、省显存。这种拆法很实用,因为不同目标下参数取舍完全不一样。

比如高吞吐配置会倾向于增大并发序列数、提高显存利用率、开启前缀缓存:

# 中文注释:高吞吐模式通过增大 batch 和并发序列数,提高 tokens/s
python -m vllm.entrypoints.openai.api_server \
  --model /root/autodl-tmp/models/qwen/Qwen3-0.6B \
  --port 8000 \
  --dtype float16 \
  --max-model-len 4096 \
  --max-num-seqs 64 \
  --max-num-batched-tokens 4096 \
  --gpu-memory-utilization 0.92 \
  --enable-prefix-caching \
  --trust-remote-code

如果目标是低延迟,就会更关注 Chunked Prefill 和 batch 上限:

# 中文注释:低延迟模式限制批处理规模,并开启分块预填充降低长 prompt 阻塞
python -m vllm.entrypoints.openai.api_server \
  --model /root/autodl-tmp/models/qwen/Qwen3-0.6B \
  --port 8000 \
  --dtype float16 \
  --max-num-seqs 16 \
  --max-num-batched-tokens 512 \
  --enable-chunked-prefill \
  --gpu-memory-utilization 0.90 \
  --trust-remote-code

Chunked Prefill(分块预填充):把长 prompt 的 Prefill 计算拆成小块,让它和 decode 请求交错执行。它解决的是长 prompt 独占 GPU、导致其他请求首 token 变慢的问题。

Prefix Caching(前缀缓存):复用相同 prompt 前缀的 KV Cache。多轮对话、固定系统提示词、RAG 模板都很适合开启。

TTFT 和 TPOT 要分开测

我不建议只看端到端耗时,因为它会把不同阶段混在一起。

TTFT(Time To First Token):首 token 延迟,主要反映 Prefill 阶段、排队时间和冷启动影响。

TPOT(Time Per Output Token):相邻输出 token 的时间间隔,主要反映 Decode 阶段是否稳定。

代码里用流式输出测这两个指标,思路很直接:记录请求开始时间,再记录每个 chunk 到达的时间。

# 中文注释:发送流式请求,逐个接收 token,并记录每个 token 到达时间
stream = client.chat.completions.create(
    model=MODEL_NAME,
    messages=messages,
    stream=True,
    max_tokens=max_tokens,
    temperature=0.7,
)

# 中文注释:第一个 token 的时间点用于计算 TTFT,后续相邻时间差用于计算 TPOT
for chunk in stream:
    if chunk.choices[0].delta.content:
        now = time.perf_counter()
        token_timestamps.append(now)

有一个很好的细节:短 prompt 的 TTFT 反而最高,达到 355ms;中 prompt 和长 prompt 只有 40ms、63ms。

这看起来违反直觉,但原因是冷启动。第一个请求时 CUDA kernel 预热、内存分配、运行时初始化都会增加额外延迟。生产环境里通常会发预热请求,避免首批真实用户承担这段成本。

并发压测要找拐点

并发压测的目的不是证明“并发越高越好”,而是找到吞吐和尾延迟的拐点。

代码里的压测逻辑是用线程池发起多个请求,然后统计 QPS、tokens/s 和延迟分位数:

# 中文注释:用线程池模拟指定并发度,同时向 vLLM OpenAI 接口发送请求
with concurrent.futures.ThreadPoolExecutor(max_workers=concurrency) as executor:
    futures = [
        executor.submit(send_chat_request, prompt, max_tokens)
        for prompt in prompts
    ]

    # 中文注释:每个请求完成后收集结果,用于后续计算延迟分位数和吞吐量
    for future in concurrent.futures.as_completed(futures):
        all_results.append(future.result())

教结果很适合做基准判断:

并发度QPS吞吐量 tok/sP50 延迟P99 延迟
11.02101.0899.8ms1453.9ms
22.02201.6985.1ms1048.2ms
43.85385.41018.2ms1128.3ms
86.35635.01037.3ms1101.5ms
169.87987.21520.6ms2085.3ms

我会这样解读:并发从 1 到 8,吞吐提升明显,P50 基本稳定,这是 Continuous Batching 在发挥作用。并发到 16 时,吞吐还在涨,但 P50 和 P99 都明显上升,说明系统已经开始接近饱和。

如果这是线上服务,我不会只看最高 tokens/s。我会根据业务能接受的 P99,选择一个更稳的并发上限。

Prometheus 指标要接入常态监控

vLLM 可以暴露 Prometheus 格式的 /metrics。这比压测脚本的单次输出更适合长期观察。

Prometheus(普罗米修斯):常见的监控指标采集系统,服务暴露文本格式指标,Prometheus 定期抓取并存储,Grafana 可以基于这些数据画图。

代码里解析的关键指标包括:

# 中文注释:定义需要从 Prometheus 文本中提取的 vLLM 核心指标
patterns = {
    "prompt_tokens_total": r"vllm:prompt_tokens_total[^\n]*?\s+([\d.]+)",
    "generation_tokens_total": r"vllm:generation_tokens_total[^\n]*?\s+([\d.]+)",
    "num_requests_running": r"vllm:num_requests_running\s+([\d.]+)",
    "num_requests_waiting": r"vllm:num_requests_waiting\s+([\d.]+)",
    "gpu_cache_usage_perc": r"vllm:gpu_cache_usage_perc\s+([\d.]+)",
}

我会重点看这些指标:

num_requests_running:当前正在执行的请求数。它能反映 batch 是否真正跑起来。

num_requests_waiting:排队等待请求数。如果长期增长,说明入口流量超过服务处理能力。

gpu_cache_usage_perc:GPU KV Cache 使用率。接近上限时,要警惕 swapping、OOM 或尾延迟上升。

prompt_tokens_totalgeneration_tokens_total:输入和输出 token 累计量。结合时间窗口可以估算吞吐。

time_to_first_token_secondstime_per_output_token_seconds:分别对应 TTFT 和 TPOT 的分布。

负载前后对比显示,20 个并发请求让生成 token 从 21634 增加到 24634,增量 3000。持续监控时每 2 秒新增约 900 tokens,也就是实际吞吐约 450 tok/s。

这类时间序列比单次压测结果更有价值,因为它能看到负载期间是否抖动。

常见瓶颈可以按现象定位

如果 TTFT 过高,我会先看 prompt 长度、等待队列和 Prefill 是否阻塞 decode。

常见处理方式是开启 Chunked Prefill、开启 Prefix Caching,或者进一步做 PD 分离。

PD 分离(Prefill-Decode Disaggregation):把 Prefill 和 Decode 拆到不同资源或不同服务上执行。它解决的是长 prompt 的 Prefill 计算阻塞短请求 decode 的问题。

如果 TPOT 抖动大,我会看 batch size 是否波动太大、推测解码接受率是否下降、是否存在抢占或换出。

如果 GPU 利用率低,但显存占用很高,这通常不是“机器不努力”,而是 memory-bound。此时可以考虑增大 --max-num-seqs、使用 FP8 KV Cache、缩短上下文,或者在小 batch 场景启用推测解码。

FP8 KV Cache:用更低精度保存 KV Cache,目标是节省显存和提升吞吐。它通常会带来很小的精度影响,但生产上仍要结合业务结果验证。

小结

vLLM 调优不是一组固定参数,而是一套监控闭环。

我会按这个顺序做:

  1. 用基础配置确认服务能正常返回。
  2. 用流式请求拆开测 TTFT 和 TPOT。
  3. 用并发压测找到吞吐和 P99 的拐点。
  4. 用 Prometheus 长期观察队列、KV Cache、token 吞吐和延迟分布。
  5. 根据瓶颈选择高吞吐、低延迟、省显存或推测解码方案。

做到这一步,调优才不是玄学。每个参数为什么改、改完有没有收益,都能用指标说清楚。


评论