本篇速览

  • 语音链路里 LLM 已本地化;若 TTS 仍走云端,断网即哑、数据出设备——这是替换 TTS 环的直接动机。
  • kokoro-onnx 是基于 Kokoro-TTS 与 ONNX Runtime 的轻量本地语音合成封装:README 标称约 300MB(量化后约 80MB),多语言多音色,官方建议用 uv 建隔离环境。
  • 一个 TTS 系统内部有清晰的三段:文本前端 → 时长预测 → 声码器,延迟主要被这三者瓜分,用打点而不是体感来定位。
  • 精度不是只有全精度:导出脚本支持 fp16 与 int8,但仓库提交明确记录"量化副本预测的时长略有差异,只有全精度被验证输出一致"——这是真实的精度坑。
  • 边界必须写清:本地 TTS 的音色丰富度与自然度通常不如云端服务,这是取舍不是缺陷。

一、一句话结论

把语音助手搬到点位上,最容易被人忽略的一环是"说话"。很多人把 LLM 和 ASR 都本地化了,TTS 却还留着云端接口——结果断网时整条链路立刻哑火,前面所有本地化努力都白做。本篇用一个轻量组件 kokoro-onnx 把 TTS 也搬下来,并把它从外到内拆一遍,让你知道"一段文字变成声音"这件事,时间到底花在哪儿。


二、背景:为什么先拆 TTS 环

2.1 链路里 TTS 是"最后一跳"

回顾整条链路:麦克风 → VAD → ASR → LLM → TTS → 扬声器。前四环都本地化之后,TTS 是最后一跳。这一跳如果还在云端,有两个后果:

  1. 断网即哑:虽然 LLM 已经能本地回答了,但答案合成不出声音。
  2. 数据出设备:要合成的文本(可能是对话内容)经过外部 TTS 服务。

所以 TTS 本地化不是"锦上添花",而是"闭环"的必要一步。

2.2 为什么选 kokoro-onnx

选型时考虑过更知名的 Piper,但它的主仓库最后 Release 停在 2023-11-14、最后提交 2025-08-26,近三年无 Release(GitHub 元数据),且 sherpa-onnx 的示例集已包含 Piper TTS,素材会与 A 类主线重叠。kokoro-onnx 近期仍在更新(代码最近 push 2026-09-01),且已支持导出 fp16 / int8 精度副本,能做出"三种精度对照"的完整实验。许可证也清晰:README 的 License 小节写明代码为 MIT、模型为 Apache 2.0,没有双许可陷阱。

2.3 它是什么

kokoro-onnx 是基于 Kokoro-TTS 与 ONNX Runtime 的轻量级本地语音合成封装。README 的 Features 明确写出:约 300MB(量化后约 80MB)、在 macOS M1 上达到接近实时的合成速度、支持多种语言、提供多个音色。具体音色与语言清单由上游 Kokoro-82M 仓库的 VOICES.md 维护。使用方式为安装 kokoro-onnx 包,下载 kokoro-v1.0.onnx 与 voices-v1.0.bin 两个文件后直接调用,官方建议用 uv 建立隔离环境,v1.0 起搭配 misaki 做音素前端。

“near real-time on macOS M1” 是在特定硬件上的表述,搬到 ARM 开发板上必须重测,不能套用。这也是为什么本篇所有性能数字都标注来源与条件——同一个组件在不同板子上的表现可能差出数倍,拿一处的数据当通用结论会误导选型。

2.4 kokoro 的模型与音色是怎么组织的

kokoro-onnx 把"模型权重"和"音色表"拆成了两个文件:kokoro-v1.0.onnx 是声学模型本身,voices-v1.0.bin 是预训练的音色向量集合。这种拆分带来一个工程上的便利:换音色不需要换模型,只换 voices 里的向量即可。具体有哪些音色、每种音色的语言与风格,由上游的 VOICES.md 定义,命名通常带语言/性别/风格前缀(如 af_* 表示美式英语女声一类)。选型时要先翻 VOICES.md,确认你需要的语言确实有对应音色,再决定用不用——没有对应音色的语言,硬上要么报错要么效果差。

2.5 许可证与版本

