在上一篇博客中,探讨了如何使用 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. Vs Code, Visual Studio 2022, Angular and Live Server Running Through Https and IP Address

    前言 之前就写过 angular cli, vs code liveserver, vs 2019 iis express 10, vs code kestrel 使用 https + ip. 但写的 ...

  2. Azure – Azure Active Directory

    前言 虽然它好像是快过时了, 但目前还得用到. 先不研究新的先. Azure 的 service 如果要通过 API 调用的话, 就需要 Azure Active Directory (Azure A ...

  3. [TK] 颜色

    谴责这道题发明在线莫队的人,简直就是异端,还好我给在线莫队卡了,支持正义制裁 题意简述 给定序列,设 \(f(l,r,x)\) 表示 \(x\) 在 \([l,r]\) 内的出现次数,对给定 \(l, ...

  4. USB协议详解第2讲(协议核心学习要点)

    USB协议详解第2讲(协议核心学习要点) 看了这么多概念,想必大家会问"我要学会USB协议,并且会编程,我具体要学习那些有关的内容?",这一篇我们将会讲解在学习USB协议中务必要掌 ...

  5. dotnet实现多态的三种方法

    虚方法 virual 抽象方法 abstract 不能 new  不带方法体: 接口 Interface

  6. Promise 有几种状态,什么时候会进入catch?

    Promise 有几种状态 三个状态:pending.fulfilled.reject 两个过程:padding -> fulfilled.padding -> rejected Prom ...

  7. MidJourney新手攻略

    目录 MidJourney简介 MidJourney的使用 1. 加入Discord 2. 选择一个频道 3. 使用/imagine来输入提示 4. 等待一分钟左右,会输出四张图 5. 查看结果 Re ...

  8. vue打包后,添加入spring boot下,访问不到字体的BUG

    主要报错:OTS parsing error: incorrect file size in WOFF header OTS parsing error: incorrect entrySelecto ...

  9. 10款每个Web 开发人员都应该知道的JavaScript 插件

    那些从事 Web 开发工作的人肯定会注意到 JavaScript 的多功能性.与 ASP 或 PHP 不同,这种客户端编程语言为开发人员提供了多种可应用于项目的功能. 如果我们正在处理数据并希望丰富网 ...

  10. direasch目录扫描

    direasch目录扫描工具 安装: 1.github源码下载解压 使用 git 安装:(推荐git clone https://github.com/maurosoria/dirsearch.git ...