环境搭建
硬件:NVIDIA GPU
软件:CUDA Toolkit(提供 nvcc 编译器、CUDA 头文件、库文件)
我现在的环境:
GPU: RTX 4060
Driver: 591.86
CUDA Toolkit: 13.1
搭建
- 先装 NVIDIA 驱动:验证命令:nvidia-smi
- 安装 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