【注】:模型转换在x86 PC上进行,需要预先完成SNPE Setup。
模型转换本身并不复杂,因为模型本身只代表一系列运算(算子Ops),但是不同的框架(也可以说是不同的硬件平台所导致的)使用了不同的规范和实现,在各个框架之间做模型转换,通常会借助ONNX(Open Neural Network Exchange)这一规范来完成。
snpe-onnx-to-dlc --input_network models/bvlc_alexnet/bvlc_alexnet/model.onnx
--output_path bvlc_alexnet.dlc
SNPE将onnx模型转换为dlc的命令很简单,转换失败最主要的原因就是算子不支持,这个需要自行去一层一层网络进行排查,转换失败的log也会给出一些提示。
注:SNPE支持的ONNX算子可以在Support ONNX Ops中查到。
注:SNPE支持的网络层可以在Supported Network Layers中查到。
基于pytorch hub,yolov5不同格式的模型导出可以参考官方教程:TFLite, ONNX, CoreML, TensorRT Export,以下内容基于用户配置好了yolov5官方要求的环境进行。
【注】:本Repo基于yolo5 v6.0进行。
yolov5s的v6.0版本以前的模型转换成onnx模型后,再转换为snpe的dlc模型时,会有snpe不支持的5-dimensions的输入输出数据的问题。需要使用v6.0版本后的yolov5s的模型,用如下命令进行转换:
python3 export.py --weights yolov5s.pt --optimize --opset 11 --simplify --include onnx
注:首次运行时会自动帮用户安装onnx package,建议最好先安装好onnx,高通要求的onnx的版本是1.6.0。建议安装1.8.1版本的onnx更好些,可以转换yolov7的模型,1.6.0版本的则不行。
# 运行如下命令前需要确保SNPE环境配置正常
# source ${SNPE_ROOT}/bin/envsetup.sh -o ${ONNX_PACKAGE_DIR}
snpe-onnx-to-dlc --input_network yolov5s.onnx --output_path yolov5s.dlc
假如使用官方的预训练模型,在转dlc时会有一系列WARNING日志输出:
WARNING_OP_VERSION_NOT_SUPPORTED:可以忽略
RuntimeWarning:error_message=Layer is not supported in AIP:可以忽略。
Log输出为AIP Runtime不支持Reshape_199
,Transpose_200
等layers,原因是AIP只支持4-dimension的输入输出数据,而Reshape_199
等layers的尺寸为5-dimensions,但如SNPETask中setCPUFallbackMode()
章节所言,runtime不支持的layers会被fullback到CPU上运行,因此并不会影响前向推理。
使用netron解析导出的onnx模型,可以看到这些不支持的layers为前向推理的后处理部分:
主要是将1x80x80x255
,1x40x40x255
,1x20x20x255
的三个输出层数据reshape并且concat到一个1x25200x85
的layer中,这部分工作我们可以在后处理中自行完成,因此假如想要去掉这部分WARNING提示,我们可以修改${YOLOV5_ROOT}/model/yolo.py
中的forward(self, x)
函数(第54-74行)为:
def forward(self, x):
z = [] # inference output
for i in range(self.nl):
x[i] = self.m[i](x[i]) # conv
bs, _, ny, nx = x[i].shape # x(bs,255,20,20) to x(bs,3,20,20,85)
# x[i] = x[i].view(bs, self.na, self.no, ny, nx).permute(0, 1, 3, 4, 2).contiguous()
if not self.training: # inference
# if self.onnx_dynamic or self.grid[i].shape[2:4] != x[i].shape[2:4]:
# self.grid[i], self.anchor_grid[i] = self._make_grid(nx, ny, i)
y = x[i].sigmoid()
# if self.inplace:
# y[..., 0:2] = (y[..., 0:2] * 2 + self.grid[i]) * self.stride[i] # xy
# y[..., 2:4] = (y[..., 2:4] * 2) ** 2 * self.anchor_grid[i] # wh
# else: # for YOLOv5 on AWS Inferentia https://github.com/ultralytics/yolov5/pull/2953
# xy, wh, conf = y.split((2, 2, self.nc + 1), 4) # y.tensor_split((2, 4, 5), 4) # torch 1.8.0
# xy = (xy * 2 + self.grid[i]) * self.stride[i] # xy
# wh = (wh * 2) ** 2 * self.anchor_grid[i] # wh
# y = torch.cat((xy, wh, conf), 4)
# z.append(y.view(bs, -1, self.no))
z.append(y)
# return x if self.training else (torch.cat(z, 1),) if self.export else (torch.cat(z, 1), x)
z.append(x)
return z
这时候再导出的onnx模型就去掉了,再次使用netron可以看到模型的输出层发生了变化:
转dlc的错误信息也相应变少:
注:本sample采用去除Reshape和Concat等layers之后转出来的dlc为例,yolov5s.dlc和yolov5s_full.dlc两者在使用上的差距体现在yolov5s/YOLOv5sImpl.cpp
中的PostProcess()
函数中,在PostProcess()
的第142-173行我们做的工作就是将将1x80x80x255
,1x40x40x255
,1x20x20x255
的三个输出层数据拷贝到到一个1x25200x85
的buffer中。
注:yolov5s_full.dlc的后处理可以参考yolov5-opencv-cpp-python中的detect()
函数。
此处可能存在不合适展示的内容,页面不予展示。您可通过相关编辑功能自查并修改。
如您确认内容无涉及 不当用语 / 纯广告导流 / 暴力 / 低俗色情 / 侵权 / 盗版 / 虚假 / 无价值内容或违法国家有关法律法规的内容,可点击提交进行申诉,我们将尽快为您处理。