GPU的“心脏”:流式多处理器(SM)架构详解

各位同学,今天我们来聊聊GPU真正的核心——流式多处理器,也就是SM。我常说,不理解SM,就等于没入门GPU架构。它就像一颗心脏,负责把指令变成实实在在的计算结果。

你想想看,一块GPU里有成百上千个核心,但真正干活的组织单元,就是SM。每个SM内部,藏着大量的计算单元、调度器、缓存和寄存器。说白了,SM就是GPU里的“迷你CPU集群”。

核心概念:SM(Streaming Multiprocessor)是GPU内部最基本的硬件计算单元。一个GPU由多个SM组成,每个SM独立执行线程块(Thread Block)。

SM的内部结构长什么样?

我习惯把SM想象成一个“小工厂”。这个工厂里有几条生产线,每条线上有几个工人。工人就是CUDA核心,生产线就是Warp调度器。嗯,这里要注意,不同代的GPU,SM内部布局差异很大。

以NVIDIA的Fermi架构为例,一个SM包含:

  • 32个CUDA核心:负责整数和浮点运算
  • 16个LD/ST单元:处理内存加载和存储
  • 4个SFU:专门处理特殊函数(如sin、cos)
  • Warp调度器:每个时钟周期选择一条Warp执行
  • 共享内存:线程块内快速数据交换
  • 寄存器文件:每个线程私有,容量巨大

我在项目中遇到过一个问题:共享内存用多了,寄存器就少了。这俩是“抢地盘”的关系。你想想看,一个线程块如果占用了太多共享内存,能同时运行的线程块数量就会减少,最终影响整体吞吐量。

Warp:SM里的执行单位

SM不直接调度线程,它调度的是Warp。一个Warp包含32个线程,它们在同一时刻执行同一条指令。这就是SIMT(单指令多线程)的精髓。

为什么会这样?因为硬件设计上,32个线程共享一个指令解码器。这样省面积、省功耗。但代价是——如果32个线程走不同的分支,性能就惨了。

避坑指南:我曾经在写CUDA程序时,让一个Warp里的线程走了不同的if-else分支。结果性能直接腰斩。后来我学会了用“谓词执行”或者重新组织数据,让同一个Warp的线程尽量走同一条路。

Warp的调度过程是这样的:

  1. Warp调度器从就绪队列中选一个Warp
  2. 检查该Warp的指令是否准备好(数据依赖是否解决)
  3. 如果准备好,发射指令到执行单元
  4. 执行单元在4个时钟周期内完成32个线程的计算

说白了,SM通过快速切换Warp来隐藏延迟。当一个Warp在等内存数据时,调度器立刻切换到另一个Warp。这就是GPU高吞吐量的秘密。

共享内存与寄存器:SM的“高速缓存”

共享内存是SM内部的一块SRAM,所有线程都可以访问。它的延迟只有几十个时钟周期,远低于全局内存。我习惯用它来做线程间的数据交换,或者做数据重用。

寄存器呢?每个线程私有,速度最快。但数量有限。一个SM的寄存器总数是固定的,比如Kepler架构是65536个。如果每个线程用32个寄存器,那一个SM最多同时容纳2048个线程。

架构 SM寄存器数 共享内存大小 最大线程数/SM
Fermi 32768 48 KB 1536
Kepler 65536 48 KB 2048
Maxwell 65536 96 KB 2048
Turing 65536 64 KB 1024

你看这张表,寄存器数量从Fermi到Turing翻了一倍,但共享内存大小也在涨。我建议你在写kernel时,先用--ptxas-options=-v编译选项看看寄存器使用量。如果超过32个,就该优化了。

SM的指令流水线

SM内部有一条完整的指令流水线。我把它分成几个阶段:

  • 取指:从指令缓存中读取Warp的指令
  • 译码:解析指令类型和操作数
  • 发射:将指令发送到对应的执行单元
  • 执行:CUDA核心、SFU或LD/ST单元干活
  • 写回:将结果写回寄存器

嗯,这里要注意,不同指令的延迟不一样。整数加法只要1个周期,但浮点乘法可能要4个周期。全局内存加载更是要几百个周期。SM通过Warp级并行来掩盖这些延迟。

警告:不要以为SM内部所有单元都能同时工作。CUDA核心和SFU是共享发射端口的。如果你在代码里大量使用sin/cos,SFU会成为瓶颈,CUDA核心反而闲着。我踩过这个坑,后来用查表法替代了部分三角函数。

SM的线程调度策略

SM怎么决定哪个Warp先执行?我见过两种策略:

  1. 轮询调度:每个就绪的Warp轮流执行一个周期
  2. 优先级调度:根据Warp的优先级或等待时间决定

实际硬件用的是混合策略。SM会维护一个就绪队列,每次选一个“最老”的Warp。这样能保证公平性,也能避免某些Warp饿死。

你想想看,如果一个Warp一直在等全局内存的数据,它就不会进入就绪队列。SM会一直调度其他Warp,直到数据回来。这就是为什么线程数越多,延迟隐藏效果越好。

SM架构的演进

从Fermi到Hopper,SM架构一直在变。我简单总结几个关键变化:

  • Fermi:首次引入真正的SM概念,32个CUDA核心
  • Kepler:SMX单元,192个核心,但调度器更复杂
  • Maxwell:SMM单元,128个核心,分区调度提升效率
  • Pascal:SM单元回归,但支持FP16和统一内存
  • Turing:加入Tensor Core,专门做矩阵运算
  • Ampere/Hopper:SM内部分区更细,支持异步指令

我个人觉得,Turing架构是个分水岭。Tensor Core的出现,让SM不再只是做通用计算,而是开始为AI推理和训练做优化。你想想看,一个SM里既有CUDA核心,又有Tensor Core,还有RT Core,调度复杂度可想而知。

SM性能调优的实战建议

说了这么多理论,来点实际的。我在调优SM性能时,会关注这几个指标:

  • Occupancy:活跃Warp数 / 最大Warp数。低于50%说明资源利用率低
  • 共享内存使用量:每个线程块用的共享内存,尽量控制在16KB以内
  • 寄存器压力:每个线程寄存器数,超过32个会降低Occupancy
  • Warp发散:同一个Warp里分支路径数,越少越好

小技巧:我常用NVIDIA的Nsight Compute工具看SM的stall原因。如果发现“long scoreboard”占比高,说明内存延迟没藏好。这时候可以增加线程数,或者用__ldg()指令启用只读缓存。

最后说一句,SM架构虽然复杂,但核心思想很简单:用大量线程并行执行,用快速切换隐藏延迟。你只要记住这个,后面学内存层次结构、学Tensor Core,都会轻松很多。

SM内部结构图 流式多处理器 (SM) Warp调度器 每周期选一个Warp 指令分发单元 译码并发射指令 寄存器文件 65536个寄存器 执行单元 CUDA核心 x32 整数/浮点运算 LD/ST单元 x16 内存加载/存储 SFU x4 特殊函数 (sin/cos) 共享内存 (48-96 KB) 线程块内数据交换

这张图展示了SM内部的核心组件。Warp调度器选好Warp后,指令分发单元把指令送到执行单元。CUDA核心、LD/ST单元和SFU各司其职。共享内存和寄存器文件为它们提供数据支持。

好了,SM架构的核心内容就这些。记住,SM是GPU的“心脏”,理解它,你就掌握了GPU硬件架构的钥匙。