CUDA

CUDA（Compute Unified Devices Architectured，统一计算架构）是由英伟达NVIDIA所推出的一種軟硬體整合技術，是該公司對於GPGPU的正式名稱。透過這個技術，使用者可利用NVIDIA的GPU进行图像处理之外的運算，亦是首次可以利用GPU作為C-编译器的开发环境。CUDA 開發套件（CUDA Toolkit ）只能將自家的CUDA C-語言（對OpenCL只有链接的功能），也就是執行於GPU的部分編譯成PTX（英语：Parallel Thread Execution）中間語言或是特定NVIDIA GPU架構的機器碼（NVIDIA 官方稱為 "device code"）；而執行於中央处理器部分的C / C++程式碼（NVIDIA 官方稱為 "host code"）仍依賴於外部的編譯器，如Microsoft Windows下需要Microsoft Visual Studio；Linux下則主要依賴於GCC。 GPU不僅用於進行圖形渲染，而且用於物理運算（物理效果如碎片、煙、火、流體）如PhysX和Bullet。進一步的，GPU可以用在計算生物學與密碼學等領域的非圖形應用上。在NVIDIA收購AGEIA後，NVIDIA取得相關的物理加速技術，即是PhysX物理引擎。配合CUDA技術，顯示卡可以模擬成一顆PhysX物理加速晶片。目前，全系列的GeForce 8顯示核心都支援CUDA。而NVIDIA亦不會再推出任何的物理加速卡，顯示卡將會取代相關產品。 而使用CUDA技術，GPU可以用來進行通用處理（不僅僅是圖形）；這種方法被稱為GPGPU。與CPU不同的是，GPU以較慢速度並行大量執行緒，而非快速執行單一執行緒。以GeForce 8800 GTX为例，其核心擁有128个内处理器。利用CUDA技術，就可以將那些内处理器做為线程处理器，以解决数据密集的计算。而各個内处理器能够交换、同步和共享数据。GeForce 8800 GTX的运算能力可达到520GFlops，如果建設SLI系统，就可以达到1TFlops。 目前，已有軟體廠商利用CUDA技術，研發出Adobe Premiere Pro的插件。通過插件，使用者就可以利用顯示核心去加速H.264/MPEG-4 AVC的编码速度。速度是單純利用CPU作軟體加速的7倍左右。 雖然CUDA底層是以C/C++為主，並以使用“NVCC”——NVIDIA基於LLVM的C/C++編譯器介面來進行編譯，但工程師也可以使用編譯器指令（如OpenACC）以及多種程式設計語言擴展對CUDA平臺進行操作。如Fortran工程師可以使用“CUDA Fortran”，或PGI公司的PGI CUDA Fortran 編譯器進行編譯。除了此之外CUDA平臺還支援其它計算介面，如Khronos Group的OpenCL，Microsoft的DirectCompute，以及C++AMP。也可以透過其他語言如 Python，Perl，Java，Ruby，Lua，Haskell，MATLAB，IDL及Mathematica 的介面間接調用CUDA。 CUDA最初的CUDA軟體發展包（SDK）於2007年2月15日公佈，同時支持Microsoft Windows和Linux。而後在第二版中加入對Mac OS X的支持(但於CUDA Toolkit 10.2起放棄對macOS的支援)，取代2008年2月14日發佈的測試版。所有G8x系列及以後的NVIDIA GPUs皆支援CUDA技術，包括GeForce，Quadro和Tesla系列。CUDA與大多數標準作業系統相容。Nvidia聲明：根據二進位相容性，基於G8x系列開發的程式無需修改即可在未來所有的Nvidia顯卡上運行。 在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個執行緒使用相同的執行路徑，則程式分支不會顯著影響效果；在處理本質上不同的任務時，单指令流多数据流執行模型將成為一個瓶頸（如在光線追蹤演算法中遍歷一個空間分割的資料結構） 只有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舍入模式（舍位與舍入到最近的偶數），這些在每條指令的基礎上指定，而非控制字碼；除法/平方根的精度比單精確度略低。 '*' – 仅限 OEM 产品 详情请参见Nvidia（页面存档备份，存于互联网档案馆）: 利用CUDA技術，配合適當的軟體（例如MediaCoder、Freemake Video Converter），就可以利用顯示核心進行高清视频編碼加速。视频解碼方面，同樣可以利用CUDA技術實現。此前，NVIDIA的顯示核心本身已集成PureVideo單元。可是，實現相關加速功能的一個微軟API－DXVA，偶爾會有加速失效問題。所以利用CoreAVC配合CUDA，變相在顯示核心上實現軟體解碼，解決兼容性問題。另外，配合適當的引擎，顯示核心就可以計算光线跟踪。NVIDIA就放出了自家的Optix实时光线跟踪引擎，透過CUDA技術利用GPU計算光线跟踪。 所有基於G80及之後架構的民用與專業顯示卡或運算模組皆支援CUDA技術。 下面將示範以最底層的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 <stdio.h> #include <stdlib.h> #include <cuda.h> #include <builtin_types.h> // Driver api 的型態定義 #define N 1024 //列向量長度 // 利用CUDA函數的錯誤傳回做例外處理 inline void checkCudaErrors( CUresult err) { if( CUDA_SUCCESS != err) { printf("CUDA Driver API error = %04d from file <%s>, 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)4*1024*1024*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 < N; ++i) { a[i] = i; b[i] = N - i; } initCUDA(); // 動態分配 GPU 記憶體 // CUresult cuMemAlloc ( CUdeviceptr* dptr, size_t bytesize ) checkCudaErrors( cuMemAlloc(&d_a, sizeof(int) * N) ); // checkCudaErrors( cuMemAlloc(&d_b, sizeof(int) * N) ); checkCudaErrors( cuMemAlloc(&d_c, sizeof(int) * N) ); // 將列向量傳入裝置 // CUresult cuMemcpyHtoD ( CUdeviceptr dstDevice, const void* srcHost, size_t ByteCount ) checkCudaErrors( cuMemcpyHtoD(d_a, a, sizeof(int) * N) ); checkCudaErrors( cuMemcpyHtoD(d_b, b, sizeof(int) * N) ); void *args[3] = { &d_a, &d_b, &d_c }; // 包裝放入GPU 函數的引數 // 運行 GPU 函數 // CUresult cuLaunchKernel ( CUfunction f, unsigned int gridDimX, unsigned int gridDimY, unsigned int gridDimZ, // unsigned int blockDimX, unsigned int blockDimY, unsigned int blockDimZ, // unsigned int sharedMemBytes, CUstream hStream, void** kernelParams, void** extra ) checkCudaErrors( cuLaunchKernel(function, N, 1, 1, // Nx1x1 blocks 1, 1, 1, // 1x1x1 threads 0, 0, args, 0) ); // 將運算結果送回主記憶體 // CUresult cuMemcpyDtoH ( void* dstHost, CUdeviceptr srcDevice, size_t ByteCount ) checkCudaErrors( cuMemcpyDtoH(c, d_c, sizeof(int) * N) ); // 將 CPU 和 GPU 運算結果做對照 for (int i = 0; i < N; ++i) { if (c[i] != a[i] + b[i]) printf("* Error at array position %d: Expected %d, Got %d\n", i, a[i]+b[i], c[i]); } // 釋放 GPU 記憶體 // CUresult cuMemFree ( CUdeviceptr dptr ) checkCudaErrors( cuMemFree(d_a) ); checkCudaErrors( cuMemFree(d_b) ); checkCudaErrors( cuMemFree(d_c) ); cuCtxDetach(context); // 釋放 CUDA 內容物件 return 0; } 而以下是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 < N) c[tid] = a[tid] + b[tid]; //每個 thread 做一次加法 } 下列的範例是以相較於 Driver API 來說比較簡便的 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 <stdio.h> #include <stdlib.h> // 引用動態分配 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 < N){ C[i] = A[i] + B[i]; // 每個 thread 作一次加法 } } // Host code: 送入CPU執行的部分 int main() { size_t size = N * sizeof(float); // 向量的實際大小，以位元組(bytes)為單位 int i; // 迴圈計數 // 動態分配位於"host(CPU) 記憶體" 的向量 float* h_A = (float*)malloc(size); float* h_B = (float*)malloc(size); float* h_C = (float*)malloc(size); // 隨機初始化輸入向量 for(i = 0; i < N; i++){ h_A[i] = (float)rand() / (float)RAND_MAX; h_B[i] = (float)rand() / (float)RAND_MAX; } // 動態分配位於"device(GPU) 記憶體"的向量 float* d_A; cudaMalloc(&d_A, size); // cudaError_t cudaMalloc ( void** devPtr, size_t size ) float* d_B; cudaMalloc(&d_B, size); float* d_C; cudaMalloc(&d_C, size); // 將向量從 CPU 複製到 GPU cudaMemcpy(d_A, h_A, size, cudaMemcpyHostToDevice); cudaMemcpy(d_B, h_B, size, cudaMemcpyHostToDevice); // 將 device code 送入 GPU 並執行，執行時一個 Grid 只有一個 block ，一個 block 有 N 個 thread VecAdd<<<1, N>>>(d_A, d_B, d_C); // 將算好的向量從 GPU 複製到 CPU cudaMemcpy(h_C, d_C, size, cudaMemcpyDeviceToHost); // 印出運算結果 for(i = 0; i < N; i++){ printf("%f ", h_C[i]); } // 釋放 GPU 記憶體 cudaFree(d_A); cudaFree(d_B); cudaFree(d_C); // 釋放 CPU 記憶體 free(h_A); free(h_B); free(h_C); } 以下以pycuda （页面存档备份，存于互联网档案馆），間接調用 CUDA 做列向量的阿達瑪乘積(也就是元素間乘積而非內積) import pycuda.driver as drv #CUDA drivers import pycuda.autoinit # 自動初始化CUDA import numpy # 矩陣運算 # 讀取並編譯GPU執行的程式碼，以 CUDA-C 寫成 mod = drv.SourceModule(""" __global__ void multiply_them(float *c, float *a, float *b) { int i = threadIdx.x; c[i] = a[i] * b[i]; } """) # 獲取GPU程式碼中的 multiply_them 函數 multiply_them = mod.get_function("multiply_them") #生成兩個常態隨機分布的浮點數ndarray，shape 為(400, ) h_a = numpy.random.randn (400).astype(numpy.float32) h_b = numpy.random.randn (400).astype(numpy.float32) # 儲存結果的 h_c 列向量，其 shape 和向量 a 相同但值為零 h_c = numpy.zeros_like (a) ''' 執行GPU的函數: 注意這裡h_a, h_b 和 h_c 是在CPU記憶體的 python 變數 以下按照原來 GPU 程式碼的區域變數順序，指定哪些是從CPU傳入GPU (drv.In)；哪些是從GPU傳入CPU (drv.Out)， 簡單來說，自 GPU 傳出到 h_c； h_a 傳入 GPU；h_b 傳入 GPU。 (400,1,1) 代表一個block裡有的thread數量為 400 x 1 x 1 ''' multiply_them( drv.Out(h_c), drv.In(h_a), drv.In(h_b), block=(400,1,1)) #印出結果 print (d_c) 也可以用pycublas 間接調用 CUDA ，來計算矩陣乘法 import numpy from pycublas import CUBLASMatrix # 以 numpy 定義矩陣並傳入 CUBLASMatrix A = CUBLASMatrix(numpy.mat([[1,2,3],[4,5,6]],numpy.float32)) B = CUBLASMatrix(numpy.mat([[2,3],[4,5],[6,7]],numpy.float32)) # 以 CUBLASMatrix 做矩陣乘法 C = A*B # 將運算結果轉回 numpy 並印出 print(C.np_mat()) OpenCL Vulkan CUDA是Compute Unified Device Architecture（统一计算架构）的简称. [2010-02-08]. （原始内容存档于2010-02-13）.  Nvcc compiler and OpenCL kernel. NVIDIA Developer Forums. 2021-05-25 [2022-06-22]. （原始内容存档于2022-06-27） （中文（中国大陆））.  NVIDIA CUDA Compiler Driver NVCC. docs.nvidia.com. [2022-06-22]. （原始内容存档于2022-08-11） （美国英语）.  CUDA Installation Guide for Microsoft Windows. docs.nvidia.com. [2022-06-23]. （原始内容存档于2022-08-10） （美国英语）.  NVIDIA CUDA Installation Guide for Linux. docs.nvidia.com. [2022-06-23]. （原始内容存档于2022-09-04） （美国英语）.  全系列GeForce 8显卡将获得PhysX物理支持. [2008-05-02]. （原始内容存档于2008-04-17）.  小熊在线-宁道奇. 双剑合璧：CPU+GPU异构计算完全解析. 小熊在线. [2013-06-18]. （原始内容存档于2013-06-24）.  CUDA C++ Programming Guide. docs.nvidia.com. [2022-06-23]. （原始内容存档于2021-05-03） （美国英语）.  CUDA Toolkit Archive. NVIDIA Developer. [2020-07-16]. （原始内容存档于2020-07-09）.  NVIDIA CUDA Programming Guide. Version 1.0 (PDF). 2007-06-23 [2020-07-16]. （原始内容存档 (PDF)于2018-04-17）.  NVIDIA CUDA Programming Guide. Version 2.1 (PDF). 2008-12-08 [2020-07-16]. （原始内容存档 (PDF)于2020-04-06）.  NVIDIA CUDA Programming Guide. Version 2.2 (PDF). 2009-04-02 [2020-07-16]. （原始内容存档 (PDF)于2019-03-06）.  NVIDIA CUDA Programming Guide. Version 2.2.1 (PDF). 2009-05-26 [2020-07-16]. （原始内容存档 (PDF)于2019-09-25）.  NVIDIA CUDA Programming Guide. Version 2.3.1 (PDF). 2009-08-26 [2020-07-16]. （原始内容存档 (PDF)于2012-02-18）.  NVIDIA CUDA Programming Guide. Version 3.0 (PDF). 2010-02-20 [2020-07-16]. （原始内容存档 (PDF)于2020-07-29）.  NVIDIA CUDA C Programming Guide. Version 3.1.1 (PDF). 2010-07-21 [2020-07-16]. （原始内容存档 (PDF)于2020-06-01）.  NVIDIA CUDA C Programming Guide. Version 3.2 (PDF). 2010-11-09 [2020-07-16]. （原始内容存档 (PDF)于2020-03-02）.  NVIDIA Quadro NVS 420 Specs. TechPowerUp GPU Database. [2020-07-16]. （原始内容存档于2020-07-29）.  Larabel, Michael. NVIDIA Rolls Out Tegra X2 GPU Support In Nouveau. Phoronix. 2017-03-29 [2017-08-08]. （原始内容存档于2017-08-09）.  Nvidia Xavier Specs （页面存档备份，存于互联网档案馆） on TechPowerUp (preliminary) CUDA转码软件. [2009-09-14]. （原始内容存档于2009-09-18）.  另类CUDA高清方案. [2009-09-14]. （原始内容存档于2009-11-29）.  NVIDIA Optix实时光线追踪DEMO. [2009-09-14]. （原始内容存档于2009-09-12）.  NVIDIA官方支援CUDA技術的產品列表. [2010-04-19]. （原始内容存档于2010-02-18）.  官方网站 周秉誼. GPU高效能運算環境—CUDA與GPU Cluster介紹. 台灣大學計算機及資訊網路中心. [2010-12-05]. （原始内容存档于2010-05-16） （中文（繁體））.