ディープラーニングモデルの軽量化ツールPCASの使い方 (3)　ーモデル変換と性能評価 (CPU/VPU) 編ー

本記事は、[**ディープラーニングモデルの軽量化ツールPCASの使い方 (2)　ー画像分類モデルの軽量化編ー**](https://qiita.com/ymmt833/items/6e6495ac634fb7e4b812) の続編です。

前回の記事では、モデル軽量化ツールPCAS（以下、PCASツール）を使用して、VGG10モデルをCIFAR10データセットに対してスクラッチ学習し、軽量化を行いました。今回は、軽量化したVGG10モデルをOpenVINO形式（IR形式）に変換し、エッジコンピューターである **AE2100 (CPU/VPU)** と **汎用PC (CPU)** 上での推論速度を実際に計測したいと思います。

## 本記事の要約
* 軽量化済みモデル (PyTorch) をONNX形式を経由して**OpenVINO形式**に変換する方法を解説します。
* 「CIFAR10 + VGG10」のAE2100上の推論速度に関して、**最大約6.2倍(CPU)と最大約2.4倍(VPU)の高速化効果**を確認しました。
* PCASツールの**INT8量子化機能**を使用した高速化について説明します。

## PyTorchモデルをONNX形式に変換する
※ [前回の記事](https://qiita.com/ymmt833/items/6e6495ac634fb7e4b812)で作成したVGG10モデルを例として解説していきます。

PCASツールの各エントリーポイント（`train_XXX.py`）は、PyTorchモデルをONNX形式に変換する機能を引数`--export_onnx`として備えています。それを使用してPCASツールが出力したPyTorchモデルファイル（`XXX.ckpt`）をONNXモデルに変換します。

具体的には、PyTorchモデルファイルパスと軽量化実行時に生成される`prune_config.yaml`ファイルパスを与えることで、PyTorchモデルファイルと同じディレクトリパスにONNXモデルファイル（`XXX.onnx`）を生成します。

例えば、VGG10モデルのモデルファイル名が`epoch=191-step=75071.ckpt`の時、以下を実行すると、
```shell
python examples/classification/train_plain.py \
--export_onnx \
--outdir /path/to/output \
--model my_vgg10 \
--checkpoint /path/to/epoch=191-step=75071.ckpt \
--prune_config /path/to/prune_config.yaml
```
以下のメッセージが表示されて変換が完了します。
```
ONNX export of the model was completed! (/path/to/output/epoch=191-step=75071.onnx)
```

### ONNX変換の一括実行（補足）
PCASツールの実行後に生成される**複数の軽量化済みモデルファイル**（例えば、iterXX_train/YY.ckpt）に対して、**一括でONNX変換する場合**は、以下の一括変換用のスクリプト`examples/utils/ckpt2onnx.py`が利用可能です。

```shell
python examples/utils/ckpt2onnx.py \
--input_dir /result/my_cifar10_vgg10/my_vgg10-20220509-084713/ \
--entrypoint examples/classification/train_plain.py \
--in_ch 3 \
--in_size 32
```
`--input_dir`にPCASツールの出力ディレクトリを指定し、`--entrypoint`にはモデルの生成に使用した実行コードを指定します。`--in_ch`と`--in_size`は、それぞれ入力画像のチャネル数と解像度を指定しています。処理が完了すると、以下のメッセージが表示され、"onnx"ディレクトリに全てのONNXモデルファイルが格納されます。
```
All models are converted into ONNX! (/result/my_cifar10_vgg10/my_vgg10-20220509-084713/onnx)
```

## ONNXモデルをOpenVINO形式（IR形式）に変換する
[Intel OpenVINO Toolkit](https://www.intel.com/content/www/us/en/developer/tools/openvino-toolkit/download.html?elq_cid=6167346)を入手・インストールして、Model Optimizerを実行します。元のONNXファイル名に"="記号が含まれる場合はエラーとなるため、一旦リネームしてから適用しています。また、`--data_type FP16`でビット精度を16ビットの浮動小数点に指定しています。
```
> mv epoch=191-step=75071.onnx my_vgg10.onnx
> mo --input_model my_vgg10.onnx \
--input_shape "[1,3,32,32]" \
--mean_values "[0.485,0.456,0.406]" \
--scale_values "[0.229,0.224,0.225]" \
--data_type FP16
```
実行後、ONNXファイルと同じディレクトリに`my_vgg10.xml, my_vgg10.bin, my_vgg10.mapping`の３つのファイルが出力されます。

## AE2100 (CPU/VPU) 上で推論速度を計測する
※本記事では、AE2100の環境構築方法については解説していませんが、AE2100付属のマニュアル（「AE2100 シリーズ SDK 取扱説明書 ― 共通編 ― 」）を参考にセットアップをお願いします。なお、[AIエッジユーザーサイト](https://www.oki-partner.net/aieu/)にて、**OpenVINOがインストール済みのAE2100用コンテナイメージ**が提供されていますので、そちらを利用すると簡単です。

Model Optimizerで生成したモデルファイル`my_vgg10.xml, my_vgg10.bin, my_vgg10.mapping`を、AE2100に転送した後、OpenVINO Tookitに同梱の [Benchmark Python Tool](https://docs.openvino.ai/latest/openvino_inference_engine_tools_benchmark_tool_README.html) (benchmark_app.py) を使用して、例えば以下のようにモデルの推論速度を計測します。
```
> python3 benchmark_app.py -m my_vgg10.xml -d CPU -nireq 2 -niter 1000
Count:      1000 iterations
Duration:   14126.24 ms
Latency:    55.88 ms
Throughput: 70.79 FPS
```
ここで、`-m`でモデルファイル（.xml）を指定し、`-d`で使用デバイスを指定しています。CPUで実行する場合は`CPU`を、AIアクセラレータチップ MyriadX (VPU) を使用して実行する場合は`HDDL`を指定します。`-nireq 2`で2つの推論の非同期実行を、`-niter 1000`で1000回の推論実行を指定しています。

続いて、[前回の記事](https://qiita.com/ymmt833/items/6e6495ac634fb7e4b812) で作成したVGG10のオリジナルモデルや軽量化レベル（圧縮率）の異なるモデルについて、推論速度（FPS: Frame Per Second）を計測したところ、以下の結果となりました。

|軽量化レベル (圧縮率)[^1]|認識精度|FPS - CPU| FPS - VPU |サイズ|
|:-:|:-:|:-:|:-:|:-:|
|Original (0%)|93.2%|**36.35**|**302.32**|13.9MB|
|Iter01 (15%)|93.2%|42.33|323.07|10.5MB|
|Iter02 (28%)|93.1%|58.94|381.37|7.6MB|
|Iter03 (39%)|91.8%|96.45|469.92|5.8MB|
|Iter04 (48%)|91.3%|134.12|539.34|4.4MB|
|Iter05 (56%)|90.0%|167.42|580.68|3.3MB|
|Iter06 (62%)|89.6%|195.48|668.11|2.7MB|
|Iter07 (68%)|89.3%|**223.87**|**714.15**|1.6MB|

[^1]: 軽量化レベルを"IterXX"と呼び、XXには軽量化処理のループ回数が入ります。XXが大きくなるほど軽量化されたモデルとなります。圧縮率は、モデル全体のチャネル数に対する削減比率を表すもの（値が大きいほど削減量が多い）で、今回の軽量化処理ループ1回におけるチャネル削減率を$c$、プルーニングを行った回数を$n$とすると、$1−(1−c)^n$ で計算されます。今回のケースでは、デフォルト値である $c=0.15$（引数`--comp_rate`で指定可能） を使用しています。

CPUでの推論実行では、モデル軽量化により **最大約6.2倍** の高速化ができ、最大時の精度劣化は3.9％でした。モデルサイズの観点では、**約1/8倍** となりました。

また、VPUでの軽量化による高速化効果は **最大で約2.4倍** でした。なお、軽量化前はCPUでの実行よりVPUの方が8倍程度高速でしたが、VPUでの軽量化の効果はCPUでの場合より相対的に小さい比率になっています。


## 汎用PC (CPU) 上で推論速度を計測する
汎用PCの例として、私の作業用ノートPCに内蔵されているCore-i5のCPU[^6]を使用した場合の軽量化効果を確認します。AE2100の場合と同様に、表形式で結果を示します。
[^6]: 正確には、Intel(R) Core(TM) i5-7300U CPU @ 2.60GHz を使用しています。

|軽量化レベル (圧縮率)[^1]|認識精度|FPS|サイズ|
|:-:|:-:|:-:|:-:|
|Original (0%)|93.2%|**101.28**|13.9MB|
|Iter01 (15%)|93.2%|109.27|10.5MB|
|Iter02 (28%)|93.1%|152.79|7.6MB|
|Iter03 (39%)|91.8%|270.62|5.8MB|
|Iter04 (48%)|91.6%|411.86|4.4MB|
|Iter05 (56%)|89.9%|600.92|3.3MB|
|Iter06 (62%)|89.5%|768.35|2.7MB|
|Iter07 (68%)|89.3%|**984.06**|1.6MB|

本検証に使用したCPUは、リソース制約のあるAE2100に搭載の組込み用CPUではないため、オリジナルモデルからやや高いスループットが出ており、最大で **約9.7倍** の高速化効果が得られました。

ここで、INT8演算に対応するCPUの場合は、PCASツールのオプションであるINT8量子化機能を使用した更なる高速化が可能です。

## INT8量子化機能を使用する (汎用PCでの実行)
AE2100がINT8演算の高速化に有効なAVX命令に対応していないため、今回は **汎用PC** で量子化による高速化効果を測定します。

PCASツールでは、高速化のためのオプション機能として独自アルゴリズムの **量子化学習（Quantization Aware Training）**[^7] が利用でき、推論時に使用するビット精度をINT8に落とすことが可能です。
[^7]:量子化学習は、学習済みのモデルに対して事後的に量子化を行う手法（Post-training Quantization）ではなく、モデルの学習段階から量子化を行う手法です。そのため、**スクラッチ学習の段階から**本オプションを適用する必要があります。一般に、量子化学習は事後的な量子化よりも高い認識精度を達成できることが知られています。

本機能は、軽量化処理ループを開始するエントリーポイント（`train_XXX.py`）で軽量化を実行する際に、追加の引数として`--quantization W8A8`を設定することで有効化できます。なお、この状態で生成されるモデルは全て **INT8による推論専用** となります。

例えば、以下のコマンドで量子化モデルのスクラッチ学習と軽量化が実行できます（--quantization以外は前回と同じです）。
```shell
python examples/classification/train_plain.py \
--outdir /result/cifar10_vgg10 \
--data_dir /data/cifar-10-batches-py \
--dataset my_cifar10 \
--model my_vgg10 \
--max_epochs 200 \
--devices 1 \
--learning_rate 0.1 \
--batch_size 128 \
--pcas_max_iters 8 \
--mode train \
--finetune_lr_ratio 0.3 \
--quantization W8A8  # 追加
```

前述の量子化機能を使用しない場合（FP16）の結果に、今回の結果を加えたものが以下になります。

|軽量化レベル (圧縮率)[^1]|認識精度 - FP16|FPS - FP16|サイズ - FP16|認識精度 - INT8|FPS - INT8|サイズ - INT8|
|:-:|:-:|:-:|:-:|:-:|:-:|:-:|
|Original (0%)|93.2%|**101.28**|13.9MB|93.4%|348.02|6.9MB|
|Iter01 (15%)|93.2%|109.27|10.5MB|92.8%|377.16|5.3MB|
|Iter02 (28%)|93.1%|152.79|7.6MB|92.0%|546.94|4.0MB|
|Iter03 (39%)|91.8%|270.62|5.8MB|91.5%|846.70|3.1MB|
|Iter04 (48%)|91.6%|411.86|4.4MB|90.7%|1070.95|2.5MB|
|Iter05 (56%)|89.9%|600.92|3.3MB|90.4%|1397.33|1.8MB|
|Iter06 (62%)|89.5%|768.35|2.7MB|89.7%|1813.08|1.3MB|
|Iter07 (68%)|89.3%|984.06|1.6MB|88.9%|**2141.25**|0.9MB|

FP16の場合よりも、**さらに2～3倍高速化**することができました。一部、認識精度が若干改善している軽量化レベル（Iter05など）も確認できました。

オリジナルモデル（Original + FP16）と 最も軽量化したモデル（Iter07 + INT8）を比較すると、精度劣化4.3％で **約21.1倍の高速化** となりました。また、モデルサイズの観点では、**約1/15倍** となりました。

## まとめ
本記事では、PCASツールを使用して軽量化したVGG10モデルを例に、OpenVINO形式へのモデル変換方法と、AE2100 (CPU/VPU) と汎用PC (CPU) 上での推論速度の実測評価を行いました。さらに、INT8演算対応CPUを利用する場合に有効な量子化機能による高速化方法について解説しました。次回は、強力な画像分類ライブラリである **[pytorch-image-models (timm)](https://github.com/rwightman/pytorch-image-models) とPCASツールを連携する方法** について紹介したいと思います。

⇒　[ディープラーニングモデルの軽量化ツールPCASの使い方 (4)　ー画像分類ライブラリtimmとの連携編ー](https://qiita.com/ymmt833/items/9ea9bf40bd84d60d576e)

本記事のバックナンバー：
[ディープラーニングモデルの軽量化ツールPCASの使い方 (1)　ーインストール編ー](https://qiita.com/ymmt833/items/8b3f34bb9d6789e8295e)
[ディープラーニングモデルの軽量化ツールPCASの使い方 (2)　ー画像分類モデルの軽量化編ー](https://qiita.com/ymmt833/items/6e6495ac634fb7e4b812)