<?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>Precision on KbWen Blog</title>
    <link>https://www.kbwen.com/tags/precision/</link>
    <description>KbWen is a practical technology blog about 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>Thu, 10 Sep 2026 09:15:00 +0800</lastBuildDate><atom:link href="https://www.kbwen.com/tags/precision/index.xml" rel="self" type="application/rss+xml" />
    <item>
      <title>bfloat16 vs float16: how two 16-bit formats split their bits</title>
      <link>https://www.kbwen.com/bfloat16-vs-float16-training/</link>
      <pubDate>Thu, 10 Sep 2026 09:15:00 +0800</pubDate><dc:creator>KbWen</dc:creator>
      <guid>https://www.kbwen.com/bfloat16-vs-float16-training/</guid>
      <description>A look at bfloat16 and float16 through their bit layouts, loss scaling, and a small example of low-precision addition.</description>
      <content:encoded><![CDATA[<blockquote>
<p><strong>TL;DR:</strong> bfloat16 keeps FP32&rsquo;s exponent width, while float16 keeps more fractional bits. That gives bfloat16 a wider range and float16 finer precision. In training, the difference affects both small gradients and the accuracy of running totals.</p>
</blockquote>
<p>bfloat16 and float16 each store a number in sixteen bits. Both appear in mixed-precision training, where different parts of a computation can use different numeric formats.</p>
<h2 id="where-the-sixteen-bits-go">Where the sixteen bits go</h2>
<p>A floating-point number has a sign, an exponent, and a significand. The exponent sets the scale; the significand supplies the significant digits within it. Format diagrams often call the stored fractional bits the mantissa. For normalized numbers, there is also an implicit leading bit that does not occupy storage.</p>
<p>The layouts below follow <a href="https://cloud.google.com/blog/products/ai-machine-learning/bfloat16-the-secret-to-high-performance-on-cloud-tpus">Google&rsquo;s bfloat16 description</a> and <a href="https://docs.nvidia.com/deeplearning/performance/mixed-precision-training/index.html">NVIDIA&rsquo;s half-precision reference</a>:</p>
<table>
  <thead>
      <tr>
          <th>Format</th>
          <th>Sign</th>
          <th>Exponent</th>
          <th>Stored fraction</th>
          <th>Total</th>
      </tr>
  </thead>
  <tbody>
      <tr>
          <td>FP32 (IEEE single-precision)</td>
          <td>1</td>
          <td>8</td>
          <td>23</td>
          <td>32</td>
      </tr>
      <tr>
          <td>float16 (IEEE half-precision)</td>
          <td>1</td>
          <td>5</td>
          <td>10</td>
          <td>16</td>
      </tr>
      <tr>
          <td>bfloat16</td>
          <td>1</td>
          <td>8</td>
          <td>7</td>
          <td>16</td>
      </tr>
  </tbody>
</table>
<p>bfloat16 retains FP32&rsquo;s sign and exponent fields while dropping the bottom sixteen fraction bits from the layout. float16 uses a narrower exponent and leaves more room for the fraction. Both fields differ between the sixteen-bit formats.</p>
<h2 id="small-gradients-during-training">Small gradients during training</h2>
<p>During backpropagation, gradients can become too small for float16 to represent. Rounding them to zero loses the corresponding contribution to the weight update. NVIDIA&rsquo;s <a href="https://docs.nvidia.com/deeplearning/performance/mixed-precision-training/index.html">mixed-precision training guide</a> describes loss scaling as a way to preserve these small gradients.</p>
<p>The loss is multiplied by a scale factor before backpropagation, which scales the gradients too. Before the optimizer updates the weights, the gradients are divided by that factor. The intervening calculation uses larger magnitudes, so values that would otherwise underflow can survive. Choosing the scale still matters: raising it too far can cause overflow.</p>
<p>bfloat16&rsquo;s wider exponent reduces the need for this adjustment. Google attributes its choice for Cloud TPUs to neural networks being more sensitive to exponent width than mantissa width, and gave bfloat16 FP32&rsquo;s exponent size so that underflows, overflows, and NaNs behave the same way. The post describes training without loss scaling or manual code changes through the TPU software stack&rsquo;s automatic conversions. It also notes that those TPUs flush subnormal bfloat16 values to zero, so matching FP32&rsquo;s exponent width does not make every tiny value behave identically.</p>
<h2 id="adding-small-values-to-a-running-total">Adding small values to a running total</h2>
<p>Precision becomes visible even at an ordinary number such as 256. In bfloat16, the next representable value above it is 258. The <a href="https://raw.githubusercontent.com/jax-ml/ml_dtypes/main/README.md">ml_dtypes README</a> demonstrates what happens when 1 is added:</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">from</span> <span class="nn">ml_dtypes</span> <span class="kn">import</span> <span class="n">bfloat16</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="n">bfloat16</span><span class="p">(</span><span class="mi">256</span><span class="p">)</span> <span class="o">+</span> <span class="n">bfloat16</span><span class="p">(</span><span class="mi">1</span><span class="p">)</span>
</span></span><span class="line"><span class="cl"><span class="c1"># 256</span>
</span></span></code></pre></div><p>The exact answer, 257, falls halfway between those two representable values; the sum rounds back to 256. If a running total has reached that point, adding values smaller than 1 will not change it either.</p>
<p>The README shows this happening in a sum of random values, then keeps the accumulator in FP32 to preserve the small additions. The result is converted to bfloat16 after summation. float16&rsquo;s extra fraction bits provide finer spacing at the same scale, though it too has finite precision.</p>
<p>This is also why a mixed-precision operation need not use the same format throughout. In Google&rsquo;s description of TPU matrix multiplication, the multiplication uses bfloat16 inputs and the accumulation uses FP32. The running total gets more precision even though the multiplication uses sixteen-bit inputs.</p>
]]></content:encoded>
    </item>
    
  </channel>
</rss>
