Перейти к содержанию
Публикация AiManual

CRAP: файнтюн Gemma 4 E2B, который генерирует ELF-бинарники напрямую, без компилятора и ассемблера

CRAP - файнтюн Gemma 4 E2B, который по промпту выдаёт готовый ELF в hex: без gcc, ассемблера и вызовов инструментов. Разбираем цифры v1.1 против v1, обвал Q4_K_

Коротко

Что будет в материале

  1. 01

    Что такое CRAP и что он на самом деле делает

  2. 02

    Результаты v1.1: где прирост есть, а где его нет

  3. 03

    Почему loss 0.038 не означает рабочий бинарник

  4. 04

    Запуск локально и в браузере: что нужно и какие квантования работают

CRAP (Compiler Replacement for Assembling Programs) - файнтюн Gemma 4 E2B, который принимает промпт вида «generate a Linux x86_64 binary that prints 'boop'» и выдаёт рабочий ELF-бинарник в виде hex. Автор описал проект и метрики в посте «Coding is solved* (exclusions apply)» на r/LocalLLaMA.

Ни компилятора, ни ассемблера, ни вызовов инструментов: модель генерирует байты исполняемого файла сама, токен за токеном. gcc, as и ld в этой цепочке не участвуют вообще.

Для повседневной разработки пользы здесь немного. Тот же бинарник обычный компилятор собирает детерминированно, корректно и за миллисекунды, а CRAP тратит несколько гигабайт весов, работает медленнее и иногда ошибается. Интерес в другом: насколько языковая модель удерживает жёсткую структуру байтового формата и обобщает ли она на данные, которых не видела при обучении.

Что такое CRAP и что он на самом деле делает

CRAP решает один узкий класс задач: программа печатает строку и завершается с заданным кодом возврата. Больше ничего модель не умеет, и автор оговаривает это прямо, без оговорок в сносках.

Чем CRAP отличается от «LLM пишет код»

Обычная LLM по промпту «напиши программу, печатающую boop» выдаёт текст исходника на C, Rust или Python. Дальше его собирает gcc или rustc, и только потом появляется исполняемый файл. CRAP пропускает оба шага: на выходе сразу hex-строка, которая парсится в ELF и запускается.

Различие в классе задачи. В первом случае модель работает как помощник программиста и отдаёт текст, который человек прочитает и соберёт. Во втором модель выступает генератором байтов формата: каждый токен - это часть заголовка, таблицы или машинного кода x86_64.

Отсутствие tool calls принципиально. Модель не может собрать черновик, запустить его и поправить по ошибке: проверки внутри цикла генерации нет, всё решается одним проходом.

Почему ELF - жёсткий тест для языковой модели

ELF (Executable and Linkable Format) описывает файл строго: 64-байтный заголовок с магией \x7fELF и полями вроде e_type и e_entry, таблица program headers, секции с кодом и данными, машинные инструкции x86_64. Ошибка в одном байте ломает запуск целиком: неверный e_entry даёт падение до первой инструкции, сбитое смещение в program header приводит к тому, что ядро отказывается загружать файл.

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

Результаты v1.1: где прирост есть, а где его нет

В v1.1 автор переобучил модель и прогнал её по пяти наборам, сравнив с первой версией.

Сценарийv1v1.1
Новые строки8/4026/40
Многоходовые запросы (~3 хода)0/1210/12
Строки с удвоенными символами (boom и подобные)2/2418/24
Случайные exit-коды, которых не было в обучениине указано23/25
Held-out legacy-набор20/2320/23

Цифры взяты из того же поста автора. Прирост виден на новых данных и в диалоге, legacy-набор не сдвинулся вообще. И 26/40 на новых строках означает 14 провалов из 40: примерно каждый третий запрос заканчивается нерабочим или неверным бинарником.

Многоходовые сценарии: 10/12 против 0/12

Многоходовый запрос в этой оценке - около трёх реплик в диалоге. Типичный сценарий: сначала попросить бинарник, печатающий boop, затем заменить строку, потом сменить код возврата. v1 такие цепочки проваливала полностью, 0 из 12: модель держала только один выстрел. v1.1 вытянула 10 из 12, то есть научилась удерживать параметры задачи через несколько реплик.

Строки с удвоенными символами и случайные exit-коды

Удвоенные символы вроде boom и exit-коды, которых не было в обучении, - два out-of-distribution сценария. На удвоенных строках v1.1 даёт 18/24 против 2/24 у v1, на случайных кодах возврата 23/25.

Цифры выглядят убедительно, но класс задач остаётся узким. Exit-код - одно число, которое модель проставляет в готовом каркасе; умение вернуть код 147 не равно умению написать произвольную программу. 23/25 говорит об обобщении внутри шаблона, а не о понимании системного программирования.

Почему loss 0.038 не означает рабочий бинарник

Один из обучающих прогонов дошёл до loss 0.038 и при этом провалил 11 из 12 exit-кодов, на которых обучался. Вывод автора короткий: gradient descent не равен task completion ascent.

