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.

xychart-beta title "Distribution des activations dans un LLM (>6.7B)" x-axis ["-2", "-1", "0", "1", "2", "15", "20", "30+"] y-axis "Densité" 0 --> 100 bar [10, 40, 90, 40, 10, 5, 5, 3]

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#

flowchart LR subgraph AVANT["=== AVANT SmoothQuant ==="] direction LR AX["Activations X
⚠️ 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#

flowchart TD A["1. CALIBRATION (offline, ~5 min)
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
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#

  1. Limité à INT8 — SmoothQuant n'est pas conçu pour la quantification sub-8-bit (INT4, INT3). Pour cela, voir GPTQ ou AWQ.
  2. α nécessite un tuning — bien que α=0.5 fonctionne pour la plupart des modèles, certains (OPT-175B) nécessitent α=0.85.
  3. 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#

ia llm quantification smoothquant ptq int8 post-training-quantization