Skip to content
KitploitKITPLOIT
ИнструментыБлог
Отправить
ИнструментыБлог
Отправить

Инструменты для хакинга, пентеста и кибербезопасности — ваш арсенал защиты!

Kitploit — это каталог инструментов для хакинга, кибербезопасности и пентестинга. Находите последние обновления проектов для поиска уязвимостей, анализа систем, автоматизации тестирования и усиления вашей безопасности.

··Ленты·Контакты·Конфиденциальность·© 2026 Kitploit

Каталог инструментов

Категории

Все категории
Loading categories
basalt — Первый в мире детектор конфликтов (hazard checker) для NVIDIA Blackwell (sm_120) с ассемблером и планировщиком, сверенными байт в байт с собственным компилятором NVIDIA. Та проверка, которую они так и не выпустили. | Kitploit
Инструменты/GitHubGitHub/sunnypatell/basalt
Статический анализАнализ уязвимостейАнализ КодаОбратная инженерияАппаратная БезопасностьАнализ Бинарных Файлов
GitHubsunnypatell/basalt

basalt

Первый в мире детектор конфликтов (hazard checker) для NVIDIA Blackwell (sm_120) с ассемблером и планировщиком, сверенными байт в байт с собственным компилятором NVIDIA. Та проверка, которую они так и не выпустили.

Репозиторий
30 дней назадЕщё не проверено
Сайт

Популярное

Смотреть все →

Откройте для себя самые используемые инструменты нашего сообщества.

Изучить все инструменты

Просмотрите нашу коллекцию инструментов

Смотреть все инструменты →
Поделиться
basalt: первый в мире проверщик рисков для NVIDIA Blackwell (sm_120) с ассемблером и планировщиком, побайтово сверенными с собственным компилятором. Та проверка, которую они так и не выпустили. В sm_120 нет аппаратной блокировки, поэтому один неверный счётчик тактов заставляет GPU читать устаревший регистр и молча возвращать неверный ответ.
Архитектура Python Лицензия PyPI

CI Зависимости времени выполнения GPU не требуется

Проверки

DOI 10.5281/zenodo.22072811 Архивировано на Zenodo ORCID 0009-0005-3863-7642 Цитировать этот репозиторий


Проблема · Какие GPU · Как это работает · Быстрый старт · Измерено, а не предположено · Находки · API · Метод · Дорожная карта · Чистая комната


Проблема

Инструкция NVIDIA GPU состоит из 128 бит, и 21 из них — это вовсе не инструкция. Это управляющее слово планировщика, от stall до reuse: сколько тактов ждать перед выдачей следующей инструкции, какие скорборды сигнализировать, какие ожидать и какие операнды могут быть обслужены из кэша повторного использования.

Аппаратура не проверяет ничего из этого. В sm_120 нет блокировки (interlock) для инструкций с фиксированной задержкой. Кремний доверяет тому, что создало управляющее слово. Если количество тактов ожидания меньше задержки значения, которое потребляет следующая инструкция, не возникает ни сбоя, ни ожидания, ни предупреждения. Инструкция читает ещё не записанный регистр и каждый раз вычисляет на устаревших данных, на полной скорости.

Это странный вид ошибки. Она не приводит к падению. Она не видна в отладчике. Она даёт числа, которые просто неверны, а в умножении матриц или в kernel внимания это означает модель, которая обучается слегка неправильно, а не модель, которая наглядно ломается.

Тактов на инструкцию для каждого значения stall в sm_120: stall 0 стоит 36,85 такта и корректен; 1, 2 и 3 стоят 4,88, 4,88 и 5,88 и молча возвращают неверный ответ; 4, 8 и 15 стоят 6,88, 10,88 и 18,02 и корректны.

Три самые дешёвые кодировки — сломанные, и нигде об этом не сообщается. Обратите также внимание на левый столбец: stall ноль — это не ноль тактов, а отдельная безопасная кодировка, которая ожидает незавершённые результаты и стоит примерно в девять раз дороже запланированной инструкции. Проверщик, прочитавший её как ноль, назвал бы корректные программы сломанными.

Инструменты, генерирующие машинный код для этой архитектуры, присваивают эти управляющие биты на основе модели задержек. basalt — это то, что проверяет ответ.

Проверка, которую NVIDIA так и не выпустила

NVIDIA даёт вам компилятор, который записывает эти 21 бит. Она не даёт вам ничего, что прочитало бы их обратно и сказало, что они безопасны, — и никто другой тоже не даёт.

