一条 NVIDIA GPU 指令有 128 位,其中 21 位根本不是指令本身。它们是一个调度控制字,从 stall 到 reuse:在发出下一条指令之前要停顿多少个周期、要通知哪些 scoreboard、要等待哪些 scoreboard,以及哪些操作数可以由复用缓存提供服务。
硬件不会检查其中的任何一位。 在 sm_120 上,固定延迟指令没有任何互锁机制。硅片信任任何产生控制字的东西。如果 stall 计数小于下一条指令所消费的值的延迟,那么不会发生故障、不会停顿、也不会发出警告。指令读取一个尚未写入的寄存器,并以全速、每次都对陈旧数据进行计算。
这是一种奇怪的 bug。它不会崩溃,不会出现在调试器中。它产生的数字仅仅是错的——在矩阵乘法或注意力内核中,这意味着模型训练得略微糟糕,而不是明显地坏掉。
最便宜的那三种编码恰好是坏掉的,而且没有任何地方会报告这一点。同时请注意 左数第一根柱子的含义:stall 为零并不是零个周期,它是一种特殊的安全编码, 会等待未完成的结果,花费约是调度指令的九倍。如果某个检查器把它读作零, 就会把正确的程序判定为错误。
为这种架构生成机器码的工具,会根据延迟模型来分派这些控制位。basalt 就是检查答案正确性的东西。
NVIDIA 给你一个编译器来写这 21 位。它不给你任何能读回并告诉你这些位是否安全的东西,其他人也没有。
NVIDIA GPU 的汇编器已经存在十年了,Blackwell 编码此前也已被逆向过,有公开的 sm_120 周期级特征描述,并且这种架构已经有一个公开汇编器会自行分派调度控制位,并在显卡上运行自己的内核来确认答案正确。这些都对,但都不是那个声明:
没有其他工具可以被人塞给它一个不是它自己生成的 cubin,然后让它判断其中的调度控制位是否安全。
你的编译器生成了那个 cubin,或者某个库发布了它,又或者有人手写了它,而在此之前没有任何方法去询问。在一个没有硬件互锁的架构上,这就是“能跑”与“正确”之间的差别,而这一差别是不可见的:stall 少一个周期就会读取过期寄存器,并以全速返回错误数字,没有故障、没有警告,每次都一样。
这里其他所有东西,都是为了让这句话变得可测试。汇编器是用于构建一个刻意缩短了一个 stall 的程序。调度器是用于迫使模型给出一个答案,而不是评判别人的答案。而审计,则让这句话不再是一个空缺,而变成一次测量:basalt 指向 NVIDIA 在 cuBLAS、cuSOLVER、cuSPARSE、NPP 等库中随附的 2,473 个 sm_120 cubin。
用领域自己的话来说,为什么它之前不存在。 使用最广泛的 SASS 汇编器在其自己的文档中说,“对整个程序进行严格的正确性检查……在没有官方支持的情况下远非可能。因此,保证程序正确性的责任被留给了用户,汇编器只能提供非常有限的帮助。” SIP 在关于 SASS 调度自动调优时指出,“对 GPU 原生汇编代码进行验证是不可能的,因为 sass 的形式语义是闭源的。”
两者讲的都是语义正确性:内核计算的是否是它应当计算的东西。basalt 不回答这个问题,这里也没有任何东西声称回答它。它回答的是一个严格更小的问题,而重点在于,这个更小的问题不需要语义就可以判定:
这个程序的控制位是否覆盖了它自身的数据依赖?
这需要依赖结构(编码本身就会暴露),以及一个延迟模型(硅片在测量下会给出)。两者都不需要知道任何指令计算什么。一个内核可以通过这项检查却仍然是错误的算法;但它不可能在值还未落地之前就去读寄存器。
其次,没有任何其他东西是拿厂商自己的字节来度量的。 basalt 的参照物是 ptxas 的输出,因此一旦出现分歧,在没有证明相反情况之前,那就是 basalt 的 bug。它的汇编器必须逐位重现编译器的精确 128 位。它的调度器必须丢弃编译器选择的每一个控制位,计算新的,并让 GPU 计算出同样的答案。
对这一切只有一个标准:与厂商完全一致,否则就说清为什么。
还有调度器通常避而不谈的部分:正确性的代价。basalt 的调度所花费的 issue 周期是厂商的 1.05 倍,在 1,323 个内核与优化级别组合中有 111 个更慢、842 个更便宜,并且每个可比内核在 GPU 上仍然保持字节一致。
第三行,正是改变了其他三行的那个。一个在语料库上校准过的检查器,不可能在该语料库上失败:编译器被观察到留下的最小间隙就是下限——这是由构造决定的,仅对它被测过的那段代码成立。这个检查器第一次见到来自其他地方的代码时,在一个从未返回过错像素的 JPEG 解码器中报告了每个内核 26 个 hazard,全部 6,593 个都是 basalt 自己的。修正这些后降到了零,但一个库上的零也并不是证据:将保留集合扩大到三个库、520 万条指令后,数字直接回到 940,并在先前八个模型错误之外又发现了五个。十三次修正,没有一次是 NVIDIA 的,而从 24,311 个已发布内核中重新挖掘出来的需求,在 229,567 个观测点上设置了一个 13 周期的守卫谓词——这个数字正是本卡上通过故意弄坏程序所测得的故障注入数量。参见 发现 32。
比厂商更便宜,并不是一件值得沾沾自喜的事。basalt 将每一个依赖都调度到 ptxas 在那种精确配对下被观察到的最小间隙,而 ptxas 在优化一个数字的同时,还在平衡寄存器压力和内存以及 issue 延迟。这个数字也不是一眼就被相信的:第一次比率低于 1.0 时,硬件往返测试失败了,直到它暴露出的那个 bug 被修复,数字才算数。因此,这个比率在测试套件中被从两边固定。
中间那一列才是关键。一个检查器和一个调度器共享同一个延迟模型,它们会彼此一致,同时却都可能是错的,所以任何一方都不能为另一方作证;只有硅片在这场争论中没有利益。在一段时间里,用七个手写内核运行调度器,七个全部通过。改用三百个运行时,发现四十一个是错的,而 发现 中的每一个修正,都来自观察那个数字的变动。
同样的纪律也适用于输入。只有当过期值与新值不同时,过期读取才会改变答案,所以一种字节模式只是一次发现问题的机会。让每个内核再跑第二种、第三种、第四种模式后,立刻发现了一个进位输出谓词——操作数模型从一开始就把这个谓词当作源读取。在那之前,它通过了每一项控制,包括往返测试本身。
同样的纪律决定了汇编器被允许做什么,而且有必要把它拥有的两个数字分开。
覆盖率是语料库的 99.9% 和已发布库代码的 87.5%。正确率是 100%,而正是这个数字被一个测试钉死。 两者之间的差距是 basalt 拒绝的指令,每条都指名了它无法放置的字段,因为一个靠猜的工具可以通过发出那些反汇编成正确文本、却计算出别的什么东西的字,来达到完整覆盖率。它从未发出过这样的字——在 59,760 条语料指令和 5,237,448 条已发布指令上从未有过。
它是经历了八轮自信的错误之后才走到这一步的:
其中每一个都产生了一个能汇编、能反汇编回完全相同的文本、却计算出别的东西的字。这正是本仓库其余部分存在所要捕获的那类失败,这也是为什么现在这八个都被拒绝,并附上说明该字段真实含义的理由,以及为什么“汇编成错误字节的指令数量”是一个被钉在零的测试,而不是表中的某个数字。
第九个是在汇编器第一次被指向不是它生成的机器码时出现的,而且是另一种类型。c[0x0][UR4] 用一个寄存器来索引其偏移量,而记录下来的形式里存放的是一个数字,于是编码器抛出了异常,而不是拒绝。在外部输入上崩溃,比给出错误判定更糟糕,因为调用者两者都得不到。
sm_120 不是型号。它是整个消费级 Blackwell 产品线共用的计算能力,因此指令编码、数据库、汇编器和检查器适用于其中的每一张卡:
ptxas 还面向 sm_121,这是同一家族中的另一款芯片。basalt 从未在上面运行过,也不声称支持它。它能说的都是实测结果:编译器在这里提供的全部六个目标上发出字节一致的代码,包括控制字,因此内核所需的调度是架构的属性,而不是具体芯片的属性(发现 28)。如果这不是真的,NVIDIA 自己的编译器就会为其中一个目标发出不安全的调度。
这里在硅片上测得的每个数字,都来自一张具体的物理显卡,并且点名说明,因为“一块 5070 Ti”不足以复现一次运行:
basalt 的大部分功能完全不需要 GPU。 两个 oracle、指令数据库、汇编器和 hazard 检查器都以普通子进程的方式针对 ptxas 和 nvdisasm 运行,这也是为什么它们能在 CI 的一台没有显卡的机器上运行。252 个测试中有 237 个属于这一组,其中 200 个既不需要显卡也不需要 NVIDIA 二进制文件。
GPU 只被用于三件事,而正是这三件事把一个貌似可信的工具变成了可信的工具:
这里测到的一切都在一张卡上测得,basalt 会把 SKU 和每次测量一起记录下来,而不是把它们当作普适数据。5090 的 SM 数量是两倍多,而且有自己的时钟行为;编码会相同,但延迟应当重新测量,而不是假定:```bash python -m basalt.cli measure -o my-card.json python -m basalt.cli verify kernel.cubin --latencies my-card.json
这并非谦虚。检查器与调度器共用的延迟模型,正是错误数字藏身之处,因此第二张显卡是任何人所能贡献的最有用的东西。
</details>
## 工作原理
整个机制建立于两个 oracle 之上,二者皆为以外部进程方式驱动的 NVIDIA 官方二进制程序。不使用、也不重新分发任何 NVIDIA 源代码、头文件或库。
| Oracle | 调用方式 | 提供内容 |
| :--- | :--- | :--- |
| **基准真值** | `ptxas` → cubin → `nvdisasm -c -hex` | 厂商编译器实际生成的编码。语义无可争议。 |
| **探针** | `nvdisasm -b SM120a` 作用于原始字节 | 解码 `ptxas` 永远不会生成的指令字,由此将编码空间变为可搜索,而非靠猜测。 |
探针 oracle 才是关键。受限于编译器输出的工具,只能重新发现编译器已经做过的事情。将合成的 128 位指令字直接送入解码器,意味着指令集可以被*测量*。
两个 oracle 都不需要 GPU,因此整个指令数据库可在任意机器上的 CI 中重建。
### 通过改变编码来推导编码
basalt 不会从任何地方读取操作码表。它取一条已成功汇编的编码,翻转其中一位,解码结果,并记录发生了什么变化。改变目标寄存器的位是目标位;改变助记符的位是选择器;不引起任何可观察变化的位是惰性位。
对 `IADD R5, R5, 0x2a` 运行测量,结果如下:```
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
8 位寄存器字段和一个 32 位立即数,是通过实验而非假设得出的。
| 组件 | 作用 | 检查方式 | 结果 |
|---|
| 汇编器 | 将 SASS 文本转为 128 位字 | 重新汇编 ptxas 发出的每条指令并逐字节比较,在语料库以及 520 万条从未见过的已发布库指令上进行 | 59,760 条语料指令中有 59,693 条、5,237,448 条已发布指令中有 4,585,336 条完全一致,其余按名称拒绝,两者中 0 条错误 |
| 检查器 | 读取调度,报告 hazard | 厂商自己的输出必须能干净通过验证,而一个刻意缩短的 stall 必须被捕获 | 在 1,323 个厂商内核与优化级别组合上 0 个错误,在 233 个被破坏的内核上 0 个漏报 |
| 审计 | 同一个检查器,用于已发布的库 | 在来自生产环境、且从其读取的每张表中被排除的 sm_120 内核上运行 | 在 2,762 个内核和 10,218,030 个依赖上 0 个错误,2,762 个全部完成分析 |
| 调度器 | 从零开始分派每一位控制位 | 丢弃厂商的控制位,计算新的,在 GPU 上针对八个输入分别运行两者,比较输出字节 | 439 个可比内核中有 439 个字节一致,在全部三个优化级别下均如此 |
| 显卡 | 计算能力 | 已覆盖 |
|---|
| GeForce RTX 5090、5090D | 12.0(sm_120) | 是 |
| GeForce RTX 5080、5070 Ti、5070 | 12.0(sm_120) | 是 |
| GeForce RTX 5060 Ti、5060、5050 | 12.0(sm_120) | 是 |
| GeForce RTX 50 系列笔记本部件 | 12.0(sm_120) | 是 |
| RTX PRO Blackwell 工作站显卡 | 12.0(sm_120) | 是 |
| 数据中心 Blackwell(B100、B200、GB200) | 10.0(sm_100) | 否,编码不同 |
| 这张卡 | 具体是什么 |
|---|
| 品牌型号 | 技嘉 GeForce RTX 5070 Ti EAGLE OC |
| 驱动报告 | NVIDIA GeForce RTX 5070 Ti |
| 计算能力 | 12.0 |
| 流多处理器 | 70 |
| 加速频率 | 2542 MHz |
| 工具链 | CUDA 13.3.1,ptxas V13.3.73 |
| 需要显卡 | 原因 |
|---|
measure、probe-stalls | 对指令计时,并通过破坏依赖来找出依赖真正需要什么 |
scripts/roundtrip_corpus.py | 重新调度每个语料内核,并运行两个版本以比较输出字节 |
scripts/agreement_sweep.py | 每个内核缩短一个依赖,然后询问硅片 basalt 是否正确 |
出厂超频不会改变测量结果。这里的每个延迟都以周期为单位,这是流水线的属性,而不是时钟的属性;加速频率被记录在旁边,只是为了让墙钟时间比较仍然可能。这块板真正影响的是可复现性,这也是为什么 basalt measure --board 会记录它。
| 字段 | 位 | 含义 |
|---|
stall | 108:105 | 在发出下一条指令前等待的周期数 |
yield | 109 | 提示 warp 调度器可以切换 warp |
write_barrier | 112:110 | 写回时发出信号的记分板(7 = 无) |
read_barrier | 115:113 | 操作数读取时发出信号的记分板(7 = 无) |
wait_mask | 121:116 | 在发出前必须清除的记分板 |
reuse | 125:122 | 操作数复用缓存标志,每个源槽位一个 |
这种布局一经使用便自证其正确。在一个简单内核中,S2R 设置 write_barrier=0,而消费其结果的那条 IMAD 带有 wait_mask=0x01;LDCU.64 设置 write_barrier=1,依赖它的 STG.E 带有 wait_mask=0x02。每一对生产者与消费者都能一一对应,并且 nvdisasm 标注为 .reuse 的指令也设置了相应的复用位。
无需安装 CUDA,也无需 GPU。工具链脚本会获取固定的可再分发组件,大小约 45 MB,无需管理员权限,也不会向您的 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 input content was provided.```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 会安装 CLI、库以及全部三个测量表,
且没有运行时依赖。如果机器上已经装有 CUDA,可以使用这种方式;
如果尚未安装,或想要复现测量结果,则应该使用上面的 checkout 方式。```bash
pip install basalt-sass
basalt doctor
basalt verify kernel.cubin
### 它在哪里查找 `ptxas` 和 `nvdisasm`
basalt 将两者作为外部进程驱动,并且不随包重新分发,因此它必须找到一份副本。
它选取第一个响应的副本,并且**任何 CUDA 13 安装都可以**:无需使用固定的可再发行版本。
| 顺序 | 位置 |
| ---: | :--- |
| 1 | `--cuda-bin`,通过命令行传入 |
| 2 | `BASALT_CUDA_BIN`,包含这两个二进制文件的目录 |
| 3 | `CUDA_PATH`、`CUDA_HOME` 或 `CUDA_ROOT`,各自加上 `/bin` |
| 4 | 您 `PATH` 中的 `ptxas` |
| 5 | 检出(checkout)中的 `third_party/cuda/<version>/bin`,最新的优先 |
`basalt doctor` 会打印它解析到了哪一个,并在找不到任何副本时以非零状态退出,因此它可作为构建步骤的前置条件,而不仅仅是供阅读的内容。
查询指令数据库完全不需要工具链,因为该数据库是预先测量好的,并随包一起提供:```bash
basalt isa --stats
basalt isa --opcode QMMA
从头重建指令数据库,或查询已提交的数据库:```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
### 检查不是你编写的机器码
这是其他工具都不具备的功能,它不需要 GPU,也不需要任何参数。测量得到的
延迟模型和挖掘出的需求表都已提交,因此一个全新克隆的仓库可以
直接指向某个 cubin,无论它是由什么生成的:```bash
python -m basalt.cli verify kernel.cubin
(empty)```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
一个库 ELF 包含数百个内核,每个内核都会单独检查,因为偏移量
从零重新开始,不会从一个延续到下一个。添加 `--strict` 可在遇到风险时以非零状态退出,
这正是构建步骤所期望的。如果你有一块 sm_120 显卡,并希望
在你自己的芯片上测量模型,而不是在本仓库的芯片上:```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
| 命令 | 功能 | 需要 GPU |
|---|---|---|
doctor | 端到端检查两个 oracle | 否 |
build-isa | 收集并探测,写入指令数据库 | 否 |
isa | 查询表单、操作码或覆盖率 | 否 |
validate-isa | 证明测得的字段可以被写入 | 否 |
mine-stalls | 根据编译器的调度学习每对需求 | 否 |
verify | 检查 cubin 的控制位是否存在数据冒险 | 否 |
schedule | 从头分配 cubin 的控制位并检查结果 | 否 |
assemble | 编码 SASS 文本或整个 cubin,并读回以证明 | 否 |
measure | 在真实硅片上测量指令延迟 | 是 |
probe-stalls | 通过故意破坏程序来找出所需的停顿 | 是 |
CLI 能做到的一切都可导入,带有可运行示例的库接口
位于 docs/API.md。
还有两个让其余部分保持可信的校验机制。第一个需要显卡;第二个需要 随附的库,完全不需要硬件:```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
输入内容为空,没有可翻译的文本。```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
这里的数字由工具打印,并在干净的检出(clean checkout)下重新生成。上面的命令才是事实来源;这些表格只是快照。
指令数据库。 每个条目都承载着一个实际汇编出的编码以及生成它的编译器构建。
| 指令数据库 | 数量 |
|---|---|
| 指令形式 | 345 |
| 不同操作码 | 90 |
| 带完整操作数映射的形式 | 339 |
| 张量核心形式 | 46 |
| 构建工具 | ptxas V13.3.73 |
张量覆盖范围正是低精度硬件的所在之处:HMMA 和 IMMA,QMMA 涵盖 FP8、FP6 和 FP4 类型,包括非对称操作数对;携带每块指数的比例因子形式 QMMA.SF 和 OMMA.SF;稀疏 IMMA.SP;以及矩阵移动指令 LDSM、STSM 和 MOVM 的所有形状,包括转置变体。
延迟,基于 RTX 5070 Ti。 70 个 SM,每次拟合 R² ≥ 0.9998。测量方法是对依赖链计时并取斜率,链长度从编译后的 SASS 中读回,而非假设。
| 指令 | 周期 |
|---|---|
IMAD IADD3 FFMA FADD FMUL LOP3 SHF | 4 |
POPC | 18 |
I2FP + F2I 合计 | 24 |
MUFU | 44 |
DADD DFMA | 64 |
其中三项与 basalt 随附的假设模型相矛盾:DADD 被假设为 48,POPC 被假设为 4,每项转换被假设为 6,而实测往返为 24。
假设的延迟模型并非实测模型的轻微近似,这正是测量的全部意义所在。
零停顿(stall)并不是零周期。 它是一种独特的、安全的编码,用于等待未完成的结果,在一条已调度的指令仅需 4 个周期的情况下,它要耗费约 37 个周期。这就是为什么 ptxas -O0 会发出一个完全置零的控制字,而代码仍然能正确计算,只是大约慢了九倍。
stall | 周期/指令 | 结果 |
|---|---|---|
| 0 | 36.85 | 正确 |
| 1 | 4.88 | 错误 |
| 2 | 4.88 | 错误 |
| 3 | 5.88 | 错误 |
| 4 | 6.88 | 正确 |
它在语料库的每个内核上都与厂商编译器一致。 ptxas 从语料库构建的每个内核,都会在每一个进行调度的优化级别下,依据其自身的调度进行验证:30,421 条依赖,零错误。该扫描在每次推送时都会在 CI 中运行,这个项目犯过的每一个建模错误都是被它捕获的,而不是靠推理。
判定与芯片一致。 对于依赖生产者上的每一个可编码停顿,basalt 的静态答案与硬件实际计算的结果都一致,包括零停顿的情况。这一点是以测试形式维护的,而不是在此处断言。完整证据(包括确定所需停顿的三种独立方法以及过程中所做的修正)见 发现。
当它说某个调度不安全时,芯片也同意。 取厂商自己针对 233 个内核的工作调度,在每个内核中缩短一个真实依赖,然后将 basalt 的判定与 GPU 实际计算的结果进行比较:它判定为损坏的 79 个确实损坏,而 它判定为安全的调度均未计算出错误答案。这个数字最初是 34 个漏报,而不是 0,发现 说明了原因,以及修复它所带来的误报代价,因为一个只报告最终数字的扫描,其价值不如一个同时报告最初数字的扫描。
验证器回答的是某个调度是否安全。调度器则基于同样的测量,回答一个安全的调度应该是什么样的:它丢弃 ptxas 产生的每一个控制位,计算自己的控制位,把结果交还给验证器,然后在 GPU 上将其与厂商版本的同一内核并排运行。
在显卡上对整个语料库运行,在每一个会生成调度的优化级别下,所有 439 个可比较的内核最终都由 basalt 自行算出的控制位,计算出了与厂商调度逐字节一致的结果。被排除的 2 个内核会读取时钟和网格 id,因此它们连自身都无法保持一致,发现 明确说明了这一点,而不是把它们并入某个百分比。
这种控制正是其余一切可信的原因。检查器和调度器读取同一个延迟模型,因此模型中的一个错误条目会同时满足两者,使它们彼此一致,而实际上两者都是错的。只有芯片在这件事上没有既得利益。调度器在七个手写内核上运行,很长一段时间内都是七战七胜;在三百个内核上运行,则发现了四十一个错误的;此后每一次模型修正都来自观察这个数字的变化。
这个循环正是真正 bug 的来源。花费在生产者与其消费者之间窗口之外的停顿毫无意义,而把它花在那里会让搜索最终得到一个仍然不足的程序。一个被固定到安全编码上的停顿被后续的 pass 覆盖,用一个小数字替换了保证。fp64 操作数占用寄存器对,而助记符中没有任何信息表明这一点,因此每个 fp64 依赖的一半对检查器和调度器都是不可见的。用作指令守护条件的谓词需要十三个周期,而同样的谓词作为数据读取时只需要五个周期,因为守护条件必须在指令发出之前就被解析。等待记分板并不能完全解决依赖:生产者仍有自己的一小段停顿,fp64 加法为两个周期,少一个周期就会静默地出错。这些都不是靠推理发现的;每一个都是通过运行输出并得到错误数字而发现的。
[!NOTE] 1.0,并明确说明其含义。 已完成的部分:两个 oracle(指令数据库,其字段已被证明可写;以及基于真实控制流图的冒险检查器);在一款 SKU 上通过三种独立方法测量的延迟;一个能对每个可比较的语料库内核进行逐字节硬件往返验证的调度器;以及一项对 2,762 个已发布内核的审计,这些内核被排除在检查器读取的所有表格之外。未完成的部分:12 个因构造而无法运行的语料库内核,以及 2 个厂商输出不确定的内核,均已在发现中列出;十个操作码仍然带有假设延迟而非实测延迟,且它们在任何一份代码中都从未作为生产者出现;另外只测量了一款 GPU,发现 28 表明这影响比听起来小。当某些内容是推断而非测量得到时,工具会明确指出,而不会把它美化成为事实。参见 路线图 和 方法。
```
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>
## 洁净室立场
basalt 是一项独立的、基于洁净室方法开发的互操作性工作。它不包含任何 NVIDIA 源代码、头文件、库或文档,也不重新分发任何内容。它观察公开分发的可执行程序的行为并记录之,这正是此类工作十多年来所立足的基础。
NVIDIA、CUDA 和 Blackwell 是 NVIDIA Corporation 的商标。本项目与 NVIDIA 无关联,未获其认可或赞助。
依据 [Apache-2.0](https://github.com/sunnypatell/basalt/blob/main/LICENSE) 许可。刻意选择 Apache 而非某些限制性许可,原因在于:一个不允许任何人基于其构建的正确性工具,就是没人会运行的正确性工具;而且对于如此贴近硬件的工作而言,专利授权至关重要。
## 贡献
最具价值的贡献是 basalt 编码错误的案例。请参阅 [`CONTRIBUTING.md`](https://github.com/sunnypatell/basalt/blob/main/CONTRIBUTING.md) 和 [ISA gap 模板](https://github.com/sunnypatell/basalt/blob/main/.github/ISSUE_TEMPLATE/isa_gap.yml),该模板收集了足够的信息,可在无需你的机器的情况下复现问题。
[`SUPPORT.md`](https://github.com/sunnypatell/basalt/blob/main/SUPPORT.md) 说明了问题应该去哪里提问,[`GOVERNANCE.md`](https://github.com/sunnypatell/basalt/blob/main/GOVERNANCE.md) 说明了变更需要满足哪些条件,[`RELEASING.md`](https://github.com/sunnypatell/basalt/blob/main/RELEASING.md) 说明了版本发布及验证的方式,[`SECURITY.md`](https://github.com/sunnypatell/basalt/blob/main/SECURITY.md) 则说明了如何私下报告安全问题。
## 引用 basalt
如果 basalt 为论文、工具、模型或 bug 报告提供了信息,请引用它。GitHub 原生读取
[`CITATION.cff`](https://github.com/sunnypatell/basalt/blob/main/CITATION.cff),因此侧边栏中的 **Cite this repository** 可直接为你提供
APA 和 BibTeX 格式,无需手动转录。同一文件也是 Zenodo 和文献管理工具所解析的内容,并且是作者身份的唯一权威记录。
下面的按键是 GitHub 自动生成的,因此从这里复制和从侧边栏复制会得到相同的条目,而不是两个看似不同作品的条目:```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}
}
请引用概念 DOI 10.5281/zenodo.22072811,而不是版本 DOI 或此 URL。它会解析到最新的发布版本,因此无需再次编辑即可保持正确。CITATION.cff 携带该 DOI,因此上述两种形式中都已包含。
如果你重复使用已测量的数据表(src/basalt/data/)或复现某个图表,请引用它们所来自的发布版本,而不是 main:这些数字由 scripts/verify_all.py 在特定提交时重新生成,而标签(tag)正是使其可重现的关键。
署名是许可条款,而非礼节。 Apache-2.0 §4 要求 LICENSE 和 NOTICE 随任何再分发或衍生作品一并提供,且 NOTICE 包含作者信息和净室声明。分叉、供应商副本和重新打包的 wheel 均须保留这两个文件。
Sunny Patel · sunnypatel.net · github.com/sunnypatell