Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

音声合成をキャンセル可能にする #64

Closed
k-kuroguro opened this issue Sep 4, 2021 · 12 comments · Fixed by #166
Closed

音声合成をキャンセル可能にする #64

k-kuroguro opened this issue Sep 4, 2021 · 12 comments · Fixed by #166

Comments

@k-kuroguro
Copy link

内容

フロント側では効率的な波形生成のため、ユーザの操作待機中に音声合成を始めており、ユーザがキャンセルしてもバックグラウンドで合成が続行されます。
それを避けるため、ユーザの操作に合わせてAPIで音声合成をキャンセルできるようにしたい。

参考: https://github.com/Hiroshiba/voicevox/issues/190

@Hiroshiba
Copy link
Member

実行中の音声合成を停止する方法は2つありそうです。

1つ目はeach_cpp_forwarder.dllの中身を変えて停止可能にする方法ですが、これは実装がlibtorchに依存しており、libtorchは計算の停止機能が(おそらく)無く、実装に工夫が必要であまり現実的ではありません。。

2つ目は、APIが実行されるたびにプロセスが立ち上がるようFastAPI(かuvicorn)の設定を変更し、キャンセルされたらプロセスごとkillする方法です。(無理矢理ですが・・・。)

他にもアイデアがあればぜひ!

@isnot
Copy link
Contributor

isnot commented Sep 6, 2021

私なりに下調べした参考情報を、羅列してみます。実際に使えるかどうか、VOICEVOXとしての適合性に関してはひとまずおいておいて、ただ並べただけです。ご検討の参考として見てください。

以下の手法を使えば、アプリケーションの実装としてプロセス監視をする必要なく、ただ単に自死(sys.exit())すればrespawnしてサービス全体としては継続できるはずです。

FastAPI+Uvicorn+Gunicornという構成でマルチプロセスとプロセス監視(自動respawn)できる

Running with Gunicorn
副次的に、「run.exeが落ちていてフロントエンド側が応答不可になる」問題が軽減できそうです。

エンドポイントでRequestオブジェクトを受け取って、「Streamの途中でクライアント側からの切断」を検知する

FastAPI:Using the Request Directly
Starlette:Request.is_disconnected

この場合クライアント側では、FetchAPI(ex. OpenAPI Generator / typescript-fetch)ですと、
AbortController に対応するかたちで手を加えることになりそうです。

@Hiroshiba
Copy link
Member

@isnot なるほど、すごく合理的な方法だと思います!
どうでしょう、せっかくなので(とりあえずサーバー側だけでも)実装してみませんか?

@takana-v
Copy link
Member

https://github.com/Hiroshiba/voicevox/issues/254 の続きです。

exit()はおそらくエンジン全体ではなく、マルチプロセスのうちの1つがexitするんだと思います!

恐らくそうなりますね、失礼しました。

上で挙げられていた方法について調査してみました。

FastAPI+Uvicorn+Gunicorn

これはGunicornがwindowsでは動かないようなので難しそうです。
https://docs.gunicorn.org/en/stable/index.html

Gunicorn ‘Green Unicorn’ is a Python WSGI HTTP Server for UNIX.


こちらのページでプロセスマネージャーがいくつか書いてあったので調査しました。
https://www.uvicorn.org/deployment/
使えそうなのは以下の一つです。

circus

https://github.com/circus-tent/circus
0.12(2015/6/2)でwindowsの実験的サポート(現在は不明)
そもそも動くのか、exe化できるのかは要確認です。

その他、計算部分を別スレッド(や別プロセス)で動かしてそれを無理やり止める、という方法でも実装可能かもしれません(無理やりですが)。

Requestオブジェクトの受け取り

Request.is_disconnected()で切断されたかは分かりますが、それを監視する方法が見つかりませんでした。
単純にビジーループで監視するのは良くないですし...

@Hiroshiba
Copy link
Member

移動ありがとうございます。

なるほど、Gunicornはwindows非対応なんですね。残念です。
Issue benoitc/gunicorn#524

circusは・・・よくわからないですね・・・
そういえばunicornは--workers INTEGERでワーカー数を指定できるのですが、こちらはどうなんでしょう。

別スレッド(や別プロセス)で動かして

