热门问题
时间线
聊天
视角

CUDA

NVIDIA所推出的一種軟硬體整合技術 来自维基百科,自由的百科全书

CUDA
Remove ads

CUDACompute Unified Devices Architecture,統一計算架構[1])是由英偉達(NVIDIA)所推出的一種硬體整合技術,是該公司對於GPGPU的正式名稱。透過這個技術,使用者可利用NVIDIA的GPU進行圖像處理之外的運算,亦是首次可以利用GPU作為C-編譯器的開發環境。CUDA 開發套件(CUDA Toolkit )只能將自家的CUDA C-語言(對OpenCL只有鏈接的功能[2]),也就是執行於GPU的部分編譯成PTX英語Parallel Thread Execution中間語言或是特定NVIDIA GPU架構的機器碼(NVIDIA 官方稱為 "device code");而執行於中央處理器部分的C / C++程式碼(NVIDIA 官方稱為 "host code")仍依賴於外部的編譯器,如Microsoft Windows下需要Microsoft Visual StudioLinux下則主要依賴於GCC[3][4][5]

事实速览 開發者, 首次發布 ...
Remove ads

簡介

Thumb
CUDA的資料傳輸
1.將主記憶體的資料傳入GPU記憶體
2. CPU指令驅動GPU
3. GPU平行運算
4. 將運算結果自GPU記憶體傳回主記憶體

GPU不僅用於進行圖形渲染,而且用於物理運算(物理效果如碎片、煙、火、流體)如PhysX和Bullet。進一步的,GPU可以用在計算生物學與密碼學等領域的非圖形應用上。在NVIDIA收購AGEIA後,NVIDIA取得相關的物理加速技術,即是PhysX物理引擎。配合CUDA技術,顯示卡可以模擬成一顆PhysX物理加速晶片[6]。目前,全系列的GeForce 8顯示核心都支援CUDA。而NVIDIA亦不會再推出任何的物理加速卡,顯示卡將會取代相關產品。

而使用CUDA技術,GPU可以用來進行通用處理(不僅僅是圖形);這種方法被稱為GPGPU。與CPU不同的是,GPU以較慢速度並行大量執行緒,而非快速執行單一執行緒。以GeForce 8800 GTX為例,其核心擁有128個內處理器。利用CUDA技術,就可以將那些內處理器做為線程處理器,以解決數據密集的計算。而各個內處理器能夠交換、同步和共享數據。GeForce 8800 GTX的運算能力可達到520GFlops,如果建設SLI系統,就可以達到1TFlops。[7]

目前,已有軟體廠商利用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顯卡上運行。

Remove ads

優點

在GPUs(GPGPU)上使用圖形APIs進行傳統通用計算,CUDA技術有下列幾個優點:[8]

  • 分散讀取——代碼可以從記憶體的任意位址讀取
  • 統一虛擬記憶體(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捨入模式(舍位與捨入到最近的偶數),這些在每條指令的基礎上指定,而非控制字碼;除法/平方根的精度比單精確度略低。

顯卡的受支持情況

更多信息 CUDA 版本, 支持的計算能力 ...
更多信息 計算能力(版本), 微架構 ...

'*' – 僅限 OEM 產品

詳情請參見Nvidia頁面存檔備份,存於網際網路檔案館):

Remove ads

應用

利用CUDA技術,配合適當的軟體(例如MediaCoder[21]、Freemake Video Converter),就可以利用顯示核心進行高清視頻編碼加速。視頻解碼方面,同樣可以利用CUDA技術實現。此前,NVIDIA的顯示核心本身已集成PureVideo單元。可是,實現相關加速功能的一個微軟API-DXVA,偶爾會有加速失效問題。所以利用CoreAVC配合CUDA,變相在顯示核心上實現軟體解碼,解決兼容性問題[22]。另外,配合適當的引擎,顯示核心就可以計算光線跟蹤。NVIDIA就放出了自家的Optix實時光線跟蹤引擎,透過CUDA技術利用GPU計算光線跟蹤[23]

支援的產品

所有基於G80及之後架構的民用與專業顯示卡或運算模組皆支援CUDA技術[24]

示例

CUDA Driver API

下面將示範以最底層的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 做一次加法
}
Remove ads

CUDA Runtime API

下列的範例是以相較於 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);
}

Python

以下以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())
Remove ads

相關條目

參考文獻

外部連結

Loading related searches...

Wikiwand - on

Seamless Wikipedia browsing. On steroids.

Remove ads