pytorch一机多卡训练

1. 一机多卡(one matchine multi-GPU)

1.1 DataParallel

DataParallel(DP):Parameter Server模式,一张卡位reducer,实现也超级简单,一行代码。 有个不能接受的缺陷是:DataParallel是基于Parameter server的算法,所有的loss都在主卡上计算,负载不均衡的问题比较严重,有时在模型较大的时候(比如bert-large),主卡占满了,其他的卡一半都占不到,及其浪费资源。

值得注意的是,模型和数据都需要先 loadGPU 中,DataParallelmodule 才能对其进行处理,否则会报错:

示例代码:

# coding=utf-8

import torch
import torch.nn as nn
from torch.autograd import Variable
from torch.utils.data import Dataset, DataLoader

class RandomDataset(Dataset):
    def __init__(self, size, length):
        self.len = length
        self.data = torch.randn(length, size)

    def __getitem__(self, index):
        return self.data[index]

    def __len__(self):
        return self.len

class Model(nn.Module):
    def __init__(self, input_size, output_size):
        super(Model, self).__init__()
        self.fc = nn.Linear(input_size, output_size)

    def forward(self, input):
        output = self.fc(input)
        return output

input_size = 5
output_size = 2
batch_size = 30
data_size = 30

dataset = RandomDataset(input_size, data_size)
rand_loader = DataLoader(dataset=dataset,
                         batch_size=batch_size, shuffle=True)
model = Model(input_size, output_size)

if torch.cuda.is_available():
    model.cuda()

if torch.cuda.device_count() > 1:
    model = nn.DataParallel(model)  # 关键代码

for data in rand_loader:
    if torch.cuda.is_available():
        input_var = Variable(data.cuda())
    else:
        input_var = Variable(data)
    output = model(input_var)
1.2 DistributedDataParallel

是的,你没有看错,这个函数是为了分布式训练设计的。但是,即使在单机多卡上,官方也建议使用新的DistributedDataParallel,采用all-reduce算法。

(1)初始化后端

torch.cuda.set_device(args.local_rank)
torch.distributed.init_process_group(backend='nccl', init_method='env://')

(2)模型并行化 这里也很简单,使用DistributedDataParallel函数warp一下就可以:

model = torch.nn.parallel.DistributedDataParallel(model, device_ids=[local_rank], output_device=local_rank)

(3)数据并行 这里需要注意,如果指定了sampler,则shuffle=False,其中DataLoader的num_worker是每一个卡独立设置。

dataset = RandomDataset(input_size, data_size)
sampler = torch.utils.data.distributed.DistributedSampler(dataset)
rand_loader = DataLoader(dataset=dataset,
                         batch_size=batch_size, shuffle=False, sampler=sampler)

(4)启动脚本

python -m torch.distributed.launch --nproc_per_node=8 train_face_troch.py

完整代码示例:

# coding=utf-8

import torch
import torch.distributed
import torch.nn as nn
from torch.autograd import Variable
from torch.utils.data import Dataset, DataLoader
import apex
import argparse

class RandomDataset(Dataset):
    def __init__(self, size, length):
        self.len = length
        self.data = torch.randn(length, size)
        self.label = torch.mean(self.data, dim=-1)

    def __getitem__(self, index):
        return self.data[index], self.label[index]

    def __len__(self):
        return self.len

class Model(nn.Module):
    def __init__(self, input_size, output_size):
        super(Model, self).__init__()
        self.fc = nn.Linear(input_size, output_size)

    def forward(self, input):
        output = self.fc(input)
        return output

def parse_args():
    parser = argparse.ArgumentParser()
    parser.add_argument('--local_rank', default=0, type=int)
    args = parser.parse_args()
    return args

input_size = 5
output_size = 2
batch_size = 30
data_size = 30

args = parse_args()
local_rank = args.local_rank

torch.cuda.set_device(local_rank) # 设定cuda的默认GPU,每个rank不同
torch.distributed.init_process_group(backend='nccl', init_method='env://')

dataset = RandomDataset(input_size, data_size)
sampler = torch.utils.data.distributed.DistributedSampler(dataset)
rand_loader = DataLoader(dataset=dataset,
                         batch_size=batch_size, shuffle=False, sampler=sampler)

model = Model(input_size, output_size)

if torch.cuda.is_available():
    model.cuda()

model = torch.nn.parallel.DistributedDataParallel(model, device_ids=[local_rank], output_device=local_rank)

optimizer = torch.optim.Adam(model.parameters())
criterion = torch.nn.CrossEntropyLoss()

# if torch.cuda.device_count() > 1:
#    model = nn.DataParallel(model)

for data, label in rand_loader:
    data = data.cuda()
    label = label.cuda()

    output = model(data)
    loss = criterion(output, label)

    optimizer.zero_grad()
    loss.backward()
    optimizer.step()

使用 torch.multiprocessing 取代启动器

有的同学可能比较熟悉 torch.multiprocessing,也可以手动使用 torch.multiprocessing
进行多进程控制。绕开 torch.distributed.launch 自动控制开启和退出进程的一些小毛病~

使用时,只需要调用 torch.multiprocessing.spawntorch.multiprocessing 就会帮助我们自动创建进程。如下面的代码所示,spawn 开启了 nprocs=4 个线程,每个线程执行 main_worker 并向其中传入 local_rank(当前进程 index)和 args(即 4myargs)作为参数:

import torch.multiprocessing as mp

mp.spawn(main_worker, nprocs=4, args=(4, myargs))

这里,我们直接将原本需要 torch.distributed.launch 管理的执行内容,封装进 main_worker 函数中,其中 proc 对应 local_rank(当前进程 index),ngpus_per_node 对应 4args 对应 myargs

