mirror of
https://github.com/R0m1k3/Loki.git
synced 2026-10-11 17:26:57 +02:00
Loki passait TOUJOURS -ngl, avec 999 comme repli quand le champ est vide : vider « Couches sur GPU » ne changeait donc rien. Or llama.cpp sait ajuster lui-même le nombre de couches à la mémoire libre (common_fit_params), mais y renonce dès que la valeur est imposée — « n_gpu_layers already set by user to 999, abort ». D'où, sur une carte trop juste pour le contexte demandé, des cudaMalloc en échec, un repli partiel sur le CPU, et un débit effondré alors que le GPU tourne à 100 %. NGL=auto n'envoie plus -ngl du tout. Le défaut reste 999 : omettre le drapeau sur un llama.cpp antérieur à cet ajustement le ferait tourner 100 % CPU. Le champ de l'UI passe en texte pour accepter « auto ». Vérifié : NGL=auto → aucun -ngl dans la ligne de commande ; NGL=999 → -ngl 999.
186 lines
6.9 KiB
Go
186 lines
6.9 KiB
Go
package loki
|
|
|
|
import (
|
|
"fmt"
|
|
"os"
|
|
"os/exec"
|
|
"path/filepath"
|
|
"strconv"
|
|
"strings"
|
|
)
|
|
|
|
// sys_service.go holds the platform-neutral pieces of service management. The
|
|
// actual start/stop/restart/status/logs implementation is platform-specific:
|
|
// - sys_service_linux.go → systemd (systemctl/journalctl)
|
|
// - sys_service_darwin.go → launchd (launchctl)
|
|
// - sys_service_windows.go → PID-file background process supervisor
|
|
//
|
|
// editConfig and showVram live here because they work the same everywhere.
|
|
|
|
// preflightEngine vérifie ce sans quoi le moteur ne PEUT pas démarrer, avant de
|
|
// lancer le service. Sinon llama-server sortait en erreur, systemd le relançait
|
|
// toutes les 3 s, et `loki start` affichait un « activating » rassurant pendant
|
|
// que `loki test` répondait « /health ne répond pas ». Le diagnostic n'était
|
|
// visible que dans le journal.
|
|
func preflightEngine() error {
|
|
cfg := ReadConfig()
|
|
bin := strings.TrimSpace(cfg["BIN"])
|
|
if bin == "" {
|
|
return fmt.Errorf("BIN non défini — installe un moteur : %s (ou renseigne BIN avec %s)",
|
|
bold("loki llamacpp install"), bold("loki edit"))
|
|
}
|
|
if !filepath.IsAbs(bin) {
|
|
bin = filepath.Join(LokiHome(), bin)
|
|
}
|
|
if _, err := os.Stat(prebuiltResolveBin(bin)); err != nil {
|
|
return fmt.Errorf("moteur introuvable : %s — relance %s", bin, bold("loki llamacpp install"))
|
|
}
|
|
model := strings.TrimSpace(cfg["MODEL"])
|
|
if model == "" {
|
|
return fmt.Errorf("MODEL non défini — indique un .gguf avec %s (dossier des modèles : %s)",
|
|
bold("loki edit"), modelsDir())
|
|
}
|
|
p, err := resolveServeModelPath(model)
|
|
if err != nil {
|
|
return fmt.Errorf("MODEL=%s : %w", model, err)
|
|
}
|
|
if _, err := os.Stat(p); err != nil {
|
|
return fmt.Errorf("modèle introuvable : %s — corrige MODEL avec %s", p, bold("loki edit"))
|
|
}
|
|
// Modèle en plusieurs fichiers : une tranche manquante ne se voit qu'au moment
|
|
// où llama-server réclame un tenseur absent, dans le journal du service.
|
|
if missing := shardFamilyMissing(filepath.Dir(p), filepath.Base(p)); len(missing) > 0 {
|
|
return fmt.Errorf("modèle incomplet : il manque %s dans %s — ce modèle tient en %d fichiers, télécharge-les tous",
|
|
strings.Join(missing, ", "), filepath.Dir(p), len(shardFamily(filepath.Base(p))))
|
|
}
|
|
return nil
|
|
}
|
|
|
|
// configTemplate est le squelette commenté proposé par `loki edit` : la
|
|
// configuration vit en base, donc sur une installation neuve le fichier
|
|
// temporaire était QUASI VIDE — impossible de deviner quoi écrire. On y déroule
|
|
// donc les clés utiles avec leur rôle, les valeurs déjà définies telles quelles,
|
|
// les autres commentées.
|
|
var configTemplate = []struct{ key, help string }{
|
|
{"BIN", "chemin de llama-server (posé par « loki llamacpp install »)"},
|
|
{"MODEL", "nom de fichier .gguf ou chemin complet"},
|
|
{"HOST", "adresse d'écoute du moteur (défaut 0.0.0.0)"},
|
|
{"PORT", "port du moteur (défaut 8080)"},
|
|
{"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)"},
|
|
{"BATCH", "batch (défaut 2048)"},
|
|
{"UBATCH", "micro-batch (défaut 512)"},
|
|
{"THREADS", "threads CPU, 0 = auto"},
|
|
{"THREADS_BATCH", "threads CPU du prefill, 0 = auto"},
|
|
{"KV_TYPE", "quantization du cache KV (q8_0, q4_0…) ; KV_TYPE_K / KV_TYPE_V pour les séparer"},
|
|
{"REASONING", "passthrough du mode raisonnement (on/auto/deepseek)"},
|
|
{"REASONING_BUDGET", "plafond de tokens de réflexion ; -1 = illimité"},
|
|
{"COMPACT", "compactage automatique du contexte (off pour couper)"},
|
|
{"MEM_MODE", "mémoire de l'IA : off / ondemand / always"},
|
|
{"EXTRA_ARGS", "ajouté tel quel à la ligne de commande de llama-server"},
|
|
}
|
|
|
|
// configEditorText rend la configuration au format présenté dans $EDITOR.
|
|
func configEditorText(cfg map[string]string) string {
|
|
var b strings.Builder
|
|
b.WriteString("# Configuration du moteur Loki (loki-engine).\n")
|
|
b.WriteString("# Une clé par ligne : CLE=valeur. Les lignes commentées (#) sont ignorées :\n")
|
|
b.WriteString("# décommente celles dont tu as besoin. « loki restart » applique.\n\n")
|
|
seen := map[string]bool{}
|
|
for _, f := range configTemplate {
|
|
seen[f.key] = true
|
|
fmt.Fprintf(&b, "# %s\n", f.help)
|
|
if v, ok := cfg[f.key]; ok && v != "" {
|
|
fmt.Fprintf(&b, "%s=%s\n\n", f.key, quoteValue(v))
|
|
} else {
|
|
fmt.Fprintf(&b, "#%s=\n\n", f.key)
|
|
}
|
|
}
|
|
// Tout ce que le squelette ne connaît pas (clés d'une version plus récente,
|
|
// réglages posés par l'UI) : conservé tel quel, en fin de fichier.
|
|
rest := map[string]string{}
|
|
for k, v := range cfg {
|
|
if !seen[k] {
|
|
rest[k] = v
|
|
}
|
|
}
|
|
if len(rest) > 0 {
|
|
b.WriteString("# --- autres clés déjà définies ---\n")
|
|
b.WriteString(formatEnv(rest))
|
|
}
|
|
return b.String()
|
|
}
|
|
|
|
// editConfig ouvre la configuration dans $EDITOR. La configuration vit en base
|
|
// (voir store.go) : on la déroule dans un fichier temporaire au format clé=valeur,
|
|
// on laisse l'éditeur faire son travail, puis on relit. Le contenu n'est réécrit
|
|
// que si l'éditeur sort proprement — un éditeur avorté ne doit rien effacer.
|
|
func editConfig() error {
|
|
editor := os.Getenv("EDITOR")
|
|
if editor == "" {
|
|
editor = defaultEditor()
|
|
}
|
|
tmp, err := os.CreateTemp("", "loki-config-*.env")
|
|
if err != nil {
|
|
return err
|
|
}
|
|
path := tmp.Name()
|
|
defer os.Remove(path)
|
|
if _, err := tmp.WriteString(configEditorText(ReadConfig())); err != nil {
|
|
tmp.Close()
|
|
return err
|
|
}
|
|
tmp.Close()
|
|
|
|
cmd := exec.Command(editor, path)
|
|
cmd.Stdin, cmd.Stdout, cmd.Stderr = os.Stdin, os.Stdout, os.Stderr
|
|
if err := cmd.Run(); err != nil {
|
|
return err
|
|
}
|
|
b, err := os.ReadFile(path)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
if err := WriteConfig(parseEnv(string(b))); err != nil {
|
|
return err
|
|
}
|
|
fmt.Println(dim("[info] loki restart pour appliquer"))
|
|
return nil
|
|
}
|
|
|
|
// showVram parses `nvidia-smi --query-gpu=...` and renders a colored bar.
|
|
// nvidia-smi is available on both Linux and Windows when an NVIDIA driver is
|
|
// installed, so this is platform-neutral.
|
|
func showVram() error {
|
|
out, err := hideCmd(exec.Command("nvidia-smi",
|
|
"--query-gpu=name,memory.used,memory.total,utilization.gpu,temperature.gpu",
|
|
"--format=csv,noheader,nounits")).Output()
|
|
if err != nil {
|
|
return fmt.Errorf("nvidia-smi indisponible: %w", err)
|
|
}
|
|
for _, line := range strings.Split(strings.TrimSpace(string(out)), "\n") {
|
|
parts := strings.Split(line, ",")
|
|
if len(parts) != 5 {
|
|
continue
|
|
}
|
|
for i := range parts {
|
|
parts[i] = strings.TrimSpace(parts[i])
|
|
}
|
|
name := parts[0]
|
|
used, _ := strconv.Atoi(parts[1])
|
|
total, _ := strconv.Atoi(parts[2])
|
|
util, _ := strconv.Atoi(parts[3])
|
|
temp, _ := strconv.Atoi(parts[4])
|
|
pct := 0
|
|
if total > 0 {
|
|
pct = used * 100 / total
|
|
}
|
|
full := pct / 5
|
|
bar := strings.Repeat("█", full) + strings.Repeat("░", 20-full)
|
|
fmt.Printf("\n %s\n", cyan(name))
|
|
fmt.Printf(" VRAM %s %3d%% %.1f / %.1f GiB\n", green(bar), pct, float64(used)/1024, float64(total)/1024)
|
|
fmt.Printf(" GPU %3d%% Temp %d°C\n\n", util, temp)
|
|
}
|
|
return nil
|
|
}
|