TensorFlow Lite for C/C++ on VAB-5000
前回の記事で産業用Raspberry Piに代わるAPU搭載のシングルボードコンピュータ「VAB-5000」について紹介しました。このVAB-5000の APU(AI Processing Unit)であるMDLA(MediaTek Deep Learning Accelerator) を最大限に活用できるAIアプリケーションを開発するためには、TensorFlow Lite for C/C++を利用して Delegate と呼ばれる、アクセラレーターを呼び出すプログラムを書く必要があります。今回はその前段階となる TensorFlow Lite for C/C++ (Delegate無し) をビルドしてサンプルソースコードをコンパイルする手順をご紹介します。
SoCベンダーのMediaTekが推奨するDelegateの記述例
C/C++で書かないとDelegateにアクセスできません...今回はそのための準備です!
本記事の目的
今回は以上の開発を行うべく、VIA VAB-5000環境上にTensorFlow Lite for C/C++の開発環境を構築する手順についてご紹介いたします。VAB-5000上でTensorFlow Lite for C++を直接ビルドすることはできないため、 本手順では、x86-64 Ubuntu22.04上でTensorFlow Lite for C/C++のソースコードをaarch64向けにクロスコンパイル します。
参考:x86-64向けのTensorFlow Lite for C/C++をビルドする手順
x86-64版のTensorFlow Lite for C/C++を利用したい方はこちらをご覧ください。
開発環境を準備をする
まず、 OSのインストールや開発に便利なツールなどのインストールを行い、 TensorFlow Lite for C/C++をインストールするための土台を組み上げていきます。
- Core i5-14600K (14cores, 20threads)
- RAM 64GB
- On-Board Graphics (CPU)
- Ubuntu 22.04 LTS
- Yocto Linux等、併設する他の開発環境も考慮しました
Ubuntu 22.04 LTSをインストールする
まず、PCに「Ubuntu22.04 LTS」をインストールします。以下のインストーラーを利用し、インストールしました。今回はGPUを搭載せず、マザーボード上のオンボードのHDMIポートを利用しました。
TensorFlow for C/C++をビルドする準備をする
それでは TensorFlow Lite for C/C++ のビルドをはじめていきましょう。ビルドには bazel
、 clang
、その他いくつかのパッケージ
が必要になるため、事前にインストールします。
ビルドに必要なパッケージをインストールする
apt
コマンドで以下のパッケージをインストールしましょう。
$ sudo apt update
$ sudo apt install -y vim
$ sudo apt install -y linux-headers-$(uname -r) build-essential wget git cmake clang lldb lld
$ sudo apt install -y python3-dev python3-pip python3.10-venv
$ python3 -m pip install -U --user pip
$ sudo apt install -y libopencv-dev ffmpeg libavcodec-dev libavformat-dev libavutil-dev libswscale-dev libxml2 libstdc++6
ビルドツール「bazel」をインストールする
続いて、TensorFlow Liteをビルドするための bazel
をインストールしましょう。
$ sudo apt install -y apt-transport-https curl gnupg
$ curl -fsSL https://bazel.build/bazel-release.pub.gpg | gpg --dearmor >bazel-archive-keyring.gpg
$ sudo mv bazel-archive-keyring.gpg /usr/share/keyrings
$ echo "deb [arch=amd64 signed-by=/usr/share/keyrings/bazel-archive-keyring.gpg] https://storage.googleapis.com/bazel-apt stable jdk1.8" | sudo tee /etc/apt/sources.list.d/bazel.list
$ sudo apt update && sudo apt install bazel
ビルドツール「clang」をインストールする
加えて、TensorFlow Liteをコンパイルするための clang
をインストールします。
$ wget https://github.com/llvm/llvm-project/releases/download/llvmorg-17.0.2/clang+llvm-17.0.2-x86_64-linux-gnu-ubuntu-22.04.tar.xz
$ tar -xvf clang+llvm-17.0.2-x86_64-linux-gnu-ubuntu-22.04.tar.xz
$ sudo cp -r clang+llvm-17.0.2-x86_64-linux-gnu-ubuntu-22.04/* /usr
$ clang --version
# clang version 17.0.2 (https://github.com/llvm/llvm-project b2417f51dbbd7435eb3aaf203de24de6754da50e)
# Target: x86_64-unknown-linux-gnu
# Thread model: posix
# InstalledDir: /usr/bin
以上で、クロスビルドするための準備は完了となります。
TensorFlow Lite for C/C++をクロスコンパイルする
それでは、TensorFlow Lite for C/C++をaarch64向けにビルドしてみましょう。
GithubからTensorFlowの【v2.16.1】を入手する。
まず、tensorflow
のリポジトリから git
コマンドを使ってソースコード一式を入手し、git checkout
コマンドで v2.16.1
のソースコードへと切り替えます。 私が試したところ、他のバージョンを利用するとエラーが発生してビルドの通らないことが頻発したため、下記の記事で実績のある v2.16.1
を選択しました。
### 作業用ディレクトリを作成する
$ mkdir cross_tensorflow
$ cd cross_tensorflow/
### tensorflowのソースコードを取得する
$ git clone https://github.com/tensorflow/tensorflow.git
$ cd tensorflow
### 今回はv2.16.1をビルドする
$ git checkout v2.16.1
TensorFlow Lite for C/C++をビルドする
ビルドする前に ./configure
を実行してビルドの構成を決定 します。今回はGPUアクセラレーションを利用しない、以下の構成としました。 ./configure
を行った後、 bazel build
コマンドでTensorFlow Lite for C/C++をビルド します。なお、本手順には bazel-6.5.0
が必要だったため、apt
コマンドでバージョンを変更しました。
### ビルドの設定を行う
$ ./configure
# You have bazel 6.5.0 installed.
# Please specify the location of python. [Default is /home/shino/anaconda3/bin/python3]:
# Found possible Python library paths:
# /home/shino/anaconda3/lib/python3.12/site-packages
# Please input the desired Python library path to use. Default is [/home/shino/anaconda3/lib/python3.12/site-packages]
# Do you wish to build TensorFlow with ROCm support? [y/N]: N
# No ROCm support will be enabled for TensorFlow.
# Do you wish to build TensorFlow with CUDA support? [y/N]: N
# No CUDA support will be enabled for TensorFlow.
# Do you want to use Clang to build TensorFlow? [Y/n]: Y
# Clang will be used to compile TensorFlow.
# Please specify the path to clang executable. [Default is /usr/bin/clang]:
# You have Clang 17.0.2 installed.
# Please specify optimization flags to use during compilation when bazel option "--config=opt" is specified [Default is -Wno-sign-compare]:
# Would you like to interactively configure ./WORKSPACE for Android builds? [y/N]: N
# Not configuring the WORKSPACE for Android builds.
#
# Preconfigured Bazel build configs. You can use any of the below by adding "--config=<>" to your build command. See .bazelrc for more details.
# --config=mkl # Build with MKL support.
# --config=mkl_aarch64 # Build with oneDNN and Compute Library for the Arm Architecture (ACL).
# --config=monolithic # Config for mostly static monolithic build.
# --config=numa # Build with NUMA support.
# --config=dynamic_kernels # (Experimental) Build kernels into separate shared objects.
# --config=v1 # Build with TensorFlow 1 API instead of TF 2 API.
# Preconfigured Bazel build configs to DISABLE default on features:
# --config=nogcp # Disable GCP support.
# --config=nonccl # Disable NVIDIA NCCL support.
# Configuration finished
### ビルドができるか試してみる
$ bazel build --config=elinux_aarch64 -c opt //tensorflow/lite/c:libtensorflowlite_c.so
# ERROR: The project you're trying to build requires Bazel 6.5.0 (specified in /home/shino/tensorflow/.bazelversion), but it wasn't found in /usr/bin.
#
# You can install the required Bazel version via apt:
# sudo apt update && sudo apt install bazel-6.5.0
# ...
### bazelのバージョンを指定してインストールしなおす
$ sudo apt update && sudo apt install bazel-6.5.0
### 再度ビルドする
# C++ライブラリをクロスビルドする
$ bazel build --config=elinux_aarch64 -c opt //tensorflow/lite/c:libtensorflowlite_c.so
# Cライブラリをクロスビルドする
$ bazel build --config=elinux_aarch64 -c opt //tensorflow/lite:libtensorflowlite.so
# Delegateを呼び出す際に利用するライブラリもクロスビルドする
$ bazel build --config=elinux_aarch64 -c opt //tensorflow/lite/experimental/acceleration/compatibility:android_info
$ bazel build --config=elinux_aarch64 -c opt //tensorflow/lite/delegates/utils/experimental/stable_delegate:tflite_settings_json_parser
$ bazel build --config=elinux_aarch64 -c opt //tensorflow/lite/delegates/utils/experimental/stable_delegate:delegate_loader
ビルド成果物を確認する
以下がビルドの成果物となります。 正しく aarch64
向けにビルドされているか を file
コマンドを使って確認しておきましょう。
### C++ライブラリがArm用にビルドされていることを確認する
$ file bazel-bin/tensorflow/lite/libtensorflowlite.so
# bazel-bin/tensorflow/lite/libtensorflowlite.so: ELF 64-bit LSB shared object, ARM aarch64, version 1 (SYSV), dynamically linked, BuildID[md5/uuid]=6dc411bc5ff781a5d77bec37cf4d46b3, stripped
### CライブラリがArm用にビルドされていることを確認する
$ file bazel-bin/tensorflow/lite/c/libtensorflowlite_c.so
# bazel-bin/tensorflow/lite/c/libtensorflowlite_c.so: ELF 64-bit LSB shared object, ARM aarch64, version 1 (SYSV), dynamically linked, BuildID[md5/uuid]=1ffb549a913b1a1d161a8ab9ed3cc11f, stripped
ビルド成果物をtensorflowのディレクトリ内にコピーする
本手順によるビルドでは、ビルドの成果物は tensorflow
のディレクトリ内ではなく、~/.cache/bazel
以下に格納されます。ディレクトリ名はランダムで決まりますので、ビルド後の tensorflow
ディレクトリで ls -al bazel-tensorflow
をすると確認することができます。確認後、対象のディレクトリを tensorflow
ディレクトリ以下へコピーし、シンボリックリンクを貼り直し tensorflow
ディレクトリのみで完結するフォルダ構成にします。
### ビルド成果物を tensorflow ディレクトリ内にコピーする
### -->> ソースコードとまとめてVAB-5000にコピーするため
$ cp ~/.cache/bazel/_bazel_shino/6bb7e8b4bffe091da9c41eb8a1440c54/execroot/org_tensorflow ~/cross_tensorflow/tensorflow/org_tensorflow -r
### bazel-bin ディレクトリのシンボリックリンクを貼り直す
$ rm bazel-bin
$ ln -s org_tensorflow/bazel-out/aarch64-opt/bin/ ./bazel-bin
$ ls bazel-bin
# bin external tensorflow
### bazel-out ディレクトリのシンボリックリンクを貼り直す
$ rm bazel-out
$ ln -s org_tensorflow/bazel-out ./bazel-out
$ ls bazel-out
# _actions aarch64-opt stable-status.txt
# _tmp k8-opt-exec-50AE0418 volatile-status.txt
### bazel-tensorflow ディレクトリのシンボリックリンクを貼り直す
$ rm bazel-tensorflow
$ ln -s org_tensorflow ./bazel-tensorflow
$ ls bazel-tensorflow
# bazel-out external tensorflow third_party
### bazel-testlogsディレクトリのシンボリックリンクを貼り直す
$ rm bazel-testlogs
$ ln -s org_tensorflow/bazel-out/aarch64-opt/testlogs ./bazel-testlogs
$ ls bazel-testlogs
# empty
ビルド成果物をtarに固めてUSBフラッシュメモリにコピーする
以上でaarch64(VAB-5000のアーキテクチャ)向けのTensorFlow Lite for C/C++の準備が整いましたので tensorflow
ディレクトリを tar
でまとめて、USBフラッシュメモリへ書き出しましょう。
### クロスコンパイルした成果物をtarに固めて、USBフラッシュへコピーする
$ tar -cvf tensorflow.tar ./tensorflow
$ ls ./tensorflow.tar
# tensorflow.tar
### USBフラッシュが "/media/shino/23D8-AF6D/" の場合の例
$ cp tensorflow.tar /media/shino/23D8-AF6D/
### 書き込み後umoundする
$ sudo umount /media/shino/23D8-AF6D
VAB-5000上にTensorFlowを利用したアプリ開発によく利用されるパッケージをインストールする (VAB-5000上での操作)
VAB-5000上にTensorFlow Lite for C++をインストールする前に、TensorFlowを使って推論するプログラムによく利用されるパッケージを apt
でインストールします。 特に、最後の libabsl-dev
はTensorFlow Lite for C/C++でDelegateを利用する際に必要とされるパッケージです。
### 一般的な開発ツールをインストールする
$ sudo apt update
$ sudo apt install -y vim
$ sudo apt install -y build-essential wget git curl cmake clang lldb lld
$ sudo apt install -y python3-dev python3-pip python3-venv
$ sudo apt install -y libopencv-dev ffmpeg libavcodec-dev libavformat-dev libavutil-dev libswscale-dev libxml2 libstdc++6
$ sudo apt install -y libabsl-dev
$ sudo find / -type f | grep cleanup.h
# /usr/include/absl/cleanup/cleanup.h
USBフラッシュメモリからクロスコンパイルしたTensorFlow Liteをホームディレクトリへコピーする (VAB-5000上での操作)
クロスコンパイルの結果得られたTensorFlow Lite for C/C++をVAB-5000のホームディレクトリ(/home/debian
)以下にUSBフラッシュメモリからコピーし tar
で展開します。
### クロスコンパイルしたTensorFlowをVAB-5000上に展開する
$ cp /media/shino/23D8-AF6D/tensorflow.tar ~/
$ tar -xvf tensorflow.tar
TensorFlow Lite for C++が利用するflatbuffersをソースコードからコンパイルする (VAB-5000上での操作)
TensorFlow Lite for C/C++でTFLite形式のモデルを読み込む際に flatbuffers
というライブラリが必要となります。git
コマンドにより、FlatBuffersのソースコードを入手し、 git checkout
コマンドでTensorFlowの要求するFlatBuffersのバージョンにソースコードを切り替えます。その後 cmake
を利用してFlatBuffersをビルドします。
### FlatBuffers 23.5.26を入手する
$ cd ~
$ git clone https://github.com/google/flatbuffers.git
$ cd flatbuffers
$ git checkout v23.5.26
### FlatBuffersをビルドする
$ mkdir build
$ cd build/
$ cmake ..
$ make
TensorFlow Lite for C/C++を利用する (VAB-5000上での操作)
それでは実際に、ビルドしたTensorFlow Lite for C/C++を利用してみましょう。TensorFlow Lite for C/C++を呼び出すためのコードは、以下のサイトで非常にわかりやすく書かれていました。とても参考になりました、ありがとうございます。
以上のサイトを参考に収集したソースコード
以下のリポジトリが、以降の手順をまとめたソースコードとなります。 CMakeLists.txt
に含まれているディレクトリのパスは、実行環境に合わせて調整してください。
ビルドしたTensorFlow Lite for C++を参照するCMakeLists.txt (VAB-5000上での操作)
まず CMakeLists.txt
を記述します。 CMakeLists.txt
は cmake
コマンドを実行した際に呼び出され、Makefile
を自動的に生成するためのファイルです。 ファイルパスに上記でビルドしたTensorFlow Lite for C/C++のフォルダ位置を記述してください。 私の手順ではVAB-5000上の /home/debian/tensorflow
にTensorFlow for C++を配置したため、以下のような記述となりました。
cmake_minimum_required(VERSION 2.8)
project(NumberDetector)
# Create Main project
add_executable(NumberDetector
main.cpp
)
# For OpenCV
find_package(OpenCV REQUIRED)
if(OpenCV_FOUND)
target_include_directories(NumberDetector PUBLIC ${OpenCV_INCLUDE_DIRS})
target_link_libraries(NumberDetector ${OpenCV_LIBS})
endif()
# For Tensorflow Lite
target_link_libraries(NumberDetector /home/debian/tensorflow/bazel-bin/tensorflow/lite/libtensorflowlite.so)
target_include_directories(NumberDetector PUBLIC /home/debian/tensorflow)
target_include_directories(NumberDetector PUBLIC /home/debian/tensorflow/tensorflow)
target_include_directories(NumberDetector PUBLIC /home/debian/tensorflow/tensorflow/lite)
target_include_directories(NumberDetector PUBLIC /home/debian/flatbuffers/include)
set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -std=c++17 -lstdc++")
# Copy resouce
file(COPY ${CMAKE_SOURCE_DIR}/resource/ DESTINATION ${PROJECT_BINARY_DIR}/resource/)
add_definitions(-DRESOURCE_DIR="${PROJECT_BINARY_DIR}/resource/")
TensorFlow for C++を呼び出すソースコード (VAB-5000上での操作)
TensorFlowを呼び出すソースコードは、以下の記事よりお借りしました。実行には、入力となる画像ファイル(ここでは 4.jpg
) と tflite形式のAIモデル が必要になります。本ソースコードは、白地に黒で書かれた数字の画像が、0~9のうちいずれであるかを推論するプログラムです。実行には 画像ファイルとtflite形式のファイルが必要になります。
入力画像 4.jpg
とtflite形式のモデル conv_mnist.tflite
は以下に格納してあります。
#include <stdio.h>
#include <opencv2/opencv.hpp>
#include "tensorflow/lite/interpreter.h"
#include "tensorflow/lite/kernels/register.h"
#include "tensorflow/lite/model.h"
#include "tensorflow/lite/optional_debug_tools.h"
#define MODEL_FILENAME RESOURCE_DIR"conv_mnist.tflite"
#define TFLITE_MINIMAL_CHECK(x) \
if (!(x)) { \
fprintf(stderr, "Error at %s:%d\n", __FILE__, __LINE__); \
exit(1); \
}
int main()
{
/* 入力となる画像データを読み込む "4.jpg" */
printf("input image path : %s\n", RESOURCE_DIR"4.jpg");
cv::Mat image = cv::imread(RESOURCE_DIR"4.jpg");
/* ディスプレイに出力する */
cv::imshow("InputImage", image);
/* 画面表示が閉じられるまで待機 */
cv::waitKey(0);
/* 入力画像をgrayscaleへと変換する */
cv::cvtColor(image, image, cv::COLOR_BGR2GRAY);
/* 28px x 28pxにリサイズする */
cv::resize(image, image, cv::Size(28, 28));
/* 背景が黒、文字色が白にする */
image = ~image;
/* ディスプレイに出力する */
cv::imshow("InputImage for CNN", image);
/* Normalize: 0.0 ~ 1.0 する */
image.convertTo(image, CV_32FC1, 1.0 / 255);
/* 画面表示が閉じられるまで待機 */
cv::waitKey(0);
/* tfliteモデルのパス */
printf("model file name : %s\n", MODEL_FILENAME);
/* tfliteのモデルをFlatBufferに読み込む */
std::unique_ptr<tflite::FlatBufferModel> model = tflite::FlatBufferModel::BuildFromFile(MODEL_FILENAME);
/* 開けたかチェック */
TFLITE_MINIMAL_CHECK(model != nullptr);
/* インタープリタを生成する */
tflite::ops::builtin::BuiltinOpResolver resolver;
tflite::InterpreterBuilder builder(*model, resolver);
std::unique_ptr<tflite::Interpreter> interpreter;
builder(&interpreter);
/* 生成できたかチェック */
TFLITE_MINIMAL_CHECK(interpreter != nullptr);
/* 入出力のバッファを確保する */
TFLITE_MINIMAL_CHECK(interpreter->AllocateTensors() == kTfLiteOk);
printf("=== Pre-invoke Interpreter State ===\n");
tflite::PrintInterpreterState(interpreter.get());
/* 入力テンソルに読み込んだ画像を格納する */
float* input = interpreter->typed_input_tensor<float>(0);
memcpy(input, image.reshape(0, 1).data, sizeof(float) * 1 * 28 * 28 * 1);
/* 推論を実行 */
TFLITE_MINIMAL_CHECK(interpreter->Invoke() == kTfLiteOk);
printf("\n\n=== Post-invoke Interpreter State ===\n");
tflite::PrintInterpreterState(interpreter.get());
/* 出力テンソルから結果を取得して表示 */
float* probs = interpreter->typed_output_tensor<float>(0);
for (int i = 0; i < 10; i++) {
printf("prob of %d: %.3f\n", i, probs[i]);
}
/* 終了 */
return 0;
}
ビルドと実行結果 (VAB-5000上での操作)
以上を格納したディレクトリを作成し、これに対して cmake
と make
を使ってビルドを実施すると NumberDetector
というバイナリが生成 されます。これを実行することにより、画像に書かれた文字を認識することができます。
実行結果(成功例)
### 作業用ディレクトリ(参考)
$ pwd
# /home/debian/sandbox/tensorflow_c/first
### ソースコードとCMakeLists.txtを準備する
$ ls
# CMakeLists.txt main.cpp resource
### resourceに画像とtfliteモデルを格納する
$ ls resource/
# 4.jpg conv_mnist.tflite
### ビルドする
$ mkdir build
$ cd build
$ cmake ..
$ make
# [ 50%] Building CXX object CMakeFiles/NumberDetector.dir/main.cpp.o
# [100%] Linking CXX executable NumberDetector
# [100%] Built target NumberDetector
### 実行する
$ ./NumberDetector
# input image path : /home/debian/sandbox/first/tensorflow_c/build/resource/4.jpg
# model file name : /home/debian/sandbox/tensorflow_c/first/build/resource/conv_mnist.tflite
# ...
# prob of 0: 0.000
# prob of 1: 0.000
# prob of 2: 0.000
# prob of 3: 0.000
# prob of 4: 1.000
# prob of 5: 0.000
# prob of 6: 0.000
# prob of 7: 0.000
# prob of 8: 0.000
# prob of 9: 0.000
以上が VIA VAB-5000 向け (aarch64(64bit-Arm)向け) に
TensorFlow Lite for C/C++をx86_64上でビルドして
デバイス上から利用する手順です。やや複雑に感じられますが、
一度設定を行えば、以降は利用できますので、是非お試しください。
お疲れさまでした!