1. 项目概述为什么我们需要对 vLLM OpenAI API 进行压力测试在部署基于 vLLM 的大模型推理服务时很多开发者会直接使用其内置的 OpenAI API 兼容接口。这个接口用起来确实方便一行curl命令就能调用感觉和调用官方的 OpenAI 服务没什么两样。但当你把服务部署上线准备迎接真实流量时心里总会有点没底我这个服务到底能扛住多少并发响应延迟在压力下会变成什么样内存会不会因为请求堆积而爆掉这些问题单靠功能测试是回答不了的必须得上压力测试。我最近就在调优一个部署在内部集群的 Qwen2-72B 模型服务用的就是 vLLM 的 OpenAI API 接口。在开发环境跑得好好的一到预发布环境模拟几个用户同时提问服务响应就开始不稳定甚至偶尔超时。这时候一个轻量级但威力强大的命令行工具wrk就派上了用场。它不像 JMeter 那样需要复杂的 GUI 配置写个简单的 Lua 脚本就能模拟出高并发的 HTTP POST 请求直击我们服务的性能瓶颈。通过分析wrk输出的详细报告我们不仅能得到每秒处理请求数QPS、平均延迟这些宏观指标更能结合 vLLM 自身的监控指标比如vllm:request_latency深入分析从请求进入、模型推理到结果返回的全链路性能找到到底是网络层、推理引擎还是 GPU 算力成了拖后腿的环节。这次我就把整个压力测试的设计思路、具体操作步骤、结果分析方法以及踩过的坑完整地分享出来。2. 测试环境与工具选型解析2.1 为什么选择wrk而不是 JMeter 或 Locust压力测试工具有很多JMeter 功能全面Locust 可以用 Python 写脚本都很强大。但我最终选择wrk主要是基于以下几个实际考量极致轻量与高性能wrk是用 C 语言开发的它本身对系统资源的消耗极低。这意味着在施压过程中测试工具本身不会成为性能瓶颈也不会因为占用过多 CPU 或内存而干扰我们对被测服务vLLM的观测。相比之下基于 JVM 的 JMeter 在发起高并发时自身开销就不容忽视。学习成本低上手快wrk的核心命令非常简单通常就是wrk -t 线程数 -c 连接数 -d 持续时间 -s 脚本路径 URL。复杂的逻辑比如构造带有不同参数的 POST 请求体可以通过 Lua 脚本实现。对于我们测试固定的 API 接口写一个脚本后就可以反复使用。结果清晰直观wrk的输出报告直接给出了 Latency延迟分布和 Requests/sec每秒请求数这些正是评估 API 性能最关键的指标。它没有花哨的图表但数据非常直接有用。当然wrk也有局限比如它主要擅长 HTTP 测试且 Lua 脚本对于需要复杂业务逻辑如依赖上一个请求的响应的场景支持不够友好。但对于我们“向固定 API 发送 POST 请求”这个目标它是最合适的“手术刀”。2.2 被测服务vLLM OpenAI API 接口部署要点我们的压力测试对象是一个已经启动的 vLLM 服务。这里有几个关键配置直接影响测试结果和结论启动命令与参数我们使用类似以下的命令启动服务python -m vllm.entrypoints.openai.api_server \ --model /path/to/your/model \ # 例如 Qwen2-7B-Instruct --served-model-name qwen2-7b \ --host 0.0.0.0 \ --port 8000 \ --tensor-parallel-size 1 \ # 根据 GPU 数量调整 --max-model-len 4096 \ # 模型最大上下文长度 --gpu-memory-utilization 0.9 \ # GPU 内存利用率目标 --max-num-batched-tokens 2048 \ # 批处理最大 token 数影响吞吐 --disable-log-requests # 压力测试时建议关闭请求日志以减少干扰关键参数解析--max-num-batched-tokens这是 vLLM 吞吐性能的“油门”。它决定了单次前向传播能处理的最大 token 总数。设置得太低GPU 算力无法饱和设置得太高可能导致 OOM 或排队延迟激增。压力测试的一个重要目的就是找到当前硬件下的最优值。--disable-log-requests在压力测试期间每个请求都打印日志会带来巨大的 I/O 开销严重影响性能表现导致测试结果失真。务必关闭。API 端点vLLM 的 OpenAI 兼容接口默认在/v1/chat/completions提供聊天补全服务。这就是我们wrk将要攻击的“靶心”。监控准备在运行wrk的同时我们需要另开终端观察 vLLM 服务本身的状态。除了基础的nvidia-smi查看 GPU 利用率更关键的是 vLLM 的 metrics。默认情况下vLLM 会在http://localhost:8000/metrics提供 Prometheus 格式的指标。我们需要关注vllm:request_latency请求延迟、vllm:num_requests_running正在处理的请求数等。3. 构造wrk测试脚本与执行策略3.1 编写 Lua 脚本模拟真实的聊天请求wrk的强大之处在于可以用 Lua 脚本自定义请求。我们的目标是模拟用户向/v1/chat/completions发送一个典型的聊天请求。创建一个文件命名为vllm_stress.lua-- vllm_stress.lua -- 初始化阶段设置全局变量 wrk.method POST wrk.headers[Content-Type] application/json wrk.headers[Authorization] Bearer dummy-key -- vLLM 若未启用鉴权可留空但建议保留头部结构 -- 定义请求体模板 -- 注意为了测试公平性每次请求的输入长度应保持一致或在一个可控范围内随机。 local prompt_template [[ { model: qwen2-7b, messages: [ {role: user, content: %s} ], max_tokens: 128, -- 限制生成长度避免响应过长影响测试 temperature: 0.7, stream: false -- 压力测试建议关闭流式输出简化处理 } ]] -- 准备一个请求内容池避免所有请求完全一致可选但更真实 local prompts { 请用中文解释一下牛顿第一定律。, 写一首关于春天的五言绝句。, 计算一下 15的平方加上28的三次方等于多少, 简述人工智能发展的三个主要阶段。, 如何快速学习一门新的编程语言给出三个建议。 } -- 每个线程初始化时调用 function setup(thread) thread.addr http://your-server-ip:8000/v1/chat/completions -- 替换为你的 vLLM 服务地址 end -- 每次请求前调用用于生成动态请求体 function request() -- 随机选择一个提示词模拟不同用户的输入 local random_prompt prompts[math.random(#prompts)] local body string.format(prompt_template, random_prompt) wrk.body body return wrk.format() end -- 响应处理函数可选可用于校验响应或记录特定错误 function response(status, headers, body) -- 如果只想记录错误可以这样 -- if status ~ 200 then -- io.write(string.format(Error: Status %d\n, status)) -- io.write(body .. \n) -- end -- 注意在高压下频繁 io.write 会影响性能仅用于调试。 end脚本要点解析动态内容使用一个提示词池并随机选择这比所有请求发送完全相同的内容更能模拟真实场景也能避免服务端可能存在的缓存优化带来的测试偏差。固定生成长度”max_tokens”: 128确保了每次请求的“工作量”生成token数大致可控使延迟数据更具可比性。关闭流式”stream”: false对于压力测试至关重要。流式输出会保持长连接极大地增加服务端的并发连接管理和内存占用复杂度不利于我们聚焦在核心的推理吞吐和延迟上。鉴权头即使 vLLM 服务未启用鉴权也保留Authorization头因为这是 OpenAI API 的标准格式避免因头部差异导致意外问题。3.2 设计科学的压力测试执行策略直接用一个高并发参数去“猛打”服务是不科学的这很可能瞬间把服务打挂得不到渐进式的性能曲线。我推荐采用阶梯增压Step-load策略。我们可以写一个简单的 Shell 脚本来自动化这个过程#!/bin/bash # stress_test_step.sh SERVER_URLhttp://10.0.1.100:8000 # 替换为你的 vLLM 服务地址 DURATION30 # 每个压力阶梯持续30秒 THREADS4 # 根据测试机CPU核心数调整通常等于核心数 for CONNECTIONS in 10 30 50 80 120 200; do echo echo 开始测试: 连接数$CONNECTIONS, 线程数$THREADS, 持续时间${DURATION}s echo # 运行 wrk wrk -t $THREADS -c $CONNECTIONS -d $DURATION -s ./vllm_stress.lua $SERVER_URL echo echo 等待10秒让服务恢复稳定... sleep 10 done执行策略解析渐进增加连接数-c从 10 个并发连接开始逐步增加到 200。这允许我们观察系统在不同负载下的表现何时响应延迟开始线性增长何时吞吐量达到平台期何时开始出现错误或超时固定线程数-twrk的线程数用于管理网络连接通常设置为测试机器可用的 CPU 逻辑核心数即可避免线程切换开销。持续时长-d每个阶梯持续 30 秒到 60 秒是比较合适的。时间太短系统可能未达到稳定状态时间太长整体测试周期会拉得很长。间隔休息sleep在每个压力阶梯之间插入 10 秒左右的等待时间让 vLLM 服务处理完队列中的请求GPU 内存和算力恢复到空闲状态确保下一个阶梯的测试是独立的。注意运行压力测试的机器施压机最好与被测的 vLLM 服务器在同一个内网并确保网络带宽例如万兆不是瓶颈。否则测试结果反映的可能是网络限制而非 vLLM 服务的真实性能。4. 测试结果深度解读与性能瓶颈分析假设我们运行了上述阶梯测试并得到了类似下面的一组输出以连接数 80 为例的简化报告Running 30s test http://10.0.1.100:8000/v1/chat/completions 4 threads and 80 connections Thread Stats Avg Stdev Max /- Stdev Latency 1.23s 385.62ms 2.11s 85.12% Req/Sec 16.22 4.67 30.00 78.33% Latency Distribution 50% 1.19s 75% 1.45s 90% 1.78s 99% 2.05s 1942 requests in 30.09s, 12.34MB read Requests/sec: 64.56 Transfer/sec: 420.11KB同时我们通过curl http://localhost:8000/metrics在 vLLM 服务器上持续抓取了一些关键指标。4.1 核心指标拆解Latency 和 Requests/sec平均延迟Avg Latency1.23秒。这个时间是从wrk发送请求到收到完整响应所经历的时间。它包含了网络传输、vLLM 请求排队、模型推理和结果返回的全过程。1秒多的延迟对于交互式应用来说已经偏高了。延迟分布这是比平均延迟更有价值的指标。50%中位数1.19秒。一半的请求在 1.19 秒内完成。90%1.78秒。90% 的请求在 1.78 秒内完成。这意味着有 10% 的请求延迟超过了 1.78 秒。99%2.05秒。尾部延迟较高。在并发 80 时最慢的 1% 请求需要超过 2 秒。高尾部延迟是影响用户体验的“杀手”用户会感觉服务“卡顿”。每秒请求数Requests/sec64.56。这是系统在当前负载下的吞吐量。我们需要结合不同并发下的吞吐量来看趋势。4.2 结合 vLLM Metrics 进行瓶颈定位仅看wrk的输出是不够的我们需要知道时间花在了哪里。此时vLLM 的监控指标就至关重要。我们重点关注以下两个vllm:request_latencyvLLM 自身统计的请求处理延迟。这个延迟通常从请求被 vLLM 引擎接收开始到推理完成结束。比较wrk报告的总延迟和vllm:request_latency如果两者差距很大比如总延迟2秒vLLM延迟仅0.5秒那么瓶颈很可能不在模型推理而在网络、API 网关或者wrk测试机本身。vllm:num_requests_running与vllm:num_requests_waitingrunning表示正在被调度和推理的请求数。它受--max-num-batched-tokens和 GPU 算力限制。waiting表示在队列中等待的请求数。如果waiting数持续大于 0甚至在增加说明请求到达速率超过了服务处理速率队列在堆积。这是系统过载的明确信号也是导致高延迟尤其是尾部延迟的直接原因。性能瓶颈分析流程绘制性能曲线将不同并发数-c下的Requests/sec吞吐和Latency延迟分别制成图表。理想情况随着并发增加吞吐线性上升延迟缓慢增长。瓶颈出现当并发增加到某个点后吞吐增长停滞甚至下降而延迟开始急剧上升。这个拐点就是系统的最佳并发点。超过这个点增加并发只会增加排队时间不会提升吞吐。对照资源监控在出现拐点时查看 GPU 利用率nvidia-smi是否已达到 90% 以上vllm:num_requests_waiting是否开始显著增长如果 GPU 未饱和但延迟已飙升瓶颈可能在于--max-num-batched-tokens设置过小导致 GPU 无法充分并行处理请求或者 CPU 预处理/后处理成为瓶颈亦或是测试机网络或 vLLM 所在服务器的网络中断处理能力不足。如果 GPU 已饱和且队列堆积这就是纯粹的算力瓶颈。需要更强大的 GPU或者考虑模型量化、使用更小模型、增加tensor-parallel-size多卡并行来提升算力。4.3 一个实战排查案例调整--max-num-batched-tokens在我的测试中初期使用默认设置在并发 50 时吞吐就上不去了延迟飙升。观察nvidia-smiGPU 利用率仅在 60%-70% 徘徊而vllm:num_requests_waiting却有 10 多个。问题分析GPU 没喂饱但请求在排队。这强烈暗示了--max-num-batched-tokens批处理最大 token 数是瓶颈。vLLM 的 PagedAttention 引擎会尝试将多个请求的 KV Cache 拼接在一起进行批处理以提高计算效率。如果这个上限设得太低即使有很多请求在等待引擎也无法将它们放入同一个批处理中导致 GPU 利用率低吞吐上不去。解决方案逐步增加--max-num-batched-tokens的值例如从 2048 到 4096再到 8192并重复压力测试。同时密切监控 GPU 内存使用情况nvidia-smi避免因批处理过大导致 OOM。调整后的效果将--max-num-batched-tokens从 2048 调整为 8192 后在并发 80 时GPU 利用率稳定在 95% 以上吞吐从 64.56 提升到了 89.23平均延迟从 1.23秒 降低到 980毫秒。这就是通过压力测试找到并优化关键参数带来的直接收益。5. 高级测试场景与常见问题排查5.1 模拟更复杂的请求模式上面的脚本模拟了请求内容长度基本固定的场景。但真实场景可能更复杂变长输入测试用户的问题有长有短。我们需要测试服务对变长输入的鲁棒性。可以在 Lua 脚本的prompts池中加入长度差异很大的提示词比如从 10 个 token 到 500 个 token。观察长文本输入是否会对延迟和吞吐产生不成比例的影响。混合流量测试同时模拟“短平快”的问答max_tokens: 50和“长思考”的创作max_tokens: 512。这可以测试 vLLM 调度器在处理异质工作负载时的公平性和效率。这需要更复杂的 Lua 脚本为不同比例的请求分配不同的请求体模板。5.2wrk测试中常见的坑与解决之道错误socket: Too many open files现象wrk报错无法建立更多连接。原因Linux 系统对单个进程可打开的文件描述符数量有限制。wrk每个连接都会占用一个。解决临时提高限制ulimit -n 65536。在运行wrk的终端中执行此命令。测试结果波动巨大现象两次相同的测试QPS 和延迟差异很大。排查系统干扰确保测试期间没有其他重型进程在运行如系统更新、备份。GPU 状态确保每次测试前 GPU 是“冷启动”的或者至少处于相同的初始状态可通过重启 vLLM 服务实现。GPU Boost 频率、显存碎片都可能影响结果。预热在正式记录数据的测试前先运行一个 30 秒的低压力测试如-c 10让 vLLM 的 CUDA 内核、内存分配等完成初始化进入稳定状态。服务端直接崩溃或无响应现象wrk报告大量连接失败或超时vLLM 服务进程可能挂掉。排查检查日志查看 vLLM 服务的输出日志是否有 OOMOut of Memory错误。这可能是--max-num-batched-tokens设置过高或并发请求导致显存耗尽。检查系统内存使用free -h查看系统内存是否耗尽。vLLM 除了显存也会使用部分主机内存。降低负载这是最直接的验证方法。如果降低并发后服务恢复稳定说明当前硬件配置无法支撑预设的负载。wrk脚本性能问题现象wrk本身 CPU 占用率很高甚至达到 100%。排查检查 Lua 脚本中的response()函数。如果在这个函数里执行了复杂的逻辑如字符串解析、文件写入会严重拖慢wrk的发包速度。压力测试时response()函数应尽可能轻量或直接注释掉。5.3 从压力测试到性能调优清单根据测试结果你可以形成一个有针对性的调优清单现象可能原因调优方向高延迟低吞吐GPU利用率低批处理大小限制增加--max-num-batched-tokens高延迟吞吐尚可GPU利用率高算力瓶颈模型量化、启用更多GPU增加--tensor-parallel-size、升级硬件延迟不稳定尾部延迟极高请求队列堆积优化调度策略如vLLM的优先级调度、实施请求速率限制、增加服务实例水平扩展wrk总延迟远大于vllm:request_latency网络或前端代理瓶颈检查网络带宽和延迟、优化API网关如Nginx配置、确保wrk测试机性能足够服务内存/显存溢出崩溃内存资源不足降低--max-num-batched-tokens、降低--gpu-memory-utilization、使用量化模型、减少单请求max_tokens压力测试不是一次性的任务而是一个迭代的过程。每次对服务配置、模型参数或基础设施进行调整后都应该重新运行压力测试用数据来验证优化是否有效从而持续推动服务性能向更高的水平迈进。通过wrk这把精准的尺子我们能清晰地度量出 vLLM OpenAI API 接口的性能边界为生产环境的稳定、高效运行打下坚实的基础。