PyTorch中使用DistributedDataParallel進行多GPU分布式模型訓練

2021-01-11 deephub

先進的深度學習模型參數正以指數級速度增長:去年的GPT-2有大約7.5億個參數,今年的GPT-3有1750億個參數。雖然GPT是一個比較極端的例子但是各種SOTA模型正在推動越來越大的模型進入生產應用程式,這裡的最大挑戰是使用GPU卡在合理的時間內完成模型訓練工作的能力。

為了解決這些問題,從業者越來越多地轉向分布式訓練。 分布式訓練是使用多個GPU和/或多個機器訓練深度學習模型的技術。 分布式訓練作業使您能夠克服單GPU內存瓶頸,通過同時利用多個GPU來開發更大,功能更強大的模型。

這篇文章是使用torch.nn.parallel.DistributedDataParallel API在純PyTorch中進行分布式訓練的簡介。 我們會:

討論一般的分布式訓練方式,尤其是數據並行化涵蓋torch.dist和DistributedDataParallel的相關功能,並舉例說明如何使用它們測試真實的訓練腳本,以節省時間什麼是分布式訓練?

在研究分布式和數據並行之前,我們需要先了解一些關於分布式訓練的背景知識。

目前普遍使用的分布式訓練基本上有兩種不同形式:數據並行化和模型並行化。

在數據並行化中,模型訓練作業是在數據上進行分割的。作業中的每個GPU接收到自己獨立的數據批處理切片。每個GPU使用這些數據來獨立計算梯度更新。例如,如果你要使用兩個GPU和32的批處理大小,一個GPU將處理前16條記錄的向前和向後傳播,第二個處理後16條記錄的向後和向前傳播。這些梯度更新然後在gpu之間同步,一起平均,最後應用到模型。

(同步步驟在技術上是可選的,但理論上更快的異步更新策略仍是一個活躍的研究領域)

在模型並行化中,模型訓練作業是在模型上進行分割的。工作中的每個GPU接收模型的一個切片,例如它的層的一個子集。例如,一個GPU負責它的輸出頭,另一個負責輸入層,另一個負責中間的隱藏層。

雖然這兩種技術各有優缺點,但數據並行化在這兩種技術中更容易實現(它不需要了解底層網絡架構),因此通常首先嘗試這種策略。

(也可以結合使用這些技術,例如同時使用模型和數據並行化,但這是一個高級主題,我們不在這裡介紹)

因為這篇文章是對DistributedDataParallel並行API的介紹,所以我們不會再進一步討論模型並行化的細節——但請關注以後關於這個主題的文章!

數據並行是如何工作的

在前一節中,我給出了數據並行化的概述。在這一節中,我們將深入研究細節。

第一個被廣泛採用的數據並行技術是TensorFlow中的參數伺服器策略。這個功能實際上早於TensorFlow的第一個版本,早在2012年google內部的前身DistBelief中就已經實現了。這一策略在下圖中得到了很好的說明:

在參數伺服器策略中,worker和parameter進程的數量是可變的,每個worker進程在GPU內存中維護自己的模型獨立副本。梯度更新計算如下:

在接收到開始信號後,每個工作進程為其特定的批處理片積累梯度。這些工人以扇出的方式將更新發送到參數伺服器。參數伺服器會一直等待,直到它們擁有所有worker更新,然後對它們負責的梯度更新參數空間的那部分梯度求平均。梯度更新被分散到worker上,然後將它們加起來,應用到內存中模型權重的副本上(從而保持worker模型同步)。一旦每個worker都應用了更新,新的一批訓練就可以開始了。雖然很容易實現,但是這個策略有一些主要的限制。其中最重要的一點是,每個附加的參數伺服器在每個同步步驟中都需要n_workers額外的網絡調用——一個O(n)複雜度代價。計算的總體速度取決於最慢的連接,因此基於大參數伺服器的模型訓練作業在實踐中效率非常低,將網絡GPU利用率推到50%或以下。

