CUDA线程全局索引计算方式

释放双眼,带上耳机,听听看~!
本文介绍了CUDA线程全局索引计算方式,包括线程层次模型和线程索引的计算方法,对于对GPU编程感兴趣的人士有很大的参考价值。

欢迎关注我的公众号 [极智视界],获取我的更多经验分享

大家好,我是极智视界,本文分享一下 CUDA线程全局索引计算方式。

邀您加入我的知识星球「极智视界」,星球内有超多好玩的项目实战源码和资源下载,链接:t.zsxq.com/0aiNxERDq

CUDA 线程全局索引的计算,是很容易混淆的概念,因为 CUDA 线程模型的矩阵排布和咱们通常理解的数学矩阵排布秩序不太一样。在谈论这个话题前,还是要先看下下面这张 CUDA 线程层次模型图,关键先要对 Grid、Block、Thread 有个清晰的概念,如下,(需要注意的是,下面是 CUDA12 中的线程模型,新引入了一个 Thread Block Cluster 结构),

CUDA线程全局索引计算方式

基本关系是 Thread 在一起组成了 Block,Block 在一起组成了 Grid,所以是 Grid 包含 Block 再包含 Thread 的关系。为了便于讲解以及回归最经典的 CUDA 线程模型,下面还有张更加清晰的图可以表达,

CUDA线程全局索引计算方式

可以看到在上面的图中,一个 Grid 中是包含了 6 个线程块,而每个线程块又包含了 9 个线程,这其中有两个重要的概念,

  • grid ==> 网格 ==> grid 中包含若干 block ==> 最大允许网格大小为 2^31 -1 (针对一维网格情况);
  • block ==> 线程块 ==> block 中包含若干 thread ==> 最大允许线程块大小为 1024

线程是 CUDA 编程中的最小单位,实际上线程分块是逻辑上的划分,在物理上线程不分块。在调用 GPU 的时候,核函数中是允许开很多线程的,开的线程个数可以远高于 GPU 计算核心的数量。在设计总的线程数时,至少需要等于硬件的计算核心数,这样才可能充分发挥硬件的计算能力。

在实际 CUDA 编程中,代码中是使用 <<<grid_size, block_size>>> 来进行配置线程的,意思是说线程都是在这个 <<<grid_size, block_size>>> 的规模中,其中 grid_size 是用来配置 block 的规模,而 block_size 是用来配置 thread 的规模,这点比较绕不要弄错了。

  • grid_size ==> 通过 gridDim.x 来配置;==> 可以是 intdim3 的结构;
  • block_size ==> 通过 blockDim.x 来配置;==> 可以是 intdim3 的结构;

然后又来一个概念叫 线程索引,如下,它们是 built-in 的内建变量,意思是代码中可以直接拿来用而不用提前声明,

  • blockIdx.x ==> 用于指定线程在网格中的线程块索引值,取值范围 0 ~ gridDim.x-1;==> 展平开来;
  • threadIdx.x ==> 用于指定线程在线程块中的线程索引值,取值范围 0 ~ blockDim.x-1;==> 展平开来;

来举个例子,来定义一个 kernel_ful<<<2, 4>>>() 的线程模型,示意如下,

CUDA线程全局索引计算方式

其中 gridDim.x 值为 2,blockDim.x 值为 4,blockIdx.x 的取值范围为 0 ~ 1threadIdx.x 的取值范围为 0 ~ 3,计算标红色 Star 线程的唯一标识为:Idx = threadIdx.x + blockIdx.x * blockDim.x = 2 + 1 * 4 = 6,所以标红色 Star 的全局索引为 thread6,这是符合预期的。 当然,这是一维网格一维线程块的情况,也是最简单的情况,接着来推广到多维。

CUDA 最多其实可以组织三维的网格和线程块,可以用 dim3 的数据结构来表达,其实一维的情况也可以用 dim3 来表达,只是其他维度为 1 而已。这样来看,其实 blockIdxthreadIdx 是一个 uint3 的结构体,都包括 x、y、z 三个维度。再回过头来看上面的例子,对于一维的情况只用到了 x 维度,其他维度默认为 1,这个 1 若是展示出来就是 dim3,若是隐藏起来就是 int,所以 <<< _ , _ >>> 中的传参可以是 dim3 也可以为 int,奏是这么简单。

