CUDA 中的每个内核调用是否保证唯一的线程 ID?

Is Unique Thread Id guaranteed for each Kernel Call in CUDA?

我最近开始使用 Cuda,我有 C++ 的多线程、多进程编码经验,Java 和 Python。

使用 PyCuda,我看到了这样的示例代码,

ker = SourceModule("""
__global__ void scalar_multiply_kernel(float *outvec, float scalar, float *vec)
{
     int i = threadIdx.x;
     outvec[i] = scalar*vec[i];
}
""")

似乎线程 id 本身参与了代码的逻辑。然后问题是是否有足够的线程 ID 覆盖我的整个数组(我显然需要其索引到达那里的所有元素),如果我更改数组的大小会发生什么。

索引总是在 0 和 N 之间吗?

在 CUDA 中,线程 ID 只是每个所谓的线程块唯一的,这意味着,您的示例内核只做正确的事情,只有一个块在工作。这可能是在早期示例中完成的,目的是让您更容易理解这些想法,但就性能而言,这样做通常是一件非常糟糕的事情:

对于一个块,您只能使用 GPU 中的众多流式多处理器 (SM) 之一,即使该 SM 在等待时有足够的并行工作要做时,也只能隐藏内存访问延迟。

如果您的内核不包含循环以便每个线程可以计算多个元素,则单个线程块还会限制您的线程数量并因此限制问题的大小。

内核执行被认为是强烈层次化的:为了简单起见,我们将自己限制为一维索引,内核在所谓的 gridDim.x 线程块网格上执行,每个线程块包含 blockDim.x 个线程,每个线程编号块 threadIdx.x,而每个块通过 blockIdx.x.

编号

要获得线程的唯一 ID(以理想情况下使用硬件从数组加载元素的方式),您必须采用 blockIdx.x * blockDim.x + threadIdx.x。如果每个线程要计算一个以上的元素,则使用

形式的循环
for (int i = blockIdx.x * blockDim.x + threadIdx.x; i < InputSize; i += gridDim.x * blockDim.x) { 
/* ... */
}

这称为网格步幅循环,因为 gridDim.x * blockDim.x 是在内核上工作的所有线程数。不同的步幅(尤其是在连续元素上工作的线程:stride = 1)可能会起作用,但由于内存访问模式不理想,速度会慢得多。