# エクスポートの量子化

すべての [export](./exporters) 設定は `quantizer` を受け取れます。これはエクスポート中にモデルを量子化します。量子化器は `transformers.exporters.quantizers` に配置されており、それぞれがエクスポートの固定のタイミングで実行されます。具体的には、`torch.export` の FX グラフ上で変換を行う前、またはバックエンドが変換後のモデル上で実行されます。

| Quantizer | 量子化 | Dynamo | ONNX | ExecuTorch | OpenVINO |
| --- | --- | --- | --- | --- | --- |
| [PT2EQuantizer](/docs/transformers/main/ja/main_classes/exporters#transformers.exporters.PT2EQuantizer) | PyTorch 2 Export (PT2E) 量子化を用いた FX グラフ | 🟢 | 🟢 | 🟢 | 🟢 |
| [NNCFTorchFXQuantizer](/docs/transformers/main/ja/main_classes/exporters#transformers.exporters.NNCFTorchFXQuantizer) | [NNCF](https://github.com/openvinotoolkit/nncf) を使用した FX グラフ | 🟢 | 🟢 | 🟢（重みのみ） | 🟢 |
| [OnnxRuntimeQuantizer](/docs/transformers/main/ja/main_classes/exporters#transformers.exporters.OnnxRuntimeQuantizer) | `onnxruntime.quantization` を使用した ONNX モデル | 🔴 | 🟢 | 🔴 | 🔴 |
| [NNCFOnnxQuantizer](/docs/transformers/main/ja/main_classes/exporters#transformers.exporters.NNCFOnnxQuantizer) | [NNCF](https://github.com/openvinotoolkit/nncf) を使用した ONNX モデル | 🔴 | 🟢 | 🔴 | 🔴 |
| [NNCFOpenVINOQuantizer](/docs/transformers/main/ja/main_classes/exporters#transformers.exporters.NNCFOpenVINOQuantizer) | [NNCF](https://github.com/openvinotoolkit/nncf) を使用した OpenVINO モデル | 🔴 | 🔴 | 🔴 | 🟢 |

エクスポートでは、トレースの実行前に、クオンタイザーがバックエンドおよびインストール済みパッケージと整合しているかをチェックするため、不一致がある場合は早期に失敗します。エクスポートツールと同様に、各クオンタイザーはテスト済みのパッケージバージョンを列挙しており、ユーザーの環境が異なる場合は警告を表示します。

以下の例では、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/ja/main_classes/exporters#transformers.exporters.PT2EQuantizer) は、torchao またはターゲットランタイム由来の PT2E クオンタイザーをラップし、FX グラフ上で実行します（`prepare_pt2e` → 較正 → `convert_pt2e`）。そのため、すべてのバックエンドで動作します。各 PT2E クオンタイザーは独自の quantize/dequantize 演算子を挿入するため、お使いのランタイムがサポートしているものを選択してください。

| 実行場所 | PT2E量子化器 | インポート元 |
| --- | --- | --- |
| PyTorch inductor、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 の引数を持ち、各コンポーネントは、そのコンポーネント用にキャプチャされた入力でキャリブレーションを行います。テキストのみのモデルには `prefill` と `decode` のコンポーネントがあり、マルチモーダルモデルでは prefill がさらに、モダリティエンコーダー（`image_encoder` など）、プロジェクター、`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 の重みは quantize/dequantize のペアの背後でフル精度のまま保持され、OpenVINO がこれを `FakeQuantize` に変換し、独自に int8 に圧縮します。

## ONNX Runtime

[OnnxRuntimeQuantizer](/docs/transformers/main/ja/main_classes/exporters#transformers.exporters.OnnxRuntimeQuantizer) は、デフォルトで `quantize_static` を実行します（`calibration_dataset` でキャリブレーションされた int8 QDQ アクティベーションと重み）。上記の例と同様に、[OnnxRuntimeStaticConfig](/docs/transformers/main/ja/main_classes/exporters#transformers.exporters.OnnxRuntimeStaticConfig) の引数を使用します。[OnnxRuntimeDynamicConfig](/docs/transformers/main/ja/main_classes/exporters#transformers.exporters.OnnxRuntimeDynamicConfig) を使用すると、`quantize_dynamic` が実行されます（int8 重み、アクティベーションはランタイムで量子化、キャリブレーションなし）。

```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

上記の表にある3つのNNCF量子化器は、デフォルトで`nncf.quantize`を実行します（活性化と重みのint8、`calibration_dataset`でのキャリブレーション、`model_type=nncf.ModelType.TRANSFORMER`を渡さない限りこの値が使用されます）。引数には[NNCFQuantizationConfig](/docs/transformers/main/ja/main_classes/exporters#transformers.exporters.NNCFQuantizationConfig)の引数が使用されます。[NNCFWeightCompressionConfig](/docs/transformers/main/ja/main_classes/exporters#transformers.exporters.NNCFWeightCompressionConfig)を使用すると、`nncf.compress_weights`を実行します（重みだけのint8またはint4、int4の場合はAWQとスケール推定、OpenVINOではGPTQとLoRA補正）。ExecuTorchは、[NNCFTorchFXQuantizer](/docs/transformers/main/ja/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 以前）では飽和が発生するため、これらの環境では精度が低下する可能性があります。Weight-only compression は matmul を浮動小数点のまま維持します。

## キャリブレーション

量子化器の`calibration_dataset`は、モデルの入力（トークナイザーの出力のリストや、バッチがモデルの入力にコラートされる`DataLoader`など）のイテラブルです。これは通常の属性であるため、量子化器の作成後に設定することもできます（`quantizer.calibration_dataset = samples`）。省略した場合、データが必要な量子化器は、警告を表示しつつ、エクスポート自身のサンプル入力でキャリブレーションを行います。これは、1つのサンプルだけで観測される範囲が偏る可能性があるためです。データが不要な量子化器（動的 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/ja/main_classes/exporters#transformers.exporters.utils.decompose_for_generation) が生成するすべてのコンポーネントを指定する必要があります（マルチモーダルモデルではエンコーダーが追加され、`language_model` と `lm_head` が分離される場合があります）。config でクオンタイザーが設定されていないコンポーネントは、フル精度のまま保持されます。この dict において、クオンタイザーの `calibration_dataset` には、generate の kwargs ではなく、コンポーネントの forward の kwargs が保持されます。

## 量子化器を追加

Quantizer は `transformers.exporters.quantizers` に存在し、ツールチェーンと表現ごとに1クラスずつ用意されています。[ExportQuantizer](/docs/transformers/main/ja/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`：インポートするパッケージを指定します。エクスポートでは、トレース前にそれらがインストールされていることを確認し、最低バージョン未満の場合は例外を送出し、テスト済みバージョンと異なる場合は警告を表示します。

次に、`quantize(model, sample_inputs, export_format)` を実装します。`model` は FX グラフまたはバックエンドモデルであり、`sample_inputs` はエクスポート自身の入力です。[CalibrationSet](/docs/transformers/main/ja/main_classes/exporters#transformers.exporters.quantizers.CalibrationSet) でキャリブレーションを実行します。これは、クオンタイザーの `calibration_dataset`（存在しない場合は `sample_inputs`）を読み取り、`transformers.exporters.quantizers.utils` のフィード（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))
```

