pytorch多GPU并行计算,pytorch并行库
1. nn.DataParallel
torch.nn.DataParallel(module, device_ids=None, output_device=None, dim=0)
- module -要并行化的模块
- device_ids (python列表:int或torch.device) - CUDA设备(默认:所有设备)
- output_device (int或torch.device) -输出的设备位置(默认:device_ids[0]) (用于汇总梯度信息的设备)
在模块级别实现数据并行。此容器通过在批尺寸维度中分块(其他对象将在每个设备上复制一次),在指定的设备上分割输入,从而并行化给定模块的应用程序。
在正向传递过程中,模型被复制到每个设备上,每个副本处理输入的一部分。在向后传递过程中,每个副本的梯度将累加到原始模块中。
批尺寸的大小应该大于所使用的gpu的数量。
https://pytorch.org/tutorials/beginner/blitz/data_parallel_tutorial.html
if torch.cuda.device_count() > 1: model = nn.DataParallel(model.cuda(), device_ids=gpus, output_device=gpus[0])
DataParallel 可以自动拆分数据并发送作业指令到多个gpu上的多个模型。在每个模型完成它们的工作之后,dataparparallel收集并合并结果,然后再返回给您。
DataParallel 使用起来非常方便,我们只需要用 DataParallel 包装模型,再设置一些参数即可。需要定义的参数包括:参与训练的 GPU 有哪些,device_ids=gpus;用于汇总梯度的 GPU 是哪个,output_device=gpus[0] 。DataParallel 会自动帮我们将数据切分 load 到相应 GPU,将模型复制到相应 GPU,进行正向传播计算梯度并汇总。
DataParallel 仅需改动一行代码即可。但是DataParallel 速度慢,GPU 负载存在不均衡的问题。
2. 使用 torch.distributed 加速并行训练
It is recommended to use DistributedDataParallel
, instead of DataParallel
to do multi-GPU training, even if there is only a single node.
对于单节点多GPU数据并行训练,事实证明,DistributedDataParallel的速度明显高于torch.nn.DataParallel。
torch.nn.parallel.
DistributedDataParallel
(module, device_ids=None, output_device=None, dim=0, broadcast_buffers=True, process_group=None, bucket_cap_mb=25, find_unused_parameters=False, check_reduction=False, gradient_as_bucket_view=False)
在模块级别实现基于torch.distributed包的分布式数据并行。
此容器通过在批尺寸维度中分组,在指定的设备之间分割输入,从而并行地处理给定模块的应用程序。模块被复制到每台机器和每台设备上,每个这样的复制处理输入的一部分。在反向传播过程中,每个节点的梯度取平均值。
批处理的大小应该大于本地使用的gpu数量。
输入的约束与torch.nn.DataParallel中的约束相同。
此类的创建要求torch.distributed已通过调用torch.distributed.init_process_group()
进行初始化。
要在具有N个GPU的主机上使用DistributedDataParallel,应生成N个进程,以确保每个进程在0到N-1的单个GPU上独自工作。这可以通过为每个进程设置CUDA_VISIBLE_DEVICES或调用以下命令来完成:
torch.cuda.set_device(i)
i从0到N-1。 在每个进程中,都应参考以下内容来构造此模块:
torch.distributed.init_process_group(backend='nccl', world_size=N, init_method='...') model = DistributedDataParallel(model, device_ids=[i], output_device=i)
为了在每个节点上生成多个进程,可以使用torch.distributed.launch或torch.multiprocessing.spawn。
如果使用DistributedDataParallel,可以使用torch.distributed.launch启动程序,请参阅第三方后端(Third-party backends)。
当使用gpu时,nccl后端是目前最快的,并且强烈推荐使用。这适用于单节点和多节点分布式训练。
区别
DistributedDataParallel和DataParallel之间的区别是:DistributedDataParallel使用多进程,其中为每个GPU创建一个进程,而DataParallel使用多线程。
通过使用多进程,每个GPU都有其专用的进程,从而避免了Python解释器的GIL导致的性能开销。
分布式通信包- TORCH.DISTRIBUTED
https://pytorch.org/docs/stable/distributed.html#distributed-basics
Backends-后端
torch.distributed支持三个内置后端,每个后端具有不同的功能。 下表显示了可用于CPU / CUDA张量的功能。
MPI仅在用于构建PyTorch的实现支持CUDA的情况下才支持CUDA。
PyTorch自带的后端
PyTorch分布式包支持Linux(stable)、MacOS(stable)和Windows (prototype)。
对于Linux,默认情况下,会构建Gloo和NCCL后端并将其包含在PyTorch分布式中(仅在使用CUDA进行构建时才为NCCL)。
MPI是一个可选的后端,仅当您从源代码构建PyTorch时才可以包括在内。 (例如,在安装了MPI的主机上构建PyTorch。)
Note:
从PyTorch v1.8开始,Windows支持除NCCL外所有集体通信后端。
如果init_process_group()的init_method参数指向文件,则它必须遵循以下架构:
- 本地文件系统, init_method =“ file:/// d:/ tmp / some_file”
- 共享文件系统,init_method =“ file:////// {machine_name} / {share_folder_name} / some_file”
与Linux平台相同,您可以通过设置环境变量MASTER_ADDR和MASTER_PORT来启用TcpStore。
使用哪个后端?
在过去,我们经常被问到:“我应该使用哪个后端?”
经验法则
- 使用NCCL后端进行分布式GPU训练
- 使用Gloo后端进行分布式CPU训练
具有InfiniBand互连的GPU主机
- 使用NCCL,因为它是当前唯一支持InfiniBand和GPUDirect的后端。
具有以太网互连的GPU主机
- 使用NCCL,因为它目前提供最佳的分布式GPU训练性能,尤其是对于单节点多进程或多节点分布式训练。
- 如果您在使用NCCL时遇到任何问题,请使用Gloo作为后备选项。 (请注意,对于GPU,Gloo当前的运行速度比NCCL慢。)
具有InfiniBand互连的CPU主机
- 如果您的InfiniBand已启用IB上的IP,请使用Gloo,否则,请使用MPI。
- 我们计划在即将发布的版本中增加InfiniBand对Gloo的支持。
具有以太网互连的CPU主机
- 除非有特殊原因要使用MPI,否则请使用Gloo。
常用环境变量
选择要使用的网络接口
默认情况下,NCCL和Gloo后端都将尝试找到要使用的正确网络接口。 如果自动检测到的接口不正确,则可以使用以下环境变量(适用于各自的后端)覆盖它:
- NCCL_SOCKET_IFNAME,例如导出NCCL_SOCKET_IFNAME = eth0
- GLOO_SOCKET_IFNAME,例如导出GLOO_SOCKET_IFNAME = eth0
如果您使用的是Gloo后端,则可以用逗号分隔多个接口,例如:export GLOO_SOCKET_IFNAME = eth0,eth1,eth2,eth3。
后端将以循环方式在这些接口之间调度操作。 至关重要的是,所有进程都必须在此变量中指定相同数量的接口。
其他NCCL环境变量
NCCL还提供了许多环境变量以进行微调。
常用的调试工具包括以下内容:
- export NCCL_DEBUG = INFO
- export NCCL_DEBUG_SUBSYS = ALL
有关NCCL环境变量的完整列表,请参阅NVIDIA NCCL的官方文档(NVIDIA NCCL’s official documentation)
1. 基础知识
torch.distributed包为运行在一台或多台机器上的多个计算节点之间的多进程并行性提供PyTorch支持和基本通信模块。
torch.nn.parallel.DistributedDataParallel()类建立在此功能之上,以提供同步的分布式训练,作为对任何PyTorch模型的包装。
这与Multiprocessing包提供的并行性(torch.multiprocessing和torch.nn.DataParallel())不同。因为它支持多个连接网络的机器,并且用户必须为每个进程显式启动主训练脚本的单独副本。
在单机同步情况下,torch.distributed或torch.nn.parallel.DistributedDataParallel()包装器仍可能具有优于其他数据并行方法的优势,包括torch.nn.DataParallel():
- 每个进程都维护自己的优化器,并在每次迭代时执行一个完整的优化步骤。尽管这看起来可能是多余的,但由于梯度已经被收集在一起并在各个进程之间求平均,因此对于每个进程都是相同的,这意味着不需要参数广播步骤,从而减少了在节点之间传递张量的时间。
- 每个进程都包含一个独立的Python解释器,从而消除了由单个Python进程驱动多个执行线程,模型副本或GPU带来的额外解释器开销和“ GIL超负荷”。这对于大量使用Python runtime的模型尤其重要,包括具有循环层或许多小组件的模型。
2. 初始化
在调用任何其他方法之前,需要使用torch.distributed.init_process_group()函数对程序包进行初始化。这将阻塞,直到所有进程都已加入。
torch.distributed.is_available()
如果分布式程序包可用,则返回True。 否则,torch.distributed不会公开任何其他API。当前,torch.distributed在Linux,MacOS和Windows上可用。
从源代码构建PyTorch时,设置USE_DISTRIBUTED = 1启用它。 当前,对于Linux和Windows,默认值为USE_DISTRIBUTED = 1,对于MacOS,默认值为USE_DISTRIBUTED = 0。
torch.distributed.init_process_group(backend, init_method=None, timeout=datetime.timedelta(0, 1800), world_size=-1, rank=-1, store=None, group_name='')
torch.distributed.init_process_group用于初始化默认的分布式进程组,这也将初始化分布式包。
有两种主要的方法来初始化进程组:
- 1. 明确指定store,rank和world_size参数。
- 2. 指定init_method(URL字符串),它指示在何处/如何发现对等方。 可以指定rank和world_size,或者在URL中编码所有必需的参数并省略它们。
如果两者均未指定,则将init_method假定为“ env://”。
Parameters:
backend(str or Backend)–要使用的后端。根据构建时配置,有效值包括mpi,gloo和nccl。该字段应以小写字符串(例如“ gloo”)给出,也可以通过Backend属性(例如Backend.GLOO)进行访问。如果每台具有nccl后端的计算机使用多个进程,则每个进程必须对其使用的每个GPU都具有独占访问权限,因为在进程之间共享GPU可能会导致死锁。
init_method(str,可选)–指定如何初始化进程组的URL。 如果未指定init_method或store,则默认值为“ env://”。 与store参数互斥。
world_size(整数,可选)–参与作业的进程数。 如果指定了store,则为必需。
rank(整数,可选)–当前进程的排序(它应该是0到world_size-1之间的数字)。 如果指定了store,则为必需。
Store(Store,可选)–所有workers均可访问的Key/value存储,用于交换连接/地址信息。 与init_method参数互斥。
timeout (timedelta,可选)——对进程组执行操作的超时时间。默认值为30分钟。这适用于gloo后端。 对于nccl,这仅在环境变量NCCL_BLOCKING_WAIT或NCCL_ASYNC_ERROR_HANDLING设置为1时适用。设置NCCL_BLOCKING_WAIT时,这是进程将阻塞并等待集合完成以引发异常之前的持续时间。设置NCCL_ASYNC_ERROR_HANDLING时,这是一个持续时间,在该持续时间之后,集合将被异步中止并且该过程将崩溃。NCCL_BLOCKING_WAIT将向用户提供可以捕获和处理的错误,但是由于其阻塞性质,因此会产生性能开销。另一方面,NCCL_ASYNC_ERROR_HANDLING的性能开销很小,但是由于错误而使进程崩溃。这样做是因为CUDA执行是异步的,并且继续执行用户代码不再安全,因为失败的异步NCCL操作可能会导致随后的CUDA操作在损坏的数据上运行。只能设置这两个环境变量之一。
group_name (str,可选,弃用)-组名。
要启用backend == Backend.MPI,需要在支持MPI的系统上从源代码构建PyTorch。
torch.distributed.Backend
一个类似枚举的可用后端类:GLOO、NCCL、MPI和其他注册后端。
此类的值是小写字符串,例如“ gloo”。 可以将它们作为属性来访问,例如Backend.NCCL。可以直接调用此类来解析字符串,例如Backend(backend_str)将检查backend_str是否有效,如果是则返回解析后的小写字符串。 它还接受大写字符串,例如Backend(“ GLOO”)返回“ gloo”。
存在条目Backend.UNDEFINED,但仅用作某些字段的初始值。 用户既不应直接使用它,也不应该假定它的存在。
torch.distributed.get_backend(group=None)
返回给定进程组的后端。
torch.distributed.get_rank(group=None)
返回当前进程组的序号。Rank是分配给分布式进程组中每个进程的唯一标识符。 它们始终是从0到world_size的连续整数。
torch.distributed.get_world_size(group=None)
返回当前进程组中的进程数
torch.distributed.is_initialized()
检查默认进程组是否已初始化
torch.distributed.is_mpi_available()
检查MPI后端是否可用。
torch.distributed.is_nccl_available()
检查NCCL后端是否可用。
目前支持三种初始化方法:
1. TCP initialization
有两种使用TCP进行初始化的方法,两种方法都要求所有进程都可以访问一个网络地址,并且需要一个所需的world_size。
第一种方法要求指定一个地址,该地址属于Rank 0进程。 此初始化方法要求所有进程都具有手动指定的Ranks。
请注意,最新的分布式程序包中不再支持组播地址(multicast address)。 同样不推荐使用group_name。
import torch.distributed as dist # Use address of one of the machines dist.init_process_group(backend, init_method='tcp://10.1.1.20:23456', rank=args.rank, world_size=4)
2. Shared file-system initialization
另一种初始化方法利用了文件系统以及所需的world_size,该文件系统可在组中的所有计算机上共享并可见。
URL应以file://开头,并包含指向共享文件系统上不存在的文件(在现有目录中)的路径。
如果文件系统初始化不存在,则会自动创建该文件,但不会删除该文件。
因此,您有责任在相同文件路径/名称的下一个init_process_group()调用之前,确保已清理文件。
请注意,最新的分布式软件包不再支持自动Ranks分配,并且不建议使用group_name。
WARNING
此方法假定文件系统支持使用fcntl进行锁定-大多数本地系统和NFS支持该锁定。
WARNING
此方法将始终创建文件,并尽力在程序末尾清理并删除文件。
换句话说,使用文件init方法进行的每次初始化都需要一个全新的空文件,以使初始化成功。
如果再次使用先前初始化使用的同一文件(碰巧不会被清理),则这是意外行为,通常会导致死锁和失败。
因此,即使此方法将尽最大努力清除文件,但如果自动删除碰巧失败,您有责任确保在训练结束时将文件删除,以防止同一文件下一次被再次重用。
如果您计划对同一文件名多次调用init_process_group(),则这一点尤其重要。
换句话说,如果未删除/清理文件,而您对该文件再次调用init_process_group(),则可能会失败。
经验法则是,确保每次调用init_process_group()时文件都不存在或为空。
import torch.distributed as dist # rank should always be specified dist.init_process_group(backend, init_method='file:///mnt/nfs/sharedfile', world_size=4, rank=args.rank)
3. Environment variable initialization
此方法将从环境变量中读取配置,从而可以完全自定义如何获取信息。 要设置的变量是:
MASTER_PORT-必填: 必须是Rank为0的计算机上的空闲端口。
MASTER_ADDR-必填(Rank 0除外): Rank 0节点的地址。
WORLD_SIZE-必填: 可以在此处设置,也可以在调用init函数时设置
RANK-必填: 可以在此处设置,也可以在对init函数的调用中进行设置
Rank为0的计算机将用于建立所有连接。
这是默认方法,这意味着不必指定init_method(或可以为env://)。
Distributed Key-Value Store
分布式程序包附带一个分布式键值存储,可用于在组中的进程之间共享信息以及在torch.distributed.init_process_group()中初始化分布式程序包(通过显式创建存储来替代指定init_method。)
Store参数与init_method参数互斥。
键值存储有3个选择:TCPStore,FileStore和HashStore。
torch.distributed.Store
所有store实现的基类,例如PyTorch分布式所提供的3种:(TCPStore,FileStore和HashStore)。
torch.distributed.TCPStore
一种基于TCP的分布式键值存储实现。 服务器存储区保存数据,而客户端存储区可以通过TCP连接到服务器存储区,并执行诸如set()插入键值对,get()检索键值对等操作。
Parameters:
host_name(str)–服务器存储应在其上运行的主机名或IP地址。
port(int)–服务器存储应在其上侦听传入请求的端口。
world_size(int)–store用户总数(客户端数+1)。
is_master(布尔型)–初始化服务器存储时为True,对于客户端存储为False。
timeout(timedelta)–初始化期间store使用的超时试时间,并且用于诸如get()和wait()之类的方法。
>>> import torch.distributed as dist >>> from datetime import timedelta >>> # Run on process 1 (server) >>> server_store = dist.TCPStore("127.0.0.1", 1234, 2, True, timedelta(seconds=30)) >>> # Run on process 2 (client) >>> client_store = dist.TCPStore("127.0.0.1", 1234, 2, False) >>> # Use any of the store methods from either the client or server after initialization >>> server_store.set("first_key", "first_value") >>> client_store.get("first_key")
torch.distributed.HashStore
基于基础哈希图的线程安全存储实现。 该存储可以在同一进程内使用(例如,由其他线程使用),但不能在多个进程之间使用。
>>> import torch.distributed as dist >>> store = dist.HashStore() >>> # store can be used from other threads >>> # Use any of the store methods after initialization >>> store.set("first_key", "first_value")
torch.distributed.FileStore
使用文件存储基础键值对的存储实现。
Parameters
file_name(str)–存储键值对的文件的路径
world_size(int)–使用store的进程总数
>>> import torch.distributed as dist >>> store1 = dist.FileStore("/tmp/filestore", 2) >>> store2 = dist.FileStore("/tmp/filestore", 2) >>> # Use any of the store methods from either the client or server after initialization >>> store1.set("first_key", "first_value") >>> store2.get("first_key")
3. model parallel
DataParparallel在多个gpu上训练神经网络;该特性将相同的模型复制到所有GPU,每个GPU使用输入数据的不同分区。虽然它可以显著加速训练过程,但它不适用于一些模型太大而不能放入单个GPU的用例。当模型太大时,可以将模型的不同部分放在不同的GPU上。
模型并行的高级思想是将一个模型的不同子网络放置到不同的设备上,并相应地实现forward方法来在设备之间移动中间输出。
由于一个模型只有一部分在单个设备上运行,所以一组设备可以共同服务于一个更大的模型。
https://pytorch.org/tutorials/intermediate/model_parallel_tutorial.html
import torch import torch.nn as nn import torch.optim as optim class ToyModel(nn.Module): def __init__(self): super(ToyModel, self).__init__() self.net1 = torch.nn.Linear(10, 10).to('cuda:0') self.relu = torch.nn.ReLU() self.net2 = torch.nn.Linear(10, 5).to('cuda:1') def forward(self, x): x = self.relu(self.net1(x.to('cuda:0'))) return self.net2(x.to('cuda:1'))
参考:
pytorch(分布式)数据并行个人实践总结——DataParallel/DistributedDataParallel
https://zhuanlan.zhihu.com/p/39752167
https://ptorch.com/docs/8/cuda
https://www.cnblogs.com/yh-blog/p/12877922.html
https://www.zhihu.com/question/67726969?sort=created
https://blog.csdn.net/u013398034/article/details/83989808