Skip to content

11. 在开发板上运行离线网络模型

建议在开发板上运行离线网络模型之前,使用Simulator对模型进行验证。Simulator能够在PC端模拟板上运行环境。 首先确保开发板已经烧录好最新的固件,使用Demo中的dla_classify可对单张图片推演一次,结果为推演结果的TOP5。下面分解dla_classify.cpp文件,针对使用MI_IPU API的顺序举例说明。 dla_classify.cpp文件路径为:sdk/verify/mi_demo/source/dla_classify/dla_classify.cpp


11.1. 创建IPU设备

MI_S32 IPUCreateDevice(char* pFirmwarePath, MI_U32 u32VarBufSize) 
{ 
    MI_S32 s32Ret = MI_SUCCESS; 
    MI_IPU_DevAttr_t stDevAttr; 
    stDevAttr.u32MaxVariableBufSize = u32VarBufSize; 
    stDevAttr.u32YUV420_W_Pitch_Alignment = 16; 
    stDevAttr.u32YUV420_H_Pitch_Alignment = 2; 
    stDevAttr.u32XRGB_W_Pitch_Alignment = 16; 
    s32Ret = MI_IPU_CreateDevice(&stDevAttr, NULL, pFirmwarePath, 0); 
    return s32Ret;
 }

输入参数:

```ini
pFirmwarePath:Firmware文件的路径,传NULL时会调用/config/dla/ipu_firmware.bin 

u32VarBufSize:模型内部Tensor使用的memory的最大大小
```
    u32VarBufSize获取方法如下

    方法1 :可以通过parse_net工具获取

         parse_net工具路径SGS_IPU_SDK/bin/parse_net 

         使用时直接在parse_net命令后加上定点网络模型路径即可,可参考以下命令:

         ./parse_net xxx_fixed.sim | grep Variable
         输出: 
         | |--> SubGraph[0]: Variable buffer 0xd2f00 @ 0x82f3b40.
         其中0xd2f002对齐后为u32VarBufSize大小
         C/C++中,向2对齐的定义为:
         #define alignment_up(a, size) ((a + size - 1) & (~(size - 1)))
         所以u32VarBufSize为864000

    方法2 :使用MI的API获取

         MI_IPU_OfflineModelStaticInfo_t OfflineModelInfo;
         ret = MI_IPU_GetOfflineModeStaticInfo(NULL, modelFilePath, &OfflineModelInfo);
         if (ret != MI_SUCCESS) 
         { 
            std::cerr << "get model variable buffer size failed!" << std::endl;
            return;
         } 
         u32VarBufSize = OfflineModelInfo.u32VariableBufferSize

         如果有多个模型需要运行,选取最大的u32VarBufSize创建IPU设备即可

输出参数:MI_IPU API 错误码,具体参考《MI_IPU_API手册》


11.2. 创建IPU通道

MI_S32 IPUCreateChannel(MI_U32* u32Channel, char* pModelImage) 
{ 
    MI_S32 s32Ret ; 
    MI_IPUChnAttr_t stChnAttr; 

    //create channel 
    memset(&stChnAttr, 0, sizeof(stChnAttr)); 
    stChnAttr.u32InputBufDepth = 2; 
    stChnAttr.u32OutputBufDepth = 2; 
    return MI_IPU_CreateCHN(u32Channel, &stChnAttr, NULL, pModelImage); 
}
MI_U32 u32Channel; 
ret = IPUCreateChannel(&u32ChannelID, ./mobilenet_v1_fixed.img);

输入参数:

s32Channel:创建IPU通道的ID 

pModelImage:离线网络模型文件路径离线

输出参数:

MI_IPU API 错误码,具体参考《MI_IPU_API手册》


11.3. 获取模型输入输出Tensor属性

MI_IPU_SubNet_InputOutputDesc_t desc; 
MI_IPU_GetInOutTensorDesc(u32ChannelID, &desc);

输入参数:

u32ChnId:IPU通道的ID 
pstDesc:IPU子网络输入/输出描述结构体