对于多维的情况,可以采用下面的定义方式:

dim3 grid_size(Gx, Gy, Gz);
dim3 block_size(Bx, By, Bz);

kernel_ful<<<grid_size, block_size>>>();

具体来说,比如定义一个 2 x 3 x 1 的网格、6 x 2 x 2 的线程块,可以这么写,

dim3 grid_size(2, 3);  // or dim3 grid_size(2, 3, 1);
dim3 block_size(6, 2, 2);

kernel_ful<<<grid_size, block_size>>>();

然后继续来看这张图,

CUDA线程全局索引计算方式

前面说到 CUDA 线程模型的矩阵排布和咱们通常理解的数学矩阵排布秩序不太一样,看上图中 Block(1, 1) 中的矩阵排布,第一排是 (0, 0)、(1, 0)、(2, 0)、(3, 0),是最先变化 x;第一列是 (0, 0)、(0, 1)、(0, 2),再变化 y,原点是在左上。整体的排序组织顺序是下面这样的,

CUDA线程全局索引计算方式

下面介绍 CUDA 线程全局索引的计算方式,全部列举出来其实是有下面九种情况,

  • 一维网格一维线程块;
  • 一维网格两维线程块;
  • 一维网格三维线程块;
  • 两维网格一维线程块;
  • 两维网格两维线程块;
  • 两维网格三维线程块;
  • 三维网格一维线程块;
  • 三维网格两维线程块;
  • 三维网格三维线程块;

前面已经介绍过一维网格一维线程块的线程全局索引的计算方式,接着来介绍二维网格二维线程块的计算方式,如下,

CUDA线程全局索引计算方式

要计算上图中红色块的线程索引,调用核函数的线程配置代码如下,

dim3 grid_size(3, 2);
dim3 block_size(4, 4);

kernel_ful<<<grid_size, block_size>>>();

然后开始计算,

int blockId = blockIdx.x + blockId.y * gridDim.x;
int threadId = threadIdx.y * blockDim.x + threadIdx.x;
int id = blockId * (blockDim.x * blockDim.y) + threadId;

// 带入计算
int blockId = 1 + 1 * 3 = 4;
int threadId = 2 * 4 + 2 = 10;
int id = 4 * (4 * 4) + 10 = 74;

所以计算出来,红色块线程的全局索引就为 thread74,这也是符合预期的。

三维的图画画起来稍显复杂,我这里就不画画了。

其实仔细想想,上面的九种组织情况都可以视为是 三维网格三维线程块 的情况,只是比如一维或者二维的时候,其他维度为 1 而已。若是都把它们都看成三维格式,这样不管哪种线程组织方式,都可以套用 三维网格三维线程块 的计算方式,整理如下,

// 线程块索引
int blockId = blockIdx.x + blockIdx.y * gridDim.x + gridDim.x * gridDim.y * blockIdx.z;
// 局部线程索引
int threadId = threadIdx.z * blockDim.x * blockDim.y + threadIdx.y * blockDim.x + threadIdx.x;
// 全局线程索引
int id = blockId * (blockDim.x * blockDim.y * blockDim.z) + threadId;

好了,以上分享了 CUDA线程全局索引计算方式,希望我的分享能对你的学习有一点帮助。

 【公众号传送】

《极智开发 | CUDA线程模型与全局索引计算方式》

畅享人工智能的科技魅力,让好玩的AI项目不难玩。邀请您加入我的知识星球, 星球内我精心整备了大量好玩的AI项目,皆以工程源码形式开放使用,涵盖人脸、检测、分割、多模态、AIGC、自动驾驶、工业等。一定会对你学习有所帮助,也一定非常好玩,并持续更新更加有趣的项目。 t.zsxq.com/0aiNxERDq

CUDA线程全局索引计算方式

本网站的内容主要来自互联网上的各种资源,仅供参考和信息分享之用,不代表本网站拥有相关版权或知识产权。如您认为内容侵犯您的权益,请联系我们,我们将尽快采取行动,包括删除或更正。
AI教程

OpenAI推出的ChatGPT:人工智能聊天产品的创新之处

2023-12-17 22:01:14

AI教程

AC算法:Actor-Critic方法在强化学习中的应用

2023-12-18 0:11:14

个人中心
购物车
优惠劵
今日签到
有新私信 私信列表
搜索