代码 MIT、模型 Apache 2.0,这对要落地到产品的团队是友好的:MIT 宽松到几乎无附加条件,Apache 2.0 还带专利授权。版本方面,模型 Release 为 model-files-v1.1,代码最近有 push(2026-09-01)。版本迭代快,发布前建议再核一次模型文件名与接口是否变化——尤其是 voices 文件的版本要和 onnx 模型版本匹配,错配会导致加载报错或音色错乱。版本校验还有一个动作:下载后比对文件的字节数或官方提供的校验和,排除传输损坏。模型文件损坏的表现前文 2.6 已提过,这里再强调一次——版本与完整性是模型落地的两道基础关,往往被急于跑通的人跳过,最后却花更多时间排"灵异"问题。


2.6 模型文件要不要校验完整性

从官方仓库下载 kokoro-v1.0.onnx 与 voices-v1.0.bin 后,建议对文件做完整性确认:记录下载后的字节大小,若仓库提供了校验和就比对,没有就用字节数 + 后续能否正常加载来间接确认。模型文件在网络传输中损坏后会表现为加载时报"文件截断""magic number 错误"或推理输出全零/乱码,排查时先怀疑下载完整性而不是代码。把实际文件名、字节数、来源 URL 写进 env.txt,是改造类项目的基本纪律。

2.7 本篇与 A 类 TTS 的关系

A 类把 TTS 作为 sherpa-onnx 的一部分提过(其示例集含 Piper、Matcha 等),本篇为何另写 kokoro-onnx?因为两者是"同一环的不同实现":sherpa 的 TTS 和 VAD/ASR 同库,适合"一个库收三环";kokoro 是专门的轻量 TTS 封装,体积与精度对照更干净,适合"只替换 TTS 一环、且要 fp16/int8 三种精度实验"的场景。两篇不是重复,而是给 TTS 这一环两种可替换的实现选项——你按"要不要和 ASR 同库""要不要做精度对照"来选。

三、安装与运行

3.1 官方推荐做法

README 的 Setup 小节给出(以项目文档当前版本为准):

pip install -U kokoro-onnx        # 包名以官方文档为准
# 或官方推荐的 uv 隔离环境
uv add kokoro-onnx soundfile

然后下载两个文件:kokoro-v1.0.onnx(模型)与 voices-v1.0.bin(音色),放进同一目录即可运行。v1.0 起建议搭配 misaki g2p 做音素前端。

3.2 一个接口面很窄的好处

它依赖少(主要是 onnxruntime 与音频读写),核心用法只有几行。这让一篇功能拆解文能把整个组件从上到下讲清楚,写到字节与毫秒的粒度,而不是停在"调用示例"这一层。窄接口也意味着出错面小——能出错的地方就那么几个:模型/音色路径、输入文本格式、输出采样率、运行时版本。

3.3 一个最小可运行示例

# 接口形态示例(实际 API 以官方文档当前版本为准)
from kokoro_onnx import Kokoro
import soundfile as sf

# 模型与音色两个文件需提前下载到本地
kokoro = Kokoro("kokoro-v1.0.onnx", "voices-v1.0.bin")

# 核心调用:文本 → 音频
audio, sample_rate = kokoro.create(
    text="今天天气不错,我们出去走走吧。",
    voice="af_heart",     # 音色名,需存在于 voices 表
    speed=1.0,            # 语速
    # lang="zh"           # 语言提示,按文档可选
)

# 写出可播放文件
sf.write("out.wav", audio, sample_rate)

要点:voice 必须是 voices 表里真实存在的键,传一个不存在的音色名会在加载或推理阶段报错。这一段就是 TTS 环在链路里被调用的最小形态——LLM 产出文本后,调一次 create 拿到音频,再交给播放线程。工程上建议在 create 外包一层异常处理:捕获模型加载失败(路径/版本错)、推理异常(输入格式错)、以及音频写出失败(路径权限)。把异常类型和触发条件记到日志,比"合成没声音"这种笼统反馈好定位得多。尤其模型/音色路径错在板子上很常见(见 10.x 的路径与版本问题)。


四、分段耗时在哪

4.1 TTS 内部的三段

一段文字变成声音,内部大致是三段流水线:

文本前端(文字 → 音素/拼音)
   ↓
时长预测(每个音素持续多久)
   ↓
