양자화 (Quantization)

Qualcomm® AI Hub 는 양자화 라고 불리는 프로세스를 통해 부동 소수점 모델을 고정 소수점으로 변환할 수 있습니다. 고정 소수점 표현은 실수를 정수(e.g., 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 “가짜 양자화” 형식입니다. 이는 연산이 기술적으로 부동 소수점 입력/출력을 가지며 양자화 병목 현상이 QuantizeLinear/DequantizeLinear 쌍으로 별도로 표현되는 양자화 표현입니다. 이는 여기 의 정적 ONNX QDQ 형식과 유사하지만, 가중치는 여전히 부동 소수점으로 저장된 후 QuantizeLinear가 뒤따릅니다. 이는 Qualcomm® AI Hub 가 컴파일 작업의 입력으로 공식적으로 지원하는 유일한 ONNX 양자화 형식입니다.

보정 데이터로 imagenette_samples.zip 을 사용합니다. 파일을 다운로드하고 아래 코드를 실행하기 전에 로컬 디렉토리에 압축을 풉니다. 이 튜토리얼에서는 100개의 샘플을 사용합니다. 일반적으로 500-1000개의 샘플을 사용하는 것이 좋습니다.

위의 예제를 계속하는 이 예제에서는 8비트 정수 가중치와 8비트 정수 활성화(i.e., 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로 남습니다. 이는 정수 및 부동 소수점 수학을 모두 지원하는 플랫폼에서 약간의 오버헤드를 추가할 수 있습니다. 그러나 부동 소수점 수학을 전혀 지원하지 않는 플랫폼에서는 더 심각한 문제가 발생할 수 있습니다. 이를 해결하기 위해 --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

지원되지 않음

*혼합 정밀도는 동일한 네트워크에서 다른 정밀도로 다른 연산을 실행할 수 있습니다. 장기적으로 모든 런타임은 가중치 및 활성화에 대해 int4, int8int16 외에도 혼합 정밀도를 지원해야 합니다.

추가 옵션에 대해서는 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)