def main_worker(proc, ngpus_per_node, args):

   dist.init_process_group(backend='nccl', init_method='tcp://127.0.0.1:23456', world_size=4, rank=gpu)
   torch.cuda.set_device(args.local_rank)

   train_dataset = ...
   train_sampler = torch.utils.data.distributed.DistributedSampler(train_dataset)

   train_loader = torch.utils.data.DataLoader(train_dataset, batch_size=..., sampler=train_sampler)

   model = ...
   model = torch.nn.parallel.DistributedDataParallel(model, device_ids=[args.local_rank])

   optimizer = optim.SGD(model.parameters())

   for epoch in range(100):
      for batch_idx, (data, target) in enumerate(train_loader):
          images = images.cuda(non_blocking=True)
          target = target.cuda(non_blocking=True)
          ...
          output = model(images)
          loss = criterion(output, target)
          ...
          optimizer.zero_grad()
          loss.backward()
          optimizer.step()

在上面的代码中值得注意的是,由于没有 torch.distributed.launch 读取的默认环境变量作为配置,我们需要手动为 init_process_group 指定参数:

dist.init_process_group(backend='nccl', init_method='tcp://127.0.0.1:23456', world_size=4, rank=gpu)

汇总一下,添加 multiprocessing 后并行训练部分主要与如下代码段有关:

# main.py
import torch
import torch.distributed as dist
import torch.multiprocessing as mp

mp.spawn(main_worker, nprocs=4, args=(4, myargs))

def main_worker(proc, ngpus_per_node, args):

   dist.init_process_group(backend='nccl', init_method='tcp://127.0.0.1:23456', world_size=4, rank=gpu)
   torch.cuda.set_device(args.local_rank)

   train_dataset = ...
   train_sampler = torch.utils.data.distributed.DistributedSampler(train_dataset)

   train_loader = torch.utils.data.DataLoader(train_dataset, batch_size=..., sampler=train_sampler)

   model = ...
   model = torch.nn.parallel.DistributedDataParallel(model, device_ids=[args.local_rank])

   optimizer = optim.SGD(model.parameters())

   for epoch in range(100):
      for batch_idx, (data, target) in enumerate(train_loader):
          images = images.cuda(non_blocking=True)
          target = target.cuda(non_blocking=True)
          ...
          output = model(images)
          loss = criterion(output, target)
          ...
          optimizer.zero_grad()
          loss.backward()
          optimizer.step()

在使用时,直接使用 python 运行就可以了:

python main.py
### 使用个GPU进行PyTorch Lightning训练 对于希望利用GPU加速模型训练的研究者和开发者来说,PyTorch Lightning 提供了个简洁高效的接口。所有 PyTorch Lightning 的代码库围绕着几个抽象概念构建[^2]。 #### 设置环境变量 在启动训练之前,确保操作系统配置正确支持GPU操作。这通常意味着为主板配备足够的 PCIe 插槽数量以适应计划使用的 GPU 数目;值得注意的是,每增加块 GPU 可能会占用额外的空间,例如个 GPU 需要两个插槽,并且每个系统最可以安装四个 GPU[^3]。 #### 初始化Trainer对象 为了启用GPU并行计算,在初始化 `Trainer` 对象时指定相应的参数即可: ```python from pytorch_lightning import Trainer, seed_everything import torch seed_everything(42) trainer = Trainer( accelerator='gpu', devices=torch.cuda.device_count(), # 自动检测可用的GPU数量 strategy="ddp" # 或 "dp", "ddp_spawn" ) ``` 这里的关键在于设置 `accelerator` 参数为 `'gpu'` 并通过 `devices` 来指明想要使用的设备数目或具体ID列表。此外,还可以选择不同的分布式策略(`strategy`)如 DDP (Distributed Data Parallel),这是推荐的方式因为它提供了更好的性能和稳定性。 #### 数据准备与加载 当涉及到跨个GPU的数据分发时,建议使用 PyTorch 的内置 DataLoader 类来管理数据集划分以及批处理逻辑。这样做的好处是可以让框架自动处理好各节点间的数据同步问题。 ```python from torchvision.datasets import MNIST from torchvision.transforms import ToTensor from torch.utils.data import DataLoader train_dataset = MNIST(root='./data', train=True, download=True, transform=ToTensor()) val_dataset = MNIST(root='./data', train=False, download=True, transform=ToTensor()) train_loader = DataLoader(train_dataset, batch_size=64, shuffle=True, num_workers=8) val_loader = DataLoader(val_dataset, batch_size=64, shuffle=False, num_workers=8) ``` #### 定义模型类 最后步就是定义继承自 `LightningModule` 的模型类,其中包含了前向传播、损失函数定义及优化器配置等内容。这部分内容与其他单版实现基本致,无需特别修改就能很好地运行于GPU环境中。 ```python from pytorch_lightning.core.lightning import LightningModule import torch.nn as nn import torch.optim as optim class SimpleModel(LightningModule): def __init__(self): super().__init__() self.model = nn.Sequential( nn.Linear(784, 10), nn.ReLU(), nn.Linear(10, 10) ) def forward(self, x): return self.model(x.view(-1, 784)) def configure_optimizers(self): optimizer = optim.Adam(self.parameters(), lr=1e-3) return optimizer def training_step(self, batch, batch_idx): inputs, labels = batch outputs = self(inputs) loss = F.cross_entropy(outputs, labels) logs = {'loss': loss} return {"loss": loss, 'log': logs} model = SimpleModel() ``` 完成上述步骤之后就可以调用 trainer.fit() 方法开始正式训练过程了。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值