3 个由浅到深的 CUDA 编程完整示例

下面提供 3个由浅到深的CUDA编程完整示例,覆盖基础向量运算、矩阵乘法(经典案例)、以及和PyTorch结合的实用场景,帮助你从入门到理解核心用法。

前置说明

  1. 环境:需要安装CUDA Toolkit(推荐11.x/12.x),编译器用nvcc
  2. 编译命令:nvcc 文件名.cu -o 可执行文件(比如 nvcc vector_add.cu -o vec_add);
  3. 运行:./可执行文件(需有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. 先跑通「示例1」,理解CUDA的核心流程(内存管理、核函数、数据拷贝);
  2. 再研究「示例2」,掌握矩阵乘法的并行拆分和共享内存优化(CUDA性能的核心);
  3. 最后看「示例3」,结合PyTorch实现实用的自定义算子(工业界主流用法);
  4. 调试技巧:
    • cuda-memcheck检测内存越界:cuda-memcheck ./可执行文件
    • nvprof分析性能:nvprof ./可执行文件(查看核函数耗时、内存访问效率)。

这些示例覆盖了CUDA编程的核心场景,理解后可以扩展到更复杂的算子(如卷积、注意力机制)。

相关推荐
ouliten16 小时前
cuda编程笔记(39)--Asynchronous Barriers(异步屏障)
笔记·cuda
ouliten1 天前
cuda编程笔记(38)--CUDA 异步回调
笔记·cuda
李昊哲小课4 天前
WSL Ubuntu 24.04 GPU 加速环境完整安装指南
c++·pytorch·深度学习·ubuntu·cuda·tensorflow2
bloglin999995 天前
Ubuntu22.04 离线安装 CUDA12.3 依赖报错解决方案
ai·cuda
fpcc10 天前
并行编程实战——CUDA编程的图之六子图的创建
人工智能·cuda
明月醉窗台11 天前
[jetson] AGX Xavier 安装Ubuntu18.04及jetpack4.5
人工智能·算法·nvidia·cuda·jetson
飞翔的SA11 天前
全程 Python:无需离开 Python 即可实现光速级 CUDA 加速,无需c++支持
开发语言·c++·python·nvidia·cuda
阿钱真强道16 天前
01 飞腾 S5000C 服务器环境搭建实战:PyTorch + CUDA + RTX 4090D 安装与验证
pytorch·cuda·aarch64·深度学习环境搭建·飞腾服务器·s5000c·rtx4090d
酌量19 天前
nvidia orin agx刷机忘记CUDA runtime,安装torch和cuda
linux·笔记·ubuntu·torch·cuda·agx