close

7.3 GPU使用

深度學習之所以可以發展迅猛,得益於強大的計算力。在PyTorch中,自然加持GPU加速運算,本節將介紹PyTorchGPU的使用原理與多GPU使用的DataParallel原理,還有一些針對GPU的實用程式碼片段。

gpucpu

在處理器家族中,有兩大陣營,分別是CPUGPU,它們分工協作,共同完成電腦複雜功能。

但它們兩者主要差別在哪裡?下麵一探究竟。

CPU(central processing unit, 中央處理器)cpu主要包括兩個部分,即控制器、運算器,除此之外還包括快取記憶體等

GPU(Graphics Processing Unit, 圖形處理器)是為處理類型統一並且相互無依賴的大規模資料運算,以及不需要被打斷的純淨的計算環境為設計的處理器,因早期僅有圖形圖像任務中設計大規模統一無依賴的運算,因此該處理器稱為影像處理器,俗稱顯卡。

那麼它們之間主要區別在哪裡呢,來看一張示意圖

<<AI人工智慧 PyTorch自學>> 7.3 GPU使用

綠色的是計算單元,橙紅色的是存儲單元,橙黃色的是控制單元,從示意圖中看出,gpu的重點在計算,cpu的重點在控制,這就是兩者之間的主要差異。

pytorch中,可以將訓練資料以及模型參數遷移到gpu上,這樣就可以加速模型的運算

在這裡,需要瞭解的是,在pytorch中,兩個資料運算必須在同一個設備上。

PyTorch的設備——torch.device

前面提到,PyTorch的運算需要將運算資料放到同一個設備上,因此,需要瞭解PyTorch中設備有哪些?

目前,PyTorch支援兩種設備,cpucuda,為什麼是cuda而不是gpu?因為早期,只有NvidiaGPU能用於模型訓練加速,因此稱之為cuda

即使現在支援了AMD顯卡進行加速,仍舊使用cuda來代替gpu

PyTorch中表示設備通常用torch.device)這個函數進行設置,例如:

>>> torch.device('cuda:0')

device(type='cuda', index=0)

 

>>> torch.device('cpu')

device(type='cpu')

 

>>> torch.device('cuda'# current cuda device

device(type='cuda')

Copy

補充資料:

  1. ROCm平臺及HIP介紹
  2. https://pytorch.org/docs/stable/notes/hip.html

把資料放到GPU——to函數

pytorch中,只需要將要進行運算的資料放到gpu上,即可使用gpu加速運算

在模型運算過程中,需要放到GPU的主要是兩個:

  1. 輸入資料——形式為tensor
  2. 網路模型——形式為module

pytorch中針對這兩種資料都有相應的函數把它們放到gpu上,我們來認識一下這個函數,就是to函數

tensorto函數: to(*args, \kwargs) Tensor**

功能:轉換張量的資料類型或者設備

注意事項:to函數不是inplace操作,所以需要重新賦值,這與moduleto函數不同

使用:

  1. 轉換資料類型

x = torch.ones((3, 3))

x = x.to(torch.float64)

  1. 轉換設備

x = torch.ones((3, 3))

x = x.to("cuda")

moduleto函數:to(*args, \kwargs)**

功能:move and/or cast the parameters and buffers,轉換模型中的參數和緩存

注意事項:實行的是inplace操作

使用:

  1. 轉換資料類型

linear = nn.Linear(2, 2)

print(linear.weight)

linear.to(torch.double)

print(linear.weight)

  1. 遷移至gpu

gpu1 = torch.device("cuda:1")

linear.to(gpu1)

print(linear.weight)

torch.device to函數聯合使用,就是第六章混淆矩陣代碼中使用過的方式

device = torch.device("cuda" if torch.cuda.is_available() else "cpu")

model.to(device)

inputs, labels = inputs.to(device), labels.to(device)

Copy

通常,會採用torch.cuda.is_available()函數來自適應當前設備,若沒有gpu可用,自動設置devicecpu,不會影響代碼的運行。

除了torch.cuda.is_available()torch庫中還有一些關於cuda的實用函數,下面一起看看。

torch.cuda

torch.cuda中有幾十個關於guda的函數,詳細請查閱官方文檔)

