假设你要搬家:一辆大货车(FP32)能装下所有家具,但转弯慢、油耗高;一辆面包车(FP16)轻快一些;而快递小哥的电动小三轮(FP8)速度最快、最省电——可惜货箱只有一点点大。把 AI 模型的训练想象成一场持续数周的大搬家:几十亿个参数不停地在显存和计算核心之间往返。谁的车跑得快、装得巧,谁就能更早把模型练出来。FP8 就是这个思路的极致:只用 8 个二进制位表示一个数字——相当于把每件货物压缩成一个小包袱。可包袱变小了,怎么保证东西不丢、不碎?这就需要聪明的打包术:E4M3 是精装小盒,尾数写得细,适合装权重;E5M2 是超大标签包,能标到很大的数字,适合装梯度。梯度缩放像汇率换算,把不起眼的零钱放大成整票再装箱,防止它们在搬运中掉进下水道。NVIDIA 的 Hopper 架构为这些包袱专门修了高速传送带(Tensor Core)。这篇指南会带你从打包原理走到实际部署路线,看看怎样用 8 位精度把大模型训练提速到接近翻倍。

岗位关联

学会 FP8 训练的核心原理与落地手法,你就能在不牺牲精度的前提下把大模型训练吞吐提升 40%~90%——这是大模型训练工程师必须掌握的硬核提速技能。

目录
  1. 可视化演示
  2. 低精度初心:为什么要用8位
  3. E4M3格式:精装小盒
  4. E5M2格式:超大标签包
  5. 训练稳定性:保险柜里的主权重
  6. 梯度缩放:把零钱放大成整票
  7. 对比FP16:面包车和小电驴
  8. Hopper支撑:高速传送带
  9. 部署实践:上路前的三件事
  10. AI 应用场景
  11. Python 演示
  12. 常见误区
  13. 面试题

可视化演示

演示FP8的E4M3/E5M2位格式分布、梯度缩放防下溢机制、FP8与FP16/BF16的吞吐量与显存对比,交互切换模式与缩放因子。

本节配有交互动效(进入视口后自动加载,约 3KB)。

FP8 E4M3与E5M2格式 E4M3: 1+4+3位 精度优先 E5M2: 1+5+2位 范围优先 E4M3: ±448, E5M2: ±57344 FP8将显存与算力需求减半

低精度初心:为什么要用8位

搬家公司的车队里,FP32 是最大的货车,一次能装 32 个箱子;FP16 面包车只有一半容量;FP8 小电驴只有 8 个格子。大模型训练的耗时,有很大一部分花在“搬箱子”上——把权重和中间结果在显存与计算核心之间搬来搬去。既然搬得快的车更省油更快,为什么不干脆用小电驴呢?因为箱子太小,东西可能装不下。可反过来想:如果货物本身不怕轻微磕碰,小电驴的优势就非常诱人。

深度学习的梯度天生“皮实”。它只是一堆统计性的修正信号,即使每个数字都带一点噪声,只要方向大致正确,参数更新依然能收敛。这种对误差的容忍,正是低精度训练能成立的根本原因。于是工程师们开始琢磨:把数字从 16 位压到 8 位,计算核心一个时钟周期能处理的格子数接近翻倍,显存搬运量也减半——吞吐自然水涨船高。

位宽减半带来的收益,可以用一个简单的比值刻画:

当然,理想上限要打折扣,因为还得给精度留后路。但方向已经明确:小电驴能跑多快,取决于我们把货物打包得多聪明。接下来就是两种最流行的打包方案——E4M3 和 E5M2。