更現代的分布式培訓策略廢除了參數伺服器,在DistributedDataParallel 並行策略中,每個進程都是一個工作進程。每個進程仍然在內存中維護模型權重的完整副本,但是批處理片梯度更新現在是同步的,並且直接在工作進程本身上平均。這是使用從高性能計算領域借來的技術來實現的:全歸約算法(all-reduce algorithm)

該圖顯示了全歸約算法的一種特定實現方式,即循環全歸約。 該算法提供了一種優雅的方式來同步一組進程之間的一組變量(在本例中為張量)的狀態。 向量在直接的worker到worker連接的序列中直接傳遞。 這消除了worker與參數伺服器之間的連接所造成的網絡瓶頸,從而大大提高了性能。

在該方案中,梯度更新計算如下:

每個worker維護它自己的模型權重副本和它自己的數據集副本。在接收到開始信號後,每個工作進程從數據集中提取一個分離的批處理,並為該批處理計算一個梯度。worker使用all-reduce算法來同步他們各自的梯度,本地計算所有節點上相同的平均梯度。每個worker都將梯度更新應用到它的本地模型副本上。下一批訓練開始。在2017年百度的一篇論文《Bringing HPC Techniques to Deep Learning》中,這種全精簡策略被提上了日程。它的偉大之處在於它基於眾所周知的HPC技術以及長期存在的開源實現。All-reduce包含在消息傳遞接口(MPI)的標準中,這就是為什麼PyTorch不少於三個不同的後端實現:Open MPI、NVIDIA NCCL和Facebook Gloo(一般情況下建議使用NVIDIA NCCL)

數據分發,第1部分:流程初始化

不幸的是,將訓練腳本修改為使用DistributedDataParallel 並行策略並不是簡單的一行更改。

為了演示API是如何工作的,我們將構建一個完整的分布式訓練腳本(在本文後面的基準測試中,我們將繼續討論這個腳本)。

您需要處理的第一個也是最複雜的新事情是進程初始化。普通的PyTorch訓練腳本在單個進程中執行其代碼的單一副本。使用數據並行模型,情況就更加複雜了:現在訓練腳本的同步副本與訓練集群中的gpu數量一樣多,每個gpu運行在不同的進程中。

考慮以下最小的例子:

# multi_init.pyimport torchimport torch.distributed as distimport torch.multiprocessing as mpdef init_process(rank, size, backend='gloo'):""" Initialize the distributed environment. """ os.environ['MASTER_ADDR'] = '127.0.0.1' os.environ['MASTER_PORT'] = '29500' dist.init_process_group(backend, rank=rank, world_size=size)def train(rank, num_epochs, world_size): init_process(rank, world_size) print( f"Rank {rank + 1}/{world_size} process initialized.\n" ) # rest of the training script goes here!WORLD_SIZE = torch.cuda.device_count()if __name__=="__main__": mp.spawn( train, args=(NUM_EPOCHS, WORLD_SIZE), nprocs=WORLD_SIZE, join=True )

在MPI的世界中,WORLDSIZE是編排的進程數量,(全局)rank是當前進程在該MPI中的位置。例如,如果這個腳本要在一個有4個gpu的強大機器上執行,WORLDSIZE應該是4(因為torch.cuda.device_count() == 4),所以是mp.spawn會產生4個不同的進程,它們的等級 分別是0、1、2或3。等級為0的進程被賦予一些額外的職責,因此被稱為主進程。

當前進程的等級將作為派生入口點(在本例中為訓練方法)作為其第一個參數傳遞。 在訓練時可以執行任何工作之前,它需要首先建立與對等點對點的連接。 這是dist.initprocessgroup的工作。 在主進程中運行時,此方法在MASTERADDR:MASTERPORT上設置套接字偵聽器,並開始處理來自其他進程的連接。 一旦所有進程都已連接,此方法將處理建立對等連接,以允許進程進行通信。

請注意,此代碼僅適用於在一臺多GPU機器上進行訓練! 同一臺機器用於啟動作業中的每個流程,因此訓練只能利用連接到該特定機器的GPU。 這使事情變得容易:設置IPC就像在localhost上找到一個空閒埠一樣容易,該埠對於該計算機上的所有進程都是立即可見的。 跨計算機的IPC更為複雜,因為它需要配置一個對所有計算機可見的外部IP位址。

