1. 数据并行概述:什么是数据并行、为什么需要数据并行、数据并行的基本工作流程

1.1 什么是数据并行

数据并行,说白了就是「把数据切开,分给多个工人同时干活」。

想象一下,你有一万张图片要训练。单卡训练时,一张卡要处理全部一万张。数据并行呢?四张卡,每张只处理两千五百张。每张卡上的模型是完全一样的副本,只是喂进去的数据不同。

我刚开始接触这个概念时,总觉得这不就是「多线程」吗?其实不一样。多线程是在同一块GPU上抢资源,数据并行是让多块GPU各干各的,最后再同步一下梯度。嗯,这个区别很重要。

核心定义:数据并行是一种分布式训练策略,将训练数据切分成多个小批次(mini-batch),分发到多个设备上并行计算梯度,再通过梯度同步更新模型参数。

你想想看,如果一张卡能装下整个模型,但数据量太大,训练太慢——这时候数据并行就是最直接的加速方案。

1.2 为什么需要数据并行

这个问题我问过不少新人。答案往往很直接:「因为快啊。」没错,但不止于此。

原因一:训练时间太长

我在项目中遇到过最夸张的一次,单卡训练一个ResNet-50在ImageNet上,要跑两周。换成8卡数据并行,两天就搞定了。你说香不香?

原因二:显存不够用

虽然模型能装下,但batch size一大,显存就爆了。数据并行允许你每张卡用小batch,整体上却能达到大batch的效果。

原因三:资源利用率

现在的GPU集群动辄几十上百张卡。你只跑一张卡,其他卡闲着?老板会找你谈话的。

场景 单卡训练 数据并行(4卡)
训练时间(1万张图) 10小时 约2.5小时
单卡显存占用 8GB 2GB
总batch size 64 256

小提示:数据并行不是万能的。如果你的模型大到一张卡都放不下,那就得考虑模型并行或者流水线并行了。这个我们后面章节会讲。

1.3 数据并行的基本工作流程

数据并行的工作流程,我习惯把它拆成四个步骤。你记住这四个字就行:分、算、聚、更。

第一步:分(数据分发)

主进程(或者叫rank 0)把当前batch的数据切分成N份,分发给N张卡。每张卡拿到的是完整数据的一个子集。

第二步:算(前向+反向)

每张卡独立做前向传播,计算loss,然后反向传播得到梯度。注意,这时候每张卡上的梯度是不一样的——因为喂进去的数据不同。

第三步:聚(梯度同步)

这是最关键的一步。所有卡把自己的梯度发出去,通过AllReduce操作求平均。最终每张卡拿到的是所有卡梯度的平均值。

第四步:更(参数更新)

每张卡用平均后的梯度更新自己的模型参数。因为初始参数相同,梯度也相同,所以更新后的参数仍然保持一致。

注意:我曾经踩过一个坑——忘记在反向传播后做梯度同步,结果每张卡各自为政,模型越跑越偏。调试了两天才发现是同步代码没写对。嗯,这种低级错误希望大家别犯。

下面这张图,我画的是4卡数据并行的流程。你看一眼就能明白整个链路:

训练数据 数据分发 GPU 0 数据子集 0 GPU 1 数据子集 1 GPU 2 数据子集 2 AllReduce 梯度同步 参数更新

你看,从数据分发到梯度同步,再到参数更新,整个流程是串行的。但每张卡上的前向和反向计算是并行的——这才是加速的关键。

1.4 一个简单的代码示例

下面我用PyTorch写一个最简的数据并行示例。注意,这只是演示逻辑,实际工程中会用DistributedDataParallel(DDP)。

import torch
import torch.nn as nn
import torch.distributed as dist
from torch.nn.parallel import DistributedDataParallel as DDP

# 初始化进程组
dist.init_process_group(backend='nccl')
local_rank = dist.get_rank()
torch.cuda.set_device(local_rank)

# 创建模型并包装
model = nn.Linear(10, 5).cuda()
model = DDP(model, device_ids=[local_rank])

# 数据:每张卡只拿自己那份
data = torch.randn(32, 10).cuda()  # 实际会由DataLoader按rank切分
labels = torch.randn(32, 5).cuda()

# 前向 + 反向
output = model(data)
loss = output.sum()
loss.backward()

# DDP会自动做梯度同步,不用手动写AllReduce
# 优化器更新
optimizer.step()
optimizer.zero_grad()

个人经验:我第一次用DDP时,忘了设置环境变量RANK和WORLD_SIZE,结果程序一直卡在初始化。后来养成习惯,每次启动前先检查环境变量对不对。你如果也遇到类似问题,先看看这个。

1.5 数据并行的适用场景

不是所有场景都适合数据并行。我总结了几条判断标准:

  • 模型能装进单卡显存——这是前提。装不下就别想了。
  • 数据量足够大——如果数据只有几百条,单卡几秒就跑完,没必要折腾多卡。
  • batch size可以比较大——数据并行会放大batch size,太大的batch可能影响收敛。

我曾经在一个小项目里强行上数据并行,结果通信开销比计算还大,反而更慢了。嗯,有时候「少即是多」。

好了,这一章就到这里。数据并行是分布式训练的基础,后面的章节我们会深入AllReduce的实现细节、同步与异步的取舍、以及各种工程优化技巧。

想要更多资料,联系可可