Aller au contenu principal

Pourquoi un system prompt de 35 Ko casse sur un modèle local

Intermédiaire
What you'll learn
  • Faire l'arithmétique du budget de contexte avant de déplacer un prompt, et voir pourquoi un prompt de 35 Ko invisible sur Claude représente un quart d'une petite fenêtre locale
  • Savoir exactement ce que fait Ollama quand le prompt est trop long — il supprime le milieu, garde quelques tokens de préfixe, et ne le dit qu'au journal du serveur
  • Reconnaître les cinq signaux comportementaux de l'épuisement du contexte dans un agent local avant qu'il ne gaspille une heure
  • Restructurer un prompt monolithique en unités à objectif unique qui tiennent, et se transmettre l'état sur disque
  • Régler les boutons du runtime qui comptent : OLLAMA_CONTEXT_LENGTH, la quantification du cache KV, la flash attention et le multiplicateur de requêtes parallèles

Un billet arrivé en une de Hacker News les 13 et 14 septembre 2026 décrivait une expérience que beaucoup de gens s'apprêtent à vivre : un system prompt de 35 Ko qui tournait très bien sur Claude Opus a été pointé vers un modèle 27B auto-hébergé via Ollama, et en trois minutes environ l'agent relisait des fichiers qu'il avait déjà lus, répétait des appels d'outils identiques et réécrivait du travail terminé. Rien n'allait « mal » dans le modèle. Le prompt plus l'historique de session avaient simplement dépassé une fenêtre de 65K tokens, et le runtime s'était mis à jeter des morceaux de la conversation.

Cette page est le guide que l'auteur aurait aimé trouver. Elle traite de la taille du prompt face au budget de contexte, un problème différent de la formulation du prompt d'un modèle à l'autre, couverte dans Porter ses prompts d'un modèle à l'autre, et du choix du modèle local, couvert dans Faire tourner des modèles en local avec Ollama.

L'arithmétique que personne ne fait en premier​

Sur Claude, un system prompt de 35 Ko fait à peu près 9 000 tokens, logés dans une fenêtre de 1 000 000 de tokens : moins de 1 %. Vous n'y pensez jamais. Sur un runtime local, le même prompt fait les mêmes 9 000 tokens, mais la fenêtre est celle que vous avez configurée, et la valeur par défaut est petite :

Contexte par défaut d'Ollama (documentation main actuelle)Machine
4K tokensmoins de 24 Gio de VRAM
32K tokens24 à 48 Gio de VRAM
256K tokens48 Gio de VRAM ou plus

À 4K, un system prompt de 9 000 tokens ne tient tout simplement pas. À 32K, il occupe 28 % de la fenêtre avant que l'utilisateur ait dit un mot. Aux 65K configurés par l'auteur du billet HN, il en occupe 14 %, ce qui paraît confortable jusqu'à ce que vous ajoutiez ce que transporte réellement une session d'agent : les définitions d'outils (quelques centaines à quelques milliers de tokens), chaque fichier lu par l'agent, chaque résultat d'outil et la production du modèle lui-même. Un agent de code qui lit trois fichiers moyens et lance deux commandes est couramment à 20–30K tokens dans la conversation. Avec un coût fixe de 9K, la mémoire de travail utilisable disparaît en quelques tours.

Les deux habitudes du cloud qui rendent cela invisible sur Claude sont absentes en local : la fenêtre est deux ordres de grandeur plus petite, et il n'y a pas de compactage intégré. Claude Code résume la conversation quand elle approche de la limite (Gestion du contexte) ; un endpoint Ollama brut, non. Certains harnais d'agents locaux font leur propre compactage, mais pas le runtime qui les porte.

Mesurer le prompt avant de migrer (n'importe quel endpoint local compatible OpenAI)

# Count what the model will actually see, from Ollama's own tokenizer.
# prompt_eval_count in the response is the tokenised prompt length.
curl -s http://localhost:11434/api/chat -d '{
"model": "qwen3:32b",
"messages": [{"role":"system","content":"'"$(cat system-prompt.md | sed 's/"/\\"/g')"'"},
             {"role":"user","content":"ping"}],
"stream": false,
"options": {"num_predict": 1}
}' | jq '{prompt_tokens: .prompt_eval_count, cached: .prompt_eval_cached_count}'

Comparez ce chiffre au num_ctx avec lequel vous tournez, puis retranchez le schéma des outils et une transcription réaliste. Si le reste représente moins de la moitié de la fenêtre environ, le prompt est trop gros pour l'installation, et aucune reformulation n'y changera rien.

Ce que fait Ollama quand le prompt ne tient pas​

C'est le mécanisme qui transforme « un peu trop long » en « mystérieusement stupide », et il n'est documenté en clair nulle part ; il est dans le code source.

