要約
- この記事ではOpenVINO2022で新たに導入されたモデルキャッシュ機能について紹介します。
- モデルキャッシュを使うことで、2回目以降のモデル読み込みから推論開始までの時間を大幅に短縮できます。
※本記事は、AE2100向けのOpenVINO2022.1コンテナを対象としています
はじめに
OpenVINOでは推論の実行の前に、モデルをデバイス(プラグイン)に送信し実行デバイス向けにコンパイルをおこないます。
GPUでは実行するモデルによりコンパイルに数分待たされる場合があります。
今回紹介するモデルキャッシュを使うことで、推論開始までの時間を大幅に短縮できます。
サンプルプログラム
以前紹介した記事 AE2100 OpenVINO API2.0移行ガイド で作成した「画像分類プログラム」にモデルキャッシュ機能を組み込んでみます。
下記をクリックしてC++ソースコードをコピーし、”sample.cpp”という名前のファイルを作成してください。
またmakefileも掲載しますので”Makefile”という名前のファイルを作成してください。
ここをクリックしてsample.cppを表示
#include <iostream>
#include <string>
#include <vector>
#include <opencv2/opencv.hpp>
#include <openvino/openvino.hpp>
const std::string labels_path = "inception_v3_2016_08_28_frozen.labels";
const std::string model_path = "inception_v3_2016_08_28_frozen.xml";
const std::string input_path = "car_1.bmp";
const std::string device_name = "GPU";
int main(int argc, char* argv[]) {
// ラベルリストを読込み。
std::vector<std::string> labels;
FILE* fp = fopen(labels_path.c_str(), "r");
if (fp == NULL) {
std::cout << "labels is not exist" << std::endl;
return -1;
}
char buf[256];
while (fgets(buf, 256, fp) != NULL) {
if (buf[strlen(buf) - 1] == '\n') {
buf[strlen(buf) - 1] = '\0';
}
labels.push_back(buf);
}
//画像ファイルの読み込み
cv::Mat image = cv::imread(input_path);
if (image.empty()) {
std::cout << "cv::imread() failed\n" << std::endl;
return -1;
}
//Coreオブジェクト生成
ov::Core ie;
//モデルキャッシュの設定
ie.set_property(ov::cache_dir("./"));
//IRモデル読み込み
std::shared_ptr<ov::Model> network;
try{
network = ie.read_model(model_path);
}
catch(...){
std::cout << "ie.read_model() failed\n" << std::endl;
return -1;
}
//前処理をモデルに組み込み
ov::preprocess::PrePostProcessor ppp(network);
ppp.input().preprocess().resize(ov::preprocess::ResizeAlgorithm::RESIZE_LINEAR);
ppp.input().tensor().set_shape({ 1, (unsigned long long)image.rows, (unsigned long long)image.cols, (unsigned long long)image.channels() });
ppp.input().tensor().set_layout("NHWC");
ppp.input().tensor().set_element_type(ov::element::u8);
network = ppp.build();
int64 start = cv::getTickCount();//速度計測開始
//モデルをデバイスに送信してコンパイル
ov::CompiledModel compiled_model = ie.compile_model(network, device_name);
int64 end = cv::getTickCount();//速度計測終了
double elapsedMsec = (end - start) * 1000 / cv::getTickFrequency();
std::cout << elapsedMsec << "ms\n" << std::endl;
//推論要求を送るため infer_requestオブジェクトの生成
ov::InferRequest infer_request = compiled_model.create_infer_request();
//推論データをモデルの入力にセット
ov::Tensor input_tensor = ov::Tensor(ov::element::u8, { 1, (unsigned long long)image.rows, (unsigned long long)image.cols, (unsigned long long)image.channels() }, image.data);
infer_request.set_input_tensor(input_tensor);
//推論実行
infer_request.infer();
//推論結果の取り出し
const ov::Tensor& output_tensor = infer_request.get_output_tensor();
float* output = reinterpret_cast<float*>(output_tensor.data());
ov::Shape output_shappe = output_tensor.get_shape();
//推論結果の表示
std::cout << "classid probability label\n------ ------ ------" << std::endl;
std::vector<int> idx;
for (int i = 0; i < output_shappe[1]; i++) {
idx.push_back(i);
}
std::sort(idx.begin(), idx.end(), [output](const int& left, const int& right) { return output[left] > output[right]; });
for (size_t id = 0; id < 5; ++id) {
std::cout << idx[id] << ": " << output[idx[id]] * 100 << "% " << ": " << labels[idx[id]] << std::endl;
}
return 0;
}
ここをクリックしてMakefileを表示
CC = g++
CFLAGS = -std=c++11
CXFLAGS =
LDFLAGS = -L/opt/intel/openvino/extras/opencv/lib \
-lopencv_core -lopencv_imgcodecs -lopencv_highgui -lopencv_imgproc -lopencv_videoio -lopencv_video \
-L/opt/intel/openvino/runtime/lib/intel64 \
-lopenvino
LIBS =
INCLUDE = -I /opt/intel/openvino/extras/opencv/include \
-I /opt/intel/openvino/runtime/include/ie \
-I /opt/intel/openvino/runtime/include
SRC_DIR = ./
OBJ_DIR = ./build
SOURCES = $(shell ls $(SRC_DIR)/*.cpp)
OBJS = $(subst $(SRC_DIR),$(OBJ_DIR), $(SOURCES:.cpp=.o))
TARGET = sample
DEPENDS = $(OBJS:.o=.d)
all: $(TARGET)
$(TARGET): $(OBJS) $(LIBS)
$(CC) -o $@ $(OBJS) $(LIBS) $(LDFLAGS)
$(OBJ_DIR)/%.o: $(SRC_DIR)/%.cpp
@if [ ! -d $(OBJ_DIR) ]; \
then echo "mkdir -p $(OBJ_DIR)"; mkdir -p $(OBJ_DIR); \
fi
$(CC) $(CFLAGS) $(INCLUDE) -O2 -o $@ -c $<
clean:
$(RM) $(OBJS) $(TARGET) $(DEPENDS)
-include $(DEPENDS)
.PHONY: all clean
ソースコードについて説明します。
以下では実行デバイスを指定しています。GPUの場合"GPU"、CPUの場合"CPU"を記述してください。
const std::string device_name = "GPU";
次にモデルのキャッシュ先を指定します。
追加するコードは一行だけです。
Coreオブジェクト生成後にie.set_property()関数でキャッシュ出力先のディレクトリを指定します。ここではカレントディレクトリにしています。
ie.compile_model()関数でモデルコンパイルをおこなうと、初回実行時のみ指定先のディレクトリにCPUやHDDLの場合"xxx.blob"、GPUの場合は”xxx..cl_cache”というファイルが作成されます。
2回目以降は、コンパイルはおこなれずコンパイル済みのキャッシュファイルから読み込みがおこなわれます。
なお、モデルファイルやモデルに組み込む前処理/後処理を変えた場合、別名のキャッシュファイルが作成されますので、不要なキャッシュファイルは削除するようにしてください。
//Coreオブジェクト生成
ov::Core ie;
//モデルキャッシュの設定
ie.set_property(ov::cache_dir("./"));
以下のコードではOpenCVの関数を使い、ie.compile_model()関数にかかる時間を計測しています。
int64 start = cv::getTickCount();//速度計測開始
//モデルをデバイスに送信してコンパイル
ov::CompiledModel compiled_model = ie.compile_model(network, device_name);
int64 end = cv::getTickCount();//速度計測終了
double elapsedMsec = (end - start) * 1000 / cv::getTickFrequency();
std::cout << elapsedMsec << "ms\n" << std::endl;
高速化の確認
上記で掲載したC++コードを開発環境でビルドします。
開発環境の構築は、AE2100のSDKマニュアル「AE2100_SDKManual(DeepLearning)_v1.5.pdf」の2.1章を参照してください。
開発環境のカレントディレクトリに先ほどのsample.cppとMakefileを置きmakeコマンドを実行します。
# make
makeが成功すると、”sample”という実行ファイルが出力されます。
次にAE2100のSDKマニュアル「AE2100_SDKManual(DeepLearning)_v1.5.pdf」の2.2章および2.3章を参照し、画像分類モデルと画像ファイル用意してください。
以下のファイルをAE2100のOpenVINOコンテナ内にコピーします。
- sample
- inception_v3_2016_08_28_frozen.xml
- inception_v3_2016_08_28_frozen.bin
- inception_v3_2016_08_28_frozen.labels
- car_1.bmp
AE2100のOpenVINOコンテナ内でプログラムの実行をおこないます。
# source /opt/intel/openvino/setupvars.sh
# ./sample
実行が成功すると以下の表示が出力されます。
38661.7ms
再度sampleを実行すると以下の時間になりました。
1394.78ms
掲載プログラムでは実行デバイスにGPUを指定していましたが、初回の実行では38.6秒掛かっていたものが、2回目は1.3秒に短縮されました。
※推論デバイスを”HDDL”に変更し試してみましたがOpenVINO2022.1 時点ではプログラムの途中で例外エラーが発生してしまいました。今後HDDLでもモデルキャッシュがサポートされることを期待したいです。
まとめ
今回はOpenVINO2022で新たに導入されたOpenVINOのモデルキャッシュ機能についてご紹介しました。
モデルキャッシュ機能を組み込むことで、推論開始までの時間が大幅に短縮されますのでぜひ活用してみてください。
参考
OpenVINO公式ドキュメント