Chapter 5.1.1: 编写你的第一个GPU kernel——Cuda Basics

文章目录

本文介绍GPU编程的基础,包括一些基本术语以及如何编写一个kernel实现两个矩阵的加法

基础术语

Kernel

kernel是GPU跑的的一个特殊的函数,GPU有很多同时工作的线程,而kernel就像发给他们的指令。我们通常用__global__ 关键字来标识一个kernel,其返回值只能为空

cpp 复制代码
__global__ void addNumbers(int *a, int *b, int *result) {
    *result = *a + *b;
}

Grid

grid代表单个kernel 召唤时的所有线程集/线程块,是整个执行空间。当你启动kernel,你需要指定grid的维度,需要定义有多少blocks会被创建,可以是1D,2D或者3D

比如:处理一个大图像,每个block可以处理图像的一个16x16的区域

Block

block是一组线程,他们可以合作和快速在共享内存中分享数据1D,2D或者3D。

一个block的线程可以:

  • 共享内存
  • 互相之间同步
  • 在任务上协作

Threads

线程是最小的执行单元,每个线程独立执行kernel 代码. 在一个block里,线程被一个独特的线程ID唯一定义,这个ID让你可以获取某个数据或者根据线程在block的位置执行不同的操作

理解CUDA的线程索引

刚刚提到"在一个block里,线程被一个独特的线程ID唯一定义",如下的变量通常被用来定义这个ID:

  1. threadIdx:

    • 一个三元组 (threadIdx.x, threadIdx.y, threadIdx.z) 描述了线程在一个block的位置.
  2. blockDim:

    • 一个三元组 (blockDim.x, blockDim.y, blockDim.z) 指定了block的维度
  3. blockIdx:

    • 一个三元组(blockIdx.x, blockIdx.y, blockIdx.z) 描述了block在Grid中的位置.
  4. gridDim:

    • 一个三元组 (gridDim.x, gridDim.y, gridDim.z) 指定了Grid的维度

计算全局的线程ID

对于一个一维的线程ID,可以通过如下公式计算:

cpp 复制代码
int globalThreadId = blockIdx.x * blockDim.x + threadIdx.x;

*辅助类型和函数

dim3 描述之前三元组的简单方式

cpp 复制代码
dim3 blockSize(16, 16, 1);  // 16x16x1 threads per block
dim3 gridSize(8, 8, 1);     // 8x8x1 blocks in grid

<<<>>>

一个启动kernel的特殊括号,指定了grid size和block size, shared memory size, and stream for kernel execution. 合理配置这些参数对于GPU编程和性能优化是至关重要的

cpp 复制代码
addNumbers<<<gridSize, blockSize>>>(a, b, result);

GPU内存以及管理

GPU内存的类型

  • global memory:
    • 主存,所有线程共享
    • 慢但尺寸大
    • 用来所有线程共享数据
    • 例如:大矩阵或者数据集
  • shared memory:
    • 每个block中所有线程共享的内存
    • 非常快但尺寸小
    • 用来做block内所有线程需要频繁共享的数据
    • 例如: 临时变量或者小的LUT(查找表)
  • registers:
    • 最快的内存,每个线程独占
    • 存放临时变量
    • 数量很少,所以要用好它们
    • 例如:循环变量或者计算的中间结果
  • constant memory:
    • 所有线程的只读内存
    • 用来存放在kernel运行期间不变的常量
    • 目的是缓存来做快速存取
    • 例如:常量或者配置参数
  • local memory:
    • 当寄存器不够(spilling)时使用
    • 慢(存放在全局内存)
    • 如果可以请避免
    • 例如:装不下寄存器的大矩阵或者变量

内存管理

cudaMalloc: 为GPU分配内存

cpp 复制代码
int *device_array;
cudaMalloc(&device_array, size * sizeof(int));

cudaMemcpy

It can copy from device to host, host to device, or device to device (edge cases)

  • host to device ⇒ CPU to GPU

  • device to host ⇒ GPU to CPU

  • device to device ⇒ GPU location to different GPU location

  • cudaMemcpyHostToDevice , cudaMemcpyDeviceToHost , or cudaMemcpyDeviceToDevice

  • cudaFree will free memory on the device

  • Example:

cpp 复制代码
cudaMemcpy(device_array, host_array, size * sizeof(int), cudaMemcpyHostToDevice);

cudaDeviceSynchronize()

默认 CPU and GPU 一起工作降低延迟,有时候我们需要CPU等待GPU,我们需要使用 cudaDeviceSynchronize().

  • 适合需要结果才能继续的场景
  • 例如在运行某些benchmark的时候:
cpp 复制代码
kernel<<<gridSize, blockSize>>>(data);
cudaDeviceSynchronize();  // Wait for kernel to finish
printf("Kernel completed!");

综合应用

如下是两个数组相加的kernel实现,先定义了每个block 256个线程,然后计算grid的大小(向上取整)并且线程内设置了防止数组越界的限制

cpp 复制代码
// Kernel definition
__global__ void addArrays(int *a, int *b, int *c, int size) {
    // Calculate unique index for this thread
    int index = blockIdx.x * blockDim.x + threadIdx.x;
    
    // Make sure we don't go past array bounds
    if (index < size) {
        c[index] = a[index] + b[index];
    }
}

// In main function:
dim3 blockSize(256);  // 256 threads per block
dim3 gridSize((size + blockSize.x - 1) / blockSize.x);  // Calculate needed blocks
addArrays<<<gridSize, blockSize>>>(d_a, d_b, d_c, size);
相关推荐
软件课代表CiCi2 分钟前
运行库是什么?电脑缺运行库怎么办?c++运行库缺失修复全攻略
开发语言·c++·windows·电脑·dll修复·dll丢失·运行库
蒸蒸yyyyzwd1 小时前
从面经中学到的三件事
c++·笔记
时时三省1 小时前
【时时三省】计算机c语言二级考试选择题重点
c++
ThornArmor4 小时前
重铸1996|肉体渲染:关节的裂缝:分段模型积木拼装与未成熟的 RSP 矩阵骨骼动画形变
c语言·汇编·c++·python·硬件架构·游戏机
Elsa️7466 小时前
算法一周刷题总结
c++·算法
风合星语6 小时前
2026 机器人工程实例(五):让 Allegro Hand 在 MuJoCo 中完成接触抓取——抓取状态机、稳定抬升与受控释放
c++·python·机器人·仿真
小羊没烦恼!6 小时前
Memory 记忆设计讨论:Agent Memory 的数据模型可以怎么设计
java·开发语言·前端·c++·c#
welfare9627 小时前
新号别搞:内存管理+模板初阶+STL简介
开发语言·c++
雪落漂泊8 小时前
C++11(上)
开发语言·c++
zh路西法9 小时前
【玩转VLA具身智能机械臂】(四):点云分割与 OBB 夹取——从 PCA 失效到主动扫描
c++·机械臂·pca·vla·planner·obb·moveit2