文章

LLM FP8 微调上线生产实战:用 Amax Scaling、层级白名单与 BF16 回退守住收敛

FP8 能降低大模型微调的算力与显存压力,但缩放策略、异常值和分布式同步处理不当会导致静默精度退化。本文给出 Amax 监控、层级白名单、BF16 回退、双轨评测与发布门禁的完整工程方案。

为什么 FP8 微调不能只看”训练跑起来了”

FP8 常被描述成一次简单的精度开关——把 BF16 换成 FP8,就能获得更高吞吐和更低显存占用。这个理解忽略了训练与推理量化之间最关键的差别。

推理权重量化主要关心静态权重压缩后是否还能保持输出质量;而训练中的激活、权重和梯度分布会持续变化,缩放因子必须跟随数据和优化过程动态更新。一次异常 Batch、少数离群值、不同 Rank 的尺度不一致,或者某个小矩阵的转换开销,都可能让 FP8 的收益消失,甚至产生不容易被总体 Loss 发现的静默退化

因此,FP8 微调上线的真正目标不是”所有层都启用 FP8”,而是建立一套可审计的低精度训练策略:

  • 哪些算子进入 FP8,哪些保持 BF16
  • E4M3、E5M2 和不同 Scaling Recipe 如何选择
  • Amax 和 Scale 是否在合理范围内
  • 分布式 Rank 是否使用一致尺度
  • 发生饱和、下溢或能力退化时如何局部回退
  • FP8 制品是否通过与 BF16 基线一致的发布门禁

核心原理:FP8 的关键不是 8 位,而是动态范围管理

E4M3 与 E5M2 承担不同职责

FP8 常见的两种格式是 E4M3E5M2

格式指数位尾数位特点典型用途
E4M343精度更高,动态范围较小前向权重与激活
E5M252动态范围更大,精度较低反向梯度

在典型混合配方中,NVIDIA Transformer Engine 将 E4M3 + E5M2 组合定义为 Format.HYBRID。但这并不意味着所有张量都应强制使用同一格式——Embedding、Normalization、Loss、LM Head、极小 Linear 和数值敏感模块通常需要单独验证,必要时保持 BF16。

Scale 决定数值是否进入可表示区间

高精度张量转换成 FP8 前,需要根据张量幅值计算缩放因子。Transformer Engine 的 Delayed Scaling 默认形式可概括为:

scale = (FP8_MAX / amax) / (2 ^ margin)

其中:

  • amax:张量绝对值最大值
  • FP8_MAX:目标格式可表示的最大值
  • margin:为异常波动预留安全空间

Scale 太大,离群值容易饱和;Scale 太小,大量小值会被压到零附近。生产治理需要关注的是缩放质量,而不只是 Loss 曲线。

Delayed Scaling 用历史换取稳定性

Delayed Scaling 不直接使用当前迭代计算出的 Scale,而是记录一段 Amax History,根据历史窗口选出代表值,再供下一次迭代使用。

常见策略对比:

策略行为优势风险
max使用窗口内最大 Amax降低突发溢出风险普通 Batch 有效精度下降
most_recent使用最近一次 Amax响应更快对离群 Batch 更敏感
自定义(分位数/截尾)按需设计灵活适配必须有充足回放验证

Amax History 长度不是越长越好。窗口过长会让一次历史峰值长期压低后续张量的有效精度;窗口过短则容易随 Batch 抖动。应依据数据分布、梯度累积、序列长度和训练阶段做实验。

分布式训练必须治理 Scale 一致性

FSDP、Tensor Parallel 或其他分布式训练会把权重和激活切分到多个 Rank。若各 Rank 独立维护 Amax 和 Scale,相同逻辑张量可能使用不同量化尺度,影响单卡与多卡语义一致性。

Transformer Engine 默认可对 Amax 做分布式归约,TorchTitan 的 Float8 文档也强调在 FSDP 和 TP 场景中需要通信全局 max(abs)。若为了性能关闭 Amax 归约,就必须把本地 Scale 视为训练状态的一部分:所有 Rank 都要进入检查点,恢复时也要保持正确映射。

工程落地:从全局开关改成逐层准入

第一步:建立 BF16 可复现基线

在启用 FP8 前,先冻结一条 BF16 基线:

  • 模型和 Tokenizer 指纹
  • 数据集版本、采样顺序和 Chat Template
  • Optimizer、Scheduler、Gradient Clipping 和随机种子
  • 每步 Token 数、梯度累积和有效 Batch Size
  • Loss、梯度范数、吞吐、显存和关键能力集结果
  • 固定步骤的 Checkpoint 与生成样本

