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.
其中0xd2f00向2对齐后为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_SYS模块示例模型输入零拷贝数据,需注意:
创建IPU通道时,输入InputBufDepth设为零,不再使用MI_IPU_GetInputTensors分配输入空间
stChnAttr.u32InputBufDepth = 0;
由于不使用MI_IPU_GetInputTensors,需手动将网络描述的网络模型输入个数赋给
InputTensorVector.u32TensorCount。
InputTensorVector.u32TensorCount = desc.u32InputTensorCount;
使用MI_SYS的API分配空间:
MI_S32 s32ret = 0;
MI_PHY phyAddr = 0;
void* pVirAddr = NULL;
s32ret = MI_SYS_MMA_Alloc(NULL, BufSize, &phyAddr);
if (s32ret != MI_SUCCESS)
{
throw std::runtime_error("Alloc buffer failed!");
}
s32ret = MI_SYS_Mmap(phyAddr, BufSize, &pVirAddr, TRUE);
if (s32ret != MI_SUCCESS)
{
MI_SYS_MMA_Free(phyAddr);
throw std::runtime_error("Mmap buffer failed!");
}
传递虚拟地址和物理地址给InputTensorVector
InputTensorVector.astArrayTensors[0].ptTensorData[0] = pVirAddr;
InputTensorVector.astArrayTensors[0].phyTensorAddr[0] = phyAddr;
之后可以进行模型推演。 使用零拷贝数据,不需要再释放输入Tensor,即不再调用MI_IPU_PutInputTensors函数,释放Buffer调用MI_SYS的API完成。 释放MI_SYS的Buffer
MI_SYS_Munmap(pVirAddr, BufSize);
MI_SYS_MMA_Free(phyAddr);
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()