
温馨提示:另有配套视频版,附带图解演示,观感不同,欢迎到各大视频平台搜索【蛋先生说识】
BF16 是啥?
丹尼尔:蛋兄,我前几天刷到一个知识点------现在大模型的参数,训练后保存下来基本都是BF16格式。也就是说,一个数字只用 16 个 bit 来存?16 位是怎么存的?BF 又是什么意思?我脑子里全是问号!
蛋先生:BF16 中的 BF 指的是 Boy Friend
丹尼尔:???
蛋先生:哈哈,开玩笑的!是指 Brain Float,浮点数的一种存储设计。为什么是 Brain 呢?因为它是由 Google Brain 团队设计的------也许,这就是开发者最单纯的追求:自豪感。那为什么要设计 BF16 呢?它是为深度学习量身定做的,之所以会出现,就是因为当时的现状不太理想。那当时的现状指的是什么呢?就是 FP16。所以我们只要搞懂了 FP16,自然也就理解了 BF16
丹尼尔:好家伙,自问自答一圈,赶紧开讲吧,哈哈!
蛋先生:简单说就是把 16 个 bit 分成三部分:1 个符号位(S)、5 个指数位(E)、10 个尾数位(M)
浮点数原来是这样?
丹尼尔:等会儿,尾数位我能理解,就是小数点后面的数字嘛。但这指数位是什么鬼?为什么不直接搞个"整数位",像 3.14 那样整数部分和小数部分分开存?
蛋先生:好问题!你这个思路其实叫"定点数"。整数位和小数位各自固定,乍一看很直观
丹尼尔:不应该这样吗?
蛋先生:但它的致命伤就是"鱼与熊掌不可兼得" - 就是你很难同时去表达一个极大的数值和无限趋近于 0 的极小数值。你想啊,假如 15 位全给整数部分(就是二进制 15 个 1,请自行计算验证),那最大值也就 32767,小数一个都表示不了。反过来全给小数位,连个大于 1 的数都表示不了哦
丹尼尔:(⊙ˍ⊙) 那岂不是要么只能数星星,要么只能数细菌?太极端了!
蛋先生:所以聪明的人类发明了"浮点"(Floating Point)------通过指数位来移动小数点,左移缩小、右移放大,相当于给数字加了一个"缩放因子"
丹尼尔:哦~"浮点"这个名字原来是这么来的,小数点可以浮动!那具体怎么算呢?
蛋先生:公式长这样:
scss
V = (-1)^S × 2^(E-15) × (1.M)₂(即 V = (−1)ˢ × 2ᴱ⁻¹⁵ × (1.M)₂)
S 是符号位,决定正(S 为 0 时值为 1)负(S 为 1 时值为 -1);E 是指数位;M 是尾数位(比如 1.101..101,如果 M=15,即 1.xxx 就有 15 个 0 和 1 组成的二进制数字)
指数位怎么理解?
丹尼尔:等下,为什么指数是 E-15?这个 15 是哪里冒出来的?
蛋先生:我们既需要正的指数来放大数字,也需要负的指数来缩小数字(简单理解就是指数多少,就将小数点移动多少。比如 1.01 * 2¹ 就是将小数点往右移动一格,就变成 10.1)。如果直接存正负符号,机器还得额外处理符号,又折腾又浪费空间
丹尼尔:那咋办?
蛋先生:指数位是 5,5 个 bit 有多少个 1 和 0 的组合?就是 2⁵,所以它能表示 32 个数,从 0 到 31。取个中间值 15 作为偏移量(Bias)。指数存 13(二进制 01101),实际指数就是 13 - 15 = -2;存 17(二进制 10001),实际指数就是 17 - 15 = 2。按照 IEEE 754 规约数规范(只需要知道就是一种规范就行了),E 的取值是 1~30(E=0 和 E=31 被保留给了零/非规约数和无穷/NaN)。所以真实指数范围是 -14 到 +15(严谨讲是规约数规范下的范围)
丹尼尔:哇塞,这也太巧妙了!
蛋先生:这样的设计也可以让指数直接按无符号整数比较大小,大幅简化了浮点数比较的硬件电路设计(就不展开说了,越说越蒙圈)
隐式前导位的巧妙设计
丹尼尔:那 (1.M)₂ 里面的 1 又是什么?
蛋先生:这是最妙的地方------这个 1 叫"隐式前导位"。按照 IEEE 754 规范,规约数永远在前面有个 1,比如 1.10101... 但它不用存!机器自动认,凭空省了 1 个 bit
丹尼尔:(゚Д゚*) 还能这样操作?但这样好像无法表示 0 值?
蛋先生:眼睛很尖嘛。看公式计算 2¹⁻¹⁵ × 1.0 = 2⁻¹⁴ ≈ 0.000061035,这是它能表示的最小正值,所以确切地说,是无法表示靠近 0 的极小数值。实际应用中,如果遇到非规约数,会有各种各样的解决方案,这里就不展开了
指数位多少才合适?
丹尼尔:那我还有一个疑问:指数位为什么偏偏是 5 位?是拍脑袋决定的还是精心设计的?
蛋先生:当然不是拍脑袋。指数位越多 → 数值范围越大,但尾数位就少了 → 精度下降。反过来也一样。5 位是指数范围和精度,根据当时的应用场景权衡出来的结果(具体当时主要是什么应用场景?当时又是如何权衡?感兴趣的话自个回头问问 AI 哈)
丹尼尔:等等,那开头说的 BF16 呢?讲了半天 FP16,BF16 到底怎么回事?
蛋先生:别急,铺垫到这了。你想啊,FP16 这么好用,为什么还要搞个 BF16 出来?
丹尼尔:对啊,吃饱了撑的?
蛋先生:问题出在"训练"上。模型训练用的是 FP32(32 位浮点),指数位有 8 位。而 FP16 才 5 位。保存时如果使用 FP16,那肯定会各种溢出,就好比蛇吞象一样
丹尼尔:那 BF16 怎么解决?
蛋先生:BF16 就是 1 位符号 + 8 位指数 + 7 位尾数。指数位和 FP32 完全一致,所以 BF16 的数值范围跟 FP32 一模一样,从 FP32 转 BF16 几乎零成本。代价是尾数位少了,精度降了------但深度学习对精度不敏感,对范围更敏感
丹尼尔:从定点到浮点,从 FP16 到 BF16,无不体现着人类的智慧啊!
蛋先生:( ̄▽ ̄)~*