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

相关推荐
武子康1 小时前
调查研究-192 AI Agent 之间也需要“信任“:把多 Agent 信任变成可测指标
人工智能·openai·agent
Smoothcloud_润云1 小时前
Hermes Agent 的上下文记忆机制:一个开源 Agent 是怎么"记住"你的
人工智能·agent·gpu
早点睡啊1 小时前
精读 LangChain 官方文档(一)总览、安装与快速开始:从 create_agent 跑通第一个智能体
人工智能
牛奶1 小时前
AI时代裁员后:清零是君子豹变
人工智能·程序员
武子康2 小时前
调查研究-191 SenseVoice 不只是 ASR:把语音从“转文字“升级成“理解状态“
人工智能·深度学习·openai
程序员cxuan3 小时前
Codex 会把磁盘给烧了?完整复盘来了!
人工智能·后端·程序员
甲维斯3 小时前
字节版“Codex”初体验,Seed 2.1pro所有人免费用!
人工智能·ai编程·豆包marscode
半个落月4 小时前
从 Tokenization 到 Embedding:用 Node.js 搞懂大模型为什么先“分词”再“向量化”
人工智能·node.js
vanuan4 小时前
MCP协议实战(Java版):用Spring Boot让AI直接查你的数据库
人工智能
雪隐5 小时前
个人电脑玩AI-06让5060 Ti给你打工——不光能画画,Qwen3-TTS还能学人说话,连我老板都信了!
人工智能·后端·python