mirror of
https://github.com/R0m1k3/Loki.git
synced 2026-10-20 05:28:15 +02:00
Bench : une mesure honnête à profondeur réelle, en tâche de fond
L'ancien bench ne lisait pas le statut HTTP, inventait un partage 15/85 du temps quand les timings manquaient (et l'enregistrait comme mesuré), tuilait un petit corpus qu'un brouillon recopiait, et ne disait rien du prefill à 30k ni du chemin où le cache est repris. Synchrone, il était coupé par les proxys (60-100 s) pendant que le moteur continuait, et un simple GET suffisait à le lancer. - Tâche de fond : POST /api/bench (202, 405 sur GET, 409 si occupé), /api/bench/status pour la progression, /api/bench/cancel ; chaque requête au moteur porte le contexte annulable. - Verrou de génération tenu pendant toute la mesure : chat, tâches et compaction reçoivent un refus clair ; les clients /v1 un 503 avec Retry-After ; /slots occupé (client externe, CLI) refuse aussi. - Mode rapide par défaut : préchauffage jeté + la ligne courte, sur le chat avec gabarit, raisonnement et échantillonnage du preset, seed fixe. - Mode complet (bouton « bench complet », loki bench --full) : prefill à froid à D = min(CTX/2, 32k, ce qui laisse tenir les tours), 16k si des poids tournent sur CPU, puis 3 tours user → assistant → user de code jamais vu. Reprise du cache lue dans cache_n, jamais supposée ; note pour les hybrides (points de contrôle). Pas d'ignore_eos. - Rien n'est enregistré sans réponses 200 et timings réels, ni pour un résultat partiel (budget de 6 min par requête, contexte plein). - Empreinte enregistrée (configuration + CUDA_VISIBLE_DEVICES + protocole) ; les anciennes mesures retombent sur le nom du modèle. Types de cache KV et build du moteur affichés, KV quantifié signalé (zone grise). - Linux : indication « possible thrash » (read_bytes, VmRSS), jamais enregistrée. - Effacement du slot en fin de bench inchangé (engineSideJob, différé : il tourne aussi sur erreur et annulation). Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
1 parent
9f6bd725e6
commit
cf83f5f629
17 files changed
+1904
-245
No files matched your search
+14
-35
@@ -408,6 +408,7 @@ func handlePresets(w http.ResponseWriter, r *http.Request) {
|
||||
return
|
||||
}
|
||||
store := loadBenchStore()
|
||||
cur := ReadConfig()
|
||||
out := []map[string]any{}
|
||||
for _, p := range list {
|
||||
item := map[string]any{"id": p.ID, "name": p.Name, "active": p.Active}
|
||||
@@ -430,12 +431,23 @@ func handlePresets(w http.ResponseWriter, r *http.Request) {
|
||||
item["reasoning"] = strings.ToLower(r)
|
||||
}
|
||||
}
|
||||
if sb, ok := store[p.ID]; ok && benchMatchesPreset(sb, parseEnv(content)) {
|
||||
item["bench"] = map[string]any{
|
||||
if sb, ok := store[p.ID]; ok && benchMatchesPreset(sb, parseEnv(content), cur) {
|
||||
bench := map[string]any{
|
||||
"prefill": sb.Result.PromptPerSecond,
|
||||
"decode": sb.Result.PredictedPerSec,
|
||||
"at": sb.At,
|
||||
}
|
||||
// Types de cache à côté de la mesure : un KV quantifié n'est pas une
|
||||
// référence sans perte, la pastille le dit.
|
||||
if sb.Result.KV != "" {
|
||||
bench["kv"] = sb.Result.KV
|
||||
bench["kv_quantized"] = sb.Result.KVQuantized
|
||||
}
|
||||
if d := sb.Result.Depth; d != nil && d.DecodePerSec > 0 && d.Cold != nil {
|
||||
bench["depth"] = d.Cold.New
|
||||
bench["depth_decode"] = d.DecodePerSec
|
||||
}
|
||||
item["bench"] = bench
|
||||
}
|
||||
out = append(out, item)
|
||||
}
|
||||
@@ -1340,39 +1352,6 @@ func svcHandler(action string) http.HandlerFunc {
|
||||
// handleChat is the SSE proxy with tool-calling. The HTTP handler writes raw
|
||||
// data: lines matching what the embedded JS expects (delta.content,
|
||||
// delta.reasoning_content, delta.tool_used).
|
||||
// handleBench runs `runBench` synchronously. Long enough (~30-60s) that we
|
||||
// rely on the client side to show a spinner / disable the button.
|
||||
func handleBench(w http.ResponseWriter, r *http.Request) {
|
||||
nPrompt, nPredict := 2000, 300
|
||||
if v := r.URL.Query().Get("prompt"); v != "" {
|
||||
if parsed, err := strconv.Atoi(v); err == nil && parsed > 0 {
|
||||
nPrompt = parsed
|
||||
}
|
||||
}
|
||||
if v := r.URL.Query().Get("n"); v != "" {
|
||||
if parsed, err := strconv.Atoi(v); err == nil && parsed > 0 {
|
||||
nPredict = parsed
|
||||
}
|
||||
}
|
||||
res, err := runBench(nPrompt, nPredict)
|
||||
if err != nil {
|
||||
sendJSON(w, 500, map[string]any{"ok": false, "error": err.Error()})
|
||||
return
|
||||
}
|
||||
sendJSON(w, 200, map[string]any{"ok": true, "result": res})
|
||||
}
|
||||
|
||||
// handleBenchLast returns the most recent persisted benchmark, or {ok:false}
|
||||
// when none has been run yet.
|
||||
func handleBenchLast(w http.ResponseWriter, r *http.Request) {
|
||||
sb := loadLastBench()
|
||||
if sb == nil {
|
||||
sendJSON(w, 200, map[string]any{"ok": false})
|
||||
return
|
||||
}
|
||||
sendJSON(w, 200, map[string]any{"ok": true, "result": sb.Result, "model": sb.Model, "at": sb.At})
|
||||
}
|
||||
|
||||
// chatReq est le corps d'une requête de chat (commun au chat clair et au chat E2E).
|
||||
//
|
||||
// `messages` et `ctx_used`, que les clients envoyaient du temps où l'historique
|
||||
|
||||
Reference in new issue
Block a user