Skip to content

Benchmarking Your Own Inference Stack

One constructed request: five token arrivals

1
2
3
4
5
Sent: 0 msLast token: 500 ms

TTFT = 300 ms · gaps = 40, 50, 40, 70 ms

Time to last token = 500 ms. Mean later gap = (500 − 300) / 4 = 50 ms.

A serving benchmark is useful when it predicts what happens to your requests. A high token rate on short, repeated prompts does not tell you whether a long coding prompt will interrupt an active chat stream.

Start with a question you can test: how much traffic can this deployment serve while meeting its latency budgets? Then keep enough detail to reproduce the answer.

Name the measurements

Time to first token (TTFT) measures the interval from a specified start point to the first generated token. Client-observed TTFT includes network and front-end work as well as queuing and prefill. Server-side TTFT may use different boundaries; label them.

Inter-token latency (ITL) is the sequence of gaps between generated tokens. Time per output token (TPOT) is usually a per-request average over the decode interval. For G>1G>1 output tokens, a common definition is

TPOT=end latency−TTFTG−1.\mathrm{TPOT}=\frac{\mathrm{end\ latency}-\mathrm{TTFT}}{G-1}.

The average does not reveal an isolated long stall. Keep token-gap tails and per-request averages separate, and state whether end latency includes the final protocol event.

End-to-end latency is the time to the final output. When the boundaries match, it is TTFT plus the sum of the remaining token gaps. There are G−1G-1 such gaps, not GG.

Output throughput is completed output tokens divided by elapsed wall-clock time. Report whether it covers a device, a node, or the entire deployment. If an eight-GPU tensor-parallel replica generates 800 tokens/s in total, its normalized rate is 100 tokens/s/GPU, not 800. Input-plus-output throughput is a different metric and must be named separately.

Report the mean, median, and relevant tail percentiles alongside sample counts and failures. Means are useful for aggregate accounting; they cannot stand in for tail latency.

Generated TTFT samples; fixed histogram scale

0130260040080012001600TTFT (ms)Requests per 50 ms bin

Mean 316 msp50 250 msp95 1093 msp99 1379 ms

Deterministic teaching data with an assumed 8% chance of an added long delay. Percentiles use nearest ranks. These are not measurements of a model or service.

A generated teaching distribution, not a measured service. Compare its center and tail; use the same summary on actual request records.

A streaming HTTP event is not necessarily one model token. It may contain only a role field, several tokens, usage data, or a finish marker. A client that increments a token counter for every event and timestamps the first event as TTFT can report convincing but incorrect numbers. Inspect the protocol and the benchmark's counting method.

Make the workload representative

Record prompt and output length distributions, request arrival times, generation settings, and prefix sharing. Also retain content where it affects execution: prefix hits depend on actual token identity; speculative acceptance, MoE routing, early stopping, and constrained decoding can depend on the text.

Long prompts deserve explicit coverage. Dense attention still has quadratic arithmetic even when FlashAttention avoids storing the complete score matrix in HBM.

constructed prompt lengths (tokens)NARROWtoyp50 220p95 316TAILEDtoyp50 140p95 746205k10k20k30k40kassumed 6% long-prompt mixturesame axes, 500-token bins, n = 4000 each

Scroll horizontally to inspect the diagram.

Bar height counts prompts. Both panels share a linear 0–4,320 height scale, with 500-token bins and 4,000 constructed prompts each.

Two constructed prompt-length distributions illustrate why a narrow synthetic workload can miss the long-prompt tail.

Use both controlled synthetic inputs and a representative trace. Synthetic cases isolate a change; trace replay tests whether that change helps the workload you intend to serve. Keep prefix caching conditions explicit so a warm repeated prompt is not compared against a cold unique one.

Sweep load, and count good results

A closed-loop concurrency sweep keeps up to a fixed number of requests outstanding. Each completion makes room for another. This is useful for exploring saturation, but slower responses automatically reduce the offered arrival rate.

An open-loop test schedules arrivals independently of completions. It can expose a growing queue that a closed-loop test hides. Replay realistic bursts or use a documented arrival process, and ensure the client can sustain it. Distinguish time waiting in a client-side semaphore from time waiting at the server.

Constructed load curves

Assumed output rate: 582 tokens/s

016003200141664256Concurrency (log₂)

Assumed p95 TTFT: 144 ms

014002800141664256Concurrency (log₂)

Assumed SLO: p95 TTFT ≤ 500 ms. This model passes it at c=8; the largest passing integer is 67. A real capacity decision also needs error rates, output latency, and repeated measurements.

