一、梯度累积基础:什么是梯度累积、为什么需要梯度累积、梯度累积与Batch Size的关系

各位同学好,我是你们的老朋友。今天咱们来聊聊梯度累积这个话题。

说实话,我第一次接触梯度累积的时候,心里想的是:「这不就是攒着梯度一起更新吗?有啥好讲的?」后来在实际项目中踩了坑,才发现这里面的门道还真不少。嗯,咱们今天就把它彻底讲透。

1.1 什么是梯度累积?

先看一个最直观的理解。

正常的训练流程是这样的:

  • 前向传播 → 计算损失 → 反向传播 → 更新参数

梯度累积呢?它把流程改成了:

  • 前向传播 → 计算损失 → 反向传播 → 不更新参数,先攒着梯度 → 重复N次 → 最后统一更新参数

说白了,就是把N个小批量的梯度加起来,当作一个大批量的梯度来用。

我习惯用一个比喻来解释:

正常训练就像你每次吃一口饭就消化一次。梯度累积呢?你先吃N口饭,攒在胃里,然后一次性消化。最终你吸收的营养(梯度信息)是一样的,但过程不同。

核心公式:

假设你设定了累积步数为4,那么:

# 伪代码示意
for step, data in enumerate(dataloader):
    outputs = model(data)
    loss = criterion(outputs, labels)
    loss.backward()  # 梯度累积到 .grad 中
    
    if (step + 1) % 4 == 0:
        optimizer.step()   # 更新参数
        optimizer.zero_grad()  # 清空梯度

注意看,这里的关键是 loss.backward() 每次都会把梯度加到 model.parameters().grad 上,而不是覆盖。这就是「累积」二字的由来。

1.2 为什么需要梯度累积?

你可能会问:「直接设大一点的Batch Size不就行了?干嘛要这么麻烦?」

问得好。我当年也是这么想的。直到我在项目中遇到一个尴尬的情况:

我想用Batch Size=64训练一个BERT模型,结果显存直接爆了。换成Batch Size=8倒是能跑,但训练效果差得离谱。怎么办?

这时候梯度累积就派上用场了:

  • 显存限制:显卡的显存是有限的。大Batch Size意味着一次要加载更多数据、存更多中间激活值。显存不够,你就只能用小Batch Size。
  • 小Batch Size的问题:Batch Size太小,梯度噪声大,训练不稳定,收敛慢。说白了就是「步子太小,容易原地踏步」。
  • 梯度累积的解决方案:用小Batch Size跑多次,攒够梯度再更新。这样既省显存,又模拟了大Batch Size的效果。

我的经验:

我曾经在训练一个图像分割模型时,目标Batch Size是32,但我的2080Ti只能塞下Batch Size=4。于是我设了累积步数=8,效果和直接跑Batch Size=32几乎一样。嗯,这个技巧救了我好几次。

1.3 梯度累积与Batch Size的关系

这里有一个容易混淆的点,我专门拿出来讲清楚。

假设你设了累积步数为N,每个小批量的Batch Size为B。那么:

  • 实际Batch Size = B × N
  • 显存消耗 ≈ 只加载B个样本的显存(而不是B×N)

为什么会这样?因为每次反向传播后,中间激活值就被释放了。只有梯度被保留下来。所以显存只跟单次Batch Size B有关,跟累积步数N无关。

我画了一张图,帮你理解这个流程:

梯度累积流程示意图 小批量1 (B=4) 前向+反向 累积梯度 小批量2 (B=4) 前向+反向 累积梯度 小批量3 (B=4) 前向+反向 累积梯度 小批量4 (B=4) 前向+反向 累积梯度 统一更新参数 等效Batch Size=16 累积步数=4,单次Batch Size=4,等效Batch Size=16

你看,4个小批量各贡献了梯度,最后汇总成一次更新。显存只占用了单次Batch Size=4的空间,但效果相当于Batch Size=16。

注意:

梯度累积不是万能的。它模拟的是「梯度」的累积,但无法模拟「Batch Normalization」的统计量累积。如果你用了BN层,需要额外处理。这个我们后面章节会详细讲。

1.4 什么时候该用梯度累积?

我总结了几种典型场景:

场景 推荐做法 原因
显存不够,但想用大Batch Size 梯度累积 省显存,效果好
训练不稳定,梯度噪声大 梯度累积 平滑梯度,稳定训练
多卡训练,通信开销大 梯度累积 减少通信频率
显存充足,追求速度 直接大Batch Size 梯度累积有额外开销

我个人习惯是:先试试直接跑大Batch Size,显存爆了再上梯度累积。别一上来就搞复杂了。

避坑指南:

我曾经犯过一个错误:设了累积步数=8,但忘了把学习率调大。结果训练了三天,模型几乎没动。后来才意识到,梯度累积相当于扩大了Batch Size,学习率也应该相应调整。一般来说,累积步数翻倍,学习率可以适当调高10%-20%。

1.5 小结

好了,这一章的内容就这些。咱们回顾一下:

  • 梯度累积:攒N次梯度,更新一次参数
  • 为什么需要:显存不够,又想用大Batch Size
  • 与Batch Size的关系:等效Batch Size = 单次Batch Size × 累积步数

下一章咱们会深入讲梯度累积的具体实现,包括PyTorch代码怎么写、有哪些坑要避开。嗯,到时候见。

可可精品资料库