量化

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 格式,也進行編譯,因為這樣可以讓編譯器在量化之前運行優化過程。這將確保解決那些可能在量化過程中引發問題的未優化模式。

此步驟是使用調用 submit_compile_job() 並選擇 --target_runtime onnx 完成的。請參閱 編譯模型 了解更多信息。

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 的「假量化(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 = 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 模型中的量化操作將成為目標運行時資產中的量化操作,準備更好地利用可用硬件。

預設情況下,它將輸入和輸出保留在 float32。這可能會在支持整數和浮點數學的平台上增加一些開銷。然而,在完全不支持浮點數學的平台上,這可能會導致更嚴重的問題。為了解決這個問題,我們可以告訴編譯器即使在 IO 邊界也尊重量化,使用 --quantize_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 了解更多信息。

量化選項

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

權重

激活

混合精度*

TFLite

int8

int8

不支持

QNN

int8

int8, int16

不支持

ONNX

int8

int8, int16

不支持

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

請參閱 submit_quantize_job()Quantize 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)