CUDA:矩阵乘法的实现(Share Memory)

这篇具有很好参考价值的文章主要介绍了CUDA:矩阵乘法的实现(Share Memory)。希望对大家有所帮助。如果存在错误或未考虑完全的地方,请大家不吝赐教,您也可以点击"举报违法"按钮提交疑问。

本文参加2022CUDA on Platform线上训练营学习笔记

一、矩阵乘法(Matrix Multiply)基础

CUDA:矩阵乘法的实现(Share Memory)
CUDA:矩阵乘法的实现(Share Memory)
矩阵相乘是线性代数的基础,简单来解释就是A矩阵的行与B矩阵所在列相乘之和的结果,CPU端的代码可以采用模拟思想非常好编写,相信聪明的你一定熟练掌握了矩阵相乘,这里就不做多的介绍了

二、矩阵乘法的CPU端实现

void cpu_matrix_mult(int* h_a, int* h_b, int* h_result, int m, int n, int k) {
    for (int i = 0; i < m; ++i)
    {
        for (int j = 0; j < k; ++j)
        {
            int tmp = 0.0;
            for (int h = 0; h < n; ++h)
            {
                tmp += h_a[i * n + h] * h_b[h * k + j];
            }
            h_result[i * k + j] = tmp;
        }
    }
}

CPU端的代码主要采用了模拟思想,外两层循环,遍历了在结果矩阵中的位置,第三层循环,遍历了A矩阵的行与B矩阵的列,进行相乘求和,可以思考的是如果A矩阵与B矩阵的规模足够大,将是一个巨大的计算任务,在CPU端的串行执行,将面临巨大的压力。因此我们可以集合并行编程思想,使用CUDA代码来实现。话不多说,开始GPU端代码的编写

三、矩阵乘法的GPU端实现(Share Memory)

考虑到矩阵的规模足够大,本篇的代码在实现过程中,直接考虑了GPU Share Memory不足的情况,采用了移动tile的形式来解决这一问题。
在前几篇CUDA学习介绍中,我们已经成功实现出了没有使用Share Memory的版本,代码如下:

__global__ void gpu_matrix_mult(int* d_a, int* d_b, int* d_c, int m, int n, int k) {
    int row = threadIdx.y + blockDim.y * blockIdx.y;
    int col = threadIdx.x + blockDim.x * blockIdx.x;
    if (row < m && col < k) {
        for (int i = 0; i < n; i++) {
            d_c[row * k + col] += d_a[row * n + i] * d_b[col + i * k];
        }
    }
}

d_a,d_c,d_b都是存在于全局内存中的数组,该代码在执行的过程中会多次访问Global Memory,由于Global Memorylatency比较高,所以大大降低了代码执行的效率,所以我们引入了Share Memory来进行优化,主要采用了Share Memory的一次写入多次读取来降低执行过程中在数据传输方面的损耗。先使用__share__ 标识符来定义两个存在于共享内存中的数组

	__shared__ int smem_m[BLOCK_SIZE][BLOCK_SIZE];
    __shared__ int smem_n[BLOCK_SIZE][BLOCK_SIZE];

本篇中采用的是正方形的tile 大小为当前Block的大小。在核函数的代码当中,每个线程将扮演两个角色:1.将Global Memory中的数据赋值到Share Memory中,2.计算当前的矩阵中的值。我们的代码使用了移动tile 的方法拷贝时将一个一个tile边移动边拷贝边计算,具体如下图所示,smem_m将向x轴的正方向移动,smem_n将向y轴正方向移动,步长为当前tile的边长,移动过程中涉及到了一个sub概念,即当前tile移动的步数,tile的总步数为n / BLOCK_SIZE 向下取整即可
CUDA:矩阵乘法的实现(Share Memory)