输出参数:MI_IPU API 错误码,具体参考《MI_IPU_API手册》


11.4. 获取输入输出Tensor

MI_IPU_TensorVector_t InputTensorVector; 
MI_IPU_TensorVector_t OutputTensorVector; 
MI_IPU_GetInputTensors(u32ChannelID, &InputTensorVector); 
MI_IPU_GetOutputTensors(u32ChannelID, &OutputTensorVector);

输入参数:

u32ChannelID:IPU通道的ID 

InputTensorVector:输入IPU Tensor数组结构体 

OutputTensorVector:输出IPU Tensor数组结构体

输出参数:

MI_IPU API 错误码,具体参考《MI_IPU_API手册》


11.5. 模型数据输入

11.5.1. 拷贝数据

将数据拷贝至模型输入Tensor的虚拟地址,拷贝完成后调用MI_SYS_FlushInvCache:

MI_U8* pdata = (MI_U8 *)InputTensorVector.astArrayTensors[0].ptTensorData[0]; 
MI_U8* pSrc = (MI_U8 *)Input_Data; 
for(int i = 0; i < imageSize; i++) 
{ 
    *(pdata + i) = *(pSrc + i); 
} 
MI_SYS_FlushInvCache(pdata, imageSize);
  • 模型中input formats设置为BGR或者RGB时,stride不要做alignment,做了alignment反而有问题。

  • 模型中input formats设置为BGRA或者RGBA(A channel在高地址),alignmet规则是stride = alignment_up(width*4, 16 )。

    input_formats设置为BGRA,对应MI_SYS_PixelFormat_e为E_MI_SYSPIXEL_FRAME_ARGB8888

    input_formats设置为RGBA,对应MI_SYS_PixelFormat_e为E_MI_SYSPIXEL_FRAME_ABGR8888

  • input formats设置为YUV_NV12或者GRAY时,alignment规则是stride = alignment_up(width,16),height需要2 alignment。

  • input_formats设置为YUV_NV12或者GRAY时,对应MI_SYS_PixelFormat_e为E_MI_SYSPIXEL_FRAME_YUV_SEMIPLANAR_420


11.5.2.零拷贝数据

如果使用MI的其他模块,为模型输入做好了数据准备,可以不用拷贝数据,直接给传递其他MI模块的物理地址。

以下以MI_DIVP模块示例模型输入零拷贝数据,需注意:

创建IPU通道时,输入InputBufDepth设为零,不再使用MI_IPU_GetInputTensors分配输入空间

stChnAttr.u32InputBufDepth = 0;

由于不使用MI_IPU_GetInputTensors,需手动将网络描述的网络模型输入个数赋给

InputTensorVector.u32TensorCount。

InputTensorVector.u32TensorCount = desc.u32InputTensorCount;

使用MI_DIVP缩放图片:

分配MI_DIVP的空间

MI_PHY phySrcBufAddr = 0; 
MI_PHY phyDstBufAddr = 0;
void *pVirSrcBufAddr; 
void *pVirDstBufAddr; 
MI_S32 ret;

ret = MI_SYS_MMA_Alloc(NULL, SRC_BUFF_SIZE, &phySrcBufAddr); 
if(ret != MI_SUCCESS) 
{
    divp_ut_dbg("alloc src buff failed\n"); 
    return -1;
}
ret = MI_SYS_Mmap(phySrcBufAddr, SRC_BUFF_SIZE, &pVirSrcBufAddr, TRUE); 
if(ret != MI_SUCCESS) 
{
    MI_SYS_MMA_Free(phySrcBufAddr); 
    divp_ut_dbg("mmap src buff failed\n"); 
    return -1;
}
ret = MI_SYS_MMA_Alloc(NULL, DST_BUFF_SIZE, &phyDstBufAddr); 
if(ret != MI_SUCCESS) 
{
    MI_SYS_Munmap(pVirSrcBufAddr, SRC_BUFF_SIZE); 
    MI_SYS_MMA_Free(phySrcBufAddr); 
    divp_ut_dbg("alloc dst buff failed\n"); 
    return -1;
}
ret = MI_SYS_Mmap(phyDstBufAddr, DST_BUFF_SIZE, &pVirDstBufAddr, TRUE); 
if(ret != MI_SUCCESS)
{ 
    MI_SYS_Munmap(pVirSrcBufAddr, SRC_BUFF_SIZE); 
    MI_SYS_MMA_Free(phySrcBufAddr); 
    MI_SYS_MMA_Free(phyDstBufAddr); 
    divp_ut_dbg("mmap dst buff failed\n"); 
    return -1; 
}
配置MI_DIVP参数:

