梯度累积 Gradient Accumulation

Gradient Accumulation (Grad Accum / GA)

梯度累积是在显存不足时,把原本一个大 Batch 的训练拆分成多个 Micro-Batch 分步算梯度并累积相加,等累积够等效大 Batch 之后才更新一次权重,用来在小显存 GPU 上模拟大 Batch 训练效果的技巧。

详细解释

Gradient Accumulation(梯度累积,简称 Grad Accum 或 GA)一句话:你想用 Batch Size = 128 训一个 7B 模型(保证 Batch Norm / Loss 稳定性 / 大 Batch 泛化性这些好处),但你的 4090 24G 一次最多塞 BS=16;梯度累积就是把 128 拆成 8 次 Micro-Batch,每次拿 16 个样本算一次 Forward+Backward 得到梯度后不更新权重,把梯度累加起来;等做完 8 次 Micro-Batch 之后,累积出来的梯度就等效于一次性算 BS=128 的梯度了,这时候才调用 optimizer.step + scheduler.step + zero_grad 更新一次权重。这样就用「每 8 个 step 更新一次权重」的时间换了「模拟 8 倍大 Batch」的效果。它是 2022 年之后大模型微调的「平民神器」——没有它,普通人根本没法用消费级显卡微调大模型。

梯度累积的数学等效性:只要你在累积 N 个 Micro-Batch 时,对每个 Micro-Batch 的 Loss 除以 N(做归一化),然后不做梯度裁剪 / optimizer.step,只累积梯度,那么 N 次累积之后的梯度,和真实把 N 个 Micro-Batch 拼成一个 Mega-Batch 一次性算出来的梯度数学上完全相等(前提是你的模型没有 BatchNorm 这种跨样本统计归一化的层——LLM 都是 LayerNorm,没问题),最终收敛曲线和效果基本完全一样,不会有任何精度损失。唯一的区别是训练时间变长(原来 1000 step 更新 1000 次,现在 1000×8 step 只更新 1000 次,耗时接近 ×8,因为要算更多次 Backward),但 GPU 峰值显存占用直接降到 1/N。

唯元智创 的 SFT / DPO 云训练平台,在你选择小 GPU(4090 / A10)时会自动帮你算最优的 Batch Size 和 Gradient Accumulation 步数组合:目标是让 Micro-Batch 尽量大(提高 GPU 利用率、计算效率高)、累积步数刚好凑够你想要的等效 Batch Size(通常 128 / 256 对 SFT 最优),而且会自动处理 Loss 缩放、梯度裁剪时机、Learning Rate 调度器更新时机这些容易踩坑的细节,用户不用自己调。

用梯度累积的 4 个核心等效关系(只要这几条满足就和真大 Batch 没差别)

维度真实一次性大 Batch BS=BGradient Accumulation N 步,Micro-Batch = b,B = N × b
梯度数值对 B 条样本平均 Loss 后 Backward,得到 Grad_true每个 Micro-Batch 的 Loss 先除以 N,Backward 得到 grad_i;累积 Sum(grad_i) 之后再更新 → Grad_accum
梯度等效性Grad_trueGrad_accum 严格等于 Grad_true(数学上,无 LayerNorm LLM 场景下)
权重更新频率每 1 step 更新 1 次(1 step = 1 次 Forward/Backward + 1 step 更新)每 N 个 Micro-Step 更新 1 次(前面 N-1 步只累积梯度不更新)
Learning Rate 调度更新时机每次权重更新之后,scheduler.step 一次每 N 个 Micro-Step(做完一次真正的权重更新)之后 scheduler.step 一次——绝对不能每个 Micro-Step 调 scheduler.step(会让 LR 下降速度变成原来的 1/N,训练直接崩)
梯度裁剪时机每次 Backward 之后、optimizer.step 之前 clip累积完 N 步之后、optimizer.step 之前做一次全局 clip;不能每个 Micro-Step 单独 clip(先 clip 再相加和加完再 clip 结果完全不同)
BatchNorm/LayerNorm 统计量BatchNorm 统计 B 条样本均值方差;LayerNorm 不跨样本LLM 都是 LayerNorm,对 Micro-Batch 大小不敏感,所以完全没问题;如果你训的是 CV 小模型带 BatchNorm,GA 会损失精度(因为 BatchNorm 在 Micro-Batch 上算统计量不是在大 Batch 上算),这个时候 GA 不是严格等效
训练总步数配置Epoch × 样本数 / B 个 update stepEpoch × 样本数 / B 个 update step(和左边一样,update 步数不变);总 Micro-Step 数 = N × Update_Steps(比原来多 N 倍)

