4. 性能分析工具:采集vLLM吞吐瓶颈的第一手数据

在定位吞吐瓶颈时,最忌讳的就是“拍脑袋”猜测。GPU利用率低,究竟是显存带宽受限、计算单元闲置,还是CPU来不及喂数据?KV Cache命中率低,是调度策略问题还是显存容量不足?这些问题必须依靠分层采集的性能数据来回答。本章围绕vLLM内置监控、Profiling工具和系统级工具三个层次,讲解如何建立一套完整的性能数据采集体系。

4.1 核心原理:三层数据采集模型

vLLM推理系统的性能瓶颈可能出现在GPU计算层、显存管理层、CPU调度层或网络通信层。单一工具只能看到局部,必须分层采集、交叉验证。下图展示了三层工具的分工与配合关系:

vLLM性能数据采集三层架构 ① vLLM内置监控(应用层) · /metrics 端点:吞吐量、KV Cache使用率、调度延迟、队列长度 · --verbose 日志:每次请求的TTFT、TPOT、prefill/decode耗时分解 · 适用:快速判断“服务整体表现”,定位调度与显存管理问题 ② Profiling工具(框架/内核层) · PyTorch Profiler:torch.profiler 捕获算子级耗时、CUDA kernel时间线 · Nsight Systems/Compute:GPU kernel占用率、显存带宽利用率、warp状态 · 适用:定位“哪个算子/内核”是瓶颈,区分计算密集与访存密集 ③ 系统级工具(OS/硬件层) · nvidia-smi:GPU利用率、显存占用、温度、功耗、PCIe吞吐 · perf / py-spy:CPU热点、Python GIL竞争、内核态系统调用 · 适用:判断CPU是否成为瓶颈、GPU是否真正“忙”而非“等” 数据粒度 粗 → 请求级 中 → 算子级 细 → 指令级 耗时成本 低 → 高 生产环境 可用性 高 → 低

三层工具并非互相替代,而是从不同时间粒度和抽象层级回答不同问题。vLLM内置监控告诉你“服务有多慢”,Profiling告诉你“GPU在忙什么”,系统级工具告诉你“CPU和GPU之间谁在拖后腿”。

4.2 方法流程:从粗粒度到细粒度的诊断路径

第一步:vLLM内置监控——建立吞吐基线

启动vLLM服务时,通过 --metrics-port 暴露Prometheus格式的监控端点。核心指标包括:

  • vllm:num_requests_running:当前正在执行的请求数,反映GPU实际负载
  • vllm:cache_usage:KV Cache使用率,接近1.0说明显存吃紧,可能触发swap
  • vllm:time_to_first_token_seconds:TTFT直方图,反映prefill阶段延迟
  • vllm:time_per_output_token_seconds:TPOT直方图,反映decode阶段吞吐
  • vllm:e2e_request_latency_seconds:端到端延迟,包含排队等待时间

采集命令示例:

# 启动vLLM时开启metrics
python -m vllm.entrypoints.openai.api_server \
    --model meta-llama/Llama-3.1-8B-Instruct \
    --gpu-memory-utilization 0.9 \
    --max-num-seqs 256 \
    --metrics-port 8001

# 从另一终端采集指标
curl -s http://localhost:8001/metrics | grep -E "vllm:(num_requests_running|cache_usage|time_per_output_token)"
判断要点:如果 num_requests_running 长期低于 max-num-seqs,说明服务端没有足够的并发请求来填满GPU;如果 cache_usage 持续接近1.0,则说明KV Cache容量是限制吞吐的瓶颈,需要调整 --max-num-seqs 或 --gpu-memory-utilization。

第二步:Profiling工具——定位算子级瓶颈

当内置监控确认GPU利用率不高但吞吐上不去时,需要用Profiling工具查看GPU内部到底在做什么。推荐使用PyTorch Profiler进行快速采样:

import torch
from torch.profiler import profile, ProfilerActivity

# 在vLLM的推理循环中包裹关键步骤
with profile(
    activities=[ProfilerActivity.CPU, ProfilerActivity.CUDA],
    record_shapes=True,
    profile_memory=True,
    with_stack=True
) as prof:
    # 执行一次batch推理(模拟vLLM的prefill+decode)
    output = model.generate(input_ids, max_new_tokens=128)

# 按CUDA时间排序,输出Top 10耗时算子
print(prof.key_averages().table(
    sort_by="cuda_time_total",
    row_limit=10,
    top_level_events_only=True
))