rate(c) = 3200c/(36+c); tail(c) = 140 + 11 max(0,c−36) + 1.5√c.

These independent illustrative functions are not a queueing simulation or fitted benchmark. The red line applies only to TTFT.

Increase concurrency to find where this constructed load curve crosses the 500 ms TTFT threshold.

For interactive traffic, measure the request rate that meets both TTFT and streaming-latency requirements. Goodput counts successful work meeting the chosen budgets. A failed, timed-out, or truncated request must not silently disappear from the denominator. Leave operating headroom for variability rather than deploying exactly at one observed threshold.

Control the measurement conditions

Warm up the execution paths you intend to measure, and record the warmup policy. Compilation, graph capture, and allocation may affect initial requests. Separately measure startup or cache-miss behavior if it matters to the product; “warm” is not automatically more representative.

Use the model's tokenizer and chat template. Confirm whether reported counts include prompt tokens, special tokens, or speculative drafts. Pin the checkpoint revision, quantization configuration, engine version, drivers, hardware topology, power settings, and sharing/isolation policy.

Constructed traces: ten output tokens, nine later gaps

Token 1Token 2Token 3Token 4Token 5Token 6Token 7Token 8Token 9Token 10
180 ms220 ms260 ms300 ms340 ms380 ms420 ms460 ms500 ms540 ms

TTFT 180 ms + 9 × 40 ms = 540 ms to last token.

These numbers are assumed to illustrate timing boundaries, not measured multipliers. Cold starts and high load are valid conditions to benchmark separately; averaging them into one run can conceal their effects.

Constructed timelines for cold execution, steady operation, and overload. The measurement needs to identify which regime it covers.

A few hundred requests may characterize a median while providing very few observations near p99. Repeat runs, report variability, and use enough duration and samples for the tail you care about.

Single-request sweep

Concurrency 1. Vary prompt and output lengths, and control cache state. Measure baseline TTFT and decoding gaps.

request → completion → next request

Load test

Replay a representative request mix. Sweep offered arrival rate or closed-loop concurrency, record failures, and measure latency tails.

overlapping requests → queues and batches

The first test isolates per-request behavior. The second checks how scheduling and contention change it. Durations and distributions must be measured for the deployment.

First isolate single-request behavior; then test realistic concurrent arrivals.

A reproducible starting command

vLLM provides an online benchmark CLI with workload and load-pattern controls. Use the CLI belonging to the installed release; flags and supported datasets can change. Its benchmark guide explains the metric boundaries and arrival controls.

# In one terminal, after installing a compatible vLLM build:
vllm serve meta-llama/Llama-3.1-8B-Instruct
 
# In another terminal, inspect this installed version's options:
vllm bench serve --help
 
# A small synthetic baseline against the running server:
vllm bench serve \
  --model meta-llama/Llama-3.1-8B-Instruct \
  --dataset-name random \
  --num-prompts 200 \
  --max-concurrency 8 \
  --save-result

This example requires checkpoint access and suitable hardware. It leaves synthetic length defaults to the installed release, so record those defaults or set them explicitly before comparing runs. It is a smoke measurement, not a production capacity result. Extend it with a documented length distribution, warmup, arrival-rate sweep, and saved detailed records.

Convert achieved throughput into cost

Suppose a complete deployment costs an assumed $2/hour and sustains an assumed 1,800 output tokens/s over the billed interval. Its compute-only cost is

23600×1800×106≈$0.31\frac{2}{3600\times1800}\times10^6\approx\$0.31

per million output tokens. These inputs are hypothetical, not a current GPU rental quote or a measured Llama result.

Hypothetical compute cost per million output tokens

$2.0 ÷ 3,600 ÷ 1800 × 1,000,000 = $0.31

Assumed price and sustained throughput, not a current quote or measured result. Use the whole deployment’s cost and output rate over the same interval. Add other operating costs and count failures separately.

Change the assumed hourly cost and achieved output rate. The displayed cost is calculated directly from those inputs.

Use an achieved rate over the same interval as the cost. If you instead start from a peak saturated rate, account for idle time separately; do not apply an extra utilization factor to a rate that already includes it. Host memory, networking, storage, replicas, and other operating costs may belong in the final budget.

An increase in concurrency at the latency threshold lowers cost per token only if successful throughput rises relative to provisioned cost. Report both alongside the workload and latency measurements.

The next arc turns from serving weights to producing them, beginning with Pretraining Data.

Additional reading