Vigilant Inference Governor

ADR-0045: autotune stellt ein — innerhalb der Vertraege

Status: Akzeptiert · 2026-09-15 Betrifft: cli/autotune (Schritt tune), vig-bench/vig-fit (VIG_FIT_ARMS=governed); ADR-0002 (Pipelining), ADR-0004 (Slots), ADR-0034 (Sicherheitsmarge), ADR-0038 (Kalibrierung an der Karte), ADR-0041 (Versorgungsschutz), ADR-0044 (Qualifikation beim Anwender) Ausloeser: Produktentscheidung vom 15.09.2026: autotune soll den Governor auf der Last des Anwenders einstellen und nicht nur beurteilen.

Kontext

vig autotune fuehrte bis hierher vier Schritte zusammen: discover, measure, fit, check (ADR-0044). Gemessen wurde die Maschine, geurteilt wurde ueber den Governor — aber ueber den Governor in der Einstellung, die gerade in der Datei stand. Die Stellgroessen, die ueber seine Wirkung entscheiden, waehlt bis heute der Mensch:

Stellgroesse Voreinstellung Was sie tut
backend.pipelining_depth 1 zusaetzliche Kredite je Slot (ADR-0002)
backend.protect_supply aus Look-ahead haelt Hintergrund auch fuer die Versorgung zurueck (ADR-0041)
backend.margin_learning aus Planung kalibriert sich an der Karte (ADR-0038)
backend.safety_margin_percent 110 Marge auf die Laufzeitprognose (Spec 13.2, ADR-0034)

Welche Einstellung auf einer Last traegt, haengt an Hardware, Backend und Modellen — also genau an dem, was nach ADR-0044 nur beim Anwender gemessen werden kann. Wer die Stellgroessen nicht kennt, bekommt die Voreinstellung, und vig-fit sagt ihm dann, ob sich die lohnt. Das ist eine ehrliche Antwort auf die falsche Frage.

Ein Werkzeug, das einstellt, bringt zwei neue Gefahren mit. Es kann auf Rauschen einstellen: Zwei Laeufe derselben Einstellung liegen nicht auf dasselbe Promille genau, und wer jede kleinere Differenz behaelt, schreibt Zufall als Konfiguration fest. Und es kann an Groessen drehen, die ihm nicht gehoeren: Ein Strom, dessen Periode verdoppelt wird, verliert weniger Takte — und der Anwender bekommt ein besseres Ergebnis fuer eine Zusage, die er nie gegeben hat.

Entscheidung

autotune bekommt den Schritt tune zwischen measure und fit. Die Reihenfolge ist der Kern: fit vergleicht danach den eingestellten Governor mit dem direkten Weg, also den, der in Betrieb geht. Und fit misst dafuer neu, mit beiden Armen und an seinen eigenen Lastpunkten. Die Einstellung, die in tune auf einer verrauschten Messung gewonnen hat, wird damit nicht durch dieselbe Messung bestaetigt, die sie ausgewaehlt hat.

Wie bewertet wird

Jede Fassung laeuft durch vig-fit mit VIG_FIT_ARMS=governed: nur der Governor-Arm, 10 s je Lastpunkt, bei 100, 110 und 125 % Last (--quick: 5 s, 110 und 125 %). 90 % fehlt absichtlich — unterhalb der Saettigung verliert keine Einstellung etwas, und ein Punkt, an dem alle Fassungen null Promille haben, kostet Zeit, ohne zu unterscheiden. Den direkten Arm braucht der Vergleich zweier Einstellungen desselben Governors nicht; vig-fit sagt im JSON (arms: governed, direkte Felder null) und im Satz, dass das eine Bewertung ohne Vergleich ist und kein Urteil.

Zielgroesse

Aus den Zellen, je Promille unabgedeckter Abtastungen aus Verbrauchersicht:

