点击上方“视学算法”,选择加"星标"或“置顶

重磅干货,第一时间送达

仅作学术分享,不代表本公众号立场,侵权联系删除

转载于:记忆的迷谷@知乎(已授权)
来源 | https://zhuanlan.zhihu.com/p/402198819
编辑 | 极市平台

为什么要使用多GPU并行训练

简单来说,有两种原因:第一种是模型在一块GPU上放不下,两块或多块GPU上就能运行完整的模型(如早期的AlexNet)。第二种是多块GPU并行计算可以达到加速训练的效果。想要成为“炼丹大师“,多GPU并行训练是不可或缺的技能。

常见的多GPU训练方法:

1.模型并行方式:如果模型特别大,GPU显存不够,无法将一个显存放在GPU上,需要把网络的不同模块放在不同GPU上,这样可以训练比较大的网络。(下图左半部分)

2.数据并行方式:将整个模型放在一块GPU里,再复制到每一块GPU上,同时进行正向传播和反向误差传播。相当于加大了batch_size。(下图右半部分)

在pytorch1.7 + cuda10 + TeslaV100的环境下,使用ResNet34,batch_size=16, SGD对花草数据集训练的情况如下:使用一块GPU需要9s一个epoch,使用两块GPU是5.5s, 8块是2s。这里有一个问题,为什么运行时间不是9/8≈1.1s ? 因为使用GPU数量越多,设备之间的通讯会越来越复杂,所以随着GPU数量的增加,训练速度的提升也是递减的。

误差梯度如何在不同设备之间通信?

在每个GPU训练step结束后,将每块GPU的损失梯度求平均,而不是每块GPU各计算各的。

BN如何在不同设备之间同步?

假设batch_size=2,每个GPU计算的均值和方差都针对这两个样本而言的。而BN的特性是:batch_size越大,均值和方差越接近与整个数据集的均值和方差,效果越好。使用多块GPU时,会计算每个BN层在所有设备上输入的均值和方差。如果GPU1和GPU2都分别得到两个特征层,那么两块GPU一共计算4个特征层的均值和方差,可以认为batch_size=4。注意:如果不用同步BN,而是每个设备计算自己的批次数据的均值方差,效果与单GPU一致,仅仅能提升训练速度;如果使用同步BN,效果会有一定提升,但是会损失一部分并行速度。

下图为单GPU、以及是否使用同步BN训练的三种情况,可以看到使用同步BN(橙线)比不使用同步BN(蓝线)总体效果要好一些,不过训练时间也会更长。使用单GPU(黑线)和不使用同步BN的效果是差不多的。

两种GPU训练方法:DataParallel 和 DistributedDataParallel:

  • DataParallel是单进程多线程的,仅仅能工作在单机中。而DistributedDataParallel是多进程的,可以工作在单机或多机器中。

  • DataParallel通常会慢于DistributedDataParallel。所以目前主流的方法是DistributedDataParallel。

pytorch中常见的GPU启动方式:

注:distributed.launch方法如果开始训练后,手动终止程序,最好先看下显存占用情况,有小概率进程没kill的情况,会占用一部分GPU显存资源。

下面以分类问题为基准,详细介绍使用DistributedDataParallel时的过程:

首先要初始化各进程环境:

def init_distributed_mode(args):# 如果是多机多卡的机器,WORLD_SIZE代表使用的机器数,RANK对应第几台机器# 如果是单机多卡的机器,WORLD_SIZE代表有几块GPU,RANK和LOCAL_RANK代表第几块GPUif'RANK'in os.environ and'WORLD_SIZE'in os.environ:args.rank = int(os.environ["RANK"])args.world_size = int(os.environ['WORLD_SIZE'])# LOCAL_RANK代表某个机器上第几块GPUargs.gpu = int(os.environ['LOCAL_RANK'])elif'SLURM_PROCID'in os.environ:args.rank = int(os.environ['SLURM_PROCID'])args.gpu = args.rank % torch.cuda.device_count()else:print('Not using distributed mode')args.distributed = Falsereturnargs.distributed = Truetorch.cuda.set_device(args.gpu)  # 对当前进程指定使用的GPUargs.dist_backend = 'nccl'# 通信后端,nvidia GPU推荐使用NCCLdist.barrier()  # 等待每个GPU都运行完这个地方以后再继续

在main函数初始阶段,进行以下初始化操作。需要注意的是,学习率需要根据使用GPU的张数增加。在这里使用简单的倍增方法。

