在上一篇博客中,探讨了如何使用 Python 和 hailo_model_zoo 中预编译的模型来实现目标检测。本篇博客将深入介绍如何将用户自定义训练的模型转换并优化为能够在 Hailo NPU 上高效运行的 .hef 模型。

Python 环境配置

为了将自定义模型编译为 .hef 模型,需要安装 Hailo Dataflow Compiler(DFC) 工具。登录 Hailo 的网站 https://hailo.ai/developer-zone/software-downloads,找到对应 Python 版本的 .whl 文件,并按照下面的步骤创建虚拟环境并安装必要的软件包:

conda create -n hailo-model python=3.10  # 创建虚拟环境
conda activate hailo-model # 激活虚拟环境
sudo apt install libgraphviz-dev
pip install hailo_dataflow_compiler-3.29.0-py3-none-linux_x86_64.whl # 安装 Hailo Dataflow Compiler Python 包

将自定义模型转换为 .hef 模型需要三步:

  1. 将 Tensorflow 或 ONNX 模型转换成 Hailo Archive 模型(.har)。
  2. .har 模型进行量化。
  3. 编译为 Hailo Executable File 模型(.hef)。

转换

Tensorflow 与 ONNX 模型都可以进行转换,这里以 yolov8n 的 ONNX 模型为例,首先引入软件包并定义相关变量。

from hailo_sdk_client import ClientRunner
import os
import cv2
import numpy as np input_size = 640 # 模型输入的尺寸
chosen_hw_arch = "hailo8l" # 要使用的 Hailo 硬件架构,这里是 Hailo-8L
onnx_model_name = "yolov8n" # 模型的名字
onnx_path = "yolov8n.onnx" # 模型的路径
hailo_model_har_path = f"{onnx_model_name}_hailo_model.har" # 转换后模型的保存路径
hailo_quantized_har_path = f"{onnx_model_name}_hailo_quantized_model.har" # 量化后模型的保存路径
hailo_model_hef_path = f"{onnx_model_name}.hef" # 编译后模型的保存路径

接着实例化 ClientRunner 类,并调用 translate_onnx_model() 方法进行转换。

runner = ClientRunner(hw_arch=chosen_hw_arch)
hn, npz = runner.translate_onnx_model(model=onnx_path, net_name=onnx_model_name) # 将 onnx 模型转为 har
runner.save_har(hailo_model_har_path) # 保存转换后的模型

在模型结构较为简单时,通常不会报错。当模型结构较为复杂时,会存在 Hailo NPU 不支持的算子,从而报错导致转换失败。NPU 支持的算子可以查询官网的数据手册,或者查看下文参考中的链接。例如在转换 YOLOv8 模型时会提示以下错误信息:

hailo_sdk_client.model_translator.exceptions.ParsingWithRecommendationException: Parsing failed. The errors found in the graph are:
UnsupportedShuffleLayerError in op /model.22/dfl/Reshape: Failed to determine type of layer to create in node /model.22/dfl/Reshape
Please try to parse the model again, using these end node names: /model.22/Concat_3

出现错误时有两种解决方案。一是根据报错信息,使用 Netron https://netron.app 查看模型结构,并修改原始模型,移除或替换 Hailo NPU 不支持的算子。二是报错信息中会推荐解决方法,在转换时绕过不支持的算子,那么 translate_onnx_model() 方法则需要传递额外的参数:

  • start_node_names:原始模型中开始转换的节点(对应新模型的输入)的名称。
  • end_node_names:原始模型中停止转换的节点(对应新模型的输出)的名称。
  • net_input_shapesstart_node_names 输入的尺寸,如常见的 [b, c, h, w]

节点的名称可以使用 Netron 查看,或者使用下面的程序遍历打印节点的名称。

import onnx

onnx_path = "yolov8n.onnx"
model = onnx.load(onnx_path) print("Input Nodes:")
for input in model.graph.input:
print(input.name)
print("Output Nodes:")
for output in model.graph.output:
print(output.name)
print("Nodes:")
for node in model.graph.node:
print(node.name)

根据上面的错误信息提示,要将停止转换的节点修改为 /model.22/Concat_3,修改后的程序如下。

hn, npz = runner.translate_onnx_model(model=onnx_path, net_name=onnx_model_name, start_node_names=["images"], end_node_names=["/model.22/Concat_3"], net_input_shapes={"images": [1, 3, input_size, input_size]})

程序执行后并未报错,但在最后一步编译时会出现 Hailo NPU 内存不够的情况,我们再观察一下转换时输出的日志:

