本文目录导读:

这是一个很好的技术问题,在深度学习(特别是大模型训练)中,梯度累积步长(通常称为 Gradient Accumulation Steps,简写为 gradient_accumulation_steps)是一个至关重要的参数。
它解决的是 “显存不够,但想用大批量数据训练” 的矛盾。
下面我将从定义、工作原理、为什么需要它以及如何设置这四个方面详细解释。
定义
梯度累积步长 指的是:在执行一次模型参数更新之前,需要累计多少次小批量数据(Micro-Batch)的梯度。
- Effective Batch Size(有效批量大小) =
per_device_batch_size(每个设备的小批量大小)×world_size(设备数量)×gradient_accumulation_steps(梯度累积步数)
工作原理(核心)
这里的关键在于,训练过程是分“前向传播”和“反向传播”的。
-
正常情况(无累积):
- 你设置
batch_size=64。 - 硬件一次处理64个样本,计算梯度,立即更新参数。
- 你设置
-
有梯度累积(假设 steps=4):
- 你设置
per_device_batch_size=16,gradient_accumulation_steps=4。 - 流程如下:
- 第1步(step 1): 加载16个样本 → 前向传播 → 反向传播 → 计算梯度(但不更新参数) → 将梯度累加到临时的“梯度累加器”中。
- 第2步(step 2): 加载另外16个样本 → 前向传播 → 反向传播 → 计算梯度(但不更新参数) → 将新梯度累加到累加器中。
- 第3步(step 3): 同第2步。
- 第4步(step 4): 加载最后16个样本 → 前向传播 → 反向传播 → 计算梯度 → 将梯度累加到累加器中,此时累加器中已经累积了 4个16样本(即64个样本)的梯度总和。
- 优化器更新: 使用这个总梯度对模型参数进行 一次更新,然后清空梯度累加器,为下一个循环做准备。
- 你设置
关键点: 在累积的4步中,模型参数本身是没有被更新的,它只是在不停地计算并累加梯度,只有到了第4步,才会根据累加的总梯度,一次性调整参数。
为什么需要它?(主要用途)
-
突破显存限制: 这是最主要的原因,如果你的显卡显存很小(比如8GB、16GB),无法直接加载
batch_size=64的数据(会OOM),通过将batch拆成16,走4步累积,你实际上实现了batch_size=64的效果,而每步只消耗batch_size=16的显存。 -
模拟更大的有效batch size: 某些模型或任务(如自监督学习SimCLR、对比学习)需要非常大的batch size(比如4096),在单卡甚至多卡都做不到时,通过梯度累积可以虚拟地实现。
-
更稳定的梯度估计: 使用更大的批量大小计算得到的梯度,方差更小,方向更精确,训练通常更稳定,梯度累积让你可以在小显存下获得这种优势。
代价与注意事项
-
训练时间增加: 这是最主要的代价,因为虽然参数更新次数少了(每4步才更新一次),但 前向+反向传播的次数并没有减少,你依然需要处理相同数量的总样本(64个),所以总训练步骤数不变,但模型更新的频率降低了,总训练时间会比直接用
batch_size=64基本一致(因为计算量一样)。batch_size=64很快但会OOM,那累积就是唯一办法。 -
Batch Normalization (BN) 的失效: 严重问题! 如果你使用了
BatchNorm层,梯度累积的方式会破坏BN的正常统计,因为每小步只看到小批量(16个样本),BN会基于这16个样本计算均值和方差,而不是整个64个样本的全局分布,这会导致模型性能下降。- 解决方法: 尽量使用 Layer Normalization (LN)(如Transformer、BERT、GPT中),它对batch大小不敏感,如果你的模型必须用BN,可以考虑使用 同步BatchNorm(SyncBN),但实现比较复杂。
-
优化器状态更新时机: 优化器(如Adam)中的动量(Momentum)和学习率调度器(Scheduler)的“步数”是针对 参数更新次数 的。
total_steps应该设置为总样本数 / 有效batch_size,而不是实际的前向传播步数。 -
虚拟批次大小(Virtual Batch Size): 累积后的那个总大小被称为“虚拟批次大小”或“有效批次大小”。
如何设置?(实战建议)
-
第一步:确定最大物理Batch Size。 先尝试直接设置一个较小的
per_device_batch_size(2, 4, 8),看看你的显卡能跑多大而不会OOM,记下这个值。 -
第二步:确定目标有效Batch Size。 参考论文或经验,确定你模型需要的“有效batch size”(比如64, 128, 256)。
-
第三步:计算步数。
gradient_accumulation_steps = 有效Batch Size / (物理Batch Size × 显卡数量)。- 目标有效batch=64,单卡最大物理batch=8,单卡训练,则
steps = 64 / (8 × 1) = 8。
- 目标有效batch=64,单卡最大物理batch=8,单卡训练,则
-
代码示例(Hugging Face Transformers Trainer):
from transformers import Trainer, TrainingArguments training_args = TrainingArguments( output_dir="./results", per_device_train_batch_size=8, # 物理batch size gradient_accumulation_steps=8, # 累积8步,有效batch = 8 * 8 = 64 num_train_epochs=3, # ... 其他参数 ) trainer = Trainer( model=your_model, args=training_args, train_dataset=your_dataset, # ... )
总结表格
| 概念 | 解释 | 类比 |
|---|---|---|
| per_device_batch_size | 每次前向传播时,单个GPU(或CPU)上处理的样本数。 | 每次拿取的酒杯大小。 |
| 梯度累积步长 | 在执行一次参数更新之前,累计多少次梯度。 | 积满几杯酒才倒进大桶。 |
| Effective Batch Size | 实际用于更新参数的、经过累积的总样本数。 | 最终倒入大桶的酒的总体积。 |
| 梯度累加器 | 存储累积梯度的临时变量,相当于一个中间杯子。 | 存储临时积分的中间桶。 |
一句话总结:梯度累积是用“训练时间”(步数增加)和“注意BN问题”为代价,换取在不增加硬件需求下实现大有效batch size的方法。