for (int stride = 0; stride <= n / BLOCK_SIZE; stride++) {
        int idm = stride * BLOCK_SIZE + row * n + threadIdx.x;
        if (row < m && BLOCK_SIZE * stride + threadIdx.x < n) {
            smem_m[threadIdx.y][threadIdx.x] = a[idm];
        }
        else {
            smem_m[threadIdx.y][threadIdx.x] = 0;
        }
        int idn = stride * BLOCK_SIZE * k + col + threadIdx.y * k;
        if (col < k && BLOCK_SIZE * stride + threadIdx.y < n) {
            smem_n[threadIdx.y][threadIdx.x] = b[idn];
        }
        else {
            smem_n[threadIdx.y][threadIdx.x] = 0;
        }
        __syncthreads();
        for (int i = 0; i < BLOCK_SIZE; i++) {
            tmp += smem_m[threadIdx.y][i] * smem_n[i][threadIdx.x];
        }
        __syncthreads();
    }

由于当前线程在A中拷贝的位置与在B中拷贝的位置不同,所以要分开计算idmidn,确保所有线程都参加集体活动完毕,我们采用__syncthreads()函数来同步当前block中的线程,同步完成之后将当前tile涉及到的计算步骤算出结果存在临时的tmp中,在tile执行完所有移动时,将tmp赋值到我们的global Memory中。

	if (row < m && col < k)
    {
        c[row * k + col] = tmp;
    }

需要特别注意的是,条件的判断,当前线程各自有着各自的计算任务,也有着当前的集体任务(将数据从Global Memory中拷贝到Share Memory当中),不能因为各自的计算任务的无效而不参加集体任务。
通过上述分析,我们获得了完整的使用了Share Memory优化版本GPU代码

__global__ void gpu_matrix_mult(int* a, int* b, int* c, int m, int n, int k)
{
    __shared__ int smem_m[BLOCK_SIZE][BLOCK_SIZE];
    __shared__ int smem_n[BLOCK_SIZE][BLOCK_SIZE];
    int row = blockDim.y * blockIdx.y + threadIdx.y;
    int col = blockDim.x * blockIdx.x + threadIdx.x;
    int tmp = 0;
    for (int stride = 0; stride <= n / BLOCK_SIZE; stride++) {
        int idm = stride * BLOCK_SIZE + row * n + threadIdx.x;
        if (row < m && BLOCK_SIZE * stride + threadIdx.x < n) {
            smem_m[threadIdx.y][threadIdx.x] = a[idm];
        }
        else {
            smem_m[threadIdx.y][threadIdx.x] = 0;
        }
        int idn = stride * BLOCK_SIZE * k + col + threadIdx.y * k;
        if (col < k && BLOCK_SIZE * stride + threadIdx.y < n) {
            smem_n[threadIdx.y][threadIdx.x] = b[idn];
        }
        else {
            smem_n[threadIdx.y][threadIdx.x] = 0;
        }
        __syncthreads();
        for (int i = 0; i < BLOCK_SIZE; i++) {
            tmp += smem_m[threadIdx.y][i] * smem_n[i][threadIdx.x];
        }
        __syncthreads();
    }
    if (row < m && col < k)
    {
        c[row * k + col] = tmp;
    }
}

四、代码参考


#include <stdio.h>
#include <math.h>
#include <cuda_runtime.h>
#include <device_launch_parameters.h>
#include <stdlib.h>

#define CHECK(call)                                   \
do                                                    \
{                                                     \
    const cudaError_t error_code = call;              \
    if (error_code != cudaSuccess)                    \
    {                                                 \
        printf("CUDA Error:\n");                      \
        printf("    File:       %s\n", __FILE__);     \
        printf("    Line:       %d\n", __LINE__);     \
        printf("    Error code: %d\n", error_code);   \
        printf("    Error text: %s\n",                \
            cudaGetErrorString(error_code));          \
        exit(1);                                      \
    }                                                 \
} while (0)


#define BLOCK_SIZE 32

