Le KV Cache sur des GPU de 16 Go : faire tenir réellement les contextes longs
Pourquoi le contexte de 128 Ko échoue sur 16 Go
Un modèle peut revendiquer une fenêtre de contexte de 128K et échouer pourtant à 40K jetons sur un GPU de 16 Go. La limite architecturale ne promettait jamais que les poids, le cache KV, les tampons de calcul et le compositeur de bureau tiendraient simultanément sur votre carte.
Le cache KV est généralement le point où les plans de long contexte rencontrent cette limite physique. Il grandit avec chaque jeton actif et chaque séquence, si bien qu’une configuration qui semble confortable au démarrage peut ralentir brutalement, déborder vers la mémoire système ou échouer lors d’un grand préremplissage.

Ce guide transforme le problème en un budget VRAM. Il couvre la formule du cache, des tables de taille reproductibles de 32K à 128K, et des configurations fonctionnelles pour --cache-type-k et --cache-type-v de llama.cpp, le cache paginé et par préfixe de vLLM, et les contrôles de contexte d’Ollama — plus les forks expérimentaux à cache adaptatif qui méritent intérêt mais pas confiance aveugle. Pour le contexte plus large de débit, latence et benchmarks derrière ces chiffres, commencez par le hub Performance LLM.
La réponse courte pour un GPU de 16 Go
Commencez avec une seule séquence, un contexte maximal réaliste, Flash Attention et un cache KV en 8 bits. Mesurez cette configuration avant d’essayer le cache en 4 bits, le déchargement CPU, plusieurs emplacements parallèles ou un fork expérimental.
| Cible | Première tentative raisonnable sur 16 Go | Risque principal |
|---|---|---|
| 32K | Poids Q4 ou Q5, KV Q8, une séquence | Les poids du modèle laissent trop peu d’espace tampon |
| 64K | Modèle plus petit ou quantification des poids agressive, KV Q8 | Latence de préremplissage et bande passante du cache |
| 128K | Modèle GQA petit, KV Q8 ou Q4 testé, une séquence | Le cache seul peut consommer la majeure partie du VRAM |
| Deux sessions 64K concurrentes | Traiter comme un budget cache d’environ 128K | La capacité parallèle est confondue avec un débit gratuit |
Mon opinion est simple : une configuration 64K stable est généralement plus utile qu’une configuration 128K nominale qui tourne au bord d’un échec de mémoire insuffisante. La capacité de contexte n’est pas un trophée ; c’est une décision de latence, de qualité et de concurrence.
Ce que stocke le cache KV
Pendant la génération auto-régressive, chaque couche d’attention produit des tenseurs de clés et de valeurs pour chaque jeton traité. Le runtime retient ces tenseurs pour que le jeton suivant puisse prêter attention aux jetons antérieurs sans recalculer tout le préfixe.
Le cache sauve un calcul énorme, mais il consomme de la mémoire en proportion du nombre de jetons retenus. Pour un transformateur conventionnel avec attention à requêtes groupées, une base utile est :
octets KV = séquences * jetons * couches * 2 * têtes KV * dimension de tête * octets par valeur
Le facteur deux représente les clés et les valeurs. L’attention multi-têtes utilise autant de têtes KV que de têtes de requête, l’attention à requêtes groupées utilise moins de têtes KV, et l’attention latente multi-têtes ou les architectures récurrentes hybrides nécessitent des calculs différents.
Pourquoi le nombre de paramètres ne suffit pas
Deux modèles de 8B peuvent avoir des coûts de cache KV très différents. L’un peut utiliser 32 couches et huit têtes KV, tandis qu’un autre peut utiliser moins de têtes KV, des couches KV partagées, l’attention à fenêtre glissante ou des états latents compressés.
Le nombre de paramètres prédit principalement la mémoire des poids. La géométrie KV provient de l’architecture d’attention, il faut donc lire les métadonnées du modèle plutôt que de deviner à partir de 8B, 27B ou de la taille du fichier GGUF. L’illustration la plus claire est de voir à quel point la conception de l’attention a dépassé la simple attention multi-têtes (MHA) :
- Attention Multi-Query (MQA) partage une seule tête K/V sur toutes les têtes de requête — économies de cache maximales, mais c’est le compromis de qualité le plus agressif et il est rarement utilisé seul dans les modèles de pointe actuels.
- Attention Grouped-Query (GQA) regroupe les têtes de requête en clusters qui partagent chacun une tête K/V — le compromis majeur utilisé par la plupart des modèles denses ouverts, et la géométrie que la formule ci-dessus suppose.
- Attention Multi-Head Latent (MLA), introduite dans DeepSeek-V2 et reprise dans DeepSeek-V3 et Kimi K2, adopte une approche entièrement différente : au lieu de partager les K/V entre les têtes, elle projette les clés et les valeurs dans un vecteur latent de basse rangée compressé et reconstruit les K/V pleine résolution à la demande au moment de l’attention. DeepSeek a rapporté une réduction de cache KV d’environ 93 % par rapport à un modèle dense MHA de taille équivalente, tout en maintenant une qualité concurrente avec — parfois supérieure à — la GQA pour le même budget mémoire.
La conséquence pratique est qu’un “modèle GQA 27B” et un “modèle MLA 27B” peuvent avoir des empreintes de cache KV qui diffèrent d’un ordre de grandeur pour la même longueur de contexte. Ne supposez pas que la formule ci-dessus s’applique à un modèle qui se documente comme utilisant l’attention latente, l’état de type DeltaNet ou des couches à fenêtre glissante — vérifiez d’abord la section architecture de la fiche modèle.
Avec Ollama, ollama show MODEL --verbose expose les métadonnées du modèle y compris le nombre de couches, les têtes d’attention, les têtes KV et la longueur de contexte si le format les fournit. Avec llama.cpp, la sortie du chargeur de modèle imprimée au démarrage inclut généralement les métadonnées GGUF équivalentes et l’allocation de cache réelle du runtime.
Limite du modèle, contexte alloué et contexte utilisé
Ce sont trois nombres distincts. La limite du modèle est le maximum supporté par son entraînement et son encodage positionnel, le contexte alloué est ce que le runtime réserve ou autorise, et le contexte utilisé est les jetons actuellement retenus pour une séquence.
Augmenter un drapeau d’ingénierie ne peut pas étendre en toute sécurité un modèle au-delà de son schéma de positions supporté. Le mise à l’échelle RoPE peut étendre certaines architectures, mais c’est une expérimentation de qualité du modèle, pas une optimisation de la mémoire KV.
Table de taille du cache KV : budgets de contexte 32K, 64K et 128K
Considérons un modèle GQA représentatif avec 32 couches, huit têtes KV et une dimension de tête de 128. Ces dimensions produisent 65 536 éléments de clés et de valeurs par jeton avant multiplication par la taille de stockage de chaque élément.
Le tableau utilise des Go binaires et les tailles de blocs physiques généralement associées à f16, q8_0 et q4_0 de llama.cpp. C’est un calcul de base, pas une promesse concernant la mémoire totale du processus ; l’alignement, les métadonnées, les couches hybrides et les espaces de travail de l’arrière-plan ajoutent des surcoûts.
| Type de cache | Oct. approx. par valeur stockée | Contexte 32K | Contexte 64K | Contexte 128K |
|---|---|---|---|---|
| F16 | 2,0000 | 4,00 Go | 8,00 Go | 16,00 Go |
| Q8_0 | 1,0625 | 2,13 Go | 4,25 Go | 8,50 Go |
| Q4_0 | 0,5625 | 1,13 Go | 2,25 Go | 4,50 Go |
| Q8_0 K plus Q4_0 V | Mixte | 1,63 Go | 3,25 Go | 6,50 Go |
Doublez maintenant le nombre de couches à 64 en laissant les autres dimensions inchangées. Le cache FP16 devient de 8 Go à 32K, 16 Go à 64K, et 32 Go à 128K, ce qui démontre pourquoi une recommandation unique de contexte ne peut pas couvrir tous les modèles.
L’équation réelle de 16 Go
Un budget pratique est plus large que la formule KV :
VRAM utilisable = VRAM total - réserve bureau et pilote
Budget KV = VRAM utilisable
- poids du modèle résidents sur GPU
- tampons de graphe et d'activation
- espace de travail du runtime
- état de décodage spéculatif
- marge de sécurité
Sur une carte de 16 Go attachée à un écran, ne planifiez pas autour de la disponibilité de tous les 16 Go. Réservez au moins quelques centaines de Mo pour le bureau et le pilote, puis laissez une autre marge pour les tampons dépendants de la charge de travail ; 1,0 à 1,5 Go de marge de manœuvre totale est une hypothèse de départ raisonnable, mais vos journaux sont l’autorité.
Supposons qu’un modèle GGUF occupe 10,8 Go sur le GPU et que les surcoûts du runtime culminent à environ 1,2 Go. Après une marge de sécurité de 1 Go, il ne reste qu’environ 3 Go pour le KV, de sorte que le modèle représentatif tient environ 45K jetons avec Q8_0 ou 87K avec Q4_0, avant les surcoûts spécifiques au moteur.
Cela ne fait pas automatiquement de Q4_0 le bon choix. Si la précision du long contexte diminue sur votre charge de travail, un modèle plus petit ou plus agressivement quantifié avec un cache Q8_0 peut être meilleur que des poids plus grands appariés à un cache fragile. Les points d’ancrage mesurés pour exactement cette arithmétique se trouvent dans les tableaux de benchmark llama.cpp 16 Go VRAM, où le VRAM par modèle est enregistré à 19K, 32K et 64K de contexte. Pour une enquête plus large sur les tailles de modèle et les niveaux de quantification qui se comportent bien sous Ollama sur la même classe de carte, voir Comparaison des performances de LLM sur Ollama sur GPU 16 Go VRAM.
Calculer le budget du cache KV pour votre modèle
Le extrait Python suivant estime un cache GQA d’attention complète conventionnel. Remplacez la géométrie par les valeurs de la configuration du modèle ou des métadonnées GGUF.
def kv_gib(tokens, layers, kv_heads, head_dim, bytes_per_value, sequences=1):
total = (
sequences
* tokens
* layers
* 2
* kv_heads
* head_dim
* bytes_per_value
)
return total / (1024 ** 3)
model = {
"layers": 32,
"kv_heads": 8,
"head_dim": 128,
}
types = {
"f16": 2.0,
"q8_0": 34 / 32,
"q4_0": 18 / 32,
}
for tokens in (32768, 65536, 131072):
row = {
name: round(kv_gib(tokens=tokens, bytes_per_value=size, **model), 2)
for name, size in types.items()
}
print(tokens, row)
Les rapports Q8_0 et Q4_0 incluent des métadonnées de bloc simples, c’est pourquoi ils sont légèrement plus grands qu’exactement un octet et la moitié d’un octet par valeur. Le rapport de démarrage du runtime reste plus précis car il connaît les dispositions de cache spécifiques au modèle.
Quand cette formule est fausse : architectures hybrides et à fenêtre glissante
Ne forcez pas les architectures hybrides dans l’équation GQA conventionnelle. Les couches à fenêtre glissante ne retiennent qu’une fenêtre récente, les couches KV partagées réduisent la duplication, les couches récurrentes peuvent porter un état de taille fixe, et l’attention latente multi-têtes stocke une représentation compressée plutôt que des tenseurs K/V par tête — le cas MLA ci-dessus étant l’exemple le plus spectaculaire.
Les moteurs modernes gèrent de plus en plus explicitement ces dispositions mixtes. Utilisez la formule pour expliquer les termes dominants, puis confirmez l’allocation rapportée par la version exacte du moteur et l’arrière-plan que vous prévoyez de déployer.
llama.cpp : Contrôle direct de la précision K et V
llama.cpp expose les options distinctes --cache-type-k et --cache-type-v dans son analyseur d’arguments actuel. C’est l’interface d’inférence locale la plus utile lorsque vous devez échanger la précision du cache contre la capacité de contexte au lieu d’accepter un preset global. Si vous avez besoin d’abord de l’installation et de la configuration de service environnantes, le guide llama.cpp couvre llama-cli, llama-server et les drapeaux VRAM clés.
Une configuration 64K conservatrice pour un seul utilisateur ressemble à ceci :
./llama-server \
--model /models/model.gguf \
--n-gpu-layers 999 \
--ctx-size 65536 \
--parallel 1 \
--flash-attn on \
--cache-type-k q8_0 \
--cache-type-v q8_0 \
--batch-size 1024 \
--ubatch-size 256
La syntaxe des drapeaux et le support de l’arrière-plan changent rapidement, exécutez donc llama-server --help pour la version installée. Plus important encore, inspectez le journal de démarrage : il doit montrer le contexte prévu, les types de cache, le déchargement GPU et les tampons K et V alloués.
Quels types de cache llama.cpp essayer
Commencez par Q8_0 pour les K et les V. Il réduit approximativement la mémoire KV par deux par rapport à F16, et des tests indépendants de perplexité sur des modèles de 20B et plus (Qwen3.6-27B, Nemotron-30B) montrent que l’écart de qualité agrégé par rapport à F16 est dans le bruit de mesure — un pari beaucoup moins dramatique que de passer directement à Q4_0, que les mêmes tests ont montré comme effondrant la vitesse de décodage et la précision à long contexte sur les modèles plus petits.
Si Q8_0 ne tient pas, testez les clés Q8_0 avec les valeurs Q4_0 avant de quantifier les deux côtés en Q4_0. Cet ordre a un soutien de recherche, pas seulement de la folklore : des études contrôlées d’allocation de bits sur les checkpoints Llama, Phi-4, Qwen3 et Mistral ont trouvé que les tenseurs de clés sont constamment deux à dix fois plus sensibles à l’erreur de quantification que les tenseurs de valeurs, et que donner aux clés le budget de bits le plus grand (par exemple 4 bits clés avec 2 bits valeurs) récupère jusqu’à 94–98 % de la précision pleine précision — tandis que l’inverse (2 bits clés, 4 bits valeurs) peut perdre 30 points de pourcentage sur des tâches comme GSM8K. Les clés déterminent à quels jetons antérieurs l’attention correspond réellement, donc les protéger en premier est le choix architecturalement sain, pas seulement le son le plus sûr.
| Configuration | Mémoire | Risque de qualité | Recommandation |
|---|---|---|---|
| K et V F16 | Plus élevée | La plus faible | Base lorsque ça tient |
| K et V Q8_0 | À peu près la moitié de F16 | Faible mais pas nul | Point de départ 16 Go par défaut |
| K Q8_0, V Q4_0 | Entre Q8 et Q4 | Modéré | Deuxième étape utile |
| K et V Q4_0 | À peu près le quart de F16 | Le plus élevé | Valider à la profondeur cible |
Une mise en garde qu’il vaut la peine d’intégrer : “risque de qualité faible” sur les benchmarks agrégés ne signifie pas zéro risque au niveau du jeton. Un test contrôlé qui a maintenu Flash Attention constant et n’a changé que la précision KV sous décodage glouton (déterministe) a trouvé que le cache Q8_0 a modifié le texte généré exact sur la grande majorité des prompts, et Q4_0 l’a modifié sur l’essentiel d’entre eux — dès qu’un jeton bascule, le reste de la suite peut diverger. La perplexité et les scores de tâches en aval peuvent sembler bien en moyenne tandis que les sorties individuelles diffèrent encore de la base F16. Si votre application a besoin de reproductibilité octet par octet (tests de régression, réponses en cache, agents déterministes), traitez toute quantification KV comme un changement comportemental, pas seulement comme une optimisation mémoire, et validez contre votre propre ensemble de prompts fixes.
Le cache V quantifié peut nécessiter Flash Attention ou un chemin d’arrière-plan compatible. Un serveur qui bascule silencieusement sur un autre type invalide l’expérience, c’est pourquoi les journaux de démarrage comptent plus que les lignes de commande copiées.
Contexte, emplacements parallèles et cache unifié
--ctx-size décrit une capacité du moteur, pas une garantie que chaque emplacement parallèle reçoive autant de jetons indépendamment. La gestion du cache a évolué dans llama.cpp, y compris le comportement de cache unifié, donc testez la version exacte plutôt que de vous fier à une règle ancienne qui divise simplement le contexte par le nombre d’emplacements.
L’équation de capacité survit encore aux changements d’implémentation : les jetons uniques simultanés ont besoin d’un stockage quelque part. Si deux sessions d’agent peuvent chacune atteindre 48K, prévoyez environ 96K jetons vivants à moins que la charge de travail ne partage des préfixes ou ne tolère l’éviction et le recalcul.
La taille du lot ne réduit pas le KV stocké
--batch-size et --ubatch-size affectent le traitement des prompts et la mémoire temporaire. Les réduire peut sauver un grand préremplissage d’un pic de mémoire d’activation, mais cela ne change pas les octets persistants nécessaires pour chaque jeton retenu.
Cette distinction explique un motif d’échec courant : le modèle démarre et une demande vide fonctionne, mais un prompt de 60K échoue pendant l’ingestion. Réduisez le micro-lot pour diagnostiquer le pic transitoire ; réduisez le contexte, la précision du cache, le parallélisme ou la résidence des poids pour changer la capacité persistante.
vLLM : la capacité paginée reste une capacité
vLLM aborde le problème comme un moteur de service. Il profile la mémoire disponible, réserve un pool de cache KV et alloue le cache par blocs pour que les séquences concurrentes n’aient pas chacune besoin d’une grande région contiguë. Si vous décidez si vous devriez passer à vLLM en premier lieu, le guide de migration d’Ollama à vLLM couvre les signaux de charge de travail ; ici la question est purement combien de cache le pool peut contenir, et le démarrage rapide vLLM couvre l’installation et les drapeaux de service généraux au-delà des leviers de capacité ci-dessous.
PagedAttention réduit la fragmentation et le gaspillage autour des longueurs de séquence variables — l’allocation paginée élimine la fragmentation, pas le coût de stockage par jeton, donc une demande unique de 128K a toujours besoin de suffisamment de blocs pour son état KV.
Le guide officiel de conservation de la mémoire vLLM recommande de limiter max_model_len et max_num_seqs lorsque la mémoire est tendue, et note que les graphes CUDA consomment une mémoire GPU supplémentaire. Sur une carte de 16 Go, les deux paramètres devraient être intentionnels plutôt qu’hérités de la configuration maximale d’un modèle.
Un serveur de séquence unique ciblé pourrait commencer ici :
vllm serve MODEL_ID \
--max-model-len 65536 \
--max-num-seqs 1 \
--gpu-memory-utilization 0.90 \
--kv-cache-dtype fp8 \
--enable-prefix-caching
Tous les GPU de 16 Go, modèles, méthodes de quantification ou arrière-plans d’attention ne supportent pas cette combinaison exacte. Traitez-le comme une forme de configuration : contraindre la longueur et la concurrence, réserver une marge, sélectionner un dtype de cache supporté et valider le rapport d’initialisation.
Cache KV FP8 dans vLLM
La documentation actuelle sur le cache KV quantifié vLLM supporte les formats de cache FP8 sur les chemins CUDA et ROCm compatibles. FP8 réduit approximativement le stockage brut du cache par deux par rapport à BF16 ou FP16 et peut donc augmenter la capacité de jetons ou la concurrence.
L’échelle compte. La documentation distingue les échelles par défaut, le calcul de mise en place et la calibration par jeu de données, et recommande la calibration par jeu de données pour la plus haute précision ; simplement définir FP8 avec une échelle de 1,0 est pratique mais n’est pas automatiquement le choix de qualité le plus fiable.
Le cache de préfixe est une optimisation de réutilisation
Le cache de préfixe automatique permet à une nouvelle requête de réutiliser les blocs KV pour un préfixe identique mis en cache. C’est excellent pour les requêtes répétées sur le même long document, les prompts système partagés et les conversations multi-tours car il évite de recalculer le préremplissage correspondant.
Cela ne rend pas une longue requête unique plus petite, et cela n’accélère pas la génération de nouveaux jetons. La documentation du cache de préfixe vLLM limite explicitement le bénéfice au travail de préremplissage de préfixe partagé.
L’utilisation de la mémoire GPU n’est pas une mémoire gratuite
Augmenter --gpu-memory-utilization donne à vLLM un objectif de réservation plus grand, mais cela ne crée pas de VRAM. Le pousser trop près de 1,0 peut laisser une place insuffisante pour l’affichage, un autre processus, des pics d’activation changeants ou des allocations non-PyTorch.
Commencez autour de 0,88 à 0,92 sur un GPU dédié de 16 Go, inspectez le profil et n’augmentez que si la charge de travail reste stable. Si l’initialisation réussit mais que de vrais prompts échouent, réduisez les jetons lotis, la concurrence de séquence, la capture du graphe CUDA ou le contexte maximal avant de supposer que l’allocateur est cassé.
Ollama : contrôles plus faciles, diagnostic moins granulaire
Ollama fournit délibérément une surface opérationnelle plus petite. Sa documentation de longueur de contexte actuelle définit une longueur de contexte par défaut de 4K pour les GPU inférieurs à 24 Go, recommande au moins 64K pour les charges de travail d’agent et de codage, et avertit qu’un contexte plus grand consomme plus de mémoire.
Définissez la valeur par défaut globale du serveur et confirmez le modèle chargé comme ceci :
OLLAMA_CONTEXT_LENGTH=65536 ollama serve
ollama ps
Vous pouvez aussi définir num_ctx par requête ou par modèle. ollama ps est important car ses colonnes PROCESSOR et CONTEXT révèlent si le modèle est resté entièrement sur le GPU et si le contexte demandé a réellement été alloué. Soyez conscient que le comportement de planification derrière ces nombres a changé entre les versions d’Ollama ; ma comparaison de l’allocation mémoire Ollama v0.12.1 montre que le nouvel ordonnanceur pousse certains modèles plus loin vers le CPU sur une carte de 16 Go, donc fixez la version que vous avez mesurée.
Cache KV quantifié dans Ollama
Ollama expose OLLAMA_KV_CACHE_TYPE avec les choix f16, q8_0 et q4_0 dans sa FAQ actuelle. Le cache KV quantifié nécessite Flash Attention, que Ollama utilise automatiquement sur les arrière-plans supportés ou qui peut être demandé avec OLLAMA_FLASH_ATTENTION=1.
Un service long contexte de 16 Go peut donc être démarré comme ceci :
OLLAMA_CONTEXT_LENGTH=65536 \
OLLAMA_FLASH_ATTENTION=1 \
OLLAMA_KV_CACHE_TYPE=q8_0 \
OLLAMA_NUM_PARALLEL=1 \
ollama serve
Q8_0 est l’alternative recommandée par Ollama à F16. La FAQ avertit que Q4_0 peut produire une perte de qualité plus notable, surtout à plus grand contexte, donc il devrait être un repli mesuré plutôt qu’un preset 16 Go automatique.
Le parallélisme Ollama multiplie le budget de contexte
Ollama documente une règle particulièrement claire : la mémoire requise varie avec OLLAMA_NUM_PARALLEL * OLLAMA_CONTEXT_LENGTH. Quatre requêtes parallèles à une configuration de 32K peuvent impliquer une allocation de contexte agrégée de 128K pour ce modèle.
Pour un agent personnel sur 16 Go, maintenez OLLAMA_NUM_PARALLEL=1 jusqu’à ce qu’une longue session soit stable. Mettre une deuxième requête en file d’attente est généralement préférable à pousser le premier modèle partiellement sur le CPU et ralentir les deux requêtes. Les mécanismes de file d’attente, 503 et de déchargement de modèle derrière ce choix sont documentés dans comment Ollama gère les requêtes parallèles.
Déchargement CPU : une issue de secours valide avec un prix
Déplacer certaines couches du modèle ou l’état KV vers la mémoire système peut transformer un échec d’allocation en un processus fonctionnel. Cela place également la bande passante PCIe et la latence de la mémoire hôte dans le chemin de décodage, où chaque jeton généré peut payer le coût. La preuve des pistes et de la génération de savoir quand le PCIe mord vraiment est dans Performance LLM et pistes PCIe.
Le déchargement peut être raisonnable pour un travail par lots occasionnel, mais il est rarement le meilleur défaut par défaut pour un agent de codage interactif. Comparez d’abord une quantification de poids plus petite, un KV Q8, une concurrence réduite et une limite de contexte réaliste ; utilisez le déchargement lorsque la capacité compte plus que la latence.
Regardez la falaise plutôt que la moyenne. Un serveur peut décoder rapidement à 8K, puis ralentir gravement après qu’une partie de l’ensemble de travail déborde, donc effectuez des benchmarks à 32K, 64K et le maximum prévu plutôt que de rapporter seulement un taux de jetons à contexte vide.
Caches KV à fenêtre glissante et adaptatifs
L’attention à fenêtre glissante change le budget en ne retenant qu’une fenêtre récente pour les couches sélectionnées. Les modèles hybrides peuvent combiner ces couches avec une attention globale occasionnelle ou un état récurrent, faisant en sorte qu’un calcul de contexte complet plat surestime considérablement ou mal place la mémoire.
L’optimisation fait partie de l’architecture du modèle, pas d’un commutateur générique qui peut être appliqué sans conséquences. Un moteur doit comprendre correctement le motif de couche, les règles d’éviction, les positions et tout jeton global.
Ce que le KV adaptatif essaie d’améliorer
Les forks expérimentaux vont plus loin en choisissant la précision ou la disposition du cache par couche et par profondeur de contexte. L’objectif est séduisant : préserver une plus haute précision là où ça compte, compresser les couches moins sensibles et changer le mélange avant que la pression VRAM ne cause un débordement dur — la même découverte de sensibilité des clés par rapport aux valeurs décrite ci-dessus est exactement le type de signal qu’un allocateur adaptatif voudrait exploiter automatiquement au lieu de le laisser au réglage manuel --cache-type-k/--cache-type-v.
Un projet dérivé d’août 2026, llama.cpp-adaptive-turboquant, rapporte un sélecteur automatique pour plusieurs modes adaptatifs par couche et publie des tests de grande profondeur sur un RTX 5080 16 Go. Ces chiffres sont des résultats rapportés par l’auteur d’un fork spécialisé, pas une preuve que llama.cpp amont se comporte de la même façon.
Pourquoi c’est toujours expérimental
Le fork combine des types de cache personnalisés, des noyaux CUDA, des chemins spécifiques au modèle et des contraintes d’outils. C’est beaucoup plus de code à faire confiance que de changer le stockage de cache amont de F16 à Q8_0.
Utilisez un tel fork uniquement lorsque l’amont ne peut pas répondre à une exigence réelle et que vous pouvez reproduire la qualité, la stabilité et la vitesse sur votre modèle. Enregistrez le commit et la version CUDA, car un résultat attaché uniquement à un nom de projet n’est pas reproductible.
Un test de cache adaptatif équitable
Comparez le fork contre une base Q8_0 amont avec le même GGUF, le même prompt, le même échantillonneur, la même profondeur de contexte et la même longueur de sortie. Mesurez le VRAM au démarrage, le VRAM de pic de préremplissage, la vitesse de traitement du prompt, la vitesse de décodage et une tâche de qualité qui a réellement besoin de preuves de la partie la plus ancienne du contexte.
N’acceptez pas une allocation réussie comme un résultat complet. Un cache peut contenir 128K et encore perdre des faits précoces, corrompre la sortie tard dans la séquence ou décoder trop lentement pour être utile.
Une procédure d’ajustement 16 Go détaillée : une variable à la fois
Le chemin le plus rapide vers une configuration stable est de changer une dimension mémoire à la fois. Modifier aléatoirement le type de cache, la taille du lot, le déchargement de couche, le parallélisme et le contexte ensemble produit une commande fonctionnelle sans explication.
Étape 1 : Établir le plancher des poids
Chargez le modèle à contexte 8K, une séquence et le déchargement GPU prévu. Enregistrez le VRAM du processus après la mise en place et vérifiez que aucune couche n’est déplacée inattendu vers le CPU.
Si les poids et le runtime consomment déjà plus d’environ 14,5 à 15 Go, le long contexte n’a pas de marge saine. Choisissez une quantification de poids plus petite ou un modèle avant d’ajuster le cache.
Étape 2 : Mesurer KV F16 ou BF16 comme base de qualité
Exécutez le plus petit contexte qui supporte votre test et retenez le cache haute précision par défaut. Enregistrez les sorties des tâches de récupération, d’édition de code, de sélection d’outil et de longue instruction.
Cette base vous dit si les erreurs ultérieures proviennent de la quantification du cache. Sans elle, un problème de modèle de chat ou un modèle faible peut facilement être blâmé au KV Q4.
Étape 3 : Passer à Q8 ou FP8
Activez Flash Attention si nécessaire, sélectionnez Q8_0 dans llama.cpp ou Ollama, ou un mode FP8 supporté dans vLLM. Répétez les mêmes prompts aux mêmes profondeurs de jetons et confirmez que le journal montre le type de cache prévu.
Pour de nombreux déploiements de 16 Go, c’est le point d’arrêt utile. Il double approximativement la capacité brute du KV sans faire de la compression du cache la quantification la plus agressive de la pile.
Étape 4 : Monter le contexte par étapes
Testez 32K, 64K, 96K et 128K au lieu de sauter directement au maximum revendiqué. À chaque étape, enregistrez les jetons de traitement du prompt par seconde, les jetons de décodage par seconde, le VRAM de pic et si les preuves près du début peuvent encore être récupérées.
Le décodage long contexte ralentit souvent même après que la mémoire tient parce que l’attention lit plus d’état en cache. La capacité et la performance sont des axes distincts.
Étape 5 : Ajuster la mémoire transitoire
Si l’échec se produit pendant le préremplissage plutôt que l’initialisation, réduisez le micro-lot ou les jetons lotis maximaux. Si l’échec se produit seulement avec des requêtes simultanées, réduisez la concurrence de séquence ou les emplacements parallèles.
Seulement après que ces contrôles sont compris devriez-vous essayer le cache mixte Q8/Q4, le cache Q4 complet, le déchargement CPU ou un fork adaptatif. Gardez l’exécution Q8 amont comme base de comparaison. Si vous ajoutez plus tard du décodage spéculatif ou MTP, souvenez-vous que ses tampons de brouillon sont une autre ligne dans l’équation de budget, pas une vitesse gratuite — le guide du décodage spéculatif couvre les mécaniques et leur coût VRAM, et mon benchmark MTP vs Standard pour Qwen 3.6 27B et 35B montre exactement combien de contexte l’état supplémentaire d’une tête MTP peut coûter sur une carte de 16 Go.
Ce qu’il faut enregistrer dans un benchmark long contexte
Une seule valeur de jetons/s cache le problème exact que cet article essaie de résoudre. Les tests long contexte devraient conserver suffisamment de détails pour qu’un autre opérateur puisse reproduire la limite de mémoire.
| Champ | Pourquoi c’est important |
|---|---|
| GPU et VRAM utilisable | L’utilisation de l’affichage et d’autres processus changent le budget |
| Version du moteur ou commit | Le comportement du cache et les drapeaux évoluent rapidement |
| Version du pilote, CUDA, ROCm ou Vulkan | Détermine le comportement de l’arrière-plan et des noyaux |
| Modèle exact et quantification des poids | Définit la résidence des poids et l’architecture |
| Types de cache K et V | Définit la taille du cache persistant et le risque de qualité |
| Capacité de contexte et profondeur du prompt | L’allocation n’est pas la même que la profondeur réelle |
| Séquences parallèles | Multiplie ou partage la demande de cache |
| Lot et micro-lot | Affecte les pics de préremplissage et la vitesse |
| Vitesse de traitement du prompt | Révèle l’utilité du long préremplissage |
| Vitesse de décodage à chaque profondeur | Révèle le ralentissement de bande passante du cache |
| VRAM de pic et déchargement CPU | Distingue l’ajustement du débordement |
| Résultat de qualité long contexte | Détecte les échecs de compression ou de position |
Utilisez l’échantillonnage nvidia-smi ou l’outillage de l’éditeur équivalent pendant le préremplissage et le décodage. Le rapport d’allocation du moteur est nécessaire, mais la mémoire de périphérique de pic pendant un prompt réel est le chiffre qui décide de la stabilité.
Erreurs courantes de cache KV sur les GPU de 16 Go
Traiter le support 128K comme une promesse matérielle
Le champ de contexte dans une configuration de modèle est une limite architecturale. Il ne dit rien sur la mémoire restante après le chargement d’une quantification particulière sur un moteur particulier.
Calculez le cache et vérifiez le runtime. Un contexte de taille marketing sans budget VRAM est simplement un OOM retardé jusqu’au premier prompt sérieux.
Quantifier les poids mais oublier le KV
Un GGUF 4 bits réduit les poids du modèle, pas un cache KV F16. À long contexte, le cache peut effacer toute l’économie et finir par dépasser l’empreinte des poids.
Rapportez les deux quantifications. Modèle Q4_K_M, KV Q8_0 est significatif ; modèle 4 bits est incomplet.
Supposer que l’attention paginée comprime les jetons
La pagination améliore le comportement d’allocation et de partage. Elle ne change pas la précision des tenseurs ni n’élimine l’état KV requis par une séquence unique.
Utilisez l’allocation paginée pour servir efficacement les charges de travail variables. Utilisez la précision du cache, l’architecture du modèle, les limites de contexte et les limites de concurrence pour contrôler la capacité.
Supposer que le cache de préfixe aide tous les longs prompts
Le cache de préfixe sauve le calcul de préremplissage répété lorsque les requêtes partagent un préfixe exact. Une décharge de dépôt de 100K en une seule fois ne reçoit pas de réduction de mémoire magique simplement parce que le cache de préfixe est activé.
C’est une optimisation de charge de travail, pas un substitut à l’équation de budget. Mesurez le taux de coupure et la pression du cache retenu dans le service multi-utilisateurs.
Utiliser un KV Q4 sans test de qualité
Le cache peu de bits peut échouer subtilement. Le modèle écrit encore un texte fluide, mais l’attention sur des preuves distantes, des noms exacts, des arguments d’outil ou des dépendances de code peut se dégrader — et comme la recherche de divergence de jetons ci-dessus le montre, même le réglage “sûr” Q8_0 n’est pas garanti de reproduire la sortie exacte F16 sous décodage déterministe, seulement de préserver la précision en agrégé.
Testez la tâche cible à la profondeur cible. Les benchmarks de chat courts sont presque inutiles pour valider un cache long contexte.
Laisser le parallélisme en automatique
Un moteur peut choisir une concurrence raisonnable pour le débit mais impossible pour votre objectif de long contexte. Sur 16 Go, une séquence profonde et plusieurs séquences courtes sont des charges de travail fondamentalement différentes.
Définissez la limite explicitement, puis augmentez-la avec du trafic mesuré. Sinon une deuxième requête peut transformer une configuration 64K stable en une surprise d’allocation ou de latence.
Profils recommandés 16 Go
Ces profils sont des points de départ, pas des presets universels. Un modèle avec une géométrie KV inhabituelle — en particulier un design MLA ou hybride à fenêtre glissante — peut être beaucoup moins cher ou plus cher que l’exemple GQA conventionnel.
Agent de codage interactif
Utilisez une séquence, un contexte de 48K à 64K, un cache Q8, Flash Attention et une résidence complète des poids sur le GPU si possible. Ce profil favorise une latence prévisible et une bonne précision du cache par rapport à un maximum impressionnant mais rarement utile.
Activez la réutilisation du préfixe lorsque le moteur le supporte car les tours de codage partagent souvent un grand préfixe de dépôt ou de conversation. Compressez toujours la sortie de l’outil et les transcriptions anciennes ; l’ingénierie du cache ne rend pas les jetons non pertinents précieux.
Analyse de documents longs
Utilisez un modèle plus petit avec une capacité de 64K à 128K, un cache Q8 ou FP8 calibré, et un cache de préfixe répété lorsque plusieurs questions ciblent le même document. Mesurez le temps jusqu’au premier jeton car le préremplissage peut dominer même lorsque le décodage reste acceptable.
Si une seule question sera posée, la récupération ou la résumation par morceaux peut être plus rapide et plus fiable que de forcer le corpus entier à travers une carte de 16 Go. Le long contexte est un outil, pas un substitut à l’architecture de l’information.
Petit serveur multi-utilisateurs
Limitez le contexte par requête et le nombre total de séquences actives plutôt que de revendiquer le maximum du modèle à chaque client. L’allocation paginée de vLLM est utile ici, tandis qu’Ollama et llama.cpp nécessitent également une attention explicite aux jetons vivants agrégés.
Préférez la file d’attente au débordement non contrôlé. Une politique d’admission plus lente est moins néfaste que toutes les requêtes traversant soudainement le PCIe pendant le décodage.
Recommandation finale pour le long contexte 16 Go
Pour le long contexte sur 16 Go, un KV Q8 et une séquence active sont la bonne base. Ils révèlent la limite réelle sans faire échouer simultanément la qualité du cache peu de bits, l’allocation parallèle et la latence de déchargement.
Calculez à partir de la géométrie d’attention, soustrayez les poids et les surcoûts du runtime, puis confirmez le résultat dans les journaux du moteur et les mesures de mémoire de pic. Si 128K ne tient toujours pas, un modèle plus petit est souvent l’optimisation la plus propre ; si ça tient mais rampe, réduire le contexte est souvent l’honnête.
L’attention paginée, le cache de préfixe, les fenêtres glissantes et la précision adaptative résolvent toutes des problèmes utiles mais différents. La configuration gagnante est celle qui reste sur le GPU, récupère correctement les preuves anciennes et maintient une vitesse de décodage acceptable à la profondeur de contexte que vous utilisez réellement.
Références
- vLLM : conservation de la mémoire GPU
- vLLM : cache KV quantifié (FP8)
- vLLM : cache de préfixe automatique
- Ollama : documentation de longueur de contexte
- FAQ Ollama :
OLLAMA_KV_CACHE_TYPEet Flash Attention - llama.cpp-adaptive-turboquant — fork expérimental de cache adaptatif par couche (résultats rapportés par l’auteur)
- Raschka, S. “Multi-Head Latent Attention (MLA).” LLM Architecture Gallery. https://sebastianraschka.com/llm-architecture-gallery/mla/
- “Decoding Multi-Head Latent Attention: The KV Cache Memory Bottleneck, Solved.” Vizuara. https://vizuara.substack.com/p/decoding-multi-head-latent-attention
- “Quantize What Counts: Bit Allocation Insights Informed by Spectral Gaps in Keys and Values.” arXiv:2502.15075. https://ar5iv.labs.arxiv.org/html/2502.15075
- v-code01. “kvdivergence — does KV cache quantization change the generated text under greedy decoding?” GitHub. https://github.com/v-code01/kvdivergence
- Comparaison des performances de LLM sur Ollama sur GPU 16 Go VRAM
- Qwen 3.6 27B et 35B MTP vs Standard sur GPU 16 Go