清晨的后厨,百年老字号“梯度楼”接到一份千人大单。淮扬菜大师傅老李用高精度电子秤,每道菜精确到 0.1 克,出品稳定但一天只能做 100 道;川菜快手小张用弹簧秤爆炒,30 秒一道、火力生猛,但全凭手感,偶尔把菜炒糊。两份菜品单,一边是“必须保守”,一边是“追求速度”,到底用谁?餐厅老板请来一位“火候指挥官”——它给每道菜自动贴好标签:炖汤精细菜交给老李,爆炒菜交给小张,全程无需人工干预。同时配了一位“烟感报警员”:一闻到糊味就立刻调小火,等锅凉了再把火调回去。最终,千人宴 5 小时完成,一道菜没糊。这就是自动混合精度(AMP,Automatic Mixed Precision):不把整个模型粗暴地变成 FP16,而是让矩阵乘、卷积等“爆炒菜”走 FP16 快车道,让归一化、Softmax 等“精细菜”留在 FP32 稳车道,再用 GradScaler 守护梯度不溢出。你只需加几行代码,训练速度与显存双双受益。

岗位关联

掌握 AMP,等于给训练实验装上“涡轮增压”:工程师能在更短时间内跑更多实验、用更低显存放大模型,是现代深度学习必备的性能优化技能。

目录
  1. 可视化演示
  2. 精度基础:FP32 与 FP16
  3. 核心矛盾:精度瓶颈与溢出
  4. AMP 原理:火候指挥官
  5. GradScaler:火警感应器
  6. PyTorch 实战:三连 SOP
  7. 问题排查:后厨灭火指南
  8. AI 应用场景
  9. Python 演示
  10. 常见误区
  11. 面试题

可视化演示

展示不同总体分布(均匀、指数、双峰)的抽样均值分布如何随样本量增大趋近正态分布,可视化中心极限定理的收敛过程。

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

均匀分布抽样 上方为总体分布,下方为样本均值的抽样分布直方图 红色曲线为理论正态分布,绿色线为总体均值 黄色点表示正在抽取的随机样本 X̄ ~ N(μ, σ²/n) 当 n→∞ 中心极限定理:无论总体分布如何,样本均值渐近正态分布

精度基础:FP32 与 FP16

怀旧的老字号后厨里,老李的电子秤能精确到 0.1 克,但读数慢、占地方;小张的弹簧秤轻巧速读,但只能看个大概。在计算机里,这两把秤就是 FP32 与 FP16。FP32 用 32 位二进制表示一个数:1 位符号、8 位指数、23 位尾数,能表达从 1.4×10⁻⁴⁵ 到 3.4×10³⁸ 的广阔范围,小数精度约 7 位十进制;FP16 则把指数压到 5 位、尾数压到 10 位,范围缩窄到 6×10⁻⁵ 到 65504,精度约 3 位十进制,但存储和计算开销都几乎减半。

这两把秤没有绝对好坏,只看用途。称一袋面粉,弹簧秤够用;称 0.2 克酵母,必须电子秤。深度学习模型的权重和激活,就是“一袋面粉”和“0.2 克酵母”的混装:矩阵乘、卷积这种体量大、对噪声容忍度高的计算,FP16 完全顶得住;可一旦遇到特别小或特别大的数值,FP16 就会失手。

FP32 的 23 位尾数像电子秤的精细刻度,FP16 的 10 位尾数像弹簧秤的粗略格子。显存减半、带宽减半,这就是 AMP 能加速的第一个物理基础——但前提是,别让弹簧秤称出离谱的读数。

FP16 数值=(−1)s×2e−15×(1.m)2\text{FP16 数值} = (-1)^s \times 2^{e-15} \times (1.m)_2
  1. 符号位 s1 位二进制,s=1 表示负数,s=0 表示正数,如同秤上的正负号。
    s∈{0,1}s \in \{0, 1\}
  2. 指数 e 与偏置 155 位指数范围 0 到 31,减去偏置 15 后得到 -14 到 15 的实际指数,决定了数能有多大或多小。
    e−15∈[−14,15]e - 15 \in [-14, 15]
  3. 尾数 (1.m)10 位尾数表示 1 到 2 之间的小数部分,只能近似真实值,这就是精度损失的来源。
    (1.m)2=1+∑i=110bi2−i(1.m)_2 = 1 + \sum_{i=1}^{10} b_i 2^{-i}

核心矛盾:精度瓶颈与溢出

