您当前的位置:首页 > 电脑百科 > 程序开发 > 语言 > Python

PyTorch 并行训练 DistributedDataParallel 完整代码示例

时间:2023-02-19 16:12:29  来源:DeepHub IMBA  作者:Joseph El Kettaneh

使用大型数据集训练大型深度神经网络 (DNN) 的问题是深度学习领域的主要挑战。 随着 DNN 和数据集规模的增加,训练这些模型的计算和内存需求也会增加。 这使得在计算资源有限的单台机器上训练这些模型变得困难甚至不可能。 使用大型数据集训练大型 DNN 的一些主要挑战包括:

  • 训练时间长:训练过程可能需要数周甚至数月才能完成,具体取决于模型的复杂性和数据集的大小。
  • 内存限制:大型 DNN 可能需要大量内存来存储训练期间的所有模型参数、梯度和中间激活。 这可能会导致内存不足错误并限制可在单台机器上训练的模型的大小。

为了应对这些挑战,已经开发了各种技术来扩大具有大型数据集的大型 DNN 的训练,包括模型并行性、数据并行性和混合并行性,以及硬件、软件和算法的优化。

在本文中我们将演示使用 PyTorch 的数据并行性和模型并行性。

 

图片

 

我们所说的并行性一般是指在多个gpu,或多台机器上训练深度神经网络(dnn),以实现更少的训练时间。数据并行背后的基本思想是将训练数据分成更小的块,让每个GPU或机器处理一个单独的数据块。然后将每个节点的结果组合起来,用于更新模型参数。在数据并行中,模型体系结构在每个节点上是相同的,但模型参数在节点之间进行了分区。每个节点使用分配的数据块训练自己的本地模型,在每次训练迭代结束时,模型参数在所有节点之间同步。这个过程不断重复,直到模型收敛到一个令人满意的结果。

下面我们用用Re.NET50和CIFAR10数据集来进行完整的代码示例:

在数据并行中,模型架构在每个节点上保持相同,但模型参数在节点之间进行了分区,每个节点使用分配的数据块训练自己的本地模型。

PyTorch的DistributedDataParallel 库可以进行跨节点的梯度和模型参数的高效通信和同步,实现分布式训练。本文提供了如何使用ResNet50和CIFAR10数据集使用PyTorch实现数据并行的示例,其中代码在多个gpu或机器上运行,每台机器处理训练数据的一个子集。训练过程使用PyTorch的DistributedDataParallel 库进行并行化。

导入必须要的库

 

import os
 from datetime import datetime
 from time import time
 import argparse
 import torchvision
 import torchvision.transforms as transforms
 import torch
 import torch.nn as nn
 import torch.distributed as dist
 from torch.nn.parallel import DistributedDataParallel
 

接下来,我们将检查GPU。

 

import subprocess
 result = subprocess.run(['nvidia-smi'], stdout=subprocess.PIPE)
 print(result.stdout.decode())

 

因为我们需要在多个服务器上运行,所以手动一个一个执行并不现实,所以需要有一个调度程序。这里我们使用SLURM文件来运行代码(slurm面向linux和Unix类似内核的免费和开源工作调度程序),

 

def mAIn():
         
     # get distributed configuration from Slurm environment
     
     parser = argparse.ArgumentParser()
     parser.add_argument('-b', '--batch-size', default=128, type =int,
                         help='batch size. it will be divided in mini-batch for each worker')
     parser.add_argument('-e','--epochs', default=2, type=int, metavar='N',
                         help='number of total epochs to run')
     parser.add_argument('-c','--checkpoint', default=None, type=str,
                         help='path to checkpoint to load')
     args = parser.parse_args()
     
     rank = int(os.environ['SLURM_PROCID'])
     local_rank = int(os.environ['SLURM_LOCALID'])
     size = int(os.environ['SLURM_NTASKS'])
     master_addr = os.environ["SLURM_SRUN_COMM_HOST"]
     port = "29500"
     node_id = os.environ['SLURM_NODEID']
     ddp_arg = [rank, local_rank, size, master_addr, port, node_id]
     train(args, ddp_arg)

然后,我们使用DistributedDataParallel 库来执行分布式训练。

 