没有可复现的 BF16 基线,FP8 的性能收益和质量变化就无法归因。

第二步:只转换收益明确的矩阵层

推荐从 Transformer 中尺寸较大的 Linear 层开始,逐步扩展:

  1. Attention 的 Q/K/V 和输出投影
  2. MLP 的上投影、门控投影和下投影
  3. 经过实测后再扩展到其他模块

初始阶段的 BF16 保留清单应包括:

  • Embedding 与 LM Head
  • RMSNorm、LayerNorm 和最终 Loss
  • 尺寸过小、转换开销高于 Tensor Core 收益的 Linear
  • 历史上 Amax 波动显著或易出现梯度异常的层
  • 自定义算子及尚未验证的融合 Kernel

层级白名单应记录模块 FQN、Shape、Recipe 和回退原因,作为训练制品的一部分。

第三步:固定 Recipe,禁止运行中静默漂移

生产配置中应把以下字段写入不可变 Manifest:

precision_policy:
  base_dtype: bf16
  fp8_format: hybrid
  scaling: delayed
  amax_history_len: 16
  amax_compute_algo: max
  margin: 0
  reduce_amax: true
  include_modules:
    - "*.self_attn.q_proj"
    - "*.self_attn.k_proj"
    - "*.self_attn.v_proj"
    - "*.mlp.*_proj"
  exclude_modules:
    - "embed_tokens"
    - "lm_head"
    - "*.norm"

升级 Transformer Engine、PyTorch、CUDA、cuDNN、GPU 型号或并行拓扑时,必须重新跑数值与吞吐回放。不要把 FP8 Recipe 当成与运行环境无关的普通超参数。

第四步:建立 Amax 与低精度异常画像

每个被转换模块至少应输出以下分层指标:

指标类别具体指标
缩放状态Amax 当前值及历史分位数、Scale 与步间变化率
量化质量接近 FP8 上限的值占比、量化后零值比例
梯度健康输入/权重/梯度范数、NaN/Inf/Overflow/跳步次数
精度对比FP8 与 BF16 抽样输出的相对误差
Kernel 效率每层 FP8 Kernel 命中率与回退率

建议使用三个等级的告警:

等级触发条件动作
观察Scale 抖动、零值比例上升,但 Loss 和能力集正常记录并持续监控
降级连续多窗口饱和、异常梯度或 FP8/BF16 差分扩大局部切换 BF16
停止NaN、不可恢复发散、多 Rank 不一致或关键能力跌破硬门槛暂停训练并排查

第五步:实现按层 BF16 回退,而不是全局二选一

精度策略应允许不同层使用不同 Recipe。Transformer Engine 支持在同一训练中嵌套多个 autocast 区域。工程上可以维护一份动态但可审计的回退表:

precision_overrides:
  "model.layers.17.mlp.down_proj":
    dtype: bf16
    reason: "persistent saturation"
    activated_at_step: 18240
  "model.layers.29.self_attn.o_proj":
    dtype: bf16
    reason: "capability regression isolation"
    activated_at_step: 20100

回退动作必须同时记录:触发指标、阈值、步骤、数据版本、前后 Checkpoint 和恢复结论。局部 BF16 回退后,应观察吞吐损失是否可接受,并重新计算整体训练 ETA。

第六步:用双轨评测判断”收敛是否等价”

FP8 不能只与自己的上一版本比较,至少需要三条轨道:

  1. BF16 控制组:同数据、同 Token 数和相同优化参数
  2. FP8 候选组:目标 Recipe 和层级白名单
  3. FP8 回退组:对高风险层保持 BF16,用于定位退化来源

对比维度:

  • 相同步数与相同训练 Token 下的 Loss
  • 梯度范数和更新范数分布
  • 关键能力集、格式遵循、拒答与安全指标
  • 长短序列、不同语言和难例分桶
  • 训练吞吐、显存峰值、通信开销和有效 Token/秒
  • Checkpoint 恢复后的指标连续性

不要把单个最终分数当成等价证明。FP8 可能总体平均分接近 BF16,却在代码、多语言、长上下文或低频格式任务上发生局部退化。

适用场景