S=T16T8≈W16W8=2\displaystyle S=\frac{T_{16}}{T_{8}}\approx \frac{W_{16}}{W_{8}}=2
这个式子在说:FP16 的搬运宽度是 FP8 的两倍,所以理想情况下 FP8 的训练速度接近 FP16 的两倍。
  1. T16 与 T8分别表示用 FP16 和 FP8 训练同一模型所需的时间;在算力不受限时,位宽减半意味着同样时间能处理约一倍的数据量。
    T16≈2T8\displaystyle T_{16}\approx 2T_{8}
  2. W16/W8W 表示单个数字占用的位宽之比,16 位压到 8 位后比值恰好为 2,这是吞吐翻倍的理想上限。
    W16W8=168=2\displaystyle \frac{W_{16}}{W_{8}}=\frac{16}{8}=2

E4M3格式:精装小盒

想象你要往小电驴上装一批高档瓷器。箱子很小,但你可以把每个盒子的标签写得很细:盒子上的数字能精确到小数点后三位。代价是标签能容纳的“量级范围”有限,太大的数字写不下。E4M3 就是这种“精装小盒”——它的 8 个格子里,1 格写符号(正负),4 格写指数(决定量级),3 格写尾数(决定精度)。

E4M3 的意思是:E 代表指数占 4 位,M 代表尾数占 3 位。指数用偏移量(bias)为 7 的方式编码,这样既能表示很大的数,也能表示很小的数。尾数有 3 位,相当于有 8 种细分刻度,精度比只有 2 位尾数的格式高出一倍。它最大的数字是 448,再大的数就只能“爆表”了。

一个 E4M3 数字具体等于多少,由下面公式决定:

E4M3 精度好但范围窄,最适合装“确定且稳定”的货物——模型权重和激活值。它们通常分布在一个集中的区间里,用精装小盒正好。

x=(−1)s×2E−7×(1+M8)\displaystyle x=(-1)^{s}\times 2^{E-7}\times \left(1+\frac{M}{8}\right)
符号位定正负,指数位定大小尺度,尾数位定精细程度——三者相乘就是一个 E4M3 数的真实值。
  1. s 符号位1 位符号,s=0 表示正数,s=1 表示负数,像货物上的“易碎”箭头一样标记方向。
    (−1)s∈{1, −1}\displaystyle (-1)^{s}\in\{1,\,-1\}
  2. E 指数位4 位指数配合偏移量 7,决定数字落在哪个量级,范围大约从极其小的分数到几百的量级。
    E−7∈[−6, 8]\displaystyle E-7\in[-6,\,8]
  3. M 尾数位3 位尾数只能表示 0 到 7,除以 8 后得到 0 到 0.875 的分数,分数越细,精度越高。
    M8∈{0, 18, 28, …, 78}\displaystyle \frac{M}{8}\in\left\{0,\,\frac{1}{8},\,\frac{2}{8},\,\dots,\,\frac{7}{8}\right\}

E5M2格式:超大标签包

另一类货物是“容易飘”的:梯度的数值可以忽大忽小,有时小到尘埃,有时大到惊人。对这种货物,精装小盒就不够了。于是快递站准备了另一种包:标签能写到的数字范围大得多,但标签上的字写得比较粗——只有 2 位尾数。这就是 E5M2:指数占 5 位,尾数占 2 位。

E5M2 的指数偏移量为 15,能表示的最大值高达 57344,最小能表示到 2 的负 16 次方左右,动态范围比 E4M3 宽了上百倍。代价是尾数只有 4 种细分刻度,精度大幅下降。它就像一把“大秤”:能称很重或很轻的物体,但称出来的数值比较粗略。

E5M2 的取值公式与 E4M3 如出一辙,只是参数变了:

E5M2 的大范围很适合装梯度:反向传播中梯度跨越多个数量级,用宽范围格式能显著减少小梯度“掉进下水道”变成 0 的情况。大秤称重虽粗,但至少称得出来。

