
Der weltweit erste Hazard-Checker für NVIDIA Blackwell (sm_120), mit einem Assembler und Scheduler, der Byte für Byte gegen den eigenen Compiler abgeglichen ist. Der Check, den sie nie ausgeliefert haben.
Das Problem · Welche GPUs · Wie es funktioniert · Schnellstart · Gemessen, nicht angenommen · Erkenntnisse · API · Methode · Roadmap · Clean-Room
Eine NVIDIA-GPU-Instruktion ist 128 Bit lang, und 21 davon sind überhaupt nicht die Instruktion. Sie sind ein Scheduling-Steuerwort, von stall bis reuse: wie viele Zyklen vor der Ausgabe der nächsten Instruktion gewartet werden soll, welche Scoreboards signalisiert werden sollen, auf welche gewartet werden soll und welche Operanden aus dem Reuse-Cache bedient werden dürfen.
Die Hardware prüft nichts davon. Auf sm_120 gibt es keine Verriegelung bei Instruktionen mit fester Latenz. Das Silizium vertraut dem, was das Steuerwort erzeugt hat. Wenn eine Stall-Anzahl kürzer ist als die Latenz eines Werts, den die nächste Instruktion konsumiert, tritt kein Fehler auf, nichts wartet, und keine Warnung wird ausgegeben. Die Instruktion liest ein Register, das noch nicht geschrieben wurde, und rechnet jedes Mal mit veralteten Daten, mit voller Geschwindigkeit.
Das ist eine seltsame Art von Bug. Er stürzt nicht ab. Er taucht nicht im Debugger auf. Er erzeugt Zahlen, die lediglich falsch sind, was bei einer Matrixmultiplikation oder einem Attention-Kernel bedeutet, dass ein Modell leicht schlechter trainiert, statt sichtbar zu brechen.
Die drei billigsten Kodierungen sind die kaputten, und niemand irgendwo meldet es. Beachte außerdem, was der linke Balken tut: Ein Stall von null ist nicht null Zyklen, sondern eine eigene sichere Kodierung, die auf ausstehende Ergebnisse wartet und etwa neunmal so viel kostet wie eine schedulierte Instruktion. Ein Prüfer, der das als null lesen würde, würde korrekte Programme als defekt bezeichnen.
Werkzeuge, die Maschinencode für diese Architektur erzeugen, weisen diese Steuerbits anhand eines Latenzmodells zu. basalt ist das Werkzeug, das die Antwort prüft.
NVIDIA gibt dir einen Compiler, der diese 21 Bits schreibt. Es gibt dir nichts, das sie zurückliest und dir sagt, dass sie sicher sind, und auch sonst niemand.
Assembler für NVIDIA-GPUs gibt es seit einem Jahrzehnt, die Blackwell-Kodierung wurde bereits früher per Reverse Engineering untersucht, es gibt veröffentlichte Charakterisierungen von sm_120 auf Zyklenebene, und ein öffentlicher Assembler für diese Architektur weist die Scheduling-Steuerbits bereits selbst zu und führt seine eigenen Kernel auf einer Karte aus, um zu sehen, dass die Antworten stimmen. Alles wahr, und nichts davon ist die Behauptung:
Nichts anderes kann ein cubin vorgelegt bekommen, das es nicht selbst erzeugt hat, und gefragt werden, ob seine Scheduling-Steuerbits sicher sind.
Dein Compiler hat dieses cubin erzeugt, oder eine Bibliothek hat es ausgeliefert, oder jemand hat es von Hand geschrieben, und bis jetzt gab es keine Möglichkeit zu fragen. Auf einer Architektur ohne Hardware-Verriegelung ist das der Unterschied zwischen „es lief“ und „es ist korrekt“, und der Unterschied ist unsichtbar: Ein Stall, der einen Zyklus zu kurz ist, liest ein veraltetes Register und liefert jedes Mal mit voller Geschwindigkeit eine falsche Zahl zurück, ohne Fehler und ohne Warnung.
Alles andere hier existiert, um diesen Satz testbar zu machen. Der Assembler ist das, was ein Programm mit einem absichtlich verkürzten Stall baut. Der Scheduler ist das, was das Modell zwingt, sich auf eine Antwort festzulegen, statt die eines anderen zu benoten. Und das Audit ist der Punkt, an dem der Satz aufhört, eine Abwesenheit zu sein, und zu einer Messung wird: basalt, gerichtet auf 2,473 sm_120-Cubins, die NVIDIA in cuBLAS, cuSOLVER, cuSPARSE, NPP und dem Rest ausliefert.
Warum es nicht existierte, in den eigenen Worten des Fachgebiets. Der am häufigsten verwendete SASS-Assembler sagt in seiner eigenen Dokumentation, dass „die rigorose Korrektheit des gesamten Programms … [ist] alles andere als möglich ohne offizielle Unterstützung. Es bleibt also dem Benutzer überlassen, die Korrektheit des Programms zu garantieren, mit sehr begrenzter Hilfe des Assemblers.“ SIP stellt zum Autotuning von SASS-Schedules fest, dass „Validierung für GPU-native Assembly-Codes unmöglich ist, weil die formale Semantik des sass closed-source ist.“
Beide betreffen die semantische Korrektheit: ob ein Kernel das berechnet, was er berechnen soll. basalt beantwortet das nicht, und nichts hier behauptet es. Es beantwortet eine streng kleinere Frage, und der Punkt ist, dass die kleinere Frage ohne die Semantik entscheidbar ist:
Decken die Steuerbits dieses Programms seine eigenen Datenabhängigkeiten ab?
Dazu braucht es die Abhängigkeitsstruktur, die die Kodierung preisgibt, und ein Latenzmodell, das das Silizium unter Messung preisgibt. Weder erfordert es zu wissen, was eine Instruktion berechnet. Ein Kernel kann diese Prüfung bestehen und trotzdem der falsche Algorithmus sein; was er nicht tun kann, ist, ein Register zu lesen, bevor der Wert ankommt.
Zweitens: Nichts anderes wird an den eigenen Bytes des Herstellers gemessen. Die Referenz von basalt ist die ptxas-Ausgabe, daher ist eine Abweichung basalts Bug, bis das Gegenteil bewiesen ist. Sein Assembler muss die exakten 128 Bits des Compilers reproduzieren. Sein Scheduler muss jedes Steuerbit, das der Compiler gewählt hat, verwerfen, neue berechnen und die GPU dieselbe Antwort berechnen lassen.
Ein Standard für alles: exakt mit dem Hersteller übereinstimmen – oder sagen, warum nicht.
| Komponente | Funktion | Wie es geprüft wird | Ergebnis |
|---|---|---|---|
| Assembler | SASS-Text in das 128-Bit-Wort | Jede von ptxas ausgegebene Instruktion reassemblen und Bytes vergleichen, auf dem Korpus und auf 5,2 M Instruktionen ausgelieferten Bibliothekscodes, den es nie gesehen hatte | 59,693 von 59,760 Korpus-Instruktionen und 4,585,336 von 5,237,448 ausgelieferten exakt, der Rest namentlich verweigert, 0 falsch in beiden |
| Prüfer | Liest einen Schedule, meldet Hazards | Die eigene Ausgabe des Herstellers muss sauber verifizieren, und ein absichtlich verkürzter Stall muss erkannt werden | 0 Fehler über 1,323 Paare aus Hersteller-Kernels und Optimierungsstufen, 0 übersehen bei 233 kaputten |
| Audit | Derselbe Prüfer, auf ausgelieferten Bibliotheken | Über Produktions-sm_120-Kernels ausführen, die aus jeder Tabelle, die es liest, zurückgehalten wurden | 0 Fehler über 2,762 Kernels und 10,218,030 Abhängigkeiten, alle 2,762 vollständig analysiert |
| Scheduler | Weist jedes Steuerbit von Grund auf zu | Die des Herstellers verwerfen, neue berechnen, beide auf der GPU gegen acht Eingaben ausführen und Ausgabebytes vergleichen | 439 von 439 vergleichbaren Kernels byte-identisch, auf allen drei Optimierungsstufen |
Und der Teil, über den ein Scheduler normalerweise schweigt: was die Korrektheit kostet. Die Schedules von basalt verbrauchen das 1,05-fache der Issue-Zyklen des Herstellers, langsamer bei 111 der 1,323 Paare aus Kernels und Optimierungsstufen und billiger bei 842, wobei jeder vergleichbare Kernel auf der GPU weiterhin byte-identisch ist.
Die dritte Zeile ist diejenige, die die anderen drei verändert hat. Ein Prüfer, der auf einem Korpus kalibriert wurde, kann auf diesem Korpus nicht scheitern: Die engste Lücke, die der Compiler je hinterließ, ist per Konstruktion die Untergrenze, und zwar genau für den Code, an dem er gemessen wurde. Als dieser Prüfer zum ersten Mal Code von woanders sah, meldete er sechsundzwanzig Hazards pro Kernel in einem JPEG-Dekoder, der noch nie ein falsches Pixel geliefert hat, und alle 6.593 stammten von basalt. Das Beheben brachte ihn auf null, und null über eine Bibliothek war auch kein Beleg: Als die zurückgehaltene Menge auf drei Bibliotheken und 5,2 Millionen Instruktionen erweitert wurde, sprang sie direkt zurück auf 940 und fand zusätzlich zu den ersten acht fünf weitere Modellfehler. Dreizehn Korrekturen, keine davon von NVIDIA, und die aus 24.311 ausgelieferten Kernels neu gewonnene Anforderung setzte ein Guard-Prädikat bei 13 Zyklen über 229.567 Beobachtungen, was die Zahl ist, die Fehlerinjektion auf dieser Karte durch absichtliches Brechen eines Programms gemessen hatte. Siehe Erkenntnis 32.
Billiger als der Hersteller zu sein, ist kein Anlass zur Selbstzufriedenheit. basalt plant jede Abhängigkeit mit der engsten Lücke, die ptxas je für genau diese Paarung hinterließ, und ptxas balanciert Registerdruck und Speicher neben der Issue-Latenz, während dieses hier eine einzelne Zahl optimiert. Es wurde auch nicht auf den ersten Blick geglaubt: Als das Verhältnis zum ersten Mal unter 1,0 fiel, brach der Hardware-Roundtrip, und die Zahl stand erst, nachdem der dadurch aufgedeckte Bug behoben war. Aus diesem Grund ist das Verhältnis im Test-Suite von beiden Seiten festgenagelt.
Die mittlere Spalte ist der Punkt. Ein Prüfer und ein Scheduler, die sich ein Latenzmodell teilen, stimmen miteinander überein, während beide falsch liegen, also ist keiner ein Beleg für den anderen; nur das Silizium hat kein Interesse an dem Argument. Den Scheduler über sieben handgeschriebene Kernels laufen zu lassen, bestand lange Zeit sieben von sieben. Als er über dreihundert lief, fand er einundvierzig falsche, und jede Korrektur in den Erkenntnissen entstand dadurch, diese Zahl sich bewegen zu sehen.
Dasselbe gilt für die Eingaben. Ein veralteter Lesevorgang ändert die Antwort nur, wenn sich der veraltete Wert und der frische unterscheiden, also ist ein Byte-Muster eine Chance, es zu bemerken, und das Ausführen jedes Kernels gegen ein zweites, drittes und viertes Muster fand sofort ein Carry-out-Prädikat, das das Operandenmodell von Anfang an als Quelle gelesen hatte. Es hatte jede Kontrolle bis zu diesem Punkt überlebt, einschließlich des Roundtrips selbst.
Dieselbe Disziplin entscheidet, was der Assembler tun darf, und es lohnt sich, die beiden Zahlen, die er hat, zu trennen.
Die Abdeckung beträgt 99,9 % des Korpus und 87,5 % des ausgelieferten Bibliothekscodes. Die Korrektheit beträgt 100 %, und das ist die Zahl, die durch einen Test festgelegt ist. Die Lücke dazwischen sind Instruktionen, die basalt verweigert, jede mit Benennung des Feldes, das es nicht zuordnen konnte, denn ein Werkzeug, das raten würde, würde volle Abdeckung erreichen, indem es Wörter ausgibt, die zum richtigen Text zerlegt werden und etwas anderes berechnen. Es hat nie eines ausgegeben, über 59.760 Korpus-Instruktionen und 5.237.448 ausgelieferte hinweg.
Dorthin kam es erst nach acht getrennten Runden selbstsicheren Falschliegens:
Jede einzelne davon erzeugte ein Wort, das assembliert, zurück zum exakt gleichen Text, aus dem es stammte, disassembliert und etwas anderes berechnet. Das ist derselbe Fehler, den der Rest dieses Repositoriums zu fangen existiert, weshalb alle acht jetzt mit einer Begründung verweigert werden, die benennt, was das Feld wirklich enthält, und weshalb die Zahl der Instruktionen, die zu den falschen Bytes assemblieren, ein Test ist, der bei null festgelegt ist, statt einer Zahl in einer Tabelle.
Ein neunter tauchte beim ersten Mal auf, als der Assembler auf Maschinencode gerichtet wurde, den er nicht erzeugt hatte, und er war anderer Art. c[0x0][UR4] indiziert seinen Offset über ein Register, wo die aufgezeichnete Form eine Zahl enthält, und der Encoder warf einen Fehler, statt zu verweigern. Ein Absturz bei fremder Eingabe ist schlimmer als ein falsches Urteil, weil der Aufrufer keins von beidem bekommt.
sm_120 ist keine Modellnummer. Es ist die Compute Capability, die die gesamte Consumer-Blackwell-Reihe gemeinsam hat, daher gelten die Instruktionskodierung, die Datenbank, der Assembler und der Prüfer für jede Karte darin:
| Karte | Compute Capability | Abgedeckt |
|---|---|---|
| GeForce RTX 5090, 5090D | 12.0 (sm_120) | ja |
| GeForce RTX 5080, 5070 Ti, 5070 | 12.0 (sm_120) | ja |
| GeForce RTX 5060 Ti, 5060, 5050 | 12.0 (sm_120) | ja |
| GeForce-RTX-50-Serie-Laptop-Modelle | 12.0 (sm_120) | ja |
| RTX PRO Blackwell-Workstation-Karten | 12.0 (sm_120) | ja |
| Datacenter-Blackwell (B100, B200, GB200) | 10.0 (sm_100) | nein, andere Kodierung |
ptxas zielt auch auf sm_121, einen anderen Chip derselben Familie. basalt ist nie auf einem gelaufen und behauptet nicht, es zu unterstützen. Was es sagen kann, ist gemessen: Der Compiler erzeugt byte-identischen Code, einschließlich der Steuerwörter, für alle sechs hier angebotenen Ziele, also ist der Schedule, den ein Kernel braucht, eine Eigenschaft der Architektur und nicht des Bauteils (Erkenntnis 28). Wenn das nicht wahr wäre, würde NVIDIAs eigener Compiler für eines davon einen unsicheren Schedule ausgeben.
Jede auf Silizium gemessene Zahl stammt von einer physischen Karte, die exakt benannt ist, weil „eine 5070 Ti“ nicht ausreicht, um einen Lauf zu reproduzieren:
| Die Karte | Genau was sie ist |
|---|---|
| Board | Gigabyte GeForce RTX 5070 Ti EAGLE OC |
| Vom Treiber gemeldet | NVIDIA GeForce RTX 5070 Ti |
| Compute Capability | 12.0 |
| Streaming-Multiprozessoren | 70 |
| Boost-Takt | 2542 MHz |
| Toolchain | CUDA 13.3.1, ptxas V13.3.73 |
Der größte Teil von basalt braucht überhaupt keine GPU. Beide Orakel, die Instruktionsdatenbank, der Assembler und der Hazard-Checker laufen gegenüber ptxas und nvdisasm als gewöhnliche Subprozesse, weshalb sie in CI auf einer Maschine ohne Grafikkarte laufen. 237 der 252 Tests gehören zu dieser Gruppe, und 200 brauchen weder eine Karte noch die NVIDIA-Binaries.
Eine GPU wird für genau drei Dinge benötigt, und das sind die drei, die aus einem plausiblen Werkzeug ein glaubwürdiges machen:
| Braucht eine Karte | Warum |
|---|---|
measure, probe-stalls | Eine Instruktion timen und herausfinden, was eine Abhängigkeit wirklich erfordert, indem man sie bricht |
scripts/roundtrip_corpus.py | Jeden Korpus-Kernel neu schedulen und beide Versionen ausführen, um Ausgabebytes zu vergleichen |
scripts/agreement_sweep.py | Eine Abhängigkeit pro Kernel verkürzen und das Silizium fragen, ob basalt richtig lag |
Das Werks-Overclocking verschiebt die Messungen nicht. Jede Latenz hier ist in Zyklen angegeben, was eine Eigenschaft der Pipeline und nicht des Takts ist, und die Boost-Zahl wird nur daneben aufgezeichnet, damit ein Echtzeit-Vergleich möglich bleibt. Was das Board beeinflusst, ist die Reproduzierbarkeit, weshalb basalt measure --board sie erfasst.
Alles, was hier gemessen wurde, wurde auf einer Karte gemessen, und basalt zeichnet die SKU neben jeder Messung auf, statt sie als universell darzustellen. Eine 5090 hat mehr als doppelt so viele SMs und ein eigenes Taktverhalten; die Kodierung wird identisch sein und die Latenzen sollten neu gemessen statt angenommen werden:```bash python -m basalt.cli measure -o my-card.json python -m basalt.cli verify kernel.cubin --latencies my-card.json
Das ist keine Bescheidenheit. Ein Latenzmodell, das zwischen einem Prüfer und einem Planer geteilt wird, ist genau
der Ort, an dem sich eine falsche Zahl versteckt, daher ist eine zweite Karte das Nützlichste, was jemand beitragen kann.
</details>
## Wie es funktioniert
Alles beruht auf zwei Orakeln, die beide unveränderte NVIDIA-Binärdateien sind und als externe Prozesse ausgeführt werden. Es werden keine NVIDIA-Quellen, Header oder Bibliotheken verwendet oder weitergegeben.
| Orakel | Aufruf | Was es liefert |
| :--- | :--- | :--- |
| **Referenz** | `ptxas` → cubin → `nvdisasm -c -hex` | Kodierungen, die der Hersteller-Compiler tatsächlich ausgibt. Semantik unbestritten. |
| **Probe** | `nvdisasm -b SM120a` über rohe Bytes | Dekodiert Wörter, die `ptxas` nie ausgibt, was den Kodierungsraum in etwas Durchsuchbares verwandelt statt in etwas, das man erraten muss. |
Das Probe-Orakel ist das entscheidende. Ein Werkzeug, das auf Compiler-Ausgabe beschränkt ist, kann nur das wiederentdecken, was der Compiler bereits tut. Wenn man synthetisierte 128-Bit-Wörter direkt dem Dekoder zuführt, lässt sich der Befehlssatz *messen*.
Keines der Orakel benötigt eine GPU, sodass die gesamte Befehlsdatenbank auf jeder Maschine in CI neu aufgebaut wird.
### Die Kodierung ableiten, indem man sie verändert
basalt liest nirgendwo eine Tabelle von Opcodes ein. Es nimmt eine Kodierung, die assembliert wurde, dreht ein Bit um, dekodiert das Ergebnis und zeichnet auf, was sich geändert hat. Ein Bit, das das Zielregister ändert, ist ein Zielbit; ein Bit, das den Mnemonic ändert, ist ein Selektor; ein Bit, das nichts Beobachtbares ändert, ist inert.
Gegen `IADD R5, R5, 0x2a` ausgeführt, ergibt die Messung:```
operand[0] bits 16:23 flip 16 -> R4, flip 17 -> R7 destination register
operand[1] bits 24:31 plus bit 72, which negates it source register
operand[2] bits 32:63 flip 32 -> 0x2b, flip 33 -> 0x28 32-bit immediate
opcode bits 2, 4, 12:15
inert 36 bits no observable effect
invalid 11 bits the decoder rejects the mutation
Acht-Bit-Registerfelder und eine 32-Bit-Immediate, ermittelt durch Experimente statt durch Annahmen.
Das Layout bestätigt sich bei der Anwendung selbst. In einem trivialen Kernel setzt S2R write_barrier=0, und der IMAD, der das Ergebnis konsumiert, trägt wait_mask=0x01; LDCU.64 setzt write_barrier=1, und das abhängige STG.E trägt wait_mask=0x02. Jedes Erzeuger-Verbraucher-Paar stimmt überein, und Instruktionen, die nvdisasm mit .reuse annotiert, haben das passende Reuse-Bit gesetzt.
Keine CUDA-Installation und keine GPU. Das Toolchain-Skript lädt gepinnte Redistributables herunter, etwa 45 MB, keine Administratorrechte, nichts wird zu Ihrem PATH hinzugefügt.```bash git clone https://github.com/sunnypatell/basalt.git cd basalt python -m venv .venv && source .venv/bin/activate # Windows: ..venv\Scripts\Activate.ps1 pip install -e ".[dev]"
python scripts/fetch_toolchain.py # pinned ptxas + nvdisasm python -m basalt.cli doctor # verify both oracles end to end python scripts/verify_all.py # every control in this README, in order
Der Eingabeabschnitt ist leer – es wurde kein Inhalt für die Übersetzung bereitgestellt.```console
$ python -m basalt.cli doctor
ok toolchain V13.3.73 in third_party/cuda/13.3.1/bin
ok ptxas assembled sm_120a
ok cubin oracle 16 instructions with encodings
ok probe oracle 16/16 mnemonics round-tripped
both oracles healthy. no GPU required for anything above.
pip install basalt-sass installiert die CLI, die Bibliothek und alle drei gemessenen Tabellen, ohne
Laufzeitabhängigkeiten. Nutzen Sie dies, wenn auf dem Rechner bereits CUDA vorhanden ist; der Checkout oben
ist die richtige Wahl, wenn dies nicht der Fall ist oder wenn Sie die Messungen reproduzieren möchten.```bash
pip install basalt-sass
basalt doctor
basalt verify kernel.cubin
### Wo es nach `ptxas` und `nvdisasm` sucht
basalt treibt beide als externe Prozesse an und bündelt keines von beiden, daher muss es eine Kopie finden.
Es nimmt die erste, die antwortet, und **jede CUDA-13-Installation ist geeignet**: Es muss sich nicht um die festgepinnte Redistributable handeln.
| Reihenfolge | Wo |
| ---: | :--- |
| 1 | `--cuda-bin`, auf der Kommandozeile übergeben |
| 2 | `BASALT_CUDA_BIN`, ein Verzeichnis, das beide Binärdateien enthält |
| 3 | `CUDA_PATH`, `CUDA_HOME` oder `CUDA_ROOT`, jeweils plus `/bin` |
| 4 | `ptxas` im `PATH` |
| 5 | `third_party/cuda/<version>/bin` in einem Checkout, neueste zuerst |
`basalt doctor` gibt aus, welche es aufgelöst hat, und beendet sich mit einem Exit-Code ungleich Null, wenn es keine finden kann, sodass es als Voraussetzung für einen Build-Schritt fungiert und nicht nur als etwas zum Lesen.
Das Abfragen der Instruktionsdatenbank benötigt überhaupt keine Toolchain, da die Datenbank im Voraus gemessen wird und im Paket enthalten ist:```bash
basalt isa --stats
basalt isa --opcode QMMA
Erstellen Sie die Anweisungsdatenbank von Grund auf neu oder fragen Sie die festgeschriebene ab:```bash python -m basalt.cli build-isa # harvest, probe, write src/basalt/data/isa/sm_120a.json python -m basalt.cli isa --stats python -m basalt.cli isa IMAD.WIDE.U32 # one form, with its measured field layout python -m basalt.cli isa --opcode QMMA # every form of one opcode
### Maschinencode prüfen, den du nicht geschrieben hast
Das ist der Teil, den sonst nichts übernimmt, und er benötigt weder GPU noch Argumente. Das gemessene
Latenzmodell und die ermittelte Anforderungstabelle sind beide eingecheckt, sodass ein frischer Klon
direkt auf eine Cubin-Datei gerichtet werden kann, was auch immer sie erzeugt hat:```bash
python -m basalt.cli verify kernel.cubin
Bitte fügen Sie den zu übersetzenden Markdown-Inhalt ein.```console $ python -m basalt.cli verify nvjpeg.sm_120.cubin 25 kernels, 0 with an error 7984 instructions in 789 blocks, 11580 dependencies checked across blocks: 0 errors, 3 warnings pair data: 3957 pairings from 25634 kernels, 427 producers with enough observations to use latency model: measured on NVIDIA GeForce RTX 5070 Ti
Eine ELF-Bibliothek enthält Hunderte von Kerneln, und jeder wird einzeln geprüft, da die Offsets bei Null neu beginnen und nichts von einem in den nächsten übergeht. Fügen Sie `--strict` hinzu, um bei einem Hazard mit einem Nicht-Null-Exitcode zu beenden, was ein Build-Schritt möchte. Wenn Sie eine sm_120-Karte haben und das Modell auf Ihrer eigenen Hardware statt auf der in diesem Repository messen möchten:```bash
python -m basalt.cli measure -o my-card.json # needs a GPU, once
python -m basalt.cli verify kernel.cubin --latencies my-card.json
Alles, was die CLI tut, ist importierbar, und die Bibliotheksoberfläche mit ausführbaren Beispielen ist
in docs/API.md.
Und die beiden Kontrollen, die den Rest ehrlich halten. Die erste benötigt eine Grafikkarte; die zweite benötigt die mitgelieferten Bibliotheken und keinerlei Hardware:```bash python scripts/roundtrip_corpus.py # reschedule all 441 corpus kernels, run both on the GPU
python scripts/fetch_toolchain.py --libs # ~1.2 GB, no admin, nothing on PATH python scripts/audit_shipped.py --libs third_party/cuda/13.3.1/libs
Es wurde kein Inhalt zum Übersetzen bereitgestellt.```console
$ python -m basalt.cli verify kernel.cubin --latencies src/basalt/data/latency/rtx-5070-ti.json
kernel.cubin
32 instructions in 3 blocks, 23 dependencies checked: clean
latency model: measured on NVIDIA GeForce RTX 5070 Ti
Die Zahlen hier werden von den Werkzeugen ausgegeben und aus einem sauberen Checkout neu erzeugt. Die obigen Befehle sind die Quelle der Wahrheit; diese Tabellen sind Momentaufnahmen.
Befehlsdatenbank. Jeder Eintrag trägt die Codierung, die tatsächlich assembliert wurde, und den Compiler-Build, der sie erzeugt hat.
| Befehlsdatenbank | Anzahl |
|---|
Die Tensor-Abdeckung ist dort, wo die Hardware mit niedriger Präzision lebt: HMMA und IMMA, QMMA über die FP8-, FP6- und FP4-Typen, einschließlich asymmetrischer Operandenpaare, die Skalenfaktor-Formen QMMA.SF und OMMA.SF, die einen Pro-Block-Exponenten tragen, das sparse IMMA.SP und die Matrix-Bewegungsbefehle LDSM, STSM und MOVM in jeder Form, einschließlich der transponierenden Varianten.
Latenz, auf einer RTX 5070 Ti. 70 SMs, jede Anpassung R² ≥ 0.9998. Gemessen durch Zeitnahme abhängiger Ketten und Ermittlung der Steigung, wobei die Kettenlänge aus dem kompilierten SASS ausgelesen und nicht angenommen wird.
Drei davon widersprechen dem angenommenen Modell, mit dem basalt ausgeliefert wurde: Für DADD wurden 48 angenommen, für POPC 4, und für jede Konvertierung 6 – gegenüber 24 gemessenen für den Roundtrip.
Ein angenommenes Latenzmodell ist keine kleine Näherung eines gemessenen – das ist das gesamte Argument für das Messen.
Und ein Stall von null ist nicht null Zyklen. Es ist eine eigene sichere Codierung, die auf ausstehende Ergebnisse wartet und etwa 37 Zyklen kostet, während eine geplante Anweisung 4 kostet. Deshalb erzeugt ptxas -O0 ein vollständig genulltes Steuerwort, und der Code rechnet trotzdem korrekt, etwa neunmal langsamer.
Es stimmt mit dem Compiler des Herstellers bei jedem Kernel im Korpus überein. Jeder Kernel, den ptxas aus dem Korpus baut, wird gegen seine eigene Planung verifiziert, auf jeder Optimierungsstufe, die einen Zeitplan erstellt: 30.421 Abhängigkeiten, null Fehler. Dieser Durchlauf läuft bei jedem Push in der CI, und jeder Modellierungsfehler, den dieses Projekt gemacht hat, wurde von ihm gefangen und nicht durch Nachdenken.
Die Urteile stimmen mit dem Silizium überein. Für jeden codierbaren Stall auf einem abhängigen Produzenten stimmen die statische Antwort von basalt und das, was die Hardware tatsächlich berechnet, überein, einschließlich des Nullfalls. Das wird als Test gehalten, nicht hier behauptet. Vollständige Beweise, einschließlich dreier unabhängiger Methoden für den erforderlichen Stall und der unterwegs vorgenommenen Korrekturen, finden sich in findings.
Und wenn es sagt, dass ein Zeitplan unsicher ist, stimmt das Silizium zu. Nehmen Sie den eigenen funktionierenden Zeitplan des Herstellers für 233 Kernel, verkürzen Sie in jedem eine echte Abhängigkeit und vergleichen Sie basalts Urteil mit dem, was die GPU berechnet: 79, die es als defekt bezeichnete, waren defekt, und nichts, das es als sicher bezeichnete, berechnete eine falsche Antwort. Diese Zahl begann bei 34 übersehenen statt bei null, und findings sagt, was die Ursache war und was die Behebung an Fehlalarmen kostete, denn ein Durchlauf, der immer nur seine endgültige Zahl meldete, wäre weniger wert als einer, der seine erste meldete.
Der Verifizierer beantwortet, ob ein Zeitplan sicher ist. Der Scheduler beantwortet, wie ein sicherer Zeitplan aussehen würde, aus denselben Messungen: Er verwirft jedes von ptxas erzeugte Steuerbit, berechnet seine eigenen, gibt das Ergebnis an den Verifizierer zurück und führt es dann auf der GPU neben der Version des Herstellers desselben Kernels aus.
Über den gesamten Korpus auf der Karte ausgeführt, ergeben alle 439 vergleichbaren Kernel auf jeder Optimierungsstufe, die einen Zeitplan erzeugt, byteidentische Ergebnisse zum Zeitplan des Herstellers, und zwar aus Steuerbits, die basalt selbst ermittelt hat. Die 2, die ausgeschlossen sind, lesen die Uhr und die Grid-ID, stimmen also auch mit sich selbst nicht überein, und findings sagt das, statt sie in einen Prozentsatz einfließen zu lassen.
Diese Kontrolle ist der Grund, warum der Rest vertrauenswürdig ist. Der Prüfer und der Scheduler lesen dasselbe Latenzmodell, sodass ein falscher Eintrag darin beide gleichzeitig befriedigt und sie miteinander übereinstimmen, während beide falsch liegen. Nur das Silizium hat kein Interesse an dem Argument. Lange Zeit bestand der Scheduler bei sieben handgeschriebenen Kerneln sieben von sieben; als er über dreihundert lief, fand er einundvierzig falsche, und jede Modellkorrektur seither entstand aus der Beobachtung, wie sich diese Zahl bewegte.
Dieser Kreislauf war die Quelle der eigentlichen Fehler. Ein Stall, der außerhalb des Fensters zwischen einem Produzenten und seinem Konsumenten verbracht wird, zählt nichts, und ihn dort zu verbringen, beendet die Suche mit einem Programm, das immer noch zu kurz ist. Ein an die sichere Codierung gehefteter Stall wurde von einem späteren Durchlauf überschrieben, wobei eine Garantie durch eine kleine Zahl ersetzt wurde. fp64-Operanden belegen Registerpaare, ohne dass der Mnemonic darauf hinweist, sodass die Hälfte jeder fp64-Abhängigkeit sowohl für den Prüfer als auch für den Scheduler unsichtbar war. Ein Prädikat, das als Guard einer Anweisung verwendet wird, braucht dreizehn Zyklen, während dasselbe Prädikat als Daten gelesen fünf braucht, denn ein Guard muss aufgelöst sein, bevor die Anweisung überhaupt ausgegeben wird. Und das Warten auf ein Scoreboard löst eine Abhängigkeit nicht vollständig: Der Produzent schuldet immer noch einen kleinen eigenen Stall, zwei Zyklen für fp64-Add, und ein Zyklus weniger ist stillschweigend falsch. Keiner dieser Fehler wurde durch Nachdenken gefunden; jeder wurde gefunden, indem man die Ausgabe ausführte und die falsche Zahl erhielt.
[!NOTE] 1.0, und konkret dazu, was das bedeutet. Was getan ist: beide Orakel, die Befehlsdatenbank mit nachweislich beschreibbaren Feldern, der Hazard-Prüfer über einen echten Kontrollflussgraphen, Latenz gemessen auf einer SKU mit drei unabhängigen Methoden, ein Scheduler, der jeden vergleichbaren Korpus-Kernel byte für byte durch die Hardware hin- und zurückführt, und eine Prüfung von 2.762 ausgelieferten Kerneln, die aus jeder Tabelle, die der Prüfer liest, herausgehalten wurden. Was nicht getan ist: 12 Korpus-Kernel, die konstruktionsbedingt nicht lauffähig sind, und 2, deren Herstellerausgabe nicht deterministisch ist, alle in den Befunden genannt; zehn Opcodes tragen immer noch eine angenommene statt einer gemessenen Latenz, keiner von ihnen jemals ein Produzent in einem der beiden Codekörper; und es wurde nur eine GPU gemessen, was finding 28 zeigt, dass es weniger ausmacht, als es klingt. Wo etwas abgeleitet statt gemessen wird, sagt das Werkzeug das, statt es zu einer Tatsache aufzurunden. Siehe roadmap und method.
```
src/basalt/
toolchain.py Locating and driving ptxas / nvdisasm
encoding.py The 128-bit instruction word and its control fields
disasm.py Both oracles: cubin ground truth and raw-word probe
harvest/ PTX corpus generation and encoding extraction
probe/ Differential bit probing and field inference
isa/ The generated instruction database and its builder
asm/ The assembler, and the ELF reader that rewrites words in place
sched/ Assigning the control bits, and costing the result
verify/ Register def-use analysis, hazard model, latency checking
gpu/ Driver-API bindings and the latency measurement harness
src/basalt/data/ The measured tables, inside the package so an installed copy
has them: the ISA database, the latency model and the mined
stall requirement
docs/ Findings, method, the Python API, roadmap, artwork sources
scripts/ Toolchain fetch, asset rendering, drift check, and the two
hardware controls: the corpus round trip and the agreement sweep
tests/ Unit tests, plus toolchain- and GPU-marked suites
</details>
## Clean-Room-Position
basalt ist eine unabhängige Clean-Room-Arbeit für Interoperabilität. Es enthält keinen NVIDIA-Quellcode, keine Header, keine Bibliotheken und keine Dokumentation und verteilt auch nichts davon weiter. Es beobachtet das Verhalten öffentlich verteilter ausführbarer Dateien und zeichnet es auf – das ist die Grundlage, auf der diese Art von Arbeit seit über einem Jahrzehnt steht.
NVIDIA, CUDA und Blackwell sind Marken der NVIDIA Corporation. Dieses Projekt ist weder mit NVIDIA verbunden noch von NVIDIA unterstützt oder gesponsert.
Lizenziert unter [Apache-2.0](https://github.com/sunnypatell/basalt/blob/main/LICENSE). Bewusst Apache statt etwas Restriktiverem: Ein Korrektheitswerkzeug, auf dem niemand aufbauen darf, ist ein Korrektheitswerkzeug, das niemand ausführt, und die Patentlizenz ist für Arbeiten, die so nah an der Hardware sind, von Bedeutung.
## Mitwirken
Der wertvollste Beitrag ist eine Kodierung, die basalt falsch verarbeitet. Siehe [`CONTRIBUTING.md`](https://github.com/sunnypatell/basalt/blob/main/CONTRIBUTING.md) und die [ISA-Lückenvorlage](https://github.com/sunnypatell/basalt/blob/main/.github/ISSUE_TEMPLATE/isa_gap.yml), die genügend Informationen sammelt, um das Problem ohne deine Maschine zu reproduzieren.
[`SUPPORT.md`](https://github.com/sunnypatell/basalt/blob/main/SUPPORT.md) sagt, wohin eine Frage gehört, [`GOVERNANCE.md`](https://github.com/sunnypatell/basalt/blob/main/GOVERNANCE.md), was eine Änderung durchlaufen muss, [`RELEASING.md`](https://github.com/sunnypatell/basalt/blob/main/RELEASING.md), wie ein Release erstellt und verifiziert wird, und [`SECURITY.md`](https://github.com/sunnypatell/basalt/blob/main/SECURITY.md), wie man vertraulich meldet.
## basalt zitieren
Wenn basalt in eine Veröffentlichung, ein Werkzeug, ein Modell oder einen Fehlerbericht einfließt, zitiere es bitte. GitHub liest
[`CITATION.cff`](https://github.com/sunnypatell/basalt/blob/main/CITATION.cff) nativ, daher liefert dir **Dieses Repository zitieren** in der Seitenleiste
APA und BibTeX ohne Abschreiben. Dieselbe Datei ist es, die Zenodo und Zitierverwaltungen
parsen, und sie ist der maßgebliche Nachweis der Autorenschaft.
Der unten stehende Schlüssel ist der von GitHub generierte, sodass das Kopieren von hier und das Kopieren aus der Seitenleiste
denselben Eintrag ergeben, statt zweier, die wie verschiedene Werke aussehen:```bibtex
@software{Patel_basalt_a_hazard_2026,
author = {Patel, Sunny},
license = {Apache-2.0},
month = aug,
title = {{basalt: a hazard checker, assembler and scheduler for NVIDIA consumer Blackwell (sm\_120)}},
doi = {10.5281/zenodo.22072811},
url = {https://github.com/sunnypatell/basalt},
version = {1.0.0},
year = {2026}
}
| Feld | Bits | Bedeutung |
|---|
stall | 108:105 | Taktzyklen, die gewartet werden, bevor die nächste Instruktion ausgegeben wird |
yield | 109 | Hinweis, dass der Warp-Scheduler Warps wechseln darf |
write_barrier | 112:110 | Scoreboard, das beim Write-Back signalisiert wird (7 = keines) |
read_barrier | 115:113 | Scoreboard, das beim Lesen von Operanden signalisiert wird (7 = keines) |
wait_mask | 121:116 | Scoreboards, die vor dem Ausgeben frei sein müssen |
reuse | 125:122 | Operanden-Reuse-Cache-Flags, eines pro Quell-Slot |
| Befehl | Was er tut | Benötigt GPU |
|---|
doctor | Beide Orakel Ende-zu-Ende prüfen | nein |
build-isa | Sammeln und testen, die Befehlsdatenbank schreiben | nein |
isa | Ein Formular, einen Opcode oder die Abdeckung abfragen | nein |
validate-isa | Nachweisen, dass die gemessenen Felder durchgeschrieben werden können | nein |
mine-stalls | Paarweise Anforderungen aus dem Compiler-Scheduling lernen | nein |
verify | Die Steuerbits eines Cubins auf Datenkonflikte prüfen | nein |
schedule | Steuerbits eines Cubins von Grund auf zuweisen und das Ergebnis prüfen | nein |
assemble | SASS-Text oder einen ganzen Cubin encodieren und zurücklesen, um es zu beweisen | nein |
measure | Befehls-Latenz auf echter Hardware messen | ja |
probe-stalls | Die erforderliche Wartezeit finden, indem Programme absichtlich beschädigt werden | ja |
| Befehlsformen | 345 |
| Verschiedene Opcodes | 90 |
| Formen mit vollständiger Operanden-Zuordnung | 339 |
| Tensor-Core-Formen | 46 |
| Erstellt mit | ptxas V13.3.73 |
| Anweisungen | Zyklen |
|---|
IMAD IADD3 FFMA FADD FMUL LOP3 SHF | 4 |
POPC | 18 |
I2FP + F2I zusammen | 24 |
MUFU | 44 |
DADD DFMA | 64 |
stall | Zyklen/Anweisung | Ergebnis |
|---|
| 0 | 36.85 | korrekt |
| 1 | 4.88 | falsch |
| 2 | 4.88 | falsch |
| 3 | 5.88 | falsch |
| 4 | 6.88 | korrekt |
Zitieren Sie die Konzept-DOI 10.5281/zenodo.22072811, anstelle
einer Versions-DOI oder dieser URL. Sie verweist auf das neueste Release, bleibt also korrekt, ohne
jemals erneut bearbeitet zu werden. CITATION.cff enthält sie, sodass sie bereits in beiden obigen Formen vorliegt.
Wenn Sie die gemessenen Tabellen (src/basalt/data/) wiederverwenden oder eine Abbildung reproduzieren, zitieren Sie das Release,
aus dem sie stammen, anstelle von main: Die Zahlen werden von scripts/verify_all.py bei
einem bestimmten Commit regeneriert, und ein Tag macht das reproduzierbar.
Namensnennung ist eine Lizenzbedingung, keine Höflichkeit. Apache-2.0 §4 verlangt, dass
LICENSE und NOTICE bei jeder Weiterverbreitung oder abgeleiteten Arbeit mitgeführt werden,
und NOTICE enthält die Autorenangabe und die Clean-Room-Erklärung. Forks, Vendor-Kopien und
neu verpackte Wheels behalten beide Dateien.
Sunny Patel · sunnypatel.net · github.com/sunnypatell