GPU架构基础:SM、Warp、Thread Block、Global Memory、Shared Memory

各位同学,今天我们来聊聊GPU的骨架。说白了,你要写好CUDA核函数,就得先摸清楚GPU到底是怎么干活的。我刚开始接触CUDA时,也踩过不少坑——比如以为线程越多越快,结果跑起来比CPU还慢。后来才明白,不理解硬件架构,写出来的代码就是瞎蒙。

SM:GPU的心脏

SM(Streaming Multiprocessor)是GPU最核心的计算单元。你可以把它想象成一个微型处理器,里面有自己的寄存器、计算单元、缓存和调度器。一块GPU上通常有几十到上百个SM。

我在项目中遇到过一个问题:同一个核函数,在RTX 3090上跑得飞快,换到A100上反而慢了。后来一查,原来是SM数量不同导致的资源分配策略差异。嗯,这里要注意——SM的数量决定了你的并行度上限。

关键点:每个SM能同时驻留的线程块数量是有限的,通常为16-32个。别想着把几千个线程块全塞进去,硬件吃不消。

Warp:真正的执行单元

Warp是GPU调度和执行的基本单位。一个Warp包含32个线程,它们在同一时刻执行同一条指令。你想想看,这就像32个人排成一排,教官喊「向左转」,所有人必须同时向左转。

为什么会这样?因为GPU的设计哲学就是「单指令多线程」(SIMT)。32个线程共享一个指令单元,这样能省下大量的控制逻辑,把晶体管用在计算上。

个人经验:我建议你在写核函数时,尽量让同一个Warp里的线程走相同的分支。如果出现分支发散(比如if-else),性能会大打折扣。我曾经在一个图像处理项目里,因为没注意分支发散,性能直接掉了40%。

Thread Block:组织线程的容器

Thread Block(线程块)是程序员直接控制的线程组织方式。一个Block里的线程可以协作,共享数据,还能同步。Block的大小通常设为128、256或512个线程。

这里有个坑:Block的大小必须是32的倍数。为什么?因为Warp是32个线程一组,Block大小不是32的倍数,就会有空闲的线程,浪费计算资源。

Block大小 Warp数量 资源利用率
128 4 100%
256 8 100%
300 10(有浪费) 93.75%

避坑指南:我曾经在写矩阵乘法时,把Block大小设成了100。结果每个Block有4个线程闲置,整体性能比用128的版本慢了15%。从那以后,我再也不用非32倍数的Block大小了。

Global Memory:大而慢的仓库

Global Memory就是GPU的显存,容量大(几GB到几十GB),但访问延迟高(几百个时钟周期)。所有线程都能读写它,但速度嘛...说实话,挺慢的。

优化Global Memory访问的关键是合并访问。什么意思?就是让同一个Warp里的32个线程,访问连续的内存地址。这样GPU可以把多次内存访问合并成一次,效率翻倍。

核心原则:尽量让线程ID和内存地址一一对应。比如线程0访问地址0,线程1访问地址1,以此类推。别让线程0访问地址0,线程1访问地址100,那样就乱套了。

Shared Memory:小而快的缓存

Shared Memory是每个SM内部的高速缓存,容量很小(通常几十KB),但访问速度极快(几个时钟周期)。同一个Block里的线程可以共享这块内存。

我建议你在需要频繁数据复用的场景下,优先使用Shared Memory。比如矩阵乘法、卷积、归约等操作,把数据从Global Memory搬到Shared Memory里,然后反复使用,能省下大量的全局内存访问。

个人技巧:使用Shared Memory时要注意Bank Conflict。Shared Memory被分成32个Bank,如果多个线程同时访问同一个Bank,就会发生冲突,导致串行化。解决办法是让线程访问的地址错开,比如加个步长或者填充一些无用数据。

知识体系总览

下面这张图展示了GPU架构的核心概念和它们之间的关系。我画这张图时,特意把层次关系理清楚了——从硬件到软件,从大到小。

GPU SM 0 SM 1 SM N Shared Memory (几十KB, 低延迟) Warp Scheduler (调度32线程组) Register File (65536个寄存器) Thread Block (128-1024线程) 包含多个Warp Global Memory (显存, 几GB-几十GB, 高延迟) Shared Memory Warp Thread Block Global Memory

从这张图可以看出,GPU的层次结构非常清晰:

  • 最底层是Global Memory,所有SM共享,容量大但速度慢
  • 中间层是SM,每个SM有自己的Shared Memory、Warp调度器和寄存器
  • 最上层是Thread Block,它被分配到某个SM上执行,Block内的线程共享Shared Memory

总结一下

理解这五个概念,你就掌握了GPU编程的底层逻辑。说白了,写CUDA代码就是在做资源管理——怎么分配线程块,怎么利用Shared Memory,怎么让Global Memory访问更高效。我见过太多人一上来就写核函数,结果性能惨不忍睹。嗯,先打好基础,后面优化起来才顺手。

一句话记住:SM是工厂,Warp是流水线,Thread Block是工单,Global Memory是仓库,Shared Memory是工作台。你作为工程师,就是要把工单安排得明明白白,让流水线不空转,让工作台不闲置。

想要更多资料,联系可可