Closed8

「TTS.cpp」を試す

kun432kun432

先日、llama.cppでTTSが使えるのを試した。

https://zenn.dev/kun432/scraps/4bcd5634ff3e82

その際に、llama.cppをベースに、TTS特化で派生させたプロジェクトがあった。

あと、llama.cppの実装を元にした他のプロジェクトもある。

Orpheus TTS、OuteTTSをサポート
https://github.com/foldl/chatllm.cpp

Parler TTS、Kokoro、Diaをサポート
https://github.com/mmwillet/TTS.cpp

あとはMeloTTSもあるな

https://github.com/apinge/MeloTTS.cpp

llama.cpp本体では現時点(2025/05/28)ではOuteTTSのみがサポートされており、他のTTSについてはllama.cpp側の対応を待つか、上記のプロジェクトを使う必要がある。

今回は上記のうちTTS.cppを試す。

kun432kun432

GitHubレポジトリ

https://github.com/mmwillet/TTS.cpp

TTS.cpp

ロードマップ / 修正済みGGML

目的と目標

このリポジトリの一般的な目的は、GGMLテンソルライブラリを使用して、一般的なデバイスアーキテクチャにおいてオープンソースTTS(テキスト音声変換)モデルを用いたリアルタイム生成をサポートすることです。GGML上では、高速なSTT(音声認識)、埋め込み生成、およびLLM生成がすでに whisper.cppllama.cpp を通じて広くサポートされています。したがって、本リポジトリは、それらの機能を補完する形で、同様に最適化され移植可能なTTSライブラリを提供することを目指します。

この取り組みにおいては、MacOSおよびMetalサポートが主要なプラットフォームとして扱われ、機能はまずMacOS向けに開発され、その後他のOSへと拡張されます。

サポートされている機能

警告! 現在、TTS.cppは概念実証(Proof of Concept)段階であり、さらなる開発が予定されています。現時点の機能はMacOS X以外の環境ではテストされていません。

モデルサポート

モデル CPU Metalアクセラレーション 量子化 GGUFファイル
Parler TTS Mini こちら
Parler TTS Large こちら
Kokoro こちら
Dia こちら

追加モデルのサポートは、TTS Model Arena におけるオープンソースモデルの性能およびアーキテクチャやチェックポイントの可用性に基づいて優先的に追加されます。

機能対応状況

実装予定機能 OS X Linux Windows
基本的なCPU生成
Metalアクセラレーション _ _
CUDAサポート _
量子化 ✓_*_
レイヤーオフロード
サーバーサポート
Vulkanサポート _
Komputeサポート _
ストリーミング音声

* 現在は生成モデルのみがこれに対応しています。

パフォーマンス

本ライブラリの中心的な目標は、OS Xにおけるリアルタイム音声生成をサポートすることです。このため、生成速度のベンチマークは対応モデル(Parler Mini バージョン1.0)を用いてOS X環境にて集中的に実施されています。

DAC音声デコーダモデルへのMetalアクセラレーションの導入により、標準的なApple M1 Max上で約3GBのメモリ消費でほぼリアルタイムの音声生成が可能となっています。加速モデルにおける最良のリアルタイム係数は現在1.112033です。これは、1秒の音声生成に約1.112033秒の処理時間を要することを意味します(生成モデルにQ5_0量子化を適用した場合)。最新のベンチマーク統計については perf_batteryのREADME を参照してください。

kun432kun432

Mac(M2 Pro)で試す

レポジトリクローン

git clone https://github.com/mmwillet/TTS.cpp && cd TTS.cpp

GGMLのフォーク版もクローン

git clone -b support-for-tts https://github.com/mmwillet/ggml.git

ビルド。テキスト→音素への変換はEspeak-ngを使えるようになっているらしいのでこれを有効化しておく。Espeak-ngはHomebrewでインストールできる。

export ESPEAK_INSTALL_DIR=/opt/homebrew/opt/espeak-ng
cmake -B build
cmake --build build --config Release

モデルをダウンロード。モデルの種類はREADMEからHuggingFaceに飛んで確認。ここでは models ディレクトリを作成してそこにダウンロードしている。

