PyTorch 分布式訓練DDP 單機多卡快速上手

PyTorch 分布式訓練DDP 單機多卡快速上手

本文旨在幫助新人快速上手最有效的 PyTorch 單機多卡訓練,對于 PyTorch 分布式訓練的理論介紹、多方案對比,本文不做詳細介紹,有興趣的讀者可參考:

[分布式訓練] 單機多卡的正確打開方式:理論基礎

當代研究生應當掌握的并行訓練方法(單機多卡)

DP與DDP

我們知道 PyTorch 本身對于單機多卡提供了兩種實現方式

  • DataParallel(DP):Parameter Server模式,一張卡位reducer,實現也超級簡單,一行代碼。
  • DistributedDataParallel(DDP):All-Reduce模式,本意是用來分布式訓練,但是也可用于單機多卡。

DataParallel是基于Parameter server的算法,實現比較簡單,只需在原單機單卡代碼的基礎上增加一行:

model = nn.DataParallel(model, device_ids=config.gpu_id)

但是其負載不均衡的問題比較嚴重,有時在模型較大的時候(比如bert-large),reducer的那張卡會多出3-4g的顯存占用。

并且速度也比較慢:

在這里插入圖片描述

(圖像來自:當代研究生應當掌握的并行訓練方法(單機多卡)[][])

DistributedDataParallel

官方建議用新的DDP,采用all-reduce算法,本來設計主要是為了多機多卡使用,但是單機上也能用。

首先明確幾個概念:

  • rank

    多機多卡:代表某一臺機器

    單機多卡:代表某一塊GPU

  • world_size

    多機多卡:代表有幾臺機器

    單機多卡:代表有幾塊GPU

  • local_rank

    多機多卡:代表某一塊GPU的編號

    單機多卡:代表某一塊GPU的編號

單機單卡訓練代碼

我們先給出一個單機單卡訓練代碼的 demo,簡單地跑一下數據流。麻雀雖小,五臟俱全。這個 demo 包含了我們平時深度學習訓練過程中的完整步驟。包括模型、數據集的定義與實例化,損失函數,優化器的定義,梯度清零、梯度反傳,優化器迭代更新以及訓練日志的打印。

接下來我們將會使用 PyTorch 提供的 DistributedDataParallel 把這個單機單卡的訓練過程改裝為單機多卡并行訓練。

import torch
import torch.nn as nn
from torch.optim import SGD
from torch.autograd import Variable
from torch.utils.data import Dataset, DataLoader
import os
import argparseparser = argparse.ArgumentParser()
parser.add_argument('--gpu_id', type=str, default='0,2')
parser.add_argument('--batchSize', type=int, default=32)
parser.add_argument('--epochs', type=int, default=5)
parser.add_argument('--dataset-size', type=int, default=128)
parser.add_argument('--num-classes', type=int, default=10)
config = parser.parse_args()os.environ['CUDA_VISIBLE_DEVICES'] = config.gpu_id# 定義一個隨機數據集,隨機生成樣本
class RandomDataset(Dataset):def __init__(self, dataset_size, image_size=32):images = torch.randn(dataset_size, 3, image_size, image_size)labels = torch.zeros(dataset_size, dtype=int)self.data = list(zip(images, labels))def __getitem__(self, index):return self.data[index]def __len__(self):return len(self.data)# 定義模型,簡單的一層卷積加一層全連接softmax
class Model(nn.Module):def __init__(self, num_classes):super(Model, self).__init__()self.conv2d = nn.Conv2d(3, 16, 3)self.fc = nn.Linear(30*30*16, num_classes)self.softmax = nn.Softmax(dim=1)def forward(self, x):batch_size = x.shape[0]x = self.conv2d(x)x = x.reshape(batch_size, -1)x = self.fc(x)out = self.softmax(x)return out# 實例化模型、數據集、加載器和優化器
model = Model(config.num_classes)
dataset = RandomDataset(config.dataset_size)
loader = DataLoader(dataset, batch_size=config.batchSize, shuffle=True)
loss_func = nn.CrossEntropyLoss()if torch.cuda.is_available():model.cuda()
optimizer = SGD(model.parameters(), lr=0.1, momentum=0.9)# 若使用DP,僅需一行
# if torch.cuda.device_count > 1: model = nn.DataParallel(model)
# 我們不用DP,而將用DDP# 開始訓練
for epoch in range(config.epochs):for step, (images, labels) in enumerate(loader):if torch.cuda.is_available(): images = images.cuda()labels = labels.cuda()preds = model(images)loss = loss_func(preds, labels)optimizer.zero_grad()loss.backward()optimizer.step()print(f'Step: {step}, Loss: {loss.item()}')print(f'Epoch {epoch} Finished !')

