当前位置:首页 > 文章列表 > 科技周边 > 人工智能 > PyTorch AMP 怎样区分 autocast 与 GradScaler 的职责

PyTorch AMP 怎样区分 autocast 与 GradScaler 的职责

来源:17golang原创 2026-11-14 04:34:16 0浏览 收藏

我在给训练脚本加 AMP 时,最容易混淆的不是 API 名字,而是把“选择计算精度”和“保护梯度更新”当成了同一件事。PyTorch 官方文档把它们拆成两个可以独立使用的模块:autocast 负责在选定区域内为算子选择合适的精度,GradScaler 负责梯度缩放、反缩放和溢出处理。先分清这条边界,训练循环就不容易越写越乱。

官方地址:https://docs.pytorch.org/docs/stable/amp.html

一句话判断:autocast 管“前向怎么计算”,GradScaler 管“反向得到的梯度怎样安全地交给优化器”。float16 训练通常把两者配合起来;bfloat16 是否需要缩放,则要看设备、模型和数值表现,不能因为用了 autocast 就机械创建 scaler。

先把 autocast 和 GradScaler 拆成两条线

AMP 不是一个只能整包开启的开关。把训练过程画成三段更容易理解:前向算子产生输出,loss 进入反向传播,优化器读取梯度更新参数。

组件解决的问题主要位置不负责什么
torch.autocast哪些算子用较低精度、哪些保留较高精度forward 和 loss 计算区域不负责检查梯度是否溢出,也不替代 optimizer.step()
torch.amp.GradScaler低精度梯度过小或出现 inf/NaN 时怎样保护更新loss.backward() 前后以及 optimizer.step()不决定前向算子的 dtype,不会让所有算子都变成 float16
autocast 在前向计算中按算子选择 float16、bfloat16 或 float32 的静态说明图
图1:autocast 的职责是前向算子精度选择;这是静态结构说明图,不是运行截图。

因此,看到 with torch.autocast(...) 时,先问“这段前向计算想怎样分配精度”;看到 scaler.scale(loss) 时,问的是“这次反向梯度是否需要数值保护”。两者可以组合,但不是互相替代。

autocast 应该包住哪些计算

常见训练循环把模型前向和 loss 放进 autocast 上下文,把 backward 放在上下文外。这样做的重点不是强制所有计算降精度,而是交给 AMP 的算子策略选择:适合低精度的计算可以更快,数值敏感的计算仍可能保留更高精度。

import torch

device = "cuda"
model = Net().to(device)
optimizer = torch.optim.AdamW(model.parameters(), lr=3e-4)

for inputs, targets in loader:
    inputs = inputs.to(device)
    targets = targets.to(device)
    optimizer.zero_grad(set_to_none=True)

    # autocast 只描述前向与 loss 的精度选择范围。
    with torch.autocast(device_type=device, dtype=torch.float16):
        outputs = model(inputs)
        loss = loss_fn(outputs, targets)

    # backward 不需要因为前向用了 autocast 就继续写在上下文里。
    loss.backward()
    optimizer.step()

这段代码只展示 autocast 的职责,适合用来理解推理或不需要梯度缩放的场景。它不等于完整的 float16 AMP 训练方案,因为 loss 仍然可能让很小的梯度在反向过程中下溢。

GradScaler 怎样接管梯度更新

需要梯度缩放时,先创建一个 scaler。每轮的顺序是:用 scaler 放大 loss,再对放大后的 loss 调用 backward;调用 scaler.step(optimizer) 时,scaler 会先检查并还原梯度,只有没有发现 inf 或 NaN 时才让优化器真正更新;最后用 scaler.update() 调整下一轮的缩放因子。

scaler = torch.amp.GradScaler("cuda")

for inputs, targets in loader:
    inputs = inputs.to(device)
    targets = targets.to(device)
    optimizer.zero_grad(set_to_none=True)

    # autocast 决定前向算子的计算精度。
    with torch.autocast(device_type="cuda", dtype=torch.float16):
        outputs = model(inputs)
        loss = loss_fn(outputs, targets)

    # scale 只放大反向使用的 loss,不改变模型参数的真实目标。
    scaler.scale(loss).backward()

    # step 会检查 inf/NaN;异常时跳过本次 optimizer.step()。
    scaler.step(optimizer)

    # 根据本轮是否稳定,调整后续迭代的 scale。
    scaler.update()
