QoQ / QServe — Quantification W4A8KV4#

QoQ (quattuor-octo-quattuor = 4-8-4 en latin) est un schéma de quantification W4A8KV4 : poids en 4-bit, activations en 8-bit, KV cache en 4-bit. Couplé au système QServe (kernels GPU optimisés), c'est la première solution INT4 qui accélère le large-batch cloud serving, avec un throughput jusqu'à 3.5× supérieur à TensorRT-LLM.


Le problème : INT4 ne suffit pas pour le cloud serving#

Les méthodes de quantification comme GPTQ et AWQ compressent les poids en INT4 — ce qui réduit la mémoire et accélère l'inférence pour des batch sizes petits. Mais en cloud serving (servir des dizaines de requêtes simultanées), le bottleneck change :

flowchart LR subgraph SINGLE["Single User (batch 1)"] S1["Bottleneck = mémoire poids
(weight loading)"] S2["Solution: poids INT4
(GPTQ, AWQ) ✅ Résolu"] end subgraph BATCH["Large Batch (batch 32+)"] B1["Bottleneck = COMPUTE
(activations × poids)
+ KV cache mémoire"] B2["⚠️ Non adressé par GPTQ/AWQ"] end

En large batch, les activations (FP16) dominent le compute. Quantifier seulement les poids ne suffit pas — il faut aussi quantifier les activations. Mais les activations ont des outliers qui rendent l'INT4 catastrophique. D'où la solution intermédiaire : W4A8KV4.


Niveau néophyte : l'analogie#

Imaginez une cuisine industrielle qui prépare 100 repas à la fois. Les recettes (poids) peuvent être stockées en version super-condensée (4 mots-clés au lieu de 16). Mais les ingrédients frais (activations) arrivent en continu et en grande quantité — les condenser autant que les recettes rendrait les plats immangeables. QoQ utilise donc une compression modérée pour les ingrédients (8 niveaux au lieu de 16), suffisante pour accélérer la production sans ruiner la qualité.

De plus, les notes de commande (KV cache) s'accumulent pendant le service — les compresser aussi (4 niveaux) évite d'encombrer le plan de travail.


Niveau technique : la quantification W4A8KV4#

Décomposition des trois précisions#

Composant Bits Raison
Poids (W) INT4 Compression max, stable (comme GPTQ/AWQ), réduit la mémoire
Activations (A) INT8 INT4 trop agressif (outliers), INT8 = bon compromis compute/qualité
KV Cache (KV) INT4 Énorme footprint mémoire, quantifiable à 4-bit sans trop de perte (avec SmoothAttention)

Diagramme du pipeline QServe#

flowchart TD M["Modèle FP16 original"] --> S1 subgraph S1["Étape 1 : SmoothQuant"] S1A["Migrer les outliers des activations vers les poids
via scaling par-canal
X' = X · diag(1/s), W' = diag(s) · W
→ Activations « lissées » quantifiables en INT8"] end S1 --> S2 subgraph S2["Étape 2 : Quantification"] S2A["W' → INT4 (group quant)
X' → INT8 (per-token)
KV → INT4 (per-channel) + SmoothAttention"] end S2 --> S3 subgraph S3["Étape 3 : Inférence QServe"] S3A["Progressive dequant : INT4 → INT8 → GEMM INT8
(pas de déquant 4→16 !)
Kernels GPU optimisés :
• Compute-aware reordering
• Register-level parallelism
• INT8 Tensor Cores"] end

Les 3 innovations clés de QoQ#

1. Progressive quantization (INT4 → INT8 → GEMM)#

Le problème classique : pour multiplier du INT4 (poids) par du INT8 (activations), il faut déquantifier les poids en INT8 ou FP16. QoQ fait une transition progressive sans passer par FP16 :

flowchart TD subgraph NAIVE["Méthode naïve"] N1["W_int4"] --> N2["déquant 4→16 bit
⚠️ LENT"] N2 --> N3["W_fp16"] N3 --> N4["GEMM FP16 × INT8
⚠️ LENT"] end subgraph QOQ["Méthode QoQ"] Q1["W_int4"] --> Q2["déquant 4→8 bit
(zéro coût FP16 !)"] Q2 --> Q3["W_int8"] Q3 --> Q4["GEMM INT8 × INT8
✅ RAPIDE
(Tensor Cores INT8 !)"] end

Cette transition INT4→INT8 est beaucoup moins coûteuse que INT4→FP16 car elle opère uniquement sur des entiers, et le GEMM INT8 utilise les Tensor Cores INT8 natifs du GPU (débit 2× supérieur au FP16 sur Ampere).

2. SmoothAttention pour le KV cache 4-bit#

La quantification du KV cache en INT4 dégrade l'attention. QoQ introduit SmoothAttention : appliquer une technique similaire à SmoothQuant, mais spécifiquement aux channels du KV cache pour lisser leurs distributions avant quantification.

flowchart TD KV1["KV Cache original
(distribution avec outliers)"] --> SA["SmoothAttention
Scaling par-canal des K/V
(migration des outliers)"] SA --> KV2["KV Cache INT4
(distribution lissée,
quantifiable sans dégradation)"]

3. Optimisations système (compute-aware reordering + register parallelism)#

Le système QServe implémente des kernels GPU qui réorganisent les poids au niveau des registers pour minimiser les conflits de banques et maximiser le parallélisme au niveau des registres :

mindmap root((Kernel QServe GPU)) Compute-aware weight reordering Poids INT4 réarrangés Déquant INT4→INT8 alignée avec access patterns des registers Register-level parallelism Plusieurs channels déquantifiés en parallèle Pas de spill en shared memory INT8 Tensor Core GEMM GEMM final utilise Tensor Cores INT8 Débit 2× supérieur au FP16 sur Ampere

Caractéristiques#

Propriété Valeur
Type PTQ + co-design système
Précision W4A8KV4 (poids INT4, activations INT8, KV cache INT4)
Bottleneck ciblé Large-batch cloud serving (compute + KV mémoire)
GPU requis NVIDIA Ampere+ (A100, L40S, H100)
Throughput vs TensorRT-LLM 1.2×–3.5× supérieur
Coût de serving Réduit de (L40S bat A100)
Outils QServe (MIT HAN Lab)

Résultats throughput#

Comparaison vs TensorRT-LLM (FP16)#

Modèle GPU TRT-LLM FP16 (tok/s) QServe W4A8KV4 (tok/s) Speedup
Llama-3-8B A100 ~6 000 ~7 200 1.2×
Llama-3-8B L40S ~4 000 ~5 600 1.4×
Qwen1.5-72B A100 ~800 ~1 900 2.4×
Qwen1.5-72B L40S ~500 ~1 750 3.5×
Llama-2-70B A100 ~850 ~1 900 2.2×

Le résultat clé : L40S bat A100#

xychart-beta title "Llama-3-8B — Throughput batch 64 (tok/s)" x-axis ["A100 FP16", "A100 QServe", "L40S QServe", "L40S FP16"] y-axis "Throughput (tok/s)" 3000 --> 8000 bar [6000, 7200, 5600, 4000]

Avec QServe, L40S (~8K$) égale A100 (~20K$) en throughput → coût de serving réduit de ~3×.

Le L40S (carte data-center à ~8 000 $) avec QServe surpasse un A100 (~20 000 $) en FP16. C'est une réduction de coût de serving de 3×.


Exemple pratique#

Installation de QServe#

# Cloner le repository QServe (MIT HAN Lab)
git clone https://github.com/mit-han-lab/omniserve.git
cd omniserve

# Installer les dépendances
pip install -e .

Quantification W4A8KV4 d'un modèle#

from qserve import QoQQuantizer, QServeEngine

# 1. Configuration de la quantification QoQ
quant_config = {
    "w_bits": 4,           # Poids INT4
    "a_bits": 8,           # Activations INT8
    "kv_bits": 4,          # KV cache INT4
    "w_group_size": 128,   # Group quantization pour les poids
    "smoothquant_alpha": 0.5,  # Migration outliers (SmoothQuant)
    "smoothattention": True,   # SmoothAttention pour KV 4-bit
}

# 2. Quantifier le modèle
quantizer = QoQQuantizer(quant_config)
model = quantizer.quantize("meta-llama/Meta-Llama-3-8B")

# 3. Servir avec le moteur QServe
engine = QServeEngine(model, tensor_parallel_size=1)

Serving avec QServe#

# Lancer le serveur QServe
python -m qserve.entrypoints.openai.api_server \
    --model meta-llama/Meta-Llama-3-8B \
    --quantization qoq \
    --w-bits 4 \
    --a-bits 8 \
    --kv-bits 4 \
    --port 8000

Benchmark throughput#

# Benchmark QServe
python -m qserve.benchmark.benchmark_serving \
    --model meta-llama/Meta-Llama-3-8B \
    --quantization qoq \
    --num-prompts 256 \
    --request-rate 32

# Comparer avec TensorRT-LLM FP16
python -m qserve.benchmark.benchmark_serving \
    --model meta-llama/Meta-Llama-3-8B \
    --quantization fp16 \
    --backend tensorrt-llm \
    --num-prompts 256 \
    --request-rate 32

Comparaison : QoQ vs autres schémas#

Schéma Poids Activations KV Cache Batch serving Accuracy
GPTQ / AWQ INT4 FP16 FP16 ⚠️ Faible (batch < 8) Excellente
SmoothQuant INT8 INT8 FP16 ✅ Bon (batch 16+) Excellente
GPTQ + Marlin INT4 FP16 FP16 ✅ Bon (batch ≤ 64) Excellente
QoQ / QServe INT4 INT8 INT4 ✅✅ Excellent (batch 64+) Très bonne
FP8 (H100) FP8 FP8 FP8 ✅ Bon (H100 uniquement) Excellente

Le positionnement unique de QoQ : c'est le seul schéma qui quantifie simultanément poids (4-bit), activations (8-bit) et KV cache (4-bit) avec des kernels GPU optimisés pour le large-batch serving.


Limitations#

  1. Complexité de mise en œuvre — le co-design algorithme + système nécessite les kernels QServe custom (pas de drop-in dans n'importe quel framework).
  2. Configuration très spécifique — W4A8KV4 est figée ; pas de flexibilité comme GPTQ (3/4/8-bit au choix).
  3. SmoothQuant requis — la quantification INT8 des activations nécessite une étape de calibration SmoothQuant préalable.
  4. GPU Ampere+ obligatoire — les Tensor Cores INT8 sont essentiels pour le throughput promis.
  5. Support framework limité — principalement via le repository QServe officiel, intégration partielle dans vLLM/TensorRT-LLM.

Références#

ia llm quantification qoq qserve w4a8kv4 smoothquant kv-cache throughput serving mit-han-lab