目录
引言
最近DeepSeek的火爆带火了一个词PTX,很多人说DS使用PTX绕过了英伟达的CUDA生态,其实这里面有很深的误解,今天简单介绍一下英伟达CUDA的编译过程。
CUDA
CUDA(Compute Unified Device Architecture)是英伟达(NVIDIA)推出的一种并行计算平台和编程模型,它允许开发者利用NVIDIA GPU的强大计算能力进行通用计算,极大地提升了计算性能,在多个领域都有广泛应用。
- CUDA的架构与核心组件:在CUDA架构中,GPU由多个流处理器(Stream Processor,SP)组成,这些SP被组织成流式多处理器(Streaming Multiprocessor,SM)。SM是GPU的核心计算单元,能够同时执行大量的线程。CUDA程序通过主机(Host)端的CPU和设备(Device)端的GPU协同工作。开发者使用CUDA C/C++等编程语言编写代码,其中包含在主机上执行的部分和在设备上执行的核函数(Kernel Function)。核函数是在GPU上并行执行的函数,由大量线程组成线程块(Thread Block),线程块再组成线程格(Grid),这种层次结构方便对大规模数据进行并行处理。
- CUDA的优势与应用领域:CUDA的优势在于能够利用GPU的高度并行性,相比传统CPU在某些计算密集型任务上实现显著的加速。在科学计算领域,如分子动力学模拟、气候建模等,CUDA可以加速复杂的数学计算,大幅缩短计算时间。在深度学习方面,CUDA更是成为了主流的计算平台,像TensorFlow、PyTorch等深度学习框架都深度支持CUDA,用于加速神经网络的训练和推理过程,使得训练大规模深度学习模型成为可能。在计算机图形学中,CUDA可用于加速光线追踪、图像处理等任务,提升图形渲染的质量和速度。
CUDA 工具包面向一类应用程序,这类应用程序的控制部分作为一个进程在通用计算设备上运行,并且使用一个或多个 NVIDIA GPU 作为协处理器,以加速单程序多数据(SPMD)并行作业。这些作业是自包含的,也就是说,它们可以由一批 GPU 线程完全独立地执行并完成,无需主机进程干预,从而从并行图形硬件中获得最佳效益。
GPU 代码以一种本质上是 C++ 的语言实现为一组函数,但使用了一些注释来将它们与主机代码区分开,同时还使用注释来区分 GPU 上存在的不同类型的数据内存。这些函数可以有参数,并且可以使用与常规 C 函数调用非常相似的语法来调用,但语法略有扩展,以便能够指定必须执行被调用函数的 GPU 线程矩阵。在其生命周期内,主机进程可以调度许多并行 GPU 任务。
CUDA编译原理
CUDA 关键编译文件
除了 C++ 程序编译中常见的文件,如静态库文件、共享库文件、目标文件外,还有三个特定于 CUDA 程序编译的关键文件,包括 PTX 文件、CUBIN 文件和 FATBIN 文件。
PTX
GPU 编译是通过一种中间表示形式 PTX 进行的,PTX 可被视为虚拟 GPU 架构的汇编语言。与 CPU 的汇编语言一样,PTX 始终以文本格式表示。
虚拟 GPU 架构可以通过 NVCC 编译器的 --gpu-architecture
参数指定。虚拟 GPU 架构规范,如 compute_53
,总是以 compute_
开头。通常,我们为一个 CUDA 程序只指定一个虚拟 GPU 架构。不过,NVCC 编译器也支持通过 --generate-code
针对多个不同的虚拟 GPU 架构进行编译。
具有特定虚拟 GPU 架构的 PTX 通常是向前兼容的。也就是说,用 compute_53
生成的 PTX 可以在比 53
新或相同的真实 GPU 架构上进一步编译,但不能在更旧的架构上编译,尽管可能会生成不太理想的代码,因为它可能会缺少新虚拟 GPU 架构中的一些特性和优化。
CUBIN
CUBIN 是针对单个真实 GPU 架构的 CUDA 设备代码二进制文件。真实 GPU 架构可以通过 NVCC 编译器的 --gpu-code
参数指定。真实 GPU 架构规范,如 sm_53
,总是以 sm_
开头。对于一个虚拟 GPU 架构,可以指定多个真实架构。
在 NVCC 编译过程中,CUBIN 文件也是可选的。如果在 NVCC 编译期间没有指定真实的 GPU 架构,那么在 CUDA 程序执行之前,CUDA 设备代码将由 CUDA 运行时库针对执行设备进行即时(JIT)编译。此外,如果在 NVCC 编译期间指定的真实 GPU 架构与执行设备不匹配,CUDA 设备代码也将由 CUDA 运行时库针对执行设备进行 JIT 编译。
FATBIN
由于可能存在多个 PTX 文件以及可选的多个 CUBIN 文件,NVCC 会将这些文件合并到一个 CUDA 胖二进制文件 FATBIN 中。这个 FATBIN 文件最终将嵌入到由 C++ 编译器编译的主机代码二进制文件中。
CUDA 编译流程
CUDA 编译过程如下:输入程序首先进行预处理以进行设备编译,被编译为 CUDA 二进制(CUBIN)和 / 或 PTX 中间代码,这些代码被放置在一个胖二进制文件 FATBIN 中。然后输入程序再次进行预处理以进行主机编译,并合成以嵌入 FATBIN 并将 CUDA 特定的 C++ 扩展转换为标准 C++ 结构。接着 C++ 主机编译器将嵌入 FATBIN 的合成主机代码编译为主机对象。
对于每个虚拟 / 真实架构组合,都要重复 ptxas 和 nvlink 的操作。设备链接由多个步骤组成,最终生成可执行文件。
设备代码编译
如 CUDA 关键编译文件中所述,设备代码编译遵循先为特定虚拟架构生成 PTX,然后为特定真实架构生成 CUBIN 的过程。
NVCC(设备代码为.x.cu)
- 阶段 1(PTX 生成):生成.x.ptx
- 阶段 2(CUBIN 生成):生成.x.cubin 并执行
具有虚拟和真实架构的两阶段设备代码编译
CUDA编译举例
我们可以用Compiler Explorer来理解这个编译过程。在Compiler Explorer的页面,选择编译器为CUDA C++,可以看到它有两个生成窗口。中间的窗口显示的是主机编译的结果,包括x86的汇编代码和Fatbin文件的内容。右边的窗口显示的PTX代码的内容。
结语
从上面的过程可以看出,DS并没有绕开英伟达的GPU,只是相当于用汇编代码写了一些函数,从而获得了更高的执行效率。这种做法实际上和英伟达的技术绑得更紧了。