第六章 kenel矩阵计算实战篇(上篇)

cuda教程目录

第一章 指针篇
第二章 CUDA原理篇
第三章 CUDA编译器环境配置篇
第四章 kernel函数基础篇
第五章 kernel索引(index)篇
第六章 kenel矩阵计算实战篇
第七章 kenel实战强化篇
第八章 CUDA内存应用与性能优化篇
第九章 CUDA原子(atomic)实战篇
第十章 CUDA流(stream)实战篇
第十一章 CUDA的NMS算子实战篇
第十二章 YOLO的部署实战篇
第十三章 基于CUDA的YOLO部署实战篇

cuda教程背景

随着人工智能的发展与人才的内卷,很多企业已将深度学习算法的C++部署能力作为基本技能之一。面对诸多arm相关且资源有限的设备,往往想更好的提速,满足更高时效性,必将更多类似矩阵相关运算交给CUDA处理。同时,面对市场诸多教程与诸多博客岑子不起的教程或高昂教程费用,使读者(特别是小白)容易迷糊,无法快速入手CUDA编程,实现工程化。
因此,我将结合我的工程实战经验,我将在本专栏实现CUDA系列教程,帮助读者(或小白)实现CUDA工程化,掌握CUDA编程能力。学习我的教程专栏,你将绝对能实现CUDA工程化,完全从环境安装到CUDA核函数编程,从核函数到使用相关内存优化,从内存优化到深度学习算子开发(如:nms),从算子优化到模型(以yolo系列为基准)部署。最重要的是,我的教程将简单明了直切主题,CUDA理论与实战实例应用,并附相关代码,可直接上手实战。我的想法是掌握必要CUDA相关理论,去除非必须繁杂理论,实现CUDA算法应用开发,待进一步提高,将进一步理解更高深理论。

cuda教程内容

第一章到第三章探索指针在cuda函数中的作用与cuda相关原理及环境配置;

第四章初步探索cuda相关函数编写(globaldevice、__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教程背景
  • cuda教程内容
    • 源码链接地址[点击这里](https://github.com/tangjunjun966/cuda-tutorial-master)
  • 前言
  • 一、矩阵加减法
  • 二、矩阵加减法编码步骤
    • 1、初始化与内存分配
      • ①、条件设置
      • ②、host分配内存与赋值
      • ③、device分配内存与赋值
      • ④、设置kernel相关配置
      • ⑤kernel函数调用
  • 三、矩阵加减法多种kernel实现方法
    • 1、kernel使用row与col作为index
    • 2、kernel核使用线程index
  • 四、矩阵加减法完整代码
  • 五、总结


前言

到此为止,之前章节已让我们大致熟悉cuda相关理论与cuda函数运作原理,特别是kernel函数的线程index计算规则。为此,本节我们将正式使用cuda编写基本矩阵运算,主要涉及矩阵加法与乘法,而矩阵减法与除法与本节介绍的方法相似,将不在作为教程。我们选择四则运算也是因其具有一定实用性和代表性,矩阵加减运算是基于对应像素处理过程,矩阵乘除法运算相对较为复杂,是行与列运算逻辑。更重要的是,我们也通过cuda的基本运算帮助大家熟悉cuda编程的线程index使用的相关逻辑。


一、矩阵加减法

矩阵减法实际就是kernel函数符号“+”变成符号“-”,其运算原理基本与矩阵加法一致,基于此,我们只介绍矩阵加法相关规则。同时,我们也会介绍如何使用cuda核函数获得row与col索引。

假设:矩阵 a[m,n] 与 b[m,n]
目的:实现矩阵a与矩阵b逐像素之和

二、矩阵加减法编码步骤

1、初始化与内存分配

①、条件设置

假设m与n及block_size

const int  BLOCK_SIZE = 2;     
int m = 8;    //行     
int n = 10;   //列 

②、host分配内存与赋值

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端均需指针传递。

③、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原理点击这里。

④、设置kernel相关配置

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寻找(详情点击这里)。

⑤kernel函数调用

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方法使用更合理, 因其重要,我将在下面详细介绍。

三、矩阵加减法多种kernel实现方法

1、kernel使用row与col作为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看成是一个不同取值的变量,这样便于理解与后续代码开发。

2、kernel核使用线程index

我们分配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函数编码的几种索引方式寻找。

你可能感兴趣的:(CUDA,c++,边缘计算,人工智能)