Closed15

TFLite Unity Plugin の対応プラットフォーム追加

Koki IbukuroKoki Ibukuro

私が公開しているリポジトリ

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

へ対応プラットフォームを追加してほしいとの依頼があった。仕事だがオープンソースのままで良いとのこと。(ありがたい。)Unity のネイティブプラグイン開発は情報が少ないので、せっかくなので作業ログも残す。やってることはTensorFlow Liteのビルドですが。

今まで対応したやつ

  • macOS x86_64 CPU, MetalDelegate
  • macOS arm64 CPU, MetalDelegate
  • Windows x86_64 CPU
  • Linux x86_64
  • iOS Armv7, CPU, MetalDelegate
  • iOS Arm64, CPU, MetalDelegate
  • Android Arm64, CPU, GPUDelegateV2, OpenGLDeleate

追加で対応予定プラットフォーム

  • Android Armv7: CPUライブラリ
  • Android Armv7: GPUDelegatev2, OpenGLESDelegate
  • Linux Arm64: CPU
  • Linux Arm64: GPUDelegatev2
    • ビルド通ることまで確認して、OpenCL Delegateまでつくった。デバイスごとの環境依存が多そうなのでちょっと考え中。

v2.9からビルドが通らなくなったので修正が必要なやつ

  • macOS: MetalDelegate

もし動作しそうなら以下もおまけでやろうかな

  • Linux x86_64: GPUDelegatev2
  • Windows x86_64 GPUDelegatev2
Koki IbukuroKoki Ibukuro

作業中 in PR
https://github.com/asus4/tf-lite-unity-sample/pull/228

  • Android Armv7: CPUライブラリ
  • Android Armv7: GPUDelegatev2

については、複数のcpuビルドをAndroidのaar形式にパッケージングするbazelを発見したので、soライブラリの代わりに、aar形式で組み込むことに。

https://github.com/tensorflow/tensorflow/blob/d8ce9f9c301d021a69953134185ab728c1c248d3/tensorflow/lite/java/BUILD#L124-L137

https://github.com/tensorflow/tensorflow/blob/d8ce9f9c301d021a69953134185ab728c1c248d3/tensorflow/lite/java/BUILD#L171-L177

Koki IbukuroKoki Ibukuro

Windows CPUはデフォルトでXNNDelegate付きでアップデートした。Draft PR作って作業中。

https://github.com/asus4/tf-lite-unity-sample/pull/228

macOSはCPUは問題なくビルドできるものの、GPUDelegateがv2.9.0から壊れていることを確認。

