4、同步策略对比:同步SGD、异步SGD、梯度累积、混合精度训练中的同步问题

多卡训练,说白了就是一群人干活。但怎么分活、怎么汇总,这里面的门道可深了。我见过不少团队,卡买了不少,结果训练速度还不如单卡,问题就出在同步策略上。

今天咱们就把四种主流策略掰开揉碎讲清楚。同步SGD、异步SGD、梯度累积,还有混合精度训练里的同步坑,我一个一个说。

4.1 同步SGD:最稳,但也最慢

同步SGD的逻辑很简单:所有卡算完一个batch,大家一起同步梯度,再统一更新参数。

打个比方,就像小组作业,每个人都必须写完自己的部分,组长才收齐,然后统一交给老师。谁写得慢,全组都得等。

我在项目中遇到过这种情况:8张卡跑同步SGD,其中一张卡因为散热问题降频了,结果每轮迭代都要等它多花30%的时间。整体训练速度直接被拖垮。

核心公式:
θ(t+1) = θ(t) - η * (1/N) * Σ(g_i(t))
其中N是卡数,g_i是第i张卡的梯度。所有卡梯度求平均,再更新。

优点很明显:收敛稳定,数学性质好。每个step的更新方向都是全局梯度的无偏估计,不会跑偏。

缺点也致命:木桶效应。最慢的那张卡决定了整个训练的速度。卡越多,等待时间越长。

我的建议:如果集群网络带宽充足、各卡性能一致,同步SGD是首选。否则,考虑下面几种方案。

4.2 异步SGD:快,但容易跑偏

异步SGD的思路是:各卡算完自己的梯度,直接更新参数,不用等别人。

还是小组作业的例子。异步SGD就像每个人写完自己的部分,直接交给老师,不用管别人写没写完。速度快了,但老师收到的作业可能是乱的——因为大家交的顺序和写的顺序不一样。

这就是异步SGD的核心问题:梯度陈旧性(Stale Gradient)。当一张卡算完梯度时,参数可能已经被其他卡更新了好几次。它拿到的梯度,对应的是旧版本的参数。

我记得有一次,我用异步SGD跑一个BERT-large模型,32张卡。训练速度确实快,但loss曲线像心电图一样上下乱跳。最后收敛效果比同步SGD差了将近2个点。

避坑指南:我曾经在异步SGD中踩过一个大坑——学习率没调。异步SGD的梯度噪声大,学习率通常要比同步SGD小一个数量级。否则,参数更新步长太大,直接发散。

异步SGD适合什么场景?大规模稀疏模型,比如推荐系统。因为每个样本只更新少量参数,梯度冲突的概率低。

4.3 梯度累积:用时间换空间

梯度累积,说白了就是攒够一定数量的梯度再更新。

为什么要这么做?因为显存不够。比如你想用batch size=1024,但单卡显存只够塞batch size=128。怎么办?每张卡算8次,每次梯度都累加起来,攒够8次再更新一次参数。

公式长这样:

# 梯度累积伪代码
accumulation_steps = 8
optimizer.zero_grad()

for i in range(accumulation_steps):
    outputs = model(inputs)
    loss = criterion(outputs, labels)
    loss.backward()  # 梯度累加,不清零

optimizer.step()    # 攒够了,更新参数
optimizer.zero_grad()  # 清零梯度

这里有个坑:Batch Normalization层的统计量。梯度累积时,每个micro-batch的BN统计量是独立的,和真正的large batch不一样。我建议用SyncBN或者干脆冻结BN层。

个人经验:梯度累积的步数不要太大。我一般控制在2-8步。步数太多,梯度更新频率太低,收敛变慢。而且梯度累积的数值精度问题也要注意,后面会讲。

4.4 混合精度训练中的同步问题

混合精度训练(AMP)现在几乎是标配了。但很多人不知道,精度转换也会引入同步问题。

混合精度训练的核心是:前向和反向用FP16,权重更新用FP32。这样既省显存又保持精度。

但同步梯度时,问题来了:

  • FP16梯度范围窄,容易溢出。梯度值太小会变成0,太大会变成inf。
  • AllReduce操作在FP16下做,精度损失会累积。

我见过一个案例:某团队用8卡训练,开了AMP后,loss死活不降。排查了两天,发现是梯度同步时用了FP16 AllReduce,导致小梯度被截断为0。换成FP32 AllReduce后,问题解决。

最佳实践:
  1. 梯度同步用FP32,不要省那点带宽。
  2. Loss Scaling要开,防止梯度下溢。
  3. 检查梯度分布,确保没有大量0梯度。

另外,混合精度训练和梯度累积一起用时,要特别注意。每次micro-step的梯度都是FP16,累积多次后再转FP32,精度损失会放大。我建议每步都做Loss Scaling,并且累积步数不超过4。

4.5 四种策略对比一览

策略 收敛速度 稳定性 显存占用 适用场景
同步SGD 慢(受最慢卡限制) 高 低 小规模集群、同构硬件
异步SGD 快 低(梯度陈旧) 低 大规模稀疏模型
梯度累积 中等 中等 低(等效大batch) 显存受限场景
混合精度+同步 快(计算加速) 中等(精度问题) 低(FP16省显存) 通用场景,需调参

4.6 知识体系总览

下面这张图,把四种策略的核心逻辑和关系画清楚了。你看一眼就能明白各自的位置。

多卡同步策略对比 多卡训练同步策略 同步SGD 等所有人,再更新 异步SGD 谁先算完谁先更新 梯度累积 攒够梯度再更新 混合精度同步 FP16算,FP32同步 关键属性对比 收敛稳定性 高 低 中 中 训练速度 慢 快 中 快 显存占用 低 低 低 低

嗯,四种策略各有千秋。同步SGD稳但慢,异步SGD快但容易飘,梯度累积是折中方案,混合精度训练则要额外关注精度转换的同步问题。

我个人建议:新手先从同步SGD+梯度累积入手,等摸透了再尝试异步。混合精度训练现在基本是必选项,但一定要做好梯度检查和Loss Scaling。

最后说一句:没有银弹。选哪种策略,取决于你的硬件、模型和数据。多试试,多对比,找到最适合自己的组合。

可可精品资料库