海思 NNIE 芯片开发笔记

举报
嵌入式视觉 发表于 2023/03/25 17:03:55 2023/03/25
【摘要】 NNIE 是Neural Network Inference Engine 的简称,是海思媒体 SoC 中专门针对神经网络特别是深度学习卷积神经网络进行加速处理的硬件单元,可以理解为 CNN 加速器。

NNIE 是 Neural Network Inference Engine 的简称,是海思媒体 SoC 中专门针对神经网络特别是深度学习卷积神经网络进行加速处理的硬件单元,可以理解为 CNN 加速器,即 NPU 模块,支持现有大部分的公开网络。

海思提供的软件资源包主要包括 PC 端模型量化编译及仿真工具 + 板端模型推理 SDKNNIE 和 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 框架上训练模型并使用 NNIEmapper 工具转化为 wk 模型等步骤都在 PC 端离线的。通过设置不同的模式,mapper 可以将 *.caffemodel 转化成在仿真器、仿真库或板端上可加载执行的数据指令文件。一般在开发前期,用户可使用仿真器对训练出来的模型进精度、性能、带宽进行初步评估,符合用户预期后再使用仿真库进行完整功能的仿真,最后将程序移植到板端。

二,SDK 框架解读

海思 3519 平台的 SDK 框架非常大,包含了很多硬件模块的代码,如 DPUSVPVIO 等模块,由于是做 DL 模型推理的任务,所以只需关注 ~/mpp/sample/SVP 目录下的 hirtnnie 目录代码。整个 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\RH\S\VL\A\B 顺序按通道平面进行存储

2.1,NNIE运行流程

NNIE 示例程序文件非常大,多达 3000 多行,所有的 cv(computer vision) 功能函数都放在一个 c 代码文件中,没有按模块划分和用 cpp 进行 OOP 方式编程,这给我们阅读代码带来一些困难。NNIE 模型运行工作流程如下,更多底层 api 的定义可参考源代码文件 sample/sample_nnie.csample_comm_nnie.c

NNIE工作流程.png

可知 NNIE 总的流程可以分为 5 部分:系统初始化、加载模型、填充数据、模型推理和网络后处理。nnie/hirt 代码中,如果输入图像是离线的图像文件(如 Planar 格式存储的二进制文件),可以使用 fopen 打开文件,然后 fread 函数读取数据;如果是 png/jpg 图片,则需要使用 opencvc api cvLoadImage 函数读取图像数据。

除了和系统相关都是一些操作,NNIE 运行的第一步就是加载模型 SAMPLE_COMM_SVP_NNIE_LoadModel(pcModelName, &s_stCnnModel),该函数是对底层 HI_MPI_SVP_NNIE_LoadModel 函数的封装,模型加载函数其工作流程图如下

SAMPLE_COMM_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 参考文章》只有涉及到 NNIERuntime 相关的 API 接口参数和函数描述,但是和 Sample(例子)相关的 API 描述并没有,需要我们前往 SDK 代码自行阅读和理解。

1,对于 NNIE 的模型运行过程来说,3CNN 模型参数结构体是非常重要的,模型运行流程中调用的底层 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_SSVP_MEM_INFO_SSAMPLE_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 解释如下所示。

blob.png

数据类型和数据结构的定义代码在 ~/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 做的上层封装,使得模型运行逻辑更清晰直观,代码上手难度也更低。其工作流程图如下图所示。

runtime工作流程.jpg

三,参考资料

  1. 《HiSVP 开发指南》
  2. 《HiSVP API 参考》

“本文正在参加人工智能创作者扶持计划 ”

【版权声明】本文为华为云社区用户原创内容,转载时必须标注文章的来源(华为云社区)、文章链接、文章作者等基本信息, 否则作者和本社区有权追究责任。如果您发现本社区中有涉嫌抄袭的内容,欢迎发送邮件进行举报,并提供相关证据,一经查实,本社区将立刻删除涉嫌侵权内容,举报邮箱: cloudbbs@huaweicloud.com
  • 点赞
  • 收藏
  • 关注作者

评论(0

0/1000
抱歉,系统识别当前为高风险访问,暂不支持该操作

全部回复

上滑加载中

设置昵称

在此一键设置昵称,即可参与社区互动!

*长度不超过10个汉字或20个英文字符,设置后3个月内不可修改。

*长度不超过10个汉字或20个英文字符,设置后3个月内不可修改。