ERROR: /Users/ibu/Projects/ML/tensorflow/tensorflow/lite/delegates/gpu/BUILD:166:12: Linking tensorflow/lite/delegates/gpu/tensorflow_lite_gpu_dylib_bin failed: (Aborted): wrapped_clang_pp failed: error executing command external/local_config_cc/wrapped_clang_pp @bazel-out/macos-arm64-min10.13-applebin_macos-darwin_arm64-opt-ST-4d5b244fa833/bin/tensorflow/lite/delegates/gpu/tensorflow_lite_gpu_dylib_bin-2.params
Undefined symbols for architecture arm64:
  "absl::lts_20211102::UnknownError(std::__1::basic_string_view<char, std::__1::char_traits<char> >)", referenced from:
      tflite::gpu::metal::(anonymous namespace)::AllocateTensorMemory(id<MTLDevice>, tflite::gpu::StrongShape<(tflite::gpu::Layout)12> const&, tflite::gpu::TensorDescriptor const&, void const*, id<MTLBuffer> __autoreleasing*, id<MTLTexture> __autoreleasing*) in libmetal_spatial_tensor.a(metal_spatial_tensor.o)
      tflite::gpu::metal::(anonymous namespace)::CreateTextureBuffer(id<MTLBuffer>, unsigned long long, tflite::gpu::StrongShape<(tflite::gpu::Layout)12> const&, tflite::gpu::TensorDescriptor const&, id<MTLTexture> __autoreleasing*) in libmetal_spatial_tensor.a(metal_spatial_tensor.o)
      tflite::gpu::metal::CreateSharedImage2DBufferTensor(id<MTLBuffer>, tflite::gpu::StrongShape<(tflite::gpu::Layout)12> const&, tflite::gpu::TensorDescriptor const&, int, tflite::gpu::metal::MetalSpatialTensor*, unsigned long long) in libmetal_spatial_tensor.a(metal_spatial_tensor.o)
      tflite::gpu::metal::LinearStorage::CreateFromTensorLinearDescriptor(tflite::gpu::TensorLinearDescriptor const&, id<MTLDevice>) in liblinear_storage.a(linear_storage.o)
      tflite::gpu::metal::Texture2D::CreateFromTexture2DDescriptor(tflite::gpu::Texture2DDescriptor const&, id<MTLDevice>) in libtexture2d.a(texture2d.o)
      tflite::gpu::metal::(anonymous namespace)::CreateTexture2D(int, int, tflite::gpu::DataType, void*, id<MTLDevice>, tflite::gpu::metal::Texture2D*) in libtexture2d.a(texture2d.o)
  "absl::lts_20211102::DataLossError(std::__1::basic_string_view<char, std::__1::char_traits<char> >)", referenced from:
      tflite::gpu::metal::InferenceContext::RestoreDeserialized(absl::lts_20211102::Span<unsigned char const>, id<MTLDevice>, tflite::gpu::CreateGpuModelInfo*) in libinference_context.a(inference_context.o)
  "absl::lts_20211102::InternalError(std::__1::basic_string_view<char, std::__1::char_traits<char> >)", referenced from:
      tflite::gpu::metal::(anonymous namespace)::DelegatePrepare(TfLiteContext*, TfLiteDelegate*)::$_1::__invoke(TfLiteContext*, char const*, unsigned long) in libmetal_delegate.a(metal_delegate.o)
      tflite::gpu::metal::(anonymous namespace)::DelegatePrepare(TfLiteContext*, TfLiteDelegate*)::$_4::__invoke(TfLiteContext*, TfLiteNode*) in libmetal_delegate.a(metal_delegate.o)
      tflite::gpu::metal::ComputeTask::CompileProgram(tflite::gpu::metal::MetalDevice*, std::__1::basic_string<char, std::__1::char_traits<char>, std::__1::allocator<char> > const&, std::__1::map<std::__1::basic_string<char, std::__1::char_traits<char>, std::__1::allocator<char> >, std::__1::basic_string<char, std::__1::char_traits<char>, std::__1::allocator<char> >, std::__1::less<std::__1::basic_string<char, std::__1::char_traits<char>, std::__1::allocator<char> > >, std::__1::allocator<std::__1::pair<std::__1::basic_string<char, std::__1::char_traits<char>, std::__1::allocator<char> > const, std::__1::basic_string<char, std::__1::char_traits<char>, std::__1::allocator<char> > > > > const&) in libcompute_task.a(compute_task.o)
      tflite::gpu::metal::(anonymous namespace)::AllocateTensorMemory(id<MTLDevice>, tflite::gpu::StrongShape<(tflite::gpu::Layout)12> const&, tflite::gpu::TensorDescriptor const&, void const*, id<MTLBuffer> __autoreleasing*, id<MTLTexture> __autoreleasing*) in libmetal_spatial_tensor.a(metal_spatial_tensor.o)
      tflite::gpu::metal::MetalSpatialTensor::ReadData(id<MTLDevice>, void*) const in libmetal_spatial_tensor.a(metal_spatial_tensor.o)
      tflite::gpu::metal::MetalSpatialTensor::WriteData(id<MTLDevice>, void const*) in libmetal_spatial_tensor.a(metal_spatial_tensor.o)
      tflite::gpu::metal::CreateComputeProgram(id<MTLDevice>, std::__1::basic_string<char, id<MTLDevice>::char_traits<char>, id<MTLDevice>::allocator<char> > const&, std::__1::basic_string<char, id<MTLDevice>::char_traits<char>, id<MTLDevice>::allocator<char> > const, id<MTLDevice>::map<id<MTLDevice>::allocator<char>, id<MTLDevice>::allocator<char>, id<MTLDevice>::less<id<MTLDevice>::allocator<char> >, id<MTLDevice>::char_traits<char><id<MTLDevice>::pair<std::__1::basic_string<char, id<MTLDevice>::char_traits<char>, id<MTLDevice>::allocator<char> >, id<MTLDevice>::allocator<char> > > > const&, id<MTLComputePipelineState> __autoreleasing*) in libcommon.a(common.o)
      ...
  "absl::lts_20211102::NotFoundError(std::__1::basic_string_view<char, std::__1::char_traits<char> >)", referenced from:
      _TFLGpuDelegateBindMetalBufferToTensor in libmetal_delegate.a(metal_delegate.o)
      tflite::gpu::metal::(anonymous namespace)::DelegatePrepare(TfLiteContext*, TfLiteDelegate*)::$_1::__invoke(TfLiteContext*, char const*, unsigned long) in libmetal_delegate.a(metal_delegate.o)
      tflite::gpu::metal::ComputeTask::Tune(tflite::gpu::TuningType, tflite::gpu::metal::MetalDevice*) in libcompute_task.a(compute_task.o)
      tflite::gpu::metal::MetalArguments::SetInt(std::__1::basic_string<char, std::__1::char_traits<char>, std::__1::allocator<char> > const&, int) in libmetal_arguments.a(metal_arguments.o)
      tflite::gpu::metal::MetalArguments::SetFloat(std::__1::basic_string<char, std::__1::char_traits<char>, std::__1::allocator<char> > const&, float) in libmetal_arguments.a(metal_arguments.o)
      tflite::gpu::metal::MetalArguments::SetHalf(std::__1::basic_string<char, std::__1::char_traits<char>, std::__1::allocator<char> > const&, tflite::gpu::half) in libmetal_arguments.a(metal_arguments.o)
      tflite::gpu::metal::MetalArguments::SetObjectRef(std::__1::basic_string<char, std::__1::char_traits<char>, std::__1::allocator<char> > const&, tflite::gpu::metal::GPUObject const&) in libmetal_arguments.a(metal_arguments.o)
      ...
  "absl::lts_20211102::strings_internal::CatPieces(std::initializer_list<std::__1::basic_string_view<char, std::__1::char_traits<char> > >)", referenced from:
      tflite::gpu::metal::MetalArguments::GetArgumentBufferStructDefinition(bool) in libmetal_arguments.a(metal_arguments.o)
      tflite::gpu::metal::MetalArguments::GetListOfArgs(int, int) in libmetal_arguments.a(metal_arguments.o)
  "absl::lts_20211102::UnimplementedError(std::__1::basic_string_view<char, std::__1::char_traits<char> >)", referenced from:
      tflite::gpu::GPUObjectDescriptor::PerformConstExpr(tflite::gpu::GpuInfo const&, std::__1::basic_string<char, std::__1::char_traits<char>, std::__1::allocator<char> > const&, std::__1::basic_string<char, std::__1::char_traits<char>, std::__1::allocator<char> >*) const in libmetal_delegate.a(metal_delegate.o)
      tflite::gpu::GPUObjectDescriptor::PerformSelector(tflite::gpu::GpuInfo const&, std::__1::basic_string<char, std::__1::char_traits<char>, std::__1::allocator<char> > const&, std::__1::vector<std::__1::basic_string<char, std::__1::char_traits<char>, std::__1::allocator<char> >, std::__1::allocator<std::__1::basic_string<char, std::__1::char_traits<char>, std::__1::allocator<char> > > > const&, std::__1::vector<std::__1::basic_string<char, std::__1::char_traits<char>, std::__1::allocator<char> >, std::__1::allocator<std::__1::basic_string<char, std::__1::char_traits<char>, std::__1::allocator<char> > > > const&, std::__1::basic_string<char, std::__1::char_traits<char>, std::__1::allocator<char> >*) const in libmetal_delegate.a(metal_delegate.o)
      tflite::gpu::GPUObjectDescriptor::PerformConstExpr(tflite::gpu::GpuInfo const&, std::__1::basic_string<char, std::__1::char_traits<char>, std::__1::allocator<char> > const&, std::__1::basic_string<char, std::__1::char_traits<char>, std::__1::allocator<char> >*) const in libinference_context.a(inference_context.o)
      tflite::gpu::GPUObjectDescriptor::PerformSelector(tflite::gpu::GpuInfo const&, std::__1::basic_string<char, std::__1::char_traits<char>, std::__1::allocator<char> > const&, std::__1::vector<std::__1::basic_string<char, std::__1::char_traits<char>, std::__1::allocator<char> >, std::__1::allocator<std::__1::basic_string<char, std::__1::char_traits<char>, std::__1::allocator<char> > > > const&, std::__1::vector<std::__1::basic_string<char, std::__1::char_traits<char>, std::__1::allocator<char> >, std::__1::allocator<std::__1::basic_string<char, std::__1::char_traits<char>, std::__1::allocator<char> > > > const&, std::__1::basic_string<char, std::__1::char_traits<char>, std::__1::allocator<char> >*) const in libinference_context.a(inference_context.o)
      tflite::gpu::GPUObjectDescriptor::PerformConstExpr(tflite::gpu::GpuInfo const&, std::__1::basic_string<char, std::__1::char_traits<char>, std::__1::allocator<char> > const&, std::__1::basic_string<char, std::__1::char_traits<char>, std::__1::allocator<char> >*) const in libmetal_spatial_tensor.a(metal_spatial_tensor.o)
      tflite::gpu::GPUObjectDescriptor::PerformSelector(tflite::gpu::GpuInfo const&, std::__1::basic_string<char, std::__1::char_traits<char>, std::__1::allocator<char> > const&, std::__1::vector<std::__1::basic_string<char, std::__1::char_traits<char>, std::__1::allocator<char> >, std::__1::allocator<std::__1::basic_string<char, std::__1::char_traits<char>, std::__1::allocator<char> > > > const&, std::__1::vector<std::__1::basic_string<char, std::__1::char_traits<char>, std::__1::allocator<char> >, std::__1::allocator<std::__1::basic_string<char, std::__1::char_traits<char>, std::__1::allocator<char> > > > const&, std::__1::basic_string<char, std::__1::char_traits<char>, std::__1::allocator<char> >*) const in libmetal_spatial_tensor.a(metal_spatial_tensor.o)
      tflite::gpu::GPUObjectDescriptor::PerformConstExpr(tflite::gpu::GpuInfo const&, std::__1::basic_string<char, std::__1::char_traits<char>, std::__1::allocator<char> > const&, std::__1::basic_string<char, std::__1::char_traits<char>, std::__1::allocator<char> >*) const in libmetal_arguments.a(metal_arguments.o)
      ...
  "absl::lts_20211102::substitute_internal::SubstituteAndAppendArray(std::__1::basic_string<char, std::__1::char_traits<char>, std::__1::allocator<char> >*, std::__1::basic_string_view<char, std::__1::char_traits<char> >, std::__1::basic_string_view<char, std::__1::char_traits<char> > const*, unsigned long)", referenced from:
      tflite::gpu::metal::MetalArguments::Init(bool, tflite::gpu::metal::MetalDevice*, tflite::gpu::Arguments*, std::__1::basic_string<char, std::__1::char_traits<char>, std::__1::allocator<char> >*) in libmetal_arguments.a(metal_arguments.o)
  "absl::lts_20211102::InvalidArgumentError(std::__1::basic_string_view<char, std::__1::char_traits<char> >)", referenced from:
      tflite::gpu::metal::ComputeTask::UpdateParams() in libcompute_task.a(compute_task.o)
      tflite::gpu::metal::InferenceContext::InitFromGraph(tflite::gpu::CreateGpuModelInfo const&, tflite::gpu::GraphFloat32 const&, id<MTLDevice>, std::__1::vector<unsigned char, id<MTLDevice>::allocator<unsigned char> >*) in libinference_context.a(inference_context.o)
      tflite::gpu::metal::InferenceContext::RestoreDeserialized(absl::lts_20211102::Span<unsigned char const>, id<MTLDevice>, tflite::gpu::CreateGpuModelInfo*) in libinference_context.a(inference_context.o)
      tflite::gpu::metal::InferenceContext::SetTensor(unsigned int const&, tflite::gpu::metal::MetalSpatialTensor*) in libinference_context.a(inference_context.o)
      tflite::gpu::metal::MetalSpatialTensor::GetGPUResources(tflite::gpu::GPUObjectDescriptor const*, tflite::gpu::metal::GPUResourcesWithValue*) const in libmetal_spatial_tensor.a(metal_spatial_tensor.o)
      tflite::gpu::metal::MetalSpatialTensor::IsValid(tflite::gpu::StrongShape<(tflite::gpu::Layout)10> const&) const in libmetal_spatial_tensor.a(metal_spatial_tensor.o)
      tflite::gpu::metal::MetalSpatialTensor::IsValid(tflite::gpu::StrongShape<(tflite::gpu::Layout)12> const&) const in libmetal_spatial_tensor.a(metal_spatial_tensor.o)
      ...
  "absl::lts_20211102::ResourceExhaustedError(std::__1::basic_string_view<char, std::__1::char_traits<char> >)", referenced from:
      tflite::gpu::metal::InferenceContext::AllocateMemoryForBuffers(tflite::gpu::metal::MetalDevice*) in libinference_context.a(inference_context.o)
