什么是 GPU 編程?
GPU 計算是使用圖形處理單元 (graphics processing unit) 來執(zhí)行曾經(jīng)由中央處理單元 (central processing unit) 處理的高度并行的獨立計算。
GPU 編程是一種在 GPU 加速器上運行高度并行的通用計算的方法。
雖然過去的 GPU 專為計算機圖形設(shè)計,但如今它們也被廣泛用于通用計算。除了圖形渲染,GPU 驅(qū)動的并行計算現(xiàn)在還用于科學(xué)建模、機器學(xué)習和其他需要并行化的工作。
CPU 與 GPU 計算差異
中央處理器 (CPU) 是延遲優(yōu)化的通用處理器,旨在按順序處理各種不同的任務(wù),而圖形處理單元 (GPU) 是吞吐量優(yōu)化的專用處理器,專為高端并行設(shè)計計算。
您是否需要 GPU 加速器的問題歸結(jié)為您要解決的問題的具體情況。CPU 和 GPU都有不同的優(yōu)勢領(lǐng)域,了解它們的局限性會讓你在決定是否為你的項目使用 GPU 編程時做得更好。
GPU 編程 API
GPU 根據(jù)圖元理解計算問題。如今,有多種可用的編程框架可以在幕后為您操作這些原語,因此您可以專注于更高級別的計算概念。
CUDA
計算統(tǒng)一設(shè)備架構(gòu) (CUDA) 是 Nvidia 于 2006 年創(chuàng)建的并行計算平臺和應(yīng)用程序編程接口 (API),可直接訪問 GPU 的虛擬指令集以執(zhí)行計算內(nèi)核。
內(nèi)核是在 GPU 上運行的函數(shù)。當我們啟動內(nèi)核時,它做為一組線程執(zhí)行。每個線程都映射到 GPU 上的單個 CUDA 內(nèi)核,并對數(shù)據(jù)子集執(zhí)行相同的操作。根據(jù)Flynn 的分類法,它是一種單指令多數(shù)據(jù) (SIMD) 計算。
線程被分組為塊,當內(nèi)核啟動時,它們被映射到一組相應(yīng)的 CUDA 內(nèi)核。塊進一步分組為網(wǎng)格,每次內(nèi)核啟動都會創(chuàng)建一個網(wǎng)格。
CUDA 編程模型允許軟件工程師使用支持 CUDA 的 GPU 在 C/C++ 和 Fortran 中進行通用處理,第三方包裝器也可用于 Python、Java、R 和其他幾種編程語言。CUDA 兼容從 G8x 系列開始的所有 Nvidia GPU,以及大多數(shù)標準操作系統(tǒng)。
OpenCL
雖然 CUDA 是專有框架,但 OpenCL 是由 Khronos Group 創(chuàng)建的跨異構(gòu)平臺并行編程的開放標準。OpenCL 與中央處理器 (CPU)、圖形處理單元 (GPU)、數(shù)字信號處理器、現(xiàn)場可編程門陣列 (FPGA) 和其他處理器或硬件加速器協(xié)同工作。
OpenCL 用途極為廣泛,已被科技行業(yè)巨頭成功采用,包括 AMD、Apple、IBM、Intel、Nvidia、Qualcomm、Samsung 等。它基于 C/C++ 語言,第三方包裝器也可用于 Python、Java、R、GO、JavaScript 等。
OpenACC
OpenACC 是一種用戶驅(qū)動的基于指令的并行編程標準,專為有興趣將其代碼移植到各種異構(gòu)高性能計算 (HPC) 硬件平臺的科學(xué)家和工程師而設(shè)計。該標準是由用戶為用戶設(shè)計的。
OpenACC 旨在簡化異構(gòu) CPU/GPU 硬件平臺和架構(gòu)的并行編程,其編程工作量比低級模型所需的工作量少得多。它支持 C/C++ 和 Fortran 編程語言。
使用 CUDA 和 Python 進行 GPU 編程
開始構(gòu)建 GPU 加速程序有多種標準和多種編程語言,但我們選擇了 CUDA 和 Python 來說明我們的示例。CUDA 是最容易上手的框架,Python 在科學(xué)、工程、數(shù)據(jù)分析和深度學(xué)習領(lǐng)域非常流行——所有這些都嚴重依賴并行計算。
即使在 Python 中,您也可以在不同的抽象層上進行 GPU 編程。如果您不需要較低抽象層可以提供的額外自定義和控制功能,那么從滿足您的應(yīng)用程序的最高抽象層開始是合理的。
讓我們回顧一下在 Python 中使用 CUDA 的方法,從最高抽象級別開始一直到最低級別。
CUDA 用于專業(yè)庫
如果您只想使用神經(jīng)網(wǎng)絡(luò)或任何其他深度學(xué)習算法,那么 Tensorflow 或 PyTorch 等專業(yè)深度學(xué)習庫可能是您的正確選擇。這些庫可以在后臺為您自動在 CPU 和 GPU 處理之間切換。
CUDA 做為直接替代品
如果您是使用 NumPy 和 SciPy 的科學(xué)家,優(yōu)化 GPU 計算代碼的最簡單方法是使用 CuPy。它模仿大多數(shù) NumPy 函數(shù),并允許您簡單地插入 NumPy 代碼并將其替換為在 GPU 而不是 CPU 上處理的 CuPy 函數(shù)。
CUDA 用于自定義算法。
當您需要使用自定義算法時,您不可避免地需要深入抽象層次結(jié)構(gòu)并使用 NUMBA。它綁定到 CUDA 并允許您在 Python 中編寫自己的 CUDA 內(nèi)核。通過這種方式,您可以僅使用 Python 非常接近 CUDA C/C++,而無需自己分配內(nèi)存。
CUDA 做為 C/C++ 擴展
如果您想要對硬件進行最高級別的控制,例如手動內(nèi)存分配、動態(tài)并行性或紋理內(nèi)存管理,那么使用 C/C++ 是沒有辦法的。對于 Python 應(yīng)用程序,最方便的方法是使用 PyCUDA 擴展,它允許您在 Python 字符串中編寫 CUDA C/C++ 代碼。
如何在 Ubuntu 20.04 上開始使用 CUDA for Python?
在 Ubuntu 20.04 上安裝 CUDA
首先,您需要在具有支持 CUDA 的 GPU 的機器上安裝 CUDA。要使用本地安裝程序在 Ubuntu 20.04 上安裝 CUDA 驅(qū)動程序,請按照以下說明操作:
wget https://developer.download.nvidia.com/compute/cuda/repos/ubuntu2004/x86_64/cuda-ubuntu2004.pin
sudo mv cuda-ubuntu2004.pin /etc/apt/preferences.d/cuda-repository-pin-600
wget https://developer.download.nvidia.com/compute/cuda/11.4.2/local_installers/cuda-repo-ubuntu2004-11-4-local_11.4.2-470.57.02-1_amd64.deb
sudo dpkg -i cuda-repo-ubuntu2004-11-4-local_11.2-470.57.02-1_amd64.deb
sudo apt-key add /var/cuda-repo-ubuntu2004-11-4-local/7fa2af80.pub
sudo apt-get update
sudo apt-get -y install cuda
安裝 CuPy 庫
接下來,我們需要安裝一個 Python 庫來使用 CUDA。如上所述,有許多方法可以在不同的抽象級別在 Python 中使用 CUDA。由于 NumPy 是 Python 數(shù)據(jù)科學(xué)生態(tài)系統(tǒng)的骨干庫,我們將選擇對其進行加速以進行本次演示。
使用 NumPy 的最簡單方法是使用名為 CuPy 的插入式替換庫,它在 GPU 上復(fù)制 NumPy 函數(shù)。您可以通過 pip 安裝穩(wěn)定版的 CuPy 源碼包:
pip install cupy
編寫腳本來比較 CPU 和 GPU
最后,您要確保 CuPy 在您的系統(tǒng)上運行良好,以及它能在多大程度上提高您的性能。為此,讓我們編寫一個簡單的腳本來實現(xiàn)這一目的。
讓我們導(dǎo)入 NumPy 和 CuPy 庫,以及我們將用于基準處理單元的時間庫。
import numpy as np
import cupy as cp
from time import time
接下來,讓我們定義一個將用于基準測試的函數(shù)。
def benchmark_processor(arr, func, argument):
start_time = time()
func(arr, argument) # your argument will be broadcasted into a matrix automatically
finish_time = time()
elapsed_time = finish_time – start_time
return elapsed_time
然后需要實例化兩個矩陣:一個用于 CPU,一個用于 GPU。我們將為我們的矩陣選擇 9999 x 9999 的形狀。
# load a matrix to global memory
array_cpu = np.random.randint(0, 255, size=(9999, 9999))
# load the same matrix to GPU memory
array_gpu = cp.asarray(array_cpu)
最后,我們要運行一個簡單的加法函數(shù)來確定 CPU 與 GPU 處理器的性能差異。
# benchmark matrix addition on CPU by using a NumPy addition function
cpu_time = benchmark_processor(array_cpu, np.add, 999)
# you need to run a pilot iteration on a GPU first to compile and cache the function kernel on a GPU
benchmark_processor(array_gpu, cp.add, 1)
# benchmark matrix addition on GPU by using CuPy addition function
gpu_time = benchmark_processor(array_gpu, cp.add, 999)
# determine how much is GPU faster
faster_processor = (gpu_time - cpu_time) / gpu_time * 100
并將結(jié)果打印到控制臺。
print(f"CPU time: {cpu_time} seconds\nGPU time: {gpu_time} seconds.\nGPU was {faster_processor} percent faster")
在配備來自Cherry Servers GPU Cloud的 Nvidia Geforce GT1030 GPU 加速器的 Intel Xeon 1240v3 機器上運行此腳本后,我們確認整數(shù)加法在 GPU 上的運行速度快了許多倍。例如,當使用 10000x10000 矩陣時,GPU 運行整數(shù)加法的速度要快 1294 倍。
事實上,矩陣越大,您期望的性能提升就越高。
cuda-gpu-基準測試
圖 1 – GPU 性能提升。
我們通過對 100x100、500x500、1000x1000、7500x7500 和 10000x10000 二維矩陣使用整數(shù)加法,比較了 CPU 與 GPU 的性能(以秒為單位)。當使用足夠大的矩陣時,GPU 開始明顯超過 CPU。
總結(jié)
NVIDIA官方網(wǎng)站。https://developer.nvidia.com/language-solutions
Aliyun Servers GPU 服務(wù)器。https://www.aliyun.com/minisite/goods?userCode=69hiviin
Cherry Servers GPU 服務(wù)器。https://www.cherryservers.com/?affiliate=1TO67IVM
如果您正在處理可以并行處理的大量數(shù)據(jù),那么深入研究 GPU 編程可能是值得的。如您所見,使用 GPU 計算處理大型矩陣時性能顯著提高。到一天結(jié)束時,如果您的應(yīng)用程序可以利用并行計算,它可能會為您節(jié)省寶貴的時間和資源。
半熟不熟,慢慢就熟了。