第1章:GPU架构基础——从Fermi到Hopper的演进之路
各位同学,欢迎来到《从零搭建GPU性能分析流水线》的第一章。
说实话,我刚开始接触GPU编程那会儿,面对各种架构名词也是一头雾水。什么SM、Warp、共享内存,听着就头大。但后来我发现,搞懂这些底层结构,恰恰是写出高性能代码的关键。
这一章,我们就从NVIDIA GPU的架构演进讲起,然后深入SM内部,看看Warp是怎么调度执行的,最后把内存层次结构理清楚。嗯,内容不少,但都是硬核干货。
1.1 NVIDIA GPU架构演进:从Fermi到Hopper
先聊聊架构演进。我习惯把NVIDIA的架构分成几个关键节点,每个节点都解决了一些核心痛点。
| 架构代号 | 发布年份 | 核心改进 | 我的评价 |
|---|---|---|---|
| Fermi (GF100) | 2010 | 首个完整SM架构,支持C++、ECC | 奠定了现代GPU的基础 |
| Kepler (GK104) | 2012 | SMX单元,动态并行 | 能效比大幅提升 |
| Maxwell (GM204) | 2014 | 改进调度器,降低功耗 | 我当年做项目最爱的架构 |
| Pascal (GP100) | 2016 | NVLink,统一内存 | 大模型训练的起点 |
| Volta (GV100) | 2017 | Tensor Core,独立线程调度 | AI计算的转折点 |
| Turing (TU102) | 2018 | RT Core,混合精度 | 实时光线追踪的突破 |
| Ampere (GA100) | 2020 | 第三代Tensor Core,稀疏性支持 | 目前最通用的架构 |
| Hopper (GH100) | 2022 | Transformer Engine,DPX指令 | 专为大模型而生 |
你想想看,从Fermi到Hopper,差不多12年时间。每一代都在解决上一代的瓶颈。我个人觉得,理解这些演进背后的逻辑,比死记硬背参数重要得多。
核心观点:架构演进的核心驱动力是「计算密度」和「内存带宽」的平衡。每次新架构都在试图打破这个瓶颈。
1.2 SM(流式多处理器)架构详解
SM是GPU的心脏。说白了,一个GPU就是由多个SM组成的。每个SM内部包含:
- CUDA Core:执行整数和浮点运算
- Tensor Core:执行矩阵乘加运算(Volta开始引入)
- Warp Scheduler:负责Warp的调度
- Register File:寄存器文件,容量巨大
- Shared Memory:共享内存,可编程缓存
- L1 Cache:一级缓存,与共享内存共享容量
我记得在Ampere架构上,每个SM有128个CUDA Core和4个Tensor Core。但到了Hopper,SM内部结构又做了调整,增加了DPX指令单元,专门加速动态规划算法。
实战技巧:写代码时,我习惯先查一下目标GPU的SM数量。比如A100有108个SM,每个SM有64个FP32 CUDA Core。这样就能估算出理论峰值性能。
1.3 Warp调度与执行机制
Warp是GPU执行的基本单位。一个Warp包含32个线程,它们以SIMT(单指令多线程)方式执行。
为什么会是32?嗯,这是NVIDIA经过大量测试后选定的最优值。太小了调度开销大,太大了分支发散严重。
Warp调度有几个关键点:
- 零开销上下文切换:SM可以快速切换Warp,隐藏内存延迟
- Warp Divergence:如果Warp内线程走不同分支,性能会下降
- Occupancy:SM上活跃Warp数量与最大Warp数量的比值
我曾经在一个项目中,因为没注意Warp Divergence,导致性能只有预期的30%。后来把分支条件改成按Warp对齐,性能直接翻了3倍。这个坑,你们一定要记住。
避坑指南:我曾经在写归约操作时,让Warp内32个线程都去访问全局内存的不同地址。结果因为Warp内线程执行顺序不确定,导致数据竞争。后来改用Warp Shuffle指令才解决。
1.4 内存层次结构
GPU的内存层次,说白了就是「越靠近计算单元,越快但越小」。我们从上往下看:
寄存器(Register)
最快,每个线程私有。每个SM有数万个寄存器。但要注意,寄存器溢出会打到Local Memory(实际在全局内存中),性能会暴跌。
共享内存(Shared Memory)
可编程缓存,同一个Block内的线程可以共享。延迟极低(约20-30个时钟周期)。容量有限,通常每个SM 48KB-164KB不等。
我建议你把频繁访问的数据放到共享内存里。比如矩阵分块计算,用共享内存做Tile,能减少全局内存访问次数。
L1/L2缓存
L1缓存和共享内存共享硬件资源。L2缓存是所有SM共享的。在Ampere上,L2缓存达到了40MB,对很多场景已经够用了。
全局内存(Global Memory)
最大,但最慢。延迟400-800个时钟周期。访问时要尽量合并访问(Coalesced Access),让Warp内线程访问连续地址。
| 内存类型 | 延迟 | 容量 | 作用域 |
|---|---|---|---|
| 寄存器 | 1个周期 | 每个SM 64K-256K | 线程私有 |
| 共享内存 | 20-30个周期 | 每个SM 48-164KB | Block内共享 |
| L1缓存 | 30-40个周期 | 与共享内存共享 | SM私有 |
| L2缓存 | 200-300个周期 | 40MB (A100) | 全局共享 |
| 全局内存 | 400-800个周期 | 40-80GB | 全局 |
性能口诀:能放寄存器不放共享内存,能放共享内存不放全局内存。这个顺序搞反了,性能就悬了。
1.5 本章小结
这一章我们走马观花地看了GPU架构的演进,深入了SM内部,理解了Warp调度机制,也理清了内存层次结构。
你可能会觉得内容有点多。没关系,后面每一章都会用到这些知识。我建议你把这张内存层次表格打印出来贴在工位上,写代码时对照着看。
下一章,我们会搭建第一个性能分析工具——NVIDIA Nsight Systems。到时候你会看到,这些底层知识如何指导我们定位性能瓶颈。
课后思考:为什么NVIDIA不把共享内存做得更大?如果共享内存有1MB,会有什么问题?想明白这个问题,你就理解了GPU架构设计的权衡之道。
好了,第一章就到这里。记住,理解架构是写出高性能代码的第一步。我们下章见。