显存限制的是一次前向/反向的 micro-batch,不是统计意义上的 global batch。梯度累积用顺序执行的 micro-batch 把二者拆开,代价是 loss 归约口径、梯度同步位置和 optimizer step 边界都要重新对齐。这篇页面把 Megatron、PyTorch DDP/FSDP、DeepSpeed 的实现和 Goyal、Smith、Megatron SC21 的实测放在同一张账本上。
账本本身是乘法,难的是口径。 global_batch_size = micro_batch_size × data_parallel_size × gradient_accumulation_steps;Megatron 的配置文档原话是 “local batch size times data parallel size times number of micro batches”(megatron/training/config/training_config.py:13-14)。
累积的是梯度,不是 loss。 每个 micro-batch 的 backward() 把梯度加到同一组 .grad 上;no_sync 只是把 all-reduce 推到累积边界。DDP 在退出 no_sync 后的第一次前向/反向同步,FSDP 在累积期间保留完整梯度,Megatron 则在 FSDPv2,或 FSDPv1/DDP 且开启 overlap_grad_reduce 时安装 no_sync。
变长序列会把 mean-of-means 和 token-level mean 分开。 默认路径是「micro-batch 内按 token 平均 → 再按 micro-batch 平均」;calculate_per_token_loss=True 则把整个 global batch 的非 padding token 作为分母。两者只在各 micro-batch token 数相等时相同。
读完这节应知道:global batch 是优化与统计单位,micro-batch 是显存与执行单位;两者之间隔着 DP 和累积步数。
训练一步要存的不只是参数,还有前向激活、反向中间量和梯度。对固定模型,激活峰值随一次前向的样本数增长;参数、梯度和 optimizer state 则与 micro-batch 大小基本无关。于是「一步看多少样本」和「一次放进 GPU 多少样本」必须拆成两个变量。
Megatron 的训练配置文档把它写成 “local batch size times data parallel size times number of micro batches”(megatron/training/config/training_config.py:13-14)。
三个乘数买的东西不同。micro_batch_size 决定单次前向的激活峰值,也决定 BN、dropout 等按 micro-batch 统计的层看到多少样本;data_parallel_size 决定模型副本数和集合通信宽度;gradient_accumulation_steps 决定一个 optimizer step 前串行跑多少个 micro-batch。把 GBS 固定在 128,可以让 MBS=4、DP=8、GA=4,也可以让 MBS=8、DP=16、GA=1;样本数相同,显存、通信次数和每卡的处理节奏都不同。
直接决定激活峰值。调小它最省显存,但单卡矩阵乘的尺寸变小,GPU 利用率可能下降;BN 的 per-worker 统计也跟着变。
每张卡一份完整模型(TP/PP 之外),梯度在 DP 组内归约。增加 DP 能提高吞吐,但集合通信量和副本显存不会消失。
前向/反向依次执行,激活只保留当前 micro-batch;梯度在缓冲区里累加。它省激活,不省参数、梯度和 optimizer state。
因此「把 batch 调大」不是单一动作。减小 batch 会改变梯度噪声和优化步数;增加 DP 需要更多副本和带宽;增加 GA 则把并行时间换成串行时间。三者的账必须在同一个 GBS 下对齐,否则训练曲线和吞吐都不可比。
读完这节应知道:三种方案都能让模型「跑起来」,但它们改变的是不同资源,不能互相替代。
显存随 MBS 下降,代价是每步梯度更 noisy、达到同样样本数需要更多 optimizer step。Goyal 的 baseline 是 256 张图(8 GPUs × 32 images/worker),Smith 的原始 CIFAR-10 schedule 用 batch 128、约 80,000 次参数更新(Smith §5.2)。
每次只前向/反向一个 micro-batch,梯度加到同一组 .grad;累积到边界才 step。DeepSpeed 在 backward() 里先做 loss / gradient_accumulation_steps(默认 scale_wrt_gas=True,deepspeed/runtime/engine.py:3444),PyTorch 的 no_sync 则把梯度同步推迟到退出上下文后的第一次前向/反向(torch/nn/parallel/distributed.py:1700-1702)。
DP 复制模型、按数据切分;TP/PP/CP 把层、流水线阶段或序列切开。Megatron 里 batch_parallel_size = world_size / (TP × PP × CP)(megatron/training/arguments.py:494-499),它才是账本里乘 GBS 的「独立数据分区数」。TP/PP/CP 不会额外乘一个 GBS 因子;PP 的 microbatch 计数就是 GA 本身,见 §07。
表 1 · 三种方案改的是不同资源(定性对照,不含实测数字)
| 方案 | 降激活峰值 | 降参数/优化器内存 | 对 GBS 的影响 | 主要代价 |
|---|---|---|---|---|
| 减小 MBS | 是 | 否 | 变小 | 梯度噪声上升、optimizer step 变多 |
| 梯度累积 | 是 | 否 | 可保持不变 | 串行时间、loss 归约与同步边界 |
| DP | 否(每卡一份) | 否 | × DP | 梯度 all-reduce / reduce-scatter 带宽 |
| TP / PP / CP | 是(切模型或序列) | 是 | 不乘 GBS | 集合通信、流水线气泡、实现复杂度 |
一个常见混淆是把「PP 的 microbatch」当成第二个 gradient accumulation。Megatron 的 pipeline schedule 收到的 num_microbatches 就是 get_num_microbatches()(megatron/training/training.py:3574-3579;scheduled_num_microbatches 由 3460 赋值),它同时是 GA 计数;PP 只改变这些 micro-batch 在流水线中的调度方式,不额外乘一个 PP 因子。
读完这节应知道:no_sync 省掉的是每个 micro-batch 的集合通信,不是梯度显存;loss 的除法位置决定变长序列的加权。
.grad 里相加loss.backward() 会把当前计算图的梯度累加到参数的 .grad 上,而不是覆盖。PyTorch 文档对 no_sync 的描述是 “gradients will be accumulated on module variables”(torch/nn/parallel/distributed.py:1700-1702)。因此不需要 losses += loss;需要的是别在 micro-batch 之间 zero_grad()。
Megatron 在每个训练迭代开始时清零一次梯度缓冲区,然后跑 num_microbatches 次前向/反向,最后只调用一次 optimizer.step()(megatron/training/training.py:3473-3478、3574-3580、3662)。DeepSpeed 在累积边界上用 (micro_steps + 1) % gradient_accumulation_steps == 0 判断是否更新(deepspeed/runtime/engine.py:3494),ZeRO-3 则在 micro_step_id == 0 时拷贝、之后 add_ 到 fp32 梯度分区(deepspeed/runtime/zero/stage3.py:2041-2047)。
这段示意只覆盖「等长 micro-batch + 每卡同 token 数」的情形;变长序列或 padding 不同时,loss / gas 不是 token-level 的正确缩放,见 §3.3。
no_sync:把 all-reduce 推到边界PyTorch DDP 的 no_sync 把 require_backward_grad_sync 置为 False,退出时恢复。文档明确:梯度先累积在模块变量上,在退出上下文后的第一次前向/反向同步;并且 forward 必须放在上下文内,否则梯度仍会同步(torch/nn/parallel/distributed.py:1696-1722)。DDP 默认的通信 hook 是「先除以组大小,再 all-reduce」(torch/distributed/algorithms/ddp_comm_hooks/default_hooks.py:19-32),所以 DDP 的同步是跨 rank 的平均。
FSDP 的 no_sync 只能用在 root 实例上,并递归作用到子实例;它会在累积期间保留完整模型梯度而不是梯度分片,因此显存通常更高;使用 CPU offload 时,梯度也要等到最终同步后才下 CPU(torch/distributed/fsdp/fully_sharded_data_parallel.py:1044-1081)。FULL_SHARD 平时在反向结束后做 reduce-scatter;no_sync 期间不做这一步。
DeepSpeed 的 no_sync 由 inside_no_sync_ctxt 标记;Engine.step() 里直接断言不能在该上下文内调用(deepspeed/runtime/engine.py:3606-3608)。ZeRO stage ≥ 2 在反向期间重叠梯度分区,stage 0/1 只在累积边界做 allreduce_gradients(deepspeed/runtime/engine.py:3094-3112)。
no_sync 语义套到 Megatron 账本Megatron DDP 的 no_sync 只是把每个 bucket group 的 is_last_microbatch 置为 False,退出时恢复(megatron/core/distributed/distributed_data_parallel.py:640-650)。它只在模型是 FSDPv2,或 FSDPv1/DDP 且开启 overlap_grad_reduce 时安装(megatron/training/training.py:4980-4995)。如果 overlap_grad_reduce 关闭,反向期间本来就不发梯度归约,归约被推迟到迭代末尾的 finish_grad_sync(megatron/core/distributed/distributed_data_parallel.py:718-729),此时没有需要抑制的通信。Megatron 的同步边界由 schedule + finalize_model_grads 决定,不是由 Python 层的 with no_sync() 单独决定。
Megatron 的 GPT loss function 返回的是当前 micro-batch 的 token loss 之和,同时返回非 padding token 数:loss = torch.sum(losses * loss_mask),num_tokens = loss_mask.sum()(pretrain_gpt.py:288-293)。它不是先做 mean 再返回。
默认路径(calculate_per_token_loss=False)在 schedule 里做两次除法:先 output_tensor /= clamp(num_tokens, min=1),再 output_tensor /= num_microbatches(megatron/core/pipeline_parallel/schedules.py:343-349)。随后 DDP 在 DP 组内做平均。合起来是三层平均:micro-batch 内按 token 平均 → micro-batch 之间等权平均 → DP rank 之间等权平均。如果每个 micro-batch 的 token 数相同,这与全局 token-level mean 相同;不同则不等。
逐 token 路径(calculate_per_token_loss=True)保留 loss 的和,在 finalize_model_grads 里把最后一个 PP stage 的 num_tokens 广播到 PP 组、在 DP(+CP) 组内 all-reduce(dp_cp_group),然后用 1 / total_global_tokens 缩放所有梯度(megatron/core/distributed/finalize_model_grads.py:805-822)。这条路径下 DDP 不做 1/dp 预缩放(megatron/core/distributed/distributed_data_parallel.py:268-273),分母只出现一次。
Lm 是第 m 个 micro-batch 的 token loss 之和,nm 是它的非 padding token 数。两式相等当且仅当所有 nm 相同(或权重相同);变长序列、padding 比例不同、packing 后各 micro-batch 长度不同,都会让它们分开。
这不是数值误差,而是梯度权重的差异:mean-of-means 给每个 micro-batch 相同权重,短 micro-batch 的每个 token 被相对放大;token-level 给每个 token 相同权重。Megatron 的配置注释也说明,默认行为是「假设所有 token 都非 padding」,逐 token 路径才按 global batch 的实际非 padding token 数计算(megatron/core/transformer/transformer_config.py:324-326)。
第 1 组:nA=1000,LA=2.0;nB=3000,LB=4.0。
mean-of-means = (2.0 + 4.0) / 2 = 3.0000
token-level = (1000×2.0 + 3000×4.0) / (1000+3000) = 14000 / 4000 = 3.5000
差 = 3.5000 − 3.0000 = 0.5000;权重 A = 1000/4000 = 25.0%,B = 75.0%。
第 2 组:nA=512,LA=1.5;nB=2048,LB=3.5。
mean-of-means = (1.5 + 3.5) / 2 = 2.5000
token-level = (512×1.5 + 2048×3.5) / (512+2048) = 7936 / 2560 = 3.1000
差 = 3.1000 − 2.5000 = 0.6000;权重 A = 512/2560 = 20.0%,B = 80.0%。
简化与未覆盖:只算两个 micro-batch、无 padding、无 drop_last、无 packing;真实训练里 M 个 micro-batch 的 mean-of-means 要再除以 M,token-level 要汇总 ΣL 与 Σn。默认路径的 DP 平均也未画进来;它会在 rank 之间再做一次等权平均。未覆盖 rampup 与 PP:前者改变 GBS/GA,后者改变 micro-batch 的调度,二者都不改变这里的两个归约公式。
梯度裁剪必须在完整累积、DP 归约之后、参数更新之前。Megatron 的 optimizer.step() 依次做 prepare_grads()、clip_grad_norm(config.clip_grad)、step_with_ready_grads()(megatron/core/optimizer/optimizer.py:1078-1109);裁剪用的 grad norm 在 grad_stats_parallel_group 上归约(megatron/core/optimizer/optimizer.py:545-551),看到的是全局梯度,不是某个 micro-batch 的局部梯度。
DeepSpeed ZeRO-3 同样在 step() 里按子组做 unscale_and_clip_grads,再调用 optimizer(deepspeed/runtime/zero/stage3.py:2913-2960、3019-3028)。如果在 micro-batch 循环里裁剪,你裁剪的是尚未累加的偏梯度,算法已经变了;这不是「同一个 clip 换个位置」。
zero_grad() 放在 optimizer step 之后、下一个累积循环之前。Megatron 在每次迭代开头清一次(megatron/training/training.py:3473-3478);DeepSpeed 在边界 step 内部/之后处理。不要在 micro-batch 之间清零。
读完这节应知道:GBS 由 MBS、独立数据分区数和 GA 相乘;lr 的线性缩放只在论文列出的条件下成立,不能从 GBS 直接推。
TGB 是固定长度、无 padding、无 packing 时的 token 数;真实训练要按非 padding token 统计。
Megatron 在初始化时断言 global_batch_size % (micro_batch_size × data_parallel_size) == 0,并计算 num_micro_batches = global_batch_size // (micro_batch_size × data_parallel_size)(megatron/core/num_microbatches_calculator.py:381-388)。如果 GBS 没显式给,默认取 micro_batch_size × batch_parallel_size(megatron/training/arguments.py:641-642),也就是 num_microbatches=1。
这里的 DP 是「独立数据分区数」。Megatron 把 get_batch_parallel_size(args) 传给 num-microbatches calculator(megatron/training/global_vars.py:225-234),而 batch_parallel_size = world_size / (TP × PP × CP)(megatron/training/arguments.py:494-499)。TP/PP/CP 不乘 GBS;PP 的 microbatch 计数就是 GA,见 §07。
表 2 · 四组账本示例(按 GBS = MBS × DP × GA 计算;无 drop_last、无 packing、固定 seq_len)
| 设定 | MBS | seq_len | DP | GA | GBS 样本 | GBS token | optimizer step / 全局 batch | 梯度同步 / step |
|---|---|---|---|---|---|---|---|---|
| A 单卡 | 8 | 2048 | 1 | 1 | 8 | 16,384 | 1 | 0(单 DP) |
| B 8 卡 | 4 | 2048 | 8 | 1 | 32 | 65,536 | 1 | 1 |
| C 8 卡 + GA4 | 4 | 2048 | 8 | 4 | 128 | 262,144 | 1 | 1(no_sync) |
| D 16 卡 + GA8 | 1 | 4096 | 16 | 8 | 128 | 524,288 | 1 | 1(no_sync) |
「梯度同步 / step」列写 1 是指 no_sync / 延迟归约把 DP 集合通信放在累积边界;如果每个 micro-batch 都同步,次数是 GA。A 组 DP=1,没有跨 rank 通信,所以实际同步 0 次。两种做法的样本账相同,通信账不同。表里的 1 个 optimizer step 是定义,不是实测。
第 1 组:MBS=4,seq_len=2048,DP=8,GA=4。
GBS 样本 = 4 × 8 × 4 = 128
GBS token = 128 × 2048 = 262,144
每 rank 每步样本 = 4 × 4 = 16;每 rank 每步 micro-batch = GA = 4。
optimizer step = 1 次 / 全局 batch;梯度同步 = 1 次(no_sync)或 4 次(每个 micro-batch 都同步)。
第 2 组:MBS=1,seq_len=4096,DP=16,GA=8。
GBS 样本 = 1 × 16 × 8 = 128
GBS token = 128 × 4096 = 524,288
每 rank 每步样本 = 1 × 8 = 8;每 rank 每步 micro-batch = GA = 8。
optimizer step = 1 次 / 全局 batch;梯度同步 = 1 次(no_sync)或 8 次(每个 micro-batch 都同步)。
简化与未覆盖:假定 GBS 能被 MBS×DP 整除;未覆盖 drop_last(最后一批可能不足 GBS)、packing(token 数不等于样本数×seq_len)、rampup(GA 随训练变化)、PP(不额外乘 GBS,但会改变 pipeline 调度)。表 2 A 组 DP=1 时无跨 rank 通信,梯度同步实际为 0 次;上面两组 DP>1,1 次是累积边界的语义计数。
Goyal et al. §2.1 的规则是:minibatch size 乘以 k,learning rate 也乘以 k。§5.1 给出参考学习率 η = 0.1 · kn/256,baseline 是 k=8 GPUs × n=32 images/worker,即 kn=256、η=0.1。条件必须一起读:其他超参数保持不变,包括 weight decay(论文用 1e-4,且不作用于 BN 的 γ/β)和 BN 的 per-worker batch n=32;规则假设小 batch 步上的梯度变化不大,在训练早期会失效;§5.3 的 batch sweep 显示 8k 之后 error 上升。
Warmup 不是可选装饰。Goyal §2.2 给出 warmup 方法,§5.1 给出取值:从 η=0.1 在 5 epochs 内线性升到 3.2;constant warmup(先 0.1 保持 5 epochs 再跳)会在跳变后出现 error spike,训练无法完全恢复。Goyal Table 1 的三种 8k 设置里,gradual warmup 的 top-1 error 是 23.74,no warmup 是 24.84(差 1.10 个百分点),constant warmup 是 25.88(比 gradual 差 2.14 个百分点,甚至比 no warmup 还高 1.04 个百分点)。这些数字的条件是 ResNet-50 / ImageNet / 90 epochs / SGD Nesterov m=0.9 / 5 次独立运行。
Smith et al. 给出另一条路径:不衰减 lr,而是在 decay 点增大 batch。§5.2 的 WRN 16-4 / CIFAR-10 实验里,SGD m=0.9、ghost BN 128;原始 schedule 是 lr 0.1、batch 128、约 80,000 次更新、94.3% 测试准确率;把 batch 从 128 起每个衰减点 ×5(上限 5120)、lr 保持 0.1,约 29,000 次更新、94.4%(单次;五次追加运行的中位数 94.2%)。Smith §5.1 的总结句是 “it is the noise scale which is relevant, not the learning rate.” 这不是「batch 越大越好」:同一节里把 momentum 提到 0.98、初始 batch 3200 的设置只到 93.3%(中位数 93.5%),更新少于 2,500 次。
Megatron SC21 报告的是系统层面的 batch 选择:§5.1 的 175B 训练时间估算按 1024 张 A100、300B tokens、B=1536、140 TFLOP/s/GPU 计算;§5.4.3 明确 GPT-3 训练收敛时用的就是 B=1536。530B 对比在 560 GPUs 上放不下时,Megatron Table 2 caption 说明这是 ZeRO-3、microbatch size 4 的条件,作者把 GPU 数提到 640、B 提到 2560。Megatron §5.1 固定 seq_len=2048,所以 1536×2048 = 3,145,728 token/batch。SC21 报告的是 B 的取值;linear scaling rule 的表述来自 Goyal,不来自 SC21。
下面的顺序是本页整理的工程做法,不是论文规则:① 先按数据量、训练 token 预算和优化稳定性选 GBS(通常写成 token 数);② 按单卡显存选 MBS;③ 按集群规模选 DP;④ 用 GA = GBS / (MBS × DP) 配平并检查整除;⑤ 若要用 lr×k,按 Goyal 的条件加 warmup、固定 BN per-worker batch 和 weight decay,并在目标 batch 上验证;⑥ 不满足条件时,直接在目标 GBS 上调 lr,不要从 256 的 0.1 直接外推。
一个容易忽略的边界:GBS 相同不代表更新等价。MBS 改变 BN/dropout 的 per-step 统计,GA 改变串行顺序,DP 改变 rank 间的平均;即使 loss 归约写成 token-level,数据顺序、dropout mask 和 optimizer 的动量轨迹也不会与「一次大 batch」逐位相同。只有在固定模型、无 BN/dropout 随机性、相同数据顺序且 loss 归约一致的条件下,累积梯度才与大 batch 梯度相等。
读完这节应知道:同步、裁剪、step 各自只发生一次;micro-batch 循环里发生的是前向、反向和梯度累加。
no_sync / 延迟归约下累积;最后一个 micro-batch 退出 no_sync,DP 归约一次,然后裁剪、更新、清零。PP 只改变这些 micro-batch 在流水线中的调度,不改变计数。把顺序拆成可核对的五步:
zero_grad_buffer() / optimizer.zero_grad(),之后整个 micro-batch 循环不再清零(megatron/training/training.py:3473-3478)。no_sync 或延迟归约下只累加,最后一次触发 DP 归约。finalize_model_grads 里做 all-grads-sync,并在逐 token 路径上除以全局 token 数(megatron/core/distributed/finalize_model_grads.py:805-822)。optimizer.step() 内部先 clip_grad_norm,再更新参数(megatron/core/optimizer/optimizer.py:1078-1109)。zero_grad 在下一轮开头。PP 的情形:每个 micro-batch 还要穿过 pipeline stages,schedule 里的 num_microbatches 仍是 GA(megatron/training/training.py:3460、3574-3579)。interleaved 1F1B schedule 要求 num_microbatches 是 PP degree 的整数倍(SC21 §2.2.2),这是调度约束,不是账本里多一个乘数。PP 带来的是 bubble,不是额外的 GBS。
读完这节应知道:大 batch 的实测收益依赖 warmup、模型和优化器;论文里的「可行」不等于「无条件更好」。
表 3 · Goyal et al. Table 1:ResNet-50 / ImageNet / 90 epochs / SGD Nesterov m=0.9 / weight decay 1e-4 / 5 次独立运行的均值 ± 标准差(每次运行取最终 5 epochs 的中位数误差)
| 设置 | batch(kn) | lr η | warmup | top-1 error(%) |
|---|---|---|---|---|
| baseline | 256 | 0.1 | — | 23.60 ± 0.12 |
| 8k | 8192 | 3.2 | 无 | 24.84 ± 0.37 |
| 8k | 8192 | 3.2 | constant,0.1 保持 5 epochs | 25.88 ± 0.56 |
| 8k | 8192 | 3.2 | gradual,0.1→3.2 over 5 epochs | 23.74 ± 0.09 |
表 3 的读法:8k + gradual warmup 与 256 baseline 相差 0.14 个百分点(23.74 − 23.60),这是这篇论文条件下「大 batch 可行」的核心证据;但同一张表里 constant warmup 25.88 比 no warmup 24.84 还高 1.04 个百分点,说明 warmup 的形状本身会改变结论。8k 是 256 的 32 倍(8192/256),学习率 3.2 也是 0.1 的 32 倍;这个倍数关系只在这套设定和这些条件下成立。
表 4 · Smith et al. §5.2:WRN 16-4 / CIFAR-10 / SGD / ghost BN 128;更新次数与测试准确率
| schedule | lr / batch | momentum | updates(约) | test acc(单次 / 五次追加运行的中位数) |
|---|---|---|---|---|
| 原始:lr 衰减 | 0.1 / 128 固定 | 0.9 | 80,000 | 94.3% / 94.3% |
| 增大 batch | 0.1 / 128 起每个衰减点 ×5,上限 5120 | 0.9 | 29,000 | 94.4% / 94.2% |
| 再提高初始 lr | 0.5 / 640 起 | 0.9 | <6,500 | 94.5% / 94.2% |
| 再提高 momentum | 0.5 / 3200 起 | 0.98 | <2,500 | 93.3% / 93.5% |
表 4 的读法:在相同的 epoch 预算下,把 lr 衰减换成 batch 增长,可以把参数更新从约 80,000 次压到约 29,000 次,单次准确率 94.4% 与原始 94.3% 相差 0.1 个百分点(94.4 − 94.3)。但这个替代不是无条件的:同一节里 momentum 0.98、初始 batch 3200 的设置只有 93.3%,说明噪声尺度、momentum 和 batch 上限要一起调。Smith §5.1 的表述是 “it is the noise scale which is relevant, not the learning rate.”
Megatron SC21 的 batch 选择更偏系统账:§5.1 的 175B 训练时间估算按 1024 张 A100、300B tokens、B=1536 计算,§5.4.3 明确 GPT-3 训练收敛时用的就是 B=1536;530B 对比在 560 GPUs 上放不下,Megatron Table 2 caption 说明这是 ZeRO-3、microbatch size 4 的条件,作者把 GPU 数提高到 640、B 提高到 2560。在 §5.1 固定的 seq_len=2048 下,1536×2048 = 3,145,728 token/batch。SC21 报告的是 B 与系统配置;本页不把它读成「更大的 batch 准确率更高」,它说明的是 batch 选择与显存、GPU 数、并行配置绑在一起。
把三组结果放在一起:Goyal 证明的是「在 ResNet-50 / ImageNet 的特定条件下,8k batch + 线性 lr + gradual warmup 可以接近 256 baseline」;Smith 证明的是「在 WRN / CIFAR-10 上,用 batch 增长替代 lr 衰减可以得到相近的 epoch 曲线,同时减少更新次数」;SC21 报告的是「大规模 GPT 训练实际使用的 B」。三者都不能单独外推成「GBS 越大越好」或「lr 必须线性缩放」。
读完这节应知道:梯度累积省的是激活,不是所有显存;它也不会自动修正 BN、loss 权重或流水线调度。
BatchNorm 的均值和方差在每次前向的 micro-batch 上统计。梯度累积把多个 micro-batch 串行跑,但每个 micro-batch 仍然只看到 MBS 条样本,所以 BN 统计不会因为 GA 变大而变成 global batch 的统计。Goyal 在放大 batch 时保持每个 worker 的 n=32 不变,正是为了让 BN 的 loss function 不变(§2.3、§5.1);Smith 的 CIFAR-10 实验用 ghost BN 128(§5.1、§5.2)。如果模型依赖 BN,想复现大 batch 的行为需要 sync BN、ghost BN 或重新调 BN 的 momentum,而不是只加 GA。
默认路径先除以当前 micro-batch 的 token 数,再除以 micro-batch 数(megatron/core/pipeline_parallel/schedules.py:343-349)。当序列长度不同、padding 比例不同或 packing 后各段长度不同,短 micro-batch 会被赋更高的每 token 权重。逐 token 路径把整个 global batch 的非 padding token 数作为分母(megatron/core/distributed/finalize_model_grads.py:805-822),并在 DDP 上关闭 1/dp 预缩放(megatron/core/distributed/distributed_data_parallel.py:268-273)。使用哪条路径要在训练配置里明确写出来;不要用「loss 除以 GA」去代替 token 加权。
裁剪的 grad norm 必须在完整累积和 DP 归约之后计算。Megatron 的 optimizer.step() 先 prepare_grads、再 clip_grad_norm、最后 step_with_ready_grads(megatron/core/optimizer/optimizer.py:1078-1109),grad norm 在 grad_stats_parallel_group 上归约(megatron/core/optimizer/optimizer.py:545-551)。DeepSpeed ZeRO-3 在 step() 里做 unscale_and_clip_grads(deepspeed/runtime/zero/stage3.py:3019-3028)。在 micro-batch 循环里裁剪会改变算法,不能当成等价实现。
历史上的 rampup_batch_size 是三元组:起始 batch、增量、ramp-up samples。配置文档的例子是 [16, 8, 300000] 配合 global-batch-size 1024:从 16 开始,按 (1024−16)/8 = 126 个区间线性升到 1024,每个区间约 300000/126 = 2380 个样本(megatron/training/config/training_config.py:21-31)。但在这个 commit 里,rampup_batch_size 已标记 deprecated 且被忽略(megatron/core/num_microbatches_calculator.py:100-104、157-161),替代品是 step_batch_size_schedule,格式 "THRESHOLD:BS ...",阈值可按样本或 token(megatron/training/config/training_config.py:33-40)。schedule 更新时会按 consumed samples 重新算 current_global_batch_size 和 num_micro_batches(megatron/core/num_microbatches_calculator.py:575-593)。
不要把 batch rampup 和 lr warmup 混为一谈:前者改变 GBS/GA,后者改变学习率。Goyal 的 warmup 是 lr 从 0.1 升到 3.2(§2.2);rampup 是让账本本身随训练推进变化。前者改变第几步的 GBS,后者改变第几步的 lr;两者都会让「第几步的有效更新」不同。
Megatron 的 pipeline schedule 收到的 num_microbatches 就是 get_num_microbatches()(megatron/training/training.py:3460、3574-3579),也就是 GBS / (MBS × DP)。因此不要写 GBS = MBS × DP × GA × PP:PP 不乘 GBS。PP 的 microbatch 是流水线调度单位,每个 microbatch 的梯度都累加到同一组缓冲区;PP 带来的额外约束是 interleaved 1F1B 要求 num_microbatches 是 PP degree 的整数倍(SC21 §2.2.2),以及 bubble 会降低利用率。想通过增加 PP microbatch 来减少 bubble,必须同时调 GBS、MBS 或 DP,否则你改变的是优化 batch,不只是调度。
no_sync 不省显存:梯度仍然完整保存在 .grad;FSDP 的 no_sync 反而因为保留完整梯度而更费显存(torch/distributed/fsdp/fully_sharded_data_parallel.py:1053-1059)。手算和交互假设固定 seq_len、无 padding、无 drop_last、无 packing、无 rampup、无 PP bubble;没有覆盖 TP/CP 的通信账、PP 的 bubble 公式、ZeRO 的分区细节、FSDP 的 shard 策略、以及不同框架对 no_sync 的边界差异。表 2 B/C/D 行的「1 次同步」是 no_sync / 延迟归约下的计数;A 组 DP=1 为 0 次。如果实现选择每个 micro-batch 都同步,计数会变成 GA,但样本账不变。