Transformers documentation

エクスポートの量子化

You are viewing main version, which requires installation from source. If you'd like regular pip install, checkout the latest stable version (v5.22.1).
Hugging Face's logo
Join the Hugging Face community

and get access to the augmented documentation experience

to get started

エクスポートの量子化

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

Quantizer量子化DynamoONNXExecuTorchOpenVINO
PT2EQuantizerPyTorch 2 Export (PT2E) 量子化を用いた FX グラフ🟢🟢🟢🟢
NNCFTorchFXQuantizerNNCF を使用した FX グラフ🟢🟢🟢(重みのみ)🟢
OnnxRuntimeQuantizeronnxruntime.quantization を使用した ONNX モデル🔴🟢🔴🔴
NNCFOnnxQuantizerNNCF を使用した ONNX モデル🔴🟢🔴🔴
NNCFOpenVINOQuantizerNNCF を使用した OpenVINO モデル🔴🔴🔴🟢

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

以下の例では、ONNX Runtime を使用して感情分類モデルを int8 に量子化し、その後実行します。

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 は、torchao またはターゲットランタイム由来の PT2E クアンタイザーをラップし、FX グラフ上で実行します(prepare_pt2e → カリブレーション → convert_pt2e)。そのため、すべてのバックエンドで動作します。各 PT2E クアンタイザーは独自のクアンタイズ/デクアンタイズ演算を挿入するため、お使いのランタイムがサポートしているものを選択してください。

実行場所PT2E量子化器インポート元
PyTorch inductor、ONNX Runtime (QDQ)、OpenVINOX86InductorQuantizertorchao.quantization.pt2e.quantizer.x86_inductor_quantizer
ExecuTorch XNNPACK バックエンドXNNPACKQuantizerexecutorch.backends.xnnpack.quantizer.xnnpack_quantizer

ExecuTorchでは、ExecuTorch独自のXNNPACKQuantizerをラップします:

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 の仕組み を参照)。

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 は、デフォルトで quantize_static を実行します(int8 QDQ アクティベーションと重み、calibration_dataset によるキャリブレーション)。上記の例と同様に、OnnxRuntimeStaticConfig の引数を使用します。OnnxRuntimeDynamicConfig を使用すると、quantize_dynamic が実行されます(int8 重み、アクティベーションはランタイムで量子化、キャリブレーションなし)。

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を使用します。NNCFWeightCompressionConfigを使用すると、nncf.compress_weightsを実行します(重みだけのint8またはint4、int4の場合はAWQとスケール推定、OpenVINOではGPTQとLoRA補正)。ExecuTorchは、NNCFTorchFXQuantizerの重みだけの結果のみをローワーします。

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 を適用します。

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() が生成するすべてのコンポーネントを指定する必要があります(マルチモーダルモデルではエンコーダーが追加され、language_model と lm_head が分離される場合があります)。config でクオンタイザーが設定されていないコンポーネントは、フル精度のまま保持されます。この dict において、クオンタイザーの calibration_dataset には、generate の kwargs ではなく、コンポーネントの forward の kwargs が格納されます。

量子化器を追加

Quantizer は transformers.exporters.quantizers に存在し、ツールチェーンと表現ごとに1クラスずつ用意されています。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 でキャリブレーションを実行します。これは、クオンタイザーの calibration_dataset(存在しない場合は sample_inputs)を読み取り、transformers.exporters.quantizers.utils のフィード(FX グラフの場合は traced_inputs、ONNX の場合は onnx_feed、OpenVINO の場合は openvino_feed)を使用して、各サンプルをモデル自身の入力にマッピングします。

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))
Update on GitHub