Что удалось сделать
TL;DR: за примерно месяц создан полностью совместимый с OpenGL ES 3.0 GPU-драйвер для M4 Mac Mini и MacBook Neo — работа, которая обычно требует лет. Chrome и Firefox успешно запускают WebGL с рабочей композицией:

Главное — драйвер работает достаточно быстро, чтобы запускать Minecraft на 212 fps:

Создание драйвера потребовало реверс-инжиниринга сложнейшего firmware ABI GPU (Apple обозначает его как AGX) и компонентов пользовательского пространства. Всё проделано прозрачно с использованием проверенных методов так называемого чистого помещения. Код ещё не готов для конечных пользователей, но разработчики стремятся это исправить как можно скорее.
Как это получилось
Ранее Cody создал гипервизор для реверс-инжиниринга macOS. Теперь целью стало сделать с ним что-нибудь полезное — естественным выбором была разработка GPU-драйвера. GPU — по сути обязателен для современной системы: без него всё приходится рендерить на CPU, что медленнее в разы и энергоёффективнее. Цель: реализовать совместимые драйверы OpenGL (и вскоре Vulkan) для M4 Mac Mini и MacBook Neo.
Обычно разработка GPU-драйвера занимает годы; цель ставилась — за дни. Дни оказались чересчур оптимистичны, но недели — это всё равно огромный прогресс. За эти недели удалось:
- Провести реверс-инжиниринг пользовательского пространства M4, A18 Pro и (в основном) M5, используя только live probing — обнаружены аппаратные возможности и инструкции, которые не испускает драйвер Apple;
- Построить полностью рабочий драйвер пользовательского пространства с новым компилятором IR/shader, конструктором потока команд и множеством других компонентов;
- С нуля произвести реверс-инжиниринг полного AGX firmware ABI, используя traces из гипервизора;
- Реализовать полный Linux kernel driver для этого firmware ABI.
На протяжении этого процесса не анализировались никакие бинарники Apple, только hardware traces (из гипервизора) и собственные шейдеры. При реверс-инжиниринге пользовательского пространства графики требуемые Apple blobs обрабатывались как непрозрачные объекты. Друг написал документацию к этим blob'ам, позволившую реализовать чистый вариант (в основном подбором, пока не сработало). Все эксперименты выложены открыто, чтобы каждый мог проверить источники работы (см. agx-re репозитории в разделе Deliverables).
Пост разделён на две части — пользовательское и kernel пространство. Это отражает разделение во всех современных GPU-драйверах: kernel отвечает за связь с firmware, выделение буферов и управление планированием, а содержимое этих буферов и что планируется — непрозрачно. Пользовательское пространство отвечает за понимание того, как работает GPU, и заполнение буферов информацией.
Kernel пространство
На Apple Silicon kernel driver не общается напрямую с железом. Вместо этого он взаимодействует с GPU firmware, работающим на пользовательской RTOS под названием RTKit. Значит, первый шаг в разработке kernel driver — не общение с железом, а разбор firmware ABI.
Firmware ABI стал самой неприятной частью проекта, потому что вместо разумного подхода с хорошо обдуманным ABI Apple сделала то, что регулярный kernel driver разрезали пополам — одну половину положили в AGX и назвали firmware, а другую половину kernel driver оставили для связи через shared structs в памяти. Во многие из этих структур firmware вставляет свои поля (которые никогда не трогать и которые только реверс-инжинерить), перемешивая их с полями, контролируемыми хостом. Чтобы представить сложность ABI, вот как выглядит граф shared memory на M1/M2:

Asahi Lina знаменито разобралась со всем этим через изнурительные 12-часовые дни работы, создав kernel driver для M1/M2 — потрясающий технический подвиг. К сожалению, A18 Pro firmware ABI (начинался реверс-инжиниринг на MacBook Neo, позже переключился на M4 Mac Mini) значительно сложнее уже и так очень сложного M1 firmware ABI:

Что за F@!#, Apple. Заметим на A18:
- В 1.5 раза больше структур;
- в два раза больше указателей;
- значительно более сложный процесс отправки работы.
Много чего ещё усложняет реверс-инжиниринг. Была какая-то документация по firmware ABI, но она сильно неполная и честно сказать — не очень полезная.
Подход был простым и основан на методе, успешно использованном для реверс-инжиниринга M1/M2: смотреть, что делает macOS, повторить, потом попробовать сделать самому — возможное благодаря гипервизору.
Когда подход описывался LLM, оно буквально понимало слово replay: сначала дождалось первого видимого firmware события (называются «kicks»), потом сохранило копию всего GPU memory state. После перезагрузки скопировало сохранённое состояние прямо в память хоста, выполнило kick, увидело, как изменились страницы выходных данных. Потом пыталось переконструировать эти объекты в коде, следуя всем указателям и разбираясь с содержимым. В последовательных экспериментах Codex снижал количество скопированных страниц, пока не осталось ничего из replay-состояния и всё не собиралось с нуля. Удивительно, но Codex хорошо ощущал, когда стоит побольше поковыряться с железом, а когда просто запустить гипервизор и захватить состояние.
Было три крупных проблемы, все вызванные неспособностью получить чистый захват host work:
Первая — render work, отправленная после того как GPU firmware запустилась. Можно было prestage work до старта firmware, запустить GPU и работа выполнялась как ожидалось. Но как только firmware запустилась, любая отправленная работа просто ACKалась и заканчивалась без каких-либо действий. После запуска firmware захват состояния становится намного сложнее — всё становится динамичным, firmware становится stateful объектом с состоянием, которое не легко replay'ить.
Пришлось вмешаться и исследовать процесс Codex. Оказалось, что оно пыталось replay'ить захват очень поздно в жизненном цикле AGX, когда было уже много предыдущих событий. Когда подсказали выбрать захват намного раньше в AGX жизни — самый первый захват после firmware старта — Codex почти сразу обнаружило проблему (не хватало одного byte дескриптора). Это заняло несколько дней.
Вторая, и единственная серьёзная блокирующая, проблема была с compute. AGX в целом поддерживает две вида работы: compute и render. В обычном GUI пути compute work планируется только после того как выполнено значительное количество render work. Поэтому долго собирались чистый захват compute workload'а, и когда Codex наконец получило — это было 336 МБ и невозможно было replay'ить (долго пыталось). Оно также пыталось сконструировать объекты самостоятельно, посмотрев захват, потратив на это больше недели, но в итоге не преуспело. Было просто слишком много шума для разбора. Это усложнялось моими ошибками — после того как render заработал, ожидалось что compute будет проще (firmware ABI для compute действительно проще, так что это было правильно), но потерялось смирение и казалось что это легко сделать за несколько часов. Потому не подготовлен правильно task для LLM.
Решение пришло из другой Codex сессии. По сути:
- Отключить GUI загрузкой в single-user mode; это означает что не будет render work.
- Установить LaunchDaemon чтобы запуститься в самую раннюю возможную точку, в момент когда Metal (proprietary graphics framework Apple) стал доступен.
- Запустить крохотную Metal программу которую подготовили.
- Захватить и replay'ить этот крохотный, чистый compute trace.
Trace успешно захватилась. За несколько часов Codex деконструировало его, и за несколько дней compute заработал. Но почему оригинальный compute codebase не работал… Codex понятия не имеет. Рабочий и сломанный выглядят очень похожо.
Задним числом, эта стратегия должна была быть с самого начала — самый маленький возможный захват, запуск в single-user mode чтобы не возмущать результаты. Здесь выучился на ошибках для финальной проблемы:
Partial renders оказались одной из самых трудных вещей для разбора. Они происходят когда Tiled Vertex Buffer (TVB) не достаточно большой чтобы хранить текущую геометрию (то есть просто слишком много треугольников для рисования). В этих случаях есть два варианта, и драйвер должен поддерживать оба: либо увеличить размер TVB, либо выполнить partial render — то есть отрендерить часть геометрии, перезагрузить буфер с остальными треугольниками и завершить partial render. Эти partial renders оказались очень, очень капризны, даже больше чем остальная работа, потому что фактически это означает добавление save и resume в GPU драйвер.
Workflow обнаруженный ранее здесь очень помог. Codex смог replay'ить одну partial render транзакцию, и потом изменил Metal shader чтобы выполнить множество partial renders (это довольно просто просто забив одну плитку тысячами треугольников пока не спровоцируется partial render) и потом научился как их replay'ить. Как только Codex получил успешный replay, это было просто вопрос времени пока не научилось строить его самостоятельно.
Построение Kernel Driver
Переход от Python prototype driver к полнофункциональному Linux driver занял три дня, и один из этих дней почти полностью потрачен впустую потому что Codex по какой-то причине выбрало tackle partial renders первым (абсолютно самая трудная задача) вместо compute (самая лёгкая задача). Как только сказали делать compute первым — всё пошло гладко.
На высоком уровне, весь процесс был в основном:
- Переписать существующий
drm-shimна Rust следуя точно такому же паттерну; это даёт синхронный Rust driver. - Переписать frontend чтобы быть асинхронным; фактический GPU submission остаётся синхронным.
- Рефакторить GPU submissions чтобы быть асинхронными и вместо polling'а слушать firmware события и связывать работу с fence.
- Реализовать какие-нибудь низко висящие оптимизации, такие как batched work submission.
Это все довольно рутинная инженерная работа в которой LLM'ы определённо способны.
Единственное примечательное что заметили — Codex агрессивно использовал гипервизор чтобы debug'ить почему код не работает, включая захват полного address space и сравнение с известно хорошими samples. Такой систематичный debugging это почему Codex далеко мой любимый coding agent.
User Space
A18 Pro user space очень отличается от M1/M2; имеет новые форматы дескрипторов, новый ISA и кучу других новых вещей. Помимо того что это tile based deferred renderer разработанный чтобы работать с Metal, это просто другой GPU.
Хорошая новость что user-space RE имеет очень чётко определённый процесс. Просто напишите маленькую Metal программу, скомпилируйте её, запустите, посмотрите что изменилось, потом разберите и начните комкать биты пока не разберётесь что они делают. Если думаете что это звучит как скучная, повторяющаяся, рутинная работа в которой LLM'ы очень хороши — вы правы.
RE работа происходила в два этапа. На первом этапе Claude посмотрел на каждую возможную Metal программу которую смог найти и попыталась построить disassembler, assembler и понять формат всех других descriptors/command streams/etc требуемых для GPU драйвера. Claude перечислила всё, включая то что Linux не может использовать (как tessellation) для полноты. Это было успешно, но только потому что Claude смогла разассемблировать и потом переассемблировать программы не означает что она знала как построить одну сама. Когда пытались закрыть этот gap, то есть понять каждую инструкцию достаточно чтобы действительно уметь компилировать наши собственные произвольные программы, Claude ужасно справлялась и по сути не было никакого прогресса.
В этот момент Niklas закончил свой drm-shim для M4 Mac Mini и присоединился для второй фазы user-space RE. Было два разных подхода к финализации user-space драйвера:
Мой подход был приоритизировать hardware RE и сфокусироваться просто на разборе как работают hardware и все инструкции. Потом напишу спецификацию и дам LLM реализовать, надеясь закончить с полной OpenGL и Vulkan совместимостью. Это означает что большинство времени LLM потрачено на написание экспериментов на hardware, не на фактическую реализацию Mesa кода. Идея была что как только разберусь в hardware всё остальное следует.
Niklas взял другой подход, который я бы описал как «Mesa first». Фактически пытался построить Mesa first и только делал RE для того чтобы построить какую-нибудь функциональность. Его время было разделено между построением и тестированием Mesa и выполнением RE.
Оказалось что Niklas сделал значительно быстрейший прогресс чем я, потому что мой agent тратил много времени на небольшие неважные задачи во имя полноты. В контрасте его agent был обоснован нуждой на фактическом построении Mesa, поэтому использовал время и ресурсы много более эффективно. Он прогрессировал настолько быстрей чем я что в итоге просто пытался поддерживать его работу исследуя любое поведение которое ещё не понимал.
Это было одно большое ограничение Codex которое заметили. Лучшее слово которое можно думать чтобы описать это — «pedantic» — это чрезвычайно тщательно всё время, что может быть большой выгодой в некоторых сценариях, но другие времена это застревает в сорняках на каком-нибудь случайном tangent в ущерб общей цели.
Во время RE нашли поведение которое поддерживалось hardware но не поддерживалось Metal; это найдено прямым механическим изменением битов разных инструкций и экстраполяцией чего может существовать на основе чего известно существовало, точно как Alyssa Rosenzweig сделала когда REила M1/M2. Это включало:
- Native single-instruction 64-bit add
- Anisotropy до 128x (Metal caps на 16x)
- Новый режим matrix unit
- 7-bit immediate support для
uniform_mov
Mesa Development
Есть несколько вещей что массивно работают в нашу пользу когда построение user-space graphics. Наиболее примечательно Khronos compatibility test suite (CTS) уже исчерпывающий корпус тестов которые наш драйвер должен пройти. Другими словами самая трудная и чувствительная часть работы с LLM'ами дать им хорошие тесты чтобы их заземлить уже сделана для нас.
Дополнительно Mesa уже имеет большие абстракции что делают нашу жизнь значительно проще. Вот как выглядит современный OpenGL stack на Linux:

