OpenVINO™ 低精度の変換¶
はじめに¶
低精度変換 (LPT と呼ばれる) は、1 つのライブラリーに結合された一連の OpenVINO 変換です。このライブラリーは、インテル CPU、GPU、および ARM プラットフォームで最大のパフォーマンスを発揮しながら、低精度で量子化モデルを推論するには OpenVINO の必須の部分です。このライブラリーには 45 を超える変換が含まれ、30 を超える操作がサポートされます。一部の変換は必須ですが、一部はオプションであり、特定のデバイス用に開発されています。
低精度変換 (LPT) の目的は、量子化モデルを元の精度 (FP16 または FP32) から低精度 (INT8: signed int8
または unsigned int8
) に変換し、OpenVINO™ プラグインで低精度推論の準備を整えることです。これは、2 つの主な原則によって実現されます。
-
FakeQuantize
操作は 2 つの部分に分解されます。パート 1: 量子化操作 - 低精度範囲の出力量子化間隔を持つ新しい
FakeQuantize
操作 (signed int8: [-128, 127] または [-127, 127]、unsigned int8: [0, 255] または [0, 256]) 低精度出力 (signed int8
またはunsigned int8
)。パート 2: 低精度の入力と元の精度の出力による逆量子化操作。
元のモデルの操作による逆量子化演算の伝播。これは、元のモデル操作の前に逆量子化操作が行われないようにするため、低精度出力の量子化操作は元のモデル操作の前に残ります。
その結果、演算入力テンソル精度は元の精度から低精度に変更され、OpenVINO™ プラグインによって低精度で操作を推論できるようになります。
モデルを量子化する方法の詳細については、以下の低精度ツールセクションを参照してください。モデルの量子化の詳細については、このホワイトペーパーのディープラーニングにおける低精度の歴史概要セクションを参照してください。
入力モデルの要件¶
LPT 変換は、次の操作を通じて逆量子化操作を伝播します。
操作が LPT でサポートされていない場合、逆量子化操作は伝播されず、入力テンソル精度は低精度に変更されないため、操作は元の精度で実行されます。
例えば、低精度の畳み込み操作でモデルを推論したい場合、モデルは次の図のようになります。
アクティベーションと重みに関して、サポートされるいくつかの量子化アプローチがあります。サポートされるすべてのアプローチは、以下の量子化アプローチのセクションで説明します。デモモデルでは、FakeQuantize 操作の量子化アプローチが使用されます。
量子化のアプローチ¶
LPT 変換は、次の 2 つの量子化アプローチをサポートします。
FakeQuantize
操作。量子化および逆量子化操作
畳み込み操作について、両方のアプローチを詳しく見てみましょう。
FakeQuantize 操作¶
この場合、FakeQuantize
操作はアクティベーションに使用され、重みには量子化された定数が使用されます。
元の入力モデル:
量子化および逆量子化操作¶
この場合、FakeQuantize
操作と Convert
が量子化操作として使用され、量子化された低精度テンソルが返されます。アクティベーションでの量子化操作の後に、分解を補正するための変換操作と逆量子化操作があります。
元の入力モデル:
どちらのケースでも結果は同じです。LPT 結果モデルでは、次のことが分かります。
-
必要に応じて、アクティベーションに対する
FakeQuantize
操作が 2 つの部分に分解されます。低精度範囲および低精度出力の更新された出力間隔を備えた新しい
FakeQuantize
操作。アクティベーション時の逆量子化操作。
必要に応じて、既存の
FakeQuantize
分解を再加工して精度を高めることができます。逆量子化操作は畳み込みを通じて伝播されました。
LPT 結果モデル:
低精度変換パイプライン¶
LPT 変換パイプラインにはいくつかのステップがあります。各変換の内部では、1 ステップのパターンマッチャーは変換ごとに一意ですが、各操作は複数の変換に割り当てることができます。
各ステップ内で、LPT 変換は入力モデルの操作を処理し、ステップから操作への各変換に変換一致パターンを適用し、パターンが一致した場合に変換を実行します。分解変換は、FakeQuantize
を分解して量子化および逆量子化操作を行います。以前の変換結果からの逆量子化操作が変換結果に使用され、モデルの最後に達するまで同様に続きます。
その結果、すべての操作はプラグインによって低精度で推論されます。プラグインが低精度での操作の推論をサポートしていない場合、対応する LPT 変換を無効にすることができ、操作の入力テンソル精度は変更されません。この場合、操作は元の精度で推論されます。
低精度変換パイプラインには 4 つのステップが含まれます。
ステップ 1. 必要条件¶
このステップでは、モデル内のいくつかの操作を融合および伝播して、次のステップの準備をします。これは OpenVINO プラグインに必要です。
変換:
このステップのモデルが変更されます。詳細については、開発者ガイドの前提条件の変換を参照してください。
ステップ 2. マークアップ¶
このステップでは、操作のランタイム属性を作成します。これらの属性は次のステップで使用されます。
変換:
このステップのモデルが変更されます。新しい属性のみが一部の操作に追加されます。詳細については、開発者ガイドのマークアップ変換を参照してください。
ステップ 3. 主な変換、FakeQuantize 分解および逆量子化操作の処理¶
このステップでは最も多くの変換が行われます。これらの変換は、分解変換と逆量子化操作の処理という 2 つのグループに分けられます。詳細については、開発者ガイドの主要な変換を参照してください。
変換:
分解変換¶
分解変換は、FakeQuantize
操作を次のように分解します: 量子化 (低精度出力による FakeQuantize
) および逆量子化操作 (低精度入力と元の精度出力による量子化の逆)。逆量子化演算では、LPT は変換、減算、乗算の 3 つの操作を使用します。要素ごとの操作では、減算と乗算の 2 番目の分岐には定数があります。逆量子化操作が LPT パイプラインの最後で処理されない場合、それらは FakeQuantize
に融合されて戻されます。
元の FakeQuantize
:
FakeQuantize
は量子化および逆量子化操作を分解します。
変換を処理する逆量子化操作¶
このステップでは、LPT 変換は逆量子化操作を融合するか、可能な限り既存のモデル操作を介して移動します。
FP32 での元の畳み込み操作とその前の逆量子化操作:
Convolution
は、分解および逆量子化操作後に INT8 で操作します。
ステップ 4. 結果モデルのクリーンアップ¶
LPT クリーンアップ変換は、LPT パイプラインの最終ステージです。このステップでは、LPT 変換によって結果モデルがクリーンアップされ、処理されない逆量子化操作が回避されます。可能であれば逆量子化操作を融合します (結果モデルをクリーンアップするために、そうでない場合は少なくとも変換操作を他のモデル操作に融合します)。
変換:
詳細については、開発者ガイドの変換のクリーンアップを参照してください。
FakeQuantize
は逆量子化操作が処理されない操作です。
FakeQuantize
は融合逆量子化演算を使用した操作です。
プラグイン変換パイプラインでの低精度変換¶
典型的な変換パイプラインを以下に説明します。
ステップ 1. 一般的な最適化¶
このステップは LPT ではオプションですが、通常は OpenVINO™ プラグインで提供されます。このステップでは LPT 変換を使用しません。まず、このステップでは、次のプラグイン変換で逆量子化情報が失われるのを防ぐため、重みの定数サブグラフに対する逆量子化操作の定数折りたたみを無効にします。その後、変換関数を最適化し、操作を操作セット 1 に変換します。通常、このステップは、入力量子化モデルの LPT 要件を満たす最も簡単な方法です。プラグインが LPT 入力要件を満たすことを保証できる場合は、このステップをスキップできます。
// check if the function is quantized to ignore LPT transformations for not quantized function to speed up model loading
const bool useLpt = ov::pass::low_precision::LowPrecision::isFunctionQuantized(model);
auto defaultPrecisions =
useLpt ? ov::pass::low_precision::precision_set::get_int8_support() : std::vector<ov::element::Type>{};
if (useLpt) {
// disable constant folding on dequantization subgraphs so they can be processed by LPT
manager.register_pass<ov::pass::MarkDequantizationSubgraph>(defaultPrecisions);
}
// OpenVINO common transformations happen here
if (useLpt) {
// convert subtract constant to INT8 to prevent unnecessary FP16 to FP32 conversion
manager.register_pass<ov::pass::low_precision::ConvertSubtractConstant>(defaultPrecisions);
}
// OpenVINO common transformations happen here
if (useLpt) {
// convert not supported cases FakeQuantize -> Convert -> Convert -> Subtract -> Multiply to a single FakeQuantize
pass_config->set_callback<ov::pass::ConvertQuantizeDequantize>([&defaultPrecisions](const std::shared_ptr<const ov::Node> &node) -> bool {
return ov::pass::low_precision::NetworkHelper::areQuantizeAndDequantizeSupportedForMultiply(node, defaultPrecisions);
});
// convert not supported cases FakeQuantize -> Convert -> Convert -> Subtract -> Multiply to a single FakeQuantize
pass_config->set_callback<ov::pass::ConvertSubtract>([&defaultPrecisions](const std::shared_ptr<const ov::Node> &node) -> bool {
return ov::pass::low_precision::NetworkHelper::areQuantizeAndDequantizeSupportedForSubtract(node, defaultPrecisions);
});
}
manager.run_passes(model);
ステップ 2. 低精度の変換の実行¶
このステップは必須です。LPT 変換を構成して実行します。
using namespace ov::pass::low_precision;
if (useLpt) {
// Low precision transformations plugin specific configuration: restrictions definition
auto supportedPrecisions = std::vector<PrecisionsRestriction>({
PrecisionsRestriction::create<ov::opset1::Convolution>({
{{0}, {ov::element::u8}},
{{1}, {ov::element::i8}},
}),
PrecisionsRestriction::create<ov::opset1::ConvolutionBackpropData>(
{{{0}, {ov::element::u8, ov::element::i8}}, {{1}, {ov::element::i8}}}),
PrecisionsRestriction::create<ov::opset1::GroupConvolution>(
{{{0}, {ov::element::u8}}, {{1}, {ov::element::i8}}}),
PrecisionsRestriction::create<ov::opset1::Multiply>({
{{0}, {ov::element::u8}},
{{1}, {ov::element::i8}},
}),
});
// Low precision transformations plugin specific configuration: per-tensor quantization operations definition
auto perTensorQuantization = std::vector<QuantizationGranularityRestriction>({
QuantizationGranularityRestriction::create<ov::opset1::Convolution>({0}),
QuantizationGranularityRestriction::create<ov::opset1::ConvolutionBackpropData>({0})
});
// Low precision transformations instantiation and registration in pass manager
ov::pass::Manager lptManager;
lptManager.register_pass<ov::pass::low_precision::LowPrecision>(supportedPrecisions, perTensorQuantization);
// Low precision transformations plugin specific configuration: transformation callbacks definition
lptManager.get_pass_config()->set_callback<MarkupPrecisions>([](const std::shared_ptr<const ov::Node>& node) -> bool {
if (const auto multiply = std::dynamic_pointer_cast<const ov::opset1::Multiply>(node)) {
return !MultiplyToGroupConvolutionTransformation::canBeTransformedToGroupConvolution(multiply);
}
return false;
});
lptManager.get_pass_config()->set_callback<ConvolutionBackpropDataTransformation>([&defaultPrecisions](const std::shared_ptr<const ov::Node>& node) -> bool {
return LayerTransformation::isAsymmetricQuantization(node, defaultPrecisions) || WeightableLayerTransformation::isAsymmetricOnWeights(node);
});
lptManager.get_pass_config()->set_callback<MultiplyToGroupConvolutionTransformation>([](const std::shared_ptr<const ov::Node>& node) -> bool {
return MultiplyToGroupConvolutionTransformation::isDynamicOrScalar(node);
});
// Low precision transformations execution
lptManager.run_passes(model);
}
ステップ 3. プラグイン固有の変換¶
このステップはオプションです。変換関数をデバイス固有の操作セットに変更します。
ov::pass::Manager deviceSpecificManager;
deviceSpecificManager.register_pass<ov::pass::device::ConvertOpSet1ToDeviceSpecific>();
deviceSpecificManager.run_passes(model);
結果モデルの概要¶
ResNet-50 モデルの量子化された TensorFlow 実装を見てみましょう。モデル・ダウンローダーを使用して、OpenVINO™ ツールキット - Open Model Zoo リポジトリーから fp16
モデルをダウンロードします。
omz_downloader --name resnet-50-tf --precisions FP16-INT8
その後、モデル量子化ツールを使用してモデルを量子化する必要があります。
omz_quantizer --model_dir public/resnet-50-tf --dataset_dir <DATASET_DIR> --precisions=FP16-INT8
推論¶
モデルを推論してパフォーマンス・カウンターを収集する最も簡単な方法は、ベンチマーク・アプリケーションです。
./benchmark_app -m resnet-50-tf.xml -d CPU -niter 1 -api sync -report_type average_counters -report_folder pc_report_dir
OpenVINO™ CPU プラグインを使用してモデルを推論し、パフォーマンス・カウンターを収集すると、すべての操作 (最後の量子化されていない SoftMax を除く) が INT8 精度で実行されます。
結果の分析¶
結果モデルはさまざまな要因に依存します。
元のモデルの量子化可能性と量子化品質。一部のモデルでは、一部の操作を NNCF ツールで量子化することができません。その場合、操作の前に
FakeQuantize
操作が存在しないため、元の精度で推論されます。LPT のカスタマイズとプラグインでサポートされる操作。プラグインが一部の操作に対して INT8 推論をサポートしない場合、対応する LPT 変換を無効にする必要があり、操作は元の精度で推論されます。
レイヤーの精度に関する情報は、OpenVINO ランタイム API で利用できるパフォーマンス・カウンターに保存されます。例えば、CPU プラグイン上の ResNet-50 モデル推論の量子化された TensorFlow 実装のパフォーマンス・カウンター・テーブルの一部は次のようになります。
layerName |
execStatus |
layerType |
execType |
realTime (ms) |
cpuTime (ms) |
---|---|---|---|---|---|
resnet_model/batch_normalization_15/FusedBatchNorm/Add |
EXECUTED |
Convolution |
jit_avx512_1x1_I8 |
0.377 |
0.377 |
resnet_model/conv2d_16/Conv2D/fq_input_0 |
NOT_RUN |
FakeQuantize |
undef |
0 |
0 |
resnet_model/batch_normalization_16/FusedBatchNorm/Add |
EXECUTED |
Convolution |
jit_avx512_I8 |
0.499 |
0.499 |
resnet_model/conv2d_17/Conv2D/fq_input_0 |
NOT_RUN |
FakeQuantize |
undef |
0 |
0 |
resnet_model/batch_normalization_17/FusedBatchNorm/Add |
EXECUTED |
Convolution |
jit_avx512_1x1_I8 |
0.399 |
0.399 |
resnet_model/add_4/fq_input_0 |
NOT_RUN |
FakeQuantize |
undef |
0 |
0 |
resnet_model/add_4 |
NOT_RUN |
Eltwise |
undef |
0 |
0 |
resnet_model/add_5/fq_input_1 |
NOT_RUN |
FakeQuantize |
undef |
0 |
0 |
テーブルの execStatus
カラムには、可能な値が含まれています。
EXECUTED
- レイヤーはスタンドアロンのプリミティブで実行されました。NOT_RUN
- レイヤーはスタンドアロン・プリミティブによって実行されなかったか、別の操作と融合されて別のレイヤー・プリミティブで実行されました。
テーブルの execType
カラムには、特定のサフィックス付きの推論プリミティブが含まれます。レイヤーには次のマークが付いています。
8 ビットのデータタイプ入力を持ち、8 ビット精度で計算されたレイヤーのサフィックス
I8
32 ビット精度で計算されたレイヤーのサフィックス
FP32
結果として、OpenVINO™ CPU プラグインのすべての操作 (モデルの最後で量子化されていない SoftMax
を除く) は低精度で推論されます。結果モデルには FP32 の FakeQuantize
操作がありますが、プラグインはこれらの操作を以前の操作と融合する責任があることに注意してください。OpenVINO™ CPU プラグインは、FP32 入力と INT8 出力による FakeQuantize
操作による FP32 出力の INT8 畳み込みを融合することにより、すべての操作で最大限に最適化された推論を実現します。この場合、OpenVINO™ CPU プラグインは INT8 および FP32 ベクトル命令を使用しますが、推論の 1 つの INT8 カーネル使用量について報告します。これは、このケースに最も最適化されています。
混合精度¶
LPT 入力モデル演算出力の精度が fp16
の場合でも、逆量子化計算は fp32
精度で行われます。このアプローチは、fp16
算術計算の精度損失を回避するのに使用されます。逆量子化操作の最終的な出力は、予想どおり fp16
の精度になります。
カスタマイズ¶
低精度変換はカスタマイズ可能です。
ビルトインのカスタマイズ操作:
操作精度の制限
テンソル量子化制限ごとの操作
更新精度
逆量子化精度
操作精度の制限¶
このオプションは、操作入力ポートに許可される精度を定義します。オプション値は、LowPrecision
コンストラクターの入力引数として渡されます。
例:
auto supportedPrecisions = std::vector<PrecisionsRestriction>({
PrecisionsRestriction::create<ov::opset1::Convolution>({
{{0}, {ov::element::u8}},
{{1}, {ov::element::i8}},
}),
});
ov::pass::Manager lptManager;
lptManager.register_pass<ov::pass::low_precision::LowPrecision>(supportedPrecisions);
lptManager.run_passes(model);
結果モデルの例では、畳み込み操作の入力には特定の精度が必要です。入力 0 (アクティベーション時) では u8
(unsigned int8) 精度、入力 1 (重み時) では i8
(signed int8) 精度です。
テンソル量子化制限ごとの操作¶
このオプションは、操作がテンソルごとの量子化のみをサポートするかどうかを定義します。オプション値は、LowPrecision
コンストラクターの入力引数として渡されます。
例:
using namespace ov::pass::low_precision;
const std::vector<PrecisionsRestriction> emptyRestrictions;
auto perTensorQuantization = std::vector<QuantizationGranularityRestriction>({
QuantizationGranularityRestriction::create<ov::opset1::Convolution>({0})
});
ov::pass::Manager lptManager;
lptManager.register_pass<ov::pass::low_precision::LowPrecision>(emptyRestrictions, perTensorQuantization);
lptManager.run_passes(model);
結果モデルで提供されている例では、畳み込み操作では入力 0 (アクティベーション時) にテンソルごとの量子化が必要です。
更新精度¶
このオプションは、各 LPT 変換で精度を更新するかどうかを定義します。オプション値はブール値で、LowPrecision
コンストラクターの入力引数である LayerTransformation::Params
の updatePrecisions
メンバーとして渡されます。すべての変換が影響を受けます。true
の場合、低精度変換は精度を低精度に更新し、false
の場合は更新しません。通常、このオプションはプラグインのデバッグに使用されます。
典型的なカスタマイズの使用例¶
プラグイン固有のカスタマイズは、変換コールバックを介して実装できます。例えば、非対称量子化のサポートは、コールバックで LayerTransformation::isAsymmetricQuantization
メソッドと WeightableLayerTransformation::isAsymmetricOnWeights
メソッドの使用によって簡単にカスタマイズできます。
例:
using namespace ov::pass::low_precision;
ov::pass::Manager lptManager;
lptManager.register_pass<ov::pass::low_precision::LowPrecision>();
lptManager.get_pass_config()->set_callback<ConvolutionBackpropDataTransformation>([&defaultPrecisions](const std::shared_ptr<const ov::Node>& node) -> bool {
return LayerTransformation::isAsymmetricQuantization(node, defaultPrecisions) || WeightableLayerTransformation::isAsymmetricOnWeights(node);
});
lptManager.run_passes(model);