litagin02 / Style-Bert-VITS2

Style-Bert-VITS2: Bert-VITS2 with more controllable voice styles.
GNU Affero General Public License v3.0
696 stars 83 forks source link

二つ目のインスタンスが実行できない->dictを... #71

Closed OzoneAsai closed 6 months ago

OzoneAsai commented 6 months ago
Running server_editor.py --inbroser
reading dict_data\user.dict_csv-d621e6ba-c5b8-41ba-b6ba-9d9ecaf1d7cb.tmp ... 5
emitting double-array: 100% |###########################################|

done!
Error: Failed to update dictionary.
Traceback (most recent call last):
  File "C:\WorkSpace\Style-Bert-VITS2\text\user_dict\__init__.py", line 146, in update_dict
    tmp_compiled_path.replace(compiled_dict_path)
  File "pathlib.py", line 1247, in replace
PermissionError: [WinError 5] アクセスが拒否されました。: 'dict_data\\user.dict_compiled-d621e6ba-c5b8-41ba-b6ba-9d9ecaf1d7cb.tmp' -> 'dict_data\\user.dic'
Traceback (most recent call last):
  File "C:\WorkSpace\Style-Bert-VITS2\server_editor.py", line 45, in <module>
    from text.japanese import g2kata_tone, kata_tone2phone_tone, text_normalize
  File "C:\WorkSpace\Style-Bert-VITS2\text\japanese.py", line 21, in <module>
    update_dict()
  File "C:\WorkSpace\Style-Bert-VITS2\text\user_dict\__init__.py", line 154, in update_dict
    raise e
  File "C:\WorkSpace\Style-Bert-VITS2\text\user_dict\__init__.py", line 146, in update_dict
    tmp_compiled_path.replace(compiled_dict_path)
  File "pathlib.py", line 1247, in replace
PermissionError: [WinError 5] アクセスが拒否されました。: 'dict_data\\user.dict_compiled-d621e6ba-c5b8-41ba-b6ba-9d9ecaf1d7cb.tmp' -> 'dict_data\\user.dic'
litagin02 commented 6 months ago

報告ありがとうございます。 辞書ファイルはuser.dicで1つであり、文字列処理が必要になる動作をしようとすると最初に辞書をコンパイルする動作が入るので、複数立ち上げようとすると、使用中の辞書に書き込もうとしてエラーが出るという状態ですね。このふるまいはあまり考えていませんでした。

対策は考えますが、どうすればいいのかすぐには分からないので、とりあえず同時に複数のものを立ち上げずに1つのもののみで使っていただけたらと思います。

OzoneAsai commented 6 months ago

コンパイル時のファイル名は任意のものを指定できないのですか?できるならuuid並みのランダムな文字列をファイルに入れましょう(?)

litagin02 commented 6 months ago

ファイル名は指定できますが、そうすると、プロセス終了時にそれをうまく削除しないと、辞書ファイルのコピーが大量にあふれてしまう問題があり、うまい解決策は見つけられないです。

OzoneAsai commented 6 months ago

普段よく見る.lck#みたいな気持ちで、プロセスが起動したら特定のファイルにPIDとそのプロセスが作成したファイルの名前を書き込んで、ほかのプロセスとして実行されたら特定のファイルに書き込まれたPIDが特定のファイルに書き込まれたプロセス名と一致するかどうか調べて、一致するものがなければコンパイルされた辞書ファイルを削除して新しく作成。一致するものがあれば、新しく作成。 というのはどうでしょうか? ChatGPTによる説明 この要求は、プロセスの実行を監視し、特定のファイルに関連情報を書き込んで管理する仕組みを説明しています。以下に、この仕組みを具体的なチャートで説明します。

  1. プロセスの起動と監視

    • プロセスが起動すると、そのPIDとプロセスが作成したファイルの名前、プロセス名を特定のファイルに書き込む。
    • これにより、新しいプロセスが開始されたことが記録される。
  2. プロセスの実行の確認

    • 他のプロセスとして実行される場合、特定のファイルからPIDを読み取り、そのPIDが関連するプロセス名と一致するか確認する。
    • 一致する場合は次のステップに進み、一致しない場合は新しい辞書ファイルを作成する。
  3. 辞書ファイルの管理

    • 一致するものがない場合、コンパイルされた辞書ファイルを削除し、新しい辞書ファイルを作成する。
    • 一致するものがある場合、新しい辞書ファイルを作成する。