GradScaler 对 loss 和梯度执行缩放、反缩放、溢出检查并决定是否更新参数的静态说明图
图2:GradScaler 的职责是让梯度更新经过缩放与溢出保护;这是静态结构说明图,不是运行截图。

这里的“跳过更新”很重要:它不是训练循环报错,而是 scaler 在发现当前梯度不适合更新时保护参数。不要用 loss 的数值变化直接推断 optimizer 一定执行了更新;要把 scaler 的 step 视为一次带检查的更新请求。

需要梯度裁剪时,先 unscale 再裁剪

scaler.scale(loss).backward() 之后,参数的 .grad 还是缩放后的梯度。如果直接调用梯度裁剪,阈值也会被缩放,结果就不是原本想要的阈值。正确顺序是对目标优化器调用一次 unscale_,再按普通梯度执行裁剪,最后继续交给 scaler.step。

scaler.scale(loss).backward()

# 先把 optimizer 管理的梯度还原到真实尺度,再进行裁剪。
scaler.unscale_(optimizer)
torch.nn.utils.clip_grad_norm_(model.parameters(), max_norm=1.0)

# unscale_ 已经做过还原,step 会继续负责 inf/NaN 检查。
scaler.step(optimizer)
scaler.update()

同一个 optimizer 在一次 step 之间不要重复调用 unscale_。如果有多个 optimizer,需要分别对需要检查或修改的 optimizer 处理,但 scaler.update() 通常在这一轮所有 optimizer 都完成 step 后调用一次。

float16、bfloat16 和梯度累积不要混为一谈

autocast 与 GradScaler 是模块化的,所以组合方式取决于目标 dtype 和训练策略。

  • float16:低精度范围较窄,梯度下溢风险更值得关注,常见做法是 autocast 配合 GradScaler。
  • bfloat16:指数范围更接近 float32,很多场景下不需要同样的梯度缩放;但是否启用 scaler 仍要看设备支持、模型数值稳定性和实际训练表现。
  • 梯度累积:一个有效 batch 的累积期间应保持相同 scale,不要在累积尚未完成时 unscale 或 update;完成一次有效 batch 后再 step 和 update。
for index, (inputs, targets) in enumerate(loader):
    with torch.autocast(device_type="cuda", dtype=torch.float16):
        loss = loss_fn(model(inputs.to(device)), targets.to(device))
        # 除以累积步数,保持有效 batch 的梯度尺度一致。
        loss = loss / accumulate_steps

    # 累积期间保持 scaled gradients,不提前 unscale。
    scaler.scale(loss).backward()

    if (index + 1) % accumulate_steps == 0:
        # 有效 batch 完成后才提交更新并调整 scale。
        scaler.step(optimizer)
        scaler.update()
        optimizer.zero_grad(set_to_none=True)

如果只记住一条边界,可以这样记:autocast 是“算子级的前向计算策略”,GradScaler 是“优化器级的反向更新保护”。不要用扩大 batch、改变学习率或强行把所有层 cast 成 float16 来代替它们各自的职责。

我会怎样排查 AMP 训练循环

  1. 先看 autocast 的作用域,确认它覆盖了 forward 和 loss,而不是把一大段无关的参数更新代码都包进去。
  2. 再看 loss 是否通过 scaler.scale(loss).backward() 进入反向;如果使用 scaler,却仍直接 loss.backward(),缩放链路就断了。
  3. 若做梯度裁剪、梯度范数读取或自定义梯度检查,确认先对对应 optimizer 调用了 unscale_。
  4. 最后检查 step() 和 update() 的粒度:梯度累积要在有效 batch 完成后更新,多优化器要让每个 optimizer 都完成自己的 step。

这套排查顺序比“看到 loss 变成 NaN 就把所有地方改成 float32”更有针对性:先定位是算子精度选择、梯度缩放时机,还是优化器更新边界出了问题。

常见问题

只用 autocast 可以训练吗?

可以,autocast 和 GradScaler 是模块化的;但使用 float16 训练时,是否需要梯度缩放要根据模型和设备的数值表现判断。autocast 本身不会完成梯度溢出保护。

GradScaler 会把所有参数永久改成 float16 吗?

不会。GradScaler 只围绕 loss 和梯度更新做数值尺度处理,不负责决定模型参数或前向算子的 dtype。

为什么 scaler.step 没有让参数更新?

如果 scaler 在梯度中发现 inf 或 NaN,它可能跳过这次 optimizer.step(),随后通过 update 调整缩放因子。排查时要把这种保护分支和真正的程序异常区分开。

