Personalize this lesson
Adapt explanations and teaching visuals to your background and preferred voice.
A new serving build finishes 12% more requests per second in a short test. It looks ready until six mixed requests arrive in a tight burst. First tokens slow down, live streams pause, and two responses fail the output schema.
The 12% number wasn't false. It answered the wrong question. A release benchmark must reproduce the workload that creates queueing, measure the moments users feel, and reject fast answers that aren't acceptable answers.
GPU Profiling, Correctness, and Benchmarking established how to time completed GPU work and preserve a kernel receipt. Continuous Batching and Scheduling showed why prompt processing and token generation compete. GPU Serving and Autoscaling turned that contention into a fleet capacity problem, while Distributed Inference Data Plane followed request and KV-cache state across workers. Here those pieces become one release test.
The running artifact is incident-assistant-v3, a fixed request trace for an incident-response assistant. Baseline and candidate must receive the same requests at the same scheduled times. Only then can their operating curves be compared.
Freeze workload before measuring system
A benchmark workload has three independent parts:
- What arrives: prompt text or token IDs, output policy, priority, shared prefixes, and correctness expectations.
- When it arrives: a realized timestamp for every request.
- How completion is judged: latency service-level objectives (SLOs), error rules, and quality checks.
A seed and a distribution name aren't a frozen trace. Library versions can change random-number streams, tokenizers can change token counts, and two candidates can draw different long-tail samples. Generate once, save realized rows, hash file, and replay it.
The small incident-assistant-v3 fixture contains six request identities:
| ID | Scheduled arrival | Prompt tokens | Output policy | Prefix | Class | Pressure exposed |
|---|---|---|---|---|---|---|
r01 | 0 ms | 320 | natural stop, cap 96 | runbook-a | interactive | warm short request |
r02 | 120 ms | 336 | natural stop, cap 96 | runbook-a | interactive | prefix reuse |
r03 | 700 ms | 4,096 | natural stop, cap 256 | none | interactive | long prefill |
r04 | 760 ms | 280 | natural stop, cap 64 | none | interactive | TTFT behind prefill |
r05 | 800 ms | 2,048 | natural stop, cap 512 | policy-b | background | long decode occupancy |
r06 | 1,900 ms | 480 | natural stop, cap 128 | runbook-a | interactive | recovery after burst |
Prompt tokens must be counted with pinned tokenizer. max_tokens is only a cap, not actual output length. Store generated length after run as a result, never as if server knew it at admission.
Forced output lengths have a legitimate but narrower use. Setting an engine to ignore end-of-sequence tokens can isolate scheduler capacity at exact shapes. It also creates requests users wouldn't send and can hide quality changes. Label such run a synthetic stress test, then keep natural-stop trace as release gate.
Preserve distributions, not only averages
An average 512-token prompt can mean every prompt has 512 tokens, or half have 64 and half have 960. Those workloads create different prefill batches, KV-cache pressure, and queue tails.
Keep at least these workload properties:
| Property | Why average loses it | Fixture field |
|---|---|---|
| Prompt-length distribution | long prompts create prefill interference | prompt text or token IDs plus token count |
| Natural output distribution | long generations occupy decode slots longer | output cap plus observed output length |
| Prompt/output correlation | some request classes are long in both phases | stable request ID and class |
| Shared-prefix frequency | cache hits change prefill work | prefix group and cache policy |
| Priority and tenant mix | scheduler policy can starve one class | priority or tenant label |
| Streaming mode | buffered responses hide token cadence | protocol and streaming flag |
BurstGPT publishes a real serving trace for studying arrival, concurrency, token-length, conversation, and failure patterns.[1] It doesn't make those patterns representative of your endpoint. ServeGen builds parameterized, sanitized workloads by composing per-client arrival and data models.[2] It remains a model of traffic. Validate either source against your own request classes before turning it into a release fixture.
The fixture now says what and when. Next it needs a load model that doesn't erase overload.
Keep offered load independent of response time
An open-loop generator schedules arrivals independently of server completions. If r04 is due at 760 ms, it arrives then even while r03 is still in prefill. Queue growth remains visible.
A closed-loop generator sends a new request only after a prior response completes. When server slows, generator also slows. That feedback makes a saturated system look calmer because offered rate falls with service rate.
| Load model | Arrival trigger | Good use | Dangerous interpretation |
|---|---|---|---|
| Open-loop rate | prerecorded time or external clock | SLO capacity, burst, and queue tests | client must keep up with schedule |
| Closed-loop concurrency | response frees virtual user | max-concurrency and backpressure studies | can't establish independent offered-rate capacity |
| All-at-once | every request at time zero | offline throughput or admission stress | not normal online traffic |
MLPerf Inference makes distinction explicit. Its Server/Interactive scenario uses Poisson arrivals, while Single Stream issues next query after previous one finishes. Valid runs also pair scenario latency rules with quality requirements.[3] Those rules are useful design evidence. A custom endpoint test isn't an MLPerf result unless it follows full applicable rule set and submission process.
Rate, burstiness, and concurrency are different knobs
Arrival rate is intended requests per second. Concurrency is number currently outstanding. In a stable system, Little's Law gives useful check:[4]
is average outstanding work and is average time in system. At 2 requests per second and 1.5 seconds average end-to-end latency, expect about 3 requests in flight. A concurrency cap of 2 will throttle generator below intended 2 requests per second.
Burstiness describes variation in inter-arrival gaps. One simple summary is coefficient of variation:
Exponential inter-arrival times from a Poisson process have . A trace with has more variable gaps by this summary, often with clusters followed by quiet periods. One number still can't preserve diurnal changes, correlated clients, or a planned release burst. Store timestamps when those patterns matter.
Current vllm bench serve can generate finite-rate traffic, vary Gamma-distributed inter-arrival times with --burstiness, cap outstanding requests with --max-concurrency, and report goodput-related metrics.[5] Its parameter named burstiness is Gamma shape , not coefficient of variation: , values below 1 produce more bursty gaps, and 1 gives Poisson traffic. Record tool version and realized schedule rather than relying on flag name.
You schedule 3 requests per second, observe 900 ms mean end-to-end latency, and cap client concurrency at 2. Can this run establish capacity at 3 requests per second?
Answer
No. Little's Law predicts about requests in flight at that rate, above client cap. Generator will delay or suppress arrivals before server sees intended load. Raise cap, retain scheduled and sent timestamps, and verify schedule lag before treating run as open-loop capacity evidence.
Detect client-side coordinated omission
Record five timestamps or event series:
| Field | Owner | What it reveals |
|---|---|---|
scheduled_at | fixture | intended arrival under open-loop load |
sent_at | client | generator schedule lag |
accepted_at | gateway or server | network and admission delay |
token_at[] | streaming client or server | first-token and token-cadence timing |
completed_at | client | full response latency |
If client schedules r04 for 760 ms but can't send until 1,100 ms, measuring from sent_at deletes 340 ms of load-generator delay. Keep both values. Fail run when schedule lag exceeds preregistered client budget, because benchmark client, not server, became bottleneck.
One trap remains: a benchmark can reproduce arrivals perfectly while measuring startup noise instead of operating state.
Separate warmup, steady state, and drain
Warmup prepares mechanisms that production path expects to be warm. It may include model loading, tokenizer initialization, memory allocation, just-in-time compilation, CUDA Graph capture, connection setup, and scheduler stabilization. It must not secretly prefill prefix cache when production requests normally miss it.
Use phase boundaries that can be audited:
| Phase | Admission | Metrics | Exit rule |
|---|---|---|---|
| Readiness | health probes only | startup time separately | model and workers ready |
| Warmup | representative requests | excluded from steady result | declared compile/capture paths complete and latency stabilizes |
| Steady state | frozen open-loop trace | all request and system events | fixed window or fixed realized trace completes |
| Drain | no new arrivals | completion and timeout outcomes retained | every admitted request finishes or reaches deadline |
Don't start clock after queue is already full. Don't stop at final arrival and discard slow completions. Drained completions belong to request outcomes, but drain time must not turn an unstable arrival window into sustainable throughput. Track queue depth or unfinished-work slope during steady state.
A practical stability gate might require all of following, with values chosen before run:
- client schedule lag stays below budget;
- achieved arrival rate stays close to offered rate;
- queue depth has no persistent positive slope;
- completed rate doesn't keep falling across windows;
- error, timeout, and rejection rates remain within bounds;
- temperature, clocks, replica count, and worker restarts are recorded.
Cold-start and warm-capacity tests answer different questions. Publish separate curves rather than averaging them.
Baseline runs first from cold process. Candidate runs second with compiled kernels and populated prefix cache, then posts lower TTFT. What comparison should release review accept?
Answer
Neither raw result is attributable to candidate code. Declare cache and compilation state, apply same warmup policy to both systems, alternate run order, and repeat boundary points. Keep cold-start curve separate when startup behavior belongs to product contract.
Measure moments users can distinguish
Let request arrive at , first output token appear at , final token at , and output contain tokens.
Time to first token (TTFT) includes queueing and prefill from chosen client boundary:
Inter-token latency (ITL), also called time between tokens, is each gap after first:
Time per output token (TPOT) averages decode cadence after first token:
For , TPOT and ITL are undefined. Report those requests separately instead of dividing by zero or assigning zero.
End-to-end latency (E2E) covers full wait:
Consider one five-token response with arrival-relative token times [240, 280, 320, 440, 480] ms. TTFT is 240 ms. ITLs are [40, 40, 120, 40] ms. TPOT is ms, and E2E latency is 480 ms.
A 60 ms mean hides one 120 ms pause. That pause may or may not be visible because earlier tokens arrived ahead of playback pace. We need both distribution and sequence.
Percentiles need named populations
p99 ITL = 120 ms is incomplete. Did calculation pool every token gap, compute p99 per request then take p99 across requests, or use one request's gaps? Long responses dominate pooled-token distribution because they contribute more samples.
Choose denominator that matches promise:
| Promise | Suggested population |
|---|---|
| first response starts promptly | request-level TTFT |
| each session streams smoothly | per-request worst or high-percentile ITL, then percentile across requests |
| random emitted token gap stays small | pooled ITL events, with token weighting stated |
| whole response completes promptly | request-level E2E, stratified by length class |
Report sample count with every high percentile. A p99 from 40 requests is effectively one extreme observation, not stable tail estimate. Repeat runs or bootstrap intervals can expose that uncertainty, but they can't repair unrepresentative trace.
Fluidity respects token deadlines
Etalon proposes fluidity index, a deadline-based view of streaming. It sets a first-token deadline based on prefill size and a desired token cadence. Tokens produced early create slack. A late token consumes slack; if it misses deadline, later deadlines reset so one stall isn't counted forever.[6]
For target TTFT 300 ms and token cadence 80 ms, nominal deadlines are 300, 380, 460, 540, and 620 ms. Our token times 240, 280, 320, 440, and 480 ms stay ahead of them. The raw 120 ms ITL exceeds 80 ms, but user already has buffered tokens, so this trace remains fluid under deadline model.
Now shift token times to 300, 380, 460, 580, and 620 ms. Average TPOT is still 80 ms, yet fourth token misses 540 ms deadline. Mean TPOT alone can't distinguish these experiences.
Fluidity doesn't replace TTFT, E2E, or raw ITL. It answers different question: did token stream meet playback deadlines after accounting for useful early work?
Metrics now describe one run. A release decision still needs curve over increasing load.
Find saturation on operating curve
Run same fixture at increasing offered rates. Preserve request order, shape mix, arrival-burst pattern, seed, warmup rule, and duration. Time-scaling realized timestamps is acceptable when goal is rate sweep and transformation is recorded.
The table below is a synthetic worked fixture, not hardware result. Its release contract requires at least 95% of requests to pass correctness, TTFT ms, and request-mean TPOT ms. Client schedule lag must remain below 20 ms and backlog slope at or below 0.02 requests per second. The p99 ITL column pools all token gaps; each response has four gaps, so every request has equal weight in this fixture.
| Offered RPS | Completed RPS | p95 TTFT | p95 TPOT | Pooled p99 ITL | Contract attainment | Backlog slope | Gate |
|---|---|---|---|---|---|---|---|
| 0.5 | 0.50 | 170 ms | 44 ms | 71 ms | 100% | 0.00 | Pass |
| 1.0 | 0.99 | 220 ms | 49 ms | 75 ms | 100% | 0.00 | Pass |
| 1.5 | 1.48 | 285 ms | 61 ms | 82 ms | 95% | 0.00 | Pass |
| 2.0 | 1.82 | 510 ms | 86 ms | 140 ms | 80% | +0.16 | Fail |
| 2.5 | 1.86 | 920 ms | 121 ms | 260 ms | 58% | +0.61 | Fail |
Raw throughput keeps rising from 1.48 to 1.82 completed requests per second. Release capacity doesn't. At 2.0 offered RPS, queue grows and tail latency fails. Highest passing offered rate is 1.5 RPS.
DistServe defines goodput as maximum request rate served while meeting declared TTFT and TPOT SLO attainment goal per provisioned GPU.[7] For broader release contract here:
is offered rate, is TTFT deadline, is decode deadline, and is required attainment. Add ITL or fluidity gate when streaming smoothness belongs to product contract. Add stability predicates so short overloaded run can't pass by draining backlog later.
This equation reports fleet request rate. At stable point, offered and completed rates converge apart from bounded errors and rejections. Divide by fixed provisioned GPU count only when per-GPU goodput is comparison target.

