Custom Dataset Guide
Benchmark LLMs with your own data using single-turn requests, multi-turn conversations, random sampling, or production trace replay.
Overview
AIPerf supports these custom dataset types for benchmarking with your own data:
Single Turn, Multi Turn, and Random Pool support:
- Client-side batching
- Automatic media handling: local files are converted to base64 format, while remote URLs are sent directly to the API
Trace replay requests are text-only, so client-side batching and media handling do not apply. See Trace Replay and Baseten Trace Replay.
Server Setup
Start a vLLM server for testing:
Verify the server is ready:
Single-Turn Datasets
Each line represents one independent single-turn request.
When to Use
Use single_turn when you need deterministic, sequential execution where requests always run in the exact order they appear in the file:
- Debugging: Test specific prompts in a known sequence
- Regression testing: Same input file → same output order every time
- Timing control: Schedule requests with precise timestamps or delays
- Predictable testing: Know exactly which request runs when
Execution: Sequential by default (request 1, then 2, then 3, etc.) Input: Single JSONL file only
Basic Text Example
Output:
Inline alternative
Same content as prompts.jsonl, embedded in the AIPerf YAML config:
See Inline Datasets for the full feature reference.
Per-Request Output Length
Control the maximum output tokens per request using the output_length field:
Precedence: Per-line output_length takes priority over the global --osl flag. Lines without output_length fall back to --osl if set (200 in this example), or let the server decide the output length.
The output_length field also works per-turn in multi_turn datasets.
Per-Request extra
Send vendor-specific or sampling parameters per request via the extra field. The dict is shallow-merged into the top of the request body at dispatch. Per-line keys win over --extra-inputs:
The extra field also works per-turn in multi_turn datasets.
Multi-Turn Datasets
Each entry represents a complete conversation with multiple turns.
When to Use
Use multi_turn when you need conversations with context where each turn builds on previous turns in the conversation:
- Chat testing: Test conversational AI that maintains context across turns
- Realistic interactions: Simulate real user conversations with follow-up questions
- Task completion: Test multi-step tasks that require conversation history
Execution: Sequential within each conversation (turn 1, then 2, then 3, etc.), but multiple conversations run concurrently Input: Single JSONL file only
Basic Conversation
Output:
Key Points:
- Each turn includes full conversation history
- Turns execute sequentially within each conversation
- Multiple conversations run concurrently (up to
--concurrency) - Each turn supports
output_lengthandextra(same semantics as single_turn — vendor extras shallow-merged into the top of the wire body, latest turn wins for chat-style endpoints)
Inline alternative
Persistent System Prompt
To apply a system prompt to every turn of a conversation, author it as a leading turn with "role": "system". AIPerf lifts that turn into the conversation-level system message, so it is prepended to every turn’s request rather than dispatched as its own (user-less) request:
Behavior:
- The system prompt persists across all turns and is not counted as a turn (the example above runs 2 turns, not 3).
- Only a leading, text-only system turn is hoisted. A
systemturn that appears mid-conversation, one that carries image/audio/video media, or one that sets dispatch-time fields (timestamp,delay,output_length,extra) stays a normal turn. - Hoisting only takes effect on endpoints that send a system message (
chat,responses,messages,chat_embeddings). On other endpoints (e.g.completions) the leading system turn is left in place, so it is dispatched as a normal turn rather than being silently dropped. - The same form works in the inline
recordsconfig.
Random Pool Datasets
Randomly sample from one or more data pools for varied request patterns.
When to Use
Use random_pool when you need random sampling with replacement for unpredictable, varied request patterns:
- Load testing: Generate diverse request patterns with variety
- Production simulation: Model real-world workloads where requests vary
- Stress testing: Test system behavior under mixed input patterns
- Multiple data sources: Combine files from a directory (each file becomes a pool)
Execution: Random sampling with replacement (same entry can be selected multiple times) Input: Single JSONL file OR directory of multiple JSONL files Note: Does NOT support timing control or multi-turn conversations
Basic Single-File Sampling
Output:
Behavior:
- Randomly samples 50 requests from 8-entry pool
- Sampling with replacement (entries can repeat)
- Use
--random-seedfor reproducibility
Multimodal batch sizes
For multimodal workloads, random_pool supports per-modality batch-size flags that control how many items are packed into each request:
Setting a batch size to 0 for a modality that is absent from the pool is a no-op: it neither suppresses anything nor counts as batching, so --image-batch-size 0 against a single-file text-only pool leaves that pool sampled normally. A configuration where every modality is either absent from the pool or set to 0 produces empty requests and is rejected, since there is nothing left to send. Directory input rejects any batch size other than 1 regardless of which modalities are present, because pool contents are not known at config time — see below.
These flags are only valid with format: random_pool (set via --custom-dataset-type random_pool on the CLI, or format: random_pool directly in a YAML dataset config — either selects the same format). Using them with other file dataset formats (e.g. mooncake_trace) is an error.
Batch sizes other than 1 are also rejected outright whenever batching would discard pool identity: (1) multiple separately named pools — directory input (multiple files, e.g. queries.jsonl / passages.jsonl) or inline YAML records: with multiple top-level keys (e.g. records: {queries: [...], passages: [...]}) both flatten into one anonymous pool per modality, discarding the names that name-sensitive endpoints (e.g. rankings) depend on; (2) a single pool whose entries embed named Text/Image/Audio/Video objects, or Image objects carrying uuids (vLLM cache-reuse IDs) — reachable from a single file or a single-key inline records: list, not just directory input. Batch sizes only apply to a single unnamed pool with unnamed entries.
The directory case is caught up front, before the benchmark starts. The remaining shapes are only visible once the pool has been parsed, so they surface when the dataset is built.
Batching does not preserve per-entry associations across modalities. If a pool entry pairs a specific text with a specific image, a batch size > 1 flattens each modality into an independent pool and samples from them separately, so the original text-image pairing is not preserved in the resulting request. Use single_turn instead of random_pool if exact pairing must be preserved.
The pool.jsonl file above is text-only, so --image-batch-size would have no images to sample. A multimodal random_pool needs an image field in the pool entries:
Inline alternative (multi-pool)
Adding a System Prompt
--system-prompt and --system-prompt-file attach a fixed system message to every
conversation. Unlike --shared-system-prompt-length, which generates synthetic filler of a
target token length, these take the exact text — so prefix-cache hit rates and TTFT
reflect the system prompt your deployment actually sends.
They work with every dataset kind: synthetic, file-based, and public.
Or in YAML:
Behavior worth knowing:
- Tokens are additive. With
--isl 1000and a 350-token system prompt, the request carries roughly 1350 tokens:--islcontinues to size the generated user prompt only. - A dataset’s own system message is kept. If the dataset already authors one, your text is prepended to it, separated by a blank line, and both are sent as a single system message. Repeated system roles are mishandled by many OpenAI-compatible servers.
- The file is read once at startup, so a missing or unreadable path fails immediately
rather than mid-benchmark. Paths containing a symlinked component are rejected — note that
on macOS this includes anything under
/tmpor$TMPDIR, since/varis itself a symlink. - Supported on
chat,responses,messages, andchat_embeddings. Endpoints with no system role (completions,embeddings, the rankings endpoints) reject the option at startup rather than silently dropping it. - Mutually exclusive with
--shared-system-prompt-lengthand with--num-prefix-prompts/--prefix-prompt-length, all of which fill the same slot. Setting two of them is rejected at startup rather than one taking precedence, so a misconfigured run fails before sending any requests. It does combine with--user-context-prompt-lengthfor a two-tier shared/per-session structure.
Note:
--num-prefix-promptsand--prefix-prompt-lengthapply only to synthetic datasets — they are dropped for file and public datasets.--system-promptis not.
Related
- Multi-Turn Conversations - Multi-turn conversation benchmarking
- Conversation Context Mode - How conversation history accumulates in multi-turn