<?xml version="1.0" encoding="utf-8" standalone="yes"?><rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom"><channel><title>Flashattention on lo0 — Tech Blog</title><link>https://blog.lo0.es/en/tags/flashattention/</link><description>Recent content in Flashattention on lo0 — Tech Blog</description><generator>Hugo -- gohugo.io</generator><language>en</language><lastBuildDate>Mon, 08 Jun 2026 05:40:00 +0200</lastBuildDate><atom:link href="https://blog.lo0.es/en/tags/flashattention/index.xml" rel="self" type="application/rss+xml"/><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><item><title>FlashAttention v1/v2/v3/v4: the librarian who never clears the desk — IO-awareness, async and Blackwell's asymmetry</title><link>https://blog.lo0.es/en/posts/flashattention-librarian-io-awareness-async-blackwell-asymmetry/</link><pubDate>Fri, 29 May 2026 17:00:00 +0200</pubDate><guid>https://blog.lo0.es/en/posts/flashattention-librarian-io-awareness-async-blackwell-asymmetry/</guid><description>&lt;blockquote>
&lt;p>This post complements &lt;a href="https://blog.lo0.es/en/posts/kv-cache-working-memory-llm-inference/">KV cache: the working memory&lt;/a> and &lt;a href="https://blog.lo0.es/en/posts/pagedattention-vllm-block-manager/">PagedAttention deep dive&lt;/a>. The KV cache post explains what is stored; PagedAttention, how it is managed in memory; FlashAttention, how the computation is executed. They are three different layers of the same problem and they stack multiplicatively.&lt;/p>
&lt;/blockquote>
&lt;h2 id="you-are-here-deploy">You are here: DEPLOY&lt;/h2>
&lt;div class="diagram" style="max-width:780px;margin:1rem auto;">
&lt;svg viewBox="0 0 780 90" xmlns="http://www.w3.org/2000/svg" role="img" aria-label="you are here: Deploy">
&lt;style>.box{stroke:#444;stroke-width:1.4;rx:6}.active{fill:#7ad88f;stroke-width:3}.idle{fill:#f4f4f4}.lbl{font:600 12px sans-serif;fill:#222}.arr{stroke:#666;stroke-width:1.4;fill:none;marker-end:url(#fam)}.cyc{stroke:#888;stroke-width:1.2;fill:none;stroke-dasharray:4 2;marker-end:url(#fam)}&lt;/style>
&lt;defs>&lt;marker id="fam" 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="#666"/>&lt;/marker>&lt;/defs>
&lt;text x="390" y="20" text-anchor="middle" class="lbl">You are here: DEPLOY · attention kernel, one layer below PagedAttention and KV cache&lt;/text>
&lt;rect x="30" y="35" width="110" height="35" class="box idle"/>&lt;text x="85" y="58" text-anchor="middle" class="lbl">1 · Data&lt;/text>
&lt;rect x="155" y="35" width="110" height="35" class="box idle"/>&lt;text x="210" y="58" text-anchor="middle" class="lbl">2 · Tune&lt;/text>
&lt;rect x="280" y="35" width="110" height="35" class="box idle"/>&lt;text x="335" y="58" text-anchor="middle" class="lbl">3 · Eval&lt;/text>
&lt;rect x="405" y="35" width="110" height="35" class="box active"/>&lt;text x="460" y="58" text-anchor="middle" class="lbl">4 · Deploy&lt;/text>
&lt;rect x="530" y="35" width="110" height="35" class="box idle"/>&lt;text x="585" y="58" text-anchor="middle" class="lbl">5 · Observe&lt;/text>
&lt;rect x="655" y="35" width="110" height="35" class="box idle"/>&lt;text x="710" y="58" text-anchor="middle" class="lbl">6 · Retrain&lt;/text>
&lt;path class="arr" d="M140,52 L155,52"/>&lt;path class="arr" d="M265,52 L280,52"/>&lt;path class="arr" d="M390,52 L405,52"/>&lt;path class="arr" d="M515,52 L530,52"/>&lt;path class="arr" d="M640,52 L655,52"/>
&lt;path class="cyc" d="M710,72 L710,82 L85,82 L85,72"/>
&lt;/svg>
&lt;/div>
&lt;h2 id="tldr">TL;DR&lt;/h2>
&lt;p>The standard attention of a transformer has a structural problem on modern GPUs: when you look at it with a profiler, it is not compute-bound, it is &lt;strong>memory&lt;/strong>-bound. The &lt;code>S = QK^T&lt;/code> matrix of size &lt;code>N × N&lt;/code>, with N the sequence length, does not fit in the fast on-chip SRAM and forces round trips to HBM that dominate the total time. &lt;strong>FlashAttention&lt;/strong> is the family of kernels that avoids materialising that matrix by &lt;em>tiling&lt;/em> over Q, K and V, computing the softmax block by block with the &lt;em>online&lt;/em> version and keeping everything inside SRAM. Each version raises the utilisation ceiling: &lt;strong>FA1&lt;/strong> (Dao et al. 2022) abolished the N×N matrix and brought memory complexity down to O(N); &lt;strong>FA2&lt;/strong> (Dao 2023) parallelised along the sequence dimension and cut non-matmul FLOPs to get close to 70 % utilisation on A100; &lt;strong>FA3&lt;/strong> (Shah, Bikshandi, Zhang, Thakkar, Ramani, Dao 2024) exploited three Hopper-specific mechanisms, async WGMMA, TMA and FP8, to reach 740 TFLOPS BF16 (75 % of peak) and 1.2 PFLOPS FP8 on H100; &lt;strong>FA4&lt;/strong> (March 2026) rewrote the kernel from scratch for Blackwell, where the tensor core scales 2.25× but the SFU (where the softmax &lt;code>exp&lt;/code> runs) and the SMEM bandwidth do not scale at all. The answer is a software-emulated exponential that runs on tensor cores. The result on B200: 1,605 TFLOPS BF16, 1.3× faster than cuDNN 9.13 and 2× faster than FA3 on the same GPU. This post takes apart the why (memory roofline, IO complexity), the master analogy of the librarian, the minimum mathematics and the real numbers on H100 and B200.&lt;/p>
&lt;h2 id="the-analogy-the-librarian-who-never-clears-the-desk">The analogy: the librarian who never clears the desk&lt;/h2>
&lt;p>A large library with two zones: a very fast but small work desk (the librarian&amp;rsquo;s desk, 200 books fit on top of it), and a giant shelving system spanning three floors where everything lives (50 million books). The librarian has to cross-reference all the books in one thematic room and produce a summary.&lt;/p>
&lt;p>A &lt;strong>naive librarian&lt;/strong> does it the direct way: fetches every relevant book from the shelves, piles them on the work desk, and since they do not fit, leaves half of them on the floor, on the chair, on top of boxes. The day is spent running between the floor and the desk, opening and closing books, never able to concentrate. The work desk is extremely fast but underused, because most of the time the librarian is moving books between the floor and the desk. That is what &lt;strong>standard attention&lt;/strong> does: it materialises the &lt;code>S = QK^T&lt;/code> matrix in HBM and keeps coming back for it piece by piece.&lt;/p>
&lt;p>A &lt;strong>FlashAttention v1 librarian&lt;/strong> changes strategy: asks for one shelf at a time, brings it to the desk, reads what is needed, jots notes in a compact notebook (&amp;ldquo;from this shelf I care about this, this and this, with these relative weights&amp;rdquo;), returns the shelf to its place and brings the next one. The notebook is the only thing that carries over between shelves. Nothing is ever piled higher than the desk can hold. The trick that makes this possible is the &lt;em>online softmax&lt;/em>: instead of needing all the content at once in order to normalise, it keeps a running max and a running sum that are updated shelf by shelf.&lt;/p>
&lt;p>A &lt;strong>FlashAttention v2 librarian&lt;/strong> realises that several topics can be worked in parallel, because the desk is big and the shelves are independent along some axes. Three assistants are put to work, each with a notebook, each covering a different block of the room, and the results are combined at the end.&lt;/p>
&lt;p>A &lt;strong>FlashAttention v3 librarian&lt;/strong> manages to mechanise the flow: a conveyor belt with two stations is installed. While station A reads the current shelf and takes notes, station B already has the next shelf in transit from the shelving system. When A finishes, B hands over the new shelf with no waiting. This is the &lt;strong>ping-pong producer/consumer&lt;/strong> pattern: the TMA offloads the data loading and the consumer warps do the work in parallel with the loads. On top of that, the notes are written in lower-precision shorthand (FP8), because the pages that matter have already had an orthogonal pre-treatment that stops them losing precision where it hurts.&lt;/p>
&lt;p>A &lt;strong>FlashAttention v4 librarian&lt;/strong> discovers something new about the remodelled library (Blackwell): two much faster conveyor belts have been installed (tensor cores 2.25×), but the shorthand typewriter has not been upgraded (the SFU is unchanged). Now the bottleneck is writing the notes, not fetching the shelves. The fix is elegant: instead of using the shorthand machine, the notes are written with polynomial formulas that the tensor core itself can evaluate (software-emulated exponential). The belt no longer sits idle waiting for the machine.&lt;/p>
&lt;p>The analogy holds up with four mappings:&lt;/p>
&lt;ul>
&lt;li>&lt;strong>Work desk&lt;/strong> = SRAM per SM (228 KB on H100, 256 KB on B200).&lt;/li>
&lt;li>&lt;strong>Giant shelving system&lt;/strong> = HBM (3.35 TB/s on H100, 8 TB/s on B200).&lt;/li>
&lt;li>&lt;strong>Notebook with running max and sum&lt;/strong> = online softmax stats (&lt;code>m&lt;/code>, &lt;code>ℓ&lt;/code>).&lt;/li>
&lt;li>&lt;strong>Conveyor belt with two stations&lt;/strong> = TMA + WGMMA producer/consumer pipeline.&lt;/li>
&lt;/ul>
&lt;h2 id="why-standard-attention-was-the-bottleneck">Why standard attention was the bottleneck&lt;/h2>
&lt;p>The naive intuition, &amp;ldquo;attention is matmuls, and GPUs are good at matmuls&amp;rdquo;, is correct but incomplete. There are two matmuls (&lt;code>QK^T&lt;/code> and then &lt;code>softmax(S) V&lt;/code>), and in between a non-matmul operation (softmax) that requires materialising the intermediate matrix &lt;code>S&lt;/code> of size &lt;code>N × N&lt;/code>.&lt;/p>
&lt;p>Modern GPUs have a very concrete roofline. For H100 SXM5:&lt;/p>
&lt;ul>
&lt;li>Compute peak: 989 TFLOPS BF16 (tensor core, dense, without sparsity).&lt;/li>
&lt;li>Memory bandwidth: 3.35 TB/s HBM3.&lt;/li>
&lt;li>Break-even point (arithmetic intensity break-even): &lt;code>989 × 10¹² / (3.35 × 10¹²) ≈ 295 FLOP/byte&lt;/code>.&lt;/li>
&lt;/ul>
&lt;p>Any operation that fails to perform 295 operations for every byte it moves from HBM is memory-bound: the tensor core sits waiting for data. On B200 the ratio is similar (~281 FLOP/byte) because both compute and bandwidth went up.&lt;/p>
&lt;p>Standard attention materialising &lt;code>S&lt;/code> reads the matrix twice (once for the softmax, once to multiply by V) and writes it once. For Llama 3 70B with head dim &lt;code>d = 128&lt;/code> and context &lt;code>N = 128K&lt;/code>:&lt;/p>
&lt;ul>
&lt;li>Matrix &lt;code>S&lt;/code> per head per layer: &lt;code>N × N × 2 bytes = 128K × 128K × 2 = 34.36 GB&lt;/code>.&lt;/li>
&lt;li>80 layers × 64 Q-heads → aggregate HBM traffic (if it were materialised serially) on the order of TBs, prohibitive in transient.&lt;/li>
&lt;li>Even if it is not all materialised at once, the round trips dominate the time: effective arithmetic intensity well below 295 → memory-bound operation → tensor core underused at around 25 %.&lt;/li>
&lt;/ul>
&lt;p>&lt;strong>FlashAttention does not change the mathematics of attention, it changes the order of the operations so that S is never materialised in HBM&lt;/strong>. Its IO complexity is &lt;code>Θ(N²·d²/M)&lt;/code> with M = SRAM size per SM, against &lt;code>Θ(N·d + N²)&lt;/code> for standard attention. With d = 128 and M = 228 KB: an approximate reduction factor of M/d² ≈ 14× less HBM traffic. That is what moves the operation from memory-bound to nearly compute-bound.&lt;/p>
&lt;h2 id="the-trick-that-made-it-all-possible-online-softmax">The trick that made it all possible: online softmax&lt;/h2>
&lt;p>Without online softmax there is no FlashAttention. The idea comes from Milakov and Gimelshein, 2018 (the paper &amp;ldquo;Online normalizer calculation for softmax&amp;rdquo;, arXiv:1805.02867), and it allows &lt;code>softmax([x_1, ..., x_N])&lt;/code> to be computed in one incremental pass without needing to know the global maximum before starting.&lt;/p>
&lt;p>The standard softmax is:&lt;/p>
$$\text{softmax}(x_i) = \frac{e^{x_i - m}}{\sum_{j} e^{x_j - m}}, \quad m = \max_j x_j$$
&lt;p>The &lt;em>online&lt;/em> trick: keep a running maximum &lt;code>m^{(t)}&lt;/code> and a running sum &lt;code>ℓ^{(t)}&lt;/code>. When a new block of values arrives with local maximum &lt;code>m_{\text{new}}&lt;/code>:&lt;/p>
$$m^{(t+1)} = \max(m^{(t)}, m_{\text{new}})$$
$$\ell^{(t+1)} = e^{m^{(t)} - m^{(t+1)}} \cdot \ell^{(t)} + \sum_{j \in \text{new}} e^{x_j - m^{(t+1)}}$$
&lt;p>And the accumulated partial outputs are rescaled by the same factor &lt;code>e^{m^{(t)} - m^{(t+1)}}&lt;/code>. At the end, dividing by the final &lt;code>ℓ&lt;/code> gives exactly the same result as the standard softmax. It is mathematically exact, not an approximation.&lt;/p>
&lt;p>This is what allows K to be traversed block by block without materialising the whole &lt;code>S&lt;/code> matrix. Each block updates the stats and the accumulated outputs, and is then discarded. The desk never fills up.&lt;/p>
&lt;h2 id="the-four-versions-may-2026">The four versions (May 2026)&lt;/h2>
&lt;table>
&lt;thead>
&lt;tr>
&lt;th>&lt;/th>
&lt;th>&lt;strong>FA1&lt;/strong> (2022)&lt;/th>
&lt;th>&lt;strong>FA2&lt;/strong> (2023)&lt;/th>
&lt;th>&lt;strong>FA3&lt;/strong> (2024)&lt;/th>
&lt;th>&lt;strong>FA4&lt;/strong> (2026)&lt;/th>
&lt;/tr>
&lt;/thead>
&lt;tbody>
&lt;tr>
&lt;td>Target GPU&lt;/td>
&lt;td>A100 / Ampere&lt;/td>
&lt;td>A100 / H100&lt;/td>
&lt;td>H100 / Hopper&lt;/td>
&lt;td>B200 / Blackwell&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>Core idea&lt;/td>
&lt;td>Tiling + online softmax&lt;/td>
&lt;td>Sequence parallelism + work partitioning&lt;/td>
&lt;td>Async WGMMA + TMA + FP8&lt;/td>
&lt;td>Polynomial exp + 2-CTA tensor cores&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>Memory&lt;/td>
&lt;td>O(N) (vs O(N²))&lt;/td>
&lt;td>same&lt;/td>
&lt;td>same&lt;/td>
&lt;td>same&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>Typical peak util&lt;/td>
&lt;td>~25 % A100&lt;/td>
&lt;td>~70 % A100, ~35 % H100&lt;/td>
&lt;td>&lt;strong>75 % H100 BF16, 60 % H100 FP8&lt;/strong>&lt;/td>
&lt;td>&lt;strong>71 % B200 BF16&lt;/strong>&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>Effective TFLOPS&lt;/td>
&lt;td>—&lt;/td>
&lt;td>225 TFLOPS A100 BF16&lt;/td>
&lt;td>740 H100 BF16, 1,200 H100 FP8&lt;/td>
&lt;td>&lt;strong>1,605 B200 BF16&lt;/strong>&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>Speedup vs previous&lt;/td>
&lt;td>2-4× standard&lt;/td>
&lt;td>2× FA1&lt;/td>
&lt;td>1.5-2× FA2 (BF16), 2.6× (FP8)&lt;/td>
&lt;td>2× FA3 on B200&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>Paper&lt;/td>
&lt;td>arXiv:2205.14135&lt;/td>
&lt;td>arXiv:2307.08691&lt;/td>
&lt;td>arXiv:2407.08608&lt;/td>
&lt;td>arXiv:2603.05451&lt;/td>
&lt;/tr>
&lt;/tbody>
&lt;/table>
&lt;h3 id="fa1--changing-the-order-of-the-operations">FA1 — changing the order of the operations&lt;/h3>
&lt;p>Three ideas combined: tiling of Q/K/V into blocks that fit in SRAM, online softmax over those blocks, and recomputation in the backward pass. The N×N matrix S is not stored, only the stats &lt;code>(m, ℓ)&lt;/code>, and in the backward pass S is recomputed block by block from Q, K and the stats. Result: 7.6× speedup on GPT-2 versus PyTorch standard attention, memory O(N).&lt;/p>
&lt;h3 id="fa2--parallelising-along-the-sequence">FA2 — parallelising along the sequence&lt;/h3>
&lt;p>FA1 parallelised only over batch × heads. With a small batch (1-4) and models with few heads or aggressive GQA, the GPU was left with idle SMs. FA2 also parallelises along the sequence dimension: different SMs process different stretches of Q at the same time. It also rewrites the algorithm to minimise the non-matmul operations (softmax rescaling), because those do not go through tensor cores. And it improves work partitioning between warps (split-Q instead of split-K reduces shared memory traffic). Result: around 2× over FA1 on H100 and A100, 225 TFLOPS on A100 (72 % MFU). On H100 it stays around 30-35 % of BF16 peak because it does not exploit async WGMMA.&lt;/p>
&lt;h3 id="fa3--the-hopper-moment">FA3 — the Hopper moment&lt;/h3>
&lt;p>This is where FlashAttention stops being an algorithm and becomes a Hopper-specific artefact. Three pillars:&lt;/p>
&lt;ol>
&lt;li>&lt;strong>Async WGMMA&lt;/strong>: Hopper&amp;rsquo;s new tensor core instructions let a warpgroup fire a GEMM and have the rest of the warpgroup do something else (the softmax, for instance) while the tensor core keeps working. This is the trick that unlocks matmul/softmax overlap.&lt;/li>
&lt;li>&lt;strong>TMA (Tensor Memory Accelerator)&lt;/strong>: dedicated hardware for copying tiles between HBM and SRAM. It frees the SM from computing indices and predicating out-of-bounds accesses, which used to consume SM cycles. It is the equivalent of hiring warehouse staff: the librarian no longer has to carry the books personally.&lt;/li>
&lt;li>&lt;strong>FP8 with block quantisation + incoherent processing&lt;/strong>: quantising Q and K to FP8 doubles tensor core throughput. The precision loss is mitigated with two tricks: a per-tile scale (64×d) instead of a whole-tensor scale, and a pre-multiplication by a random orthogonal Hadamard-based matrix that &amp;ldquo;spreads&amp;rdquo; the outliers before quantising. Documented result: numerical error 2.6× lower than the FP8 baseline.&lt;/li>
&lt;/ol>
&lt;p>These three pillars combine with producer/consumer warp specialisation (producer warps do TMA loads; consumer warps do WGMMA + softmax) and ping-pong scheduling with two warpgroups taking turns so that there are never pipeline bubbles. When WG1 does softmax, WG2 does GEMM; then they swap.&lt;/p>
&lt;p>Numbers: 740 TFLOPS BF16 on H100 (75 % of the 989 peak), 1.2 PFLOPS FP8 (60 % of the 1978 FP8 dense peak). For sequences ≥ 1K it beats cuDNN. Speedup over FA2: 1.5-2× BF16, 2.6× FP8.&lt;/p>
&lt;h3 id="fa4--blackwells-asymmetry">FA4 — Blackwell&amp;rsquo;s asymmetry&lt;/h3>
&lt;p>Blackwell scaled everything unevenly:&lt;/p>
&lt;ul>
&lt;li>Tensor core BF16 throughput: 1 PFLOP H100 → 2.25 PFLOPS B200 (2.25×).&lt;/li>
&lt;li>SFU count (where the softmax &lt;code>exp&lt;/code> runs): unchanged.&lt;/li>
&lt;li>Shared memory bandwidth: unchanged.&lt;/li>
&lt;/ul>
&lt;p>In other words, if FA3 runs as-is on a B200 with no changes, the matmul goes twice as fast but the softmax stays exactly the same, and that stalls the pipeline. It was only a matter of time before someone fixed the imbalance.&lt;/p>
&lt;p>FA4 (March 2026, same team of Dao + Princeton + Together AI + Meta + NVIDIA + Colfax) is a ground-up rewrite with three ideas:&lt;/p>
&lt;ol>
&lt;li>&lt;strong>Software-emulated exponential&lt;/strong>: a polynomial approximation of &lt;code>exp&lt;/code> that runs on the tensor core instead of on the SFU. It loses a little precision (carefully bounded and compensated by the rest of the kernel) but keeps the conveyor belt moving.&lt;/li>
&lt;li>&lt;strong>Conditional softmax rescaling&lt;/strong>: avoids rescaling accumulators when the running max does not change significantly. A &amp;ldquo;lazy&amp;rdquo; style optimisation: it only pays the cost when the cost is needed.&lt;/li>
&lt;li>&lt;strong>2-CTA tensor core&lt;/strong>: two CTAs (Cooperative Thread Arrays) cooperate to feed the tensor cores with larger tiles. It gets more out of Blackwell&amp;rsquo;s new capabilities.&lt;/li>
&lt;/ol>
&lt;p>Written in CuTeDSL (NVIDIA&amp;rsquo;s CUTLASS Python DSL, not CUDA C++ directly). Result on B200 BF16: 1,605 TFLOPS (71 % of the 2250 peak). 1.3× over cuDNN 9.13. 2.7× over Triton. 2× over FA3 run as-is on B200 (which was the previous baseline). It is the first attention kernel to pass 1 PFLOPS.&lt;/p>
&lt;blockquote>
&lt;p>Note: there is recurring confusion about &amp;ldquo;FP4 attention&amp;rdquo;. Blackwell&amp;rsquo;s NVFP4/MXFP4 extensions apply to weights, not to attention. FA4 can be combined with NVFP4 weights, but the attention computation itself is still BF16 or FP8 depending on the configuration. FP4 quantisation of QK^T does exist in some proprietary kernels (Fireworks AI&amp;rsquo;s FireAttention V4 combines it) but it is not standard practice.&lt;/p>
&lt;/blockquote>
&lt;h2 id="implementations-and-libraries-in-2026">Implementations and libraries in 2026&lt;/h2>
&lt;ul>
&lt;li>&lt;strong>Dao-AILab/flash-attention&lt;/strong> (the canonical repo): supports SM 8.0 (Ampere) with FA2, SM 9.0 (Hopper) with FA3, SM 10.0 (Blackwell datacenter B100/B200/B300) with FA4. The consumer Blackwell version (5090, SM 12.0) has partial support as this post goes out.&lt;/li>
&lt;li>&lt;strong>FlashInfer&lt;/strong> (flashinfer-ai/flashinfer, arXiv:2501.01005): an attention engine aimed at &lt;em>serving&lt;/em> (not training). Its conceptual contribution is the &lt;strong>Block-Sparse Row (BSR)&lt;/strong>, a unified abstraction covering paged KV cache, the radix tree of prefix caching and the tree masks of speculative decoding. Internally it can call FA2/FA3, cuDNN, CUTLASS or trtllm-gen FMHA kernels depending on the case. It JIT-compiles specific variants at runtime. Integrated into vLLM, SGLang and TensorRT-LLM.&lt;/li>
&lt;li>&lt;strong>vLLM (May 2026)&lt;/strong>: automatic backend selection by GPU. Default &lt;strong>FA4 on SM 10.0+, FA3 on SM 9.0, FA2 on the rest&lt;/strong>. Fallbacks on Blackwell: TRT-LLM Ragged → FlashInfer → TokenSpeed MLA. For FP8 KV cache on B200, FlashInfer is competitive.&lt;/li>
&lt;li>&lt;strong>SGLang&lt;/strong>: uses FlashInfer as its attention backend; RadixAttention is the prefix caching layer on top (a radix tree over the KV cache).&lt;/li>
&lt;li>&lt;strong>TensorRT-LLM&lt;/strong>: its own fused kernels (trtllm-gen FMHA). XQA is NVIDIA&amp;rsquo;s own optimisation for GQA in decode.&lt;/li>
&lt;li>&lt;strong>PyTorch SDPA and FlexAttention&lt;/strong>: &lt;code>torch.nn.functional.scaled_dot_product_attention&lt;/code> selects the backend automatically. &lt;strong>FlexAttention&lt;/strong> (new) lets you define custom masks declaratively and compiles to kernels that can use FA4 as a backend.&lt;/li>
&lt;li>&lt;strong>xFormers&lt;/strong>: still alive but residual. The built-in PyTorch SDPA covers most cases.&lt;/li>
&lt;/ul>
&lt;h2 id="cases-where-flashattention-does-not-help">Cases where FlashAttention does not help&lt;/h2>
&lt;ul>
&lt;li>&lt;strong>Very short contexts (N &amp;lt; 512)&lt;/strong>: the tiling and kernel launch overhead does not pay off; cuDNN can win.&lt;/li>
&lt;li>&lt;strong>Non-standard custom masks&lt;/strong>: FA ships causal, sliding window and ALiBi only. Arbitrary masks need FlexAttention or the JIT variants of FlashInfer.&lt;/li>
&lt;li>&lt;strong>Non-standard head dim&lt;/strong>: FA optimises for d = 64, 128, 256. Odd dimensions (d = 96, d = 192) fall into slow paths.&lt;/li>
&lt;li>&lt;strong>GQA/MQA with extreme ratios&lt;/strong>: natively supported, but the speedup versus pure MHA depends on the Q-heads : KV-heads ratio.&lt;/li>
&lt;li>&lt;strong>Cross-attention&lt;/strong>: supported but less optimised; self-attention is where the gain is largest.&lt;/li>
&lt;li>&lt;strong>FP8 without block quantisation or incoherent processing&lt;/strong>: it loses several points in benchmarks. If your serving framework does not implement the two tricks from the FA3 paper, FP8 attention can be a bad idea.&lt;/li>
&lt;/ul>
&lt;h2 id="implications-on-on-premise-hardware">Implications on on-premise hardware&lt;/h2>
&lt;h3 id="on-an-rtx-4090-24-gb-ada-lovelace-sm-89">On an RTX 4090 (24 GB, Ada Lovelace, SM 8.9)&lt;/h3>
&lt;p>The 4090 is Ada Lovelace, not Hopper. &lt;strong>It runs neither FA3 nor FA4&lt;/strong>; it runs FA2. That means around 70 % utilisation in BF16 attention (~250 effective TFLOPS against the 4090&amp;rsquo;s 330 TFLOPS BF16 peak). It is not a disaster, FA2 is already very good compared with standard attention, but the ceiling is clearly below that of an H100. For consumer deploys on a 4090 with Llama 3 8B BF16 or any 14B-32B INT4 AWQ, FA2 is what you will be using, and it is perfectly reasonable.&lt;/p>
&lt;h3 id="on-a-generic-4h100-sxm-cluster-320-gb-nvlink-native-fp8">On a generic 4×H100 SXM cluster (320 GB, NVLink, native FP8)&lt;/h3>
&lt;p>This is where FA3 shines and it is what vLLM/SGLang/TRT-LLM will select by default. Two common configurations:&lt;/p>
&lt;ul>
&lt;li>&lt;strong>Llama 3 70B FP8 with FA3 FP8 attention&lt;/strong>: 1.2 PFLOPS peak on the GPU, aggregate cluster throughput on the order of 8,000-12,000 tokens/s at medium batch depending on TP and context. For FP8 attention to deliver its full performance it is crucial to use the block quantisation + incoherent processing techniques from the FA3 paper (they are enabled by default in vLLM).&lt;/li>
&lt;li>&lt;strong>DeepSeek-V3 671B FP8 + MLA with FlashInfer&lt;/strong>: DeepSeek uses &lt;strong>Multi-head Latent Attention&lt;/strong> (MLA), a different variant of standard attention. FlashInfer has specific kernels (FlashMLA). The typical stack is vLLM/SGLang + FlashInfer + FlashMLA + an FA3 fallback for the non-MLA layers.&lt;/li>
&lt;/ul>
&lt;p>If the infrastructure is Blackwell (B200/B300, which some clusters start receiving in 2026), &lt;strong>FA4 is the right option&lt;/strong> and it should be enabled by default in vLLM 0.16+ and SGLang 0.5.11+.&lt;/p>
&lt;h2 id="what-we-have-not-covered">What we have not covered&lt;/h2>
&lt;ul>
&lt;li>&lt;strong>MLA (Multi-head Latent Attention)&lt;/strong> from DeepSeek and the specific FlashMLA kernels: they optimise KV cache compression but require different kernels.&lt;/li>
&lt;li>&lt;strong>Flexible masking and the FlexAttention use cases&lt;/strong> (PyTorch 2.5+): how to declare arbitrary masks without paying the cost of a custom kernel.&lt;/li>
&lt;li>&lt;strong>Hardware assistance for sparse attention&lt;/strong> (NVIDIA 2:4 sparse tensor cores) and why sparse attention has not established itself as a higher ceiling than dense FA.&lt;/li>
&lt;li>&lt;strong>FA in the backward pass of fine-tuning&lt;/strong>: this post focuses on inference, but FA3/FA4 also go through the backward pass and they are what makes training models with long contexts viable on H100/B200.&lt;/li>
&lt;/ul>
&lt;h2 id="see-also">See also&lt;/h2>
&lt;ul>
&lt;li>&lt;a href="https://blog.lo0.es/en/posts/vllm-attention-backend-flashattention-flashinfer-prefill-decode-asymmetry/">The attention backend in vLLM (FlashAttention vs FlashInfer)&lt;/a> — the level above this post: how the engine chooses between FA2/FA3/FA4 and FlashInfer according to the architecture, and why prefill (compute-bound) and decode (memory-bound) call for different kernels.&lt;/li>
&lt;li>&lt;a href="https://blog.lo0.es/en/posts/kv-cache-working-memory-llm-inference/">KV cache: the working memory that holds up LLM inference&lt;/a> — the KV cache is what FlashAttention traverses and multiplies against Q on every iteration; understanding one requires understanding the other.&lt;/li>
&lt;li>&lt;a href="https://blog.lo0.es/en/posts/pagedattention-vllm-block-manager/">PagedAttention deep dive&lt;/a> — PagedAttention organises the KV cache into non-contiguous physical blocks; FlashAttention is the kernel that iterates over those blocks. Different layers of the same problem.&lt;/li>
&lt;li>&lt;a href="https://blog.lo0.es/en/posts/quantization-llm-inference-fp8-int4-gguf/">Quantisation for LLM inference&lt;/a> — FA3&amp;rsquo;s FP8 attention with block quantisation and Blackwell&amp;rsquo;s FP4 weights stack up; this post gives the general quantisation framework.&lt;/li>
&lt;li>&lt;a href="https://blog.lo0.es/en/posts/disaggregated-serving-prefill-decode-specialised-pods/">Disaggregated serving: prefill and decode in specialised pods&lt;/a> — prefill is compute-bound and benefits a great deal from FA3/FA4 FP8; decode is memory-bound and benefits less but still benefits. Separating them allows the kernel to be optimised per phase.&lt;/li>
&lt;li>&lt;a href="https://blog.lo0.es/en/posts/speculative-decoding-fundamentals-maths-state-of-play/">Speculative decoding: the secretary who anticipates what the boss is going to say&lt;/a> — speculative decoding produces more tokens per forward pass; FlashAttention makes each forward pass cheaper. Multiplicative levers.&lt;/li>
&lt;li>&lt;a href="https://blog.lo0.es/en/posts/moe-inference-fundamentals-expert-parallel-deepseek-economics/">MoE inference: the call centre with 256 specialists&lt;/a> — attention is still dense in every MoE of 2026; FlashAttention is reused as-is. FlashMLA is the specific variant for DeepSeek&amp;rsquo;s Multi-head Latent Attention (latent compression of the KV cache) that extends the FA pattern with one extra step.&lt;/li>
&lt;li>&lt;a href="https://blog.lo0.es/en/posts/six-stage-llmops-pipeline-architecture-and-deep-dive/">The six-stage LLMOps pipeline&lt;/a> — the master map where Deploy is stage 4.&lt;/li>
&lt;/ul>
&lt;h2 id="references">References&lt;/h2>
&lt;ul>
&lt;li>Dao, T., Fu, D., Ermon, S., Rudra, A., Ré, C. &lt;em>FlashAttention: Fast and Memory-Efficient Exact Attention with IO-Awareness&lt;/em>. NeurIPS 2022. &lt;a href="https://arxiv.org/abs/2205.14135">https://arxiv.org/abs/2205.14135&lt;/a>&lt;/li>
&lt;li>Dao, T. &lt;em>FlashAttention-2: Faster Attention with Better Parallelism and Work Partitioning&lt;/em>. 2023. &lt;a href="https://arxiv.org/abs/2307.08691">https://arxiv.org/abs/2307.08691&lt;/a>&lt;/li>
&lt;li>Shah, J., Bikshandi, G., Zhang, Y., Thakkar, V., Ramani, P., Dao, T. &lt;em>FlashAttention-3: Fast and Accurate Attention with Asynchrony and Low-precision&lt;/em>. NeurIPS 2024. &lt;a href="https://arxiv.org/abs/2407.08608">https://arxiv.org/abs/2407.08608&lt;/a>&lt;/li>
&lt;li>Dao, T. et al. &lt;em>FlashAttention-4: Algorithm and Kernel Pipelining Co-Design for Asymmetric Hardware Scaling&lt;/em>. 2026. &lt;a href="https://arxiv.org/abs/2603.05451">https://arxiv.org/abs/2603.05451&lt;/a>&lt;/li>
&lt;li>Milakov, M., Gimelshein, N. &lt;em>Online normalizer calculation for softmax&lt;/em>. 2018. &lt;a href="https://arxiv.org/abs/1805.02867">https://arxiv.org/abs/1805.02867&lt;/a>&lt;/li>
&lt;li>Ye, Z. et al. &lt;em>FlashInfer: Efficient and Customizable Attention Engine for LLM Inference Serving&lt;/em>. MLSys 2025. &lt;a href="https://arxiv.org/abs/2501.01005">https://arxiv.org/abs/2501.01005&lt;/a>&lt;/li>
&lt;li>Tri Dao FA3 blog: &lt;a href="https://tridao.me/blog/2024/flash3/">https://tridao.me/blog/2024/flash3/&lt;/a>&lt;/li>
&lt;li>Tri Dao FA4 blog: &lt;a href="https://tridao.me/blog/2026/flash4/">https://tridao.me/blog/2026/flash4/&lt;/a>&lt;/li>
&lt;li>PyTorch FlashAttention-3 announcement: &lt;a href="https://pytorch.org/blog/flashattention-3/">https://pytorch.org/blog/flashattention-3/&lt;/a>&lt;/li>
&lt;li>PyTorch FlexAttention + FA4: &lt;a href="https://pytorch.org/blog/flexattention-flashattention-4-fast-and-flexible/">https://pytorch.org/blog/flexattention-flashattention-4-fast-and-flexible/&lt;/a>&lt;/li>
&lt;li>Together AI FA4 blog: &lt;a href="https://www.together.ai/blog/flashattention-4">https://www.together.ai/blog/flashattention-4&lt;/a>&lt;/li>
&lt;li>Colfax Research FA3: &lt;a href="https://research.colfax-intl.com/flashattention-3-fast-and-accurate-attention-with-asynchrony-and-low-precision/">https://research.colfax-intl.com/flashattention-3-fast-and-accurate-attention-with-asynchrony-and-low-precision/&lt;/a>&lt;/li>
&lt;li>Colfax Research FA4: &lt;a href="https://research.colfax-intl.com/flashattention-4-algorithm-and-kernel-pipelining-co-design-for-asymmetric-hardware-scaling/">https://research.colfax-intl.com/flashattention-4-algorithm-and-kernel-pipelining-co-design-for-asymmetric-hardware-scaling/&lt;/a>&lt;/li>
&lt;li>Repo Dao-AILab/flash-attention: &lt;a href="https://github.com/Dao-AILab/flash-attention">https://github.com/Dao-AILab/flash-attention&lt;/a>&lt;/li>
&lt;li>Repo flashinfer-ai/flashinfer: &lt;a href="https://github.com/flashinfer-ai/flashinfer">https://github.com/flashinfer-ai/flashinfer&lt;/a>&lt;/li>
&lt;li>vLLM attention backends: &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>NVIDIA Hopper Architecture in Depth: &lt;a href="https://developer.nvidia.com/blog/nvidia-hopper-architecture-in-depth/">https://developer.nvidia.com/blog/nvidia-hopper-architecture-in-depth/&lt;/a>&lt;/li>
&lt;/ul></description></item></channel></rss>