声码器(声学特征 → 波形)

每一段都有自己的耗时,定位瓶颈要先分清楚是哪一段慢,而不是笼统说"合成慢"。

4.2 文本前端:中文尤其不能省

英文可以靠字母拼读,中文必须把汉字转成拼音或音素才能合成。这一步通常叫 g2p(grapheme-to-phoneme)。kokoro-onnx 在 v1.0 起明确建议搭配 misaki 做音素前端。漏掉这一步是中文 TTS 最常见的翻车点——直接把汉字丢进模型,要么报错,要么读音全错。

更细一点:misaki 对英文有成熟的音素前端,但中文需要对应的中文 g2p 支持;如果目标语言是中文,要确认 misaki 的中文路径可用,或换成其他中文 g2p 工具,再喂给模型。这一处"语言—前端"的匹配,是 kokoro-onnx 在多语言落地时最容易踩的坑。

4.3 时长预测与声码器

时长预测决定"每个字拖多长",声码器决定"最后的波形长什么样"。前者影响节奏自然度,后者影响音色。对延迟而言,声码器往往是耗时大头,尤其是高采样率输出时。定位方法很朴素:在三段之间各打一个时间戳,看哪一段占的时间最长。如果声码器占了一半以上,优化重点就应该是声码器(或换更快的声码器 / 降采样率),而不是去折腾文本前端。补充一点通用背景(不特指 kokoro 内部实现):声码器的任务是把声学特征(如 mel 谱)还原成时域波形,主流路线有基于 GAN 的、基于流(flow)的、以及基于扩散(diffusion)的,各有速度与质量的取舍。端侧更看重速度与体积,所以落地时常常选轻量声码器,代价是高频细节略损。了解这个大背景,有助于理解"为什么声码器是耗时大头、又为什么量化后音质先在这里掉"。

4.4 用打点而不是体感

不要靠"感觉合成挺快"。在固定测试文本集上跑,记录三段各自的耗时中位数,再乘以一轮对话的句数(句级流式下每轮会合成多句),得到整轮的合成耗时。这样才有数字可对照,也才能在优化时知道该动哪一段。

4.5 一个打点示例

import time
t0 = time.perf_counter()
phonemes = g2p(text)              # 文本前端
t1 = time.perf_counter()
durations = kokoro.predict_duration(phonemes)  # 时长预测(示意)
t2 = time.perf_counter()
audio = kokoro.vocode(phonemes, durations)     # 声码器(示意)
t3 = time.perf_counter()
print(f"前端 {(t1-t0)*1000:.1f}ms | 时长 {(t2-t1)*1000:.1f}ms | 声码 {(t3-t2)*1000:.1f}ms")

上面的 predict_duration / vocode 是示意拆分,实际 API 可能把这几步包在 create 里一次完成;打点时要按真实 API 的可拆分点来插桩,不要凭示意代码硬套。实务上 kokoro-onnx 的 create 往往一步完成三段,要打点就要在库内部或前后包裹计时;若库不暴露中间产物,可用"输入前时间戳"与"输出后时间戳"先算出总耗时,再用"固定开销 + 单句耗时 × 句数"的结构反推各段占比。反推虽不如直接插桩准,但足以定位"瓶颈大概在哪一段"。


4.6 多音字与数字:中文前端的两道坎

中文音素前端最棘手的是多音字(如"银行"的"行"和"行走"的"行")和数字的读法中英文混排。g2p 模型若训练语料覆盖不足,会把多音字读错、把"2026"读成"两千零二十六"或逐位读。这一项要在测试集里专门准备"含多音字 + 含数字 + 中英混读"的句子,逐句核对读音。它不是 kokoro 独有的问题,而是所有中文 TTS 的共性难点,写稿时要作为"前端质量"的硬检查项列出。

4.7 不同语言的文本前端差异

kokoro 走多语言路线,但每种语言的 g2p 难度不同:英文靠字母拼读相对规则,中文必须做汉字→拼音,日文要处理假名与汉字混排,韩文有自身的字母组合规则。落地多语言时,要为每种目标语言确认其前端的可用性(misaki 对中文的支持程度就是一例),不能假设"一个前端通吃所有语言"。缺某语言前端时,要么补该语言的 g2p,要么该语言退回到云端 TTS,不要硬上。

