AWQ (Activation-Aware Weight Quantization)#

AWQ est une méthode de quantification Post-Training weight-only qui découvre que protéger seulement ~1% des canaux de poids "saillants" suffit à préserver la qualité du modèle. Au lieu de garder ces canaux en FP16 (inefficace), AWQ les scale mathématiquement pour réduire leur erreur de quantification — sans mixed-precision à l'inférence.


L'intuition clé : tous les poids ne sont pas égaux#

AWQ part d'une observation contre-intuitive : si vous identifiez les ~1% des canaux de poids les plus importants et les gardez en FP16 (mixed-precision), le modèle conserve sa qualité même si les 99% restants sont quantifiés en INT4.

block-beta columns 12 block:row1["░"]:1 block:row2["░"]:1 block:row3["░"]:1 block:row4["░"]:1 block:row5["░"]:1 block:row6["░"]:1 block:row7["░"]:1 block:row8["░"]:1 block:row9["░"]:1 block:row10["░"]:1 block:row11["░"]:1 block:row12["░"]:1 block:row21["░"]:1 block:row22["░"]:1 block:row23["░"]:1 block:s1["▓"]:1 block:row25["░"]:1 block:row26["░"]:1 block:row27["░"]:1 block:row28["░"]:1 block:s2["▓"]:1 block:row2A["░"]:1 block:row2B["░"]:1 block:row2C["░"]:1 block:row31["░"]:1 block:row32["░"]:1 block:row33["░"]:1 block:row34["░"]:1 block:row35["░"]:1 block:row36["░"]:1 block:row37["░"]:1 block:row38["░"]:1 block:row39["░"]:1 block:row3A["░"]:1 block:row3B["░"]:1 block:row3C["░"]:1 block:s3["▓"]:1 block:row42["░"]:1 block:row43["░"]:1 block:row44["░"]:1 block:row45["░"]:1 block:row46["░"]:1 block:row47["░"]:1 block:row48["░"]:1 block:row49["░"]:1 block:row4A["░"]:1 block:row4B["░"]:1 block:row4C["░"]:1 block:row51["░"]:1 block:row52["░"]:1 block:row53["░"]:1 block:row54["░"]:1 block:row55["░"]:1 block:row56["░"]:1 block:row57["░"]:1 block:row58["░"]:1 block:row59["░"]:1 block:row5A["░"]:1 block:row5B["░"]:1 block:row5C["░"]:1

= poids normaux (quantifiables en INT4) — = canaux saillants (1% à protéger !)

Qu'est-ce qui rend un canal "saillant" ?#

La saliency d'un poids w est le produit de deux facteurs :

    Saliency(w) = |w| × |x|

    où :
    • |w| = magnitude du POIDS
    • |x| = magnitude de l'ACTIVATION correspondante

Un poids est saillant si ET il a une grande magnitude ET l'activation qui le traverse est importante. Un poids grand avec une activation négligeable ne l'est pas, et inversement.

