Ein Modell kann laufen und trotzdem falsch sein

Ein Open-Weight-Modell von einem Inference-Stack in einen anderen zu übertragen, wirkt oft wie eine Deployment-Aufgabe: Gewichte konvertieren, Datei laden, Backend wählen, einen Prompt senden und die Antwort prüfen. Wenn Tokens ausgegeben werden, scheint die Migration abgeschlossen.

Dieser Schluss ist zu schwach. Eine Runtime kann ein Modell laden, seine Tensoren anlegen und flüssigen Text erzeugen, obwohl sie eine subtil andere Funktion als das beabsichtigte Modell berechnet. Die dauerhafte Engineering-Disziplin, mit der du dieses Problem erkennst, ist Inference Correctness über mehrere Runtimes hinweg.

Das ist aktuell, weil llama.cpp mit Release b10829 vom 6. September die Gated-Delta-Net-Normalisierung für Qwen3-Next korrigierte. Laut Release Notes unterschied sich der vorherige Pfad von der Referenzformel darin, wo epsilon platziert wurde – eine kleine Konstante für numerische Stabilität. Die Änderung platzierte epsilon wie in der Referenz innerhalb der Quadratwurzel. Die Release Notes beschreiben die Korrektur. Hugging Face hatte zuvor eine Änderung an der l2-Normalisierung zusammengeführt, die Qwen3-Next an die Implementierung von Flash Linear Attention angleichen sollte. Der frühere Fix ist im Pull Request dokumentiert.

Die eigentliche Lehre ist kleiner als die Release Note: Ein Modellartefakt ist nicht das gesamte Modell im Betrieb. Erst Artefakt, Konverter, Graph-Konstruktion, Kernels, Zustandsverwaltung, Tokenizer und Sampling-Konfiguration erzeugen gemeinsam das beobachtbare Verhalten.

Was ist Inference Correctness?

Inference Correctness fragt, ob eine Modellimplementierung, ein Konvertierungspfad, die gewählte Präzision und ein Serving-Backend die beabsichtigte Berechnung für einen festgelegten Zweck hinreichend erhalten. „Hinreichend“ ist dabei entscheidend. Der Begriff ist nicht vollständig standardisiert.

Manche Teams meinen damit numerische Akzeptanz: Sie vergleichen eine Runtime mit einer Referenz und verlangen, dass Logits, Hidden States oder ausgewählte Zwischentensoren innerhalb definierter Toleranzen übereinstimmen. Andere meinen Verhaltensakzeptanz: Output-Verteilungen oder aufgabenbezogene Ergebnisse müssen ausreichend ähnlich bleiben. Beides hängt zusammen, beantwortet aber unterschiedliche Fragen. Ein Verhaltenstest kann zeigen, dass Nutzende eine Abweichung vermutlich nicht bemerken. Er zeigt aber nicht unbedingt, wo eine mathematische Abweichung begann. Ein numerischer Vergleich kann einen Fehler lokalisieren, kann jedoch auch eine gültige optimierte Implementierung verwerfen, weil Floating-Point-Arithmetik auf unterschiedlichen Geräten nicht exakt identisch ist.

Einfach gesagt: „Das Modell läuft“ heißt nur, dass die Software nicht abgestürzt ist. „Inference Correct“ heißt, dass sie die beabsichtigte Modellberechnung in einem von dir definierten und begründbaren Maß ausführt.

Es geht nicht um Bit-für-Bit-Gleichheit. Floating-Point-Additionen können andere Ergebnisse liefern, wenn eine Implementierung die Reihenfolge von Operationen ändert. Quantization – also die Darstellung von Gewichten oder Aktivierungen mit geringerer Präzision – verändert Werte absichtlich. Correctness ist daher ein Akzeptanzvertrag: Welche Referenz, welche Eingaben, welche Outputs, welche Präzision und welche Toleranz sind für dieses Deployment akzeptabel?

Warum Portabilität nicht genügt