梯度裁剪能放在 scaler.step 之后吗?

通常不行。需要按真实梯度裁剪时,应在 scaler.step 前对对应 optimizer 调用 unscale_,再执行裁剪。

参考:PyTorch 官方 AMP 文档、Automatic Mixed Precision examples 与 AMP recipe。本文的图片均为围绕职责关系制作的原创静态说明图,不是软件截图或运行证据。

版本声明
本文转载于:17golang原创 如有侵犯,请联系study_golang@163.com删除
PyTorch DataLoader in_order=False 为什么可能改变样本分布PyTorch DataLoader in_order=False 为什么可能改变样本分布
上一篇
PyTorch DataLoader in_order=False 为什么可能改变样本分布
RAG chunk metadata 怎样保留原文页码和标题层级
下一篇
RAG chunk metadata 怎样保留原文页码和标题层级
查看更多
最新文章
查看更多
课程推荐
  • 前端进阶之JavaScript设计模式
    前端进阶之JavaScript设计模式
    设计模式是开发人员在软件开发过程中面临一般问题时的解决方案,代表了最佳的实践。本课程的主打内容包括JS常见设计模式以及具体应用场景,打造一站式知识长龙服务,适合有JS基础的同学学习。
    543次学习
  • GO语言核心编程课程
    GO语言核心编程课程
    本课程采用真实案例,全面具体可落地,从理论到实践,一步一步将GO核心编程技术、编程思想、底层实现融会贯通,使学习者贴近时代脉搏,做IT互联网时代的弄潮儿。
    516次学习
  • 简单聊聊mysql8与网络通信
    简单聊聊mysql8与网络通信
    如有问题加微信:Le-studyg;在课程中,我们将首先介绍MySQL8的新特性,包括性能优化、安全增强、新数据类型等,帮助学生快速熟悉MySQL8的最新功能。接着,我们将深入解析MySQL的网络通信机制,包括协议、连接管理、数据传输等,让
    500次学习
  • JavaScript正则表达式基础与实战
    JavaScript正则表达式基础与实战
    在任何一门编程语言中,正则表达式,都是一项重要的知识,它提供了高效的字符串匹配与捕获机制,可以极大的简化程序设计。
    487次学习
  • 从零制作响应式网站—Grid布局
    从零制作响应式网站—Grid布局
    本系列教程将展示从零制作一个假想的网络科技公司官网,分为导航,轮播,关于我们,成功案例,服务流程,团队介绍,数据部分,公司动态,底部信息等内容区块。网站整体采用CSSGrid布局,支持响应式,有流畅过渡和展现动画。
    485次学习
查看更多
AI推荐
  • PubMedQA数据集详解:生物医学问答基准、功能与应用指南
    PubMedQA
    深入了解PubMedQA生物医学问答数据集,涵盖其核心功能、使用方法及在临床决策、药物研发等场景的应用,助力提升NLP模型性能。
    1018次使用
  • H2O EvalGPT:开源LLM大模型评估与排行榜工具
    H2O EvalGPT
    H2O EvalGPT是H2O.ai推出的开源LLM评估平台,提供详细的大模型性能排行榜、行业特定基准测试及A/B测试功能,助您快速选择最适合项目的高性能大语言模型。
    1020次使用
  • LMArena是什么?伯克利AI模型评估平台使用指南与功能解析
    LMArena
    LMArena是加州大学伯克利分校推出的AI模型匿名评测平台。通过盲测投票机制,用户可对比不同大模型回答并生成实时排行榜,助力开发者优化模型及用户选择最佳AI工具。
    1155次使用
  • 斯坦福HELM:大语言模型Holistic Evaluation整体评估框架详解
    HELM
    深入了解斯坦福推出的HELM(Holistic Evaluation of Language Models)大模型评测体系。本文解析其核心功能、安装配置步骤及应用场景,涵盖准确性、公平性、鲁棒性等多维度指标,助力开发者全面优化语言模型性能。
    1121次使用
  • MMBench详解:多模态大模型基准测试、功能特点与使用指南
    MMBench
    MMBench是由上海人工智能实验室等机构联合推出的多模态基准测试平台,提供细粒度能力评估、大规模数据集及VLMEvalKit工具。本文详细介绍其核心功能、安装使用方法及应用场景,助力开发者全面评估多模态模型性能。
    962次使用
微信登录更方便
  • 密码登录
  • 注册账号
登录即同意 用户协议 和 隐私政策
返回登录
  • 重置密码