daisukeの技術ブログ

AI、機械学習、最適化、Pythonなどについて、技術調査、技術書の理解した内容、ソフトウェア/ツール作成について書いていきます

TensorFlow Lite C++で量子化モデルをRaspberry Pi 4で動かす

今回もAIの量子化について学んでいきます。論文を読むことは継続しつつ、今回は、実際に量子化モデルを動かして、推論の高速化を実感したいと思います。

前回(TensorFlow Lite Pythonで量子化モデルをRaspberry Pi 4で動かす - daisukeの技術ブログ)は、Python の tflite-runtime をインストールして実行しましたが、今回は、C++でやっていきます。手順通りにやれば、必ず同じことが出来るように、環境構築、ビルド、実行の手順を書いていきます。

それでは、やっていきます!

はじめに

「AIモデルの量子化」の記事一覧です。良かったら参考にしてください。

AIモデルの量子化の記事一覧

エンジニアグループのランキングに参加中です。

気楽にポチッとよろしくお願いいたします🙇

それではやっていきます!

Raspberry Pi 4 で TensorFlow Lite の環境構築

TensorFlow Lite の公式サイトで、CMakeでビルドする手順のページです。

www.tensorflow.org

公式サイトでは、Raspberry Pi 4 用のビルドは、クロスコンパイルの手順しかありませんでしたが、ここではセルフコンパイル(Raspberry Pi 4 でビルド)でやっていきます。

まずは、Git と CMake をインストールします。

$ sudo apt-get install git cmake

次に、TensorFlow のリポジトリをクローンします。公式サイトの手順では master をクローンするようになってますが、それは良くないと思うので、最新のリリースの v2.16.1 のタグをクローンします。

なぜか、clone に -b タグ名 で失敗したので、クローン後にスイッチしました。

$ git clone -b v2.16.1 https://github.com/tensorflow/tensorflow.git
$ cd tensorflow/
$ git switch -c v2.16.1
$ git status
On branch v2.16.1
nothing to commit, working tree clean

ビルド手順がほとんど書かれてないので、普通にビルドしてみます。CMakeでエラー出たので、build-essential を入れておきます。

$ mkdir build
$ cd build/
$ cmake ../tensorflow/lite
エラー
$ sudo apt install build-essential
$ cmake ../tensorflow/lite
$ make -j$(nproc)

約1時間でビルドが完了して、buildディレクトリに「libtensorflow-lite.a」が作られました。

「label_image」の C++版をやっていきます。label_imageはビルドされてなかったので、公式手順通りに、ビルドします。

$ cmake --build . -j -t label_image

すぐに完了しました。build/examples/label_image/label_image が作られました。

make した後に label_image をビルドしましたが、label_imageのビルド時に libtensorflow-lite.a も作られるので、make は不要だったと思います。

これで準備完了です。

Raspberry Pi 4 で TensorFlow Lite の実行

それでは実行していきます。学習済みモデル、入力する画像ファイル、ラベルテキストは、前回使ったものを使います。

daisuke20240310.hatenablog.com

Python版とはコマンドライン引数が異なるので注意です。--help でコマンドライン引数が見れます。

$ examples/label_image/label_image --help
INFO:
usage: label_image <flags>
Flags:
        --num_threads=-1                        int32   optional        number of threads used for inference on CPU.
        --max_delegated_partitions=0            int32   optional        Max number of partitions to be delegated.
        --min_nodes_per_partition=0             int32   optional        The minimal number of TFLite graph nodes of a partition that has to be reached for it to be delegated.A negative value or 0 means to use the default choice of each delegate.
        --first_delegate_node_index=0           int32   optional        The index of the first node that could be delegated. Used only when TFLITE_DEBUG_DELEGATE is defined. Default is 0.
        --last_delegate_node_index=2147483647   int32   optional        The index of the last node that could be delegated. Used only when TFLITE_DEBUG_DELEGATE is defined. Default is INT_MAX.
        --delegate_serialize_dir=               string  optional        Directory to be used by delegates for serializing any model data. This allows the delegate to save data into this directory to reduce init time after the first run. Currently supported by NNAPI delegate with specific backends on Android. Note that delegate_serialize_token is also required to enable this feature.
        --delegate_serialize_token=             string  optional        Model-specific token acting as a namespace for delegate serialization. Unique tokens ensure that the delegate doesn't read inapplicable/invalid data. Note that delegate_serialize_dir is also required to enable this feature.
        --use_xnnpack=false                     bool    optional        explicitly apply the XNNPACK delegate. Note the XNNPACK delegate could be implicitly applied by the TF Lite runtime regardless the value of this parameter. To disable this implicit application, set the value to false explicitly.
        --xnnpack_force_fp16=false              bool    optional        enforce float16 inference.
        --accelerated, -a: [0|1] use Android NNAPI or not
        --allow_fp16, -f: [0|1], allow running fp32 models with fp16 or not
        --count, -c: loop interpreter->Invoke() for certain times
        --gl_backend, -g: [0|1]: use GL GPU Delegate on Android
        --hexagon_delegate, -j: [0|1]: use Hexagon Delegate on Android
        --input_mean, -b: input mean
        --input_std, -s: input standard deviation
        --image, -i: image_name.bmp
        --labels, -l: labels for the model
        --tflite_model, -m: model_name.tflite
        --profiling, -p: [0|1], profiling or not
        --num_results, -r: number of results to show
        --threads, -t: number of threads
        --verbose, -v: [0|1] print more information
        --warmup_runs, -w: number of warmup runs
        --xnnpack_delegate, -x [0:1]: xnnpack delegate
        --help, -h: Print this help message