Quand le prompt d'une requête est plus long que le contexte et que la troncature est activée (elle l'est, par défaut, pour /api/chat et /api/generate), le runner d'Ollama adossé à llama.cpp fait ceci, dans llm/llama_server.go :

  1. Il garde les num_keep premiers tokens du prompt. La valeur par défaut de num_keep est 4, fixée « pour éviter les problèmes lors des décalages de contexte ».
  2. Il calcule une longueur cible qui laisse à peu près la moitié du contexte restant libre pour la génération.
  3. Il supprime des tokens au milieu : le préfixe conservé reste, la fin reste, le bloc entre les deux est retiré.
  4. Il journalise truncating input prompt avec les tailles avant/après sur le serveur. La réponse de l'API ne porte aucun indicateur, et une bibliothèque cliente voit une complétion normale.

Le tableau de tests du projet rend l'échelle concrète : avec un contexte de 4 096 tokens et le num_keep par défaut, un prompt trop long est coupé à 2 050 tokens. La moitié de la fenêtre est délibérément réservée à la réponse. Un system prompt de 9 000 tokens sur une fenêtre de 4K arrive donc au modèle sous la forme de quatre tokens de son ouverture, puis un trou, puis ce qui tient à la fin de la conversation. Les instructions ont disparu ; le dernier message utilisateur survit. C'est exactement le comportement de « reformulation des objectifs, répétition des appels d'outils » que décrit le billet HN : on demande au modèle d'agir sur une conversation dont les règles ont été silencieusement supprimées.

Deux façons de sortir de la version silencieuse :

  • Envoyez "truncate": false dans la requête. Ollama renvoie alors un HTTP 400 avec un message indiquant que le prompt est plus long que le contexte actuellement disponible, au lieu de deviner. Pour un harnais d'agent c'est le bon comportement : échouer bruyamment, puis compacter ou redémarrer.
  • Montez num_keep à la longueur de votre system prompt pour que le préfixe survive à un décalage. Cela préserve les règles mais supprime toujours de la conversation au milieu ; c'est un pansement, pas un correctif.

L'endpoint de chat a une seconde couche, plus douce, au-dessus de celle-ci : quand la liste de messages est trop longue pour la limite, le constructeur de prompt d'Ollama supprime d'abord les messages les plus anciens tout en gardant le message système et le dernier tour (voir server/prompt.go). Vous pouvez aussi perdre des résultats d'outils intermédiaires de cette façon, et c'est pourquoi un agent peut « oublier » qu'il a déjà lu un fichier. Surveillez le journal du serveur ; avec OLLAMA_DEBUG=1 l'élagage est visible.

Pas seulement Ollama

Le serveur de llama.cpp a le même comportement de décalage de contexte sous un autre flag, vLLM refuse les prompts trop longs par défaut, et LM Studio expose la longueur de contexte comme un réglage par modèle qu'il est facile de laisser à sa petite valeur par défaut. Le motif à intégrer est : sachez si votre runtime tronque ou renvoie une erreur, et préférez l'erreur.

Les cinq signaux d'échec​

L'auteur du billet HN a tenu une liste de ce à quoi l'épuisement du contexte ressemblait vu de l'extérieur, et elle correspond à ce que l'on rapporte de tous les agents à petite fenêtre. Traitez chacun de ces signaux comme « la fenêtre est pleine », pas comme « le modèle est mauvais » :

  1. Des appels d'outils identiques consécutifs. Le modèle n'a plus le résultat précédent sous les yeux, alors il redemande.
  2. La relecture d'un fichier déjà lu. Même cause : le résultat de la lecture a été élagué.
  3. La reformulation de l'objectif avec ses propres mots en cours de tâche, parfois avec une dérive. Le system prompt a été coupé, et le modèle reconstruit la tâche à partir de la fin de la conversation.
  4. Des échecs d'analyse des appels d'outils qui apparaissent après une série d'appels propres. Une fois les exemples et le schéma du prompt disparus, les petits modèles retombent sur leur formatage par défaut.
  5. Un nombre de tours élevé rapporté aux fichiers réellement modifiés. Ce ratio est la métrique la moins chère à suivre dans un harnais ; quand il s'envole, arrêtez et redémarrez avec un contexte neuf.

Si vous passez par un client compatible OpenAI, journalisez prompt_eval_count à chaque tour. Quand il cesse de croître alors que la conversation continue de grossir, la troncature a commencé.

Restructurer : prompts à objectif unique et état sur disque​

Un prompt de 35 Ko contient en général l'équivalent des règles d'un produit entier : personas, standards de code, étiquette des outils, formats de sortie, cas limites et une longue liste de « ne jamais faire X ». Sur un modèle à 1M de tokens, cela fonctionne par force brute. Sur un modèle à 32K, non, et le correctif auquel l'auteur du billet HN est arrivé est celui-là même que les commentateurs les plus pertinents défendaient indépendamment : découper le prompt en unités à objectif unique et exécuter chacune dans sa propre session courte.

Guided walkthrough1 of 6
  1. Parcourez les 35 Ko et étiquetez chaque paragraphe avec la tâche qu'il sert : planifier, éditer, tester, relire, écrire le commit. La plupart des monolithes contiennent cinq à huit tâches distinctes plus un noyau commun (persona, faits sur le dépôt, règles de sécurité) qui fait moins de 2 Ko.

Un fichier de passation que l'unité à objectif unique suivante lit en premier

