第 1 章 课程导入:混合精度训练的定义、发展背景、核心价值与整体课程框架

当你第一次在单卡上训练一个十亿参数级别的模型时,最直观的瓶颈往往不是显存容量,而是显存带宽与算力利用率。FP32 训练虽然数值稳定,但每个参数、梯度、优化器状态都占用 4 字节,激活值更是显存大户。混合精度训练(Mixed Precision Training)正是为解决这一矛盾而生的工程实践:它让模型在关键计算路径上使用 FP16/BF16 加速,同时保留 FP32 主权重以维持数值稳定。

一、什么是混合精度训练

混合精度训练并不是“把 FP32 全部换成 FP16”这么简单。它的核心思想是:在计算密集、对精度不敏感的部分使用低精度,在参数更新、损失累加等对数值范围敏感的部分保留高精度。典型做法是维护一份 FP32 主权重(Master Weight),前向与反向传播使用 FP16/BF16,梯度在更新前被放大并转换回 FP32,再作用于主权重。

一句话定义:混合精度训练 = 低精度计算 + 高精度参数更新 + 损失缩放(Loss Scaling)防止梯度下溢。

其中损失缩放是混合精度能否稳定收敛的关键。FP16 的最小正规数约为 6.1e-5,很多梯度值远小于该阈值,直接反向传播会变成 0。动态损失缩放通过自动调整缩放因子,让梯度在 FP16 可表示范围内被保留,再在更新前还原。

混合精度训练数据流与精度切换 FP32 主权重 Master Weight FP16/BF16 副本 前向/反向计算 损失缩放 Loss × Scale FP32 梯度更新 Unscale + Optimizer 更新主权重后重新生成低精度副本 动态损失缩放判断路径 反向传播得到梯度 检查是否 Inf/NaN 无溢出:保留缩放 或尝试增大 Scale 有溢出:跳过更新 Scale 减半 稳定窗口后 Scale 翻倍

二、发展背景:从 FP32 到混合精度的必然

2017 年 NVIDIA 在 Volta 架构中引入 Tensor Core,FP16 矩阵乘加吞吐达到 FP32 的 8 倍以上。但早期直接使用 FP16 训练常出现梯度下溢、权重更新失效、Loss 震荡等问题。2018 年 NVIDIA 与百度联合发表《Mixed Precision Training》,系统提出 FP32 主权重 + 损失缩放的方案,使混合精度在 ResNet、BERT 等模型上达到与 FP32 几乎一致的精度。

随后 BF16 出现,它保留 FP32 的 8 位指数,动态范围与 FP32 相同,几乎不需要损失缩放,在 A100、H100 及国产加速卡上迅速普及。今天,混合精度已成为大模型训练的默认配置,与 ZeRO、梯度检查点、FlashAttention 等技术共同构成现代训练栈。

三、核心价值:为什么值得投入学习

维度 FP32 训练 混合精度训练 收益来源
显存占用 高 降低约 30%~50% 激活值、梯度用 FP16 存储
计算吞吐 基准 提升 2~8 倍 Tensor Core 低精度矩阵乘
通信开销 高 降低约一半 梯度以 FP16 通信
收敛精度 基准 可持平或略优 损失缩放 + 主权重
工程复杂度 低 中高 需处理溢出、缩放策略

四、典型场景与常见误区

混合精度最适合计算密集、显存受限、矩阵维度较大的场景,例如 Transformer 预训练、大规模推荐模型、视觉检测模型。但在小模型、小批量或含大量归约操作的模型中,收益可能有限,甚至因溢出处理带来额外开销。

常见误区:把混合精度当成“免费加速”。若未正确配置损失缩放,训练可能在第几百步突然 Loss 变 NaN;若优化器状态未用 FP32,Adam 的动量与方差会被低精度截断,导致收敛变差。

另一个误区是认为 BF16 可以完全替代损失缩放。BF16 动态范围大,确实很少溢出,但其尾数只有 7 位,梯度精度较低,在部分任务上仍需配合主权重与谨慎的超参设置。

五、整体课程框架

本课程共 30 章,围绕“原理—配置—实操—排障—复盘”展开。前段建立混合精度与损失缩放的理论基础;中段进入 PyTorch AMP、GradScaler、BF16 与分布式训练配置;后段覆盖溢出诊断、性能剖析、精度回归与生产级训练模板。

# 典型 PyTorch 混合精度训练骨架
scaler = torch.cuda.amp.GradScaler()

for data, target in loader:
    optimizer.zero_grad()
    with torch.cuda.amp.autocast(dtype=torch.float16):
        output = model(data)
        loss = criterion(output, target)

    scaler.scale(loss).backward()
    scaler.step(optimizer)
    scaler.update()
学习建议:每章都动手跑一遍最小示例,记录 Loss、Scale、显存与吞吐变化。混合精度的很多问题不是“看懂”能解决的,而是要在日志里看到 Inf、NaN、Scale 骤降等信号后,才能形成真正的工程直觉。

完成本章后,你应当能用自己的话解释:混合精度为什么需要 FP32 主权重、损失缩放解决什么问题、BF16 与 FP16 的取舍差异,以及本课程将如何带你从“能跑”走向“跑得稳、跑得快”。