mirror of
https://github.com/R0m1k3/Loki.git
synced 2026-10-17 12:16:21 +02:00
Port de backend_strata.go (rust/loki-rs/src/strata) : la machine (cartes par nvidia-smi, Python 3.10+ et venv, RAM totale et disponible, RAM du moteur Strata en marche, place libre), ce qui y tient et la recommandation, la configuration de lancement en clair des modèles installés (placement des experts drop/ram/arena/disk, carte d'aide, réglages du preset), les réglages d'un modèle installé et l'installation (job des moteurs : paquet figé vérifié par SHA-256, installeur lancé et journalisé comme le sinkWriter du Go, preset écrit puis activé par le Go). Tenu seulement avec LOKI_BACKENDS=rust ; le lancement (loki serve) reste au Go, qui relaie les routes du tunnel. Correction du Go : /api/strata/settings décidait « en service » après avoir écrit les nouveaux réglages, donc jamais pour un réglage changé ; décidé désormais avant l'écriture, comme SavePresetApplying. Le port suit. Parité : parity_strata_test.go (crochets de test : release, meminfo, place libre, action de service) tire du Go un journal rejoué par tests/strata.rs ; les tests du Go portés dans strata/tests.rs ; TestFacadeStrata côté Go. Mutations : 71 tuées sur 71. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_016tcGfJWefW5kUGoC2wL8cv
465 lines
20 KiB
Go
465 lines
20 KiB
Go
//go:build linux
|
|
|
|
package loki
|
|
|
|
import (
|
|
"crypto/sha256"
|
|
"encoding/hex"
|
|
"encoding/json"
|
|
"io/fs"
|
|
"net/http"
|
|
"net/http/httptest"
|
|
"os"
|
|
"os/exec"
|
|
"path/filepath"
|
|
"runtime"
|
|
"sort"
|
|
"strings"
|
|
"sync"
|
|
"testing"
|
|
"time"
|
|
)
|
|
|
|
// parity_strata_test.go — journal de référence de Strata (lot 25 de la
|
|
// migration Rust) : la machine vue par l'écran d'installation (cartes,
|
|
// Python, RAM, disque) et ce qui y tient, les réglages d'un modèle installé
|
|
// (et la bascule qui les applique), l'installation (paquet figé vérifié,
|
|
// installeur lancé, preset écrit puis activé) et ses échecs.
|
|
// Rangé dans rust/loki-rs/tests/parity_strata/strata.json ; tests/strata.rs
|
|
// le rejoue par la façade.
|
|
//
|
|
// LOKI_PARITY_UPDATE=1 go test ./internal/loki -run TestPariteStrata
|
|
|
|
const parityStrataFile = "../../rust/loki-rs/tests/parity_strata/strata.json"
|
|
|
|
// Ce que sert la fausse release : les sources (archive), le moteur (des
|
|
// octets), un statut imposé, des empreintes attendues fausses.
|
|
type parityStrataRel struct {
|
|
Status int `json:"status,omitempty"`
|
|
Src []parityArchEntry `json:"src"`
|
|
Engine string `json:"engine"`
|
|
BadSHA bool `json:"bad_sha,omitempty"`
|
|
}
|
|
|
|
type parityStrataStep struct {
|
|
Name string `json:"name"`
|
|
Machine string `json:"machine,omitempty"`
|
|
Meminfo string `json:"meminfo,omitempty"`
|
|
DiskFree int64 `json:"disk_free,omitempty"`
|
|
Files map[string]string `json:"files,omitempty"`
|
|
Config map[string]string `json:"config,omitempty"`
|
|
State map[string]string `json:"state,omitempty"`
|
|
Release *parityStrataRel `json:"release,omitempty"`
|
|
Tune bool `json:"tune,omitempty"`
|
|
Method string `json:"method"`
|
|
Path string `json:"path"`
|
|
Body string `json:"body,omitempty"`
|
|
Wait bool `json:"wait,omitempty"`
|
|
Status int `json:"status"`
|
|
Out any `json:"out"`
|
|
Job any `json:"job,omitempty"`
|
|
Actions []string `json:"actions"`
|
|
After map[string]string `json:"after"`
|
|
Tree []string `json:"tree"`
|
|
}
|
|
|
|
// Les machines : nvidia-smi et python3 dans le PATH (bash et mkdir toujours).
|
|
func parityStrataMachines() map[string]map[string]string {
|
|
py := func(version string, venv bool) string {
|
|
s := "#!/bin/sh\ncase \"$1\" in\n--version) echo '" + version + "';;\n-c) "
|
|
if venv {
|
|
s += "exit 0;;\n"
|
|
} else {
|
|
s += "echo 'ModuleNotFoundError: No module named ensurepip'; exit 1;;\n"
|
|
}
|
|
return s + "esac\n"
|
|
}
|
|
ok := py("Python 3.12.3", true)
|
|
smi := func(rows string) string {
|
|
return "#!/bin/sh\ncase \"$1\" in\n--query-gpu=*) printf '" + rows + "';;\nesac\n"
|
|
}
|
|
deux := smi(`0, NVIDIA GeForce RTX 3070, 8192, 100, 8.6, 00000000:01:00.0, 4, 4, 16, 16, 7001\n1, NVIDIA GeForce RTX 5060 Ti, 16311, 5, 12.0, 00000000:02:00.0, 5, 5, 16, 16, 14001\n`)
|
|
return map[string]map[string]string{
|
|
"sans-carte": {"python3": ok},
|
|
"smi-en-panne": {"nvidia-smi": "#!/bin/sh\nexit 9\n", "python3": ok},
|
|
"smi-vide": {"nvidia-smi": smi(``), "python3": ok},
|
|
"vieille-carte": {"nvidia-smi": smi(`0, NVIDIA GeForce GTX 1080 Ti, 11264, 0, 6.1, 00000000:01:00.0, 3, 3, 16, 16, 5505\n`), "python3": ok},
|
|
"petite-carte": {"nvidia-smi": smi(`0, NVIDIA GeForce RTX 3050, 6144, 0, 8.6, 00000000:01:00.0, 4, 4, 8, 8, 7001\n`), "python3": ok},
|
|
"sans-python": {"nvidia-smi": deux},
|
|
"python-casse": {"nvidia-smi": deux, "python3": "#!/bin/sh\nexit 1\n"},
|
|
"python-muet": {"nvidia-smi": deux, "python3": py("Python inconnu", true)},
|
|
"python-ancien": {"nvidia-smi": deux, "python3": py("Python 3.8.10", true)},
|
|
"sans-venv": {"nvidia-smi": deux, "python3": py("Python 3.11.2", false)},
|
|
"deux-cartes": {"nvidia-smi": deux, "python3": ok},
|
|
"jumelles": {"nvidia-smi": smi(`0, NVIDIA GeForce RTX 4060 Ti, 16311, 0, 8.9, 00000000:01:00.0, 4, 4, 8, 8, 9001\n1, NVIDIA GeForce RTX 5060 Ti, 16311, 0, 12.0, 00000000:02:00.0, 5, 5, 8, 8, 14001\n2, NVIDIA GeForce GTX 1650, 4096, 0, 7.46, 00000000:03:00.0, 3, 3, 16, 16, 4001\n`),
|
|
"python3": ok},
|
|
"aide-ancienne": {"nvidia-smi": smi(`0, NVIDIA GeForce GTX 1070, 8192, 0, 6.1, 00000000:01:00.0, 3, 3, 16, 16, 4004\n1, NVIDIA GeForce RTX 4090, 24564, 0, 8.9, 00000000:02:00.0, 4, 4, 16, 16, 10501\n`),
|
|
"python3": ok},
|
|
}
|
|
}
|
|
|
|
// L'installeur factice : ses arguments, une barre de progression, puis ce
|
|
// que le modèle demandé lui fait faire.
|
|
const parityStrataSetup = `#!/bin/bash
|
|
echo "installeur : $*"
|
|
echo "cache : $XDG_CACHE_HOME"
|
|
printf 'modèle 10%%\r'
|
|
printf 'modèle 20%%\r'
|
|
echo "modèle téléchargé"
|
|
mkdir -p engine-cuda12 .venv/bin
|
|
fam=""; model=""; data=""
|
|
while [ $# -gt 0 ]; do
|
|
case "$1" in
|
|
--family) fam="$2"; shift;;
|
|
--model) model="$2"; shift;;
|
|
--data-dir) data="$2"; shift;;
|
|
esac
|
|
shift
|
|
done
|
|
case "$model" in
|
|
Q2_0) echo "erreur : téléchargement interrompu" >&2; exit 3;;
|
|
IQ2_XS) echo "rien d'écrit"; exit 0;;
|
|
esac
|
|
low=$(echo "$model" | tr 'A-Z' 'a-z')
|
|
if [ "$fam" = "swift" ]; then tag="swift-$low"; else tag="$low"; fi
|
|
mkdir -p "$data/packs/$tag"
|
|
printf '{"exe":"engine-cuda12/strata","args":["--pack","%s","--kv","int8","--spec","4","--max-context","131072","--vision","--vram-reserve-mib","700"],"vision":{"exe":"v","gpu":true},"gpu":0,"port":8080}\n' "$data/packs/$tag" > "strata-$tag.json"
|
|
echo "prêt : strata-$tag.json"
|
|
`
|
|
|
|
func TestPariteStrata(t *testing.T) {
|
|
if runtime.GOARCH != "amd64" {
|
|
t.Skip("Strata n'est proposé que sous Linux x86_64")
|
|
}
|
|
home := testHome(t)
|
|
resetBackendsState()
|
|
t.Cleanup(resetBackendsState)
|
|
|
|
tools := t.TempDir()
|
|
bash, err := exec.LookPath("bash")
|
|
if err != nil {
|
|
t.Skip("bash introuvable")
|
|
}
|
|
mkdir, _ := exec.LookPath("mkdir")
|
|
tr, _ := exec.LookPath("tr")
|
|
t.Setenv("PATH", tools)
|
|
machines := parityStrataMachines()
|
|
setMachine := func(name string) {
|
|
entries, _ := os.ReadDir(tools)
|
|
for _, e := range entries {
|
|
_ = os.Remove(filepath.Join(tools, e.Name()))
|
|
}
|
|
for n, p := range map[string]string{"bash": bash, "mkdir": mkdir, "tr": tr} {
|
|
if err := os.Symlink(p, filepath.Join(tools, n)); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
}
|
|
for n, body := range machines[name] {
|
|
if err := os.WriteFile(filepath.Join(tools, n), []byte(body), 0o755); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
}
|
|
}
|
|
|
|
meminfo := filepath.Join(t.TempDir(), "meminfo")
|
|
oldMem, oldDisk, oldBase, oldSHA, oldSvc := meminfoPath, strataDiskFree, strataReleaseBase, strataAssetSHA, serviceActionOSFn
|
|
var mu sync.Mutex
|
|
var disk int64
|
|
var actions []string
|
|
meminfoPath = meminfo
|
|
strataDiskFree = func(string) int64 { mu.Lock(); defer mu.Unlock(); return disk }
|
|
serviceActionOSFn = func(a string) error { mu.Lock(); defer mu.Unlock(); actions = append(actions, a); return nil }
|
|
t.Cleanup(func() {
|
|
meminfoPath, strataDiskFree, strataReleaseBase, strataAssetSHA, serviceActionOSFn = oldMem, oldDisk, oldBase, oldSHA, oldSvc
|
|
})
|
|
|
|
// La fausse release.
|
|
var rel parityStrataRel
|
|
srv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
|
mu.Lock()
|
|
cur := rel
|
|
mu.Unlock()
|
|
if cur.Status != 0 {
|
|
w.WriteHeader(cur.Status)
|
|
return
|
|
}
|
|
switch strings.TrimPrefix(r.URL.Path, "/") {
|
|
case strataSrcAsset:
|
|
_, _ = w.Write(parityTarGz(t, cur.Src))
|
|
case strataEngineAsset:
|
|
_, _ = w.Write([]byte(cur.Engine))
|
|
default:
|
|
w.WriteHeader(404)
|
|
}
|
|
}))
|
|
defer srv.Close()
|
|
strataReleaseBase = srv.URL + "/"
|
|
setRelease := func(r parityStrataRel) {
|
|
mu.Lock()
|
|
rel = r
|
|
mu.Unlock()
|
|
sum := func(b []byte) string { h := sha256.Sum256(b); return hex.EncodeToString(h[:]) }
|
|
strataAssetSHA = map[string]string{
|
|
strataSrcAsset: sum(parityTarGz(t, r.Src)),
|
|
strataEngineAsset: sum([]byte(r.Engine)),
|
|
}
|
|
if r.BadSHA {
|
|
strataAssetSHA[strataEngineAsset] = strings.Repeat("0", 64)
|
|
}
|
|
}
|
|
|
|
mask := func(s string) string {
|
|
s = strings.ReplaceAll(s, srv.URL, "{{REL}}")
|
|
s = strings.ReplaceAll(s, home, "{{HOME}}")
|
|
return parityTSRe.ReplaceAllString(s, `"$1":"{{T}}"`)
|
|
}
|
|
unmask := func(s string) string { return strings.ReplaceAll(s, "{{HOME}}", home) }
|
|
handlers := map[string]http.HandlerFunc{
|
|
"/api/strata": handleStrata,
|
|
"/api/strata/settings": handleStrataSettings,
|
|
"/api/strata/install": handleStrataInstall,
|
|
}
|
|
// L'arborescence : presets/ et strata/ (sans le contenu des archives, qui
|
|
// diffère d'une implémentation à l'autre).
|
|
tree := func() []string {
|
|
var out []string
|
|
for _, root := range []string{"presets", "strata"} {
|
|
_ = filepath.WalkDir(filepath.Join(home, root), func(p string, d fs.DirEntry, err error) error {
|
|
if err != nil {
|
|
return nil
|
|
}
|
|
rel, _ := filepath.Rel(home, p)
|
|
info, err := os.Lstat(p)
|
|
if err != nil {
|
|
return nil
|
|
}
|
|
switch {
|
|
case info.IsDir():
|
|
out = append(out, "d "+rel)
|
|
case strings.HasPrefix(rel, "strata/dl/") || strings.HasPrefix(rel, "strata/prebuilt/"):
|
|
out = append(out, "f "+rel)
|
|
default:
|
|
b, _ := os.ReadFile(p)
|
|
out = append(out, "f "+rel+" "+info.Mode().Perm().String()+" "+mask(string(b)))
|
|
}
|
|
return nil
|
|
})
|
|
}
|
|
sort.Strings(out)
|
|
return out
|
|
}
|
|
|
|
src := []parityArchEntry{
|
|
{Name: "ajean-moe-1.0/", Type: "dir", Mode: 0o755},
|
|
{Name: "ajean-moe-1.0/setup.sh", Type: "file", Body: parityStrataSetup, Mode: 0o755},
|
|
{Name: "ajean-moe-1.0/setup.py", Type: "file", Body: "# installeur\n", Mode: 0o644},
|
|
{Name: "ajean-moe-1.0/serve/server.py", Type: "file", Body: "# serveur\n", Mode: 0o644},
|
|
}
|
|
goodRel := parityStrataRel{Src: src, Engine: "moteur strata 1.0"}
|
|
mem47 := "MemTotal: 49283072 kB\nMemFree: 1000000 kB\nMemAvailable: 41943040 kB\n"
|
|
mem64 := "MemTotal: 67108864 kB\nMemAvailable: 65011712 kB\n"
|
|
mem16 := "MemTotal: 16777216 kB\nMemAvailable: 12582912 kB\n"
|
|
settings := func(preset string, ctx int, extra string) string {
|
|
return `{"preset":"` + preset + `","ctx":` + jsonInt(ctx) + `,"kv":"int8","spec":4,"vision":false,"helper":true` + extra + `}`
|
|
}
|
|
|
|
var steps []parityStrataStep
|
|
run := func(st parityStrataStep) {
|
|
if st.Machine != "" {
|
|
setMachine(st.Machine)
|
|
}
|
|
if st.Meminfo != "" {
|
|
if err := os.WriteFile(meminfo, []byte(st.Meminfo), 0o644); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
}
|
|
if st.DiskFree != 0 {
|
|
mu.Lock()
|
|
disk = st.DiskFree
|
|
mu.Unlock()
|
|
}
|
|
files := map[string]string{}
|
|
for k, v := range st.Files {
|
|
files[k] = unmask(v)
|
|
}
|
|
parityApplyFiles(t, home, files)
|
|
for k, v := range st.Config {
|
|
if err := SetConfigKey(k, unmask(v)); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
}
|
|
for k, v := range st.State {
|
|
if err := putStr(bkState, k, v); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
}
|
|
if st.Release != nil {
|
|
setRelease(*st.Release)
|
|
}
|
|
mu.Lock()
|
|
actions = nil
|
|
mu.Unlock()
|
|
var lock *tuneLock
|
|
if st.Tune {
|
|
l, err := tuneLockAcquire("web", false)
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
lock = l
|
|
}
|
|
rec := httptest.NewRecorder()
|
|
handlers[st.Path](rec, httptest.NewRequest(st.Method, st.Path, strings.NewReader(unmask(st.Body))))
|
|
if lock != nil {
|
|
lock.release()
|
|
}
|
|
st.Status = rec.Code
|
|
if err := json.Unmarshal([]byte(mask(rec.Body.String())), &st.Out); err != nil {
|
|
t.Fatalf("%s : corps illisible %q", st.Name, rec.Body.String())
|
|
}
|
|
if st.Wait {
|
|
deadline := time.Now().Add(30 * time.Second)
|
|
for lcJobSnapshot(0, false)["running"] == true {
|
|
if time.Now().After(deadline) {
|
|
t.Fatalf("%s : le job ne finit pas", st.Name)
|
|
}
|
|
time.Sleep(10 * time.Millisecond)
|
|
}
|
|
b, _ := json.Marshal(lcJobSnapshot(0, true))
|
|
_ = json.Unmarshal([]byte(mask(string(b))), &st.Job)
|
|
}
|
|
mu.Lock()
|
|
st.Actions = append([]string{}, actions...)
|
|
mu.Unlock()
|
|
st.After = map[string]string{}
|
|
cfg := ReadConfig()
|
|
for _, k := range []string{"ENGINE", "STRATA_CONFIG", "CTX", "STRATA_KV", "STRATA_HELPER"} {
|
|
if v := cfg[k]; v != "" {
|
|
st.After["config/"+k] = mask(v)
|
|
}
|
|
}
|
|
st.Tree = tree()
|
|
steps = append(steps, st)
|
|
}
|
|
|
|
for _, st := range []parityStrataStep{
|
|
{Name: "aucune carte", Machine: "sans-carte", Meminfo: mem47, DiskFree: 400e9, Method: "GET", Path: "/api/strata"},
|
|
{Name: "nvidia-smi en panne", Machine: "smi-en-panne", Method: "GET", Path: "/api/strata"},
|
|
{Name: "nvidia-smi sans carte", Machine: "smi-vide", Method: "GET", Path: "/api/strata"},
|
|
{Name: "carte trop ancienne", Machine: "vieille-carte", Method: "GET", Path: "/api/strata"},
|
|
{Name: "carte trop petite", Machine: "petite-carte", Method: "GET", Path: "/api/strata"},
|
|
{Name: "sans Python", Machine: "sans-python", Method: "GET", Path: "/api/strata"},
|
|
{Name: "Python en panne", Machine: "python-casse", Method: "GET", Path: "/api/strata"},
|
|
{Name: "Python illisible", Machine: "python-muet", Method: "GET", Path: "/api/strata"},
|
|
{Name: "Python trop ancien", Machine: "python-ancien", Method: "GET", Path: "/api/strata"},
|
|
{Name: "sans venv", Machine: "sans-venv", Method: "GET", Path: "/api/strata"},
|
|
{Name: "deux cartes, 47 Go", Machine: "deux-cartes", Method: "GET", Path: "/api/strata"},
|
|
{Name: "trois cartes, la plus récente devant", Machine: "jumelles", Method: "GET", Path: "/api/strata"},
|
|
{Name: "carte d'aide trop ancienne", Machine: "aide-ancienne", Method: "GET", Path: "/api/strata"},
|
|
{Name: "64 Go de RAM", Machine: "deux-cartes", Meminfo: mem64, Method: "GET", Path: "/api/strata"},
|
|
{Name: "16 Go de RAM", Meminfo: mem16, Method: "GET", Path: "/api/strata"},
|
|
{Name: "disque presque plein", Meminfo: mem47, DiskFree: 50e9, Method: "GET", Path: "/api/strata"},
|
|
{Name: "disque juste trop petit", DiskFree: 78e9, Method: "GET", Path: "/api/strata"},
|
|
{Name: "données déjà là", DiskFree: 60e9, Files: map[string]string{
|
|
"strata/data/models/swift-IQ3_XXS/a.gguf": strings.Repeat("x", 2000),
|
|
}, Method: "GET", Path: "/api/strata"},
|
|
{Name: "meminfo sans MemAvailable", DiskFree: 400e9, Meminfo: "MemTotal: 49283072 kB\n", Method: "GET", Path: "/api/strata"},
|
|
|
|
// Réglages.
|
|
{Name: "réglages en GET", Meminfo: mem47, Method: "GET", Path: "/api/strata/settings"},
|
|
{Name: "réglages illisibles", Method: "POST", Path: "/api/strata/settings", Body: `{"ctx":"x"}`},
|
|
{Name: "réglages sans preset", Method: "POST", Path: "/api/strata/settings", Body: settings("", 65536, "")},
|
|
{Name: "réglages, preset avec chemin", Method: "POST", Path: "/api/strata/settings", Body: settings("../x", 65536, "")},
|
|
{Name: "réglages, preset absent", Method: "POST", Path: "/api/strata/settings", Body: settings("absent", 65536, "")},
|
|
{Name: "réglages, preset llama.cpp", Files: map[string]string{"presets/llama.env": "MODEL=/m.gguf\n"},
|
|
Method: "POST", Path: "/api/strata/settings", Body: settings("llama", 65536, "")},
|
|
{Name: "réglages hors limites", Files: map[string]string{"presets/S.env": "# NAME=Strata S\nENGINE=strata\nSTRATA_HELPER_GPU=0\nCTX=131072\n"},
|
|
Method: "POST", Path: "/api/strata/settings", Body: settings("S", 1000, "")},
|
|
{Name: "réglages, experts inconnus", Method: "POST", Path: "/api/strata/settings", Body: settings("S", 65536, `,"experts":"nvme"`)},
|
|
{Name: "réglages d'un preset inactif", Method: "POST", Path: "/api/strata/settings", Body: settings("S", 65536, `,"experts":"disk"`)},
|
|
{Name: "réglages du preset actif", Config: map[string]string{"ENGINE": "strata", "STRATA_HELPER_GPU": "0", "CTX": "65536",
|
|
"STRATA_KV": "int8", "STRATA_SPEC": "4", "STRATA_VISION": "0", "STRATA_HELPER": "1", "STRATA_EXPERTS": "disk"},
|
|
Method: "POST", Path: "/api/strata/settings", Body: settings("S", 98304, "")},
|
|
{Name: "réglages du preset actif pendant une optimisation", Tune: true,
|
|
Method: "POST", Path: "/api/strata/settings", Body: settings("S", 98304, "")},
|
|
|
|
// Installation.
|
|
{Name: "installation en GET", Method: "GET", Path: "/api/strata/install"},
|
|
{Name: "installation illisible", Method: "POST", Path: "/api/strata/install", Body: `[`},
|
|
{Name: "installation d'un quant inconnu", Method: "POST", Path: "/api/strata/install", Body: `{"family":"swift","quant":"Q2_0"}`},
|
|
{Name: "installation pendant une optimisation", Tune: true, Method: "POST", Path: "/api/strata/install", Body: `{"family":"swift","quant":"IQ3_XXS"}`},
|
|
{Name: "installation sur une machine sans carte", Machine: "sans-carte", Method: "POST", Path: "/api/strata/install",
|
|
Body: `{"family":"swift","quant":"IQ3_XXS"}`, Wait: true},
|
|
{Name: "installation qui ne tient pas", Machine: "deux-cartes", Meminfo: mem16, Method: "POST", Path: "/api/strata/install",
|
|
Body: `{"family":"qwen","quant":"IQ3_S"}`, Wait: true},
|
|
{Name: "release introuvable", Meminfo: mem47, Release: &parityStrataRel{Status: 404, Src: src, Engine: "x"},
|
|
Method: "POST", Path: "/api/strata/install", Body: `{"family":"swift","quant":"IQ3_XXS"}`, Wait: true},
|
|
{Name: "moteur altéré", Release: &parityStrataRel{Src: src, Engine: "moteur strata 1.0", BadSHA: true},
|
|
Method: "POST", Path: "/api/strata/install", Body: `{"family":"swift","quant":"IQ3_XXS"}`, Wait: true},
|
|
{Name: "installation", Release: &goodRel, Method: "POST", Path: "/api/strata/install",
|
|
Body: `{"family":"swift","quant":"IQ3_XXS"}`, Wait: true},
|
|
{Name: "après l'installation", Method: "GET", Path: "/api/strata"},
|
|
{Name: "mode lancé connu, modèles inactifs", State: map[string]string{"strata_experts_mode": "drop"}, Files: map[string]string{
|
|
"presets/STRATA FLASH NEXT CLASSIQUE IQ2_XS.env": "# NAME=Classique IQ2_XS\nENGINE=strata\nSTRATA_CONFIG={{HOME}}/strata/ajean-moe-1.0/strata-swift-iq3_xxs.json\n" +
|
|
"STRATA_MAIN_GPU=1\nSTRATA_HELPER_GPU=-1\nSTRATA_EXPERTS=disk\n",
|
|
"presets/STRATA FLASH NEXT CLASSIQUE Q2_0.env": "ENGINE=moe\nSTRATA_CONFIG={{HOME}}/strata/ajean-moe-1.0/strata-swift-iq3_xxs.json\n" +
|
|
"STRATA_MAIN_GPU=0\nSTRATA_HELPER_GPU=1\nSTRATA_HELPER=0\nSTRATA_VISION=0\nSTRATA_EXPERTS=ram\n",
|
|
}, Method: "GET", Path: "/api/strata"},
|
|
{Name: "experts.bin et moteur du paquet", Files: map[string]string{
|
|
"strata/data/packs/swift-iq3_xxs/experts.bin": "x",
|
|
}, State: map[string]string{"strata_experts_mode": ""}, Method: "GET", Path: "/api/strata"},
|
|
{Name: "RAM juste sous le seuil du mode hors RAM", Meminfo: "MemTotal: 49283072 kB\nMemAvailable: 43164063 kB\n",
|
|
Method: "GET", Path: "/api/strata"},
|
|
{Name: "RAM au-dessus du seuil du mode hors RAM", Meminfo: "MemTotal: 49283072 kB\nMemAvailable: 43457032 kB\n",
|
|
Method: "GET", Path: "/api/strata"},
|
|
{Name: "mode hors RAM coupé dans le preset", Files: map[string]string{
|
|
"presets/STRATA FLASH NEXT SWIFT 1.5 IQ3_XXS.env": "# NAME=Qwen3.8 Flash Next Swift 1.5 IQ3_XXS (Strata)\nENGINE=strata\n" +
|
|
"STRATA_CONFIG={{HOME}}/strata/ajean-moe-1.0/strata-swift-iq3_xxs.json\nSTRATA_MAIN_GPU=1\nSTRATA_HELPER_GPU=0\n" +
|
|
"STRATA_VISION=1\nSTRATA_EXPERTS=auto\nSTRATA_DROP=0\nSTRATA_KV=fp16\nSTRATA_KV_RESIDENT=32768\nSTRATA_SPEC=3\nCTX=131072\nREASONING=off\n",
|
|
}, Method: "GET", Path: "/api/strata"},
|
|
{Name: "moteur d'un autre paquet", Meminfo: mem47, Files: map[string]string{
|
|
"strata/ajean-moe-1.0/engine-cuda12/.loki-engine": "ajean-moe-engine-0.9-linux-x64-cuda12.zip\n",
|
|
}, Method: "GET", Path: "/api/strata"},
|
|
{Name: "réinstallation, moteur téléchargé périmé", Files: map[string]string{
|
|
"strata/dl/ajean-moe-engine-1.0-linux-x64-cuda12.zip": "ancien moteur",
|
|
}, Method: "POST", Path: "/api/strata/install", Body: `{"family":"swift","quant":"IQ3_XXS"}`, Wait: true},
|
|
{Name: "installeur en échec", Method: "POST", Path: "/api/strata/install", Body: `{"family":"qwen","quant":"Q2_0"}`, Wait: true},
|
|
{Name: "installeur sans configuration", Method: "POST", Path: "/api/strata/install", Body: `{"family":"qwen","quant":"IQ2_XS"}`, Wait: true},
|
|
{Name: "configuration de lancement disparue", Files: map[string]string{
|
|
"strata/ajean-moe-1.0/strata-swift-iq3_xxs.json": "-",
|
|
}, Method: "GET", Path: "/api/strata"},
|
|
{Name: "configuration de lancement illisible", Files: map[string]string{
|
|
"strata/ajean-moe-1.0/strata-swift-iq3_xxs.json": "[1]",
|
|
}, Method: "GET", Path: "/api/strata"},
|
|
} {
|
|
run(st)
|
|
}
|
|
|
|
got, err := parityJSON(map[string]any{"machines": machines, "setup": parityStrataSetup, "steps": steps})
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
if os.Getenv("LOKI_PARITY_UPDATE") == "1" {
|
|
if err := os.MkdirAll(filepath.Dir(parityStrataFile), 0o755); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
if err := os.WriteFile(parityStrataFile, got, 0o644); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
return
|
|
}
|
|
want, err := os.ReadFile(parityStrataFile)
|
|
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("Strata ne produit plus le journal rangé dans %s.\n"+
|
|
"Si c'est voulu : LOKI_PARITY_UPDATE=1 go test ./internal/loki -run TestPariteStrata,\n"+
|
|
"puis cargo test --test strata dit si le port Rust suit.\n--- obtenu ---\n%s", parityStrataFile, got)
|
|
}
|
|
}
|
|
|
|
func jsonInt(n int) string {
|
|
b, _ := json.Marshal(n)
|
|
return string(b)
|
|
}
|