GPU硬件基础:核心组件与协同工作
各位同学,今天我们来聊聊GPU的硬件基础。说实话,很多做驱动开发的朋友,一开始都只盯着软件层面,觉得硬件是硬件工程师的事。但我个人的经验是——不懂硬件的驱动工程师,写出来的代码往往效率堪忧。
我刚开始接触GPU驱动时,就吃过这个亏。那时候我写了个看似完美的内存分配逻辑,结果一跑起来,显存带宽利用率不到30%。后来一查,原来是没搞清楚显存控制器的bank冲突。嗯,从那以后,我再也不敢跳过硬件基础了。
一、GPU核心组件
先说说GPU里最关键的几个部件。你想想看,一个GPU芯片上,真正干活的核心单元是什么?
1. SM/CU:真正的计算单元
NVIDIA叫它SM(Streaming Multiprocessor),AMD叫它CU(Compute Unit)。名字不同,本质一样——这是GPU里执行计算的最小独立单元。
一个SM内部包含什么?
- 多个CUDA核心(或流处理器)——真正干算术活的
- 共享内存——同一SM内线程可以快速交换数据
- 寄存器文件——每个线程的私有存储
- 调度器与分发单元——决定哪个warp/wavefront该执行了
关键点:SM的数量决定了GPU的并行度。但别以为SM越多就一定越快——还要看你的程序能不能充分利用它们。
我在项目中遇到过一种情况:某个深度学习推理任务,在A100上跑得还不如V100快。为什么?因为A100的SM更多,但每个SM的共享内存相对少了,而那个模型特别依赖共享内存。说白了,硬件配置和软件需求要匹配。
2. 显存:GPU的“内存”
显存(VRAM)就是GPU专用的内存。它和系统内存最大的区别是什么?
- 带宽极高——HBM2e显存带宽可达2TB/s以上
- 延迟相对较低——但比片上缓存还是慢得多
- 容量有限——目前消费级最多24GB,专业级可达80GB
显存类型也在不断演进:GDDR6 → GDDR6X → HBM2e → HBM3。每一代都在带宽和功耗上做平衡。
个人建议:写驱动时,一定要搞清楚显存的page size和alignment要求。我曾经因为没对齐,导致显存分配效率直接腰斩。
3. 显存控制器:带宽的守门人
显存控制器(Memory Controller)负责管理GPU核心和显存之间的数据流动。它有几个关键参数:
| 参数 | 说明 | 影响 |
|---|---|---|
| 通道数 | 控制器到显存的独立路径数 | 通道越多,并行访问能力越强 |
| 位宽 | 每个通道的数据宽度(如32-bit) | 位宽×频率=理论带宽 |
| Bank数 | 显存内部的存储分区 | bank冲突会降低有效带宽 |
嗯,这里要注意:显存控制器不是简单的“转发器”。它内部有复杂的调度逻辑,会合并访问请求、处理bank冲突、做预取优化。写驱动时,如果你能理解控制器的行为模式,就能写出更高效的访存代码。
二、GPU指令流水线
GPU的指令执行和CPU有很大不同。CPU追求单线程性能,流水线很深(十几级甚至二十几级)。GPU呢?它追求吞吐量,流水线相对较浅,但并行度极高。
一个典型的GPU指令流水线包含这几个阶段:
- 取指(Fetch)——从指令缓存中读取指令
- 译码(Decode)——解析指令类型和操作数
- 发射(Issue)——将指令发送到执行单元
- 执行(Execute)——在ALU或特殊功能单元中计算
- 写回(Writeback)——将结果写回寄存器或内存
但GPU有个独特的设计——warp/wavefront调度。一个warp包含32个(NVIDIA)或64个(AMD)线程,它们执行相同的指令,但处理不同的数据。这就是SIMT(单指令多线程)的精髓。
避坑指南:我曾经在驱动里实现了一个warp调度器,一开始没处理好分支发散(divergence)的情况。结果一个warp里32个线程走了不同的分支,性能直接掉了80%。记住:GPU最怕的就是分支发散。
为什么会这样?因为warp内的线程是锁步执行的。如果线程A走if分支,线程B走else分支,那这两个分支就得串行执行。你想想看,32个线程本来可以并行,现在变成串行,性能能不降吗?
三、GPU与CPU的协同工作模式
CPU和GPU怎么配合?说白了,CPU是“指挥官”,GPU是“执行者”。
典型的协同流程是这样的:
- CPU准备数据——把数据从系统内存拷贝到显存
- CPU下发命令——通过命令缓冲区告诉GPU要做什么
- GPU执行计算——GPU从命令缓冲区读取指令,开始干活
- GPU通知完成——通过中断或轮询机制告诉CPU“我干完了”
- CPU取回结果——把计算结果从显存拷贝回系统内存
这里面有个关键问题:同步与异步。
CPU下发命令后,是等着GPU干完,还是继续干别的?
- 同步模式:CPU阻塞等待,简单但效率低
- 异步模式:CPU继续执行其他任务,GPU在后台计算
现代GPU驱动几乎都采用异步模式。我建议你在设计驱动时,也优先考虑异步模型。虽然实现起来复杂一些,但性能提升是实打实的。
个人经验:实现异步模型时,一定要处理好命令缓冲区的环形队列(ring buffer)。我曾经因为没处理好生产者-消费者关系,导致命令覆盖,GPU执行了错误的指令——嗯,那次debug花了我整整两天。
还有一个重要的概念:上下文切换。GPU可以在多个上下文(进程)之间快速切换,但切换是有代价的。每个上下文都有自己的页表、寄存器状态、命令缓冲区。切换时,需要保存和恢复这些状态。
我见过一些驱动实现,为了省事,每次提交任务都做一次完整的上下文切换。结果呢?GPU大部分时间都在做切换,真正干活的时间反而少了。所以,批量提交是个好习惯——把多个小任务合并成一个大任务,减少切换次数。
四、知识体系总览
下面这张图,是我个人总结的GPU硬件与驱动交互的核心逻辑。你可以把它当作本章的思维导图:
这张图展示了CPU、命令缓冲区、GPU、显存之间的数据流和控制流。你可以看到,整个流程是闭环的——CPU下发命令,GPU执行计算,结果最终回到CPU。而显存控制器,就是连接GPU核心和显存之间的桥梁。
重要提醒:别小看这张图里的任何一个环节。我在实际项目中见过太多问题——命令缓冲区溢出导致GPU hang、显存控制器bank冲突导致带宽骤降、CPU-GPU同步不当导致数据竞争。每一个细节,都可能成为性能瓶颈或bug的根源。
好了,这一章的内容就到这里。记住:理解硬件,才能写出好的驱动。下一章我们会深入GPU的内存层次结构,看看缓存、共享内存、全局内存之间到底怎么配合。
公众号:蓝海资料掘金营,微信deep3321