探索CUDA内核执行的全流程,从代码编译到GPU计算,详细描述每个关键步骤,包括编译过程、主机触发机制、数据传输、内核执行细节以及结果返回CPU的完整路径。
CUDA作为NVIDIA提供的并行计算平台和编程模型,广泛应用于科学计算、深度学习和图形处理等领域。当我们运行一个CUDA内核时,背后涉及复杂的编译、数据传输和并行计算过程。本文将从代码编译开始,逐步描述CUDA内核执行的完整旅程,帮助读者理解GPU计算的底层机制。 首先,CUDA程序的编译过程涉及多个阶段。nvcc编译器会将CUDA代码分为主机代码和设备代码进行处理。设备代码会经过cicc编译器转换为PTX(Parallel Thread Execution)虚拟指令集,然后由ptxas编译器将其转换为特定于GPU架构的SASS(Sass Assembly)机器代码。
PTX是一种设备无关的虚拟指令集,包含无限数量的寄存器,而SASS则是针对特定GPU架构的实际机器指令。例如,在编译一个简单的向量加法内核时,PTX代码会包含寄存器操作和内存访问指令,而SASS代码则会将这些操作优化为更高效的机器指令序列。 接下来,主机需要触发GPU执行内核。这个过程涉及多个步骤。首先,编译器会在主机代码中插入一个隐藏的构造函数,在main函数执行之前注册设备代码。然后,当遇到内核启动调用时,编译器会将其替换为一个生成的主机启动存根。
这个存根会将内核参数打包到主机内存中,并调用__cudaLaunch函数。这个函数会通过查询注册表,找到对应的设备代码符号名,并通过用户模式驱动程序(libcuda.so.1)触发内核的实际启动。 将内核代码加载到GPU上是一个关键步骤。在CUDA 12.2之后,模块加载默认是延迟的,这意味着内核代码只有在第一次启动时才会被上传到GPU内存中。当cuLaunchKernel需要将工作放到GPU上时,它会通过内核模式驱动程序nvidia.ko与GPU进行通信。这个过程涉及多个结构,包括主机内存中的pushbuffer和GPFIFO,以及设备内存中的USERD结构。
pushbuffer是一个内存区域,用于存储GPU命令,而GPFIFO是一个环形缓冲区,用于协调GPU和CPU之间的工作状态。 内核启动的核心是通过一系列方法(methods)来实现的。首先,设置SET_INLINE_QMD_ADDRESS_A/B方法,然后通过LOAD_INLINE_QMD_DATA方法将队列元数据(QMD)流式传输到pushbuffer中。QMD是一个描述计算网格的启动描述符,包含网格和块的维度、每个线程的寄存器数量、共享内存大小以及内核参数等信息。QMD还包含内核代码的起始地址和常量银行地址,这些信息告诉GPU如何执行内核。 GPU通过门铃(doorbell)机制被触发执行内核。
门铃是一个内存映射的寄存器,驱动程序通过写入门铃来通知GPU有新的工作需要处理。GPU的主机引擎会读取更新后的GP_PUT指针,并通过GPFIFO找到对应的pushbuffer区域,然后通过DMA(直接内存访问)将命令读取到GPU内部。当GPU完成内核执行后,它会通过QMD中的完成信号量通知CPU。 内核执行的细节涉及多个硬件组件。GPU的计算工作分配器(compute work distributor)负责将内核任务分配给各个流式多处理器(SM)。每个SM包含多个调度器,负责管理多个warp(warp是GPU中的基本执行单元,通常包含32个线程)。
调度器会根据每个warp的状态决定哪个warp可以执行下一条指令。这个过程涉及多个控制位,包括静态延迟计数、yield提示和依赖屏障索引。这些控制位由编译器根据内核代码的特性生成,帮助硬件调度器高效地管理warp的执行。 数据加载是内核执行的关键部分。当warp调度器找到可执行的warp并发出LDG.E加载指令时,硬件会计算每个线程的地址,并检测是否可以合并请求。由于我们的内核访问的是连续的浮点数组,硬件可以将32个4字节的请求合并为四个32字节的扇区请求。
这些请求首先会检查SM的L1数据缓存,如果未命中,则会通过高带宽交叉连接到L2缓存。如果L2缓存也未命中,请求会进一步传递到内存控制器,并通过内存总线到达物理GDDR6X VRAM芯片。 内核执行完成后,结果需要返回到CPU。由于内核启动是异步的,CPU会在内核执行期间继续运行。当内核执行完成后,GPU会通过QMD中的完成信号量通知CPU。然后,CPU会通过cudaMemcpy函数将结果从GPU内存复制到主机内存。
由于结果仍然在GPU的L2缓存中,这个复制过程可以直接从L2缓存读取,而无需经过DRAM,从而提高了数据传输的效率。 通过上述过程,我们可以看到CUDA内核的执行涉及复杂的编译、数据传输和并行计算机制。从代码编译到GPU计算,再到结果返回CPU,每个步骤都经过精心设计,以确保高效的并行计算。理解这些底层机制不仅有助于优化CUDA程序的性能,还能帮助开发者更好地利用GPU的计算能力。 。