Files
note_sync/有趣的项目/AI桌面陪伴助手/P0-语音基准报告.md
T
ylhan bdd6bfb63d AI桌面陪伴助手:方案更新至 v2.4,代码剥离到独立仓库
代码剥离
- 助手本体、TTS 服务、P0 基准脚本、ESP32 固件移到 ~/workspace/ai-companion
  (独立 git 仓库),本目录只留方案、P0 报告与硬件记录
- 因此删除 esp32_smile_test.ino / esp32_touch_eyes.ino
- 同时移除已失效的 .gitignore(原本只为挡住模型体积,代码已搬走)

方案 v2.4 重大变更:架构从"单机"改为"服务器即助手、本地为客户端"
- 起因:实测 N5095 无 AVX2,跑不动可用的语音模型
- N5095 降为客户端/外设桥,只做 USB 接入、DoA、WebRTC 客户端
- 实施阶段表加进度列(P0/P1/P3/P4a 部分完成),新增三条验收要求
- 采购清单按实际到货更新,标出屏幕形态待确认(2.8寸 vs 原定1.28寸圆屏)

技术栈(实测更新)
- LLM:DeepSeek deepseek-flash(云端,必须关思考);本机 27B 已停用
- TTS:Qwen3-TTS 1.7B(服务器 3090);vits-zh-ll 因音色不满意被替换
- STT:Zipformer int8 未变,中文识别率仍是未闭合项

新增两份 P0 报告
- P0-Qwen3TTS报告.md:TTS 换型过程与实测
- P0-DeepSeek验证报告.md:四条路径验证

两份旧报告加了状态说明(结论仍有效但选型已变),
并修正了随代码移走而失效的引用与复现命令。
2026-09-15 11:08:56 +08:00

