# Quantizing exports

모든 [export](./exporters) 설정은 `quantizer`를 받을 수 있으며, 이는 export 과정에서 모델을 양자화합니다. 양자화기는 `transformers.exporters.quantizers`에 위치하며, 각각 export의 고정된 시점에 실행됩니다. `torch.export` FX 그래프에서 모든 변환 전에 실행되거나, 백엔드가 변환한 모델에서 실행됩니다.

| Quantizer | 양자화 | Dynamo | ONNX | ExecuTorch | OpenVINO |
| --- | --- | --- | --- | --- | --- |
| [PT2EQuantizer](/docs/transformers/main/ko/main_classes/exporters#transformers.exporters.PT2EQuantizer) | PyTorch 2 Export(PT2E) 양자화를 사용한 FX 그래프 | 🟢 | 🟢 | 🟢 | 🟢 |
| [NNCFTorchFXQuantizer](/docs/transformers/main/ko/main_classes/exporters#transformers.exporters.NNCFTorchFXQuantizer) | [NNCF](https://github.com/openvinotoolkit/nncf)를 사용한 FX 그래프 | 🟢 | 🟢 | 🟢 (가중치 전용) | 🟢 |
| [OnnxRuntimeQuantizer](/docs/transformers/main/ko/main_classes/exporters#transformers.exporters.OnnxRuntimeQuantizer) | `onnxruntime.quantization`를 사용한 ONNX 모델 | 🔴 | 🟢 | 🔴 | 🔴 |
| [NNCFOnnxQuantizer](/docs/transformers/main/ko/main_classes/exporters#transformers.exporters.NNCFOnnxQuantizer) | [NNCF](https://github.com/openvinotoolkit/nncf)가 적용된 ONNX 모델 | 🔴 | 🟢 | 🔴 | 🔴 |
| [NNCFOpenVINOQuantizer](/docs/transformers/main/ko/main_classes/exporters#transformers.exporters.NNCFOpenVINOQuantizer) | [NNCF](https://github.com/openvinotoolkit/nncf)가 적용된 OpenVINO 모델 | 🔴 | 🔴 | 🔴 | 🟢 |

export는 tracing 전에 quantizer를 해당 backend 및 설치된 패키지와 대조하여 확인하므로, 불일치가 발생하면 초기 단계에서 실패합니다. exporters와 마찬가지로 각 quantizer는 테스트에 사용된 패키지 버전을 나열하며, 사용 중인 버전이 다를 경우 경고를 표시합니다.

아래 예시에서는 ONNX Runtime을 사용하여 감정 분류기를 int8로 양자화한 후 실행합니다.

```python
from transformers import AutoModelForSequenceClassification, AutoTokenizer
from transformers.exporters import OnnxConfig, OnnxExporter, OnnxRuntimeQuantizer

model_id = "distilbert/distilbert-base-uncased-finetuned-sst-2-english"
tokenizer = AutoTokenizer.from_pretrained(model_id)
model = AutoModelForSequenceClassification.from_pretrained(model_id)

texts = [
    "A wonderful, heartfelt film.",
    "The plot was dull and the acting worse.",
    "I would watch it again tomorrow.",
    "Two hours I will never get back.",
]
calibration_dataset = [tokenizer(text, return_tensors="pt") for text in texts]
inputs = tokenizer("An absolute delight from start to finish.", return_tensors="pt")

config = OnnxConfig(dynamic=True, quantizer=OnnxRuntimeQuantizer(calibration_dataset=calibration_dataset))
onnx_program = OnnxExporter().export(model, inputs, config)

logits = onnx_program(**inputs)[0]
print(model.config.id2label[int(logits.argmax())])  # POSITIVE
```

## PT2E

[PT2EQuantizer](/docs/transformers/main/ko/main_classes/exporters#transformers.exporters.PT2EQuantizer)는 torchao 또는 대상 런타임에서 가져온 PT2E 양자화기를 감싸서 FX 그래프(`prepare_pt2e` → 캘리브레이션 → `convert_pt2e`)에서 실행하므로 모든 백엔드와 호환됩니다. 각 PT2E 양자화기는 자체적인 양자화/역양자화 연산을 삽입하므로, 해당 런타임이 지원하는 양자화기를 선택하세요:

| 실행 위치 | PT2E 양자화기 | 다음에서 가져오기 |
| --- | --- | --- |
| PyTorch 인덕터, ONNX Runtime (QDQ), OpenVINO | `X86InductorQuantizer` | `torchao.quantization.pt2e.quantizer.x86_inductor_quantizer` |
| ExecuTorch XNNPACK 백엔드 | `XNNPACKQuantizer` | `executorch.backends.xnnpack.quantizer.xnnpack_quantizer` |

ExecuTorch의 경우, ExecuTorch 자체의 `XNNPACKQuantizer`를 래핑합니다:

```python
from executorch.backends.xnnpack.quantizer.xnnpack_quantizer import (
    XNNPACKQuantizer,
    get_symmetric_quantization_config,
)
from transformers.exporters import ExecutorchConfig, PT2EQuantizer

xnnpack = XNNPACKQuantizer().set_global(get_symmetric_quantization_config())
config = ExecutorchConfig(backend="xnnpack", quantizer=PT2EQuantizer(xnnpack, calibration_dataset=calibration_dataset))
```

`~HfExporter.export_for_generation`을 통해 생성 모델을 양자화합니다. 여기서 `calibration_dataset`에는 `inputs`와 같은 generate kwargs가 포함되며, 각 구성 요소는 해당 구성 요소에 대해 캡처된 입력으로 보정됩니다. 텍스트 전용 모델에는 `prefill`과 `decode` 구성 요소가 있고, 멀티모달 모델은 prefill을 `image_encoder`와 같은 모달리티 인코더, projector, `language_model`, `lm_head`로 더 세분화합니다([`export_for_generation` 작동 방식](./exporters#how-exportforgeneration-works) 참조).

```python
from torchao.quantization.pt2e.quantizer.x86_inductor_quantizer import (
    X86InductorQuantizer,
    get_default_x86_inductor_quantization_config,
)
from transformers import AutoModelForCausalLM, AutoTokenizer
from transformers.exporters import DynamoConfig, DynamoExporter, PT2EQuantizer

model_id = "meta-llama/Llama-3.2-1B"
tokenizer = AutoTokenizer.from_pretrained(model_id)
model = AutoModelForCausalLM.from_pretrained(model_id)

prompts = ["The capital of France is", "Photosynthesis turns sunlight into", "The first moon landing was in"]
calibration_dataset = [tokenizer(prompt, return_tensors="pt") for prompt in prompts]
inputs = tokenizer("Once upon a time", return_tensors="pt")

x86 = X86InductorQuantizer().set_global(get_default_x86_inductor_quantization_config())
config = DynamoConfig(dynamic=True, quantizer=PT2EQuantizer(x86, calibration_dataset=calibration_dataset))
components = DynamoExporter().export_for_generation(model, inputs, config)  # {"prefill": ..., "decode": ...}
```

OpenVINO에서는 PT2E 가중치가 양자화/역양자화 쌍 뒤에 전체 정밀도로 유지되며, OpenVINO는 이를 `FakeQuantize`로 변환하고 자체적으로 int8로 압축합니다.

## ONNX Runtime

[OnnxRuntimeQuantizer](/docs/transformers/main/ko/main_classes/exporters#transformers.exporters.OnnxRuntimeQuantizer)는 기본적으로 `quantize_static`를 실행합니다(int8 QDQ activations 및 weights, `calibration_dataset`로 calibration). 위 예시와 마찬가지로 [OnnxRuntimeStaticConfig](/docs/transformers/main/ko/main_classes/exporters#transformers.exporters.OnnxRuntimeStaticConfig)의 인수를 사용합니다. [OnnxRuntimeDynamicConfig](/docs/transformers/main/ko/main_classes/exporters#transformers.exporters.OnnxRuntimeDynamicConfig)를 사용하면 `quantize_dynamic`를 실행합니다(int8 weights, 런타임에 quantize되는 activations, calibration 없음).

```python
from transformers.exporters import OnnxRuntimeDynamicConfig, OnnxRuntimeStaticConfig

config = OnnxConfig(
    quantizer=OnnxRuntimeQuantizer(OnnxRuntimeStaticConfig(per_channel=True), calibration_dataset=calibration_dataset)
)
config = OnnxConfig(quantizer=OnnxRuntimeQuantizer(OnnxRuntimeDynamicConfig()))
```

## NNCF

위 표의 세 가지 NNCF 양자화기는 기본적으로 `nncf.quantize`를 실행합니다(활성화 및 가중치 int8, `calibration_dataset`로 보정, `model_type=nncf.ModelType.TRANSFORMER`를 전달하지 않는 한 해당 값 사용)이며, [NNCFQuantizationConfig](/docs/transformers/main/ko/main_classes/exporters#transformers.exporters.NNCFQuantizationConfig)의 인수를 사용합니다. [NNCFWeightCompressionConfig](/docs/transformers/main/ko/main_classes/exporters#transformers.exporters.NNCFWeightCompressionConfig)를 사용하면 `nncf.compress_weights`를 실행합니다(가중치 전용 int8 또는 int4, int4의 경우 AWQ 및 스케일 추정, OpenVINO의 경우 GPTQ 및 LoRA 보정 포함). ExecuTorch는 [NNCFTorchFXQuantizer](/docs/transformers/main/ko/main_classes/exporters#transformers.exporters.NNCFTorchFXQuantizer)의 가중치 전용 결과만 로우합니다.

```python
import nncf
from transformers.exporters import (
    DynamoConfig,
    NNCFOnnxQuantizer,
    NNCFOpenVINOQuantizer,
    NNCFTorchFXQuantizer,
    NNCFWeightCompressionConfig,
    OnnxConfig,
    OpenVINOConfig,
    OpenVINOExporter,
)

int4 = NNCFWeightCompressionConfig(mode=nncf.CompressWeightsMode.INT4_SYM, group_size=128)

# OpenVINO: int8 activations and weights, or weight-only int4
openvino_int8 = OpenVINOConfig(quantizer=NNCFOpenVINOQuantizer(calibration_dataset=calibration_dataset))
openvino_int4 = OpenVINOConfig(quantizer=NNCFOpenVINOQuantizer(int4))
# ONNX: weight-only int4, as ONNX Runtime's `MatMulNBits`
onnx_int4 = OnnxConfig(quantizer=NNCFOnnxQuantizer(int4))
# Dynamo: int8 on the FX graph
dynamo_int8 = DynamoConfig(quantizer=NNCFTorchFXQuantizer(calibration_dataset=calibration_dataset))

ov_model = OpenVINOExporter().export(model, inputs, openvino_int4)
```

Int8 activations는 int8 matmul을 수행하며, VNNI가 없는 CPU(AVX2 전용 Intel 및 AMD Zen 2 이전)에서는 포화(saturation)가 발생하여 정확도가 낮아질 수 있습니다. Weight-only compression은 matmul을 부동소수점으로 유지합니다.

## 교정

Quantizer의 `calibration_dataset`는 모델 입력의 반복 가능한(iterable) 객체로, 토크나이저 출력 목록이나 모델 입력으로 collate되는 배치의 `DataLoader` 등이 해당됩니다. 이는 일반 속성이므로, quantizer 생성 후에도 설정할 수 있습니다(`quantizer.calibration_dataset = samples`). 이 값을 생략하면, 데이터가 필요한 quantizer는 경고와 함께 export 자체의 샘플 입력으로 보정(calibration)을 수행합니다. 이는 단일 샘플이 관찰된 범위를 왜곡시킬 수 있기 때문입니다. 데이터가 필요 없는 quantizer(동적 ONNX Runtime 양자화, int8 가중치 압축)는 이 값을 읽지 않습니다.

## 컴포넌트별 다른 레시피

`{component: config}` 딕셔너리를 `~HfExporter.export_for_generation`에 전달하여 각 구성 요소를 개별적으로 양자화합니다. 위의 모델의 경우, `prefill`에는 정적 int8을, `decode`에는 더 가벼운 동적 int8을 사용합니다:

```python
dynamic_x86 = X86InductorQuantizer().set_global(get_default_x86_inductor_quantization_config(is_dynamic=True))

config = {
    "prefill": DynamoConfig(dynamic=True, quantizer=PT2EQuantizer(x86)),
    "decode": DynamoConfig(dynamic=True, quantizer=PT2EQuantizer(dynamic_x86)),
}
components = DynamoExporter().export_for_generation(model, inputs, config)
```

dict에는 [decompose_for_generation()](/docs/transformers/main/ko/main_classes/exporters#transformers.exporters.utils.decompose_for_generation)이 생성하는 모든 구성 요소를 명시해야 합니다(멀티모달 모델은 인코더를 추가하고, `language_model`과 `lm_head`를 분리할 수 있습니다). config에 quantizer가 설정되지 않은 구성 요소는 전체 정밀도로 유지됩니다. 이 dict에서 quantizer의 `calibration_dataset`에는 generate kwargs가 아니라 해당 구성 요소의 forward kwargs가 포함됩니다.

## 양자화기 추가

Quantizer는 `transformers.exporters.quantizers`에 있으며, 도구 체인 및 표현 방식별로 하나의 클래스씩 정의되어 있습니다. [ExportQuantizer](/docs/transformers/main/ko/main_classes/exporters#transformers.exporters.ExportQuantizer)를 서브클래스로 선언하고 다음을 명시하세요:

- `quantizes_fx_graph`: `torch.export` FX 그래프에 대해 `True` (변환 전, 즉 모든 백엔드가 결과를 받음) 또는 백엔드가 변환하는 모델(`onnx_ir.Model`, `openvino.Model`)에 대해 `False`(기본값).
- `supported_formats`: 양자화할 내보내기 형식입니다. 트레이싱 전에 다른 형식은 모두 거부됩니다.
- `required_packages`, 그리고 선택적으로 `min_versions`와 `tested_versions`: 모듈이 import하는 패키지입니다. export는 tracing 전에 해당 패키지가 설치되어 있는지 확인하고, 최소 버전 미만이면 예외를 발생시키며, 테스트된 버전과 다르면 경고를 표시합니다.

이후 `quantize(model, sample_inputs, export_format)`를 구현합니다. `model`은 FX 그래프 또는 백엔드 모델이고, `sample_inputs`는 export 자체의 입력입니다. [CalibrationSet](/docs/transformers/main/ko/main_classes/exporters#transformers.exporters.quantizers.CalibrationSet)에서 보정(calibrate)합니다. 이는 quantizer의 `calibration_dataset`(없을 경우 `sample_inputs`)를 읽고, `transformers.exporters.quantizers.utils`의 feed를 통해 각 샘플을 모델 자체의 입력으로 매핑합니다(FX 그래프의 경우 `traced_inputs`, ONNX의 경우 `onnx_feed`, OpenVINO의 경우 `openvino_feed`).

```python
from transformers.exporters import ExportFormat
from transformers.exporters.quantizers import CalibrationSet, ExportQuantizer
from transformers.exporters.quantizers.utils import onnx_feed

class MyOnnxQuantizer(ExportQuantizer):
    supported_formats = (ExportFormat.ONNX,)
    required_packages = ("my_toolkit",)
    tested_versions = {"my_toolkit": "1.0.0"}

    def quantize(self, model, sample_inputs, export_format):
        import my_toolkit

        names = [graph_input.name for graph_input in model.graph.inputs]
        calibration = CalibrationSet(self.calibration_dataset, sample_inputs, lambda sample: onnx_feed(names, sample))
        return my_toolkit.quantize(model, calibration_feeds=list(calibration))
```

