Skip to content

数据并行:DDP 与 FSDP

数据并行是最直观的分布式训练方案:每个 GPU 持有完整模型副本,处理不同数据子集,通过梯度同步保持参数一致。PyTorch 提供 DDP 和 FSDP 两种实现。

数据并行架构

DistributedDataParallel (DDP)

DDP 是 PyTorch 经典的数据并行实现,每个 GPU 持有完整模型:

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

# 初始化进程组
dist.init_process_group(backend="nccl")
local_rank = int(os.environ["LOCAL_RANK"])

# 创建模型并移到当前 GPU
model = MyModel().cuda(local_rank)
model = DDP(model, device_ids=[local_rank])

# 训练循环
optimizer = torch.optim.AdamW(model.parameters(), lr=1e-4)
for data, target in dataloader:
    data, target = data.cuda(local_rank), target.cuda(local_rank)
    loss = model(data, target)
    loss.backward()  # DDP 自动同步梯度
    optimizer.step()
    optimizer.zero_grad()

DDP 梯度同步机制

DDP 在反向传播时自动触发梯度 AllReduce:

  • 使用 Bucket 机制将参数分组
  • 每个 Bucket 的梯度就绪后立即启动异步 AllReduce
  • 与反向传播计算重叠,减少通信等待

FullyShardedDataParallel (FSDP)

FSDP 将模型参数、梯度和优化器状态分片到各 GPU,仅在计算时聚合:

python
from torch.distributed.fsdp import FullyShardedDataParallel as FSDP
from torch.distributed.fsdp import ShardingStrategy

model = MyModel()

# FSDP 包装
model = FSDP(
    model,
    sharding_strategy=ShardingStrategy.FULL_SHARD,  # ZeRO-3
    device_id=local_rank,
)

# 训练循环与 DDP 相同
for data, target in dataloader:
    loss = model(data.cuda(), target.cuda())
    loss.backward()
    optimizer.step()
    optimizer.zero_grad()

DDP vs FSDP 对比

维度DDPFSDP
参数存储每个 GPU 完整副本分片存储
显存占用O(模型大小)O(模型大小 / N)
通信量梯度 AllReduce参数 AllGather + 梯度 ReduceScatter
适用模型模型能放入单 GPU模型超出单 GPU
实现复杂度
性能(小模型)更优略低(额外 AllGather)
性能(大模型)OOM可训练

选择建议

  • 模型能放入单 GPU:使用 DDP(更简单、小模型性能更优)
  • 模型超出单 GPU:使用 FSDP(分片存储突破显存限制)
  • 极大模型:FSDP + CPU Offload

FSDP 注意事项

FSDP 的 AllGather 通信会增加延迟。对于小模型(<1B),DDP 通常更快。FSDP 在模型 >7B 时优势明显。FSDP 要求模型前向传播中不能跨分片边界共享参数。

相关资源

最近更新