def main(args):if torch.cuda.is_available() isFalse:raise EnvironmentError("not find GPU device for training.")# 初始化各进程环境init_distributed_mode(args=args)rank = args.rankdevice = torch.device(args.device)batch_size = args.batch_sizenum_classes = args.num_classesweights_path = args.weightsargs.lr *= args.world_size  # 学习率要根据并行GPU的数倍增

实例化数据集可以使用单卡相同的方法,但在sample样本时,和单机不同,需要使用DistributedSampler和BatchSampler。

#给每个rank对应的进程分配训练的样本索引
train_sampler=torch.utils.data.distributed.DistributedSampler(train_data_set)
val_sampler=torch.utils.data.distributed.DistributedSampler(val_data_set)
#将样本索引每batch_size个元素组成一个list
train_batch_sampler=torch.utils.data.BatchSampler(
train_sampler,batch_size,drop_last=True)

DistributedSampler原理如图所示:假设当前数据集有0~10共11个样本,使用2块GPU计算。首先打乱数据顺序,然后用 11/2 =6(向上取整),然后6乘以GPU个数2 = 12,因为只有11个数据,所以再把第一个数据(索引为6的数据)补到末尾,现在就有12个数据可以均匀分到每块GPU。然后分配数据:间隔将数据分配到不同的GPU中。

BatchSampler原理: DistributedSmpler将数据分配到两个GPU上,以第一个GPU为例,分到的数据是6,9,10,1,8,7,假设batch_size=2,就按顺序把数据两两一组,在训练时,每次获取一个batch的数据,就从组织好的一个个batch中取到。注意:只对训练集处理,验证集不使用BatchSampler。

