前言

纸上的来终觉浅,绝知此事要躬行。

naive写法

一个矩阵的乘法简单如下:C=A*B, 一般用gemm(A,B,C,M,N,K)来表示,其中的m,n,k代表的位置如下,默认是k表示消失的纬度。

上图的红色虚线围起来的是一个block要负责的数据区域,具体的代码如下:

__global__
void matrixMul(const float *A, const float *B, float *C, int M, int N, int K)
{int col = blockIdx.x * blockDim.x + threadIdx.x;int row = blockIdx.y * blockDim.y + threadIdx.y;if(row < M && col < N) {float c = 0;for(int i = 0; i < K; ++i){c += A[row * K + i] * B[i * N + col];}C[row * N + col] = c;}
}

第一步先写一个简单的矩阵乘法,不要觉得简单就不写,后面不管怎么耍花招,都是基于当下的核心逻辑和计算流程的。

share memory优化

代码我们可以看到,每一个线程要k次乘法和K次加法,每做计算一次 FMA(乘累加)之前需要读一次 A 和读一次 B,读取 Global Memory 的代价很大,通常都需要几百个 cycle(时钟周期),而计算一次 FMA 通常只需要几个 cycle,大量的时间被花费在了访存上, 那么如何减少访问global memory呢?我们知道share_memory是片上内存,访问速度比较快,我们考虑把A和B中的虚线内的数据放在share_memory上,然后计算的时候从share_memeory上取,这样的话其实会多一次数据从global转移到share上的操作,但是每次做乘加计算取数的时候,省的时间会远远多于这一次操作。

上面我们假设是把A、B中的虚线部分数据导入到share中,但其实share的大小有限,所以还是继续分片,原理如下:

具体的伪代码是:

//分为j块
for(int i = 0; i < j; i+1)
{load_gmem_to_smem(A, i, smemA);load_gmem_to_smem(B, i, smemB);__syncthreads();//computeC_i=gemm(a_i,b_i);//累加C += C_i;}

可以运行的代码:

template <int BLOCK_SIZE>
__global__
void MatrixMulCUDA(const float * __restrict__ A,const float * __restrict__ B,float * __restrict__ C,const int M,const int K,const int N) {// Block indexint bx = blockIdx.x;int by = blockIdx.y;// Thread indexint tx = threadIdx.x;int ty = threadIdx.y;//对应上图a2的左上角位置int aBegin = K * BLOCK_SIZE * by;//对应上图aj的右上角位置(注意上图有溢出到A矩阵外,这里代码默认是刚刚在A矩阵里的)int aEnd   = aBegin + K - 1;//每一个a矩阵的左上角位置距离差int aStep  = BLOCK_SIZE;// 对应上图b2的左上角位置int bBegin = BLOCK_SIZE * bx;// 每一个b矩阵的左上角位置距离差int bStep  = BLOCK_SIZE * N;float Csub = 0;// 存储aj的share memory__shared__ float As[BLOCK_SIZE][BLOCK_SIZE];// 存储bj的share memory__shared__ float Bs[BLOCK_SIZE][BLOCK_SIZE];//对应上图就是循环3次for (int a = aBegin, b = bBegin; a < aEnd; a += aStep, b += bStep) {//每个线程下载一个元素到share memoryAs[ty][tx] = A[a + K * ty + tx];Bs[ty][tx] = B[b + N * ty + tx];// Synchronize 为了确保share memory需要的数据都下载到位__syncthreads();// 每个线程计算cj中的一个位置的结果#pragma unrollfor (int k = 0; k < BLOCK_SIZE; ++k) {Csub = fma(As[ty][k], Bs[k][tx], Csub);}// 确保cj中的所有位置的结果都算好了__syncthreads();}//最后把每个线程负责的算的结果从寄存器导入到device memoryC[N * ( BLOCK_SIZE * by + ty ) + BLOCK_SIZE * bx + tx ] = Csub;
}

实验结果如下,当M=160,K=160, N=160的时候,实现效果显示还不错,比naive好但是比cublas还是有差距。

第二次优化

第一次优化后,访存代价从几百 cycle 降低到几十 cycle,并不改变问题的本质。问题的关键在于主体循环由两条 Load 指令与一条 FMA 指令构成,计算指令只占总体的 1/3,计算访存比过低,最终导致了访存延迟不能被隐藏,从而性能不理想.

这里解释一下,线程的指令会发给调度器,调度器分配对应的执行单元。这里比如有20个线程,机器上一个调度器,一个计算单元和访存单元,此刻线程1告诉调度器要执行加法计算,计算单元需要10s可以得到结果, 等待期间调度器就会问问其他线程有需要访存的吗?毕竟有一个访存单元在闲着,这时候线程8说他需要访存操作,不过一次访存需要200s, 因为计算单元速度很快,所以当20个线程的计算任务都完成时,只有一个线程的访存任务完成,所以后面还要200s*19这么长的时间。这里可以发现一共需要200S*20的时间,我们计算时间10s*20被完成隐藏了用户感知不到,这就是所谓的隐藏延迟,知道了原理,我们的任务说白了就是别让计算单元闲着,如果一个线程的计算时间如果和访存时间一模一样,那么完全就可以隐藏计算或者是访存的时间了,这不是美滋滋?但是但是,这里的前提是只有一个访存单元和计算单元,实际上底层硬件还是差距很大的,不同架构和型号的显卡也不一样,这也是为啥同样的的代码在不同机器上的性能不一样,此外调度器的规则,计算与访存任务的依赖等等都有可能导致性能差异。

float c[4][4] = {{0}};
float a_reg[4];
float b_reg[4];
for(int i = 0; i < K; i += tile_with)
{load_gmem_tile_to_smem(A, i, smemA);load_gmem_tile_to_smem(B, i, smemB);__syncthreads();//computefor(int j = 0; j < tile_with; ++j) {// load tile from shared mem to register load_smem_tile_to_reg(smemA, j, a_reg);load_smem_tile_to_reg(smemB, j, b_reg);// compute matrix multiply accumulate 4x4mma4x4(a_reg, b_reg, c);}// 累加C += C_i;
}

未完待续。。。
参考:https://zhuanlan.zhihu.com/p/410278370

CUDA矩阵乘法优化相关推荐

  1. CUDA 矩阵乘法优化

    这个完全是基础知识啊~~  哪不对 大佬们帮忙指出啊 CUDA 矩阵乘法优化手段详解 Naive 实现的分析:到底差在哪里? 笔者面试过不少具有 CUDA 编程经验的校招同学,当提问使用 CUDA 编 ...

  2. CUDA: 矩阵乘法优化

    矩阵乘法是有实用价值的程序,我们会使用浮点数. 虽然矩阵乘法有点老套,不过因为它相当简单,而且也可以用来介绍一些有关 CUDA 的有趣性质. 矩阵乘法 为了单纯起见,我们这里以方形的矩阵为例子.基本上 ...

  3. MegEngine| CUDA 矩阵乘法终极优化

    前言 单精度矩阵乘法(SGEMM)几乎是每一位学习 CUDA 的同学绕不开的案例,这个经典的计算密集型案例可以很好地展示 GPU 编程中常用的优化技巧,而能否写出高效率的 SGEMM Kernel , ...

  4. CUDA实例系列一: 矩阵乘法优化

    CUDA实例系列一----矩阵乘法优化 很多朋友在学习CUDA的时候都会面临一个题目----矩阵乘法, 这也是CUDA最广泛的应用之一. 本文将详细讲解如何利用GPU加速矩阵乘法的计算. 话不多说, ...

  5. 形态形成场(矩阵乘法优化dp)

    形态形成场(矩阵乘法优化dp) 短信中将会涉及前\(k\)种大写字母,每个大写字母都有一个对应的替换式\(Si\),替换式中只会出现大写字母和数字,比如\(A→BB,B→CC0,C→123\),代表 ...

  6. 【BZOJ 3326】[Scoi2013]数数 数位dp+矩阵乘法优化

    挺好的数位dp-- 先说一下我个人的做法: 经过观察,发现这题按照以往的思路从后往前递增,不怎么好推,然后我就大胆猜想,从前往后推,发现很好推啊,维护四个变量,从开始位置到现在有了i个数 f[i]:所 ...

  7. cuda矩阵乘法(简单理解)

    提示:文章写完后,目录可以自动生成,如何生成可参考右边的帮助文档 CUDA矩阵乘法 矩阵规模 一.一维并行 1.一维线程并行(thread) 2.一维块线程并行(block) 3.一维共享A一行程并行 ...

  8. OpenBLAS项目与矩阵乘法优化 | AI 研习社

    提起矩阵计算,学过<高等数学>的人可能都听过,但若不是这个领域的研究者,恐怕也只停在"听过"的程度.在矩阵计算领域,开源项目OpenBLAS影响巨大,除IBM.华为等巨 ...

  9. 【Contra】 矩阵乘法优化 dp

    偶然间,chnlich发现了他小时候玩过的一个游戏"魂斗罗",于是决定怀旧.但是这是一个奇怪的魂斗罗MOD.有N个关卡,初始有Q条命.每通过一个关卡,会得到u分和1条命,生命上限为 ...

最新文章

  1. linux账号管理命令,linux账号管理及相关命令和操作
  2. 第十二课.统计推断的基本思想
  3. Javaweb乱码解决
  4. LaTeX (1)——LaTex环境的下载与安装(Tex live 2020+ Tex studio编辑器、 proTeXt(MiKTeX+TeXstudio编辑器))
  5. OpenCV2计算机编程手册(二)基于类的图像处理
  6. 监控hdfs坏块脚本
  7. nvidia ubuntu 驱动升级_解决 Ubuntu 在启动时冻结的问题
  8. 10种软件滤波方法的示例程序(匠人转载学习)
  9. 【Unity3D开发小游戏】《太空射击游戏》Unity开发教程
  10. HDU-3987 Harry Potter and the Forbidden Forest(最大流)
  11. 远程桌面提示“用户帐户限制(例如,时间限制)会阻止你登录。请与系统管理员或技术支持联系以获取帮助。”
  12. c语言编写 构成的梯形,用C语言编写梯形
  13. 计算机没网络本地连接接下来,电脑本地连接没有了网络连接的本地连接不见的解决方法...
  14. 刘强东:B2C电商本质在于娇惯消费者
  15. java证书加签_证书加签、验签、加密、解密Demo
  16. MATLAB常用命令及函数大全(字母顺序)
  17. 阅文JAVA后端笔试
  18. 学习云计算费用大概多少 一般需要多长时间
  19. [Discuz 插件] SEO天涯海角 3.1.0 正式版.rar
  20. OpenCV - 训练分类器

热门文章

  1. 我对SOA的反思:SOA架构的本质
  2. git clone出现 fatal: unable to access ‘https://github.com/...‘的解决办法(亲测有效)
  3. ChatGPT 类大语言模型为什么会带来“神奇”的涌现能力?
  4. HDMI2.0RE驱动控制方案AG7120和AG7220性能参数对比
  5. 基于gpg的fwknop配置流程
  6. 一个测试工程师应具备那些素质和技能?
  7. 【微信生态圈】-谈谈我的学习经验
  8. 个人博客处理——页面处理
  9. Apache Hadoop
  10. 极简微前端框架-京东MicroApp开源了