2、GPU架构基础:CUDA核心与SM、内存层次结构(Global/Shared/Register)、PCIe与NVLink互联
好,咱们正式开始聊多GPU集群之前,得先把单卡上的那点事儿捋清楚。说白了,你连一个GPU内部怎么干活都不知道,后面搞多卡通信那就是瞎忙活。这一节,我带你看看GPU的“五脏六腑”。
2.1 CUDA核心与SM:到底谁在干活?
很多人一上来就问:“GPU有多少个核心?” 嗯,这个问题其实有点坑。你看到的那些几千上万个“核心”,跟我理解的CPU核心不是一回事。
在NVIDIA的GPU里,最小的执行单元叫 CUDA Core。但真正独立调度、拥有完整指令流水线的,是 SM(Streaming Multiprocessor,流式多处理器)。你可以把SM想象成一个车间,CUDA Core就是车间里的工人。
关键认知: 一个SM包含多个CUDA Core(比如几十个到上百个不等),还有共享内存、寄存器文件、调度器、缓存等。程序是跑在SM上的,不是直接跑在单个CUDA Core上的。
我记得刚入行时,有个同事写了个kernel,以为线程数越多越好,结果性能反而下降了。为什么?因为他没搞懂 Warp 的概念。SM调度的最小单位是Warp,通常是32个线程一组。同一个Warp里的线程,必须执行同一条指令(SIMT模型)。如果分支发散,那性能就惨了。
避坑指南: 我曾经在优化一个粒子模拟程序时,发现分支判断导致Warp内线程走了不同路径。后来我把数据按条件重新排序,让同一个Warp里的线程执行相同分支,性能直接翻倍。记住:Warp内无分支,这是写CUDA代码的黄金法则之一。
每个SM能同时驻留多少个Warp?这取决于寄存器资源和共享内存的消耗。你想想看,一个SM的寄存器文件是有限的(比如65536个寄存器),每个线程分得越多,能同时跑的线程就越少。这就是所谓的 Occupancy(占用率)。
2.2 内存层次结构:Global / Shared / Register
GPU的内存,说白了就是个金字塔。越往上越快,但越小。我习惯把这三层记成:大、慢、远 vs 小、快、近。
| 内存类型 | 位置 | 速度 | 容量 | 作用域 |
|---|---|---|---|---|
| Global Memory | 显存(DRAM) | 慢(几百周期) | 大(GB级) | 所有线程 |
| Shared Memory | SM内部(SRAM) | 快(几十周期) | 小(几十KB~几百KB) | 同一个Block内的线程 |
| Register | SM内部(寄存器文件) | 极快(1周期) | 极小(每个线程几十个) | 单个线程 |
Global Memory 就是咱们常说的显存。所有线程都能读写,但延迟高得吓人。我建议你尽量合并访问(coalesced access),让相邻线程访问相邻地址,这样显存控制器才能一次搬一大块数据过来。
Shared Memory 是个好东西。同一个Block里的线程可以共享数据,延迟比Global低一个数量级。我在做矩阵乘法优化时,就是靠Shared Memory做分块(tiling),把数据从Global搬到Shared里,然后反复用,性能提升非常明显。
注意: Shared Memory虽然快,但容量有限。而且要注意 Bank Conflict。如果多个线程同时访问同一个Bank的不同地址,就会串行化。我曾经调试一个程序,发现性能瓶颈就在Bank Conflict上,调整了数据布局后,速度提升了30%。
Register 是最快的,但每个线程能用的寄存器数量有限。如果你在kernel里声明太多局部变量,编译器会 spill(溢出)到Local Memory(其实还是Global Memory),那性能就崩了。我个人习惯是:能用寄存器就别用Shared,能用Shared就别用Global。
2.3 PCIe与NVLink互联:GPU怎么跟外界说话?
单卡再强,数据进不去也是白搭。GPU跟CPU通信,或者GPU之间通信,靠的就是这两条路:PCIe 和 NVLink。
PCIe 是通用总线,所有显卡都支持。但它的带宽有限,延迟也高。比如PCIe 4.0 x16,理论带宽也就32GB/s(双向64GB/s)。你想想看,一个A100的显存带宽是2TB/s,PCIe这点带宽根本不够塞牙缝的。
NVLink 是NVIDIA自家的高速互联技术。它直接连接GPU到GPU,或者GPU到CPU(比如IBM Power9)。NVLink的带宽比PCIe高得多,延迟也更低。比如NVLink 3.0,每个链路带宽是50GB/s(单向),一个GPU可以有多个链路。
核心区别: PCIe走的是树形拓扑,所有设备共享带宽。NVLink走的是点对点直连,带宽独享。说白了,PCIe像一条主干道,车多了就堵;NVLink像多条高速公路,各走各的。
我记得在搭建一个8卡DGX-1集群时,发现GPU之间通过NVLink通信,延迟比PCIe低了将近一半。尤其是在做AllReduce操作时,NVLink的拓扑优势非常明显。如果你用PCIe做多卡通信,那瓶颈基本就在总线上。
避坑指南: 我曾经以为只要用了NVLink,通信就万事大吉了。结果发现,如果数据没有正确放置在 统一内存(Unified Memory) 或者显存里,NVLink也救不了你。记住:NVLink只加速显存之间的直接访问,不加速CPU到GPU的拷贝。
下面这张图,是我自己画的GPU内部结构示意,帮你把这几层关系理清楚:
嗯,这张图里,CPU通过PCIe连到GPU,GPU内部有多个SM,每个SM里有CUDA Core、Shared Memory和Register。SM之间通过NVLink直连(在多卡场景下)。说白了,你要优化的目标就是:让数据尽量待在Register和Shared里,少去Global,更少走PCIe。
最后说一句,NVLink虽然快,但它不是万能的。它只解决GPU之间的通信瓶颈,不解决计算瓶颈。你想想看,如果算法本身是计算密集型的,那通信再快也没用。所以,先优化计算,再优化通信,这是我个人的经验之谈。