autowzry-agent

AMP混合精度训练

日期: 2025-11-23 类型: 性能优化 影响范围: 配置管理、训练器


概述

实现了AMP(Automatic Mixed Precision)混合精度训练支持,允许模型使用FP16精度进行训练以提升速度并减少显存占用。PyTorch的AMP自动管理精度转换和梯度缩放,在保证训练稳定性的同时获得性能提升。


需求背景

原有训练使用FP32精度,在计算密集的卷积操作中存在性能瓶颈。特别是mini模型(500K参数)的训练速度受限于GPU计算能力。现代GPU(如RTX 20系列及以上)配备Tensor Core,对FP16运算有硬件加速支持,使用混合精度训练可以显著提升训练速度。

同时,项目的输入是图像数据,从0-255归一化到0-1的过程已经损失精度,使用FP16不会对模型性能产生明显影响。DQN配合Target Network、BatchNorm和保守学习率,在FP16下能保持良好的数值稳定性。


设计方案

核心思路

使用PyTorch的torch.cuda.amp模块实现自动混合精度训练:

默认策略

根据model_mode自动决定是否启用AMP:

启用条件

AMP最终启用需要满足三个条件的与运算:

  1. 配置文件中use_amp=true
  2. torch.cuda.is_available()返回True(有GPU)
  3. 模型和数据都在CUDA设备上

任一条件不满足,自动fallback到FP32训练。


代码修改

1. config/config.py

字段定义(第58-59行):

# Mixed Precision Training
use_amp: bool = None  # 是否使用AMP混合精度训练,None表示根据model_mode自动决定

from_yaml加载(第76-84行):

# Read model_mode first
model_mode = data.get('model', {}).get('mode', 'full')

# Read use_amp config, if None, decide based on model_mode
use_amp_config = data.get('training', {}).get('use_amp', None)
if use_amp_config is None:
    use_amp = (model_mode == 'mini')
else:
    use_amp = use_amp_config

save_yaml保存(第138行):

'use_amp': self.use_amp

关键设计点


2. config/agent.config.yaml

添加配置项(第25行):

use_amp: null  # 混合精度训练(FP16),null表示mini模式自动开启,full/high模式自动关闭

3. core/trainer.py

AMP初始化(第94-101行):

# AMP (Automatic Mixed Precision)
self.use_amp = config.use_amp and torch.cuda.is_available()
if self.use_amp:
    self.scaler = torch.cuda.amp.GradScaler()
    print(f"  Using AMP: True (FP16 mixed precision)")
else:
    self.scaler = None
    print(f"  Using AMP: False")

训练循环修改(第175-190行):

for batch in dataloader:
    self.optimizer.zero_grad()

    if self.use_amp:
        with torch.cuda.amp.autocast():
            loss = self._compute_loss(batch)
        self.scaler.scale(loss).backward()
        self.scaler.step(self.optimizer)
        self.scaler.update()
    else:
        loss = self._compute_loss(batch)
        loss.backward()
        self.optimizer.step()

    # Record batch loss
    epoch_losses.append(loss.item())

进度输出增强(第202-206行):

# Update target network periodically
if self.config.use_target_network and (epoch + 1) % self.config.target_update_epochs == 0:
    self.target_model.load_state_dict(self.model.state_dict())
    scale_info = f", Scale: {self.scaler.get_scale():.0f}" if self.use_amp else ""
    print(f"  Epoch {epoch+1}/{self.config.num_epochs}, Avg Loss: {avg_loss:.4f}{scale_info} [Target Network Updated]")
elif (epoch + 1) % 10 == 0 or epoch == 0:
    scale_info = f", Scale: {self.scaler.get_scale():.0f}" if self.use_amp else ""
    print(f"  Epoch {epoch+1}/{self.config.num_epochs}, Avg Loss: {avg_loss:.4f}{scale_info}")

关键设计点


无需修改的文件

以下文件自动适配,无需修改:


GradScaler工作原理

解决的问题

FP16的数值范围比FP32小,最小正数约6e-8。深度学习中的梯度常常小于这个值,导致underflow(变成0)。

解决方案

