Files
Loki/docs/superpowers/specs/2026-10-16-rust-migration-lot28-optimiseur-design.md
T
Claude f6c191922f Lot 28 : optimiseur tenu par la façade (LOKI_TUNE=rust)
Avec LOKI_TUNE=rust (expérimental, Unix seulement), la façade sert
/api/tune, /api/tune/status, /api/tune/cancel, /api/tune/apply et
/api/tune/last, et déroule l'optimisation sans perte (rust/loki-rs/src/tune) :
vrai moteur arrêté, essais composés à blanc puis lancés par les « loki
serve » du Go (LOKI_TUNE_TRIAL), chacun dans son groupe de processus,
mesurés par le bench complet à profondeur fixe, retenus au-delà du
bruit ; vrai moteur relancé. Rien n'est écrit sans un clic : copie du
preset, ou le preset lui-même — redémarrage, sonde, et ancienne version
rétablie si elle échoue.

- tune/core.rs : la partie pure (EXTRA_ARGS, réglages, essais de chaque
  étape, garde-fous, diff et réécriture du preset, score) ;
- tune/lock.rs : le verrou tune.lock au format du Go, identité des
  processus comme procIdentity (la reprise du Go reconnaît la façade
  vivante, et écarte son verrou si elle meurt), ménage d'un verrou
  périmé (essai orphelin arrêté, application interrompue défaite) ;
- tune/job.rs : routes, job, essais et application.

Le verrou de génération reste celui du Go, pris par le bail du moteur au
motif « tune » (refus de l'optimiseur pour les autres, stop du chat qui
annule). Le Go prête /internal/v1/tune/context (bench, génération, jobs
du mode code, état du moteur, télémétrie) et le démarrage ou l'arrêt du
moteur sans le refus de l'optimiseur (service/action « os »). Hors
Unix, la façade passe LOKI_TUNE vide au Go, qui garde l'optimiseur.

serde_json lit désormais les flottants à l'arrondi exact
(float_roundtrip), comme le Go.

Parité : parity_tune_test.go tire du Go un journal (partie pure, contenu
du verrou, résultat enregistré, vérification après application) rejoué
par tests/tune_core.rs. tests/tune.rs déroule de vraies optimisations, le
binaire de test jouant « loki serve » ; les tests du verrou du Go sont
portés ; TestFacadeTune côté Go.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_016tcGfJWefW5kUGoC2wL8cv
2026-10-16 01:06:31 +00:00

7.5 KiB

Lot 28 — Optimiseur tenu par la façade

Contexte

L'optimiseur sans perte (backend_tune*.go) cherche des réglages du moteur plus rapides sans rien changer à ce que le modèle calcule : même contexte, mêmes slots, mêmes types de cache, même échantillonnage. Il procède ainsi :

  1. Il arrête le vrai moteur.
  2. Il lance chaque essai sur une COPIE de la configuration :
    • posée dans LOKI_HOME/tune/run/<essai>/config.env, jamais config.env ;
    • composée par « loki serve » à blanc ;
    • puis lancée sur 127.0.0.1 et un port libre.
  3. Il mesure chaque essai par le bench complet, à une profondeur fixe.
  4. Il garde ce qui gagne au-delà du bruit.
  5. Il relance le vrai moteur.

Rien n'est écrit dans un preset sans un geste de l'utilisateur : dans une copie, ou dans le preset lui-même — moteur redémarré, sondé, et ancienne version rétablie si la sonde échoue.

Cinq routes :

  • POST /api/tune lance (202) ;
  • GET /api/tune/status dit la phase, les essais, l'ETA, puis le résultat ;
  • POST /api/tune/cancel annule ;
  • POST /api/tune/apply écrit le résultat, sur un clic (copy ou preset) ;
  • GET /api/tune/last rend le dernier résultat d'un preset, et si le preset ou le moteur ont changé depuis.

Le moteur, pendant une optimisation, appartient aux essais. Le verrou est un fichier de LOKI_HOME (tune.lock), créé de façon exclusive, que tout le monde lit :

  • le Go, dans tous ses refus (tuneGuard) ;
  • « loki tune » en ligne de commande ;
  • désormais la façade.

Le lot

Avec LOKI_TUNE=rust (expérimental, Unix seulement), la façade (rust/loki-rs/src/tune) sert les cinq routes et déroule l'optimisation :

  • tune/core.rs : la partie pure, portée à l'octet près :
    • découpe et réécriture d'EXTRA_ARGS ;
    • réglages par clé ou par drapeau ;
    • essais de chaque étape selon la machine et les cases cochées ;
    • garde-fous : cœur de la ligne, dénature, clés permises ;
    • diff et réécriture du preset ;
    • journal d'un essai ;
    • score et départage ;
  • tune/lock.rs : le verrou, au format du Go :
    • contenu du fichier, clé de prise, binaires comparés par leur nom ;
    • identité d'un processus comme procIdentity l'écrit : champ 22 de /proc/<pid>/stat, préfixé de l'identifiant du démarrage ; ps ailleurs ;
    • écriture d'un bloc ;
    • rendu neutre avant retrait ;
    • ménage par déplacement-relecture ;
  • tune/job.rs : les routes, le job, les essais et l'application.

