1、GPU架构概览:从图形渲染到通用计算,GPU的进化史与核心设计哲学
大家好,我是你们这堂课的讲师。咱们直接开门见山——聊GPU架构,得先搞清楚它从哪来,要到哪去。我个人习惯把GPU的进化史看作一部「被逼出来的逆袭史」。它最初就是个画图工具,结果硬生生被市场、被算法、被我们这些工程师,逼成了今天这个并行计算的巨无霸。
1.1 从「画图卡」到「计算卡」:一段被逼出来的进化史
早期的GPU,说白了就是显卡。它的任务很单纯:把CPU算好的顶点坐标,变成屏幕上一个个像素点。那时候没人拿它算科学计算,连想都不敢想。
第一阶段:固定功能管线时代(1990s - 2000s)
这个阶段的GPU,内部是「死」的。顶点处理、光栅化、像素着色,每个阶段都是固定的硬件电路。你没法编程,只能通过API(比如OpenGL、DirectX)去配置参数。我记得那时候做游戏优化,调个光照参数都得小心翼翼,因为硬件根本不给你灵活度。
- 特点: 专用性强,功耗低,但功能单一。
- 代表: NVIDIA RIVA TNT、ATI Rage系列。
- 我的感受: 那时候写Shader?不存在的。你只能祈祷硬件厂商把光照模型做得足够好。
第二阶段:可编程着色器时代(2000s - 2010s)
转折点来了。游戏开发者不满足于固定的光照和特效,他们想要更炫酷的画面。于是,可编程着色器(Shader)诞生了。顶点着色器和像素着色器开始变成可编程的小处理器。嗯,这里要注意,虽然能编程了,但顶点和像素还是分开的硬件单元,不能混用。
核心设计哲学: 用大量的、简单的、并行的计算单元,去掩盖内存访问的延迟。这就是GPU和CPU最根本的区别。CPU是「单核超强,缓存巨大」,GPU是「多核堆叠,延迟容忍」。
第三阶段:统一着色器架构与通用计算(2010s - 至今)
这才是真正的大爆发。NVIDIA在G80架构(GeForce 8800 GTX)上做了一个疯狂的决定:把顶点着色器和像素着色器合并成统一的流处理器(SP)。为什么?因为游戏里有时候顶点多、像素少,有时候反过来。分开设计会导致资源浪费。统一之后,所有计算单元可以灵活分配,效率大幅提升。
也就是从这时候起,大家发现:咦?这些流处理器既然能算像素,那能不能算物理模拟?能不能算矩阵乘法?于是,CUDA和OpenCL应运而生。GPU正式从「图形加速器」变成了「通用计算加速器」。
避坑指南: 我曾经在项目里犯过一个低级错误——把CPU上的优化思路直接搬到GPU上。比如,在CPU上我们喜欢用大缓存来减少访存,但在GPU上,缓存小得可怜,你更应该关注的是「合并访问」和「占用率」。这个后面章节会细讲。
1.2 核心设计哲学:吞吐量优先,延迟靠边站
理解了历史,我们就能提炼出GPU的核心设计哲学。说白了就一句话:为了吞吐量,可以牺牲单次任务的延迟。
CPU的设计目标是:让一个任务跑得尽可能快。所以它有庞大的乱序执行逻辑、巨大的缓存、超深的流水线。但GPU的目标是:同时跑成千上万个任务,哪怕每个任务慢一点,只要总数多,总吞吐量就大。
为什么会这样?因为图形渲染本身就是天然的并行任务。屏幕上有1920x1080个像素,每个像素的计算相互独立。你想想看,如果CPU去一个个算,得算到猴年马月?GPU的做法是:派2000个「小工」同时去算,每个小工算一个像素。
| 对比维度 | CPU | GPU |
|---|---|---|
| 核心数量 | 4-16个(大核) | 数千个(小核) |
| 缓存策略 | 大缓存(减少延迟) | 小缓存(更多晶体管给计算) |
| 控制单元 | 复杂(分支预测、乱序执行) | 简单(顺序执行,容忍分支) |
| 设计目标 | 降低单线程延迟 | 提高整体吞吐量 |
| 典型应用 | 操作系统、数据库 | 图形渲染、AI训练、科学计算 |
这张表很关键。你每次做性能调优时,都应该回头看看这张表,问问自己:我现在的瓶颈是延迟还是吞吐量?如果是延迟,那可能CPU更合适;如果是吞吐量,那GPU才是你的菜。
1.3 现代GPU的「三驾马车」:SM、Memory Hierarchy、Warp
现代GPU架构虽然各家略有不同(NVIDIA的Ampere、Hopper,AMD的RDNA、CDNA),但核心组件逃不出这三个东西。
1. 流式多处理器(SM / CU)
这是GPU的基本计算单元。你可以把它想象成一个「小型的多核CPU」。每个SM内部包含几十到几百个CUDA核心(或者叫流处理器),还有共享内存、寄存器文件、调度器等。我习惯把SM看作一个「作战小组」,小组内成员(线程)可以高效通信(通过共享内存),但小组之间通信就很慢(得走全局内存)。
2. 存储层次(Memory Hierarchy)
GPU的存储层次比CPU更「极端」:
- 全局内存(Global Memory): 大(几GB到几十GB),但慢(几百个时钟周期)。所有线程都能访问。
- 共享内存(Shared Memory): 小(几十KB到几百KB),但快(几个时钟周期)。同一个SM内的线程共享。
- 寄存器(Registers): 最快,但每个线程数量有限(通常255个/线程)。
- 常量/纹理缓存: 只读,有专门的加速硬件。
警告: 很多新手一上来就把所有数据放全局内存,然后抱怨GPU慢。其实90%的性能问题都出在「访存模式」上。记住:合并访问是GPU调优的第一课。如果相邻线程访问的地址不连续,那性能会断崖式下跌。
3. 线程调度与Warp
GPU不直接调度单个线程,而是以Warp(或Wavefront)为单位。一个Warp通常是32个线程(NVIDIA)或64个线程(AMD)。这32个线程执行同一条指令,但处理不同的数据(SIMT模型)。
为什么这么做?因为这样可以减少调度器的压力。调度器只需要每4个时钟周期发射一条指令,就能让32个线程同时工作。如果每个线程单独调度,那调度器得忙死。
但这也带来了一个问题:分支发散。如果Warp里的32个线程走了不同的分支(if-else),那GPU只能先执行if分支,再执行else分支,性能直接减半。我在项目中遇到过这种情况:一个简单的if判断,因为数据分布不均匀,导致性能下降了40%。后来我们通过数据重排,把相同条件的数据打包在一起,才解决了这个问题。
1.4 从图形到通用:架构设计上的「妥协」
GPU从图形渲染转向通用计算,并不是一帆风顺的。图形渲染的特点是:计算密集、数据局部性好、分支少。但通用计算(比如AI、科学计算)的特点是:访存模式多样、分支多、数据依赖复杂。
为了适应通用计算,现代GPU做了很多妥协:
- 增加了缓存: 早期的GPU几乎没有缓存,因为图形渲染的访存模式很规律。但通用计算不行,所以现代GPU的L1/L2缓存越来越大。
- 改进了分支处理: 引入了「预测执行」和「收敛优化」技术,尽量减少分支发散的影响。
- 引入了Tensor Core: 专门为AI矩阵乘法设计的硬件单元,可以在一个时钟周期内完成4x4矩阵的乘加运算。这已经不是传统GPU的范畴了,而是「专用加速器」的思路。
我个人觉得,未来的GPU会越来越像一个「异构计算综合体」:里面有传统的CUDA Core(做通用计算),有Tensor Core(做AI),有RT Core(做光线追踪),甚至可能还有专用的稀疏计算单元。你想想看,这其实就是在走CPU的老路——从通用走向专用,再从专用走向融合。
1.5 小结:调优的起点
这一章我们聊了GPU的进化史和核心设计哲学。你不需要记住所有架构代号,但一定要记住以下几点:
- GPU是吞吐量优先的机器,别用CPU的思维去优化它。
- 访存是最大的瓶颈,合并访问是基本功。
- Warp是调度的基本单位,避免分支发散。
- 理解SM和存储层次,才能用好共享内存和寄存器。
下一章,我们会深入SM内部,看看一个Warp到底是怎么被调度和执行的。到时候我会拿一个实际的Kernel代码来拆解,保证让你看得明明白白。
好了,第一章就到这里。有问题随时在群里讨论,或者直接来找我。咱们下章见。