SmoothQuant#
SmoothQuant est une méthode de Post-Training Quantization (PTQ) INT8 qui résout le problème des outlier features dans les activations en migrant la difficulté de quantification des activations vers les poids, via une simple transformation mathématique équivalente.
Le problème : pourquoi les activations sont difficiles à quantifier ?#
Dans les LLM de grande échelle (≥ 6.7B paramètres), certaines dimensions d'activation développent des outliers systématiques — des valeurs 20× à 100× supérieures à la moyenne, concentrées dans ~0.1% des canaux. Ces outliers rendent la quantification INT8 des activations catastrophique en précision.
La plupart des canaux sont faciles à quantifier (-2 à +2), mais les outliers (15-30+) cassent l'INT8. Les poids, en revanche, ont une distribution gaussienne uniforme — très facile à quantifier.
Les poids, en revanche, ont une distribution gaussienne uniforme — très facile à quantifier.
L'intuition : transférer la difficulté#
Si les activations sont "trop grandes" et les poids "faciles", pourquoi ne pas rééquilibrer ?
Pour une couche linéaire Y = X · W (X = activations, W = poids), SmoothQuant introduit un vecteur de scaling s (un facteur par canal) :
Y = X · W
= (X · diag(1/s)) · (diag(s) · W) ← transformation équivalente
= X' · W'
- X' = X / s → les activations sont divisées → les outliers sont réduits
- W' = W · s → les poids sont multipliés → ils restent quantifiables (ils étaient faciles au départ)
Le résultat mathématique Y est strictement identique, mais la nouvelle paire (X', W') est beaucoup plus facile à quantifier en INT8.
Diagramme Before / After#
⚠️ Difficile à quantifier
(outliers ⚡)"] AW["Poids W
✓ Facile à quantifier"] end subgraph APRES["=== APRÈS SmoothQuant ==="] direction LR PX["Activations X' = X/s
✓ Facile à quantifier INT8
(lissé !)"] PW["Poids W' = W·s
✓ Facile à quantifier INT8
(légèrement amplifié
mais toujours OK)"] end AVANT ~-> APRES
La formule du scaling factor#
Le facteur s est calculé offline (sur un petit set de calibration, typiquement 128–512 échantillons) pour chaque canal d'activation j :
α
s_j = max(|X_j|) / max(|W_j|)^(1-α)
Où :
- max(|X_j|) — magnitude maximum des activations sur le canal j
- max(|W_j|) — magnitude maximum des poids sur le canal j
- α (alpha) — hyperparamètre de migration (entre 0 et 1)
Rôle du paramètre α#
α = 0.0 → s = 1/max(|W_j|) → aucune migration, poids seuls normalisés
α = 0.5 → migration équilibrée → VALEUR PAR DÉFAUT (recommandée)
α = 1.0 → s = max(|X_j|) → migration totale vers les poids
- α faible (< 0.5) : meilleur pour les modèles avec peu d'outliers
- α fort (> 0.5) : meilleur pour les modèles avec outliers très extrêmes (ex: OPT-175B utilise α=0.85)
- La plupart des modèles fonctionnent bien avec α = 0.5
Niveau néophyte : l'analogie#
Imaginez que vous devez transporter deux sacs : un sac d'activations (très lourd, instable) et un sac de poids (léger, facile). SmoothQuant ne change pas le poids total, il transfère une partie de la charge du sac "activations" vers le sac "poids". Comme le sac "poids" avait beaucoup de marge, il absorbe le surplus sans problème, et le sac "activations" devient soudainement facile à porter (quantifier).
Niveau technique : architecture du pipeline#
128 samples → Forward pass
Collecter max(|X_j|) et max(|W_j|)"] A --> B["2. CALCUL DU SCALING FACTOR
s_j = max(|X_j|)^α / max(|W_j|)^(1-α)
(par canal, pour chaque couche)"] B --> C["3. TRANSFORMATION DES POIDS (offline, one-shot)
W' = W · diag(s)
W'_quant = Quant_INT8(W')"] C --> D["4. INFERENCE (runtime)
X' = X / diag(s) — division par canal
X'_quant = Quant_INT8(X')
Y = INT8_GEMM(X'_quant, W'_quant)"]
Point clé : à l'inférence, il n'y a aucune mixed-precision — tout passe par des GEMM INT8 pures, contrairement à LLM.int8() qui doit décomposer en INT8 + FP16 pour les outliers.
Caractéristiques#
| Propriété | Valeur |
|---|---|
| Type | Post-Training Quantization (PTQ) |
| Précision | W8A8 (poids INT8 + activations INT8) |
| Entraînement requis | ❌ Non (training-free) |
| Données de calibration | 128–512 échantillons |
| Mixed-precision à l'inférence | ❌ Non (INT8 pur) |
| Speedup | 1.56× vs FP16 |
| Réduction mémoire | 2× |
| Plus grand modèle testé | MT-NLG 530B |
Comparaison avec les méthodes voisines#
| Critère | SmoothQuant | LLM.int8() | GPTQ |
|---|---|---|---|
| Bits | W8A8 | W8A8 (mixed) | W4A16 |
| Quantifie les activations | ✅ Oui (INT8) | ✅ Oui (INT8 + FP16 outliers) | ❌ Non (FP16) |
| Mixed-precision runtime | ❌ Non | ✅ Oui (overhead) | ❌ Non |
| Sub-8-bit | ❌ Non | ❌ Non | ✅ Oui (INT4, INT3) |
| Vitesse de quantification | Minutes | N/A (runtime) | Heures |
Exemple pratique#
Installation et quantification#
# Cloner le dépôt officiel
git clone https://github.com/mit-han-lab/smoothquant.git
cd smoothquant
# Installer les dépendances
pip install -r requirements.txt
Script Python de quantification#
from smoothquant.smooth import smooth_lm
from smoothquant.calibration import get_act_scales
from transformers import AutoModelForCausalLM, AutoTokenizer
# 1. Charger le modèle FP16
model = AutoModelForCausalLM.from_pretrained("facebook/opt-1.3b", torch_dtype="float16")
tokenizer = AutoTokenizer.from_pretrained("facebook/opt-1.3b")
# 2. Calibration : collecter les scales d'activation
act_scales = get_act_scales(
model, tokenizer,
dataset_path="pile",
num_samples=128,
seq_len=512
)
# 3. Appliquer le smoothing (α = 0.5 par défaut)
smooth_lm(model, act_scales, alpha=0.5)
# 4. Quantifier en INT8 (via PyTorch ou bitsandbytes)
# Le modèle smoothé peut maintenant être quantifié en W8A8
model = model.to("cuda")
# ... quantification INT8 et inférence
Intégration dans vLLM#
# SmoothQuant est intégré dans plusieurs frameworks de serving
# Exemple avec TensorRT-LLM qui supporte SmoothQuant nativement
python build.py --model_dir /path/to/smoothed_model \
--quantize smoothquant \
--smoothquant_val 0.5
Modèles supportés et résultats#
| Modèle | Paramètres | α utilisé | Accuracy loss |
|---|---|---|---|
| OPT-175B | 175B | 0.85 | Négligeable |
| BLOOM-176B | 176B | 0.5 | Négligeable |
| GLM-130B | 130B | 0.75 | Négligeable |
| MT-NLG 530B | 530B | 0.8 | Négligeable |
| LLaMA-7B | 7B | 0.5 | Négligeable |
Note : SmoothQuant brille particulièrement pour les modèles ≥ 6.7B où les outlier features apparaissent. Pour les petits modèles, le smoothing a moins d'impact car les activations sont déjà bien comportées.
Limitations#
- Limité à INT8 — SmoothQuant n'est pas conçu pour la quantification sub-8-bit (INT4, INT3). Pour cela, voir GPTQ ou AWQ.
- α nécessite un tuning — bien que α=0.5 fonctionne pour la plupart des modèles, certains (OPT-175B) nécessitent α=0.85.
- Ne traite que les activations — la migration suppose que les poids peuvent absorber le scaling, ce qui n'est pas toujours optimal en très basse précision.
Références#
- Papier arXiv : SmoothQuant: Accurate and Efficient Post-Training Quantization for Large Language Models (Xiao et al., ICML 2023)
- Code officiel : github.com/mit-han-lab/smoothquant
- Site du laboratoire : hanlab.mit.edu
- Voir aussi : GPTQ · AWQ · Index quantification