下面介紹幾個常用的函數。

  1. torch.cuda.device_count() 查看可用GPU數量
  2. torch.cuda.current_device():查看當前使用的設備的序號
  3. torch.cuda.get_device_name():獲取設備的名稱
  4. torch.cuda.get_device_capability(device=None):查看設備的計算力
  5. torch.cuda.is_available():查看cuda是否可用
  6. torch.cuda.get_device_properties():查看GPU屬性
  7. torch.cuda.set_device(device):設置可用設備,已不推薦使用,建議通過CUDA_VISIBLE_DEVICES來設置,下文會講解CUDA_VISIBLE_DEVICES的使用。
  8. torch.cuda.mem_get_info(device=None):查詢gpu空餘顯存以及總顯存。
  9. torch.cuda.memory_summary(device=None, abbreviated=False):類似模型的summary,它將GPU的詳細資訊進行輸出。
  10. torch.cuda.empty_cache():清空緩存,釋放顯存碎片。
  11. torch.backends.cudnn.benchmark = True : 提升運行效率,僅適用於輸入資料較固定的,如卷積

會讓程式在開始時花費一點額外時間,為整個網路的每個卷積層搜索最適合它的卷積實現演算法,進而實現網路的加速讓內置的 cuDNN auto-tuner 自動尋找最適合當前配置的高效演算法,來達到優化運行效率的問題

  1. torch.backends.cudnn.deterministic: 用以保證實驗的可重複性.

由於cnDNN 每次都會去尋找一遍最優配置,會產生隨機性,為了模型可複現,可設置torch.backends.cudnn.deterministic = True

運行代碼,可看到如下資訊:

 

device_count: 1

current_device:  0

(8, 6)

NVIDIA GeForce RTX 3060 Laptop GPU

True

['sm_37', 'sm_50', 'sm_60', 'sm_61', 'sm_70', 'sm_75', 'sm_80', 'sm_86', 'compute_37']

_CudaDeviceProperties(name='NVIDIA GeForce RTX 3060 Laptop GPU', major=8, minor=6, total_memory=6144MB, multi_processor_count=30)

(5407899648, 6442450944)

|===========================================================================|

|                  PyTorch CUDA memory summary, device ID 0                 |

|---------------------------------------------------------------------------|

|            CUDA OOMs: 0            |        cudaMalloc retries: 0         |

|===========================================================================|

|        Metric         | Cur Usage  | Peak Usage | Tot Alloc  | Tot Freed  |

|---------------------------------------------------------------------------|

| Allocated memory      |       0 B  |       0 B  |       0 B  |       0 B  |

|       from large pool |       0 B  |       0 B  |       0 B  |       0 B  |

|       from small pool |       0 B  |       0 B  |       0 B  |       0 B  |

|---------------------------------------------------------------------------|

| Active memory         |       0 B  |       0 B  |       0 B  |       0 B  |

|       from large pool |       0 B  |       0 B  |       0 B  |       0 B  |

|       from small pool |       0 B  |       0 B  |       0 B  |       0 B  |

|---------------------------------------------------------------------------|

| GPU reserved memory   |       0 B  |       0 B  |       0 B  |       0 B  |

|       from large pool |       0 B  |       0 B  |       0 B  |       0 B  |

|       from small pool |       0 B  |       0 B  |       0 B  |       0 B  |

|---------------------------------------------------------------------------|

| Non-releasable memory |       0 B  |       0 B  |       0 B  |       0 B  |

|       from large pool |       0 B  |       0 B  |       0 B  |       0 B  |

|       from small pool |       0 B  |       0 B  |       0 B  |       0 B  |

|---------------------------------------------------------------------------|

| Allocations           |       0    |       0    |       0    |       0    |

|       from large pool |       0    |       0    |       0    |       0    |

|       from small pool |       0    |       0    |       0    |       0    |

|---------------------------------------------------------------------------|

| Active allocs         |       0    |       0    |       0    |       0    |

|       from large pool |       0    |       0    |       0    |       0    |

|       from small pool |       0    |       0    |       0    |       0    |

|---------------------------------------------------------------------------|