如果把深度学习训练比作一场烹饪比赛,那么梯度就是“火候信号”——它告诉厨师下一步该加盐还是关小火。在 FP32 的电子秤上,梯度哪怕小到 10⁻⁶ 也能被读出;但在 FP16 的弹簧秤上,读数小于最小刻度 6×10⁻⁵ 时,就直接显示为 0,这就是“下溢”。另一头,如果某次计算数值超过了 65504,弹簧秤的指针会直接打满盘,变成无穷大 Inf,反向传播中 Inf 会迅速扩散成 NaN,整个训练瞬间报废,这就是“上溢”。

深度学习偏偏是“两头都占”的典型:正常梯度常常落在 10⁻⁵ 到 10⁻¹ 之间,有些层还会偶然产生很大的激活值。用纯 FP16 训练,相当于所有菜都交给猛火小张——大部分菜能爆炒出锅,但总有一两盘会糊。

所以问题很清楚:全 FP32 是慢而稳的淮扬菜,全 FP16 是快但易糊的爆炒。深度学习中卷积、矩阵乘这类计算量占 90% 以上的算子,对数值精度并不敏感,适合快车道;而某些归一化、Softmax 等对数值极敏感的算子,需要电子秤级精度。AMP 的诞生,就是给不同算子贴上不同的火候标签。

6.1×10−5≤∣x∣≤65504(FP16),1.4×10−45≤∣x∣≤3.4×1038(FP32)6.1 \times 10^{-5} \le |x| \le 65504 \quad (\text{FP16}), \quad 1.4 \times 10^{-45} \le |x| \le 3.4 \times 10^{38} \quad (\text{FP32})
  1. 下界 6.1×10⁻⁵FP16 能表达的最小正规正数,梯度比它小就会被记成 0,像弹簧秤称不出 0.2 克酵母。
    FP16 下界:6.1×10−5\text{FP16 下界}: 6.1 \times 10^{-5}
  2. 上界 65504超过这一数值就溢出为 Inf,Inf 参与后续运算就会产生 NaN,像秤盘超载直接卡死。
    FP16 上界:65504\text{FP16 上界}: 65504
  3. FP32 的动态范围对比FP32 上界比 FP16 大 34 个数量级,这就是为什么 FP32 足够“稳”而 FP16 会“脆”。
    3.4×1038≫655043.4 \times 10^{38} \gg 65504

AMP 原理:火候指挥官

火候指挥官站在后厨中央,手里拿着一张贴纸机。它看到卷积、矩阵乘这类“大盘菜”,就贴上绿色标签“FP16 快车道”;看到 LayerNorm、Softmax 这类“精细菜”,就贴上蓝色标签“FP32 稳车道”。在 PyTorch 中,这一过程由 autocast 上下文自动完成,你不需要手工决定每一层用什么精度,它按一张“算子白名单”动态分派。

更妙的是,指挥官还安排了一位“备菜员”在后厨守着原配方。模型的主权重始终以 FP32 保存,每次前向时才临时转成 FP16 给快车道使用,反向传播得到的 FP16 梯度再转回 FP32 去更新主权重。这样即使某个小更新量只有 10⁻⁷,也能稳稳地累积在 FP32 主副本里,不会在 FP16 中被抹成 0。

这套“原配方保留 + 临时快炒”的机制,让 AMP 在快的同时几乎不损失精度。不过,快车道还有一个隐患:梯度太小被下溢。这时,就需要火警感应器 GradScaler 出场了。

wFP32⟶wFP16⟶lossFP16⟶gFP16⟶wFP32neww_{\mathrm{FP32}} \longrightarrow w_{\mathrm{FP16}} \longrightarrow \mathrm{loss}_{\mathrm{FP16}} \longrightarrow g_{\mathrm{FP16}} \longrightarrow w_{\mathrm{FP32}}^{\mathrm{new}}
  1. 主权重转 FP16前向计算前,把 FP32 权重临时复制成 FP16,一次轻量内存拷贝。
    wFP32→wFP16w_{\mathrm{FP32}} \to w_{\mathrm{FP16}}
  2. FP16 快车道前向卷积、矩阵乘等核心算子在 FP16 下完成,速度和显存都受益。
    wFP16→lossFP16w_{\mathrm{FP16}} \to \mathrm{loss}_{\mathrm{FP16}}
  3. 梯度更新回 FP32FP16 梯度折算回 FP32 后更新主权重,小步长也不会被截断丢失。
    gFP16→wFP32newg_{\mathrm{FP16}} \to w_{\mathrm{FP32}}^{\mathrm{new}}

