CUDA(Compute Unified Device Architecture)是 NVIDIA 推出的并行计算平台和编程模型。本文将介绍 CUDA 编程的基础知识,包括环境搭建、基本概念和常用编程技巧。
CUDA 简介
CUDA 是一种通用的并行计算架构,允许开发者使用 C/C++ 语言编写在 GPU 上运行的程序。CUDA 的主要特点包括:
- 易于使用:基于 C/C++,学习曲线平缓
- 高性能:充分利用 GPU 的并行计算能力
- 丰富的库:提供 cuBLAS、cuDNN、cuFFT 等高性能库
- 跨平台:支持 Windows、Linux、macOS
环境搭建
1. 安装 NVIDIA 驱动
首先需要安装 NVIDIA 显卡驱动:
# Ubuntu/Debian
sudo apt-get update
sudo apt-get install nvidia-driver-535
# 检查驱动是否安装成功
nvidia-smi
2. 安装 CUDA Toolkit
从 NVIDIA 官网下载并安装 CUDA Toolkit:
# Ubuntu/Debian
wget https://developer.download.nvidia.com/compute/cuda/repos/ubuntu2204/x86_64/cuda-ubuntu2204.pin
sudo mv cuda-ubuntu2204.pin /etc/apt/preferences.d/cuda-repository-pin-600
wget https://developer.download.nvidia.com/compute/cuda/12.2.0/local_installers/cuda-repo-ubuntu2204-12-2-local_12.2.0-535.54.03-1_amd64.deb
sudo dpkg -i cuda-repo-ubuntu2204-12-2-local_12.2.0-535.54.03-1_amd64.deb
sudo cp /var/cuda-repo-ubuntu2204-12-2-local/cuda-*-keyring.gpg /usr/share/keyrings/
sudo apt-get update
sudo apt-get install cuda
# 配置环境变量
echo 'export PATH=/usr/local/cuda/bin:$PATH' >> ~/.bashrc
echo 'export LD_LIBRARY_PATH=/usr/local/cuda/lib64:$LD_LIBRARY_PATH' >> ~/.bashrc
source ~/.bashrc
# 验证安装
nvcc --version
3. 安装 cuDNN
cuDNN 是 NVIDIA 的深度学习加速库:
# 下载 cuDNN(需要 NVIDIA 账号)
tar -xvf cudnn-linux-x86_64-8.9.7.29_cuda12-archive.tar.xz
sudo cp cudnn-linux-x86_64-8.9.7.29_cuda12-archive/include/cudnn*.h /usr/local/cuda/include
sudo cp -P cudnn-linux-x86_64-8.9.7.29_cuda12-archive/lib/libcudnn* /usr/local/cuda/lib64
sudo chmod a+r /usr/local/cuda/include/cudnn*.h /usr/local/cuda/lib64/libcudnn*
CUDA 编程基础
Hello World
第一个 CUDA 程序:
#include <stdio.h>
// CUDA Kernel 函数
__global__ void helloCUDA() {
printf("Hello from GPU thread %d\n", threadIdx.x);
}
int main() {
// 启动 Kernel,使用 1 个 Block,每个 Block 10 个线程
helloCUDA<<<1, 10>>>();
// 等待 GPU 执行完成
cudaDeviceSynchronize();
return 0;
}
编译并运行:
nvcc hello.cu -o hello
./hello
CUDA 关键字
| 关键字 | 执行位置 | 调用位置 |
|---|---|---|
__global__ |
Device(GPU) | Host(CPU) |
__device__ |
Device(GPU) | Device(GPU) |
__host__ |
Host(CPU) | Host(CPU) |
// __global__: Kernel 函数,从 Host 调用,在 Device 执行
__global__ void kernel(float *data) {
// ...
}
// __device__: 设备函数,从 Device 调用,在 Device 执行
__device__ float deviceFunction(float x) {
return x * 2.0f;
}
// __host__: 主机函数,从 Host 调用,在 Host 执行(默认)
__host__ float hostFunction(float x) {
return x * 2.0f;
}
// __host__ __device__: 既可以在 Host 也可以在 Device 调用
__host__ __device__ float sharedFunction(float x) {
return x * 2.0f;
}
线程层次结构
CUDA 的线程层次结构包括:
- Thread(线程):最基本的执行单元
- Block(线程块):一组线程,可以访问共享内存和同步
- Grid(网格):一组线程块,构成一个完整的 kernel
__global__ void kernel(float *data) {
// 线程 ID
int tid = threadIdx.x;
// Block ID
int bid = blockIdx.x;
// 全局线程 ID
int globalTid = threadIdx.x + blockIdx.x * blockDim.x;
data[globalTid] = globalTid;
}
int main() {
int N = 1024;
float *d_data;
cudaMalloc(&d_data, N * sizeof(float));
// 启动 Kernel:4 个 Block,每个 Block 256 个线程
int blockSize = 256;
int numBlocks = (N + blockSize - 1) / blockSize;
kernel<<<numBlocks, blockSize>>>(d_data);
cudaFree(d_data);
return 0;
}
内存管理
设备内存分配
int main() {
int N = 1024;
int *h_data; // 主机内存
int *d_data; // 设备内存
// 分配主机内存
h_data = (int*)malloc(N * sizeof(int));
// 分配设备内存
cudaMalloc(&d_data, N * sizeof(int));
// 初始化主机数据
for (int i = 0; i < N; i++) {
h_data[i] = i;
}
// 主机到设备拷贝
cudaMemcpy(d_data, h_data, N * sizeof(int), cudaMemcpyHostToDevice);
// 执行 Kernel
kernel<<<1, 256>>>(d_data);
// 设备到主机拷贝
cudaMemcpy(h_data, d_data, N * sizeof(int), cudaMemcpyDeviceToHost);
// 释放内存
free(h_data);
cudaFree(d_data);
return 0;
}
统一内存(Managed Memory)
CUDA 6.0 引入了统一内存,简化了内存管理:
int main() {
int N = 1024;
int *data; // 统一内存
// 分配统一内存
cudaMallocManaged(&data, N * sizeof(int));
// 初始化数据
for (int i = 0; i < N; i++) {
data[i] = i;
}
// 执行 Kernel
kernel<<<1, 256>>>(data);
// 同步
cudaDeviceSynchronize();
// 释放内存
cudaFree(data);
return 0;
}
常用编程模式
向量加法
__global__ void vectorAdd(float *a, float *b, float *c, int n) {
int idx = threadIdx.x + blockIdx.x * blockDim.x;
if (idx < n) {
c[idx] = a[idx] + b[idx];
}
}
int main() {
int N = 1024 * 1024;
float *a, *b, *c;
cudaMallocManaged(&a, N * sizeof(float));
cudaMallocManaged(&b, N * sizeof(float));
cudaMallocManaged(&c, N * sizeof(float));
// 初始化数据
for (int i = 0; i < N; i++) {
a[i] = i;
b[i] = i * 2;
}
int blockSize = 256;
int numBlocks = (N + blockSize - 1) / blockSize;
vectorAdd<<<numBlocks, blockSize>>>(a, b, c, N);
cudaDeviceSynchronize();
// 释放内存
cudaFree(a);
cudaFree(b);
cudaFree(c);
return 0;
}
矩阵乘法
#define TILE_SIZE 16
__global__ void matrixMul(float *A, float *B, float *C, int N) {
__shared__ float tileA[TILE_SIZE][TILE_SIZE];
__shared__ float tileB[TILE_SIZE][TILE_SIZE];
int bx = blockIdx.x, by = blockIdx.y;
int tx = threadIdx.x, ty = threadIdx.y;
int row = by * TILE_SIZE + ty;
int col = bx * TILE_SIZE + tx;
float sum = 0.0f;
for (int t = 0; t < N / TILE_SIZE; ++t) {
tileA[ty][tx] = A[row * N + t * TILE_SIZE + tx];
tileB[ty][tx] = B[(t * TILE_SIZE + ty) * N + col];
__syncthreads();
for (int k = 0; k < TILE_SIZE; ++k) {
sum += tileA[ty][k] * tileB[k][tx];
}
__syncthreads();
}
C[row * N + col] = sum;
}
int main() {
int N = 1024;
float *A, *B, *C;
cudaMallocManaged(&A, N * N * sizeof(float));
cudaMallocManaged(&B, N * N * sizeof(float));
cudaMallocManaged(&C, N * N * sizeof(float));
// 初始化数据...
dim3 blockSize(TILE_SIZE, TILE_SIZE);
dim3 gridSize(N / TILE_SIZE, N / TILE_SIZE);
matrixMul<<<gridSize, blockSize>>>(A, B, C, N);
cudaDeviceSynchronize();
cudaFree(A);
cudaFree(B);
cudaFree(C);
return 0;
}
错误处理
#define CUDA_CHECK(call) \
do { \
cudaError_t error = call; \
if (error != cudaSuccess) { \
printf("CUDA error: %s:%d, code: %d, reason: %s\n", \
__FILE__, __LINE__, error, cudaGetErrorString(error)); \
exit(1); \
} \
} while(0)
int main() {
int *d_data;
int N = 1024;
// 使用错误检查宏
CUDA_CHECK(cudaMalloc(&d_data, N * sizeof(int)));
// 检查 Kernel 执行错误
kernel<<<1, 256>>>(d_data);
CUDA_CHECK(cudaGetLastError()); // 检查 Kernel 启动错误
CUDA_CHECK(cudaDeviceSynchronize()); // 检查执行错误
CUDA_CHECK(cudaFree(d_data));
return 0;
}
性能分析
使用 nvprof
# 编译程序
nvcc -O3 program.cu -o program
# 使用 nvprof 分析
nvprof ./program
# 详细分析
nvprof --print-gpu-trace ./program
使用 Nsight Compute
# 安装 Nsight Compute
# 从 NVIDIA 官网下载并安装
# 分析 Kernel
ncu --set full ./program
# 查看报告
ncu-ui report.ncu-rep
最佳实践
- 内存合并访问:确保相邻线程访问连续的内存地址
- 避免线程分歧:Warp 内的线程应执行相同的代码路径
- 使用共享内存:利用共享内存减少全局内存访问
- 最大化占用率:保持足够的活跃线程
- 异步执行:使用流和异步内存拷贝
- 错误处理:始终检查 CUDA API 调用的返回值
总结
CUDA 是一个强大的并行计算平台,通过简单的 C/C++ 扩展,开发者可以充分利用 GPU 的计算能力。本文介绍了 CUDA 编程的基础知识,包括环境搭建、基本概念、内存管理和常用编程模式。掌握这些基础知识后,可以进一步学习更高级的优化技巧和 CUDA 库的使用。