硬件架构基础:GPU与NPU的硬件流水线、计算单元与存储层次、带宽与延迟的瓶颈分析

做算子融合,说白了就是跟硬件谈恋爱。你得摸透它的脾气,知道它哪里快、哪里慢,才能把活儿干得漂亮。今天咱们就聊聊GPU和NPU这两位的“性格”。

GPU的硬件流水线:吞吐为王

GPU的设计哲学很简单——用大量的计算单元掩盖延迟。我刚开始接触GPU编程时,总觉得它像个“傻大个”,只会一股脑地堆算力。后来才明白,这正是它的高明之处。

GPU的流水线大致分这么几步:

  • 指令发射:从显存取指令,分发给各个计算单元。这里有个坑——指令带宽。我曾经在一个项目里,因为指令缓存没命中,导致流水线空转,性能直接腰斩。
  • 线程调度:GPU用Warp(NVIDIA)或Wavefront(AMD)作为调度单位。一个Warp里的32个线程,必须执行同一条指令。这就是所谓的SIMT(单指令多线程)。
  • 计算执行:交给CUDA Core或Tensor Core去算。Tensor Core是专门为矩阵乘法设计的,做AI推理时,它比普通Core快一个数量级。
  • 访存与写回:结果写回寄存器或显存。这一步往往是瓶颈。

关键点:GPU的流水线是吞吐优先的。它不在乎单个任务有多快,只在乎单位时间内能处理多少任务。所以,算子融合的目标就是让每个Warp都“吃饱”,别闲着。

NPU的硬件流水线:专用为王

NPU就不一样了。它是为AI推理量身定做的,流水线更“专一”。

  • 数据加载:从片外存储(比如DDR)把数据搬到片上SRAM。NPU通常有专门的DMA引擎干这事儿,不占用计算资源。
  • 计算阵列:NPU的核心是一个脉动阵列(Systolic Array)。数据在里面像流水一样“流动”,每个周期完成一次乘加运算。我见过最夸张的设计,一个周期能算几千次乘加。
  • 激活与池化:计算完的结果,直接喂给激活函数或池化单元。这些单元通常也是硬件化的,速度极快。
  • 数据写回:结果写回SRAM或直接传给下一层。

我的经验:NPU的流水线是数据流驱动的。数据一到,计算就开始。所以,算子融合的关键是减少数据在片外和片内之间的搬运次数。我曾经把一个Conv+BN+ReLU融合成一个算子,访存开销直接降了60%。

计算单元:GPU的通用 vs NPU的专用

GPU的计算单元是通用型的。CUDA Core能算整数、浮点、逻辑运算,啥都能干。Tensor Core虽然专一点,但也只针对矩阵乘法。

NPU的计算单元是专用型的。它只做乘加运算,而且数据位宽通常是8位甚至4位。你想想看,这就像用一把瑞士军刀和一把专用菜刀切菜——瑞士军刀功能多,但切菜效率肯定不如菜刀。

特性 GPU NPU
核心类型 CUDA Core / Tensor Core 脉动阵列 / 乘加单元
数据位宽 FP32/FP16/INT8 INT8/INT4/FP16
并行粒度 线程级(Warp) 数据级(脉动)
灵活性 高(支持各种算子) 低(只支持特定算子)
能效比 中等 高(通常高5-10倍)

避坑指南:我曾经在一个项目里,试图在NPU上跑一个非标准的激活函数。结果发现,NPU的硬件不支持,只能用CPU模拟,性能惨不忍睹。所以,算子融合前,一定要确认目标硬件支持哪些算子。

存储层次:带宽与延迟的博弈

存储层次是硬件架构里最“现实”的部分。说白了,就是越快的存储越贵、越小。

GPU的存储层次

  • 全局显存(Global Memory):容量大(几十GB),但延迟高(几百个周期)。带宽虽然高(比如HBM2e能到1.6TB/s),但架不住计算单元多,分摊下来每个线程能分到的带宽有限。
  • 共享内存(Shared Memory):每个Block私有,延迟低(几十个周期)。容量小(几十KB)。这是算子融合的“主战场”。
  • 寄存器(Register):每个线程私有,延迟最低(1个周期)。但数量有限(每个线程最多255个)。

NPU的存储层次

  • 片外存储(DDR/HBM):容量大,但带宽和延迟都不如GPU的HBM。NPU通常更依赖数据复用来缓解这个瓶颈。
  • 片上SRAM:容量小(几MB),但带宽极高。NPU的脉动阵列直接挂在SRAM上,数据搬运几乎零延迟。
  • 寄存器文件:每个计算单元私有,容量极小(几百字节)。

核心矛盾:计算速度 > 片上带宽 > 片外带宽。也就是说,计算单元永远在等数据。算子融合的目的,就是让数据尽量留在片上,减少片外访问。

带宽与延迟的瓶颈分析

咱们用一张图来总结一下:

GPU与NPU的瓶颈分析 GPU 架构 计算单元(CUDA Core) 存储层次(HBM/Shared) 数据流 瓶颈:Warp 分支发散 / 共享内存 Bank Conflict NPU 架构 脉动阵列(Systolic Array) 片上 SRAM 数据流 瓶颈:片外带宽不足 / 算子不匹配 共同瓶颈:访存带宽 < 计算带宽 解决方案:算子融合 → 减少片外访存 → 提升数据复用

这张图里,我特意把访存带宽标成了红色。为什么?因为这是所有硬件架构的“阿喀琉斯之踵”。

  • GPU的瓶颈:Warp分支发散。如果一个Warp里的线程走了不同的分支,那它们就得串行执行,性能直接打折扣。另外,共享内存的Bank Conflict也是个坑,我调试过最头疼的问题就是这个。
  • NPU的瓶颈:片外带宽。NPU的计算能力太强了,片上的SRAM根本喂不饱。所以,NPU特别依赖数据复用——同一个数据,尽量多算几次。

我的建议:做算子融合时,先分析计算密度(计算量/访存量)。如果计算密度低,说明访存是瓶颈,融合后效果会很明显。如果计算密度高,那融合的意义就不大,因为计算单元已经够忙了。

总结:融合的“黄金法则”

嗯,说了这么多,其实就一句话:让数据多流动,让计算少等待。GPU和NPU虽然长得不一样,但痛点是一样的——访存。算子融合,就是给这个痛点“贴膏药”。

我个人习惯,拿到一个新模型,先画它的数据流图,标出哪些算子是“访存密集”的,哪些是“计算密集”的。然后,把访存密集的算子尽量融合在一起。这样,性能通常能提升30%以上。