大模型性能测试工具:Aisbench和Evalscope性能测试TPOT差异定位报告
作者:昇腾实战派
知识地图:https://blog.csdn.net/Lumos_Lovegood/article/details/161601003
1. 背景介绍
在大模型服务化部署与性能评估领域,Aisbench与Evalscope作为业界主流的开源性能测试工具,已广泛应用于模型推理性能验证场景。其中,Aisbench 作为华为官方推荐使用的测试工具,凭借其对 vLLM 等主流推理框架的深度支持、稳态性能分析能力及可视化特性,已在多个内部及客户项目中得到验证与应用。
近期,我们发现,针对同一 vLLM 服务实例(使用相同输入与输出长度配置),Aisbench 测得的 TPOT(Time Per Output Token,每输出一个 Token 所需时间)性能指标显著优于 Evalscope 测试结果。
TPOT 作为衡量大模型推理性能的核心指标之一,直接影响着实际业务场景中的推理效率与资源规划。当前差异现象不仅关系到工具选择的准确性,更涉及性能评估标准的理解深度与工具推荐的可信度。为确保一致、准确的性能评估,亟需系统性地分析并定位 Aisbench 与 Evalscope 在 TPOT 测试结果上存在差异的根本原因。
2. 定位记录
2.1 准备测试复现脚本
在此报告中,使用DeepSeek-V4-Flash-w8a8-mtp模型作为样例进行测试,vLLM-Ascend 使用0.21.0rc1版本镜像,服务部署脚本如下:
export OMP_PROC_BIND=false
export OMP_NUM_THREADS=10
export PYTORCH_NPU_ALLOC_CONF=expandable_segments:True
export LD_PRELOAD=/usr/lib/aarch64-linux-gnu/libjemalloc.so.2:$LD_PRELOAD
export HCCL_BUFFSIZE=1024
export VLLM_ASCEND_APPLY_DSV4_PATCH=1
export VLLM_ASCEND_ENABLE_FLASHCOMM1=1
export TASK_QUEUE_ENABLE=1
export HCCL_OP_EXPANSION_MODE="AIV"
vllm serve /home/weights/DeepSeek-V4-Flash-w8a8-mtp \
--max_model_len 133120 \
--max-num-batched-tokens 8192 \
--served-model-name dsv4 \
--gpu-memory-utilization 0.9 \
--max-num-seqs 48 \
--data-parallel-size 1 \
--tensor-parallel-size 8 \
--enable-expert-parallel \
--tokenizer-mode deepseek_v4 \
--tool-call-parser deepseek_v4 \
--enable-auto-tool-choice \
--reasoning-parser deepseek_v4 \
--safetensors-load-strategy 'prefetch' \
--profiler-config '{"profiler": "torch", "torch_profiler_dir": "./vllm_profile", "torch_profiler_with_stack": true}' \
--no-enable-prefix-caching \
--model-loader-extra-config='{"enable_multithread_load": "true", "num_threads": 128}' \
--quantization ascend \
--port 8900 \
--block-size 128 \
--hf-overrides '{"use_index_cache": true, "index_topk_freq": 4}' \
--speculative-config '{"num_speculative_tokens": 3,"method": "mtp","enforce_eager": true}' \
--compilation-config '{"cudagraph_mode": "FULL_DECODE_ONLY"}'\
--async-scheduling \
--additional-config '{
"ascend_compilation_config":{
"enable_npugraph_ex":true,
"enable_static_kernel":false
},
"enable_cpu_binding": true,
"enable_dsa_cp": true,
"multistream_overlap_shared_expert":true
}'
2.1.1 Evalscope 性能测试
Evalscope 最初使用的是 ShareGPT 数据集,后来更换使用和 Aisbench 相同的 GSM8K 数据集。输入长度 5120,输出长度 512,测试脚本如下:
from evalscope.perf.main import run_perf_benchmark
from evalscope.perf.arguments import Arguments
task_cfg = Arguments(
parallel=[1,2,4],
number=[16,32,64],
model='dsv4',
url='http://0.0.0.0:8900/v1/chat/completions',
api='openai',
dataset='openqa',
dataset_path='/home/benchmark/ais_bench/datasets/gsm8k/test.jsonl',
min_tokens=512,
max_tokens=512,
prefix_length=0,
min_prompt_length=5120,
max_prompt_length=5120,
tokenizer_path='/home/weights/DeepSeek-V4-Flash-w8a8-mtp',
extra_args={'ignore_eos': True}
)
results = run_perf_benchmark(task_cfg)
print(results)
2.1.2 Aisbench 性能测试
Aisbench 测试同样使用 GSM8K 数据集,同步修改 benchmark/ais_bench/benchmark/configs/models/vllm_api/vllm_api_stream_chat.py文件中的相关配置,测试命令:
ais_bench --models vllm_api_stream_chat --datasets gsm8k_gen_0_shot_cot_str_perf --debug --summarizer default_perf --mode perf --num-prompts ${TOTAL_REQ}
其中 ${TOTAL_REQ} 为并发数的 16 倍,即请求 16 轮。
2.2 性能收集
在输入相同、输出 token 数相同的情况下,对比 Aisbench 和 Evalscope 的测试结果,整体趋势如下:
- TTFT 上两者耗时一致,相差较小;
- TPOT 上两者差异较大,Evalscope 的 TPOT 明显高于 Aisbench(约为其两倍),即 Aisbench 的 TPOT 指标更优。
2.3 输入输出分析
因 TPOT 性能数据差异较大,首先查看两个工具输出的 token 情况:

