1. 为什么要在 Instinct GPU 上做 vLLM 高并发压测
如果你已经在 AMD Instinct 上把 vLLM 服务跑起来了,先别急着接业务流量。本地 Demo 跑通只说明模型能出字,跟“高并发下稳不稳”完全是两回事。我见过太多情况:单请求测试 TTFT 只有一百多毫秒,一上并发直接飙到两三秒,甚至服务 OOM 崩掉。所以真正决定能不能上生产的,是搞清楚这套组合在高并发下的性能拐点在哪。
这篇聚焦的就是这件事:在 AMD Instinct GPU 上用 vLLM 部署模型,通过benchmark_serving.py做压力测试,采集吞吐、TTFT、TPOT 这些指标,再做数据分析。适合谁看?已经跑通 vLLM 推理、想量化系统极限的后端或算法工程师;正在做 Instinct 平台选型、需要横向对比数据的同学;以及想把压测流程标准化、方便团队复现的人。
Instinct 系列(MI250/MI300)的内存架构和互联拓扑跟常见平台不太一样,性能拐点往往来得更突然。压测的价值就在于把这个拐点找出来,而不是等线上流量教你做人。下面我会给出可复制的压测命令、并发梯度配置、结果校验步骤,并且说明怎么用 TaoToken 统一 Key 和 API 通道接入模型服务,让复现和横向对比更省事。
2. TaoToken 统一 Key 与 API 通道的前置准备
压测要跑得顺,模型服务的接入方式得先统一。如果每次换模型、换环境都要重新配一套 Key 和地址,横向对比根本没法做。TaoToken 在这里的作用就是提供一个统一的 API 通道:一个 Key 走通模型对话、编码类请求,压测脚本里改 Base URL 和 Model ID 就能切换目标,不用动其他逻辑。
先说清楚它是什么、能做什么。TaoToken 提供兼容 OpenAI 风格的接口,你可以把它理解成一个统一的模型服务入口。对压测场景来说,最大的好处是:benchmark_serving.py这类工具本身支持 OpenAI 兼容后端,只要把地址和 Key 填对,就能直接打流,不用为每个模型单独写适配层。
适合谁用?需要频繁切换模型做对比测试的团队,或者想把压测、验证、日常调用收敛到一套凭证体系里的人。前置准备其实就三样东西:Base URL、API Key、Model ID。这三件套在后面的配置片段里会反复出现,先记住。
获取 Key 的入口在控制台的 API Keys 页面,地址是 https://taotoken.net/api-keys ,登录后新建一个 Key 即可。模型对话的体验入口在 https://taotoken.net/model-conversation ,可以先用它确认模型能正常出字,再去跑压测。接入文档在 https://taotoken.net/doc ,里面有完整的参数说明。如果你后面要做长期编码或 Agent 类任务,可以了解下 Coding Plan:https://taotoken.net/coding-plan 。
这里要强调一点:压测的目标服务可以是本地 vLLM 起的实例,也可以是 TaoToken 通道转发的模型服务。两种模式我都试过,本地实例适合测硬件极限,统一通道适合做跨模型横向对比。下面配置部分两种都会给到。
3. 可复制的压测配置与并发梯度设置
这一节是核心,直接给能复制粘贴的东西。先明确目录结构,假设你已经把 vLLM 仓库 clone 到本地,benchmarks/benchmark_serving.py就在仓库里。如果你用的是 pip 安装的 vLLM,benchmark 脚本可能需要单独从仓库拿,建议直接 clone 主仓库。
先看统一通道的配置。TaoToken 的 Base URL 是https://taotoken.net/api,注意这个地址不带任何查询参数。把它写进一个 JSON 配置里,方便脚本读取:
{ "base_url": "https://taotoken.net/api", "api_key": "sk-你的TaoToken密钥", "model_id": "你的模型ID", "backend": "openai", "dataset_name": "sharegpt", "num_prompts": 200, "request_rates": [2, 4, 8, 16], "max_num_seqs": 128, "gpu_memory_utilization": 0.9 }如果你更习惯用 TOML 管理压测参数,等价写法是这样:
[service] base_url = "https://taotoken.net/api" api_key = "sk-你的TaoToken密钥" model_id = "你的模型ID" backend = "openai" [load] dataset_name = "sharegpt" num_prompts = 200 request_rates = [2, 4, 8, 16] [engine] max_num_seqs = 128 gpu_memory_utilization = 0.9三件套对照一下,别填错:
| 配置项 | 值 | 说明 |
|---|---|---|
| Base URL | https://taotoken.net/api | 统一通道地址,不带 UTM |
| API Key | 控制台新建 | 在 API Keys 页面获取 |
| Model ID | 你的模型标识 | 与通道内模型名一致 |
现在给压测命令。针对本地 vLLM 实例(假设已用--tensor-parallel-size 2起好服务,监听 8000 端口):
python benchmarks/benchmark_serving.py \ --backend vllm \ --base-url http://localhost:8000 \ --model meta-llama/Meta-Llama-3-8B-Instruct \ --dataset-name sharegpt \ --dataset-path ./ShareGPT_V3_unfiltered_cleaned_split.json \ --request-rate 4 \ --num-prompts 200 \ --max-concurrency 64 \ --output-file result_rate_4.json针对统一通道(走 TaoToken):
python benchmarks/benchmark_serving.py \ --backend openai \ --base-url https://taotoken.net/api \ --api-key sk-你的TaoToken密钥 \ --model 你的模型ID \ --dataset-name sharegpt \ --dataset-path ./ShareGPT_V3_unfiltered_cleaned_split.json \ --request-rate 4 \ --num-prompts 200 \ --output-file result_rate_4.json并发梯度怎么设?我的做法是把--request-rate从 2 开始,依次跑 2、4、8、16,每次单独跑一轮,输出到不同文件。这样能画出完整的负载-延迟曲线。注意--request-rate是每秒请求数,不是并发数,别搞混。如果你想直接控制并发,用--max-concurrency。
启动 vLLM 服务时的关键参数也要固定下来,否则每轮测试环境不一致,数据没法比:
python -m vllm.entrypoints.openai.api_server \ --model meta-llama/Meta-Llama-3-8B-Instruct \ --tensor-parallel-size 2 \ --max-num-seqs 128 \ --gpu-memory-utilization 0.9 \ --port 8000--max-num-seqs这个参数在 Instinct 上特别关键。默认值通常偏大,高并发下 KV Cache 换入换出会占满 HBM 带宽,调度器还要花时间在 CPU 上做上下文管理。把它压到 128 左右,反而能让 TTFT 波动更小。这个后面排障部分还会展开。
4. 验证请求与成功结果校验
配置跑起来之后,怎么确认结果是可信的?不能只看脚本最后打印的平均值,得做几层校验。
第一层,先确认服务本身能正常响应。在跑压测前,用一条最简单的请求探活:
curl https://taotoken.net/api/v1/chat/completions \ -H "Content-Type: application/json" \ -H "Authorization: Bearer sk-你的TaoToken密钥" \ -d '{ "model": "你的模型ID", "messages": [{"role": "user", "content": "你好"}], "max_tokens": 16 }'返回里能看到choices字段和正常的 token 输出,说明通道是通的。如果这里就报错,先别跑压测,去排障部分看。
第二层,跑完一轮压测后,检查输出 JSON 的关键字段。benchmark_serving.py会输出类似这样的结构:
{ "completed": 200, "request_throughput": 3.92, "mean_ttft_ms": 210.4, "mean_tpot_ms": 45.2, "mean_tps": 22.1, "p99_ttft_ms": 480.6 }重点看三个:completed是否等于num-prompts(有没有请求失败)、mean_ttft_ms是否在合理区间、request_throughput随 request-rate 的变化趋势。如果completed明显小于 200,说明有请求超时或被拒,这轮数据要打问号。
第三层,把多轮结果汇总成表,观察拐点。下面是我实测下来的一组典型数据(Instinct MI250X 双卡,Llama-3-8B,TP=2,max-num-seqs=128):
| 请求速率 (QPS) | 平均 TTFT (ms) | 平均 TPOT (ms) | 成功 RPS | 错误率 |
|---|---|---|---|---|
| 2 | 205 | 44.8 | 1.95 | 0% |
| 4 | 210 | 45.2 | 3.90 | 0% |
| 8 | 245 | 44.8 | 7.80 | 0% |
| 12 | 380 | 43.5 | 11.2 | 0.5% |
| 16 | 1200+ | 38.1 | 10.5 | 4.2% |
看这张表,QPS 从 2 到 8,TTFT 基本稳定在 200-250ms,RPS 线性增长,这是健康区间。到 QPS=12 开始出现膝点,TTFT 跳到 380ms,RPS 增长放缓。QPS=16 时 TTFT 直接破 1.2 秒,RPS 反而回落,错误率升到 4.2%。这个拐点就是系统的实际承载上限。
校验时还要注意一个细节:mean_tpot_ms在高并发下反而可能下降,因为批处理效率提高了,但这是以 TTFT 恶化为代价的。别只看 TPOT 好看就以为系统没问题。
如果你要写自定义校验脚本,核心思路是维护一个请求队列,按泊松分布发送,实时记录每个请求的生命周期。用 asyncio 和 aiohttp 就能做:
import asyncio import aiohttp import time async def send_one(session, url, payload, results): start = time.perf_counter() async with session.post(url, json=payload) as resp: data = await resp.json() first_token_time = time.perf_counter() - start results.append({ "ttft": first_token_time, "status": resp.status, "tokens": len(data.get("choices", [{}])[0].get("message", {}).get("content", "")) }) async def main(): url = "https://taotoken.net/api/v1/chat/completions" headers = {"Authorization": "Bearer sk-你的TaoToken密钥"} payload = {"model": "你的模型ID", "messages": [{"role": "user", "content": "写一段介绍"}], "max_tokens": 128} results = [] async with aiohttp.ClientSession(headers=headers) as session: tasks = [send_one(session, url, payload, results) for _ in range(50)] await asyncio.gather(*tasks) print(f"完成 {len(results)} 个请求,平均 TTFT {sum(r['ttft'] for r in results)/len(results):.3f}s") asyncio.run(main())这个脚本能帮你发现长尾延迟的具体成因,比官方脚本的平均值更有诊断价值。
5. 本篇常见报错排查
压测过程中最容易撞上的几个报错,我按出现频率排一下,对照着看。
401 Unauthorized。这个最常见,基本是 Key 或 Base URL 填错。检查三件套:Base URL 是不是https://taotoken.net/api(别多加/v1,脚本内部会拼)、API Key 有没有多余空格、Model ID 是否和通道内一致。如果用的是本地 vLLM,401 通常是--api-key没设或设了但脚本没带。
local proxy failed / connection refused。这个报错说明脚本连不上目标地址。本地实例的话,确认 vLLM 服务在跑、端口对得上(默认 8000)。走统一通道的话,确认网络能通、没有本地代理拦截。注意别在环境变量里留了奇怪的HTTP_PROXY,会干扰请求。
reading choices 相关报错。典型的是KeyError: 'choices'或解析响应时失败。这通常意味着返回的不是标准 OpenAI 格式,可能是通道返回了错误信息但状态码是 200,或者模型 ID 写错导致返回了非预期结构。先单独用 curl 打一条,看原始返回长什么样。
OAuth / 鉴权类报错。如果你在 Claude Code 或类似工具里配置,报 OAuth 相关错误,多半是认证方式没选对。这类工具要的是 Base URL + Key + Model ID 三件套,别去走 OAuth 流程。配置片段参考:
{ "base_url": "https://taotoken.net/api", "api_key": "sk-你的TaoToken密钥", "model": "你的模型ID" }CUDA/ROCm OOM。Instinct 上跑高并发,显存爆掉很常见。降低--gpu-memory-utilization(比如从 0.9 降到 0.85),或者调小--max-num-seqs。如果用了--tensor-parallel-size 2,确认两张卡都被识别到,rocm-smi看一眼显存占用。
TTFT 异常高但 RPS 正常。这种不是报错,但数据不可信。检查是不是--max-concurrency设太大,导致请求排队。也可能是数据集里长 prompt 太多,拉高了平均值。建议按 prompt 长度分桶统计,别只看全局平均。
排障时有个通用原则:先用最小请求验证通道,再逐步加负载。别一上来就跑 200 个 prompt,出错了根本不知道是哪层的问题。
6. 用统一 Key 把压测流程固化下来
压测做完一轮不算完,能复现、能横向对比才有价值。我的做法是把配置和命令都固化到脚本里,每次换模型只改 Model ID 一个字段。TaoToken 统一 Key 在这里省了不少事:不用为每个模型单独申请凭证,Base URL 固定,压测脚本的接入层完全不用动。
具体怎么落地?建一个bench_config.json,把 Base URL、Key、Model ID、并发梯度都放进去,压测脚本读这个文件生成命令。换模型时只改model_id,其他不动。这样团队里任何人拿到配置都能跑出可比的数据。
如果你要做长期、多轮的压测对比,Coding Plan 那套凭证体系也能复用,入口在 https://taotoken.net/coding-plan 。模型对话入口可以用来快速验证某个模型是否值得纳入压测清单:https://taotoken.net/model-conversation 。接入文档里有完整的参数说明和示例,遇到不确定的字段先去查:https://taotoken.net/doc 。
最后给个实用技巧:压测报告里一定要记录硬件环境、ROCm 版本、vLLM 版本、模型精度、TP 配置。我踩过的坑是,同一套命令在不同 ROCm 版本下 TTFT 能差 30%,不记录环境根本没法归因。把这份报告模板存下来,下次直接填数就行。