スレッド数を指定するオプションがありますね。これで速く実行できそうです。まずは、量子化してないモデルで、デフォルトで実行します。

$ examples/label_image/label_image -m ../../tflite/models/mobilenet_v1_1.0_224/mobilenet_v1_1.0_224.tflite -l ../../tflite/labels.txt -i ../../tf
lite/grace_hopper.bmp
INFO: Loaded model ../../tflite/models/mobilenet_v1_1.0_224/mobilenet_v1_1.0_224.tflite
INFO: resolved reporter
INFO: Created TensorFlow Lite XNNPACK delegate for CPU.
INFO: invoked
INFO: average time: 73.992 ms
INFO: 0.860174: 653 653:military uniform
INFO: 0.0481022: 907 907:Windsor tie
INFO: 0.00786703: 466 466:bulletproof vest
INFO: 0.00644932: 514 514:cornet, horn, trumpet, trump
INFO: 0.00608026: 543 543:drumstick

速くなりました。Python の tflite-runtime で実行すると「144.901ms」でしたが、C++では「73.992ms」です。約2倍速くなりました。「--threds」に 4 を指定してみたり、「--num_threads」に 4 を指定してみましたが、変わりませんでした。デフォルトで4スレッドが指定されているのかもしれません。

精度は、Python の tflite-runtime では「92.0%」でしたが、C++では「86.0%」です。「--input_mean」と「--input_std」を指定してないからと思って、それぞれ「127.5(label_image.pyのデフォルトで指定される値)」を指定してみましたが、結果は変わりませんでした。

続いて、量子化モデルで実行してみます。

$ examples/label_image/label_image -m ../../tflite/models/mobilenet_v1_1.0_224_quant/mobilenet_v1_1.0_224_quant.tflite -l ../../tflite/labels.txt -i ../../tflite/grace_hopper.bmp -b 127.5 -s 127.5
INFO: Loaded model ../../tflite/models/mobilenet_v1_1.0_224_quant/mobilenet_v1_1.0_224_quant.tflite
INFO: resolved reporter
INFO: Created TensorFlow Lite XNNPACK delegate for CPU.
INFO: invoked
INFO: average time: 30.148 ms
INFO: 0.768627: 653 653:military uniform
INFO: 0.105882: 907 907:Windsor tie
INFO: 0.0196078: 458 458:bow tie, bow-tie, bowtie
INFO: 0.0117647: 466 466:bulletproof vest
INFO: 0.00784314: 835 835:suit, suit of clothes

こちらも速くなりました。Python の tflite-runtime で実行すると「99.783ms」でしたが、C++では「30.148ms」です。3倍以上速くなりました。

精度は、Python の tflite-runtime では「87.5%」でしたが、C++では「76.9%」です。こちらも、Python に比べて悪化しました。

おわりに

今回は、TensorFlow Lite の C++版で実行してみました。だいぶ速くなりましたが、精度が落ちてるところが気になりますね。

このあたりの分析をするために、次回(Raspberry Pi 4のTensorFlow Lite C++をVSCodeでリモートデバッグする - daisukeの技術ブログ)はデバッグ環境を立ち上げてみたいと思います。

今回は以上です。

最後までお読みいただき、ありがとうございました。