1、GPU架构基础:从渲染管线到计算单元
做GPU驱动开发这些年,我最大的感触是:不懂硬件,驱动就是空中楼阁。你想想看,连GPU怎么干活都不知道,怎么给它写代码?今天我们就从最底层开始,把GPU的骨架摸清楚。
1.1 渲染管线:GPU的流水线工厂
GPU最初是为图形渲染设计的。它的核心工作方式,就是一条流水线。我习惯把它想象成一个汽车工厂:
- 第一站:组装底盘
- 第二站:安装发动机
- 第三站:喷漆
- 最后:质检出厂
渲染管线也是类似的。它把一张3D画面变成2D像素,分几个固定步骤走。每个步骤都有专门的硬件单元处理。
1.1.1 顶点着色器(Vertex Shader)
这是管线的第一站。输入是3D模型的顶点数据——位置、颜色、纹理坐标。输出是变换后的顶点。
我在项目中遇到过一个问题:顶点数量太多,导致渲染卡顿。后来发现是顶点着色器单元不够用。嗯,这里要注意:每个顶点都要过一遍着色器,顶点数直接决定性能。
关键点:顶点着色器是每个顶点独立执行的。它没有「邻居」的概念。所以非常适合并行计算。
1.1.2 光栅化(Rasterization)
这一步很有意思。它把连续的几何图形,变成离散的像素点。说白了,就是决定「哪些像素被三角形覆盖」。
光栅化是固定功能的硬件单元。驱动开发者不需要写代码控制它,但必须理解它的行为。我曾经调试过一个花屏问题,查了两天才发现是光栅化单元的配置寄存器写错了——一个bit位搞反了。
1.1.3 片段着色器(Fragment Shader)
也叫像素着色器。它处理每个像素的颜色计算。光照、纹理、阴影,都在这里完成。
这是最耗计算资源的阶段。为什么?因为每个像素都要算一遍。1080p分辨率下,一帧就有200多万个像素。60帧每秒,就是1.2亿次计算。
个人经验:调试片段着色器时,我习惯先降低分辨率。这样像素数量少,问题容易复现。等逻辑调通了,再改回正常分辨率。
1.2 计算单元:GPU的真正心脏
现代GPU早就不是单纯的图形处理器了。它还能做通用计算(GPGPU)。这背后的功臣,就是计算单元。
1.2.1 SM / CU 架构
NVIDIA叫它SM(Streaming Multiprocessor),AMD叫它CU(Compute Unit)。名字不同,本质一样:一组计算核心的集合。
一个SM里面包含:
- 几十到上百个ALU(算术逻辑单元)
- 共享内存(Shared Memory)
- 寄存器文件(Register File)
- 调度器(Warp Scheduler)
我刚开始做驱动时,总搞不清SM和线程的关系。后来想通了:SM就像一个小型CPU,但它有几十个核心。每个核心可以跑一个线程。几十个线程同时跑,这就是GPU的并行能力。
| 组件 | 作用 | 驱动关注点 |
|---|---|---|
| ALU | 执行算术运算 | 指令发射、流水线停顿 |
| 共享内存 | 线程间数据交换 | bank冲突、容量限制 |
| 寄存器 | 线程私有数据 | 寄存器压力、spill |
| 调度器 | 分配线程到核心 | warp调度策略 |
1.2.2 线程与Warp
GPU执行的最小单位不是单个线程,而是Warp(NVIDIA叫法,AMD叫Wavefront)。一个Warp包含32个线程。它们同时执行同一条指令,但处理不同的数据。
这就是SIMT(单指令多线程)模型。你想想看,32个线程一起干活,效率当然高。但有个坑:如果线程分支了(if-else),那部分线程就得等待。这叫「分支发散」。
避坑指南:我曾经写过一个kernel,里面有个if语句。一半线程走true,一半走false。结果性能直接腰斩。后来改成无分支的算法,速度翻倍。记住:GPU讨厌分支。
1.3 内存层次结构
GPU的内存系统,比CPU复杂得多。我画个简图帮你理解:
全局内存(Global Memory)
↓
L2缓存(L2 Cache)
↓
L1缓存 / 共享内存(L1/Shared Memory)
↓
寄存器(Register)
越往下,速度越快,容量越小。驱动开发的核心任务之一,就是帮应用程序用好这个层次结构。
1.3.1 全局内存
这是最大的内存池。几GB到几十GB。但延迟很高——几百个时钟周期。驱动要负责分配、释放、地址映射。
我建议你记住一个数字:全局内存的带宽很大(几百GB/s),但延迟也很高。所以算法要设计成「连续访问、充分利用带宽」。
1.3.2 共享内存
这是SM内部的SRAM。速度极快,延迟只有几个时钟周期。但容量很小——通常几十KB。
共享内存是手动管理的。驱动不帮你自动缓存。你得自己决定:哪些数据放共享内存,哪些放全局内存。
实用技巧:矩阵乘法这种需要数据复用的算法,一定要用共享内存。我见过一个优化案例:用共享内存后,性能提升了8倍。原因很简单——减少了全局内存访问次数。
1.4 驱动开发者的视角
说了这么多硬件知识,你可能会问:跟我写驱动有什么关系?关系大了。
驱动要做的事情,说白了就是:
- 初始化硬件(配置寄存器、启动时钟)
- 管理内存(分配、映射、同步)
- 提交任务(把kernel代码加载到SM上执行)
- 处理中断(通知CPU任务完成)
每一步都离不开对硬件架构的理解。比如:
- 你知道SM有128个核心,那一次最多能跑多少个线程?
- 你知道共享内存只有48KB,那应用程序申请64KB怎么办?
- 你知道Warp是32个线程一组,那调度器怎么分配?
这些问题,都是驱动开发要解决的。没有硬件知识,寸步难行。
1.5 本章小结
GPU架构的核心就三块:渲染管线、计算单元、内存层次。渲染管线是图形任务的流水线,计算单元是并行计算的发动机,内存层次是数据流动的通道。
我个人习惯是:先理解硬件,再写驱动。硬件不懂,代码写得再漂亮也是白搭。下一章,我们会深入驱动开发的第一个实战环节——设备初始化。到时候你会看到,这些硬件知识是怎么用到代码里的。
嗯,今天就到这里。记住我说的:GPU讨厌分支,喜欢连续访问。这两个原则,能帮你避开80%的性能坑。