GPU數據傳輸概覽

在機器學習訓練過程中,大家往往會發現IO成爲制約訓練速度提升的瓶頸。

提升訓練速度,數據傳輸是繞不開的話題。那麼GPU機器中,數據傳輸是如何做的呢?

同機的CPU和GPU之間數據如何傳輸?

同機的多卡之間數據如何傳輸?

多機的卡之間數據如何傳輸?

1、CPU和GPU之間

1)CPU->GPU

圖1 鎖頁內存

從CPU向GPU傳輸數據,最爲人熟知的就是cudaMemcpy了。

默認情況下,數據是從系統的分頁內存先到鎖頁內存,然後再到GPU顯存。因此如果顯式指定使用鎖頁內存,是可以加快數據傳輸速度的。

(鎖頁內存,在cuda編程裏使用CudaHostMalloc分配。實質上和linux的mlock系統調用一樣,就是給內存頁打上標記,不讓操作系統將其從物理內存交換到硬盤)

至於爲什麼cuda要這樣設計,個人理解是爲了實現的方便。因爲操作系統已經處理了硬盤和物理內存間的頁交換等情況,顯卡驅動只需要實現物理內存到GPU顯存這一種數據傳輸即可,不需要把操作系統內存管理的事情再做一遍。

圖2 G9機型(P40卡)上系統內存向顯存拷貝速度

2) GPU->CPU

GPU向CPU拷貝數據時,鎖頁內存同樣比分頁內存快

圖3 G9機型(P40卡)上顯存向系統內存拷貝速度

值得一提的是,適當使用pinned memory顯然可以加快IO速度。但是並不是越多越好,因爲鎖頁內存是完全獨佔住了物理內存,操作系統無法調度,可能會影響系統整體性能。

3)同一張GPU卡內部

同一張卡內兩塊顯存對拷,實測P40上高達~285GB/s。也比較接近於GPU卡本身的訪存速度

圖4 摘自P40 whitepaper

4)數據拷貝的overhead

在上面的測試數據中,可以看到傳輸數據量從1M->32M增長的過程中,測得的傳輸帶寬是有逐漸增加的。

這是因爲每次調用cuda api進行數據傳輸都有overhead,在數據量小的時候這個overhead在數據傳輸時間中的佔比就顯得很高。這也提示我們儘量合併小數據的傳輸

2、同機的GPU之間

一般可以通過cudaMemcpyPeer/cudaMemcpyPeerAsync函數進行顯存拷貝

1)cudaMemcpyPeer withoutP2P

/********代碼示例*******/

cudaSetDevice(1);

cudaMalloc((int**)&dest, bytes);

cudaSetDevice(2);

cudaMalloc((int**)&dsrc, bytes);

cudaMemcpyPeer(dest, 1, dsrc, 2, bytes);

圖5 GPU2向GPU1顯存拷貝

通過nvprof+nvpp可以看到:禁用GPU P2P時,數據是先從GPU2拷貝到系統內存(DtoH),然後再從系統內存拷貝到GPU1(HtoD)

當然,這裏是在一個進程內做GPU之間的數據拷貝。如果是2個進程分別運行在GPU1和GPU2上,那在CPU上這2個進程間可以通過共享內存或者socket通信來完成數據的拷貝。

2)cudaMemcpyPeer withP2P

/********代碼示例*******/

cudaSetDevice(1);

cudaMalloc((int**)&dest, bytes);

cudaSetDevice(2);

cudaMalloc((int**)&dsrc, bytes);

cudaDeviceEnablePeerAccess(1,0);

cudaDeviceEnablePeerAccess(2,0);

cudaMemcpyPeer(dest, 1, dsrc, 2, bytes);

圖6 GPU2向GPU1通過P2P進行顯存拷貝

啓用GPU P2P時,數據直接從GPU2拷貝到了GPU1,不再經過系統內存。

3)通過變量賦值方式傳輸數據

深度學習中,卡之間傳遞的數據其實很多都是參數數值,因此也可以直接用一個GPU內的變量給另一個GPU上的變量賦值來進行數據傳輸

/********代碼示例*******/

cudaOccupancyMaxPotentialBlockSize(&numBlocks, &blockSize, copyp2p_float);

