Raspberry Pi ZeroでTensorFlow Liteランタイムを実行しようとしたらハマった話

## はじめに

タイトルのとおり、ハマってしまったので記事に残すことにしました。

## ハマったこと

Raspberry Pi Zero(armv6l)用のPython Wheelパッケージが提供されていないので、[公式ドキュメント](https://www.tensorflow.org/lite/guide/build_cmake_pip?hl=ja)の通りにやってみたら、ビルドに失敗してしまう…

## 試してみたこと-1

[こちらの記事](https://qiita.com/moritalous/items/bcc49610843968674731)を参考にして、Dockerコンテナ上で試してみても、ビルドに失敗してしまう(汗)
失敗したときの実行環境・実行したコマンドは、以下の通りです。

### 実行環境

- OS: Ubuntu 20.04.2 LTS (ConoHa VPS)
- CPU: 2Core
- Mem: 1GB
- SSD: 100GB

### 実行したコマンド

```shell-session
# Dockerのインストール(環境:ローカル)
# apt update && apt upgrade
# apt install \
    apt-transport-https \
    ca-certificates \
    curl \
    gnupg \
    lsb-release
# curl -fsSL https://download.docker.com/linux/ubuntu/gpg | sudo gpg --dearmor -o /usr/share/keyrings/docker-archive-keyring.gpg
# echo \
    "deb [arch=amd64 signed-by=/usr/share/keyrings/docker-archive-keyring.gpg] https://download.docker.com/linux/ubuntu \
    $(lsb_release -cs) stable" | sudo tee /etc/apt/sources.list.d/docker.list > /dev/null
# apt update
# apt install docker-ce docker-ce-cli containerd.io

# cmakeのインストール(環境:ローカル)
# apt install cmake

# TensorFlowリポジトリのクローンを作成
# cd ~
# git clone https://github.com/tensorflow/tensorflow.git tensorflow_src

# Dockerコンテナの作成・起動
# docker pull tensorflow/tensorflow:devel
# docker run -it -v /var/run/docker.sock:/var/run/docker.sock \
    -v $PWD/tensorflow_src:/root/tensorflow_src -w /root/tensorflow_src \
    tensorflow/tensorflow:devel bash

# Dockerのインストール(環境:Dockerコンテナ)
# apt update && apt upgrade
# apt install \
    apt-transport-https \
    ca-certificates \
    curl \
    gnupg \
    lsb-release
# curl -fsSL https://download.docker.com/linux/ubuntu/gpg | sudo gpg --dearmor -o /usr/share/keyrings/docker-archive-keyring.gpg
# echo \
    "deb [arch=amd64 signed-by=/usr/share/keyrings/docker-archive-keyring.gpg] https://download.docker.com/linux/ubuntu \
    $(lsb_release -cs) stable" | sudo tee /etc/apt/sources.list.d/docker.list > /dev/null
# apt update
# apt install docker-ce docker-ce-cli containerd.io

# cmakeのインストール(環境:Dockerコンテナ)
# apt install cmake

# Wheelのビルド(環境:Dockerコンテナ)
# tensorflow/tools/ci_build/ci_build.sh PI-PYTHON37 \
    tensorflow/lite/tools/pip_package/build_pip_package_with_cmake.sh rpi0
```

ちなみに、ビルドに失敗すると、このようなメッセージが表示されます。

```shell-session
/workspace/tensorflow/lite/tools/cmake/toolchains/gcc-arm-8.3-2019.03-x86_64-arm-linux-gnueabihf/arm-linux-gnueabihf/include/c++/8.3.0/bits/deque.tcc:585:7: note: parameter passing for argument of type '__gnu_cxx::__normal_iterator<const double*, std::vector<double> >' changed in GCC 7.1
/workspace/tensorflow/lite/tools/cmake/toolchains/gcc-arm-8.3-2019.03-x86_64-arm-linux-gnueabihf/arm-linux-gnueabihf/include/c++/8.3.0/bits/deque.tcc:585:7: note: parameter passing for argument of type '__gnu_cxx::__normal_iterator<const double*, std::vector<double> >' changed in GCC 7.1
/workspace/tensorflow/lite/tools/cmake/toolchains/gcc-arm-8.3-2019.03-x86_64-arm-linux-gnueabihf/arm-linux-gnueabihf/include/c++/8.3.0/bits/deque.tcc:625:11: note: parameter passing for argument of type '__gnu_cxx::__normal_iterator<const double*, std::vector<double> >' changed in GCC 7.1
           _M_insert_aux(__pos, __first, __last, __n);
           ^~~~~~~~~~~~~
In file included from /workspace/tensorflow/lite/tools/cmake/toolchains/gcc-arm-8.3-2019.03-x86_64-arm-linux-gnueabihf/arm-linux-gnueabihf/include/c++/8.3.0/deque:64,
                 from /workspace/tensorflow/lite/kernels/internal/spectrogram.h:35,
                 from /workspace/tensorflow/lite/kernels/internal/spectrogram.cc:16:
/workspace/tensorflow/lite/tools/cmake/toolchains/gcc-arm-8.3-2019.03-x86_64-arm-linux-gnueabihf/arm-linux-gnueabihf/include/c++/8.3.0/bits/stl_deque.h: In member function 'bool tflite::internal::Spectrogram::GetNextWindowOfSamples(const std::vector<InputSample>&, int*) [with InputSample = double]':
/workspace/tensorflow/lite/tools/cmake/toolchains/gcc-arm-8.3-2019.03-x86_64-arm-linux-gnueabihf/arm-linux-gnueabihf/include/c++/8.3.0/bits/stl_deque.h:2021:4: note: parameter passing for argument of type '__gnu_cxx::__normal_iterator<const double*, std::vector<double> >' changed in GCC 7.1
    _M_range_insert_aux(__pos, __first, __last,
    ^~~~~~~~~~~~~~~~~~~
/workspace/tensorflow/lite/tools/cmake/toolchains/gcc-arm-8.3-2019.03-x86_64-arm-linux-gnueabihf/arm-linux-gnueabihf/include/c++/8.3.0/bits/stl_deque.h:2021:4: note: parameter passing for argument of type '__gnu_cxx::__normal_iterator<const double*, std::vector<double> >' changed in GCC 7.1
    _M_range_insert_aux(__pos, __first, __last,
    ^~~~~~~~~~~~~~~~~~~
make[3]: Leaving directory '/workspace/tensorflow/lite/tools/pip_package/gen/tflite_pip/python3.7/cmake_build'
make[2]: *** [CMakeFiles/tensorflow-lite.dir/all] Error 2
CMakeFiles/Makefile2:1145: recipe for target 'CMakeFiles/tensorflow-lite.dir/all' failed
make[2]: Leaving directory '/workspace/tensorflow/lite/tools/pip_package/gen/tflite_pip/python3.7/cmake_build'
CMakeFiles/Makefile2:1253: recipe for target 'CMakeFiles/_pywrap_tensorflow_interpreter_wrapper.dir/rule' failed
make[1]: *** [CMakeFiles/_pywrap_tensorflow_interpreter_wrapper.dir/rule] Error 2
make[1]: Leaving directory '/workspace/tensorflow/lite/tools/pip_package/gen/tflite_pip/python3.7/cmake_build'
make: *** [_pywrap_tensorflow_interpreter_wrapper] Error 2
Makefile:215: recipe for target '_pywrap_tensorflow_interpreter_wrapper' failed
root@06a2eea49469:~/tensorflow_src#
```

## 試してみたこと-2

結論から言うと、こちらの方法でWheelのビルドに成功しました。

コンテナではなくRaspberry Pi Zero上で普通にビルドした方が良いのでは？、と思い```build_pip_package_with_cmake.sh```の中身を解読して、一行ずつ端末から実行してみました。

成功したときの実行環境・実行したコマンドは、以下の通りです。

### 実行環境

- Raspberry Pi Zero WH
- OS: Raspberry Pi OS Lite (Release data: May 7th 2021)
- SDカード: 64GB

### 実行したコマンド

```shell-session
$ sudo apt update && sudo apt upgrade -y
$ sudo apt install cmake
$ cd ~
$ git clone https://github.com/tensorflow/tensorflow.git tensorflow_src
$ mkdir tflite_build
$ cd tflite_build
$ sudo cmake ../tensorflow_src/tensorflow/lite \
    -DCMAKE_C_FLAGS="-I/usr/include/python3.7m -I/home/pi/.local/lib/python3.7/site-packages/pybind11/include" \
    -DCMAKE_CXX_FLAGS="-I/usr/include/python3.7m -I/home/pi/.local/lib/python3.7/site-packages/pybind11/include" \
    -DCMAKE_SHARED_LINKER_FLAGS='-latomic'
    -DCMAKE_SYSTEM_NAME=Linux \
    -DCMAKE_SYSTEM_PROCESSOR=armv6 \
    -DTFLITE_ENABLE_XNNPACK=OFF
$ sudo cmake --build . --verbose -j 1 -t _pywrap_tensorflow_interpreter_wrapper
$ cd ~/tensorflow_src/tensorflow/lite/tools/pip_package/
$ sudo mkdir gen
$ sudo mkdir gen/tflite_pip
$ sudo mkdir gen/tflite_pip/python3.7
$ sudo mkdir gen/tflite_pip/python3.7/tflite_runtime
$ cd gen/tflite_pip/python3.7
$ sudo cp -r ~/tensorflow_src/tensorflow/lite/tools/pip_package/debian/ ./
$ sudo cp -r ~/tensorflow_src/tensorflow/lite/tools/pip_package/MANIFEST.in ./
$ sudo cp -r ~/tensorflow_src/tensorflow/lite/python/interpreter_wrapper/ ./
$ sudo cp ~/tensorflow_src/tensorflow/lite/tools/pip_package/setup_with_binary.py ./setup.py
$ sudo cp ~/tensorflow_src/tensorflow/lite/python/interpreter.py ./tflite_runtime/
$ sudo cp ~/tensorflow_src/tensorflow/lite/python/metrics_interface.py ./tflite_runtime/
$ sudo cp ~/tensorflow_src/tensorflow/lite/python/metrics_portable.py ./tflite_runtime/
$ sudo cp ~/tflite_build/_pywrap_tensorflow_interpreter_wrapper.so ./tflite_runtime/
$ sudo chmod u+w ./tflite_runtime/_pywrap_tensorflow_interpreter_wrapper.so
$ sudo touch ./tflite_runtime/__init__.py
$ sudo echo "__version__ = '2.7.0'" >> ./tflite_runtime/__init__.py
$ sudo echo "__git_version__ = '$(git -C ~/tensorflow_src describe)'" >> ./tflite_runtime/__init__.py
$ sudo python3 setup.py bdist --plat-name=linux_armv6l bdist_wheel --plat-name=linux-armv6l
```

ビルドに成功すると、```tensorflow_src/tensorflow/lite/tools/pip_package/gen/tflite_pip/python3.7/dist```に
Wheelが出力されます。ちなみに、ビルド完了するまで、4時間くらいかかりました。

```shell-session
pi@raspberrypi:~/tensorflow_src/tensorflow/lite/tools/pip_package/gen/tflite_pip/python3.7/dist $ ls
tflite_runtime-2.7.0-cp37-cp37m-linux_armv6l.whl
tflite-runtime-2.7.0.linux_armv6l.tar.gz
```

## 動作確認

動作確認のため、学習済みモデル(```test.tflite```)を読み込み、```get_input_details()```と```get_output_details()```を実行してみました。結果、エラーは発生せず、入力層と出力層の情報を表示することができました。

```shell-session
pi@raspberrypi:~ $ python3
Python 3.7.3 (default, Jan 22 2021, 20:04:44)
[GCC 8.3.0] on linux
Type "help", "copyright", "credits" or "license" for more information.
>>> import tflite_runtime.interpreter as tflite
>>> interpreter = tflite.Interpreter(model_path='./test.tflite')
>>> interpreter.get_input_details()
[{'name': 'serving_default_flatten_input:0', 'index': 0, 'shape': array([ 1, 28, 28]), 'shape_signature': array([-1, 28, 28]), 'dtype': <class 'numpy.float32'>, 'quantization': (0.0, 0), 'quantization_parameters': {'scales': array([], dtype=float32), 'zero_points': array([], dtype=int32), 'quantized_dimension': 0}, 'sparsity_parameters': {}}]
>>> interpreter.get_output_details()
[{'name': 'StatefulPartitionedCall:0', 'index': 9, 'shape': array([ 1, 10]), 'shape_signature': array([-1, 10]), 'dtype': <class 'numpy.float32'>, 'quantization': (0.0, 0), 'quantization_parameters': {'scales': array([], dtype=float32), 'zero_points': array([], dtype=int32), 'quantized_dimension': 0}, 'sparsity_parameters': {}}]
>>>
```

## おわりに

あまり需要のない内容かと思いますが、誰かの参考になれば幸いです。