MoE : corrections de relecture du lot 2 — LOAD_GUARD ne refuse plus sur une borne fausse, et le refus se voit

La garde du mode de chargement est le seul refus actif par défaut du lot 2 :
il ne doit tomber que sur un échec certain, et se dire là où l'on regarde.

- Serveurs --rpc (ou LLAMA_ARG_RPC) : une part des poids part ailleurs, la
  VRAM locale ne borne plus rien — avertissement, jamais de refus.
- Mac Intel : la mémoire n'est unifiée que sur Apple Silicon ; ailleurs la
  VRAM est inconnue, donc jamais de refus (l'agrandissement du cache RAM
  reste coupé sur tout macOS, comme avant).
- LLAMA_ARG_NO_MMAP / LLAMA_ARG_MLOCK restées dans l'environnement ne
  comptent plus sur un moteur à --load-mode, qui les ignore : un lancement en
  mmap n'est plus refusé pour elles.
- Le refus sortait de « loki serve » avant tout chargement : l'interface
  affichait un chargement sans fin. modelLoadError le reconnaît et dit quoi
  faire (--load-mode mmap ou LOAD_GUARD=off).

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
MichaelandClaude Opus 5.5 committed 2026-10-04 12:28:57 +02:00
1 parent f0d5711a1a
commit 78d15cefbf
5 files changed
+69 -14

No files matched your search

