VLLM部署Qwen大模型性能压测实践:自研Python压测脚本+指标解读

摘要

本文基于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 脚本核心逻辑说明

  1. 线程池并发 :使用ThreadPoolExecutor实现并发请求,控制并发worker数量,模拟多用户同时访问大模型服务。
  2. 流式接口采集TTFT :调用vLLM流式接口,记录收到第一个delta块的时间,精准计算TTFT,贴近真实用户对话场景。
  3. 关闭思考模式 :通过extra_body传入vLLM自定义参数enable_thinking=False,关闭Qwen系列模型的思考输出,减少无效token。
  4. 本地分词器统计真实Token:使用模型原生tokenizer统计输出文本token数量,避免服务端返回token计数不准的问题。
  5. P95长尾延迟计算:对全部TTFT排序,取95%分位数,衡量压力下的最坏延迟,比平均值更适合生产评估。

环境依赖安装

复制代码
pip install openai transformers

5 压测使用方法

  1. 修改配置区参数:接口地址、密钥、模型路径、并发数、总请求数、prompt。
  2. 建议渐进加压:并发从2、4、8、16、20逐步增加,观察TTFT和GPU显存占用,找到服务性能拐点。
  3. 执行脚本: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
======================================

结果解读

  1. Token吞吐基本不变:Token吞吐代表GPU每秒生成token的硬件上限,和单次输出长度无关。
  2. QPS随生成长度下降:同样并发下,单请求生成token越长,单次请求占用GPU时间越长,单位时间完成请求数QPS降低。
  3. TTFT稳定:两种场景P95首token延迟基本持平,说明vLLM的PagedAttention机制在不同输出长度下,首包延迟表现稳定。

7 调优与踩坑总结

  1. 并发不要一次性拉满:并发过高会造成KV缓存争抢,TTFT长尾延迟飙升,服务稳定性下降。
  2. 显存监控:压测同时监控GPU显存,vLLM启动时一次性预分配KV缓存,显存占用固定。
  3. 错误捕获:脚本捕获网络异常、服务崩溃异常,统计失败请求,评估服务稳定性。
  4. 压测建议:生产环境压测,优先关注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

相关推荐
薛定猫AI2 小时前
【技术干货】多AI团队大模型协作编排与编程项目评测架构解析
python·机器学习·ai·大模型·技术实战
深海鱼肝油ya2 小时前
企业级RAG问答系统(一)数据处理模块
人工智能·大模型·pdf文件解析·rag问答系统·智能分块
User_芊芊君子2 小时前
官方 URDF 之外:把启元 Q1 放进 MuJoCo 物理仿真跑起来
安全·ai·大模型
Tiandaren2 小时前
么是agent、为什么要做agent、怎么做agent、怎么工程化
人工智能·python·深度学习·大模型
艾莉丝努力练剑6 小时前
【AI大模型接入SDK】ChatServer接口实现
c++·人工智能·学习·ai·架构·大模型·sdk
小七-七牛开发者20 小时前
周一上线 | DeepSeek 开始卷“许愿式编程”,Agent 手机来了,Character.AI 申请破产
ai·大模型·agent·claude·codex·deepseek·ai coding
海带紫菜菠萝汤1 天前
PostgreSQL 19 延期一个月:AI 把找 bug 变便宜了,修 bug 还是人工价
人工智能·ai·程序员·架构·大模型
缘友一世1 天前
GLM5.3 CyberSec w4A16本地部署[2]:并行策略与 PP 切分
ubuntu·vllm·glm5.3 cybersec