摘要
本文基于vLLM推理框架部署Qwen3.5-4B大模型,采用Python线程池实现并发压测,完整采集TTFT、P95首token延迟、QPS、Token吞吐等核心推理指标,讲解压测脚本实现原理、指标含义与测试结果分析,适合大模型私有化部署、推理性能调优场景参考。压测脚本可直接复用,开箱即用。
1 背景介绍
在大模型私有化部署场景中,vLLM凭借PagedAttention核心技术,实现了高吞吐低开销的大模型推理服务。上线前必须对推理服务做并发压力测试,量化首token延迟、吞吐能力等核心性能指标,评估服务承载能力。
市面上部分压测工具功能臃肿,部分工具无法对接OpenAI兼容接口,同时缺少对TTFT(Time To First Token,首token生成耗时)的精准采集。本文手写轻量化压测脚本,兼容vLLM OpenAI兼容接口,可自定义并发数、总请求量、最大生成长度,输出专业性能指标。
测试环境:
- 推理框架:vLLM
- 模型:Qwen3.5-4B
- GPU:A10(24G显存)
- 部署方式:Docker容器私有化部署
- 接口协议:OpenAI兼容API
2 压测核心指标定义
| 指标 | 说明 | 工程意义 |
|---|---|---|
| TTFT(Time To First Token) | 首Token响应耗时,从发起请求到收到第一个输出token的时间 | 衡量用户侧感知延迟,是对话场景最核心指标 |
| P95 TTFT | 95分位首token延迟,排序后取95%位置的延迟值 | 反映高压力下的长尾延迟,比平均值更有生产参考价值 |
| QPS | 每秒成功完成的请求数量 | 代表接口每秒可以处理多少轮对话请求 |
| Token吞吐(tokens/s) | GPU每秒生成token总数 | 衡量GPU硬件推理上限,评估算力利用率 |
3 完整压测Python源码
import time
import statistics
from concurrent.futures import ThreadPoolExecutor, as_completed
from openai import OpenAI
from transformers import AutoTokenizer
# ===================== 【压测配置区,直接修改这里】 =====================
API_ADDRESS = "[http://localhost:8006/v1](http://localhost:8006/v1)"
API_SECRET_KEY = "xiaoyi"
MODEL_ID = "Qwen3.5-4B"
TOKENIZER_LOCAL_PATH = "/workspace/models/Qwen3.5-4B" # 本地模型目录,分词器
TOTAL_REQUEST_COUNT = 100 # 总请求数量
CONCURRENT_WORKER_NUM = 20 # 并发数,建议从2/4/8逐步加压
MAX_OUTPUT_TOKEN_LIMIT = 1000 # 单次回答最大生成token,TTFT压测推荐256
USER_QUESTION = "简单介绍人工智能大模型的应用场景10000字"
# ======================================================================
tokenizer = AutoTokenizer.from_pretrained(TOKENIZER_LOCAL_PATH, trust_remote_code=True)
client = OpenAI(base_url=API_ADDRESS, api_key=API_SECRET_KEY)
record_collection = []
request_failure_count = 0
def single_benchmark_request():
global request_failure_count
try:
req_start_timestamp = time.perf_counter()
stream_response = client.chat.completions.create(
model=MODEL_ID,
messages=[{"role": "user", "content": USER_QUESTION}],
stream=True,
max_tokens=MAX_OUTPUT_TOKEN_LIMIT,
extra_body={
"chat_template_kwargs": {
"enable_thinking": False
}
}
)
first_token_arrival = None
full_generated_text = ""
for chunk in stream_response:
delta_text = chunk.choices[0].delta.content
if delta_text is not None:
if first_token_arrival is None:
first_token_arrival = time.perf_counter()
full_generated_text += delta_text
# 使用本地Qwen分词器统计真实输出token
output_token_count = len(tokenizer.encode(full_generated_text, add_special_tokens=False))
ttft_duration = first_token_arrival - req_start_timestamp
return {
"ttft": ttft_duration,
"output_token_num": output_token_count,
"success_flag": True
}
except Exception as err:
request_failure_count += 1
print(f"请求执行异常:{str(err)}")
return {"success_flag": False}
def run_benchmark_task():
"""压测主逻辑封装函数"""
print(f"==== 启动大模型并发压测 ====")
print(f"并发线程数:{CONCURRENT_WORKER_NUM},总请求数:{TOTAL_REQUEST_COUNT},最大输出token:{MAX_OUTPUT_TOKEN_LIMIT}")
global_test_start = time.perf_counter()
# 线程池执行并发请求
with ThreadPoolExecutor(max_workers=CONCURRENT_WORKER_NUM) as pool:
task_list = [pool.submit(single_benchmark_request) for _ in range(TOTAL_REQUEST_COUNT)]
for future in as_completed(task_list):
task_result = future.result()
if task_result["success_flag"]:
record_collection.append(task_result)
global_test_end = time.perf_counter()
total_test_duration = global_test_end - global_test_start
success_req_total = len(record_collection)
# 指标计算
all_ttft_values = [item["ttft"] for item in record_collection]
all_output_tokens = [item["output_token_num"] for item in record_collection]
average_ttft = statistics.mean(all_ttft_values)
sorted_ttft_array = sorted(all_ttft_values)
p95_pos = int(success_req_total * 0.95)
p95_ttft_result = sorted_ttft_array[p95_pos]
qps_value = success_req_total / total_test_duration
sum_total_output_tokens = sum(all_output_tokens)
token_throughput = sum_total_output_tokens / total_test_duration
avg_generated_tokens = statistics.mean(all_output_tokens)
std_generated_tokens = statistics.stdev(all_output_tokens) if success_req_total > 1 else 0
# 输出报告
print("\n===== 大模型并发压测统计报告 =====")
print(f"成功完成请求数量: {success_req_total}/{TOTAL_REQUEST_COUNT}")
print(f"平均首Token响应耗时(TTFT): {average_ttft:.2f} 秒")
print(f"首Token延迟 P95分位值: {p95_ttft_result:.2f} 秒")
print(f"每秒请求数(QPS): {qps_value:.2f}")
print(f"Token吞吐速率: {token_throughput:.2f} tokens/s")
print(f"单次输出token数量 均值: {avg_generated_tokens:.2f}")
print(f"单次输出token数量 标准差: {std_generated_tokens:.2f}")
print(f"失败请求总数: {request_failure_count}")
print("======================================\n")
if __name__ == "__main__":
run_benchmark_task()
4 脚本核心逻辑说明
- 线程池并发 :使用
ThreadPoolExecutor实现并发请求,控制并发worker数量,模拟多用户同时访问大模型服务。 - 流式接口采集TTFT :调用vLLM流式接口,记录收到第一个delta块的时间,精准计算TTFT,贴近真实用户对话场景。
- 关闭思考模式 :通过
extra_body传入vLLM自定义参数enable_thinking=False,关闭Qwen系列模型的思考输出,减少无效token。 - 本地分词器统计真实Token:使用模型原生tokenizer统计输出文本token数量,避免服务端返回token计数不准的问题。
- P95长尾延迟计算:对全部TTFT排序,取95%分位数,衡量压力下的最坏延迟,比平均值更适合生产评估。
环境依赖安装
pip install openai transformers
5 压测使用方法
- 修改配置区参数:接口地址、密钥、模型路径、并发数、总请求数、prompt。
- 建议渐进加压:并发从2、4、8、16、20逐步增加,观察TTFT和GPU显存占用,找到服务性能拐点。
- 执行脚本:
python bench_vllm.py,直接输出压测报告。
6 测试结果与分析
测试用例A:并发20,总请求100,max_output_token=256
===== 大模型并发压测统计报告 =====
成功完成请求数量: 100/100
平均首Token响应耗时(TTFT): 0.20 秒
首Token延迟 P95分位值: 0.42 秒
每秒请求数(QPS): 3.00
Token吞吐速率: 767.72 tokens/s
单次输出token数量 均值: 255.78
单次输出token数量 标准差: 0.54
失败请求总数: 0
======================================
测试用例B:并发20,总请求100,max_output_token=1000
===== 大模型并发压测统计报告 =====
成功完成请求数量: 100/100
平均首Token响应耗时(TTFT): 0.22 秒
首Token延迟 P95分位值: 0.41 秒
每秒请求数(QPS): 0.77
Token吞吐速率: 765.36 tokens/s
单次输出token数量 均值: 999.50
单次输出token数量 标准差: 1.09
失败请求总数: 0
======================================
结果解读
- Token吞吐基本不变:Token吞吐代表GPU每秒生成token的硬件上限,和单次输出长度无关。
- QPS随生成长度下降:同样并发下,单请求生成token越长,单次请求占用GPU时间越长,单位时间完成请求数QPS降低。
- TTFT稳定:两种场景P95首token延迟基本持平,说明vLLM的PagedAttention机制在不同输出长度下,首包延迟表现稳定。
7 调优与踩坑总结
- 并发不要一次性拉满:并发过高会造成KV缓存争抢,TTFT长尾延迟飙升,服务稳定性下降。
- 显存监控:压测同时监控GPU显存,vLLM启动时一次性预分配KV缓存,显存占用固定。
- 错误捕获:脚本捕获网络异常、服务崩溃异常,统计失败请求,评估服务稳定性。
- 压测建议:生产环境压测,优先关注P95延迟,而不是平均值;平均值容易掩盖长尾高延迟请求。
8 总结
本文实现了轻量化vLLM大模型压测脚本,原生支持vLLM OpenAI兼容接口,可采集TTFT、P95延迟、QPS、Token吞吐核心指标。脚本代码量精简,无第三方重型压测工具依赖,适合私有化部署大模型上线前性能验证。 vLLM依靠PagedAttention,在固定并发场景下,Token吞吐由GPU算力决定,不受单次生成长度影响;而QPS和单请求输出token长度强相关。在工程落地时,需要结合业务prompt长度、可接受TTFT,选择合理并发值。
配套资源:完整脚本已贴在文中,可直接复制使用。 本文配套环境:vLLM + Qwen3.5-4B,支持Qwen系列推理时关闭思考模式。
写在最后
平台内已上线**《** 大模型 入门精品课》+ 简历和面试指导 (对于小白和想转行小伙伴而已,如果没有简历和面试辅导是很难通过面试的),课程内容包括:
-
纯干货一个月学完,没有冗余的理论,项目都是大型企业项目,非demo。
-
覆盖 RAG、智能体、Lora微调实战项目、vLLM私有化部署大模型、模型压缩(剪枝、量化、蒸馏)。
-
感兴趣的小伙伴可以前往主页了解!课程地址: https://edu.csdn.net/course/detail/41422