229 lines
11 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# P0 基准报告 · 本地 STT / TTS
> ⚠️ **TTS 选型已于 2026-09-15 变更。**
> 本报告选出的 vits-zh-ll 虽然速度达标(服务器 RTF 0.09),
> 但用户试听反馈**音色不满意**,最终换成 Qwen3-TTS 1.7B(RTF 0.55–0.94)。
> 见 `P0-Qwen3TTS报告.md`。
>
> **本报告的核心结论仍然有效且在用**:
> - STT 线程数不能按"核多给多"猜(N5095 上 4 线程比 2 线程慢 3.9 倍)
> - 冲刷耗时 ≈ 0,流式解码已持续输出
> - VITS 非自回归、`max_num_sentences=1` 会静默截断——这两条对 Qwen3-TTS 同样适用
> - STT 中文识别率未测(无参考文本),此结论仍然成立
>
> 另:本报告测出的"服务器(i9-14900K)上 STT RTF 0.03"仍是当前生产配置。
日期:2026-09-14。机器:Celeron N5095(4 核,**无 AVX2**)/ Ubuntu 24.04.3 / 16G。
脚本目录:`p0/stt_tts_bench/`。全部为实测数据,非估算。
复现:
```bash
cd ~/workspace/ai-companion/p0/stt_tts_bench
./download_models.sh # 走 hf-mirror
python3 bench_stt.py # STT
python3 bench_tts_compare.py # TTS 横向对比
python3 bench_concurrent.py # 并发
python3 bench_roundtrip.py # 往返
python3 bench_threads.py # 线程扫描
```
## 一、结论
**本地语音方案成立,但必须换 TTS 模型。**
内核:**STT 没问题,vits-zh-ll 不行,Piper 可以。**
| 组件 | 选型 | RTF | 判定 |
|---|---|---|---|
| STT | 流式 Zipformer 中英双语 int8 | 0.22(单跑)/ 0.50(并发) | ✅ 可用 |
| TTS | vits-zh-ll(115MB) | **1.86** | ❌ 慢于实时,不可用 |
| TTS | **piper-huayan-medium(61MB)** | **0.36** | ✅ 推荐 |
| TTS | piper-huayan-x_low(20MB) | 0.17 | ⚠ 快但音质存疑,待试听 |
## 二、STT
### 速度
| 线程数 | RTF 中位 | 单块解码 p95 | 判定 |
|---|---|---|---|
| 1 | 0.37 | 1.19 | 平均够,但存在突发积压 |
| **2** | **0.22** | **0.79** | ✅ 最佳 |
| 3 | 0.63 | — | 反而变慢 |
| 4 | 0.86 | 3.78 | 严重变慢 |
**关键发现:线程数超过 2 会急剧退化**,2→4 线程 RTF 从 0.22 涨到 0.86(慢 3.9 倍)。
重复 5 轮稳定复现,不是噪声。
原因判断:N5095(Jasper Lake)是**单通道内存**,且 L2 仅 1.5MB、L3 仅 4MB。
并且 onnxruntime 空闲工作线程默认自旋占核;4 个 ORT 线程 + Python 主线程 = 5 线程抢 4 个核,
主线程(负责喂音频)被饿死。
→ **实现时 STT 固定 `num_threads=2`。** 不要"核多就多给线程"。
### 其他指标
- **首字延迟**:喂进 0.5~1.4 秒音频后出现第一个中间结果。这是模型的算法前瞻量,与算力无关。
- **冲刷耗时 = 0ms**:`input_finished()` 后立即拿到最终结果。流式解码过程中已经持续输出,
收尾不额外花钱。**"说完到出结果"的开销主要在 VAD 判定说完的等待,不在识别器。**
- 注:并非每个 100ms 块都触发解码(约每 3 块解一次,即模型 chunk 约 300ms),
所以"单块 p95 > 1"不等于积压,应看 RTF。
### 识别正确率:未测
4 段测试音频是 sherpa-onnx 自带的中英双语真人录音,**没有参考文本**,无法算字错率。
尝试用"TTS 合成已知文本 → 喂给 STT"来算 CER,但**这个方法在这台机器上不成立**:
合成音频会让 STT 产生大量重复("晴晴转转"、"月月月"),而同样的解码配置下真人录音完全正常。
换 greedy / beam_search、换块大小、开关端点检测,四种配置结果完全一致 —— 说明问题出在合成音频
本身,不是解码配置。
**结论:STT 在中文真人语音上的正确率目前没有可信数据。** 需要一份带标注的中文真人测试集,
或用麦克风录一批带文本的语音。这是 P0 尚未闭合的一项,不能靠现有数据下结论。
## 三、TTS
### 速度
| 模型 | 大小 | RTF(2线程) | 19字句首音延迟 | 判定 |
|---|---|---|---|---|
| vits-zh-ll | 115MB | 1.86 | 2748ms | ❌ |
| piper-x_low | 20MB | 0.17 | 585ms | ✅ 快 |
| piper-huayan-medium | 61MB+espeak | 0.36 | ~650ms | ✅ 推荐 |
**VITS 非自回归**:验收时确认回调只在合成完成后触发一次,
所以**首音延迟 = 总合成耗时**,没有"边合成边播"的可能。
分句播放是唯一的提前出声手段(`max_num_sentences=1` 逐句合成)。
### 连续播放模拟(5 句)
| 模型 | 各句空档 | 最大空档 |
|---|---|---|
| vits-zh-ll | 2.0 / 1.6 / 0.5 / 2.9 / 3.8 秒 | **3.80s** |
| piper-x_low | 0.5 / 0 / 0 / 0 / 0 秒 | 0.47s(仅首句) |
vits-zh-ll 每句之间都会出现 0.5~3.8 秒的静默空档,说话断断续续。
Piper 除首句外无空档。
### 音质:必须人工试听
往返测试的 CER 不可信(见上),但可以给出**相对**信号:
| 模型 | 往返 CER | 输出峰值 | 输出 RMS |
|---|---|---|---|
| vits-zh-ll | 0.25 | **0.15** | 0.045 |
| piper-x_low | 0.72 | 0.49 | 0.092 |
| piper-huayan-medium | 0.32 | 0.59 | 0.105 |
两点值得注意:
1. **vits-zh-ll 输出音量极低**(峰值 0.15,只有 Piper 的 1/4)。接音箱时必须补增益,
否则要么听不见,要么底噪被一起放大。
2. **piper-x_low 音质存疑**:合成时有 `Unknown token: ̪` 警告,且往返 CER 明显偏差。
速度虽好(RTF 0.17),但**不建议直接用**。
已生成三个模型同一句话的试听文件,请人工判断:
```
~/workspace/ai-companion/p0/stt_tts_bench/listening_test/vits-zh-ll.wav
p0/stt_tts_bench/listening_test/piper-x_low.wav
p0/stt_tts_bench/listening_test/piper-medium.wav
```
**音质只能靠耳朵定,仪器测不出来。这一项在试听前不能算通过。**
## 四、并发(这是最接近真实负载的测试)
助手说话时麦克风不关(要检测打断),所以 STT 必须与 TTS 同时跑。
| TTS 模型 | STT 单跑 | TTS 单跑 | STT 并发 | TTS 并发 | 退化 | 判定 |
|---|---|---|---|---|---|---|
| piper-x_low | 0.26 | 0.19 | **0.50** | **0.33** | 1.9× / 1.7× | ✅ 余量 2.0× / 3.1× |
| vits-zh-ll | 0.26 | 1.97 | 0.48 | **2.39** | 1.9× / 1.2× | ❌ 跟不上 |
并发下两者都退化约 1.8 倍(4 核争抢所致,符合预期)。
用 Piper 时 STT 0.50 + TTS 0.33,都远低于 1,**架构成立**。
但这只是 STT + TTS 两项。方案还要同时跑人脸检测/跟踪。视觉再加进来是否还够,
需要单独测(未测)。
## 五、对方案 P1 指标的影响
方案 P1 目标:"短问答结束到首段有声回复,P50≤2 秒、P95≤4 秒"。
用实测数据推算(`thinking` 关闭):
| 环节 | 耗时 | 来源 |
|---|---|---|
| VAD 判定说完 | 200~500ms(估) | 待实测 |
| STT 冲刷 | ≈0ms | 实测 |
| LLM 首 token | 786ms(P50) | P0-DeepSeek 报告 |
| TTS 首音(Piper medium,短句) | ~650ms | 实测 |
| **合计** | **约 1.6~2.0 秒** | |
**用 Piper 时 P50 目标勉强可达**,但没有富余;P95 需要看网络抖动。
用 vits-zh-ll 则完全不可能(TTS 单项就 2.7 秒)。
## 六、未测事项(不要当成已通过)
- **STT 中文真人识别率**:无参考文本,未测。
- **TTS 音质**:待人工试听 `listening_test/`。
- **视觉并发**:人脸检测加入后的 CPU 争抢未测。
- **真实音频链路**:麦克风阵列、远场、AEC、回声参考全部未测,无硬件。
- **int8 量化损失**:只测了 int8 encoder,未与 fp32 对照(fp32 315MB,本机大概率跑不动)。
- **长时稳定性**:未做 2 小时/8 小时测试。
## 六之二、补充:更多 TTS 候选(同日追加)
用户试听后反馈 **vits-zh-ll 听感最好**,但它是 RTF 1.86 的不可用项。
据此追加测试了 5 个候选,全部实测:
| 模型 | 大小 | 单独 RTF(最佳线程) | 并发 RTF | 备注 |
|---|---|---|---|---|
| piper-x_low | 20MB | 0.17 @2 | 0.33 | 有 Unknown token 警告,音质存疑 |
| **piper-medium** | 61MB | **0.32 @2** | **0.51** | 用户反馈不如 vits |
| **matcha-icefall-zh-baker** | 88MB + 3.6MB 声码器 | **0.38 @2** | **0.94** | 新候选,流匹配 + HiFi-GAN |
| vits-zh-ll | 115MB | 1.14 @4 / 1.87 @2 | 1.48–2.39 | 听感最好,但不可用 |
| kokoro fp32 | 383MB | 1.96 @4 | — | 排除 |
| kokoro int8 | 181MB | **3.54 @4** | — | 排除 |
### 两条新增的机理结论
1. **TTS 的线程扩展性与 STT 相反**。STT 超过 2 线程急剧退化(4线程慢 3.9 倍),
而 vits-zh-ll 是线程越多越快(1线程 3.59 → 4线程 1.14)。
原因:STT 是大量小算子(每算子一次线程同步,线程越多同步开销越大),
TTS 是少数大算子(CPU 密集,可有效并行)。
**两类服务的线程配置必须分开调,不能统一。**
2. **int8 量化在这台机器上反而更慢**。Kokoro int8(3.54)比 fp32(1.96)慢近一倍。
原因:Tremont 没有 VNNI 指令,int8 的快速点积路径用不上,
权重需要现场反量化,额外开销盖过了带宽收益。
→ **本机不要走 int8 量化路线做加速。**(STT 的 int8 encoder 是发布方直接训练的量化模型,
不是运行时量化,不受此限。)
### 当前选型状态
- **速度与余量最优**:piper-medium(并发 0.51,余量约 2 倍)
- **架构档次更高、速度尚可**:matcha-baker(并发 0.94,**几乎没有余量**)
- matcha 的并发退化(0.38→0.94,2.5 倍)明显大于 piper-medium(0.32→0.51,1.6 倍),
判断与 HiFi-GAN 声码器吃内存带宽有关(本机单通道内存)。
**若选 matcha,人脸检测等其它负载再加进来很可能压垮实时性,需要实测。**
已生成响度归一化的 A/B 试听(统一到 -20 dBFS RMS,
避免"响度差被误当成音质差"——vits-zh-ll 原始峰值仅 0.17,比 Piper 低 3.5 倍):
```
~/workspace/ai-companion/p0/stt_tts_bench/listening_ab/{vits-zh-ll,matcha-baker,piper-medium}_{0,1}.wav
```
## 七、工程陷阱记录
1. **`max_num_sentences=1` 会截断多句输入**为第一句,且不报错。
传含多个句号的文本会静默丢失后半段。必须自己先分句逐句送。
2. **hf-mirror 对 urllib 默认 UA 返回 403**,需伪装 User-Agent。
3. **espeak-ng-data 里有带空格的文件名**(`voices/!v/Mr serious`),URL 必须转义。
4. **代码曾位于 note_sync(Obsidian 笔记仓库)内,是经软链接访问的**,
模型一度差点被推进笔记仓库。**2026-09-15 已把代码剥离为独立仓库**
(`~/workspace/ai-companion/`),笔记库只剩文档,这个问题不复存在。
教训:代码和笔记共用版本控制,迟早要在 ignore 规则上反复花时间。