Aller au contenu principal

Module 8 — Quantification et service à moindre coût

Un modèle de 8 milliards de paramètres en pleine précision demande une machine que la plupart des équipes n'ont pas. Pour notre assistant de support, la question est de savoir sur quel matériel il tournera : un accélérateur cloud haut de gamme facturé à l'heure, une carte grand public dans un serveur local, ou un simple CPU. La réponse dépend d'une opération qui a transformé le paysage depuis 2023 : la quantification. Ce module explique ce qu'elle fait, ses variantes, et le moteur d'inférence à choisir.

De la précision à la mémoire

Un paramètre en float32 occupe 4 octets. En float16 ou bfloat16, il en occupe 2. La quantification pousse plus loin en encodant chaque paramètre sur 8 bits (1 octet) ou 4 bits (un demi-octet). La mémoire suit directement.

MpoidsNb/8,M_{\text{poids}} \approx N \cdot b / 8,

NN est le nombre de paramètres et bb le nombre de bits par poids.

Modèlefp32bf16int8int4
7 milliards28 Go14 Go7 Go3,5 Go
13 milliards52 Go26 Go13 Go6,5 Go
70 milliards280 Go140 Go70 Go35 Go

Ces chiffres n'incluent que les poids. Le cache KV (module 6) et les activations en cours de calcul ajoutent typiquement 20 à 50 pour cent selon le contexte. Un modèle de 8 milliards en 4 bits tient donc sur une carte grand public de 8 Go, à condition d'accepter une fenêtre de contexte modeste.

Trois familles de quantification

Toutes les méthodes de quantification ne se valent pas. Trois dominent le paysage ouvert.

GPTQ (Frantar et al., 2022) quantifie les poids couche par couche en minimisant l'erreur sur un petit jeu de calibration. Elle produit des modèles 4 bits de bonne qualité pour l'inférence sur GPU. Fichiers typiques : .safetensors avec métadonnées GPTQ.

AWQ (Lin et al., 2023) part d'une observation : quelques poids ont un impact démesuré sur la sortie. Elle les préserve à haute précision et quantifie agressivement les autres. La qualité est en général meilleure que GPTQ à taille égale, et le débit d'inférence est excellent sur GPU récents.

GGUF (successeur de GGML, format de llama.cpp) est un format universel qui embarque poids, tokeniseur et métadonnées dans un seul fichier. Il propose plusieurs niveaux de quantification (Q4_K_M, Q5_K_S, Q8_0…) et vise l'inférence CPU efficace, avec accélération GPU optionnelle. C'est le format des modèles distribués sur Hugging Face pour un usage local sur portable.

FormatCibleQualité 4 bitsVitesse GPUVitesse CPU
GPTQGPUbonnerapidelente
AWQGPUmeilleuretrès rapidenon
GGUFuniverselbonne (Q4_K_M)rapide (avec offload)correcte

Pour notre assistant, la décision se joue en général entre AWQ pour un service haut débit et GGUF pour un déploiement compact.

Ce que la quantification coûte réellement

La perte de qualité liée à la quantification est modeste mais réelle. Sur les jeux de référence standards, un 4 bits bien fait perd 1 à 3 points de MMLU par rapport à la précision 16 bits d'origine. Ce chiffre cache une réalité plus fine.

  • La génération courte est presque inaffectée.
  • La génération longue accumule des micro-écarts qui, sur 500 jetons, peuvent modifier le contenu de façon perceptible.
  • Le raisonnement en chaîne (arithmétique, logique) est le plus sensible ; une quantification trop agressive fait chuter la performance de plusieurs dizaines de points.

Pour un support client dont les réponses tiennent en 100 à 300 jetons et n'exigent pas de raisonnement complexe, la quantification 4 bits est largement acceptable. Pour un assistant de codage ou un analyste de contrat, on préfère souvent 8 bits ou fp16.

# Chargement d'un modele quantifie AWQ avec transformers
from transformers import AutoTokenizer, AutoModelForCausalLM

nom = "TheBloke/Llama-3-8B-Instruct-AWQ"
tok = AutoTokenizer.from_pretrained(nom)
modele = AutoModelForCausalLM.from_pretrained(
nom,
device_map="auto",
torch_dtype="auto",
)

Aucune configuration supplémentaire n'est nécessaire : les métadonnées AWQ sont dans le dépôt, et transformers déquantifie à la volée pendant l'inférence.

vLLM, le traitement par lots continu

Servir un modèle avec transformers en boucle est correct pour un prototype, catastrophique en production. Le problème n'est pas la vitesse d'un jeton isolé mais l'usage du GPU quand plusieurs requêtes arrivent simultanément. Une implémentation naïve traite les requêtes une par une, ou en lots figés à taille fixe.

