Vigilant Inference Governor

Auf der eigenen Maschine nachfahren

Stand: 2026-09-14 · RTX 3070 Laptop (8 GB), Treiber 580.178.04, Triton 2.70.0 (26.06-py3), ONNX-Runtime-Backend

Jede andere Zahl in diesem Verzeichnis stammt von Modellen, die niemand ausserhalb hat: einem internen Detektionsmodell und ResNet-Stellvertretern mit passenden Formen und Laufzeiten. Fuer die Scheduling-Aussage ist das zulaessig — der Scheduler sieht Laufzeiten, keine Gewichte. Zum Nachfahren taugt es nicht.

Dieses Dokument beschreibt den Aufbau, mit dem sich die Kernaussage mit ausschliesslich frei lizenzierten Modellen wiederholen laesst, und nennt die Stolpersteine, die dabei Zeit kosten.

cargo build --release --workspace
tools/repro/run.sh

Alles Weitere macht das Skript: Voraussetzungen pruefen, Modelle holen und gegen feste SHA-256 pruefen, Triton auf eigenen Ports starten, die Profile auf dieser Maschine messen und die Lastfaelle fahren.

Wie lange das dauert. Gemessen am 14.09.2026 auf der oben genannten Maschine: die Detektor-Lastfaelle rund 15 Minuten, davon allein acht auf Lastfall (b), der sich auf dieser Karte nicht kalibrieren liess und acht vergebliche Anlaeufe brauchte; mit --with-llm kommen gut sieben Minuten dazu. Auf einer Karte, auf der (b) durchlaeuft, verschiebt sich das.

Was beim ersten Mal dazukommt — und es dominiert. Die Modelle sind mit 258 MB harmlos (mit Sprachmodell 1,5 GB mehr). Die Container-Images sind es nicht: tritonserver:26.06-py3 bringt 22,7 GB mit, das vLLM-Image fuer --with-llm weitere 35,3 GB. Bei uns lagen beide laengst lokal, und deshalb stand hier zuerst eine Viertelstunde. Wer frisch anfaengt, wartet auf den Download um Groessenordnungen laenger als auf die Messung. Eine Dauerangabe, die das verschweigt, ist von der eigenen, laengst eingerichteten Maschine abgelesen — und faellt genau dem auf, der uns nachrechnen wollte.

Die Modelle

Alle Apache-2.0. Die Lizenz stammt jeweils vom Basismodell: die ONNX-Konvertierungen auf HuggingFace tragen kein eigenes Lizenzfeld, und was dort kein Feld hat, ist nicht lizenzfrei, sondern ungeklaert.

Modell Basismodell Lizenz Groesse Rolle
rtdetr_r18 PekingU/rtdetr_r18vd Apache-2.0 83 MB die kleine Variante
rtdetr_r50 PekingU/rtdetr_r50vd Apache-2.0 175 MB die grosse Variante
qwen Qwen/Qwen3-0.6B Apache-2.0 1,5 GB das Sprachmodell, nur --with-llm

Die Digests stehen in tools/repro/fetch-models.sh und werden bei jedem Lauf geprueft. Weicht einer ab, bricht das Skript ab, statt weiterzumessen: eine Messung gegen unbekannte Gewichte ist keine Messung.

Nicht dabei, und warum. YOLOv8 ist AGPL, und seine ONNX-Fassungen antworten ohne Anmeldung mit HTTP 401. YOLOX liegt hinter einer Weiterleitung, die sich schlecht gegen einen Digest pruefen laesst. Beides waere bequemer gewesen und beides haette das Paket wertlos gemacht.

Warum ausgerechnet zwei RT-DETR-Groessen

Die Variantenwahl setzt voraus, dass zwei Varianten dasselbe bedeuten — sonst waehlt der Governor dem Client unbemerkt etwas anderes aus, als er bestellt hat. Fuer dieses Paar ist das geprueft und nicht angenommen:

Genau diese Pruefung fehlt den vier RF-DETR-Varianten aus rfdetr-variants.md, und dort ist das Ergebnis: sie sind nicht austauschbar. Hier sind sie es.

Drei Stolpersteine, die Zeit kosten

Beide sind beim Aufbau dieses Pakets tatsaechlich aufgetreten. Sie stehen hier, weil ein Reproduktionspaket genau dafuer da ist.

