<?xml version="1.0" encoding="utf-8" standalone="yes"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom">
    <channel>
        <title>BF16 on KnightLi的博客</title>
        <link>https://www.knightli.com/tags/bf16/</link>
        <description>Recent content in BF16 on KnightLi的博客</description>
        <generator>Hugo -- gohugo.io</generator>
        <language>zh-cn</language>
        <lastBuildDate>Wed, 22 Apr 2026 22:40:00 +0800</lastBuildDate><atom:link href="https://www.knightli.com/tags/bf16/index.xml" rel="self" type="application/rss+xml" /><item>
        <title>大模型常见张量类型入门：FP32、FP16、BF16、TF32 与 FP8</title>
        <link>https://www.knightli.com/2026/04/22/common-tensor-formats-fp32-fp16-bf16-tf32-fp8/</link>
        <pubDate>Wed, 22 Apr 2026 22:40:00 +0800</pubDate>
        
        <guid>https://www.knightli.com/2026/04/22/common-tensor-formats-fp32-fp16-bf16-tf32-fp8/</guid>
        <description>&lt;p&gt;只要你开始接触大模型训练、推理或者部署，很快就会遇到一组高频缩写：&lt;code&gt;FP32&lt;/code&gt;、&lt;code&gt;FP16&lt;/code&gt;、&lt;code&gt;BF16&lt;/code&gt;、&lt;code&gt;TF32&lt;/code&gt;、&lt;code&gt;FP8&lt;/code&gt;。它们看起来像是参数页上的几个附加标签，但实际影响远不止“写法不同”。&lt;/p&gt;
&lt;p&gt;这些类型决定了数字在显存里怎么存、在计算中怎么表示，也直接影响模型训练是否稳定、推理速度如何，以及一张显卡到底能装下多大的模型。&lt;/p&gt;
&lt;p&gt;所以如果你想真正理解大模型里的精度取舍，最值得先补的一课，不是某个具体模型的跑分，而是先把这些常见张量类型各自是什么、为什么会这样设计搞清楚。&lt;/p&gt;
&lt;h2 id=&#34;张量类型到底在决定什么&#34;&gt;张量类型到底在决定什么
&lt;/h2&gt;&lt;p&gt;大模型本质上是海量参数参与的矩阵运算，而张量类型就是这些数字在显存里如何存、在计算中如何表示。&lt;/p&gt;
&lt;p&gt;它的核心取舍通常围绕三个维度展开：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;精度&lt;/li&gt;
&lt;li&gt;显存占用&lt;/li&gt;
&lt;li&gt;计算速度&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;这和图片格式其实很像。无损格式保留细节更多，但体积大、加载慢；压缩格式会丢掉一部分肉眼不敏感的信息，换来更小的体积和更快的处理速度。大模型之所以能接受这种取舍，是因为在极大量参数里，很多微小数值变化并不会明显影响最终输出。&lt;/p&gt;
&lt;p&gt;也正因为如此，模型世界里才会出现一整套不同精度的张量格式。&lt;/p&gt;
&lt;h2 id=&#34;一个数字是怎么被表示的&#34;&gt;一个数字是怎么被表示的
&lt;/h2&gt;&lt;p&gt;理解这些格式之前，可以先记住一个非常基础的结构。一个浮点数通常由三部分组成：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;符号位：决定正负&lt;/li&gt;
&lt;li&gt;指数位：决定数值范围&lt;/li&gt;
&lt;li&gt;尾数位：决定数值精细程度&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;在大模型里，尾数精度当然重要，但很多时候模型更怕的是数值范围不够，也就是指数位太小，导致溢出或者训练不稳定。很多张量格式的设计，本质上就是在“范围”和“细节”之间重新分配有限的 bit 数。&lt;/p&gt;
&lt;p&gt;下面这张图可以先帮你建立一个整体印象：&lt;/p&gt;
&lt;p&gt;&lt;img src=&#34;https://www.knightli.com/2026/04/22/common-tensor-formats-fp32-fp16-bf16-tf32-fp8/tensor-format-overview.svg&#34;
	
	
	
	loading=&#34;lazy&#34;
	
		alt=&#34;FP32、FP16、BF16、TF32 与 FP8 的位宽结构总览&#34;
	
	