mkdir models
pushd models

# Parler-TTS
# miniとlargeがあり、それぞれF32、Q5がある。
# ここではlarge・Q5をダウンロード
wget --content-disposition https://huggingface.co/mmwillet2/Parler_TTS_GGUF/resolve/main/Parler_TTS_large_Q5.gguf?download=true

# Dia
# Q4、Q5,Q8、F16、F32がある。
# またそれに`_DAC_F16`がついているものは16ビットDACエンコーダでエンコーダされたバージョン。
# 今回はQ5・16ビットDACエンコーダ版を使う
wget --content-disposition  https://huggingface.co/mmwillet2/Dia_GGUF/resolve/main/Dia_Q5_DAC_F16.gguf?download=true

# Kokoro-TTS
# Espeak-ng対応版・非対応版がある様子。
# 英語だけならEspeak-ng非対応のもので良さそうだが、日本語でも使いたいのと一部記号を上手く処理できないみたいなので、Espeak-ng対応版を。
wget --content-disposition https://huggingface.co/mmwillet2/Kokoro_GGUF/resolve/main/Kokoro_espeak.gguf?download=true

popd

推論は./build/bin/cliを使う。Usageは以下。

./build/bin/cli --help
出力
--temperature (-t):
    The temperature to use when generating outputs. Defaults to 1.0.
--repetition-penalty (-r):
    The by channel repetition penalty to be applied the sampled output of the model. defaults to 1.0.
--top-p (-tp):
    (OPTIONAL) the sum of probabilities to sample over. Must be a value between 0.0 and 1.0. Defaults to 1.0.
--n-threads (-nt):
    The number of cpu threads to run generation with. Defaults to hardware concurrency. If hardware concurrency cannot be determined then it defaults to 1.
--topk (-tk):
    (OPTIONAL) When set to an integer value greater than 0 generation uses nucleus sampling over topk nucleaus size. Defaults to 50.
--max-tokens (-mt):
    (OPTIONAL) The max audio tokens or token batches to generate where each represents approximates 11 ms of audio. Only applied to Dia generation. If set to zero as is its default then the default max generation size. Warning values under 15 are not supported.
--use-metal (-m):
    (OPTIONAL) Whether to use metal acceleration
--no-cross-attn (-ca):
    (OPTIONAL) Whether to not include cross attention
--vad (-va):
    (OPTIONAL) whether to apply voice inactivity detection (VAD) and strip silence form the end of the output (particularly useful for Parler TTS). By default, no VAD is applied.
--play:
    (OPTIONAL) Whether to play back the audio immediately instead of saving it to file.
--model-path (-mp):
    (REQUIRED) The local path of the gguf model file for Parler TTS mini or large v1, Dia, or Kokoro.
--prompt (-p):
    (REQUIRED) The text prompt for which to generate audio in quotation markers.
--save-path (-sp):
    (OPTIONAL) The path to save the audio output to in a .wav format. Defaults to TTS.cpp.wav
--conditional-prompt (-cp):
    (OPTIONAL) A distinct conditional prompt to use for generating. If none is provided the preencoded prompt is used. '--text-encoder-path' must be set to use conditional generation.
--text-encoder-path (-tep):
    (OPTIONAL) The local path of the text encoder gguf model for conditional generaiton.
--voice (-v):
    (OPTIONAL) The voice to use to generate the audio. This is only used for models with voice packs.
--espeak-voice-id (-eid):
    (OPTIONAL) The espeak voice id to use for phonemization. This should only be specified when the correct espeak voice cannot be inferred from the kokoro voice ( see MultiLanguage Configuration in the README for more info).

では各TTSごとに細かいオプションなどは置いといてまずはざっと実行。

Parler-TTS

./build/bin/cli \
    --model-path models/Parler_TTS_large_Q5.gguf \
    --prompt "Good morning. It's a beautiful day today. On days like this, I feel like going to the horse races." \
    --save-path tts-cpp-parlertts-sample.wav

https://audio.com/kun432/audio/tts-cpp-parlertts-sample

