Keine NaNs mehr in der Produktion: Das numerische Hygiene‑Playbook für CTOs

Von Diogo Hudson Dias
Senior backend engineer reviewing live system metrics on multiple monitors in a dimly lit operations room.

NaNs sind nicht dramatisch. Sie bringen Ihren Server nicht mit einem Blue Screen zum Absturz und lassen auch nicht die Logs explodieren. Sie schleichen sich leise durch JSON, vergiften Ihre Rankings und lassen Ihre A/B‑Statistiken lügen. Im Jahr 2026 – wenn Sie FP8/BF16‑Inference, quantisierte Embeddings und Third‑Party‑Model‑Calls mischen – sind NaNs die Bug‑Klasse, die Ihr Produkt schleichend verschlechtert, ohne dass es jemand bemerkt, bis sich der Umsatz bewegt.

Hacker News hat kürzlich mit „Two Case Studies of NaN“ eine starke Erinnerung hervorgeholt. Das Fazit: NaNs sind ansteckend. Sie propagieren sich über Berechnungen und tauchen selten als Exception auf. Wenn Sie nicht auf numerische Hygiene designen, schlüpfen sie durch Ihre KI, Ihre Services und Ihr Warehouse. Fixen wir das.

Warum Sie NaNs fast nie sehen (selbst wenn sie da sind)

  • JSON versteckt sie. Die JSON‑Spezifikation verbietet NaN und Infinity. Viele Encoder serialisieren NaN zu null; einige emittieren das literale NaN und hoffen; viele Decoder lehnen die Payload ab. So oder so verlieren Sie Signal oder sanitisieren stillschweigend.
  • Die meisten Sprachen fangen IEEE‑754‑Exceptions standardmäßig nicht ab. Division durch Null, ungültige sqrt, katastrophales exp – nichts davon wirft, außer man fordert es explizit an. Auf GPUs kann man in Produktions‑Runtimes überhaupt nicht trapen.
  • Ihre Metrik‑Pipelines glätten sie. Charting‑Layer zwingen NaN implizit zu Null oder lassen den Punkt fallen. Ihr hübsches Dashboard lügt.

Unterdessen macht ein einzelnes NaN in einem 768‑dimensionalen Embedding die Kosinus‑Ähnlichkeit häufig zu NaN, was viele ANN‑Bibliotheken als schlechtestmöglichen Score behandeln oder komplett überspringen. Das ist kein Crash; das ist eine subtile Recall‑Klippe. Wir haben gesehen, dass Teams 5–10 % Retrieval‑Recall zurückgewinnen, indem sie eine <0,1 %‑NaN‑Rate in Embeddings eliminieren.

Woher NaNs in modernen Stacks kommen

  • Mixed‑Precision Inference/Training. FP16/FP8 sättigen schnell. Softmax auf großen Logits überläuft; Layer‑Norm mit nahezu Null‑Varianz teilt durch ~0; das Zurückcasten nach FP32 ergibt Inf oder NaN.
  • Normalisierung und Statistik. Batches mit Null‑Varianz, log nicht‑positiver Werte, sqrt negativer Zahlen oder Summe leerer Arrays.
  • Drittanbieter‑API‑Ergebnisse. Modellanbieter liefern bei Edge‑Inputs manchmal NaN‑Wahrscheinlichkeiten oder ‑Scores. Ihr SDK erzwingt oder verwirft sie vermutlich, ohne es mitzuteilen.
  • Parser‑ und ETL‑Drift. CSVs mit "" zu Float gemappt; NumPy und Go können bei permissivem Parsing auf fehlerhaften Parses NaN erzeugen. Nachgelagerte Features erben das Gift.
  • Fast‑Math‑Compiler. -ffast-math und äquivalente Flags versprechen Speed, indem sie IEEE‑Garantien lockern. Sie erlauben auch algebraische Transformationen, die ungültige Operationen verstärken.
  • Quantisierungs-/Dequantisierungsfehler. Asymmetrische Skalen nahe Null plus aggressives Clipping können nachgelagerte Nenner praktisch zu Null machen.