x=(−1)s×2E−15×(1+M4)\displaystyle x=(-1)^{s}\times 2^{E-15}\times \left(1+\frac{M}{4}\right)
指数位数多了、偏移量大了,可表示的范围更宽;尾数位数少了,精度自然更粗。
  1. E 指数位扩展到 5 位偏移量变成 15,指数取值范围更宽,能表示从极小到极大的数量级。
    E−15∈[−14, 15]\displaystyle E-15\in[-14,\,15]
  2. M 尾数位收缩到 2 位分数只能取 0、0.25、0.5、0.75 四档,精度只有 E4M3 的一半。
    M4∈{0, 14, 12, 34}\displaystyle \frac{M}{4}\in\left\{0,\,\frac{1}{4},\,\frac{1}{2},\,\frac{3}{4}\right\}
  3. 范围与精度互换位数总量不变,指数多占一位,尾数就少一位——这是 8 位格式里最核心的“取舍平衡”。
    1+5+2=1+4+3=8\displaystyle 1+5+2=1+4+3=8

训练稳定性:保险柜里的主权重

把小电驴开出城之前,快递公司会先做一件事:贵重物品单独放保险柜。FP8 训练也是同样的逻辑——模型真正的“账本”(主权重)绝不能轻易交给低精度格式,否则量化误差会像滚雪球一样,一步错、步步错。

在 FP8 训练中,权重通常以 FP32(必要时 FP16)格式保存在显存里,这就像保险柜里的原版账本。前向传播时,把账本“誊抄”成 FP8 副本用于计算;反向传播得到梯度后,也用低精度格式进行传递;但最终更新账本时,用的是高精度的主权重加低精度的梯度修正量。这样,即使每次修正都有噪声,账本始终干净。

更新主权重的过程可以写成:

实践中的分工通常是:前向传播用 E4M3 处理权重和激活,反向传播用 E5M2 传递梯度与误差信号——精装盒给权重,大标签包给梯度。再配合对损失曲线和梯度范数的监控,训练就能稳稳地跑下去。

wFP32←wFP32−η gFP8\displaystyle w_{\text{FP32}}\leftarrow w_{\text{FP32}}-\eta\, g_{\text{FP8}}
低精度梯度只负责提供“修正方向”,真实的账本永远记在高精度格式里,误差不会无限累积。
  1. w_FP32 主权重训练全程维护一份 FP32 的权威权重,低精度副本每次使用后作废,不影响账本。
    wFP32∈高精度空间\displaystyle w_{\text{FP32}}\in \text{高精度空间}
  2. g_FP8 低精度梯度反向传播算出的梯度先量化成 FP8 再用于更新,相当于用粗糙但便宜的方向指引修正。
    gFP8=FP8⁡(g)\displaystyle g_{\text{FP8}}=\operatorname{FP8}(g)
  3. η 学习率与减法每次只沿梯度方向前进一小步,噪声被学习率天然平滑掉,账本越记越准。
    Δw=η gFP8\displaystyle \Delta w=\eta\, g_{\text{FP8}}

梯度缩放:把零钱放大成整票

小电驴货箱太小,细碎的零钱(特别小的梯度)很容易在搬运中掉进下水道——量化成 FP8 后直接变成 0,相当于梯度消失了。快递员的做法是:先把零钱放进放大镜底下,放大成整票再装箱;到了目的地,再按同样的倍数还原回去。这就是梯度缩放(gradient scaling)。

具体操作是:反向传播前先把损失值乘上一个缩放因子 s,这样算出来的梯度天然被放大了 s 倍;梯度量化成 FP8 后,在更新权重之前再除以 s,还原成真实的梯度大小。由于缩放是在“装箱前”统一完成的,几乎不增加任何计算成本。

完整流程用一个式子串起来:

但缩放因子不能盲目放大:如果放得太大,梯度又会“爆表”变成无穷大。所以训练框架会动态调节 s——一旦发现上溢就减半,连续很多步安全就翻倍。这就像一个会自己校准的放大镜,永远把零钱调到最合适的票面。

