GPU(Graphics Processing Unit,图形处理器,显卡):最早主要是进行图形处理,现在已经被广泛应用于深度学习,最大的优势就是高效的并行计算能力。
GPU和CPU的区别:
为什么要用GPU:GPU比CPU的优势在于能提供高性能的并行运算,对于大规模同类型数据处理,可以通过算法并行优化来提高运行效率。
CUDA(Compute Unified Device Architecture):2006年,NIVIDIA公司发布了CUDA。CUDA是操作GPU计算的硬件和软件的架构,提供了GPU编程的简易接口。
CUDA支持多语言:C/C++、Python、Fortran等。
CUDA提供两层API接口:CUDA驱动(Driver)和CUDA运行时(Runtime)API。两者调用性能几乎差异不大,但Runtime API使用更加友好。因为Driver API 可以通过直接操作硬件执行一些复杂的功能,Runtime API 对 Driver API 进行了一定的封装,隐藏了部分实现细节。
NVCC:NVCC是CUDA的编译器,位于bin/目录中。
工作流程:CUDA程序是包含主机(Host)代码和设备(Device)代码的统一源代码,Host代码在CPU运行,Device代码在GPU运行。NVCC编译器在编译过程中将两者区分开来,Host代码是用C语言编译的,那么就会交给C编译器进一步编译,以一个CPU进程的方式运行;Device代码用C扩展语言编写,GPU运行的核函数(后面有介绍核函数)代码会由NVCC进一步编译并在GPU上执行。
cu/cuh文件:cu和cuh都是CUDA的后缀格式。cu文件只是含有CUDA代码的cpp文件,cuh相当于CUDA的头文件后缀名。
CUDA文件里面可以包含Host代码和Device代码。
核函数(Kernel function):核函数在GPU上进行并行执行。
函数编写形式:
__global__ void func(argument arg)
{
printf("GPU: Hellow World\n");
}
编写注意事项:
核函数的调用:使用“<<
int main(void){
func<<<1, 1>>>();
return 0;
}
CUDA 的并行计算:通过成千上万个线程(Thread)的并行执行来实现。
线程模型Thread(线程)、Block(线程块)、Grid(网格)三者关系:如下图所示,Thread组成Block,Block组成Grid,Kernel执行的时候启动的是一个Grid。这些线程块的划分都是逻辑上,并不是物理上,划分是为了方便编写代码。
理解核函数调用:这时候其实就能理解前面核函数的“<<
func<<<1, 1>>>();//输出一次:GPU: Hellow World,因为有1个线程。
func<<<2, 4>>>();//输出八次:GPU: Hellow World,因为有8个线程。
多维线程:前面代码部分提到的都是一维的Grid和Block,但Grid和Block可以不止一维甚至最多能到三维。例如在上面线程模型图中,Grid和Block里面的结构都是二维的,从索引的坐标可以看得出来。在执行核函数前,用dim3变量规定一下grid_size和block_size参数的维度就行了。
dim3 grid_size(2,2);//二维
dim3 block_size(16, 16, 2);//三维
func<<<grids, blocks>>>();
线程索引:每个线程都会一个唯一编号标识。下面示范一个二维Grid和二维Block的线程索引计算,可以配合上面的线程模型图理解。
// blockIdx、threadIdx都是unit3类型,表示它们的坐标。
// gridDim、blockDime都是dim3类型,表示网格的大小。
int blockId=blockIdx.x+blockId.y*gridDim.x;// block在grid的ID
int threadId=threadIdx.y*blockDim.x+threadIdx.x;// thread在block的ID
int id=blockId*(blockDim.x*blockDim.y)+threadId;// thread在grid的ID
为什么要有多维线程:主要是为了简化一些三维建模以及二维图像处理的问题。例如二维图片做图像处理,每个核函数要对图片像素进行操作,但是传入的是图片的矩阵,那么核函数还需要计算出自己处理的是那个像素。这个时候因为block结构本身就是二维,可以直接用thread本身的x和y来索引。如果block结构是一维的,还得花很多心思去调整偏移量和步幅来找到要处理的像素。
// 多维线程实现矩阵加法
__global__ void MatAdd(int A[N][N], int B[N][N], int C[N][N]){
int x = threadIdx.x;// 在block中线程的x坐标
int y = threadIdx.y;// 在block中线程的y坐标
C[x][y] = A[x][y] + B[x][y];
}
int main(){
...
int grid_size = 1;
dim3 block_size(N,N);
MatAdd<<<grid_size,block_size>>>(A,B,C);
...
}
了解了CUDA程序运行流程,复杂的CUDA程序会涉及到CPU和GPU之间的数据交互。CPU和GPU之间的数据是不共享的,所以需要分配、拷贝、传递以及释放。
功能 | 函数 |
---|---|
内存分配 | cudaMalloc |
数据传递 | cudaMemcpy |
内存初始化 | cudaMemset |
内存释放 | cudaFree |