TensorFlow LiteのGPU Buffer BindingをUnityで対応したい
TensorFlow Lite for Unityでこのissueの対応
TensorFlow LiteはiOSではMetal, AndroidではOpenGLによる高速化ができるが、CPUとGPU間のデータ転送が時間がかかります。
The current redundant workflow 🤦
Unity GPU → Unity CPU → TFL CPU → TFL GPU Delegate → TFL CPU → Unity CPU → Unity GPU
今はこんな無駄な経路をたどってGPUを使っているので、
Face Meshという顔の3Dメッシュを推定するプログラムでは、半分以上がGPUとCPU間のやり取りにかかってしまっています。
TensorFlow Liteでは直接Metalや、OpenGLのバッファーをCPUを介さずにGPU delegateへバインディングする方法が提供されているので、それを使いたい…。
しかし、APIはC++でしか提供されておらず、C APIのラッパーであるUnityからは使えないのがネックになっています。今まで目をつむっていましたが、このライブラリを使うことが増えているので、本腰を入れて対応して、TensorFlow本体へPR作りたいと思います。
C++のGPUバインディングについてもドキュメントが少なすぎて、サンプルを探そうにもMediaPipeで使われているくらい。(これはむしろ、MediaPipeで使うためにこのAPIを用意したのでは無いかと思っています)
そのため、GPUバインディングの最小動作を確認する所からはじめます。
テストに使用するモデルはGPUを使ったときに、非対応opsの一切ないDeepLab。
テスト用プロジェクトをこちらにひな形を作成しました。
結局つかいませんでした。
久しぶりに再開。InputのComputeBuffer (in Metal) がバインディングに成功。
TensorFlow側
GPUバインディングをC APIから使えるうように修正中。
Unity側
ハマリポイントしては、モデルの入力画像が3chでもMealBufferは4chでバッファを確保しないと行けない。
MediaPipeのソースを読んで気づいた。
まだOutput側のバインディングがうまくいっていない。
DeepLabはアウトプットが1x257x257x21
という変則的なモデルなので、
Meetの1x128x128x2
のモデルで試して見る
Metal でバインディングが動いた!!!!
Output側も同じくGPUとバインディングするときは4chにパッティングされるぽい。
MediaPipeのコード読まないと分からんじゃん…。
MediaPipe該当部分↓
TFLGpuDelegateWaitType::TFLGpuDelegateWaitTypeActive
を設定することでGPUの待機時間を最小にできるらしいが、Unityエディタ側の挙動が怪しくなった。iOSのMetalでどうなるかを試す。
次にやること:
AndroidのOpenGL ESでのGPUバインディングを試す。
macOS Unity Editorで動いたとおもい、iOSで動かすとこのエラー.
2021-05-04 15:07:45.263733+0200 tf-lite-unity-sample[3869:4148876] Execution of the command buffer was aborted due to an error during execution. Ignored (for causing prior/excessive GPU errors) (IOAF code 4)
2021-05-04 15:07:45.263826+0200 tf-lite-unity-sample[3869:4148876] Execution of the command buffer was aborted due to an error during execution. Ignored (for causing prior/excessive GPU errors) (IOAF code 4)
2021-05-04 15:07:45.269642+0200 tf-lite-unity-sample[3869:4148722] Replacing 235 node(s) with delegate (TfLiteMetalDelegate) node, yielding 1 partitions.
こっちのエラーっぽいので、こちらを先に対応する。
実機でエラー起きるのは直ったぽい。
今日は別件をすすめるので、Android対応はまた後日。
作業開始
iOSでもMetal DelegateがGPU binding付きで動作することを確認。
Androidの実装に取り掛かる。
Metal iOSで有意な高速化を確認。
30%以上の高速化。
軽量なモデルほど、GPU/CPU間のデータやり取りが占める割合が高くなるので、効果が出そう。
Android TensorFlow Lite: OpenGLES Delegateは2種類ある。
- TfLiteGpuDelegateV2Create: Unityで使ってるやる
- TfLiteGpuDelegateCreate:Obsolute MediaPipeで使ってるやつ
そして、GPU Buffer BundingはObsolute版でしか対応してない。
Obsoluteな方に切り替えるか、TensorFlow Lite側に手をつけて、新しい方もBuffer Bindingに対応させるか悩むな…。
なんか対応してない的なエラーが出ている。…、がTfLiteInterpreterOptionsSetErrorReporter
の対応をしないとちゃんとログが見えないな…。
Replacing 235 node(s) with delegate (TfLiteGpuDelegate) node, yielding 1 partitions.
05-07 18:07:33.985 32718 32741 W Unity : Interperter Warning: TfLiteGpuDelegate Invoke: %s
05-07 18:07:33.985 32718 32741 W Unity : TensorFlowLite.Interpreter:TfLiteInterpreterInvoke(IntPtr)
05-07 18:07:33.985 32718 32741 W Unity : TensorFlowLite.Interpreter:Invoke()
05-07 18:07:33.985 32718 32741 W Unity : GpuBindSample:RunInterpreter(Boolean)
05-07 18:07:33.985 32718 32741 W Unity : <Start>d__10:MoveNext()
05-07 18:07:33.985 32718 32741 W Unity : UnityEngine.SetupCoroutine:InvokeMoveNext(IEnumerator, IntPtr)
05-07 18:07:33.985 32718 32741 W Unity :
05-07 18:07:33.985 32718 32741 W Unity : (Filename: ./Runtime/Export/Debug/Debug.bindings.h Line: 39)
05-07 18:07:33.985 32718 32741 W Unity :
05-07 18:07:33.986 32718 32741 W Unity : Interperter Warning: Node number %d (%s) %s.
05-07 18:07:33.986 32718 32741 W Unity :
cなら一瞬なのに、c#側で、printf("hogehoge %d", va_list) のva_listの取り扱いが全然わからなかったので、ネイティブプラグイン書いた。
最近のUnityはil2cpp環境なら、AndroidでもcppをUnityに入れとけば、勝手にビルドしてくれるの便利。
今まではAndroid Studioでaarビルドしてから…とやっていたのに。
実機でちゃんとログを見れるようにした!
Interpreter Warning: TfLiteGpuDelegate Invoke: BhwcToPhwc4: Input data size does not match expected size.
また手が空いたので再開。(本業よりOSS活動のほうを優先したいなあ。)
Androidでは、
TFLite Warning: TfLiteGpuDelegate Invoke: BhwcToPhwc4: Input data size does not match expected size.: required: 196608 : actual: 1
というエラーがでる。どうやら、GPU delegateをアクティブにしたときにInputのバリデーションがうまくいってないぽい。なぞやん
OpenGLのSSBOのバイトサイズが正しく取れないので、Unity側からバイトサイズを送ってあげるとエラーは消えたが、まだうまく動かない。
OpenGLESのContextがUnityとTensorFlow Liteで合って無いのかもしれない。Metalでは問題なかったんだけどな〜。
う、うごいた〜〜〜。
OpenCLベースの新しい方のGPU delegateと連携することに成功。
こちらのブランチ。Metalのほうがマージされたらこっちも聞いてみようかな…。
Androidのビルド設定にいくつか癖があって、そこもハマリポイント。
マルチスレッド周りはGraphicsFenceをTensorFlowと共有できれば対応できるんじゃないかと思ってるけど、難しい気もするな〜〜〜。
TensorFlow側にPR出すところまで行ったのでCloseします。