跳到主要内容

CUDA 编程模型

CUDA 编程模型是理解 GPU 并行计算的基础。本章将详细介绍 CUDA 的异构计算模型、核函数的概念以及基本的编程流程。

异构计算模型

CUDA 采用异构计算模型,程序在 CPU(Host,主机)和 GPU(Device,设备)上协同执行。这种设计充分利用了 CPU 擅长逻辑控制和串行计算、GPU 擅长大规模并行计算的特点。

Host 与 Device

概念说明角色
HostCPU 及其内存负责逻辑控制、数据准备、结果处理
DeviceGPU 及其显存负责大规模并行计算
Kernel核函数在 Device 上并行执行的函数

执行流程

一个典型的 CUDA 程序执行流程如下:

代码示例

下面是一个完整的 CUDA 程序示例,展示向量加法的实现:

#include <cuda_runtime.h>
#include <stdio.h>

#define N 1024

__global__ void vectorAdd(float *a, float *b, float *c, int n) {
int idx = blockIdx.x * blockDim.x + threadIdx.x;
if (idx < n) {
c[idx] = a[idx] + b[idx];
}
}

int main() {
float *h_a, *h_b, *h_c;
float *d_a, *d_b, *d_c;
size_t size = N * sizeof(float);

h_a = (float*)malloc(size);
h_b = (float*)malloc(size);
h_c = (float*)malloc(size);

for (int i = 0; i < N; i++) {
h_a[i] = 1.0f;
h_b[i] = 2.0f;
}

cudaMalloc(&d_a, size);
cudaMalloc(&d_b, size);
cudaMalloc(&d_c, size);

cudaMemcpy(d_a, h_a, size, cudaMemcpyHostToDevice);
cudaMemcpy(d_b, h_b, size, cudaMemcpyHostToDevice);

int threadsPerBlock = 256;
int blocksPerGrid = (N + threadsPerBlock - 1) / threadsPerBlock;
vectorAdd<<<blocksPerGrid, threadsPerBlock>>>(d_a, d_b, d_c, N);

cudaMemcpy(h_c, d_c, size, cudaMemcpyDeviceToHost);

cudaFree(d_a);
cudaFree(d_b);
cudaFree(d_c);
free(h_a);
free(h_b);
free(h_c);

return 0;
}

核函数(Kernel)

核函数是在 GPU 上并行执行的函数,是 CUDA 编程的核心概念。

核函数定义

使用 __global__ 修饰符定义核函数:

__global__ void kernelFunction(parameters) {
}

核函数的特点:

  1. 返回类型必须是 void
  2. 在 Device 上执行,由 Host 调用
  3. 每个线程独立执行一份副本
  4. 不能是类的成员函数(C++11 前)

函数修饰符

CUDA 提供了多种函数修饰符:

修饰符执行位置调用位置说明
__global__DeviceHost 或 Device核函数,返回 void
__device__DeviceDevice设备函数,只能被设备代码调用
__host__HostHost主机函数(默认)
__host__ __device__Host/DeviceHost/Device同时在 Host 和 Device 上可用
__device__ float deviceFunction(float x) {
return x * 2.0f;
}

__global__ void kernel(float *data) {
int idx = threadIdx.x;
data[idx] = deviceFunction(data[idx]);
}

__host__ __device__ int commonFunction(int x) {
return x + 1;
}

核函数调用

核函数使用特殊的执行配置语法调用:

kernel<<<gridSize, blockSize>>>(arguments);

其中:

  • gridSize:Grid 中的 Block 数量
  • blockSize:每个 Block 中的 Thread 数量

完整形式:

kernel<<<gridDim, blockDim, sharedMemSize, stream>>>(args);
参数类型说明
gridDimdim3Grid 维度(Block 数量)
blockDimdim3Block 维度(Thread 数量)
sharedMemSizesize_t动态共享内存大小(字节)
streamcudaStream_t执行流

dim3 类型

dim3 是 CUDA 定义的三维向量类型,用于指定 Grid 和 Block 的维度:

dim3 grid(2, 2, 1);
dim3 block(16, 16, 1);

kernel<<<grid, block>>>(args);

一维示例:

int n = 1024;
int blockSize = 256;
int gridSize = (n + blockSize - 1) / blockSize;

kernel<<<gridSize, blockSize>>>(data, n);

二维示例(适合图像处理):

dim3 blockSize(16, 16);
dim3 gridSize((width + 15) / 16, (height + 15) / 16);

imageKernel<<<gridSize, blockSize>>>(image, width, height);

内置变量

CUDA 提供了一组内置变量,用于在核函数中获取线程信息:

线程索引变量

变量类型说明
threadIdxuint3线程在 Block 中的索引
blockIdxuint3Block 在 Grid 中的索引
blockDimdim3Block 的维度(线程数量)
gridDimdim3Grid 的维度(Block 数量)

计算全局索引

一维 Grid 和 Block:

int idx = blockIdx.x * blockDim.x + threadIdx.x;

二维 Grid 和 Block:

int x = blockIdx.x * blockDim.x + threadIdx.x;
int y = blockIdx.y * blockDim.y + threadIdx.y;
int idx = y * width + x;

三维 Grid 和 Block:

int x = blockIdx.x * blockDim.x + threadIdx.x;
int y = blockIdx.y * blockDim.y + threadIdx.y;
int z = blockIdx.z * blockDim.z + threadIdx.z;
int idx = z * width * height + y * width + x;

