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)。为什么?因为游戏里有时候顶点多、像素少,有时候反过来。分开设计会导致资源浪费。统一之后,所有计算单元可以灵活分配,效率大幅提升。

也就是从这时候起,大家发现:咦?这些流处理器既然能算像素,那能不能算物理模拟?能不能算矩阵乘法?于是,CUDAOpenCL应运而生。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的进化史和核心设计哲学。你不需要记住所有架构代号,但一定要记住以下几点:

  1. GPU是吞吐量优先的机器,别用CPU的思维去优化它。
  2. 访存是最大的瓶颈,合并访问是基本功。
  3. Warp是调度的基本单位,避免分支发散。
  4. 理解SM和存储层次,才能用好共享内存和寄存器。

下一章,我们会深入SM内部,看看一个Warp到底是怎么被调度和执行的。到时候我会拿一个实际的Kernel代码来拆解,保证让你看得明明白白。

好了,第一章就到这里。有问题随时在群里讨论,或者直接来找我。咱们下章见。