TBD,自用,图床To fix…
一般CUDA文件的后缀是.cu,当然用一般的.cpp也可以,只不过要在其头文件中加入CUDA调用API所在的库;
<<<4, 4>>> 使用4 * 4 共16个线程;
cudaDeviceSynchronize()是CPU和GPU的同步代码;
1 2 3 4 5 6 7 8 9 10 11 12 13 14 #include <stdio.h> __global__ void hello_from_gpu() { printf("Hello World from the GPU\n"); } int main(void) { hello_from_gpu<<<4, 4>>>(); cudaDeviceSynchronize(); return 0; }
上述代码用如下的指令编译:
1 2 nvcc test.cu -o test ./test
编写的kernel在一个4 * 4的线程块上运行,所以会打印出16个Hello world from GPU
一般GPU使用率高得时候,一般显存使用率也是很高的;
但是显存存满了,不一定GPU在高速工作;
即线程没有处于运算状态,所以GPU使用率不高;但是线程依旧需要被GPU显存存储;
核函数 GPU看成CPU的外设,需要CPU发送控制指令;
既要编写CPU上的控制代码,又要编写GPU上的调用代码;
核函数在GPU 上进行并行 执行:编写并行执行的函数,并行执行是GPU的特点;编写人员不需要自己考虑多线程;
CUDA和C++编写和函数是类似的
限定词
1 2 3 __global__ 核函数必须要有限定词global,这个global前后必须都是双下划线; 核函数返回值必须是void
global和void两个位置是可以互换的
核函数的编写规则:
(1)核函数只能访问GPU的内存;CPU和GPU异构,两者各自有各自的内存,两者的内存访问是通过PCIe进行;如果需要在GPU和CPU之间做内存交互,则需要使用运行时API做内存交互;
编写核函数不能访问主机内存!
(2)核函数不能使用变长参数;
(3)核函数不能使用静态变量;
(4)核函数不能使用函数指针;
(5)核函数具有异步性:和CPU、GPU的异构架构有关,CPU只是启动了GPU,但是CPU主机无法控制GPU的执行;CPU不会等待GPU执行完毕,所以我们需要显式地调用同步函数,同步主机CPU与设备GPU的工作进程;
有些线程的执行也是需要进行同步的;
带核函数的CUDA编写流程:
1 2 3 4 5 6 7 int main (void ) { 主机代码:一般用来对GPU做配置、数据处理; 核函数调用:并行调用核函数,对数据进行加速处理; 主机代码:一般会讲GPU运算处理后的数据回传给主机,还会对GPU、CPU进行内存释放的工作; return 0 ; }
注意:核函数不支持c++的iostream类!
进行终端打印,要用printf来进行终端显示;
1 2 3 4 5 6 7 8 9 10 11 12 13 14 #include <stdio.h> __global__ void hello_from_gpu () { printf ("Hello World from the GPU!\n" ); } int main (void ) { hello_from_gpu<<<1 , 1 >>>(); cudaDeviceSynchronize(); return 0 ; }
注意到,核函数调用是和C++调用有区别的,有个<<<1,1>>> 尖括号内的参数是用来设置执行核函数的线程的;
需要明确执行的线程模型;
尖括号内第一个参数是执行的线程块的数量、第二个参数是线程块内线程的个数;
问题:GPU没有执行完,但是CPU已经执行完了,所以需要同步。这样CPU就不会出现,在GPU执行完之前就顺序执行完控制流了;
主机与设备之间的同步是CPU和GPU的一个特点;
CUDA线程模型 一、线程模型结构
当一个核函数在主机host(CPU)中启动的时候,他所有线程构成了一个网格grid;
(1)grid 网格
一个网格grid中又包含了若干个线程块block
(2)block 线程块
一个线程块block中又包含了若干个线程;
线程是GPU编程中的最小单位;
线程分块是GPU逻辑上的划分,物理上线程并不分块;
核函数配置线程:<<<grid_size, block_size >>>
grid_size用来配置,该核函数所包含的线程块Block的数目;block_size用来配置,一个线程块所包含的线程的数目和结构;
线程数目是允许远远高于计算核心数目的;
最大允许的线程块大小:1024;最大允许的网格大小:2^31 - 1(针对一维网格的情况,也即最大允许2^31 - 1个线程块)
总的线程数至少等于计算核心数才能充分利用计算资源;实际上,只有大于计算核心数时才能更充分利用好计算资源;
这样能使的GPU内存访问和计算同时进行,以节省计算时间;
使用CUDA的核心:让GPU的计算核心一直运作;
GPU和CPU访问之间的耗时很长,甚至大于GPU的运行时间;
一维线程模型:
每个线程在核函数中都有唯一的身份标识;
每一个线程的唯一标识由<<<grid_size, block_size >>>确定;两者保存在内建变量 build-in variable中;目前考虑一维的情况:
(1)gridDim.x:该变量的数值等于执行配置中变量grid_size的值;
(2)blockDim.x:该变量的数值等于执行配置中变量block_size的值;
线程索引保存成内建变量:build-in variable
(1)blockIdx.x:该变量指定一个线程在一个网格中的线程块索引值,范围为0~gridDim.x - 1
(2)threadIdx.x:该变量指定一个线程在一个线程块中的线程索引值,范围为0~blockDim.x - 1
举例:<<<2, 4>>>
我们的grid中有2个线程块,一个线程块中有4个线程;
线程块取值为0和1;
线程取值为0,1,2,3;
通过threadIdx,可以在核函数中找到其身份标识;
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 #include <stdio.h> __global__ void hello_from_gpu () { const int bid = blockIdx.x; const int tid = threadIdx.x; const int id = threadIdx.x + blockIdx.x * blockDim.x; printf ("Hello World from block %d and thread %d, global id %d\n" , bid, tid, id); } int main (void ) { hello_from_gpu<<<2 , 4 >>>(); cudaDeviceSynchronize (); return 0 ; }
由上面的例子,我们利用blockIdx.x(线程块编号);和threadIdx.x(线程块内部该线程的编号);和我们计算的线程对应唯一的id,然后接着输出;
推广到多维线程:
在核函数以外,不能用剩余的变量;
gridDim和blockDim没有指定的维度,默认为1;
注意:
在三维及更高维的线程块处理的时候,优先计数的线程块是按照,先进行x维度的计数;再进行y维度的计数;最后进行z维度的计数;
(所以,高维计算id的时候是:(以3维线程块(x,y,z)为例)
tid = threadIdx.z * blockDim.x * blockDim.y + threadIdx.y * blockDim.x + threadIdx;
线程全局索引计算方式
考虑一维网格、一维线程块;二维网格、二维线程块;三维网格、三维线程块三种不同的情况,如何计算线程全局的索引计算方式。
一维网格 一维线程块 计算对应的id:
1 2 3 4 5 int blockid = blockId.x;int threadid = threadId.x;int id = blockid * blockDim.x + threadid;
二维网格 二维线程块 计算对应的id:
1 2 3 4 5 int blockid = blockId.x + blockId.y * gridDim.x; int threadid = threadId.y * blockDim.x + threadId.x; int id = blockid * (blockDim.x * blockDim.y) + threadid;
三维网格 三维线程块
1 2 3 4 5 6 7 int blockid = blockId.x * gridDim.y * gridDim.z + blockId.y * gridDim.z + blockId.z;int threadid = threadId.z * (blockDim.x * blockDim.y) + threadId.y * blockDim.x + threadId.x;int id = blockid * (blockDim.x * blockDim.y * blockDim.z) + threadid;
这里算的id,就是blockid个线程块(线程块本身本部包含的线程数目为blockDim.x * blockDim.y * blockDim.z;
nvcc编译流程与GPU计算能力 可能GPU架构不同,nvcc编译出来的代码存在移植问题。
nvcc编译流程:
在指定计算能力的时候,GPU的真实计算能力一定是要大于虚拟计算能力的;
PTX: Parallel Thread Execution 是CUDA平台为基于GPU的通用计算而定义的虚拟机和指令集,PTX的作用是为GPU的演化提供稳定的指令集架构支持,同时上层C/C++解耦,提供稳定支持;
(也就是为了移植性设计的一个IR)
nvcc编译命令总是使用两个体系结构:一个是虚拟的中间体系结构,另一个是实际的GPU体系结构; 虚拟架构更像是对应用所需的GPU功能的声明;
虚拟架构应该尽可能选择低,以尽量适配更多实际GPU;
真实架构应该尽可能选择高,以充分发挥GPU性能;
PTX文档:https://docs.nvidia.com/cuda/parallel-thread-execution/index.html
不同GPU之间的二进制兼容性不同,不能相互使用;
每一种GPU的指令集、指令编码与其他GPU是不同的;
次版本号高的GPU程序可以运行在次版本号较低的GPU程序;
注意,GPU版本和GPU架构是两个东西。如伏特Volta和图灵Turing两代GPU架构,其主版本号都是7。但是后者从X.Y=7.5以后就不认为是Volta架构的了;
GPU的计算能力和性能并不是正比的;
表明其计算能力的衡量单元:
浮点数运算峰值(还包含双精度浮点数、单精度浮点数)
而GPU性能还与显存容量、显存带宽等相关;
CUDA程序兼容性问题 CUDA程序可移植问题
1 2 nvcc helloworld.cu -o helloworld -arch=compute_61
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 #include <stdio.h> __global__ void hello_from_gpu () { int blockid = blockIdx.x; int threadid = threadIdx.x; int id = blockid * blockDim.x + threadid; printf ("Hello from GPU! block id: %d, thread id: %d, and the global thread index: %d\n" , blockid, threadid, id); } int main () { printf ("Hello from CPU!\n" ); hello_from_gpu<<<2 , 2 >>>(); cudaDeviceSynchronize (); return 0 ; }
指定高于设备实际计算能力的GPU虚拟架构时候,GPU无法被正确调用!
虚拟架构尽量指定低一些,以兼容更多的GPU架构和版本;
PTX指令转化为二进制cubin代码与具体的GPU架构有关;
指定真实架构能力如果大于虚拟架构能力,则可能调用不了对应GPU;
指定多个版本的架构,生成指定多个GPU版本的编译文件(跨GPU版本的二进制文件)
称为胖二进制文件 fatbinary
(1)执行上述指令必须要求CUDA版本支持7.0计算能力,否则会报错;
(2)过多指定计算能力,会增加编译时间和可执行文件的大小;
CUDA程序例子
运行时API:
获取GPU设备数量:
1 2 int iDeviceCount = 0; cudaGetDeviceCount(&iDeviceCount);
会返回计算能力>=2.0的设备数量;
注意,运行时API文档中有host,也有device;则说明该API既可以在hostCPU中调用,又可以在Device设备GPU中调用;
设置GPU执行时使用的设备
1 2 int iDev = 0 ;cudaSetDevice (iDev);
只能在host主机上设置,来说明主机应该选择哪一个编号的GPU来执行设备代码;
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 #include <stdio.h> int main (void ) { int iDeviceCount = 0 ; cudaError_t error = cudaGetDeviceCount (&iDeviceCount); if (error != cudaSuccess || iDeviceCount == 0 ) { printf ("No CUDA compatable GPU found!\n" ); exit (-1 ); } else { printf ("The count of GPUs is %d. \n" , iDeviceCount); } int iDev = 0 ; error = cudaSetDevice (iDev); if (error != cudaSuccess) { printf ("faile to set GPU 0 for computing!" ); exit (-1 ); } else { printf ("set GPU 0 for computing.\n" ); } return 0 ; }
CUDA中的内存管理: CUDA通过内存分配、数据传输、内存初始化、内存释放来进行内存管理;
内存分配:在GPU中开辟一块地址,分配一段内存;
数据传输:host和device之间传输数据;
内存初始化:内存开辟好了后对齐内容处死花花;
内存释放:类比c的free
例如:标准C语言内存管理函数对应cudaMalloc、cudaMemcpy、cudaMemset、cudaFree
cudaMalloc内存分配函数
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 extern void *malloc (unsigned int num_bytes) ;float *fp = (float *)malloc (bytes_to_malloc);函数定义为: __host____device__ cudaError_t cudaMalloc (void **devPtr, size_t size) ;注,这里因为cudaMalloc的API返回值是cudaError_t,所以这里要传入一个双重指针,来将分配好的空间的地址绑定到我们上层的指针中; 理解:其实就是一个传址调用,只不过传址的对象是指针了。执行完上述API后,直接将我们分配好的地址设置到指针存储内容中去了。 而朴素的malloc是返回地址,在上层界面赋值给指针; 用法: float *fpDevice_A;cudaMalloc ((float **)(&fpDevice_A), numbytes);
cudaMemcpy 数据拷贝函数
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 code: memcpy ((void *)d, (void *)s, nBytes); API定义为: __host__ cudaError_t cudaMemcpy (void *dst, const void *src, size_t count, cudaMemcpyKind kind) ;数据拷贝的几个kind: cudaMemcpyHostToHost 主机->主机 cudaMemcpyHostToDevice 主机->设备 cudaMemcpyDeviceToHost 设备->主机 cudaMemcpyDeviceToDevice 设备->设备 cudaMemcpyDefault 默认 默认方式只允许在支持统一虚拟寻址的系统上使用; 该API应该只能在CPU上调用运行?因为无论如何数据拷贝都需要CPU调用PCIe总线来转发或运输;
内存初始化
1 2 3 4 5 6 7 8 9 API具体定义: __host__ cudaError_t cudaMemset (void *devPtr, int value, size_t count) devPtr:指向设备内存的指针 value:设置每字节内存内容的变量 count:设置的bytes数
内存释放
1 2 3 4 5 6 7 free (p_hostA);cudaFree (p_DeviceA)__host____device__ cudaError_t cudaFree (void *devPtr) ;devPtr是设备内存指针;
下面用一个cuda矩阵加法的实例来展示上述运行时API之间的简单交互:
tools文件夹下创建一个common.cuh头文件,其内容如下所示:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 #pragma once #include <stdlib.h> #include <stdio.h> void setGPU () { int iDeviceCount = 0 ; cudaEror_t error = cudaGetDeviceCount (&iDeviceCount); if (error != cudaSuccess || iDeviceCount == 0 ) { printf ("No CUDA campatable GPU found!\n" ); exit (-1 ); } else { printf ("The count of GPU is %d\n" , iDeviceCount); } int iDev = 0 ; error = cudaSetDevice (iDev); if (error != cudaSuccess) { printf ("fail to set GPU 0 for computing!.\n" ); exit (-1 ); } else { printf ("Set GPU %d, for Computing!\n" , iDev); } }
然后主代码文件为:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 #include "./tools/common.cuh" #include <stdio.h> __global__ void addFromGPU (float *A, float *B, float *C, const int N) { const int bid = blockIdx.x; const int tid = threadIdx.x; const int id = bid * blockDim.x + tid; C[id] = A[id] + B[id]; } void initialData (float *addr, int elemCount) { for (int i = 0 ; i < elemCount ; i++) { addr[i] = (float )(rand () % 0xff ) / 10.f ; } return ; } int main (void ) { setGPU (); int iElemCount = 512 ; size_t stBytesCount = iElemCount * sizeof (float ); float *fpHost_A, *fpHost_B, *fpHost_C; fpHost_A = (float *)malloc (stBytesCount); fpHost_B = (float *)malloc (stBytesCount); fpHost_C = (float *)malloc (stBytesCount); if (fpHost_A != NULL && fpHost_B != NULL && fpHost_C != NULL ) { memset (fpHost_A, 0 , stBytesCount); memset (fpHost_B, 0 , stBytesCount); memset (fpHost_C, 0 , stBytesCount); } else { printf ("Fail to allocate host memory!\n" ); exit (-1 ); } float *fpDevice_A, *fpDevice_B, *fpDevice_C; cudaMalloc ((float **)(&fpDevice_A), stBytesCount); cudaMalloc ((float **)(&fpDevice_B), stBytesCount); cudaMalloc ((float **)(&fpDevice_C), stBytesCount); if (fpDevice_A != NULL && fpDevice_B != NULL && fpDevice_C != NULL ) { cudaMemset (fpDevice_A, 0 , stBytesCount); cudaMemset (fpDevice_B, 0 , stBytesCount); cudaMemset (fpDevice_C, 0 , stBytesCount); } else { printf ("Failed to allocate memory!\n" ); free (fpHost_A); free (fpHost_B); free (fpHost_C); exit (-1 ); } srand (666 ); initData (fpHost_A, iElemCount); initData (fpHost_B, iElemCount); cudaMemcpy (fpDevice_A, fpHost_A, stBytesCount, cudaMemcpyHostToDevice); cudaMemcpy (fpDevice_B, fpHost_B, stBytesCount, cudaMemcpyHostToDevice); cudaMemcpy (fpDevice_C, fpHost_C, stBytesCount, cudaMemcpyHostToDevice); dim3 block (32 ) ; dim3 grid (iElemCount / 32 ) ; addFromGPU<<<grid, block>>>(fpDevice_A, fpDevice_B, fpDevice_C, iElementCount); cudaDeviceSynchronize (); cudaMemcpy (fpHost_C, fpDevice_C, stBytesCount, cudaMemcpyDeviceToHost); for (int i = 0 ; i < 10 ; i++) { printf ("idx=%2d\tmatrix_A:%.2f\tmatrix_B:%.2f\tresult=%.2f\n" , i+1 , fpHost_A[i], fpHost_B[i], fpHost_C[i]); } free (fpHost_A); free (fpHost_B); free (fpHost_C); cudaFree (fpDevice_A); cudaFree (fpDevice_B); cudaFree (fpDevice_C); cudaDeviceReset (); return 0 ; }
上面是一个cuda编写GPU多线程的实例,本质是将一个一维数组的每一维都看作一个线程,对应元素的位置都同步加相应的内容;
cudaMemcpy函数,在进行cudaMemcpyDeviceToHost,即将数据从设备传到主机的过程中,这个函数会自己进行一次同步;
自定义设备函数
设备函数 device function
(1)定义只能执行在GPU设备上的函数称为设备函数;
(2)设备函数只能被核函数或其他设备函数调用;
(3)设备函数用__device__修饰
核函数 kernel function
(1)用__global__修饰的函数称为核函数,一般由主机调用,在设备中执行;
(2)__global__修饰符既不能和__host__同时使用,也不可与__device__同时使用;
主机函数 host function
(1)主机端的普通C++函数可用__host__修饰
(2)对于主机端的函数,__host__修饰符可省略
(3)可以用__host__和__device__同时修饰一个函数减少冗余代码,编译器会针对主机和设备分别编译该函数;
注意到,我们上面计算线程块的数目(也即网格包含的线程块)时是直接用512除以32来完成的,如果不能整除,如总线程数为513,那么就需要用额外的线程块来确保能够整除。如修改为grid((iElemCount + block.x - 1) / 32),那么
此时,注意我们需要修改核函数中的访存范 围。例如对于线程号大于514的,就不在数组范围内了,如果还是在核函数中做add的话就会有越界操作;所以需要仔细思考这里的细节,对于大于513编号的访问线程直接return
CUDA错误检查 运行时API错误代码
cudaSuccess = 0,运行时API调用成功;
剩余是一系列错误代码;
编写错误检查函数
1 2 3 4 5 6 7 8 9 cudaGetErrorName __host____device__ const char *cudaGetErrorName (cudaError_t error) 返回一个字符串char *类型指针,返回错误代码名词; __host____device__ const char *cudaGetErrorString (cudaError_t error) 返回一个含有错误代码描述信息的字符串;
整体封装一个错误检查函数,如下所示:
1 2 3 4 5 6 7 8 9 10 cudaError_t ErrorCheck (cudaError_t error_code, const char *filename, int lineNumber) { if (error_code != cudaSuccess) { printf ("CUDA error:\r\ncode=%d, name=%s, description=%s\r\nfile=%s, line=%d\r\n" , error_code, cudaGetErrorName (error_code), cudaGetErrorString (error_code), filename, lineNumber); } return error_code; }
参数filename和lineNumber可以分别用__FILE__或__LINE__来进行处理;
错误函数返回运行时API调用的错误代码;
一个纠错的例子代码:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 #include <stdio.h> #include "./tools/common.cuh" int main (void ) { float *fpHost_A; fpHost_A = (float *)malloc (4 ); memset (fpHost_A, 0 , 4 ); float *fpDevice_A; cudaError_t error = ErrorCheck (cudaMalloc ((float **)(&fpDevice_A), 4 ), __FILE__, __LINE__); cudaMemset (fpDevice_A, 0 ,4 ); ErrorCheck (cudaMemcpy (fpDevice_A, fpHost_A, 4 , cudaMemcpyDeviceToHost), __FILE__, __LINE__); free (fpHost_A); ErrorCheck (cudaFree (fpDevice_A), __FILE__, __LINE__); ErrorCheck (cudaDeviceReset (), __FILE__, __LINE__); return 0 ; }
检查核函数,错误检测函数是不能捕捉调用核函数相关错误的,因为核函数的返回值必定是void;
我们捕捉调用核函数的方式是通过两个CUDA的API:
1 2 3 4 5 ErrorCheck (cudaGetLastError (), __FILE__, __LINE__);ErrorCheck (cudaDeviceSynchronize (), __FILE__, __LINE__);
例子:
1 2 3 4 5 dim3 block (2048 ) ; dim3 grid ((iElemCount + block.x - 1 ) / 2048 ) ;addFromGPU<<<grid, block>>>(fpDevice_A, fpDevice_B, fpDevice_C, iElemCount); ErrorCheck (cudaGetLastError (), __FILE__, __LINE__);
CUDA计时 分析性能等使用
CUDA可以使用事件计时来对程序执行性能进行度量,使用CUDA event事件计时方式。CUDA事件可以为主机代码、设备代码计时。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 cudaEvent_t start, stop; ErrorCheck (cudaEventCreate (&start), __FILE__, __LINE__);ErrorCheck (cudaEventCreate (&stop), __FILE__, __LINE__);ErrorCheck (cudaEventRecord (start), __FILE__, __LINE__);cudaEventQuery (start); ErrorCheck (cudaEventRecord (stop), __FILE__, __LINE__);ErrorCheck (cudaEventSynronize (stop), __FILE__, __LINE__);float elapsed_time;ErrorCheck (cudaEventElapsedTime (&elapsed_time, start, stop), __FILE__, __LINE__);printf ("Time = %g ms.\n" , elapsed_time);ErrorCheck (cudaEventDestroy (start), __FILE__, __LINE__);ErrorCheck (cudaEventDestroy (stop), __FILE__, __LINE__);
计时的一些细节:往往计算效率的时候,需要进行多次采样计算,往往可以抛弃第一次调用核函数的时间。因为第一次调用核函数会耗费不少时间!
nvprof性能解析:
nvprof是一个命令行程序,可以分析nv各个API使用的效率和耗时等
使用方式:nvprof ./ex3 这里的ex3为可执行程序;
它会显示GPU各个行为(具体的,调用不同API所用的最短时间、最大时间和平均时间等操作;
运行时API查询GPU信息 1 2 3 4 cudaDeviceProp prop; ErrorCheck (cudaGetDeviceProperties (&prop, device_id), __FILE__, __LINE__);
具体的内容查看手册即可;
组织线程模型 处理多维数组,建立多维数组与线程之间组织关系。
构建二维网格和二维线程块
想要发挥GPU中多线程的优点,就要让每个线程处理不同的计算;
要分配每个线程处理不同的数据,避免多个线程访问或处理同一个数据,也要避免多个线程胡乱访问内存;
多线程不能按照规定的顺序访问内存,就可能造成数据的混乱,程序break;
所以当前线程
1 2 ix = threadIdx.x + blockIdx.x * blockDim.x iy = threadIdx.y + blockIdx.y * blockDim.y
线程与二维矩阵的映射关系:
1 idx = iy * nx + ix (nx为二维矩阵x方向上的维度)
用二维网格和二维线程来计算二维矩阵的加法:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 #include <stdio.h> #include "./tools/common.cuh" cudaError_t ErrorCheck (cudaError_t error_code, const char *filename, int lineNumber) { if (error_code != cudaSuccess) { printf ("CUDA error:\r\ncode=%d, name=%s, description=%s\r\nfile=%s, line=%d\r\n" , error_code, cudaGetErrorName (error_code), cudaGetErrorString (error_code), filename, lineNumber); } return error_code; } __global__ void addMatrix (int *A, int *B, int *C, const int nx, const int ny) { int ix = threadIdx.x + blockIdx.x * blockDim.x; int iy = threadIdx.y + blockIdx.y * blockDim.y; unsigned int id = iy * nx + ix; if ((ix < nx) && (iy < ny)) { C[id] = A[id] + B[id]; } } int main (void ) { setGPU (); int nx = 16 ; int ny = 8 ; int nxy = nx * ny; size_t stBytesCount = nxy * sizeof (int ); int *ipHost_A, *ipHost_B, *ipHost_C; ipHost_A = (int *)malloc (stBytesCount); ipHost_B = (int *)malloc (stBytesCount); ipHost_C = (int *)malloc (stBytesCount); if (ipHost_A != NULL && ipHost_B != NULL && ipHost_C != NULL ) { for (int i = 0 ; i < nxy; i++) { ipHost_A[i] = i; ipHost_B[i] = i + 1 ; } memset (ipHost_C, 0 , stBytesCount); } else { printf ("Fail to allocate host memory!\n" ); exit (-1 ); } int *ipDevice_A, *ipDevice_B, *ipDevice_C; ErrorCheck (cudaMalloc ((int **)&ipDevice_A, stBytesCount), __FILE__, __LINE__); ErrorCheck (cudaMalloc ((int **)&ipDevice_B, stBytesCount), __FILE__, __LINE__); ErrorCheck (cudaMalloc ((int **)&ipDevice_C, stBytesCount), __FILE__, __LINE__); if (ipDevice_A != NULL && ipDevice_B != NULL && ipDevice_C != NULL ) { ErrorCheck (cudaMemcpy (ipDevice_A, ipHost_A, stBytesCount, cudaMemcpyHostToDevice), __FILE__, __LINE__); ErrorCheck (cudaMemcpy (ipDevice_B, ipHost_B, stBytesCount, cudaMemcpyHostToDevice), __FILE__, __LINE__); ErrorCheck (cudaMemcpy (ipDevice_C, ipHost_C, stBytesCount, cudaMemcpyHostToDevice), __FILE__, __LINE__); } else { printf ("Fail to allocate memory\n" ); free (ipHost_A); free (ipHost_B); free (ipHost_C); exit (1 ); } dim3 block (4 , 4 ) ; dim3 grid ((nx + block.x -1 ) / block.x, (ny + block.y - 1 ) / block.y) ; printf ("Thread config:grid:<%d, %d>, block:<%d, %d>\n" , grid.x, grid.y, block.x, block.y); addMatrix<<<grid, block>>>(ipDevice_A, ipDevice_B, ipDevice_C, nx, ny); ErrorCheck (cudaMemcpy (ipHost_C, ipDevice_C, stBytesCount, cudaMemcpyDeviceToHost), __FILE__, __LINE__); for (int i = 0 ; i < 10 ; i++) { printf ("id=%d, matrix_A=%d, matrix_B=%d, result=%d\n" , i + 1 ,ipHost_A[i], ipHost_B[i], ipHost_C[i]); } for (int j = 0 ; j < ny ; j++) { int *aj = ipHost_A + j * nx; int *bj = ipHost_B + j * nx; int *cj = ipHost_C + j * nx; for (int i = 0 ; i < nx ; i++) { int aji = *(aj + i); int bji = *(bj + i); int cji = *(cj + i); printf ("a[%d][%d](%d) + b[%d][%d](%d) = c[%d][%d](%d)\n" , j,i,aji, j,i, bji, j,i, cji); } } free (ipHost_A); free (ipHost_B); free (ipHost_C); ErrorCheck (cudaFree (ipDevice_A), __FILE__, __LINE__); ErrorCheck (cudaFree (ipDevice_B), __FILE__, __LINE__); ErrorCheck (cudaFree (ipDevice_C), __FILE__, __LINE__); ErrorCheck (cudaDeviceReset (), __FILE__, __LINE__); return 0 ; }
二维网格一维线程块:
一维网格一维线程块:
每一个线程块对应的是二维矩阵一列的计算,需要设计循环,依次计算每一列的情况。这种因为效率低,略;
CUDA编写的算子和Pytorch编译 https://zhuanlan.zhihu.com/p/645330027
https://godweiyang.com/2021/03/18/torch-cpp-cuda/#toc-heading-5
https://godweiyang.com/2021/03/21/torch-cpp-cuda-2/
https://godweiyang.com/2021/03/24/torch-cpp-cuda-3/