1. Triton laedt gar nichts, wenn die Ausgabedimensionen fest sind

Die Messwerkzeuge lesen die Eingabeform aus den Modellmetadaten und ersetzen darin nur die Batchdimension (crates/vig-bench/src/bin/gate-m3.rs). Ein Modell mit vier dynamischen Achsen ergibt so keine Tensorgroesse. Die naheliegende Abhilfe — in der config.pbtxt alle Formen festnageln — fuehrt dazu, dass der Server gar nicht erst startet:

ERROR: Failed to create instance: model 'rtdetr_r50', tensor 'logits':
the model expects 3 dimensions (shape [-1,300,80]) but the model
configuration specifies 3 dimensions (shape [1,300,80])
...
error: creating server: Internal - failed to load all models

Richtig ist die Mischung: die Eingabe fest, damit die Werkzeuge eine Groesse bilden koennen, die Ausgaben mit ihrer dynamischen Batchachse.

max_batch_size: 0
input  [ { name: "pixel_values" data_type: TYPE_FP32 dims: [  1, 3, 640, 640 ] } ]
output [ { name: "logits"       data_type: TYPE_FP32 dims: [ -1, 300,  80    ] },
         { name: "pred_boxes"   data_type: TYPE_FP32 dims: [ -1, 300,   4    ] } ]

Der Fehler ist unangenehm, weil er nicht das einzelne Modell ueberspringt, sondern den ganzen Serverstart abbricht.

2. Die Klassenausgabe traegt Logits, keine Wahrscheinlichkeiten

Das steht an keiner Tensorform und faellt im Betrieb nicht auf — ein Client, der die Werte fuer Wahrscheinlichkeiten haelt, schwellt einfach gegen die falsche Skala und findet zu wenig. Nachgemessen am laufenden Modell, mit einem gleichmaessig grauen Bild:

Modell logits min max pred_boxes min max
rtdetr_r18 −9,77 −3,60 0,0036 1,0000
rtdetr_r50 −8,89 −3,76 0,0001 1,0000

Ausserhalb von [0, 1] — also Logits vor der Sigmoid-Funktion. In der Konfiguration steht deshalb unit: logits, und das ist gemessen und nicht abgeschrieben. pred_boxes liegt in [0, 1], also coordinates: normalized.

Das Boxlayout (cxcywh gegen xyxy) laesst sich aus einem Wertebereich nicht ableiten, und es steht in den Konfigurationen dieses Pakets deshalb auch nicht. Nicht beschrieben ist eine ehrliche Aussage; eine geratene waere keine.

3. Auf einem Laptop unter Leistungslimit qualifiziert sich kein getaktetes Profil

Der Stolperstein, der am meisten Zeit gekostet hat — und er trifft jeden, der dieses Paket auf einem Laptop nachfaehrt.

vig profile und vig calibrate verwerfen eine Messreihe, wenn sich der Hardwarezustand waehrend der Reihe aendert:

FEHLER rtdetr_r18: Messung verworfen: Hardwarezustand geaendert:
       GPU 0 clock_sm_mhz: ~1900 -> ~1600
WARNUNG Die Karte ist waehrend der Messung gedrosselt: [SwPowerCap].

Auf der Messmaschine ist das der Normalfall und nicht die Ausnahme: die Karte regelt dauerhaft am Software-Leistungslimit und pendelt zwischen 1650 und 1920 MHz — in einem Lauf lag SwPowerCap in 30 von 70 Stichproben an. Die Meldeschwelle betraegt 100 MHz.

Kuerzere Serien helfen nicht. Mit --samples 100 — dem Minimum, unter dem ein p99 kein Quantil mehr waere, sondern das Maximum — qualifizierte sich in acht Versuchen kein einziger Lauf mit beiden Modellen.

Es liegt an der Luecke zwischen den Freigaben, nicht am Modell. Dieselben zwei Modelle, je drei Versuche:

Betriebsart rtdetr_r18 rtdetr_r50
getaktet (33-ms-Raster) 0 von 3 1 von 3
Ruecken an Ruecken 2 von 3 3 von 3

