端侧AI工具链:模型从 PyTorch 到板子,中间隔着整个版本地狱
PyTorch 里跑得好好的模型,一导 ONNX 就报错;同一个模型,RKNN 能转、NCNN 转不了;SDK 升了个小版本,之前调通的模型全部作废。模型能"跑起来"和能"上线",中间隔的不是算法,是工具链。
先抛三个反直觉的事实:
1. 大多数转换报错,跟算子本身没关系。 翻一下报错记录:Unsupported op 只占一小部分,更多是动态 shape、常量折叠、导出设置、SDK 版本这些"外围"问题。一上来就怀疑算子,等于把诊断方向带偏。
2. custom op 是成本最高的一种"解法"。 写一个算子要过参考实现、单元测试、simulator 比对、上板验证四道关,还要跟着 SDK 升级持续维护。很多场景下,算子融合、CPU fallback、换网络结构,比自研算子便宜一个量级。
3. "升级到最新版"通常是版本地狱的开端,不是终结。 工具链稳定靠的不是新,而是可复现:锁死的版本矩阵、能一键重放的转换脚本、最小回归集。这三样没有,出了问题只能靠运气。
这篇文章是整个「工具链与算子」专题的总述,先给你一张地图:这条链路分几段、每段会出什么事、手上有什么解法、以及后面 10 篇分别展开讲什么。
01 这主题讲什么:一条链,四个战场
先把链路画出来,后面所有坑都能对号入座:
PyTorch 模型
↓ 导出
ONNX 中间层
← 战场 1:导出与静态化
↓ 转换/量化
RKNN / NCNN / 地平线 / 昇腾 工具链
← 战场 2:转换与量化
↓ 编译
板端 runtime + NPU 驱动
← 战场 3:runtime 与驱动
↓ 长期运行
上线跑业务
← 战场 4:版本维护与回归
四个战场对应的坑:
• 战场 1(导出):动态 shape、不支持的数据类型、常量折叠失败、图结构里混进奇怪的节点。
• 战场 2(转换):Unsupported op、量化掉点、转换后精度对不上、工具链版本之间行为不一致。
• 战场 3(runtime):驱动和 runtime 不匹配、内存对齐要求、多核调度、simulator 和真机结果不一致。
• 战场 4(维护):SDK 升级把旧模型搞废、环境漂移、换了台电脑就复现不了。
这条链路有个特点:错误信息常在"下游"暴露,根因在"上游"。比如板端跑出 NaN,可能是导出阶段精度设置的问题;工具链报 Unsupported op,可能是 PyTorch 版本导出的算子集太新。所以排查的第一步永远是"先定位问题发生在哪一段",而不是盯着最后一行报错硬看。
02 平时会踩哪些坑:四类高发问题
2.1 转换失败:报错看不懂,重导没卵用
典型现场:
• 报错文案是一串十六进制 op type,搜不到任何资料。
• 同一个模型,周一能转、周五转不了——中间升级了 SDK。
• 试了网上说的"加一行 opset_version=17",还是不行。
这类问题卡人的地方不是"不会修",而是没有定位方法:不知道报错指向哪个算子、哪一层、哪段图。多数人靠试,试三次没成就开始怀疑人生。
2.2 custom op:写了不跑,跑了不对
典型现场:
• 模型里有个 DeformableConv,工具链不支持,决定"自己写一个"。
• 写完了 simulator 里精度正常,上板输出全错。
• 或者精度对了,性能比 CPU 还慢——白写。
custom op 不是不能写,但它是最后手段。判断标准、落地流程、常见坑,后面 03.5 / 03.6 单独讲。
2.3 版本地狱:升级一时爽,模型火葬场
典型现场:
• SDK 从 1.6 升到 1.7,之前调通的模型重新转换后精度掉了。
• 转换工具、runtime、驱动三个版本各差一个,谁都不兼容谁。
• 换了个同事的电脑,环境对不上,转换结果不一样。
版本问题的本质是不可复现。只要"转换一次成功"不可复现,出了问题就无法归因,只能反复试。
2.4 精度对不上:转换掉点还是量化掉点,傻傻分不清
典型现场:
• 模型转完精度掉 3 个点,第一反应是"量化害的"。
• 其实逐层比对发现,问题在转换阶段就丢了精度,跟量化没关系。
• 没有逐层比对手段,只能靠猜,猜错方向白忙一周。
这一条承接 02(量化)那篇:先分清掉点发生在哪一段,再谈优化。
03 有哪些解法:一套能落地的打法
整个专题的解法,可以压成四句话:
1. 先定位,再动手:报错先分三类(算子不支持 / 图结构问题 / 版本问题),再逐层二分定位到具体算子。(展开:03.2)
2. 建可复现基线:锁版本矩阵 + 一键转换脚本 + 最小回归集,让"转换成功"可以被重放。(展开:03.7)
3. 能不写算子就不写:custom op 之前,先走算子融合、CPU fallback、换结构三条路。(展开:03.5)
4. 逐层比对保精度:转换后和 PyTorch 逐层输出比对,先分清是转换掉点还是量化掉点。(展开:03.8)
具体到每个问题怎么解,见 05 节的文章地图。
04 再往前探索:工具链的边界在哪
这一节不解决问题,只讲方向,帮你判断该往哪投入:
• 中间层在收敛,但没收敛完:ONNX 之外,MLIR、TVM 也在做统一表示。现阶段多平台迁移,守住 ONNX 中间层 + 导出规范仍是性价比最高的做法;指望"一套模型到处跑"还太早。
• 端侧 LLM 把工具链问题放大了:RKLLM、llama.cpp 这类新 runtime 带来新的算子集、新的量化格式(GGUF / GPTQ)、新的版本漂移。传统 CV 工具链踩过的坑,在 LLM 上会以更大规模重演。
• 值得沉淀的不是工具,是 SOP:项目里最有价值的东西,是一份"转换 SOP + 常见报错库 + 锁版本记录"。工具会换,这套流程能跟着项目走。
05 本专题文章地图
06 自检清单
进入这个专题前,先回答这几个问题:
☐我能说出"问题发生在导出 / 转换 / runtime 哪一段"吗?
☐报错我按"算子 / 图结构 / 版本"分类过,还是盯着最后一行猜?
☐转换环境(SDK / 驱动 / 工具链版本)有记录、可复现吗?
☐转换脚本是一键可重放,还是靠记忆手动点?
☐custom op 之前,确认过融合 / fallback / 换结构都不行吗?
☐精度掉点,先做过逐层比对、分清是转换还是量化吗?
附:关键结论速查表
这一专题先用定性结论打底,具体数字在对应文章里给(估算值会标注):
先画地图,再进战场。工具链的坑不会少,但如果你知道坑在哪一段、手上有什么工具,大多数坑都能在两小时内收工。
下期预告
转换链路全景:PyTorch 到板子,中间到底有几道关
同一个模型,在 RKNN 上要过一遭,在 NCNN 上又是另一遭;你以为在写模型,其实在跟三条不同的转换链打交道。下一篇把每家工具链的转换路径画出来,告诉你问题出在"哪一层"时,该去哪找答案。
夜雨聆风