区分微批量、反向传播与更新
微批量是某个副本上一次前向传播所处理的一组样本。反向传播计算它对梯度的贡献。优化器更新利用当前可用的梯度来修改参数。采用累积时,在这一次更新之前会有多次前向/反向传播;在此期间参数保持不变。
在 PyTorch 中,梯度会累积到为此预留的张量里。因此,在每个微批量之后清除梯度,就会抵消预期的累积效果。反过来,如果在两组之间忘记将它们清零,上一次更新中的样本就会产生贡献。
请明确定义你的记录单位:微批量编号、优化器更新次数、已见样本数或 token 数。单说“step”是有歧义的。如果某个实验的横轴代表八倍于其他实验的样本数,那么损失曲线就无法正确比较。
技术来源: PyTorch —— 梯度累积与清零
计算有效批量时不要重复计算 GPU
设 m 为每个微批量在每个副本中的样本数,A 为累积的微批量数,D 为数据并行副本数。如果这些大小恒定且样本被正确分配,那么参与一次全局更新的样本数为 m × A × D。
系数 D 不一定指机器上的所有显卡。通过张量并行或流水线并行共享同一个模型的 GPU,并不会因此变成同等数量的数据副本。请记录实际配置的组,而不只是商品描述的批量数量。
算术示例:每个微批次两个样本,八次累积和两个副本,得到每次全局更新 32 个样本。每个副本在该组中处理十六个样本。表格比较的是计数,并不对它们的内存或速度进行排名。
| m | A | D | 每次全局更新的样本数 |
|---|---|---|---|
| 2 | 8 | 2 | 32 |
| 1 | 16 | 2 | 32 |
| 4 | 4 | 2 | 32 |
| 2 | 8 | 1 | 16 |
技术来源: PyTorch — 混合精度下的有效批次与累积 · PyTorch — DistributedDataParallel 副本的行为
按实际参与计算元素归一化损失
对于包含相同数量相关元素的微批次的平均损失,将每个贡献除以 A 即可得到该组的平均值。此规则假设框架尚未执行该归一化。若使用已处理累积的工具,在手动添加除法前请先查阅其约定。
对于按 token 的平均损失,不同长度会改变分母。应将损失之和除以该组实际参与监督的 token,排除 padding 和被忽略的位置。微批次平均值的平均通常并不会给出相同的目标。
理论示例:一个微批次有 512 个受监督 token,平均损失为 2;另一个有 1 536 个,平均值为 4。加权平均为 (512 × 2 + 1 536 × 4) ÷ 2 048 = 3.5。未加权平均为 3,对小样本组赋予了过高权重。这些数值仅用于说明计算。
组织一个完整的累积组
首先确定该组的边界及其分母。对每个微批次,计算输出、归一化损失和反向传播,且不进行中间更新。释放不再需要的输出;将损失连同其计算图保存在列表中可能延长分配的生命周期。
在最后一个贡献之后,对完整梯度执行预定操作,然后进行更新。随后为下一组重置梯度。如果遍历结束时少于 A 个微批次,请明确选择以真实分母处理该不完整组,或将其丢弃;记录涉及的样本。
在使用 GradScaler 的混合精度下,缩放因子在累积期间保持不变。实际的重新缩放以及可能的裁剪在贡献之后进行;缩放器的更新紧随步进尝试。对非有限值的检查可能阻止参数被修改。
调度器应遵循你的循环所声明的单位。若它按优化器更新来定义,则每个微批次都调用它会改变日程。当你的系统可能跳过某些步进时,请分别记录尝试次数与实际应用的更新。
技术来源: PyTorch — autograd 计算图与为 backward 保留的张量 · PyTorch — 累积、unscale、裁剪与 GradScaler
在多 GPU 下,检查归约与样本分配
DistributedDataParallel 在各副本之间同步梯度。在其通常行为中,归约会取它们的平均值;因此,本地求和的损失与本地平均的损失并不具有相同的尺度。当各副本的 token 数不同时,全局分母与该归约必须一并考虑。
检查实际处理的 ID:无意中在所有显卡上重复相同的样本,并不会使组内的信息量同比例增加。为了推迟中间的通信,可在最终同步之前的微批次上使用 no_sync;其上下文也必须覆盖前向传播。
不要将此规则套用到所有分布式系统。状态分片、流水线、通信钩子和框架都可能改变实际执行的操作。请先从你的工具所支持的配置入手,然后在每个副本上核对一个完整的组。
为什么相同的有效批次并不能保证相同的体验
等式 m × A × D 是一种计数。要还原大 batch 的梯度,尤其需要权重正确的贡献、组内参数状态保持一致,以及兼容这种分解的运算。数值上的接近要用适当的容差来验证;它不能仅由乘积推出。
BatchNorm 会根据其前向传播的输入计算统计量:多个小 microbatch 呈现给它的分组与一个大 batch 不同。随机操作、计算顺序和舍入也可能有差异。不要承诺最终权重逐位完全一致。
改变全局 batch 也可能在相同的样本数下改变更新次数。提前确定比较轴和质量规则。不要在未记录这些新假设的情况下,同时改变学习率、scheduler 和时长。
控制内存并决定后续方向
对包含反向传播和首次更新的组进行监测,然后再监测后续各组。仅前向传播成功,并不能验证优化器创建的梯度或状态。计数器必须保持其按 device 划分的范围。内存档案提供了基线峰值的读取方法。
如果某一组失败,就减小 microbatch 并重新计算 A,以便在这一选择仍然合适时保持目标的有效 batch。这一修改既不能保证峰值按比例下降,也不能保证时长更优。如果权重或状态占主导,仅靠累积可能不够。
在开展一轮实验前,先在一个受控小数据集上验证一次更新:相同的样本、加权的损失、有限的梯度、组边界和步数。然后用选定的协议评估质量。可下载档案中的小型推理 MLP 并不执行这套训练配方;它不能替代这一验证。
你的最终记录汇总 m、A、D、被监督的 tokens、精度、归一化、最后一组的处理方式以及观察到的峰值。然后再回到配置选择和实验预算,把准备工作、试验和真正被接受的结果区分开来。
- 损失异常小:检查是否因累积而重复相除。
- 结果随切分方式变化:检查被忽略的 tokens 和平均值的平均。
- 累积没有效果:检查 zero_grad 和 optimizer.step。
- 内存持续增长:查找 microbatch 之间保留的引用。
- 时间表偏移:区分反向传播与更新。
技术来源: Hugging Face — 一次训练的内存要点
实际问题
累积十六个 microbatch 会把内存乘以十六吗?
不一定:各项贡献会被依次处理。梯度会持续保留,而无用的激活值可以被释放。不过峰值仍取决于模型、所保留的引用以及优化器状态;请对完整分组进行实测。
两块 GPU 总能将有效 batch 翻倍吗?
只有当这些 GPU 作为所述 microbatch 的两个数据副本参与时才是如此。共享同一模型的卡并不会自动构成两个副本。请检查分布式分组和处理过的样本。
我可以把所有损失除以累积次数吗?
这条简单规则对应于权重相等的 microbatch,且框架未处理归一化的情况。当被监督的 tokens 数量可变或最后一组不完整时,请使用目标的真实分母。