Auf dem Raster faellt die Karte zwischen zwei Freigaben im Takt zurueck. Das schnellere Modell trifft es haerter, weil seine Luecke groesser ist: rtdetr_r50 fuellt die 33-ms-Periode fast aus und haelt die Karte damit auf Takt, rtdetr_r18 braucht etwa die Haelfte.

Was daraus folgt:

Warum die Vorlagen keine Zahlen enthalten

vig calibrate laesst das Profil einer Variante, deren Messreihe es verworfen hat, unveraendert stehen und endet trotzdem mit Exitcode 0. Stuenden in den Vorlagen unter tools/repro/scenarios/ Laufzeiten, bekaeme man im Fehlerfall eine Konfiguration, die gemessen aussieht und mit den Laufzeiten einer fremden Maschine plant — ohne jedes Anzeichen.

Die Vorlagen tragen deshalb kein profile:. Fehlt es hinterher weiterhin, scheitert der Start hoerbar:

FAIL models.detektor.variants[0].profile: kein Laufzeitprofil;
     `vig profile` ausfuehren oder profile: {p50_us, p95_us, p99_us, samples} angeben
RESULT NOT_READY

run.sh zaehlt nach der Kalibrierung nach, statt dem Exitcode zu glauben, und bricht den Lastfall ab, wenn Profile fehlen. Ein Unittest haelt fest, dass die Vorlagen zahlenfrei bleiben (repro_scenarios.rs).

Die drei Lastfaelle

# Lastfall Was er prueft
a Ein Detektor (protected, 30 Hz) neben dem Sprachmodell (best_effort) der Fall „humanoider Roboter": laeuft der Bericht ueberhaupt, ohne dass die Wahrnehmung dafuer zahlt (wp26, ADR-0012/0014)
b Vier Kameras auf einen Detektor, mit unterschiedlicher Wichtigkeit ob die Summe der Reserven aufgeht, wenn mehrere Stroeme denselben Slot teilen (S8 aus scenarios.md)
c Zwei Groessen desselben Detektors die Variantenwahl mit echten Modellen statt Laufzeit-Stellvertretern

Lastfall (c) ist der, wegen dem dieses Paket ueberhaupt entstanden ist: die bisherige Variantenmessung (frontier) benutzt ResNet-50 gegen ResNet-18 als Laufzeitpaar — sie erzeugen eine reproduzierbare Spreizung, sind aber auf Protokollebene nicht austauschbar und tragen eine erklaerte, nicht gemessene Qualitaet. rtdetr_r50 gegen rtdetr_r18 ist ein echtes Variantenpaar.

Alle vier Kameras in Lastfall (b) fragen dasselbe Backendmodell. Eine Kamera ist kein eigenes Modell, sondern ein eigener Vertrag; der Unterschied zwischen ihnen ist Wichtigkeit und Takt, nicht Rechenlast. Das hat eine praktische Folge fuer die Kalibrierung, siehe Stolperstein 3.

Ergebnisse

Gemessen am 14.09.2026 auf der oben genannten Maschine. Jede Zelle traegt ihren Status; verschmutzte Zellen stehen mit dabei, statt still zu fehlen.

Die gemessenen Profile

Ruecken an Ruecken, 100 Messwerte je Reihe (auf dem Vertragsraster qualifizierte sich keine — Stolperstein 3):

Modell p50 p95 p99
rtdetr_r50 26 570 us 27 367 us 27 577 us
rtdetr_r18 14 194 us 14 925 us 15 661 us

Die grosse Variante braucht also rund das 1,9-fache der kleinen. Damit liegt die geschuetzte Auslastung bei 33 ms Periode und 110 % Marge bei 91 %, und vig doctor meldet READY_WITH_WARNINGS samt OK detektor: Varianten fachlich austauschbar.

Lastfall (c): zwei Groessen — der Governor verliert hier

Versorgungsvergleich (vig-fit, Verbrauchersicht, unabgedeckte Abtastungen je Promille, drei Laeufe):

Last direkt Governor Status
90 % 0 ‰ 0 ‰ gueltig (Lauf 1, 3)
100 % 0 ‰ 0–3 ‰ gueltig
110 % 0 ‰ 3 ‰ gueltig
125 % 2 ‰ 2 ‰ gueltig