五、体积与精度:三种档位

5.1 为什么是三种

仓库近期的提交记录说明,导出脚本 scripts/export.py 支持 --fp16 与 --int8,会生成全精度之外的降精度副本。三种档位对应不同的体积与质量:

档位体积(量级)速度质量风险
fp32(全精度)最大最慢基准,输出被验证一致
fp16介于中间居中通常可接受
int8最小(约 80MB 量级)最快可能引入可闻差异

5.2 一个真实的精度坑

仓库提交里明确写道:量化副本预测的时长略有差异,因此只有全精度导出被验证为输出一致。这句话值得放大——它说明量化不只是"体积变小",还会让"每个音素拖多长"的预测偏掉,最终表现为节奏不对、停顿位置异常。

所以精度选择不能只看体积,要实测:用固定文本合成后,主观听节奏与音色,并对比全精度版本。把"量化后时长预测差异"作为一个专门的检查项,比只看"音色像不像"更全面。

5.3 怎么实测精度差异

一个可复用的实验:取同一段固定文本,分别用 fp32 / fp16 / int8 三个模型合成,做三件事:

  1. 节奏检查:把三段音频对齐播放,听停顿位置与语速是否一致;
  2. 音色检查:听音色的明亮度、共振峰是否偏移;
  3. 客观检查:算三段音频的时长差、以及和参考(fp32)的波形相似度(如 MSE)。

只有三项都过,才能放心用低精度副本上板;任何一项明显异常,就退回更高精度。

5.4 量化对体积/速度/质量的综合影响

把 5.1 的三方表在真机上填实:记录每个档位的模型体积(磁盘)、首次推理耗时、单句合成耗时、以及 5.3 的主观/客观评分。板子内存紧时,int8 的体积优势可能直接决定"装不装得下";但一旦质量掉到不可接受,体积优势就毫无意义。所以这张表是“能不能上”和“上哪个档”的联合判据。一个具体的填法示例(数字为推算,真机替换):fp32 体积 300MB、首帧 260ms、单句 450ms、节奏评分 5/5;int8 体积 80MB、首帧 180ms、单句 300ms、节奏评分 4/5 且有可察停顿偏移。当板子内存只够装 int8 时,4/5 的节奏若产品可接受,int8 就是答案;若不可接受,就要么换更小模型、要么接受保留部分云端。这张表的真正价值,是把“上不上、上哪个档”从拍脑袋变成查表。


六、流式策略:边合成边播放

6.1 为什么不能整段等完

在语音对话里,TTS 如果是"整段合成完再播",用户要等很久才听到第一个字。句级流式的做法是一句一句合成、合成完一句就播一句,把首帧延迟压到可接受。

6.2 句长与延迟的关系

整轮合成耗时大致随句数线性增长。如果每轮对话合成 5–8 句,每一句的固定开销(初始化、文本前端、首帧)都会被乘上句数。优化时有两个方向:一是缩短不必要的固定开销,二是把可并行的部分(如提前对下一句做文本前端)前移。这两个方向的共同前提是 6.4 的队列设计——如果队列不做背压,优化单句耗时的收益会被上游突发灌入抵消,所以 TTS 优化要和编排层的队列参数一起调。

6.3 长度过长要切块

超长文本一次性合成会占用大量内存并拉长首帧。常见做法是按标点切句,逐句合成;切句的同时要注意"句尾停顿"自然,避免切成碎句后听起来像电报。切句逻辑要和 LLM 的输出节奏配合——LLM 边生成边吐句,TTS 边收边合成,两者用有界队列衔接(见第一章骨架)。一个切块的工作例:以中文标点为切分依据,句号/问号/感叹号作为强切分,逗号作为弱切分(弱切分累积到一定长度再切),同时限制单句最大字数(如 30 字)防止超长。这样既能控制首帧,又保留自然停顿,避免切成碎句后听起来像电报。

6.4 句级流式的队列设计

