这里以 V853 芯片内置 NPU 为对象学习 NPU 架构。这个 NPU,其处理性能为最大 1 TOPS 并切带有 128KB 内部高速缓存用于高速数据交换,支持 OpenCL、OpenVX、android NN 与 ONNX 的 API 调用,同时也支持导入大量常用的深度学习模型。
1 系统架构
上层的应用程序可以通过加载模型与数据到 NPU 进行计算,也可以使用 NPU 提供的软件 API 操作 NPU 执行计算。
NPU包括三个部分:
- 可编程引擎(Programmable Engines,PPU):可编程引擎可以使用 EVIS 硬件加速指令与 Shader 语言进行编程,也可以实现激活函数等操作。
- 神经网络引擎(Neural Network Engine,NN):神经网络引擎包含 NN 核心与 Tensor Process Fabric(TPF,图中简写为 Fabric) 两个部分。NN核心一般计算卷积操作, Tensor Process Fabric 则是作为 NN 核心中的高速数据交换的通路。
- 各级缓存
算子是由可编程引擎与神经网络引擎共同实现的。
NPU 支持 UINT8,INT8,INT16 三种数据格式。
2 NPU 模型转换
NPU 使用的模型是 NPU 自定义的一类模型结构,不能直接将网络训练出的模型直接导入 NPU 进行计算。这就需要将网络训练出的模型到 NPU 的模型上。
NPU 的模型转换步骤如下图所示:
NPU 模型转换包括准备阶段、量化阶段与验证阶段。
2.1 准备阶段
首先把准备好的模型使用工具导入,并创建配置文件。这时候工具会把模型导入并转换为 NPU 所使用的网络模型、权重模型与配置文件。
配置文件用于对网络的输入和输出的参数进行描述以及配置。这些参数包括输入/输出 tensor 的形状、归一化系数 (均值/零点)、图像格式、tensor 的输出格式、后处理方式等等。
2.2 量化阶段
由于训练好的神经网络对数据精度以及噪声的不敏感,因此可以通过量化将参数从浮点数转换为定点数。这样做有两个优点:
- 减少了数据量,进而可以使用容量更小的存储设备,节省了成本;
- 由于数据量减少,浮点转化为定点数也大大降低了系统的计算量,也提高了计算的速度。
但是量化也有一个致命缺陷,会导致精度的丢失。
由于浮点数转换为定点数时会大大降低数据量,导致实际的权重参数准确度降低。在简单的网络里这不是什么大问题,但是如果是复杂的多层多模型的网络,每一层微小的误差都会导致最终数据的错误。
但是对于原始网络,由于使用的是浮点数,无法将数据导入到只支持定点运算的 NN 核心进行计算,这就需要可编程引擎来代替 NN 核进行计算,这样可能大大降低运算效率。
在进行量化过程中,不仅对参数进行了量化,也会对输入输出的数据进行量化。如果模型没有输入数据,就不知道输入输出的数据范围。这时候就需要准备一些具有代表性的输入来参与量化。这些输入数据一般从训练模型的数据集里获得,例如图片数据集里的图片。
另外选择的数据集不一定要把所有训练数据全部加入量化,通常我们选择几百张能够代表所有场景的输入数据就即可。理论上说,量化数据放入得越多,量化后精度可能更好,但是到达一定阈值后效果增长将会非常缓慢甚至不再增长。
2.3 验证阶段
由于上一阶段对模型进行了量化导致了精度的丢失,就需要对每个阶段的模型进行验证,对比结果是否一致。
首先需要使用非量化情况下的模型运行生成每一层的 tensor 作为 Golden tensor。输入的数据可以是数据集中的任意一个数据。然后量化后使用预推理相同的数据再次输出一次 tensor,对比这一次输出的每一层的 tensor 与 Golden tensor 的差别。
如果差别较大可以尝试更换量化模型和量化方式。差别不大即可使用 IDE 进行仿真。也可以直接部署到 V853 上进行测试。
此时测试同样会输出 tensor 数据,对比这一次输出的每一层的 tensor 与 Golden tensor 的差别,差别不大即可集成到 APP 中了。
3 NPU 模型的部署
NPU 系统的模型部署流程一般包括以下四个部分:
数据预处理,将数据处理到适合模型使用的过程。
这里就以一个图像主体识别案例来举例:摄像头捕获到了图像数据,其数据格式是 YUV 的,而模型使用的输入数据是 RGB 的数据,所以需要使用前处理将 YUV 数据转换到 RGB。
接下来是将模型加载到 NPU 内,初始化 NPU 的环境与分配内存,然后将之前预处理的数据交给 NPU 进行计算。计算后 NPU 会输出一个 tensor 数据,这时候就需要数据后处理,将 tensor 数据转换为具体的坐标与类型,就可以反馈到上层应用程序做应用的处理了。