一、为什么要在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。
enable_language(CUDA) 在项目中间动态开启。但我建议还是在project()里一次性声明,更清晰。
2.2 指定CUDA架构
不同显卡的架构不一样。比如RTX 3090是sm_86,GTX 1080是sm_61。如果不指定,CMake会用默认值,但生成的代码可能跑不起来。
我一般这样写:
set(CMAKE_CUDA_ARCHITECTURES "61;75;86")
这表示生成三个架构的二进制代码。注意,这里写的是计算能力版本号,不是显卡型号。怎么查?去NVIDIA官网查你的显卡计算能力就行。
三、编译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,分开写更清晰。
四、核心知识体系
我把整个流程画了张图,方便你理解:
五、常见问题与避坑指南
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库