mirror of
https://github.com/R0m1k3/Loki.git
synced 2026-10-20 05:28:15 +02:00
Moteur : cache de prompts dimensionné et travaux annexes isolés — la conversation n'est plus recalculée après un vérificateur ou un sous-agent
llama-server garde en RAM hôte une copie exacte des états de slot qu'il quitte (KV, état récurrent, points de reprise, brouillon MTP) et la recharge octet pour octet. Mais son défaut de 8 Gio ne tient pas une conversation de 30 à 65 k jetons à côté de l'état d'un vérificateur, d'un sous-agent, d'une tâche ou d'un bench : à la requête suivante il évince la conversation pour sauver l'annexe, et tout est recalculé (30 à 80 s sur un 27B). Rien de ce que voit le modèle ne change ici : seul le bruit de découpage des lots, comme le cache_prompt actuel. - CACHE_RAM (Mio ; vide/auto, nombre passé tel quel, -1, 0) → --cache-ram, seulement si l'aide du moteur le connaît. Loki se tait devant -cram d'EXTRA_ARGS et LLAMA_ARG_CACHE_RAM. L'auto ne descend JAMAIS sous le défaut : il agrandit seulement un modèle tout-GPU (VRAM NVIDIA connue, aucun poids ni KV sur CPU, NGL complet), d'après l'état mesuré dans le GGUF (2,5 × KV à CTX + état récurrent et points de reprise des hybrides), plafonné à 30 % de la RAM (limite cgroup comprise) et à la moitié de la RAM libre. - --slot-save-path LOKI_HOME/slots (0700, purgé au lancement) uniquement si le moteur est en boucle locale ou protégé par clé, et si le dossier existe. Les proxys /v1 et du relais refusent désormais toute action /slots (405). - engineSideJob efface le slot 0 APRÈS le sous-agent, la passe de vérification, la tâche planifiée et le bench (pas après la compaction : rien à y gagner). Synchrone, borné à 2 s, et seulement si : moteur local, build ≥ 8660 lu dans /props, total_slots == 1, slot 0 au repos, aucune requête de Loki en vol (compteur tenu par le chat, les résumés, le bench et les proxys). CACHE_ISOLATE=off le coupe. - usage.prompt_tokens_details.cached_tokens : si la conversation revient avec moins de la moitié en cache, conseil (une fois) de relever CACHE_RAM. - Éditeur de preset : champ « Cache de prompts » à côté d'UBATCH, avec la valeur auto calculée par le serveur (/api/preset/cacheram). - Lecteur GGUF : embedding_length, head_count et dimensions ssm.*. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
1 parent
d028e81ce2
commit
3565b594ea
21 files changed
+1377
-5
No files matched your search
@@ -728,6 +728,9 @@ type StatsEvent struct {
|
||||
// Taille TOTALE du prompt traité ce tour (préfixe caché compris), issue de
|
||||
// `usage.prompt_tokens`. 0 si le backend ne renvoie pas d'usage.
|
||||
PromptTokensTotal int `json:"prompt_tokens_total,omitempty"`
|
||||
// Conseil ponctuel quand le cache de prompts n'a pas tenu après un travail
|
||||
// annexe (voir noteEnginePrompt). Vide la plupart du temps.
|
||||
CacheHint string `json:"cache_hint,omitempty"`
|
||||
}
|
||||
|
||||
// ChatCallback receives stream events. Return false to abort the stream.
|
||||
@@ -756,9 +759,12 @@ type streamChunk struct {
|
||||
} `json:"timings"`
|
||||
// Chunk final (include_usage) : taille totale du prompt, hors choices.
|
||||
Usage *struct {
|
||||
PromptTokens int `json:"prompt_tokens"`
|
||||
CompletionTokens int `json:"completion_tokens"`
|
||||
TotalTokens int `json:"total_tokens"`
|
||||
PromptTokens int `json:"prompt_tokens"`
|
||||
CompletionTokens int `json:"completion_tokens"`
|
||||
TotalTokens int `json:"total_tokens"`
|
||||
PromptTokensDetails *struct {
|
||||
CachedTokens int `json:"cached_tokens"`
|
||||
} `json:"prompt_tokens_details"`
|
||||
} `json:"usage"`
|
||||
}
|
||||
|
||||
@@ -1046,8 +1052,16 @@ func runChat(ctx context.Context, messages []Message, temperature float64, caps
|
||||
// temps qu'un 401 garanti. Chaque endpoint porte la sienne.
|
||||
ep.auth(req.Header.Set)
|
||||
tReq := time.Now() // secours des stats quand le serveur n'envoie pas de timings
|
||||
// Requête en vol vers le moteur local, jusqu'à la lecture complète du corps :
|
||||
// l'isolation des travaux annexes n'efface jamais le slot pendant ce temps
|
||||
// (llm_slots.go). Rien à compter pour une API externe.
|
||||
endReq := func() {}
|
||||
if !ep.External {
|
||||
endReq = engineRequestStart()
|
||||
}
|
||||
resp, err := http.DefaultClient.Do(req)
|
||||
if err != nil {
|
||||
endReq()
|
||||
// Rien n'est encore parti à l'écran : le tour est rejouable tel quel.
|
||||
// C'est le cas du moteur qui redémarre (bascule de preset, rechargement
|
||||
// de modèle) — quelques secondes de connexion refusée qui faisaient
|
||||
@@ -1070,6 +1084,7 @@ func runChat(ctx context.Context, messages []Message, temperature float64, caps
|
||||
if resp.StatusCode != http.StatusOK {
|
||||
b, _ := io.ReadAll(io.LimitReader(resp.Body, 2000))
|
||||
resp.Body.Close()
|
||||
endReq()
|
||||
msg := strings.TrimSpace(string(b))
|
||||
if msg == "" {
|
||||
msg = resp.Status
|
||||
@@ -1249,6 +1264,13 @@ func runChat(ctx context.Context, messages []Message, temperature float64, caps
|
||||
if chunk.Usage != nil && chunk.Usage.PromptTokens > 0 {
|
||||
stats.PromptTokensTotal = chunk.Usage.PromptTokens
|
||||
s := stats
|
||||
if !ep.External {
|
||||
cached, has := 0, chunk.Usage.PromptTokensDetails != nil
|
||||
if has {
|
||||
cached = chunk.Usage.PromptTokensDetails.CachedTokens
|
||||
}
|
||||
s.CacheHint = noteEnginePrompt(chunk.Usage.PromptTokens, cached, has)
|
||||
}
|
||||
scb(StreamEvent{Stats: &s})
|
||||
}
|
||||
if len(chunk.Choices) == 0 {
|
||||
@@ -1464,6 +1486,7 @@ func runChat(ctx context.Context, messages []Message, temperature float64, caps
|
||||
// arguments tronqués. On refuse donc le tour, en le disant.
|
||||
scanErr := sc.Err()
|
||||
resp.Body.Close()
|
||||
endReq()
|
||||
if !sawTimings && !tFirst.IsZero() {
|
||||
// Décodage mesuré ici ; le 1er token tombe dans la lecture du prompt.
|
||||
// Pas de débit de lecture : le serveur a pu réutiliser une partie du
|
||||
|
||||
Reference in new issue
Block a user