1. GPU架构基础:从图形卡到通用计算引擎
各位同学,今天咱们聊聊GPU架构的根。说实话,我入行那会儿,GPU还只是个「画图卡」。谁能想到,十几年后它成了AI时代的算力核心?
这一章,我会从发展简史讲起,再对比CPU和GPU的本质区别,最后深入现代GPU的架构细节。嗯,内存层次结构这块,我踩过不少坑,会重点说。
1.1 GPU发展简史:三个关键转折点
GPU的历史,我习惯分成三个阶段来看:
- 1999-2006:固定功能管线时代
NVIDIA发布GeForce 256,第一次提出「GPU」这个概念。那时候的渲染管线是固定的——顶点着色、像素着色,硬件写死了,程序员改不了。我记得当时做游戏开发,想实现个水波效果,得用一堆trick,累得够呛。 - 2007-2014:可编程着色器时代
DirectX 10和CUDA的推出,彻底改变了游戏规则。着色器变成了统一架构,顶点、像素、几何着色器共用一套计算单元。我2010年第一次在CUDA上写矩阵乘法,那种「CPU跑不动的东西GPU秒出」的震撼,到现在还记得。 - 2015至今:通用计算与AI时代
Tensor Core、RT Core这些专用硬件加入,GPU从图形处理器变成了「通用并行计算引擎」。说白了,现在的GPU更像一个「超级并行数学协处理器」。
关键洞察:GPU的演进,本质上是「从专用走向通用,再从通用走向专用+通用混合」的过程。你想想看,Tensor Core不就是为AI量身定做的专用电路吗?
1.2 GPU与CPU的区别:一个比喻让你彻底明白
很多人问我:「CPU和GPU到底差在哪?」我一般这么打比方:
- CPU:像一个全能教授,什么题都会做,但一次只能做一两道。
- GPU:像一千个小学生,每个人只会加减乘除,但可以同时算一千道题。
从硬件设计上看,区别更明显:
| 维度 | CPU | GPU |
|---|---|---|
| 控制逻辑 | 复杂(分支预测、乱序执行) | 简单(顺序执行为主) |
| 缓存 | 大(L1/L2/L3,几十MB) | 小(L1/L2,几MB) |
| ALU数量 | 几个到几十个 | 几千个 |
| 设计目标 | 低延迟 | 高吞吐 |
为什么会这样?因为GPU假设你要处理的是「大量相同操作的数据」——比如对100万个像素做同样的颜色变换。这种情况下,每个像素不需要复杂的控制逻辑,只需要一个简单的ALU就够了。
个人经验:我曾在项目中遇到一个性能问题——CPU上跑得好好的算法,搬到GPU上反而变慢了。后来发现是分支太多,导致GPU的SIMT(单指令多线程)执行效率极低。记住:GPU怕分支,CPU怕并行。
1.3 现代GPU架构概览:SM/CU/ALU
现代GPU的架构,我习惯从三个层次去理解:
1.3.1 芯片级:GPC(图形处理集群)
一个GPU芯片由多个GPC组成。每个GPC包含若干SM(NVIDIA叫法)或CU(AMD叫法)。GPC之间通过片上互联网络通信。
1.3.2 核心级:SM/CU
这是GPU最重要的计算单元。以NVIDIA的SM为例:
- 每个SM包含多个CUDA Core(其实就是ALU)
- 还有共享内存、寄存器文件、调度器
- SM内部采用SIMT模型——32个线程组成一个warp,一起执行同一条指令
AMD的CU(Compute Unit)类似,但内部是SIMD(单指令多数据)模型,一个wavefront是64个线程。嗯,这里要注意,虽然概念相似,但调度方式有差异,写驱动时得区分对待。
1.3.3 执行级:ALU
ALU就是干活的。现代GPU的ALU分几种:
- 整数ALU:处理整数运算
- 浮点ALU:处理FP32/FP64运算
- 特殊函数单元(SFU):处理sin、cos、log等复杂函数
- Tensor Core:专门做矩阵乘加,AI训练推理的利器
避坑指南:我曾经在写驱动时,没注意Tensor Core的对齐要求——输入矩阵必须是16x16的倍数。结果跑出来的结果全是错的,查了两天才发现。所以,用Tensor Core前,一定检查数据对齐。
1.4 GPU内存层次结构:速度与容量的博弈
GPU的内存层次,说白了就是「越快的越贵,越贵的越小」。我画个简化的层次图:
寄存器(最快,最小)
↓
L1缓存(每个SM私有)
↓
共享内存(可编程,同一SM内线程共享)
↓
L2缓存(芯片全局共享)
↓
显存(HBM/GDDR,最大,最慢)
↓
系统内存(通过PCIe访问,最慢)
每个层次的特点:
| 层次 | 典型大小 | 延迟 | 带宽 | 作用 |
|---|---|---|---|---|
| 寄存器 | 256KB/SM | ~1 cycle | 极高 | 线程私有变量 |
| 共享内存 | 48-96KB/SM | ~5 cycles | 极高 | 线程间通信 |
| L1缓存 | 128KB/SM | ~20 cycles | 高 | 自动缓存数据 |
| L2缓存 | 4-8MB | ~200 cycles | 中 | 全局缓存 |
| 显存 | 8-80GB | ~400 cycles | 低(相对) | 主存储 |
这里有个关键点:共享内存是可编程的,而L1缓存是硬件自动管理的。我个人的习惯是:如果数据访问模式是规则的(比如矩阵分块),就用共享内存;如果是随机的,就靠L1缓存。
实战建议:写GPU驱动时,内存分配策略直接影响性能。我一般遵循三条原则:
- 能放寄存器的绝不放共享内存
- 能放共享内存的绝不放显存
- 显存访问尽量合并(coalesced)——连续线程访问连续地址
1.5 小结:理解架构才能写好驱动
这一章我们走马观花看了GPU的架构基础。你可能会问:「知道这些有什么用?」
我告诉你,写GPU驱动,本质上就是「把上层应用的请求,翻译成硬件能理解的指令」。如果你不懂SM怎么调度warp,不懂共享内存和L1缓存的区别,那写出来的驱动要么性能差,要么直接崩溃。
下一章,我们会深入驱动开发的第一个环节——设备初始化。到时候你会看到,这些架构知识是怎么落地到代码里的。
课后思考:为什么GPU的L1缓存通常比CPU小得多?这和它们的工作负载有什么关系?想明白这个问题,你就理解了GPU设计的核心哲学。