From 1db987844e704ed5d6d6e34a98758f6a785fca59 Mon Sep 17 00:00:00 2001 From: Michael SCHAL Date: Mon, 28 Sep 2026 00:24:58 +0200 Subject: [PATCH] =?UTF-8?q?Chargement=20du=20mod=C3=A8le=20:=20--mlock=20s?= =?UTF-8?q?eul=20ne=20coupe=20plus=20le=20mmap?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit UN VRAI BUG D'ABORD normalizeLoadFlags traduisait --mlock seul en « --load-mode mlock ». Or, pour llama.cpp (common/arg.cpp), « mlock » veut dire PAS de mmap + résident : le modèle entier montait en RAM au lieu d'être mappé. L'ancien --mlock, lui, gardait le mmap — son équivalent est « mmap+mlock ». Sur un modèle plus gros que la RAM (Qwen3.8-Flash-Next, 82 Go pour 64 Go), un preset qui avait coché « Garder en RAM » tournait donc à l'OOM dès qu'un moteur récent prenait le relais. Correspondance alignée sur AJEAN 0.16.0 : --mlock seul → mmap+mlock · --no-mmap → none · les deux → mlock. DANS LES DEUX SENS Un moteur ancien (ou un fork) ne connaît pas --load-mode et sort en erreur si on le lui passe : downgradeLoadMode le retraduit en anciens drapeaux (dio, inconnu de ces moteurs, est abandonné avec un avertissement). UN SÉLECTEUR À LA PLACE DE DEUX INTERRUPTEURS « Garder en RAM » et « Charger tout en mémoire » ne disaient pas leur combinaison. L'éditeur de preset propose les six modes de llama.cpp (auto, mmap, none, mlock, mmap+mlock, dio), avec ce que fait chacun en sous-titre, et écrit la forme moderne. Un preset aux anciens drapeaux s'affiche sur le bon mode sans modification. Au passage, q5_1 porte la mention « lent sur CUDA » dans la liste du cache KV (voir le commit du port occupé : non accéléré en Flash-Attention). Co-Authored-By: Claude Opus 5.5 --- internal/loki/backend_serve.go | 84 +++++++++++++++++++---- internal/loki/backend_serve_flags_test.go | 17 +++-- internal/loki/ui/index.html | 63 ++++++++++++++--- internal/loki/ui/src/index.tmpl.html | 20 ++++-- internal/loki/ui/src/js/07-models.js | 43 +++++++++++- 5 files changed, 193 insertions(+), 34 deletions(-) diff --git a/internal/loki/backend_serve.go b/internal/loki/backend_serve.go index dec4e45..9c8f633 100644 --- a/internal/loki/backend_serve.go +++ b/internal/loki/backend_serve.go @@ -141,22 +141,34 @@ func hasAnyFlag(args []string, flags ...string) bool { return false } -// normalizeLoadFlags traduit les drapeaux de chargement dépréciés vers -// --load-mode, sur les moteurs qui le connaissent : +// normalizeLoadFlags choisit, au lancement, la forme des drapeaux de chargement +// que CE binaire comprend. llama.cpp récent a remplacé --mlock / --no-mmap par +// --load-mode (et refuse les anciens) ; un moteur ancien ou un fork ne connaît +// que les anciens. L'éditeur écrit --load-mode, un preset d'avant peut porter +// les anciens : on traduit dans le sens qu'il faut (repris d'AJEAN 0.16.0). // -// --mlock → --load-mode mlock (garder le modèle en RAM) -// --no-mmap → --load-mode none (tout charger, pas de mmap) -// --mmap → --load-mode mmap +// Vers --load-mode (moteur récent). Deux interrupteurs → une seule valeur, au +// sens où llama.cpp l'entend (« mlock » = PAS de mmap + résident) : // -// Les deux à la fois (--mlock --no-mmap) : mlock l'emporte, c'est l'intention la -// plus forte (résident en RAM, jamais rendu au système). Un --load-mode déjà -// écrit à la main dans EXTRA_ARGS gagne sur tout : on retire alors simplement -// les vieux drapeaux. Sur un moteur ancien, rien n'est touché. +// --mlock seul → mmap+mlock (l'ancien sens de --mlock : mmap conservé) +// --no-mmap seul → none +// --mlock + --no-mmap → mlock +// --mmap → mmap +// +// ⚠️ Jusqu'ici --mlock seul devenait « mlock », qui COUPE le mmap : le modèle +// entier montait en RAM au lieu d'être mappé — un OOM assuré sur un modèle plus +// gros que la mémoire. Un --load-mode déjà écrit gagne sur tout : on retire +// alors simplement les vieux drapeaux. +// +// Vers les anciens drapeaux (moteur qui ne connaît pas --load-mode) : +// +// none → --no-mmap · mlock → --mlock --no-mmap · mmap+mlock → --mlock +// auto, mmap, dio → rien (défaut de l'ancien moteur) func normalizeLoadFlags(args []string, supportsLoadMode bool) []string { if !supportsLoadMode { - return args + return downgradeLoadMode(args) } - explicit := hasAnyFlag(args, "--load-mode") + explicit := hasAnyFlag(args, "--load-mode", "-lm") mlock, nommap, mmap := false, false, false out := make([]string, 0, len(args)+2) for _, a := range args { @@ -176,14 +188,62 @@ func normalizeLoadFlags(args []string, supportsLoadMode bool) []string { } mode := "mmap" switch { - case mlock: + case mlock && nommap: mode = "mlock" + case mlock: + mode = "mmap+mlock" case nommap: mode = "none" } return append(out, "--load-mode", mode) } +// downgradeLoadMode retraduit un --load-mode pour un moteur qui ne le connaît +// pas : le lui passer tel quel le ferait sortir en erreur au démarrage. +func downgradeLoadMode(args []string) []string { + mode, found := "", false + kept := make([]string, 0, len(args)) + for i := 0; i < len(args); i++ { + a := args[i] + if a == "--load-mode" || a == "-lm" { + found = true + if i+1 < len(args) { + mode = strings.ToLower(args[i+1]) + i++ + } + continue + } + if v, ok := strings.CutPrefix(a, "--load-mode="); ok { + found, mode = true, strings.ToLower(v) + continue + } + kept = append(kept, a) + } + if !found { + return args + } + add := func(flag string) { + for _, a := range kept { + if a == flag { + return + } + } + kept = append(kept, flag) + } + switch mode { + case "none": + add("--no-mmap") + case "mlock": + add("--mlock") + add("--no-mmap") + case "mmap+mlock": + add("--mlock") + case "dio": + fmt.Fprintf(os.Stderr, "[loki serve] ce moteur ne connaît pas --load-mode dio (DirectIO) : chargement par défaut\n") + } + return kept +} + // cmdServe replaces the historic start.sh: read config.env, build the // llama-server invocation, and exec it (replacing this process so systemd // supervises llama-server directly). diff --git a/internal/loki/backend_serve_flags_test.go b/internal/loki/backend_serve_flags_test.go index a2b4287..171a915 100644 --- a/internal/loki/backend_serve_flags_test.go +++ b/internal/loki/backend_serve_flags_test.go @@ -33,16 +33,25 @@ func TestNormalizeLoadFlags(t *testing.T) { supports bool want []string }{ - {"moteur ancien : rien ne bouge", []string{"--mlock", "--no-mmap"}, false, + {"moteur ancien : anciens drapeaux intacts", []string{"--mlock", "--no-mmap"}, false, []string{"--mlock", "--no-mmap"}}, - {"mlock traduit", []string{"--mlock", "-fa"}, true, - []string{"-fa", "--load-mode", "mlock"}}, + // L'ancien --mlock gardait le mmap : « mlock » de llama.cpp le coupe. + {"mlock seul → mmap+mlock", []string{"--mlock", "-fa"}, true, + []string{"-fa", "--load-mode", "mmap+mlock"}}, {"no-mmap traduit", []string{"--no-mmap"}, true, []string{"--load-mode", "none"}}, {"mmap traduit", []string{"--mmap"}, true, []string{"--load-mode", "mmap"}}, - {"mlock l'emporte sur no-mmap", []string{"--no-mmap", "--mlock"}, true, + {"mlock + no-mmap → mlock", []string{"--no-mmap", "--mlock"}, true, []string{"--load-mode", "mlock"}}, + {"moteur ancien : none retraduit", []string{"-fa", "--load-mode", "none"}, false, + []string{"-fa", "--no-mmap"}}, + {"moteur ancien : mlock retraduit", []string{"--load-mode", "mlock"}, false, + []string{"--mlock", "--no-mmap"}}, + {"moteur ancien : mmap+mlock retraduit", []string{"-lm", "mmap+mlock"}, false, + []string{"--mlock"}}, + {"moteur ancien : dio abandonné", []string{"--load-mode=dio", "-fa"}, false, + []string{"-fa"}}, {"--load-mode explicite gagne, vieux drapeaux retirés", []string{"--mlock", "--load-mode", "dio"}, true, []string{"--load-mode", "dio"}}, diff --git a/internal/loki/ui/index.html b/internal/loki/ui/index.html index d5fad03..3a9516f 100644 --- a/internal/loki/ui/index.html +++ b/internal/loki/ui/index.html @@ -3440,7 +3440,7 @@ html[data-files="1"] #files-btn{color:var(--accent)} - + @@ -3493,13 +3493,19 @@ html[data-files="1"] #files-btn{color:var(--accent)} Flash attentionplus rapide, moins de VRAM +
- Garder en RAMévite le swap (mlock) - -
-
- Charger tout en mémoiredésactive le mmap - + Chargement +
@@ -5789,8 +5795,47 @@ function populateSettings(){ } } chk('s-flash', eaHasFlag('--flash-attn') && !/^off$/i.test(eaGetValued('--flash-attn'))); - chk('s-mlock', eaHasFlag('--mlock')); - chk('s-nommap', eaHasFlag('--no-mmap')); + const lm = eaLoadMode(); + set('s-loadmode', lm); + syncLoadModeSub(lm); +} +// Chargement du modèle. llama.cpp récent a REMPLACÉ --mlock / --no-mmap par +// --load-mode (auto, none, mmap, mlock, mmap+mlock, dio). L'éditeur écrit la +// forme moderne ; au lancement, loki serve la retraduit pour un moteur ancien +// (normalizeLoadFlags côté Go), et inversement. +function eaLoadMode(){ + const lm = (eaGetValued('--load-mode') || eaGetValued('-lm')).toLowerCase(); + if(lm) return lm === 'auto' ? '' : lm; + // Preset écrit avec les anciens drapeaux : même correspondance que le serveur. + const mlock = eaHasFlag('--mlock'), nommap = eaHasFlag('--no-mmap'); + if(mlock && nommap) return 'mlock'; + if(mlock) return 'mmap+mlock'; + if(nommap) return 'none'; + if(eaHasFlag('--mmap')) return 'mmap'; + return ''; +} +function eaSetLoadMode(mode){ + const t = eaTokens().filter(x=>x!=='--mlock' && x!=='--no-mmap' && x!=='--mmap' && !x.startsWith('--load-mode=')); + for(const f of ['--load-mode','-lm']){ + const i = t.indexOf(f); + if(i>=0){ const hadVal = i+1f16 (max qualité) - + @@ -1285,13 +1285,19 @@ document.documentElement.setAttribute('data-side',localStorage.getItem('loki-sid Flash attentionplus rapide, moins de VRAM +
- Garder en RAMévite le swap (mlock) - -
-
- Charger tout en mémoiredésactive le mmap - + Chargement +
diff --git a/internal/loki/ui/src/js/07-models.js b/internal/loki/ui/src/js/07-models.js index 519466e..3f2c551 100644 --- a/internal/loki/ui/src/js/07-models.js +++ b/internal/loki/ui/src/js/07-models.js @@ -926,8 +926,47 @@ function populateSettings(){ } } chk('s-flash', eaHasFlag('--flash-attn') && !/^off$/i.test(eaGetValued('--flash-attn'))); - chk('s-mlock', eaHasFlag('--mlock')); - chk('s-nommap', eaHasFlag('--no-mmap')); + const lm = eaLoadMode(); + set('s-loadmode', lm); + syncLoadModeSub(lm); +} +// Chargement du modèle. llama.cpp récent a REMPLACÉ --mlock / --no-mmap par +// --load-mode (auto, none, mmap, mlock, mmap+mlock, dio). L'éditeur écrit la +// forme moderne ; au lancement, loki serve la retraduit pour un moteur ancien +// (normalizeLoadFlags côté Go), et inversement. +function eaLoadMode(){ + const lm = (eaGetValued('--load-mode') || eaGetValued('-lm')).toLowerCase(); + if(lm) return lm === 'auto' ? '' : lm; + // Preset écrit avec les anciens drapeaux : même correspondance que le serveur. + const mlock = eaHasFlag('--mlock'), nommap = eaHasFlag('--no-mmap'); + if(mlock && nommap) return 'mlock'; + if(mlock) return 'mmap+mlock'; + if(nommap) return 'none'; + if(eaHasFlag('--mmap')) return 'mmap'; + return ''; +} +function eaSetLoadMode(mode){ + const t = eaTokens().filter(x=>x!=='--mlock' && x!=='--no-mmap' && x!=='--mmap' && !x.startsWith('--load-mode=')); + for(const f of ['--load-mode','-lm']){ + const i = t.indexOf(f); + if(i>=0){ const hadVal = i+1