說明
在前面講模型加載和保存的時候,在多GPU情況下,實際上是挖了坑的,比如在多GPU加載時,GPU的利用率是不均衡的,而當時沒詳細探討這個問題,今天來詳細地討論一下。
問題
在訓練的時候,如果GPU資源有限,而數據量和模型大小較大,那么在單GPU上運行就會極其慢的訓練速度,此時就要使用多GPU進行模型訓練了,在pytorch上實現多GPU訓練實際上十分簡單:
只需要將模型使用nn.DataParallel
進行裝飾即可。
model = nn.DataParallel(model,device_ids=range(torch.cuda.device_count()))
但是問題在於這樣直接處理后的模型的負載可能是不均衡的,因為在不同的GPU上進行運算,而最后的loss計算過程是要合並到主GPU上,這樣主GPU的的占用率將比較高,而其余GPU的利用率則沒有那么高。
解決思路
- 一種比較簡單的解決方法是將模型計算loss的過程封裝在model里,這樣每個GPU forward的時候就會計算到對應的loss,當然缺陷在於這樣每次得到的loss都是一個數組,需要另外mean或者sum處理一下。
class FullModel(nn.Module):
def __init__(self, model, loss):
super(FullModel, self).__init__()
self.model = model
self.loss = loss
def forward(self, targets, *inputs):
outputs = self.model(*inputs)
loss = self.loss(outputs, targets)
return torch.unsqueeze(loss,0),outputs
在上述的代碼中,構建了另外一個包含model和loss的殼,在殼里計算loss的值,需要注意的是,在進行DataParallel時,也需要對這個並行,而到了收集loss的時候,則使用loss的和:
loss,_ = model(gt,input)
loss = loss.sum()
optimizer.zero_grad()
loss.backward()
optimizer.step()
已經有人造了這個輪子,並開源了出來,可以參考:https://github.com/zhanghang1989/PyTorch-Encoding 代碼庫,整個寫法依然沒有太大的變化:
from utils.encoding import DataParallelModel, DataParallelCriterion
model = DataParallelModel(model)
criterion = DataParallelCriterion(criterion)
- 通過distributedDataparallel來實現
實際上官方考慮過負載不均衡的問題,在文檔中也推薦使用distributedDataparallel(ddp)進行訓練,盡管ddp是用來解決不同機器的分布式訓練問題的。
This is the highly recommended way to useDistributedDataParallel, with multiple processes, each of which operates on a single GPU. This is currently the fastest approach to do data parallel training using PyTorch and applies to both single-node(multi-GPU) and multi-node data parallel training. It is proven to be significantly faster thantorch.nn.DataParallelfor single-node multi-GPU data parallel training.
ddp使用起來比DataParallel更快,數據也更均衡,但是缺點是配置起來相對要麻煩一些。
# 初始化使用的后端
torch.distributed.init_process_group(backend="nccl")
# 對數據進行划分
train_sampler = torch.utils.data.distributed.DistributedSampler(train_dataset)
test_sampler = torch.utils.data.distributed.DistributedSampler(test_dataset)
train_loader = torch.utils.data.DataLoader(train_dataset, batch_size=batch_size, shuffle=False, num_workers=n_worker, pin_memory=True, sampler=train_sampler)
test_loader = torch.utils.data.DataLoader(test_dataset, batch_size=batch_size, shuffle=False, num_workers=n_worker, pin_memory=True, sampler=test_sampler) # sampler和shuffle不能同時使用
model=torch.nn.parallel.DistributedDataParallel(model)
注意:需要注意的是,盡量設定pin_memory參數為true,該參數是鎖存操作,使用會加快數據讀取速度,但是此時要限定內存的大小是要使用顯存的兩倍
以上就配置好了,經過測試,使用ddp的訓練時間比DataParallel快一倍。
在運行的時候,使用以下命令進行分布式訓練: python -m torch.distributed.launch --nproc_per_node=NUM_GPUS_YOU_HAVE yourscript.py