| GPU reserved segments |       0    |       0    |       0    |       0    |

|       from large pool |       0    |       0    |       0    |       0    |

|       from small pool |       0    |       0    |       0    |       0    |

|---------------------------------------------------------------------------|

| Non-releasable allocs |       0    |       0    |       0    |       0    |

|       from large pool |       0    |       0    |       0    |       0    |

|       from small pool |       0    |       0    |       0    |       0    |

|---------------------------------------------------------------------------|

| Oversize allocations  |       0    |       0    |       0    |       0    |

|---------------------------------------------------------------------------|

| Oversize GPU segments |       0    |       0    |       0    |       0    |

|===========================================================================|

 

None

Copy

gpu訓練——nn.DataParallel

人多力量大的道理在PyTorch的訓練中也是適用的,PyTorch支持多個GPU共同訓練,加快訓練速度。

GPU可分為單機多卡和多機多卡,這裡僅介紹單機多卡的方式。

單機多卡的實現非常簡單,只需要增加一行代碼:

net = nn.DataParallel(net)

代碼的意思是將一個nn.Module變為一個特殊的nn.Module,這個Moduleforward函數實現多GPU調用。

如果對nn.Module的概念以及forward函數不理解的話,請回到第四章進行學習。

首先看一幅示意圖,理解多GPU是如何工作的

<<AI人工智慧 PyTorch自學>> 7.3 GPU使用

整體有四個步驟:

  1. 資料平均劃為N
  2. 模型參數複製N
  3. NGPU上同時運算
  4. 回收NGPU上的運算結果

瞭解了多gpu運行機制,下面看看DataParallel是如何實現的。

torch.nn.DataParallel(module, device_ids=None, output_device=None, dim=0)

功能:實現模型的資料並行運算

主要參數:

module - 需要並行的module

device_ids: (list of python:int or torch.device) – CUDA devices (default: all devices), eg: [2, 3]

默認採用所有可見gpu,這裡強調了可見gpu,就是說可以設置部分gpu對當前python腳本不可見,這個可以通過系統環境變數設置

output_device: int or torch.device , 設置輸出結果所在設備,預設為 device_ids[0],通常以第1個邏輯gpu為主gpu

原始程式碼分析:

DataParallel仍舊是一個nn.Module類,所以首要關注它的forward函數。

來到/torch/nn/parallel/data_parallel.py147行:

def forward(self, *inputs, **kwargs):

    with torch.autograd.profiler.record_function("DataParallel.forward"):

        if not self.device_ids:

            return self.module(*inputs, **kwargs)

 

        for t in chain(self.module.parameters(), self.module.buffers()):

            if t.device != self.src_device_obj:

                raise RuntimeError("module must have its parameters and buffers "

                                   "on device {} (device_ids[0]) but found one of "

                                   "them on device: {}".format(self.src_device_obj, t.device))

 

        inputs, kwargs = self.scatter(inputs, kwargs, self.device_ids)

        # for forward function without any inputs, empty list and dict will be created

        # so the module can be executed on one device which is the first one in device_ids

        if not inputs and not kwargs:

            inputs = ((),)

            kwargs = ({},)

 

        if len(self.device_ids) == 1:

            return self.module(*inputs[0], **kwargs[0])

        replicas = self.replicate(self.module, self.device_ids[:len(inputs)])

        outputs = self.parallel_apply(replicas, inputs, kwargs)

        return self.gather(outputs, self.output_device)

Copy

核心點有4行代碼,分別是:

inputs, kwargs = self.scatter(inputs, kwargs, self.device_ids)

replicas = self.replicate(self.module, self.device_ids[:len(inputs)])

outputs = self.parallel_apply(replicas, inputs, kwargs)

return self.gather(outputs, self.output_device)

Copy

一、數據切分

inputs, kwargs = self.scatter(inputs, kwargs, self.device_ids):利用scatter函數,將資料切分為多塊,為各GPU需要的資料做準備。

scatter函數在torch\nn\parallel\scatter_gather.py11行。

def scatter(inputs, target_gpus, dim=0):

    r"""

    Slices tensors into approximately equal chunks and

    distributes them across given GPUs. Duplicates

    references to objects that are not tensors.

    """

