mirror of
https://github.com/R0m1k3/Loki.git
synced 2026-10-20 21:48:47 +02:00
Slots : SLOT_PERSIST garde l'état de la discussion à la bascule de preset, en opt-in
Passer de A à B puis revenir à A relance llama-server deux fois, et la conversation de A est recalculée en entier au retour. Avec SLOT_PERSIST=on (off par défaut), Loki demande au moteur d'écrire l'état du slot 0 juste avant la bascule, et le recharge au retour avant le premier message de la discussion — seulement si tout concorde, sinon calcul normal, en silence. llama.cpp ne vérifie à la relecture que types et tailles : rien ne dit quel build ni quels réglages ont calculé ce cache. La clé de validité est donc l'empreinte de tout ce qui touche au calcul, prise par « loki serve » au lancement : ligne de commande complète (modèle, CTX, types KV, NGL, lots, gabarit, SPEC, EXTRA_ARGS…), LLAMA_ARG_*/GGML_*/CUDA_*, taille et date du modèle et de ses tranches, du projecteur, du brouillon, du binaire et de ses bibliothèques. Pas de cas « mise à jour du moteur » : elle change la clé. - --slot-save-path posé aussi pour SLOT_PERSIST accepté, sous les mêmes conditions de sécurité que l'isolation du lot 1 (boucle locale ou clé d'API) ; refusé avec le décodage spéculatif (le save ne garde pas le brouillon ; /slots speculative revérifié à chaque fois), des poids sur CPU, un --slot-save-path à la main ; la ligne reste alors celle d'avant - sauvegarde depuis l'interface ou une tâche, avant l'arrêt : le slot doit porter un tour de la discussion (tampon d'engineMarkMain), ≥ 4096 jetons, ≤ 8 Gio estimés, double de place libre ; nom provisoire, renommé seulement si aucune requête n'est partie pendant l'écriture - rechargement avant la première requête de la discussion (ou son préchauffage) : même clé de moteur, même discussion, slot 0 vierge (/slots sans id_task) ; une seule tentative, fichier retiré ; toute erreur = calcul normal - deux fichiers au plus ; clé retirée = tout effacé au lancement suivant ; SLOT_PERSIST préservé à la bascule comme HOST (sinon B effaçait l'état de A) - README : SIDE_SLOT, le cache KV plein ne déclenche jamais de compaction - tests : plan et refus, ligne inchangée, empreinte (clé d'API exclue, fichiers et réglages inclus), rotation, séquence save puis restore sur un faux moteur, refus à la sauvegarde et au rechargement, autre clé, défaut sans aucun appel Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
1 parent
7a57322a62
commit
28ba44eb46
12 files changed
+1133
-23
No files matched your search
@@ -450,30 +450,29 @@ func loopbackHost(h string) bool {
|
||||
// restore, qui écrivent des Gio sur le disque, à quiconque joint le moteur. On
|
||||
// ne le pose donc que si le moteur est fermé aux autres (boucle locale, ou clé
|
||||
// d'API exigée), que le dossier existe (le moteur refuse de démarrer sinon), et
|
||||
// que l'isolation servira : cache actif, CACHE_ISOLATE pas à off. Les proxys de
|
||||
// Loki refusent de leur côté tout /slots qui n'est pas une lecture.
|
||||
// qu'il servira : à l'isolation (cache actif, CACHE_ISOLATE pas à off), ou à
|
||||
// SLOT_PERSIST accepté (backend_serve_persist.go). Les proxys de Loki refusent
|
||||
// de leur côté tout /slots qui n'est pas une lecture.
|
||||
func slotSaveArgs(cfg map[string]string, extra []string, si serveSysInfo) []string {
|
||||
if si.SlotDir == "" || !strings.Contains(si.Help, "--slot-save-path") ||
|
||||
hasAnyFlag(extra, "--slot-save-path") || !cacheIsolationOn(cfg) || cacheRAMOff(cfg, extra, si.ArgEnv) {
|
||||
if si.SlotDir == "" || !strings.Contains(si.Help, "--slot-save-path") || hasAnyFlag(extra, "--slot-save-path") {
|
||||
return nil
|
||||
}
|
||||
host := flagValue(extra, "--host")
|
||||
if host == "" {
|
||||
host = cfg["HOST"]
|
||||
persist, _ := slotPersistPlan(cfg, extra, si)
|
||||
if !persist && (!cacheIsolationOn(cfg) || cacheRAMOff(cfg, extra, si.ArgEnv)) {
|
||||
return nil
|
||||
}
|
||||
if host == "" {
|
||||
host = "0.0.0.0"
|
||||
}
|
||||
if !loopbackHost(host) && si.APIKey == "" {
|
||||
if !slotSaveSafe(cfg, extra, si) {
|
||||
return nil
|
||||
}
|
||||
return []string{"--slot-save-path", si.SlotDir}
|
||||
}
|
||||
|
||||
// prepareSlotDir crée LOKI_HOME/slots (0700) et le vide : Loki n'y sauve jamais
|
||||
// rien, un fichier présent ne peut venir que d'un appel qui n'aurait pas dû
|
||||
// passer. Vide = dossier indisponible, le drapeau ne sera pas posé.
|
||||
func prepareSlotDir(home string) string {
|
||||
// prepareSlotDir crée LOKI_HOME/slots (0700) et le vide : un fichier présent ne
|
||||
// peut venir que d'un appel qui n'aurait pas dû passer. Seule exception, avec
|
||||
// keepPersist (SLOT_PERSIST demandé) : les états que Loki y a lui-même gardés
|
||||
// à la bascule de preset, au nom reconnaissable. Vide = dossier indisponible,
|
||||
// le drapeau ne sera pas posé.
|
||||
func prepareSlotDir(home string, keepPersist bool) string {
|
||||
dir := filepath.Join(home, "slots")
|
||||
if err := os.MkdirAll(dir, 0o700); err != nil {
|
||||
return ""
|
||||
@@ -483,7 +482,7 @@ func prepareSlotDir(home string) string {
|
||||
}
|
||||
if entries, err := os.ReadDir(dir); err == nil {
|
||||
for _, e := range entries {
|
||||
if e.Type().IsRegular() {
|
||||
if e.Type().IsRegular() && !(keepPersist && slotPersistFileRe.MatchString(e.Name())) {
|
||||
_ = os.Remove(filepath.Join(dir, e.Name()))
|
||||
}
|
||||
}
|
||||
|
||||
Reference in new issue
Block a user