目录
Programming Interface
CUDA
C++ 为熟悉 C++ 编程语言的用户提供了一条简单的途径,可以轻松编写供设备执行的程序。
它由 C++ 语言的最小扩展集和运行时库组成。
核心语言扩展已在编程模型中引入。它们允许程序员将内核定义为 C++ 函数,并在每次调用该函数时使用一些新语法来指定网格和块维度。所有扩展的完整描述可以在 C++ 语言扩展中找到。包含其中一些扩展的任何源文件都必须使用 nvcc 进行编译,如使用 NVCC
编译中所述。
运行时是在 CUDA Runtime
中引入的。它提供在主机上执行的 C 和 C++ 函数,以分配和释放设备内存、在主机内存和设备内存之间传输数据、管理具有多个设备的系统等。运行时的完整描述可以在 CUDA
参考手册中找到。
运行时构建在较低级别的 C API(CUDA
驱动程序 API)之上,应用程序也可以访问该 API。驱动程序 API 通过公开较低级别的概念(例如 CUDA
上下文(设备的主机进程的模拟)和 CUDA
模块(设备的动态加载库的模拟))来提供额外的控制级别。大多数应用程序不使用驱动程序 API,因为它们不需要这种额外的控制级别,并且在使用运行时时,上下文和模块管理是隐式的,从而产生更简洁的代码。由于运行时可与驱动程序 API 互操作,因此大多数需要某些驱动程序 API 功能的应用程序可以默认使用运行时 API,并且仅在需要时使用驱动程序 API。驱动程序 API 在驱动程序 API 中进行了介绍,并在参考手册中进行了完整描述。
Compilation with NVCC
可以使用称为 PTX
的 CUDA
指令集架构来编写内核,PTX
参考手册中对此进行了描述。然而,使用高级编程语言(例如 C++)通常更有效。在这两种情况下,内核都必须由 nvcc
编译为二进制代码才能在设备上执行。
nvcc
是一个编译器驱动程序,可简化编译 C++
或 PTX
代码的过程:它提供简单且熟悉的命令行选项,并通过调用实现不同编译阶段的工具集合来执行它们。本节概述了 nvcc
工作流程和命令选项。完整的描述可以在 nvcc
用户手册中找到。
1. Compilation Workflow
1.1. Offline Compilation
使用 nvcc
编译的源文件可以包含主机代码(即在主机上执行的代码)和设备代码(即在设备上执行的代码)的混合。 nvcc
的基本工作流程包括将设备代码与主机代码分离,然后:
- 将设备代码编译为汇编形式(
PTX
代码)和/或二进制形式(cubin
对象), - 并通过必要的
CUDA
运行时函数调用替换内核中引入的 <<<…>>> 语法(并在执行配置中更详细地描述)来修改主机代码,以从PTX
代码加载和启动每个已编译的内核,以及/或cubin
对象。
修改后的主机代码可以作为 C++ 代码输出,留待使用其他工具进行编译,也可以通过让 nvcc
在最后一个编译阶段调用主机编译器来直接作为目标代码输出。修改后的主机代码可以作为 C++ 代码输出,留待使用其他工具进行编译,也可以通过让 nvcc
在最后一个编译阶段调用主机编译器来直接作为目标代码输出。
然后应用程序可以:
- 链接到已编译的主机代码(这是最常见的情况),
- 或者忽略修改后的主机代码(如果有)并使用 CUDA 驱动程序 API(请参阅驱动程序 API)加载并执行 PTX 代码或 cubin 对象。
1.2. Just-in-Time Compilation
应用程序在运行时加载的任何 PTX
代码都会由设备驱动程序进一步编译为二进制代码。这称为即时编译。即时编译增加了应用程序加载时间,但允许应用程序从每个新设备驱动程序附带的任何新编译器改进中受益。这也是应用程序在编译应用程序时不存在的设备上运行的唯一方法,如应用程序兼容性中详细介绍的。
当设备驱动程序即时编译某些应用程序的某些 PTX
代码时,它会自动缓存生成的二进制代码的副本,以避免在应用程序的后续调用中重复编译。当设备驱动程序升级时,缓存(称为计算缓存)会自动失效,因此应用程序可以从设备驱动程序中内置的新即时编译器的改进中受益。
环境变量可用于控制即时编译,如 CUDA
环境变量中所述
作为使用 nvcc
编译 CUDA
C++ 设备代码的替代方案,NVRTC
可用于在运行时将 CUDA
C++ 设备代码编译为 PTX
。 NVRTC
是CUDA
C++的运行时编译库;更多信息可在 NVRTC
用户指南中找到。
2. Binary Compatibility
二进制代码是特定于体系结构的。cubin
对象是使用指定目标架构的编译器选项 -code
生成的:例如,使用 -code=sm_80
进行编译会为计算能力 8.0
的设备生成二进制代码。从一个次要修订版到下一个修订版可以保证二进制兼容性,但不能从一个次要修订版到前一个修订版或跨主要修订版保证二进制兼容性。换句话说,为计算能力 X.y 生成的 cubin
对象将仅在计算能力 X.z
的设备上执行,其中 z≥y
。
Note
仅桌面支持二进制兼容性。
Tegra
不支持它。此外,不支持桌面和Tegra
之间的二进制兼容性。
3. PTX Compatibility
某些 PTX
指令仅在具有较高计算能力的设备上受支持。例如,Warp Shuffle Functions
仅在计算能力 5.0 及以上的设备上支持。 -arch
编译器选项指定将 C++ 编译为 PTX
代码时假定的计算能力。因此,例如,包含 warp shuffle
的代码必须使用 -arch=compute_50
(或更高版本)进行编译。
为某些特定计算能力生成的 PTX
代码始终可以编译为具有更大或相同计算能力的二进制代码。请注意,从早期 PTX
版本编译的二进制文件可能无法使用某些硬件功能。例如,从为计算能力 6.0 (Pascal
) 生成的 PTX
编译的计算能力 7.0 (Volta) 的二进制目标设备将不会使用 Tensor Core
指令,因为这些指令在 Pascal
上不可用。因此,最终的二进制文件的性能可能比使用最新版本的 PTX
生成的二进制文件的性能更差。
4. Application Compatibility
要在具有特定计算功能的设备上执行代码,应用程序必须加载与此计算功能兼容的二进制或 PTX
代码,如二进制兼容性和 PTX
兼容性中所述。特别是,为了能够在具有更高计算能力的未来架构上执行代码(尚无法生成二进制代码),应用程序必须加载将为这些设备即时编译的 PTX
代码(请参阅 Just-
及时编译(Just-in-Time Compilation))。
将哪些 PTX
和二进制代码嵌入到 CUDA
C++ 应用程序中由 -arch
和 -code
编译器选项或 -gencode
编译器选项控制,如 nvcc
用户手册中详细说明。例如,
nvcc x.cu
-gencode arch=compute_50,code=sm_50
-gencode arch=compute_60,code=sm_60
-gencode arch=compute_70,code=\"compute_70,sm_70\"
嵌入与计算能力 5.0 和 6.0(第一个和第二个 -gencode
选项)兼容的二进制代码以及与计算能力 7.0(第三个 -gencode
选项)兼容的 PTX
和二进制代码。
生成主机代码以在运行时自动选择要加载和执行的最合适的代码,在上面的示例中,该代码将是:
- 5.0 二进制代码,适用于计算能力 5.0 和 5.2 的设备,
- 6.0 二进制代码,适用于计算能力 6.0 和 6.1 的设备,
- 7.0 二进制代码,适用于计算能力 7.0 和 7.5 的设备,
- PTX 代码在运行时编译为二进制代码,适用于计算能力 8.0 和 8.6 的设备。
x.cu
可以具有使用扭曲减少操作的优化代码路径,例如,仅在计算能力 8.0 及更高版本的设备中支持这些操作。 __CUDA_ARCH__
宏可用于根据计算能力区分各种代码路径。它仅为设备代码定义。例如,当使用 -arch=compute_80
进行编译时,__CUDA_ARCH__
等于 800。
使用驱动程序 API 的应用程序必须编译代码以分隔文件,并在运行时显式加载和执行最合适的文件。
Volta 架构引入了独立线程调度,它改变了 GPU 上线程的调度方式。对于依赖于以前架构中 SIMT 调度的特定行为的代码,独立线程调度可能会改变参与线程的集合,从而导致不正确的结果。为了帮助迁移,同时实施独立线程调度中详细介绍的纠正措施,Volta 开发人员可以使用编译器选项组合 -arch=compute_60 -code=sm_70
选择加入 Pascal 的线程调度。
nvcc
用户手册列出了 -arch、-code
和 -gencode
编译器选项的各种简写。例如,-arch=sm_70
是 -arch=compute_70
-code=compute_70,sm_70
的简写(与 -gencode arch=compute_70,code=\"compute_70,sm_70\"
相同)。
5. C++ Compatibility
编译器前端根据C++语法规则处理CUDA源文件。主机代码支持完整的 C++。但是,设备代码仅完全支持 C++ 的一个子集,如 C++ 语言支持中所述。
6. 64-Bit Compatibility
64 位版本的 nvcc
以 64 位模式编译设备代码(即指针是 64 位)。仅以 64 位模式编译的主机代码支持以 64 位模式编译的设备代码。