Model Portability beantwortet eine nützliche, aber engere Frage: Lässt sich dieses Modell in einer anderen Umgebung laden und ausführen? Inference Correctness fragt, ob diese Umgebung die beabsichtigte Funktion hinreichend genau berechnet.

Diese Unterscheidung wird auf jeder Ebene praktisch. Eine Architektur wird in Tensor-Operationen, Layouts, Datentypen, Kernels und manchmal in Graph-Fusionsschritte übersetzt. Ein Kernel ist eine hardwarenahe Implementierung einer Operation auf CPU, GPU oder einem anderen Beschleuniger. Ein fusionierter Kernel kombiniert Operationen, um Speicherzugriffe oder Overhead zu reduzieren. Beides kann schnell und dennoch semantisch falsch sein.

Hinzu kommen weniger sichtbare Teile der Konformitätsfläche: Tokenisierung, Positionskodierung, Cache-Updates, rekurrenter Zustand, Konvertierungsregeln und Generationseinstellungen. Der KV Cache speichert Aufmerksamkeitszustände vorheriger Tokens, damit die Generierung nicht bei jedem Schritt den gesamten Prompt neu berechnen muss. Ein Fehler bei Cache-Updates kann erst ab einer bestimmten Prompt-Länge oder in einer mehrteiligen Session sichtbar werden. Ein Konverter kann einen Tensor falsch transponieren, eine falsche Skala bei der Quantization anfügen oder eine Modellkomponente der falschen Runtime-Operation zuordnen. Der Prozess bleibt lauffähig; nur ist sein Output dann kein verlässlicher Beleg mehr dafür, dass das beabsichtigte Modell läuft.

Die Qwen3-Next-Implementierung von llama.cpp enthält selbst architekturspezifische Graph-Konstruktion für Gated Normalization, rekurrente Attention, Umformungen und Zustandsverwaltung. Der Quellcode macht das sichtbar. Deshalb kann eine allgemeine Suite mit „Die Runtime funktioniert“-Tests keine Konformität für jede Architektur und jeden Konvertierungspfad belegen.

So testest du es Schritt für Schritt

Lege zuerst eine Referenz explizit fest. Eine Referenz ist nicht zwangsläufig eine metaphysische Wahrheit; sie ist die Implementierung und Konfiguration, mit der du einen Kandidaten vergleichst. Halte Modellartefakt, Code- oder Runtime-Version, gegebenenfalls Konverter-Version, Tokenizer, Gerät, dtype und Generationseinstellungen fest. Auch Referenzen können Fehler enthalten – die Fixes im Ökosystem rund um Qwen3-Next erinnern daran. Bewahre daher genug Provenienz auf, um die Baseline später anpassen zu können.

Baue als Nächstes einen kleinen, architekturspezifischen Testkorpus. Allgemeine Prompts reichen nicht. Die Eingaben sollten jene Operationen auslösen, die besonders leicht abweichen: kurze und lange Sequenzen, Generierung nach wachsendem Cache, ungewöhnliche Shapes oder Batch-Größen, soweit unterstützt, sowie Komponenten wie Normalisierung, Positionskodierung, Attention oder rekurrenten Zustand. Es geht nicht um eine breite Produktbewertung. Du willst eine bestimmte Berechnung sichtbar machen.

Vergleiche auf drei Ebenen – von der Diagnose bis zur Auswirkung für Nutzende:

  1. Vergleiche Zwischentensoren, wenn du beide Implementierungen instrumentieren kannst. So findest du die erste Schicht oder Operation, in der Werte auseinanderlaufen.
  2. Vergleiche Logits, also die Rohwerte vor der Auswahl des nächsten Tokens, oder Hidden States. Diese Tests helfen bei numerischer Konformität, bevor Sampling kleine Unterschiede verstärkt.
  3. Vergleiche generierte Outputs und Aufgabenergebnisse bei festen Einstellungen. Damit klärst du, ob eine verbleibende Differenz Verhalten verändert, das für das Deployment relevant ist.

