2. NPU硬件架构基础
大家好,我是你们这趟NPU驱动开发之旅的引路人。今天咱们聊聊NPU的硬件架构。说实话,很多做驱动的兄弟一上来就撸代码,结果遇到性能瓶颈或者奇怪的bug,查来查去发现是对硬件不够了解。我个人习惯是,写驱动之前,先把芯片的“骨架”摸清楚。
NPU的硬件架构,说白了就是四个核心模块:MAC阵列(干活的)、片上存储(放数据的)、DMA引擎(搬数据的)、指令调度器(管事的)。这四个家伙配合得好,NPU才能跑得飞起。咱们一个一个来看。
核心观点:驱动开发的核心任务,就是让这四个模块高效协同。你写的每一行驱动代码,最终都是在跟它们打交道。
1. MAC阵列:NPU的“肌肉”
MAC阵列,全称是乘累加阵列。它是NPU里最核心的计算单元。你想想看,神经网络里最多的操作是什么?卷积、全连接,这些本质上都是大量的乘法和加法。MAC阵列就是专门干这个的。
一个MAC单元,一次能做一个乘法和一个加法。但NPU里可不是只有一个MAC单元,而是成百上千个排成阵列。比如一个16x16的MAC阵列,就有256个MAC单元,可以同时做256组乘加运算。这就是NPU比CPU快那么多的原因——并行计算。
驱动开发中要注意什么?
- 数据排布:MAC阵列要求输入数据是连续且对齐的。我在项目中遇到过,因为数据没对齐,导致MAC阵列空转了好几个周期,性能直接掉了30%。
- 精度模式:现在的NPU支持INT8、INT16甚至BF16。驱动里要正确配置MAC单元的精度模式,不然算出来的结果全是错的。
- 流水线:MAC阵列内部是流水线结构的。驱动要保证数据喂得够快,别让MAC阵列“饿着”。
避坑指南:我曾经调试过一个模型,推理结果总是不对。查了两天才发现,是驱动里把MAC阵列的饱和模式配置错了。INT8乘法溢出后,结果直接截断,而不是饱和。嗯,从那以后我每次配置MAC都会多看两眼寄存器描述。
2. 片上存储:SRAM与Cache
MAC阵列算得快,但数据得喂得快才行。这就轮到片上存储出场了。NPU的片上存储主要分两种:SRAM和Cache。
SRAM:这是NPU的“工作台”。权重、输入特征图、中间结果,都临时放在这里。SRAM的特点是快,但容量小(通常几MB到几十MB)。驱动开发时,你要管理好这块空间,比如用双缓冲机制,让MAC阵列和DMA同时工作。
Cache:这是NPU的“缓存柜”。它自动缓存最近用过的数据,减少访问外部DDR的次数。但Cache有个问题——一致性。如果CPU和NPU共享数据,驱动得手动维护Cache一致性,不然读到的是脏数据。
驱动开发要点:
- SRAM分配:我建议用静态分配,避免运行时动态分配带来的碎片和延迟。
- Cache刷新:每次NPU开始计算前,驱动要确保Cache里的数据是最新的。每次计算结束后,要刷新Cache,让CPU能看到结果。
- Bank冲突:SRAM通常分多个Bank。如果连续访问同一个Bank,会有延迟。驱动在安排数据地址时,要尽量分散到不同Bank。
警告:千万别小看Cache一致性问题。我见过一个项目,NPU推理结果时对时错,最后发现是驱动忘了在NPU启动前做Cache刷新。这种bug极难复现,但一旦出现,排查起来非常痛苦。
3. 数据通路:DMA引擎
DMA引擎,是NPU的“搬运工”。它负责在DDR和片上SRAM之间搬数据。没有DMA,CPU就得亲自搬,那效率就太低了。
DMA引擎有几个关键特性:
- 多通道:通常有4-8个独立通道,可以同时搬多路数据。
- 链式传输:支持一次配置多个传输任务,DMA自己按顺序执行,CPU不用反复干预。
- 二维传输:对于图像数据,支持按行、按块传输,非常灵活。
驱动开发实战:
配置DMA传输时,我习惯用链表描述符。每个描述符定义一个传输任务,包括源地址、目的地址、长度、是否触发中断等。多个描述符连成链表,DMA会依次执行。
// DMA描述符结构体示例
struct dma_desc {
uint32_t src_addr; // 源地址
uint32_t dst_addr; // 目的地址
uint32_t length; // 传输长度
uint32_t cfg; // 配置:中断使能、链式标志等
struct dma_desc *next; // 下一个描述符
};
技巧:我曾经优化过一个视频处理模型,瓶颈就在DMA传输上。后来我把DMA通道优先级调高,并且用双缓冲(一个缓冲在计算,另一个缓冲在传输),吞吐量直接翻倍。你想想看,让MAC阵列等数据,多浪费啊。
4. 控制单元:指令调度器
指令调度器,是NPU的“大脑”。它负责解析驱动下发的指令,然后控制MAC阵列、DMA、存储等模块协同工作。
NPU的指令通常不是单条指令,而是一个指令序列(也叫任务图)。调度器会按依赖关系,把指令发射到各个执行单元。比如,先发射DMA指令搬数据,等数据到位了,再发射MAC指令开始计算。
驱动开发要点:
- 指令格式:不同NPU的指令格式不同。有的用VLIW(超长指令字),有的用微码。驱动要按规格书正确拼装指令。
- 同步机制:调度器通常提供同步信号(如门铃寄存器)。驱动写完指令后,要写门铃通知调度器开始执行。
- 错误处理:调度器执行出错时,会触发中断。驱动要能解析错误码,定位问题。
经验之谈:调试指令调度器相关的问题,最有效的方法是看硬件trace。我一般会在驱动里加一个调试模式,把每次下发的指令序列dump出来,然后对照硬件手册逐条检查。这招帮我解决过不少疑难杂症。
知识体系总览
下面这张图,是我画的NPU硬件架构核心模块关系图。你看一眼,就能明白这四个模块是怎么配合的。
从这张图你能看到,指令调度器是“大脑”,它控制着DMA、存储和MAC阵列。DMA从DDR搬数据到片上存储,MAC阵列从片上存储取数据计算,结果再通过DMA写回DDR。整个流程环环相扣,驱动开发就是要确保这个流程顺畅无阻。