&gt;&lt;/p&gt;
&lt;h2 id=&#34;fp32最稳但太贵&#34;&gt;FP32：最稳，但太贵
&lt;/h2&gt;&lt;p&gt;&lt;code&gt;FP32&lt;/code&gt; 是最传统的单精度浮点格式，总共 32 bit，也就是 4 个字节。&lt;/p&gt;
&lt;p&gt;&lt;img src=&#34;https://www.knightli.com/2026/04/22/common-tensor-formats-fp32-fp16-bf16-tf32-fp8/fp32-layout.svg&#34;
	
	
	
	loading=&#34;lazy&#34;
	
		alt=&#34;FP32 位宽结构示意图&#34;
	
	
&gt;&lt;/p&gt;
&lt;p&gt;它的优点很直接：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;数值范围大&lt;/li&gt;
&lt;li&gt;精度高&lt;/li&gt;
&lt;li&gt;训练最稳&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;但问题也同样明显：太占显存。&lt;/p&gt;
&lt;p&gt;一个非常粗略的估算方式是：&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;div class=&#34;chroma&#34;&gt;
&lt;table class=&#34;lntable&#34;&gt;&lt;tr&gt;&lt;td class=&#34;lntd&#34;&gt;
&lt;pre tabindex=&#34;0&#34; class=&#34;chroma&#34;&gt;&lt;code&gt;&lt;span class=&#34;lnt&#34;&gt;1
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;
&lt;td class=&#34;lntd&#34;&gt;
&lt;pre tabindex=&#34;0&#34; class=&#34;chroma&#34;&gt;&lt;code class=&#34;language-text&#34; data-lang=&#34;text&#34;&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;显存占用 ≈ 参数量 × 每个参数的字节数
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;
&lt;/div&gt;
&lt;/div&gt;&lt;p&gt;如果一个 27B 模型完全用 &lt;code&gt;FP32&lt;/code&gt; 存权重，那么光权重本身就大约需要：&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;div class=&#34;chroma&#34;&gt;
&lt;table class=&#34;lntable&#34;&gt;&lt;tr&gt;&lt;td class=&#34;lntd&#34;&gt;
&lt;pre tabindex=&#34;0&#34; class=&#34;chroma&#34;&gt;&lt;code&gt;&lt;span class=&#34;lnt&#34;&gt;1
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;
&lt;td class=&#34;lntd&#34;&gt;
&lt;pre tabindex=&#34;0&#34; class=&#34;chroma&#34;&gt;&lt;code class=&#34;language-text&#34; data-lang=&#34;text&#34;&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;27B × 4 bytes ≈ 108GB
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;
&lt;/div&gt;
&lt;/div&gt;&lt;p&gt;这还没算激活值、KV Cache、优化器状态和其他运行开销。也就是说，&lt;code&gt;FP32&lt;/code&gt; 在今天的大模型推理和训练里，已经不是“默认选择”，而更像是“最稳的基线格式”。&lt;/p&gt;
&lt;h2 id=&#34;fp16体积减半但稳定性一般&#34;&gt;FP16：体积减半，但稳定性一般
&lt;/h2&gt;&lt;p&gt;&lt;code&gt;FP16&lt;/code&gt; 把每个参数压缩到 2 个字节，显存占用相比 &lt;code&gt;FP32&lt;/code&gt; 直接减半。&lt;/p&gt;
&lt;p&gt;&lt;img src=&#34;https://www.knightli.com/2026/04/22/common-tensor-formats-fp32-fp16-bf16-tf32-fp8/fp16-layout.svg&#34;
	
	
	
	loading=&#34;lazy&#34;
	
		alt=&#34;FP16 位宽结构示意图&#34;
	
	