Ассемблеры для NVIDIA GPU существуют уже десятилетие, кодировка Blackwell была реверс-инженерингована ранее, есть опубликованные характеристики sm_120 на уровне тактов, и один публичный ассемблер для этой архитектуры уже сам назначает управляющие биты планировщика и запускает свои собственные kernel'ы на карте, чтобы убедиться, что ответы получаются правильными. Всё это правда, и ни одно из этих утверждений не является главным:

Никакому другому инструменту нельзя передать cubin, который он не создавал, и попросить сказать, безопасны ли его управляющие биты планировщика.

Ваш компилятор создал этот cubin, или его поставила библиотека, или кто-то написал его вручную — и до сих пор не было способа спросить. В архитектуре без аппаратной блокировки это разница между «он запустился» и «он корректен», и эта разница невидима: stall на один такт короче читает устаревший регистр и возвращает неверное число на полной скорости, без сбоя и без предупреждения, каждый раз.

Всё остальное здесь существует для того, чтобы сделать это утверждение проверяемым. Ассемблер — это то, что собирает программу с одним намеренно укороченным stall. Планировщик — это то, что заставляет модель зафиксировать ответ, а не оценивать чужой. А аудит — это то, где утверждение перестаёт быть отсутствием и становится измерением: basalt направили на 2,473 cubin'а sm_120, которые NVIDIA поставляет в cuBLAS, cuSOLVER, cuSPARSE, NPP и остальных.

Почему этого не существовало, словами самого сообщества. Самый используемый SASS-ассемблер в своей документации говорит, что «проверка строгой корректности всей программы … [далеко] не возможна без официальной поддержки. Поэтому гарантировать корректность программы предоставлено пользователю, при весьма ограниченной помощи со стороны ассемблера». SIP, занимаясь автонастройкой SASS-расписаний, утверждает, что «валидация невозможна для нативных ассемблерных кодов GPU, поскольку формальная семантика sass закрыта (closed-source)».

Оба утверждения касаются семантической корректности: вычисляет ли kernel то, что должен. basalt на это не отвечает, и ничто здесь на это не претендует. Он отвечает на строго более узкий вопрос, и суть в том, что этот более узкий вопрос разрешим без семантики:

Покрывают ли управляющие биты этой программы её собственные зависимости по данным?

Для этого нужна структура зависимостей, которую раскрывает кодировка, и модель задержек, которую раскрывает кремний при измерении. Ни то, ни другое не требует знать, что вычисляет какая-либо инструкция. Kernel может пройти эту проверку и всё равно быть неверным алгоритмом; чего он не может — так это прочитать регистр до того, как значение приземлится.

Во-вторых, ничто другое не сверяется с собственными байтами вендора. Эталон basalt — вывод ptxas, поэтому расхождение — это баг basalt, пока не доказано обратное. Его ассемблер должен воспроизводить точные 128 бит компилятора. Его планировщик должен отбросить все управляющие биты, выбранные компилятором, вычислить новые и заставить GPU вычислить тот же ответ.

Один стандарт для всего этого: точно соглашаться с вендором — или объяснить, почему нет.

КомпонентЧто делаетКак проверяетсяРезультат
АссемблерТекст SASS в 128-битное словоПовторно ассемблировать каждую инструкцию, выпущенную ptxas, и сравнить байты, на корпусе и на 5.2M инструкций поставляемого библиотечного кода, который он никогда не видел59,693 из 59,760 инструкций корпуса и 4,585,336 из 5,237,448 поставляемых — точные, остальные отклонены по имени, 0 неверных в обоих случаях
ПроверщикЧитает расписание, сообщает о рискахСобственный вывод вендора должен проходить проверку начисто, а намеренно укороченный stall должен быть пойман0 ошибок на 1,323 парах «kernel вендора и уровень оптимизации», 0 пропущено на 233 сломанных
АудитТот же проверщик, на поставляемых библиотекахЗапустить его на производственных sm_120 kernel'ах, исключённых из всех таблиц, которые он читает0 ошибок на 2,762 kernel'ах и 10,218,030 зависимостях, все 2,762 полностью проанализированы
ПланировщикНазначает каждый управляющий бит с нуляОтбросить вендорские, вычислить новые, запустить обе версии на GPU на восьми входных наборах, сравнить выходные байты439 из 439 сравнимых kernel'ов байт-идентичны на всех трёх уровнях оптимизации

И та часть, о которой планировщик обычно умалчивает: во что обходится корректность. Расписания basalt тратят 1.05x вендорских тактов выдачи: медленнее на 111 из 1,323 пар «kernel и уровень оптимизации» и дешевле на 842, при этом каждый сравнимый kernel остаётся байт-идентичным на GPU.

