Berth — брокер аренды видеопамяти для локальных ИИ-моделей

· 7 мин чтения
gpu vram local-llm self-hosted devops
📂 Исходный код на GitHub

Брокер аренды видеопамяти для NVIDIA: локальные ИИ-движки запускаются через berth run и получают карту по очереди с приоритетами, играм и creative-приложениям соответствуют отдельные правила, есть шлюз с OpenAI-совместимым API, веб-панель harbor и сборка моделей с Hugging Face и CivitAI. Написано на Go 1.27, лицензия Apache-2.0.

Berth — брокер аренды видеопамяти для локальных ИИ-моделей

Berth — брокер аренды видеопамяти для локальных ИИ-моделей

На одной видеокарте NVIDIA часто хотят работать сразу несколько программ: чат-движок, генератор картинок, синтез речи, иногда игра. Но они не умеют делиться памятью. Кто загрузился первым, тот занял всю VRAM, а остальные падают с ошибкой «out of memory».

Проект Berth ставит между движками и картой посредника. Он выдаёт видеопамять по очереди и ждёт, пока прошлый владелец её освободит. Это один бинарник на Go 1.27: без облачного аккаунта, без базы данных, без менеджера пакетов. Лицензия Apache-2.0, статус — alpha (v0.2).

Как проходит передача карты

Каждый движок спрашивает у брокера разрешения. Это делает команда berth run, шлюз, API ComfyUI или запрос на игровую сессию. Дальше всё идёт по четырём шагам:

  1. Запрос встаёт в очередь по приоритету: hold, foreground, interactive, batch.
  2. Текущий владелец заканчивает свой ответ или задание, после чего Berth его выгружает.
  3. Драйвер NVIDIA показывает, что память действительно освободилась. Просто завершившийся процесс может ещё какое-то время держать VRAM, поэтому Berth ждёт подтверждения от драйвера, а не просто выхода процесса.
  4. Аренда выдаётся следующему движку, и запрос, который ждал, начинает отдавать результат.

Выгрузка тоже идёт в три захода: сначала штатная команда движка (у llama-swap — это его собственный unload-URL), затем SIGTERM всей группе процессов, и только потом SIGKILL. Между шагами есть пауза, чтобы процесс успел завершиться сам.

Если процесс занимает видеопамять, но аренды не имеет, Berth показывает его отдельно: PID, имя и объём памяти. Убивать такой процесс он не станет.

Приоритеты и отказы

Запрос равного или более высокого приоритета просит текущего владельца освободить карту. Запрос меньшего приоритета ждёт и получает отказ с указанием, кто именно держит карту (код выхода 75). Падения с out of memory при этом не происходит.

Отдельно решается проблема «пинг-понга». Если два клиента одновременно просят разные движки, карта начинает прыгать туда-сюда, и оба запроса падают. Berth считает это «жонглированием» и запрещает его: рейтинг движков решает, кто победит, а проигравший просто ждёт. Рейтинг задаётся в конфиге:

[priority]
order = ["llama-swap", "strata", "comfyui"]
juggle_window = "15s"

Если порядок не задан, первым идёт тот движок, который провёл на карте больше всего часов за последнюю неделю.

ComfyUI передаётся на границе задания: Berth дожидается, пока очередь картинок опустеет, и только тогда освобождает карту. Работающий процесс он не обрывает.

Если демон перезапускается или обновляется, запущенные движки остаются живы и снова привязываются к брокеру по PID и времени старта.

Какие движки брокер находит сам

Berth опрашивает стандартные порты и читает состояние движков:

Движок Порт по умолчанию Что читает
llama.cpp (llama-server) 8080 токены, скорость, очередь, окно контекста, слоты KV-кэша
llama-swap 8080 запущенные модели, журнал запросов, окно контекста по моделям
Ollama 11434 загруженные модели и их видеопамять
vLLM 8000 модели, окно контекста, очередь из /metrics
SGLang 30000 модели, окно контекста, очередь
ComfyUI 8188 выполняющиеся и отложенные задания
Kokoro-FastAPI 8880 состояние, модели, голоса
LM Studio 1234 загруженные модели, длина контекста
KoboldCpp 5001 модель, длина контекста
TabbyAPI 5000 модели, длина контекста
LocalAI 8080 модели, токены

Любой OpenAI-совместимый сервер тоже подходит. Свой сервер можно описать вручную в конфиге — с любым портом и любым адресом. Если движку нужен ключ, в конфиге указывается имя переменной окружения, а сам ключ туда не попадает.

Игры и тяжёлые приложения

Berth умеет отдавать карту игре. Пока вы в Steam, Lutris или gamescope на Linux (или в игре из Epic, GOG, EA, Ubisoft, Xbox, Battle.net на Windows), ИИ-движки выгружаются, и карта ждёт вас. На 15 секунд дольше, чтобы лаунчер не начал «пинг-понг» с игрой.

Приложения для работы с видео и графикой обрабатываются мягче. Blender, DaVinci Resolve, OBS, Kdenlive, Premiere и After Effects не выгружаются. Berth просто считает, сколько видеопамяти они держат, и зачитывает это в общий счёт.

Свои правила описываются в berth.toml:

[[claim]]
name = "training"
cmdline = 'train\.py'
action = "hold"
linger = "30s"

Команда berth hold освобождает карту под ваш звонок или игру, которую вы запускаете сами. Ctrl-C возвращает карту. А berth run --pinned даёт приложению аренду, которую нельзя прервать, — она живёт до выхода процесса.

Один порт вместо десяти

Команда berth gateway поднимает один OpenAI-совместимый порт. Вы называете модель в запросе, и Berth сам запускает нужный движок, выгружает того, кто сейчас держит карту, ждёт загрузки и отдаёт вам поток ответа. Схема простая:

{ "baseUrl": "http://127.0.0.1:9799/v1", "apiKey": "!berth key" }

Шлюз умеет и картинки: запрос в ComfyUI ставится в ту же очередь и ждёт ту же аренду. Есть «пробуждающие» страницы: закладка на веб-интерфейс остановленного движка показывает, кто держит карту, и кнопку Start. Сама страница карту не занимает — кнопка запрашивает аренду.

На всех маршрутах требуется bearer-ключ, слушает шлюз только локальный интерфейс, а запросы с браузерным Origin отклоняются: с сайта нельзя запустить движок на вашей машине.

Замеры на опорной RTX 4090 при переключении между NInfer через llama-swap и Strata с контекстом 262K:

Переключение Освобождение карты Ответ после
Strata на пустой карте — 106 с
Strata → NInfer Strata освободил за 2 с 41 с
NInfer → Strata NInfer освободил за 0,6 с 110 с

Ни в один момент два движка не держали карту одновременно.

Модели

berth pull hf:org/repo:Q4_K_M скачивает модель с Hugging Face, а berth pull civitai:VERSION_ID — с CivitAI. Каждый файл сначала качается во временное имя и переезжает на место только после того, как размер и sha256 совпали с хешем, который опубликовал сайт. При несовпадении файл удаляется и команда падает. Прерванную загрузку можно продолжить повторным запуском команды.

Перед скачиванием Berth проверяет свободное место в каталоге и отказывается работать, если после загрузки останется меньше доли, заданной в конфиге (по умолчанию — десятая часть). Каталоги моделей делятся на полки: чат, картинки, звук. Команда berth models читает заголовки GGUF-файлов и показывает, что именно лежит на диске, с квантизацией, размером, числом параметров, окном контекста и датой последнего использования. berth storage показывает свободное место по каждому каталогу, berth verify перепроверяет хеши офлайн, а berth models export ollama превращает уже скачанный GGUF в модель для Ollama.

Наблюдение

berth harbor открывает веб-панель на 127.0.0.1:9798. В ней видна доска аренд, счётчик предотвращённых столкновений, график видеопамяти по владельцам, лента событий и таблица движков. Панель работает только на чтение и по умолчанию доступна лишь с самой машины.

Телеметрия встроенная: память, загрузка, температура, питание и частоты по каждой карте, токены в секунду, очередь, задержки в проценталях, загрузка KV-кэша, счётчик токенов и таблица лидеров по моделям. История за 30 дней лежит на диске. Есть пороговая система оповещений, /metrics для Prometheus и экспорт в CSV или JSON. Сам демон ест около 0,2% ядра и 21 МБ памяти, панель — около 0,4% ядра и 23 МБ.

Весь интерфейс командной строки устроен одинаково: --format json|yaml, результат в stdout, диагностика в stderr, коды выхода 0, 1 и 2. Команда berth skill install ставит агентам инструкцию просить карту у Berth, а не убивать процессы.

Установка

Готовый бинарник или сборка из исходников:

go install github.com/T-Crypt/berth/cmd/berth@latest

mkdir -p ~/.config/systemd/user
cp contrib/berthd.service contrib/berth-harbor.service ~/.config/systemd/user/
systemctl --user daemon-reload
systemctl --user enable --now berthd berth-harbor

Дальше нужно обернуть команды запуска движков:

berth run --as <имя> --priority <batch|interactive|foreground> -- <команда движка>

Готовые рецепты для llama-swap, llama.cpp, Ollama, ComfyUI, vLLM, SGLang и своих серверов лежат в contrib/INTEGRATION.md. Есть и режим установки через агента: можно скормить коду агенту текст из README, и он сам всё поставит, показав список изменений и способ их отменить.

Ограничения

Поддерживается Linux с картой NVIDIA. Windows 10 и 11 работают в экспериментальном режиме: аренда, определение процессов и игровые правила есть, но драйвер под WDDM не показывает память по процессам, поэтому там нужны аренды на всю карту. Карты AMD, Intel и Apple пока не поддерживаются, а на несколько GPU пока работает один демон на одну карту.

Чего Berth не делает

Он не убивает процессы, которые не запускал. Он не выдаёт карту только потому, что процесс завершился, — драйвер должен подтвердить освобождение памяти. Он не считает память занятой, если она лишь предположительно свободна. И он не требует облачного аккаунта и не отправляет телеметрию наружу.

Лицензия — Apache-2.0. Текст и условия лежат в LICENSE, сторонние компоненты — в THIRD_PARTY_NOTICES.

Источник: https://github.com/T-Crypt/berth