Я написал транслятор CUDA/PTX в LLVM IR: он берёт PTX от настоящего nvcc и собирает его обычным clang. Восемь ядер уже исполнены на живой GeForce MX450 и побитово совпали с хост‑эталоном. А главное — как настоящий nvcc и видеокарта сломали мои зелёные тесты. Читать далее
Уровень сложностиСредний
Время на прочтение5 мин
Охват и читатели4.5K
Кейс
1. Зачем я сделал свой транслятор, когда есть другие проекты?TL;DR: Проблема переносимости CUDA для меня оказалась не столько в самих арифметических операциях, сколько в огромном количестве инфраструктуры вокруг них. Поэтому вместо попытки повторять CUDA runtime я решил перехватить программу раньше — на уровне PTX. Я написал ROUGE‑V — открытый AOT‑компилятор, разработанный по принципу clean‑room: берёт PTX, сгенерированный официальным
nvcc13.4, транслирует его в LLVM IR, дальше собирает обычныйclang. Исполняется на x86-64 и на живой GeForce MX450 (побитово с хост‑эталоном); AMD и RISC‑V — пока только сборка, без вранья про «любое железо».Мой GitHub: github.com/MrModelOS/ROUGE‑V (автор: MrModelOS).
Покрытие:
ctest— 21/21; 8 ядер (7 канонических + 1 настоящее отnvcc) исполнены на GeForce MX450.
Один из очевидных способов бороться с vendor lock‑in NVIDIA — реимплементировать runtime или перехватывать API на лету. Это вечная гонка за чужим интерфейсом: вендор поменял поведение — ты догоняешь.
Я пошёл по пути Clean‑Room разработки.
Компилятор nvcc генерирует не машинный код, а PTX — текстовый промежуточный ассемблер с публично опубликованной спецификацией (выхлоп нашего nvcc 13.4 — PTX ISA 9.4). Я выбрал его как точку перехвата:
Беру стандартный .cu‑код и компилирую его через nvcc в .ptx.
Мой транслятор ptx2ir парсит PTX и строит эквивалентное представление в LLVM IR.
Стандартный clang / LLVM собирает конечный бинарник под целевую архитектуру.
Никаких проприетарных бинарников и заголовков NVIDIA я не копирую и не реверсирую — правило clean‑room закреплено в CONTRIBUTING.md.
Пайплайн у меня выглядит так:
CUDA / .cu
│
▼
nvcc --ptx
│
▼
PTX
│
▼
ptx2ir
│
▼
LLVM IR
│
▼
LLVM / clang
│
▼
native code
NVPTX → GeForce MX450 → bit-exact checkЧестная легенда: AMD (amdgcn) и RISC‑V (rv64gcv) — кросс‑компиляция, исполнения на железе у меня не было. Исполняется — хост и NVIDIA.
На практике весь процесс занимает 4 команды (проверено на живом стенде):
# 1. Скомпилировал .cu в PTX вендорным nvcc 13.4
nvcc -arch=sm_75 -ptx sq.cu -o sq.ptx
# 2. Транслировал PTX в LLVM IR своим ptx2ir
ptx2ir --target nvptx64-nvidia-cuda sq.ptx sq.ll
# 3. Собрал настоящим бэкендом clang в PTX ядра
clang --target=nvptx64-nvidia-cuda -march=sm_75 -S sq.ll -o sq_gpu.ptx
# 4. Запустил на видеокарте через CUDA Driver API
./gosq
# Output: OK: 1024 квадрата посчитаны на GeForce MX450
Что внутри sq_gpu.ptx — настоящее ядро, а не заглушка:
.visible .entry _Z2sqPKfPfj(
mul.lo.s32 %r7, %r6, %r5;
ld.global.b32 %r12, [%rd9];
mul.rn.f32 %r14, %r13, %r13;
st.global.b32 [%rd14], %r15;3. Грабли: как я ловил тихую порчу данныхручной PTX → всё зелёное → настоящий nvcc → парсер ломается → чиню грамматику → реальная GPU → вылазят address-space баги → исправляю pipeline
Написать парсер для add/mul — просто. Сложности начались на выхлопе свежего nvcc.
Камень 1: однострочные сигнатуры. Современный nvcc пишет .visible .entry _Z...(…) в одну строку, а мой парсер ждал скобку на отдельной строке — и тело ядра оставалось пустым. Плюс 37 мелких пробелов в грамматике: and/or/xor, сдвиги, selp, div/rem, mad.lo.s32, bfi/bfe, f16x2, варп‑shfl. Базовая арифметика, которую компилятор генерирует в каждом ядре.
Камень 2: инверсия предикатов. Запись @!%p1 bra LABEL означает «перейти, если предикат ложен» — флаг ! обязан явно инвертироваться в IR. У меня это закрыто на уровне чтения предикатных операндов: ведущий ! превращается в xor i1 …, true, и то же правило действует в интерпретаторе. Тихой порчи здесь нет именно потому, что инверсия — часть контракта чтения операнда, а не «надежда, что бэкенд угадает».
Камень 3: setp и div. setp.lt.f32 у меня сравнивал биты целочисленным icmp вместо fcmp, а div.s32 эмитил mul. Оба не падали — молча считали неправильно. Нашлись только сверкой с настоящим входом от nvcc.
Мораль, которую я вынес: тест на собственных данных доказывает, что код работает на твоих данных. Нужен чужой вход — настоящий PTX из CI (
compiler_nvcc_ptx).
А потом видеокарта нашла ещё два бага, невидимых на хосте: вывод матрицы писался в shared вместо global (метка регистра переживала перезапись — теперь пространство берётся из суффикса инструкции), а адрес device‑глобала читался как загрузка из него. На плоской хост‑памяти оба молчали.
4. Мои результаты и тестыctest — 21/21, ноль ворнингов. Из них:
7 канонических ядер (vadd, block_reduce, atomic_reduce, fp16_reduce, gemm_tile, shfl_reduce, atom_cas) исполнены на GeForce MX450 и сошлись побитово с хост‑эталоном;
настоящее ядро от nvcc (манглированное имя, device-глобал, syncthreads) — насквозь nvcc → ptx2ir → GPU. Честная оговорка: в его исходнике гонка (редукция без syncthreads), вендорная сборка гонится так же — эталон принимает доказанный интервал, а не одно число;
shfl.sync.bfly проверен отдельным NVPTX‑тестом (shfl_reduce) на реальной GPU — через настоящий NVVM‑интринсик; остальные warp‑коллективы покрытыми тестами не считаю: vote / activemask / предикатный shfl — явный отказ, а не тихий фолбэк;
без карты sm_75+ GPU‑тесты говорят честный SKIP, а не «успех».
Производительности я не мерял — только бит‑идентичность. Кто обещает скорость без замеров, тот врёт; я не обещаю.
5. Что дальше?SIMT → RVV векторизация: сворачивать 32 потока в векторные линии (MLIR‑контур уже анализирует паттерны, полноценной векторизации пока нет);
mma и тензорные инструкции — пока осознанный отказ: «примерно правильно» опаснее, чем никак;
больше настоящих CUDA‑бинарников на входе вместо синтетики;
публичные бенчмарки — но сначала повторяемый стенд.
Без сборки, два бинарника (кроме системного libc зависимостей нет):
curl -fsSL https://github.com/MrModelOS/ROUGE-V/releases/download/v0.1/rouge-v-0.1-linux-x86_64.tar.gz | tar xz -C ~/.local
export PATH="$HOME/.local/bin:$PATH"
ptx2ir kernel.ptx kernel.ll
Из исходников:
git clone https://github.com/MrModelOS/ROUGE-V.git && cd ROUGE-V
cmake -B build -G Ninja -DCMAKE_BUILD_TYPE=Release && cmake --build build
ctest --test-dir build --output-on-failure
Архитектура — docs/06-compiler-architecture.md. Лицензия — Apache 2.0 WITH LLVM‑exception.
Я не копирую и не реверс‑инжинирю проприетарные бинарники и заголовки NVIDIA.
PTX, который я перевожу, выпускает nvcc пользователя — из его собственной лицензии, по открытой спецификации PTX ISA.
ROUGE‑V не связан с NVIDIA и не одобрен ими. Торговые марки — только чтобы описать совместимость.
Если возьмёте PTX от реального nvcc и мой транслятор скажет отказ — расскажите, что именно: это самый полезный баг‑репорт для проекта.
| # | Наименование новости | Тональность | Информативность | Дата публикации |
|---|---|---|---|---|
| 1 | Локальные нейросети без Python, CUDA и терминала: делаю программу, в которой всё настраивается само | 0 | 7.01 | 27-09-2026 |
| 2 | Я прогнал топ VRAM-калькуляторов для LLM — все ошибаются в одном и том же | 0 | 10.17 | 08-09-2026 |
| 3 | Running vLLM on NVIDIA DGX Spark with Docker: Zero-Compilation Guide for CUDA 13 | 0 | 8.69 | 21-06-2026 |
| 4 | В драйверах Nvidia десятки ошибок существуют годами Ресурс PC Games ... | 0 | 13.95 | 02-10-2026 |
| 5 | RuntimeNodes: как мы, ML‑щики, стали писать рантайм | 0 | 7.74 | 24-09-2026 |
| 6 | Купил мини‑ПК с приставкой «AI» ради локальной LLM. Что может NPU на самом деле | 0 | 10.63 | 01-10-2026 |
| 7 | Моя собственная Gated RNN: как работает? (и бенчмарки, конечно же) | 0 | 5.62 | 26-09-2026 |
| 8 | LLM уверенно называет шахматные ходы, которых на доске нет. Как я проверяю каждый ее ответ кодом | 0 | 5.87 | 26-09-2026 |
| 9 | Qualcomm готовит официальные GPU-драйверы для запуска ПК-игр на Android Сейчас ... | 0 | 13.14 | 28-09-2026 |
| 10 | NOCL Making Progress As Mesa Gallium/OpenCL Atop NVIDIA CUDA | 0 | 6.11 | 29-09-2026 |