__global__ void gpu_matrix_mult(int* a, int* b, int* c, int m, int n, int k)
{
    __shared__ int smem_m[BLOCK_SIZE][BLOCK_SIZE];
    __shared__ int smem_n[BLOCK_SIZE][BLOCK_SIZE];
    int row = blockDim.y * blockIdx.y + threadIdx.y;
    int col = blockDim.x * blockIdx.x + threadIdx.x;
    int tmp = 0;
    for (int stride = 0; stride <= n / BLOCK_SIZE; stride++) {
        int idm = stride * BLOCK_SIZE + row * n + threadIdx.x;
        if (row < m && BLOCK_SIZE * stride + threadIdx.x < n) {
            smem_m[threadIdx.y][threadIdx.x] = a[idm];
        }
        else {
            smem_m[threadIdx.y][threadIdx.x] = 0;
        }
        int idn = stride * BLOCK_SIZE * k + col + threadIdx.y * k;
        if (col < k && BLOCK_SIZE * stride + threadIdx.y < n) {
            smem_n[threadIdx.y][threadIdx.x] = b[idn];
        }
        else {
            smem_n[threadIdx.y][threadIdx.x] = 0;
        }
        __syncthreads();
        for (int i = 0; i < BLOCK_SIZE; i++) {
            tmp += smem_m[threadIdx.y][i] * smem_n[i][threadIdx.x];
        }
        __syncthreads();
    }
    if (row < m && col < k)
    {
        c[row * k + col] = tmp;
    }
}

void cpu_matrix_mult(int* h_a, int* h_b, int* h_result, int m, int n, int k) {
    for (int i = 0; i < m; ++i)
    {
        for (int j = 0; j < k; ++j)
        {
            int tmp = 0.0;
            for (int h = 0; h < n; ++h)
            {
                tmp += h_a[i * n + h] * h_b[h * k + j];
            }
            h_result[i * k + j] = tmp;
        }
    }
}

int main(int argc, char const* argv[])
{
    int m = 111;
    int n = 222;
    int k = 333;

    int* h_a, * h_b, * h_c, * h_cc;
    cudaMallocHost((void**)&h_a, sizeof(int) * m * n);
    cudaMallocHost((void**)&h_b, sizeof(int) * n * k);
    cudaMallocHost((void**)&h_c, sizeof(int) * m * k);
    cudaMallocHost((void**)&h_cc, sizeof(int) * m * k);

    for (int i = 0; i < m; ++i) {
        for (int j = 0; j < n; ++j) {
            h_a[i * n + j] = rand() % 1024;
        }
    }

    for (int i = 0; i < n; ++i) {
        for (int j = 0; j < k; ++j) {
            h_b[i * k + j] = rand() % 1024;
        }
    }

    int* d_a, * d_b, * d_c;
    CHECK(cudaMalloc((void**)&d_a, sizeof(int) * m * n));
    cudaMalloc((void**)&d_b, sizeof(int) * n * k);
    cudaMalloc((void**)&d_c, sizeof(int) * m * k);

    // copy matrix A and B from host to device memory
    CHECK(cudaMemcpy(d_a, h_a, sizeof(int) * m * n, cudaMemcpyHostToDevice));
    cudaMemcpy(d_b, h_b, sizeof(int) * n * k, cudaMemcpyHostToDevice);

    unsigned int grid_rows = (m + BLOCK_SIZE - 1) / BLOCK_SIZE;
    unsigned int grid_cols = (k + BLOCK_SIZE - 1) / BLOCK_SIZE;
    dim3 dimGrid(grid_cols, grid_rows);
    dim3 dimBlock(BLOCK_SIZE, BLOCK_SIZE);


    cudaEvent_t cudastart;
    cudaEvent_t cudaend;
    cudaEventCreate(&cudastart);
    cudaEventCreate(&cudaend);

    cudaEventRecord(cudastart);
    cudaEventQuery(cudastart);
    gpu_matrix_mult << <dimGrid, dimBlock >> > (d_a, d_b, d_c, m, n, k);
    cudaEventRecord(cudaend);
    cudaEventSynchronize(cudaend);

    float ms;
    cudaEventElapsedTime(&ms, cudastart, cudaend);
    printf("GPU time is %fms\n", ms);

    cudaMemcpy(h_c, d_c, (sizeof(int) * m * k), cudaMemcpyDeviceToHost);
    //cudaThreadSynchronize();

    cpu_matrix_mult(h_a, h_b, h_cc, m, n, k);

    int ok = 1;
    for (int i = 0; i < m; ++i)
    {
        for (int j = 0; j < k; ++j)
        {
            if (fabs(h_cc[i * k + j] - h_c[i * k + j]) > (1.0e-10))
            {

                ok = 0;
            }
        }
    }

    if (ok)
    {
        printf("Pass!!!\n");
    }
    else
    {
        printf("Error!!!\n");
    }

    // free memory
    cudaFree(d_a);
    cudaFree(d_b);
    cudaFree(d_c);
    cudaFreeHost(h_a);
    cudaFreeHost(h_b);
    cudaFreeHost(h_c);
    return 0;
}