Nachtrag 15.09., nach dem ersten Lauf auf Hardware: Die erste Fassung nahm je Lastpunkt den schlechtesten nicht geschuetzten Strom. Auf dem Laptop stand darin in allen sechs Fassungen der unteilbare 95-ms-Block bei 1000 ‰ — er passt neben einer 33-ms-Periode nie (ADR-0012) —, waehrend pose und depth zwischen 0 und 3 ‰ lagen. Als Maximum haette dieser eine unerfuellbare Strom jede Verbesserung der anderen verdeckt; das Tuning konnte dort gar nichts gewinnen. Der Mittelwert laesst ihn mitzaehlen, aber nicht alles andere zudecken.

Die geschuetzten Stroeme gehen vor. Das ist keine Wahl dieses Werkzeugs, sondern die Policy, die der Betreiber mit class: protected getroffen hat.

Rauschschwelle und Entscheidungsregel

Eine Fassung wird behalten, wenn sie die Konfigurationspruefung besteht (Config::diagnose im Prozess, vig-fit nicht mit Exitcode 2) und

Nie behalten wird eine Fassung mit protected_worst ueber dem der unverstellten Fassung. Aus den beiden Regeln folgt das bereits; es steht trotzdem als erste Pruefung im Code, weil es die Zusage ist, auf die es ankommt. Gerechnet wird ganzzahlig mit checked_sub: Liegt der beste Wert unter der Schwelle, gibt es dort nichts mehr zu gewinnen.

Fuenf und zehn Promille und „ein Zehntel" sind gesetzte Grenzen, keine gemessene Streuung. Die Streuung einer Bewertung auf der Hardware des Anwenders kennt dieses Werkzeug nicht, und sie zu messen hiesse, jede Fassung mehrfach zu fahren. Die Grenzen sind deshalb bewusst grob: lieber eine echte, kleine Verbesserung verpassen als Rauschen festschreiben. Der relative Teil waechst mit, weil ein Strom, der 400 ‰ verliert, staerker streut als einer, der 10 ‰ verliert.

Warum Koordinatensuche und kein Gitter

Die Suche geht einmal ueber die Stellgroessen, in fester Reihenfolge (Tiefe, Versorgungsschutz, Kalibrierung, Marge), und stellt jede ausgehend von der bisher besten Fassung um: pipelining_depth 0 ↔ 1 (eine hoehere Tiefe wird mit 0 verglichen), protect_supply umgekehrt, margin_learning aus ↔ Voreinstellungen (was margin_learning: {} ergibt; nicht versucht, wenn prediction: active gesetzt ist, weil die Pruefung beides zusammen ablehnt), safety_margin_percent ± 15 in den Grenzen 100 bis 300 — aus 110 werden 125 und 100. Das sind im ueblichen Fall sechs Bewertungen: die unverstellte und fuenf Umstellungen.

Ein vollstaendiges Gitter ueber dieselben Werte haette 2 × 2 × 2 × 3 = 24 Fassungen. Bei rund 40 s je Bewertung sind das 16 Minuten statt vier — mehr als die Haelfte der zugesagten halben Stunde (ADR-0044) fuer einen einzigen Schritt. Und jede zusaetzliche Fassung ist ein zusaetzlicher Versuch, auf Rauschen hereinzufallen: Wer 24 verrauschte Messungen vergleicht und die beste nimmt, nimmt mit hoher Wahrscheinlichkeit eine, die Glueck hatte.

Der Preis ist ausgesprochen: Wechselwirkungen zwischen Stellgroessen werden nicht gefunden, und das Ergebnis haengt von der Reihenfolge ab. Eine Marge, die erst mit eingeschalteter Kalibrierung hilft, sieht ein Durchgang, der die Kalibrierung vorher verworfen hat, nicht. Eine Stellgroesse wird nicht noch einmal versucht, nachdem sich eine spaetere geaendert hat. Der Bericht sagt deshalb „die beste von N versuchten Einstellungen" und nicht „das Optimum", und er listet jede versuchte Fassung mit ihren Zahlen und dem Grund.

Was nie eingestellt wird

Was aus ADR-0038 und ADR-0041 wird