訓練日志輸出為:

Step: 0, Loss: 1.4611507654190063
Step: 1, Loss: 1.4611507654190063
...
Step: 7, Loss: 1.4611507654190063
Epoch 0 Finished !
...

修改代碼

使用 PyTorch 提供的 DistributedDataParallel 將單機單卡的訓練代碼為單機多卡的并行訓練代碼需要以下幾個步驟:

  1. 初始化

    torch.distributed.init_process_group(backend="nccl")
    local_rank = torch.distributed.get_rank()
    torch.cuda.set_device(local_rank)
    device = torch.device("cuda", local_rank)
    
  2. 設置模型并行

    model=torch.nn.parallel.DistributedDataParallel(model)
    
  3. 設置數據并行

    from torch.utils.data.distributed import DistributedSampler
    sampler = DistributedSampler(dataset) # 這個sampler會自動分配數據到各個gpu上
    loader = DataLoader(dataset, batch_size=batch_size, sampler=sampler)
    

在上面單機單卡的代碼合適位置中增添這么幾行即可。

DDP多卡訓練的啟動

另外需要注意的是,單機多卡的啟動與平時的 python ddp_demo.py 也不一樣,需要:

python -m torch.distributed.launch --nproc_per_node 2  ddp_demo.py --batchSize 64 --epochs 10 --gpu_id 1,2
# 或 torchrun  --nproc_per_node=2 ddp_demo.py --batchSize 64 --epochs 10

其中 --nproc_per_node 是我們要使用的顯卡數量。argparse 的參數加在后面即可。

注意 --local_rank 不是由我們手動指定。

DDP多卡訓練的日志輸出

還有,由于 DDP 多卡訓練是多進程進行的,每個進程都會打印一遍日志輸出。即會出現類似這種輸出:

Step: 0, Loss: 1.4611507654190063
Step: 0, Loss: 1.4611507654190063
Step: 1, Loss: 1.4611507654190063
Step: 1, Loss: 1.4611507654190063
...
Step: 7, Loss: 1.4611507654190063
Step: 7, Loss: 1.4611507654190063
Epoch 0 Finished !
Epoch 0 Finished !
...

因此在訓練驗證過程中的日志記錄輸出也要注意:

在啟動器啟動python腳本后,在執行過程中,啟動器會將當前進程的 index 通過參數傳遞給 python,我們可以這樣獲得當前進程的 index:即通過命令行參數 --local_rank 來告訴我們當前進程使用的是哪個GPU,用于我們在每個進程中指定不同的device(也有其他的方式來獲取當前進程)。進程可以簡單理解為運行一個代碼,分布式訓練采用多GPU多進程的方式,即每個進程都要獨立運行一份訓練代碼,由此,為每個GPU分配一個進程進行分布式訓練。通常不需要在每個進程中都有日志或其他信息(模型權重等)的輸出,即可以通過--local_rank來指定打印日志或其他信息(模型權重等)的進程。

查看當前設備

我們可以在訓練循環里加上一行,來查看當前進程時在哪個GPU上進行計算的:

