CUDA

CUDA（Compute Unified Devices Architecture，统一计算架构）是由英伟达（NVIDIA）所推出的一种软硬体整合技术，是该公司对于GPGPU的正式名称。透过这个技术，使用者可利用NVIDIA的GPU进行图像处理之外的运算，亦是首次可以利用GPU作为C-编译器的开发环境。CUDA 开发套件（CUDA Toolkit ）只能将自家的CUDA C-语言（对OpenCL只有链接的功能），也就是执行于GPU的部分编译成中间语言或是特定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技术有下列几个优点：
显卡的受支持情况.
'*' – 仅限 OEM 产品
详情请参见Nvidia:
应用.
利用CUDA技术，配合适当的软体（例如MediaCoder、Freemake Video Converter），就可以利用显示核心进行高清视频编码加速。视频解码方面，同样可以利用CUDA技术实现。此前，NVIDIA的显示核心本身已集成PureVideo单元。可是，实现相关加速功能的一个微软API－DXVA，偶尔会有加速失效问题。所以利用CoreAVC配合CUDA，变相在显示核心上实现软体解码，解决兼容性问题。另外，配合适当的引擎，显示核心就可以计算光线跟踪。NVIDIA就放出了自家的Optix实时光线跟踪引擎，透过CUDA技术利用GPU计算光线跟踪。
支援的产品.
所有基于G80及之后架构的民用与专业显示卡或运算模组皆支援CUDA技术。
示例.
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
// 利用CUDA函数的错误传回做例外处理
inline void checkCudaErrors( CUresult err)
 if( CUDA_SUCCESS != err) {
 printf("CUDA Driver API error = %04d from file &lt;%s&gt;, 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(&amp;deviceCount));
 if (deviceCount == 0) { // 确定有可用的装置
 fprintf(stderr, "Error: no devices supporting CUDA\n");
 exit(-1);
 // get first CUDA device
 checkCudaErrors(cuDeviceGet(&amp;device, 0)); // 取编号为 0 的装置
 char name[100];
 cuDeviceGetName(name, 100, device); // 印出装置名称
 printf("&gt; Using device 0: %s\n", name);
 checkCudaErrors( cuDeviceTotalMem(&amp;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 &gt; (unsigned long long)4*1024*1024*1024L)?
 "YES" : "NO");
 // 创建 CUDA 内容
 err = cuCtxCreate(&amp;context, 0, device);
 if (err != CUDA_SUCCESS) {
 fprintf(stderr, "* Error initializing the CUDA context.\n");
 cuCtxDetach(context);
 exit(-1);
 // 读取编译好的cubin GPU程式码
 err = cuModuleLoad(&amp;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(&amp;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 &lt; N; ++i) {
 a[i] = i;
 b[i] = N - i;
 initCUDA();
 // 动态分配 GPU 记忆体
 // CUresult cuMemAlloc ( CUdeviceptr* dptr, size_t bytesize )
 checkCudaErrors( cuMemAlloc(&amp;d_a, sizeof(int) * N) ); // 
 checkCudaErrors( cuMemAlloc(&amp;d_b, sizeof(int) * N) );
 checkCudaErrors( cuMemAlloc(&amp;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] = { &amp;d_a, &amp;d_b, &amp;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 &lt; 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
extern "C" __global__ void matSum(int *a, int *b, int *c)
 int tid = blockIdx.x; // thread 的 x 座标
 if (tid &lt; N)
 c[tid] = a[tid] + b[tid]; //每个 thread 做一次加法
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 中间码。
// Device code: 送入GPU执行的部分
__global__ void VecAdd(float* A, float* B, float* C)
 int i = threadIdx.x; // thread 的 x 座标
 if (i &lt; 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 &lt; N; i++){
 h_A[i] = (float)rand() / (float)RAND_MAX;
 h_B[i] = (float)rand() / (float)RAND_MAX;
 // 动态分配位于"device(GPU) 记忆体"的向量
 float* d_A;
 cudaMalloc(&amp;d_A, size); // cudaError_t cudaMalloc ( void** devPtr, size_t size )
 float* d_B;
 cudaMalloc(&amp;d_B, size);
 float* d_C;
 cudaMalloc(&amp;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«&lt;1, N»&gt;(d_A, d_B, d_C);
 // 将算好的向量从 GPU 复制到 CPU
 cudaMemcpy(h_C, d_C, size, cudaMemcpyDeviceToHost);
 // 印出运算结果
 for(i = 0; i &lt; 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 # 矩阵运算
mod = drv.SourceModule("""
__global__ void multiply_them(float *c, float *a, float *b)
 int i = threadIdx.x;
 c[i] = a[i] * b[i];
""")
multiply_them = mod.get_function("multiply_them")
h_a = numpy.random.randn (400).astype(numpy.float32)
h_b = numpy.random.randn (400).astype(numpy.float32)
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
A = CUBLASMatrix(numpy.mat(1,2,3],[4,5,6,numpy.float32))
B = CUBLASMatrix(numpy.mat(2,3],[4,5],[6,7,numpy.float32))
C = A*B
print(C.np_mat())