Files
Loki/internal/loki/parity_perf_test.go
T
Claude 35886da9b1 Lot 39 : anneau de télémétrie et /api/perf/summary tenus par la façade
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
2026-10-16 04:17:04 +00:00

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)
}
}