DigitableCourses

Локальная модель без Ollama: digit local

Команда digit local start|stop|status: Digit ставит llama-server из релиза llama.cpp с проверкой sha256, качает GGUF и поднимает локальную модель на порту 8127. Что скачивается, куда кладётся, сколько стоит в памяти и на диске, и почему свой роутер не может быть основной моделью.

Локальная модель без Ollama: digit local

До этой команды «локальный» путь первого запуска упирался в чужой установщик: мастер искал ollama в PATH и, не найдя, печатал ссылку на ollama.com и выходил. Человек, который хотел просто запустить агента, получал домашнее задание — и получал его ровно в том месте, ради которого мастер и запускали.

digit local доводит дело до конца силами самого Digit: скачивает llama-server, скачивает веса, поднимает OpenAI-совместимый сервер на 127.0.0.1:8127 и печатает готовую секцию для config.yaml.

digit local start | stop | status

Почему llama.cpp, а не Ollama

Ollama — отдельный демон со своим форматом хранения. GGUF в него надо импортировать через Modelfile, а для этого Ollama уже должна быть установлена: чтобы решить проблему «нет модели», сначала нужно решить проблему «нет Ollama».

llama.cpp публикует готовые бинарники под каждую платформу (лицензия MIT), читает GGUF с диска как есть и поднимает ровно тот OpenAI-совместимый эндпоинт, с которым Digit и так работает через провайдер custom. Не хватало только того, кто его поднимет.

Если Ollama в системе уже стоит, мастер первого запуска возьмёт её и качать ничего не станет. Это не предпочтение, а вежливость: поднимать рядом второй сервер и качать вторые веса — расточительство.

Что происходит при digit local start

Четыре шага, и каждый пропускается, если его результат уже на месте.

  1. Бинарник. Скачивается ассет релиза llama.cpp под текущую платформу, сверяется sha256, архив распаковывается в $DIGIT_HOME/bin/llama.cpp-<тег>/ целиком — не одним файлом.
  2. Веса. GGUF качается в $DIGIT_HOME/models/ во временный файл рядом и переименовывается только целиком.
  3. Сервер. Запускается llama-server, и команда ждёт не появления процесса, а ответа 200 на /health.
  4. Конфиг. Печатается готовая секция model: — Digit её не подставляет молча, а показывает.

Проверено запуском на этой машине (Linux x86_64, 8 ядер, сборка без ускорителей):

· Скачиваю llama.cpp b10295 (llama-b10295-bin-ubuntu-x64.tar.gz)
установлено за 0.86 с -> …/bin/llama.cpp-b10295/llama-server
version: 10295 (3db4ff877)

Замер разработчика на 16-ядерном хосте, с основными весами: установка llama-server — 1,2 с, загрузка весов (2,3 ГиБ) — 75 с, подъём сервера — 9,1 с, 8,9 ГиБ RSS при окне 64 000 с KV-кэшем в q8_0. Числа зависят от канала и процессора: на этой машине те же 424 МиБ вспомогательных весов качались 179,6 с, а сервер поднялся за 13,2 с.

Куда всё кладётся

Что Где Сколько
llama-server и его библиотеки $DIGIT_HOME/bin/llama.cpp-<тег>/ 40 МиБ распакованными
Веса основной модели $DIGIT_HOME/models/Qwen3-4B-Instruct-2507-Q4_K_M.gguf 2,3 ГиБ
PID и лог сервера $DIGIT_HOME/models/llama-server-<порт>.{pid,log} байты

$DIGIT_HOME по умолчанию — ~/.digit. Наружу при этом уходят ровно две загрузки: релиз llama.cpp с GitHub и файл весов с Hugging Face.

Что видно без запуска: digit local status

Команда отвечает и тогда, когда ничего ещё не скачано, — и это её основная работа. Живой прогон на машине, где локальная модель не поднималась:

$ digit local status
llama-server не запущен (порт 8127)
бинарник: не установлен — поставится при `digit local start`
веса qwen3-4b-instruct-2507: не скачаны (2.3 ГиБ, окно 262144)
веса digit-router-0.6b: не скачаны (0.4 ГиБ, окно 40960)

Отдельно различается случай «на порту что-то отвечает, но запущено не через digit local»: чужой сервер на 8127 не будет ни перезапущен, ни остановлен.

Тег llama.cpp прибит гвоздями вместе с хешами

Версия берётся не из /releases/latest, и причина не теоретическая: 2026-08-06 в 13:42 UTC у свежего тега в релизе висел один ассет из двадцати пяти — CI ещё догружал остальные. «Latest» у llama.cpp не атомарен, и автоустановка, которая ему доверяет, ломается в случайные минуты дня.

Контрольные суммы взять неоткуда: апстрим файла с ними не публикует. Поэтому sha256 каждого ассета снят руками при закреплении тега и лежит в коде (digit_cli/local_model.py). Без него любой, кто может подменить ответ на пути к серверу раздачи, кладёт исполняемый файл в $DIGIT_HOME/bin. Не сошёлся хеш — архив не распаковывается вовсе.

Обновление тега — отдельное осознанное действие вместе с пересчётом хешей, а не побочный эффект запуска.

Какие платформы поддержаны, а какие нет

Готовых сборок шесть: Linux x86_64 и aarch64, macOS arm64 и x86_64, Windows x64 и ARM64. Этого достаточно для обычных машин и не покрывает FreeBSD, musl-дистрибутивы вроде Alpine, 32-битные системы и Termux (ассет android-arm64 в релизе есть, но собран не под Termux и нами не проверялся — поэтому мы его не обещаем).

