テキストを渡すと自分の声で読み上げてくれるパイプラインを、8GBのローカルGPU(RTX 3060 Ti)だけで組んだ。構成はVOICEVOXでテキスト読み上げ→RVCで声質変換の2段。クラウドAPI課金ゼロで動く一方、ここに至るまでに踏んだ落とし穴と、いまだ解決していない課題を書く。
TTS→声質変換の2段パイプラインという構成の考え方と、ローカルGPUで無人実行を組むときに実際に踏んだ4つの落とし穴、そして今も残っている未解決課題。
仕組み自体はシンプルな2段のパイプラインだ。①VOICEVOX(ローカルTTS)がテキストを一般的な音声に変換し、②RVC(声質変換)がその音声を、事前に学習させた自分の声のモデルへ変換する。テキストを直接「自分の声」で合成しているわけではなく、いったん誰かの声で読み上げてから、声だけ自分に着せ替えるという2段構えになっている。
自分の声モデルは、ブログ記事の読み上げ音声約24分分を学習データにして作った。RVC側の学習は前処理→ピッチ抽出→特徴抽出→200エポックの学習→検索インデックス構築まで一括のスクリプトで自動化してあり、50エポックごとにモデルを保存する。今のお気に入り設定は特定のエポックのモデルに、ピッチ・indexレート・保護率などいくつかのパラメータを組み合わせたものだ——ここは声の好みが分かれる部分なので、この記事では「調整可能なパラメータがある」ことだけ書いておく。
ローカルGPU実行特有の落とし穴。クラウドAPIなら意識せずに済む層でつまずく。
RVC同梱のffmpeg.exeがPATHに通っていないと、音声読み込みの段階で失敗する。新しいセッションからスクリプトを動かすたびに、作業ディレクトリを固定しつつPATH環境変数へ同梱ffmpegのフォルダを先頭追加する処理を、スクリプト冒頭に必ず入れる必要がある。
KeyError: 'rmvpe_root'ピッチ抽出モデル(RMVPE)の格納先を指す環境変数が未設定だと、ここで例外が飛ぶ。weight_root・index_root・outside_index_root・rmvpe_rootの4つを、スクリプト冒頭でまとめて既定値をセットしておくのが定石になった。
CUDA error: out of memory8GB VRAMだと、他のアプリを併用している状態でバッチサイズ6を指定すると落ちることがある。バッチサイズ4まで下げると安定する。それでも落ちる場合は、GPUを使っている他プロセスが残っていないかを先に疑う。
ここが核心。ローカルGPUパイプラインのエラーは、モデルの精度ではなく作業ディレクトリ・PATH・環境変数という地味な層で起きる。「動かない」の原因切り分けは、まずここを疑うと早い。
環境変数の3つを潰しても、まだ落とし穴が残っていた。重い変換処理をスケジュール実行に任せたところ、実行が固まったまま進まなくなったという実績がある。原因は、実行環境側の許可待ち(承認プロンプト)で処理が止まり、誰も操作していない無人実行ではそれが永遠に解消されないことだった。
この教訓から、重い処理は対話セッションの中でバックグラウンド実行する方式に倒すのが今のところ確実、という運用に落ち着いている。どうしてもスケジュール登録したい場合は、初回だけ手動で「今すぐ実行」して承認を済ませておく、という回避策になる。
パイプラインは動くが、品質面では未対応の項目が残っている。正直に書いておく。
数字と単位が混ざった表記を、TTSエンジンが意図と違う読みで発話することがある(正しくは「ワンケー」)。対応案は2つ考えていて、1つはTTSに渡す前にテキスト置換で読み仮名化するその場しのぎ、もう1つはVOICEVOXのユーザー辞書APIに単語と読みを登録する恒久対応。後者は一度登録すれば以降の全記事に自動で反映されるため、こちらが本命だと考えているが、まだ着手していない。
音声合成クエリが持つ抑揚スケールと話速スケールのパラメータを既定値のまま使っている状態で、これを調整すればもう少し自然になる余地がある。加えて、今は記事を行単位でTTSに渡しているが、句点単位に分割し直すと抑揚のつき方が変わる可能性がある——ここも仮説止まりで、まだ検証していない。
ローカルGPUでTTS/音声変換系のパイプラインを組む場面全般に使える。