2020年11月21日土曜日

Fedora 33でTensorflow 2.4-rc2(CUDA11.1 cuDNN8.0.5)をビルドする

目的


Tensorflowの2.4-rc2をFedora 33でソースビルドする。
2.4の正式版リリースに向けての準備と備忘録。


環境


  • Fedora 33 x86_64
  • python 3.9.0(virtualenv)
  • GCC 10.2.1
  • CUDA 11.1 + cuDNN 8.0.5
  • CPU AMD Ryzen 7 1700
  • GPU GeForce GTX 1070


事前の準備


GCC9のビルド


前回の記事
と同様、CUDA 11.1がサポートするGCCは9で、Fedora 33のGCC10ではビルドができない。このため、まずはGCC9のビルドを行う。
GCCのビルドについては、以前の記事を参照。


GCC9.3のソースダウンロード&ビルド


ソースをダウンロードし、ビルドする。

$ wget https://ftp.gnu.org/gnu/gcc/gcc-9.3.0/gcc-9.3.0.tar.gz
$ cd gcc-9.3.0/
$ ./contrib/download_prerequisites 
$ mkdir build
$ cd build/
$ ../configure \
    --enable-bootstrap \
    --enable-languages=c,c++ \
    --prefix=/home/xxxx/gcc/9.3 
    --enable-shared \
    --enable-threads=posix \
    --enable-checking=release \
    --disable-multilib \
    --with-system-zlib \
    --enable-__cxa_atexit \
    --disable-libunwind-exceptions \
    --enable-gnu-unique-object \
    --enable-linker-build-id \
    --with-gcc-major-version-only \
    --with-linker-hash-style=gnu \
    --enable-plugin \
    --enable-initfini-array \
    --with-isl \
    --enable-libmpx \
    --enable-gnu-indirect-function \
    --build=x86_64-redhat-linux
$ make -j16
$ make install


specsファイルの作成


コンパイルしたGCC9でビルドした際に、適切な動的リンクライブラリ(libstdc++.so)がリンクされるようにSPECEファイルを修正する。

$ /home/xxxx/gcc/9.3/bin/gcc -dumpspecs > specs
$ vi specs

# before
*link_libgcc:
%D

# after
*link_libgcc:
%{!static:%{!static-libgcc:-rpath /home/xxxx/gcc/9.3/lib64/}} %D

$ mv specs /home/xxxx/gcc/9.3/lib/gcc/x86_64-redhat-linux/9/


Environment Modulesの設定


GCC8をEnvironment Modulesで切り替えられるようにする。/etc/modulefiles 配下に、gcc9xのファイルを作成する。

#%Module 1.0
#
#  gcc-9.X module for use with 'environment-modules' package:
#

conflict        gcc5x gcc7x gcc9x
prepend-path    PATH                    /home/xxxx/gcc/9.3/bin/


Bazelのビルド


Bazel をソースビルドする。最新の3.7をソースビルドした。
公式のソースビルド方法はここを参照。手順どおりであり詳細の説明は割愛。


CUDA、cuDNNのインストール


CUDA: 11.1、cuDNN: 8.0.5をインストール。CUDAはRPM Fusion Howto/ CUDA を参考にインストールを行う。Which driver Packageにもあるとおり、RPM FusionとCUDAのリポジトリの両方にnvidia driverが存在するが、バージョンの不一致を起こしてしまうことがある。手順どおりにインストールしないとCUDAとnvidia driverの不一致で使えない。。。
cuDNNはNVIDIAのダウンロードサイトからダウンロード、インストールを行う。


Tensorflowのビルド


さて、本題。TensorFlow 2.4-rc1をビルドする。


virtualenvの設定


まずはvirtualenv(virtualenvwapper)でTensorflow用の仮想Python環境を作成し、必要なモジュールをインストールする。

$ mkvirtualenv -p python3  tf2.4-rc2
$ pip install pip six numpy wheel setuptools mock 'future>=0.17.1'
$ pip install keras_applications --no-deps
$ pip install keras_preprocessing --no-deps

ビルド


Githubからソースを取得し、configureスクリプト実行し、ビルドを行う。
  • CUDAのサポートを有効とする。
  • Host compilerにGCC9のgccのパスを指定してあげる。
  • ビルドオプションには"--config=v2"と"--config=nonccl "を指定。


(tf2.4-rc1) $ wget https://github.com/tensorflow/tensorflow/archive/v2.4.0-rc1.tar.gz
(tf2.4-rc1) $ tar xf v2.4.0-rc1.tar.gz 
(tf2.4-rc1) $ cd tensorflow-2.4.0-rc1/
(tf2.4-rc1) $ ./configure 
(tf2.4-rc1) $ bazel build \
                --config=opt \
                --config=v2 \
                --cxxopt="-D_GLIBCXX_USE_CXX11_ABI=0" \
                --config=cuda \
                --config=nonccl \
                --verbose_failures \
                //tensorflow/tools/pip_package:build_pip_package
(tf2.4-rc1) $ ./bazel-bin/tensorflow/tools/pip_package/build_pip_package /tmp/tensorflow_pkg
(tf2.4-rc1) $ pip install /tmp/tensorflow_pkg/tensorflow-2.4.0rc2-cp39-cp39m-linux_x86_64.whl


インストール確認


  • tf.__version__が2.4-rc1であること。
  • GPUデバイスを認識していること。

(tf2.4-rc1) $ python
    Python 3.9.0 (default, Oct  6 2020, 00:00:00) 