对测试结果进行整理,发现 Aisbench 和 Evalscope 测试时输入的请求内容相同,输出内容前 10+ 个 token 相同,无法看出其他有用信息。
2.4 vLLM 日志分析
观察推理时的 vLLM 服务日志,查看在 Avg generation tokens 速率和 MTP 采信率上是否存在差异。
-
单并发下,Evalscope 测试时,MTP 采信率大概在 65%~73% 之间,同时 Avg generation tokens 在 25 个左右,与 TPOT 数据能对应:

-
单并发下,Aisbench 测试时,MTP 采信率大概在 60%~78% 之间,同时 Avg generation tokens 在 40 个左右,与 TPOT 结果能对应:

Evalscope 和 Aisbench 在 MTP 采信率上并无差异,但在整体 Avg generation tokens 生成速率上,却存在较大差异,因此需要采集 Profiling 分析原因。
3. 采集分析 Profiling
vLLM-Ascend 自带 profiling 采集功能,在 vLLM 启动命令中添加 --profiler-config '{"profiler": "torch", "torch_profiler_dir": "./vllm_profile", "torch_profiler_with_stack": false}' \即可。具体使用可以查看官方指导。
在此文档中,因为需要查看具体的堆栈信息,所以需要将 "torch_profiler_with_stack": 改为 true。
在 Aisbench 或 Evalscope 执行性能测试的时候,我们通过执行脚本完成 profiling 的采集:
curl -X POST http://localhost:8900/start_profile
sleep 5
curl -X POST http://localhost:8900/stop_profile
采集完之后进行解压:
from torch_npu.profiler.profiler import analyse
analyse("./vllm_profile/localhost.localdomain_*_ascend_pt/")
解压完成后通过 MindstudioInsight 工具 进行 profiling 的查看。
3.1 Profiling 耗时分析
在 Profiling 对比中,发现两个工具的耗时差异主要体现在单次 decode 及 MTP 阶段:
Evalscope 单次 decode(含 MTP)耗时较高:

其中 MTP 阶段耗时占比较高:

Aisbench 单次 decode(含 MTP)耗时相对较低,明显低于 Evalscope:

其中 MTP 阶段耗时占比相对较低:

