GPU 多线程与执行单位


在 GPU 编程中,特别是在 NVIDIA 的 CUDA 架构中,有一些特定的术语用于描述并行计算的基本单位:线程 (thread)束 (warp)线程块 (block)网格 (grid)核 (kernel)。这些概念对理解 GPU 并行计算至关重要。

1. 线程 (Thread)

线程是执行代码的最小单位。在 GPU 上,每个线程运行一小段代码,通常称为 内核 (kernel)。每个线程都有自己的寄存器、局部内存和程序计数器,可以独立执行指令。

2. 线程束 (Warp)

线程束是 GPU 中执行调度的基本单位。一个线程束 (warp) 是一组由 GPU 硬件同时调度和执行的 32 个线程(在 NVIDIA CUDA 架构中)。在同一个线程束内的所有线程在同一个时钟周期内执行相同的指令。

但是,由于不同线程可能会处理不同的数据,线程束中的线程可能会遇到条件分支(例如 if-else 语句)。如果这些分支导致线程需要执行不同的指令集,就会发生 线程束分裂 (warp divergence),这会降低性能,因为分裂的线程束必须顺序执行不同的分支。

3. 线程块 (Block)

线程块是 GPU 中组织线程的一个基本单元。一个线程块由多个线程组成,通常可以有 32 到 1024 个线程,具体取决于硬件的支持。线程块的线程可以共享局部内存(称为 共享内存 (shared memory)),并可以通过线程同步原语(如 __syncthreads())进行协作。

线程块是被分配到 流式多处理器 (Streaming Multiprocessor, SM) 上执行的。每个线程块中的线程可以在同一个 SM 上共享资源。线程块内的线程执行时并行处理,并且在同一块中的线程可以协作计算。

4. 网格 (Grid)

一个网格由多个线程块组成。GPU 在执行任务时,会将一个网格中的所有线程块分配到可用的流式多处理器上并行执行。每个线程块相互独立,但可以通过全局内存进行通信。

5. 核 (kernel)

在 GPU 编程中,尤其是 CUDA 和 OpenCL 编程中,kernel 是一个非常重要的概念。它是指在 GPU 上执行的函数,负责处理数据并执行并行计算。具体来说,kernel 可以看作是在 GPU 上被所有线程执行的代码。

在 GPU 编程中,尤其是 CUDA 和 OpenCL 编程中,kernel 是一个非常重要的概念。它是指在 GPU 上执行的函数,负责处理数据并执行并行计算。具体来说,kernel 可以看作是在 GPU 上被所有线程执行的代码。

Kernel 的详细解释:

  1. Kernel 是什么?

    • Kernel 是运行在 GPU 上的代码块,相当于在 CPU 上运行的函数。
    • 它由 GPU 上的多个线程并行执行,而每个线程都执行相同的代码(即 kernel 函数),但通常处理不同的输入数据。
    • Kernel 是由 CPU 发起的,它被“启动”后,GPU 会并行地在多个线程上运行这个 kernel。
  2. Kernel 是如何执行的?

    • 当我们启动一个 kernel 时,我们需要定义 GPU 线程的分布结构,即定义有多少个线程要执行这个 kernel。线程的结构一般用 gridblock 来表示。
    • Grid 是由多个 block 组成的,而每个 block 又由多个 thread 组成。启动 kernel 时,我们要指定 block 和 grid 的大小(即线程的总数和分布方式)。

    例如,在 CUDA 中,启动 kernel 的语法是:

    kernel_function<<<gridDim, blockDim>>>(params);

    这里 gridDim 表示 grid 中 block 的数量,blockDim 表示每个 block 中线程的数量。

  3. Kernel 的并行性

    • 当一个 kernel 被启动时,GPU 会为 grid 中的每个 block 分配一组硬件线程来执行该 kernel 中的代码。
    • 每个 block 运行一组线程,每个线程独立执行 kernel 中的指令,并处理不同的数据。
    • 不同 block 之间没有通信,但 block 内部的线程可以通过共享内存进行通信与同步。
  4. Kernel 函数的特点

    • Kernel 函数在 CPU 上定义,但由 GPU 上的线程执行。
    • 这些线程共享相同的代码,但它们的执行是独立的,处理的数据可以不同。每个线程都有一个唯一的索引(thread ID),用于区分它处理的数据片段。
    • Kernel 函数的执行是异步的,也就是说,当 GPU 开始执行 kernel 时,CPU 可以继续执行其他任务,而不必等待 GPU 完成计算。
  5. Kernel 的例子
    假设我们要对数组中的每个元素执行平方操作,在 CUDA 中,kernel 函数可能是这样:

    __global__ void square(float *d_out, float *d_in) {
        int idx = threadIdx.x;  // 获取线程的索引
        d_out[idx] = d_in[idx] * d_in[idx];  // 线程执行平方操作
    }
    • __global__ 关键字表示这是一个 kernel 函数,将在 GPU 上执行。
    • threadIdx.x 是当前线程在 block 中的索引,表示每个线程处理的数组元素。
    • 这个 kernel 会并行运行在 GPU 上的多个线程中,每个线程都处理数组中的一个元素。

    我们可以这样启动这个 kernel:

    int numThreads = 256;
    square<<<1, numThreads>>>(d_out, d_in);  // 启动 kernel,每个线程处理一个元素

    在这个例子中,我们启动了 1 个 block,block 中包含 256 个线程,每个线程处理数组中的一个元素。

Kernel 与 CPU 代码的区别

  • CPU 上的函数:一次只能执行一个任务(通常是单线程或多线程的串行处理)。
  • GPU 上的 kernel:成百上千个线程并行执行,可以同时处理大量数据,极大提高了处理效率,尤其适合处理数据并行的任务。

Kernel 小结

Kernel 是 GPU 上并行执行的函数。它由 CPU 端启动,GPU 上的线程并行执行 kernel 函数中的代码。通过定义线程的 grid 和 block 结构,kernel 可以高效地处理大规模并行计算任务。

关系总结

  • 线程 (thread):最小的执行单元,每个线程执行一个内核函数。
  • 线程束 (warp):一组由硬件同时调度的 32 个线程。
  • 线程块 (block):一组线程,可以在共享内存中协作工作。
  • 网格 (grid):由多个线程块组成,用于解决大型并行问题。
  • 核 (kernel):运行在 GPU 上的代码块,相当于在 CPU 上运行的函数。

示例

如果我们要在一个 GPU 上执行一个简单的矩阵乘法计算,我们可能会将每个线程分配给矩阵中的一个元素计算。多个线程组成一个线程块,线程块可以同时处理矩阵的不同部分,而多个线程束会在同一个块内执行。如果数据规模很大,整个网格中可以有很多个线程块在并行工作。

通过这些层次化的结构,GPU 能够有效地处理大量的并行任务。


Author: Yixiang Zhang
Reprint policy: All articles in this blog are used except for special statements CC BY 4.0 reprint policy. If reproduced, please indicate source Yixiang Zhang !
评论
  TOC