GPU核心架构解析:流式多处理器(SM)、CUDA核心、内存层次结构与线程束调度
各位同学,今天我们来啃一块硬骨头——GPU的核心架构。说实话,我当年刚接触GPU编程时,也被这些概念绕得头晕。但后来我发现,只要把几个关键模块搞明白,整个GPU的运作逻辑就清晰了。
咱们从最核心的部件开始聊起。
流式多处理器(SM)——GPU的“计算单元”
SM,全称Streaming Multiprocessor,你可以把它想象成GPU里的一个小型处理器集群。一个GPU芯片上,少则有几十个SM,多则上百个。每个SM内部,才是真正干活的地方。
我习惯把SM比作一个“车间”。这个车间里有工人(CUDA核心)、有仓库(寄存器)、有临时堆放区(共享内存),还有一条高效的流水线(线程束调度器)。
为什么GPU能同时处理成千上万个线程?秘密就在SM的设计上。每个SM都能独立调度和执行线程块(Thread Block)。你写的CUDA代码里,每个block会被分配到某个SM上执行。SM之间是相对独立的,它们并行工作,互不干扰。
关键点:SM的数量决定了GPU的并行度上限。比如NVIDIA的A100有108个SM,而RTX 3090有82个SM。SM越多,能同时处理的block就越多。
CUDA核心——真正干活的“工人”
每个SM内部包含多个CUDA核心。注意,这里的“CUDA核心”其实是NVIDIA的营销术语,本质上就是一个浮点运算单元(FP32 Unit)。
举个例子,一个SM里有128个CUDA核心,那这个SM就能同时执行128个浮点运算。但这里有个坑——我刚开始做优化时,以为CUDA核心越多性能就一定越好。后来发现,核心利用率才是关键。
为什么?因为CUDA核心需要数据才能工作。如果数据没准备好,核心就只能干等着。这就是内存层次结构要解决的问题。
| 组件 | 每个SM的数量(典型值) | 主要职责 |
|---|---|---|
| CUDA核心 | 64~128 | 执行浮点/整数运算 |
| 特殊函数单元(SFU) | 4~16 | 执行sin、cos、sqrt等 |
| 加载/存储单元(LD/ST) | 16~32 | 处理内存访问 |
| 线程束调度器 | 2~4 | 调度warp执行 |
内存层次结构——GPU的“仓储系统”
GPU的内存体系,说白了就是速度与容量的博弈。我见过太多同学把数据一股脑塞进全局内存,结果性能惨不忍睹。咱们一层层看。
全局内存(Global Memory)
这是GPU最大的存储空间,通常有几十GB。但它的延迟很高,大约400~800个时钟周期。你想想看,CPU访问L1缓存只要几个周期,这差距有多大。
避坑指南:我曾经有个项目,矩阵乘法跑得特别慢。一分析发现,每次计算都要从全局内存读数据。后来改成用共享内存做分块,性能直接提升了10倍。
共享内存(Shared Memory)
共享内存是SM内部的一块高速存储,延迟只有几十个周期。但它很小,每个SM通常只有几十KB到上百KB。共享内存的作用是让同一个block内的线程可以快速交换数据。
嗯,这里要注意:共享内存是程序员手动管理的。你得自己决定哪些数据放进去,什么时候同步。用好了是神器,用不好就是坑。
寄存器(Registers)
寄存器是速度最快的存储,几乎没有延迟。每个SM有成千上万个寄存器,但每个线程能分到的数量有限。如果线程需要的寄存器太多,就会发生“寄存器溢出”,数据被迫存到本地内存,性能骤降。
我个人的经验是:写kernel时尽量控制每个线程的变量数量。如果发现性能异常,先查一下寄存器使用量。
本地内存(Local Memory)
本地内存其实是个“伪概念”。它物理上位于全局内存,只是逻辑上归每个线程私有。当寄存器不够用时,编译器会自动把变量溢出到本地内存。说白了,这是最后的退路,性能很差。
小技巧:在NVIDIA的Nsight工具里,可以查看每个kernel的寄存器使用量和本地内存使用量。如果本地内存不为0,说明发生了寄存器溢出,需要优化。
线程束(Warp)调度机制——GPU的“流水线管理”
这是GPU架构里最精妙的部分。线程束(Warp)是SM调度和执行的基本单位,通常包含32个线程。为什么是32?因为NVIDIA的设计师发现,32个线程一起执行,能最大化利用SIMD(单指令多数据)的优势。
每个SM里有多个线程束调度器。调度器的工作很简单:每个时钟周期,选一个warp,发射一条指令。但问题来了——如果某个warp在等数据(比如从全局内存读数据),调度器就会立刻切换到另一个warp。
这就是GPU的“零开销上下文切换”。CPU切换线程需要保存和恢复寄存器,开销很大。但GPU的warp切换几乎不花时间,因为每个warp的寄存器是物理独立的。
为什么会这样?因为GPU的设计哲学就是“用并行掩盖延迟”。一个warp在等数据,那就让另一个warp先算。只要你有足够多的warp,内存延迟就被完美隐藏了。
我刚开始写CUDA时,总想着减少warp数量。后来发现,warp越多越好。只要不超过SM的资源上限,更多的warp意味着更好的延迟隐藏。
注意:warp内的线程执行的是同一指令。如果遇到分支(if-else),部分线程会执行,部分线程会被“屏蔽”。这就是所谓的“warp divergence”。我踩过这个坑——一个简单的if语句,让性能直接腰斩。解决办法是尽量让warp内的线程走同一分支。
核心逻辑关系图
下面这张图,是我自己总结的GPU核心架构关系。你看一遍,应该就能把SM、CUDA核心、内存层次和warp调度串起来了。
从这张图你能看到:SM是核心计算单元,内部有CUDA核心、共享内存、寄存器和调度器。多个SM共享全局内存。线程束是调度的基本单位,由调度器分配到SM上执行。
好了,这一章的内容就到这里。记住一句话:理解GPU架构,就是理解SM、内存层次和warp调度这三者的协作关系。后面几章我们会深入每个细节,到时候你会发现自己已经能看懂大部分GPU优化技巧了。