量子化

Qualcomm® AI Hub は、浮動小数点モデルを固定小数点に変換するプロセスである量子化を可能にします。固定小数点表現は実数を整数(例:int8、int16)にマッピングし、より高速でメモリ効率の高い推論を可能にします。量子化モデルは、Qualcomm® AI Hub でサポートされているすべてのターゲットランタイムにコンパイルでき、パフォーマンスが最大3倍向上する可能性があります。Snapdragon® Hexagon Tensor Processorは、量子化された操作で最も優れたパフォーマンスを発揮します。

これらのパフォーマンス向上を実現しながらモデルの精度を維持するために、量子化モデルはラベルのないサンプル入力データでキャリブレーションする必要があります。キャリブレーションは、浮動小数点とその量子化された整数表現との間の固定小数点マッピング(スケールとゼロポイント)を決定するプロセスです。量子化されていないソースモデルとキャリブレーションデータを使用して、Qualcomm® AI Hub はデバイス上で実行できる量子化モデルアセットを生成します。

量子化によりモデルの精度が低下する可能性があります。この機能は、さまざまなモデルタイプに対してより堅牢に動作するよう積極的に開発を進めています。問題が発生した場合は、Slack チャンネル にてご連絡ください。

概要

Qualcomm® AI Hub の量子化ジョブは、量子化されていない ONNX を入力として受け取り、量子化された ONNX モデルを出力として生成します。ソースモデルが PyTorch であっても、量子化ジョブに加えてコンパイルジョブを使用してエンドツーエンドのワークフローを構築することで、これを TensorFlow Lite や Qualcomm® AI Engine Direct にデプロイすることができます。次の例を見ていきましょう:

ソースモデルの準備

最初のステップは、モデルをトレースして ONNX にコンパイルすることです。ソースモデルがすでにONNXであっても、量子化前にコンパイラが最適化パスを実行できるため、ONNX にコンパイルすることをお勧めします。これにより、量子化中に問題を引き起こす可能性のある非最適化パターンが解決されます。

このステップは、--target_runtime onnx オプションを使用して submit_compile_job() を呼び出すことで行われます。詳細については モデルのコンパイル を参照してください

import os

import numpy as np
import torch
import torchvision
from PIL import Image

import qai_hub as hub

# 1. Load pre-trained PyTorch model from torchvision
torch_model = torchvision.models.mobilenet_v2(weights="IMAGENET1K_V1").eval()

# 2. Trace the model to TorchScript format
input_shape = (1, 3, 224, 224)
pt_model = torch.jit.trace(torch_model, torch.rand(input_shape))

# 3. Compile the model to ONNX
device = hub.Device("Samsung Galaxy S24 (Family)")
compile_onnx_job = hub.submit_compile_job(
    model=pt_model,
    device=device,
    input_specs=dict(image_tensor=input_shape),
    options="--target_runtime onnx",
)
assert isinstance(compile_onnx_job, hub.CompileJob)

unquantized_onnx_model = compile_onnx_job.get_target_model()
assert isinstance(unquantized_onnx_model, hub.Model)

ONNX モデルの量子化

submit_quantize_job() 関数を使用して ONNX モデルを量子化できます。この関数は ONNX モデルとキャリブレーションデータを入力として受け取り、モデルを量子化し、量子化された ONNX モデルを返します。

結果として得られる量子化モデルは、ONNX の "フェイク量子化" 形式です。これは、opsが技術的に浮動小数点の入力/出力を持ち、量子化のボトルネックがQuantizeLinear/DequantizeLinearペアで別々に表現される量子化表現です。これは、here にある静的 ONNX QDQ形式と似ていますが、重みは依然として浮動小数点として保存され、その後にQuantizeLinearが続きます。これは、Qualcomm® AI Hub がコンパイルジョブの入力として公式にサポートする唯一の ONNX 量子化形式であることに注意してください。

キャリブレーションデータには imagenette_samples.zip を使用します。以下のコードを実行する前に、ファイルをダウンロードしてローカルディレクトリに解凍してください。このチュートリアルでは100サンプルを使用します。一般的には500-1000サンプルを使用することをお勧めします。

この例では、上記の例の続きとして、8ビット整数の重みと8ビット整数のアクティベーション(つまり、w8a8)に量子化することを選択します。

# 4. Load and pre-process downloaded calibration data
# This transform is required for PyTorch imagenet classifiers
# Source: https://pytorch.org/hub/pytorch_vision_resnet/
mean = np.array([0.485, 0.456, 0.406]).reshape((3, 1, 1))
std = np.array([0.229, 0.224, 0.225]).reshape((3, 1, 1))
sample_inputs = []