[info] Translation started on ONNX model yolov8n
[info] Restored ONNX model yolov8n (completion time: 00:00:00.06)
[info] Extracted ONNXRuntime meta-data for Hailo model (completion time: 00:00:00.21)
[info] NMS structure of yolov8 (or equivalent architecture) was detected.
[info] In order to use HailoRT post-processing capabilities, these end node names should be used: /model.22/cv2.0/cv2.0.2/Conv /model.22/cv3.0/cv3.0.2/Conv /model.22/cv2.1/cv2.1.2/Conv /model.22/cv3.1/cv3.1.2/Conv /model.22/cv2.2/cv2.2.2/Conv /model.22/cv3.2/cv3.2.2/Conv.
...

日志建议将停止转换的节点修改为 /model.22/cv2.0/cv2.0.2/Conv /model.22/cv3.0/cv3.0.2/Conv /model.22/cv2.1/cv2.1.2/Conv /model.22/cv3.1/cv3.1.2/Conv /model.22/cv2.2/cv2.2.2/Conv /model.22/cv3。即在 NMS 处理前将模型切割,查阅 Hailo 开发者论坛得知,Hailo NPU 不具备进行 NMS 运算的能力,这一部分将在 CPU 上运行。Hailo 的 GitHub 仓库提供了主流模型转换时结束节点的名称,具体请查看下文参考中的链接。最终,程序修改为:

hn, npz = runner.translate_onnx_model(model=onnx_path, net_name=onnx_model_name, start_node_names=["images"], end_node_names=["/model.22/cv2.0/cv2.0.2/Conv", "/model.22/cv3.0/cv3.0.2/Conv", "/model.22/cv2.1/cv2.1.2/Conv", "/model.22/cv3.1/cv3.1.2/Conv", "/model.22/cv2.2/cv2.2.2/Conv", "/model.22/cv3.2/cv3.2.2/Conv"], net_input_shapes={"images": [1, 3, input_size, input_size]})

量化

模型量化(Quantization)是将深度学习模型中的权重和激活值(输出)从高精度的浮点数(如 float32)转换为低精度的数据类型(如 int8),以减少模型的存储需求、加快推理速度并降低功耗,这一过程对于将深度学习模型部署到边缘设备中特别重要。这里使用的是训练后量化,即在已经训练好的模型上直接进行量化,无需重新训练或微调,但可能会导致一些准确性的损失。

首先需要准备好量化时使用的校准数据集。校准数据集主要用于帮助确定量化参数,以尽量减少量化过程对模型性能的影响。校准数据集的质量直接影响到量化模型的最终性能,应该尽可能涵盖所有的数据变化,以确保量化后的模型在不同条件下都能有良好的泛化能力。校准数据集不需要标签,其主要用于收集每一层激活值的统计数据,例如最小值、最大值、平均值和标准差等。这些统计信息用于确定如何最佳地映射浮点数到整数,从而保持模型性能,这个过程不需要知道输入数据对应的标签,只需要了解数据的分布特性。

本篇博客用到的 YOLOv8 模型是使用 COCO 数据集训练的,下面就以此为例进行校准数据集的准备。

images_path = "data/images"  # 数据集图像路径
dataset_output_path = "calib_set.npy" # 处理完成后的保存路径 images_list = [img_name for img_name in os.listdir(images_path) if os.path.splitext(img_name)[1] in [".jpg", ".png", "bmp"]][:1500] # 获取图像名称列表
calib_dataset = np.zeros((len(images_list), input_size, input_size, 3)) # 初始化 numpy 数组 for idx, img_name in enumerate(sorted(images_list)):
img = cv2.imread(os.path.join(images_path, img_name))
resized = cv2.resize(img, (input_size, input_size)) # 调整原始图像的尺寸为模型输入的尺寸
calib_dataset[idx,:,:,:]=np.array(resized)
np.save(dataset_output_path, calib_dataset)

接着实例化 ClientRunner 类,并调用 optimize() 方法进行量化。

calib_dataset = np.load(dataset_output_path)
runner = ClientRunner(har=hailo_model_har_path)
runner.optimize(calib_dataset) # 量化模型
runner.save_har(hailo_quantized_har_path) # 保存量化后的模型

在量化过程中还可以添加一些脚本对参数进行设置,例如 model_optimization_flavor() 设置量化的级别、resources_param() 设置模型能够使用的资源量等。hailo_model_zoo 仓库提供了主流模型的参数设置脚本,具体请查看下文参考中的链接。程序示例如下。

alls_lines = [
'model_optimization_flavor(optimization_level=1, compression_level=2)',
'resources_param(max_control_utilization=0.6, max_compute_utilization=0.6, max_memory_utilization=0.6)',
'performance_param(fps=5)'
]
runner.load_model_script('\n'.join(alls_lines))
runner.optimize(calib_dataset)

编译