落地梯度累积的 5 个坑(90% 的人第一次用都会踩至少 3 个)

  1. 坑 1:忘了给每个 Micro-Batch 的 Loss 除以累积步数 N——这是 GA 第一大坑,踩了之后你的等效学习率相当于被放大了 N 倍,训练一开始 Loss 直接飞上天然后发散。直观理解:真实大 Batch 的 Loss 是 B 条样本的平均(÷ B);你每个 Micro-Batch 的 Loss 是 b 条的平均(÷ b),直接加 N 个的话,总和比真实大 Batch Gradient 大了 N 倍;正确做法是每个 Micro-Batch Loss 先 ÷ N,这样 N 个 ÷N 之后的 Loss 梯度加起来,刚好等于真实 ÷ B 的大 Batch Loss 梯度。
  2. 坑 2:每个 Micro-Step 都调了 scheduler.step 和 optimizer.step——只有「第 N 步、第 2N 步、第 3N 步」这些刚好累积完一轮的时候才调用 optimizer.step 和 scheduler.step,其余 Micro-Step 只算 loss.backward() 不做任何更新;90% 的新手上来就把 optimizer.step() 写在每个 for batch 循环里,那和没用 GA 完全一样,白浪费 N-1 倍时间。
  3. 坑 3:在每个 Micro-Step 做完 backward 之后就梯度裁剪了——应该等 N 步累积完梯度之后、optimizer.step 之前做一次全局 clip;每个 Micro-Step 单独 clip 之后再相加,等效于「把每个分量的梯度先上限截断,再求和」,和「先求完整大 Batch 梯度再整体 clip」的结果完全不一样,会严重影响收敛。
  4. 坑 4:Multi-GPU DDP 场景下的梯度同步时机错了——多卡 DDP 默认每个 backward 之后会自动做 all-reduce 跨卡同步梯度;你用 GA 时前 N-1 步根本不需要同步(跨卡同步的目的就是为了更新权重做准备,不更新权重同步纯粹浪费带宽)。正确做法是用 PyTorch model.no_sync() 上下文管理器把前 N-1 个 Micro-Batch 的 backward 包起来,只有第 N 步才做正常带同步的 backward;这样能省 N-1 次 all-reduce 的网络开销,多卡 GA 的速度能提升 20-40%。
  5. 坑 5:把 Micro-Batch 调得太小(比如 1 或 2)——很多人觉得「反正 GA 能累积,Micro-Batch 越小越好,一张 4090 能塞下 7B 模型就行」,但 Micro-Batch 太小(< 4)时,GPU 的 Tensor Core 利用率会从 70% 掉到 30%,计算效率暴跌;同样一个 Micro-Batch 从 1 调到 16,GPU 利用率涨 2 倍,完成 1 个 update step 的时间反而差不多。经验设置:让 Micro-Batch 把显存吃到 90%(留 10% 给 KV Cache/碎片),然后用 GA 步数凑够等效 Batch Size,这是最高性价比的组合。

常见问题

