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 つの操作を使用します: Convert
、Subtract
、および Multiply
。要素ごとの操作では、減算と乗算の 2 番目の分岐には定数があります。逆量子化操作が LPT パイプラインの最後で処理されない場合、それらは FakeQuantize
に融合されて戻されます。
元の FakeQuantize
:
FakeQuantize
は量子化および逆量子化操作を分解します:
トランスフォーメーションを処理する逆量子化操作#
このステップでは、LPT トランスフォーメーションは逆量子化操作を融合するか、可能な限り既存のモデル操作を介して移動します。
FP32 での元の畳み込み操作とその前の逆量子化操作:
Convolution
は、分解および逆量子化操作後に INT8 で操作します:
ステップ 4: 結果モデルのクリーンアップ#
LPT クリーンアップ・トランスフォーメーションは、LPT パイプラインの最終ステージです。このステップでは、LPT トランスフォーメーションによって結果モデルがクリーンアップされ、処理されない逆量子化操作が回避されます。可能であれば逆量子化操作を融合します (結果モデルをクリーンアップするために、そうでない場合は少なくとも変換操作を他のモデル操作に融合します)。
トランスフォーメーション:
詳細については、開発者ガイドの変換のクリーンアップを参照してください。
FakeQuantize
は逆量子化操作が処理されない操作です:
FakeQuantize
は融合逆量子化演算を使用した操作です:
プラグイン・トランスフォーメーション・パイプラインでの低精度トランスフォーメーション#
典型的なトランスフォーメーション・パイプラインを以下に説明します。
ステップ 1. 一般的な最適#
このステップは LPT ではオプションですが、通常は OpenVINO™ プラグインで提供されます。このステップでは LPT トランスフォーメーションを使用しません。まず、このステップでは、次のプラグイン・トランスフォーメーションで逆量子化情報が失われるのを防ぐため、重みの定数サブグラフに対する逆量子化操作の定数折りたたみを無効にします。その後、トランスフォーメーション関数を最適化し、操作を操作セット 1 に変換します。通常、このステップは、入力量子化モデルの LPT 要件を満たす最も簡単な方法です。プラグインが LPT 入力要件を満たすことを保証できる場合は、このステップをスキップできます。
// 関数が量子化されているかチェックして、量子化されていない関数の LPT トランスフォーメーションを無視し、モデルの読み込みを高速化
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) {
// 逆量子化サブグラフの定数畳み込みを無効にして、LPT で処理できるようにします
manager.register_pass<ov::pass::MarkDequantizationSubgraph>(defaultPrecisions);
}
// OpenVINO の一般的なトランスフォーメーションはここで行われます
if (useLpt) {
// 不要な FP16 から FP32 への変換を防ぐために減算定数を INT8 に変換
manager.register_pass<ov::pass::low_precision::ConvertSubtractConstant>(defaultPrecisions);
}
// OpenVINO の一般的なトランスフォーメーションはここで行われます
}
if (useLpt) {
// 変換がサポートされていないケース FakeQuantize -> 変換 -> 変換 -> 減算 -> 乗算して単一の 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);
});
// サポートされていないケース FakeQuantize -> 変換 -> 変換 -> 減算 -> 乗算して単一の 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) {
// 低精度トランスフォーメーション・プラグイン固有の設定: 制限の定義
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}},
}),
});
// 低精度トランスフォーメーション・プラグイン固有の設定: テンソルごとの量子化操作の定義
auto perTensorQuantization = std::vector<QuantizationGranularityRestriction>({
QuantizationGranularityRestriction::create<ov::opset1::Convolution>({0}),
QuantizationGranularityRestriction::create<ov::opset1::ConvolutionBackpropData>({0})
});
// パスマネージャーにおける低精度トランスフォーメーションのインスタンス化と登録
ov::pass::Manager lptManager;
lptManager.register_pass<ov::pass::low_precision::LowPrecision>(supportedPrecisions, perTensorQuantization);
// 低精度トランスフォーメーション・プラグイン固有の設定: 変換コールバックの定義
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);
});
// 低精度トランスフォーメーションの実行
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
(符号なし int8) 精度、入力 1 (重み) では i8
(符号あり 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);