Raspberry Pi4 で TensorFlow Lite GPU Delegate (OpenGLES) を試す

#0. 更新履歴
　**(2020/06/27) Tensorflow r2.3用に記述内容を全面修正しました。**

#1. はじめに
　ラズパイ４の **OpenGLES** はリリース当初 ver 3.0 でしたが、2020/1月に **[ver 3.1 にアップデート](https://blogs.igalia.com/itoral/2020/01/17/raspberry-pi-4-v3d-driver-gets-opengl-es-3-1-conformance/)されました**。
　OpenGLES 3.1 では、**ComputeShader** が使えるようになり、GPGPUプログラミングしやすくなります。超うれしいアップデートです。

　一方 TensorFlow Lite には、推論にかかる演算処理を CPUからGPU にオフロードする **GPU Delegate** 機能があります。
　この GPU Delegate は、さらに ```V1(OpenGLES)``` と ```V2(OpenCL)``` とがあり、V1 は OpenGLES 3.1 が使える環境であれば動かすことができます。V2は OpenCL が使える環境での動作を想定していますが、OpenCLが使えない環境では自動で V1 相当の OpenGLES で動作するようになっています。ラズパイ4 や Jetson nano には OpenCL 環境は提供されていませんが、この自動フォールバック機能があるので、**GPU Delegate V2を使っておけば大丈夫** です。

　今回のOpenGLESアップデートにより、この GPU Delegate をラズパイ4で動かせるようになったわけですが、果たして、GPUを活用することでTensorFlow Lite の推論時間を短縮することができるでしょうか。

　本記事は、ラズパイ4の OpenGLES 3.1 環境で **TensorFlow Lite GPU Delegate** を動かす手順についてまとめます。その後、GPU Delegate 有無による性能差比較結果についてもふれます。


　題材として Posenet による姿勢推定を用うこととし、結果表示も OpenGLES で行います。
　手順説明については、その中身の技術解説というより、記事の通りにコマンド入力すればどなたでも動かせるようになることを目標に書いていきます。

![gl2posenet_m.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/265135/c1ce4f8c-420f-9470-8246-d44190c3eb8a.png)

　■（ご参考）
　　[・Coral Edge TPU Dev Board で TensorFlow Lite GPU Delegate V1(OpenGLES) を試す](https://qiita.com/terryky/items/a639ea48f1cf240e8929)
　　[・Coral Edge TPU Dev Board で TensorFlow Lite GPU Delegate V2 (OpenCL) を試す](https://qiita.com/terryky/items/9f096149efbc6e3ac3d2)


#(2020/06/27追記)
　本記事はもともと Tensorflow r2.0~r2.2 を前提に書いたものですが、Tensorflow r2.3 用に記述内容を全面更新しました。というのも、これまでラズパイやJetson nano用に GPUDelegateライブラリをクロスビルドするには、Tensorflow Lite 本体のソースや　```Makefile``` にパッチをあてる必要があったものが、```Bazel``` コマンド一発でクロスビルドできるようになったからです。
　以降、r2.2以前向けの記述を取り消し線で消しつつ、r2.3 以降向けの記述を追記しています。

　さらに、[64bit版のラズパイOSがリリース](https://www.raspberrypi.org/forums/viewtopic.php?f=117&t=275370)されたことに伴い、ラズパイ４を32bitではなく64bitで動かす想定で記述内容を更新しています。同じ aarch64 なので、ラズパイ４だけでなくJetson nano や Coral Devboard でも同じ手順を踏めば GPUDelegate を動かすことができます。

　なお、GPUDelegate使用した場合と使用しない場合の性能変化は、別途[この記事](https://qiita.com/terryky/items/243000fcfcb89af11510)にまとめていますので、併せてご覧頂ければと思います。　



#2. 作業手順

　ラズパイで TFLite GPU Delegate アプリを動かすまでの手順として、大きく２つのステップを踏みます。
　・(step1) 最初にホストPCで TFLite GPUDelegate ライブラリを ~~armv7l~~ aarch64 クロスビルドし、それをラズパイ実機にコピー
　・(step2) ラズパイでサンプルアプリをコンパイル、上記 TFLite GPUDelegate ライブラリをリンク、実行

　以下、このステップごとに説明します。

##2.1 ホストPCでの作業
　ホストPCとして x86_64 Ubuntu 18.04 で作業しました。
　ラズパイで使うための TensorFlow Lite ライブラリをビルドします。

###2.1.1 (前準備) Bazelのインストール
　あらかじめ ```Bazel``` をインストールしておきます。
　どのバージョンの TensorFlow をビルドしたいかによって、インストールする ```Bazel``` のバージョンも変える必要があります。今回は TensorFlow ~~2.0~~ 2.3をベースに作業を行うため、~~```Bazel 0.26.1```~~ ```Bazel 3.1.0```をインストールします。

```sh
# (前準備) Bazel 3.1.0 のインストール
$ wget https://github.com/bazelbuild/bazel/releases/download/3.1.0/bazel-3.1.0-installer-linux-x86_64.sh
$ chmod 755 bazel-3.1.0-installer-linux-x86_64.sh
$ sudo ./bazel-3.1.0-installer-linux-x86_64.sh
```

<details>
<summary>~~Bazel 0.26.1 はこちら~~</summary>
<div>

```sh
# (前準備) Bazel 0.26.1 のインストール
$ wget https://github.com/bazelbuild/bazel/releases/download/0.26.1/bazel-0.26.1-installer-linux-x86_64.sh
$ chmod 755 bazel-0.26.1-installer-linux-x86_64.sh
$ sudo ./bazel-0.26.1-installer-linux-x86_64.sh
```
</div>
</details>


###2.1.2 TFLite GPUDelegate ライブラリのビルド

　GPUDelegateライブラリをビルドする手順として、Android用とiOS用に関しては [公式サイト](https://www.tensorflow.org/lite/performance/gpu_advanced)に記載があります。

　~~ですが、ラズパイのように一般的なLinux用の GPUDelegateライブラリをビルドするのはいろいろと面倒 (詳細を知りたい方は[こちら](https://qiita.com/terryky/items/a639ea48f1cf240e8929)) です。
　いろいろと試行錯誤した結果、コマンド一発でビルドできるようなスクリプトを用意しましたので、それを使います。~~

　一方、ラズパイのように一般的なaarch Linux向けにビルドするには、公式サイトには記載されていませんが、```Bazel``` コマンドの引数として ```--config=elinux_aarch64``` オプションを付与すればOKです。

```sh
# aarch64向けに Tensorflow Lite ライブラリをビルド (libtensorflowlite.so)
bazel build -s -c opt --config=elinux_aarch64 //tensorflow/lite:libtensorflowlite.so

# aarch64向けに GPU Delegate ライブラリをビルド (libdelegate.so)
bazel build -s -c opt --config=elinux_aarch64 --copt="-DMESA_EGL_NO_X11_HEADERS" tensorflow/lite/delegates/gpu:libtensorflowlite_gpu_delegate.so
```

　シンブルな手順でビルドできるようになったので、上記コマンドを素直に入力してもそんなに負担にならないのですが、もっと気軽にコマンド一発で GPUDelegateライブラリをビルドできるようなスクリプトを用意しています。

```sh:aarch64向けに、コマンド一発で「TensorflowLiteライブラリ」と「GPUDelegateライブラリ」をクロスビルドする

$ cd ~/work 

# TFLiteライブラリビルド用のスクリプトをGitHubから取得
$ git clone https://github.com/terryky/tflite_gles_app.git

# TFLiteライブラリビルド実行
$ ./tflite_gles_app/tools/scripts/tf2.3/build_libtflite_r2.3_aarch64.sh
```



<details>
<summary>~~Tensorflow r2.0はこちら~~</summary>
<div>

```sh
# GPUDelegate 有効版 TensorFlow Lite ライブラリをビルド
$ cd ~/work 

# TFLiteライブラリビルド用のスクリプトをGitHubから取得
$ git clone https://github.com/terryky/tflite_gles_app.git

# TFLiteライブラリビルド実行
$ ./tflite_gles_app/tools/scripts/tf2.0/build_libtflite_r2.0_with_gpu_delegate_rpi.sh
```
</div>
</details>


　スクリプトを実行すると、TensorFlow のソースコードを GitHub からダウンロードし、その次に ```configure``` コマンドによるプロンプト待ちになります。
　ここの設定はデフォルト設定で大丈夫だと思いますので、Enterを連射してください。

　```configure``` 設定が終わると、ビルドが始まります。しばらく待つと、~~```libtensorflow-lite.a```~~ Tensorflow Lite 本体のライブラリである ```libtensorflowlite.so``` と、GPUDelegateを使うためのライブラリである ```libtensorflowlite_gpu_delegate.so``` が出来上がるはずです。

 ビルドログの抜粋は下記のとおりです。

```
Cloning into '/home/yositeru/work/tensorflow_r2.3'...
remote: Enumerating objects: 44, done.
remote: Counting objects: 100% (44/44), done.
remote: Compressing objects: 100% (39/39), done.
remote: Total 931186 (delta 11), reused 37 (delta 5), pack-reused 931142
Receiving objects: 100% (931186/931186), 542.81 MiB | 9.07 MiB/s, done.
Resolving deltas: 100% (757135/757135), done.
Checking out files: 100% (22561/22561), done.
Branch 'r2.3' set up to track remote branch 'r2.3' from 'origin'.
Switched to a new branch 'r2.3'
Starting local Bazel server and connecting to it...
INFO: Options provided by the client:
  Inherited 'common' options: --isatty=1 --terminal_columns=150
INFO: Reading rc options for 'clean' from /home/yositeru/work/tensorflow_r2.3/.bazelrc:
  Inherited 'common' options: --experimental_repo_remote_exec
INFO: Reading rc options for 'clean' from /home/yositeru/work/tensorflow_r2.3/.bazelrc:
  Inherited 'build' options: --apple_platform_type=macos --define framework_shared_object=true --define open_source_build=true --java_toolchain=//third_party/toolchains/java:tf_java_toolchain --host_java_toolchain=//third_party/toolchains/java:tf_java_toolchain --define=use_fast_cpp_protos=true --define=allow_oversize_protos=true --spawn_strategy=standalone -c opt --announce_rc --define=grpc_no_ares=true --noincompatible_remove_legacy_whole_archive --noincompatible_prohibit_aapt1 --enable_platform_specific_config --config=v2
INFO: Found applicable config definition build:v2 in file /home/yositeru/work/tensorflow_r2.3/.bazelrc: --define=tf_api_version=2 --action_env=TF2_BEHAVIOR=1
INFO: Found applicable config definition build:linux in file /home/yositeru/work/tensorflow_r2.3/.bazelrc: --copt=-w --define=PREFIX=/usr --define=LIBDIR=$(PREFIX)/lib --define=INCLUDEDIR=$(PREFIX)/include --cxxopt=-std=c++14 --host_cxxopt=-std=c++14 --config=dynamic_kernels
INFO: Found applicable config definition build:dynamic_kernels in file /home/yositeru/work/tensorflow_r2.3/.bazelrc: --define=dynamic_loaded_kernels=true --copt=-DAUTOLOAD_DYNAMIC_KERNELS
INFO: Starting clean (this may take a while). Consider using --async if the clean takes more than several minutes.
----------------------------------------------------
 (configure) press ENTER-KEY several times.
----------------------------------------------------
You have bazel 3.1.0 installed.
Please specify the location of python. [Default is /usr/bin/python3]:

★ここでEnterを数回押してください★

（略）

----------------------------------------------------
 build success.
----------------------------------------------------
-r-xr-xr-x  1 terryky terryky 3020032  6月 27 11:53 libtensorflowlite.so
-r-xr-xr-x  1 terryky terryky 4779264  6月 27 11:54 libtensorflowlite_gpu_delegate.so
```



<details>
<summary>~~Tensorflow r2.0 はこちら~~</summary>
<div>

```
Cloning into '/home/terryky/work/tensorflow_r2.0'...
remote: Enumerating objects: 11, done.
remote: Counting objects: 100% (11/11), done.
remote: Compressing objects: 100% (11/11), done.
remote: Total 817557 (delta 2), reused 9 (delta 0), pack-reused 817546
Receiving objects: 100% (817557/817557), 469.27 MiB | 5.79 MiB/s, done.
Resolving deltas: 100% (661446/661446), done.
Checking out files: 100% (19378/19378), done.
Branch 'r2.0' set up to track remote branch 'r2.0' from 'origin'.
Switched to a new branch 'r2.0'
patching file tensorflow/lite/delegates/gpu/gl/gl_buffer.cc
patching file tensorflow/lite/delegates/gpu/gl/gl_call.h
patching file tensorflow/lite/delegates/gpu/gl/gl_errors.cc
patching file tensorflow/lite/delegates/gpu/gl/gl_program.cc
patching file tensorflow/lite/delegates/gpu/gl/gl_shader.cc
patching file tensorflow/lite/delegates/gpu/gl/gl_sync.cc
patching file tensorflow/lite/delegates/gpu/gl/gpu_info.cc
patching file tensorflow/lite/delegates/gpu/gl/kernels/conv.cc
patching file tensorflow/lite/delegates/gpu/gl_delegate.cc
patching file tensorflow/lite/tools/make/Makefile
Starting local Bazel server and connecting to it...
INFO: Options provided by the client:
  Inherited 'common' options: --isatty=1 --terminal_columns=161
INFO: Reading rc options for 'clean' from /home/terryky/work/tensorflow_r2.0/.bazelrc:
  Inherited 'build' options: --apple_platform_type=macos --define framework_shared_object=true --define open_source_build=true --define=use_fast_cpp_protos=true --define=allow_oversize_protos=true --spawn_strategy=standalone -c opt --announce_rc --define=grpc_no_ares=true --define=PREFIX=/usr --define=LIBDIR=$(PREFIX)/lib --define=INCLUDEDIR=$(PREFIX)/include --copt=-w --config=v2
INFO: Found applicable config definition build:v2 in file /home/terryky/work/tensorflow_r2.0/.bazelrc: --define=tf_api_version=2 --action_env=TF2_BEHAVIOR=1
INFO: Starting clean (this may take a while). Consider using --async if the clean takes more than several minutes.
----------------------------------------------------
 (configure) press ENTER-KEY several times.
----------------------------------------------------
WARNING: Running Bazel server needs to be killed, because the startup options are different.
WARNING: Waiting for server process to terminate (waited 5 seconds, waiting at most 60)
WARNING: --batch mode is deprecated. Please instead explicitly shut down your Bazel server using the command "bazel shutdown".
You have bazel 0.26.1 installed.
Please specify the location of python. [Default is /usr/bin/python]:

★ここでEnterを数回押してください★

（略）

----------------------------------------------------
 build success.
----------------------------------------------------
合計 28696
-rw-rw-r-- 1 terryky terryky 19761050  2月  1 15:45 benchmark-lib.a
-rw-rw-r-- 1 terryky terryky  9618448  2月  1 15:45 libtensorflow-lite.a

```
</div>
</details>



###2.1.3 ビルドできたTFLiteライブラリをラズパイ実機へコピー

2.1.2 で作った ~~```libtensorflow-lite.a```~~ ```libtensorflowlite.so``` と ```libtensorflowlite_gpu_delegate.so``` をラズパイ実機へ ```scp``` します。
とりあえずラズパイ実機上のホーム直下にコピーしておきます。

```sh
$ scp ~/work/tensorflow_r2.3/bazel-bin/tensorflow/lite/libtensorflowlite.so pi@192.168.11.11:/home/pi/
$ scp ~/work/tensorflow_r2.3/bazel-bin/tensorflow/lite/delegates/gpu/libtensorflowlite_gpu_delegate.so pi@192.168.11.11:/home/pi/
```


<details>
<summary>~~Tensorflow r2.0 はこちら~~</summary>
<div>

```sh
$ scp ~/work/tensorflow_r2.0/tensorflow/lite/tools/make/gen/rpi_armv7l/lib/libtensorflow-lite.a pi@192.168.11.11:/home/pi/
```
</div>
</details>


##2.2 ラズパイ実機での作業
　ここからはラズパイ実機上での作業となります。
　ホストPCで作った ~~```libtensorflow-lite.a```~~ ```libtensorflowlite.so``` と ```libtensorflowlite_gpu_delegate.so``` を使ってアプリをビルドしていきます。

###2.2.1 (前準備) OpenGLES ライブラリの更新

　ラズパイにインストールされている OpenGLES ライブラリが ver 3.0 のままだと GPUDelegate は動かせないので、最新版に更新します。

```sh
$ sudo apt install libgles2-mesa-dev libegl1-mesa-dev xorg-dev
$ sudo apt update
$ sudo apt upgrade
```

　念のため、OpenGLES 3.1 が使える状態になっていることを確認しておきましょう。

```sh
# サンプルアプリのソースをGitHubから取得
$ cd ~/work
$ git clone https://github.com/terryky/tflite_gles_app.git
$ cd tflite_gles_app/list_egl_configs
$ make TARGET_ENV=raspi4
$ ./list_egl_config

(略)
============================================
    GL INFO
============================================
GL_VERSION      : OpenGL ES 3.1 Mesa 19.3.2　  # OpenGLES 3.1 が使える！
GL_SL_VERSION   : OpenGL ES GLSL ES 3.10
GL_VENDOR       : Broadcom
GL_RENDERER     : V3D 4.2
(略)

```





###2.2.2 TFLiteヘッダファイルの取得
　今回のサンプルアプリは TFLite の C++ APIを叩きます。これに必要なヘッダファイルをダウンロードします。
　なお、今回のサンプルは、これらのヘッダが ```$HOME/work/tensorflow``` 以下にダウンロードされている前提で Makefile が書かれているのでご注意ください。(下記手順通りの場所に ```git clone``` すれば大丈夫です)

```sh
# GitHub から TensorFlow のソースコードを取得
$ cd ~/work
$ git clone https://github.com/tensorflow/tensorflow.git
$ cd tensorflow

# r2.3 ブランチを使う
$ git checkout r2.3

# 依存ライブラリのヘッダをダウンロード
$ ./tensorflow/lite/tools/make/download_dependencies.sh
```


<details>
<summary>~~Tensorflow r2.0 はこちら~~</summary>
<div>

```sh
# GitHub から TensorFlow のソースコードを取得
$ cd ~/work
$ git clone https://github.com/tensorflow/tensorflow.git
$ cd tensorflow

# r2.0 ブランチを使う
$ git checkout r2.0

# 依存ライブラリのヘッダをダウンロード
$ ./tensorflow/lite/tools/make/download_dependencies.sh
```
</div>
</details>

###2.2.3_0 (Tensorflow r2.3 用に追記) TFLiteライブラリをパスの通った場所へ移動

　ホストPCで作った ```libtensorflowlite.so``` と ```libtensorflowlite_gpu_delegate.so``` は、ラズパイのホームディレクトリに ```scp``` しました。これを、パスの通った場所へ移動します。
　今回のサンプルアプリでは、アプリビルドに必要なライブラリを探すのに ```~/lib``` を見に行くように設定していますので、ライブラリを ```~/lib``` へ移動させます。また、アプリ実行時にこの Shared ライブラリを発見できるよう、環境変数 ```LD_LIBRARY_PATH``` にライブラリ検索パスを追加しておきます。

```sh
# ホストPCからラズパイのホームディレクトリにscpしたライブラリを ~/lib へ移動
$ mkdir ~/lib
$ mv ~/libtensorflowlite.so ~/lib
$ mv ~/libtensorflowlite_gpu_delegate.so ~/lib

# ライブラリ検索パスを登録
$ export LD_LIBRARY_PATH=~/lib:$LD_LIBRARY_PATH
```


###2.2.3 TFLiteサンプルアプリのビルド
　準備が整ったので、いよいよアプリのビルドです。

```sh
# サンプルアプリのソースをGitHubから取得
$ cd ~/work
$ git clone https://github.com/terryky/tflite_gles_app.git
$ cd tflite_gles_app/gl2posenet

# ラズパイ４用にGPUDelegate有効にしてアプリをビルド
$ make clean
$ make TARGET_ENV=raspi4 TFLITE_DELEGATE=GPU_DELEGATEV2

# アプリ実行
$ ./gl2posenet
```


<details>
<summary>~~Tensorflow r2.0 はこちら~~</summary>
<div>

```sh
# サンプルアプリのソースをGitHubから取得
$ cd ~/work
$ git clone https://github.com/terryky/tflite_gles_app.git
$ cd tflite_gles_app/gl2posenet

# ホストPCでビルドした TFLite ライブラリをアプリと同じディレクトリにコピー
$ cp ~/libtensorflow-lite.a .

# ラズパイ４用にGPUDelegate有効にしてアプリをビルド
$ make clean
$ make TARGET_ENV=raspi4 TFLITE_DELEGATE=GL_DELEGATE

# アプリ実行
$ ./gl2posenet
```
</div>
</details>

### 2.2.4 実行結果

**(2020/06/27追記)
　GPUDelegateを使うことによる速度変化については、[こちらの記事] (https://qiita.com/terryky/items/243000fcfcb89af11510) に詳細をまとめていますので、併せてご欄頂ければと思います。** 

アプリを実行すると、下記のようなウィンドウが表示されると思います。各関節の位置が正しく認識できていますね。
GPUDelegate 有効にした状態で、posenet の推論にかかる処理時間は **1544[ms]** でした。

（ご参考）
　このサンプルアプリは、ラズパイにUSBカメラがささっていればカメラ映像で姿勢認識するように作っています。
　ただ、対応ピクセルフォーマットが YUYV (YUV422) 決め打ちなので、カメラによっては動かないかもしれません。
　その場合は USBカメラぬいてください。すみません。

![rpi4_tflite_gpudelegate.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/265135/ccccc716-2839-0435-ecd7-4c5d69091a1f.png)


### 2.2.5 (ご参考) GPUDelegate 使わない場合
　アプリビルド時に、```TFLITE_DELEGATE``` を指定せずに make すると GPUDelegateを使わないバージョンとしてアプリをビルドすることができます。

```sh
# ラズパイ４用にアプリをビルド (GPUDelegateなし)
$ make clean
$ make TARGET_ENV=raspi4

# アプリ実行
$ ./gl2posenet
```

　GPUDelegate を使わない場合のスクリーンキャプチャはこちら。こちらも関節位置は正しく認識できています。
　一方で、推論にかかる処理時間は **214[ms]** でした。ラズパイ4だと、GPUDelegate 使わないほうが速いようです。
![rpi4_tflite.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/265135/f2211240-8314-ef64-b484-a90fc44438e2.png)


# 3. 最後に
　ラズパイ４で TFLite GPU Delegate を動かす具体的な手順について書きました。
　
　ラズパイとして初となる **OpenGLES 3.1 Compute Shader** がハングせずに動いたことは素直に感動しましたが、残念ながらその速度性能はCPUより７倍も遅いという残念な結果となりました。

　ラズパイの Compute Shader 性能については、ラズパイ用OpenGLESライブラリの開発者と思われる方の興味深い発言がありましたので引用しておきます。


https://blogs.igalia.com/itoral/2020/01/17/raspberry-pi-4-v3d-driver-gets-opengl-es-3-1-conformance/
<img width="600" alt="rpi_gles3.1.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/265135/4ce4344a-c7a4-77fa-22a5-c21346d4d271.png">

> ***Jochen***：「TFLite GPU delegate が CPU より３～４倍も遅いんだけど」
> ***Iago Toral***：「(現状OpenGLESの実装は) **WorkGroupが１個しか動かない**ようにハードコーディングしてるよ。この数を増やして並列度を高めれば性能あがるだろうから、そのうち調べないとね」

　現状のOpenGLES実装は、GPUハードが持つ演算性能全てを引き出しているわけではないようです。開発者の方も OpenGLES の性能チューニングの必要性を認識されているとのこと。期待して待とうと思います。