vLLM (Kwon et al., 2023) a introduit deux techniques qui font sauter le verrou.

  • PagedAttention stocke le cache KV en blocs comme la mémoire virtuelle d'un système d'exploitation, éliminant la fragmentation et permettant de partager des préfixes entre requêtes.
  • Continuous batching entrelace des requêtes qui commencent, se génèrent et finissent à des moments différents dans un même lot, sans attendre que le lot précédent soit terminé.

Le résultat : un débit deux à dix fois supérieur à un service naïf, sur le même matériel.

# Lancement d'un serveur vLLM compatible OpenAI (extrait de commande)
# python -m vllm.entrypoints.openai.api_server \
# --model TheBloke/Llama-3-8B-Instruct-AWQ \
# --quantization awq \
# --dtype auto \
# --max-model-len 8192 \
# --gpu-memory-utilization 0.9

from openai import OpenAI

client = OpenAI(base_url="http://localhost:8000/v1", api_key="local")
reponse = client.chat.completions.create(
model="TheBloke/Llama-3-8B-Instruct-AWQ",
messages=[
{"role": "system", "content": "Tu es l'assistant support d'ACME."},
{"role": "user", "content": "Comment retourner un article ?"},
],
temperature=0.3,
)
print(reponse.choices[0].message.content)

Le serveur expose une interface compatible avec l'API OpenAI : le même code Python vaut pour un modèle local ou un modèle externe.

llama.cpp, le service sur CPU

Toutes les équipes n'ont pas d'accélérateur. llama.cpp est un moteur écrit en C++ optimisé pour l'inférence CPU, avec accélération GPU en option. Ses forces : dépendances minimales, portabilité totale, fichier unique GGUF.

Sur un serveur CPU récent (16 cœurs), un modèle de 8 milliards en Q4_K_M produit typiquement 5 à 15 jetons par seconde. Pour un assistant de support qui génère 200 jetons par réponse, cela donne une latence de 15 à 40 secondes — inutilisable pour un tchat, acceptable pour un traitement asynchrone comme la génération de brouillons de réponse.

# Servir un modele GGUF avec l'interface HTTP de llama.cpp
# ./llama-server -m modeles/llama-3-8b-instruct.Q4_K_M.gguf \
# --host 0.0.0.0 --port 8080 --ctx-size 4096

Le seuil de décision, dans notre projet, est le trafic. Sous quelques milliers de requêtes par jour, un CPU dédié suffit et coûte moins de cinquante euros par mois. Au-delà, la latence oblige à passer sur GPU.

Débit contre latence, le compromis à connaître

Deux métriques mesurent le service.

  • Latence : temps entre requête et réponse pour une requête isolée.
  • Débit : nombre de requêtes traitées par seconde à charge soutenue.

vLLM optimise le débit ; llama.cpp sur CPU optimise le coût. Un même GPU peut afficher 40 jetons par seconde en latence à requête unique et 800 jetons par seconde en débit agrégé sur 20 requêtes simultanées. Le passage de l'un à l'autre demande simplement des requêtes concurrentes.

Requeˆtes par secondejetons geˊneˊreˊs par seconde en deˊbitjetons moyens par reˊponse.\text{Requêtes par seconde} \approx \frac{\text{jetons générés par seconde en débit}}{\text{jetons moyens par réponse}}.

Sur notre assistant, un débit de 800 jetons/s avec des réponses moyennes de 200 jetons donne 4 requêtes/s soutenues, soit 350 000 conversations par jour sur une seule carte. C'est le chiffre qui rend l'auto-hébergement crédible pour toute entreprise sérieuse.

La mémoire promise n'est pas toute la mémoire utilisée

Un modèle 4 bits de 8 milliards de paramètres tient sur 3,5 Go de poids, mais le cache KV pour un contexte de 8 000 jetons et un lot de 8 requêtes ajoute facilement 5 à 8 Go. Compter la mémoire uniquement sur les poids conduit à sous-dimensionner et à voir le service saturer sous la première charge. Toujours ajouter au moins 50 pour cent de marge pour cache et activations.

Mesurer avant d'acheter

Avant d'engager un budget matériel, faites tourner un banc d'essai avec vos vraies requêtes : cent invites représentatives, débit soutenu sur cinq minutes, latence p95. Les chiffres publiés par les vendeurs de GPU sont optimistes par construction. Un banc maison coûte une journée d'ingénieur et évite des mois de sur-dimensionnement.

En résumé

  • La quantification ramène la mémoire d'un LLM à un tiers ou un huitième de sa valeur d'origine, pour une perte de qualité modeste sur les tâches de génération courte.
  • AWQ et GGUF dominent aujourd'hui : AWQ pour un service GPU à haut débit, GGUF pour un déploiement portable CPU ou hybride.
  • vLLM avec son continuous batching et sa PagedAttention multiplie le débit d'inférence sur GPU, condition d'un service économique.
  • Le vrai compromis n'est pas qualité contre coût, mais latence contre débit ; le même matériel sert deux régimes très différents selon la simultanéité des requêtes.

Module suivant : mesurer la qualité d'un LLM sans se laisser abuser par les jeux de référence publics.