沿用第一章的"本地多线程 + 有界队列"骨架,TTS 环节的输入是 LLM 产出的"文本句队列",输出是"音频队列"喂给播放线程。关键参数有两个:队列长度上限(防止 LLM 突发吐很多句把内存堆满)和背压策略(队列满时 LLM 侧应暂停生成或丢弃最旧句)。这两点和 VAD/ASR 的队列是同一套方法论,整套链路的稳定性取决于每个环节都正确做了背压。


6.5 实时性预算:一句话要多少毫秒

把 6.2 的"句数 × 固定开销"落成可算的数字。假设单次 create 的首帧约 200ms、单句合成约 400ms(示意,真机实测替换),一轮对话合成 6 句,则合成总耗时约 6 × 400 = 2400ms,加上首帧 200ms 约 2600ms。这个数字要和 LLM 生成耗时叠加看——如果 LLM 生成 6 句花了 3000ms,而 TTS 合成它们要 2600ms,且两者是串行的,整轮就要 ~5600ms。优化思路要么是并行(LLM 出一句 TTS 合成一句),要么是压 TTS 单句耗时(见 4.3 声码器优化)。所有数字为示意,真机填入实测。

七、音色取舍

7.1 音色丰富度不如云端

本地方案的音色数量通常少于云端 TTS 服务(如 CosyVoice、EdgeTTS 之类)。如果产品对"角色音色"要求高,本地化可能要接受音色选择变少这一代价。若还想要品牌专属音色,就要考虑能否用少量数据微调——而微调又带来训练与分发成本,这要在选型时一并算进去,而不是只看"本地 TTS 免费"这一面。

7.2 自然度通常不如云端

端侧小模型的韵律与自然度,通常不敌云端的大模型 TTS。这不是缺陷,是端侧方案的固有取舍,必须写明。

7.3 怎么写才诚实

在对照表里同时给出"本地 TTS"和"云端 TTS"的音色数量与自然度(主观评分),并说明测量条件。优势(离线可用、数据不出设备、零调用成本)和边界(音色少、自然度弱)一起列,比单写优势可信得多。写对照表时,还建议加一列"测量条件",注明测试集、播放设备、板子温度——否则不同条件下的分数放一起比没有意义。诚实的对照不是比谁分数高,而是让读者知道"在你的场景下,本地 TTS 能不能用、代价是什么"。

7.4 音色选择的方法

不要凭名字挑音色。实务上把 VOICES.md 里的目标语言音色逐个合成同一段测试文本,主观打分(自然度、有无电音/破音、停顿是否自然),选出 2–3 个稳定可用的,作为产品默认音色池。这一步在真机上做,因为同一音色在不同 ORT 版本/不同板子上的表现可能略有差异。


7.5 韵律与标点

TTS 的"自然度"很大程度来自韵律,而韵律的输入常常是标点与句法。句号、逗号、问号对应的停顿长短不同,缺失或错误的标点会让合成听起来像一口气念完。在 LLM 产出文本后、送进 TTS 前,最好做一次"标点规整":补全缺失标点、把异常的长句按语义切分。这一步看似小事,却常常是"本地 TTS 听着比云端生硬"的关键差异点之一——云端大模型的韵律建模更强,本地小模型更依赖正确标点。

八、集成路径

8.1 挂回主链路

把 kokoro-onnx 的合成函数封装成一个本地调用,由编排层在 LLM 产出文本后调用。关键改动是:原来调用云端 TTS 的 base_url 换成本地函数,其余编排(句级流式、边合成边播)沿用第一章的骨架。

8.2 改造前后差异

改造前:LLM → 云端 TTS(网络 API) → 播放
改造后:LLM → 本地 TTS(onnxruntime) → 播放

差异只有"TTS 这一跳是否出设备"。这一跳的消除,正是离线可用与数据不出设备两条优势的直接来源。

8.3 与第一章 LLM 编排的衔接

第一章把 LLM 换成了本地 OpenAI 兼容服务,链路此时是 VAD→ASR→LLM(本地)→TTS(云端)→播放。本篇把 TTS 这环也换成本地后,整条链路在编排上只剩"本地调用 + 本地队列",没有任何一环需要网络。衔接点就是 LLM 文本输出队列的下游消费者从"云端 TTS 客户端"换成"本地 kokoro-onnx 调用"——接口形态尽量保持一致(输入文本、输出音频流),让编排层无感切换。