# HANDOFF — written by unit 2 (implement), read by unit 3 (test)
Objective completed: renamed getUser -> fetchUser across src/
Files changed: src/api/user.ts, src/pages/profile.tsx, src/hooks/useUser.ts
Not done: tests still reference getUser (tests/user.test.ts)
Constraints carried forward: no new dependencies; keep TypeScript strict
Next unit should: update tests, run `npm test`, write result to HANDOFF.md

C'est aussi la réponse honnête au commentateur qui affirmait qu'un prompt de 35 Ko est « confus et sans focus sur n'importe quel LLM ». Les modèles de frontière masquent la boursouflure ; les modèles locaux l'exposent. Le découpage par objectif améliore en général aussi les résultats sur Claude, et c'est la conception que suppose Claude et les modèles locaux quand il route les étapes bon marché en local.

Régler le runtime​

Une fois le prompt à la bonne taille, quatre boutons décident de la fenêtre que vous pouvez réellement vous offrir. Tous viennent de la FAQ et de la documentation d'Ollama.

BoutonCe qu'il faitPiège
OLLAMA_CONTEXT_LENGTH=65536 ollama serve (ou num_ctx par requête, ou PARAMETER num_ctx dans un Modelfile)Définit la fenêtreUn contexte plus grand coûte de la mémoire. La documentation le dit crûment ; le cache KV croît linéairement avec lui
OLLAMA_KV_CACHE_TYPE=q8_0Quantifie le cache KV ; environ la moitié de la mémoire du f16 par défaut. q4_0 en consomme environ un quartNécessite la flash attention ; faible coût en qualité, plus élevé avec q4_0
OLLAMA_FLASH_ATTENTION=1Active la flash attention ; prérequis pour la quantification du cache KV, et réduit la mémoire sur les contextes longsTous les backends/modèles ne la prennent pas en charge ; activez-la et vérifiez le journal du serveur
OLLAMA_NUM_PARALLEL (1 par défaut)Requêtes concurrentes par modèleLa mémoire requise croît selon OLLAMA_NUM_PARALLEL × OLLAMA_CONTEXT_LENGTH. Deux emplacements parallèles à 64K coûtent la mémoire KV d'un seul à 128K

Un ordre des opérations utile à budget mémoire fixe : prenez le plus gros modèle que vous pouvez tenir avec une petite fenêtre, puis activez la flash attention et le cache KV en q8_0, puis montez le contexte jusqu'à ce que le journal du serveur montre un débordement vers le CPU. Un contexte réparti entre GPU et CPU est le point où les tokens par seconde s'effondrent ; plusieurs commentateurs HN ont rapporté des modèles denses de 27B à 32B en FP8 avec de très longues fenêtres sur des configurations bi-GPU, mais sur une machine unique à 128 Go de mémoire unifiée, le plafond pratique était les 65K utilisés par l'auteur.

Le cache de préfixe aide, mais seulement pour le préfixe

Ollama rapporte prompt_eval_cached_count dans chaque réponse. Un system prompt stable placé tout au début de la conversation est réutilisé depuis le cache KV d'une requête à l'autre : son coût est donc payé une fois par session, pas à chaque tour. Tout ce qui suit le premier changement dans la conversation est recalculé. Mettez la matière fixe en premier et la matière volatile en dernier, exactement la même règle que pour le cache de prompt de Claude (Économie du cache de prompt).

Checklist de migration​

  • Tokeniser le system prompt avec le tokeniseur local et noter le chiffre.
  • Définir num_ctx explicitement. Ne jamais se fier à la valeur par défaut ; elle dépend de la VRAM de la machine.
  • Décider : tronquer ou erreur. Pour des agents, envoyez "truncate": false et gérez le 400.
  • Découper le prompt par objectif ; noyau commun sous 2K tokens ; chaque unité de 2 à 4K.
  • Se transmettre l'état entre unités par un fichier ; terminer la session après chaque unité.
  • Journaliser prompt_eval_count à chaque tour et alerter quand il plafonne.
  • Activer la flash attention et le cache KV q8_0 avant d'agrandir la fenêtre.
  • Surveiller truncating input prompt dans le journal du serveur pendant la première semaine.
Appuyez sur Entrée ou Espace pour retourner la carte. Utilisez les flèches gauche et droite pour naviguer entre les cartes.Terme affiché.
1 / 6

Vérifiez vos acquis

0/5
  1. Un system prompt de 9 000 tokens est envoyé à Ollama avec num_ctx à 4096 et les réglages par défaut. Qu'est-ce qui atteint le modèle ?
  2. Quel signal indique le plus fiablement un épuisement du contexte dans un agent local ?
  3. Vous voulez que le runtime échoue bruyamment quand un prompt est trop long. Qu'envoyez-vous ?
  4. Vous réglez OLLAMA_NUM_PARALLEL=4 et OLLAMA_CONTEXT_LENGTH=32768. De quelle mémoire le cache KV a-t-il besoin, par rapport à une seule requête à 32K ?
  5. Quel changement apporte le correctif le plus durable pour un prompt de 35 Ko sur un modèle à fenêtre de 32K ?

Sources et lectures complémentaires​

Suite​