卷0 · 基础篇
0.4 训练工程常识:混合精度、断点续训、调试与监控
展开本章目录与同卷章节
1. 混合精度(AMP):不只是”换个 dtype”2. 梯度裁剪:LLM 训练的保险丝3. EMA:训练一份,评测另一份4. 断点续训(长训练的命根子)5. 可复现性三件套6. 监控清单:训练时到底看什么7. 调试的标准动作:单 batch 过拟合8. 训练时长估算(租卡前必做)9. 自测题10. 动手作业(把 0.2 的脚本升级为”专业版”)11. 延伸资源同卷章节0.1 数学基础:线性代数 · 概率 · 优化(深度学习视角三合一)0.2 Python 与 PyTorch 实战:从装环境到手写训练循环0.3 神经网络与反向传播:从零实现(micrograd 路线)0.4 训练工程常识:混合精度、断点续训、调试与监控0.5 Git / Linux / 远程开发生存包本章目标:把”能跑起来”和”跑得专业”之间的差距补上。这些是 AI 代码助手最容易漏掉、而实验失败时最烧时间的部分。学完标准:你的训练脚本具备断点续训、梯度裁剪、EMA、完整日志四件套,并且知道”训练出问题时的标准排查顺序”。
1. 混合精度(AMP):不只是”换个 dtype”
原理拆解:训练时前向和反向用低精度(bf16/fp16)加速,但主权重和优化器状态保持 fp32:
- 前向在
autocast里:框架自动把 matmul/conv 换成低精度,而 softmax、log、norm 统计等对精度敏感的算子保持 fp32(框架替你做了,但自己手写 loss 时要注意:对 logits 求对数前先.float()是保命习惯); - fp32 主权重:梯度更新量可能小于低精度的最小可表示数,直接在 fp16 权重上累加会被”吞掉”,所以低精度只是”计算现场”,真身永远 fp32;
- GradScaler 只在 fp16 下需要:fp16 指数位只有 5 位,小梯度会下溢为 0,所以先放大 loss(乘个 2^16 级别的数),反向后再缩小。bf16 指数位与 fp32 相同,动态范围大,完全不需要 scaler——这就是现代 GPU 上 bf16 一统江湖的原因。
回报:速度约 2 倍、显存大头(激活)减半。纪律:所有训练默认开 AMP;报 NaN 时第一嫌疑人是精度/学习率。
2. 梯度裁剪:LLM 训练的保险丝
torch.nn.utils.clip_grad_norm_(model.parameters(), max_norm=1.0)
放在 backward() 之后、step() 之前:若全体梯度的 L2 范数超过阈值,按比例整体缩小。动机:训练中偶发 loss spike(坏数据、精度抖动),spike 往往伴随梯度爆炸,一步就把权重打飞。NLP/LLM 训练默认裁剪到 1.0;CV 分类任务可不加。日志里同时记录裁剪前的 grad norm——它的突变是最好的异常报警器。
3. EMA:训练一份,评测另一份
指数滑动平均:维护权重影子副本 ( 常取 0.999)。理论依据:SGD 最终解附近来回震荡,EMA 是”震荡轨迹的平均”,落在更平坦的盆里,评测/部署普遍好零点几个点。LLM 和扩散模型训练标配(checkpoint 里经常同时存 raw 和 ema 两份权重)。实现可以直接用 torch.optim.swa_utils.AveragedModel,或几十行手写。
4. 断点续训(长训练的命根子)
只存 model.state_dict() 是新手做法。完整的 checkpoint 至少包含五样东西——否则中断后训练无法严格复原:
ckpt = {
"model": model.state_dict(),
"opt": opt.state_dict(), # AdamW 的动量状态丢了 = 训练"失忆"
"sched": sched.state_dict(), # 学习率进度
"epoch": epoch,
"rng": torch.get_rng_state(), # 随机状态(数据增强、dropout)
}
torch.save(ckpt, f"ckpt_ep{epoch}.pth")
# 恢复
model.load_state_dict(ckpt["model"]); opt.load_state_dict(ckpt["opt"])
sched.load_state_dict(ckpt["sched"])
for ep in range(ckpt["epoch"] + 1, total): ... # 从断点继续
工程惯例:定期存(每 N epoch)+ 只留最近 2 份 + 单独留最优 1 份,硬盘会被写爆的。LoRA 微调时额外注意:get_peft_model 后的模型要存 peft 格式,别直接存完整 state_dict(10GB 白送给硬盘)。
5. 可复现性三件套
- 全链路 seed:
random、numpy、torch、torch.cuda各设一次(封装成set_seed(s)函数); - 记录配置:git commit hash + 完整超参 dump 成 json/yaml 随 checkpoint 一起存——三个月后你会感谢自己;
- 接受不完全复现:cuDNN 非确定性算子(
torch.backends.cudnn.deterministic=True可关掉但速度损失大)。原则:复现 seed 内逐位一致不追求,追求同配置下结果稳定(多次运行指标方差小于效应量)。
6. 监控清单:训练时到底看什么
用 wandb(或 tensorboard)记录,缺一不可:
| 量 | 看什么 | 异常信号 |
|---|---|---|
| loss(原始+平滑) | 总趋势 | spike → 查 lr/坏数据;平台 → 查容量/瓶颈 |
| grad norm | 稳定范围 | 骤升 → 爆炸前兆(clip 阈值报警);趋 0 → 死了 |
| lr | 当前值 | 和 schedule 对照,warmup 没生效是常见 bug |
| 显存占用 | 峰值 | 接近上限 → OOM 风险(卷7 详述) |
| 吞吐 samples/s | 稳定性 | 骤降 → DataLoader 瓶颈(num_workers、IO) |
训练远程挂着时的正确姿势:loss spike 报警(wandb alert)+ 每小时自动 checkpoint,而不是盯屏。
7. 调试的标准动作:单 batch 过拟合
任何新模型/新数据/新 loss,跑全量训练前先做这一步——DL 调试的第一定律:
x, y = next(iter(train_loader)) # 只取一个 batch
for i in range(200):
loss = train_step(model, x, y) # 反复喂同一个 batch
print(loss) # 必须趋近 0
- loss 降不到接近 0 → 代码有 bug(标签错位、loss 写错、数据没对齐、梯度断了),不是模型能力问题。先修代码再谈调参;
- 能过拟合单 batch 但全量数据学不动 → 数据量/容量/增强过度的问题,方向是调参而非 debug。
配套两招:① 训练前可视化 8 个样本及其标签(眼睛看,30 秒抓住标签错位);② 先用最小模型 + 5% 数据跑通全流程(含评估和 checkpoint),再放大。顺序错了就是”训了 8 小时才发现标签错位”的悲剧。
8. 训练时长估算(租卡前必做)
例:LoRA 微调 7B,1 万条样本、3 epoch、batch 4、4090 实测约 0.5 samples/s → 10000×3/(4×0.5×3600) ≈ 4.2 小时 ≈ 10 元。下单前先跑 50 step 实测吞吐再乘,不要拍脑袋。
9. 自测题
- AMP 里什么是 fp32、什么是 bf16?为什么主权重必须 fp32?(激活与计算用 bf16;权重/优化器状态 fp32;低精度累加小更新会被吞)
- bf16 为什么不需要 GradScaler?(指数位 8 位同 fp32,动态范围大,小梯度不下溢)
- GradScaler 只在哪一步起作用?(backward 前 scale loss,step 前 unscale 梯度)
- 断点续训只存模型权重会怎样?(优化器动量清零、lr 进度重置、随机流错位——训练”失忆”且结果不可比)
- 单 batch 过拟合降不到 0,说明什么?(代码有 bug,按标签→loss→数据对齐→梯度链顺序排查)
- grad norm 趋近 0 且 loss 不降,两种可能?(学习率太小/梯度断了(detach 误用);或模型输出饱和)
- 吞吐骤降但 GPU 利用率低,先查什么?(数据加载:num_workers、磁盘 IO、pin_memory)
10. 动手作业(把 0.2 的脚本升级为”专业版”)
给 CIFAR 脚本加装四件套,每件独立 commit:
set_seed函数 + 配置 dump 成 json;- 完整 checkpoint(第 4 节五要素)+ 断点续训自测(kill 掉重启,loss 曲线应无缝衔接);
clip_grad_norm_+ wandb 记录 grad norm;- 单 batch 过拟合测试脚本
debug_overfit.py,以及一个故意注入标签错误的版本,观察它如何暴露问题。
11. 延伸资源
- Karpathy 博客《A Recipe for Training Neural Networks》——本章的加长原版,必读
- PyTorch 官方文档 AMP / autocast 页——API 细节查询
- wandb 官方 quickstart——10 分钟接入
