Eva: оркестратор локальных LLM без единой строчки Python

· 4 мин · BadRabbit

Сегодня главный пост этого блога. Ради Евы, по сути, всё и затевалось: остальные проекты либо выросли из неё, либо помогают её делать.

Eva — это демон, который управляет несколькими локальными языковыми моделями сразу. Пользователь отправляет задачу, Eva решает, какие модели нужны и в каком порядке, загружает их в видеопамять, гоняет между ними данные и собирает ответ. Всё это на одной видеокарте с 8 ГБ памяти и без единого запроса в интернет.

Зачем, если есть Ollama

Ollama и llama.cpp отлично запускают одну модель. А мне хотелось конвейер: маленькая быстрая модель разбирает запрос, средняя собирает контекст, ещё одна строит план. Как только моделей становится несколько, всплывают три проблемы:

  1. Связь между ними. Если агенты разговаривают по HTTP и JSON, каждый шаг стоит сериализации, копирования и сетевого стека. Для пайплайна из пяти шагов это заметно.
  2. Память. Пять моделей в 8 ГБ не помещаются. Кто-то должен решать, кого выгрузить, а кого держать.
  3. Безопасность. Модель, которой дали инструменты, рано или поздно попробует сделать что-то странное. Сама по себе или потому, что её попросили в промпте.

Eva решает все три.

Как это устроено

Проект собран из четырёх крейтов на Rust:

КрейтЧто делает
coreДемон: планировщик DAG, движок контекста с RAG, менеджер воркеров
shared-ipcОбщая память, атомарные заголовки состояния, кольцевые буферы
worker-candleИзолированный процесс инференса на HuggingFace Candle
eva-apiШлюз: REST и SSE снаружи, gRPC по Unix-сокету внутрь
Ядро, шлюз и воркеры. Пунктир — данные в движении, жёлтая шина — общая память.
Ядро, шлюз и воркеры. Пунктир — данные в движении, жёлтая шина — общая память.

Общая память вместо HTTP

Воркеры и ядро обмениваются сырыми байтами через разделяемую память. Внутри лежат кольцевые буферы «один пишет, один читает» (SPSC), которым не нужны блокировки. Структуры описаны с #[repr(C)], поэтому раскладка в памяти фиксирована и одинакова для всех процессов. Подробно об этом слое я писал 10 октября.

Каждый воркер в клетке

Любой процесс с моделью запускается внутри Bubblewrap:

bwrap --ro-bind / / --bind /dev/shm /dev/shm --bind /tmp /tmp --unshare-net -- ./worker-candle

Файловая система доступна только на чтение, кроме общей памяти и /tmp, и сети нет совсем. (Про /tmp честный разговор был в посте про пространства имён: там дыра, которую я ещё закрываю.) Если bwrap в системе не найден, Eva не запустится. Это сделано специально: тихо работать без защиты хуже, чем честно упасть.

Планировщик, который умеет вытеснять

Задача превращается в граф шагов (DAG). Порядок выполнения выбирается по формуле WSJF, Weighted Shortest Job First: сначала то, что ценнее всего на единицу затраченного времени. Время каждой модели Eva оценивает по скользящему среднему (EMA) прошлых запусков.

Когда модели не хватает видеопамяти, ядро отправляет кому-то из соседей сигнал UnloadWeights. Воркер выгружает веса и освобождает VRAM, но процесс остаётся живым и готовым загрузиться обратно.

Модели

Веса загружаются в формате GGUF с квантованием Q4_K_M, архитектура определяется по метаданным файла. Для каждой роли своя модель:

  • Узел Зеро (gemma-3-1b): за ~50 мс решает, пойдёт запрос по быстрому шаблону или по глубокому пути.
  • Context Architect (aya-expanse-8b): собирает контекст и хорошо дружит с русским языком.
  • Pipeline Architect строит из собранного план выполнения.

В последних коммитах появились бенчмарк моделей на CPU, GPU и в гибридном режиме, замер задержки при подмене весов, перевод NLLB-200 в Safetensors и сквозные gRPC-тесты.

Стеклянная панель

У Евы есть веб-интерфейс на Svelte, о нём был отдельный пост. Он полностью отделён от ядра: можно закрыть вкладку, удалить фронтенд, а демон продолжит работать, не потеряв ни байта контекста.

Что дальше

  • Довести Deep Track до автономного цикла «собрать контекст → построить план → выполнить → проверить».
  • Подключить Persona, чтобы у Евы появилось настроение, влияющее на тон ответов (но не на факты).
  • Подключить llm-wiki как источник правил по MCP.

Чем меньше у тебя железа, тем честнее приходится думать об архитектуре. 8 ГБ VRAM научили меня большему, чем любой курс.