Taste of Tech Topics

Acroquest Technology株式会社のエンジニアが書く技術ブログ

モデル最適化ソフトウェアOpenVINOを用いた性能高速化とモデル比較の実験

皆さんこんにちは。
@tereka114です。

モデル最適化の選択肢の一つであるOpenVINOを試してみました。
モデル最適化とは、モデルの精度を殆ど落とさず、高速化する技術で、以下のような恩恵が得られることが知られています。

①特にGPU等を利用できない、RasberryPiのようなエッジデバイス上で機械学習モデルを動かすケースで性能を上げられる
②モデル最適化のエンジンが動く環境であれば、一度構築したモデルを複数の環境で実行させることができる

今回、その技術の一つであるOpenVINOとそれを用いた有名なモデルのベンチマークを紹介します。

OpenVINO

Intel社が開発したディープラーニングを高速に実行するためのソフトウェアです。
OpenVINOが学習済のモデルをハードウェアに合わせて最適化し、CPU、GPUなどのアクセラレータで高速で推論できるようにします。

www.intel.com

本記事では、インストールに関して詳細を扱いません。
インストール方法は次のリンク先を参考にしてください。

docs.openvino.ai

PyTorchからOpenVINOを動かす

PyTorchは実装のしやすさとPyTorch Image Models(timmライブラリ)を利用した学習モデルの多様性から私も含め、多くのデータサイエンティストが利用しています。
そのため、今回は、PyTorchで作られたモデルからOpenVINOを動かしてみます。

github.com

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ライブラリのリンクをご確認ください。

github.com

結果は次のとおりです。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