Gemma 4 запустили на 13-летнем Xeon без GPU, баг с AVX1 нашёл и починил ИИ-агент Claude

Разработчик Райан Финдли взял старый сервер из подвала, переделанное хранилище HP StoreVirtual с двумя процессорами Intel Xeon E5-2690 v2 (поколение Ivy Bridge, 2013 год) без GPU, и запустил на нём открытую модель Google Gemma 4 с 26 млрд параметров (архитектура mixture-of-experts, MoE). Итоговая скорость, около 5 токенов в секунду, то есть примерно со скоростью чтения.

Поводом стал другой пост на Hacker News, где автор гонял Gemma 4 на процессоре Xeon 2016 года (поколение Broadwell) через форк движка llama.cpp под названием ik_llama.cpp, с флэш-атеншном, портированным под CPU, спекулятивным декодированием и MoE-роутингом, оптимизированными под процессор. Финдли решил повторить эксперимент на своём железе, но сборка не запустилась.

Он передал ошибку ИИ-агенту Claude, и тот сразу указал причину: чип автора оригинального поста, Broadwell, а Xeon E5-2690 v2 Финдли, на поколение старше, Ivy Bridge. У него есть набор инструкций AVX1, но нет AVX2 и FMA3, они появились только в Haswell в 2014 году, то есть уже после выпуска этого процессора. Быстрые ядра форка ik_llama.cpp рассчитаны именно на AVX2, и на процессоре Финдли их попросту не было.

Клод не просто указал на несовместимость, а довёл дело до рабочего патча. При выключенном флаге GGML_USE_IQK_MULMAT (который отвечает за быстрые AVX2-ядра) диспетчер операций движка не имел обработчиков для двух узлов графа вычислений, MOE_FUSED_UP_GATE и FUSED_UP_GATE, которые как раз нужны для MoE-блока Gemma 4. Билдер графа вычислений всё равно генерировал эти операции, они проваливались в ветку по умолчанию, и тензоры для примерно 240 экспертных блоков на каждый проход попросту не вычислялись, модель на выходе читала мусор из неинициализированной памяти. Внешне это выглядело как бегло звучащая, но бессмысленная многоязычная тарабарщина: токены разбрасывались по всему 262-тысячному словарю, результат был детерминированным (не зависел от количества потоков), и никаких NaN в вычислениях не было.

Клод нашёл причину, изучив сами логиты перед сэмплированием: средний логит первого токена держался на подозрительно ровных +16, а около 80% словаря получало положительные значения, так выглядит не случайный шум, а систематическая добавка от неинициализированных данных в памяти. После этого поиск двух пропущенных case-веток в диспетчере занял около минуты.

Патч состоит из трёх частей: (1) починка компиляции, «скалярные» ветки кода на самом деле ссылались на AVX2-функции и не собирались без них, их переписали на переносимый скалярный код; (2) исправление самого рантайм-бага, вместо правки диспетчера операций граф вычислений стали строить иначе: совмещённый тензор весов gate+up разбивается на два вида (view), для каждого запускается отдельный mul_mat_id, а результаты объединяются функцией SILU без обращения к AVX2-ядрам; то же самое сделано для плотных (не MoE) слоёв; (3) починка CI-заглушек, чтобы тесты вообще собирались на процессорах без AVX2.

Отдельно нашли ещё один баг: флаг --run-time-repack переупаковывает веса в специальный AVX2-only формат и на процессорах без AVX2 портит вывод так же, как и основная проблема. Этот баг не чинили, его просто обходят, не используя флаг.

В итоге на процессоре без GPU получили около 5,2 токена в секунду на генерации и около 16 токенов в секунду на обработке промпта для модели Gemma 4 26B-A4B в квантовании Q8_0. Патч опубликован как pull request ikawrakow/ik_llama.cpp#2138 и на момент публикации поста ждёт ревью мейнтейнера.

