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