mirror of
https://github.com/R0m1k3/Loki.git
synced 2026-10-15 11:19:07 +02:00
Moteur : files de lancement CUDA élargies (4x) sur deux GPU quand le pipeline entre cartes est possible
Sur plusieurs cartes, llama.cpp fait travailler les GPU en pipeline quand le modèle y tient en entier ; encore faut-il que le CPU empile assez de lancements CUDA d'avance. CUDA_SCALE_LAUNCH_QUEUES=4x agrandit cette file du pilote : mêmes noyaux, même ordre, sortie identique — seul le prompt peut gagner (+10-25 % en amont sur un 70B, non mesuré ici), le décodage ne bouge pas. llama.cpp l'a retiré de ses défauts après des blocages sur Jetson : Loki ne le pose donc que là où le pipeline peut réellement exister. - launchQueuesEnv (pure) : 4x seulement si ≥ 2 GPU CUDA servis (--device, sinon CUDA_VISIBLE_DEVICES, sinon nvidia-smi borné à 3 s) et aucun bloqueur : -ot, --cpu-moe, --n-cpu-moe ≠ 0, -nkvo, -sm autre que layer, NGL chiffré hors 999 — drapeaux d'EXTRA_ARGS ou LLAMA_ARG_* équivalents - une variable déjà dans l'environnement n'est jamais touchée ; CUDA_LAUNCH_QUEUES=off la coupe, 0.25x/0.5x/2x/4x l'imposent, toute autre valeur est ignorée en le disant - la valeur posée s'affiche sur la ligne « [loki serve] » ; la ligne de commande ne change pas, BATCH/UBATCH non plus - CUDA_LAUNCH_QUEUES survit aux bascules de preset (réglage machine qu'un preset peut imposer) - éditeur : « Experts MoE sur CPU » rappelle que ça coupe le pipeline sur 2 GPU Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
1 parent
0a02604968
commit
f7f1a30d08
7 files changed
+338
-7
No files matched your search
@@ -189,7 +189,10 @@ func safePresetPath(name string) (string, error) {
|
||||
// version effaçait la clé, et le moteur repartait sur le défaut « toutes les
|
||||
// interfaces » : une machine volontairement fermée se rouvrait toute seule au
|
||||
// premier changement de preset.
|
||||
var preservedKeys = []string{"MEM_MODE", "CRAWL4AI_URL", "WEB_ENGINE", "CUDA_VISIBLE_DEVICES", "HOST"}
|
||||
// CUDA_LAUNCH_QUEUES (files de lancement CUDA, voir launchQueuesEnv) suit la
|
||||
// même logique : un « off » posé parce que CETTE machine bloque ne doit pas
|
||||
// disparaître à la bascule suivante.
|
||||
var preservedKeys = []string{"MEM_MODE", "CRAWL4AI_URL", "WEB_ENGINE", "CUDA_VISIBLE_DEVICES", "HOST", "CUDA_LAUNCH_QUEUES"}
|
||||
|
||||
// softPreservedKeys : préservées SEULEMENT si le preset d'arrivée ne les définit
|
||||
// pas lui-même. CUDA_VISIBLE_DEVICES est dans ce cas : c'est d'ordinaire un
|
||||
@@ -199,7 +202,8 @@ var preservedKeys = []string{"MEM_MODE", "CRAWL4AI_URL", "WEB_ENGINE", "CUDA_VIS
|
||||
// inconditionnellement écrasait cette valeur par l'ancienne (mono-GPU) : le
|
||||
// modèle se retrouvait entièrement sur une seule carte et mourait sur
|
||||
// « cudaMalloc failed: out of memory ». Le preset explicite gagne.
|
||||
var softPreservedKeys = map[string]bool{"CUDA_VISIBLE_DEVICES": true, "HOST": true}
|
||||
// CUDA_LAUNCH_QUEUES aussi : un preset peut l'imposer pour son modèle.
|
||||
var softPreservedKeys = map[string]bool{"CUDA_VISIBLE_DEVICES": true, "HOST": true, "CUDA_LAUNCH_QUEUES": true}
|
||||
|
||||
// applyPresetFile installe le preset comme configuration active, en réinjectant
|
||||
// les réglages « appareil » par-dessus. Séparé de SwitchToPreset pour être
|
||||
|
||||
@@ -47,7 +47,7 @@ func TestSwitchToPresetGardeLesReglagesMachine(t *testing.T) {
|
||||
}
|
||||
}
|
||||
// Configuration courante : un preset + les réglages machine.
|
||||
setConfig(t, "MODEL=ancien.gguf\nCTX=4096\nCUDA_VISIBLE_DEVICES=1\nWEB_ENGINE=go\nMEM_MODE=always\n")
|
||||
setConfig(t, "MODEL=ancien.gguf\nCTX=4096\nCUDA_VISIBLE_DEVICES=1\nWEB_ENGINE=go\nMEM_MODE=always\nCUDA_LAUNCH_QUEUES=off\n")
|
||||
target := filepath.Join(home, "cible.env")
|
||||
write(target, "MODEL=nouveau.gguf\nCTX=8192\n")
|
||||
|
||||
@@ -60,7 +60,7 @@ func TestSwitchToPresetGardeLesReglagesMachine(t *testing.T) {
|
||||
t.Fatalf("le preset n'a pas été appliqué : %v", cfg)
|
||||
}
|
||||
for k, want := range map[string]string{
|
||||
"CUDA_VISIBLE_DEVICES": "1", "WEB_ENGINE": "go", "MEM_MODE": "always",
|
||||
"CUDA_VISIBLE_DEVICES": "1", "WEB_ENGINE": "go", "MEM_MODE": "always", "CUDA_LAUNCH_QUEUES": "off",
|
||||
} {
|
||||
if cfg[k] != want {
|
||||
t.Errorf("%s = %q après bascule, attendu %q (réglage machine effacé)", k, cfg[k], want)
|
||||
|
||||
@@ -336,7 +336,12 @@ func cmdServe(args []string) error {
|
||||
si.CPU = cpusetThreads(os.DirFS("/"))
|
||||
}
|
||||
|
||||
llmArgs, env, notes := buildServeArgs(cfg, splitArgs(cfg["EXTRA_ARGS"]), bin, si)
|
||||
extra := splitArgs(cfg["EXTRA_ARGS"])
|
||||
// Deux GPU ou plus : de quoi décider des files de lancement CUDA (voir
|
||||
// launchQueuesEnv). Après la sélection GPU, qui fait partie de la réponse.
|
||||
probeServeGPUs(cfg, extra, &si)
|
||||
|
||||
llmArgs, env, notes := buildServeArgs(cfg, extra, bin, si)
|
||||
applyServeEnv(env)
|
||||
for _, n := range notes {
|
||||
fmt.Fprintln(os.Stderr, "[loki serve] "+n)
|
||||
@@ -373,6 +378,10 @@ type serveSysInfo struct {
|
||||
MMProj string // chemin du projecteur vision, résolu et vérifié ; vide = pas de vision
|
||||
APIKey string // clé effective (.api_key, sinon config.env) ; vide = serveur ouvert
|
||||
CPU cpuBudget // sonde de conteneur (Linux) ; zéro = llama.cpp choisit ses threads seul
|
||||
|
||||
LaunchQueues string // CUDA_SCALE_LAUNCH_QUEUES déjà dans l'environnement : choix de l'utilisateur, intouché
|
||||
GPUs int // GPU visibles (CUDA_VISIBLE_DEVICES, sinon nvidia-smi) ; 0 = inconnu ou non sondé
|
||||
ArgEnv map[string]string // LLAMA_ARG_* de l'environnement qui décident du pipeline (voir serveArgEnv)
|
||||
}
|
||||
|
||||
// cudaDeviceEnv : sélection GPU (loki gpu), on filtre les devices visibles par
|
||||
@@ -436,6 +445,14 @@ func buildServeArgs(cfg map[string]string, extra []string, bin string, si serveS
|
||||
|
||||
// Threads : vide ou 0 = AUCUN drapeau, pour que llama.cpp prenne ses cœurs
|
||||
// physiques au lieu de tous les threads logiques (voir threadArgs).
|
||||
// Files de lancement CUDA : une variable d'environnement, pas un drapeau —
|
||||
// la ligne de commande n'en dépend pas (voir launchQueuesEnv).
|
||||
q, qNotes := launchQueuesEnv(cfg, extra, si)
|
||||
if q != "" {
|
||||
env["CUDA_SCALE_LAUNCH_QUEUES"] = q
|
||||
}
|
||||
notes = append(notes, qNotes...)
|
||||
|
||||
threads, threadNotes := threadArgs(cfg["THREADS"], cfg["THREADS_BATCH"], extra, si.CPU)
|
||||
notes = append(notes, threadNotes...)
|
||||
args = []string{bin,
|
||||
|
||||
@@ -0,0 +1,201 @@
|
||||
package loki
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"os"
|
||||
"os/exec"
|
||||
"strings"
|
||||
"time"
|
||||
)
|
||||
|
||||
// Files de lancement CUDA (CUDA_SCALE_LAUNCH_QUEUES) sur deux GPU ou plus.
|
||||
//
|
||||
// Quand llama.cpp répartit les couches entre plusieurs cartes et que le modèle
|
||||
// y tient en entier, il fait travailler les cartes en pipeline : pendant que la
|
||||
// seconde calcule un micro-lot, la première attaque le suivant. Encore faut-il
|
||||
// que le CPU puisse empiler assez de lancements CUDA d'avance ; avec la file par
|
||||
// défaut du pilote, il bloque et le pipeline se vide. « 4x » quadruple cette
|
||||
// file (PR llama.cpp #19042) : c'est une affaire de pilote, pas de calcul — mêmes
|
||||
// noyaux, mêmes entrées, même ordre, donc une sortie identique au jeton près. Le
|
||||
// prix : ~120 Mo de RAM système par GPU.
|
||||
//
|
||||
// llama.cpp l'a posé par défaut puis retiré (#19227) après des blocages sur
|
||||
// Jetson (mémoire unifiée, MoE décodé en partie sur CPU) ; ses docs le
|
||||
// recommandent désormais à la main en multi-GPU. Loki le pose donc lui-même,
|
||||
// mais seulement là où le pipeline peut réellement exister (voir
|
||||
// pipelineBlocker) : ailleurs le gain est nul et l'on ne s'exposerait qu'au
|
||||
// seul cas de panne jamais rapporté.
|
||||
//
|
||||
// Gain non mesuré sur la machine de référence (5060 Ti + 3060, 27B) : en amont,
|
||||
// +10 à 25 % de prompt seulement sur un 70B réparti sur deux grosses cartes,
|
||||
// rien de mesurable sur un 8B. Le décodage ne bouge pas. Pour vérifier qu'il
|
||||
// s'applique, le journal du moteur doit dire « pipeline parallelism enabled ».
|
||||
//
|
||||
// Choix de l'utilisateur, par ordre de priorité :
|
||||
//
|
||||
// CUDA_SCALE_LAUNCH_QUEUES déjà dans l'environnement → intouché (conteneur Unraid…)
|
||||
// CUDA_LAUNCH_QUEUES=off → rien
|
||||
// CUDA_LAUNCH_QUEUES=0.25x|0.5x|2x|4x → cette valeur, sans condition
|
||||
// vide ou auto → 4x si ≥ 2 GPU et pipeline possible
|
||||
//
|
||||
// Une autre valeur serait lue comme 1x par le pilote, sans un mot : on l'ignore
|
||||
// en le disant.
|
||||
|
||||
const launchQueuesAuto = "4x"
|
||||
|
||||
// launchQueueValues : les seules valeurs que le pilote CUDA comprend.
|
||||
var launchQueueValues = map[string]bool{"0.25x": true, "0.5x": true, "2x": true, "4x": true}
|
||||
|
||||
// serveArgEnv : les variables LLAMA_ARG_* qui font, sans drapeau, ce que lit
|
||||
// pipelineBlocker. llama.cpp les applique quand le drapeau manque — les ignorer
|
||||
// ferait conclure à un pipeline que le moteur n'ouvrira pas.
|
||||
var serveArgEnv = []string{"LLAMA_ARG_DEVICE", "LLAMA_ARG_SPLIT_MODE", "LLAMA_ARG_N_GPU_LAYERS",
|
||||
"LLAMA_ARG_CPU_MOE", "LLAMA_ARG_N_CPU_MOE", "LLAMA_ARG_NO_KV_OFFLOAD"}
|
||||
|
||||
// launchQueuesEnv décide de CUDA_SCALE_LAUNCH_QUEUES. Fonction pure : le nombre
|
||||
// de GPU et l'environnement arrivent déjà sondés dans si. Valeur vide = ne rien
|
||||
// poser.
|
||||
func launchQueuesEnv(cfg map[string]string, extra []string, si serveSysInfo) (val string, notes []string) {
|
||||
if si.LaunchQueues != "" {
|
||||
return "", nil
|
||||
}
|
||||
want := strings.ToLower(strings.TrimSpace(cfg["CUDA_LAUNCH_QUEUES"]))
|
||||
switch {
|
||||
case want == "off":
|
||||
return "", nil
|
||||
case launchQueueValues[want]:
|
||||
return want, []string{"CUDA_SCALE_LAUNCH_QUEUES=" + want + " (CUDA_LAUNCH_QUEUES)"}
|
||||
case want != "" && want != "auto":
|
||||
notes = append(notes, "CUDA_LAUNCH_QUEUES="+cfg["CUDA_LAUNCH_QUEUES"]+
|
||||
" ignoré (0.25x, 0.5x, 2x, 4x ou off) → automatique")
|
||||
}
|
||||
n := servedGPUCount(extra, si)
|
||||
if n < 2 || pipelineBlocker(cfg, extra, si.ArgEnv) != "" {
|
||||
return "", notes
|
||||
}
|
||||
return launchQueuesAuto, append(notes, fmt.Sprintf("CUDA_SCALE_LAUNCH_QUEUES=%s (%d GPU, parallélisme pipeline "+
|
||||
"possible) — CUDA_LAUNCH_QUEUES=off pour s'en passer", launchQueuesAuto, n))
|
||||
}
|
||||
|
||||
// servedGPUCount : combien de GPU CUDA llama-server utilisera. Le --device du
|
||||
// preset d'abord (c'est là que l'éditeur range le choix des cartes), puis ce
|
||||
// que cmdServe a compté (CUDA_VISIBLE_DEVICES, sinon nvidia-smi). Un --device
|
||||
// qui ne nomme que des cartes Vulkan ou Metal compte zéro : la variable ne
|
||||
// concerne que CUDA.
|
||||
func servedGPUCount(extra []string, si serveSysInfo) int {
|
||||
dev := flagValue(extra, "-dev", "--device")
|
||||
if dev == "" {
|
||||
dev = si.ArgEnv["LLAMA_ARG_DEVICE"]
|
||||
}
|
||||
if dev == "" {
|
||||
return si.GPUs
|
||||
}
|
||||
n := 0
|
||||
for _, d := range strings.Split(dev, ",") {
|
||||
if strings.HasPrefix(strings.ToUpper(strings.TrimSpace(d)), "CUDA") {
|
||||
n++
|
||||
}
|
||||
}
|
||||
return n
|
||||
}
|
||||
|
||||
// pipelineBlocker dit pourquoi llama.cpp n'ouvrira PAS le pipeline entre
|
||||
// cartes (llama-context.cpp : toutes les couches sur GPU, découpe par couches,
|
||||
// cache KV sur GPU, aucune surcharge de tenseurs). Vide = il peut l'ouvrir.
|
||||
// --n-cpu-moe et --cpu-moe sont des surcharges de tenseurs déguisées : le
|
||||
// preset MoE à experts sur CPU n'a donc jamais de pipeline.
|
||||
func pipelineBlocker(cfg map[string]string, extra []string, argEnv map[string]string) string {
|
||||
if hasAnyFlag(extra, "-ot", "--override-tensor", "--cpu-moe", "-cmoe") || envOn(argEnv["LLAMA_ARG_CPU_MOE"]) {
|
||||
return "surcharge de tenseurs"
|
||||
}
|
||||
moe := flagValue(extra, "--n-cpu-moe", "-ncmoe")
|
||||
if moe == "" {
|
||||
moe = argEnv["LLAMA_ARG_N_CPU_MOE"]
|
||||
}
|
||||
if moe != "" && moe != "0" {
|
||||
return "experts MoE sur CPU"
|
||||
}
|
||||
if hasAnyFlag(extra, "-nkvo", "--no-kv-offload") || envOn(argEnv["LLAMA_ARG_NO_KV_OFFLOAD"]) {
|
||||
return "cache KV sur CPU"
|
||||
}
|
||||
sm := flagValue(extra, "-sm", "--split-mode")
|
||||
if sm == "" {
|
||||
sm = argEnv["LLAMA_ARG_SPLIT_MODE"]
|
||||
}
|
||||
if sm != "" && sm != "layer" {
|
||||
return "découpe " + sm
|
||||
}
|
||||
// Couches GPU : celles d'EXTRA_ARGS, sinon NGL (que buildServeArgs traduit),
|
||||
// sinon la variable du moteur. Un nombre choisi — hors la sentinelle 999 —
|
||||
// laisse en général des couches au CPU ; auto, all et 999 visent le tout-GPU.
|
||||
ngl := flagValue(extra, "-ngl", "--n-gpu-layers", "--gpu-layers")
|
||||
if ngl == "" {
|
||||
ngl = strings.TrimSpace(cfg["NGL"])
|
||||
}
|
||||
if ngl == "" || ngl == "auto" {
|
||||
ngl = argEnv["LLAMA_ARG_N_GPU_LAYERS"]
|
||||
}
|
||||
if ngl != "" && ngl != "auto" && ngl != "all" && ngl != "999" {
|
||||
return "couches GPU limitées à " + ngl
|
||||
}
|
||||
return ""
|
||||
}
|
||||
|
||||
// envOn lit une variable booléenne comme llama.cpp : vide, 0 ou false = non.
|
||||
func envOn(v string) bool {
|
||||
v = strings.ToLower(strings.TrimSpace(v))
|
||||
return v != "" && v != "0" && v != "false"
|
||||
}
|
||||
|
||||
// probeServeGPUs remplit ce dont launchQueuesEnv a besoin. On ne sonde
|
||||
// nvidia-smi que si la réponse peut servir : variable déjà posée, « off », un
|
||||
// --device explicite ou CUDA_VISIBLE_DEVICES suffisent à trancher sans lui.
|
||||
func probeServeGPUs(cfg map[string]string, extra []string, si *serveSysInfo) {
|
||||
si.LaunchQueues = os.Getenv("CUDA_SCALE_LAUNCH_QUEUES")
|
||||
si.ArgEnv = map[string]string{}
|
||||
for _, k := range serveArgEnv {
|
||||
if v := os.Getenv(k); v != "" {
|
||||
si.ArgEnv[k] = v
|
||||
}
|
||||
}
|
||||
if si.LaunchQueues != "" || strings.EqualFold(strings.TrimSpace(cfg["CUDA_LAUNCH_QUEUES"]), "off") {
|
||||
return
|
||||
}
|
||||
if hasAnyFlag(extra, "-dev", "--device") || si.ArgEnv["LLAMA_ARG_DEVICE"] != "" {
|
||||
return
|
||||
}
|
||||
// cmdServe a déjà posé la sélection de config.env dans l'environnement :
|
||||
// os.Getenv voit donc le preset d'abord, le conteneur ensuite.
|
||||
if cvd := strings.TrimSpace(os.Getenv("CUDA_VISIBLE_DEVICES")); cvd != "" {
|
||||
for _, d := range strings.Split(cvd, ",") {
|
||||
if strings.TrimSpace(d) != "" {
|
||||
si.GPUs++
|
||||
}
|
||||
}
|
||||
return
|
||||
}
|
||||
si.GPUs = nvidiaGPUCount(3 * time.Second)
|
||||
}
|
||||
|
||||
// nvidiaGPUCount compte les cartes NVIDIA. Borné dans le temps : un pilote
|
||||
// coincé peut faire pendre nvidia-smi, et le démarrage du moteur ne doit pas
|
||||
// en dépendre. Au moindre doute, 0 — on ne pose rien.
|
||||
func nvidiaGPUCount(timeout time.Duration) int {
|
||||
if !hasTool("nvidia-smi") {
|
||||
return 0
|
||||
}
|
||||
ctx, cancel := context.WithTimeout(context.Background(), timeout)
|
||||
defer cancel()
|
||||
out, err := hideCmd(exec.CommandContext(ctx, "nvidia-smi", "--query-gpu=index", "--format=csv,noheader")).Output()
|
||||
if err != nil {
|
||||
return 0
|
||||
}
|
||||
n := 0
|
||||
for _, line := range strings.Split(string(out), "\n") {
|
||||
if strings.TrimSpace(line) != "" {
|
||||
n++
|
||||
}
|
||||
}
|
||||
return n
|
||||
}
|
||||
@@ -0,0 +1,109 @@
|
||||
package loki
|
||||
|
||||
import (
|
||||
"reflect"
|
||||
"testing"
|
||||
)
|
||||
|
||||
// TestLaunchQueuesEnv : CUDA_SCALE_LAUNCH_QUEUES n'est posé que là où le
|
||||
// pipeline entre cartes peut exister, et jamais par-dessus un choix de
|
||||
// l'utilisateur.
|
||||
func TestLaunchQueuesEnv(t *testing.T) {
|
||||
two := serveSysInfo{GPUs: 2}
|
||||
cases := []struct {
|
||||
name string
|
||||
cfg map[string]string
|
||||
si serveSysInfo
|
||||
want string
|
||||
wantNotes int
|
||||
}{
|
||||
{name: "2 GPU, dense tout sur GPU : 4x, et on le dit",
|
||||
cfg: map[string]string{"NGL": "999"}, si: two, want: "4x", wantNotes: 1},
|
||||
{name: "NGL=all ou auto : pipeline possible",
|
||||
cfg: map[string]string{"NGL": "all"}, si: two, want: "4x", wantNotes: 1},
|
||||
{name: "1 GPU : rien",
|
||||
cfg: map[string]string{"NGL": "999"}, si: serveSysInfo{GPUs: 1}},
|
||||
{name: "nombre de GPU inconnu : rien",
|
||||
cfg: map[string]string{}},
|
||||
{name: "variable déjà posée par l'utilisateur : intouchée",
|
||||
cfg: map[string]string{"CUDA_LAUNCH_QUEUES": "2x"}, si: serveSysInfo{GPUs: 2, LaunchQueues: "1x"}},
|
||||
{name: "CUDA_LAUNCH_QUEUES=off : rien",
|
||||
cfg: map[string]string{"CUDA_LAUNCH_QUEUES": "off"}, si: two},
|
||||
{name: "valeur explicite valide : posée telle quelle, même sur 1 GPU",
|
||||
cfg: map[string]string{"CUDA_LAUNCH_QUEUES": "2X"}, si: serveSysInfo{GPUs: 1}, want: "2x", wantNotes: 1},
|
||||
{name: "valeur invalide : ignorée, dite, puis automatique",
|
||||
cfg: map[string]string{"CUDA_LAUNCH_QUEUES": "8x"}, si: two, want: "4x", wantNotes: 2},
|
||||
{name: "valeur invalide sur 1 GPU : seulement la note",
|
||||
cfg: map[string]string{"CUDA_LAUNCH_QUEUES": "beaucoup"}, si: serveSysInfo{GPUs: 1}, wantNotes: 1},
|
||||
{name: "preset MoE (-ot, --n-cpu-moe) sur 2 GPU : rien",
|
||||
cfg: map[string]string{"EXTRA_ARGS": `-ot "per_layer_token_embd=CPU" --n-cpu-moe 40`}, si: two},
|
||||
{name: "--n-cpu-moe seul : rien",
|
||||
cfg: map[string]string{"EXTRA_ARGS": "--n-cpu-moe=12"}, si: two},
|
||||
{name: "--n-cpu-moe 0 : aucun expert sur CPU, pipeline possible",
|
||||
cfg: map[string]string{"EXTRA_ARGS": "--n-cpu-moe 0"}, si: two, want: "4x", wantNotes: 1},
|
||||
{name: "--cpu-moe : rien",
|
||||
cfg: map[string]string{"EXTRA_ARGS": "--cpu-moe"}, si: two},
|
||||
{name: "-sm row : rien",
|
||||
cfg: map[string]string{"EXTRA_ARGS": "-sm row"}, si: two},
|
||||
{name: "--split-mode=layer explicite : 4x",
|
||||
cfg: map[string]string{"EXTRA_ARGS": "--split-mode=layer"}, si: two, want: "4x", wantNotes: 1},
|
||||
{name: "NGL=28 : des couches restent au CPU, rien",
|
||||
cfg: map[string]string{"NGL": "28"}, si: two},
|
||||
{name: "-ngl 40 dans EXTRA_ARGS l'emporte sur NGL=999 : rien",
|
||||
cfg: map[string]string{"NGL": "999", "EXTRA_ARGS": "-ngl 40"}, si: two},
|
||||
{name: "cache KV sur CPU : rien",
|
||||
cfg: map[string]string{"EXTRA_ARGS": "-nkvo"}, si: two},
|
||||
{name: "--device d'une seule carte : rien",
|
||||
cfg: map[string]string{"EXTRA_ARGS": "--device CUDA0"}, si: two},
|
||||
{name: "--device de deux cartes CUDA, nvidia-smi non sondé : 4x",
|
||||
cfg: map[string]string{"EXTRA_ARGS": "--device CUDA1,CUDA0 --tensor-split 0.6,0.4"}, want: "4x", wantNotes: 1},
|
||||
{name: "--device Vulkan : la variable ne concerne pas ce moteur",
|
||||
cfg: map[string]string{"EXTRA_ARGS": "-dev Vulkan0,Vulkan1"}, si: two},
|
||||
{name: "--device none : rien",
|
||||
cfg: map[string]string{"EXTRA_ARGS": "-dev none"}, si: two},
|
||||
{name: "LLAMA_ARG_N_CPU_MOE dans l'environnement du moteur : rien",
|
||||
cfg: map[string]string{}, si: serveSysInfo{GPUs: 2, ArgEnv: map[string]string{"LLAMA_ARG_N_CPU_MOE": "30"}}},
|
||||
{name: "LLAMA_ARG_SPLIT_MODE=none : rien",
|
||||
cfg: map[string]string{}, si: serveSysInfo{GPUs: 2, ArgEnv: map[string]string{"LLAMA_ARG_SPLIT_MODE": "none"}}},
|
||||
{name: "NGL=auto et LLAMA_ARG_N_GPU_LAYERS=20 : rien",
|
||||
cfg: map[string]string{"NGL": "auto"}, si: serveSysInfo{GPUs: 2, ArgEnv: map[string]string{"LLAMA_ARG_N_GPU_LAYERS": "20"}}},
|
||||
{name: "LLAMA_ARG_CPU_MOE=0 : désactivé, pipeline possible",
|
||||
cfg: map[string]string{}, si: serveSysInfo{GPUs: 2, ArgEnv: map[string]string{"LLAMA_ARG_CPU_MOE": "0"}}, want: "4x", wantNotes: 1},
|
||||
{name: "LLAMA_ARG_DEVICE d'une seule carte : rien",
|
||||
cfg: map[string]string{}, si: serveSysInfo{GPUs: 2, ArgEnv: map[string]string{"LLAMA_ARG_DEVICE": "CUDA1"}}},
|
||||
}
|
||||
for _, c := range cases {
|
||||
t.Run(c.name, func(t *testing.T) {
|
||||
got, notes := launchQueuesEnv(c.cfg, splitArgs(c.cfg["EXTRA_ARGS"]), c.si)
|
||||
if got != c.want {
|
||||
t.Fatalf("valeur = %q, attendu %q", got, c.want)
|
||||
}
|
||||
if len(notes) != c.wantNotes {
|
||||
t.Fatalf("notes = %q, en attendait %d", notes, c.wantNotes)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
// La variable passe par l'environnement : la ligne de commande ne bouge pas
|
||||
// d'un octet, et la sélection GPU l'accompagne.
|
||||
func TestBuildServeArgsLaunchQueues(t *testing.T) {
|
||||
const bin, model = "/opt/llama/llama-server", "/models/Qwen3.6-27B-Q4_K_M.gguf"
|
||||
cfg := map[string]string{"NGL": "999", "CUDA_VISIBLE_DEVICES": "1,0"}
|
||||
si := serveSysInfo{Help: helpRecent, Model: model}
|
||||
without, _, _ := buildServeArgs(cfg, nil, bin, si)
|
||||
si.GPUs = 2
|
||||
args, env, notes := buildServeArgs(cfg, nil, bin, si)
|
||||
if !reflect.DeepEqual(args, without) {
|
||||
t.Fatalf("la ligne de commande a changé\n got %q\nwant %q", args, without)
|
||||
}
|
||||
want := map[string]string{"CUDA_VISIBLE_DEVICES": "1,0", "CUDA_DEVICE_ORDER": "PCI_BUS_ID",
|
||||
"CUDA_SCALE_LAUNCH_QUEUES": "4x"}
|
||||
if !reflect.DeepEqual(env, want) {
|
||||
t.Fatalf("env = %v, want %v", env, want)
|
||||
}
|
||||
// Une note pour 999 → auto, une pour les files de lancement.
|
||||
if len(notes) != 2 {
|
||||
t.Fatalf("notes = %q", notes)
|
||||
}
|
||||
}
|
||||
@@ -3437,7 +3437,7 @@ html[data-files="1"] #files-btn{color:var(--accent)}
|
||||
<span class="pe-row-c"><input id="s-ngl" class="pe-val" type="text" inputmode="numeric" placeholder="999" oninput="cfgWriteKey('NGL', this.value)"></span>
|
||||
</div>
|
||||
<div class="pe-row">
|
||||
<span class="pe-row-l">Experts MoE sur CPU<span class="pe-sub">vide = non</span></span>
|
||||
<span class="pe-row-l">Experts MoE sur CPU<span class="pe-sub">vide = non · sur 2 GPU, coupe le pipeline entre cartes (prompt plus lent)</span></span>
|
||||
<span class="pe-row-c"><input id="s-moe" class="pe-val" type="number" min="0" step="1" placeholder="—" oninput="eaSetValued('--n-cpu-moe', this.value)"></span>
|
||||
</div>
|
||||
<div class="pe-row">
|
||||
|
||||
@@ -1214,7 +1214,7 @@ document.documentElement.setAttribute('data-side',localStorage.getItem('loki-sid
|
||||
<span class="pe-row-c"><input id="s-ngl" class="pe-val" type="text" inputmode="numeric" placeholder="999" oninput="cfgWriteKey('NGL', this.value)"></span>
|
||||
</div>
|
||||
<div class="pe-row">
|
||||
<span class="pe-row-l">Experts MoE sur CPU<span class="pe-sub">vide = non</span></span>
|
||||
<span class="pe-row-l">Experts MoE sur CPU<span class="pe-sub">vide = non · sur 2 GPU, coupe le pipeline entre cartes (prompt plus lent)</span></span>
|
||||
<span class="pe-row-c"><input id="s-moe" class="pe-val" type="number" min="0" step="1" placeholder="—" oninput="eaSetValued('--n-cpu-moe', this.value)"></span>
|
||||
</div>
|
||||
<div class="pe-row">
|
||||
|
||||
Reference in new issue
Block a user