VOICEVOX / voicevox_project

VOICEVOX内のプロジェクトを管理するリポジトリ
15 stars 3 forks source link

VOICEVOXコアのプロプライエタリ部分をonnxruntimeに逃がして別々にビルド可能にする #24

Open Hiroshiba opened 1 year ago

Hiroshiba commented 1 year ago

概要

今VOICEVOXのコードは大半がOSSですが、唯一コアのonnxモデル暗号化の部分だけがプロプライエタリになっています。 そのせいでコアはいくつかの問題が浮き彫りになっています。

  1. OSSのGithub Actionsでビルドできないためビルドリソースを気にしないといけない(月3,000分)
  2. プロプライエタリ部分のビルドでコケるるとヒホが解決するまで全体の進捗が止まる
  3. Android版やiOS版などの開発に手順が増えて手間
  4. 実は脆弱性がある

特に2つ目が厄介で、Rust化してからヒホの技量不足により足を引っ張ってしまい、OSS本来の力を出せていません。

そこで生まれたのが 暗号化部分(バイナリの変換)を全部onnxruntime内に逃がす という発想です。(thx @qryxip !) これにより↑の1~4が全部解決し、特にOSS的には「専用のonnxruntimeライブラリさえ用意できればOSS版コアは独立に開発・バージョンアップできる」というかっこいい状態になります。

ちなみにキャラ追加部分は独立できないのですが、これはこちらで取り組み中です。(thx @qwerty2501 !)

手法

この「コアの製品版コードをonnxruntimeに逃がすプロジェクト」は大きく分けて3段階の工程があると思っていて、その工程ごとに人を募集したい感じです。

まず1つ目が「通常のonnxruntimeをGithub Actionsでビルドする」ことです。 Windows CPU/DirectML/CUDA、Mac CPU、Linux CPU/CUDAでのビルドが必要です。 ぶっちゃけここが一番難しい気がしています。

2つ目が「暗号化・復号化を組み込めるような関数を用意する」ことです。 これはload関数の中や外に復号化専用の経路を用意する必要があります。 ぶっちゃけここはこの発想に至ること自体は難しい一方、実装はある程度簡単な気がしています。

3つ目が製品部分、つまり暗号化・復号化処理です。 これはOSSではないため、秘匿コード開発専用のチームに参加して頂くことになります。 ここは難度が不明です。セキュリティ周り詳しい方がいたらぜひ意見聞きたいです。 成果物ができてもコードは公開できませんが、誰と一緒に作ったかはちゃんと公表させていただこうと思います。

その他

興味あれば 👀 リアクションなどいただければ・・・!

参考

Linux armhf版などのonnxruntimeをGithub Actions上でビルドしている例 https://github.com/VOICEVOX/onnxruntime-builder

Rustから直接VOICEVOX Coreを使いたい https://github.com/VOICEVOX/voicevox_core/issues/388

Hiroshiba commented 1 year ago

まず手を付け始められるのは1と、あと3もあります。 (暗号化周りはちょっとonnxruntime関係ないRust周りで困っているので、ご興味あればぜひ・・・ 😇 )

qryxip commented 1 year ago

2つ目の「暗号化・復号化を組み込めるような関数を用意する」ですが、私が前試したときはまず

