Repository navigation
Pré-chauffage du prompt : le premier message ne paie plus le préfill du préfixe - #163
Conversation
…pond, en tâche de fond Le préfill du premier message d'une conversation était payé à chaque démarrage du moteur. Un pré-chauffage interne (llm_prewarm.go) envoie la même requête max_tokens=1 sur le préfixe exact du premier tour, puis le laisse dans le cache KV. Il attend le moteur, s'efface devant un tour utilisateur ou une génération en cours, se relance si preset/prompt/outils changent, et s'abstient en preset externe, cloud ou modèle brut.
|
Merci pour la PR, l'idée est bonne mais je ne peux pas la prendre en l'état, pour deux raisons :
Pour que ça passe : ne pré-chauffer que si la conversation active est vide, utiliser les réglages du mode actif, et un test qui compare avec la vue réellement envoyée par |
Prewarm only empty conversations using the mode and capabilities that will handle the first message. Remember the last chat mode, skip warming when the target is unknown or mismatched, and guard against a conversation starting before the request is sent. Add tests for mode-specific prefixes and conversation safety.
C'est fait |
|
Merci, c'est fusionné. Mesuré sur notre machine avec le moteur MoE : le premier mot d'une nouvelle conversation passe de 4,3 s à 0,3 s en mode Projet, et de 5,9 s à 0,3 s en mode Jean. J'ai ajouté un interrupteur dans Paramètres pour couper le pré-chauffage (actif par défaut). |
Contexte
Sur un modèle local, le délai avant le premier jeton d'une conversation est presque entièrement du préfill : préambule système, briefing machine, prompt du preset et surtout les schémas d'outils — plusieurs milliers de jetons. Or ce préfixe est identique d'une conversation à l'autre (le contexte projet, lui, part dans le premier message utilisateur, voir
normalizeSystemMessages).Aujourd'hui ce préfill est repayé après chaque démarrage du moteur, alors que
llama-serversait garder le cache KV du créneau (--cache-ram,--cache-idle-slotssont actifs par défaut) : un préfixe traité une fois n'a plus besoin d'être retraité.Ce que fait ce changement
Dès que le moteur répond, une complétion minuscule (
max_tokens=1) est envoyée avec exactement le préfixe du premier tour. Elle reste interne : rien n'est affiché, la réponse est jetée, et la connexion reste ouverte dans le pool par défaut pour que le vrai message la réutilise.Mécanique
baseSystemPrompt+machineSystemPrompt+readSysPrompt(si agent, hors mode terminal), même ordre quegenerate/InjectSkillswithRecallTools(EnabledTools(caps), nil)— fil neuf, commerunChatavant tout compactagesha256sur URL + modèle + système + outils JSON (tronquée à 12 octets)max_tokens: 1,temperature: 0,stream: false,cache_prompt: true,tools+parallel_tool_calls: false15 s × tentative, revérification toutes les 15 s une fois chauffé, un seul envoi à la foisnewWebMux()(santé immédiate, sinon sonde toutes les 3 s pendant 10 min) ethandleStatussur/api/status(goroutine détachée)healthy: false → true(moteur redémarré) remet signature,warmedet tentatives à zéroGarde-fous
doLLMincrémentellmInFlight(chat, tâches, Jean, apps, compaction, cloud) ; le pré-chauffage s'abstient tant qu'il est non nul.llmTurnsActiveest incrémenté parStartTurnet décrémenté dans ledeferdegenerate: il couvre l'exécution d'un outil ou une lecture de fichier, quandllmInFlightest retombé à zéro.cancelLLMPrewarmForTurn()est appelé au démarrage du tour et coupe un pré-chauffage déjà parti ; le cache est marqué chauffé puisque le tour traitera le préfixe de toute façon.Fichiers
internal/ajean/llm_prewarm.gointernal/ajean/llm_prewarm_test.gointernal/ajean/backend_cloud.gollmInFlightdansdoLLMinternal/ajean/chat_conversation.gollmTurnsActive+ annulation au démarrage du tourinternal/ajean/web_server.gointernal/ajean/web_api.go/api/statusVérifications
go build ./...— OKgo vet ./...— OKstaticcheck(v0.8.2) — aucun avertissementgofmt -l .— propre (toolchaingo1.25.0, celui dego.mod)go test ./...— OK, saufTestMoeMigratequi échoue par absence du privilège Windows de création de lien symbolique (A required privilege is not held by the client) : limite de la machine de développement, sans rapport avec cette PR.Les quatre tests ajoutés verrouillent les invariants qui rendent le pré-chauffage utile :
TestPrewarmPrefixMatchesChatPrefix— le bloc système et les outils pré-chauffés sont identiques à ce queInjectSkills+normalizeSystemMessages+runChatproduisent pour une conversation neuve ; si ce n'est plus vrai, le cache ne se raccorde pas et le pré-chauffage devient du travail perdu.TestPrewarmPrefixEmptyForRawModel— aucun envoi pour un modèle brut.TestPrewarmPayload— corps minimal (un jeton, non streamé,cache_prompt),toolsabsent quand il n'y a pas d'outil.TestLLMPrewarmSendsOnce— envoi réel contre un serveur de test, puis aucun second envoi pour le même préfixe.