学习目标
学完本节你将能够:
- 使用 CMake 管理 CUDA 项目,包括编译、链接和依赖
- 组织多文件 CUDA 项目,合理划分头文件和源文件
- 创建和使用静态库、动态库,并在项目中正确链接
- 处理跨平台 CUDA 项目的配置差异
- 理解编译依赖和增量构建,提高开发效率
1. CUDA 项目的文件组织
1.1 典型目录结构
一个中大型 CUDA 项目通常包含以下组成部分:
project/
├── CMakeLists.txt
├── include/
│ ├── common.h # 公共宏、工具函数
│ ├── kernel_utils.cuh # 设备端辅助函数声明
│ └── ...
├── src/
│ ├── main.cu # 主机端主程序
│ ├── kernels.cu # 核心 Kernel 实现
│ ├── memory.cu # 内存管理封装
│ └── ...
├── tests/
│ ├── test_kernels.cu
│ └── ...
└── libs/
├── mylib/
│ ├── CMakeLists.txt
│ ├── include/
│ └── src/
- 头文件(
.h、.cuh) :声明函数、类型、宏。设备端代码通常放在.cuh中,以便被多个.cu包含。 - 源文件(
.cu):包含主机端和设备端实现。 - 库 :将可复用的组件编译为静态库(
.a)或动态库(.so)。
1.2 头文件中的设备代码
如果设备端函数需要在多个 .cu 文件中使用,应该放在头文件中并用 __device__ 或 __global__ 修饰。链接时使用 -rdc=true 支持可重定位设备代码。
// kernel_utils.cuh
#pragma once
__device__ __forceinline__ float square(float x) {
return x * x;
}
2. CMake 构建配置
2.1 基础 CMakeLists.txt
现代 CMake(3.18+)原生支持 CUDA,可以通过 enable_language(CUDA) 启用。
cmake_minimum_required(VERSION 3.18)
project(MyCudaProject LANGUAGES CXX CUDA)
# 设置 CUDA 标准
set(CMAKE_CUDA_STANDARD 17)
set(CMAKE_CUDA_STANDARD_REQUIRED ON)
# 添加可执行文件
add_executable(myapp
src/main.cu
src/kernels.cu
)
# 设置目标架构
set_target_properties(myapp PROPERTIES
CUDA_ARCHITECTURES "70;80" # 为 sm_70 和 sm_80 生成代码
)
# 链接 CUDA 运行时
target_link_libraries(myapp cudart)
2.2 指定计算能力
使用 CUDA_ARCHITECTURES 属性可以为目标生成多架构代码:
set_target_properties(myapp PROPERTIES
CUDA_ARCHITECTURES "native" # 自动检测本机架构
)
或者手动指定:
set_target_properties(myapp PROPERTIES
CUDA_ARCHITECTURES "70;75;80;86"
)
2.3 编译选项
# 优化级别
target_compile_options(myapp PRIVATE $<$<COMPILE_LANGUAGE:CUDA>:-O3>)
# 调试版本
target_compile_options(myapp_debug PRIVATE $<$<COMPILE_LANGUAGE:CUDA>:-g -G>)
# 快速数学
target_compile_options(myapp PRIVATE $<$<COMPILE_LANGUAGE:CUDA>:-use_fast_math>)
3. 静态库与动态库
3.1 创建静态库
# libs/mylib/CMakeLists.txt
add_library(mylib STATIC
src/kernel1.cu
src/kernel2.cu
)
target_include_directories(mylib PUBLIC ${CMAKE_CURRENT_SOURCE_DIR}/include)
set_target_properties(mylib PROPERTIES
CUDA_ARCHITECTURES "70;80"
CUDA_SEPARABLE_COMPILATION ON # 等价于 -rdc=true
)
3.2 创建动态库
add_library(mylib SHARED
src/kernel1.cu
src/kernel2.cu
)
target_include_directories(mylib PUBLIC ${CMAKE_CURRENT_SOURCE_DIR}/include)
set_target_properties(mylib PROPERTIES
CUDA_ARCHITECTURES "70;80"
CUDA_SEPARABLE_COMPILATION ON
POSITION_INDEPENDENT_CODE ON
)
3.3 链接库到可执行文件
# 主项目 CMakeLists.txt
add_subdirectory(libs/mylib)
add_executable(myapp src/main.cu)
target_link_libraries(myapp PRIVATE mylib)
4. 可重定位设备代码与动态并行
4.1 什么是可重定位设备代码
默认情况下,设备代码在编译时直接生成最终的可执行文件,不允许跨编译单元的 __device__ 函数调用。使用 -rdc=true(或 CMake 的 CUDA_SEPARABLE_COMPILATION ON)后,设备代码编译为可重定位对象,最终链接时解析设备端符号。
需要 -rdc=true 的场景:
- 多个
.cu文件之间调用__device__函数 - 使用动态并行(Kernel 内启动 Kernel)
- 使用协作组跨 Block 的全局同步(某些情况)
4.2 CMake 设置
set_target_properties(myapp PROPERTIES
CUDA_SEPARABLE_COMPILATION ON
)
5. 跨平台 CUDA 项目配置
5.1 处理不同操作系统
CMake 可以自动处理大部分平台差异,但有些细节需要注意:
- Windows :默认使用 MSVC,需要正确设置
CMAKE_CUDA_HOST_COMPILER。 - Linux:常用 GCC,注意 CUDA 版本对 GCC 版本有要求。
- macOS:CUDA 已停止支持 macOS,通常只考虑 Windows/Linux。
5.2 检测 CUDA 可用性
find_package(CUDAToolkit REQUIRED)
if(CUDAToolkit_FOUND)
message(STATUS "CUDA Toolkit found: ${CUDAToolkit_VERSION}")
endif()
5.3 设置主机编译器
# 指定主机编译器
set(CMAKE_CUDA_HOST_COMPILER /usr/bin/g++)
6. 编译依赖与增量构建
CMake 自动管理依赖关系,当文件修改后只重新编译受影响的目标。对于 CUDA 项目,如果启用了可分离编译(-rdc=true),设备代码的链接步骤会更加耗时,此时增量构建的优势明显。
6.1 加快构建的技巧
- 使用 Ninja 生成器:
cmake -G Ninja .. - 并行构建:
cmake --build . -- -j$(nproc) - 使用 ccache 缓存编译结果
6.2 编译数据库
生成 compile_commands.json 便于代码分析:
cmake -DCMAKE_EXPORT_COMPILE_COMMANDS=ON ..
7. 代码演示:完整的多文件项目
7.1 项目结构
demo/
├── CMakeLists.txt
├── include/
│ └── vector_ops.cuh
├── src/
│ ├── main.cu
│ └── vector_ops.cu
7.2 CMakeLists.txt
cmake_minimum_required(VERSION 3.18)
project(CudaVectorDemo LANGUAGES CXX CUDA)
set(CMAKE_CUDA_STANDARD 17)
set(CMAKE_CUDA_STANDARD_REQUIRED ON)
include_directories(${CMAKE_SOURCE_DIR}/include)
add_executable(vector_demo
src/main.cu
src/vector_ops.cu
)
set_target_properties(vector_demo PROPERTIES
CUDA_ARCHITECTURES "native"
CUDA_SEPARABLE_COMPILATION ON
)
target_compile_options(vector_demo PRIVATE $<$<COMPILE_LANGUAGE:CUDA>:-O3>)
7.3 头文件 include/vector_ops.cuh
#pragma once
__global__ void vectorAdd(const float *a, const float *b, float *c, int N);
7.4 Kernel 实现 src/vector_ops.cu
#include "vector_ops.cuh"
__global__ void vectorAdd(const float *a, const float *b, float *c, int N) {
int idx = blockIdx.x * blockDim.x + threadIdx.x;
if (idx < N) {
c[idx] = a[idx] + b[idx];
}
}
7.5 主程序 src/main.cu
#include <cstdio>
#include <cstdlib>
#include <cuda_runtime.h>
#include "vector_ops.cuh"
#define CUDA_CHECK(call) \
do { \
cudaError_t err = call; \
if (err != cudaSuccess) { \
fprintf(stderr, "CUDA error at %s:%d: %s\n", __FILE__, __LINE__, cudaGetErrorString(err)); \
exit(EXIT_FAILURE); \
} \
} while (0)
int main() {
const int N = 1 << 20;
size_t bytes = N * sizeof(float);
float *h_a = (float*)malloc(bytes);
float *h_b = (float*)malloc(bytes);
float *h_c = (float*)malloc(bytes);
for (int i = 0; i < N; i++) {
h_a[i] = (float)i;
h_b[i] = 2.0f * i;
}
float *d_a, *d_b, *d_c;
CUDA_CHECK(cudaMalloc(&d_a, bytes));
CUDA_CHECK(cudaMalloc(&d_b, bytes));
CUDA_CHECK(cudaMalloc(&d_c, bytes));
CUDA_CHECK(cudaMemcpy(d_a, h_a, bytes, cudaMemcpyHostToDevice));
CUDA_CHECK(cudaMemcpy(d_b, h_b, bytes, cudaMemcpyHostToDevice));
int block = 256;
int grid = (N + block - 1) / block;
vectorAdd<<<grid, block>>>(d_a, d_b, d_c, N);
CUDA_CHECK(cudaDeviceSynchronize());
CUDA_CHECK(cudaMemcpy(h_c, d_c, bytes, cudaMemcpyDeviceToHost));
printf("h_c[0] = %f, h_c[N-1] = %f\n", h_c[0], h_c[N-1]);
free(h_a); free(h_b); free(h_c);
CUDA_CHECK(cudaFree(d_a));
CUDA_CHECK(cudaFree(d_b));
CUDA_CHECK(cudaFree(d_c));
return 0;
}
7.6 构建与运行
mkdir build && cd build
cmake ..
make -j
./vector_demo
8. 课后练习
练习1:创建自己的静态库
将前面章节中的矩阵转置或归约 Kernel 封装成静态库,并在一个新项目中链接使用。
练习2:多文件项目
将一个大 Kernel 拆分为多个 .cu 文件,使用可分离编译,确保 __device__ 函数可以跨文件调用。
练习3:CMake 配置不同架构
修改 CMakeLists.txt,为你的 GPU 和一个其他架构(如 sm_75)生成多架构代码,并在不同 GPU 上测试运行。
练习4:动态库链接
创建一个动态库,并在主程序中动态加载(使用 dlopen 或 LoadLibrary),实现插件式加载 CUDA Kernel。
练习5:优化构建流程
使用 Ninja 和 ccache 加速构建,测量全量构建和增量构建的时间差异。
9. 第6板块总结
恭喜你完成第6板块「编译、调用、错误捕获、调试工具(工程基础)」的全部内容!
回顾核心知识:
- ✅ NVCC 编译选项与代码生成深入
- ✅ CUDA 运行时与驱动 API
- ✅ 错误处理与调试工具(compute‑sanitizer、cuda‑gdb)
- ✅ 构建系统与多文件项目
下一步
下一板块将进入 通用算子 Kernel 分类 & CUTLASS 模板进阶,你将学习:
- 通用算子的分类与设计模式
- CUTLASS 的核心抽象与优化策略
- 使用 CUTLASS 编写高性能 GEMM
- 将 CUTLASS 集成到实际项目中