Module-LLM 単体オンデバイス MeloTTS 日本語発話 (#67)
AX630C (Module-LLM) 単体で日本語 TTS を生成できるか、の調査結果。
結論: 可能。ただし M5Module-LLM ライブラリが叩く標準の StackFlow デーモン経路ではなく、 ml-inory/melotts.axera の Python 経路を使う。
2 つの経路
| StackFlow デーモン経路 | melotts.axera Python 経路 ✅ | |
|---|---|---|
| 実行主体 | llm-melotts 1.8 (C++ デーモン) | デバイス Ubuntu 上の Python |
| 呼び出し | CoreS3 → M5Module-LLM lib → UART | Module-LLM 内で直接 (CoreS3 非経由) |
| 日本語正規化 | WeTextProcessing (FST _tn_/_itn_) | MeCab + pykakasi + tokenizers (自前 G2P) |
| 結果 | クラッシュ (ABRT) | 発話成功 |
デーモン経路がクラッシュする理由
-DSKIP_MELOTTS_SETUP=0 で焼くと melotts setup が work_id を返さず、デバイスの llm-melotts デーモンログに以下:
FstHeader::Read: Bad FST header: /opt/m5stack/data/melotts-ja-jp/
F [wetext_processor.cc:36] Invalid fst prefix, prefix should contain either "_tn_" or "_itn_"
code=killed, status=6/ABRT/opt/m5stack/data/melotts-ja-jp/ には ja_tn_tagger.fst / ja_tn_verbalizer.fst が 存在し命名も正しい (_tn_ を含む) のに、デーモンがディレクトリパスを FST として 読もうとして失敗している。デーモン (1.8) と ja-jp 0.6 パッケージの組み合わせの不整合で、 ライブラリ経路では現状回避困難。NPU メモリ (CMM) 不足ではない (LLM skip で 307MB 空けても crash)。
melotts.axera 経路 (動作する方法)
ポイント: モデルファイル (encoder-jp.ort / decoder-jp.axmodel / g-jp.bin) は デバイス上の既存 ja-jp パッケージにそのまま入っているので再利用する。 不足するのは Python ランタイムだけ。Module-LLM はネット不通 (eth0 NO-CARRIER) なので 母艦 Mac で aarch64 wheel を集めて adb push する。
git clone https://github.com/ml-inory/melotts.axera /tmp/melotts.axera
cd mac-studio/scripts/melotts-onboard
./01-collect-wheels.sh # Mac で aarch64/cp310 wheel 収集
./02-push-install.sh # adb push + offline pip install (--target /opt/melotts-rt)
./03-speak.sh "こんにちは、ドナックです" # out.wav 生成 → Mac に pull実行時の要点
- 起動:
cd /opt/melotts-py/python; PYTHONPATH=/opt/melotts-rt LD_LIBRARY_PATH=/opt/lib python3 tts_jp.py ...axengine(pyaxengine) は/opt/lib/libax_engine.soを ctypes で叩く →LD_LIBRARY_PATH=/opt/lib必須numpy 1.24.4をシステム (2.1.3) より優先させるため専用 target をPYTHONPATHで先頭に
- BERT/torch は 不要 (japanese.py の transformers 経路はコメントアウト済み)
soundfile/libsndfileは使わない。tts_jp.pyが stdlibwaveで int16 PCM を書く (上流split_utils.pyの未使用import soundfileだけ無効化する)
計測 (MC20E = AX630C, Engine 2.6.3sp)
model load 5.3 s (プロセス毎 1 回)
MeCab 辞書ロード 3.3 s (プロセス毎 1 回; 2 文目以降は 0.06ms)
encoder (NPU) ~215 ms / 文
decoder (NPU) ~225 ms / slice
→ 実質合成: ~0.9 s / 文 (ロード除く)出力: 44100Hz / mono / int16 PCM。例: 約 22 文字 → 5.2 秒の音声。
音声出力 (#69, 解決済み ✅)
Module-LLM はスピーカーを持ち、ALSA card0 の再生デバイス pcmC0D1p で鳴らせる。 ただし 48kHz / stereo / 16bit でないと hw params が通らない (44100/mono は失敗)。
tts_jp.py --play で合成音声を 48kHz/stereo に線形リサンプル → tinyplay -D0 -d1 で再生:
./03-speak.sh "やあ、ぼくドナック" # 合成して実機スピーカーで再生重要: llm-audio デーモンを止めずに再生できる (再生 pcmC0D1p と録音 pcmC0D0c は 別サブデバイスで競合しない)。よって KWS/whisper の mic 入力を維持したまま発話できる。
直接 Axera AO API (
sample_audio ao/AX_AO_EnableDev) は失敗する (MW を llm-audio が管理)。 tinyalsa (tinyplay) で ALSA レイヤから直接叩くのが正解。 tinyplay は/opt/usr/lib/libtinyalsa.so.2を要するので、LD_LIBRARY_PATHを/opt/libで上書きする場合は/opt/usr/libを補うこと。
実運用へ統合する場合の残課題
- プロセスのコールド起動が重い (model+辞書 ~8.6s)。発話毎に起動すると遅い。 → 常駐サーバ (
melotts_svr.py相当) にして 1 回だけロードする設計が必要。 - 発話テキストの供給。オフライン時に誰が melotts サーバへテキストを渡すか (UART/StackFlow 経由 or CoreS3 側ロジック)。
- 現行ハイブリッド (TTS=Mac VOICEVOX 満別花丸) は 7 秒応答・高音質で安定。 このオンデバイス TTS が効くのは 完全オフライン (Mac 切り離し) 運用のときのみ。
関連: [[hybrid-asr-claude-voicevox]] / [[module-llm-japanese-pipeline]]