LeetLLM
My PlanLearnGlossaryTracksPracticeBlog
LeetLLM

Your go-to resource for mastering AI & LLM systems.

Product

  • Learn
  • Glossary
  • Tracks
  • Practice
  • Blog
  • RSS

Legal

  • Terms of Service
  • Privacy Policy

© 2026 LeetLLM. All rights reserved.

All Topics
Your Progress
0%

0 of 192 articles completed

🛠️Computing Foundations0/9
Git, Shell, Linux for AIDocker for Reproducible AIPython for AI EngineeringNumPy and Tensor ShapesCUDA for ML TrainingMPS & Metal for ML on MacData Structures for AISQL and Data ModelingAlgorithms for ML Engineers
📊Math & Statistics0/9
Gradients and BackpropVectors, Matrices & TensorsLinear Algebra for MLAdam, Momentum, SchedulersProbability for Machine LearningBayesian Inference from EvidenceStatistics and UncertaintyDistributions and SamplingHypothesis Tests, Intervals, and pass@k
📚Preparation & Prerequisites0/13
Neural Networks from ScratchCNNs from ScratchTraining & BackpropagationSoftmax, Cross-Entropy & OptimizationRNNs, LSTMs, GRUs, and Sequence ModelingAutoencoders and VAEsThe Transformer Architecture End-to-EndLanguage Modeling & Next TokensFrom GPT to Modern LLMsPrompt Engineering FundamentalsCalling LLM APIs in ProductionFirst AI App End-to-EndThe LLM Lifecycle
🧮ML Algorithms & Evaluation0/15
Linear Regression from ScratchLogistic Regression and MetricsDecision Trees, Forests, and BoostingReinforcement Learning BasicsValidation and LeakageStatistical Learning and GeneralizationConformal Prediction and UncertaintyClustering and PCAProbabilistic Generative ModelsCore Retrieval AlgorithmsDecoding AlgorithmsExperiment Design and A/B TestingCausal Inference and CounterfactualsPyTorch Training LoopsDataset Pipelines and Data Quality
📦Production ML Systems0/6
Feature Engineering for Production MLBatch and Streaming Feature PipelinesGradient Boosted Trees in ProductionRanking and Recommendation SystemsForecasting and Anomaly DetectionMonitoring Predictive Models
🧪Core LLM Foundations0/8
The Bitter Lesson & ComputeBPE, WordPiece, and SentencePieceStatic to Contextual EmbeddingsPerplexity & Model EvaluationFile Ingestion for AIChunking StrategiesLLM Benchmarks & LimitationsInstruction Tuning & Chat Templates
🧰Applied LLM Engineering0/25
Dimensionality Reduction for EmbeddingsCoT, ToT & Self-Consistency PromptingFunction Calling & Tool UseMCP & Tool Protocol StandardsContext EngineeringPrompt Injection DefenseResponsible AI GovernancePrivacy-Preserving Machine LearningData Labeling and Human FeedbackEvaluating AI AgentsProduction RAG PipelinesHybrid Search: Dense + SparseReranking and Cross-Encoders for RAGRAG Evaluation for Reliable AnswersLLM-as-a-Judge EvaluationBias & Fairness in LLMsHallucination Detection & MitigationLLM Observability & MonitoringExperiment Tracking with MLflow and W&BPrompt Optimization with DSPyModel Versioning & DeploymentSemantic Caching & Cost OptimizationLLM Cost Engineering & Token EconomicsModel Gateways, Routing, and FallbacksDesign an Automated Support Agent
🎓Portfolio Capstones0/9
Capstone: Delivery ETA PredictionCapstone: Product RankingCapstone: Demand ForecastingCapstone: Image Damage ClassifierCapstone: Production ML PipelineCapstone: Document QACapstone: Eval DashboardCapstone: Fine-Tuned ClassifierCapstone: Reproducible ML Study
🧠Transformer Deep Dives0/8
Sentence Embeddings & Contrastive LossEmbedding Similarity & QuantizationScaled Dot-Product AttentionVision Transformers and Image EncodersPositional Encoding: RoPE & ALiBiLayer Normalization: Pre-LN vs Post-LNMechanistic InterpretabilityDecoding Strategies: Greedy to Nucleus
🧬Advanced Training & Adaptation0/17
Scaling Laws & Compute-Optimal TrainingPre-training Data at ScaleBuild GPT from Scratch LabJAX for PyTorch ResearchersContinued Pretraining for Domain ShiftSynthetic Data PipelinesSupervised Fine-Tuning PipelineMixed Precision TrainingDistributed Training: FSDP & ZeROLoRA & Parameter-Efficient TuningTraining Run OperationsReward Modeling from Preference DataRLHF & DPO AlignmentConstitutional AI & Red TeamingRLVR & Verifiable RewardsKnowledge Distillation for LLMsModel Merging and Weight Interpolation
🤖Advanced Agents & Retrieval0/16
Vector DB Internals: HNSW & IVFAdvanced RAG: HyDE & Self-RAGGraphRAG & Knowledge GraphsRAG Security & Access ControlStructured Output GenerationReAct & Plan-and-ExecuteGuardrails & Safety FiltersCode Generation & SandboxingComputer-Use / GUI / Browser AgentsHuman-in-the-Loop Agent ArchitectureAI Coding Workflow with AgentsAgent Memory & PersistenceAgent Failure & RecoveryRecursive Language Models (RLM)Multi-Agent OrchestrationCapstone: Production Agent
⚡Inference & Production Scale0/27
Inference: TTFT, TPS & KV CacheMulti-Query & Grouped-Query AttentionKV Cache & PagedAttentionPrefix Caching and Prompt CachingFlashAttention & Memory EfficiencyContinuous Batching & SchedulingScaling LLM InferenceModel Parallelism for LLM InferenceAccelerator Architecture Field GuideCorrect GPU Profiling and BenchmarkingGPU Kernel Performance Engineering LabGPU Compilers and Kernel LanguagesGEMM From Naive CUDA to Tensor CoresLow-Precision GPU ProgrammingModel Quantization: GPTQ, AWQ & GGUFLocal LLM DeploymentSLM Specialization & Edge DeploymentSpeculative DecodingLong Context Window ManagementMixture of Experts ArchitectureMamba & State Space ModelsReasoning & Test-Time ComputeAdvanced MLOps & DevOps for AIGPU Serving & AutoscalingDistributed Inference Data PlaneServing Benchmark EngineeringA/B Testing for LLMs
🏗️System Design Capstones0/9
Content Moderation SystemCode Completion SystemMulti-Tenant LLM PlatformLLM-Powered Search EngineVision-Language Models & CLIPMultimodal LLM ArchitectureDiffusion Models: Images & TextReal-Time Voice AI AgentReasoning Agent System Design
🎤AI Lab Interviewing0/4
AI Lab Coding Interview: Python SystemsAI Lab System Design InterviewAI Lab Behavioral InterviewAI Lab Technical Presentation
🔬Project Deep Dives0/17
Deep Dive - vLLMDeep Dive - SkyRLDeep Dive - FlashAttentionDeep Dive - FlashInferDeep Dive - DeepGEMMDeep Dive - NCCLDeep Dive - MegatronDeep Dive - DeepSpeedDeep Dive - RayDeep Dive - MLflowDeep Dive - PyTorchDeep Dive - TransformersDeep Dive - SGLangDeep Dive - slimeDeep Dive - DeepEPDeep Dive - TinkerDeep Dive - Light-PEFT
Back to Topics
LearnInference & Production ScaleServing Benchmark Engineering
📊HardEvaluation & Benchmarks

