W0702 / 聚焦微调实战演练
训练可观测与动态调参
建立从代码、数据、超参到 Loss、梯度、显存和吞吐的实验谱系,用证据定位训练事故。
- 建议时长
- 7–9 小时
- 难度
- 工程进阶
- 课程位置
- 7 / 12
学完这一课,你应该能:
- 能定义一条可复现训练运行的完整身份
- 能同时监控优化、数据与系统指标
- 能用时间线定位 OOM、Loss spike 和吞吐下降
- 能设计安全的 checkpoint 与恢复策略
1. 一次实验的身份是什么
可复现实验不是一个 run name,而是:
run identity =
code SHA
+ dependency lock
+ dataset hash
+ model revision
+ config
+ random seed
+ hardware
+ precision
任意一项缺失,都可能得到不同结果。final-v2-good 这种命名不能回答使用了哪个数据和基座。
2. 四类指标必须同时存在
优化指标
- train/eval loss
- learning rate
- global/per-layer gradient norm
- parameter norm、update/weight ratio
数据指标
- tokens/batch 与有效监督 token
- 截断率、padding 比例
- 数据读取等待
- 当前 batch 的数据 shard 与样本 id
系统指标
- step time、tokens/s
- GPU utilization、SM occupancy(需要时)
- allocated/reserved VRAM
- CPU、磁盘、网络与 dataloader wait
质量指标
- 任务 metric
- 黄金集错误分类
- 固定 prompt 的生成样例
Loss 下降只说明优化目标下降,不保证验证质量、事实性或业务行为改善。
3. 建立最小实验记录
import os, subprocess, torch
def git_sha():
return subprocess.check_output(
["git", "rev-parse", "HEAD"], text=True
).strip()
run_config = {
**training_args,
"git_sha": git_sha(),
"dataset_hash": dataset_hash,
"model_revision": model_revision,
"torch_version": torch.__version__,
"cuda_version": torch.version.cuda,
"gpu": torch.cuda.get_device_name(0),
}
tracker.init(project="ai-dev", config=run_config)
制品也要版本化:训练配置、数据卡、checkpoint、评测输出和失败样例。指标图没有对应权重,就不能重放结果。
4. 为什么 Loss spike 要看时间线
Loss 在 step 3200 突升,可能来自:
- 学习率调度变化
- 梯度累积边界错误
- 特殊数据 batch
- 混合精度 overflow
- 从 checkpoint 恢复后优化器状态丢失
- 多卡数据或梯度不同步
正确做法是把这些事件放在同一时间轴:
tracker.log({
"train/loss": loss.item(),
"train/grad_norm": grad_norm,
"train/lr": scheduler.get_last_lr()[0],
"data/supervised_tokens": valid_labels,
"system/allocated_gb": torch.cuda.memory_allocated() / 2**30,
"system/reserved_gb": torch.cuda.memory_reserved() / 2**30,
"system/tokens_per_sec": tokens / step_seconds,
}, step=global_step)
同时记录 sample_ids 到本地审计日志。若异常能随同一 batch 重现,数据原因的证据就很强。
5. OOM 不能只看模型大小
峰值显存近似包含:
weights
+ gradients
+ optimizer states
+ saved activations
+ temporary kernels/buffers
+ allocator reserved/fragmentation
排查顺序:
- 记录 OOM 前序列长度与 batch。
- 区分 allocated 与 reserved。
- 减小 micro-batch,保持 gradient accumulation,验证是否激活主导。
- 开启 gradient checkpointing,比较显存与 step time。
- 检查评测/生成是否忘记 inference mode。
- 不要把
empty_cache()当根治;它不释放仍被引用的 tensor。
6. 吞吐下降的分解
step_time =
data_wait
+ host_to_device
+ forward
+ backward
+ optimizer
+ communication
+ checkpoint/eval
GPU utilization 低不等于模型小。可能是 tokenizer 在主进程、数据存储慢、频繁打印、评测过密或多卡通信等待。
先测空 dataloader 的模型吞吐,再测完整流水线;再逐项增加 logging、evaluation、checkpoint。分层基线比盯一个 utilization 百分比更可靠。
7. Checkpoint 要能真正恢复
完整恢复通常需要:
- model/adapter state
- optimizer state
- scheduler state
- scaler state
- global step/epoch
- RNG state
- dataloader sampler state
只加载权重再继续训练,不等于无缝恢复。学习率、Adam 动量和数据位置都可能重置。
做一次恢复演练:训练 100 步保存,从 checkpoint 再跑 20 步;与不中断的 120 步比较 Loss 与权重差异。确定性限制要写进报告。
8. 训练事故实验
主动制造:
- OOM:插入超长样本。
- Loss spike:把一批标签错位或学习率提高 20 倍。
- 吞吐下降:在每步同步保存大文件。
要求不看代码修改记录,只根据指标时间线提出根因候选,再用实验验证。交付:
- 事故时间线
- 根因证据
- 临时处置
- 永久修复
- 防止复发的自动检查
9. 练习与答案提示
- allocated 低而 reserved 高说明什么?**答:**allocator 保留或碎片,不能直接等同活跃 tensor。
- eval loss 降、任务分数不升可能吗?**答:**可能,优化目标与任务指标不完全一致。
- 为什么记录 sample id?**答:**把异常与具体数据关联并重放。
- 只保存 LoRA adapter 够吗?**答:**发布可能够,训练恢复还需优化器、调度器等状态。
- 梯度 checkpointing 的代价?**答:**反向时重算前向,省显存但增加计算时间。