Dia(Diaだけはプロンプトに[S1]を追加している)

./build/bin/cli \
    --model-path models/Dia_Q5_DAC_F16.gguf \
    --prompt "[S1] Good morning. It's a beautiful day today. On days like this, I feel like going to the horse races." \
    --save-path tts-cpp-dia-sample.wav

https://audio.com/kun432/audio/tts-cpp-dia-sample

Kokoro-TTS

./build/bin/cli \
    --model-path models/Kokoro_espeak.gguf \
    --prompt "Good morning. It's a beautiful day today. On days like this, I feel like going to the horse races." \
    --save-path tts-cpp-kokoro-tts-sample.wav

https://audio.com/kun432/audio/tts-cpp-kokoro-tts-sample

kun432kun432

CLIのオプションやTTSごとの違いなどは以下にある。

https://github.com/mmwillet/TTS.cpp/tree/main/examples/cli/README.md

少しTTSごとに異なるオプションを試してみる。

Kokoro TTS

TTS.cppで対応しているTTSのうち日本語が使えるのはKokoro-TTSだけ。--voicej* で始まる声を選択すればいいはず。

./build/bin/cli \
    --model-path models/Kokoro_espeak.gguf \
    --prompt "おはようございます。今日はいいお天気ですね。こんな日は競馬に行きたくなりますね。" \
    --save-path tts-cpp-kokoro-tts-sample-ja.wav \
    --voice "jf_alpha"

https://audio.com/kun432/audio/tts-cpp-kokoro-tts-sample-ja

うーん、これは・・・途中までしか生成されてないし、しかも発音もおかしい。

本来のKokoro-TTSではg2pにmisakiというライブラリを使っているので、Espeak-ngとは多分音素化された結果が違うのだと思うし、Espeak-ngだとひらがな・カタカナしか受け取れないはず。

Espeak-ngで音素化