images_dir = "imagenette_samples/images"
for image_path in os.listdir(images_dir):
    image = Image.open(os.path.join(images_dir, image_path))
    image = image.convert("RGB").resize(input_shape[2:])
    sample_input = np.array(image).astype(np.float32) / 255.0
    sample_input = np.expand_dims(np.transpose(sample_input, (2, 0, 1)), 0)
    sample_inputs.append(((sample_input - mean) / std).astype(np.float32))
calibration_data = dict(image_tensor=sample_inputs)

# 5. Quantize the model
quantize_job = hub.submit_quantize_job(
    model=unquantized_onnx_model,
    calibration_data=calibration_data,
    weights_dtype=hub.QuantizeDtype.INT8,
    activations_dtype=hub.QuantizeDtype.INT8,
)

quantized_onnx_model = quantize_job.get_target_model()
assert isinstance(quantized_onnx_model, hub.Model)

量子化モデルのコンパイル

量子化された ONNX モデルは、さらに TensorFlow Lite または Qualcomm® AI Engine Direct にコンパイルできます。ONNX モデルの量子化されたopsは、ターゲットランタイムアセットの量子化されたopsとなり、利用可能なハードウェアをより効果的に活用できるようになります。

デフォルトでは、入力と出力はfloat32のままです。これにより、整数と浮動小数点の両方をサポートするプラットフォームではいくつかのオーバーヘッドが追加される場合があります。ただし、浮動小数点をまったくサポートしないプラットフォームでは、より深刻な問題が発生する可能性があります。これを解決するために、--quantize_io コンパイルオプションを使用して、IO境界でも量子化を尊重するようにコンパイラに指示できます(詳細は Compile Options を参照してください)。この場合、整数型への変換と整数型からの変換は、統合コードの外部で行う必要があります。

# 6. Compile to target runtime (TFLite)
compile_tflite_job = hub.submit_compile_job(
    model=quantized_onnx_model,
    device=device,
    options="--target_runtime tflite --quantize_io",
)
assert isinstance(compile_tflite_job, hub.CompileJob)

詳細については ONNX モデルを TensorFlow Lite またはQNNにコンパイル を参照してください。

量子化オプション

以下の表は、各ランタイムでサポートされている精度を示しています。

重み

アクティベーション

混合精度*

TFLite

int8

int8

サポートされていません

QNN

int8

int8, int16

サポートされていません

ONNX

int8

int8, int16

サポートされていません

*混合精度は、同じネットワーク内で異なる精度で異なるopsを実行することを可能にします。長期的には、すべてのランタイムが重みとアクティベーションのために int4, int8、および int16 に加えて混合精度をサポートする必要があります。

Please refer to submit_quantize_job() and Quantize Options for additional options.

量子化モデルのパフォーマンスベンチマーク

サンプル入力データを取得するプロセスを経る前に、モデルのレイテンシを迅速にベンチマークすることが有用な場合があります。このユースケースでは、キャリブレーションデータは単一のランダムサンプルである可能性があります。結果として得られる量子化モデルの精度は低くなりますが、正確なモデルと同じオンデバイスのレイテンシを持ちます。

import numpy as np

import qai_hub as hub

device = hub.Device("Samsung Galaxy S24 (Family)")
calibration_data = dict(
    image_tensor=[np.random.randn(1, 3, 224, 224).astype(np.float32)]
)

# Convert the input onnx to optimized ONNX then quantize to ONNX QDQ format
compile_onnx_job = hub.submit_compile_job(
    model="mobilenet_v2.onnx",
    device=device,
    input_specs=dict(image_tensor=(1, 3, 224, 224)),
)
assert isinstance(compile_onnx_job, hub.CompileJob)

unquantized_onnx_model = compile_onnx_job.get_target_model()
assert isinstance(unquantized_onnx_model, hub.Model)

quantize_job = hub.submit_quantize_job(
    model=unquantized_onnx_model,
    calibration_data=calibration_data,
    weights_dtype=hub.QuantizeDtype.INT8,
    activations_dtype=hub.QuantizeDtype.INT8,
)
assert isinstance(quantize_job, hub.QuantizeJob)

quantized_onnx_model = quantize_job.get_target_model()
assert isinstance(quantized_onnx_model, hub.Model)

# Model can be compiled to tflite, qnn, or onnx format
compile_qnn_job = hub.submit_compile_job(
    model=quantized_onnx_model,
    device=device,
    options="--target_runtime qnn_context_binary --quantize_io",
)
assert isinstance(compile_qnn_job, hub.CompileJob)

compiled_model = compile_qnn_job.get_target_model()
assert isinstance(compiled_model, hub.Model)

profile_job = hub.submit_profile_job(
    model=compiled_model,
    device=device,
)
assert isinstance(profile_job, hub.ProfileJob)