# 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 运行时): ```bash 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-loader` pip 包自动提供。**仅真实文本合成需要;`--demo-tokens` 与 `synthesize_tokens()` 不依赖 eSpeak。** - 本机(Host)无数值 NPU 时的验证替身:`pip install onnxruntime`,把 `../model_convert/export/encoder.onnx` / `../model_convert/export/decoder.onnx` 作为模型路径传入即可(backend 自动切换)。 ## API ```python 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。 ## 示例 ```bash 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`)→ eSpeak `en-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 替身) ```bash 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 问题。