Closed qryxip closed 1 week ago
まだドキュメントやテストが詰めきれていませんが、とりあえずこのパブリックAPIがUX的にどうなのかご意見を頂けたらなと思っています。
onnxruntime-libloading
とonnxruntime-link-dylib
ですが、実行時挙動はどうにもならないとして、APIの形だけでも一つにまとまらないかと考えてみました。macOSのXCFrameworkや、Swift API作成のことを考えるとAPIの形が分かれているのが気になった次第です。
以下のようにすれば、onnxruntime-link-dylib
はonnxruntime-libloading
から単にできることを減らしたものになるはずなので、ドキュメントをちゃんと書けばユーザーを驚かせずにすむのではないかと思いました。
(着想を得たのはPythonのctypesです。どういう理由で用意されているかはわかりませんが、Unix限定でctypes.CDLL(None)
というのができます)
class Onnxruntime:
LIB_VERSIONED_FILENAME: str
# Python APIやJava APIにおいては`filename=None`は実際には許可しない
def init_once(filename: str | None = LIB_VERSIONED_FILENAME) -> Self:
...
libloading
, filename=Some("…")
: LoadLibraryExW(_, filename, _)
libloading
, filename=None
: 「Windowsでは無理」というエラーlink-dylib
, filename=Some("…")
: GetModuleHandleExW(_, filename, _)
を開き、そこからのOrtGetApiBase()
の戻り値がロード時動的リンクされている方のものと一致しているか検査。違っていたらエラー。検査後はロード時動的リンクされているAPI達を使っていくlink-dylib
, filename=None
: 「Windowsでは無理」というエラーlibloading
, filename=Some("…")
: dlopen(filename, _)
libloading
, filename=None
: dlopen(NULL, _)
link-dylib
, filename=Some("…")
: 「dlopen
は禁止されている」というエラーlink-dylib
, filename=None
: dlopen
はせずにリンクされたONNX Runtimeの関数を利用する (dlopen(NULL, _)
の方も似た挙動になるはず)@qryxip とりあえず最後のコメントへのコメントです!
ctypes.CDLLのような形、面白いですね。UXとしてありな気がしました!
Windows, link-dylib, filename=None: 「Windowsでは無理」というエラー
このときって「ロード時動的リンクされている方のもの」は使えない感じですかね? たぶん今のコアがこの形(ビルド時リンク)だと思ってて、だとしたらできるんじゃないかなと思った次第です。 というのも、たぶんエンジンもこの形になるので、雑にNULLでいけるなら楽そうだな〜と。
他は僕の知識量からコメントできることはなさそうでした!! UX的には結構便利そうに感じます。
追記:あ、この辺りはそもそもWindowsで2種類作るのかによりそう。 1種のがよければエンジン側が頑張って合わせるのが良さそう。
あ。仮称かもなのですが、libloading
やlink-dylib
の名称がややこしめなのがちょっと気になりました!
たぶんdylibは避けて、役目で分けちゃうのがわかりやすいかなと。
で、「iOSのdylibはこれ使うと良いよ」みたいな案内がある感じとか。
名称・・・この辺りのリンク方法?、なんて呼ぶんですかね。。。
libloading
は実行時の読み込みだから、dynamic-load
とか・・・?
link-dylib
はビルド時のリンクだから、buildtime-link
とか・・・?
うーん。。。
2値だから1つにまとめてtrue/falseで制御するとかもありかも。
だとしたらlinkする(link-dylib
)かしない(libloading
)かだから、onnxruntime-link
でtrue
とfalse
とか・・・・・?
Windows, link-dylib, filename=None: 「Windowsでは無理」というエラー
このときって「ロード時動的リンクされている方のもの」は使えない感じですかね? たぶん今のコアがこの形(ビルド時リンク)だと思ってて、だとしたらできるんじゃないかなと思った次第です。
SymInitialize(W)
を使ってよいのなら「extern
なONNX RuntimeのAPIに対する実体を持っているonnxruntime.dll」を見つけだすことができるのですが、ちょっとこれを使うのには抵抗を覚えました。
というのも、たぶんエンジンもこの形になるので、雑にNULLでいけるなら楽そうだな〜と。
追記:あ、この辺りはそもそもWindowsで2種類作るのかによりそう。 1種のがよければエンジン側が頑張って合わせるのが良さそう。
単に"onnxruntime.dll"
とだけ指定すれば前もってctypes.CDLL
で開いた方のonnxruntime.dllを開いてくれるので、compatible_engine
だとそれでいいのではと思います。voicevox_onnxruntime.dllでも同様 (確かめました)。
あ。仮称かもなのですが、
libloading
やlink-dylib
の名称がややこしめなのがちょっと気になりました! たぶんdylibは避けて、役目で分けちゃうのがわかりやすいかなと。 で、「iOSのdylibはこれ使うと良いよ」みたいな案内がある感じとか。2値だから1つにまとめてtrue/falseで制御するとかもありかも。 だとしたらlinkする(
link-dylib
)かしない(libloading
)かだから、onnxruntime-link
でtrue
とfalse
とか・・・・・?
RustのfeatureはAPIの拡張を行うもので「デフォルトの挙動」を変えるべきではないとされているので、それでやるとしたらonnxruntime-link
| onnxruntime-no-link
の二択で両方選択したらコンパイルエラーという形ですね。
(といってもfeatureでデフォルトの挙動を変えるクレートは世に溢れていて、ortもその一つだったりするのですが…)
名称・・・この辺りのリンク方法?、なんて呼ぶんですかね。。。
libloading
は実行時の読み込みだから、dynamic-load
とか・・・?link-dylib
はビルド時のリンクだから、buildtime-link
とか・・・? うーん。。。
「静的リンク」, 「ロード時動的リンク」, 「実行時動的リンク」の三つのどれかなのか混乱しないような名前ですかね… (pykeio/ortはONNX Runtimeの静的リンクをやっているライブラリだったりするし)
SymInitialize(W)を使ってよいのなら「externなONNX RuntimeのAPIに対する実体を持っているonnxruntime.dll」を見つけだすことができるのですが、ちょっとこれを使うのには抵抗を覚えました。 単に"onnxruntime.dll"とだけ指定すれば前もってctypes.CDLLで開いた方のonnxruntime.dllを開いてくれるので、compatible_engineだとそれでいいのではと思います。voicevox_onnxruntime.dllでも同様 (確かめました)。
よくわかってないのですが、エンジン側はたしかに名前指定で良さそうに思いました! よくよく考えれば今そんな感じになってました。ファイル名で検索して見つかったらそれ指定、みたいな感じだった記憶。
となると、掲げられた2^3パターンの挙動で良さそう感!!
RustのfeatureはAPIの拡張を行うもので「デフォルトの挙動」を変えるべきではないとされているので
おおーなるほどです。それはそうかも。
となると、片方をデフォルトにし、もう片方はプラスにするみたいなのもありかも?
今のmainブランチの実装(ビルド時にリンクする)をデフォルトとするならonnxruntime-no-link
だけ、ビルド時リンクしないのをデフォルトにするならonnxruntime-link
で良さそう。link-on-build
とかでも良いかもしれない。
「静的リンク」, 「ロード時動的リンク」, 「実行時動的リンク」の三つのどれかなのか混乱しないような名前ですかね…
link-on-build
とlink-on-runtime
とかで良い気もしてきました。
まあ最悪注釈コメントかドキュメントがあれば別に良いかも。
onnxruntime-load
とonnxruntime-link-cdylib
でどうでしょうか?
「ロード時動的リンク」(run-time dynamic linking)ってあまり言わない気がしますし、"link"で通じそうな気がします。
(といっても正確に区別するときにロード時動的リンクと言ったりはするのですが)
追記: 静的リンクをやる予定が一切無いならonnxruntime-link
でもいいかも?
onnxruntime-loadとonnxruntime-link-cdylibでどうでしょうか? 「ロード時動的リンク」(run-time dynamic linking)ってあまり言わない気がしますし、"link"で通じそうな気がします。
良さそう!!
onnxruntime-link
も良さそう。他にはonnxruntime-load-cdylib
も良さそう!
どれもややこしさは解消されてると感じます!!
b1dca9c
(#802)
f0c720c
(#802)
4af36e0
(#802)
249f8f3
(#802)
7b57268
(#802)
e8dc442
(#802)
フィーチャ名はload-onnxruntime
とlink-onnxruntime
にしてみました。
…ちょっと"load"と"link"で目が滑りそう。load-ort
とlink-ort
にしたらましになるかも?
(実際今CIが落ちまくったのは、私が"load"と"link"を二箇所で間違えたためです)
https://github.com/VOICEVOX/voicevox_core/pull/802#issuecomment-2195444056 放送でも話したのですが、やはりどうあがいても二つは別々の挙動ですし、Swift APIも多分XCFramework経由でONNX Runtimeを読むことになりそう(i.e. "load"ではなく"link"で固定)なのでこのアイデアは取り止める方向にしようと思います。
c18e335
(#802): build_and_deploy
でsed
をし損ねていたので直しました。1492995
(#802): ドキュメントの表記のずれとかを直しました。e619d8b
(#802): C APIにOnnxruntime::LIB_{,UN}VERSIONED_FILENAME
のgetterを追加しました。ae1c71a
(#802)
ドキュメントで"ONNX Runtimeのfilename"としていたところを、"ONNX Runtimeのファイル名(モジュール名)もしくはファイルパス"にしてみました。
やはりどうあがいても二つは別々の挙動ですし、Swift APIも多分XCFramework経由でONNX Runtimeを読むことになりそう(i.e. "load"ではなく"link"で固定)なのでこのアイデアは取り止める方向にしようと思います。
どういうときにどっちを使うのか、がわかりやすい形で案内できるならそれでも良いと思います!
ちなみに2つの経路で別の関数を使うのであれば、featureで切り替えることなくどちらも実装できたりしますか・・・?
ちなみに2つの経路で別の関数を使うのであれば、featureで切り替えることなくどちらも実装できたりしますか・・・?
APIの表面は別にこうしなきゃいけないという制約は無いと思うんですが、2つの経路はほぼ直交しているので片方は"unsupported"と返すくらいしかできないんじゃないかと思ってます。ならconditional compilationが楽にできるC, Rust, (あとSwift?)は分岐したままでいいんじゃないかなーと。ロード時動的リンクの用途も「dlopen
入りのバイナリはストアにリジェクトされるので抜きにしたい」くらいしか思い浮かばないですし…
あ、dlopenがバイナリに含まれてるとリジェクトされるんでしたっけ。だとしたら無くさないといけない気がします。
分ける場合(片方の関数が見えない場合)、ドキュメントの案内が大変になるだろうなーと想像しています。 C APIを使う場合、
みたいなのがいろんなとこに必要になりそうです。
書いてて実行時にdefineしなきゃいけないことに気づきました。 この辺りのややこしさがあるので、どっちでも良いならまとめた方が良いのでは派です。
バイナリのリリースではiOSだけlinkで、他は全部loadでいいと思います (今のPRの状態がそうなっています)。iOSのリリース形態はXCFrameworkなのでちょうどよい区分になるかと。あとバイナリリリースではヘッダをsed
して片方を#define
しているので、ユーザーが選ぶという感じではないような感じがします。ただ「iOSだけ違うので注意」ということにはなりそうてすが。
c79c3f2
(#802)
init_once
とload_once
の片方のみが利用であることがC APIのドキュメントからわからないので、"Availability"というセクションを追加してみました。
バイナリのリリースではiOSだけlinkで、他は全部loadでいいと思います (今のPRの状態がそうなっています)。
こちらは賛成です!
あと気づいたのですが、C 言語・Rust以外もlink/load片方だけ提供で良さそうなことに気づきました! なのでCとRustのドキュメントがOK なら 良さそう!
Availability
良さそう! だけど結構見ない人多そう・・・! それでもここに案内があればそれで良さそう!
UX に関して色々考えてみました! 結論から言うと、関数は分けていいと思います!!
先ほどの通り、CとRust以外のドキュメントはそもそもlink/load片方しか出ないので無視できるはず。 あとはCとRustでどう調理するかかなと!
RustとC言語APIは、結構プログラミングを知ってる人を想定して良いと思います。 なので、結構プログラミングが分かってる人に伝わるドキュメントが書けるならOK。
Rustユーザーは相当分かっていると思うので、link/loadをそのまま 説明してもOK。 link用とload用の関数が分かれているのもOK。多分むしろ別れていた方が良い。
Cユーザーはわりとライト層だと思うので、link/loadをそのまま説明するのは避けた方が良さそう。 代わりに「iOSかどうか」で説明を分けるのなら伝わりそう。この説明だったら別れてても良いはず。
Rust向けドキュメントは置いといて、C向けドキュメントは各関数ごとに「iOSでしか使えないか、iOS以外でしか使えないか」が案内されてれば良さそう! 一応ちゃんとわかってる人向けに、リンクなのかロードなのかみたいな説明もあっても良さそう。
Cユーザー向けのVOICEVOXコアの使い方がいろんな記事で紹介され始めたらもっと簡略化しても良さそう。 それまではちょっとメンテナンスめんどくさいけど丁寧かつ初学者にもちょっと分かりやすい説明の方が良さそう
Rust向けドキュメントは・・・お任せします!! 多分 @qryxip さんが分かりやすい形が一番わかりやすい!
dc587ae
(#802): C APIでの紹介を「iOSか否か」にしてみました。またRust APIのCargo feature部分に書いていたコメントを、Rust APIのrustdocに移した上で説明も詳細化しました。
e99b293
(#802): C APIのビルドについて書いてあるドキュメントにヘッダの書き換えについて書いてなかったので、付け加えました。
c545f89
(#802)
f60cd0e
(#802)
このPRの主旨から若干外れますが、cargo test -p voicevox_core --features {load,link}-onnxruntime
(非--workspace
)がdoctestを含めて通るようにしました。
(このPRで織り込んだdoctest用の工夫が今後壊れるかもしれないため)
追記: CIはそのままにしました。非--workspace
ビルドでのテストはそんなに重要じゃないので。
download_test
が落ちてるのは、https://github.com/VOICEVOX/onnxruntime-builder/pull/26でDirectMLとCUDAを一緒くたにしてしまったため (microsoft/onnxruntimeでは"-gpu"と"-cuda12"で分けている)。なんか故意にやったような記憶があるような無いような気がするけど、CUDA版も結構大きいし普通に分けた方がいいかも…?ちょんと把握できてないのですが、とりあえず問題なければ(mainにマージすればテストは通るとか、リリースを一度すれば通るとか)マージしても大丈夫かなと!
問題はある(ダウンローダーで--device directml
を指定するとonnxruntime_providers_cuda.dllまで付いてくるし、リリースはできてもdownload_test
は落ちる)のですが、onnxruntime-rsからortに切り替えた時点でこの問題は発生しており、このPRに起因はしていないと思います。
すいませんちょっとわかっておらず。。。 🙇 あ! ortに切り替える時点でdownload_testは落ちるはずだったんだけど、download_testの稼働条件にcargo.tomlの更新が含まれいなくてテストが回ってなく、このプルリクエストで初めてテストが回った、みたいな感じでしょうか?
ま~だとしたらこのプルリクエストはマージしちゃってもいいのかなと思いました!! ただmainブランチのテストが落ちちゃうことにはなる?ので、気になるようでしたら先にそっちを修正でもいいかなと。
どちらでも!!別にいいならマージしていただければ! (個人的にはどっちにしろ一長一短ですが、このプルリクエストは大きいのでとりあえずマージが進めやすいのかなと思いました。)
mainブランチのテストが落ちちゃうことにはなる?ので
このリポジトリでdownload_test
が最後に動いたのは5ヶ月前ですね。
https://github.com/VOICEVOX/voicevox_core/actions/workflows/download_test.yml
download_testの稼働条件にcargo.tomlの更新が含まれいなくてテストが回ってなく、このプルリクエストで初めてテストが回った、みたいな感じでしょうか?
こんな感じです…
私が貼ったGHAのログは、qryxip/voicevox_coreで「バージョン999.999.999
のリリース」を行うことによってdownload_test
を作動させたものです。
あーーーーーーなるほどです!!! このPRのテストが落ちていたのが、download_test由来だと勘違いしてました!!! そうか、テスト落ちるよねってことでそういえば停止してましたね。。。
追加コミットも見ました、コード的にはOKなのでマージしてOKならしていただければ!! (完了のタイミングがわからない)
@takejohn 共有です!
Onnxruntime
が追加され、Synthesizer
のコンストラクタに要求されるようになりました。
supported_devices
はOnnxruntime
のメソッドになりました。Synthesizer
はOnnxruntime
のgetterを持つようになります。load-onnxruntime
とlink-onnxruntime
が追加されました。ただしPythonとJavaではload-onnxruntime
で固定です。Node.jsもそうすることになると思います。
内容
パブリックAPIに
Onnxruntime
型を追加し、そこからlibonnxruntimeのdlopen
/LoadLibrary*
をするようにします。Onnxruntime
型Synthesizer
のコンストラクトはこのようになります。オプションとしてDLLの"filename"を指定することもできます。
あと、
supported_device
はOnnxruntime
のメソッドになりました。iOSのXCFramework
Rust APIとC APIだけ、
dlopen
/LoadLibrary*
ではなくロード時動的リンクで動く形でビルドできるようにしています。C APIだと、GHAでのパッケージング時に次のどちらかのコメントアウトを
sed
で外すようにします。VOICEVOX_ONNXRUNTIME_LIBLOADING
の場合、dlopen
/LoadLibrary*
でlibonnxruntimeをロードします。VOICEVOX_ONNXRUNTIME_LINK_DYLIB
の場合、libonnxruntimeをロード時動的リンクします。load_once
はinit_once
となり、filename
のオプションが消失します。iOSのXCFrameworkのみ
VOICEVOX_ONNXRUNTIME_LINK_DYLIB
で、他はVOICEVOX_ONNXRUNTIME_LIBLOADING
です。libonnxruntimeとlibvoicevox_onnxruntime
Onnxruntime.LIB_NAME = "onnxruntime"
としていますが、https://github.com/VOICEVOX/voicevox_project/issues/24以降はこれを"voicevox_onnxruntime"
にする方向で考えています。考えているのは大体次の通りです。VOICEVOX_ONNXRUNTIME_LINK_DYLIB
の場合リンク対象をlibonnxruntimeのままにする。変えたければpatchelf
かinstall_name_tool
でバイナリを変更するという形 (実際、今のCOREのXCFrameworkをビルドするにあたってはinstall_name_tool
が使われています)関連 Issue
Resolves #721. Fixes #537. Closes #445.
その他