深度学习——分布式训练

1. 前言

最近开始上手进行分布式训练,于是在网上开始搜各种教程和资料,看到了许多写得很好的文章,但是有些文章又不是特别的全面,于是决定对这些文章进行归纳和总结,方便后来者的学习和查阅。这里友情提醒一下,大家查资料的时候不要仅仅局限于百度,有时候在百度上查半天,不如去官方文档或者google搜一下,立马就能看到很多优质的解答。

2.分布式训练的分类

关于分布式训练的分类,网上的分类方法五花八门,目前看的这么多文章中最为清晰的要属这篇文章了大模型LLM之分布式训练,简单来讲,分布式训练方法可以分为以下几类:

  • 数据并行
  • 模型并行(包含流水线并行张量并行
  • 混合并行

关于分布式训练分类大家也可以去看一下Hugging Face上的一篇文章Efficient Training on Multiple GPUs,它的分类方式和我们上面提到的有些不同。

接下来的内容主要来自这篇文章:分布式并行训练(DP、DDP、DeepSpeed、Accelerate、Trainer)

3.不并行(单机单卡)

判断GPU是否可用:device = "cuda" if torch.cuda.is_available() else "cpu"
模型拷贝:model.to(device)
数据拷贝:data.to(device)
模型加载:torch.load(xx.pt, map_location=device)
模型保存:torch.save(model, "model.pt")

4. 数据并行 DP和DDP

Pytorch提供的分布式数据并行有两种Data Parallel(DP)和Distributed Data Parallel(DDP) 目前pytorch官网更推荐大家使用的是DDP模式,详见官方文档Data Parallel官方文档Distributed Data Parallel

4.1 异同点

DP 和DDP在工作模式上有着共同点和差异,这里博采众长,做一下梳理和总结:

  • 共同点:都是数据并行,都是即插即用

  • 不同点:

    • DP:仅支持单机多卡, 显存负载不均衡(device[0]负载大),不支持Apex混合精度训练,GPU利用不均衡,因Python GIL争用影响 仅支持单进程多线程,训练速率低。
    • DDP:支持单机多卡&多机多卡,数据分配均衡,支持Apex混合精度训练,借助All-Reduce的数据交换方式提高GPU的通讯效率,支持多进程,训练速率高,如果模型特别大,DDP也可以和模型并行一起工作,这个在官方教程中有提到

    在这里插入图片描述
    关于DP和DDP的区别还可以看一下这篇有深度的文章:Distributed data parallel training using Pytorch on AWS

4.2 原理

DP原理
在这里插入图片描述

  • 将模型权重从主卡(默认GPU:0)复制到所有GPU上,输入的bacth大小的数据会分散到各个GPU上(每张卡上输入batch / 4,假设有四张卡)。
  • 之后独自在各张卡上执行前向过程,每张卡的输出结果会被聚合到主卡上,然后在主卡上计算总的损失,总损失是一个标量,一般可以求平均值,再通过损失函数求导,得到损失的梯度,分发到各个GPU上。
  • 在每个GPU根据链式法则继续计算各个参数的梯度,再将所有设备上的梯度回传到主卡上进行梯度累加并求梯度均值。在主卡上通过平均梯度更新模型权重。最后将更新后的模型参数 广播 到其余 GPU 中进行下一轮的前向传播。

DDP 原理
DDP背后的实现是依靠Ring-AllReduce 具体细节可以参考这两篇文章:
Pytorch 分布式训练DDP(torch.distributed)详解-原理-代码
分布式训练(中)——DP、DDP模式

4.3 DP 实现(单机多卡)

在Pytorch中torch.nn.DataParallel()包含三个参数:
在这里插入图片描述
module:参数传入具体的模型,比如ResNet,Unet等
device_ids:传入gpu编号例如:[0,1,2,3],默认为None即使用所有GPU
output_device:指定中心设备(参数服务器),用于汇总梯度的 GPU 是哪个,默认device_ids[0]

 os.environ["CUDA_VISIBLE_DEVICES"] = "0,1,2"   #设置可见的gpu
 net=Unet()  #初始化一个模型
 device_ids=[2,0,1]   #gpu编号,这里故意写成2,0,1而不是0,1,2是为了方便大家理解特殊请情况
 net.to(device_ids[0]) #设置中心设备,注意是device_ids[0] 这一行不能少
 net = torch.nn.DataParallel(net, device_ids=[2, 0, 1]) 
 batch_data.to(device_ids[0])  #
 output = net(batch_data) 

模型的加载和保存,为了和单机单卡保持一致,可以这样写:
保存

if isinstance(net,torch.nn.DataParallel):
  torch.save(net.module.state_dict(), "model.pth")
else:
  torch.save(net.state_dict(), "model.pth")

加载

if isinstance(self.model, torch.nn.DataParallel):
	net.module.load_state_dict(torch.load("model.pth"), strict=False)
else:
	net.load_state_dict(torch.load("model.pth"), strict=False)

注:

  • os.environ[“CUDA_VISIBLE_DEVICES”] = “0,1,2” 设置可见的GPU
  • 在上述的代码中net.to(device_ids[0])设置的是中心设备,这一行不能少,这里device_ids=[2,0,1]是为了方便大家理解device_ids[0]不代表一定是cuda:0。
  • torch.nn.DataParallel() 是对模型进行包裹
  • 关于使用中的一些详细的细节可以看这两篇博客torch.nn.DataParallel使用细节pytorch GPU torch.nn.DataParallel (DP)多卡并行

4.4 DDP 实现(单机多卡,多机分布式)

DDP相比于DP要复杂得多,这里首先推荐官网的教程,教程很详细,一定要认真看,官方的教程看明白了再看接下来的内容!!!

官方教程 Tutorials
官方API DistributedDataParallel

4.4.1 DDP 基本概念

WORLD:

  • world 表示包含所有进程的组(所有gpu的集合)。
  • 每个进程通常对应一个 GPU, world 中的进程可以相互通信,这使得使用分布式数据并行(Distributed Data Parallel, DDP)进行训练成为可能。

WORLD_SIZE:

  • world_size 表示分布式训练环境中的总进程数/gpu数。
  • 每个进程都会被分配一个唯一的标识符(rank),从 0 到 world_size-1。

RANK:

  • rank 是分配给world中每个进程的唯一标识符,用于标识每个进程在分布式训练中的角色。

LOCAL RANK:

  • local rank是分配个单个node中每个进程的标识符,world中可能有多个node。每个进程的local_rank都不一样。 判断master GPU:if local_rank == 0:

NODE:

  • node 可以理解为一个服务器,代表着物理设备上的一个实体。
  • 在多机分布式训练中,每台机器被视为一个节点,节点之间需要进行通信。例如,如果有2 个node/server,每个 node/server/machine 各有4张卡(4 gpus)。total_world_size = 2(节点数) * 4(每个节点的 GPU 数量)= 8, rank 的取值范围为 [0, 1, 2, 3, 4, 5, 6, 7], local_rank 的取值范围为 [0, 1, 2, 3],[0, 1, 2, 3] 分别对应着不同的节点上的进程。

这里可以参考这篇博客中的例子,如图所示,有三个node,每个node有4个GPU(则每个node会有四个进程,一个进程对应一个GPU)
在这里插入图片描述

4.4.2 DDP之单机多卡

Pytorch支持的分布式框架的后端主要有 Gloo、MPI 和 NCCL 三种。PyTorch 官方的规则是:如果是分布式 GPU 训练,优先推荐 NCCL:如果是分布式 CPU 训练,优先推荐 Gloo,其次是 MPI。

三种方式:torch.distributed.launch,torch.multiprocessing,torchrun

4.4.2.1 torch.distributed.launch(淘汰)和 torchrun (推荐)
python -m torch.distributed.launch   参数列表1    main.py   argsparse参数列表
torchrun    参数列表1    main.py    argsparse参数列表

关于这两个命令官方有具体说明官方文档

torch.distributed.launch 具体有什么作用?

  1. 从torch.distributed.run中获取parser(一般通过argsparse.ArgumentParser()创建),并再添加一个–use-env(–use_env)参数,代表从环境变量中获取LOCAL RANK值 (argsparse参数列表将不再有–local-rank这个参数)。取而代之,可以在main.py中通过os.environ[‘LOCAL_RANK’]获得。
  2. 从torch.distributed.run中获取run函数,执行run(args),这个args就是参数列表1获取的参数。
  3. 可见,torch.distributed.launchtorchrun的区别仅在于local rank的获取方式,launch有两种,而torchrun只能通过os.environ[‘LOCAL_RANK’]。这是因为pytorch官方虽然说torch.distributed.launch将会被弃用,因为torchrun增加了一些新功能,但实际上他们把torchrun和torch.distributed.launch合并了。

在main.py中可获取的环境变量,来自官方文档
在这里插入图片描述
分布式训练模板代码
https://zhuanlan.zhihu.com/p/561218626
【深度学习实战(27)】「分布式训练」DDP单机多卡并行指南

import  os

import torch.distributed as dist
import argparse
from torch.utils.data import DataLoader
from  torchvision import datasets, transforms
from torchvision.models import resnet18
import torch
import torchvision
os.environ['CUDA_VISIBLE_DEVICES'] = '0,1'

def train(args):
    local_rank=int(os.environ['LOCAL_RANK'])
    world_size=int(os.environ['WORLD_SIZE'])
    print("world_size:", world_size)
    print("local_rank:", local_rank)


    # dist.init_process_group(backend='nccl', init_method=args.init_method, rank=local_rank, world_size=world_size)
    dist.init_process_group(backend='nccl' ,rank=local_rank, world_size=world_size)

    # ......
    transform=transforms.Compose([transforms.Resize((args.image_size,args.image_size)),transforms.ToTensor()])
    trainset = torchvision.datasets.CIFAR10(root='./data', train=True, download=True, transform=transform)
    train_sampler = torch.utils.data.distributed.DistributedSampler(trainset)
    trainloader = torch.utils.data.DataLoader(trainset, batch_size=args.batch_size, sampler=train_sampler)

    # ......

    net = resnet18()
    net.to(f'cuda:{local_rank}')
    net = torch.nn.parallel.DistributedDataParallel(net, device_ids=[local_rank],find_unused_parameters=False)


    net=torch.nn.SyncBatchNorm.convert_sync_batchnorm(net)

    for epoch in range(1,args.epochs+1):
        train_sampler.set_epoch(epoch)
        for batch_idx, (inputs, targets) in enumerate(trainloader):
            inputs, targets = inputs.to(f'cuda:{local_rank}'), targets.to(f'cuda:{local_rank}')
            # outputs = net(inputs)
            # print(outputs.device)
            rank = dist.get_rank()
            print(f"Start running basic DDP example on rank {rank}.")


if __name__ == '__main__':
    parser = argparse.ArgumentParser(description='PyTorch distributed training on cifar-10')
    parser.add_argument('--image_size', type=int, default=224, help='image size')
    parser.add_argument('--batch_size', type=int, default=8, help='batch size')
    parser.add_argument('--epochs', type=int, default=10, help='number of epochs')
    # parser.add_argument('--init_method', type=str, default='tcp://127.0.0.1:23456', help='init method')
    args = parser.parse_args()
    train(args)

torchrun --nnodes=1 --nproc_per_node=2 main.py

torchrun常用参数

4.4.2.2 torch.multiprocessing

Multi-GPU Training in PyTorch with Code (Part 3): Distributed Data Parallel

在训练过程中难免会遇到一些问题,这时候需要调试,可以参考这篇文章:pycharm进行torchrun的调试

4.4.4 DDP之多机分布式

一般情况下不会用到这种情况所以,我们这里不做讲解,感兴趣的可以看这篇博客:PyTorch分布式训练基础–DDP使用

参考文献

大模型LLM之分布式训练
A Survey of Large Language Models
Pytorch 分布式训练DDP(torch.distributed)详解-原理-代码
Efficient Training on Multiple GPUs
分布式训练(中)——DP、DDP模式
torch.nn.DataParallel使用细节
pytorch GPU torch.nn.DataParallel (DP)多卡并行
DistributedDataParallel
Tutorials
pycharm进行torchrun的调试
PyTorch分布式训练基础–DDP使用
Distributed data parallel training using Pytorch on AWS
torchrun

DDP
分布式并行训练(DP、DDP、DeepSpeed、Accelerate、Trainer)!!!!!!!
Pytorch多卡训练
分布式训练(上)——rank local_rank node

  • 20
    点赞
  • 8
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

千禧皓月

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值