(接 8.3)一个串起整链路的计时草图(数字为引用/推算,真机替换):LLM 首 token 300–600ms、后续每句生成约 500ms、TTS 每句首帧 200ms 加合成 400ms。若 LLM 与 TTS 串行,一轮 6 句约 (600 + 6×500) + (200 + 6×400) ≈ 3600 + 2600 = 6200ms;若改为 LLM 出一句 TTS 合成一句的流水线,瓶颈变为两者较慢者,总耗时接近 max 侧而非求和。这说明"串行 vs 流水线"对整轮体感影响巨大,是本地化后最该优化的架构点。

第一章把 LLM 换成了本地 OpenAI 兼容服务,链路此时是 VAD→ASR→LLM(本地)→TTS(云端)→播放。本篇把 TTS 这环也换成本地后,整条链路在编排上只剩"本地调用 + 本地队列",没有任何一环需要网络。衔接点就是 LLM 文本输出队列的下游消费者从"云端 TTS 客户端"换成"本地 kokoro-onnx 调用"——接口形态尽量保持一致(输入文本、输出音频流),让编排层无感切换。


九、质量评估与对照实验

9.1 主观评测怎么做得不走过场

本地 TTS 的质量不能只靠"我听着还行"。一个可重复的主观评测:找若干条覆盖不同句长、不同语气、含数字/专有名词的测试文本,由 3 人以上独立打分,维度包括自然度(1–5)、 intelligibility(能否听清)、韵律(停顿是否自然)。取平均分与方差,方差大说明不稳定,不能只看均值。多人评测之所以必要,是因为单人的"好听"受个人口音偏好影响很大;3 人以上独立打分取均值,能抵消个体偏差。测试文本要覆盖不同情绪与句长,因为 TTS 在长句、感叹句、疑问句上的韵律表现往往和陈述句不同。评测结果要记"平均分 + 方差 + 最差样本",最差样本往往比均值更能暴露问题。

9.2 与云端 TTS 的对照表模板

指标本地 kokoro-onnx云端 TTS(如 CosyVoice)测量条件
音色数量待填(看 VOICES.md)待填—
自然度(主观 1–5)待填待填同一测试集
首帧延迟待填待填固定文本
单句合成耗时待填待填固定文本
是否出设备否是—
调用成本0按量计费—

上表实测列待真机填入,发布前不许留空;"自然度"为主观分,要注明评测人数与测试集。

9.3 控制变量

对照时控制:同一批测试文本、同一录音回放设备、相近板子温度。不要让"本地在冷板子上测、云端在网络好的时候测"这种变量污染结论。尤其延迟项,本地要等板子温度稳定再测,云端要标明网络往返。另外,对照结论要写成"在什么条件下本地 TTS 可替代云端",而不是"本地明显占优云端"——本地 TTS 在离线/隐私/成本上占优,在音色丰富度与自然度上通常让步,这是条件化的取舍,不是优劣。结论里把"占优项"和"让步项"并列,读者才能照着自己的产品需求判断。


9.4 评测要避免的偏差

做主观评测时几个常见偏差要避开:一是"用自己的声音偏好打分",应多人独立;二是"只测陈述句",忽略了疑问/感叹/长句的韵律;三是不记录测试集,导致结论无法复现。客观评测也要避免"只测首帧不测长句"——长句才暴露内存峰值与韵律衰减。对照实验的价值在于可复现,而可复现的前提是测试集、设备、条件都固定并写清楚。

十、常见故障

10.1 misaki 装不上或中文路径缺失

misaki 是音素前端依赖,版本不匹配或缺少中文支持时会报错。排查:确认 misaki 版本与 kokoro-onnx 文档要求一致;中文场景确认 misaki 的中文 g2p 可用,否则换其他中文前端。漏装或版本错是最常见的"合成报错"源。

10.2 音色文件版本错配

voices-v1.0.bin 要和 kokoro-v1.0.onnx 的版本匹配。模型升级后旧 voices 文件不兼容,表现为加载报错或音色错乱。做法是模型与音色成对下载、成对升级,并在 env.txt 记录两者的版本号。

10.3 输出采样率与播放不一致

模型输出采样率(如 22050 或 24000)要和播放设备支持的采样率对齐,否则变调或播放失败。合成后若直接写 wav 播放没问题;但若要经过其他音频管线,要显式重采样到设备支持的速率。

