<?xml version="1.0" encoding="utf-8" standalone="yes"?><rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom"><channel><title>Reproducibilidad on lo0 — Blog Técnico</title><link>https://blog.lo0.es/tags/reproducibilidad/</link><description>Recent content in Reproducibilidad on lo0 — Blog Técnico</description><generator>Hugo -- gohugo.io</generator><language>es</language><lastBuildDate>Tue, 16 Jun 2026 12:00:00 +0200</lastBuildDate><atom:link href="https://blog.lo0.es/tags/reproducibilidad/index.xml" rel="self" type="application/rss+xml"/><item><title>El harness reproducible: medir coste, rendimiento y energía en un solo experimento auditable</title><link>https://blog.lo0.es/posts/harness-reproducible-medicion-coste-rendimiento-energia/</link><pubDate>Tue, 16 Jun 2026 12:00:00 +0200</pubDate><guid>https://blog.lo0.es/posts/harness-reproducible-medicion-coste-rendimiento-energia/</guid><description>&lt;blockquote>
&lt;p>Notación: importes en &lt;strong>euros (N €)&lt;/strong>, decimales con coma, millares con espacio fino. No se usa el símbolo de dólar (en este sitio es delimitador de fórmula).&lt;/p>
&lt;/blockquote>
&lt;h2 id="tldr">TL;DR&lt;/h2>
&lt;p>La serie &amp;ldquo;datos&amp;rdquo; ha producido tres ejes de medición independientes: coste por millón de tokens (OpenCost + LiteLLM), rendimiento bajo SLO (GuideLLM + AIPerf) y energía por token (DCGM + Kepler). El problema es que los tres se han medido en artículos distintos, con cargas distintas y en momentos distintos: no son comparables entre sí. Este artículo de cierre describe el &lt;strong>harness integrado&lt;/strong> que ejecuta los tres ejes &lt;strong>en el mismo experimento&lt;/strong>, sobre el mismo nodo (4×H100 SXM, referencia genérica), con todos los metadatos fijados, la salida en JSON/CSV versionados y un Job de Kubernetes idempotente. El resultado es el &lt;strong>scorecard de 3 ejes&lt;/strong> (€/1M tok, Wh/token, TTFT/ITL P99) que permite comparar configuraciones sobre una &lt;strong>frontera de Pareto multi-objetivo&lt;/strong> y auditar cualquier cifra con el banco para reproducirla.&lt;/p>
&lt;hr>
&lt;h2 id="por-qué-los-tres-ejes-deben-medirse-juntos">Por qué los tres ejes deben medirse juntos&lt;/h2>
&lt;p>El post de apertura de la serie (&lt;a href="https://blog.lo0.es/posts/tres-ejes-coste-rendimiento-energia-inferencia-llm/">Los tres ejes&lt;/a>) estableció la identidad:&lt;/p>
&lt;p>$$\text{CPM} = \frac{\text{coste/h}}{\text{throughput (tok/s)} \times 3{,}6 \times 10^{-3}}$$&lt;/p>
&lt;p>$$\text{energía/token (Wh)} = \frac{\text{potencia media (W)}}{\text{throughput (tok/s)} \times 3,600}$$&lt;/p>
&lt;p>El throughput es el denominador común. Si se mide en experimentos distintos —diferente hora, diferente carga, diferente temperatura de GPU— el CPM y la energía/token &lt;strong>no comparten denominador&lt;/strong>: son tres anécdotas, no un scorecard. El harness los captura en la misma ventana temporal, sobre la misma carga, con ventanas de Prometheus alineadas al segundo. Solo así la fila del scorecard es coherente por construcción.&lt;/p>
&lt;p>La segunda razón es la reproducibilidad. El post &lt;a href="https://blog.lo0.es/posts/sesgo-medicion-reproducibilidad-bench/">Sesgo y reproducibilidad en el benchmarking&lt;/a> listó doce sesgos que invalidan comparaciones: motor no pinneado, tokenizador no declarado, longitud de entrada/salida no fijada, warmup ausente, cliente fuera del cluster. El harness elimina todos ellos porque los metadatos son parte del Job, no de la documentación.&lt;/p>
&lt;hr>
&lt;h2 id="arquitectura-del-banco-integrado">Arquitectura del banco integrado&lt;/h2>
&lt;p>El harness tiene cuatro capas. Cada una es OSS, exporta a Prometheus y convive en el mismo namespace de Kubernetes:&lt;/p>
&lt;table>
&lt;thead>
&lt;tr>
&lt;th>Capa&lt;/th>
&lt;th>Herramienta(s)&lt;/th>
&lt;th>Métrica primaria&lt;/th>
&lt;th>Protocolo de export&lt;/th>
&lt;/tr>
&lt;/thead>
&lt;tbody>
&lt;tr>
&lt;td>&lt;strong>Rendimiento&lt;/strong>&lt;/td>
&lt;td>GuideLLM (sweep SLO) + AIPerf&lt;/td>
&lt;td>TTFT P99, ITL P99, goodput (tok/s)&lt;/td>
&lt;td>JSON/CSV nativo + &lt;code>/metrics&lt;/code> OpenMetrics&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>&lt;strong>Coste&lt;/strong>&lt;/td>
&lt;td>OpenCost + LiteLLM proxy&lt;/td>
&lt;td>CPM (€/1M tok), coste/petición&lt;/td>
&lt;td>API REST + Prometheus scrape&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>&lt;strong>Energía (GPU)&lt;/strong>&lt;/td>
&lt;td>DCGM Exporter&lt;/td>
&lt;td>&lt;code>DCGM_FI_DEV_POWER_USAGE&lt;/code> (W), &lt;code>DCGM_FI_DEV_TOTAL_ENERGY_CONSUMPTION&lt;/code> (mJ)&lt;/td>
&lt;td>DaemonSet Prometheus&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>&lt;strong>Energía (pod)&lt;/strong>&lt;/td>
&lt;td>Kepler&lt;/td>
&lt;td>&lt;code>kepler_container_joules_total&lt;/code>, energía por pod&lt;/td>
&lt;td>DaemonSet Prometheus&lt;/td>
&lt;/tr>
&lt;/tbody>
&lt;/table>
&lt;p>MLPerf Power se usa como &lt;strong>referencia de comparabilidad externa&lt;/strong>: sus resultados publicados, con hardware documentado al detalle, permiten calibrar si las cifras del harness son plausibles. El banco propio no pretende ser un submission de MLPerf, sino ser &lt;strong>reproducible en el propio cluster&lt;/strong>.&lt;/p>
&lt;div class="diagram" style="max-width:820px;margin:1rem auto;">
&lt;svg viewBox="0 0 820 320" role="img" aria-label="Arquitectura del harness: Job Kubernetes orquesta GuideLLM y AIPerf como generadores de carga, OpenCost y LiteLLM miden coste, DCGM y Kepler miden energia, todo confluye en Prometheus y el scorecard JSON" 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.2;marker-end:url(#ah)}.dsh{fill:none;stroke:currentColor;stroke-width:1;stroke-dasharray:4 3;marker-end:url(#ah)}&lt;/style>
&lt;defs>&lt;marker id="ah" viewBox="0 0 10 10" refX="9" refY="5" markerWidth="6" markerHeight="6" orient="auto">&lt;path d="M0,0 L10,5 L0,10 z" fill="currentColor"/>&lt;/marker>&lt;/defs>
&lt;rect class="bx" x="10" y="10" width="800" height="300" rx="10"/>
&lt;text x="20" y="28" class="tl">Namespace: benchmark&lt;/text>
&lt;rect class="bx" x="30" y="40" width="180" height="70" rx="6"/>
&lt;text x="40" y="60" class="tl">Job benchmark-run&lt;/text>
&lt;text x="40" y="76" class="ts">GuideLLM sweep (SLO)&lt;/text>
&lt;text x="40" y="92" class="ts">AIPerf concurrencia fija&lt;/text>
&lt;rect class="bx" x="30" y="130" width="180" height="60" rx="6"/>
&lt;text x="40" y="150" class="tl">LiteLLM proxy&lt;/text>
&lt;text x="40" y="168" class="ts">token counting + CPM&lt;/text>
&lt;rect class="bx" x="30" y="210" width="180" height="60" rx="6"/>
&lt;text x="40" y="230" class="tl">DaemonSet DCGM&lt;/text>
&lt;text x="40" y="248" class="ts">POWER_USAGE, ENERGY&lt;/text>
&lt;rect class="bx" x="30" y="280" width="180" height="28" rx="6"/>
&lt;text x="40" y="298" class="tl">DaemonSet Kepler&lt;/text>
&lt;rect class="bx" x="280" y="40" width="180" height="70" rx="6"/>
&lt;text x="290" y="60" class="tl">Inference endpoint&lt;/text>
&lt;text x="290" y="78" class="ts">vLLM / SGLang&lt;/text>
&lt;text x="290" y="96" class="ts">modelo pinneado, FP8/FP16&lt;/text>
&lt;rect class="bx" x="280" y="130" width="180" height="60" rx="6"/>
&lt;text x="290" y="150" class="tl">OpenCost&lt;/text>
&lt;text x="290" y="168" class="ts">€/GPU-h por pod/ns&lt;/text>
&lt;rect class="bx" x="540" y="60" width="160" height="80" rx="6"/>
&lt;text x="550" y="82" class="tl">Prometheus&lt;/text>
&lt;text x="550" y="100" class="ts">scrape 15 s&lt;/text>
&lt;text x="550" y="116" class="ts">retención 30 días&lt;/text>
&lt;rect class="bx" x="540" y="170" width="160" height="80" rx="6"/>
&lt;text x="550" y="192" class="tl">Scorecard exporter&lt;/text>
&lt;text x="550" y="210" class="ts">PromQL → JSON/CSV&lt;/text>
&lt;text x="550" y="228" class="ts">versionado en git&lt;/text>
&lt;path class="ar" d="M210,75 L280,75"/>
&lt;path class="ar" d="M460,155 L540,155"/>
&lt;path class="ar" d="M210,155 L280,155"/>
&lt;path class="dsh" d="M210,240 L540,200"/>
&lt;path class="dsh" d="M210,294 L540,220"/>
&lt;path class="ar" d="M700,100 L700,170"/>
&lt;text x="550" y="278" class="ts">una fila por (modelo, config, hardware)&lt;/text>
&lt;/svg>
&lt;/div>
&lt;hr>
&lt;h2 id="el-job-de-kubernetes-yaml-completo">El Job de Kubernetes: YAML completo&lt;/h2>
&lt;p>El experimento se ejecuta como un &lt;strong>Kubernetes Job&lt;/strong> versionado. Todos los metadatos relevantes son variables de entorno declaradas en el manifiesto: ni en scripts ad-hoc, ni en documentación externa. El Job es idempotente (mismo nombre = misma corrida) y deja trazas en el log del pod y en el volumen de salida.&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-yaml" data-lang="yaml">&lt;span class="line">&lt;span class="cl">&lt;span class="nt">apiVersion&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="l">batch/v1&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w">&lt;/span>&lt;span class="nt">kind&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="l">Job&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w">&lt;/span>&lt;span class="nt">metadata&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="nt">name&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="l">bench-llama3-70b-fp8-h100x4-20260616&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="nt">namespace&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="l">benchmark&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="nt">labels&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="nt">bench/model&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="l">llama3-70b&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="nt">bench/precision&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="l">fp8&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="nt">bench/engine&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="l">vllm-0.9.1&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="nt">bench/hardware&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="l">h100x4-sxm&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="nt">bench/isl&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="s2">&amp;#34;1024&amp;#34;&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="nt">bench/osl&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="s2">&amp;#34;256&amp;#34;&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="nt">bench/concurrency&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="s2">&amp;#34;32&amp;#34;&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="nt">bench/tokenizer&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="l">meta-llama-3-tokenizer-v3&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="nt">bench/run-id&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="s2">&amp;#34;20260616T1200&amp;#34;&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w">&lt;/span>&lt;span class="nt">spec&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="nt">backoffLimit&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="m">0&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="nt">template&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="nt">spec&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="nt">restartPolicy&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="l">Never&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="nt">serviceAccountName&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="l">bench-runner&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="nt">volumes&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>- &lt;span class="nt">name&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="l">results&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="nt">persistentVolumeClaim&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="nt">claimName&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="l">bench-results-pvc&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="nt">initContainers&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="c"># Warmup: 60 s de tráfico previo al experimento&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>- &lt;span class="nt">name&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="l">warmup&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="nt">image&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="l">ghcr.io/vllm-project/guidellm:0.4.2&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="nt">command&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>- &lt;span class="l">guidellm&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>- &lt;span class="l">benchmark&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>- --&lt;span class="l">target&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>- &lt;span class="l">http://vllm-svc.inference.svc.cluster.local:8000&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>- --&lt;span class="l">rate-type&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>- &lt;span class="l">concurrent&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>- --&lt;span class="l">rate&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>- &lt;span class="s2">&amp;#34;4&amp;#34;&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>- --&lt;span class="l">max-seconds&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>- &lt;span class="s2">&amp;#34;60&amp;#34;&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>- --&lt;span class="l">data&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>- &lt;span class="l">prompt_tokens=1024,output_tokens=256&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="nt">env&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>- &lt;span class="nt">name&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="l">GUIDELLM_ENV&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="nt">value&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="l">production&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="nt">containers&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>- &lt;span class="nt">name&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="l">guidellm-sweep&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="nt">image&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="l">ghcr.io/vllm-project/guidellm:0.4.2&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="nt">command&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>- &lt;span class="l">guidellm&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>- &lt;span class="l">benchmark&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>- --&lt;span class="l">target&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>- &lt;span class="l">http://vllm-svc.inference.svc.cluster.local:8000&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>- --&lt;span class="l">rate-type&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>- &lt;span class="l">sweep&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>- --&lt;span class="l">max-seconds&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>- &lt;span class="s2">&amp;#34;120&amp;#34;&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>- --&lt;span class="l">data&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>- &lt;span class="l">prompt_tokens=1024,output_tokens=256&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>- --&lt;span class="l">output-path&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>- &lt;span class="l">/results/guidellm-$(BENCH_RUN_ID).json&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="nt">env&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>- &lt;span class="nt">name&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="l">BENCH_RUN_ID&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="nt">valueFrom&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="nt">fieldRef&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="nt">fieldPath&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="l">metadata.labels[&amp;#39;bench/run-id&amp;#39;]&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="nt">volumeMounts&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>- &lt;span class="nt">name&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="l">results&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="nt">mountPath&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="l">/results&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>- &lt;span class="nt">name&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="l">aiperf-concurrent&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="nt">image&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="l">nvcr.io/nvidia/aiperf:0.2.0&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="nt">command&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>- &lt;span class="l">aiperf&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>- &lt;span class="l">profile&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>- --&lt;span class="l">url&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>- &lt;span class="l">http://vllm-svc.inference.svc.cluster.local:8000/v1&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>- --&lt;span class="l">model&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>- &lt;span class="l">meta-llama/Meta-Llama-3-70B-Instruct&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>- --&lt;span class="l">concurrency&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>- &lt;span class="s2">&amp;#34;4,8,16,32&amp;#34;&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>- --&lt;span class="l">input-tokens&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>- &lt;span class="s2">&amp;#34;1024&amp;#34;&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>- --&lt;span class="l">output-tokens&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>- &lt;span class="s2">&amp;#34;256&amp;#34;&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>- --&lt;span class="l">num-requests&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>- &lt;span class="s2">&amp;#34;200&amp;#34;&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>- --&lt;span class="l">output&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>- &lt;span class="l">/results/aiperf-$(BENCH_RUN_ID).json&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="nt">volumeMounts&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>- &lt;span class="nt">name&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="l">results&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="nt">mountPath&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="l">/results&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>- &lt;span class="nt">name&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="l">scorecard-exporter&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="nt">image&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="l">python:3.12-slim&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="nt">command&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>- &lt;span class="l">python&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>- &lt;span class="l">/scripts/export_scorecard.py&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>- --&lt;span class="l">run-id&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>- &lt;span class="s2">&amp;#34;$(BENCH_RUN_ID)&amp;#34;&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>- --&lt;span class="l">prometheus&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>- &lt;span class="l">http://prometheus.monitoring.svc.cluster.local:9090&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>- --&lt;span class="l">output&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>- &lt;span class="l">/results/scorecard-$(BENCH_RUN_ID).json&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="nt">env&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>- &lt;span class="nt">name&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="l">BENCH_RUN_ID&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="nt">valueFrom&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="nt">fieldRef&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="nt">fieldPath&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="l">metadata.labels[&amp;#39;bench/run-id&amp;#39;]&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="nt">volumeMounts&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>- &lt;span class="nt">name&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="l">results&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="nt">mountPath&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="l">/results&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>El nombre del Job (&lt;code>bench-llama3-70b-fp8-h100x4-20260616&lt;/code>) es el identificador único de la corrida. Cambiarlo es suficiente para registrar una variante. Los labels son los metadatos que el &lt;code>scorecard-exporter&lt;/code> lee para enriquecer el JSON de salida.&lt;/p>
&lt;hr>
&lt;h2 id="capa-de-rendimiento-guidellm-y-aiperf">Capa de rendimiento: GuideLLM y AIPerf&lt;/h2>
&lt;h3 id="guidellm--el-sweep-dirigido-por-slo">GuideLLM — el sweep dirigido por SLO&lt;/h3>
&lt;p>GuideLLM (proyecto vLLM) genera patrones de tráfico realistas —synchronous, concurrent, poisson, throughput, &lt;strong>sweep&lt;/strong>— y captura distribuciones completas de TTFT e ITL (&lt;a href="https://developers.redhat.com/articles/2025/06/20/guidellm-evaluate-llm-deployments-real-world-inference">Red Hat Developer&lt;/a>). El modo &lt;code>sweep&lt;/code> barre de idle a saturación en 10 rondas e identifica el &lt;strong>codo&lt;/strong>: la carga máxima donde el goodput ≈ throughput bajo el SLO declarado. La salida es JSON/CSV con todos los percentiles por ronda, lista para versionarse.&lt;/p>
&lt;p>Para el harness, el SLO de referencia del banco es:&lt;/p>
&lt;table>
&lt;thead>
&lt;tr>
&lt;th>Métrica&lt;/th>
&lt;th>Umbral&lt;/th>
&lt;th>Percentil&lt;/th>
&lt;/tr>
&lt;/thead>
&lt;tbody>
&lt;tr>
&lt;td>TTFT&lt;/td>
&lt;td>500 ms&lt;/td>
&lt;td>P99&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>ITL (TPOT)&lt;/td>
&lt;td>50 ms/tok&lt;/td>
&lt;td>P95&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>Tasa de error&lt;/td>
&lt;td>0,5 %&lt;/td>
&lt;td>—&lt;/td>
&lt;/tr>
&lt;/tbody>
&lt;/table>
&lt;p>El comando del Job ya aparece en el YAML anterior. La salida JSON incluye por ronda: tasa (req/s), TTFT (P50/P95/P99), ITL (P50/P95/P99), throughput (tok/s) y goodput (tok/s). El goodput bajo el SLO del codo es el valor que entra en el scorecard.&lt;/p>
&lt;p>Para la capa de verificación cruzada de datos y comparabilidad con resultados publicados, véase el post &lt;a href="https://blog.lo0.es/posts/guidellm-validacion-slo-bajo-carga/">GuideLLM a fondo&lt;/a>.&lt;/p>
&lt;h3 id="aiperf--el-perfilador-de-concurrencia-de-nvidia">AIPerf — el perfilador de concurrencia de NVIDIA&lt;/h3>
&lt;p>AIPerf (sucesor de GenAI-Perf, repositorio &lt;code>ai-dynamo/aiperf&lt;/code>) mide TTFT, ITL, throughput y latencias en distribución a concurrencias fijas (&lt;a href="https://github.com/ai-dynamo/aiperf">GitHub ai-dynamo/aiperf&lt;/a>). Donde GuideLLM da el sweep automático hasta el codo, AIPerf da el perfil detallado a concurrencias concretas (4, 8, 16, 32 en el ejemplo): permite caracterizar la curva throughput-latencia punto a punto.&lt;/p>
&lt;p>Los dos son complementarios: GuideLLM halla el codo de forma automática; AIPerf lo confirma y caracteriza el comportamiento en el entorno de ese codo. Ambas salidas van al volumen de resultados con el mismo &lt;code>BENCH_RUN_ID&lt;/code>. El análisis profundo de AIPerf/GenAI-Perf está en &lt;a href="https://blog.lo0.es/posts/nvidia-genai-perf-a-fondo/">GenAI-Perf a fondo&lt;/a>.&lt;/p>
&lt;p>Métricas que el harness extrae de AIPerf para el scorecard:&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">&lt;span class="c1"># TTFT P99 a concurrencia 16 (la más cercana al codo del sweep):&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">aiperf profile ... --concurrency &lt;span class="m">16&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> &lt;span class="p">|&lt;/span> jq &lt;span class="s1">&amp;#39;.results[] | select(.concurrency==16) | .ttft_ms.p99&amp;#39;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="c1"># Goodput (tok/s) a concurrencia 16:&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">aiperf profile ... --concurrency &lt;span class="m">16&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> &lt;span class="p">|&lt;/span> jq &lt;span class="s1">&amp;#39;.results[] | select(.concurrency==16) | .output_token_throughput&amp;#39;&lt;/span>
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;hr>
&lt;h2 id="capa-de-coste-opencost-y-litellm">Capa de coste: OpenCost y LiteLLM&lt;/h2>
&lt;h3 id="opencost--el-denominador-en-euros-por-gpu-hora">OpenCost — el denominador en euros por GPU-hora&lt;/h3>
&lt;p>OpenCost (CNCF incubating, &lt;a href="https://opencost.io/">opencost.io&lt;/a>) asigna coste de Kubernetes a namespace, label, pod y contenedor en tiempo real (&lt;a href="https://github.com/opencost/opencost">GitHub opencost/opencost&lt;/a>). Para el harness, OpenCost resuelve la pregunta: &lt;strong>¿cuánto cuesta en euros por hora el pod &lt;code>vllm-svc&lt;/code> durante la ventana del experimento?&lt;/strong>&lt;/p>
&lt;p>El harness llama a la API REST de OpenCost al terminar el experimento:&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">&lt;span class="c1"># Coste del namespace inference en la ventana del experimento (1 hora)&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">curl -s &lt;span class="s2">&amp;#34;http://opencost.monitoring.svc.cluster.local:9003/allocation&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> --data-urlencode &lt;span class="s1">&amp;#39;window=2026-06-16T12:00:00Z,2026-06-16T13:00:00Z&amp;#39;&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-urlencode &lt;span class="s1">&amp;#39;aggregate=namespace&amp;#39;&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-urlencode &lt;span class="s1">&amp;#39;namespace=inference&amp;#39;&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> &lt;span class="p">|&lt;/span> jq &lt;span class="s1">&amp;#39;.data[0].inference.totalCost&amp;#39;&lt;/span>
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>El coste devuelto (en EUR, configurado con precios reales del nodo) se divide por el throughput medido en esa misma ventana para obtener el CPM:&lt;/p>
&lt;p>$$\text{CPM} = \frac{\text{coste}_\text{namespace/h} \times 10^6}{\text{goodput (tok/s)} \times 3,600}$$&lt;/p>
&lt;p>El post &lt;a href="https://blog.lo0.es/posts/opencost-cost-allocation-kubernetes/">OpenCost: cost allocation en Kubernetes&lt;/a> detalla la configuración de precios y la asignación por label de GPU.&lt;/p>
&lt;h3 id="litellm--el-token-counter-por-petición">LiteLLM — el token counter por petición&lt;/h3>
&lt;p>LiteLLM (&lt;a href="https://www.litellm.ai/">litellm.ai&lt;/a>, &lt;a href="https://github.com/BerriAI/litellm">GitHub BerriAI/litellm&lt;/a>) actúa como proxy OpenAI-compatible con contabilidad de tokens por petición, modelo y equipo. En el harness, GuideLLM y AIPerf apuntan al endpoint de LiteLLM (que a su vez reenvía a vLLM): cada petición queda registrada con &lt;code>prompt_tokens&lt;/code>, &lt;code>completion_tokens&lt;/code> y &lt;code>cost&lt;/code> (usando el pricing custom configurado para el nodo on-prem).&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-yaml" data-lang="yaml">&lt;span class="line">&lt;span class="cl">&lt;span class="c"># litellm-config.yaml (fragmento de pricing custom on-prem)&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w">&lt;/span>&lt;span class="nt">model_list&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>- &lt;span class="nt">model_name&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="l">llama3-70b-fp8&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="nt">litellm_params&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="nt">model&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="l">openai/meta-llama/Meta-Llama-3-70B-Instruct&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="nt">api_base&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="l">http://vllm-svc.inference.svc.cluster.local:8000/v1&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="nt">api_key&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="l">sk-dummy&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="nt">input_cost_per_token&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="m">0.00000109&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="c"># 1,09 EUR/1M tok on-prem&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="nt">output_cost_per_token&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="m">0.00000109&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>Los registros de LiteLLM se exportan a Prometheus como &lt;code>litellm_request_total_tokens&lt;/code> y &lt;code>litellm_spend_metric_total&lt;/code>, que el scorecard exporter consume para calcular el CPM real por tipo de token (input vs output).&lt;/p>
&lt;hr>
&lt;h2 id="capa-de-energía-dcgm-y-kepler">Capa de energía: DCGM y Kepler&lt;/h2>
&lt;h3 id="dcgm-exporter--potencia-y-energía-gpu">DCGM Exporter — potencia y energía GPU&lt;/h3>
&lt;p>DCGM Exporter (&lt;a href="https://github.com/NVIDIA/dcgm-exporter">GitHub NVIDIA/dcgm-exporter&lt;/a>, &lt;a href="https://docs.nvidia.com/datacenter/dcgm/latest/gpu-telemetry/dcgm-exporter.html">docs&lt;/a>) expone métricas de GPU en &lt;code>/metrics&lt;/code> para Prometheus como DaemonSet en los nodos GPU. Las dos métricas de energía que usa el harness:&lt;/p>
&lt;table>
&lt;thead>
&lt;tr>
&lt;th>Métrica DCGM&lt;/th>
&lt;th>Tipo&lt;/th>
&lt;th>Unidad&lt;/th>
&lt;th>Uso en el harness&lt;/th>
&lt;/tr>
&lt;/thead>
&lt;tbody>
&lt;tr>
&lt;td>&lt;code>DCGM_FI_DEV_POWER_USAGE&lt;/code>&lt;/td>
&lt;td>gauge&lt;/td>
&lt;td>W&lt;/td>
&lt;td>potencia instantánea por GPU&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>&lt;code>DCGM_FI_DEV_TOTAL_ENERGY_CONSUMPTION&lt;/code>&lt;/td>
&lt;td>counter&lt;/td>
&lt;td>mJ&lt;/td>
&lt;td>energía acumulada desde boot&lt;/td>
&lt;/tr>
&lt;/tbody>
&lt;/table>
&lt;p>Para calcular la energía consumida durante la ventana del experimento (sin la línea de base idle), el harness hace la diferencia del counter antes y después del sweep:&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">&lt;span class="c1"># Despliegue como DaemonSet (fragmento del chart oficial)&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">helm repo add gpu-helm-charts &lt;span class="se">\
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="se">&lt;/span> https://nvidia.github.io/dcgm-exporter/helm-charts
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">helm install dcgm-exporter gpu-helm-charts/dcgm-exporter &lt;span class="se">\
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="se">&lt;/span> --namespace monitoring &lt;span class="se">\
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="se">&lt;/span> --set serviceMonitor.enabled&lt;span class="o">=&lt;/span>&lt;span class="nb">true&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> --set serviceMonitor.interval&lt;span class="o">=&lt;/span>15s
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>El campo &lt;code>DCGM_FI_DEV_TOTAL_ENERGY_CONSUMPTION&lt;/code> (en mJ) permite calcular la energía del experimento con exactitud de contador de hardware, no de estimación:&lt;/p>
&lt;p>$$\text{energía experimento (J)} = \left(\text{counter}&lt;em>\text{fin} - \text{counter}&lt;/em>\text{inicio}\right) \times 10^{-3}$$&lt;/p>
&lt;p>$$\text{Wh/token} = \frac{\text{energía experimento (J)}}{3,600 \times \text{tokens generados}}$$&lt;/p>
&lt;h3 id="kepler--energía-a-nivel-de-pod">Kepler — energía a nivel de pod&lt;/h3>
&lt;p>Kepler (CNCF sandbox, &lt;a href="https://github.com/sustainable-computing-io/kepler">GitHub sustainable-computing-io/kepler&lt;/a>) usa eBPF para estimar el consumo energético a nivel de contenedor y pod, exportando &lt;code>kepler_container_joules_total&lt;/code> a Prometheus (&lt;a href="https://next.redhat.com/2023/08/22/introducing-kepler-efficient-power-monitoring-for-kubernetes/">Red Hat Emerging Technologies&lt;/a>). Combina RAPL (CPU/DRAM), NVML (GPU) y modelos de regresión cuando no hay sensores disponibles.&lt;/p>
&lt;p>En el harness, Kepler complementa a DCGM: DCGM da la medición de hardware de la GPU (más precisa para cargas GPU-intensivas como la inferencia LLM), mientras Kepler atribuye la energía al pod de vLLM específico (incluida la contribución de CPU del nodo). La métrica principal que consume el harness:&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-promql" data-lang="promql">&lt;span class="line">&lt;span class="cl">&lt;span class="c1"># Energía del pod vllm-svc durante el experimento (J)&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w">&lt;/span>&lt;span class="kr">increase&lt;/span>&lt;span class="o">(&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="nv">kepler_container_joules_total&lt;/span>&lt;span class="p">{&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="nl">container_namespace&lt;/span>&lt;span class="o">=&lt;/span>&lt;span class="p">&amp;#34;&lt;/span>&lt;span class="s">inference&lt;/span>&lt;span class="p">&amp;#34;,&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="nl">container_name&lt;/span>&lt;span class="o">=&lt;/span>&lt;span class="p">&amp;#34;&lt;/span>&lt;span class="s">vllm&lt;/span>&lt;span class="p">&amp;#34;&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="p">}[&lt;/span>&lt;span class="err">${BENCH_DURATION}&lt;/span>&lt;span class="p">]&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w">&lt;/span>&lt;span class="o">)&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>El análisis comparativo de DCGM vs Kepler vs Zeus está en &lt;a href="https://blog.lo0.es/posts/herramientas-energia-deploy-precision-overhead/">Herramientas de energía: deploy, precisión y overhead&lt;/a>.&lt;/p>
&lt;h3 id="referencia-mlperf-power">Referencia MLPerf Power&lt;/h3>
&lt;p>MLPerf Power (&lt;a href="https://mlcommons.org/benchmarks/inference-datacenter/">MLCommons&lt;/a>, paper IEEE HPCA 2025) establece el protocolo de medición de eficiencia energética de sistemas ML con medición externa de alta precisión. El banco propio &lt;strong>no es un submission MLPerf&lt;/strong> (requiere vatímetros externos y revisión por comité), pero sus resultados publicados son la referencia de calibración: si el harness da un resultado del mismo orden que el submission MLPerf del mismo hardware con el mismo modelo, la medición es plausible. Si difiere más de un factor 2, hay un problema metodológico. Véase el post &lt;a href="https://blog.lo0.es/posts/mlperf-power-eficiencia-energetica/">MLPerf Power: eficiencia energética&lt;/a> para los datos de referencia actuales.&lt;/p>
&lt;hr>
&lt;h2 id="unificación-de-métricas-las-queries-promql">Unificación de métricas: las queries PromQL&lt;/h2>
&lt;p>El &lt;code>scorecard-exporter&lt;/code> es el contenedor del Job que, al terminar GuideLLM y AIPerf, recoge todas las métricas de Prometheus y construye el JSON de la fila del scorecard. Las queries clave:&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-promql" data-lang="promql">&lt;span class="line">&lt;span class="cl">&lt;span class="c1"># Potencia media GPU durante el sweep (W) — nodo 4×H100&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w">&lt;/span>&lt;span class="kr">avg_over_time&lt;/span>&lt;span class="o">(&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="k">sum&lt;/span>&lt;span class="o">(&lt;/span>&lt;span class="nv">DCGM_FI_DEV_POWER_USAGE&lt;/span>&lt;span class="p">{&lt;/span>&lt;span class="nl">Hostname&lt;/span>&lt;span class="o">=~&lt;/span>&lt;span class="p">&amp;#34;&lt;/span>&lt;span class="s">gpu-node-.*&lt;/span>&lt;span class="p">&amp;#34;}&lt;/span>&lt;span class="o">)&lt;/span>&lt;span class="p">[&lt;/span>&lt;span class="err">${BENCH_DURATION}:&lt;/span>&lt;span class="s">15s&lt;/span>&lt;span class="p">]&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w">&lt;/span>&lt;span class="o">)&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w">&lt;/span>&lt;span class="c1"># Energía total GPU durante el sweep (mJ → convertir a J)&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w">&lt;/span>&lt;span class="o">(&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="k">sum&lt;/span>&lt;span class="o">(&lt;/span>&lt;span class="nv">DCGM_FI_DEV_TOTAL_ENERGY_CONSUMPTION&lt;/span>&lt;span class="p">{&lt;/span>&lt;span class="nl">Hostname&lt;/span>&lt;span class="o">=~&lt;/span>&lt;span class="p">&amp;#34;&lt;/span>&lt;span class="s">gpu-node-.*&lt;/span>&lt;span class="p">&amp;#34;}&lt;/span>&lt;span class="o">)&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="k">offset&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="mi">0&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="o">-&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="k">sum&lt;/span>&lt;span class="o">(&lt;/span>&lt;span class="nv">DCGM_FI_DEV_TOTAL_ENERGY_CONSUMPTION&lt;/span>&lt;span class="p">{&lt;/span>&lt;span class="nl">Hostname&lt;/span>&lt;span class="o">=~&lt;/span>&lt;span class="p">&amp;#34;&lt;/span>&lt;span class="s">gpu-node-.*&lt;/span>&lt;span class="p">&amp;#34;}&lt;/span>&lt;span class="o">)&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="k">offset&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="err">$&lt;/span>&lt;span class="p">{&lt;/span>&lt;span class="err">BENCH_DURATION&lt;/span>&lt;span class="p">}&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w">&lt;/span>&lt;span class="o">)&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="o">*&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="mf">0.001&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w">&lt;/span>&lt;span class="c1"># Tokens generados durante el sweep (desde LiteLLM)&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w">&lt;/span>&lt;span class="kr">increase&lt;/span>&lt;span class="o">(&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="nv">litellm_request_total_tokens&lt;/span>&lt;span class="p">{&lt;/span>&lt;span class="nl">model&lt;/span>&lt;span class="o">=&lt;/span>&lt;span class="p">&amp;#34;&lt;/span>&lt;span class="s">llama3-70b-fp8&lt;/span>&lt;span class="p">&amp;#34;,&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="nl">token_type&lt;/span>&lt;span class="o">=&lt;/span>&lt;span class="p">&amp;#34;&lt;/span>&lt;span class="s">completion&lt;/span>&lt;span class="p">&amp;#34;}[&lt;/span>&lt;span class="err">${BENCH_DURATION}&lt;/span>&lt;span class="p">]&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w">&lt;/span>&lt;span class="o">)&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w">&lt;/span>&lt;span class="c1"># Coste del namespace inference en la ventana (desde OpenCost API)&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w">&lt;/span>&lt;span class="c1"># → llamada REST al iniciar y al finalizar el Job, diferencia de accrual&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>La variable &lt;code>${BENCH_DURATION}&lt;/code> es la duración real del sweep (en formato Prometheus, ej. &lt;code>22m&lt;/code>), que el exporter calcula como &lt;code>end_ts - start_ts&lt;/code> y sustituye en todas las queries.&lt;/p>
&lt;p>El JSON de salida de cada corrida tiene esta estructura:&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;run_id&amp;#34;&lt;/span>&lt;span class="p">:&lt;/span> &lt;span class="s2">&amp;#34;20260616T1200&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;metadata&amp;#34;&lt;/span>&lt;span class="p">:&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;meta-llama/Meta-Llama-3-70B-Instruct&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;engine&amp;#34;&lt;/span>&lt;span class="p">:&lt;/span> &lt;span class="s2">&amp;#34;vllm-0.9.1&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;precision&amp;#34;&lt;/span>&lt;span class="p">:&lt;/span> &lt;span class="s2">&amp;#34;fp8&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;4xH100-SXM-80GB&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;isl_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>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nt">&amp;#34;osl_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>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nt">&amp;#34;tokenizer&amp;#34;&lt;/span>&lt;span class="p">:&lt;/span> &lt;span class="s2">&amp;#34;meta-llama-3-tokenizer-v3&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;slo_ttft_p99_ms&amp;#34;&lt;/span>&lt;span class="p">:&lt;/span> &lt;span class="mi">500&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;slo_itl_p95_ms&amp;#34;&lt;/span>&lt;span class="p">:&lt;/span> &lt;span class="mi">50&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;bench_tool_guidellm&amp;#34;&lt;/span>&lt;span class="p">:&lt;/span> &lt;span class="s2">&amp;#34;0.4.2&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;bench_tool_aiperf&amp;#34;&lt;/span>&lt;span class="p">:&lt;/span> &lt;span class="s2">&amp;#34;0.2.0&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;dcgm_exporter&amp;#34;&lt;/span>&lt;span class="p">:&lt;/span> &lt;span class="s2">&amp;#34;3.3.9&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;kepler&amp;#34;&lt;/span>&lt;span class="p">:&lt;/span> &lt;span class="s2">&amp;#34;0.10.2&amp;#34;&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;span class="line">&lt;span class="cl"> &lt;span class="nt">&amp;#34;performance&amp;#34;&lt;/span>&lt;span class="p">:&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;goodput_tok_s&amp;#34;&lt;/span>&lt;span class="p">:&lt;/span> &lt;span class="mi">3120&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;ttft_p99_ms&amp;#34;&lt;/span>&lt;span class="p">:&lt;/span> &lt;span class="mi">487&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_p95_ms&amp;#34;&lt;/span>&lt;span class="p">:&lt;/span> &lt;span class="mi">42&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;throughput_peak_tok_s&amp;#34;&lt;/span>&lt;span class="p">:&lt;/span> &lt;span class="mi">3890&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;elbow_concurrency&amp;#34;&lt;/span>&lt;span class="p">:&lt;/span> &lt;span class="mi">28&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;span class="line">&lt;span class="cl"> &lt;span class="nt">&amp;#34;cost&amp;#34;&lt;/span>&lt;span class="p">:&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;cpm_eur_1m&amp;#34;&lt;/span>&lt;span class="p">:&lt;/span> &lt;span class="mf">0.97&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;gpu_cost_eur_h&amp;#34;&lt;/span>&lt;span class="p">:&lt;/span> &lt;span class="mf">10.8&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;cost_window_eur&amp;#34;&lt;/span>&lt;span class="p">:&lt;/span> &lt;span class="mf">3.24&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;span class="line">&lt;span class="cl"> &lt;span class="nt">&amp;#34;energy&amp;#34;&lt;/span>&lt;span class="p">:&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;wh_per_token&amp;#34;&lt;/span>&lt;span class="p">:&lt;/span> &lt;span class="mf">0.00044&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;j_per_token&amp;#34;&lt;/span>&lt;span class="p">:&lt;/span> &lt;span class="mf">1.58&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;power_mean_w&amp;#34;&lt;/span>&lt;span class="p">:&lt;/span> &lt;span class="mi">4924&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;energy_total_kwh&amp;#34;&lt;/span>&lt;span class="p">:&lt;/span> &lt;span class="mf">0.287&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;pue&amp;#34;&lt;/span>&lt;span class="p">:&lt;/span> &lt;span class="mf">1.4&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;wh_per_token_pue_adjusted&amp;#34;&lt;/span>&lt;span class="p">:&lt;/span> &lt;span class="mf">0.000616&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;span class="line">&lt;span class="cl"> &lt;span class="nt">&amp;#34;carbon&amp;#34;&lt;/span>&lt;span class="p">:&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;grid_intensity_gco2_kwh&amp;#34;&lt;/span>&lt;span class="p">:&lt;/span> &lt;span class="mi">40&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;co2_per_1m_tokens_g&amp;#34;&lt;/span>&lt;span class="p">:&lt;/span> &lt;span class="mf">24.6&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;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>Todos los campos son calculados a partir de las mismas ventanas temporales. El JSON se versiona en git junto al código del harness. La reproducción de cualquier fila del scorecard es: &lt;code>git checkout &amp;lt;run-id&amp;gt; &amp;amp;&amp;amp; kubectl apply -f job.yaml&lt;/code>.&lt;/p>
&lt;hr>
&lt;h2 id="el-scorecard-de-3-ejes-tabla-e-interpretación">El scorecard de 3 ejes: tabla e interpretación&lt;/h2>
&lt;p>La siguiente tabla ilustra cómo se comparan cinco configuraciones del mismo modelo (Llama 3 70B) sobre el mismo nodo de referencia (4×H100 SXM) con el harness. Las cifras son &lt;strong>ilustrativas de orden de magnitud&lt;/strong> (el banco las rellena con mediciones reales):&lt;/p>
&lt;table>
&lt;thead>
&lt;tr>
&lt;th>Config&lt;/th>
&lt;th>Motor&lt;/th>
&lt;th>Precisión&lt;/th>
&lt;th>CPM (€/1M)&lt;/th>
&lt;th>Goodput (tok/s)&lt;/th>
&lt;th>Wh/tok&lt;/th>
&lt;th>TTFT P99 (ms)&lt;/th>
&lt;th>ITL P95 (ms)&lt;/th>
&lt;th>CO2 /1M (g, FR)&lt;/th>
&lt;/tr>
&lt;/thead>
&lt;tbody>
&lt;tr>
&lt;td>&lt;strong>A&lt;/strong>&lt;/td>
&lt;td>vLLM 0.9&lt;/td>
&lt;td>FP16&lt;/td>
&lt;td>1,64&lt;/td>
&lt;td>1.890&lt;/td>
&lt;td>0,00074&lt;/td>
&lt;td>498&lt;/td>
&lt;td>49&lt;/td>
&lt;td>29,6&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>&lt;strong>B&lt;/strong>&lt;/td>
&lt;td>vLLM 0.9&lt;/td>
&lt;td>FP8&lt;/td>
&lt;td>0,97&lt;/td>
&lt;td>3.120&lt;/td>
&lt;td>0,00044&lt;/td>
&lt;td>487&lt;/td>
&lt;td>42&lt;/td>
&lt;td>17,6&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>&lt;strong>C&lt;/strong>&lt;/td>
&lt;td>SGLang 0.4&lt;/td>
&lt;td>FP8&lt;/td>
&lt;td>0,88&lt;/td>
&lt;td>3.410&lt;/td>
&lt;td>0,00041&lt;/td>
&lt;td>421&lt;/td>
&lt;td>38&lt;/td>
&lt;td>16,4&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>&lt;strong>D&lt;/strong>&lt;/td>
&lt;td>vLLM 0.9&lt;/td>
&lt;td>FP8, ISL 512&lt;/td>
&lt;td>1,31&lt;/td>
&lt;td>2.240&lt;/td>
&lt;td>0,00056&lt;/td>
&lt;td>294&lt;/td>
&lt;td>31&lt;/td>
&lt;td>22,4&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>&lt;strong>E&lt;/strong>&lt;/td>
&lt;td>vLLM 0.9&lt;/td>
&lt;td>FP8, max-batch 128&lt;/td>
&lt;td>0,84&lt;/td>
&lt;td>3.590&lt;/td>
&lt;td>0,00040&lt;/td>
&lt;td>721&lt;/td>
&lt;td>55&lt;/td>
&lt;td>16,0&lt;/td>
&lt;/tr>
&lt;/tbody>
&lt;/table>
&lt;p>Cómo se lee la tabla:&lt;/p>
&lt;ul>
&lt;li>&lt;strong>B vs A&lt;/strong>: FP8 sobre FP16 reduce el CPM un 41 % y la energía por token un 41 % por el mismo throughput más alto. Ambos cumplen el SLO (TTFT P99 &amp;lt; 500 ms, ITL P95 &amp;lt; 50 ms). B domina a A en los tres ejes: es Pareto-superior.&lt;/li>
&lt;li>&lt;strong>C vs B&lt;/strong>: SGLang da goodput un 9 % mayor y CPM un 9 % menor con FP8. Ambos cumplen el SLO. C es Pareto-superior a B (si el motor es indiferente para el stack).&lt;/li>
&lt;li>&lt;strong>D vs B&lt;/strong>: ISL más corto (512 tok) baja la latencia (TTFT P99 294 ms vs 487 ms) pero baja el goodput y sube el CPM. Si el caso de uso exige TTFT &amp;lt; 300 ms, D es el candidato; si TTFT &amp;lt; 500 ms es suficiente, B es mejor en coste y energía.&lt;/li>
&lt;li>&lt;strong>E vs C&lt;/strong>: &lt;code>max-batch 128&lt;/code> sube el goodput (+5 %) y baja el CPM, pero el TTFT P99 rompe el SLO (721 ms &amp;gt; 500 ms) y el ITL P95 también (55 ms &amp;gt; 50 ms). E tiene el mejor throughput bruto &lt;strong>pero está fuera del SLO&lt;/strong>: no es un candidato válido para el caso de uso de chat interactivo.&lt;/li>
&lt;/ul>
&lt;p>La &lt;strong>frontera de Pareto&lt;/strong> del scorecard, bajo el SLO declarado, incluye solo A, B, C y D (E queda fuera por SLO roto). De esas cuatro, C domina a B que domina a A. D solo entra en la frontera si el caso de uso exige TTFT P99 &amp;lt; 300 ms. La decisión no es un número; es la fila entera más el SLO.&lt;/p>
&lt;hr>
&lt;h2 id="la-frontera-de-pareto-multi-objetivo">La frontera de Pareto multi-objetivo&lt;/h2>
&lt;p>Con tres métricas de minimización —CPM (€/1M tok), Wh/tok y TTFT P99 (ms)— la frontera de Pareto se define como el conjunto de configuraciones donde ninguna domina a otra en los tres ejes simultáneamente. Formalmente, la config ( i ) &lt;strong>domina&lt;/strong> a la config ( j ) si:&lt;/p>
&lt;p>$$\text{CPM}_i \leq \text{CPM}_j ;\land; \text{Wh/tok}&lt;em>i \leq \text{Wh/tok}&lt;em>j ;\land; \text{TTFT}&lt;/em>{i} \leq \text{TTFT}&lt;/em>{j}$$&lt;/p>
&lt;p>con al menos una desigualdad estricta. El scorecard permite calcularla sobre el conjunto de corridas con una operación vectorial sencilla. En el ejemplo de la tabla, la frontera bajo SLO (con E excluido) es ({C, D}): C domina en coste/energía/goodput; D domina en latencia. Entre C y D la elección depende del SLO de TTFT del caso de uso concreto.&lt;/p>
&lt;div class="diagram" style="max-width:700px;margin:1rem auto;">
&lt;svg viewBox="0 0 700 320" role="img" aria-label="Frontera de Pareto en dos ejes: CPM EUR por millon de tokens en eje Y y goodput tok por s en eje X; los puntos A B C D E marcados; E excluido por SLO roto; la curva de Pareto pasa por C y D" xmlns="http://www.w3.org/2000/svg">
&lt;style>.ax{fill:none;stroke:currentColor;stroke-width:1}.ts{font:11px sans-serif;fill:currentColor}.tl{font:600 12px sans-serif;fill:currentColor}.pt{fill:currentColor}.dsh{fill:none;stroke:currentColor;stroke-width:1;stroke-dasharray:4 3}&lt;/style>
&lt;line class="ax" x1="70" y1="30" x2="70" y2="270"/>
&lt;line class="ax" x1="70" y1="270" x2="660" y2="270"/>
&lt;text x="10" y="155" class="ts" transform="rotate(-90 10 155)">CPM (EUR/1M tok) ↑ peor&lt;/text>
&lt;text x="320" y="300" class="ts">goodput (tok/s) → mejor&lt;/text>
&lt;text x="70" y="26" class="ts">1,70&lt;/text>
&lt;text x="70" y="80" class="ts">1,40&lt;/text>
&lt;text x="70" y="134" class="ts">1,10&lt;/text>
&lt;text x="70" y="188" class="ts">0,90&lt;/text>
&lt;text x="70" y="242" class="ts">0,84&lt;/text>
&lt;text x="115" y="284" class="ts">1.800&lt;/text>
&lt;text x="263" y="284" class="ts">2.200&lt;/text>
&lt;text x="410" y="284" class="ts">3.100&lt;/text>
&lt;text x="520" y="284" class="ts">3.400&lt;/text>
&lt;text x="600" y="284" class="ts">3.600&lt;/text>
&lt;circle class="pt" cx="130" cy="55" r="5"/>
&lt;text x="140" y="52" class="tl">A (FP16)&lt;/text>
&lt;circle class="pt" cx="380" cy="170" r="5"/>
&lt;text x="390" y="167" class="tl">B (FP8)&lt;/text>
&lt;circle class="pt" cx="500" cy="152" r="5"/>
&lt;text x="510" y="148" class="tl">C (SGLang FP8) ★&lt;/text>
&lt;circle class="pt" cx="270" cy="195" r="5"/>
&lt;text x="280" y="192" class="tl">D (ISL 512)&lt;/text>
&lt;circle class="pt" cx="570" cy="138" r="4" opacity="0.4"/>
&lt;text x="580" y="135" class="ts" opacity="0.4">E (SLO roto)&lt;/text>
&lt;path class="dsh" d="M130,55 L270,195 L500,152"/>
&lt;text x="190" y="148" class="ts">frontera Pareto&lt;/text>
&lt;text x="72" y="286" class="ts">(A domina solo en latencia; C+D en la frontera bajo SLO)&lt;/text>
&lt;/svg>
&lt;/div>
&lt;hr>
&lt;h2 id="diseño-reproducible-los-doce-metadatos-obligatorios">Diseño reproducible: los doce metadatos obligatorios&lt;/h2>
&lt;p>El checklist que cierra el dossier de sesgo (&lt;a href="https://blog.lo0.es/posts/sesgo-medicion-reproducibilidad-bench/">Sesgo y reproducibilidad&lt;/a>). Para que el JSON del harness sea auditable, los doce campos deben estar presentes en cada corrida:&lt;/p>
&lt;table>
&lt;thead>
&lt;tr>
&lt;th>#&lt;/th>
&lt;th>Campo&lt;/th>
&lt;th>Ejemplo&lt;/th>
&lt;th>Por qué es obligatorio&lt;/th>
&lt;/tr>
&lt;/thead>
&lt;tbody>
&lt;tr>
&lt;td>1&lt;/td>
&lt;td>Versión del motor de serving&lt;/td>
&lt;td>&lt;code>vllm-0.9.1&lt;/code>&lt;/td>
&lt;td>el mismo modelo puede rendir diferente en distintas versiones&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>2&lt;/td>
&lt;td>Versión del modelo (commit/tag)&lt;/td>
&lt;td>&lt;code>meta-llama/Meta-Llama-3-70B-Instruct@sha256:abc&lt;/code>&lt;/td>
&lt;td>evita ambigüedad entre variantes del mismo nombre&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>3&lt;/td>
&lt;td>Precisión&lt;/td>
&lt;td>&lt;code>fp8&lt;/code>&lt;/td>
&lt;td>FP16 vs FP8 cambia throughput y latencia&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>4&lt;/td>
&lt;td>Tokenizador y versión&lt;/td>
&lt;td>&lt;code>meta-llama-3-tokenizer-v3&lt;/code>&lt;/td>
&lt;td>el ISL/OSL en tokens depende del tokenizador&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>5&lt;/td>
&lt;td>ISL (input sequence length)&lt;/td>
&lt;td>&lt;code>1024&lt;/code> tok&lt;/td>
&lt;td>cambia el tiempo de prefill y el codo&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>6&lt;/td>
&lt;td>OSL (output sequence length)&lt;/td>
&lt;td>&lt;code>256&lt;/code> tok&lt;/td>
&lt;td>cambia el tiempo de decode&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>7&lt;/td>
&lt;td>Hardware (modelo y cantidad)&lt;/td>
&lt;td>&lt;code>4×H100-SXM-80GB&lt;/code>&lt;/td>
&lt;td>base de cualquier comparación&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>8&lt;/td>
&lt;td>Concurrencia máxima probada&lt;/td>
&lt;td>&lt;code>32&lt;/code>&lt;/td>
&lt;td>define el rango del sweep&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>9&lt;/td>
&lt;td>Versión de la herramienta de bench&lt;/td>
&lt;td>&lt;code>guidellm-0.4.2&lt;/code>, &lt;code>aiperf-0.2.0&lt;/code>&lt;/td>
&lt;td>distintas versiones pueden dar métricas distintas&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>10&lt;/td>
&lt;td>SLO declarado&lt;/td>
&lt;td>&lt;code>TTFT P99 &amp;lt; 500 ms, ITL P95 &amp;lt; 50 ms&lt;/code>&lt;/td>
&lt;td>el codo depende del SLO; sin él no hay goodput&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>11&lt;/td>
&lt;td>Duración de warmup&lt;/td>
&lt;td>&lt;code>60 s&lt;/code>&lt;/td>
&lt;td>sin warmup el KV cache está frío y las primeras rondas son sesgadas&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>12&lt;/td>
&lt;td>Versión del harness / run-id&lt;/td>
&lt;td>&lt;code>20260616T1200&lt;/code>&lt;/td>
&lt;td>identifica la corrida para reproducirla con &lt;code>git checkout&lt;/code>&lt;/td>
&lt;/tr>
&lt;/tbody>
&lt;/table>
&lt;p>Un scorecard sin cualquiera de estos doce campos es una anécdota. Con los doce, es un dato auditable. El run-id en el nombre del Job (&lt;code>bench-llama3-70b-fp8-h100x4-20260616&lt;/code>) incorpora implícitamente los campos 1-3 y 7, forzando que el nombre del Job cambie con cada variante — lo que hace imposible solapar corridas en el mismo namespace.&lt;/p>
&lt;hr>
&lt;h2 id="idempotencia-y-versionado-de-resultados">Idempotencia y versionado de resultados&lt;/h2>
&lt;p>El Job de Kubernetes es idempotente: si se vuelve a aplicar el mismo manifiesto (mismo nombre), el Job ya existe y Kubernetes no lo relanza (política &lt;code>backoffLimit: 0&lt;/code>). Esto evita corridas accidentales duplicadas. Para relanzar, hay que borrar el Job (&lt;code>kubectl delete job &amp;lt;nombre&amp;gt;&lt;/code>) o cambiar el &lt;code>run-id&lt;/code>.&lt;/p>
&lt;p>Los resultados se versionan con la siguiente convención de directorios en el repositorio git del harness:&lt;/p>
&lt;pre tabindex="0">&lt;code>results/
20260616T1200/
guidellm-20260616T1200.json
aiperf-20260616T1200.json
scorecard-20260616T1200.json
20260617T0900/
guidellm-20260617T0900.json
aiperf-20260617T0900.json
scorecard-20260617T0900.json
scorecard-aggregate.csv # todas las filas, para análisis y gráficas
&lt;/code>&lt;/pre>&lt;p>El &lt;code>scorecard-aggregate.csv&lt;/code> es la tabla del scorecard en formato plano: cada fila es una corrida, cada columna un campo del JSON. Se genera con:&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">&lt;span class="c1"># Genera el CSV agregado a partir de todos los JSON en results/&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">python scripts/aggregate_scorecards.py results/ &amp;gt; scorecard-aggregate.csv
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>El CSV es lo que entra en las gráficas de Pareto, en los informes y en las decisiones de arquitectura. Está versionado en git; su diff es el cambio de configuración.&lt;/p>
&lt;hr>
&lt;h2 id="cómo-se-conecta-con-los-otros-posts-de-la-serie">Cómo se conecta con los otros posts de la serie&lt;/h2>
&lt;p>El harness no es un artículo autónomo: es la síntesis de los veintiocho artículos. Cada herramienta tiene su deep dive:&lt;/p>
&lt;ul>
&lt;li>&lt;strong>Rendimiento&lt;/strong>: &lt;a href="https://blog.lo0.es/posts/guidellm-validacion-slo-bajo-carga/">GuideLLM a fondo&lt;/a> — sweep, SLO, goodput, codo; &lt;a href="https://blog.lo0.es/posts/nvidia-genai-perf-a-fondo/">GenAI-Perf / AIPerf a fondo&lt;/a> — concurrencia, rate sweep, métricas de distribución.&lt;/li>
&lt;li>&lt;strong>Coste&lt;/strong>: &lt;a href="https://blog.lo0.es/posts/opencost-cost-allocation-kubernetes/">OpenCost: cost allocation en Kubernetes&lt;/a> — asignación por namespace/label, precios custom on-prem.&lt;/li>
&lt;li>&lt;strong>Energía&lt;/strong>: &lt;a href="https://blog.lo0.es/posts/herramientas-energia-deploy-precision-overhead/">Herramientas de energía: deploy, precisión y overhead&lt;/a> — DCGM vs Kepler vs Zeus, overhead de instrumentación, precisión relativa; &lt;a href="https://blog.lo0.es/posts/mlperf-power-eficiencia-energetica/">MLPerf Power: eficiencia energética&lt;/a> — la referencia externa de calibración.&lt;/li>
&lt;li>&lt;strong>Marco teórico&lt;/strong>: &lt;a href="https://blog.lo0.es/posts/tres-ejes-coste-rendimiento-energia-inferencia-llm/">Los tres ejes: coste, rendimiento y energía&lt;/a> — la identidad CPM/energía/throughput que justifica medir juntos.&lt;/li>
&lt;li>&lt;strong>Sesgo&lt;/strong>: &lt;a href="https://blog.lo0.es/posts/sesgo-medicion-reproducibilidad-bench/">Sesgo y reproducibilidad en el benchmarking&lt;/a> — los doce sesgos que el harness elimina.&lt;/li>
&lt;/ul>
&lt;p>El harness cierra el dossier de la serie porque hace posible la promesa del artículo de apertura: &amp;ldquo;cuando alguien rebata un número de la propuesta, la respuesta no es &amp;rsquo;lo dice un blog&amp;rsquo;, sino &amp;rsquo;este es el banco, esta la metodología, reprodúcelo&amp;rsquo;&amp;rdquo;. Con el run-id y el repo git, reproducir es un comando.&lt;/p>
&lt;hr>
&lt;h2 id="flujo-completo-de-una-sesión-de-benchmarking">Flujo completo de una sesión de benchmarking&lt;/h2>
&lt;p>El procedimiento operativo estándar del harness, de principio a fin:&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">&lt;span class="c1"># 1. Asegurarse de que el endpoint de inferencia está activo&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">kubectl get svc vllm-svc -n inference
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="c1"># 2. Verificar que DCGM y Kepler están scrapeando&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">curl -s http://prometheus.monitoring.svc.cluster.local:9090/api/v1/query &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-urlencode &lt;span class="s1">&amp;#39;query=DCGM_FI_DEV_POWER_USAGE&amp;#39;&lt;/span> &lt;span class="p">|&lt;/span> jq &lt;span class="s1">&amp;#39;.data.result | length&amp;#39;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="c1"># 3. Registrar el counter de energía ANTES del experimento&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="nv">PRE_ENERGY&lt;/span>&lt;span class="o">=&lt;/span>&lt;span class="k">$(&lt;/span>kubectl &lt;span class="nb">exec&lt;/span> -n monitoring dcgm-exporter-xxx -- &lt;span class="se">\
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="se">&lt;/span> curl -s localhost:9400/metrics &lt;span class="se">\
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="se">&lt;/span> &lt;span class="p">|&lt;/span> grep DCGM_FI_DEV_TOTAL_ENERGY_CONSUMPTION &lt;span class="p">|&lt;/span> awk &lt;span class="s1">&amp;#39;{sum+=$2} END{print sum}&amp;#39;&lt;/span>&lt;span class="k">)&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="c1"># 4. Lanzar el Job (nombre único por corrida)&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">kubectl apply -f jobs/bench-llama3-70b-fp8-h100x4-20260616.yaml
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="c1"># 5. Esperar a que termine&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">kubectl &lt;span class="nb">wait&lt;/span> --for&lt;span class="o">=&lt;/span>&lt;span class="nv">condition&lt;/span>&lt;span class="o">=&lt;/span>&lt;span class="nb">complete&lt;/span> job/bench-llama3-70b-fp8-h100x4-20260616 &lt;span class="se">\
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="se">&lt;/span> -n benchmark --timeout&lt;span class="o">=&lt;/span>3600s
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="c1"># 6. Registrar el counter de energía DESPUÉS&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="nv">POST_ENERGY&lt;/span>&lt;span class="o">=&lt;/span>&lt;span class="k">$(&lt;/span>kubectl &lt;span class="nb">exec&lt;/span> -n monitoring dcgm-exporter-xxx -- &lt;span class="se">\
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="se">&lt;/span> curl -s localhost:9400/metrics &lt;span class="se">\
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="se">&lt;/span> &lt;span class="p">|&lt;/span> grep DCGM_FI_DEV_TOTAL_ENERGY_CONSUMPTION &lt;span class="p">|&lt;/span> awk &lt;span class="s1">&amp;#39;{sum+=$2} END{print sum}&amp;#39;&lt;/span>&lt;span class="k">)&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="c1"># 7. Recoger los resultados del volumen&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">kubectl cp benchmark/bench-run-pod:/results/ ./results/20260616T1200/
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="c1"># 8. Calcular la energía del experimento (mJ → kWh)&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">python scripts/calc_energy.py &lt;span class="se">\
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="se">&lt;/span> --pre &lt;span class="nv">$PRE_ENERGY&lt;/span> --post &lt;span class="nv">$POST_ENERGY&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> --run-id 20260616T1200
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="c1"># 9. Añadir fila al CSV agregado&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">python scripts/aggregate_scorecards.py results/ &amp;gt; scorecard-aggregate.csv
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="c1"># 10. Versionar&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">git add results/20260616T1200/ scorecard-aggregate.csv
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">git commit -m &lt;span class="s2">&amp;#34;bench: llama3-70b fp8 4xH100 ISL1024 OSL256 (20260616T1200)&amp;#34;&lt;/span>
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>Los pasos 6-9 son candidatos a automatizarse como un segundo Job de post-procesado que se lanza al completarse el principal (con &lt;code>ownerReferences&lt;/code> o un CronJob de limpieza). En el estado básico del harness, los pasos manuales son precisamente los que fuerzan la revisión del resultado antes de versionarlo.&lt;/p>
&lt;hr>
&lt;h2 id="coste-del-experimento">Coste del experimento&lt;/h2>
&lt;p>Un sweep de GuideLLM de 10 rondas a 120 segundos por ronda ocupa el nodo ~22-26 minutos. El perfil de AIPerf a 4 concurrencias (200 peticiones cada una) añade ~8-12 minutos. Total por corrida: ~35-40 minutos de nodo 4×H100.&lt;/p>
&lt;p>A coste amortizado de referencia (~10,8 €/h para 4×H100 on-prem):&lt;/p>
&lt;p>$$\text{coste por corrida} \approx 10{,}8 \times \frac{38}{60} \approx 6{,}84 \text{ EUR}$$&lt;/p>
&lt;p>A precio cloud europeo de referencia (4 × 2,73 = 10,92 €/h):&lt;/p>
&lt;p>$$\text{coste por corrida (cloud)} \approx 10{,}92 \times \frac{38}{60} \approx 6{,}92 \text{ EUR}$$&lt;/p>
&lt;p>Un programa de benchmarking continuo (10 configuraciones × 3 modelos × 2 sweeps/semana) suma ~415 EUR/semana. Por eso el harness incluye sweeps cortos (5 rondas, ~15 minutos) para CI y sweeps completos para releases.&lt;/p>
&lt;hr>
&lt;h2 id="lo-que-el-harness-no-mide-y-por-qué">Lo que el harness no mide (y por qué)&lt;/h2>
&lt;table>
&lt;thead>
&lt;tr>
&lt;th>Dimensión ausente&lt;/th>
&lt;th>Motivo&lt;/th>
&lt;th>Dónde se cubre&lt;/th>
&lt;/tr>
&lt;/thead>
&lt;tbody>
&lt;tr>
&lt;td>Calidad del output (MMLU, HumanEval)&lt;/td>
&lt;td>requiere banco de evaluación de calidad, no de serving&lt;/td>
&lt;td>&lt;a href="https://blog.lo0.es/posts/tres-ejes-coste-rendimiento-energia-inferencia-llm/">Benchmarks de calidad LLM&lt;/a>&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>Throughput de training&lt;/td>
&lt;td>el harness es exclusivamente para inferencia&lt;/td>
&lt;td>fuera del scope de la serie &amp;ldquo;datos&amp;rdquo;&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>Coste de red y almacenamiento&lt;/td>
&lt;td>OpenCost puede atribuirlo, pero requiere configuración adicional&lt;/td>
&lt;td>&lt;a href="https://blog.lo0.es/posts/opencost-cost-allocation-kubernetes/">OpenCost: cost allocation&lt;/a>&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>Intensidad de carbono horaria&lt;/td>
&lt;td>usar ElectricityMaps API + la energía medida&lt;/td>
&lt;td>&lt;a href="https://blog.lo0.es/posts/tres-ejes-coste-rendimiento-energia-inferencia-llm/">Del vatio al carbono: PUE y mix de red&lt;/a>&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>Latencia de red cliente-servidor&lt;/td>
&lt;td>el Job corre dentro del cluster; latencia WAN requiere cliente externo&lt;/td>
&lt;td>documentar como metadato adicional&lt;/td>
&lt;/tr>
&lt;/tbody>
&lt;/table>
&lt;hr>
&lt;h2 id="fuentes">Fuentes&lt;/h2>
&lt;ul>
&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>GuideLLM · PyPI — &lt;a href="https://pypi.org/project/guidellm/">https://pypi.org/project/guidellm/&lt;/a>&lt;/li>
&lt;li>Red Hat Developer · 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>Red Hat Developer · 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>AIPerf · GitHub (ai-dynamo/aiperf) — &lt;a href="https://github.com/ai-dynamo/aiperf">https://github.com/ai-dynamo/aiperf&lt;/a>&lt;/li>
&lt;li>GenAI-Perf · documentación NVIDIA Triton — &lt;a href="https://docs.nvidia.com/deeplearning/triton-inference-server/user-guide/docs/perf_analyzer/genai-perf/README.html">https://docs.nvidia.com/deeplearning/triton-inference-server/user-guide/docs/perf_analyzer/genai-perf/README.html&lt;/a>&lt;/li>
&lt;li>NVIDIA · LLM performance benchmarking con GenAI-Perf — &lt;a href="https://developer.nvidia.com/blog/llm-performance-benchmarking-measuring-nvidia-nim-performance-with-genai-perf">https://developer.nvidia.com/blog/llm-performance-benchmarking-measuring-nvidia-nim-performance-with-genai-perf&lt;/a>&lt;/li>
&lt;li>NVIDIA NIM Benchmarking · métricas (TTFT, ITL, throughput) — &lt;a href="https://docs.nvidia.com/nim/benchmarking/llm/latest/metrics.html">https://docs.nvidia.com/nim/benchmarking/llm/latest/metrics.html&lt;/a>&lt;/li>
&lt;li>OpenCost · web oficial — &lt;a href="https://opencost.io/">https://opencost.io/&lt;/a>&lt;/li>
&lt;li>OpenCost · GitHub (opencost/opencost) — &lt;a href="https://github.com/opencost/opencost">https://github.com/opencost/opencost&lt;/a>&lt;/li>
&lt;li>OpenCost · CNCF blog (sandbox → incubating) — &lt;a href="https://www.cncf.io/blog/2022/12/06/opencost-a-new-cncf-sandbox-project-for-real-time-kubernetes-cost-monitoring/">https://www.cncf.io/blog/2022/12/06/opencost-a-new-cncf-sandbox-project-for-real-time-kubernetes-cost-monitoring/&lt;/a>&lt;/li>
&lt;li>LiteLLM · web oficial — &lt;a href="https://www.litellm.ai/">https://www.litellm.ai/&lt;/a>&lt;/li>
&lt;li>LiteLLM · GitHub (BerriAI/litellm) — &lt;a href="https://github.com/BerriAI/litellm">https://github.com/BerriAI/litellm&lt;/a>&lt;/li>
&lt;li>LiteLLM · Spend Tracking docs — &lt;a href="https://docs.litellm.ai/docs/proxy/cost_tracking">https://docs.litellm.ai/docs/proxy/cost_tracking&lt;/a>&lt;/li>
&lt;li>NVIDIA DCGM Exporter · documentación oficial — &lt;a href="https://docs.nvidia.com/datacenter/dcgm/latest/gpu-telemetry/dcgm-exporter.html">https://docs.nvidia.com/datacenter/dcgm/latest/gpu-telemetry/dcgm-exporter.html&lt;/a>&lt;/li>
&lt;li>NVIDIA DCGM Exporter · GitHub — &lt;a href="https://github.com/NVIDIA/dcgm-exporter">https://github.com/NVIDIA/dcgm-exporter&lt;/a>&lt;/li>
&lt;li>NVIDIA DCGM Exporter · métricas CSV — &lt;a href="https://github.com/NVIDIA/dcgm-exporter/blob/main/etc/dcp-metrics-included.csv">https://github.com/NVIDIA/dcgm-exporter/blob/main/etc/dcp-metrics-included.csv&lt;/a>&lt;/li>
&lt;li>Kepler (Kubernetes-based Efficient Power Level Exporter) · GitHub — &lt;a href="https://github.com/sustainable-computing-io/kepler">https://github.com/sustainable-computing-io/kepler&lt;/a>&lt;/li>
&lt;li>Red Hat Emerging Technologies · Introducing Kepler — &lt;a href="https://next.redhat.com/2023/08/22/introducing-kepler-efficient-power-monitoring-for-kubernetes/">https://next.redhat.com/2023/08/22/introducing-kepler-efficient-power-monitoring-for-kubernetes/&lt;/a>&lt;/li>
&lt;li>MLCommons · MLPerf Inference Datacenter benchmark — &lt;a href="https://mlcommons.org/benchmarks/inference-datacenter/">https://mlcommons.org/benchmarks/inference-datacenter/&lt;/a>&lt;/li>
&lt;li>MLCommons · MLPerf Power presentado en IEEE HPCA 2025 — &lt;a href="https://mlcommons.org/2025/03/ml-commons-power-hpca/">https://mlcommons.org/2025/03/ml-commons-power-hpca/&lt;/a>&lt;/li>
&lt;li>IEEE Xplore · MLPerf Power paper (μWatts to MWatts) — &lt;a href="https://ieeexplore.ieee.org/document/10946778/">https://ieeexplore.ieee.org/document/10946778/&lt;/a>&lt;/li>
&lt;li>MLCommons · MLPerf Inference v5.1 results (septiembre 2025) — &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>Red Hat · Efficient and reproducible LLM inference: MLPerf Inference v5.1 — &lt;a href="https://www.redhat.com/en/blog/efficient-and-reproducible-llm-inference-red-hat-mlperf-inference-v51-results">https://www.redhat.com/en/blog/efficient-and-reproducible-llm-inference-red-hat-mlperf-inference-v51-results&lt;/a>&lt;/li>
&lt;li>BentoML · Beyond tokens-per-second: cost, speed and quality in LLM inference — &lt;a href="https://www.bentoml.com/blog/beyond-tokens-per-second-how-to-balance-speed-cost-and-quality-in-llm-inference">https://www.bentoml.com/blog/beyond-tokens-per-second-how-to-balance-speed-cost-and-quality-in-llm-inference&lt;/a>&lt;/li>
&lt;/ul></description></item><item><title>Sesgo de medición y reproducibilidad: por qué dos benchmarks del mismo modelo dan cifras que difieren hasta 7×</title><link>https://blog.lo0.es/posts/sesgo-medicion-reproducibilidad-bench/</link><pubDate>Tue, 16 Jun 2026 07:00:00 +0200</pubDate><guid>https://blog.lo0.es/posts/sesgo-medicion-reproducibilidad-bench/</guid><description>&lt;blockquote>
&lt;p>Notación: importes en &lt;strong>euros (N €)&lt;/strong>, decimales con coma. El símbolo matemático se
reserva para fórmulas: los importes se expresan en € o USD. Millar con espacio fino
((1,000)).&lt;/p>
&lt;/blockquote>
&lt;h2 id="tldr">TL;DR&lt;/h2>
&lt;p>Correr el mismo modelo Llama-3-70B-Instruct en el mismo nodo de 4×H100 SXM 80 GB con dos
herramientas distintas puede producir resultados de throughput que difieren &lt;strong>7,2×&lt;/strong> sin que
el motor cambie una línea de código. La causa no es el motor: es el &lt;strong>método de medida&lt;/strong>. A
1.000 QPS, un cliente asyncio mono-proceso (vLLM bench, SGLang bench, genai-perf
pre-AIPerf) procesó &lt;strong>75.574 tokens&lt;/strong> frente a los &lt;strong>545.733 tokens&lt;/strong> de un cliente
multi-proceso —ambos midiendo el mismo endpoint—
(&lt;a href="https://arxiv.org/abs/2605.24217">arXiv 2605.24217&lt;/a>). El sesgo no es aleatorio: es
sistemático, direccional y reproducible. Este artículo cataloga las fuentes de sesgo, cuantifica
su magnitud y describe el harness mínimo que convierte un número en un dato auditable.&lt;/p>
&lt;hr>
&lt;h2 id="las-fuentes-de-sesgo-catálogo-con-magnitudes">Las fuentes de sesgo: catálogo con magnitudes&lt;/h2>
&lt;p>Cada fuente de sesgo mueve el número publicado en una dirección determinada y con una
magnitud característica. La tabla siguiente ordena las fuentes de mayor a menor impacto
observado en la literatura:&lt;/p>
&lt;table>
&lt;thead>
&lt;tr>
&lt;th>Fuente de sesgo&lt;/th>
&lt;th>Dirección del sesgo&lt;/th>
&lt;th>Magnitud documentada&lt;/th>
&lt;th>Referencia&lt;/th>
&lt;/tr>
&lt;/thead>
&lt;tbody>
&lt;tr>
&lt;td>&lt;strong>Saturación del cliente mono-proceso&lt;/strong> (GIL Python, asyncio)&lt;/td>
&lt;td>infravalora throughput; sobrevalora latencia&lt;/td>
&lt;td>hasta &lt;strong>7,2×&lt;/strong> menos tokens procesados a 1.000 QPS&lt;/td>
&lt;td>arXiv 2605.24217&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>&lt;strong>Tokenizer distinto al del modelo&lt;/strong> (LLMPerf usa LlamaTokenizer universal)&lt;/td>
&lt;td>tok/s incomparables entre vocabularios distintos&lt;/td>
&lt;td>variable; hasta &lt;strong>33 %&lt;/strong> de overhead de overhead de cliente en denominador del TPS&lt;/td>
&lt;td>NVIDIA blog (TPS = overhead de cliente ÷ duración total)&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>&lt;strong>ignore_eos ausente u OSL inconsistente&lt;/strong>&lt;/td>
&lt;td>subestima throughput; acorta artificialmente OSL real&lt;/td>
&lt;td>hasta terminación prematura; benchmark de longitud fija irreal&lt;/td>
&lt;td>docs vLLM bench; NVIDIA fundamental concepts&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>&lt;strong>Distribución ISL/OSL irreal&lt;/strong> (longitud fija en vez de distribución realista)&lt;/td>
&lt;td>codo optimista que no se parece a producción&lt;/td>
&lt;td>desplazamiento del codo; prefill infravalorado con prompts cortos&lt;/td>
&lt;td>docs AIPerf sequence-length-distributions&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>&lt;strong>Warmup ausente / prefix-cache caliente&lt;/strong>&lt;/td>
&lt;td>sobrevalora TTFT (artificialmente bajo en las primeras peticiones)&lt;/td>
&lt;td>TTFT infra-estimado por KV-cache ya poblado&lt;/td>
&lt;td>vLLM prefix caching docs; arXiv 2605.24217&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>&lt;strong>&lt;code>concurrency&lt;/code> vs &lt;code>request-rate&lt;/code>&lt;/strong> como modo de carga&lt;/td>
&lt;td>concurrency satura simétricamente; request-rate puede acumular cola&lt;/td>
&lt;td>ITL sobreestimado si la cola crece sin techo con request-rate&lt;/td>
&lt;td>NVIDIA fundamentals; AIPerf docs&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>&lt;strong>Ventana de medición demasiado corta&lt;/strong>&lt;/td>
&lt;td>no capta el régimen estacionario; incluye ramp-up&lt;/td>
&lt;td>throughput sobre-estimado; latencia subestimada&lt;/td>
&lt;td>meta-métricas arXiv 2508.10251&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>&lt;strong>Estado térmico y frecuencias de GPU&lt;/strong>&lt;/td>
&lt;td>throttling térmico reduce throughput si no hay estabilización previa&lt;/td>
&lt;td>temperatura &amp;gt; 75 °C dispara throttling; diferencia de ~3 W de potencia estable&lt;/td>
&lt;td>arXiv 2604.09048&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>&lt;strong>Prefix-cache caliente entre corridas&lt;/strong>&lt;/td>
&lt;td>sobrevalora TTFT; TTFT irreal si el cache está pre-poblado de la corrida anterior&lt;/td>
&lt;td>efecto severo en evaluaciones multi-prompt con prefijos compartidos&lt;/td>
&lt;td>arXiv 2605.24217&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>&lt;strong>Media en vez de percentiles&lt;/strong>&lt;/td>
&lt;td>oculta la cola de latencia&lt;/td>
&lt;td>P99 puede ser 4–6× la mediana a alta concurrencia&lt;/td>
&lt;td>NVIDIA fundamentals; Anyscale docs&lt;/td>
&lt;/tr>
&lt;/tbody>
&lt;/table>
&lt;hr>
&lt;h2 id="el-sesgo-dominante-saturación-del-cliente-mono-proceso">El sesgo dominante: saturación del cliente mono-proceso&lt;/h2>
&lt;p>El sesgo de mayor magnitud no está en el motor sino en el &lt;strong>cliente que genera la carga&lt;/strong>. Las
herramientas mono-proceso (vLLM bench, SGLang bench, genai-perf antes de su reemplazo por
AIPerf el 15 de abril de 2026) usan un único proceso Python con asyncio para gestionar las
peticiones concurrentes. El Python Global Interpreter Lock (GIL) impide que un solo proceso
utilice más de un núcleo CPU simultáneamente, lo que introduce un cuello de botella en el
lado cliente que se vuelve crítico a alta concurrencia.&lt;/p>
&lt;p>El efecto matemático: a medida que el request rate sube, el cliente no consigue despachar
peticiones al ritmo configurado, acumula tiempo de espera en cola en el lado cliente, y ese
tiempo se registra como latencia del motor. El resultado es que el TTFT y el TPOT
&lt;strong>aparecen inflados&lt;/strong> y el throughput &lt;strong>aparece deprimido&lt;/strong>, sin que el motor haya cambiado
nada (&lt;a href="https://arxiv.org/abs/2605.24217">arXiv 2605.24217&lt;/a>).&lt;/p>
&lt;p>La discrepancia documentada a 1.000 QPS:&lt;/p>
&lt;table>
&lt;thead>
&lt;tr>
&lt;th>Arquitectura de cliente&lt;/th>
&lt;th>Tokens procesados a 1.000 QPS&lt;/th>
&lt;th>Ratio&lt;/th>
&lt;/tr>
&lt;/thead>
&lt;tbody>
&lt;tr>
&lt;td>Mono-proceso (asyncio, Python GIL)&lt;/td>
&lt;td>75.574&lt;/td>
&lt;td>1× (base)&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>Multi-proceso (carga distribuida)&lt;/td>
&lt;td>545.733&lt;/td>
&lt;td>&lt;strong>7,2×&lt;/strong>&lt;/td>
&lt;/tr>
&lt;/tbody>
&lt;/table>
&lt;p>Nota: ambos clientes apuntan al &lt;strong>mismo endpoint&lt;/strong>; la diferencia es íntegramente atribuible
al cliente de benchmark, no al motor.&lt;/p>
&lt;div class="diagram" style="max-width:780px;margin:1rem auto;">
&lt;svg viewBox="0 0 780 220" role="img" aria-label="Diagrama de saturacion del cliente: cliente mono-proceso acumula cola en el lado cliente y reporta latencia inflada; cliente multi-proceso elimina el cuello y mide el motor real" 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(#bm2)}&lt;/style>
&lt;defs>&lt;marker id="bm2" 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="22" class="tl">MONO-PROCESO (vLLM bench, SGLang bench, genai-perf pre-AIPerf)&lt;/text>
&lt;rect class="bx" x="20" y="32" width="140" height="44" rx="5"/>
&lt;text x="30" y="52" class="ts">1 proc. Python&lt;/text>
&lt;text x="30" y="67" class="ts">asyncio + GIL&lt;/text>
&lt;rect class="dsh" x="200" y="32" width="130" height="44" rx="5"/>
&lt;text x="210" y="52" class="ts">cola cliente&lt;/text>
&lt;text x="210" y="67" class="ts">(≠ cola motor)&lt;/text>
&lt;path class="ar" d="M160,54 L198,54"/>
&lt;path class="ar" d="M330,54 L375,54"/>
&lt;rect class="bx" x="375" y="32" width="110" height="44" rx="5"/>
&lt;text x="385" y="58" class="ts">Motor (vLLM…)&lt;/text>
&lt;text x="498" y="48" class="ts">TTFT/TPOT&lt;/text>
&lt;text x="498" y="63" class="ts">sobreestimados&lt;/text>
&lt;text x="498" y="78" class="ts">TPS subestimado&lt;/text>
&lt;text x="20" y="112" class="tl">MULTI-PROCESO (AIPerf, GuideLLM)&lt;/text>
&lt;rect class="bx" x="20" y="122" width="140" height="44" rx="5"/>
&lt;text x="30" y="142" class="ts">N procesos&lt;/text>
&lt;text x="30" y="157" class="ts">sin GIL compartido&lt;/text>
&lt;path class="ar" d="M160,144 L375,144"/>
&lt;rect class="bx" x="375" y="122" width="110" height="44" rx="5"/>
&lt;text x="385" y="148" class="ts">Motor (vLLM…)&lt;/text>
&lt;text x="498" y="138" class="ts">mide el motor;&lt;/text>
&lt;text x="498" y="153" class="ts">7,2× más throughput&lt;/text>
&lt;text x="498" y="168" class="ts">capturado&lt;/text>
&lt;text x="20" y="210" class="ts">La arquitectura del cliente determina qué se mide: el cuello del cliente o el del motor.&lt;/text>
&lt;/svg>
&lt;/div>
&lt;hr>
&lt;h2 id="el-sesgo-del-tokenizer-toks-no-son-comparables-entre-vocabularios">El sesgo del tokenizer: tok/s no son comparables entre vocabularios&lt;/h2>
&lt;p>Los tokens no son universales. Cada modelo tiene su propio tokenizer con su propio
vocabulario. Llama-3 usa un vocabulario de 128.256 tokens; Gemma usa 256.128; modelos
anteriores usaban 32.000–50.000. El mismo texto en inglés produce un número de tokens
distinto según el tokenizer del modelo.&lt;/p>
&lt;p>La consecuencia directa para el benchmarking: si la herramienta mide tok/s con un
tokenizer distinto al del modelo servido, el número de tokens —y por tanto el throughput en
tok/s y el coste por token— están sesgados. LLMPerf (archivado en diciembre de 2025) usaba
&lt;strong>LlamaTokenizer&lt;/strong> de forma universal para todos los modelos, lo que garantizaba consistencia
interna en el leaderboard pero hacía los tok/s &lt;strong>incomparables&lt;/strong> con mediciones de otros
modelos con vocabularios distintos.&lt;/p>
&lt;p>La fórmula del TPS en LLMPerf incluía además el denominador completo del benchmark —tiempo
de generación de prompts, preparación de peticiones y almacenamiento de respuestas—, que
NVIDIA estimó en hasta un &lt;strong>33 % de la duración total&lt;/strong> a concurrencia 1. Esto hace que el
TPS de LLMPerf sea sistemáticamente inferior al de GenAI-Perf/AIPerf para el mismo sistema,
sin que el motor sea peor:&lt;/p>
&lt;p>$$\text{TPS}&lt;em>{\text{LLMPerf}} = \frac{\text{tokens salida}}{T&lt;/em>{\text{end}} - T_{\text{start}}}$$&lt;/p>
&lt;p>$$\text{TPS}_{\text{GenAI-Perf}} = \frac{\text{tokens salida}}{T_y - T_x}$$&lt;/p>
&lt;p>Donde (T_{\text{start}}) y (T_{\text{end}}) incluyen los overheads de cliente, mientras
que (T_x) y (T_y) son el instante del primer request y el del último token recibido,
respectivamente (&lt;a href="https://developer.nvidia.com/blog/llm-benchmarking-fundamental-concepts/">NVIDIA · Fundamental Concepts&lt;/a>).&lt;/p>
&lt;p>La regla operativa: &lt;strong>siempre contar tokens con el tokenizer del modelo servido&lt;/strong>, no con un
tokenizer proxy.&lt;/p>
&lt;hr>
&lt;h2 id="el-sesgo-de-ignore_eos-y-osl-inconsistente">El sesgo de ignore_eos y OSL inconsistente&lt;/h2>
&lt;p>La mayoría de los modelos LLM generan un token especial de fin de secuencia (EOS) cuando
consideran completa la respuesta. Si el benchmark no configura &lt;code>ignore_eos=True&lt;/code>, la longitud
real de salida (OSL) varía de petición en petición según la distribución de longitudes
naturales del modelo, lo que produce:&lt;/p>
&lt;ol>
&lt;li>&lt;strong>OSL inconsistente&lt;/strong>: dos corridas con la misma semilla pueden producir distribuciones
de OSL distintas si el modelo varía su output natural.&lt;/li>
&lt;li>&lt;strong>Comparación espuria&lt;/strong>: un modelo &amp;ldquo;más rápido&amp;rdquo; puede serlo simplemente porque genera
respuestas más cortas (termina antes en EOS), no porque tenga más throughput real.&lt;/li>
&lt;li>&lt;strong>Throughput subestimado&lt;/strong>: si el benchmark espera OSL=256 pero el modelo termina en
OSL=80 de media, el throughput en tok/s parece mayor pero mide menos trabajo.&lt;/li>
&lt;/ol>
&lt;p>El parámetro &lt;code>ignore_eos&lt;/code> (o &lt;code>--ignore-eos&lt;/code> en vLLM bench) instruye al motor a ignorar el
token EOS y continuar hasta alcanzar &lt;code>max_tokens&lt;/code>. Es &lt;strong>obligatorio&lt;/strong> para que el OSL sea el
configurado, no el natural del modelo, y para que dos corridas sean comparables
(&lt;a href="https://docs.vllm.ai/en/latest/benchmarking/cli/">vLLM benchmark CLI docs&lt;/a>).&lt;/p>
&lt;hr>
&lt;h2 id="el-sesgo-de-la-distribución-islosl">El sesgo de la distribución ISL/OSL&lt;/h2>
&lt;p>La distribución de longitudes de entrada (ISL) y salida (OSL) determina qué proporción del
tiempo de cómputo se destina al prefill (costoso en TTFT) y cuánto al decode (costoso en
ITL). Un benchmark con longitud fija —por ejemplo ISL=128, OSL=128— produce resultados que
no se parecen a ningún tráfico real.&lt;/p>
&lt;p>Los casos de uso reales tienen distribuciones muy distintas:&lt;/p>
&lt;table>
&lt;thead>
&lt;tr>
&lt;th>Caso de uso&lt;/th>
&lt;th>ISL típico (tokens)&lt;/th>
&lt;th>OSL típico (tokens)&lt;/th>
&lt;th>Domina&lt;/th>
&lt;/tr>
&lt;/thead>
&lt;tbody>
&lt;tr>
&lt;td>Traducción&lt;/td>
&lt;td>500–2.000&lt;/td>
&lt;td>500–2.000&lt;/td>
&lt;td>equilibrado&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>Generación (código, email)&lt;/td>
&lt;td>~100&lt;/td>
&lt;td>~1.000&lt;/td>
&lt;td>decode (OSL largo)&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>Resumen / RAG&lt;/td>
&lt;td>~1.000&lt;/td>
&lt;td>~100&lt;/td>
&lt;td>prefill (ISL largo)&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>Razonamiento (CoT)&lt;/td>
&lt;td>~100&lt;/td>
&lt;td>1.000–10.000&lt;/td>
&lt;td>decode muy largo&lt;/td>
&lt;/tr>
&lt;/tbody>
&lt;/table>
&lt;p>Un benchmark de ISL corto con un modelo optimizado para prefill dará un TTFT artificialmente
bajo y un throughput artificialmente alto. El codo del sweep se desplaza: con ISL=64 el
sistema admite más concurrencia sin romper el SLO de TTFT; con ISL=1.024 el prefill satura
antes y el codo aparece antes. Usar la distribución equivocada es dimensionar para un tráfico
que no existe.&lt;/p>
&lt;p>AIPerf introduce &lt;strong>distribuciones de secuencia&lt;/strong> con varianza configurable por componente
para reproducir mezclas de tráfico realistas
(&lt;a href="https://docs.nvidia.com/aiperf/tutorials/datasets-inputs/sequence-length-distributions-for-advanced-benchmarking">AIPerf · Sequence Length Distributions&lt;/a>):&lt;/p>
&lt;pre tabindex="0">&lt;code>--sequence-distribution &amp;#34;64|10,32|8:70;256|40,128|20:20;1024|100,512|50:10&amp;#34;
&lt;/code>&lt;/pre>&lt;p>Esto crea el 70 % de peticiones con (\text{ISL} \sim \mathcal{N}(64, 10)) y
(\text{OSL} \sim \mathcal{N}(32, 8)), el 20 % con ISL/OSL medios, y el 10 % con
ISL/OSL largos —mucho más fiel a un tráfico de chatbot real que una longitud fija—.&lt;/p>
&lt;hr>
&lt;h2 id="el-sesgo-del-warmup-y-el-prefix-cache">El sesgo del warmup y el prefix-cache&lt;/h2>
&lt;p>Dos fuentes de sesgo relacionadas pero distintas:&lt;/p>
&lt;p>&lt;strong>Warmup ausente.&lt;/strong> Las primeras peticiones de un benchmark llegan a un motor &amp;ldquo;frío&amp;rdquo;: la
GPU está en estado de baja frecuencia, el KV cache está vacío, y el sistema operativo puede
estar paginando memoria. El TTFT de las primeras peticiones es estructuralmente más alto que
el del régimen estacionario. Si el benchmark no descarta un periodo de warmup, la media de
TTFT incluye estos outliers y sobreestima la latencia real de producción. Algunos frameworks
(GenAI-Perf/AIPerf) usan una &lt;strong>ventana deslizante&lt;/strong> que excluye las peticiones de ramp-up y
ramp-down; otros no.&lt;/p>
&lt;p>&lt;strong>Prefix-cache caliente entre corridas.&lt;/strong> La caché de KV de prefijos (prefix cache o prompt
cache) almacena los resultados computados de los prefijos de prompt que se repiten. Si el
benchmark corre múltiples corridas consecutivas con los mismos prompts, la segunda y
siguientes corridas encuentran el KV cache ya poblado y reportan un TTFT artificialmente
bajo —el del decode, no el del prefill—. Para un benchmark de baseline, el prefix cache debe
estar frío; para uno que simula producción con prompts repetidos, caliente. La distinción
debe explicitarse
(&lt;a href="https://arxiv.org/abs/2605.24217">arXiv 2605.24217&lt;/a>; &lt;a href="https://docs.vllm.ai/en/stable/design/prefix_caching/">vLLM · Automatic Prefix Caching&lt;/a>).&lt;/p>
&lt;hr>
&lt;h2 id="el-sesgo-de-concurrency-vs-request-rate">El sesgo de &lt;code>concurrency&lt;/code> vs &lt;code>request-rate&lt;/code>&lt;/h2>
&lt;p>Los dos modos de control de carga producen distribuciones de latencia distintas para el
mismo sistema:&lt;/p>
&lt;table>
&lt;thead>
&lt;tr>
&lt;th>Modo&lt;/th>
&lt;th>Semántica&lt;/th>
&lt;th>Cuándo usar&lt;/th>
&lt;th>Riesgo&lt;/th>
&lt;/tr>
&lt;/thead>
&lt;tbody>
&lt;tr>
&lt;td>&lt;strong>concurrency N&lt;/strong>&lt;/td>
&lt;td>mantiene exactamente N peticiones en vuelo; en cuanto termina una, lanza otra&lt;/td>
&lt;td>medir el sistema bajo una carga de concurrencia fija&lt;/td>
&lt;td>sobrerepresenta la carga sostenida; no simula llegadas reales&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>&lt;strong>request-rate r&lt;/strong> (constante o Poisson)&lt;/td>
&lt;td>lanza una petición cada (1/r) segundos (constante) o con interarrival (\sim \text{Exp}(1/r)) (Poisson)&lt;/td>
&lt;td>simular tráfico real (llegadas aleatorias)&lt;/td>
&lt;td>si el motor no puede absorber r req/s, la cola crece sin techo&lt;/td>
&lt;/tr>
&lt;/tbody>
&lt;/table>
&lt;p>NVIDIA recomienda el modo &lt;strong>concurrency&lt;/strong> para la mayoría de los benchmarks de capacidad
(&lt;a href="https://developer.nvidia.com/blog/llm-benchmarking-fundamental-concepts/">NVIDIA · LLM Benchmarking Fundamental Concepts&lt;/a>). El modo request-rate es más fiel para tráfico online
(distribución Poisson de llegadas), pero si el rate supera la capacidad del motor la cola
crece indefinidamente y las métricas de latencia incluyen tiempo de espera en cola que puede
dominar el TTFT, mezclando comportamiento de cola con comportamiento del motor.&lt;/p>
&lt;p>Un sistema medido con concurrency=16 y otro con request-rate=16 req/s &lt;strong>no están bajo la
misma carga&lt;/strong>: la concurrencia fija garantiza 16 peticiones simultáneas; el rate configura
el ritmo de llegada pero no la concurrencia instantánea. Comparar sus resultados sin ajuste
es incorrecto.&lt;/p>
&lt;hr>
&lt;h2 id="el-sesgo-de-la-ventana-de-medición-y-el-estado-térmico">El sesgo de la ventana de medición y el estado térmico&lt;/h2>
&lt;p>&lt;strong>Ventana demasiado corta.&lt;/strong> Un benchmark de 30 segundos puede medir el ramp-up del
sistema, no el régimen estacionario. La recomendación de arXiv 2508.10251 es que la ventana
de medición cubra al menos 3-5× el tiempo de rampa del sistema bajo carga, y que las
métricas se calculen solo sobre la ventana estacionaria —excluyendo warmup y cooldown—.&lt;/p>
&lt;p>&lt;strong>Estado térmico de la GPU.&lt;/strong> Las GPUs de datacenter (H100 SXM, A100) operan con throttling
térmico por encima de ~75 °C. Si la GPU no ha alcanzado la temperatura de régimen antes del
benchmark, las primeras mediciones corresponden a un estado de mayor frecuencia que el
sostenible. Experimentos controlados documentan que la potencia debe estabilizarse en un
rango de &lt;strong>3 W&lt;/strong> durante al menos 30 segundos antes de que las medidas sean representativas
(&lt;a href="https://arxiv.org/html/2604.09048v1">arXiv 2604.09048 · Watt Counts&lt;/a>). El efecto es
especialmente severo en benchmarks de prefill (TTFT), donde las frecuencias altas del estado
frío producen TTFT artificialmente bajo.&lt;/p>
&lt;p>Para un benchmark reproducible en 4×H100 SXM 80 GB: antes de medir, correr carga sostenida
durante al menos 2–3 minutos hasta que &lt;code>nvidia-smi&lt;/code> reporte temperatura estable y potencia
estable (variación &amp;lt; 3 W en 30 s). Solo entonces iniciar la ventana de medición.&lt;/p>
&lt;hr>
&lt;h2 id="cómo-las-herramientas-calculan-diferente-la-itl">Cómo las herramientas calculan diferente la ITL&lt;/h2>
&lt;p>La ITL (Inter-Token Latency) aparece en todas las herramientas pero su fórmula varía, y las
diferencias no son pequeñas:&lt;/p>
&lt;p>&lt;strong>GenAI-Perf / AIPerf:&lt;/strong>&lt;/p>
&lt;p>$$\text{ITL} = \frac{\text{e2e latency} - \text{TTFT}}{\text{tokens salida} - 1}$$&lt;/p>
&lt;p>El TTFT se excluye del numerador y el denominador descuenta el primer token. ITL es una
métrica pura del &lt;strong>decode&lt;/strong>, sin contaminación del prefill.&lt;/p>
&lt;p>&lt;strong>LLMPerf&lt;/strong> (archivado dic. 2025):&lt;/p>
&lt;p>$$\text{ITL}_{\text{LLMPerf}} = \frac{\text{e2e latency}}{\text{tokens salida}}$$&lt;/p>
&lt;p>El TTFT se &lt;strong>incluye&lt;/strong> en el numerador. Para secuencias cortas (OSL &amp;lt; 50 tokens), el TTFT
puede representar el 50–80 % de la e2e_latency, con lo que la ITL de LLMPerf mide
principalmente el prefill, no el decode. Dos sistemas con el mismo decode pero distinto
prefill aparecerán con ITL distintas en LLMPerf aunque sean idénticos en velocidad de decode.&lt;/p>
&lt;p>&lt;strong>vLLM bench (benchmark_serving.py):&lt;/strong>&lt;/p>
&lt;p>Calcula ITL como media de los intervalos entre tokens consecutivos del stream de salida,
incluyendo varianza intra-petición. Puede revelar jitter de decode que los otros promedian.&lt;/p>
&lt;p>La consecuencia práctica: &lt;strong>nunca cruzar una ITL de GenAI-Perf con una de LLMPerf&lt;/strong> como si
fueran la misma métrica. Son fórmulas distintas sobre la misma señal.&lt;/p>
&lt;hr>
&lt;h2 id="comparabilidad-entre-herramientas-por-qué-sus-números-no-se-cruzan">Comparabilidad entre herramientas: por qué sus números no se cruzan&lt;/h2>
&lt;p>La tabla siguiente resume las diferencias metodológicas que hacen que los números de una
herramienta sean &lt;strong>estructuralmente incompatibles&lt;/strong> con los de otra sin ajuste explícito:&lt;/p>
&lt;table>
&lt;thead>
&lt;tr>
&lt;th>Dimensión&lt;/th>
&lt;th>vLLM bench&lt;/th>
&lt;th>LLMPerf (archivado)&lt;/th>
&lt;th>GenAI-Perf / AIPerf&lt;/th>
&lt;th>GuideLLM&lt;/th>
&lt;/tr>
&lt;/thead>
&lt;tbody>
&lt;tr>
&lt;td>&lt;strong>Arquitectura cliente&lt;/strong>&lt;/td>
&lt;td>mono-proceso asyncio&lt;/td>
&lt;td>mono-proceso&lt;/td>
&lt;td>&lt;strong>multi-proceso&lt;/strong>&lt;/td>
&lt;td>&lt;strong>multi-proceso&lt;/strong>&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>&lt;strong>ITL incluye TTFT&lt;/strong>&lt;/td>
&lt;td>no&lt;/td>
&lt;td>&lt;strong>sí&lt;/strong>&lt;/td>
&lt;td>no&lt;/td>
&lt;td>no&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>&lt;strong>TPS denominador&lt;/strong>&lt;/td>
&lt;td>(T_y - T_x)&lt;/td>
&lt;td>(T_{\text{end}} - T_{\text{start}}) (overhead incluido)&lt;/td>
&lt;td>(T_y - T_x)&lt;/td>
&lt;td>por ronda&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>&lt;strong>Tokenizer&lt;/strong>&lt;/td>
&lt;td>el del modelo servido&lt;/td>
&lt;td>LlamaTokenizer universal&lt;/td>
&lt;td>el del modelo servido&lt;/td>
&lt;td>el del modelo servido&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>&lt;strong>Warmup / sliding window&lt;/strong>&lt;/td>
&lt;td>no automático&lt;/td>
&lt;td>no&lt;/td>
&lt;td>&lt;strong>sí (sliding window)&lt;/strong>&lt;/td>
&lt;td>por ronda&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>&lt;strong>ignore_eos por defecto&lt;/strong>&lt;/td>
&lt;td>no&lt;/td>
&lt;td>no&lt;/td>
&lt;td>recomendado explícitamente&lt;/td>
&lt;td>configurable&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>&lt;strong>Distribución ISL/OSL&lt;/strong>&lt;/td>
&lt;td>parámetro manual&lt;/td>
&lt;td>parámetro manual&lt;/td>
&lt;td>distribuciones con varianza&lt;/td>
&lt;td>&lt;code>--data&lt;/code> configurable&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>&lt;strong>Modo de carga primario&lt;/strong>&lt;/td>
&lt;td>concurrency&lt;/td>
&lt;td>concurrency (batches drenados)&lt;/td>
&lt;td>concurrency (recomendado)&lt;/td>
&lt;td>sweep + poisson&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>&lt;strong>Salida estructurada&lt;/strong>&lt;/td>
&lt;td>JSON básico&lt;/td>
&lt;td>JSON&lt;/td>
&lt;td>JSON + CSV&lt;/td>
&lt;td>JSON + HTML + CSV&lt;/td>
&lt;/tr>
&lt;/tbody>
&lt;/table>
&lt;p>Un resultado de LLMPerf y uno de GenAI-Perf para el mismo endpoint pueden diferir en TTFT,
ITL y TPS &lt;strong>simultáneamente&lt;/strong> y en todas ellas por razones metodológicas, no por diferencias
del motor. La única forma de cruzarlos es ejecutar ambas herramientas sobre el mismo sistema
en las mismas condiciones y calcular el factor de conversión empírico —lo que en la práctica
equivale a re-medir con una sola herramienta.&lt;/p>
&lt;hr>
&lt;h2 id="el-harness-honesto-checklist-de-reproducibilidad">El harness honesto: checklist de reproducibilidad&lt;/h2>
&lt;p>Un benchmark cuyo número puede defenderse en una auditoría tiene que venir acompañado de
todos los metadatos que permiten reproducirlo. El mínimo exigible, organizado por categoría:&lt;/p>
&lt;h3 id="hardware">Hardware&lt;/h3>
&lt;table>
&lt;thead>
&lt;tr>
&lt;th>Metadato&lt;/th>
&lt;th>Ejemplo 4×H100&lt;/th>
&lt;th>Efecto si no se fija&lt;/th>
&lt;/tr>
&lt;/thead>
&lt;tbody>
&lt;tr>
&lt;td>GPU (modelo, variante, cantidad)&lt;/td>
&lt;td>4× NVIDIA H100 SXM 80 GB&lt;/td>
&lt;td>H100 PCIe vs SXM difieren en BW de memoria y NVLink&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>Interconexión GPU-GPU&lt;/td>
&lt;td>NVLink 4.0&lt;/td>
&lt;td>tensor parallelism depende de la BW de interconexión&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>CPU, RAM, ancho de banda CPU-GPU&lt;/td>
&lt;td>2× Intel Xeon 8480+, 512 GB DDR5, PCIe 5.0&lt;/td>
&lt;td>el cuello de tokenización puede estar en la CPU&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>Estado térmico al inicio&lt;/td>
&lt;td>temperatura GPU &amp;lt; 65 °C, potencia estable ± 3 W&lt;/td>
&lt;td>throttling altera TPS y TTFT en hasta ~15 %&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>Frecuencias de GPU (clocks)&lt;/td>
&lt;td>sin &lt;code>nvidia-smi -pm 1&lt;/code> pueden variar&lt;/td>
&lt;td>diferencia de hasta ~10 % en throughput&lt;/td>
&lt;/tr>
&lt;/tbody>
&lt;/table>
&lt;h3 id="software-y-modelo">Software y modelo&lt;/h3>
&lt;table>
&lt;thead>
&lt;tr>
&lt;th>Metadato&lt;/th>
&lt;th>Ejemplo&lt;/th>
&lt;th>Efecto si no se fija&lt;/th>
&lt;/tr>
&lt;/thead>
&lt;tbody>
&lt;tr>
&lt;td>Motor + versión + flags&lt;/td>
&lt;td>vLLM 0.8.4, &lt;code>--tensor-parallel-size 4 --gpu-memory-utilization 0.90&lt;/code>&lt;/td>
&lt;td>cada versión cambia el scheduler y el KV cache management&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>Modelo + precisión&lt;/td>
&lt;td>Llama-3-70B-Instruct, FP8&lt;/td>
&lt;td>FP16 vs FP8 difieren ~1,4–1,8× en throughput&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>Tokenizer usado para contar&lt;/td>
&lt;td>tokenizer del modelo servido (HF tokenizer)&lt;/td>
&lt;td>LlamaTokenizer universal sesga tok/s entre vocabularios&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>Semilla de generación&lt;/td>
&lt;td>&lt;code>--seed 42&lt;/code>&lt;/td>
&lt;td>sin semilla fija, la OSL varía run-to-run&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>&lt;code>ignore_eos&lt;/code>&lt;/td>
&lt;td>&lt;code>True&lt;/code>&lt;/td>
&lt;td>sin él, OSL varía con el contenido del prompt&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>Sampling parameters&lt;/td>
&lt;td>&lt;code>temperature=1.0, top_p=0.95&lt;/code>&lt;/td>
&lt;td>greedy vs sampling afectan velocidad de logit&lt;/td>
&lt;/tr>
&lt;/tbody>
&lt;/table>
&lt;h3 id="carga">Carga&lt;/h3>
&lt;table>
&lt;thead>
&lt;tr>
&lt;th>Metadato&lt;/th>
&lt;th>Ejemplo&lt;/th>
&lt;th>Efecto si no se fija&lt;/th>
&lt;/tr>
&lt;/thead>
&lt;tbody>
&lt;tr>
&lt;td>Herramienta de bench + versión&lt;/td>
&lt;td>AIPerf 0.2.1&lt;/td>
&lt;td>cada versión cambia fórmulas y warmup&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>Distribución ISL/OSL&lt;/td>
&lt;td>(\mathcal{N}(512, 64)) ISL, (\mathcal{N}(128, 20)) OSL&lt;/td>
&lt;td>cambiar distribución mueve el codo&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>Modo de carga&lt;/td>
&lt;td>concurrency, sweep 1–64&lt;/td>
&lt;td>concurrency vs request-rate: distribuciones distintas&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>Niveles de concurrencia&lt;/td>
&lt;td>1, 2, 4, 8, 16, 32, 64 (sweep completo, más allá del codo)&lt;/td>
&lt;td>sin pasar el codo, la capacidad segura es desconocida&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>Duración por punto&lt;/td>
&lt;td>120 s mínimo por nivel&lt;/td>
&lt;td>ventanas cortas capturan ramp-up, no régimen estacionario&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>Warmup&lt;/td>
&lt;td>30 s excluidos del cómputo de métricas&lt;/td>
&lt;td>sin warmup, las métricas incluyen estado frío&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>Tratamiento prefix cache&lt;/td>
&lt;td>frío (flush entre corridas) o caliente (declarar explícitamente)&lt;/td>
&lt;td>cache caliente: TTFT irreal para baseline&lt;/td>
&lt;/tr>
&lt;/tbody>
&lt;/table>
&lt;h3 id="slo-y-métricas-reportadas">SLO y métricas reportadas&lt;/h3>
&lt;table>
&lt;thead>
&lt;tr>
&lt;th>Metadato&lt;/th>
&lt;th>Ejemplo&lt;/th>
&lt;th>Efecto si no se declara&lt;/th>
&lt;/tr>
&lt;/thead>
&lt;tbody>
&lt;tr>
&lt;td>SLO declarado&lt;/td>
&lt;td>TTFT P99 &amp;lt; 500 ms, ITL P95 &amp;lt; 50 ms&lt;/td>
&lt;td>el goodput depende del SLO; sin SLO no hay goodput&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>Percentiles reportados&lt;/td>
&lt;td>P50, P95, P99 (no solo media)&lt;/td>
&lt;td>la media oculta la cola; irrelevante para SLO&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>Throughput vs goodput&lt;/td>
&lt;td>goodput bajo SLO, no throughput bruto&lt;/td>
&lt;td>el throughput de catálogo puede ser 5–10× el goodput&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>Número de peticiones totales&lt;/td>
&lt;td>≥ 1.000 por nivel de concurrencia&lt;/td>
&lt;td>muestras pequeñas: alta varianza en percentiles&lt;/td>
&lt;/tr>
&lt;/tbody>
&lt;/table>
&lt;hr>
&lt;h2 id="defensa-metodológica-del-dato-ante-una-auditoría">Defensa metodológica del dato ante una auditoría&lt;/h2>
&lt;p>Un número de throughput que se presenta sin la ficha anterior no es un dato: es una anécdota.
El patrón de validación para una auditoría técnica:&lt;/p>
&lt;p>&lt;strong>1. Trazabilidad de la herramienta.&lt;/strong> La herramienta y su versión deben ser pinables a un
commit de Git o a un hash de imagen de contenedor. AIPerf y GuideLLM exportan JSON con
metadatos de versión; vLLM bench los omite por defecto y hay que capturarlos manualmente.&lt;/p>
&lt;p>&lt;strong>2. Reproducibilidad del entorno.&lt;/strong> El script de despliegue del motor (o el manifiesto de
Kubernetes) y el comando exacto de benchmark deben ser suficientes para que un tercero
reproduzca el número en el mismo hardware. GuideLLM exporta el archivo de benchmark como
registro autoritativo de la sesión: configuración, metadatos, estadísticas por benchmark y
entradas de petición con tiempos individuales
(&lt;a href="https://github.com/vllm-project/guidellm">GitHub vllm-project/guidellm&lt;/a>).&lt;/p>
&lt;p>&lt;strong>3. Verificación del régimen estacionario.&lt;/strong> Las métricas deben proceder de la ventana
estacionaria del benchmark (excluido warmup). La curva throughput vs concurrencia debe
mostrar el codo —el punto donde el throughput satura y la latencia se dispara— y extenderse
más allá de él. Sin codo visible, la capacidad reportada puede ser el límite del cliente, no
del motor.&lt;/p>
&lt;p>&lt;strong>4. Goodput, no throughput bruto.&lt;/strong> El throughput auditable es el goodput bajo el SLO
declarado, no el throughput máximo. Un sistema que reporta 4.000 tok/s pero cuyo goodput
bajo TTFT P99 &amp;lt; 500 ms es 1.800 tok/s tiene una capacidad real de 1.800 tok/s para los
casos de uso interactivos.&lt;/p>
&lt;p>&lt;strong>5. Comparabilidad interna.&lt;/strong> Si se comparan dos motores o dos configuraciones, la
herramienta, la distribución de carga, el SLO, el hardware y el tratamiento del warmup
deben ser &lt;strong>idénticos&lt;/strong>. Cualquier diferencia en estas dimensiones contamina la comparación.&lt;/p>
&lt;p>La fórmula del goodput como métrica auditable:&lt;/p>
&lt;p>$$\text{goodput} = \text{throughput} \times \Pr[\text{TTFT} \leq \text{SLO}&lt;em>{\text{TTFT}}] \times \Pr[\text{ITL} \leq \text{SLO}&lt;/em>{\text{ITL}}]$$&lt;/p>
&lt;p>Donde las probabilidades se estiman sobre la distribución empírica de la corrida. Un motor
con throughput de 4.000 tok/s y (\Pr[\text{TTFT} \leq 500,\text{ms}] = 0{,}45) tiene
un goodput de (4{,}000 \times 0{,}45 = 1{,}800) tok/s.&lt;/p>
&lt;hr>
&lt;h2 id="ejemplo-numérico-el-mismo-nodo-cuatro-medidas-distintas">Ejemplo numérico: el mismo nodo, cuatro medidas distintas&lt;/h2>
&lt;p>Hardware de referencia: 4×H100 SXM 80 GB, modelo Llama-3-70B-Instruct FP8, tensor
parallel 4, SLO TTFT P99 &amp;lt; 500 ms.&lt;/p>
&lt;table>
&lt;thead>
&lt;tr>
&lt;th>Configuración de benchmark&lt;/th>
&lt;th>Throughput reportado&lt;/th>
&lt;th>Goodput real&lt;/th>
&lt;th>Factor vs goodput&lt;/th>
&lt;/tr>
&lt;/thead>
&lt;tbody>
&lt;tr>
&lt;td>vLLM bench, concurrency=32, sin ignore_eos, sin warmup, OSL=64 fijo&lt;/td>
&lt;td>5.800 tok/s&lt;/td>
&lt;td>~1.200 tok/s (OSL corto infla TPS)&lt;/td>
&lt;td>&lt;strong>4,8×&lt;/strong>&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>LLMPerf, concurrency=32, LlamaTokenizer, overhead incluido en denominador&lt;/td>
&lt;td>3.100 tok/s&lt;/td>
&lt;td>~1.600 tok/s (ITL incluye TTFT)&lt;/td>
&lt;td>&lt;strong>1,9×&lt;/strong>&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>AIPerf, concurrency=32, ignore_eos, sliding window, tokenizer del modelo&lt;/td>
&lt;td>3.400 tok/s&lt;/td>
&lt;td>3.350 tok/s&lt;/td>
&lt;td>&lt;strong>1,01×&lt;/strong>&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>GuideLLM, sweep 1–64, poisson, distribución ISL/OSL realista, SLO declarado&lt;/td>
&lt;td>3.330 tok/s goodput (codo en ronda 6)&lt;/td>
&lt;td>3.330 tok/s&lt;/td>
&lt;td>&lt;strong>1,0× (base honesta)&lt;/strong>&lt;/td>
&lt;/tr>
&lt;/tbody>
&lt;/table>
&lt;p>El rango: de 1.200 tok/s a 5.800 tok/s para el mismo motor, el mismo hardware y el mismo
modelo. El factor máximo es &lt;strong>4,8×&lt;/strong>. La causa no es el motor; son las decisiones de
instrumentación.&lt;/p>
&lt;hr>
&lt;h2 id="la-descomposición-del-sesgo-total">La descomposición del sesgo total&lt;/h2>
&lt;p>El sesgo total observable entre dos herramientas es la composición multiplicativa de los
sesgos individuales. Para una comparación de vLLM bench mono-proceso vs GuideLLM
multi-proceso sobre el mismo sistema:&lt;/p>
&lt;p>$$\text{factor total} \approx \underbrace{f_{\text{cliente}}}&lt;em>{\leq 7{,}2\times} \times \underbrace{f&lt;/em>{\text{tokenizer}}}&lt;em>{1{,}0{-}1{,}3\times} \times \underbrace{f&lt;/em>{\text{ignore-eos}}}&lt;em>{1{,}0{-}2{,}0\times} \times \underbrace{f&lt;/em>{\text{ISL/OSL}}}&lt;em>{1{,}0{-}1{,}5\times} \times \underbrace{f&lt;/em>{\text{warmup}}}_{1{,}0{-}1{,}2\times}$$&lt;/p>
&lt;p>El factor de saturación del cliente ((\leq 7{,}2\times)) domina, pero los demás factores
se multiplican. En condiciones adversas (cliente mono-proceso + tokenizer distinto + sin
ignore_eos + OSL irreal + sin warmup), el sesgo compuesto puede superar &lt;strong>15–20×&lt;/strong> para
el mismo sistema.&lt;/p>
&lt;hr>
&lt;h2 id="estado-del-arte-2026-qué-ha-cambiado">Estado del arte 2026: qué ha cambiado&lt;/h2>
&lt;ul>
&lt;li>&lt;strong>Migración genai-perf → AIPerf&lt;/strong> (15-abr-2026): NVIDIA retiró genai-perf y lo sustituyó
por AIPerf, que es multi-proceso con detección automática de saturación, distribuciones
ISL/OSL configurables y ventana deslizante integrada. La migración elimina el sesgo de
cliente mono-proceso del tooling oficial de NVIDIA.&lt;/li>
&lt;li>&lt;strong>Archivo de LLMPerf&lt;/strong> (dic. 2025): el repositorio &lt;code>ray-project/llmperf&lt;/code> está archivado y
en modo solo-lectura. Los resultados históricos del LLMPerf leaderboard son comparables
solo entre sí; no se deben cruzar con mediciones modernas sin ajuste.&lt;/li>
&lt;li>&lt;strong>GuideLLM 0.5.x&lt;/strong> (2025–2026): refactor arquitectural completo, soporte multimodal, y
exportación JSON autoritativa con todos los metadatos de sesión. Es el estándar OSS de
evaluación dirigida por SLO.&lt;/li>
&lt;li>&lt;strong>arXiv 2605.24217&lt;/strong> (may. 2026): primera caracterización formal del sesgo sistemático de
medición en benchmarks de producción LLM, con demostración matemática del efecto GIL y
propuesta de harness multi-proceso.&lt;/li>
&lt;li>&lt;strong>arXiv 2508.10251&lt;/strong>: meta-métricas y buenas prácticas para benchmarking de rendimiento a
nivel de sistema; establece las dimensiones mínimas del espacio de parámetros que deben
declararse para que un resultado sea reproducible.&lt;/li>
&lt;li>&lt;strong>MLPerf Inference v5.1&lt;/strong> (sep. 2025): 27 participantes, récord de participación. Las
reglas de MLPerf exigen declarar el código exacto, el dataset y el hardware, y admiten
revisión pública —es el único benchmark de la industria con proceso de revisión formal de
reproducibilidad.&lt;/li>
&lt;/ul>
&lt;hr>
&lt;h2 id="cross-links-del-track-de-benchmarking">Cross-links del track de benchmarking&lt;/h2>
&lt;p>Los sesgos descritos aquí aplican a todas las herramientas del catálogo. Para la ficha
completa de cada herramienta:&lt;/p>
&lt;ul>
&lt;li>Catálogo completo: &lt;a href="https://blog.lo0.es/posts/herramientas-benchmark-llm-ficha-a-ficha/">Herramientas de benchmarking LLM, ficha a ficha&lt;/a>&lt;/li>
&lt;li>GuideLLM y la validación de SLO: &lt;a href="https://blog.lo0.es/posts/guidellm-validacion-slo-bajo-carga/">GuideLLM a fondo: validar el SLO bajo carga&lt;/a>&lt;/li>
&lt;li>AIPerf (ex genai-perf) y la detección de saturación: &lt;a href="https://blog.lo0.es/posts/nvidia-genai-perf-a-fondo/">NVIDIA GenAI-Perf a fondo&lt;/a>&lt;/li>
&lt;li>Estado del arte de frameworks: &lt;a href="https://blog.lo0.es/posts/benchmarking-llm-frameworks-estado-del-arte/">Benchmarking LLM: frameworks y estado del arte&lt;/a>&lt;/li>
&lt;li>Decisión de motor en el eje Pareto: &lt;a href="https://blog.lo0.es/posts/comparativa-motores-serving-pareto/">Comparativa de motores de serving: frontera Pareto&lt;/a>&lt;/li>
&lt;/ul>
&lt;hr>
&lt;h2 id="fuentes">Fuentes&lt;/h2>
&lt;ul>
&lt;li>arXiv 2605.24217 · Identifying and Mitigating Systemic Measurement Bias in Production LLM Inference Benchmarks — &lt;a href="https://arxiv.org/abs/2605.24217">https://arxiv.org/abs/2605.24217&lt;/a>&lt;/li>
&lt;li>arXiv 2508.10251 · Meta-Metrics and Best Practices for System-Level Inference Performance Benchmarking — &lt;a href="https://arxiv.org/pdf/2508.10251">https://arxiv.org/pdf/2508.10251&lt;/a>&lt;/li>
&lt;li>arXiv 2604.09048 · Watt Counts: Energy-Aware Benchmark for Sustainable LLM Inference on Heterogeneous GPU Architectures — &lt;a href="https://arxiv.org/html/2604.09048v1">https://arxiv.org/html/2604.09048v1&lt;/a>&lt;/li>
&lt;li>NVIDIA Technical Blog · LLM Inference Benchmarking: Fundamental Concepts (ITL, TPS, ISL/OSL, ignore_eos, concurrency vs request-rate) — &lt;a href="https://developer.nvidia.com/blog/llm-benchmarking-fundamental-concepts/">https://developer.nvidia.com/blog/llm-benchmarking-fundamental-concepts/&lt;/a>&lt;/li>
&lt;li>NVIDIA AIPerf Docs · Sequence Length Distributions for Advanced Benchmarking — &lt;a href="https://docs.nvidia.com/aiperf/tutorials/datasets-inputs/sequence-length-distributions-for-advanced-benchmarking">https://docs.nvidia.com/aiperf/tutorials/datasets-inputs/sequence-length-distributions-for-advanced-benchmarking&lt;/a>&lt;/li>
&lt;li>NVIDIA AIPerf · Request Rate with Max Concurrency — &lt;a href="https://docs.nvidia.com/aiperf/tutorials/load-patterns-scheduling/request-rate-with-max-concurrency">https://docs.nvidia.com/aiperf/tutorials/load-patterns-scheduling/request-rate-with-max-concurrency&lt;/a>&lt;/li>
&lt;li>NVIDIA AIPerf · Comprehensive LLM Benchmarking Guide — &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>vLLM Documentation · Benchmark CLI (ignore_eos, métricas disponibles) — &lt;a href="https://docs.vllm.ai/en/latest/benchmarking/cli/">https://docs.vllm.ai/en/latest/benchmarking/cli/&lt;/a>&lt;/li>
&lt;li>vLLM Documentation · Automatic Prefix Caching — &lt;a href="https://docs.vllm.ai/en/stable/design/prefix_caching/">https://docs.vllm.ai/en/stable/design/prefix_caching/&lt;/a>&lt;/li>
&lt;li>GitHub · ray-project/llmperf (archivado dic. 2025) — &lt;a href="https://github.com/ray-project/llmperf">https://github.com/ray-project/llmperf&lt;/a>&lt;/li>
&lt;li>GitHub · vllm-project/guidellm (exportación JSON autoritativa, metadatos de sesión) — &lt;a href="https://github.com/vllm-project/guidellm">https://github.com/vllm-project/guidellm&lt;/a>&lt;/li>
&lt;li>Red Hat Developer · 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>MLCommons · MLPerf Inference v5.1 results (récord 27 participantes, reproducibilidad formal) — &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 2502.16721 · Speed and Conversational LLMs: Not All Is About Tokens per Second (tokenizer incompatibilidad y tok/s entre vocabularios) — &lt;a href="https://arxiv.org/pdf/2502.16721">https://arxiv.org/pdf/2502.16721&lt;/a>&lt;/li>
&lt;li>Medium · LLM Inference Benchmarking (genAI-perf y vLLM, discrepancia 7,2×) — &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>