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

21 篇文章 15 订阅 ¥49.90 ¥99.00

前言

学习我的教程专栏,你将绝对能实现CUDA工程化,实现环境安装、index计算、kernel核函数编程、内存优化与steam性能优化、原子操作、nms的cuda算子、yolov5的cuda部署等内容,并开源教程源码。

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

专栏概括

1、cuda教程目录

第一章 指针篇–>点击这里
第二章 CUDA原理篇–>点击这里
第三章 CUDA编译器环境配置篇–>点击这里
第四章 kernel函数基础篇–>点击这里
第五章 kernel索引(index)篇–>点击这里
第六章 kenel矩阵计算实战篇–>点击这里-上篇点击这里-下篇

第七章 kenel实战强化篇–>点击这里
第八章 CUDA内存应用与性能优化篇–>点击这里-上篇点击这里-中篇点击这里-下篇
第九章 CUDA原子(atomic)实战篇–>点击这里
第十章 CUDA流(stream)实战篇–>点击这里
第十一章 CUDA的NMS算子实战篇–>点击这里-上篇点击这里-下篇
第十二章 YOLO的部署实战篇–>点击这里-上篇点击这里-下篇

第十三章 基于CUDA的YOLO部署实战篇–>点击这里

2、cuda教程背景

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

3、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教学全部内容,若后续读者有想了解知识,可留言,我们将根据实际情况,更新相关教学内容。

大神忽略

源码链接地址点击这里
yolov5部署代码链接点击这里
yolov5的cuda部署代码链接:文中链接源码

基于我的代码实测,cuda部署yolov5加速10倍,只想说cuda太香了!!!

一、矩阵乘除法

矩阵除法实际就是kernel函数符号“*”变成符号“/”,其运算原理基本与矩阵乘法一致,基于此,我们只介绍矩阵乘法相关规则。

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

二、矩阵乘除法编码步骤

1、初始化与内存分配

①、条件设置

假设m与n及block_size

    const int  BLOCK_SIZE = 2;
    int m = 8;    //行
    int n = 4;   //中间变量
    int k = 10;    //列

②、host分配内存与赋值

	int* a, * b;
    // 初始化 a与b
    cudaMallocHost((void**)&a, sizeof(int) * m * n);
    cudaMallocHost((void**)&b, sizeof(int) * n * k);
    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() % 6;
            std::cout << "\t" << a[i * n + j];
        }
        std::cout << "\n";
    }

    std::cout << "value of b:" << endl;
    for (int i = 0; i < n; i++) {
        for (int j = 0; j < k; j++) {
            b[i * k + j] = rand() % 10;
            std::cout << "\t" << b[i * k + j];
        }
        std::cout << "\n";
    }

变量a与变量b为host分配内存,for循环为a与b始化赋值。
变量a与b是指针,也就是我为什么在第一章写了一篇指针原由,后面基本host端数据给到device端均需指针传递。

