
Le tout premier vérificateur d'aléas au monde pour NVIDIA Blackwell (sm_120), avec un assembleur et un ordonnanceur comparés octet par octet à leur propre compilateur. La vérification qu'ils n'ont jamais livrée.
Le problème · Quels GPU · Comment ça marche · Démarrage rapide · Mesuré, pas supposé · Résultats · API · Méthode · Feuille de route · Salle blanche
Une instruction GPU NVIDIA fait 128 bits, et 21 d'entre eux ne sont pas du tout l'instruction. Ils constituent un mot de contrôle d'ordonnancement, de stall à reuse : combien de cycles attendre avant d'émettre l'instruction suivante, quels scoreboards signaler, lesquels attendre, et quels opérandes peuvent être servis depuis le cache de réutilisation.
Le matériel ne vérifie rien de tout cela. Sur sm_120, il n'y a pas d'interverrouillage pour les instructions à latence fixe. Le silicium fait confiance à ce qui a produit le mot de contrôle. Si un compte de stall est plus court que la latence d'une valeur consommée par l'instruction suivante, rien ne déclenche de faute, rien ne bloque, et aucun avertissement n'est émis. L'instruction lit un registre qui n'a pas encore été écrit et calcule sur des données obsolètes, à pleine vitesse, à chaque fois.
C'est un type de bug étrange. Il ne plante pas. Il n'apparaît pas dans un débogueur. Il produit des nombres simplement faux, ce qui, dans une multiplication matricielle ou un noyau d'attention, signifie un modèle qui s'entraîne légèrement mal plutôt qu'un modèle qui casse visiblement.
Les trois encodages les moins chers sont ceux qui sont cassés, et rien, nulle part, ne le signale. Remarquez aussi ce que fait la barre de gauche : un stall de zéro n'est pas zéro cycle, c'est un encodage sûr distinct qui attend les résultats en cours et coûte environ neuf fois le prix d'une instruction ordonnancée. Un vérificateur qui le lirait comme zéro considérerait des programmes corrects comme cassés.
Les outils qui génèrent du code machine pour cette architecture assignent ces bits de contrôle à partir d'un modèle de latence. basalt est l'outil qui vérifie la réponse.
NVIDIA vous donne un compilateur qui écrit ces 21 bits. Il ne vous donne rien qui les relise et vous dise qu'ils sont sûrs, et personne d'autre non plus.
Des assembleurs pour GPU NVIDIA existent depuis une décennie, l'encodage Blackwell a déjà fait l'objet de rétro-ingénierie, des caractérisations au niveau du cycle de sm_120 sont publiées, et un assembleur public pour cette architecture assigne déjà lui-même les bits de contrôle d'ordonnancement et exécute ses propres noyaux sur une carte pour vérifier que les réponses sont correctes. Tout cela est vrai, et rien de tout cela n'est l'affirmation :
Rien d'autre ne peut recevoir un cubin qu'il n'a pas produit et se voir demander de dire si ses bits de contrôle d'ordonnancement sont sûrs.
Votre compilateur a émis ce cubin, ou une bibliothèque l'a livré, ou quelqu'un l'a écrit à la main, et jusqu'à présent il n'y avait aucun moyen de le demander. Sur une architecture sans interverrouillage matériel, c'est la différence entre « ça a tourné » et « c'est correct », et cette différence est invisible : un stall court d'un cycle lit un registre obsolète et renvoie un mauvais nombre à pleine vitesse, sans faute ni avertissement, à chaque fois.
Tout le reste ici existe pour rendre cette phrase testable. L'assembleur est ce qui construit un programme avec un stall délibérément raccourci. L'ordonnanceur est ce qui force le modèle à s'engager sur une réponse plutôt que de noter celle d'un autre. Et l'audit est l'endroit où la phrase cesse d'être une absence et devient une mesure : basalt pointé sur 2 473 cubins sm_120 que NVIDIA livre dans cuBLAS, cuSOLVER, cuSPARSE, NPP et le reste.
Pourquoi cela n'existait pas, selon les propres mots du domaine. L'assembleur SASS le plus utilisé dit dans sa propre documentation que « vérifier rigoureusement l'exactitude du programme entier … [est] loin d'être possible sans support officiel. Il est donc laissé à l'utilisateur le soin de garantir l'exactitude du programme, avec une aide très limitée de l'assembleur. » SIP, à propos de l'auto-réglage des ordonnancements SASS, affirme que « la validation est impossible pour les codes d'assemblage natifs GPU car la sémantique formelle du SASS est à code source fermé. »
Les deux concernent l'exactitude sémantique : si un noyau calcule ce qu'il est censé calculer. basalt n'y répond pas, et rien ici ne prétend le faire. Il répond à une question strictement plus petite, et le fait est que cette question plus petite est décidable sans la sémantique :
Les bits de contrôle de ce programme couvrent-ils ses propres dépendances de données ?
Cela nécessite la structure de dépendances, que l'encodage révèle, et un modèle de latence, que le silicium révèle sous mesure. Ni l'un ni l'autre n'exige de savoir ce que calcule une instruction. Un noyau peut passer ce contrôle et rester le mauvais algorithme ; ce qu'il ne peut pas faire, c'est lire un registre avant que la valeur n'arrive.
Deuxièmement, rien d'autre n'est mesuré contre les octets du vendeur lui-même. La référence de basalt est la sortie de ptxas, donc un désaccord est un bug de basalt jusqu'à preuve du contraire. Son assembleur doit reproduire exactement les 128 bits du compilateur. Son ordonnanceur doit jeter tous les bits de contrôle choisis par le compilateur, en calculer de nouveaux, et faire calculer au GPU la même réponse.
Une seule norme pour tout cela : être exactement d'accord avec le vendeur, ou expliquer pourquoi.
| Composant | Ce qu'il fait | Comment il est vérifié | Résultat |
|---|---|---|---|
| Assembleur | Texte SASS vers le mot de 128 bits | Réassemble chaque instruction émise par ptxas et compare les octets, sur le corpus et sur 5,2 M d'instructions de code de bibliothèque livré qu'il n'avait jamais vues | 59 693 des 59 760 instructions du corpus et 4 585 336 des 5 237 448 instructions livrées exactes, le reste refusé nommément, 0 erreur dans les deux |
| Vérificateur | Lit un ordonnancement, signale les aléas | La sortie du vendeur lui-même doit se vérifier sans erreur, et un stall délibérément raccourci doit être détecté | 0 erreur sur 1 323 paires noyau/niveau d'optimisation du vendeur, 0 manqué sur 233 noyaux cassés |
| Audit | Le même vérificateur, sur les bibliothèques livrées | L'exécuter sur des noyaux sm_120 de production retenus hors de chaque table qu'il lit | 0 erreur sur 2 762 noyaux et 10 218 030 dépendances, les 2 762 entièrement analysés |
| Ordonnanceur | Assigne chaque bit de contrôle à partir de zéro | Jeter ceux du vendeur, en calculer de nouveaux, exécuter les deux sur le GPU avec huit entrées, comparer les octets de sortie | 439 des 439 noyaux comparables octet pour octet identiques, aux trois niveaux d'optimisation |
Et la partie sur laquelle un ordonnanceur est généralement discret : ce que coûte l'exactitude. Les ordonnancements de basalt dépensent 1.05x les cycles d'émission du vendeur, plus lents sur 111 des 1 323 paires noyau/niveau d'optimisation et moins chers sur 842, chaque noyau comparable restant octet pour octet identique sur le GPU.
La troisième ligne est celle qui a changé les trois autres. Un vérificateur calibré sur un corpus ne peut pas échouer sur ce corpus : l'écart le plus serré que le compilateur a été vu laisser est le plancher, par construction, pour exactement le code sur lequel il a été mesuré. La première fois que celui-ci a vu du code venu d'ailleurs, il a signalé vingt-six aléas par noyau dans un décodeur JPEG qui n'a jamais renvoyé un mauvais pixel, et les 6 593 étaient tous ceux de basalt. Les corriger l'a ramené à zéro, et zéro sur une bibliothèque n'était pas non plus une preuve : élargir l'ensemble retenu à trois bibliothèques et 5,2 millions d'instructions l'a ramené directement à 940, et a trouvé cinq erreurs de modèle supplémentaires en plus des huit premières. Treize corrections, aucune de NVIDIA, et l'exigence ré-extraite de 24 311 noyaux livrés a placé un prédicat de garde à 13 cycles sur 229 567 observations, soit le nombre que l'injection de fautes avait mesuré sur cette carte en cassant un programme exprès. Voir finding 32.
Être moins cher que le vendeur n'est pas un motif de fierté. basalt ordonnance chaque dépendance à l'écart le plus serré que ptxas ait jamais été vu laisser pour cette paire exacte, et ptxas équilibre la pression des registres et la mémoire en plus de la latence d'émission pendant que celui-ci optimise un seul nombre. Cela n'a pas non plus été cru sur parole : la première fois que le ratio est passé sous 1.0, l'aller-retour matériel a cassé, et le nombre n'a tenu qu'une fois le bug qu'il exposait corrigé. Le ratio est verrouillé des deux côtés dans la suite de tests pour cette raison.
La colonne du milieu est le point crucial. Un vérificateur et un ordonnanceur qui partagent un modèle de latence sont d'accord entre eux tout en ayant tous deux tort, donc ni l'un ni l'autre n'est une preuve pour l'autre ; seul le silicium n'est pas partie prenante dans l'argument. Lancer l'ordonnanceur sur sept noyaux écrits à la main a passé sept sur sept pendant longtemps. Le lancer sur trois cents en a trouvé quarante et un de faux, et chaque correction dans les résultats est sortie en observant ce nombre bouger.
La même chose s'applique aux entrées. Une lecture obsolète ne change la réponse que lorsque la valeur obsolète et la valeur fraîche diffèrent, donc un motif d'octets est une seule chance de le remarquer, et l'exécution de chaque noyau contre un deuxième, un troisième et un quatrième motif a immédiatement trouvé un prédicat de retenue que le modèle d'opérandes lisait comme une source depuis le début. Il avait survécu à tous les contrôles jusqu'à ce point, y compris à l'aller-retour lui-même.
La même discipline décide de ce que l'assembleur est autorisé à faire, et il vaut la peine de séparer les deux nombres qu'il possède.
La couverture est de 99,9 % du corpus et de 87,5 % du code de bibliothèque livré. L'exactitude est de 100 %, et c'est le nombre verrouillé par un test. L'écart entre les deux est constitué d'instructions que basalt refuse, chacune nommant le champ qu'il n'a pas pu placer, car un outil qui devinerait atteindrait une couverture complète en émettant des mots qui se désassemblent en le bon texte et calculent autre chose. Il n'en a jamais émis un seul, sur 59 760 instructions du corpus et 5 237 448 instructions livrées.
Il n'y est parvenu qu'après huit séries distinctes d'erreurs commises en toute confiance :
Chacune de ces erreurs a produit un mot qui s'assemble, se désassemble en exactement le texte dont il provient, et calcule autre chose. C'est la même défaillance que le reste de ce dépôt existe pour attraper, et c'est pourquoi ces huit cas sont désormais refusés avec une raison nommant ce que le champ contient réellement, et pourquoi le nombre d'instructions assemblées en de mauvais octets est un test verrouillé à zéro plutôt qu'un nombre dans un tableau.
Une neuvième est apparue la première fois que l'assembleur a été pointé sur du code machine qu'il n'avait pas produit, et c'était d'un genre différent. c[0x0][UR4] indexe son décalage par un registre là où la forme enregistrée contient un nombre, et l'encodeur a levé une exception plutôt que de refuser. Un plantage sur une entrée étrangère est pire qu'un mauvais verdict, car l'appelant n'obtient ni l'un ni l'autre.
sm_120 n'est pas un numéro de modèle. C'est la capacité de calcul partagée par toute la gamme grand public Blackwell, donc l'encodage des instructions, la base de données, l'assembleur et le vérificateur s'appliquent à chaque carte de cette gamme :
| Carte | Capacité de calcul | Couverte |
|---|---|---|
| GeForce RTX 5090, 5090D | 12.0 (sm_120) | oui |
| GeForce RTX 5080, 5070 Ti, 5070 | 12.0 (sm_120) | oui |
| GeForce RTX 5060 Ti, 5060, 5050 | 12.0 (sm_120) | oui |
| Configurations portables GeForce RTX série 50 | 12.0 (sm_120) | oui |
| Cartes de poste de travail RTX PRO Blackwell | 12.0 (sm_120) | oui |
| Blackwell datacentre (B100, B200, GB200) | 10.0 (sm_100) | non, encodage différent |
ptxas cible aussi sm_121, une puce différente de la même famille. basalt n'a jamais tourné dessus et ne prétend pas la prendre en charge. Ce qu'il peut dire est mesuré : le compilateur émet un code octet pour octet identique, mots de contrôle compris, pour les six cibles qu'il propose ici, donc l'ordonnancement dont un noyau a besoin est une propriété de l'architecture plutôt que de la pièce (finding 28). Si ce n'était pas vrai, le compilateur de NVIDIA lui-même émettrait un ordonnancement non sûr pour l'une d'elles.
Chaque nombre mesuré sur silicium provient d'une seule carte physique, nommée précisément, car « une 5070 Ti » ne suffit pas pour reproduire une exécution :
| La carte | Son identité exacte |
|---|---|
| Carte | Gigabyte GeForce RTX 5070 Ti EAGLE OC |
| Signalée par le pilote | NVIDIA GeForce RTX 5070 Ti |
| Capacité de calcul | 12.0 |
| Multiprocesseurs de streaming | 70 |
| Fréquence boost | 2542 MHz |
| Chaîne d'outils | CUDA 13.3.1, ptxas V13.3.73 |
La plus grande partie de basalt n'a pas besoin de GPU du tout. Les deux oracles, la base de données d'instructions, l'assembleur et le vérificateur d'aléas s'exécutent contre ptxas et nvdisasm comme de simples sous-processus, c'est pourquoi ils tournent dans le CI sur une machine sans carte graphique. 237 des 252 tests font partie de ce groupe, et 200 n'ont besoin ni d'une carte ni des binaires NVIDIA.
Un GPU n'est nécessaire que pour exactement trois choses, et ce sont les trois qui transforment un outil plausible en un outil crédible :
| Nécessite une carte | Pourquoi |
|---|---|
measure, probe-stalls | Chronométrer une instruction et découvrir ce qu'une dépendance exige vraiment en la cassant |
scripts/roundtrip_corpus.py | Réordonnancer chaque noyau du corpus et exécuter les deux versions pour comparer les octets de sortie |
scripts/agreement_sweep.py | Raccourcir une dépendance par noyau et demander au silicium si basalt avait raison |
L'overclock d'usine ne modifie pas les mesures. Chaque latence ici est en cycles, ce qui est une propriété du pipeline plutôt que de l'horloge, et la fréquence boost n'est enregistrée à côté que pour qu'une comparaison en temps d'horloge reste possible. Ce que la carte affecte, c'est la reproductibilité, et c'est pourquoi basalt measure --board l'enregistre.
Tout ce qui a été mesuré ici l'a été sur une seule carte, et basalt enregistre le SKU à côté de chaque mesure plutôt que de les présenter comme universelles. Une 5090 a plus de deux fois plus de SM et son propre comportement d'horloge ; l'encodage sera identique et les latences devraient être re-mesurées plutôt que supposées :```bash python -m basalt.cli measure -o my-card.json python -m basalt.cli verify kernel.cubin --latencies my-card.json
Ce n'est pas de la modestie. Un modèle de latence partagé entre un vérificateur et un ordonnanceur est exactement
là où un mauvais nombre se cache, si bien qu'une seconde carte est la chose la plus utile que l'on puisse apporter.
</details>
## Comment ça marche
Tout repose sur deux oracles, tous deux des binaires NVIDIA standards exécutés comme processus externes. Aucun code source, en-tête ou bibliothèque NVIDIA n'est utilisé ni redistribué.
| Oracle | Invocation | Ce qu'il fournit |
| :--- | :--- | :--- |
| **Vérité terrain** | `ptxas` → cubin → `nvdisasm -c -hex` | Encodages que le compilateur du fournisseur émet réellement. Sémantique incontestable. |
| **Sonde** | `nvdisasm -b SM120a` sur des octets bruts | Décode des mots que `ptxas` n'émettra jamais, ce qui transforme l'espace d'encodage en quelque chose de recherchable plutôt qu'en quelque chose à deviner. |
L'oracle sonde est celui qui compte. Un outil limité à la sortie du compilateur ne peut que redécouvrir ce que le compilateur fait déjà. Nourrir le décodeur directement avec des mots de 128 bits synthétisés signifie que le jeu d'instructions peut être *mesuré*.
Aucun des deux oracles n'a besoin de GPU, si bien que l'intégralité de la base de données d'instructions est reconstruite en CI sur n'importe quelle machine.
### Dériver l'encodage en le modifiant
basalt ne lit une table d'opcodes nulle part. Il prend un encodage assemblé, inverse un bit, décode le résultat et enregistre ce qui a bougé. Un bit qui change le registre de destination est un bit de destination ; un bit qui change le mnémonique est un sélecteur ; un bit qui ne change rien d'observable est inerte.
Exécuté sur `IADD R5, R5, 0x2a`, la mesure ressort comme suit :```
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
Champs de registre de huit bits et valeur immédiate de 32 bits, obtenus par expérimentation plutôt que par hypothèse.
La disposition se valide d'elle-même au contact. Dans un noyau trivial, S2R définit write_barrier=0 et l'IMAD qui consomme son résultat porte wait_mask=0x01 ; LDCU.64 définit write_barrier=1 et le STG.E dépendant porte wait_mask=0x02. Chaque paire producteur-consommateur s'aligne, et les instructions que nvdisasm annote .reuse ont le bit de réutilisation correspondant défini.
Aucune installation CUDA et aucun GPU requis. Le script de la chaîne d'outils récupère des redistribuables épinglés, environ 45 Mo, sans droits administrateur, rien n'est ajouté à votre 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
No content was provided 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 installe la CLI, la bibliothèque et les trois tables mesurées, sans
dépendances d'exécution. Utilisez ceci si vous avez déjà CUDA sur la machine ; le checkout ci-dessus
est ce qu'il vous faut si ce n'est pas le cas, ou si vous avez l'intention de reproduire les mesures.```bash
pip install basalt-sass
basalt doctor
basalt verify kernel.cubin
### Où il cherche `ptxas` et `nvdisasm`
basalt lance les deux comme processus externes et ne redistribue ni l'un ni l'autre, il doit donc en trouver une copie.
Il prend la première qui répond, et **toute installation CUDA 13 fait l'affaire** : rien ne doit être la
redistribution épinglée.
| Ordre | Emplacement |
| ---: | :--- |
| 1 | `--cuda-bin`, passé sur la ligne de commande |
| 2 | `BASALT_CUDA_BIN`, un répertoire contenant les deux binaires |
| 3 | `CUDA_PATH`, `CUDA_HOME` ou `CUDA_ROOT`, chacun plus `/bin` |
| 4 | `ptxas` dans votre `PATH` |
| 5 | `third_party/cuda/<version>/bin` dans un checkout, les plus récents d'abord |
`basalt doctor` affiche celle qui a été résolue, et quitte avec un code non nul lorsqu'il ne peut pas en trouver une, donc
il fonctionne comme une condition préalable d'étape de build plutôt que comme un simple outil à lire.
L'interrogation de la base de données d'instructions ne nécessite aucune chaîne d'outils, car la base de données est mesurée
à l'avance et est incluse dans le package :```bash
basalt isa --stats
basalt isa --opcode QMMA
Reconstruisez la base de données d’instructions à partir de zéro, ou interrogez celle qui est validée :```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
### Vérifiez le code machine que vous n'avez pas écrit
C'est la partie que rien d'autre ne fait, et elle ne nécessite ni GPU ni arguments. Le modèle
de latence mesuré et la table d'exigences extraite sont tous deux commités, de sorte qu'un clone frais peut être
pointé directement vers un cubin, quel que soit ce qui l'a produit :```bash
python -m basalt.cli verify kernel.cubin
Aucun contenu n'a été fourni dans le champ INPUT pour cette traduction. Merci de fournir le texte du chunk 17 à traduire.```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
Une bibliothèque ELF contient des centaines de kernels, et chacun est vérifié indépendamment, car les décalages repartent de zéro et rien ne se propage d'un kernel au suivant. Ajoutez `--strict` pour sortir avec un code non nul en cas de risque, ce qui est ce qu'une étape de build attend. Si vous disposez d'une carte sm_120 et souhaitez que le modèle soit mesuré sur votre propre matériel plutôt que sur celui de ce dépôt :```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
Tout ce que fait la CLI est importable, et la surface de la bibliothèque avec des exemples exécutables se trouve
dans docs/API.md.
Et les deux contrôles qui gardent le reste honnête. Le premier nécessite une carte ; le second nécessite les bibliothèques fournies et aucun matériel du tout :```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
Veuillez fournir le contenu à traduire (chunk 23 de 29) — la section INPUT est vide.```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
Les chiffres ici sont imprimés par l'outillage et se régénèrent à partir d'un checkout propre. Les commandes ci-dessus sont la source de vérité ; ces tableaux sont des instantanés.
Base de données d'instructions. Chaque entrée porte un encodage réellement assemblé et la version du compilateur qui l'a produit.
| Base de données d'instructions |
|---|
La couverture tensorielle est là où vit le matériel basse précision : HMMA et IMMA, QMMA à travers les types FP8, FP6 et FP4, y compris les paires d'opérandes asymétriques, les formes à facteur d'échelle QMMA.SF et OMMA.SF qui portent un exposant par bloc, le IMMA.SP sparse, et les instructions de mouvement matriciel LDSM, STSM et MOVM dans toutes les formes, y compris les variantes de transposition.
Latence, sur une RTX 5070 Ti. 70 SMs, chaque ajustement R² ≥ 0,9998. Mesuré en chronométrant des chaînes dépendantes et en prenant la pente, la longueur de chaîne étant relue dans le SASS compilé plutôt que supposée.
Trois d'entre elles contredisent le modèle supposé avec lequel basalt a été livré : DADD était supposé être de 48, POPC était supposé être de 4, et chaque conversion était supposée être de 6, contre 24 mesurés pour l'aller-retour.
Un modèle de latence supposé n'est pas une petite approximation d'un modèle mesuré, ce qui est tout l'argument en faveur de la mesure.
Et un stall de zéro n'est pas zéro cycle. C'est un encodage sûr distinct qui attend les résultats en attente, coûtant environ 37 cycles là où une instruction planifiée en coûte 4. C'est pourquoi ptxas -O0 émet un mot de contrôle entièrement mis à zéro et que le code calcule quand même correctement, environ neuf fois plus lentement.
Il concorde avec le compilateur du fabricant sur chaque kernel du corpus. Chaque kernel que ptxas construit à partir du corpus est vérifié par rapport à son propre ordonnancement, à chaque niveau d'optimisation qui ordonnance : 30 421 dépendances, zéro erreur. Ce balayage s'exécute en CI à chaque push, et chaque erreur de modélisation que ce projet a commise a été attrapée par lui plutôt que par le raisonnement.
Les verdicts correspondent au silicium. Pour chaque stall encodable sur un producteur dépendant, la réponse statique de basalt et ce que le matériel calcule réellement concordent, y compris le cas zéro. Cela est maintenu comme un test, pas affirmé ici. La preuve complète, y compris trois méthodes indépendantes pour le stall requis et les corrections apportées en cours de route, se trouve dans les constatations.
Et quand il dit qu'un ordonnancement est non sûr, le silicium est d'accord. Prenez le propre ordonnancement fonctionnel du fabricant pour 233 kernels, raccourcissez une vraie dépendance dans chacun, et comparez le verdict de basalt à ce que le GPU calcule : 79 qu'il a déclarés cassés l'étaient bien, et rien de ce qu'il a déclaré sûr n'a calculé une mauvaise réponse. Ce nombre a commencé à 34 manqués plutôt qu'à zéro, et les constatations disent quelle en était la cause et ce que sa correction a coûté en fausses alertes, parce qu'un balayage qui n'aurait jamais rapporté que son chiffre final vaudrait moins que celui qui a rapporté son premier.
Le vérificateur détermine si un ordonnancement est sûr. L'ordonnanceur détermine ce que serait un ordonnancement sûr, à partir des mêmes mesures : il jette chaque bit de contrôle produit par ptxas, calcule les siens, redonne le résultat au vérificateur, puis l'exécute sur le GPU à côté de la version du fabricant du même kernel.
Exécutés sur la carte, sur l'ensemble du corpus, à chaque niveau d'optimisation qui produit un ordonnancement, les 439 kernels comparables ressortent avec des résultats identiques octet pour octet à ceux de l'ordonnancement du fabricant, à partir de bits de contrôle que basalt a déterminés lui-même. Les 2 qui sont exclus lisent l'horloge et l'identifiant de grille, donc ils ne concordent pas non plus avec eux-mêmes, et les constatations le disent plutôt que de les fondre dans un pourcentage.
Ce contrôle est la raison pour laquelle tout le reste est digne de confiance. Le vérificateur et l'ordonnanceur lisent le même modèle de latence, donc une entrée erronée dans celui-ci les satisfait tous les deux à la fois et ils s'accordent entre eux tout en ayant tous les deux tort. Seul le silicium n'a aucun intérêt dans le débat. Exécuter l'ordonnanceur sur sept kernels écrits à la main a passé sept sur sept pendant longtemps ; l'exécuter sur trois cents en a trouvé quarante et un erronés, et depuis, chaque correction du modèle est venue en observant ce nombre évoluer.
C'est de cette boucle que viennent les vrais bugs. Un stall passé en dehors de la fenêtre entre un producteur et son consommateur ne compte pour rien, et le passer là met fin à la recherche avec un programme encore trop court. Un stall épinglé à l'encodage sûr était écrasé par une passe ultérieure, remplaçant une garantie par un petit nombre. Les opérandes fp64 occupent des paires de registres sans que rien dans le mnémonique ne le dise, donc la moitié de chaque dépendance fp64 était invisible pour le vérificateur comme pour l'ordonnanceur. Un prédicat utilisé comme garde d'une instruction a besoin de treize cycles là où le même prédicat lu comme donnée en demande cinq, parce qu'une garde doit être résolue avant même que l'instruction ne soit émise. Et attendre un scoreboard ne règle pas complètement une dépendance : le producteur doit encore un petit stall qui lui est propre, deux cycles pour l'addition fp64, et un cycle de moins est silencieusement incorrect. Aucun de ces bugs n'a été trouvé par le raisonnement ; chacun a été trouvé en exécutant la sortie et en obtenant un mauvais résultat.
[!NOTE] 1.0, et précis sur ce que cela signifie. Ce qui est fait : les deux oracles, la base de données d'instructions dont les champs sont prouvés écrivables, le vérificateur d'aléas sur un vrai graphe de flux de contrôle, la latence mesurée sur un seul SKU par trois méthodes indépendantes, un ordonnanceur qui fait faire un aller-retour à chaque kernel comparable du corpus à travers le matériel, octet pour octet, et un audit de 2 762 kernels livrés tenus à l'écart de chaque tableau que le vérificateur lit. Ce qui ne l'est pas : 12 kernels du corpus qui ne sont pas exécutables par construction et 2 dont la sortie du fabricant n'est pas déterministe, tous nommés dans les constatations ; dix opcodes portent encore une latence supposée plutôt que mesurée, aucun d'eux n'étant jamais un producteur dans l'un ou l'autre code ; et un seul GPU a été mesuré, ce que la constatation 28 montre comme moins important que cela n'en a l'air. Quand quelque chose est inféré plutôt que mesuré, l'outillage le signale plutôt que de le faire passer pour un fait. Voir la feuille de route et la méthode.
```
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>
## Position clean-room
basalt est un travail indépendant et clean-room, réalisé pour l'interopérabilité. Il ne contient aucun code source, en-tête, bibliothèque ou documentation NVIDIA, et n'en redistribue aucun. Il observe le comportement d'exécutables distribués publiquement et l'enregistre, ce qui est le fondement sur lequel ce type de travail repose depuis plus d'une décennie.
NVIDIA, CUDA et Blackwell sont des marques déposées de NVIDIA Corporation. Ce projet n'est ni affilié à NVIDIA, ni approuvé ou sponsorisé par NVIDIA.
Sous licence [Apache-2.0](https://github.com/sunnypatell/basalt/blob/main/LICENSE). Apache plutôt qu'une licence restrictive, à dessein : un outil de correction que personne n'est autorisé à utiliser comme base n'est un outil de correction que personne n'utilise, et la concession de brevets compte pour un travail aussi proche du matériel.
## Contribuer
La contribution la plus précieuse est un encodage que basalt traite incorrectement. Voir [`CONTRIBUTING.md`](https://github.com/sunnypatell/basalt/blob/main/CONTRIBUTING.md) et le [modèle d'écart ISA](https://github.com/sunnypatell/basalt/blob/main/.github/ISSUE_TEMPLATE/isa_gap.yml), qui recueille suffisamment d'informations pour reproduire le problème sans votre machine.
[`SUPPORT.md`](https://github.com/sunnypatell/basalt/blob/main/SUPPORT.md) indique où poser une question, [`GOVERNANCE.md`](https://github.com/sunnypatell/basalt/blob/main/GOVERNANCE.md) ce qu'une modification doit satisfaire, [`RELEASING.md`](https://github.com/sunnypatell/basalt/blob/main/RELEASING.md) comment une version est préparée et vérifiée, et [`SECURITY.md`](https://github.com/sunnypatell/basalt/blob/main/SECURITY.md) comment signaler un problème de manière privée.
## Citer basalt
Si basalt éclaire un article, un outil, un modèle ou un rapport de bug, veuillez le citer. GitHub lit
[`CITATION.cff`](https://github.com/sunnypatell/basalt/blob/main/CITATION.cff) nativement, donc **Cite this repository** dans la barre latérale vous fournit
APA et BibTeX sans transcription. Ce même fichier est ce que Zenodo et les gestionnaires de citations
analysent, et il constitue le registre faisant autorité de la paternité.
La clé ci-dessous est celle que GitHub génère, donc copier d'ici et copier depuis la barre latérale
donnent la même entrée plutôt que deux qui semblent être des travaux différents :```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 | Cycles à attendre avant d'émettre l'instruction suivante |
yield | 109 | Indication que l'ordonnanceur de warps peut permuter les warps |
write_barrier | 112:110 | Scoreboard à signaler lors du write-back (7 = aucun) |
read_barrier | 115:113 | Scoreboard à signaler lors de la lecture d'opérande (7 = aucun) |
wait_mask | 121:116 | Scoreboards qui doivent être libres avant l'émission |
reuse | 125:122 | Drapeaux de cache de réutilisation d'opérande, un par emplacement source |
| Commande | Ce qu'elle fait | Nécessite un GPU |
|---|
doctor | Vérifier les deux oracles de bout en bout | non |
build-isa | Récolter et sonder, écrire la base de données d'instructions | non |
isa | Interroger une forme, un opcode ou la couverture | non |
validate-isa | Prouver que les champs mesurés peuvent être écrits | non |
mine-stalls | Apprendre les exigences par paire à partir de ce que le compilateur ordonnance | non |
verify | Vérifier les bits de contrôle d'un cubin pour détecter les aléas de données | non |
schedule | Attribuer les bits de contrôle d'un cubin à partir de zéro et vérifier le résultat | non |
assemble | Encoder du texte SASS, ou un cubin entier, et le relire pour le prouver | non |
measure | Mesurer la latence des instructions sur du silicium réel | oui |
probe-stalls | Trouver le stall requis en cassant volontairement des programmes | oui |
| Nombre |
|---|
| Formes d'instructions | 345 |
| Opcodes distincts | 90 |
| Formes avec une carte d'opérandes complète | 339 |
| Formes tensor-core | 46 |
| Compilé avec | ptxas V13.3.73 |
| Instructions | Cycles |
|---|
IMAD IADD3 FFMA FADD FMUL LOP3 SHF | 4 |
POPC | 18 |
I2FP + F2I ensemble | 24 |
MUFU | 44 |
DADD DFMA | 64 |
stall | cycles/instruction | résultat |
|---|
| 0 | 36,85 | correct |
| 1 | 4,88 | incorrect |
| 2 | 4,88 | incorrect |
| 3 | 5,88 | incorrect |
| 4 | 6,88 | correct |
Citez le DOI de concept, 10.5281/zenodo.22072811, plutôt
qu'un DOI de version ou cette URL. Il pointe vers la version la plus récente, il reste donc correct sans
jamais avoir besoin d'être modifié. CITATION.cff le contient, il est donc déjà présent sous les deux formes ci-dessus.
Si vous réutilisez les tableaux mesurés (src/basalt/data/) ou reproduisez une figure, citez la version
dont ils proviennent plutôt que main : les valeurs sont régénérées par scripts/verify_all.py à
un commit spécifique, et un tag est ce qui rend cela reproductible.
L'attribution est une condition de licence, pas une courtoisie. Apache-2.0 §4 exige que
LICENSE et NOTICE accompagnent toute redistribution ou œuvre dérivée,
et NOTICE contient la mention de paternité et la déclaration de clean-room. Les forks, les copies intégrées et
les wheels reconstruits conservent tous les deux fichiers.
Sunny Patel · sunnypatel.net · github.com/sunnypatell