多轮对话压测功能允许用户在真实的多轮交互场景下测试模型服务。与普通压测不同,多轮模式会将模型的真实回复追加到上下文,后续每轮请求都携带完整的历史对话,从而真实模拟用户与模型的连续对话过程,并能评估服务在上下文不断增长时的延迟、吞吐量及 KV 缓存命中率表现。
- 真实上下文累积:每轮请求成功后,将模型实际输出追加到对话历史,下一轮请求携带完整历史,而非仅发送当前用户消息。
- KV 缓存可命中率估算:基于客户端 token 计数,估算每轮请求中历史对话 token 占总输入 token 的比例,即理论上可被服务端 prefix caching 利用的上限;实际是否命中取决于服务端是否开启并维持了对应缓存。
- 多数据集支持:提供随机合成(
random_multi_turn)、真实对话(share_gpt_zh_multi_turn/share_gpt_en_multi_turn)、自定义本地数据(custom_multi_turn)和真实 Agent 轨迹(swe_smith)五种数据集。 - 参数语义一致:
--number为总对话数,--parallel为并发对话数,与普通压测模式语义保持一致。
| 参数 | 类型 | 说明 | 默认值 |
|---|---|---|---|
--multi-turn |
bool |
启用多轮对话压测模式 | False |
--min-turns |
int |
每个对话最少用户轮数,仅 random_multi_turn 使用 |
1 |
--max-turns |
int |
每个对话最多用户轮数;random_multi_turn 必须设置;ShareGPT / custom_multi_turn 等数据集可选,用于截断过长对话;swe_smith live 构建时每条对话轮次从 [min_turns, max_turns] 随机采样 |
None |
--dataset-offset |
int |
跳过数据集前 N 条对话,用于分片测试或避免缓存命中 | 0 |
--max-turn-tokens |
list[int] |
逐轮 max_tokens 覆盖值;接受一个整数列表,按 turn index(从 0 开始)指定每轮的最大输出 token 数。列表短于实际轮数时,复用最后一个值。仅在 --multi-turn 模式下生效 |
None |
swe_smith 数据集的 live 构建模式支持通过 MultiTurnArgs 对象精细控制对话 token 长度目标。
每条对话的轮次数量从 [--min-turns, --max-turns] 中随机采样,每轮按以下 token 长度参数填充消息。
| 参数 | 类型 | 说明 | 默认值 |
|---|---|---|---|
first_turn_length |
int 或 [int, int] |
第 1 轮目标 prompt token 数;从原始轨迹中截取恰好达到该长度的消息片段 • 单个整数:固定值,如 65000• 两个整数的列表: [最小值, 最大值],每条对话独立均匀随机采样,如 [32000, 65000] |
65000 |
subsequent_turn_length |
int 或 [int, int] |
后续每轮在上一轮基础上新增的目标 token 数;决定每轮 delta 的大小 • 单个整数:固定值 • 两个整数的列表: [最小值, 最大值],每条对话独立均匀随机采样 |
500 |
chars_per_token |
float |
无 tokenizer 时的字符/token 估算比,用于预过滤原始轨迹 | 3.0 |
num_workers |
int |
live 构建模式下并行 worker 数量(>1 使用 multiprocessing.Pool) | 4 |
| 参数 | 多轮模式下的含义 |
|---|---|
--number |
总 对话数;所有 worker 合计完成的对话数达到此值后停止 |
--parallel |
同时并发执行的对话数(每个 worker 持有一条对话) |
-
加载对话池:启动时从数据集文件顺序读取,将所有对话预加载到内存,最多加载
--number条(防止大数据集占用过多内存)。 -
启动 workers:创建
--parallel个并发协程(worker),各自独立运行,所有 worker 共享同一个全局对话计数器。 -
对话分配(先到先得,顺序轮询):对话按顺序分配给 worker,谁先完成当前对话,谁先取下一条。所有对话用完后从头循环复用。
以
--parallel 3、共 5 条对话为例:启动时:3 个 worker 同时开始 Worker 0 → 取第 1 条对话 Worker 1 → 取第 2 条对话 Worker 2 → 取第 3 条对话 ↓ 各自发送请求(等待回复期间其他 worker 可以继续执行) 假设 Worker 1 最先跑完第 2 条对话的所有轮次: Worker 1 → 取第 4 条对话 假设 Worker 0 第二个跑完第 1 条对话: Worker 0 → 取第 5 条对话 第 5 条用完后若还未达到 --number: Worker 0 → 循环回到第 1 条对话重新开始 ... 以此类推,谁先跑完当前对话,谁先取下一条 -
单条对话执行(逐 turn):每个 worker 持有自己独立的对话历史,不与其他 worker 共享:
第 1 轮:发送 [用户消息1] → 收到模型回复1 第 2 轮:发送 [用户消息1, 模型回复1, 用户消息2] → 收到模型回复2 第 3 轮:发送 [用户消息1, 模型回复1, 用户消息2, 模型回复2, 用户消息3] → ...每轮将用户消息追加到历史后发送请求,收到模型的真实回复后追加到历史,供下一轮携带。数据集中原有的 assistant 内容不会发给模型,仅用模型实际输出构建上下文。
-
预算控制与停止:全局对话计数在每条对话开始前同步递增,确保所有 worker 合计完成的对话数不超过
--number。一旦达到上限,所有 worker 停止,不再取新对话。 -
失败处理:某轮请求失败时,当前对话立即放弃,worker 取下一条新对话重新开始,不会将失败的上下文带入后续请求。
-
指标汇总:所有 worker 完成后,汇总全部延迟、吞吐量及多轮专属指标(平均上下文轮数、KV 缓存命中率)后输出结果。
注意:请求成功率低于 100% 时,被中断的对话不会贡献后续轮次的上下文,KV 缓存命中率等指标可能偏低。
基于 random 数据集随机生成 token 序列,每个对话包含 [min_turns, max_turns] 轮用户消息,无需外部数据文件,适合快速基准测试和性能对比。
必需参数:--tokenizer-path、--max-turns
可选参数:--min-turns(默认 1)、--min-prompt-length、--max-prompt-length(控制每轮用户消息的 token 长度范围)
数据集产出的每条对话结构如下:
[
{"role": "user", "content": "...turn 1 随机 token 序列..."},
{"role": "user", "content": "...turn 2 随机 token 序列..."}
]注意:
--tokenize-prompt在多轮模式下不支持,会被自动忽略。多轮对话始终通过/v1/chat/completions端点以消息列表形式发送。
使用示例:适用场景:快速评测服务在指定 prompt 长度分布和多轮深度下的性能,无需真实数据集。
evalscope perf \
--model YOUR_MODEL \
--tokenizer-path YOUR_MODEL \
--url OPENAI_API_COMPAT_URL \
--api openai \
--dataset random_multi_turn \
--min-prompt-length 256 \
--max-prompt-length 512 \
--max-tokens 256 \
--multi-turn \
--min-turns 2 \
--max-turns 5 \
--number 20 \
--parallel 10输出示例:
Detailed Performance Metrics
┏━━━━━━┳━━━━━━┳━━━━━━┳━━━━━━━━━┳━━━━━━━━━┳━━━━━━━━━┳━━━━━━━━━┳━━━━━━━━━┳━━━━━━━━┳━━━━━━━━━┳━━━━━━━━┓
┃ ┃ ┃ ┃ Avg ┃ P99 ┃ Avg ┃ P99 ┃ Avg ┃ P99 ┃ Gen. ┃ Success┃
┃Conc. ┃ Rate ┃ RPS ┃ Lat.(s) ┃ Lat.(s) ┃ TTFT(s) ┃ TTFT(s) ┃ TPOT(s) ┃ TPOT(… ┃ toks/s ┃ Rate┃
┡━━━━━━╇━━━━━━╇━━━━━━╇━━━━━━━━━╇━━━━━━━━━╇━━━━━━━━━╇━━━━━━━━━╇━━━━━━━━━╇━━━━━━━━╇━━━━━━━━━╇━━━━━━━━┩
│ 10 │ INF │ 4.32 │ 2.289 │ 3.541 │ 0.041 │ 0.072 │ 0.009 │ 0.011 │ 1103.48 │ 100.0%│
└──────┴──────┴──────┴─────────┴─────────┴─────────┴─────────┴─────────┴────────┴─────────┴────────┘
Request Metrics
┏━━━━━━┳━━━━━━━━━━┳━━━━━━━━━━━━━┳━━━━━━━━━━━━┳━━━━━━━━━━━━━┳━━━━━━━━━━━━┳━━━━━━━━━━━━━┳━━━━━━━━━━━━┓
┃ ┃ ┃ ┃ P99 In ┃ Avg Out ┃ P99 Out ┃ Avg ┃ Approx┃
┃Conc. ┃ Num Reqs ┃ Avg In Toks ┃ Toks ┃ Toks ┃ Toks ┃ Turns/Req ┃ Cache Hit┃
│ 10 │ 20 │ 315.4 │ 650.0 │ 92.0 │ 128.0 │ 1.60 │ 58.1%│
└──────┴──────────┴─────────────┴────────────┴─────────────┴────────────┴─────────────┴────────────┘
指标解读:
Avg Turns/Req: 1.60:测试期间每次请求平均携带 1.60 轮上下文,符合--min-turns 2 --max-turns 5的随机采样分布(第 1 轮无历史,后续轮次历史逐步增长)。Approx Cache Hit: 58.1%:约 58% 的输入 token 来自历史对话。
包含两个数据集:share_gpt_zh_multi_turn(中文)与 share_gpt_en_multi_turn(英文)。使用来自 swift/sharegpt 的真实对话数据(约 70k 条中文 / 英文对话),保留完整的 user + assistant 轮次交替结构,适合评测模型在真实对话分布下的表现。
- 数据自动下载:未指定
--dataset-path时,自动从 ModelScope 下载数据集 - 支持本地数据:通过
--dataset-path指定本地 JSONL 文件(每行一条conversation对象) - 可选截断:通过
--max-turns限制每条对话最多使用的用户轮数
本地 JSONL 数据集格式(每行一条对话):
{"conversation": [{"human": "你好", "assistant": "你好!有什么可以帮助你?"}, {"human": "帮我写一首诗", "assistant": "好的,..."}]}运行时上下文结构(第 2 轮发送时):
[
{"role": "user", "content": "你好"},
{"role": "assistant", "content": "<模型第 1 轮实际回复>"},
{"role": "user", "content": "帮我写一首诗"}
]说明:数据集中的参考助手回复仅用于结构完整性,不会被直接发送给模型。运行时 worker 始终将模型的实际输出追加到上下文,保证历史准确。
使用示例:适用场景:使用真实用户对话分布评测服务,反映更贴近生产环境的性能。
evalscope perf \
--model YOUR_MODEL \
--url OPENAI_API_COMPAT_URL \
--api openai \
--dataset share_gpt_zh_multi_turn \
--max-tokens 512 \
--multi-turn \
--max-turns 3 \
--number 300 \
--parallel 10若已本地下载数据集,可通过 --dataset-path 避免重复下载:
evalscope perf \
--model YOUR_MODEL \
--url OPENAI_API_COMPAT_URL \
--api openai \
--dataset share_gpt_zh_multi_turn \
--dataset-path /path/to/common_zh_70k.jsonl \
--max-tokens 512 \
--multi-turn \
--max-turns 3 \
--number 300 \
--parallel 10输出示例:
Detailed Performance Metrics
┏━━━━━━┳━━━━━━┳━━━━━━┳━━━━━━━━━┳━━━━━━━━━┳━━━━━━━━━┳━━━━━━━━━┳━━━━━━━━━┳━━━━━━━━┳━━━━━━━━━┳━━━━━━━━┓
┃ ┃ ┃ ┃ Avg ┃ P99 ┃ Avg ┃ P99 ┃ Avg ┃ P99 ┃ Gen. ┃ Success┃
┃Conc. ┃ Rate ┃ RPS ┃ Lat.(s) ┃ Lat.(s) ┃ TTFT(s) ┃ TTFT(s) ┃ TPOT(s) ┃ TPOT(… ┃ toks/s ┃ Rate┃
┡━━━━━━╇━━━━━━╇━━━━━━╇━━━━━━━━━╇━━━━━━━━━╇━━━━━━━━━╇━━━━━━━━━╇━━━━━━━━━╇━━━━━━━━╇━━━━━━━━━╇━━━━━━━━┩
│ 10 │ INF │ 3.87 │ 2.571 │ 4.103 │ 0.055 │ 0.098 │ 0.010 │ 0.013 │ 985.21 │ 100.0%│
└──────┴──────┴──────┴─────────┴─────────┴─────────┴─────────┴─────────┴────────┴─────────┴────────┘
Request Metrics
┏━━━━━━┳━━━━━━━━━━┳━━━━━━━━━━━━━┳━━━━━━━━━━━━┳━━━━━━━━━━━━━┳━━━━━━━━━━━━┳━━━━━━━━━━━━━┳━━━━━━━━━━━━┓
┃ ┃ ┃ ┃ P99 In ┃ Avg Out ┃ P99 Out ┃ Avg ┃ Approx┃
┃Conc. ┃ Num Reqs ┃ Avg In Toks ┃ Toks ┃ Toks ┃ Toks ┃ Turns/Req ┃ Cache Hit┃
│ 10 │ 300 │ 428.7 │ 912.0 │ 186.3 │ 384.0 │ 1.98 │ 53.7%│
└──────┴──────────┴─────────────┴────────────┴─────────────┴────────────┴─────────────┴────────────┘
指标解读:
Avg Turns/Req: 1.98:受--max-turns 3限制,平均每请求包含约 2 轮上下文,符合预期(第 1 轮无历史,第 2、3 轮分别携带 1、2 轮历史,平均约 1.98)。Approx Cache Hit: 53.7%:真实对话中上下文较长,历史 token 占比约 54%。
使用本地 JSONL 文件作为自定义多轮对话数据集,每行直接以 OpenAI messages 格式存储一条完整对话,无需任何格式转换,适合已有对话数据直接压测的场景。
- 必须指定
--dataset-path指向本地 JSONL 文件 - 可选截断:通过
--max-turns限制每条对话最多使用的用户轮数
JSONL 数据集格式(每行一条对话,为 OpenAI messages 数组):
[{"role": "user", "content": "你好"}, {"role": "assistant", "content": "你好!有什么可以帮助你?"}, {"role": "user", "content": "帮我写一首诗"}]
[{"role": "user", "content": "What is the capital of France?"}, {"role": "assistant", "content": "Paris."}, {"role": "user", "content": "Tell me more about it."}]每行需满足:
- 必须是一个 JSON 数组
- 每个元素必须包含
role和content字段 role取值为user或assistant- 至少包含一个
user消息
运行时上下文结构(第 2 轮发送时):
[
{"role": "user", "content": "你好"},
{"role": "assistant", "content": "<模型第 1 轮实际回复>"},
{"role": "user", "content": "帮我写一首诗"}
]说明:数据集中的
assistant消息仅用于标识对话结构,不会被直接发送给模型。运行时 worker 始终将模型的实际输出追加到上下文,保证历史准确。
在模拟 Agent 工具调用性能的场景中,开源模型无法像实际模型那样输出工具调用结构,导致每轮输出长度与实际模型不同。通过 --max-turn-tokens 可以逐轮限制模型的输出长度,从而近似模拟实际模型的上下文增长行为。
使用示例:10 轮对话,前 9 轮模拟工具调用(各 150 token),最后一轮输出完整回答(1000 token)。
首先准备 JSONL 数据文件(每行一条 10 轮对话,system prompt 约 4000 token):
[{"role": "system", "content": "<4000 token 的系统提示>"}, {"role": "user", "content": "帮我分析这段代码"}, {"role": "assistant", "content": "x"}, {"role": "user", "content": "继续"}, {"role": "assistant", "content": "x"}, {"role": "user", "content": "继续"}, {"role": "assistant", "content": "x"}, {"role": "user", "content": "继续"}, {"role": "assistant", "content": "x"}, {"role": "user", "content": "继续"}, {"role": "assistant", "content": "x"}, {"role": "user", "content": "继续"}, {"role": "assistant", "content": "x"}, {"role": "user", "content": "继续"}, {"role": "assistant", "content": "x"}, {"role": "user", "content": "继续"}, {"role": "assistant", "content": "x"}, {"role": "user", "content": "继续"}, {"role": "assistant", "content": "x"}, {"role": "user", "content": "请给出完整的最终回答"}]说明:assistant 消息仅定义对话结构,实际运行中会被模型的真实输出替换。
然后运行压测:
evalscope perf \\
--model YOUR_MODEL \\
--url OPENAI_API_COMPAT_URL \\
--api openai \\
--dataset custom_multi_turn \\
--dataset-path /path/to/tool_call_sim.jsonl \\
--multi-turn \\
--max-turn-tokens 150 150 150 150 150 150 150 150 150 1000 \\
--number 50 \\
--parallel 10 \\
--extra-args '{"ignore_eos": true}'| 轮次 | max_tokens |
模拟效果 |
|---|---|---|
| 第 1 轮 | 150 | 模拟首次工具调用 |
| 第 2-9 轮 | 150 | 模拟中间轮工具调用 |
| 第 10 轮 | 1000 | 最终完整回答 |
提示:列表长度不足时自动复用最后一个值给后续所有轮次。例如
--max-turn-tokens 150 1000在 10 轮对话中效果为[150, 1000, 1000, 1000, 1000, 1000, 1000, 1000, 1000, 1000](第一轮为 150,后续均为 1000)。
使用示例:适用场景:已有 OpenAI messages 格式的对话数据,直接用于多轮压测,无需转换格式。
首先准备 JSONL 数据文件(每行一条对话):
[{"role": "user", "content": "你好"}, {"role": "assistant", "content": "你好!有什么可以帮助你?"}, {"role": "user", "content": "帮我写一首诗"}, {"role": "assistant", "content": "好的,..."}, {"role": "user", "content": "再写一首"}]
[{"role": "user", "content": "介绍一下北京"}, {"role": "assistant", "content": "北京是中国的首都..."}, {"role": "user", "content": "有哪些著名景点?"}]然后运行压测:
evalscope perf \
--model YOUR_MODEL \
--url OPENAI_API_COMPAT_URL \
--api openai \
--dataset custom_multi_turn \
--dataset-path /path/to/my_conversations.jsonl \
--max-tokens 512 \
--multi-turn \
--max-turns 3 \
--number 100 \
--parallel 10使用来自 SWE-bench/SWE-smith-trajectories 的真实 Agent 代码修复轨迹数据,专为长上下文 + 多轮 Agent 场景压测设计。每条轨迹由工具调用、代码片段、Patch 结果等组成,单条 prompt 通常超过数万 token,适合评测模型在大上下文下的 prefill 吞吐和 KV 缓存命中率。
支持两种数据源模式:
- 预构建 JSON 模式(推荐):指定
--dataset-path加载已生成的agentic_dataset.json,无需 tokenizer,启动快。 - Live 构建模式(不指定
--dataset-path):运行时从 ModelScope 拉取原始轨迹并动态构建对话,必须指定--tokenizer-path用于精确 token 计数。
两种模式共同特性:
- 支持偏移:通过
--dataset-offset跳过前 N 条对话,用于分片测试或规避缓存热点
说明:每条对话的轮次数量从
[--min-turns, --max-turns]中随机采样,每轮填充的消息量由first_turn_length/subsequent_turn_length决定。
预构建数据集生成
推荐在压测前使用 examples/perf/build_swe_smith_dataset.py 脚本预先构建 agentic_dataset.json,一次生成、多次复用,避免每次压测都重复下载和构建。
主要参数:
| 参数 | 说明 | 默认值 |
|---|---|---|
--model-path |
用于精确 token 计数的 tokenizer 路径(ModelScope 模型 ID 或本地路径) | Qwen/Qwen2.5-7B-Instruct |
--first-turn-length |
第 1 轮目标 prompt token 数 | 65000 |
--subsequent-turn-length |
后续每轮新增的目标 token 数 | 500 |
--min-turns |
每条对话最少轮数 | 1 |
--max-turns |
每条对话最多轮数;实际轮数从 [min_turns, max_turns] 随机采样(与 live 构建模式行为一致);未设置时等于 --min-turns |
None |
--number |
要生成的对话条数 | 128 |
--output-path |
输出文件路径 | agentic_dataset.json |
--seed |
随机种子,保证可复现 | 42 |
--num-workers |
并行 worker 数量 | CPU 核心数 |
python examples/perf/build_swe_smith_dataset.py \
--model-path Qwen/Qwen2.5-7B-Instruct \
--first-turn-length 8192 \
--subsequent-turn-length 1024 \
--min-turns 3 \
--max-turns 8 \
--number 128 \
--output-path outputs/agentic_dataset.json \
--seed 42 \
--num-workers 8使用示例:预构建 JSON 模式(推荐)
预先生成 agentic_dataset.json 后,通过 --dataset-path 加载,无需 tokenizer,启动更快:
evalscope perf \
--model YOUR_MODEL \
--url OPENAI_API_COMPAT_URL \
--api openai \
--dataset swe_smith \
--dataset-path /path/to/agentic_dataset.json \
--max-tokens 512 \
--multi-turn \
--dataset-offset 100 \
--number 200 \
--parallel 20说明:
--dataset-offset可跳过数据集前 N 条对话,适合多机分片压测或规避 KV 缓存热点。
使用示例:Live 构建模式
自动从 ModelScope 拉取 SWE-smith-trajectories 数据集并实时构建对话,需指定 --tokenizer-path:
evalscope perf \
--model YOUR_MODEL \
--url OPENAI_API_COMPAT_URL \
--api openai \
--dataset swe_smith \
--tokenizer-path YOUR_MODEL \
--max-tokens 512 1024 \
--min-tokens 512 \
--multi-turn \
--multi-turn-args '{
"first_turn_length": [4096, 8192],
"subsequent_turn_length": [512, 1024]
}' \
--min-turns 3 \
--max-turns 8 \
--seed 42 \
--number 10 20 \
--parallel 5 10 \
--extra-args '{"ignore_eos": true}'