# 使用示例 本文按「压测对象 → 输入数据 → 请求参数 → 负载模式 → 结果观测」的顺序给出可直接复制运行的命令。完整参数含义见[参数说明](./parameters.md),多轮对话场景见[多轮对话压测](./multi_turn.md)。 ## 本地模型推理 支持本地 transformers 推理和 vLLM 推理(需先安装 vllm),无需指定 `--url`。`--model` 可填 ModelScope 模型名称(如 `Qwen/Qwen2.5-0.5B-Instruct`),也可直接指定模型权重路径(如 `/path/to/model_weights`)。 **transformers 推理**:指定 `--api local`。 ```bash evalscope perf \ --model 'Qwen/Qwen2.5-0.5B-Instruct' \ --number 20 \ --parallel 2 \ --api local \ --dataset openqa ``` 可选追加 `--attn-implementation`,取值 `flash_attention_2`、`eager` 或 `sdpa`。 **vLLM 推理**:指定 `--api local_vllm`。 ```bash evalscope perf \ --model 'Qwen/Qwen2.5-0.5B-Instruct' \ --number 20 \ --parallel 2 \ --api local_vllm \ --dataset openqa ``` ## 输入数据构造 ### 固定 prompt 用 `--prompt` 指定单条固定 prompt,所有请求发送相同内容,无需数据集。 ```bash evalscope perf \ --url 'http://127.0.0.1:8000/v1/chat/completions' \ --parallel 2 \ --model 'qwen2.5' \ --log-every-n-query 10 \ --number 20 \ --api openai \ --temperature 0.9 \ --max-tokens 1024 \ --prompt '写一个科幻小说,请开始你的表演' ``` 也可以用 `@` 前缀从本地文件读取: ```bash evalscope perf \ --url 'http://127.0.0.1:8000/v1/chat/completions' \ --parallel 2 \ --model 'qwen2.5' \ --log-every-n-query 10 \ --number 20 \ --api openai \ --temperature 0.9 \ --max-tokens 1024 \ --prompt @prompt.txt ``` ### 随机数据集 根据 `prefix-length`、`max-prompt-length` 和 `min-prompt-length` 随机生成 prompt,必需指定 `tokenizer-path`。生成 prompt 的 token 数量在 `prefix_length + min-prompt-length` 和 `prefix_length + max-prompt-length` 之间均匀分布,在一次测试中所有请求 prefix 部分相同。 ```{note} 由于 chat_template 以及 tokenize 算法的影响,生成的 prompt 的 token 数量可能有些误差,不是精确的指定 token 数量。 ``` ```bash evalscope perf \ --parallel 20 \ --model Qwen2.5-0.5B-Instruct \ --url http://127.0.0.1:8801/v1/chat/completions \ --api openai \ --dataset random \ --min-tokens 128 \ --max-tokens 128 \ --prefix-length 64 \ --min-prompt-length 1024 \ --max-prompt-length 2048 \ --number 100 \ --tokenizer-path Qwen/Qwen2.5-0.5B-Instruct \ --debug ``` ```{note} 若需要服务端收到的 token 数与配置精确匹配,可加上 `--tokenize-prompt` 参数。该参数将 prompt 在客户端 tokenize 为 token ID 列表后直接通过 `/v1/completions` 的 `prompt` 字段发送,绕过服务端的重新 tokenize。 服务端将收到恰好 `prefix_length + inner_seq_length` 个 token,落在 `[min-prompt-length, max-prompt-length]` 范围内。适用于 vLLM、SGLang、LMDeploy 等支持接收 token ID 的推理框架;不支持 `random_vl` 数据集。 ``` ### 随机图文数据集 使用 `random_vl` 数据集,随机生成图像和文本输入,在 `random` 基础上增加了图像相关参数(`image-width`、`image-height`、`image-format`、`image-num`)。 ```bash evalscope perf \ --parallel 20 \ --model Qwen2.5-VL-3B-Instruct \ --url http://127.0.0.1:8801/v1/chat/completions \ --api openai \ --dataset random_vl \ --min-tokens 128 \ --max-tokens 128 \ --prefix-length 0 \ --min-prompt-length 100 \ --max-prompt-length 100 \ --image-width 512 \ --image-height 512 \ --image-format RGB \ --image-num 1 \ --number 100 \ --tokenizer-path Qwen/Qwen2.5-VL-3B-Instruct \ --debug ``` ### 长上下文前缀注入 想用**真实语料**压测 128K/256K 级长上下文时,`random` 数据集虽然能定长,但生成的是高熵无意义 token,无法反映 Prefix-Cache 命中率、MTP 接受率等真实特征;而真实指令集大多只有 4K-8K token,直接把 `target_input_len` 设成 128K 会导致数据被筛空或长短不一。 `prefix_file` 解决这个问题:指定一份长文本语料(书籍、文档、代码库等),框架按 token 预算把它精确切成 `target_input_len − prompt 长度` 的前缀,与短 prompt 拼接,使**每条请求的输入恰好等于目标长度**,同时保持真实人类语言的低熵特征。参数说明见[长上下文前缀注入](./parameters.md#长上下文前缀注入)。 **前缀注入到 system 角色**(推荐,贴近真实 RAG 流量): ```bash evalscope perf \ --parallel 4 \ --model Qwen2.5-0.5B-Instruct \ --url http://127.0.0.1:8801/v1/chat/completions \ --api openai \ --dataset openqa \ --tokenizer-path Qwen/Qwen2.5-0.5B-Instruct \ --dataset-args '{"target_input_len": 8192, "prefix_file": "long_text.txt", "prefix_role": "system"}' \ --max-tokens 128 \ --number 20 ``` 每条请求被构造成 `[{"role": "system", "content": "<长前缀>"}, {"role": "user", "content": "<原始问题>"}]`,服务端上报的 `Input Tokens` 各条完全一致(`avg` = `p50` = `p99` = `max`),便于做单一长度点的性能对照。 **前缀拼进 user 消息**:把 `prefix_role` 改为 `user`,前缀直接拼在用户问题前面,产出单条 user 消息。适用于不希望引入 system 角色的场景。 ```bash evalscope perf \ --parallel 2 \ --model Qwen2.5-0.5B-Instruct \ --url http://127.0.0.1:8801/v1/chat/completions \ --api openai \ --dataset openqa \ --tokenizer-path Qwen/Qwen2.5-0.5B-Instruct \ --dataset-args '{"target_input_len": 131072, "prefix_file": "long_text.txt", "prefix_role": "user"}' \ --number 10 ``` ```{note} **实操要点** - **前缀语料准备**:任意 UTF-8 纯文本文件即可。语料 token 数不足以填满预算时会自动循环重复(tile)补齐并打 warning——想完全避免重复,准备一份 token 数大于 `target_input_len` 的语料。 - **别超过服务端上限**:`target_input_len` 需小于服务端的 `max_model_len`(vLLM 可用 `--max-model-len` 调整),否则请求会被拒绝。注意 chat template 本身还会额外占用十几个 token。 - **适用数据集**:`openqa`、`longalpaca`、`line_by_line`(仅纯文本行)、ShareGPT(`share_gpt_zh` / `share_gpt_en`);均需 `--tokenizer-path`。ShareGPT 按整段对话所有消息内容计量预算,总长已超过 `target_input_len` 的对话会被整条丢弃。 - **不能配 `drop`**:`prefix_file` 与 `input_len_mode="drop"` 互斥(`drop` 只留下已填满目标长度的 prompt,前缀预算恒为 0),配置时会直接报错;定长请用默认的 `cap` 并由前缀补齐。 - **Prefix-Cache 效果**:所有请求共享同一段前缀开头,重复跑同一配置时可观察到 TTFT 显著下降(推理框架命中前缀缓存)。反之,若要测**无缓存**的冷启动性能,请改用 `random` 数据集(它有 `--dataset-offset` 机制保证各轮 prompt 不同)。 - **`/v1/completions` 端点**:该端点不应用 chat template,`prefix_role="system"` 会自动降级为纯文本拼接并打 warning;此时前缀与 prompt 直接相接,拼接处可能发生 token 合并/拆分,实测总长与目标相差约 ±1 token(`prefix_role="user"` 同理,详见[参数说明](./parameters.md#长上下文前缀注入)的“拼接边界”)。 ``` ## 请求参数配置 ### 生成参数与超时 组合使用 `stop`、`stream`、`temperature` 以及读写超时等参数: ```bash evalscope perf \ --url 'http://127.0.0.1:8000/v1/chat/completions' \ --parallel 2 \ --model 'qwen2.5' \ --log-every-n-query 10 \ --read-timeout 120 \ --connect-timeout 120 \ --number 20 \ --max-prompt-length 128000 \ --min-prompt-length 128 \ --api openai \ --temperature 0.7 \ --max-tokens 1024 \ --stop '<|im_end|>' \ --dataset openqa \ --stream ``` ### 自定义请求体 用 `--query-template` 直接定义完整的请求体 JSON,其中 `%m` 和 `%p` 会被替换为模型名称和 prompt: ```bash evalscope perf \ --url 'http://127.0.0.1:8000/v1/chat/completions' \ --parallel 2 \ --model 'qwen2.5' \ --log-every-n-query 10 \ --read-timeout 120 \ --connect-timeout 120 \ --number 20 \ --max-prompt-length 128000 \ --min-prompt-length 128 \ --api openai \ --query-template '{"model": "%m", "messages": [{"role": "user","content": "%p"}], "stream": true, "skip_special_tokens": false, "stop": ["<|im_end|>"], "temperature": 0.7, "max_tokens": 1024}' \ --dataset openqa ``` 模板较长时,可写入本地 JSON 文件后用 `@` 前缀引用: ```{code-block} json :caption: template.json { "model":"%m", "messages":[ { "role":"user", "content":"%p" } ], "stream":true, "skip_special_tokens":false, "stop":[ "<|im_end|>" ], "temperature":0.7, "max_tokens":1024 } ``` ```bash evalscope perf \ --url 'http://127.0.0.1:8000/v1/chat/completions' \ --parallel 2 \ --model 'qwen2.5' \ --log-every-n-query 10 \ --read-timeout 120 \ --connect-timeout 120 \ --number 20 \ --max-prompt-length 128000 \ --min-prompt-length 128 \ --api openai \ --query-template @template.json \ --dataset openqa ``` ## 负载模式 ### Warmup 预热 在正式压测前发送一批预热请求,消除冷启动影响(如 KV-cache 填充、JIT 编译、连接池初始化等),使性能指标更准确。 预热请求使用与正式压测相同的并发和速率发送,但**不计入性能指标**(延迟、吞吐、百分位等均排除预热数据)。 在闭环(closed-loop)模式下,预热还承担另一项同等重要的职责:吸收压测启动时的瞬时冲击。若不开启预热,最初的 `--parallel` 个请求会在同一时刻被一起放出、打在空载的服务端上,彼此的 prefill 相互排队,测得的 TTFT 在后续压测中不会再复现。开启预热后,这一冲击由预热请求承担,且调度器会把并发槽位**直接交接**给正式请求、中间不排空——每个预热请求完成时恰好放出一个正式请求,而此时服务端已处于繁忙状态。因此正式压测的第一个请求,面对的就已经是满载运行中的服务端。这一点直接决定了分位数是否可信:若不开预热,最初 `--parallel` 个请求偏高的 TTFT 会和后面的正常请求一起参与分位数计算——只要 `--parallel / --number` 超过 1%,`p99` 就完全由这批启动请求决定。 这种交接只在每个并发槽位都被预热请求占满时才成立,因此闭环模式下 `--warmup-num` 应至少取 `--parallel`;取值不足时压测会打印告警并直接指明应填的值。若服务端还需吸收真正的冷启动开销,或你同时关注分位表中的 `max` 一列,可以取更大的值(如 `2 × --parallel`)。 **绝对数量模式**:`--warmup-num` 取 `>= 1` 的整数,表示预热请求的绝对条数。 ```bash evalscope perf \ --url 'http://127.0.0.1:8000/v1/chat/completions' \ --parallel 10 \ --model 'qwen2.5' \ --number 100 \ --warmup-num 10 \ --api openai \ --dataset openqa \ --stream ``` 上述命令会先发送 10 个预热请求,再发送 100 个正式压测请求,指标仅统计后 100 个请求。 **比例模式**:`--warmup-num` 取 0~1 之间的浮点数,按 `--number` 的比例计算预热数量,适用于 sweep 模式(多轮 `--number` 不同时自动适配)。 ```bash evalscope perf \ --url 'http://127.0.0.1:8000/v1/chat/completions' \ --parallel 10 \ --model 'qwen2.5' \ --number 100 \ --warmup-num 0.1 \ --api openai \ --dataset openqa \ --stream ``` `--warmup-num 0.1` 表示预热数量为 `--number` 的 10%,即 `max(1, int(0.1 * 100)) = 10` 个预热请求。 ```{note} **注意事项** - 预热请求与正式请求使用相同的数据集和请求参数。 - 预热期间会单独显示进度条(`Warmup[...]`),与正式压测进度条(`Processing[...]`)并存。闭环模式下两者会短暂重叠,因为最后几个预热响应会在首批正式请求发出之后才返回。 - `--duration` 以第一个正式请求**真正发出**的时刻为起点计时,预热不会占用计时预算。 - 多轮对话模式下,`--warmup-num` 表示预热的对话数量(与 `--number` 语义一致),预热对话内的所有 turn 均不计入指标。上述关于 `--parallel` 的建议仅适用于闭环单轮压测:开环模式按到达速率节奏发送,多轮模式统计的是对话数而非请求数。 ``` ### Open-loop 开放环路 Open-loop 模式下,请求按泊松到达调度(由 `--rate` 控制)立即发出,不等待服务端返回,从而模拟真实流量中请求到达与服务时间无关的场景。通过一次命令指定多个速率点,可自动扫描吞吐-延迟曲线。 以下示例在 5、10、20 req/s 三个速率点各发送 500、1000、2000 个请求,观察不同负载下的延迟与吞吐变化: ```bash evalscope perf \ --url 'http://127.0.0.1:8000/v1/chat/completions' \ --model 'qwen2.5' \ --api openai \ --dataset openqa \ --open-loop \ --rate 5 10 20 \ --number 500 1000 2000 \ --max-tokens 1024 \ --stream ``` ```{note} **注意事项** - `--rate` 所有值必须 **> 0**;open-loop 模式下不支持 `rate=-1`(无限速)。 - `--number` 与 `--rate` 必须**等长**,每对 `(rate, number)` 对应一轮独立压测。 - `--parallel` 在 open-loop 模式下**被忽略**(内部自动设为 INF),无需手动指定。 - open-loop 并发上限为无穷大,若服务端处理能力不足,在高速率下可能积压大量飞行中的请求,请根据服务端资源合理设置速率上限。 - 本模式与 closed-loop(默认)模式的核心区别:closed-loop 每个 worker 等待响应后再发下一条(背压保护),open-loop 不等待、按调度直接发出(更接近真实流量)。 ``` ### 生产流量回放 `workload_trace` 数据集把录制的生产流量按**原始到达节奏**逐字回放,贴近真实负载——突发到达、异构请求形状、多模型混合路由,这些是合成数据集(`random`、`openqa` 等)难以复现的。它基于 open-loop 调度,但到达时刻由 trace 里的 `timestamp` 决定,**无需 `--rate`**。 准备一个 JSONL trace 文件(每行一条请求),字段说明见[参数说明 · 生产流量回放](./parameters.md#生产流量回放): ```json {"body": {"model": "qwen-plus", "messages": [{"role": "user", "content": "你好"}]}, "timestamp": 1700000000.0} {"body": {"model": "qwen-max", "messages": [{"role": "user", "content": "写一首诗"}]}, "timestamp": 1700000001.5, "request_id": "req-42"} ``` **基础回放**:按原始时间戳逐字重放整个 trace。 ```bash evalscope perf \ --dataset workload_trace \ --dataset-path trace.jsonl \ --url http://127.0.0.1:8000/v1/chat/completions \ --open-loop ``` **倍速 + 模型映射**:2× 速率回放,把 trace 里的 `gpt-4` 映射到本地 `qwen-max`,并按记录的输出长度对齐(需 vLLM 等支持 `ignore_eos`)。 ```bash evalscope perf \ --dataset workload_trace \ --dataset-path trace.jsonl \ --url http://127.0.0.1:8000/v1/chat/completions \ --open-loop \ --dataset-args '{"speed": 2.0, "model_mapping": {"gpt-4": "qwen-max"}, "match_output_length": true}' ``` **只回放前 500 条**:用 `--number` 截断。 ```bash evalscope perf \ --dataset workload_trace \ --dataset-path trace.jsonl \ --url http://127.0.0.1:8000/v1/chat/completions \ --open-loop \ --number 500 ``` ```{note} **注意事项** - **仅支持 open-loop**:必须加 `--open-loop`,否则报错。 - **`--model` 可选且不改写 body**:每条请求保留自己的 `model`(多模型路由得以保留)。需要改模型请用 `--dataset-args` 的 `model_override`(全量替换)或 `model_mapping`(按名映射)。 - **`--number` 可选**:不传则回放全部记录,传入则截断到前 N 条。 - **时间戳须单调不减**:支持 epoch 数字或 ISO-8601 字符串;乱序会告警并按时间戳排序。 - 建议用 `--name` 指定有意义的输出目录名(不传 `--model` 时目录名默认取数据集名)。 ``` ## Embedding 与 Rerank ### Embedding 模型 使用 `openai_embedding` API 模式和 `random_embedding` 数据集进行压测。使用随机数据集时需指定 `tokenizer-path` 用于生成指定长度范围的 query 文本。 ```bash evalscope perf \ --parallel 2 \ --number 10 \ --model 'text-embedding-v4' \ --url 'https://dashscope.aliyuncs.com/compatible-mode/v1/embeddings' \ --api-key ${DASHSCOPE_API_KEY} \ --api openai_embedding \ --dataset random_embedding \ --min-prompt-length 256 \ --max-prompt-length 256 \ --tokenizer-path 'Qwen/Qwen3-Embedding-0.6B' ``` ### Rerank 模型 使用 `openai_rerank` API 模式和 `random_rerank` 数据集进行压测。使用随机数据集时需指定 `tokenizer-path` 用于生成指定长度范围的 query 文本。 可以通过 `extra-args` 指定生成数据的参数: - `num_documents`:每条 query 对应的文档数量 - `document_length_ratio`:文档长度相对于 query 长度的倍数 ```bash evalscope perf \ --parallel 2 \ --number 10 \ --model 'qwen3-rerank' \ --url 'https://dashscope.aliyuncs.com/compatible-api/v1/reranks' \ --api-key ${DASHSCOPE_API_KEY} \ --api openai_rerank \ --dataset random_rerank \ --min-prompt-length 256 \ --max-prompt-length 256 \ --tokenizer-path 'Qwen/Qwen3-Embedding-0.6B' \ --extra-args '{"num_documents": 5, "document_length_ratio": 3}' ``` ## 调试请求 使用 `--debug` 选项,我们将输出请求和响应,输出示例如下: **非 `stream` 模式输出示例** ```text 2024-11-27 11:25:34,161 - evalscope - http_client.py - on_request_start - 116 - DEBUG - Starting request: )> 2024-11-27 11:25:34,163 - evalscope - http_client.py - on_request_chunk_sent - 128 - DEBUG - Request sent: 2024-11-27 11:25:38,172 - evalscope - http_client.py - on_response_chunk_received - 140 - DEBUG - Request received: ``` **`stream` 模式输出示例** ```text 2024-11-27 20:02:24,760 - evalscope - http_client.py - _handle_stream - 57 - DEBUG - Response recevied: data: {"model":"Qwen2.5-0.5B-Instruct","object":"chat.completion.chunk","choices":[{"index":0,"delta":{"content":"重要的"},"finish_reason":null}],"usage":null} 2024-11-27 20:02:24,803 - evalscope - http_client.py - _handle_stream - 57 - DEBUG - Response recevied: data: {"model":"Qwen2.5-0.5B-Instruct","object":"chat.completion.chunk","choices":[{"index":0,"delta":{"content":""},"finish_reason":null}],"usage":null} 2024-11-27 20:02:24,847 - evalscope - http_client.py - _handle_stream - 57 - DEBUG - Response recevied: data: {"model":"Qwen2.5-0.5B-Instruct","object":"chat.completion.chunk","choices":[{"index":0,"delta":{"content":",以便"},"finish_reason":null}],"usage":null} 2024-11-27 20:02:24,890 - evalscope - http_client.py - _handle_stream - 57 - DEBUG - Response recevied: data: {"model":"Qwen2.5-0.5B-Instruct","object":"chat.completion.chunk","choices":[{"index":0,"delta":{"content":"及时"},"finish_reason":null}],"usage":null} 2024-11-27 20:02:24,933 - evalscope - http_client.py - _handle_stream - 57 - DEBUG - Response recevied: data: {"model":"Qwen2.5-0.5B-Instruct","object":"chat.completion.chunk","choices":[{"index":0,"delta":{"content":"得到"},"finish_reason":null}],"usage":null} 2024-11-27 20:02:24,976 - evalscope - http_client.py - _handle_stream - 57 - DEBUG - Response recevied: data: {"model":"Qwen2.5-0.5B-Instruct","object":"chat.completion.chunk","choices":[{"index":0,"delta":{"content":"帮助"},"finish_reason":null}],"usage":null} 2024-11-27 20:02:25,023 - evalscope - http_client.py - _handle_stream - 57 - DEBUG - Response recevied: data: {"model":"Qwen2.5-0.5B-Instruct","object":"chat.completion.chunk","choices":[{"index":0,"delta":{"content":"和支持"},"finish_reason":null}],"usage":null} 2024-11-27 20:02:25,066 - evalscope - http_client.py - _handle_stream - 57 - DEBUG - Response recevied: data: {"model":"Qwen2.5-0.5B-Instruct","object":"chat.completion.chunk","choices":[{"index":0,"delta":{"content":""},"finish_reason":null}],"usage":null} 2024-11-27 20:02:25,109 - evalscope - http_client.py - _handle_stream - 57 - DEBUG - Response recevied: data: {"model":"Qwen2.5-0.5B-Instruct","object":"chat.completion.chunk","choices":[{"index":0,"delta":{"content":""},"finish_reason":null}],"usage":null} 2024-11-27 20:02:25,111 - evalscope - http_client.py - _handle_stream - 57 - DEBUG - Response recevied: data: {"model":"Qwen2.5-0.5B-Instruct","object":"chat.completion.chunk","choices":[{"index":0,"delta":{"content":"。<|im_end|>"},"finish_reason":null}],"usage":null} 2024-11-27 20:02:25,113 - evalscope - http_client.py - _handle_stream - 57 - DEBUG - Response recevied: data: {"model":"Qwen2.5-0.5B-Instruct","object":"chat.completion.chunk","choices":[{"index":0,"delta":{},"finish_reason":"stop"}],"usage":{"prompt_tokens":50,"completion_tokens":260,"total_tokens":310}} 2024-11-27 20:02:25,113 - evalscope - http_client.py - _handle_stream - 57 - DEBUG - Response recevied: data: [DONE] ``` ## 结果可视化 ### WandB 请使用如下命令安装 wandb: ```bash pip install wandb ``` 启动测试前添加如下参数: ```bash --visualizer wandb --name 'name_of_wandb_log' ``` ![wandb sample](https://modelscope.oss-cn-beijing.aliyuncs.com/resource/wandb_sample.png) ### SwanLab 请使用如下命令安装 SwanLab: ```bash pip install swanlab ``` 启动测试前添加如下参数: ```bash # 可使用 SWANLAB_PROJ_NAME 环境变量指定项目名称 --visualizer swanlab --name 'name_of_swanlab_log' ``` ![swanlab sample](https://sail-moe.oss-cn-hangzhou.aliyuncs.com/yunlin/images/evalscope/swanlab.png) ### ClearML 请使用如下命令安装 ClearML: ```bash pip install clearml ``` 初始化 ClearML 服务器: ```bash clearml-init ``` 启动测试前添加如下参数: ```bash # 可使用 CLEARML_PROJECT_NAME 环境变量指定项目名称 --visualizer clearml --name 'name_of_clearml_task' ``` ![clearml sample](https://sail-moe.oss-cn-hangzhou.aliyuncs.com/yunlin/images/evalscope/doc/clearml_vis.jpg)