mirror of
https://github.com/R0m1k3/Loki.git
synced 2026-10-11 17:26:57 +02:00
Surface visible passee en AJEAN : aide de la CLI, messages, invite du chat terminal, commentaires, README, metadonnees Windows du binaire (InternalName / OriginalFilename). Les reglages s'appellent desormais AJEAN_* (AJEAN_HOME, AJEAN_MODEL_DIRS, AJEAN_DL_CONNS, AJEAN_LINK_URL, AJEAN_ACME_EMAIL, AJEAN_SERVICE), avec l'ancien nom JEAN_* toujours lu en second via envAny(). Ces variables sont posees a la main dans des shells, des units, des docker-compose et des scripts de deploiement qu'on ne peut pas retrouver : cesser de les lire ferait retomber silencieusement un reglage sur sa valeur par defaut. Migration one-shot du dossier de donnees retentee dans la fenetre ou l'installateur Windows a arrete toutes les instances, seule fenetre ou le rename peut aboutir. Le chemin du binaire est recalcule juste apres, sinon on reinstallerait dans le dossier qu'on vient de deplacer. Deux choses volontairement NON renommees, parce que les renommer casserait des utilisateurs sans rien apporter : - "model": "jean", le nom expose sur l'endpoint compatible OpenAI. Des integrations externes le configurent en dur. - les cles localStorage (jean-theme, jean.sys...). Les renommer reinitialiserait theme, preferences d'affichage et prompt systeme dans le navigateur de chaque utilisateur. Le plist launchd recevait /usr/local/bin/jean en dur : le chemin est desormais injecte, sinon le daemon macOS aurait refuse de demarrer apres le renommage. index.html regenere depuis ui/src via go generate.
159 lines
4.4 KiB
Go
159 lines
4.4 KiB
Go
package ajean
|
|
|
|
import (
|
|
"bufio"
|
|
"fmt"
|
|
"os"
|
|
"os/exec"
|
|
"strconv"
|
|
"strings"
|
|
)
|
|
|
|
// backend_gpu.go — sélection du/des GPU utilisés par llama-server.
|
|
//
|
|
// ajean gpu liste les GPU et montre la sélection courante
|
|
// ajean gpu 1 n'utilise que le GPU d'index 1
|
|
// ajean gpu 0 1 utilise les GPU 0 et 1
|
|
// ajean gpu all réinitialise (tous les GPU visibles)
|
|
//
|
|
// La sélection est stockée dans config.env sous CUDA_VISIBLE_DEVICES ; backend_serve.go
|
|
// l'exporte (avec CUDA_DEVICE_ORDER=PCI_BUS_ID pour que les index correspondent
|
|
// à ceux affichés par nvidia-smi).
|
|
|
|
type gpuInfo struct {
|
|
Index int
|
|
Name string
|
|
MemTotal string // en MiB
|
|
MemUsed string
|
|
Cap string // compute capability
|
|
}
|
|
|
|
func cmdGPU(args []string) error {
|
|
if len(args) == 0 || args[0] == "list" || args[0] == "ls" {
|
|
return gpuList()
|
|
}
|
|
switch args[0] {
|
|
case "all", "reset", "none", "auto":
|
|
return gpuSet("")
|
|
}
|
|
// Sinon : une liste d'index (« 1 », « 0 1 », « 0,1 »).
|
|
gpus, err := detectGPUs()
|
|
if err != nil {
|
|
return err
|
|
}
|
|
raw := strings.Join(args, ",")
|
|
var idx []string
|
|
for _, tok := range strings.Split(raw, ",") {
|
|
tok = strings.TrimSpace(tok)
|
|
if tok == "" {
|
|
continue
|
|
}
|
|
n, err := strconv.Atoi(tok)
|
|
if err != nil {
|
|
return fmt.Errorf("index GPU invalide: %q (attendu un nombre)", tok)
|
|
}
|
|
if n < 0 || n >= len(gpus) {
|
|
return fmt.Errorf("index GPU %d hors limites (0..%d) — voir « ajean gpu »", n, len(gpus)-1)
|
|
}
|
|
idx = append(idx, strconv.Itoa(n))
|
|
}
|
|
if len(idx) == 0 {
|
|
return fmt.Errorf("aucun index fourni")
|
|
}
|
|
return gpuSet(strings.Join(idx, ","))
|
|
}
|
|
|
|
// gpuList affiche les GPU détectés et marque ceux actuellement sélectionnés.
|
|
func gpuList() error {
|
|
gpus, err := detectGPUs()
|
|
if err != nil {
|
|
return err
|
|
}
|
|
sel := ReadConfig()["CUDA_VISIBLE_DEVICES"]
|
|
selected := map[int]bool{}
|
|
if sel != "" {
|
|
for _, t := range strings.Split(sel, ",") {
|
|
if n, err := strconv.Atoi(strings.TrimSpace(t)); err == nil {
|
|
selected[n] = true
|
|
}
|
|
}
|
|
}
|
|
|
|
fmt.Println()
|
|
for _, g := range gpus {
|
|
mark := " "
|
|
line := fmt.Sprintf("[%d] %s — %s/%s MiB (cc %s)", g.Index, g.Name, g.MemUsed, g.MemTotal, g.Cap)
|
|
active := sel == "" || selected[g.Index]
|
|
if sel != "" && selected[g.Index] {
|
|
mark = green("● ")
|
|
line = green(line)
|
|
} else if sel == "" {
|
|
mark = dim("○ ")
|
|
} else {
|
|
mark = dim("○ ")
|
|
line = dim(line)
|
|
}
|
|
_ = active
|
|
fmt.Printf(" %s%s\n", mark, line)
|
|
}
|
|
fmt.Println()
|
|
if sel == "" {
|
|
fmt.Printf(" Sélection : %s (tous les GPU)\n", bold("auto"))
|
|
} else {
|
|
fmt.Printf(" Sélection : %s (CUDA_VISIBLE_DEVICES=%s)\n", bold(sel), sel)
|
|
}
|
|
fmt.Printf(" %s ajean gpu <index…> pour choisir, ajean gpu all pour réinitialiser\n", dim("→"))
|
|
return nil
|
|
}
|
|
|
|
// gpuSet writes (or clears) CUDA_VISIBLE_DEVICES in config.env then offers a
|
|
// restart so the change takes effect.
|
|
func gpuSet(value string) error {
|
|
if err := SetConfigKey("CUDA_VISIBLE_DEVICES", value); err != nil {
|
|
return err
|
|
}
|
|
if value == "" {
|
|
fmt.Printf("%s sélection GPU réinitialisée — tous les GPU seront visibles\n", green("[ok]"))
|
|
} else {
|
|
fmt.Printf("%s GPU sélectionné(s) : %s\n", green("[ok]"), bold(value))
|
|
}
|
|
fmt.Print(dim("[info] redémarrer le service pour appliquer ? [Y/n] "))
|
|
sc := bufio.NewScanner(os.Stdin)
|
|
if sc.Scan() && strings.HasPrefix(strings.ToLower(strings.TrimSpace(sc.Text())), "n") {
|
|
fmt.Println(dim("[info] pense à lancer 'ajean restart'"))
|
|
return nil
|
|
}
|
|
return serviceAction("restart")
|
|
}
|
|
|
|
// detectGPUs queries nvidia-smi for the list of NVIDIA GPUs.
|
|
func detectGPUs() ([]gpuInfo, error) {
|
|
if !hasTool("nvidia-smi") {
|
|
return nil, fmt.Errorf("nvidia-smi introuvable — sélection GPU disponible uniquement sur NVIDIA")
|
|
}
|
|
out, err := hideCmd(exec.Command("nvidia-smi",
|
|
"--query-gpu=index,name,memory.total,memory.used,compute_cap",
|
|
"--format=csv,noheader,nounits")).Output()
|
|
if err != nil {
|
|
return nil, fmt.Errorf("nvidia-smi a échoué: %w", err)
|
|
}
|
|
var gpus []gpuInfo
|
|
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])
|
|
}
|
|
idx, _ := strconv.Atoi(parts[0])
|
|
gpus = append(gpus, gpuInfo{
|
|
Index: idx, Name: parts[1], MemTotal: parts[2], MemUsed: parts[3], Cap: parts[4],
|
|
})
|
|
}
|
|
if len(gpus) == 0 {
|
|
return nil, fmt.Errorf("aucun GPU NVIDIA détecté")
|
|
}
|
|
return gpus, nil
|
|
}
|