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);
相关推荐
梵尔纳多2 小时前
OpenGL着色器语言(GLSL)
c++·opengl·着色器
net3m332 小时前
单片机屏幕多级菜单系统之当前屏幕号+屏幕菜单当前深度 机制
c语言·c++·算法
mmz12072 小时前
二分查找(c++)
开发语言·c++·算法
陌路202 小时前
C++30 STL容器 -deque双端队列
开发语言·c++
AI视觉网奇3 小时前
ue 自己制作插件 c++
c++·ue5
Jayden_Ruan3 小时前
C++分解质因数
数据结构·c++·算法
微露清风3 小时前
系统性学习C++-第二十讲-哈希表实现
c++·学习·散列表
清 澜4 小时前
c++高频知识点总结 第 1 章:语言基础与预处理
c++·人工智能·面试
Taiyuuki4 小时前
WebGPU 开发者福音!在 VS Code 中实时预览你的WGSL着色器作品
前端·gpu·图形学