技术背景:为什么要把 ChatTTS 和 Pynini 绑在一起?
ChatTTS 是最近社区里热度很高的端到端中文语音合成库,支持多说话人、情感控制、语速调节,一句话:把文字扔进去,就能拿到“带情绪”的语音。
Pynini 则是 Google 开源的有限状态转换(FST)工具,用 C++ 写成,Python 层封装得足够友好,专门做文本归一化(TN)、分词、韵律预测这类“文本→音素”的脏活累活。
在 Windows 上做中文 TTS 产品,90% 的崩溃都卡在两条线:
- 文本预处理不干净,数字、字母、符号读得尴尬;
- 语音后端装不上,或者装上了跑不动。
把 ChatTTS 当“声码器”、Pynini 当“文本清洗器”,两条线一接,就能把“原始文本→干净音素→自然语音”整条链路跑通,而且全链路 Python 可控,方便后面做批量化、服务化。
环境准备:先把 Windows 的坑填平
Windows 不是 Linux,别指望apt-get一行命令解决战斗。下面给出我踩了三天总结出的“最小存活路径”。
系统要求
- Win10 21H2 及以上,64 位,BIOS 里打开虚拟化(后面 WSL2 要用)。
- NVIDIA 驱动 ≥ 512.15,且带 CUDA 11.8 以上(ChatTTS 默认用 GPU 跑得快)。
安装顺序(一步都不能跳)
- 安装 Visual Studio Build Tools 2022,勾选“C++ 桌面开发”与“Windows 11 SDK”。
- 安装 Miniconda,用
conda而不用系统 Python,避免 DLL 地狱。 - 创建干净环境:
conda create -n tts python=3.10 -y conda activate tts - 先装 Pynini 的“二进制轮子”,别直接
pip install pynini,会编译到哭。
如果找不到对应轮子,就打开 WSL2 用pip install https://github.com/kpu/kenlm/releases/download/v0.1/pynini-2.1.5-cp310-cp310-win_amd64.whlapt install libfst-tools编译好,再把.so文件拷回 Windows,亲测可行。 - 安装 ChatTTS
这一步会自动拉pip install ChatTTStorch>=2.0,但 Windows 下经常把 CUDA 版本搞错。如果torch.cuda.is_available()返回 False,手动重装:pip uninstall torch -y pip install torch torchvision torchaudio --index-url https://download.pytorch.org/whl/cu118 - 校验
在 Python 里执行:
全部不报错,环境准备 OK。import ChatTTS, pynini, torch print(torch.__version__, torch.cuda.is_available())
集成实战:30 行代码跑通“文本→语音”
下面给出单脚本版本,保存为chatts_pynini_demo.py,可直接python chattds_pynini_demo.py跑。关键注释已写在行尾,方便二次开发时直接改。
# -*- coding: utf-8 -*- """ Windows 下 ChatTTS + Pynini 最小可运行示例 依赖: conda activate tts """ import ChatTTS import pynini as pn import soundfile as sf import re import os # 1. 文本归一化规则(示例级,可继续往里面加) def text_normalize(text: str) -> str: """用 Pynini 做数字、缩写、符号的 FST 转换""" # 数字转中文读法 digit = pn.union(*"零一二三四五六七八九") digit_star = pn.closure(digit, 0) # 0-99 整数 num_fst = (digit + pn.closure(digit, 0)) | (digit + "十" + digit_star) # 简单映射 1->一 num_fst @= pn.cross("1", "一") # 交叉编译 # 这里只演示思路,生产环境请用更完整的 grammar out = num_fst.optimize().rewrite(text) # 去掉标点,ChatTTS 训练时没喂太多符号 out = re.sub(r"[,。!?;:]", " ", out) return out # 2. 加载 ChatTTS chat = ChatTTS.Chat() chat.load(compile=False) # compile=True 会 JIT,第一次慢,生产环境可打开 # 3. 推理 raw_text = "2024年6月,BTC价格突破69000美元!" # 带数字+英文+符号 norm_text = text_normalize(raw_text) print("归一化后文本:", norm_text) wav = chat.infer(norm_text, use_decoder=True) sf.write("demo.wav", wav[0][0], 24000) # 24kHz 采样率 print("已写入 demo.wav")跑通后目录会多一个demo.wav,播放检查数字是不是读成“二零二四……”,如果是,说明 Pynini 链路生效;如果声音正常,说明 ChatTTS 后端 OK。
性能优化:让 GPU 别吃满、内存别爆炸
- 批量化
ChatTTS 的infer()支持列表输入,一次性喂 32 条文本比 for 循环快 3 倍,但注意总 token 别超 2k,否则显存直接 OOM。 - 半精度
在load()里加dtype=torch.float16,显存占用直接减半,音质 AB 测试听不出区别。 - 进程模型
Windows 的torch.multiprocessing会拖 DLL,建议用fastapi+uvicorn单进程异步,外部用 nginx 负载均衡,别硬上多进程。 - 内存增长
每次infer()后手动torch.cuda.empty_cache(),否则连续调 200 次会看见内存阶梯式上涨,不是泄露,是 CUDA 缓存没释放。 - 模型裁剪
如果只做单人朗读,可在chat.load_models()时只拉decoder部分,encoder 用dummy=True,模型从 1.2G 降到 400M,冷启动快 40%。
避坑指南:五个高频错误与解药
ImportError: cannot import name 'Fst'
原因:装了pynini但 Win 下缺libfst.dll。
解决:回退到上文给出的轮子版本,或把openfst-1.8.2-win64/bin加入系统 PATH。RuntimeError: CUDA error: invalid device ordinal
原因:笔记本双显卡,ChatTTS 默认找 GPU 1,但 Windows 只有 0。
解决:chat.load(device=0)显式指定。语音出来全是“滋滋”噪声
原因:采样率错配,ChatTTS 输出 24kHz,直接当 16kHz 写文件。
解决:sf.write()时写对 24000,或重采样到 16k 再写。Pynini 规则不生效,数字原样输出
原因:FST 没编译进去,用的仍是 Python 层字符串。
解决:在text_normalize()里打印num_fst.optimize().num_states(),若等于 0 说明规则空,检查pn.cross语法。打包 exe 后闪退
原因:PyInstaller 没抓到libfst.dll。
解决:spec 文件加binaries=[('libfst.dll', '.')],并关闭--windowed,用控制台调试一目了然。
留给你的作业:做一个“自定义情感旋钮”
今天的脚本只走了“文本→语音”最短路径。ChatTTS 其实支持params_refine_text=True时传入情感向量,维度 256,值域 [-1,1]。
尝试步骤:
- 在
chat.infer()里加params={'emotion': [0.5]*256},听一听是不是更“开心”。 - 用 Gradio 画 256 个 slider,让用户自己拖,实时推理。
- 把最顺耳的向量存成 json,下次直接当模板。
做好后把 GitHub 链接甩到评论区,一起交流调参玄学。祝你编译不报错,CUDA 不 OOM,我们下一篇见。