Три прогона аудита против поставляемых NVIDIA библиотек sm_120: 6,593 ошибки на 250 kernel'ах, затем 940 после расширения до 2,762 kernel'ов и 10,218,030 зависимостей, затем 0 после тринадцати исправлений. Каждая ошибка была ошибкой самого basalt.

Третья строка — та, что изменила остальные три. Проверщик, откалиброванный на корпусе, не может ошибиться на этом корпусе: самый плотный зазор, который компилятор оставлял, является нижней границей — по построению, ровно для того кода, на котором он измерялся. В первый раз, когда этот проверщик увидел код откуда-то ещё, он сообщил о двадцати шести рисках на kernel в JPEG-декодере, который никогда не возвращал неверный пиксель, и все 6,593 были ошибками basalt. Их исправление привело его к нулю, но ноль на одной библиотеке тоже не был доказательством: расширение исключённого набора до трёх библиотек и 5.2 миллиона инструкций вернуло его прямо к 940 и обнаружило ещё пять ошибок модели в дополнение к первым восьми. Тринадцать исправлений, ни одно из которых не принадлежит NVIDIA, и требование, заново извлечённое из 24,311 поставляемых kernel'ов, установило предохраняющий предикат на 13 тактов на 229,567 наблюдениях — это число, которое инъекция отказов измерила на этой карте, намеренно ломая программу. См. находку 32.

Быть дешевле вендора — не повод для самодовольства. basalt планирует каждую зависимость с самым плотным зазором, который ptxas когда-либо оставлял для этой конкретной пары, а ptxas балансирует давление на регистры и память вместе с задержкой выдачи, тогда как этот инструмент оптимизирует одно число. Ему также не поверили сразу: когда коэффициент впервые опустился ниже 1.0, аппаратная круговая проверка сломалась, и число устояло только после того, как обнаруженный им баг был исправлен. Именно поэтому коэффициент закреплён с обеих сторон в тестовом наборе.

Средний столбец — в этом суть. Проверщик и планировщик, использующие одну модель задержек, согласуются друг с другом, оставаясь при этом оба неверными, поэтому ни один не является доказательством для другого; лишь кремний не имеет интереса в этом споре. Запуск планировщика на семи написанных вручную kernel'ах долгое время проходил семь из семи. Запуск на трёхстах выявил сорок один неверный, и каждое исправление в находках появилось в результате наблюдения за тем, как движется это число.

То же самое относится и к входным данным. Устаревшее чтение меняет ответ только тогда, когда устаревшее значение и свежее различаются, поэтому один набор байтов — это одна возможность заметить проблему, и прогон каждого kernel'а против второго, третьего и четвёртого набора сразу выявил предикат переноса, который модель операндов с самого начала читала как источник. Он пережил все проверки вплоть до этого момента, включая сам круговую проверку.

Та же дисциплина определяет, что ассемблеру разрешено делать, и стоит разделить два числа, которыми он располагает.

Ассемблер basalt воспроизводит 59,693 из 59,760 инструкций корпуса и 4,585,336 из 5,237,448 поставляемых библиотечных инструкций точно, отказываясь от остальных по имени, и ни одну из всех 5,297,208 не собрал в неверные байты.

Покрытие составляет 99.9% корпуса и 87.5% поставляемого библиотечного кода. Корректность — 100%, и именно это число закреплено тестом. Разрыв между ними — это инструкции, которые basalt отклоняет, каждая с указанием поля, которое он не смог разместить, потому что инструмент, который угадывал бы, достиг бы полного покрытия, порождая слова, которые дизассемблируются в правильный текст, но вычисляют что-то другое. Он никогда не выпустил ни одной такой инструкции — на 59,760 инструкциях корпуса и 5,237,448 поставляемых.

Он пришёл к этому только после восьми отдельных раундов уверенных ошибок:

  • запись номера регистра в кодировку непосредственного операнда (immediate),
  • трактовка униформного регистра как взаимозаменяемого с обычным,
  • сохранение цели перехода того kernel'а, из которого была извлечена форма,
  • помещение целого числа 15 в поле, которое хранит число с плавающей запятой половинной точности,
  • запись операнда в биты, которые оказались флагом повторного использования (reuse),
  • запись в поле, которое пробер атрибутировал лишь частично, из-за чего остальная часть продолжала кодировать старое значение,
  • разнесение номера регистра по биту, который выбирает, в каком регистровом файле он находится,
  • и чтение индекса регистрово-индексируемой константной загрузки как смещения.

Каждая из них порождала слово, которое ассемблируется, дизассемблируется обратно ровно в тот текст, из которого пришло, и вычисляет что-то другое. Это тот же сбой, для выявления которого существует остальная часть этого репозитория; поэтому все восемь теперь отклоняются с указанием причины, называющей, что поле действительно хранит, и поэтому число инструкций, ассемблирующихся в неверные байты, — это тест, закреплённый на нуле, а не число в таблице.