Serving Benchmark Engineering

Build a reproducible LLM serving benchmark from one versioned request trace, then find a stable release point using latency, fluidity, quality, and SLO-qualified goodput.

25 min read
Learning path
Step 161 of 192 in the full curriculum
Distributed Inference Data PlaneA/B Testing for LLMs

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:

  1. What arrives: prompt text or token IDs, output policy, priority, shared prefixes, and correctness expectations.
  2. When it arrives: a realized timestamp for every request.
  3. 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:

IDScheduled arrivalPrompt tokensOutput policyPrefixClassPressure exposed
r010 ms320natural stop, cap 96runbook-ainteractivewarm short request
r02120 ms336natural stop, cap 96runbook-ainteractiveprefix reuse
r03700 ms4,096natural stop, cap 256noneinteractivelong prefill
r04760 ms280natural stop, cap 64noneinteractiveTTFT behind prefill
r05800 ms2,048natural stop, cap 512policy-bbackgroundlong decode occupancy
r061,900 ms480natural stop, cap 128runbook-ainteractiverecovery 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:

PropertyWhy average loses itFixture field
Prompt-length distributionlong prompts create prefill interferenceprompt text or token IDs plus token count
Natural output distributionlong generations occupy decode slots longeroutput cap plus observed output length
Prompt/output correlationsome request classes are long in both phasesstable request ID and class
Shared-prefix frequencycache hits change prefill workprefix group and cache policy
Priority and tenant mixscheduler policy can starve one classpriority or tenant label
Streaming modebuffered responses hide token cadenceprotocol and streaming flag