接下来使用定义好的数据集和sampler方法加载数据:

 train_loader = torch.utils.data.DataLoader(train_data_set,batch_sampler=train_batch_sampler,pin_memory=True,   # 直接加载到显存中,达到加速效果num_workers=nw,collate_fn=train_data_set.collate_fn)val_loader = torch.utils.data.DataLoader(val_data_set,batch_size=batch_size,sampler=val_sampler,pin_memory=True,num_workers=nw,collate_fn=val_data_set.collate_fn)

如果有预训练权重的话,需要保证每块GPU加载的权重是一模一样的。需要在主进程保存模型初始化权重,在不同设备上载入主进程保存的权重。这样才能保证每块GOU上加载的权重是一致的:

# 实例化模型model = resnet34(num_classes=num_classes).to(device)# 如果存在预训练权重则载入if os.path.exists(weights_path):weights_dict = torch.load(weights_path, map_location=device)# 简单对比每层的权重参数个数是否一致load_weights_dict = {k: v for k, v in weights_dict.items()if model.state_dict()[k].numel() == v.numel()}model.load_state_dict(load_weights_dict, strict=False)else:checkpoint_path = os.path.join(tempfile.gettempdir(), "initial_weights.pt")# 如果不存在预训练权重,需要将第一个进程中的权重保存,然后其他进程载入,保持初始化权重一致if rank == 0:torch.save(model.state_dict(), checkpoint_path)dist.barrier()# 这里注意,一定要指定map_location参数,否则会导致第一块GPU占用更多资源model.load_state_dict(torch.load(checkpoint_path, map_location=device))

如果需要冻结模型权重,和单GPU基本没有差别。如果不需要冻结权重,可以选择是否同步BN层。然后再把模型包装成DDP模型,就可以方便进程之间的通信了。多GPU和单GPU的优化器设置没有差别,这里不再赘述。

# 是否冻结权重if args.freeze_layers:for name, para in model.named_parameters():# 除最后的全连接层外,其他权重全部冻结if"fc"notin name:para.requires_grad_(False)else:# 只有训练带有BN结构的网络时使用SyncBatchNorm采用意义if args.syncBN:# 使用SyncBatchNorm后训练会更耗时model = torch.nn.SyncBatchNorm.convert_sync_batchnorm(model).to(device)# 转为DDP模型model = torch.nn.parallel.DistributedDataParallel(model, device_ids=[args.gpu])# optimizer使用SGD+余弦淬火策略pg = [p for p in model.parameters() if p.requires_grad]optimizer = optim.SGD(pg, lr=args.lr, momentum=0.9, weight_decay=0.005)lf = lambda x: ((1 + math.cos(x * math.pi / args.epochs)) / 2) * (1 - args.lrf) + args.lrf  # cosinescheduler = lr_scheduler.LambdaLR(optimizer, lr_lambda=lf)

与单GPU不同的地方:rain_sampler.set_epoch(epoch),这行代码会在每次迭代的时候获得一个不同的生成器,每一轮开始迭代获取数据之前设置随机种子,通过改变传进的epoch参数改变打乱数据顺序。通过设置不同的随机种子,可以让不同GPU每轮拿到的数据不同。后面的部分和单GPU相同。

for epoch in range(args.epochs):train_sampler.set_epoch(epoch)  mean_loss = train_one_epoch(model=model,optimizer=optimizer,data_loader=train_loader,device=device,epoch=epoch)scheduler.step()sum_num = evaluate(model=model,data_loader=val_loader,device=device)acc = sum_num / val_sampler.total_size

我们详细看看每个epoch是训练时和单GPU训练的差异(上面的train_one_epoch)

def train_one_epoch(model, optimizer, data_loader, device, epoch):model.train()loss_function = torch.nn.CrossEntropyLoss()mean_loss = torch.zeros(1).to(device)optimizer.zero_grad()# 在进程0中打印训练进度if is_main_process():data_loader = tqdm(data_loader)for step, data in enumerate(data_loader):images, labels = datapred = model(images.to(device))loss = loss_function(pred, labels.to(device))loss.backward()loss = reduce_value(loss, average=True)  #  在单GPU中不起作用,多GPU时,获得所有GPU的loss的均值。mean_loss = (mean_loss * step + loss.detach()) / (step + 1)  # update mean losses# 在进程0中打印平均lossif is_main_process():data_loader.desc = "[epoch {}] mean loss {}".format(epoch, round(mean_loss.item(), 3))ifnot torch.isfinite(loss):print('WARNING: non-finite loss, ending training ', loss)sys.exit(1)optimizer.step()optimizer.zero_grad()# 等待所有进程计算完毕if device != torch.device("cpu"):torch.cuda.synchronize(device)return mean_loss.item()def reduce_value(value, average=True):world_size = get_world_size()if world_size < 2:  # 单GPU的情况return valuewith torch.no_grad():dist.all_reduce(value)   # 对不同设备之间的value求和if average:  # 如果需要求平均,获得多块GPU计算loss的均值value /= world_sizereturn value

接下来看一下验证阶段的情况,和单GPU最大的额不同之处是预测正确样本个数的地方。

  @torch.no_grad()def evaluate(model, data_loader, device):model.eval()# 用于存储预测正确的样本个数,每块GPU都会计算自己正确样本的数量sum_num = torch.zeros(1).to(device)# 在进程0中打印验证进度if is_main_process():data_loader = tqdm(data_loader)for step, data in enumerate(data_loader):images, labels = datapred = model(images.to(device))pred = torch.max(pred, dim=1)[1]sum_num += torch.eq(pred, labels.to(device)).sum()# 等待所有进程计算完毕if device != torch.device("cpu"):torch.cuda.synchronize(device)sum_num = reduce_value(sum_num, average=False)  # 预测正确样本个数return sum_num.item()

需要注意的是:保存模型的权重需要在主进程中进行保存。

if rank == 0:print("[epoch {}] accuracy: {}".format(epoch, round(acc, 3)))tags = ["loss", "accuracy", "learning_rate"]tb_writer.add_scalar(tags[0], mean_loss, epoch)tb_writer.add_scalar(tags[1], acc, epoch)tb_writer.add_scalar(tags[2], optimizer.param_groups[0]["lr"], epoch)torch.save(model.module.state_dict(), "./weights/model-{}.pth".format(epoch))

如果从头开始训练,主进程生成的初始化权重是以临时文件的形式保存,需要训练完后移除掉。最后还需要撤销进程组。

if rank == 0:# 删除临时缓存文件        if os.path.exists(checkpoint_path) is True:            os.remove(checkpoint_path)    dist.destroy_process_group()  # 撤销进程组,释放资源

鸣谢:本博客内容借鉴于up主:霹雳吧啦Wz

点个在看 paper不断!

收藏 | GPU多卡并行训练总结相关推荐

  1. 多卡并行训练遇到的问题

    1.mxnet+mpi+horovod 16卡并行训练 使用mpirun -np16 开启16个进程新建ndarray时报错: cudaMemGetInfo failed:out of memory ...

  2. pytorch多gpu并行训练操作指南

    关注上方"深度学习技术前沿",选择"星标公众号", 资源干货,第一时间送达! 来源:知乎 作者:link-web 链接:https://zhuanlan.zhi ...

  3. pytorch多gpu并行训练

    pytorch多gpu并行训练 link-web 转自:pytorch多gpu并行训练 - 知乎 目录(目录不可点击) 说明 1.和DataParallel的区别 2.如何启动程序的时候 2.1 单机 ...

  4. 2080Ti与3080Ti单机多卡安装与并行训练

    1 本人环境 机器:dell T640服务器 系统:Ubuntu16.04 LTS 已安装显卡:2080Ti 已安装驱动版本:NVIDIA-Linux-x86_64-470.57.02.run 已安装 ...

  5. PyTorch多卡分布式训练:DistributedDataParallel (DDP) 简要分析

    ©作者 | 伟大是熬出来的 单位 | 同济大学 研究方向 | 机器阅读理解 前言 因为课题组发的卡还没有下来,先向导师问了实验室的两张卡借用.之前都是单卡训练模型,正好在这个机会实践以下单机多卡训练模 ...

  6. 在TensorFlow和PaddleFluid中使用多块GPU卡进行训练

    专栏介绍:PaddleFluid 是用来让用户像 PyTorch 和 Tensorflow Eager Execution 一样执行程序.在这些系统中,不再有模型这个概念,应用也不再包含一个用于描述 ...

  7. torch dataloader 数据并行_PyTorch Parallel Training(单机多卡并行、混合精度、同步BN训练指南文档)

    0 写在前面 这篇文章是我做实验室组会汇报的时候顺带整理的文档,在1-3部分参考了很多知乎文章,感谢这些大佬们的工作,所以先贴出Reference,本篇文章结合了这些内容,加上了我的一些理解,不足之处 ...

  8. Pytorch:多块GPU分布式|并行训练

    分布式与并行训练的区别 分布式: 多台服务器上的多个GPU,分布式涉及了服务器之间的通信,因此比较复杂,PyTorch封装了相应的接口,可以用几句简单的代码实现分布式训练. 并行: 一台服务器上的多个 ...

  9. tensorflow 多gpu 并行训练

    多卡训练模式 进行深度学习模型训练的时候,一般使用GPU来进行加速,当训练样本只有百万级别的时候,单卡GPU通常就能满足我们的需求,但是当训练样本量达到上千万,上亿级别之后,单卡训练耗时很长,这个时候 ...

最新文章

  1. CentOS 中使用yum时常见的一种提示信息
  2. GIT学习笔记二(本地项目发布到GIT)
  3. MyEclipse使用总结——MyEclipse文件查找技巧
  4. 怎样避免每次都解释大量指令?
  5. 全国计算机运用计算机绘图考试,计算机绘图期末考试题库
  6. 读进程和写进程同步设计_浅谈unix进程进程间通信IPC原理
  7. 如何让小程序页面更顺滑_小程序怎样让wx.navigateBack更好用的方法实现
  8. jQuery获取URL参数
  9. AcWing 842. 排列数字(DFS)
  10. [转载]jquery ajax/post/get 传参数给 mvc的action
  11. 【面向代码】学习 Deep Learning(一)Neural Network
  12. SQL数据库的查询操作大全(select)
  13. Notepad++汉化教程
  14. 解决模拟器Emulator: emulator: ERROR: x86 emulation currently requires hardware acceleration!问题
  15. 计算机专业bs和cs,BS和CS的区别以及各自的优缺点
  16. 个体工商户营业执照在网上如何年检?
  17. 记一次usb3.0千兆网卡的选购经历;
  18. CRUSH与PG分布
  19. Echarts各个图表data的格式问题
  20. 印度理工学院有多难考?

热门文章

  1. Hibernate中get方法和load方法的区别
  2. 【WA】九度OJ题目1435:迷瘴
  3. 创建对象_工厂方法(Factory Method)模式 与 静态工厂方法
  4. [PyQt4]项目开发中遇到的错误与解决办法
  5. 中国电子学会青少年编程能力等级测试图形化四级编程题:太空大战
  6. 如何利用离散Hopfield神经网络进行高校科研能力评价(2)
  7. 【数据结构】单链表的应用(C语言)
  8. 你的编程能力从什么时候开始突飞猛进的?
  9. 「软件」2.0时代已经到来,你需要这样的开发工具
  10. 360数科张家兴:如何突破三大瓶颈,破解金融科技发展难题?