gs=g×s,gFP8=FP8⁡(gs),g^=gFP8s\displaystyle g_{s}=g\times s,\qquad g_{\text{FP8}}=\operatorname{FP8}\left(g_{s}\right),\qquad \hat{g}=\frac{g_{\text{FP8}}}{s}
乘 s 把小的梯度变“胖”,量化完再除 s 恢复原样——小梯度因此不再被 8 位格式无情抹掉。
  1. g × s 放大把所有梯度同乘一个大于 1 的缩放因子,让原本低于 FP8 最小分辨率的数值变得可表示。
    gs=g×s\displaystyle g_{s}=g\times s
  2. FP8 量化放大后的梯度进入 8 位格式,有效位数被充分利用。
    gFP8=FP8⁡(gs)\displaystyle g_{\text{FP8}}=\operatorname{FP8}(g_{s})
  3. 除以 s 还原更新权重时除回 s,让学习率语义不变,不会因为放大而让训练变猛。
    g^=gFP8/s\displaystyle \hat{g}=g_{\text{FP8}}/s

对比FP16:面包车和小电驴

FP16 是上一代的“明星车型”,已经相当轻快。FP8 和它相比,相当于把面包车换成了小电驴:轮胎(位宽)少了一半,但同样一趟能拉货,只是每件货都得精心打包。要判断值不值得换,得从三笔账算起:显存、吞吐、精度。

显存账最直观:同样的模型,8 位参数占用只有 16 位的一半。省下的显存可以换成更大的 batch size,训练更稳定、吞吐更高。吞吐账也很漂亮:在支持 FP8 的硬件上,计算核心处理 8 位数据的速率通常是 16 位的接近两倍。唯一要小心的是精度账——FP8 动态范围窄、尾数少,必须配合主权重和梯度缩放才能不掉点。

显存账可以直接算:

FP16 的优势是“不用操心”:精度高、范围宽,开箱即用。而 FP8 像一台需要熟练车手的小电驴——打包得当,速度翻倍;打包马虎,货物全洒。这也是为什么实际训练中很少“全程 FP8”,而是让框架智能地在各层之间切换精度。

MFP8=816MFP16=MFP162\displaystyle M_{\text{FP8}}=\frac{8}{16}M_{\text{FP16}}=\frac{M_{\text{FP16}}}{2}
同样的模型,FP8 的显存占用恰好是 FP16 的一半,这是“省下来就是赚到”的硬收益。
  1. 比例关系单个参数从 16 位降到 8 位,内存消耗按比例缩小,比值就是 8/16。
    816=12\displaystyle \frac{8}{16}=\frac{1}{2}
  2. 显存减半的连锁反应省出的显存能容纳更大 batch 或更长序列,间接提升训练吞吐与稳定性。

Hopper支撑:高速传送带

再好的小电驴,如果小区里没有给它修的专用车道,也快不起来。NVIDIA 的 Hopper 架构(如 H100)就是为 FP8 专门修了高速车道:新一代 Tensor Core 原生支持 FP8 矩阵乘加,算力直接翻倍。从 Ampere 到 Hopper,Tensor Core 第一次把 8 位精度纳入正式会员。

在 Hopper 上,FP8 的矩阵乘遵循“低精度算乘法、高精度做累加”的原则:两个 8 位矩阵相乘,结果累积到 FP32 累加器中。这就像传送带上每个包裹都用小标签快速分拣,但最终的总账本仍用高精度登记,防止误差滚大。

矩阵乘加运算可以写成:

借助 Transformer Engine 等框架组件,Hopper 能自动判断每一层该用 E4M3 还是 E5M2,甚至回退到 FP16。模型越大、矩阵乘越多,FP8 传送带带来的收益就越明显——这也是大模型训练工程师最看重的部分。