print(f"data: {images.device}, model: {next(model.parameters()).device}")

注意這里我們的 model.parameters() 實際上是 Python 中的一個生成器,因此,需要用 next() 方法來取得其中一個,查看其所在設備。

部分輸出:

Epoch 0 Finished !
data: cuda:0, model: cuda:0
data: cuda:1, model: cuda:1
...
data: cuda:0, model: cuda:0
data: cuda:1, model: cuda:1
data: cuda:0, model: cuda:0
data: cuda:1, model: cuda:1
Epoch 1 Finished !
...

可以看到,在我們的DDP多進程單機多卡訓練中,在兩個設備上都會有訓練數據和模型的分布,并且也都會打印出來。

附錄:完整DDP訓練代碼

# ddp_demo.py
import torch
import torch.nn as nn
from torch.optim import SGD
from torch.autograd import Variable
from torch.utils.data import Dataset, DataLoader
from torch.utils.data.distributed import DistributedSampler
import os
import argparse# 定義一個隨機數據集
class RandomDataset(Dataset):def __init__(self, dataset_size, image_size=32):images = torch.randn(dataset_size, 3, image_size, image_size)labels = torch.zeros(dataset_size, dtype=int)self.data = list(zip(images, labels))def __getitem__(self, index):return self.data[index]def __len__(self):return len(self.data)# 定義模型
class Model(nn.Module):def __init__(self, num_classes):super(Model, self).__init__()self.conv2d = nn.Conv2d(3, 16, 3)self.fc = nn.Linear(30*30*16, num_classes)self.softmax = nn.Softmax(dim=1)def forward(self, x):batch_size = x.shape[0]x = self.conv2d(x)x = x.reshape(batch_size, -1)x = self.fc(x)out = self.softmax(x)return outparser = argparse.ArgumentParser()
parser.add_argument('--gpu_id', type=str, default='0,2')
parser.add_argument('--batchSize', type=int, default=64)
parser.add_argument('--epochs', type=int, default=5)
parser.add_argument('--dataset-size', type=int, default=1024)
parser.add_argument('--num-classes', type=int, default=10)
config = parser.parse_args()os.environ['CUDA_VISIBLE_DEVICES'] = config.gpu_id
torch.distributed.init_process_group(backend="nccl")local_rank = torch.distributed.get_rank()
torch.cuda.set_device(local_rank)
device = torch.device("cuda", local_rank)# 實例化模型、數據集和加載器loader
model = Model(config.num_classes)dataset = RandomDataset(config.dataset_size)
sampler = DistributedSampler(dataset) # 這個sampler會自動分配數據到各個gpu上
loader = DataLoader(dataset, batch_size=config.batchSize, sampler=sampler)# loader = DataLoader(dataset, batch_size=config.batchSize, shuffle=True)
loss_func = nn.CrossEntropyLoss()if torch.cuda.is_available():model.cuda()
model = torch.nn.parallel.DistributedDataParallel(model)
optimizer = SGD(model.parameters(), lr=0.1, momentum=0.9)# 開始訓練
for epoch in range(config.epochs):for step, (images, labels) in enumerate(loader):if torch.cuda.is_available(): images = images.cuda()labels = labels.cuda()preds = model(images)# print(f"data: {images.device}, model: {next(model.parameters()).device}")loss = loss_func(preds, labels)optimizer.zero_grad()loss.backward()optimizer.step()print(f'Step: {step}, Loss: {loss.item()}')print(f'Epoch {epoch} Finished !')

啟動訓練(以兩張卡為例):

torchrun  --nproc_per_node=2 ddp_demo.py --batchSize 64 --epochs 10

Ref:

https://blog.csdn.net/weixin_44966641/article/details/121015241

https://zhuanlan.zhihu.com/p/98535650

https://zhuanlan.zhihu.com/p/384893917