在本入門教程中,我們將特別關注單機訓練(也稱為垂直擴展)。 即使在單主機,垂直擴展也是一個非常強大的工具。 如果在雲端,垂直擴展可讓您將深度學習訓練工作一直擴展到8xV100實例(例如AWS上的p3.16xlarge)。

我們將在以後的博客文章中討論水平擴展和數據並行化。

數據分發,第2部分:流程同步

現在我們了解了初始化過程,我們可以開始完成所有工作的train方法的主體。

回想一下我們到目前為止:

def train(rank, num_epochs, world_size):init_process(rank, world_size) print( f"{rank + 1}/{world_size} process initialized.\n" ) # rest of the training script goes here!

我們的四個訓練過程中的每一個都會運行此函數直到完成,然後在完成時退出。 如果我們現在(通過python multi_init.py)運行此代碼,我們將在控制臺上看到類似以下內容:

$ python multi_init.py1/4 process initialized.3/4 process initialized.2/4 process initialized.4/4 process initialized.

這些過程是獨立執行的,並且不能保證訓練循環中任一點處於什麼狀態。 所以這裡需要對初始化過程進行一些仔細的更改。

(1)任何下載數據的方法都應隔離到主進程中。

否則,將在所有過程之間複製數據下載過程,從而導致四個過程同時寫入同一文件,這是造成數據損壞的原因。

修改這很容易做到:

# import torch.distributed as distif rank == 0:downloading_dataset() downloading_model_weights()dist.barrier()print( f"Rank {rank + 1}/{world_size} training process passed data download barrier.\n")

此代碼示例中的dist.barrier將阻塞調用,直到完成主進程(rank == 0)downloadingdataset和downloadingmodel_weights為止。 這樣可以將所有網絡I / O隔離到一個進程中,並防止其他進程繼續前進。

(2)數據加載器需要使用DistributedSampler。

代碼示例:

def get_dataloader(rank, world_size):dataset = PascalVOCSegmentationDataset() sampler = DistributedSampler( dataset, rank=rank, num_replicas=world_size, shuffle=True ) dataloader = DataLoader( dataset, batch_size=8, sampler=sampler )

DistributedSampler使用rank和worldsize找出如何將整個過程中的數據集拆分為不重疊的批次。 工作進程的每個訓練步驟都從其本地數據集副本中檢索batchsize觀測值。 在四個GPU的示例情況下,這意味著有效批大小為8 * 4 = 32。

(3)在正確的設備中加載張量。

為此,請使用該進程正在管理的設備的rank來參數化.cuda()調用:

batch = batch.cuda(rank)segmap = segmap.cuda(rank)model = model.cuda(rank)

(4)必須禁用模型初始化中的任何隨機性。

在整個訓練過程中,模型必須啟動並保持同步,這一點非常重要。 否則,您將獲得不正確的漸變,並且模型將無法收斂。

可以使用以下代碼使torch.nn.init.kaimingnormal之類的隨機初始化方法具有確定性:

torch.manual_seed(0)torch.backends.cudnn.deterministic = Truetorch.backends.cudnn.benchmark = Falsenp.random.seed(0)

PyTorch文檔有一整個頁面專門討論此主題:randomness.html

(5)任何執行文件I / O的方法都應與主進程隔離。

這與隔離網絡I / O的原因相同,是必要的:由於並發寫入同一文件而導致的效率低下和潛在的數據損壞。 同樣,使用簡單的條件邏輯很容易做到這一點:

if rank == 0:if not os.path.exists('/spell/checkpoints/'): os.mkdir('/spell/checkpoints/') torch.save( model.state_dict(), f'/spell/checkpoints/model_{epoch}.pth' )

順便說一句,請注意,要記錄的所有全局損失值或統計信息都需要您自己同步數據。 可以使用torch.distributed中的其他MPI原語來完成此操作,本教程未對此進行深入介紹。 可以參閱Distributed Communication Package PyTorch文檔頁面以獲取詳細的API參考。

(6)將模型包裝在DistributedDataParallel中。

假設您已正確完成所有其他操作,這就是神奇的地方。

model = DistributedDataParallel(model, device_ids=[rank])

