您可以捐助,支持我们的公益事业。

1元 10元 50元





认证码:  验证码,看不清楚?请点击刷新验证码 必填



  求知 文章 文库 Lib 视频 iPerson 课程 认证 咨询 工具 讲座 Model Center 汽车系统工程   模型库  
会员   
   
知识图谱、本体论、RAG与大模型
8月22-23日 北京+线上
企业架构方法与实践
8月27-28日 深圳+线上
AI智能体开发技术实践
9月17-18日 厦门+线上
     
   
 订阅
EvalScope模型压力测试实战 - 指南
 
 
  5   次浏览      1 次
 2026-8-18
 
编辑推荐:
文章介绍了如何使用开源工具EvalScope对微调后的Qwen2.5-1.5B模型进行压力测试(包括并发量、输出稳定性和推理性能),并演示了从部署、自定义测试内容到可视化分析及结果解读的完整流程, 希望对你的学习有帮助。
本文来自于博客园clnchanpin,由火龙果软件Alice编辑推荐。

一、问题和需求

实际生产中,我们成功在服务器上部署好了模型,可能是量化的,也可能是非量化的模型。比如使用vllm,ollama,llama.cpp等推理框架部署模型后,在日志中只能看到简单平均生成token的速度,如果是并发之类的,可能需要手动写代码进行测试,分析指标,整个过程还是挺麻烦的,本篇文章将介绍一个开源的模型压力测试工具,更好地解决我们的问题。

本章的需要做的是,实测一下我上一章微调的模型Qwen2.5-1.5B-Instruct-12lora900,使用vllm框架推理部署后,并发量,输出情况,输出稳定性这几个方面观察一下,微调后的模型部署的效果如何。

二、 EvalScope

2.1 EvalScope是什么

EvalScope 是 魔搭社区 倾力打造的模型评测与性能基准测试框架,为您的模型评估需求提供一站式解决方案。无论您在开发什么类型的模型,EvalScope 都能满足您的需求:

  • 大语言模型
  • 多模态模型
  • Embedding 模型
  • Reranker 模型
  • ️ CLIP 模型
  • AIGC模型(图生文/视频)
  • ...以及更多!

EvalScope 不仅仅是一个评测工具,它是您模型优化之旅的得力助手:

  • 内置多个业界认可的测试基准和评测指标:MMLU、CMMLU、C-Eval、GSM8K 等。
  • 模型推理性能压测:确保您的模型在实际应用中表现出色。
  • ms-swift 训练框架无缝集成,一键发起评测,为您的模型开发提供从训练到评估的全链路支持。

2.2 安装EvalScope

创建conda环境 (可选)

 conda create -n evalscope python=3.10 
# 激活conda环境
conda activate evalscope

安装包

pip install evalscope 
pip install 'evalscope[perf]' # 若要使用模型服务推理压测功能,需安装perf依赖
pip install 'evalscope[app]' #若要使用可视化功能,需安装app依赖: 

三、EvalScope模型压力测试

3.1 获取模型的url

通过vllm、llama.cpp等模型推理框架部署好模型,得到模型的url

vllm serve /root/model/Qwen2.5-1.5B-Instruct-12lora900 --max-model-len 10000
 --gpu-memory-utilization 0.75 --host 0.0.0.0 --port 8000 --served-model-name Qwen3-4B

3.2 启动命令

下面的是官方的命令,没有加上可视化的分析.

evalscope perf \
  --parallel 1 10 50 100 200 \
  --number 10 20 100 200 400 \
  --model Qwen2.5-0.5B-Instruct \
  --url http://127.0.0.1:8801/v1/chat/completions \
  --api openai \
  --dataset random \
  --max-tokens 1024 \
  --min-tokens 1024 \
  --prefix-length 0 \
  --min-prompt-length 1024 \
  --max-prompt-length 1024 \
  --tokenizer-path Qwen/Qwen2.5-0.5B-Instruct \
  --extra-args '{"ignore_eos": true}'

下面的表格清晰地展示了命令中每个核心参数的作用:

参数 值(示例) 说明
​ ​ evalscope perf ​ ​ - 这是EvalScope工具中用于启动性能压测的子命令。
​ ​ --parallel ​ ​ 1 10 50 100 200 ​ ​并发数梯度​ ​。工具将依次模拟1、10、50、100、200个​ ​同时发生​ ​的请求,以测试系统在不同压力下的表现。
​ ​ --number ​ ​ 10 20 100 200 400 ​ ​总请求数梯度​ ​。这个列表与 --parallel 列表​ ​一一对应​ ​。例如,当并发数为1时,总共发送10个请求;当并发数为200时,总共发送400个请求。
​ ​ --model ​ ​ Qwen2.5-0.5B-Instruct 被测试的​ ​模型名称​ ​。这个值需要与模型服务端返回的模型标识一致。
​ ​ --url ​ ​ http://127.0.0.1:8801/... 模型服务的​ ​API地址​ ​。命令中使用的是OpenAI API兼容的接口。
​ ​ --api ​ ​ openai 指定使用的API服务类型为OpenAI格式。
​ ​ --dataset ​ ​ random 指定使用​ ​随机生成的数据集​ ​作为测试输入。
​ ​ --max-tokens ​ ​ / ​ ​ --min-tokens ​ ​ 1024 强制模型对每个请求都​ ​生成1024个token​ ​的输出。这确保了测试输出长度的稳定性,使结果可比较。
​ ​ --min-prompt-length ​ ​ / ​ ​ --max-prompt-length ​ ​ 1024 控制随机生成的​ ​输入提示的长度​ ​也在1024个token。这样就固定了“输入1024 token,输出1024 token”的测试场景。
​ ​ --prefix-length ​ ​ 0 设置生成随机提示时的前缀长度为0。
​ ​ --tokenizer-path ​ ​ Qwen/Qwen2.5-0.5B-Instruct ​ ​分词器的路径​ ​。在使用 random 数据集时​ ​必须提供​ ​,因为工具需要用它来精确控制生成提示的token长度。
​ ​ --extra-args ​ ​ '{"ignore_eos": true}' ​ ​一个重要参数​ ​。它告诉模型​ ​忽略结束标记(EOS)​ ​,持续生成直到达到 max-tokens 的限制。这可以避免模型提前结束生成,确保每次输出都是1024个token,从而得到一致的性能测量结果

这是非可视化的数据分析结果,结果会终端展示,数据会保存起来。


每个文件的架构是这样的:

outputs/ 
├── [时间戳_测试名称]/ 
│   ├── benchmark_data.db      # 核心的SQLite数据库,包含所有原始数据 
│   ├── benchmark_summary.json  # 测试摘要(平均延迟、吞吐量等) 
│   └── benchmark_percentile.json # 百分位延迟等详细统计数据

大模型的输入和输出都在db文件里面

3.3 可视化分析启动

如果你注册了swanlab就在3.2的参数加上这两个参数

在环境中安装一下swanlab

pip install swanlab 
# ... 
--swanlab-api-key 'swanlab_api_key' 
--name 'name_of_swanlab_log' 

比如我的启动命令

evalscope perf \
--parallel 1 5 10 15 20 \ --number 10 10 20 30 40 \ --model Qwen3-4B \

--url http://0.0.0.0:8000/v1/chat/completions \ --api openai \ --dataset random \ --max-tokens 1024 \ --min-tokens 1024 \ --prefix-length 0 \ --min-prompt-length 2048 \ --max-prompt-length 2048 \ --tokenizer-path /root/model/Qwen2.5-1.5B-Instruct-12lora900 \ --extra-args '{"ignore_eos": true}' \ --swanlab-api-key 'xxxxxxxxx' \ --name 'evalscope_test'

3.4 性能指标分析