本文來自互聯網用戶投稿,該文觀點僅代表作者本人,不代表本站立場。本站僅提供信息存儲空間服務,不擁有所有權,不承擔相關法律責任。
如若轉載,請注明出處:http://www.pswp.cn/news/532629.shtml
繁體地址,請注明出處:http://hk.pswp.cn/news/532629.shtml
英文地址,請注明出處:http://en.pswp.cn/news/532629.shtml

如若內容造成侵權/違法違規/事實不符,請聯系多彩編程網進行投訴反饋email:809451989@qq.com,一經查實,立即刪除!

相關文章

Linux free 命令詳解

Linux free 命令詳解 free 命令用來查看系統中已用的和可用的內存。 命令選項及輸出簡介 關于各種命令的功能和命令選項,還是推薦英語比較好的同學直接看手冊 RTFM:man free。這里簡單總結一下一些重點: 功能及輸出簡介 free 命令顯示系…

CTF web題 wp:

1.簽到題 火狐F12查看源碼,發現注釋: 一次base64解碼出flag 2.Encode 在這里插入圖片描述 和第一題界面一樣?? 輕車熟路f12: 發現編碼: 格式看上去是base64,連續兩次base64后,觀…

【深度學習】深入理解Batch Normalization批歸一化

【深度學習】深入理解Batch Normalization批歸一化 轉自:https://www.cnblogs.com/guoyaohua/p/8724433.html 這幾天面試經常被問到BN層的原理,雖然回答上來了,但還是感覺答得不是很好,今天仔細研究了一下Batch Normalization的原…

ThinkPHP V5 漏洞利用

ThinkPHP 5漏洞簡介 ThinkPHP官方2018年12月9日發布重要的安全更新,修復了一個嚴重的遠程代碼執行漏洞。該更新主要涉及一個安全更新,由于框架對控制器名沒有進行足夠的檢測會導致在沒有開啟強制路由的情況下可能的getshell漏洞,受影響的版本…

Vim 重復操作的宏錄制

Vim 重復操作的宏錄制 轉自:https://www.cnblogs.com/ini_always/archive/2011/09/21/2184446.html 在編輯某個文件的時候,可能會出現需要對某種特定的操作進行許多次的情況,以編輯下面的文件為例: ; ;This is a sample config…

Vim 進階1

Vim 進階1 所有你覺得簡單重復,可以自動化實現的操作,都是可以自動化實現的。 Vim光標移動拾遺 w:下一個單詞的開頭,e:下一個單詞的結尾,b:上一個單詞的開頭, 0:行首…

攻防世界web題ics-06(爆破id值)

打開界面:嚯!這花里胡哨 點來點去只有報表中心有回顯: 發現url中id等于1,sql注入嘗試無果, burp工具爆破id 對id的值進行爆破 burp報ERROR的話這是個bug,先點擊Hex后點decimal手動刷新就可以使用 強行總…

crontab用法與實例

crontab用法與實例 本文基于 ubuntu 18.04 在Linux系統的實際使用中,可能會經常碰到讓系統在某個特定時間執行某些任務的情況,比如定時采集服務器的狀態信息、負載狀況;定時執行某些任務/腳本來對遠端進行數據采集等。這里將介紹下crontab的配…

手工sql注入常規總結

1.發現注入點 2.報數據庫 先用單引號(也嘗試雙引號)閉合前面的語句,使注入的語句能夠執行, 數字 0 :匹配字段,還有 11 12 等等都可以使用,有些網站會有過濾處理,建議采用 1%2b12 1%2b1>1 繞…

Systemd入門教程:命令篇

Systemd入門教程:命令篇 轉自:http://www.ruanyifeng.com/blog/2016/03/systemd-tutorial-commands.html 作者: 阮一峰 日期: 2016年3月 7日 Systemd 是 Linux 系統工具,用來啟動守護進程,已成為大多數…

【CVE-2018-12613】phpmyadmin 4.8.1 遠程文件包含漏洞復現