Девятая ошибка всплыла в первый раз, когда ассемблеру дали машинный код, который он не создавал, и она была другого рода. c[0x0][UR4] индексирует своё смещение регистром там, где записанная форма хранит число, и кодировщик поднял исключение, а не отказал. Падение на чужом входе хуже неверного вердикта, потому что вызывающая сторона не получает ни того, ни другого.

Какие GPU

NVIDIA Blackwell GeForce RTX 50 series Compute capability 12.0

sm_120 — это не номер модели. Это вычислительная способность (compute capability), общая для всей потребительской линейки Blackwell, поэтому кодировка инструкций, база данных, ассемблер и проверщик применимы к каждой карте в ней:

КартаCompute capabilityОхват
GeForce RTX 5090, 5090D12.0 (sm_120)да
GeForce RTX 5080, 5070 Ti, 507012.0 (sm_120)да
GeForce RTX 5060 Ti, 5060, 505012.0 (sm_120)да
Мобильные GeForce RTX 50 серии12.0 (sm_120)да
Рабочие станции RTX PRO Blackwell12.0 (sm_120)да
Датцентровые Blackwell (B100, B200, GB200)10.0 (sm_100)нет, другая кодировка

ptxas также нацеливается на sm_121 — другой чип в том же семействе. basalt никогда не запускался на нём и не заявляет о его поддержке. Что он может сказать — измерено: компилятор испускает байт-идентичный код, включая управляющие слова, для всех шести целей, которые он здесь предлагает, поэтому расписание, необходимое kernel'у, — это свойство архитектуры, а не конкретной детали (находка 28). Если бы это было не так, собственный компилятор NVIDIA испускал бы небезопасное расписание для одной из них.

Каждое число, измеренное на кремнии, получено с одной физической карты, названной точно, потому что «какой-то 5070 Ti» недостаточно для воспроизведения запуска:

КартаЧто именно это такое
ПлатаGigabyte GeForce RTX 5070 Ti EAGLE OC
Сообщено драйверомNVIDIA GeForce RTX 5070 Ti
Compute capability12.0
Потоковые мультипроцессоры70
Буст-частота2542 МГц
ИнструментарийCUDA 13.3.1, ptxas V13.3.73
Что требует GPU, а что — нет

Большей части basalt GPU вообще не нужен. Оба оракула, база данных инструкций, ассемблер и проверщик рисков работают с ptxas и nvdisasm как обычные подпроцессы, поэтому они выполняются в CI на машине без видеокарты. 237 из 252 тестов входят в эту группу, а 200 не требуют ни карты, ни бинарников NVIDIA.

GPU нужен ровно для трёх вещей, и это те три вещи, которые превращают правдоподобный инструмент в заслуживающий доверия:

Требуется картаЗачем
measure, probe-stallsЗамер времени инструкции и выяснение того, что действительно требует зависимость, путём её разрушения
scripts/roundtrip_corpus.pyПерепланирование каждого kernel'а корпуса и запуск обеих версий для сравнения выходных байтов
scripts/agreement_sweep.pyУкорачивание одной зависимости на kernel и вопрос к кремнию, был ли basalt прав

Фабричный разгон не влияет на измерения. Каждая задержка здесь указана в тактах — это свойство конвейера, а не частоты, а значение буста записывается рядом только для того, чтобы оставалась возможность сравнения по реальному времени. На что плата действительно влияет — так это на воспроизводимость, поэтому basalt measure --board записывает её.

Почему одна карта — это оговорка, а не сноска

Всё, что здесь измерено, было измерено на одной карте, и 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

root@kitploit:~
Это не скромность. Модель задержек, общая для проверяющего и планировщика, — это именно то место,
где прячется ошибочное число, поэтому вторая карта — самое полезное, что кто-либо может внести.

</details>

## Как это работает

Всё опирается на два оракула, оба из которых — штатные бинарники NVIDIA, запускаемые как внешние процессы. Никакие исходники, заголовки или библиотеки NVIDIA не используются и не распространяются.

| Оракул | Вызов | Что даёт |
| :--- | :--- | :--- |
| **Эталон** | `ptxas` → cubin → `nvdisasm -c -hex` | Кодировки, которые компилятор вендора реально выдаёт. Семантика вне сомнений. |
| **Зонд** | `nvdisasm -b SM120a` по сырым байтам | Декодирует слова, которые `ptxas` никогда не выдаст, превращая пространство кодировок в нечто, что можно искать, а не угадывать. |

