Moonshine 项目 Android 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 的开发者与维护者,讲解 Android 平台上 ONNX Runtime(ORT)运行库的供应链管理方案:为什么仓库内core/third-party/onnxruntime/lib/android/下的三个.so必须由源码构建而非直接下载官方 AAR,最小化构建对模型格式与执行提供程序(Execution Provider)产生了哪些硬性约束,以及如何在提交前验证裁剪与 strip 结果。读完本文,你将掌握scripts/build-ort-android.sh的完整用法、各 ABI 的体积收益数据,以及用scripts/measure-mobile-size.sh实测真实安装成本的流程。
目录
- 这些
.so从哪来:源码构建而非下载 - 最小化构建的两条硬性约束
- 每个 ABI 的实测体积收益
- 构建脚本用法与参数详解
- 构建流程中的关键工程细节
- 跨平台版本锁:ort-build-common.sh
- 提交前的验证与真实安装体积测量
一、这些.so从哪来:源码构建而非下载
关联文档 core/third-party/onnxruntime/lib/android/README.md 开宗明义:仓库内android/目录下按 ABI 分目录存放的libonnxruntime.so(当前包含arm64/、armeabi-v7a/、x86_64/三个变体)不是从官方发布渠道下载的预编译产物,而是由scripts/build-ort-android.sh从源码构建后 vendor 进仓库的。
这一点至关重要,原因有三:
- 版本被固定:构建脚本将 ONNX Runtime 钉在特定版本(当前为
1.23.2),避免官方 AAR 升级引入行为漂移; - 构建被裁剪:整个构建被限制到 core/third-party/onnxruntime/moonshine-required-operators.config 所列的操作符集合,只编译 Moonshine 模型真正用到的算子;
- 产物被 strip:vendor 前会剥离符号与调试信息,否则仓库体积会失控。
因此文档明确警告:不要向这个目录投放标准的 ONNX Runtime AAR。上层所有代码都假设底层是一个最小化构建(minimal build),放入完整版库虽然能跑,但会破坏体积预算,并在行为上与其它平台不一致。
二、最小化构建的两条硬性约束
最小化构建(--minimal_build)会丢弃 ONNX 解析器与部分图优化器,由此产生两条在会话创建(session creation)时报错、而非编译时报错的约束,任何使用该库的调用方都必须遵守:
1. 只加载 ORT 格式模型(.ort)
最小化构建中根本没有编译进 ONNX 解析器,因此.onnx文件在 Android(以及 WebAssembly、iOS)上永远无法被读取,无论调用代码怎么写。为消除"桌面端能用、移动端报晦涩解析错误"的双格式分裂,Moonshine 在所有平台统一只接受 ORT flatbuffer 编码的.ort模型。详细背景见 docs/ort-only-models.md。
对使用方的影响是:自行提供的自定义模型文件必须先用转换工具迁移:
python scripts/convert-models-to-ort.py path/to/model.onnx转换后.onnx会在启动时报出指明文件名与本命令的错误信息,而不再是难以定位的解析异常。ONNX external data(model.onnx.data/model.onnx_data)支持也随之移除,因为.ort是自包含单文件。
2. 没有 NNAPI 执行提供程序
Android 库在默认构建中不包含 NNAPI execution provider。这不是疏漏,而是一个被量化过的工程决策——详见 docs/execution-providers.md:
- NNAPI 是"编译型" provider,它把图中自己能识别的节点成组摘出、编译成自己的图,其余留给 CPU;每个编译组与 CPU 之间的边界都要付出同步(甚至跨内存拷贝)代价。因此决定它是否有用的关键指标不是"支持多少节点",而是"图被切成了几块"。
- Moonshine 的模型以全优化方式转换为
.ort,优化会把整段区域融合为com.microsoft域算子(FusedConv、MultiHeadAttention、MatMulNBits、SkipLayerNormalization等),这些是 CPU 内核,任何编译型 provider 都不认识,反而把其余节点切成碎片。 - 实测数据(由
scripts/check-ep-partitioning.py复现):Piperen_US-amy-low被切成220 个分区,Piperen_US-saikat被切成141 个分区,Kokoro 148 个分区,没有一个模型能达到每分区 7 个节点的门槛。 - NNAPI 本身的成本是 0.55 MB(约占 6.5 MB 库的 9%),换来的却是上述碎片化执行——边界开销大于加速收益,所以默认剔除。
ort_providers选项仍接受CoreML与NNAPI,但任何已发布库中都不包含它们,请求会返回指向 docs/execution-providers.md 的错误,而不是静默降速。
三、每个 ABI 的实测体积收益
文档给出了最小化构建替换 stock 1.23.2 mobile 库后的按 ABI 体积对比(单位 MB):
| ABI | Stock mobile | Minimal | Saved |
|---|---|---|---|
| arm64-v8a | 18.5 | 6.0 | 12.5 |
| armeabi-v7a | 13.3 | 3.7 | 9.6 |
| x86_64 | 22.1 | 6.4 | 15.7 |
一个设备只安装一个 ABI,因此一台 arm64 手机实际节省的是12.5 MB。操作符裁剪约砍掉了 ORT 约三分之二的代码——这是从源码 scripts/build-ort-android.sh 头部注释中明确记载的数字。
裁剪的核心机制是--include_ops_by_config:构建被限制到 moonshine-required-operators.config 列出的算子。该文件由 scripts/generate-ort-op-config.py 自动生成(文件头注明"Generated by ... do not edit by hand"),覆盖了仓库内全部 TTS 音色、转写模型、VAD、拼写模型等ai.onnx各版本段与com.microsoft扩展域算子(如FusedConv、MultiHeadAttention、MatMulNBits、RotaryEmbedding等)。由于配置按模型清单生成,每当模型变更时都需重新生成;CI 中的check-ort-op-config测试负责强制这一点,防止构建出的库缺失新模型所需算子而在运行时失败。
四、构建脚本用法与参数详解
构建入口为 scripts/build-ort-android.sh,其用法签名如下:
scripts/build-ort-android.sh [force] [with-nnapi] [abi ...]| 参数 | 作用 |
|---|---|
force | 即使对应 ABI 的.so已存在也强制重建并重新 vendor |
with-nnapi | 把 NNAPI 执行提供程序编回库中(默认剔除,见上文第二部分) |
abi | arm64-v8a、armeabi-v7a、x86_64中的若干项,缺省时构建全部三个 |
支持的受控环境变量:
| 环境变量 | 默认值 | 说明 |
|---|---|---|
ANDROID_SDK_ROOT/ANDROID_HOME | ~/Library/Android/sdk | Android SDK 路径 |
ANDROID_NDK_VERSION | 28.2.13676358 | 构建所用 NDK 版本,需与 language-bindings/android/build.gradle.kts 中minSdk = 26匹配的应用保持一致 |
ANDROID_API | 26 | 目标 API 级别;高于应用 minSdk 的 API 会导致库引用旧设备上不存在的符号 |
ORT_ANDROID_CONFIG | Release | 构建配置,可选MinSizeRel |
MOONSHINE_ORT_ROOT | ~/moonshine-ort(或 wasm 构建遗留的~/moonshine-ort-wasm) | ORT 源码检出与各平台构建树所在位置 |
关于构建配置,脚本注释特别提醒:MinSizeRel能把 arm64 从 Release 的 6.0 MB 压到 5.3 MB,省下约 0.7 MB(占 arm64 安装量的 3%),但它是靠-Os编译换来的速度折损,而 TTS 是实时性预算最紧的场景,模拟器计时又说明不了真机问题,因此该配置保持 opt-in:上真机测过再发布。
脚本还内置了智能跳过:如果请求的每个 ABI 都已在core/third-party/onnxruntime/lib/android/下存在,则直接退出并提示加force重建。
五、构建流程中的关键工程细节
深入 scripts/build-ort-android.sh 源码,可以看到几个不读源码就难以发现的工程细节:
ABI 目录名映射
ORT 构建体系内部把arm64-v8a称为arm64,因此 vendor 目录做了映射(见脚本dest_dir_for_abi()):arm64-v8a→lib/android/arm64/,其余两个与 ABI 名一致。这就是为什么仓库里是arm64/而非arm64-v8a/。
用 NDK 自带的llvm-strip剥离
ORT 构建默认留下调试信息,未 strip 的 arm64 库约675 MB,其中约636 MB是.debug*段(Gradle 打包时虽会丢弃,但仓库会通过 Git LFS 永久背着它,每个 clone 都受害)。脚本用 NDK 工具链自带的llvm-strip --strip-unneeded完成剥离——只有它才理解自己工具链的输出。同时脚本按宿主系统选择HOST_TAG(macOS 为darwin-x86_64,Linux 为linux-x86_64)。
KleidiAI 的交叉编译陷阱
ORT 1.23 只要宿主机是 arm64(如 Apple Silicon)就会为所有目标启用 KleidiAI,而不检查交叉编译目标,导致armeabi-v7a与x86_64的源码被排除、代码路径却被启用,链接时因未定义ArmKleidiAI::Mlas*符号而失败。脚本对非 arm64 目标显式传--no_kleidiai规避。
强制重建时的陈旧缓存清理
ORT 的build.py只会向 CMakeCache 中添加-D,关闭某特性时不会移除旧的开关,因此force重建必须从空构建目录开始(脚本对已存在的构建目录执行清理),否则上次遗留的ON会让关闭标志静默失效——这是陈旧状态真正的来源。
六、跨平台版本锁:ort-build-common.sh
关联文档最后强调:保持每个 ABI 使用同一个 ORT 版本,并且与其它平台版本一致。这一约束由 scripts/ort-build-common.sh 落实,它被三个构建脚本(build-ort-android.sh、build-ort-wasm.sh、build-ort-ios.sh)共同 source,确保三者不会漂移:
- 统一版本:
ORT_VERSION默认1.23.2,并须与 core/third-party/onnxruntime/find-ort-library-path.cmake 以及各平台未从源码构建的预编译库(core/third-party/onnxruntime/lib/*/README.md)保持一致。头文件在所有平台共享,库版本不一致可能在会话创建时才以诡异方式失败。 - 共享检出:一个约 1 GB 的递归 ORT checkout 供所有平台复用(
MOONSHINE_ORT_ROOT),因为算子裁剪写入构建目录而非源码树。 - 共享最小化标志:
ort_minimal_flags()输出四组标志并附注释解释其必要性:--minimal_build extended custom_ops:丢弃 ONNX 解析器与不可用的图优化器,extended保留运行时优化器(NNAPI/CoreML 这类加载期编译内核的 provider 必需),custom_ops保留自定义算子注册(ZipVoice 需要);--include_ops_by_config <OP_CONFIG>:按算子配置文件裁剪;--disable_ml_ops:去掉无模型使用的经典 ML(ai.onnx.ml)内核;--compile_no_warning_as_error:绕开 ORT 1.23 最小化构建中-Werror,-Wunused-const-variable的编译失败,待上游修复后可移除。
脚本还提供ort_require_op_config()守护:算子配置文件缺失时直接报错并提示先运行scripts/generate-ort-op-config.py,防止无配置构建出完整体积的库。
七、提交前的验证与真实安装体积测量
验证库确实被 strip
替换任何.so之前,用文档给出的两条命令确认:
file <abi>/libonnxruntime.so # expect "stripped" strings -a <abi>/libonnxruntime.so | grep -o 'VERS_1\.[0-9.]*' | sort -u第一条确认符号已被剥离(输出应含stripped),第二条确认版本符号符合预期(如VERS_1.23)。因为 Git LFS 会"乐意"永久携带调试信息,所以这一检查是提交前的硬性门槛。
测量真实安装成本
仓库磁盘上的.so大小并不能直接代表用户支付的成本:Android 的.so是整体随 AAR 发布的,但 AAR 里装着所有 ABI,而设备只下载一个。因此文档强调要用 scripts/measure-mobile-size.sh 读取构建出的 AAR(而非这些源码文件)来测量:
scripts/build-android.sh local && scripts/measure-mobile-size.sh android从 scripts/measure-mobile-size.sh 源码看,measure_android()会自动在 Gradle 输出目录、发布目录与本地 Maven 缓存中选取最近修改的 AAR(支持MOONSHINE_AAR覆盖),用unzip -l逐 ABI 统计libonnxruntime.so与 AAR 内全部.so的总和,并明确输出"AAR total(没人下载这个)"与各 ABI 的真实下载量。脚本注释还点明了另一个反直觉事实:Gradle 会在打包时对原生库做 strip,中间产物(intermediates)约 98 MB,而实际发布约 18 MB,因此必须从 AAR 测量。
该脚本同样支持 iOS 侧(measure_ios()通过真实链接最小二进制并报告__TEXT段大小),ios/all参数可对照使用——这也呼应了关联文档"与其它平台保持同版本、同思路"的跨平台一致性原则。
小结:core/third-party/onnxruntime/lib/android/下的三个.so是 Moonshine 移动端体积与延迟预算的核心支柱。它由 scripts/build-ort-android.sh 从源码构建、按 moonshine-required-operators.config 裁剪算子、用 NDKllvm-strip剥离后 vendor 进仓库,换来 arm64 上每设备 12.5 MB 的安装节省;代价是只支持.ort模型且默认无 NNAPI。任何替换都必须遵守"同版本、同配置、验证 strip、从 AAR 实测"四条纪律。
【免费下载链接】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),仅供参考