
先说一个判断在安卓上跑大模型类应用真正的工程瓶颈早就不是模型本身了而是“环境搬运”这件事。过去想在手机上玩 GPT-SoVITS 这类语音合成项目大家第一反应都是开 proot 装 Linux 发行版然后按电脑端流程走一遍。但 proot 本质是一个用户态 syscall 翻译器性能损耗和文件系统隔离问题会直接拖垮推理速度更别提动不动几个 GB 的发行版根文件系统。如果能绕开这一层直接在安卓的 Termux 环境里把 PyTorch 模型转成 ONNX再用轻量推理引擎加载整条链路的体积、启动速度、资源占用都会发生质的改变。14 岁少年做完这件事说明这条路并不是只有资深工程师才能走通。这篇文章会拆解“不用 proot在安卓上跑 GPT-SoVITS”的完整思路。你会看到这件事并不是把电脑上的代码复制到手机上而是围绕安卓的硬件特性重新设计了一条模型部署链路。文章既有架构层面的方案对比也有 Termux 环境准备、PyTorch 转 ONNX、移动端推理、音频后处理与播放的实操步骤。全文不要求你有多深的 C 功底但会教你把每一步背后的原理和坑都看清楚。1. 为什么要在安卓上跑 GPT-SoVITSGPT-SoVITS 是当前声音克隆与语音合成社区里热度非常高的一个开源项目。它的核心能力是用很少的参考音频甚至几秒就能完成音色特征提取再结合 GPT 式的语言模型生成韵律配合 SoVITS 声学模型输出高质量语音。很多人以为这类项目只能跑在 PC 或云端 GPU 上但现实是开源社区已经有大量项目把 TTS 链路轻量化甚至在手机上运行的案例也越来越多。要在手机上实现真正的“本地离线声音克隆”难点并不在模型结构本身而在环境依赖链。如果你去看 GPT-SoVITS 的官方 README会发现它依赖 Python、PyTorch、FFmpeg、多个音色特征提取模型、中文/英文/日文的语音 tokenizer。这些东西在 x86_64 Linux 上一条命令能装完但在安卓上处理器架构是 arm64系统库路径、Python 环境、编译工具链都和桌面端差异巨大。过去大家会选择 proot 来解决这个问题等于在安卓里装一个完整的 Ubuntu 文件系统再在这个“模拟的 Linux”里装 Python 和 PyTorch。听起来很通用代价也很大proot 的 syscall 翻译会让 CPU 密集型任务变得非常慢而且文件 I/O 会经过多层转发在加载大模型时体验非常差。所以这个项目真正有价值的地方不是“跑通了”这三个字而是它示范了一条更高效的安卓端 AI 应用部署路径。它绕开了 proot直接在 Termux 原生环境中用 Python 完成模型转换和推理脚本编写推理阶段用 ONNX Runtime 加载模型最后音频输出直接走安卓音频抽象层。这套方案解决的不只是 GPT-SoVITS 这一个项目而是一整类桌面端 AI 项目向安卓迁移时的共性问题。如果你现在正准备做手机端离线语音助手、声音克隆工具、游戏角色配音工具或者只是想在安卓上跑通 TTS这篇文章的思路可以直接复用到你的项目里。读完之后你会清楚三条路径一是 proot 整包搬运适合快速验证但性能差二是 Termux 原生环境直接 pip 安装 PyTorch适合模型结构简单、依赖少的项目三是模型转换加轻量推理引擎也就是本文重点讲的方案兼顾性能与部署体积。2. GPT-SoVITS 的核心概念与安卓部署难点先统一术语。GPT-SoVITS 不是一个单一模型而是一条多阶段 TTS 流水线。理解这条流水线才能知道后续每一步在做什么。2.1 流水线输入输出从用户视角看GPT-SoVITS 的输入是一段参考音频用于提取音色、一段参考文本对应参考音频内容、一段待合成的目标文本。输出是一个 wav 文件。而从模型视角看内部经过了三个关键环节语音 tokenizer 和语义编码器把参考音频转成语义 token 和音色向量。GPT 模型根据参考音色向量和目标文本生成目标语音的语义 token 序列。SoVITS 声学模型把语义 token 序列转换成 mel 频谱再由声码器Vocoder转成最终波形。在 PC 端这些模型全部以 PyTorch 权重形式加载到 GPU 或 CPU 上混合精度推理。在安卓端要完整跑通这条链路最直接的方案是装 PyTorch Mobile。PyTorch Mobile 支持直接加载 TorchScript 格式模型也能在安卓上用 Java/Kotlin 调用。但现实问题是GPT-SoVITS 的模型权重较大且包含多个子模型全部转成 TorchScript 后整体包体可能超过 2GB在手机上加载速度感人。因此更合理的路径是把 GPT 和 SoVITS 子模型分别导出为 ONNX 格式然后用 ONNX Runtime 的安卓端版本来推理。ONNX Runtime 在 arm64 上做了线程池和内存池优化支持量化、算子融合、内存复用对移动端的优化明显优于直接用 PyTorch Mobile。这也是“不用 proot 也能跑”的关键技术基础之一。2.2 安卓环境里为什么不能照着 PC 文档装很多人第一次尝试时会直接打开 Termux 输入pip install torch。如果网络没问题这一步其实能成功因为 PyTorch 官方已经发布了 Termux 可用的 arm64 版本。但真正的问题在后边GPT-SoVITS 还依赖ffmpeg、librosa、soundfile、huggingface_hub等音频处理库这些库在 Termux 的 pip 源里经常缺编译依赖。项目里的langchain之类的文本处理子模块在 Python 3.12 上容易出现类型错误。pip 安装完成后动态链接库的路径和 Termux 的 sysroot 不一致运行时经常出现libc.so.6: version GLIBC_2.33 not found。这些坑并不是不能解而是在手机上逐个解决它们时间成本很高。proot 方案本质上就是绕过这些问题Ubuntu 的 glibc 环境是完整的pip 安装什么都有但它用性能换兼容。而本文思路则是在“原生 Termux”里通过手动管理依赖和模型转换来换性能与可行性。3. 方案选型proot、原生 Python 与 ONNX 推理的对比在做安卓端 TTS 方案设计时其实有三种主要路线。把这三条路线的取舍搞清楚比直接抄代码更重要。方案优点缺点适合场景proot Ubuntu Python PyTorch和 PC 端环境几乎一致安装简单文件 I/O 慢CPU 推理极慢根文件系统体积大快速验证模型能不能在手机跑不计较性能Termux 原生 Python PyTorch Mobile使用官方移动端推理库兼容性好需要手动处理 GLIBC 和音频库依赖复杂模型的 TorchScript 导出有算子兼容问题模型依赖少、结构固定的自研小模型Termux 原生 Python 导出 ONNX ONNX Runtime推理速度快内存占用小不用 root需要自己写导出脚本和处理算子映射生产级移动端 AI 应用追求离线效果与性能从材料看这个 14 岁作者选择的是第三条路线。这是个非常成熟的选择因为它把“计算密集部分”交给了 ONNX Runtime 的 C 内核Python 只作为脚本胶水层跑在 Termux 的 Python 环境里。换句话说手机上的代码并不“慢”慢的只是模型转换前的那一次 PC 端准备工作。这个选型还有一个隐藏优点不用编译任何 C 代码。你可以直接在手机端用 pip 安装onnxruntime的 arm64 包然后用 Python 写推理脚本。PyTorch 只在 PC 端用于导出模型手机上完全不需要安装大体积的 torch。4. 整体架构设计从 PC 到手机的完整链路先说清楚整体流程。整个项目由两条链路组成第一条链路离线准备在 PC 上完成从开源社区获取训练好的 GPT-SoVITS 模型权重。写 Python 脚本加载 PyTorch 模型。选择一组固定形状的输入把 GPT 模型和 SoVITS 模型导出为 ONNX。对 ONNX 模型做动态轴设置或量化可选。将 ONNX 文件、参考音频、tokenizer 文件打包成手机端需要的目录结构。第二条链路在线推理在安卓手机上完成Termux 安装 python、ffmpeg、onnxruntime、numpy、soundfile。用 Python 脚本读取参考音频经过音量归一化和重采样后提取音色向量。把目标文本编码成 token 序列输入到 GPT ONNX 模型得到语义 token。把语义 token 输入到 SoVITS ONNX 模型得到 mel 频谱。用声码器Vocoder生成波形保存或直接播放。这里要注意一点从材料看原作者没有说明是否把手动特征提取模型如 contentvec、hubert也导成了 ONNX。更稳妥的设计是保留这些特征模型在五件套目录中在手机上运行时用 numpy 和 onnxruntime 加载它们。它们本身也是 PyTorch 模型同样可以导出为 ONNX但在切片和重采样逻辑上需要你自己补齐。关于这个架构我觉得作者做得最好的一件事是跳过了在手机上运行完整训练代码这个不切实际的目标。他只保留了“推理”链路。训练过程应该在 PC 或云端完成手机只负责“读入参考音频、合成目标文本、输出音频”。这个边界划分对所有想在移动端跑模型的人都值得借鉴。5. 环境准备Termux 安装与依赖配置在动手之前先把环境准备好。Termux 是目前安卓上最主流的终端模拟器可以在不 root 的情况下提供类 Linux 环境。需要注意由于安卓系统版本差异Termux 的安装包来源也不同。推荐从 F-Droid 渠道获取 Termux因为 Google Play 版本存在长期不更新导致兼容问题的情况。5.1 Termux 基础安装# 更新 pkg 源 pkg update pkg upgrade -y # 安装基础工具链 pkg install -y python openblas ffmpeg wget git build-essential这里要说明openblas是很多科学计算库的底层依赖安装 it 之后numpy和onnxruntime的矩阵运算性能会有明显提升。build-essential并不是必须的因为本项目不编译 C 扩展但如果你后续需要安装某些需要编译的 pip 包它会是救命的。5.2 Python 依赖安装pip install --upgrade pip pip install onnxruntime numpy soundfile librosa pyyaml如果网络不佳可以换国内 pip 镜像pip config set global.index-url https://mirrors.aliyun.com/pypi/simple/这里有个常见问题librosa在 Termux 上安装时会拉取numba而numba需要 llvmlite在 arm64 环境下很容易装不上。如果遇到这个问题不一定非要安装 librosa。实际上我们可以用soundfile读取音频用numpy做重采样逻辑去掉了 librosa 也能完成大部分工作。具体看后面的推理代码。5.3 音频播放与保存工具Termux 原生支持 VLC 或 mpv 播放音频但更轻量的方式是直接生成 wav 文件后用termux-open调用系统播放器pkg install termux-api termux-open output.wav当然后续如果要做成更完整的安卓应用这些逻辑最终会封装到 APK 里Termux 只是开发调试阶段的环境。这个细节后面再展开。6. 模型导出把 PyTorch 权重转成 ONNX这是整个项目最关键的一步。前面说过手机端不安装 PyTorch因此所有 PyTorch 算子必须在 PC 端完成“追踪”或“脚本化”导出成静态计算图。6.1 导出前的准备在 PC 端你需要先安装原始 GPT-SoVITS 项目依赖然后下载预训练模型。这里用GPT_SoVITS/pretrained_models下的权重作为示例。目录结构如下pretrained_models/ ├── s2G2333k.pth # SoVITS 模型 ├── s2D2333k.pth # SoVITS 判别器推理不用 ├── gpt_weights/ │ └── s1bert25hz-2kh-longer-epoch68e-step50232.ckpt # GPT 模型 ├── chinese-roberta-wwm-ext-large/ # 中文 BERT 特征模型 └── hubert-base/6.2 导出 GPT 模型GPT 模型的输入是 token 序列和音色向量。在 GPT-SoVITS 中tokens是语音语义 tokentext_tokens是经过 BERT 编码的文本 tokenprompt_tokens是参考音频的语义 token。这三个序列经模型前向传播后得到预测的语义 token 概率分布。下面是一段可直接参考的导出脚本它把超长动态输入固定成max_token_len200这样对手机端的显存和计算更友好但也会限制单次合成的文本长度。如果需要更长文本可以把动态轴打开但推理成本会上升。# 文件路径export_gpt_onnx.py import torch from module.models import SynthesizerTrn from AR.models.t2s_model import Text2SemanticDecoder import config as cfg # 初始化 GPT 模型 gpt_model Text2SemanticDecoder( configcfg.model_args, bert_pathcfg.bert_path, prompt_repscfg.prompt_reps, max_seq_len200, num_layerscfg.num_layers, hidden_sizecfg.hidden_size, num_headscfg.num_heads, ff_sizecfg.ff_size, dropoutcfg.dropout ) ckpt torch.load(pretrained_models/gpt_weights/s1bert25hz-2kh-longer-epoch68e-step50232.ckpt, map_locationcpu) state_dict ckpt.get(model, ckpt) gpt_model.load_state_dict(state_dict, strictFalse) gpt_model.eval() gpt_model.cpu() # 构造固定形状的示例输入 batch_size 1 max_token_len 200 text_token_len 80 tokens torch.randint(0, 200, (batch_size, max_token_len)) text_tokens torch.randint(0, 200, (batch_size, text_token_len)) prompt_tokens torch.randint(0, 200, (batch_size, max_token_len)) with torch.no_grad(): torch.onnx.export( gpt_model, (tokens, text_tokens, prompt_tokens), gpt_model.onnx, input_names[tokens, text_tokens, prompt_tokens], output_names[logits], dynamic_axes{ tokens: {0: batch, 1: seq}, text_tokens: {0: batch, 1: seq}, prompt_tokens: {0: batch, 1: seq}, logits: {0: batch, 1: seq}, }, opset_version17, ) print(GPT ONNX export done)这段代码里最关键的是动态轴设置。如果不设置动态轴模型就只能接受最大长度 200 的固定输入太短的文本会 pad太长的直接报错。设置动态轴之后手机端可以按实际 token 长度传入减少无效计算。6.3 导出 SoVITS 模型SoVITS 模型是声学模型输入是语义 token、音色向量、文本 token 等输出是 mel 频谱。它的导出逻辑和 GPT 类似需要实例化SynthesizerTrn并加载权重。# 文件路径export_sovits_onnx.py import torch from module.models import SynthesizerTrn sovits_model SynthesizerTrn( spec_channels1025, segment_size8192, inter_channels192, hidden_channels192, filter_channels768, n_heads2, n_layers6, kernel_size3, p_dropout0, resblock1, resblock_kernel_sizes[3, 7, 11], resblock_dilation_sizes[[1, 3, 5], [1, 3, 5], [1, 3, 5]], upsample_rates[10, 8, 2, 2], upsample_initial_channel512, upsample_kernel_sizes[16, 16, 4, 4], n_layers_q3, use_spectral_normFalse, gin_channels512, vocoder_typensf-hifigan, # 按你下载的模型版本调整 vocoder_ckpt_pathpretrained_models/s2G2333k.pth ) ckpt torch.load(pretrained_models/s2G2333k.pth, map_locationcpu) state_dict ckpt.get(weight, ckpt) sovits_model.load_state_dict(state_dict, strictFalse) sovits_model.eval() sovits_model.cpu() # 假设语义 token 长度为 100 semantic_len 100 semantic_tokens torch.randint(0, 200, (1, semantic_len)) text_tokens torch.randint(0, 200, (1, 80)) phone_len semantic_len # 简化对齐实际按 phone 序列长度 phone_tokens torch.randint(0, 200, (1, phone_len)) bert_feature torch.randn(1, 1024, phone_len) mel_len semantic_len * 2 mel torch.randn(1, 1025, mel_len) with torch.no_grad(): torch.onnx.export( sovits_model, (semantic_tokens, text_tokens, phone_tokens, bert_feature, mel), sovits_model.onnx, input_names[semantic_tokens, text_tokens, phone_tokens, bert_feature, mel], output_names[audio], dynamic_axes{ semantic_tokens: {0: batch, 1: seq}, text_tokens: {0: batch, 1: seq}, phone_tokens: {0: batch, 1: seq}, bert_feature: {0: batch, 2: seq}, mel: {0: batch, 1: seq}, audio: {0: batch, 1: seq}, }, opset_version17, ) print(SoVITS ONNX export done)需要强调这段代码是基于 GPT-SoVITS 官方网络结构写的示例具体参数名和 modules 路径可能因模型版本而不同。建议在没有把握时直接打印state_dict的 key 名再和模型类的参数名对齐。常见问题包括filter_channels不匹配、gin_channels不匹配、vocoder_type选错导致导出时找不到声码器。6.4 导出特征模型GPT-SoVITS 使用的 HuBERT 或 contentvec 模型可以单独导出。这一步不是必须的你也可以在手机上继续用 PyTorch Mobile 加载这些特征模型但既然目的是去掉 PyTorch 依赖最好一并用 ONNX 导出。导出逻辑更简单import torch from transformers import HubertModel model HubertModel.from_pretrained(pretrained_models/hubert-base) model.eval() dummy_input torch.randn(1, 32000) # 1 秒 16k 音频 torch.onnx.export( model, dummy_input, hubert.onnx, input_names[wav], output_names[features], dynamic_axes{wav: {0: batch, 1: time}}, opset_version17, )导出的 ONNX 模型统一放在手机端项目的models/目录下。7. 手机端推理代码实现在手机上跑推理环境已经具备。现在写一个最小的合成流程。为了突出“不用 proot”的轻量性代码不会把整个 GPT-SoVITS 官方仓库复制过来而是只保留推理必需的数据处理逻辑。7.1 读取参考音频并提取音色向量# 文件路径mobile_tts.py import numpy as np import soundfile as sf import onnxruntime as ort def load_audio(path, target_sr16000): audio, sr sf.read(path) if sr ! target_sr: # 简单的线性重采样工程上建议用 soxr 或 ffmpeg num int(len(audio) * target_sr / sr) audio np.interp( np.linspace(0, len(audio), num, endpointFalse), np.arange(len(audio)), audio ) return audio.astype(np.float32) # 加载 HuBERT ONNX 模型 hubert_session ort.InferenceSession(models/hubert.onnx, providers[CPUExecutionProvider]) ref_audio load_audio(ref.wav) # 这里只做特征提取示意真实链路还要做 VAD 切分和静音过滤 feat hubert_session.run([features], {wav: ref_audio.reshape(1, -1)})[0]7.2 GPT 模型推理# 简化版省略 BERT 文本编码直接用 token 序列 gpt_session ort.InferenceSession(models/gpt_model.onnx, providers[CPUExecutionProvider]) # 假设已有 token 序列 tokens np.random.randint(0, 200, (1, 100)).astype(np.int64) text_tokens np.random.randint(0, 200, (1, 50)).astype(np.int64) prompt_tokens np.random.randint(0, 200, (1, 100)).astype(np.int64) logits gpt_session.run( [logits], { tokens: tokens, text_tokens: text_tokens, prompt_tokens: prompt_tokens, }, )[0] semantic_tokens logits.argmax(axis-1)7.3 SoVITS 模型推理sovits_session ort.InferenceSession(models/sovits_model.onnx, providers[CPUExecutionProvider]) # 准备固定形状输入 audio sovits_session.run( [audio], { semantic_tokens: semantic_tokens, text_tokens: text_tokens, phone_tokens: semantic_tokens, bert_feature: np.random.randn(1, 1024, semantic_tokens.shape[1]).astype(np.float32), mel: np.zeros((1, 1025, semantic_tokens.shape[1] * 2)).astype(np.float32), }, )[0] # 保存音频 sf.write(output.wav, audio[0], samplerate32000)上面这段代码是一个最小可运行链路重点在于展示 ONNX Runtime 的调用方式。真实的 GPT-SoVITS 推理还需要处理中文 G2Pgrapheme-to-phoneme、BERT 特征提取、语义 token 与 phone 的对齐等细节这些在官方仓库里都有现成实现建议直接复用。8. 运行验证与性能判断标准很多人会在这一步卡住模型导出来了脚本也写了但输出的音频是噪声或者长度不对。我给出一个排查顺序先确认输入形状和 ONNX 模型要求匹配。报错时 ONNX Runtime 会明确告知Expected shape ... but got ...按提示调整即可。再确认采样率。GPT-SoVITS 内部使用 32000 Hz 的采样率最终音频也按 32000 输出。如果你用 16000 的参考音频要确保重新采样后再送入特征模型。最后观察输出长度。如果 SoVITS 输出的音频长度比预期短很多说明 semantic token 和 phone 的对齐有问题通常是 text_tokens 长度不正确导致。在手机上启动脚本后可以使用time命令查看各个阶段的耗时time python mobile_tts.py对于一个 5 秒左右的短句在 arm64 手机上如果全链路耗时在 10 到 30 秒之间说明性能是可用的。如果超过一分钟建议对 ONNX 模型做动态量化或者考虑把部分数据处理改成 C 扩展。# 对 GPT 模型做动态量化仅整数优化 python -m onnxruntime.quantization.quantize --input gpt_model.onnx --output gpt_model_quant.onnx --dynamic9. 常见问题与排查方法问题现象可能原因排查方式解决方案Termux 中 pip 安装 onnxruntime 失败Python 版本过高或 pip 源无 arm64 包查看 pip 报错信息执行python --version安装 Python 3.11 或 3.9切换镜像源ONNX Runtime 加载模型报错算子不兼容或 opset 版本过低打印模型文件的下标信息提高 opset_version 到 17 或更高输出音频全是噪声特征模型输出未归一化打印 hubert 特征均值和方差对特征做 L2 归一化合成速度太慢未开启 ONNX Runtime 线程池检查 provider 是否为 CPUExecutionProvider设置sess_options.intra_op_num_threads音色不像参考音频prompt_tokens 长度过短或参考音频有噪声更换更干净的参考音频使用 5 秒以上静音段的参考音频手机内存不足同时加载了多个大模型使用free -h查看内存对模型做 int8 量化或分阶段加载模型GLIBC 报错未使用 Termux 原生 Python错误调用系统 Python执行which python确保使用的是/data/data/com.termux/files/usr/bin/python10. 工程建议与后续学习方向从“跑通 demo”到“做成能用的应用”中间还有几步值得认真打磨。第一模型加载要改成惰性加载。不要在启动时把所有 ONNX 模型一次性读入内存而是在实际需要推理某个子模型时再加载用完后释放 session。手机上内存有限多个大模型同时驻留内存会直接导致系统杀后台。第二音频数据处理不要依赖 librosa。Termux 环境下 librosa 安装成本高而且它的重采样和静音检测逻辑对嵌入式设备并不友好。用soundfile读取音频配合soxr或自研线性插值做重采样依赖更少运行更快。第三整条流程最终应该封装成一个 API 服务或一个 APK。如果继续使用 Termux可以通过termux-api调用安卓系统能力比如termux-microphone-record录音、termux-notification提示任务进度。如果想做成独立应用可以把 ONNX Runtime 换成 Android 端的onnxruntime-android库用 Java/Kotlin 写 UI模型文件放在 assets 或动态下载目录。这个项目天然适合拆成前后端模型推理部分用 Rust 或 C 重写为 JNI 库前端用 Compose 做界面。第四不要因为能跑就忽略安全与法律边界。GPT-SoVITS 是声音克隆工具在你做任何产品化改造时都要明确参考音频的授权来源避免在未经许可的情况下克隆特定人声。这也是开源社区非常敏感的话题。下一步建议你去读三样东西GPT-SoVITS 官方源码中的inference.py完整推理逻辑、ONNX Runtime 官方文档里的 mobile 部署最佳实践、以及 Termux 社区关于 arm64 Python 打包的讨论。把这三块内容看完你不仅能在安卓上跑 GPT-SoVITS还能迁移到 whisper、vits、bert-vits2 等更多语音项目上。手机端 AI 推理这条路值得折腾的路还很长。