第一章 指针篇
第二章 CUDA原理篇
第三章 CUDA编译器环境配置篇
第四章 kernel函数基础篇
第五章 kernel索引(index)篇
第六章 kenel矩阵计算实战篇
第七章 kenel实战强化篇
第八章 CUDA内存应用与性能优化篇
第九章 CUDA原子(atomic)实战篇
第十章 CUDA流(stream)实战篇
第十一章 CUDA的NMS算子实战篇
第十二章 YOLO的部署实战篇
第十三章 基于CUDA的YOLO部署实战篇
随着人工智能的发展与人才的内卷,很多企业已将深度学习算法的C++部署能力作为基本技能之一。面对诸多arm相关且资源有限的设备,往往想更好的提速,满足更高时效性,必将更多类似矩阵相关运算交给CUDA处理。同时,面对市场诸多教程与诸多博客岑子不起的教程或高昂教程费用,使读者(特别是小白)容易迷糊,无法快速入手CUDA编程,实现工程化。
因此,我将结合我的工程实战经验,我将在本专栏实现CUDA系列教程,帮助读者(或小白)实现CUDA工程化,掌握CUDA编程能力。学习我的教程专栏,你将绝对能实现CUDA工程化,完全从环境安装到CUDA核函数编程,从核函数到使用相关内存优化,从内存优化到深度学习算子开发(如:nms),从算子优化到模型(以yolo系列为基准)部署。最重要的是,我的教程将简单明了直切主题,CUDA理论与实战实例应用,并附相关代码,可直接上手实战。我的想法是掌握必要CUDA相关理论,去除非必须繁杂理论,实现CUDA算法应用开发,待进一步提高,将进一步理解更高深理论。
第一章到第三章探索指针在cuda函数中的作用与cuda相关原理及环境配置;
第四章初步探索cuda相关函数编写(global、device、__host__等),实现简单入门;
第五章探索不同grid与block配置,如何计算kernel函数的index,以便后续通过index实现各种运算;
第六、七章由浅入深探索核函数矩阵计算,深入探索grid、block与thread索引对kernel函数编写作用与影响,并实战多个应用列子(如:kernel函数实现图像颜色空间转换);
第八章探索cuda内存纹理内存、常量内存、全局内存等分配机制与内存实战应用(附代码),通过不同内存的使用来优化cuda计算性能;
第九章探索cuda原子(atomic)相关操作,并实战应用(如:获得某些自加索引等);
第十章探索cuda流stream相关应用,并给出相关实战列子(如:多流操作等);
第十一到十三章探索基于tensorrt部署yolo算法,我们首先将给出通用tensorrt的yolo算法部署,该部署的前后处理基于C++语言的host端实现,然后给出基于cuda的前后处理的算子核函数编写,最后数据无需在gpu与host间复制操作,实现gpu处理,提升算法性能。
目前,以上为我们的cuda教学全部内容,若后续读者有想了解知识,可留言,我们将根据实际情况,更新相关教学内容。
大神忽略
到此为止,之前章节已让我们大致熟悉cuda相关理论与cuda函数运作原理,特别是kernel函数的线程index计算规则。为此,本节我们将正式使用cuda编写基本矩阵运算,主要涉及矩阵加法与乘法,而矩阵减法与除法与本节介绍的方法相似,将不在作为教程。我们选择四则运算也是因其具有一定实用性和代表性,矩阵加减运算是基于对应像素处理过程,矩阵乘除法运算相对较为复杂,是行与列运算逻辑。更重要的是,我们也通过cuda的基本运算帮助大家熟悉cuda编程的线程index使用的相关逻辑。
矩阵减法实际就是kernel函数符号“+”变成符号“-”,其运算原理基本与矩阵加法一致,基于此,我们只介绍矩阵加法相关规则。同时,我们也会介绍如何使用cuda核函数获得row与col索引。
假设:矩阵 a[m,n] 与 b[m,n]
目的:实现矩阵a与矩阵b逐像素之和
假设m与n及block_size
const int BLOCK_SIZE = 2;
int m = 8; //行
int n = 10; //列
int* a, * b; //分配host内存
cudaMallocHost((void**)&a, sizeof(int) * m * n);
cudaMallocHost((void**)&b, sizeof(int) * m * n);
init_variables(a, b, m, n);//随机初始化变量
变量a与变量b为host分配内存,init_varialbles函数将其指针分配空间初始化对应矩阵数值。
变量a与b是指针,也就是我为什么在第一章写了一篇指针原由,后面基本host端数据给到device端均需指针传递。
int* g_a, * g_b; //分配gpu内存
cudaMalloc((void**)&g_a, sizeof(int) * m * n);
cudaMalloc((void**)&g_b, sizeof(int) * m * n);
cudaMemcpy(g_a, a, sizeof(int) * m * n, cudaMemcpyHostToDevice);
cudaMemcpy(g_b, b, sizeof(int) * m * n, cudaMemcpyHostToDevice);
此时,变量g_a与g_b通过cudaMalloc将其在device端开辟内存空间,在gpu为全局变量,在使用cudaMemcpy方式将host端变量a与变量b的值复制给g_a与g_b变量。至于如何进行,可参考我的专栏第二章cuda原理点击这里。
unsigned int grid_rows = (m + BLOCK_SIZE - 1) / BLOCK_SIZE; //行写给y
unsigned int grid_cols = (n + BLOCK_SIZE - 1) / BLOCK_SIZE; //列写给x
dim3 dimGrid(grid_cols, grid_rows);
dim3 dimBlock(BLOCK_SIZE, BLOCK_SIZE);
设置kernel函数的grid与block配置,这些配置设置影响一维索引index寻找(详情点击这里)。
gpu_matrix_plus_thread << <dimGrid, dimBlock >> > (g_a, g_b, g_c); 方法一
gpu_matrix_plus1 << <dimGrid, dimBlock >> > (g_a, g_b, g_c2, m, n); 方法二
gpu_matrix_plus2 << <dimGrid, dimBlock >> > (g_a, g_b, g_c3, m, n); 方法三
以上为kernel函数调用,即可实现矩阵加减运算。在这里,我使用了三种方式编写kernel函数计算逻辑,与其说三种计算逻辑,更换成三种index方法使用更合理, 因其重要,我将在下面详细介绍。
我们分配grid_rows与grid_cols是按照m行n列方式,且grid与block均为2维度。在每一个线程内部,我们可以获得该线程所在的行和列,进而求出该线程需要计算的数据的位置。为此,比如我们计算行row,我们将忽略列的干扰(可将x看成1),线程row是y引导,可在y方向展平,看做是一维方向,线程y由grid中blockIdx.y索引且在block中为bloackDim.y,此时blockIdx.y * blockDim.y找到对应block上,在加上threadIdx.y可找到对应的线程y,最终row = blockIdx.y * blockDim.y + threadIdx.y。而线程列col与row思路相同,将不在介绍。 代码如下:
__global__ void gpu_matrix_plus1(int* a, int* b, int* c, int m, int n)
{ //方法二:通过row与col的方式计算-->通过变换列给出id
int row = blockIdx.y * blockDim.y + threadIdx.y;
int col = blockIdx.x * blockDim.x + threadIdx.x;
c[row*n + col] = a[row*n + col] + b[row*n + col];
}
其中[rown + col]可理解为线程行row不变,即:假设row为0情况,则col会一直从0到m-1,将遍历了0n的值;假设row为1情况,则col会一直从0到m-1,将遍历了1n+0,1,2,3…的值,相当于第二行遍历;假设row为2情况,则col会一直从0到m-1,将遍历了2n+0,1,2,3…的值,相当于第三行便利;以此类推,row将从0到m-1行取值,逐列遍历,将其赋值给device的c变量中。 另外,我们也可以假设列不变,其思想与上类似,代码如下:
__global__ void gpu_matrix_plus2(int* a, int* b, int* c, int m, int n)
{ //方法三:通过row与col的方式计算-->通过变换行给出id
int row = blockIdx.y * blockDim.y + threadIdx.y;
int col = blockIdx.x * blockDim.x + threadIdx.x;
c[row + col*m] = a[row + col*m] + b[row + col*m]; }
总结:实际并行运算代码可将row与col看成是一个不同取值的变量,这样便于理解与后续代码开发。
我们分配grid_rows与grid_cols是按照m行n列方式,且grid与block均为2维度。在每一个线程内部,我们可以通过上一节索引方法,使用线程方式,实现2个矩阵的加法。因其线程id索引上节有介绍,将不在赘述。 代码如下:
__global__ void gpu_matrix_plus_thread(int* a, int* b, int* c)
{
//方法一:通过id方式计算
//grid为2维度,block为2维度,使用公式id=blocksize * blockid + threadid
int blocksize = blockDim.x*blockDim.y; int blockid = gridDim.x*blockIdx.y+blockIdx.x;
int threadid = blockDim.x*threadIdx.y+threadIdx.x;
int id = blocksize * blockid + threadid; c[id] = a[id] + b[id];
}
比较简单,id实际会不断赋值,与上一个row和col类似。
#include
#include
#include "opencv2/highgui.hpp" //实际上在/usr/include下
#include "opencv2/opencv.hpp"
#include "device_launch_parameters.h"
#include
using namespace cv;
using namespace std;
void Print_2dim(int* ptr, int m,int n) {
std::cout << "result:\n";
for (int i = 0; i < m; i++) {
for (int j = 0; j < n; j++) {
std::cout <<"\t" << ptr[i * n + j];
}
std::cout << "\n";
}
}
__global__ void gpu_matrix_plus_thread(int* a, int* b, int* c)
{
//方法一:通过id方式计算
//grid为2维度,block为2维度,使用公式id=blocksize * blockid + threadid
int blocksize = blockDim.x*blockDim.y;
int blockid = gridDim.x*blockIdx.y+blockIdx.x;
int threadid = blockDim.x*threadIdx.y+threadIdx.x;
int id = blocksize * blockid + threadid;
c[id] = a[id] + b[id];
}
__global__ void gpu_matrix_plus1(int* a, int* b, int* c, int m, int n)
{ //方法二:通过row与col的方式计算-->通过变换列给出id
int row = blockIdx.y * blockDim.y + threadIdx.y;
int col = blockIdx.x * blockDim.x + threadIdx.x;
c[row*n + col] = a[row*n + col] + b[row*n + col];
}
__global__ void gpu_matrix_plus2(int* a, int* b, int* c, int m, int n)
{ //方法三:通过row与col的方式计算-->通过变换行给出id
int row = blockIdx.y * blockDim.y + threadIdx.y;
int col = blockIdx.x * blockDim.x + threadIdx.x;
c[row + col*m] = a[row + col*m] + b[row + col*m];
}
void init_variables(int *a,int *b,int m,int n) {
//初始化变量
std::cout << "value of a:" << endl;
for (int i = 0; i < m; i++) {
for (int j = 0; j < n; j++) {
a[i * n + j] = rand() % 256;
std::cout << "\t" << a[i * n + j];
}
std::cout << "\n";
}
std::cout << "value of b:" << endl;
for (int i = 0; i < m; i++) {
for (int j = 0; j < n; j++) {
b[i * n + j] = rand() % 256;
std::cout << "\t" << b[i * n + j];
}
std::cout << "\n";
}
std::cout << "value of a+b:" << endl;
for (int i = 0; i < m; i++) {
for (int j = 0; j < n; j++) {
std::cout << "\t" << a[i * n + j] + b[i * n + j];
}
std::cout << "\n";
}
}
int kernel_plus()
{
/*
matrix a[m,n], matrix b[m,n]
a[m,n]+b[m,n]=[m,n]
*/
const int BLOCK_SIZE = 2;
int m = 8; //行
int n = 10; //列
int* a, * b;
//分配host内存
cudaMallocHost((void**)&a, sizeof(int) * m * n);
cudaMallocHost((void**)&b, sizeof(int) * m * n);
init_variables(a, b, m, n);//随机初始化变量
int* g_a, * g_b;
//分配gpu内存
cudaMalloc((void**)&g_a, sizeof(int) * m * n);
cudaMalloc((void**)&g_b, sizeof(int) * m * n);
cudaMemcpy(g_a, a, sizeof(int) * m * n, cudaMemcpyHostToDevice);
cudaMemcpy(g_b, b, sizeof(int) * m * n, cudaMemcpyHostToDevice);
unsigned int grid_rows = (m + BLOCK_SIZE - 1) / BLOCK_SIZE; //行写给y
unsigned int grid_cols = (n + BLOCK_SIZE - 1) / BLOCK_SIZE; //列写给x
dim3 dimGrid(grid_cols, grid_rows);
dim3 dimBlock(BLOCK_SIZE, BLOCK_SIZE);
std::cout << "gridDIM.x:" <<grid_cols<< "\tgridDIM.y:" << grid_rows<< endl;
std::cout << "blockDIM.x:" << BLOCK_SIZE << "\tblockDIM.y:" << BLOCK_SIZE << endl;
int* c1, * g_c;
cudaMalloc((void**)&g_c, sizeof(int) * m * n);
cudaMallocHost((void**)&c1, sizeof(int) * m * n);
gpu_matrix_plus_thread << <dimGrid, dimBlock >> > (g_a, g_b, g_c);
cudaMemcpy(c1, g_c, sizeof(int) * m * n, cudaMemcpyDeviceToHost);
Print_2dim(c1, m,n);
int* c2, * g_c2;
cudaMallocHost((void**)&c2, sizeof(int) * m * n);
cudaMalloc((void**)&g_c2, sizeof(int) * m * n);
gpu_matrix_plus1 << <dimGrid, dimBlock >> > (g_a, g_b, g_c2, m, n);
cudaMemcpy(c2, g_c2, sizeof(int) * m * n, cudaMemcpyDeviceToHost); //将device端转host端
Print_2dim(c2, m, n);
int* c3, * g_c3;
cudaMallocHost((void**)&c3, sizeof(int) * m * n);
cudaMalloc((void**)&g_c3, sizeof(int) * m * n);
gpu_matrix_plus2 << <dimGrid, dimBlock >> > (g_a, g_b, g_c3, m, n);
cudaMemcpy(c3, g_c3, sizeof(int) * m * n, cudaMemcpyDeviceToHost); //将device端转host端
Print_2dim(c3, m, n);
//释放内存
cudaFree(g_a);
cudaFree(g_b);
cudaFree(g_c);
cudaFreeHost(a);
cudaFreeHost(b);
cudaFreeHost(c1);
return 0;
}
以上为矩阵加减法相关实战应用,我个人认觉得矩阵加减法较为简单,本节重点是知道kernel函数编码的几种索引方式寻找。