Files
Loki/internal/loki/backend_engine_oci_net_test.go
Claude b378311873 Moteur : mettre à jour llama.cpp sans reconstruire l'image
llama.cpp publie plusieurs versions par jour ; l'image de Loki ne se
reconstruit qu'à une mise à jour de Loki. Le moteur y était donc figé à
la date du dernier build, et le rattraper imposait un rebuild complet de
2,6 Go pour un composant qui en pèse 170 Mo. Le panneau « Moteur » ne le
disait même pas : il annonçait « rien à mettre à jour ici ».

Réglages → Moteur affiche maintenant la version qui tourne (bXXXXX et son
commit, lus dans la bannière du binaire — jusqu'ici invisibles ailleurs
que dans le journal du moteur) et la met à jour en un clic.

Où le moteur est pris. Pas dans les releases GitHub de llama.cpp : elles
ne contiennent AUCUN binaire CUDA pour Linux, et le mode « précompilé »
retomberait sur Vulkan, donc sur une régression pour une carte NVIDIA. La
seule distribution CUDA/Linux officielle et précompilée est l'image de
conteneur — celle-là même dont l'image de Loki hérite. On lit son
manifeste OCI et on ne télécharge que les couches qui portent /app, en
descendant du sommet : le runtime CUDA (2 Go) et la base système sont
déjà là. S'arrêter au binaire ne suffit pas — llama.cpp le range dans une
couche et ses .so dans la précédente — d'où une règle d'arrêt sur
« binaire + libggml-base + libllama », et un garde-fou de taille qui
interdit de descendre jusqu'au runtime.

Le moteur atterrit dans /data/engine/<version>/, donc sur le volume de
données : il survit à un docker compose pull. La variante (CUDA, Vulkan,
SYCL, MUSA, CPU) est déduite des backends ggml posés à côté du moteur
courant — le conteneur ne sait pas de quelle image il vient, et faire
retenir « server-cuda » à l'utilisateur serait un piège.

Le risque, et ce qui le couvre. La mise à jour apporte llama.cpp, pas le
runtime CUDA, qui reste celui de l'image : un llama.cpp compilé pour un
CUDA plus récent ne chargerait pas son backend GPU. Le symptôme serait
silencieux — tout marche, mais sur le processeur. Le nouveau moteur est
donc lancé à blanc avant toute bascule ; il est refusé s'il ne démarre
pas, ET s'il ne voit plus aucune carte alors que le moteur courant en
voyait. Dans les deux cas le moteur courant n'est pas touché, et celui de
l'image reste intact : « revenir au moteur de l'image » y ramène en un
clic, sans réseau.

Vérifié de bout en bout contre le vrai ghcr.io (166 Mo, 7 s, toutes les
bibliothèques et leurs liens de version présents) et, pour les chemins
d'échec, contre un faux registre.

LOKI_OCI_REGISTRY permet de viser un miroir quand ghcr.io n'est pas
joignable.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01LPyFxVHNAN9u5pVzSYMwjd
2026-08-16 20:43:47 +00:00

54 lines
1.8 KiB
Go

package loki
// Vérification contre le VRAI registre. Ignoré par défaut (la CI ne doit pas
// dépendre du réseau ni télécharger 170 Mo) :
//
// LOKI_NET_TEST=1 go test ./internal/loki -run TestMoteurReelDepuisGhcr -v
//
// Ce test existe parce que le faux registre ne peut pas garantir une chose : que
// la DISPOSITION de l'image amont est toujours celle qu'on suppose. Si llama.cpp
// range un jour son moteur autrement — sous-dossier, couche gigantesque, autre
// jeu de bibliothèques — c'est ici qu'on le verra, et pas chez l'utilisateur.
import (
"os"
"path/filepath"
"testing"
)
func TestMoteurReelDepuisGhcr(t *testing.T) {
if os.Getenv("LOKI_NET_TEST") == "" {
t.Skip("test réseau — LOKI_NET_TEST=1 pour l'exécuter")
}
if os.Getenv("LOKI_HOME") == "" {
t.Setenv("LOKI_HOME", t.TempDir())
}
build, tag, err := engineOCILatest("server-cuda")
if err != nil {
t.Fatal(err)
}
t.Logf("dernier build publié : %d → %s", build, tag)
if build == 0 {
t.Error("numéro de build absent de l'annotation de l'image")
}
bin, err := engineOCIInstall(tag, func(s string) { t.Log(s) }, func(s string) { t.Log("▶ " + s) })
if err != nil {
t.Fatal(err)
}
dir := filepath.Dir(bin)
// Ce que l'éditeur de liens réclamera au lancement. Un seul manquant et le
// moteur meurt sur « cannot open shared object file ».
for _, n := range []string{"libllama.so", "libggml.so", "libggml-base.so", "libggml-cuda.so", "libmtmd.so"} {
if _, err := os.Stat(filepath.Join(dir, n)); err != nil {
t.Errorf("%s : %v", n, err)
}
}
// Et le binaire doit savoir dire sa version : c'est ce qui pilote la
// comparaison « installé vs publié » de tout le panneau.
if got, commit := engineBuildOf(bin); got != build {
t.Errorf("le moteur installé annonce le build %d (%s), attendu %d", got, commit, build)
}
}