Files
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

11 KiB
Raw Permalink Blame History

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/。全部为实测数据,非估算。

复现:

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 规则上反复花时间。