GradScaler:火警感应器

小张的爆炒偶尔会冒烟,烟感报警器一响,自动关小火,等烟散了再逐步把火调回去。GradScaler 就是这个烟感报警器。它的做法是:在损失函数上乘一个大数 S(默认 65536),把梯度“放大”到 FP16 能表达的舒适区,再执行反向传播。这样原本只有 10⁻⁵ 量级的梯度,放大后被 FP16 的弹簧秤称得清清楚楚。

如果反向传播中检测到梯度里有 Inf 或 NaN,说明这轮火候失控了,GradScaler 会跳过这次参数更新,并把 S 减半;如果连续许多步都风平浪静,它再把 S 翻倍,恢复到更大的放大倍数。一缩一放之间,训练既避免了爆炸,又不会因为长期“小火”而变慢。

GradScaler 最迷人的地方在于“自动”:你不需要盯着训练曲线手动调整缩放,它会在每次优化器更新后自行诊断并动态调节。烟感报警器把“糊锅”变成了一次可恢复的小插曲,而不是整桌宴席的灾难。

Ls=L×S,若 ∂Ls∂w 含 inf/NaN: S←S2,否则每 N 步: S←S×2L_s = L \times S, \quad \text{若 } \frac{\partial L_s}{\partial w} \text{ 含 inf/NaN: } S \leftarrow \frac{S}{2}, \quad \text{否则每 } N \text{ 步: } S \leftarrow S \times 2
  1. 放大损失把损失 L 乘以缩放因子 S,再做反向传播;默认 S 从 65536 起步。
    Ls=L×SL_s = L \times S
  2. 溢出诊断与退火梯度中出现 Inf 或 NaN 时,S 减半,并跳过本次参数更新,等数值恢复稳定。
    S←S2, 跳过更新S \leftarrow \frac{S}{2}, \text{ 跳过更新}
  3. 平稳恢复连续 N 步(PyTorch 默认 2000 步)无溢出,S 翻倍,重新追求更大的放大倍数。
    S←S×2S \leftarrow S \times 2

PyTorch 实战:三连 SOP

理论到位了,怎么在代码里请来这位火候指挥官?只需要一个口诀:先进入 autocast 上下文,再给训练循环加上 scaler.scale → step → update 三连。这就像后厨的标准化作业流程,照着做就不会乱。

三句话的分工非常清晰:scale 把损失放大再反向传播;step 在真正更新参数前检查梯度里有没有 Inf/NaN,有就跳过;update 在每轮结束时更新缩放因子 S,为下一轮做准备。顺序不能乱,尤其 update 必须在 step 之后,因为只有 step 执行完,GradScaler 才知道这一轮是否存在溢出。

这套 SOP 一旦跑通,训练脚本几乎不用改别的地方。唯一要记住的例外是:如果你用了梯度裁剪,需要先用 scaler.unscale_(optimizer) 把梯度还原,再执行裁剪,否则裁剪作用在放大后的梯度上会失真。

S⋅L→backward∂(SL)∂w→stepw←w−η⋅1S⋅∂(SL)∂w→updateS′S \cdot L \xrightarrow{\text{backward}} \frac{\partial (S L)}{\partial w} \xrightarrow{\text{step}} w \leftarrow w - \eta \cdot \frac{1}{S} \cdot \frac{\partial (S L)}{\partial w} \xrightarrow{\text{update}} S'
  1. scale 放大对损失乘 S 后反向传播,梯度也整体放大 S 倍。
    S⋅L→∂(SL)∂w=S⋅∂L∂wS \cdot L \to \frac{\partial (S L)}{\partial w} = S \cdot \frac{\partial L}{\partial w}
  2. step 前还原优化器更新前把梯度除以 S,恢复真实梯度大小,再执行标准参数更新。
    w←w−η⋅1S⋅S⋅∂L∂w=w−η⋅∂L∂ww \leftarrow w - \eta \cdot \frac{1}{S} \cdot S \cdot \frac{\partial L}{\partial w} = w - \eta \cdot \frac{\partial L}{\partial w}
  3. update 调参根据本轮是否溢出,决定 S 减半、不变或翻倍。
    S′=ϕ(S,溢出标志)S' = \phi(S, \text{溢出标志})
python9 行
from torch.cuda.amp import autocast, GradScalerscaler = GradScaler()for batch in dataloader:    optimizer.zero_grad()    with autocast():        loss = loss_fn(model(x), y)    scaler.scale(loss).backward()    scaler.step(optimizer)    scaler.update()