边界检查

由于 Grid 大小可能不是 Block 大小的整数倍,需要进行边界检查:

__global__ void kernel(float *data, int n) {
int idx = blockIdx.x * blockDim.x + threadIdx.x;

if (idx < n) {
data[idx] = data[idx] * 2.0f;
}
}

内存管理函数

CUDA 提供了一组内存管理函数,用于在 Host 和 Device 之间分配、拷贝和释放内存。

显存分配

cudaError_t cudaMalloc(void **devPtr, size_t size);

示例:

float *d_data;
cudaMalloc(&d_data, N * sizeof(float));

显存释放

cudaError_t cudaFree(void *devPtr);

示例:

cudaFree(d_data);

内存拷贝

cudaError_t cudaMemcpy(void *dst, const void *src, size_t count, cudaMemcpyKind kind);

拷贝方向:

枚举值说明
cudaMemcpyHostToDeviceHost → Device
cudaMemcpyDeviceToHostDevice → Host
cudaMemcpyDeviceToDeviceDevice → Device
cudaMemcpyHostToHostHost → Host

示例:

cudaMemcpy(d_data, h_data, size, cudaMemcpyHostToDevice);
cudaMemcpy(h_result, d_data, size, cudaMemcpyDeviceToHost);

错误处理

所有 CUDA 运行时 API 函数都返回 cudaError_t 类型:

cudaError_t err = cudaMalloc(&d_data, size);
if (err != cudaSuccess) {
printf("CUDA error: %s\n", cudaGetErrorString(err));
exit(EXIT_FAILURE);
}

推荐使用宏简化错误处理:

#define CUDA_CHECK(call) \
do { \
cudaError_t err = call; \
if (err != cudaSuccess) { \
fprintf(stderr, "CUDA error at %s:%d: %s\n", \
__FILE__, __LINE__, cudaGetErrorString(err)); \
exit(EXIT_FAILURE); \
} \
} while(0)

CUDA_CHECK(cudaMalloc(&d_data, size));

核函数错误检查

核函数不直接返回错误,需要使用 cudaGetLastError() 检查:

kernel<<<grid, block>>>(args);

cudaError_t err = cudaGetLastError();
if (err != cudaSuccess) {
printf("Kernel launch error: %s\n", cudaGetErrorString(err));
}

cudaDeviceSynchronize();
err = cudaGetLastError();
if (err != cudaSuccess) {
printf("Kernel execution error: %s\n", cudaGetErrorString(err));
}

完整示例:向量加法

下面是一个完整的向量加法示例,包含错误处理:

#include <cuda_runtime.h>
#include <stdio.h>

#define CUDA_CHECK(call) \
do { \
cudaError_t err = call; \
if (err != cudaSuccess) { \
fprintf(stderr, "CUDA error at %s:%d: %s\n", \
__FILE__, __LINE__, cudaGetErrorString(err)); \
exit(EXIT_FAILURE); \
} \
} while(0)

__global__ void vectorAdd(const float *a, const float *b, float *c, int n) {
int idx = blockIdx.x * blockDim.x + threadIdx.x;
if (idx < n) {
c[idx] = a[idx] + b[idx];
}
}

int main() {
const int N = 1 << 20;
const size_t size = N * sizeof(float);

float *h_a = (float*)malloc(size);
float *h_b = (float*)malloc(size);
float *h_c = (float*)malloc(size);

for (int i = 0; i < N; i++) {
h_a[i] = 1.0f;
h_b[i] = 2.0f;
}

float *d_a, *d_b, *d_c;
CUDA_CHECK(cudaMalloc(&d_a, size));
CUDA_CHECK(cudaMalloc(&d_b, size));
CUDA_CHECK(cudaMalloc(&d_c, size));

CUDA_CHECK(cudaMemcpy(d_a, h_a, size, cudaMemcpyHostToDevice));
CUDA_CHECK(cudaMemcpy(d_b, h_b, size, cudaMemcpyHostToDevice));

int blockSize = 256;
int gridSize = (N + blockSize - 1) / blockSize;

vectorAdd<<<gridSize, blockSize>>>(d_a, d_b, d_c, N);

CUDA_CHECK(cudaGetLastError());
CUDA_CHECK(cudaDeviceSynchronize());

CUDA_CHECK(cudaMemcpy(h_c, d_c, size, cudaMemcpyDeviceToHost));

bool success = true;
for (int i = 0; i < N; i++) {
if (fabs(h_c[i] - 3.0f) > 1e-5) {
success = false;
break;
}
}
printf("Result: %s\n", success ? "PASS" : "FAIL");

CUDA_CHECK(cudaFree(d_a));
CUDA_CHECK(cudaFree(d_b));
CUDA_CHECK(cudaFree(d_c));
free(h_a);
free(h_b);
free(h_c);

return 0;
}

小结

本章介绍了 CUDA 编程模型的核心概念:

  1. 异构计算模型:Host 与 Device 协同执行
  2. 核函数:使用 __global__ 定义,在 GPU 上并行执行
  3. 执行配置:通过 <<<grid, block>>> 指定线程组织
  4. 内置变量threadIdxblockIdxblockDimgridDim
  5. 内存管理cudaMalloccudaMemcpycudaFree

下一章将详细介绍 线程层次结构,深入理解 Grid、Block、Thread 的组织方式。