BurstGPT publishes a real serving trace for studying arrival, concurrency, token-length, conversation, and failure patterns.[1]Reference 1BurstGPT: A Real-World Workload Dataset to Optimize LLM Serving Systemshttps://arxiv.org/abs/2401.17644 It doesn't make those patterns representative of your endpoint. ServeGen builds parameterized, sanitized workloads by composing per-client arrival and data models.[2]Reference 2ServeGen: Workload Characterization and Generation of Large Language Model Serving in Productionhttps://www.usenix.org/conference/nsdi26/presentation/xiang-servegen 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 modelArrival triggerGood useDangerous interpretation
Open-loop rateprerecorded time or external clockSLO capacity, burst, and queue testsclient must keep up with schedule
Closed-loop concurrencyresponse frees virtual usermax-concurrency and backpressure studiescan't establish independent offered-rate capacity
All-at-onceevery request at time zerooffline throughput or admission stressnot 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]Reference 3MLPerf Inference Benchmark Rules.https://github.com/mlcommons/inference_policies/blob/master/inference_rules.adoc 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 λ\lambdaλ is intended requests per second. Concurrency is number currently outstanding. In a stable system, Little's Law gives useful check:[4]Reference 4A Proof for the Queuing Formula: L = lambda Whttps://doi.org/10.1287/opre.9.3.383

L≈λWL \approx \lambda WL≈λW

LLL is average outstanding work and WWW 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:

CV=standard deviation of inter-arrival timemean inter-arrival timeCV = \frac{\text{standard deviation of inter-arrival time}}{\text{mean inter-arrival time}}CV=mean inter-arrival timestandard deviation of inter-arrival time​

Exponential inter-arrival times from a Poisson process have CV=1CV=1CV=1. A trace with CV>1CV>1CV>1 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]Reference 5Benchmark CLIhttps://docs.vllm.ai/en/stable/benchmarking/cli/ Its parameter named burstiness is Gamma shape kkk, not coefficient of variation: CV=1/kCV=1/\sqrt{k}CV=1/k​, 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 L=3×0.9=2.7L=3\times0.9=2.7L=3×0.9=2.7 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:

FieldOwnerWhat it reveals
scheduled_atfixtureintended arrival under open-loop load
sent_atclientgenerator schedule lag
accepted_atgateway or servernetwork and admission delay
token_at[]streaming client or serverfirst-token and token-cadence timing
completed_atclientfull 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:

PhaseAdmissionMetricsExit rule
Readinesshealth probes onlystartup time separatelymodel and workers ready
Warmuprepresentative requestsexcluded from steady resultdeclared compile/capture paths complete and latency stabilizes
Steady statefrozen open-loop traceall request and system eventsfixed window or fixed realized trace completes
Drainno new arrivalscompletion and timeout outcomes retainedevery 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 aaa, first output token appear at t1t_1t1​, final token at tnt_ntn​, and output contain nnn tokens.