Именно зонд-оракул имеет значение. Инструмент, ограниченный выводом компилятора, может лишь заново открыть то, что компилятор уже делает. Подача синтезированных 128-битных слов напрямую в декодер означает, что набор инструкций можно *измерить*.

Ни одному из оракулов не нужен 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

Восьмибитные поля регистров и 32-битное непосредственное значение, полученные экспериментальным путём, а не по предположению.

Управляющее слово

Инструкция sm_120 — 128-битная, из которых биты с 105 по 125 являются управляющим словом планирования: stall — биты 108:105, yield — бит 109, write_barrier — биты 112:110, read_barrier — биты 115:113, 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, имеют установленным соответствующий бит reuse.

Быстрый старт

Никакой установки CUDA и никакого GPU. Скрипт тулчейна загружает закреплённые распространяемые пакеты, примерно 45 МБ, без прав администратора, ничего не добавляется в ваш 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

root@kitploit:~
<!-- wp:heading {"level":3} -->
<h3 class="wp-block-heading">Использование</h3>
<!-- /wp:heading -->

**Интеграция со службами с ключами**

Библиотека включает интеграцию с функцией служб с ключами .NET. Это полезно в мультитенантных сценариях, когда один и тот же тип службы регистрируется несколько раз с разными ключами (например, для каждого тенанта). Когда служба зарегистрирована с ключом, контекст тенанта не будет использоваться для её разрешения. Вместо этого вы можете использовать ключ для разрешения конкретной регистрации.

- `WithKeyedServiceLifetimeScope` — метод расширения, который позволяет создать область службы на основе типа службы, зарегистрированного с ключом.
- `WithKeyedServiceResolution` — метод расширения, который позволяет разрешить службу с ключом, используя её ключ.

```csharp
services.AddSingleton<IService>(factory => ...);
// ...
using var scope = services.CreateScope();
var service = scope.ServiceProvider.GetRequiredService<IService>(); // resolved from the tenant context

Для служб, зарегистрированных с ключами, необходимо использовать WithKeyedServiceLifetimeScope.

root@kitploit:~
services.AddKeyedSingleton<IService>("key", factory => ...);
// ...
using var scope = services.WithKeyedServiceLifetimeScope(typeof(IService), "key");
var service = scope.ServiceProvider.GetRequiredService<IService>();

AddKeyedSingleton регистрирует синглтон с ключом, а WithKeyedServiceLifetimeScope создаёт область с использованием внутреннего TenantContext, связанного с ключом.

Мультитенантный резолвер

Библиотека включает встроенный MultiTenantResolver, который использует текущий HttpContext для определения тенанта.

[!IMPORTANT] Этот резолвер предназначен для приложений, использующих разделение баз данных по тенантам, например, когда каждый тенант использует собственную базу данных.

Пример использования MultiTenantResolver
root@kitploit:~
using TenantCore;
using Microsoft.AspNetCore.Http;

var builder = WebApplication.CreateBuilder(args);

builder.Services.AddHttpContextAccessor();
builder.Services.AddMultiTenant<Tenant>()
    .WithIdentityResolver<MultiTenantResolver>()
    .WithDatabaseResolver<DatabaseResolver>();

MultiTenantResolver отвечает за определение тенанта на основе HttpContext при создании новой области (например, HTTP-запроса), а DatabaseResolver отвечает за разрешение строки подключения.

Если тенант найден, строка подключения разрешается на основе этого тенанта.

Регистрация и настройка

Чтобы использовать библиотеку, необходимо зарегистрировать службы с помощью метода AddMultiTenant и настроить необходимые компоненты.

Пример базовой регистрации

root@kitploit:~
using TenantCore;

var builder = WebApplication.CreateBuilder(args);

builder.Services.AddMultiTenant<Tenant>()
    .WithIdentityResolver<CustomTenantResolver>()
    .WithDatabaseResolver<DatabaseResolver>();

var app = builder.Build();

AddMultiTenant регистрирует все необходимые службы: IHttpContextAccessor, ITenantContext, IMultiTenantConnection и другие.

[!NOTE] Более подробный пример см. в демонстрационном проекте.

Пользовательский резолвер тенанта

Вы можете создать пользовательский резолвер тенанта, реализовав интерфейс ITenantResolver.

Пример пользовательского резолвера

root@kitploit:~
public class CustomTenantResolver : ITenantResolver
{
    public ValueTask<string> ResolveAsync(object? context)
    {
        if (context is HttpContext httpContext)
        {
            var tenant = httpContext.Request.Headers["X-Tenant-ID"].ToString();
            return ValueTask.FromResult(tenant);
        }

        return ValueTask.FromResult(string.Empty);
    }
}

Этот резолвер ищет заголовок X-Tenant-ID в HTTP-запросе и использует его значение в качестве идентификатора тенанта.

Фабрика мультитенантных подключений

Шаблон ConnectionFactory используется для создания подключения к базе данных для конкретного тенанта с учётом предоставленной строки подключения.

Библиотека включает MultiTenantConnectionFactory, которая автоматически используется при работе с IMultiTenantConnection и выполняет следующие задачи:```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.