flowchart LR subgraph S["Saliency = |w| × |x|"] R1["|w|=0.5, |x|=0.1
→ 0.05 ❌"] R2["|w|=0.5, |x|=20.0
→ 10.0 ✅ SAILLANT"] R3["|w|=3.0, |x|=0.1
→ 0.3 ❌"] R4["|w|=3.0, |x|=20.0
→ 60.0 ✅ TRÈS SAILLANT"] end

Les canaux saillants dominent la sortie du modèle. Les quantifier naïvement détruit la performance.


Le problème de la mixed-precision#

La solution naïve serait de garder les 1% de canaux saillants en FP16 et quantifier le reste en INT4. Mais c'est désastreux pour le hardware :

flowchart TD GEMM["❌ GEMM Mixed-Precision"] GEMM --> FP16["FP16 × FP16 (saillants)"] GEMM --> INT4["INT4 × INT4 (normaux)"] FP16 --> PROB["→ kernels custom complexes
→ overhead de branchement
→ inefficace sur GPU"] INT4 --> PROB

La solution AWQ : scaling au lieu de mixed-precision#

AWQ réalise que l'on peut simuler la protection du mixed-precision avec un simple scaling, similaire à SmoothQuant mais appliqué aux poids saillants :

    Pour un canal saillant avec poids W et activation X :

    AVANT :  Y = X · W                     ← W quantifié avec erreur élevée

    APRÈS :  Y = (X/s) · (W·s)             ← transformation équivalente
             =   X'    ·   W'
  • W' = W · s → le poids est multiplié par s → sa magnitude augmente → l'erreur relative de quantification diminue
  • X' = X / s → l'activation est divisée par s → compense exactement

Pourquoi le scaling réduit l'erreur de quantification#

    Fonction d'erreur de quantification :

    Err = |Quant(w) - w|     (erreur absolue, constante par niveau)

    Err_relative = |Quant(w) - w| / |w|

    En multipliant w par s :

    Err'_relative = |Quant(w·s) - w·s| / |w·s|
                  = |Quant(w·s) - w·s| / (|w|·s)

    Comme Quant(w·s) ≈ w·s avec la même erreur absolue :

    Err'_relative ≈ Err_relative / s    ← ERREUR RÉDUITE de facteur s !

Diagramme : distribution des poids et protection#

xychart-beta title "Effet du scaling sur l'erreur de quantification relative" x-axis ["AVANT (s=1)", "APRÈS (s=5)"] y-axis "Erreur relative" 0 --> 25 bar [20, 1.3]

Avant (s=1) : erreur relative ~20%. Après (s=5) : erreur relative ~1.3% — l'erreur absolue reste ~0.2 mais la magnitude des poids est 5× plus grande, donc l'erreur relative est divisée par s.


Niveau néophyte : l'analogie#

Imaginez un orchestre de 100 musiciens. AWQ découvre que seulement 1 musicien (le soliste) est crucial pour la qualité du concert. Au lieu de lui donner un instrument spécial (coûteux et compliqué à gérer), AWQ augmente simplement le volume de son micro (scaling). L'auditeur ne fait pas la différence, mais techniquement, le soliste est "protégé" des perturbations (erreurs de quantification). Le reste de l'orchestre joue normalement en version compressée.


Niveau technique : le pipeline AWQ#

flowchart TD A["1. CALIBRATION (offline)
128 samples → Forward pass
Collecter |X_j| (activations)"] A --> B["2. IDENTIFICATION DES CANAUX SAILLANTS
Saliency_j = |W_j| × |X_j|
Top 1% des canaux"] B --> C["3. RECHERCHE DU SCALE OPTIMAL (grid search)
Tester s ∈ [0, 1] par grille
Minimiser l'erreur globale
(pas de backprop)"] C --> D["4. APPLICATION DU SCALING
W_salient *= s (scale up)
X_salient /= s (scale down,
fusionné dans le RMSNorm)"] D --> E["5. QUANTIFICATION INT4 STANDARD
Tous les poids en INT4 uniformément
(pas de mixed-precision !)"] E --> F["6. INFERENCE
Déquant INT4 → FP16
GEMM standard (pas de kernel spécial)"]

Pourquoi AWQ ne surajuste pas (contrairement à GPTQ)#

GPTQ reconstruit la matrice de poids à partir du set de calibration, ce qui peut causer de l'overfitting. AWQ, en revanche :

  1. N'utilise les activations que pour identifier les canaux (pas pour reconstruire les poids)
  2. Le scaling est une transformation fermée — pas d'optimisation itérative
  3. Généralise mieux sur des données hors distribution

Caractéristiques#

Propriété Valeur
Type Post-Training Quantization (PTQ) weight-only
Précisions INT4 (principal), INT3
Activations Restent en FP16
Group-size Configurable (typiquement 128 ou None)
Canaux protégés ~1% des plus saillants
Données de calibration 128–512 échantillons
Mixed-precision runtime ❌ Non (INT4 uniforme)
Speedup ~3× sur GPU desktop/mobile
Backprop requise ❌ Non

Exemple pratique avec AutoAWQ#

Installation#

pip install autoawq
# ou depuis la source :
pip install git+https://github.com/casper-hansen/AutoAWQ.git

Quantification avec AutoAWQ#

from awq import AutoAWQForCausalLM
from transformers import AutoTokenizer

# 1. Configuration
model_path = "meta-llama/Llama-2-7B-hf"
quant_path = "./llama-2-7b-awq-4bit"

quant_config = {
    "zero_point": True,       # quantification asymétrique
    "q_group_size": 128,      # group-size standard
    "w_bit": 4,               # INT4
    "version": "GEMM",        # format kernel (GEMM ou GEMV)
}

# 2. Charger le modèle
model = AutoAWQForCausalLM.from_pretrained(model_path)
tokenizer = AutoTokenizer.from_pretrained(model_path)

# 3. Préparer les données de calibration
# AutoAWQ peut utiliser des datasets Hugging Face nativement
from datasets import load_dataset
calib_data = load_dataset("pile", split="train", streaming=True)
calib_texts = [list(calib_data.take(128))[i]["text"] for i in range(128)]

# 4. Quantifier
model.quantize(
    tokenizer,
    quant_config=quant_config,
    calib_data=calib_texts,
)

# 5. Sauvegarder
model.save_quantized(quant_path)
tokenizer.save_pretrained(quant_path)

Inférence avec vLLM#

# vLLM supporte nativement les modèles AWQ
python -m vllm.entrypoints.openai.api_server \
    --model ./llama-2-7b-awq-4bit \
    --quantization awq \
    --dtype float16

Utilisation d'un modèle pré-quantifié (Hugging Face)#

from awq import AutoAWQForCausalLM
from transformers import AutoTokenizer

# Charger un modèle déjà quantifié AWQ depuis Hugging Face
model_id = "TheBloke/Llama-2-7B-Chat-AWQ"
model = AutoAWQForCausalLM.from_quantized(model_id, fuse_layers=True)
tokenizer = AutoTokenizer.from_pretrained(model_id)

# Génération
inputs = tokenizer("Bonjour, comment ça va ?", return_tensors="pt")
output = model.generate(**inputs, max_new_tokens=100)
print(tokenizer.decode(output[0]))

Comparaison AWQ vs GPTQ#

Critère AWQ GPTQ
Approche Scaling des canaux saillants Compensation via Hessienne
Précision INT4 ⭐ Légèrement supérieure Excellente
Généralisation ⭐ Meilleure (pas d'overfitting) Sensible au calibration set
Modèles instruction-tuned ⭐ Recommandé Bon
Modèles multimodal ⭐ Recommandé Bon
Vitesse de quantification Légèrement plus lent ⭐ Rapide (~4 GPU-h pour 175B)
Sub-4-bit (INT3/INT2) Bon ⭐ Légèrement meilleur en INT2
Kernels GPU Marlin, TensorRT-LLM ⭐ GPTQ-Marlin, ExLlamaV2
Support écosystème Très large ⭐ Plus mature
Papier MLSys 2024 Best Paper ICLR 2023

Tableau de performance (perplexity, plus bas = mieux)#

Modèle FP16 GPTQ (INT4) AWQ (INT4)
LLaMA-7B 5.68 5.71 5.70
LLaMA-13B 4.89 4.91 4.90
LLaMA-33B 4.10 4.12 4.11
LLaMA-65B 3.53 3.55 3.54
OPT-6.7B 10.86 11.12 10.92 ← AWQ préserve mieux
Différence baseline +0.03 +0.02 ← AWQ légèrement meilleur

Quand choisir AWQ vs GPTQ ?#

flowchart LR subgraph AWQ["AWQ si :"] direction TB A1["• Modèle chat"] A2["• Instruction tuned"] A3["• Multimodal"] A4["• Code/math"] A5["• Robustesse calibration"] end subgraph GPTQ["GPTQ si :"] direction TB G1["• Quantif. rapide"] G2["• INT2/INT3"] G3["• Base model"] G4["• Kernels ExLlama"] G5["• Écosystème mature"] end

Modèles testés et résultats#

Modèle Configuration Accuracy loss Speedup
LLaMA-7B INT4, group=128 +0.02 PPL 3.2×
LLaMA-13B INT4, group=128 +0.01 PPL 3.1×
LLaMA-2-70B INT4, group=128 +0.02 PPL 3.0×
Vicuna-7B INT4, group=128 <0.5% sur benchmarks 3.2×
Llama-2-70B (mobile) INT4 Deployable sur GPU mobile 2.7×

Performance notable : AWQ permet de déployer Llama-2-70B sur des GPU mobiles/desktop grâce à sa compression efficace combinée aux kernels TinyChat.


Limitations#

  1. Weight-only — les activations restent en FP16, ce qui limite le speedup par rapport aux méthodes W8A8.
  2. Nécessite des données de calibration — pour collecter les statistiques d'activation et identifier les canaux saillants.
  3. INT2 suboptimal — en régime extrême (≤ 2 bits), AWQ est moins performant que QuIP# ou AQLM.
  4. Le grid search du scale — bien que rapide, il reste un hyperparamètre à optimiser.

Références#

ia llm quantification awq ptq int4 weight-only activation-aware autoawq