OmniQuant (Omnidirectionally Calibrated Quantization)#

OmniQuant améliore les méthodes PTQ existantes en rendant apprenables (learnable) les paramètres qui étaient auparavant fixés manuellement : le clipping threshold des poids (LWC) et les paramètres de transformation équivalente (LET). Cette approche différenciable couvre toutes les configurations — de W2A16 (2-bit weight-only) à W6A6 — avec une qualité supérieure aux heuristiques fixes.


Le problème : les paramètres fixes des PTQ classiques#

Pour un néophyte#

Les méthodes de quantification PTQ classiques (GPTQ, AWQ, SmoothQuant) utilisent des règles empiriques pour fixer leurs paramètres :

mindmap root((PTQ classiques
paramètres fixes)) SmoothQuant α = 0.5 fixé à la main GPTQ group-size = 128 fixé AWQ Grid search du scale

→ Que se passe-t-il si on LAISSE le modèle CHERCHER les meilleurs paramètres ? C'est l'idée d'OmniQuant ! ✅

Analogie : régler une radio. SmoothQuant (manuel) → Tu tournes le bouton à mi-chemin (α=0.5) parce que « ça marche en général ». OmniQuant (apprenable) → Le bouton s'ajoute TOUT SEUL pour chaque station (chaque couche du modèle). Trouve le réglage OPTIMAL, pas juste « correct ».

Le problème technique en détail#

Quantification PTQ standard : Y = X · W (multiplication matricielle). Quantifier W : Q(W) = Clip(W, -τ, τ) / s puis Round, où τ = clipping threshold (seuil) et s = scale = τ / (2^(b-1) - 1).

Le seuil τ contrôle TOUT :
- τ petit → haute résolution mais écrête les valeurs extrêmes (clipping error)
- τ grand → pas d'écrêtage mais faible résolution (rounding error)

Les PTQ classiques fixent τ par heuristique → sous-optimal !


Les deux piliers d'OmniQuant#

1. Learnable Weight Clipping (LWC)#

Au lieu d'un seuil de clipping fixe, LWC optimise le seuil de clipping des poids de manière différentiable :