[GCC 10.2.1 20200826 (Red Hat 10.2.1-3)] on linux
Type "help", "copyright", "credits" or "license" for more information.
>>> import tensorflow as tf
2020-11-21 09:17:05.361081: I tensorflow/stream_executor/platform/default/dso_loader.cc:49] Successfully opened dynamic library libcudart.so.11.0
>>> from tensorflow.python.client import device_lib
>>> device_lib.list_local_devices()
2020-11-21 09:17:17.759422: I tensorflow/stream_executor/platform/default/dso_loader.cc:49] Successfully opened dynamic library libcuda.so.1
2020-11-21 09:17:17.800045: I tensorflow/stream_executor/cuda/cuda_gpu_executor.cc:941] successful NUMA node read from SysFS had negative value (-1), but there must be at least one NUMA node, so returning NUMA node zero
2020-11-21 09:17:17.803501: I tensorflow/core/common_runtime/gpu/gpu_device.cc:1720] Found device 0 with properties: 
pciBusID: 0000:09:00.0 name: GeForce GTX 1070 computeCapability: 6.1
coreClock: 1.7085GHz coreCount: 15 deviceMemorySize: 7.93GiB deviceMemoryBandwidth: 238.66GiB/s
2020-11-21 09:17:17.803527: I tensorflow/stream_executor/platform/default/dso_loader.cc:49] Successfully opened dynamic library libcudart.so.11.0
2020-11-21 09:17:17.805845: I tensorflow/stream_executor/platform/default/dso_loader.cc:49] Successfully opened dynamic library libcublas.so.11
2020-11-21 09:17:17.805916: I tensorflow/stream_executor/platform/default/dso_loader.cc:49] Successfully opened dynamic library libcublasLt.so.11
2020-11-21 09:17:17.806788: I tensorflow/stream_executor/platform/default/dso_loader.cc:49] Successfully opened dynamic library libcufft.so.10
2020-11-21 09:17:17.816671: I tensorflow/stream_executor/platform/default/dso_loader.cc:49] Successfully opened dynamic library libcurand.so.10
2020-11-21 09:17:17.854541: I tensorflow/stream_executor/platform/default/dso_loader.cc:49] Successfully opened dynamic library libcusolver.so.11
2020-11-21 09:17:17.862372: I tensorflow/stream_executor/platform/default/dso_loader.cc:49] Successfully opened dynamic library libcusparse.so.11
2020-11-21 09:17:17.961227: I tensorflow/stream_executor/platform/default/dso_loader.cc:49] Successfully opened dynamic library libcudnn.so.7
2020-11-21 09:17:17.961410: I tensorflow/stream_executor/cuda/cuda_gpu_executor.cc:941] successful NUMA node read from SysFS had negative value (-1), but there must be at least one NUMA node, so returning NUMA node zero
2020-11-21 09:17:17.962245: I tensorflow/stream_executor/cuda/cuda_gpu_executor.cc:941] successful NUMA node read from SysFS had negative value (-1), but there must be at least one NUMA node, so returning NUMA node zero
2020-11-21 09:17:17.962836: I tensorflow/core/common_runtime/gpu/gpu_device.cc:1862] Adding visible gpu devices: 0
2020-11-21 09:17:17.963571: I tensorflow/stream_executor/platform/default/dso_loader.cc:49] Successfully opened dynamic library libcudart.so.11.0
2020-11-21 09:17:18.745397: I tensorflow/core/common_runtime/gpu/gpu_device.cc:1261] Device interconnect StreamExecutor with strength 1 edge matrix:
2020-11-21 09:17:18.745448: I tensorflow/core/common_runtime/gpu/gpu_device.cc:1267]      0 
2020-11-21 09:17:18.745463: I tensorflow/core/common_runtime/gpu/gpu_device.cc:1280] 0:   N 
2020-11-21 09:17:18.747106: I tensorflow/stream_executor/cuda/cuda_gpu_executor.cc:941] successful NUMA node read from SysFS had negative value (-1), but there must be at least one NUMA node, so returning NUMA node zero
2020-11-21 09:17:18.747776: I tensorflow/stream_executor/cuda/cuda_gpu_executor.cc:941] successful NUMA node read from SysFS had negative value (-1), but there must be at least one NUMA node, so returning NUMA node zero
2020-11-21 09:17:18.748358: I tensorflow/stream_executor/cuda/cuda_gpu_executor.cc:941] successful NUMA node read from SysFS had negative value (-1), but there must be at least one NUMA node, so returning NUMA node zero
2020-11-21 09:17:18.748909: I tensorflow/core/common_runtime/gpu/gpu_device.cc:1406] Created TensorFlow device (/device:GPU:0 with 7120 MB memory) -> physical GPU (device: 0, name: GeForce GTX 1070, pci bus id: 0000:09:00.0, compute capability: 6.1)
2020-11-21 09:17:18.751666: I tensorflow/compiler/jit/xla_gpu_device.cc:99] Not creating XLA devices, tf_xla_enable_xla_devices not set
[name: "/device:CPU:0"
device_type: "CPU"
memory_limit: 268435456
locality {
}
incarnation: 12838272530603917041
, name: "/device:GPU:0"
device_type: "GPU"
memory_limit: 7466015808
locality {
  bus_id: 1
  links {
  }
}
incarnation: 3621828384593309124
physical_device_desc: "device: 0, name: GeForce GTX 1070, pci bus id: 0000:09:00.0, compute capability: 6.1"
]

OK!

2020年11月3日火曜日

YoctoでTensorFlow Lite (Build pip package)

目的


YoctoでTensorFlow Liteのpip packageをビルド、インストールするレシピを作成する。
bitbakeしたイメージをラズパイ4で動かしてみる。


動機


TensorFlowのレシピはあったりするが(以前ブログにした)、TensorFlow LiteのPython interpreterのレシピがなかったこと、あとからpipでインストールするのが結構めんどくさいので、レシピを作成することにした。


meta-tensorflow-lite


meta-tensorflow-liteとしてGitHubにレシピを公開している。v2.3.1に対応。



リファレンス


TensorFlow Liteのビルドは以下が参考になる。


ビルド


