1

我是 CUDA 初学者。

到目前为止,我了解到,每个 SM 有 8 个块(线程)。假设我将数组中的元素乘以 2 的简单工作。但是,我的数据比线程少。

没问题,因为我可以切断线程的“尾巴”以使它们空闲。但是,如果我理解正确,这将意味着一些 SM 将获得 100% 的工作,而一部分(甚至没有)。

因此,我想计算哪个 SM 正在运行给定线程并以这样的方式进行计算,即每个 SM 具有相同的工作量。

我希望它首先有意义:-)如果是这样,如何计算给定线程正在运行哪个SM?或者——当前SM的索引和它们的总数?换句话说,在 SM 术语中相当于 threadDim/threadIdx。

更新

评论太长了。

罗伯特,谢谢你的回答。当我尝试消化所有内容时,这就是我所做的——我有一个“大”数组,我只需将值相乘*2并将其存储到输出数组(作为热身;顺便说一句。我所做的所有计算,在数学上都是正确的)。所以首先我在 1 个块,1 个线程中运行它。美好的。接下来,我尝试以这样一种方式拆分工作,即每个乘法只由一个线程完成一次。结果我的程序运行速度慢了大约 6 倍. 我什至感觉到为什么——获取有关 GPU 的信息的小惩罚,然后计算我应该使用多少块和线程,然后在每个线程中而不是单次乘法,现在我有大约 10 个额外的乘法来计算数组中的偏移量一个线程。一方面我试图找出如何改变这种不受欢迎的行为,另一方面我想在 SM 之间平均分布线程的“尾巴”。

我改写 - 也许我错了,但我想解决这个问题。我有 1G*2的小作业(仅此而已)——我应该用 1K 线程创建 1K 块,还是用 1 个线程创建 1M 块,用 1M 线程创建 1 个块,等等。到目前为止,我阅读了 GPU 属性,划分、划分和盲目地使用网格/块的每个维度的最大值(或所需的值,如果没有要计算的数据)。

编码

size是输入和输出数组的大小。一般来说:

output_array[i] = input_array[i]*2;

计算我需要多少块/线程。

size_t total_threads = props.maxThreadsPerMultiProcessor
                       * props.multiProcessorCount;
if (size<total_threads)
    total_threads = size;

size_t total_blocks = 1+(total_threads-1)/props.maxThreadsPerBlock;

size_t threads_per_block = 1+(total_threads-1)/total_blocks;  

Haveprops.maxGridSizeprops.maxThreadsDimI 以类似的方式计算块和线程的尺寸 - 从total_blocksthreads_per_block.

然后是杀手部分,计算线程的偏移量(“内部”线程):

size_t offset = threadIdx.z;
size_t dim = blockDim.x;
offset += threadIdx.y*dim;
dim *= blockDim.y;
offset += threadIdx.z*dim;
dim *= blockDim.z;
offset += blockIdx.x*dim;
dim *= gridDim.x;
offset += blockIdx.y*dim;
dim *= gridDim.y;

size_t chunk = 1+(size-1)/dim;

所以现在我有了当前线程的起始偏移量,以及用于乘法的数组(块)中的数据量。我没有在grimDim.z上面使用,因为 AFAIK 总是 1,对吧?

4

1 回答 1

6

这是一件不寻常的事情。鉴于您是 CUDA 初学者,在我看来,这样的问题表明您试图不正确地解决问题。您要解决的问题是什么?如果您在 SM X 与 SM Y 上执行特定线程,它对您的问题有何帮助?如果您希望机器发挥最大性能,请以使所有线程处理器和 SM 都可以处于活动状态的方式构建您的工作,并且实际上对所有人来说“工作量绰绰有余”。GPU 依靠超额订阅的资源来隐藏延迟。

作为 CUDA 初学者,您的目标应该是:

  • 在块和线程中创建足够的工作
  • 有效地访问内存(这主要与合并有关 - 你可以阅读)

确保“每个 SM 的工作量相等”没有任何好处。如果您在网格中创建足够多的块,则每个 SM工作量大致相等。这是调度器的工作,你应该让调度器来做。如果你没有创建足够的块,你的第一个目标应该是创建或找到更多的工作要做,而不是想出一个不会产生任何好处的每个块的花哨的工作分解。

Fermi GPU(例如)中的每个 SM 都有 32 个线程处理器。为了使这些处理器即使在由于内存访问等不可避免的机器停顿的情况下也保持忙碌,机器被设计为在发生停顿时通过交换另一个线程束 (32) 来隐藏延迟,以便处理可以继续. 为了促进这一点,您应该尝试为每个 SM 提供大量可用的经纱。这可以通过以下方式实现:

  • 网格中的许多线程块(至少是 GPU 中 SM 数量的 6 倍)
  • 每个线程块多个扭曲(可能至少 4 到 8 个扭曲,因此每个块有 128 到 256 个线程)

由于(Fermi)SM 一次总是执行 32 个线程,如果我的 GPU 中任何时刻的线程数少于 SM 数量的 32 倍,那么我的机器就没有得到充分利用。如果我的整个问题仅由 20 个线程组成,那么它的设计根本就不能很好地利用任何 GPU,并且将这 20 个线程分成多个 SM/线程块不太可能有任何明显的好处。

编辑:由于您不想发布您的代码,我将提出更多建议或意见。

  1. 您尝试修改一些代码,发现它运行速度较慢,然后跳到(我认为)错误的结论。
  2. 您可能应该熟悉一个简单的代码示例,例如vector add。它不是将每个元素相乘,但结构很接近。使用单个线程执行此向量添加实际上不会运行得更快。我想如果你研究这个例子,你会找到一种直接的方法来扩展它来做数组元素乘以 2。
  3. 没有人按照您概述的方式计算每个块的线程数。首先,每个块的线程数应该是 32 的倍数。其次,习惯上选择每个块的线程作为起点,并从中构建其他启动参数,而不是相反。对于一个大问题,只需从每个块 256 或 512 个线程开始,而无需为此进行计算。
  4. 根据您选择的线程块大小构建您的其他启动参数(网格大小)。您的问题本质上是一维的,因此一维线程块的一维网格是一个很好的起点。如果这个计算在 x 维度的最大块方面超过了机器限制,那么您可以让每个线程循环处理多个元素,或者扩展到 2D 网格(1D 线程块)。
  5. 您的偏移量计算不必要地复杂。请参阅向量添加示例,了解如何使用相对简单的偏移计算来创建线程网格来处理数组。
于 2013-02-10T22:21:44.703 回答