趋近智
大师班
手动实现混合精度训练,小心地在FP32、FP16或BF16之间进行张量类型转换并管理损失缩放,可能既复杂又容易出错。幸运的是,像PyTorch这样的现代深度学习框架提供了内置工具来自动化此过程的大部分,通常被称为自动混合精度(AMP)。这些工具大大简化了混合精度技术的采用,使工程师能够将精力放在模型架构和训练动态上,而不是底层数值管理。
torch.cuda.amp)PyTorch 通过其 torch.cuda.amp 模块提供强有力的 AMP 支持(对于英特尔 GPU 是 torch.xpu.amp,对于苹果 M 系列芯片是 torch.mps.amp 等,尽管对于大型语言模型(LLM)来说,CUDA 最常用)。你将使用的两个主要组件是 autocast 上下文管理器和 GradScaler。
autocast 上下文管理器autocast 上下文管理器为你代码的选定区域启用自动类型转换,通常是模型的前向传播。启用后,它会识别可以安全高效地以更低精度(FP16 或 BF16)运行的操作,并自动相应地转换它们的输入。被认为对数值敏感或不从更低精度中获益的操作(例如通常配置为在FP32中运行的归约或归一化层)会保持完全精度。
这是你在模型前向传播周围通常如何使用 autocast 的方法:
import torch
# 假设模型、数据、损失函数已定义
# 假设使用 CUDA 设备
scaler = torch.cuda.amp.GradScaler() # 初始化 GradScaler(下一步解释)
optimizer = torch.optim.AdamW(model.parameters(), lr=1e-4)
# 训练循环迭代内的示例
for data, target in dataloader:
optimizer.zero_grad()
# 为前向传播启用自动类型转换
# 默认情况下,在 CUDA 上使用 FP16
with torch.cuda.amp.autocast():
output = model(data)
loss = loss_fn(output, target)
# 这里的损失是 FP32,但 backward() 中计算的梯度
# 将由 GradScaler 进行缩放
# 反向传播需要 scaler
scaler.scale(loss).backward()
# 优化器步骤需要 scaler
scaler.step(optimizer)
# 更新下一次迭代的缩放因子
scaler.update()
默认情况下,在CUDA设备上,autocast 使用FP16数据类型。如果需要,你可以显式指定数据类型,例如使用BF16:
# 启用 BFloat16 自动类型转换
with torch.cuda.amp.autocast(dtype=torch.bfloat16):
output = model(data)
loss = loss_fn(output, target)
# ... 循环的其余部分
仅使用 autocast 可以管理前向传播精度。然而,当使用FP16时,其小的可表示范围可能导致反向传播过程中梯度下溢(变为零)。这使得使用梯度缩放变得必要。
GradScalertorch.cuda.amp.GradScaler 在使用FP16时有助于防止梯度下溢。它通过在调用 backward() 前将损失值乘以一个缩放因子来发挥作用。这有效地放大了整个反向传播过程中的梯度,将小值推入FP16的可表示范围。在优化器更新权重之前,GradScaler 会对梯度进行反缩放(通过除以相同的缩放因子),使其恢复到原始值,从而确保权重更新正确。如果inf或NaN值在反缩放步骤中在梯度中检测到(如果缩放因子变得过大,可能会发生这种情况),则该批次的优化器步骤将被跳过,并且缩放因子会在后续迭代中被减小。反之,如果梯度在一段时间内保持稳定,scaler 会增加缩放因子,以充分使用更多的FP16范围。
典型使用模式包括:
GradScaler 实例。backward() 前,使用 scaler.scale(loss) 对损失进行缩放。scaler.step(optimizer) 对梯度进行反缩放并调用优化器步骤。此步骤会自动检查 inf/NaN 梯度,并在必要时跳过更新。scaler.update() 更新下一次迭代的缩放因子。前面的代码片段已经展示了这种集成方式。请注意,当使用 autocast 和BF16时,通常不需要 GradScaler,因为BF16的动态范围类似于FP32,使得梯度下溢的可能性大大降低。如果使用BF16,你通常可以省略 GradScaler 步骤:
# BF16 示例(通常不带 GradScaler)
optimizer.zero_grad(set_to_none=True)
# 使用 set_to_none=True 可能会节省内存
# 启用 BFloat16 自动类型转换
with torch.cuda.amp.autocast(dtype=torch.bfloat16):
output = model(data)
loss = loss_fn(output, target)
# 不带缩放的直接反向传播
loss.backward()
# 直接优化器步骤
optimizer.step()
然而,即使使用BF16,检查梯度范数发散可能仍然有用,并且一些实践者为了程序的稳定性仍然选择使用BF16搭配 GradScaler,尽管其主要原因(防止FP16下溢)没那么重要。
让我们改进典型的训练循环结构,以清楚地展示FP16训练的 AMP 组件:
import torch
import torch.cuda.amp as amp
# --- 初始化 ---
model = YourLargeModel().cuda()
optimizer = torch.optim.AdamW(model.parameters(), lr=1e-4)
loss_fn = torch.nn.CrossEntropyLoss()
dataloader = YourDataLoader() # 假设返回数据和目标张量
# 初始化 FP16 的 GradScaler
# 设置 enabled=False 可轻松禁用 AMP
scaler = amp.GradScaler(enabled=True)
# --- 训练循环 ---
num_epochs = 3
for epoch in range(num_epochs):
model.train()
for batch_idx, (data, target) in enumerate(dataloader):
data, target = data.cuda(), target.cuda()
optimizer.zero_grad()
# --- 使用 Autocast 的前向传播 ---
# 自动对符合条件的操作使用 FP16
with amp.autocast(enabled=True):
predictions = model(data)
loss = loss_fn(predictions, target)
# 这里的“损失”通常是 FP32,autocast 会转换中间操作
# --- 带缩放的反向传播 ---
# 缩放损失,计算梯度,并在内部处理梯度反缩放
scaler.scale(loss).backward()
# 可选:梯度裁剪(未缩放的梯度)
# scaler.unscale_(optimizer) # 在裁剪前对梯度进行反缩放
# torch.nn.utils.clip_grad_norm_(model.parameters(), max_norm=1.0)
# --- 优化器步骤 ---
# 检查 inf/NaN,如果梯度有效则执行优化器步骤
scaler.step(optimizer)
# --- 更新 Scaler ---
# 调整下一次迭代的缩放因子
scaler.update()
if batch_idx % 100 == 0:
print(f"Epoch {epoch}, Batch {batch_idx}, Loss: {loss.item():.4f}, "
f"Scale: {scaler.get_scale()}")
# --- 验证循环(通常只使用 autocast 完成)---
model.eval()
with torch.no_grad(): # 禁用梯度计算
for data_val, target_val in \
validation_dataloader: # 假设 validation_dataloader 存在
data_val, target_val = (data_val.cuda(),
target_val.cuda())
with amp.autocast(enabled=True):
val_output = model(data_val)
# 计算验证指标...
这种结构结合了FP16混合精度训练的必要的 autocast 和 GradScaler 步骤。enabled=True 标志允许你轻松地开启或关闭 AMP,以便进行比较或调试。请注意,可选的梯度裁剪步骤需要在裁剪之前对梯度进行反缩放,这由 scaler.unscale_ 处理。
AMP 旨在与 PyTorch 的分布式训练工具(torch.distributed.DistributedDataParallel)以及像 DeepSpeed 或 Fully Sharded Data Parallel (FSDP) 这样的更高层级库协同工作。通常,autocast 可以应用于每个副本的前向传播中,而 GradScaler 可以管理跨进程的缩放,这通常只需要对上面所示的标准 AMP 设置进行少量更改。像 DeepSpeed 这样的框架可能提供自己的集成混合精度处理,这可能会取代或封装 PyTorch 的原生 AMP,因此请查阅它们的具体文档。
通过使用框架对 AMP 的支持,你大大降低了实现混合精度训练的复杂性。这让你更容易地使用FP16和BF16等更低精度格式的速度和内存优势,使得在现有硬件上训练大型语言模型变得更可行。
这部分内容有帮助吗?
autocast 和 GradScaler。© 2026 ApX Machine Learning用心打造