问题排查:后厨灭火指南

用了 AMP 之后如果出现 NaN,先别慌,后厨有一套灭火流程。第一反应永远是“降学习率”,因为 90% 的 NaN 源于学习率过大导致的梯度爆炸;如果降学习率无效,就检查数据里有没有 NaN,或者模型里有没有数值不稳定的操作——比如某些指数运算在 FP16 下极易溢出。

更专业的排查方式是把 GradScaler 当作诊断仪表盘:如果 S 反复减半、最终卡在很小的值,说明模型确实在持续溢出,此时要往上查学习率和网络初始化;如果训练 loss 正常下降但收敛变慢,则可能是 S 长期偏小,导致梯度下溢,需要观察 scaler.get_scale() 的变化趋势。

回到厨房比喻:火候不对时,先看食材和油温,再看烟感报警器有没有误报。AMP 的排查也遵循同样的顺序:先用 FP32 全精度跑一遍作为基准,再逐步开启 AMP,对比 loss 曲线和 GradScaler 状态,就能精准定位“糊锅”的源头。

溢出条件: ∥∇wL∥∞≥65504S,下溢条件: 0<∥∇wL∥∞<6.1×10−5S\text{溢出条件: } \|\nabla_w L\|_\infty \ge \frac{65504}{S}, \quad \text{下溢条件: } 0 < \|\nabla_w L\|_\infty < \frac{6.1 \times 10^{-5}}{S}
  1. 溢出阈值梯度被放大 S 倍后超出 65504 就会溢出,所以 S 越大,能容忍的真实梯度越小。
    ∥∇wL∥∞≥65504S\|\nabla_w L\|_\infty \ge \frac{65504}{S}
  2. 下溢阈值如果 S 不够大,真实梯度在放大后仍小于 FP16 最小正数,会被抹成 0。
    ∥∇wL∥∞<6.1×10−5S\|\nabla_w L\|_\infty < \frac{6.1 \times 10^{-5}}{S}
  3. 平衡之道S 既不能太大也不能太小,这正是 GradScaler 动态调整的意义所在。
    6.1×10−5S≤∥∇wL∥∞≤65504S\frac{6.1 \times 10^{-5}}{S} \le \|\nabla_w L\|_\infty \le \frac{65504}{S}

AI 应用场景

大规模视觉模型训练提速

阶段:模型训练加速

解决的问题:目标检测、图像分类通常吃显存。传统 FP32 训练 batch size 被显存卡死,模型一深就 OOM;用 AMP 后激活值和权重减半存储,同样显存能塞下更大 batch,训练吞吐提升 30%-50%。

具体案例:某安防公司训练 YOLO 检测模型,原来 batch size 只能开到 16,开启 AMP 后开到 32,单卡显存占用反而更低;训练一轮从 6 小时缩短到 3.5 小时,mAP 几乎不变,相当于白赚一块显卡的算力。

大模型微调与低显存部署

阶段:预训练与微调

解决的问题:大语言模型全参微调对显存要求极高,FP32 下 7B 模型单卡 16G 根本放不下;AMP 配合 LoRA 等参数高效微调,让消费级显卡也能跑起大模型训练。

具体案例:某创业团队用单张 RTX 4090 微调 7B 模型。开启 AMP 后,激活值显存减半,配合梯度检查点,batch size 从 2 提升到 8,训练时间缩短约 40%,且验证集困惑度没有明显退化。

超参数搜索实验周期压缩

阶段:实验调优

解决的问题:调参是炼丹师的日常工作,每轮实验时间决定了搜索空间的上限。AMP 将单轮训练时间压缩近一半,同样的算力预算下能尝试更多学习率、批次大小组合。

具体案例:某推荐系统团队做 CTR 模型超参搜索,原本 100 组配置要跑 10 天,接入 AMP 后 6 天跑完,提前锁定最优配置上线,节省约 40% 的 GPU 机时费用。

Python 演示

