
一、端侧模型供给的链路全景
1.1 从"跑得通"到"模型从哪来"
第 03 讲我们解决了一个很硬的问题:同一份代码,怎么在 SNPE、QNN、RKNN、TFLite 之间自由切换,怎么在犀牛派 A1 和 X1 之间几乎免改迁移。那时候我们默认手里已经有一个"能在高通 NPU 上跑"的模型。但真实的工程流程里,模型并不是凭空出现的------你真正面对的,往往是一个从 PyTorch 训出来的 .pt、一个从别处导出的 .onnx,甚至一个别人开源但从未针对边缘优化过的浮点模型。
所以这一讲要补上链条最前端的那一段:模型从哪来,又怎么变成高通 NPU 能高效运行的格式。这件事做不好,后面再漂亮的多后端迁移、再丝滑的流水线,都是空中楼阁。
把视野拉到整个边缘 AI 项目生命周期来看,"模型供给"卡在中间偏前的位置:选型搭环境之后、单点推理之前。它的产出(一个量化好、适配好、版本对得上的模型文件)是后面每一讲能够动手的前提。也正是因为这个环节承前启后,我会反复强调它与第 03 讲"QNN 版本一致"那条纪律的关联------转换时选的版本,决定了板子上要装哪个 aidlite-qnn{ver}。
1.2 一条完整的模型供给链路
一个模型从"实验室里的浮点权重"变成"板子上跑得欢的量化模型",中间要过几道关,我把它们串成一条链路:
#mermaid-svg-1CL766EhhKpOzYlS{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;fill:#333;}@keyframes edge-animation-frame{from{stroke-dashoffset:0;}}@keyframes dash{to{stroke-dashoffset:0;}}#mermaid-svg-1CL766EhhKpOzYlS .edge-animation-slow{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 50s linear infinite;stroke-linecap:round;}#mermaid-svg-1CL766EhhKpOzYlS .edge-animation-fast{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 20s linear infinite;stroke-linecap:round;}#mermaid-svg-1CL766EhhKpOzYlS .error-icon{fill:#552222;}#mermaid-svg-1CL766EhhKpOzYlS .error-text{fill:#552222;stroke:#552222;}#mermaid-svg-1CL766EhhKpOzYlS .edge-thickness-normal{stroke-width:1px;}#mermaid-svg-1CL766EhhKpOzYlS .edge-thickness-thick{stroke-width:3.5px;}#mermaid-svg-1CL766EhhKpOzYlS .edge-pattern-solid{stroke-dasharray:0;}#mermaid-svg-1CL766EhhKpOzYlS .edge-thickness-invisible{stroke-width:0;fill:none;}#mermaid-svg-1CL766EhhKpOzYlS .edge-pattern-dashed{stroke-dasharray:3;}#mermaid-svg-1CL766EhhKpOzYlS .edge-pattern-dotted{stroke-dasharray:2;}#mermaid-svg-1CL766EhhKpOzYlS .marker{fill:#333333;stroke:#333333;}#mermaid-svg-1CL766EhhKpOzYlS .marker.cross{stroke:#333333;}#mermaid-svg-1CL766EhhKpOzYlS svg{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;}#mermaid-svg-1CL766EhhKpOzYlS p{margin:0;}#mermaid-svg-1CL766EhhKpOzYlS .label{font-family:"trebuchet ms",verdana,arial,sans-serif;color:#333;}#mermaid-svg-1CL766EhhKpOzYlS .cluster-label text{fill:#333;}#mermaid-svg-1CL766EhhKpOzYlS .cluster-label span{color:#333;}#mermaid-svg-1CL766EhhKpOzYlS .cluster-label span p{background-color:transparent;}#mermaid-svg-1CL766EhhKpOzYlS .label text,#mermaid-svg-1CL766EhhKpOzYlS span{fill:#333;color:#333;}#mermaid-svg-1CL766EhhKpOzYlS .node rect,#mermaid-svg-1CL766EhhKpOzYlS .node circle,#mermaid-svg-1CL766EhhKpOzYlS .node ellipse,#mermaid-svg-1CL766EhhKpOzYlS .node polygon,#mermaid-svg-1CL766EhhKpOzYlS .node path{fill:#ECECFF;stroke:#9370DB;stroke-width:1px;}#mermaid-svg-1CL766EhhKpOzYlS .rough-node .label text,#mermaid-svg-1CL766EhhKpOzYlS .node .label text,#mermaid-svg-1CL766EhhKpOzYlS .image-shape .label,#mermaid-svg-1CL766EhhKpOzYlS .icon-shape .label{text-anchor:middle;}#mermaid-svg-1CL766EhhKpOzYlS .node .katex path{fill:#000;stroke:#000;stroke-width:1px;}#mermaid-svg-1CL766EhhKpOzYlS .rough-node .label,#mermaid-svg-1CL766EhhKpOzYlS .node .label,#mermaid-svg-1CL766EhhKpOzYlS .image-shape .label,#mermaid-svg-1CL766EhhKpOzYlS .icon-shape .label{text-align:center;}#mermaid-svg-1CL766EhhKpOzYlS .node.clickable{cursor:pointer;}#mermaid-svg-1CL766EhhKpOzYlS .root .anchor path{fill:#333333!important;stroke-width:0;stroke:#333333;}#mermaid-svg-1CL766EhhKpOzYlS .arrowheadPath{fill:#333333;}#mermaid-svg-1CL766EhhKpOzYlS .edgePath .path{stroke:#333333;stroke-width:2.0px;}#mermaid-svg-1CL766EhhKpOzYlS .flowchart-link{stroke:#333333;fill:none;}#mermaid-svg-1CL766EhhKpOzYlS .edgeLabel{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-1CL766EhhKpOzYlS .edgeLabel p{background-color:rgba(232,232,232, 0.8);}#mermaid-svg-1CL766EhhKpOzYlS .edgeLabel rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-1CL766EhhKpOzYlS .labelBkg{background-color:rgba(232, 232, 232, 0.5);}#mermaid-svg-1CL766EhhKpOzYlS .cluster rect{fill:#ffffde;stroke:#aaaa33;stroke-width:1px;}#mermaid-svg-1CL766EhhKpOzYlS .cluster text{fill:#333;}#mermaid-svg-1CL766EhhKpOzYlS .cluster span{color:#333;}#mermaid-svg-1CL766EhhKpOzYlS div.mermaidTooltip{position:absolute;text-align:center;max-width:200px;padding:2px;font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:12px;background:hsl(80, 100%, 96.2745098039%);border:1px solid #aaaa33;border-radius:2px;pointer-events:none;z-index:100;}#mermaid-svg-1CL766EhhKpOzYlS .flowchartTitleText{text-anchor:middle;font-size:18px;fill:#333;}#mermaid-svg-1CL766EhhKpOzYlS rect.text{fill:none;stroke-width:0;}#mermaid-svg-1CL766EhhKpOzYlS .icon-shape,#mermaid-svg-1CL766EhhKpOzYlS .image-shape{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-1CL766EhhKpOzYlS .icon-shape p,#mermaid-svg-1CL766EhhKpOzYlS .image-shape p{background-color:rgba(232,232,232, 0.8);padding:2px;}#mermaid-svg-1CL766EhhKpOzYlS .icon-shape .label rect,#mermaid-svg-1CL766EhhKpOzYlS .image-shape .label rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-1CL766EhhKpOzYlS .label-icon{display:inline-block;height:1em;overflow:visible;vertical-align:-0.125em;}#mermaid-svg-1CL766EhhKpOzYlS .node .label-icon path{fill:currentColor;stroke:revert;stroke-width:revert;}#mermaid-svg-1CL766EhhKpOzYlS :root{--mermaid-font-family:"trebuchet ms",verdana,arial,sans-serif;} 训练产出: .pt/.onnx
导出通用格式: ONNX/TFLite
AIMO 转换+量化
高通 NPU 格式: QNN/.amf/.bin
Model Farm 现成模型
AidLite 加载推理
你的业务应用
这条链路有两条入口:一条是你自己的模型,走"导出 → AIMO 转换量化";另一条是社区和官方沉淀下来的成熟模型,直接从 Model Farm(模型广场)取。两条路最终都汇到 AidLite 的 build_interpreter,接回第 03 讲那套可参数化模板。
理解这条链路的价值在于:你不会再把"模型不对"当成"代码 bug"。模型供给环节出的问题(版本错配、量化崩精度、截断点填错)和推理环节出的问题(shape 填错、后端选错)是两类完全不同的故障,定位路线也完全不同。分清楚"病在模型"还是"病在推理",能省下大量无谓的联调时间。
1.3 AIMO 与 Model Farm 的分工
很多人初看会混淆:AIMO 和 Model Farm 不都是"搞模型的"吗,有什么区别?一句话区分:
- AIMO(AI Model Optimizer) 是"加工厂":你把原始模型喂进去,它帮你做格式转换、算子适配、量化压缩,产出能上 NPU 的成品。它是个主动加工过程,你要参与参数决策(选平台、选量化方式、给校准数据)。
- Model Farm(模型广场) 是"仓库":里面已经躺着大量针对高通平台优化适配过的现成模型,很多还带前后处理示例。你进去挑、下载、直接用,省去自己转换的功夫。它是个被动取用过程。
一个典型的项目节奏是:先用 Model Farm 找有没有现成的(省时间),没有再拿 AIMO 自己转(保灵活)。两者不是二选一,而是"现成优先、缺啥补啥"的互补关系。后面我会分别演示这两条路的接法。
1.4 本讲目标与硬件准备
读完本讲,你应该能做到三件事:第一,用 AIMO 把一个 ONNX 模型转成高通 QNN INT8 格式并下载;第二,从 Model Farm 取一个现成模型并确认它和板子版本对得上;第三,把上面任一产物接回第 03 讲的模板,在犀牛派 A1 上跑起来。
硬件准备与前面一致:完成环境搭建的犀牛派 A1(或 X1),aid-pkg installed 能看到 aidlite。AIMO 本身是云端/私有化平台,不挑板子;转换完成后回板子验证即可。本讲以犀牛派 A1 主演示,X1 给出预期差异;代码以 Python 为主。
二、AIMO 工作原理与两种部署形态
2.1 什么是 AIMO
AIMO 是阿加犀的 AI 模型优化平台,形态上是一个 Web 端交互式的模型优化工具。它把"模型转换 + 量化压缩 + 算子适配"这串原本要在命令行里敲一堆工具、配一堆环境的工作,收敛成一个图形化的任务流。你不需要在本地装一整套高通转换工具链,打开网页、上传模型、点几下配置,平台就在后端帮你把活干了。
这对新手极其友好:传统上把一个 ONNX 转成 QNN,要装 SNPE/QNN 转换工具、配 Python 环境、处理算子不支持的报错、再手动做量化校准------每一步都可能卡你半天。AIMO 把这些封装起来,你面对的只是"上传什么、转成什么、怎么量化"这几个高层决策。当然,封装不等于黑盒,理解它背后在做什么,会让你在出错时知道往哪查。
2.2 SaaS 与私有化 Docker 两种形态
AIMO 提供两种部署形态,对应的是两类数据敏感度的团队:
- SaaS 在线版:模型上传到阿加犀的云端平台处理,开箱即用、零部署,适合大多数开发者和中小团队。你的模型文件会离开本地,所以在上传前要想清楚知识产权边界。
- 私有化 Docker 版:平台能力打包成 Docker 镜像,部署在你自己的服务器或内网环境里,模型不出域。适合对数据合规、模型保密有硬要求的企业的量产项目。
两种形态在转换与量化的能力上是一致的,区别只在"模型数据落在哪"。如果是公司自研、尚未公开的模型,量产前建议走私有化;如果是开源模型或公开 benchmark,SaaS 版更省事。选型看清这条边界即可。
2.3 转换能力全景:从通用框架到 NPU 可执行
AIMO 的核心能力是"转格式"。它能把主流训练/推理框架产出的模型,转换成目标平台可执行的格式,覆盖 TFLite、ONNX、DLC、QNN 等多种输出。对高通平台而言,最关键的输出就是 QNN 相关的可执行格式------这正是 AidLite 在犀牛派上用 TYPE_QNN 后端加载的那一类。
更贴心的一点是,针对高通平台,AIMO 内置了额外的算子库支持。边缘模型里常有训练框架里的"冷门算子"在端侧运行时没有原生实现,AIMO 在这类场景提供了补充算子能力,减少你"转一半发现某个算子不支持"的尴尬。这部分与 AidLite 的 UDO(用户自定义算子)能力是配合使用的:转换时适配的算子,运行时由 AidLite 调度执行。
一句话:AIMO 把"能不能转过去"的问题,从"你本地环境配没配好"变成了"平台支持不支持这个算子",前者的不确定性被平台吃掉了一大半。
2.4 为什么转换还"顺手"做了优化
很多人以为模型转换只是"换了种文件格式",其实远不止。一次合格的端侧转换,通常顺带完成了两件优化:
第一是图优化(graph optimization):平台会在计算图层面做算子融合、常量折叠、冗余节点剪除,让推理时的计算图更紧凑。这一步不损失精度,纯赚性能。
第二是量化(quantization):把浮点权重和激活值压成 INT8 甚至 INT16,这是端侧 NPU 跑得快的根本原因(NPU 的算力峰值往往以 INT8 TOPS 计)。量化是有损的,所以才需要校准数据和合理的量化策略------这正是下一节要展开的核心。
理解了"转换 = 格式变化 + 图优化 + 量化"这个复合动作,你就不会再把它当成一个无足轻重的"另存为",而会认真对待它的每一个参数。
三、量化原理入门:让模型瘦下来、跑起来
3.1 浮点到 INT8:到底省了什么
端侧 NPU 之所以标称"12 TOPS""48 TOPS",那个数字几乎都是按 INT8 算力算的。如果一个模型是 FLOAT32 的,NPU 要么跑不满、要么干脆走 CPU/GPU 兜底。所以"转成 INT8"不是可选项,而是端侧部署的必答题。
量化本质上是把"用 32 位浮点数表示一个数值"改成"用 8 位整数表示一个数值"。带来的好处非常直接:
- 带宽减半再减半:数据从 32 位变 8 位,搬运量降到 1/4,这对端侧最贵的"内存搬运"环节帮助巨大;
- 计算更快:INT8 的乘加在 NPU 上远快于 FLOAT32;
- 模型更小:权重大小降到 1/4,对存储和加载都友好。
代价是精度损失:8 位能表达的数值分辨率远不如 32 位浮点,如果"怎么映射"没做好,模型的输出会系统性偏移。量化要解决的,就是"如何用最少的精度代价,换最大的速度收益"。
3.2 校准数据的作用:别让量化"瞎猜"
INT8 量化的核心是要知道"我的激活值大概落在什么范围",才能把浮点范围合理地映射到 8 位整数范围。如果映射范围定错了(比如实际激活值能到 10,你却按 0~1 去量化),大量信息会被截断或压扁,精度直接崩。
校准数据(calibration data) 就是用来"摸底"的:你给平台一小批(几十条到几百条,取决于模型)有代表性的真实输入样本,平台跑一遍前向,统计每层激活值的分布,从而确定每层的量化参数(scale 和 zero-point)。校准数据越贴近真实分布,量化越准。
这里有个常见误区:校准数据不是训练数据,不需要标签,也不需要多。它只要"有代表性"------覆盖你实际会遇到的输入形态。给一批全黑图去校准一个要识别彩色物体的模型,量化参数会严重偏离真实分布。这就是为什么后面"坑点"里把"校准数据不足/不具代表性"列为精度崩的头号原因。
3.3 CLE 是什么:通道级量化校准
CLE(Cross Layer Equalization,跨层均衡)是针对神经网络量化的一类经典优化技术。它的出发点很朴素:很多模型里相邻层之间存在"数值尺度不均衡",比如某一层权重特别大、下一层激活特别小,这种不均衡会让逐层独立量化时误差被放大。CLE 通过在数学等价的约束下,把这种不均衡在相邻层之间重新分配,让量化误差更小。
你可以把 CLE 理解为"在量化之前,先把模型内部的数值尺度抚平"。它不是所有模型都必需,但对某些激活分布跨度大、层间不均衡明显的模型,开启 CLE 往往能明显挽回量化精度。AIMO 在量化策略里提供这类选项,你不必自己实现 CLE,只要理解它"解决什么、什么时候该开"即可。
3.4 自动量化与自定义模式怎么选
AIMO 通常提供两类量化模式,对应两种能力水平的用户:
- 自动量化(Auto Quantization):平台根据模型结构和你给的校准数据,自动决定量化粒度、是否启用 CLE、选 INT8 还是 INT16。适合大多数场景,尤其是你一时说不清"该压多狠"的时候,先自动跑一版看效果。
- 自定义模式(Customize Mode):你手动指定量化方式、目标精度、逐层策略、截断点等。适合对模型了如指掌、自动量化效果不达标、需要精细控制精度的进阶用户。
我的建议是:先用自动量化跑通全链路、建立基线;发现精度不达标,再切自定义模式,针对性调整(比如某几层精度敏感就保 FLOAT16、其余压 INT8)。这和第 03 讲"先验证后优化"的工程哲学一脉相承------别一上来就追求最优,先把通路跑通。
3.5 对称量化与非对称量化:量化公式的两个流派
落地到具体实现,量化通常用一条线性公式把浮点映射到整数:r = S * (q - Z),其中 r 是浮点实数、q 是量化后的整数、S(scale)是缩放因子、Z(zero-point)是零点偏移。根据 Z 是否为零,分成两派:
- 对称量化(symmetric) :零点固定在 0(
Z=0),量化范围关于原点对称,公式简化为r = S * q。好处是实现简单、运算快;缺点是如果真实分布偏向一侧(比如 ReLU 之后全是正值),表达范围会浪费近一半。 - 非对称量化(asymmetric):零点可以不为零,能更贴合"只在一侧有值"的分布,充分利用 8 位整数的动态范围,对激活值量化常常更准。
你不需要记住公式去手算------AIMO 会根据校准数据自动决定每层用哪种、S 和 Z 取多少。但理解这两派,能让你看懂转换日志里的"量化方案"字段,也能在精度不达标时意识到"可能是这层的量化方式没选对",从而有的放矢地切自定义模式调整,而不是瞎试。
四、环境调研:在线入口与版本纪律
4.1 AIMO 在线平台入口
AIMO 的 SaaS 入口在阿加犀 AidLux 生态的官方站点内,打开后通常要先登录账号,进入"模型优化"或"模型转换"类工作台。具体的菜单命名会随平台版本迭代调整,但核心动作始终是"新建转换任务 → 上传 → 配置 → 执行 → 下载"。
提一句版本纪律:AIMO 转换时让你选的"目标 QNN 版本",必须和你在板子上 aid-pkg installed 看到的 AidLite QNN 版本对应。这一点第 03 讲强调过,但这里它是"源头"------版本错配的祸根,往往就是在这一步埋下的。每次转换,把"模型文件 → 所用 QNN 版本"记进项目文档,后面排查能省大劲。
4.2 Model Farm 模型广场入口
Model Farm(模型广场)是阿加犀面向高通平台聚集的大量已优化模型的集散地,地址是 https://aiot.aidlux.com/zh/models 。进入后能按任务类型(检测、分类、分割、姿态、大模型等)、芯片平台筛选。很多模型详情页会直接标注它适配的芯片、所用 QNN 版本、甚至附带前后处理参考代码。
这里要养成一个习惯:下载任何模型前,先看它的"适配平台与 QNN 版本"字段,确认和你的板子(A1 是 QCS6490、X1 是 QCS8550)以及板载 AidLite 版本对得上。广场里的模型虽已优化,但版本错配一样会加载失败------"现成"不等于"免检"。
4.3 再次强调:QNN 版本必须前后一致
把这条单独拎出来,是因为它是全系列最高频的坑,且横跨第 03、04 两讲。一个完整的版本链路是:
#mermaid-svg-rIM8lLTh7oy8nQeg{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;fill:#333;}@keyframes edge-animation-frame{from{stroke-dashoffset:0;}}@keyframes dash{to{stroke-dashoffset:0;}}#mermaid-svg-rIM8lLTh7oy8nQeg .edge-animation-slow{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 50s linear infinite;stroke-linecap:round;}#mermaid-svg-rIM8lLTh7oy8nQeg .edge-animation-fast{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 20s linear infinite;stroke-linecap:round;}#mermaid-svg-rIM8lLTh7oy8nQeg .error-icon{fill:#552222;}#mermaid-svg-rIM8lLTh7oy8nQeg .error-text{fill:#552222;stroke:#552222;}#mermaid-svg-rIM8lLTh7oy8nQeg .edge-thickness-normal{stroke-width:1px;}#mermaid-svg-rIM8lLTh7oy8nQeg .edge-thickness-thick{stroke-width:3.5px;}#mermaid-svg-rIM8lLTh7oy8nQeg .edge-pattern-solid{stroke-dasharray:0;}#mermaid-svg-rIM8lLTh7oy8nQeg .edge-thickness-invisible{stroke-width:0;fill:none;}#mermaid-svg-rIM8lLTh7oy8nQeg .edge-pattern-dashed{stroke-dasharray:3;}#mermaid-svg-rIM8lLTh7oy8nQeg .edge-pattern-dotted{stroke-dasharray:2;}#mermaid-svg-rIM8lLTh7oy8nQeg .marker{fill:#333333;stroke:#333333;}#mermaid-svg-rIM8lLTh7oy8nQeg .marker.cross{stroke:#333333;}#mermaid-svg-rIM8lLTh7oy8nQeg svg{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;}#mermaid-svg-rIM8lLTh7oy8nQeg p{margin:0;}#mermaid-svg-rIM8lLTh7oy8nQeg .label{font-family:"trebuchet ms",verdana,arial,sans-serif;color:#333;}#mermaid-svg-rIM8lLTh7oy8nQeg .cluster-label text{fill:#333;}#mermaid-svg-rIM8lLTh7oy8nQeg .cluster-label span{color:#333;}#mermaid-svg-rIM8lLTh7oy8nQeg .cluster-label span p{background-color:transparent;}#mermaid-svg-rIM8lLTh7oy8nQeg .label text,#mermaid-svg-rIM8lLTh7oy8nQeg span{fill:#333;color:#333;}#mermaid-svg-rIM8lLTh7oy8nQeg .node rect,#mermaid-svg-rIM8lLTh7oy8nQeg .node circle,#mermaid-svg-rIM8lLTh7oy8nQeg .node ellipse,#mermaid-svg-rIM8lLTh7oy8nQeg .node polygon,#mermaid-svg-rIM8lLTh7oy8nQeg .node path{fill:#ECECFF;stroke:#9370DB;stroke-width:1px;}#mermaid-svg-rIM8lLTh7oy8nQeg .rough-node .label text,#mermaid-svg-rIM8lLTh7oy8nQeg .node .label text,#mermaid-svg-rIM8lLTh7oy8nQeg .image-shape .label,#mermaid-svg-rIM8lLTh7oy8nQeg .icon-shape .label{text-anchor:middle;}#mermaid-svg-rIM8lLTh7oy8nQeg .node .katex path{fill:#000;stroke:#000;stroke-width:1px;}#mermaid-svg-rIM8lLTh7oy8nQeg .rough-node .label,#mermaid-svg-rIM8lLTh7oy8nQeg .node .label,#mermaid-svg-rIM8lLTh7oy8nQeg .image-shape .label,#mermaid-svg-rIM8lLTh7oy8nQeg .icon-shape .label{text-align:center;}#mermaid-svg-rIM8lLTh7oy8nQeg .node.clickable{cursor:pointer;}#mermaid-svg-rIM8lLTh7oy8nQeg .root .anchor path{fill:#333333!important;stroke-width:0;stroke:#333333;}#mermaid-svg-rIM8lLTh7oy8nQeg .arrowheadPath{fill:#333333;}#mermaid-svg-rIM8lLTh7oy8nQeg .edgePath .path{stroke:#333333;stroke-width:2.0px;}#mermaid-svg-rIM8lLTh7oy8nQeg .flowchart-link{stroke:#333333;fill:none;}#mermaid-svg-rIM8lLTh7oy8nQeg .edgeLabel{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-rIM8lLTh7oy8nQeg .edgeLabel p{background-color:rgba(232,232,232, 0.8);}#mermaid-svg-rIM8lLTh7oy8nQeg .edgeLabel rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-rIM8lLTh7oy8nQeg .labelBkg{background-color:rgba(232, 232, 232, 0.5);}#mermaid-svg-rIM8lLTh7oy8nQeg .cluster rect{fill:#ffffde;stroke:#aaaa33;stroke-width:1px;}#mermaid-svg-rIM8lLTh7oy8nQeg .cluster text{fill:#333;}#mermaid-svg-rIM8lLTh7oy8nQeg .cluster span{color:#333;}#mermaid-svg-rIM8lLTh7oy8nQeg div.mermaidTooltip{position:absolute;text-align:center;max-width:200px;padding:2px;font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:12px;background:hsl(80, 100%, 96.2745098039%);border:1px solid #aaaa33;border-radius:2px;pointer-events:none;z-index:100;}#mermaid-svg-rIM8lLTh7oy8nQeg .flowchartTitleText{text-anchor:middle;font-size:18px;fill:#333;}#mermaid-svg-rIM8lLTh7oy8nQeg rect.text{fill:none;stroke-width:0;}#mermaid-svg-rIM8lLTh7oy8nQeg .icon-shape,#mermaid-svg-rIM8lLTh7oy8nQeg .image-shape{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-rIM8lLTh7oy8nQeg .icon-shape p,#mermaid-svg-rIM8lLTh7oy8nQeg .image-shape p{background-color:rgba(232,232,232, 0.8);padding:2px;}#mermaid-svg-rIM8lLTh7oy8nQeg .icon-shape .label rect,#mermaid-svg-rIM8lLTh7oy8nQeg .image-shape .label rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-rIM8lLTh7oy8nQeg .label-icon{display:inline-block;height:1em;overflow:visible;vertical-align:-0.125em;}#mermaid-svg-rIM8lLTh7oy8nQeg .node .label-icon path{fill:currentColor;stroke:revert;stroke-width:revert;}#mermaid-svg-rIM8lLTh7oy8nQeg :root{--mermaid-font-family:"trebuchet ms",verdana,arial,sans-serif;} AIMO 转换时选 QNN2.31
产出 QNN2.31 的模型
板子装 aidlite-qnn231
AidLite TYPE_QNN 加载成功
AIMO 转换时选 QNN2.36
产出 QNN2.36 的模型
板子装 aidlite-qnn236
AidLite TYPE_QNN 加载成功
只要"转换版本"和"板载版本"是同一档,就能对上;跨档就会出问题。所以把版本当成模型文件的"身份证后缀"来对待,永远不过时。
五、操作步骤:AIMO 四步法
下面以把一个 YOLOv5s 的 ONNX 模型转成 QNN INT8 为例,演示 AIMO 的实战四步法。注意:具体的网页字段名以平台当前界面为准,这里讲的是不变的流程逻辑,你可以照着映射。
5.1 第一步:上传原始模型
在 AIMO 工作台新建一个转换任务,上传你的原始模型文件。对高通路线,常见起点是 ONNX(从 PyTorch 用 torch.onnx.export 导出)。上传时平台一般会识别模型结构、输入/输出节点,并给出初步信息。
几个要点:
- 确保导出的 ONNX 是"固定输入 shape"或明确动态维度,避免后面 shape 对不上;
- 记下模型的输入分辨率(如 640×640 或 320×320),后面配置和板端
set_model_properties都要用到; - 如果模型有后处理(如 YOLO 的解码)被一起导出,建议评估是否要把它留在图里------端侧常把后处理放 CPU 更灵活。
5.2 第二步:选择目标平台与后端
在任务配置里选择目标芯片平台与输出格式。对犀牛派系列,目标平台对应高通 QCS6490(A1)或 QCS8550(X1);输出格式选 QNN 相关格式(具体命名随平台,本质是 AidLite 能用 TYPE_QNN 加载的格式,常见为 .amf 或配套二进制)。
这里有个实操建议:如果当前主要在 A1 上验证,可以先选 QCS6490 目标;后续要上 X1,由于两者都是高通 NPU、AidLite 接口一致,很多时候同一份 QNN 模型两板通用(以平台说明和真机实测为准),但稳妥起见转换时也可以针对 X1 重新产出一版,确保 NPU backend 对齐。
5.3 第三步:设置量化与校准参数
这是决定"能不能跑快"和"跑得准不准"的关键一步:
- 量化精度:选 INT8(端侧主力)或 INT16(精度敏感、算力允许时)。多数检测模型 INT8 即可。
- 量化模式:自动量化入门;效果不佳再切自定义。
- 校准数据:上传一小批有代表性的真实图片(几十到几百张,覆盖你的典型场景)。平台会据此统计激活分布。
- 是否启用 CLE 等优化:按平台提供的选项,精度不达标时开启。
校准数据这一步最容易被"随便凑几张"糊弄。请务必用真实分布的数据------它直接关系到量化质量,后面精度崩了回来查,十有八九是这里。
5.4 第四步:转换并下载
确认配置后提交任务,平台执行"图优化 + 算子适配 + 量化",完成后提供下载。下载的通常是一组文件:模型本体(如 .amf 或 .bin)以及可能的配套说明/示例。
下载后,把文件拷到板子的 /home/aidlux/... 工作目录(Web 桌面只能接收这个目录的上传,第 01 讲提过)。同时把"模型 → QNN 版本 → 输入 shape"记进第 03 讲 4.4 那张对照表,做到可追溯。
5.5 用 Netron 看一眼转换结果
下载到本地后,强烈建议用 Netron(开源模型可视化工具)打开转换后的模型,确认:
- 输入节点名、输入 shape(NHWC 还是 NCHW)符合预期;
- 输出节点名和数量符合你的后处理约定(YOLO 通常是多尺度输出);
- 没有明显的"断头"或多余输出(如果转换时截断点填错,这里一眼能看出)。
这一步在本地电脑就能做,不耗板子资源,却能提前发现大量"上了板才暴露"的问题。把它当成转换后的"出厂质检"。
5.6 从 Model Farm 直接取现成模型
如果你要的模型在 Model Farm 里已经有了,流程就更短:筛选 → 找到适配你平台和版本的模型 → 下载 → 看详情页的适配说明与示例 → 拷到板子。很多 Model Farm 模型已经做好了量化与适配,下载即用的概率很高。
不过"下载即用"也有前提:详情页标注的 QNN 版本要和板载一致。若不一致,要么在板子上装对应版本,要么自己用 AIMO 重新转一版。别因为"现成的"就跳过版本核对------这条纪律对广场模型同样有效。
六、关键代码:把转换后的模型接回 AidLite
6.1 加载 .amf / .bin 量化模型
转换/下载得到的模型,最终都要回到 AidLite 加载。下面给出加载量化模型的最小片段,注意它和第 02、03 讲的骨架一致,只是模型文件变成了 AIMO 产出的 .amf:
python
#!/usr/bin/env python3
# -*- coding: utf-8 -*-
"""
加载 AIMO 转换产出的 QNN INT8 模型并推理(犀牛派 A1 / X1 通用)
"""
import aidlite
import numpy as np
def load_aimo_model(model_path, input_shape, output_shape):
# 1) 建 Model
model = aidlite.Model.create_instance(model_path)
if model is None:
raise RuntimeError("create model failed: %s" % model_path)
model.set_model_properties(
[input_shape], aidlite.DataType.TYPE_FLOAT32,
[output_shape], aidlite.DataType.TYPE_FLOAT32,
)
# 2) 建 Config:QNN + NPU + 量化
cfg = aidlite.Config.create_instance()
cfg.framework_type = aidlite.FrameworkType.TYPE_QNN
cfg.accelerate_type = aidlite.AccelerateType.TYPE_DSP # NPU
cfg.is_quantify_model = 1 # 量化模型
# 3) 建 Interpreter
interp = aidlite.InterpreterBuilder.build_interpreter_from_model_and_config(model, cfg)
if interp is None or interp.init() != 0 or interp.load_model() != 0:
raise RuntimeError("build/init/load failed")
return interp
注意 is_quantify_model = 1:AIMO 产出的是量化模型,这里必须置 1,否则 AidLite 会按浮点去解析,结果必然错乱。
6.2 接入第 03 讲的可参数化模板
第 03 讲我们留了一个 BACKENDS 字典和 build_interpreter 封装。AIMO 产出的模型,本质上就是往那个模板里"塞一个新的模型路径":
python
# 把第03讲的 BACKENDS 里的 qnn 项复用,只需换模型路径
interp = build_interpreter(
"yolov5s_int8.amf", # AIMO 转换产物
backend="qnn",
input_shapes=[[1, 320, 320, 3]],
output_shapes=[[1, 10, 10, 255], [1, 20, 20, 255], [1, 40, 40, 255]],
)
这就是第 03 讲那套模板真正的价值兑现:无论模型是你 AIMO 自己转的、还是 Model Farm 下的,只要框架是 QNN、后端是 NPU、shape 对得上,加载代码一行都不用改。模型供给环节的所有折腾,被收敛到"换一个文件路径"上。
6.3 YOLOv5s 转换前后推理对照
把转换前后的模型都接进模板,跑同一张图,做一个直观对照:
python
import time
import numpy as np
def bench_qnn(interp, prep_fn, img, warmup=10, repeat=50):
inp = prep_fn(img)
for _ in range(warmup): # 预热
interp.set_input_tensor(0, inp); interp.invoke()
t0 = time.perf_counter()
for _ in range(repeat):
interp.set_input_tensor(0, inp); interp.invoke()
_ = interp.get_output_tensor(0)
ms = (time.perf_counter() - t0) / repeat * 1000
return ms
# float_amf = 你转换前(或浮点参考)的模型;int8_amf = AIMO INT8 产物
# t_float = bench_qnn(interp_float, preprocess, img)
# t_int8 = bench_qnn(interp_int8, preprocess, img)
# print(f"浮点: {t_float:.1f} ms | INT8: {t_int8:.1f} ms")
预期是 INT8 显著快于浮点(NPU 跑满 INT8 算力),且检测结果框位置基本一致。若 INT8 明显慢于预期,先排查是不是 accelerate_type 没落到 NPU、或版本错配导致回退。
6.4 量化参数速查表
把本讲涉及的量化决策点收成一张速查表,方便你每次转换时对照:
| 决策项 | 选项 | 经验建议 |
|---|---|---|
| 量化精度 | INT8 / INT16 | 多数检测 INT8;精度敏感用 INT16 |
| 量化模式 | 自动 / 自定义 | 先自动建基线,不达标再自定义 |
| 校准数据量 | 几十~几百张 | 有代表性比数量多更重要 |
| CLE 等优化 | 开 / 关 | 激活分布跨度大、层间不均衡时开 |
| 目标平台 | QCS6490 / QCS8550 | 与板子一致;版本与板载对齐 |
| 输出格式 | QNN 系列(.amf/.bin) | 由 AidLite TYPE_QNN 加载 |
这张表不是银弹,但能让你在平台那一堆选项前不慌------知道每个旋钮"调的是啥"。
6.5 转换日志里该看什么
AIMO 转换完成后通常会给出一份日志或报告,里面有几项值得重点看:
- 算子支持情况:有没有算子落到 CPU 回退(fallback)。回退越多,NPU 收益越小;
- 量化误差估计:平台有时给出每层或整体的量化误差提示,帮助判断哪些层敏感;
- 输入输出摘要:确认 shape、数据类型与你的预期一致。
把这份日志和模型文件一起存档。将来"为什么这个模型在板上慢"或"为什么精度掉",日志往往是最早的线索。
七、坑点
7.1 校准数据太少或代表性不足,精度崩了
这是量化最经典的翻车。现象是 INT8 模型输出和浮点版差很多,检测框大量错漏。根因是校准数据没覆盖真实分布,量化参数定偏了。对策:换/补有代表性的校准数据,重新量化;精度仍不达标就切自定义模式,对敏感层保更高精度。
7.2 截断点 / 输出节点填错
转换时若指定了"截断点"或"输出节点",填错会导致模型图被切断在错误位置,输出张量完全对不上你的后处理。对策:用 Netron 打开转换后的模型(5.5 节)核对输出节点名与数量,必要时回到 AIMO 重设截断点。
7.3 又是 QNN 版本错配
老生常谈但必须再讲:AIMO 转换选的 QNN 版本,必须和板载 aidlite-qnn{ver} 一致。现象是加载失败或结果异常。对策:aid-pkg installed 核对,不对就装对应版本。这条和第 03 讲是同一根线,源头在转换、爆发在板端。
7.4 输入分辨率与 shape 对不上
你在 AIMO 里按 640×640 转的模型,板端 set_model_properties 却填了 [1,320,320,3],必然推理异常或全 0。对策:转换时记下的输入分辨率,原样填进板端的 input/output shape,NHWC 布局别搞反(见第 02 讲 3.7)。
八、验证:转换前后结构与精度评估
8.1 在线结构比对
AIMO 平台通常会在转换完成后提供转换前后的结构对比(节点数、输入输出、算子分布变化)。重点确认:输入输出节点没丢、shape 正确、量化算子已注入。这一关过了,才值得把模型下到板子。
8.2 精度评估脚本
下了板,用同一批固定测试图,分别跑浮点参考模型和 INT8 模型,统计检测框的重合度(mAP 或 IoU 均值)与分类准确率:
python
import numpy as np
def compare_outputs(float_out, int8_out):
a = np.asarray(float_out).ravel()
b = np.asarray(int8_out).ravel()
n = min(len(a), len(b))
diff = np.abs(a[:n] - b[:n])
print(f"最大差: {diff.max():.4f} 均值差: {diff.mean():.4f}")
# 对检测任务,更该看"框是否一致"而非逐位差
提醒:量化有损,不要苛求逐位一致,而要看"检测结果是否一致、框是否明显错位"。如果框大面积错位,回到 7.1 查校准;如果只是小幅偏移,属正常量化损失。
8.3 在 A1 与 X1 上分别验收
把转换后的模型分别在犀牛派 A1 和 X1 上跑一遍,预期:
| 指标 | 犀牛派 A1(QCS6490) | 犀牛派 X1(QCS8550) |
|---|---|---|
| INT8 单帧时延 | 基线 | 更优(以实测为准) |
| 检测框一致性 | 与浮点版基本一致 | 与浮点版基本一致 |
| 多模型并发 | 受限 | 从容(以实测为准) |
同一份转换模型,两板几乎免改即可运行------这正是"高低搭配"在模型供给层的体现:A1 验证流程、X1 放量性能。具体时延数字以你的真机实测为准,别拿 TOPS 标称当实测。
8.4 量化收益到底有多大:一个直觉化的换算
为了让你对"量化值不值得做"有个数,给一个直觉化的换算:假设一个检测模型权重 14MB(FLOAT32),量化到 INT8 后约 3.5MB,权重大小降到 1/4;推理时每次从内存搬权重,带宽占用同步降到 1/4。再叠加 NPU 的 INT8 乘加吞吐远高于 FLOAT32,单帧时延往往能降一个数量级量级------当然具体倍数以你的真机实测为准,这里只想说明"量化不是微调,而是质变"。
更重要的是"能不能上 NPU":很多模型的 FLOAT32 版本在端侧根本没法高效走 NPU(NPU 算力是按 INT8 计峰值的),量化之后才真正把那 12 TOPS 或 48 TOPS 用起来。所以量化的意义,一半是"更快更小",另一半是"终于能用上 NPU"。这也是为什么本系列从第 02 讲起就反复把"量化 + NPU"当成端侧主路径,而把浮点留给验证与兜底。
九、FAQ
Q1:AIMO 转换一定要量化吗?
不一定要,但端侧 NPU 跑 INT8 才划算。如果你只想验证格式能不能转过去,可以先转浮点;要上板跑性能,必须量化。
Q2:校准数据要带标签吗?
不需要标签,只要是有代表性的真实输入样本(图片、音频等)。它用于统计激活分布,不参与训练。
Q3:Model Farm 的模型能直接商用吗?
Model Farm 帮助你加速落地,具体每个模型的授权与商用条款请以模型详情页和官方说明为准,下载前看清楚。
Q4:AIMO 转出来的模型,X1 上能直接用 A1 转的版本吗?
两者都是高通 NPU,AidLite 接口一致,很多时候通用;但稳妥起见转换时针对目标平台(QCS8550)产出对应版本,并确保 QNN 版本与板载对齐。以平台说明和真机实测为准。
Q5:转换报"某算子不支持"怎么办?
可看 AIMO 是否提供补充算子/UDO 适配;或调整模型结构避开该算子;必要时联系阿加犀 FAE 评估支持。
Q6:INT8 和 INT16 怎么选?
精度优先或模型对量化敏感选 INT16;速度/体积优先、精度可接受选 INT8。先 INT8 建基线,不达标再升 INT16。
Q7:自动量化效果不好,自定义模式从哪下手?
优先对精度敏感的层保更高精度(如这些层用 INT16/FLOAT16),其余压 INT8;必要时开启 CLE 等优化,并复查校准数据代表性。
Q8:为什么我的 INT8 模型比浮点还慢?
大概率没真正上 NPU(accelerate_type 没落 DSP),或大量算子回退到 CPU。先核对后端与版本,再看转换日志的算子回退情况。
Q9:转换后的模型文件拷到板子哪个目录?
Web 桌面只能接收 /home/aidlux 的上传,建议把模型和工作脚本都放这里,路径好记也好备份。
Q10:AIMO 和直接用高通官方转换工具比,优势在哪?
省去本地搭转换工具链、配环境、处理算子/量化报错的成本,图形化、内置算子库与量化策略。深入定制时也可结合原生工具,二者不冲突。
十、结论
本讲我们补上了边缘 AI 链条最前端的"模型供给"环节:用 AIMO 把通用框架模型转成高通 NPU 可高效运行的 INT8 格式,并从 Model Farm 取用现成模型,最终都接回第 03 讲那套可参数化模板。你手里现在有了"任意模型 → 高通 NPU 可运行模型"的完整能力。
模型供给这件事,最怕的是"病在模型却当成病在代码"。本讲反复强调的版本一致、校准数据代表性、输出节点核对,都是让你在故障发生时能快速定位到"是转换出的问题"还是"是推理接的问题"。把转换日志、版本对照表、校准数据一起归档,你的项目就多了一份可追溯的资产。
下一讲我们解决"图像从哪来、框怎么画出来":用 AidCV 在犀牛派上做相机采集、预处理与 Web 桌面渲染,并把本讲跑通的模型接成一条实时检测画框的流水线。那时你才算真正把"感知"闭环了。
十一、官方资源导航
| 资源 | 地址 | 用途 |
|---|---|---|
| 模型广场 Model Farm | https://aiot.aidlux.com/zh/models | 下载已适配高通平台的模型 |
| AidLux 开发者文档中心 | https://docs.aidlux.com | SDK 接口与指南 |
| AidLux 软件总览 | https://developer.aidlux.com/software/aidlite | 工具链组件总览 |
| 技术论坛 | https://forum.aidlux.com | 社区问答、案例 |
| 官网 | https://www.aidlux.com | 产品与方案 |
| 技术支持 | support@aidlux.com | FAE 支持 |
本文 AIMO/Model Farm 的平台能力、转换与量化参数、入口地址等均来自 AidLux 官方文档;跨板时延参考高通公开资料,精确值以真机实测为准。