mirror of
https://github.com/R0m1k3/Loki.git
synced 2026-10-17 04:10:04 +02:00
L'anneau des complétions (perf_log.go) est porté en Rust (src/perfring.rs) : perte de cache contre la complétion précédente de la même discussion et de la même nature, ce qui s'est intercalé, seuil d'alerte du preset (dans l'ordre du lancement, 2048 sur un modèle hybride), ligne [perf], résumé de /api/perf/summary. ChatShared le porte : tours, vérifications, résumés de compaction et préchauffage de la façade y rangent leurs complétions sans aller-retour au Go. Côté Go, quand la façade tient la conversation, perfRecord range chez elle (/internal/facade/v1/perf/record) et perfSnapshot relit son anneau (optimiseur) ; /api/perf/summary lui est remis. Façade injoignable : l'anneau du Go prend le relais. La route /internal/v1/turn/perf, plus appelée, est retirée. Parité : journal tiré du Go (parity_perf_test.go → tests/parity_perf/ perf.json : 14 complétions, résumé, onze seuils), rejoué par src/perfring/tests.rs ; deux tours qui perdent le cache (tests/lot6.rs), l'API de l'anneau pour le Go, et TestFacadePerfAnneau côté Go. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_016tcGfJWefW5kUGoC2wL8cv
143 lines
5.9 KiB
Go
143 lines
5.9 KiB
Go
//go:build linux
|
|
|
|
package loki
|
|
|
|
import (
|
|
"bytes"
|
|
"encoding/json"
|
|
"os"
|
|
"path/filepath"
|
|
"strconv"
|
|
"testing"
|
|
"time"
|
|
)
|
|
|
|
// parity_perf_test.go — journal de référence de l'anneau de télémétrie (lot 39
|
|
// de la migration Rust) : une suite de complétions rangées (chacune avec les
|
|
// empreintes des messages envoyés), ce que l'anneau en a tiré (numéro, perte
|
|
// de cache, ce qui s'est intercalé), la ligne [perf], le résumé de
|
|
// /api/perf/summary, puis les seuils d'alerte d'une série de presets. Rangé
|
|
// dans rust/loki-rs/tests/parity_perf/perf.json ; src/perfring/tests.rs le
|
|
// rejoue.
|
|
//
|
|
// LOKI_PARITY_UPDATE=1 go test ./internal/loki -run TestPariteAnneauPerf
|
|
|
|
const parityPerfFile = "../../rust/loki-rs/tests/parity_perf/perf.json"
|
|
|
|
type parityPerfStep struct {
|
|
Rec json.RawMessage `json:"rec"`
|
|
Msgs []string `json:"msgs,omitempty"` // contenus des messages envoyés (rôle user)
|
|
Prefix []string `json:"prefix,omitempty"`
|
|
Out json.RawMessage `json:"out"`
|
|
Line string `json:"line"`
|
|
Alert int `json:"alert_1000"` // perfAlert au seuil 1000 : 1 vrai, 0 faux
|
|
}
|
|
|
|
type parityPerfAlert struct {
|
|
Cfg map[string]string `json:"cfg"`
|
|
Env string `json:"env,omitempty"` // LLAMA_ARG_CHECKPOINT_MIN_SPACING_NT
|
|
Want int `json:"want"`
|
|
}
|
|
|
|
type parityPerfJournal struct {
|
|
Steps []parityPerfStep `json:"steps"`
|
|
Summary json.RawMessage `json:"summary"`
|
|
Empty json.RawMessage `json:"empty"`
|
|
Alerts []parityPerfAlert `json:"alerts"`
|
|
}
|
|
|
|
func TestPariteAnneauPerf(t *testing.T) {
|
|
testHome(t)
|
|
freshPerf(t)
|
|
at := time.Date(2026, 10, 16, 8, 0, 0, 0, time.UTC)
|
|
var j parityPerfJournal
|
|
rec := func(r perfRec, msgs ...string) {
|
|
var prefix []uint64
|
|
if msgs != nil {
|
|
ms := make([]Message, len(msgs))
|
|
for i, c := range msgs {
|
|
ms[i] = Message{Role: "user", Content: c}
|
|
}
|
|
prefix = perfPrefix(ms)
|
|
}
|
|
in, _ := json.Marshal(r)
|
|
r.At = at
|
|
at = at.Add(time.Second)
|
|
got := perfLog.record(r, prefix)
|
|
out, _ := json.Marshal(got)
|
|
st := parityPerfStep{Rec: in, Msgs: msgs, Out: out, Line: perfLine(got)}
|
|
for _, p := range prefix {
|
|
st.Prefix = append(st.Prefix, strconv.FormatUint(p, 10))
|
|
}
|
|
if perfAlert(got, 1000) {
|
|
st.Alert = 1
|
|
}
|
|
j.Steps = append(j.Steps, st)
|
|
}
|
|
main := func(conv string, iter, total int, cached *int) perfRec {
|
|
return perfRec{Kind: perfMain, Conv: conv, Iter: iter, Complete: true, Total: total, Cached: cached}
|
|
}
|
|
full := perfRec{Kind: perfMain, Conv: "c1", Iter: 0, Complete: true, Total: 4000, Cached: iptr(3000),
|
|
New: 1000, PPms: 2000.4, PPtps: 499.9, Gen: 50, TGtps: 40.25, DraftN: iptr(40), DraftAcc: iptr(30), TTFTms: ptr64(2100)}
|
|
rec(full, "a")
|
|
rec(main("c1", 1, 4600, iptr(4000)), "a", "b", "c") // prolonge : rien de perdu
|
|
rec(perfRec{Kind: perfSubagent, Conv: "c1", Complete: true, Total: 300, Cached: iptr(0), New: 300, PPms: 600, PPtps: 500}, "z")
|
|
rec(perfRec{Kind: perfForeign}) // client externe : vu, pas mesuré
|
|
rec(main("c1", 2, 5200, iptr(200)), "a", "b", "c", "d") // perte après le sous-agent et le client
|
|
rec(main("c1", 3, 5200, nil), "a", "b", "c", "d", "e") // cache inconnu
|
|
rec(main("c1", 4, 5300, iptr(10)), "x", "b") // diverge
|
|
rec(main("c2", 0, 20000, iptr(0)), "q") // autre discussion, gros prompt sans cache
|
|
rec(perfRec{Kind: perfMain, Conv: "c2", Iter: 1, Cached: iptr(0)}, "q", "r") // complétion coupée
|
|
rec(main("c2", 1, 21000, iptr(19000)), "q", "r") // prolonge malgré la coupure
|
|
rec(perfRec{Kind: perfCompact, Conv: "c2", Complete: true, Total: 9000, Cached: iptr(8800), New: 200, PPms: 100, PPtps: 2000, Gen: 300, TGtps: 30, TTFTms: ptr64(900)})
|
|
rec(perfRec{Kind: perfBench, Complete: true, Total: 33000, New: 33000, PPms: 30000, PPtps: 1100, Gen: 64, TGtps: 20.5})
|
|
rec(perfRec{Kind: perfVerify, Conv: "c1", Iter: 0, Complete: true, Total: 12000, Cached: iptr(0), New: 12000, PPms: 9000.5, PPtps: 1333.3, Gen: 2, TGtps: 9.75, DraftN: iptr(0)})
|
|
rec(main("c1", 0, 6000, iptr(5900)), "a", "b", "c", "d", "e", "f") // prolonge la dernière terminée de c1
|
|
|
|
sum, _ := json.Marshal(perfSummarize(perfLog.snapshot()))
|
|
j.Summary = sum
|
|
empty, _ := json.Marshal(perfSummarize(nil))
|
|
j.Empty = empty
|
|
|
|
for _, c := range []parityPerfAlert{
|
|
{Cfg: map[string]string{}},
|
|
{Cfg: map[string]string{"CKPT_MIN_STEP": "2048"}},
|
|
{Cfg: map[string]string{"CKPT_MIN_STEP": "4096", "UBATCH": "1024"}},
|
|
{Cfg: map[string]string{"CKPT_MIN_STEP": "beaucoup"}},
|
|
{Cfg: map[string]string{"CKPT_MIN_STEP": " 300 ", "UBATCH": "100"}},
|
|
{Cfg: map[string]string{"CKPT_MIN_STEP": "2048", "EXTRA_ARGS": "--checkpoint-min-step 8192 -ub 256"}},
|
|
{Cfg: map[string]string{"CKPT_MIN_STEP": "2048", "UBATCH": "1024", "EXTRA_ARGS": "-ub=2048"}},
|
|
{Cfg: map[string]string{"CKPT_MIN_STEP": "-5", "UBATCH": "0"}},
|
|
{Cfg: map[string]string{"CKPT_MIN_STEP": "2048"}, Env: "3000"},
|
|
{Cfg: map[string]string{"CKPT_MIN_STEP": "2048"}, Env: "rien"},
|
|
{Cfg: map[string]string{"MODEL": "absent.gguf"}},
|
|
} {
|
|
t.Setenv("LLAMA_ARG_CHECKPOINT_MIN_SPACING_NT", c.Env)
|
|
c.Want = perfLostAlertAt(c.Cfg)
|
|
j.Alerts = append(j.Alerts, c)
|
|
}
|
|
|
|
got, err := json.MarshalIndent(j, "", " ")
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
got = append(got, '\n')
|
|
if os.Getenv("LOKI_PARITY_UPDATE") == "1" {
|
|
if err := os.MkdirAll(filepath.Dir(parityPerfFile), 0o755); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
if err := os.WriteFile(parityPerfFile, got, 0o644); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
return
|
|
}
|
|
want, err := os.ReadFile(parityPerfFile)
|
|
if err != nil {
|
|
t.Fatalf("journal de référence absent (%v) : LOKI_PARITY_UPDATE=1 pour le créer", err)
|
|
}
|
|
if !bytes.Equal(got, want) {
|
|
t.Errorf("l'anneau de télémétrie ne produit plus le journal rangé dans %s.\n"+
|
|
"LOKI_PARITY_UPDATE=1 pour le régénérer si le changement est voulu.", parityPerfFile)
|
|
}
|
|
}
|