cuda 矩阵乘法运算并行

2017-09-28  本文已影响1228人  bidai541

一直很好奇GPU做矩阵运算是怎么并行加速的,今天看了一些粗浅的东西,并总结整理出来。
version:cuda 8

cuda C 中扩展的一些概念

主要包括函数声明、变量声明、内存类型声明、文理内存、原子函数等,常用的有这么几个:
参考(http://bbs.csdn.net/topics/390798229,原地址已经失效)

__global__ void kernel( void ) {
}

函数修饰符
1. __global__,表明被修饰的函数在设备上执行,但在主机上调用。
2. __device__,表明被修饰的函数在设备上执行,但只能在其他__device__函数或者__global__函数中调用。

常用的GPU内存函数

GPU内存分类

cuda C 做矩阵乘法(Tiled 算法)

为什么看cuda C 做矩阵乘法运算呢?在深度神经网络中,全连接层、卷积层、池化层,几乎我们可以想到的所有操作都离不开矩阵运算,卷积层最后其实也是转化为矩阵的乘法操作进行优化,在【conv2d 实现 caffe&tensorflow】中有介绍原理。
参考视频地址:https://www.youtube.com/watch?v=SqZaletdPCY


思想: 为了引入共享内存的概念降低GPU带宽使用,把要计算的两个矩阵A B 先分解成BLOCK_SIZE=16大小的submatrix,每一个block结构运算一个submatrix乘法,这样在一个block中所有的线程是共享参数的,不用每次计算都从global memory中重新加载。

template <int BLOCK_SIZE> __global__ void
matrixMulCUDA(float *C, float *A, float *B, int wA, int wB)
{
 // Thread 所在 block 的 location 
 int bx = blockIdx.x;
 int by = blockIdx.y;

 // Thread 的location 
 int tx = threadIdx.x;
 int ty = threadIdx.y;

 // A矩阵16 * 16 子矩阵的起始下标
 int aBegin = wA * BLOCK_SIZE * by;

 // A矩阵16 * 16 子矩阵的终止下标(就是A矩阵一次运算一行,对应着B 矩阵一次运算一列)
 int aEnd = aBegin + wA - 1;

 // A矩阵下标一次移动的步长, 子矩阵是16 * 16,一次处理一个子矩阵,那么步长显然就是16了
 int aStep = BLOCK_SIZE;

 // B 矩阵子矩阵对应的起始下标
 int bBegin = BLOCK_SIZE * bx;

 // B 矩阵子矩阵对应的步长,一次移动16*widthB,同样也是隔出16*16的子矩阵出来
 int bStep = BLOCK_SIZE * wB;

 // 累加,得到行 * 列的值
 float Csub = 0;

 // 循环次数等于widthA / 16,把长向量点积运算转化为两个短向量点积后的和
 for (int a = aBegin, b = bBegin; a <= aEnd; a += aStep, b += bStep)
 {
 // 定义A的共享子矩阵变量,因为__shared__声明,所以同一个block中的所有threads都可见,
 //每个thread填充一个元素,并计算一个行列乘积,减小带宽使用
 __shared__ float As[BLOCK_SIZE][BLOCK_SIZE];

 // 定义A的共享子矩阵变量
 __shared__ float Bs[BLOCK_SIZE][BLOCK_SIZE];

 // 每个block包含16 * 16 个线程,所以每个线程负责一个矩阵元素的拷贝(注意同步)
 As[ty][tx] = A[a + wA * ty + tx];
 Bs[ty][tx] = B[b + wB * ty + tx];

 // Synchronize to make sure the matrices are loaded
 __syncthreads();

 // 每个线程计算 子矩阵的行列乘积,大循环外边还有累加,累加的是不同子矩阵点积和
 for (int k = 0; k < BLOCK_SIZE; ++k)
 {
 Csub += As[ty][k] * Bs[k][tx];
 }

 // 再次同步
 __syncthreads();
 }

 // 把结果赋值到C矩阵,计算结果对应C下边的过程
 int c = wB * BLOCK_SIZE * by + BLOCK_SIZE * bx;
 C[c + wB * ty + tx] = Csub;
}

只看源代码很难理解矩阵加速真正的原理,这是一个坑,还有是输入矩阵的尺寸大小,只能是BLOCK_SIZE=16的整数倍,不然会出错(实验结果也表明确实出错了,又是一个坑)。
为什么采用Tiled 算法呢?主要是不这么做GPU从global memory读取数据的代价太大了。

上一篇下一篇

猜你喜欢

热点阅读