def train(args, ddp_arg):
     
     rank, local_rank, size, MASTER_ADDR, port, NODE_ID = ddp_arg
     
     # display info
     if rank == 0:
         #print(">>> Training on ", len(hostnames), " nodes and ", size, " processes, master node is ", MASTER_ADDR)
         print(">>> Training on ", size, " GPUs, master node is ", MASTER_ADDR)
     #print("- Process {} corresponds to GPU {} of node {}".format(rank, local_rank, NODE_ID))
     
     print("- Process {} corresponds to GPU {} of node {}".format(rank, local_rank, NODE_ID))
     
     
     # configure distribution method: define address and port of the master node and initialise communication backend (NCCL)
     #dist.init_process_group(backend='nccl', init_method='env://', world_size=size, rank=rank)
     dist.init_process_group(
         backend='nccl',
         init_method='tcp://{}:{}'.format(MASTER_ADDR, port),
         world_size=size,
         rank=rank
    )
     
     # distribute model
     torch.cuda.set_device(local_rank)
     gpu = torch.device("cuda")
     #model = ResNet18(classes=10).to(gpu)
     model = torchvision.models.resnet50(pretrained=False).to(gpu)
     ddp_model = DistributedDataParallel(model, device_ids=[local_rank])
     if args.checkpoint is not None:
         map_location = {'cuda:%d' % 0: 'cuda:%d' % local_rank}
         ddp_model.load_state_dict(torch.load(args.checkpoint, map_location=map_location))
     
     # distribute batch size (mini-batch)
     batch_size = args.batch_size
     batch_size_per_gpu = batch_size // size
     
     # define loss function (criterion) and optimizer
     criterion = nn.CrossEntropyLoss()  
     optimizer = torch.optim.SGD(ddp_model.parameters(), 1e-4)
     
     
     transform_train = transforms.Compose([
         transforms.RandomCrop(32, padding=4),
         transforms.RandomHorizontalFlip(),
         transforms.ToTensor(),
         transforms.Normalize((0.4914, 0.4822, 0.4465), (0.2023, 0.1994, 0.2010)),
    ])
 
     # load data with distributed sampler
     #train_dataset = torchvision.datasets.CIFAR10(root='./data',
     #                                           train=True,
     #                                           transform=transform_train,
     #                                           download=False)
     
     # load data with distributed sampler
     train_dataset = torchvision.datasets.CIFAR10(root='./data',
                                                train=True,
                                                transform=transform_train,
                                                download=False)
     
     train_sampler = torch.utils.data.distributed.DistributedSampler(train_dataset,
                                                                     num_replicas=size,
                                                                     rank=rank)
     
     train_loader = torch.utils.data.DataLoader(dataset=train_dataset,
                                                batch_size=batch_size_per_gpu,
                                                shuffle=False,
                                                num_workers=0,
                                                pin_memory=True,
                                                sampler=train_sampler)
 
     # training (timers and display handled by process 0)
     if rank == 0: start = datetime.now()        
     total_step = len(train_loader)
     
     for epoch in range(args.epochs):
         if rank == 0: start_dataload = time()
         
         for i, (images, labels) in enumerate(train_loader):
             
             # distribution of images and labels to all GPUs
             images = images.to(gpu, non_blocking=True)
             labels = labels.to(gpu, non_blocking=True)
             
             if rank == 0: stop_dataload = time()
 
             if rank == 0: start_training = time()
             
             # forward pass
             outputs = ddp_model(images)
             loss = criterion(outputs, labels)
 
             # backward and optimize
             optimizer.zero_grad()
             loss.backward()
             optimizer.step()
             
             if rank == 0: stop_training = time()
             if (i + 1) % 10 == 0 and rank == 0:
                 print('Epoch [{}/{}], Step [{}/{}], Loss: {:.4f}, Time data load: {:.3f}ms, Time training: {:.3f}ms'.format(epoch + 1, args.epochs,
                                                                         i + 1, total_step, loss.item(), (stop_dataload - start_dataload)*1000,
                                                                        (stop_training - start_training)*1000))
             if rank == 0: start_dataload = time()
                     
         #Save checkpoint at every end of epoch
         if rank == 0:
             torch.save(ddp_model.state_dict(), './checkpoint/{}GPU_{}epoch.checkpoint'.format(size, epoch+1))
 
     if rank == 0:
         print(">>> Training complete in: " + str(datetime.now() - start))
 
 
 if __name__ == '__main__':
 
     main()

代码将数据和模型分割到多个gpu上,并以分布式的方式更新模型。下面是代码的一些解释:

train(args, ddp_arg)有两个参数,args和ddp_arg,其中args是传递给脚本的命令行参数,ddp_arg包含分布式训练相关参数。

rank, local_rank, size, MASTER_ADDR, port, NODE_ID = ddp_arg:解包ddp_arg中分布式训练相关参数。

如果rank为0,则打印当前使用的gpu数量和主节点IP地址信息。