リポジトリのREADMEにもある通り。
  • 確認はラズパイ4の32 / 64bitで実施。
  • イメージはcore-image-weston(他も動くはず)。
  • dunfell, zeusに対応。
    zeusは人生初めてPRをもらった!(Support also zeus version #1

必要なリポジトリをClone。
$ git clone git://git.yoctoproject.org/poky.git
$ git clone git://git.yoctoproject.org/meta-raspberrypi
$ git clone git://git.openembedded.org/meta-openembedded
$ git clone https://github.com/NobuoTsukamoto/meta-tensorflow-lite.git
$ source poky/oe-init-build-env rpi-build

レイヤーの追加
$ bitbake-layers add-layer ../meta-openembedded/meta-oe/
$ bitbake-layers add-layer ../meta-openembedded/meta-python/
$ bitbake-layers add-layer ../meta-openembedded/meta-networking/
$ bitbake-layers add-layer ../meta-openembedded/meta-multimedia/
$ bitbake-layers add-layer ../meta-raspberrypi/
$ bitbake-layers add-layer ../meta-tensorflow-lite/

conf/local.confにpython3-tensorflow-liteを追加。あと、opencvのpythonやgitも追加すると楽。
MACHINE ?= "raspberrypi4-64"
IMAGE_INSTALL_append = " python3-tensorflow-lite"

あとはBitbakeしてSDカードに書き込み。
$ bitbake core-image-weston

このリポジトリでTensorFlow Liteモデル(Float, INT8)が動くことを確認。
(注意:Edge TPUは動作しません)




その他



やったこと


tensorflow/lite/tools/pip_package/build_pip_package.shでpip packageを作成してインストールするレシピを作成。



PiCameraが有効にならなかった


meta-rasberrypiの設定でPiCameraを有効としてもまったく認識しなくて1週間悩んでいた...
Interface誌で「My オリジナルLinuxの作り方」連載されている!みつきんさん(@yusuke_mitsuki)!!にもアドバイスいただいたのだが認識せず。。。(その節はありがとうございます!)


結局はboot/config.txtのPiCameraを有効にする「start_x=1」を643行目?以降に記述するとカメラを認識できなくなるようだ...
同じような事象で困っていた人は他にもいたようだ...

(個体なのか何なのか)認識する場合もあるので、もし、ラズパイ4で同じ事象があったら、行数を意識すること。


終わりに


あとは、Edge TPUも動かしてみたいなぁ...

2020年6月6日土曜日

Jetson NanoでTF-TRTを試す(JetPack 4.4 Developer Preview)

目的


(ずいぶん前になるが)
JetPack 4.4 Developer Previewがリリースされたので、TF-TRT(TensorFlow integration with TensorRT)を使ってFP16に最適化したモデルでの処理時間を計測する。
また、TensorFlow Object Detection APIにも新しいモデル(MnasFPN、MobileDets)が追加されたので試してみる。


まとめ


  • SSDLite MobileNet V3は前回(JetPack4.3 + TF1.15)より多少の改善がみられる。
  • MnasFPNは思った効果(推論時間)は確認できなかった。
  • MobileNet V3がMobileNet V2より有効(推論時間がはやい)ことが確認できた。
  • Object detection modelではMobileDetsが良さそう。


インストール


JetPack4.4、TensorFlow、TF-TRT modelsのインストールを行う。


JetPack


JetPack SDKからJetPack 4.4 Developer PreviewのSDイメージをダウンロードして、SDカードに書き込み。


TensorFlow


TensorFlow on Jetson Platform をもとにTensorFlowをインストールする。
注意することは、バージョンは1.xをインストールすること(TensorFlow Object Detection APIは1.x系のみ対応)。
インストール時は‘tensorflow<2’をつける。
※2020.6.6時点では1.15.2がインストールされる。


TF-TRT Models


TF-TRT Models(TensorFlow/TensorRT Models on Jetson)のリポジトリをcloneし、インストールする。
なお、オリジナルのリポジトリからforkして、変更を加えている。
  • submodule(TensorFlow Model Garden)を更新。
    (MobileDetsを変換したいため)
  • 変換スクリプト、ベンチマークスクリプトを追加



まず、TensorFlow Object Detection APIに必要なパッケージをインストールする。

$ sudo apt install pyton3-tk python3-matplotlib
$ pip3 install --user tf_slim

その後、TF-TRT Modelsのリポジトリをcloneし、インストールスクリプトを実行する。

$ git clone https://github.com/NobuoTsukamoto/tf_trt_models.git
$ cd tf_trt_models/
$ ./install.sh python3

※Python3での実行のみ確認している。


モデルの変換


変換スクリプトを使用して、TF-TRT FP16モデルに変換する。


Object detection model


pre-trained modelの場合は、prefixを指定すれば変換する。
ただ、mobiledetはフォルダの構成が他と違うため手動でダウンロード&変換が必要。
今回の対象モデルは以下。
  • ssdlite_mobilenet_v2 (※)
  • ssdlite_mobilenet_v3_small
  • ssdlite_mobilenet_v3_large
  • ssd_mobilenet_v2_mnasfpn
  • ssdlite_mobiledet_cpu
  • ssdlite_mobiledet_edgetpu
  • ssdlite_mobiledet_dsp
  • ssdlite_mobilenet_edgetpu

変換は以下のように実行

cd examples/detection/
$ python3 convert.py --model=ssdlite_mobilenet_v2_coco
$ python3 convert.py --model=ssdlite_mobilenet_v3_small_coco --force_nms_cpu
$ python3 convert.py --model=ssdlite_mobilenet_v3_large_coco --force_nms_cpu
$ python3 convert.py --model=ssd_mobilenet_v2_mnasfpn_coco --force_nms_cpu

# ssdlite_mobilenet_edgetpuはwgetできないのでリンクをクリックしてダウンロード
$ cd data/
$ tar xf checkpoints_ssdlite_mobilenet_edgetpu_coco_quant.tar.gz
$ cd ../
$ python3 convert.py --path=data/ssdlite_mobilenet_edgetpu_coco_quant/ --force_nms_cpu

$ cd data/
$ wget http://download.tensorflow.org/models/object_detection/ssdlite_mobiledet_cpu_320x320_coco_2020_05_19.tar.gz
$ tar xf ssdlite_mobiledet_cpu_320x320_coco_2020_05_19.tar.gz
$ cd ssdlite_mobiledet_cpu_320x320_coco_2020_05_19
$ mv model.ckpt-400000.data-00000-of-00001  model.ckpt.data-00000-of-00001
$ mv model.ckpt-400000.index model.ckpt.index
$ mv model.ckpt-400000.meta model.ckpt.meta
$ cd ../../
$ python3 convert.py --path=data/ssdlite_mobiledet_cpu_320x320_coco_2020_05_19 --force_nms_cpu

$ cd data/
$ wget http://download.tensorflow.org/models/object_detection/ssdlite_mobiledet_edgetpu_320x320_coco_2020_05_19.tar.gz
$ tar xf ssdlite_mobiledet_edgetpu_320x320_coco_2020_05_19.tar.gz
$ cd data/ssdlite_mobiledet_edgetpu_320x320_coco_2020_05_19/fp32/
$ mv model.ckpt-400000.data-00000-of-00001  model.ckpt.data-00000-of-00001
$ mv model.ckpt-400000.index model.ckpt.index
$ mv model.ckpt-400000.meta model.ckpt.meta
$ cd ../../
$ python3 convert.py --path=data/ssdlite_mobiledet_edgetpu_320x320_coco_2020_05_19/fp32 --force_nms_cpu

$ cd data/
$ wget http://download.tensorflow.org/models/object_detection/ssdlite_mobiledet_dsp_320x320_coco_2020_05_19.tar.gz
$ tar xf ssdlite_mobiledet_dsp_320x320_coco_2020_05_19.tar.gz
$ cd data/ssdlite_mobiledet_dsp_320x320_coco_2020_05_19/fp32/
$ mv model.ckpt-400000.data-00000-of-00001  model.ckpt.data-00000-of-00001
$ mv model.ckpt-400000.index model.ckpt.index
$ mv model.ckpt-400000.meta model.ckpt.meta
$ cd ../
$ python3 convert.py --path=data/ssdlite_mobiledet_dsp_320x320_coco_2020_05_19/fp32  --force_nms_cpu

前回のブログでもふれた「NMSをCPU実行に書き換えると変換できない」が発生するため、NMSはそのまま(GPUで実行)とする。変換スクリプトに"--force_nms_cpu"を指定する。


Image classification model


こちらも変換スクリプトを用意している。
pre-trained modelのprefixを指定すれば変換する。
今回の対象モデルは以下(Inputはすべて244x244)。
  • mobilenet_v2 (depth_multiplier=0.5)
  • mobilenet_v2 (depth_multiplier=1.0)
  • mobilenet_v2 (depth_multiplier=1.4)
  • mobilenet_v3_small
  • mobilenet_v3_small

$ cd examples/classification/
$ python3 convert.py --model=mobilenet_v2_0p5_224
$ python3 convert.py --model=mobilenet_v2_1p0_224
$ python3 convert.py --model=mobilenet_v2_1p4_224
$ python3 convert.py --model=mobilenet_v3_small
$ python3 convert.py --model=mobilenet_v3_small_224


ベンチマーク


ベンチマーク実施はMax power&CUIモードで実行する。

$ sudo systemctl set-default multi-user.target
$ sudo reboot
$ sudo jetson_clocks

Object detection model


  • ssdlite_mobilenet_v3_small / largeは前回(JetPack4.3+TF1.15)と比べると推論時間が改善している。
  • ssdlite_mobilenet_v2はNMSのCPU実行ができない分、推論時間が遅い。
  • ssdlite_mobilenet_v2_mnasfpnは想定よりかなり遅い。
    CPUに最適化しているからだろうか?
  • 精度(mAP)と推論時間のトータルを考慮するとssdlite_mobiledet_edgetpuが一番。
  • ssdlite_mobilenet_v3_small or largeも使える。

初回の推論時間

Model
Input size
Inference time [ms]
TF-TRT (FP16)
ssdlite_mobilenet_v2300x30013025
ssdlite_mobilenet_v3_small320x3208375
ssdlite_mobilenet_v3_large320x3207366
ssdlite_mobilenet_v2_mnasfpn320x32017502
ssdlite_mobiledet_cpu320x3208117
ssdlite_mobiledet_edgetpu320x3207332
ssdlite_mobiledet_dsp320x3208203
ssdlite_mobilenet_edgetpu320x32023643


2回目以降の100回の推論の平均時間

Model
Input size
Inference time [ms]
TF-TRT (FP16)
ssdlite_mobilenet_v2300x30088
ssdlite_mobilenet_v3_small320x32056
ssdlite_mobilenet_v3_large320x32068
ssdlite_mobilenet_v2_mnasfpn320x320223
ssdlite_mobiledet_cpu320x32073
ssdlite_mobiledet_edgetpu320x32070
ssdlite_mobiledet_dsp320x32080
ssdlite_mobilenet_edgetpu320x320186



Image classification model


  • mobilenet_v2 (depth_multiplier=0.5)とmobilenet_v3_smallがほぼ同等の推論時間。
  • mobilenet_v2 (depth_multiplier=1.0)とmobilenet_v3_largeがほぼ同等の推論時間。
  • FP32での精度を考えるとmobilenet_v3を使うのが良いかも?


初回の推論時間

Model
Input size
Inference time [ms]
TF-TRT (FP16)
mobilenet_v2_0.5_224224x2243477
mobilenet_v2_1.0_224224x22411354
mobilenet_v2_1.4_224224x2249356
mobilenet_v3_small_224224x2244187
mobilenet_v3_large_224224x2245387


2回目以降の100回の推論の平均時間

Model
Input size
Inference time [ms]
TF-TRT (FP16)
mobilenet_v2_0.5_224224x22410
mobilenet_v2_1.0_224224x22416
mobilenet_v2_1.4_224224x22423
mobilenet_v3_small_224224x22410
mobilenet_v3_large_224224x22417



最後に


Jetson Nano(JetPack 4.4 Developer Preview)でTF-TRT モデルの推論時間をベンチマークしてみた。

結果からMobileNet V3ベースまたは、SSDLite MobileDets Edge TPUが良い結果に思える。
FP16にした場合の制度の低下がどれぐらいかは気になるところではあるが、FP32とほぼ同等(の低下)と考えれば、MobileNet V3、MobileDetsは良い選択肢になると思う。
(あとはどのぐらい学習しやすさによるか?)

Object detection modelはモバイルCPU、Edge TPU、DSPに特化したモデルが登場した(MnasFpn、MobileDets)。
これらのモデルの中にはJetson NanoのGPUではそれほど良い結果は得られなかった。
これはモデルがHWに特化していることも要因の一つに思える。

あとは、Efficientnet、EfficientDetも試してみたい。

2020年4月25日土曜日

TensorFlow Model Optimization ToolkitでQuantization aware training in Keras

変更


2020.5.16 - QATでkeras modelとTF-Lite modelの精度の差がなくなった(問題が解消した)ので修正。


目的



tf.kerasでQuantization aware training(QAT)が実行できるTensorFlow Model Optimization Toolkit(tensorflow-model-optimization)のAPIがリリースされたので試してみる。




注意


現時点(2020.4.25)ではtensorflow-model-optimizationのQATのAPIは1.0リリース前(v0.3.0)で、experimentalなAPIも含まれている。このため、将来ここに記載された内容は変更になっている可能性がある。

また、TensorFlow 2.2.0-rc3以下では、TF-Lite integet quant modelに変換できないためtf-nightlyを使うことにも注意(参考資料のissue#38285も参照) 。

詳細はリファレンスも参照。


動機


以前、TF2.0のKerasでPost-training quantizationをブログに書いた。

しかし、Post-training quantizationは扱いやすい反面、精度が落ちる場合がある。精度をなるべく落とさない場合は、QATをしたいが、tf.kerasではサポートされていなかった。

今回、tensorflow-model-optimizationでQATのAPIがリリースされた。まずはImage ClassificationのモデルでQATを行い、TF-Lite Integer quant modelやEdge TPU Modelへの変換を試してみたかったので、今回やったことをまとめてみる。


参考資料



環境・バージョン情報


Google Colaboratoryで確認できるnotebookを作成した。
ただし、TensorFlow 2.2.0-rc3以下では、TF-Lite modelに変換できないためtf-nightly-gpuを使う。
  • tf-nightly-gpu: 2.2.0.dev20200423
  • tensorflow-model-optimization: 0.3.0


KerasのMobileNet v2でQAT


今回、tf.kerasのMobilenet v2をQATし、TF-Lite modelに変換する。
MobileNet v2のモデルでtf_flower datasetを学習する。tfsdなどの使い方はTF2.0のKerasでPost-training quantizationも参照。
Google Colaboratoryで実行できるnotebookは以下。


インストール


tensorflow-model-optimizationのインストールはpipでインストール可能。
$ pip install tensorflow-model-optimization

Import


以下でtensorflow-model-optimizationをimport。
import tensorflow_model_optimization as tfmo

モデルの生成


Functional APIやSequential modelを使ってモデルを生成する。
keras.applicationsで用意されているモデルを使う場合、Functional APIで構築する(下記の注意を参照)。
def setup_mobilenet_v2_model():
  base_model = MobileNetV2(include_top=False,
                           weights='imagenet',
                           pooling='avg',
                           input_shape=(IMG_SIZE, IMG_SIZE, 3))
  x = base_model.output
  x = tf.keras.layers.Dense(info.features['label'].num_classes,
                            activation="softmax")(x)
  model = tf.keras.Model(inputs=base_model.input, outputs=x)

  return model

モデル生成時の注意


Sequential modelの場合、入れ子となるようなモデルを構築すると量子化モデルの生成時にエラーとなる。
下記のようにモデルを定義するとエラー(ValueError: Quantizing a tf.keras Model inside another tf.keras Model is not supported.)が発生してしまうので注意。
def setup_mobilenet_v2_model():
  base_model = MobileNetV2(include_top=False,
                           weights='imagenet',
                           pooling='avg',
                           input_shape=(IMG_SIZE, IMG_SIZE, 3))
  model = tf.keras.Sequential([base_model,
                               tf.keras.layers.Dense(info.features['label'].num_classes,
                                                     activation='softmax')])

  return model

サポートモデルの注意


現時点でサポートされているImage classification modelはここ(Image classification with tools)を参照。MobileNet、ResNetあたり。
DensNetは量子化モデルの生成時にエラーとなってしまう。サポート状況は改善されるので要確認。


モデルの学習(Not QAT)


QATのを行う前にまずは通常の学習を行う。
QATは通常より学習の時間がかかるため事前の学習を行っておいた方がよい。
history = model.fit(train.repeat(),
                    epochs=10,
                    steps_per_epoch=steps_per_epoch,
                    validation_data=validation.repeat(),
                    validation_steps=validation_steps)

なお、MobileNet v2で、おおよそ1Stepあたりの学習時間は
  • QATでない通常の学習時: 20秒
  • QAT時: 40秒
なので、約2倍の差があった。


モデルの学習(QAT)


学習済みのモデルを使って量子化モデルへの生成、QATを行う。

まず、quantize_model APIでモデル全体を量子化する。
なお、APIとしては一部のレイヤーのみを量子化することも可能。
量子化したあとは、compileが必要になる。
q_aware_model = tfmo.quantization.keras.quantize_model(model)
q_aware_model.compile(optimizer = tf.keras.optimizers.RMSprop(lr=base_learning_rate),
                      loss = 'sparse_categorical_crossentropy',
                      metrics = ["accuracy"])

モデルのサマリを表示してみる。
各Layerがquantize_xxxとなり、量子化モデルになっていることがわかる。
Layer (type)                    Output Shape         Param #     Connected to                     
==================================================================================================
input_1 (InputLayer)            [(None, 224, 224, 3) 0                                            
__________________________________________________________________________________________________
quantize_layer (QuantizeLayer)  (None, 224, 224, 3)  3           input_1[0][0]                    
__________________________________________________________________________________________________
quant_Conv1_pad (QuantizeWrappe (None, 225, 225, 3)  1           quantize_layer[1][0]             
__________________________________________________________________________________________________
quant_Conv1 (QuantizeWrapper)   (None, 112, 112, 32) 929         quant_Conv1_pad[0][0]            
__________________________________________________________________________________________________
quant_bn_Conv1 (QuantizeWrapper (None, 112, 112, 32) 129         quant_Conv1[0][0]                

〜 中略 〜

__________________________________________________________________________________________________
quant_block_16_project (Quantiz (None, 7, 7, 320)    307841      quant_block_16_depthwise_relu[0][
__________________________________________________________________________________________________
quant_block_16_project_BN (Quan (None, 7, 7, 320)    1283        quant_block_16_project[0][0]     
__________________________________________________________________________________________________
quant_Conv_1 (QuantizeWrapper)  (None, 7, 7, 1280)   412161      quant_block_16_project_BN[0][0]  
__________________________________________________________________________________________________
quant_Conv_1_bn (QuantizeWrappe (None, 7, 7, 1280)   5121        quant_Conv_1[0][0]               
__________________________________________________________________________________________________
quant_out_relu (QuantizeWrapper (None, 7, 7, 1280)   3           quant_Conv_1_bn[0][0]            
__________________________________________________________________________________________________
quant_global_average_pooling2d  (None, 1280)         3           quant_out_relu[0][0]             
__________________________________________________________________________________________________
quant_dense (QuantizeWrapper)   (None, 5)            6410        quant_global_average_pooling2d[0]
==================================================================================================

QAT(Quantization aware training)を行う。
これは、生成した量子化モデルで学習を行う。
q_aware_history = q_aware_model.fit(train.repeat(),
                                    initial_epoch=10,
                                    epochs=70,
                                    steps_per_epoch=steps_per_epoch,
                                    validation_data=validation.repeat(),
                                    validation_steps=validation_steps)

学習開始直後は、loss, accuracyが悪化するが学習を進めるとQAT直前とほぼ同等になる。
事前に学習を行っていると精度がすぐに回復するようである。

学習の結果は以下。マゼンダの縦線より左が通常の学習、右がQATである。


QATの注意


(2020.5.16 変更)
Issue #368 でQATのKeras modelとTF-Lite modelで精度の差が異なる問題が修正された。これによってQATの収束とTF-Lite modelの精度は一致する。

自分があげたissueにあるとおり、QATの学習が少ないとKeras model(量子化モデル)とTF-Lite modelの精度に差が出てしまう。

作成したサンプルは10epochs程度でloss, accuracyが収束したように見え、keras modelではtestセットで0.98〜の精度がでる。しかし、TF-Lite modelに変換すると精度が0.20(tf_flowerは5クラス!)となってしまう。

原因はこちらのissueのコメントにあるとおり、量子化のMin / Maxの初期値が-6 / 6でかなり広く、Min / Maxが収束していない状態だと量子化時に損失が発生してしまうとのこと。
また、量子化の更新はMovingAverageQuantizerに定義されていて、tensorflowのmoving_averages(EMA: 移動平均)を使って値が更新される。このときのema_decay(減衰)が0.999で収束するまでにそれなりのstep数がかかってしまうとのことである。

複雑なタスクの場合、QATでloss, accuracyが回復するまでとEMAが収束するまではほぼ同じぐらいかもしれない。しかし、tf_flowerのような簡単?なタスクの場合は、loss, accuracyのほうがはやく収束してしまうため、このような結果となってしまう。

今後、このコメントのとおり、EMAの収束も監視できるようになるといいと思う。

また、モデルによってもMEAの収束は異なる模様。以下のようなモデルの場合は、10epochsでほぼ収束していた。
def setup_cnn_model():
  # extract image features by convolution and max pooling layers
  inputs = tf.keras.Input(shape = (IMG_SIZE, IMG_SIZE, 3))
  x = tf.keras.layers.Conv2D(32, kernel_size=3, padding="same", activation="relu")(inputs)
  x = tf.keras.layers.MaxPooling2D(pool_size=(2, 2))(x)
  x = tf.keras.layers.Conv2D(64, kernel_size=3, padding="same", activation="relu")(x)
  x = tf.keras.layers.MaxPooling2D(pool_size=(2, 2))(x)
  # classify the class by fully-connected layers
  x = tf.keras.layers.Flatten()(x)
  x = tf.keras.layers.Dense(512, activation="relu")(x)
  x = tf.keras.layers.Dense(info.features['label'].num_classes)(x)
  x = tf.keras.layers.Activation("softmax")(x)
  model_functional = tf.keras.Model(inputs=inputs, outputs=x)

  return model_functional

QATのパラメータは変更が可能なので試してみてもよい。


かならず、TF-Lite modelでも精度を確認したほうがよいと思う。


TF-Lite Integer quantization Modelの変換


TF-Lit modelに変換する際は、MLIRのコンバーターが必要(ここでは、experimental_new_converter フラグをセットしているが不要になるはず)。
以下のパラメータで変換ができる。
converter = tf.lite.TFLiteConverter.from_keras_model(q_aware_model)

converter.experimental_new_converter = True
converter.optimizations = [tf.lite.Optimize.DEFAULT]
tflite_q_aware_integer_quant_model = converter.convert()
with open(os.path.join(models_dir, 'mobilenet_v2_q_aware_integer_quant.tflite'), 'wb') as f:
    f.write(tflite_q_aware_integer_quant_model)

TF-Lite Modelのテスト


前回のPost-training quantizationで使用したコードがそのまま使用できる(ここでは省略)。
Post-training quantizationとQATで、Keras modelからの精度低下を確認。

Test accuracykeras modelTF-Lite model
Baseline0.9730.946
QAT1.0001.000

一応、Post-training quantization ではTF-Lite modelでの精度の低下があるが、QATでは低下がない。
ただ、今回のデータセット(tf_flower)だとちょっと適切でないかも。。。


Edge TPU Modelの変換


TF-Lite Integer quant modelに変換できたので、Edge TPU Modelにも変換できるはずである。
ただし、Edge TPU Compiler version 2.1.302470888では、
  • MobileNet v1は成功
  • MobileNet v2は失敗( Internal compiler error. Aborting! )
であった。。。

今後のEdge TPU Compilerのアップデートに期待!


最後に


TensorFlow Model Optimization Toolkitを使ってKerasモデルのQuantization aware trainingを行ってみた。少しハマったポイントはあるけど、簡単にQATができる。

KerasモデルでQATができるとモバイルやEdge TPUでの利用がさらに面白くなると思う。

これからもウォッチしてみたい。

2020年3月28日土曜日

Fedora 31でTensorflow 1.15.2(CUDA10.2 cuDNN7.6.5)をビルドする

目的


前回のブログにつづき、TensorFlow 1.15.2をソースビルドする。1.x系と2.x系は両方あった方がよい。Dockerを使えばよいのだが、簡単に使える方がよいのでビルドする。


環境


前回と同様である。
  • Fedora 31 x86_64
  • python 3.7.6(virtualenv)
  • CUDA 10.2 + cuDNN 7.6.5
  • CPU AMD Ryzen 7 1700
  • GPU GeForce GTX 1070


事前準備


こちらも前回のブログと同様なので割愛。
詳細は、こちらを参照。ただし、Bazelのバージョンは0.26.1を使用する。


Tensorflowのビルド


さて、本題。TensorFlow 1.15.2をビルドする。1.x系の注意事項。

virtualenvの設定




ビルド


ソースを展開後、third_party/nccl/build_defs.bzl.tpl の116行目を削除する。これでCUDA 10.2 + cuDNN 7.6.5でのビルドができる。



ビルドを行う。途中でsys_gettidの問題でエラーとなる。
なお、/home/xxx/.cache/bazel/_bazel_xxx/aca0f394050ca263374306622f61644f のパスは、bazelのキャッシュであり毎回変わるので注意。



ビルドエラーが発生後、該当の external/grpc/src/core/lib/gpr/log_linux.cc を修正する。"gettid"を "sys_gettid"に置き換えて競合しないようにする。

ビルド完了後、pipパッケージを作成して、インストールする。


インストール確認



  • tf.__version__が1.15.2であること。
  • GPUデバイスを認識していること。

  • OK!

    Fedora 31でTensorflow 2.2-rc1(CUDA10.2 cuDNN7.6.5)をビルドする

    目的


    Tensorflowの2.2-rc1をFedora 30でソースビルドする。
    2.2の正式版リリースに向けての準備と備忘録。


    環境


    • Fedora 31 x86_64
    • python 3.7.6(virtualenv)
    • CUDA 10.2 + cuDNN 7.6.5
    • CPU AMD Ryzen 7 1700
    • GPU GeForce GTX 1070


    事前の準備


    GCC8のビルド


    前回の記事と同様、CUDA 10.2がサポートするGCCは8で、Fedora 31のGCC9ではビルドができない。このため、まずはGCC8のビルドを行う。
    GCCのビルドについては、以前の記事を参照。


    GCC8.4のソースダウンロード&ビルド


    以下でビルドを行う。


    specsファイルの作成


    コンパイルしたGCC7でビルドした際に、適切な動的リンクライブラリ(libstdc++.so)がリンクされるようにSPECEファイルを修正する。


    Environment Modulesの設定


    GCC8をEnvironment Modulesで切り替えられるようにする。/etc/modulefiles 配下に、gcc8xのファイルを作成する。
    ※筆者の環境にはソースビルドしたGCC5、GCC7がある。


    Bazelのビルド


    Bazelをソースビルドする(リポジトリのバージョンだとTensorflowが期待するバージョンと一致しないことがあるため)。2.2-rc1はBazel2.0.0が必要なため、ソースビルドした。

    公式のソースビルド方法はここを参照。手順どおりであり詳細の説明は割愛。


    CUDA、cuDNNのインストール


    CUDA: 10.2、cuDNN: 7.6.5をインストール。CUDAはRPM Fusion Howto/ CUDA を参考にインストールを行う。cuDNNはNVIDIAのダウンロードサイトからダウンロード、インストールを行う。


    Tensorflowのビルド


    さて、本題。TensorFlow 2.2-rc1をビルドする。


    virtualenvの設定


    まずはvirtualenv(virtualenvwapper)でTensorflow用の仮想Python環境を作成し、必要なモジュールをインストールする。


    ビルド


    Githubからソースを取得し、configureスクリプト実行し、ビルドを行う。

    • CUDAのサポートを有効とする。
    • Host compilerにGCC8のgccのパスを指定してあげる。
    • ビルドオプションには"--config=v2"と"--config=nonccl "(NCCLのライブラリをインストールしたがビルドエラーとなったので外す)を指定。
    • 日本語環境では依存関係のダウンロードで失敗したので、ビルド前に"LANG=C"で回避。

    なお、エラーは以下であった。



    インストール確認


    • tf.__version__が2.2-rc1であること。
    • GPUデバイスを認識していること。


    OK!

    2020年2月29日土曜日

    Jetson NanoでTF-TRTを試す(JetPack4.3)

    目的


    (ずいぶん前になるが)JetPack4.3がリリースされたので、TF-TRT(TensorFlow integration with TensorRT)を使ってFP16に最適化したモデルでの処理時間を計測する。


    動機


    前回のブログ「Jetson NanoでTF-TRTを試す(Object detection)」で、よくわからない事象として「SSDLite MobileNet v3 LargeのPre-trained Modelで検出ができない」をあげていた。これはtensorflow/modelsにIssueを報告していたのだけど、アップされたmodelに問題があったとのことで新しいmodelがPRされた(2020.2.29時点ではまだマージされていない)。

    問題が解決したのと、JetPackがアップデートされたので、再度、処理時間を計測しようと思った。


    まとめ


    • SSDLite MobileNet v3 large / smallはJetpack 4.2 + TF1.14と比べ処理時間が遅くなっている。
    • MobileNet v3以外のDetection model(v1, v2など)は、NonMaxSupperssionをCPU実行に書き換えるとTF-TRT FP16のモデル変換に失敗する。
    • Classification model(MobileNet v1, v2)はJetPack 4.2 + TF1.14と比べ処理時間が若干だが早くなっている。

    早くなっているね!で終わると思っていたのだが、つぶやいたとおりいろいろとあった。



    環境


    今回、3つのバージョンで計測した。
    • JetPack 4.2.2 + TF1.14
      JetPack: 4.2.2 (r32.2.1)
      TensorRT: 5.1.6
      TensorFlow: 1.14.0+nv19.10
      Python: 3.6.9
    • JetPack4.2.2 + TF1.15
      JetPack: 4.2.2 (r32.2.1)
      TensorRT: 5.1.6
      TensorFlow: 1.15.0+nv19.11
      Python: 3.6.9
    • JetPack4.3 + TF1.15
      JetPack: 4.3 (r32.3.1)
      TensorRT: 6.0.1
      TensorFlow: 1.15.0+nv20.1
      Python: 3.6.9

    モデルの生成


    TF-TRT FP16 のモデル変換は前回と同様、本家よりForkしたリポジトリを使用する。



    なお、SSDLite MobileNet v3 large/smallのpre-trained modelのリンクは変更済みである。モデルの変換および、ベンチマークの際は、CUIモード&jetson_clocksで実行している。


    Detection modelのベンチマーク


    今回は対象を以下のモデルに絞って行った。
    • ssdlite_mobilenet_v2_coco
    • ssdlite_mobilenet_v3_small_coco
    • ssdlite_mobilenet_v3_large

    TF-TRT FP16への変換、計測方法は前回のブログと同様である。
     NonMaxSupperssion(NMS)をCPU実行に書き換えて、TF-TRT FP16に変換。変換後、ベンチマーク用のスクリプト(初回の推論を除く100回の平均)で計測した。

    JetPack4.2.2+TF1.15、JetPack4.3+TF1.15の場合、ssdlite_mobilenet_v2_coco モデルがモデル変換時に「Aborted (core dumped) 」となり失敗する。

    2020-02-19 18:42:53.932561: I tensorflow/core/common_runtime/gpu/gpu_device.cc:1304] Created TensorFlow device (/job:localhost/replica:0/task:0/device:GPU:0 with 1208 MB memory) -> physical GPU (device: 0, name: NVIDIA Tegra X1, pci bus id: 0000:00:00.0, compute capability: 5.3)
    2020-02-19 18:43:21.378537: I tensorflow/compiler/tf2tensorrt/segment/segment.cc:460] There are 1379 ops of 31 different types in the graph that are not converted to TensorRT: Range, Sum, GreaterEqual, Where, Select, ConcatV2, Less, Fill, ExpandDims, Unpack, GatherV2, NoOp, TopKV2, Cast, Slice, Transpose, Pad, Placeholder, ResizeBilinear, Squeeze, Mul, Reshape, AddV2, Pack, Identity, NonMaxSuppressionV5, Shape, Greater, Sub, Minimum, StridedSlice, (For more information see https://docs.nvidia.com/deeplearning/frameworks/tf-trt-user-guide/index.html#supported-ops).
    2020-02-19 18:43:22.630756: I tensorflow/compiler/tf2tensorrt/convert/convert_graph.cc:633] Number of TensorRT candidate segments: 2
    2020-02-19 18:43:22.784932: F tensorflow/core/util/device_name_utils.cc:92] Check failed: IsJobName(job) 
    Aborted (core dumped)

    これは、この方のブログ「JetPack-4.3 for Jetson Nano」でも同様の事象が発生するとあり、NMSをCPU実行に書き換えることをやめると変換に成功するとあった(実際に成功する)。ssdlite_mobilenet_v3は変換に成功するため、単純にNMSのCPU実行の書き換えが問題でないと思われる。

    このため、NMSを書き換えない場合も計測した。


    NMSをCPU実行に書き換えた場合


    Detection model
    (force_nms_cpu=True)
    Inference [ms]
    JetPack4.2.2+TF1.14JetPack4.2.2+TF1.15JetPack4.3+
    TF1.15
    ssdlite_mobilenet_v2_coco_fp16.pb53.62--
    ssdlite_mobilenet_v3_small_coco_fp1648.8964.9259.13
    ssdlite_mobilenet_v3_large_coco_fp1666.0180.5073.45
    -: モデル変換時にAborted (core dumped) が発生



    NMSを書き換えない場合


    Detection model
    (force_nms_cpu=False)
    Inference [ms]
    JetPack4.2.2+TF1.14JetPack4.2.2+TF1.15JetPack4.3+
    TF1.15
    ssdlite_mobilenet_v2_coco_fp16.pb68.3978.2388.34
    ssdlite_mobilenet_v3_small_coco_fp1699.22110.26119.78
    ssdlite_mobilenet_v3_large_coco_fp16111.26120.45123.28



    ベンチマーク結果


    JetPack4.3 + TF1.15は
    • TF-TRT FP16 に変換できるモデルに限りがある。
      今のところはSSDLite MobileNet v3 small / large(SSDLite MobileNet EdgeTPU)が確認できている。
      (おそらく、モデルを学習した時のTensorFlowのバージョンが関係する?)
    • SSDLite MobileNet v3 small / largeは
      JetPack4.2.2 + TF1.14と比べ、推論の処理時間が遅くなっている。
      JetPack4.2.2 + TF1.15と比べ、推論の処理時間は早くなっている。
    • NMSのCPU実行への書き換えを行わなければモデルの変換に成功するが、推論時間は長くなってしまう(ためおすすめではない)。
    • (NMSになにかありそう???)

    Classification modelのベンチマーク


    こちらも、以下のモデルでベンチマークを行った。
    • mobilenet_v1_1.0_224
    • mobilenet_v2_1.0_224

    (以前のブログ「Jetson NanoでTF-TRTを試す(Image Classification)その2」)と同様、TF-TRT FP16に変換。変換後、ベンチマーク用のスクリプト(初回の推論を除く100回の平均)で計測した。

    こちらは、変換時に問題も発生することはなく、JetPack4.3 + TF1.15で処理時間が早くなっていることが確認できた。

    Classification model
    Inference [ms]
    JetPack4.2.2+TF1.14JetPack4.2.2+TF1.15JetPack4.3+
    TF1.15
    mobilenet_v1_1.0_22423.6822.7421.58
    mobilenet_v2_1.0_22427.4526.6924.95