综合来看,两者主模型耗时基本相当,耗时差异主要集中在 MTP 推理阶段。这里我们先将 MTP 特性关闭,观察 TPOT 性能数据是否一致。
3.2 MTP 性能影响验证
关闭 MTP 后,排除 MTP 对性能的影响,分别使用 Aisbench 和 Evalscope 工具测试性能,观察性能数据。
在 vLLM 服务中删除
--speculative-config '{"num_speculative_tokens": 3,"method": "mtp","enforce_eager": true}' \,即关闭 MTP。
关闭 MTP 后的测试结果显示,Aisbench 和 Evalscope 的 TTFT 与 TPOT 表现一致,因此可以将问题定界在 MTP 推理特性上。
3.3 MTP 阶段 profiling 分析
MTP 推理目前采用的是 enforce_eager 模式,推理过程中会存在 Free 空泡。观察 Aisbench 和 Evalscope 的 Free 耗时占比:
Aisbench MTP 阶段推理耗时情况:

Evalscope MTP 阶段推理耗时情况:

对比发现,Aisbench 和 Evalscope 的耗时差异主要在 Free 耗时阶段,Evalscope 的 Free 耗时明显高于 Aisbench(多出数十毫秒)。
从 “Communication (Not Overlapped)” 耗时为 0 可以看出,通信耗时已被计算耗时掩盖,因此这里不统计。
观察 Evalscope 在 CPU 上的算子下发情况,发现在 MTP 推理开始前存在一大段的 copy 耗时(占比明显偏高),导致后续 MTP 推理算子需要临时下发,产生 Free 空泡:


而 Aisbench 测试时,则没有这一段 copy 耗时,保证 MTP 推理算子早早下发,减少 Free 空泡:


这说明,Aisbench 工具测试和 Evalscope 工具测试,在 vLLM 推理时走的代码逻辑存在不一致,因此需要根据 profiling 堆栈信息进入代码查看。
代码定位与验证
观察 profiling 堆栈信息,发现两者在 /vllm-workspace/vllm/vllm/v1/sample/logits_processor/builtin.py:apply_with_spec_decode 方法上存在差异:
Aisbench 测试时,在这个方法上没有走任何逻辑:

Evalscope 测试时,则存在大段的 copy 耗时:

代码添加打点与验证
走读代码,发现在方法最开始的地方,就会判断 self.min_toks 参数,如果它是 None,则会直接 return,不进行任何处理:
## builtin.py:apply_with_spec_decode
def apply_with_spec_decode(
self,
logits: torch.Tensor,
num_draft_tokens: list[int],
) -> torch.Tensor:
"""Spec-decode version of apply().
Priority: ``min_tokens`` > ``stop_token_ids`` / EOS.
Example: ``num_draft_tokens = [2, 3, 1]``
→ ``logits`` shape ``[6, V]``, ``cumsum = [0, 2, 5, 6]``
→ request 0 owns rows 0‑1, request 1 rows 2‑4, request 2 row 5.
"""
if not self.min_toks: # ← 猜测Aisbench测试时,self.min_toks为None,直接return
return logits
num_draft_arr = np.array(num_draft_tokens, dtype=np.int64)
cumsum = np.concatenate([[0], np.cumsum(num_draft_arr)])
## ...
## 省略剩余代码
在 apply_with_spec_decode 方法最前段添加 print(self.min_toks) 验证猜测:
Aisbench 测试,self.min_toks 为空字典:

Evalscope 测试,self.min_toks 不为空:

因此可以判定,Aisbench 工具和 Evalscope 工具在测试时,self.min_toks 不一致,导致 decode 耗时存在差异。
询问 deepwiki,走读代码调用栈发现,请求中的 min_tokens 参数会影响 MinTokensLogitsProcessor 类的 self.min_toks 参数的更新逻辑:

