<?xml version="1.0" encoding="utf-8" standalone="yes"?><rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom"><channel><title>Mlperf on lo0 — Tech Blog</title><link>https://blog.lo0.es/en/tags/mlperf/</link><description>Recent content in Mlperf on lo0 — Tech Blog</description><generator>Hugo -- gohugo.io</generator><language>en</language><lastBuildDate>Sat, 29 Aug 2026 09:00:00 +0200</lastBuildDate><atom:link href="https://blog.lo0.es/en/tags/mlperf/index.xml" rel="self" type="application/rss+xml"/><item><title>MLPerf Inference: how to read it, what LoadGen enforces and what comparability it offers</title><link>https://blog.lo0.es/en/posts/mlperf-inference-reading-loadgen-comparability/</link><pubDate>Sat, 29 Aug 2026 09:00:00 +0200</pubDate><guid>https://blog.lo0.es/en/posts/mlperf-inference-reading-loadgen-comparability/</guid><description>&lt;blockquote>
&lt;p>Notation: amounts in &lt;strong>euros (N €)&lt;/strong>, decimals with a point. The dollar sign is not used
(on this site it is a formula delimiter).&lt;/p>
&lt;/blockquote>
&lt;h2 id="tldr">TL;DR&lt;/h2>
&lt;p>MLPerf Inference is maintained by MLCommons on a six-monthly cadence. It imposes three things no home-made benchmark has: a &lt;strong>mandatory load generator&lt;/strong> (LoadGen, compiled from a tagged revision and unmodified), model-equivalence rules that bound what can be optimised, and an &lt;strong>audit process&lt;/strong> that reviews up to two submissions per round. The four scenarios (SingleStream, MultiStream, Server, Offline) have a minimum duration of 600 s and different metrics: Server reports the sustained Poisson rate and is the only one with a latency constraint. For the LLM benchmarks that constraint is not a request latency but two simultaneous SLOs at the 99th percentile: TTFT and TPOT, with per-model figures ranging from 2000 ms / 200 ms on Llama 2 70B to 450 ms / 40 ms on its Interactive variant. Quality is required relative to the reference model (99 % or 99.9 %) and the &lt;strong>generation length&lt;/strong> is audited as well (Llama 2 70B: more than 90 % of 294.45 tokens per sample). Three reading rules sum up the real comparability: results are only compared within the same version, division, category and scenario; per-accelerator is not an official MLCommons metric, it is derived; and neither cost nor energy enters the primary metric, to the point that round v5.1 had 2 power submissions against 27 participating organisations.&lt;/p>
&lt;hr>
&lt;h2 id="what-it-is-and-how-it-is-organised">What it is and how it is organised&lt;/h2>
&lt;p>MLPerf Inference is maintained by the &lt;strong>MLCommons Association&lt;/strong> through the MLPerf Inference Working Group, whose participation is limited to MLCommons members and affiliates (&lt;a href="https://mlcommons.org/working-groups/benchmarks/inference/">MLPerf Inference Working Group&lt;/a>). The declared cadence for publishing results is roughly six months.&lt;/p>
&lt;table>
&lt;thead>
&lt;tr>
&lt;th>Round&lt;/th>
&lt;th>Results published&lt;/th>
&lt;/tr>
&lt;/thead>
&lt;tbody>
&lt;tr>
&lt;td>v4.0&lt;/td>
&lt;td>27 March 2024&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>v4.1&lt;/td>
&lt;td>28 August 2024&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>v5.0&lt;/td>
&lt;td>2 April 2025&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>v5.1&lt;/td>
&lt;td>9 September 2025&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>v6.0&lt;/td>
&lt;td>1 April 2026&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>v6.1&lt;/td>
&lt;td>in preparation as of this data sheet&lt;/td>
&lt;/tr>
&lt;/tbody>
&lt;/table>
&lt;p>The suite splits into two system categories: &lt;strong>Datacenter&lt;/strong> and &lt;strong>Edge&lt;/strong>, the latter defined in the rules as everything that is not datacenter (&lt;a href="https://github.com/mlcommons/inference_policies/blob/master/inference_rules.adoc">inference_rules.adoc&lt;/a>). A Datacenter system has two requirements that do not apply in Edge:&lt;/p>
&lt;ul>
&lt;li>&lt;strong>Mandatory ECC&lt;/strong> on DRAM and HBM, active during all performance and accuracy runs. There is no requirement on SRAM.&lt;/li>
&lt;li>&lt;strong>Mandatory networking&lt;/strong> since round v3.0, with a minimum bandwidth computed from the achieved throughput. For Llama 3.1 405B the minimum ingress is &lt;code>throughput × 20000 × dtype_size&lt;/code> bytes/s; for Llama 2 70B the minimum egress is &lt;code>throughput × 1024 × dtype_size&lt;/code>.&lt;/li>
&lt;/ul>
&lt;p>Edge additionally allows &lt;strong>inferred results&lt;/strong>: a MultiStream derived from SingleStream counts as 8 times the p99 latency; an Offline derived from MultiStream, 8000 divided by the mean latency in milliseconds.&lt;/p>
&lt;h2 id="the-four-scenarios">The four scenarios&lt;/h2>
&lt;p>This is the normative table, transcribed from the rules:&lt;/p>
&lt;table>
&lt;thead>
&lt;tr>
&lt;th>Scenario&lt;/th>
&lt;th>Query generation&lt;/th>
&lt;th>Duration&lt;/th>
&lt;th>Samples/query&lt;/th>
&lt;th>Latency constraint&lt;/th>
&lt;th>Tail latency&lt;/th>
&lt;th>Reported metric&lt;/th>
&lt;/tr>
&lt;/thead>
&lt;tbody>
&lt;tr>
&lt;td>SingleStream&lt;/td>
&lt;td>The next query is issued when the SUT completes the previous one&lt;/td>
&lt;td>600 s&lt;/td>
&lt;td>1&lt;/td>
&lt;td>None&lt;/td>
&lt;td>90 %&lt;/td>
&lt;td>p90 latency with early stopping&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>Server / Interactive&lt;/td>
&lt;td>LoadGen issues queries following a &lt;strong>Poisson distribution&lt;/strong>&lt;/td>
&lt;td>600 s&lt;/td>
&lt;td>1&lt;/td>
&lt;td>Benchmark-specific&lt;/td>
&lt;td>99 %&lt;/td>
&lt;td>Maximum Poisson parameter supported&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>Offline&lt;/td>
&lt;td>All samples are delivered at the start in &lt;strong>a single query&lt;/strong>&lt;/td>
&lt;td>1 query, 600 s&lt;/td>
&lt;td>At least 24,576&lt;/td>
&lt;td>None&lt;/td>
&lt;td>N/A&lt;/td>
&lt;td>Measured throughput&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>MultiStream&lt;/td>
&lt;td>The next query is issued when the SUT completes the previous one&lt;/td>
&lt;td>600 s&lt;/td>
&lt;td>&lt;strong>8&lt;/strong>&lt;/td>
&lt;td>None&lt;/td>
&lt;td>99 %&lt;/td>
&lt;td>p99 query latency with early stopping&lt;/td>
&lt;/tr>
&lt;/tbody>
&lt;/table>
&lt;p>Four operational clarifications that change how each number is interpreted:&lt;/p>
&lt;ul>
&lt;li>In &lt;strong>Server&lt;/strong>, LoadGen does not measure a latency: it runs a binary search over the target rate. For a given value it generates queries at that QPS with Poisson arrivals; if the run fails the latency gate, it lowers the value and repeats. The final published metric is, per the official FAQ, &lt;strong>scheduled samples per second&lt;/strong>, not completed samples nor the target entered by the user.&lt;/li>
&lt;li>In &lt;strong>MultiStream&lt;/strong>, the latency of a query is the &lt;strong>maximum&lt;/strong> of the latencies of its samples, and the 8 samples of each query are contiguous in the order in which they were loaded. The value &lt;code>multi_stream_samples_per_query = 8&lt;/code> is fixed in the official configuration.&lt;/li>
&lt;li>In &lt;strong>Offline&lt;/strong> there is no latency constraint at all. The rules themselves forbid techniques that only work in fixed-length experiments &lt;em>except in the Offline scenario&lt;/em>, which is a normative acknowledgement that an Offline result says nothing about behaviour under an SLO.&lt;/li>
&lt;li>The percentiles in the table are the &lt;strong>lower theoretical bound&lt;/strong> for runs with very many queries. With early stopping, the percentile actually computed is somewhat above the nominal one, as a penalty for processing few queries.&lt;/li>
&lt;/ul>
&lt;p>The mandatory scenarios depend on the benchmark. In Datacenter, the summarisation, question answering, text generation and VLM tasks require &lt;code>(Server | Interactive), Offline&lt;/code>, that is, the submitter chooses between Server and Interactive. Reasoning and recommendation require &lt;code>Server, Offline&lt;/code>. Medical segmentation, node classification, speech-to-text and RAG require only &lt;code>Offline&lt;/code>.&lt;/p>
&lt;h2 id="loadgen-what-it-measures-and-what-it-does-not">LoadGen: what it measures and what it does not&lt;/h2>
&lt;p>LoadGen is the module that generates the load and computes the metrics. Its use is mandatory for all submissions, and it must be compiled from a tagged and approved revision of the repository, unaltered; the README is explicit that local modifications of the C++ library are not accepted.&lt;/p>
&lt;p>Its four declared responsibilities are generating the queries according to the scenario, tracking the latency of each query, validating the accuracy of the results and computing the final metrics. Latency is defined as the time from when LoadGen was scheduled to pass a query to the system under test until it receives the response.&lt;/p>
&lt;p>Equally important is what it declares &lt;strong>out of scope&lt;/strong>: LoadGen does not know the model, does not know the data formats, cannot score accuracy and does not know the scenario constraints of the MLPerf rules. The consequence is written in its own README: being model-agnostic, it cannot enforce the MLPerf requirements, for example the target percentiles and latencies. Those limits are injected from &lt;code>mlperf.conf&lt;/code> and &lt;code>user.conf&lt;/code>. The justification for making it mandatory appears in the founding ISCA 2020 paper: it establishes a clear boundary between the components that belong to the submitter and those that belong to MLPerf, and it measures the performance of the complete system instead of that of a single piece.&lt;/p>
&lt;h3 id="duration-query-count-and-early-stopping">Duration, query count and early stopping&lt;/h3>
&lt;p>The minimum duration is 600,000 ms in all four scenarios. The minimum number of queries comes from a confidence interval table that the rules publish:&lt;/p>
&lt;table>
&lt;thead>
&lt;tr>
&lt;th>Tail percentile&lt;/th>
&lt;th>Confidence&lt;/th>
&lt;th>Margin of error&lt;/th>
&lt;th>Inferences&lt;/th>
&lt;th>Rounded&lt;/th>
&lt;/tr>
&lt;/thead>
&lt;tbody>
&lt;tr>
&lt;td>90 %&lt;/td>
&lt;td>99 %&lt;/td>
&lt;td>0.50 %&lt;/td>
&lt;td>23,886&lt;/td>
&lt;td>24,576&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>95 %&lt;/td>
&lt;td>99 %&lt;/td>
&lt;td>0.25 %&lt;/td>
&lt;td>50,425&lt;/td>
&lt;td>57,344&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>97 %&lt;/td>
&lt;td>99 %&lt;/td>
&lt;td>0.15 %&lt;/td>
&lt;td>85,811&lt;/td>
&lt;td>90,112&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>99 %&lt;/td>
&lt;td>99 %&lt;/td>
&lt;td>0.05 %&lt;/td>
&lt;td>262,742&lt;/td>
&lt;td>270,336&lt;/td>
&lt;/tr>
&lt;/tbody>
&lt;/table>
&lt;p>The submission checker applies &lt;code>Server: 270336&lt;/code>, &lt;code>SingleStream: 1024&lt;/code> and &lt;code>MultiStream: 662&lt;/code>. For Offline, the minimum is set per benchmark: 24,576 on Llama 2 70B, 15,000 on Mixtral 8x7B, 13,368 on Llama 3.1 8B, 8,313 on Llama 3.1 405B, 4,388 on DeepSeek-R1, 1,633 on Whisper and 43 on 3D UNet.&lt;/p>
&lt;p>The &lt;strong>early stopping&lt;/strong> mechanism allows runs to be shortened while keeping the statistical guarantee. With tolerance &lt;code>d = 0&lt;/code> and confidence &lt;code>c = 0.99&lt;/code>, the algorithm resolves by binary search the smallest number of queries below the latency threshold that satisfies the criterion, given the number of queries observed above it.&lt;/p>
&lt;p>LoadGen&amp;rsquo;s &lt;strong>modes&lt;/strong> are &lt;code>SubmissionRun&lt;/code> (accuracy followed by performance), &lt;code>AccuracyOnly&lt;/code>, &lt;code>PerformanceOnly&lt;/code> and &lt;code>FindPeakPerformance&lt;/code>. The last, applicable only to Server, takes &lt;code>target_qps&lt;/code> as a lower bound if it passes, estimates the upper bound at double and doubles it until failure, and then does a binary search. In performance mode LoadGen selects queries uniformly at random with replacement from a set of size QSL; in accuracy mode it uses a copy of the validation dataset, each sample exactly once. One accuracy run must be executed for every performance result submitted, and the same code in both modes.&lt;/p>
&lt;p>The &lt;strong>seeds&lt;/strong> are announced four weeks before the deadline and the mandatory generator is Mersenne Twister 19937. They change every round: in v5.1, &lt;code>qsl_rng_seed = 1780908523862526354&lt;/code>; in v5.0 it was &lt;code>6023615788873153749&lt;/code>.&lt;/p>
&lt;h3 id="the-compliance-tests">The compliance tests&lt;/h3>
&lt;p>They are activated by placing an &lt;code>audit.config&lt;/code> file in the working directory, whose parameters &lt;strong>override&lt;/strong> those of &lt;code>mlperf.conf&lt;/code> and &lt;code>user.conf&lt;/code>. Their logs are mandatory in the submission package. The declared purpose is to detect anomalies, not to diagnose their cause.&lt;/p>
&lt;p>For the LLM benchmarks the required test is &lt;strong>TEST06&lt;/strong>, specifically against the end-of-sequence token &lt;em>exploit&lt;/em>. It runs 100 samples and demands three conditions: that the first token reported separately matches the first token of the model&amp;rsquo;s output, that the output ends with zero or a single EOS token, and that the number of tokens reported matches the real length of the generated sequence.&lt;/p>
&lt;h2 id="divisions-categories-and-what-can-be-touched">Divisions, categories and what can be touched&lt;/h2>
&lt;h3 id="the-three-divisions">The three divisions&lt;/h3>
&lt;ul>
&lt;li>&lt;strong>Closed&lt;/strong>: requires preprocessing, postprocessing and model equivalent to the reference implementation. It allows calibration for quantisation and &lt;strong>does not allow retraining&lt;/strong>. It is the only one that can use the MLPerf name unqualified.&lt;/li>
&lt;li>&lt;strong>Open&lt;/strong>: allows arbitrary preprocessing, postprocessing and model, including retraining. The accuracy, latency and scenario constraints &lt;strong>do not apply&lt;/strong>: instead, the accuracy obtained and the latency constraints under which the performance was obtained must be reported. The model may have any origin, be quantised in any way and be sparsified in any way. It must use the same validation dataset as the corresponding Closed and use all of it.&lt;/li>
&lt;li>&lt;strong>Network&lt;/strong>: inherits all the Closed requirements, applies only to Datacenter, and separates the LoadGen node from the system under test through a &lt;em>fabric&lt;/em>. The submitter&amp;rsquo;s dispatch library cannot preprocess, postprocess, &lt;strong>batch&lt;/strong>, pad or &lt;strong>cache&lt;/strong>. Bus interconnects are forbidden by name (PCIe, CXL, CCIX, HyperTransport, NVLink, QPI, UPI, ICI) and only Ethernet, IEEE 802.11, InfiniBand and 3GPP are admitted, with a requirement to work chassis to chassis at more than ten metres.&lt;/li>
&lt;/ul>
&lt;h3 id="the-availability-categories">The availability categories&lt;/h3>
&lt;table>
&lt;thead>
&lt;tr>
&lt;th>Category&lt;/th>
&lt;th>Hardware&lt;/th>
&lt;th>Software&lt;/th>
&lt;/tr>
&lt;/thead>
&lt;tbody>
&lt;tr>
&lt;td>Available in cloud&lt;/td>
&lt;td>Available for cloud rental&lt;/td>
&lt;td>Available&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>Available on premise&lt;/td>
&lt;td>Available for purchase&lt;/td>
&lt;td>Available&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>Preview&lt;/td>
&lt;td>Must be available for the next submission, or the one after 140 days, whichever is longer&lt;/td>
&lt;td>Available except what is needed for substantially new hardware&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>RDI&lt;/td>
&lt;td>Does not meet the above&lt;/td>
&lt;td>Does not meet the above&lt;/td>
&lt;/tr>
&lt;/tbody>
&lt;/table>
&lt;p>Available demands four cumulative conditions: price available, having been rented or shipped to at least one third party, public evidence of availability and reasonable availability to additional third parties on the submission date. A Preview result obliges a resubmission as Available with equal or better performance, tolerating up to &lt;strong>2 % degradation&lt;/strong> from noise; if there is no resubmission, the Preview result is &lt;strong>marked invalid&lt;/strong>. RDI components cannot be submitted as Available until the cycle after the next one, or 221 days, whichever is longer.&lt;/p>
&lt;h3 id="what-closed-allows-and-forbids">What Closed allows and forbids&lt;/h3>
&lt;p>MLPerf delivers the weights in fp16 or fp32. The central rule is that the submitter may perform purely mathematical and reproducible quantisation, using only the calibration data and the tensors of the delivered model, to any numerical format that reaches the required quality, and that the method must be described publicly at a level that allows it to be reproduced. The test against back doors is elegant: the description of the quantisation method must be &lt;em>much smaller&lt;/em> than the non-zero weights it produces.&lt;/p>
&lt;p>&lt;strong>Allowed&lt;/strong>, among other things: any framework or runtime, arbitrary data layout, varying the matrix multiplication algorithm, mathematically equivalent transformations, polynomial approximations of transcendentals, processing queries out of order within what the scenario admits, replacing dense operations with mathematically equivalent sparse operations, hand-picking different precisions per operation, fusing and unfusing, dynamic batching, and mixtures of experts combining weights with different quantisation.&lt;/p>
&lt;p>&lt;strong>Forbidden&lt;/strong>: replacing or supplementing weights wholesale, discarding non-zero weights, including pruning, caching queries or responses, coalescing identical queries, modifying weights during the timed portion, quantisation algorithms of size comparable to the weights they produce, hardcoding the total number of queries, using knowledge of the LoadGen implementation to predict peaks or troughs in the Server scenario, changing the number of search beams, and incorporating statistics from the performance or accuracy sets.&lt;/p>
&lt;p>Two clarifications that matter to anyone serving LLMs in production: the &lt;strong>KV cache&lt;/strong> is allowed just as in the reference model provided it is not applied &lt;strong>across queries&lt;/strong>; PagedAttention is admitted if the block is reused only within the batch; continuous or dynamic batching is allowed; and KV cache entries are treated as activations for quantisation purposes, and cannot be pruned. &lt;strong>Speculative decoding&lt;/strong> is only admitted in the benchmark and scenario combinations explicitly listed, with the reference head at the same precision, and implementations that artificially manipulate the acceptance rate are forbidden.&lt;/p>
&lt;h2 id="the-slos-almost-nobody-cites-ttft-and-tpot">The SLOs almost nobody cites: TTFT and TPOT&lt;/h2>
&lt;p>This is the point where an MLPerf table is most often misread. In the LLM benchmarks of the Server scenario, the constraint is not a request latency: the rules set &lt;code>target_latency = 0&lt;/code> and activate &lt;code>use_token_latencies = 1&lt;/code>, so that the gate is formed by &lt;strong>two simultaneous metrics&lt;/strong>, both evaluated at the &lt;strong>99th percentile&lt;/strong>:&lt;/p>
&lt;ul>
&lt;li>&lt;strong>TTFT&lt;/strong> (&lt;em>time to first token&lt;/em>): latency of the first token.&lt;/li>
&lt;li>&lt;strong>TPOT&lt;/strong> (&lt;em>time per output token&lt;/em>): mean interval between all generated tokens.&lt;/li>
&lt;/ul>
&lt;p>The figures are in &lt;code>mlperf.conf&lt;/code> and change per round and per benchmark:&lt;/p>
&lt;table>
&lt;thead>
&lt;tr>
&lt;th>Benchmark (Server scenario)&lt;/th>
&lt;th>TTFT&lt;/th>
&lt;th>TPOT&lt;/th>
&lt;th>Round&lt;/th>
&lt;/tr>
&lt;/thead>
&lt;tbody>
&lt;tr>
&lt;td>&lt;code>llama2-70b&lt;/code>&lt;/td>
&lt;td>2000 ms&lt;/td>
&lt;td>200 ms&lt;/td>
&lt;td>v5.0, v5.1, v6.x&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>&lt;code>llama2-70b-interactive&lt;/code>&lt;/td>
&lt;td>&lt;strong>450 ms&lt;/strong>&lt;/td>
&lt;td>&lt;strong>40 ms&lt;/strong>&lt;/td>
&lt;td>v5.1, v6.x&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>&lt;code>mixtral-8x7b&lt;/code>&lt;/td>
&lt;td>2000 ms&lt;/td>
&lt;td>200 ms&lt;/td>
&lt;td>v5.0, v5.1, v6.x&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>&lt;code>llama3_1-405b&lt;/code>&lt;/td>
&lt;td>6000 ms&lt;/td>
&lt;td>175 ms&lt;/td>
&lt;td>v5.0, v5.1, v6.x&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>&lt;code>llama3_1-405b-interactive&lt;/code>&lt;/td>
&lt;td>4500 ms&lt;/td>
&lt;td>80 ms&lt;/td>
&lt;td>v6.x&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>&lt;code>llama3_1-8b&lt;/code>&lt;/td>
&lt;td>2000 ms&lt;/td>
&lt;td>100 ms&lt;/td>
&lt;td>v5.1, v6.x&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>&lt;code>llama3_1-8b-interactive&lt;/code>&lt;/td>
&lt;td>500 ms&lt;/td>
&lt;td>30 ms&lt;/td>
&lt;td>v6.x&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>&lt;code>deepseek-r1&lt;/code>&lt;/td>
&lt;td>2000 ms&lt;/td>
&lt;td>80 ms&lt;/td>
&lt;td>v5.1, v6.x&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>&lt;code>deepseek-r1-interactive&lt;/code>&lt;/td>
&lt;td>1500 ms&lt;/td>
&lt;td>15 ms&lt;/td>
&lt;td>v6.x&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>&lt;code>gpt-oss-120b&lt;/code>&lt;/td>
&lt;td>3000 ms&lt;/td>
&lt;td>80 ms&lt;/td>
&lt;td>v6.x&lt;/td>
&lt;/tr>
&lt;/tbody>
&lt;/table>
&lt;p>A TPOT of 40 ms is equivalent to 25 tokens per second per user, which is the order of magnitude of a conversational interface; one of 200 ms is equivalent to 5 tokens per second, which is a batch use case dressed up as a service. Two results for the same model under the Server and Interactive labels are not the same measurement. Two documented discrepancies between primary sources remain: the &lt;code>llama2-70b-interactive&lt;/code> variant does not appear in the &lt;code>mlperf.conf&lt;/code> tagged as v5.0 despite having been announced in that round, and in the v6.x cycle the TPOT of &lt;code>gpt-oss-120b-interactive&lt;/code> appears as 15 ms in &lt;code>mlperf.conf&lt;/code> and as 20 ms in the text of the rules.&lt;/p>
&lt;p>For contrast, the constraints of the non-LLM benchmarks in the Server scenario are conventional request latencies: ResNet-50 15 ms, RetinaNet 100 ms, BERT 130 ms, DLRMv2 60 ms, DLRMv3 80 ms, RNN-T 1000 ms, GPT-J and Stable Diffusion XL 20,000 ms.&lt;/p>
&lt;h2 id="quality-the-99--and-the-token-count">Quality: the 99 % and the token count&lt;/h2>
&lt;p>The quality targets are expressed relative to the reference model, never in absolute terms, and each benchmark demands one variant, the other or both. In the current Datacenter suite, 3D UNet, Llama 3.1 8B and Whisper ask for 99 % and 99.9 % of FP32, which generates two distinct results; Llama 2 70B asks only for 99.9 %; Llama 3.1 405B, Mixtral, DeepSeek-R1, GPT-OSS-120B, Qwen3-VL, RGAT and WAN-2.2 ask only for 99 %; and E2E-RAG is the exception at 97 %. In the checker this materialises as separate benchmarks (&lt;code>bert-99&lt;/code> and &lt;code>bert-99.9&lt;/code>), each with its computed numerical threshold.&lt;/p>
&lt;p>On top of that metric there is a second LLM-specific constraint that is cited very rarely: the generation length is audited as well.&lt;/p>
&lt;table>
&lt;thead>
&lt;tr>
&lt;th>Benchmark&lt;/th>
&lt;th>Length constraint&lt;/th>
&lt;/tr>
&lt;/thead>
&lt;tbody>
&lt;tr>
&lt;td>Llama 2 70B&lt;/td>
&lt;td>tokens per sample &amp;gt; 90 % of 294.45&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>Llama 3.1 405B&lt;/td>
&lt;td>tokens per sample between 90 % and 110 % of 684.68&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>Mixtral 8x7B&lt;/td>
&lt;td>tokens per sample between 90 % and 110 % of 144.84&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>Llama 3.1 8B&lt;/td>
&lt;td>total generated length &amp;gt; 90 % of 8,167,644&lt;/td>
&lt;/tr>
&lt;/tbody>
&lt;/table>
&lt;p>The FAQ settles the obvious shortcut: reducing the maximum output length below the reference is not allowed, and truncating tokens to improve performance or reach the accuracy target &lt;strong>is not permitted&lt;/strong>. The inference parameters are fixed in Closed (&lt;code>max_new_tokens = 1024&lt;/code> on Llama 2 70B, &lt;code>20000&lt;/code> on Llama 3.1 405B and DeepSeek-R1), as is the decoding algorithm: greedy search in the Llama family, Mixtral and DeepSeek-R1; sampling with &lt;code>temperature = 1.0&lt;/code> and &lt;code>top_p = 1.0&lt;/code> in GPT-OSS-120B and Qwen3-VL. Accuracy is reported to &lt;strong>five significant figures&lt;/strong> with round-half-to-even.&lt;/p>
&lt;h2 id="the-current-datacenter-suite">The current Datacenter suite&lt;/h2>
&lt;table>
&lt;thead>
&lt;tr>
&lt;th>Area&lt;/th>
&lt;th>Task&lt;/th>
&lt;th>Model&lt;/th>
&lt;th>Dataset&lt;/th>
&lt;th>QSL&lt;/th>
&lt;th>Required quality&lt;/th>
&lt;/tr>
&lt;/thead>
&lt;tbody>
&lt;tr>
&lt;td>Vision&lt;/td>
&lt;td>Medical segmentation&lt;/td>
&lt;td>3D UNet&lt;/td>
&lt;td>KiTS 2019&lt;/td>
&lt;td>42&lt;/td>
&lt;td>99 % and 99.9 % of FP32 (DICE 0.86330)&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>Language&lt;/td>
&lt;td>Summarisation&lt;/td>
&lt;td>Llama 3.1 8B&lt;/td>
&lt;td>CNN DailyMail v3.0.0&lt;/td>
&lt;td>13,368&lt;/td>
&lt;td>99 % and 99.9 % of FP32&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>Language&lt;/td>
&lt;td>Question answering&lt;/td>
&lt;td>Llama 2 70B&lt;/td>
&lt;td>OpenOrca&lt;/td>
&lt;td>24,576&lt;/td>
&lt;td>99.9 % of FP32&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>Language&lt;/td>
&lt;td>Text generation&lt;/td>
&lt;td>Llama 3.1 405B&lt;/td>
&lt;td>LongBench, Ruler, GovReport&lt;/td>
&lt;td>8,313&lt;/td>
&lt;td>99 % of FP16&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>Language&lt;/td>
&lt;td>QA, maths and code&lt;/td>
&lt;td>Mixtral 8x7B&lt;/td>
&lt;td>OpenOrca, GSM8K, MBXP&lt;/td>
&lt;td>15,000&lt;/td>
&lt;td>99 % of FP16&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>Language&lt;/td>
&lt;td>Reasoning&lt;/td>
&lt;td>DeepSeek-R1&lt;/td>
&lt;td>&lt;code>mlperf_deepseek_r1&lt;/code>&lt;/td>
&lt;td>4,388&lt;/td>
&lt;td>99 % of FP16 (exact match 81.9132 %)&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>Language&lt;/td>
&lt;td>QA, maths and code&lt;/td>
&lt;td>GPT-OSS-120B&lt;/td>
&lt;td>AIME25, GPQA Diamond, LiveCodeBench v6&lt;/td>
&lt;td>6,396&lt;/td>
&lt;td>99 % of 83.13 %&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>Vision&lt;/td>
&lt;td>Vision-language model&lt;/td>
&lt;td>Qwen3-VL-235B-A22B&lt;/td>
&lt;td>Shopify product catalogue&lt;/td>
&lt;td>48,289&lt;/td>
&lt;td>99 % of BF16 (hierarchical F1 0.7824)&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>Commerce&lt;/td>
&lt;td>Recommendation&lt;/td>
&lt;td>DLRMv3&lt;/td>
&lt;td>Synthetic Streaming 100B&lt;/td>
&lt;td>34,996&lt;/td>
&lt;td>99.9 % of FP32&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>Generative&lt;/td>
&lt;td>Text to video&lt;/td>
&lt;td>WAN-2.2-T2V-A14B&lt;/td>
&lt;td>VBench prompts&lt;/td>
&lt;td>248&lt;/td>
&lt;td>99 % of BF16 (VBench 69.7752)&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>Graphs&lt;/td>
&lt;td>Node classification&lt;/td>
&lt;td>RGAT&lt;/td>
&lt;td>IGBH&lt;/td>
&lt;td>788,379&lt;/td>
&lt;td>99 % of FP32 (72.86 %)&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>Audio&lt;/td>
&lt;td>Speech to text&lt;/td>
&lt;td>Whisper&lt;/td>
&lt;td>LibriSpeech&lt;/td>
&lt;td>1,633&lt;/td>
&lt;td>99 % and 99.9 % of FP32 (WER 2.0671 %)&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>Language&lt;/td>
&lt;td>End-to-end RAG&lt;/td>
&lt;td>E2E-RAG&lt;/td>
&lt;td>FRAMES&lt;/td>
&lt;td>824&lt;/td>
&lt;td>97 % of FP32&lt;/td>
&lt;/tr>
&lt;/tbody>
&lt;/table>
&lt;p>The &lt;strong>E2E-RAG&lt;/strong> benchmark deserves attention from anyone designing a RAG platform, because it fixes the whole pipeline: a FAISS HNSW index with mandatory parameters &lt;code>M = 32&lt;/code>, &lt;code>efConstruction = 200&lt;/code> and &lt;code>efSearch = 100&lt;/code>; e5-base-v2 embeddings; ColBERTv2 reranking; generation and query rewriting with GPT-OSS-120B; evaluation with Llama 3.1 8B as judge; and a maximum of 5 retrieval iterations, mandatory for all submitters. Model loading is not timed; building the vector database is.&lt;/p>
&lt;p>The definition of &lt;em>sample&lt;/em> changes per model and is a frequent source of confusion: in the LLMs it is a sequence, in DLRMv3 it is a request with a user history and &lt;strong>2048 candidates&lt;/strong>, in WAN-2.2 it is a pair of positive and negative prompts, and in PointPainting it is five images and a lidar point cloud.&lt;/p>
&lt;h2 id="how-to-read-a-results-row">How to read a results row&lt;/h2>
&lt;p>The columns of the official table in the Closed division are &lt;em>Submitter, Software, System, Benchmark Results, Processor/Count, Details, Accelerator/Count&lt;/em> and &lt;em>Code&lt;/em>. The Open division adds &lt;em>Model Used&lt;/em> and &lt;em>Notes&lt;/em>. Rows with power measurement add &lt;em>System Power&lt;/em> in Server and Offline, or &lt;em>Energy Per Stream&lt;/em> in the stream scenarios. Any citation must use the identifier in the format &lt;code>major-version.minor-version.entry.benchmark&lt;/code>, of the style &lt;code>5.1-0053&lt;/code>, and carry a footnote with suite, version, division, benchmark, scenario, date and source.&lt;/p>
&lt;h3 id="per-accelerator-is-not-an-official-metric">Per-accelerator is not an official metric&lt;/h3>
&lt;p>&lt;strong>There is no per-accelerator column.&lt;/strong> It is a derived metric, and the MLCommons messaging guidelines are blunt: any comparison based on a different or derived metric, such as power, cost, model size or accuracy, must make the basis of comparison clear in the text and in a footnote, and secondary and derived metrics cannot be presented as official or verified MLPerf metrics. NVIDIA itself states this with the standard formula in its round blogs: per-GPU performance is not a primary MLPerf Inference metric and is computed by dividing the reported throughput by the number of reported accelerators. The same applies to combining results from several benchmarks: MLCommons allows it but &lt;strong>does not endorse it&lt;/strong>, and the composite cannot be presented as an official result.&lt;/p>
&lt;h3 id="the-comparison-rules">The comparison rules&lt;/h3>
&lt;ul>
&lt;li>MLPerf results can only be compared &lt;strong>with compatible MLPerf results&lt;/strong>: same benchmark, same scenario and compatible versions per the normative compatibility table.&lt;/li>
&lt;li>MLPerf results cannot be compared with non-MLPerf results.&lt;/li>
&lt;li>When comparing, any difference in version, division, category, verification status, scenario or number of chips must be clearly identified. When comparing Open with Closed, it must be identified in what sense the Open result would not qualify as Closed.&lt;/li>
&lt;li>Submitters cannot publish results of a version before its official date; non-submitters must wait &lt;strong>two weeks&lt;/strong> from that date.&lt;/li>
&lt;li>The sanctions regime goes as far as barring an infringer from submitting results in the future and marking their results as non-compliant permanently in the database, with a deadline of &lt;strong>three working days&lt;/strong> to withdraw the infringing content.&lt;/li>
&lt;/ul>
&lt;h3 id="how-much-audit-sits-behind-a-row">How much audit sits behind a row&lt;/h3>
&lt;p>In each round up to two submissions are audited: one chosen at random from all of them and zero or one chosen by the review committee. And there is a constraint that conditions the reading of the whole table: only Available submissions in the Closed division are auditable. Preview, RDI and Open fall outside the process. There is an exemption from the random audit if the system is equivalent to another already audited and neither the aggregate performance nor the per-accelerator performance differ by more than 10 % from those of the previous audit. The deadlines are 28 days to select an auditor, 30 for the report after signing the confidentiality agreements and around 90 days for the complete process, with &lt;strong>two days&lt;/strong> of hardware access for the auditor. The rules close with three sentences that define the standard of proof: results that cannot be replicated are not valid results, benchmark detection is not allowed and input-based optimisation is not allowed either.&lt;/p>
&lt;h2 id="the-recent-rounds-in-figures">The recent rounds, in figures&lt;/h2>
&lt;table>
&lt;thead>
&lt;tr>
&lt;th>Round&lt;/th>
&lt;th>Organisations&lt;/th>
&lt;th>Performance results&lt;/th>
&lt;th>Power results&lt;/th>
&lt;th>New benchmarks&lt;/th>
&lt;/tr>
&lt;/thead>
&lt;tbody>
&lt;tr>
&lt;td>v4.0&lt;/td>
&lt;td>23&lt;/td>
&lt;td>more than 8,500&lt;/td>
&lt;td>900&lt;/td>
&lt;td>2&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>v4.1&lt;/td>
&lt;td>22&lt;/td>
&lt;td>964&lt;/td>
&lt;td>31&lt;/td>
&lt;td>1&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>v5.0&lt;/td>
&lt;td>23&lt;/td>
&lt;td>17,457&lt;/td>
&lt;td>n/a&lt;/td>
&lt;td>4&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>v5.1&lt;/td>
&lt;td>27&lt;/td>
&lt;td>n/a&lt;/td>
&lt;td>2&lt;/td>
&lt;td>3&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>v6.0&lt;/td>
&lt;td>24&lt;/td>
&lt;td>n/a&lt;/td>
&lt;td>n/a&lt;/td>
&lt;td>5 of 11 Datacenter tests&lt;/td>
&lt;/tr>
&lt;/tbody>
&lt;/table>
&lt;p>These counts do not form a time series: the counting criteria differ between press releases and none documents the criterion, to the point that v4.0 and v4.1 differ by a factor close to nine.&lt;/p>
&lt;p>The published performance jumps are comparable within each announcement. Between v4.0 and v5.0, twelve months apart, the number of Llama 2 70B submissions multiplied by 2.5, the &lt;strong>median&lt;/strong> score doubled and the best score was &lt;strong>3.3 times faster&lt;/strong>. Between v5.0 and v5.1, in six months, the best systems improved &lt;strong>by up to 50 %&lt;/strong> in some scenarios. Round v6.0 changed axis and its press release talks about scale rather than speed: 30 % more multi-node submissions, 10 % of the systems with more than ten nodes against 2 % in the previous round, and a system larger than 72 nodes and 288 accelerators, quadrupling the previous maximum node count.&lt;/p>
&lt;p>Hardware that debuted in each round: MI300X, TPU v6e, Xeon Granite Rapids and B200 in v4.1; MI325X, Xeon 6980P, GB200 and Jetson AGX Thor in v5.0; MI355X, Intel Arc Pro B60, GB300 and RTX Pro 6000 Blackwell Server Edition in v5.1.&lt;/p>
&lt;h2 id="what-mlperf-inference-does-not-measure">What MLPerf Inference does not measure&lt;/h2>
&lt;ul>
&lt;li>&lt;strong>Cost.&lt;/strong> There is no cost or TCO metric, and cost is explicitly classified as a derived metric that cannot be presented as official.&lt;/li>
&lt;li>&lt;strong>Energy, except as an optional extension.&lt;/strong> MLPerf Power is regulated in a separate document, requires measuring at the wall with the power group&amp;rsquo;s workflow and PTDaemon, and forbids any other method. Its real adoption is marginal: 2 power submissions in v5.1 against 27 participating organisations. In addition, MLCommons bounds what that figure means: the measured power is only valid for the benchmark it accompanies, and any other reference to power, such as a TDP setting or a power supply rating, is neither measured nor validated by MLCommons. The messaging rule forbids submitters from publishing per-watt normalised comparisons using any proxy other than the measured power.&lt;/li>
&lt;li>Configurations not optimised by the vendor. The bias is acknowledged in the audit rule itself, which contemplates submissions whose performance is not consistent with the known characteristics of the hardware, or where the committee lacks visibility into how it was achieved, or where hardware and software are not reasonably available to the public.&lt;/li>
&lt;li>&lt;strong>Barrier to entry.&lt;/strong> Only MLCommons members and &lt;em>test partners&lt;/em> can submit results for review. Anyone measuring with the same code without submitting must label every figure as &lt;strong>unverified&lt;/strong> with the corresponding legend.&lt;/li>
&lt;/ul>
&lt;h2 id="usage-flow-for-an-on-premise-platform">Usage flow for an on-premise platform&lt;/h2>
&lt;p>MLPerf Inference is useful in a purchasing decision if it is used for what it is, a test bench with written rules, and not as a ranking.&lt;/p>
&lt;ol>
&lt;li>Filter by division and category before looking at any number: keep Closed and Available. A Preview or RDI result describes hardware that cannot yet be bought, and neither of the two is auditable.&lt;/li>
&lt;li>Choose the scenario that matches the load pattern: Server or Interactive if there are users waiting, Offline if the work is batch. An Offline result does not authorise promising a latency.&lt;/li>
&lt;li>Read the SLO before the throughput: in any LLM benchmark, the number of queries per second only means something alongside its TTFT/TPOT pair. The same machine publishes very different figures under Server and under Interactive.&lt;/li>
&lt;li>Check the quality variant: 99 % and 99.9 % are different results for the same model, and the second usually costs performance.&lt;/li>
&lt;li>Translate to your own configuration carefully: dividing by the number of accelerators is a derived metric, not an official one, and it has to be declared as such.&lt;/li>
&lt;li>Close the gap MLPerf leaves open with your own measurement: cost, energy and behaviour under real traffic. That is exactly the function of the reproducible harness and of the track B bench tools, and the reason no platform decision stands on a public table alone.&lt;/li>
&lt;/ol>
&lt;h2 id="cross-links-from-the-benchmarking-track">Cross-links from the benchmarking track&lt;/h2>
&lt;ul>
&lt;li>&lt;a href="https://blog.lo0.es/en/posts/llm-inference-benchmarking-frameworks-metrics-state-of-the-art/">B1/B2 — LLM inference benchmarking: frameworks, metrics and state of the art&lt;/a>: the TTFT, TPOT and goodput metrics outside the MLPerf corset, and which tool measures each one.&lt;/li>
&lt;li>&lt;a href="https://blog.lo0.es/en/posts/llm-benchmark-tool-catalogue-practical-deep-dive/">B2 — LLM benchmark tool catalogue&lt;/a>: the tools with which you reproduce at home what MLPerf standardises.&lt;/li>
&lt;li>&lt;a href="https://blog.lo0.es/en/posts/guidellm-validating-slo-under-load-sizing-knee/">B3 — GuideLLM and SLO validation under load&lt;/a>: the practical equivalent of LoadGen&amp;rsquo;s binary search in the Server scenario.&lt;/li>
&lt;li>&lt;a href="https://blog.lo0.es/en/posts/measurement-bias-reproducibility-llm-benchmarks/">B6 — Measurement bias and reproducibility&lt;/a>: why LoadGen exists, told from the side of what happens when there is none.&lt;/li>
&lt;li>&lt;a href="https://blog.lo0.es/en/posts/llm-serving-engines-pareto-frontier-comparison/">B8 — Serving engines compared on the Pareto frontier&lt;/a>: the comparison MLPerf does not make, with engines instead of systems.&lt;/li>
&lt;li>&lt;a href="https://blog.lo0.es/en/posts/mlperf-power-energy-efficiency-benchmark/">C4 — MLPerf Power&lt;/a>: the energy extension of this same machinery, with its certified measurement at the wall.&lt;/li>
&lt;/ul>
&lt;hr>
&lt;h2 id="see-also">See also&lt;/h2>
&lt;ul>
&lt;li>&lt;a href="https://blog.lo0.es/en/posts/reproducible-harness-cost-performance-energy-experiment/">The reproducible harness: cost, performance and energy in a single auditable experiment&lt;/a> — how to build at home the auditable equivalent of a submission, measuring on top of that what MLPerf leaves out.&lt;/li>
&lt;li>&lt;a href="https://blog.lo0.es/en/posts/slo-to-number-of-gpus-sizing-justifying-inference-hardware/">Sizing and justifying the GPU investment&lt;/a> — the step from a throughput figure under an SLO to the number of accelerators and the return.&lt;/li>
&lt;/ul>
&lt;h2 id="sources">Sources&lt;/h2>
&lt;ul>
&lt;li>MLCommons, &lt;em>MLPerf Inference Working Group&lt;/em> — &lt;a href="https://mlcommons.org/working-groups/benchmarks/inference/">https://mlcommons.org/working-groups/benchmarks/inference/&lt;/a>&lt;/li>
&lt;li>MLCommons, &lt;em>MLPerf Inference Rules (inference_rules.adoc)&lt;/em> — &lt;a href="https://github.com/mlcommons/inference_policies/blob/master/inference_rules.adoc">https://github.com/mlcommons/inference_policies/blob/master/inference_rules.adoc&lt;/a>&lt;/li>
&lt;li>MLCommons, &lt;em>MLPerf Inference Power Measurement (power_measurement.adoc)&lt;/em> — &lt;a href="https://github.com/mlcommons/inference_policies/blob/master/power_measurement.adoc">https://github.com/mlcommons/inference_policies/blob/master/power_measurement.adoc&lt;/a>&lt;/li>
&lt;li>MLCommons, &lt;em>MLPerf Submission Rules (submission_rules.adoc)&lt;/em> — &lt;a href="https://github.com/mlcommons/policies/blob/master/submission_rules.adoc">https://github.com/mlcommons/policies/blob/master/submission_rules.adoc&lt;/a>&lt;/li>
&lt;li>MLCommons, &lt;em>MLPerf Results Messaging Guidelines&lt;/em> — &lt;a href="https://github.com/mlcommons/policies/blob/master/MLPerf_Results_Messaging_Guidelines.adoc">https://github.com/mlcommons/policies/blob/master/MLPerf_Results_Messaging_Guidelines.adoc&lt;/a>&lt;/li>
&lt;li>MLCommons, &lt;em>LoadGen README&lt;/em> — &lt;a href="https://github.com/mlcommons/inference/blob/master/loadgen/README.md">https://github.com/mlcommons/inference/blob/master/loadgen/README.md&lt;/a>&lt;/li>
&lt;li>MLCommons, &lt;em>loadgen/test_settings.h&lt;/em> — &lt;a href="https://github.com/mlcommons/inference/blob/master/loadgen/test_settings.h">https://github.com/mlcommons/inference/blob/master/loadgen/test_settings.h&lt;/a>&lt;/li>
&lt;li>MLCommons, &lt;em>loadgen/mlperf.conf (master)&lt;/em> — &lt;a href="https://github.com/mlcommons/inference/blob/master/loadgen/mlperf.conf">https://github.com/mlcommons/inference/blob/master/loadgen/mlperf.conf&lt;/a>&lt;/li>
&lt;li>MLCommons, &lt;em>loadgen/mlperf.conf (tag v5.1)&lt;/em> — &lt;a href="https://github.com/mlcommons/inference/blob/v5.1/loadgen/mlperf.conf">https://github.com/mlcommons/inference/blob/v5.1/loadgen/mlperf.conf&lt;/a>&lt;/li>
&lt;li>MLCommons, &lt;em>loadgen/mlperf.conf (tag v5.0)&lt;/em> — &lt;a href="https://github.com/mlcommons/inference/blob/v5.0/loadgen/mlperf.conf">https://github.com/mlcommons/inference/blob/v5.0/loadgen/mlperf.conf&lt;/a>&lt;/li>
&lt;li>MLCommons, &lt;em>tools/submission/submission_checker.py (tag v5.0)&lt;/em> — &lt;a href="https://github.com/mlcommons/inference/blob/v5.0/tools/submission/submission_checker.py">https://github.com/mlcommons/inference/blob/v5.0/tools/submission/submission_checker.py&lt;/a>&lt;/li>
&lt;li>MLCommons, &lt;em>compliance/nvidia TEST06 README (tag v5.1)&lt;/em> — &lt;a href="https://github.com/mlcommons/inference/blob/v5.1/compliance/nvidia/TEST06/README.md">https://github.com/mlcommons/inference/blob/v5.1/compliance/nvidia/TEST06/README.md&lt;/a>&lt;/li>
&lt;li>MLCommons, &lt;em>Benchmark results: Inference Datacenter&lt;/em> — &lt;a href="https://mlcommons.org/benchmarks/inference-datacenter/">https://mlcommons.org/benchmarks/inference-datacenter/&lt;/a>&lt;/li>
&lt;li>Reddi et al., &lt;em>MLPerf Inference Benchmark&lt;/em>, ISCA 2020 — &lt;a href="https://arxiv.org/abs/1911.02549">https://arxiv.org/abs/1911.02549&lt;/a>&lt;/li>
&lt;li>MLCommons, &lt;em>MLPerf Inference v5.0 Benchmark Results&lt;/em> (abril 2025) — &lt;a href="https://www.businesswire.com/news/home/20250402313932/en/MLCommons-Releases-New-MLPerf-Inference-v5.0-Benchmark-Results">https://www.businesswire.com/news/home/20250402313932/en/MLCommons-Releases-New-MLPerf-Inference-v5.0-Benchmark-Results&lt;/a>&lt;/li>
&lt;li>MLCommons, &lt;em>MLPerf Inference v5.1 Benchmark Results&lt;/em> (septiembre 2025) — &lt;a href="https://www.globenewswire.com/news-release/2025/09/09/3147136/0/en/MLCommons-Releases-New-MLPerf-Inference-v5-1-Benchmark-Results.html">https://www.globenewswire.com/news-release/2025/09/09/3147136/0/en/MLCommons-Releases-New-MLPerf-Inference-v5-1-Benchmark-Results.html&lt;/a>&lt;/li>
&lt;li>MLCommons, &lt;em>MLPerf Inference v6.0 Benchmark Results&lt;/em> (abril 2026) — &lt;a href="https://www.globenewswire.com/news-release/2026/04/01/3266801/0/en/MLCommons-Releases-New-MLPerf-Inference-v6-0-Benchmark-Results.html">https://www.globenewswire.com/news-release/2026/04/01/3266801/0/en/MLCommons-Releases-New-MLPerf-Inference-v6-0-Benchmark-Results.html&lt;/a>&lt;/li>
&lt;li>MLCommons, &lt;em>MLPerf Inference v4.1 Benchmark Results&lt;/em> (agosto 2024) — &lt;a href="https://www.businesswire.com/news/home/20240828886616/en/New-MLPerf-Inference-v4.1-Benchmark-Results-Highlight-Rapid-Hardware-and-Software-Innovations-in-Generative-AI-Systems">https://www.businesswire.com/news/home/20240828886616/en/New-MLPerf-Inference-v4.1-Benchmark-Results-Highlight-Rapid-Hardware-and-Software-Innovations-in-Generative-AI-Systems&lt;/a>&lt;/li>
&lt;li>NVIDIA Developer Blog, &lt;em>NVIDIA Blackwell delivers massive performance leaps in MLPerf Inference v5.0&lt;/em> — &lt;a href="https://developer.nvidia.com/blog/nvidia-blackwell-delivers-massive-performance-leaps-in-mlperf-inference-v5-0/">https://developer.nvidia.com/blog/nvidia-blackwell-delivers-massive-performance-leaps-in-mlperf-inference-v5-0/&lt;/a>&lt;/li>
&lt;li>NVIDIA Developer Blog, &lt;em>NVIDIA Blackwell Ultra sets new inference records in MLPerf debut&lt;/em> — &lt;a href="https://developer.nvidia.com/blog/nvidia-blackwell-ultra-sets-new-inference-records-in-mlperf-debut/">https://developer.nvidia.com/blog/nvidia-blackwell-ultra-sets-new-inference-records-in-mlperf-debut/&lt;/a>&lt;/li>
&lt;li>MLCommons, &lt;em>MLPerf Automotive&lt;/em> (octubre 2025) — &lt;a href="https://arxiv.org/html/2510.27065v1">https://arxiv.org/html/2510.27065v1&lt;/a>&lt;/li>
&lt;li>Tschand et al., &lt;em>MLPerf Power: Benchmarking the Energy Efficiency of Machine Learning Systems from Microwatts to Megawatts&lt;/em> — &lt;a href="https://arxiv.org/abs/2410.12032">https://arxiv.org/abs/2410.12032&lt;/a>&lt;/li>
&lt;/ul></description></item><item><title>Storage in the AI era (2/4): performance</title><link>https://blog.lo0.es/en/posts/ai-era-storage-performance/</link><pubDate>Mon, 22 Jun 2026 11:00:00 +0200</pubDate><guid>https://blog.lo0.es/en/posts/ai-era-storage-performance/</guid><description>&lt;p>In the &lt;a href="https://blog.lo0.es/en/posts/ai-era-storage-state-of-the-art/">first article&lt;/a> we drew the map: the memory-storage hierarchy, the media technologies and the software stack that dominates GPU clusters. This second article goes into the property that justifies all that complexity: performance. The question an architect has to answer is not &amp;ldquo;how many GB/s does my array deliver?&amp;rdquo;, but &amp;ldquo;can I get my GPUs working at 95 % instead of 40 %?&amp;rdquo;. That difference is, literally, half the compute bill.&lt;/p>
&lt;h2 id="the-metric-that-really-matters-accelerator-utilisation">The metric that really matters: accelerator utilisation&lt;/h2>
&lt;p>It is tempting to measure AI storage with the classic metrics, sequential &lt;em>throughput&lt;/em>, random IOPS, latency, and we do need them. But none of them captures what the business pays for. The integrating metric is &lt;strong>accelerator utilisation&lt;/strong>: what fraction of the time the GPU spends computing rather than waiting for data.&lt;/p>
&lt;p>Most real ML &lt;em>pipelines&lt;/em> run at between 30 % and 60 % GPU utilisation. Industry surveys are damning: only around 7 % of AI/ML teams manage to exceed 85 % at peak, and poor forecasting or weak autoscaling can leave GPUs idle 70-85 % of the time. A &lt;em>job&lt;/em> running at 30 % utilisation is paying for 70 % of its compute capacity and getting nothing for it.&lt;/p>
&lt;p>We can formalise this. If \( t_{\text{cmp}} \) is the compute time per &lt;em>batch&lt;/em> and \( t_{\text{io}} \) the time the GPU spends blocked waiting for I/O that fails to overlap with compute, effective utilisation is:&lt;/p>
$$U = \frac{t_{\text{cmp}}}{t_{\text{cmp}} + t_{\text{io}}}$$
&lt;p>The goal of all AI storage engineering is to drive \( t_{\text{io}} \) towards zero, whether by increasing bandwidth, reducing latency or overlapping I/O with compute through &lt;em>prefetching&lt;/em> and &lt;em>pipelining&lt;/em>. And there is an aggravating factor: the faster the GPU, the harder it is to feed. Moving from V100 to H100 cut the per-&lt;em>batch&lt;/em> compute time of the 3D U-Net model by 76 %, turning a bandwidth-sensitive workload into a latency-sensitive one. Every accelerator generation raises the bar for storage.&lt;/p>
&lt;p>This change of regime, from bandwidth-limited to latency-limited, has a design consequence worth internalising. When a workload is bandwidth-limited, the fix is to add transfer capacity: more NVMe, more &lt;em>lanes&lt;/em>, more nodes. When it becomes latency-limited, that recipe stops working; what matters then is response time per operation, which is attacked with cache, with intelligent &lt;em>prefetching&lt;/em> and with data paths that remove hops (GPUDirect, for instance). Diagnosing correctly which of the two regimes each workload sits in is the first task of any optimisation: throwing bandwidth at a latency-limited workload is spending money without moving utilisation.&lt;/p>
&lt;h2 id="mlperf-storage-the-benchmark-that-does-measure-the-right-thing">MLPerf Storage: the benchmark that does measure the right thing&lt;/h2>
&lt;p>For years there was no neutral way to compare AI storage platforms. MLPerf Storage, from MLCommons, has provided one. Its design insight is not to measure GB/s in the abstract but to simulate the accelerators&amp;rsquo; &lt;em>think time&lt;/em> so as to generate a realistic I/O pattern, and to require those simulated accelerators to sustain a minimum utilisation level. In other words, it measures exactly what matters: how many accelerators a platform can keep fed without their utilisation dropping below the threshold.&lt;/p>
&lt;p>&lt;strong>v1.0&lt;/strong> (September 2024) used the 3D U-Net, ResNet-50 and CosmoFlow models, simulating A100 and H100, with utilisation thresholds of 90 % for the first two and 70 % for CosmoFlow. &lt;strong>v2.0&lt;/strong> (August 2025) marked a leap: more than 200 results from 26 organisations, including Alluxio, DDN, Hammerspace, HPE, IBM, KIOXIA, Micron, Oracle, Samsung and WDC, systems able to sustain roughly twice as many accelerators as in v1.0, and, above all, a new &lt;em>checkpointing&lt;/em> workload (which we will come back to).&lt;/p>
&lt;p>Some v2.0 results help calibrate the state of the art:&lt;/p>
&lt;table>
&lt;thead>
&lt;tr>
&lt;th>System&lt;/th>
&lt;th>Throughput&lt;/th>
&lt;th>Accelerators&lt;/th>
&lt;th>GPU utilisation&lt;/th>
&lt;/tr>
&lt;/thead>
&lt;tbody>
&lt;tr>
&lt;td>Volumez&lt;/td>
&lt;td>1.079 TB/s&lt;/td>
&lt;td>—&lt;/td>
&lt;td>92.21 %&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>Hammerspace (5 nodes)&lt;/td>
&lt;td>420.8 GB/s&lt;/td>
&lt;td>140&lt;/td>
&lt;td>&amp;gt;94.7 %&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>Hammerspace (3 nodes)&lt;/td>
&lt;td>253.1 GB/s&lt;/td>
&lt;td>84&lt;/td>
&lt;td>&amp;gt;94.7 %&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>Hammerspace (1 node)&lt;/td>
&lt;td>85.6 GB/s&lt;/td>
&lt;td>28&lt;/td>
&lt;td>&amp;gt;94.7 %&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>Alluxio&lt;/td>
&lt;td>24.14 GiB/s&lt;/td>
&lt;td>128&lt;/td>
&lt;td>99.57 %&lt;/td>
&lt;/tr>
&lt;/tbody>
&lt;/table>
&lt;p>The Hammerspace figure illustrates a property an architect should demand: &lt;strong>linear scaling&lt;/strong>. From 1 to 5 nodes &lt;em>throughput&lt;/em> grows from 85.6 to 420.8 GB/s while keeping utilisation above 94.7 % with a minute coefficient of variation (0.08-0.14 %). Linear scaling is what separates an AI platform from a fast NAS.&lt;/p>
&lt;h2 id="the-numbers-from-commercial-platforms">The numbers from commercial platforms&lt;/h2>
&lt;p>Outside the benchmark, platforms certified for DGX SuperPOD publish aggregate figures worth keeping as an order-of-magnitude reference:&lt;/p>
&lt;ul>
&lt;li>&lt;strong>WEKA WEKApod Nitro&lt;/strong>: 720 GB/s read and 186 GB/s write per configuration, 18 million IOPS, with ConnectX-8 NICs at 800 Gb/s. Per node, 70 GB/s read and 40 GB/s write; the minimum 8-node configuration delivers 560 GB/s read. A single entry-level system covers the I/O demand of a 1,152-GPU GB200 &lt;em>scalable unit&lt;/em>.&lt;/li>
&lt;li>&lt;strong>DDN AI400X2&lt;/strong>: more than 90 GB/s and 3 million IOPS per &lt;em>appliance&lt;/em>; the Turbo variant saturates close to 100 GB/s (800 Gbps) per DGX B200. DDN&amp;rsquo;s new platform claims more than 1 TB/s read per &lt;em>appliance&lt;/em> with per-rack scaling, and has fed NVIDIA&amp;rsquo;s Eos supercomputer at 4 TB/s.&lt;/li>
&lt;li>&lt;strong>VAST Data&lt;/strong>: multiple TB/s from a single &lt;em>mount point&lt;/em> via NFS &lt;em>multipathing&lt;/em>, RDMA over NFS and GPUDirect, managing exabytes in a single multiprotocol namespace.&lt;/li>
&lt;li>&lt;strong>Pure Storage&lt;/strong>: certified for DGX SuperPOD with GB200 and GB300.&lt;/li>
&lt;/ul>
&lt;p>These figures only mean something in relation to the cluster they feed. A GB200 NVL72 moves 130 TB/s of internal GPU-to-GPU communication; storage does not compete with that, but it does have to sustain &lt;em>data loading&lt;/em> and &lt;em>checkpointing&lt;/em> without strangling 72 GPUs that behave as one.&lt;/p>
&lt;h2 id="the-io-pattern-of-training-and-gpudirect-storage">The I/O pattern of training and GPUDirect Storage&lt;/h2>
&lt;p>Understanding the I/O pattern is the key to neither over- nor under-sizing. Training combines several patterns: large sequential reads of the dataset, small random reads during &lt;em>shuffling&lt;/em> between epochs, and massive periodic writes during &lt;em>checkpointing&lt;/em>. Each stresses storage differently.&lt;/p>
&lt;p>The traditional data path goes through the CPU: from storage to a &lt;em>bounce buffer&lt;/em> in host memory and from there to GPU memory. Every byte of every &lt;em>checkpoint&lt;/em> and every weight load pays the latency and CPU &lt;em>overhead&lt;/em> of that double hop. &lt;strong>GPUDirect Storage (GDS)&lt;/strong> cuts it out: it enables direct DMA between GPU memory and NVMe through the cuFile user-space library and the nvidia-fs kernel module, which intercepts POSIX calls and redirects them through the DMA engine. The result is &lt;em>zero-copy&lt;/em>, with direct transfers exceeding 40 GB/s and, above all, without stealing cycles from the CPU.&lt;/p>
&lt;p>There is a nuance that naive deployments miss: &lt;strong>GDS is optimised for large, aligned, sequential transfers&lt;/strong>. For workloads dominated by small files or heavily random access, the traditional path with CPU caching can be more efficient. GDS is not a magic &amp;ldquo;more performance&amp;rdquo; switch; it is a tool for a specific pattern. Designing the &lt;em>data pipeline&lt;/em>, meaning dataset format, &lt;em>shard&lt;/em> size and number of &lt;em>DataLoader&lt;/em> &lt;em>workers&lt;/em>, matters as much as picking the array.&lt;/p>
&lt;p>The &lt;em>data pipeline&lt;/em> deserves a pause, because it is where performance leaks most quietly. Training does not read the dataset just any way: it walks it in &lt;em>batches&lt;/em>, shuffles it between epochs to avoid ordering bias, and often applies transformations (image decoding, tokenisation, &lt;em>augmentation&lt;/em>) on the CPU before handing the tensor to the GPU. Each of those steps can become the bottleneck. Too few &lt;em>DataLoader&lt;/em> &lt;em>workers&lt;/em> leaves the GPU waiting; a dataset format that forces millions of small files open sinks metadata performance; a badly designed &lt;em>shuffle&lt;/em> turns efficient sequential reads into costly random accesses. Common practice in serious workloads is to pack the dataset into large &lt;em>shards&lt;/em> (formats such as WebDataset, TFRecord or Parquet) precisely so that the read pattern is large and sequential, the terrain where GDS and parallel file systems perform best. The mental rule: the fastest storage in the world will not save a badly designed data &lt;em>pipeline&lt;/em>.&lt;/p>
&lt;h2 id="the-real-cost-of-checkpointing-when-there-is-training">The real cost of checkpointing (when there is training)&lt;/h2>
&lt;p>&lt;em>Checkpointing&lt;/em> is the most demanding write workload in the AI world, but it needs to be placed properly: it is a &lt;strong>training&lt;/strong> problem. A pure inference factory barely suffers from it, since its heavy writes are different ones, KV-cache, vector indexes, telemetry. We cover it here because many installations combine some &lt;em>fine-tuning&lt;/em> or occasional training with serving, and because it illustrates better than any other case how asynchrony beats brute force. If your platform is for inference, you can read this section as context and jump to the next one, which is the one that really concerns you.&lt;/p>
&lt;p>That said, most people&amp;rsquo;s intuition about &lt;em>checkpointing&lt;/em> is wrong on two counts: size and frequency.&lt;/p>
&lt;p>&lt;strong>Size.&lt;/strong> A &lt;em>checkpoint&lt;/em> is not just the weights. The rule of thumb is around 16 bytes per parameter, because the bulk is optimiser state. The figures from the MLPerf Storage v2.0 &lt;em>checkpointing&lt;/em> workload, based on Meta&amp;rsquo;s &lt;em>Llama 3 Herd&lt;/em>, make it plain:&lt;/p>
&lt;table>
&lt;thead>
&lt;tr>
&lt;th>Model&lt;/th>
&lt;th>Processes&lt;/th>
&lt;th>Checkpoint size&lt;/th>
&lt;th>Of which, optimiser state&lt;/th>
&lt;/tr>
&lt;/thead>
&lt;tbody>
&lt;tr>
&lt;td>8B&lt;/td>
&lt;td>8&lt;/td>
&lt;td>105 GB&lt;/td>
&lt;td>90 GB&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>70B&lt;/td>
&lt;td>64&lt;/td>
&lt;td>912 GB&lt;/td>
&lt;td>—&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>405B&lt;/td>
&lt;td>512&lt;/td>
&lt;td>5.29 TB&lt;/td>
&lt;td>—&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>1T&lt;/td>
&lt;td>1024&lt;/td>
&lt;td>15 TB&lt;/td>
&lt;td>13.2 TB&lt;/td>
&lt;/tr>
&lt;/tbody>
&lt;/table>
&lt;p>&lt;strong>Frequency.&lt;/strong> At scale, hardware failures are constant (we develop this in the &lt;a href="https://blog.lo0.es/en/posts/storage-ai-era-availability/">article on availability&lt;/a>). Meta&amp;rsquo;s model for a 16,000-accelerator cluster predicts a failure every few hours. To lose less than 5 % of progress between failures, you need on the order of 20 &lt;em>checkpoints&lt;/em> in that interval. That translates into aggressive cadences:&lt;/p>
&lt;ul>
&lt;li>16,000 accelerators: around 155 &lt;em>checkpoints&lt;/em> a day, one every 9.3 minutes.&lt;/li>
&lt;li>100,000 accelerators: around 967 &lt;em>checkpoints&lt;/em> a day, one every 1.5 minutes.&lt;/li>
&lt;/ul>
&lt;p>And here is where the bandwidth problem appears. A synchronous &lt;em>checkpoint&lt;/em> halts training while it writes. If \( S_{\text{ckpt}} \) is the &lt;em>checkpoint&lt;/em> size and \( t_{\text{win}} \) the acceptable time window for writing it while keeping &lt;em>overhead&lt;/em> below 5 %, the required bandwidth is:&lt;/p>
$$B_{\text{req}} = \frac{S_{\text{ckpt}}}{t_{\text{win}}}$$
&lt;p>For a 1T model on 100,000 accelerators, the window falls to around 4.4 seconds. Writing 15 TB in that time demands on the order of 3.6 TB/s across the cluster, or some 200 GB/s sustained over the whole &lt;em>job&lt;/em>. On &lt;em>checkpoints&lt;/em> alone, that scenario generates more than 14 PB of writes a day. This is not a marginal workload: it is one of the most demanding write workloads in existence.&lt;/p>
&lt;p>It is worth seeing the calculation laid out, because it reveals where the lever is. Take the 405B model, whose &lt;em>checkpoint&lt;/em> weighs 5.29 TB. If policy requires writing it while keeping &lt;em>overhead&lt;/em> below 5 % at a cadence where the acceptable window is, say, 30 seconds, the synchronous bandwidth needed is around 176 GB/s sustained, within reach of a high-end array, but only one dedicated to that task. If instead of 30 seconds the window contracts to 5 (a larger cluster, more frequent failures), the requirement jumps above 1 TB/s. Sensitivity to the window is brutal: the difference between a viable design and an impossible one lies in how time is managed, not in how many drives are bought. That is where asynchrony changes the rules.&lt;/p>
&lt;p>&lt;strong>The solution is not just more bandwidth; it is asynchrony.&lt;/strong> &lt;em>Asynchronous checkpointing&lt;/em> decouples the &lt;em>snapshot&lt;/em> (a fast GPU→CPU copy) from persistence (a background write, overlapped with compute). PyTorch&amp;rsquo;s &lt;em>Distributed Async Checkpointing&lt;/em> cuts effective &lt;em>checkpoint&lt;/em> time by 10 to 20 times; in one IBM example a 7B model brought its &lt;em>down time&lt;/em> down from 148.8 to 6.3 seconds, a 23.6-fold improvement. Techniques such as CheckFreq pipeline the &lt;em>snapshot&lt;/em> and persistence with training, allowing a &lt;em>checkpoint&lt;/em> every 14-19 iterations. This is why vendors like VAST argue the relevant metric is not GB/s but &lt;em>checkpoint overlap&lt;/em>: what fraction of the &lt;em>checkpoint&lt;/em> overlaps with compute. Around 10 % overlap is usually enough.&lt;/p>
&lt;p>The design lesson is clear: sizing training storage for the peak write rate of synchronous &lt;em>checkpointing&lt;/em> is expensive and often unnecessary if the software stack gets asynchrony right. But trusting asynchrony without measuring &lt;em>overlap&lt;/em> is playing roulette with weeks of compute.&lt;/p>
&lt;h2 id="performance-in-an-inference-factory">Performance in an inference factory&lt;/h2>
&lt;p>Here is the heart of the matter for anyone running an inference factory. In an inference factory storage is not measured in epochs or GPU-hours but in the experience the client perceives, and that experience condenses into two metrics: &lt;strong>TTFT&lt;/strong> (&lt;em>time to first token&lt;/em>, the time until the first token of the answer) and &lt;strong>inter-token latency&lt;/strong> (the rate at which the following tokens arrive). At fleet scale, the metric that pays the bills is &lt;strong>throughput in tokens per second&lt;/strong> per GPU, because it determines how many requests each accelerator serves. Storage touches all three, in ways a training-centric design does not anticipate.&lt;/p>
&lt;h3 id="prefill-decode-and-their-disaggregation">Prefill, decode and their disaggregation&lt;/h3>
&lt;p>Inference has two phases with opposite profiles. &lt;strong>Prefill&lt;/strong> processes the entire input &lt;em>prompt&lt;/em> at once, generates the key-value pairs of the KV-cache and is a compute-intensive phase; it dominates TTFT. &lt;strong>Decode&lt;/strong> generates tokens one by one, reusing that cache, is memory-bandwidth-intensive and dominates inter-token latency. Because their bottlenecks differ, the 2025-2026 trend is to &lt;strong>disaggregate them&lt;/strong>: run &lt;em>prefill&lt;/em> and &lt;em>decode&lt;/em> on separate GPU &lt;em>pools&lt;/em> and transfer the KV-cache between them. That transfer, which may travel over NVLink, over the network or through a shared storage layer, makes KV-cache movement a performance-critical operation. A factory that disaggregates prefill and decode needs a very high bandwidth, low latency KV-cache path between both &lt;em>pools&lt;/em>; storage (or the DPU that governs it) stops being a passive actor and enters the critical path of every request.&lt;/p>
&lt;h3 id="the-kv-cache-as-a-performance-layer">The KV-cache as a performance layer&lt;/h3>
&lt;p>The central problem is the size of the KV-cache with long contexts: it overflows HBM, which is scarce and expensive. The options are to recompute it on every request, ruinously expensive in compute and lethal for TTFT, or to offload it to a slower layer and reuse it. Two approaches compete here. &lt;em>Offload&lt;/em> to &lt;strong>CXL&lt;/strong> memory offers gains of more than 5 times against SSD or RDMA-based &lt;em>caching&lt;/em>, according to CXL supply chain vendors, and allows &lt;em>batches&lt;/em> up to 30 % larger while holding latency targets, which raises &lt;em>throughput&lt;/em> and GPU utilisation. &lt;em>Offload&lt;/em> to &lt;strong>NVMe&lt;/strong>, standardised by NVIDIA in 2026 with its ICMS platform on BlueField-4, is slower but much cheaper and more capacious, ideal for &lt;em>pod&lt;/em>-scale KV-cache.&lt;/p>
&lt;p>The big performance lever here is &lt;strong>KV-cache reuse&lt;/strong> (&lt;em>prefix caching&lt;/em>). Many requests share prefixes: the same &lt;em>system prompt&lt;/em>, the same context document, the same prior conversation. If the KV-cache of those prefixes is kept in a fast storage layer and reused instead of recomputed, &lt;em>prefill&lt;/em> shortens drastically and TTFT collapses. This turns a low-latency storage layer into a direct multiplier of the factory&amp;rsquo;s throughput: the more useful cache fits and the faster it is served, the less compute is wasted recalculating what has already been calculated. Sizing this layer well, in capacity, bandwidth, latency and eviction policy, is one of the most profitable performance decisions in the whole factory.&lt;/p>
&lt;h3 id="model-load-time-and-cold-start">Model load time and cold start&lt;/h3>
&lt;p>There is a storage cost specific to inference that training ignores: &lt;strong>starting a replica&lt;/strong>. When autoscaling decides to bring up a new instance of a model, because demand is rising, because a replica has failed or because a version is being rotated, the weights have to be read from the registry and loaded into HBM. For a model of tens or hundreds of GB, that &lt;em>cold start&lt;/em> can run from seconds to minutes depending on registry bandwidth and the load path. And during that time the new GPU serves no requests while the system is, perhaps, saturated. A slow model registry translates directly into missed SLAs at peak. The mitigations, keeping weights on hot local flash, using fast-loading formats, &lt;em>streaming&lt;/em> weights as they are needed, or GPUDirect Storage to load straight into HBM, are storage decisions that determine the factory&amp;rsquo;s real elasticity.&lt;/p>
&lt;h3 id="rag-performance-and-vector-search">RAG performance and vector search&lt;/h3>
&lt;p>If the factory serves RAG, there is one more link in the critical path of every request: retrieval. Before the model generates anything, the system searches a vector database for the relevant fragments, and that similarity search, over indexes that can run to TB, adds to TTFT. Vector search performance depends on keeping the indexes on low-latency flash and on an efficient random read pattern; an index that does not fit in memory and is served from slow storage adds latency to every request. Beyond that, precomputed KV-caches of the retrieved documents speed up &lt;em>prefill&lt;/em>, because the model reuses keys and values already calculated and only encodes the user&amp;rsquo;s query. An inference factory with RAG therefore has two latency-sensitive read workloads coexisting, the vector index and the KV-cache, competing for the same fast flash and needing to be sized together.&lt;/p>
&lt;p>For an inference architect, all this introduces a storage plane with a profile of its own: latency-sensitive random reads (indexes, reusable KV-cache prefixes), high-turnover ephemeral writes (new KV-cache), large, sporadic but urgent reads (model loading) and an append-only telemetry flow. It is not sized in the least like the storage of a training cluster.&lt;/p>
&lt;h2 id="how-to-measure-your-own-beyond-the-spec-sheet">How to measure your own: beyond the spec sheet&lt;/h2>
&lt;p>Vendor and MLPerf figures are useful as a reference, but real performance depends on the specific workload, and an architect must measure rather than assume. The methodology that works starts from three principles.&lt;/p>
&lt;p>The first is to &lt;strong>measure accelerator utilisation, not storage in the abstract&lt;/strong>. An array can deliver 500 GB/s in a synthetic test and still leave the GPUs at 50 % because the real pattern is small random files that the sequential test does not reproduce. The right question is always: what fraction of the time is my GPU computing, with this workload, this dataset format and this &lt;em>pipeline&lt;/em>?&lt;/p>
&lt;p>The second is to &lt;strong>reproduce the real I/O pattern&lt;/strong>, not a convenient one. That means testing with the same dataset format, the same &lt;em>shard&lt;/em> size, the same number of &lt;em>workers&lt;/em> and the same &lt;em>checkpointing&lt;/em> cadence that will be used in production. MLPerf Storage&amp;rsquo;s insight was precisely to simulate accelerator &lt;em>think time&lt;/em> in order to generate a realistic pattern; replicating that rigour at your own scale is what separates a useful measurement from a &lt;em>marketing&lt;/em> number.&lt;/p>
&lt;p>The third is to &lt;strong>look for scaling, not peak&lt;/strong>. A platform that delivers a very high peak in a small configuration but flattens out as nodes are added is no use for growth. Tests should be run at several scales to verify that &lt;em>throughput&lt;/em> grows roughly linearly and that accelerator utilisation holds, as the Hammerspace example in MLPerf v2.0 showed. A low coefficient of variation across configurations matters as much as the peak.&lt;/p>
&lt;h2 id="the-cost-of-getting-it-wrong">The cost of getting it wrong&lt;/h2>
&lt;p>It is worth putting a number on the problem. The price of an H100 in the cloud in mid-2026 moves across a wide range by provider, with a market median around 2.3-3.1 USD per GPU-hour. Multiply that by thousands of GPUs and by the weeks a training run lasts, and 40 % utilisation instead of 90 % translates into millions of euros of wasted compute. That is why more than 50 % of organisations report data or storage bottlenecks limiting their AI in 2026, and why storage bandwidth has emerged as a hard ceiling on scaling, comparable to power and cooling: when there is &lt;em>data starvation&lt;/em>, adding compute gives diminishing returns.&lt;/p>
&lt;p>The operational recommendation that keeps coming up is to size the I/O &lt;em>pipeline&lt;/em> for 300 Gbps or more in serious workloads, and to measure, not assume, accelerator utilisation as a health indicator. Optimised storage can deliver up to 5 times the &lt;em>throughput&lt;/em> of standard S3 access over HTTP: more than 100 GB/s aggregate against some 20 GB/s.&lt;/p>
&lt;h2 id="the-physical-layer-networks-and-pcie-gen6">The physical layer: networks and PCIe Gen6&lt;/h2>
&lt;p>All of the above rests on a physical layer that has also advanced. NVIDIA&amp;rsquo;s ConnectX-8 NIC combines PCIe Gen6 with 800 GbE or 800 Gb InfiniBand XDR, designed for Blackwell systems. InfiniBand XDR offers 800 Gbps per direction (double NDR, which remains the usual &lt;em>storage fabric&lt;/em>), and the Quantum-X800 &lt;em>switch&lt;/em> adds in-network compute that accelerates collective operations. The transition to XDR is accelerating with the Blackwell deployments of 2025-2026. The mental rule is simple: storage must match the network, and the network must match the GPU. One slow link defines the performance of the whole chain.&lt;/p>
&lt;p>It is worth translating this into concrete numbers. An 800 Gb/s NIC delivers, at best, around 100 GB/s to a node. If the array can serve more than the NIC absorbs, the network is the bottleneck; if the array serves less, storage is. Balance is found by sizing each layer so none strangles the next, and by verifying it with end-to-end measurements rather than the isolated specifications of each component. It is common to find installations with an excellent array and an undersized &lt;em>fabric&lt;/em>, or the reverse, performing far below the sum of their parts. The physical layer also imposes its geography: network topology (how many hops there are between the compute node and the array, whether they share a switch or cross the cluster spine) affects latency as much as the array itself, and an AI storage design that ignores network topology is incomplete.&lt;/p>
&lt;h2 id="three-questions-before-sizing">Three questions before sizing&lt;/h2>
&lt;p>All this complexity can be organised into three questions an architect should answer before buying anything. What is the dominant I/O pattern of my workload, large sequential, small random, bursty writes, and therefore which metric limits me? Is my workload bandwidth-limited or latency-limited, and consequently where do I invest? And what is my &lt;em>checkpointing&lt;/em> budget, in size, cadence and, above all, what fraction can I overlap with compute? Whoever answers these three with measured data, rather than intuitions, sizes well; whoever skips them ends up with idle GPUs or with an oversized, very expensive array that is never put to use. AI storage performance is, at bottom, an exercise in fit: putting the right capacity where the workload needs it, no more and no less.&lt;/p>
&lt;h2 id="takeaways">Takeaways&lt;/h2>
&lt;p>AI storage performance is not measured in GB/s but in accelerator utilisation, and that utilisation depends on three things: enough bandwidth, low latency and, above all, overlapping I/O with compute. MLPerf Storage v2.0 has finally given a common language for comparing platforms through that lens. &lt;em>Checkpointing&lt;/em> is the most demanding write workload and is mastered with asynchrony, not hardware alone. Long-context inference adds a new storage layer around the KV-cache. And the cost of getting it wrong is measured in idle GPUs, which is the most expensive line in the budget.&lt;/p>
&lt;p>Performance, however, is worth nothing if the data is neither protected nor available. The &lt;a href="https://blog.lo0.es/en/posts/storage-ai-era-security/">third article&lt;/a> takes on the security of this new storage layer.&lt;/p>
&lt;h2 id="see-also">See also&lt;/h2>
&lt;ul>
&lt;li>&lt;a href="https://blog.lo0.es/en/posts/ai-era-storage-state-of-the-art/">Storage in the AI era (1/4): the state of the art&lt;/a>&lt;/li>
&lt;li>&lt;a href="https://blog.lo0.es/en/posts/storage-ai-era-security/">Storage in the AI era (3/4): security&lt;/a>&lt;/li>
&lt;li>&lt;a href="https://blog.lo0.es/en/posts/storage-ai-era-availability/">Storage in the AI era (4/4): availability&lt;/a>&lt;/li>
&lt;/ul>
&lt;h2 id="sources">Sources&lt;/h2>
&lt;ul>
&lt;li>MLCommons, &lt;em>MLPerf Storage v2.0 Results&lt;/em> — &lt;a href="https://mlcommons.org/2025/08/mlperf-storage-v2-0-results/">https://mlcommons.org/2025/08/mlperf-storage-v2-0-results/&lt;/a>&lt;/li>
&lt;li>MLCommons, &lt;em>Announcing the MLPerf Storage v2.0 Checkpointing Workload&lt;/em> — &lt;a href="https://mlcommons.org/2025/08/storage-2-checkpointing/">https://mlcommons.org/2025/08/storage-2-checkpointing/&lt;/a>&lt;/li>
&lt;li>MLCommons, &lt;em>MLPerf Storage v1.0 Benchmark Results&lt;/em> — &lt;a href="https://mlcommons.org/2024/09/mlperf-storage-v1-0-benchmark-results/">https://mlcommons.org/2024/09/mlperf-storage-v1-0-benchmark-results/&lt;/a>&lt;/li>
&lt;li>Hammerspace, &lt;em>MLPerf Storage v2.0 Benchmark Results&lt;/em> — &lt;a href="https://hammerspace.com/hammerspace-mlperf-storage-v2-0-benchmark-results/">https://hammerspace.com/hammerspace-mlperf-storage-v2-0-benchmark-results/&lt;/a>&lt;/li>
&lt;li>Alluxio, &lt;em>Strong Performance in MLPerf Storage v2.0&lt;/em> — &lt;a href="https://www.alluxio.io/blog/alluxio-demonstrates-strong-performance-in-mlperf-storage-v2-0-benchmarks">https://www.alluxio.io/blog/alluxio-demonstrates-strong-performance-in-mlperf-storage-v2-0-benchmarks&lt;/a>&lt;/li>
&lt;li>NVIDIA, &lt;em>GPUDirect Storage Overview Guide&lt;/em> — &lt;a href="https://docs.nvidia.com/gpudirect-storage/overview-guide/index.html">https://docs.nvidia.com/gpudirect-storage/overview-guide/index.html&lt;/a>&lt;/li>
&lt;li>Spheron, &lt;em>GPU Direct Storage Guide 2026&lt;/em> — &lt;a href="https://www.spheron.network/blog/gpu-direct-storage-nvme-ai-training-inference-guide/">https://www.spheron.network/blog/gpu-direct-storage-nvme-ai-training-inference-guide/&lt;/a>&lt;/li>
&lt;li>PyTorch, &lt;em>Reducing Checkpointing Times with Async Checkpointing&lt;/em> — &lt;a href="https://pytorch.org/blog/reducing-checkpointing-times/">https://pytorch.org/blog/reducing-checkpointing-times/&lt;/a>&lt;/li>
&lt;li>VAST Data, &lt;em>Optimizing Checkpoint Bandwidth for LLM Training&lt;/em> — &lt;a href="https://www.vastdata.com/blog/optimizing-checkpoint-bandwidth-for-llm-training">https://www.vastdata.com/blog/optimizing-checkpoint-bandwidth-for-llm-training&lt;/a>&lt;/li>
&lt;li>Astera Labs, &lt;em>How CXL Transforms RAG and KV Cache Performance&lt;/em> — &lt;a href="https://www.asteralabs.com/breaking-through-the-memory-wall-how-cxl-transforms-rag-and-kv-cache-performance/">https://www.asteralabs.com/breaking-through-the-memory-wall-how-cxl-transforms-rag-and-kv-cache-performance/&lt;/a>&lt;/li>
&lt;li>MinIO, &lt;em>AI Storage Architecture: Overcoming the Bottleneck in 2026&lt;/em> — &lt;a href="https://www.min.io/blog/ai-storage-architecture-bottleneck-2026">https://www.min.io/blog/ai-storage-architecture-bottleneck-2026&lt;/a>&lt;/li>
&lt;li>WEKA, &lt;em>WEKApod Nitro reference architecture&lt;/em> — &lt;a href="https://www.weka.io/resources/reference-architecture/nvidia-gb200-nvl72-systems-reference-architecture-for-cloud-partners/">https://www.weka.io/resources/reference-architecture/nvidia-gb200-nvl72-systems-reference-architecture-for-cloud-partners/&lt;/a>&lt;/li>
&lt;li>Blocks &amp;amp; Files, &lt;em>Storage vendors rally behind Nvidia at GTC 2025&lt;/em> — &lt;a href="https://blocksandfiles.com/2025/03/18/nvidia-storage-announcements/">https://blocksandfiles.com/2025/03/18/nvidia-storage-announcements/&lt;/a>&lt;/li>
&lt;li>ServeTheHome, &lt;em>NVIDIA ConnectX-8 SuperNIC PCIe Gen6 800G&lt;/em> — &lt;a href="https://www.servethehome.com/nvidia-connectx-8-supernic-pcie-gen6-800g-nic-detailed/">https://www.servethehome.com/nvidia-connectx-8-supernic-pcie-gen6-800g-nic-detailed/&lt;/a>&lt;/li>
&lt;li>SpendArk, &lt;em>GPU Cloud Pricing 2026&lt;/em> — &lt;a href="https://spendark.com/blog/machine-learning-cloud-cost/">https://spendark.com/blog/machine-learning-cloud-cost/&lt;/a>&lt;/li>
&lt;/ul></description></item><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>