1. 程式人生 > 程式設計 >Pytorch 高效使用GPU的操作

Pytorch 高效使用GPU的操作

前言

深度學習涉及很多向量或多矩陣運算,如矩陣相乘、矩陣相加、矩陣-向量乘法等。深層模型的演算法,如BP,Auto-Encoder,CNN等,都可以寫成矩陣運算的形式,無須寫成迴圈運算。然而,在單核CPU上執行時,矩陣運算會被展開成迴圈的形式,本質上還是序列執行。GPU(Graphic Process Units,圖形處理器)的眾核體繫結構包含幾千個流處理器,可將矩陣運算並行化執行,大幅縮短計算時間。隨著NVIDIA、AMD等公司不斷推進其GPU的大規模並行架構,面向通用計算的GPU已成為加速可並行應用程式的重要手段。得益於GPU眾核(many-core)體系結構,程式在GPU系統上的執行速度相較於單核CPU往往提升幾十倍乃至上千倍。

目前,GPU已經發展到了較為成熟的階段。利用GPU來訓練深度神經網路,可以充分發揮其數以千計計算核心的能力,在使用海量訓練資料的場景下,所耗費的時間大幅縮短,佔用的伺服器也更少。如果對適當的深度神經網路進行合理優化,一塊GPU卡相當於數十甚至上百臺CPU伺服器的計算能力,因此GPU已經成為業界在深度學習模型訓練方面的首選解決方案。

如何使用GPU?現在很多深度學習工具都支援GPU運算,使用時只要簡單配置即可。Pytorch支援GPU,可以通過to(device)函式來將資料從記憶體中轉移到GPU視訊記憶體,如果有多個GPU還可以定位到哪個或哪些GPU。Pytorch一般把GPU作用於張量(Tensor)或模型(包括torch.nn下面的一些網路模型以及自己建立的模型)等資料結構上。

單GPU加速

使用GPU之前,需要確保GPU是可以使用,可通過torch.cuda.is_available()的返回值來進行判斷。返回True則具有能夠使用的GPU。

通過torch.cuda.device_count()可以獲得能夠使用的GPU數量。

如何檢視平臺GPU的配置資訊?在命令列輸入命令nvidia-smi即可 (適合於Linux或Windows環境)。圖5-13是GPU配置資訊樣例,從中可以看出共有2個GPU。

Pytorch 高效使用GPU的操作

圖 GPU配置資訊

把資料從記憶體轉移到GPU,一般針對張量(我們需要的資料)和模型。 對張量(型別為FloatTensor或者是LongTensor等),一律直接使用方法.to(device)或.cuda()即可。

device = torch.device("cuda:0" if torch.cuda.is_available() else "cpu")
#或device = torch.device("cuda:0")
device1 = torch.device("cuda:1") 
for batch_idx,(img,label) in enumerate(train_loader):
  img=img.to(device)
  label=label.to(device)

對於模型來說,也是同樣的方式,使用.to(device)或.cuda來將網路放到GPU視訊記憶體。

#例項化網路
model = Net()
model.to(device)  #使用序號為0的GPU
#或model.to(device1) #使用序號為1的GPU

多GPU加速

這裡我們介紹單主機多GPUs的情況,單機多GPUs主要採用的DataParallel函式,而不是DistributedParallel,後者一般用於多主機多GPUs,當然也可用於單機多GPU。

使用多卡訓練的方式有很多,當然前提是我們的裝置中存在兩個及以上的GPU。

使用時直接用model傳入torch.nn.DataParallel函式即可,如下程式碼:

#對模型

net = torch.nn.DataParallel(model)

這時,預設所有存在的顯示卡都會被使用。

如果你的電腦有很多顯示卡,但只想利用其中一部分,如只使用編號為0、1、3、4的四個GPU,那麼可以採用以下方式:

#假設有4個GPU,其id設定如下
device_ids =[0,1,2,3]
#對資料
input_data=input_data.to(device=device_ids[0])
#對於模型
net = torch.nn.DataParallel(model)
net.to(device)

或者

os.environ["CUDA_VISIBLE_DEVICES"] = ','.join(map(str,[0,3]))

net = torch.nn.DataParallel(model)

其中CUDA_VISIBLE_DEVICES 表示當前可以被Pytorch程式檢測到的GPU。

下面為單機多GPU的實現程式碼。

背景說明

這裡使用波士頓房價資料為例,共506個樣本,13個特徵。資料劃分成訓練集和測試集,然後用data.DataLoader轉換為可批載入的方式。採用nn.DataParallel併發機制,環境有2個GPU。當然,資料量很小,按理不宜用nn.DataParallel,這裡只是為了說明使用方法。

載入資料

boston = load_boston()
X,y  = (boston.data,boston.target)
 
