网上找了一圈性能评测工具,很多都要自己把模型拉起来,还动不动就想去HuggingFace下载,都不太好用。考虑到目前不管是开源还是闭源,各大模型的推理服务,基本都遵循 OpenAI 的 API 接口。所以针对该接口编写一个简单的脚本,应该即可评测各种模型的性能了。这种任务,对于我这种20多年的老码农来说,当然要用AI帮忙了 :)因此用AI生成了一个初始版本,调试了1个多小时可以跑通了。但是结果数据有问题,隐藏的一个逻辑错误,OpenAI 和 DeepSeek 都没发现。解决这个问题之后,又花时间把结果调测到自己满意的程度,加上中间的反复测试和参数调整,又花了1天半的时间。。。 但是总体来说呢,比我自己从0开始写,还是要快很多。写代码其实时间大头还是调测和完善等工作,这个不管初始版本是AI写的,还是自己写的,都是少不了的。有时AI写的,这一步可能还更花时间,因为要把AI不小心埋的坑给找出来填平。
来看下成品的使用样例:
nohup python3 -u simple-bench-to-api.py --url http://localhost:7800/v1 \
--model DeepSeek-V3-0324 \
--concurrencys 1,10,20,30,40,50 \
--prompt "Introduce the history of China" \
--max_tokens 100,1024,16384,32768,65536 \
--api_key sk-xxx \
--duration_seconds 30 \
> simple-bench-to-api.log 2>&1 &
其中参数的含义:
-
--url 推理服务的基础地址,路径以 /v1 结束
-
--model 推理服务的模型名称
-
--concurrency 并发数,如果这个设置了,则忽略 --concurrencys 参数内容
-
--prompt 每个请求的用户问题
-
--max_tokens 告诉模型最多返回的 token 数,逗号分隔多个,会针对每个值分别测试
-
--concurrencys 逗号分隔的并发数列表,如 1,5,10,15,20,30;不设置 concurrency 时才生效。会分别按设置的并发数发起压力,两个并发批次中间间隔5秒
-
--duration_seconds 持续时间,单位秒
最后一行 > simple-bench-to-api.log 2>&1 & 是把输出结果重定向到文件中,配合开头的 nohup 后台执行。
simple-bench-to-api.py 是单个文件的python脚本,文末会放出源码。针对参数 max_tokens 逗号分隔的每个取值,脚本会挨个测试不同的 concurrencys 并发值,输出每个并发值的结果,并汇总成一张该 max_tokens 取值下,不同并发值的 Markdown 格式表格,例如:
----- max_tokens=100 压测结果汇总 -----
| 指标 \ 并发数 | 1个并发 | 10个并发 | 20个并发 | 30个并发 | 40个并发 | 50个并发 |
| --- | --- | --- | --- | --- | --- | --- |
| 总请求数 | 3 | 30 | 60 | 90 | 120 | 150 |
| 成功率 | 100.00% | 100.00% | 100.00% | 100.00% | 100.00% | 100.00% |
| 平均延迟 | 13.9587s | 13.9900s | 14.0511s | 14.0769s | 14.1673s | 14.2916s |
| 最大延迟 | 14.7636s | 14.1010s | 14.1825s | 14.2707s | 14.5726s | 14.5179s |
| 最小延迟 | 13.4980s | 13.8632s | 13.8544s | 13.8677s | 13.9031s | 13.9850s |
| P90延迟 | 14.5338s | 14.0850s | 14.1607s | 14.2467s | 14.4279s | 14.4478s |
| P95延迟 | 14.6487s | 14.0952s | 14.1649s | 14.2566s | 14.5099s | 14.4803s |
| P99延迟 | 14.7407s | 14.0994s | 14.1749s | 14.2640s | 14.5641s | 14.5124s |
| 平均首字延迟 | 13.9587s | 13.9900s | 14.0511s | 14.0769s | 14.1673s | 14.2916s |
| 总生成tokens数 | 300 | 3000 | 6000 | 9000 | 12000 | 15000 |
| 单并发最小吞吐量 | 6.77 tokens/s | 7.09 tokens/s | 7.05 tokens/s | 7.01 tokens/s | 6.86 tokens/s | 6.89 tokens/s |
| 单并发最大吞吐量 | 7.41 tokens/s | 7.21 tokens/s | 7.22 tokens/s | 7.21 tokens/s | 7.19 tokens/s | 7.15 tokens/s |
| 单并发平均吞吐量 | 7.18 tokens/s | 7.15 tokens/s | 7.12 tokens/s | 7.10 tokens/s | 7.06 tokens/s | 7.00 tokens/s |
| 总体吞吐量 | 7.16 tokens/s | 71.40 tokens/s | 142.02 tokens/s | 212.27 tokens/s | 280.99 tokens/s | 347.65 tokens/s |
其中有几个概念需要解释下
-
”延迟“:从发出请求,到接收到最后一个token/字符的时间(包含了首字延迟时间)
-
“P90延迟”:分位数90的延迟,计算方法为延迟从小到大排序,前90%的最大延迟值,和下一个延迟值,基于线性插值计算的一个介于2者之间的值。
-
“首字延迟”:从发出请求,到接收到第一个返回字符的时间。
-
“单并发吞吐量”的概念,是指站在每个并发用户/通道的角度看,从首token返回后,token的生成速度。统计时间不包含首字延迟。即一个通道的吞吐量 = 该通道生成的token数/除首token延迟外的生成时间。个人觉得,这个指标加上平均首字延迟,能反映真实的用户体感。
具体指标的含义:
-
平均延迟:所有通道的延迟平均值(包含了首字延迟时间)
-
平均首字延迟:所有通道的首字延迟的平均值
-
单并发最小吞吐量: 所有并发通道中,吞吐量最小的通道的吞吐量(不包括首字延迟时间)
-
单并发最大吞吐量: 所有并发通道中,吞吐量最大的通道的吞吐量(不包括首字延迟时间)
-
单并发平均吞吐量:所有并发通道的吞吐量的平均值(不包括首字延迟时间)
-
总体吞吐量:在压测期间所有通道生成的tokens总数/压测开始到结束的时间
-
P90延迟: 表示有90%的请求延迟低于这个数值
-
P95延迟: 表示有95%的请求延迟低于这个数值
-
P99延迟: 表示有99%的请求延迟低于这个数值
接下来分别对部署在单卡 RTX 4090(24G显存)上的 DeepSeek 针对 qwen2.5-7B 和 qwen2.5-32B 的蒸馏版本做压测
DeepSeek-R1-Distill-Qwen-7B
-
模型地址:https://huggingface.co/deepseek-ai/DeepSeek-R1-Distill-Qwen-7B
-
部署方式:vLLM 0.7.3
-
压测发起地:与模型推理同一台机器,这样就消除了远程网络开销的影响。
参数组合1
服务端:
--gpu_memory_utilization 0.95 \
--max-num-seqs 512 \
--max-model-len 65536
客户端压测命令:
python3 simple-bench-to-api.py \
--url http://10.96.2.221:7869/v1 \
--model DeepSeek-R1-Distill-Qwen-7B \
--concurrencys 1,10,50,100,150,200 \
--prompt "Tell me a story" \
--max_tokens 100 \
--api_key 服务端配置的 API Key \
--duration_seconds 30
压测结果(脚本会汇总每个并发的结果,生成Markdown表格,下文中的表格都是我将脚本生成的Markdown表格文本,拷贝到我本地一个md编辑器后渲染的结果):
从这个结果汇总看,当并发从1到200时
-
平均首字延迟(avg_ttft)从0.0363s增长到了0.6999s,仍然很快。
-
单并发平均吞吐量(avg)从 60.14 tokens/s 下降到了 23.27 tokens/s; 说明在200个并发时,单用户的延迟会变慢1倍多,符合预期。
-
总体吞吐量从 58.76 tokens/s 上升到了 3730.98 tokens/s,并越大,总体吞吐量增长越缓慢,符合一般规律。
-
平均延迟(avg_latency)从1.6825s 增长到了4.9803s, 还算不错的。
总体说明,单卡 4090 跑 R1-7B 模型,在200并发之内都是很流畅的。
服务端日志:
# 1并发
INFO 03-03 03:59:21 metrics.py:455] Avg prompt throughput: 1.3 tokens/s, Avg generation throughput: 0.1 tokens/s, Running: 1 reqs, Swapped: 0 reqs, Pending: 0 reqs, GPU KV cache usage: 0.0%, CPU KV cache usage: 0.0%.
INFO 03-03 03:59:26 metrics.py:455] Avg prompt throughput: 3.6 tokens/s, Avg generation throughput: 59.7 tokens/s, Running: 0 reqs, Swapped: 0 reqs, Pending: 0 reqs, GPU KV cache usage: 0.0%, CPU KV cache usage: 0.0%.
INFO 03-03 03:59:31 metrics.py:455] Avg prompt throughput: 5.4 tokens/s, Avg generation throughput: 59.5 tokens/s, Running: 1 reqs, Swapped: 0 reqs, Pending: 0 reqs, GPU KV cache usage: 0.1%, CPU KV cache usage: 0.0%.
INFO 03-03 03:59:36 metrics.py:455] Avg prompt throughput: 5.4 tokens/s, Avg generation throughput: 59.5 tokens/s, Running: 1 reqs, Swapped: 0 reqs, Pending: 0 reqs, GPU KV cache usage: 0.1%, CPU KV cache usage: 0.0%.
INFO 03-03 03:59:41 metrics.py:455] Avg prompt throughput: 5.4 tokens/s, Avg generation throughput: 59.4 tokens/s, Running: 1 reqs, Swapped: 0 reqs, Pending: 0 reqs, GPU KV cache usage: 0.1%, CPU KV cache usage: 0.0%.
INFO 03-03 03:59:46 metrics.py:455] Avg prompt throughput: 5.4 tokens/s, Avg generation throughput: 59.5 tokens/s, Running: 1 reqs, Swapped: 0 reqs, Pending: 0 reqs, GPU KV cache usage: 0.1%, CPU KV cache usage: 0.0%.
INFO 03-03 03:59:51 metrics.py:455] Avg prompt throughput: 5.4 tokens/s, Avg generation throughput: 59.5 tokens/s, Running: 1 reqs, Swapped: 0 reqs, Pending: 0 reqs, GPU KV cache usage: 0.1%, CPU KV cache usage: 0.0%.
# 10并发
INFO 03-03 03:59:57 metrics.py:455] Avg prompt throughput: 3.4 tokens/s, Avg generation throughput: 2.4 tokens/s, Running: 10 reqs, Swapped: 0 reqs, Pending: 0 reqs, GPU KV cache usage: 0.2%, CPU KV cache usage: 0.0%.
INFO 03-03 04:00:02 metrics.py:455] Avg prompt throughput: 50.3 tokens/s, Avg generation throughput: 561.9 tokens/s, Running: 10 reqs, Swapped: 0 reqs, Pending: 0 reqs, GPU KV cache usage: 1.0%, CPU KV cache usage: 0.0%.
INFO 03-03 04:00:07 metrics.py:455] Avg prompt throughput: 53.9 tokens/s, Avg generation throughput: 553.2 tokens/s, Running: 10 reqs, Swapped: 0 reqs, Pending: 0 reqs, GPU KV cache usage: 0.8%, CPU KV cache usage: 0.0%.
INFO 03-03 04:00:12 metrics.py:455] Avg prompt throughput: 54.0 tokens/s, Avg generation throughput: 553.3 tokens/s, Running: 10 reqs, Swapped: 0 reqs, Pending: 0 reqs, GPU KV cache usage: 0.5%, CPU KV cache usage: 0.0%.
INFO 03-03 04:00:17 metrics.py:455] Avg prompt throughput: 54.0 tokens/s, Avg generation throughput: 554.3 tokens/s, Running: 10 reqs, Swapped: 0 reqs, Pending: 0 reqs, GPU KV cache usage: 0.3%, CPU KV cache usage: 0.0%.
INFO 03-03 04:00:22 metrics.py:455] Avg prompt throughput: 39.6 tokens/s, Avg generation throughput: 557.7 tokens/s, Running: 10 reqs, Swapped: 0 reqs, Pending: 0 reqs, GPU KV cache usage: 0.8%, CPU KV cache usage: 0.0%.
INFO 03-03 04:00:27 metrics.py:455] Avg prompt throughput: 50.3 tokens/s, Avg generation throughput: 556.0 tokens/s, Running: 10 reqs, Swapped: 0 reqs, Pending: 0 reqs, GPU KV cache usage: 0.8%, CPU KV cache usage: 0.0%.
INFO 03-03 04:00:32 metrics.py:455] Avg prompt throughput: 6.1 tokens/s, Avg generation throughput: 50.9 tokens/s, Running: 50 reqs, Swapped: 0 reqs, Pending: 0 reqs, GPU KV cache usage: 0.8%, CPU KV cache usage: 0.0%.
# 50并发
INFO 03-03 04:00:37 metrics.py:455] Avg prompt throughput: 172.2 tokens/s, Avg generation throughput: 1967.8 tokens/s, Running: 49 reqs, Swapped: 0 reqs, Pending: 0 reqs, GPU KV cache usage: 5.5%, CPU KV cache usage: 0.0%.
INFO 03-03 04:00:42 metrics.py:455] Avg prompt throughp