Die wahren Kosten eines NaN an der falschen Stelle

  • Ranker und Retrieval. Ein einziges NaN in einem Vektor macht die Kosinus‑Ähnlichkeit zu NaN; der Kandidat fällt evtl. heraus oder landet ganz hinten. Wenn 0,05 % der Queries einen NaN‑Vektor mitbringen, sind das 500 schlechte Ergebnisse pro eine Million Anfragen. Bei 3 % CTR‑Lift durch korrekte Top‑3 verschenken Sie echtes Geld.
  • Tool‑Aufrufe für LLMs. Ein NaN in einem Funktions‑Score kann den Selektor kurzschließen; Ihr Agent „entscheidet“, ein Tool zu überspringen, oder der Planer degeneriert. Das sind keine 5xx – es wirkt, als wäre das Modell „dümmer“ geworden.
  • Datenbanken und Analytics. In Postgres ist NaN ein gültiger Float‑Wert. Er sortiert sich seltsam und kann Deduplizierung/Aggregationen auf überraschende Weise brechen. Wenn Sie nicht mit isfinite(x) filtern, liefern Sie unsinnige Dashboards aus.
  • Pricing und Gebote. Jeder Score, der in einen Gebotsmultiplikator oder eine Rabatt‑Engine fließt, propagiert NaN zu einem Default; wenn Ihr Default großzügig ist – herzlichen Glückwunsch, Sie zahlen zu viel oder rabattieren zu stark.

Ein CTO‑Playbook: erkennen, eindämmen, dann eliminieren

Fangen Sie nicht mit Kernel‑Rewrites an. Machen Sie aus NaNs erst einmal keine Geister, sondern Ereignisse, die Ihre Organisation sehen und umleiten kann. Hier ist ein geschichteter Plan mit konkreten, ressourcenschonenden Schritten.

Schicht 1: Leitungs‑ und Speicherhygiene

  • Verwenden Sie binäre Protokolle, die Float‑Bits erhalten. Bevorzugen Sie Protobuf oder Arrow Flight für KI‑Datenpfade. JSON macht NaNs unsichtbar oder zum Problem eines anderen. Wenn Sie JSON zum Browser verwenden müssen, downcasten Sie oder kodieren Sie Floats als base64, wenn Präzision zählt, und validieren Sie an den Servergrenzen.
  • gRPC/HTTP‑Interceptor, die Payloads scannen. Für Vektoren und Score‑Arrays ist ein einziger Pass günstig. Ein moderner CPU‑Kern streamt 20–40 GB/s; das Scannen eines 3‑KB‑Embeddings bei 1.000 RPS sind ~3 MB/s – ~0,01 Core. Bei Fund: Zähler inkrementieren, Tags für Modell/Version anhängen und entweder bereinigen (nan_to_num) oder je Route hart fehlschlagen.
  • Datenbank‑Constraints. In Postgres CHECK (isfinite(x)) auf Float‑Spalten, die niemals NaN/Inf speichern dürfen. Für Arrays per Trigger oder im Write‑Pfad der Services erzwingen. Schlechte Zeilen früh abzuweisen ist günstiger, als später Warehouses zu reparieren.

Schicht 2: Modellstabilität

  • Stabile Softmax und Normalisierung. Immer vor exp das Maximum der logits abziehen; in FP32 akkumulieren, auch wenn Gewichte FP16/FP8 sind; ein eps (z. B. 1e‑5) zu Nennern in Layer‑/Batch‑Norm hinzufügen.
  • Am Modellausgang begrenzen und bereinigen. Funktionen wie torch.nan_to_num(x, nan=0.0, posinf=1e9, neginf=-1e9) in der letzten Stufe für Embeddings und Scores verwenden. Clamping am Rand ist besser, als NaN in Ranking‑Systeme zu propagieren.
  • Unsichere Fast‑Math in kritischen Pfaden deaktivieren. Compiler und Graph‑Engines bieten Flags, die NaN‑Sicherheit für Speed opfern. Lassen Sie sie bei numerisch sensiblen Ops aus; der Speed‑Gewinn liegt oft bei 0–3 % und ist das Risiko nicht wert.
  • Quantisierung mit Leitplanken. Mit echten Tail‑Inputs kalibrieren; per‑Channel‑Skalen aktivieren; verifizieren, dass Dequant unter extremen Inputs nur finite Werte liefert. Unit‑Tests mit adversariellen Wertebereichen fahren.

