SHL

得陇复望蜀
在学习DL性能优化的学生
© 2018. All rights reserved.

NNIE

NNIE 是 Neural Network Inference Engine 的简称,是海思媒体 SoC 中专门针对神经网 络特别是深度学习卷积神经网络进行加速处理的硬件单元,支持现有大部分的公开网 络,如 Alexnet、VGG16、Googlenet、Resnet18、Resnet50 等分类网络,Faster R- CNN、YOLO、SSD、RFCN 等检测网络,以及 SegNet、FCN 等场景分割网络。目前 NNIE 配套软件及工具链仅支持以 Caffe 框架,使用其他框架的网络模型需要转化 为 Caffe 框架下的模型。

linux下模型转换工具nnie_mapper

通过设置不同的模式,mapper*.caffemodel 转化成在仿真器、仿真库或板端上可加载执行的数据指令文件。

量化方法

猜测:
应该是根据所提供的图片数据来进行分析,用的是非线性量化,应该是针对典型场景图片所有的像素值,将密集出现的像素值划区域压缩在一个值上。 应该跟输入的大小有关,用压缩过的图片来进行量化。

信息存储结构体

NNIE中定义了很多的结构体,并用这些结构体来存储信息,如果光看代码,很容易看着看着忘了上面的,所以记录下来。

SAMPLE_SVP_NNIE_PARAM_S *pstNnieParam

这是NNIE中最重要的结构体,它保存了最关键的信息,包括网络参数,输入输出等。