self.min_toks 参数更新逻辑代码调用栈
其中 Aisbench 请求未设置 min_tokens 参数,所以 self.min_toks 始终为空字典;而 Evalscope 请求中将 min_tokens 设置为 512,self.min_toks 参数不为空。
将 Evalscope 测试代码中的 min_tokens 参数设置为 None,重新测试,观察测试结果,两个工具测得的性能数据能对齐。
经验总结
在大模型服务性能评估实践中,不同测试工具(如 Aisbench 与 Evalscope)虽均遵循行业标准规范,但因统计逻辑、采样策略或测试参数的细微差异,可能导致同一服务的 TPOT(Time Per Output Token)等核心指标出现差异。作为业界开源且广泛验证的测试工具,其统计方法在设计原则上应保持合理性和一致性。当遇到此类指标差异时,建议通过以下方法进行系统性排查:
- vLLM 日志深度分析:验证 Avg Generation Tokens 速率与 TPOT 的正相关性(即生成速率越高,TPOT 值越低,性能越优)。若测试指标为 TTFT(Time to First Token),则需同步关注 Avg Prompt Tokens 速率与 TTFT 的关联性,确保指标计算逻辑一致。
- MTP 特性专项验证:如服务启用了多请求处理(MTP)特性,需重点核查 MTP 采信率是否异常。MTP 采信率波动可能导致并发性能统计偏差,影响 TPOT 计算结果。
- 请求参数一致性校验:在 vLLM 服务端捕获并比对两个测试工具的完整客户端请求参数(包括输入序列长度、输出序列长度、温度参数、top_p 等关键配置),确保测试条件完全一致。
- 系统级性能剖析:通过 profiling 工具采集深度性能数据,分析关键算子耗时分布、计算与 CPU 资源调度、请求队列处理等环节,精准定位性能瓶颈所在。
通过以上四步结构化排查方法,可有效识别测试工具差异的根本原因,确保性能评估结果的客观性与可比性。此方法论已通过多个客户现场验证,能显著提升性能差异定位效率,为模型服务优化提供可靠依据。
附录:min_tokens 逻辑
min_tokens 参数能确保在允许输出结束符(如 EOS 或 stop_token_ids)出现之前,每个输出序列必须生成的最小 token 数量。当存在 ignore_eos=True 参数时,不需要指定 min_tokens 参数,也能达到最大的 max_tokens 输出长度。
当存在这个参数时,vLLM 中的逻辑会是:
-
当 MTP(speculative decoding)特性开启时,只有
MinTokensLogitsProcessor会被加载,其他 logits processors 会被禁用 init 方法。因为 speculative decoding 需要特殊的处理逻辑来确保 draft tokens 的验证正确性:
-
在
RejectionSampler中,MinTokensLogitsProcessor通过apply_with_spec_decode方法被特殊调用:
-
apply_with_spec_decode方法会根据 draft tokens 的数量和位置,对尚未达到最小 token 数的请求的 stop token 进行掩码处理,具体流程为:3.1. 计算 draft token 分布: 将
num_draft_tokens转换为 numpy 数组,并计算累积和,确定每个请求在 logits tensor 中的行范围。例如:num_draft_tokens = [2, 3, 1]→cumsum = [0, 2, 5, 6],表示请求 0 占用行 0-1,请求 1 占用行 2-4,请求 2 占用行 5;
3.2. 筛选需要处理的请求:从
self.min_toks中提取有 stop_token_ids 的请求条目;
3.3. 计算掩码位置:对每个请求,计算还需要生成多少 token 才能达到
min_tokens,然后确定需要掩码的 draft token 位置数量;
3.4. 构建掩码索引:对需要掩码的位置,构建行索引和 token 索引,使用
np.repeat和np.tile进行扩展;
3.5. 应用掩码:将所有需要掩码的位置的 logits 设置为
-inf,阻止模型在这些位置生成 stop token;
-
非 MTP(speculative Decoding)模式下,所有内置的 logits processors 都会被加载,
MinTokensLogitsProcessor使用标准的apply方法而不是apply_with_spec_decode方法,因此当不开启 MTP 时,Aisbench 和 Evalscope 测试的 TPOT 结果并无差异:
鲲鹏昇腾开发者社区是面向全社会开放的“联接全球计算开发者,聚合华为+生态”的社区,内容涵盖鲲鹏、昇腾资源,帮助开发者快速获取所需的知识、经验、软件、工具、算力,支撑开发者易学、好用、成功,成为核心开发者。
更多推荐
所有评论(0)