Moonshine 项目 iOS 端 ONNX Runtime 最小化构建:静态库体积与安装成本的实测优化指南
【免费下载链接】moonshineVery low latency speech to text, intent recognition, and text to speech, for building voice agents and interfaces项目地址: https://gitcode.com/GitHub_Trending/moonshine3/moonshine
Moonshine 的 iOS 端不再下载 ONNX Runtime 官方预编译 pod archive,而是由scripts/build-ort-ios.sh从源码构建一个被裁剪到"只含本项目算子"的最小化静态库。本文以仓库中core/third-party/onnxruntime/lib/ios/README.md为核心,结合构建脚本、测量脚本与相关文档,完整讲解 iOS 静态库的构建方式、arm64 设备切片与模拟器 fat 切片的体积实测数据、以及"静态库大小 ≠ 应用安装成本"的正确衡量方法,帮助你在自己工程中复现同样的构建与测量流程。
为什么 iOS 的 ORT 库必须从源码构建
仓库在core/third-party/onnxruntime/lib/ios/下只保留了arm64/与simulator/两个目录,各自存放一个libonnxruntime.a。按照 lib/ios/README.md 的说明,这两个静态库不是下载来的预编译产物,而是由scripts/build-ort-ios.sh从源码构建并 vendor 进仓库的:
These are built from source by
scripts/build-ort-ios.sh, not downloaded.
它们替换掉了官方预编译 pod archivepod-archive-onnxruntime-mobile-c-1.23.2.zip。原 README 特别强调了一个约束:
- 不要把 pod archive 再放回该目录;
- 该目录之上的一切代码(Moonshine 的转录、TTS、嵌入等所有模型加载逻辑)都基于"最小化构建"的前提工作。
那么"最小化构建"到底意味着什么?scripts/build-ort-ios.sh的头部注释给出了精确的界定:构建被限制在 core/third-party/onnxruntime/moonshine-required-operators.config 列出的算子集合内——这是scripts/generate-ort-op-config.py生成的全项目统一算子白名单,与 Android、WebAssembly 等其他平台使用的是同一份配置(文件头注释明确写着 "Generated by scripts/generate-ort-op-config.py; do not edit by hand")。这份配置文件共 269 行,逐条列出了 Moonshine 能加载的所有模型(Kokoro、Piper、ZipVoice、拼写模型、OOV G2P 等)所需的算子。
最小化构建的两个直接后果
原 README 明确指出,iOS 最小化构建带来两个与 Android 平台一致的后果,分别由两份官方文档支撑:
1. 只加载 ORT 格式模型(.ort)
见 docs/ort-only-models.md:最小化构建根本没有编译进 ONNX 解析器,所以.onnx文件在任何平台上都无法被读取。这统一了各平台行为——原来桌面端支持.onnx、移动端不支持,导致"开发机上能跑、手机或浏览器上却报 ORT 解析错误"的割裂体验。现在所有平台统一拒绝.onnx,转换命令为:
python scripts/convert-models-to-ort.py path/to/model.onnx.ort是自包含的,所以原来依赖外部数据 sidecar(model.onnx.data等)的模型必须转换后再使用;选项名(如piper_onnx、oov_onnx_override)保持不变,只是接受的格式收窄了。
2. 没有 CoreML execution provider
见 docs/execution-providers.md:这不是选择,而是上游限制。ONNX Runtime 1.23 的 CoreML provider 会在GetCapability中无条件调用Graph::GetModel(),而该方法在最小化构建中被#if !defined(ORT_MINIMAL_BUILD)编译掉(位于 ORT 的include/onnxruntime/core/graph/graph.h),--minimal_build extended仍然会定义ORT_MINIMAL_BUILD,因此构建会报no member named 'GetModel' in 'onnxruntime::Graph'直接失败。ort_providers选项仍保留,但cpu是任何已发布构建唯一可用的取值。
构建脚本build-ort-ios.sh实战详解
构建入口是 scripts/build-ort-ios.sh,完整命令形如:
scripts/build-ort-ios.sh [force] [with-coreml] [device|simulator]各参数含义:
| 参数 | 作用 |
|---|---|
force | 即使库已存在也强制重建并重新 vendor;强制重建会先清空对应 build 目录,避免 ORTbuild.py只增不减 CMake 选项导致的陈旧缓存 |
with-coreml | 尝试加入 CoreML(见上,预期编译失败,用于验证后续 ORT 版本是否修复了GetModel问题) |
no-coreml | 显式关闭 CoreML(默认即关闭) |
device | 只构建 arm64 设备切片 |
simulator | 只构建 fat(x86_64 + arm64)模拟器切片 |
| 不传 slice | 默认同时构建 device 和 simulator |
环境变量:
| 变量 | 默认值 | 说明 |
|---|---|---|
ORT_IOS_CONFIG | Release | 构建配置;选择 Release 是因为 MinSizeRel 仅节省约 3% 库体积却损失了未量化的速度(与 Android 的对比结论一致,见脚本注释) |
MOONSHINE_ORT_ROOT | 无 | ORT 源码 checkout 与 build 树所在目录 |
IOS_DEPLOY_TARGET | 15.1 | 最低部署版本,必须与scripts/build-swift.sh中IOS_VERSION保持一致,否则会出现"库声称支持的版本高于实际链接版本、在旧系统设备上运行失败"的问题 |
关键构建流程(来自脚本实现):
ort_require_op_config校验算子配置文件存在,构建全程通过--include_ops_by_config传入moonshine-required-operators.config;- 对每个 sysroot/arch 组合调用 ORT 的
./build.sh,核心参数包括--ios、--cmake_generator Xcode(ORT 明确拒绝 iOS 构建使用其他 generator)、--build_apple_framework(让 ORT 把各组件静态库合并成单一归档,即我们需要的libonnxruntime.a来源)、--apple_deploy_target与--skip_tests; - 架构差异处理:
--osx_arch一次只能接受 arm64、arm64e、x86_64 中的单个架构,所以模拟器 fat 库要分别构建 arm64 与 x86_64 两个切片后用lipo -create合并; - KleidiAI 处理:ORT 1.23 在宿主机为 arm64 时会对 x86_64 目标也开启 KleidiAI(aarch64-only),导致 Apple Silicon 机器上的 x86_64 模拟器构建出现未定义的
ArmKleidiAI符号,因此脚本对非 arm64 架构自动追加--no_kleidiai; - 构建完成后
cp到对应目录并打印lipo -info校验切片信息。
vendored 后,CMake 侧通过 core/third-party/onnxruntime/find-ort-library-path.cmake 按 sysroot 选择库:CMAKE_OSX_SYSROOT STREQUAL "iphonesimulator"时取lib/ios/simulator/libonnxruntime.a,其余 iOS 场景取lib/ios/arm64/libonnxruntime.a——这正对应原 README 说的 "../find-ort-library-path.cmakeexpects"。
体积数字:为什么.a大小不能代表应用安装成本
原 README 给出了一张关键对比表(arm64 设备切片,单位 MB)。注意 iOS 静态库是应用安装成本的糟糕代理指标:链接器(app linker)会丢弃没有任何符号引用的 object file,所以.a在磁盘上往往远大于它真正贡献给安装包的字节数。
| 指标 | Pod archive | Minimal | 节省 |
|---|---|---|---|
libonnxruntime.a | 36.6 | 18.1 | 18.5 |
libmoonshine.a(合并) | 60.3 | 41.8 | 18.5 |
| Linked app binary | 45.0 | 30.6 | 14.4 |
该二进制__TEXT | 31.1 | 22.8 | 8.3 |
"Linked app binary" 这一行才是真正要紧的,它就是scripts/measure-mobile-size.sh ios报告的数字:脚本会链接一个引用 Moonshine C API 入口点的小型 app,并以-dead_strip模拟真实应用的链接取舍,然后测量结果。
另有两个必须说明的测量口径:
- 表中两个 Moonshine 列是在说话人分离(diarization)模型仍编译进库内时测得的,因此比今天的库高 8.2 MB——按 docs/diarization-models.md 的说明,这两个模型(pyannote community-1 的 segmentation 与 speaker-embedding)已改为从 CDN 下载,任何平台都因此省下 8.2 MB:iOS 的 linked app binary 由 30.6 MB 降到22.4 MB;
- 每行的"节省"数值不受影响,因为它是同一批都携带这些模型的两个构建之间的差值。
也就是说,当前仓库的 iOS 最小化构建的实际安装成本约为 22.4 MB(arm64 设备切片),而原 README 表格反映的是旧口径下的对比。
用measure-mobile-size.sh复现实测
scripts/measure-mobile-size.sh支持android/ios/all三个参数,iOS 分支的核心做法(对应原 README 中 "links a small app against the xcframework with-dead_stripand measures the result" 的描述):
- 静态库位置默认取
language-bindings/swift/Moonshine.xcframework/ios-arm64/libmoonshine.a(需先运行scripts/build-swift.sh),也可用MOONSHINE_IOS_LIB指向其他libmoonshine.a——这正是做前后对比的方式:用旧版 ONNX Runtime 在别处构建设备切片,再测量该归档; - 生成一个引用
moonshine_load_transcriber_from_files、moonshine_create_tts_synthesizer_from_files、moonshine_create_embedding_model三个 C API 入口的main.c,保证链接器保留转录、TTS、嵌入三条路径及其全部依赖(见脚本内main.c模板); - 用
xcrun clang -target arm64-apple-ios15.1 -dead_strip ... -framework Foundation -framework CoreFoundation -framework Accelerate链接出可执行文件; - 报告 linked binary 大小,并用
xcrun size -m切出__TEXT(可执行代码 + 只读数据,随 ORT 链接量增减而变化的正是这一部分)与__DATA段大小。
因此,无论你如何调整 ORT 的构建方式,都应该在改动前后各跑一次:
scripts/build-swift.sh && scripts/measure-mobile-size.sh ios用真实链接结果说话,而不是用libonnxruntime.a的磁盘大小做推断。脚本头部注释也明确说明了两平台的相反偏差:Android 的.so整体打包、文件大小接近真实,但 AAR 同时携带全部 ABI 而设备只下载一个;iOS 的静态.a则因链接器裁剪而远大于其实际贡献。所以统一用"真实链接一个二进制"来度量 iOS,用"按 ABI 拆分"来度量 Android。
模拟器切片为什么是 fat 的
原 README 最后一行解释了模拟器库的体积来源:模拟器切片是 fat 库(x86_64 arm64双架构),所以仅仅因为这一点就比设备切片大。scripts/build-ort-ios.sh对 simulator 的处理是分别构建iphonesimulator/arm64与iphonesimulator/x86_64两个切片再lipo -create合并,从而同时覆盖 Intel Mac 与 Apple Silicon Mac;lipo -info确认合并结果与原 pod archive 一致(x86_64+arm64),这也正是find-ort-library-path.cmake所期望的形态。
延伸:这一结论的适用范围与注意事项
- 改动模型必须重新生成算子配置:最小化构建中,配置里缺少的算子会在 session 创建时报错。每当模型发生变化,就需要重新运行
scripts/generate-ort-op-config.py更新 moonshine-required-operators.config,否则新模型无法加载。 - CoreML 期望值管理:对 iOS 上最终用户而言,模型只跑 CPU execution provider。虽然
ort_providers选项仍接受CoreML,但任何已发布构建都不包含它,请求时会得到明确报错(指向docs/execution-providers.md),而不是静默降级。 - 8.2 MB 口径修正:若引用本文或原 README 中的旧表格数字(45.0 / 30.6 / 22.8 MB),务必同时说明 diarization 模型已外移,当前 arm64 设备切片的 linked app binary 为 22.4 MB。
如果你需要在自己的应用中复现这套做法,完整的查看路径是:构建入口 scripts/build-ort-ios.sh、测量入口 scripts/measure-mobile-size.sh、库选择逻辑 core/third-party/onnxruntime/find-ort-library-path.cmake、算子白名单 core/third-party/onnxruntime/moonshine-required-operators.config,以及两份行为约定文档 docs/ort-only-models.md 与 docs/execution-providers.md。
【免费下载链接】moonshineVery low latency speech to text, intent recognition, and text to speech, for building voice agents and interfaces项目地址: https://gitcode.com/GitHub_Trending/moonshine3/moonshine
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考