量化

Qualcomm® AI Hub 允許將浮點模型轉換為定點模型,這個過程稱為 量化。定點表示將實數映射到整數(例如,int8、int16),允許更快且更高效的內存推理。量化模型可以編譯到 Qualcomm® AI Hub 支持的所有目標runtime,性能最多可提高3倍。Snapdragon® Hexagon Tensor Processor 在量化操作中表現最佳。

為了在保持模型準確性的同時捕捉這些性能改進,量化模型需要使用未標記的樣本輸入數據進行校準。校準是確定浮點和其量化整數表示之間的定點映射(比例和零點)的過程。使用未量化的來源模型和校準數據,Qualcomm® AI Hub 生成可以在設備上運行的量化模型資產。

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

概述

Qualcomm® AI Hub 量化工作接受未量化的 ONNX 作為輸入,並生成量化的 ONNX 模型作為輸出。即使來源模型是 PyTorch 並且您希望將其部署到 TensorFlow Lite 或 Qualcomm® AI Engine Direct,也可以通過構建包含編譯工作的點到點工作流程來使用量化工作。我們將通過以下範例進行演示:

準備源模型

第一步是追蹤模型並將其編譯為 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 "假量化" 格式。這是一種量化表示,其中操作技術上具有浮點輸入/輸出,量化瓶頸分別由 QuantizeLinear/DequantizeLinear 對表示。這類似於靜態 ONNX QDQ 格式 here,除了權重仍然存儲為浮點,然後是 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 模型中的量化操作將成為目標運行時資產中的量化操作,準備更好地利用可用硬件。

預設情況下,它將輸入和輸出保留在 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)