最后使用 compile() 方法完成模型的编译。

runner = ClientRunner(har=hailo_quantized_har_path)
compiled_hef = runner.compile()
with open(hailo_model_hef_path, "wb") as f:
f.write(compiled_hef)

完整程序如下。

from hailo_sdk_client import ClientRunner
import os
import cv2
import numpy as np input_size = 640 # 模型输入的尺寸
chosen_hw_arch = "hailo8l" # 要使用的 Hailo 硬件架构,这里是 Hailo-8L
onnx_model_name = "yolov8n" # 模型的名字
onnx_path = "yolov8n.onnx" # 模型的路径
hailo_model_har_path = f"{onnx_model_name}_hailo_model.har" # 转换后模型的保存路径
hailo_quantized_har_path = f"{onnx_model_name}_hailo_quantized_model.har" # 量化后模型的保存路径
hailo_model_hef_path = f"{onnx_model_name}.hef" # 编译后模型的保存路径
images_path = "data/images" # 数据集图像路径 # 将 onnx 模型转为 har
runner = ClientRunner(hw_arch=chosen_hw_arch)
hn, npz = runner.translate_onnx_model(model=onnx_path, net_name=onnx_model_name, start_node_names=["images"], end_node_names=["/model.22/cv2.0/cv2.0.2/Conv", "/model.22/cv3.0/cv3.0.2/Conv", "/model.22/cv2.1/cv2.1.2/Conv", "/model.22/cv3.1/cv3.1.2/Conv", "/model.22/cv2.2/cv2.2.2/Conv", "/model.22/cv3.2/cv3.2.2/Conv"], net_input_shapes={"images": [1, 3, input_size, input_size]})
runner.save_har(hailo_model_har_path) # 校准数据集准备
images_list = [img_name for img_name in os.listdir(images_path) if os.path.splitext(img_name)[1] in [".jpg", ".png", "bmp"]][:1500] # 获取图像名称列表
calib_dataset = np.zeros((len(images_list), input_size, input_size, 3)) # 初始化 numpy 数组
for idx, img_name in enumerate(sorted(images_list)):
img = cv2.imread(os.path.join(images_path, img_name))
resized = cv2.resize(img, (input_size, input_size)) # 调整原始图像的尺寸为模型输入的尺寸
calib_dataset[idx,:,:,:]=np.array(resized) # 量化模型
runner = ClientRunner(har=hailo_model_har_path)
alls_lines = [
'model_optimization_flavor(optimization_level=1, compression_level=2)',
'resources_param(max_control_utilization=0.6, max_compute_utilization=0.6, max_memory_utilization=0.6)',
'performance_param(fps=5)'
]
runner.load_model_script('\n'.join(alls_lines))
runner.optimize(calib_dataset)
runner.save_har(hailo_quantized_har_path) # 编译为 hef
runner = ClientRunner(har=hailo_quantized_har_path)
compiled_hef = runner.compile()
with open(hailo_model_hef_path, "wb") as f:
f.write(compiled_hef)

参考

  1. Supported operators - Hailo Community:https://community.hailo.ai/t/supported-operators/5046/2
  2. hailo_model_zoo - GitHub:https://github.com/hailo-ai/hailo_model_zoo/tree/master/hailo_model_zoo/cfg/networks
  3. Dataflow Compiler v3.29.0:https://hailo.ai/developer-zone/documentation/dataflow-compiler-v3-29-0