At 2.0 offered RPS, system produces 1.46 contract-compliant completions per second, slightly above 1.41 at 1.5 RPS. Should release boundary move to 2.0 RPS?
Answer
No. Aggregate count of acceptable completions isn't same as request-level attainment. At 2.0 RPS, only 80% of requests pass 95% contract and backlog grows. Highest stable passing offered rate remains 1.5 RPS, with production operating point chosen below measured cliff.
Run one point below and one above selected boundary. Repeat boundary point, alternate baseline and candidate order, and sweep downward once when cache state or thermal history may cause hysteresis.
Compute percentiles and goodput from request records
The exercise below creates four deterministic synthetic load points. Each request carries scheduled arrival, token timestamps, and correctness. Nearest-rank percentile is explicit, as are per-request SLO and backlog gates.
1from dataclasses import dataclass
2from math import ceil
3
4@dataclass(frozen=True)
5class RequestTrace:
6 scheduled_ms: float
7 token_ms: tuple[float, ...]
8 correct: bool = True
9
10 @property
11 def ttft_ms(self) -> float:
12 return self.token_ms[0] - self.scheduled_ms
13
14 @property
15 def tpot_ms(self) -> float:
16 if len(self.token_ms) < 2:
17 raise ValueError("TPOT is undefined for one-token output")
18 return (self.token_ms[-1] - self.token_ms[0]) / (len(self.token_ms) - 1)
19
20 @property
21 def itl_ms(self) -> tuple[float, ...]:
22 return tuple(
23 current - previous
24 for previous, current in zip(self.token_ms, self.token_ms[1:])
25 )
26
27def percentile(values: list[float], q: float) -> float:
28 """Nearest-rank percentile with a stated, reproducible convention."""
29 ordered = sorted(values)
30 return ordered[max(0, ceil(q * len(ordered)) - 1)]
31
32def make_run(offered_rps, ttfts, gap_patterns, bad_quality=()):
33 traces = []
34 for index, (ttft, gaps) in enumerate(zip(ttfts, gap_patterns, strict=True)):
35 scheduled = index * 1000 / offered_rps
36 first = scheduled + ttft
37 tokens = [first]
38 for gap in gaps:
39 tokens.append(tokens[-1] + gap)
40 traces.append(RequestTrace(scheduled, tuple(tokens), index not in bad_quality))
41 return traces
42
43runs = {
44 0.5: (make_run(
45 0.5,
46 [140] * 18 + [170, 170],
47 [(40,) * 4] * 18 + [(44,) * 4, (35, 35, 35, 71)],
48 ), 0.00),
49 1.0: (make_run(
50 1.0,
51 [190] * 18 + [220, 220],
52 [(46,) * 4] * 18 + [(49,) * 4, (40, 40, 41, 75)],
53 ), 0.00),
54 1.5: (make_run(
55 1.5,
56 [250] * 18 + [285, 340],
57 [(58,) * 4] * 18 + [(61,) * 4, (79, 79, 80, 82)],
58 ), 0.00),
59 2.0: (make_run(
60 2.0,
61 [270] * 17 + [410, 510, 620],
62 [(68,) * 4] * 18 + [(86,) * 4, (76, 76, 76, 140)],
63 bad_quality=(5,),
64 ), 0.16),
65}
66
67TTFT_SLO_MS = 300
68TPOT_SLO_MS = 80
69ATTAINMENT_TARGET = 0.95
70MAX_BACKLOG_SLOPE = 0.02
71passing_rates = []
72
73print("offered p95_ttft p95_tpot p99_itl attainment stable gate")
74for offered, (traces, backlog_slope) in runs.items():
75 passed = [
76 trace.correct
77 and trace.ttft_ms <= TTFT_SLO_MS
78 and trace.tpot_ms <= TPOT_SLO_MS
79 for trace in traces
80 ]
81 attainment = sum(passed) / len(passed)
82 stable = backlog_slope <= MAX_BACKLOG_SLOPE
83 gate = attainment >= ATTAINMENT_TARGET and stable
84 pooled_itls = [gap for trace in traces for gap in trace.itl_ms]
85 if gate:
86 passing_rates.append(offered)
87 print(
88 f"{offered:>6.1f} {percentile([t.ttft_ms for t in traces], 0.95):>8.0f} ms"
89 f" {percentile([t.tpot_ms for t in traces], 0.95):>8.0f} ms"
90 f" {percentile(pooled_itls, 0.99):>6.0f} ms"
91 f" {attainment:>10.0%} {str(stable):>6} {'PASS' if gate else 'FAIL'}"
92 )
93
94print(f"fixture goodput: {max(passing_rates):.1f} requests/s")1offered p95_ttft p95_tpot p99_itl attainment stable gate
2 0.5 170 ms 44 ms 71 ms 100% True PASS
3 1.0 220 ms 49 ms 75 ms 100% True PASS
4 1.5 285 ms 61 ms 82 ms 95% True PASS
5 2.0 510 ms 86 ms 140 ms 80% False FAIL
6fixture goodput: 1.5 requests/sAt 2.0 RPS, three requests miss TTFT and one otherwise fast request fails quality, leaving 16 of 20 accepted. The predicate counts each request once even when one request violates several conditions.
Why does p99 ITL select single 71 ms maximum at 0.5 RPS? Each load point has 20 requests with four token gaps, so nearest-rank p99 of 80 pooled gaps selects 80th ordered value. Likewise, request-level p95 selects 19th of 20 requests. Larger samples are needed for stable tail claims. Code states population and convention so another implementation can reproduce them.
Change ATTAINMENT_TARGET to 0.99. The 1.5 RPS point fails because 19 of 20 is 95%, and fixture goodput drops to 1.0 RPS. SLO isn't decoration on graph. It changes answer.
Keep quality inside acceptance predicate
Serving candidate can alter output even when model weights stay fixed. Quantization changes numerical behavior. Scheduler order can expose nondeterminism. Speculative decoding, tokenizer changes, stop handling, truncation, or a malformed streaming parser can change result contract.
Use two linked suites:
- Performance trace: representative arrival and shape fixture, with every response checked for protocol success, finish reason, token count, and schema validity.
- Quality set: stable prompts with task-specific scoring, such as exact tool arguments, source-grounded claims, refusal behavior, structured-output validity, or human-reviewed rubric.
MLPerf treats quality as model's ability to produce correct outputs and requires valid benchmark run to meet scenario performance and quality constraints.[3] Carry same principle into custom benchmark without borrowing compliance label.
Don't count these as good requests:
- HTTP success with empty body;
- truncated stream missing terminal event;
- timeout completed during drain;
- gateway rejection or client cancellation;
- schema-valid answer with wrong tool argument;
- correct text produced after latency deadline when deadline is part of product contract.
For stochastic generation, exact string equality may be wrong gate. Pin decoding when possible, then score invariant behavior. Compare quality with enough examples to detect agreed regression. Performance curve is meaningless if candidate silently changed task.
Token telemetry must match protocol
Server-sent event chunk isn't necessarily one token. A server may buffer several tokens into one chunk, while tokenizer can split chunk text differently after Unicode boundaries. Chunk-to-chunk delay is valid client experience metric, but it isn't token ITL unless protocol guarantees token granularity or server emits token timestamps.
Name metric honestly:
| Available evidence | Safe name | Unsupported claim |
|---|---|---|
| first response byte | time to first byte | TTFT without proving byte carries token |
| streamed text chunks | inter-chunk latency | per-token ITL |
| token IDs plus server timestamps | token ITL | network playback cadence without client timestamps |
| client token events | user-observed ITL | scheduler-only latency |
Quality gate closes semantics. Operating curve closes capacity. Architecture must preserve both evidence paths.
Build benchmark as evidence pipeline