运行结果如下:
CUDA:矩阵乘法的实现(Share Memory)

五、实践心得

1、通过__syncthreads()的角色变换

与前几次的代码相比,本篇代码的最大的不同体现在在for函数中有着两个__syncthreads(),通过huan老师的专业又抽象的描述为:每一个线程通过__syncthreads()转换了自己的身份,__syncthreads()之前,每个现在参加集体活动,负责数据的传输,同步之后每个线程负责自己对应的计算。可见__syncthreads()函数对于同一个block中的线程的作用之大。

2、并行思维中的同步

本篇代码体现了并行思维,在share Memory赋值操作中,由于是并行执行的, 所以每个线程执行的速度也有着差异,若不执行同步操作的话,可能会导致线程的计算发生在share Memory的赋值操作之前,导致了错误的计算,线程的同步很好的解决了这一问题,通过同步保证当前block中的线程全部对share Memory赋值完毕,再执行操作,避免了上述问题的出现。可见在并行编程中的同步思维的重要性,该思维的丢失往往在bug的寻找过程中很难弥补,所以在code环节中要谨慎思考,避免在debug环节中的停滞

3、提高硬件的使用效率

本文中的tile的大小的设置为了方便演示和理解,设置成了BLOCK_SIZE*BLOCK_SIZE,实际中应该为其分配更加合理的值,由于每次的移动都是以一个tile大小的share memory来进行赋值的,所以其大小将对性能有着较大的影响。同时为了提高硬件的使用效率,GridDimBlockDim的设置也需要调优。文章来源地址https://www.toymoban.com/news/detail-401073.html

到了这里,关于CUDA:矩阵乘法的实现(Share Memory)的文章就介绍完了。如果您还想了解更多内容,请在右上角搜索TOY模板网以前的文章或继续浏览下面的相关文章,希望大家以后多多支持TOY模板网!

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处: 如若内容造成侵权/违法违规/事实不符,请点击违法举报进行投诉反馈,一经查实,立即删除!

领支付宝红包 赞助服务器费用