これにより、重複した辞書ファイルの参照や、強制終了されたプロセスによって作成された辞書ファイルの増加を防ぐことができます。

以下に、上記の内容を示すチャートを提供します:

  プロセスの起動
        |
        ↓
  特定のファイルに
PIDとプロセス情報を書き込む
        |
        ↓
   プロセスの実行の確認
        |
        ↓
特定のファイルからPIDを読み取る
        |
        ↓
PIDとプロセス名の一致確認
        |
        ↓
一致するものがない場合:
  辞書ファイルの削除
  新しい辞書ファイルを作成
一致するものがある場合:
  新しい辞書ファイルを作成

このような仕組みによって、プロセスの管理と辞書ファイルの効率的な利用が実現されます。

litagin02 commented 6 months ago

提案ありがとうございます。プロセス管理やPIDやらロックについては全く知らないので、自分がちゃんとコードを書けるかは分からないです……。 しばらくバグ修正等他のことをやるので、どなたか実装していただけると非常に助かります。

kale4eat commented 6 months ago

お世話になっております。

まずは

などを正確に分析する必要がありそうです。 以下のように分析しました。間違っていたら申し訳ございません。

コードの問題

エディター と FastAPI、前処理など、同時に実行できないプロセスがある -> 後に起動したプロセスがuser.dicの書き込もうとするとエラー -> 先に起動したプロセスが既に開いている -> pyopenjtalkがプロセス実行中、オープンしている

コンパイル済みユーザー辞書ファイルuser.dicがプロセス間で共通であり、 最初に起動したプロセスがオープンし続けている おそらくpyopenjtalk -> openjtalk -> Mecab由来

想定するユースケース

前処理とエディターによるユーザー辞書の変更

  1. ユーザーはエディターと学習用のUIを同時に起動する
  2. 前処理を行う
  3. 誤った解析結果があった場合、
  4. エディターでユーザー辞書に単語を追加する
  5. 前処理を再度行うと、意図通りの解析が行われる

目標

最初に起動したプロセスがファイルをオープンし続けていることがネックです。思いついた以下の方法を提案させていただきます。

方法1 個々のプロセスごとのコンパイル済みユーザー辞書ファイル

各プロセスが別名でコンパイル済みユーザー辞書を扱います。 Ex. server_editor.user.dic, server_fastapi.user.dic

ユースケースのため、辞書の内容は同期されるようにする必要があります。 これは何らかの方法で辞書変更イベントを監視する必要があります。 Ex. ファイルの変更

懸念事項

方法2 pyopenjtalk別プロセス化とプロセス間通信

辞書の変更やrun_frontendなど、pyopenjtalkに関する処理を行う別プロセスで起動します。 Ex. pyopenjtalk_worker

プロセス間通信によってリクエストを送り、レスポンスを受け取ります。 Ex. 名前付きパイプ

ここで主なリクエストはpyopenjtalkのメソッドの引数、 レスポンスは戻り値です。

若干の実装のイメージもあります。 Ex. 既存のimport pyopenjtalkはimport pyopenjtalk as pyopenjtalk_workerにする

懸念事項

他にも方法はあると思いますが、どれがベストかは何とも言えません。 個人的には方法2ですが、一定の複雑さは出てしまうと思っています。 まだどこまで実装できるかわかりませんが、 ご意見ありましたらよろしくお願いいたします。

kale4eat commented 6 months ago

プロセス間通信の方法として、名前付きパイプを検討していましたが、 以下の問題がわかりました。

かわりにソケット通信で試してみます。

デメリット

メリット

litagin02 commented 6 months ago

2.4.0にてワーカー化で対応することで解決されました。ご意見等ありがとうございました!