OpenVINO™ 低精度の変換

はじめに

低精度変換 (LPT と呼ばれる) は、1 つのライブラリーに結合された一連の OpenVINO 変換です。このライブラリーは、インテル CPU、GPU、および ARM プラットフォームで最大のパフォーマンスを発揮しながら、低精度で量子化モデルを推論するには OpenVINO の必須の部分です。このライブラリーには 45 を超える変換が含まれ、30 を超える操作がサポートされます。一部の変換は必須ですが、一部はオプションであり、特定のデバイス用に開発されています。

低精度変換 (LPT) の目的は、量子化モデルを元の精度 (FP16 または FP32) から低精度 (INT8: signed int8 または unsigned int8) に変換し、OpenVINO™ プラグインで低精度推論の準備を整えることです。これは、2 つの主な原則によって実現されます。

  1. FakeQuantize 操作は 2 つの部分に分解されます。

    • パート 1: 量子化操作 - 低精度範囲の出力量子化間隔を持つ新しい FakeQuantize 操作 (signed int8: [-128, 127] または [-127, 127]、unsigned int8: [0, 255] または [0, 256]) 低精度出力 (signed int8 または unsigned int8)。

    • パート 2: 低精度の入力と元の精度の出力による逆量子化操作。

  2. 元のモデルの操作による逆量子化演算の伝播。これは、元のモデル操作の前に逆量子化操作が行われないようにするため、低精度出力の量子化操作は元のモデル操作の前に残ります。

その結果、演算入力テンソル精度は元の精度から低精度に変更され、OpenVINO™ プラグインによって低精度で操作を推論できるようになります。

モデルを量子化する方法の詳細については、以下の低精度ツールセクションを参照してください。モデルの量子化の詳細については、このホワイトペーパーディープラーニングにおける低精度の歴史概要セクションを参照してください。

入力モデルの要件

LPT 変換は、次の操作を通じて逆量子化操作を伝播します。

操作が LPT でサポートされていない場合、逆量子化操作は伝播されず、入力テンソル精度は低精度に変更されないため、操作は元の精度で実行されます。

例えば、低精度の畳み込み操作でモデルを推論したい場合、モデルは次の図のようになります。

Quantized Convolution

アクティベーションと重みに関して、サポートされるいくつかの量子化アプローチがあります。サポートされるすべてのアプローチは、以下の量子化アプローチのセクションで説明します。デモモデルでは、FakeQuantize 操作の量子化アプローチが使用されます。

低精度ツール

量子化モデルを取得する方法の詳細は、モデルの最適化ドキュメントを参照してください。

量子化のアプローチ

LPT 変換は、次の 2 つの量子化アプローチをサポートします。

  1. FakeQuantize 操作。

  2. 量子化および逆量子化操作

畳み込み操作について、両方のアプローチを詳しく見てみましょう。

FakeQuantize 操作

この場合、FakeQuantize 操作はアクティベーションに使用され、重みには量子化された定数が使用されます。
元の入力モデル:

Original model with FakeQuantize

量子化および逆量子化操作

この場合、FakeQuantize 操作と Convert が量子化操作として使用され、量子化された低精度テンソルが返されます。アクティベーションでの量子化操作の後に、分解を補正するための変換操作と逆量子化操作があります。
元の入力モデル:

Original model with Q/DQ

どちらのケースでも結果は同じです。LPT 結果モデルでは、次のことが分かります。

  1. 必要に応じて、アクティベーションに対する FakeQuantize 操作が 2 つの部分に分解されます。

    • 低精度範囲および低精度出力の更新された出力間隔を備えた新しい FakeQuantize 操作。

    • アクティベーション時の逆量子化操作。

  2. 必要に応じて、既存の FakeQuantize 分解を再加工して精度を高めることができます。

  3. 逆量子化操作は畳み込みを通じて伝播されました。

LPT 結果モデル:

Result model

低精度変換パイプライン

LPT 変換パイプラインにはいくつかのステップがあります。各変換の内部では、1 ステップのパターンマッチャーは変換ごとに一意ですが、各操作は複数の変換に割り当てることができます。

Low precision transformations pipeline

各ステップ内で、LPT 変換は入力モデルの操作を処理し、ステップから操作への各変換に変換一致パターンを適用し、パターンが一致した場合に変換を実行します。分解変換は、FakeQuantize を分解して量子化および逆量子化操作を行います。以前の変換結果からの逆量子化操作が変換結果に使用され、モデルの最後に達するまで同様に続きます。

その結果、すべての操作はプラグインによって低精度で推論されます。プラグインが低精度での操作の推論をサポートしていない場合、対応する LPT 変換を無効にすることができ、操作の入力テンソル精度は変更されません。この場合、操作は元の精度で推論されます。

低精度変換パイプラインには 4 つのステップが含まれます。

ステップ 1. 必要条件

このステップでは、モデル内のいくつかの操作を融合および伝播して、次のステップの準備をします。これは OpenVINO プラグインに必要です。
変換:

このステップのモデルが変更されます。詳細については、開発者ガイドの前提条件の変換を参照してください。

ステップ 2. マークアップ

このステップでは、操作のランタイム属性を作成します。これらの属性は次のステップで使用されます。
変換:

このステップのモデルが変更されます。新しい属性のみが一部の操作に追加されます。詳細については、開発者ガイドのマークアップ変換を参照してください。

ステップ 3. 主な変換、FakeQuantize 分解および逆量子化操作の処理

このステップでは最も多くの変換が行われます。これらの変換は、分解変換と逆量子化操作の処理という 2 つのグループに分けられます。詳細については、開発者ガイドの主要な変換を参照してください。

変換:

分解変換

分解変換は、FakeQuantize 操作を次のように分解します: 量子化 (低精度出力による FakeQuantize) および逆量子化操作 (低精度入力と元の精度出力による量子化の逆)。逆量子化演算では、LPT は変換減算乗算の 3 つの操作を使用します。要素ごとの操作では、減算乗算の 2 番目の分岐には定数があります。逆量子化操作が LPT パイプラインの最後で処理されない場合、それらは FakeQuantize に融合されて戻されます。

元の FakeQuantize:

FakeQuantize operation before LPT

FakeQuantize は量子化および逆量子化操作を分解します。

FakeQuantize operation after LPT

変換を処理する逆量子化操作

このステップでは、LPT 変換は逆量子化操作を融合するか、可能な限り既存のモデル操作を介して移動します。

FP32 での元の畳み込み操作とその前の逆量子化操作:

Convolution operation before LPT

Convolution は、分解および逆量子化操作後に INT8 で操作します。

Convolution operation after LPT

ステップ 4. 結果モデルのクリーンアップ

LPT クリーンアップ変換は、LPT パイプラインの最終ステージです。このステップでは、LPT 変換によって結果モデルがクリーンアップされ、処理されない逆量子化操作が回避されます。可能であれば逆量子化操作を融合します (結果モデルをクリーンアップするために、そうでない場合は少なくとも変換操作を他のモデル操作に融合します)。

変換:

詳細については、開発者ガイドの変換のクリーンアップを参照してください。

FakeQuantize は逆量子化操作が処理されない操作です。

TODO: FakeQuantize operation with dequantization operations before LPT

FakeQuantize は融合逆量子化演算を使用した操作です。

TODO: FakeQuantize operation with fused operations after LPT

プラグイン変換パイプラインでの低精度変換

典型的な変換パイプラインを以下に説明します。

ステップ 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::ParamsupdatePrecisions メンバーとして渡されます。すべての変換が影響を受けます。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);