1. 为什么单请求跑通不等于服务可用
vLLM 部署完之后,很多人第一件事是 curl 一个请求,看到返回正常就认为大功告成。但真正上线之后才发现,QPS 一上来延迟就飙、吞吐卡在某个数字上不去,甚至偶发超时。问题不在于 vLLM 本身不行,而在于你没有在高并发下量化过它的行为边界。
这篇内容聚焦一个具体场景:用benchmark_serving.py对 vLLM 发起阶梯式并发压测,同时用rocprof采集 GPU 侧 kernel 执行数据,定位吞吐骤降和延迟毛刺到底出在哪个环节。适合已经在跑 vLLM 推理服务、想搞清楚性能天花板在哪的开发者。整条链路里,模型请求的鉴权入口我用 TaoToken 统一 Key 来管理,这样压测脚本、coding agent、日常调试都走同一套凭证,不用在多个配置文件之间来回改 base_url 和 api_key。
下面按“环境准备 → 压测脚本配置 → rocprof 采集 → 指标对比 → 排障”的顺序展开,每一步都给可复制的命令和参数。
2. TaoToken 统一 Key 的前置配置
压测脚本本身不直接调 TaoToken,但你的 vLLM 服务如果需要对上游模型做转发或对比测试,统一 Key 能省掉很多切换成本。TaoToken 的 API 入口是https://taotoken.net/api,兼容 OpenAI 风格的请求格式,所以benchmark_serving.py里如果走--backend openai模式,可以直接把 base_url 指过去。
先拿 Key:打开https://taotoken.net/api-keys?utm_source=taotoken_aicg_blog_end&utm_medium=csdn&utm_campaign=rewrite,创建一个新 Key,复制出来。然后在你常用的编辑器或终端配置里写入。以 VS Code 的settings.json为例:
{ "taotoken.apiKey": "sk-你的Key", "taotoken.baseUrl": "https://taotoken.net/api", "taotoken.defaultModel": "claude-sonnet-4-20250514" }如果你用的是命令行工具链,config.toml骨架如下:
[taotoken] api_key = "sk-你的Key" base_url = "https://taotoken.net/api" default_model = "claude-sonnet-4-20250514" timeout = 120这样配置之后,压测过程中如果需要对比不同模型在同一并发下的表现,只需要改default_model字段,不用动脚本里的请求逻辑。接入文档在https://taotoken.net/doc?utm_source=taotoken_aicg_blog_end&utm_medium=csdn&utm_campaign=rewrite,里面有完整的参数说明。
3. benchmark_serving.py 可复制启动参数
vLLM 仓库自带benchmarks/benchmark_serving.py,核心思路是模拟真实流量分布,按阶梯并发逐步加压。先确认你的 vLLM 服务已经启动,比如:
python -m vllm.entrypoints.openai.api_server \ --model /path/to/your/model \ --host 0.0.0.0 \ --port 8000 \ --tensor-parallel-size 1 \ --max-num-seqs 256 \ --max-num-batched-tokens 8192 \ --gpu-memory-utilization 0.90服务起来之后,另开一个终端跑压测。下面这组参数是我实测下来比较能暴露瓶颈的配置:
python benchmarks/benchmark_serving.py \ --backend vllm \ --base-url http://localhost:8000 \ --model /path/to/your/model \ --dataset-name sharegpt \ --dataset-path ./ShareGPT_V3_unfiltered_cleaned_split.json \ --num-prompts 2000 \ --request-rate inf \ --concurrency 1 4 8 16 32 64 128 \ --output-json result_$(date +%s).json几个关键参数说明:
--concurrency是核心,它定义同时向服务端发请求的客户端数量。从 1 开始阶梯递增,能观察到系统从空闲到饱和再到过载的完整曲线。--request-rate inf表示不限制请求速率,让并发数成为唯一变量。--num-prompts 2000保证每个并发档位有足够的样本量,避免统计噪声。
数据集建议用 ShareGPT 或你自己的业务日志,确保输入输出 token 长度分布贴近真实场景。如果手头没有 ShareGPT 文件,可以用--dataset-name random快速跑一轮,但随机数据的长度分布均匀,可能掩盖长尾问题。
跑完之后会生成一个 JSON 文件,里面包含每个并发档位的 RPS、TTFT、TPOT、Token/s 等指标。把这些数据拉出来画曲线,重点关注两个拐点:RPS 不再线性增长的点和 TTFT 开始陡升的点。
4. rocprof 采集 GPU 侧指标
压测只能告诉你“慢了”,但慢在哪需要 rocprof 来回答。在 AMD Instinct GPU 上,rocprof 可以记录每个 kernel 的执行时间、显存拷贝量和 SM 利用率。
启动 vLLM 服务时挂上 rocprof:
rocprof --stats \ -o vllm_trace \ --timestamp on \ python -m vllm.entrypoints.openai.api_server \ --model /path/to/your/model \ --port 8000 \ --max-num-seqs 256 \ --max-num-batched-tokens 8192--stats会输出汇总统计,-o指定输出文件名前缀,--timestamp on让每条记录带时间戳,方便和压测时间线对齐。服务启动后正常跑一轮benchmark_serving.py,然后停掉服务,rocprof 会生成vllm_trace.csv和vllm_trace.stats.csv。
打开 stats 文件,按耗时排序,重点看这几个 kernel:
paged_attention_kernel是 vLLM PagedAttention 的核心算子,调用最频繁。如果它的执行时间随并发数增加呈非线性上升,说明 KV Cache 的显存访问模式在高压下变得随机,显存控制器成为瓶颈。
reshape_and_cache_kernel负责把新计算的 KV 写入 cache block。如果这个 kernel 耗时占比异常高,可能是 block 分配策略有问题,碎片率过高导致频繁分配释放。
还要关注 H2D(Host to Device)拷贝的耗时。如果发现大量小尺寸拷贝,说明 CPU 侧准备输入数据的效率跟不上 GPU 消费速度,需要检查 tokenizer 是否成为瓶颈。
一个实操技巧:把 rocprof 的输出和压测的 JSON 结果按时间戳对齐,就能看到“并发数升到 64 时,paged_attention_kernel 耗时从 2ms 跳到 15ms”这种直接关联。
5. 压测前后的指标对比验证
光看单轮数据不够,需要做前后对比才能确认调优是否有效。建议按这个流程走:
第一轮,用默认参数跑 baseline,保存result_baseline.json和vllm_trace_baseline.stats.csv。然后调整 vLLM 启动参数,比如把--max-num-batched-tokens从 8192 降到 4096,或者把--block-size从 16 改成 8,重启服务,再跑一轮同样的压测,保存为result_tuned.json。
对比时重点看三个指标的变化:
| 指标 | baseline | tuned | 变化 |
|---|---|---|---|
| 并发 64 时 RPS | 12.3 | 15.8 | +28% |
| 并发 64 时 TTFT P99 | 3200ms | 1800ms | -44% |
| paged_attention_kernel 平均耗时 | 8.7ms | 5.2ms | -40% |
如果 RPS 提升但 TTFT 没降,说明吞吐上去了但排队时间没改善,可能需要开--enable-chunked-prefill。如果 TTFT 降了但 RPS 没变,说明调度策略优化了但计算资源仍是瓶颈,考虑加卡或降精度。
验证请求是否走通,可以用一个简单的 curl 确认服务状态:
curl -s http://localhost:8000/v1/models | python -m json.tool返回模型列表就说明服务正常。压测脚本跑完后,检查 JSON 里completed字段是否等于num-prompts,如果有大量失败请求,先排查服务端日志里的 OOM 或超时错误。
6. 本篇常见错排查
报错一:Connection refused或Max retries exceeded
压测脚本连不上 vLLM 服务。先确认--base-url的端口和服务启动端口一致,再检查防火墙是否放行。如果 vLLM 启动时绑的是127.0.0.1,而压测脚本在另一个容器里跑,需要改成0.0.0.0。
报错二:CUDA out of memory或HIP out of memory
并发数太高导致 KV Cache 爆显存。降低--max-num-seqs或--gpu-memory-utilization,也可以减小--max-num-batched-tokens。如果用的是 AMD GPU,确认--block-size和--swap-space配置合理,swap 太小会导致频繁换出。
报错三:rocprof 输出为空或只有 header
通常是权限问题。rocprof 需要访问 GPU 性能计数器,确认当前用户在video或render组里。另外,如果 vLLM 是以 daemon 方式启动的,rocprof 可能挂不上去,建议用前台进程跑。
报错四:TTFT 正常但 TPOT 很高
首字延迟低说明 prefill 阶段没问题,但每个输出 token 的生成时间高,通常是 decode 阶段的计算或显存带宽瓶颈。检查paged_attention_kernel的耗时,如果它占了大头,尝试减小--block-size提高显存访问局部性。
报错五:压测结果波动大,同一并发两次跑差异超过 20%
检查后台是否有其他进程占用 GPU,比如另一个推理服务或训练任务。用rocm-smi确认 GPU 利用率基线。另外,--num-prompts太小会导致统计不收敛,建议至少 1000 条以上。
7. 继续压榨性能的下一步
跑完这一轮,你手里应该有了三条曲线:并发-RPS、并发-TTFT、并发-Token/s,以及一份 rocprof 的 kernel 耗时排名。接下来可以做的:把--enable-chunked-prefill打开,观察 P99 延迟是否改善;尝试--quantization fp8看吞吐能提升多少;或者用 TaoToken 的模型对话入口快速对比不同模型在同一压测配置下的表现,入口在https://taotoken.net/chat?utm_source=taotoken_aicg_blog_end&utm_medium=csdn&utm_campaign=rewrite。
如果你打算把压测流程固化到 CI 里,长期跑编码 agent 做自动化回归,可以看看 Coding Plan 的配置方式:https://taotoken.net/coding-plan?utm_source=taotoken_aicg_blog_end&utm_medium=csdn&utm_campaign=rewrite。控制台在https://taotoken.net/console?utm_source=taotoken_aicg_blog_end&utm_medium=csdn&utm_campaign=rewrite,里面可以看 Key 的调用量和余额。
性能优化没有终点,但每一次基于数据的调整都比盲目改参数靠谱。先把 baseline 跑出来,再谈优化。