使用 PyTorch 进行分布式计算

编辑|我不爱机器学习

1. 相关概念

  • Process :python的一个实例。可以使用进程控制GPU。简单理解:进程个数=使用的GPU的个数
  • Node :一个节点就像一台拥有所有资源的计算机。简单理解:多个节点=多台电脑/主机
  • World-Size :可用的GPU总数。它是节点总数 和每个节点的 GPU 总数的乘积。例如,如果有两台服务器,每台服务器有两个 GPU,则World-Size为 4。
  • Rank :它是在所有进程中标识进程的ID。例如,如果有两个节点服务器每个有四个 GPU,排名则为不同的0 - 7。Rank 0将识别进程0等等。
  • Local Rank :Rank是用来识别所有节点(主机个数*每个主机的GPU个数),而Local Rank是用来识别本地服务器,比如节点2上的某个进程的rank可以为2和0,则可以理解为,在所有进程中排名为2(global rank),在本地机器上中排名为0。

PyTorch 中的多进程处理

python 复制代码
torch.multiprocessing.spawn(fn, 
                            args=(), 
                            nprocs=1, 
                            join=True, 
                            daemon=False, 
                            start_method='spawn')

它用于产生nprocs给定的进程数。这些进程使用args运行fn。此函数可用于在每个 GPU 上训练模型。

举个例子。假设有一个节点服务器有两个 GPU。

  • 可以创建一个函数fn来处理训练部分。

  • nprocs将等于两个,即 GPU 的数量。

因此,torch.multiprocessing.spawn可通过args在每个GPU上训练函数fn。这可以在每个节点/服务器上完成。

接下来,介绍进程将如何相互协调。

分布式并行DDP

在 DDP 中,模型在每个 GPU 上复制,每个 GPU 由一个进程处理。

DDP 需要知道以下信息:

  • 节点数量和每个节点中的 GPU 数量。可以从这些信息中得到World-Size。
  • 使用 DDP 进行训练需要进程之间的同步和通信。这是通过 distributed.init_process_group实现的。
pyton 复制代码
os.environ['MASTER_ADDR'] = '19.16.19.19'
os.environ['MASTER_PORT'] = '8888'
dist.init_process_group(backend='nccl', init_method='env://', world_size=world_size, rank=rank)

init_process_group

  • 需要 master 的详细信息。这些已设置为环境变量。它用于所有进程的同步。

  • 还需要 world_size 以及进程 rank,注意不是local rank。

可以将所有这些信息作为torch.multiprocessing.spawn中的args传递。rank 可以通过:node_rank × gpus_per_node + local_rank 找到。

注意,local rank 的变化范围是 0,nprocs。

分布式采样器

在 DDP 中,每个进程都处理一个 GPU,每个 GPU 使用数据集的单独某块来训练数据集。例如,如果有两个 GPU 和 100 个训练样本,批量大小为 50,那么每个 GPU 将使用 50 个非重叠训练样本。这是通过 PyTorch 提供的DistributedSampler实现的。它确保每个 GPU 都使用数据集的单独子集。它需要 world_size 和 进程的rank/global rank。

在 PyTorch 中使用分布式计算:

python 复制代码
def get_dataset(world_size, global_rank, batch_size):

    transform_train = transforms.Compose([
    transforms.Resize((224,224)),
    transforms.ToTensor(),
    transforms.Normalize(mean=[0.456, 0.456, 0.456], std=[0.224, 0.224, 0.224])])

    train_dataset = torchvision.datasets.CIFAR10(root='./data', train=True, transform=transform_train, download=True)

    train_sampler=torch.utils.data.distributed.DistributedSampler(train_dataset,\
    num_replicas=world_size,rank=global_rank)

    train_loader = torch.utils.data.DataLoader(dataset=train_dataset, \
        batch_size=batch_size, shuffle=False, num_workers=0, pin_memory=True,\
        sampler=train_sampler)

    return train_loader

使用train_sampler 将不同样本传递给每个 GPU。

接下来,编写一个函数来训练模型:

python 复制代码
def train(local_rank, args):
    ## global_rank is the global rank of the process (among all the GPUs (not just on a particular node). )
    global_rank = args.node_rank * args.gpus + local_rank
    dist.init_process_group(backend='nccl', init_method='env://', world_size=args.world_size, rank=global_rank)
    torch.manual_seed(0)
    torch.cuda.set_device(local_rank)   
    batch_size = 64
    criterion = nn.CrossEntropyLoss().cuda(local_rank)

    model=models.resnet18(pretrained=True)
    model.fc=nn.Linear(512, 10)
    model.cuda(local_rank)
    model = nn.parallel.DistributedDataParallel(model, device_ids=[local_rank])

    optimizer = torch.optim.SGD(model.parameters(), lr=1e-3)

    train_loader=get_dataset(args.world_size, global_rank, batch_size)
    
    for epoch in range(10):
        for i, (images, labels) in enumerate(train_loader):
            images = images.cuda(non_blocking=True)
            labels = labels.cuda(non_blocking=True)
            # Forward pass
            outputs = model(images)
            loss = criterion(outputs, labels)

            # Backward and optimize
            optimizer.zero_grad()
            loss.backward()
            optimizer.step()
            ## if this condition is not used, output will print for each process (GPU)
            if local_rank == 0:
                print('Epoch [{}/{}],Loss: {}'.format(epoch + 1, 10,loss.item()))