ld: symbol(s) not found for architecture arm64
clang++: error: linker command failed with exit code 1 (use -v to see invocation)
Error in child process '/usr/bin/xcrun'. 1
Target //tensorflow/lite/delegates/gpu:tensorflow_lite_gpu_dylib failed to build
Use --verbose_failures to see the command lines of failed build steps.
INFO: Elapsed time: 29.955s, Critical Path: 4.50s
INFO: 417 processes: 3 internal, 414 local.
FAILED: Build did NOT complete successfully

MetalSpatialTensorは多分2.9系から導入されたやつかな?iOSだけ対応で、macOSで動かないっぽいエラー。macOS版のMetal Delegateは恐らく私しか使って使ってないので、自分で直さないといけないんだろうな。後ほどissue建てるか…。

Koki IbukuroKoki Ibukuro

MetalSpatialTensorに関するビルドエラーの件、

https://github.com/google/mediapipe/issues/2275

MediaPipeのIssueを参考に、 --cxxopt=--std=c++17 をつけることでビルド通るっぽいことを確認。目処が立ちそう。TensorFlow Liteのビルドしてるのに、TensorFlowよりMediaPipeのイシューの方が参考になるのが謎。

Koki IbukuroKoki Ibukuro

GpuDelegate をLinuxでビルドするのを試し中。

