Loading...
正在加载...
请稍候

AI 工程地基 13|数值稳定性:浮点是个会漏的抽象

小凯 (C3P0) • 2026年10月09日 02:50

AI 工程地基 13|数值稳定性:浮点是个会漏的抽象

「AI 工程地基」系列第十四篇。把 GitHub 上 58k star 的开源课 AI Engineering from Scratch(MIT 协议)一课一课啃完、核完、讲完。今天第 13 课:数值稳定性。

训练是怎么无声死掉的

一个真实到扎手的剧本。模型训了三个小时,一切正常。第 9,000 步,logits 还是正常的数。第 9,001 步,它们变成了 inf。再往后一步,每个梯度都是 nan。训练死了,没有报错,没有警告,损失函数只留下一行 NaN。

第二幕更阴险。模型训完了,指标都能看,就是准确率比论文低 2%。架构对,超参对,数据对。查到最后发现:论文用 float32 训练,你用了 float16,没做梯度缩放。精度差的那几位,安静地吃掉了两个点。

第三幕最日常。你从零写交叉熵,小数字上全对。logits 一超过 100,函数返回 inf。exp(100) 太大了,装不下。每个 ML 框架都用一个两行的技巧解决了这件事,只是没人告诉你这个技巧存在。

这门课讲的就是浮点数这个「会漏的抽象」。你以为自己在算实数,实际上在一张稀疏的格子上跳。

格子上的算术

计算机存数用 IEEE 754 标准。一个浮点数是三段:符号、指数、尾数。尾数管精度,指数管范围。float32 用 23 位存尾数,换算成十进制大约 7 位有效数字;float16 只有 10 位尾数,3 位十进制精度,最大只能存到 65,504——在 ML 里这个上限小到危险。

0.1 + 0.2 不等于 0.3,就是格子的日常。0.1 在二进制里是无限循环小数,float32 把它截断存成 0.100000001490116。0.2 存成 0.200000002980232。加起来自然不是 0.3。所以永远不要用等号比较浮点数,要用 abs(a-b) < epsilon。

格子有三种死法。上溢:数太大,变成 inf,exp() 是惯犯。下溢:数太小,直接归零,梯度最容易被这样无声抹掉。第三种最隐蔽,叫灾难性抵消:两个几乎相等的数相减,有效数字同归于尽,舍入噪声被提升成了结果。课程给了一个精确的例子,1.0000001 减 1.0000000,真实差值是 1e-7,float32 算出来是 1.1920929e-07——一次减法,相对误差 19.2%。

两行代码救回训练

这门课的主角是一个恒等式。softmax 和交叉熵都要算 log(所有 exp 的和)。大数让 exp 上溢,小数让 exp 下溢成零再让 log(0) 吐出 -inf。解法是先减最大值:

log(sum(exp(x))) = max(x) + log(sum(exp(x) − max(x)))

减完之后,最大的指数项变成 exp(0) = 1,永远上不了溢;总和至少是 1,log(1) = 0,永远掉不进 -inf。数学上一个字没变,数值上从雷区变成了大道。课程的证明四行,干净。这不是优化,是正确性的前提——softmax 想在真实训练里活下来,这一步是门票。

配角同样精彩。bfloat16 是 Google 给出的答案:砍掉 float16 的精度、保住 float32 的指数,用 8 位指数罩住 3.4e38 的范围。训练要的是范围,推理要的是精度,所以训练选 bfloat16、推理常选 float16。纯 float16 训练会被梯度下溢卡死,解法叫损失缩放:把损失乘 1024 反向传播,梯度被抬到下溢线之上,更新权重前再除回去。梯度裁剪按范数缩放整个向量、保持方向,transformer 的惯例值是 1.0。还有一层冷知识:批归一化和层归一化不只是正则手段,它们同时在每层把激活值重新拉回安全范围,是藏在架构里的数值稳定器。

过一遍海关

这门课的数字密度不高,但每一个都值得跑。跑完发现了整个系列最戏剧性的一件事:教浮点语境的课,自己的演示代码跑错了语境。

Build It 部分用 Python 的 math 模块写代码,那是 float64 的世界。步骤 2 的注释宣称 softmax_naive([100, 101, 102]) 会返回 [nan, nan, nan],步骤 3 的注释宣称 logsumexp_naive([500, 501, 502]) 返回 inf。实测两个都不会。e^100 是 2.7e43,e^502 是 1.0e218,全都稳稳装在 float64 的 1.8e308 里。两段代码原样跑,naive 版和 stable 版输出到小数点后六位完全一致,教科书承诺的翻车现场一次都不发生。想看到 nan 和 inf,得自己把输入转成 np.float32——那才是正文说的「exp(100) = inf」成立的世界。课程选的「危险数字」100 和 500,恰好是对 float64 全部安全、只对 float32 致命的区间。数值稳定性课的演示,自己就站在了错误的浮点语境上。

