NGL=999 : la sentinelle « toutes les couches » devient -ngl auto

Le correctif précédent ne pouvait pas s'appliquer. Il ne posait « -ngl auto »
que si la clé NGL était ABSENTE de config.env — or defaultConfig() y sème
NGL=999 sur chaque installation neuve. La quasi-totalité des configurations la
portent donc sans que personne ne l'ait choisie, le code la lisait comme un
choix délibéré, et l'abandon revenait intact :

  W common_fit_params: failed to fit params to free device memory:
    n_gpu_layers already set by user to 999, abort

999 n'a jamais été un nombre de couches : c'est la sentinelle historique
« toutes ». Sur un moteur qui sait mesurer la VRAM libre, elle devient donc
« -ngl auto », et Loki le dit sur stderr plutôt que de le faire en douce. Tout
autre nombre reste intouché — c'est un vrai choix. NGL=all force l'ancien
comportement, NGL=auto n'envoie toujours aucun drapeau, et un moteur ancien
reçoit toujours 999 (l'omettre le ferait tourner 100 % CPU).

- La décision sort dans nglArgs(), fonction pure : la seule question qui
  demande le moteur arrive déjà tranchée, donc elle se teste sans lancer
  llama-server. 10 cas couverts.
- binFitsLayersItself() double la lecture fine de l'aide par la présence de
  --load-mode. Les deux sont arrivés dans la même vague ; une description
  reformulée ou une colonne plus large ne doit pas faire conclure « moteur
  incapable » et réimposer 999.
- L'aide de la clé et le sous-titre du champ disent la nouvelle règle.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
MichaelandClaude Opus 5 committed 2026-08-21 09:47:32 +02:00
1 parent fedbaf32a0
commit 2585d2c2e1
5 files changed
+91 -20

No files matched your search

+53 -17
View File
@@ -84,6 +84,41 @@ func binSupportsNGLAuto(bin string) bool {
return strings.Contains(h[i:end], "'auto'") return strings.Contains(h[i:end], "'auto'")
} }
// binFitsLayersItself : ce moteur sait-il répartir les couches tout seul ?
//
// La lecture fine de l'aide (binSupportsNGLAuto) reste la source de vérité, mais
// elle dépend de la mise en page d'un texte d'aide — une description reformulée
// ou une colonne plus large, et on conclut « non » sur un moteur parfaitement
// capable, donc on lui réimpose 999 et l'abandon revient. --load-mode est arrivé
// dans la même vague que « -ngl auto » : sa présence sert de second témoin, plus
// grossier mais insensible à la mise en page.
func binFitsLayersItself(bin string) bool {
return binSupportsNGLAuto(bin) || binSupportsLoadMode(bin)
}
// nglArgs traduit la valeur NGL du preset en arguments, et renvoie au passage la
// note à afficher quand Loki a substitué quelque chose. Fonction pure : la seule
// question qui demande le moteur (« sait-il répartir les couches tout seul ? »)
// arrive déjà tranchée, ce qui la rend testable sans lancer llama-server.
func nglArgs(ngl string, fitsItself bool) ([]string, string) {
ngl = strings.TrimSpace(ngl)
switch {
case strings.EqualFold(ngl, "auto"):
return nil, "" // rien du tout : le moteur décide seul
case ngl == "999" && fitsItself:
return []string{"-ngl", "auto"},
"NGL=999 (sentinelle « toutes les couches ») → -ngl auto : ce moteur mesure lui-même la " +
"VRAM libre. NGL=all pour forcer l'ancien comportement."
case ngl != "":
return []string{"-ngl", ngl}, ""
case fitsItself:
return []string{"-ngl", "auto"}, ""
default:
// Moteur ancien : omettre -ngl le ferait tourner 100 % CPU.
return []string{"-ngl", "999"}, ""
}
}
// hasAnyFlag dit si l'utilisateur a déjà posé l'un de ces drapeaux dans // hasAnyFlag dit si l'utilisateur a déjà posé l'un de ces drapeaux dans
// EXTRA_ARGS. Loki ajoute plusieurs valeurs par défaut (--parallel, -ngl) : les // EXTRA_ARGS. Loki ajoute plusieurs valeurs par défaut (--parallel, -ngl) : les
// ajouter EN PLUS de celles de l'utilisateur fait râler le moteur (« argument // ajouter EN PLUS de celles de l'utilisateur fait râler le moteur (« argument
@@ -240,31 +275,32 @@ func cmdServe(args []string) error {
if !hasAnyFlag(extra, "--parallel", "-np") { if !hasAnyFlag(extra, "--parallel", "-np") {
llmArgs = append(llmArgs, "--parallel", get("PARALLEL", "1")) llmArgs = append(llmArgs, "--parallel", get("PARALLEL", "1"))
} }
// Couches GPU. Trois cas, dans cet ordre : // Couches GPU. Quatre cas, dans cet ordre :
// //
// NGL=<nombre> → -ngl <nombre> (l'utilisateur tranche) // NGL=auto → rien du tout (le moteur décide seul)
// NGL=auto → rien du tout (compat : anciens presets) // NGL=999 → -ngl auto sur un moteur récent (voir plus bas)
// NGL=<nombre> → -ngl <nombre> (l'utilisateur tranche pour de vrai)
// clé absente → -ngl auto si le moteur le propose, sinon -ngl 999 // clé absente → -ngl auto si le moteur le propose, sinon -ngl 999
// //
// Imposer un nombre désarme common_fit_params, qui mesure la VRAM libre et // Imposer un NOMBRE désarme common_fit_params, qui mesure la VRAM libre et
// choisit combien de couches y tiennent : « n_gpu_layers already set by user // choisit combien de couches y tiennent : « n_gpu_layers already set by user
// to 999, abort ». Le moteur pousse alors tout sur le GPU, échoue en // to 999, abort ». Le moteur pousse alors tout sur le GPU, échoue en
// cudaMalloc ou se replie à moitié sur le CPU — débit effondré, GPU à 100 %. // cudaMalloc ou se replie à moitié sur le CPU — débit effondré, GPU à 100 %.
// D'où « auto » par défaut dès que le moteur sait le faire. Sur un moteur //
// plus ancien, qui ne connaît pas la valeur, on garde 999 : omettre -ngl le // 999 est traité à part parce que ce n'a JAMAIS été un nombre de couches :
// ferait tourner 100 % CPU. // c'est la sentinelle historique « toutes », semée dans config.env par
// defaultConfig() sur chaque installation neuve. La quasi-totalité des
// configurations la portent donc sans que personne ne l'ait choisie — la
// traiter comme un choix délibéré, c'est condamner tout le monde à l'abandon
// ci-dessus. Sur un moteur qui connaît « auto », 999 devient donc auto, et on
// le DIT sur stderr plutôt que de le faire en douce. Qui veut réellement
// forcer tout sur le GPU écrit NGL=all (ou un nombre qui n'est pas 999).
if !hasAnyFlag(extra, "-ngl", "--n-gpu-layers", "--gpu-layers") { if !hasAnyFlag(extra, "-ngl", "--n-gpu-layers", "--gpu-layers") {
ngl := get("NGL", "") args, note := nglArgs(get("NGL", ""), binFitsLayersItself(bin))
switch { if note != "" {
case strings.EqualFold(ngl, "auto"): fmt.Fprintln(os.Stderr, "[loki serve] "+note)
// rien : le moteur décide seul
case ngl != "":
llmArgs = append(llmArgs, "-ngl", ngl)
case binSupportsNGLAuto(bin):
llmArgs = append(llmArgs, "-ngl", "auto")
default:
llmArgs = append(llmArgs, "-ngl", "999")
} }
llmArgs = append(llmArgs, args...)
} }
if ktv != "" { if ktv != "" {
llmArgs = append(llmArgs, "-ctk", ktv) llmArgs = append(llmArgs, "-ctk", ktv)
+35
View File
@@ -56,3 +56,38 @@ func TestNormalizeLoadFlags(t *testing.T) {
}) })
} }
} }
func TestNGLArgs(t *testing.T) {
cases := []struct {
name string
ngl string
fitsItself bool
want []string
wantNote bool
}{
// Le cas qui motive tout : defaultConfig() sème NGL=999 sur chaque
// installation neuve. Personne ne l'a choisi, et le moteur récent
// abandonne son calcul de VRAM dès qu'on lui impose un nombre.
{"sentinelle 999, moteur récent", "999", true, []string{"-ngl", "auto"}, true},
{"sentinelle 999, moteur ancien", "999", false, []string{"-ngl", "999"}, false},
{"nombre choisi, jamais touché", "28", true, []string{"-ngl", "28"}, false},
{"nombre choisi, moteur ancien", "28", false, []string{"-ngl", "28"}, false},
{"all reste all", "all", true, []string{"-ngl", "all"}, false},
{"auto : aucun drapeau", "auto", true, nil, false},
{"auto insensible à la casse", "AUTO", true, nil, false},
{"espaces parasites autour de la sentinelle", " 999 ", true, []string{"-ngl", "auto"}, true},
{"clé absente, moteur récent", "", true, []string{"-ngl", "auto"}, false},
{"clé absente, moteur ancien", "", false, []string{"-ngl", "999"}, false},
}
for _, c := range cases {
t.Run(c.name, func(t *testing.T) {
got, note := nglArgs(c.ngl, c.fitsItself)
if !reflect.DeepEqual(got, c.want) {
t.Fatalf("args = %q, want %q", got, c.want)
}
if (note != "") != c.wantNote {
t.Fatalf("note = %q, en voulait-on une ? %v", note, c.wantNote)
}
})
}
}
+1 -1
View File
@@ -67,7 +67,7 @@ var configTemplate = []struct{ key, help string }{
{"HOST", "adresse d'écoute du moteur (défaut 0.0.0.0)"}, {"HOST", "adresse d'écoute du moteur (défaut 0.0.0.0)"},
{"PORT", "port du moteur (défaut 8080)"}, {"PORT", "port du moteur (défaut 8080)"},
{"CTX", "taille du contexte (défaut 32768)"}, {"CTX", "taille du contexte (défaut 32768)"},
{"NGL", "couches déportées sur le GPU (défaut 999 = tout ; auto = ce qui tient en VRAM)"}, {"NGL", "couches déportées sur le GPU (999 ou auto = ce qui tient en VRAM ; all = tout, quitte à saturer)"},
{"BATCH", "batch (défaut 2048)"}, {"BATCH", "batch (défaut 2048)"},
{"UBATCH", "micro-batch (défaut 512)"}, {"UBATCH", "micro-batch (défaut 512)"},
{"THREADS", "threads CPU, 0 = auto"}, {"THREADS", "threads CPU, 0 = auto"},
+1 -1
View File
@@ -3121,7 +3121,7 @@ html[data-files="1"] #files-btn{color:var(--accent)}
<span class="pe-row-c"><input id="s-ctx" class="pe-val" type="number" min="0" step="1024" placeholder="32768" oninput="cfgWriteKey('CTX', this.value)"><span class="pe-unit">tok</span></span> <span class="pe-row-c"><input id="s-ctx" class="pe-val" type="number" min="0" step="1024" placeholder="32768" oninput="cfgWriteKey('CTX', this.value)"><span class="pe-unit">tok</span></span>
</div> </div>
<div class="pe-row"> <div class="pe-row">
<span class="pe-row-l">Couches sur GPU<span class="pe-sub">999 = tout sur le GPU · auto = ce qui tient en VRAM</span></span> <span class="pe-row-l">Couches sur GPU<span class="pe-sub">999 ou auto = ce qui tient en VRAM · all = tout, quitte à saturer</span></span>
<span class="pe-row-c"><input id="s-ngl" class="pe-val" type="text" inputmode="numeric" placeholder="999" oninput="cfgWriteKey('NGL', this.value)"></span> <span class="pe-row-c"><input id="s-ngl" class="pe-val" type="text" inputmode="numeric" placeholder="999" oninput="cfgWriteKey('NGL', this.value)"></span>
</div> </div>
<div class="pe-row"> <div class="pe-row">
+1 -1
View File
@@ -984,7 +984,7 @@ document.documentElement.setAttribute('data-side',localStorage.getItem('loki-sid
<span class="pe-row-c"><input id="s-ctx" class="pe-val" type="number" min="0" step="1024" placeholder="32768" oninput="cfgWriteKey('CTX', this.value)"><span class="pe-unit">tok</span></span> <span class="pe-row-c"><input id="s-ctx" class="pe-val" type="number" min="0" step="1024" placeholder="32768" oninput="cfgWriteKey('CTX', this.value)"><span class="pe-unit">tok</span></span>
</div> </div>
<div class="pe-row"> <div class="pe-row">
<span class="pe-row-l">Couches sur GPU<span class="pe-sub">999 = tout sur le GPU · auto = ce qui tient en VRAM</span></span> <span class="pe-row-l">Couches sur GPU<span class="pe-sub">999 ou auto = ce qui tient en VRAM · all = tout, quitte à saturer</span></span>
<span class="pe-row-c"><input id="s-ngl" class="pe-val" type="text" inputmode="numeric" placeholder="999" oninput="cfgWriteKey('NGL', this.value)"></span> <span class="pe-row-c"><input id="s-ngl" class="pe-val" type="text" inputmode="numeric" placeholder="999" oninput="cfgWriteKey('NGL', this.value)"></span>
</div> </div>
<div class="pe-row"> <div class="pe-row">