输出中重点关注两类算子:

  • 访存密集型算子(如 reshape、copy、concat):如果这些算子占比高,说明数据搬运开销大,可能由KV Cache碎片化或连续张量拼接引起
  • 计算密集型算子(如 matmul、attention):如果这些算子占比高但GPU利用率低,可能是batch size太小,未充分占用SM

第三步:系统级工具——排除CPU与I/O瓶颈

使用 nvidia-smi 和 perf 检查GPU与CPU的协同状态:

# 每0.5秒刷新一次GPU状态
watch -n 0.5 nvidia-smi --query-gpu=utilization.gpu,utilization.memory,power.draw,temperature.gpu,pcie.link.gen.current,pcie.link.width.current --format=csv

# 采集CPU热点(需要root权限)
sudo perf top -p $(pgrep -f vllm) --sort comm,dso

# 检查Python GIL竞争(如果使用py-spy)
py-spy record -p $(pgrep -f vllm) -o cpu_profile.svg --duration 30

关键判断逻辑:

现象 可能瓶颈 下一步动作
GPU利用率高(>95%)但吞吐低 计算kernel本身效率低或访存模式差 用Nsight Compute分析kernel的memory throughput
GPU利用率波动大(30%~80%跳变) CPU来不及准备batch数据 检查CPU线程数、--num-scheduler-steps是否过小
GPU利用率低(<30%)且显存占用高 KV Cache碎片化或等待swap 查看vLLM日志中的swap次数,调整block大小
PCIe吞吐持续打满 CPU与GPU间数据拷贝频繁 检查是否启用了zero-copy或pinned memory

4.3 案例:一次真实的多层诊断过程

某团队部署Llama-3.1-70B服务,目标吞吐500 tokens/s,实际仅达到180 tokens/s。他们按以下流程排查:

  1. vLLM内置监控:发现 num_requests_running 平均只有32(max-num-seqs设为128),cache_usage 仅0.4。说明GPU远未满载,且KV Cache还有大量空闲。
  2. PyTorch Profiler:发现 aten::copy_ 算子占CUDA总时间的28%,reshape 占12%。大量时间花在张量搬运而非计算上。
  3. nvidia-smi:GPU利用率仅35%,但显存带宽利用率达到82%。说明kernel是访存受限而非计算受限。
  4. perf:CPU侧发现 memcpy 系统调用频繁,且vLLM的Python调度线程占用单核100%。

最终定位:vLLM默认的block大小为16个token,当请求长度差异大时,KV Cache碎片化严重,导致每次attention都要做大量非连续内存的拷贝。将 --block-size 调整为32,并开启 --enable-prefix-caching 后,吞吐提升至420 tokens/s。

4.4 误区与复盘:数据采集中的常见陷阱

误区一:只看GPU利用率,忽略“假忙”状态。 GPU利用率100%并不代表计算单元都在做有用功。如果kernel是访存密集型的,SM大部分时间在等待数据返回,此时利用率高但吞吐低。必须结合 nvidia-smi 中的 utilization.memory 和Profiling中的 memory throughput 综合判断。
误区二:在Profiling时改变负载特征。 开启PyTorch Profiler会显著降低吞吐(通常下降30%~50%),如果此时并发请求数不变,会导致排队加剧,采集到的数据反映的是“Profiling干扰下的系统状态”,而非真实瓶颈。建议在低峰期采集,或使用 torch.profiler 的 schedule 参数设置on/off周期,减少干扰。
误区三:忽略CPU侧的采集。 很多团队只盯着GPU数据,但vLLM的调度器、tokenizer、采样器都在CPU上运行。如果CPU单核跑满,GPU就会因“断粮”而空闲。务必同时采集CPU使用率(top 或 perf),并关注 vllm:num_requests_waiting 指标——如果等待队列长期非空,说明CPU调度速度跟不上GPU消费速度。
复盘要点:性能数据采集不是一次性的动作,而应形成持续基线。建议每次修改配置或升级版本后,都跑一遍标准化的benchmark脚本,记录三层工具的关键指标到日志系统。当吞吐下降时,先对比基线数据,能快速缩小排查范围——是代码变更导致kernel变慢,还是负载特征变化导致调度效率下降,还是系统资源被其他进程抢占。

掌握三层工具的使用,等于为vLLM装上了“CT机”。下一层的数据回答“是什么”,上一层的数据回答“为什么”,交叉验证后,吞吐瓶颈的根因往往无处遁形。在实际调优中,建议按照“内置监控 → Profiling → 系统工具”的顺序逐层深入,每层都记录数据后再决定是否进入更细粒度的分析,避免一上来就陷入算子级排查的泥潭。