<?xml version="1.0" encoding="utf-8" standalone="yes"?><rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom"><channel><title>Triton on lo0 — Tech Blog</title><link>https://blog.lo0.es/en/tags/triton/</link><description>Recent content in Triton on lo0 — Tech Blog</description><generator>Hugo -- gohugo.io</generator><language>en</language><lastBuildDate>Mon, 15 Jun 2026 03:00:00 +0200</lastBuildDate><atom:link href="https://blog.lo0.es/en/tags/triton/index.xml" rel="self" type="application/rss+xml"/><item><title>GenAI-Perf in depth: LLM inference profiling with real data</title><link>https://blog.lo0.es/en/posts/genai-perf-llm-inference-profiling-in-depth/</link><pubDate>Mon, 15 Jun 2026 03:00:00 +0200</pubDate><guid>https://blog.lo0.es/en/posts/genai-perf-llm-inference-profiling-in-depth/</guid><description>&lt;blockquote>
&lt;p>Notation: amounts in &lt;strong>euros (N EUR)&lt;/strong>, decimals with a point. The dollar symbol is not used
(on this site it is a formula delimiter).&lt;/p>
&lt;/blockquote>
&lt;h2 id="tldr">TL;DR&lt;/h2>
&lt;p>GenAI-Perf (the &lt;code>genai-perf&lt;/code> package, part of NVIDIA&amp;rsquo;s Triton ecosystem) is the reference LLM
inference profiler for OpenAI-compatible endpoints. Installable with &lt;code>pip install genai-perf&lt;/code>.
A single &lt;code>genai-perf profile&lt;/code> command produces a console table with TTFT, ITL, request latency
(avg/min/max/P75/P90/P99), output sequence length, output token throughput and request
throughput, plus &lt;code>profile_export_genai_perf.json&lt;/code> and &lt;code>.csv&lt;/code> artefacts. The &lt;code>analyze&lt;/code>
subcommand automatically sweeps concurrencies from 1 to 256 (powers of 2) and generates a
summary &lt;code>analyze_export_genai_perf.csv&lt;/code>. GenAI-Perf was declared &lt;strong>retired in April 2026&lt;/strong>
(replaced by AIPerf); it remains the reference benchmark for historical runs and documented
comparisons, and the NVIDIA tool with the most public documentation for LLMs before 2026.&lt;/p>
&lt;hr>
&lt;h2 id="what-genai-perf-is-and-where-it-fits">What GenAI-Perf is and where it fits&lt;/h2>
&lt;p>GenAI-Perf is a command-line tool for measuring the throughput and latency of generative AI
models served through an inference server. It is part of the
&lt;code>triton-inference-server/perf_analyzer&lt;/code> GitHub repository and is distributed as a pip package
(&lt;code>genai-perf&lt;/code>) and inside the Triton SDK container
(&lt;code>nvcr.io/nvidia/tritonserver:YY.MM-py3-sdk&lt;/code>).&lt;/p>
&lt;p>&lt;strong>Position in the NVIDIA ecosystem:&lt;/strong>&lt;/p>
&lt;div class="diagram" style="max-width:780px;margin:1rem auto;">
&lt;svg viewBox="0 0 780 200" role="img" aria-label="NVIDIA ecosystem: Triton Inference Server and TensorRT-LLM as engines, NIM as a microservice, GenAI-Perf as an endpoint profiler, AIPerf as the successor" xmlns="http://www.w3.org/2000/svg">
&lt;style>.bx{fill:none;stroke:currentColor;stroke-width:1.3}.tl{font:600 12px sans-serif;fill:currentColor}.ts{font:11px sans-serif;fill:currentColor}.ar{fill:none;stroke:currentColor;stroke-width:1.3;marker-end:url(#am)}&lt;/style>
&lt;defs>&lt;marker id="am" viewBox="0 0 10 10" refX="9" refY="5" markerWidth="7" markerHeight="7" orient="auto">&lt;path d="M0,0 L10,5 L0,10 z" fill="currentColor"/>&lt;/marker>&lt;/defs>
&lt;rect class="bx" x="20" y="60" width="140" height="44" rx="6"/>&lt;text x="90" y="79" text-anchor="middle" class="tl">Inference engine&lt;/text>&lt;text x="90" y="95" text-anchor="middle" class="ts">Triton + TRT-LLM / vLLM&lt;/text>
&lt;path class="ar" d="M160,82 L200,82"/>
&lt;rect class="bx" x="200" y="60" width="120" height="44" rx="6"/>&lt;text x="260" y="79" text-anchor="middle" class="tl">Endpoint&lt;/text>&lt;text x="260" y="95" text-anchor="middle" class="ts">OpenAI-compatible&lt;/text>
&lt;path class="ar" d="M320,82 L360,82"/>
&lt;rect class="bx" x="360" y="60" width="140" height="44" rx="6"/>&lt;text x="430" y="79" text-anchor="middle" class="tl">GenAI-Perf&lt;/text>&lt;text x="430" y="95" text-anchor="middle" class="ts">profiler (until Apr 2026)&lt;/text>
&lt;path class="ar" d="M500,82 L540,82"/>
&lt;rect class="bx" x="540" y="60" width="140" height="44" rx="6"/>&lt;text x="610" y="79" text-anchor="middle" class="tl">AIPerf&lt;/text>&lt;text x="610" y="95" text-anchor="middle" class="ts">successor (Apr 2026+)&lt;/text>
&lt;text x="20" y="155" class="ts">GenAI-Perf works against any endpoint compatible with the OpenAI API (vLLM, NIM, Triton, SGLang, TGI…).&lt;/text>
&lt;text x="20" y="172" class="ts">AIPerf is the official successor from April 2026 (GitHub: ai-dynamo/aiperf).&lt;/text>
&lt;/svg>
&lt;/div>
&lt;p>&lt;strong>Relationship with perf_analyzer:&lt;/strong> GenAI-Perf internally uses Triton&amp;rsquo;s &lt;code>perf_analyzer&lt;/code>
binary to generate load and measure latencies. perf_analyzer is the low-level load generator;
GenAI-Perf is the high-level layer that adds prompt synthesis, OpenAI payload construction and
the LLM-specific metrics (TTFT, ITL, sequence lengths).&lt;/p>
&lt;p>&lt;strong>Installation:&lt;/strong>&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"># Option 1: pip (requires CUDA 12, Ubuntu 24.04, Python 3.10+)&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">pip install genai-perf
&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"># Option 2: Triton SDK container (recommended for reproducibility)&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="nb">export&lt;/span> &lt;span class="nv">RELEASE&lt;/span>&lt;span class="o">=&lt;/span>&lt;span class="s2">&amp;#34;25.01&amp;#34;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">docker run -it --net&lt;span class="o">=&lt;/span>host --gpus&lt;span class="o">=&lt;/span>all &lt;span class="se">\
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="se">&lt;/span> nvcr.io/nvidia/tritonserver:25.01-py3-sdk
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="c1"># Inside the container:&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">genai-perf --help
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;hr>
&lt;h2 id="metrics-genai-perf-produces">Metrics GenAI-Perf produces&lt;/h2>
&lt;p>The metrics cover the full cycle of a streaming generation request. All of them are reported
with avg, min, max, P75, P90 and P99 except the throughput ones (avg only).&lt;/p>
&lt;h3 id="metrics-glossary">Metrics glossary&lt;/h3>
&lt;table>
&lt;thead>
&lt;tr>
&lt;th>Metric&lt;/th>
&lt;th>Symbol&lt;/th>
&lt;th>Exact definition (GenAI-Perf)&lt;/th>
&lt;th>Unit&lt;/th>
&lt;/tr>
&lt;/thead>
&lt;tbody>
&lt;tr>
&lt;td>&lt;strong>Time to First Token&lt;/strong>&lt;/td>
&lt;td>TTFT&lt;/td>
&lt;td>Time from sending the request to receiving the first token (includes queueing, prefill and network)&lt;/td>
&lt;td>ms&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>&lt;strong>Inter-Token Latency&lt;/strong>&lt;/td>
&lt;td>ITL (= TPOT)&lt;/td>
&lt;td>\(\frac{e2e\_latency - TTFT}{output\_tokens - 1}\) — decode phase only, excludes the first token&lt;/td>
&lt;td>ms/token&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>&lt;strong>Request Latency&lt;/strong>&lt;/td>
&lt;td>e2e_latency&lt;/td>
&lt;td>\(e2e = TTFT + Generation\_time\) — from the first request to the last response&lt;/td>
&lt;td>ms&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>&lt;strong>Output Token Throughput&lt;/strong>&lt;/td>
&lt;td>OTT&lt;/td>
&lt;td>\(\frac{total\_output\_tokens}{T_y - T_x}\) where \(T_x\) = first request, \(T_y\) = last token received&lt;/td>
&lt;td>tok/s&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>&lt;strong>Request Throughput&lt;/strong>&lt;/td>
&lt;td>RPS&lt;/td>
&lt;td>\(\frac{total\_completed\_requests}{T_y - T_x}\)&lt;/td>
&lt;td>req/s&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>&lt;strong>Input Sequence Length&lt;/strong>&lt;/td>
&lt;td>ISL&lt;/td>
&lt;td>Mean length in tokens of the input prompt&lt;/td>
&lt;td>tokens&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>&lt;strong>Output Sequence Length&lt;/strong>&lt;/td>
&lt;td>OSL&lt;/td>
&lt;td>Mean length in tokens of the generated response&lt;/td>
&lt;td>tokens&lt;/td>
&lt;/tr>
&lt;/tbody>
&lt;/table>
&lt;p>&lt;strong>Methodological note on ITL versus LLMPerf:&lt;/strong> GenAI-Perf excludes TTFT from the ITL
calculation (pure decode only). LLMPerf includes TTFT in its ITL mean. The numbers are not
directly comparable.&lt;/p>
&lt;p>&lt;strong>Methodological note on TPS versus LLMPerf:&lt;/strong> GenAI-Perf measures throughput between the
first request and the last token received (\(T_y - T_x\)). LLMPerf uses the total duration of
the benchmark including prompt generation and response storage; in a concurrency-1 scenario,
that difference can amount to up to 33% in the reported throughput figure.&lt;/p>
&lt;h3 id="console-output-table-real-example-trt-llm-backend">Console output table (real example, TRT-LLM backend)&lt;/h3>
&lt;p>The table GenAI-Perf prints for a profile with ISL=200, OSL=100, concurrency=1:&lt;/p>
&lt;pre tabindex="0">&lt;code> NVIDIA GenAI-Perf | LLM Metrics
┏━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┳━━━━━━━━┳━━━━━━━━┳━━━━━━━━┳━━━━━━━━┳━━━━━━━━┳━━━━━━━━┓
┃ Statistic ┃ avg ┃ min ┃ max ┃ p99 ┃ p90 ┃ p75 ┃
┡━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━╇━━━━━━━━╇━━━━━━━━╇━━━━━━━━╇━━━━━━━━╇━━━━━━━━╇━━━━━━━━┩
│ Time to first token (ms) │ 13.68 │ 11.07 │ 21.50 │ 18.81 │ 14.29 │ 13.97 │
│ Inter token latency (ms) │ 1.86 │ 1.28 │ 2.11 │ 2.11 │ 2.01 │ 1.95 │
│ Request latency (ms) │ 203.70 │ 180.33 │ 228.30 │ 225.45 │ 216.48 │ 211.72 │
│ Output sequence length │ 103.46 │ 95.00 │ 134.00 │ 122.96 │ 108.00 │ 104.75 │
│ Input sequence length │ 200.00 │ 200.00 │ 200.00 │ 200.00 │ 200.00 │ 200.00 │
│ Output token throughput (per sec) │ 504.02 │ N/A │ N/A │ N/A │ N/A │ N/A │
│ Request throughput (per sec) │ 4.87 │ N/A │ N/A │ N/A │ N/A │ N/A │
└───────────────────────────────────┴────────┴────────┴────────┴────────┴────────┴────────┘
&lt;/code>&lt;/pre>&lt;p>&lt;em>(Source: docs.nvidia.com/deeplearning/triton-inference-server — LLM tutorial)&lt;/em>&lt;/p>
&lt;hr>
&lt;h2 id="invocation-against-openai-compatible-endpoints">Invocation against OpenAI-compatible endpoints&lt;/h2>
&lt;h3 id="profile-mode-one-load-point">&lt;code>profile&lt;/code> mode: one load point&lt;/h3>
&lt;p>The &lt;code>profile&lt;/code> subcommand measures a fixed operating point (concurrency or request-rate,
synthetic ISL/OSL).&lt;/p>
&lt;p>&lt;strong>Example 1: vLLM endpoint (chat, streaming)&lt;/strong>&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. Bring up the vLLM server&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">docker run -it --net&lt;span class="o">=&lt;/span>host --rm --gpus&lt;span class="o">=&lt;/span>all &lt;span class="se">\
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="se">&lt;/span> vllm/vllm-openai:latest &lt;span class="se">\
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="se">&lt;/span> --model meta-llama/Llama-3.1-70B-Instruct &lt;span class="se">\
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="se">&lt;/span> --dtype float16
&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. Profile with GenAI-Perf&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">genai-perf profile &lt;span class="se">\
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="se">&lt;/span> -m meta-llama/Llama-3.1-70B-Instruct &lt;span class="se">\
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="se">&lt;/span> --endpoint-type chat &lt;span class="se">\
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="se">&lt;/span> --streaming &lt;span class="se">\
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="se">&lt;/span> --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> --synthetic-input-tokens-mean &lt;span class="m">1024&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> --synthetic-input-tokens-stddev &lt;span class="m">128&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> --output-tokens-mean &lt;span class="m">256&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> --output-tokens-stddev &lt;span class="m">0&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> --output-tokens-mean-deterministic &lt;span class="se">\
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="se">&lt;/span> --request-count &lt;span class="m">200&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> --warmup-request-count &lt;span class="m">20&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> --tokenizer meta-llama/Llama-3.1-70B-Instruct
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>&lt;strong>Example 2: completions endpoint (no streaming)&lt;/strong>&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">genai-perf profile &lt;span class="se">\
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="se">&lt;/span> -m meta-llama/Llama-3.1-70B-Instruct &lt;span class="se">\
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="se">&lt;/span> --endpoint-type completions &lt;span class="se">\
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="se">&lt;/span> --concurrency &lt;span class="m">32&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> --synthetic-input-tokens-mean &lt;span class="m">512&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> --synthetic-input-tokens-stddev &lt;span class="m">64&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> --output-tokens-mean &lt;span class="m">128&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> --request-count &lt;span class="m">200&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> --warmup-request-count &lt;span class="m">20&lt;/span>
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>&lt;strong>Example 3: NVIDIA NIM (TRT-LLM backend endpoint, via Triton)&lt;/strong>&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">genai-perf profile &lt;span class="se">\
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="se">&lt;/span> -m meta/llama-3.1-70b-instruct &lt;span class="se">\
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="se">&lt;/span> --backend tensorrtllm &lt;span class="se">\
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="se">&lt;/span> --streaming &lt;span class="se">\
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="se">&lt;/span> --concurrency &lt;span class="m">8&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> --synthetic-input-tokens-mean &lt;span class="m">2048&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> --synthetic-input-tokens-stddev &lt;span class="m">0&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> --output-tokens-mean &lt;span class="m">512&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> --output-tokens-mean-deterministic &lt;span class="se">\
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="se">&lt;/span> --request-count &lt;span class="m">100&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> --warmup-request-count &lt;span class="m">10&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> --generate-plots
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>&lt;strong>Example 4: request-rate instead of concurrency (Poisson)&lt;/strong>&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">genai-perf profile &lt;span class="se">\
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="se">&lt;/span> -m meta-llama/Llama-3.1-70B-Instruct &lt;span class="se">\
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="se">&lt;/span> --endpoint-type chat &lt;span class="se">\
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="se">&lt;/span> --streaming &lt;span class="se">\
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="se">&lt;/span> --request-rate 5.0 &lt;span class="se">\
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="se">&lt;/span> --synthetic-input-tokens-mean &lt;span class="m">1024&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> --output-tokens-mean &lt;span class="m">256&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> --request-count &lt;span class="m">300&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> --warmup-request-count &lt;span class="m">30&lt;/span>
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;h3 id="main-flags-and-what-they-do">Main flags and what they do&lt;/h3>
&lt;table>
&lt;thead>
&lt;tr>
&lt;th>Flag&lt;/th>
&lt;th>Typical values&lt;/th>
&lt;th>Function&lt;/th>
&lt;/tr>
&lt;/thead>
&lt;tbody>
&lt;tr>
&lt;td>&lt;code>--endpoint-type&lt;/code>&lt;/td>
&lt;td>&lt;code>chat&lt;/code>, &lt;code>completions&lt;/code>, &lt;code>embeddings&lt;/code>&lt;/td>
&lt;td>Type of OpenAI-compatible endpoint&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>&lt;code>--streaming&lt;/code>&lt;/td>
&lt;td>(boolean)&lt;/td>
&lt;td>Enables SSE streaming; required to measure real TTFT and ITL&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>&lt;code>--concurrency&lt;/code>&lt;/td>
&lt;td>1, 8, 16, 32, 64&lt;/td>
&lt;td>Number of concurrent requests maintained; GenAI-Perf guarantees N active at all times&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>&lt;code>--request-rate&lt;/code>&lt;/td>
&lt;td>1.0, 5.0, 10.0&lt;/td>
&lt;td>Constant arrival rate (req/s); does not guarantee N active&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>&lt;code>--synthetic-input-tokens-mean&lt;/code>&lt;/td>
&lt;td>128–8192&lt;/td>
&lt;td>Mean of synthetic ISL tokens&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>&lt;code>--synthetic-input-tokens-stddev&lt;/code>&lt;/td>
&lt;td>0–512&lt;/td>
&lt;td>Standard deviation of the ISL (0 = fixed)&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>&lt;code>--output-tokens-mean&lt;/code>&lt;/td>
&lt;td>64–2048&lt;/td>
&lt;td>Mean of target OSL tokens&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>&lt;code>--output-tokens-mean-deterministic&lt;/code>&lt;/td>
&lt;td>(boolean)&lt;/td>
&lt;td>Sets the minimum output tokens = target mean (more precise with TRT-LLM)&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>&lt;code>--request-count&lt;/code>&lt;/td>
&lt;td>100–2000&lt;/td>
&lt;td>Number of requests to benchmark&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>&lt;code>--warmup-request-count&lt;/code>&lt;/td>
&lt;td>10–50&lt;/td>
&lt;td>Warm-up requests discarded from the metrics&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>&lt;code>--generate-plots&lt;/code>&lt;/td>
&lt;td>(boolean)&lt;/td>
&lt;td>Generates PNG plots of TTFT vs ISL, ITL vs token position, etc.&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>&lt;code>--tokenizer&lt;/code>&lt;/td>
&lt;td>HF model id&lt;/td>
&lt;td>Tokenizer for counting tokens; mandatory when ISL/OSL matter&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>&lt;code>--backend&lt;/code>&lt;/td>
&lt;td>&lt;code>tensorrtllm&lt;/code>, &lt;code>vllm&lt;/code>&lt;/td>
&lt;td>Triton backend (for serving directly via Triton without an OpenAI endpoint)&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>&lt;code>--extra-inputs&lt;/code>&lt;/td>
&lt;td>&lt;code>ignore_eos:true&lt;/code>&lt;/td>
&lt;td>Extra request parameters; &lt;code>ignore_eos:true&lt;/code> guarantees a consistent OSL&lt;/td>
&lt;/tr>
&lt;/tbody>
&lt;/table>
&lt;p>&lt;strong>Note on &lt;code>--concurrency&lt;/code> versus &lt;code>--request-rate&lt;/code>:&lt;/strong> NVIDIA recommends using &lt;code>--concurrency&lt;/code>.
With &lt;code>--request-rate&lt;/code>, if the rate exceeds the engine&amp;rsquo;s throughput, the queue grows without
bound and the metrics stop being stable. With &lt;code>--concurrency&lt;/code>, there are always exactly N
active requests, which gives a clean latency-throughput curve.&lt;/p>
&lt;h3 id="output-artefacts">Output artefacts&lt;/h3>
&lt;p>GenAI-Perf dumps all results into an &lt;code>artifacts/&lt;/code> directory:&lt;/p>
&lt;table>
&lt;thead>
&lt;tr>
&lt;th>Artefact&lt;/th>
&lt;th>Format&lt;/th>
&lt;th>Content&lt;/th>
&lt;/tr>
&lt;/thead>
&lt;tbody>
&lt;tr>
&lt;td>&lt;code>profile_export_genai_perf.json&lt;/code>&lt;/td>
&lt;td>JSON&lt;/td>
&lt;td>Full metrics (avg, min, max, P75, P90, P99) + CLI arguments used&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>&lt;code>profile_export_genai_perf.csv&lt;/code>&lt;/td>
&lt;td>CSV&lt;/td>
&lt;td>Exported console tables, ready to import into Excel/pandas&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>&lt;code>profile_export.json&lt;/code>&lt;/td>
&lt;td>JSON&lt;/td>
&lt;td>Raw perf_analyzer data (per-request traces)&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>&lt;code>inputs.json&lt;/code>&lt;/td>
&lt;td>JSON&lt;/td>
&lt;td>Synthetic payloads sent (for reproducibility)&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>PNG plots&lt;/td>
&lt;td>PNG&lt;/td>
&lt;td>TTFT analysis, Request latency, TTFT vs ISL, ITL vs position, ISL vs OSL (with &lt;code>--generate-plots&lt;/code>)&lt;/td>
&lt;/tr>
&lt;/tbody>
&lt;/table>
&lt;hr>
&lt;h2 id="the-analyze-subcommand-automatic-concurrency-sweep">The &lt;code>analyze&lt;/code> subcommand: automatic concurrency sweep&lt;/h2>
&lt;p>The &lt;code>analyze&lt;/code> subcommand sweeps multiple values of a parameter in a single command and
generates a summary CSV.&lt;/p>
&lt;h3 id="concurrency-sweep-the-most-used">Concurrency sweep (the most used)&lt;/h3>
&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"># Sweeps concurrencies 1, 2, 4, 8, 16, 32, 64, 128, 256&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">genai-perf analyze &lt;span class="se">\
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="se">&lt;/span> -m meta-llama/Llama-3.1-70B-Instruct &lt;span class="se">\
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="se">&lt;/span> --endpoint-type chat &lt;span class="se">\
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="se">&lt;/span> --streaming &lt;span class="se">\
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="se">&lt;/span> --sweep-type concurrency &lt;span class="se">\
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="se">&lt;/span> --sweep-range 1:256 &lt;span class="se">\
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="se">&lt;/span> --synthetic-input-tokens-mean &lt;span class="m">1024&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> --output-tokens-mean &lt;span class="m">256&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> --request-count &lt;span class="m">200&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> --warmup-request-count &lt;span class="m">20&lt;/span>
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;h3 id="request-rate-sweep">Request-rate sweep&lt;/h3>
&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"># Sweeps 2, 4, 6, 8, 10, 12 req/s&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">genai-perf analyze &lt;span class="se">\
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="se">&lt;/span> -m meta-llama/Llama-3.1-70B-Instruct &lt;span class="se">\
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="se">&lt;/span> --endpoint-type chat &lt;span class="se">\
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="se">&lt;/span> --streaming &lt;span class="se">\
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="se">&lt;/span> --sweep-type request_rate &lt;span class="se">\
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="se">&lt;/span> --sweep-list 2,4,6,8,10,12 &lt;span class="se">\
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="se">&lt;/span> --synthetic-input-tokens-mean &lt;span class="m">1024&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> --output-tokens-mean &lt;span class="m">256&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> --request-count &lt;span class="m">200&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> --warmup-request-count &lt;span class="m">20&lt;/span>
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;h3 id="isl-sweep-to-study-the-effect-of-prefill">ISL sweep (to study the effect of prefill)&lt;/h3>
&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">genai-perf analyze &lt;span class="se">\
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="se">&lt;/span> -m meta-llama/Llama-3.1-70B-Instruct &lt;span class="se">\
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="se">&lt;/span> --endpoint-type chat &lt;span class="se">\
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="se">&lt;/span> --streaming &lt;span class="se">\
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="se">&lt;/span> --sweep-type input_sequence_length &lt;span class="se">\
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="se">&lt;/span> --sweep-list 256,512,1024,2048,4096 &lt;span class="se">\
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="se">&lt;/span> --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> --output-tokens-mean &lt;span class="m">256&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> --request-count &lt;span class="m">200&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> --warmup-request-count &lt;span class="m">20&lt;/span>
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;h3 id="artefact-structure-of-analyze">Artefact structure of &lt;code>analyze&lt;/code>&lt;/h3>
&lt;p>For a sweep with &lt;code>--sweep-type concurrency --sweep-range 1:32&lt;/code>:&lt;/p>
&lt;pre tabindex="0">&lt;code>artifacts/
llama70b-openai-chat-concurrency1/
inputs.json
profile_export.json
profile_export_genai_perf.json
profile_export_genai_perf.csv
llama70b-openai-chat-concurrency2/
...
llama70b-openai-chat-concurrency4/
...
[...]
analyze_export_genai_perf.csv ← summary of every scenario
checkpoint.json ← allows interrupted sweeps to be resumed
&lt;/code>&lt;/pre>&lt;h3 id="summary-csv-format">Summary CSV format&lt;/h3>
&lt;pre tabindex="0">&lt;code class="language-csv" data-lang="csv">Config Name,Concurrency,ISL,p99 TTFT (ms),p99 ITL (ms),p99 Request Latency (ms),Avg. OTT (tok/s),RPS
llama70b_run_0,1,1024,45.2,6.8,1823.4,128.3,0.55
llama70b_run_1,4,1024,48.1,7.1,1901.2,510.7,2.18
llama70b_run_2,8,1024,61.3,7.9,2142.5,989.2,4.12
llama70b_run_3,16,1024,98.7,9.4,2891.3,1741.6,6.88
llama70b_run_4,32,1024,284.1,14.2,4320.8,2134.2,7.14
llama70b_run_5,64,1024,892.4,28.7,8741.2,2251.8,7.23
&lt;/code>&lt;/pre>&lt;p>The summary CSV also includes a second table with GPU metrics (P99 power, energy, utilisation,
memory) when they are captured via Triton&amp;rsquo;s telemetry URLs
(&lt;code>--server-metrics-urls http://localhost:8002/metrics&lt;/code>).&lt;/p>
&lt;hr>
&lt;h2 id="comparison-table-genai-perf-versus-guidellm-versus-llmperf-versus-vllm-bench">Comparison table: GenAI-Perf versus GuideLLM versus LLMPerf versus vLLM bench&lt;/h2>
&lt;p>The choice of tool changes the number. They are not comparable with each other without
adjustment.&lt;/p>
&lt;table>
&lt;thead>
&lt;tr>
&lt;th>Dimension&lt;/th>
&lt;th>&lt;strong>GenAI-Perf&lt;/strong>&lt;/th>
&lt;th>&lt;strong>GuideLLM&lt;/strong>&lt;/th>
&lt;th>&lt;strong>LLMPerf&lt;/strong>&lt;/th>
&lt;th>&lt;strong>vLLM bench serve&lt;/strong>&lt;/th>
&lt;/tr>
&lt;/thead>
&lt;tbody>
&lt;tr>
&lt;td>&lt;strong>Origin / maintainer&lt;/strong>&lt;/td>
&lt;td>NVIDIA / Triton team (retired Apr 2026)&lt;/td>
&lt;td>Red Hat / vLLM project&lt;/td>
&lt;td>Anyscale / Ray&lt;/td>
&lt;td>vLLM project&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>&lt;strong>Class&lt;/strong>&lt;/td>
&lt;td>multi-process load generator&lt;/td>
&lt;td>multi-process load generator&lt;/td>
&lt;td>multi-process load generator&lt;/td>
&lt;td>single-process micro-bench&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>&lt;strong>Supported endpoints&lt;/strong>&lt;/td>
&lt;td>OpenAI-compatible + KServe + native Triton&lt;/td>
&lt;td>OpenAI-compatible&lt;/td>
&lt;td>OpenAI-compatible&lt;/td>
&lt;td>native vLLM&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>&lt;strong>Main load mode&lt;/strong>&lt;/td>
&lt;td>fixed concurrency N (recommended) or constant request-rate&lt;/td>
&lt;td>synchronous, concurrent, poisson, throughput, &lt;strong>automatic sweep&lt;/strong>&lt;/td>
&lt;td>batches of N concurrent (with a draining period at the end)&lt;/td>
&lt;td>request-rate, concurrency&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>&lt;strong>Draining period&lt;/strong>&lt;/td>
&lt;td>NO — guarantees N active at all times&lt;/td>
&lt;td>NO (poisson)&lt;/td>
&lt;td>YES — at the end of each batch the system empties, concurrency drops to 0&lt;/td>
&lt;td>N/A&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>&lt;strong>Automatic sweep&lt;/strong>&lt;/td>
&lt;td>&lt;code>analyze&lt;/code> (concurrency, request-rate, ISL, OSL)&lt;/td>
&lt;td>&lt;code>--rate-type sweep&lt;/code> (idle to saturation, 10 rounds)&lt;/td>
&lt;td>manual (several runs)&lt;/td>
&lt;td>&lt;code>vllm bench sweep serve&lt;/code>&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>&lt;strong>LLM metrics&lt;/strong>&lt;/td>
&lt;td>TTFT, ITL, e2e latency, OTT, RPS, ISL/OSL&lt;/td>
&lt;td>TTFT, ITL/TPOT, throughput, goodput under SLO&lt;/td>
&lt;td>TTFT, ITL (includes TTFT in the mean), TPS (total benchmark duration)&lt;/td>
&lt;td>TTFT, TPOT, throughput&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>&lt;strong>ITL difference&lt;/strong>&lt;/td>
&lt;td>excludes TTFT: \((e2e - TTFT) / (N_{tok}-1)\)&lt;/td>
&lt;td>excludes TTFT (same as GenAI-Perf)&lt;/td>
&lt;td>includes TTFT in the mean&lt;/td>
&lt;td>excludes TTFT&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>&lt;strong>TPS difference&lt;/strong>&lt;/td>
&lt;td>\(total\_tokens / (T_y - T_x)\)&lt;/td>
&lt;td>throughput + goodput under SLO&lt;/td>
&lt;td>\(total\_tokens / (T_{end} - T_{start})\) — up to 33% lower&lt;/td>
&lt;td>\(total\_tokens / (T_y - T_x)\)&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>&lt;strong>Goodput / SLO&lt;/strong>&lt;/td>
&lt;td>NO&lt;/td>
&lt;td>YES (the key differentiator)&lt;/td>
&lt;td>NO&lt;/td>
&lt;td>NO&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>&lt;strong>Exports&lt;/strong>&lt;/td>
&lt;td>JSON + CSV + PNG + checkpoint&lt;/td>
&lt;td>JSON + YAML + CSV + interactive HTML&lt;/td>
&lt;td>JSON&lt;/td>
&lt;td>console / JSON&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>&lt;strong>Built-in warm-up&lt;/strong>&lt;/td>
&lt;td>YES (&lt;code>--warmup-request-count&lt;/code>)&lt;/td>
&lt;td>YES (warm-up requests)&lt;/td>
&lt;td>NOT native&lt;/td>
&lt;td>YES&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>&lt;strong>Real datasets&lt;/strong>&lt;/td>
&lt;td>OpenOrca, CNN DailyMail, JSONL file, moon_cake&lt;/td>
&lt;td>file with a traffic distribution&lt;/td>
&lt;td>JSONL file&lt;/td>
&lt;td>sharegpt, random&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>&lt;strong>GPU telemetry&lt;/strong>&lt;/td>
&lt;td>YES (via Triton&amp;rsquo;s server-metrics-urls → summary CSV)&lt;/td>
&lt;td>NOT native (integrate DCGM separately)&lt;/td>
&lt;td>NO&lt;/td>
&lt;td>NO&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>&lt;strong>When to choose it&lt;/strong>&lt;/td>
&lt;td>fine profiling of one operating point, comparing configs with the same harness, integration with NIM/Triton&lt;/td>
&lt;td>SLO-driven sweep, finding the knee, sizing replicas&lt;/td>
&lt;td>quick endpoint validation in the Ray ecosystem&lt;/td>
&lt;td>iterating on vLLM flags&lt;/td>
&lt;/tr>
&lt;/tbody>
&lt;/table>
&lt;p>&lt;strong>Fair-comparison rule:&lt;/strong> never cross numbers from different tools for the same conclusion. If
vLLM and TRT-LLM are being compared, use GenAI-Perf against both, with the same ISL/OSL, the
same concurrency and the same &lt;code>--warmup-request-count&lt;/code>. The tool is the constant; the engine is
the variable.&lt;/p>
&lt;hr>
&lt;h2 id="profiling-on-4h100-sxm-80-gb-a-full-sweep-example">Profiling on 4×H100 SXM 80 GB: a full sweep example&lt;/h2>
&lt;p>Generic reference hardware: 4×H100 SXM 80 GB NVLink, model Llama-3.1-70B-Instruct FP16,
ISL=1024 tokens, OSL=256 tokens, vLLM endpoint.&lt;/p>
&lt;h3 id="illustrative-result-of-a-concurrency-sweep-164">Illustrative result of a concurrency sweep (1→64)&lt;/h3>
&lt;table>
&lt;thead>
&lt;tr>
&lt;th>Concurrency&lt;/th>
&lt;th>TTFT P50 (ms)&lt;/th>
&lt;th>TTFT P99 (ms)&lt;/th>
&lt;th>ITL P50 (ms)&lt;/th>
&lt;th>ITL P99 (ms)&lt;/th>
&lt;th>OTT (tok/s)&lt;/th>
&lt;th>RPS&lt;/th>
&lt;/tr>
&lt;/thead>
&lt;tbody>
&lt;tr>
&lt;td>1&lt;/td>
&lt;td>38&lt;/td>
&lt;td>45&lt;/td>
&lt;td>6.4&lt;/td>
&lt;td>7.1&lt;/td>
&lt;td>130&lt;/td>
&lt;td>0.51&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>4&lt;/td>
&lt;td>40&lt;/td>
&lt;td>51&lt;/td>
&lt;td>6.8&lt;/td>
&lt;td>8.2&lt;/td>
&lt;td>510&lt;/td>
&lt;td>2.00&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>8&lt;/td>
&lt;td>46&lt;/td>
&lt;td>62&lt;/td>
&lt;td>7.3&lt;/td>
&lt;td>9.4&lt;/td>
&lt;td>980&lt;/td>
&lt;td>3.83&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>16&lt;/td>
&lt;td>68&lt;/td>
&lt;td>98&lt;/td>
&lt;td>8.9&lt;/td>
&lt;td>12.1&lt;/td>
&lt;td>1,720&lt;/td>
&lt;td>6.72&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>32&lt;/td>
&lt;td>145&lt;/td>
&lt;td>284&lt;/td>
&lt;td>13.7&lt;/td>
&lt;td>21.4&lt;/td>
&lt;td>2,130&lt;/td>
&lt;td>8.32&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>48&lt;/td>
&lt;td>380&lt;/td>
&lt;td>740&lt;/td>
&lt;td>22.3&lt;/td>
&lt;td>38.6&lt;/td>
&lt;td>2,280&lt;/td>
&lt;td>8.90&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>64&lt;/td>
&lt;td>890&lt;/td>
&lt;td>1,820&lt;/td>
&lt;td>41.2&lt;/td>
&lt;td>78.3&lt;/td>
&lt;td>2,340&lt;/td>
&lt;td>9.14&lt;/td>
&lt;/tr>
&lt;/tbody>
&lt;/table>
&lt;p>How to read it: throughput saturates around concurrency 48–64, while TTFT P99 already exceeds
500 ms from concurrency 32 onwards. The &lt;strong>knee&lt;/strong> (the last point with TTFT P99 &amp;lt; 500 ms) is at
concurrency 16 with 1,720 useful tok/s. Anyone reporting &amp;ldquo;2,340 tok/s&amp;rdquo; (concurrency 64) is
describing the maximum throughput, at a point where the system meets no interactive chat SLO
at all.&lt;/p>
&lt;h3 id="formulas-for-the-key-metrics">Formulas for the key metrics&lt;/h3>
$$
\text{ITL} = \frac{e2e\_latency - TTFT}{N_{output\_tokens} - 1}
$$
$$
\text{OTT} = \frac{\sum output\_tokens}{T_y - T_x}
$$
$$
\text{RPS} = \frac{N_{requests}}{T_y - T_x}
$$
&lt;p>where \(T_x\) is the timestamp of the first request sent and \(T_y\) the timestamp of the last
token received from the last request.&lt;/p>
&lt;hr>
&lt;h2 id="synthetic-islosl-profiling-what-to-configure-for-each-use-case">Synthetic ISL/OSL profiling: what to configure for each use case&lt;/h2>
&lt;p>The ISL/OSL parameters determine which engine of the system is stressed: a long ISL stresses
prefill (KV-cache, TTFT); a long OSL stresses decode (ITL, memory bandwidth).&lt;/p>
&lt;table>
&lt;thead>
&lt;tr>
&lt;th>Use case&lt;/th>
&lt;th>Typical ISL&lt;/th>
&lt;th>Typical OSL&lt;/th>
&lt;th>&lt;code>--synthetic-input-tokens-mean&lt;/code>&lt;/th>
&lt;th>&lt;code>--output-tokens-mean&lt;/code>&lt;/th>
&lt;/tr>
&lt;/thead>
&lt;tbody>
&lt;tr>
&lt;td>Short interactive chat&lt;/td>
&lt;td>~300 tok&lt;/td>
&lt;td>~100 tok&lt;/td>
&lt;td>300&lt;/td>
&lt;td>100&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>Code copilot&lt;/td>
&lt;td>~800 tok&lt;/td>
&lt;td>~200 tok&lt;/td>
&lt;td>800&lt;/td>
&lt;td>200&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>Document summarisation&lt;/td>
&lt;td>~2000 tok&lt;/td>
&lt;td>~256 tok&lt;/td>
&lt;td>2000&lt;/td>
&lt;td>256&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>RAG with long context&lt;/td>
&lt;td>~4096 tok&lt;/td>
&lt;td>~512 tok&lt;/td>
&lt;td>4096&lt;/td>
&lt;td>512&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>Batch without latency&lt;/td>
&lt;td>~512 tok&lt;/td>
&lt;td>~1024 tok&lt;/td>
&lt;td>512&lt;/td>
&lt;td>1024&lt;/td>
&lt;/tr>
&lt;/tbody>
&lt;/table>
&lt;p>The &lt;code>--extra-inputs ignore_eos:true&lt;/code> option disables the engine&amp;rsquo;s EOS token, forcing exactly
the requested tokens to be generated. Essential for measuring a consistent OSL on TRT-LLM;
optional on vLLM.&lt;/p>
&lt;hr>
&lt;h2 id="honest-methodology-how-not-to-measure-badly">Honest methodology: how not to measure badly&lt;/h2>
&lt;h3 id="1-warm-up-is-mandatory">1. Warm-up is mandatory&lt;/h3>
&lt;p>Without warm-up, the first requests include the KV-cache loading latency, the engine&amp;rsquo;s
autotuning and the initialisations. GenAI-Perf applies a sliding window to detect stability
(max/min ratio within a margin over the last 3 measurements), but the explicit
&lt;code>--warmup-request-count&lt;/code> discards the first N requests from the metrics. Recommended minimum
value: 10 requests (or 5% of the total if the total is large).&lt;/p>
&lt;h3 id="2-the-concurrency-knee-extend-the-sweep-until-you-see-it">2. The concurrency knee: extend the sweep until you see it&lt;/h3>
&lt;p>Throughput saturates once the engine&amp;rsquo;s effective max batch size is reached. If the sweep stops
before saturation is reached, the &amp;ldquo;maximum capacity&amp;rdquo; reported is the last concurrency tested,
not the real knee. The rule: the sweep must reach a point where TTFT P99 has doubled relative
to the low-load value (or where OTT stops growing by more than 5% between steps). Only then is
the knee visible.&lt;/p>
&lt;p>For a sweep with &lt;code>--sweep-range 1:256&lt;/code>, GenAI-Perf sweeps 1, 2, 4, 8, 16, 32, 64, 128, 256
(powers of 2). On most 70B models over 4×H100, saturation occurs between concurrency 32 and
64; the range 1:128 covers the knee with room to spare.&lt;/p>
&lt;h3 id="3-concurrency-versus-request-rate-choosing-correctly">3. Concurrency versus request-rate: choosing correctly&lt;/h3>
&lt;table>
&lt;thead>
&lt;tr>
&lt;th>Mode&lt;/th>
&lt;th>Behaviour&lt;/th>
&lt;th>When to use&lt;/th>
&lt;/tr>
&lt;/thead>
&lt;tbody>
&lt;tr>
&lt;td>&lt;code>--concurrency N&lt;/code>&lt;/td>
&lt;td>Always N active requests; when one finishes, the next is sent&lt;/td>
&lt;td>Clean latency-throughput curve; comparison between configs&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>&lt;code>--request-rate R&lt;/code>&lt;/td>
&lt;td>1 request is sent every 1/R seconds; the queue can grow without bound&lt;/td>
&lt;td>Simulating traffic with arrivals at a constant rate&lt;/td>
&lt;/tr>
&lt;/tbody>
&lt;/table>
&lt;p>NVIDIA recommends &lt;code>--concurrency&lt;/code> for benchmarking. With &lt;code>--request-rate&lt;/code>, if R exceeds the
engine&amp;rsquo;s maximum throughput, the queue grows indefinitely and the metrics become unstable.&lt;/p>
&lt;h3 id="4-single-process-versus-multi-process-bias">4. Single-process versus multi-process bias&lt;/h3>
&lt;p>GenAI-Perf is multi-process: it maintains N concurrent requests using perf_analyzer&amp;rsquo;s internal
manager. There is no risk of client saturation. This sets it apart from &lt;code>vllm bench serve&lt;/code>
(single-process), which can saturate on the client at high concurrency and give inflated
throughput metrics.&lt;/p>
&lt;h3 id="5-ignore_eos-and-an-inconsistent-osl">5. &lt;code>ignore_eos&lt;/code> and an inconsistent OSL&lt;/h3>
&lt;p>Without &lt;code>--extra-inputs ignore_eos:true&lt;/code>, the engine may generate fewer tokens than requested
(if the model produces an EOS earlier). This makes the real OSL vary between requests and the
ITL become inconsistent between runs. For reproducible benchmarks with a controlled OSL: always
enable &lt;code>ignore_eos:true&lt;/code>.&lt;/p>
&lt;h3 id="6-the-correct-tokenizer">6. The correct tokenizer&lt;/h3>
&lt;p>The &lt;code>--tokenizer&lt;/code> flag must point to the model being served. If it is omitted, GenAI-Perf uses
a default tokenizer that can give different ISL/OSL in the real model&amp;rsquo;s tokens, making the
throughput numbers in tok/s not comparable between models with different vocabularies.&lt;/p>
&lt;h3 id="7-one-run--a-data-point-three-or-more--a-number">7. One run = a data point; three or more = a number&lt;/h3>
&lt;p>Variance between runs under identical conditions can be 5–10% on tail latency metrics (P99). A
single profile is not a defensible number; the mean of three runs with the same config is.&lt;/p>
&lt;div class="diagram" style="max-width:780px;margin:1rem auto;">
&lt;svg viewBox="0 0 780 170" role="img" aria-label="Flow of a reproducible benchmark with GenAI-Perf: deploy engine, warm up, concurrency sweep beyond the knee, collect JSON, compare" xmlns="http://www.w3.org/2000/svg">
&lt;style>.bx{fill:none;stroke:currentColor;stroke-width:1.3}.tl{font:600 12px sans-serif;fill:currentColor}.ts{font:11px sans-serif;fill:currentColor}.ar{fill:none;stroke:currentColor;stroke-width:1.3;marker-end:url(#bm)}&lt;/style>
&lt;defs>&lt;marker id="bm" viewBox="0 0 10 10" refX="9" refY="5" markerWidth="7" markerHeight="7" orient="auto">&lt;path d="M0,0 L10,5 L0,10 z" fill="currentColor"/>&lt;/marker>&lt;/defs>
&lt;rect class="bx" x="20" y="50" width="120" height="44" rx="6"/>&lt;text x="80" y="69" text-anchor="middle" class="tl">1 · Engine&lt;/text>&lt;text x="80" y="85" text-anchor="middle" class="ts">pinned config&lt;/text>
&lt;path class="ar" d="M140,72 L163,72"/>
&lt;rect class="bx" x="163" y="50" width="120" height="44" rx="6"/>&lt;text x="223" y="69" text-anchor="middle" class="tl">2 · Warm-up&lt;/text>&lt;text x="223" y="85" text-anchor="middle" class="ts">≥10 requests&lt;/text>
&lt;path class="ar" d="M283,72 L306,72"/>
&lt;rect class="bx" x="306" y="50" width="130" height="44" rx="6"/>&lt;text x="371" y="69" text-anchor="middle" class="tl">3 · Sweep&lt;/text>&lt;text x="371" y="85" text-anchor="middle" class="ts">1:256, past the knee&lt;/text>
&lt;path class="ar" d="M436,72 L459,72"/>
&lt;rect class="bx" x="459" y="50" width="130" height="44" rx="6"/>&lt;text x="524" y="69" text-anchor="middle" class="tl">4 · Artefacts&lt;/text>&lt;text x="524" y="85" text-anchor="middle" class="ts">versioned JSON + CSV&lt;/text>
&lt;path class="ar" d="M589,72 L612,72"/>
&lt;rect class="bx" x="612" y="50" width="130" height="44" rx="6"/>&lt;text x="677" y="69" text-anchor="middle" class="tl">5 · Compare&lt;/text>&lt;text x="677" y="85" text-anchor="middle" class="ts">P99 at the knee&lt;/text>
&lt;text x="20" y="128" class="ts">Pin: genai-perf version, engine version, model, precision, ISL/OSL, hardware, tokenizer.&lt;/text>
&lt;text x="20" y="145" class="ts">Without that metadata in the JSON, the number is neither reproducible nor comparable.&lt;/text>
&lt;/svg>
&lt;/div>
&lt;hr>
&lt;h2 id="connection-with-the-cost-and-energy-track">Connection with the cost and energy track&lt;/h2>
&lt;p>GenAI-Perf can capture GPU metrics in the same summary CSV if
&lt;code>--server-metrics-urls http://localhost:8002/metrics&lt;/code> is passed (Triton&amp;rsquo;s metrics endpoint,
which exposes power and utilisation via DCGM). The &lt;code>analyze&lt;/code> summary CSV then includes a second
table with P99 power (W), energy (MJ), utilisation (%) and memory (GB) per GPU and per
scenario.&lt;/p>
&lt;p>With that data, a single sweep yields the three axes of the scorecard:&lt;/p>
&lt;table>
&lt;thead>
&lt;tr>
&lt;th>Axis&lt;/th>
&lt;th>Source&lt;/th>
&lt;th>Metric&lt;/th>
&lt;/tr>
&lt;/thead>
&lt;tbody>
&lt;tr>
&lt;td>Performance&lt;/td>
&lt;td>GenAI-Perf&lt;/td>
&lt;td>OTT (tok/s), TTFT P99, ITL P99&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>Energy&lt;/td>
&lt;td>DCGM via server-metrics-urls&lt;/td>
&lt;td>Power (W) → \(J/token = W / OTT\)&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>Cost&lt;/td>
&lt;td>node price&lt;/td>
&lt;td>EUR/hour → \(EUR/token = EUR_{hour} / (OTT \times 3600)\)&lt;/td>
&lt;/tr>
&lt;/tbody>
&lt;/table>
&lt;p>The formula for cost per million tokens:&lt;/p>
$$
CPM = \frac{EUR_{hour}}{OTT \times 3{.}6 \times 10^3}
$$
&lt;p>where \(OTT\) is the output token throughput in tok/s and \(CPM\) is the cost per million
tokens in EUR.&lt;/p>
&lt;p>It connects with &lt;a href="https://blog.lo0.es/en/posts/gpu-observability-llm-inference-twelve-dcgm-vllm-metrics/">GPU observability with DCGM&lt;/a> for
the power capture, and with &lt;a href="https://blog.lo0.es/en/posts/anatomy-llm-request-production-tour-six-stages/">anatomy of an LLM request&lt;/a>
for the breakdown of phases (prefill versus decode) that TTFT and ITL quantify.&lt;/p>
&lt;hr>
&lt;h2 id="status-2026-genai-perf--aiperf">Status 2026: GenAI-Perf → AIPerf&lt;/h2>
&lt;p>NVIDIA announced in April 2026 that GenAI-Perf moves to passive maintenance mode (no new
features) and that the active successor is &lt;strong>AIPerf&lt;/strong> (&lt;code>github.com/ai-dynamo/aiperf&lt;/code>). AIPerf
adds automatic detection of the saturation point (&lt;code>estimatedCapacity&lt;/code>) and is integrated into
the NVIDIA Dynamo pipeline.&lt;/p>
&lt;p>For new benchmarks in production: migrate to AIPerf. To reproduce historical runs documented
with GenAI-Perf, or to use NVIDIA&amp;rsquo;s NIM benchmarking artefacts (which use GenAI-Perf): stay on
GenAI-Perf 25.x.&lt;/p>
&lt;p>The article &lt;a href="https://blog.lo0.es/en/posts/llm-benchmark-tool-catalogue-practical-deep-dive/">LLM benchmark tools, one by one&lt;/a>
covers the full comparison including AIPerf. The article
&lt;a href="https://blog.lo0.es/en/posts/guidellm-validating-slo-under-load-sizing-knee/">GuideLLM: SLO validation under load&lt;/a> details
the SLO-driven sweep that GenAI-Perf does not do natively. For the general methodology context:
&lt;a href="https://blog.lo0.es/en/posts/llm-inference-benchmarking-frameworks-metrics-state-of-the-art/">LLM benchmarking frameworks, state of the art&lt;/a>.&lt;/p>
&lt;hr>
&lt;h2 id="see-also">See also&lt;/h2>
&lt;ul>
&lt;li>&lt;a href="https://blog.lo0.es/en/posts/measurement-bias-reproducibility-llm-benchmarks/">Measurement bias and reproducibility&lt;/a> — the configuration traps that invalidate a GenAI-Perf sweep even when the commands are correct: insufficient warm-up, synthetic versus real dataset, shared environment.&lt;/li>
&lt;li>&lt;a href="https://blog.lo0.es/en/posts/llm-serving-engines-pareto-frontier-comparison/">LLM serving engines compared (vLLM/SGLang/TRT-LLM/Dynamo)&lt;/a> — where the numbers measured with GenAI-Perf land: the goodput-latency Pareto frontier across the four main engines.&lt;/li>
&lt;/ul>
&lt;h2 id="sources">Sources&lt;/h2>
&lt;ul>
&lt;li>NVIDIA · GenAI-Perf — README oficial — &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 · Tutorial LLM con GenAI-Perf (comandos y tablas de salida reales) — &lt;a href="https://docs.nvidia.com/deeplearning/triton-inference-server/user-guide/docs/perf_analyzer/genai-perf/docs/tutorial.html">https://docs.nvidia.com/deeplearning/triton-inference-server/user-guide/docs/perf_analyzer/genai-perf/docs/tutorial.html&lt;/a>&lt;/li>
&lt;li>NVIDIA · GenAI-Perf Analyze subcommand (sweep, CSV resumen, checkpoint) — &lt;a href="https://docs.nvidia.com/deeplearning/triton-inference-server/user-guide/docs/perf_analyzer/genai-perf/docs/analyze.html">https://docs.nvidia.com/deeplearning/triton-inference-server/user-guide/docs/perf_analyzer/genai-perf/docs/analyze.html&lt;/a>&lt;/li>
&lt;li>NVIDIA · NIM LLM Benchmarking — Métricas (definiciones TTFT, ITL, TPS, RPS, fórmulas exactas) — &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>NVIDIA · NIM LLM Benchmarking — Parámetros y buenas prácticas (ISL/OSL, concurrencia vs request-rate, ignore_eos) — &lt;a href="https://docs.nvidia.com/nim/benchmarking/llm/latest/parameters.html">https://docs.nvidia.com/nim/benchmarking/llm/latest/parameters.html&lt;/a>&lt;/li>
&lt;li>NVIDIA Technical Blog · Measuring Generative AI Model Performance Using NVIDIA GenAI-Perf and an OpenAI-Compatible API — &lt;a href="https://developer.nvidia.com/blog/measuring-generative-ai-model-performance-using-nvidia-genai-perf-and-an-openai-compatible-api/">https://developer.nvidia.com/blog/measuring-generative-ai-model-performance-using-nvidia-genai-perf-and-an-openai-compatible-api/&lt;/a>&lt;/li>
&lt;li>GitHub · triton-inference-server/perf_analyzer (genai-perf) — &lt;a href="https://github.com/triton-inference-server/perf_analyzer/blob/main/genai-perf/README.md">https://github.com/triton-inference-server/perf_analyzer/blob/main/genai-perf/README.md&lt;/a>&lt;/li>
&lt;li>GitHub · ray-project/llmperf (métricas y diferencias metodológicas) — &lt;a href="https://github.com/ray-project/llmperf">https://github.com/ray-project/llmperf&lt;/a>&lt;/li>
&lt;li>PyPI · nvidia-genai-perf-eval — &lt;a href="https://pypi.org/project/nvidia-genai-perf-eval/">https://pypi.org/project/nvidia-genai-perf-eval/&lt;/a>&lt;/li>
&lt;li>Macnica · Benchmarking LLM Applications Part 1: What is GenAI-Perf? — &lt;a href="https://www.macnica.co.jp/en/business/semiconductor/articles/nvidia/145977/">https://www.macnica.co.jp/en/business/semiconductor/articles/nvidia/145977/&lt;/a>&lt;/li>
&lt;/ul></description></item><item><title>The signature-dish specialist: vLLM's attention backend (FlashAttention, FlashInfer and the prefill/decode asymmetry)</title><link>https://blog.lo0.es/en/posts/vllm-attention-backend-flashattention-flashinfer-prefill-decode-asymmetry/</link><pubDate>Mon, 08 Jun 2026 05:40:00 +0200</pubDate><guid>https://blog.lo0.es/en/posts/vllm-attention-backend-flashattention-flashinfer-prefill-decode-asymmetry/</guid><description>&lt;blockquote>
&lt;p>Part of the &lt;em>under the engine&lt;/em> series. The &lt;a href="https://blog.lo0.es/en/posts/pagedattention-vllm-block-manager/">PagedAttention post&lt;/a> explained where the KV lives (in paged blocks). This one explains who reads it and how: the attention kernel. And it connects with &lt;a href="https://blog.lo0.es/en/posts/flashattention-librarian-io-awareness-async-blackwell-asymmetry/">FlashAttention v1-v4&lt;/a>, which took that kernel apart from the inside; here we look at the level above, how vLLM chooses between several kernels and why it needs more than one.&lt;/p>
&lt;/blockquote>
&lt;h2 id="tldr">TL;DR&lt;/h2>
&lt;p>An LLM forward is, for the most part, standard matrix multiplications that any library does well. The exception that decides performance is &lt;strong>attention&lt;/strong>, and having one good kernel is not enough: you need two, because the two phases of inference are physically opposite problems. &lt;strong>Prefill&lt;/strong> processes the whole prompt: many queries against many keys, dense and &lt;strong>compute-bound&lt;/strong>, the home ground of &lt;a href="https://blog.lo0.es/en/posts/flashattention-librarian-io-awareness-async-blackwell-asymmetry/">FlashAttention&lt;/a>&amp;rsquo;s IO-aware tiling. &lt;strong>Decode&lt;/strong> generates one token: a single query against all the accumulated KV, thin and &lt;strong>memory-bound&lt;/strong>, where the only thing that matters is saturating HBM bandwidth while reading the paged KV. That is why vLLM does not have &amp;ldquo;the attention kernel&amp;rdquo; but a &lt;strong>switchable backend&lt;/strong> (FLASH_ATTN, FLASHINFER, TRITON_ATTN and others) plus logic that picks according to the GPU: by default FA4 on Blackwell (SM100), FA3 on Hopper (SM90), FA2 elsewhere, with &lt;strong>FlashInfer&lt;/strong> as the alternative that compiles bespoke kernels (JIT) and knows how to do &lt;strong>cascade attention&lt;/strong> for shared prefixes. This post explains why prefill and decode are opposites (with arithmetic intensity), how the backend reads paged KV, how the engine chooses, what FlashInfer brings, the 10 knobs and the trap of pinning a backend blindly. On the generic 4×H100 SXM cluster.&lt;/p>
&lt;h2 id="where-you-are-the-specialist-not-the-commis">Where you are: the specialist, not the commis&lt;/h2>
&lt;p>In the kitchen, almost all the work is chopping and sautéing: standard operations any competent commis chef executes, the matrix multiplications of the &lt;em>feed-forward&lt;/em> layers and the projections. There is one dish that is not delegated: the signature dish, the one that defines the restaurant. That dish is &lt;strong>attention&lt;/strong>, and it has a quirk: it is cooked in two radically different ways depending on the moment of service.&lt;/p>
&lt;p>During &lt;em>prefill&lt;/em>, when a new order arrives with its whole prompt, you cook big: a lot of raw material at once, a lot of fire, an intense operation that fills the stoves. During &lt;em>decode&lt;/em>, when a table asks for &amp;ldquo;one more dish&amp;rdquo;, you cook à la carte: a single dish, but you have to go to the larder and bring back every ingredient that table has accumulated over the whole meal. One is a problem of firepower; the other, of larder speed. The same specialist does not do both well. That is why vLLM has several, and a chef who decides which one steps up depending on the GPU and the phase. That is the &lt;strong>attention backend&lt;/strong>.&lt;/p>
&lt;h2 id="why-prefill-and-decode-are-opposite-problems">Why prefill and decode are opposite problems&lt;/h2>
&lt;p>This is the central idea, and one sum proves it: &lt;strong>arithmetic intensity&lt;/strong> (FLOPs per byte read). An operation with high intensity is limited by compute; one with low intensity, by memory.&lt;/p>
&lt;p>&lt;strong>Prefill.&lt;/strong> We serve $N$ queries (the whole prompt) against $N$ keys. The $QK^\top$ operation and the $\text{softmax}\cdot V$ do on the order of $N^2 d$ FLOPs and read on the order of $N d$ of data. Intensity grows with $N$:&lt;/p>
$$I_\text{prefill} \sim \frac{N^2 d}{N d} = N$$
&lt;p>With large $N$ (a prompt of thousands of tokens), intensity is high: &lt;strong>compute-bound&lt;/strong>. This is where FlashAttention&amp;rsquo;s tiling squeezes the tensor cores and where you get close to the GPU&amp;rsquo;s peak TFLOPS.&lt;/p>
&lt;p>&lt;strong>Decode.&lt;/strong> We serve one query (the new token) against $L$ keys (all the accumulated KV). FLOPs on the order of $L d$; bytes read on the order of $L d s$ (the entire KV has to be read from HBM). Intensity is:&lt;/p>
$$I_\text{decode} \sim \frac{L d}{L d s} = \frac{1}{s} \quad (\approx 0.5 \text{ FLOP/byte in FP16})$$
&lt;p>Constant and tiny: &lt;strong>memory-bound&lt;/strong>. The decode kernel is not limited by how much the GPU can compute but by how fast it reads the KV from HBM. It makes no difference that the H100 has 132 idle SMs (&lt;a href="https://blog.lo0.es/en/posts/sms-cuda-streams-cuda-graphs-inference/">see the SM post&lt;/a>): the bottleneck is the 3.35 TB/s of bandwidth, and the decode kernel exists so as not to waste a single one of those bytes/s.&lt;/p>
&lt;svg viewBox="0 0 720 220" xmlns="http://www.w3.org/2000/svg" font-family="sans-serif" font-size="12" role="img" aria-label="Prefill compute-bound vs decode memory-bound">
&lt;line x1="60" y1="180" x2="700" y2="180" stroke="currentColor" stroke-width="1.5"/>
&lt;line x1="60" y1="180" x2="60" y2="30" stroke="currentColor" stroke-width="1.5"/>
&lt;text x="40" y="34" fill="currentColor" font-size="11" transform="rotate(-90 40 34)">performance&lt;/text>
&lt;text x="380" y="205" text-anchor="middle" fill="currentColor" font-size="11">arithmetic intensity (FLOP/byte)&lt;/text>
&lt;path d="M60 180 L300 60" stroke="#16a34a" stroke-width="2" fill="none"/>
&lt;line x1="300" y1="60" x2="700" y2="60" stroke="#16a34a" stroke-width="2"/>
&lt;text x="500" y="52" fill="#16a34a" font-size="11">compute roof (peak TFLOPS)&lt;/text>
&lt;circle cx="95" cy="160" r="5" fill="#2563eb"/>&lt;text x="105" y="158" fill="#2563eb">decode (I≈0.5): memory-bound, limited by HBM BW&lt;/text>
&lt;circle cx="430" cy="60" r="5" fill="#7c3aed"/>&lt;text x="330" y="48" fill="#7c3aed">prefill (I≈N): compute-bound, limited by compute&lt;/text>
&lt;text x="70" y="120" fill="currentColor" font-size="10">the ramp = the&lt;/text>
&lt;text x="70" y="134" fill="currentColor" font-size="10">memory-bound region&lt;/text>
&lt;/svg>
&lt;p>The design consequence: a kernel optimised for prefill (dense tiling, maximum tensor-core occupancy) is not the optimum for decode (coalesced reads of the paged KV, minimum latency). Serious servers have different kernels, or one kernel with two paths. In models with &lt;strong>MLA&lt;/strong> (multi-head latent attention), vLLM goes as far as using &lt;strong>separate backends&lt;/strong> for prefill and decode, selectable independently (&lt;a href="https://docs.vllm.ai/en/latest/design/attention_backends/">attention backends, vLLM&lt;/a>).&lt;/p>
&lt;h2 id="the-schedulers-trick-prefill-and-decode-in-the-same-forward">The scheduler&amp;rsquo;s trick: prefill and decode in the same forward&lt;/h2>
&lt;p>Here the circle closes with the &lt;a href="https://blog.lo0.es/en/posts/vllm-scheduler-step-maitre-d-each-round/">scheduler post&lt;/a>. Because vLLM V1 mixes requests in prefill and in decode in every step, one single forward has to serve both. The backend receives metadata telling it, for each sequence in the batch, how many queries it brings and how much KV it has to read, and applies the right path to each one. That is why the attention backend and the scheduler are coupled: the first has to digest the heterogeneous batch the second builds.&lt;/p>
&lt;h2 id="how-the-backend-reads-the-paged-kv">How the backend reads the paged KV&lt;/h2>
&lt;p>The kernel does not receive a contiguous KV tensor: it receives the &lt;strong>block table&lt;/strong> from the &lt;a href="https://blog.lo0.es/en/posts/pagedattention-vllm-block-manager/">block manager&lt;/a> and does a gather over the physical blocks. This imposes a real constraint on the backend: it has to support the paged &lt;em>layout&lt;/em> and vLLM&amp;rsquo;s &lt;code>block_size&lt;/code>. Not every kernel in the world does; the ones vLLM integrates (FlashAttention, FlashInfer, Triton) are adapted to read KV in fixed-size blocks scattered across HBM. That is why you cannot plug in any attention kernel from a paper: it has to speak the language of the pigeonhole larder.&lt;/p>
&lt;h2 id="the-backends-and-how-the-engine-chooses">The backends and how the engine chooses&lt;/h2>
&lt;p>vLLM exposes a &lt;strong>backend abstraction&lt;/strong> with several implementations (&lt;a href="https://deepwiki.com/vllm-project/vllm/8.2-flashattention-and-flashinfer">vLLM deepwiki&lt;/a>):&lt;/p>
&lt;ul>
&lt;li>&lt;strong>FLASH_ATTN&lt;/strong> — the &lt;a href="https://blog.lo0.es/en/posts/flashattention-librarian-io-awareness-async-blackwell-asymmetry/">FlashAttention&lt;/a> family. By default the version is picked by architecture: FA4 on SM100 (Blackwell), FA3 on SM90 (Hopper), FA2 elsewhere, configurable with &lt;code>flash_attn_version&lt;/code>.&lt;/li>
&lt;li>&lt;strong>FLASHINFER&lt;/strong> — an attention engine with &lt;strong>JIT&lt;/strong> compilation and specialisable &lt;em>kernels&lt;/em>; strong on heterogeneous KV and shared prefixes.&lt;/li>
&lt;li>&lt;strong>TRITON_ATTN&lt;/strong> — written in Triton, portable and with no dependency on precompiled CUDA binaries (&lt;a href="https://vllm.ai/blog/2026-03-04-vllm-triton-backend-deep-dive">Triton backend deep dive, vLLM, mar-2026&lt;/a>).&lt;/li>
&lt;li>Specific backends for &lt;strong>MLA&lt;/strong> and for non-NVIDIA hardware.&lt;/li>
&lt;/ul>
&lt;p>Selection is automatic unless you force it with &lt;code>VLLM_ATTENTION_BACKEND&lt;/code>. The heuristic tries FlashAttention first; on Blackwell (SM100) the fallback order for MLA is TRT-LLM Ragged → FlashInfer → others; on other GPUs only FlashAttention is considered for the main path (&lt;a href="https://docs.vllm.ai/en/latest/design/attention_backends/">attention backends, vLLM&lt;/a>). The decision depends on: architecture (SM), dtype (FP16/BF16/FP8), head dimension, and whether the load needs a feature only one backend has (cascade attention, certain &lt;em>soft caps&lt;/em>, FP8 in KV).&lt;/p>
&lt;svg viewBox="0 0 720 220" xmlns="http://www.w3.org/2000/svg" font-family="sans-serif" font-size="12" role="img" aria-label="Attention backend selection">
&lt;rect x="270" y="20" width="180" height="36" rx="6" fill="none" stroke="currentColor" stroke-width="1.5"/>
&lt;text x="360" y="43" text-anchor="middle" fill="currentColor">which architecture (SM)?&lt;/text>
&lt;rect x="40" y="100" width="160" height="36" rx="6" fill="none" stroke="#7c3aed" stroke-width="1.5"/>
&lt;text x="120" y="123" text-anchor="middle" fill="#7c3aed">SM100 Blackwell → FA4&lt;/text>
&lt;rect x="280" y="100" width="160" height="36" rx="6" fill="none" stroke="#2563eb" stroke-width="1.5"/>
&lt;text x="360" y="123" text-anchor="middle" fill="#2563eb">SM90 Hopper → FA3&lt;/text>
&lt;rect x="520" y="100" width="160" height="36" rx="6" fill="none" stroke="#16a34a" stroke-width="1.5"/>
&lt;text x="600" y="123" text-anchor="middle" fill="#16a34a">rest → FA2&lt;/text>
&lt;path d="M320 56 L120 100" stroke="currentColor" stroke-width="1.2" marker-end="url(#c)"/>
&lt;path d="M360 56 V100" stroke="currentColor" stroke-width="1.2" marker-end="url(#c)"/>
&lt;path d="M400 56 L600 100" stroke="currentColor" stroke-width="1.2" marker-end="url(#c)"/>
&lt;rect x="200" y="170" width="320" height="36" rx="6" fill="none" stroke="currentColor" stroke-width="1.5" stroke-dasharray="4 3"/>
&lt;text x="360" y="193" text-anchor="middle" fill="currentColor">special feature? (cascade, FP8 KV, MLA) → FlashInfer / specific&lt;/text>
&lt;path d="M360 136 V170" stroke="currentColor" stroke-width="1.2" marker-end="url(#c)"/>
&lt;defs>&lt;marker id="c" markerWidth="8" markerHeight="8" refX="7" refY="4" orient="auto">&lt;path d="M0 0 L8 4 L0 8 z" fill="currentColor"/>&lt;/marker>&lt;/defs>
&lt;/svg>
&lt;h2 id="what-flashinfer-brings-jit-and-cascade-attention">What FlashInfer brings: JIT and cascade attention&lt;/h2>
&lt;p>FlashInfer does not compete with FlashAttention on &amp;ldquo;being a bit faster&amp;rdquo;; it attacks a different problem: the &lt;strong>heterogeneity&lt;/strong> of the KV in real serving (&lt;a href="https://arxiv.org/abs/2501.01005">FlashInfer, arXiv 2501.01005&lt;/a>). Two ideas:&lt;/p>
&lt;p>&lt;strong>JIT compilation.&lt;/strong> Instead of a monolithic kernel, FlashInfer generates bespoke kernels for the attention variant, the problem shape and the KV layout you have, injecting &lt;em>functors&lt;/em> (query/key/logit transformations, masks). It specialises rather than generalises.&lt;/p>
&lt;p>&lt;strong>Cascade attention.&lt;/strong> Here is the jewel for serving with shared prefixes. If $R$ requests share a prefix of $P$ tokens (a common system prompt), naive attention would read that prefix $R$ times. Cascade attention computes it once against the shared prefix and then combines with each request&amp;rsquo;s own suffix:&lt;/p>
$$\text{reads: } \underbrace{R \cdot (P + s_i)}_{\text{naive}} \;\longrightarrow\; \underbrace{P + \textstyle\sum_i s_i}_{\text{cascade}}$$
&lt;p>With $R=50$ requests and a prefix $P=1000$, that is reading 50,000 prefix tokens versus 1,000. It is the &lt;strong>natural complement&lt;/strong> to &lt;a href="https://blog.lo0.es/en/posts/prefix-cache-hit-rate-engineering/">prefix caching&lt;/a>: the block manager shares the prefix&amp;rsquo;s &lt;em>memory&lt;/em>, and cascade attention shares the &lt;em>compute&lt;/em> of attending over it.&lt;/p>
&lt;h2 id="the-arithmetic-that-matters-when-switching-backend-gives-you-something">The arithmetic that matters: when switching backend gives you something&lt;/h2>
&lt;p>The backend only moves the needle where attention is the bottleneck. In memory-bound decode, a kernel that exploits HBM bandwidth better gives a real improvement; in compute-bound prefill with long sequences, FA3/FA4 getting close to the tensor-core peak gives a real improvement. But if your bottleneck is in another layer, &lt;a href="https://blog.lo0.es/en/posts/sms-cuda-streams-cuda-graphs-inference/">launch overhead&lt;/a>, a badly sized &lt;a href="https://blog.lo0.es/en/posts/vllm-scheduler-step-maitre-d-each-round/">scheduler&lt;/a>, the &lt;a href="https://blog.lo0.es/en/posts/pantry-lift-from-disk-to-hbm-why-the-kitchen-opens-late/">cold start&lt;/a>, then switching backend does not touch that part. The rule, once again: measure the regime before optimising.&lt;/p>
&lt;h2 id="the-10-knobs">The 10 knobs&lt;/h2>
&lt;table>
&lt;thead>
&lt;tr>
&lt;th>#&lt;/th>
&lt;th>Knob&lt;/th>
&lt;th>What it controls&lt;/th>
&lt;th>Cost / risk&lt;/th>
&lt;/tr>
&lt;/thead>
&lt;tbody>
&lt;tr>
&lt;td>1&lt;/td>
&lt;td>&lt;code>VLLM_ATTENTION_BACKEND&lt;/code>&lt;/td>
&lt;td>force a backend&lt;/td>
&lt;td>mismatch with hardware/feature&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>2&lt;/td>
&lt;td>&lt;code>flash_attn_version&lt;/code> (2/3/4)&lt;/td>
&lt;td>FA version&lt;/td>
&lt;td>version unsupported on your SM&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>3&lt;/td>
&lt;td>enable FlashInfer&lt;/td>
&lt;td>JIT + cascade&lt;/td>
&lt;td>initial JIT compilation time&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>4&lt;/td>
&lt;td>cascade attention&lt;/td>
&lt;td>prefix compute reuse&lt;/td>
&lt;td>only helps with a heavily shared prefix&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>5&lt;/td>
&lt;td>&lt;code>kv_cache_dtype&lt;/code> (FP8)&lt;/td>
&lt;td>FP8 support in the kernel&lt;/td>
&lt;td>not every backend/SM supports it&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>6&lt;/td>
&lt;td>&lt;code>block_size&lt;/code>&lt;/td>
&lt;td>layout the kernel must read&lt;/td>
&lt;td>consistency with PagedAttention&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>7&lt;/td>
&lt;td>MLA prefill backend&lt;/td>
&lt;td>kernel for the dense phase&lt;/td>
&lt;td>MLA models only&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>8&lt;/td>
&lt;td>MLA decode backend&lt;/td>
&lt;td>kernel for the thin phase&lt;/td>
&lt;td>MLA models only&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>9&lt;/td>
&lt;td>soft cap / sliding window&lt;/td>
&lt;td>features that restrict backends&lt;/td>
&lt;td>fewer kernel options&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>10&lt;/td>
&lt;td>head_dim / variant&lt;/td>
&lt;td>which kernels are eligible&lt;/td>
&lt;td>exotic models with no support&lt;/td>
&lt;/tr>
&lt;/tbody>
&lt;/table>
&lt;h2 id="how-it-connects-to-the-rest-of-the-stack">How it connects to the rest of the stack&lt;/h2>
&lt;p>&lt;strong>With FlashAttention.&lt;/strong> The &lt;a href="https://blog.lo0.es/en/posts/flashattention-librarian-io-awareness-async-blackwell-asymmetry/">FA post&lt;/a> explains the kernel from the inside (tiling, online softmax, FA1-4); this is the level above, how vLLM chooses between kernels and why it needs more than one.&lt;/p>
&lt;p>&lt;strong>With PagedAttention.&lt;/strong> The backend &lt;strong>reads&lt;/strong> the KV that the &lt;a href="https://blog.lo0.es/en/posts/pagedattention-vllm-block-manager/">block manager&lt;/a> places in blocks; it has to speak block-table.&lt;/p>
&lt;p>&lt;strong>With the scheduler.&lt;/strong> The &lt;a href="https://blog.lo0.es/en/posts/vllm-scheduler-step-maitre-d-each-round/">scheduler&lt;/a> builds mixed prefill+decode batches; the backend has to serve both regimes in a single forward.&lt;/p>
&lt;p>&lt;strong>With CUDA graphs.&lt;/strong> Attention kernels are captured in the &lt;a href="https://blog.lo0.es/en/posts/sms-cuda-streams-cuda-graphs-inference/">CUDA graphs&lt;/a>; a backend that launches many small kernels benefits more from capture.&lt;/p>
&lt;p>&lt;strong>With prefix caching.&lt;/strong> Cascade attention is the &lt;em>compute&lt;/em> side of what &lt;a href="https://blog.lo0.es/en/posts/prefix-cache-hit-rate-engineering/">prefix caching&lt;/a> does in &lt;em>memory&lt;/em>.&lt;/p>
&lt;p>&lt;strong>With FP8.&lt;/strong> Attending over KV in &lt;a href="https://blog.lo0.es/en/posts/fp8-end-to-end-enable-measure-quality-decide/">FP8&lt;/a> requires the backend to have the FP8 path; not all of them do on every architecture.&lt;/p>
&lt;h2 id="traps-and-things-that-are-not-what-they-look-like">Traps and things that are not what they look like&lt;/h2>
&lt;p>&lt;strong>&amp;ldquo;FlashInfer is always faster than FlashAttention.&amp;rdquo;&lt;/strong> No. FlashInfer wins when its specialisation (cascade, heterogeneous KV, a particular attention variant) applies to your load; in classic dense prefill, FA3/FA4 usually does as well or better. It depends on the regime, there is no universal winner.&lt;/p>
&lt;p>&lt;strong>&amp;ldquo;A good attention kernel works for everything.&amp;rdquo;&lt;/strong> The underlying error of this post. Prefill and decode are compute-bound and memory-bound respectively; a kernel tuned for one wastes effort on the other. That is why there are separate paths (and separate backends in MLA).&lt;/p>
&lt;p>&lt;strong>&amp;ldquo;Decode is compute-bound because the GPU is at 100%.&amp;rdquo;&lt;/strong> &lt;code>nvidia-smi&lt;/code> at 100% is misleading (&lt;a href="https://blog.lo0.es/en/posts/sms-cuda-streams-cuda-graphs-inference/">see the SM post&lt;/a>): decode is &lt;strong>memory-bound&lt;/strong>, the GPU is moving KV, not computing. Optimising decode&amp;rsquo;s compute is polishing what is not the bottleneck.&lt;/p>
&lt;p>&lt;strong>&amp;ldquo;I pin &lt;code>VLLM_ATTENTION_BACKEND&lt;/code> and forget about it.&amp;rdquo;&lt;/strong> Pinning a backend by hand can leave you on a suboptimal one when you change GPU or version, or force a slow fallback if your hardware does not support what you asked for. Autoselection usually gets it right; pin it only with a measurement that justifies it.&lt;/p>
&lt;p>&lt;strong>&amp;ldquo;Cascade attention always helps.&amp;rdquo;&lt;/strong> Only with a &lt;strong>heavily shared&lt;/strong> prefix across many concurrent requests. If each request has its own context, there is nothing to share and the overhead of organising the cascade is not amortised.&lt;/p>
&lt;p>&lt;strong>&amp;ldquo;The attention backend is the bottleneck, that is why I am slow.&amp;rdquo;&lt;/strong> Almost always the bottleneck is higher up (launch, scheduling, memory) or lower down (bandwidth). The backend matters where attention dominates; measure it with &lt;code>nsys&lt;/code>/DCGM before changing it.&lt;/p>
&lt;h2 id="conclusion">Conclusion&lt;/h2>
&lt;p>Of everything an LLM does while generating text, almost all of it is matrix multiplications that any library resolves. Performance is decided in a single kernel, attention, and the surprise is that it is not even &lt;em>one&lt;/em> kernel: it is two opposite problems wearing the same name. Prefill wants fire, dense compute over thousands of tokens, and decode wants a fast larder, reading a token&amp;rsquo;s entire KV with the minimum waste of bandwidth. That is why vLLM did not pick a winning kernel but an abstraction that switches: FlashAttention tuned to each architecture for the general case, FlashInfer compiling bespoke kernels when there is heterogeneity or prefixes to share, Triton for portability. The head chef does not cook the signature dish just one way: he looks at who is ordering and at what point of service, and sends out the right specialist. The lesson for whoever is tuning is the usual one in this series: before changing specialist, make sure the signature dish really is what is holding you back.&lt;/p>
&lt;h2 id="see-also">See also&lt;/h2>
&lt;ul>
&lt;li>
&lt;p>&lt;a href="https://blog.lo0.es/en/posts/multimodal-on-premise-serving-vlm-vllm/">Multimodal on-premise: serving a VLM with vLLM (vision + language)&lt;/a> — the token and KV-cache cost that images add to prefill in a VLM.&lt;/p>
&lt;/li>
&lt;li>
&lt;p>&lt;a href="https://blog.lo0.es/en/posts/flashattention-librarian-io-awareness-async-blackwell-asymmetry/">FlashAttention v1/v2/v3/v4&lt;/a> — the kernel from the inside; this post is the level above (how a kernel is chosen).&lt;/p>
&lt;/li>
&lt;li>
&lt;p>&lt;a href="https://blog.lo0.es/en/posts/pagedattention-vllm-block-manager/">PagedAttention and the block manager&lt;/a> — the paged KV the backend reads via the block table.&lt;/p>
&lt;/li>
&lt;li>
&lt;p>&lt;a href="https://blog.lo0.es/en/posts/vllm-scheduler-step-maitre-d-each-round/">The pass: vLLM&amp;rsquo;s scheduler step&lt;/a> — the mixed prefill+decode batch the backend digests in one forward.&lt;/p>
&lt;/li>
&lt;li>
&lt;p>&lt;a href="https://blog.lo0.es/en/posts/sms-cuda-streams-cuda-graphs-inference/">SMs, CUDA streams and CUDA graphs&lt;/a> — why &lt;code>nvidia-smi&lt;/code> at 100% does not mean compute-bound, and where attention kernels are captured.&lt;/p>
&lt;/li>
&lt;li>
&lt;p>&lt;a href="https://blog.lo0.es/en/posts/prefix-cache-hit-rate-engineering/">Prefix cache hit rate engineering&lt;/a> — the memory side of what cascade attention does in compute.&lt;/p>
&lt;/li>
&lt;li>
&lt;p>&lt;a href="https://blog.lo0.es/en/posts/fp8-end-to-end-enable-measure-quality-decide/">FP8 end-to-end: weights and KV&lt;/a> — the FP8 path the backend needs to support.&lt;/p>
&lt;/li>
&lt;li>
&lt;p>&lt;a href="https://blog.lo0.es/en/posts/continuous-batching-orca-vllm-chunked-prefill-goodput/">Continuous batching&lt;/a> — why one forward has to serve prefill and decode at the same time.&lt;/p>
&lt;/li>
&lt;li>
&lt;p>&lt;a href="https://blog.lo0.es/en/posts/kv-cache-working-memory-llm-inference/">KV cache: the working memory&lt;/a> — the data the decode kernel reads in full on every step.&lt;/p>
&lt;/li>
&lt;/ul>
&lt;h2 id="references">References&lt;/h2>
&lt;ul>
&lt;li>vLLM, &lt;em>Attention Backends&lt;/em> (selección, FA2/3/4 por arquitectura, MLA): &lt;a href="https://docs.vllm.ai/en/latest/design/attention_backends/">https://docs.vllm.ai/en/latest/design/attention_backends/&lt;/a>.&lt;/li>
&lt;li>vLLM / DeepWiki, &lt;em>FlashAttention and FlashInfer&lt;/em>: &lt;a href="https://deepwiki.com/vllm-project/vllm/8.2-flashattention-and-flashinfer">https://deepwiki.com/vllm-project/vllm/8.2-flashattention-and-flashinfer&lt;/a>.&lt;/li>
&lt;li>vLLM, &lt;em>Triton Attention Backend Deep Dive&lt;/em> (mar-2026): &lt;a href="https://vllm.ai/blog/2026-03-04-vllm-triton-backend-deep-dive">https://vllm.ai/blog/2026-03-04-vllm-triton-backend-deep-dive&lt;/a>.&lt;/li>
&lt;li>Z. Ye et al., &lt;em>FlashInfer: Efficient and Customizable Attention Engine for LLM Inference Serving&lt;/em> (arXiv 2501.01005): &lt;a href="https://arxiv.org/abs/2501.01005">https://arxiv.org/abs/2501.01005&lt;/a>.&lt;/li>
&lt;li>T. Dao, &lt;em>FlashAttention-2&lt;/em> / &lt;em>FlashAttention-3&lt;/em> (kernel IO-aware, async Hopper): &lt;a href="https://github.com/Dao-AILab/flash-attention">https://github.com/Dao-AILab/flash-attention&lt;/a>.&lt;/li>
&lt;/ul></description></item></channel></rss>