Что проверяли и почему «на глаз» здесь не работает
Независимая проверка low-bit моделей на CPU, опубликованная в r/LocalLLaMA, дала два противоположных результата. Основная ветка bitnet.cpp (коммит 0b341e5) выдаёт некорректный вывод для BitNet b1.58 2B4T. Файлы Bonsai и Ternary-Bonsai 1.7B, наоборот, дают корректный результат во всех трёх рантаймах, где загружаются: llama.cpp, форк Prism от PrismML и ik_llama.cpp.
Ловушка в том, что на x86 текст BitNet в bitnet.cpp выглядит связно. Читаешь абзац, он грамматически нормальный, и повода сомневаться нет. Перплексия на тех же токенах при этом примерно в 6 раз выше эталона из HF transformers. На ARM модель вообще печатает «????» вместо текста. Обе причины были описаны в issue задолго до проверки, так что это подтверждение известных багов, а не свежая находка (разбор с числами и коммитами).
Методика простая и жёсткая: сравнение с эталоном, а не оценка качества на глаз. Для Bonsai считали KL-дивергенцию против точного F16-эталона на ARM и x86. Для BitNet сравнивали перплексию с HF transformers на одинаковых токенах. Связное предложение на выходе ничего не доказывает: распределение вероятностей следующего токена может уехать далеко от эталонного, и внешне это почти незаметно.
Что попало в проверку:
- модели: BitNet b1.58 2B4T, Bonsai и Ternary-Bonsai 1.7B, Bonsai и Ternary-Bonsai 27B;
- рантаймы: bitnet.cpp, llama.cpp, форк Prism, ik_llama.cpp;
- железо: ARM-сервер на Neoverse-N1 и x86-виртуалки с AVX2 (Intel Xeon, AMD EPYC).
У каждого числа зафиксированы коммит рантайма, sha256 файла и флаги запуска. Один скрипт перезапускает основные измерения. Это не бюрократия: без такой тройки метаданных цифры скорости несравнимы, потому что один и тот же файл в разных сборках отличается в десятки раз.
BitNet b1.58 2B4T: почему bitnet.cpp даёт неверный вывод
Модель обучена с активацией ReLU², то есть с квадратом ReLU. Основная ветка bitnet.cpp применяет на этом месте SiLU. Функции похожи по форме, но ведут себя по-разному: SiLU пропускает небольшие отрицательные значения и иначе масштабирует положительные. Веса модели подогнаны под ReLU², поэтому подмена меняет распределение выходов на каждом слое, и ошибка накапливается по глубине сети.
Вторая причина касается ARM. Там нужен отдельный кернел под эту архитектуру, а без него вычисления ломаются настолько, что модель выдаёт «????». Два независимых дефекта дают два разных симптома: на x86 тихая деградация качества, на ARM явный мусор на выходе.
Что именно ломается: SiLU вместо ReLU² и ARM-ядро
Проблему активации зафиксировали в issue #588, проблему ARM в issue #470. Оба отчёта существовали до проверки, то есть тест подтвердил давно известное состояние основной ветки, а не открыл новую ошибку.
Коварство ситуации в асимметрии симптомов. Пользователь на x86 видит нормальный текст, не замечает разницы в 6 раз по перплексии и делает вывод, что всё работает. Пользователь на ARM видит «????» и сразу идёт искать причину. Хуже всего первому: он получает поток связных, но статистически неверных ответов и не имеет повода ему не доверять.
Как починить BitNet: патчи, #642 и вариант без патчей
Рабочих путей два, и они разной степени удобства. Первый - патчить bitnet.cpp, второй - не трогать его вообще и взять другой рантайм.
Однострочная правка активации и ARM-ядро
Правка активации из #588 меняет одну строку, и этого достаточно, чтобы вернуть корректность на x86. Альтернатива: применить #605 и выставить runtime key. Для ARM нужен кернел из isHuangXin/llama.cpp#2.
Если не хочется собирать два патча по отдельности, есть #642: в третьей версии от 15 октября он объединяет однострочную правку активации с тем же ARM-ядром и корректен и на ARM, и на x86. Для тех, кто готов собирать из исходников, это самый цельный вариант.
Вариант без патчей: конвертация в iq2_bn_r4 для ik_llama.cpp
Без патчей корректный вывод даёт ik_llama.cpp после однократной конвертации файла в формат iq2_bn_r4. Ключевое слово: однократной. Конвертация делается один раз, дальше вы работаете с готовым файлом и не возвращаетесь к сборке патчей.
Оговорка по-честному: это обходной путь, а не исправление bitnet.cpp. Основная ветка bitnet.cpp на момент проверки для BitNet b1.58 2B4T непригодна, и патч в апстрим ситуацию на вашей машине сам не изменит. Если нужен другой CPU-only движок под ту же задачу, есть отдельный разбор Project Zero на чистом C99 с mmap-загрузкой GGUF и собственным VBMI-ядром.
Bonsai и Ternary-Bonsai 1.7B: где вывод корректен
С файлами Bonsai картина проще и приятнее. Проверка KL против точного F16-эталона на ARM и x86 показала: файлы 1.7B дают корректный вывод везде, где загружаются, будь то llama.cpp, форк Prism или ik_llama.cpp. Для 27B результаты Prism и ik_llama.cpp согласуются между собой. Проблема здесь не в корректности, а в скорости и в том, какой файл вообще загрузится в конкретной сборке.
Какие файлы брать: PQ2_0 и Q2_0_g64
Проверенные и рекомендуемые к работе файлы: PQ2_0 для Prism и ik_llama.cpp, Q2_0_g64 для llama.cpp, Prism и ik_llama.cpp. У второго варианта есть важная дата. 13 октября llama.cpp смержил AVX2-ядро, ускорение около 6 раз. В тот же день ik_llama.cpp добавил поддержку Q2_0 с ускорением примерно в 15 раз относительно Prism, корректно на ARM и x86.
Отсюда практическое правило: смотрите на дату коммита рантайма. Сборка недельной давности и свежая сборка с одним и тем же файлом дадут разницу в разы, и вы спишете её на железо.
Ловушка с файлом «recommended»
На карточке Ternary-Bonsai-1.7B есть файл Q2_0 с пометкой «recommended». Он не загружается ни в llama.cpp, ни в Prism, ни в ik_llama.cpp. Три рантайма, один результат: файл просто не подхватывается.
Вывод неприятный, но полезный: бейдж на карточке модели не заменяет проверку. Сверяйтесь с sha256 файла и коммитом рантайма, а не с надписью «recommended». Это же правило спасает при сравнении чисел из чужих бенчмарков.
Скорость на CPU: цифры и почему размер файла не главное
Главный тезис проверки: скорость определяется комбинацией файл × рантайм × CPU, а не размером файла. Цифры prompt processing для Ternary-Bonsai 1.7B на x86:
| Файл | Рантайм | Prompt processing |
|---|---|---|
| PQ2_0 | ik_llama.cpp с флагом -rtr | 71 t/s |
| PQ2_0 | Prism | 28 t/s |
| Q2_0 g64 | Prism | 2 t/s |
Тот же файл PQ2_0 разгоняется с 28 до 71 t/s, и разница здесь не в модели и не в железе, а в рантайме и флаге. Для 27B картина ещё нагляднее: меньший файл PTQ1_0 примерно в 4,4 раза медленнее PQ2_0 по prompt processing в Prism на AVX2, замер через llama-bench. Меньше по размеру не значит быстрее. Похожая логика работает и в других сборках: в разборе ускорения MoE-инференса в llama.cpp прирост тоже дают патчи под конкретное железо, а не смена размера весов.
Флаг -rtr: главный переключатель скорости в ik_llama.cpp
-rtr выключен по умолчанию. Без него ik_llama.cpp часто оказывается самым медленным из трёх рантаймов, с ним на том же x86 выдаёт 71 t/s против 28 в Prism. Это самый дешёвый способ ускориться: проверить флаг до того, как менять рантайм или файл.
Практический риск очевиден. Человек запускает ik_llama.cpp без -rtr, видит худший результат, делает вывод «рантайм плохой» и уходит. Флаг при этом никто не включал.
Почему Q2_0 g64 в Prism даёт 2 t/s
У Prism пока нет SIMD-ядра под Q2_0 g64. 2 t/s здесь не свойство файла, а отсутствие быстрого кернела: вычисления идут по медленному пути. Когда ядро появляется, картина меняется радикально, как это случилось 13 октября в llama.cpp (AVX2-ядро, около 6 раз) и в ik_llama.cpp с поддержкой Q2_0 (примерно в 15 раз быстрее Prism). Смотреть на дату коммита рантайма полезнее, чем на таблицу квантизаций.
Границы теста: что осталось открытым вопросом
Все приведённые числа получены на ARM Neoverse-N1 и на x86 с AVX2 (Intel Xeon, AMD EPYC). Результаты для AVX-512, Zen 4, Graviton и Raspberry Pi 5 не проверялись и остаются открытым вопросом. Apple Silicon в тест тоже не попал, а скрипты пока только под Linux.
Это ограничение самой проверки, и переносить цифры на своё железо без замеров нельзя. Разница между AVX2 и AVX-512 в ядрах инференса может быть кратной, особенно там, где появляются новые SIMD-пути. Числа из таблицы выше описывают конкретные машины и конкретные коммиты, а не «CPU в целом».
Как воспроизвести проверку самому
Автор проверки оставил путь для повторения на своём железе: ноутбук прогоняет подмножество измерений на бесплатной виртуалке Kaggle или Colab за 30-60 минут и сравнивает результат с опубликованными числами. Плюс один скрипт, который перезапускает основные измерения.
Минимальный набор для честного сравнения:
- зафиксировать коммит рантайма, сборка из main без хеша ничего не доказывает;
- сверить sha256 файла модели, разные файлы под одним именем встречаются;
- записать флаги запуска, включая -rtr и параметры контекста;
- сравнивать prompt processing отдельно от генерации, это разные метрики.
На бесплатной VM прогоняется подмножество, а не весь набор измерений, и это нормально для первичной сверки. Автор отдельно оговаривает, что английский для него не родной и что для анализа и формулировок использовался Claude Code, а прогоны шли на его ARM-сервере и бесплатных Kaggle/Colab VM, числа проверены. Это стоит держать в голове, оценивая статус материала: независимая проверка, но не рецензируемая статья.
Кому подходят low-bit модели на CPU: практический вывод
Для Bonsai и Ternary-Bonsai 1.7B решение простое. Берите PQ2_0 для Prism и ik_llama.cpp либо Q2_0_g64 для llama.cpp, Prism и ik_llama.cpp с 13 октября. В ik_llama.cpp не забудьте про -rtr, иначе получите самый медленный вариант из доступных. Файл Q2_0 с пометкой «recommended» пропускайте: он не загружается ни в одном из трёх рантаймов.
Для BitNet b1.58 2B4T выбора два. Либо #642 в третьей версии от 15 октября (однострочная правка активации плюс ARM-ядро, корректно на ARM и x86), либо ik_llama.cpp после однократной конвертации файла в iq2_bn_r4. Основную ветку bitnet.cpp для этой модели использовать не стоит.
Ограничения, о которых нужно помнить до того, как строить на этом продакшен:
- тесты не покрывают AVX-512, Zen 4, Graviton, Raspberry Pi 5 и Apple Silicon;
- файл «recommended» на карточке модели не загружается;
- меньший файл может быть в 4,4 раза медленнее большего по prompt processing;
- без -rtr ik_llama.cpp часто проигрывает остальным;
- скрипты воспроизведения пока только под Linux.
Low-bit модели на CPU уже дают корректный вывод, но требуют внимания к деталям сборки. Если ваш сценарий ближе к GPU с ограниченной VRAM, пригодится отдельное сравнение Bonsai Ternary PQ2 против Qwen3.8 27B IQ3_XXS на 16 ГБ: там качество совпало, а разница по скорости и расходу токенов оказалась трёхкратной.