На неподдержанной платформе команда не притворяется, что справилась, а печатает три выхода: собрать llama.cpp самому и указать путь через DIGIT_LLAMA_SERVER, поставить пакетом (brew install llama.cpp) или подключить любой другой OpenAI-совместимый эндпоинт через digit model.

DIGIT_LLAMA_SERVER проверяется первым и на поддержанных платформах тоже: у кого собрана своя сборка с CUDA или Metal, тот не должен получить нашу процессорную вместо неё.

Чем сервер запускается и почему именно так

Флаги не подобраны на глаз, у каждого есть цена, которую он покупает.

  • --jinja — без него llama-server не применяет шаблон чата модели и не собирает tool_calls. Для агента, который весь состоит из вызовов инструментов, это разница между «работает» и «просто болтает».
  • --cache-type-k q8_0, --cache-type-v q8_0 — KV-кэш квантованный. На окне 64 000 у модели 4B это 4,8 ГиБ против 9,7 ГиБ: разница между «влезло в ноутбук» и «не влезло». Квантованный V-кэш llama.cpp принимает только вместе с --flash-attn, поэтому она включена.
  • --parallel 1 — один слот. Агент ходит по одному запросу за раз, а KV-кэш делится между слотами: при двух слотах каждому досталась бы половина окна.
  • --cors-origins localhost — по умолчанию llama-server отдаёт Access-Control-Allow-Origin: * и сам предупреждает об этом в логе. Слушаем мы только 127.0.0.1, но этого мало: страница в браузере ходит на localhost беспрепятственно и с открытым CORS может и гонять модель, и прочитать из /props путь к файлу весов.
  • --alias <имя файла> — без него сервер отдаёт в /v1/models полный путь к файлу, и model.default в конфиге с ним не совпадает.

Почему окно 64 000 и почему модель именно эта

Требование жёсткое и задано не выбором модели: Digit отвергает окно меньше 64 000 токенов на старте, потому что схемы инструментов и системный промпт занимают большой фиксированный префикс. Замер: системный промпт с одним тулсетом — 7,4 тыс. токенов, со стандартным набором — 18,6 тыс.

Это отсекает почти все «маленькие» модели: Qwen3-1.7B и Qwen3-4B обучены на 32 768, наш собственный роутер — на 40 960. Просить у сервера больше бессмысленно: llama-server обрезает окно слота по обучающему и пишет об этом в лог («the slot context exceeds the training context of the model — capping»), а отключить обрезку нечем.

Qwen3-4B-Instruct-2507 обучена на 262 144 и вызывает инструменты — отсюда и выбор, а не из соображений «поменьше скачивать».

Свой роутер основной моделью быть не может

digit local start --model router

Скачает digit-router-0.6b (424 МиБ) и поднимет его на том же порту, но предупредит о себе при запуске, и предупреждение стоит читать. Роутер обучен на окне 40 960 — меньше требуемых 64 000, и Digit отвергнет такую конфигурацию на старте с явной ошибкой.

Его место вспомогательное: выбрать инструмент и фрагмент корпуса, а не отвечать. Содержание ответа в проверяемом режиме приходит из утилиты, дословной цитаты или сертификата FTS — поставить роутер источником фактов значит потерять ровно то, ради чего он обучался. Разбор устройства — на странице «Как устроена проверяемость».

Живой прогон вспомогательных весов на этой машине: 424 МиБ скачаны за 179,6 с, сервер поднялся за 13,2 с, RSS 2,8 ГиБ при окне 40 960, /v1/models вернул router-0.6b-v3-Q5_K_M.gguf.

Сервер — процесс, а не служба

После перезагрузки llama-server сам не вернётся. Поэтому при обычном запуске digit поднимет его сам — но только если выполнены оба условия: конфиг указывает именно на наш порт и наши веса, и веса с бинарником уже лежат на диске. Ничего не докачивается молча: тихая загрузка на гигабайты при обычном запуске digit — не то, чего от него ждут.

digit local stop останавливает только тот сервер, который запускали мы: PID берётся из своего файла, а не из поиска по процессам.

Чего это стоит

2,3 ГиБ на диске под веса, 40 МиБ под сервер и около 9 ГиБ оперативной памяти при окне 64 000. Без квантованного KV-кэша — почти вдвое больше. Модель на 4B в Q4 на процессоре отвечает приемлемо; кому нужна видеокарта, тот ставит свою сборку и указывает её через DIGIT_LLAMA_SERVER.

Скрипт scripts/local-router.sh остался в репозитории обёрткой над этой же командой — своей логики у него больше нет.

Границы

Качество ответов этой модели на задачах агента мы не измеряли. При подготовке страницы прогонялись установка бинарника, загрузка весов, подъём сервера, /health, /v1/models и остановка. Сквозной прогон агента с вызовом инструмента (read_file) снят разработчиком, не нами, — как и числа на основных весах.

Числа выше сняты на процессорной сборке без ускорителей; на других машинах они другие. Замера на macOS и Windows у нас нет: код собирает ту же команду, но проверялся он на Linux.

Справочник по командам целиком — в английской документации; digit local там пока не описана, эта страница — единственное её описание.

Нашли неточность? Выделите фрагмент текста — рядом появится жучок.

Нужен разбор именно вашей ситуации?

Статья описывает общий случай. Если у вас частный — можно разобрать его отдельно, платно. А если не хватает целого материала, предложите тему: её оплачивают вскладчину, и она выходит открытой для всех.

Доска запросов
Дальше