CUDA编程入门

环境搭建

硬件:NVIDIA GPU

软件:CUDA Toolkit(提供 nvcc 编译器、CUDA 头文件、库文件)

我现在的环境:

GPU: RTX 4060

Driver: 591.86

CUDA Toolkit: 13.1

搭建

  1. 先装 NVIDIA 驱动:验证命令:nvidia-smi
  2. 安装 CUDA Toolkit :验证命令:nvcc --version

具体的环境搭建可以看这篇文章:C++开发基础之初探CUDA计算环境搭建

函数编写流程:

内存模型:Host Memory CPU内存 Device Memory GPU内存

CUDA编程整体代码架构

CPU部分:逻辑控制、内存显存申请&释放(申请CPU、GPU内存、 CPU到GPU内存拷贝、启动kernel (launch kernel)、将结果从GPU拷贝到CPU、释放相关内存

GPU部分:算子编写(执行相应并行计算算子操作)

核函数(kernel function)

基础用法:

函数的定义

在普通函数前+__global__关键字

函数的调用

函数名<<<参数>>>();

cpp 复制代码
#include <cuda_runtime.h>
#include <cuda.h>
#include <stdio.h>
#include <time.h>

__global__ void testPrint() {
    printf("hello kernel\n");
}



int main() {
    testPrint<<<1, 1, 0, nullptr>>>();
    cudaDeviceSynchronize();
    return 0;
}
cpp 复制代码
#include <cuda_runtime.h>
#include <cuda.h>
#include <stdio.h>
#include <time.h>

/*
计算两个数的和(cpu)
*/
double_t numSum(double_t a, double_t b) {
    return a + b;
}

/*
计算两个数字的和(GPU)
*/
__global__ void numSumKernel(double_t* a, double_t* b, double_t* result) {
    *result = *a + *b;
}


int main() {
    
    // 1.在主存上开辟空间即初始化 a\b\result
    // cudaMallocHost(&指针,空间大小)
    double_t* a{nullptr};
    double_t* b{nullptr};
    double_t* result{nullptr};

    cudaMallocHost(&a, sizeof(double_t));
    cudaMallocHost(&b, sizeof(double_t));
    cudaMallocHost(&result, sizeof(double_t));

    *a = 1.4;
    *b = 1.3;
    *result = 0.0;

    // 2.将主存空间中的数据放到显存中
    // cudaMemcpy(目的地,源地址,数据量,搬运方式)
    // 开辟显存的空间
    // cudaMalloc(地址,空间大小)
    double_t* aDevice{nullptr};
    double_t* bDevice{nullptr};
    double_t* resultDevice{nullptr};
    cudaMalloc(&aDevice, sizeof(double_t));
    cudaMalloc(&bDevice, sizeof(double_t));
    cudaMalloc(&resultDevice, sizeof(double_t));
   
    cudaMemcpy(aDevice, a, sizeof(double_t), cudaMemcpyHostToDevice);
    cudaMemcpy(bDevice, b, sizeof(double_t), cudaMemcpyHostToDevice);

    // 3.调用核函数进行运算,结果保存到显存中
    numSumKernel<<<1, 1, 0, nullptr>>>(aDevice, bDevice, resultDevice);


    //4. 把运算结果从显存中搬运到主存中,进行后续操作
    // 把显存内容般涌道主存
    cudaMemcpy(result, resultDevice, sizeof(double_t), cudaMemcpyDeviceToHost);
    printf("result = %f\n", *result);

    // 资源回收
    // 显存资源
    // cudaFree()
    cudaFree(resultDevice);
    cudaFree(bDevice);
    cudaFree(aDevice);
    // 主存
    cudaFree(result);
    cudaFree(b);
    cudaFree(a);


    cudaDeviceSynchronize();
    return 0;
}

第二个代码的实例完全跟函数编写流程一致

大家看下面的例子

cpp 复制代码
#include <cuda_runtime.h>
#include <cuda.h>
#include <stdio.h>
#include <time.h>


/*
多线程中,函数会执行多次,每次调用都会在一个新的线程中执行
*/
__global__ void numSumKernel(uint32_t* a, size_t arr_len_a, uint32_t* b, size_t arr_len_b, uint32_t* result, size_t arr_len_c) {
    // for (int i = 0; i < arr_len_a; ++i) {
    //     result[i] = a[i] + b[i];
    // }
    // threadIdx为核函数内置变量
    // 8个线程、8个加法运算、每次取2个
    // 16条传入-》threadIdx内置变量-》从16条数据中取出对应要处理的数据
    // printf("threadIdx: %d\n", threadIdx.x);
    result[threadIdx.x] = a[threadIdx.x] + b[threadIdx.x];
}

int main() {

    // 申请主存空间
    int32_t* a{nullptr};
    int32_t* b{nullptr};
    int32_t* result{nullptr};

    constexpr size_t ARR_LEN = 8;

    cudaMallocHost(&a, ARR_LEN * sizeof(int32_t));
    cudaMallocHost(&b, ARR_LEN * sizeof(int32_t));
    cudaMallocHost(&result, ARR_LEN * sizeof(int32_t));

    // 主存空间数据初始化
    for (int i = 0; i < ARR_LEN; ++i) {
        a[i] = i;
        b[i] = i;
        result[i] = 0;
    }

    // 申请显存空间,主存空间的数据放到显存中
    uint32_t* aDevice{nullptr};
    uint32_t* bDevice{nullptr};
    uint32_t* resultDevice{nullptr};

    cudaMalloc(&aDevice, ARR_LEN * sizeof(uint32_t));
    cudaMalloc(&bDevice, ARR_LEN * sizeof(uint32_t));
    cudaMalloc(&resultDevice, ARR_LEN * sizeof(uint32_t));

    cudaMemcpy(aDevice, a, sizeof(uint32_t) * ARR_LEN, cudaMemcpyHostToDevice);
    cudaMemcpy(bDevice, b, sizeof(uint32_t) * ARR_LEN, cudaMemcpyHostToDevice);
    cudaMemcpy(resultDevice, result, sizeof(uint32_t) * ARR_LEN, cudaMemcpyHostToDevice);


    // 调用核函数进行运算 blockDim
    numSumKernel<<<1, ARR_LEN, 0, nullptr>>>(aDevice, ARR_LEN, bDevice, ARR_LEN, resultDevice, ARR_LEN);

    // 将显存的结果转移到主存中
    cudaMemcpy(result, resultDevice, sizeof(uint32_t) * ARR_LEN, cudaMemcpyDeviceToHost);

    for (int i = 0; i < ARR_LEN; ++i) {
         printf("result[%zu]: %d\n", i,  result[i]);
    } 
   

    // 释放资源
    cudaFree(resultDevice);
    cudaFree(bDevice);
    cudaFree(aDevice);
    // 释放主存资源
    cudaFree(result);
    cudaFree(b);
    cudaFree(a);

    cudaDeviceSynchronize();

    return 0;
}

上面的例子中,开启了8个线程,一个线程只处理一部分数据的运算。

注意,每次用nvcc ./文件名 -o 可执行文件名.exe。当文件比较多,每次这样都比较麻烦,所以我们可以用cmake来连接。

参考的cmake程序如下:

cpp 复制代码
cmake_minimum_required(VERSION 3.18)

# 设定CUDA程序所运行的目标架构
set(CMAKE_CUDA_ARCHITECTURES "75;80;86;89")

# 设定使用的c++版本
set(CMAKE_CXX_STANDARD 20)
# 检查系统是否满足当前版本的c++规范
set(CMAKE_CXX_STANDARD_REQUIRED ON)
# 超出规范的扩展是否需要
set(CMAKE_CXX_EXTENSIONS OFF)

# 声明项目
project(cmake_cuda C CXX CUDA)

# 查找CUDA Tookit
find_package(CUDAToolkit REQUIRED)


# 源码文件
file(GLOB SOURCE_FILES "src/*.cu" "src/*.h")

# 构建目标
add_executable(cmake_cuda ${SOURCE_FILES})

# 链接CUDA库
target_link_libraries(cmake_cuda
    PRIVATE
        CUDA::cudart
        cuda
)

# 把目标可执行文件编译到bin目录
set(EXECUTABLE_OUTPUT_PATH ${CMAKE_SOURCE_DIR}/bin)

具体的cmake可以看我的这篇文档:Cmake

相关推荐
奔跑中的小象6 小时前
统信UOS + 天数AI卡部署SGLang服务手册
人工智能·uos·sglang·天数智芯
DevSecOps选型指南6 小时前
中国版Mythos,为何是悬镜安全灵脉CodeAI?
人工智能·安全
Shockang6 小时前
LangGraph 状态机实战
人工智能
刹那芳华19926 小时前
循环神经网络的从零开始实现(RNN)
人工智能·rnn·深度学习
阿童木写作7 小时前
跨境图片翻译工具多合一,批量图片视频字幕翻译加智能抠图
人工智能·python·音视频·语音识别
科技之门7 小时前
AI3D从建模到贴图、绑骨和动画的完整流程怎么做?V2Fun完整工作流指南
人工智能·3d·贴图
宇宙第一小趴菜7 小时前
二、机器学习的应用领域和发展史
人工智能·机器学习
前端开发江鸟7 小时前
我写过 MCP Server,却一直以为 MCP 只有 Tool
人工智能
天国梦7 小时前
自习室智能化升级避坑指南:天学网AI智习室方案实测与选型建议
大数据·人工智能
阿里云云原生8 小时前
可用性从 99.9% 跃升至 99.995%:畅捷通如何用 AI 重塑运维底座?
运维·网络·人工智能