python30 行
import torchimport torch.nn as nnfrom torch.cuda.amp import autocast, GradScaler torch.manual_seed(42)if not torch.cuda.is_available():    raise RuntimeError("AMP demo needs CUDA GPU") model = nn.Sequential(    nn.Linear(64, 128),    nn.ReLU(),    nn.Linear(128, 10),).cuda()optimizer = torch.optim.SGD(model.parameters(), lr=0.01)loss_fn = nn.CrossEntropyLoss()scaler = GradScaler() X = torch.randn(256, 64).cuda()y = torch.randint(0, 10, (256,)).cuda() for step in range(200):    optimizer.zero_grad()    with autocast():        out = model(X)        loss = loss_fn(out, y)    scaler.scale(loss).backward()    scaler.step(optimizer)    scaler.update()    if step % 40 == 0:        print(f"step={step} loss={loss.item():.3f} scale={scaler.get_scale():.0f}")
目的:演示 PyTorch 中最标准的 AMP 训练循环,用 20 行核心代码跑通"autocast + GradScaler"三连 SOP。
  1. scaler = GradScaler()创建“烟感报警器”,负责损失缩放与溢出检测。
  2. with autocast():开启混合精度上下文,矩阵乘和卷积自动走 FP16 快车道。
  3. scaler.scale(loss).backward()把损失放大 65536 倍再反向传播,小梯度也能被称出来。
  4. scaler.step(optimizer)检查梯度是否含 Inf/NaN,有则跳过本轮参数更新。
  5. scaler.update()每轮结束动态调整缩放因子 S,无溢出则慢慢恢复放大倍数。

常见误区

误区FP16 只有 FP32 一半精度,训练出来的模型肯定更差,所以总觉得 AMP 是“牺牲精度换速度”。

正解AMP 的关键是“混合”:主权重始终以 FP32 保存,精度敏感的算子也留在 FP32,只有卷积、矩阵乘这类对噪声容忍度高的计算走 FP16。实践中,视觉和 NLP 任务在 AMP 下精度损失通常小于 0.1%,有时甚至因 batch 变大而更好。

为何常见:人们把“FP16 精度低”直接等同于“模型精度低”,忽略了混合精度设计和主权重副本的缓冲作用。

误区以为开了 autocast 之后,模型里每一个张量、每一个算子都会变成 FP16,像全局替换一样。

正解autocast 有一套“白名单”机制:卷积、矩阵乘被自动 cast 到 FP16;LayerNorm、Softmax 这类数值敏感或容易溢出的算子仍以 FP32 执行。这是自动选择,不是一刀切。

为何常见:混合精度名字里带“混合”,但初学者容易望文生义,把它理解成“全半精度”。

误区GradScaler 的作用是放大损失,那 S 设得越大,梯度越不容易下溢,岂不是更好?

正解S 太大会让放大后的梯度超过 65504,直接触发 Inf 溢出,GradScaler 检测到后会把 S 减半。所以 S 存在一个动态平衡范围,PyTorch 默认 65536 起步,这正是经验上的安全起点。

为何常见:只想到下溢,没意识到 FP16 还有一个上溢上限,放大过头同样会爆。

误区训练中途 loss 变成 NaN,第一反应就是“废了,重新跑”,然后把 AMP 关掉当成罪魁祸首。

正解GradScaler 的设计初衷就是容忍偶发溢出:检测到 Inf/NaN 时会跳过更新并减小 S,训练可以继续。若 NaN 频繁出现,优先排查学习率、数据和模型数值稳定性,而不是直接放弃或禁用 AMP。

为何常见:传统 FP32 训练极少出现溢出,初学者没见过“可恢复的 NaN”,容易把警戒信号当成末日。

误区在自己机器上开了 AMP,发现速度没变化,就断定 AMP 是营销噱头。

正解AMP 的速度收益主要来自 Tensor Core 等专用 FP16 计算单元,现代 NVIDIA GPU(V100、A100、RTX 20 系以上)才有明显加速;纯 CPU 或老 GPU 上 FP16 没有硬件加速,自然感受不到收益。

为何常见:把“软件层的自动精度选择”等同于“任何硬件都能提速”,忽略了底层硬件的算力支撑。

面试题

Q1. FP16 相比 FP32 最直接的优势是什么?

单选题 · easy

  • A.显存占用直接减半← 正确答案
  • B.数值范围更加宽广
  • C.模型精度必然提升
  • D.梯度永远不会下溢

答案:A。FP16 用 16 位存一个数,存储和带宽天然减半,这是 AMP 节省显存的物理基础。B 说反了,FP16 范围比 FP32 窄得多;C 和 D 不符合浮点常识。

Q2. FP16 能表示的最大数值约为多少?

单选题 · easy

  • A.约 65504← 正确答案
  • B.约 3.4e38
  • C.约 1.4e-45
  • D.约 2 的 64 次方

