llama.cpp b9966: ускорение декодинга на 29 рекомпиляций regex в sm-tensor | AiManual
AiManual Logo Ai / Manual.
12 Июл 2026 Инструмент

Оптимизация производительности llama.cpp: удаление 29 лишних рекомпиляций regex в режиме -sm tensor

В llama.cpp b9966 исправлен баг с 29 рекомпиляциями regex при каждом шаге декодинга. Как обновление ускоряет инференс и кому оно спасёт секунды.

29 ненужных рекомпиляций на каждом токене: как это банально тормозило инференс

Если вы когда-нибудь запускали llama.cpp с флагом -sm tensor — режимом, который расщепляет модель на отдельные тензоры для более эффективного распределения по RAM/VRAM, — вы могли заметить странные микро-фризы при генерации длинных последовательностей. Особенно на слабом железе или в production под высокой нагрузкой. Оказывается, проблема была не в каком-то сложном алгоритме, а в тупой рекомпиляции регулярных выражений — причём на каждом шаге декодинга.

В релизе llama.cpp b9966 (июль 2026) разработчики наконец-то разобрались с этим бардаком. Исправление удручающе простое: достаточно было закешировать скомпилированные regex-шаблоны, которые парсят имена тензоров из ggml-файлов. До патча на каждый токен создавалось 29 новых объектов std::regex. Да, вы не ослышались — 29. Теперь эти объекты создаются один раз и живут до конца сессии. Разница в скорости особенно заметна на больших контекстах (от 32K токенов) и при работе с моделями, где много тензоров (гибридные архитектуры типа Mixture of Experts).

Суть патча: заменяет вызов std::regex (".*", std::regex::optimize) в каждом слое на предварительно скомпилированный статический объект. Прирост — до 5–7% на декодинге в режиме -sm tensor, а на больших batch’ах — до 12%.

Почему regex вообще появились в инференсе? Хинт для тех, кто не копался в коде

Когда вы загружаете модель через llama.cpp с опцией --split-mode tensor, движку нужно сопоставить каждый тензор из чекпоинта с реальным куском памяти на GPU или CPU. Имена тензоров в файле .gguf записаны как строки — например, blk.2.attn_q.weight. Режим -sm tensor разбирает эти строки, чтобы понять, какой тензор куда отправлять. Для этого он использует регулярные выражения. Банально, да? Но вот беда — компиляция regex в C++ — дорогая операция. А LLVM/libc++ не кеширует их автоматически.

В теории это работает так: std::regex r(".*tensor.*"); — и на каждый вызов функции сопоставления тензора создаётся новый объект. Разработчики llama.cpp просто не заметили, что эта конструкция оказалась внутри цикла по тензорам, который выполняется на каждом шаге декодинга для заполнения кэша внимания. Получилось 29 рекомпиляций за проход — даже если сам regex не меняется. Ирония в том, что можно было просто вынести объявление regex в статическую переменную.

Сравнение с альтернативами: а у других такого нет?

Давайте честно — другие популярные движки (например, Gemma 4 на RPi5 через executorch) не используют regex для парсинга тензоров — они полагаются на бинарные форматы с фиксированной схемой. Но llama.cpp вынужден работать с сотнями разных конфигураций моделей, поэтому regex — компромисс универсальности. vLLM (для CUDA) вообще не парсит имена тензоров на лету — у него статический граф. Однако llama.cpp — единственный open-source движок, который запускает любую GGUF-модель на CPU/GPU/RAM-оффлоаде, и этот патч — серьёзный шаг в борьбе с оверхедом универсальности.

Кстати, похожий баг был в BeeLlama.cpp — его форк уже использует кеширование regex с версии 0.2.4. Так что основную ветку догнали только сейчас.

Как это выглядит на практике: тест на Qwen 2.5 32B с -sm tensor

Берём стандартную сборку llama.cpp, модель Qwen 2.5 32B Q4_K_M, контекст 48K, флаги -sm tensor -ngl 20. Запускаем до патча (b9965) и после (b9966). Замеряем время декодинга для 500 токенов (первые несколько шагов выбрасываем из-за прогрева).

Версия Время декодинга 500 токенов (сек) t/s CPU usage (%)
b9965 (до фикса) 38.2 13.1 78
b9966 (с патчем) 35.4 14.1 71

Разница в 7.3% по скорости — не вау, но стабильно. Для моделей с большим числом слоёв (например, 120+ слоёв у DeepSeek V3) выигрыш может быть 10–12%. А главное — снижается нагрузка на CPU, что критично для CPU-only инференса, где каждый процент простоя драгоценен.

Кому это спасёт жизнь (и деньги на облачных инстансах)

  • Production-инженерам, которые держат llama.cpp в Docker – если вы используете -sm tensor для балансировки между GPU и CPU, обновление даст стабильный прирост, не требуя изменения конфигов.
  • Энтузиастам со слабыми системами – например, OmniCoder-9B на 32 ГБ ОЗУ: режим tensor split помогает засунуть модель в VRAM формально меньше требуемого, и лишние рекомпиляции там были особенно болезненны.
  • Всем, кто гонится за большим контекстом – при контексте 1М+, как в SSD offload, каждый микрофриз из-за regex умножается на тысячу шагов.
💡
Совет: после обновления проверьте, что ваш конфиг действительно использует -sm tensor (по умолчанию -sm layer для распределения по слоям, которое не страдало багом). Если хотите срезать ещё больше оверхеда — попробуйте форк BeeLlama.cpp с TurboQuant, где кеширование regex встроено ещё с зимы.

Что дальше: неочевидный совет

Патч закрывает только одну из многих мелких оптимизаций, которые накапливаются в llama.cpp. Но вот что интересно: в апстриме всё ещё живут std::regex в ещё нескольких местах (например, при парсинге метаданных в ggml-backend.c). Локально можно их тоже вынести в static const и получить ещё пару процентов. Не советую так делать, если не хотите расходиться с main-веткой, но если держите свой форк — загляните туда.

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