张高兴的 Raspberry Pi AI 开发指南:(三)将自定义模型编译为 Hailo NPU 的 .hef 模型的更多相关文章

  1. [Lua游戏AI开发指南] 笔记零 - 框架搭建

    一.图书详情 <Lua游戏AI开发指南>,原作名: Learning Game AI Programming with Lua. 豆瓣:https://book.douban.com/su ...

  2. Raspberry PI 2上的802.11ac网卡驱动编译

    Raspberry PI 2上的802.11ac网卡驱动编译 最近在树莓派2上折腾视频,用来做FPV,但是发现2.4G的控会严重干扰2.4G WIFI,在开控的时候我的台式机+外置USB网卡都频频掉线 ...

  3. ReadHub项目Kotlin版开发指南(三、MVP架构)

    ReadHub项目Kotlin版转换指南(一.环境搭建) ReadHub项目Kotlin版转换指南(二.数据库和网络请求) ReadHub项目Kotlin版转换指南(三.MVP架构) Android ...

  4. 树莓派(raspberry pi)系统开发

    [树莓派(raspberry pi)] 01.在linux环境下给树莓派安装系统及入门各种资料 [树莓派(raspberry pi)] 02.PI3安装openCV开发环境做图像识别(详细版) 出处: ...

  5. 利用raspberry pi搭建typecho笔记(三) typecho nginx sqlite FAQ

    前言 这是一个汇总文,用来总结我在整个配置过程中遇到的各种问题.因为我在解决这些问题的过程中发现,typecho被部署在这种需要完全自己配置的平台上的情况是比较少的,相关的资料也比较少,所以我的解决过 ...

  6. ASP.NET Web API实现微信公众平台开发(三)自定义菜单

    承接之前的流程,在完成服务器绑定和获取access_token之后,本文主要讲述如何实现微信自定义菜单. 官方示例效果 开始之前 .自定义菜单最多包括3个一级菜单,每个一级菜单最多包含5个二级菜单. ...

  7. asp.net微信开发第三篇----自定义会话管理

    和微信用户的沟通少不了,总觉得看起来微信官网后台管理中的会话回复消息有点呆板,所以我这里就自定义了一个会话管理功能,最终效果图如下: 因为我试使用富文本文件CKEDITOR来进行编写,你看到稳中可能会 ...

  8. 2014年基于Raspberry Pi的5大项目

    2014年基于Raspberry Pi的5大项目   Raspberry Pi(即树莓派)是一款基于Linux系统(Debian.ArchLinux)的单板机计算机,它只有一张信用卡大小,可用于电子表 ...

  9. 树莓派(Raspberry Pi)上手小记

    引言 本日志中有不少软广告,博主并没有收他们任何好处,完全是给想入手的小伙伴们指条路而已.不喜勿看,不喜勿闻,不喜勿喷. 介绍 之前两三个月突然听说了这么个东西,也没有留意,某天突然在一个微信公众号上 ...

  10. 2019 年在 Raspberry Pi 「树莓派」上运行的 10 个操作系统推荐

    原文:2019 年在 Raspberry Pi 「树莓派」上运行的 10 个操作系统推荐 image Raspberry Pi** 是一款基于 ARM 的单板计算机,默认运行一款称为 Raspbian ...

随机推荐

  1. CSS – 实战 Spacing & Layout

    前言 这篇想整理一下在网页开发中, Spacing (间距) 和 Layout 排版是如果被处理的. Spacing 介绍 东西密密麻麻会给人一种很恐怖的感觉. 只要加上一点空间 (间距), 整体感觉 ...

  2. Flutter 因你更优秀 | 2021 第一季度开发者调研

    Flutter 终于在新的一年迎来了 2.0 版本,这是继 Dart 健全的空安全 Beta 版,以及 在测试方面取得重大进展 之后的一个全新的里程碑.在迈入这个新版本的当下,我们也已经准备好为大家带 ...

  3. MySQL笔记--数据库定时备份与恢复

    利用crontab定时.利用mysqldump备份 编写sh启动脚本时记得赋予执行权限(x) 如果没有mysqldump命令执行,基于centos7 yum -y install mysql-clie ...

  4. WeiXin.Export.20211230

    C# 在PC上的通过蓝牙(bluetooth)发送数据到手机 将.net framework 4 部署在docker中的全过程(支持4.0 到 4.8,3.5应该也可以) .Net Core Http ...

  5. vant 2 的 toast

    因为toast使用的场景比较频繁,所以在 注册使用 Toast 的时候,直接在Vue实列的原型上添加了toast方便我们使用 : 格式:this.$toast.fail()      this.$to ...

  6. c#传统读取配置文件

    using Microsoft.Extensions.Configuration; using Microsoft.Extensions.Configuration.Json; namespace C ...

  7. 不要慌,FastGPT 告诉我这是技术性调整,利好大 A!

    一觉醒来,股市又变天了,到处一片哀嚎,我看了下前几天牛市的赚钱名单,咱们公众号的粉丝没有一个在里面,说实话很失望,希望大家多做些有意义的事情,而不是整天虚度光阴.一个个平时看着都挺厉害,也没赚到钱,我 ...

  8. AJE润色优惠,35%的优惠券

    AJE润色 折扣35%的优惠,如下所示 全网目前只有如下优惠 USTCJC扣10% DOCTOR22扣15%,AJENEW22扣260 10%的可以和15%的叠加成-25% 10的也可以和260叠加成 ...

  9. 一、Tomcat基础知识与运行原理

    本章节为介绍如何安装Tomcat工具以及其主要架构知识概念,深入浅出让新人玩家理解为什么选择该容器以及该容器的优点 web服务器 概念 服务器:安装了服务器软件的计算机 服务器软件:接收用户的请求,处 ...

  10. MySQL数据的导入

    我们在帖子MySQL数据的导出 - brucexia - 博客园 (cnblogs.com)中讲了MySQL数据的导出,本文讲讲解MySQL数据的导入. MySQL数据的导入包括使用LOAD DATA ...