最后,使用 multiprocessing 的spawn训练函数:

python 复制代码
parser = argparse.ArgumentParser()
parser.add_argument('--total_nodes', default=1, type=int, help='total number of the nodes')
parser.add_argument('--gpus', default=1, type=int, help='number of gpus per node')
parser.add_argument('--node_rank', default=0, type=int, help='rank of present node (server).')
args = parser.parse_args()

args.world_size = args.gpus * args.total_nodes
os.environ['MASTER_ADDR'] = '19.16.19.19'
os.environ['MASTER_PORT'] = '8888'
mp.spawn(train, nprocs=args.gpus, args=(args,))

然后可以通过在每个节点上使用合适的参数运行脚本来使用 DDP。例如,如果有 2 个节点,每个节点有 4 个 GPU:

节点服务器0 将是:

--total_nodes 2 --gpus 4 --node_rank 0

对于节点 1:

--total_nodes 2 --gpus 4 --node_rank 1

请注意,数据集必须存在于每个节点上。

简单的方法

使用torch.distributed.launch可以大大简化上述方法。它将负责spawn过程。我们只需要解析命令行参数:--local_rank,其余的将由该模块处理。修改后的脚本如下:

python 复制代码
parser = argparse.ArgumentParser()
parser.add_argument("--local_rank", type=int, default=-1, help="local_rank for distributed training on gpus")
args = parser.parse_args()

os.environ['MASTER_ADDR'] = '19.16.19.19'
os.environ['MASTER_PORT'] = '8888'

dist.init_process_group(backend='nccl', init_method='env://')
torch.manual_seed(0)

torch.cuda.set_device(args.local_rank)

model=models.resnet18(pretrained=True)
model.fc=nn.Linear(512, 10)
model.cuda(args.local_rank)
model = nn.parallel.DistributedDataParallel(model, device_ids=[args.local_rank])


batch_size = 64
criterion = nn.CrossEntropyLoss().cuda(args.local_rank)
optimizer = torch.optim.SGD(model.parameters(), lr=1e-3)
train_loader=get_dataset( batch_size)

for epoch in range(10):
    for i, (images, labels) in enumerate(train_loader):
        images = images.cuda(non_blocking=True)
        labels = labels.cuda(non_blocking=True)
        # Forward pass
        outputs = model(images)
        loss = criterion(outputs, labels)

        # Backward and optimize
        optimizer.zero_grad()
        loss.backward()
        optimizer.step()
        ## if this condition is not used, output will print for each process (GPU)
        if args.local_rank == 0:
            print('Epoch [{}/{}],Loss: {}'.format(epoch + 1, 10,loss.item()))

在节点 0 上,此脚本可写作:

python 复制代码
python -m torch.distributed.launch --nnodes 2  --node_rank 0 --nproc_per_node=2 train_distributed_2.py

在节点 1 上:

python 复制代码
python -m torch.distributed.launch --nnodes 2  --node_rank 1 --nproc_per_node=2 train_distributed_2.py

同样,对于其他节点也是如此。主地址(master address),也可以作为此模块的参数传递。

python 复制代码
--master_addr=127.0.0.2 --master_port=29502

还有一些关于模型保存以及DDP使用技巧,可以参考:

--master_addr=127.0.0.2 --master_port=29502

参考:

https://shivgahlout.github.io/2021-05-18-distributed-computing/

https://github.com/shivgahlout/DDP-Pytorch

https://github.com/jia-zhuang/pytorch-multi-gpu-training

相关推荐
摸鱼仙人~2 小时前
从 AI 应用开发到 GPU 计算软件:一份完整的 GPGPU、PyTorch、vLLM 与 AI Infra 学习路线
人工智能·pytorch·vllm
计算机毕业编程指导师1 天前
计算机毕设推荐:基于深度学习的动物图像识别方法研究(Python+Django+PyTorch)
pytorch·python·深度学习·计算机·毕业设计·课程设计·图像识别
zh路西法2 天前
【四足强化学习经典算法】(一):RMA——盲视运动控制与快速适应模块
pytorch·强化学习·1024程序员节·四足机器人·dagger·pyhon·rma
Be for thing2 天前
PyCharm安装与PyTorch项目配置|Jupyter Notebook与PyCharm使用对比
pytorch·学习·1024程序员节
zh路西法3 天前
【模型式强化学习】(二)PETS——从概率动力学模型到模型预测控制(MPC)
pytorch·python·强化学习·pe·mpc·ces·pets
段一凡-华北理工大学3 天前
高炉炼铁机器视觉与智能识别十八讲~系列文章17:实战案例:一个风口监控系统的完整搭建
人工智能·pytorch·深度学习·机器学习·计算机视觉·高炉风口监测系统
Yolanda_20223 天前
PyTorch 线性层nn.Linear底层数学原理
人工智能·pytorch·python
专注仿真3 天前
多智能体强化学习实战:从 Dec-POMDP 到 QMIX/MADDPG/MAPPO 的可运行 PyTorch 实现
人工智能·pytorch·python
Web3&Basketball4 天前
TorchTPU 迁移实战:3 个 CUDA 卡点
pytorch·性能调优·cuda·tpu·模型迁移·torchtpu
田里的水稻4 天前
FA_CNN卷积神经网络十二_CIFAR10全流程PyTorch
人工智能·pytorch·深度学习·神经网络·cnn