VOICEVOX / voicevox_core

無料で使える中品質なテキスト読み上げソフトウェア、VOICEVOXのコア
https://voicevox.hiroshiba.jp/
MIT License
861 stars 115 forks source link

voicevox_coreのクラス設計discussion #280

Closed qwerty2501 closed 1 year ago

qwerty2501 commented 2 years ago

内容

https://github.com/VOICEVOX/voicevox_core/issues/276#issuecomment-1245472220 , https://github.com/VOICEVOX/voicevox_core/pull/274#issuecomment-1246896362 によりvoicevox_coreの C APIをオブジェクト指向に寄せたAPIにする必要が出てきて、そのための議論をしたいと考えています。 個人的には以下のようなクラスでC APIを考えてるのですがいかがでしょうか? またpython APIについてもこのissueで議論したクラス設計を元にAPI設計を作りたいと考えてます。 今考えてるものとしてはversion,metas,supported_devices情報の取得はVoicevoxCoreクラスのstaticメンバ関数として実装しようと考えています。

struct VoicevoxCore;

VoicevoxCore* voicevox_core_new();

VoicevoxResultCode voicevox_core_initialize(VoicevoxCore* voicevox_core, VoicevoxCoreInitializeOptions options);

void voicevox_core_delete(VoicevoxCore* voicevox_core);

// staticメンバ関数のためVoicevoxCore のオブジェクトpointerは不要
char* voicevox_core_get_version();

// staticメンバ関数のためVoicevoxCore のオブジェクトpointerは不要
char* voicevox_core_get_metas_json();

// staticメンバ関数のためVoicevoxCore のオブジェクトpointerは不要
char* voicevox_core_get_supported_devices_json();

VoicevoxResultCode voicevox_core_load_model(VoicevoxCore* voicevox_core,uint32_t speaker_id);

bool voicevox_core_is_gpu_mode(const VoicevoxCore* voicevox_core);

//以下、同様にオブジェクト指向的にC APIを定義していく

voicevox_core_newとvoicevox_core_initializeは一緒にするべきかもしれませんがresultcodeの関係上分けようかと思ってます。 また現在のvoicevox_coreの関数のprefixは voicevox ですが、これもきちんとオブジェクト指向的な名前にすると voicevox_core にprefixがなると思い、 prefix を voicevox_core にしようと考えています。

その他

@sevenc-nanashi @shigobu お二人はvoicevox_coreのwrapperを作成されているように見受けられますのでぜひご意見をいただきたいです。

qwerty2501 commented 2 years ago

確かにvvmファイルから読み込むことを考えるとmodelを隠蔽しきるのは無理ですね。 しかしModelSetについてはやはり必要性が感じられないと思います。 下手に抽象化せず、Model structとして読み込んだほうが良いのかも?

Hiroshiba commented 2 years ago

ModelSetができることは、VoicevoxCoreでもできるModelの隠蔽(unloadは不可なのも一緒)なので、とりあえず考えるのはModelを隠蔽するかしないか決まったあとが良さそうかもです