&gt;&lt;/p&gt;
&lt;p&gt;对于同一个 27B 模型，如果只看权重体积：&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;div class=&#34;chroma&#34;&gt;
&lt;table class=&#34;lntable&#34;&gt;&lt;tr&gt;&lt;td class=&#34;lntd&#34;&gt;
&lt;pre tabindex=&#34;0&#34; class=&#34;chroma&#34;&gt;&lt;code&gt;&lt;span class=&#34;lnt&#34;&gt;1
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;
&lt;td class=&#34;lntd&#34;&gt;
&lt;pre tabindex=&#34;0&#34; class=&#34;chroma&#34;&gt;&lt;code class=&#34;language-text&#34; data-lang=&#34;text&#34;&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;27B × 2 bytes ≈ 54GB
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;
&lt;/div&gt;
&lt;/div&gt;&lt;p&gt;这就已经能解释为什么很多部署说明里，27B 模型的显存需求会落在 50GB 左右。&lt;/p&gt;
&lt;p&gt;&lt;code&gt;FP16&lt;/code&gt; 的优势很明显：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;显存压力大幅下降&lt;/li&gt;
&lt;li&gt;吞吐更高&lt;/li&gt;
&lt;li&gt;早期混合精度训练大量使用&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;但它的问题在于指数位偏小，动态范围不够大。对于大模型训练来说，这会让溢出更容易发生，需要额外依赖 loss scaling 一类技巧来补救，工程上比较麻烦。&lt;/p&gt;
&lt;p&gt;所以现在 &lt;code&gt;FP16&lt;/code&gt; 仍然常见，但在很多场景里，它已经不再是最舒服的选择。&lt;/p&gt;
&lt;h2 id=&#34;bf16大模型时代更实用的半精度&#34;&gt;BF16：大模型时代更实用的半精度
&lt;/h2&gt;&lt;p&gt;&lt;code&gt;BF16&lt;/code&gt; 同样只占 2 个字节，但和 &lt;code&gt;FP16&lt;/code&gt; 的设计重点不一样。&lt;/p&gt;
&lt;p&gt;&lt;img src=&#34;https://www.knightli.com/2026/04/22/common-tensor-formats-fp32-fp16-bf16-tf32-fp8/bf16-layout.svg&#34;
	
	
	
	loading=&#34;lazy&#34;
	
		alt=&#34;BF16 位宽结构示意图&#34;
	
	
&gt;&lt;/p&gt;
&lt;p&gt;它保留了更大的指数范围，让它在动态范围上更接近 &lt;code&gt;FP32&lt;/code&gt;，只是牺牲了一部分尾数精度。这种取舍对大模型尤其友好，因为很多时候模型对“范围”更敏感，对尾数少几位反而没那么敏感。&lt;/p&gt;
&lt;p&gt;这也是为什么现在很多训练框架、很多大模型论文和大量实际部署方案，都更偏向 &lt;code&gt;BF16&lt;/code&gt;。&lt;/p&gt;
&lt;p&gt;你可以把它理解成：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;显存成本接近 &lt;code&gt;FP16&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;稳定性体验更接近 &lt;code&gt;FP32&lt;/code&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;如果一套 27B 部署方案写的是 50GB 左右显存，而另一套经过进一步优化后接近 30GB，前者往往还停留在 &lt;code&gt;FP16/BF16&lt;/code&gt; 这一层，后者则通常已经继续向更低精度或量化方向走了。&lt;/p&gt;
&lt;h2 id=&#34;tf32不是省显存而是加速-fp32-工作流&#34;&gt;TF32：不是省显存，而是加速 FP32 工作流
&lt;/h2&gt;&lt;p&gt;&lt;code&gt;TF32&lt;/code&gt; 很容易被误会成“又一种更省的格式”，但它的定位其实不太一样。&lt;/p&gt;
&lt;p&gt;从常见理解上看，它可以近似看成一种保留了较大指数范围、但缩短了尾数精度的计算格式。&lt;/p&gt;
&lt;p&gt;&lt;img src=&#34;https://www.knightli.com/2026/04/22/common-tensor-formats-fp32-fp16-bf16-tf32-fp8/tf32-layout.svg&#34;
	
	
	
	loading=&#34;lazy&#34;
	
		alt=&#34;TF32 计算格式示意图&#34;
	
	
