
在对更强计算能力(尤其是在人工智能等领域)的不懈追求中,我们表示数字的方式本身已成为一个关键前沿。数值格式的选择是速度、内存效率和准确性之间的一种微妙平衡。本文深入探讨 bfloat16 (Brain 浮点) 格式,这是一种专门的 16 位数字表示法,已成为现代人工智能硬件的基石。它解决了如何在不陷入数值不稳定性(如上溢和下溢,这可能使大型模型的训练脱轨)的情况下加速大规模计算这一根本问题。我们将探讨这种格式如何做出激进的权衡,即牺牲精度以换取巨大的动态范围。
以下章节将引导您探索这个引人入胜的主题。首先,在“原理与机制”部分,我们将剖析浮点数的结构,将 bfloat16 与其对应的 fp32 和 fp16 格式进行比较,并揭示使其可行的数值陷阱和巧妙的解决方案(如混合精度计算)。然后,在“应用与跨学科联系”部分,我们将考察 bfloat16 的变革性影响,解释它如何实现了当今人工智能的速度和规模,以及其原理如何被用于彻底改变传统的科学模拟。
要真正领会 Brain 浮点格式(或称 bfloat16)的独创性,我们必须首先踏上一段简短的旅程,深入了解计算机表示数字的核心方式。这是一个充满妥协、权衡和巧妙设计的世界,这些设计在硬件的有限性与数学的无限广阔之间取得了平衡。
想象一下,你接到一个任务,要创造一种新型的尺子。你有两个选择。你可以制作一把标准的 12 英寸尺子,上面标有精细到千分之一英寸的刻度。这把尺子会非常精确,让你能够高精度地测量小物体。它的弱点是什么?它的范围有限;你不能用它来测量一个足球场的长度。
或者,你可以制作一卷一英里长的卷尺。然而,为了便于携带,你只能在上面为每一英尺设置一个标记。这把尺子有巨大的范围,但其精度很差。你不能用它来测量一张邮票的大小。
数字正面临着完全相同的困境。浮点数是计算机表示实数的标准方式,其本质上是一个科学记数法形式的数字。它由三部分组成:一个符号( 或 )、一个用于保存有效数字的尾数(或称有效数),以及一个用于确定小数点位置的指数。
分配给尾数的比特数决定了数字的精度——即它可以存储多少有效数字。分配给指数的比特数决定了其动态范围——即它可以表示的从最小到最大的数字跨度。
几十年来,科学计算的主力一直是 32 位单精度浮点数,即 fp32。它使用 1 个符号位、8 个指数位和 23 个用于尾数的小数位。这在精度和范围之间提供了良好的平衡。当为了节省内存和加速计算而出现对 16 位计算的需求时,自然而然的第一步是采用 IEEE 754 半精度格式,即 fp16。它大致上将所有部分减半,提供 1 个符号位、5 个指数位和 10 个小数位。它就像是 fp32 尺子的一个更短、精度更低的版本。
这正是 bfloat16 闪亮登场之处。Google 的设计者们着眼于人工智能的需求,做出了一种不同且激进的权衡。bfloat16 使用 1 个符号位、8 个指数位和仅 7 个小数位。
注意这个神奇的数字:8 个指数位。这与 32 位 fp32 格式的指数位数相同。这意味着 bfloat16 拥有与 fp32 同样巨大的动态范围。它是一种 16 位数字格式,能够表示与其 32 位“老大哥”一样天文数字般巨大和无穷小的数值。为获得这个不可思议的范围所付出的代价是尾数的大幅缩减:仅有 7 个小数位,而 fp32 有 23 位,fp16 有 10 位。它就像那卷一英里长、但每隔几英尺才有一个标记的卷尺。
为什么会有人做这笔交易?因为对于深度学习来说,这是一个绝妙的选择。神经网络,作为现代人工智能的引擎,出奇地稳健。它们通常表现得像大型统计系统,其中任何单个权重或激活的精确值,都不如数百万此类值的整体模式和分布重要。它们对低精度算术引入的“噪声”具有非凡的容忍度。
它们所不能容忍的是上溢和下溢。在训练过程中,被称为梯度的计算值有时会变得极大(“爆炸”)或小到无法用该格式表示(“消失”)。如果一个数字上溢到无穷大或下溢到零,学习过程就会陷入停滞。bfloat16 从 fp32 继承的巨大动态范围是它的超能力;它提供了一个广阔的空间,让数字可以在其中“漫游”而不会掉下数值悬崖。它的竞争对手 fp16,由于其只有 5 位的短指数,更容易发生此类溢出,这使得它在没有仔细处理的情况下,对于训练大型模型来说更为脆弱。
当然,天下没有免费的午餐。bfloat16 的 7 位尾数所带来的严重受限的精度,会产生深刻且有时令人震惊的后果。想象一下,在一片广阔平坦的地形上,你的 GPS 只能以十米为增量报告你的海拔高度。如果你向前迈出一小步,你的 GPS 读数不会改变。在你的 GPS 看来,地面是完全平坦的。
这正是 bfloat16 在数值微分中可能发生的情况。为了求函数 的斜率,我们通常对一个小的步长 计算 。但是,如果 太小,bfloat16 的低精度意味着计算机可能会发现 与 是完全相同的可表示数。分子变为零,计算出的斜率也为零,即使是对于像 这样简单的函数也是如此。这种低精度格式使我们无法看到函数的曲率。
另一个危险是“淹没”(swamping)或吸收(absorption),这在对许多数字求和时尤其成问题——这是人工智能中无处不在的点积运算的核心操作。想象一个亿万富翁的银行账户只追踪到美元。如果你存入一美分,余额不会改变。这一美分被吸收、丢失了。如果你进行一百万次一美分的存款,余额仍然没有改变。这个总和是灾难性的错误。
这种情况在 bfloat16 累加中不断发生。如果一个累加和变得很大,向其中添加一个很小的新数可能会导致这个小数被完全舍去。对于依赖累加许多小更新的方法来说,这是一场灾难,例如数值积分的梯形法则,其中低精度求和产生的舍入误差可以完全主导实际的数学答案。它也是矩阵乘法中误差的来源,因为矩阵乘法需要对许多乘积求和。在涉及两个几乎相等的大数相减的场景中,微小而正确的答案可能会被 bfloat16 的粗糙度引入的舍入噪声完全淹没。
那么,bfloat16 给了我们所需的范围,但其不准确性可能具有“欺骗性”。我们如何解决这个悖论?答案不是放弃 bfloat16,而是明智地将其用作一个更大、更智能策略的一部分:混合精度计算。
其核心思想异常简单:用快速、内存高效的低精度数字执行大部分计算,但用更高精度的格式执行少数几个关键敏感的操作。
这一原则最重要的应用是高精度累加器。现代人工智能硬件,如 Google 的张量处理单元 (TPU) 和 NVIDIA 的张量核心 (Tensor Core),就是为此设计的。它们使用 bfloat16 输入执行点积所需的数百万次乘法。但累加和——即累加器——则保存在一个完整的 32 位 fp32 寄存器中。这就像亿万富翁的银行有一个秘密的高精度账本用来追踪每一分钱。只有当这些分钱累加到一整美元时,主账户的低精度余额才会被更新。
这种简单的架构选择巧妙地避开了淹没问题。累加舍入误差现在由 fp32 小得多的单位舍入误差决定,变得几乎可以忽略不计。主要的误差来源仅仅是输入到 bfloat16 的初始、不可避免的量化。在一个完美平衡的场景中,我们甚至可以找到累加误差和输入量化误差处于相当量级的案例,这表明系统已为其任务进行了完美调优。这使我们能够以 16 位乘法的速度和效率获得 32 位加法的准确性。
在没有硬件支持的情况下,类似的效果也可以通过软件实现。例如,补偿求和算法使用一种巧妙的“无误差转换”,在一个高精度变量中跟踪每次加法产生的舍入误差,并在最后将这个累积的误差加回去,以恢复一个近乎完美的总和。
另一个行业技巧是损失缩放(loss scaling)。如果你担心梯度变得太小,可能会被 bfloat16 的低精度压缩为零,你可以简单地将整个目标函数乘以一个大的缩放因子,比如 。根据链式法则,你所有的梯度现在都增大了 倍,从而将它们安全地带出数值危险区。你用这些缩放后的梯度执行权重更新,然后在最后,通过除以 来取消对最终权重的缩放。由于 是 2 的幂,这个除法是一个精确、无误差的操作——它只是对浮点数的指数域进行一个简单的减法。
归根结底,bfloat16 不仅仅是一个“更差”或“更不准确”的数字格式。它是计算科学艺术的证明——一个诞生于对应用需求(人工智能)和浮点运算基本性质深刻理解的专用工具。当在混合精度的优雅框架内使用时,它代表了一种大师级的妥协,通过以有原则且智能的方式拥抱不完美,解锁了前所未有的计算能力。
既然我们已经剖析了 bfloat16 格式的内部结构,我们可能会留下一个萦绕不去的问题:何必多此一举?为什么要心甘情愿地扔掉我们宝贵精度的一半?这感觉有点像一个音乐家选择用一门弦更少的乐器来演奏。正如我们即将看到的,答案是出奇地反直觉。这种刻意牺牲的行为不是一种妥协,而是一把钥匙——一把能够解锁计算速度和能源效率惊人收益的钥匙,它正在彻底改变从人工智能到基础科学模拟的各个领域。这是一个关于“足够好”的算术艺术的故事,以及它如何帮助我们以十年前只能梦想的方式进行计算。
让我们从计算最原始的方面开始:能耗。芯片上每当一个晶体管翻转,它就消耗一小口电能。当你有数十亿个晶体管每秒翻转数十亿次时,这些小口就汇成了一场洪流。现代 CMOS 芯片的动态功耗遵循一个来自物理学的优美而简单的关系式:。功耗随工作电压 的平方增长!这种平方依赖关系是个“暴君”。为了构建更快、更节能的芯片,架构师们拼命地想降低电压。
我们的故事就从这里开始。为 bfloat16 这种更简单的格式设计的专用硬件,通常可以比其更复杂、更高精度的同类产品在更低的电压下运行。即使 bfloat16 操作涉及稍高的开关电容 ,从 项中获得的收益也可能是巨大的。这导致每次计算消耗的能量急剧下降,我们可以将这个量建模为 。在一个数据中心耗电量与小国相当的世界里,这不仅仅是学术上的好奇心,更是一项全球性的迫切需求。
当然,如果结果无用,这种节能将是一笔愚蠢的交易。我们付出的数值代价是什么?让我们想象一个长长的计算链,比如在卷积神经网络中处理单个图像所需的数百万次乘加运算。每个 bfloat16 操作都会引入一个微小的舍入误差。虽然每个误差都很小,但它们会累积起来。我们可以将其建模为在每一步都添加一点“噪声”。
这个噪声有多“大”?如果我们分析信噪比 (SNR),即真实信号强度与累积计算噪声的比值,差异是惊人的。一个在标准 32 位浮点 (FP32) 格式下可能具有高达 的卓越信噪比的假设计算,在相同条件下用 bfloat16 执行时,其信噪比可能会下降到约 。另一种看待它的方式是通过最坏情况的视角,即累积的相对误差会在多次运算后大幅增长。
信噪比大约只有二十!这听起来很糟糕!但这就是许多机器学习算法的魔力所在。通过梯度下降法训练神经网络的过程本身就是一个充满噪声的随机过程。损失函数的地形是一个狂野的高维地带,而算法只是在试图找到下山的路。来自 bfloat16 算术的额外噪声通常就像一种温和的、随机的颠簸,并不会使算法偏离轨道——在某些情况下,甚至可能帮助它摆脱一个糟糕的局部最小值!对于许多常见的、表现良好的问题,无论你使用 bfloat16 还是 FP32,最终的准确性几乎是相同的。
然而,这并非一个普适的保证。如果问题在数值上很棘手(“病态的”),或者我们需要找到一个精度极高的解,bfloat16 的量化噪声可能就太大了。优化过程可能会减慢,甚至永久卡住,无法进行达到目标所需的最后、微妙的调整。艺术在于知道何时“足够好”是真的足够好,并通过适当地预缩放数据来管理数字的动态范围,以防止上溢或下溢。
当我们从单个处理器转向用于训练当今庞大基础模型的大规模分布式超级计算机时,bfloat16 的好处会成倍增加。这些模型非常大,必须被分割到数千个 GPU 上。这个过程中的一个关键瓶颈是通信:GPU 必须不断地相互通信以同步它们的工作,主要方式是在一个称为全归约 (All-Reduce) 的步骤中对它们本地计算的梯度求和。由于 bfloat16 数字的大小是 FP32 的一半,你可以在相同的成本下通过网络线路传输两倍的数据,或者在一半的时间内传输相同的数据。这极大地减少了依赖于网络带宽的通信时间。
但在这里,大自然也揭示了它的微妙之处。通信时间有两个部分:一个带宽项(管道有多粗?)和一个延迟项(第一滴水通过需要多长时间?)。bfloat16 有助于前者,但对后者毫无作用。在极端规模下,当你拥有大量处理器时,随着处理器数量增长的延迟项可能成为主要瓶颈,较小数据格式的优势随之减弱。理解计算、带宽和延迟之间的这种相互作用,对于构建下一代人工智能超级计算机至关重要。
你可能认为这只是一个关于人工智能的故事。但科学中最深刻的思想往往会溢出其原有的边界。在深度学习的熔炉中磨练出来的混合精度计算原理,现在正在改变传统科学和工程模拟的格局。
考虑计算科学中最古老、最基本的问题之一:求解线性方程组 。这是从设计桥梁到预测天气等一切事物的核心。标准方法,即 LU 分解,对于大型系统来说在计算上可能极其严苛。但如果我们能用一个技巧呢?如果我们用快速的低精度 bfloat16 来执行大部分工作——即昂贵的分解过程呢?这将给我们一个快速但有些不准确的初始答案。现在,神来之笔在于,我们使用高精度的 64 位算术来计算我们的答案偏差了多少(即“残差”)。这个残差告诉我们解中的误差。然后我们再次使用我们快速但廉价的求解器来求解一个修正量,并将这个修正量在高精度下加回到我们的解中。
这个过程被称为*迭代求精*,就像用粗炭笔画出粗略的草图,然后用细尖笔清理细节。通过重复几次,我们通常能以完全高精度求解成本的一小部分,获得一个高度准确的解。同样强大的思想也延伸到更复杂的求解器,如用于计算地球物理学等领域的 GMRES。在这里,人们必须更像一位艺术家,仔细选择算法的哪些部分可以容忍 bfloat16 的噪声(如矩阵-向量乘积),哪些部分需要双精度不折不扣的精确性(如确保基向量保持正交的步骤)。
这将我们带到了一个激动人心的融合点:机器学习与传统科学模拟的结合。科学家们现在正在使用物理信息神经网络 (PINN) 来求解复杂的微分方程,例如控制流体流动的纳维-斯托克斯方程。PINN 通过不仅试图满足实验数据,而且试图满足方程本身的物理定律来学习解。为了训练这些用于复杂 3D 问题的庞大模型,我们需要动用我们所有的技巧。我们使用 bfloat16 来实现网络中闪电般快速的前向和后向传播。但是,当我们计算损失函数——它衡量网络遵守物理定律的程度——我们必须格外小心。这些计算通常涉及相减两个几乎相等的大数,这在低精度下是灾难性的,被称为灾难性抵消。因此,基于物理的残差必须在更高精度下计算和累积。这种技术的优雅融合使我们能够利用人工智能硬件的力量来解决以前无法企及的科学问题,这完美地证明了计算原理的统一力量。
我们与 bfloat16 的旅程,带领我们从单个晶体管的物理学走向行星级超级计算机的架构;从神经网络损失函数地形的抽象世界走向流体动力学的具体模拟。我们发现一个反复出现的主题:在每一步都追求绝对精度并不总是最明智的道路。通过理解我们问题和算法的结构,我们可以在关键之处应用精度,并在能够加速我们进程的地方拥抱“足够好”。bfloat16 格式不仅仅是一个巧妙的工程技巧;它是这种更深层次计算智慧的体现。它告诉我们,通过智能地放弃完美,我们可以实现以前不可能完成的事情。