Dij=Dij+∑k=1KAikFP8⋅BkjFP8\displaystyle D_{ij}=D_{ij}+\sum_{k=1}^{K}A_{ik}^{\text{FP8}}\cdot B_{kj}^{\text{FP8}}
乘数用 8 位快速算,累加在 FP32 中精确记账——快而不糙。
  1. A 与 B 用 FP8两个输入矩阵都切成 8 位小块,交给 Tensor Core 并行处理。
    AikFP8,  BkjFP8\displaystyle A_{ik}^{\text{FP8}},\; B_{kj}^{\text{FP8}}
  2. 乘积累加到 D每一对乘积立即加进 FP32 的累加变量 Dij,避免中间误差累积。
    Dij←Dij+AikBkj\displaystyle D_{ij}\gets D_{ij}+A_{ik}B_{kj}

部署实践:上路前的三件事

理论都懂了,小电驴也领到了,怎么真正上路?实际部署 FP8 训练通常分三步走。第一步,用成熟框架接入:PyTorch 里配合 Transformer Engine 或 vLLM 的 FP8 后端,只需几行配置就能让关键层跑在 FP8 上。第二步,小规模验证:先拿小模型或几步训练做诊断,重点看损失曲线是否异常、有没有 NaN 冒出来。第三步,全量开启并紧盯缩放因子。

缩放因子的动态调节是上路后的“方向盘”:检测到上溢或非数值(NaN/Inf)就立刻缩小,连续多步安全就适度放大,让梯度始终落在 FP8 的舒适区。这个规则常写成:

跑通之后,收益是实打实的:在 H100 上训练大模型,FP8 相比 FP16 通常能带来 1.4~1.9 倍的吞吐提升,显存占用同步减半。小电驴虽小,只要打包术到位,它就能驮着整个大模型一路狂奔。

