mirror of
https://github.com/R0m1k3/Loki.git
synced 2026-10-11 17:26:57 +02:00
Bench : corrections de relecture — niveau de raisonnement, préchauffage borné, verrou fiable
La relecture du bench en tâche de fond a trouvé quatre défauts qui le faisaient échouer ou s'interrompre là où il n'aurait pas dû, et une course entre la fin annoncée et le verrou rendu. - Niveau de raisonnement refusé par le gabarit (Qwen3.8 et « high ») : le bench rejoue une fois avec le niveau accepté, comme runChatTools, et retient la traduction. Il échouait sur ce 500 dans un processus neuf. L'erreur HTTP garde le corps entier : les niveaux acceptés sont cités au-delà des 300 caractères affichés. - Préchauffage borné au quart du contexte : -ub 4096 sur 8k de contexte envoyait un prompt plus grand que la fenêtre. - Changer de discussion n'annule plus le bench, et ne lui reprend pas le verrou. La libération suit le drapeau benching au lieu de l'epoch, qu'une bascule de fil bumpe. Reset arrête toujours le bench et rend le verrou. - Le verrou du chat est rendu avant que le statut ne dise « terminé » : un message envoyé aussitôt n'est plus refusé (et le test n'est plus instable). - Indication « possible thrash » mesurée sur les tours seulement, pas sur le prefill à froid qui lit légitimement les pages du modèle. - Interface : agrégats absents (omitempty) affichés à 0 au lieu de casser le rendu. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
1 parent
cf83f5f629
commit
09ee04037b
6 files changed
+206
-42
No files matched your search
@@ -198,7 +198,14 @@ func LoadConversation() {
|
||||
// cache de la discussion active change sous mu, avec le contenu (voir
|
||||
// convActivate). L'appelant NE doit PAS détenir mu.
|
||||
func (c *Conversation) loadFrom(id string, b []byte) {
|
||||
c.Stop()
|
||||
// Un benchmark n'appartient à aucune discussion : changer de fil ne l'arrête
|
||||
// pas, et le verrou reste à lui jusqu'à sa fin (llm_bench_job.go).
|
||||
c.mu.Lock()
|
||||
bench := c.benching
|
||||
c.mu.Unlock()
|
||||
if !bench {
|
||||
c.Stop()
|
||||
}
|
||||
c.mu.Lock()
|
||||
if c == conv {
|
||||
convActiveRemember(id)
|
||||
@@ -211,8 +218,10 @@ func (c *Conversation) loadFrom(id string, b []byte) {
|
||||
c.Messages = stripImageParts(c.Messages) // même guérison qu'au chargement
|
||||
}
|
||||
go pruneChatImages() // plus rien ne référence les images de l'ancien fil
|
||||
c.Generating = false
|
||||
c.cancel = nil
|
||||
if !c.benching {
|
||||
c.Generating = false
|
||||
c.cancel = nil
|
||||
}
|
||||
c.epoch++
|
||||
c.cond.Broadcast()
|
||||
c.mu.Unlock()
|
||||
@@ -1069,6 +1078,7 @@ func (c *Conversation) Reset() {
|
||||
c.ctxUsedLen, c.genPeak = 0, 0
|
||||
c.epoch++
|
||||
c.Generating = false
|
||||
c.benching = false // Stop a annulé le bench : le verrou est rendu ici
|
||||
c.cancel = nil
|
||||
c.queued = nil // les messages en attente visaient le fil qu'on vient de vider
|
||||
c.cond.Broadcast()
|
||||
|
||||
+72
-17
@@ -236,15 +236,30 @@ func (e benchEngine) do(ctx context.Context, method, path string, payload any) (
|
||||
defer resp.Body.Close()
|
||||
data, err := io.ReadAll(io.LimitReader(resp.Body, 8<<20))
|
||||
if resp.StatusCode != http.StatusOK {
|
||||
snip := strings.TrimSpace(string(data))
|
||||
if len(snip) > 300 {
|
||||
snip = snip[:300]
|
||||
}
|
||||
return nil, fmt.Errorf("%s %s : %s %s", method, path, resp.Status, snip)
|
||||
return nil, &benchHTTPError{method: method, path: path, status: resp.Status, body: string(data)}
|
||||
}
|
||||
return data, err
|
||||
}
|
||||
|
||||
// benchHTTPError : une réponse non-200 du moteur. Le corps est gardé en entier :
|
||||
// le refus d'un niveau de raisonnement cite les niveaux acceptés en FIN de
|
||||
// message (llm_effort.go), bien au-delà de l'extrait affiché.
|
||||
type benchHTTPError struct {
|
||||
method, path, status, body string
|
||||
}
|
||||
|
||||
func (e *benchHTTPError) Error() string {
|
||||
snip := strings.TrimSpace(e.body)
|
||||
if len(snip) > 300 {
|
||||
cut := 300
|
||||
for cut > 0 && !utf8.RuneStart(snip[cut]) {
|
||||
cut-- // jamais au milieu d'un caractère
|
||||
}
|
||||
snip = snip[:cut]
|
||||
}
|
||||
return fmt.Sprintf("%s %s : %s %s", e.method, e.path, e.status, snip)
|
||||
}
|
||||
|
||||
// idle : le moteur est-il libre ? Un slot qui travaille, c'est une requête
|
||||
// d'un autre (client /v1, autre processus loki) : le bench passerait derrière
|
||||
// elle et mesurerait l'attente. /slots coupé (501) ou absent : on ne sait pas,
|
||||
@@ -395,8 +410,12 @@ var benchArgEnv = []string{"LLAMA_ARG_CACHE_TYPE_K", "LLAMA_ARG_CACHE_TYPE_V",
|
||||
// benchSetup : ce que le bench tire du preset, une fois.
|
||||
type benchSetup struct {
|
||||
cfg map[string]string
|
||||
effort string
|
||||
want string // niveau de raisonnement demandé par le preset
|
||||
off bool // raisonnement interdit
|
||||
effort string // niveau envoyé (want, ou la traduction apprise)
|
||||
kwargs map[string]any
|
||||
retried bool // refus du niveau déjà rejoué une fois
|
||||
learn func(want, got string) // retient la traduction (nil : tests)
|
||||
ub int
|
||||
cpuPlaced bool // poids sur CPU (-ot, --n-cpu-moe…) : profondeur plafonnée
|
||||
hybrid bool // modèle hybride : reprise du cache aux points de contrôle
|
||||
@@ -412,7 +431,7 @@ func benchSetupFrom(cfg map[string]string, argEnv map[string]string) benchSetup
|
||||
want := reasoningEffortValue(cfg["REASONING_EFFORT"])
|
||||
effort := effortResolve(want)
|
||||
off := reasoningExplicitlyOff(cfg["REASONING"]) || want == "none"
|
||||
s := benchSetup{cfg: cfg, effort: effort, kwargs: reasoningTemplateKwargs(off, effort), ub: 512,
|
||||
s := benchSetup{cfg: cfg, want: want, off: off, effort: effort, kwargs: reasoningTemplateKwargs(off, effort), ub: 512,
|
||||
gpus: strings.TrimSpace(cfg["CUDA_VISIBLE_DEVICES"])}
|
||||
for _, v := range []string{flagValue(extra, "-ub", "--ubatch-size"), cfg["UBATCH"]} {
|
||||
if n, err := strconv.Atoi(strings.TrimSpace(v)); err == nil && n > 0 {
|
||||
@@ -476,6 +495,29 @@ func (s benchSetup) payload(msgs []Message, maxTokens int, cache bool) map[strin
|
||||
return p
|
||||
}
|
||||
|
||||
// ask envoie une requête de chat du bench. Un gabarit qui REFUSE le niveau de
|
||||
// raisonnement du preset (Qwen3.8 ne connaît pas « high ») est traité comme
|
||||
// dans le chat (runChatTools) : on rejoue une fois avec le niveau qu'il accepte,
|
||||
// ou sans le champ, et la traduction est retenue pour ce modèle. Sans ça, le
|
||||
// bench d'un processus neuf échouait sur un 500 là où le chat, lui, répond.
|
||||
func (s *benchSetup) ask(ctx context.Context, e benchEngine, msgs []Message, maxTokens int, cache bool) (benchReply, error) {
|
||||
r, err := e.chat(ctx, s.payload(msgs, maxTokens, cache))
|
||||
var he *benchHTTPError
|
||||
if err == nil || s.retried || s.want == "" || !errors.As(err, &he) {
|
||||
return r, err
|
||||
}
|
||||
fixed, ok := effortFromRejection(he.body, s.want)
|
||||
if !ok {
|
||||
return r, err
|
||||
}
|
||||
s.retried = true
|
||||
if s.learn != nil {
|
||||
s.learn(s.want, fixed)
|
||||
}
|
||||
s.effort, s.kwargs = fixed, reasoningTemplateKwargs(s.off, fixed)
|
||||
return e.chat(ctx, s.payload(msgs, maxTokens, cache))
|
||||
}
|
||||
|
||||
// benchDepthFor choisit la profondeur : la moitié du contexte, plafonnée, et
|
||||
// assez basse pour que les tours ajoutés y tiennent encore (préchauffage compris,
|
||||
// par prudence). Trop petit : la phase est sautée, pas ratée.
|
||||
@@ -580,11 +622,17 @@ func benchRun(ctx context.Context, e benchEngine, opts benchOpts, s benchSetup,
|
||||
|
||||
// 1. Préchauffage, jeté : premières allocations, graphes CUDA, pages du
|
||||
// modèle — sans lui, la première mesure paie l'installation. Pris en fin de
|
||||
// corpus, loin de ce que relira la profondeur.
|
||||
// corpus, loin de ce que relira la profondeur. Deux ubatchs, mais jamais plus
|
||||
// du quart du contexte : un preset à -ub 4096 sur 8k de contexte enverrait
|
||||
// sinon un prompt plus grand que la fenêtre, et le bench échouerait là.
|
||||
next("préchauffage")
|
||||
warm := 2*s.ub + benchWarmGen
|
||||
warmText, _ := benchSlice(corp.code, benchLineStart(corp.code, len(corp.code)-int(float64(2*s.ub)*codeRatio)), len(corp.code))
|
||||
if _, err := e.chat(ctx, s.payload([]Message{{Role: "user", Content: warmText + "\n\n" + benchAskFirst}}, benchWarmGen, false)); err != nil {
|
||||
warmTok := 2 * s.ub
|
||||
if nCtx > 0 {
|
||||
warmTok = max(min(warmTok, nCtx/4), 64)
|
||||
}
|
||||
warm := warmTok + benchWarmGen
|
||||
warmText, _ := benchSlice(corp.code, benchLineStart(corp.code, len(corp.code)-int(float64(warmTok)*codeRatio)), len(corp.code))
|
||||
if _, err := s.ask(ctx, e, []Message{{Role: "user", Content: warmText + "\n\n" + benchAskFirst}}, benchWarmGen, false); err != nil {
|
||||
return nil, fmt.Errorf("préchauffage : %w", err)
|
||||
}
|
||||
|
||||
@@ -599,7 +647,7 @@ func benchRun(ctx context.Context, e benchEngine, opts benchOpts, s benchSetup,
|
||||
chunk, _ := benchSlice(corp.code, benchLineStart(corp.code, len(corp.code)/2), int(float64(rest)*codeRatio))
|
||||
prompt += "\n\n" + chunk
|
||||
}
|
||||
quick, err := e.chat(ctx, s.payload([]Message{{Role: "user", Content: strings.TrimSpace(prompt) + benchAskProse}}, opts.Predict, false))
|
||||
quick, err := s.ask(ctx, e, []Message{{Role: "user", Content: strings.TrimSpace(prompt) + benchAskProse}}, opts.Predict, false)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("ligne courte : %w", err)
|
||||
}
|
||||
@@ -621,7 +669,7 @@ func benchRun(ctx context.Context, e benchEngine, opts benchOpts, s benchSetup,
|
||||
res.Elapsed = time.Since(t0).Seconds()
|
||||
return res, nil
|
||||
}
|
||||
if err := benchDepthRun(ctx, e, s, corp.code, codeRatio, depth, next); err != nil {
|
||||
if err := benchDepthRun(ctx, e, &s, corp.code, codeRatio, depth, next); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
res.Elapsed = time.Since(t0).Seconds()
|
||||
@@ -635,12 +683,11 @@ func benchTimedOut(ctx context.Context, err error) bool {
|
||||
|
||||
// benchDepthRun : prefill à froid à D, puis benchTurns tours qui prolongent la
|
||||
// même discussion avec du code jamais vu.
|
||||
func benchDepthRun(ctx context.Context, e benchEngine, s benchSetup, code string, ratio float64, depth *benchDepth, next func(string)) error {
|
||||
func benchDepthRun(ctx context.Context, e benchEngine, s *benchSetup, code string, ratio float64, depth *benchDepth, next func(string)) error {
|
||||
next(fmt.Sprintf("prefill à froid (%d jetons)", depth.Target))
|
||||
prefix, off := benchSlice(code, 0, int(float64(depth.Target)*ratio))
|
||||
msgs := []Message{{Role: "user", Content: "Voici un fichier source de l'interface web de Loki :\n\n```html\n" + prefix + "\n```\n\n" + benchAskFirst}}
|
||||
io0, rss0, ioOK := engineIOSample()
|
||||
cold, err := e.chat(ctx, s.payload(msgs, benchGenTokens, false))
|
||||
cold, err := s.ask(ctx, e, msgs, benchGenTokens, false)
|
||||
if benchTimedOut(ctx, err) {
|
||||
depth.Partial = fmt.Sprintf("prefill à froid interrompu après %s", benchReqTimeout)
|
||||
return nil
|
||||
@@ -651,6 +698,10 @@ func benchDepthRun(ctx context.Context, e benchEngine, s benchSetup, code string
|
||||
ct := cold.turn
|
||||
depth.Cold = &ct
|
||||
prev, answer := cold.promptTotal(), cold.content
|
||||
// Relectures du disque comptées sur les TOURS seulement (decode à D) : le
|
||||
// prefill à froid lit légitimement des pages du modèle, il fausserait
|
||||
// l'indication.
|
||||
io0, rss0, ioOK := engineIOSample()
|
||||
var newN, gen int
|
||||
var promptMs, genMs float64
|
||||
for r := 1; r <= benchTurns; r++ {
|
||||
@@ -671,7 +722,7 @@ func benchDepthRun(ctx context.Context, e benchEngine, s benchSetup, code string
|
||||
msgs = append(msgs,
|
||||
Message{Role: "assistant", Content: answer},
|
||||
Message{Role: "user", Content: "Suite du fichier :\n\n```html\n" + chunk + "\n```\n\n" + benchAskNext})
|
||||
rep, err := e.chat(ctx, s.payload(msgs, benchGenTokens, true))
|
||||
rep, err := s.ask(ctx, e, msgs, benchGenTokens, true)
|
||||
if benchTimedOut(ctx, err) {
|
||||
depth.Partial = fmt.Sprintf("tour %d interrompu après %s", r, benchReqTimeout)
|
||||
break
|
||||
@@ -770,6 +821,10 @@ func runBench(ctx context.Context, opts benchOpts, progress benchProgress) (*ben
|
||||
}
|
||||
}
|
||||
setup := benchSetupFrom(cfg, argEnv)
|
||||
setup.learn = func(want, got string) {
|
||||
effortRemember(want, got)
|
||||
logEffortFallback(want, got)
|
||||
}
|
||||
eng := benchEngine{base: fmt.Sprintf("http://localhost:%d", port), auth: resolveChatEndpoint().auth, client: http.DefaultClient}
|
||||
// Le bench prend le slot de la conversation : une fois fini (erreur et
|
||||
// annulation comprises), on l'efface si c'est sans risque, pour que son état
|
||||
|
||||
@@ -33,8 +33,10 @@ var errBenchBusy = errors.New("un benchmark occupe le moteur — attends sa fin
|
||||
|
||||
// benchLease prend le verrou de génération pour un benchmark. cancel est
|
||||
// branché sur le bouton stop du chat, comme pour une tâche planifiée. La
|
||||
// fonction rendue le libère ; si un Reset l'a déjà rendu entre-temps (epoch
|
||||
// changé), elle ne touche pas au tour qui a pu démarrer depuis.
|
||||
// fonction rendue le libère tant que benching dit que le verrou est encore au
|
||||
// bench : un Reset l'a peut-être déjà rendu (benching remis à faux), et le tour
|
||||
// qui a pu démarrer depuis ne doit pas être déclaré libre. L'epoch ne suffit
|
||||
// pas : changer de discussion le bumpe SANS arrêter le bench (loadFrom).
|
||||
func (c *Conversation) benchLease(cancel context.CancelFunc) (func(), error) {
|
||||
c.mu.Lock()
|
||||
defer c.mu.Unlock()
|
||||
@@ -42,15 +44,12 @@ func (c *Conversation) benchLease(cancel context.CancelFunc) (func(), error) {
|
||||
return nil, ErrBusy
|
||||
}
|
||||
c.Generating, c.benching, c.cancel = true, true, cancel
|
||||
epoch := c.epoch
|
||||
var once sync.Once
|
||||
return func() {
|
||||
once.Do(func() {
|
||||
c.mu.Lock()
|
||||
c.benching = false
|
||||
if c.epoch == epoch {
|
||||
c.Generating = false
|
||||
c.cancel = nil
|
||||
if c.benching {
|
||||
c.Generating, c.benching, c.cancel = false, false, nil
|
||||
}
|
||||
c.mu.Unlock()
|
||||
})
|
||||
@@ -115,7 +114,6 @@ func benchStart(opts benchOpts) (id string, code int, err error) {
|
||||
benchJob.cancel, benchJob.result, benchJob.err, benchJob.canceled = cancel, nil, "", false
|
||||
go func() {
|
||||
defer cancel()
|
||||
defer release()
|
||||
res, err := benchRunner(ctx, opts, func(phase string, step, steps int) {
|
||||
benchJob.mu.Lock()
|
||||
if benchJob.id == id {
|
||||
@@ -123,6 +121,11 @@ func benchStart(opts benchOpts) (id string, code int, err error) {
|
||||
}
|
||||
benchJob.mu.Unlock()
|
||||
})
|
||||
canceled := ctx.Err() != nil
|
||||
// Le verrou du chat est rendu AVANT d'annoncer la fin : l'interface qui
|
||||
// lit « terminé » et envoie aussitôt un message ne doit pas se voir
|
||||
// refuser « benchmark en cours ».
|
||||
release()
|
||||
benchJob.mu.Lock()
|
||||
defer benchJob.mu.Unlock()
|
||||
if benchJob.id != id {
|
||||
@@ -131,9 +134,9 @@ func benchStart(opts benchOpts) (id string, code int, err error) {
|
||||
benchJob.running, benchJob.finished, benchJob.cancel = false, time.Now(), nil
|
||||
benchJob.result = res
|
||||
if err != nil {
|
||||
benchJob.canceled = ctx.Err() != nil
|
||||
benchJob.canceled = canceled
|
||||
benchJob.err = err.Error()
|
||||
if benchJob.canceled {
|
||||
if canceled {
|
||||
benchJob.err = "benchmark annulé"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -18,14 +18,15 @@ import (
|
||||
// de jetons à 4 octets par jeton, un cache qui reprend le prompt précédent
|
||||
// quand cache_prompt est vrai, et le journal des requêtes de chat reçues.
|
||||
type fakeBenchEngine struct {
|
||||
mu sync.Mutex
|
||||
nCtx int
|
||||
busy bool
|
||||
failAt int // n° (1-based) de la requête de chat qui répond 500 ; 0 = aucune
|
||||
noTiming bool // réponses sans timings
|
||||
slowHot time.Duration // délai des tours qui reprennent le cache
|
||||
prev int
|
||||
calls []map[string]any
|
||||
mu sync.Mutex
|
||||
nCtx int
|
||||
busy bool
|
||||
failAt int // n° (1-based) de la requête de chat qui répond 500 ; 0 = aucune
|
||||
noTiming bool // réponses sans timings
|
||||
rejectEffort string // niveau de reasoning_effort que le « gabarit » refuse (500)
|
||||
slowHot time.Duration // délai des tours qui reprennent le cache
|
||||
prev int
|
||||
calls []map[string]any
|
||||
}
|
||||
|
||||
func (f *fakeBenchEngine) handler(t *testing.T) http.Handler {
|
||||
@@ -57,6 +58,13 @@ func (f *fakeBenchEngine) handler(t *testing.T) http.Handler {
|
||||
http.Error(w, `{"error":"boom"}`, http.StatusInternalServerError)
|
||||
return
|
||||
}
|
||||
if e, _ := p["reasoning_effort"].(string); f.rejectEffort != "" && e == f.rejectEffort {
|
||||
// Le vrai message cite la trace jinja d'abord : les niveaux acceptés
|
||||
// arrivent bien après les 300 premiers caractères.
|
||||
http.Error(w, `{"error":{"code":500,"message":"`+strings.Repeat("jinja trace ", 40)+
|
||||
`Unexpected reasoning effort `+e+`. Supported types are xhigh (default), medium, and low."}}`, http.StatusInternalServerError)
|
||||
return
|
||||
}
|
||||
cache, _ := p["cache_prompt"].(bool)
|
||||
if d := f.slowHot; cache && d > 0 {
|
||||
select {
|
||||
@@ -541,3 +549,91 @@ func TestBenchRefuseV1(t *testing.T) {
|
||||
t.Fatalf("pendant le bench : %d, Retry-After=%q", rec.Code, rec.Header().Get("Retry-After"))
|
||||
}
|
||||
}
|
||||
|
||||
// Un gabarit qui refuse le niveau de raisonnement du preset : le bench rejoue
|
||||
// une fois avec le niveau accepté, comme le chat, et le retient — au lieu
|
||||
// d'échouer sur le 500.
|
||||
func TestBenchNiveauRaisonnementRefuse(t *testing.T) {
|
||||
f := &fakeBenchEngine{nCtx: 16384, rejectEffort: "high"}
|
||||
eng, _ := f.start(t)
|
||||
s := benchSetupFrom(map[string]string{"REASONING_EFFORT": "high"}, nil)
|
||||
var learned []string
|
||||
s.learn = func(want, got string) { learned = append(learned, want+">"+got) }
|
||||
res, err := benchRun(context.Background(), eng, benchOpts{Mode: benchModeQuick}, s, testBenchCorpora(), nil)
|
||||
if err != nil {
|
||||
t.Fatalf("refus du niveau non rattrapé : %v", err)
|
||||
}
|
||||
if len(f.calls) != 3 || res.PredictedPerSec != 50 {
|
||||
t.Fatalf("%d requêtes (attendu 3 : refus, préchauffage rejoué, ligne courte)", len(f.calls))
|
||||
}
|
||||
for i, p := range f.calls[1:] {
|
||||
kw, _ := p["chat_template_kwargs"].(map[string]any)
|
||||
if p["reasoning_effort"] != "xhigh" || kw["reasoning_effort"] != "xhigh" {
|
||||
t.Errorf("requête %d : niveau %v / %v, attendu xhigh", i+2, p["reasoning_effort"], kw)
|
||||
}
|
||||
}
|
||||
if len(learned) != 1 || learned[0] != "high>xhigh" {
|
||||
t.Errorf("traduction retenue : %v", learned)
|
||||
}
|
||||
// Une autre erreur que ce refus n'est pas rejouée.
|
||||
f2 := &fakeBenchEngine{nCtx: 16384, failAt: 1}
|
||||
eng2, _ := f2.start(t)
|
||||
if _, err := benchRun(context.Background(), eng2, benchOpts{}, benchSetupFrom(map[string]string{"REASONING_EFFORT": "high"}, nil), testBenchCorpora(), nil); err == nil || len(f2.calls) != 1 {
|
||||
t.Errorf("500 ordinaire : err=%v, %d requêtes", err, len(f2.calls))
|
||||
}
|
||||
}
|
||||
|
||||
// Un gros ubatch sur un petit contexte : le préchauffage reste sous le quart
|
||||
// de la fenêtre au lieu de la dépasser.
|
||||
func TestBenchPrechauffageBorne(t *testing.T) {
|
||||
f := &fakeBenchEngine{nCtx: 8192}
|
||||
eng, _ := f.start(t)
|
||||
s := benchSetupFrom(map[string]string{"UBATCH": "4096"}, nil)
|
||||
if _, err := benchRun(context.Background(), eng, benchOpts{}, s, testBenchCorpora(), nil); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
c := f.calls[0]["messages"].([]any)[0].(map[string]any)["content"].(string)
|
||||
if toks := len(c) / 4; toks > 8192/4+64 {
|
||||
t.Errorf("préchauffage de %d jetons sur 8192 de contexte", toks)
|
||||
}
|
||||
}
|
||||
|
||||
// Changer de discussion n'arrête pas le bench et ne rend pas le verrou ; un
|
||||
// Reset l'arrête et le rend, et la libération tardive du bench ne déclare pas
|
||||
// libre le tour qui a démarré depuis.
|
||||
func TestBenchVerrouEtDiscussions(t *testing.T) {
|
||||
testHome(t)
|
||||
c := newTestConv()
|
||||
ctx, cancel := context.WithCancel(context.Background())
|
||||
defer cancel()
|
||||
release, err := c.benchLease(cancel)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
c.loadFrom("autre", nil)
|
||||
if ctx.Err() != nil || !c.isGenerating() || c.busyReason() != errBenchBusy {
|
||||
t.Fatal("changer de discussion a arrêté le bench ou rendu son verrou")
|
||||
}
|
||||
release()
|
||||
if c.isGenerating() {
|
||||
t.Fatal("verrou non rendu à la fin du bench, après un changement de discussion")
|
||||
}
|
||||
|
||||
ctx2, cancel2 := context.WithCancel(context.Background())
|
||||
defer cancel2()
|
||||
release2, err := c.benchLease(cancel2)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
c.Reset()
|
||||
if ctx2.Err() == nil || c.isGenerating() {
|
||||
t.Fatal("Reset doit arrêter le bench et rendre le verrou")
|
||||
}
|
||||
c.mu.Lock()
|
||||
c.Generating = true // un tour démarré depuis
|
||||
c.mu.Unlock()
|
||||
release2()
|
||||
if !c.isGenerating() {
|
||||
t.Fatal("la libération tardive du bench a déclaré libre le tour suivant")
|
||||
}
|
||||
}
|
||||
@@ -5043,8 +5043,8 @@ function benchResultHTML(x, saved){
|
||||
if(d.skipped) notes.push('profondeur sautée : '+d.skipped);
|
||||
if(d.cold) rows.push(['prefill à froid · '+d.cold.new+' tok', d.cold.prompt_per_second.toFixed(0)+' tok/s']);
|
||||
if(d.turns&&d.turns.length){
|
||||
rows.push(['prefill des tours suivants (cache)', d.cached_per_second.toFixed(0)+' tok/s']);
|
||||
rows.push(['decode en profondeur', d.decode_per_second.toFixed(1)+' tok/s']);
|
||||
rows.push(['prefill des tours suivants (cache)', (d.cached_per_second||0).toFixed(0)+' tok/s']);
|
||||
rows.push(['decode en profondeur', (d.decode_per_second||0).toFixed(1)+' tok/s']);
|
||||
rows.push(['reprise du cache', d.reuse>=0 ? Math.round(d.reuse*100)+' %' : 'n/a']);
|
||||
rows.push(['brouillon accepté (code)', benchDraftTxt(d.draft)]);
|
||||
}
|
||||
|
||||
@@ -75,8 +75,8 @@ function benchResultHTML(x, saved){
|
||||
if(d.skipped) notes.push('profondeur sautée : '+d.skipped);
|
||||
if(d.cold) rows.push(['prefill à froid · '+d.cold.new+' tok', d.cold.prompt_per_second.toFixed(0)+' tok/s']);
|
||||
if(d.turns&&d.turns.length){
|
||||
rows.push(['prefill des tours suivants (cache)', d.cached_per_second.toFixed(0)+' tok/s']);
|
||||
rows.push(['decode en profondeur', d.decode_per_second.toFixed(1)+' tok/s']);
|
||||
rows.push(['prefill des tours suivants (cache)', (d.cached_per_second||0).toFixed(0)+' tok/s']);
|
||||
rows.push(['decode en profondeur', (d.decode_per_second||0).toFixed(1)+' tok/s']);
|
||||
rows.push(['reprise du cache', d.reuse>=0 ? Math.round(d.reuse*100)+' %' : 'n/a']);
|
||||
rows.push(['brouillon accepté (code)', benchDraftTxt(d.draft)]);
|
||||
}
|
||||
|
||||
Reference in new issue
Block a user