答案:A。FP16 指数位只有 5 位,最大值约为 2^15 × (1+1023/1024) ≈ 65504。3.4e38 是 FP32 的上界,1.4e-45 是 FP32 的最小正非规格化数。

Q3. AMP 训练时,模型的“主权重”保存在什么精度下?

单选题 · easy

  • A.FP32← 正确答案
  • B.FP16
  • C.FP64
  • D.INT8

答案:A。主权重保存在 FP32,每次前向计算时才临时转成 FP16。FP32 主副本能留住小到 10⁻⁷ 的更新量,避免在 FP16 中被截断归零,这是 AMP 精度不掉的关键。

Q4. 自动混合精度的核心思想是?

单选题 · mid

  • A.自动为不同算子选择合适精度← 正确答案
  • B.所有算子一律用 FP16
  • C.只把损失改成 FP16 计算
  • D.只把权重存储改成低精度

答案:A。AMP 的“混合”体现在按算子自动分派:卷积、矩阵乘走 FP16 快车道,归一化、Softmax 等敏感层走 FP32 稳车道。B 是纯 FP16,C、D 都不是混合精度的完整含义。

Q5. GradScaler 存在的根本原因是什么?

单选题 · hard

  • A.防止小梯度在 FP16 下被截断归零← 正确答案
  • B.防止学习率在训练中震荡
  • C.让反向传播速度提升一倍
  • D.把损失值压缩到更小的范围

答案:A。FP16 最小正则数约 6.1e-5,而深度学习梯度常常在 1e-5 到 1e-3 之间。GradScaler 先放大损失,让梯度落进 FP16 可表达区间,反向传播后再还原,本质是给“小梯度”做翻译,让弹簧秤称得出细盐。

Q6. AMP 训练循环中,三连操作的正确顺序是?

单选题 · mid

  • A.scale 损失 - step 优化器 - update 缩放← 正确答案
  • B.update 缩放 - scale 损失 - step 优化器
  • C.step 优化器 - scale 损失 - update 缩放
  • D.scale 损失 - update 缩放 - step 优化器

答案:A。标准流程是 scaler.scale(loss).backward()、scaler.step(optimizer)、scaler.update()。update 必须在 step 之后,因为 update 需要依据本轮是否溢出来决定缩放因子减半、不变还是翻倍。

Q7. 在 autocast 上下文中,哪些操作通常更受益于 FP16 快车道?

多选题 · mid

  • A.矩阵乘法(线性层)← 正确答案
  • B.卷积运算← 正确答案
  • C.LayerNorm 归一化
  • D.高精度求和

答案:AB。矩阵乘和卷积计算量大且对噪声容忍度高,是 FP16 最大的受益者;LayerNorm 和高精度求和属于数值敏感操作,autocast 会让它们留在 FP32,避免精度崩塌。

Q8. 开启 AMP 后,常见的收益有哪些?

多选题 · mid

  • A.显存占用明显降低← 正确答案
  • B.Tensor Core 加速计算← 正确答案
  • C.模型精度必然提升
  • D.任何硬件都能提速

答案:AB。FP16 存储减半降低显存,Tensor Core 让矩阵运算变快,这两者是 AMP 的核心收益。精度是否提升取决于具体任务,且老 GPU 或 CPU 缺少 FP16 加速单元,速度未必有变化。

Q9. 使用 AMP 后 loss 频繁变成 NaN,下列哪些是可能原因?

多选题 · hard

  • A.学习率过大导致梯度爆炸← 正确答案
  • B.训练数据本身含有 NaN← 正确答案
  • C.模型内部有数值不稳定的算子← 正确答案
  • D.训练启动速度设置过慢

答案:ABC。NaN 的三大来源是梯度爆炸、脏数据和不稳定计算。学习率过大会让放大后的梯度溢出成 Inf,脏数据直接污染损失,某些指数或除法算子在 FP16 下也容易爆。启动速度与数值稳定性无关。

Q10. PyTorch 中 GradScaler 默认的缩放恢复机制是什么?

单选题 · extreme

  • A.连续约 2000 步无溢出就翻倍← 正确答案
  • B.每做一次反向传播就翻倍
  • C.只在 loss 低于阈值时翻倍
  • D.只在学习率衰减后翻倍

答案:A。GradScaler 默认 growth_interval=2000,即连续 2000 步未出现 Inf/NaN 后,缩放因子乘 2 恢复放大倍数;一旦出现溢出,则立即减半并重置计数,形成“进二退一”的动态平衡。