Le KV Cache sur des GPU de 16 Go : faire tenir réellement les longs contextes
Pourquoi un contexte de 128 Ko échoue sur 16 Go
Un modèle peut afficher une fenêtre de contexte de 128K et échouer tout de même à 40K jetons sur une carte graphique de 16 Go. La limite architecturale ne garantissait jamais que les poids, le cache KV, les tampons de calcul et le compositeur de bureau tiendraient sur votre carte en même temps.
Le cache KV est généralement là que les plans à long terme rencontrent cette limite physique. Il augmente avec chaque jeton actif et chaque séquence, si bien qu’une configuration qui semble confortable au démarrage peut ralentir considérablement, déborder en mémoire système ou échouer lors d’une pré-affectation (prefill) importante.

Ce guide transforme le problème en budget de VRAM. Il couvre la formule du cache, des tables de tailles reproductibles de 32K à 128K, et des configurations fonctionnelles pour --cache-type-k et --cache-type-v de llama.cpp, le cache paged et de préfixe de vLLM, et les contrôles de contexte d’Ollama — ainsi que les fourchettes expérimentales à cache adaptatif qui méritent de l’intérêt, mais pas une confiance aveugle. Pour le contexte plus large concernant le débit, la latence et les benchmarks derrière ces chiffres, commencez par le Hub Performance LLM.
La réponse courte pour une carte graphique de 16 Go
Commencez avec une séquence, un contexte maximal réaliste, Flash Attention et un cache KV en 8 bits. Mesurez cette configuration avant d’essayer un cache en 4 bits, le déchargement sur CPU, plusieurs slots parallèles ou une fourchette expérimentale.
| Objectif | 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 agressive des poids, KV Q8 | Latence de pré-affectation et bande passante du cache |
| 128K | Petit modèle GQA, KV Q8 ou Q4 testé, une séquence | Le cache seul peut consommer la majeure partie de la VRAM |
| Deux sessions 64K simultanées | Considérez comme un budget de cache d’environ 128K | La capacité parallèle est confondue avec un débit gratuit |
Mon avis est simple : une configuration stable à 64K est généralement plus utile qu’une configuration nominale à 128K qui opère à la limite d’une défaillance de mémoire insuffisante (out-of-memory). La capacité de contexte n’est pas un trophée ; c’est une décision concernant la latence, la qualité et la concurrence.
Ce que le cache KV stocke
Lors de 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 s’attacher aux jetons antérieurs sans recompter l’ensemble du préfixe.
Le cache économise 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 ligne de 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 8 milliards de paramètres peuvent avoir des coûts de cache KV très différents. L’un peut utiliser 32 couches et huit têtes KV, tandis que l’autre peut utiliser moins de têtes KV, des couches KV partagées, une 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 du KV provient de l’architecture d’attention, lisez donc 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 la distance parcourue par la conception de l’attention au-delà de la simple attention multi-têtes (MHA) :
- Attention Multi-Requête (MQA) partage une seule tête K/V entre toutes les têtes de requête — économies maximales de cache, mais c’est le compromis de qualité le plus agressif et rarement utilisé seul dans les modèles de pointe actuels.
- Attention Groupée de Requête (GQA) regroupe les têtes de requête en clusters qui partagent chacun une tête K/V — le compromis principal utilisé par la plupart des modèles denses ouverts, et la géométrie que la formule ci-dessus suppose.
- Attention Latente Multi-Têtes (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 rang bas compressé et reconstruit les K/V à pleine résolution sur demande lors de l’attention. DeepSeek a rapporté une réduction du cache KV d’environ 93 % par rapport à un modèle dense MHA de taille équivalente, tout en maintenant une qualité compétitive avec — parfois supérieure à — la GQA pour le même budget mémoire.
La conséquence pratique est qu’un « modèle GQA de 27B » et un « modèle MLA de 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 lorsque 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 pris en charge 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 du moteur ne peut pas étendre en toute sécurité un modèle au-delà de son schéma de position pris en charge. Le scalage RoPE peut étendre certaines architectures, mais c’est une expérience de qualité de modèle, pas une optimisation de la mémoire KV.
Table des tailles de 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 GiB binaires et les tailles de bloc physiques couramment associées à f16, q8_0 et q4_0 de llama.cpp. C’est un calcul de référence, pas une promesse concernant la mémoire totale du processus ; l’alignement, les métadonnées, les couches hybrides et les espaces de travail des backends ajoutent des surcoûts.
| Type de cache | Octets 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 |
Doubles maintenant le nombre de couches à 64 en laissant les autres dimensions inchangées. Le cache FP16 devient 8 Go à 32K, 16 Go à 64K, et 32 Go à 128K, ce qui démontre pourquoi une seule recommandation 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 totale - 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 du décodage spéculatif
- Marge de sécurité
Sur une carte de 16 Go attachée à un écran, ne planifiez pas en supposant que les 16 Go entiers sont disponibles. Réservez au moins plusieurs centaines de Mo pour le bureau et le pilote, puis laissez une autre marge pour les tampons dépendants de la charge de travail ; une marge totale de 1,0 à 1,5 Go 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 à 1,2 Go. Après une marge de sécurité de 1 Go, il ne reste que environ 3 Go pour le KV, donc 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 rend pas automatiquement 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 de grands poids appariés à un cache fragile. Les ancrages mesurés pour exactement ce calcul sont dans les tables de benchmark llama.cpp sur 16 Go de VRAM, où la VRAM par modèle est enregistrée à 19K, 32K et 64K de contexte. Pour une enquête plus large sur les tailles de modèles et les niveaux de quantification qui se comportent bien sous Ollama sur cette même classe de carte, voir Comparaison des performances des LLM sur Ollama avec 16 Go de VRAM.
Calculer le budget de cache KV pour votre modèle
Le fragment Python suivant estime un cache GQA complet à attention conventionnelle. 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 ratios Q8_0 et Q4_0 incluent de simples métadonnées de bloc, c’est pourquoi ils sont légèrement plus grands qu’un octet exact 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 dramatique.
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 le backend que vous prévoyez de déployer.
llama.cpp : contrôle direct de la précision K et V
llama.cpp expose des options séparées --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 préréglage global. Si vous avez d’abord besoin de l’installation et du service environnants, le guide llama.cpp couvre llama-cli, llama-server et les drapeaux VRAM clés.
Une configuration conservatrice à 64K 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 la prise en charge des backends changent rapidement, donc exécutez llama-server --help pour la build installée. Plus important, inspectez le journal de démarrage : il devrait 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 avec Q8_0 pour K et V. Cela divise approximativement la mémoire KV par deux par rapport à F16, et des tests indépendants de perplexité sur des modèles de 20 milliards et plus (Qwen3.6-27B, Nemotron-30B) montrent que l’écart de qualité global 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é provoquer l’effondrement de la vitesse de décodage et de la précision à long contexte sur les modèles plus petits.
Si Q8_0 ne tient pas, testez des clés Q8_0 avec des valeurs Q4_0 avant de quantifier les deux côtés en Q4_0. Cet ordre a un soutien scientifique, pas seulement folklorique : des études contrôlées d’allocation de bits sur des checkpoints Llama, Phi-4, Qwen3 et Mistral ont trouvé que les tenseurs de clés sont systématiquement deux à dix fois plus sensibles à l’erreur de quantification que les tenseurs de valeurs, et que donner aux clés un budget de bits plus large (par exemple 4 bits de clés avec 2 bits de valeurs) récupère jusqu’à 94–98 % de la précision à pleine précision — tandis que l’ordre inversé (2 bits de clés, 4 bits de 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 celui qui semble le plus sûr.
| Configuration | Mémoire | Risque de qualité | Recommandation |
|---|---|---|---|
| K et V F16 | La plus élevée | La plus basse | Référence quand ça tient |
| K et V Q8_0 | Moins de 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 | Moins du quart de F16 | La plus élevée | Valider à la profondeur cible |
Une mise en garde à intégrer : « faible risque de qualité » 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 a seulement changé 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 essentiellement tous — une fois qu’un jeton bascule, le reste de la continuation peut diverger. La perplexité et les scores de tâches en aval peuvent sembler corrects en moyenne tandis que les sorties individuelles diffèrent toujours de la référence F16. Si votre application a besoin d’une reproductibilité byte-par-byte (tests de régression, réponses en cache, agents déterministes), considérez 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 de backend 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 commandées copiées.
Contexte, slots parallèles et cache unifié
--ctx-size décrit une capacité du moteur, pas une garantie que chaque slot parallèle reçoit ce nombre de jetons indépendamment. La gestion du cache a évolué dans llama.cpp, y compris le comportement de cache unifié, donc testez la build exacte plutôt que de vous fier à une règle plus ancienne qui divise simplement le contexte par le nombre de slots.
L’équation de capacité survit toujours aux changements d’implémentation : les jetons uniques simultanés ont besoin d’un stockage quelque part. Si deux sessions agent peuvent chacune atteindre 48K, budgétisez près de 96K jetons vivants sauf si la charge de travail partage des préfixes ou tolère l’éviction et le recalcul.
La taille de lot ne réduit pas le KV stocké
--batch-size et --ubatch-size affectent le traitement des prompts et la mémoire temporaire. Les baisser peut sauver un grand pré-affectement d’un pic de mémoire d’activation, mais cela ne change pas les octets persistants requis pour chaque jeton retenu.
Cette distinction explique un schéma de défaillance commun : le modèle démarre et une requête 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ésidentialité des poids pour changer la capacité persistante.
vLLM : la capacité paged 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 en blocs pour que les séquences concurrentes n’aient pas chacune besoin d’une grande région contiguë unique. Si vous décidez de passer à vLLM en premier lieu, le guide de migration Ollama vers vLLM couvre les signaux de charge de travail ; ici la question est purement de savoir combien de cache le pool peut contenir, et le début rapide vLLM couvre l’installation et les drapeaux généraux de service 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 paged élimine la fragmentation, pas le coût de stockage par jeton, donc une requête unique de 128K a toujours besoin de suffisamment de blocs pour son état KV.
Le guide officiel de conservation de la mémoire de vLLM recommande de limiter max_model_len et max_num_seqs lorsque la mémoire est serrée, et note que les graphes CUDA consomment de la mémoire GPU supplémentaire. Sur une carte de 16 Go, les deux réglages devraient être intentionnels plutôt qu’hérités de la configuration maximale d’un modèle.
Un serveur à séquence unique focalisé 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
Chaque carte GPU de 16 Go, modèle, méthode de quantification ou backend d’attention ne prend pas en charge cette combinaison exacte. Traitez-la comme une forme de configuration : contraignez la longueur et la concurrence, réservez de la marge, sélectionnez un type de cache pris en charge et validez le rapport d’initialisation.
Cache KV FP8 dans vLLM
La documentation actuelle du cache KV quantifié de vLLM prend en charge les formats de cache FP8 sur les chemins CUDA et ROCm compatibles. Le FP8 divise approximativement le stockage brut du cache par deux par rapport à BF16 ou FP16 et peut donc augmenter la capacité en jetons ou la concurrence.
Le scalage compte. La documentation distingue les échelles par défaut, le calcul de réchauffement et l’étalonnage par jeu de données, et recommande l’étalonnage par jeu de données pour la précision la plus élevée ; 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 des blocs KV pour un préfixe en cache identique. 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 cela évite de recalculer le pré-affectement 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é-affectement de préfixe partagé.
L’utilisation de la mémoire GPU n’est pas une mémoire gratuite
Augmenter --gpu-memory-utilization donne à vLLM une cible de réservation plus large, mais cela ne crée pas de VRAM. Le pousser trop près de 1.0 peut laisser insuffisamment de place 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 augmentez seulement si la charge de travail reste stable. Si l’initialisation réussit mais que les prompts réels échouent, réduisez les jetons par lot, la concurrence des séquences, la capture de graphique CUDA ou le contexte maximal avant de supposer que l’allocation est cassée.
Ollama : des contrôles plus simples, un diagnostic moins granulaire
Ollama fournit délibérément une surface opérationnelle plus petite. Sa documentation actuelle sur la longueur de contexte par défaut les GPU de moins de 24 Go à un contexte de 4K, recommande au moins 64K pour les charges de travail agent et de codage, et met en garde que le contexte plus large consomme plus de mémoire.
Définissez la valeur par défaut du serveur et confirmez le modèle chargé comme ceci :
OLLAMA_CONTEXT_LENGTH=65536 ollama serve
ollama ps
Vous pouvez également 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 été réellement alloué. Soyez conscient que le comportement de planification derrière ces chiffres a changé entre les versions d’Ollama ; ma comparaison de l’allocation mémoire d’Ollama v0.12.1 montre le nouvel ordonnanceur poussant 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 KV quantifié nécessite Flash Attention, qu’Ollama utilise automatiquement sur les backends pris en charge ou 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 met en garde que Q4_0 peut produire une perte de qualité plus notable, surtout à contexte élevé, donc ce devrait être un retour mesuré plutôt qu’un préréglage automatique de 16 Go.
Le parallélisme Ollama multiplie le budget de contexte
Ollama documente une règle particulièrement claire : la mémoire requise échelle avec OLLAMA_NUM_PARALLEL * OLLAMA_CONTEXT_LENGTH. Quatre requêtes parallèles à un réglage 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 à rendre les deux requêtes lentes. Les mécanismes de mise en 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 dans la RAM système peut transformer une défaillance d’allocation en un processus fonctionnel. Il place aussi 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 de la voie et de la génération de quand PCIe mord réellement est dans Performance LLM et voies PCIe.
Le déchargement peut être raisonnable pour un travail de lot occasionnel, mais c’est rarement le meilleur point par défaut pour un agent de codage interactif. Comparez d’abord une quantification des 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 sévèrement après que une partie du jeu de travail déborde, donc faites le benchmark à 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, rendant un calcul à contexte complet à plat surestimant ou mal placant la mémoire.
L’optimisation fait partie de l’architecture du modèle, pas un interrupteur générique qui peut être appliqué sans conséquences. Un moteur doit comprendre correctement le schéma des couches, les règles d’éviction, les positions et tout jeton global.
Ce que le KV adaptatif tente d’améliorer
Les fourchettes expérimentales vont plus loin en choisissant la précision ou la disposition du cache par couche et profondeur de contexte. L’objectif est attractif : préserver une précision plus élevée là où cela compte, compresser les couches moins sensibles, et changer le mélange avant que la pression de la VRAM ne cause un débordement dur — la même découverte de sensibilité des clés par rapport à la sensibilité des 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 descendant d’août 2026, llama.cpp-adaptive-turboquant, rapporte un sélecteur automatique pour plusieurs modes adaptatifs par couche et publie des tests à grande profondeur sur une RTX 5080 16 Go. Ces chiffres sont des résultats rapportés par l’auteur d’une fourchette spécialisée, pas une preuve que llama.cpp en amont se comporte de la même manière.
Pourquoi c’est encore expérimental
La fourchette combine des types de cache personnalisés, des noyaux CUDA, des chemins spécifiques au modèle et des contraintes de chaîne d’outils. C’est beaucoup plus de code à faire confiance que de passer du stockage de cache en amont de F16 à Q8_0.
Utilisez une telle fourchette seulement 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é seulement à un nom de projet n’est pas reproductible.
Un test équitable du cache adaptatif
Comparez la fourchette contre un baseline Q8_0 en amont avec le même GGUF, prompt, échantillonneur, profondeur de contexte et longueur de sortie. Mesurez la VRAM au démarrage, la VRAM de pic de pré-affectation, la vitesse de traitement du prompt, la vitesse de décodage et une tâche de qualité qui nécessite réellement des preuves de la partie la plus ancienne du contexte.
N’acceptez pas une allocation réussie comme un résultat complet. Un cache peut tenir 128K et encore perdre des faits initiaux, corrompre la sortie tard dans la séquence, ou décoder trop lentement pour être utile.
Une procédure de réglage 16 Go détaillée : une variable à la fois
La voie la plus rapide vers une configuration stable est de changer une dimension mémoire à la fois. Modifier aléatoirement le type de cache, la taille de lot, le déchargement de couches, le parallélisme et le contexte ensemble produit une commande fonctionnelle sans explication.
Étape 1 : Établir le plancher des poids
Chargez le modèle à 8K de contexte, une séquence, et le déchargement GPU prévu. Enregistrez la VRAM du processus après réchauffement et vérifiez que des couches ne sont pas déplacées inattendument 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 des poids plus petite ou un modèle avant de régler le cache.
Étape 2 : Mesurer F16 ou BF16 KV comme baseline qualité
Exécutez le plus petit contexte qui supporte votre test et conservez le cache haute précision par défaut. Sauvegarde les sorties des tâches de récupération, d’édition de code, de sélection d’outils et de longues instructions.
Cette baseline 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é sur le KV Q4.
Étape 3 : Passer à Q8 ou FP8
Activez Flash Attention si requis, sélectionnez Q8_0 dans llama.cpp ou Ollama, ou un mode FP8 pris en charge 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 beaucoup de déploiements de 16 Go, c’est le point d’arrêt utile. Cela double approximativement la capacité brute du KV sans rendre la compression du cache la quantification la plus agressive de la pile.
Étape 4 : Augmenter le contexte par étapes
Testez 32K, 64K, 96K et 128K au lieu de sauter directement au maximum affiché. À chaque étape, enregistrez les jetons par seconde de traitement du prompt, les jetons par seconde de décodage, la 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 les performances sont des axes séparés.
Étape 5 : Régler la mémoire transitoire
Si l’échec se produit pendant le pré-affectement plutôt que l’initialisation, réduisez le micro-lot ou les jetons maximum par lot. Si l’échec se produit seulement avec des requêtes simultanées, réduisez la concurrence des séquences ou les slots parallèles.
Seulement après que ces contrôles sont compris, essayez le cache mixte Q8/Q4, le cache Q4 complet, le déchargement CPU ou une fourchette adaptative. Conservez l’exécution Q8 en amont comme baseline 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 la mécanique et son coût VRAM, et mon benchmark Qwen 3.6 27B et 35B MTP vs Standard 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
Un seul chiffre de jetons/s cache le problème exact que cet article essaie de résoudre. Le test à long contexte devrait conserver assez de détails pour qu’un autre opérateur puisse reproduire la frontière mémoire.
| Champ | Pourquoi c’est important |
|---|---|
| GPU et VRAM utilisable | L’utilisation de l’affichage et d’autres processus changent le budget |
| Version ou commit du moteur | Le comportement du cache et les drapeaux évoluent rapidement |
| Version du pilote, CUDA, ROCm ou Vulkan | Détermine le comportement du backend et des noyaux |
| Modèle exact et quantification des poids | Définit la résidentialité 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é-affectement et la vitesse |
| Vitesse de traitement du prompt | Expose la praticité du long pré-affectement |
| Vitesse de décodage à chaque profondeur | Expose le ralentissement de la 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 équivalent du fournisseur pendant le pré-affectement et le décodage. Le rapport d’allocation du moteur est nécessaire, mais la mémoire périphérique de pic pendant un prompt réel est le chiffre qui décide la stabilité.
Erreurs courantes de cache KV sur GPU de 16 Go
Traiter le support 128K comme une promesse matérielle
Le champ de contexte dans une configuration de modèle est un plafond architectural. Il ne dit rien de la mémoire restante après avoir chargé 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 compresse les jetons
La pagination améliore le comportement d’allocation et de partage. Elle ne change pas la précision du tenseur ni ne supprime l’état KV requis par une séquence unique.
Utilisez l’allocation paginée pour servir efficacement des 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 économise le calcul de pré-affectement répété lorsque les requêtes partagent un préfixe exact. Un déversement de dépôt de 100K en une seule fois ne reçoit aucune 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 réussite et la pression du cache retenu dans le service multi-utilisateurs.
Utiliser KV Q4 sans test de qualité
Le cache à faible bit peut échouer subtilement. Le modèle écrit toujours un texte fluide, mais l’attention sur des preuves distantes, des noms exacts, des arguments d’outils 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 sur l’agrégat.
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 à 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 un trafic mesuré. Sinon, une deuxième requête peut transformer une configuration stable de 64K en une surprise d’allocation ou de latence.
Profils 16 Go recommandés
Ces profils sont des positions de départ, pas des préréglages 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, 48K à 64K de contexte, cache Q8, Flash Attention, et une résidentialité complète des poids sur GPU si possible. Ce profil privilégie une latence prévisible et une bonne précision du cache sur un maximum impressionnant mais rarement utile.
Activez la réutilisation du préfixe si le moteur le prend en charge car les tours de codage partagent souvent un grand préfixe de dépôt ou de conversation. Restez compactes les sorties d’outils et les anciennes transcriptions ; 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 étalonné, 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é-affectement peut dominer même si le décodage reste acceptable.
Si une seule question sera posée, la récupération ou la résumée par morceaux peut être plus rapide et plus fiable que forcer l’ensemble du corpus à travers une carte de 16 Go. Le long contexte est un outil, pas un remplacement pour l’architecture de l’information.
Petit serveur multi-utilisateurs
Limitez le contexte par requête et les séquences actives totales plutôt que d’afficher 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 mise en file d’attente au débordement incontrôlé. Une politique d’admission plus lente est moins nuisible que chaque requête traversant soudainement le PCIe pendant le décodage.
Recommandation finale pour le long contexte de 16 Go
Pour le long contexte sur 16 Go, le KV Q8 et une séquence active sont le bon point de base. Ils exposent la vraie limite sans faire échouer en même temps la qualité du cache à faible bit, l’allocation parallèle et la latence du déchargement.
Calculez depuis 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 ; s’il 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 anciennes preuves et soutient 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 sur la longueur de contexte
- FAQ Ollama :
OLLAMA_KV_CACHE_TYPEet Flash Attention - llama.cpp-adaptive-turboquant — fourchette expérimentale 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/
- “Décoder l’Attention Latente Multi-Têtes : le goulot d’étranglement mémoire du cache KV, résolu.” Vizuara. https://vizuara.substack.com/p/decoding-multi-head-latent-attention
- “Quantifier ce qui compte : aperçus d’allocation de bits informés par les lacunes spectrales dans les clés et les valeurs.” arXiv:2502.15075. https://ar5iv.labs.arxiv.org/html/2502.15075
- v-code01. “kvdivergence — la quantification du cache KV change-t-elle le texte généré sous décodage glouton ?” GitHub. https://github.com/v-code01/kvdivergence
- Comparaison des performances des LLM sur Ollama avec 16 Go de VRAM
- Qwen 3.6 27B et 35B MTP vs Standard sur GPU 16 Go