指标 英文名称 解释
首次生成token时间 TTFT (Time to First Token) 从发送请求到生成第一个token的时间(以秒为单位),评估首包延时
输出token间时延 ITL (Inter-token Latency) 生成每个输出token间隔时间(以秒为单位),评估输出是否平稳
每token延迟 TPOT (Time per Output Token) 生成每个输出token所需的时间(不包含首token,以秒为单位),评估解码速度
端到端延迟时间 Latency 从发送请求到接收完整响应的时间(以秒为单位):TTFT + TPOT * Output tokens
输入token数 Input tokens 请求中输入的token数量
输出token数 Output tokens 响应中生成的token数量
输出吞吐量 Output Throughput 每秒输出的token数量:输出tokens / 端到端延时
总吞吐量 Total throughput 每秒处理的token数量:(输入tokens + 输出tokens) / 端到端延时

  1. 吞吐量随并发增加而提升​ ​,但增长逐渐放缓
  2. 延迟随并发增加而上升​ ​,用户体验变差
  3. 并发10是较好的平衡点​ ​:吞吐量379 toks/秒,延迟27秒
  4. ​所有测试成功率100%​ ​,系统稳定性良好

这里存在一个问题,就是如果只是按照官方推荐的模型测试方案,固定输入随机字符,模型固定输出内容。这样的话,其实输入的内容是完全无意义的,可能会造成模型计算速度比实际生产的速度快很多。

3.5 自定义测试内容

参数说明 | EvalScope

我的需求是,并发用户问的问题都是同一个差不多的问题,我来模型压力测试一下,相当于固定输入token,但是模型的输出没有固定输出,这样比较符合实际的生成请求。

第一步,新建一个xxx.txt文件,文件中添加的内容就是您想反复测试的固定问题:

{"role": "user", "content": "你好,请介绍一下人工智能的发展历史,重点说明深度学习的突破性进展。"}

第二步,参考我的命令来修改对应的参数

evalscope perf \ 
--parallel 1 5 10 15 20 \
--number 10 10 20 30 40 \ 
--model Qwen3-4B \
--url http://0.0.0.0:8000/v1/chat/completions \
--api openai \
--dataset line_by_line \ 
--dataset-path /root/outputs/tollm.txt \ 
--prefix-length 0 \ 
--tokenizer-path /root/model/Qwen2.5-1.5B-Instruct-12lora900 \ 
--swanlab-api-key 'xxxxxxxxxxxxxxxxxxxxxxx' \
--name 'evalscope_test' 

这样的效果更好了。

查看一下对话内容,检测一下微调后的模型的输出稳定性:执行我的这个代码,可以得到对话的消息信息。

import base64
import json
import pickle
import sqlite3
# 替换为您的实际数据库路径
db_path = './benchmark_data.db'
conn = sqlite3.connect(db_path)
cursor = conn.cursor()
# 获取列名
cursor.execute('PRAGMA table_info(result)')
columns = [info[1] for info in cursor.fetchall()]
print('列名:', columns)
# 查询成功的请求
cursor.execute('SELECT * FROM result WHERE success=1')
rows = cursor.fetchall()
print(f'找到 {len(rows)} 条成功记录')
for i, row in enumerate(rows):
    row_dict = dict(zip(columns, row))
    # 解码请求和响应
    request_data = pickle.loads(base64.b64decode(row_dict['request']))
    response_data = pickle.loads(base64.b64decode(row_dict['response_messages']))
    print(f"\n=== 第 {i+1} 条记录 ===")
    print("=== 请求内容 ===")
    # 如果是聊天格式的请求
    if 'messages' in request_data:
        for msg in request_data['messages']:
            print(f"{msg['role']}: {msg['content'][:100]}...")  # 只打印前100字符
    else:
        print(request_data)  # 其他格式的请求
    print("=== 模型回复 ===")
    full_response = ""  # 用于累积完整回复
    # 处理流式响应(多个数据块)
    for j, resp in enumerate(response_data):
        try:
            # 先检查响应数据的类型[7](@ref)
            if isinstance(resp, dict):
                # 如果已经是字典,直接使用
                data = resp
            elif isinstance(resp, str):
                # 如果是字符串,尝试解析JSON
                # 处理流式响应可能的前缀[1](@ref)
                clean_resp = resp.strip()
                if clean_resp.startswith('data: '):
                    clean_resp = clean_resp[6:]  # 移除 'data: ' 前缀
                if clean_resp == '[DONE]':  # 流式结束标记
                    break
                data = json.loads(clean_resp)
            elif isinstance(resp, bytes):
                # 如果是字节,先解码
                resp_str = resp.decode('utf-8').strip()
                data = json.loads(resp_str)
            else:
                print(f"未知响应类型: {type(resp)}")
                continue
            # 解析响应内容
            if 'choices' in data:
                for choice in data['choices']:
                    if 'delta' in choice and 'content' in choice['delta']:
                        content = choice['delta']['content']
                        print(content, end='', flush=True)
                        full_response += content
                    elif 'message' in choice and 'content' in choice['message']:
                        content = choice['message']['content']
                        print(content, end='', flush=True)
                        full_response += content
            else:
                print(f"响应格式异常: {data.keys()}")
        except json.JSONDecodeError as e:
            print(f"JSON解析错误(块{j+1}): {e}")
            print(f"原始内容: {str(resp)[:200]}...")  # 打印前200字符用于调试
            continue
        except Exception as e:
            print(f"处理响应时出错: {e}")
            continue
    print(f"\n完整回复长度: {len(full_response)} 字符")
    print("="*50)