MI_DIVP_DirectBuf_t stSrcBuf; 
MI_DIVP_DirectBuf_t stDstBuf; 
MI_SYS_WindowRect_t stSrcCrop; 
stSrcBuf.ePixelFormat = E_MI_SYS_PIXEL_FRAME_YUV_SEMIPLANAR_420; 
stSrcBuf.u32Width = SRC_WIDTH; 
stSrcBuf.u32Height = SRC_HEIGHT; 
stSrcBuf.u32Stride[0] = SRC_BUFF_STRIDE; 
stSrcBuf.u32Stride[1] = SRC_BUFF_STRIDE; 
stSrcBuf.phyAddr[0] = phySrcBufAddr; 
stSrcBuf.phyAddr[1] = stSrcBuf.phyAddr[0] + SRC_BUFF_STRIDE*SRC_HEIGHT; 
stDstBuf.ePixelFormat = E_MI_SYS_PIXEL_FRAME_YUV_SEMIPLANAR_420; 
stDstBuf.u32Width = DST_WIDTH; stDstBuf.u32Height = DST_HEIGHT; 
stDstBuf.u32Stride[0] = DST_BUFF_STRIDE; 
stDstBuf.u32Stride[1] = DST_BUFF_STRIDE; 
stDstBuf.phyAddr[0] = phyDstBufAddr; 
stDstBuf.phyAddr[1] = stDstBuf.phyAddr[0] + DST_BUFF_STRIDE*DST_HEIGHT; 
stSrcCrop.u16X = CROP_X; stSrcCrop.u16Y = CROP_Y; 
stSrcCrop.u16Width = CROP_W; 
stSrcCrop.u16Height = CROP_H;
输入图片数据:

int ReadSize = 0; 
FILE *input_fp; 
input_fp = fopen("./1920x1080_yuv420.yuv","r"); 
if(!input_fp) 
{ 
    divp_ut_dbg("open file[%s] failed\n","./1920x1080_yuv420.yuv"); 
    return -1;
} 
for(Idx = 0; Idx < SRC_HEIGHT * 3 / 2; Idx++) 
{ 
    ReadSize = fread(pVirSrcBufAddr+Idx*SRC_BUFF_STRIDE, 1, SRC_WIDTH, input_fp); 
    if(ReadSize != width) 
        { 
        return -1; 
        } 
}

使用MI_DIVP缩放图片:

MI_DIVP_StretchBuf(&stSrcBuf, &stSrcCrop, &stDstBuf)

将MI_DIVP的输出物理地址赋给MI_IPU的输入Tensor物理地址:

InputTensorVector.astArrayTensors[0].phyTensorAddr[0] = stDstBuf.phyAddr[0]; 
InputTensorVector.astArrayTensors[0].phyTensorAddr[1] = stDstBuf.phyAddr[1];
之后可以进行模型推演。 使用零拷贝数据,不需要再释放输入Tensor,即不再调用MI_IPU_PutInputTensors函数。


11.6. 模型推演

ret = MI_IPU_Invoke(u32ChannelID, &InputTensorVector, &OutputTensorVector); 
if (ret != MI_SUCCESS) 
{ 
    MI_IPU_DestroyCHN(u32ChannelID); 
    MI_IPU_DestroyDevice(); 
    std::cerr << "IPU invoke failed!!" << std::endl; 
}

输入参数:

u32ChannelID:IPU通道的ID 

InputTensorVector:输入IPU Tensor数组结构体 