Главный тезис автора: ценность ИИ-агента здесь не в том, чтобы один раз написать «почини», а в том, чтобы прочитать чужой производительный C++, понять, почему конкретное ядро не работает на конкретной микроархитектуре, и обойти проблему, не выбрасывая оптимизации форка. Роль самого Финдли он описывает как «рулить»: ставить эксперименты и узнавать правильный ответ, когда он появится.

Ключевые факты

  • Gemma 4 (26 млрд параметров, MoE) запустили на списанном сервере 2013 года с процессорами Xeon E5-2690 v2 без GPU, около 5 токенов в секунду
  • Причина изначальной поломки, процессор старше, чем поддерживаемый форком набор инструкций: есть AVX1, но нет AVX2/FMA3 (появились в 2014 году)
  • ИИ-агент Claude сам нашёл скрытый баг: диспетчер операций движка ik_llama.cpp не обрабатывал два узла MoE-графа Gemma 4, из-за чего тензоры части экспертов оставались невычисленными и модель выдавала мусор из памяти
  • Диагноз поставили по логитам: подозрительно ровное среднее значение +16 указывало на неинициализированную память, а не на случайный сбой
  • Патч из трёх частей (починка сборки, обход бага без правки диспетчера, починка CI) отправлен как pull request в ikawrakow/ik_llama.cpp, ждёт ревью мейнтейнера

Почему это важно

История показывает практический, а не рекламный пример работы ИИ-агента: Claude не просто подсказал одну команду, а провёл полноценное расследование чужого производительного C++-кода, нашёл несовместимость процессорных инструкций, диагностировал скрытый баг по косвенным признакам (аномальные логиты) и написал рабочий патч, который не ломает оптимизации для более новых процессоров.

Кому это важно

Тем, у кого есть старое серверное железо (процессоры без AVX2, то есть до 2014 года выпуска) и кто хочет запускать открытые модели локально без GPU, как резервный вариант на случай недоступности платных API или как дешёвый способ обрабатывать неспешные пакетные задачи. Также интересно тем, кто использует ИИ-агентов для работы с низкоуровневым, производительным кодом, а не только с обычными приложениями.

Как это применить

Автор указывает точный рецепт: процессоры Intel Xeon E5-2690 v2 (Ivy Bridge, есть AVX1, нет AVX2), форк ik_llama.cpp собирается из ветки с патчем при выключенном флаге GGML_USE_IQK_MULMAT, модель, Gemma 4 26B-A4B в квантовании Q8_0, флаг --run-time-repack при запуске использовать нельзя, он несовместим с AVX1 и портит вывод. Патч на момент публикации ещё не принят в основную ветку, поэтому нужно ставить именно ветку с исправлением.

Можно ли доверять

Автор прозрачно раскрывает роль ИИ-агента: сам он не программист на C++, диагностику и патч написал Claude, а сам автор ставил эксперименты и проверял корректность результата. Технические детали (конкретные названия функций, номера коммитов, номер pull request, точные цифры производительности) подробные и проверяемые, патч опубликован в открытом репозитории и на момент написания поста ожидает ревью мейнтейнера, то есть независимой проверки со стороны сообщества.

Риски и подводные камни

Патч ещё не прошёл ревью мейнтейнера форка и может быть изменён или отклонён. Обходной путь работает ценой производительности: вместо одного слитного AVX2-ядра используются два отдельных вызова, хотя автор отмечает, что на этом железе узкое место, не вычисления, а пропускная способность памяти, так что потеря невелика. Отдельный баг с флагом --run-time-repack не исправлен, а просто обойдён отключением флага. Подход рассчитан на энтузиастов старого серверного железа и не подойдёт тем, кому нужна гарантированная стабильность или поддержка.

«Я не писал руками фолбэки ядра для квантованного матмул-движка и не буду делать вид, что писал. Моя работа была, рулить процессом.»

— Райан Финдли, автор поста