Fixture owns scheduled arrivals. Load generator records schedule lag and client timestamps. Serving system contributes queue, cache, scheduler, GPU, and worker events. Scorer joins them by immutable request ID, computes metrics, and emits a receipt on pass or a rejection with raw artifacts on failure.
Keep controller outside system under test. Pin network path or measure it explicitly. If baseline runs through local loopback while candidate crosses gateway and transport security, comparison changed scope.
Framework boundaries
No single tool owns whole contract:
| Tool or source | Strong use | Boundary to state |
|---|---|---|
| Etalon[6] | black-box token timing, capacity search, fluidity | workload and SLO still need product calibration |
| MLPerf Inference[3] | standardized scenarios, LoadGen rules, latency plus quality discipline | custom run can't claim MLPerf compliance |
vllm bench serve[5] | convenient rate, burstiness, concurrency, and metric sweep | versioned client doesn't make trace representative or comparison fair |
| BurstGPT[1] | public real-world arrival and token-shape evidence | source traffic isn't your traffic or a quality set |
| ServeGen[2] | parameterized per-client synthetic workload generation | generated workload must be validated against target distribution |
Use tool for layer it controls. A polished CLI output isn't release receipt by itself.
Diagnose benchmark failures before tuning server
| Symptom | Likely benchmark error | Evidence | Repair |
|---|---|---|---|
| latency barely changes past saturation | closed-loop feedback lowered offered rate | offered versus achieved arrival timeline | rerun open-loop at fixed schedule |
| candidate wins only on second run | warmup or cache state differs | compile, graph-capture, prefix-hit events | declare warm state and alternate order |
| p99 moves wildly | too few tail samples or mixed populations | sample counts and stratified distributions | run longer and report uncertainty |
| server appears idle while client is busy | load generator saturated | sent_at - scheduled_at grows | shard client or lower rate; invalidate run |
| ITL equals network chunk spacing | chunks contain multiple tokens | chunk payload token counts | rename inter-chunk metric or add token telemetry |
| throughput passes after long drain | backlog grew during steady window | positive queue or unfinished-work slope | add stability predicate to gate |
| candidate throughput rises and quality falls | output contract changed | quality suite, finish reasons, lengths | reject or report separate trade-off |
| results change with regenerated fixture | candidates received different tail samples | trace hashes differ | freeze realized rows once |
| one length bucket looks healthy | average hides long prompts or outputs | stratified TTFT, E2E, and output length | preserve class distribution and correlations |
Retune one cause at time. A larger batch, new quantization, different replica count, and new load trace in same comparison yield no attribution.
Archive release receipt
Receipt should let another engineer answer three questions without contacting author: what ran, whether outputs remained acceptable, and where operating boundary sits.
1{
2 "claim": "candidate sustains at least baseline goodput for incident-assistant-v3",
3 "source": {
4 "baseline_image": "sha256:<digest>",
5 "candidate_image": "sha256:<digest>",
6 "repository_commit": "<full SHA>",
7 "benchmark_client": "vllm bench serve <exact version or commit>"
8 },
9 "system": {
10 "gpu_model_count": "<model and count>",
11 "topology": "<links and placement>",
12 "driver_runtime": "<driver, CUDA, engine>",
13 "model_tokenizer": "<weight and tokenizer digests>",
14 "precision": "<dtype or quantization>",
15 "engine_args": "engine-args.json"
16 },
17 "workload": {
18 "fixture": "incident-assistant-v3.jsonl",
19 "fixture_sha256": "<digest>",
20 "arrival_mode": "open-loop realized timestamps",
21 "warmup_rule": "warmup.json",
22 "load_points_rps": [0.5, 1.0, 1.5, 2.0, 2.5]
23 },
24 "gates": {
25 "ttft_ms": 300,
26 "tpot_ms": 80,
27 "attainment": 0.95,
28 "max_backlog_slope_rps": 0.02,
29 "quality_suite": "incident-quality-v7",
30 "quality_regression_allowed": 0
31 },
32 "result": {
33 "baseline_goodput_rps": "<value>",
34 "candidate_goodput_rps": "<value>",
35 "selected_operating_point_rps": "<value below boundary>",
36 "decision": "promote or reject",
37 "rollback_trigger": "<live metric and threshold>"
38 },
39 "artifacts": {
40 "request_events": "request-events.parquet",
41 "system_metrics": "system-metrics.parquet",
42 "quality_results": "quality-results.json",
43 "operating_curve": "operating-curve.json",
44 "logs": "logs/"
45 }
46}Values in worked fixture aren't defaults for production. Set thresholds from product promise and baseline evidence. Select operating point below measured cliff with capacity margin for drift, failures, and traffic mix changes. Receipt should state margin rather than presenting boundary as safe production target.
Release protocol
One repeatable protocol is enough:
- Pin baseline, candidate, hardware, model, tokenizer, decoding, and gateway scope.
- Freeze request rows and realized open-loop timestamps; hash fixture.
- Predeclare TTFT, TPOT or ITL/fluidity, quality, error, schedule-lag, and stability gates.
- Warm only mechanisms expected warm in selected production scenario.
- Replay identical trace at ascending load points, including one passing neighbor and one failing neighbor.
- Alternate candidate order and repeat boundary points.
- Join request events with queue, KV-cache, GPU, worker, and error telemetry.
- Compute named percentiles, SLO attainment, and highest stable passing offered rate.
- Run quality set and reject semantic regression before comparing performance.
- Archive raw events, environment, curve, decision, operating margin, and rollback trigger.
A benchmark becomes engineering when it can refuse misleading win. Stable request trace supplies same question to each candidate. Operating curve exposes queueing cliff. Receipt preserves enough evidence to repeat decision after code, hardware, or traffic changes.