<?xml version="1.0" encoding="utf-8" standalone="yes"?><rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom"><channel><title>Llmperf on lo0 — Tech Blog</title><link>https://blog.lo0.es/en/tags/llmperf/</link><description>Recent content in Llmperf on lo0 — Tech Blog</description><generator>Hugo -- gohugo.io</generator><language>en</language><lastBuildDate>Sun, 14 Jun 2026 02:00:00 +0200</lastBuildDate><atom:link href="https://blog.lo0.es/en/tags/llmperf/index.xml" rel="self" type="application/rss+xml"/><item><title>LLM benchmark tool catalogue: the practical deep dive</title><link>https://blog.lo0.es/en/posts/llm-benchmark-tool-catalogue-practical-deep-dive/</link><pubDate>Sun, 14 Jun 2026 02:00:00 +0200</pubDate><guid>https://blog.lo0.es/en/posts/llm-benchmark-tool-catalogue-practical-deep-dive/</guid><description>&lt;blockquote>
&lt;p>Notation: amounts in &lt;strong>euros (N €)&lt;/strong>, decimals with a point. The dollar symbol is not used
(on this site it is a formula delimiter).&lt;/p>
&lt;/blockquote>
&lt;h2 id="what-this-article-covers">What this article covers&lt;/h2>
&lt;p>Second article in the &lt;strong>benchmarking&lt;/strong> track (B2). The &lt;a href="https://blog.lo0.es/en/posts/llm-inference-benchmarking-frameworks-metrics-state-of-the-art/">B1 introduction&lt;/a>
pinned the metrics and the methodology; this article is the &lt;strong>practical manual&lt;/strong>: for each
tool, what it measures, how it is actually invoked (with the command), what output format
it produces and when to choose it. The goal is that, after reading it, you know what to run to
measure your engine and you can reproduce the number. No universal recommendations; only the
mechanics of each tool and the criterion for choosing.&lt;/p>
&lt;hr>
&lt;h2 id="how-to-choose-the-tool-map">How to choose: the tool map&lt;/h2>
&lt;p>Recalling the split from B1, the tools are ordered on two axes: &lt;strong>micro-bench&lt;/strong>
(single-process, for tuning an engine) versus &lt;strong>load generator&lt;/strong> (multi-process, for measuring
real capacity), and &lt;strong>engine-native&lt;/strong> versus &lt;strong>endpoint-agnostic&lt;/strong>.&lt;/p>
&lt;div class="diagram" style="max-width:780px;margin:1rem auto;">
&lt;svg viewBox="0 0 780 250" role="img" aria-label="Map of benchmark tools on two axes: single-process micro-bench versus multi-process load generator, and engine-native versus endpoint-agnostic" xmlns="http://www.w3.org/2000/svg">
&lt;style>.ax{fill:none;stroke:currentColor;stroke-width:1}.bx{fill:none;stroke:currentColor;stroke-width:1.2}.tl{font:600 12px sans-serif;fill:currentColor}.ts{font:11px sans-serif;fill:currentColor}&lt;/style>
&lt;line class="ax" x1="60" y1="200" x2="720" y2="200"/>
&lt;line class="ax" x1="390" y1="40" x2="390" y2="200"/>
&lt;text x="70" y="216" class="ts">micro-bench (single-process)&lt;/text>
&lt;text x="560" y="216" class="ts">load generator (multi-process)&lt;/text>
&lt;text x="400" y="52" class="ts">↑ endpoint-agnostic&lt;/text>
&lt;text x="400" y="195" class="ts">↓ engine-native&lt;/text>
&lt;rect class="bx" x="150" y="150" width="150" height="34" rx="5"/>&lt;text x="225" y="171" text-anchor="middle" class="ts">vLLM bench / SGLang bench&lt;/text>
&lt;rect class="bx" x="470" y="70" width="120" height="34" rx="5"/>&lt;text x="530" y="91" text-anchor="middle" class="ts">AIPerf&lt;/text>
&lt;rect class="bx" x="470" y="115" width="120" height="34" rx="5"/>&lt;text x="530" y="136" text-anchor="middle" class="ts">GuideLLM&lt;/text>
&lt;rect class="bx" x="610" y="93" width="110" height="34" rx="5"/>&lt;text x="665" y="114" text-anchor="middle" class="ts">LLMPerf&lt;/text>
&lt;rect class="bx" x="150" y="60" width="150" height="34" rx="5"/>&lt;text x="225" y="81" text-anchor="middle" class="ts">MLPerf (standard suite)&lt;/text>
&lt;text x="60" y="240" class="ts">To tune a specific engine: micro-bench. To measure capacity under SLO: load generator. To compare vendors: MLPerf.&lt;/text>
&lt;/svg>
&lt;/div>
&lt;hr>
&lt;h2 id="datasets-the-synthetic-load-matters-as-much-as-the-tool">Datasets: the synthetic load matters as much as the tool&lt;/h2>
&lt;p>Before the profiles, one point that changes results without anyone noticing: &lt;strong>what load you
feed it&lt;/strong>. The tools accept different kinds of dataset, and each one measures something
different:&lt;/p>
&lt;table>
&lt;thead>
&lt;tr>
&lt;th>Dataset&lt;/th>
&lt;th>What it simulates&lt;/th>
&lt;th>Bias it introduces&lt;/th>
&lt;/tr>
&lt;/thead>
&lt;tbody>
&lt;tr>
&lt;td>&lt;strong>random&lt;/strong> (fixed lengths)&lt;/td>
&lt;td>controlled uniform load&lt;/td>
&lt;td>unrealistic: traffic is not fixed length&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>&lt;strong>sharegpt&lt;/strong> (real conversations)&lt;/td>
&lt;td>realistic prompt distribution&lt;/td>
&lt;td>the de facto standard for comparison&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>&lt;strong>your own traces&lt;/strong>&lt;/td>
&lt;td>your real traffic&lt;/td>
&lt;td>the most faithful, but specific to your case&lt;/td>
&lt;/tr>
&lt;/tbody>
&lt;/table>
&lt;p>The trap: a benchmark with short, fixed-length prompts gives a very high throughput that
looks nothing like production, where lengths vary and long prompts dominate the prefill
cost. For a defensible figure, use &lt;strong>sharegpt&lt;/strong> (comparability) or, better, &lt;strong>traces from your
own traffic&lt;/strong> (fidelity). And always declare the length distribution (prompt/output) alongside
the number, because two benchmarks with different datasets are not comparable even if they
use the same tool.&lt;/p>
&lt;hr>
&lt;h2 id="vllm-bench-serve">vLLM bench serve&lt;/h2>
&lt;p>What it measures: TTFT, TPOT, throughput and latencies of the vLLM server under a
synthetic load. Class: micro-bench (single-process). It is the tool for &lt;strong>tuning vLLM&lt;/strong> and
seeing the effect of its optimisations (&lt;a href="https://blog.lo0.es/en/posts/optimising-decode-vllm-squeezing-every-token-small-hardware/">decode&lt;/a>,
&lt;a href="https://blog.lo0.es/en/posts/optimising-prefill-vllm-knobs-ttft-will-not-forgive/">prefill&lt;/a>).&lt;/p>
&lt;p>Typical invocation (&lt;a href="https://docs.vllm.ai/en/latest/cli/bench/serve/">vLLM · benchmark CLI&lt;/a>):&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-bash" data-lang="bash">&lt;span class="line">&lt;span class="cl">vllm bench serve &lt;span class="se">\
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="se">&lt;/span> --backend vllm &lt;span class="se">\
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="se">&lt;/span> --model meta-llama/Llama-3.1-8B-Instruct &lt;span class="se">\
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="se">&lt;/span> --endpoint /v1/completions &lt;span class="se">\
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="se">&lt;/span> --dataset-name sharegpt &lt;span class="se">\
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="se">&lt;/span> --dataset-path ShareGPT_V3_unfiltered_cleaned_split.json &lt;span class="se">\
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="se">&lt;/span> --num-prompts &lt;span class="m">1000&lt;/span>
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>Key parameters: &lt;code>--num-prompts&lt;/code> (load), &lt;code>--dataset-name&lt;/code> (sharegpt, random, etc.),
&lt;code>--request-rate&lt;/code> (requests/s). The output is a console summary with TTFT, TPOT, throughput
and percentiles, and it can be dumped to JSON. To sweep concurrencies there is &lt;strong>&lt;code>vllm bench sweep serve&lt;/code>&lt;/strong>, which automates the sweep (&lt;a href="https://docs.vllm.ai/en/latest/cli/bench/sweep/serve/">vLLM · sweep&lt;/a>).&lt;/p>
&lt;p>Limit: single-process, it saturates on the client at high concurrency; less flexible than
GuideLLM in datasets and load patterns. Use it to iterate quickly on the vLLM config, not to
measure maximum capacity at scale.&lt;/p>
&lt;hr>
&lt;h2 id="sglang-bench">SGLang bench&lt;/h2>
&lt;p>What it measures: the equivalent for the &lt;strong>SGLang&lt;/strong> engine (TTFT, TPOT, throughput). Class:
micro-bench. Use: tuning SGLang and comparing it against itself across configurations. The
mechanics are analogous to &lt;code>vllm bench serve&lt;/code>: a synthetic load, console/JSON output with the
same metrics. If you are evaluating SGLang against vLLM, do not compare them with their
respective micro-benches (same class but different implementations): use an endpoint-agnostic
load generator (GuideLLM/AIPerf) against both endpoints, so that the tool is not the variable.&lt;/p>
&lt;hr>
&lt;h2 id="aiperf-nvidia-formerly-genai-perf">AIPerf (NVIDIA, formerly genai-perf)&lt;/h2>
&lt;p>What it measures: TTFT, ITL, throughput and latency against any compatible endpoint (vLLM,
NIM, TGI, SGLang). Class: &lt;strong>multi-process&lt;/strong> load generator. It is the successor to genai-perf
(retired on 15 Apr 2026). Its distinguishing feature: during the sweep it &lt;strong>detects GPU
saturation&lt;/strong> and returns the previous iteration as &lt;code>estimatedCapacity&lt;/code>, which is why the sweep
has to extend beyond the knee
(&lt;a href="https://lucaberton.com/blog/nvidia-aiperf-llm-inference-benchmarking-guide/">AIPerf&lt;/a>).&lt;/p>
&lt;p>Output: structured metrics (JSON) with TTFT/ITL distributions and the &lt;code>estimatedCapacity&lt;/code>. Use
it when you want the real capacity of an endpoint, whatever the engine, with automatic
detection of the saturation point.&lt;/p>
&lt;hr>
&lt;h2 id="guidellm-vllm-project">GuideLLM (vLLM project)&lt;/h2>
&lt;p>What it measures: full distributions of TTFT, ITL and end-to-end behaviour, for &lt;strong>SLO-driven&lt;/strong>
evaluation. Class: multi-process load generator. It is the recommended tool for benchmarking
vLLM servers in production: more flexible than &lt;code>vllm bench serve&lt;/code> in dataset loading, request
format and traffic patterns, with live progress and automatic report generation
(&lt;a href="https://developers.redhat.com/articles/2025/12/24/how-deploy-and-benchmark-vllm-guidellm-kubernetes">Red Hat&lt;/a>).&lt;/p>
&lt;p>Typical invocation:&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-bash" data-lang="bash">&lt;span class="line">&lt;span class="cl">guidellm benchmark &lt;span class="se">\
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="se">&lt;/span> --target &lt;span class="s2">&amp;#34;http://localhost:8000&amp;#34;&lt;/span> &lt;span class="se">\
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="se">&lt;/span> --rate-type throughput &lt;span class="se">\
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="se">&lt;/span> --max-requests &lt;span class="m">1000&lt;/span> &lt;span class="se">\
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="se">&lt;/span> --data &lt;span class="s2">&amp;#34;samples=1000,prompt_tokens=1024,output_tokens=256&amp;#34;&lt;/span>
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>Key parameters: &lt;code>--rate-type&lt;/code> (&lt;code>synchronous&lt;/code>, &lt;code>concurrent&lt;/code>, &lt;code>throughput&lt;/code>, or rate-based),
&lt;code>--data&lt;/code> (the load specification: number of samples and prompt/output lengths), &lt;code>--target&lt;/code>
(the endpoint). It generates reproducible sweeps to find the safe operating range under
SLO, with full distributions (not just means). Output: a report with percentiles and, often,
exportable to JSON/HTML. It is the default option for answering &amp;ldquo;how hard can I load this
engine without breaking the SLO?&amp;rdquo;.&lt;/p>
&lt;hr>
&lt;h2 id="llmperf-anyscaleray">LLMPerf (Anyscale/Ray)&lt;/h2>
&lt;p>What it measures: throughput and latency at the inference level. Class: load generator. Use:
endpoint validation, historically very widespread in the Ray/Anyscale ecosystem. It is a solid
and well-known option, though less focused on distributions and sweeps than GuideLLM/AIPerf.
It fits if you already operate on Ray or want a simple tool to validate an endpoint.&lt;/p>
&lt;hr>
&lt;h2 id="inference-benchmarker-hugging-face">inference-benchmarker (Hugging Face)&lt;/h2>
&lt;p>What it measures: latency and throughput of inference endpoints, aimed at producing comparable
reports. Class: load generator. Use: an OSS alternative inside the Hugging Face ecosystem,
useful if you already work with TGI or the HF stack. Like the others in its class, its value
lies in measuring real capacity with distributed load; the choice between this one, GuideLLM
and AIPerf usually comes down to the ecosystem you already operate in rather than to deep
differences in what they measure.&lt;/p>
&lt;hr>
&lt;h2 id="guidellm-versus-aiperf-which-of-the-two-load-generators">GuideLLM versus AIPerf: which of the two load generators&lt;/h2>
&lt;p>They are the two serious multi-process load options, and they overlap a great deal. The
practical differences that tip the choice:&lt;/p>
&lt;table>
&lt;thead>
&lt;tr>
&lt;th>Criterion&lt;/th>
&lt;th>GuideLLM&lt;/th>
&lt;th>AIPerf&lt;/th>
&lt;/tr>
&lt;/thead>
&lt;tbody>
&lt;tr>
&lt;td>Origin&lt;/td>
&lt;td>vLLM project (Red Hat)&lt;/td>
&lt;td>NVIDIA (successor to genai-perf)&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>Focus&lt;/td>
&lt;td>&lt;strong>SLO&lt;/strong>-driven evaluation, reproducible sweeps&lt;/td>
&lt;td>real capacity, automatic &lt;strong>estimatedCapacity&lt;/strong>&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>Load patterns&lt;/td>
&lt;td>synchronous, concurrent, rate-based&lt;/td>
&lt;td>sweep with saturation detection&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>Report&lt;/td>
&lt;td>live progress + automatic report&lt;/td>
&lt;td>structured metrics (JSON)&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>Ecosystem&lt;/td>
&lt;td>vLLM / OpenShift AI&lt;/td>
&lt;td>NVIDIA NIM / Triton / vLLM&lt;/td>
&lt;/tr>
&lt;/tbody>
&lt;/table>
&lt;p>In practice: if your question is &amp;ldquo;how hard can I load it without breaking the SLO?&amp;rdquo;,
&lt;strong>GuideLLM&lt;/strong> answers it more directly with its SLO-driven sweeps; if your question is &amp;ldquo;what is
the maximum capacity of this endpoint?&amp;rdquo;, &lt;strong>AIPerf&lt;/strong> gives it with its automatic knee
detection. Many teams use both: GuideLLM for the operational SLO, AIPerf for the reference
capacity. What you must not do is compare a GuideLLM result with an AIPerf one as if they
were the same measurement: although both are multi-process, their sweep methodology differs;
pick one for a given comparison and stick to it.&lt;/p>
&lt;hr>
&lt;h2 id="mlperf-inference-mlcommons">MLPerf Inference (MLCommons)&lt;/h2>
&lt;p>What it measures: performance under standardised scenarios (Offline, Server, Interactive)
with strict rules. Unlike the previous ones, MLPerf is not &amp;ldquo;run&amp;rdquo; for your day-to-day case:
it is a vendor comparison suite whose results are read (published by NVIDIA, AMD, Intel and
others). Use it to compare hardware and engines against each other under identical rules, not
to size your specific workload. For that, GuideLLM/AIPerf against your endpoint.&lt;/p>
&lt;hr>
&lt;h2 id="practical-comparison-table">Practical comparison table&lt;/h2>
&lt;table>
&lt;thead>
&lt;tr>
&lt;th>Tool&lt;/th>
&lt;th>Class&lt;/th>
&lt;th>Base command&lt;/th>
&lt;th>Output&lt;/th>
&lt;th>When&lt;/th>
&lt;/tr>
&lt;/thead>
&lt;tbody>
&lt;tr>
&lt;td>&lt;strong>vllm bench serve&lt;/strong>&lt;/td>
&lt;td>micro&lt;/td>
&lt;td>&lt;code>vllm bench serve&lt;/code>&lt;/td>
&lt;td>console/JSON&lt;/td>
&lt;td>tune vLLM&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>&lt;strong>vllm bench sweep&lt;/strong>&lt;/td>
&lt;td>micro (sweep)&lt;/td>
&lt;td>&lt;code>vllm bench sweep serve&lt;/code>&lt;/td>
&lt;td>console/JSON&lt;/td>
&lt;td>sweep concurrency in vLLM&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>&lt;strong>SGLang bench&lt;/strong>&lt;/td>
&lt;td>micro&lt;/td>
&lt;td>bench from the SGLang repo&lt;/td>
&lt;td>console/JSON&lt;/td>
&lt;td>tune SGLang&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>&lt;strong>AIPerf&lt;/strong>&lt;/td>
&lt;td>load&lt;/td>
&lt;td>&lt;code>aiperf profile …&lt;/code>&lt;/td>
&lt;td>JSON + estimatedCapacity&lt;/td>
&lt;td>real capacity, multi-endpoint&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>&lt;strong>GuideLLM&lt;/strong>&lt;/td>
&lt;td>load&lt;/td>
&lt;td>&lt;code>guidellm benchmark …&lt;/code>&lt;/td>
&lt;td>report + JSON/HTML&lt;/td>
&lt;td>SLO, reproducible sweep&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>&lt;strong>LLMPerf&lt;/strong>&lt;/td>
&lt;td>load&lt;/td>
&lt;td>Ray/LLMPerf script&lt;/td>
&lt;td>JSON&lt;/td>
&lt;td>validate endpoint (Ray)&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>&lt;strong>MLPerf&lt;/strong>&lt;/td>
&lt;td>suite&lt;/td>
&lt;td>(results are read)&lt;/td>
&lt;td>official results&lt;/td>
&lt;td>compare vendors&lt;/td>
&lt;/tr>
&lt;/tbody>
&lt;/table>
&lt;hr>
&lt;h2 id="summary-what-you-run-for-each-question">Summary: what you run for each question&lt;/h2>
&lt;p>So as not to get lost in the catalogue, the direct mapping from question to tool:&lt;/p>
&lt;table>
&lt;thead>
&lt;tr>
&lt;th>Your question&lt;/th>
&lt;th>Tool&lt;/th>
&lt;th>Why&lt;/th>
&lt;/tr>
&lt;/thead>
&lt;tbody>
&lt;tr>
&lt;td>&amp;ldquo;Does my vLLM config change help?&amp;rdquo;&lt;/td>
&lt;td>&lt;code>vllm bench serve&lt;/code> (+ sweep)&lt;/td>
&lt;td>fast, engine-native&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>&amp;ldquo;How hard can I load without breaking the SLO?&amp;rdquo;&lt;/td>
&lt;td>GuideLLM&lt;/td>
&lt;td>SLO-driven sweeps&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>&amp;ldquo;What is the maximum capacity of the endpoint?&amp;rdquo;&lt;/td>
&lt;td>AIPerf&lt;/td>
&lt;td>automatic knee detection&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>&amp;ldquo;vLLM or SGLang for my workload?&amp;rdquo;&lt;/td>
&lt;td>GuideLLM/AIPerf against both&lt;/td>
&lt;td>same tool, engine as the variable&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>&amp;ldquo;Which hardware/engine is better in the abstract?&amp;rdquo;&lt;/td>
&lt;td>read MLPerf&lt;/td>
&lt;td>cross-vendor comparability&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>&amp;ldquo;Is there a regression in this release?&amp;rdquo;&lt;/td>
&lt;td>short sweep in CI against a baseline&lt;/td>
&lt;td>continuous detection&lt;/td>
&lt;/tr>
&lt;/tbody>
&lt;/table>
&lt;p>The rule underlying the whole table: &lt;strong>micro-bench to iterate on an engine, load generator to
measure capacity and decide, MLPerf to compare vendors&lt;/strong>. And, for any comparison between
systems, the same tool for every candidate. If these three categories are clear to you, the
specific choice is secondary.&lt;/p>
&lt;hr>
&lt;h2 id="step-by-step-benchmark-methodology">Step-by-step benchmark methodology&lt;/h2>
&lt;p>A reliable run is not &amp;ldquo;launching the command&amp;rdquo;: it is a procedure. The steps, with the load
tool (GuideLLM/AIPerf) against your endpoint:&lt;/p>
&lt;div class="diagram" style="max-width:780px;margin:1rem auto;">
&lt;svg viewBox="0 0 780 170" role="img" aria-label="Benchmark flow: deploy the engine, warm up, sweep concurrency, collect metrics and compare" xmlns="http://www.w3.org/2000/svg">
&lt;style>.bx{fill:none;stroke:currentColor;stroke-width:1.3}.tl{font:600 12px sans-serif;fill:currentColor}.ts{font:11px sans-serif;fill:currentColor}.ar{fill:none;stroke:currentColor;stroke-width:1.3;marker-end:url(#wm)}&lt;/style>
&lt;defs>&lt;marker id="wm" viewBox="0 0 10 10" refX="9" refY="5" markerWidth="7" markerHeight="7" orient="auto">&lt;path d="M0,0 L10,5 L0,10 z" fill="currentColor"/>&lt;/marker>&lt;/defs>
&lt;rect class="bx" x="20" y="50" width="120" height="44" rx="6"/>&lt;text x="80" y="69" text-anchor="middle" class="tl">1 · Deploy&lt;/text>&lt;text x="80" y="85" text-anchor="middle" class="ts">engine + config&lt;/text>
&lt;path class="ar" d="M140,72 L165,72"/>
&lt;rect class="bx" x="165" y="50" width="120" height="44" rx="6"/>&lt;text x="225" y="69" text-anchor="middle" class="tl">2 · Warm up&lt;/text>&lt;text x="225" y="85" text-anchor="middle" class="ts">discard warm-up&lt;/text>
&lt;path class="ar" d="M285,72 L310,72"/>
&lt;rect class="bx" x="310" y="50" width="120" height="44" rx="6"/>&lt;text x="370" y="69" text-anchor="middle" class="tl">3 · Sweep&lt;/text>&lt;text x="370" y="85" text-anchor="middle" class="ts">go past the knee&lt;/text>
&lt;path class="ar" d="M430,72 L455,72"/>
&lt;rect class="bx" x="455" y="50" width="120" height="44" rx="6"/>&lt;text x="515" y="69" text-anchor="middle" class="tl">4 · Collect&lt;/text>&lt;text x="515" y="85" text-anchor="middle" class="ts">JSON with percentiles&lt;/text>
&lt;path class="ar" d="M575,72 L600,72"/>
&lt;rect class="bx" x="600" y="50" width="120" height="44" rx="6"/>&lt;text x="660" y="69" text-anchor="middle" class="tl">5 · Compare&lt;/text>&lt;text x="660" y="85" text-anchor="middle" class="ts">goodput vs SLO&lt;/text>
&lt;text x="20" y="130" class="ts">Pin model, precision, hardware, dataset and SLO before step 1; record them in the output of step 4.&lt;/text>
&lt;/svg>
&lt;/div>
&lt;ol>
&lt;li>&lt;strong>Deploy&lt;/strong> the engine with the exact config to be measured (model, precision, flags),
pinned.&lt;/li>
&lt;li>&lt;strong>Warm up&lt;/strong>: send a few requests and discard them, so that the prefix cache and the
autotuning do not inflate the first numbers.&lt;/li>
&lt;li>&lt;strong>Sweep&lt;/strong>: sweep rising concurrencies (1, 8, 16, 24, 32…) beyond the knee, to see where
latency takes off.&lt;/li>
&lt;li>&lt;strong>Collect&lt;/strong> the output in JSON with percentiles (TTFT/ITL/throughput/goodput) and the
metadata.&lt;/li>
&lt;li>&lt;strong>Compare&lt;/strong>: read the goodput under the SLO, not the maximum throughput.&lt;/li>
&lt;/ol>
&lt;p>Skipping step 2 (warm-up) or not going past the knee in step 3 are the two mistakes that bias
the result the most.&lt;/p>
&lt;hr>
&lt;h2 id="comparing-two-engines-the-fair-protocol">Comparing two engines: the fair protocol&lt;/h2>
&lt;p>If the goal is to choose between vLLM and SGLang (or TRT-LLM), the protocol that avoids false
conclusions:&lt;/p>
&lt;ul>
&lt;li>&lt;strong>Same load tool&lt;/strong> (GuideLLM or AIPerf) against both endpoints, never each engine&amp;rsquo;s own
micro-bench.&lt;/li>
&lt;li>&lt;strong>Same dataset and length distribution.&lt;/strong>&lt;/li>
&lt;li>&lt;strong>Same hardware and precision.&lt;/strong>&lt;/li>
&lt;li>&lt;strong>Same SLO&lt;/strong> to compute the goodput of both.&lt;/li>
&lt;li>Vary only the engine; everything else fixed.&lt;/li>
&lt;/ul>
&lt;p>Only then is the difference you measure down to the engine and not to the tool, the dataset or
the hardware. It is the controlled experiment that supports the scorecard row (article B8).&lt;/p>
&lt;hr>
&lt;h2 id="output-format-and-comparability">Output format and comparability&lt;/h2>
&lt;p>What you record alongside the number is what makes it comparable. A useful output includes,
in JSON so it can be versioned:&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-json" data-lang="json">&lt;span class="line">&lt;span class="cl">&lt;span class="p">{&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nt">&amp;#34;tool&amp;#34;&lt;/span>&lt;span class="p">:&lt;/span> &lt;span class="s2">&amp;#34;guidellm&amp;#34;&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="nt">&amp;#34;version&amp;#34;&lt;/span>&lt;span class="p">:&lt;/span> &lt;span class="s2">&amp;#34;x.y.z&amp;#34;&lt;/span>&lt;span class="p">,&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nt">&amp;#34;model&amp;#34;&lt;/span>&lt;span class="p">:&lt;/span> &lt;span class="s2">&amp;#34;Llama-3.1-70B&amp;#34;&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="nt">&amp;#34;precision&amp;#34;&lt;/span>&lt;span class="p">:&lt;/span> &lt;span class="s2">&amp;#34;FP16&amp;#34;&lt;/span>&lt;span class="p">,&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nt">&amp;#34;hardware&amp;#34;&lt;/span>&lt;span class="p">:&lt;/span> &lt;span class="s2">&amp;#34;8xH100 SXM NVLink&amp;#34;&lt;/span>&lt;span class="p">,&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nt">&amp;#34;load&amp;#34;&lt;/span>&lt;span class="p">:&lt;/span> &lt;span class="p">{&lt;/span>&lt;span class="nt">&amp;#34;prompt_tokens&amp;#34;&lt;/span>&lt;span class="p">:&lt;/span> &lt;span class="mi">1024&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="nt">&amp;#34;output_tokens&amp;#34;&lt;/span>&lt;span class="p">:&lt;/span> &lt;span class="mi">256&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="nt">&amp;#34;concurrency&amp;#34;&lt;/span>&lt;span class="p">:&lt;/span> &lt;span class="mi">16&lt;/span>&lt;span class="p">},&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nt">&amp;#34;results&amp;#34;&lt;/span>&lt;span class="p">:&lt;/span> &lt;span class="p">{&lt;/span>&lt;span class="nt">&amp;#34;ttft_p50_ms&amp;#34;&lt;/span>&lt;span class="p">:&lt;/span> &lt;span class="mi">180&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="nt">&amp;#34;ttft_p99_ms&amp;#34;&lt;/span>&lt;span class="p">:&lt;/span> &lt;span class="mi">460&lt;/span>&lt;span class="p">,&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nt">&amp;#34;itl_p50_ms&amp;#34;&lt;/span>&lt;span class="p">:&lt;/span> &lt;span class="mi">22&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="nt">&amp;#34;throughput_tok_s&amp;#34;&lt;/span>&lt;span class="p">:&lt;/span> &lt;span class="mi">3400&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="nt">&amp;#34;goodput_tok_s&amp;#34;&lt;/span>&lt;span class="p">:&lt;/span> &lt;span class="mi">3330&lt;/span>&lt;span class="p">}&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="p">}&lt;/span>
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>Saving this for every run turns a benchmark into an auditable datum: anyone can reproduce the
figure with the same tool, version, model, hardware and load. It is the material of the
reproducible harness (article S4), and the difference between a defensible number and a
console screenshot.&lt;/p>
&lt;hr>
&lt;h2 id="worked-example-reading-the-output-of-a-sweep">Worked example: reading the output of a sweep&lt;/h2>
&lt;p>An illustrative output from a GuideLLM sweep on a 70B on 8×H100 (SLO: P99 TTFT &amp;lt; 500 ms), just
as you would read it:&lt;/p>
&lt;table>
&lt;thead>
&lt;tr>
&lt;th>Concurrency&lt;/th>
&lt;th>TTFT P99 (ms)&lt;/th>
&lt;th>ITL P50 (ms)&lt;/th>
&lt;th>Throughput (tok/s)&lt;/th>
&lt;th>Goodput (tok/s)&lt;/th>
&lt;/tr>
&lt;/thead>
&lt;tbody>
&lt;tr>
&lt;td>8&lt;/td>
&lt;td>240&lt;/td>
&lt;td>20&lt;/td>
&lt;td>2,100&lt;/td>
&lt;td>2,100&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>16&lt;/td>
&lt;td>460&lt;/td>
&lt;td>22&lt;/td>
&lt;td>3,400&lt;/td>
&lt;td>3,330&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>24&lt;/td>
&lt;td>980&lt;/td>
&lt;td>31&lt;/td>
&lt;td>3,900&lt;/td>
&lt;td>2,420&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>32&lt;/td>
&lt;td>1,800&lt;/td>
&lt;td>54&lt;/td>
&lt;td>4,000&lt;/td>
&lt;td>800&lt;/td>
&lt;/tr>
&lt;/tbody>
&lt;/table>
&lt;p>How to read it: the &lt;strong>knee&lt;/strong> sits between 16 and 24. At concurrency 16, P99 (460 ms) meets the
SLO and goodput (3,330 tok/s) ≈ throughput. At 24, throughput rises little (3,400 → 3,900) but
P99 already violates the SLO and goodput falls to 2,420. At 32, throughput is at its
maximum (4,000) but goodput collapses to 800: the system &amp;ldquo;performs well&amp;rdquo; while serving requests
that do not comply. The defensible capacity is the one at concurrency 16 (3,330 useful
tok/s), and that is the number that goes into &lt;a href="https://blog.lo0.es/en/posts/capacity-planning-on-premise-llm-inference/">capacity planning&lt;/a>
and into cost per token. Anyone reporting &amp;ldquo;4,000 tok/s&amp;rdquo; is describing the point where the
system no longer meets its SLO.&lt;/p>
&lt;hr>
&lt;h2 id="automating-the-harness">Automating the harness&lt;/h2>
&lt;p>A manual run does not scale to a benchmarking programme. The minimum automation:&lt;/p>
&lt;ul>
&lt;li>An &lt;strong>idempotent script&lt;/strong> that deploys the engine, warms it up, runs the sweep and saves the
JSON with all the metadata (model, version, hardware, dataset, SLO).&lt;/li>
&lt;li>&lt;strong>Naming by date and config&lt;/strong>, to version the runs and compare over time.&lt;/li>
&lt;li>A &lt;strong>results store&lt;/strong> (a git repo of JSONs, or a bucket) so that anyone can reproduce and
compare.&lt;/li>
&lt;/ul>
&lt;p>The goal is that reproducing a number is one command, not an afternoon. It is the basis of the
harness in article S4, and what turns benchmarking from a one-off activity into a continuous
platform capability.&lt;/p>
&lt;hr>
&lt;h2 id="ci-integration-continuous-benchmarking">CI integration: continuous benchmarking&lt;/h2>
&lt;p>The next level is measuring on every change: a CI job that, when the engine or the config
is updated, launches a short sweep against a test environment and &lt;strong>compares against the
baseline&lt;/strong>. If goodput falls by more than a threshold, the pipeline fails. That way a
performance regression is caught at the commit, not in production. Watch out for two things:
benchmarking in CI consumes GPU-hours (budget for it) and it needs a stable environment (same
hardware) for the comparison to be valid. You do not need the full sweep on every commit: a
short sweep covering the knee is enough to detect regressions; the exhaustive sweep is for
releases.&lt;/p>
&lt;hr>
&lt;h2 id="the-cost-of-measuring-in-euros">The cost of measuring (in euros)&lt;/h2>
&lt;p>Benchmarking consumes GPU-hours, and that has a cost worth budgeting for. A serious sweep
on an 8×H100 node can occupy the cards for a couple of hours; at an amortised cost of ~11 €/h,
that is ~22 € per full sweep, more if you sweep several models and precisions. It is not much
per run, but a continuous benchmarking programme (every release, every config change) adds up.
The practical rule: automate the harness so that each run is cheap and reproducible, and
measure what you are going to use to decide, not for the sake of exhaustiveness. The cost of
measuring is part of the cost of the platform, small next to the cost of serving but real.&lt;/p>
&lt;hr>
&lt;h2 id="observing-during-the-benchmark-one-run-three-axes">Observing during the benchmark: one run, three axes&lt;/h2>
&lt;p>A trick that saves work and connects the series: &lt;strong>while the sweep runs, capture the GPU
metrics too&lt;/strong>. With DCGM exporting to Prometheus during the run, you record at the same time:&lt;/p>
&lt;table>
&lt;thead>
&lt;tr>
&lt;th>Axis&lt;/th>
&lt;th>Source during the sweep&lt;/th>
&lt;th>Metric&lt;/th>
&lt;/tr>
&lt;/thead>
&lt;tbody>
&lt;tr>
&lt;td>Performance&lt;/td>
&lt;td>the tool (GuideLLM/AIPerf)&lt;/td>
&lt;td>TTFT, ITL, throughput, goodput&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>Energy&lt;/td>
&lt;td>DCGM&lt;/td>
&lt;td>power (W) → J/token&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>Cost&lt;/td>
&lt;td>node price (OpenCost)&lt;/td>
&lt;td>€/hour → CPM per point&lt;/td>
&lt;/tr>
&lt;/tbody>
&lt;/table>
&lt;p>That way, from a single run you get the three numbers for the same operating point: at
concurrency 16, the goodput (3,330 tok/s), the average power (from DCGM, which divided by the
throughput gives J/token) and the cost per token (with the node price). Instead of three
separate campaigns, you measure the three axes at once, and they end up &lt;strong>coherent by
construction&lt;/strong> because they correspond to the same instant and the same load. It is what the
harness in article S4 does, and the reason to export DCGM during the benchmark even if you are
only after performance: energy and cost come almost free if you capture them in the same
window.&lt;/p>
&lt;p>The methodological warning: align the time windows. The DCGM power figures and the tool&amp;rsquo;s
metrics have to cover exactly the same interval (no warm-up, no shutdown), or the J/token does
not correspond to the throughput measured. Same window, same three numbers.&lt;/p>
&lt;hr>
&lt;h2 id="the-benchmark-is-also-finops-and-energy">The benchmark is also FinOps and energy&lt;/h2>
&lt;p>An idea that connects this article with the rest of the series: &lt;strong>measuring performance is, in
fact, measuring cost and energy&lt;/strong>. By the identity of the &lt;a href="https://blog.lo0.es/en/posts/cost-performance-energy-three-axes-llm-inference/">opening article&lt;/a>,
goodput is the denominator of cost per token and of energy per token. When a sweep reveals
that config A gives 3,330 tok/s of goodput and config B gives 4,000, you are not only measuring
speed: you are measuring that B costs fewer euros and fewer watts per token. That is why the
output JSON of a benchmark should be accompanied by the iron cost (from OpenCost) to compute
the real CPM of every point in the sweep: throughput × node price = cost per token.
Benchmarking is not an isolated axis; it is the tool that, indirectly, moves the cost and the
energy of the platform the most, and the one that fills the performance column of the scorecard
with numbers that translate directly into euros.&lt;/p>
&lt;p>The operational consequence: do not benchmark performance in a vacuum. Every run you save with
its throughput and its goodput should be crossable with the node cost (€/hour) and the energy
(J/token) to give the three faces of the same operating point. That is how the benchmarking
track feeds the FinOps and energy tracks instead of living apart.&lt;/p>
&lt;hr>
&lt;h2 id="state-of-the-art-2026-and-limits">State of the art 2026 and limits&lt;/h2>
&lt;ul>
&lt;li>&lt;strong>Migration to multi-process&lt;/strong>: AIPerf (formerly genai-perf) and GuideLLM consolidate the
measurement of real capacity; micro-benches are left for tuning engines.&lt;/li>
&lt;li>&lt;strong>GuideLLM as the OSS standard&lt;/strong> for SLO-driven evaluation with an automatic report.&lt;/li>
&lt;li>&lt;strong>Careful with comparing across classes&lt;/strong>: a micro-bench and a load generator are not
comparable; pin the class and the tool.&lt;/li>
&lt;li>&lt;strong>Version and dataset matter&lt;/strong>: the same command with a different dataset or version gives a
different number; pin them.&lt;/li>
&lt;li>&lt;strong>MLPerf does not size your case&lt;/strong>: it compares vendors, it does not replace a sweep against
your workload.&lt;/li>
&lt;/ul>
&lt;p>With the practical catalogue covered, the next article in the track (B3) goes into GuideLLM
and SLO validation under load in depth. The tool is the means; the reproducible datum is the
end.&lt;/p>
&lt;h2 id="mistakes-that-invalidate-a-benchmark">Mistakes that invalidate a benchmark&lt;/h2>
&lt;p>To finish, the list of what turns a run into rubbish, by frequency:&lt;/p>
&lt;table>
&lt;thead>
&lt;tr>
&lt;th>Mistake&lt;/th>
&lt;th>Effect&lt;/th>
&lt;th>Fix&lt;/th>
&lt;/tr>
&lt;/thead>
&lt;tbody>
&lt;tr>
&lt;td>Comparing different classes&lt;/td>
&lt;td>up to 7× of spurious difference&lt;/td>
&lt;td>same tool for everyone&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>Not discarding the warm-up&lt;/td>
&lt;td>artificially low TTFT&lt;/td>
&lt;td>warm up and discard&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>Not going past the knee&lt;/td>
&lt;td>you do not know the safe capacity&lt;/td>
&lt;td>extend the sweep&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>Unrealistic dataset (fixed length)&lt;/td>
&lt;td>throughput that does not apply&lt;/td>
&lt;td>sharegpt or your own traces&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>Reporting the mean, not P99&lt;/td>
&lt;td>hides the tail&lt;/td>
&lt;td>percentiles always&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>Tokenizer foreign to the model&lt;/td>
&lt;td>biased tok/s and cost per token&lt;/td>
&lt;td>count with the model&amp;rsquo;s own&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>Not pinning version/config&lt;/td>
&lt;td>irreproducible&lt;/td>
&lt;td>save everything in the JSON&lt;/td>
&lt;/tr>
&lt;/tbody>
&lt;/table>
&lt;p>Any one of these is enough for the number not to be defensible. A benchmark is only as good as
its methodology: the tool matters less than running it properly and recording everything.&lt;/p>
&lt;h2 id="closing">Closing&lt;/h2>
&lt;p>The catalogue of benchmark tools boils down to a simple decision (micro-bench to tune an
engine, load generator to measure capacity, MLPerf to compare vendors) and to a discipline that
weighs more than the choice: &lt;strong>the method&lt;/strong>. The same &lt;code>guidellm benchmark&lt;/code> gives a golden datum
or a useless one depending on the dataset, the warm-up, how far the sweep goes and what you
record in the output. For a sovereign architecture proposal, performance only counts if it
comes with its command, its version, its load and its goodput under SLO, and, crossed with cost
in euros and energy per token, it becomes the scorecard column that decides which engine and
which configuration hold up the platform. You choose the tool in five minutes; the methodology
is what makes the number survive an audit.&lt;/p>
&lt;h2 id="see-also">See also&lt;/h2>
&lt;ul>
&lt;li>&lt;a href="https://blog.lo0.es/en/posts/genai-perf-llm-inference-profiling-in-depth/">GenAI-Perf in depth&lt;/a> — an extended profile of the NVIDIA profiler: metrics (TTFT/TPOT/ISL/OSL), invocation against an OpenAI-compatible endpoint and comparison with GuideLLM/LLMPerf.&lt;/li>
&lt;li>&lt;a href="https://blog.lo0.es/en/posts/llm-serving-engines-pareto-frontier-comparison/">LLM serving engines compared (vLLM/SGLang/TRT-LLM/Dynamo)&lt;/a> — once goodput has been measured with these tools, here is which engine wins at each point of the Pareto frontier.&lt;/li>
&lt;li>&lt;a href="https://blog.lo0.es/en/posts/measurement-bias-reproducibility-llm-benchmarks/">Measurement bias and reproducibility&lt;/a> — the sources of bias that invalidate results even when the tool is correctly configured: warm-up, real versus synthetic dataset, shared environment.&lt;/li>
&lt;/ul>
&lt;h2 id="sources">Sources&lt;/h2>
&lt;ul>
&lt;li>vLLM · CLI de benchmark (&lt;code>bench serve&lt;/code>) — &lt;a href="https://docs.vllm.ai/en/latest/cli/bench/serve/">https://docs.vllm.ai/en/latest/cli/bench/serve/&lt;/a>&lt;/li>
&lt;li>vLLM · &lt;code>bench sweep serve&lt;/code> — &lt;a href="https://docs.vllm.ai/en/latest/cli/bench/sweep/serve/">https://docs.vllm.ai/en/latest/cli/bench/sweep/serve/&lt;/a>&lt;/li>
&lt;li>Red Hat · desplegar y benchmarkear vLLM con GuideLLM en Kubernetes — &lt;a href="https://developers.redhat.com/articles/2025/12/24/how-deploy-and-benchmark-vllm-guidellm-kubernetes">https://developers.redhat.com/articles/2025/12/24/how-deploy-and-benchmark-vllm-guidellm-kubernetes&lt;/a>&lt;/li>
&lt;li>GuideLLM · GitHub (proyecto vLLM) — &lt;a href="https://github.com/vllm-project/guidellm">https://github.com/vllm-project/guidellm&lt;/a>&lt;/li>
&lt;li>NVIDIA AIPerf · guía de benchmarking — &lt;a href="https://lucaberton.com/blog/nvidia-aiperf-llm-inference-benchmarking-guide/">https://lucaberton.com/blog/nvidia-aiperf-llm-inference-benchmarking-guide/&lt;/a>&lt;/li>
&lt;li>Medium · Benchmarking LLM Serving Performance (guía) — &lt;a href="https://medium.com/@kimdoil1211/benchmarking-llm-serving-performance-a-comprehensive-guide-db94b1bfe8cf">https://medium.com/@kimdoil1211/benchmarking-llm-serving-performance-a-comprehensive-guide-db94b1bfe8cf&lt;/a>&lt;/li>
&lt;/ul></description></item><item><title>LLM inference benchmarking: frameworks, metrics and the state of the art (tool by tool)</title><link>https://blog.lo0.es/en/posts/llm-inference-benchmarking-frameworks-metrics-state-of-the-art/</link><pubDate>Sat, 13 Jun 2026 02:30:00 +0200</pubDate><guid>https://blog.lo0.es/en/posts/llm-inference-benchmarking-frameworks-metrics-state-of-the-art/</guid><description>&lt;blockquote>
&lt;p>Notation: amounts in &lt;strong>euros (N €)&lt;/strong>, decimals with a point. Performance is not very
sensitive to the country, but its associated cost (cost per token) is expressed in € and
links back to the &lt;a href="https://blog.lo0.es/en/posts/cost-performance-energy-three-axes-llm-inference/">opening article&lt;/a>.&lt;/p>
&lt;/blockquote>
&lt;h2 id="what-this-introduction-covers">What this introduction covers&lt;/h2>
&lt;p>Third article in the data series, a deep dive into the performance axis. Measuring the
performance of an inference engine looks trivial (&amp;ldquo;how many tokens per second?&amp;rdquo;) and it is
exactly where people fool themselves most: two tools can report results that differ by a
factor of 7 for the same system. This article inventories the metrics that matter and &lt;strong>how
they are defined&lt;/strong>, why the architecture of the tool biases the figure, how the saturation
point is found with a concurrency &lt;em>sweep&lt;/em>, and the profile of each framework. No
recommendations: the choice of engine is settled in the Pareto article (B8); here there are
only the facts and the methodology, because &lt;strong>a benchmark without a published methodology is
not comparable&lt;/strong>.&lt;/p>
&lt;hr>
&lt;h2 id="the-performance-metrics">The performance metrics&lt;/h2>
&lt;p>There is no single performance metric, there are five, and mixing them is the first source
of error:&lt;/p>
&lt;table>
&lt;thead>
&lt;tr>
&lt;th>Metric&lt;/th>
&lt;th>Definition&lt;/th>
&lt;th>Unit&lt;/th>
&lt;th>Dominant phase&lt;/th>
&lt;/tr>
&lt;/thead>
&lt;tbody>
&lt;tr>
&lt;td>&lt;strong>TTFT&lt;/strong> (Time To First Token)&lt;/td>
&lt;td>time from sending the prompt to the first token&lt;/td>
&lt;td>ms&lt;/td>
&lt;td>prefill&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>&lt;strong>TPOT / ITL&lt;/strong> (Time Per Output Token / Inter-Token Latency)&lt;/td>
&lt;td>average time between output tokens once generation has started&lt;/td>
&lt;td>ms/token&lt;/td>
&lt;td>decode&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>&lt;strong>Request throughput&lt;/strong>&lt;/td>
&lt;td>complete request-response cycles per second at the concurrency tested&lt;/td>
&lt;td>req/s&lt;/td>
&lt;td>both&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>&lt;strong>Token throughput&lt;/strong>&lt;/td>
&lt;td>total tokens (input + output) per second across all concurrent requests&lt;/td>
&lt;td>tok/s&lt;/td>
&lt;td>both&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>&lt;strong>Goodput&lt;/strong>&lt;/td>
&lt;td>share of requests that &lt;strong>meet the defined SLO&lt;/strong>&lt;/td>
&lt;td>useful tok/s&lt;/td>
&lt;td>both&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>&lt;strong>P50 / P95 / P99&lt;/strong>&lt;/td>
&lt;td>latency percentiles (not the mean)&lt;/td>
&lt;td>ms&lt;/td>
&lt;td>—&lt;/td>
&lt;/tr>
&lt;/tbody>
&lt;/table>
&lt;p>Precise definitions, because every tool computes them its own way (&lt;a href="https://docs.anyscale.com/llm/serving/benchmarking/metrics">Anyscale · latency and
throughput metrics&lt;/a>): TTFT is
what a user waits before seeing the first character, dominated by prefill compute; ITL is
the average time between successive output tokens and sets the perceived &amp;ldquo;typing speed&amp;rdquo; of
the answer; request throughput is complete cycles per second; token throughput is
total tokens (input plus output) per second across all concurrent requests.&lt;/p>
&lt;h3 id="the-latency-decomposition">The latency decomposition&lt;/h3>
&lt;p>The total latency of a request with \(N\) output tokens decomposes as:&lt;/p>
$$\text{latency} \approx \text{TTFT} + (N-1)\times \text{TPOT}$$
&lt;p>That is why TTFT and TPOT are reported separately: a single mean latency hides very
different profiles. A system with high TTFT and low TPOT (expensive prefill, fast decode) and
another one the other way round can have the same mean latency for one particular length, yet
behave in opposite ways when the response size changes. For an interactive chat experience
TTFT and TPOT rule; for a batch of long summaries, token throughput does. Measuring the mean
hides both realities.&lt;/p>
&lt;h3 id="goodput-the-honest-metric">Goodput: the honest metric&lt;/h3>
&lt;p>Raw throughput (TPS, RPS) says how much work the system does; &lt;strong>goodput&lt;/strong> says how much
of that work meets your quality-of-service standards (SLO)
(&lt;a href="https://docs.anyscale.com/llm/serving/benchmarking/metrics">Anyscale&lt;/a>). An engine can boast
10,000 aggregate tok/s, but if half the requests violate the P99 TTFT SLO, its goodput is
5,000. The figure you defend in a proposal is the &lt;strong>goodput&lt;/strong>, not the catalogue throughput:
it is the only one that translates into satisfied users and an honest cost per token.&lt;/p>
&lt;hr>
&lt;h2 id="how-each-metric-is-instrumented-and-where-the-error-creeps-in">How each metric is instrumented (and where the error creeps in)&lt;/h2>
&lt;p>Before comparing numbers it helps to know where each clock starts and stops, because two
tools can call different things &amp;ldquo;TTFT&amp;rdquo;:&lt;/p>
&lt;ul>
&lt;li>&lt;strong>Client-side versus server-side TTFT.&lt;/strong> The TTFT measured by the client includes network
latency and gateway queueing; the one measured by the server does not. For an engine
comparison the server-side figure is the one that matters; for user experience, the
client-side one. Mixing the two invalidates the comparison.&lt;/li>
&lt;li>&lt;strong>Streaming is required.&lt;/strong> TTFT and ITL can only be measured if the response arrives as a
&lt;em>stream&lt;/em> (token by token). If the tool measures complete responses, there is no real TTFT:
there is total latency in disguise.&lt;/li>
&lt;li>&lt;strong>Token counting.&lt;/strong> Throughput in tok/s depends on which tokenizer counts the tokens.
If the tool uses a tokenizer different from the model&amp;rsquo;s, the token count (and therefore the
tok/s and the cost per token) is biased. You have to count with the tokenizer of the model
being served.&lt;/li>
&lt;li>&lt;strong>Warm-up and prefix cache.&lt;/strong> The first requests of a benchmark benefit from a warm prefix
cache and give artificially low TTFT; the warm-up has to be discarded or the result
inflates reality.&lt;/li>
&lt;/ul>
&lt;p>These four instrumentation decisions explain a good share of the discrepancies between tools.
A performance figure that does not say where the clock is measured and with which tokenizer
is not comparable, however precise it may look.&lt;/p>
&lt;hr>
&lt;h2 id="the-architecture-of-the-tool-biases-the-figure">The architecture of the tool biases the figure&lt;/h2>
&lt;p>Here is the trap that invalidates half the published benchmarks. Tools split into two classes
by the architecture of the client that generates the load, and that architecture
determines whether the measurement is reliable at high concurrency:&lt;/p>
&lt;ul>
&lt;li>&lt;strong>Single-process micro-bench&lt;/strong> (vLLM bench, SGLang bench, genai-perf): a Python client with
asyncio in a single process. Useful for quick experiments on one engine, but the
single-process architecture introduces a bottleneck on the client side that &lt;strong>biases the
data at high concurrency&lt;/strong> (&lt;a href="https://kchandan.medium.com/llm-inference-benchmarking-genai-perf-and-vllm-5dd06b57428e">genAI-perf and vLLM&lt;/a>):
the client cannot generate enough load and you measure the limit of the client, not the
limit of the engine.&lt;/li>
&lt;li>&lt;strong>Multi-process load&lt;/strong> (GuideLLM, AIPerf): they spread load generation across several
processes, avoiding that limit. This is the class that has emerged for measuring at real
scale.&lt;/li>
&lt;/ul>
&lt;p>The size of the bias is enormous: at 1,000 QPS, a single-process benchmark processed
75,574 tokens against the 545,733 tokens of a distributed architecture, a
discrepancy of 7.2× in measurement capacity for the same system ([search]). Anyone
comparing two engines with tools from different classes is not comparing the engines: they
are comparing the benchmark clients.&lt;/p>
&lt;div class="diagram" style="max-width:780px;margin:1rem auto;">
&lt;svg viewBox="0 0 780 210" role="img" aria-label="Two classes of benchmark tool: a single-process client that saturates at high concurrency versus a multi-process load generator that measures the engine" xmlns="http://www.w3.org/2000/svg">
&lt;style>.bx{fill:none;stroke:currentColor;stroke-width:1.3}.dsh{fill:none;stroke:currentColor;stroke-width:1.3;stroke-dasharray:5 3}.tl{font:600 12px sans-serif;fill:currentColor}.ts{font:11px sans-serif;fill:currentColor}.ar{fill:none;stroke:currentColor;stroke-width:1.3;marker-end:url(#bm)}&lt;/style>
&lt;defs>&lt;marker id="bm" viewBox="0 0 10 10" refX="9" refY="5" markerWidth="7" markerHeight="7" orient="auto">&lt;path d="M0,0 L10,5 L0,10 z" fill="currentColor"/>&lt;/marker>&lt;/defs>
&lt;text x="20" y="26" class="tl">Single-process (vLLM bench, SGLang bench, genai-perf)&lt;/text>
&lt;rect class="bx" x="20" y="36" width="150" height="40" rx="6"/>
&lt;text x="32" y="62" class="ts">1 asyncio client&lt;/text>
&lt;path class="ar" d="M170,58 L215,58"/>
&lt;rect class="dsh" x="215" y="36" width="120" height="40" rx="6"/>
&lt;text x="227" y="56" class="ts">client bottleneck&lt;/text>
&lt;text x="227" y="71" class="ts">bias at high conc.&lt;/text>
&lt;path class="ar" d="M335,58 L380,58"/>
&lt;rect class="bx" x="380" y="36" width="110" height="40" rx="6"/>
&lt;text x="392" y="62" class="ts">engine (vLLM…)&lt;/text>
&lt;text x="510" y="52" class="ts">you measure the client,&lt;/text>
&lt;text x="510" y="68" class="ts">not the engine&lt;/text>
&lt;text x="20" y="118" class="tl">Multi-process (GuideLLM, AIPerf)&lt;/text>
&lt;rect class="bx" x="20" y="128" width="150" height="46" rx="6"/>
&lt;text x="32" y="148" class="ts">N load processes&lt;/text>
&lt;text x="32" y="164" class="ts">(real load)&lt;/text>
&lt;path class="ar" d="M170,151 L380,151"/>
&lt;rect class="bx" x="380" y="131" width="110" height="40" rx="6"/>
&lt;text x="392" y="155" class="ts">engine (vLLM…)&lt;/text>
&lt;text x="510" y="145" class="ts">you measure the engine;&lt;/text>
&lt;text x="510" y="161" class="ts">7.2× more capacity&lt;/text>
&lt;/svg>
&lt;/div>
&lt;hr>
&lt;h2 id="frameworks-one-by-one">Frameworks, one by one&lt;/h2>
&lt;h3 id="vllm-bench-and-sglang-bench--engine-micro-bench">vLLM bench and SGLang bench — engine micro-bench&lt;/h3>
&lt;p>What they measure: TTFT, TPOT and throughput of the engine itself (vLLM or SGLang). Class:
single-process micro-bench. Use: quick experiments to tune one engine and see the effect of
its optimisations (see &lt;a href="https://blog.lo0.es/en/posts/optimising-decode-vllm-squeezing-every-token-small-hardware/">decode&lt;/a> and
&lt;a href="https://blog.lo0.es/en/posts/optimising-prefill-vllm-knobs-ttft-will-not-forgive/">prefill&lt;/a>). Limit: they saturate on the client at
high concurrency; they are no good for measuring real capacity at scale.&lt;/p>
&lt;h3 id="aiperf--the-nvidia-one-formerly-genai-perf-multi-process">AIPerf — the NVIDIA one (formerly genai-perf), multi-process&lt;/h3>
&lt;p>What it measures: TTFT, ITL, throughput and latency against &lt;strong>vLLM, NIM, TGI and any
compatible endpoint&lt;/strong>. Class: multi-process load. State-of-the-art datum: NVIDIA &lt;strong>retired
genai-perf and replaced it with AIPerf on 15 April 2026&lt;/strong>. During the &lt;em>sweep&lt;/em>, AIPerf
detects GPU saturation and identifies the previous iteration, returning it as
&lt;code>estimatedCapacity&lt;/code>; if it detects no saturation, &lt;code>estimatedCapacity&lt;/code> is the last iteration
tested, which is why the sweep has to extend beyond the knee
(&lt;a href="https://lucaberton.com/blog/nvidia-aiperf-llm-inference-benchmarking-guide/">AIPerf&lt;/a>).&lt;/p>
&lt;h3 id="guidellm--from-the-vllm-project-slo-oriented">GuideLLM — from the vLLM project, SLO-oriented&lt;/h3>
&lt;p>What it measures: full distributions of TTFT, ITL and end-to-end behaviour, for
SLO-driven evaluation. Class: multi-process load. Differentiator: it generates realistic,
configurable traffic patterns in synchronous, concurrent and rate-based modes, including
reproducible sweeps to identify safe operating ranges
(&lt;a href="https://developers.redhat.com/articles/2025/06/20/guidellm-evaluate-llm-deployments-real-world-inference">Red Hat · GuideLLM&lt;/a>,
&lt;a href="https://github.com/vllm-project/guidellm">GuideLLM · GitHub&lt;/a>). It is the tool for answering
&amp;ldquo;how hard can I load this engine without breaking the SLO?&amp;rdquo;.&lt;/p>
&lt;h3 id="llmperf--the-anyscaleray-classic">LLMPerf — the Anyscale/Ray classic&lt;/h3>
&lt;p>What it measures: throughput and latency at the inference level. Use: endpoint validation,
historically very widespread. Class: load generator. Limit: less focused on distributions and
sweeps than GuideLLM/AIPerf.&lt;/p>
&lt;h3 id="mlperf-inference--the-industry-standard">MLPerf Inference — the industry standard&lt;/h3>
&lt;p>What it measures: performance under standardised scenarios with strict rules, for
comparability across vendors. Maintainer: &lt;strong>MLCommons&lt;/strong>. It is the gold standard of
cross-vendor comparability; it is expanded on below.&lt;/p>
&lt;h3 id="comparison-table">Comparison table&lt;/h3>
&lt;table>
&lt;thead>
&lt;tr>
&lt;th>Tool&lt;/th>
&lt;th>Class&lt;/th>
&lt;th>What it measures&lt;/th>
&lt;th>Maintainer&lt;/th>
&lt;th>When to use it&lt;/th>
&lt;/tr>
&lt;/thead>
&lt;tbody>
&lt;tr>
&lt;td>&lt;strong>vLLM bench&lt;/strong>&lt;/td>
&lt;td>single-process micro&lt;/td>
&lt;td>TTFT, TPOT, throughput of vLLM&lt;/td>
&lt;td>vLLM (OSS)&lt;/td>
&lt;td>tune vLLM, quick experiments&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>&lt;strong>SGLang bench&lt;/strong>&lt;/td>
&lt;td>single-process micro&lt;/td>
&lt;td>metrics of the SGLang engine&lt;/td>
&lt;td>SGLang (OSS)&lt;/td>
&lt;td>tune SGLang&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>&lt;strong>AIPerf&lt;/strong> (formerly genai-perf)&lt;/td>
&lt;td>multi-process load&lt;/td>
&lt;td>TTFT, ITL, throughput; estimatedCapacity&lt;/td>
&lt;td>NVIDIA (OSS)&lt;/td>
&lt;td>real capacity, multi-endpoint&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>&lt;strong>GuideLLM&lt;/strong>&lt;/td>
&lt;td>multi-process load&lt;/td>
&lt;td>distributions, SLO, sweeps&lt;/td>
&lt;td>vLLM (OSS)&lt;/td>
&lt;td>validate SLO, find the knee&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>&lt;strong>LLMPerf&lt;/strong>&lt;/td>
&lt;td>load&lt;/td>
&lt;td>throughput and latency&lt;/td>
&lt;td>Anyscale/Ray (OSS)&lt;/td>
&lt;td>endpoint validation&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>&lt;strong>MLPerf Inference&lt;/strong>&lt;/td>
&lt;td>standard suite&lt;/td>
&lt;td>server/offline/interactive scenarios&lt;/td>
&lt;td>MLCommons&lt;/td>
&lt;td>cross-vendor comparability&lt;/td>
&lt;/tr>
&lt;/tbody>
&lt;/table>
&lt;hr>
&lt;h2 id="the-concurrency-sweep-finding-the-knee">The concurrency sweep: finding the knee&lt;/h2>
&lt;p>The most useful measurement for sizing is not a number, it is a curve: how latency and
throughput change as concurrency rises. Raising concurrency keeps the GPU busier and raises
RPS, but past a certain point it sends TTFT, ITL and end-to-end latency through the roof
([search]). The goal of the &lt;em>sweep&lt;/em> is to find the &lt;strong>knee&lt;/strong>: the maximum concurrency where
throughput still rises without latency breaking the SLO.&lt;/p>
&lt;div class="diagram" style="max-width:780px;margin:1rem auto;">
&lt;svg viewBox="0 0 780 260" role="img" aria-label="Concurrency sweep curve: throughput rises and saturates while latency spikes beyond the knee, which marks the safe capacity under SLO" xmlns="http://www.w3.org/2000/svg">
&lt;style>.ax{fill:none;stroke:currentColor;stroke-width:1}.cv{fill:none;stroke:currentColor;stroke-width:1.6}.dsh{fill:none;stroke:currentColor;stroke-width:1;stroke-dasharray:4 3}.tl{font:600 12px sans-serif;fill:currentColor}.ts{font:11px sans-serif;fill:currentColor}&lt;/style>
&lt;line class="ax" x1="60" y1="40" x2="60" y2="200"/>
&lt;line class="ax" x1="60" y1="200" x2="720" y2="200"/>
&lt;text x="20" y="120" class="ts" transform="rotate(-90 20 120)">metric&lt;/text>
&lt;text x="360" y="228" class="ts">concurrency →&lt;/text>
&lt;path class="cv" d="M60,190 C200,150 300,120 400,112 C520,104 620,100 700,98"/>
&lt;text x="600" y="92" class="ts">throughput (saturates)&lt;/text>
&lt;path class="cv" d="M60,180 C260,176 360,170 430,150 C520,120 600,70 700,46"/>
&lt;text x="600" y="40" class="ts">latency (spikes)&lt;/text>
&lt;line class="dsh" x1="430" y1="40" x2="430" y2="200"/>
&lt;text x="392" y="56" class="tl">knee&lt;/text>
&lt;text x="362" y="216" class="ts">safe capacity under SLO&lt;/text>
&lt;text x="60" y="250" class="ts">Before the knee, raising concurrency gives more throughput for free. After it, latency breaks the SLO with barely any gain.&lt;/text>
&lt;/svg>
&lt;/div>
&lt;p>That is why AIPerf extends the sweep beyond the knee: only by seeing where latency takes
off can it return the safe capacity (&lt;code>estimatedCapacity&lt;/code>). This curve is the raw material of
&lt;a href="https://blog.lo0.es/en/posts/capacity-planning-on-premise-llm-inference/">capacity planning&lt;/a>: the knee
yields the replica count and the cost per token at the target load.&lt;/p>
&lt;hr>
&lt;h3 id="worked-example-reading-a-sweep">Worked example: reading a sweep&lt;/h3>
&lt;p>An illustrative sweep on an example node (a 70B on 8×H100, SLO of P99 TTFT &amp;lt; 500 ms), to
see how the knee is read:&lt;/p>
&lt;table>
&lt;thead>
&lt;tr>
&lt;th>Concurrency&lt;/th>
&lt;th>RPS&lt;/th>
&lt;th>TTFT P50 (ms)&lt;/th>
&lt;th>TTFT P99 (ms)&lt;/th>
&lt;th>Token tput (tok/s)&lt;/th>
&lt;th>Goodput&lt;/th>
&lt;/tr>
&lt;/thead>
&lt;tbody>
&lt;tr>
&lt;td>1&lt;/td>
&lt;td>2&lt;/td>
&lt;td>80&lt;/td>
&lt;td>110&lt;/td>
&lt;td>350&lt;/td>
&lt;td>100 %&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>8&lt;/td>
&lt;td>14&lt;/td>
&lt;td>110&lt;/td>
&lt;td>240&lt;/td>
&lt;td>2,100&lt;/td>
&lt;td>100 %&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>16&lt;/td>
&lt;td>22&lt;/td>
&lt;td>180&lt;/td>
&lt;td>460&lt;/td>
&lt;td>3,400&lt;/td>
&lt;td>98 %&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>24&lt;/td>
&lt;td>26&lt;/td>
&lt;td>320&lt;/td>
&lt;td>980&lt;/td>
&lt;td>3,900&lt;/td>
&lt;td>62 %&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>32&lt;/td>
&lt;td>27&lt;/td>
&lt;td>540&lt;/td>
&lt;td>1,800&lt;/td>
&lt;td>4,000&lt;/td>
&lt;td>20 %&lt;/td>
&lt;/tr>
&lt;/tbody>
&lt;/table>
&lt;p>Reading: up to concurrency ~16 throughput grows and P99 stays under the SLO (goodput
~100 %). Between 16 and 24 sits the &lt;strong>knee&lt;/strong>: throughput barely rises any more (3,400 →
3,900 tok/s) but P99 takes off (460 → 980 ms) and goodput collapses (98 % → 62 %). At
concurrency 32 raw throughput is at its maximum (4,000 tok/s) but goodput is 20 %: the
system &amp;ldquo;performs well&amp;rdquo; while serving mostly requests that violate the SLO. The defensible
safe capacity is the one at concurrency ~16, not the one at maximum throughput. This is the
number that goes into &lt;a href="https://blog.lo0.es/en/posts/capacity-planning-on-premise-llm-inference/">capacity planning&lt;/a>
and into cost per token: at 3,400 useful tok/s, not 4,000 raw tok/s.&lt;/p>
&lt;hr>
&lt;h2 id="mlperf-inference-the-comparability-standard">MLPerf Inference: the comparability standard&lt;/h2>
&lt;p>To compare across vendors and engines with identical rules there is MLPerf Inference
(MLCommons). The datacenter category focuses on two scenarios plus an optional one
(&lt;a href="https://mlcommons.org/benchmarks/inference-datacenter/">MLCommons · datacenter&lt;/a>):&lt;/p>
&lt;table>
&lt;thead>
&lt;tr>
&lt;th>Scenario&lt;/th>
&lt;th>What it simulates&lt;/th>
&lt;th>Metric&lt;/th>
&lt;/tr>
&lt;/thead>
&lt;tbody>
&lt;tr>
&lt;td>&lt;strong>Offline&lt;/strong>&lt;/td>
&lt;td>raw throughput processing the whole dataset in batch&lt;/td>
&lt;td>maximum throughput&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>&lt;strong>Server&lt;/strong>&lt;/td>
&lt;td>interactive environment: requests one at a time following Poisson at a mean RPS&lt;/td>
&lt;td>RPS under TTFT and TPOT limits&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>&lt;strong>Interactive&lt;/strong> (optional)&lt;/td>
&lt;td>like server but with &lt;strong>stricter latency limits&lt;/strong>&lt;/td>
&lt;td>RPS under a hard SLO&lt;/td>
&lt;/tr>
&lt;/tbody>
&lt;/table>
&lt;p>The Server scenario is the realistic one for online inference: the load generator sends
requests following a Poisson distribution and demands that specific TTFT and TPOT bounds
be met. &lt;strong>MLPerf Inference v5.0&lt;/strong> (April 2025) introduced a large-scale 405B benchmark
and a low-latency interactive 70B one, offering language benchmarks at every scale (7B to
405B), architectural diversity (including MoE) and scenarios
(&lt;a href="https://mlcommons.org/2025/04/llm-inference-v5/">MLCommons · v5.0&lt;/a>); &lt;strong>v5.1&lt;/strong> (September
2025) widened the results with record participation
(&lt;a href="https://mlcommons.org/2025/09/mlperf-inference-v5-1-results/">MLCommons · v5.1&lt;/a>).&lt;/p>
&lt;p>The value of MLPerf is comparability: everyone measures the same thing under the same
rules. Its limit is that those rules may not match your workload (your length distribution,
your specific SLO), so it serves to compare hardware and engines against each other, not
necessarily to size your case. For that, your own sweep.&lt;/p>
&lt;hr>
&lt;h2 id="measurement-bias-and-reproducibility">Measurement bias and reproducibility&lt;/h2>
&lt;p>That two benchmarks give very different results for the same system is no accident:
systematic measurement bias in production benchmarks is characterised in the literature
(&lt;a href="https://arxiv.org/html/2605.24217">arXiv 2605.24217&lt;/a>), and there is work dedicated to the
meta-metrics and good practices of system-level performance benchmarking
(&lt;a href="https://arxiv.org/pdf/2508.10251">arXiv 2508.10251&lt;/a>). The most common sources of bias:&lt;/p>
&lt;table>
&lt;thead>
&lt;tr>
&lt;th>Source of bias&lt;/th>
&lt;th>Effect&lt;/th>
&lt;th>Mitigation&lt;/th>
&lt;/tr>
&lt;/thead>
&lt;tbody>
&lt;tr>
&lt;td>Single-process client&lt;/td>
&lt;td>underestimates throughput at high concurrency&lt;/td>
&lt;td>use multi-process load&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>Unrealistic length distribution&lt;/td>
&lt;td>results that do not apply to your traffic&lt;/td>
&lt;td>use realistic traces&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>Measuring the mean instead of percentiles&lt;/td>
&lt;td>hides the latency tail&lt;/td>
&lt;td>report P95/P99&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>Uncontrolled warm-up&lt;/td>
&lt;td>the prefix cache inflates the first results&lt;/td>
&lt;td>discard the warm-up&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>Not pinning engine/model version&lt;/td>
&lt;td>irreproducible&lt;/td>
&lt;td>pin everything and publish it&lt;/td>
&lt;/tr>
&lt;/tbody>
&lt;/table>
&lt;p>The methodological conclusion of this article: &lt;strong>a benchmark without a published methodology
is not comparable&lt;/strong>. For a performance figure to support a proposal it has to come with the
tool, its version, the model and precision, the load distribution and the SLO. Synthesis
article S4 builds a reproducible harness that pins all of that.&lt;/p>
&lt;hr>
&lt;h2 id="checklist-for-a-reproducible-benchmark">Checklist for a reproducible benchmark&lt;/h2>
&lt;p>For a performance figure to be defensible before a committee, it has to come with everything
that allows it to be reproduced. The minimum published alongside the result:&lt;/p>
&lt;table>
&lt;thead>
&lt;tr>
&lt;th>What to pin&lt;/th>
&lt;th>Why&lt;/th>
&lt;/tr>
&lt;/thead>
&lt;tbody>
&lt;tr>
&lt;td>Tool + version&lt;/td>
&lt;td>each one measures differently; the version changes behaviour&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>Model + precision (FP16/FP8/INT4)&lt;/td>
&lt;td>precision changes throughput and quality&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>Hardware (GPU, count, interconnect)&lt;/td>
&lt;td>an 8×H100 NVLink is not an 8×H100 PCIe&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>Engine + version + flags&lt;/td>
&lt;td>vLLM/SGLang/TRT-LLM and their configuration&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>Length distribution (in/out)&lt;/td>
&lt;td>real traffic is not fixed length&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>Concurrency levels of the sweep&lt;/td>
&lt;td>you have to go past the knee&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>SLO (which percentile, which threshold)&lt;/td>
&lt;td>it defines the goodput&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>Warm-up handling&lt;/td>
&lt;td>discard it or it biases the result&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>Tokenizer used for counting&lt;/td>
&lt;td>it affects tok/s and cost per token&lt;/td>
&lt;/tr>
&lt;/tbody>
&lt;/table>
&lt;p>The practical rule: if you cannot hand over this table alongside the figure, the figure is
not a datum, it is an anecdote. The reproducible harness of article S4 automates the recording
of all these parameters so that anyone, including whoever challenges the proposal, can
reproduce the exact number.&lt;/p>
&lt;hr>
&lt;h2 id="performance--quality">Performance ≠ quality&lt;/h2>
&lt;p>One warning that avoids the most expensive mistake: these tools measure &lt;strong>speed and
throughput, not accuracy&lt;/strong>. An engine can be blisteringly fast while serving a model that
answers badly. Quality is measured with another family of tools
(&lt;strong>lm-evaluation-harness&lt;/strong>, &lt;strong>HELM&lt;/strong>, task &lt;em>leaderboards&lt;/em>) which is another axis of the
scorecard (article B7). Confusing &amp;ldquo;fast&amp;rdquo; with &amp;ldquo;good&amp;rdquo; is how you build a platform that serves
bad answers very quickly. On the final Pareto frontier, performance and quality are two
distinct axes that must be looked at together, never one instead of the other.&lt;/p>
&lt;p>The other family of tools, for reference (expanded in B7):&lt;/p>
&lt;table>
&lt;thead>
&lt;tr>
&lt;th>Tool&lt;/th>
&lt;th>What it measures&lt;/th>
&lt;th>Common trap&lt;/th>
&lt;/tr>
&lt;/thead>
&lt;tbody>
&lt;tr>
&lt;td>&lt;strong>lm-evaluation-harness&lt;/strong>&lt;/td>
&lt;td>accuracy on hundreds of standardised tasks&lt;/td>
&lt;td>contamination of the test dataset&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>&lt;strong>HELM&lt;/strong>&lt;/td>
&lt;td>holistic evaluation (accuracy, robustness, bias, efficiency)&lt;/td>
&lt;td>heavy to run&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>&lt;strong>LiveBench / dynamic leaderboards&lt;/strong>&lt;/td>
&lt;td>tasks that rotate to avoid contamination&lt;/td>
&lt;td>comparability over time&lt;/td>
&lt;/tr>
&lt;/tbody>
&lt;/table>
&lt;p>The data point: contamination, the model having seen the test during training, inflates
quality metrics just as warm-up inflates performance ones. That is why dynamic leaderboards
rotate their questions. Quality and performance share that lesson: the measurement method
biases the result as much as the system being measured.&lt;/p>
&lt;hr>
&lt;h2 id="the-latency-versus-throughput-trade-off">The latency versus throughput trade-off&lt;/h2>
&lt;p>A property the sweep reveals and which is worth keeping in mind: &lt;strong>latency and throughput
pull in opposite directions&lt;/strong>. Batching groups requests to amortise the cost of moving the
weights out of VRAM, which raises throughput, but each request waits for the batch to form,
which raises individual latency. There are two operating regimes, and the benchmark
serves to place you in the right one:&lt;/p>
&lt;table>
&lt;thead>
&lt;tr>
&lt;th>Regime&lt;/th>
&lt;th>Optimises&lt;/th>
&lt;th>Configuration&lt;/th>
&lt;th>Case&lt;/th>
&lt;/tr>
&lt;/thead>
&lt;tbody>
&lt;tr>
&lt;td>&lt;strong>Latency&lt;/strong>&lt;/td>
&lt;td>low TTFT/TPOT&lt;/td>
&lt;td>small batch, low concurrency&lt;/td>
&lt;td>interactive chat, copilots&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>&lt;strong>Throughput&lt;/strong>&lt;/td>
&lt;td>maximum tok/s&lt;/td>
&lt;td>large batch, high concurrency&lt;/td>
&lt;td>overnight batch, ingestion&lt;/td>
&lt;/tr>
&lt;/tbody>
&lt;/table>
&lt;p>There is no single &amp;ldquo;best&amp;rdquo; point: there is the best point for your SLO. A benchmark that
reports only maximum throughput is describing the throughput regime and ignoring whether that
point meets the latency your case needs. That is why &lt;strong>goodput&lt;/strong>, throughput under the SLO,
is the metric that reconciles the two regimes: it measures how much throughput you get
without stepping outside acceptable latency. The sweep traverses the curve between both
regimes; your SLO marks where on that curve your system sits.&lt;/p>
&lt;hr>
&lt;h2 id="the-connection-with-cost-and-energy">The connection with cost and energy&lt;/h2>
&lt;p>Performance is not an isolated axis: by the identity of the &lt;a href="https://blog.lo0.es/en/posts/cost-performance-energy-three-axes-llm-inference/">opening article&lt;/a>,
throughput is the denominator of cost per token and of energy per token. A sweep that finds a
knee at 4,000 tok/s rather than 2,800 is not just &amp;ldquo;faster&amp;rdquo;: it lowers the CPM from ~1.09 to
~0.76 €/1M tok and the energy per token in the same proportion, on the same iron. That is
why performance benchmarking is the tool that, indirectly, moves cost the most: every goodput
improvement translates into euros and into watts per token. The number that connects the
three axes is &lt;strong>goodput&lt;/strong>, the throughput that meets the SLO, not catalogue throughput.&lt;/p>
&lt;hr>
&lt;h2 id="state-of-the-art-2026">State of the art 2026&lt;/h2>
&lt;ul>
&lt;li>&lt;strong>genai-perf → AIPerf migration&lt;/strong> (15 Apr 2026): NVIDIA consolidates its benchmarking into
a multi-process tool with saturation detection.&lt;/li>
&lt;li>&lt;strong>GuideLLM&lt;/strong> as the OSS standard for SLO-driven evaluation with reproducible sweeps.&lt;/li>
&lt;li>&lt;strong>MLPerf Inference v5.0/v5.1&lt;/strong> extends to 405B, interactive 70B and MoE, with record
participation: cross-vendor comparability is maturing.&lt;/li>
&lt;li>&lt;strong>Measurement bias characterised&lt;/strong>: the community accepts that the measurement method
matters as much as the system measured; the emphasis on reproducibility and meta-metrics is
growing.&lt;/li>
&lt;/ul>
&lt;hr>
&lt;h2 id="limits-and-traps-data-driven">Limits and traps (data-driven)&lt;/h2>
&lt;ol>
&lt;li>&lt;strong>Comparing tools from different classes.&lt;/strong> A single-process micro-bench and a
multi-process load generator are not comparable; the difference can be 7×. Pin the class.&lt;/li>
&lt;li>&lt;strong>Catalogue throughput instead of goodput.&lt;/strong> The honest number is the one that meets the
SLO.&lt;/li>
&lt;li>&lt;strong>Means instead of percentiles.&lt;/strong> The mean hides the tail; report P95/P99.&lt;/li>
&lt;li>&lt;strong>Not extending the sweep beyond the knee.&lt;/strong> Without seeing where latency takes off you do
not know the safe capacity.&lt;/li>
&lt;li>&lt;strong>Confusing performance with quality.&lt;/strong> They are distinct axes; fast is not good.&lt;/li>
&lt;li>&lt;strong>Not pinning versions.&lt;/strong> Engine, model, precision and load left unpinned = irreproducible
= not defensible.&lt;/li>
&lt;/ol>
&lt;p>The next article in the track (B2) goes into the tool catalogue in depth; this one pins the
metrics and the methodology. With performance measured reproducibly, the scorecard can cross
it with cost (in €) and energy for the final decision.&lt;/p>
&lt;h2 id="closing">Closing&lt;/h2>
&lt;p>Performance benchmarking looks like the most &amp;ldquo;objective&amp;rdquo; of the three axes (they are tokens
per second, after all) and it is exactly where the most manipulation happens, almost always
without bad intent: a single-process tool here, a mean instead of a P99 there, a catalogue
throughput instead of the goodput. The difference between a marketing number and a defensible
datum lies not in the engine measured but in the &lt;strong>methodology&lt;/strong>: the class of tool, where
the clock is measured, with which tokenizer, how far the sweep goes and which SLO defines the
goodput. For a sovereign architecture proposal, performance only counts if it is delivered
with that reproducibility record, and, crossed with cost in euros and energy per token, it
becomes the column of the Pareto frontier that decides which engine and which configuration
hold up the platform. The number you defend is not the highest one: it is the &lt;strong>reproducible
goodput&lt;/strong>.&lt;/p>
&lt;h2 id="see-also">See also&lt;/h2>
&lt;ul>
&lt;li>&lt;a href="https://blog.lo0.es/en/posts/llm-serving-engines-pareto-frontier-comparison/">LLM serving engines compared (vLLM/SGLang/TRT-LLM/Dynamo)&lt;/a> — choosing the engine after measuring goodput: from the methodology of this article to the Pareto frontier.&lt;/li>
&lt;li>&lt;a href="https://blog.lo0.es/en/posts/mlperf-inference-reading-loadgen-comparability/">MLPerf Inference: how to read it and what comparability it offers&lt;/a> — the industry standard: the four scenarios, the TTFT/TPOT SLOs of the Server scenario and the rules for comparing results.&lt;/li>
&lt;li>&lt;a href="https://blog.lo0.es/en/posts/measurement-bias-reproducibility-llm-benchmarks/">Measurement bias and reproducibility&lt;/a> — sources of systematic bias in performance benchmarks and how to control them to obtain defensible data.&lt;/li>
&lt;/ul>
&lt;h2 id="sources">Sources&lt;/h2>
&lt;ul>
&lt;li>Anyscale · métricas de latencia y throughput de LLM — &lt;a href="https://docs.anyscale.com/llm/serving/benchmarking/metrics">https://docs.anyscale.com/llm/serving/benchmarking/metrics&lt;/a>&lt;/li>
&lt;li>Red Hat · GuideLLM: evaluar despliegues LLM para inferencia real — &lt;a href="https://developers.redhat.com/articles/2025/06/20/guidellm-evaluate-llm-deployments-real-world-inference">https://developers.redhat.com/articles/2025/06/20/guidellm-evaluate-llm-deployments-real-world-inference&lt;/a>&lt;/li>
&lt;li>GuideLLM · GitHub (proyecto vLLM) — &lt;a href="https://github.com/vllm-project/guidellm">https://github.com/vllm-project/guidellm&lt;/a>&lt;/li>
&lt;li>NVIDIA AIPerf · guía de benchmarking (ex genai-perf) — &lt;a href="https://lucaberton.com/blog/nvidia-aiperf-llm-inference-benchmarking-guide/">https://lucaberton.com/blog/nvidia-aiperf-llm-inference-benchmarking-guide/&lt;/a>&lt;/li>
&lt;li>MLCommons · MLPerf Inference datacenter (escenarios) — &lt;a href="https://mlcommons.org/benchmarks/inference-datacenter/">https://mlcommons.org/benchmarks/inference-datacenter/&lt;/a>&lt;/li>
&lt;li>MLCommons · MLPerf Inference v5.0 (405B + 70B interactivo) — &lt;a href="https://mlcommons.org/2025/04/llm-inference-v5/">https://mlcommons.org/2025/04/llm-inference-v5/&lt;/a>&lt;/li>
&lt;li>MLCommons · MLPerf Inference v5.1 — &lt;a href="https://mlcommons.org/2025/09/mlperf-inference-v5-1-results/">https://mlcommons.org/2025/09/mlperf-inference-v5-1-results/&lt;/a>&lt;/li>
&lt;li>arXiv 2605.24217 · sesgo sistemático de medición en benchmarks de inferencia LLM — &lt;a href="https://arxiv.org/html/2605.24217">https://arxiv.org/html/2605.24217&lt;/a>&lt;/li>
&lt;li>arXiv 2508.10251 · meta-métricas y buenas prácticas de benchmarking de rendimiento — &lt;a href="https://arxiv.org/pdf/2508.10251">https://arxiv.org/pdf/2508.10251&lt;/a>&lt;/li>
&lt;li>Medium · LLM Inference Benchmarking (genAI-perf y vLLM) — &lt;a href="https://kchandan.medium.com/llm-inference-benchmarking-genai-perf-and-vllm-5dd06b57428e">https://kchandan.medium.com/llm-inference-benchmarking-genai-perf-and-vllm-5dd06b57428e&lt;/a>&lt;/li>
&lt;/ul></description></item></channel></rss>