這一步時必須的也是最後一步,如果你做完了恭喜你,你的模型現在可以在分布式數據並行模式下訓練!

那DataParallel呢?

熟悉PyTorch API的讀者可能知道PyTorch中還有另一種數據並行化策略,即torch.nn.DataParallel。 該API易於使用。 您要做的就是包裝模型初始化,如下所示:

model = nn.DataParallel(model)

所有的改動只有一行! 為什麼不使用它呢?

在後臺,DataParallel使用多線程而不是多處理來管理其GPU工作器。 這極大地簡化了實現:由於工作進程是同一進程的所有不同線程,因此它們都可以訪問相同的共享狀態,而無需任何其他同步步驟。

但是,由於存在全局解釋器鎖,在Python中將多線程用於計算作業的效果很差。 如下一節中的基準測試所示,使用DataParallel並行化的模型比使用DistributedDataParallel並行化的模型要慢得多。

儘管如此,如果你不想花費額外的時間和精力郵箱使用多GPU訓練,DataParallel實可以考慮的。

基準測試

為了對分布式模型訓練性能進行基準測試,我在PASCAL VOC 2012數據集(來自torchvision數據集)上訓練了20個輪次的DeepLabV3-ResNet 101模型(通過Torch Hub)。 我啟動了五個不同版本的模型巡訓練工作:一次在單個V100上(在AWS上為p3.2xlarge),一次在V100x4(p3.8xlarge)和V100x8(p3.16xlarge)上使用 DistributedDataParallel和DataParallel。 該基準測試不包括運行開始時花在下載數據上的時間-僅模型訓練和節省時間計數。

DistributedDataParallel的效率明顯高於DataParallel,但還遠遠不夠完美。 從V100x1切換到V100x4是原始GPU功耗的4倍,但模型訓練速度僅為3倍。 通過升級到V100x8使計算進一步加倍,只會使訓練速度提高約30%,但是DataParallel的效率幾乎達到了DistributedDataParallel的水平。

結論

在本文中,我們討論了分布式訓練和數據並行化,了解了DistributedDataParallel和DataParallel API,並將其應用於實際模型並進行了一個簡單的基準測試。

分布式計算的領域還有很多可以改進,PyTorch團隊剛剛在本月獲得了新的PR,該PR承諾將對DistributedDataParallel的性能進行重大改進。 希望這些時間在將來的版本中降下來!

我認為討論不多的事情是分布式訓練對開發人員生產力的影響。 從「需要三個小時的訓練」到「需要一個小時的訓練」,即使採用中等大小的模型,也可以極大地增加您可以在一天之內和使用該模型進行的實驗的數量,這對開發人員而言是一個巨大的進步。

作者:Aleksey Bilogur

github/spellml/deeplab-voc-2012

deephub翻譯組

註:本文是我們和pytorch-handbook的作者合作翻譯的第一篇文章,後續還會有更深入的合作,另外介紹一下pytorch-handbook .這本pytorch的中文手冊已經在github上獲取了12000+的star是一本非常詳細的pytorch入門教程和查詢手冊,如果是想深入的學習,趕緊關注這個項目吧。

github/zergtant/pytorch-handbook

