Transformers documentation

Exportations quantifiées

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

Exportations quantifiées

Chaque configuration export peut prendre un quantizer, qui quantise le modèle pendant l’export. Les quantiseurs se trouvent dans transformers.exporters.quantizers, et chacun s’exécute à un point fixe de l’export : sur le graphe FX de torch.export, avant toute conversion, ou sur le modèle dans lequel un backend le convertit.

QuantizerQuantifieDynamoONNXExecuTorchOpenVINO
PT2EQuantizerle graphe FX, avec la quantification PyTorch 2 Export (PT2E)🟢🟢🟢🟢
NNCFTorchFXQuantizerle graphe FX, avec NNCF🟢🟢🟢 (poids uniquement)🟢
OnnxRuntimeQuantizerle modèle ONNX, avec onnxruntime.quantization🔴🟢🔴🔴
NNCFOnnxQuantizerle modèle ONNX, avec NNCF🔴🟢🔴🔴
NNCFOpenVINOQuantizerle modèle OpenVINO, avec NNCF🔴🔴🔴🟢

L’export vérifie le quantificateur par rapport à son backend et aux paquets installés avant le traçage, de sorte qu’une incompatibilité échoue tôt. Comme les exportateurs, chaque quantificateur énumère les versions de paquets sur lesquelles il est testé et émet un avertissement si les vôtres diffèrent.

L'exemple ci-dessous quantifie un classifieur de sentiments en int8 avec ONNX Runtime, puis l'exécute.

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 encapsule un quantificateur PT2E, issu de torchao ou du runtime cible, et l’exécute sur le graphe FX (prepare_pt2e → calibration → convert_pt2e), ce qui lui permet de fonctionner avec tous les backends. Chaque quantificateur PT2E injecte ses propres opérations de quantification/déquantification ; choisissez donc celui pris en charge par votre runtime :

L’endroit où vous l’exécuterezQuantificateur PT2EImporter depuis
PyTorch inductor, ONNX Runtime (QDQ), OpenVINOX86InductorQuantizertorchao.quantization.pt2e.quantizer.x86_inductor_quantizer
Backend XNNPACK d'ExecuTorchXNNPACKQuantizerexecutorch.backends.xnnpack.quantizer.xnnpack_quantizer

Pour ExecuTorch, enveloppez le XNNPACKQuantizer d’ExecuTorch :

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

Quantifiez les modèles génératifs via ~HfExporter.export_for_generation. Ici, calibration_dataset contient les arguments de génération, tels que inputs, et chaque composant calibre sur les entrées capturées pour lui. Un modèle texte uniquement possède un composant prefill et un composant decode ; un modèle multimodal subdivise davantage son prefill, en ses encodeurs de modalité (tels que image_encoder), son projecteur, son language_model et son lm_head (voir Fonctionnement de 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": ...}

Sur OpenVINO, les poids PT2E restent en pleine précision derrière des paires quantize/dequantize, que OpenVINO convertit en FakeQuantize et compresse lui-même en int8.

ONNX Runtime

OnnxRuntimeQuantizer exécute quantize_static par défaut (activations et poids int8 QDQ, calibrés sur calibration_dataset), comme dans l’exemple ci-dessus, avec les arguments d’un OnnxRuntimeStaticConfig. Avec un OnnxRuntimeDynamicConfig, il exécute quantize_dynamic (poids int8, activations quantifiées à l’exécution, sans calibration).

from transformers.exporters import OnnxRuntimeDynamicConfig, OnnxRuntimeStaticConfig

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

NNCF

Les trois quantificateurs NNCF du tableau ci-dessus exécutent par défaut nncf.quantize (activations et poids int8, calibrés sur calibration_dataset, avec model_type=nncf.ModelType.TRANSFORMER sauf si vous en passez un autre), avec les arguments d’une NNCFQuantizationConfig. Avec une NNCFWeightCompressionConfig, ils exécutent nncf.compress_weights (poids uniquement int8 ou int4, avec AWQ et estimation d’échelle pour int4, plus correction GPTQ et LoRA sur OpenVINO). ExecuTorch ne réduit que le résultat poids uniquement de 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)

Les activations Int8 exécutent des matmuls int8, qui saturent sur les CPU sans VNNI (Intel AVX2 uniquement et AMD Zen 2 et antérieurs), il faut donc s’attendre à une précision inférieure sur ces processeurs. La compression uniquement des poids maintient les matmuls en virgule flottante.

Calibration

Le calibration_dataset d’un quantiseur est un itérable d’entrées de modèle, tel qu’une liste de sorties de tokenizer ou un DataLoader dont les lots sont collés en entrées de modèle. C’est un attribut simple, vous pouvez donc également le définir après la création du quantiseur (quantizer.calibration_dataset = samples). S’il est omis, un quantiseur qui a besoin de données se calibre sur les propres entrées d’échantillons de l’export, avec un avertissement, car un seul échantillon peut fausser les plages observées. Les quantiseurs qui n’ont pas besoin de données (quantification dynamique ONNX Runtime, compression de poids int8) ne le lisent jamais.

Une recette différente par composant

Passez un dictionnaire {component: config} à ~HfExporter.export_for_generation pour quantiser chaque composant selon ses propres critères. Pour le modèle ci-dessus, utilisez l'int8 statique sur prefill et l'int8 dynamique plus léger sur decode :

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)

Le dictionnaire doit nommer chaque composant produit par decompose_for_generation() (un modèle multimodal ajoute ses encodeurs et peut séparer language_model et lm_head). Un composant dont la configuration ne définit aucun quantiseur reste en pleine précision. Dans ce dictionnaire, le calibration_dataset d’un quantiseur contient les kwargs de l’appel avant du composant, et non les kwargs de generate.

Ajouter un quantiseur

Les quantiseurs se trouvent dans transformers.exporters.quantizers, avec une classe par chaîne d’outils et par représentation. Sous-classez ExportQuantizer et déclarez :

  • quantizes_fx_graph : True pour le graphe FX de torch.export, avant toute conversion (de sorte que chaque backend reçoit le résultat), ou False (par défaut) pour le modèle dans lequel un backend le convertit (onnx_ir.Model, openvino.Model).
  • supported_formats : les formats d’exportation qu’il quantise. L’exportation rejette tout autre format avant le traçage.
  • required_packages, et éventuellement min_versions et tested_versions : ce qu’il importe. L’export vérifie qu’ils sont installés avant le traçage, lève une erreur si la version est inférieure au minimum requis, et émet un avertissement si la version diffère de celle testée.

Ensuite, implémentez quantize(model, sample_inputs, export_format). model est le graphe FX ou le modèle backend, et sample_inputs sont les propres entrées de l’export. Calibrez sur un CalibrationSet : il lit le calibration_dataset du quantiseur (ou sample_inputs s’il n’y en a pas) et associe chaque échantillon aux propres entrées du modèle à l’aide d’un feed de transformers.exporters.quantizers.utils (traced_inputs pour le graphe FX, onnx_feed pour ONNX, openvino_feed pour OpenVINO).

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