【CUDA】CUDA Hierarchy

【CUDA】CUDA 基本概念和 Hierarchy

CUDA 编程基础:Host 和 Device 工作流程

首先简单介绍CUDA 编程的基本概念:讲解 Host(CPU)与 Device(GPU)的区别、内存管理以及 CUDA 运行时的工作机制。


Host(主机) vs. Device(设备)

  • Host(CPU)
    • 执行通用代码(无需 CUDA 扩展)。
    • 使用主板上的 RAM 作为内存。
    • 运行标记为 __host__ 的函数。
  • Device(GPU)
    • 进行高效并行计算。
    • 使用 GPU 自带的 VRAM(视频内存、显存)。
    • 运行标记为 __global____device__ 的函数。

CUDA 程序运行流程

  1. 将数据从 Host 复制到 Device :使用 cudaMemcpy 传输输入数据到 GPU 的显存。
  2. 加载并执行 CUDA 内核:
    • 使用 GPU 并行执行内核函数(__global__)。
    • 内核函数处理传入的变量并完成计算。
  3. 将结果从 Device 复制回 Host:将处理后的数据从显存复制回主机内存。

CUDA 命名约定

  • 变量命名:
    • h_A:Host(CPU)上的变量,例如 A
    • d_A:Device(GPU)上的变量,例如 A
  • 函数修饰符:
    • __global__:GPU 上的内核函数,可以由 CPU 调用。它通常不返回值,而是通过修改传入的变量完成操作,例如矩阵乘法。
    • __device__:只能由 GPU 调用,用于在内核函数中执行特定任务。它类似于调用库函数,但只能在 GPU 内部执行。
    • __host__:只能在 CPU 上执行,与普通的 C/C++ 函数相似。

CUDA 内存管理

  • 显存分配 : 使用 cudaMalloc 在显存中分配内存。

    cpp 复制代码
    float *d_a, *d_b, *d_c;
    cudaMalloc(&d_a, N * N * sizeof(float));
    cudaMalloc(&d_b, N * N * sizeof(float));
    cudaMalloc(&d_c, N * N * sizeof(float));
  • 内存拷贝 : 使用 cudaMemcpy 在 Host 和 Device 间传输数据:

    • Host → Device(CPU → GPU):cudaMemcpyHostToDevice
    • Device → Host(GPU → CPU):cudaMemcpyDeviceToHost
    • Device → Device(GPU 内部或不同 GPU 之间):cudaMemcpyDeviceToDevice
  • 释放显存 : 使用 cudaFree 释放分配的显存。

    cpp 复制代码
    cudaFree(d_a);
    cudaFree(d_b);
    cudaFree(d_c);

CUDA 编译器(nvcc)

  • Host 代码
    • 被修改以支持 CUDA 内核。
    • 编译为普通的 x86 二进制。
  • Device 代码
    • 编译为 PTX(并行线程执行)代码。
    • PTX 是跨 GPU 代的稳定中间表示,通过 JIT(即时编译)转为本地 GPU 指令,实现向前兼容。

CUDA 的并行计算模型是基于层次化的线程结构设计的,这种设计为大规模并行计算提供了高效管理线程的方式。以下是 CUDA 的核心层次结构:


层次结构概览

  1. Kernel :
    • 定义:CUDA 程序的核心计算函数,运行在 GPU 上。
    • 工作方式:通过网格 (Grid) 和块 (Block) 的组织方式来并行化任务。
  2. Thread :
    • 定义:GPU 的基本执行单元,每个线程独立运行。
    • 特性:每个线程有自己的寄存器和局部内存空间。
  3. Thread Block (Block) :
    • 定义:线程的逻辑分组,一个 Block 包含若干个线程。
    • 重要性:Block 是 CUDA 的调度单元,提供线程间共享的共享内存。
    • 限制:每个 Block 中的线程数量有上限,通常是 1024 个线程(具体依赖于 GPU 架构)。
  4. Grid (网格) :
    • 定义:Block 的逻辑分组,一个 Grid 包含若干个 Block。
    • 重要性:通过组织多个 Block 实现大规模并行任务。

CUDA 的工作流

  1. 用户定义一个 Kernel 函数,用于描述 GPU 上的计算。
  2. 调用时通过 <<<Grid, Block>>> 来指定 Grid 和 Block 的规模。
  3. GPU 硬件会为每个线程分配一个唯一的索引,这些索引用于访问内存和分配任务。

4 个核心术语

这4个变量都是内置变量,由编译器自动提供,供核函数使用。

1. gridDim ⇒ 网格的维度
  • 定义gridDim 定义了 Grid 在每个维度上的 Block 数量。

  • 类型 :3D 变量,gridDim.x, gridDim.y, gridDim.z

  • 用途:决定网格规模,帮助计算全局索引。

  • 示例:

    cpp 复制代码
    dim3 grid(4, 3);  // 4 个 Block 在 X 方向,3 个 Block 在 Y 方向
    printf("Grid dimensions: %d x %d\n", gridDim.x, gridDim.y);