相關焦點

  • Pytorch中的分布式神經網絡訓練
    經常,在訓練這些網絡時,深度學習從業人員需要使用多個GPU來有效地訓練它們。 在本文中,我將向您介紹如何使用PyTorch在GPU集群上設置分布式神經網絡訓練。通常,分布式訓練會在有一下兩種情況。在GPU之間拆分模型:如果模型太大而無法容納在單個GPU的內存中,則需要在不同GPU之間拆分模型的各個部分。跨GPU進行批量拆分數據。
  • Pytorch中的分布式神經網絡訓練|pytorch|bat|拆分|調用_網易訂閱
    經常,在訓練這些網絡時,深度學習從業人員需要使用多個GPU來有效地訓練它們。 在本文中,我將向您介紹如何使用PyTorch在GPU集群上設置分布式神經網絡訓練。  在向後傳遞過程中,將每個副本的梯度求和以生成最終的梯度,並將其應用於主gpu(上圖中的GPU-1)以更新模型權重。 在下一次迭代中,主GPU上的更新模型將再次複製到每個GPU設備上。  在PyTorch中,只需要一行就可以使用nn.DataParallel進行分布式訓練。 該模型只需要包裝在nn.DataParallel中。
  • PyTorch實現TPU版本CNN模型
    隨著深度學習模型在各種應用中的成功實施,現在是時候獲得不僅準確而且速度更快的結果。為了得到更準確的結果,數據的大小是非常重要的,但是當這個大小影響到機器學習模型的訓練時間時,這一直是一個值得關注的問題。為了克服訓練時間的問題,我們使用TPU運行時環境來加速訓練。為此,PyTorch一直在通過提供最先進的硬體加速器來支持機器學習的實現。
  • 代碼詳解:用Pytorch訓練快速神經網絡的9個技巧
    GPU的訓練將對許多GPU核心上的數學計算進行並行處理。能加速多少取決於使用的GPU類型。個人使用的話,推薦使用2080Ti,公司使用的話可用V100。剛開始你可能會覺得壓力很大,但其實只需做兩件事: 1)將你的模型移動到GPU上,2)在用其運行數據時,把數據導至GPU中。
  • PyTorch 0.2發布:更多NumPy特性,高階梯度、分布式訓練等
    PyTorch的GitHub新版發布說明中介紹了0.2版的以下新特性:NumPy風格的Tensor BroadcastingBroadcasting是NumPy在算數運算中處理不同形狀數組的一種方式,在特定條件下,比較小的數組會通過比較大的數組進行「廣播」,來獲得相應的形狀。
  • 9個技巧讓你的PyTorch模型訓練變得飛快!
    **任何使用Pytorch進行深度學習模型研究的人,如研究人員、博士生、學者等,我們在這裡談論的模型可能需要你花費幾天的訓練,甚至是幾周或幾個月。大多數模型使用32bit精度數字進行訓練。然而,最近的研究發現,16bit模型也可以工作得很好。混合精度意味著對某些內容使用16bit,但將權重等內容保持在32bit。要在Pytorch中使用16bit精度,請安裝NVIDIA的apex庫,並對你的模型進行這些更改。
  • Pytorch-Transformers 1.0 發布,支持六個預訓練框架,含 27 個預...
    哪些支持PyTorch-Transformers(此前叫做pytorch-pretrained-bert)是面向自然語言處理,當前性能最高的預訓練模型開源庫。27個預訓練模型項目中提供了27個預訓練模型,下面是這些模型的完整列表,以及每個模型的簡短介紹。
  • PyTorch 1.7發布,支持CUDA 11、Windows分布式訓練
    該版本增添了很多新特性,如支持 CUDA 11、Windows 分布式訓練、增加了支持快速傅立葉變換(FFT)的新型 API 等。PyTorch 1.7 版本包含很多新的 API,如支持 NumPy 兼容的 FFT 操作、性能分析工具,以及對基於分布式數據並行(DDP)和基於遠程過程調用(RPC)的分布式訓練的重要更新。
  • PyTorch最新:分布式模型並行、Java程序、移動端等多項新功能
    本次更新的重點是增加了很多重要的新特性,包括給用戶提供 Build 級別的移動端定製化支持、增加分布式模型並行訓練、讓 Java 程序能夠運行 TorchScript 等。此外還有 JIT、C++、分布式訓練、Eager 前端、PyTorch Mobile 等方面的功能改進和 Bug 修復。
  • PyTorch最新:支持分布式模型並行、Java程序、移動端等多項新功能
    本次更新的重點是增加了很多重要的新特性,包括給用戶提供 Build 級別的移動端定製化支持、增加分布式模型並行訓練、讓 Java 程序能夠運行 TorchScript 等。此外還有 JIT、C++、分布式訓練、Eager 前端、PyTorch Mobile 等方面的功能改進和 Bug 修復。
  • PyTorch上也有Keras了,訓練模型告別Debug,只需專注數據和邏輯
    魚羊 發自 凹非寺量子位 報導 | 公眾號 QbitAI在開始一個新的機器學習項目時,難免要重新編寫訓練循環,加載模型,分布式訓練……然後在Debug的深淵裡看著時間譁譁流逝,而自己離項目核心還有十萬八千裡。
  • 兩個提高深度學習訓練效率的絕技
    2.2 解決方案採用多進程並行處理,加快CPU加載數據的性能 keras keras 中提供了workers use_multiprocessing來採用多進程方式,並行處理數據,並push到隊列中,共GPU模型訓練。
  • 高性能PyTorch是如何煉成的?過來人吐血整理的10條避坑指南
    建議 4:調整 DataLoader 的工作程序PyTorch 使用一個 DataLoader 類來簡化用於訓練模型的批處理過程。為了加快速度,它可以使用 Python 中的多進程並行執行。大多數情況下,它可以直接使用。還有幾點需要記住:每個進程生成一批數據,這些批通過互斥鎖同步可用於主進程。
  • 高性能PyTorch是如何煉成的?整理的10條脫坑指南
    建議 4:調整 DataLoader 的工作程序PyTorch 使用一個 DataLoader 類來簡化用於訓練模型的批處理過程。為了加快速度,它可以使用 Python 中的多進程並行執行。大多數情況下,它可以直接使用。還有幾點需要記住:每個進程生成一批數據,這些批通過互斥鎖同步可用於主進程。
  • 使用PyTorch進行主動遷移學習:讓模型預測自身的錯誤
    在當前的機器學習中,遷移學習通常是指獲取一個現有的神經模型,然後對最後一層 (或最後幾層) 進行再訓練,以完成新的任務,它可以表示為:遷移學習的一個例子。模型預測標籤為「a」、「B」、「C」或「D」,單獨的數據集標籤為「W」、「X」、「Y」和「Z」。再訓練模型的最後一層模型現在能夠預測標籤「W」、「X」、「Y」和「Z」。
  • 基於PyTorch的「Keras」:除了核心邏輯通通都封裝
    例如在 PyTorch 1.0 中,編譯工具 torch.jit 就包含一種名為 Torch Script 的語言,它是 Python 的子語言,開發者使用它能進一步對模型進行優化。用 PyTorch 寫模型,除了數據加載和模型定義部分外,整個訓練和驗證的邏輯、配置都需要我們手動完成,這些步驟都較為繁瑣。甚至可以說,研究者需要耗費相當多的精力處理這一部分的代碼,還要祈禱不出 Bug。
  • 讓PyTorch更輕便,這款深度學習框架你值得擁有!GitHub 6.6k星
    話不多說,我們就來看看這個輕量版的「PyTorch」。關於LightningLightning將DL/ML代碼分為三種類型:研究代碼、工程代碼、非必要代碼。針對不同的代碼,Lightning有不同的處理方式。這裡的研究代碼指的是特定系統及其訓練方式,比如GAN、VAE,這類的代碼將由LightningModule直接抽象出來。
  • GPU上的隨機森林:比Apache Spark快2000倍
    該算法通過bootstrap聚合訓練出多棵決策樹,然後通過集成對輸出進行預測。由於其集成特徵的特點,隨機森林是一種可以在分布式計算環境中實現的算法。樹可以在集群中跨進程和機器並行訓練,結果比使用單個進程的訓練時間快得多。
  • 語音識別開源工具PyTorch-Kaldi:兼顧Kaldi效率與PyTorch靈活性
    [exp]:這部分指定了一些高級信息,例如用於實驗的文件夾、訓練迭代次數、隨機數種子,它也允許用戶指定實驗是在 CPU/GPU 或者多 GPU 上進行,下面是一個例子:[exp] cmd =Kaldi 中的原生 C++庫進行提取的。
  • Deep CARs:使用Pytorch學習框架實現遷移學習
    在遷移學習中,現有的這個模型稱為預訓練模型。大多數用於遷移學習的預訓練模型都是基於大型卷積神經網絡之上的。一些人使用的預訓練的模型有VGGNet、ResNet、DenseNet、谷歌的Inception等等。這些網絡大多是在ImageNet上訓練的。ImageNet是一個龐大的數據集,包含100多萬張標記圖像,種類達1000個。