はなちるのマイノート

Unityをメインとした技術ブログ。自分らしくまったりやっていきたいと思いますー!

【Unity, iOS】OnnxRuntimeをiOS端末で動作するように修正してみる

はじめに

今回はOnnxRuntimeiOSで動作させてみようという記事になります。

github.com

ONNX Runtime: cross-platform, high performance ML inferencing and training accelerator

一応公式でiOSに対応していると記述されていますが、Unityで動作させるのにはかなり苦労をしました。

OnnxRuntimeの対応早見表

ONNX Runtime | Home


備忘録の意味合いも兼ねて書き残しておきたいと思います。

環境

Unity2021.3.0f1
OnnxRuntime v1.11.1
macOS Monterey

依存先のライブラリのインストール

OnnxRuntimeを動作させるために依存しているライブラリをダウンロードします。


それぞれ拡張子を.zipに変更した後展開、〇〇/lib/net461/〇〇.dllPluginsフォルダの中に入れてください。

dllをインポート

一応補足ですがiOS.dllって大丈夫?と思う方もいるかもしれませんが、こいつらはマネージプラグインとして扱われるので全く問題はありません。
マネージプラグイン - Unity マニュアル

OnnxRuntimeをインストール

次に本命のOnnxRuntimeを入れます。こちらも.dllが配布されているのですが、中のコードを弄らないとiOSでは動作しなかったため、ソースコードを入れちゃいます。

github.com

GitHubonnxruntime/csharp/src/Microsoft.ML.OnnxRuntime/の中身をUnityにぶち込んじゃいます。

ソースコードを入れる


あとはプラットフォーム依存のライブラリを入れる必要があるので、Microsoft.ML.OnnxRuntimeNuGetから取ってきて、runtimesの中身だけPluginsに入れる必要があります。
NuGet Gallery | Microsoft.ML.OnnxRuntime 1.11.0

無事にインポートできたらプラットフォーム設定をインスペクターから行う必要があるのですが、これはかなりめんどくさい作業ですので頑張ってください。

runtimesをPluginsに入れる
プラットフォーム設定(一個ずつ設定する必要あり)

修正する箇所

具体的に以下の操作を行う必要があります。

  • 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です。

Allow 'unsafe' Codeを有効に
アセンブリ単位でunsafeを有効にする

プロジェクト単位だと範囲広すぎだと怒り出しちゃう人がいるかもしれないですが、Assembly Definition Filesにて設定することができます。

Allow 'unsafe' Codeを有効に

また画像だと名前がOnnxRuntimeにしちゃいましたが、他の重複があるっぽい?のでMicrosoft.ML.OnnxRuntimeあたりが良いかと思います。

依存先にCoreMLを設定する

OnnxRuntimeiOSで動作させるためにはCoreMLを依存することを明示しないとダメなようです。

onnxruntime.frameworkフォルダを選択するとインスペクターにPlatform Settingsが表示されるので、Framework dependenciesの中のCoreMLにチェックマークを入れます。

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 SettingsEnable BitcodeNoに変更すれば出なくなるようです。

サンプルコードの用意

動作確認のためにサンプルコードを用意したいと思います。

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 EditoriOSではエラーなく実行できました。

ひとこと

MacOSiOSでしか動作確認していないので、他プラットフォームでこの記述が正しく動作するかは分かっていないです。

もしエラーが出てくるようなら適宜修正をしてみてください。

ちなみに以下プラットフォームならUnity上OnnxRuntimeの動作確認済みです。

  • Windows
  • MacOS
  • Andriod
  • iOS