はじめに
今回はOnnxRuntime
をiOS
で動作させてみようという記事になります。
ONNX Runtime: cross-platform, high performance ML inferencing and training accelerator
一応公式でiOS
に対応していると記述されていますが、Unityで動作させるのにはかなり苦労をしました。
備忘録の意味合いも兼ねて書き残しておきたいと思います。
環境
Unity2021.3.0f1
OnnxRuntime v1.11.1
macOS Monterey
依存先のライブラリのインストール
OnnxRuntime
を動作させるために依存しているライブラリをダウンロードします。
- system.buffers.4.5.1.nupkg
- system.memory.4.5.5.nupkg
- system.runtime.compilerservices.unsafe.6.0.0.nupkg
それぞれ拡張子を.zip
に変更した後展開、〇〇/lib/net461/〇〇.dll
をPlugins
フォルダの中に入れてください。
一応補足ですがiOS
で.dll
って大丈夫?と思う方もいるかもしれませんが、こいつらはマネージプラグインとして扱われるので全く問題はありません。
マネージプラグイン - Unity マニュアル
OnnxRuntimeをインストール
次に本命のOnnxRuntime
を入れます。こちらも.dll
が配布されているのですが、中のコードを弄らないとiOS
では動作しなかったため、ソースコードを入れちゃいます。
GitHub
のonnxruntime/csharp/src/Microsoft.ML.OnnxRuntime/
の中身をUnityにぶち込んじゃいます。
あとはプラットフォーム依存のライブラリを入れる必要があるので、Microsoft.ML.OnnxRuntime
をNuGet
から取ってきて、runtimes
の中身だけPlugins
に入れる必要があります。
NuGet Gallery | Microsoft.ML.OnnxRuntime 1.11.0
無事にインポートできたらプラットフォーム設定をインスペクターから行う必要があるのですが、これはかなりめんどくさい作業ですので頑張ってください。
修正する箇所
具体的に以下の操作を行う必要があります。
NativeLib
の定義の重複unsafe
なコードの利用- 依存先にCoreMLを設定する
- シンボル定義の修正
NativeLibの定義の重複
NativeLib
関連の.cs
が複数あるので、一個だけに減らします。
修正前
- NativeLib.andriod.cs
- NativeLib.ios.cs
- NativeLib.net.cs
- NativeLib.netstandard.cs
↓
- NativeLib.cs
// NativeLib.cs namespace Microsoft.ML.OnnxRuntime { internal class NativeLib { #if !UNITY_EDITOR && UNITY_IOS internal const string DllName = "__Internal"; #else internal const string DllName = "onnxruntime"; #endif } }
また一応NativeLib.andriod.cs
ではlibonnxruntime.so
となっていましたが、Unityではlib
と.so
は取り除いてOKだったはずです。
https://helpdesk.unity3d.co.jp/hc/ja/articles/204791370-Android-%E3%81%AE%E3%83%8D%E3%82%A4%E3%83%86%E3%82%A3%E3%83%96%E3%83%97%E3%83%A9%E3%82%B0%E3%82%A4%E3%83%B3-so-%E3%83%95%E3%82%A1%E3%82%A4%E3%83%AB-%E3%81%AB%E3%81%A4%E3%81%84%E3%81%A6%E3%81%AE%E6%B3%A8%E6%84%8F%E4%BA%8B%E9%A0%85
unsafeなコードの利用
プロジェクトでunsafeを有効にする
まずはプロジェクト単位でunsafe
を利用できるようにする方法を紹介します。
PlayerSettings
を開き、OtherSettings/Allow 'unsafe' Code
にチェックマークを入れればOKです。
アセンブリ単位でunsafeを有効にする
プロジェクト単位だと範囲広すぎだと怒り出しちゃう人がいるかもしれないですが、Assembly Definition Files
にて設定することができます。
また画像だと名前がOnnxRuntime
にしちゃいましたが、他の重複があるっぽい?のでMicrosoft.ML.OnnxRuntime
あたりが良いかと思います。
依存先にCoreMLを設定する
OnnxRuntime
をiOS
で動作させるためにはCoreML
を依存することを明示しないとダメなようです。
onnxruntime.framework
フォルダを選択するとインスペクターにPlatform Settings
が表示されるので、Framework dependencies
の中のCoreML
にチェックマークを入れます。
シンボル定義の修正
シンボル定義をUnity用に変換する必要があるので、以下の.cs
のコードを修正します。
(置換を使えば楽にできます)
__MOBILE__
→((UNITY_IOS || UNITY_ANDROID) && !UNITY_EDITOR)
__ANDROID__
→(UNITY_ANDROID && !UNITY_EDITOR)
__IOS__
→(UNITY_IOS && !UNITY_EDITOR)
__ENABLE_COREML__
→(UNITY_IOS || UNITY_EDITOR_OSX || UNITY_STANDALONE_OSX)
対象のファイルは以下の3つです。
- SessionOptions.shared.cs
- NativeMethods.shared.cs
- AssemblyInfo.shared.cs
XCodeビルド時のエラー
ビルドしようとしたところ以下のエラーが出てきてしまいました。
'.../onnxruntime(onnxruntime_c_api.o)' does not contain bitcode.
これはBuild Settings
のEnable Bitcode
をNo
に変更すれば出なくなるようです。
サンプルコードの用意
動作確認のためにサンプルコードを用意したいと思います。
GitHub
から手書き文字認識のonnx
(mnist-8.onnx
)をダウンロードし、StreamingAssets
の中に入れます。
models/vision/classification/mnist/model at main · onnx/models · GitHub
あとはそれを利用した簡単なコードを書いていきます。
using System; using System.Collections.Generic; using System.IO; using System.Linq; using UnityEngine; using Microsoft.ML.OnnxRuntime; using Microsoft.ML.OnnxRuntime.Tensors; using UnityEngine.UI; public class Sample : MonoBehaviour { [SerializeField] private Text text; private void Start() { var path = Path.Combine(Application.streamingAssetsPath, "model.onnx"); var model = File.ReadAllBytes(path); var session = new InferenceSession(model); // 28x28のグレスケール var input = new float[784]; var inputName = session.InputMetadata.First().Key; var inputDim = session.InputMetadata.First().Value.Dimensions; var inputTensor = new DenseTensor<float>(new Memory<float>(input), inputDim); var inputOnnxValues = new List<NamedOnnxValue> { NamedOnnxValue.CreateFromTensor(inputName, inputTensor) }; var results = session.Run(inputOnnxValues); var scores = results.First().AsTensor<float>().ToArray(); text.text = string.Join(",", scores); foreach (var score in scores) Debug.Log(score); } }
StreamingAssets
の読み込みはAndroid
のときUnityWebRequest
を利用する必要がある点には注意してください。
実験
macOS Editor
とiOS
ではエラーなく実行できました。
ひとこと
MacOS
とiOS
でしか動作確認していないので、他プラットフォームでこの記述が正しく動作するかは分かっていないです。
もしエラーが出てくるようなら適宜修正をしてみてください。
ちなみに以下プラットフォームならUnity上OnnxRuntime
の動作確認済みです。
- Windows
- MacOS
- Andriod
- iOS