GPTQ (GPT Quantization)#
GPTQ est une méthode de quantification Post-Training weight-only qui utilise l'information de second ordre (matrice de Hesse) pour quantifier les poids couche par couche avec compensation d'erreur itérative. Elle permet de compresser un modèle de 175B paramètres en INT4 sur un seul GPU en ~4 heures.
Le problème : quantifier 175B sans ré-entraînement#
Quantifier un LLM en INT4 signifie que chaque poids, codé sur 16 bits (FP16), est compressé à 4 bits — un facteur 4×. Mais si l'on arrondit chaque poids naïvement, l'erreur accumulée à travers 70 couches détruit complètement le modèle.
GPTQ résout ce problème en utilisant une idée issue de l'élagage de réseaux de neurones (Optimal Brain Surgeon / Optimal Brain Damage) : quand on quantifie un poids, on peut compenser l'erreur introduite en ajustant les poids restants, en utilisant la matrice de Hesse pour savoir quels poids ajuster et de combien.
Niveau néophyte : l'analogie#
Imaginez que vous devez traduire un livre dans un alphabet très limité (seulement 16 lettres au lieu de 64). Si vous traduisez chaque lettre une par une sans réfléchir, le texte devient incompréhensible. GPTQ procède lettre par lettre, mais à chaque fois qu'il traduit une lettre, il regarde les lettres déjà traduites autour et les ajuste légèrement pour que le sens global soit préservé. La matrice de Hesse est comme un dictionnaire qui lui dit quelles lettres sont importantes et comment les ajuster.
Niveau technique : l'algorithme étape par étape#
Fondation théorique : OBQ (Optimal Brain Quantization)#
GPTQ s'inspire d'OBQ, qui quantifie les poids colonne par colonne dans une matrice de poids W. À chaque étape :
- Quantifier la colonne
i:q_i = Quant(w_i) - Calculer l'erreur :
e_i = w_i - q_i - Compenser en mettant à jour les colonnes restantes
F:
e_i
W_F ← W_F - ───────── · (H⁻¹)_{i,F}
(H⁻¹)_{i,i}
Où H est la matrice de Hesse (H = 2 · X^T · X, X étant les entrées de calibration).
Optimisations de GPTQ pour la scalabilité#
OBQ est trop lent pour les LLMs (O(n⁴) ou pire). GPTQ introduit trois optimisations clés :
(lazy batch updates)
Mise à jour par BATCH de colonnes
(ex: 128) au lieu de chaque colonne
→ Réduit les accès mémoire"] O2["2. INVERSION DE CHOLESKY
Calcul de H⁻¹ via décomposition de Cholesky
→ Stable numériquement + rapide O(n³/3)"] O3["3. TRAITEMENT PAR BLOCS
H approximée par blocs diagonaux
→ Scalable aux très grandes matrices"]
Algorithme complet (pseudo-code)#
# Input: W (matrice de poids, d_in × d_out)
# X (données de calibration, n × d_in)
# group_size (ex: 128)
# 1. Calculer la Hessienne
H = 2 * X.T @ X # d_in × d_in
H += lambda * I # régularisation (stabilité)
# 2. Inverser H via Cholesky
H_inv = cholesky_inverse(H)
# 3. Quantifier colonne par colonne (en batches)
for i in range(0, d_in, batch_size):
# Quantifier le batch de colonnes [i : i+batch_size]
Q[:, i:i+batch_size] = quantize(W[:, i:i+batch_size], group_size)
# Calculer l'erreur
Err = W[:, i:i+batch_size] - Q[:, i:i+batch_size]
# Compenser les colonnes RESTANTES (lazy update)
W[:, i+batch_size:] -= (
Err @ H_inv[i:i+batch_size, i+batch_size:]
) / diag(H_inv[i:i+batch_size, i:i+batch_size])
# 4. Résultat: Q est la matrice quantifiée compensée
return Q
Diagramme du processus layer-by-layer#
• Calc H • Inv H
• Quant • Compens"] L2 --> Q2["Quantify Layer 2
• Calc H • Inv H
• Quant • Compens"] L3 --> Q3["Quantify Layer 3
• Calc H • Inv H
• Quant • Compens"] LN --> QN["Quantify Layer N
• Calc H • Inv H
• Quant • Compens"] Q1 --> R["MODÈLE INT4 QUANTIFIÉ"] Q2 --> R Q3 --> R QN --> R
Caractéristiques#
| Propriété | Valeur |
|---|---|
| Type | Post-Training Quantization (PTQ) weight-only |
| Précisions | INT4 (principal), INT3, INT2 (extrême) |
| Activations | Restent en FP16 (non quantifiées) |
| Group-size | Configurable (typiquement 128) |
| Données de calibration | 128–1024 échantillons |
| Temps de quantification | ~4 GPU-heures pour 175B |
| Speedup à l'inférence | 3.25× (A100) à 4.5× (A6000) |
| Accuracy INT4 | Quasi identique au FP16 |
Exemple pratique avec AutoGPTQ#
Installation#
pip install auto-gptq optimum
# Pour le support GPU avec kernels optimisés :
pip install auto-gptq[triton] # CUDA/Triton
# ou
pip install auto-gptq[accelerate] # CPU/offload
Quantification avec AutoGPTQ#
from auto_gptq import AutoGPTQForCausalLM, BaseQuantizeConfig
from transformers import AutoTokenizer
# 1. Configuration de la quantification
quantize_config = BaseQuantizeConfig(
bits=4, # INT4
group_size=128, # group-size standard
desc_act=False, # True = ordre par importance (plus lent, + précis)
)
# 2. Charger le modèle et le tokenizer
model_path = "meta-llama/Llama-2-7B-hf"
tokenizer = AutoTokenizer.from_pretrained(model_path)
# 3. Préparer les données de calibration
train_dataset = tokenizer(
["Votre texte de calibration ici..."] * 128,
return_tensors="pt",
padding=True,
truncation=True,
max_length=512,
)
# 4. Quantifier
model = AutoGPTQForCausalLM.from_pretrained(model_path, quantize_config)
model.quantize(train_dataset["input_ids"])
# 5. Sauvegarder
model.save_quantized("./llama-2-7b-gptq-4bit")
tokenizer.save_pretrained("./llama-2-7b-gptq-4bit")
Inférence avec vLLM#
# vLLM supporte nativement les modèles GPTQ
python -m vllm.entrypoints.openai.api_server \
--model ./llama-2-7b-gptq-4bit \
--quantization gptq \
--dtype float16
Quantification en ligne de commande (one-liner)#
# Avec le Hugging Face Optimum
optimum-cli export onnx \
--model meta-llama/Llama-2-7B-hf \
--task text-generation \
--quantize gptq \
--bits 4 \
--group-size 128 \
./llama-2-7b-gptq
Comparaison GPTQ vs AWQ#
| Critère | GPTQ | AWQ |
|---|---|---|
| Approche | Compensation d'erreur via Hessienne | Protection des canaux saillants par scaling |
| Précision INT4 | Excellente | Légèrement supérieure (généralisation) |
| Overfitting calibration | Possible (sensibilité au dataset) | Robuste (ne s'appuie pas sur reconstruction) |
| Vitesse de quantification | Rapide (~4 GPU-h pour 175B) | Légèrement plus lent |
| Vitesse d'inférence | Bonne (déquant + GEMM) | Bonne (scaling + déquant + GEMM) |
| Meilleur pour | Speed de quantification, sub-4-bit | Qualité sur instruction-tuned / multimodal |
| Kernels optimisés | GPTQ-Marlin, ExLlamaV2 | Marlin, TensorRT-LLM |
| Format de sortie | .safetensors GPTQ |
.safetensors AWQ |
| Papier | arXiv:2210.17323 | arXiv:2306.00978 |
Règle pratique de choix#
(rapide, efficace)"] C --> E["✅ AWQ
(meilleure généralisation)"]
Impact du group-size#
| Group-size | Bits/effective | Accuracy | Vitesse inférence |
|---|---|---|---|
| -1 (per-col) | ~4.00 | ★★★★★ | ★★★★★ (rapide) |
| 1024 | ~4.13 | ★★★★☆ | ★★★★☆ |
| 128 (défaut) | ~4.25 | ★★★★★ | ★★★☆☆ |
| 64 | ~4.50 | ★★★★★ | ★★☆☆☆ (plus lent) |
| 32 | ~5.00 | ★★★★★ | ★☆☆☆☆ (overhead) |
Règle : group-size = 128 est le sweet spot universel pour GPTQ. Plus petit = plus précis mais plus lent à l'inférence (plus de métadonnées de scaling).
Modèles testés et résultats#
| Modèle | Bits | Perplexity (FP16) | Perplexity (quant.) | Loss |
|---|---|---|---|---|
| OPT-175B | INT3 | 8.34 | 8.92 | +0.58 |
| OPT-175B | INT4 | 8.34 | 8.37 | +0.03 |
| LLaMA-7B | INT4 | 5.68 | 5.71 | +0.03 |
| LLaMA-65B | INT4 | 3.53 | 3.55 | +0.02 |
Limitations#
- Weight-only — les activations restent en FP16, ce qui limite le speedup par rapport aux méthodes W8A8 comme SmoothQuant.
- Overhead de déquantification — à l'inférence, les poids INT4 doivent être déquantifiés avant chaque GEMM (mitigé par des kernels comme GPTQ-Marlin).
- Sensibilité au group-size — un mauvais choix dégrade soit la qualité, soit la vitesse.
- Sensibilité au calibration set — GPTQ peut surajuster aux données de calibration (moins robuste qu'AWQ).
Références#
- Papier arXiv : GPTQ: Accurate Post-Training Quantization for Generative Pre-trained Transformers (Frantar et al., ICLR 2023)
- AutoGPTQ : github.com/AutoGPTQ/AutoGPTQ
- GPTQ-for-LLaMa : github.com/qwopqwop200/GPTQ-for-LLaMa
- ExLlamaV2 : github.com/turboderp/exllamav2
- Voir aussi : AWQ · SmoothQuant · Index quantification