PyTorch 分布式数据并行入门
在深度学习模型的训练过程中,数据并行是一种常见的加速训练的方法。PyTorch 提供了 DistributedDataParallel(DDP)模块,用于实现高效的多 GPU 分布式训练。DDP 通过使用 torch.distributed 包中的通信集合来同步梯度、参数和缓冲区,支持在单机或跨多机的多进程环境中进行训练。本文将带您深入了解 PyTorch 分布式数据并行的基本概念、实现方法和优化技巧,帮助您高效地利用多 GPU 资源加速模型训练。
一、DDP 与 DataParallel 的比较
DDP 与 DataParallel 相比具有显著的优势:
- 模型并行支持 :DDP 可与模型并行结合使用,而
DataParallel不支持。当模型规模过大无法放入单个 GPU 时,DDP 能更好地处理这种情况。 - 多进程支持 :
DataParallel是单进程多线程,仅限单机;DDP 是多进程,支持单机多 GPU 和多机多 GPU 训练,训练速度更快。 - 性能优化 :DDP 预先复制模型,避免了每次迭代的模型复制和全局解释器锁定,性能更优。
二、DDP 基本用法
(一)环境设置
在使用 DDP 之前,需要正确设置分布式环境。以下代码展示了如何初始化和清理进程组:
import os
import torch
import torch.distributed as dist
import torch.nn as nn
import torch.optim as optim
import torch.multiprocessing as mp
from torch.nn.parallel import DistributedDataParallel as DDP
def setup(rank, world_size):
os.environ['MASTER_ADDR'] = 'localhost'
os.environ['MASTER_PORT'] = '12355'
dist.init_process_group("gloo", rank=rank, world_size=world_size)
torch.manual_seed(42)
def cleanup():
dist.destroy_process_group()
(二)定义模型并封装 DDP
将定义好的模型封装到 DDP 中,以实现分布式训练:
class ToyModel(nn.Module):
def __init__(self):
super(ToyModel, self).__init__()
self.net1 = nn.Linear(10, 10)
self.relu = nn.ReLU()
self.net2 = nn.Linear(10, 5)
def forward(self, x):
return self.net2(self.relu(self.net1(x)))
def demo_basic(rank, world_size):
setup(rank, world_size)
n = torch.cuda.device_count() // world_size
device_ids = list(range(rank * n, (rank + 1) * n))
model = ToyModel().to(device_ids[0])
ddp_model = DDP(model, device_ids=device_ids)
loss_fn = nn.MSELoss()
optimizer = optim.SGD(ddp_model.parameters(), lr=0.001)
optimizer.zero_grad()
outputs = ddp_model(torch.randn(20, 10))
labels = torch.randn(20, 5).to(device_ids[0])
loss_fn(outputs, labels).backward()
optimizer.step()
cleanup()
def run_demo(demo_fn, world_size):
mp.spawn(demo_fn, args=(world_size,), nprocs=world_size, join=True)
三、保存和加载检查点
在分布式训练中,检查点的保存和加载可以提高训练的可靠性和恢复能力。DDP 提供了优化的方法来保存和加载模型:
import tempfile
def demo_checkpoint(rank, world_size):
setup(rank, world_size)
n = torch.cuda.device_count() // world_size
device_ids = list(range(rank * n, (rank + 1) * n))
model = ToyModel().to(device_ids[0])
ddp_model = DDP(model, device_ids=device_ids)
loss_fn = nn.MSELoss()
optimizer = optim.SGD(ddp_model.parameters(), lr=0.001)
CHECKPOINT_PATH = tempfile.gettempdir() + "/model.checkpoint"
if rank == 0:
torch.save(ddp_model.state_dict(), CHECKPOINT_PATH)
dist.barrier()
rank0_devices = [x - rank * len(device_ids) for x in device_ids]
device_pairs = zip(rank0_devices, device_ids)
map_location = {'cuda:%d' % x: 'cuda:%d' % y for x, y in device_pairs}
ddp_model.load_state_dict(torch.load(CHECKPOINT_PATH, map_location=map_location))
optimizer.zero_grad()
outputs = ddp_model(torch.randn(20, 10))
labels = torch.randn(20, 5).to(device_ids[0])
loss_fn(outputs, labels).backward()
optimizer.step()
dist.barrier()
if rank == 0:
os.remove(CHECKPOINT_PATH)
cleanup()
四、DDP 与模型并行结合
DDP 还可以与多 GPU 模型结合使用,以支持更大的模型和更多的数据:
class ToyMpModel(nn.Module):
def __init__(self, dev0, dev1):
super(ToyMpModel, self).__init__()
self.dev0 = dev0
self.dev1 = dev1
self.net1 = torch.nn.Linear(10, 10).to(dev0)
self.relu = torch.nn.ReLU()
self.net2 = torch.nn.Linear(10, 5).to(dev1)
def forward(self, x):
x = x.to(self.dev0)
x = self.relu(self.net1(x))
x = x.to(self.dev1)
return self.net2(x)
def demo_model_parallel(rank, world_size):
setup(rank, world_size)
dev0 = rank * 2
dev1 = rank * 2 + 1
mp_model = ToyMpModel(dev0, dev1)
ddp_mp_model = DDP(mp_model)
loss_fn = nn.MSELoss()
optimizer = optim.SGD(ddp_mp_model.parameters(), lr=0.001)
optimizer.zero_grad()
outputs = ddp_mp_model(torch.randn(20, 10))
labels = torch.randn(20, 5).to(dev1)
loss_fn(outputs, labels).backward()
optimizer.step()
cleanup()
五、总结与展望
本文详细介绍了 PyTorch 中分布式数据并行的基本概念、实现方法和优化技巧,包括 DDP 与 DataParallel 的比较、DDP 的基本用法、保存和加载检查点的方法,以及 DDP 与模型并行的结合使用。通过这些内容,您可以利用多 GPU 资源高效地加速深度学习模型的训练过程。未来,您可以进一步探索更复杂的分布式训练场景和优化策略,以满足更大规模模型和数据集的训练需求。编程狮将持续为您提供更多深度学习分布式训练的优质教程,助力您的技术成长之路。