kontinent / evalsKapitel 8
Das Offline-Harness
Die Technik drumherum: wie Sie einen Testlauf so aufsetzen, dass zwei Läufe überhaupt vergleichbar sind.
Ein Harness ist die Infrastruktur hinter einem Eval. Es macht aus „Wir haben ein Eval-Set und ein paar Grader“ ein „Wir können das jederzeit wiederholen und vergleichen“. Das ist unspektakulär. Es ist zum größten Teil Buchhaltung. Aber Fehler in dieser Buchhaltung sind der häufigste Grund, warum Eval-Zahlen am Ende niemand mehr deuten kann.
Der Runner in Ein Eval in fünf Dateien ist das kleinste Harness, das es gibt. Dieses Kapitel beschreibt, wozu er heranwächst.
Woraus ein Harness besteht
Vier Teile, egal womit Sie es bauen:
- Ein Datensatz: versionierte Beispiele, jeweils mit erwarteter Ausgabe oder mit Bewertungskriterien.
- Ein Runner: führt Ihr System gegen jedes Beispiel aus. Er kümmert sich um Nebenläufigkeit, Wiederholungen und Cache.
- Grader: einer oder mehrere pro Beispiel, von Code-Prüfungen bis zu Judges.
- Ein Ergebnisspeicher: jeder Lauf, mit genug Metadaten. Genug heißt: Sie können den Lauf reproduzieren und mit einem anderen vergleichen.
Inspect nennt dieselben vier Teile etwas anders. Diese Namen lohnt es sich zu übernehmen, auch wenn Sie das Werkzeug nie benutzen:
| Inspects Name | Was es ist |
|---|---|
dataset | Ihre Beispiele |
Solver | Das, was geprüft wird: Ihr Prompt, Ihre Kette oder Ihr Agent. Er löst jedes Beispiel |
Scorer | Der Grader, der die Antwort des Solvers benotet |
Task | Das Bündel, das sagt: „Lass diesen Datensatz durch diesen Solver laufen und benote ihn mit diesem Scorer“ |
Entscheidend ist: Die vier Teile sind trennbar. Das zahlt sich in zwei alltäglichen Situationen aus. Sie wollen ein neues Modell ausprobieren? Dann tauschen Sie den Solver. Datensatz und Scorer bleiben unberührt. Ihnen fällt eine neue Prüfung ein? Dann ergänzen Sie einen Scorer und benoten die gespeicherten Ausgaben von gestern neu. Sie müssen nicht noch einmal dafür bezahlen, diese Ausgaben zu erzeugen.
In einem Harness, in dem diese Teile verwoben sind, wird aus beidem ein Umbau statt einer Änderung von einer Zeile.
Den vierten Teil überspringen Teams am häufigsten. Und genau er entscheidet, ob sich das Harness lohnt.
Alles festnageln
Ein Eval-Wert sagt nur etwas aus im Verhältnis zu den Bedingungen, unter denen er entstanden ist. Jede nicht festgenagelte Variable ist ein Grund, warum zwei Läufe sich unterscheiden können. Und diesen Grund hat niemand aufgeschrieben.
| Festnageln | Warum |
|---|---|
| Modellversion, inklusive datiertem Snapshot | gpt-5 ist ein bewegliches Ziel; gpt-5-2026-04-01 nicht |
| Dekodierparameter | Temperatur, Top-p, maximale Tokens, Seed sofern verfügbar |
| Version des Judge-Modells | Ihre Alignment-Messung galt für eine bestimmte Version |
| Judge-Prompt | Mit dem Lauf speichern, nicht nur im Repo auf HEAD |
| Datensatzversion | Werte aus verschiedenen Datensatzversionen sind nicht vergleichbar |
| System-Prompt und Templates | Auch alles, was erst zur Laufzeit zusammengesetzt wird |
| Version des Retrieval-Index | Bei RAG gehört der Index zum geprüften System |
| Tool-Schemata | Eine geänderte Parameterbeschreibung ändert das Verhalten |
Faustregel: Sie müssen eine sechs Monate alte Zahl aus dem Gespeicherten nachbauen können. Geht das nicht, haben Sie zu wenig gespeichert.
Auch das Prompt-Format festnageln
An diesen Punkt denkt kaum jemand. Dabei steht ein bemerkenswerter Befund dahinter.
Zuerst: Was wird hier festgenagelt? Viele Evals zeigen dem Modell vor der eigentlichen Frage ein paar gelöste Beispiele, sogenannte In-Context-Beispiele. Irgendetwas muss diese Beispiele voneinander trennen. Dieses Trennzeichen heißt Delimiter. Meist wählt man es, ohne nachzudenken:
Frage: Was ist die Hauptstadt von Frankreich?
Antwort: Paris
### <- das ist der Delimiter
Frage: Was ist die Hauptstadt von Spanien?
Antwort: Madrid
###
Frage: Was ist die Hauptstadt von Italien?
Antwort:
Tauschen Sie dieses ### gegen eine Leerzeile, gegen --- oder gegen \n\n. An der
Aufgabe ändert das nichts. Der Befund: Am Ergebnis ändert es etwas.
Befund: A Single Character can Make or Break Your LLM Evals (2025) hat gemessen, wie stark der Delimiter zwischen In-Context-Beispielen die MMLU-Leistung verändert: um bis zu ±23 %. Die Einbrüche lagen bei 18,3–29,4 %, über die Modellfamilien Llama, Qwen und Gemma hinweg. Durch dieses eine Zeichen ließen sich die Modell-Rankings so umsortieren, dass jedes beliebige Modell vorn lag. Der Effekt zieht sich durch alle Themen und Modellfamilien und wird mit der Modellgröße nicht kleiner. Wer den gewählten Delimiter im Prompt ausdrücklich benannte, machte die Messung robuster.
Quelle: A Single Character can Make or Break Your LLM Evals, 2025
Halten Sie diese Zahl neben das, was Sie normalerweise messen wollen. Eine Prompt-Verbesserung, die eine Auslieferung wert ist, bewegt Ihren Wert vielleicht um 3 bis 5 Punkte. Das Trennzeichen bewegt MMLU um 23. Das heißt nicht, dass es Ihren Wert um 23 bewegt. Der Befund stammt aus Few-Shot-Prompts an Modelle mit offenen Gewichten, also aus einem Aufbau der Ebene A. Ihr Anwendungs-Eval über eine Chat-API sieht anders aus. Der Befund zeigt aber, welche Größenordnung ein Formatdetail erreichen kann. Wie groß der Effekt bei Ihnen ist, erfahren Sie nur auf einem Weg: Sie variieren das Format einmal. Bis dahin ist es eine Variable mit unbekannter Wirkung. Also: festnageln.
Die Folgerung lautet nicht, dass Benchmarks wertlos sind. Sie lautet: Das Prompt-Template ist eine Versuchsbedingung. Ihre Effektgröße kann die meisten Änderungen übertreffen, die Sie messen wollen. Behandeln Sie das Template als festgenagelte Konfiguration. Versionieren Sie es zusammen mit dem Datensatz. Und lassen Sie nie eine „harmlose Formatierungsbereinigung“ im selben Change landen wie einen Modellvergleich.
Wenn Sie Modelle vergleichen, halten Sie das Format über alle Modelle hinweg fest. Und seien Sie sich bewusst: Ein Format, das ein Modell begünstigt, ist ein echter Störfaktor, kein hypothetischer.
Determinismus und seine Grenzen
Temperatur ist der Regler für den Zufall. Sie bestimmt, wie viel Zufall ein Modell bei
der Wahl des nächsten Wortes zulässt. Bei jedem Schritt hat das Modell eine Rangliste von
Kandidaten. Bei temperature=0 nimmt es immer den obersten. Drehen Sie die Temperatur
hoch, nimmt es manchmal den zweiten oder dritten. Deshalb wirken Ausgaben dann
abwechslungsreich und kreativ. Und deshalb bekommt dieselbe Frage jedes Mal eine andere
Antwort.
temperature = 0 gleiche Frage → (fast) dieselbe Antwort, in jedem Lauf
temperature = 1 gleiche Frage → jedes Mal eine andere Antwort
Für ein Eval wollen Sie 0. Sie wollen messen, ob sich Ihr System verändert hat. Zufällige Schwankung zwischen Läufen ist Rauschen, das sich auf diese Messung legt. Kreativität ist eine Produkteigenschaft. In einer Messung ist sie eine Störung.
Setzen Sie in Offline-Läufen temperature=0, wenn Ihr Produkt damit läuft. Ist das Eval
eine Auswahl aus festen Optionen, lesen Sie die Token-Wahrscheinlichkeiten aus, statt
Antworten zu ziehen. Siehe Statistik.
Umstritten: Soll ein Eval bei Temperatur 0 laufen, wenn das Produkt bei 0,7 läuft? Dafür spricht: weniger Rauschen, billigere Läufe, reproduzierbare Diffs. Dagegen spricht: Sie messen dann ein anderes System als das, das Ihre Nutzer sehen. Das ist genau der fünfte Fall in Online-Evaluation. Und Fehlermodi, die erst durch das Ziehen entstehen, tauchen im Eval nie auf. Die Position dieses Leitfadens: Produktionsparameter, K Ziehungen pro Fall, der Mittelwert als Wert des Falls. Temperatur 0 ist die Abkürzung für zwei Situationen. Erstens für Stufen, die nur die Form prüfen und nicht die Antwort, also die Smoke-Stufe. Zweitens für Produkte, die ohnehin bei 0 laufen.
Erwarten Sie keine bitgenaue Reproduzierbarkeit. Gehostete Inferenz schwankt mit
Batching, Hardware und stillen Änderungen im Backend. temperature=0 senkt die Varianz,
beseitigt sie aber nicht. Planen Sie damit, statt dagegen anzukämpfen:
- Berichten Sie einen Mittelwert über K Ziehungen für alles, worüber Sie entscheiden.
- Behandeln Sie eine kleine Wertänderung ohne Code-Änderung als Messrauschen, bis das Gegenteil bewiesen ist.
- Loggen Sie die Rohausgaben, nicht nur die Werte. Wenn sich eine Zahl bewegt, verraten nur die Ausgaben, warum.
Aggressiv cachen
Zwei Caches mit zwei verschiedenen Aufgaben:
Generierungs-Cache, geschlüsselt auf (Modell, Parameter, Prompt). Damit können Sie
einen Grader ergänzen oder reparieren und neu bewerten, ohne neu zu generieren. Das
Generieren ist der Großteil der Kosten und der Wartezeit.
Judge-Cache, geschlüsselt auf (Judge-Modell, Judge-Prompt, Ausgabe). Judge-Aufrufe
machen den Großteil der Kosten einer ausgereiften Eval-Suite aus.
Beide Caches müssen auf alles schlüsseln, was das Ergebnis beeinflusst. Ein Cache, der nur auf den Prompt-Text schlüsselt, liefert Ihnen nach einem Modell-Upgrade die Antworten des alten Modells. Und zwar stillschweigend. Dieser Bug ist wirklich häufig. Und er erzeugt das schlimmstmögliche Symptom: ein Eval, das behauptet, es habe sich nichts geändert.
Budget
Evaluation kostet echtes Geld und echte Zeit. Eine Suite, die niemand laufen lässt, weil sie vierzig Minuten braucht, ist keine Suite.
- Staffeln Sie die Suite. Eine schnelle Teilmenge bei jeder Änderung, das volle Set jede Nacht. Siehe Evals in der CI.
- Billige Grader zuerst. Scheitert die Schemaprüfung, müssen Sie selten noch einen Judge für die Prosaqualität bezahlen.
- Nehmen Sie einen kleineren Judge, wo Sie einen validiert haben. Judge-Qualität wird gemessen, nicht unterstellt. Erreicht ein kleineres Modell auf Ihrem versiegelten Stapel Ihre Schwelle für Trefferquote und Richtig-negativ-Rate, ist es ein gültiger Judge. Und es kostet einen Bruchteil.
- Führen Sie die Kosten pro Lauf als vollwertige Kennzahl mit. Suiten wachsen. Die Frage „Warum ist die CI plötzlich teuer?“ kommt später und schmerzhafter als ein regelmäßiger Blick auf die Kennzahl.
Nebenläufigkeit und Fehler
Praktische Details, die entscheiden, ob das Harness benutzbar ist:
- Begrenzen Sie die Nebenläufigkeit und behandeln Sie 429-Antworten mit Backoff und Jitter. Ein Eval-Lauf ist eine Lastspitze.
- Unterscheiden Sie Systemfehler von Bewertungsfehlern. Ein Timeout ist keine falsche Antwort des Modells. Zählen Sie beides getrennt. Ein Lauf mit 12 % Infrastrukturfehlern ist keine gültige Messung. Wer sie als Fehlschläge mitzählt, stellt das eigene System stillschweigend schlechter dar, als es ist.
- Machen Sie Läufe fortsetzbar. Mit einem Generierungs-Cache ist das fast gratis. Aus einer abgestürzten Stunde wird eine wiederholte Minute.
- Scheitern Sie bei Teilläufen laut. Eine Suite, die still 340 von 400 Beispielen bewertet, meldet eine Zahl für einen Datensatz, den es nicht gibt.
Ergebnisse vergleichbar speichern
Der Mindestdatensatz pro Lauf:
run_id, timestamp, git_sha
dataset_name, dataset_version
model, model_version, decoding_params
judge_model, judge_version, judge_prompt_hash
per_example: input_id, output, scores{}, latency, tokens, cost, error
aggregate: score, n, standard_error
Die Zeilen je Beispiel sind der Teil, der sich auszahlt. Aggregate sagen Ihnen, dass sich eine Zahl bewegt hat. Zeilen je Beispiel zeigen im Diff zweier Läufe, welche Beispiele sich geändert haben. Das ist die erste Frage, die jeder stellt. Und ein Aggregat kann sie nicht beantworten.
Häufiger Fehler: Das Harness bauen, bevor die Fehleranalyse gemacht ist. Die Form des Harness sollte daraus folgen, was Sie messen. Teams, die zuerst Infrastruktur bauen, landen bei einem eleganten Runner. Der hängt an Metriken, die sie nur gewählt haben, weil das Framework sie mitbrachte.