flowchart LR subgraph FIXED["Clipping STANDARD (fixe)"] F1["Distribution des poids"] --> F2["Seuil FIXE τ = max|W|
→ petits outliers écrêtés"] end subgraph LWC["Clipping LWC (apprenable)"] L1["Distribution des poids"] --> L2["Seuil OPTIMAL τ* (appris)
→ meilleur trade-off trouvé"] end

Implémentation différentiable : Au lieu de Clip(W, τ), on utilise Q(W; γ) = tanh(γ · τ) · Round(W / tanh(γ · τ)) où γ est un paramètre APPRENABLE (gradient descent). γ = 1 : pas de clipping (= max|W|). γ < 1 : clipping agressif (réduit la range). Optimisé par block-wise error minimization.

2. Learnable Equivalent Transformation (LET)#

LET étend l'idée de SmoothQuant en rendant apprenables les paramètres de transformation équivalente :

    SmoothQuant (transformation fixe) :
    Y = (X · diag(1/s)) · (diag(s) · W)
    où s_j = max(|X_j|)^α / max(|W_j|)^(1-α)
    α fixé (typiquement 0.5)

    OmniQuant LET (transformation apprenable) :
    Y = (X · diag(1/s*)) · (diag(s*) · W + b*)
    où s* = paramètre de scaling APPRENABLE
          b* = paramètre de shifting APPRENABLE

    → Le modèle APPREND les meilleures transformations
      pour chaque couche, au lieu d'utiliser une heuristique
flowchart TD subgraph SQ["SmoothQuant (transformation fixe)"] SQX["X"] --> SQX2["÷s(α=0.5)"] --> SQX3["X'"] SQW["W"] --> SQW2["×s(α=0.5)"] --> SQW3["W' → Quant(W')"] SQN["α = 0.5 POUR TOUTES les couches
→ sous-optimal"] end subgraph OQ["OmniQuant LET (transformation apprenable)"] OQX["X"] --> OQX2["÷s₁* + b₁*"] --> OQX3["X'"] OQW["W"] --> OQW2["×s₁* - b₁*"] --> OQW3["W' → Quant(W')"] OQN["Couche 1: s₁*, b₁* optimisés
Couche 2: s₂*, b₂* optimisés
Couche 3: s₃*, b₃* optimisés
→ chaque couche a SA transformation optimale"] end

Diagramme du process OmniQuant#

flowchart TD M["1. Modèle FP16 (gelé) + 128 samples calibration"] --> INIT INIT["2. Initialisation des paramètres apprenables
LWC: γ₀ (clipping init)
LET: s₀, b₀ (smooth init)"] --> BLOCK subgraph BLOCK["3. Block-wise Error Minimization (par bloc transformer)"] FW["Forward pass (bloc)
X → LET(X, s*, b*) → X'
W → LET(W, s*, b*) → W'
W' → LWC_Quant(W', γ*) → Q
Y' = X' · Q"] --> LOSS LOSS["Loss = MSE(Y', Y_FP16)
Backprop → update γ*, s*, b*
(modèle FP16 reste GELÉ)"] LOSS --> FW end BLOCK --> FINAL FINAL["4. Quantification finale
Appliquer γ*, s*, b* optimaux"] --> RESULT RESULT["5. Modèle quantifié final
W2A16, W3A16, W4A16, W4A4, W6A6...
avec paramètres optimaux par couche"]

Caractéristiques#

Propriété Valeur
Type Post-Training Quantization (PTQ) différenciable
Configurations W2A16, W3A16, W4A16, W4A4, W6A6
Paramètres apprenables Clipping (LWC) + Transformation (LET)
Optimisation Block-wise error minimization
Calibration 128 samples
Temps de quantif. 1-16h sur A100-40G (LLaMA-2 7-70B)
Backprop requise ✅ Oui (mais modèle gelé)
Activation quantif. ✅ Oui (W4A4, W6A6)

Comparaison avec GPTQ / AWQ#

Critère OmniQuant GPTQ AWQ SmoothQuant
Approche Paramètres apprenables Compensation Hessienne Scaling saillant Migration fixe (α)
Différentiable ⭐ Oui Non Non (grid search) Non
Configurations ⭐ W2-W6, A4-A16 W2-W4 (weight-only) W3-W4 (weight-only) W8A8
Weight + Activation ⭐ W4A4, W6A6 ❌ Non ❌ Non ✅ W8A8
Paramètres par couche ⭐ Optimisés individuellement Uniformes Grid search α fixe
2-bit viable ⭐ W2A16 compétitif Dégradé Dégradé N/A
Temps de quantif. 1-16h (GPU) ⭐ Rapide (~4 GPU-h) ⭐ Rapide ⭐ Minutes
Complexité Élevée Moyenne Faible Faible
Support écosystème Limité ⭐ Très large ⭐ Très large Bon

OmniQuant vs SmoothQuant : la même idée, mais apprenable#

SmoothQuant : Y = (X · diag(1/s)) · (diag(s) · W) — s fixé par heuristique.

OmniQuant : Y = (X · diag(1/s*) + b*) · (diag(s*) · W - b*) — s, b APPRIS par couche.

La différence : SmoothQuant a 1 paramètre α global. OmniQuant a s + b par couche (optimisés). → OmniQuant trouve TOUJOURS un meilleur ou égal réglage que SmoothQuant. Mais ça coûte une optimisation (gradient).


Niveau néophyte : l'analogie#

Imagine que tu cuisines un gâteau. Les recettes classiques (GPTQ, AWQ) te disent : "mets 200g de sucre" — un montant fixe pour tous les gâteaux. OmniQuant dit : "apprends la quantité parfaite de sucre pour chaque gâteau". Au début, tu fais plusieurs essais (optimisation par gradient), mais au final, chaque gâteau est parfait car la quantité a été ajustée individuellement. C'est plus long que de suivre une recette fixe, mais le résultat est meilleur.


Résultats de performance#

Perplexité sur LLaMA-2#

xychart-beta title "Perplexité WikiText2 — LLaMA-2 W4A16 (plus bas = mieux)" x-axis ["FP16", "GPTQ", "AWQ", "OmniQuant"] y-axis "Perplexité" 5 --> 6 bar [5.47, 5.69, 5.63, 5.58]
xychart-beta title "Perplexité WikiText2 — LLaMA-2 W2A16 (plus bas = mieux)" x-axis ["FP16", "GPTQ", "AWQ", "OmniQuant"] y-axis "Perplexité" 5 --> 12 bar [5.47, 11.36, 8.48, 7.75]
xychart-beta title "Perplexité WikiText2 — LLaMA-2 W4A4 (plus bas = mieux)" x-axis ["FP16", "OmniQuant", "SmoothQuant"] y-axis "Perplexité" 5 --> 9 bar [5.47, 5.91, 8.71]

Tableau récapitulatif#

Configuration OmniQuant PPL Concurrent PPL Avantage
W4A16 (LLaMA-2-7B) 5.58 5.63 (AWQ)
W3A16 (LLaMA-2-7B) 6.10 6.40 (AWQ)
W2A16 (LLaMA-2-7B) 7.75 8.48 (AWQ)
W4A4 (LLaMA-2-7B) 5.91 8.71 (SmoothQuant) ⭐⭐
W6A6 (LLaMA-2-7B) 5.52 5.55 (SmoothQuant)

Points forts : OmniQuant surpasse systématiquement les PTQ classiques sur toutes les configurations, avec un avantage particulièrement marqué en W4A4 (quantification poids + activation) et en W2A16 (2-bit extrême).


Exemple pratique#

Installation#

git clone https://github.com/OpenGVLab/OmniQuant.git
cd OmniQuant
pip install -e .

Quantification W4A16 (weight-only 4-bit)#

# Quantifier LLaMA-2-7B en W4A16
python omniquant.py \
    --model meta-llama/Llama-2-7B-hf \
    --output_dir ./llama-7b-omniquant-w4a16 \
    --wbits 4 \
    --abits 16 \
    --let --lwc \
    --calib_dataset pile \
    --nsamples 128 \
    --epochs 5 \
    --gpu_id 0

Quantification W4A4 (weight + activation)#

python omniquant.py \
    --model meta-llama/Llama-2-7B-hf \
    --output_dir ./llama-7b-omniquant-w4a4 \
    --wbits 4 \
    --abits 4 \
    --let --lwc \
    --calib_dataset pile \
    --nsamples 128 \
    --epochs 10 \
    --gpu_id 0

Quantification W2A16 (2-bit extrême)#

python omniquant.py \
    --model meta-llama/Llama-2-7B-hf \
    --output_dir ./llama-7b-omniquant-w2a16 \
    --wbits 2 \
    --abits 16 \
    --let --lwc \
    --calib_dataset pile \
    --nsamples 128 \
    --epochs 20 \
    --gpu_id 0

Inférence#

import torch
from transformers import AutoTokenizer
from omniquant import OmniQuantModelForCausalLM

# Charger le modèle quantifié
model = OmniQuantModelForCausalLM.from_quantized(
    "./llama-7b-omniquant-w4a16",
    device="cuda",
)
tokenizer = AutoTokenizer.from_pretrained("./llama-7b-omniquant-w4a16")

# Génération
inputs = tokenizer("Qu'est-ce que la quantification apprenable ?", return_tensors="pt").to("cuda")
output = model.generate(**inputs, max_new_tokens=200)
print(tokenizer.decode(output[0], skip_special_tokens=True))

Avantages et inconvénients#

✅ Avantages#

  • Différentiable : optimise les paramètres plutôt que de les fixer manuellement
  • Très flexible : couvre W2A16 → W6A6 (weight-only ET weight+activation)
  • Best-in-class : surpasse GPTQ, AWQ, SmoothQuant sur toutes les configurations testées
  • W4A4 compétitif : une des rares PTQ viables en quantification poids + activation simultanée
  • W2A16 viable : meilleure PTQ 2-bit weight-only avant QuIP#/AQLM
  • Block-wise : optimisation par bloc, pas de ré-entraînement complet

⚠️ Inconvénients#

  • Nécessite un GPU pour l'optimisation (gradient descent sur 128 samples)
  • Plus complexe que GPTQ/AWQ (training-based PTQ, pas purement one-shot)
  • Plus lent à exécuter que la PTQ pure (1-16h vs minutes pour GPTQ)
  • Support limité dans les frameworks mainstream (vs GPTQ/AWQ très intégrés)
  • Moins d'écosystème d'inférence (kernels custom nécessaires)

Quand choisir OmniQuant ?#

flowchart TD Q1{"Tu veux W4A4 ?
(poids + activation 4-bit)"} -- OUI --> R1["★ OmniQuant est le meilleur choix PTQ ★"] Q1 -- NON --> Q2 Q2{"Tu veux W2A16 ?
(2-bit weight-only)
sans QuIP#/AQLM ?"} -- OUI --> R2["OmniQuant est compétitif en PTQ simple"] Q2 -- NON --> Q3 Q3{"Tu veux la meilleure
qualité W4A16 ?"} -- OUI --> R3["OmniQuant bat AWQ et GPTQ"] Q3 -- NON --> R4["→ GPTQ ou AWQ sont plus rapides"]

Références#

ia llm quantification omniquant ptq learnable clipping smoothquant int2 int4 weight-only weight-activation