CUDA 并行层次
CUDA 的 SIMT 编程模型中,每个独立运行的部分称为一个线程(thread)。通过建立大量的线程,可以把任务分解为并行运行的多个部分,充分利用 GPU 的并行计算能力。然而,线程的组织是非常复杂的包括了多个层次,这也是 CUDA 里面大量名词的来历。
Kernel
在 CUDA 编程中,一个计算任务通常被分成若干个同样的核函数并行计算,每个核函数自行通过本身的索引和数据处理,执行大致相同的计算过程。由于每个核函数执行的任务类似,这使得 GPU 得以充分利用其中大量的计算单元。
Thread
线程是 GPU 并行计算中的最小单元。通常来说,一个任务中的的线程应该执行同样的任务,才能高效发挥 GPU 的性能,这点类似于 SIMD 中的 lane。与 SIMD 不同的是,GPU 通过设计了一系列的“兜底”方法,使得各个线程执行不同指令,即有分歧(divergence)时,程序仍然可以执行,只是效率不高。
Warp
Warp 是线程之上的第一个组织层次,除非无法除尽,否则统一为 32 个线程。这点上,GPU 可以看作类似于 32 lane 的 SIMD 处理器,即为了高效计算,这 32 个线程应该执行相同的任务。
同时,CUDA 将 SIMD 中多个 lane 的内存读写,即 scatter/gather,改为允许每个线程自由读取任意内存,由专门的 memory coalescing 单元去处理一组内存访问。该方法在处理连续内存时非常自然,也保留了线程的独立性。然而硬件资源终究是有限的,这要求内存读写满足一定的特点,尤其是读取非连续内存时存在复杂的 bank conflict 问题,可能会使得内存读写性能严重下降。
Block
一个 block 中所有的内存限制在一个流多处理器(SM)上执行,这使得 block 共享一个 SM 中的所有资源,包括共享内存和同步原语__syncthread
,是进行同步的基本单元。
受到硬件资源的限制,一个 block 中可以有最多 1024 个线程,当然实际上受到具体计算资源的限制,取 128 或者 256 也很多。
在较新的 GPU 上,为了实现更加复杂的任务,又增加了 block cluster、cooperative group 等同步单元,起到的作用和 block 是类似的。与之相比,block 可以发挥同步作用,是所有的线程都在同一个 SM 上带来的必然结果,并非特殊的设计。
Grid
经典的 CUDA 程序中,一个 grid 需要完成一整个计算任务。包括若干个 block。