&gt;&lt;/p&gt;
&lt;p&gt;不过要注意，&lt;code&gt;TF32&lt;/code&gt; 更像是一条 Tensor Core 计算路径里的内部计算格式，而不是像 &lt;code&gt;FP16/BF16&lt;/code&gt; 那样主要拿来做权重存储。&lt;/p&gt;
&lt;p&gt;它主要是 NVIDIA 在较新的 GPU 上提供的一种计算模式，目标不是减少显存占用，而是让原本基于 &lt;code&gt;FP32&lt;/code&gt; 的训练流程，在尽量不大改代码的前提下跑得更快。&lt;/p&gt;
&lt;p&gt;它的特点可以概括成一句话：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;对外看起来还是 &lt;code&gt;FP32&lt;/code&gt; 工作流&lt;/li&gt;
&lt;li&gt;底层在矩阵乘法时做了更快的近似计算&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;所以 &lt;code&gt;TF32&lt;/code&gt; 主要解决的是“&lt;code&gt;FP32&lt;/code&gt; 太慢”的问题，而不是“&lt;code&gt;FP32&lt;/code&gt; 太占显存”的问题。如果你关心的是为什么同一个模型部署时显存需求不一样，&lt;code&gt;TF32&lt;/code&gt; 不是最主要的答案。&lt;/p&gt;
&lt;h2 id=&#34;fp8进一步压缩但更考验工程能力&#34;&gt;FP8：进一步压缩，但更考验工程能力
&lt;/h2&gt;&lt;p&gt;再往下走就是 &lt;code&gt;FP8&lt;/code&gt;。它把单个数值继续压缩到更少 bit 数，进一步降低显存带宽和存储成本。&lt;/p&gt;
&lt;p&gt;它常见的不是单一一种格式，而是两类变体：&lt;code&gt;E4M3&lt;/code&gt; 和 &lt;code&gt;E5M2&lt;/code&gt;。&lt;/p&gt;
&lt;p&gt;&lt;img src=&#34;https://www.knightli.com/2026/04/22/common-tensor-formats-fp32-fp16-bf16-tf32-fp8/fp8-layout.svg&#34;
	
	
	
	loading=&#34;lazy&#34;
	
		alt=&#34;FP8 两种常见变体示意图&#34;
	
	