Schicht 3: Runtime‑Guards in Ihren Services

  • Sprach‑Level‑Checks. In Python/NumPy: an den Grenzen mit np.isfinite prüfen; in CI und Tests numpy.seterr(all='raise') setzen, um Fehler zu erzwingen. In Go: math.IsNaN, math.IsInf beim Deserialisieren und vor dem Persistieren prüfen. In Java/Kotlin: Double.isFinite.
  • ONNX Runtime/TensorRT Debug‑Gates (zuerst nicht‑prod). In Staging‑Runs Modell‑weite NaN‑Propagation prüfen, um die Schicht zu finden, in der NaNs entstehen. Für PyTorch‑Training fängt torch.autograd.set_detect_anomaly(True) in Debug‑Builds die Quelle; für Inference kritische Ops umhüllen und auf finiten Tensor prüfen.
  • Contracts, keine Kommentare. Schema‑Level‑Verträge in Protobuf ergänzen: einen dedizierten Message‑Typ für Embedding, der bei der Konstruktion validiert wird; keine rohen repeated float32 in Ihrer Flotte herumreichen.

Schicht 4: Observability und Policy

  • Definieren Sie ein NaN‑SLO. Beispiel: „Weniger als 1e‑7 nicht‑finite Werte pro Ausgabe‑Element, pro Modell, pro Tag.“ Als Counter mit Labels tracken: Modell, Version, Layer, Feature, Route.
  • Fail‑closed vs. Sanitize – explizit entscheiden. Für alles mit Geld, Identität oder Security‑Policies: Request fehlschlagen lassen und einen wiederholbaren Fehler oberflächlich machen. Für Ranking/Retrieval: auf sichere Defaults bereinigen und eine hochkardinale Stichprobe zur Reproduktion aufzeichnen.
  • Automatisierte Fallbacks. Überschreitet die NaN‑Rate den Schwellenwert für eine Modellversion oder Route, per Circuit‑Breaker auf einen langsameren, aber stabilen Pfad schalten (z. B. CPU‑Inference oder Last‑Known‑Good), bis ein Mensch prüft.
  • Tail‑Samples 30 Tage aufbewahren. 100–1.000 reproduzierbare Exemplare pro Modell/Version mit exakten Inputs und Seeds sichern, um zu reproduzieren und zu fixen. Sie sind klein (<100 KB je) und im Incident unbezahlbar.

Konkrete Implementierungsmuster (nach Stack)

Python/PyTorch‑Services

  • Alle Modellausgaben mit einem kleinen Utility umhüllen, das Finitheit asserted, auf Bereiche clamped und eine Metrik emittiert. Den Overhead pro Request durch Vektorisierung der Checks im Mikrosekunden‑Bereich halten.
  • In der Vorverarbeitung Benutzereingaben strikt zu Float parsen. Ablehnen oder imputieren; niemals still ein fehlerhaftes Parse zu NaN werden lassen.
  • In Training und Evaluation einen nächtlichen „numerischen Fuzz“‑Job fahren: extreme Inputs sampeln, kleine Störung addieren, Batch‑Größen sweepen; Job fehlschlagen lassen, wenn nach Layer‑Norm/Softmax/Attention irgendwo nicht‑finite Werte auftreten.