GradScaler通过以下步骤防止underflow:

  1. 放大loss: 反向传播前,loss乘以缩放因子(默认65536)
  2. 计算梯度: 梯度也被放大相同倍数,避免underflow
  3. 缩小梯度: 更新权重前,梯度除以缩放因子,恢复原始尺度
  4. 自适应调整: 检测到inf/nan时跳过更新并调整缩放因子

默认参数

GradScaler(
    init_scale=65536.0,      # 初始缩放因子(2^16)
    growth_factor=2.0,        # 增长倍数
    backoff_factor=0.5,       # 衰减倍数
    growth_interval=2000,     # 增长检查间隔
)

这些默认参数对大多数情况都适用,本项目直接使用。


预期效果

训练速度

在支持Tensor Core的GPU上:

显存占用

训练质量


监控与调试

正常训练输出

[Trainer] Init
  Device: cuda
  Using Target Network: True
  Using AMP: True (FP16 mixed precision)

[Train] Starting training...
  Epoch 1/320, Avg Loss: 0.1234, Scale: 65536
  Epoch 10/320, Avg Loss: 0.0567, Scale: 65536
  Epoch 50/320, Avg Loss: 0.0234, Scale: 131072 [Target Network Updated]

缩放因子变化

异常情况处理

loss为nan:

  1. 检查gradient scale是否持续下降
  2. 尝试降低学习率(1e-4 → 5e-5)
  3. 尝试降低init_scale(65536 → 32768)

训练很慢:

  1. 确认GPU支持FP16(查看CUDA capability)
  2. 检查batch_size是否太小
  3. 确认数据已在GPU上(避免CPU-GPU传输)

兼容性说明

向后兼容

数据兼容


适用性分析

为什么适合本项目

  1. 模型规模适中: mini模型500K参数,FP16加速明显
  2. 输入是图像: 对精度不敏感,FP16不影响特征提取
  3. 训练稳定: Target Network + BatchNorm + 保守学习率
  4. reward范围适中: -2到1之间,在FP16安全表示范围内
  5. 硬件支持: 现代GPU普遍支持Tensor Core

潜在风险

  1. Dueling DQN的advantage均值计算: 理论上FP16可能有精度损失,但autocast会自动处理
  2. 小reward值(0.01): GradScaler的缩放机制可以防止underflow
  3. 不同GPU表现差异: 旧GPU可能不支持FP16加速

设计决策

为什么选择AMP而非手动FP16

手动FP16:

model.half()
data = data.half()
# 需要手动处理loss scaling、数值稳定性等

AMP:

with torch.cuda.amp.autocast():
    loss = model(data)
scaler.scale(loss).backward()

选择AMP的原因:

  1. 实现简单: 只需3处代码修改
  2. 数值稳定: 自动选择FP32/FP16,自动缩放梯度
  3. 维护性好: 代码侵入性小,易于开关
  4. 性能相当: 与手动FP16效果一致

为什么mini模式默认启用

计算逻辑:

决策:


使用建议

新训练项目

  1. mini模式: 默认启用,直接使用
  2. full/high模式: 如果显存充足,可以显式设置use_amp: true
  3. 监控训练: 观察gradient scale变化,确认稳定性

性能优化

  1. 增大batch_size: FP16显存占用减少,可以从32增至64
  2. 观察加速比: 在实际硬件上测试训练时间
  3. 对比模型质量: 对比FP32和FP16训练的模型在battle中的表现

调试问题

  1. 出现nan: 降低学习率或init_scale
  2. 无加速效果: 检查GPU型号和CUDA版本
  3. 频繁跳过步: 检查gradient scale,可能需要调整参数

文档更新

已更新以下文档:

  1. docs/design/ARCHITECTURE.md - 更新Config属性、Trainer初始化、train方法和属性说明
  2. docs/logs/development_log.md - 添加本次开发日志索引

总结

本次开发成功实现了AMP混合精度训练支持,通过最小化的代码改动(仅3个文件,约20行代码)获得了显著的性能提升潜力。设计简洁、易于维护,配置驱动的启用策略兼顾了易用性和灵活性。

AMP的引入为项目带来了:

这一优化将大幅提升autowzry-agent项目的训练效率,特别是在mini模式下进行快速原型验证时。