mirror of
https://github.com/R0m1k3/Loki.git
synced 2026-10-12 01:37:06 +02:00
- module github.com/R0m1k3/Loki, cmd/loki, internal/loki (package loki) - LOKI_HOME, LOKI_MODEL_DIRS, LOKI_SERVICE, LOKI_DL_CONNS ; /etc/loki ; units loki-engine / loki-ui ; binaire et aide CLI - updateRepo pointe sur R0m1k3/Loki (l'auto-update ne tirera plus les binaires AJEAN amont) Conservé à l'identique : le domaine ajean.link (service de tunnel amont), les littéraux de migration 0.7.x (migrate_07.go), RELEASE_NOTES.md et LICENSE (historique et licence de l'amont). go build/vet/test : verts.
159 lines
4.4 KiB
Go
159 lines
4.4 KiB
Go
package loki
|
|
|
|
import (
|
|
"bufio"
|
|
"fmt"
|
|
"os"
|
|
"os/exec"
|
|
"strconv"
|
|
"strings"
|
|
)
|
|
|
|
// backend_gpu.go — sélection du/des GPU utilisés par llama-server.
|
|
//
|
|
// loki gpu liste les GPU et montre la sélection courante
|
|
// loki gpu 1 n'utilise que le GPU d'index 1
|
|
// loki gpu 0 1 utilise les GPU 0 et 1
|
|
// loki 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 « loki 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 loki gpu <index…> pour choisir, loki 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 'loki 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
|
|
}
|