Skip to content

cuda 编程学习:Grid-Block-Thread

约 1487 字大约 5 分钟

2025-04-05

cuda的软件层级

image

cuda的软件逻辑上包含Grid-Block-Thread三级结构,kernel可以理解为一次函数调用,生成一个Grid。

矩阵乘kernel为例编写cuda代码

调用kernel时,实际上传入了kernel_name<<<grid,block>>>​参数。如上图,grid​参数控制了一个Grid有多少个Block,block​参数控制了一个Block里需要多少Thread,这两个参数都是三维向量,如下方矩阵乘的kernel定义:

// 定义线程组织结构
// 每个 block 包含 BLOCK_SIZE x BLOCK_SIZE 个线程
dim3 block(THREAD_SIZE_X, THREAD_SIZE_Y, THREAD_SIZE_Z);
// grid 的维度,根据输出矩阵 C 的尺寸(M x N)确定
dim3 grid(grid_size_x, grid_size_y, grid_size_z);
// 启动 kernel
matrixMulKernel<<<grid, block>>>(A, B, C, M, N, K);

dim block也可以写成,Z方向默认为1

dim3 block(THREAD_SIZE_X, THREAD_SIZE_Y);

定义好维度之后,在kernel函数里面就可以通过变量拿到执行thread的信息:

int grid_x = gridDim.x; // grid 在x方向的线程块数量
int block_y = blockIdx.y;
int block_y_dim = blockDim.y; // block在y方向的线程数量
int thread_x = threadIdx.x;

如果我们要计算一个矩阵乘,可以这么写,注意B是转置后的矩阵,具有局部性,和数学上的不一样,这是一般矩阵乘编写时的习惯

调用kernel前后的操作

调用kernel之前需要做一些准备工作:申请内存,内存拷贝,定义grid和block大小

调用kernel后需要cudaDeviceSynchronize();​等待结果,这是因为kernel的调用是异步提交的任务,完成后拷贝内存和释放device内存。

cuda实际上的调度

软件层级是编写kernel代码才会出现的,实际上GPU在计算时只有SM存在,可以理解为CPU的一个core,一个SM可以执行成百上千thread的执行。

GPU在调度kernel的时候,会保证一个Block内的thread被分配到同一个SM(SIMT)。

NVIDIA在调度时,32个thread是一个wrap,wrap才是SM调度和运行的基本单元,一次并行执行相同的指令,一个wrap占用一个SM运行,可以理解为CPU的一个线程。

这样一来,一个block内的thread如果很多,就会有多个wrap,假如是512,则有512/32=16个wrap,这16个wrap的运行是轮流进入SM的,由SM的硬件(wrap schedule)负责调度。

完整代码