数据并行(Data Parallelism)原理

数据并行,说白了就是「把数据切开,分给多个工人一起干」。这是分布式训练里最基础、也最常用的一种并行方式。我个人习惯把它比作「流水线上的分拣工」——每个人手里拿到的零件不一样,但干的活是一样的。

数据并行的核心思想

假设你有一张巨大的训练集,比如 100 万张图片。单机训练,跑一轮可能要一天。数据并行怎么做?把数据切成 N 份,每份交给一个 GPU 去算。

每个 GPU 上放的是同一个模型副本。模型参数一模一样,只是喂进去的数据不同。每个 GPU 独立计算前向传播、反向传播,算出各自的梯度。

嗯,这里要注意:每个 GPU 算出来的梯度是不一样的。因为数据不同嘛。那问题来了——模型参数怎么更新?

答案就是:把所有 GPU 算出来的梯度做个平均,然后用这个平均梯度去更新模型参数。这样每个 GPU 上的模型就保持一致了。

核心公式(简化版):

θ_new = θ_old - lr * (1/N) * Σ(g_i)

其中 g_i 是第 i 个 GPU 算出的梯度,N 是 GPU 数量。

我在项目中遇到过一个问题:数据切分不均匀会导致某些 GPU 空等。比如数据量不能被 GPU 数量整除,最后一块 GPU 分到的数据少,算得快,但得等其他人算完才能做 AllReduce。所以数据切分一定要均匀,最好用 DataLoader 的 drop_last 参数。

梯度同步机制(AllReduce)

梯度同步是数据并行的灵魂。没有它,每个 GPU 就是「各玩各的」。AllReduce 是一种高效的通信原语,专门用来把多个节点上的数据「归约」成一个结果,再广播回去。

常见的 AllReduce 实现有两种:

  • Ring AllReduce:把 GPU 排成一个环,数据在环上转圈。每个 GPU 只和邻居通信。通信量是 O(N),但带宽利用率高。
  • Tree AllReduce:用树形结构做归约和广播。通信量是 O(logN),但容易有单点瓶颈。

我个人习惯用 Ring AllReduce。为什么?因为它在大规模集群上表现更稳定。我记得有一次在 64 卡集群上做测试,Tree AllReduce 在最后几层树节点上出现了网络拥塞,导致整体慢了 30%。换成 Ring 之后,问题就解决了。

下面这张图展示了 Ring AllReduce 的基本流程:

Ring AllReduce 梯度同步流程 GPU 0 GPU 1 GPU 2 GPU 3 梯度分块A 梯度分块B 梯度分块C 梯度分块D 第1步:每个GPU把自己的梯度分成4块 第2步:沿环方向发送一块,接收一块,做累加 第3步:经过N-1轮后,每个GPU持有完整的平均梯度 最终效果:所有GPU上的梯度完全一致,可以直接更新模型参数

你想想看,如果没有 AllReduce,每个 GPU 各自为政,模型参数会迅速发散。有了 AllReduce,大家步调一致,模型才能收敛。

同步更新 vs 异步更新

这是数据并行里一个经典的「二选一」问题。我直接说结论:同步更新更稳定,异步更新更快,但容易出幺蛾子。

对比维度 同步更新 异步更新
梯度一致性 所有GPU梯度严格对齐 每个GPU独立更新,可能有延迟
收敛稳定性 高,等价于扩大batch size 低,可能出现梯度陈旧
训练速度 受最慢GPU拖累 快,不用等别人
实现复杂度 简单,AllReduce后统一更新 复杂,需要处理参数版本冲突
适用场景 GPU性能相近、网络稳定 异构集群、GPU性能差异大

同步更新的逻辑很直观:等所有 GPU 算完梯度,做 AllReduce,拿到平均梯度,然后大家一起更新参数。下一轮迭代开始前,所有 GPU 上的模型参数是完全一样的。

异步更新就不一样了。每个 GPU 算完梯度后,直接去更新全局参数。不用等别人。这听起来很高效,对吧?但有个坑——梯度陈旧问题。

我曾经踩过的坑:有一次在 32 卡集群上跑异步更新,训练 loss 死活降不下去。排查了三天,发现是某个 GPU 因为网络延迟,拿到的参数版本落后了 5 轮。它用旧参数算出的梯度去更新新参数,相当于「用昨天的天气预报决定今天穿什么衣服」。后来我改成同步更新,问题立刻解决。

那什么时候用异步更新呢?我个人建议:

  • GPU 数量少(≤8)且网络好:用同步更新,省心。
  • GPU 数量多(≥64)且异构:可以考虑异步更新,但要做好梯度裁剪和版本控制。
  • 训练大模型(百亿参数以上):同步更新是主流,因为异步更新的参数服务器会成为瓶颈。

小技巧:如果你不确定用哪种,先试试同步更新。PyTorch 的 DistributedDataParallel(DDP)默认就是同步模式。等跑通了,再根据实际瓶颈考虑是否切换到异步。

嗯,最后说一句:数据并行不是银弹。当模型大到单卡放不下时,就得考虑模型并行或流水线并行了。但那是后面章节的内容,今天先到这里。

想要更多资料,联系可可