こちらも良い方法に感じました!
PythonはGILがある関係でスレッド(疑似スレッド)が不思議な挙動をするので、プロセスのが良いかもです。

Request.is_disconnected()で切断されたかは分かりますが、それを監視する方法が見つかりませんでした。

ちょっとググってみたのですが、Requestが来る前段で頑張ればできるかも・・・?
fastapi/fastapi#2496

@takana-v
Copy link
Member

そういえばunicornは--workers INTEGERでワーカー数を指定できるのですが、こちらはどうなんでしょう。

https://github.com/encode/uvicorn/blob/c67ffad08c555e1cfb9f8d7f6b88be46688fd730/uvicorn/main.py#L432
uvicorn.runで、文字列を渡さないとworkers機能は使えないようです。
(現状はgenerate_app関数の結果を渡している)

ちょっとググってみたのですが、Requestが来る前段で頑張ればできるかも・・・?

websocketでは使えそうですが、普通のhttpの接続では方法が見つかりませんでした。
websocketを採用すれば実現できそうですが、下位互換性の問題もあるので難しいかもしれませんね。

@Hiroshiba
Copy link
Member

文字列を渡さないとworkers機能は使えないようです。

なるほどーーー uvicornをコマンドライン経由で起動してappという文字列を与えたときに可能な感じかな・・・
exeファイルにするならどうすればいいか見当がつきませんね・・・

websocketでは使えそうですが

普通のhttp接続で使える方法ではないんですね、失礼しました。

@Hiroshiba
Copy link
Member

Request.is_disconnected()で切断されたかは分かりますが、それを監視する方法が見つかりませんでした。

そういえばこちらですが、ちょっとtime.sleepしてRequest.is_disconnected()を確認し、ループしてまた確認し、というのを繰り返すちょっと愚直な手はあるかなと思いました。

@takana-v
Copy link
Member

実装するなら以下の感じでしょうか。

  • /synthesisを処理している関数
    • 音声合成部分をmultiprocessingで別プロセスで実行
    • Requestmultiprocessing.Processをグローバル変数に格納
  • 別スレッド
    • Request.is_disconnected()で接続の確認→切れてるならプロセスをkill
    • multiprocessing.Process.is_alive()でプロセスの生死を確認
    • 1秒sleep

https://docs.python.org/ja/3/library/multiprocessing.html

プロセスオブジェクトが作成したプロセスのみが start(), join(), is_alive(), terminate() と exitcode のメソッドを呼び出すべきです。

ということなので、接続監視は別プロセスではなく別スレッドの方が良いでしょう。

ただ、少し気になる点があります。

警告: Unix において、'spawn' あるいは 'forkserver' で開始された場合、 "frozen" な実行可能形式 (PyInstaller や cx_Freeze で作成されたバイナリなど) は使用できません。'fork' で開始した場合は動作します。

@Hiroshiba
Copy link
Member

Hiroshiba commented Sep 25, 2021

処理の流れ、良さそうに感じました!!

torch.multiprocessingは使わなくても可能だと思います(処理はDLL内にあり、pythonと完全に分離されていると思うので)

よくよく考えてみると、process killしてコアを再起動しようとすると、initializeし直す必要がありそうな気がしてきました。
initializeはかなり時間がかかるので、実はprocess killする方法はあまり現実的ではないかもしれません・・・。

@takana-v
Copy link
Member

とりあえず自分なりに実装してみました。
takana-v@756d788

別スレッドで通信の切断を監視するのはうまくいきませんでした...
asyncio周りで躓いた感じです。
(イベントループに、切断されたか調べるコルーチン投げても結果が永遠に戻ってこない)

/reserve_synthesisで予約IDを取得して、recv_synthesisで音声データを取得する感じです。
/cancel_synthesisでキャンセルします。

この方法でも再度エンジンを作成する必要がありました。
自環境では2秒ほどかかります。

@Hiroshiba
Copy link
Member

おー!!!良い実装だと感じました!!

ちょっとマイナーですがPythonにもFutureがあって、プロセスが稼働中かと計算結果の取得を1つのインスタンスで行なえます。
procとconの2つに分けている箇所をfuture1つで管理できてきれいな実装になるかも?と感じました!
https://docs.python.org/ja/3/library/concurrent.futures.html

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

Successfully merging a pull request may close this issue.

4 participants