CUDA(Compute Unified Devices Architecture,统一计算架构)是由英伟达(NVIDIA)所推出的一个并行计算平台,是該公司對於GPGPU的正式名稱。透過這個技術,使用者可利用NVIDIA的GPU进行图像处理之外的運算。
CUDA 開發套件(CUDA Toolkit )只能將自家的CUDA C-語言(對OpenCL只有链接的功能),編譯成PTX中間語言,或特定NVIDIA GPU架構的機器碼(NVIDIA 官方稱為 "device code").執行於CPU部分的C / C++程式碼(NVIDIA 官方稱為 "host code")仍依賴於外部的編譯器,如Microsoft Windows下需要Microsoft Visual Studio;Linux下則主要依賴於GCC。
簡介
GPU不僅用於進行圖形渲染,而且用於物理運算(物理效果如碎片、煙、火、流體)如PhysX和Bullet。進一步的,GPU可以用在計算生物學與密碼學等領域的非圖形應用上。
使用CUDA技術,GPU可以用來進行通用處理(不僅僅是圖形);這種方法被稱為GPGPU。與CPU不同的是,GPU并行计算能力更强,更擅长处理数据密集型计算。
需要注意的是,Nvidia CUDA仅支持在部分Nvidia GPU上运行。Nvidia CUDA Toolkit在EULA中也禁止用户将编译后的代码运行在其他类型GPU上
優點
在GPUs(GPGPU)上使用圖形APIs進行傳統通用計算,CUDA技術有下列幾個優點:
- 分散讀取——代碼可以從記憶體的任意位址讀取
- 統一虛擬記憶體(Unified Memory, 從 CUDA 6.0 開始)—— 將所有 CPU 和 GPU 的內存置於統一管理的虛擬記憶體空間下。
- 共用記憶體(Global Memory)—— 存取快速的區域,使之在多個執行緒間共用,有效頻寬比紋理記憶體(Texture Memory)更大。
- 與GPU之間更快的下載與回讀
- 全面支持整型與位操作,包括整型紋理查找
限制
- CUDA不支援完整的C語言標準。它在C++編譯器上運行主機程式碼時,會使一些在C中合法(但在C++中不合法)的代碼無法編譯。
- 不支持紋理渲染(CUDA 3.2及以後版本通過在CUDA陣列中引入“表面寫操作”——底層的不透明資料結構——來進行處理)
- 受系統主線的頻寬和延遲的影響,主機與設備記憶體之間資料複製可能會導致性能下降(通過GPU的DMA引擎處理,非同步記憶體傳輸可在一定範圍內緩解此現象)
- 當執行緒總數為數千時,執行緒應按至少32個一組來運行才能獲得最佳效果。如果每組中的32個執行緒使用相同的執行路徑,則程式分支不會顯著影響效果;在處理本質上不同的任務時,单指令流多数据流(SIMD)執行模型將成為一個瓶頸(如在光線追蹤演算法中遍歷一個空間分割的資料結構)
- 只有NVIDIA的GPUs支援CUDA技術
- 由於編譯器需要使用優化技術來利用有限的資源,即使合法的C/C++有時候也會被標記並中止編譯
- CUDA(計算能力1.x)使用一個不包含遞迴、函數指標的C語言子集,外加一些簡單的擴展。而單個進程必須運行在多個不相交的記憶體空間上,這與其它C語言運行環境不同。
- CUDA(計算能力2.x)允許C++類功能的子集,如成員函數可以不是虛擬的(這個限制將在以後的某個版本中移除)[參見《CUDA C程式設計指南3.1》-附錄D.6]
- 雙精度浮點(CUDA計算能力1.3及以上)與IEEE754標準有所差異:倒數、除法、平方根僅支持舍入到最近的偶數。單精確度中不支持反常值(denormal)及sNaN(signaling NaN);只支援兩種IEEE舍入模式(舍位與舍入到最近的偶數),這些在每條指令的基礎上指定,而非控制字碼;除法/平方根的精度比單精確度略低。
示例
CUDA Driver API
下面將示範以最底層的[https://docs.nvidia.com/cuda/cuda-driver-api/index.html CUDA Driver API] 調用GPU做列向量的加法,以下為 CPU 端的程式碼
// 本範例修改自 Andrei de A. Formiga (2012-06-04) 寫的範例: https://gist.github.com/tautologico/2879581
// 編譯指令 nvcc -O3 -lcuda add.c -o add.exe
#include
#include
#include
#include // Driver api 的型態定義
#define N 1024 //列向量長度
// 利用CUDA函數的錯誤傳回做例外處理
inline void checkCudaErrors( CUresult err)
{
if( CUDA_SUCCESS != err) {
printf("CUDA Driver API error = %04d from file , line %i.\n",
err, __FILE__, __LINE__ );
exit(-1); // 直接終止程式
}
}
CUdevice device; // CUDA 裝置(也就是GPU)物件
CUcontext context; // CUDA 內容物件
CUmodule module; // 代表GPU程式碼的物件
CUfunction function; // CUDA GPU 函數
size_t totalGlobalMem; // CUDA 裝置記憶體總量
// Driver API 只能自外部檔案讀取 GPU 程式,可以為 PTX 中間碼也可以是 cubin 機器碼(或是混合各種架構機器碼的fatbin)
char module_file = (char) "matSumKernel.cubin";
// GPU 函數名稱
char kernel_name = (char) "matSum";
// 初始化 CUDA 的手續
void initCUDA()
{
int deviceCount = 0; // 當前可使用的 CUDA 裝置(GPU)數
CUresult err = cuInit(0); // 初始化 CUDA API
if (err == CUDA_SUCCESS) // 取得可用裝置數
checkCudaErrors(cuDeviceGetCount(&deviceCount));
if (deviceCount == 0) { // 確定有可用的裝置
fprintf(stderr, "Error: no devices supporting CUDA\n");
exit(-1);
}
// get first CUDA device
checkCudaErrors(cuDeviceGet(&device, 0)); // 取編號為 0 的裝置
char name[100];
cuDeviceGetName(name, 100, device); // 印出裝置名稱
printf("> Using device 0: %s\n", name);
checkCudaErrors( cuDeviceTotalMem(&totalGlobalMem, device) );
// 印出裝置可用記憶體
printf(" Total amount of global memory: %llu bytes\n",
(unsigned long long)totalGlobalMem);
// GPU 記憶體是否是為64bits定址
printf(" 64-bit Memory Address: %s\n",
(totalGlobalMem > (unsigned long long)410241024*1024L)?
"YES" : "NO");
// 創建 CUDA 內容
err = cuCtxCreate(&context, 0, device);
if (err != CUDA_SUCCESS) {
fprintf(stderr, "* Error initializing the CUDA context.\n");
cuCtxDetach(context);
exit(-1);
}
// 讀取編譯好的cubin GPU程式碼
err = cuModuleLoad(&module, module_file);
if (err != CUDA_SUCCESS) {
fprintf(stderr, "* Error loading the module %s\n", module_file);
cuCtxDetach(context); // 釋放 CUDA 內容物件
exit(-1);
}
// 獲取GPU程式裡函數"matSum"的指標
err = cuModuleGetFunction(&function, module, kernel_name);
if (err != CUDA_SUCCESS) {
fprintf(stderr, "* Error getting kernel function %s\n", kernel_name);
cuCtxDetach(context);
exit(-1);
}
}
int main(int argc, char **argv)
{
int a[N], b[N], c[N];
CUdeviceptr d_a, d_b, d_c;
// 注意 GPU 變數指標的型態是 CUdeviceptr
// typedef unsigned int CUdeviceptr_v2
// typedef CUdeviceptr_v2 CUdeviceptr
// 初始化主記憶體變數
for (int i = 0; i 而以下是GPU端的程式碼
// 本範例修改自 Andrei de A. Formiga (2012-06-04) 寫的範例: https://gist.github.com/tautologico/2879581
// 此部分要先編譯成 cubin 後才可以被 CPU 端程式使用
// 編譯指令 nvcc -O3 -cubin -arch=native matSumKernel.cu -o matSumKernel.cubin
#define N 1024 //列向量長度
extern "C" __global__ void matSum(int a, int b, int *c)
{
int tid = blockIdx.x; // thread 的 x 座標
if (tid
CUDA Runtime API
下列的範例是以相較於 Driver API 來說比較簡便的 [https://docs.nvidia.com/cuda/cuda-runtime-api/index.html CUDA Runtime API] 做列向量的加法:
// 本範例修改自Nvidia官方的CUDA開發指引: https://docs.nvidia.com/cuda/cuda-c-programming-guide/index.html#kernels
// 編譯指令 nvcc vector_add.cu -arch=native -o vector_add.exe
// -arch=native 代表將 device code 編譯成當前電腦 Nvidia GPU 架構的機器碼,拿掉就是照預設編譯成 PTX 中間碼。
#include
#include // 引用動態分配 malloc、隨機函數 rand() 和隨機上限 RAND_MAX
#define N 1024 // 列向量長度
// Device code: 送入GPU執行的部分
__global__ void VecAdd(float A, float B, float* C)
{
int i = threadIdx.x; // thread 的 x 座標
if (i >>(d_A, d_B, d_C);
// 將算好的向量從 GPU 複製到 CPU
cudaMemcpy(h_C, d_C, size, cudaMemcpyDeviceToHost);
// 印出運算結果
for(i = 0; i
Python
以下用 Python 编写的例子,将两组数在 GPU 上进行相乘,并输出结果。使用[https://mathema.tician.de/software/pycuda/ PyCUDA] 作为CUDA的Python绑定:import numpy
import pycuda.autoinit
from numpy.typing import NDArray, float32
from pycuda.compiler import SourceModule
from pycuda.driver import Function, In, Out
mod: SourceModule = SourceModule(
"""
__global__ void multiply_them(float dest, float a, float* b) {
const int i = threadIdx.x;
dest[i] = a[i] * b[i];
}
"""
)
multiply_them: Function = mod.get_function("multiply_them")
a: NDArray[float32] = numpy.random.randn(400).astype(numpy.float32)
b: NDArray[float32] = numpy.random.randn(400).astype(numpy.float32)
dest: NDArray[float32] = numpy.zeros_like(a)
multiply_them(Out(dest), In(a), In(b), block=(400, 1, 1))
print(dest - a * b)[https://web.archive.org/web/20090420124748/http://kered.org/blog/2009-04-13/easy-python-numpy-cuda-cublas/ pycublas]间接调用 CUDA 进行矩阵乘法,如下所示:
import numpy
from pycublas import CUBLASMatrix
A: CUBLASMatrix = CUBLASMatrix(numpy.mat([[1, 2, 3], [4, 5, 6]], numpy.float32))
B: CUBLASMatrix = CUBLASMatrix(numpy.mat([[2, 3], [4, 5], [6, 7]], numpy.float32))
C: CUBLASMatrix = A * B
print(C.np_mat())
,使用CUDA加速,可以直接替换NumPy:
import cupy
from cupy.typing import NDArray, float64
a: NDArray[float64] = cupy.random.randn(400)
b: NDArray[float64] = cupy.random.randn(400)
dest: NDArray[float64] = cupy.zeros_like(a)
print(dest - a * b)
显卡的受支持情况
详情请参见[http://developer.nvidia.com/cuda-gpus Nvidia]
應用
利用CUDA技術,配合適當的軟體(例如MediaCoder、Freemake Video Converter),就可以利用顯示核心進行高清视频編碼加速。视频解碼方面,同樣可以利用CUDA技術實現。此前,NVIDIA的顯示核心本身已集成PureVideo單元。可是,實現相關加速功能的一個微軟API-DXVA,偶爾會有加速失效問題。所以利用CoreAVC配合CUDA,變相在顯示核心上實現軟體解碼,解決兼容性問題。另外,配合適當的引擎,顯示核心就可以計算光线跟踪。NVIDIA就放出了自家的Optix实时光线跟踪引擎,透過CUDA技術利用GPU計算光线跟踪。
支援的產品
所有基於G80及之後架構的民用與專業顯示卡或運算模組皆支援CUDA技術。
相關條目
- OpenCL
- Vulkan
- AMD ROCm
参考文献
外部連結
*
*
评论 (0)