Einfach gesagt: Wenn zwei Antworten abweichen, streite nicht zuerst über die Antwort. Finde zuerst heraus, ob die interne Berechnung schon vor der Auswahl des nächsten Tokens auseinanderlief.

Lege für jeden Vergleich eine Toleranzrichtlinie fest, statt erst nach einem Fehlschlag eine Grenze zu wählen. Die passende Schwelle hängt von dtype, Quantization, Operationstyp, Sequenzlänge und Einsatzzweck ab. Full-Precision-Pfade auf CPU und GPU können enge numerische Checks verdienen. Ein stark quantisierter Pfad braucht möglicherweise zusätzlich zu weiteren numerischen Grenzen eine Verhaltens- oder Aufgabenakzeptanz. Verwende nicht stillschweigend dieselbe Schwelle für alle Architekturen und Backends.

Mache den Fall schließlich zu Differential Testing: Du führst dieselbe kontrollierte Eingabe durch unabhängige Implementierungen und untersuchst unerwartete Unterschiede. Lass den Test laufen, wenn du einen Konverter änderst, eine fusionierte Operation einführst, ein Backend ergänzt, einen Graph Rewrite veränderst oder eine Runtime aktualisierst. Speichere das Ergebnis mit dem exakten Implementierungskontext. „Der Prompt sah gut aus“ ist kein Regressionseintrag.

Was es nicht ist

Inference Correctness liegt neben mehreren wertvollen Praktiken, ersetzt sie aber nicht.

Sie ist keine gewöhnliche Model Evaluation. Evaluation fragt, ob ein Modell bei einem Datensatz oder einer Aufgabe gut abschneidet. Ein Runtime-Fehler kann eine seltene Fähigkeit verschlechtern, die ein Benchmark nicht erfasst; ein gutes Ergebnis kann also neben einer falschen Implementierung bestehen. Umgekehrt kann ein kleiner numerischer Unterschied keinen relevanten Task Score verändern. Evaluation erkennt Ergebnisse; Konformitätstests untersuchen, ob die Implementierung die Berechnung verändert hat.

Sie ist auch nicht nur Quantization Validation. Quantization ist eine Quelle erwartbarer numerischer Abweichungen. Inference Correctness umfasst außerdem unquantisierte Fehler in Normalisierung, Layout, Broadcasting, Konvertierung, Zustandsverwaltung und Backend-Semantik.

Sie ist nicht allein Kernel Correctness. Die Validierung einer einzelnen Operation ist wichtig, doch ein Modell besteht aus der Zusammensetzung tausender Operationen sowie architekturspezifischem Zustand und Konvertierungslogik. Ein korrekter Kernel in einem falschen Graphen ergibt weiterhin einen falschen Inference-Pfad.

Und sie ist keine Garantie für Performance, Sicherheit, Memory Safety oder Zuverlässigkeit. Ein konformes Backend kann langsam sein, Ressourcen verlieren, Credentials an seinem Service falsch behandeln oder unter Produktionslast ausfallen. Dafür brauchst du eigene Testpläne.

Ein realistisches Engineering-Beispiel

Nimm ein hypothetisches Team, das ein Qwen3-Next-Modell in einem Referenz-Stack betreibt und für ein Hardware- oder Kostenexperiment einen selbst gehosteten llama.cpp-Pfad ergänzt. Das Team konvertiert dieselben Gewichte, sieht stimmige Vervollständigungen und bereitet einen Rollout vor.

Ein robusterer Rollout beginnt mit einem versionierten Testpaket. Es pinnt Quellartefakt, Konverter- und Runtime-Versionen, Tokenizer, dtype, Backend und eine feste Decoding-Konfiguration. Es enthält Prompts für kurze Generierung ebenso wie für längere zustandsbehaftete Generierung. Vor dem Sampling vergleicht das Team Logits an ausgewählten Positionen. Falls Instrumentierung möglich ist, ergänzt es Checks an den architekturspezifischen Grenzen von Normalisierung und rekurrentem Zustand.

