mirror of
https://github.com/R0m1k3/Loki.git
synced 2026-10-23 15:08:16 +02:00
Télémétrie : corrections de relecture — compaction aussi tolérante qu'avant, pas d'alerte de cache sur une API externe, seuil qui suit EXTRA_ARGS
La relecture du relevé par complétion a trouvé quatre écarts, tous d'affichage ou de robustesse ; rien ne touche aux requêtes ni au contexte. - Compaction : la réponse lue d'un bloc passait de Decoder à Unmarshal, plus strict (un octet parasite après le JSON faisait échouer une compaction qui passait). Retour au Decoder, et perfWire fait de même. - API externe : son cache suit ses propres règles, sans slot ni points de reprise à surveiller ici. Plus de ligne ambre « recalculés » pour elle ; le relevé reste dans l'anneau. - Seuil d'alerte : même priorité qu'au lancement — -cms d'EXTRA_ARGS, puis LLAMA_ARG_CHECKPOINT_MIN_SPACING_NT, puis CKPT_MIN_STEP ; -ub d'EXTRA_ARGS avant UBATCH. - UI : « recalculés après sous-agent, client /v1 » au lieu des identifiants internes (subagent, foreign). Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
1 parent
80b7793773
commit
18a723a671
6 files changed
+49
-12
No files matched your search
@@ -680,8 +680,11 @@ Write the summary in the SAME language as the conversation.`
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
return "", err
|
return "", err
|
||||||
}
|
}
|
||||||
|
// Decoder et non Unmarshal : comme avant, seule la première valeur JSON
|
||||||
|
// compte — un octet parasite après elle (proxy, passerelle) ne doit pas
|
||||||
|
// faire échouer une compaction qui passait.
|
||||||
var out summarizeResp
|
var out summarizeResp
|
||||||
if err := json.Unmarshal(raw, &out); err != nil {
|
if err := json.NewDecoder(bytes.NewReader(raw)).Decode(&out); err != nil {
|
||||||
return "", err
|
return "", err
|
||||||
}
|
}
|
||||||
// Télémétrie de la compaction : réponse non streamée, timings et usage sont
|
// Télémétrie de la compaction : réponse non streamée, timings et usage sont
|
||||||
|
|||||||
@@ -1553,7 +1553,10 @@ func runChat(ctx context.Context, messages []Message, temperature float64, caps
|
|||||||
// depuis le journal, il ne fait que repeindre la même ligne.
|
// depuis le journal, il ne fait que repeindre la même ligne.
|
||||||
s := stats
|
s := stats
|
||||||
s.Kind, s.Lost, s.LostAfter = rec.Kind, rec.Lost, rec.LostAfter
|
s.Kind, s.Lost, s.LostAfter = rec.Kind, rec.Lost, rec.LostAfter
|
||||||
if (rec.Lost != nil && *rec.Lost > 0) || (rec.Cached != nil && *rec.Cached == 0) {
|
// Pas d'alerte pour une API externe : son cache (s'il existe) suit ses
|
||||||
|
// propres règles, sans slot ni points de reprise à surveiller ici. Le
|
||||||
|
// relevé reste dans l'anneau, l'affichage ne change pas.
|
||||||
|
if !ep.External && ((rec.Lost != nil && *rec.Lost > 0) || (rec.Cached != nil && *rec.Cached == 0)) {
|
||||||
s.LostAlert = perfAlert(rec, perfLostAlertAt(chatCfg))
|
s.LostAlert = perfAlert(rec, perfLostAlertAt(chatCfg))
|
||||||
}
|
}
|
||||||
cb(StreamEvent{Stats: &s})
|
cb(StreamEvent{Stats: &s})
|
||||||
|
|||||||
@@ -1,6 +1,7 @@
|
|||||||
package loki
|
package loki
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"bytes"
|
||||||
"context"
|
"context"
|
||||||
"encoding/json"
|
"encoding/json"
|
||||||
"fmt"
|
"fmt"
|
||||||
@@ -115,8 +116,10 @@ type perfWire struct {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func decodePerfWire(data []byte) perfWire {
|
func decodePerfWire(data []byte) perfWire {
|
||||||
|
// Decoder plutôt qu'Unmarshal : un octet parasite après la réponse ne doit
|
||||||
|
// pas tout rendre inconnu. Ce qui a pu être lu l'est ; le reste reste inconnu.
|
||||||
var w perfWire
|
var w perfWire
|
||||||
_ = json.Unmarshal(data, &w) // ce qui a pu être lu l'est ; le reste reste inconnu
|
_ = json.NewDecoder(bytes.NewReader(data)).Decode(&w)
|
||||||
return w
|
return w
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -390,12 +393,24 @@ func perfLine(r perfRec) string {
|
|||||||
// reprise : perdre jusqu'à un espacement de points (--checkpoint-min-step,
|
// reprise : perdre jusqu'à un espacement de points (--checkpoint-min-step,
|
||||||
// 2048 posé par Loki) plus un micro-batch est l'état NORMAL, pas un incident.
|
// 2048 posé par Loki) plus un micro-batch est l'état NORMAL, pas un incident.
|
||||||
// Le signaler à chaque étape noierait les vraies pertes.
|
// Le signaler à chaque étape noierait les vraies pertes.
|
||||||
|
//
|
||||||
|
// Même ordre de priorité que le lancement (ckptArgs) : un -cms d'EXTRA_ARGS ou
|
||||||
|
// LLAMA_ARG_CHECKPOINT_MIN_SPACING_NT l'emporte sur CKPT_MIN_STEP, un -ub
|
||||||
|
// d'EXTRA_ARGS sur UBATCH.
|
||||||
func perfLostAlertAt(cfg map[string]string) int {
|
func perfLostAlertAt(cfg map[string]string) int {
|
||||||
const base = 1000
|
const base = 1000
|
||||||
step := 0
|
extra := splitArgs(cfg["EXTRA_ARGS"])
|
||||||
if n, err := strconv.Atoi(strings.TrimSpace(cfg["CKPT_MIN_STEP"])); err == nil && n > 0 {
|
num := func(vals ...string) int {
|
||||||
step = n
|
for _, v := range vals {
|
||||||
} else if m := strings.TrimSpace(cfg["MODEL"]); m != "" {
|
if n, err := strconv.Atoi(strings.TrimSpace(v)); err == nil && n > 0 {
|
||||||
|
return n
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return 0
|
||||||
|
}
|
||||||
|
step := num(flagValue(extra, "-cms", "--checkpoint-min-step"),
|
||||||
|
os.Getenv("LLAMA_ARG_CHECKPOINT_MIN_SPACING_NT"), cfg["CKPT_MIN_STEP"])
|
||||||
|
if m := strings.TrimSpace(cfg["MODEL"]); step == 0 && m != "" {
|
||||||
if p, err := resolveServeModelPath(m); err == nil {
|
if p, err := resolveServeModelPath(m); err == nil {
|
||||||
if g, err := ggufMeta(p); err == nil && ggufHybrid(&g) {
|
if g, err := ggufMeta(p); err == nil && ggufHybrid(&g) {
|
||||||
step = 2048
|
step = 2048
|
||||||
@@ -405,9 +420,9 @@ func perfLostAlertAt(cfg map[string]string) int {
|
|||||||
if step == 0 {
|
if step == 0 {
|
||||||
return base
|
return base
|
||||||
}
|
}
|
||||||
ub := 512
|
ub := num(flagValue(extra, "-ub", "--ubatch-size"), cfg["UBATCH"])
|
||||||
if n, err := strconv.Atoi(strings.TrimSpace(cfg["UBATCH"])); err == nil && n > 0 {
|
if ub == 0 {
|
||||||
ub = n
|
ub = 512
|
||||||
}
|
}
|
||||||
return max(base, step+ub)
|
return max(base, step+ub)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -43,6 +43,8 @@ func TestPerfWireDecode(t *testing.T) {
|
|||||||
{"moteur muet", `{"usage":{"prompt_tokens":900}}`, nil, nil, nil},
|
{"moteur muet", `{"usage":{"prompt_tokens":900}}`, nil, nil, nil},
|
||||||
// Types de travers (fork, passerelle) : lu si c'est un nombre, sinon inconnu.
|
// Types de travers (fork, passerelle) : lu si c'est un nombre, sinon inconnu.
|
||||||
{"types de travers", `{"timings":{"cache_n":"abc","draft_n":"12","draft_n_accepted":7.0}}`, nil, iptr(12), iptr(7)},
|
{"types de travers", `{"timings":{"cache_n":"abc","draft_n":"12","draft_n_accepted":7.0}}`, nil, iptr(12), iptr(7)},
|
||||||
|
// Octets parasites après la réponse (proxy) : la première valeur compte.
|
||||||
|
{"octets parasites", `{"timings":{"cache_n":12}}` + "\nxyz", iptr(12), nil, nil},
|
||||||
{"structure de travers", `{"timings":"nope","usage":{"prompt_tokens_details":{"cached_tokens":5.9}}}`, iptr(5), nil, nil},
|
{"structure de travers", `{"timings":"nope","usage":{"prompt_tokens_details":{"cached_tokens":5.9}}}`, iptr(5), nil, nil},
|
||||||
}
|
}
|
||||||
eq := func(a, b *int) bool { return (a == nil) == (b == nil) && (a == nil || *a == *b) }
|
eq := func(a, b *int) bool { return (a == nil) == (b == nil) && (a == nil || *a == *b) }
|
||||||
@@ -268,12 +270,20 @@ func TestPerfLostAlert(t *testing.T) {
|
|||||||
{"espacement posé", map[string]string{"CKPT_MIN_STEP": "2048"}, 2560},
|
{"espacement posé", map[string]string{"CKPT_MIN_STEP": "2048"}, 2560},
|
||||||
{"espacement et micro-batch", map[string]string{"CKPT_MIN_STEP": "4096", "UBATCH": "1024"}, 5120},
|
{"espacement et micro-batch", map[string]string{"CKPT_MIN_STEP": "4096", "UBATCH": "1024"}, 5120},
|
||||||
{"espacement illisible", map[string]string{"CKPT_MIN_STEP": "beaucoup"}, 1000},
|
{"espacement illisible", map[string]string{"CKPT_MIN_STEP": "beaucoup"}, 1000},
|
||||||
|
// Comme au lancement : EXTRA_ARGS l'emporte sur les clés du preset.
|
||||||
|
{"-cms d'EXTRA_ARGS", map[string]string{"CKPT_MIN_STEP": "2048", "EXTRA_ARGS": "--checkpoint-min-step 8192 -ub 256"}, 8448},
|
||||||
|
{"-ub d'EXTRA_ARGS seul", map[string]string{"CKPT_MIN_STEP": "2048", "UBATCH": "1024", "EXTRA_ARGS": "-ub=2048"}, 4096},
|
||||||
}
|
}
|
||||||
|
t.Setenv("LLAMA_ARG_CHECKPOINT_MIN_SPACING_NT", "")
|
||||||
for _, c := range cases {
|
for _, c := range cases {
|
||||||
if got := perfLostAlertAt(c.cfg); got != c.want {
|
if got := perfLostAlertAt(c.cfg); got != c.want {
|
||||||
t.Errorf("%s : seuil %d, attendu %d", c.name, got, c.want)
|
t.Errorf("%s : seuil %d, attendu %d", c.name, got, c.want)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
t.Setenv("LLAMA_ARG_CHECKPOINT_MIN_SPACING_NT", "3000")
|
||||||
|
if got := perfLostAlertAt(map[string]string{"CKPT_MIN_STEP": "2048"}); got != 3512 {
|
||||||
|
t.Errorf("LLAMA_ARG_CHECKPOINT_MIN_SPACING_NT ignorée : seuil %d", got)
|
||||||
|
}
|
||||||
if perfAlert(perfRec{Lost: iptr(900)}, 1000) || !perfAlert(perfRec{Lost: iptr(1500)}, 1000) {
|
if perfAlert(perfRec{Lost: iptr(900)}, 1000) || !perfAlert(perfRec{Lost: iptr(1500)}, 1000) {
|
||||||
t.Fatal("seuil de perte")
|
t.Fatal("seuil de perte")
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -7414,7 +7414,7 @@ function renderStats(el, s){
|
|||||||
if(s.gen_tokens) parts.push('decode '+s.gen_tokens+' tok · '+(s.gen_per_second||0).toFixed(1)+' tok/s');
|
if(s.gen_tokens) parts.push('decode '+s.gen_tokens+' tok · '+(s.gen_per_second||0).toFixed(1)+' tok/s');
|
||||||
// Perte de cache signalée par le serveur (seuil relevé sur un modèle hybride,
|
// Perte de cache signalée par le serveur (seuil relevé sur un modèle hybride,
|
||||||
// dont les points de reprise font perdre un peu à chaque étape).
|
// dont les points de reprise font perdre un peu à chaque étape).
|
||||||
if(s.lost_alert && s.lost) parts.push(nfmt(s.lost)+' recalculés'+(s.lost_after ? ' après '+s.lost_after : ''));
|
if(s.lost_alert && s.lost) parts.push(nfmt(s.lost)+' recalculés'+(s.lost_after ? ' après '+s.lost_after.split(',').map(k=>PERF_KIND[k]||k).join(', ') : ''));
|
||||||
if(!parts.length) return;
|
if(!parts.length) return;
|
||||||
// Réponse de l'assistant : ligne de mesures dédiée sous le texte (son étiquette
|
// Réponse de l'assistant : ligne de mesures dédiée sous le texte (son étiquette
|
||||||
// est masquée dans cette mise en page). Bulle repliable : l'étiquette EST le
|
// est masquée dans cette mise en page). Bulle repliable : l'étiquette EST le
|
||||||
@@ -7426,6 +7426,9 @@ function renderStats(el, s){
|
|||||||
if(sl) sl.classList.toggle('cache-miss', !!s.lost_alert);
|
if(sl) sl.classList.toggle('cache-miss', !!s.lost_alert);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
// Ce qui s'est intercalé avant une perte de cache (lost_after, perf_log.go),
|
||||||
|
// dit en clair : les identifiants internes n'ont rien à faire dans l'interface.
|
||||||
|
const PERF_KIND={main:'tour', subagent:'sous-agent', verify:'vérification', task:'tâche', compact:'compaction', bench:'bench', foreign:'client /v1'};
|
||||||
// Milliers séparés par une espace fine insécable : « 41 230 », pas « 41230 ».
|
// Milliers séparés par une espace fine insécable : « 41 230 », pas « 41230 ».
|
||||||
function nfmt(n){ return String(Math.round(n||0)).replace(/\B(?=(\d{3})+(?!\d))/g,'\u202f'); }
|
function nfmt(n){ return String(Math.round(n||0)).replace(/\B(?=(\d{3})+(?!\d))/g,'\u202f'); }
|
||||||
// --- Compteurs de bulle -----------------------------------------------------
|
// --- Compteurs de bulle -----------------------------------------------------
|
||||||
|
|||||||
@@ -249,7 +249,7 @@ function renderStats(el, s){
|
|||||||
if(s.gen_tokens) parts.push('decode '+s.gen_tokens+' tok · '+(s.gen_per_second||0).toFixed(1)+' tok/s');
|
if(s.gen_tokens) parts.push('decode '+s.gen_tokens+' tok · '+(s.gen_per_second||0).toFixed(1)+' tok/s');
|
||||||
// Perte de cache signalée par le serveur (seuil relevé sur un modèle hybride,
|
// Perte de cache signalée par le serveur (seuil relevé sur un modèle hybride,
|
||||||
// dont les points de reprise font perdre un peu à chaque étape).
|
// dont les points de reprise font perdre un peu à chaque étape).
|
||||||
if(s.lost_alert && s.lost) parts.push(nfmt(s.lost)+' recalculés'+(s.lost_after ? ' après '+s.lost_after : ''));
|
if(s.lost_alert && s.lost) parts.push(nfmt(s.lost)+' recalculés'+(s.lost_after ? ' après '+s.lost_after.split(',').map(k=>PERF_KIND[k]||k).join(', ') : ''));
|
||||||
if(!parts.length) return;
|
if(!parts.length) return;
|
||||||
// Réponse de l'assistant : ligne de mesures dédiée sous le texte (son étiquette
|
// Réponse de l'assistant : ligne de mesures dédiée sous le texte (son étiquette
|
||||||
// est masquée dans cette mise en page). Bulle repliable : l'étiquette EST le
|
// est masquée dans cette mise en page). Bulle repliable : l'étiquette EST le
|
||||||
@@ -261,6 +261,9 @@ function renderStats(el, s){
|
|||||||
if(sl) sl.classList.toggle('cache-miss', !!s.lost_alert);
|
if(sl) sl.classList.toggle('cache-miss', !!s.lost_alert);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
// Ce qui s'est intercalé avant une perte de cache (lost_after, perf_log.go),
|
||||||
|
// dit en clair : les identifiants internes n'ont rien à faire dans l'interface.
|
||||||
|
const PERF_KIND={main:'tour', subagent:'sous-agent', verify:'vérification', task:'tâche', compact:'compaction', bench:'bench', foreign:'client /v1'};
|
||||||
// Milliers séparés par une espace fine insécable : « 41 230 », pas « 41230 ».
|
// Milliers séparés par une espace fine insécable : « 41 230 », pas « 41230 ».
|
||||||
function nfmt(n){ return String(Math.round(n||0)).replace(/\B(?=(\d{3})+(?!\d))/g,'\u202f'); }
|
function nfmt(n){ return String(Math.round(n||0)).replace(/\B(?=(\d{3})+(?!\d))/g,'\u202f'); }
|
||||||
// --- Compteurs de bulle -----------------------------------------------------
|
// --- Compteurs de bulle -----------------------------------------------------
|
||||||
|
|||||||
Reference in new issue
Block a user