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 驱动开发者的视角

说了这么多硬件知识,你可能会问:跟我写驱动有什么关系?关系大了。

驱动要做的事情,说白了就是:

  1. 初始化硬件(配置寄存器、启动时钟)
  2. 管理内存(分配、映射、同步)
  3. 提交任务(把kernel代码加载到SM上执行)
  4. 处理中断(通知CPU任务完成)

每一步都离不开对硬件架构的理解。比如:

  • 你知道SM有128个核心,那一次最多能跑多少个线程?
  • 你知道共享内存只有48KB,那应用程序申请64KB怎么办?
  • 你知道Warp是32个线程一组,那调度器怎么分配?

这些问题,都是驱动开发要解决的。没有硬件知识,寸步难行。

1.5 本章小结

GPU架构的核心就三块:渲染管线、计算单元、内存层次。渲染管线是图形任务的流水线,计算单元是并行计算的发动机,内存层次是数据流动的通道。

我个人习惯是:先理解硬件,再写驱动。硬件不懂,代码写得再漂亮也是白搭。下一章,我们会深入驱动开发的第一个实战环节——设备初始化。到时候你会看到,这些硬件知识是怎么用到代码里的。

嗯,今天就到这里。记住我说的:GPU讨厌分支,喜欢连续访问。这两个原则,能帮你避开80%的性能坑。