conn.close()

可以看到模型的回答效果很稳定,基本上每次回答的输出内容都是一致的。说明微调模型的输出稳定性是很强的。

这是vllm推理框架的对应模型的参数,这套参数可以让微调后的大模型输出比较稳定不变,适合法律等严谨性的生成任务。

SamplingParams(n=1, presence_penalty=0.0, frequency_penalty=0.0, repetition_penalty=1.1, temperature=0.0, top_p=1.0, 
top_k=0, min_p=0.0, seed=0, stop=[], stop_token_ids=[], bad_words=[], include_stop_str_in_output=False, ignore_eos=False,
 max_tokens=2048, min_tokens=0, logprobs=None, prompt_logprobs=None, skip_special_tokens=True, spaces_between_special_tokens=True,
 truncate_prompt_tokens=None, guided_decoding=None, extra_args=None), prompt_token_ids: None, prompt_embeds
 shape: None, lora_request: None, prompt_adapter_request: None.

3.5 小结和思考

1)这个框架除了可以给部署的模型进行压力测试得到较为真实的数据,还可以通过自定义数据集来对模型进行数据集的得分处理。

2)模型压力测试的参数根据自己的实际生成需求修改。

3)接下来可能要测试一下同一个微调后的模型在gguf格式下,性能会降低多少和使用llama.cpp模型推理框架来部署这个模型的话,并发量和推理速度,推理稳定性情况如何。

四 、参考内容

evalscope/README_zh.md at main · modelscope/evalscope

快速开始 | EvalScope

   
5   次浏览       1 次
相关文章

基于图卷积网络的图深度学习
自动驾驶中的3D目标检测
工业机器人控制系统架构介绍
项目实战:如何构建知识图谱
 
相关文档

5G人工智能物联网的典型应用
深度学习在自动驾驶中的应用
图神经网络在交叉学科领域的应用研究
无人机系统原理
相关课程

人工智能、机器学习&TensorFlow
机器人软件开发技术
人工智能,机器学习和深度学习
图像处理算法方法与实践

最新活动计划
知识图谱.本体论.RAG大模型 8-22[在线]
企业架构方法与实践 8-27[深圳]
FDE(前沿部署工程师)实践指南 9-8[北京]
AI智能体开发技术实践 9-17[厦门]
UAF架构体系与实践 9-22[北京]
MBSE(基于模型的系统工程)9-29[北京]
 
 
最新文章
AIGC技术与应用全解析
详解知识图谱的构建全流程
大模型升级与设计之道
自动驾驶和辅助驾驶系统
ROS机器人操作系统底层原理
最新课程
人工智能,机器学习和深度学习
人工智能与机器学习应用实战
人工智能-图像处理和识别
人工智能、机器学习& TensorFlow+Keras框架实践
人工智能+Python+大数据
成功案例
某综合性科研机构 人工智能与机器学习
某银行 人工智能+Python+大数据
北京 人工智能、机器学习& TensorFlow
某领先数字地图提供商 Python数据分析
中国移动 人工智能、机器学习和深度学习