皆さんこんにちは。
@tereka114です。
モデル最適化の選択肢の一つであるOpenVINOを試してみました。
モデル最適化とは、モデルの精度を殆ど落とさず、高速化する技術で、以下のような恩恵が得られることが知られています。
①特にGPU等を利用できない、RasberryPiのようなエッジデバイス上で機械学習モデルを動かすケースで性能を上げられる
②モデル最適化のエンジンが動く環境であれば、一度構築したモデルを複数の環境で実行させることができる
今回、その技術の一つであるOpenVINOとそれを用いた有名なモデルのベンチマークを紹介します。
OpenVINO
Intel社が開発したディープラーニングを高速に実行するためのソフトウェアです。
OpenVINOが学習済のモデルをハードウェアに合わせて最適化し、CPU、GPUなどのアクセラレータで高速で推論できるようにします。
本記事では、インストールに関して詳細を扱いません。
インストール方法は次のリンク先を参考にしてください。
PyTorchからOpenVINOを動かす
PyTorchは実装のしやすさとPyTorch Image Models(timmライブラリ)を利用した学習モデルの多様性から私も含め、多くのデータサイエンティストが利用しています。
そのため、今回は、PyTorchで作られたモデルからOpenVINOを動かしてみます。
OpenVINOを利用する手順
PyTorchのモデルをOpenVINO形式に変換するには次のステップが必要になります。
1. PyTorchのモデルからONNX形式に変換する。
2. ONNXからOpenVINO形式に変換する。
3. OpenVINOモデルで推論する。
PyTorchのモデルをONNXに変換する。
OpenVINOはPyTorchのモデルを直接変換できないため、まずはONNXに変換します。
PyTorchにONNX変換を行う関数が用意されているため、その関数を利用します。
ここで、モデルはevalを呼び出して推論モードにしておくことが必要です。
なぜならば、推論時の最適化を行う必要があるためDropoutやBatch Normalizationなど学習、推論で挙動が変わるものでは、期待する計算ができなくなります。
また、SwinTransformerには、ONNXに備わっていない演算があるため、その関数を外部からONNXに登録(roll関数)しています。
import torch import torch.onnx as torch_onnx import timm import argparse import torch from torch.onnx.symbolic_helper import parse_args, _slice_helper from sys import maxsize as maxsize @parse_args('v', 'is', 'is') def roll(g, input, shifts, dims): # Swin Transformerの計算に必要なOperatorを定義 assert len(shifts) == len(dims) result = input for i in range(len(shifts)): shapes = [] shape = _slice_helper(g, result, axes=[dims[i]], starts=[-shifts[i]], ends=[maxsize]) shapes.append(shape) shape = _slice_helper(g, result, axes=[dims[i]], starts=[0], ends=[-shifts[i]]) shapes.append(shape) result = g.op("Concat", *shapes, axis_i=dims[i]) return result parser = argparse.ArgumentParser() parser.add_argument("--model") parser.add_argument("--output") parser.add_argument("--size", type=int) args = parser.parse_args() # モデルの読み込み torch.onnx.symbolic_registry.register_op('roll', roll, '', version=9) net = timm.create_model(args.model, pretrained=True) net.eval() # モデル出力のための設定 model_onnx_path = args.output # 出力するモデルのファイル名 input_names = ["input"] # データを入力する際の名称 output_names = ["output"] # 出力データを取り出す際の名称 # ダミーインプットの作成 input_shape = (3, args.size, args.size) # 入力データの形式 batch_size = 1 # 入力データのバッチサイズ dummy_input = torch.randn(batch_size, *input_shape) # ダミーインプット生成 # 変換実行 if "swin" in args.model: # Swin Transformer用に、ONNXのOpsetを固定 output = torch_onnx.export( net, dummy_input, model_onnx_path,export_params=True, verbose=False, input_names=input_names, output_names=output_names, opset_version=11) else: output = torch_onnx.export( net, dummy_input, model_onnx_path,export_params=True, verbose=False, input_names=input_names, output_names=output_names)
この実装を次のコマンドで動かします。
python pytorch_to_onnx.py --model resnet50 --output resnet50.onnx --size 224
ONNXからOpenVINO形式に変換する。
ONNXからOpenVINOへの変換はOpenVINOのモデル最適化コマンドを実行するのみです。
前段のResNet50のモデルを利用して、変換する場合は以下のコマンドです。
python /opt/intel/openvino_2021/deployment_tools/model_optimizer/mo.py --input_model resnet50.onnx
OpenVINOモデルで推論する。
最後にOpenVINOを動作させます。
事前に以下のコマンドでOpenVINOのPythonモジュールをインストールします。
pip install openvino
以下、先程までコンパイルしたモデルの推論の実装です。
PyTorch、ONNX、OpenVINOの推論速度を比較する実装も含まれています
import numpy as np import time as tm import timm import torch import onnxruntime from openvino.inference_engine import IECore import argparse parser = argparse.ArgumentParser() parser.add_argument("--model") parser.add_argument("--output") parser.add_argument("--size", type=int) args = parser.parse_args() SIZE = int(args.size) # Pytorchの準備 net = timm.create_model(args.model, pretrained=True) net.eval() # ONNXの準備 session = onnxruntime.InferenceSession(f"{args.output}.onnx") # OpenVINOの準備 ie = IECore() model_path = f'{args.output}.xml' weight_path = f'{args.output}.bin' net_openvino = ie.read_network(model=model_path, weights=weight_path) exec_net = ie.load_network(network=net_openvino, device_name='CPU', num_requests=1) # 時間計測用 time_onnx = 0 time_openvino = 0 time_pytorch = 0 # 予測結果比較用 out_onnx = [] out_pytorch = [] out_openvino = [] TIMES = 300 for i in range(TIMES): image = torch.rand(1, 3, SIZE, SIZE) with torch.no_grad(): start_time = tm.time() out = net(image) out_pytorch.append(np.argmax(out[0])) time_pytorch += tm.time() - start_time start_time = tm.time() preds = session.run(["output"], {"input": image.cpu().numpy()}) out_onnx.append(np.argmax(preds[0])) time_onnx += tm.time() - start_time start_time = tm.time() outputs = exec_net.infer(inputs={'input': image.cpu().numpy()})['output'] out_openvino.append(np.argmax(outputs[0])) time_openvino += tm.time() - start_time # 推論結果の整合性確認のため print(np.sum(np.array(out_pytorch) == np.array(out_openvino))) print(np.sum(np.array(out_pytorch) == np.array(out_onnx))) print(np.sum(np.array(out_onnx) == np.array(out_openvino))) # 計算結果 print('PyTorch: ', time_pytorch / TIMES) print('ONNX: ', time_onnx / TIMES) print('Open VINO: ', time_openvino / TIMES)
実行は次のコマンドです。
python infer.py --size 224 --model resnet50--output resnet50
性能実験
OpenVINOを利用すればどの程度高速化されるのか
画像認識の有名なモデルと先程の実装を用いて、性能を比較しました。
計測環境
現在一般的に利用されるモデルを中心に計測しました。
モデルの精度・性能の目安はPyTorchのモデル実装の宝庫であるtimmライブラリのリンクをご確認ください。
結果は次のとおりです。PyTorch(s),ONNX(s),OpenVINO(s)は1枚あたりの推論速度を示しています。
PyTorchと比較して、30-70%ほどの高速化を達成し、また、ONNXよりもほとんどの場合で高速化を達成できました。
また、本方式では、最終的な推論結果は変わりませんでした。
Model | Image Size | PyTorch(s) | ONNX(s) | OpenVINO(s) | 高速化率(Pytorch) | 高速化率(ONNX) |
resnet50 | 224 | 0.271 | 0.137 | 0.112 | 58.67% | 18.25% |
resnet152 | 224 | 0.795 | 0.409 | 0.332 | 58.24% | 18.83% |
convnext_tiny | 224 | 0.324 | 0.201 | 0.182 | 43.83% | 9.45% |
swin_tiny_patch4_window7_224 | 224 | 0.317 | 0.135 | 0.184 | 41.96% | -36.30% |
mobilenetv2_120d | 224 | 0.065 | 0.031 | 0.028 | 56.92% | 9.68% |
mobilenetv3_large_100_miil | 224 | 0.03 | 0.014 | 0.019 | 36.67% | -35.71% |
vit_tiny_patch16_224 | 224 | 0.088 | 0.053 | 0.057 | 35.23% | -7.55% |
vgg16 | 224 | 1.09 | 0.31 | 0.275 | 74.77% | 11.29% |
vgg19 | 224 | 1.309 | 0.388 | 0.339 | 74.10% | 12.63% |
tf_efficientnet_b0_ns | 224 | 0.054 | 0.024 | 0.024 | 55.56% | 0.00% |
tf_efficientnet_b7_ns | 224 | 0.448 | 0.216 | 0.18 | 59.82% | 16.67% |
棒グラフはPyTorch(s)、ONNX(s)、OpenVINO(s)の値を表示しており、低い値であればよりよい性能であることを示しています。
最後に
CPUで処理を行った場合、OpenVINOの結果が最も早い場合が多かったです。
IoTデバイス上で動作させた場合に少し性能に満足できない場合にOpenVINOを適用すると良いかもしれません。
推論性能に困った場合の選択肢の一つに入れると良いと思います。
Acroquest Technologyでは、キャリア採用を行っています。
- ディープラーニング等を使った自然言語/画像/音声/動画解析の研究開発
- Elasticsearch等を使ったデータ収集/分析/可視化
- マイクロサービス、DevOps、最新のOSSを利用する開発プロジェクト
- 書籍・雑誌等の執筆や、社内外での技術の発信・共有によるエンジニアとしての成長
少しでも上記に興味を持たれた方は、是非以下のページをご覧ください。
Kaggle Grandmasterと話したいエンジニアWanted! - Acroquest Technology株式会社のデータサイエンティストの採用 - Wantedlywww.wantedly.com