一、为什么要在CMake里折腾CUDA?

说实话,早些年做CUDA开发,大家都是手写Makefile。那时候的痛苦,我现在还记得——链接库路径写错一个字符,编译报错能找半小时。后来CMake从3.8版本开始原生支持CUDA,这事儿就简单多了。

你想想看,CMake能帮你自动找NVCC编译器、设置架构参数、处理依赖关系。说白了,就是把那些重复劳动交给工具去做。我个人习惯是:只要项目里有一行.cu文件,就立刻在CMakeLists.txt里开启CUDA支持。

二、核心操作:启用CUDA语言

2.1 最简单的写法

在CMakeLists.txt最顶部,加上这一行:

project(MyProject LANGUAGES CXX CUDA)

这就告诉CMake:我要用C++和CUDA两种语言。CMake会自动去找nvcc编译器。如果找不到,会报错提示你安装CUDA Toolkit。

小提示: 如果你用的是CMake 3.18以上版本,还可以用 enable_language(CUDA) 在项目中间动态开启。但我建议还是在project()里一次性声明,更清晰。

2.2 指定CUDA架构

不同显卡的架构不一样。比如RTX 3090是sm_86,GTX 1080是sm_61。如果不指定,CMake会用默认值,但生成的代码可能跑不起来。

我一般这样写:

set(CMAKE_CUDA_ARCHITECTURES "61;75;86")

这表示生成三个架构的二进制代码。注意,这里写的是计算能力版本号,不是显卡型号。怎么查?去NVIDIA官网查你的显卡计算能力就行。

踩坑提醒: 我曾经在项目里只写了sm_86,结果同事用GTX 1060(sm_61)跑,直接报"no kernel image"错误。从那以后,我至少会覆盖三代主流架构。

三、编译CUDA代码的两种方式

3.1 直接编译.cu文件

最简单的情况,你的.cu文件直接参与编译:

add_executable(myapp main.cpp kernel.cu)
target_link_libraries(myapp PRIVATE cuda)

CMake会自动调用nvcc编译.cu文件,然后和C++文件一起链接。这里要注意,cuda是CMake提供的导入库,包含了CUDA运行时和驱动库。

3.2 分离编译(推荐)

项目大了以后,我建议把CUDA代码单独编译成静态库:

# 创建CUDA库
add_library(cuda_kernels STATIC
    vector_add.cu
    matrix_mul.cu
)

# 设置CUDA架构
set_property(TARGET cuda_kernels PROPERTY CUDA_ARCHITECTURES "61;75;86")

# 主程序链接
add_executable(myapp main.cpp)
target_link_libraries(myapp PRIVATE cuda_kernels cuda)

这样做的好处很明显:CUDA代码和C++代码的编译选项互不干扰。比如CUDA部分需要-arch=sm_86,而C++部分需要-O3,分开写更清晰。

四、核心知识体系

我把整个流程画了张图,方便你理解:

CMake CUDA支持核心流程 1. 启用CUDA语言 project(... LANGUAGES CXX CUDA) 2. 设置架构参数 CMAKE_CUDA_ARCHITECTURES 3. 编译CUDA代码 add_library / add_executable 编译方式选择 方式A:直接编译 .cu文件直接加入可执行文件 适合小项目、快速原型 方式B:分离编译 CUDA代码打包成静态库 适合中大型项目、团队协作 关键:CUDA_ARCHITECTURES 必须匹配目标显卡 否则运行时会出现 "no kernel image" 错误

五、常见问题与避坑指南

5.1 NVCC找不到

如果你装了CUDA但CMake还是报找不到nvcc,八成是环境变量没配好。检查一下:

# Linux/macOS
export PATH=/usr/local/cuda/bin:$PATH
export LD_LIBRARY_PATH=/usr/local/cuda/lib64:$LD_LIBRARY_PATH

# Windows
# 把 C:\Program Files\NVIDIA GPU Computing Toolkit\CUDA\v12.0\bin 加到PATH

我个人习惯在CMake里加个判断:

if(NOT CMAKE_CUDA_COMPILER)
    message(FATAL_ERROR "CUDA编译器未找到!请检查CUDA Toolkit安装。")
endif()

5.2 架构写错导致运行时崩溃

这是最隐蔽的问题。编译能通过,但一运行就崩。我曾经在部署时吃过这个亏——开发机是RTX 3080,部署机是T4,忘了改架构参数,结果现场调试了两小时。

我的建议是:

  • 开发阶段用 CMAKE_CUDA_ARCHITECTURES "native" 自动检测当前显卡
  • 发布阶段手动指定目标架构列表

5.3 混合编译的链接错误

当C++和CUDA代码互相调用时,记得把CUDA库链接上:

target_link_libraries(myapp PRIVATE cuda cudart)

如果用了cuBLAS、cuFFT等库,也要显式链接:

target_link_libraries(myapp PRIVATE cublas cufft)

六、一个完整的实战例子

下面是一个完整的CMakeLists.txt,包含了上面讲的所有要点:

cmake_minimum_required(VERSION 3.18)
project(CUDAExample LANGUAGES CXX CUDA)

# 设置C++标准
set(CMAKE_CXX_STANDARD 17)
set(CMAKE_CXX_STANDARD_REQUIRED ON)

# 设置CUDA架构(覆盖三代主流)
set(CMAKE_CUDA_ARCHITECTURES "61;75;86")

# 开启CUDA可分离编译(提高大项目编译速度)
set(CMAKE_CUDA_SEPARABLE_COMPILATION ON)

# 创建CUDA库
add_library(cuda_ops STATIC
    src/vector_add.cu
    src/matrix_mul.cu
)

# 设置CUDA库的编译选项
target_compile_options(cuda_ops PRIVATE
    $<$<COMPILE_LANGUAGE:CUDA>:--use_fast_math>
)

# 创建主程序
add_executable(cuda_demo
    src/main.cpp
)

# 链接
target_link_libraries(cuda_demo PRIVATE
    cuda_ops
    cuda
    cudart
)

# 安装
install(TARGETS cuda_demo DESTINATION bin)

这个例子涵盖了:语言声明、架构设置、分离编译、编译选项、链接依赖。你直接拿去改改就能用。

七、总结

CMake对CUDA的支持已经非常成熟了。说白了就三步:声明语言、设置架构、编译链接。但细节决定成败——架构参数写错、链接库漏掉、环境变量没配,这些坑我都踩过。

最后送你一句话:能用工具解决的问题,别用手工解决。 CMake帮你管好CUDA编译,你就能把精力放在算法优化上,这才是正道。

核心要点回顾:

  • project()里加上CUDA语言声明
  • CMAKE_CUDA_ARCHITECTURES必须匹配目标显卡
  • 大项目用分离编译,小项目直接编译
  • 别忘了链接cuda和cudart库
公众号:蓝海资料掘金营,微信deep3321