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来说,这个结构体中只有pszPic
和aenNnieCoreId
是有用的。
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
顾名思义,这个函数就是用来获取task
和blob
所需的大小
得到所需大小后再根据其分配内存。
软件参数初始化
软件参数放在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层。