Das Urteil des Werkzeugs lautet in zwei von drei Laeufen woertlich: „Bis 125 % Angebotslast verliert auch der direkte Weg nichts. Auf dieser Maschine, mit diesen Modellen und Vertraegen lohnt sich der Governor nicht."

Lauf 2 zeigte bei 90 % Last 158 ‰ direkt gegen 0 ‰ mit Governor und eine Luecke von 928 ms — und geht trotzdem nicht in die Aussage ein. Die Systemlast lag waehrenddessen bei 1,7–1,8; eine 928-ms-Luecke auf dem direkten Arm bei unterlasteter GPU ist der Fingerabdruck einer fremden Stoerung, nicht eines Schedulingerfolgs. Ihn als Sieg zu berichten waere Auswaehlen und kein Messen.

Direktvergleich (gate-m3, 30 s je Puffertiefe, Kopierpfad):

Lauf Abdeckung Triton Vigilant Faktor Status
1 99 % 93 % −21,0x verschmutzt (security-… 64 %, nv23_bounded-… 100 % nebenher)
2 100 % 94 % −60,0x gueltig
3 99 % 89 % −52,5x gueltig

Verbrauchersicht: Abdeckung 100 % gegen 99–100 %, laengste Luecke 30 ms gegen 31–37 ms, mittleres Antwortalter 42–44 ms gegen 46–47 ms.

Der Governor ist hier also schlechter, und seine eigene Zaehlerzeile sagt warum:

angenommen 1780  weitergereicht 1780  supersediert 0  stale 0
unmachbar 0  verspaetet 0  zurueckgestellt 0  best-effort ausgehungert 0

Er hat nichts verworfen, nichts zurueckgehalten, nichts ersetzt — es gab nichts zu entscheiden. Ein einzelner Strom bei 91 % Auslastung hat keine Konkurrenz, gegen die man ihn schuetzen koennte; es bleibt sein eigener Aufwand von rund 5 Prozentpunkten Abdeckung. Das ist kein Widerspruch zu den Gate-M3-Zahlen dieses Repositorys, sondern dieselbe Aussage von der anderen Seite: load-ramp.md verortet den Knick zwischen 100 und 110 %, und die Startseite empfiehlt fuer einen einzelnen Strom ausdruecklich keinen Governor.

Was dieser Lastfall damit nicht belegt: dass die Variantenwahl etwas bringt. Sie ist nachweislich aktiv (doctor, und der Funktionstest repro_scenario_mock.rs nagelt sie fest), aber ohne Konkurrenz um den Slot gibt es keinen Grund zu degradieren. Fuer eine Aussage ueber ihren Nutzen braeuchte es den Aufbau aus frontier: feste grosse gegen feste kleine gegen automatische Wahl.

Lastfall (b): auf dieser Karte nicht messbar

Vier Kameras auf einen Detektor — kein Ergebnis, und zwar aus dem Grund aus Stolperstein 3, nur verschaerft: vier Vertraege auf demselben Modell heissen vier unabhaengige Messreihen, von denen jede einzeln verworfen werden kann.

Acht Anlaeufe, mit 200 und mit 100 Messwerten, bestes Ergebnis 2 von 4 Profilen. Insgesamt 29 verworfene Reihen; die Qualifikationszeilen lauten 0 von 4, 2 von 4, 1 von 4. Die Gruende verteilen sich auf

Auf einer Karte mit festgehaltenem Takt ist dieser Lastfall messbar; hier nicht. Die Konfiguration bleibt im Paket, weil an ihr nichts falsch ist — es fehlt die Maschine, die ihren Takt haelt.

Lastfall (a): Detektor neben dem Sprachmodell — der direkte Weg gewinnt doppelt

Detektor rtdetr_r18 bei 33 ms, Sprachmodell Qwen3-0.6B ueber das vLLM-Backend in einem eigenen Prozess, ein Slot, 30 s je Betriebsart, drei Laeufe. Datenpfad: System Shared Memory.

Median der drei Laeufe (Einzelwerte in Klammern, wo sie streuen):

Betriebsart Detektor-Abdeckung Antwortalter p95 Generierungen Zeichen
direkt zu Triton 100 % (97/100/100) 17 ms 26 7 878
Governor ohne Zerlegung 96 % 14 ms 2 606
Governor mit Zerlegung 33 % (33/33/32) 43 ms 20 5 175