+7 -4
View File
@@ -74,10 +74,13 @@ func probeCacheRAM(cfg map[string]string, extra []string, si *serveSysInfo) {
}
}
}
si.UnifiedMem = runtime.GOOS == "darwin"
// Mémoire unifiée (macOS), AMD, Vulkan, --device choisi à la main : pas de
// total VRAM fiable, donc pas d'agrandissement.
if si.UnifiedMem || hasAnyFlag(extra, "-dev", "--device") || si.ArgEnv["LLAMA_ARG_DEVICE"] != "" {
// Mémoire unifiée : Apple Silicon seulement. Un Mac Intel à carte AMD a une
// VRAM à lui, que Loki ne sait pas mesurer (loadModeRisk la tient pour
// inconnue, jamais pour nulle).
si.UnifiedMem = runtime.GOOS == "darwin" && runtime.GOARCH == "arm64"
// macOS, AMD, Vulkan, --device choisi à la main : pas de total VRAM fiable,
// donc pas d'agrandissement.
if runtime.GOOS == "darwin" || hasAnyFlag(extra, "-dev", "--device") || si.ArgEnv["LLAMA_ARG_DEVICE"] != "" {
return
}
// La sélection du preset d'abord (cudaDeviceEnv l'accompagne toujours de
+20 -3
View File
@@ -181,7 +181,9 @@ func firstNonEmpty(vals ...string) string {
}
// loadArgEnv : les variables qui règlent le chargement sans drapeau.
var loadArgEnv = []string{"LLAMA_ARG_LOAD_MODE", "LLAMA_ARG_MLOCK", "LLAMA_ARG_NO_MMAP"}
// LLAMA_ARG_RPC n'en est pas une, mais dit que des poids partent sur des
// machines distantes (loadModeRisk).
var loadArgEnv = []string{"LLAMA_ARG_LOAD_MODE", "LLAMA_ARG_MLOCK", "LLAMA_ARG_NO_MMAP", "LLAMA_ARG_RPC"}
// probeLoadEnv relève ces variables. Appelée après probeServeGPUs, qui
// remet ArgEnv à zéro.
@@ -227,6 +229,10 @@ func residentLoadMode(args []string, argEnv map[string]string) string {
return ""
}
// loadGuardMarker : la fin du message de refus, que l'interface reconnaît
// dans le journal du service (modelLoadError).
const loadGuardMarker = "LOAD_GUARD=off pour lancer quand même"
// loadGuardOff : LOAD_GUARD=off, la clé d'échappement du refus.
func loadGuardOff(cfg map[string]string) bool {
switch strings.ToLower(strings.TrimSpace(cfg["LOAD_GUARD"])) {
@@ -256,13 +262,24 @@ func loadModeRisk(cfg map[string]string, args []string, si serveSysInfo) (warn,
if isExternalConfig(cfg) {
return "", ""
}
mode := residentLoadMode(args, si.ArgEnv)
// Un moteur qui connaît --load-mode n'a plus LLAMA_ARG_MLOCK ni
// LLAMA_ARG_NO_MMAP : restées dans l'environnement, elles n'ont aucun effet
// et ne doivent pas faire refuser un lancement en mmap.
env := si.ArgEnv
if helpSupportsLoadMode(si.Help) && (env["LLAMA_ARG_MLOCK"] != "" || env["LLAMA_ARG_NO_MMAP"] != "") {
env = map[string]string{"LLAMA_ARG_LOAD_MODE": si.ArgEnv["LLAMA_ARG_LOAD_MODE"]}
}
mode := residentLoadMode(args, env)
modelMiB := si.ModelBytes >> 20
if mode == "" || modelMiB <= 0 || si.RAMMiB <= 0 {
return "", ""
}
// Serveurs RPC : une part des poids part sur d'autres machines, la VRAM
// locale ne borne plus rien — inconnu, donc un avis au plus, jamais un refus.
remote := hasAnyFlag(args, "--rpc") || strings.TrimSpace(si.ArgEnv["LLAMA_ARG_RPC"]) != ""
low, est, upTo := int64(0), modelMiB, true
switch {
case remote:
case si.UnifiedMem:
low, est, upTo = modelMiB, modelMiB, false
case si.VRAMMiB > 0:
@@ -281,7 +298,7 @@ func loadModeRisk(cfg map[string]string, args []string, si serveSysInfo) (warn,
if loadGuardOff(cfg) {
return msg + " ; LOAD_GUARD=off : lancé quand même", ""
}
return "", msg + " ; LOAD_GUARD=off pour lancer quand même"
return "", msg + " ; " + loadGuardMarker
}
if est*10 > ram*8 {
return fmt.Sprintf("%s : %s%s de poids en RAM pour %s — au-delà, swap (décodage de 25 à 7,5 t/s dans "+
+25
View File
@@ -128,6 +128,15 @@ func TestLoadModeRisk(t *testing.T) {
{name: "LLAMA_ARG_NO_MMAP dans l'environnement",
si: serveSysInfo{ModelBytes: 82 * gib, VRAMMiB: 16 << 10, RAMMiB: 60 << 10,
ArgEnv: map[string]string{"LLAMA_ARG_NO_MMAP": "1"}}, refuse: true},
// Corrections de relecture du lot 2 : pas de refus sur une borne fausse.
{name: "LLAMA_ARG_NO_MMAP sans effet sur un moteur à --load-mode : rien",
si: serveSysInfo{Help: "--load-mode", ModelBytes: 82 * gib, VRAMMiB: 16 << 10, RAMMiB: 60 << 10,
ArgEnv: map[string]string{"LLAMA_ARG_NO_MMAP": "1"}}},
{name: "--rpc : une part des poids part ailleurs, avis seulement",
args: "--load-mode none --rpc 10.0.0.2:50052", si: serveSysInfo{ModelBytes: 82 * gib, VRAMMiB: 16 << 10, RAMMiB: 60 << 10}, warn: true},
{name: "LLAMA_ARG_RPC : de même",
args: "--load-mode none", si: serveSysInfo{ModelBytes: 82 * gib, VRAMMiB: 16 << 10, RAMMiB: 60 << 10,
ArgEnv: map[string]string{"LLAMA_ARG_RPC": "10.0.0.2:50052"}}, warn: true},
{name: "limite du conteneur (RAM effective) : refus",
args: "--load-mode none", si: serveSysInfo{ModelBytes: 40 * gib, VRAMMiB: 16 << 10, RAMMiB: 16 << 10}, refuse: true},
{name: "taille inconnue : rien",
@@ -258,3 +267,19 @@ func TestJoinArgsRoundTrip(t *testing.T) {
t.Errorf("aller-retour : %q", got)
}
}
// Le refus de LOAD_GUARD s'arrête avant tout chargement : l'interface doit le
// dire plutôt qu'un « chargement… » sans fin, même après un chargement réussi
// plus haut dans le journal.
func TestModelLoadErrorLoadGuard(t *testing.T) {
_, refuse := loadModeRisk(map[string]string{}, splitArgs("--load-mode none"),
serveSysInfo{ModelBytes: 82 * gib, VRAMMiB: 16 << 10, RAMMiB: 60 << 10})
log := "llama_model_load: loading model\nmain: model loaded\nserver is listening\n[ERREUR] " + refuse + "\n"
if got := modelLoadErrorFrom(log); !strings.Contains(got, "LOAD_GUARD=off") {
t.Errorf("refus non signalé : %q", got)
}
// Une tentative suivante qui charge efface le refus.
if got := modelLoadErrorFrom(log + "loading model\nmodel loaded\n"); got != "" {
t.Errorf("refus ancien encore signalé : %q", got)
}
}
+10 -3
View File
@@ -96,8 +96,10 @@ func handleStatus(w http.ResponseWriter, r *http.Request) {
// qui exige un fork de llama.cpp. Renvoie "" quand le dernier chargement a
// réussi ou qu'aucune tentative n'est visible (évite les faux positifs sur un
// service simplement arrêté).
func modelLoadError() string {
log := serviceLogTail(200)
func modelLoadError() string { return modelLoadErrorFrom(serviceLogTail(200)) }
// modelLoadErrorFrom : modelLoadError sur un journal donné (tests).
func modelLoadErrorFrom(log string) string {
if log == "" {
return ""
}
@@ -105,7 +107,9 @@ func modelLoadError() string {
// On ne considère que ce qui suit la DERNIÈRE tentative de chargement.
start := 0
for i, l := range lines {
if strings.Contains(l, "loading model") || strings.Contains(l, "load_model") {
// Un lancement refusé par LOAD_GUARD (backend_serve_moe.go) s'arrête
// avant tout chargement : c'est lui, la dernière tentative.
if strings.Contains(l, "loading model") || strings.Contains(l, "load_model") || strings.Contains(l, loadGuardMarker) {
start = i
}
}
@@ -124,6 +128,9 @@ func modelLoadError() string {
strings.Contains(low, "split_mode_tensor not implemented"):
// Les autres lignes qui citent SPLIT_MODE_TENSOR ne sont que des avis.
reason = "le mode tensor (-sm tensor) n'a pas pu démarrer — SPLIT_MODE=off dans le preset"
case strings.Contains(l, loadGuardMarker):
reason = "lancement refusé : trop de poids resteraient en RAM avec ce mode de chargement — " +
"--load-mode mmap dans le preset, ou LOAD_GUARD=off pour passer outre"
case strings.Contains(l, "has offset") && strings.Contains(l, "expected"):
reason = "format de quantification non reconnu par ce moteur"
case strings.Contains(low, "unknown model architecture"),