root@kitploit:~
### Или как пакет

`pip install basalt-sass` устанавливает CLI, библиотеку и все три таблицы измерений,
без зависимостей времени выполнения. Используйте это, если на машине уже есть CUDA; описанный выше checkout
подойдёт, если её нет, или если вы хотите воспроизвести измерения.```bash
pip install basalt-sass
basalt doctor
basalt verify kernel.cubin

Where it looks for ptxas and nvdisasm

basalt запускает обе как внешние процессы и не распространяет ни одну из них, поэтому ему приходится находить их копию. Он использует первую найденную, и подойдёт любая установка CUDA 13: ничего не должно быть привязано к конкретному распространяемому пакету.

basalt doctor выводит, какой именно вариант он выбрал, и завершается с ненулевым кодом, если не может найти ни один, так что это работает как предусловие для шага сборки, а не просто как справочная информация.

Для запросов к базе данных инструкций вообще не нужен тулчейн, потому что база измеряется заранее и поставляется внутри пакета:```bash basalt isa --stats basalt isa --opcode QMMA

root@kitploit:~
Пересоздайте базу данных инструкций с нуля или выполните запрос к закоммиченной:```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

root@kitploit:~
[No input text provided to translate.]```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

root@kitploit:~
<details>
<summary><b>Каждая команда и для каких нужна карта</b></summary>

<br/>

| Команда | Что делает | Нужна GPU |
| :--- | :--- | :--- |
| `doctor` | Проверяет оба оракула по полному циклу | нет |
| `build-isa` | Собирает данные и зондирует, записывает базу инструкций | нет |
| `isa` | Запрашивает форму, опкод или покрытие | нет |
| `validate-isa` | Доказывает, что измеренные поля можно записывать насквозь | нет |
| `mine-stalls` | Изучает требования для каждой пары на основе того, что планирует компилятор | нет |
| `verify` | Проверяет управляющие биты cubin на наличие конфликтов данных | нет |
| `schedule` | Назначает управляющие биты cubin с нуля и проверяет результат | нет |
| `assemble` | Кодирует текст SASS или целый cubin и читает его обратно для проверки | нет |
| `measure` | Замеряет задержку инструкций на реальном кремнии | **да** |
| `probe-stalls` | Находит требуемый stall, намеренно ломая программы | **да** |

</details>

