LLM.int8()#
TL;DR#
LLM.int8() est une méthode de quantification INT8 qui résout le problème des emergent outliers dans les grands transformers. Elle utilise une décomposition mixed-precision : 99.9% des calculs en INT8 et ~0.1% en FP16 pour les outliers. Résultat : zéro dégradation de performance pour des modèles jusqu'à 175B paramètres, avec 2× moins de mémoire.
Pour un néophyte#
Dans un grand modèle de langage, la plupart des valeurs sont "normales" (petites), mais quelques-unes deviennent énormes — comme quelques élèves brillants dans une classe. Ces valeurs extrêmes sont essentielles : si on les arrondit mal (quantification classique), le modèle perd toute sa capacité de raisonnement.
LLM.int8() fait simple mais efficace :
1. Les valeurs normales → INT8 (rapide et léger)
2. Les quelques valeurs extrêmes → FP16 (précision conservée)
On ne perd aucune précision tout en économisant ~2× de mémoire.
La découverte clé : Emergent Outlier Features#
Le problème des outliers#
Tim Dettmers et son équipe ont découvert que les transformers à grande échelle (≥ 6.7B paramètres) développent des emergent outlier features : des dimensions spécifiques où certaines activations atteignent des magnitudes extrêmes — jusqu'à 20× la moyenne.
◆ Les outliers (~0.1% des dimensions) atteignent des magnitudes 20× supérieures à la moyenne.
Pourquoi les méthodes PTQ classiques échouent#
67.0, 0.4, 0.1, 0.3]
← 67.0 = outlier"] A --> B["Scale = max/127
= 67.0/127 = 0.528"] B --> C["Après : [1, 1, 0,
127, 1, 0, 1]
← TOUT devient 0 ou 1 !"] C --> D["❌ 99.9% de l'information
est PERDUE"]
Caractéristiques des emergent outliers#
| Propriété | Valeur |
|---|---|
| Modèles concernés | ≥ 6.7B paramètres |
| Proportion des dimensions | ~0.1% des features |
| Magnitude | Jusqu'à 20× la moyenne |
| Concentration | Dans des dimensions fixes (systématiques) |
| Impact sur la performance | Dominant — les retirer détruit le modèle |
| Taille du modèle | Outliers ? | PTQ classique viable ? |
|---|---|---|
| < 1B | Non | ✅ Oui |
| 1B – 6.7B | Rarement | ⚠️ Marginalement |
| ≥ 6.7B | OUI, systématiquement | ❌ Non → nécessite LLM.int8() |
La solution : Décomposition Mixed-Precision#
LLM.int8() sépare chaque multiplication matricielle en deux parties :
Y = X × W (X = activations, W = poids)
Décomposition :
<div class="mermaid">
flowchart TD
Y["Y = X × W"]
Y --> S1["1. Extraire les outlier features<br/>X_outlier (|x| > α), W_outlier"]
Y --> S2["2. Partie normale (99.9%)<br/>Y_normal = INT8(X_normal × W_normal)<br/>Vector-wise INT8 → MatMul accélérée"]
Y --> S3["3. Partie outlier (0.1%)<br/>Y_outlier = FP16(X_outlier × W_outlier)<br/>Pleine précision"]
S2 --> R["4. Résultat final<br/>Y = Y_normal + Y_outlier"]
S3 --> R
</div>
### Schéma de la décomposition
<div class="mermaid">
flowchart TD
MATMUL["MATMUL X × W<br/>(FP16 → INT8 souhaité)"]
MATMUL --> SPLITX["Matrice X (activations)<br/>[seq, dim]"]
SPLITX --> NORM["Partie normale<br/>99.9% des valeurs"]
SPLITX --> OUT["Colonnes outlier<br/>(|x| > threshold α)"]
NORM --> NM["Vector-wise INT8<br/>X_normal × W_normal<br/>→ INT8 MatMul<br/>(très rapide)<br/>~99.9% du calcul"]
OUT --> OM["FP16 précision complète<br/>X_out × W_out<br/>→ FP16 MatMul<br/>(lent mais 0.1%)<br/>~0.1% du calcul"]
NM --> RESULT["Y = Y_int8 + Y_fp16<br/>Résultat final = EXACTEMENT comme FP16"]
OM --> RESULT
</div>
### Seuil de détection α
Le seuil α (typiquement **6.0**) détermine quelles colonnes sont considérées comme outliers :
<div class="mermaid">
flowchart TD
A["max(|X[:, j]|) > α ?"] -->|OUI| B["Colonne j = outlier<br/>→ traitée en FP16"]
A -->|NON| C["Colonne normale<br/>→ traitée en INT8"]
</div>
---
## Vector-wise INT8 Quantization
Pour la partie "normale" (99.9%), LLM.int8() utilise une **vector-wise quantization** :
<div class="mermaid">
flowchart TD
A["X_row : [x1, x2, ..., xn]<br/>scale_x = max(|X_row|)"]
B["W_col : [w1, w2, ..., wn]<br/>scale_w = max(|W_col|)"]
A --> C["X_int8 = round(X_row / scale_x)"]
B --> D["W_int8 = round(W_col / scale_w)"]
C --> E["MatMul INT8 :<br/>dot_product_int8(X_int8, W_int8)"]
D --> E
E --> F["Déquantification :<br/>result = dot_product × scale_x × scale_w"]
</div>
> 💡 **Vector-wise** = un scale factor par vecteur (ligne/colonne), pas un seul pour tout le tenseur. C'est entre per-tensor et per-channel.
---
## Bits cibles et précision
| Aspect | Valeur |
|---|---|
| **Bits (normal)** | INT8 (poids + activations) |
| **Bits (outlier)** | FP16 (pleine précision) |
| **Proportion INT8** | ~99.9% des calculs |
| **Proportion FP16** | ~0.1% des calculs |
| **Dégradation** | **0.0** (zéro perte mesurable) |
| **Réduction mémoire** | ~2× vs FP16 |
---
## Avantages
- ✅ **Zéro dégradation de performance** — la décomposition préserve exactement les calculs importants
- ✅ Réduction de mémoire par **~2×** (FP16 → INT8)
- ✅ Permet de faire tourner **OPT-175B / BLOOM-176B** sur des GPU consumer
- ✅ **Plug-and-play** : `load_in_8bit=True` et c'est tout
- ✅ Implémentation de référence dans **bitsandbytes** (CUDA kernels optimisés)
- ✅ Validé sur des modèles jusqu'à 175B paramètres
## Inconvénients
- ⚠️ **Léger overhead computationnel** (~30% plus lent que FP16 pur) à cause de la décomposition mixed-precision
- ❌ **Limité à INT8** — pas de compression plus agressive (INT4, INT2)
- ❌ Les outliers peuvent ralentir l'inférence pour les très grands modèles
- ❌ Ne résout pas le problème de quantification à très bas bitrate
---
## Performance et overhead
| Configuration | Mémoire | Vitesse (relative) |
|---|---|---|
| FP16 (baseline) | 100% | 1.0× |
| LLM.int8() (INT8) | ~50% | ~0.7-0.8× (overhead ~20-30%) |
| INT8 pur (PTQ classique) | ~50% | ~1.5-2.0× (mais dégradation sur grands modèles) |
> Le compromis : LLM.int8() est **légèrement plus lent** que l'INT8 pur, mais **zéro perte de précision**. Pour les modèles ≥ 6.7B, c'est le seul INT8 viable.
---
## Exemple pratique
### Avec Hugging Face Transformers (le plus simple)
```python
from transformers import AutoModelForCausalLM, AutoTokenizer
# ═══════════════════════════════════════════════
# Chargement d'un modèle en INT8 avec LLM.int8()
# → UNE seule ligne : load_in_8bit=True
# ═══════════════════════════════════════════════
model = AutoModelForCausalLM.from_pretrained(
"facebook/opt-6.7b", # ou tout autre modèle ≥ 6.7B
load_in_8bit=True, # ← Active LLM.int8()
device_map="auto", # Répartit sur les GPU disponibles
)
tokenizer = AutoTokenizer.from_pretrained("facebook/opt-6.7b")
# Inférence — identique à un modèle FP16 normal
inputs = tokenizer("Bonjour, comment ça va ?", return_tensors="pt")
outputs = model.generate(**inputs, max_new_tokens=50)
print(tokenizer.decode(outputs[0]))
# → Zéro dégradation vs FP16, mais ~2× moins de VRAM
Vérifier la quantification#
# Inspecter les modules quantifiés
for name, module in model.named_modules():
if hasattr(module, 'weight') and hasattr(module.weight, 'quant_state'):
print(f"{name}: INT8 quantifié")
print(f" dtype original: {module.weight.quant_state.dtype}")
print(f" shape: {module.weight.shape}")
# Vérifier la mémoire utilisée
import torch
print(f"VRAM allouée: {torch.cuda.memory_allocated() / 1e9:.2f} GB")
# OPT-6.7B : ~13 GB en FP16 → ~7 GB en INT8
Avec bitsandbytes directement#
import bitsandbytes as bnb
import torch.nn as nn
# Remplacer une couche Linear standard par une couche INT8
linear_fp16 = nn.Linear(4096, 4096)
# Conversion en Int8Params
from bitsandbytes.nn import Linear8bitLt
linear_int8 = Linear8bitLt(
input_features=4096,
output_features=4096,
bias=True,
has_fp16_weights=False, # poids quantifiés en INT8
threshold=6.0, # seuil outlier α
)
# Le forward pass gère automatiquement la décomposition mixed-precision
output = linear_int8(input_tensor)
Inférence sur GPU consumer#
# ═══════════════════════════════════════════════
# OPT-175B / BLOOM-176B sur un seul serveur
# (impossible en FP16, possible en INT8)
# ═══════════════════════════════════════════════
model = AutoModelForCausalLM.from_pretrained(
"bigscience/bloom",
load_in_8bit=True,
device_map="auto",
# Répartit automatiquement sur plusieurs GPU
# BLOOM-176B : ~350 GB FP16 → ~180 GB INT8
# → 4× RTX 3090 (24GB) ou 2× A100 (80GB)
)
Papier arXiv source#
LLM.int8(): 8-bit Matrix Multiplication for Transformers at Scale
- 📄 arXiv : 2208.07339
- 👤 Auteurs : Tim Dettmers, Mike Lewis, Younes Belkada, Luke Zettlemoyer
- 🏢 University of Washington / Meta AI
- 📅 15 août 2022
- 📰 Publication : NeurIPS 2022
Références#
- 📄 LLM.int8() (arXiv:2208.07339) — Papier original
- 📦 bitsandbytes — Implémentation de référence (CUDA kernels)
- 📦 Hugging Face — 8-bit model loading — Documentation
load_in_8bit - 📄 Blog de Tim Dettmers — LLM.int8() — Explication détaillée par l'auteur
- 📄 Emergent Abilities of Large Language Models (arXiv:2206.07682) — Contexte sur les emergent features
- 🔗 Voir aussi : [[ptq]] — Post-Training Quantization (catégorie parente)
- 🔗 Voir aussi : [[qat]] — Quantization Aware Training (alternative avec entraînement)
- 🔗 Voir aussi : [[index]] — Vue d'ensemble des méthodes de quantification