量化

Qualcomm® AI Hub Workbench 支援將浮點模型轉換為定點模型,此過程稱為 量化 (quantization)。定點表示法會將實數映射為整數(例如 int8、int16),以實現更快且更節省記憶體的推論。量化後的模型可以編譯至 Qualcomm® AI Hub Workbench 上所有支援的目標執行環境,並且效能可提升至 3 倍。Snapdragon® 的 Hexagon Tensor Processor 在執行量化運算時表現最佳。

為了在保持模型準確度的同時獲得這些效能提升,量化模型需要使用未標記的樣本輸入資料進行校準。校準是確定浮點數與其量化整數表示之間固定點映射(比例與零點)的過程。透過未量化的來源模型與校準資料,Qualcomm® AI Hub Workbench 會產生可編譯並在裝置上執行的量化模型資產。

模型在量化過程中可能會經歷準確性下降。我們正在積極開發此功能,使其對不同類型的模型更加穩健。如果您遇到問題,請在我們的 slack 頻道 上聯繫我們。

概述

Qualcomm® AI Hub Workbench 的量化作業(quantize job)會以未量化的 ONNX 作為輸入,並輸出量化後的 ONNX 模型。即使來源模型是 PyTorch,且您想部署到 TensorFlow Lite 或 Qualcomm® AI Engine Direct,也可以在量化作業之外加入編譯作業(compile jobs),建立端到端的工作流程來達成。我們將逐步示範以下範例:

準備源模型

第一步是追蹤模型並將其編譯為 ONNX 格式。我們建議即使來源模型已經是 ONNX 格式,也進行編譯,因為這樣可以讓編譯器在量化之前運行優化過程。這將確保解決那些可能在量化過程中引發問題的未優化模式。

This step is done using a call to submit_compile_job() with the option --target_runtime onnx. Please refer to 編譯模型 for more information.

import os

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

import qai_hub as hub

client = hub.Client()

# 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 = client.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 模型

The function submit_quantize_job() can be used to quantize an ONNX model. The function takes an ONNX model and calibration data as input, quantizes the model, and returns the quantized ONNX model.

產生的量化模型採用 ONNX 的「假量化(fake quantization)」格式。這是一種量化表示法,其中運算 technically 仍具有浮點輸入/輸出,而量化瓶頸則以 QuantizeLinear/DequantizeLinear 配對單獨表示。此格式類似於靜態 ONNX QDQ 格式(請參考此處),但權重仍以浮點形式儲存,並在後面加上 QuantizeLinear。請注意,這是 Qualcomm® AI Hub Workbench 唯一正式支援作為編譯作業輸入的 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 = client.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 模型中的量化操作將成為目標運行時資產中的量化操作,準備更好地利用可用硬件。

預設情況下,它將輸入和輸出保留在 float32。這可能會在支持整數和浮點數學的平台上增加一些開銷。然而,在完全不支持浮點數學的平台上,這可能會導致更嚴重的問題。為了解決這個問題,我們可以告訴編譯器即使在 IO 邊界也尊重量化,使用 --quantize_io 編譯選項(請參閱 Compile Options)。在這種情況下,整數類型的轉換需要在模型以外的代碼中發生。

# 6. Compile to target runtime (TFLite)
compile_tflite_job = client.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 了解更多信息。

量化選項

下表顯示了每個runtime支持的精度。

權重

激活

混合精度*

TFLite

int8

int8

不支持

QNN

int8

int8, int16

不支持

ONNX

int8

int8, int16

不支持

*混合精度允許在同一網絡中以不同精度運行不同操作。從長遠來看,所有運行時應支持混合精度,除了 int4int8 和 int16 用於權重和激活。

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

比較量化模型性能

有時在獲取樣本輸入數據之前快速比較模型延遲是有用的。對於這種使用情境,校準數據可以是單個隨機樣本。雖然生成的量化模型的準確性會很差,但它的設備延遲與準確模型相同。

import numpy as np

import qai_hub as hub

client = hub.Client()

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 = client.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 = client.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 = client.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 = client.submit_profile_job(
    model=compiled_model,
    device=device,
)
assert isinstance(profile_job, hub.ProfileJob)