espeak-ng -v jpx/ja -x "おはようございます。今日はいいお天気ですね。こんな日は競馬に行きた くなりますね。"
出力
,ohaj,oug,ozaim'asu
k_j,ouh,ai:,oteNk,ides'une
k,onnaC,ihak,eib,ani:k,itak,unar`,imas'une

misakiで音素化

from misaki import ja, en

g2p = ja.JAG2P()

phonemes = g2p("おはようございます。きょうはいいおてんきですね。こんなひはけいばにいきたくなりますね。")
print(phonemes)
出力
('ohajoː ɡoʣaimasɨ kʲoː βa iː o teŋkʲi desɨ ne konna çiβa keːba ɲi ikʲi takɯ naɾʲimasɨ ne', None)

この感じだと日本語は厳しそう。。。。せめて音素を直接指定できるといいんだけどな。

参考

https://zenn.dev/kun432/scraps/d0618dfa29200d

https://zenn.dev/kun432/scraps/496279cfb59567

https://zenn.dev/kun432/scraps/dc3f19eed62305

kun432kun432

Dia

上にも書いたが、Diaは複数話者の音声を生成でき、笑い声などの非言語音声タグを指定することができる。TTS.cppで実行する場合は --topk 35 --temperature 1.3を設定したほうが良いらしい。

./build/bin/cli \
    --model-path models/Dia_Q5_DAC_F16.gguf \
    --prompt "[S1] Good morning. It's a beautiful day today. [S2] Yep. On days like this, I feel like going to the horse races. [S1] (laughs) Definitely." \
    --save-path tts-cpp-dia-sample2.wav \
    --topk 35 \
    --temperature 1.3

https://audio.com/kun432/audio/tts-cpp-dia-sample2

ただ、生成がめちゃめちゃ遅い・・・デフォルトだとCPU推論っぽいので、--use-metalを有効にしてみたのだが、それでも遅い。

参考

https://zenn.dev/kun432/scraps/7563f7454fa9af

kun432kun432

Parler-TTS

Parler-TTSはプロンプトに対応している。このプロンプトは--conditional-promptで指定するのだが、これをエンコードするための google/flan-t5-large のGGUFモデルが必要になる。そのためのPythonスクリプトが用意されているので、それを使う。このあたりは以下のモデルの量子化についてのドキュメントに記載がある。

https://github.com/mmwillet/TTS.cpp/blob/main/py-gguf/README.md

python仮想環境を作成

uv venv -p 3.12.9
uv pip install pip
source .venv/bin/activate

py-ggufディレクトリに移動してパッケージインストール

cd py-gguf
pip install -r requirements.txt

スクリプトを実行。この時 --save-path はなぜかカレントディレクトリが指定できないので(no such file or directoryになる)、他のモデルと同じパスを指定している。

python ./convert_t5_encoder_to_gguf --save-path ../models/t5-encoder-large.gguf --large-model

モデルがダウンロードされ、GGUF変換される。

出力
WARNING:parler_tts.modeling_parler_tts:Flash attention 2 is not installed
INFO:gguf.gguf_writer:gguf: This GGUF file is for Little Endian only
INFO:T5Encoder:Preparing tensors for GGUF file, t5-encoder-large.gguf.
config.json: 100%|█████████████████████████████████████████████████████████████████| 7.72k/7.72k [00:00<00:00, 7.89MB/s]
model.safetensors.index.json: 100%|█████████████████████████████████████████████████| 94.5k/94.5k [00:00<00:00, 618kB/s]
Downloading shards:   0%|                                                                         | 0/2 [00:00<?, ?it/s]
model-00001-of-00002.safetensors:  16%|███████                                      | 776M/4.98G [01:23<07:32, 9.31MB/s]
(snip)
INFO:T5Encoder:Preparing configuration for GGUF file, ../models/t5-encoder-large.gguf.
INFO:T5Encoder:Beginning to write to GGUF file, ../models/t5-encoder-large.gguf.
INFO:gguf.gguf_writer:Writing the following files:
INFO:gguf.gguf_writer:../models/t5-encoder-large.gguf: n_tensors = 221, total_size = 4.9G
Writing: 100%|██████████████████████████████████████████████████████████████████| 4.91G/4.91G [00:04<00:00, 1.02Gbyte/s]
INFO:T5Encoder:Finishing GGUF converstion process for file, ../models/t5-encoder-large.gguf.

クローンしたレポジトリのルートに戻って

cd ..

推論。--text-encoder-pathで先程のモデルを指定、--consditional-promptでプロンプトを指定。

./build/bin/cli \
    --model-path models/Parler_TTS_large_Q5.gguf \
    --prompt "I am saying some words" \
    --text-encoder-path models/t5-encoder-large.gguf \
    --conditional-prompt "male deep voice" \
    --save-path tts-cpp-parlertts-sample2.wav

うーん、、、

出力
Segmentation fault: 11

ちょっとわかんないな。Linux環境でやり直してみたいところ。

参考

https://zenn.dev/kun432/scraps/81488b9b4121f5

kun432kun432

まとめ

本家llama.cppより対応しているTTSモデルが多いんだけど、日本語という観点ではちょっと厳しいかな。英語で普通に使うならまあ・・・というところではあるが、READMEに、

この取り組みにおいては、MacOSおよびMetalサポートが主要なプラットフォームとして扱われ、機能はまずMacOS向けに開発され、その後他のOSへと拡張されます。

警告! 現在、TTS.cppは概念実証(Proof of Concept)段階であり、さらなる開発が予定されています。現時点の機能はMacOS X以外の環境ではテストされていません。

とあって、個人的にはMacで使うのはMLX−Audioに任せてしまえばいいのでは?という気がしなくもない。Macネイティブだし、モデルもそこそこあるし。

https://zenn.dev/kun432/scraps/38ecd708bb85ad

どっちかというとLinuxやWindowsのほうが選択肢としての魅力はありそうな気がするんだけどね、あくまでも個人的な所感だけど。

とはいえ、TTSモデルがGGUF化されてポータブルに使えるようになるといろいろ嬉しいことが多いと思う。llama.cpp本家でのTTS対応もまだ始まったばかりのようだし、今後に期待。

このスクラップは3ヶ月前にクローズされました