ARTICLE DETAIL

资讯详情

深耕郑州网站建设与运营推广的一线实战洞察。

BECKETT开发手记:TFLite模型转MS模型记录

BECKETT开发手记:TFLite模型转MS模型记录 一次 TFLite → ONNX → MindSpore Lite 迁移中的张量边界我们在开发过程中因为需要用到端侧模型在部署的时候就遇到这个问题。我们训出来的模型是TFlite格式但是鸿蒙这块的Mindspore Lite Kit支持的是ms并且不支持控制流所以我们在模型转换的时候遇到了较大的问题。但是我们研究了很久提出了上面的这个拆解流程与使用架构把控制流剥离出模型。图中上面一条是我们在工程侧怎么把模型「变成能在端上跑的形态」。起点是 TFLite里面带 WHILE、也就是 TFL_WHILE 这类控制流如果硬怼到端推理框架里循环往往不好原样落地。所以我们先用 工具链 把它转到 ONNX在 ONNX 里循环会以 Loop 算子 等形式 显式可见方便我们做切分和验证。下一步是 拆解子图、剥离或消除 Loop得到 多段不含动态循环的 ONNX 子图再 分别转成 MindSpore Lite又因为我们的控制流在模型图的中间所以最后输出像 part_B.ms 和 part_C.ms 这样的多段模型同时我们外置控制流代码使用胶水代码重新粘合两个分离出来的模型最终输出结果等价于tflite原模型这一条是我们拆解后的模型在鸿蒙项目的部署把 **part_B、part_C 放进 rawfile运行时通过 MsHitModelService 包一层用 MindSpore Lite Kit 的 loadModelFromBuffer 把模型加载进内存。业务侧是 VideoHitRallyPipeline对音频做 scoreWindow 滑窗每一窗送进模型得到原始输出最后在 RallyDetectFacade 里做 概率后处理和标定和 TFLite 语义对齐。这样 上图解决「图怎么切、循环去哪」下图解决「包内怎么载、Pipeline 怎么接、结果怎么对齐」最终就把 转换 和 落地 串起来了也成功实现了不支持的控制流模型的转换部署使用。做端侧模型迁移时拿到目标格式文件只算走完一段路。模型能够加载输入也没有报错仍然可能把错误的数据送进推理。这个音频分类 Demo 让我把注意力从转换命令移到了模型之间、缓冲区之间的交接处。先用同一份输入证明数值一致再讨论业务效果。先画清楚模型之间传的是什么案例按固定长度窗口处理音频。每个窗口包含 15360 个 float32 采样点先进入 B 段得到 1024 维特征再交给 C 段输出一个分类概率。迁移过程涉及 TFLite、ONNX 和 MindSpore Lite端侧 Demo 使用的是拆分后的两段模型。拆图的好处是多了一个能观察的中间结果代价则是原本由计算图维护的连接变成了应用负责的接口。数据类型、batch 维度和元素数量任何一项理解错了都可能把问题带到后一段。因此我先记录接口约定再组织模型加载和调用。转换工具支持某种格式不代表任意图结构和算子都能原样落地具体支持范围仍要结合转换器版本与目标模型核对。[1]图 1 两段式模型的输入输出约定。尺寸来自案例模型名称已泛化。比 shape 更隐蔽的是 ArrayBuffer 视图批量输入常常先读成一个大的 Float32Array再用 subarray 切出窗口。这里最容易产生的误解是窗口已经切好了view.buffer 自然就是这个窗口。实际上 subarray 创建的是视图底层缓冲区仍可能包含整批输入。下面用一个不依赖模型的合成例子说明。完整数组有 12 个数window 只取其中 4 个window.length 是 4但 window.buffer.byteLength 仍然是 48。把它直接交给只接受 ArrayBuffer 的接口传入范围就与窗口语义不一致了。const all new Float32Array(12);const window all.subarray(4, 8);console.log(window.length); // 4console.log(window.buffer.byteLength); // 48const begin window.byteOffset;const end begin window.byteLength;const exact window.buffer.slice(begin, end);console.log(exact.byteLength); // 16代码 1 可独立运行的 JavaScript 示例。数值为合成数据不含模型或录音。张量边界不仅包括 shape 和 dtype还包括 byteOffset 与 byteLength。把输入校验放在同一个地方Demo 将加载、缓存输入张量、分段预测和结果汇总放在统一的推理封装里。页面负责提供窗口和展示结果不在多个按钮回调中分别组装张量。这样修改模型版本时接口约定也只需要在一个位置核对。一次推理依次完成检查窗口长度写入音频与 int32 索引执行 B 段检查特征再执行 C 段。两个模型都加载成功之后封装才进入可用状态。加载失败、输入不合规和预测失败分别报告定位会直接得多。交接位置本案例检查什么音频 → B 段float32shape 为 [1, 15360]字节范围准确辅助输入 → B 段int32固定索引值为 0B 段 → C 段1024 维 float32 特征按 [1, 1024] 提供C 段 → 页面每个窗口对应一个概率数量和顺序一致用固定输入找差异而不是先调分类阈值已有 Demo 保存了五个窗口的二进制输入以及对应的 ONNX 拆图参考输出。端侧读取同一份输入后逐窗口比较概率计算最大绝对误差和平均绝对误差。这比“看起来能识别几次”更适合判断迁移是否一致。最大误差能暴露最差样本平均误差帮助观察整体偏离。Demo 将 maxAbs 1e-4 作为这组固定样本的判据它属于这个案例的数值检查不能直接解释为分类准确率也不适合不加分析地套到其他模型。若差异超标我会依次检查输入读取、张量类型与形状、B 段特征、C 段输出。直接调整最终业务阈值可能暂时让结果看起来顺眼却把上游数据错误藏了起来。// 数值检查定义两组输出必须对应同一批输入。maxAbs max(abs(actual[i] - expected[i]));meanAbs sum(abs(actual[i] - expected[i])) / N;// 判据由目标模型和参考样本确定。代码 2 误差计算的数学伪代码非新的测试结果。迁移完成后还要回答另一个问题数值对齐回答的是“端侧与参考实现是否一致”。真实环境中的噪声、录音链路和事件去重则影响“分类是否对业务有用”。这两件事需要分别验收不能用五个固定窗口代替真实场景评估。现有材料能够支撑两段推理和数值回归方法但尚未形成包含转换、拆图、参考输出生成在内的一键工具链。文章不提供来源尚未明确可公开的模型权重或真实音频示例代码仅用合成数组解释数据边界。本次也没有重新执行端侧模型测试。我希望下一位接手迁移的开发者拿到的不只是模型文件还能拿到输入输出约定、转换参数和参考结果。只有差异能够被逐步定位迁移结果才容易维护。
返回列表