s←{s/2,出现上溢或 NaNs×2,连续 N 步无上溢\displaystyle s\gets \begin{cases}s/2, & \text{出现上溢或 NaN}\\[2mm] s\times 2, & \text{连续 } N \text{ 步无上溢}\end{cases}
缩放因子像空调温控:过热就调小,稳定就适当加大,让梯度信号始终处于最佳工作区间。
  1. 上溢分支一旦发现缩放后的梯度或损失出现 Inf/NaN,立即把 s 减半,相当于紧急降温。
    s←s/2\displaystyle s\gets s/2
  2. 稳定分支连续 N 步(典型值 2000 步左右)安全运行后,把 s 翻倍,提升小梯度的灵敏度。
    s←s×2\displaystyle s\gets s\times 2
  3. N 的意义N 越大越保守,越小越激进;实际部署常从 2000 起步,再根据损失曲线微调。

AI 应用场景

大语言模型预训练

阶段:训练阶段(前向传播 + 反向传播 + 参数更新)

解决的问题:千亿参数模型的预训练动辄几周甚至几个月,FP16 训练在 H100 上算力利用率还不够高。传统做法是“硬等”,用更多 GPU 堆时间。改用 FP8 后,Tensor Core 算力翻倍、显存减半,同样的集群能塞进更大的 batch,预训练周期显著缩短。

具体案例:某团队用 512 张 H100 训练 70B 参数模型,把 Transformer Engine 的 FP8 自动混合精度打开后,配合主权重 FP32 和动态梯度缩放,训练吞吐从 4200 tokens/s 提升到 7600 tokens/s,损失曲线与 FP16 基线几乎重合。

指令微调与持续训练

阶段:微调阶段(LoRA / 全参微调)

解决的问题:微调时显存往往被权重、优化器状态和激活值挤满,batch 开不大,收敛慢。FP8 让权重和激活以 8 位驻留显存,省出的空间能容纳更长上下文和更大 batch。

具体案例:在 7B 模型上做指令微调,开启 FP8 后单卡显存占用从 62GB 降到 38GB,batch size 从 8 提到 16,训练时间缩短约 40%,评测指标基本持平。

训练平台容量规划

阶段:MLOps / 资源调度阶段

解决的问题:训练平台的 GPU 资源有限,如何在同样预算下服务更多实验?FP8 的低显存和高吞吐特性,让平台可以在单卡上并排跑更多小实验,或在同样吞吐约束下降低对 GPU 型号的要求。

具体案例:某公司的训练平台把 FP8 设为新实验的默认精度,GPU 日均利用率从 61% 提升到 78%,排队等待时间减半,实验迭代速度明显加快。

Python 演示

python73 行
# -*- coding: utf-8 -*-"""FP8 训练模拟:E4M3/E5M2 量化与梯度缩放对比"""import mathimport random  def fp8_quantize(x, ebits, mbits):    """把浮点数模拟量化为 FP8(E#ebits M#mbits) 并返回数值"""    if x == 0.0:        return 0.0    sign = -1.0 if x < 0 else 1.0    a = abs(x)    bias = 2 ** (ebits - 1) - 1    emin = -bias + 1    emax = 2 ** ebits - 2 - bias    min_sub = 2.0 ** (emin - mbits)    if a < min_sub:        return 0.0    e = max(min(math.floor(math.log2(a)), emax), emin)    unit = 2.0 ** (e - mbits)    mant = int(round(a / unit) - (2 ** mbits))    mant = max(0, min(mant, 2 ** mbits - 1))    return sign * (1.0 + mant / (2 ** mbits)) * (2.0 ** e)  def fp8_max(ebits, mbits):    bias = 2 ** (ebits - 1) - 1    emax = 2 ** ebits - 2 - bias    return (2.0 - 2.0 ** (-mbits)) * (2.0 ** emax)  def run_trial(use_scale, ebits, mbits, steps=400, seed=42):    random.seed(seed)    w = 1.0    scale = 64.0    err_sum = 0.0    zero_grads = 0    for i in range(steps):        g = 0.002 * math.sin(i * 0.08) + 0.0003 * random.gauss(0.0, 1.0)        if use_scale:            gs = g * scale            if abs(gs) > fp8_max(ebits, mbits):                scale = max(scale / 2.0, 1.0)                gs = g * scale            gq = fp8_quantize(gs, ebits, mbits) / scale            if g != 0.0 and gq == 0.0:                scale = min(scale * 2.0, 4096.0)        else:            gq = fp8_quantize(g, ebits, mbits)        if gq == 0.0:            zero_grads += 1        w -= 0.01 * gq        err_sum += abs(g - gq)    return err_sum / steps, zero_grads, w  def main():    print("FP8 数值范围一览:")    print("  E4M3: max=%.1f 最小正常数=%.6f 最小次正规=%.6f"          % (fp8_max(4, 3), 2.0 ** -6, 2.0 ** -9))    print("  E5M2: max=%.1f 最小正常数=%.6f 最小次正规=%.6f"          % (fp8_max(5, 2), 2.0 ** -14, 2.0 ** -16))    for name, ebits, mbits in [("E4M3", 4, 3), ("E5M2", 5, 2)]:        err_n, zero_n, w_n = run_trial(False, ebits, mbits)        err_s, zero_s, w_s = run_trial(True, ebits, mbits)        print("[%s] 直接FP8:    平均误差=%.6f 零梯度=%d 权重=%.4f"              % (name, err_n, zero_n, w_n))        print("[%s] 梯度缩放:  平均误差=%.6f 零梯度=%d 权重=%.4f"              % (name, err_s, zero_s, w_s))  if __name__ == "__main__":    main()
目的:用纯 Python 模拟 FP8 量化与训练更新,直观对比“直接低精度”与“梯度缩放”对误差和下溢的影响。
  1. fp8_quantize(x, ebits, mbits)把任意浮点数模拟量化成 8 位格式,先算指数、再舍入尾数,是整段代码的“打包机”。
  2. min_sub = 2.0 ** (emin - mbits)算出最小次正规数,比它还小的数值直接归零,这就是“掉进下水道”的下溢。
  3. e = max(min(math.floor(math.log2(a)), emax), emin)指数钳制到合法区间,防止超出 E4M3/E5M2 的表达范围。
  4. mant = max(0, min(mant, 2 ** mbits - 1))尾数舍入后再钳制,保证 8 位格式不会出现越界编码。
  5. if abs(gs) > fp8_max(ebits, mbits)缩放后如果爆表,就减小缩放因子,模拟训练中的“上溢保护”。
  6. if g != 0.0 and gq == 0.0:缩放后梯度仍被量化为 0,说明放大倍数不够,动态把 scale 翻倍。
  7. w -= 0.01 * gq用 FP8 梯度更新权重,模拟真实训练中的参数修正环节。

常见误区

误区8 位精度这么低,训练出来的模型肯定比 FP16 差一大截,指标一定会掉。

正解不掉点的关键在于“哪里用低精度、哪里用高精度”。只要主权重用 FP32 保存、梯度配合缩放、关键层回退 FP16,FP8 训练在多数大模型任务上能达到与 FP16 几乎一致的收敛效果。小电驴装得巧,不比面包车差。

为何常见:人们习惯把“位宽”等同于“质量”,忽略了深度学习对噪声的天然鲁棒性,也忽略了主权重与缩放这两道保险。

误区E5M2 的指数占 5 位,数字更大,肯定比 E4M3 更“高级”、精度更高。

正解E4M3 的尾数有 3 位,E5M2 只有 2 位,精度反而更低。E5M2 的优势是动态范围更宽,适合梯度;E4M3 的优势是精度更细,适合权重和激活。两者是“范围”与“精度”的取舍,不是谁取代谁。

为何常见:人们容易把“更大的指数位”误解为“更强的规格”,忽略了尾数才是决定精度的关键。

误区给损失乘一个 1024,训练就完事了,不用再管它。

正解缩放因子必须动态调节:出现上溢(Inf/NaN)要立即减半,连续几千步安全再翻倍。固定常数要么导致小梯度仍被抹掉,要么导致大梯度爆表,训练直接发散。

为何常见:从 FP16 混合精度时代带来的习惯性简化——FP16 的动态范围宽,固定缩放勉强够用;到了 FP8 的窄范围里,固定值就不灵了。

误区硬件支持 FP8,那就把所有算子和层全部切到 FP8,一步到位。

正解归一化层、Softmax、Embedding 等对精度敏感的算子通常仍需 FP16/FP32;前向的权重激活建议 E4M3,反向的梯度建议 E5M2。框架的自动混合精度(如 Transformer Engine)正是为了按层智能分配格式。

为何常见:看到“全局开关”就以为可以一键全量,忽略了不同数值的动态范围差异巨大这一事实。

误区代码里加一行 `fp8=True`,训练速度就会自动翻倍。

正解FP8 的收益依赖 Tensor Core 利用率、数据布局、层间切换开销和缩放回退逻辑。如果 kernel 没有对齐、batch 太小或频繁回退 FP16,提速可能微乎其微。需要先用 profiler 定位瓶颈,再针对性开启。

为何常见:把硬件峰值算力当成实际收益,忽略了访存、调度和框架开销这些“路上的红灯”。

面试题

Q1. FP8 一共占用多少位存储空间?

单选题 · easy

  • A.4 位存储空间
  • B.8 位存储空间← 正确答案
  • C.16 位存储空间
  • D.32 位存储空间

答案:B。FP8 顾名思义只有 8 位,相当于小电驴货箱里的 8 个格子。它由符号位、指数位和尾数位三种“货物”共同拼满这 8 位空间,因此总位宽是 8,而非 4/16/32。

Q2. E4M3 中的 “4“ 和 ”3” 分别代表什么?

单选题 · easy

  • A.指数 4 位尾数 3 位← 正确答案
  • B.尾数 4 位指数 3 位
  • C.符号 4 位指数 3 位
  • D.指数 4 位符号 3 位

答案:A。命名规则是“E 指数 + M 尾数”,E4M3 表示指数占 4 位、尾数占 3 位,符号位固定 1 位,共 8 位。就像精装小盒的标签:4 格写量级、3 格写精度。

Q3. E4M3 与 E5M2 相比,下列说法正确的是?

单选题 · mid

  • A.E4M3 的动态范围更大
  • B.E5M2 的尾数精度更高
  • C.E4M3 的精度更高范围更小← 正确答案
  • D.E5M2 的精度更高范围更小

答案:C。E4M3 尾数 3 位精度更细,但范围最大只有 448;E5M2 指数 5 位范围大,但尾数 2 位精度粗。两者是“范围与精度”的取舍,C 准确描述了这个关系。

Q4. 梯度缩放主要用于解决什么问题?

单选题 · mid

  • A.权重参数发生上溢
  • B.小梯度发生下溢归零← 正确答案
  • C.激活值发生上溢
  • D.学习率设置过大

答案:B。FP8 的范围窄,很小的梯度量化后直接变成 0,就像零钱掉进下水道。梯度缩放先把梯度乘大再量化,就是让小梯度能被 8 位格式表达出来。

Q5. 训练中检测到缩放后的数值出现上溢,应该怎么做?

单选题 · mid

  • A.立即增大缩放因子
  • B.立即减小缩放因子← 正确答案
  • C.停止训练并重启
  • D.切换成 INT8 格式

答案:B。上溢说明放大过头了,数字爆表变成了 Inf/NaN。正确的做法像空调温控:过热就调小,把缩放因子减半,让数值回到 FP8 的舒适区。

Q6. FP8 训练中“主权重”通常保存在什么精度?

单选题 · hard

  • A.与梯度相同的 FP8 精度
  • B.更高精度的 FP16 或 FP32← 正确答案
  • C.更低精度的 INT8 整数
  • D.与激活相同的 E5M2 精度

答案:B。主权重是保险柜里的原版账本,必须用 FP32(或 FP16)保存,防止量化误差像滚雪球一样累积。FP8 只负责计算副本,不承载权威账本。

Q7. 哪一代 NVIDIA 架构原生支持 FP8 Tensor Core?

单选题 · easy

  • A.Volta 架构
  • B.Turing 架构
  • C.Ampere 架构
  • D.Hopper 架构← 正确答案

答案:D。Hopper(如 H100)是首个原生支持 FP8 的架构,相当于专门给小电驴修了高速车道。Ampere 及更早架构需要额外模拟或转换,无法发挥原生算力。

Q8. FP8 相比 FP16 的优势有哪些?

多选题 · mid

  • A.训练吞吐量更高← 正确答案
  • B.显存占用更小← 正确答案
  • C.数值范围更大
  • D.表达精度更高

答案:AB。FP8 位宽减半,计算吞吐接近翻倍、显存占用减半,这是 A 和 B。但它的数值范围和表达精度都比 FP16 差,所以 C、D 说法反了,需要缩放和主权重来补偿。

Q9. 以下哪些策略有助于提升 FP8 训练的稳定性?

多选题 · extreme

  • A.使用损失缩放技术← 正确答案
  • B.维护 FP32 主权重← 正确答案
  • C.混合使用多种格式← 正确答案
  • D.全程关闭缩放机制

答案:ABC。损失缩放防止小梯度下溢,FP32 主权重防止误差累积,混合格式(E4M3/E5M2/FP16 按层分配)兼顾范围与精度,三者都是稳定训练的关键。D 关闭缩放会让小梯度大量归零,训练发散。

Q10. 关于 E5M2 格式的说法,正确的有哪些?

多选题 · hard

  • A.动态范围比 E4M3 更宽← 正确答案
  • B.尾数只有 2 位精度较粗← 正确答案
  • C.适合表示梯度数据← 正确答案
  • D.它的最大值为 448

答案:ABC。E5M2 指数 5 位,最大可达 57344,范围比 E4M3(448)宽很多,A 对、D 错;尾数 2 位精度粗,B 对;宽范围适合数量级飘忽的梯度,C 对。