- BrainTools - https://www.braintools.ru -
В своих прошлых материалах я уже рассказывал о запуске крупных MoE-моделей на ограниченном железе через llama-server и о сравнительных экспериментах с 35B, 120B и 176B-классами моделей. Практика показала, что llama.cpp давно перестал быть просто «запускатором GGUF»: сегодня это полноценный рантайм с OpenAI-совместимым API, поддержкой инструментов, Jinja-шаблонов, спекулятивного декодирования и гибкого CPU/GPU-offload. Единственное, чего ему не хватало для повседневной работы, — удобной обвязки, которая избавила бы от ручного набора десятка флагов при каждом старте.
Именно поэтому у меня появился небольшой лаунчер на Go: llama-runner, который брал на себя типовые сценарии: сам раскрывал ~ в абсолютные пути, проверял наличие бинарника и модели, собирал аргументы llama-server, пробрасывал логи и корректно гасил дочерние процессы по Ctrl+C. Программа успешно решала свою задачу, пока я не наткнулся на статью «70 токенов в секунду в Qwen3.8-27B на RTX 3090 [1]», где помимо тонкой настройки llama-server подробно разбиралась связка локального сервера с агентной оболочкой Claude Code. Идея показалась мне логичным развитием собственного инструмента: зачем запускать сервер и клиент двумя командами в разных терминалах, если это можно сделать одним вызовом?
Так родилась вторая версия llama-runner, о которой и пойдёт речь.
Задача формулировалась просто: llama-runner должен по умолчанию запускать llama-server, дожидаться его готовности и стартовать Claude Code с уже настроенными переменными окружения. Так, чтобы пользователь видел перед собой интерфейс агента, а не возился с адресами и портами. При этом хотелось сохранить и «старое» поведение [2]: если запуск идёт только ради сервера (например, для доступа через WebUI), Claude Code поднимать не нужно.
Формально это вылилось в четыре изменения:
Флаг -server , это режим «только сервер», полностью совпадающий с прежним поведением [3].
Флаг -alias с автоподстановкой значения local, если он не задан явно и не активен -server.
Ожидание готовности llama-server через опрос /health с настраиваемым таймаутом.
Запуск Claude Code с переменными ANTHROPIC_BASE_URL, ANTHROPIC_AUTH_TOKEN, ANTHROPIC_API_KEY и ANTHROPIC_DEFAULT_*_MODEL.
Звучит несложно, но в процессе всплыли три нетривиальные проблемы, каждая из которых заслуживает отдельного абзаца.
Claude Code как известно, клиент, изначально рассчитанный на модели Anthropic, и по умолчанию он запрашивает у сервера claude-sonnet-4 или claude-haiku-4. Локальный llama-server о таких именах ничего не знает и отвечает 404 model not found. Возможных решений два: либо глобально прописать нужные переменные в ~/.bashrc, либо заставить лаунчер подставлять имя модели автоматически.
Я выбрал второй путь, но с оговоркой. Флаг -alias пробрасывается в llama-server как --alias <name> и одновременно устанавливается в три переменные: ANTHROPIC_DEFAULT_SONNET_MODEL, ANTHROPIC_DEFAULT_HAIKU_MODEL и ANTHROPIC_DEFAULT_OPUS_MODEL. Клиент и сервер договариваются об имени без участия пользователя. Чтобы не заставлять людей каждый раз вводить -alias, я предусмотрел автоподстановку значения local: если пользователь не задал флаг явно и не запустил лаунчер в режиме -server, alias выставляется автоматически. Проверка «задал ли пользователь флаг» делается через flag.Visit, так можно отличить «не задан» от «задан пустой строкой» (последнее означает сознательный отказ от алиаса).
Это оказалось самым неприятным сюрпризом. Claude Code терминальное приложение с альтернативным экраном, невидимым курсором и постоянной перерисовкой ANSI-последовательностями. Как только llama-server пишет что-то в тот же терминал (а он пишет довольно много: метрики слотов, timing, предупреждения), интерфейс агента начинает «размазываться»: текст накладывается друг на друга, курсор прыгает, ввод глючит.
Решение этой проблемы заключается в том, что бы перенаправлять stdout и stderr сервера в файл, когда мы работаем в паре с Claude Code. В llama-runner появились флаги -log-file (по умолчанию ~/.cache/llama-runner/llama-server.log) и -quiet-server (по умолчанию true). В режиме -server логи, как и раньше, идут в терминал и там это никому не мешает. Туда же уходят логи mcp-search-server, если он запущен через -webui-mcp-proxy.
Самая коварная ошибка [4]. После запуска Claude Code в логах llama-server начал появляться 500 с текстом:
Error: Jinja Exception: System message must be at the beginning.
Как выяснилось, причина в стандартном Jinja-шаблоне Qwen 3.8, который строго требует, чтобы системное сообщение было первым в диалоге. Claude Code, когда делает повторную попытку после сбоя, отправляет системное сообщение уже не первым и шаблон падает, клиент “ретраит”, цикл повторяется. В логе это выглядит как бесконечная серия W srv operator(): got exception.
Лечится подменой шаблона. Сообщество давно поддерживает исправленные версии, например, в репозитории froggeric/Qwen-Fixed-Chat-Templates на Hugging Face. Шаблон лежит в корне репозитория под именем chat_template.jinja, и скачивается одной командой через curl:
curl -fL -o ~/GGUF/qwen-fixed-chat-template.jinja
https://huggingface.co/froggeric/Qwen-Fixed-Chat-Templates/resolve/main/chat_template.jinja
Я специально кладу файл в ~/GGUF туда же, где лежат сами модели. Это не обязательно, но удобно: все связанные с моделями артефакты в одном месте, и путь к шаблону совпадает с дефолтом лаунчера. Флаг -f заставляет curl завершиться с ошибкой, если сервер вернёт не 200, а, например, HTML-страницу редиректа или заглушку блокировки.
После скачивания обязательно убедитесь, что получен именно Jinja-шаблон, а не что-то другое. Достаточно взглянуть на первые строки (пример актуален для моей конфигурации):
head -20 ~/GGUF/qwen-fixed-chat-template.jinja
Ожидаемый вывод, это конструкции вида {%- if tools %} и {{- '<|im_start|>systemn' }}. Если вместо этого вы видите <!DOCTYPE html> или JSON с полем error, значит скачалась заглушка, необходимо перепроверить URL и повторить загрузку. Полезно также сразу убедиться, что в файле нет строки, из-за которой падал сервер:
grep -n "System message must be at the beginning" ~/GGUF/qwen-fixed-chat-template.jinja
Пустой вывод grep это хороший знак: это именно исправленный шаблон, в котором проверка ослаблена. Если строка найдётся, значит скачался не тот файл (например, из другой ветки репозитория), и стоит поискать альтернативу (шаблон от Qwen2.5 или Qwen3-Coder).
В llama-runner появился флаг -chat-template с дефолтом ~/GGUF/qwen-fixed-chat-template.jinja. То есть после скачивания в указанное место вам больше ничего не нужно передавать вручную: лаунчер сам подставит --chat-template-file в аргументы llama-server. Если же файл лежит в другом каталоге или вы экспериментируете с несколькими шаблонами, путь можно переопределить:
./llama-runner -chat-template ~/models/another-template.jinja
Проверка существования файла сделана мягкой. Если по указанному пути шаблона нет, llama-runner выведет предупреждение вида предупреждение: файл шаблона "..." не найден, запускаю без --chat-template-file и продолжит работу, при этом сервер стартует со встроенным шаблоном модели. Это удобно, когда вы пробуете разные модели и не хотите каждый раз править конфигурацию: несовместимость шаблона с конкретной моделью видна сразу по ошибке 500 в логе, но сам лаунчер из-за отсутствия файла не падает. Сознательно отказаться от кастомного шаблона можно и явно, передав пустое значение:
./llama-runner -chat-template ""
Такой запуск полностью игнорирует и дефолт, и переменные окружения, и передаёт серверу управление шаблоном «как есть».
Собрав всё вместе, я получил инструмент со следующей логикой [5].
Режим по умолчанию (сервер + Claude Code).
./llama-runner
Лаунчер запускает llama-server с моделью из ~/GGUF, подставляет --alias local, подхватывает исправленный шаблон, ждёт готовности через /health, выставляет переменные окружения и стартует claude. Логи сервера уходят в ~/.cache/llama-runner/llama-server.log. Когда пользователь выходит из Claude Code (любым способом — /exit, Ctrl+C, EOF), лаунчер шлёт SIGINT серверу и дожидается его завершения. Порт не остаётся занятым «висящим» процессом.
Режим только сервера (старое поведение).
./llama-runner -server
Логи идут в терминал, Claude Code не запускается, поведение полностью совпадает с первой версией программы. Удобно, когда вы работаете через встроенный WebUI llama-server или подключаете собственный клиент.
Гибкая настройка.
./llama-runner -alias qwen3-coder -claude-dir ~/my-project
./llama-runner -model ~/GGUF/other.gguf -alias other
./llama-runner -chat-template "" # без кастомного шаблона
./llama-runner -v # показать команду без запуска
Помимо этого, есть флаги для контекста (-c), размеров батча (-b, -ub), flash attention (-fa), числа параллельных слотов (--parallel), режима инструментов (-tools), Jinja-шаблонов (-jinja), включения mmap с ленивым режимом (-lm-lazy) и MCP-прокси (-webui-mcp-proxy). В режиме прокси лаунчер дополнительно поднимает mcp-search-server на порту :8090 , который осуществляет поиск необходимой информации в интернете и корректно гасит его при выходе. Код моего “поискового” MCP сервера [6].
Код открыт и доступен по адресу: https://github.com/SkalaSkalolaz/llama-runner/ [7]
Проект распространяется свободно, без каких-либо ограничений на использование. Если у вас Ubuntu или другой Linux, есть локальная модель в GGUF и вы давно хотели попробовать агентную работу с Claude Code поверх llama-server , берите и пользуйтесь. Сборка тривиальная:
git clone https://github.com/SkalaSkalolaz/llama-runner/
cd server
go build -o llama-runner .
./llama-runner -h
Особенно рекомендую тем, кто, как и я, экспериментирует с MoE-моделями на ограниченной VRAM: связка llama-server + Claude Code + Qwen3.8-35B-A3B даёт вполне рабочую агентную среду без облаков, API-ключей и ежемесячных платежей. Если найдёте баги, присылайте issue или пул-реквесты – проект живой, и я готов его развивать дальше.
Одна оговорка: llama-server с флагом --tools all это серьёзная штука. Если вы открываете порт не только на 127.0.0.1, ограничьте режим инструментов через -tools до нужного подмножества. В остальном – запускайте, экспериментируйте и делитесь результатами.
Автор: SkalolazOther
Источник [8]
Сайт-источник BrainTools: https://www.braintools.ru
Путь до страницы источника: https://www.braintools.ru/article/36985
URLs in this post:
[1] 70 токенов в секунду в Qwen3.8-27B на RTX 3090: https://habr.com/ru/articles/1092728/
[2] поведение: http://www.braintools.ru/article/9372
[3] поведением: http://www.braintools.ru/article/5593
[4] ошибка: http://www.braintools.ru/article/4192
[5] логикой: http://www.braintools.ru/article/7640
[6] “поискового” MCP сервера: https://github.com/SkalaSkalolaz/mcp-search-server/
[7] https://github.com/SkalaSkalolaz/llama-runner/: https://github.com/SkalaSkalolaz/llama-runner/
[8] Источник: https://habr.com/ru/articles/1094534/?utm_source=habrahabr&utm_medium=rss&utm_campaign=1094534
Нажмите здесь для печати.