Transformers documentation
Exportations quantifiées
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.
| Quantizer | Quantifie | Dynamo | ONNX | ExecuTorch | OpenVINO |
|---|---|---|---|---|---|
| PT2EQuantizer | le graphe FX, avec la quantification PyTorch 2 Export (PT2E) | 🟢 | 🟢 | 🟢 | 🟢 |
| NNCFTorchFXQuantizer | le graphe FX, avec NNCF | 🟢 | 🟢 | 🟢 (poids uniquement) | 🟢 |
| OnnxRuntimeQuantizer | le modèle ONNX, avec onnxruntime.quantization | 🔴 | 🟢 | 🔴 | 🔴 |
| NNCFOnnxQuantizer | le modèle ONNX, avec NNCF | 🔴 | 🟢 | 🔴 | 🔴 |
| NNCFOpenVINOQuantizer | le 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())]) # POSITIVEPT2E
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écuterez | Quantificateur PT2E | Importer depuis |
|---|---|---|
| PyTorch inductor, ONNX Runtime (QDQ), OpenVINO | X86InductorQuantizer | torchao.quantization.pt2e.quantizer.x86_inductor_quantizer |
| Backend XNNPACK d'ExecuTorch | XNNPACKQuantizer | executorch.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:Truepour le graphe FX detorch.export, avant toute conversion (de sorte que chaque backend reçoit le résultat), ouFalse(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 éventuellementmin_versionsettested_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))