一、浮点数的基本概念
1.1 科学计数法回顾
浮点数的本质就是科学计数法(Scientific Notation):
- 十进制:$6.02 \times 10^{23}$
- 二进制:$1.xxxx \times 2^{E}$
两点关键:
- 科学计数法约定小数点前必须有且仅有一位非零数字(规范形式)
- 对二进制来说,小数点前的数字只能是1(不可能是0,否则移小数点即可)
以前觉得浮点数没什么好讲的——IEEE 754标准往那一放就行了。但近两年因为大模型的出现,浮点数突然变成了非常有趣的课题。
1.2 浮点数的基本结构
以32位(单精度)为例:
\[(-1)^S \times (1 + M) \times 2^{E - bias}\]| 字段 | 位数(FP32) | 说明 |
|---|---|---|
| 符号位 S | 1 bit | 0正1负 |
| 阶码 E | 8 bits | 用移码(biased)表示,bias = 127 |
| 尾数 M | 23 bits | 纯小数部分,隐含最高位的1 |
1.3 隐藏位(Hidden Bit)——计算机人的”小trick”
既然规格化后小数点前的一定位一定是1,那就不用存了,硬生生多出一位精度。这是计算机人的小trick——数学人不在乎,反正纸上要多长有多长。
对于尾数23位的FP32,实际精度是24位(23 + 隐藏的1)。同理,双精度的52位尾数实际精度是53位。
1.4 浮点数的分布特性
浮点数不是均匀分布的:
- 在小数附近(阶码小):尾数的23位变化可以描述得非常精细,密度高
- 在大数附近(阶码大):同样23位尾数的变化步长被阶码放大,变得稀疏
从1万到10万是$2^{23}$个描述,从1亿到10亿也是$2^{23}$个描述。数的稀疏程度完全不同——大的时候稀疏,小的时候密集。
1.5 移码(Biased Exponent)
8位阶码用移码表示,bias=127:
- 将0~255的整数空间平分,00000000→-127,01111111→0,11111111→+128
- 偏移量127 = $2^{7}-1$
这样设计的好处:比较两个浮点数大小时,可以先按无符号整数比较阶码部分,硬件实现简单。
二、IEEE 754标准
2.1 标准化的历史
1950年代计算机就出现了,但直到1985年才完成浮点数标准的制定,由William Kahan主导完成,并因此获得图灵奖。这么常用的东西居然等了三十多年,令人难以置信。在IEEE 754之前,各家厂商各自定义浮点数格式,互不兼容。
2.2 规格化数(Normalized Numbers)
当阶码 $E$ 满足 $1 \leq E \leq 254$ 时:
\[\text{value} = (-1)^S \times (1.M) \times 2^{E - 127}\]阶码全0和全1被留作特殊用途。
2.3 特殊值编码
IEEE 754的特殊值体系:
| 阶码 E | 尾数 M | 表示的值 | 说明 |
|---|---|---|---|
| 0 | 0 | $\pm 0$ | 正零和负零(浮点数有符号零) |
| 0 | $\neq 0$ | 非规格化数(Subnormal) | 表示非常接近零的值,解决”下溢缺口” |
| 1~254 | 任意 | 规格化数 | 正常的浮点数 |
| 255 | 0 | $\pm\infty$ | 正无穷/负无穷 |
| 255 | $\neq 0$ | NaN(Not a Number) | 非法运算结果(如0/0、$\infty - \infty$) |
2.4 无穷大(Infinity)
整数除零是异常,程序会崩;但浮点数除零不报异常,而是返回无穷大。这是设计上的有意选择——浮点运算希望能”优雅地”继续下去。
2.5 NaN(Not a Number)
NaN 是调试浮点程序的关键工具:
- 任何涉及 NaN 的运算结果都是 NaN
- 不同类型的 NaN(Signaling NaN vs Quiet NaN)提供了更细粒度的控制
没有NaN就没法debug浮点数。出错了就不知道在哪出错了。
2.6 非规格化数(Subnormal / Denormalized)
当阶码为0、尾数非零时,采用不同的解释方式:
\[\text{value} = (-1)^S \times (0.M) \times 2^{-126}\]隐藏位变为0(不再隐含为1),这使得可以表示比最小规格化数($2^{-126}$)更接近零的值。
2.7 关于正负零的讨论
补码设计时特意避免了正零和负零的问题,但IEEE 754保留了正负零的存在。对计算机来说无非是多一个if-else判断。
三、浮点数运算的精度问题
3.1 浮点数不能直接判等
千万不要在代码里写 if (a == b) 来判断两个浮点数相等。必须用差值小于一个很小的阈值来判断两个浮点数是否”足够接近”。
这是浮点数天生的精度限制导致的问题。
3.2 大数吃小数
当一个极大的数和一个很小的数相加时,小的数可能被”吃掉”——大数在该量级下的分辨率不足以表示小数的贡献。例如,在FP32中 $3.4 \times 10^{38} + 1$ 的结果还是 $3.4 \times 10^{38}$。
四、从FP32到低精度:AI时代的浮点数革命
4.1 FP32的问题
FP32的表示范围和精度够用,但AI时代的计算量太大——FP32太大了、太慢了。
4.2 FP16(传统半精度)
如果32位用5位阶码+10位尾数,可以按比例缩到16位。但传统FP16(5位阶码+10位尾数)与FP32之间没有简单的转换关系。
4.3 BF16(Brain Float 16)——AI时代的明星
BF16 是Google为TPU设计的格式:1位符号 + 8位阶码 + 7位尾数。
BF16成功的核心原因:BF16与FP32共享相同的8位阶码(相同的指数范围),从FP32转BF16只需要截断尾数——硬件上一个assign语句就完成,不消耗任何时间。
| 格式 | 总位数 | 阶码 | 尾数 | 与FP32转换 |
|---|---|---|---|---|
| FP32 | 32 | 8 | 23 | — |
| BF16 | 16 | 8 | 7 | 直接截断尾数 |
| FP16 | 16 | 5 | 10 | 需要完整转换 |
从高精度降到低精度,就像砍一棵树——不能把头砍掉。阶码(指数)是”头”,尾数是”尾”——保留指数范围比保留尾数精度更重要。把蛇头砍掉,这蛇就没法活了。
5.4 TF32(TensorFloat-32)
NVIDIA提出的TF32:名义上32位,实际有效位只有19位(1符号+8阶码+10尾数)。
5.5 FP8(2022年)
FP8在2022年正式提出,只有8位。有两种主要变体:
| 变体 | 阶码 | 尾数 | 特点 | 适用场景 |
|---|---|---|---|---|
| E4M3 | 4 | 3 | 精度更高 | 前向传播 |
| E5M2 | 5 | 2 | 动态范围更大 | 梯度计算 |
混合精度训练不要求所有层用同一种格式——精度敏感的层用高精度,不敏感的层用低精度。
5.6 FP4
FP4只有4位,一共只能表示16个数。不同的人可以定义不同的FP4变体,1位符号+若干位阶码+若干位尾数的分配方式有很多种。
五、大模型量化:从浮点到整数
5.1 量化的动机
大模型参数量太大(几百B到上千B),普通显卡部署不下。推理时浮点数计算太麻烦——反正推理对精度要求没那么高,凑合用整数就行了。
5.2 整数量化谱系
| 精度 | 可表示值个数 | 说明 |
|---|---|---|
| FP32 | $\sim 4\times 10^9$ | 原始训练精度 |
| INT8 | 256 | HuggingFace/ModeScope上常见 |
| INT4 | 16 | 大幅减小模型大小 |
| INT2 | 4 | 极端压缩 |
| INT1 | 2 | 1-bit量化 |
六、BitNet:1-bit Transformer
6.1 从INT8/INT4直奔1-bit
一般的选手每次都破一点点纪录,拿一点点奖金。BitNet那帮人直接开一大刀——从INT16/INT8什么的一步跳到1-bit。
6.2 BitNet(2023年10月)
微软研究院、中科院、清华联合发表的BitNet论文提出:将Transformer的权重全部量化为 +1 或 -1。
\[W_{\text{quantized}} = \text{sign}(W) = \begin{cases} +1, & W > 0 \\ -1, & W < 0 \end{cases}\]核心收益:乘法变成加法和取反!
- ×(+1) → 不变号,就是加法
- ×(-1) → 取反
- 不需要浮点乘法器,能耗大幅下降
6.3 1-bit的局限性
0.01和-0.001本来差别很小,但一个变成+1一个变成-1,差别巨大。靠近零的权重被过度放大。
6.4 BitNet 1.58(2024年2月)
将权重量化为三个值:-1、0、+1。
为什么叫”1.58 bit”?
\[\log_2(3) \approx 1.58\]这名字起得真好。正常思维三值需要2位,但人家说是1.58 bit。你说是1.58?他说是,你也没办法。论文标题叫 The Era of 1-bit LLMs——让人一下就记住了。
6.5 BitNet 1.58的性能
实验结果表明(相对于FP16基线):
- 内存占用:从2.0G降到0.8G(2.6倍提升)
- 延迟:1.23倍到2.4倍的加速(取决于模型规模)
- 精度(loss):略高于FP16基线(5.24 vs 4.7),但随着模型增大差距缩小到0.09——有竞争力(competitive)
不要太相信论文的漂亮数字——”就像出门见人,把衣服穿漂亮了、脸洗了,但可能牙没刷、衬衣破了洞。论文里面一定有坑,只是坑在哪。”
七、论文写作方法论
7.1 摘要的八股文结构
摘要写作的固定结构:
- 第一句:背景 + 当前存在的问题
- 第二句:我们做了什么工作(”In this work we introduce…“)
- 第三句:实验结果表明什么(”Experimental results show…“)
不要一上来就来散文——”在这样的日子里”——不知道你要说什么。八股文很重要。
7.2 英文论文的语言艺术
- “competitive performance”:不说”超过”,说有竞争力的表现
- “match full precision”:敢说匹配全精度了
- 介绍工作要敢于说出亮点,但措辞要严谨
7.3 取名的重要性
1.58这个数字比”2”有趣得多。如果叫”2-bit”,别人觉得早就有了;叫”1.58-bit”一下子让人记住。写论文就是在讲故事,起个好名字太重要了。
八、浮点格式全谱系总结
各种浮点格式按精度从高到低梳理:
| 格式 | 总位数 | 符号 | 阶码 | 尾数 | 典型用途 |
|---|---|---|---|---|---|
| FP80 | 80 | 1 | 15 | 64 | x86扩展精度(已少用) |
| FP64 | 64 | 1 | 11 | 52 | 科学计算双精度 |
| FP32 | 32 | 1 | 8 | 23 | 经典单精度 |
| TF32 | 19 | 1 | 8 | 10 | NVIDIA GPU训练 |
| BF16 | 16 | 1 | 8 | 7 | AI训练/推理首选 |
| FP16 | 16 | 1 | 5 | 10 | 传统半精度 |
| FP8 E4M3 | 8 | 1 | 4 | 3 | 前向传播 |
| FP8 E5M2 | 8 | 1 | 5 | 2 | 梯度计算 |
| FP4 | 4 | — | — | — | 极端量化 |
| BitNet 1.58 | 1.58 | — | — | — | {-1, 0, +1} 三值 |
| BitNet | 1 | — | — | — | {-1, +1} 二值 |
九、核心总结
9.1 三条主线
- IEEE 754标准:从科学计数法到计算机内部二进制表示,理解规格化数、非规格化数、特殊值(零、无穷、NaN)的编码方式
- 从FP32到低精度:AI驱动了浮点格式的创新——BF16的巧妙设计(保留阶码)、FP8的双变体、FP4的极端压缩
- 1-bit革命的启示:BitNet和BitNet 1.58展示了极端量化的可能性,乘法变加法的硬件收益巨大;同时也展示了论文写作、命名、传播的科学方法论
9.2 核心观点
- IEEE 754看似简单,但直到1985年才标准化——简单的标准有时最需要洞察力
- BF16成功的本质:共享FP32的指数范围,转换零开销
- 量化一定有精度损失——关键是如何让损失可控、让收益超过损失
- 1-bit不是终点——从INT8到BitNet 1.58,每一步都是工程权衡
- 论文命名和写作很重要——”1.58”比”2”有趣一万倍
1985年IEEE 754那个时代大家没抓住机会——现在浮点数这个时代又走到了脚底下。机会又来了。