From 0a02604968694d3afeb2505b675d07988d06ee6f Mon Sep 17 00:00:00 2001 From: Michael SCHAL Date: Sat, 3 Oct 2026 22:37:59 +0200 Subject: [PATCH] =?UTF-8?q?Moteur=20:=20corrections=20de=20relecture=20des?= =?UTF-8?q?=20threads=20CPU=20=E2=80=94=20un=20-tb=20absent=20n'efface=20p?= =?UTF-8?q?lus=20les=20r=C3=A9glages=20batch=20d'EXTRA=5FARGS?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Sans -tb, llama.cpp ne recopie pas seulement le nombre de -t : il remplace TOUT le réglage CPU du batch par celui de -t (postprocess_cpu_params, « cpuparams = *role_model »). Un -Cb, -Crb, --cpu-strict-batch, --prio-batch ou --poll-batch écrit dans EXTRA_ARGS était donc effacé sans un mot depuis que Loki ne pose plus « -tb 0 » — un réglage utilisateur cassé. - Dans ce cas seulement, Loki pose -tb : la valeur de -t quand elle est connue (THREADS, sonde du conteneur ou -t / --threads d'EXTRA_ARGS), sinon 0, l'ancien comportement, et le dit sur stderr. - Un -tb déjà dans EXTRA_ARGS reste maître, comme avant. - flagValue lit la dernière occurrence d'un drapeau (« -t 6 » ou « -t=6 »). - Tests : quatre lignes de construction d'arguments et une table flagValue. Co-Authored-By: Claude Opus 5.5 --- internal/loki/backend_serve_args_test.go | 26 +++++++++++ internal/loki/backend_serve_threads.go | 51 +++++++++++++++++++-- internal/loki/backend_serve_threads_test.go | 18 ++++++++ 3 files changed, 92 insertions(+), 3 deletions(-) diff --git a/internal/loki/backend_serve_args_test.go b/internal/loki/backend_serve_args_test.go index e3a3cb4..7602ff2 100644 --- a/internal/loki/backend_serve_args_test.go +++ b/internal/loki/backend_serve_args_test.go @@ -117,6 +117,32 @@ func TestBuildServeArgs(t *testing.T) { si: serveSysInfo{Help: helpRecent}, want: with("--parallel", "1", "-ngl", "28", "-t=6", "--threads-batch", "10"), }, + { + // Sans -tb, llama.cpp remplacerait tout le réglage CPU du batch par + // celui de -t : le --poll-batch de l'utilisateur serait effacé. + name: "--poll-batch dans EXTRA_ARGS, THREADS vide : -tb 0 le protège, et on le dit", + cfg: map[string]string{"NGL": "28", "EXTRA_ARGS": "--poll-batch 0"}, + si: serveSysInfo{Help: helpRecent}, + want: withThreads([]string{"-tb", "0"}, "--parallel", "1", "-ngl", "28", "--poll-batch", "0"), wantNotes: 1, + }, + { + name: "-Cb dans EXTRA_ARGS, THREADS=6 : -tb recopie 6", + cfg: map[string]string{"NGL": "28", "THREADS": "6", "EXTRA_ARGS": "-Cb 0xff"}, + si: serveSysInfo{Help: helpRecent}, + want: withThreads([]string{"-t", "6", "-tb", "6"}, "--parallel", "1", "-ngl", "28", "-Cb", "0xff"), + }, + { + name: "-t=5 et --prio-batch dans EXTRA_ARGS : -tb recopie 5", + cfg: map[string]string{"NGL": "28", "EXTRA_ARGS": "-t=5 --prio-batch 2"}, + si: serveSysInfo{Help: helpRecent}, + want: withThreads([]string{"-tb", "5"}, "--parallel", "1", "-ngl", "28", "-t=5", "--prio-batch", "2"), + }, + { + name: "-Crb et -tb dans EXTRA_ARGS : Loki se tait", + cfg: map[string]string{"NGL": "28", "EXTRA_ARGS": "-Crb 0-7 -tb 8"}, + si: serveSysInfo{Help: helpRecent}, + want: with("--parallel", "1", "-ngl", "28", "-Crb", "0-7", "-tb", "8"), + }, { name: "conteneur à l'étroit : -t de la sonde, et une note", cfg: map[string]string{"NGL": "28"}, diff --git a/internal/loki/backend_serve_threads.go b/internal/loki/backend_serve_threads.go index 3b57880..8c880f2 100644 --- a/internal/loki/backend_serve_threads.go +++ b/internal/loki/backend_serve_threads.go @@ -65,21 +65,33 @@ func threadCount(key, v string) (int, string) { // THREADS vide/0 → rien (cœurs physiques, choisis par llama.cpp) // THREADS_BATCH=N>0 → -tb N // THREADS_BATCH vide/0 → rien (le moteur recopie -t) +// … sauf -Cb/--poll-batch… → -tb (voir plus bas) // // Un -t / -tb déjà écrit dans EXTRA_ARGS gagne : on ne double pas le drapeau. // Un masque d'affinité posé à la main (-C, -Cr, --cpu-strict) désarme la sonde : // l'utilisateur a pris la main sur le placement, on ne devine rien par-dessus. +// +// Piège du -tb absent : llama.cpp ne recopie pas seulement le NOMBRE de -t, il +// remplace TOUT le réglage CPU du batch par celui de -t (postprocess_cpu_params, +// « cpuparams = *role_model »). Un -Cb, --poll-batch ou --prio-batch écrit dans +// EXTRA_ARGS serait alors effacé sans un mot — le -tb 0 d'avant le protégeait. +// Dans ce cas seulement, on pose donc -tb : la valeur de -t si on la connaît +// (« comme THREADS »), sinon 0, l'ancien comportement. func threadArgs(threads, threadsBatch string, extra []string, auto cpuBudget) (args, notes []string) { t, note := threadCount("THREADS", threads) if note != "" { notes = append(notes, note) } + tVal := "" // valeur de -t effective, si on la connaît switch { case hasAnyFlag(extra, "-t", "--threads"): + tVal = flagValue(extra, "-t", "--threads") case t > 0: - args = append(args, "-t", strconv.Itoa(t)) + tVal = strconv.Itoa(t) + args = append(args, "-t", tVal) case auto.N > 0 && !hasAnyFlag(extra, "-C", "--cpu-mask", "-Cr", "--cpu-range", "--cpu-strict"): - args = append(args, "-t", strconv.Itoa(auto.N)) + tVal = strconv.Itoa(auto.N) + args = append(args, "-t", tVal) notes = append(notes, fmt.Sprintf("threads auto → -t %d : %s (llama.cpp seul en lancerait %d)", auto.N, auto.Why, auto.Engine)) } @@ -87,12 +99,45 @@ func threadArgs(threads, threadsBatch string, extra []string, auto cpuBudget) (a if note != "" { notes = append(notes, note) } - if tb > 0 && !hasAnyFlag(extra, "-tb", "--threads-batch") { + switch { + case hasAnyFlag(extra, "-tb", "--threads-batch"): + case tb > 0: args = append(args, "-tb", strconv.Itoa(tb)) + case hasAnyFlag(extra, batchCPUFlags...): + if tVal == "" { + tVal = "0" + notes = append(notes, "EXTRA_ARGS règle le CPU du batch (-Cb, --poll-batch…) : -tb 0 posé pour que "+ + "llama.cpp le garde (tous les threads logiques) ; THREADS_BATCH pour choisir le nombre") + } + args = append(args, "-tb", tVal) } return args, notes } +// batchCPUFlags : les réglages CPU propres au batch, qu'un -tb absent effacerait. +var batchCPUFlags = []string{"-Cb", "--cpu-mask-batch", "-Crb", "--cpu-range-batch", + "--cpu-strict-batch", "--prio-batch", "--poll-batch"} + +// flagValue renvoie la valeur de la DERNIÈRE occurrence de l'un des drapeaux +// (« -t 6 » ou « -t=6 ») — celle que retient le moteur. Vide s'il n'y en a pas. +func flagValue(args []string, flags ...string) string { + v := "" + for i, a := range args { + name, val, hasEq := strings.Cut(a, "=") + for _, f := range flags { + if name != f { + continue + } + if hasEq { + v = val + } else if i+1 < len(args) { + v = args[i+1] + } + } + } + return v +} + // cpusetThreads : sonde de conteneur, Linux seulement (fsys = la racine « / »). // // L'auto de llama.cpp se trompe quand le conteneur n'a droit qu'à une partie diff --git a/internal/loki/backend_serve_threads_test.go b/internal/loki/backend_serve_threads_test.go index 5c9c2c0..731f585 100644 --- a/internal/loki/backend_serve_threads_test.go +++ b/internal/loki/backend_serve_threads_test.go @@ -143,3 +143,21 @@ func TestThreadCount(t *testing.T) { } } } + +func TestFlagValue(t *testing.T) { + for _, c := range []struct { + args []string + want string + }{ + {nil, ""}, + {[]string{"-t", "6"}, "6"}, + {[]string{"--threads=7"}, "7"}, + {[]string{"-t", "6", "--threads", "8"}, "8"}, // la dernière gagne, comme dans le moteur + {[]string{"-tb", "9"}, ""}, // -tb n'est pas -t + {[]string{"-t"}, ""}, // drapeau orphelin + } { + if got := flagValue(c.args, "-t", "--threads"); got != c.want { + t.Errorf("flagValue(%q) = %q, want %q", c.args, got, c.want) + } + } +}