OutputTensorVector:输出IPU Tensor数组结构体

输出参数:

MI_IPU API 错误码,具体参考《MI_IPU_API手册》


11.7. 释放输入输出Tensor

MI_IPU_PutInputTensors(u32ChannelID, &InputTensorVector); 
MI_IPU_PutOutputTensors(u32ChannelID, &OutputTensorVector);
输入参数:

u32ChannelID:IPU通道的ID

InputTensorVector:输入IPU Tensor数组结构体 

OutputTensorVector:输出IPU Tensor数组结构体

输出参数:

MI_IPU API 错误码,具体参考《MI_IPU_API手册》


11.8. 销毁IPU通道

MI_IPU_DestroyCHN(u32ChannelID);

输入参数:

s32Channel:创建IPU通道的ID

输出参数:

MI_IPU API 错误码,具体参考《MI_IPU_API手册》


11.9. 销毁IPU设备

MI_IPU_DestroyDevice();

输入参数:


输出参数:MI_IPU API 错误码,具体参考《MI_IPU_API手册》


11.10. Py_dla接口使用

Py_dla是Python封装后的MI_IPU接口,可以在开发板上快速处理模型输出数据,方便快速验证模型的准确性。 已准备好ARM的Python3.8的库,存放在sdk/verify/mi_demo/source/py_dla/Python3.8.tar.bz2, 使用时请先使用如下命令解压到可以mount到开发板的本地路径:

tar -jxvf Python3.8.tar.bz2

在开发板中设置Python3.8的路径:

export LD_LIBRARY_PATH=path/to/Python3.8/usr/lib/:$LD_LIBRARY_PATH
export PATH=path/to/Python3.8/usr/bin:$PATH

以下文件为Py_dla接口文件,可以使用python直接import使用:sdk/verify/mi_demo/source/py_dla/_misim.cpython-38-arm-linux-gnueabihf.so 将该文件与编写python脚本放在同一路径下,即可使用Py_dla。下面说明python中调用_misim.cpython-38-arm-linux-gnueabihf.so以及API举例介绍。 使用如下方式,加载_misim.cpython-38-arm-linux-gnueabihf.so

from _misim import MI_simulator
from _misim import IPU_GetOfflineModeStaticInfo
from _misim import IPU_CreateDevice

API举例说明: IPU_GetOfflineModeStaticInfo:获取模型variable buffer size

>>> model_path = 'mobilenet_v1_fixed.sim_sgsimg.img'
>>> variable_buffer_size = IPU_GetOfflineModeStaticInfo(model_path)

IPU_CreateDevice:创建IPU设备(如果有多个模型需要运行,选取最大的variable_buffer_size 创建IPU设备)

>>> IPU_CreateDevice(variable_buffer_size)

可选参数: ipu_firmware(用来指定ipu_firmware.bin的路径,默认为/config/dla/ipu_firmware.bin)

>>> IPU_CreateDevice(variable_buffer_size, '/path/to/ipu_firmware')

MI_simulator:创建模型实例

>>> model = MI_simulator(model_path)

获取模型输入信息

>>> in_details = model.get_input_details()
>>> print(in_details)
[{'name': 'input', 'index': 0, 'dtype': <class 'numpy.uint8'>, 'shape': array([  1, 224, 224,   3])}]

获取模型输出信息

>>> out_details = model.get_output_details()
>>> print(out_details)
[{'name': 'MobilenetV2/Predictions/Reshape_1', 'index': 0, 'dtype': <class 'numpy.float32'>, 'shape': array([   1, 1001])}]

给第0个index输入数据(img_data为与输入shape、dtype一致的numpy.ndarray数据

model.set_input(in_details[0]['index'], img_data)

模型前向推演

model.invoke()

获取第0个index的输出(输出为numpy.ndarray),建议使用numpy的copy()方法

>>> output0 = model.get_output(out_details[0]['index'])

获取第1个index的输出,使用numpy的copy()方法,拷贝内存到新的内存里

>>> output1 = model.get_output(out_details[1]['index']).copy()