Unterscheidet sich der generierte Text nur bei langen Prompts, nennt das Team dies nicht einfach eine harmlose Veränderung der „Modellpersönlichkeit“. Es grenzt den Vergleich ein: Tokenizer-IDs, anfängliche Graph-Outputs, Normalisierungs-Outputs, rekurrenter Zustand, Cache-Verhalten, dann Logits. Beginnt die Abweichung nahe einer Normalisierungsoperation, hat die Untersuchung ein konkretes Ziel. Bleiben die Logits nah beieinander, während der gesampelte Output auseinanderläuft, kann das Team bewerten, ob für dieses Feature numerische, distributionale oder aufgabenbezogene Akzeptanz gilt.

Der Punkt ist nicht, dass jedes Deployment vollständiges Tensor Tracing benötigt. Das Team wählt Beobachtbarkeit passend zum Risiko. Ein Forschungsrechner kann mehr Unsicherheit akzeptieren als ein Produktionspfad, der nachgelagerte Automatisierung speist. In beiden Fällen ist die auslieferbare Einheit das Modell plus sein Implementierungskontext, nicht eine Weight-Datei mit bekanntem Namen.

Wo diese Disziplin an Grenzen stößt

Es gibt keine universelle Toleranz, die korrekt von inkorrekt trennt. Selbst scheinbar exakte Referenz-Outputs können von Hardware, dtype und Operationsreihenfolge abhängen. Quantisierte und fusionierte Pfade können gültig sein, ohne Full Precision exakt zu entsprechen. Definiere die Anforderung, bevor du das Ergebnis liest.

Auch die Abdeckung ist teuer. Du kannst nicht jedes Gerät, jeden Treiber, jede Prompt-Länge, Batch Shape, jeden Cache-Zustand, jede Modellrevision und jeden Konvertierungspfad vollständig testen. Priorisiere die Pfade, die du auslieferst, die Architekturfunktionen, die sie auslösen, und Änderungen mit dem größten semantischen Risiko. Restrisiko bleibt; ein Coverage-Report sollte das klar sagen.

Eine zweite Falle ist, die Referenz für unfehlbar zu halten. Die Qwen3-Next-Historie zeigt, warum unabhängige Implementierungen ein Problem sichtbar machen können. Doch Übereinstimmung zwischen Implementierungen ist für sich kein Beweis. Bewahre Testeingaben und Baselines auf, damit sie bei einer geänderten Referenz erneut laufen können.

Einfach gesagt: Konformitätstests verringern Unsicherheit. Sie machen aus einem komplexen Inference-Stack keinen Beweis.

Was du diese Woche tun kannst

Wähle eine Modellarchitektur und eine alternative Runtime, die ihr bereits prüft. Schreibe einen einseitigen Akzeptanzvertrag: Referenz, unterstützte Backends, Modellartefakt, Konverter, Tokenizer, Präzision, Sampling-Konfiguration und die Bedeutung von „nah genug“ für euren Einsatz.

Füge dann eine schmale Regression Suite hinzu, bevor du einen breiten Benchmark ergänzt. Nimm deterministische oder eng kontrollierte Checks für Logits oder Hidden States auf, sofern verfügbar, und einige feste End-to-End-Generierungen. Mindestens ein Fall sollte lang genug sein, um Zustand oder Cache-Verhalten auszulösen. Bewahre Eingaben und alle Versionsmetadaten auf, damit Fehler zuordenbar bleiben.

Trenne Release Gates. Ein Konverter-Upgrade, ein neuer fusionierter Kernel und ein neues Quantization-Rezept sollten jeweils die passenden Differential Tests auslösen. Ein bestandener allgemeiner Smoke Test ist kein Ersatz für architekturspezifische Konformität.

Veröffentliche schließlich die verbleibende Unsicherheit zusammen mit dem Artefakt. Gib an, welche Backend- und Präzisionspfade auf welcher Ebene verglichen wurden und welche Fälle noch nicht getestet sind. Das ist keine Bürokratie. Es ist operativ der Unterschied zwischen „Das Modell wurde geladen“ und „Wir wissen, was wir ausliefern“.