01 · 目标批次装不下时,分几次计算
假设希望一次参数更新使用8条样本,但设备一次只能承受2条样本的激活。可以在同一参数版本下依次处理4个小批次,每次前向和反向都把梯度加到同一缓冲里,最后只更新一次参数。
这里的小批次称为 microbatch。一次更新所涵盖的数据是有效批量。梯度累积通常减少同时需要保留的激活,而不是减少8条样本需要的总前向/反向计算,也不会自动缩小权重、优化器状态或梯度缓冲。
02 · 把一次更新的完整边界画清楚
开始一个更新窗口:清空梯度
microbatch 1:前向 → 计算Loss → 反向 → 累加梯度
microbatch 2:前向 → 计算Loss → 反向 → 累加梯度
microbatch 3:前向 → 计算Loss → 反向 → 累加梯度
microbatch 4:前向 → 计算Loss → 反向 → 累加梯度
全部贡献就绪 → 必要的梯度同步 → unscale/检查/裁剪 → 更新参数
下一更新窗口再清空梯度这4次反向使用相同的参数;若每次都 optimizer.step,就是4次独立更新,已经不是同一个“大批次分开算”。通常也应在反向后释放本次不再需要的计算图,避免把所有 microbatch 的激活都攒到最后一次反向。
03 · 有效批量乘哪些维度
等大小、等积累轮次的常见情况:全局样本批量 = 每个 DP 副本的 microbatch 样本数 × 每次更新的累积次数 × DP 副本数。例如每副本2条、累积4次、DP=2,合计16条。
| 配置 | 一次更新的全局样本数 | 为什么 |
|---|---|---|
| 单副本,2条×4次 | 8 | 同一模型依次看到8条。 |
| DP=2,每副本2条×4次 | 16 | 两个副本处理不同样本。 |
| 再加TP=2、PP=2,总8卡 | 仍是16 | TP/PP协作处理这些样本,不额外产生新数据。 |
LLM 训练还常按有效 token 数定义目标归一化:样本长度、padding和mask不同,16条样本可能不是固定数量的训练 token。更改累积次数或DP度还会改变更新频率、调度器进度与学习率策略,不能仅按卡数机械缩放。
04 · 为什么有时除以累积次数仍然算错
如果各小批次的 Loss 都是相同数量有效项的平均值,可把每次平均 Loss 除以累积次数。但当有效 token 数不同,需要按全局目标的分母加权。
| microbatch | 有效token数 | 本批次平均Loss | 加权贡献 |
|---|---|---|---|
| A | 2 | 1 | 2/8×1=0.25 |
| B | 6 | 3 | 6/8×3=2.25 |
正确的全体 token 平均 Loss 是 (2×1+6×3)/8=2.5;简单把两个平均值各除以2,得到2.0,相当于人为让两批次权重相同。梯度也应对同一个目标作相应加权。
多 DP 副本的有效 token 数不等时,需要统一分母,并考虑梯度归约究竟求和还是平均。例如若普通 DDP 最终把梯度除以 D,可让每个副本的本地 Loss 总和乘 D/N_global,从而在归约后得到全体有效 token 的平均梯度。这是归一化推导,不是所有框架都应照搬的代码。
05 · DDP 为什么可以只在最后一次同步
普通复制式 DDP 的副本各自累积本地梯度。如果每个 microbatch 反向都做归约,会增加重复同步轮次。DDP 的 no_sync 可以让前几轮先保留本地贡献,在最后一轮反向同步累积结果。它减少这些梯度同步,不代表张量并行、流水线或FSDP参数通信也被关闭。
from contextlib import nullcontext
optimizer.zero_grad(set_to_none=True)
for i, batch in enumerate(equal_size_microbatches):
sync_context = nullcontext() if i == K - 1 else ddp_model.no_sync()
with sync_context:
# forward 也必须位于 no_sync 的作用域内
loss = mean_loss(ddp_model(batch), batch.targets)
(loss / K).backward()
optimizer.step()若使用 AMP,完成全部累积后再按配方 unscale、检查与裁剪,最后调用 scaler.step/update。FSDP、ZeRO或自定义通信钩子有不同语义和内存行为,不能只看到 no_sync 这个名称就套用普通复制式DDP的容量结论。
06 · 分批累积与大批次何时等价
当损失可按样本或 token 相加、参数在窗口内不变、归一化一致时,梯度累积可对应一次大批次梯度。实际还会有浮点求和顺序、dropout随机数、依赖批次统计的层,以及按批次构造的对比损失等差异。需要按模型判断。
- 把梯度裁剪放在完整目标梯度就绪后;每小批次各裁剪一次通常不是同一更新。
- 记录最后不足K批的窗口,并按实际贡献处理,不能默认分母仍为K。
- DP各rank保持兼容的同步边界,避免某个rank提前结束而其他rank等待集合通信。
- 测试学习率调度、Adam状态与weight decay的推进频率;它们通常随参数更新而非每个microbatch推进。
不要仅比较一条 Loss 曲线就宣布严格等价。可先固定样本和参数,比较合并批次与累积批次的梯度、一次更新后的权重,然后检查真实训练行为。
07 · 一个可运行的梯度对照
下方标准库例子用线性回归和平方损失推导梯度,把5条样本切为2条与3条两批。它对比完整批次、正确按样本数加权累积,以及错误地平均两份梯度,还展示每批都更新会得到不同结果。它不依赖自动微分或GPU。
它帮助检查数学目标,真正框架还需要处理梯度缓冲、混合精度、通信后端和训练状态。流水线调度也会把一个更新窗口分成多个microbatch;相同的参数版本和梯度归一化要求仍适用。
08 · 收益、代价和跨平台条件
| 可能的收益 | 需要观察的代价 |
|---|---|
| 降低同时驻留的激活,支持目标有效批量 | 小矩阵利用率可能较低,更多轮次有启动开销。 |
| 按支持的实现减少DP梯度同步 | 最后同步的暴露时间、通信分桶与重叠可能改变。 |
| 调整更新频率与数据组织 | 全局批量变化可能影响收敛;不等于无条件训练加速。 |
在 NVIDIA 或昇腾上都先确认所用训练框架的归一化、梯度通信和分片模式。DDP示意是PyTorch接口逻辑,不是NCCL/HCCL的配置指令。设备通信实现、混合精度和支持组合需要按安装版本核对。
测试应同时记录峰值内存、一次更新耗时、每秒有效token与目标质量。只比较每个小批次变短,会漏掉一个窗口需执行更多次的事实。
09 · 用四个问题检查训练边界
- 累积期间为什么不能每批次都更新参数?
- TP和PP度增加,为什么不直接乘进有效样本批量?
- 长度不同的小批次,为什么loss/K可能不正确?
- no_sync包住backward而没包住forward,为什么不符合DDP接口要求?
流程位置与关联方法
这个方法可能出现在多个位置;计算线包含模型计算与算子实现,执行线包含运行时、内存和通信。
资料与核查范围
原理依据以原始论文和官方文档为主;算例与时间线是教学推导。文档核查日期:2026-10-10。引用 main / latest 文档时,实际接口以安装版本为准。
代码为接入片段或教学伪代码;本页没有声称完成 NVIDIA / 昇腾硬件实测。