Presets : changer de modèle ramenait le moteur de l'image

Le moteur mis à jour depuis l'interface (Réglages → Moteur, BIN →
/data/engine/server-cuda-b10680/llama-server) ne tenait pas : à la
bascule de preset suivante — changer de modèle, tâche planifiée —
/app/llama-server revenait, et le modèle qui chargeait cinq minutes
plus tôt mourait sur « unknown model architecture: 'qwen4exp' ». Le
journal alternait les deux moteurs sans qu'aucun réglage visible ait
bougé, et le panneau Moteur affichait « fourni par l'image » juste
après un « ✓ moteur mis à jour ».

Cause : chaque preset créé depuis l'UI embarque BIN (newPresetSeedKeys),
donc le chemin de l'image de l'époque, et applyPresetFile remplace TOUTE
la configuration par le preset — BIN n'était pas dans preservedKeys.

Le moteur est un réglage de machine : le courant est conservé à la
bascule, sauf si le preset désigne un backend personnalisé (compilé pour
un modèle précis — là, c'est un vrai choix par modèle, il gagne). Et un
nouveau preset ne fige plus le moteur de l'image ni un moteur téléchargé.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01961iDyM6pwn2dE2SW23gYX
This commit is contained in:
MichaelandClaude Fable 5 committed 2026-08-30 08:27:42 +02:00
1 parent 78c258ab45
commit 6f6a3218ad
2 files changed
+95 -2

No files matched your search

+58
View File
@@ -68,6 +68,64 @@ func TestSwitchToPresetGardeLesReglagesMachine(t *testing.T) {
}
}
// Le moteur suit la machine. Vécu : moteur mis à jour depuis l'interface
// (BIN → /data/engine/server-cuda-b10680), Qwen3.8-Flash-Next charge ; on
// change de modèle — le preset, créé avant, porte BIN=/app/llama-server — et
// le vieux moteur revient : « unknown model architecture: 'qwen4exp' ». Seul
// un backend personnalisé, choix par modèle, a le droit d'imposer son BIN.
func TestSwitchToPresetGardeLeMoteur(t *testing.T) {
home := testHome(t)
t.Setenv("LOKI_ENGINE_BIN", "/app/llama-server")
write := func(p, body string) {
if err := os.WriteFile(p, []byte(body), 0o644); err != nil {
t.Fatal(err)
}
}
engine := filepath.Join(home, "engine", "server-cuda-b10680", "llama-server")
setConfig(t, "MODEL=ancien.gguf\nBIN="+engine+"\n")
// Preset d'avant la mise à jour : il porte le moteur de l'image.
image := filepath.Join(home, "image.env")
write(image, "MODEL=nouveau.gguf\nBIN=/app/llama-server\n")
if err := applyPresetFile(image); err != nil {
t.Fatal(err)
}
if got := ReadConfig()["BIN"]; got != engine {
t.Errorf("BIN = %q après bascule, attendu le moteur mis à jour %q", got, engine)
}
// Preset muet sur le moteur : idem.
muet := filepath.Join(home, "muet.env")
write(muet, "MODEL=autre.gguf\n")
if err := applyPresetFile(muet); err != nil {
t.Fatal(err)
}
if got := ReadConfig()["BIN"]; got != engine {
t.Errorf("BIN = %q après preset muet, attendu %q conservé", got, engine)
}
// Backend personnalisé : le preset gagne.
custom := filepath.Join(home, "custom.env")
write(custom, "MODEL=exotique.gguf\nBIN=/opt/mon-fork/llama-server\n")
if err := applyPresetFile(custom); err != nil {
t.Fatal(err)
}
if got := ReadConfig()["BIN"]; got != "/opt/mon-fork/llama-server" {
t.Errorf("BIN = %q, attendu le backend personnalisé du preset", got)
}
// Un nouveau preset ne fige pas ce backend-là… si, justement : c'est un
// choix par modèle. Mais il ne fige ni l'image ni un moteur téléchargé.
if seed := newPresetSeed(); seed["BIN"] != "/opt/mon-fork/llama-server" {
t.Errorf("seed BIN = %q, attendu le backend personnalisé", seed["BIN"])
}
setConfig(t, "MODEL=ancien.gguf\nBIN="+engine+"\n")
if seed := newPresetSeed(); seed["BIN"] != "" {
t.Errorf("seed BIN = %q, un moteur téléchargé ne doit pas être figé dans un preset", seed["BIN"])
}
setConfig(t, "MODEL=ancien.gguf\nBIN=/app/llama-server\n")
if seed := newPresetSeed(); seed["BIN"] != "" {
t.Errorf("seed BIN = %q, le moteur de l'image ne doit pas être figé dans un preset", seed["BIN"])
}
}
// Un preset qui définit LUI-MÊME la sélection de cartes doit gagner sur celle
// de la machine : « FABLE 2 GPU » impose CUDA_VISIBLE_DEVICES=1,0 pour que son
// --tensor-split ait deux cartes. Écraser ça par une sélection mono-GPU faisait