/*NNIE Execution parameters */
typedef struct hiSAMPLE_SVP_NNIE_PARAM_S
{
    SVP_NNIE_MODEL_S*    pstModel;
    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;

pstModel是读取进来的模型.wk文件

SAMPLE_SVP_NNIE_SEG_DATA_S astSegData[SVP_NNIE_MAX_NET_SEG_NUM]

该结构体嵌套上结构体内,用于保存输入输出的信息。

/*each seg input and output memory*/
typedef struct hiSAMPLE_SVP_NNIE_SEG_DATA_S
{
	SVP_SRC_BLOB_S astSrc[SVP_NNIE_MAX_INPUT_NUM];
	SVP_DST_BLOB_S astDst[SVP_NNIE_MAX_OUTPUT_NUM];
}SAMPLE_SVP_NNIE_SEG_DATA_S;

astSrc保存的是输入数据 astDst保存的是输出数据

SAMPLE_SVP_NNIE_CFG_S *pstNnieCfg

/*NNIE configuration parameter*/
typedef struct hiSAMPLE_SVP_NNIE_CFG_S
{
    HI_CHAR *pszPic;
    HI_U32 u32MaxInputNum;
    HI_U32 u32MaxRoiNum;
    HI_U64 au64StepVirAddr[SAMPLE_SVP_NNIE_EACH_SEG_STEP_ADDR_NUM*SVP_NNIE_MAX_NET_SEG_NUM];//virtual addr of LSTM's or RNN's step buffer
	SVP_NNIE_ID_E	aenNnieCoreId[SVP_NNIE_MAX_NET_SEG_NUM];
}SAMPLE_SVP_NNIE_CFG_S;

对于ssd来说,这个结构体中只有pszPicaenNnieCoreId是有用的。

SAMPLE_SVP_NNIE_SSD_SOFTWARE_PARAM_S* pstSoftwareParam

ssd所需参数保存的结构体。

/*SSD software parameter*/
typedef struct hiSAMPLE_SVP_NNIE_SSD_SOFTWARE_PARAM_S
{
	/*----------------- Model Parameters ---------------*/
	HI_U32 au32ConvHeight[12];
	HI_U32 au32ConvWidth[12];
	HI_U32 au32ConvChannel[12];
	/*----------------- PriorBox Parameters ---------------*/
	HI_U32 au32PriorBoxWidth[6];
	HI_U32 au32PriorBoxHeight[6];
	HI_FLOAT af32PriorBoxMinSize[6][1];
	HI_FLOAT af32PriorBoxMaxSize[6][1];
	HI_U32 u32MinSizeNum;
	HI_U32 u32MaxSizeNum;
	HI_U32 u32OriImHeight;
	HI_U32 u32OriImWidth;
	HI_U32 au32InputAspectRatioNum[6];
	HI_FLOAT af32PriorBoxAspectRatio[6][2];
	HI_FLOAT af32PriorBoxStepWidth[6];
	HI_FLOAT af32PriorBoxStepHeight[6];
	HI_FLOAT f32Offset;
	HI_BOOL bFlip;
	HI_BOOL bClip;
	HI_S32 as32PriorBoxVar[4];
	/*----------------- Softmax Parameters ---------------*/
	HI_U32 au32SoftMaxInChn[6];
	HI_U32 u32SoftMaxInHeight;
	HI_U32 u32ConcatNum;
	HI_U32 u32SoftMaxOutWidth;
	HI_U32 u32SoftMaxOutHeight;
	HI_U32 u32SoftMaxOutChn;
	/*----------------- DetectionOut Parameters ---------------*/
	HI_U32 u32ClassNum;
	HI_U32 u32TopK;
	HI_U32 u32KeepTopK;
	HI_U32 u32NmsThresh;
	HI_U32 u32ConfThresh;
	HI_U32 au32DetectInputChn[6];
	HI_U32 au32ConvStride[6];
	SVP_MEM_INFO_S stPriorBoxTmpBuf;
	SVP_MEM_INFO_S stSoftMaxTmpBuf;
	SVP_DST_BLOB_S stClassRoiNum;
	SVP_DST_BLOB_S stDstRoi;
	SVP_DST_BLOB_S stDstScore;
	SVP_MEM_INFO_S stGetResultTmpBuf;
}SAMPLE_SVP_NNIE_SSD_SOFTWARE_PARAM_S;

SAMPLE_COMM_SVP_CheckSysInit系统初始化

具体内容资料太少无法理解,只能知道是用于初始化系统。

SAMPLE_SVP_NNIE_Ssd_ParamInit 用于初始化ssd需要的参数

其中分别初始化硬件参数和软件参数。硬件参数指的是NNIE的参数,软件参数指的是实现NNIE不支持的层所需要的参数。

硬件参数初始化

硬件参数放在SAMPLE_SVP_NNIE_PARAM_S *pstNnieParam

SAMPLE_SVP_NNIE_FillForwardInfo

因为我们这里用的是SSD的例子,所以填充的是参数astForwardCtrl。 猜测 : 参数astForwardCtrl只是一个备用,留档。除了NnieCoreId是从其他地方读取的,其余参数都是自己给自己赋值。

同时用读取到的pstModel的参数填充了astSegData的参数。

SAMPLE_SVP_NNIE_GetTaskAndBlobBufSize

顾名思义,这个函数就是用来获取taskblob所需的大小 得到所需大小后再根据其分配内存。

软件参数初始化

软件参数放在SAMPLE_SVP_NNIE_SSD_SOFTWARE_PARAM_S* pstSoftWareParam

SAMPLE_SVP_NNIE_Ssd_SoftwareInit

该函数用来初始化Ssd需要的参数。

需要了解的是
NNIE输出的是一连串的数据,需要你自己来截断(换句话说,就是NNIE的输出是最原始的数据,除了数据,其他信息一点没有)

所以对于PriorBox层,需要提供各层的大小

pstSoftWareParam->au32PriorBoxWidth[0] = 48;
pstSoftWareParam->au32PriorBoxWidth[1] = 24;
pstSoftWareParam->au32PriorBoxWidth[2] = 12;
pstSoftWareParam->au32PriorBoxWidth[3] = 6;
pstSoftWareParam->au32PriorBoxWidth[4] = 4;
pstSoftWareParam->au32PriorBoxWidth[5] = 2;

对于softmax层和detecionout层需要提供输入的参数数量。

pstSoftWareParam->au32SoftMaxInChn[0] = 92160;
pstSoftWareParam->au32SoftMaxInChn[1] = 23040;
pstSoftWareParam->au32SoftMaxInChn[2] = 5760;
pstSoftWareParam->au32SoftMaxInChn[3] = 1440;
pstSoftWareParam->au32SoftMaxInChn[4] = 640;
pstSoftWareParam->au32SoftMaxInChn[5] = 160;

pstSoftWareParam->au32DetectInputChn[0] = 552960;
pstSoftWareParam->au32DetectInputChn[1] = 138240;
pstSoftWareParam->au32DetectInputChn[2] = 34560;
pstSoftWareParam->au32DetectInputChn[3] = 8640;
pstSoftWareParam->au32DetectInputChn[4] = 3840;
pstSoftWareParam->au32DetectInputChn[5] = 960;

以上都需要自己手动计算。

之后是根据上述参数计算所需内存并进行分配。

SAMPLE_SVP_NNIE_FillSrcData读取图片数据

就是很简单的读入数据,每次读一行,因为地址要对齐,所以地址每次需要+u32Stride

 s32Ret = fread(pu8PicAddr,u32Width*u32VarSize,1,fp);
 pu8PicAddr += u32Stride;

SAMPLE_SVP_NNIE_Ssd_GetResult代码实现nnie不支持的层

最重要的部分,这里在cpu上实现了ssd需要的但nnie不支持的层(priorbox层、softmax层和detecionout层)。

nnie大致流程(以ssd为例)

  • 先对系统进行初始化(这一步目前只能按照sample给的例子来实现)。
  • 载入模型。
  • 再进行参数初始化,分别初始化硬件和软件的参数。硬件参数为NNIE所需要的参数,如内存分配地址,网络分割出来的输出数量等;软件参数为后续代码实现的层所需要的参数,在ssd中为priorbox、softmax和detectionout层所需要的参数。
  • 然后读取二进制(bgr)图片数据。减均值和归一化操作似乎集成在nnie中了,这里只需要读取数据即可。
  • 接着将数据送入NNIE进行forward。
  • NNIEforward结束后会返回最后的几个层(如果你有设置中间上报层的话它也会返回)。
  • 得到这几层的数据后就可以着手处理了,手写priorbox层、softmax层和detecionout层来得到最后的结果。

网络切分

当网络中存在 Non-support 层时,需要将网络进行切分,不支持的部分由用户使用 CPU 或者 DSP 等方式实现,统称为非 NNIE 方式。由此整个网络会出现 NNIE->非 NNIE- >NNIE… 的分段执行方式。

nnie_mapper 将 NNIE 的 Non-support 层分为两种,“Proposal”层和“Custom”层:

一些问题和使用细节

FlushCache应该是用来将虚拟地址上的数据映射到物理地址上(Hi3559A的NNIE硬件模块的物理地址和代码的虚拟地址应该是不对应的),所以每次参数变化时都需要FlushCache。

Permute层目前只支持(0,1,2,3)->(0,2,3,1)

Reshape层的第一维必须设为0,因为它只支持对C/H/W进行reshape。第一维设为0表示其与bottom一致。

Slice层只支持分割C/H/W维度,所以不能设置axis=0(在rcnn的模型转换中,由于前置层Permute将timestep转到第一维,导致了Slice切分失败)

pstNnieParam->astSegData[0].astDst[i].u64VirAddr;就是第i个输出(如ssd中nnie有12个输出)。目前还不知道astSegData的num代表的意义。

在输出时,report层是第一个输出的,然后才是没有后续层的输出。也就是说如果有report层,那么pstNnieParam->astSegData[0].astDst[0].u64VirAddr;必然是report层。