X_train,X_test,y_train,y_test = train_test_split(X,y,test_size=0.2,random_state=0)
#組合訓練資料及標籤
myset = list(zip(X_train,y_train))

把資料轉換為批處理載入方式批次大小為128,打亂資料

from torch.utils import data
device = torch.device("cuda:0" if torch.cuda.is_available() else "cpu")
dtype = torch.FloatTensor
train_loader = data.DataLoader(myset,batch_size=128,shuffle=True)

定義網路

class Net1(nn.Module):
  """
  使用sequential構建網路,Sequential()函式的功能是將網路的層組合到一起
  """
  def __init__(self,in_dim,n_hidden_1,n_hidden_2,out_dim):
    super(Net1,self).__init__()
    self.layer1 = torch.nn.Sequential(nn.Linear(in_dim,n_hidden_1))
    self.layer2 = torch.nn.Sequential(nn.Linear(n_hidden_1,n_hidden_2))
    self.layer3 = torch.nn.Sequential(nn.Linear(n_hidden_2,out_dim))
    
 
  def forward(self,x):
    x1 = F.relu(self.layer1(x))
    x1 = F.relu(self.layer2(x1))
    x2 = self.layer3(x1)
    #顯示每個GPU分配的資料大小
    print("\tIn Model: input size",x.size(),"output size",x2.size())
    return x2

把模型轉換為多GPU併發處理格式

device = torch.device("cuda:0" if torch.cuda.is_available() else "cpu")
#例項化網路
model = Net1(13,16,32,1)
if torch.cuda.device_count() > 1:
  print("Let's use",torch.cuda.device_count(),"GPUs")
  # dim = 0 [64,xxx] -> [32,...],[32,...] on 2GPUs
  model = nn.DataParallel(model)
model.to(device)

執行結果

Let's use 2 GPUs
DataParallel(
(module): Net1(
(layer1): Sequential(
(0): Linear(in_features=13,out_features=16,bias=True)
)
(layer2): Sequential(
(0): Linear(in_features=16,out_features=32,bias=True)
)
(layer3): Sequential(
(0): Linear(in_features=32,out_features=1,bias=True)
)
)
)

選擇優化器及損失函式

optimizer_orig = torch.optim.Adam(model.parameters(),lr=0.01)

loss_func = torch.nn.MSELoss()

模型訓練,並可視化損失值

from torch.utils.tensorboard import SummaryWriter
writer = SummaryWriter(log_dir='logs')
for epoch in range(100):    
  model.train()
  for data,label in train_loader:
    input = data.type(dtype).to(device)
    label = label.type(dtype).to(device)
    output = model(input)    
    loss = loss_func(output,label)
    # 反向傳播
    optimizer_orig.zero_grad()
    loss.backward()
    optimizer_orig.step()
    print("Outside: input size",input.size(),"output_size",output.size())
  writer.add_scalar('train_loss_paral',loss,epoch)

執行的部分結果

In Model: input size torch.Size([64,13]) output size torch.Size([64,1])
In Model: input size torch.Size([64,1])
Outside: input size torch.Size([128,13]) output_size torch.Size([128,1])

從執行結果可以看出,一個批次資料(batch-size=128)拆分成兩份,每份大小為64,分別放在不同的GPU上。此時用GPU監控也可發現,兩個GPU都同時在使用。

Pytorch 高效使用GPU的操作

8. 通過web檢視損失值的變化情況

Pytorch 高效使用GPU的操作

圖 併發執行訓練損失值變化情況

圖形中出現較大振幅,是由於採用批次處理,而且資料沒有做任何預處理,對資料進行規範化應該更平滑一些,大家可以嘗試一下。

單機多GPU也可使用DistributedParallel,它多用於分散式訓練,但也可以用在單機多GPU的訓練,配置比使用nn.DataParallel稍微麻煩一點,但是訓練速度和效果更好一點。具體配置為:

#初始化使用nccl後端
torch.distributed.init_process_group(backend="nccl")
#模型並行化
model=torch.nn.parallel.DistributedDataParallel(model)

單機執行時使用下面方法啟動

python -m torch.distributed.launch main.py

使用GPU注意事項

使用GPU可以提升我們訓練的速度,如果使用不當,可能影響使用效率,具體使用時要注意以下幾點:

GPU的數量儘量為偶數,奇數的GPU有可能會出現異常中斷的情況;

GPU很快,但資料量較小時,效果可能沒有單GPU好,甚至還不如CPU;

如果記憶體不夠大,使用多GPU訓練的時候可通過設定pin_memory為False,當然使用精度稍微低一點的資料型別有時也效果。

以上這篇Pytorch 高效使用GPU的操作就是小編分享給大家的全部內容了,希望能給大家一個參考,也希望大家多多支援我們。