模型嵌入式部署
背景
自从深度学习大规模应用以来,其中一个应用方向就是将深度学习视觉算法部署到嵌入式平台上,使用NPU推理。虽然已经做了很久的模型部署,但一直都是在公司默默耕耘,为了发展一下自己“边缘部署专家”这个个人品牌,打算补一下模型部署的相关经验分享,从总结过往的工作经验开始,从我最熟悉的嵌入式部署开始。
嵌入式部署一般分为几个部分:模型转换 -> 模型量化 -> 边缘端推理。这是算法部分部署的最基本组成。当然,展开的话还有很多值得深挖的地方,如模型转换实质上是深度学习编译过程,模型量化需要分析和解决量化误差的问题等等。本文先不作深入探究,先记录基本流程,更多经验留待后面文章研究。
这里以开发板VIM3(主控A311D)为例子(虽然很少人用,但这是我最熟悉的开发板),说明整个过程。
准备
在正式开始之前,需要做一些准备工作:
- 硬件平台的工具链。开发板配套资料中,一定有NPU相关的工具链。这里我用的公司从代理采购的芯片,所以使用了公版工具链。如果是VIM3,可以从https://github.com/khadas/aml_npu_sdk下载,参考https://docs.khadas.com/products/sbc/vim3/npu/npu-sdk
- 训练好的模型。模型一般由算法工程师提供,转成onnx格式,尽量根据文档手册选择onnx opset版本,一般是opset v11较为常见。如果没有算法工程师,那就自己训练一个(误)。也可以https://github.com/onnx/models下载一个,不过可能要解决的问题比较多。我这里用的自己以前积累的模型。
- 50200张图片样本。模型的输入图片,用于量化校准。一般推荐50200张涵盖主要应用场景的样本即可。
模型转换
如前面背景所述,模型转换解决的是将模型在PC端可推理的格式,转换成硬件平台NPU可运行的格式,本质上是一个编译过程。但是因为编译本身需要维护完整的规则,一般这一步厂商都会选择提供完整工具实现。为了便于大家全局地理解这个过程,我这里列出A311D和TDA4VM的转换过程,实则大同小异。
A311D
在A311D中,工具链给出的转换脚本有三个:0_import_model.sh,1_quantize_model.sh,2_export_case_code.sh,分别对应模型转换成中间表达,模型量化参数计算,模型格式打包,后续有机会再一一说明其深入含义,本文先只把其当做是一系列工具运行。其中脚本例子如下:
#!/bin/bashNAME=test
ACUITY_PATH=../bin/pegasus=${ACUITY_PATH}pegasus
if [ ! -e "$pegasus" ]; thenpegasus=${ACUITY_PATH}pegasus.py
fi#Onnx
$pegasus import onnx\--model ${NAME}.onnx \--output-model ${NAME}.json \--output-data ${NAME}.data$pegasus generate inputmeta \--model ${NAME}.json \--input-meta-output ${NAME}_inputmeta.yml \--channel-mean-value "0 0 0 0.0039216" \--source-file dataset.txt
#!/bin/bashNAME=test
ACUITY_PATH=../bin/pegasus=${ACUITY_PATH}pegasus
if [ ! -e "$pegasus" ]; thenpegasus=${ACUITY_PATH}pegasus.py
fi#--quantizer asymmetric_affine --qtype uint8
#--quantizer dynamic_fixed_point --qtype int8(int16,note s905d3 not support int16 quantize)
# --quantizer perchannel_symmetric_affine --qtype int8(int16, note only T3(0xBE) can support perchannel quantize)
$pegasus quantize \--quantizer asymmetric_affine \--qtype uint8 \--rebuild \--with-input-meta ${NAME}_inputmeta.yml \--model ${NAME}.json \--model-data ${NAME}.data
#!/bin/bashNAME=test
ACUITY_PATH=../bin/pegasus=$ACUITY_PATH/pegasus
if [ ! -e "$pegasus" ]; thenpegasus=$ACUITY_PATH/pegasus.py
fi$pegasus export ovxlib\--model ${NAME}.json \--model-data ${NAME}.data \--model-quantize ${NAME}.quantize \--with-input-meta ${NAME}_inputmeta.yml \--dtype quantized \--optimize VIPNANOQI_PID0X99 \--viv-sdk ${ACUITY_PATH}vcmdtools \--pack-nbg-unifymkdir normal_case_demo
mv *.h *.c .project .cproject *.vcxproj *.export.data BUILD *.linux normal_case_demo
一般的工具链运行脚本由可执行程序和输入参数组成,在这里展示的A311D转换脚本中,pegasus是由芯片厂商提供的可执行程序,通过后面的参数指定运行的子功能及配置。
在使用时,需要配置相应的参数,其中较为重要的包括:
# 输入图片的归一化参数,包括均值和方差,注意要跟模型输入的通道对齐
--channel-mean-value # 校准数据集的文件列表
--source-file# 量化后数据类型
--qtype
配置完参数化,依次执行脚本:
./0_import_model.sh
./1_quantize_model.sh
./2_export_case_code.sh
即可得到最终转换后的模型。
注:如果运行有误,注意检查路径,保证脚本指定的相对路径下有pegasus。
TDA4VM
TDA4在模型转换这一步也是提供了模型转换应用,以文件形式传入参数:
./tidl_convert ./import_model.txt
其中import_model.txt的示例如下:
modelType = 2
inputNetFile = "test.onnx"
outputNetFile = "test_tidl_net.bin"
outputParamsFile = "test_tidl_io_"
inDataNorm = 1
inMean = 0 0 0
inScale = 0.0039215686275 0.0039215686275 0.0039215686275
resizeHeight = 320
resizeWidth = 320
inHeight = 320
inWidth = 320
inNumChannels = 3
inData = "quan_test.txt"
numParamBits = 8
转换完成后,会生成两个.bin文件,分别由outputNetFile和outputParamsFile指定生成路径,一个存储网络及参数,另一个存储网络的输入输出IO。
模型量化
量化过程
在嵌入式平台,基于成本考虑,多数平台支持的是8 bit运算,即INT8/UINT8,而PC端训练的模型一般是float32的,因此就存在模型量化这一步骤,完成模型从32位运算8位运算的转换。一般在部署工具链中,会集成到模型转换过程中。
量化实质上是一个线性映射过程,将模型的权重和特征图的从原始有效数值范围线性映射到[0,255](UINT8,如果是INT8,则是[-127,127]),进而计算出当前节点量化线性映射的zero_point和scale。
量化算法
量化过程需要计算模型的权重以及特征图的有效范围。权重有效范围由于训练后模型冻结可以直接统计,但特征图的有效范围不能直接得到,需要使用量化样本进行模型前向推理,然后根据得到的特征图进行统计计算。计算特征图有效数值范围的方法即量化算法,常用的量化算法有:
直接统计:直接统计模型推理得到的特征图范围的最大值(max)和最小值(min);
KL divergence:浮点数模型和定点数模型分别计算出特征,抽象成两个直方图分布,通过调整不同的阈值来更新浮点数和定点数的直方图分布,并根据 KL 散度衡量两个分布的相似性来确定量化范围的最大值和最小值。
由于实际的量化实践中,量化算法的影响远远不及量化样本的影响,因此量化算法一直以来没有太大的发展,不同平台使用的均是已知较为常见成熟的量化算法。
QAT
以上对量化过程的叙述主要是训练后量化,即PTQ(post-training quantization)。与此相对的,有量化感知训练,即QAT(Quantization Aware Training)。量化感知训练是指在模型训练过程中就插入量化和反量化节点,在训练过程中模拟了量化的损失,把量化造成的影响一并优化。本质上,QAT是一个模拟量化的过程,与硬件上的全整型计算依然是不同的,但QAT可以通过训练来把模型的数值范围调整至对量化友好的范围(如既存在截断误差又存在精度表达不足的情况下,只能通过训练来调整),所以对于量化而言,QAT是最有效的方法。
量化误差
以上量化过程对于不同平台及不同模型大同小异,工程实践过程中,实际上大部分时间是在解决量化误差问题。量化误差来源主要有两个,一是截断误差,即计算特征图取值范围时对范围进行舍弃带来的误差;二是取整误差,即进行线性映射时,映射到两个整数之间的数值进行取整带来的误差。
量化误差的表现多种多样,我会用另外的文章深入探讨这个问题和解决方案。
边缘端推理
得到转换好的模型后,还需要进行边缘端的推理代码编写,一般是C/C++接口,由平台SDK定义。尽管不同平台接口不同,一般可分为平台引擎初始化、模型加载(模型初始化)、模型推理、数据加载、获取输出、模型释放、平台引擎资源释放等过程,不同平台有微小差异。以下是我自己抽象的调用代码示例:
// 1. 初始化边缘引擎日志 && 实例化一个模型xaiedge::Log(NULL, XAIEDGE_DEBUG);Model edge_model;int status;// 2. 初始化边缘端引擎status = edge_model.init_engine(model_path.c_str(), json_path.c_str());if (status < 0){std::cout << "Init xaiedge engine failed!" << std::endl;return -1;}std::cout << "Init xaiedge engine from model file successfully!" << std::endl;// 3. 预处理图片数据 & 设置网络输入cv::Mat input_img = cv::imread(image_path, cv::IMREAD_COLOR); cv::cvtColor(input_img, input_img, cv::COLOR_BGR2RGB); // 留意是RGB还是BGRstatus = edge_model.preprocess(input_img.data, 0); // preprocess和set_input配套使用status = edge_model.set_input(NULL, 0); // 如果使用了默认的preprocess,set_input的传参为NULL;如果使用自定义的preprocess以及不使用preprocess,传入最终给到输入节点的数据地址if (status < 0){std::cout << "Preprocess failed!" << std::endl;return -1;}std::cout << "Preprocess successfully!" << std::endl;// 4. 网络推理int64_t start_us = getCurrentTimeUs();for (int i = 0; i < loop_num; ++i){status = edge_model.run();std::cout << "loop count: " << i << std::endl;}int64_t elapse_us = getCurrentTimeUs() - start_us;printf("Elapse Time = %.2fms\n", elapse_us / 1000.f / loop_num); std::cout << "run graph finish" << std::endl;// 5.释放资源edge_model.release_engine();
后续如有机会,将以具体平台做更详细的说明。````
附:一些工具的使用
Netron
Netron是开源的模型结构可视化工具,支持多种模型格式。当需要确认模型的结构,深入排查模型的结构问题时,Netron是必须的:

有在线版本和离线版本,可以直接在搜索引擎搜索使用。
相关文章:
模型嵌入式部署
背景 自从深度学习大规模应用以来,其中一个应用方向就是将深度学习视觉算法部署到嵌入式平台上,使用NPU推理。虽然已经做了很久的模型部署,但一直都是在公司默默耕耘,为了发展一下自己“边缘部署专家”这个个人品牌,打…...
Redlinux(2025.3.29)
1、将你的虚拟机的网卡模式设置为nat模式,给虚拟机网卡配置三个主机位分别为100、200、168的ip地址。(以nmtui命令为例) 2、测试你的虚拟机是否能够ping通网关和dns,如果不能请修改网关和dns的地址。 首先打开虚拟网络编辑器查看NAT设置里的网关IP&…...
uni-app项目运行在浏览器、微信开发者工具、mumu模拟器
一、安装HBuilder X 1、下载HBuilder X 官网网址:https://dcloud.io/hbuilderx.html 根据电脑系统下载对应的版本(我的电脑是Windows 10) 2.安装HBuilder X 直接将HBuilderX.4.61.2025040322-alpha.zip解压到自己想要存放的文件夹中 双击…...
2025-04-07 NO.3 Quest3 MR 配置
文章目录 1 MR 介绍1.1 透视1.2 场景理解1.3 空间设置 2 配置 MR 环境2.1 场景配置2.2 MR 配置 3 运行测试 配置环境: Windows 11Unity 6000.0.42f1Meta SDK v74.0.2Quest3 1 MR 介绍 1.1 透视 透视(Passthrough)是将应用的背景从虚拟的…...
抓wifi无线空口包之Macbook Pro抓包(一)
参考: 在MAC OS上进行Wi-Fi抓包和空中包分析_空口抓包和无线网卡抓包的区别-CSDN博客 WireShark中802.11帧的类型、子类型对照表_wireshark 怎么看disassociate帧和deauthenticate-CSDN博客 一、在macbook pro上,点击option 同时点击右上角wifi 功能&a…...
单元测试原则之——不要模拟值对象 (1)
1. 什么是值对象(Value Objects)? 值对象是指那些不可变且仅通过其属性(数据)来定义的对象。它们通常没有复杂的逻辑或行为,主要用于存储和传递数据。例如: ● 字符串(String) ● 数字(Integer, Double) ● 日期(LocalDate, Instant) ● 自定义的简单数据类(如…...
版本控制工具——SVN
目录 【版本控制系统】 【SVN概述】 【SVN基本使用】 【解决SVN拉取文件到本地后不显示绿色图标问题】 【版本控制系统】 版本控制系统(version control system)是一种用于管理文件变更的软件工具,主要用于记录文件的修改历史,…...
2022第十三届蓝桥杯大赛软件赛省赛C/C++ 大学 B 组(题解解析)
记录刷题的过程、感悟、题解。 希望能帮到,那些与我一同前行的,来自远方的朋友😉 大纲: 1、九进制转十进制-(解析)-简单的进制转化问题😄 2、顺子日期-(解析)-考察日期 3…...
Three.js 系列专题 5:加载外部模型
内容概述 Three.js 支持加载多种 3D 文件格式(如 GLTF、OBJ、FBX),这让开发者可以直接使用专业建模软件(如 Blender、Maya)创建的复杂模型。本专题将重点介绍 GLTF 格式的加载,并调整模型的位置和材质。 学习目标 理解常见 3D 文件格式及其特点。掌握使用 GLTFLoader 加…...
【EC200N-CN——Linux驱动移植】问题回顾
【EC200N-CN——Linux驱动移植】问题回顾 1)、开发回顾一、问题回顾与解决过程二、核心原理分析1. **USB设备识别的关键:VID/PID**2. **为什么之前不生成ttyUSB节点?**3. **为什么添加PID后就能生成节点?** 三、日志关键信息解读1…...
linux安装ollama
俩种方式都可 一、linux通过docker安装ollama镜像 1.下载安装ollama镜像 # 安装 Docker sudo yum install docker sudo systemctl start docker#docker查看所有容器 docker ps -a # 查看所有容器# docker查看指定容器 docker ps -a |grep ollama# 创建模型存储目录ÿ…...
构建k8s下Helm私有仓库与自定义Chart开发指南
#作者:程宏斌 文章目录 自定义helm模板1、开发自己的chare包2、调试chart3、安装chart 自定义helm模板 https://hub.helm.sh/ 1、开发自己的chare包 [rootmaster ~]# helm create mychare //创建一个名为mychare的chare包 [rootmaster ~]# tree -C mychare/ //以…...
【7】C#上位机---Modbus RTU 界面设计与封装
C#上位机---Modbus通讯 1 Modbus RTU 通讯1.1 RS485串口与串行通信(Serial Communications)1.2 Modbus RTU协议1.3 Modbus RTU主从模式1.4 Modbus 主从站模拟调试2 Modbus RTU 界面设计与封装2.1 温度控件的类属性2.2 C#封装Modbus实现通讯2.3 C#封装Modbus TRU通用类2.4 上位…...
【JVM】question
问题 JVM线程是用户态还是内核态 java线程在jdk1.2之前,是基于名为“绿色线程”的用户线程实现的,这导致绿色线程只能同主线程共享CPU分片,从而无法利用多核CPU的优势。 由于绿色线程和原生线程比起来在使用时有一些限制, jdk1.2…...
Node.js 中处理 Excel 文件的最佳实践
在现代应用开发中,Excel 文件仍然是数据交换和存储的重要格式之一。在 Node.js 环境中,处理 Excel 文件的需求日益增加。本文将介绍如何在 Node.js 中高效地处理 Excel 文件,涵盖工具选择、基本操作和最佳实践。 1. 选择合适的库 在 Node.js…...
【嵌入式学习6】多任务版TCP服务器
目录 如何实现: 客户端1.0版本: 服务端: 客户端2.0版本: thread.join() 是一个线程同步方法,用于主线程等待子线程完成。当你调用 thread.join() 时,主线程会阻塞,直到调用 join() 的子线程…...
每天认识一个设计模式-外观模式:化繁为简的接口魔法
一、前言 在设计模式中,结构型设计模式处理类或对象组合,可助力构建灵活、可维护软件结构。此前探讨过组合模式(将对象组合成树形结构,统一处理单个与组合对象,如文件系统管理)和装饰器模式(动…...
VLAN(虚拟局域网)
一、vlan概述 VLAN(virtual local area network)是一种通过逻辑方式划分网络的技术,允许将一个物理网络划分为多个独立的虚拟网络。每一个vlan是一个广播域,不同vlan之间的通信需要通过路由器或三层交换机 [!注意] vlan是交换机独有的技术,P…...
Transformers without Normalization论文翻译
论文信息: 作者:Jiachen Zhu, Xinlei Chen, Kaiming He, Yann LeCun, Zhuang Liu 论文地址:arxiv.org/pdf/2503.10622 代码仓库:jiachenzhu/DyT: Code release for DynamicTanh (DyT) 摘要 归一化层在现代神经网络中无处不在…...
题目练习之set的奇妙使用
♥♥♥~~~~~~欢迎光临知星小度博客空间~~~~~~♥♥♥ ♥♥♥零星地变得优秀~也能拼凑出星河~♥♥♥ ♥♥♥我们一起努力成为更好的自己~♥♥♥ ♥♥♥如果这一篇博客对你有帮助~别忘了点赞分享哦~♥♥♥ ♥♥♥如果有什么问题可以评论区留言或者私信我哦~♥♥♥ ✨✨✨✨✨✨ 个…...
负载均衡是什么,Kubernetes如何自动实现负载均衡
负载均衡是什么? 负载均衡(Load Balancing) 是一种网络技术,用于将网络流量(如 HTTP 请求、TCP 连接等)分发到多个服务器或服务实例上,以避免单个服务器过载,提高系统的可用性、可扩…...
网站提示“不安全“怎么办?原因分析与解决方法
引言:为什么浏览器会提示网站"不安全"? 当您访问某些网站时,浏览器可能会显示"不安全"警告。这通常意味着该网站存在安全风险,可能影响您的隐私或数据安全。本文将介绍常见原因及解决方法,帮助您…...
如何利用AI智能生成PPT,提升工作效率与创意表现
如何利用AI智能生成PPT,提升工作效率与创意表现!在这个信息爆炸的时代,制作一份既专业又富有创意的PPT,已经不再是一个简单的任务。尤其是对于每天都需要做报告、做展示的职场人士来说,PPT的质量直接影响着工作效率和个…...
【11】Redis快速安装与Golang实战指南
文章目录 1 Redis 基础与安装部署1.1 Redis 核心特性解析1.2 Docker Compose 快速部署1.3 Redis 本地快速部署 2 Golang 与 Redis 集成实战2.1 环境准备与依赖安装2.2 核心操作与数据结构实践2.2.1 基础键值操作2.2.2 哈希结构存储用户信息 3 生产级应用场景实战3.1 分布式锁实…...
【数据结构】图论存储革新:十字链表双链设计高效解决有向图入度查询难题
十字链表 导读一、邻接表的优缺点二、十字链表2.1 结点结构2.2 原理解释2.2.1 顶点表2.2.2 边结点2.2.3 十字链表 三、存储结构四、算法评价4.1 时间复杂度4.2 空间复杂度 五、优势与劣势5.1 优势5.2 劣势5.3 特点 结语 导读 大家好,很高兴又和大家见面啦ÿ…...
聊一聊没有接口文档时如何开展测试
目录 一、前期准备与信息收集 二、使用抓包工具分析接口 三、逆向工程构造测试用例 四、安全测试 五、 模糊测试(Fuzz Testing) 六、记录并维护发现的接口信息 七、 推动团队规范流程 其它注意事项 在我们进行接口测试时,总会遇到各种…...
.net6 中实现邮件发送
一、开启邮箱服务 先要开启邮箱的 SMTP 服务,获取授权码,在实现代码发送邮件中充当邮箱密码用。 在邮箱的 设置 > 账号 > POP3/IMAP/SMTP/Exchange/CardDAV/CalDAV服务中,把 SMTP 服务开启,获取授权码。 二、安装库 安装 …...
vector复制耗时
CPP中的vector对象在传参给子函数时,如果直接传参,会造成复制给形参的额外耗时 如何解决这个问题呢? 这样定义局部函数 const vector <int>&vec可以保证传递vector对象时使用地址传递,并且使用const保证vector不被改变…...
MySQL 数据库操作指南:从数据库创建到数据操作
关键词:MySQL;数据库操作;DDL;DML 一、引言 MySQL 作为广泛应用的关系型数据库管理系统,对于开发人员和数据库管理员而言,熟练掌握其操作至关重要。本文章通过一系列 SQL 示例,详细阐述 MySQL…...
【Linux】命令和权限
目录: 一、shell命令及运行原理 (一)什么是外壳 (二)为什么要有外壳 (三)外壳怎么工作的 二、Linux权限的概念 (一)Linux的文件类型 (二)L…...