dist.init_process_group(backend='nccl', init_method='tcp://{}:{}'.format(MASTER_ADDR, port), world_size=size, rank=rank) :使用NCCL后端初始化分布式进程组。

torch.cuda.set_device(local_rank):为这个进程选择指定的GPU。

model = torchvision.models. ResNet50 (pretrained=False).to(gpu):从torchvision模型中加载ResNet50模型,并将其移动到指定的gpu。

ddp_model = DistributedDataParallel(model, device_ids=[local_rank]):将模型包装在DistributedDataParallel模块中,也就是说这样我们就可以进行分布式训练了

加载CIFAR-10数据集并应用数据增强转换。

train_sampler=torch.utils.data.distributed.DistributedSampler(train_dataset,num_replicas=size,rank=rank):创建一个DistributedSampler对象,将数据集分割到多个gpu上。

train_loader =torch.utils.data.DataLoader(dataset=train_dataset,batch_size=batch_size_per_gpu,shuffle=False,num_workers=0,pin_memory=True,sampler=train_sampler):创建一个DataLoader对象,数据将批量加载到模型中,这与我们平常训练的步骤是一致的只不过是增加了一个分布式的数据采样DistributedSampler。

为指定的epoch数训练模型,以分布式的方式使用optimizer.step()更新权重。

rank0在每个轮次结束时保存一个检查点。

rank0每10个批次显示损失和训练时间。

结束训练时打印训练模型所花费的总时间也是在rank0上。

代码测试

在使用1个节点1/2/3/4个gpu, 2个节点6/8个gpu,每个节点3/4个gpu上进行了训练Cifar10上的Resnet50的测试如下图所示,每次测试的批处理大小保持不变。完成每项测试所花费的时间以秒为单位记录。随着使用的gpu数量的增加,完成测试所需的时间会减少。当使用8个gpu时,需要320秒才能完成,这是记录中最快的时间。这是肯定的,但是我们可以看到训练的速度并没有像GPU数量增长呈现线性的增长,这可能是因为Resnet50算是一个比较小的模型了,并不需要进行并行化训练。

 

图片

 

在多个gpu上使用数据并行可以显著减少在给定数据集上训练深度神经网络(DNN)所需的时间。随着gpu数量的增加,完成训练过程所需的时间减少,这表明DNN可以更有效地并行训练。

这种方法在处理大型数据集或复杂的DNN架构时特别有用。通过利用多个gpu,可以加快训练过程,实现更快的模型迭代和实验。但是需要注意的是,通过Data Parallelism实现的性能提升可能会受到通信开销和GPU内存限制等因素的限制,需要仔细调优才能获得最佳结果。


责任编辑:华轩来源: DeepHub IMBA