Всё что нужно это перевести между Gallium Mesa's internal API и AGX hardware semantics. Одна из самых крупных частей этого процесса перевод от NIR Mesa's internal IR что довольно похожа на LLVM IR к AGX's proprietary ISA. Как bonus этот компилятор может быть переиспользован для будущего Vulkan драйвера.
Niklas был способен медленно итерировать сквозь OpenGL features REila user space как он шёл пока не достигнул полной OpenGL ES 3.0 совместимости (unsupported тесты это опциональные extensions):

На всём протяжении процесса выгодили от существующей M1/M2 работы: хотя точная hardware semantics отличается общая форма остаётся похожей и потому много правильных tradeoffs/decisions уже были сделаны для нас. На протяжении времени построения этого драйвера стало ясно что Alyssa Rosenzweig и другие что построили M1/M2 драйвер это абсолютные волшебники — низкий поклон им!
Deliverables
Mesa: https://github.com/niklassheth/mesa
Linux Kernel Driver: https://github.com/GravityLinux/linux/tree/gravity-m4
User-Space RE Documentation: Cody Niklas
Дальнейшая работа
Vulkan 1.4, OpenGL 4.6, OpenGL ES 3.2, OpenCL 3.1, Direct3D 12 (через Proton) и ray tracing все в scope. Хотят что драйвер был настолько же хорош как лучшие graphics драйверы в мире.
Дополнительно Niklas и я хотим upstream всё это, но есть значительные препятствия. Использовалась неизменённая Asahi UAPI поэтому нет policy issues с Mesa upstreaming, но это нужно намного больше тестирования human review и быть рефакторено в reviewable PR. Также ожидается значительный скептицизм учитывая что это вероятно первый когда-либо полностью LLM-written GPU драйвер и что код будет held к более высокому стандарту чем human-written код. Готовы к этим challenges но они в основном human и nontechnical что LLM'ы не могут помочь с.
Linux kernel driver будет даже большей проблемой потому что M1/M2 драйвер ещё не upstream и практически не в позиции чтобы изменить это. Думаем лучший подход здесь просто ждать того что driver будет upstreamed и потом upstream наш драйвер после M1/M2 is upstream (после всего требуемого refactoring + review + decomposition + whatever). Это может быть пока к сожалению.
Когда я смогу это использовать?
Терпение молодой кузнечик, нетерпение получить этот код в ваши руки скоро достаточно и ожидание может быть много меньше чем вы может быть ожидаете. После всего яблоко не падает далеко от дерева.
Присоединитесь к нам
Если интересно быть частью этого приглашаем присоединиться к нашему Discord Server. Добро пожаловать чтобы обсудить ideas chat или просто hang out!
Addendum: Really Sam?
Использовалась Codex с GPT-5.6 Sol (позже GPT-6 Astra когда вышла) для kernel RE task. Одна из самых больших issues что столкнулись была чрезмерно агрессивная cybersecurity restrictions (не в trusted access).
Девяносто девять процентов времени простой /goal resume или «keep going» было достаточно чтобы LLM продолжило (что также показывает restrictions были чрезмерно агрессивны) но они всё равно сломали unattended workflow. Кодировалась самая быстрая дурацкая возможная solution: daemon что берёт screenshot каждую минуту diff'ит это против последнего screenshot и если идентично (потому что Codex остановилась делать прогресс) типирует /goal resume. Случайно оставила это на в некоторых group chats:


Это сказано GPT-6 Astra и GPT-5.6 Sol absolute insane и далеко лучшие performers на firmware ABI RE поэтому этот hack был более чем worth it.
Addendum: M4 vs A18 Pro vs M5
Насколько может заметить user-space implementations M4 и A18 Pro effectively идентичны с единственной разницей что может найти будучи одно значение это slightly larger на M4 consistent с тем что это имеет больше cores. Firmware ABIs двух differ significantly: второй RTKit coprocessor на A18 Pro делает всё более complicated. Этим способом A18 Pro это more akin к M5 в terms firmware чем это к M4. M5's user space имеет some similarities к M4 с ISA being mostly superset но some parts это completely different такие как его texture descriptors. M5's user space имеет been partially reverse engineered; its firmware ABI is fully reverse engineered; prototype drm-shim has been built и thoroughly tested; и я don't think it would take long чтобы promote prototype к full Rust driver. My primary targets remain M4 Mac Mini и MacBook Neo.
Footnotes
- Metal Helper Programs
- Краткий список:
- Если когда-нибудь crashed firmware единственное recovery это full reboot
- Если что-нибудь было wrong с work handshake no error would be reported work would be ACKed и потом never completed.
- Some problems resulted в output changing как expected но also arbitrary corruption на pages что weren't supposed быть touched.
- https://github.com/mischa85/apple-gpu-firmware-abi
- Если найдена страница содержащая shader выбросили эту страницу и substituted нашу собственную shader как additional copyright measure.