相关文章

  • 矩阵乘法,python简易实现

    1.首先,先了解下矩阵乘法最基本的工作原理,可简易得理解成 C矩阵(i, j)的值是 由A矩阵 i 行依次与B矩阵 j 列相乘的求和,即:  2.demo实现 3、基于矩阵结果是行和列的对应相乘的累和迭代,所以选择依次增加,核心算法:      其中,选取 i、j、k进行循环与迭代,k作为中

    2024年02月11日
    浏览(39)
  • 使用cublas实现矩阵乘法

    使用CUDA写一个矩阵乘法 C = A X B (矩阵维度: A: M X K, B: K X N, C: M X N ),当然可以自己写核函数,但效率不如CUDA自带的 cublas 算法效率高。使用 cublas 唯一值得注意的地方是, 在CPU中的矩阵数据存储是行优先存储,而在GPU中是列优先存储 ,这相当于对原矩阵做了一次转置,我

    2024年02月16日
    浏览(39)
  • markdown实现矩阵乘法

    [ x y 1 ] = [ f 0 O x 0 f O y 0 0 1 ] [ x ˉ y ˉ z ] (1) left[ begin{matrix} x \\\\ y \\\\ 1 end{matrix} right] = left[ begin{matrix} f 0 O_x\\\\ 0 f O_y\\\\ 0 0 1 end{matrix} right] left[ begin{matrix} bar{x} \\\\ bar{y} \\\\ z end{matrix} right] tag{1} ​ x y 1 ​ ​ = ​ f 0 0 ​ 0 f 0 ​ O x ​ O y ​ 1 ​ ​ ​ x ˉ y ˉ ​ z ​ ​ ( 1

    2024年02月09日
    浏览(38)
  • 矩阵乘法实现卷积运算

            矩阵根据卷积核的大小进行,从左到右、从上到i 下 的移动,对应数据相乘再相加得到的数据为该区域的值。 ​​​​​​​ ​​​​​​​         原理:根据对于相乘相加的机制,发现通过对卷积核填零构成和输入矩阵大小一致的矩阵,然后展平拼接起来,

    2024年02月12日
    浏览(45)
  • 用Java实现矩阵乘法

    矩阵相乘需注意:         1、当矩阵A的列数(column)等于矩阵B的行数(row)时,A与B可以相乘。         2、矩阵C的行数等于矩阵A的行数,C的列数等于B的列数。         3、乘积C的第m行第n列的元素等于矩阵A的第m行的元素与矩阵B的第n列对应元素乘积之和 设a为

    2023年04月08日
    浏览(41)
  • 矩阵乘法(C语言实现),超详细

    分别求得两个矩阵的行数a1,b1以及列数a2,b2。 如果a1 == b1,并且a2 == b2则进行乘法运算

    2024年02月05日
    浏览(39)
  • 【Python】矩阵乘法3种实现方案

    结论: 1、@ 符在numpy里就是矩阵乘法的意思,也是dot()的意思。 2、用这个 @ 运算符可以不用再使用matmult方法 3、一般来说,@ 比.dot()方法要慢一点点。dot是numpy里的函数,主要用于求向量相乘,矩阵乘法,矩阵与向量乘法。   内积,点积,乘法, 点积 对于元素相乘并相加,

    2024年02月12日
    浏览(42)
  • FPGA | Verilog 实现矩阵乘法(附源码)

    使用 for语句实现,后续继续做并行优化… 最近需要用 verilog写一个矩阵乘法的简单模块,本来想着网上随便搜一个复制粘贴一下,却发现居然找不到有源码的(好多还上传到了CSDN资源),罢了罢了,照着Github的自己写一个吧。 我写的是 3 * 3 的、数值位宽为 [3:0] (0-15)的矩

    2024年02月11日
    浏览(35)
  • 基于因特尔OneAPI实现矩阵并行乘法运算

    OneAPI介绍 Intel oneAPI 是一个跨行业、开放、基于标准的统一的编程模型,旨在提供一个适用于各类计算架构的统一编程模型和应用程序接口。其核心思想是使开发者只需编写一次代码,便可在跨平台的异构系统上运行,支持的底层硬件架构包括 CPU、GPU、FPGA、神经网络处理器以

    2024年02月04日
    浏览(37)
  • 通过类实现矩阵加减法、乘法、转置(C++))

    定义一个二维方阵类 matrix 通过重载二元运算符“+”、“-”、“*”和一元运算符“~”, 来实现矩阵加、矩阵减、矩阵乘以及矩阵转置。 1.由于矩阵的行与列都是未知的,首先需要通过 动态分配内存 实现创建任意大小的矩阵,由于类中默认的构造函数无法满足我们的需求,

    2024年02月06日
    浏览(56)

觉得文章有用就打赏一下文章作者

支付宝扫一扫打赏

博客赞助

微信扫一扫打赏

请作者喝杯咖啡吧~博客赞助

支付宝扫一扫领取红包,优惠每天领

二维码1

领取红包

二维码2

领红包