La reprise reste au Go. Sa reprise (tuneRecoverWatch, toutes les 30 s) lit le verrou de la façade et la reconnaît vivante, par la même identité. Si la façade meurt en plein essai, le Go écarte son verrou comme celui d'un « loki tune » tué :

  • l'essai orphelin est arrêté (identité complète vérifiée) ;
  • une application interrompue est défaite ;
  • le moteur est relancé s'il tournait.

La façade fait le même ménage quand elle trouve un verrou périmé en prenant le sien.

Les essais restent des « loki serve » du Go, lancés par la façade avec LOKI_TUNE_TRIAL (et LOKI_TUNE_DRYRUN à blanc), dans leur propre groupe de processus. Le groupe est arrêté en sortie, quoi qu'il arrive. L'essai est noté dans le verrou avant de charger quoi que ce soit.

Le verrou de génération reste celui de la conversation du Go. La façade le prend par le bail du moteur, avec le motif tune :

  • le Go note alors benching et donne aux autres le refus de l'optimiseur ;
  • changer de discussion ne rompt pas le bail ;
  • un stop du chat, un Reset ou un bail perdu annulent l'optimisation.

Le Go prête à la façade ce qu'il est seul à voir (/internal/v1/tune/context) :

  • le refus du processus web : un benchmark (le sien ou celui de la façade), une génération, des jobs d'arrière-plan du mode code ;
  • l'état du vrai moteur (engineNeedsStop) ;
  • la télémétrie, d'où viennent les poids d'un tour type.

Il prête aussi le démarrage et l'arrêt du moteur sans le refus de l'optimiseur (/internal/v1/service/action, os), puisque c'est la façade qui tient le verrou. Si la façade pilote elle-même le moteur (LOKI_ENGINE=rust), elle passe par son superviseur.

Le preset est réécrit et appliqué par les fonctions des presets (save_applying, mode apply : la configuration, sans redémarrage). Le redémarrage, la sonde et le retour arrière sont ceux de l'optimiseur.

La ligne de commande (loki tune) reste au Go.

Hors Unix, la façade ne tient pas l'optimiseur. Elle passe alors LOKI_TUNE vide au Go, qui le garde et ne relaie rien.

Parité

internal/loki/parity_tune_test.go tire du Go un journal (tests/parity_tune/core.json) rejoué par tests/tune_core.rs. Il couvre :

  • toute la partie pure ;
  • le verrou : contenu du fichier octet pour octet, clé de prise, comparaison des binaires ;
  • le résultat tel qu'enregistré et montré (listes vides écrites null comme une tranche nil du Go) ;
  • la vérification après application (la configuration est-elle l'essai retenu ?).

tests/tune.rs déroule de vraies optimisations, essais compris : le binaire de test joue « loki serve ».

  • À blanc, il écrit la ligne de commande de l'essai.
  • Pour de vrai, il sert un faux llama-server dont le prefill est d'autant plus rapide que le micro-lot est petit.

Les tests couvrent :

  • une optimisation entière :
    • bail « tune » et verrou au nom de la façade ;
    • vrai moteur arrêté puis relancé sans le refus de l'optimiseur, dictée coupée ;
    • quatre essais composés, lancés, mesurés, l'un retenu ;
    • copies de configuration sur une adresse privée ;
    • résultat enregistré par preset et rendu par /api/tune/last ;
  • l'application :
    • dans une copie (rien d'autre ne bouge) ;
    • dans le preset (réécrit, appliqué, moteur redémarré et sondé) ;
    • refusée sur un preset changé depuis la mesure ;
  • une sonde qui échoue (contexte perdu) : preset et configuration rétablis, moteur redémarré dessus, sauvegarde faite avant d'écrire ;
  • les essais refusés : à la composition, au chargement (mémoire insuffisante) ; la référence qui ne se compose pas ; la relance qui échoue, dite dans l'erreur ;
  • l'annulation, par la route et par le stop du chat ;
  • les refus d'entrée, avec les messages du Go :
    • preset externe, Strata, aucun preset actif ;
    • jobs du mode code, moteur occupé, génération en cours ;
    • optimisation d'un autre processus (et elsewhere dans le statut) ;
  • un verrou périmé :
    • l'essai orphelin arrêté seulement si son identité concorde ;
    • une application interrompue défaite ;
  • les routes sans la façade.

Les tests du verrou du Go sont portés (tune/tests.rs) :

  • prise, refus, essai noté, rendu ;
  • propriétaire mort ;
  • verrou à notre PID qui n'est pas l'un des nôtres ;
  • illisible neuf ou vieux ;
  • ménage jamais sur un verrou vivant ;
  • binaire remplacé ;
  • identité d'un processus ;
  • arrêt du groupe.

Côté Go, TestFacadeTune vérifie :

  • les cinq routes partent à la façade, et elles seules ;
  • le bail « tune » : refus, bascule de discussion, libération ;
  • le contexte : au repos, pendant l'optimisation, pendant un bench ;
  • le démarrage et l'arrêt sans le refus de l'optimiseur.

Écarts assumés

  • Une sortie à blanc qui échoue est lue dans un fichier où vont les deux sorties (comme CombinedOutput), mais une erreur de lecture de launch.json ou d'une requête garde le texte de Rust.
  • Un niveau de raisonnement appris pendant les essais n'est pas transmis au chat du Go quand le Go tient la conversation.