Time to first token (TTFT) includes queueing and prefill from chosen client boundary:

TTFT=t1−aTTFT = t_1 - aTTFT=t1​−a

Inter-token latency (ITL), also called time between tokens, is each gap after first:

ITLi=ti−ti−1,i=2,…,nITL_i = t_i - t_{i-1}, \qquad i=2,\ldots,nITLi​=ti​−ti−1​,i=2,…,n

Time per output token (TPOT) averages decode cadence after first token:

TPOT=tn−t1n−1TPOT = \frac{t_n-t_1}{n-1}TPOT=n−1tn​−t1​​

For n=1n=1n=1, 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:

E2E=tn−aE2E = t_n-aE2E=tn​−a

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 (480−240)/4=60(480-240)/4=60(480−240)/4=60 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:

PromiseSuggested population
first response starts promptlyrequest-level TTFT
each session streams smoothlyper-request worst or high-percentile ITL, then percentile across requests
random emitted token gap stays smallpooled ITL events, with token weighting stated
whole response completes promptlyrequest-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]Reference 6Etalon: Holistic Performance Evaluation Framework for LLM Inference Systems.https://arxiv.org/abs/2407.07000

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 ≤300\leq 300≤300 ms, and request-mean TPOT ≤80\leq 80≤80 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 RPSCompleted RPSp95 TTFTp95 TPOTPooled p99 ITLContract attainmentBacklog slopeGate
0.50.50170 ms44 ms71 ms100%0.00Pass
1.00.99220 ms49 ms75 ms100%0.00Pass
1.51.48285 ms61 ms82 ms95%0.00Pass
2.01.82510 ms86 ms140 ms80%+0.16Fail
2.51.86920 ms121 ms260 ms58%+0.61Fail

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]Reference 7DistServe: Disaggregating Prefill and Decoding for Goodput-optimized Large Language Model Serving.https://arxiv.org/abs/2401.09670 For broader release contract here:

goodput=max⁡λ{λ:Pr⁡[success∧quality∧TTFT≤Df∧TPOT≤Dd]≥α}\text{goodput}=\max_{\lambda}\left\{\lambda:\Pr[\text{success}\land\text{quality}\land\text{TTFT}\leq D_f\land\text{TPOT}\leq D_d]\geq\alpha\right\}goodput=λmax​{λ:Pr[success∧quality∧TTFT≤Df​∧TPOT≤Dd​]≥α}

λ\lambdaλ is offered rate, DfD_fDf​ is TTFT deadline, DdD_dDd​ is decode deadline, and α\alphaα 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.

Synthetic serving operating curve across offered rates from 0.5 to 2.5 requests per second. Completed throughput rises and then plateaus. Aggregate contract-compliant completions still rise at 2.0 requests per second even though only 80 percent of requests pass, then fall at 2.5. A release panel marks 1.5 requests per second as highest stable point meeting the 95 percent attainment gate.
Aggregate compliant completions still rise at 2.0 offered RPS, but 20% of requests miss the contract and queue grows. Goodput boundary is last stable rate meeting 95% attainment, not largest count of good responses.

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.

serving-goodput-fixture.py
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")
Output
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/s

At 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:

  1. Performance trace: representative arrival and shape fixture, with every response checked for protocol success, finish reason, token count, and schema validity.
  2. 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]Reference 3MLPerf Inference Benchmark Rules.https://github.com/mlcommons/inference_policies/blob/master/inference_rules.adoc 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 evidenceSafe nameUnsupported claim
first response bytetime to first byteTTFT without proving byte carries token
streamed text chunksinter-chunk latencyper-token ITL
token IDs plus server timestampstoken ITLnetwork playback cadence without client timestamps
client token eventsuser-observed ITLscheduler-only latency

Quality gate closes semantics. Operating curve closes capacity. Architecture must preserve both evidence paths.

Build benchmark as evidence pipeline