bazel build --config=linux -c opt --copt -Os --copt -DTFLITE_GPU_BINARY_RELEASE --copt -fvisibility=default --linkopt -s --strip always //tensorflow/lite/delegates/gpu:libtensorflowlite_gpu_delegate.so --verbose_failures

だと、EGL と OpenCLないってエラー。

以下にあるようにEGLへのフォールバックしないオプション足してもEGLへリンク残ってるっぽい??

https://github.com/tensorflow/tensorflow/blob/d8ce9f9c301d021a69953134185ab728c1c248d3/tensorflow/lite/delegates/gpu/BUILD#L206-L214

のでとりあえずEGL, OpenCLを入れる。

# Need to install dependencies EGL and OpenCL?
sudo apt-get install libegl1-mesa-dev
sudo apt install ocl-icd-opencl-dev

MediaPipeのほうがやろうとしてること近いし、似てるissueがあるかも。

https://google.github.io/mediapipe/getting_started/gpu_support.html

https://github.com/google/mediapipe/issues/305

Koki IbukuroKoki Ibukuro

お、MediaPipe参考にしたらこれ通ったかも。EGLからX11抜くオプション付き。

bazel build --config=linux -c opt --copt -Os --copt -DMESA_EGL_NO_X11_HEADERS --copt -DEGL_NO_X11  --copt -fvisibility=default --linkopt -s --strip always //tensorflow/lite/delegates/gpu:libtensorflowlite_gpu_delegate.so
Koki IbukuroKoki Ibukuro

