GPT-SoVITSで学習したはずのモデルが読み込まれない — change_sovits_weights()がジェネレータ関数だった話

症状

GPT-SoVITSで自分の声を学習させ、最終チェックポイント(my_voice_e8_s1808.pth)を指定して音声を生成したはずなのに、ログを見ると別のファイル名が表示されている。

僕の場合はこうでした。

  • 指定したモデル: SoVITS_weights_v2/my_voice_e8_s1808.pth(epoch 8、最終)
  • ログに出ていたモデル: SoVITS_weights_v2/my_voice_e4_s904.pth(epoch 4、途中)

やっかいなのは、エラーが一切出ないことです。音声ファイルは正常に生成されるし、聴いた感じも悪くない。だから僕は「めっちゃいい感じ」と満足していました。実際にはそれ、途中のモデルで作られた音声だったんです。

同じ症状の人は、こういう状況に当てはまるはずです。

  • 学習エポック数を増やしたのに出力が変わった気がしない
  • CLI(inference_cli.py)と自作サーバーで、どちらも古いモデルが使われている
  • モデル指定のパスは間違っていないのに反映されない

原因

change_sovits_weights() が、内部に yield を含むジェネレータ関数だったことが原因でした。

この関数はもともとGradio(WebUI)から呼ばれる前提で書かれていて、進捗を逐次返すために yield が使われています。Pythonのジェネレータ関数は、ただ呼び出しただけでは中身が1行も実行されません。ジェネレータオブジェクトが返るだけで、next() などで明示的に進めて初めて処理が走ります。

# これだと中身が実行されない
change_sovits_weights(sovits_path)

つまり「モデルを切り替えたつもりのコード」が、実際には何もしていなかった。結果として、起動時に読み込まれていた古いチェックポイントのまま推論が走り続けていた、というわけです。

WebUI経由では正しく動くのに、自分でPythonから直接呼び出したときだけ壊れる。これがエラーなしで静かに失敗していた理由です。

解決手順

1. まず「本当に古いモデルが使われているか」をログで確認する

推測で直し始めない。サーバー(または推論スクリプト)の起動ログ・生成時ログに、読み込まれたチェックポイントのファイル名が出ているはずです。そこに表示されているファイル名が、自分が指定したものと一致しているかを見ます。

僕はここで my_voice_e4_s904.pth という表示を見つけて、初めて異常に気づきました。

2. 呼び出し側を next() で明示的に進める

ジェネレータ関数は、呼び出すだけでなく進めてやる必要があります。

# 中身を実際に実行させる
next(change_sovits_weights(sovits_path, ...))

3. 引数を省略しない

修正後に次のエラーに当たりました。change_sovits_weights は言語引数を渡さないと、内部で未定義変数エラーになる作りになっています。中身が実行されるようになって初めて顕在化するタイプのエラーです。言語引数も明示的に渡してください。

4. 同じ呼び出しをしている箇所を全部直す

僕の環境では2か所ありました。

  • inference_cli.py(CLIでの推論)
  • simple_tts_server.py(自作のAPIサーバー)

CLIで先にテストしていた分にも同じ影響が出ていた可能性が高かったので、両方まとめて修正しました。片方だけ直すと「UIでは直ったがCLIでは古いまま」という、さらに気づきにくい状態になります。

5. サーバーを再起動し、ログで最終モデルを確認する

修正後にサーバーを再起動して、ログに my_voice_e8_s1808.pth が読み込まれていることを確認してから、あらためて音声を生成しました。「修正した」ではなく「ログで確認した」まで行ってようやく完了です。

確認時のつまずきポイント

APIを叩いて確認する段階でも2つ引っかかりました。参考までに。

  • Windows/Git BashのクォートでJSONが壊れる → リクエストボディをファイルに書き出して、ファイル経由で送ると安定します
  • コンソール出力がバッファリングされ、最新状態を反映していないように見える → 画面の出力だけを信じず、実際にAPIを叩いた結果とログの両方で突き合わせます

再発防止

エラーが出ないバグがあることを前提にする

今回の件で一番怖かったのは、音声がちゃんと生成されてしまったことです。クラッシュしてくれるバグは気づけます。でも「それっぽい結果が返ってくる失敗」は、聴いた印象だけでは判別できません。

出力の正しさは、必ずログのファイル名で裏取りする

「いい感じに聞こえたから正しいモデル」ではない。生成のたびに、読み込まれたチェックポイントのファイル名をログで確認する癖をつける。これだけで今回のミスは最初の1回目で見つかっていました。

WebUI用のコードを流用するときは yield を探す

Gradio前提で書かれた関数は、進捗表示のために yield を含んでいることがあります。WebUIのコードをCLIや自作APIに転用するときは、呼び出す関数の定義を開いて yield の有無を確認する。ジェネレータなら next() が必要です。

同系統の「静かな食い違い」に注意する

このプロジェクトでは別の場面でも、記録上は「順調に学習が進んでいる」ことになっていたのに、GPU使用率とファイルの更新時刻を実際に確認したら、12時間前のepoch 5で止まっていた、ということがありました。原因が何だったのかは今もはっきりしていません。

ただ、そこから得た教訓は今回とまったく同じです。報告や表示をそのまま信じず、GPU使用率・ファイルの更新時刻・ログのファイル名といった、動かぬ証拠を自分の目で確認する。

AIと一緒に開発すると進みは圧倒的に速いですが、速い分だけ「確認せずに通り過ぎる」機会も増えます。節目ごとに実ファイルで裏を取る。これが一番効く再発防止策でした。


この記事は、非エンジニアの僕がAI(Claude Code)と一緒に、自分の声で喋るTTSツールをゼロから作った記録の一部です。全工程公開(要点は動画で、全文は再現キットで)でやっています。作ったときの手順とコードは、noteの再現キットにまとめています。

▶ この記事のもとになった動画
非エンジニアが「自分の声で喋るAI」を作ってみた【複利で増えるAI資産 #001】

上部へスクロール