Go‑Microservices

  • An API‑Grenzen Float‑Slices mit math.IsNaN/math.IsInf scannen. Für Embeddings: vorallozieren und verzweigungsfrei mit SIMD scannen, wo verfügbar; der Flaschenhals ist Speicherbandbreite, nicht CPU.
  • In gRPC‑Interceptor einen einzigen Pass über bekannte Float‑Felder ergänzen. Bei Detektion den Kontext mit Grund annotieren und zu einem Sanitizer‑Fallback routen.
  • Beim Serialisieren nach JSON (Browser‑Clients) NaN/Inf explizit auf null oder sichere Sentinel‑Werte mappen und einen Header‑Flag setzen. Downstream‑Dienste müssen „sanitized“ als degradierte QoS behandeln.

Postgres, Warehouses und Analytics

  • CHECK (isfinite(col)) auf Float‑Spalten verwenden. Für Arrays eine Write‑Path‑Funktion erwägen, die jedes nicht‑finite Element ablehnt.
  • Queries: Metriken immer mit isfinite(value) filtern und entscheiden, wie nicht‑finite Werte zu behandeln sind – aus Aggregationen ausschließen oder separat bucketen.
  • Wenn Sie rohe Modellausgaben mit NaNs für Forensik speichern müssen, tun Sie das in einer Quarantäne‑Tabelle mit Retention und ohne Joins auf Kern‑Analytics.

ONNX Runtime und TensorRT

  • Bei sensiblen Ops (Softmax, Layer‑Norm) FP16 mit FP32‑Akkumulation bevorzugen. Ihre Engine validieren, indem Sie Eingabebereiche um Ihr reales 99,9‑Perzentil herum sweepen.
  • Einen Post‑Engine‑Plugin ergänzen, der Ausgaben auf Finitheit scannt und bei Verletzungen einen strukturierten Log emittiert. Im Hot Path für Ultra‑Latenz‑SLAs aus; für Canaries und Batch‑Jobs an.
  • Kernel‑Fusionen vermeiden, die die Quelle von NaNs verdecken, bis Sie die Numerik validiert haben; dann Fusionen selektiv wieder aktivieren.

Governance: Machen Sie numerische Hygiene zur Vorgabe, nicht zur „Best Practice“

  • Numerische Abnahmetests in CI. Synthetische Testvektoren ausliefern, die Nullen, Denormals, extreme Logits und leere Tensoren abdecken. „Keine nicht‑finiten Werte irgendwo“ assertieren. Das kostet Minuten und verhindert Wochen Incident‑Jagd.
  • Eigenschaftsbasierte Tests für Math‑Helper. Für jede Funktion mit log/exp/division/sqrt adversarielle Inputs generieren und Finitheit sowie Monotonie‑Eigenschaften assertieren.
  • Checklisten für Model‑Promotion. Vor der Promotion erforderlich: (a) Finitheits‑Scan über 1 Mio. repräsentative Ausgaben; (b) Bereichs‑Histogramme für Schlüsseltensoren; (c) Plan für Sanitize vs. Fail‑closed je Call‑Site.
  • Zuständigkeiten. Ein einzelnes Team benennen, das „numerische SLOs“ über Modelle und Services hinweg owned. Wenn es alle besitzen, besitzt es keiner.

Performance‑Trade‑offs und wie der Overhead winzig bleibt

Ja, Floats zu prüfen kostet etwas. Aber es ist billig, wenn man es richtig platziert und vektorisiert.

  • Kosten des Wire‑Scans. Das Scannen eines 3‑KB‑Embeddings bei 1.000 RPS sind ~3 MB/s. Ein einzelner Kern streamt 20–40 GB/s. Das sind ~0,01 Core für Garantien auf Leitungs‑Ebene – ein Rundungsfehler.
  • Clamping auf Modellebene. torch.nan_to_num ist durch Speicherbandbreite limitiert; auf einem 768‑dimensionalen Tensor sind es einstellige Mikrosekunden auf CPU/GPU. Einmal am Blatt anwenden.
  • Metrik‑Kardinalität. NaN‑Counter sind naturgemäß hochkardinal. Labels auf Modell/Version/Route/Layer begrenzen. Verstöße stichprobenartig für Details in Logs erfassen.
  • Fail‑fast vs. Sanitize. Das ist eine Produktentscheidung. Bei Geld/Security: failen. Bei Ranking: auf stabile Defaults bereinigen und sanft degradieren. Dokumentieren, wo was gilt.

