下面提供 3个由浅到深的CUDA编程完整示例,覆盖基础向量运算、矩阵乘法(经典案例)、以及和PyTorch结合的实用场景,帮助你从入门到理解核心用法。
前置说明
- 环境:需要安装CUDA Toolkit(推荐11.x/12.x),编译器用
nvcc; - 编译命令:
nvcc 文件名.cu -o 可执行文件(比如nvcc vector_add.cu -o vec_add); - 运行:
./可执行文件(需有NVIDIA GPU且驱动正常)。
示例1:基础向量加法(极简版)
最核心的CUDA入门案例,实现 c[i] = a[i] + b[i],包含「主机(CPU)/设备(GPU)内存管理」「核函数调用」「结果验证」全流程。
cpp
// vector_add.cu
#include <stdio.h>
#include <stdlib.h>
// CUDA核函数:GPU上并行执行向量加法
__global__ void vectorAdd(const float* a, const float* b, float* c, int n) {
// 计算当前线程的全局索引(网格跨步循环,适配任意n)
int i = blockIdx.x * blockDim.x + threadIdx.x;
if (i < n) { // 避免越界(当n不是blockDim.x的整数倍时)
c[i] = a[i] + b[i];
}
}
// 主机端(CPU)主函数
int main() {
// 1. 定义参数:向量长度
const int n = 1024 * 1024; // 100万元素,测试性能
size_t size = n * sizeof(float);
// 2. 主机内存分配(CPU侧)
float* h_a = (float*)malloc(size);
float* h_b = (float*)malloc(size);
float* h_c = (float*)malloc(size);
// 3. 初始化主机数据
for (int i = 0; i < n; i++) {
h_a[i] = i * 1.0f;
h_b[i] = 2.0f * i;
}
// 4. 设备内存分配(GPU侧)
float* d_a, *d_b, *d_c;
cudaMalloc((void**)&d_a, size);
cudaMalloc((void**)&d_b, size);
cudaMalloc((void**)&d_c, size);
// 5. 主机→设备数据拷贝(CPU→GPU)
cudaMemcpy(d_a, h_a, size, cudaMemcpyHostToDevice);
cudaMemcpy(d_b, h_b, size, cudaMemcpyHostToDevice);
// 6. 配置并启动核函数
int blockSize = 1024; // 每个block的线程数(CUDA推荐1024)
int gridSize = (n + blockSize - 1) / blockSize; // 向上取整计算grid数
vectorAdd<<<gridSize, blockSize>>>(d_a, d_b, d_c, n);
// 7. 等待核函数执行完成,并检查错误
cudaDeviceSynchronize();
cudaError_t err = cudaGetLastError();
if (err != cudaSuccess) {
printf("CUDA error: %s\n", cudaGetErrorString(err));
return -1;
}
// 8. 设备→主机数据拷贝(GPU→CPU)
cudaMemcpy(h_c, d_c, size, cudaMemcpyDeviceToHost);
// 9. 验证结果(随机检查几个值)
bool success = true;
for (int i = 0; i < 10; i++) { // 只检查前10个值,避免输出过多
if (h_c[i] != h_a[i] + h_b[i]) {
printf("Error: c[%d] = %f, expected %f\n", i, h_c[i], h_a[i]+h_b[i]);
success = false;
break;
}
}
if (success) {
printf("Vector add success!\n");
}
// 10. 释放内存(主机+设备)
free(h_a); free(h_b); free(h_c);
cudaFree(d_a); cudaFree(d_b); cudaFree(d_c);
return 0;
}
关键要点
cudaMalloc/cudaFree:GPU内存的分配/释放(必须显式调用,不像CPU有自动回收);cudaMemcpy:CPU和GPU之间的数据拷贝(方向必须指定,比如cudaMemcpyHostToDevice);cudaDeviceSynchronize():等待GPU核函数执行完成(核函数是异步启动的,CPU不会默认等待);- 错误检查:
cudaGetLastError()是调试CUDA程序的关键(核函数报错不会直接崩溃,需手动检查)。
示例2:矩阵乘法(CUDA经典案例)
矩阵乘法是GPU加速的核心场景,实现 C = A × B(A: M×K,B: K×N,C: M×N),演示「二维grid/block」和「共享内存优化」(CUDA性能优化的关键)。
cpp
// matrix_mult.cu
#include <stdio.h>
#include <stdlib.h>
// 定义矩阵块大小(共享内存优化用,通常设为32)
#define BLOCK_SIZE 32
// CUDA核函数:矩阵乘法(共享内存优化版)
__global__ void matrixMult(const float* A, const float* B, float* C, int M, int K, int N) {
// 共享内存:缓存A和B的子块,减少全局内存访问(全局内存慢,共享内存快)
__shared__ float sA[BLOCK_SIZE][BLOCK_SIZE];
__shared__ float sB[BLOCK_SIZE][BLOCK_SIZE];
// 计算当前线程负责的C矩阵元素坐标
int row = blockIdx.y * blockDim.y + threadIdx.y;
int col = blockIdx.x * blockDim.x + threadIdx.x;
float sum = 0.0f;
// 分块遍历K维度(网格跨步)
for (int k = 0; k < K; k += BLOCK_SIZE) {
// 加载A和B的子块到共享内存
if (row < M && (k + threadIdx.x) < K) {
sA[threadIdx.y][threadIdx.x] = A[row * K + k + threadIdx.x];
} else {
sA[threadIdx.y][threadIdx.x] = 0.0f; // 边界越界补0
}
if (col < N && (k + threadIdx.y) < K) {
sB[threadIdx.y][threadIdx.x] = B[(k + threadIdx.y) * N + col];
} else {
sB[threadIdx.y][threadIdx.x] = 0.0f;
}
__syncthreads(); // 等待所有线程加载完成(共享内存同步)
// 子块内乘法累加
for (int t = 0; t < BLOCK_SIZE; t++) {
sum += sA[threadIdx.y][t] * sB[t][threadIdx.x];
}
__syncthreads(); // 等待所有线程计算完成,避免覆盖共享内存
}
// 写入结果到C矩阵(避免越界)
if (row < M && col < N) {
C[row * N + col] = sum;
}
}
// 主机端:初始化矩阵
void initMatrix(float* mat, int rows, int cols) {
for (int i = 0; i < rows * cols; i++) {
mat[i] = rand() / (float)RAND_MAX; // 随机初始化0~1
}
}
// 主机端:验证矩阵乘法结果(CPU朴素实现)
bool verifyResult(const float* A, const float* B, const float* C, int M, int K, int N) {
for (int i = 0; i < M; i++) {
for (int j = 0; j < N; j++) {
float sum = 0.0f;
for (int k = 0; k < K; k++) {
sum += A[i*K + k] * B[k*N + j];
}
if (fabs(C[i*N + j] - sum) > 1e-5) { // 浮点误差容忍
printf("Error: C[%d][%d] = %f, expected %f\n", i, j, C[i*N+j], sum);
return false;
}
}
}
return true;
}
int main() {
// 矩阵维度(可调整,比如1024×1024)
const int M = 512;
const int K = 256;
const int N = 512;
size_t size_A = M * K * sizeof(float);
size_t size_B = K * N * sizeof(float);
size_t size_C = M * N * sizeof(float);
// 1. 主机内存分配+初始化
float* h_A = (float*)malloc(size_A);
float* h_B = (float*)malloc(size_B);
float* h_C = (float*)malloc(size_C);
initMatrix(h_A, M, K);
initMatrix(h_B, K, N);
// 2. 设备内存分配
float* d_A, *d_B, *d_C;
cudaMalloc(&d_A, size_A);
cudaMalloc(&d_B, size_B);
cudaMalloc(&d_C, size_C);
// 3. 主机→设备拷贝
cudaMemcpy(d_A, h_A, size_A, cudaMemcpyHostToDevice);
cudaMemcpy(d_B, h_B, size_B, cudaMemcpyHostToDevice);
// 4. 配置二维grid/block(矩阵适合二维拆分)
dim3 block(BLOCK_SIZE, BLOCK_SIZE); // 32×32=1024线程/block(符合上限)
dim3 grid((N + block.x - 1) / block.x, (M + block.y - 1) / block.y);
// 5. 启动核函数+同步
matrixMult<<<grid, block>>>(d_A, d_B, d_C, M, K, N);
cudaDeviceSynchronize();
cudaError_t err = cudaGetLastError();
if (err != cudaSuccess) {
printf("CUDA error: %s\n", cudaGetErrorString(err));
return -1;
}
// 6. 设备→主机拷贝
cudaMemcpy(h_C, d_C, size_C, cudaMemcpyDeviceToHost);
// 7. 验证结果
if (verifyResult(h_A, h_B, h_C, M, K, N)) {
printf("Matrix multiply success!\n");
} else {
printf("Matrix multiply failed!\n");
}
// 8. 释放内存
free(h_A); free(h_B); free(h_C);
cudaFree(d_A); cudaFree(d_B); cudaFree(d_C);
return 0;
}
关键优化点
- 二维grid/block :矩阵是二维结构,用
dim3定义二维线程索引(blockIdx.y/threadIdx.y对应行,blockIdx.x/threadIdx.x对应列); - 共享内存(
__shared__):GPU全局内存访问延迟高,把A/B的子块加载到共享内存(线程块内共享),能减少全局内存访问次数,提升性能; __syncthreads():线程块内的同步屏障,确保所有线程完成共享内存加载后再计算,避免数据竞争。
示例3:CUDA和PyTorch结合(实用场景)
实际项目中很少直接写纯CUDA代码,更多是和PyTorch结合(PyTorch管理GPU内存,CUDA实现自定义算子)。下面是「自定义ReLU算子」的示例,演示如何在PyTorch中调用CUDA核函数。
步骤1:CUDA核函数实现(relu_cuda.cu)
cpp
#include <torch/extension.h>
#include <cuda_runtime.h>
// CUDA核函数:ReLU激活(y = max(x, 0))
__global__ void reluKernel(const float* x, float* y, int n) {
int i = blockIdx.x * blockDim.x + threadIdx.x;
if (i < n) {
y[i] = x[i] > 0 ? x[i] : 0.0f;
}
}
// 主机端:PyTorch接口函数
void reluCuda(torch::Tensor x, torch::Tensor y) {
// 检查张量是否在GPU上,且类型为float
TORCH_CHECK(x.is_cuda(), "x must be on GPU");
TORCH_CHECK(y.is_cuda(), "y must be on GPU");
TORCH_CHECK(x.dtype() == torch::kFloat32, "x must be float32");
int n = x.numel(); // 总元素数
int blockSize = 1024;
int gridSize = (n + blockSize - 1) / blockSize;
// 获取张量的GPU指针(PyTorch张量转CUDA原始指针)
float* d_x = x.data_ptr<float>();
float* d_y = y.data_ptr<float>();
// 启动核函数
reluKernel<<<gridSize, blockSize>>>(d_x, d_y, n);
// 检查错误
cudaError_t err = cudaGetLastError();
TORCH_CHECK(err == cudaSuccess, "CUDA kernel error: ", cudaGetErrorString(err));
}
// 绑定PyTorch接口(Python可调用)
PYBIND11_MODULE(TORCH_EXTENSION_NAME, m) {
m.def("relu_cuda", &reluCuda, "ReLU CUDA implementation");
}
步骤2:编译脚本(setup.py)
python
from setuptools import setup
from torch.utils.cpp_extension import CUDAExtension, BuildExtension
setup(
name='relu_cuda',
ext_modules=[
CUDAExtension(
name='relu_cuda',
sources=['relu_cuda.cu'],
extra_compile_args={'nvcc': ['-O2']} # 编译优化
)
],
cmdclass={
'build_ext': BuildExtension
}
)
步骤3:Python调用(test_relu.py)
python
import torch
import os
import sys
# 编译并加载自定义算子
os.system(f"{sys.executable} setup.py build_ext --inplace")
import relu_cuda
# 测试自定义ReLU
device = torch.device('cuda:0')
x = torch.randn(1024, 1024, device=device, dtype=torch.float32)
y = torch.zeros_like(x)
# 调用CUDA实现的ReLU
relu_cuda.relu_cuda(x, y)
# 验证结果(和PyTorch内置ReLU对比)
y_torch = torch.relu(x)
assert torch.allclose(y, y_torch, atol=1e-5), "Result mismatch!"
print("Custom ReLU CUDA success!")
关键要点
- PyTorch张量和CUDA指针转换 :
x.data_ptr<float>()直接获取PyTorch张量的GPU原始指针(无需手动cudaMalloc); - 错误检查 :用
TORCH_CHECK替代原生C++断言,报错信息更友好; - 编译方式 :通过
torch.utils.cpp_extension编译CUDA代码,无需手动写nvcc命令; - 兼容性 :PyTorch自动管理GPU内存,无需手动
cudaFree,符合PyTorch生态习惯。
学习建议
- 先跑通「示例1」,理解CUDA的核心流程(内存管理、核函数、数据拷贝);
- 再研究「示例2」,掌握矩阵乘法的并行拆分和共享内存优化(CUDA性能的核心);
- 最后看「示例3」,结合PyTorch实现实用的自定义算子(工业界主流用法);
- 调试技巧:
- 用
cuda-memcheck检测内存越界:cuda-memcheck ./可执行文件; - 用
nvprof分析性能:nvprof ./可执行文件(查看核函数耗时、内存访问效率)。
- 用
这些示例覆盖了CUDA编程的核心场景,理解后可以扩展到更复杂的算子(如卷积、注意力机制)。
