[原始碼解析] PyTorch 分散式(4)------分散式應用基礎概念
0x00 摘要
本文以 PyTorch 官方文件 https://pytorch.org/tutorials/intermediate/dist_tuto.html 為基礎,對如何編寫分散式進行了介紹,並且加上了自己的理解。
PyTorch 的分散式包(即 torch.distributed
)使研究人員和從業人員能夠輕鬆地跨程式和跨機器叢集平行計算。它利用訊息傳遞語義來允許每個程式與任何其他程式通訊資料。與 multiprocessing ( torch.multiprocessing
) 包相反,程式可以使用不同的通訊後端,並且不限於在同一臺機器上執行。
在這個簡短的教程中,我們將介紹 PyTorch 的分散式包。我們將看到如何設定分散式,使用不同的通訊策略,並瞭解包的一些內部結構。
本系列其他文章如下:
[原始碼解析]深度學習利器之自動微分(3) --- 示例解讀
[原始碼解析]PyTorch如何實現前向傳播(1) --- 基礎類(上)
[原始碼解析]PyTorch如何實現前向傳播(2) --- 基礎類(下)
[原始碼解析] PyTorch如何實現前向傳播(3) --- 具體實現
[原始碼解析] Pytorch 如何實現後向傳播 (1)---- 呼叫引擎
[原始碼解析] Pytorch 如何實現後向傳播 (2)---- 引擎靜態結構
[原始碼解析] Pytorch 如何實現後向傳播 (3)---- 引擎動態邏輯
[原始碼解析] PyTorch 如何實現後向傳播 (4)---- 具體演算法
[原始碼解析] PyTorch 分散式(1)------歷史和概述
[原始碼解析] PyTorch 分散式(2) ----- DataParallel(上)
[原始碼解析] PyTorch 分散式(3) ----- DataParallel(下)
0x01 基本概念
我們首先介紹一些 torch.distributed 中的關鍵概念,這些概念在編寫程式時至關重要。
-
Node
- 物理例項或容器。 -
Worker
- 分佈訓練環境中的worker。 -
Group(程式組):我們所有程式的子集,用於集體通訊等。
- 預設情況下,只有一個組,一個 job 即為一個組,也即一個 world。
- 當需要進行更加精細的通訊時,可以通過 new_group 介面,使用 world 的子集來建立新組。
-
Backend(後端):程式通訊庫。PyTorch 支援NCCL,GLOO,MPI。
-
World_size :程式組中的程式數,可以認為是全域性程式個數。
-
Rank :分配給分散式程式組中每個程式的唯一識別符號。
- 從 0 到 world_size 的連續整數,可以理解為程式序號,用於程式間通訊。
- rank = 0 的主機為 master 節點。
- rank 的集合可以認為是一個全域性GPU資源列表。
-
local rank:程式內的 GPU 編號,非顯式引數,這個一般由 torch.distributed.launch 內部指定。例如, rank = 3,local_rank = 0 表示第 3 個程式內的第 1 塊 GPU。
0x02 設計思路
分散式訓練最主要的問題就是:worker 之間如何通訊。為了解決通訊問題,PyTorch 引入了幾個概念,我們先分析通訊的需求,然後看看 PyTorch 如何通過這幾個概念來滿足需求的。
2.1 通訊需求
我們總結一下分散式訓練的具體需求:
- worker 之間如何互相發現?
- worker 之間如何進行點對點通訊?
- worker 之間如何做集合通訊?
- 如何把訓練程式和集合通訊聯絡起來?
接下來圍繞這幾個問題和文件內容進行分析。
2.2 概念
針對通訊需求,PyTorch 提供的幾個概念是:程式組,後端,初始化,Store。
- 程式組 :DDP是真正的分散式訓練,可以使用多臺機器來組成一次並行運算的任務。為了能夠讓 DDP 的各個worker之間通訊,PyTorch 設定了程式組這個概念。組是我們所有程式的子集。
- 看其本質,就是進行通訊的程式們。
- 從程式碼來看,給每一個訓練的process 建立一個 通訊thread,在後臺做通訊。比如對於 ProcessGroupMPI,在通訊執行緒新增了一個 queue,做 buffer 和 非同步處理。
- 後端 :後端是一個邏輯上的概念。
- 本質上後端是一種IPC通訊機制。PyTorch 既然能夠在不同的程式間進行通訊,那必然是依賴於一些IPC的通訊機制,這些通訊機制一般是由PyTorch之外的三方實現的,比如後端使用 ProcessGroupMPI 還是 ProcessGroupGloo 。
- 後端 允許程式通過共享它們的位置來相互通訊。對於使用者來說,就是採用哪種方式來進行集合通訊,從程式碼上看,就是走什麼流程(一系列流程).....
- 初始化 : 雖然有了後端和程式組的概念,但是如何讓 worker 在建立程式組之前發現彼此? 這就需要一種初始化方法來告訴大家傳遞一個資訊:如何聯絡到其它機器上的程式。目前DDP模組支援3種初始化方法。
- Store : 分散式包(distributed package)有一個分散式鍵值儲存服務,這個服務在組中的程式之間共享資訊以及初始化分散式包 (通過顯式建立儲存來作為
init_method
的替代)。 - 初始化 vs Store :
- 當 MPI 為後端時候, init_method 沒有用處。
- 在非 MPI 後端時候,如果沒有 store 引數,則使用 init_method 構建一個store,所以最終還是落到了 store 之上。
對於這些概念,我們用下圖來看看 DDP 是如何利用這些概念。
假設 DDP 包括兩個worker 做訓練,其中每個 worker 會:
-
在 Main Thread 之中做訓練,在 Reducer 之中做 allreduce,具體是往 ProcessGroupMPI 的 workerThread_ 傳送指令。
-
workerThread_ 會呼叫 MPI_Allreduce 進行 集合通訊,使用的就是 MPI 後端。
0x03 設定
首先,我們需要能夠同時執行多個程式。如果您有權訪問計算叢集,您應該諮詢您的本地系統管理員或使用您最喜歡的協調工具(例如, pdsh、 clustershell或 其他)。本文我們將在一臺機器之上使用以下模板來fork多個程式。
"""run.py:"""
#!/usr/bin/env python
import os
import torch
import torch.distributed as dist
import torch.multiprocessing as mp
def run(rank, size):
""" Distributed function to be implemented later. """
pass
def init_process(rank, size, fn, 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)
fn(rank, size)
if __name__ == "__main__":
size = 2
processes = []
mp.set_start_method("spawn")
for rank in range(size):
p = mp.Process(target=init_process, args=(rank, size, run))
p.start()
processes.append(p)
for p in processes:
p.join()
上述指令碼產生兩個程式,每個程式將設定分散式環境,初始化程式組 ( dist.init_process_group
),最後執行給定的run
函式。
我們來看看init_process
函式。它確保每個程式都能夠使用相同的 IP 地址和埠來與主節點進行協調。請注意,我們使用了gloo
後端,但其他後端也可用。這本質上允許程式通過共享它們的位置來相互通訊。
0x04 點對點通訊
以下是點對點通訊的一個示意圖 :傳送和接收。
從一個程式到另一個程式的資料傳輸稱為點對點通訊。這些是通過send
和recv
函式或isend
和 irecv
來實現的。
"""Blocking point-to-point communication."""
def run(rank, size):
tensor = torch.zeros(1)
if rank == 0:
tensor += 1
# Send the tensor to process 1
dist.send(tensor=tensor, dst=1)
else:
# Receive tensor from process 0
dist.recv(tensor=tensor, src=0)
print('Rank ', rank, ' has data ', tensor[0])
在上面的例子中,兩個程式都以零張量開始,然後程式 0 遞增張量並將其傳送到程式 1,這樣它們都以 1.0 結束。請注意,程式 1 需要分配記憶體以儲存它將接收的資料。
還要注意send
/recv
是阻塞實現:兩個程式都停止,直到通訊完成。另一方面,isend
和 irecv
是 非阻塞的,在非阻塞情況下指令碼繼續執行,方法返回一個Work
物件,我們可以選擇在其之上進行 wait()
。
"""Non-blocking point-to-point communication."""
def run(rank, size):
tensor = torch.zeros(1)
req = None
if rank == 0:
tensor += 1
# Send the tensor to process 1
req = dist.isend(tensor=tensor, dst=1)
print('Rank 0 started sending')
else:
# Receive tensor from process 0
req = dist.irecv(tensor=tensor, src=0)
print('Rank 1 started receiving')
req.wait()
print('Rank ', rank, ' has data ', tensor[0])
使用isend
和 irecv
時,我們必須小心使用。由於我們不知道資料何時會傳送到其他程式,因此我們不應在req.wait()
完成之前修改傳送的張量或訪問接收的張量。換句話說,
- 在
dist.isend()
之後寫入tensor
,將導致未定義的行為。 - 在
dist.irecv()
之後讀取tensor
,將導致未定義的行為。
但是,在req.wait()
執行之後,我們可以保證通訊發生了,並且可以保證儲存的tensor[0]
值為 1.0。
當我們想要對程式的通訊進行細粒度控制時,點對點通訊很有用。它們可用於實現複雜巧妙的演算法,例如在百度的 DeepSpeech或 Facebook 的大規模實驗中使用的演算法。
0x05 集合通訊
以下是集合通訊的示意圖。
Scatter | Gather |
---|---|
Reduce | All-Reduce |
Broadcast | All-Gather |
與點對點通訊相反,集合是允許一個組中所有程式進行通訊的模式。組是我們所有程式的子集。要建立一個組,我們可以將一個rank列表傳遞給dist.new_group(group)
。預設情況下,集合通訊在所有程式上執行,"所有程式"也稱為world。例如,為了獲得所有過程中所有張量的總和,我們可以使用dist.all_reduce(tensor, op, group)
。
""" All-Reduce example."""
def run(rank, size):
""" Simple collective communication. """
group = dist.new_group([0, 1])
tensor = torch.ones(1)
dist.all_reduce(tensor, op=dist.ReduceOp.SUM, group=group)
print('Rank ', rank, ' has data ', tensor[0])
由於我們想要組中所有張量的總和,因此我們將其 dist.ReduceOp.SUM
用作歸約運算子。一般來說,任何可交換的數學運算都可以用作運算子。PyTorch 帶有 4 個這樣開箱即用的運算子,它們都在元素級別工作:
dist.ReduceOp.SUM
,dist.ReduceOp.PRODUCT
,dist.ReduceOp.MAX
,dist.ReduceOp.MIN
.
除了 dist.all_reduce(tensor, op, group)
之外,目前在 PyTorch 中總共實現了以下集合操作。
dist.broadcast(tensor, src, group)
:從src
複製tensor
到所有其他程式。dist.reduce(tensor, dst, op, group)
:施加op
於所有tensor
,並將結果儲存在dst
.dist.all_reduce(tensor, op, group)
: 和reduce操作一樣,但結果儲存在所有程式中。dist.scatter(tensor, scatter_list, src, group)
: 複製張量列表scatter_list[i]
中第 $ i^{\text{th}}$ 個張量到 第$ i^{\text{th}}$ 個程式。dist.gather(tensor, gather_list, dst, group)
: 從所有程式拷貝tensor
到dst
。dist.all_gather(tensor_list, tensor, group)
: 在所有程式之上,執行從所有程式拷貝tensor
到tensor_list
的操作。dist.barrier(group)
:阻止組內所有程式,直到每一個程式都已經進入該function。
0x06 分散式訓練
注意:您可以在此 GitHub 儲存庫中找到本節的示例指令碼。
現在我們瞭解了分散式模組的工作原理,讓我們用它寫一些有用的東西。我們的目標是複製DistributedDataParallel的功能 。當然,這將是一個教學示例,在實際情況下,您應該使用上面連結的經過充分測試和優化的官方版本。
我們想要實現隨機梯度下降的分散式版本。我們的指令碼將讓所有程式在他們本地擁有的一批資料上計算本地模型的梯度,然後平均他們的梯度。為了在改變程式數量時確保類似的收斂結果,我們首先必須對我們的資料集進行分割槽(您也可以使用 tnt.dataset.SplitDataset,而不是下面的程式碼段)。
""" Dataset partitioning helper """
class Partition(object):
def __init__(self, data, index):
self.data = data
self.index = index
def __len__(self):
return len(self.index)
def __getitem__(self, index):
data_idx = self.index[index]
return self.data[data_idx]
class DataPartitioner(object):
def __init__(self, data, sizes=[0.7, 0.2, 0.1], seed=1234):
self.data = data
self.partitions = []
rng = Random()
rng.seed(seed)
data_len = len(data)
indexes = [x for x in range(0, data_len)]
rng.shuffle(indexes)
for frac in sizes:
part_len = int(frac * data_len)
self.partitions.append(indexes[0:part_len])
indexes = indexes[part_len:]
def use(self, partition):
return Partition(self.data, self.partitions[partition])
使用上面的程式碼片段,我們現在可以使用以下幾行簡單地對任何資料集進行分割槽:
""" Partitioning MNIST """
def partition_dataset():
dataset = datasets.MNIST('./data', train=True, download=True,
transform=transforms.Compose([
transforms.ToTensor(),
transforms.Normalize((0.1307,), (0.3081,))
]))
size = dist.get_world_size()
bsz = 128 / float(size)
partition_sizes = [1.0 / size for _ in range(size)]
partition = DataPartitioner(dataset, partition_sizes)
partition = partition.use(dist.get_rank())
train_set = torch.utils.data.DataLoader(partition,
batch_size=bsz,
shuffle=True)
return train_set, bsz
假設我們有 2 個副本,那麼每個程式擁有的train_set
將包括 60000 / 2 = 30000 個樣本。我們還將批量大小除以副本數,以保持整體批量大小為 128。
我們現在可以編寫常見的前向後向優化訓練程式碼,並新增一個函式呼叫來平均我們模型的梯度(以下內容主要受PyTorch MNIST官方示例的啟發)。
""" Distributed Synchronous SGD Example """
def run(rank, size):
torch.manual_seed(1234)
train_set, bsz = partition_dataset()
model = Net()
optimizer = optim.SGD(model.parameters(),
lr=0.01, momentum=0.5)
num_batches = ceil(len(train_set.dataset) / float(bsz))
for epoch in range(10):
epoch_loss = 0.0
for data, target in train_set:
optimizer.zero_grad()
output = model(data)
loss = F.nll_loss(output, target)
epoch_loss += loss.item()
loss.backward()
average_gradients(model)
optimizer.step()
print('Rank ', dist.get_rank(), ', epoch ',
epoch, ': ', epoch_loss / num_batches)
它仍然需要實現該average_gradients(model)
函式,該函式只是接收一個模型並在整個世界(所有訓練程式)中平均其梯度。
""" Gradient averaging. """
def average_gradients(model):
size = float(dist.get_world_size())
for param in model.parameters():
dist.all_reduce(param.grad.data, op=dist.ReduceOp.SUM)
param.grad.data /= size
現在,我們成功實現了分散式同步 SGD,並且可以在大型計算機叢集上訓練任何模型。
注意:雖然最後一句在技術上是正確的,但實現同步 SGD 的生產級實現需要更多技巧。再次使用經過測試和優化的內容。
0x07 Ring-Allreduce
作為額外的挑戰,假設我們想要實現 DeepSpeech 的高效 ring allreduce。使用點對點集合可以很容易地實現這一點。
""" Implementation of a ring-reduce with addition. """
def allreduce(send, recv):
rank = dist.get_rank()
size = dist.get_world_size()
send_buff = send.clone()
recv_buff = send.clone()
accum = send.clone()
left = ((rank - 1) + size) % size
right = (rank + 1) % size
for i in range(size - 1):
if i % 2 == 0:
# Send send_buff
send_req = dist.isend(send_buff, right)
dist.recv(recv_buff, left)
accum[:] += recv_buff[:]
else:
# Send recv_buff
send_req = dist.isend(recv_buff, right)
dist.recv(send_buff, left)
accum[:] += send_buff[:]
send_req.wait()
recv[:] = accum[:]
在上面的指令碼中, allreduce(send, recv)
函式的簽名與 PyTorch 中 函式的簽名略有不同。它接受一個recv
張量並將所有send
張量的總和儲存在其中。作為留給讀者的練習,我們的版本與 DeepSpeech 中的版本之間仍有一個區別:它們的實現將梯度張量分成塊,以便最佳地利用通訊頻寬(提示: torch.chunk)。
0x08 高階主題
由於要涵蓋的內容很多,因此本節分為兩個小節:
- 通訊後端:我們學習如何使用 MPI 和 Gloo 進行 GPU-GPU 通訊。
- 初始化方法:我們瞭解在
dist.init_process_group()
之中如何建立初始協調階段。
8.1 通訊後端
torch.distributed
最優雅的方面之一是它能夠在不同的後端之上抽象和構建。如前所述,目前在 PyTorch 中實現了三個後端:Gloo、NCCL 和 MPI。它們每個都有不同的規格和權衡,具體取決於所需的用例。可在此處找到支援功能的比較表 。
以下資訊來自 https://pytorch.org/docs/stable/distributed.html。
8.1.1 後端種類
torch.distributed
支援三個內建後端,每個後端都有不同的功能。下表顯示了哪些函式可用於 CPU / CUDA 張量。
Backend | gloo |
mpi |
nccl |
|||
---|---|---|---|---|---|---|
Device | CPU | GPU | CPU | GPU | CPU | GPU |
send | ✓ | ✘ | ✓ | ? | ✘ | ✘ |
recv | ✓ | ✘ | ✓ | ? | ✘ | ✘ |
broadcast | ✓ | ✓ | ✓ | ? | ✘ | ✓ |
all_reduce | ✓ | ✓ | ✓ | ? | ✘ | ✓ |
reduce | ✓ | ✘ | ✓ | ? | ✘ | ✓ |
all_gather | ✓ | ✘ | ✓ | ? | ✘ | ✓ |
gather | ✓ | ✘ | ✓ | ? | ✘ | ✘ |
scatter | ✓ | ✘ | ✓ | ? | ✘ | ✘ |
reduce_scatter | ✘ | ✘ | ✘ | ✘ | ✘ | ✓ |
all_to_all | ✘ | ✘ | ✓ | ? | ✘ | ✓ |
barrier | ✓ | ✘ | ✓ | ? | ✘ | ✓ |
PyTorch 分散式包支援 Linux(穩定)、MacOS(穩定)和 Windows(原型)。對於 Linux,預設情況下,Gloo 和 NCCL 後端包含在分散式 PyTorch 中(僅在使用 CUDA 構建時才支援NCCL)。MPI是一個可選的後端,只有從原始碼構建PyTorch時才能包含它(例如,在安裝了MPI的主機上編譯PyTorch)。
8.1.2 使用哪個後端?
過去,人們經常會問:“我應該使用哪個後端"?下面是答案:
- 經驗法則
- 使用 NCCL 後端進行分散式GPU訓練
- 使用 Gloo 後端進行分散式CPU訓練。
- 如果 GPU 主機 具有 InfiniBand 互連
- 使用 NCCL,因為它是目前唯一支援 InfiniBand 和 GPUDirect 的後端。
- 如果 GPU 主機 具有乙太網互連
- 使用 NCCL,因為它目前提供了最好的分散式 GPU 訓練效能,特別是對於多程式單節點或多節點分散式訓練。如果您在使用 NCCL 時遇到任何問題,請使用 Gloo 作為後備選項。(請注意,對於 GPU訓練,Gloo 目前的執行速度比 NCCL 慢。)
- 具有 InfiniBand 互連的 CPU 主機
- 如果您的 InfiniBand 已啟用 IP over IB,請使用 Gloo,否則,請改用 MPI。我們計劃在即將釋出的版本中新增對 Gloo 的 InfiniBand 支援。
- 具有乙太網互連的 CPU 主機
- 使用 Gloo,除非您有特定原因一定需要使用 MPI。
8.1.3 Gloo 後端
到目前為止,Gloo 後端 已經得到了廣泛使用。它作為開發平臺非常方便,因為它包含在預編譯的 PyTorch 二進位制檔案中,並且適用於 Linux(自 0.2 起)和 macOS(自 1.3 起)。它支援 CPU 上的所有點對點和集合操作,以及 GPU 上的所有集合操作。但是其針對 CUDA 張量集合運算的實現不如 NCCL 後端所優化的那麼好。
您肯定已經注意到,如果您的模型使用 GPU ,我們的分散式 SGD 示例將不起作用。為了使用多個GPU,我們也做如下修改:
device = torch.device("cuda:{}".format(rank))
model = Net()
\(\rightarrow\)model = Net().to(device)
data, target = data.to(device), target.to(device)
通過上述修改,我們的模型現在可以在兩個 GPU 上進行訓練,您可以使用.watch nvidia-smi
來監控使用情況。
8.1.4 MPI後端
訊息傳遞介面 (MPI) 是來自高效能運算領域的標準化工具。它允許進行點對點和集體通訊,並且是 torch.distributed
的主要靈感來源。目前存在多種 MPI 實現(例如 Open-MPI、 MVAPICH2、Intel MPI),每一種都針對不同目的進行了優化。使用 MPI 後端的優勢在於 MPI 在大型計算機叢集上的廣泛可用性和高度優化。最近的一些 實現還能夠利用 CUDA IPC 和 GPU Direct 技術,這樣可以避免通過 CPU 進行記憶體複製。
不幸的是,PyTorch 的二進位制檔案不能包含 MPI 實現,我們必須手動重新編譯它。幸運的是,這個過程相當簡單,因為在編譯時,PyTorch 會自行 尋找可用的 MPI 實現。以下步驟通過從原始碼安裝 PyTorch來安裝 MPI 後端。
- 建立並啟用您的 Anaconda 環境,依據 the guide 安裝所有繼先決需求,但 不執行
python setup.py install
。 - 選擇並安裝您最喜歡的 MPI 實現。請注意,啟用 CUDA-aware MPI 可能需要一些額外的步驟。在我們的例子中,我們將使用沒有GPU 支援的Open-MPI :
conda install -c conda-forge openmpi
。 - 現在,轉到您克隆的 PyTorch 儲存庫並執行 .
python setup.py install
。
為了測試我們新安裝的後端,需要進行一些修改。
- 把
if __name__ == '__main__':
替換為init_process(0, 0, run, backend='mpi')
- 執行
mpirun -n 4 python myscript.py
這些更改的原因是 MPI 需要在生成程式之前建立自己的環境。MPI 還將產生自己的程式並執行初始化方法中描述的握手操作,從而使init_process_group
的rank
和size
引數變得多餘。這實際上非常強大,因為您可以傳遞額外的引數來mpirun
為每個程式定製計算資源(例如每個程式的核心數、將機器手動分配到特定rank等等)。這樣做,您應該獲得與其他通訊後端相同的熟悉輸出。
8.1.5 NCCL後端
該NCCL後端提供了一個優化的,針對對CUDA張量實現的集合操作。如果您僅將 CUDA 張量用於集合操作,請考慮使用此後端以獲得最佳效能。NCCL 後端包含在具有 CUDA 支援的預構建二進位制檔案中。
NCCL 的全稱為 Nvidia 聚合通訊庫(NVIDIA Collective Communications Library),是一個可以實現多個 GPU、多個結點間聚合通訊的庫,在 PCIe、Nvlink、InfiniBand 上可以實現較高的通訊速度。
NCCL 高度優化和相容了 MPI,並且可以感知 GPU 的拓撲,促進多 GPU 多節點的加速,最大化 GPU 內的頻寬利用率,所以深度學習框架的研究員可以利用 NCCL 的這個優勢,在多個結點內或者跨界點間可以充分利用所有可利用的 GPU。
NCCL 對 CPU 和 GPU 均有較好支援,且 torch.distributed 對其也提供了原生支援。
對於每臺主機均使用多程式的情況,使用 NCCL 可以獲得最大化的效能。每個程式內,不許對其使用的 GPUs 具有獨佔權。若程式之間共享 GPUs 資源,則可能導致 deadlocks。
8.2 初始化方法
為了完成本教程,讓我們談談我們呼叫的第一個函式 dist.init_process_group(backend, init_method)
。我們將介紹負責每個程式之間初始協調步驟的不同初始化方法。這些方法允許您定義如何完成這種協調。根據您的硬體設定,這些方法之一自然應該比其他方法更合適。除了以下部分,您還應該檢視官方文件。
環境變數
在本教程中,我們一直在使用環境變數初始化方法 。此方法將從環境變數中讀取配置,允許完全自定義獲取資訊的方式。通過在所有機器上設定以下四個環境變數,所有程式都可以正常連線到master(就是 rank 0 程式),獲取其他程式的資訊,並最終與它們握手。
MASTER_PORT
:承載等級 0 程式的機器上的一個空閒埠。MASTER_ADDR
:承載等級 0 程式的機器上的 IP 地址。WORLD_SIZE
: 程式總數,因此master知道要等待多少worker。RANK
: 每個程式的rank,所以他們會知道自己是否是master。
共享檔案系統
共享檔案系統要求所有程式都可以訪問共享檔案系統,並將通過共享檔案協調它們。這意味著每個程式都將開啟檔案,寫入其資訊,並等待每個人都這樣做。之後,所有所需的資訊都將可供所有流程使用。為了避免競爭條件,檔案系統必須通過fcntl支援鎖定 。
dist.init_process_group(
init_method='file:///mnt/nfs/sharedfile',
rank=args.rank,
world_size=4)
TCP
TCP 初始化方式是通過提供rank 0程式的IP和埠來實現的,在這裡,所有worker都可以連線到等級為 0 的程式並交換有關如何相互聯絡的資訊。
dist.init_process_group(
init_method='tcp://10.1.1.20:23456',
rank=args.rank,
world_size=4)
0xFF 參考
https://pytorch.org/docs/stable/distributed.html
https://pytorch.org/tutorials/intermediate/rpc_param_server_tutorial.html
https://m.w3cschool.cn/pytorch/pytorch-me1q3bxf.html
https://pytorch.org/tutorials/beginner/dist_overview.html
https://pytorch.org/tutorials/intermediate/model_parallel_tutorial.html
https://pytorch.org/tutorials/intermediate/ddp_tutorial.html
https://pytorch.org/tutorials/intermediate/dist_tuto.html
https://pytorch.org/tutorials/intermediate/rpc_tutorial.html
https://pytorch.org/tutorials/intermediate/dist_pipeline_parallel_tutorial.html
https://pytorch.org/tutorials/intermediate/rpc_async_execution.html
https://pytorch.org/tutorials/advanced/rpc_ddp_tutorial.html
https://pytorch.org/tutorials/intermediate/pipeline_tutorial.html
https://pytorch.org/tutorials/advanced/ddp_pipeline.html
https://pytorch.org/docs/master/rpc/distributed_autograd.html#distributed-autograd-design