
Il primo hazard checker al mondo per NVIDIA Blackwell (sm_120), con un assembler e uno scheduler confrontati byte per byte con il loro stesso compilatore. Il controllo che non hanno mai rilasciato.
Il problema · Quali GPU · Come funziona · Avvio rapido · Misurato, non presunto · Scoperte · API · Metodo · Roadmap · Clean-room
Un'istruzione GPU NVIDIA è di 128 bit, e 21 di essi non sono affatto l'istruzione. Sono una parola di controllo dello scheduling, da stall a reuse: quanti cicli attendere prima di emettere l'istruzione successiva, quali scoreboard segnalare, su quali attendere e quali operandi possono essere serviti dalla reuse cache.
L'hardware non verifica nulla di tutto ciò. Su sm_120 non c'è interlock sulle istruzioni a latenza fissa. Il silicio si fida di qualunque cosa abbia prodotto la parola di controllo. Se un conteggio di stall è più breve della latenza di un valore consumato dall'istruzione successiva, non si verifica alcun fault, nessuno stall e nessun avviso viene emesso. L'istruzione legge un registro non ancora scritto e calcola su dati obsoleti, a piena velocità, ogni singola volta.
È un tipo strano di bug. Non va in crash. Non appare in un debugger. Produce numeri semplicemente sbagliati, il che in una moltiplicazione di matrici o in un kernel di attenzione significa un modello che si addestra leggermente male, non uno che si rompe in modo visibile.
Le tre codifiche più economiche sono quelle rotte, e nulla ovunque lo segnala. Si noti anche cosa fa la barra di sinistra: uno stall di zero non sono zero cicli, è una codifica sicura distinta che attende i risultati pendenti e costa circa nove volte un'istruzione schedulata. Un checker che la leggesse come zero definirebbe rotti programmi corretti.
Gli strumenti che generano codice macchina per questa architettura assegnano quei bit di controllo a partire da un modello di latenza. basalt è ciò che verifica la risposta.
NVIDIA ti dà un compilatore che scrive quei 21 bit. Non ti dà nulla che li rilegga e ti dica che sono sicuri, e nessun altro lo fa.
Gli assembler per GPU NVIDIA esistono da un decennio, l'encoding Blackwell è stato già fatto di reverse engineering, esistono caratterizzazioni pubblicate a livello di cicli di sm_120, e un assembler pubblico per questa architettura assegna già da solo i bit di controllo dello scheduling ed esegue i propri kernel su una scheda per vedere che le risposte siano giuste. Tutto vero, e nessuna di queste cose è l'affermazione:
Nessun altro strumento può ricevere un cubin che non ha prodotto e gli si può chiedere di dire se i suoi bit di controllo dello scheduling sono sicuri.
Il tuo compilatore ha emesso quel cubin, o una libreria lo ha distribuito, o qualcuno lo ha scritto a mano, e fino ad ora non c'era modo di chiederlo. Su un'architettura senza interlock hardware, questa è la differenza tra "è girato" e "è corretto", e la differenza è invisibile: uno stall in difetto di un ciclo legge un registro obsoleto e restituisce un numero sbagliato a piena velocità, senza fault e senza avvisi, ogni singola volta.
Tutto il resto qui esiste per rendere verificabile quella frase. L'assembler è ciò che costruisce un programma con uno stall deliberatamente accorciato. Lo scheduler è ciò che costringe il modello a impegnarsi su una risposta invece di valutare quella di qualcun altro. E l'audit è il punto in cui la frase smette di essere un'assenza e diventa una misurazione: basalt puntato su 2.473 cubin sm_120 che NVIDIA distribuisce in cuBLAS, cuSOLVER, cuSPARSE, NPP e il resto.
Perché non esisteva, parole del settore. L'assembler SASS più usato dice nella propria documentazione che "verificare il rigore della correttezza dell'intero programma … [è] lontano dall'essere possibile senza supporto ufficiale. Quindi, è lasciato all'utente garantire la correttezza del programma, con un aiuto molto limitato dall'assembler." SIP, sull'autotuning degli schedule SASS, afferma che "la validazione è impossibile per i codici assembly nativi della GPU perché la semantica formale del sass è closed-source."
Entrambe riguardano la correttezza semantica: cioè se un kernel calcola ciò che dovrebbe. basalt non risponde a questo, e nulla qui lo sostiene. Risponde a una domanda strettamente più piccola, e il punto è che la domanda più piccola è decidibile senza la semantica:
I bit di controllo di questo programma coprono le sue stesse dipendenze dai dati?
Questo richiede la struttura delle dipendenze, che l'encoding rivela, e un modello di latenza, che il silicio rivela sotto misurazione. Nessuno dei due richiede di sapere cosa calcola un'istruzione. Un kernel può superare questo controllo ed essere comunque l'algoritmo sbagliato; ciò che non può fare è leggere un registro prima che il valore arrivi.
In secondo luogo, nient'altro viene misurato contro i byte del fornitore stesso. Il riferimento di basalt è l'output di ptxas, quindi un disaccordo è un bug di basalt fino a prova contraria. Il suo assembler deve riprodurre gli esatti 128 bit del compilatore. Il suo scheduler deve buttare via ogni bit di controllo scelto dal compilatore, calcolarne di nuovi e far sì che la GPU calcoli la stessa risposta.
Un unico standard per tutto: concordare esattamente con il fornitore, o spiegare perché no.
| Componente | Cosa fa | Come viene verificato | Risultato |
|---|---|---|---|
| Assembler | Testo SASS alla parola di 128 bit | Riassembla ogni istruzione emessa da ptxas e confronta i byte, sul corpus e su 5,2 milioni di istruzioni di codice di libreria distribuito mai visto prima | 59.693 su 59.760 istruzioni del corpus e 4.585.336 su 5.237.448 di quelle distribuite esatte, le altre rifiutate per nome, 0 sbagliate in entrambi |
| Checker | Legge uno schedule, segnala gli hazard | L'output del fornitore stesso deve risultare pulito, e uno stall deliberatamente accorciato deve essere intercettato | 0 errori su 1.323 coppie kernel-livello di ottimizzazione del fornitore, 0 mancati su 233 rotti |
| Audit | Lo stesso checker, sulle librerie distribuite | Eseguito su kernel sm_120 di produzione tenuti fuori da ogni tabella che legge | 0 errori su 2.762 kernel e 10.218.030 dipendenze, tutti i 2.762 completamente analizzati |
| Scheduler | Assegna ogni bit di controllo da zero | Scarta quelli del fornitore, calcolane di nuovi, esegui entrambi sulla GPU con otto input, confronta i byte di output | 439 kernel su 439 comparabili byte-identici, a tutti e tre i livelli di ottimizzazione |
E la parte su cui uno scheduler di solito tace: quanto costa la correttezza. Gli schedule di basalt spendono 1,05x i cicli di issue del fornitore, più lenti su 111 delle 1.323 coppie kernel-livello di ottimizzazione e più economici su 842, con ogni kernel comparabile ancora byte-identico sulla GPU.
La terza riga è quella che ha cambiato le altre tre. Un checker calibrato su un corpus non può fallire su quel corpus: il divario più stretto che il compilatore è stato visto lasciare è il minimo, per costruzione, esattamente per il codice su cui è stato misurato. La prima volta che questo ha visto codice da qualche altra parte, ha segnalato ventisei hazard per kernel in un decoder JPEG che non ha mai restituito un pixel sbagliato, e tutti i 6.593 erano di basalt. Correggerli lo ha portato a zero, e zero su una sola libreria non era comunque una prova: ampliando il set di esclusione a tre librerie e 5,2 milioni di istruzioni è tornato direttamente a 940, e ha trovato altri cinque errori del modello oltre ai primi otto. Tredici correzioni, nessuna di NVIDIA, e il requisito ri-estratto da 24.311 kernel distribuiti ha posto un predicato di guardia a 13 cicli su 229.567 osservazioni, che è il numero che l'iniezione di guasti aveva misurato su questa scheda rompendo un programma apposta. Vedi finding 32.
Essere più economici del fornitore non è un'affermazione di cui vantarsi. basalt scheduler ogni dipendenza al divario più stretto mai osservato in ptxas per quella esatta coppia, e ptxas sta bilanciando pressione dei registri e memoria oltre alla latenza di issue mentre questo ottimizza un solo numero. Inoltre non è stato creduto a prima vista: la prima volta che il rapporto è sceso sotto 1,0 il round trip hardware si è rotto, e il numero ha retto solo dopo aver corretto il bug che aveva esposto. Il rapporto è fissato da entrambi i lati nella suite di test per questo motivo.
La colonna centrale è il punto. Un checker e uno scheduler che condividono un modello di latenza concordano tra loro pur essendo entrambi sbagliati, quindi nessuno dei due è una prova per l'altro; solo il silicio non ha interessi in gioco nell'argomento. Eseguire lo scheduler su sette kernel scritti a mano ha passato sette su sette per molto tempo. Eseguirlo su trecento ne ha trovati quarantuno sbagliati, e ogni correzione in scoperte è venuta osservando quel numero muoversi.
Lo stesso vale per gli input. Una lettura obsoleta cambia la risposta solo quando il valore obsoleto e quello nuovo differiscono, quindi un pattern di byte è una sola occasione per accorgersene, ed eseguire ogni kernel contro un secondo, terzo e quarto pattern ha subito trovato un predicato di carry-out che il modello degli operandi leggeva come sorgente fin dall'inizio. Era sopravvissuto a ogni controllo fino a quel punto, incluso il round trip stesso.
La stessa disciplina decide cosa è permesso fare all'assembler, e vale la pena separare i due numeri che ha.
La copertura è del 99,9% del corpus e dell'87,5% del codice di libreria distribuito. La correttezza è del 100%, ed è quel numero a essere fissato da un test. Il divario tra loro sono istruzioni che basalt rifiuta, ciascuna con il nome del campo che non è riuscito a collocare, perché uno strumento che tirasse a indovinare raggiungerebbe la copertura totale emettendo parole che si disassemblano nel testo giusto e calcolano qualcos'altro. Non ne ha mai emessa una, su 59.760 istruzioni del corpus e 5.237.448 distribuite.
Ci è arrivato solo dopo otto turni distinti di sicurezza errata:
Ognuno di questi ha prodotto una parola che si assembla, si disassembla di nuovo esattamente nel testo da cui proveniva, e calcola qualcos'altro. È lo stesso fallimento che il resto di questo repository esiste per intercettare, motivo per cui tutti e otto sono ora rifiutati con un motivo che indica cosa contiene davvero il campo, e per cui il conteggio delle istruzioni che si assemblano nei byte sbagliati è un test fissato a zero piuttosto che un numero in una tabella.
Un nono è saltato fuori la prima volta che l'assembler è stato puntato su codice macchina che non aveva prodotto, ed era di tipo diverso. c[0x0][UR4] indicizza il suo offset tramite un registro dove la forma registrata contiene un numero, e l'encoder ha sollevato un'eccezione piuttosto che rifiutare. Un crash su input estraneo è peggio di un verdetto sbagliato, perché il chiamante non ottiene né l'uno né l'altro.
sm_120 non è un numero di modello. È la compute capability condivisa da tutta la linea Blackwell consumer, quindi l'encoding delle istruzioni, il database, l'assembler e il checker si applicano a ogni scheda della linea:
| Scheda | Compute capability | Coperta |
|---|---|---|
| GeForce RTX 5090, 5090D | 12.0 (sm_120) | sì |
| GeForce RTX 5080, 5070 Ti, 5070 | 12.0 (sm_120) | sì |
| GeForce RTX 5060 Ti, 5060, 5050 | 12.0 (sm_120) | sì |
| GeForce RTX 50 series laptop parts | 12.0 (sm_120) | sì |
| RTX PRO Blackwell workstation cards | 12.0 (sm_120) | sì |
| Blackwell datacenter (B100, B200, GB200) | 10.0 (sm_100) | no, encoding diverso |
ptxas ha anche come target sm_121, un chip diverso della stessa famiglia. basalt non è mai stato eseguito su uno e non sostiene di supportarlo. Ciò che può dire è misurato: il compilatore emette codice byte-identico, parole di controllo incluse, per tutti e sei i target che offre qui, quindi lo schedule di cui un kernel ha bisogno è una proprietà dell'architettura piuttosto che del componente (finding 28). Se non fosse vero, il compilatore di NVIDIA stesso emetterebbe uno schedule non sicuro per uno di essi.
Ogni numero misurato sul silicio proviene da una scheda fisica, nominata esattamente, perché "una 5070 Ti" non basta a riprodurre un'esecuzione:
| La scheda | Esattamente cos'è |
|---|---|
| Scheda | Gigabyte GeForce RTX 5070 Ti EAGLE OC |
| Riportata dal driver | NVIDIA GeForce RTX 5070 Ti |
| Compute capability | 12.0 |
| Streaming multiprocessors | 70 |
| Clock di boost | 2542 MHz |
| Toolchain | CUDA 13.3.1, ptxas V13.3.73 |
La maggior parte di basalt non richiede affatto una GPU. Entrambi gli oracoli, il database delle istruzioni, l'assembler e l'hazard checker girano contro ptxas e nvdisasm come normali subprocess, motivo per cui girano in CI su una macchina senza scheda grafica. 237 dei 252 test sono in quel gruppo, e 200 non richiedono né una scheda né i binari NVIDIA.
Una GPU serve esattamente per tre cose, e sono le tre che trasformano uno strumento plausibile in uno credibile:
| Richiede una scheda | Perché |
|---|---|
measure, probe-stalls | Misurare i tempi di un'istruzione e scoprire cosa richiede davvero una dipendenza rompendola |
scripts/roundtrip_corpus.py | Rischedulare ogni kernel del corpus ed eseguire entrambe le versioni per confrontare i byte di output |
scripts/agreement_sweep.py | Accorciare una dipendenza per kernel e chiedere al silicio se basalt aveva ragione |
L'overclock di fabbrica non sposta le misurazioni. Ogni latenza qui è in cicli, che è una proprietà della pipeline piuttosto che del clock, e la cifra del boost è registrata accanto solo perché un confronto a orologio da parete resti possibile. Ciò che la scheda influenza è la riproducibilità, ed è per questo che basalt measure --board la registra.
Tutto ciò che è stato misurato qui è stato misurato su una scheda, e basalt registra lo SKU accanto a ogni misurazione piuttosto che presentarle come universali. Una 5090 ha più del doppio degli SM e un suo comportamento di clock; l'encoding sarà identico e le latenze dovrebbero essere rimisurate piuttosto che assunte:```bash python -m basalt.cli measure -o my-card.json python -m basalt.cli verify kernel.cubin --latencies my-card.json
Non è modestia. Un modello di latenza condiviso tra un checker e uno scheduler è esattamente il punto in cui si nasconde un numero sbagliato, quindi una seconda scheda è il contributo più utile che si possa dare.
</details>
## Come funziona
Tutto poggia su due oracoli, entrambi binari NVIDIA standard eseguiti come processi esterni. Nessun sorgente, header o libreria NVIDIA viene usato o ridistribuito.
| Oracle | Invocazione | Cosa fornisce |
| :--- | :--- | :--- |
| **Verità di base** | `ptxas` → cubin → `nvdisasm -c -hex` | Codifiche che il compilatore del fornitore emette realmente. Semantica indiscutibile. |
| **Sonda** | `nvdisasm -b SM120a` su byte grezzi | Decodifica parole che `ptxas` non emetterà mai, trasformando lo spazio di codifica in qualcosa di ricercabile piuttosto che da indovinare. |
L'oracolo sonda è quello che conta. Uno strumento limitato all'output del compilatore può solo riscoprire ciò che il compilatore già fa. Inserire parole sintetizzate da 128 bit direttamente nel decoder significa che il set di istruzioni può essere *misurato*.
Nessuno dei due oracoli richiede una GPU, quindi l'intero database di istruzioni viene ricostruito in CI su qualsiasi macchina.
### Derivare la codifica modificandola
basalt non legge una tabella di opcode da nessuna parte. Prende una codifica che è stata assemblata, capovolge un bit, decodifica il risultato e registra cosa è cambiato. Un bit che modifica il registro di destinazione è un bit di destinazione; un bit che modifica il mnemonico è un selettore; un bit che non cambia nulla di osservabile è inerte.
Eseguito su `IADD R5, R5, 0x2a`, la misurazione restituisce:```
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
Campi di registro a 8 bit e un immediato a 32 bit, ottenuti sperimentalmente piuttosto che per ipotesi.
Il layout si autovalida al contatto. In un kernel banale, S2R imposta write_barrier=0 e la IMAD che consuma il suo risultato porta wait_mask=0x01; LDCU.64 imposta write_barrier=1 e la STG.E dipendente porta wait_mask=0x02. Ogni coppia produttore-consumatore si allinea, e le istruzioni che nvdisasm annota con .reuse hanno il bit di reuse corrispondente impostato.
Nessuna installazione di CUDA e nessuna GPU. Lo script della toolchain scarica redistributabili con versione bloccata, circa 45 MB, nessun privilegio di amministratore, nulla aggiunto al tuo PATH.```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
Please provide the Markdown content to translate.```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 installa la CLI, la libreria e tutte e tre le tabelle di misurazione, senza dipendenze runtime. Usalo se hai già CUDA sulla macchina; il checkout sopra è quello che ti serve se non lo hai, o se intendi riprodurre le misurazioni.```bash
pip install basalt-sass
basalt doctor
basalt verify kernel.cubin
### Dove cerca `ptxas` e `nvdisasm`
basalt li esegue entrambi come processi esterni e non ridistribuisce nessuno dei due, quindi deve trovarne una copia.
Prende il primo che risponde, e **qualsiasi installazione CUDA 13 andrà bene**: nulla deve essere il
ridistribuibile bloccato.
| Ordine | Dove |
| ---: | :--- |
| 1 | `--cuda-bin`, passato sulla riga di comando |
| 2 | `BASALT_CUDA_BIN`, una directory contenente entrambi i binari |
| 3 | `CUDA_PATH`, `CUDA_HOME` o `CUDA_ROOT`, ciascuno più `/bin` |
| 4 | `ptxas` sul tuo `PATH` |
| 5 | `third_party/cuda/<version>/bin` in un checkout, dal più recente al più vecchio |
`basalt doctor` stampa quale ha risolto, ed esce con un codice diverso da zero quando non riesce a trovarne uno, quindi
funziona come precondizione per una fase di build piuttosto che solo come qualcosa da leggere.
Interrogare il database delle istruzioni non richiede affatto una toolchain, perché il database viene misurato
in anticipo ed è incluso nel pacchetto:```bash
basalt isa --stats
basalt isa --opcode QMMA
Ricostruisci il database delle istruzioni da zero, oppure interroga quello committato:```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
### Controlla il codice macchina che non hai scritto
Questa è la parte che nessun altro fa, e non richiede GPU né argomenti. Il modello di latenza misurato e la tabella dei requisiti estratta sono entrambi commitati, quindi una nuova clone può essere puntata direttamente a un cubin, qualunque cosa lo abbia prodotto:```bash
python -m basalt.cli verify kernel.cubin
Non è stato fornito alcun contenuto da tradurre. Invia il testo della chunk 17/29 per poter procedere con la traduzione.```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
Una libreria ELF contiene centinaia di kernel e ciascuno viene controllato singolarmente, perché gli offset
ripartono da zero e nulla passa da uno all'altro. Aggiungi `--strict` per uscire
con codice diverso da zero in caso di un rischio, ed è ciò che un passo di build desidera. Se hai una scheda sm_120 e vuoi
che il modello venga misurato sul tuo silicio piuttosto che su quello in questo repository:```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
Tutto ciò che fa la CLI è importabile, e la superficie della libreria con esempi eseguibili è
in docs/API.md.
E i due controlli che fanno da garanzia per il resto. Il primo richiede una scheda; il secondo richiede le librerie incluse e nessun 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
[No content provided after "INPUT:". Please paste the Markdown text you wish to have translated.]```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
I numeri qui sono stampati dagli strumenti e vengono rigenerati da un checkout pulito. I comandi sopra sono la fonte di verità; queste tabelle sono istantanee.
Database delle istruzioni. Ogni voce riporta un encoding che è stato realmente assemblato e la build del compilatore che lo ha prodotto.
| Database delle istruzioni |
|---|
La copertura tensor è dove vive l'hardware a bassa precisione: HMMA e IMMA, QMMA attraverso i tipi FP8, FP6 e FP4, incluse le coppie di operandi asimmetriche, le forme con fattore di scala QMMA.SF e OMMA.SF che trasportano un esponente per blocco, lo sparso IMMA.SP, e le istruzioni di movimento matriciale LDSM, STSM e MOVM in ogni forma, incluse le varianti di trasposizione.
Latenza, su una RTX 5070 Ti. 70 SM, ogni fit R² ≥ 0.9998. Misurata cronometrando catene dipendenti e prendendo la pendenza, con la lunghezza della catena riletta dal SASS compilato anziché presunta.
Tre di questi contraddicono il modello presunto con cui basalt è stato distribuito: DADD era presunto 48, POPC era presunto 4, e ogni conversione era presunta 6 contro i 24 misurati per il viaggio di andata e ritorno.
Un modello di latenza presunto non è una piccola approssimazione di uno misurato, e questo è l'intero argomento per misurare.
E uno stallo di zero non è zero cicli. È un encoding sicuro distinto che attende i risultati in sospeso, costando circa 37 cicli laddove un'istruzione schedulata costa 4. Ecco perché ptxas -O0 emette una parola di controllo completamente azzerata e il codice calcola comunque correttamente, circa nove volte più lentamente.
Concorda con il compilatore del vendor su ogni kernel del corpus. Ogni kernel che ptxas costruisce dal corpus viene verificato contro la propria schedulazione, a ogni livello di ottimizzazione che schedula: 30.421 dipendenze, zero errori. Questo sweep gira in CI a ogni push, e ogni errore di modellazione che questo progetto ha commesso è stato catturato da esso piuttosto che dal ragionamento.
I verdetti corrispondono al silicio. Per ogni stallo codificabile su un produttore dipendente, la risposta statica di basalt e ciò che l'hardware calcola effettivamente concordano, incluso il caso zero. Questo è tenuto come test, non asserito qui. Le prove complete, incluse tre metodi indipendenti per lo stallo richiesto e le correzioni apportate lungo il percorso, sono in findings.
E quando dice che una schedulazione non è sicura, il silicio è d'accordo. Prendi la schedulazione funzionante del vendor per 233 kernel, accorcia una dipendenza reale in ciascuno, e confronta il verdetto di basalt con ciò che la GPU calcola: 79 che ha definito rotti erano rotti, e nulla di ciò che ha definito sicuro ha calcolato una risposta errata. Quel numero è partito da 34 mancati piuttosto che da zero, e findings dice quale sia stata la causa e quanto sia costato correggerla in falsi allarmi, perché uno sweep che ha riportato solo la sua cifra finale varrebbe meno di uno che ha riportato la sua prima.
Il verificatore risponde se una schedulazione è sicura. Lo scheduler risponde a cosa sarebbe una schedulazione sicura, a partire dalle stesse misurazioni: scarta ogni bit di controllo prodotto da ptxas, calcola i propri, restituisce il risultato al verificatore, e poi lo esegue sulla GPU accanto alla versione del vendor dello stesso kernel.
Eseguito sull'intero corpus sulla scheda, a ogni livello di ottimizzazione che produce una schedulazione, tutti i 439 kernel comparabili risultano calcolare risultati byte-identici alla schedulazione del vendor, a partire da bit di controllo che basalt ha ricavato da sé. I 2 che sono esclusi leggono l'orologio e l'id della griglia, quindi non concordano nemmeno con se stessi, e findings lo dice piuttosto che fonderli in una percentuale.
Quel controllo è il motivo per cui tutto il resto è affidabile. Il checker e lo scheduler leggono lo stesso modello di latenza, quindi una voce errata in esso soddisfa entrambi contemporaneamente e concordano tra loro mentre entrambi sono sbagliati. Solo il silicio non ha nulla in gioco in questa discussione. Eseguire lo scheduler su sette kernel scritti a mano è passato sette su sette per molto tempo; eseguirlo su trecento ne ha trovati quarantuno sbagliati, e ogni correzione del modello da allora è nata guardando muoversi quel numero.
È in quel ciclo che sono nati i veri bug. Lo stallo speso al di fuori della finestra tra un produttore e il suo consumatore non conta nulla, e spenderlo lì termina la ricerca con un programma ancora carente. Uno stallo ancorato all'encoding sicuro veniva sovrascritto da un passaggio successivo, sostituendo una garanzia con un piccolo numero. Gli operandi fp64 occupano coppie di registri senza che il mnemonico lo dica, quindi metà di ogni dipendenza fp64 era invisibile sia al checker che allo scheduler. Un predicato usato come guardia di un'istruzione richiede tredici cicli, mentre lo stesso predicato letto come dato ne richiede cinque, perché una guardia deve essere risolta prima che l'istruzione venga emessa del tutto. E attendere su uno scoreboard non risolve completamente una dipendenza: il produttore deve ancora un piccolo stallo a sua volta, due cicli per l'add fp64, e un ciclo in meno è silenziosamente errato. Nessuno di questi è stato trovato ragionando; ognuno è stato trovato eseguendo l'output e ottenendo il numero sbagliato.
[!NOTE] 1.0, e specifico su cosa significa. Cosa è fatto: entrambi gli oracoli, il database delle istruzioni con i suoi campi dimostrati scrivibili, l'hazard checker su un grafico di flusso di controllo reale, la latenza misurata su una SKU con tre metodi indipendenti, uno scheduler che fa il round-trip di ogni kernel comparabile del corpus attraverso l'hardware byte-per-byte, e un audit di 2.762 kernel distribuiti tenuti fuori da ogni tabella che il checker legge. Cosa non è: 12 kernel del corpus non eseguibili per costruzione e 2 il cui output del vendor non è deterministico, tutti nominati nei findings; dieci opcode hanno ancora una latenza presunta anziché misurata, nessuno di essi mai produttore in nessuno dei due corpi di codice; ed è stata misurata una sola GPU, cosa che finding 28 dimostra importare meno di quanto sembri. Dove qualcosa è dedotto anziché misurato, gli strumenti lo dicono piuttosto che arrotondarlo a un fatto. Vedi la roadmap e il metodo.
```
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>
## Posizione clean-room
basalt è un lavoro indipendente e clean-room per l'interoperabilità. Non contiene codice sorgente, header, librerie o documentazione NVIDIA e non ne ridistribuisce alcunché. Osserva il comportamento di eseguibili distribuiti pubblicamente e lo registra: questa è la base su cui questo tipo di lavoro si fonda da oltre un decennio.
NVIDIA, CUDA e Blackwell sono marchi di NVIDIA Corporation. Questo progetto non è affiliato a NVIDIA, né da essa approvato o sponsorizzato.
Concesso in licenza sotto [Apache-2.0](https://github.com/sunnypatell/basalt/blob/main/LICENSE). Apache piuttosto che qualcosa di restrittivo è una scelta deliberata: uno strumento di correttezza su cui a nessuno è permesso costruire è uno strumento di correttezza che nessuno esegue, e la concessione dei brevetti è importante per un lavoro così vicino all'hardware.
## Contribuire
Il contributo di maggior valore è una codifica che basalt sbaglia. Vedi [`CONTRIBUTING.md`](https://github.com/sunnypatell/basalt/blob/main/CONTRIBUTING.md) e il [modello per gap ISA](https://github.com/sunnypatell/basalt/blob/main/.github/ISSUE_TEMPLATE/isa_gap.yml), che raccoglie abbastanza per riprodurre senza la tua macchina.
[`SUPPORT.md`](https://github.com/sunnypatell/basalt/blob/main/SUPPORT.md) dice dove va una domanda, [`GOVERNANCE.md`](https://github.com/sunnypatell/basalt/blob/main/GOVERNANCE.md) cosa deve superare una modifica, [`RELEASING.md`](https://github.com/sunnypatell/basalt/blob/main/RELEASING.md) come viene preparata e verificata una release e [`SECURITY.md`](https://github.com/sunnypatell/basalt/blob/main/SECURITY.md) come segnalare privatamente.
## Citare basalt
Se basalt contribuisce a un articolo, uno strumento, un modello o una segnalazione di bug, ti preghiamo di citarlo. GitHub legge [`CITATION.cff`](https://github.com/sunnypatell/basalt/blob/main/CITATION.cff) nativamente, quindi **Cita questo repository** nella barra laterale ti fornisce APA e BibTeX senza trascrizione. Lo stesso file è ciò che Zenodo e i gestori di citazioni analizzano, ed è la fonte autorevole della paternità.
La chiave qui sotto è quella generata da GitHub, quindi copiare da qui e copiare dalla barra laterale danno la stessa voce anziché due che sembrano opere diverse:```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}
}
| Field | Bits | Meaning |
|---|
stall | 108:105 | Cicli di attesa prima di emettere l'istruzione successiva |
yield | 109 | Suggerimento che lo scheduler dei warp può commutare i warp |
write_barrier | 112:110 | Scoreboard da segnalare al write-back (7 = nessuno) |
read_barrier | 115:113 | Scoreboard da segnalare alla lettura dell'operando (7 = nessuno) |
wait_mask | 121:116 | Scoreboard che devono essere liberi prima dell'emissione |
reuse | 125:122 | Flag della reuse-cache degli operandi, uno per slot sorgente |
| Comando | Cosa fa | Richiede una GPU |
|---|
doctor | Controlla entrambi gli oracoli end-to-end | no |
build-isa | Raccoglie e sonda, scrive il database delle istruzioni | no |
isa | Interroga un form, un opcode o la copertura | no |
validate-isa | Dimostra che i campi misurati possono essere scritti | no |
mine-stalls | Impara i requisiti per coppia da ciò che il compilatore pianifica | no |
verify | Controlla i bit di controllo di un cubin per i data hazard | no |
schedule | Assegna i bit di controllo di un cubin da zero e verifica il risultato | no |
assemble | Codifica testo SASS, o un intero cubin, e rileggilo per verificarlo | no |
measure | Misura la latenza delle istruzioni su silicio reale | sì |
probe-stalls | Trova lo stallo richiesto rompendo i programmi di proposito | sì |
| Conteggio |
|---|
| Forme di istruzione | 345 |
| Opcode distinti | 90 |
| Forme con una mappa completa degli operandi | 339 |
| Forme tensor-core | 46 |
| Compilato con | ptxas V13.3.73 |
| Istruzioni | Cicli |
|---|
IMAD IADD3 FFMA FADD FMUL LOP3 SHF | 4 |
POPC | 18 |
I2FP + F2I insieme | 24 |
MUFU | 44 |
DADD DFMA | 64 |
stall | cicli/istruzione | risultato |
|---|
| 0 | 36.85 | corretto |
| 1 | 4.88 | errato |
| 2 | 4.88 | errato |
| 3 | 5.88 | errato |
| 4 | 6.88 | corretto |
Cita il DOI concettuale, 10.5281/zenodo.22072811, piuttosto
che un DOI di versione o questo URL. Si risolve nella release più recente, quindi rimane corretto senza
dover essere modificato di nuovo. CITATION.cff lo contiene, quindi è già in entrambe le forme sopra.
Se riutilizzi le tabelle misurate (src/basalt/data/) o riproduci una figura, cita la release
da cui provengono piuttosto che main: i numeri sono rigenerati da scripts/verify_all.py a
un commit specifico, e un tag è ciò che rende riproducibile il tutto.
L'attribuzione è un termine della licenza, non una cortesia. Apache-2.0 §4 richiede che
LICENSE e NOTICE accompagnino qualsiasi ridistribuzione o opera derivata,
e NOTICE riporta la paternità e la dichiarazione di clean-room. Fork, copie vendorizzate e
wheel riconfezionate mantengono entrambi i file.
Sunny Patel · sunnypatel.net · github.com/sunnypatell