mirror of
https://github.com/R0m1k3/Loki.git
synced 2026-10-23 06:58:14 +02:00
Moteur : corrections de relecture des files de lancement CUDA — les variables LLAMA_ARG_* lues comme llama.cpp les lit
La garde « pipeline possible » ne suivait pas tout à fait la façon dont llama.cpp (common/arg.cpp) lit ses réglages. Faux positifs : 4x posé sans pipeline possible, donc exposé au seul cas de panne connu sans aucun gain. Faux négatifs : gain refusé à tort. - surcharges de tenseurs cumulées : variable et drapeaux s'additionnent. Un --n-cpu-moe 0 n'annule donc ni un LLAMA_ARG_N_CPU_MOE=30 ni un --n-cpu-moe 20 placé avant lui. - --n-cpu-ffn/-ncffn (FFN dense sur CPU) et LLAMA_ARG_OVERRIDE_TENSOR comptent comme des surcharges. - cache KV : le dernier -kvo/-nkvo l'emporte. Sinon, la seule présence de LLAMA_ARG_NO_KV_OFFLOAD (même à 0) vaut « non », puis LLAMA_ARG_KV_OFFLOAD est lu comme un booléen. - LLAMA_ARG_CPU_MOE n'est actif que pour on/enabled/true/1. - NGL absent : Loki passe -ngl lui-même, donc LLAMA_ARG_N_GPU_LAYERS ne compte plus. NGL=Auto se lit sans tenir compte de la casse, comme nglArgs. - buildServeArgs : le commentaire des threads retrouve son code. - 12 cas de table en plus. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
1 parent
f7f1a30d08
commit
2de20e0ab2
3 files changed
+119
-25
No files matched your search
@@ -443,8 +443,6 @@ func buildServeArgs(cfg map[string]string, extra []string, bin string, si serveS
|
|||||||
notes = append(notes, loadNote)
|
notes = append(notes, loadNote)
|
||||||
}
|
}
|
||||||
|
|
||||||
// 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 —
|
// Files de lancement CUDA : une variable d'environnement, pas un drapeau —
|
||||||
// la ligne de commande n'en dépend pas (voir launchQueuesEnv).
|
// la ligne de commande n'en dépend pas (voir launchQueuesEnv).
|
||||||
q, qNotes := launchQueuesEnv(cfg, extra, si)
|
q, qNotes := launchQueuesEnv(cfg, extra, si)
|
||||||
@@ -453,6 +451,8 @@ func buildServeArgs(cfg map[string]string, extra []string, bin string, si serveS
|
|||||||
}
|
}
|
||||||
notes = append(notes, qNotes...)
|
notes = append(notes, qNotes...)
|
||||||
|
|
||||||
|
// Threads : vide ou 0 = AUCUN drapeau, pour que llama.cpp prenne ses cœurs
|
||||||
|
// physiques au lieu de tous les threads logiques (voir threadArgs).
|
||||||
threads, threadNotes := threadArgs(cfg["THREADS"], cfg["THREADS_BATCH"], extra, si.CPU)
|
threads, threadNotes := threadArgs(cfg["THREADS"], cfg["THREADS_BATCH"], extra, si.CPU)
|
||||||
notes = append(notes, threadNotes...)
|
notes = append(notes, threadNotes...)
|
||||||
args = []string{bin,
|
args = []string{bin,
|
||||||
|
|||||||
@@ -51,7 +51,8 @@ var launchQueueValues = map[string]bool{"0.25x": true, "0.5x": true, "2x": true,
|
|||||||
// pipelineBlocker. llama.cpp les applique quand le drapeau manque — les ignorer
|
// pipelineBlocker. llama.cpp les applique quand le drapeau manque — les ignorer
|
||||||
// ferait conclure à un pipeline que le moteur n'ouvrira pas.
|
// 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",
|
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"}
|
"LLAMA_ARG_OVERRIDE_TENSOR", "LLAMA_ARG_CPU_MOE", "LLAMA_ARG_N_CPU_MOE", "LLAMA_ARG_N_CPU_FFN",
|
||||||
|
"LLAMA_ARG_KV_OFFLOAD", "LLAMA_ARG_NO_KV_OFFLOAD"}
|
||||||
|
|
||||||
// launchQueuesEnv décide de CUDA_SCALE_LAUNCH_QUEUES. Fonction pure : le nombre
|
// 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
|
// de GPU et l'environnement arrivent déjà sondés dans si. Valeur vide = ne rien
|
||||||
@@ -103,20 +104,31 @@ func servedGPUCount(extra []string, si serveSysInfo) int {
|
|||||||
// pipelineBlocker dit pourquoi llama.cpp n'ouvrira PAS le pipeline entre
|
// pipelineBlocker dit pourquoi llama.cpp n'ouvrira PAS le pipeline entre
|
||||||
// cartes (llama-context.cpp : toutes les couches sur GPU, découpe par couches,
|
// 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.
|
// 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
|
// --n-cpu-moe, --cpu-moe et --n-cpu-ffn sont des surcharges de tenseurs
|
||||||
// preset MoE à experts sur CPU n'a donc jamais de pipeline.
|
// déguisées : le preset MoE à experts sur CPU n'a donc jamais de pipeline.
|
||||||
|
//
|
||||||
|
// Attention à la façon dont llama.cpp lit ces réglages (common/arg.cpp) : les
|
||||||
|
// variables LLAMA_ARG_* passent d'abord, la ligne de commande ensuite. Pour un
|
||||||
|
// réglage à valeur unique (-sm, -ngl, -kvo/-nkvo), la ligne de commande
|
||||||
|
// l'emporte ; mais les surcharges de tenseurs S'ACCUMULENT — variable et
|
||||||
|
// drapeaux, chaque occurrence ajoute les siennes. Un --n-cpu-moe 0 n'annule
|
||||||
|
// donc pas un LLAMA_ARG_N_CPU_MOE=30.
|
||||||
func pipelineBlocker(cfg map[string]string, extra []string, argEnv map[string]string) string {
|
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"]) {
|
if hasAnyFlag(extra, "-ot", "--override-tensor", "--cpu-moe", "-cmoe") ||
|
||||||
|
argEnv["LLAMA_ARG_OVERRIDE_TENSOR"] != "" || envTruthy(argEnv["LLAMA_ARG_CPU_MOE"]) {
|
||||||
return "surcharge de tenseurs"
|
return "surcharge de tenseurs"
|
||||||
}
|
}
|
||||||
moe := flagValue(extra, "--n-cpu-moe", "-ncmoe")
|
for _, n := range []struct{ env, short, long string }{
|
||||||
if moe == "" {
|
{"LLAMA_ARG_N_CPU_MOE", "-ncmoe", "--n-cpu-moe"},
|
||||||
moe = argEnv["LLAMA_ARG_N_CPU_MOE"]
|
{"LLAMA_ARG_N_CPU_FFN", "-ncffn", "--n-cpu-ffn"},
|
||||||
|
} {
|
||||||
|
for _, v := range append(flagValues(extra, n.short, n.long), argEnv[n.env]) {
|
||||||
|
if v = strings.TrimSpace(v); v != "" && v != "0" {
|
||||||
|
return "couches " + n.long + " sur CPU"
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
if moe != "" && moe != "0" {
|
if !kvOffloaded(extra, argEnv) {
|
||||||
return "experts MoE sur CPU"
|
|
||||||
}
|
|
||||||
if hasAnyFlag(extra, "-nkvo", "--no-kv-offload") || envOn(argEnv["LLAMA_ARG_NO_KV_OFFLOAD"]) {
|
|
||||||
return "cache KV sur CPU"
|
return "cache KV sur CPU"
|
||||||
}
|
}
|
||||||
sm := flagValue(extra, "-sm", "--split-mode")
|
sm := flagValue(extra, "-sm", "--split-mode")
|
||||||
@@ -126,26 +138,79 @@ func pipelineBlocker(cfg map[string]string, extra []string, argEnv map[string]st
|
|||||||
if sm != "" && sm != "layer" {
|
if sm != "" && sm != "layer" {
|
||||||
return "découpe " + sm
|
return "découpe " + sm
|
||||||
}
|
}
|
||||||
// Couches GPU : celles d'EXTRA_ARGS, sinon NGL (que buildServeArgs traduit),
|
// Couches GPU : celles d'EXTRA_ARGS, sinon NGL (que buildServeArgs traduit
|
||||||
// sinon la variable du moteur. Un nombre choisi — hors la sentinelle 999 —
|
// toujours en -ngl, sauf NGL=auto qui ne pose rien — la variable du moteur
|
||||||
// laisse en général des couches au CPU ; auto, all et 999 visent le tout-GPU.
|
// décide alors). 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")
|
ngl := flagValue(extra, "-ngl", "--n-gpu-layers", "--gpu-layers")
|
||||||
if ngl == "" {
|
if ngl == "" {
|
||||||
ngl = strings.TrimSpace(cfg["NGL"])
|
ngl = strings.TrimSpace(cfg["NGL"])
|
||||||
|
if strings.EqualFold(ngl, "auto") {
|
||||||
|
ngl = argEnv["LLAMA_ARG_N_GPU_LAYERS"]
|
||||||
|
}
|
||||||
}
|
}
|
||||||
if ngl == "" || ngl == "auto" {
|
if ngl = strings.ToLower(strings.TrimSpace(ngl)); ngl != "" && ngl != "auto" && ngl != "all" && ngl != "999" {
|
||||||
ngl = argEnv["LLAMA_ARG_N_GPU_LAYERS"]
|
|
||||||
}
|
|
||||||
if ngl != "" && ngl != "auto" && ngl != "all" && ngl != "999" {
|
|
||||||
return "couches GPU limitées à " + ngl
|
return "couches GPU limitées à " + ngl
|
||||||
}
|
}
|
||||||
return ""
|
return ""
|
||||||
}
|
}
|
||||||
|
|
||||||
// envOn lit une variable booléenne comme llama.cpp : vide, 0 ou false = non.
|
// kvOffloaded : le cache KV reste-t-il sur GPU ? Le dernier -kvo/-nkvo de la
|
||||||
func envOn(v string) bool {
|
// ligne de commande tranche ; sinon LLAMA_ARG_NO_KV_OFFLOAD, dont la SEULE
|
||||||
v = strings.ToLower(strings.TrimSpace(v))
|
// présence vaut « non » pour llama.cpp (quelle que soit sa valeur), puis
|
||||||
return v != "" && v != "0" && v != "false"
|
// LLAMA_ARG_KV_OFFLOAD lu comme un booléen.
|
||||||
|
func kvOffloaded(extra []string, argEnv map[string]string) bool {
|
||||||
|
on, set := true, false
|
||||||
|
for _, a := range extra {
|
||||||
|
name, _, _ := strings.Cut(a, "=")
|
||||||
|
switch name {
|
||||||
|
case "-kvo", "--kv-offload":
|
||||||
|
on, set = true, true
|
||||||
|
case "-nkvo", "--no-kv-offload":
|
||||||
|
on, set = false, true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if set {
|
||||||
|
return on
|
||||||
|
}
|
||||||
|
if argEnv["LLAMA_ARG_NO_KV_OFFLOAD"] != "" {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
switch strings.ToLower(strings.TrimSpace(argEnv["LLAMA_ARG_KV_OFFLOAD"])) {
|
||||||
|
case "off", "disabled", "false", "0":
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
// envTruthy lit un drapeau sans valeur passé par variable, comme llama.cpp :
|
||||||
|
// seuls on, enabled, true et 1 l'activent.
|
||||||
|
func envTruthy(v string) bool {
|
||||||
|
switch strings.TrimSpace(v) {
|
||||||
|
case "on", "enabled", "true", "1":
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
// flagValues : les valeurs de TOUTES les occurrences d'un drapeau (« -x 1 » ou
|
||||||
|
// « -x=1 »), pour ceux que llama.cpp cumule au lieu de garder le dernier.
|
||||||
|
func flagValues(args []string, flags ...string) []string {
|
||||||
|
var vals []string
|
||||||
|
for i, a := range args {
|
||||||
|
name, val, hasEq := strings.Cut(a, "=")
|
||||||
|
for _, f := range flags {
|
||||||
|
if name != f {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
if hasEq {
|
||||||
|
vals = append(vals, val)
|
||||||
|
} else if i+1 < len(args) {
|
||||||
|
vals = append(vals, args[i+1])
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return vals
|
||||||
}
|
}
|
||||||
|
|
||||||
// probeServeGPUs remplit ce dont launchQueuesEnv a besoin. On ne sonde
|
// probeServeGPUs remplit ce dont launchQueuesEnv a besoin. On ne sonde
|
||||||
@@ -155,8 +220,10 @@ func probeServeGPUs(cfg map[string]string, extra []string, si *serveSysInfo) {
|
|||||||
si.LaunchQueues = os.Getenv("CUDA_SCALE_LAUNCH_QUEUES")
|
si.LaunchQueues = os.Getenv("CUDA_SCALE_LAUNCH_QUEUES")
|
||||||
si.ArgEnv = map[string]string{}
|
si.ArgEnv = map[string]string{}
|
||||||
for _, k := range serveArgEnv {
|
for _, k := range serveArgEnv {
|
||||||
if v := os.Getenv(k); v != "" {
|
if v, ok := os.LookupEnv(k); ok && v != "" {
|
||||||
si.ArgEnv[k] = v
|
si.ArgEnv[k] = v
|
||||||
|
} else if ok && k == "LLAMA_ARG_NO_KV_OFFLOAD" {
|
||||||
|
si.ArgEnv[k] = "1" // présente mais vide : llama.cpp n'y lit que la présence
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if si.LaunchQueues != "" || strings.EqualFold(strings.TrimSpace(cfg["CUDA_LAUNCH_QUEUES"]), "off") {
|
if si.LaunchQueues != "" || strings.EqualFold(strings.TrimSpace(cfg["CUDA_LAUNCH_QUEUES"]), "off") {
|
||||||
|
|||||||
@@ -71,6 +71,33 @@ func TestLaunchQueuesEnv(t *testing.T) {
|
|||||||
cfg: map[string]string{}, si: serveSysInfo{GPUs: 2, ArgEnv: map[string]string{"LLAMA_ARG_CPU_MOE": "0"}}, want: "4x", wantNotes: 1},
|
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",
|
{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"}}},
|
cfg: map[string]string{}, si: serveSysInfo{GPUs: 2, ArgEnv: map[string]string{"LLAMA_ARG_DEVICE": "CUDA1"}}},
|
||||||
|
// Relecture : ce que llama.cpp fait vraiment de ses variables.
|
||||||
|
{name: "--n-cpu-moe 0 n'annule pas LLAMA_ARG_N_CPU_MOE (surcharges cumulées) : rien",
|
||||||
|
cfg: map[string]string{"EXTRA_ARGS": "--n-cpu-moe 0"},
|
||||||
|
si: serveSysInfo{GPUs: 2, ArgEnv: map[string]string{"LLAMA_ARG_N_CPU_MOE": "30"}}},
|
||||||
|
{name: "deux --n-cpu-moe, le dernier à 0 : le premier compte quand même, rien",
|
||||||
|
cfg: map[string]string{"EXTRA_ARGS": "--n-cpu-moe 20 --n-cpu-moe 0"}, si: two},
|
||||||
|
{name: "--n-cpu-ffn (FFN dense sur CPU) : rien",
|
||||||
|
cfg: map[string]string{"EXTRA_ARGS": "-ncffn 8"}, si: two},
|
||||||
|
{name: "LLAMA_ARG_OVERRIDE_TENSOR : rien",
|
||||||
|
cfg: map[string]string{}, si: serveSysInfo{GPUs: 2, ArgEnv: map[string]string{"LLAMA_ARG_OVERRIDE_TENSOR": "exps=CPU"}}},
|
||||||
|
{name: "LLAMA_ARG_CPU_MOE=yes : llama.cpp ne l'active pas, 4x",
|
||||||
|
cfg: map[string]string{}, si: serveSysInfo{GPUs: 2, ArgEnv: map[string]string{"LLAMA_ARG_CPU_MOE": "yes"}}, want: "4x", wantNotes: 1},
|
||||||
|
{name: "LLAMA_ARG_NO_KV_OFFLOAD=0 : sa seule présence coupe l'offload, rien",
|
||||||
|
cfg: map[string]string{}, si: serveSysInfo{GPUs: 2, ArgEnv: map[string]string{"LLAMA_ARG_NO_KV_OFFLOAD": "0"}}},
|
||||||
|
{name: "LLAMA_ARG_KV_OFFLOAD=false : rien",
|
||||||
|
cfg: map[string]string{}, si: serveSysInfo{GPUs: 2, ArgEnv: map[string]string{"LLAMA_ARG_KV_OFFLOAD": "false"}}},
|
||||||
|
{name: "-kvo sur la ligne l'emporte sur LLAMA_ARG_NO_KV_OFFLOAD : 4x",
|
||||||
|
cfg: map[string]string{"EXTRA_ARGS": "-kvo"},
|
||||||
|
si: serveSysInfo{GPUs: 2, ArgEnv: map[string]string{"LLAMA_ARG_NO_KV_OFFLOAD": "1"}}, want: "4x", wantNotes: 1},
|
||||||
|
{name: "-nkvo puis -kvo : le dernier gagne, 4x",
|
||||||
|
cfg: map[string]string{"EXTRA_ARGS": "-nkvo -kvo"}, si: two, want: "4x", wantNotes: 1},
|
||||||
|
{name: "NGL absent : Loki passe -ngl auto, LLAMA_ARG_N_GPU_LAYERS ne compte pas, 4x",
|
||||||
|
cfg: map[string]string{}, si: serveSysInfo{GPUs: 2, ArgEnv: map[string]string{"LLAMA_ARG_N_GPU_LAYERS": "20"}}, want: "4x", wantNotes: 1},
|
||||||
|
{name: "NGL=Auto (casse libre, comme nglArgs) : 4x",
|
||||||
|
cfg: map[string]string{"NGL": "Auto"}, si: two, want: "4x", wantNotes: 1},
|
||||||
|
{name: "-sm tensor : pas de pipeline par couches, rien",
|
||||||
|
cfg: map[string]string{"EXTRA_ARGS": "-sm tensor"}, si: two},
|
||||||
}
|
}
|
||||||
for _, c := range cases {
|
for _, c := range cases {
|
||||||
t.Run(c.name, func(t *testing.T) {
|
t.Run(c.name, func(t *testing.T) {
|
||||||
|
|||||||
Reference in new issue
Block a user