Всё, что умеет CLI, доступно для импорта, а библиотечный интерфейс с запускаемыми примерами
находится в [`docs/API.md`](https://github.com/sunnypatell/basalt/blob/main/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

The input chunk appears to be empty. No translatable content was provided after "INPUT:". Please supply the chunk text so I can translate it into Russian.```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

root@kitploit:~
## Измерено, а не предположено

Числа здесь выводятся инструментарием и пересоздаются из чистого 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.

<img src="https://raw.githubusercontent.com/sunnypatell/basalt/main/docs/assets/chart-latency.svg" alt="Три задержки, которые basalt предполагал до измерения их против того, что сообщил кремний: сложение fp64 предполагалось 48, а измерено 64, POPC предполагался 4, а измерен 18, и полный цикл конверсии I2FP плюс F2I предполагался 12, а измерен 24.">

Предполагаемая модель задержек — это не малое приближение измеренной; в этом весь аргумент в пользу измерения.

**И stall со значением ноль — это не ноль циклов.** Это отдельная безопасная кодировка, которая ожидает незавершённые результаты, и обходится примерно в 37 циклов там, где запланированная инструкция стоит 4. Вот почему `ptxas -O0` генерирует полностью обнулённое управляющее слово, и код при этом по-прежнему вычисляет корректно — примерно в девять раз медленнее.

| `stall` | циклов/инструкция | результат |
| ---: | ---: | :--- |
| **0** | **36.85** | **корректно** |
| 1 | 4.88 | неверно |
| 2 | 4.88 | неверно |
| 3 | 5.88 | неверно |
| 4 | 6.88 | корректно |

**Он согласуется с компилятором производителя по каждому ядру в корпусе.** Каждое ядро, которое `ptxas` собирает из корпуса, проверяется против его собственного планирования на каждом уровне оптимизации, который выполняет планирование: 30 421 зависимость, ноль ошибок. Этот прогон выполняется в CI при каждом пуше, и каждая ошибка моделирования, которую допустил этот проект, была поймана именно им, а не рассуждениями.

**Вердикты совпадают с кремнием.** Для каждого кодируемого stall на зависимом производителе статический ответ basalt и то, что реально вычисляет железо, согласуются, включая случай нуля. Это закреплено тестом, а не утверждается здесь. Полные доказательства, включая три независимых метода для требуемого stall и исправления, сделанные по ходу, — в [findings](https://github.com/sunnypatell/basalt/blob/main/docs/FINDINGS.md).

**И когда он говорит, что расписание небезопасно, кремний соглашается.** Возьмите собственное рабочее расписание производителя для 233 ядер, укоротите в каждом по одной реальной зависимости и сравните вердикт basalt с тем, что вычисляет GPU: 79, которые он назвал сломанными, действительно были сломаны, и **ничего из того, что он назвал безопасным, не вычислило неверный ответ**. Это число начиналось с 34 пропущенных, а не с нуля, и в [findings](https://github.com/sunnypatell/basalt/blob/main/docs/FINDINGS.md) сказано, в чём была причина и во что обошлось её исправление в ложных срабатываниях, потому что прогон, сообщающий только итоговую цифру, стоит меньше, чем тот, который сообщает свою первую.

## Он тоже умеет назначать управляющие биты

Верификатор отвечает на вопрос, безопасно ли расписание. Планировщик отвечает на вопрос, каким было бы безопасное расписание, исходя из тех же измерений: он отбрасывает каждый управляющий бит, созданный `ptxas`, вычисляет свои, возвращает результат верификатору, а затем запускает его на GPU рядом с версией того же ядра от производителя.

При прогоне по всему корпусу на карте, на каждом уровне оптимизации, который создаёт расписание, все 439 сопоставимых ядер дают побайтово идентичные результаты расписанию производителя, причём управляющие биты basalt вычислил сам. Те 2, что исключены, читают часы и идентификатор сетки, так что не согласуются даже сами с собой, и в [findings](https://github.com/sunnypatell/basalt/blob/main/docs/FINDINGS.md) об этом сказано прямо, а не вписано в проценты.

Этот контроль — причина, по которой можно доверять всему остальному. Проверяющий и планировщик читают одну и ту же модель задержек, поэтому ошибочная запись в ней удовлетворяет обоих одновременно, и они согласуются друг с другом, оставаясь оба неправыми. Только у кремния нет интереса в этом споре. Долгое время прогон планировщика по семи написанным вручную ядрам проходил семь из семи; прогон по трёмстам нашёл сорок одно неправильное ядро, и каждое последующее исправление модели родилось из наблюдения за тем, как движется это число.

Именно из этого цикла и появились настоящие баги. Stall, проведённый вне окна между производителем и его потребителем, ничего не стоит, и трата его там завершает поиск программой, которая всё ещё слишком коротка. Stall, привязанный к безопасной кодировке, перезаписывался более поздним проходом, подменяя гарантию маленьким числом. Операнды fp64 занимают пары регистров, и в мнемонике ничто об этом не говорит, поэтому половина каждой зависимости fp64 была невидима и для проверяющего, и для планировщика. Предикат, используемый как guard инструкции, требует тринадцать циклов там, где тот же предикат, читаемый как данные, требует пять, потому что guard должен быть разрешён до того, как инструкция вообще будет выдана. А ожидание на scoreboard не улаживает зависимость полностью: производитель всё ещё должен небольшой собственный stall — два цикла для сложения fp64, — и на один цикл меньше — это тихая ошибка. Ни одна из этих проблем не была найдена рассуждениями; каждая была найдена запуском вывода и получением неверного числа.

> [!NOTE]
> **1.0, и конкретно о том, что это значит.** Что сделано: оба оракула, база данных инструкций с доказанно записываемыми полями, проверяющий опасности поверх реального графа потока управления, задержка, измеренная на одном SKU тремя независимыми методами, планировщик, который прогоняет каждое сопоставимое ядро корпуса через железо туда и обратно с побайтовым совпадением, и аудит 2 762 поставляемых ядер, исключённых из всех таблиц, которые читает проверяющий. Что не сделано: 12 ядер корпуса, невыполнимых по построению, и 2, чей вывод производителя не детерминирован, — все перечислены в findings; десять опкодов по-прежнему несут предполагаемую задержку вместо измеренной, и ни один из них никогда не был производителем ни в одном из двух корпусов кода; и измерен только один GPU, а [finding 28](https://github.com/sunnypatell/basalt/blob/main/docs/FINDINGS.md) показывает, что это значит меньше, чем звучит. Где что-то выведено, а не измерено, инструментарий говорит об этом, а не округляет до факта. См. [roadmap](https://github.com/sunnypatell/basalt/blob/main/docs/ROADMAP.md) и [method](https://github.com/sunnypatell/basalt/blob/main/docs/METHOD.md).

## Структура репозитория

<details>
<summary><b>Где что находится</b></summary>

<br/>```
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

Позиция clean-room

basalt — это независимая работа в стиле clean-room, созданная для обеспечения интероперабельности. Он не содержит исходного кода NVIDIA, заголовков, библиотек или документации и ничего не распространяет. Он наблюдает за поведением публично распространяемых исполняемых файлов и фиксирует его — именно на этом основании такого рода работы ведутся уже более десяти лет.

NVIDIA, CUDA и Blackwell являются товарными знаками NVIDIA Corporation. Этот проект не связан с NVIDIA, не одобрен ею и не спонсируется ею.

Лицензировано под Apache-2.0. Apache выбран намеренно, а не что-то ограничительное: инструмент корректности, на котором никому не разрешено строить, — это инструмент корректности, который никто не запускает, а патентная оговорка важна для работы, настолько близкой к аппаратному обеспечению.

Участие в разработке

Наиболее ценный вклад — это кодировка, которую basalt определяет неверно. См. CONTRIBUTING.md и шаблон ISA gap, который собирает достаточно информации для воспроизведения без вашей машины.

SUPPORT.md описывает, куда обращаться с вопросами, GOVERNANCE.md — что должно пройти изменение, RELEASING.md — как готовится и проверяется релиз, а SECURITY.md — как сообщить о проблеме конфиденциально.

Цитирование basalt

Если basalt повлиял на статью, инструмент, модель или отчёт об ошибке, пожалуйста, процитируйте его. GitHub нативно читает 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} }