こういうRust ```toml [profile.release] opt-level = "z" lto = true codegen-units = 1 panic = "abort" strip = true [package] name = "voicevox_decrypt" version = "0.0.0" edition = "2021" publish = false [lib] crate-type = ["staticlib", "cdylib"] [dependencies] cxx = "1.0.91" thiserror = "1.0.38" ``` ```rust use thiserror::Error; #[cxx::bridge(namespace = "voicevox")] mod ffi { extern "Rust" { pub fn decrypt_model(content: &[u8]) -> Result>; } } fn decrypt_model(content: &[u8]) -> Result, DecryptModelError> { Ok(content.to_owned()) } #[derive(Error, Debug)] #[error("Could not decrypt")] struct DecryptModelError; ``` (追記) ついでなので [zeroize](https://docs.rs/crate/zeroize)で囲ってもいいかも

でlibvoicevox_decrypt.aとvoicevox_decrypt.hを作って/voicevox_decryptに突っ込み、こうやってリンクしました。

diff --git a/cmake/CMakeLists.txt b/cmake/CMakeLists.txt
index a5d28fb516..5582bf838f 100644
--- a/cmake/CMakeLists.txt
+++ b/cmake/CMakeLists.txt
@@ -966,6 +966,12 @@ function(onnxruntime_add_include_to_target dst_target)
     endforeach()
 endfunction()

+if(UNIX)
+  list(APPEND onnxruntime_EXTERNAL_LIBRARIES "${REPO_ROOT}/voicevox_decrypt/libvoicevox_decrypt.a")
+elseif(WIN32)
+  list(APPEND onnxruntime_EXTERNAL_LIBRARIES "${REPO_ROOT}/voicevox_decrypt/voicevox_decrypt.lib")
+endif()
+
 # ACL
 if (onnxruntime_USE_ACL OR onnxruntime_USE_ACL_1902 OR onnxruntime_USE_ACL_1905 OR onnxruntime_USE_ACL_1908 OR onnxruntime_USE_ACL_2002)
   set(onnxruntime_USE_ACL ON)
Hiroshiba commented 1 year ago

なーーーるほどです!! 静的リンクは全く考えてませんでした。 全然知らないのですが、この関数を直に叩かれたりとか、そこのバイナリ解析されたりとかでなんか割とあっさり突破されそうなのかもとか思いました。 いやまあ、そんなこと言ってると大体どんな手法でもかなり弱い気もするのですが・・・ 😇

Yosshi999 commented 1 year ago

バイナリ解析まで視野に入れると基本的に防御は難しいと思います。推論時には復号済みのweightsがどこかに置いてある必要があり、そもそもユーザーは自分のマシンのメモリを見ることができるので 本気でやるならいわゆるアンチチートを入れることになるとおもいますが、ちょっとやりすぎな感じもあります

(バイナリ解析を妨害する技術もありますがあまり詳しくないです...)

Hiroshiba commented 1 year ago

まあそのあたりはここで議論しちゃうと・・・って感じかなと思いました。

できる範囲で考えると、暗号部分のコードやビルドコマンドを全部含めて秘匿しておいてactionsで実行、が一番強い気がしました。 となるとまあ2と3が合体した感じになるので2の工程が吹っ飛びますが。。

Hiroshiba commented 1 year ago

議題の1つに、普通のonnxモデル(OSS内にあるもの)を読めるものと、製品版のonnxモデルを読めるものとで別のdllを用意する必要があってしまうという議論がありました。 (thx. @PickledChair !)

提案されているようにonnxruntimeを使う部分も外部ライブラリとして切り出す方法があると思います。 あるいは、onnxモデルを読む関数の引数にcrepted引数を用意して制御可能にする方法もあるかなと!

Hiroshiba commented 1 year ago

このタスク、そろそろ進めたい気持ちになってきました。 以前コアをC++からRustに移行したときに @PickledChair さんが作ってくださったようなタスクリストがとりあえずあると、もう始められるなという所感です。

(時間ができたらリスト作ろうと思いますが、もしご興味ある方いたら作ってくださるとすごく嬉しいです!!!!!)

qryxip commented 1 year ago

思い付くままに書いてみようとしたのですが、細かいタスクリストに落とし込む前に方針の認識合わせが必要かと思いました。方針としてはDiscordとかで話したことから次のようになるかと思っているのですが、どうでしょうか?

あとhttps://github.com/VOICEVOX/voicevox_core/issues/388ですが、正直VOICEVOXとしてはメリットが薄く、私個人のモチベーションに大きく左右されるかと思うので、であればここまでやってから気長にリリースという形にしたいなと思っています。

Rust API提供タスクリスト案 - (Rustのクレートとして使う場合、これをしないと使い勝手に大きく影響する) - Rustdocを書く (issue未作成) - (注意点やコード例も含め、懇切丁寧に) - (日本語ではなく英語で書く選択肢もある。(Rustが普及して欲しい身としては悲しいことに)Rustを読み書きできる日本語話者には多分影響が無い。しかしずんだもん達が英語を話せない以上、非日本語話者に向けてアピールするメリットも薄くはある) - Crates.ioに自動またはアップロードする体制を議論する (issue未作成) - リポジトリの方のバージョンを`0.0.0`で固定する現在のスタイルと相性が悪いので、それを議論する - 二回目以降のバージョンをActionsから`cargo publish`する仕組みを整える (理由は省略するが、最初のバージョンだけは私(@qryxip)の名前で手元から手動でアップロードする) - 「ENGINEと同じことができる」と言えるよう、足りない機能を実装する (どうせやるなら良い第一印象を持ってもらえるようにする) -
Hiroshiba commented 1 year ago

なるほどです、そんな感じで進められると良いのかなと思いました! ここから先は製品版暗号化開発チームを作ってそちらでprivateに進められると良さそうです。 (ちょっとお時間頂くかもです 🙇 )

Rust API提供タスクリスト案

こちらはこのissueと関係性が高くないので、関係性の高い https://github.com/VOICEVOX/voicevox_core/issues/388 で書くのが適当なのかなと思いました!!

sevenc-nanashi commented 1 year ago

APIにencrypted引数を追加する

(あまり暗号化とかに詳しくないのですが) たとえば、ファイルの最初がVVEM(VoiceVox Encrypted Model)で始まってたら暗号化されていると判断&復号、みたいな感じにすると引数の変更がなくなって本家の追従とかがやりやすくなると思いました。

Hiroshiba commented 1 year ago

たしかに引数がなくなると追従しやすそうですね! もし引数追加で微妙な感じになったら引数なくしても良いかもと思いました。