Open qryxip opened 4 months ago
まあダウンロードサイズは50MBくらいなので、一緒でもいいかもですね・・・!!
VOICEVOX/ort
的に別れていた方が都合良さそうであれば(本家からの変更が少なくて済みそうであれば)、onnxruntime-builder側を変えてあげる方が綺麗かも?
300MBは解凍後ですね。
drwx------ - ryo ryo 2024-07-02 17:29 ./voicevox_core-windows-x64-directml-999.999.999
.rw-r--r-- 17M ryo ryo 2024-07-02 17:29 ├── onnxruntime.dll
.rw-r--r-- 324M ryo ryo 2024-07-02 17:29 ├── onnxruntime_providers_cuda.dll
.rw-r--r-- 11k ryo ryo 2024-07-02 17:29 ├── onnxruntime_providers_shared.dll
.rw-r--r-- 11k ryo ryo 2024-07-02 17:29 ├── README.txt
.rw-r--r-- 12 ryo ryo 2024-07-02 17:29 ├── VERSION
.rw-r--r-- 3.6M ryo ryo 2024-07-02 17:29 ├── voicevox_core.dll
.rw-r--r-- 54k ryo ryo 2024-07-02 17:29 ├── voicevox_core.h
.rw-r--r-- 18k ryo ryo 2024-07-02 17:29 └── voicevox_core.lib
microsoft/onnxruntimeだとCPU版とDirectML版とCUDA版を分けてて、pykeio/ortだとCPU&DirectML版とCUDA版という分け方にしてますね。
正直onnxruntime_providers_cuda.dllみたいなのが入ってくるのは見通しが悪くなるし混乱もしそうなので、microsoft/onnxruntime式か、あるいはpykeio/ort式でもいいんじゃないか?と思っています。
あとsupported_devices
も{ "cuda": true, "dml": true }
を返すというのもありますが、まあこれは #802 でOnnxruntime
のメソッドになるということもあり「ONNX Runtime (≠ VOICEVOX CORE)が対応しているデバイス情報」というドキュメント/仕様にしてしまってもよいかなと思ってます。
(SupportedDevices::THIS
とstd::ops::BitAnd
実装を追加して「VOICEVOX COREとONNX RuntimeとでGPU対応がそれぞれ異なる」ということを表明する、といった工夫もできそうです)
正直onnxruntime_providers_cuda.dllみたいなのが入ってくるのは見通しが悪くなるし混乱もしそうなので、microsoft/onnxruntime式か、あるいはpykeio/ort式でもいいんじゃないか?と思っています。
良いと思います!どっちの仕様にしようか迷いますねぇ。 まあonnxruntime式(全部バラバラ)のが便利そうではありますが・・・。
supported_devices
の仕様は・・・あれ、今ってcudaとdml別々に返してないんでしたっけ。
だったら{ "cuda": true, "dml": true }
的なのを返したほうが便利だと感じますね!!
気付いたのですがpykeio/ort式だと静的リンクなのに対し、DirectML版の動的リンクだとDirectML.dllの管理を別途しなければならないですね。なのでCPU版とDirectML版を一緒にしてしまうと、CPU版も余計な手間が一つ増えることに…
(
SupportedDevices::THIS
とstd::ops::BitAnd
実装を追加して「VOICEVOX COREとONNX RuntimeとでGPU対応がそれぞれ異なる」ということを表明する、といった工夫もできそうです)
これの補足ですが、こんな感じに二つのSupportedDevices
を提供するのはどうかなと考えています。
// 「このVOICEVOX COREのビルドがサポートしているもの」
SupportedDevices::THIS == SupportedDevices { cuda: true, dml: true }
// 「ONNX Runtimeがサポートしているもの」
ort.supported_devices() == Ok(SupportedDevices { cuda: false, dml: true })
// 「このVOICEVOC COREのビルドとONNX Runtimeの両方がサポートしているもの」
SupportedDevices::THIS & ort.supported_devices().unwrap() == SupportedDevices { cuda: false, dml: true}
気付いたのですがpykeio/ort式だと静的リンクなのに対し、DirectML版の動的リンクだとDirectML.dllの管理を別途しなければならないですね。なのでCPU版とDirectML版を一緒にしてしまうと、CPU版も余計な手間が一つ増えることに…
なーーるほどです!たぁしかに。
これの補足ですが、こんな感じに二つのSupportedDevicesを提供するのはどうかなと考えています。
andでandが取れるのなるほどです。 まーーでも大体の場合で不一致なら環境構築ミスってると思うので、and取りたいユースケースは無さそう…?
…あれ、そもそもボイボコアはGPU版とCPU版で全く同じビルドになる気がしてきました!! onnxruntime.dll入りzipファイルとかは各デバイスそれぞれに必要そうですが。 …いやこっちもダウンローダーがしっかりすれば各アーキテクチャごとに1種でよくなる…? あれ、別に動的読み込みになったから変わったという話ではない気がする。元からビルドは一つで良かった…?
…あれ、そもそもボイボコアはGPU版とCPU版で全く同じビルドになる気がしてきました!! onnxruntime.dll入りzipファイルとかは各デバイスそれぞれに必要そうですが。 …いやこっちもダウンローダーがしっかりすれば各アーキテクチャごとに1種でよくなる…? あれ、別に動的読み込みになったから変わったという話ではない気がする。元からビルドは一つで良かった…?
確かONNX RuntimeにはCUDA版とかDirectML版でしか生えないAPIがあったはずで、それらを使うためにRust側のコンパイルをconditionalにする必要がある… のですが、 #802 をやった今ならその必要はもう無いですね。
pykeio/ortとしても、実装を見る限りdlopen
/LoadLibraryExW
モードになった時点でどうやらfeatureに関わらずすべてのexecution providerが利用可能になるっぽい…? (ドキュメントにはそんなことは書かれてませんが)
いずれにせよ少なくともバイナリとしてリリースするWindows版とLinux版はビルドが一種類ずつになりますし、libvoicevox_onnxruntimeを同梱しないのならリリースを分ける必要も無くなりますね。macOS + Swift package + Core MLをやりたいとなったときにまたRust側で分けないといけなくなるくらい?
追記: 今のCargo featureをこうすればよさそう。
load-onnxruntime
(ONNX Runtime側次第でDirectMLもCUDAも使える)link-onnxruntime-cpu
(iOSはこれ)link-onnxruntime-directml
(なくてもいいかも?)link-onnxruntime-cuda
(〃)まあまずはCORE本体のリリースにONNX Runtimeを含めないようにして、ダウンローダーからダウンロードするようにするところから?
なるほど!!専用の関数があったりなかったりするんですね。
ビルドするものは少ない方がシンプルで嬉しそうですね! なのてloadで良いものはload版だけビルドでも良さそう。 でも何か予期せぬ問題も起こるかもなので、link-onnxruntime-directml等がすぐ作れるようにはしておきたいかも。 あ。あとエンジンのためにもしばらくは作ってもらえると助かりそうかも…?
あ。あとエンジンのためにもしばらくは作ってもらえると助かりそうかも…?
ちょっと若干意図が掴めなかったのですが、私の理解ではDLLパス無指定での"load"は"link"と同じ感じで動くはずです。 (なのでcompatible_engineのAPIは変わらないし、環境変数とかを注入する必要も無い)
あとエンジンに組み込むときはlibonnxruntimeもlibvoicevox_coreになる(多分)上に、ONNX Runtime自体のバージョンとかCUDAのバージョンとかも上がり、model/*.binはVVMになっているので、どの道現0.15からはセットアップのしかたが変わると思います。
あ、たしかにです!!
簡単に設定できること、あとそもそも他の変更要因が大規模なことから、このlink/loadの件は今のところエンジンについて意識から外して良さそうに思いました!
動作確認と需要満たしができたることを確認するまでしばらくどっちも作るのは方針としてありだと思いますが、まあ一気に置き換えでも良いかも。 コードメンテがそんなに大変じゃないならどっちもが良い、大変そうなら片方提供が良さそう、って感じかなと思いました!
でも何か予期せぬ問題も起こるかもなので、link-onnxruntime-directml等がすぐ作れるようにはしておきたいかも
こっちについては一応、特になんか用意しておく必要はないんじゃないかと思います。
コメントを残すかどうかですが、Rustの経験およびプログラミングの勘と調査能力がある人であれば何も無くても数分で次のような結論に辿り着けるはず…多分。
load-onnxruntime
では↑を利用していない ⇒ "link"の方では必要?#[cfg(any(feature = "load-dynamic", feature = "directml"))]
みたいな指定がされているlink-onnxruntime-directml = ["voicevox-ort/directml"]
のようにすればよいでも何か予期せぬ問題も起こるかもなので、link-onnxruntime-directml等がすぐ作れるようにはしておきたいかも
こっちについては一応、特になんか用意しておく必要はないんじゃないかと思います。
たしかにしっかり確認する工程があれば大丈夫そう! 怖いのはエンジンもビルドしてエディタもビルドした後に、初見殺しで特定の環境でのみなぜか動かなかった場合くらいかなと。あるのかわかりませんが。 設定変えてサクッと変えて数日以内くらいにlinkもビルドできるなら問題なさそう感!
コメントを残すかどうかですが、
どこの話かわからないですが、未来の僕たちのためになりそうだったら残しても良いかも。 と思ったけどdocs辺りにメモ書きがあるんでしたっけ。ならそれで良さそう感。
featureをload-onnxruntime
| link-onnxruntime-cpu
にするやつを今やっているのですが、一つ困ったことがありました。今現在ユーザーから指定されるのが以下なので、もし「CUDAもDirectMLも使えるlib(voicevox_)onnxruntime」を提供するとしたらこれに対してAuto
とかGpu
とかを指定されてもどちらを使えばよいかわかりません。あと #783 をやろうとするとis_gpu_mode
もちょっと苦しくなります。
というのも、EPが実際に使えるかどうかは 。Session
を一つ作ってみなくてはわからないんですよね
思い浮かぶ方針は次の四つで、どれにしようか迷っています。
最初のdecode用Session
を作る際にCUDAもDirectMLもとりあえず試行してみる。どっちがOKだったのかを記憶しておいて、二度目以降はそちらを選ぶようにする
何もしないモデルを用意してそれのSession
を作ることで、Synthesizer
のコンストラクタの段階でCUDA/DirectMLの利用可能性を判定する
これなら上記のis_gpu_mode
も、Auto
を指定したけどCPUモードにフォールバックされるであろう状況に対してfalse
を返せます。
[追記1] 本当に空のモデルはONNX Runtimeに拒否されるのですが、これ(↓)ならいけそう。とりあえずLinuxのCUDAは判別できそうでした。50バイトなので文字列リテラルで埋め込んだ上でprotobuf表現に対する注釈も付けれそう。
import onnx
from onnx import TensorProto
i = onnx.helper.make_tensor_value_info("I", TensorProto.BOOL, [])
o = onnx.helper.make_tensor_value_info("O", TensorProto.BOOL, [])
node = onnx.helper.make_node("Not", ["I"], ["O"])
graph = onnx.helper.make_graph([node], "_", [i], [o])
model = onnx.helper.make_model(graph)
[追記2] EPの"register"はOrtSession
じゃなくOrtSessionOptions
に対して行うので、ダミーのモデルなんか作る必要は無かった!!!
AccelerationMode
を拡張し、具体的なGPUの種類を指定できるようにする
いっそのことこういう感じでdeivce_id
も指定できるようにするのもよいと思います。
// `AccelerationSpec`とかあるいは単に`Acceleration`という名前にしてもよいかも
pub enum AccelerationMode {
Auto, // 上記1.の挙動
Cpu,
Dml { device_id: i32 },
Cuda { device_id: i32 },
}
class AccelerationMode:
AUTO: "AccelerationMode"
CPU: "AccelerationMode"
def dml(*, device_id: int = 0) -> "AccelerationMode": ..
def cuda(*, device_id: int = 0) -> "AccelerationMode": ..
#[derive(Clone, Copy)]
#[repr(C)]
pub struct VoicevoxAccelerationMode {
kind: VoicevoxAccelerationModeKind,
options: VoicevoxAccelerationModeOptions,
}
#[allow(non_camel_case_types)]
#[derive(Clone, Copy)]
#[repr(usize)]
pub enum VoicevoxAccelerationModeKind {
VOICEVOX_ACCELERATION_MODE_KIND_AUTO,
VOICEVOX_ACCELERATION_MODE_KIND_CPU,
VOICEVOX_ACCELERATION_MODE_KIND_DML,
VOICEVOX_ACCELERATION_MODE_KIND_CUDA,
}
#[derive(Clone, Copy)]
#[repr(C)]
pub union VoicevoxAccelerationModeOptions {
none: [u8; 1],
dml: VoicevoxAccelerationModeDmlOptions,
cuda: VoicevoxAccelerationModeCudaOptions,
}
#[derive(Clone, Copy)]
#[repr(C)]
pub struct VoicevoxAccelerationModeDmlOptions {
device_id: i32,
}
#[derive(Clone, Copy)]
#[repr(C)]
pub struct VoicevoxAccelerationModeCudaOptions {
device_id: i32,
}
lib(voicevox_)onnxruntimeは一つのバイナリにつき高々一つのEPしか有効化しない。CUDAとDirectMLは分ける。VOICEVOC CORE側もEPが一つであるという前提を持ち、もし複数のEPが有効化されたlibonnxruntimeが読み込まれたらwarningを出しつつEPを一つ選ぶ (e.g. CUDAとDirectMLが両方あるならCUDAにしておく)
これが現状の挙動に近いのかなと思います。ただDirectMLとCUDAの同時有効化も便利ではあると思うので、個人的には1., 2., 3.のどれかにできればなと思っています。
↑ ちょっと実装の目算が立ったので、案2. + 案3.で手を付けてみようと思います。
ちょっと考え切れてないのですが、先々のことを考えると案3が良さそうに思いました! (案2はちょっとよくわかってないというのが本音です)
ユーザーもサードパーティーアプリ開発者もやりたいのは「GPUを使う」なのでdevice.GPU
を選べる形が良い気はするのですが、コアは基幹寄りな実装をしていても良いと思うのと、将来なんか細分化されていきそうな雰囲気を感じるためです。
もしかしたらNPUが増えてきて、色々選べるようにしたくなるかもしれない、くらい。
なので案1より案3のが良い・・・・かも・・・?くらいの気持ちです!
あとload-onnxruntime
はEPと思想が同じなので、せっかくだから1種のvoicevox_onnxruntime.dll
でいろんなEPを挿せるようにしたいですね!
なので案4はできれば後回しにしたい。
案2.についてですが例として、CUDAが使えないLinuxのPCでCUDA版のコア&エンジンの利用を試みた場合を考えます。
エンジンを--use_gpu
で起動すると多分次のようになると思います。
--use_gpu
でエンジンが起動initialize(use_gpu=True, …)
は成功する/initialize_speaker
は500系を返す (ことになってたような?自信無し)コアのC APIでも、現状では同じような挙動をします。
acceleration_mode=Gpu
でもSynthesizer
は作れるちなみにacceleration_mode=Auto
はどうなのかというと、現状ではvoicevox_coreとlibonnxruntimeをセットで扱う限りacceleration_mode=Gpu
と全く変わりません。
これを次のようにする案です。
acceleration_mode=Gpu
でSynthesizer
を作ろうとすると、CUDAもDirectMLも使えないという旨のエラーで失敗する。acceleration_mode=Auto
の場合でも、同様にちゃんと検査してCPU版にフォールバックする (#783)利点としては、 #783 でCPU版にフォールバックしたときにis_gpu_mode
がfalse
を返せるようになります。
案3. (ユーザーからGPUを選択可能)ですが、エンジン(のようなソフトウェア)から使うことを考えるとAccelerationMode
はもう一種類必要そうですね (compatible_engine専用の内部APIでもいいとは思いますが)。
pub enum AccelerationMode {
Auto,
AnyGpu, // `Auto`のようにGPUを試行するが、CPU版にフォールバックせずにエラー
Cpu,
Dml { device_id: i32 },
Cuda { device_id: i32 },
}
@qryxip なーーーーーーるほどです!!! ちょっとまだ一部わかってないかもなのですが、案2は良さそうだと感じました!! よほどしんどいことにならない限り、実装するのが良いかなと思います!
案3のAnyGpuもなるほどです!こちらもあると嬉しそうです! Enumの名前はGpuでも良いかも。やるならAutoGpuかも? (AnyGpuにするとAuto→Anyにしたくなってしまいそう。・・・・・・・ちょっと自信ないですが・・・!!!)
不具合の内容
https://github.com/VOICEVOX/voicevox_core/pull/802#issuecomment-2202402961
現象・ログ
割愛
再現手順
割愛
期待動作
download_test
は通るべき。--device directml
と指定するとonnxruntime_providers_cuda.dll (約300MB)まで付いてくる。このような挙動は望ましくないかもしれない。VOICEVOXのバージョン
N/A
OSの種類/ディストリ/バージョン
その他
DirectML版とCUDA版を分離する場合、段取りとしてはonnxruntime-builder (ビルドしなおし) → VOICEVOX/ort (リストの更新) → voicevox_core (voicevox-ortの更新)となるはず。