mirror of
https://github.com/R0m1k3/Loki.git
synced 2026-10-17 04:10:04 +02:00
loki-rs engine serve fait ce que faisait loki serve : lire la configuration (par l'API interne du Go), sonder le monde (aide du moteur, --version, --list-devices, GGUF, RAM, VRAM, cgroup, cpuset, variables LLAMA_ARG_*/GGML_*), composer la ligne de llama-server, vérifier le port, garder la commande pour l'interface, puis exec. Le superviseur le lance à la place de loki serve quand LOKI_SERVE=rust (expérimental ; le Go reste le défaut). serve/ porte backend_serve*.go fonction par fonction (couches GPU, --fit et sa marge, threads, files CUDA, second slot, mode tensor, cache de prompts, points de reprise, décodage spéculatif, fidélité, LOAD_GUARD, SLOT_PERSIST), toute la décision restant pure. Strata, l'essai de l'optimiseur et un lancement sans API interne passent toujours par loki serve (Go). La façade donne au Go l'adresse de son API interne (LOKI_GO_ADDR), héritée par le superviseur. Parité : parity_serve_test.go tire du Go 1 872 lancements (12 machines × 112 presets × 3 aides de moteur × 13 jeux de variables) et les sondes pures, rejoués argument par argument par tests/serve.rs ; deux essais de bout en bout lancent le vrai binaire contre un faux Go et un faux moteur. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_016tcGfJWefW5kUGoC2wL8cv
351 lines
19 KiB
Go
351 lines
19 KiB
Go
package loki
|
||
|
||
import (
|
||
"os"
|
||
"path/filepath"
|
||
"strconv"
|
||
"testing"
|
||
"testing/fstest"
|
||
)
|
||
|
||
// parity_serve_test.go — journal de référence de « loki serve » (lot 14 de la
|
||
// migration Rust) : la ligne de commande de llama-server, son environnement et
|
||
// ses notes, pour des centaines de lancements (machines × presets × aides de
|
||
// moteur × variables), plus les sondes pures (cgroup, cpuset, nvidia-smi,
|
||
// --list-devices, build du moteur). Rangé dans
|
||
// rust/loki-rs/tests/parity_serve/serve.json ; tests/serve.rs le rejoue contre
|
||
// serve::build_serve_args.
|
||
//
|
||
// LOKI_PARITY_UPDATE=1 go test ./internal/loki -run TestPariteServe
|
||
|
||
const parityServeFile = "../../rust/loki-rs/tests/parity_serve/serve.json"
|
||
|
||
type parityServeCase struct {
|
||
M int `json:"m"` // machine
|
||
C int `json:"c"` // preset
|
||
H int `json:"h"` // aide du moteur
|
||
E int `json:"e"` // variables du moteur
|
||
Args []string `json:"args"`
|
||
Env any `json:"env"`
|
||
Notes []string `json:"notes"`
|
||
}
|
||
|
||
const (
|
||
parityHelpModern = "-ngl, --gpu-layers, --n-gpu-layers N max. number of layers to store in VRAM, either an exact number, 'auto', or 'all' (default: auto)\n" +
|
||
"--reasoning [on|off|auto] --reasoning-budget N --load-mode {auto,mmap,none,mlock,mmap+mlock,dio}\n" +
|
||
"-fit, --fit [on|off] -fitt, --fit-target MiB0,MiB1 --no-kv-unified -cram, --cache-ram N --slot-save-path PATH\n" +
|
||
"-ctxcp, --ctx-checkpoints N -cms, --checkpoint-min-step N --reasoning-preserve, --no-reasoning-preserve\n" +
|
||
"--spec-type {none,draft-simple,draft-mtp,ngram-mod} --spec-draft-n-max N --spec-draft-sampling {greedy,probabilistic}\n" +
|
||
"--spec-ngram-mod-n-match N -sm, --split-mode {none,layer,row,tensor} --context-shift, --no-context-shift\n"
|
||
parityHelpOld = "-ngl, --gpu-layers, --n-gpu-layers N number of layers to store in VRAM\n" +
|
||
"--mlock --no-mmap --no-context-shift --cache-ram N\n"
|
||
)
|
||
|
||
func TestPariteServe(t *testing.T) {
|
||
gDense := &GGUFInfo{Version: 3, Arch: "qwen3", BlockCount: 64, ContextLength: 262144, KeyLen: 128, ValLen: 128,
|
||
HeadCountKV: 8, EmbeddingLength: 5120, HeadCount: 40, TensorCount: 707}
|
||
gMoE := &GGUFInfo{Version: 3, Arch: "qwen3moe", BlockCount: 48, ContextLength: 262144, ExpertCount: 128,
|
||
KeyLen: 128, ValLen: 128, HeadCountKV: 4, EmbeddingLength: 2048, HeadCount: 32, TensorCount: 579}
|
||
gHybrid := &GGUFInfo{Version: 3, Arch: "qwen35", BlockCount: 64, NextN: 1, ContextLength: 262144, KeyLen: 256,
|
||
ValLen: 256, HeadCountKV: 4, FullAttnInterval: 4, Hybrid: true, EmbeddingLength: 5120, HeadCount: 24,
|
||
SSMConv: 4, SSMInner: 6144, SSMState: 128, SSMGroups: 16, HasNextNTensor: true, TensorCount: 900}
|
||
gLayers := &GGUFInfo{Version: 3, Arch: "gemma3", BlockCount: 6, KeyLen: 64, ValLen: 64,
|
||
HeadCountKVLayers: []int{2, 2, 0, 2, 2, 0}, HeadCountKV: 2, Hybrid: true, SSMConv: 4, SSMInner: 512,
|
||
SSMState: 16, SSMGroups: 1}
|
||
gDeny := &GGUFInfo{Version: 3, Arch: "deepseek2", BlockCount: 61, ExpertCount: 256, KeyLen: 192, ValLen: 128,
|
||
HeadCountKV: 128}
|
||
draftMTP := &GGUFInfo{Arch: "qwen35", BlockCount: 1, HasNextNTensor: true}
|
||
draftEagle := &GGUFInfo{Arch: "eagle3", BlockCount: 1}
|
||
draftSmall := &GGUFInfo{Arch: "qwen3", BlockCount: 28, KeyLen: 128, ValLen: 128, HeadCountKV: 8}
|
||
gib := int64(1 << 30)
|
||
devs2 := []splitDev{{ID: "CUDA0", TotalMiB: 16311}, {ID: "CUDA1", TotalMiB: 12288}}
|
||
|
||
machines := []serveSysInfo{
|
||
// 0 : une carte de 16 Go, 64 Go de RAM, dense de 16 Go.
|
||
{Model: "/m/dense.gguf", GGUF: gDense, ModelBytes: 16 * gib, RAMMiB: 65536, RAMAvailMiB: 49152, VRAMMiB: 16311, GPUs: 1},
|
||
// 1 : deux cartes, 32 Go de RAM, MoE de 22 Go.
|
||
{Model: "/m/moe.gguf", GGUF: gMoE, ModelBytes: 22 * gib, RAMMiB: 32768, RAMAvailMiB: 20000, VRAMMiB: 28599, GPUs: 2,
|
||
SplitDevs: devs2},
|
||
// 2 : rien de connu.
|
||
{Model: "/m/inconnu.gguf"},
|
||
// 3 : hybride avec tête MTP, RAM serrée, build ancien.
|
||
{Model: "/m/hybrid.gguf", GGUF: gHybrid, ModelBytes: 20 * gib, RAMMiB: 24576, RAMAvailMiB: 6000, VRAMMiB: 24576,
|
||
GPUs: 1, EngineBuild: 9000},
|
||
// 4 : hybride, build récent, deux cartes, beaucoup de RAM.
|
||
{Model: "/m/hybrid.gguf", GGUF: gHybrid, ModelBytes: 15 * gib, RAMMiB: 131072, RAMAvailMiB: 100000, VRAMMiB: 28599,
|
||
GPUs: 2, EngineBuild: 12000, SplitDevs: devs2, SlotDir: "/data/slots"},
|
||
// 5 : architecture exclue du mode tensor, VRAM inconnue, clé d'API.
|
||
{Model: "/m/ds.gguf", GGUF: gDeny, ModelBytes: 200 * gib, RAMMiB: 262144, RAMAvailMiB: 200000, APIKey: "sk-loki-x",
|
||
SplitDevs: []splitDev{{ID: "Vulkan0", TotalMiB: 8000}, {ID: "CUDA0", TotalMiB: 8000}}},
|
||
// 6 : couches par tableau, mémoire unifiée, conteneur à l'étroit, dossier des slots.
|
||
{Model: "/m/layers.gguf", GGUF: gLayers, ModelBytes: 3 * gib, RAMMiB: 16384, RAMAvailMiB: 4096, UnifiedMem: true,
|
||
CPU: cpuBudget{N: 4, Engine: 8, Why: "cpuset du conteneur, 4 cœurs physiques permis sur 8"}, SlotDir: "/data/slots",
|
||
MMProj: "/m/mmproj.gguf", EngineVRAMMiB: 0},
|
||
// 7 : brouillon MTP à part, deux cartes, build récent, VRAM du moteur connue.
|
||
{Model: "/m/dense.gguf", GGUF: gDense, ModelBytes: 30 * gib, RAMMiB: 32768, RAMAvailMiB: 16000, VRAMMiB: 24000,
|
||
GPUs: 2, EngineBuild: 11500, Draft: "/m/mtp-head.gguf", DraftGGUF: draftMTP, EngineVRAMMiB: 24000,
|
||
SplitDevs: devs2, LaunchQueues: ""},
|
||
}
|
||
machines = append(machines,
|
||
// 8 : brouillon EAGLE, puis petit modèle brouillon, puis le modèle lui-même.
|
||
serveSysInfo{Model: "/m/dense.gguf", GGUF: gDense, ModelBytes: 16 * gib, RAMMiB: 65536, RAMAvailMiB: 49152,
|
||
VRAMMiB: 16311, GPUs: 1, EngineBuild: 12000, Draft: "/m/eagle.gguf", DraftGGUF: draftEagle},
|
||
serveSysInfo{Model: "/m/dense.gguf", GGUF: gDense, ModelBytes: 16 * gib, RAMMiB: 65536, RAMAvailMiB: 49152,
|
||
VRAMMiB: 16311, GPUs: 1, EngineBuild: 12000, Draft: "/m/small.gguf", DraftGGUF: draftSmall},
|
||
serveSysInfo{Model: "/m/hybrid.gguf", GGUF: gHybrid, ModelBytes: 15 * gib, RAMMiB: 65536, RAMAvailMiB: 49152,
|
||
VRAMMiB: 24576, GPUs: 1, EngineBuild: 12000, Draft: "/m/hybrid.gguf", DraftGGUF: gHybrid, DraftIsModel: true,
|
||
SpecAutoBlocked: "le dernier lancement avec cette configuration n'a jamais répondu"},
|
||
// 11 : brouillon introuvable.
|
||
serveSysInfo{Model: "/m/dense.gguf", GGUF: gDense, ModelBytes: 16 * gib, RAMMiB: 65536, RAMAvailMiB: 49152,
|
||
VRAMMiB: 16311, GPUs: 1, EngineBuild: 12000, DraftErr: "stat /m/absent.gguf: no such file or directory"},
|
||
)
|
||
|
||
presets := []map[string]string{
|
||
{},
|
||
{"CTX": "65536", "NGL": "999", "BATCH": "4096", "UBATCH": "2048", "HOST": "127.0.0.1", "PORT": "8081"},
|
||
{"NGL": "auto"}, {"NGL": "40"}, {"NGL": "all"}, {"NGL": " 999 "},
|
||
{"THREADS": "8", "THREADS_BATCH": "4"}, {"THREADS": "abc", "THREADS_BATCH": "-2"}, {"THREADS": "0"},
|
||
{"KV_TYPE": "q8_0"}, {"KV_TYPE_K": "q4_0", "KV_TYPE_V": "f16"}, {"KV_TYPE": "bf16"}, {"KV_TYPE": "q5_1"},
|
||
{"REASONING": "on"}, {"REASONING": "off"}, {"REASONING": "auto", "REASONING_BUDGET": "2048"}, {"REASONING": "deepseek"},
|
||
{"REASONING_PRESERVE": "on"}, {"REASONING_PRESERVE": "off"}, {"REASONING_PRESERVE": "peut-être"},
|
||
{"CACHE_RAM": "auto"}, {"CACHE_RAM": "4096"}, {"CACHE_RAM": "0"}, {"CACHE_RAM": "-1"}, {"CACHE_RAM": "beaucoup"},
|
||
{"CACHE_ISOLATE": "off"}, {"CACHE_RAM": "0", "CACHE_ISOLATE": "on"},
|
||
{"SIDE_SLOT": "on"}, {"SIDE_SLOT": "on", "PARALLEL": "3"}, {"SIDE_SLOT": "on", "CTX": "0"}, {"SIDE_SLOT": "on", "CTX": "8192", "KV_TYPE": "q8_0"},
|
||
{"SPLIT_MODE": "tensor"}, {"SPLIT_MODE": "tensor", "CTX": "16384"}, {"SPLIT_MODE": "bizarre"}, {"SPLIT_MODE": "layer"},
|
||
{"SPLIT_MODE": "tensor", "KV_TYPE": "q8_0"}, {"SPLIT_MODE": "tensor", "NGL": "20"},
|
||
{"SPEC": "auto"}, {"SPEC": "mtp"}, {"SPEC": "ngram"}, {"SPEC": "mtp+ngram"}, {"SPEC": "ngram+mtp"}, {"SPEC": "turbo"},
|
||
{"SPEC": "off", "MODEL_DRAFT": "mtp-head.gguf"}, {"SPEC": "mtp", "MODEL_DRAFT": "mtp-head.gguf", "SPEC_N_MAX": "4"},
|
||
{"SPEC": "mtp", "SPEC_N_MAX": "99", "SPEC_SAMPLING": "probabilistic"}, {"SPEC": "auto", "SPEC_SAMPLING": "probabilistic"},
|
||
{"SPEC": "mtp", "SPEC_SAMPLING": "aléatoire"}, {"SPEC": "ngram", "MODEL_DRAFT": "x.gguf", "SPEC_N_MAX": "3"},
|
||
{"SPEC": "auto", "MODEL_DRAFT": "small.gguf"}, {"SPEC": "mtp+ngram", "MODEL_DRAFT": "small.gguf"},
|
||
{"CUDA_LAUNCH_QUEUES": "off"}, {"CUDA_LAUNCH_QUEUES": "2x"}, {"CUDA_LAUNCH_QUEUES": "8x"}, {"CUDA_LAUNCH_QUEUES": "auto"},
|
||
{"CUDA_VISIBLE_DEVICES": "1,0"},
|
||
{"FIT_TARGET": "1024,3072"}, {"FIT_TARGET": "512"}, {"FIT_TARGET": "1024,,3072"}, {"FIT_TARGET": "1,2,3,4,5,6,7,8,9"},
|
||
{"FIT_TARGET": "2048/4096", "CTX": "0"}, {"FIT_TARGET": "2048", "NGL": "30"},
|
||
{"OP_OFFLOAD_MIN_BATCH": "128"}, {"OP_OFFLOAD_MIN_BATCH": "0"},
|
||
{"CUDA_GRAPH_OPT": "on"}, {"CUDA_GRAPH_OPT": "oui"}, {"CUDA_GRAPH_OPT": "off"},
|
||
{"CTX_CHECKPOINTS": "8"}, {"CTX_CHECKPOINTS": "beaucoup"}, {"CKPT_MIN_STEP": "1024"}, {"CKPT_MIN_STEP": "0"},
|
||
{"SLOT_PERSIST": "on"}, {"SLOT_PERSIST": "on", "HOST": "127.0.0.1"}, {"SLOT_PERSIST": "on", "HOST": "127.0.0.1", "SPEC": "mtp"},
|
||
{"LOAD_GUARD": "off", "EXTRA_ARGS": "--load-mode none"}, {"EXTRA_ARGS": "--mlock --no-mmap"}, {"EXTRA_ARGS": "--mlock"},
|
||
{"EXTRA_ARGS": "--no-mmap"}, {"EXTRA_ARGS": "--mmap"}, {"EXTRA_ARGS": "--load-mode dio"}, {"EXTRA_ARGS": "--load-mode=mlock"},
|
||
{"EXTRA_ARGS": "-ngl 30 --parallel 4"}, {"EXTRA_ARGS": "-t 6 -tb 3"}, {"EXTRA_ARGS": "-Cb 0xff --poll-batch 1"},
|
||
{"THREADS": "6", "EXTRA_ARGS": "--prio-batch 2"}, {"EXTRA_ARGS": "--cache-reuse 256 --context-shift"},
|
||
{"EXTRA_ARGS": "-ctk q8_0 -ctv q8_0"}, {"EXTRA_ARGS": "-ot exps=CPU"}, {"EXTRA_ARGS": "-ot 'blk\\.(1|2)\\.ffn_.*_exps=CPU' -ub 512"},
|
||
{"EXTRA_ARGS": "--n-cpu-moe 20"}, {"EXTRA_ARGS": "--cpu-moe"}, {"EXTRA_ARGS": "-sm row"}, {"EXTRA_ARGS": "-ts 1,1"},
|
||
{"EXTRA_ARGS": "-dev CUDA0,CUDA1"}, {"EXTRA_ARGS": "-dev Vulkan0"}, {"EXTRA_ARGS": "--fit off"}, {"EXTRA_ARGS": "--mmproj /m/x.gguf"},
|
||
{"EXTRA_ARGS": "--spec-synth-len 4"}, {"EXTRA_ARGS": "--slot-save-path /x"}, {"EXTRA_ARGS": "-c 0"},
|
||
{"EXTRA_ARGS": "--host 127.0.0.1 --port 9000"}, {"EXTRA_ARGS": "--mirostat 2", "SPEC": "mtp", "SPEC_SAMPLING": "probabilistic"},
|
||
{"EXTRA_ARGS": "--chat-template-kwargs '{\"preserve_reasoning\":true}'", "REASONING_PRESERVE": "on"},
|
||
{"EXTRA_ARGS": "--spec-type ngram-mod", "SPEC": "auto"}, {"EXTRA_ARGS": "--spec-draft-n-max 8", "SPEC": "ngram"},
|
||
{"EXTRA_ARGS": "-ot attn=CUDA1", "CUDA_GRAPH_OPT": "on"}, {"EXTRA_ARGS": "-fa off", "SPLIT_MODE": "tensor"},
|
||
{"EXTRA_ARGS": "-nkvo"}, {"EXTRA_ARGS": "--no-context-shift"}, {"EXTRA_ARGS": "-c 8192 -np 2", "SIDE_SLOT": "on"},
|
||
{"EXTERNAL": "1", "SIDE_SLOT": "on", "SPLIT_MODE": "tensor"},
|
||
{"MMPROJ": "x", "NGL": "999", "CTX": "40000", "KV_TYPE": "q4_0", "SPEC": "auto", "CACHE_RAM": "auto"},
|
||
}
|
||
helps := []string{parityHelpModern, parityHelpOld, ""}
|
||
argEnvs := []map[string]string{
|
||
{},
|
||
{"LLAMA_ARG_CACHE_TYPE_K": "q8_0"}, {"LLAMA_ARG_NO_KV_OFFLOAD": "1"}, {"LLAMA_ARG_N_CPU_MOE": "10"},
|
||
{"LLAMA_ARG_CACHE_RAM": "1024"}, {"LLAMA_ARG_CONTEXT_SHIFT": "on"}, {"LLAMA_ARG_FIT": "off"},
|
||
{"LLAMA_ARG_SPLIT_MODE": "row"}, {"LLAMA_ARG_DEVICE": "CUDA0"}, {"LLAMA_ARG_MLOCK": "1", "LLAMA_ARG_NO_MMAP": "1"},
|
||
{"LLAMA_ARG_NO_CONTEXT_SHIFT": "true"}, {"LLAMA_ARG_SPEC_SYNTH_LEN": "3"}, {"LLAMA_ARG_TENSOR_SPLIT": "1,1"},
|
||
}
|
||
userEnvs := []map[string]string{{}, {"GGML_CUDA_GRAPH_OPT": "1", "GGML_CUDA_ALLREDUCE": "nccl", "GGML_OP_OFFLOAD_MIN_BATCH": "64"}}
|
||
|
||
var fx struct {
|
||
Machines []serveSysInfo `json:"machines"`
|
||
Presets []map[string]string `json:"presets"`
|
||
Helps []string `json:"helps"`
|
||
ArgEnvs []map[string]string `json:"arg_envs"`
|
||
UserEnvs []map[string]string `json:"user_envs"`
|
||
Cases []parityServeCase `json:"cases"`
|
||
|
||
CPUList map[string]any `json:"cpu_list"`
|
||
Cpuset []map[string]any `json:"cpuset"`
|
||
Cgroup []map[string]any `json:"cgroup"`
|
||
SMI []map[string]any `json:"smi"`
|
||
Devices map[string]any `json:"devices"`
|
||
Builds map[string]int `json:"builds"`
|
||
Cmdlines []map[string]any `json:"cmdlines"`
|
||
Verdicts []map[string]any `json:"verdicts"`
|
||
Fit map[string]bool `json:"help_fit"`
|
||
Gib map[string]string `json:"gib"`
|
||
Load []map[string]any `json:"load_flags"`
|
||
State []map[string]any `json:"state_bytes"`
|
||
}
|
||
fx.Machines, fx.Presets, fx.Helps, fx.ArgEnvs, fx.UserEnvs = machines, presets, helps, argEnvs, userEnvs
|
||
add := func(m, c, h, e int) {
|
||
si := machines[m]
|
||
si.Help = helps[h]
|
||
si.ArgEnv = map[string]string{}
|
||
for k, v := range argEnvs[e] {
|
||
si.ArgEnv[k] = v
|
||
}
|
||
si.UserEnv = map[string]string{}
|
||
if (m+c)%3 == 1 {
|
||
for k, v := range userEnvs[1] {
|
||
si.UserEnv[k] = v
|
||
}
|
||
}
|
||
cfg := presets[c]
|
||
args, env, notes := buildServeArgs(cfg, splitArgs(cfg["EXTRA_ARGS"]), "/app/llama-server", si)
|
||
fx.Cases = append(fx.Cases, parityServeCase{M: m, C: c, H: h, E: e, Args: args, Env: env, Notes: notes})
|
||
}
|
||
for m := range machines {
|
||
for c := range presets {
|
||
add(m, c, 0, 0)
|
||
}
|
||
}
|
||
for c := range presets {
|
||
add(0, c, 1, 0)
|
||
add(2, c, 2, 0)
|
||
add(4, c, 1, 0)
|
||
}
|
||
for e := 1; e < len(argEnvs); e++ {
|
||
for _, c := range []int{0, 1, 9, 20, 27, 31, 37, 39, 40, 57, 65, 69, 77, 84, 92, 93} {
|
||
add((e+c)%len(machines), c, 0, e)
|
||
}
|
||
}
|
||
|
||
// Sondes pures.
|
||
fx.CPUList = map[string]any{}
|
||
for _, s := range []string{"0-3,8-11", " 5 ", "", "0,0,1", "3-1", "a-b", "0-70000", "2-2,1"} {
|
||
l, err := parseCPUList(s)
|
||
if err != nil {
|
||
fx.CPUList[s] = "erreur"
|
||
} else {
|
||
fx.CPUList[s] = l
|
||
}
|
||
}
|
||
topo := func(fs fstest.MapFS, cpus int, smt bool) {
|
||
for c := 0; c < cpus; c++ {
|
||
sib := c
|
||
if smt {
|
||
sib = c / 2
|
||
}
|
||
fs["sys/devices/system/cpu/cpu"+strconv.Itoa(c)+"/topology/thread_siblings"] = &fstest.MapFile{Data: []byte(strconv.Itoa(sib) + "\n")}
|
||
}
|
||
}
|
||
for _, tc := range []struct {
|
||
name string
|
||
files map[string]string
|
||
cpus int
|
||
smt bool
|
||
}{
|
||
{"sans restriction", map[string]string{"sys/devices/system/cpu/online": "0-7\n", "proc/self/status": "Name:\tx\nCpus_allowed_list:\t0-7\n"}, 8, true},
|
||
{"cpuset", map[string]string{"sys/devices/system/cpu/online": "0-15\n", "proc/self/status": "Cpus_allowed_list:\t0-5\n"}, 16, true},
|
||
{"quota v2", map[string]string{"sys/devices/system/cpu/online": "0-15\n", "proc/self/status": "Cpus_allowed_list:\t0-15\n", "sys/fs/cgroup/cpu.max": "250000 100000\n"}, 16, false},
|
||
{"quota v1", map[string]string{"sys/devices/system/cpu/online": "0-15\n", "proc/self/status": "Cpus_allowed_list:\t0-15\n", "sys/fs/cgroup/cpu/cpu.cfs_quota_us": "150000\n", "sys/fs/cgroup/cpu/cpu.cfs_period_us": "100000\n"}, 16, false},
|
||
{"cœurs E", map[string]string{"sys/devices/system/cpu/online": "0-11\n", "proc/self/status": "Cpus_allowed_list:\t0-3,8-11\n", "sys/devices/cpu_atom/cpus": "8-11\n"}, 12, false},
|
||
{"max", map[string]string{"sys/devices/system/cpu/online": "0-3\n", "proc/self/status": "Cpus_allowed_list:\t0-3\n", "sys/fs/cgroup/cpu.max": "max 100000\n"}, 4, false},
|
||
{"illisible", map[string]string{"sys/devices/system/cpu/online": "x\n"}, 0, false},
|
||
} {
|
||
fs := fstest.MapFS{}
|
||
for p, d := range tc.files {
|
||
fs[p] = &fstest.MapFile{Data: []byte(d)}
|
||
}
|
||
topo(fs, tc.cpus, tc.smt)
|
||
files := map[string]string{}
|
||
for p, f := range fs {
|
||
files[p] = string(f.Data)
|
||
}
|
||
b := cpusetThreads(fs)
|
||
fx.Cpuset = append(fx.Cpuset, map[string]any{"name": tc.name, "files": files, "n": b.N, "engine": b.Engine, "why": b.Why})
|
||
}
|
||
for _, files := range []map[string]string{
|
||
{"sys/fs/cgroup/memory.max": "17179869184\n", "sys/fs/cgroup/memory.current": "4294967296\n"},
|
||
{"sys/fs/cgroup/memory.max": "max\n"},
|
||
{"sys/fs/cgroup/memory/memory.limit_in_bytes": "9223372036854771712\n"},
|
||
{"sys/fs/cgroup/memory/memory.limit_in_bytes": "8589934592\n", "sys/fs/cgroup/memory/memory.usage_in_bytes": "1073741824\n"},
|
||
{},
|
||
} {
|
||
fs := fstest.MapFS{}
|
||
for p, d := range files {
|
||
fs[p] = &fstest.MapFile{Data: []byte(d)}
|
||
}
|
||
l, c := cgroupMemMiB(fs)
|
||
fx.Cgroup = append(fx.Cgroup, map[string]any{"files": files, "limit": l, "current": c})
|
||
}
|
||
smiOut := "0, 16311\n1, 12288\n"
|
||
for _, tc := range []struct {
|
||
out, cvd string
|
||
pci bool
|
||
}{{smiOut, "", true}, {smiOut, "1", true}, {smiOut, "1,0", false}, {smiOut, "2", true}, {smiOut, "GPU-abc", true},
|
||
{"0, 0\n", "", true}, {"", "", true}, {"0, 8192\n", "0", false}} {
|
||
fx.SMI = append(fx.SMI, map[string]any{"out": tc.out, "cvd": tc.cvd, "pci": tc.pci, "mib": vramFromSMI(tc.out, tc.cvd, tc.pci)})
|
||
}
|
||
devOut := "Available devices:\n CUDA0: NVIDIA GeForce RTX 5060 Ti (16311 MiB, 15579 MiB free)\n CUDA1: NVIDIA GeForce RTX 3060 (12288 MiB, 11000 MiB free)\r\n Vulkan0: AMD (8000 MiB, 0 MiB free)\nautre ligne\n"
|
||
fx.Devices = map[string]any{"out": devOut, "devs": splitDevsFrom(parseListDevices(devOut))}
|
||
fx.Builds = map[string]int{}
|
||
for _, out := range []string{"version: 6543 (abcdef0)\nbuilt with gcc", "llama-server build 11009, commit 1a2b3c4", "rien", "build: 12 (ff)"} {
|
||
b, _ := parseEngineBuild(out)
|
||
fx.Builds[out] = b
|
||
}
|
||
for _, tc := range []struct {
|
||
env map[string]string
|
||
args []string
|
||
}{
|
||
{map[string]string{"CUDA_VISIBLE_DEVICES": "0,1", "GGML_X": "a b"}, []string{"/app/llama-server", "-m", "/m/a b.gguf", "--api-key", "secret", "--chat-template-kwargs", `{"x":"y"}`, ""}},
|
||
{map[string]string{}, []string{"/bin/x", "--api-key-file", "/k"}},
|
||
} {
|
||
fx.Cmdlines = append(fx.Cmdlines, map[string]any{"env": tc.env, "args": tc.args, "out": engineCmdline(tc.env, tc.args)})
|
||
}
|
||
cur := specAutoMark{FP: "abc", Bin: "/app/llama-server", Build: 11500}
|
||
for _, tc := range []struct {
|
||
attempt *specAutoMark
|
||
failed map[string]string
|
||
}{{nil, nil}, {&cur, nil}, {&specAutoMark{FP: "x"}, nil}, {nil, map[string]string{cur.key(): "déjà échoué"}}} {
|
||
why, rec := specAutoVerdict(cur, tc.attempt, tc.failed)
|
||
fx.Verdicts = append(fx.Verdicts, map[string]any{"attempt": tc.attempt, "failed": tc.failed, "why": why, "record": rec})
|
||
}
|
||
fx.Fit = map[string]bool{}
|
||
for _, h := range []string{"--fit [on|off]", "-fit, --fit X", "--fit-target", "--fit=on", "x--fit", "--fit", "a,--fit,b", "--fitness"} {
|
||
fx.Fit[h] = helpSupportsFit(h)
|
||
}
|
||
fx.Gib = map[string]string{}
|
||
for _, m := range []int64{0, 512, 1280, 1536, 16311, 22528, 65536, 104857} {
|
||
fx.Gib[strconv.Itoa(int(m))] = gibText(m)
|
||
}
|
||
for _, tc := range []struct {
|
||
args []string
|
||
modern bool
|
||
}{
|
||
{[]string{"--mlock"}, true}, {[]string{"--no-mmap", "--mlock"}, true}, {[]string{"--mmap", "-x"}, true},
|
||
{[]string{"--load-mode", "none", "--mlock"}, true}, {[]string{"--load-mode", "mlock"}, false},
|
||
{[]string{"--load-mode=none", "--no-mmap"}, false}, {[]string{"-lm", "dio"}, false}, {[]string{"-lm", "MMAP+MLOCK"}, false},
|
||
{[]string{"--load-mode"}, false}, {[]string{"-x"}, false},
|
||
} {
|
||
out, note := normalizeLoadFlags(tc.args, tc.modern)
|
||
fx.Load = append(fx.Load, map[string]any{"args": tc.args, "modern": tc.modern, "out": out, "note": note})
|
||
}
|
||
for _, g := range []*GGUFInfo{gDense, gMoE, gHybrid, gLayers, gDeny, {BlockCount: 10}} {
|
||
for _, kv := range [][2]string{{"", ""}, {"q8_0", "q4_0"}, {"f32", "iq4_nl"}} {
|
||
pt, rec := ggufStateBytes(*g, kv[0], kv[1])
|
||
fx.State = append(fx.State, map[string]any{"gguf": g, "k": kv[0], "v": kv[1], "per_token": pt, "recurrent": rec})
|
||
}
|
||
}
|
||
|
||
got, err := parityJSON(fx)
|
||
if err != nil {
|
||
t.Fatal(err)
|
||
}
|
||
if os.Getenv("LOKI_PARITY_UPDATE") == "1" {
|
||
if err := os.MkdirAll(filepath.Dir(parityServeFile), 0o755); err != nil {
|
||
t.Fatal(err)
|
||
}
|
||
if err := os.WriteFile(parityServeFile, got, 0o644); err != nil {
|
||
t.Fatal(err)
|
||
}
|
||
return
|
||
}
|
||
want, err := os.ReadFile(parityServeFile)
|
||
if err != nil {
|
||
t.Fatalf("journal de référence absent (%v) : LOKI_PARITY_UPDATE=1 pour le créer", err)
|
||
}
|
||
if string(got) != string(want) {
|
||
t.Errorf("loki serve ne produit plus le journal rangé dans %s.\n"+
|
||
"Si c'est voulu : LOKI_PARITY_UPDATE=1 go test ./internal/loki -run TestPariteServe,\n"+
|
||
"puis cargo test --test serve dit si le port Rust suit.", parityServeFile)
|
||
}
|
||
}
|