ビルドしたGpuDelegate for LinuxをUnity上で読み込もうとするとEGLがリンクされてないよというエラー

https://github.com/tensorflow/tensorflow/blob/5850c0ba26745f92456234c34ed258b472f07487/tensorflow/lite/delegates/gpu/build_defs.bzl#L3-L15

原因としてはこちらでAndroid以外のビルドからはEGLが除外されているため。とりあえずAndroid以外でもEGLをリンクして上げるとGpuDelegateがUnity上で動作することを確認した。

"//conditions:default": [
            "-lEGL",
            "-lGLESv2",
        ],
Koki IbukuroKoki Ibukuro

Linux GPU delegate 動いた!!!NVIDIA GeForce 1070つないだやつで動作確認するとCPUはCore i5なのに無茶早いぜ。

arm64 embedded linuxの方はクロスコンパイル中にEGLが見つからなくて失敗するな。これは環境いろいろありそう(OpenCL, EGLの対応可否が)なので、あまり深堀しないようにして、実機でそれぞれビルドするのが正解な気もする。

Koki IbukuroKoki Ibukuro

cross-platformなarrを使ったAndroidアプリ。正常に動いていて、動作には影響は無いものの、実機で以下のようなエラーが出るっぽい。

06-16 10:40:26.829 22265 24289 D Unity   : Failed to load native plugin: Unable to load library '/data/app/~~jKYV1xoUoH0hkbgdPssPeA==/com.DefaultCompany.jazz-_hiuZw0JIfq3WfJPpcWKgw==/lib/arm64/libtensorflowlite_jni.so', error 'java.lang.NullPointerException: Attempt to invoke virtual method 'java.lang.ClassLoader java.lang.Class.getClassLoader()' on a null object reference'
06-16 10:40:26.832 22265 24289 D Unity   : Failed to load native plugin: Unable to lookup library path for 'tensorflowlite_gpu_jni.so'.
06-16 10:40:26.833 22265 24289 D Unity   : Failed to load native plugin: Unable to load library '/data/app/~~jKYV1xoUoH0hkbgdPssPeA==/com.DefaultCompany.jazz-_hiuZw0JIfq3WfJPpcWKgw==/lib/arm64/libtensorflowlite_gpu_jni.so', error 'java.lang.NullPointerException: Attempt to invoke virtual method 'java.lang.ClassLoader java.lang.Class.getClassLoader()' on a null object reference'
06-16 10:40:26.834 22265 24289 I tflite  : Created TensorFlow Lite delegate for GPU.

