第三章:硬件架构基础——GPU、NPU、TPU的架构对比
做算子优化,说白了就是跟硬件打交道。你连对手长什么样都不知道,怎么打得赢?
我见过不少工程师,上来就调CUDA kernel,结果性能死活上不去。为什么?因为没搞懂硬件到底怎么干活。今天咱们就把GPU、NPU、TPU这三兄弟扒开看看,它们的计算单元、存储层次、带宽瓶颈到底差在哪。
3.1 计算单元:核心的差异
先说说最核心的东西——计算单元。这三家的设计哲学完全不同。
GPU:大量小核,并行之王
GPU的设计思路是「堆核」。一个现代GPU里有几千个CUDA core,每个core都很轻量。我刚开始做AI加速时,总觉得GPU的核越多越好,后来发现不是这么回事。
GPU的SM(Streaming Multiprocessor)里包含多个warp scheduler,每个warp调度32个线程。这32个线程执行同样的指令,但处理不同的数据——这就是SIMT(单指令多线程)。
关键点:GPU的强项是数据并行。如果你的算子能拆成大量独立的小任务,GPU就是最佳选择。
举个例子,矩阵乘法里每个输出元素的计算是独立的,GPU可以同时算几千个。但如果是递归或者强依赖的计算,GPU就抓瞎了。
NPU:脉动阵列,数据流驱动
NPU(比如华为的昇腾)用的是脉动阵列(Systolic Array)。这玩意儿跟GPU完全不同。数据像流水一样在阵列里「流动」,每个计算单元只做一件事:乘加。
我记得第一次看脉动阵列的架构图时,觉得这设计太巧妙了。数据从左边进来,权重从上面进来,计算结果在底部输出。整个过程没有复杂的控制逻辑,就是数据流驱动。
我的经验:在NPU上做算子优化,关键是要让数据流起来。如果数据在阵列里「堵车」了,性能直接腰斩。我曾经优化一个卷积算子,就是把数据排布方式从NHWC改成NCHW,吞吐量提升了40%。
TPU:矩阵乘法专用机
Google的TPU更极端——它几乎只做矩阵乘法。TPU v1的核心是一个65,536个MAC(乘加单元)的矩阵乘法单元。这玩意儿一次能算一个256x256的矩阵乘。
说白了,TPU就是为Transformer这类模型量身定做的。你想想看,Transformer里全是矩阵乘法,TPU正好对口。
| 特性 | GPU | NPU | TPU |
|---|---|---|---|
| 计算单元类型 | CUDA Core / Tensor Core | 脉动阵列(Systolic Array) | 矩阵乘法单元(MXU) |
| 并行粒度 | 线程级(SIMT) | 数据流级 | 矩阵级 |
| 典型规模 | 数千个core | 数百到数千个MAC | 数万个MAC |
| 控制复杂度 | 高(warp调度) | 低(数据流驱动) | 极低(固定流水线) |
3.2 存储层次:谁离计算最近?
计算单元再快,数据喂不进去也是白搭。存储层次的设计直接决定了你能跑多快。
GPU:多级缓存,显存带宽是命
GPU的存储层次很复杂:寄存器 -> L1/shared memory -> L2 -> HBM显存。每一级的速度差一个数量级。
我踩过最大的坑就是shared memory。有一次写一个矩阵乘法的kernel,直接用global memory读写,带宽利用率只有20%。后来改成tiling + shared memory,直接干到80%。
注意:GPU的shared memory只有几十KB,但速度比global memory快两个数量级。算子优化的核心就是怎么把数据「搬」到shared memory里复用。
NPU:片上buffer,数据流编排
NPU的存储设计跟GPU完全不同。它没有传统意义上的cache,而是用大块的片上buffer(比如L0 buffer、L1 buffer)。数据在buffer之间流动,由编译器决定什么时候搬数据。
嗯,这里要注意:NPU的编译器非常关键。你写算子时,编译器会帮你做数据排布和搬移的优化。但如果你不了解硬件的buffer大小,写出来的代码可能根本跑不满。
TPU:统一buffer,简化设计
TPU的存储更简单:一个统一buffer(Unified Buffer)作为中间存储,直接跟矩阵乘法单元交互。数据从HBM读到UB,算完再写回HBM。
这种设计的好处是控制简单,坏处是灵活性差。你没法像GPU那样精细控制数据复用。
| 层次 | GPU | NPU | TPU |
|---|---|---|---|
| 最近 | 寄存器 / Shared Memory | L0 Buffer | 统一Buffer |
| 中间 | L2 Cache | L1 Buffer | (无) |
| 最远 | HBM(高带宽显存) | DDR / HBM | HBM |
| 带宽瓶颈 | 显存带宽 | 片外带宽 | 片外带宽 |
3.3 带宽瓶颈:算力再高也怕带宽不够
这是最现实的问题。你算力再高,数据搬不进来就是白搭。
GPU:显存带宽是硬伤
GPU的算力增长远快于带宽增长。你看NVIDIA的H100,算力快到2000 TFLOPS,但显存带宽只有3.35 TB/s。这意味着什么?
算一个简单的算术:如果每个数据需要做100次运算,那带宽还勉强够。但如果只做1次运算(比如element-wise操作),那算力利用率可能不到1%。
核心结论:GPU上做算子优化,一定要提高计算密度(compute intensity)。说白了就是让每个数据被多算几次,别让计算单元闲着。
NPU:片外带宽与片上buffer的平衡
NPU的带宽瓶颈更复杂。片外带宽(DDR/HBM)是有限的,但片上buffer可以缓解这个问题。你想想看,如果能把权重常驻在片上buffer里,那每次计算只需要搬输入数据就行了。
我曾经优化过一个卷积层,就是把权重预加载到L1 buffer里,然后反复使用。这样片外带宽的需求直接降了一个数量级。
TPU:矩阵乘法单元的「饥饿」问题
TPU的矩阵乘法单元胃口太大了。一次矩阵乘法需要搬大量的数据进来。如果数据搬得慢,计算单元就会「饿死」。
Google的解决方案是用systolic array的流水线特性——数据一边搬一边算,尽量减少等待。但说实话,这需要编译器做非常精细的调度。
3.4 架构对比总结
说了这么多,咱们用一张图来总结一下三者的核心差异。
这张图把三者的存储层次和计算单元画出来了。你可以看到,GPU的层次最多,NPU次之,TPU最简单。但简单不代表不好——TPU在矩阵乘法上的效率是最高的。
3.5 实战建议:如何选择?
说了这么多理论,最后给点实际的建议。
- 如果你的模型以卷积和矩阵乘法为主:NPU或TPU可能更合适。它们的脉动阵列和矩阵乘法单元就是为这类算子设计的。
- 如果你的模型有大量自定义算子:GPU更灵活。你可以用CUDA写任意复杂的kernel。
- 如果你追求极致性能:一定要理解硬件的存储层次。我见过太多人把时间花在优化计算上,结果瓶颈在带宽。
一个小技巧:拿到一个新硬件,先跑一个简单的带宽测试(比如memcpy),看看实际带宽是多少。然后再跑一个计算密集型算子(比如矩阵乘法),看看算力利用率。这两个数字能告诉你很多信息。
好了,这一章的内容就到这。硬件架构是算子优化的地基,地基打不牢,上面盖的房子再漂亮也经不起推敲。下一章咱们会深入具体的算子优化技巧,到时候这些硬件知识都会用上。
想要更多资料,联系可可