CUDA#
CUDA 支持有两种方式。新方法在 CMake 3.8(Windows 为 3.9)中引入,应强烈优先于旧方法——我提及旧方法是因为某个旧包很可能包含它。与旧语言不同,CUDA 支持正在快速发展,而构建 CUDA 很困难,因此我建议您要求使用非常新的 CMake 版本!CMake 3.17 和 3.18 针对 CUDA 有很多直接改进。
关于 CUDA 和现代 CMake 的一个好资源是 CMake 开发者 Robert Maynard 在 GTC 2017 上的演讲。
添加 CUDA 语言#
启用 CUDA 支持有两种方法。如果 CUDA 不是可选的:
project(MY_PROJECT LANGUAGES CUDA CXX)
您可能还想在这里列出 CXX 。如果 CUDA 是可选的,您需要将这个条件性地放在某处:
enable_language(CUDA)
您可以通过检查 CMAKE_CUDA_COMPILER
(在 CMake 3.11 之前缺失)来查看 CUDA 是否存在。
您可以检查像 CMAKE_CUDA_COMPILER_ID
(对于 nvcc
,这是 "NVIDIA" ,Clang 在 CMake 3.18 中添加)这样的变量。您可以使用 CMAKE_CUDA_COMPILER_VERSION
来检查版本。
CUDA 相关变量#
许多名称中带有 CXX 的变量都有以 CUDA 代替的 CUDA 版本。例如,要设置 CUDA 所需的 C++标准,
if(NOT DEFINED CMAKE_CUDA_STANDARD)
set(CMAKE_CUDA_STANDARD 11)
set(CMAKE_CUDA_STANDARD_REQUIRED ON)
endif()
如果你在寻找 CUDA 的标准级别,在 CMake 3.17 中增加了一系列新的编译器特性,比如 cuda_std_11
。这些特性与 cxx 版本一样,能为你带来同样的好处。
添加库/可执行文件#
这部分很简单;只要你对 CUDA 文件使用 .cu
,你就可以像平时一样直接添加库。
你也可以使用可分离编译:
set_target_properties(mylib PROPERTIES
CUDA_SEPARABLE_COMPILATION ON)
你也可以直接使用 CUDA_PTX_COMPILATION
属性来创建 PTX 文件。
针对架构#
当你构建 CUDA 代码时,通常应该针对某个架构。如果你不这样做,你会为最低支持的架构编译 PTX,这提供了基本指令,但在运行时编译,可能导致加载速度明显变慢。
所有显卡都有一个架构级别,比如“7.2”。你有两个选择;第一个是代码级别;这会向正在编译的代码报告一个版本,比如“5.0”,并且会利用到 5.0 版本的所有功能,但不会超过(假设代码写得很好/标准库)。然后是目标架构,它必须等于或大于代码架构。它需要与你的目标显卡的主版本号相同,并且要等于或小于目标显卡。所以 7.0 是我们 7.2 显卡的一个常见选择。最后,你也可以生成 PTX;这将在所有未来的显卡上工作,但会即时编译。
在 CMake 3.18 中,目标架构的设置变得非常简单。如果你的版本范围包含 3.18 或更高版本,你将使用 CMAKE_CUDA_ARCHITECTURES 变量和目标上的 CUDA_ARCHITECTURES 属性。你可以列出值(不包括 . ),例如 50 代表架构 5.0。这将为真实(SASS)和虚拟架构(PTX)生成代码。传递 '50-real' 值将仅生成 SASS,而传递 '50-virtual' 值将仅生成 PTX。如果设置为 OFF,则不会传递架构。
在 CMake 3.24 中,架构值已扩展以支持用户友好的值 'native'、'all' 和 'all-major'。
与目标工作#
使用目标应该与 CXX 类似,但存在一个问题。如果你包含一个包含编译器选项(标志)的目标,大多数情况下,这些选项不会被正确的包含保护(而且它们具有正确 CUDA 封装的几率更小)。以下是正确的编译器选项行的示例:
set(opt "$<$<BUILD_INTERFACE:$<COMPILE_LANGUAGE:CXX>>:-fopenmp>$<$<BUILD_INTERFACE:$<COMPILE_LANGUAGE:CUDA>>:-Xcompiler=-fopenmp>")
然而,如果你使用几乎任何 find_package,并且使用 Modern CMake 的 target 和继承方法,一切都会崩溃。我是吃一堑长一智。
目前,这里有一个相当合理的解决方案,只要你知道未别名化的目标名称。这是一个函数,通过使用 CUDA 编译器来包装标志,以修复仅针对 C++的目标:
function(CUDA_CONVERT_FLAGS EXISTING_TARGET)
get_property(old_flags TARGET ${EXISTING_TARGET} PROPERTY INTERFACE_COMPILE_OPTIONS)
if(NOT "${old_flags}" STREQUAL "")
string(REPLACE ";" "," CUDA_flags "${old_flags}")
set_property(TARGET ${EXISTING_TARGET} PROPERTY INTERFACE_COMPILE_OPTIONS
"$<$<BUILD_INTERFACE:$<COMPILE_LANGUAGE:CXX>>:${old_flags}>$<$<BUILD_INTERFACE:$<COMPILE_LANGUAGE:CUDA>>:-Xcompiler=${CUDA_flags}>"
)
endif()
endfunction()
有用的变量#
即使不启用 CUDA 语言,您也可以使用 FindCUDAToolkit 来查找各种有用的目标和变量。
cmake_minimum_required(VERSION 3.17)
project(example LANGUAGES CXX)
find_package(CUDAToolkit REQUIRED)
add_executable(uses_cublas source.cpp)
target_link_libraries(uses_cublas PRIVATE CUDA::cublas)
使用 find_package(CUDAToolkit)
提供的变量:
CUDAToolkit_BIN_DIR
: 存放 nvcc 可执行文件的目录CUDAToolkit_INCLUDE_DIRS
: 包含内置 Thrust 等头文件的目录列表CUDAToolkit_LIBRARY_DIR
: 存放 CUDA 运行时库的目录
启用 CUDA 语言提供的变量:
CMAKE_CUDA_COMPILER
: 带位置的 NVCCCMAKE_CUDA_TOOLKIT_INCLUDE_DIRECTORIES
: 内置 Thrust 等的位置
备注
请注意 FindCUDA 已弃用,但对于 CMake < 3.18 的版本,以下函数需要 FindCUDA:
CUDA 版本检查 / 选择版本
架构检测(注意:3.12 版本部分修复了这个问题)
从非
-.cu
文件链接 CUDA 库
经典 FindCUDA [警告:不建议使用] (仅作参考用)#
如果你需要支持较旧版本的 CMake,我建议至少将 CMake 3.9 版本中的 FindCUDA 包含到你的 cmake 文件夹中(可以查看 CLIUtils github 组织的 git 仓库)。你需要两个新增加的功能: CUDA_LINK_LIBRARIES_KEYWORD 和 cuda_select_nvcc_arch_flags ,以及更新的架构和 CUDA 版本。
要使用旧的 CUDA 支持,你使用 find_package :
find_package(CUDA 7.0 REQUIRED)
message(STATUS "Found CUDA ${CUDA_VERSION_STRING} at ${CUDA_TOOLKIT_ROOT_DIR}")
你可以通过 CUDA_NVCC_FLAGS (列表追加)控制 CUDA 标志,并通过 CUDA_SEPARABLE_COMPILATION 控制可分离编译。你还需要确保 CUDA 能良好运行,并向目标添加关键字(CMake 3.9+):
set(CUDA_LINK_LIBRARIES_KEYWORD PUBLIC)
你可能还想允许用户检查其当前硬件的架构标志:
cuda_select_nvcc_arch_flags(ARCH_FLAGS) # optional argument for arch to add