2. blockIdx ⇒ Block 的索引
  • 定义blockIdx 标识当前线程所属 Block 在 Grid 中的索引。

  • 类型 :3D 变量,blockIdx.x, blockIdx.y, blockIdx.z

  • 用途:结合线程索引计算全局索引。

  • 范围[0, gridDim.{x|y|z} - 1]

  • 示例:

    cpp 复制代码
    int block_index = blockIdx.x;  // 当前 Block 在 X 方向的索引

3. blockDim ⇒ Block 的维度
  • 定义blockDim 表示每个 Block 在每个维度上的线程数量。

  • 类型 :3D 变量,blockDim.x, blockDim.y, blockDim.z

  • 用途:用于定义 Block 内线程的局部索引范围。

  • 范围:由 Kernel 配置时的第二个参数决定。

  • 示例:

    cpp 复制代码
    dim3 block(16, 16);  // 每个 Block 包含 16x16 个线程
    printf("Block dimensions: %d x %d\n", blockDim.x, blockDim.y);

4. threadIdx ⇒ 线程的索引
  • 定义threadIdx 表示当前线程在所在 Block 中的索引。

  • 类型 :3D 变量,threadIdx.x, threadIdx.y, threadIdx.z

  • 用途 :配合 blockIdxblockDim 计算全局线程索引。

  • 范围[0, blockDim.{x|y|z} - 1]

  • 示例:

    cpp 复制代码
    int thread_index = threadIdx.x;  // 当前线程在 X 方向的索引

可以网格是由多个小长方体(block)组成的一个大长方体(grid),其中小长方体又是由多个更小的长方体(thread)组成。


线程束 (Warp)

定义
  • 线程束(Warp) 是 CUDA 调度的基本单元,每个 Warp 包含 32 个线程
  • Warp 内的线程以 SIMD(单指令多数据) 模式运行:所有线程执行相同指令,但操作的数据可以不同。

线程束的特性
  1. 执行同步
    • 一个 Warp 内的所有线程在同一个时钟周期内执行同一条指令。
  2. 线程束分歧 (Warp Divergence)
    • 如果 Warp 内的线程需要执行不同的分支(例如 if/else),Warp 会被拆分成多个子任务,依次完成分支,导致性能下降。
  3. 调度单位
    • Warp 是 CUDA 的硬件调度单位。一个 Block 中的线程数量如果不是 32 的倍数,会浪费部分调度资源。完整代码示例

实例

cpp 复制代码
#include <stdio.h>

__global__ void Whoami(void){
    int block_id = blockIdx.x + blockIdx.y * gridDim.x +
        blockIdx.z * gridDim.x * gridDim.y;

    int block_offset = block_id * blockDim.x * blockDim.y * blockDim.z;

    int thread_offset = threadIdx.x + threadIdx.y * blockDim.x +
        threadIdx.z * blockDim.x * blockDim.y;

    int id = block_offset + thread_offset;

    printf("%04d | Block(%d %d %d) = %3d | Thread(%d %d %d) = %3d\n",
        id, blockIdx.x, blockIdx.y, blockIdx.z, block_id,
        threadIdx.x, threadIdx.y, threadIdx.z, thread_offset);
}


int main(int argc,char** argv){
    const int b_x = 2, b_y = 3, b_z = 4;
    const int t_x = 4, t_y = 4, t_z = 4;

    int blocks_per_grid = b_x * b_y * b_z;
    int threads_per_block = t_x * t_y * t_z;

    printf("%d block/grid\n", blocks_per_grid);
    printf("%d threads/block\n", threads_per_block);
    printf("%d total threads\n", blocks_per_grid * threads_per_block);

    dim3 blocksPerGrid(b_x, b_y, b_z);
    dim3 threadsPerBlock(t_x, t_y, t_z);

    Whoami<<<blocksPerGrid, threadsPerBlock>>>();
    cudaDeviceSynchronize();

    return 0;
}

这段代码展示了如何使用 gridDimblockIdxblockDimthreadIdx 来理解grid,block,thread的层级结构。通过输出你也会看到线程束 (Warp)的表现,block中的线程按32分为了两部分,所以同一个block的输出被分为了两部分。

参考:https://github.com/Infatoshi/cuda-course/tree/master/05_Writing_your_First_Kernels

相关推荐
不爱学英文的码字机器5 分钟前
[操作系统] 进程程序替换
linux·运维·服务器
酥暮沐23 分钟前
LVS集群
linux·服务器·lvs
阿昊真人1 小时前
node 程序占用处理方法与后台运行方法
linux·编辑器·vim
Lllongroad4 小时前
SPI通信及设备驱动
linux·stm32·单片机
Dragon水魅5 小时前
Ubuntu22.04 配置deepseek知识库
linux·服务器·深度学习·ubuntu
96777 小时前
如何将 Jupyter Notebook (.ipynb) 文件转换为 Python (.py) 文件
linux·python·jupyter
forestqq9 小时前
openEuler22.03LTS系统升级docker至26.1.4以支持启用ip6tables功能
linux·运维·docker
蓝创精英团队12 小时前
基于Ubuntu Ollama 部署 DeepSeek-R132B 聊天大模型(附带流式接口调用示例)
linux·运维·ubuntu·deepseek
快去睡觉~13 小时前
Linux之Http协议分析以及cookie和session
linux·运维·http
致奋斗的我们13 小时前
项目:利用rsync备份全网服务器数据
linux·运维·服务器·开发语言·github·rsync·openeuler