copyp2p_float<<<numBlocks, blockSize, 0, streamToRun>>>(

(float *)dest, (float *)src, num_elems);

__global__ void copyp2p_float(float *__restrict__ dest, float const *__restrict__ src,

size_t num_elems) {

size_t globalId = blockIdx.x * blockDim.x + threadIdx.x;

size_t gridSize = blockDim.x * gridDim.x;

#pragma unroll(5)

for (size_t i = globalId; i < num_elems; i += gridSize) {

dest[i] = src[i];

}

}

圖7 GPU2向GPU1進行變量賦值

4)GPU->GPU速度測試

圖8 G9機型(P40卡)上GPU to GPU顯存拷貝

 

圖9 G9機型(P40卡)上GPU to GPU變量賦值

 

5)GPU機器架構

使用P40卡的公司某現役型號服務器拓撲結構如下

顯而易見,同一個PCIe Switch下的卡之間的數據傳輸 和 跨PCIe Switch的卡之間數據傳輸存在差異,

具體這兩種情況下數據的傳輸路徑有何不同,如何影響到傳輸速度,機智團隊會在後續文章中結合GPU架構演進進行分析。

圖10 某機型架構

3、多機的GPU之間

圖11 兩機GPU通信示意

 

1) NCCL性能參數

跨節點的GPU之間,數據傳輸當然要通過網絡。除了傳統的socket通信,還有GDR(GPU Direct RDMA)。關於GDR的原理,本文不贅述,可參考相關資料。

Nvidia提供了NCCL庫來方便基於GPU的集合通信,這也是目前分佈式GPU訓練必備的工具之一。目前最新的版本是NCCL_2.4.7,相比於之前版本,2.4提供了對通信方式更細粒度的控制。對性能有影響的參數主要包括:

  • NCCL_IB_DISABLE爲1時禁止使用ib設備
  • NCCL_P2P_LEVEL 0~5 控制在何種情況下GPU卡之間可以使用P2P
  • NCCL_P2P_DISABLE=1 相當於設置NCCL_P2P_LEVEL=0,並且會被NCCL_P2P_LEVEL的值所覆蓋
  • NCCL_NET_GDR_LEVEL 0~5 控制在何種情況下,跨節點的GPU卡之間可以使用GDR
  • NCCL_NET_GDR_READ=0 會強制在發送數據時不使用GDR;而在爲1的時候,根據NCCL_NET_GDR_LEVEL來決定發送數據時是否使用GDR。接收數據時是否使用GDR完全由距離決定,和NCCL_NET_GDR_READ無關(參見nccl源碼transport/http://net.cc中netGetGdrSupport函數)。
  • NCCL_SHM_DISABLE 在P2P不能生效的情況下,是否使用cpu的共享內存來傳輸數據。如果禁用,則使用socket通信

因爲nccl裏面以enum{ "PIX", "PXB", "PHB", "NODE", "SYS" }來描述設備(包括GPU卡和網卡)之間的”距離”,所以NCCL_P2P_LEVEL和NCCL_NET_GDR_LEVEL都有0~5這6種取值,來細粒度控制何種情況下可以使用P2P或者GDR。

圖12 LEVEL和distance的關係

 

對於圖10中機型來說,通過參考nccl源碼裏的pciDistance和netDistance函數,我們可以很輕鬆地寫出程序來輸出各GPU卡和網卡之間的”距離”。

表1 p2p_level用到的pciDistance

表2 net_gdr_level用到的netDistance

2)性能數據

表3 多機通信時,GPU/NIC間的通信方式

 

表4 不同配置下通信速度對比(以2機16張P40卡nccl_broadcast爲例,兩機間RoCEv2+100Gbps互聯)

 

圖13 不同傳輸方式對多機通信速度影響巨大

 

以上通過一些代碼分析和測試數據,介紹了實際開發中值得注意的影響GPU機器數據傳輸的因素。希望對從事分佈式訓練的同學們有一些幫助

參考資料

[1]https://docs.nvidia.com/deeplearning/sdk/nccl-developer-guide/docs/

[2]https://devblogs.nvidia.com/how-optimize-data-transfers-cuda-cc/

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章