用梯度累积训 SFT,等效 Batch Size 真的越大越好吗?多少最合适?
不是——等效 Batch Size(Effective Batch Size)对 LLM SFT 来说有一个最优区间,超过这个区间之后收益递减,甚至过大反而会让泛化性下降。经验最优区间(2025 年对 7B/13B/70B SFT,不管你是不是用 GA 凑的):(1)7B 模型 SFT:等效 BS 128 左右最优,256 饱和、≥ 512 开始掉点;(2)13B 模型 SFT:等效 BS 256 最优,≥ 1024 掉点;(3)70B 模型 SFT:等效 BS 512 最优,≥ 2048 掉点;(4)DPO 对齐训练:等效 BS 一般比 SFT 小一半(因为 DPO 是成对样本,有效样本数本来就少),64-128 最合适。为什么不是越大越好?因为 SFT 的 Data Randomness(每批样本的多样性)对收敛很重要,太大 Batch 之后每一批的多样性虽然绝对数更多但相对比例趋于稳定,优化器会在一个过于平滑的 Loss Landscape 上走,容易「踩进」局部最优窄谷而不是宽阔平坦的好谷——工业界称之为「Large Batch Generalization Gap」,ImageNet 分类任务上 BS 从 256 涨到 8K,Top-1 准确率会掉 5%,LLM 上虽然没有这么夸张,但趋势是一样的。所以如果你要的是最高的下游任务效果,不要盲目追求 BS=4096 来省训练时间,卡在上面的最优区间就好;如果你是预训练(不是 SFT),对 BS 敏感度低一些,可以往大里开,但是也要配合 Linear LR Scaling 把学习率按 BS 比例线性涨上去。Batch Size 里列的不同规模模型的经验 Batch Size,就是用 GA 算过等效值之后的推荐配置。
Gradient Accumulation 和 Mixed Precision Training 组合使用时,GradScaler(AMP Loss Scaler)要在哪个 step update?有什么特别注意的?
这是 GA + AMP 双组合的超级大坑——90% 的示例代码都写错了:GradScaler 的 scale_factor 更新、unscale_、以及 step 都必须和「真实 optimizer 更新」同步(每 N 步一次),绝对不能每个 Micro-Step 调 scaler.step()/update();否则 AMP 的 Loss Scale 会在 N-1 步里被错误地反复调小/调大,导致梯度 Underflow(全变 0)或者 Overflow(NaN Loss)。正确的代码结构模板(伪代码):scaler = GradScaler()for micro_step, batch in enumerate(dataloader): with autocast(dtype=torch.bfloat16): # AMP 前向 loss = model(batch) loss = loss / accumulation_steps # ⭐ 第一步:每个 micro-batch loss 除以 N if (micro_step + 1) % accumulation_steps != 0: with model.no_sync(): # ⭐ 多卡 DDP 时前 N-1 步不做跨卡同步 scaler.scale(loss).backward() # backward 累积梯度 else: scaler.scale(loss).backward() # 第 N 步 backward,DDP 同步 scaler.unscale_(optimizer) # ⭐ unscaling 之后再做 Gradient Clip torch.nn.utils.clip_grad_norm_(model.parameters(), max_norm=1.0) # ⭐ 完整累积完才剪梯度 scaler.step(optimizer) # ⭐ 每 N 步才 update 一次 optimizer 和 scaler scaler.update() scheduler.step() # ⭐ scheduler 每 N 步进 1 次 optimizer.zero_grad(set_to_none=True) # 只在更新完之后清零你只要按上面的模板写,GA + AMP 组合基本不会出问题;最常见的错误是在 if 分支(前 N-1 步)里也写了 scaler.step() 和 scaler.update(),导致 AMP 的 Loss Scale 在 N-1 步里被错误更新到一个离谱的值,最终要么 Loss 全 NaN 要么梯度全 0,模型完全不收敛。
Gradient Accumulation 能不能和 LoRA / QLoRA 一起用?显存占用会不会降得更多?
完全可以——而且 LoRA / QLoRA + GA 是消费级显卡微调 7B/13B 模型的黄金组合:LoRA / QLoRA 把训练参数量从 7B 降到 0.1-1% 量级(训练显存大头是参数优化器状态,降了 100-1000 倍),GA 再用时间换空间把等效 Batch Size 凑到 128/256 的理想值,两者叠加,一张 4090 24G 跑 7B QLoRA 微调完全没问题,真实训练效果和 A100 全量微调的差距只有 2-3%(大部分场景感知不到)。显存占用方面:LoRA / QLoRA 已经帮你吃掉了 90% 的显存大头(优化器状态和梯度只针对 LoRA 矩阵,不针对全量参数),GA 帮你解决的是「剩下 10% 的 Micro-Batch 太小、等效 Batch 不够、训练泛化性差」的问题;GA 本身不额外占显存(因为梯度是累加在同一块 Grad 内存上,不是每步新开一块),所以叠加后整体显存占用基本等于 QLoRA + Micro-Batch 的显存,和没开 GA 时一样。最终你获得的是:花 N 倍时间(N 是累积步数,一般 4-16 倍)换「等效 Batch Size 正确 + 泛化性提升 2-5%」的效果;对于一张 4090 跑一晚 SFT(8 小时能跑完 3 个 Epoch)的工程师来说,花 1-2 小时额外时间换 2-5% 的下游效果,是血赚的交易,所以 2025 年消费级显卡微调的主流配置就是「QLoRA + Gradient Accumulation 8 步 + FP16/BF16 AMP + Gradient Clipping」,这四件套一起开,效果和 A100 全量 FT 基本打平,成本只有 A100 云租金的百分之一。QLoRA 里列的推荐微调配置,已经把 Gradient Accumulation 的推荐步数算在里面了,可以直接套。