Причина в том, что loss измеряет совпадение предсказанных токенов с эталонными, а задача требует, чтобы файл запустился. Модель может почти идеально воспроизводить hex-разметку и всё равно испортить один байт, который ломает ELF. Кривая обучения такого не показывает.

Отсюда методический урок для любого файнтюна под проверяемый результат: нужна поведенческая оценка (behavior eval), а не только кривая на training-сете. Метрика обучения и метрика задачи - разные вещи, и расхождение между ними в этом проекте измерено в конкретных цифрах, а не описано словами.

Запуск локально и в браузере: что нужно и какие квантования работают

Демо работает в браузере: страница загружает Q6_K GGUF (~3,6 ГиБ) через wllama на WebGPU, отдаёт hex потоком по мере генерации, парсит его и передаёт пользователю готовый ELF. Запускает бинарник человек сам, демо только собирает файл.

Версия моделиРезультатКомментарий
f16114/144базовый уровень сравнения
Q6_K GGUF109/144пять дополнительных провалов: неверные значения, а не полностью сломанные бинарники
Q4_K_M3/144в демо не используется

Почему Q4_K_M не годится, а Q6_K - компромисс

Q4_K_M обваливается до 3/144. Для генерации байтов ELF агрессивное квантование убивает точность: модель путается в конкретных значениях, а здесь каждое значение критично. Q6_K теряет всего пять попаданий относительно f16 (109 против 114), и это ошибки в значениях, а не бинарники, которые не запускаются вообще.

Практический вывод для похожих задач: если важен каждый байт, экономия на квантовании не работает. Разница между Q6_K и Q4_K_M в этом проекте измеряется десятками успешных запусков, а не парой процентов.

Ограничения браузерного демо

Протестирована одна конфигурация: Chrome на M4 Pro Mac. В Firefox модель загружается, но генерирует медленно, другие окружения не проверялись. Загрузка 3,6 ГиБ занимает время и требует стабильного канала, а для запуска полученного файла нужна Linux x86_64-среда.

Если задача другая и хочется просто потрогать Gemma 4 E2B на своём железе без браузера, есть отдельный путь: компактный C-рантайм gemma4.c на 700 строк с int8-квантизацией и OpenMP. Когда важнее скорость инференса, чем сам эксперимент, полезнее разобраться с оптимизацией рантайма: патчи и бенчмарки llama.cpp на RTX 4080 показывают, где прирост реален, а где нет.

Что CRAP умеет и чего не умеет: честные ограничения

Модель генерирует только программы, которые печатают строку и завершаются. Файловый ввод-вывод, арифметика, циклы, аргументы командной строки, сеть: ничего из этого в область применимости не входит. Это не замена компилятору и не «решённое программирование», автор сам иронизирует над таким прочтением.

Где CRAP объективно проигрывает компилятору

  • Детерминизм. Компилятор на один и тот же исходник даёт один и тот же бинарник. CRAP может выдать рабочий файл, а может не выдать.
  • Скорость. Сборка занимает миллисекунды, генерация токенов в браузере - заметно дольше.
  • Корректность. Модель иногда ошибается, и результат проверяется только запуском.
  • Размер. Несколько гигабайт весов против мегабайт компилятора.
  • Класс задач. Одна строка и код возврата вместо языка общего назначения.

Это не критика автора: всё перечисленное он пишет сам, первым абзацем.

Где эксперимент всё же интересен

CRAP проверяет границы LLM на структурно жёстком формате: способна ли модель генерировать валидные байты ELF, обобщать на незнакомые строки и коды возврата, удерживать параметры через несколько реплик. Как исследовательский артефакт проект полезен, и отдельно ценен кейс про loss 0.038, который стоит держать в голове каждому, кто обучает модели под задачи с проверяемым результатом.

Прикладная сторона Gemma 4 E2B лежит в других областях. В Manga Translator Tool эта модель отвечает за чат внутри офлайн-переводчика манги, и там от неё требуется связный текст, а не байты исполняемого файла.

Итог: стоит ли следить за CRAP

CRAP - аккуратный технический эксперимент с честно оговорёнными ограничениями, а не инструмент для работы. Смотреть его стоит, если интересуют границы возможностей LLM: демо открывается в Chrome, качает ~3,6 ГиБ через wllama на WebGPU и отдаёт ELF, который вы запускаете сами. Цифры v1.1 полезно сравнить с v1, чтобы увидеть, как выглядит реальный прирост на OOD-данных и почему legacy-набор при этом не двигается.

Если нужен рабочий бинарник для Linux x86_64, берите компилятор: он сделает то же самое детерминированно, корректно и за миллисекунды. Держать CRAP в голове стоит ради другого вывода: поведенческая проверка результата важнее loss, а «модель генерирует код» и «модель генерирует исполняемый файл» - задачи с разной ценой ошибки.

Подписаться на канал