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 . 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 :

  1. Quantifier la colonne i : q_i = Quant(w_i)
  2. Calculer l'erreur : e_i = w_i - q_i
  3. Compenser en mettant à jour les colonnes restantes F :
                    e_i
    W_F  ←  W_F  -  ─────────  ·  (H⁻¹)_{i,F}
                  (H⁻¹)_{i,i}

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 :

flowchart TD O1["1. BOUCLE EN ARRIÈRE
(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#

flowchart TD M["MODÈLE FP16 (ex: LLaMA-7B)"] M --> L1["Layer 1"] M --> L2["Layer 2"] M --> L3["Layer 3"] M --> LN["Layer N"] L1 --> Q1["Quantify Layer 1
• 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#

flowchart TD A["Quel modèle à quantifier ?"] --> B["Base model (base/chat)"] A --> C["Instruction-tuned, code, multimodal"] B --> D["✅ GPTQ
(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#

  1. Weight-only — les activations restent en FP16, ce qui limite le speedup par rapport aux méthodes W8A8 comme SmoothQuant.
  2. 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).
  3. Sensibilité au group-size — un mauvais choix dégrade soit la qualité, soit la vitesse.
  4. Sensibilité au calibration set — GPTQ peut surajuster aux données de calibration (moins robuste qu'AWQ).

Références#

ia llm quantification gptq ptq int4 weight-only hessian autogptq