Ihr 90‑Minuten‑, 2‑Tage‑ und 30‑Tage‑Plan

In den nächsten 90 Minuten

  • Fügen Sie Ihrem wichtigsten Embedding/Ranking‑Response einen einzigen Finitheits‑Scan hinzu und emittieren Sie eine Metrik (Modell, Version, Route). Schalten Sie ein Dashboard‑Panel frei, das die tägliche NaN‑Rate zeigt.
  • Fügen Sie in Postgres isfinite‑Filter zu Ihren wichtigsten Analytics‑Abfragen hinzu. Sie werden überrascht sein.

In den nächsten 2 Tagen

  • Führen Sie CHECK (isfinite(x)) auf mindestens einer hochrelevanten Float‑Spalte ein und beheben Sie die ersten Verstöße an der Quelle.
  • Ummanteln Sie Modellausgaben mit nan_to_num oder Äquivalent und setzen Sie sinnvolle Clamps. Führen Sie A/Bs für Retrieval oder Ranking erneut aus – beobachten Sie, wie Recall und CTR sich bewegen.
  • Fügen Sie einen gRPC/HTTP‑Interceptor hinzu, der bekannte Float‑Payloads scannt und Verstöße zählt. Routen Sie „schlechte“ Responses durch einen Sanitizer.

In den nächsten 30 Tagen

  • Kodifizieren Sie ein NaN‑SLO, verknüpfen Sie es mit On‑Call und geben Sie dem zuständigen Team ein Budget zur Ursachenbehebung.
  • Stellen Sie für jedes promotete Modell einen nächtlichen numerischen Fuzz‑Job mit adversariellen Inputs und Batch‑Größen auf. Promotion blockt, wenn irgendwo nicht‑finite Werte auftreten.
  • Entscheiden, dokumentieren und implementieren Sie Fail‑closed vs. Sanitize Policies Route für Route. Auditieren Sie Call‑Sites, die Pricing, Identität und Security berühren.

Warum das jetzt wichtig ist

KI‑Stacks im Jahr 2026 sind per Design numerisch aggressiv: kleinere Datentypen, engere Latenzbudgets, mehr Vektor‑Traffic. NaNs sind das vorhersehbare Nebenprodukt – und sie sind die Bug‑Klasse, die sich mit schnellen Wins am leichtesten ausrotten lässt. Der Overhead ist vernachlässigbar; der Payoff ist sofort: sauberere Rankings, vertrauenswürdige Analytics, weniger „das Modell wurde dümmer“‑Tickets.

In einer Welt, in der Regulatoren Sicherheit und Verlässlichkeit im Blick haben, ist ein NaN‑Hygieneprogramm auch eine Governance‑Story. Sie können Ihrem Board – und Ihren Kunden – in die Augen schauen und sagen: Wir haben nicht nur Experimente; wir haben numerische Verträge. Das ist jetzt der Maßstab.

Key Takeaways

  • NaNs bringen Ihre Systeme selten zum Absturz; sie korrumpieren sie leise – besonders in Retrieval, Ranking und Analytics.
  • JSON versteckt NaNs; verwenden Sie Protobuf/Arrow für KI‑Datenpfade und scannen Sie Payloads an den Grenzen.
  • Fügen Sie Postgres CHECK (isfinite(x)) und Wertebegrenzungen (Clamps) an Modellausgängen für unmittelbare Wins hinzu.
  • Definieren Sie ein NaN‑SLO, entscheiden Sie, wo fail‑closed vs. sanitize gilt, und automatisieren Sie Fallbacks bei Spike‑Raten.
  • Die Performance‑Kosten von Finitheits‑Scans sind winzig (≈0,01 Core pro 1.000 RPS bei 3‑KB‑Vektoren); der ROI ist hoch.

Ready to scale your engineering team?

Tell us about your project and we'll get back to you within 24 hours.

Start a conversation