深度學(xué)習(xí)中,當(dāng)一塊GPU不夠用時(shí),我們就需要使用多卡進(jìn)行并行訓(xùn)練。其中多卡并行可分為數(shù)據(jù)并行和模型并行。具體區(qū)別如下圖所示:
成都創(chuàng)新互聯(lián)專(zhuān)業(yè)為企業(yè)提供拜泉網(wǎng)站建設(shè)、拜泉做網(wǎng)站、拜泉網(wǎng)站設(shè)計(jì)、拜泉網(wǎng)站制作等企業(yè)網(wǎng)站建設(shè)、網(wǎng)頁(yè)設(shè)計(jì)與制作、拜泉企業(yè)網(wǎng)站模板建站服務(wù),十年拜泉做網(wǎng)站經(jīng)驗(yàn),不只是建網(wǎng)站,更提供有價(jià)值的思路和整體網(wǎng)絡(luò)服務(wù)。
由于模型并行比較少用,這里只對(duì)數(shù)據(jù)并行進(jìn)行記錄。對(duì)于pytorch,有兩種方式可以進(jìn)行數(shù)據(jù)并行:數(shù)據(jù)并行(DataParallel, DP)和分布式數(shù)據(jù)并行(DistributedDataParallel, DDP)。
在多卡訓(xùn)練的實(shí)現(xiàn)上,DP與DDP的思路是相似的:
1、每張卡都復(fù)制一個(gè)有相同參數(shù)的模型副本。
2、每次迭代,每張卡分別輸入不同批次數(shù)據(jù),分別計(jì)算梯度。
3、DP與DDP的主要不同在于接下來(lái)的多卡通信:
DP的多卡交互實(shí)現(xiàn)在一個(gè)進(jìn)程之中,它將一張卡視為主卡,維護(hù)單獨(dú)模型優(yōu)化器。所有卡計(jì)算完梯度后,主卡匯聚其它卡的梯度進(jìn)行平均并用優(yōu)化器更新模型參數(shù),再將模型參數(shù)更新至其它卡上。
DDP則分別為每張卡創(chuàng)建一個(gè)進(jìn)程,每個(gè)進(jìn)程相應(yīng)的卡上都獨(dú)立維護(hù)模型和優(yōu)化器。在每次每張卡計(jì)算完梯度之后,進(jìn)程之間以NCLL(NVIDIA GPU通信)為通信后端,使各卡獲取其它卡的梯度。各卡對(duì)獲取的梯度進(jìn)行平均,然后執(zhí)行后續(xù)的參數(shù)更新。由于每張卡上的模型與優(yōu)化器參數(shù)在初始化時(shí)就保持一致,而每次迭代的平均梯度也保持一致,那么即使沒(méi)有進(jìn)行參數(shù)復(fù)制,所有卡的模型參數(shù)也是保持一致的。
Pytorch官方推薦我們使用DDP。DP經(jīng)過(guò)我的實(shí)驗(yàn),兩塊GPU甚至比一塊還慢。當(dāng)然不同模型可能有不同的結(jié)果。下面分別對(duì)DP和DDP進(jìn)行記錄。
Pytorch的DP實(shí)現(xiàn)多GPU訓(xùn)練十分簡(jiǎn)單,只需在單GPU的基礎(chǔ)上加一行代碼即可。以下是一個(gè)DEMO的代碼。
import torch from torch import nn from torch.optim import Adam from torch.nn.parallel import DataParallel class DEMO_model(nn.Module): def __init__(self, in_size, out_size): super().__init__() self.fc= nn.Linear(in_size, out_size) def forward(self, inp): outp= self.fc(inp) print(inp.shape, outp.device) return outp model= DEMO_model(10, 5).to('cuda') model= DataParallel(model, device_ids=[0, 1]) # 額外加這一行 adam = Adam(model.parameters()) # 進(jìn)行訓(xùn)練 for i in range(1): x= torch.rand([128, 10]) # 獲取訓(xùn)練數(shù)據(jù),無(wú)需指定設(shè)備 y = model(x) # 自動(dòng)均勻劃分?jǐn)?shù)據(jù)批量并分配至各GPU,輸出結(jié)果y會(huì)聚集到GPU0中 loss = torch.norm(y) loss.backward() adam.step()