Copy

二、模型分發至GPU

replicas = self.replicate(self.module, self.device_ids[:len(inputs)]):利用replicate函數將模型複製N份,用於多GPU上。

replicate函數在 torch\nn\parallel\replicate.py78行。

三、執行並行推理

outputs = self.parallel_apply(replicas, inputs, kwargs):多GPU同時進行運算。

四、結果回收

return self.gather(outputs, self.output_device)

為了理解分發過程,請使用具備多GPU的環境,運行配套代碼,可看到如下資訊:

batch size in forward: 4

batch size in forward: 4

batch size in forward: 4

batch size in forward: 4

model outputs.size: torch.Size([16, 3])

CUDA_VISIBLE_DEVICES :0,1,3,2

device_count :4

batchsize設置為16,將16個樣本平均分發給4GPU,因此在forward函數當中,看到的資料是4個樣本。

GPU訓練模型的保存與載入

當模型變為了Dataparallel時,其參數名稱會多一個module.欄位,這導致在保存的時候state_dict也會多了module.欄位。

從而,在載入的時候經常出現以下報錯。

RuntimeError: Error(s) in loading state_dict for FooNet:

​ Missing key(s) in state_dict: "linears.0.weight", "linears.1.weight", "linears.2.weight".

​ Unexpected key(s) in state_dict: "module.linears.0.weight", "module.linears.1.weight", "module.linears.2.weight".

解決方法是,移除key中的module.

    from collections import OrderedDict

    new_state_dict = OrderedDict()

    for k, v in state_dict_load.items():

        namekey = k[7:] if k.startswith('module.') else k

        new_state_dict[namekey] = v

Copy

請結合代碼運行,觀察其使用,並看到如下結果:

state_dict_load:

OrderedDict([('module.linears.0.weight', tensor([[ 0.33370.0317, -0.1331],

        [ 0.04310.04540.1235],

        [ 0.0575, -0.2903, -0.2634]])), ('module.linears.1.weight', tensor([[ 0.12350.1520, -0.1611],

        [ 0.4511, -0.1460, -0.1098],

        [ 0.0653, -0.5025, -0.1693]])), ('module.linears.2.weight', tensor([[ 0.3657, -0.1107, -0.2341],

        [ 0.0657, -0.0194, -0.3119],

        [-0.0477, -0.10080.2462]]))])

new_state_dict:

OrderedDict([('linears.0.weight', tensor([[ 0.33370.0317, -0.1331],

        [ 0.04310.0454,  0.1235],

        [ 0.0575, -0.2903, -0.2634]])), ('linears.1.weight', tensor([[ 0.12350.1520, -0.1611],

        [ 0.4511, -0.1460, -0.1098],

        [ 0.0653, -0.5025, -0.1693]])), ('linears.2.weight', tensor([[ 0.3657, -0.1107, -0.2341],

        [ 0.0657, -0.0194, -0.3119],

        [-0.0477, -0.10080.2462]]))])

 

Process finished with exit code 0

Copy

使用指定編號的gpu

通常,一台伺服器上有多個用戶,或者是會進行多個任務,此時,對gpu合理的安排使用就尤為重要

在實踐中,通常會設置當前python腳本可見的gpu,然後直接使用nn.dataparallel使用所有gpu即可,不需要手動去設置使用哪些gpu

設置python腳本可見gpu的方法為設置系統環境變數中的CUDA_VISIBLE_DEVICES

設置方法為:

os.environ.setdefault("CUDA_VISIBLE_DEVICES", "2, 3")

Copy

當時之後,即物理設備的2,3GPU,在程式中分別是0號、1GPU,這裡需要理解邏輯編號與物理編號的對應關係。

注意事項 CUDA_VISIBLE_DEVICES的設置一定要在 device = torch.device("cuda" if torch.cuda.is_available() else "cpu") 之前!

否則已經調用cudapython腳本已經獲取當前可見gpu了,再設置就無效了

 

arrow
arrow
    全站熱搜
    創作者介紹
    創作者 HCHUNGW 的頭像
    HCHUNGW

    HCHUNGW的部落格

    HCHUNGW 發表在 痞客邦 留言(0) 人氣()