矩阵乘法中每个线程就是计算一个结果矩阵中的值:
for(int k = 0;k < width;++k)
PValue += d_M[Row * width + k] * d_N[k * Width + Col];
该循环每次循环都访问两边存储器,d_M、d_N 各一次,并执行两次浮点运算,乘法一次,加法一次。浮点运算与全局存储器访问操作的比值(compute to Global Memory Access,CFMA)就等于1.0。
当今许多设备的全局存储器访问宽带达到了 200GB/S,每个单精度浮点数占 4 个字节,那么单精度数据的加载速度不高于 50GFLOPS。 如果 CFMA 为 1.0 那么 kernel 函数中每秒可执行浮点数运算的不高于 50GFLOPS,这对于许多高端 GPU 能达到的峰值性能 1500GFLOPS 是挺少的了。所以我们要提高 CFMA 来提高 GPU 的利用率。
下图底部,有全局存储器和常数存储器,主机代码可以对两者进行读写。主机可以访问设备全局存储器,与设备之间传输和复制数据。当所有线程同时访问相同位置时,常数存储器为设备提供短延时、高宽带和只读访问。
寄存器和共享存储器是片上存储器。这两种存储器中的变量可以以高度并行的方式访问。寄存器分配给单个线程,每个线程只能访问分配给自己的寄存器。共享存储器分配给线程块,同一个块中的所有线程都可以访问共享存储器中的变量。
下图是现代冯诺依曼模型机中的主存和寄存器,访问主存比访问寄存器中的数据需要多出访存的指令,而且主存利用 DRAM 技术实现,延迟高,所以访问主存中的数据比访问寄存器中的慢。
CUDA 模型中的全局存储器相当于冯诺依曼模型中的主存,寄存器相当于冯诺依曼模型中的寄存器堆。
处理单元和线程: 现代计算机中的一个线程相当于冯诺依曼模型中的一个处理器。现代处理器提供了上下文切换功能,多个线程分时共享处理器,可以暂停一个线程,执行其他线程,然后再重新启动这个线程。一些处理器提供了多个处理单元,允许多个线程同时执行,下图展示了 SIMD 的设计模式,所有的处理单元共享一个 PC 和 IR 寄存器,所有线程执行同一条指令。
共享存储器与寄存器虽然都是片上存储器,但共享存储器是芯片上存储空间的一部分。处理器访问局部存储器还是要执行内存加载操作,与访问全局存储器一样。然而共享存储器在片内访问共享存储器比访问全局存储器有更低的延迟和更高的宽带,由于需要内存加载操作,所以比寄存器有更高的延迟和更低的宽带。
在 CUDA 中共享存储器和寄存器之间的一个重要区别是共享存储器中的变量可以被线程块中的所有线程共享,而寄存器对线程是私有的。
变量声明:
变量声明 | 存储器 | 作用域 | 生命周期 |
---|---|---|---|
除数组以外的自动变量 | 寄存器 | 线程 | kernel 函数 |
自动数组变量 | 局部存储器 | 线程 | kernel 函数 |
__device__ __local__ int LocalVar; | 局部存储器 | 线程 | kernel 函数 |
__device__ __shared__ int SharedVar; | 共享存储器 | 块 | kernel 函数 |
__device__ int GlobalVar; | 全局存储器 | 网络 | 应用程序 |
__device__ __constant__ int ConstVar; | 常数存储器 | 网格 | 应用程序 |
local memory 的数据是被保存在显存中的,速度很慢。无法确定大小的数组和较大的结构体都会放在局部存储器中。
使用__local__ ,__shared__ ,__constant__ 时,__device__可选。
变量声明 | 声明位置 |
---|---|
__shared__ | kernel 函数或设备函数 |
__constant__ | 任何函数体外 |
CUDA 指针: 用来指向全局存储器中的数据对象。在 kernel 函数和设备函数中有两种使用指针的方法。
//in host function
float *A;
cudaMelloc((void **)&A, size);
//in kernel function
float *ptr = &GlobalVar;
我们可以先将要计算的元素分批从全局存储器中加载到每个块内的共享存储器,以此来减少访问全局存储器所带来的内存消耗。下面以两个 4 x 4 方阵、TILE_WIDTH 大小为 16 为例:
block[0][0] 在
在计算 block[0][0] 同时,block[0][1],block[1][0],block[1][1]也在进行同样的运算,当所有 block 的阶段 2 结束后,矩阵乘法完成。
矩阵乘法的 kernel 函数:
#define TILE_WIDTH 16
__global__ void MatrixMulKernel(float *d_M, float *d_N, float *d_P, int m, int k, int n){
__shared__ float Mds[TILE_WIDTH][TILE_WIDTH];
__shared__ float Nds[TILE_WIDTH][TILE_WIDTH];
int bx = blockIdx.x;
int by = blockIdx.y;
int tx = threadIdx.x;
int ty = threadIdx.y;
int row = by * blockDim.y + ty;
int col = bx * blockDim.x + tx;
float Pvalue = 0; //in every thread
for(int ph = 0; ph < ceil(k/float(TILE_WIDTH));++ph){
if(row < m && (ph * TILE_WIDTH + tx) < k)
Mds[ty][tx] = d_M[row * k + ph * TILE_WIDTH + tx];
else Mds[ty][tx] = 0.0;
if(col < n && (ph * TILE_WIDTH + ty) < k)
Nds[ty][tx] = d_N[(ph * TILE_WIDTH + ty) * n + col];
else Nds[ty][tx] = 0.0;
__syncthreads(); //保证所有需要的元素都加载完成,再进行运算
for(int i = 0;i < TILE_WIDTH;++i){
Pvalue += Mds[ty][i] * Nds[i][tx];
}
__syncthreads(); //保证所有线程都计算完成再进入下一个循环。
}
if (row < m && col < n)//将计算后的矩阵块放到结果矩阵d_P中
d_P[row*n + col] = Pvalue;
}
完整的 CUDA 分块矩阵乘法代码:
#include
#include
#define TILE_WIDTH 32
__global__ void MatrixMulKernel(float *d_M, float *d_N, float *d_P, int m, int k, int n){
__shared__ float Mds[TILE_WIDTH][TILE_WIDTH];
__shared__ float Nds[TILE_WIDTH][TILE_WIDTH];
int bx = blockIdx.x;
int by = blockIdx.y;
int tx = threadIdx.x;
int ty = threadIdx.y;
int row = by * blockDim.y + ty;
int col = bx * blockDim.x + tx;
float Pvalue = 0; //in every thread
for(int ph = 0; ph < ceil(k/float(TILE_WIDTH));++ph){
if(row < m && (ph * TILE_WIDTH + tx) < k)
Mds[ty][tx] = d_M[row * k + ph * TILE_WIDTH + tx];
else Mds[ty][tx] = 0.0;
if(col < n && (ph * TILE_WIDTH + ty) < k)
Nds[ty][tx] = d_N[(ph * TILE_WIDTH + ty) * n + col];
else Nds[ty][tx] = 0.0;
__syncthreads();
for(int i = 0;i < TILE_WIDTH;++i){
Pvalue += Mds[ty][i] * Nds[i][tx];
}
__syncthreads();
}
if (row < m && col < n)//将计算后的矩阵块放到结果矩阵d_P中
d_P[row*n + col] = Pvalue;
}
void gemm_cuda_shared(float *A, float *B, float *C, int m, int k, int n){
float *d_A, *d_B, *d_C;
size_t size = m * k * sizeof(float);
cudaMalloc(&d_A, size);
cudaMemcpy(d_A, A, size,
cudaMemcpyHostToDevice);
size = k * n * sizeof(float);
cudaMalloc(&d_B, size);
cudaMemcpy(d_B, B, size,
cudaMemcpyHostToDevice);
size = m * n * sizeof(float);
cudaMalloc(&d_C, size);
dim3 dimBlock(TILE_WIDTH, TILE_WIDTH);
dim3 dimGrid(ceil(m/(float)TILE_WIDTH), ceil(n/(float)TILE_WIDTH));
MatrixMulKernel<<<dimGrid, dimBlock>>>(d_A, d_B, d_C, m, k, n);
cudaMemcpy(C, d_C, size,
cudaMemcpyDeviceToHost);
cudaFree(d_A);
cudaFree(d_B);
cudaFree(d_C);
}
int main(int argc, char ** argv){
if(argc!=4){
printf("please input right parameter a,b,c\n");
exit(0);
}
int m = atoi(argv[1]);
int k = atoi(argv[2]);
int n = atoi(argv[3]);
float *A = (float *)malloc(sizeof(float) * m * k);
float *B = (float *)malloc(sizeof(float) * k * n);
float *C = (float *)malloc(sizeof(float) * m * n);
for (int i = 0; i < k;i ++){
for(int j = 0;j < m;j++){
A[i*m + j] = 1;
}
}
for(int i = 0;i < k;i++){
for(int j = 0;j < n;j++){
B[i*n + j] = 1;
}
}
gemm_cuda_shared(A, B, C, m, k, n);
for (int i = 0; i < m;i ++){
for(int j = 0;j < n;j++){
printf("%f ", C[i*n+j]);
}
printf("\n");
}
free(A);
free(B);
free(C);
return 0;
}
参考:https://icode.best/i/93343835170848