Der direkte Weg ist in beiden Dimensionen besser. Er haelt den Detektor bei 100 % und bringt 26 Berichte durch. Der Governor ohne Zerlegung schuetzt den Detektor praktisch gleich gut (96 %) und erdrosselt den Bericht auf ein Zwoelftel — das ist die Aushungerung aus ADR-0012, wie erwartet. Die Zerlegung loest sie (20 Generierungen), kostet aber zwei Drittel der Detektorabdeckung.

Bemerkenswert dabei: Protected-Deadline-Misses 0 in allen drei Laeufen. Der Governor verletzt keine Frist — er liefert schlicht seltener ein frisches Ergebnis. Von 1 215 angenommenen Anfragen reichte er 829–846 weiter und stellte 6–38 zurueck. Abdeckung und Deadline-Treue sind eben zwei verschiedene Groessen (ADR-0005).

Warum das so ausgeht, steht in den eigenen Zahlen: der Detektor braucht p99 15,7 ms bei 33 ms Periode, also rund 47 % eines Slots. Das ist keine Ueberlast. Triton faehrt beide Stroeme ueberlappend, und die Karte traegt das. Der Governor tritt hier gegen einen Gegner an, der gar nicht in Not ist — dieselbe Ursache wie in Lastfall (c), nur mit einem echten Sprachmodell daneben.

Ein Nebenbefund, der fuer die Zerlegung wichtig ist: der feste Sockel je Generierungsauftrag ist auf dieser Maschine mit diesem Modell gemessen 20–45 ms (Median rund 21 ms, aus der Kostenprobe am Anfang jedes Laufs). wp26 traegt als Vorgabe 18 000 us — fast das Tausendfache, gemessen an einem anderen Modell auf einer anderen Belegung. Wer die Zerlegung mit einer fremden Sockelzahl plant, schneidet die Quanten grob falsch zu.

Was das fuer die Kernaussage heisst

Zwei von drei Lastfaellen zeigen nicht, was man sich erhofft — und der dritte war nicht messbar. Ehrlich zusammengefasst:

Lastfall Ergebnis
(a) Detektor + Sprachmodell direkt gewinnt doppelt: 100 % gegen 33 % Abdeckung und 26 gegen 20 Berichte
(b) vier Kameras auf dieser Karte nicht messbar (29 verworfene Messreihen)
(c) zwei Groessen Governor verliert: 89–94 % gegen 99–100 %, Faktor −52x bis −60x

Das widerspricht den Gate-M3-Zahlen dieses Repositorys nicht, und es ist wichtig zu verstehen warum. Gate M3 misst bei 103 % geschuetzter Auslastung — im Ueberlastbereich, fuer den der Governor gebaut ist. Die drei Lastfaelle hier landen bei 47 % (a) und 91 % (c), also darunter. Und load-ramp.md verortet den Knick zwischen 100 und 110 %: unterhalb davon kostet der Governor nur seinen eigenen Aufwand. Die Startseite sagt dasselbe unter „When not to use it", und vig-fit sagt es in zwei von drei Laeufen woertlich: „lohnt sich der Governor nicht".

Was diese Messung also belegt: dass die Werkzeuge mit oeffentlichen Modellen laufen, dass sie ihre eigene Grenze zuverlaessig anzeigen, und dass die Empfehlung „unterhalb der Saettigung keinen Governor" auch mit echten fremden Modellen gilt. Das ist eine Bestaetigung der Ehrlichkeit des Produkts, nicht seiner Leistung.

Was sie nicht belegt: die Kernaussage im Ueberlastbereich. Dafuer fehlt ein oeffentliches Modellpaar, dessen serialisierte Auslastung auf einer 8-GB-Laptopkarte ueber 100 % kommt, ohne dass die Profilmessung am wandernden Takt scheitert (Stolperstein 3). Mit rtdetr_r50 als geschuetztem Strom bei 33 ms waere man dort — genau diese Konfiguration laesst sich hier aber nicht kalibrieren. Das ist die offene Luecke dieses Pakets, und sie gehoert benannt statt umgangen.

Was dieser Aufbau nicht beantwortet