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.
░= 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.
→ 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 :
→ 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#
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#
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 :
- N'utilise les activations que pour identifier les canaux (pas pour reconstruire les poids)
- Le scaling est une transformation fermée — pas d'optimisation itérative
- 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 ?#
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#
- Weight-only — les activations restent en FP16, ce qui limite le speedup par rapport aux méthodes W8A8.
- Nécessite des données de calibration — pour collecter les statistiques d'activation et identifier les canaux saillants.
- INT2 suboptimal — en régime extrême (≤ 2 bits), AWQ est moins performant que QuIP# ou AQLM.
- Le grid search du scale — bien que rapide, il reste un hyperparamètre à optimiser.
Références#
- Papier arXiv : AWQ: Activation-aware Weight Quantization for LLM Compression and Acceleration (Lin et al., MLSys 2024 Best Paper)
- AutoAWQ : github.com/casper-hansen/AutoAWQ
- llm-awq (MIT HAN Lab) : github.com/mit-han-lab/llm-awq
- TinyChat (inférence) : github.com/mit-han-lab/llm-awq#tinychat
- Voir aussi : GPTQ · SmoothQuant · Index quantification