③、device分配内存与赋值


    cudaMalloc((void**)&g_a, sizeof(int) * m * n);
    cudaMalloc((void**)&g_b, sizeof(int) * n * k);
    cudaMemcpy(g_a, a, sizeof(int) * m * n, cudaMemcpyHostToDevice);
    cudaMemcpy(g_b, b, sizeof(int) * k * 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 = (k + 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_mult << <dimGrid, dimBlock >> > (g_a,  g_b, g_c,  m,  n,  k);	方法一 
gpu_matrix_multiply_thread << <dimGrid, dimBlock >> > (g_a, g_b, g_c2, m,n, k);	方法二

以上为kernel函数调用,即可实现矩阵乘除运算。在这里,我使用了二种方式编写kernel函数计算逻辑,与其说二种计算逻辑,更换成二种index方法使用更合理, 因其重要,我将在下面详细介绍。

三、矩阵乘除法多种kernel实现方法

1、kernel使用row与col作为index

我们分配grid_rows与grid_cols是按照m行n列方式,且grid与block均为2维度。在每一个线程内部,我们也可通过矩阵加法原理,获得该线程所在的行和列。同时,矩阵乘法是行与列的相乘。然而,a是m行n列,b是n行k列,a*b实际为a的每行n个数与b的每列n个数一 一相乘的和。
以下代码的grid与block设置为m行k列设计的,为此row与col对应为m行与k列。
代码如下:

__global__ void gpu_matrix_mult(int* a, int* b, int* c, int m, int n, int k)
{
    int row = blockIdx.y * blockDim.y + threadIdx.y; // 行线程 y
    int col = blockIdx.x * blockDim.x + threadIdx.x; // 列线程 x
    int sum = 0;
    if (col < k && row < m) {
        for (int i = 0; i < n; i++) {
            sum += a[row * n + i] * b[i * k + col];  //看出row与col不动的方式计算
        }
        c[row * k + col] = sum;
    }
}

其中if (col < k && row < m)表示行和列不能超过此条件,a[row * n + i]表示行row不变,i从0便利到n-1的方法,为每行遍历;b[i * k + col]表示行col不变,i从0遍历到n-1的方法,为每列遍历;

int sum = 0;
   if (col < k && row < m) {
       for (int i = 0; i < n; i++) {
           sum += a[row * n + i] * b[i * k + col];  //看出row与col不动的方式计算
       }
       c[row * k + col] = sum;
   }

以上代码,可理解为每次都需要执行,或理解成有一个for循环外包即可。

2、kernel核使用线程id

我们分配grid_rows与grid_cols是按照m行n列方式,且grid与block均为2维度。在每一个线程内部,我们可以通过上一节索引方法,使用线程方式,实现2个矩阵的乘法。因其线程id索引上节有介绍,也将不在赘述。以下变量与上设置一样,仅仅将id转为行和列的思想计算。
代码如下:

_global__ void gpu_matrix_multiply_thread(int* a, int* b, int* c,int m, int n, int k)
{
   // [m*n]* [n*k]<---m n
   //方法一:通过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;

   int row = id / k;
   int col = id % k;
   int sum = 0;

   for (int i = 0; i < n; i++) {
       sum += a[row*n + i] * b[i * k +col ];
   }
   c[row * k + col] = sum;

}

至于解释,我们将不在过多说明,其理解与上相似,仅将线程id转为row与col的逻辑处理。

四、矩阵乘除法完整代码

#include <iostream>
#include <time.h>
#include "opencv2/highgui.hpp"  //实际上在/usr/include下
#include "opencv2/opencv.hpp"
#include "device_launch_parameters.h"
#include <cuda_runtime_api.h>
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_mult(int* a, int* b, int* c, int m, int n, int k)
{
    int row = blockIdx.y * blockDim.y + threadIdx.y; // 行线程 y
    int col = blockIdx.x * blockDim.x + threadIdx.x; // 列线程 x
    int sum = 0;
    if (col < k && row < m) {
        for (int i = 0; i < n; i++) {
            sum += a[row * n + i] * b[i * k + col];  //看出row与col不动的方式计算
        }
        c[row * k + col] = sum;
    }
}


__global__ void gpu_matrix_multiply_thread(int* a, int* b, int* c,int m, int n, int k)
{
    // [m*n]* [n*k]<---m n
    //方法一:通过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;

    int row = id / k;
    int col = id % k;
    int sum = 0;

    for (int i = 0; i < n; i++) {
        sum += a[row*n + i] * b[i * k +col ];

    }
    c[row * k + col] = sum;


}


int kernel_multiply()
{   
    /*
    matrix a[m,n], matrix b[n,k]
    a[m,n]*b[n,k]=[m,k]
    */

    const int  BLOCK_SIZE = 2;
    int m = 8;    //行
    int n = 4;   //中间变量
    int k = 10;    //列
    int* a, * b;
   
    // 初始化 a与b
    cudaMallocHost((void**)&a, sizeof(int) * m * n);
    cudaMallocHost((void**)&b, sizeof(int) * n * k);
    
    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() % 6;
            std::cout << "\t" << a[i * n + j];
        }
        std::cout << "\n";
    }

    std::cout << "value of b:" << endl;
    for (int i = 0; i < n; i++) {
        for (int j = 0; j < k; j++) {
            b[i * k + j] = rand() % 10;
            std::cout << "\t" << b[i * k + j];
        }
        std::cout << "\n";
    }
    
    //a*b相乘
    std::cout << "value of a*b:" << endl;
    for (int i = 0; i < m; i++) {
        for (int j = 0; j < k; j++) {
            int tmp = 0;
            for (int h = 0; h < n; h++) {
                tmp += a[i * n + h] * b[h * k + j];
            }
            //c[i * k + j] = tmp;
            std::cout << "\t" << tmp;
        }
        std::cout << "\n";
    }




    int* g_a, * g_b;
  
    cudaMalloc((void**)&g_a, sizeof(int) * m * n);
    cudaMalloc((void**)&g_b, sizeof(int) * n * k);
    cudaMemcpy(g_a, a, sizeof(int) * m * n, cudaMemcpyHostToDevice);
    cudaMemcpy(g_b, b, sizeof(int) * k * n, cudaMemcpyHostToDevice);
    unsigned int grid_rows = (m + BLOCK_SIZE - 1) / BLOCK_SIZE;  //行写给y
    unsigned int grid_cols = (k + 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;

    // 使用row与col计算
    int* c1, * g_c;
    cudaMalloc((void**)&g_c, sizeof(int) * m * k);
    cudaMallocHost((void**)&c1, sizeof(int) * m * k);
    gpu_matrix_mult << <dimGrid, dimBlock >> > (g_a,  g_b, g_c,  m,  n,  k);
    cudaMemcpy(c1, g_c, sizeof(int) * m * k, cudaMemcpyDeviceToHost);
    Print_2dim(c1, m, k);
    cudaFree(g_c);
    cudaFreeHost(c1);
    
    //使用id计算
    int* c2, * g_c2;
    cudaMalloc((void**)&g_c2, sizeof(int) * m * k);
    cudaMallocHost((void**)&c2, sizeof(int) * m * k);
    gpu_matrix_multiply_thread << <dimGrid, dimBlock >> > (g_a, g_b, g_c2, m,n, k);
    cudaMemcpy(c2, g_c2, sizeof(int) * m * k, cudaMemcpyDeviceToHost);
    Print_2dim(c2, m, k);
    cudaFree(g_c2);
    cudaFreeHost(c2);

    //释放内存
    cudaFree(g_a);
    cudaFree(g_b);
    cudaFreeHost(a);
    cudaFreeHost(b);
    

    return 0;
}

五、总结

以上为矩阵乘除法相关实战应用,我个人认觉得矩阵乘除法比矩阵加减法较为复杂些,主要是掌握如何使用kernel编写矩阵a的行与矩阵b的列逐元素相乘,本节重点依然掌握矩阵kernel函数编码的几种索引方式与矩阵行与列逐元素相乘。

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

tangjunjun-owen

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值