Files
Loki/internal/loki/parity_serve_test.go
T
Claude 13bcbc74fe Lot 14 : ligne de commande du moteur composée en Rust (LOKI_SERVE=rust)
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
2026-10-15 17:58:24 +00:00

351 lines
19 KiB
Go
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
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)
}
}