From 9d02592d4f6bdc919ee060074f991113c8b2387b Mon Sep 17 00:00:00 2001 From: Michael SCHAL Date: Sun, 4 Oct 2026 11:59:50 +0200 Subject: [PATCH] =?UTF-8?q?Optimiseur=20:=20relecture=20=E2=80=94=20slot?= =?UTF-8?q?=20fixe=20sous=20SIDE=5FSLOT,=20moteur=20mort=20vu=20t=C3=B4t,?= =?UTF-8?q?=20verrou=20r=C3=A9=C3=A9crit=20sous=20Windows?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - Avec SIDE_SLOT, les tours en profondeur d'un essai restent sur le second slot : la reprise du cache ne dépend plus du slot choisi par le moteur - Après application, un moteur qui meurt au chargement (OOM) ramène l'ancienne version en quelques secondes au lieu d'attendre le délai entier - La réécriture du verrou (essai en cours) réessaie quand Windows refuse de remplacer un fichier lu au même instant Co-Authored-By: Claude Opus 5.5 --- internal/loki/backend_tune_lock.go | 13 ++++++++++++- internal/loki/backend_tune_run.go | 25 +++++++++++++++++++++---- 2 files changed, 33 insertions(+), 5 deletions(-) diff --git a/internal/loki/backend_tune_lock.go b/internal/loki/backend_tune_lock.go index a7b8506..47ef605 100644 --- a/internal/loki/backend_tune_lock.go +++ b/internal/loki/backend_tune_lock.go @@ -197,7 +197,18 @@ func (l *tuneLock) write() error { if err := os.WriteFile(tmp, b, 0o644); err != nil { return err } - return os.Rename(tmp, l.path) + // Windows refuse de remplacer un fichier qu'un autre processus lit à cet + // instant (tuneGuard d'une requête) : on réessaie un peu avant de renoncer. + for i := 0; ; i++ { + err := os.Rename(tmp, l.path) + if err == nil || i == 4 { + if err != nil { + _ = os.Remove(tmp) + } + return err + } + time.Sleep(50 * time.Millisecond) + } } // setTrial note l'essai en cours (nil : aucun), AVANT qu'il ne charge quoi que diff --git a/internal/loki/backend_tune_run.go b/internal/loki/backend_tune_run.go index f6c26af..cf880e1 100644 --- a/internal/loki/backend_tune_run.go +++ b/internal/loki/backend_tune_run.go @@ -544,7 +544,12 @@ func runTune(ctx context.Context, opts tuneOpts, tr *tuneTracker) (*tuneResult, var runs []tuneRun for i := 0; i < n; i++ { tr.setPhase("%s : mesure %d/%d", phase, i+1, n) - r, err := benchRun(ctx, l.eng, bopts, tuneBenchSetup(cfg, env.ArgEnv, cpuPlaced), corp, nil) + // SIDE_SLOT : deux slots ; les tours en profondeur restent sur le même + // (le second, comme le bench ordinaire), sans quoi la reprise du cache + // dépendrait du slot choisi par le moteur. + s := tuneBenchSetup(cfg, env.ArgEnv, cpuPlaced) + s.sideSlot = sideSlotOn(cfg) && l.slots >= 2 + r, err := benchRun(ctx, l.eng, bopts, s, corp, nil) if err != nil { return runs, err } @@ -934,8 +939,9 @@ func tuneApply(ctx context.Context, res *tuneResult, target string, say func(str // tuneSvc et tuneProbeFn : serviceActionOS et tunePostApplyProbe, remplaçables // dans les tests (application puis retour arrière sans vrai moteur). var ( - tuneSvc = serviceActionOS - tuneProbeFn = tunePostApplyProbe + tuneSvc = serviceActionOS + tuneProbeFn = tunePostApplyProbe + tuneHealthFn = healthCheck ) // tuneAppliedMismatch : la configuration active après application doit être @@ -968,10 +974,21 @@ func tunePostApplyProbe(ctx context.Context, res *tuneResult, say func(string)) say("attente du moteur") e := benchEngine{base: fmt.Sprintf("http://localhost:%d", LLMPort()), auth: tuneAuth(), client: http.DefaultClient} deadline := time.Now().Add(tuneLoadTimeout) - for !healthCheck() { + // Un moteur mort au chargement (OOM) se voit vite : plus de service du tout, + // trois fois de suite après les premières secondes. Inutile d'attendre le + // délai entier pour revenir à l'ancienne version. + gone, t0 := 0, time.Now() + for !tuneHealthFn() { if time.Now().After(deadline) { return fmt.Errorf("le moteur ne répond pas après %s", tuneLoadTimeout) } + if time.Since(t0) > 10*time.Second && !engineNeedsStop() { + if gone++; gone >= 3 { + return errors.New("le moteur s'est arrêté au chargement") + } + } else { + gone = 0 + } select { case <-ctx.Done(): return ctx.Err()