本日はUnityの技術調査枠です。
Unity AIのドキュメントを読みながら実際に操作を試して記事に残します。
Unity AI
以下のUnity AIのドキュメントを試しながら実行時のキャプチャをしていきます。
docs.unity3d.com
モデルを実行する
ワーカーを作成したらScheduleを呼び出してモデルを実行します。
worker.Schedule(inputTensor);
Unityエディター内でのモデルの最初のスケジューリングは推論エンジンがコードとシェーダーをコンパイルします。
このときは内部メモリを割り当てる必要があるため、遅くなる可能性があります。その後の実行はキャッシュにより高速化されます。
アプリケーションの起動時にテスト実行を含めると、初期読み込み時間を短縮できます。
推論を複数フレームに分割する
モデルを1レイヤーずつ実行するにはワーカーのScheduleIterableメソッドを使用します。このメソッドはIEnumeratorオブジェクトを作成します。
例えばモデルの実行に50ミリ秒かかる場合、1フレームで実行するとゲームプレイ中にカクツキが生じたり、フレームレートが低下したりする可能性があります。
よりスムーズなパフォーマンスを確保するには10フレームに分割して実行し、1フレームあたり5ミリ秒を割り当てます。
次のコードサンプルでは、モデルを1フレームごとに1レイヤーずつ実行してモデルの実行が終了した後にのみUpdateメソッドの残りの部分を実行します。
// GPUの速度を上げるには数値を大きく設定します。 const int k_LayersPerFrame = 20; IEnumerator m_Schedule; bool m_Started = false; void Update() { if (!m_Started) { // ExecuteLayerByLayerはモデルのスケジュールを開始します。 // モデルのレイヤーを反復処理し、各レイヤーを順番にスケジュールするためのIEnumeratorを返します。 m_Schedule = m_Worker.ScheduleIterable(m_Input); m_Started = true; } int it = 0; while (m_Schedule.MoveNext()) { if (++it % k_LayersPerFrame == 0) return; } var outputTensor = m_Worker.PeekOutput() as Tensor<float>; // cpuCopyTensorは出力テンソルのCPUコピーです。 // これにアクセスして変更することができます。 var cpuCopyTensor = outputTensor.ReadbackAndClone(); // ネットワークを再度実行するにはこのフラグをfalseに設定します。 m_Started = false; cpuCopyTensor.Dispose(); }
以前作成した文字解析のサンプルスクリプトにフレーム分割の処理を組み込んでみました。
using UnityEngine; using Unity.InferenceEngine; using System.Collections; public class SplitInferenceTest : MonoBehaviour { public Texture2D inputTexture; public ModelAsset modelAsset; // Set a larger number for faster GPUs const int k_LayersPerFrame = 20; Model runtimeModel; Worker worker; public float[] results; IEnumerator m_Schedule; bool m_Started = false; Tensor<float> inputTensor; void Start() { Model sourceModel = ModelLoader.Load(modelAsset); // 入力モデルを実行し、出力にソフトマックスを適用する機能グラフを作成します。 FunctionalGraph graph = new FunctionalGraph(); FunctionalTensor[] inputs = graph.AddInputs(sourceModel); FunctionalTensor[] outputs = Functional.Forward(sourceModel, inputs); FunctionalTensor softmax = Functional.Softmax(outputs[0]); // 関数グラフをコンパイルして、ソフトマックスを使用してモデルを作成します。 graph.AddOutput(softmax); runtimeModel = graph.Compile(); // 入力データをテンソルとして作成する。 inputTensor = new Tensor<float>(new TensorShape(1, 1, 28, 28)); TextureConverter.ToTensor(inputTexture, inputTensor); // ワーカーを作成する worker = new Worker(runtimeModel, BackendType.GPUCompute); } void Update() { if (!m_Started) { Debug.Log("Starting inference..."); // ExecuteLayerByLayer はモデルのスケジュールを開始します。 // モデルのレイヤーを反復処理し、各レイヤーを順番にスケジュールするための IEnumerator を返します。 m_Schedule = worker.ScheduleIterable(inputTensor); m_Started = true; } int it = 0; while (m_Schedule.MoveNext()) { Debug.Log("Scheduling layer..."); // 各レイヤーの実行が完了したことを確認します。 if (++it % k_LayersPerFrame == 0) return; } Debug.Log("Inference completed."); // 結果を取得する。 Tensor<float> outputTensor = worker.PeekOutput() as Tensor<float>; var cpuCopyTensor = outputTensor.ReadbackAndClone(); results = cpuCopyTensor.DownloadToArray(); // このフラグを false に設定してネットワークを再実行します m_Started = false; cpuCopyTensor.Dispose(); } void OnDisable() { worker?.Dispose(); inputTensor?.Dispose(); } }
以下の通り、処理が分割されて実行されます。