Diagram showing 1. Hashed trace fixture, 2. Open-loop load generator, 3. Gateway + serving system, and 4. Token event + system logs.
1. Hashed trace fixture, 2. Open-loop load generator, 3. Gateway + serving system, and 4. Token event + system logs.

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 sourceStrong useBoundary to state
Etalon[6]Reference 6Etalon: Holistic Performance Evaluation Framework for LLM Inference Systems.https://arxiv.org/abs/2407.07000black-box token timing, capacity search, fluidityworkload and SLO still need product calibration
MLPerf Inference[3]Reference 3MLPerf Inference Benchmark Rules.https://github.com/mlcommons/inference_policies/blob/master/inference_rules.adocstandardized scenarios, LoadGen rules, latency plus quality disciplinecustom run can't claim MLPerf compliance
vllm bench serve[5]Reference 5Benchmark CLIhttps://docs.vllm.ai/en/stable/benchmarking/cli/convenient rate, burstiness, concurrency, and metric sweepversioned client doesn't make trace representative or comparison fair
BurstGPT[1]Reference 1BurstGPT: A Real-World Workload Dataset to Optimize LLM Serving Systemshttps://arxiv.org/abs/2401.17644public real-world arrival and token-shape evidencesource traffic isn't your traffic or a quality set
ServeGen[2]Reference 2ServeGen: Workload Characterization and Generation of Large Language Model Serving in Productionhttps://www.usenix.org/conference/nsdi26/presentation/xiang-servegenparameterized per-client synthetic workload generationgenerated 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

SymptomLikely benchmark errorEvidenceRepair
latency barely changes past saturationclosed-loop feedback lowered offered rateoffered versus achieved arrival timelinererun open-loop at fixed schedule
candidate wins only on second runwarmup or cache state differscompile, graph-capture, prefix-hit eventsdeclare warm state and alternate order
p99 moves wildlytoo few tail samples or mixed populationssample counts and stratified distributionsrun longer and report uncertainty
server appears idle while client is busyload generator saturatedsent_at - scheduled_at growsshard client or lower rate; invalidate run
ITL equals network chunk spacingchunks contain multiple tokenschunk payload token countsrename inter-chunk metric or add token telemetry
throughput passes after long drainbacklog grew during steady windowpositive queue or unfinished-work slopeadd stability predicate to gate
candidate throughput rises and quality fallsoutput contract changedquality suite, finish reasons, lengthsreject or report separate trade-off
results change with regenerated fixturecandidates received different tail samplestrace hashes differfreeze realized rows once
one length bucket looks healthyaverage hides long prompts or outputsstratified TTFT, E2E, and output lengthpreserve 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.

serving-benchmark-receipt.json
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:

  1. Pin baseline, candidate, hardware, model, tokenizer, decoding, and gateway scope.
  2. Freeze request rows and realized open-loop timestamps; hash fixture.
  3. Predeclare TTFT, TPOT or ITL/fluidity, quality, error, schedule-lag, and stability gates.
  4. Warm only mechanisms expected warm in selected production scenario.
  5. Replay identical trace at ascending load points, including one passing neighbor and one failing neighbor.
  6. Alternate candidate order and repeat boundary points.
  7. Join request events with queue, KV-cache, GPU, worker, and error telemetry.
  8. Compute named percentiles, SLO attainment, and highest stable passing offered rate.
  9. Run quality set and reject semantic regression before comparing performance.
  10. 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.

Complete the lesson

Mastery Check

Answer every question, then check your score. Score 75% or higher to mark this lesson complete.

1.A closed-loop client keeps 32 requests outstanding. Server slows, and offered request rate falls automatically while latency looks stable. Which change exposes saturation for an SLO capacity test?

Correct answer: Replay fixed scheduled arrivals with an open-loop generator

Open-loop arrivals don't wait for completions, so offered load remains independent of service time and queue growth stays visible. Closed-loop feedback reduces pressure when server slows.

2.Two candidates use same distribution and seed, but their generated traces have different long-prompt rows. What makes comparison reproducible?

Correct answer: Generate once, store realized request rows, and compare trace hashes

Frozen realized rows ensure both candidates receive same arrivals and tail samples. A seed alone can drift across generators, versions, or tokenizers.

