在上一篇博客中,探讨了如何使用 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. 我对互联网和网站运作的理解 – Domain, DNS, Hosting 介绍

    前言 一直没有写过关于互联网和网站运作的原理相关文章. 这篇就稍微介绍一下它们. 1. 电脑文件 网站是由许多网页组成的, 网页就是电脑里的 file. extension 是 .html 类似于 . ...

  2. CSS – border-radius (Rounded Corners)

    前言 之前的文章 CSS – W3Schools 学习笔记 (3), 这篇独立出来写, 作为整理. 参考: Youtube – Advanced CSS Border-Radius Tutorial ...

  3. Maven高级——属性

    属性 自定义属性 定义属性 <!-- 定义属性--> <properties> <spring.version>5.2.10.RELEASE</spring. ...

  4. Python技术书示例代码相关内容

    二维码用微信扫,按提示填写你的邮箱,转到电脑上打开邮箱下载.清华国企网盘,比较快速.安全.放心下载. 百度网盘链接 用Edge/Chrome浏览器下载. <Python自动化运维快速入门> ...

  5. Java日期时间API系列17-----Jdk8中java.time包中的新的日期时间API类,java日期计算4,2个日期对比,获取相差年月日部分属性和相差总的天时分秒毫秒纳秒等

    通过Java日期时间API系列9-----Jdk8中java.time包中的新的日期时间API类的Period和Duration的区别 ,可以看出java8设计非常好,新增了Period和Durati ...

  6. 深度学习 - Torch-TensorRT 推理加速

    深度学习 - Torch-TensorRT 推理加速 Torch-TensorRT 作为 TorchScript 的扩展. 它优化并执行兼容的子图,让 PyTorch 执行剩余的图. PyTorch ...

  7. 墨天轮国产数据库沙龙 | 张玮绚:TDengine,高性能、分布式、支持SQL的时序数据库

    分享嘉宾:张玮绚(Wade Zhang)北京涛思数据科技有限公司(TDengine)研发VP 整理:墨天轮 导读 TDengine 是一款高性能.分布式.支持 SQL 的时序数据库,让大量设备.数据采 ...

  8. 云原生周刊:Istio 加入 Phippy 家族 | 2024.3.18

    开源项目推荐 ko "ko" 是一个用于构建和部署 Go 应用程序的简单.快速的容器镜像构建工具.它适用于那些镜像中只包含单个 Go 应用程序且没有或很少依赖于操作系统基础镜像的情 ...

  9. AI五子棋_10 更多的算法探索

    AI五子棋 第十步 恭喜你到达第十步! 你已经完成了一个AI的设计,下面就需要发动你的智慧让你的机器大脑变得更聪明了! 我们的征途是星辰大海! 更多资料可以参考这个列表 https://gomocup ...

  10. 什么是SCI, SCIE, JCR和影响因子(IF)?

    SCI(Scientific Citation Index):是美国科学信息研究所(ISI)编辑出版的引文索引类刊物,创刊于1964年.分印刷版.光盘版和联机板等载体.印刷版.光盘版从全球数万种期刊中 ...