root@kitploit:~
Цитируйте **концептуальный DOI**, [`10.5281/zenodo.22072811`](https://doi.org/10.5281/zenodo.22072811), а не
версионный DOI или этот URL. Он ведёт на самый свежий релиз, поэтому остаётся корректным без
необходимости когда-либо редактировать его снова. `CITATION.cff` содержит его, так что он уже
есть в обеих формах выше.

Если вы используете измеренные таблицы (`src/basalt/data/`) или воспроизводите рисунок, цитируйте релиз,
из которого они взяты, а не `main`: числа перегенерируются скриптом `scripts/verify_all.py` в
конкретном коммите, и именно тег делает это воспроизводимым.

**Атрибуция — это условие лицензии, а не любезность.** Пункт 4 Apache-2.0 требует, чтобы
[`LICENSE`](https://github.com/sunnypatell/basalt/blob/main/LICENSE) и [`NOTICE`](https://github.com/sunnypatell/basalt/blob/main/NOTICE) сопровождали любое распространение или производную работу,
а `NOTICE` содержит сведения об авторстве и заявление о «чистой комнате». Форки, вендорные копии и
переупакованные wheel-пакеты сохраняют оба файла.

## Автор

**Sunny Patel** &middot; [sunnypatel.net](https://www.sunnypatel.net) &middot; [github.com/sunnypatell](https://github.com/sunnypatell)
Скачать инструмент
ПолеБитыЗначение
stall108:105Тактов ожидания перед выдачей следующей инструкции
yield109Подсказка, что планировщик варпов может переключить варпы
write_barrier112:110Скорборд для сигнализации при записи результата (7 = нет)
read_barrier115:113Скорборд для сигнализации при чтении операнда (7 = нет)
wait_mask121:116Скорборды, которые должны быть очищены перед выдачей
reuse125:122Флаги кэша повторного использования операндов, по одному на слот источника
МетодОписание
RegisterTenantContextРегистрирует инфраструктуру контекста тенанта
WithTenantContextСвязывает контекст тенанта с областью, схемой базы данных
WithKeyedServiceLifetimeScopeСоздаёт область с разрешённым типом службы с ключом
WithKeyedServiceResolutionРазрешает службу с ключом, используя ключ
GetTenantContextПолучает текущий контекст тенанта
GetMultiTenantConnectionПолучает мультитенантное подключение для текущего тенанта
ResetСбрасывает состояние контекста тенанта
OrderWhere
1--cuda-bin, передаётся через командную строку
2BASALT_CUDA_BIN, каталог, содержащий оба исполняемых файла
3CUDA_PATH, CUDA_HOME или CUDA_ROOT, каждый плюс /bin
4ptxas в вашем PATH
5third_party/cuda/<version>/bin в рабочей копии, сначала новейшая