CUDA矩阵乘法提示:文章写完后,目录可以自动生成,如何生成可参考右边的帮助文档
- 矩阵规模
- 一、一维并行
- 1.一维线程并行(thread)
- 2.一维块线程并行(block)
- 3.一维共享A一行程并行(shared)
- 二、二维并行
- 1.共享存储二维分块
矩阵规模
矩阵A: n行,l列
矩阵B:l行,m列
结果矩阵C=A✖️B: n行,m列
一、一维并行 1.一维线程并行(thread)
一个线程并行一个A1行✖️B1列
- 需要线程个数:n✖️m个,一个线程并行一个A1行✖️B1列, 即一个线程对应结果矩阵的C的一个点,e.g. c(1,1)=xid0的计算结果,所以一共需要:
| xid0=a1hang✖️b1lie | xid1=a1hang✖️b2lie | … | xid(m-1)=a1hang✖️bmlie |
|---|---|---|---|
| xid(m)=a2hang✖️b1lie | xid(m+1)=a2hang✖️b2lie | … | xid(2m-1)=a2hang✖️bmlie |
| . | … | … | … |
| xid((n-1)*m)=anhang✖️b1lie | xid((n-1)*m+1)=anhang✖️b2lie | … | xid((n-1)*(m-1))=anhang✖️bmlie |
即可知一共需要n✖️m个线程并行,每个线程处理一个向量✖️向量
2. 总共的第几个线程:xid=block.x*blockDim.x+threadIdx.x;
3. 因为线程总数为n✖️m个,所以当前线程xid对应A的第几行=xid/n,
当前线程xid对应B的第几列=xid%n;
const int idx = blockIdx.x*blockDim.x+threadIdx.x;
const int Arow = idx / n;
const int Bcolumn = idx % n;
//计算矩阵乘法
if (Arow < n && Bcolumn < m)
{
float t = 0;
for (i = 0; i < l; i++)
{
t += A[Arow * l + i] * B[i * m + Bcolumn];
}
C[Arow * m + Bcolumn] = t;
}
2.一维块线程并行(block)
一个block代表矩阵A1行,块中一个线程代表B1列,B矩阵的列已每块线程数跳步.
- 一共需要n个block,如果可以给的最大块数小于A的行数则已gridDim.x为block的跳步.
线程数为j个,如果j小于B的列数则以blockDim.x为跳步
| thread0 | thread1 | thread2 | thread3 | thread0+blockDim.x | hread1+blockDim.x | … | thread3+m/blockDim.x | |
|---|---|---|---|---|---|---|---|---|
| block0 | a1hang✖️b1lie | a1hang✖️b2lie | a1hang✖️b3lie | a1hang✖️b4lie | a1hang✖️b5lie | a1hang✖️b6lie | … | a1hang✖️bmlie |
| block1 | a2hang✖️b1lie | a2hang✖️b2lie | a2hang✖️b3lie | a2hang✖️b4lie | a2hang✖️b5lie | a2hang✖️b6lie | … | a2hang✖️bmlie |
| . | … | … | … | … | … | … | … | |
| block(n-1) | anhang✖️b1lie | anhang✖️b2lie | anhang✖️b3lie | anhang✖️b4lie | anhang✖️b5lie | anhang✖️b6lie | … | anhang✖️bmlie |
//计算矩阵乘法
for (int Arow=blockIdx.x;Arow
for (int Bcolumn=threadIdx.x; Bcolumn < m;Bcolumn+=blockDim.x)
{
float num = 0.0;
for (int i = 0; i < l; i++)
{
num += A[Arow * l + i] * B[i * m + Bcolumn];
}
C[Arow * m + Bcolumn] = num;
}
}
3.一维共享A一行程并行(shared)
上个方法中A的每行一个block,把A的每行给共享存储.
- 共享存储速的关键是数据重复利用,因为从全局读一次数据,然后重复多用几次最合适,e.g.矩阵乘法中,A的每一行就成了B的m个列,即A1的每行都用重复用了m次.可将A的每行放进共享存储.
__shared__ shA[l];
int Arow=blockIdx.x;
for(int Acolumn=threadIdx.x; Acolumn < l;Bcolumn+=blockDim.x)
{
shA[Acolumn]=A[Arow*l+Acolumn];//把A的每行给shared
}
__syncthreads();
for(int Bcolumn=threadIdx.x; Bcolumn
float num = 0.0;
for (int i = 0; i < l; i++)//A1行(已在shared中)乘B1列
{
num += shA[i] * B[i * m + Bcolumn];
}
C[Arow * m + Bcolumn] = num;
}
二、二维并行
1.共享存储二维分块
- 问题与改进
当A的列数>shared memory最大容量时->采用分块存在shared中;
一维时只存A矩阵->二维将B矩阵也分块存入; - 解决办法:设置两个二维的share memory : shA[][Tl_size]放A的shared子矩阵,shB[Tl_size][]放B的shared子矩阵,A横着更新shA子矩阵,B竖着更新shB子矩阵
template__global__ void matrixMulCUDA(float *C, float *A, float *B, int wA, int wB) { // 累加,得到行 * 列的值 float numsub = 0.0; // 循环次数等于widthA / 16,把长向量点积运算转化为两个短向量点积后的和 //因为B的宽条是竖着的,每宽条中宽是往下移动的,往下移动即代表动一行要增长m个元素,动一块即移动块宽Bsize✖️m个元素,所以步长为每次BLOCK_SIZE * m; 因为A每宽条是横着的,所以宽条中的块是横着移动,当前行每往右移动一块,增长块的块度Bsize的元素,即每宽条每移动一块步长就为Bsize for (int j = 0; a // 定义A的共享子矩阵变量,因为__shared__声明,所以同一个block中的所有threads都可见, //每个thread填充一个元素,并计算一个行列乘积,减小带宽使用 __shared__ float As[BLOCK_SIZE][BLOCK_SIZE]; // 定义A的共享子矩阵变量 __shared__ float Bs[BLOCK_SIZE][BLOCK_SIZE]; // 每个block包含16 * 16 个线程,所以每个线程负责一个矩阵元素的拷贝(注意同步) //A的shared块更新方式:1.块横着一块一块更新一宽条:l * BLOCK_SIZE * blockIdx.y为前面有的宽条:blockIdx.y表示第几个宽条,宽条的宽度为Bsize,则BLOCK_SIZE * blockIdx.y为前面一共有几行,每行有l列(BLOCK_SIZE * blockIdx.y)*l则为一共前面有多少元素. 2.hreadIdx.y 表示当前宽条的第几行,l * hreadIdx.y表示本块中本行在本块前的前面行一共的元素. 3.threadIdx.x表示本行第几个元素,所以l * BLOCK_SIZE * blockIdx.y + l * hreadIdx.y + threadIdx.x表示前面宽行中元素个数➕本宽条中前几行元素个数➕本行第几个元素 As[ty][tx] = A[l * BLOCK_SIZE * blockIdx.y + l * threadIdx.y + threadIdx.x+i]; Bs[ty][tx] = B[ BLOCK_SIZE * blockIdx.x + m * (threadIdx.y + i) + threadIdx.x]; // Synchronize to make sure the matrices are loaded __syncthreads(); // 每个线程计算 子矩阵的行列乘积,大循环外边还有累加,累加的是不同子矩阵点积和 for (int k = 0; k < BLOCK_SIZE; ++k) { numsub += As[ty][k] * Bs[k][tx]; } // 再次同步 __syncthreads(); } if(BLOCK_SIZE * blockIdx.y + threadIdx.y



