Multi-turn Conversation Benchmark#
The multi-turn conversation benchmark allows you to test a model service in realistic multi-turn interaction scenarios. Unlike a standard benchmark, multi-turn mode appends the modelβs actual replies to the conversation context so that every subsequent request carries the full history β faithfully simulating a userβs continuous conversation with the model, and enabling measurement of latency, throughput, and KV-cache utilization as context grows.
Features#
Real context accumulation: After each successful turn, the modelβs actual output is appended to the conversation history; the next turn sends the complete history rather than just the current user message.
Approx KV cache hit rate estimation: Based on client-side token counts, estimates the proportion of history tokens relative to the total input tokens in each request β i.e., the theoretical upper bound of tokens that could benefit from server-side prefix caching. Whether caching actually occurs depends on whether the server has prefix caching enabled and has retained the relevant cache.
Multiple dataset support: Provides four datasets β random synthetic (
random_multi_turn), real conversations (share_gpt_zh_multi_turn/share_gpt_en_multi_turn), and custom local data (custom_multi_turn).Consistent parameter semantics:
--numberis the total number of turns and--parallelis the number of concurrent turns, keeping the same semantics as standard benchmark mode.
Parameters#
Multi-turn Specific Parameters#
Parameter |
Type |
Description |
Default |
|---|---|---|---|
|
|
Enable multi-turn conversation benchmark mode |
|
|
|
Minimum number of user turns per conversation; used by |
|
|
|
Maximum number of user turns per conversation; required for |
|
Semantics of Existing Parameters in Multi-turn Mode#
Parameter |
Meaning in multi-turn mode |
|---|---|
|
Total number of turns to send (not conversations); all workers stop once this many HTTP requests have been made |
|
Number of concurrently in-flight turn-level requests |
Datasets#
random_multi_turn#
Generates synthetic token sequences based on the random dataset. Each conversation contains [min_turns, max_turns] user turns. No external data file is required, making it ideal for quick benchmarking and performance comparisons.
Required: --tokenizer-path, --max-turns
Optional: --min-turns (default 1), --min-prompt-length, --max-prompt-length (control the token length range of each user message)
Each conversation produced by the dataset has the following structure:
[
{"role": "user", "content": "...turn 1 random token sequence..."},
{"role": "user", "content": "...turn 2 random token sequence..."}
]
Note:
--tokenize-promptis not supported in multi-turn mode and will be silently ignored. Multi-turn conversations are always sent as message dicts to the/v1/chat/completionsendpoint.
custom_multi_turn#
Uses a local JSONL file as a custom multi-turn conversation dataset. Each line stores a complete conversation directly in OpenAI messages format β no format conversion required. Ideal for benchmarking with your own existing conversation data.
--dataset-pathis required and must point to a local JSONL file.Optional truncation: Use
--max-turnsto limit the maximum number of user turns per conversation.
JSONL dataset format (one conversation per line, as an OpenAI messages array):
[{"role": "user", "content": "Hello"}, {"role": "assistant", "content": "Hi! How can I help you?"}, {"role": "user", "content": "Write me a poem"}]
[{"role": "user", "content": "What is the capital of France?"}, {"role": "assistant", "content": "Paris."}, {"role": "user", "content": "Tell me more about it."}]
Each line must satisfy:
Must be a JSON array.
Every element must have
roleandcontentfields.rolemust be eitheruserorassistant.Must contain at least one
usermessage.
Runtime context structure (when sending turn 2):
[
{"role": "user", "content": "Hello"},
{"role": "assistant", "content": "<model's actual reply to turn 1>"},
{"role": "user", "content": "Write me a poem"}
]
Note: The
assistantmessages in the dataset are used only to identify conversation structure and are never sent directly to the model. At runtime, workers always append the modelβs actual output to the context to ensure accurate history.
Workflow#
Load conversation pool: At startup, conversations are read sequentially from the dataset file and pre-loaded into memory, up to a maximum of
--numberconversations (to avoid excessive memory usage with large datasets).Start workers:
--parallelconcurrent coroutines (workers) are launched, each running independently and sharing a single global turn counter.Conversation assignment (first-come, first-served, sequential cycling): Conversations are assigned to workers in order. Whoever finishes the current conversation first picks up the next one. Once all conversations are exhausted, cycling restarts from the beginning.
Example with
--parallel 3and 5 conversations:Startup: 3 workers begin simultaneously Worker 0 β takes conversation 1 Worker 1 β takes conversation 2 Worker 2 β takes conversation 3 β Each sends requests (other workers can continue executing while waiting for replies) Suppose Worker 1 finishes all turns of conversation 2 first: Worker 1 β takes conversation 4 Suppose Worker 0 finishes conversation 1 second: Worker 0 β takes conversation 5 If --number is not yet reached after conversation 5: Worker 0 β cycles back to conversation 1 ... and so on β whoever finishes first picks up the next oneSingle-conversation execution (turn by turn): Each worker maintains its own independent conversation history, not shared with other workers:
Turn 1: send [user_msg_1] β receive model_reply_1 Turn 2: send [user_msg_1, model_reply_1, user_msg_2] β receive model_reply_2 Turn 3: send [user_msg_1, model_reply_1, user_msg_2, model_reply_2, user_msg_3] β ...After each turn, the user message is appended to the history before sending the request. The modelβs actual reply is then appended for use in the next turn. Dataset
assistantcontent is never sent to the model β only real model outputs build the context.Budget control and stopping: The global turn counter is incremented synchronously before each request is sent, ensuring the total number of requests across all workers does not exceed
--number. Once the limit is reached, all workers stop and no new conversations are started.Failure handling: If a turn request fails, the current conversation is immediately abandoned and the worker starts a new conversation from scratch β no failed context is carried into subsequent requests.
Metrics aggregation: After all workers finish, all latency, throughput, and multi-turn-specific metrics (average context turns, KV cache hit rate) are aggregated and output.
Note: When the request success rate is below 100%, interrupted conversations do not contribute subsequent turns to the context, which may result in lower reported KV cache hit rates.
Output Metrics#
In addition to all standard benchmark metrics, multi-turn mode outputs two extra metrics:
Metric |
Description |
How to interpret |
|---|---|---|
|
Average number of user turns in the context at the time each request is sent |
Reflects the average growth of context during the test; a higher value means deeper conversations and longer prompts |
|
Estimated proportion of history tokens relative to total input tokens (theoretical upper bound for prefix caching benefit) |
A higher ratio means more of the input is historical context; if the server has prefix caching enabled, a higher ratio translates to greater latency savings. Calculation: |
All other metrics (Avg/P99 Latency, TTFT, TPOT, RPS, TPS) have the same meaning as in standard benchmark mode.
Usage Examples#
1. Using random_multi_turn (Synthetic Multi-turn Conversations)#
Use case: Quickly evaluate service performance at a specified prompt length distribution and conversation depth without a real dataset.
evalscope perf \
--model Qwen2.5-7B-Instruct \
--tokenizer-path Qwen/Qwen2.5-7B-Instruct \
--url http://127.0.0.1:8801/v1/chat/completions \
--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
Example output:
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%β
ββββββββ΄βββββββββββ΄ββββββββββββββ΄βββββββββββββ΄ββββββββββββββ΄βββββββββββββ΄ββββββββββββββ΄βββββββββββββ
Interpreting the metrics:
Avg Turns/Req: 1.60: Each request carried an average of 1.60 turns of context during the test, consistent with the--min-turns 2 --max-turns 5random sampling distribution.Approx Cache Hit: 58.1%: About 58% of input tokens came from conversation history.
3. Using custom_multi_turn (Custom Local Conversations)#
Use case: You have conversation data already in OpenAI messages format and want to benchmark directly without any format conversion.
First, prepare the JSONL data file (one conversation per line):
[{"role": "user", "content": "Hello"}, {"role": "assistant", "content": "Hi! How can I help you?"}, {"role": "user", "content": "Write me a poem"}, {"role": "assistant", "content": "Sure, ..."}, {"role": "user", "content": "Write another one"}]
[{"role": "user", "content": "What is the capital of France?"}, {"role": "assistant", "content": "Paris."}, {"role": "user", "content": "What are some famous landmarks there?"}]
Then run the benchmark:
evalscope perf \
--model Qwen2.5-7B-Instruct \
--url http://127.0.0.1:8801/v1/chat/completions \
--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