设备端语音合成(On-Device TTS)正在从云端依赖转向本地推理,这对延迟敏感型应用(如实时阅读助手、语音交互界面)至关重要。Supertonic 作为近期开源的 99M 参数多语言 TTS 系统,通过 ONNX Runtime 实现跨平台部署,支持 31 种语言的本地推理,输出 44.1kHz 16-bit 高质量音频。本文聚焦于 iOS 场景下的工程实现,详解 ONNX 图优化策略与 Swift 原生音频管道的集成要点。
架构概览:从文本到音频的端到端链路
Supertonic 的推理流程分为三个阶段:文本归一化与 tokenization、基于 Flow Matching 的声学模型推理、声码器解码。整个流程通过 ONNX Runtime 执行,输出 Float32 格式的原始音频样本,采样率为 44.1kHz。在 iOS 端,需要将 ONNX 输出无缝接入 AVAudioEngine 播放管道,同时通过图优化降低首帧延迟与内存占用。
ONNX Runtime 提供三级图优化:Basic(常量折叠、冗余节点消除)、Extended(GEMM 激活融合、LayerNorm 融合)、Layout(NCHWc 内存布局优化)。对于 TTS 这类序列生成模型,Extended 级别的算子融合能显著减少内存搬运开销,而 Layout 优化则依赖目标设备的 SIMD 指令集支持。
ONNX 图优化策略选择
在线优化 vs 离线优化
在线优化在 Session 初始化时执行,适合开发阶段快速迭代,但会增加冷启动时间。对于生产环境,推荐采用离线优化:使用 SessionOptions.SetOptimizedModelFilePath 将优化后的模型序列化到磁盘,运行时直接加载预优化版本,跳过图变换阶段。
// 离线优化模型生成(开发阶段执行一次)
let sessionOptions = ORTSessionOptions()
sessionOptions.setGraphOptimizationLevel(.ORT_ENABLE_ALL)
sessionOptions.setOptimizedModelFilePath("supertonic_optimized.onnx")
let session = try ORTSession(env: env, modelPath: "supertonic.onnx", sessionOptions: sessionOptions)
离线优化后的模型与硬件绑定,若目标设备支持 AVX2 而开发机不支持,则需在相同指令集环境下执行优化。对于 iOS 的 ARM64 架构,Layout 优化主要利用 NEON 指令,跨设备兼容性较好。
优化级别配置建议
对于 Supertonic 的 99M 参数规模,建议启用 ORT_ENABLE_EXTENDED 级别,开启 Conv-Activation 融合与 Skip Layer Normalization 融合。实测显示,Extended 优化可将推理延迟降低 15-20%,而 Layout 优化在移动端的收益相对有限,可根据具体机型测试后决定是否启用。
Swift 原生音频管道集成
AVAudioEngine 链路搭建
Supertonic 输出的是原始 PCM 样本,需通过 AVAudioEngine 完成播放。推荐架构:单例 TTSService 管理 ONNX Session 生命周期,音频缓冲区通过 AVAudioPCMBuffer 封装,由 AVAudioPlayerNode 调度播放。
final class TTSService {
private let engine = AVAudioEngine()
private let playerNode = AVAudioPlayerNode()
private var ortSession: ORTSession?
init() {
engine.attach(playerNode)
engine.connect(playerNode, to: engine.mainMixerNode, format: nil)
}
func prepare(modelPath: String) throws {
let env = try ORTEnv(loggingLevel: .warning)
let options = ORTSessionOptions()
options.setGraphOptimizationLevel(.ORT_ENABLE_EXTENDED)
ortSession = try ORTSession(env: env, modelPath: modelPath, sessionOptions: options)
}
}
音频格式对齐
Supertonic 输出 44.1kHz Float32 样本,需转换为 AVAudioPCMBuffer 的格式。关键参数:采样率 44100、声道数 1(单声道)、格式为浮点。若需降低内存占用,可在转换阶段量化为 Int16,但需确保音频引擎的格式设置与缓冲区一致。
func playAudio(samples: [Float]) throws {
let format = AVAudioFormat(standardFormatWithSampleRate: 44100, channels: 1)!
let buffer = AVAudioPCMBuffer(pcmFormat: format, frameCapacity: AVAudioFrameCount(samples.count))!
buffer.frameLength = buffer.frameCapacity
// 直接拷贝 Float32 样本
memcpy(buffer.floatChannelData![0], samples, samples.count * MemoryLayout<Float>.size)
if !engine.isRunning {
try engine.start()
}
playerNode.scheduleBuffer(buffer)
playerNode.play()
}
流式播放优化
对于长文本合成,建议采用分块流式策略:每合成 0.5-1 秒的音频片段即送入播放队列,避免等待完整波形生成。AVAudioPlayerNode 支持 scheduleBuffer(_:at:completionHandler:) 的链式调用,可在前一个缓冲区播放完成前预调度下一个,实现无缝衔接。
生产环境 Checklist
- 模型预热:应用启动时初始化 ONNX Session 并执行一次 dummy inference,避免用户首次触发时的冷启动延迟
- 线程隔离:将 TTS 推理置于后台队列(DispatchQueue.global (qos: .userInitiated)),主线程仅处理 UI 更新与音频调度
- 内存管理:Supertonic 的 99M 参数模型在运行时占用约 200-300MB 内存,需在低内存环境下测试并设置合理的缓存策略
- 格式校验:确保 ONNX 输出的样本范围在 [-1.0, 1.0] 内,避免 AVAudioEngine 出现削波或静音
- 离线模型分发:将预优化的
.onnx文件打包至 App Bundle,避免运行时优化的 CPU 开销
局限与权衡
设备端 TTS 虽消除了网络延迟与隐私风险,但仍需面对模型体积与合成质量的权衡。Supertonic 的 99M 参数量在移动端属于轻量级,但在极低内存设备(如 Apple Watch)上仍需进一步量化压缩。此外,离线优化模型与硬件绑定,跨架构(ARMv7 vs ARM64)分发时需维护多版本模型。
资料来源
- Supertonic GitHub 仓库:官方实现与多语言 SDK 示例
- ONNX Runtime 图优化文档:三级优化级别与离线模式配置
内容声明:本文无广告投放、无付费植入。
如有事实性问题,欢迎发送勘误至 i@hotdrydog.com。