Closed17

TensorFlow LiteのGPU Buffer BindingをUnityで対応したい

TensorFlow Lite for Unityでこのissueの対応

https://github.com/asus4/tf-lite-unity-sample/issues/23

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へバインディングする方法が提供されているので、それを使いたい…。

https://github.com/tensorflow/tensorflow/blob/master/tensorflow/lite/g3doc/performance/gpu_advanced.md#inputoutput-buffers-ios-c-api-only

しかし、APIはC++でしか提供されておらず、C APIのラッパーであるUnityからは使えないのがネックになっています。今まで目をつむっていましたが、このライブラリを使うことが増えているので、本腰を入れて対応して、TensorFlow本体へPR作りたいと思います。

C++のGPUバインディングについてもドキュメントが少なすぎて、サンプルを探そうにもMediaPipeで使われているくらい。(これはむしろ、MediaPipeで使うためにこのAPIを用意したのでは無いかと思っています)

https://github.com/google/mediapipe/blob/7c331ad58b2cca0dca468e342768900041d65adc/mediapipe/calculators/tflite/tflite_inference_calculator.cc

そのため、GPUバインディングの最小動作を確認する所からはじめます。
テストに使用するモデルはGPUを使ったときに、非対応opsの一切ないDeepLab。

https://www.tensorflow.org/lite/examples/segmentation/overview

テスト用プロジェクトをこちらにひな形を作成しました。
結局つかいませんでした。

https://github.com/asus4/TFLite-BindGPU-Test

久しぶりに再開。InputのComputeBuffer (in Metal) がバインディングに成功。

TensorFlow側

https://github.com/asus4/tensorflow/tree/bindbuffer-c-api

GPUバインディングをC APIから使えるうように修正中。

Unity側

https://github.com/asus4/tf-lite-unity-sample/tree/gpu-binding

ハマリポイントしては、モデルの入力画像が3chでもMealBufferは4chでバッファを確保しないと行けない。
MediaPipeのソースを読んで気づいた。

まだOutput側のバインディングがうまくいっていない。
DeepLabはアウトプットが1x257x257x21という変則的なモデルなので、
Meetの1x128x128x2のモデルで試して見る

Metal でバインディングが動いた!!!!

Output側も同じくGPUとバインディングするときは4chにパッティングされるぽい。
MediaPipeのコード読まないと分からんじゃん…。

MediaPipe該当部分↓

https://github.com/google/mediapipe/blob/ecb5b5f44ab23ea620ef97a479407c699e424aa7/mediapipe/calculators/tflite/tflite_inference_calculator.cc#L1046-L1047

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.

https://github.com/asus4/tf-lite-unity-sample/issues/111

こっちのエラーっぽいので、こちらを先に対応する。

作業開始

iOSでもMetal DelegateがGPU binding付きで動作することを確認。

Androidの実装に取り掛かる。

Metal iOSで有意な高速化を確認。
30%以上の高速化。

軽量なモデルほど、GPU/CPU間のデータやり取りが占める割合が高くなるので、効果が出そう。

Android TensorFlow Lite: OpenGLES Delegateは2種類ある。

  • TfLiteGpuDelegateV2Create: Unityで使ってるやる
  • TfLiteGpuDelegateCreate:Obsolute MediaPipeで使ってるやつ

https://github.com/asus4/tensorflow/blob/98b2c2057524fd3959bf8a4fefb62fb5372ca421/tensorflow/lite/delegates/gpu/BUILD#L117-L167

そして、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の取り扱いが全然わからなかったので、ネイティブプラグイン書いた。

https://github.com/asus4/tf-lite-unity-sample/blob/4f7cae307229bea7cdae41d3469cd6b887717da4/Packages/com.github.asus4.tflite/Plugins/tflite_unity_helper.cpp#L7-L19

最近の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のバリデーションがうまくいってないぽい。なぞやん

https://github.com/asus4/tensorflow/blob/5ed1ee50bb143320685d5dff9fde065424036965/tensorflow/lite/delegates/gpu/gl_delegate.h#L122-L125

OpenGLのSSBOのバイトサイズが正しく取れないので、Unity側からバイトサイズを送ってあげるとエラーは消えたが、まだうまく動かない。

https://docs.unity3d.com/Manual/NativePluginInterface.html

OpenGLESのContextがUnityとTensorFlow Liteで合って無いのかもしれない。Metalでは問題なかったんだけどな〜。

う、うごいた〜〜〜。
OpenCLベースの新しい方のGPU delegateと連携することに成功。

こちらのブランチ。Metalのほうがマージされたらこっちも聞いてみようかな…。

https://github.com/asus4/tensorflow/tree/bindbuffer-gpudelegate

Androidのビルド設定にいくつか癖があって、そこもハマリポイント。

このスクラップは2021/05/22にクローズされました
ログインするとコメントできます