海思 NNIE 芯片开发笔记
NNIE
是 Neural Network Inference Engine 的简称,是海思媒体 SoC
中专门针对神经网络特别是深度学习卷积神经网络进行加速处理的硬件单元,可以理解为 CNN
加速器,即 NPU
模块,支持现有大部分的公开网络。
海思提供的软件资源包主要包括 PC
端模型量化编译及仿真工具 + 板端模型推理 SDK
(NNIE 和 runtime
)。注意:NNIE
配套软件及工具链仅支持以 Caffe
框架,使用其他框架的网络模型需要转化为 Caffe
框架下的模型。
一,PC端工具链
PC
端工具链资源主要包括以下部分:
nnie_mapper(tool/nnie/linux/mapper
)目录:简称mapper
,实现caffe
模型优化及编译的功能,将训练好的caffe
模型编译成能在Hi35xx
芯片或者在仿真库中可以运行的数据指令文件。- 仿真库(software\x64目录):
PC
端仿真结果与芯片上运行结果完全一致,包括时间、带宽等。 - 仿真
sample
工程:包含仿真sample
源代码供开发者学习参考。 - 模型包(software\data): 包含若干
sample
中用到的网络的caffe
模型文件及对应的NNIE mapper
配置文件、wk
文件、图像文件等。 Windows
版 IDE(tool\nnie\windows目录)工具RuyStudio
:集成Windows版的NNIE mapper
和仿真库,用户可以将仿真Sample
工程导入运行、调试;IDE
还集成了代码编辑、编译、调试、执行、画框、相似度比对等功能,具体参考 “RuyiStudio工具使用指南”章节。
NNIE
开发流程如下图所示。
在 Caffe
框架上训练模型并使用 NNIE
的 mapper
工具转化为 wk
模型等步骤都在 PC
端离线的。通过设置不同的模式,mapper
可以将 *.caffemodel
转化成在仿真器、仿真库或板端上可加载执行的数据指令文件。一般在开发前期,用户可使用仿真器对训练出来的模型进精度、性能、带宽进行初步评估,符合用户预期后再使用仿真库进行完整功能的仿真,最后将程序移植到板端。
二,SDK 框架解读
海思 3519
平台的 SDK
框架非常大,包含了很多硬件模块的代码,如 DPU
、SVP
、VIO
等模块,由于是做 DL
模型推理的任务,所以只需关注 ~/mpp/sample/SVP
目录下的 hirt
和 nnie
目录代码。整个 SDK
框架主要目录解读如下:
mpp
├── include SDK头文件
├── lib SDK动态库和静态库文件
└── sample
├── common sample公共程序和头文件
└── svp SVP模块参考程序
├── common SVP模块公共程序和头文件
└── nnie NNIE模块参考程序
├── sample_nnie_main.c NNIE参考程序main入口
├── sample_nnie_software 网络推理CPU执行部分,包括后处理
├── sample NNIE参考程序实现代码
└── data 测试数据目录
├── nnie_image 测试图片
└── nnie_model 测试模型
└── hirt 基于NNIE开发的上层runtime软件系统
├── common runtime公共程序和偶文件
├── include runtime模块的头文件
├── src runtime示例程序
└── plugins 自定义算子程序编译的动态库目录
- 海思
3519
硬件为了快速访问内存首地址或者跨行访问数据,在使用DDR4
时,为提高访存效率,建议首地址使用256
字节对齐,stride
使用256
字节的奇数倍对齐。 - 典型的
RGB\HSV\LAB
图像Planar
格式存储,NNIE
默认以B\G\R
、H\S\V
、L\A\B
顺序按通道平面进行存储。
2.1,NNIE运行流程
NNIE
示例程序文件非常大,多达 3000
多行,所有的 cv
(computer vision) 功能函数都放在一个 c
代码文件中,没有按模块划分和用 cpp
进行 OOP
方式编程,这给我们阅读代码带来一些困难。NNIE
模型运行工作流程如下,更多底层 api
的定义可参考源代码文件 sample/sample_nnie.c
和 sample_comm_nnie.c
。
可知 NNIE
总的流程可以分为 5
部分:系统初始化、加载模型、填充数据、模型推理和网络后处理。nnie/hirt
代码中,如果输入图像是离线的图像文件(如 Planar
格式存储的二进制文件),可以使用 fopen
打开文件,然后 fread
函数读取数据;如果是 png/jpg
图片,则需要使用 opencv
的 c
api
cvLoadImage
函数读取图像数据。
除了和系统相关都是一些操作,NNIE
运行的第一步就是加载模型 SAMPLE_COMM_SVP_NNIE_LoadModel(pcModelName, &s_stCnnModel)
,该函数是对底层 HI_MPI_SVP_NNIE_LoadModel
函数的封装,模型加载函数其工作流程图如下:
2.1.1,主要 API 说明
NNIE
模块提供了创建任务和查询任务的基本接口。 用户根据需求调用相应的算子接口创建任务,指定是否阻塞等待结果,即 bInstant
类型,并记录该任务返回的 handle
号。根据返回的 handle 号,指定阻塞方式,可以查询到该任务的完成状态。在 NNIE
模型运行涉及到的主要 API
说明如下:
HI_MPI_SVP_NNIE_LoadModel
:从用户事先加载到 buf 中的模型中解析出网络模型。HI_MPI_SVP_NNIE_GetTskBufSize
:获取给定网络任务各段辅助内存大小。HI_MPI_SVP_NNIE_Forward
:多节点输入输出的 CNN 类型网络预测。HI_MPI_SVP_NNIE_ForwardWithBbox
:多个节点 feature map 输入。HI_MPI_SVP_NNIE_UnloadModel
:卸载模型。HI_MPI_SVP_NNIE_Query
:查询任务是否完成。HI_MPI_SVP_NNIE_AddTskBuf
:记录 TskBuf 地址信息。HI_MPI_SVP_NNIE_RemoveTskBuf
:移除 TskBuf 地址信息。
2.1.2,NNIE重要结构体变量解析
海思的 《HiSVP API 参考文章》只有涉及到 NNIE
和 Runtime
相关的 API
接口参数和函数描述,但是和 Sample
(例子)相关的 API
描述并没有,需要我们前往 SDK
代码自行阅读和理解。
1,对于 NNIE
的模型运行过程来说,有 3
个 CNN
模型参数结构体是非常重要的,模型运行流程中调用的底层 api
的输入参数主要就是这三个结构体,理解这 3
个结构体有助于我们理解 SDK
代码。这些结构体的初始化代码如下所示。
static SAMPLE_SVP_NNIE_MODEL_S s_stCnnModel = {0}; // 初始化模型参数结构体为 {0}
static SAMPLE_SVP_NNIE_PARAM_S s_stCnnNnieParam = {0}; // 初始化 NNIE 执行参数结构体 为 {0}
static SAMPLE_SVP_NNIE_CNN_SOFTWARE_PARAM_S s_stCnnSoftwareParam = {0}; // 初始化 CNN 模型得到 TopN 参数的结构体
注意 SVP_NNIE_MODEL_S
结构体也是 SAMPLE_SVP_NNIE_PARAM_S
结构体的成员之一,海思 NNIE SDK
代码中,结构体之间经常是多层嵌套的,以 SAMPLE_SVP_NNIE_PARAM_S
结构体为例,其内部最多嵌套了 5
层结构体,比如要想获取 u32Height
(表示设定输入图像的高度)变量的值,需知其定义的结构体跳转关系如下:
SAMPLE_SVP_NNIE_PARAM_S *pstNnieParam;
u32Height = pstNnieParam-->astSegData[u32SegIdx].astSrc[u32NodeIdx].unShape.stWhc.u32Height;
SAMPLE_SVP_NNIE_PARAM_S
NNIE 执行参数结构体代码定义如下。它包含了 SVP_NNIE_MODEL_S
、SVP_MEM_INFO_S
和 SAMPLE_SVP_NNIE_SEG_DATA_S
等结构体成员。
/*NNIE Execution parameters */
typedef struct hiSAMPLE_SVP_NNIE_PARAM_S
{
SVP_NNIE_MODEL_S* pstModel; // 定义 NNIE 模型结构体
HI_U32 u32TmpBufSize;
HI_U32 au32TaskBufSize[SVP_NNIE_MAX_NET_SEG_NUM];
SVP_MEM_INFO_S stTaskBuf;
SVP_MEM_INFO_S stTmpBuf;
SVP_MEM_INFO_S stStepBuf; //store Lstm step info
SAMPLE_SVP_NNIE_SEG_DATA_S astSegData[SVP_NNIE_MAX_NET_SEG_NUM]; //each seg's input and output blob
SVP_NNIE_FORWARD_CTRL_S astForwardCtrl[SVP_NNIE_MAX_NET_SEG_NUM];
SVP_NNIE_FORWARD_WITHBBOX_CTRL_S astForwardWithBboxCtrl[SVP_NNIE_MAX_NET_SEG_NUM];
}SAMPLE_SVP_NNIE_PARAM_S;
SVP_NNIE_MODEL_S
结构体定义 NNIE
模型参数信息,其结构体代码如下:
/* NNIE model, 定义 NNIE 模型参数信息结构体 */
typedef struct hiSVP_NNIE_MODEL_S
{
SVP_NNIE_RUN_MODE_E enRunMode; // 网络模型运行模式
HI_U32 u32TmpBufSize; /*temp buffer size,辅助内存 tmpBuf 大小*/
HI_U32 u32NetSegNum; // 网络模型中 NNIE 执行的网络分段数,取值范围:[1, 8]
// SVP_NNIE_MAX_NET_SEG_NUM NNIE将网切成的最大段数:8
SVP_NNIE_SEG_S astSeg[SVP_NNIE_MAX_NET_SEG_NUM]; // 网络在 NNIE 引擎上执行的段信息。
SVP_NNIE_ROIPOOL_INFO_S astRoiInfo[SVP_NNIE_MAX_ROI_LAYER_NUM]; /*ROIPooling info, 网络模型中 RoiPooling 以及 PsRoiPooling 的信息数组 */
SVP_MEM_INFO_S stBase; // 网络其他信息
}SVP_NNIE_MODEL_S; // SVP_NNIE_MODEL_S 等于 struct hiSVP_NNIE_MODEL_S
2,除了和模型参数相关的结构体,网络层的 Blob
数据结构体也很重要。NNIE
的网络层输入输出数据 (tensor
)用 BLOB
表示,根据不同的数据类型,BLOB 也有不同的内存排布类型,部分 BLOB 解释如下所示。
数据类型和数据结构的定义代码在 ~/mpp/include/hi_comm_svp.h
文件中,其中 BLOB
结构体 SVP_BLOB_S
定义如下:
/* 定义多个连续存放的 blob 信息。*/
typedef struct hiSVP_BLOB_S
{
SVP_BLOB_TYPE_E enType; /*Blob 类型, enType 取值范围[SVP_BLOB_TYPE_S32, SVP_BLOB_TYPE_BUTT)。*/
HI_U32 u32Stride; /*Stride, a line bytes num,Blob 中单行数据对齐后的字节数*/
HI_U64 u64VirAddr; /*virtual addr*/
HI_U64 u64PhyAddr; /*physical addr*/
// 表示连续内存块的数目,若一帧数据对应一个块,则表示 blob 中有 u32Num 帧
HI_U32 u32Num; /*N: frame num or sequence num,correspond to caffe blob's n*/
union
{
struct
{
HI_U32 u32Width; /*W: frame width, correspond to caffe blob's w*/
HI_U32 u32Height; /*H: frame height, correspond to caffe blob's h*/
HI_U32 u32Chn; /*C: frame channel,correspond to caffe blob's c*/
}stWhc;
struct
{
HI_U32 u32Dim; /*D: vecotr dimension, 向量的长度*/
HI_U64 u64VirAddrStep; /*T: virtual adress of time steps array in each sequence,数组地址,数组元素表示每段序列有多少个向量*/
}stSeq;
}unShape;
}SVP_BLOB_S;
BLOB
中存储了 Tensor
数据的首物理和虚拟内存地址,宽、高、通道数的维度信息,以及连续内存块数目(帧数)信息。
2.1.3,重要概念
网络分段
:对于NNIE
不支持的某些网络层节点,编译器支持用户对网络进行分段,不支持的部分编译器不会编译,由用户自己用CPU
实现。句柄(handle)
:用户在调用 DSP 处理任务时,系统会为每个任务分配一个 handle,用于标识不同的任务。查询(query)
:用户根据系统返回的 handle,调用 HI_MPI_SVP_DSP_Query 可以查询对应算子任务是否完成。- 及时刷
cache
:NNIE 硬件只能从DDR
中获取数据。为防止 NNIE 输入输出数据不被CPU cahce 干扰,用户需要调用
HI_MPI_SYS_MmFlushCache` 接口刷 cache。 - 跨度(
stride
):一行有效数据的 byte 数目 + 为硬件快速跨越到下一行补齐的一些无效 byte 数目,如下图所示。
2.2,runtime工作流程
runtime
模块是基于 nnie
底层 api
做的上层封装,使得模型运行逻辑更清晰直观,代码上手难度也更低。其工作流程图如下图所示。
三,参考资料
- 《HiSVP 开发指南》
- 《HiSVP API 参考》
“本文正在参加人工智能创作者扶持计划 ”
- 点赞
- 收藏
- 关注作者
评论(0)