
1. 私有化 vLLM 压测到底在测什么EvalScope 是 ModelScope 官方推出的模型评估与性能基准测试框架能对 LLM、多模态、Embedding、Reranker 等模型做统一评测。它最实用的能力之一是直接对 OpenAI 兼容接口发起并发压测输出吞吐、延迟、首 token 时间等指标。vLLM 则是当前私有化部署大模型的主流推理引擎支持 PagedAttention、连续批处理、前缀缓存适合把 Qwen3-30B-A3B 这类 MoE 模型跑成高吞吐服务。把两者组合起来就是一套完整的私有化性能压测链路vLLM 负责把模型跑起来并暴露/v1/chat/completionsEvalScope 负责按不同并发数打流量最后产出一份可对比的性能基线报告。适合谁适合已经或准备在自有 GPU 上部署 Qwen3-30B-A3B 的团队尤其是需要回答“这台机器到底能扛多少并发”“加卡之后吞吐提升多少”“首包延迟能不能压到 100ms 以内”这类问题的同学。我这次用的测试机器是 2 张 A6000 48G共 96G 显存模型是 Qwen3-30B-A3B数据集用 HC3-Chinese 的 open_qa 子集。整条链路里模型服务连通与鉴权部分我统一走 TaoToken 的 Key/API 通道来管理这样本地 vLLM、云端模型、后续评测任务可以用同一套 Key 体系省得每个服务单独维护一份密钥。下面从环境准备开始一步步把压测跑通。需要先明确一点压测不是把请求发出去就完事关键是让每次测试的输入长度、输出长度、温度、并发数都可控否则不同轮次的结果没法横向对比。EvalScope 的Arguments里提供了min_prompt_length、max_prompt_length、max_tokens、temperature这些约束正是为了让压测可复现。理解了这一点后面的配置就不会觉得参数多。2. TaoToken 统一 Key 与 vLLM 服务连通2.1 为什么压测前要先理清鉴权通道私有化部署常见的一个坑是vLLM 自己带--api-key评测框架又要求填api_key如果本地还有别的模型服务Key 就散落在各个脚本里。我的做法是把对外统一入口收敛到 TaoToken本地 vLLM 仍然用自己启动时设置的 Key但所有需要跨服务调用的场景都走统一通道。这样压测脚本里的api_key字段来源清晰不会出现“这个 Key 到底是哪台机器的”这种问题。TaoToken 的 API 地址是https://taotoken.net/api控制台里可以创建和管理 API Keys。如果你还没建过 Key进控制台新建一个即可模型对话入口可以用来快速验证通道是否正常。需要说明的是TaoToken 在这里承担的是统一 Key/API 通道的角色本地 vLLM 的推理仍然跑在你自己的 GPU 上两者不冲突。2.2 安装 EvalScope 与依赖建议 Python 3.10 以上。安装命令pip install evalscope uvicorn fastapi sse_starlette装完确认版本evalscope -v能打印出版本号就说明 CLI 可用。如果这一步报command not found多半是 pip 装的脚本目录没进 PATH用python -m evalscope -v也能验证。2.3 下载 Qwen3-30B-A3B 模型用 modelscope 把模型拉到本地modelscope download --modelQwen/Qwen3-30B-A3B --local_dir Qwen3-30B-A3B下载完成后目录里应该有config.json、tokenizer.json以及分片权重文件。Qwen3-30B-A3B 是 MoE 结构总参数 30B 但激活参数约 3B这也是它能在 2 张 48G 卡上跑起来的原因。下载慢的话可以换 modelscope 的镜像源或者提前用--revision指定版本。2.4 启动 vLLM API 服务export CUDA_VISIBLE_DEVICES0,1 vllm serve Qwen3-30B-A3B \ --host 0.0.0.0 \ --port 8060 \ --dtype bfloat16 \ --cpu-offload-gb 0 \ --gpu-memory-utilization 0.8 \ --tensor-parallel-size 2 \ --api-key sk-abcdefxxx \ --enable-prefix-caching \ --enable-reasoning \ --reasoning-parser deepseek_r1 \ --trust-remote-code几个参数值得单独说--tensor-parallel-size 2表示用 2 张卡做张量并行--gpu-memory-utilization 0.8给 KV Cache 留出空间压测时如果并发高、输出长这个值可以适当调高--enable-prefix-caching对多轮或共享前缀的请求有明显收益--enable-reasoning配合--reasoning-parser deepseek_r1是为了让带思考过程的输出能被正确解析。启动日志里看到Uvicorn running on http://0.0.0.0:8060就说明服务起来了。2.5 验证服务连通curl http://127.0.0.1:8060/v1/chat/completions \ -H Content-Type: application/json \ -H Authorization: Bearer sk-abcdefxxx \ -d { model: Qwen3-30B-A3B, messages: [ {role: system, content: You are a helpful assistant.}, {role: user, content: 用一句话解释什么是张量并行} ] }返回里能看到choices[0].message.content就说明模型服务正常。如果返回 401检查Authorization头里的 Key 是否和启动参数一致如果连接被拒确认端口没被占用、--host是0.0.0.0。3. 可复制的 EvalScope 压测配置3.1 下载压测数据集modelscope download --dataset AI-ModelScope/HC3-Chinese --local_dir AI-ModelScope/HC3-ChineseHC3-Chinese 里每条样本包含question、human_answers、chatgpt_answersEvalScope 的openqa模式会取question作为 prompt。压测时我们关心的是输入长度分布所以数据集本身的内容质量不影响吞吐指标只影响 prompt 长度。3.2 压测脚本from evalscope.perf.main import run_perf_benchmark from evalscope.perf.arguments import Arguments task_cfg Arguments( parallel[2, 5, 10], # 并发数 number[100, 100, 100], # 每轮请求总数 modelQwen3-30B-A3B, urlhttp://127.0.0.1:8060/v1/chat/completions, api_keysk-abcdefxxx, apiopenai, datasetopenqa, dataset_pathAI-ModelScope/HC3-Chinese/open_qa.jsonl, temperature0.6, max_tokens1024, prefix_length0, min_prompt_length8, max_prompt_length1024, tokenizer_pathQwen/Qwen3-30B-A3B, extra_args{ignore_eos: True}, connect_timeout30, read_timeout60, outputs_diroutputs ) results run_perf_benchmark(task_cfg)extra_args{ignore_eos: True}是压测里很关键的一项它让模型忽略结束符强制生成到max_tokens这样每轮输出长度一致吞吐数据才可比。如果不加模型可能提前结束输出 token 数参差不齐吞吐对比就失去意义。3.3 用 JSON 配置管理多轮压测如果不想每次改 Python 脚本可以把配置抽成 JSON方便版本管理和复现{ parallel: [2, 5, 10], number: [100, 100, 100], model: Qwen3-30B-A3B, url: http://127.0.0.1:8060/v1/chat/completions, api: openai, dataset: openqa, dataset_path: AI-ModelScope/HC3-Chinese/open_qa.jsonl, temperature: 0.6, max_tokens: 1024, min_prompt_length: 8, max_prompt_length: 1024, tokenizer_path: Qwen/Qwen3-30B-A3B, extra_args: {ignore_eos: true}, connect_timeout: 30, read_timeout: 60, outputs_dir: outputs }注意 JSON 里不能写注释api_key建议通过环境变量注入不要硬编码进文件。EvalScope 支持从配置文件读取参数具体字段名以官方参数文档为准。3.4 参数对照表参数作用压测建议parallel并发请求数从 2 起步逐步加到 10、20number每轮总请求数至少 100太少统计不稳max_tokens最大输出长度固定值配合 ignore_eostemperature采样温度压测用固定值避免随机性min/max_prompt_length输入长度过滤控制输入分布保证可比tokenizer_path计算 token 数必须和模型一致connect/read_timeout超时长输出场景适当调大4. 压测结果验证与基线解读4.1 三组并发结果第一组并发 2100 个请求全部成功测试总时长 675.359s输出吞吐 151.623 tok/s总吞吐 155.901 tok/s请求吞吐 0.1481 req/s平均延迟 13.5064s平均首 token 时间 0.0623s平均每输出 token 时间 0.0131s。第二组并发 5总时长 472.911s输出吞吐 216.531 tok/s总吞吐 222.64 tok/s请求吞吐 0.2115 req/s平均延迟 23.6354s平均首 token 时间 0.0745s平均每输出 token 时间 0.023s。第三组并发 10总时长 295.101s输出吞吐 346.999 tok/s总吞吐 356.789 tok/s请求吞吐 0.3389 req/s平均延迟 29.4908s平均首 token 时间 0.1014s平均每输出 token 时间 0.0287s。三组都是 100 个请求、0 失败。把关键指标拉成一张表更直观并发输出吞吐 tok/s总吞吐 tok/s请求吞吐 req/s平均延迟 s平均 TTFT s2151.623155.9010.148113.50640.06235216.531222.640.211523.63540.074510346.999356.7890.338929.49080.10144.2 百分位指标怎么看以并发 10 为例P50 的 TTFT 是 0.0953sP99 是 0.1683sP50 的 TPOT 是 0.0287sP99 是 0.0294sP50 端到端延迟 29.4917sP99 是 30.2229s。可以看到 P99 和 P50 差距很小说明延迟分布集中没有明显的长尾抖动。首 token 时间始终在毫秒级这对交互式应用很关键——用户感知的“卡不卡”主要看 TTFT而不是总生成时间。4.3 指标计算方式理解公式才能正确解读报告。测试总时长 最后一个请求结束时间 - 第一个请求开始时间输出吞吐 总输出 token 数 / 测试总时长总吞吐 (总输入 token 总输出 token) / 测试总时长请求吞吐 成功请求数 / 测试总时长平均延迟 总延迟时间 / 成功请求数平均 TTFT 总首 chunk 延迟 / 成功请求数TPOT 总每输出 token 时间 / 成功请求数不含首 tokenITL 是相邻输出 token 之间的平均间隔。百分位指标以单个请求为单位统计第 n 百分位表示有 n% 的数据点在该值以下。TTFT 评估首包延迟ITL 评估输出是否平稳TPOT 评估解码速度Latency TTFT TPOT × 输出 token 数。4.4 基线结论并发从 2 增到 10输出吞吐从 151.6 tok/s 提升到 347.0 tok/s提升约 2.29 倍延迟从 13.5s 涨到 29.5s涨幅约 2.18 倍。吞吐和延迟同步增长说明系统还没到饱和点继续加并发吞吐可能还会涨但延迟会进一步上升。首包延迟始终在毫秒级P99 与平均延迟基本一致整体表现稳定。这份数据可以作为后续调优比如调gpu-memory-utilization、加卡、换量化的对比基线。5. 压测常见报错排查5.1 401 Unauthorized报错长这样openai.AuthenticationError: Error code: 401 - {error: {message: Invalid API key, ...}}原因通常是Arguments里的api_key和 vLLM 启动时的--api-key不一致或者 Key 里带了多余空格。排查方法先用 curl 手动请求一次确认 Key 可用再检查脚本里api_key字段。如果走 TaoToken 统一通道确认 Key 是在对应控制台创建的且请求地址是https://taotoken.net/api下的正确路径。5.2 local proxy failed / Connection refusedhttpx.ConnectError: [Errno 111] Connection refused说明 EvalScope 连不上url指定的地址。检查 vLLM 是否还在运行、端口是否被防火墙拦截、--host是否为0.0.0.0。如果 vLLM 和 EvalScope 不在同一台机器127.0.0.1要换成实际 IP。5.3 reading choices 相关解析错误KeyError: choices或json.decoder.JSONDecodeError: Expecting value多半是服务返回了非 OpenAI 格式的响应比如错误页、空响应或者--enable-reasoning开启后输出结构变化导致解析异常。排查先用 curl 看原始返回体确认choices字段存在如果开了 reasoning parser确认解析器类型和模型匹配。5.4 OAuth / tokenizer 路径报错OSError: Cant load tokenizer for Qwen/Qwen3-30B-A3Btokenizer_path写的是模型 ID但本地没有缓存也没联网就会失败。改成下载到本地的实际路径比如Qwen3-30B-A3B。如果报 OAuth 相关错误检查是否误用了需要鉴权的私有仓库地址。5.5 显存不足 OOMtorch.cuda.OutOfMemoryError: CUDA out of memory压测时并发高、输出长KV Cache 占用会上升。可以调低--gpu-memory-utilization、减小max_tokens、降低并发数或者开启--enable-prefix-caching复用前缀。如果用了 Cline MCP 或 Codex 这类工具接入记得 Base URL、Key、Model ID 三件套要写全缺一个都会连不上。6. 把压测接入日常流程跑通一次压测不难难的是让它可复现、可对比。我的做法是把 vLLM 启动参数、EvalScope 配置、数据集版本都写进版本控制每次调优只改一个变量跑完把报告存到outputs下按日期归档。这样下次有人问“换了量化之后性能差多少”直接翻历史报告就行。如果你需要长期做编码类或 Agent 类任务的压测可以考虑用 Coding Plan 管理额度验证模型对话能力时用模型对话入口快速试接入文档里有完整的鉴权说明。API Keys 在控制台创建接入文档在文档页模型对话和 Coding Plan 也都有对应入口。压测本身是手段最终目的是让私有化部署的 Qwen3-30B-A3B 在真实业务负载下表现可预期——这份基线报告就是第一步。