Beide ADRs nennen das Einschalten ihrer Stellgroesse eine Handlung des Betreibers und keine, die die Policy selbst trifft. Das gilt weiter: Der Governor schaltet zur Laufzeit nichts davon selbst ein. tune ist ein Werkzeug, das der Betreiber startet und das eine Datei schreibt; in Betrieb geht sie erst, wenn er sie in Betrieb nimmt. Der Bericht nennt jede Umstellung gegen die gemessene Fassung einzeln. Den Preis, den ADR-0041 fuer den Versorgungsschutz nennt — Hintergrundfortschritt —, fuehrt die Zielgroesse als background_mean mit und der Bericht daneben: Eine Fassung, die die geschuetzten Stroeme um mehr als die Schwelle besser versorgt, wird auch dann behalten, wenn die nachrangigen dafuer mehr verlieren. Das ist die Rangfolge aus class: protected, und sie steht mit beiden Zahlen im Bericht.

Ehrlichkeitsregeln

Die Regeln aus ADR-0044 gelten fuer den neuen Schritt ohne Abstrich:

  1. Nichts steht als gemessen da, was nicht gemessen wurde. Eine nicht bewertete Fassung hat im Bericht keine Zahl, sondern einen Grund.
  2. Eine verweigerte Fassung wird genannt, mit den Befunden der Pruefung („refused by the configuration check: …"), und die Suche geht weiter.
  3. Fremdlast entwertet den Vergleich. Vor und nach dem Schritt wird die fremde Rechenzeit beobachtet wie bei measure und fit. Ist sie zu hoch oder nicht beobachtbar, steht der Schritt als contaminated da, und die unverstellte Fassung wird nach measured.yaml zurueckgeschrieben — ob der Vorsprung von der Einstellung kam oder von der anderen Arbeit, weiss dann niemand.
  4. Die unverstellte Fassung bleibt liegen (tune/untuned.yaml), jede versuchte ebenso (tune/candidate-<n>.yaml, Bewertung eval-<n>.json). measure raeumt untuned.yaml vor jeder neuen Messung weg; ein wiederholtes tune auf derselben Messung beginnt deshalb von der gemessenen und nicht von einer schon verstellten Fassung.
  5. Fehlt vig-fit, bleibt der Schritt offen (skipped), die Datei unveraendert und der Lauf unvollstaendig — wie bei fit. Scheitert die Bewertung der unverstellten Fassung, scheitert der Schritt.

Bestaetigung und Machbarkeit

Nachtrag 15.09., nach dem Lauf auf dem Pixel 2 mit schwerer Last (vig-slots2-heavy.yaml, Lauf messungen/autotune-pixel2-2026-09-15-tuned-heavy). Die erste Fassung dieses Schritts hat dort zwei Dinge falsch gemacht, und beide widersprechen dem, was oben als Zusage steht.

Befund 1: gesucht auf einer Maschine, die die Vertraege nicht traegt. Die Suche behielt protect_supply: true und safety_margin_percent: 100. Danach sagte vig doctor im Schritt check: NOT_READY, PROTECTED_WORKLOAD_UNSCHEDULABLE, 126 % geschuetzte Auslastung auf zwei Slots. Keine Einstellung des Governors macht aus 126 % auf zwei Slots eine planbare Last; was eine Suche dort misst, ist, wie verschieden eine uebersaettigte Maschine von Fenster zu Fenster ausfaellt.

Befund 2: eine behaltene Einstellung war Streuung. Die Suche sah fuer die behaltene Fassung geschuetzt 220 → 50 ‰, je ein 10-s-Fenster mit nur dem Governor-Arm. fit mass dieselbe Fassung unmittelbar danach: der Detektor unter dem Governor bei 25/90/440 ‰ an 100/110/125 % Last — die Bewertung im Tuning hatte an denselben Punkten 50/22/40 ‰ gesehen. Die Streuung eines einzelnen Fensters auf einer gesaettigten Maschine ist also um ein Vielfaches groesser als der Effekt, den die Rauschschwelle von max(5 ‰, ein Zehntel) durchgelassen hat. Eine gesetzte Grenze kann die Streuung einer fremden Maschine nicht kennen; auf einer Maschine unter Saettigung ist sie deshalb nicht die richtige Sicherung.

Entscheidung

  1. Machbarkeit vor der Suche. Bevor irgendeine Fassung bewertet wird, laeuft auf der unverstellten Fassung dasselbe Urteil wie im Schritt check (vig doctor). Sagt es NOT_READY, wird nicht gesucht: Der Schritt steht als skipped im Bericht, mit dem Grund und den FAIL-Zeilen der Pruefung, nichts wird bewertet und measured.yaml bleibt unverstellt. READY_WITH_WARNINGS haelt die Suche nicht auf.

Warum skipped und nicht failed: Nach ADR-0044 ist failed ein Schritt, der nicht durchlief — ein Werkzeugfehler. Hier lief alles, und die Antwort lautet: auf dieser Maschine gibt es nichts einzustellen. Das ist dieselbe Art offene Frage wie ein fehlendes vig-fit. Der Lauf wird damit unvollstaendig („not every step ran"), der Ausgabewert bleibt 0, und die Freigabe verweigert ohnehin schon NOT_READY aus check. Ein zweites, gleichlautendes Nein als Fehlschlag zu fuehren, wuerde eine CI rot faerben fuer ein Ergebnis — genau das, was ADR-0044 ausschliesst.

  1. Bestaetigung vor dem Schreiben. Hat die Suche etwas behalten, werden unverstellte und eingestellte Fassung noch einmal bewertet, direkt hintereinander und abwechselnd, in zwei Paaren: unverstellt, eingestellt, unverstellt, eingestellt — dieselben Lastpunkte, dieselben Sekunden. Geschrieben wird die eingestellte Fassung nur, wenn sie in jedem Paar die unverstellte dieses Paars nach derselben Regel schlaegt: geschuetzt um mindestens max(5 ‰, unverstellt / 10), oder bei nicht schlechteren geschuetzten Stroemen nachrangig um mindestens max(10 ‰, unverstellt / 10). Sonst bleibt measured.yaml Byte fuer Byte unverstellt, applied ist false, und withheld nennt die Zahlen beider Paare („did not hold up in confirmation: pair 1 untuned X/Y ‰, tuned A/B ‰ …"). Die Paare stehen im Bericht unter „Confirmation" und im JSON unter tuning.confirmation, die Bewertungen als tune/confirm-<paar>-{untuned,tuned}.json.

Warum abwechselnd und paarweise: Was sich an der Maschine zwischen zwei Bewertungen aendert — Temperatur, Takt, Hintergrund —, trifft beide Fassungen eines Paars fast gleich. Verglichen wird nur innerhalb eines Paars, nie mit den Zahlen der Suche; die haben die Einstellung gerade ausgewaehlt und sind deshalb zu ihren Gunsten verzerrt. Warum jedes Paar: Die Regel soll eine Einstellung durchlassen, die wiederholt gewinnt, und keine, die einmal Glueck hatte. Warum zwei: Eines kann denselben Zufall treffen, der schon die Suche getaeuscht hat; jedes weitere kostet zwei Bewertungen, rund 80 s.

  1. Ein tune ohne Ergebnis hinterlaesst keine Einstellung. Endet der Schritt ausgelassen oder gescheitert, wird tune/untuned.yaml nach measured.yaml zurueckgeschrieben, falls ein frueheres tune derselben Messung etwas hinterlassen hatte. Sonst stuende eine Einstellung in Betrieb, die kein Bericht nennt.

Was das nicht loest

Die Bestaetigung verhindert, dass Streuung geschrieben wird; sie macht aus einer gesaettigten Maschine keine ruhige. Auf dem Pixel-2-Lauf haette die Machbarkeitspruefung die Suche gar nicht erst beginnen lassen. Auf einer Maschine, die gerade noch planbar ist und trotzdem stark streut, wird die Bestaetigung haeufiger „nicht gehalten" sagen — dann bleibt die gemessene Fassung stehen, und das ist die sichere Richtung. Die Streuung selbst misst auch diese Fassung nicht; vier Bewertungen sind zu wenige, um aus ihnen eine Schwelle abzuleiten.

Konsequenzen