Tags:PyTorch   点击:()  评论:()
声明:本站部分内容及图片来自互联网,转载是出于传递更多信息之目的,内容观点仅代表作者本人,不构成投资建议。投资者据此操作,风险自担。如有任何标注错误或版权侵犯请与我们联系,我们将及时更正、删除。
▌相关推荐
微信基于 PyTorch 的大规模推荐系统训练实践
本文将介绍微信基于 PyTorch 进行的大规模推荐系统训练。推荐系统和其它一些深度学习领域不同,仍在使用 Tensorflow 作为训练框架,被广大开发者诟病。虽然也有使用 PyTorch 进...【详细内容】
2023-04-04  Search: PyTorch  点击:(240)  评论:(0)  加入收藏
PyTorch 并行训练 DistributedDataParallel 完整代码示例
使用大型数据集训练大型深度神经网络 (DNN) 的问题是深度学习领域的主要挑战。 随着 DNN 和数据集规模的增加,训练这些模型的计算和内存需求也会增加。 这使得在计算资源有限...【详细内容】
2023-02-19  Search: PyTorch  点击:(282)  评论:(0)  加入收藏
LeCun转推,PyTorch GPU内存分配有了火焰图可视化工具
机器之心报道编辑:杜伟 想要了解自己的 PyTorch 项目在哪些地方分配 GPU 内存以及为什么用完吗?不妨试试这个可视化工具。 近日,PyTorch 核心开发者和 FAIR 研究者 Zachary DeV...【详细内容】
2022-10-24  Search: PyTorch  点击:(333)  评论:(0)  加入收藏
基于 PyTorch 和神经网络给 GirlFriend 制作漫画风头像
本文分享自华为云社区《AnimeGANv2 照片动漫化:如何基于 PyTorch 和神经网络给 GirlFriend 制作漫画风头像?【秋招特训】-云社区-华为云》,作者:白鹿第一帅 。前言将现实世界场...【详细内容】
2022-09-13  Search: PyTorch  点击:(424)  评论:(0)  加入收藏
使用EMNIST数据集训练第一个pytorch CNN手写字母识别神经网络
MNIST 这里就不多展开了,我们上几期的文章都是使用此数据集进行的分享。手写字母识别EMNIST数据集Extended MNIST (EMNIST), 因为 MNIST 被大家熟知,所以这里就推出了 EMNIST...【详细内容】
2021-09-08  Search: PyTorch  点击:(1802)  评论:(0)  加入收藏
▌简易百科推荐
一篇文章教会你使用Python中三种简单的函数
所谓函数,就是指:把某些特定功能的代码组成为一个整体,这个整体就叫做函数。一、函数简介所谓函数,就是指:把某些特定功能的代码组成为一个整体,这个整体就叫做函数。二、函数定义...【详细内容】
2024-04-11  Go语言进阶学习  微信公众号  Tags:Python   点击:(3)  评论:(0)  加入收藏
一篇文章带你了解Python的分布式进程接口
在Thread和Process中,应当优选Process,因为Process更稳定,而且,Process可以分布到多台机器上,而Thread最多只能分布到同一台机器的多个CPU上。一、前言在Thread和Process中,应当优...【详细内容】
2024-04-11  Go语言进阶学习    Tags:Python   点击:(2)  评论:(0)  加入收藏
Python 可视化:Plotly 库使用基础
当使用 Plotly 进行数据可视化时,我们可以通过以下示例展示多种绘图方法,每个示例都会有详细的注释和说明。1.创建折线图import plotly.graph_objects as go# 示例1: 创建简单...【详细内容】
2024-04-01  Python技术    Tags:Python   点击:(8)  评论:(0)  加入收藏
Python 办公神器:教你使用 Python 批量制作 PPT
介绍本文将介绍如何使用openpyxl和pptx库来批量制作PPT奖状。本文假设你已经安装了python和这两个库。本文的场景是:一名基层人员,要给一次比赛活动获奖的500名选手制作奖状,并...【详细内容】
2024-03-26  Python技术  微信公众号  Tags:Python   点击:(17)  评论:(0)  加入收藏
Python实现工厂模式、抽象工厂,单例模式
工厂模式是一种常见的设计模式,它可以帮助我们创建对象的过程更加灵活和可扩展。在Python中,我们可以使用函数和类来实现工厂模式。一、Python中实现工厂模式工厂模式是一种常...【详细内容】
2024-03-07  Python都知道  微信公众号  Tags:Python   点击:(33)  评论:(0)  加入收藏
不可不学的Python技巧:字典推导式使用全攻略
Python的字典推导式是一种优雅而强大的工具,用于创建字典(dict)。这种方法不仅代码更加简洁,而且执行效率高。无论你是Python新手还是有经验的开发者,掌握字典推导式都将是你技能...【详细内容】
2024-02-22  子午Python  微信公众号  Tags:Python技巧   点击:(34)  评论:(0)  加入收藏
如何进行Python代码的代码重构和优化?
Python是一种高级编程语言,它具有简洁、易于理解和易于维护的特点。然而,代码重构和优化对于保持代码质量和性能至关重要。什么是代码重构?代码重构是指在不改变代码外部行为的...【详细内容】
2024-02-22  编程技术汇    Tags:Python代码   点击:(35)  评论:(0)  加入收藏
Python开发者必备的八个PyCharm插件
在编写代码的过程中,括号几乎无处不在,以至于有时我们会拼命辨别哪个闭合括号与哪个开头的括号相匹配。这款插件能帮助解决这个众所周知的问题。前言在PyCharm中浏览插件列表...【详细内容】
2024-01-26  Python学研大本营  微信公众号  Tags:PyCharm插件   点击:(87)  评论:(0)  加入收藏
Python的Graphlib库,再也不用手敲图结构了
Python中的graphlib库是一个功能强大且易于使用的工具。graphlib提供了许多功能,可以帮助您创建、操作和分析图形对象。本文将介绍graphlib库的主要用法,并提供一些示例代码和...【详细内容】
2024-01-26  科学随想录  微信公众号  Tags:Graphlib库   点击:(88)  评论:(0)  加入收藏
Python分布式爬虫打造搜索引擎
简单分布式爬虫结构主从模式是指由一台主机作为控制节点负责所有运行网络爬虫的主机进行管理,爬虫只需要从控制节点那里接收任务,并把新生成任务提交给控制节点就可以了,在这个...【详细内容】
2024-01-25  大雷家吃饭    Tags:Python   点击:(59)  评论:(0)  加入收藏
站内最新
站内热门
站内头条