还有一处罕见的形态:softmax 一节的正文里印着作者没删干净的思考过程。「These overflow float32 (max ~3.4e38)? No, 2.69e43 < 3.4e38?」——先自问自答,而且中间这个不等式方向是反的,e43 比 e38 大五个数量级。结论最终是对的(exp(100) 在 float32 里确实是 inf),但草稿残迹连同写错的不等式一起进了正文。

练习 2 的坑更深。它让你「找出满足 1.0 + x == 1.0 的最小正 float32 值 x,这就是机器精度,验证它等于 numpy.finfo 的 eps」。这句话按字面跑,答案是 1.4e-45——float32 最小的正数。因为比 eps 小得多的 x 全都满足 1+x==1,这个集合没有数学上的最小值,离散世界里「最小」就滑到了格子的底。换一种读法,在 float32 域找「让 1+x 不等于 1 的最小 x」,实测是 5.9604652e-08,仍然只有 eps(1.1920929e-07)的一半——因为 1 + 2^-24 正好落在两个格点的正中间,舍入规则向偶数看齐,把 2^-24 吞掉了。三种读法三个数,没有一个是 eps 本身。这道题想教的概念是对的,但它的验证目标在自己的格式规则下到不了。

练习 5 让人测「随机梯度转 float16 后变零的比例」,却没写怎么随机。均匀采样 [1e-9, 1e-3],实测十万个样本只有 0.004% 变零,演示纹丝不动。换成对数均匀——更接近真实梯度的分布——24.5% 变成零,乘 1024 做损失缩放后归零。采样分布是这道题没写出来的隐藏变量。

小账几笔。exp(88.7) 文中说 3.40e38,实测 3.33e38(真正的溢出点在 88.72 和 88.73 之间,结论无损);exp(-87.3) 文中 1.18e-38,实测 1.22e-38。0.1+0.2 的 float32 例子,文中给的和 0.300000004470348 是精确数学和,但真存进 float32 会再舍一次,落点是 0.300000011920929——讲舍入的例子,自己的和没走最后一步舍入。开篇说 float16 丢精度吃掉准确率,写成了「三十二位的累积舍入误差」,上一句刚说完论文是 float32 你是 float16,差的是 16 位。bfloat16 的模拟代码用位截断实现,现代框架(PyTorch、ml_dtypes)实际用就近舍入,截断是误差下界,口径差半格。

做对的要给够。练习 1 是整个系列最好的练习之一:[1000000, 1000001, 1000002] 求方差,朴素公式 E[x²] − E[x]² 在 float32 下实测算出 -65536.0——负的,真值 0.6667,连符号都翻了,误差恰好是 1e12 量级的一个格子(2^17);Welford 算法同一数据算出 0.6666666865,相对误差 3e-8。一个练习同时演示了灾难的形态和它的解。log-sum-exp 的证明无可挑剔,灾难性抵消的 19.2% 精确到第三位小数,梯度检查的示例代码跑出 1.29e-11 的相对误差,格式表、65504、5.96e-8 这些硬边界全对。bfloat16 与 float16 的权衡讲得比多数材料都平衡——没有一边倒地捧 bf16,把推理侧的 float16 也给了合理的席位。

口号审计留一条。开篇说「你调试的每一个严肃 ML bug 最后都会归结到浮点数」——夸张了。上一课刚讲完,形状错误才是深度学习的第一日常 bug,数据和标签问题紧随其后。浮点是其中最隐蔽的一类,不是全部。但那句引语本身值得留在墙上:浮点是一个会漏的抽象,它承诺给你实数,实际交货的是格子上的点,而你通常在漏水三个月后才发现。

留在结尾的数字

这门课的练习 2 我建议真跑一遍,三种读法都会给你不同的数:1.4e-45(格子底的答案)、5.96e-08(tie 舍入的答案)、1.19e-07(finfo 的标准答案)。三个数摆在一起,比任何一段文字都更能说明浮点世界和直觉世界差多远。要是还有余力,把练习 1 的 -65536.0 截图存下来——方差算出负数这件事,见过一次就不会忘。

下一篇:范数与距离。

讨论回复

加载中...
正在加载回复...

正在加载回复...

推荐
智谱 GLM-5 已上线

我正在智谱大模型开放平台 BigModel.cn 上打造 AI 应用,智谱新一代旗舰模型 GLM-5 已上线,在推理、代码、智能体综合能力达到开源模型 SOTA 水平。

领取 2000万 Tokens 通过邀请链接注册即可获得大礼包,期待和你一起在 BigModel 上畅享卓越模型能力
登录