今Model隠蔽周り整理すると、一応この3択ですよね( https://github.com/VOICEVOX/voicevox_core/issues/280#issuecomment-1252395184 とほぼ同じですが )

  1. Modelを用意する
    • 👍 設計がわかりやすい
    • 😢 ユーザーにとってわかりづらい
  2. Modelを隠蔽してunloadを実装しない
    • 👍 ユーザーにとってわかりやすい
    • 😢 unloadが必要になったらまたインターフェースが変わる
  3. Modelを隠蔽しつつunloadも実装
    • 😢 ユーザーにとってわかりづらい

3はまあいったん1と同じとみなして除外で良さそうとして、1か2を考えるにはこの議論より外のモチベーションを整理する必要があるのかなと思いました。 まず2でいく結論を出すには、ユースケースの洗い出しをすれば可能な気がします。が、実際洗い出すのは結構厳しいので、ちょっと考えてえいやで決める形になりそう。 1でいく結論を出すにも、コアの配布目的である「使ってもらうため」に反するのでなかなか厳しく、こちらもえいやで決める形になりそう・・・。

いったんペンディングにして、別の観点を見つけたり、違う論点を探すのもありかも。

Hiroshiba commented 2 years ago

別の観点なのですが、ModelのことをVoiceSetと呼ぶとわかりやすくなったりしませんかね。 声がいっぱいあるなにか、という説明です。これなら複数話者の声が入っていても不思議ではないのかなぁと。

まあ、いろんな話者の声が同じVoiceSetにあるのはそこそこ直感できそうですが、同じ話者の別の声が別のVoiceSetにあるのは直感に反するかもなのですが・・・。

qwerty2501 commented 2 years ago

@Hiroshiba 3 についてですが、unloadについてもVoiceを引数に与えるかたちでユーザーにわかりやすい実装はできそうなのですが、問題となってるのはファイルからvvmファイルをロードする際にどうしてもModelを意識せざるを得ずに隠蔽しきれなくなってしまうことだと思います。

別の観点なのですが、ModelのことをVoiceSetと呼ぶとわかりやすくなったりしませんかね。 声がいっぱいあるなにか、という説明です。これなら複数話者の声が入っていても不思議ではないのかなぁと。

もとがコアドメインではない機械学習文脈のModelでそれを無理やり名前変えただけとも見れなくもないので妥当かどうかは判断が難しいですね。 すくなくとも見極める時間は必要かと。

ペンディングについては私も賛成です。

Hiroshiba commented 2 years ago

もとがコアドメインではない機械学習文脈のModelでそれを無理やり名前変えただけとも見れなくもないので妥当かどうかは判断が難しいですね。

たしかに、なぜその声の組み合わせなのかを完璧に説明できるのは結局Modelなので、謎感は残りそうですね・・・ まあでも、少なくともModelを意識する必要はなくなり、用意された声セットをとりあえずやりくりせざるを得ないんだなと、わかりやすくなるかもです。(使いやすくはなってないですが・・・。)


ちょっと通り過ぎちゃったのですがこちら

これですが、「どのvoiceのために今loadされているのか」の情報が無いと駄目ですね。じゃないとある声に対するunloadで無条件でモデルがunloadされてしまい、ModelSetで扱うメリットが皆無になります。

というのの、voiceごとにユーザーがloadしたのかフラグを持つのも面白いアイデアだなと思いました。 (別のvoiceをloadしたら、同じModelに所属するものも一緒に使えるようになる形を想像していたので) まあ結局Modelを隠蔽するのかしないのか問題に帰着するのでこちらもペンディングになっちゃいますが・・・。

qwerty2501 commented 2 years ago

fileでまとめるのをModelにするかVoiceSetにするかで変わってきそうですね。 VoiceSet にするなら Model は隠蔽路線だし、 fileを Model として扱うなら他のAPIも Model を意識させたほうが良いでしょう

Hiroshiba commented 2 years ago

あれ、また認識違いがあるかも・・・? 僕の中ではVoiceSet==Model(ただの別名)だったのですが、 @qwerty2501 さんの中では違ってそうな印象を受けました。 VoiceSet==「1つ以上のModelが含まれるなにか」とかでしたら納得です!

qwerty2501 commented 2 years ago

いえ、私も VoiceSet==Modelであると思ってますがなにが引っかかりましたか?

Hiroshiba commented 2 years ago

なるほどです! 引っかかったのはfileでまとめるのをModelにするかVoiceSetにするか、の部分です。 どちらも中身は同じなので、ファイル構造も同じになる印象でした。 メタ情報が含まれてるが含まれてないか、などの違いでしょうか。(どちらもメタ情報は含んでる想定でした)

qwerty2501 commented 2 years ago

私もメタ情報はどちらになったとしても含まれると思ってます

Hiroshiba commented 2 years ago

fileかどうかは気にせずにModelをそのままModelとするかVoiceSetとするか

という説明で納得しました! おっしゃるとおり、隠蔽路線にしたければVoiceSet、モデルを意識させたいならModelだと思います

qwerty2501 commented 1 year ago

@Hiroshiba そういえばこの議論いつ頃再開できそうですかね? 0.14リリースするときにC APIをこの議論を反映した形にするのかどうかも決めたほうが良いかも

Hiroshiba commented 1 year ago

もう再開できると思います!

設計を決める上での制約として1つ思いついたのですが、今のAPIはそのまま提供できたほうが良いかなと思いました。 (まあこれはModelにするかVoiceSetにするかとはあまり関係ないのですが、一応の共有な感じです!)

qwerty2501 commented 1 year ago

かなり前の議論になってしまったのでdiscordでのまとめを転載

つまるところこの二択のどっち行くよという話だった気が。 「モデルを直接ロード・パージできるように、Modelクラスを作ってmodel_indexやmodel_keyで制御する」 「ボイスを暗黙的にロード・パージできるように、VoiceSetクラスを作ってvoice_indexやvoice_keyで制御する」 前者はユーザーにとってあまり関心のないモデルを意識しないといけないが、慣れてくると直感的。 後者はユーザーにとって関心のあるボイスを意識すればいいけど、拘ったことしようとすると結局モデルを意識しないといけなくなる。

qwerty2501 commented 1 year ago

@Hiroshiba VoiceSetについてですが、これはもともとModelSetだったものであり、一つのVoiceSetには 一つのdecode.onnxと一つのpredict_curation.onnxと一つのpredict_intonation.onnx とmetas.jsonが含まれているということで良かったでしたっけ?

いや、VoiceSetなので複数のonnxファイルが含まれている感じですかね

Hiroshiba commented 1 year ago

あ、前の文脈ではModel==VoiceSetでした!(1モデルに複数の声が含まれてるので)

qwerty2501 commented 1 year ago

では入ってる内容としてはこれであってます?

一つのdecode.onnxと一つのpredict_curation.onnxと一つのpredict_intonation.onnx とmetas.json

Hiroshiba commented 1 year ago

合ってます!! 個人的には(ちょっと冗長ですが)こんな感じの構成で良いのではとちょっと思っています。

qwerty2501 commented 1 year ago

それを見る限りVoiceSetとModelは別物に見えますが定義を変えるということでしょうか? Model==VoiceSetなんですよね?

Hiroshiba commented 1 year ago

Model==VoiceSetなんですよね?

ですね、以前の定義ではそうです!

話が飛躍してしまいました。 今のVOICEVOX CORE APIみたいな形のManagerは作るべきだと思っています。APIを今までとなるべく変わらないようにするためです。 となるとそのManager側がユーザーが使うメインAPIになると思うので、そっちと合わせて提案してみた感じです。

Manager側をわかりやすい名称にしたかったので、あたらめてVoiceSetも名前の候補になるかなと思ってVoiceSetという名称も候補に入れていました!

qwerty2501 commented 1 year ago

とりあえずModelやModelSet,VoiceSetでsynthesisができてしまうのは違和感があります。Voicevoxが良いかと思いましたがsynthesisすることを考えるとSynthesizerが良いかも core側でsynthesisを提供するということはengineでの処理を持ってくるということですね。こっちのほうが良いと思います。 loadが冗長になっているのを改善することを考えると以下のようにするとよいかと思います(prefix,あまり重要ではない機能やオプションなどは省略)

以前の議論ではmodelを意識させるかさせないかで別れていたかと思いますがやはりvoicevox coreと機械学習モデルはきっても切れない関係でありライブラリユーザーには意識させたほうが良いと思います

Hiroshiba commented 1 year ago

Synthesizer+Modelの構造、良いと思います!! 役割が分かれているのがわかりやすいなと思いました!

一応、Modelは機能を持っていてもまあ不自然じゃないと思います。 ここのモデルは機械学習モデルではなく音声合成モデルなので、音声合成機能を持ってもまあという感じです。

Hiroshiba commented 1 year ago

個人的にはSynthesizer+Modelの形にするのを、モデルファイルの単一ファイル化と一緒に進めるのが、二度手間にならなくて良さそうだなと思います!

時間がかかることになりそうですがそれも問題ないと思います。 なんにせよエンジン向けのAPIは同じ形で実装し続けるので、VOICEVOXのリリースタイミングに合わせる必要があまり無いので・・・!

@qryxip さんと @PickledChair さん、あとメンテナの @y-chan さんの意見もお伺いしたいです! (つまるところ、こちらの形の実装+エンジン向けのAPI提供し続けになるかなと)

qwerty2501 commented 1 year ago

一応、Modelは機能を持っていてもまあ不自然じゃないと思います。 ここのモデルは機械学習モデルではなく音声合成モデルなので、音声合成機能を持ってもまあという感じです。

一応ここがなぜ不自然に思ったかというとモデルが機械学習モデルか音声合成モデルどちらなのかはあまり重要ではなく、モデルはデータそのものを表す構造体であり責務的にデータに対する操作までが可能であり音声合成機能を提供するのは役割として違うかなと思ったからです。 仮にModelでsynthesisできるようにすると処理としてはModelがsynthesisするというふうに見えるのでおかしい。正しくはModelのデータを使用してsynthesisするのはず

qwerty2501 commented 1 year ago

discordより実は今まで引数で受け取っていたspeaker_idがspeaker_idではなくstyle_idであることが発覚しました 新しいAPIについては最低限引数名ぐらいは変えないといけないかと思いますがどういうふうに変えたほうが良いですかね

  1. speaker_idをstyle_idに変更するまでにとどめる
  2. 今までspeaker_id(style_id)のみを受け取っていた部分をspeaker_idとstyle_id両方受け取る形に変更する

またstyle_idについてvoice_idにすれば良かったという話もありましたがこれの名前もどうしましょうか

Hiroshiba commented 1 year ago

1が良いかなと思いました! voice_idの検討もいったんなしで、style_idそのままが良いかなと思います。

理由はコアだけがこの形に変わっても、仕様違いが多くなってメンテナンスが大変になると感じるためです。 他の仕様と違う部分をあわせるのは嬉しいので、speaker_id→style_idはとても賛成です! ユーザーにとっても、style_idが一意なのになぜかspeaker_idを指定する必要があると面倒かなぁと・・・!

qwerty2501 commented 1 year ago

ではそれで

Hiroshiba commented 1 year ago

で詳細が更に議論されています。 現状こんな感じかなというのをまとめてみます。

Hiroshiba commented 1 year ago

モデルファイルのファイル構造についてはこちらで議論するのが良さそうでしょうか。 といってもおそらく2通りくらいかなとか考えてます。

個人的にはzipからバイナリを読めるようなライブラリでまあまあ良い感じのがあるなら、zip方式でいいかなと思ってます。 (エンジン側のVVPPファイルと同じ方式です)

qwerty2501 commented 1 year ago

まあzipでいいんじゃないですかね Rust製ダウンローダーでもzipは解凍してるのでそのへんは問題ないでしょう

Hiroshiba commented 1 year ago

賛成です! ファイル構造としてはこんな感じですかねぇ。

manifest.json 他のファイルのファイル名・バージョンなどが入ってる  metas metas.jsonへのパス  predict_duration モデルへのパス  predict_intonation  decode  manifest_version 将来使うかも。最初はいらなそう metas.json 各モデル.onnx model/ モデルディレクトリ。あってもなくても良さそう

qwerty2501 commented 1 year ago

manifest.jsonとmetas.jsonって分ける必要ありますか? また各ファイルパスについてはバージョンが同じならファイル配置固定化できるなら不要かもです

Hiroshiba commented 1 year ago

作る側的には、意味の異なる情報は分けたほうが管理しやすいので嬉しいです。 manifest.jsonは共通で、metas.jsonだけリプレイスする運用とかができます。 まあでも、どっちでも良いなら分けたいかなくらいの気持ちです。

ファイルパスに関しては拡張子が違うことを考慮してます。 こっちはonnxから別の何かに移ることもあると思うので、冗長性をもたせときたいです。

qwerty2501 commented 1 year ago

manifest.jsonは共通で、metas.jsonだけリプレイスする運用とかができます。 まあでも、どっちでも良いなら分けたいかなくらいの気持ちです。

これはスピーカーを新しく追加するときとかですか?

ファイルパスに関しては拡張子が違うことを考慮してます。 こっちはonnxから別の何かに移ることもあると思うので、冗長性をもたせときたいです。

これはmanifestにファイルパスを持たせずにmanifest_versionをもとに読み込むファイルパスを変えるでも対応できそうですがどうしましょうか? manifest_versionは必須ではないとうことですがこれは同じvvmのスタイルが読み込まれているか判定するためのものではないということですかね

Hiroshiba commented 1 year ago

これはスピーカーを新しく追加するときとかですか?

新しくVVMを作る時とか、いっぱいあるVVMのマニフェストを刷新する時とか想像してました。 N回ファイル開いて修正して閉じてを繰り返すか、1回修正してN回コピーするか、みたいな。

これはmanifestにファイルパスを持たせずにmanifest_versionをもとに読み込むファイルパスを変えるでも対応できそうですがどうしましょうか?

確かにそうですが、ファイル名をコーディングするとテストやメンテが少し大変だったりしそうです。 あと製品版とOSS版でファイル拡張子が異なるので、2種類コードをメンテすることになって少し煩雑です。

manifest_versionは必須ではないとうことですがこれは同じvvmのスタイルが読み込まれているか判定するためのものではないということですかね

スタイルのバージョンはmetas.jsonにあるのでそっち使うことになると考えてます。 manifest_versionが必須でないのは、最初だからでした。(manifest_versionが無いというバージョンにできる) 互換性考えて、あったほうが良いと思います!


このあたりはどれもAPIには現れないことなので、そこまで議論するようなことではない気もしました…! あまり強い反対理由がなければサッと決めても良いかも?

qwerty2501 commented 1 year ago

運用のことを考えると分けたほうが良さそうですね styleごとにバージョンをもつのかなと思ってますが、これはVVMに更新が入るとmetas.json中のバージョンが全て更新されるということでしょうか?

Hiroshiba commented 1 year ago

ですです、基本的に同一VVM内のスタイルのバージョンが全部上がる感じです。 であればVVMのバージョンでも良い気もしますが、まあ過去との互換性のためスタイルごとにバージョン振る形でまあ良いかなぁ…。

qryxip commented 1 year ago

370 の"vvm-async-api"についてのタスクリストを作ってみました。

追記: https://github.com/VOICEVOX/voicevox_core/pull/497に移動しました。

qryxip commented 1 year ago

↑このタスクリストですが、#370 マージ後にすぐmainvvm-async-apiのdraft PRを作ってしまって、そこにぶら下げるのはどうでしょうか? (ここにあると埋もれて探すのが面倒になりそうに感じました)

Hiroshiba commented 1 year ago

なるほどです、良いと思います!! コンフリクトが解消し次第すぐマージしましょう!

Hiroshiba commented 1 year ago

@qryxip vvm-async-apiという名前だとブランチprotectにちょっと不便なので、project-vvm-async-apiというブランチ名に変更しました。 プロテクションもかけちゃおうと思います。

qryxip commented 1 year ago

そういえばpredict_duration/predict_intonation/decodeですが、ユーザーはあまり使わないと思うのでperform_predict_durationみたいな名前にした上でドキュメントにはlow-level/internalだということを書いておくのはどうでしょうか?

qryxip commented 1 year ago

あ、そういえばproject-vvm-async-apiだと3つとも既に消滅していますね。すみません忘れて下さい。