10.4 ORT 版本与算子

和 A 类一样,kokoro-onnx 依赖 ONNX Runtime。ORT 版本过旧可能缺模型用到的算子。板子上用不低于模型导出时依赖的 ORT 版本,或按板子 ORT 重新导出。跨平台搬运时同一版本号也可能因构建选项不同而缺算子。

10.5 内存峰值

声码器在高采样率、长文本下内存占用会跳高。板子内存紧时,用 5.4 的体积/速度表选更低精度,并把长文本切块(见 6.3)来控制峰值。监控峰值用 /proc/<pid>/status 的 VmHWM,而不是只看平均占用。


10.6 多音字读错

如前 4.6 所述,多音字、数字、中英混读是中文前端高发问题。若合成出现明显读错,先定位是前端(g2p)给的音素就错了,还是模型阶段读错——把 g2p 的输出音素打印出来核对,能快速区分。前者补前端词典/规则,后者只能换模型或接受。

10.7 中英文混读断点异常

中英混读时,前端若把英文词拆成字母逐个读、或把中文按英文规则处理,会出怪音。排查要确认中英文的 g2p 分别正确,且在语言切换处有合理的韵律边界。这一项要专门准备中英混读测试句。

十一、选型决策:什么时候本地 TTS 值得

本地 TTS 不是对所有产品都划算,决策看三件事:

  1. 离线/隐私是否是硬需求:如果是(如本地机器人、涉密场景),本地 TTS 是必选项,自然度和音色少都要接受。
  2. 音色/自然度的下限是否可接受:用 9.1 的评测确认本地 TTS 的自然度达到产品最低标准;达不到就宁可保留云端或换更强的本地模型。
  3. 板子资源是否装得下:用 5.4 的体积表确认模型(尤其全精度)能装、内存峰值可控。

三者都满足,本地 TTS 就是理所当然的闭环一步;任一不满足,就退回到"本地为主、云端兜底"的混合(这一架构在 B 类第 4 篇讲网关路由时会展开)。


11.4 选型检查清单

把 11.1–11.3 收敛成一张清单,换产品或换板子时照着打勾:

  • 离线 / 隐私是硬需求吗?(是 → 本地必选)
  • 本地 TTS 自然度达到产品下限了吗?(9.1 评测)
  • 模型(含全精度)装得下、内存峰值可控吗?(5.4)
  • 目标语言前端可用吗?(2.4 / 4.7)
  • 音色池稳定可用吗?(7.4)

任一不满足,考虑混合架构或换更强本地模型。这里说的"换更强本地模型"不是任意换——要回到 5.4 的体积/质量表,看有没有"体积可接受、质量达标"的候选;没有就接受混合架构(本地为主、罕见/高难请求回退云端),而不是硬撑一个不达标的纯本地方案。

十二、常见问题(FAQ)

问:kokoro-onnx 许可证清晰吗?
清晰。README 的 License 小节写明代码为 MIT、模型为 Apache 2.0,没有双许可陷阱,采用前不必额外纠结。

问:中文 TTS 为什么要做音素前端?
因为模型接收的是音素/拼音而非汉字。中文必须先把汉字转成拼音或音素(v1.0 起建议用 misaki g2p),漏掉这一步要么报错要么读音全错。

问:量化后体积能小多少?
README 给出全精度约 300MB、量化后约 80MB 的标称量级,但"near real-time on macOS M1"是特定硬件下的表述,搬到开发板必须重测。

问:量化会影响合成质量吗?
会,且不止体积。仓库提交明确记录量化副本的时长预测有差异,只有全精度被验证输出一致。选型时要实测节奏与音色。

问:TTS 本地化后断网能用吗?
如果 LLM、VAD、ASR、TTS 全部本地化,整条链路断网可用。TTS 是"最后一跳",它本地化之后闭环才真正成立。

问:本地 TTS 和云端 TTS 怎么公平对照?
用 9.2 的对照表,控制同一测试集、同一播放设备、相近温度,分别记自然度(主观分)、首帧、单句耗时、是否出设备、调用成本。不要只比延迟,要把边界(音色少、自然度弱)一起列。

