跨端兼容的智能分类模型:一次训练,全平台精准归类
|
跟产品吵的那天下午三点,我盯着他手机里那个“智能分类模型”的 demo 页面——白底黑字,三级分类折叠收得好好的,还带了个小动画。我点开开发者工具,切到模拟 iPhone SE,再切回 Pixel 7,再切回 iPad Pro 的 Safari。三个屏幕,同一个模型 JSON 接口返回的 label_id 居然对不上。不是错,是“对不上”:iPhone 上叫“快递异常”,Pixel 上叫“物流中断”,iPad 上叫“履约延迟”。同一帧 inference 结果,三端 parse 出来仨名词。我截图发到运维群,Lighthouse 跑分那张图带水印、没裁边、连“Performance: 83”都糊成一团,发完十秒没人回。PM 在对面啃三明治,面包屑掉在键盘上,没擦。 我原以为是前端用了不同版本的 tokenizer,结果查下来是后端把分类标签表塞进一个 config.json,又让 Webpack 打包时按 platform 字段 splitChunks——但 Android 端用的是 gradle 插件做 runtime load,iOS 用的是 Swift Package Manager 做 lazy load,Web 端……算了,Web 端连 model.json 都是 base64 写死在 index.html 里的。你说这叫“一次训练”?训练倒是真就一次,去年九月份我在 GPU 机房守了整整十八小时,显存爆了七次,最后一次跑出的 .onnx 模型文件,后端转 ONNX Runtime,前端转 TensorFlow.js,移动端转 Core ML。可训完那一刻起,“优胜劣汰”就开始了:Core ML 把 softmax 层给 fuse 了,TF.js 默认用 float32 做 infer,而安卓上那个定制 ROM 连 half_float 都不认,硬生生 fallback 到 int8 —— label mapping 表在 int8 下崩掉两个边界值,偏移一格,原本排第 17 的“发票缺失”跳成了第 19 的“开票失败”。这个我敢打包票,我当场把三个端的 logcat / console / Xcode debug console 并排贴在显示器上,左中右三列,标红了那行“idx=19 vs expected=17”。 桌面那个没关的录屏还在跑,录的是上周五的 CI 流水线——jenkins-job-ml-classify-build-staging,最后一行绿色 ✅ 后面跟着个括号(cache hit),其实没 hit,是缓存键写错了,它根本就没走 cache,纯靠重训顶上去的。但这不重要。重要的是昨天中午倒水的不是我,是前端小张,他拎着保温杯路过我工位时顺手把我桌上那杯隔夜枸杞茶给换了,新杯底还沾着两粒没泡开的枸杞。我说你这杯子是不是刚从会议室拿来的?他点头说对,刚开完需求评审,产品把“支持暗色模式下分类图标反色”改成“支持用户自定义图标色阶映射”,改了四版 PR 描述,没动一行模型代码。我笑了,但没接话。 优胜劣汰是真的。不是说算法自动筛选——是人筛的。Android 团队说“int8 够快,我们不管 label 偏移”,iOS 团队说“Core ML 编译器报错我们就换架构,不碰原始模型输出”,Web 团队说“TFLite WASM 适配太重,先上 tfjs@4.13.0 硬扛三个月再说”。没人管“全平台精准归类”这八个字到底准不准,只管自己那端的 FP 必须低于三百毫秒,只要 metrics 过线,label 对不上?加个 mapTable.js 补丁就行。补丁加到第三版,我打开 node_modules/@our-ml-core/utils/mapTable.ts,发现里面有一行注释:“// FIXME: 2023-Q3 sync with backend; temp fix for label mismatch on Xiaomi Redmi Note 12”。这个我说不准是不是真的 Xiaomi 设备的问题,还是当时谁测漏了 OPPO Reno8 的系统级 tensor kernel patch。反正补丁一直活着,比 prod 环境的监控告警配置活得还久。
文章配图,仅供参考 对面工程师换了一根充电线,Type-C 变 USB-A,他说新线供电稳,旧线插 MacBook 充电偶尔会掉 USB 设备枚举。我嗯了一声,看他把旧线卷起来塞进抽屉最底下,压在三本没拆封的《Model Compression Handbook》下面。这让我想起去年十月修过的那个 bug:某次热更下发了一个新版 label.json,但 iOS 侧没清 local cache,导致老用户重启 App 后仍用旧 map。后端说“你们客户端该加 version 字段校验”,前端说“你们 API 该加 ETag”,最后运维背锅,写了条 Ansible playbook 给所有线上 iOS 设备推了个静默 kill -9 com.ourapp. 的脚本——不是杀进程,是 kill AppGroup container,强制触发 relaunch。那脚本现在还在 /opt/ansible/roles/ios-cache-buster/tasks/main.yml 里躺着,description 是 “ugly but working, do not touch”。CDN 账单上月涨了十九万,原因不是流量暴涨,是 origin server 被迫多开了十二个 inference 实例:因为 Web 端不愿升级 TF.js,必须走 server-side fallback;iOS 不信 JS,要 model on device;Android 拒绝任何 >5MB 的 bundle,只好把分类逻辑切片甩给后端——最终,那个标榜“一次训练”的模型,实际跑了四套推理路径。KPI 折算表里,“跨端兼容达成率”这一项,PM 自己填的是 92.7%,我问依据,他翻出一张钉钉截图:群里有人说“测了五台主流机型,都分对了”。我看了眼截图时间,是上周三下午两点十七分,群里总共七个人,其中俩是实习生,一个在摸鱼刷 B 站,一个正调试小程序的 canvas drawImage 渲染偏移。没人提华为鸿蒙,也没人提那个还在用 Android 8.1 的政企定制机。 所以现在怎么做?我把 ONNX Runtime 的 Python infer 脚本扔进 Docker,加了 --no-cache 标志,镜像 push 到私有 registry;又让后端同学改掉所有 /v2/classify 的响应 header,强制加上 Content-Type: application/json; charset=utf-8,不是为兼容,是防 iOS Safari 的 text/plain MIME sniffing 导致 JSON.parse 失败——这事真发生过,去年九月份,就是模型训出来的那天,线上有个用户投诉“分类按钮点了没反应”,查到⭐️⭐️⭐️⭐️是 response body 是 JSON,但 header 写成了 text/plain,Safari 当文本渲染了。我就在这儿坐着,等着下一个 runtime mismatch 出现。要么等,要么手动 merge 那个被卡住三天的 PR ——前端同学提交的,PR 描述写的是“修复 Web 端 label 映射丢失问题(实测 Chrome/Firefox/Edge 正常)”,但没测 Safari,因为“本地 macOS 版本太低,没 Safari 17”。我准备点“Approve”,鼠标悬停三秒,撤回。先去倒杯水。不是我倒,今天轮到后端老李。 (编辑:站长网) 【声明】本站内容均来自网络,其相关言论仅代表作者个人观点,不代表本站立场。若无意侵犯到您的权利,请及时与联系站长删除相关内容! |

