<?xml version="1.0" encoding="utf-8" standalone="yes"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:content="http://purl.org/rss/1.0/modules/content/" xmlns:dc="http://purl.org/dc/elements/1.1/">
  <channel>
    <title>FlashAttention on KbWen Blog</title>
    <link>https://www.kbwen.com/tags/flashattention/</link>
    <description>Articles and notes on AI systems, machine learning, Python, data engineering, and software development.</description>
    <generator>Hugo</generator>
    <language>zh-tw</language>
    <image>
      <url>https://www.kbwen.com/images/og-default.png</url>
      <title>KbWen Blog</title>
      <link>https://www.kbwen.com/</link>
    </image>
    
    <lastBuildDate>Fri, 06 Nov 2026 14:32:00 +0800</lastBuildDate><atom:link href="https://www.kbwen.com/tags/flashattention/index.xml" rel="self" type="application/rss+xml" />
    <item>
      <title>How FlashAttention computes softmax one block at a time</title>
      <link>https://www.kbwen.com/flashattention-online-softmax-tiling/</link>
      <pubDate>Fri, 06 Nov 2026 14:32:00 +0800</pubDate><dc:creator>KbWen</dc:creator>
      <guid>https://www.kbwen.com/flashattention-online-softmax-tiling/</guid>
      <description>One query&amp;#39;s attention scores, split into two blocks and worked through by hand, to see how FlashAttention gets the softmax without having all the scores at once.</description>
      <content:encoded><![CDATA[<p>Take one <a href="/how-kv-cache-speeds-up-llm-generation/">query and four keys</a>. The query&rsquo;s attention scores against those keys are 1, 3, 5 and 2, and the values that go with the keys are 10, 20, 30 and 40. A real value is a vector with dozens of entries. The max and the sum below are shared by all of them, and only the weighted sum is worked out entry by entry, so one number per key is enough to follow it.</p>
<h2 id="all-four-scores-at-once">All four scores at once</h2>
<p>Softmax turns the scores into weights. Each score is exponentiated, and each result is divided by the sum of all of them. The attention output is then the weighted sum of the values. In practice the largest score is subtracted from every score before exponentiating, because <code>exp</code> of a large score overflows a floating-point number. Subtracting the max fixes that without changing the answer, since the same factor <code>exp(-max)</code> ends up in every numerator and in the denominator, where it cancels.</p>
<p>Here the max is 5, so the four weights before dividing are <code>exp(1-5)</code>, <code>exp(3-5)</code>, <code>exp(5-5)</code> and <code>exp(2-5)</code>:</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-fallback" data-lang="fallback"><span class="line"><span class="cl">0.0183   0.1353   1.0000   0.0498      sum ≈ 1.2034
</span></span></code></pre></div><p>Multiplying each by its value and adding gives <code>10 × 0.0183 + 20 × 0.1353 + 30 × 1 + 40 × 0.0498 ≈ 34.8813</code>, and the output is <code>34.8813 / 1.2034 ≈ 28.9847</code>. (Each figure in this post is worked out from the unrounded numbers and then rounded to four places, so redoing a sum from the printed figures can come out a digit off in the last place. The code further down does the whole calculation unrounded.)</p>
<p>The calculation needed the max of all four scores before any weight could be computed, and the sum of all four weights before any of them could be used. For a long sequence that is a whole row of the score matrix, one entry per key. The main goal in the <a href="https://arxiv.org/abs/2205.14135">FlashAttention paper</a> is &ldquo;to avoid reading and writing the attention matrix to and from HBM&rdquo;, the GPU&rsquo;s large but comparatively slow memory, and that requires &ldquo;computing the softmax reduction without access to the whole input&rdquo;. FlashAttention loads the inputs in blocks into the GPU&rsquo;s small on-chip memory instead, and works on one block at a time.</p>
<h2 id="two-blocks">Two blocks</h2>
<p>So split the four keys into two blocks of two, and pretend the second block hasn&rsquo;t arrived yet.</p>
<p>The first block holds the scores 1 and 3, with values 10 and 20. Its largest score, 3, is the max so far, and the weights are computed against it: <code>exp(1-3) ≈ 0.1353</code> and <code>exp(3-3) = 1</code>. They add up to about 1.1353, and the weighted sum of the values is <code>10 × 0.1353 + 20 × 1 ≈ 21.3534</code>. If the keys ended here, the output would be <code>21.3534 / 1.1353</code>, about 18.81, which is the right answer for a query with only these two keys. But they don&rsquo;t end here, so nothing is divided yet. What gets carried forward is three numbers: the max so far (3), the sum so far (1.1353) and the weighted sum so far (21.3534).</p>
<p>The second block holds the scores 5 and 2. The 5 is larger than the 3 that every weight so far was computed against, so each of those weights is too large by a factor of <code>exp(5-3)</code>. To undo that, both carried numbers get multiplied by <code>exp(3-5) ≈ 0.1353</code>. The sum becomes <code>1.1353 × 0.1353</code>, about 0.1537, and the weighted sum becomes <code>21.3534 × 0.1353</code>, about 2.8899.</p>
<p>The multiplication also changed each weight inside the sum. The 1 that belonged to the score 3 became 0.1353, which is <code>exp(3-5)</code>. The 0.1353 that belonged to the score 1 became 0.0183, which is <code>exp(1-5)</code>. Those are the first two numbers of the all-at-once list above. Multiplying by <code>exp(3-5)</code> is the same as subtracting 5 instead of 3 inside each exponent, because <code>exp(s-3) × exp(3-5) = exp(s-5)</code> for any score <code>s</code>, so after the correction the first block looks exactly as it would have if 5 had been the max from the beginning. The weighted sum is made of those same weights times the values, so multiplying it by 0.1353 corrects it too.</p>
<p>The second block itself is computed against the new max directly: <code>exp(5-5) = 1</code> and <code>exp(2-5) ≈ 0.0498</code>, which add up to about 1.0498, with a weighted sum of <code>30 × 1 + 40 × 0.0498 ≈ 31.9915</code>. Then adding the two blocks together gives a sum of <code>0.1537 + 1.0498 ≈ 1.2034</code> and a weighted sum of <code>2.8899 + 31.9915 ≈ 34.8813</code>. Those are the two totals from the all-at-once calculation, and <code>34.8813 / 1.2034</code> is again about 28.9847.</p>
<h2 id="more-than-two-blocks">More than two blocks</h2>
<p>Nothing in the second step depended on there being only two blocks. Every new block does the same thing: compare its max with the max so far, multiply the carried sum and weighted sum by <code>exp(old max - new max)</code>, and add the new block&rsquo;s own sum and weighted sum, computed against the new max. If the new block&rsquo;s max is not larger, the factor is <code>exp(0) = 1</code> and the carried numbers stay as they are. The update itself is older than FlashAttention: both FlashAttention papers cite <a href="https://arxiv.org/abs/1805.02867">Milakov and Gimelshein</a>, who in 2018 used it to get the max and the sum of a plain softmax &ldquo;in a single pass over input vector&rdquo;. FlashAttention carries the weighted sum of the values along with those two, and <a href="https://arxiv.org/abs/2307.08691">FlashAttention-2</a> leaves it undivided, &ldquo;un-scaled&rdquo; in the paper&rsquo;s word, until after the last block, as the example above did.</p>
<p>Here it is in NumPy, for any block size, next to the all-at-once version:</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-python" data-lang="python"><span class="line"><span class="cl"><span class="kn">import</span> <span class="nn">numpy</span> <span class="k">as</span> <span class="nn">np</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="k">def</span> <span class="nf">softmax_weighted_sum</span><span class="p">(</span><span class="n">s</span><span class="p">,</span> <span class="n">v</span><span class="p">):</span>
</span></span><span class="line"><span class="cl">    <span class="n">w</span> <span class="o">=</span> <span class="n">np</span><span class="o">.</span><span class="n">exp</span><span class="p">(</span><span class="n">s</span> <span class="o">-</span> <span class="n">s</span><span class="o">.</span><span class="n">max</span><span class="p">())</span>
</span></span><span class="line"><span class="cl">    <span class="k">return</span> <span class="p">(</span><span class="n">w</span><span class="p">[:,</span> <span class="kc">None</span><span class="p">]</span> <span class="o">*</span> <span class="n">v</span><span class="p">)</span><span class="o">.</span><span class="n">sum</span><span class="p">(</span><span class="n">axis</span><span class="o">=</span><span class="mi">0</span><span class="p">)</span> <span class="o">/</span> <span class="n">w</span><span class="o">.</span><span class="n">sum</span><span class="p">()</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="k">def</span> <span class="nf">online</span><span class="p">(</span><span class="n">s</span><span class="p">,</span> <span class="n">v</span><span class="p">,</span> <span class="n">block</span><span class="p">):</span>
</span></span><span class="line"><span class="cl">    <span class="n">m</span><span class="p">,</span> <span class="n">l</span><span class="p">,</span> <span class="n">acc</span> <span class="o">=</span> <span class="o">-</span><span class="n">np</span><span class="o">.</span><span class="n">inf</span><span class="p">,</span> <span class="mf">0.0</span><span class="p">,</span> <span class="n">np</span><span class="o">.</span><span class="n">zeros</span><span class="p">(</span><span class="n">v</span><span class="o">.</span><span class="n">shape</span><span class="p">[</span><span class="mi">1</span><span class="p">])</span>
</span></span><span class="line"><span class="cl">    <span class="k">for</span> <span class="n">i</span> <span class="ow">in</span> <span class="nb">range</span><span class="p">(</span><span class="mi">0</span><span class="p">,</span> <span class="nb">len</span><span class="p">(</span><span class="n">s</span><span class="p">),</span> <span class="n">block</span><span class="p">):</span>
</span></span><span class="line"><span class="cl">        <span class="n">sb</span><span class="p">,</span> <span class="n">vb</span> <span class="o">=</span> <span class="n">s</span><span class="p">[</span><span class="n">i</span><span class="p">:</span><span class="n">i</span> <span class="o">+</span> <span class="n">block</span><span class="p">],</span> <span class="n">v</span><span class="p">[</span><span class="n">i</span><span class="p">:</span><span class="n">i</span> <span class="o">+</span> <span class="n">block</span><span class="p">]</span>
</span></span><span class="line"><span class="cl">        <span class="n">m_new</span> <span class="o">=</span> <span class="nb">max</span><span class="p">(</span><span class="n">m</span><span class="p">,</span> <span class="n">sb</span><span class="o">.</span><span class="n">max</span><span class="p">())</span>
</span></span><span class="line"><span class="cl">        <span class="n">c</span> <span class="o">=</span> <span class="n">np</span><span class="o">.</span><span class="n">exp</span><span class="p">(</span><span class="n">m</span> <span class="o">-</span> <span class="n">m_new</span><span class="p">)</span>          <span class="c1"># rescales what is already there</span>
</span></span><span class="line"><span class="cl">        <span class="n">p</span> <span class="o">=</span> <span class="n">np</span><span class="o">.</span><span class="n">exp</span><span class="p">(</span><span class="n">sb</span> <span class="o">-</span> <span class="n">m_new</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">        <span class="n">l</span> <span class="o">=</span> <span class="n">l</span> <span class="o">*</span> <span class="n">c</span> <span class="o">+</span> <span class="n">p</span><span class="o">.</span><span class="n">sum</span><span class="p">()</span>
</span></span><span class="line"><span class="cl">        <span class="n">acc</span> <span class="o">=</span> <span class="n">acc</span> <span class="o">*</span> <span class="n">c</span> <span class="o">+</span> <span class="p">(</span><span class="n">p</span><span class="p">[:,</span> <span class="kc">None</span><span class="p">]</span> <span class="o">*</span> <span class="n">vb</span><span class="p">)</span><span class="o">.</span><span class="n">sum</span><span class="p">(</span><span class="n">axis</span><span class="o">=</span><span class="mi">0</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">        <span class="n">m</span> <span class="o">=</span> <span class="n">m_new</span>
</span></span><span class="line"><span class="cl">    <span class="k">return</span> <span class="n">acc</span> <span class="o">/</span> <span class="n">l</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="n">s</span> <span class="o">=</span> <span class="n">np</span><span class="o">.</span><span class="n">array</span><span class="p">([</span><span class="mf">1.0</span><span class="p">,</span> <span class="mf">3.0</span><span class="p">,</span> <span class="mf">5.0</span><span class="p">,</span> <span class="mf">2.0</span><span class="p">])</span>
</span></span><span class="line"><span class="cl"><span class="n">v</span> <span class="o">=</span> <span class="n">np</span><span class="o">.</span><span class="n">array</span><span class="p">([[</span><span class="mf">10.0</span><span class="p">],</span> <span class="p">[</span><span class="mf">20.0</span><span class="p">],</span> <span class="p">[</span><span class="mf">30.0</span><span class="p">],</span> <span class="p">[</span><span class="mf">40.0</span><span class="p">]])</span>
</span></span><span class="line"><span class="cl"><span class="nb">print</span><span class="p">(</span><span class="n">online</span><span class="p">(</span><span class="n">s</span><span class="p">,</span> <span class="n">v</span><span class="p">,</span> <span class="n">block</span><span class="o">=</span><span class="mi">2</span><span class="p">),</span> <span class="n">softmax_weighted_sum</span><span class="p">(</span><span class="n">s</span><span class="p">,</span> <span class="n">v</span><span class="p">))</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="n">rng</span> <span class="o">=</span> <span class="n">np</span><span class="o">.</span><span class="n">random</span><span class="o">.</span><span class="n">default_rng</span><span class="p">(</span><span class="mi">0</span><span class="p">)</span>
</span></span><span class="line"><span class="cl"><span class="n">s</span> <span class="o">=</span> <span class="n">rng</span><span class="o">.</span><span class="n">normal</span><span class="p">(</span><span class="mi">0</span><span class="p">,</span> <span class="mi">4</span><span class="p">,</span> <span class="n">size</span><span class="o">=</span><span class="mi">1000</span><span class="p">)</span>
</span></span><span class="line"><span class="cl"><span class="n">v</span> <span class="o">=</span> <span class="n">rng</span><span class="o">.</span><span class="n">normal</span><span class="p">(</span><span class="n">size</span><span class="o">=</span><span class="p">(</span><span class="mi">1000</span><span class="p">,</span> <span class="mi">8</span><span class="p">))</span>
</span></span><span class="line"><span class="cl"><span class="n">diff</span> <span class="o">=</span> <span class="n">np</span><span class="o">.</span><span class="n">abs</span><span class="p">(</span><span class="n">online</span><span class="p">(</span><span class="n">s</span><span class="p">,</span> <span class="n">v</span><span class="p">,</span> <span class="n">block</span><span class="o">=</span><span class="mi">64</span><span class="p">)</span> <span class="o">-</span> <span class="n">softmax_weighted_sum</span><span class="p">(</span><span class="n">s</span><span class="p">,</span> <span class="n">v</span><span class="p">))</span><span class="o">.</span><span class="n">max</span><span class="p">()</span>
</span></span><span class="line"><span class="cl"><span class="nb">print</span><span class="p">(</span><span class="n">diff</span><span class="p">)</span>
</span></span></code></pre></div><div class="highlight"><pre tabindex="0" class="chroma"><code class="language-fallback" data-lang="fallback"><span class="line"><span class="cl">[28.98474626] [28.98474626]
</span></span><span class="line"><span class="cl">1.3322676295501878e-15
</span></span></code></pre></div><p>On the worked example both versions print 28.98474626. On 1,000 random scores in blocks of 64, they differ by at most about 1e-15, at the level of floating-point rounding. That is what &ldquo;exact attention algorithm&rdquo; means in the first FlashAttention paper: it computes the same attention, not an approximation of it. The loop starts with the max at negative infinity and the sum at zero, so on the first block the correction factor is <code>exp(-inf) = 0</code> and there is nothing yet to correct.</p>
<p>On a GPU the loop runs over blocks of queries as well as blocks of keys, with the block sizes chosen to fit the on-chip memory, and each query in a block has its own max, sum and partial output. FlashAttention-2 puts the blocks of queries in the outer loop and walks the blocks of keys inside it, so one block of queries goes through every block of keys before its output is finished; the original FlashAttention had the two loops the other way round.</p>
<p>The worked figures were checked against the NumPy code above, and the update is the same one the FlashAttention papers write out in matrix form.</p>
]]></content:encoded>
    </item>
    
  </channel>
</rss>