问:音色文件能和模型分开升级吗?
不建议。voices 与 onnx 版本要匹配,模型升级后旧 voices 可能不兼容。两者成对下载、成对升级,并在 env.txt 记录版本。

问:声码器慢,能单独优化吗?
可以。声码器通常是耗时大头(见 4.3),优化重点应放在它:换更快的声码器、降输出采样率、或对声码器做量化。前提是 5.3 的质量检查仍通过。

问:多音字和英文混读本地 TTS 容易错吗?
容易,这是中文 TTS 的共性难点。测试集要专门准备多音字、数字、中英混读句,逐句核对读音;出错时先打印 g2p 音素区分是前端错还是模型错(见 10.6)。

问:标点会影响本地 TTS 的自然度吗?
会,而且影响很大。缺失或错误的标点让合成失去韵律边界。LLM 文本送 TTS 前做一次标点规整,常是"听感变自然"的关键一步(见 7.5)。

问:本地 TTS 能和云端混合用吗?
能。当本地自然度不达标或遇罕见语种时,可让这部分请求回退云端(架构见 B 类第 4 篇的网关路由)。但要记住回退意味着该次数据出设备,需按场景显式决策。

问:本地 TTS 在开发机(如 Mac M1)上跑得快,板子上也一定快吗?
不一定。README 的"near real-time on macOS M1"是特定硬件表述,M1 的 CPU 与神经引擎和 ARM 开发板不同。板子上的实际速度必须真机实测,不能套用开发机数字。

问:多音字读错是模型问题还是前端问题?
两类都有可能。定位方法是打印 g2p 输出的音素:如果音素本身就读错,是前端(g2p)问题;音素对但合成读错,是模型问题。前者补前端词典,后者换模型(见 10.6)。

问:kokoro 和 A 类里的 sherpa TTS 什么关系,会不会重复?
不重复。两者是 TTS 这一环的不同实现:sherpa 的 TTS 与 VAD/ASR 同库,适合"一个库收三环";kokoro 是专门的轻量 TTS 封装,适合单独替换 TTS 且做精度对照。按"要不要和 ASR 同库""要不要做 fp16/int8 实验"来选(见 2.7)。

问:本地 TTS 合成时卡顿或首帧很长,怎么查?
先按 4.4 的三段打点定位是前端、时长还是声码器慢;再确认是否长文本未切块(见 6.3)、是否模型每次都重新初始化、以及板子是否降频。多数"卡顿"是长文本未切块与串行等待叠加,按 6.5 的预算逐项核对。

问:本地 TTS 输出有杂音或电音,可能是什么原因?
常见三类:一是声码器量化(int8)引入的高频伪影,回到 fp16/fp32 验证是否消失;二是输出采样率与播放设备不匹配导致的重采样失真;三是 ORT 版本/算子差异导致数值不稳定。按"换精度→对齐采样率→换 ORT 版本"的顺序排查,比盲目调参数快。

问:本地 TTS 能不能做流式(边合成边播)?
能,且这是推荐做法(见 6.1)。做法是句级流式:LLM 边生成边吐句,TTS 边收边合成边播放,而不是等整段文本到齐再合成。前提是 LLM 的流式输出和 TTS 的句级切分配合好,二者用有界队列衔接(见 6.4)。


十三、总结与可带走物

本篇把本地 TTS 这一环从动机讲到内部拆解:替换动机是"闭环保环",内部拆解给出文本前端 / 时长预测 / 声码器三段,精度拆解给出 fp32 / fp16 / int8 三档并点出"量化改的不只是体积、还有时长预测"这个真实坑。

可带走的产出:TTS 三段打点方法、三种精度对照表(体积/速度/质量)、与云端 TTS 的对照实验模板(自然度/首帧/成本)、句级流式的队列设计要点、"什么时候本地 TTS 值得"的决策框架、以及中文前端的多音字/数字/中英混读检查清单。

最后落到一条:本地 TTS 的优势(离线、数据不出设备、零调用成本)和边界(音色少、自然度弱)要一起写;只写一边,结论就立不住。

Logo

作为“人工智能6S店”的官方数字引擎,为AI开发者与企业提供一个覆盖软硬件全栈、一站式门户。

更多推荐