恐らくaar内でJavaのクラスへの参照が残っているけど、Unityアプリでは見つからないってエラー。依存を消せると良いけど。
ちょっと確かめてみる。

Koki IbukuroKoki Ibukuro

Windows のGPUDelegateは大変そうな予感。ひとまず以下のコマンドを試す。

bazel build -c opt --copt -Os --copt -DCL_DELEGATE_NO_GL --copt -DMESA_EGL_NO_X11_HEADERS --copt -DEGL_NO_X11 --copt -DCL_TARGET_OPENCL_VERSION=210 --copt -DTFLITE_GPU_BINARY_RELEASE --linkopt -s --strip always //tensorflow/lite/delegates/gpu:libtensorflowlite_gpu_delegate.so

と以下のエラーがでる。

# Execution platform: @local_execution_config_platform//:platform
cl : Command line warning D9035 : option 'experimental:preprocessor' has been deprecated and will be removed in a future release
cl : Command line warning D9036 : us'Zc:preprocessor' instead of 'experimental:preprocessor'
.\tensorflow/lite/delegates/gpu/gl_delegate.h(19): fatal error C1083: Cannot open include file: 'GLES3/gl31.h': No such file or directory
Target //tensorflow/lite/delegates/gpu:libtensorflowlite_gpu_gl.so failed to build
INFO: Elapsed time: 1.696s, Critical Path: 0.51s
INFO: 6 processes: 6 internal.

OpenGLESをWindowsでもリンクしようとしているみたい。GLDelegateV2は基本OpenCLで動作して、OpenGLESはフォールバック。Windowsでは基本OpenCLはインストールされているものとして、OpenGLESの依存を消す戦略がよさそう。

もしくは以下のリンクのようにOpenGLESをインストールしてしまうか。

https://www.saschawillems.de/blog/2015/04/19/using-opengl-es-on-windows-desktops-via-egl/

Koki IbukuroKoki Ibukuro

/tensorflow/lite/delegates/gpu/api.h自体が結構ヘビーにOpenGLESの実装に依存しているので、難しそうだ。

恐らくWindows用のEGLとOpenGLESを

https://github.com/google/angle

からコンパイルするのが正解な気がするけど、TensorFlow LiteでもMeidaPipeでもWindowsのGPU Delegateは公式に対応されてないので、これはまたいつかの機会に挑戦します。

このスクラップは2022/06/21にクローズされました