3.A five-token response arrives at 240, 280, 320, 440, and 480 ms after request arrival. What are TTFT and TPOT?

Correct answer: TTFT is 240 ms and TPOT is 60 ms

First token appears at 240 ms. Decode spans 480 minus 240 equals 240 ms across four inter-token intervals, so TPOT is 60 ms.

4.Completed throughput rises from 1.48 to 1.82 requests per second, but contract attainment falls from 95% to 80% and backlog starts growing. Which rate is defensible goodput boundary under 95% attainment?

Correct answer: 1.50 requests per second because it's highest stable passing offered rate

Goodput is selected from offered-rate points that satisfy declared SLO attainment and stability. Higher raw completions don't rescue failed latency and growing queue.

5.Why can mean TPOT pass while streaming still feels broken?

Correct answer: A few long ITL stalls can be averaged with many fast token gaps

TPOT averages decode interval over request. Raw ITL tails and deadline-based fluidity reveal pauses that average can hide.

6.Load generator schedules request at 760 ms but sends it at 1,100 ms because client is overloaded. Which handling preserves benchmark integrity?

Correct answer: Record 340 ms schedule lag and fail run if it breaches client budget

Late sending means load generator can't reproduce intended schedule. Keeping scheduled and sent timestamps exposes client bottleneck instead of deleting delay through coordinated omission.

7.Streaming API sends chunks that may contain several tokens. What metric can client safely report without server token timestamps?

Correct answer: Inter-chunk latency with chunk boundaries stated

Chunk event is client-observed but isn't guaranteed one token. Calling it token ITL would invent granularity that protocol doesn't provide.

8.Candidate returns faster JSON that parses, but one tool argument changes meaning on quality set. How should request contribute to goodput?

Correct answer: It must fail acceptance because correctness is part of goodput predicate

Faster wrong answer isn't useful work. Quality or correctness belongs inside acceptance predicate before performance comparison.

9.Which receipt evidence most directly proves baseline and candidate faced same workload?

Correct answer: Matching fixture SHA plus realized request IDs and timestamps

Hash and realized rows bind exact workload identity. Matching averages can hide different tails, order, burst structure, and prefix reuse.

9 questions remaining.

Next Step
Continue to A/B Testing for LLMs

A serving candidate can now earn release capacity on a reproducible offline trace. Next lesson asks whether product change improves real user outcomes under sticky assignment, statistical uncertainty, and live <span data-glossary="guardrail">guardrails</span>.

PreviousDistributed Inference Data Plane
Share this article
XFacebookLinkedInBlueskyRedditHacker NewsEmail
References

BurstGPT: A Real-World Workload Dataset to Optimize LLM Serving Systems

Wang, Y., Chen, Y., Li, Z., et al. · 2025 · arXiv preprint

https://arxiv.org/abs/2401.17644

ServeGen: Workload Characterization and Generation of Large Language Model Serving in Production

Xiang, Y., Li, X., Qian, K., Zhang, Y., Yu, W., Zhai, E., Jin, X., and Zhou, J. · 2026 · NSDI 2026

https://www.usenix.org/conference/nsdi26/presentation/xiang-servegen

MLPerf Inference Benchmark Rules.

MLCommons · 2026 · Official benchmark rules

https://github.com/mlcommons/inference_policies/blob/master/inference_rules.adoc

A Proof for the Queuing Formula: L = lambda W

Little, J. D. C. · 1961 · Operations Research

https://doi.org/10.1287/opre.9.3.383

Benchmark CLI

vLLM Project · 2026 · Official vLLM documentation

https://docs.vllm.ai/en/stable/benchmarking/cli/

Etalon: Holistic Performance Evaluation Framework for LLM Inference Systems.

Agrawal, A., et al. · 2024 · arXiv preprint

https://arxiv.org/abs/2407.07000

DistServe: Disaggregating Prefill and Decoding for Goodput-optimized Large Language Model Serving.

Zhong, Y., et al. · 2024 · OSDI 2024

https://arxiv.org/abs/2401.09670

Discussion

Questions and insights from fellow learners.

Discussion loads when you reach this section.