Inflect AX TTS Python SDK
owensong/Inflect-Micro-v2(VITS 系英文 TTS,文本 → 24 kHz 单声道波形)的 Encoder/Decoder 两段式 AXMODEL 推理 SDK,双目标 **AX620E(NPU2)/ AX637(NPU1)**。两套 AXMODEL 接口完全一致(同 shape 契约),SDK 通过模型路径切换目标,代码零改动。
推理流水线
text → [Host: eSpeak 音素化 + symbols 映射 + intersperse(add_blank)]
→ tokens[1,256](int,0 补齐)+ x_lengths[1]
→ encoder.axmodel → m_p/logs_p[1,192,256]、logw[1,1,256]
→ [Host: 取前 x_lengths 帧;w=exp(logw)*length_scale;ceil;generate_path;
matmul 扩展为 T' 帧;z_p=m_p'+randn(seed)*exp(logs_p')*variation]
→ 按 Tp=512 分块(64 帧重叠 crossfade)→ decoder.axmodel
→ 波形块拼接(尾部裁剪)→ edge fade 5ms + clip → 24kHz mono wav
- encoder 静态 shape
T=256(tokens 以 0 右补齐,x_lengths填真实长度;单 chunk 上限 255 个 intersperse 后 token,超长文本自动二次拆分)。 - decoder 静态 shape
Tp=512(~5.46 s/块),Host 侧分块 + 64 帧重叠 crossfade,最后一块尾部裁剪(EXPORT_NOTES §5.3)。 - 噪声注入在 Host 侧:
np.random.default_rng(seed)。seed 在 SDK 内可复现,但与 PyTorch 参考(torch MT19937)不逐位一致。
模型文件位置
| 目标 | encoder | decoder |
|---|---|---|
| AX620E | models/ax620e/encoder.axmodel |
models/ax620e/decoder.axmodel |
| AX637 | models/ax637/encoder.axmodel |
models/ax637/decoder.axmodel(权重 S16 升级版) |
(路径相对于交付包根目录 package/;AX637 decoder 为权重 S16 升级版,wav cos 0.9995,详见 ../reports/accuracy_summary.md。)
环境与安装
板端 / 主机(有 NPU 运行时):
pip install -r requirements.txt # numpy, phonemizer, espeakng-loader, num2words, pyaxengine
- eSpeak-ng:优先使用系统库(
/usr/lib/x86_64-linux-gnu/libespeak-ng.so.1或 aarch64 对应路径,Debian/Ubuntu:apt install libespeak-ng1);无系统库时espeakng-loaderpip 包自动提供。仅真实文本合成需要;--demo-tokens与synthesize_tokens()不依赖 eSpeak。 - 本机(Host)无数值 NPU 时的验证替身:
pip install onnxruntime,把../model_convert/export/encoder.onnx/../model_convert/export/decoder.onnx作为模型路径传入即可(backend 自动切换)。
API
from inflect_ax_tts import InflectTTS
tts = InflectTTS(
"../models/ax620e/encoder.axmodel",
"../models/ax620e/decoder.axmodel",
) # backend="auto": .axmodel -> pyaxengine, .onnx -> onnxruntime
# 文本合成(需要 eSpeak)
sr, wav = tts.synthesize("The quick brown fox.", speed=1.0, variation=0.667, seed=0)
tts.save("The quick brown fox.", "out.wav")
# 免前端:直接给音素 id(未 intersperse,SDK 内部加 blank)
sr, wav = tts.synthesize_tokens([81, 83, 16, 53, 65, 102, 53], seed=0)
speed∈ [0.5, 2.0](length_scale = 1/speed);variation∈ [0.0, 1.0](噪声幅度,基线 0.667);seed整数,同参可复现。- 返回
(24000, float32 ndarray),幅值 [-1, 1];write_wav()写 16-bit PCM。
示例
cd package/python
# 板端真实文本(AX620E;AX637 换路径即可)
python example.py \
--encoder ../models/ax620e/encoder.axmodel \
--decoder ../models/ax620e/decoder.axmodel \
--text "The quick brown fox jumps over the lazy dog." \
--output out.wav
# Host 干跑:无 NPU、无 eSpeak,onnxruntime 替身 + 确定性 dummy tokens
python example.py --demo-tokens \
--encoder ../model_convert/export/encoder.onnx \
--decoder ../model_convert/export/decoder.onnx \
--output demo.wav
Host 前后处理说明
- 前端(仅
synthesize()文本路径):normalize_text(数字/缩写/日期/货币展开,num2words)→ eSpeaken-us音素化(preserve_punctuation + with_stress)→ 音素覆盖表(2 条)→ 逐字符 symbols 映射 →intersperse(0)。与origin/inference.py完全一致。 - 时长/对齐(纯 numpy 整数逻辑):
w=exp(logw)*x_mask/speed→ceil→T'=max(sum,1)→generate_path(cumsum 区间)→attn @ m_p/logs_p扩展。 - 噪声:
z_p = m_p' + randn(seed) * exp(logs_p') * variation。 - decoder 分块:
Tp=512、64 帧重叠线性 crossfade;T'<=512单块路径即 SIMULATE 验证路径。 - 后处理:每块 edge fade 5 ms、句间停顿(按上一句末标点 0.08~0.28 s)、最终 clip [-1,1]。
测试(Host,onnxruntime 替身)
cd package/python
python tests/test_host_chain.py # 或 pytest tests/
覆盖:Host 链(expand_priors)与 PyTorch 参考逐位级一致性、encoder padding 语义、decoder 分块合成与 fp32 参考链路数值一致(ORT 层面)、端到端确定性、前端冒烟(espeak 可用时)。
已知差异 / 限制
- seed 语义与 PyTorch 参考不逐位一致(RNG 不同),仅保证 SDK 内可复现。
- encoder 输入 dtype:AXMODEL 运行时为 int32(input_processors S64 已折叠进模型),ONNX 为 int64;SDK 按 session 元数据自适应。
- decoder 多块(T'>512)crossfade 路径未做数值门禁验证(仿真仅覆盖单块路径)。
- 精度结论(仿真,详见
../reports/accuracy_summary.md):encoder 双目标 cos ≥ 0.9999;AX637 decoder(权重 S16 升级版)wav cos 0.9995 [PASS];AX620E decoder(基线)wav cos ≈ 0.95,未达 0.99 门禁(权重 S8 量化导致的高频细节损失,本工具链版本不可修复),属量化方案限制,非 SDK 问题。