Pourquoi llama.cpp a gagné le local

Faire tourner un LLM sur sa propre machine était, il y a peu, réservé aux possesseurs de gros GPU. llama.cpp a renversé ça avec trois partis pris : pas de dépendances lourdes, un format de modèle autoportant, et une quantification suffisamment fine pour faire tenir un modèle 13 B dans la RAM d’un ordinateur portable. Comme on le voyait dans le comparatif des runtimes, c’est devenu le standard de fait du déploiement local.

GGUF : un format pensé pour le mmap

GGUF (GGML Universal File) stocke dans un seul fichier les tenseurs, leurs types de quantification, le vocabulaire et les métadonnées du modèle. Sa structure est alignée pour être lue par mmap : le fichier est mappé en mémoire virtuelle, et l’OS charge les pages à la demande.

Les schémas de quantification

Le cœur de llama.cpp, c’est sa famille de quantifications. Plutôt que de stocker chaque poids en 16 bits, on les regroupe par blocs et on encode chaque bloc avec une échelle commune et des indices de quelques bits. Les variantes « K » affinent ce schéma avec des sous-blocs et des échelles à deux niveaux.

SchémaBits / poidsTaille modèle 7BQualité
F1616≈ 14,5 GoRéférence
Q8_08≈ 7,7 GoQuasi identique
Q5_K_M≈ 5,5≈ 5,1 GoTrès bonne
Q4_K_M≈ 4,5≈ 4,4 GoBon compromis
Q2_K≈ 2,6≈ 2,8 GoDégradée
Tableau 1 — Schémas de quantification courants en GGUF (modèle 7 B, ordres de grandeur).

Le sweet spot pratique est presque toujours Q4_K_M : la perte de qualité reste faible et mesurable, pour une empreinte divisée par trois. En dessous de 3 bits, la dégradation devient nette et dépend beaucoup du modèle.

Le chemin d’un token

Comprendre llama.cpp, c’est suivre ce que devient un token entre l’entrée et la sortie.

Tokenizer texte → ids entrée
Graphe GGML couches ordonnancement
Kernels dequant + matmul calcul
Sampler logits → token sortie
Figure 1 — Étapes principales d'un pas de génération dans llama.cpp.

Le point intéressant est l’étape « kernels ». Les poids restent quantifiés en mémoire ; ils ne sont déquantifiés que dans le kernel, par blocs, juste avant la multiplication. On lit donc peu d’octets — bon pour la bande passante mémoire — pour un surcoût de calcul modéré.

Un kernel de déquantification

Voici la forme générale d’une déquantification de bloc Q4 : une échelle par bloc, des poids sur 4 bits, reconstruits à la volée.

ggml dequant_q4.c C
// Déquantifie un bloc Q4 : 32 poids 4 bits + une échelle f16
void dequantize_q4_block(const block_q4 * restrict b, float * restrict out) {
    const float scale = f16_to_f32(b->scale);
    for (int i = 0; i < 16; i++) {
        const uint8_t packed = b->qs[i];      // deux poids par octet
        const int lo = (packed & 0x0F) - 8;   // centré sur zéro
        const int hi = (packed >>   4) - 8;
        out[i]      = scale * (float) lo;
        out[i + 16] = scale * (float) hi;
    }
}

Tout est là : deux poids par octet, un décalage pour recentrer sur zéro, une échelle unique par bloc. Multiplié sur des millions de blocs, c’est ce qui transforme un modèle de 14,5 Go en un modèle de 4,4 Go sans réécrire le reste du moteur.

Quand llama.cpp n’est pas le bon choix

llama.cpp brille sur une à quelques requêtes. Pour servir un grand nombre d’utilisateurs concurrents, son absence de batching continu le pénalise face à vLLM. Et sur GPU datacenter récent, des runtimes spécialisés exploitent mieux les formats FP8/FP4 natifs. llama.cpp est l’outil du local et du portable — pas du service à grande échelle.

Conclusion

llama.cpp n’a pas gagné par magie : il a gagné parce que GGUF rend le chargement trivial et que sa quantification échange intelligemment un peu de qualité contre beaucoup de mémoire. C’est une leçon d’ingénierie système autant que d’IA. Pour le déploiement embarqué qui en découle, voir NPU vs GPU à l’edge.

Sources et méthode

Format GGUFspécification officielle dans le dépôt GGML (objectifs « single-file » et compatibilité mmap). La spécification ne développe pas l’acronyme ; l’expansion GGML Universal File est celle reprise par la documentation du projet.

Schémas de quantification — code source ggml-common.h de llama.cpp, qui définit chaque bloc et son coût effectif en bits par poids (block_q4_K « 4.5 bits », block_q5_K « 5.5 bits », block_q2_K « 2.625 bits »). Compromis qualité : outil quantize.

Déquantification — fonction de référence dequantize_row_q4_0 dans ggml-quants.c. Le code de l’article est une version simplifiée et fidèle de ce bloc Q4_0 ; il ne reproduit pas une variante « K » (super-blocs et échelles à deux niveaux).

Tailles de fichiers (modèle 7 B) — ordres de grandeur mesurés sur des dépôts GGUF publics, par exemple TheBloke/Mistral-7B-Instruct-v0.2-GGUF. Ils varient de quelques pour cent selon le modèle et le tokenizer.