**環境:**http://62.234.56.138:8080/server_databases.php 官網下載phpmyadmin 4.8.1 源碼:index.php文件中 函數含義: targer非空targer是否位字符串不能以index為開頭,即過濾了index值不能出現在blacklist內,即…

Systemd 入門教程:實戰篇

Systemd 入門教程:實戰篇 轉自:https://www.ruanyifeng.com/blog/2016/03/systemd-tutorial-part-two.html 作者: 阮一峰 日期: 2016年3月 8日 上一篇文章,我介紹了 Systemd 的主要命令,今天介紹如何使…

關于ubuntu自定義service服務時找不到/usr/lib/systemd/system目錄的問題

關于ubuntu自定義service服務時找不到/usr/lib/systemd/system目錄的問題 問題 我們知道在 systemd 取代了 init 而成為廣大 Linux 系統中 PID 為1的守護進程之后,Linux 中的服務(service)主要有 systemd 命令組來實現。在大多數發行版 Lin…

攻防世界web2(逆向加密算法)

打開網頁有如下代碼&#xff1a; <?php $miwen"a1zLbgQsCESEIqRLwuQAyMwLyq2L5VwBxqGA3RQAyumZ0tmMvSGM2ZwB4tws";function encode($str){$_ostrrev($str);// echo $_o;for($_00;$_0<strlen($_o);$_0){$_csubstr($_o,$_0,1);$__ord($_c)1;$_cchr($__);$_$_.$…

ctags 基本使用方法

ctags 基本使用方法 簡介 ctags&#xff08;Generate tag files for source code&#xff09;是vim下方便代碼閱讀的工具。盡管ctags也可以支持其它編輯器&#xff0c;但是它正式支持的只有 Vim。并且 Vim 中已經默認安裝了 ctags&#xff0c;它可以幫助程序員很容易地瀏覽源…

vimrc配置文件

vimrc配置文件 轉自&#xff1a;https://www.ruanyifeng.com/blog/2018/09/vimrc.html Vim 是最重要的編輯器之一&#xff0c;主要有下面幾個優點。 可以不使用鼠標&#xff0c;完全用鍵盤操作。系統資源占用小&#xff0c;打開大文件毫無壓力。鍵盤命令變成肌肉記憶以后&am…

CTFHUB 《請求方式》 http請求,curl命令總結

打開網頁&#xff1a; 思路一&#xff1a; 根據題目&#xff0c;應該是向網頁發送get方式請求&#xff0c;但并沒有具體規定要發送什么&#xff0c;嘗試get發送參數后&#xff0c;都沒有返回網頁&#xff0c;emmm’…好像不是我想的那種套路 思路二&#xff1a; 網上找到思路…

Vim進階2 map映射

Vim進階2 map映射 簡介 map是一個 vim 中的一些列映射命令&#xff0c;將常用的很長的命令映射到一個新的功能鍵上。map是Vim強大的一個重要原因&#xff0c;可以自定義各種快捷鍵&#xff0c;用起來自然得心應手。 map系列命令格式 格式 以 map 命令為例&#xff0c;它的…

CTFHUB 《基礎認證》:burp使用,basic請求了解

題目簡介&#xff1a;在HTTP中&#xff0c;基本認證&#xff08;英語&#xff1a;Basic access authentication&#xff09;是允許http用戶代理&#xff08;如&#xff1a;網頁瀏覽器&#xff09;在請求時&#xff0c;提供 用戶名 和 密碼 的一種方式。詳情請查看 https://zh.w…

信息量、熵、交叉熵、KL散度、JS散度雜談及代碼實現

信息量、熵、交叉熵、KL散度、JS散度雜談及代碼實現 信息量 任何事件都會承載著一定的信息量&#xff0c;包括已經發生的事件和未發生的事件&#xff0c;只是它們承載的信息量會有所不同。如昨天下雨這個已知事件&#xff0c;因為已經發生&#xff0c;既定事實&#xff0c;那…