&gt;&lt;/p&gt;
&lt;p&gt;但 &lt;code&gt;FP8&lt;/code&gt; 的代价也很明显：位数太少以后，你很难同时兼顾范围和精度，因此实际工程里通常会针对不同阶段采用不同变体，分别照顾前向、反向和梯度的稳定性。&lt;/p&gt;
&lt;p&gt;这类格式代表的是一种更激进的思路：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;愿意牺牲更多精度&lt;/li&gt;
&lt;li&gt;换取更低的存储和更高的吞吐&lt;/li&gt;
&lt;li&gt;需要更成熟的硬件和训练框架配合&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;它很有前景，但对普通使用者来说，日常最常碰到的核心分界点，通常还是 &lt;code&gt;FP32&lt;/code&gt;、&lt;code&gt;FP16&lt;/code&gt; 和 &lt;code&gt;BF16&lt;/code&gt;。&lt;/p&gt;
&lt;h2 id=&#34;为什么理解这些类型很重要&#34;&gt;为什么理解这些类型很重要
&lt;/h2&gt;&lt;p&gt;很多人第一次看到这些缩写，会把它们理解成“模型下载页上的一些实现细节”。但实际上，它们会直接改变你对模型训练和部署的理解方式。&lt;/p&gt;
&lt;p&gt;比如同样一张显卡：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;为什么有些模型训练必须强调数值稳定性&lt;/li&gt;
&lt;li&gt;为什么有些推理方案会优先谈量化和低精度&lt;/li&gt;
&lt;li&gt;为什么看起来参数量相近的模型，部署门槛却差很多&lt;/li&gt;
&lt;li&gt;为什么有些格式适合存权重，有些格式更适合做计算路径&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;这些问题往下拆，最后几乎都会回到同一个核心：你到底怎么在“精度、范围、显存和速度”之间做取舍。&lt;/p&gt;
&lt;p&gt;也正因为这样，理解 &lt;code&gt;FP32&lt;/code&gt;、&lt;code&gt;FP16&lt;/code&gt;、&lt;code&gt;BF16&lt;/code&gt;、&lt;code&gt;TF32&lt;/code&gt; 和 &lt;code&gt;FP8&lt;/code&gt;，不只是为了看懂术语表，而是为了在面对训练配置、推理引擎和部署门槛时，知道这些数字背后到底在交换什么。&lt;/p&gt;
&lt;h2 id=&#34;一个实用的理解框架&#34;&gt;一个实用的理解框架
&lt;/h2&gt;&lt;p&gt;如果你不想一上来就记一堆格式细节，可以先用下面这个顺序理解：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;FP32&lt;/code&gt;：最稳、最贵&lt;/li&gt;
&lt;li&gt;&lt;code&gt;FP16&lt;/code&gt;：更省显存，但范围偏小&lt;/li&gt;
&lt;li&gt;&lt;code&gt;BF16&lt;/code&gt;：显存接近 &lt;code&gt;FP16&lt;/code&gt;，稳定性更适合大模型&lt;/li&gt;
&lt;li&gt;&lt;code&gt;TF32&lt;/code&gt;：主要解决 &lt;code&gt;FP32&lt;/code&gt; 太慢，不主要解决显存&lt;/li&gt;
&lt;li&gt;&lt;code&gt;FP8&lt;/code&gt;：更激进的压缩和加速路线&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;img src=&#34;https://www.knightli.com/2026/04/22/common-tensor-formats-fp32-fp16-bf16-tf32-fp8/tensor-format-summary.svg&#34;
	
	
	
	loading=&#34;lazy&#34;
	
		alt=&#34;常见张量类型总结图&#34;
	
	
&gt;&lt;/p&gt;
&lt;p&gt;当你以后再看到模型下载页里写着 &lt;code&gt;fp16&lt;/code&gt;、&lt;code&gt;bf16&lt;/code&gt;、&lt;code&gt;fp8&lt;/code&gt;，或者看到不同部署教程给出完全不一样的显存门槛时，就不会再觉得那只是“写法不同”。它们背后其实对应的是完全不同的精度预算和工程取舍。&lt;/p&gt;
&lt;h2 id=&#34;结语&#34;&gt;结语
&lt;/h2&gt;&lt;p&gt;大模型里的张量类型，表面上是在讨论 bit 数，实际上讨论的是一整套工程取舍。&lt;/p&gt;
&lt;p&gt;&lt;code&gt;FP32&lt;/code&gt;、&lt;code&gt;FP16&lt;/code&gt;、&lt;code&gt;BF16&lt;/code&gt;、&lt;code&gt;TF32&lt;/code&gt; 和 &lt;code&gt;FP8&lt;/code&gt; 没有绝对的好坏，它们只是分别站在不同的位置上，帮你在稳定性、范围、精度、显存和速度之间做平衡。&lt;/p&gt;
&lt;p&gt;如果把这一层看懂，后面无论你是在读训练论文、调推理参数，还是比较不同部署方案，都会更容易抓住重点。&lt;/p&gt;
</description>
        </item>
        
    </channel>
</rss>