适合 FP8 微调的场景:

  • H100、H200、Blackwell 等具备相应低精度硬件能力的平台
  • 以大尺寸 Linear GEMM 为主要开销的 SFT、持续预训练或大规模对齐训练
  • 有稳定 BF16 基线、能力评测集和分层可观测性的团队
  • 训练 Token 量足够大,能摊薄转换、Scale 计算和编译成本
  • 需要与 FSDP、TP 或其他分布式策略组合,且能治理 Scale 通信

应谨慎的场景:

  • 数据量很小、训练时间很短的 LoRA 微调
  • 大量小矩阵、自定义算子或频繁动态 Shape
  • 没有 BF16 对照、无法复现数据顺序或缺少能力回归集
  • 运行环境频繁更换 GPU、CUDA、框架与 Kernel 版本
  • 只允许观察最终 Loss,无法采集分层数值指标

常见误区

误区一:硬件支持 FP8,就能自动获得加速

硬件支持只是必要条件。小矩阵、频繁 Cast、Scale Kernel、通信和未融合算子可能抵消收益。需要按模块测量端到端 Token/秒,而不是只看某个 GEMM 微基准。

误区二:Loss 没发散,就说明质量没有问题

Loss 对局部能力退化并不敏感。格式遵循、多语言、长序列和代码能力需要独立回归,分层 Amax 和梯度指标也不能省略。

误区三:Amax History 越长越稳定

长窗口可以减少抖动,但历史离群值会持续影响 Scale,使多数普通值失去有效精度。窗口长度必须基于数据和训练阶段验证。

误区四:所有 Rank 独立计算 Scale 更快,也不会影响结果

局部 Scale 可能破坏分布式单设备语义。若关闭 Amax 归约,必须明确接受数值差异,并完整保存每个 Rank 的本地量化状态。

误区五:出现异常只能整模型回退 BF16

逐层白名单和局部回退通常能保留大部分性能收益。真正需要的是可定位、可审计的精度策略,而不是全局开关。

上线检查清单

环境与制品

  • GPU、驱动、CUDA、cuDNN、PyTorch 和 Transformer Engine 版本已固定
  • FP8 Recipe、层级白名单和排除列表进入版本控制
  • 模型、Tokenizer、数据集和 Chat Template 指纹已记录
  • FP8 相关 Scale/Amax 状态能随 Checkpoint 正确恢复
  • 不同 World Size 和并行拓扑的恢复语义已验证

数值与质量

  • BF16 控制组可复现
  • Amax、Scale、零值率、饱和率、梯度范数按层可观测
  • NaN、Inf、Overflow 和异常跳步有硬门禁
  • 核心能力集完成 FP8/BF16 双轨差分
  • 长度、语言、领域和难度分桶没有显著回退
  • 局部 BF16 回退流程已演练

性能与运维

  • 统计的是端到端有效 Token/秒,而非单算子理论峰值
  • Scale 计算、Cast、编译和分布式 Amax 通信成本已计入
  • 小矩阵和收益不足模块已自动或人工过滤
  • 故障后恢复不会重置或错配 Scale History
  • 发布 Manifest 可还原完整精度策略
  • 训练停止、降级和恢复阈值已经审批

参考资料

  1. NVIDIA Transformer Engine — Using FP8 and FP4 with Transformer Engine
  2. NVIDIA Transformer Engine — Common API: DelayedScaling / Current Scaling / MXFP8
  3. PyTorch AO — torchao.float8 API
  4. PyTorch TorchTitan — Float8 Training
  5. Micikevicius et al. — FP8 Formats for Deep Learning
  6. Liang et al. — TorchTitan: One-stop PyTorch Native Solution for Production Ready LLM Pre-training

常见问题

FP8 微调是否等于把全部参数和梯度永久保存为 8 位?
不是。生产级 FP8 通常属于混合精度训练:适合的矩阵乘输入在计算前缩放并转换为 FP8,而累加、主权重、优化器状态、Loss 或敏感模块仍可保留 BF16/FP32。具体边界取决于框架、Recipe 和硬件。
为什么训练 Loss 正常,FP8 模型仍可能出现能力退化?
总体 Loss 会掩盖少数层的饱和、下溢和尺度漂移。需要同时观察分层 Amax、Scale、梯度范数、溢出事件,并用固定能力集与 BF16 基线做差分回放。
FP8 出现异常时应该直接回退整场训练吗?
不必。优先把异常层、LM Head、Embedding、归一化或小矩阵层回退到 BF16,并保留其他收益明确的 FP8 大矩阵层;只有全局数值不稳定时才执行整模型回退。