Ollama настройка памяти для мульти-LLM роутинга в Гермес-боте
У меня уже несколько месяцев крутится Гермес-бот - автономный CLI-агент, который пишет код, ищет в интернете и выполняет задачи. Изначально он работал на одной модели - Claude Opus 4.8 через подписку, и всё было неплохо, пока я не упёрся в две проблемы: дорого гонять Opus на простые задачи, а на сервере уже стоит ollama, настройка которой давно отлажена под другие проекты, и эти локальные модели хотелось задействовать. Возник логичный вопрос - а можно ли подключить к боту 2-3 модели сразу, чтобы он сам выбирал нужную под конкретную задачу?
Спойлер: можно, причём гораздо проще, чем я думал. Готового роутера писать не пришлось — нужный механизм уже встроен в Гермес из коробки. Расскажу, как я к этому пришёл, что пробовал, на какие грабли наступил и что получилось в итоге.
Что было на старте
Исходная архитектура выглядела так. На сервере живёт два слоя. Первый — это llm-router (порт 3200, отдельный домен), который работает как control plane: принимает запросы от моих проектов по Bearer-токену и проксирует их на OAuth-подписки разных провайдеров. Второй слой — сам Гермес, агентский CLI, поднимаемый через hermes_cli gateway run. Конфигурация модели лежит в ~/.hermes/config.yaml, и там уже был прописан Codex GPT-5.5 как основная модель (Opus 4.8 я держал отдельно для других сценариев).
Параллельно с этим на сервере крутится Ollama в Docker-контейнере, и через неё стабильно работает локальная Gemma 4 e4b (~12 ГБ в RAM, держится в памяти 24 часа) — её активно использует проект GPT WEB. Свободной памяти на сервере хватало: 125 ГБ ECC, из них ~93 ГБ обычно простаивают.
Итак, цель сформулирована: подключить к Гермесу локальную Qwen-кодовую модель в дополнение к облачному Codex, чтобы тяжёлые массовые задачи (типа переводов или классификации) уходили на локалку, а оркестрация и сложные рассуждения оставались на платной модели. И всё это — без переписывания бота с нуля.
Главное открытие: роутер уже встроен
Первая идея была традиционной — поднять отдельный роутер по правилам. Что-то вроде: если в запросе упоминается код — отправляй в Claude Sonnet, если нужен быстрый ответ — в Haiku, картинки — в GPT-4o. Это известный паттерн, его обычно реализуют либо классификацией по ключевым словам, либо лёгкой моделью-диспетчером (типа Haiku 4.5 или GPT-4o-mini), которая смотрит запрос и решает, куда его направить дальше.
Но когда я полез в исходники Гермеса, обнаружилась вкусная штука. У агента уже есть встроенный инструмент delegate_task — он умеет запускать суб-агентов. Из комментария в коде: "routing subagents to a different provider:model pair (e.g. cheap/fast model … while the parent runs on …)". Это ровно то, что нужно: оркестратор-модель сама решает, какие подзадачи нужно делегировать, и спускает их вниз на более дешёвую/быструю модель.
Есть, правда, нюанс, который я выяснил, прочитав схему инструмента: per-task поля model/provider в нём НЕ выставляются. То есть оркестратор не может выбрать модель отдельно для каждой подзадачи — все суб-агенты ходят на одну «рабочую» модель, заданную в конфиге через секцию delegation. Из коробки получается схема «Codex GPT-5.5 как оркестратор → массовая работа на одну локальную модель». Это всё равно покрывает 90% моих сценариев, поэтому я решил не городить кастомный код, а воспользоваться штатным механизмом.
Для сравнения, если бы я хотел свободный выбор между Qwen и Gemma под каждую подзадачу, пришлось бы писать собственный воркер с маленькой моделью-диспетчером поверх API. Возможно, потом — пока этот сценарий не нужен.
Настройка делегирования
Конфиг получился элегантным. В ~/.hermes/config.yaml я добавил секцию:
delegation:
provider: ollama
model: qwen3-coder:30b-128k
base_url: http://localhost:11434Перед правкой сделал бэкап (config.yaml.bak-before-qwen-...) — конфиги — это та часть, которую жалко терять при автоматизированной миграции.
Далее — дымовой тест. У Гермеса есть одноразовый режим запуска через флаг -z, который позволяет прогнать промпт без интерактивной сессии. Я запустил тестовый промпт с явным указанием делегировать перевод суб-агенту — и убедился, что вызов реально уходит на локальный Qwen через Ollama-эндпоинт. Роутинг сработал.
Но вылезла другая проблема: Гермес требует контекст ≥64k для надёжного использования инструментов (tool-use). А базовая Qwen3-Coder в Ollama была загружена с num_ctx: 32768. Нативно модель поддерживает до 256k — нужно было просто поднять параметр контекста.
Танцы с памятью: 256k vs 128k vs 64k
Тут начались интересные открытия про то, как Ollama расходует RAM. Когда модель крутится на CPU без GPU (а это наш случай), весь KV-кэш контекста держится в обычной оперативке. Поэтому контекст линейно ест память.
Я создал вариант qwen3-coder:30b-256k через docker exec в Ollama-контейнер. Веса от базовой модели переиспользовались автоматически — Ollama хранит блобы по content-addressing, дублирования файлов нет. И тут получил OOM: для 256k нужно 65.9 ГБ, а свободно было 63.9.
Реальные цифры памяти для Qwen3-Coder 30B на CPU:
| Контекст | RAM нужно | Помещается с Gemma (12 ГБ)? |
|---|---|---|
| 64k | ~30 ГБ | ✅ с большим запасом |
| 128k | ~42 ГБ | ⚠️ влезает, но параллельные задачи поджимают |
| 256k | ~66 ГБ | ❌ OOM |
Дополнительный нюанс: переменная окружения OLLAMA_NUM_PARALLEL=2 означает, что каждая модель держит KV-кэш на 2 одновременных запроса. То есть память за контекст удваивается. А OLLAMA_MAX_LOADED_MODELS=2 — это лимит на количество одновременно держащихся в памяти моделей. Учитывая, что Gemma регулярно используется проектом GPT WEB и должна оставаться загруженной, я остановился на варианте qwen3-coder:30b-128k как разумном компромиссе.
Урок из инцидента с Gemma
По ходу настройки я допустил ошибку, которая стоила мне разговора с пользователем. Чтобы освободить RAM под тест 256k-варианта Qwen, я послал запрос с keep_alive=0 для Gemma — это команда Ollama API, которая выгружает модель из памяти. Модель при этом не удаляется — файл остаётся на диске. Но Gemma активно работает в продакшене для проекта GPT WEB, и любая пауза в её доступности — это потенциальные ошибки у пользователей.
Правильный паттерн — не трогать чужие загруженные модели даже временно. У Ollama есть параметр OLLAMA_MAX_LOADED_MODELS именно для того, чтобы можно было настроить лимит и не пересекаться. Если бы я лучше изучил документацию Ollama до начала экспериментов, ошибки можно было бы избежать. Запомнил на будущее: перед любыми манипуляциями с runtime окружения, которое разделяется между несколькими проектами, нужно проверять, что и кем используется. Это базовый принцип безопасности, но в моменте про него легко забыть, когда фокус на одной задаче.
Что делать дальше: поиск более сильной кодовой модели
Qwen3-Coder 30B — хороший рабочий вариант, но я искал что-то с уровнем рассуждений, приближенным к GPT-5.5 или Claude Opus 4.8. Чисто плотные большие модели типа Qwen3.6 dense или DeepSeek-V на CPU без GPU будут «думать» по минуте на запрос — это неприемлемо для интерактивного бота.
Золотое правило для CPU-инференса: скорость зависит от количества активных параметров, а не общего размера модели. Поэтому MoE-архитектуры (Mixture of Experts) выигрывают у плотных моделей того же размера в разы.
При поиске свежих моделей на Hugging Face обнаружилась интересная штука — Qwen3-Coder-Next (релиз 30 января 2026, почти миллион скачиваний). Архитектура qwen3_next: 80B общих параметров, 512 экспертов из которых активны 10 (~3B активных параметров на запрос). При полном объёме знаний 80B-модели работает на скорости 3B. Это идеально ложится на сервер с большим количеством RAM и без GPU.
Перспектива такая: один большой инструмент с архитектурой MoE может закрыть и кодовые задачи, и сложные рассуждения, оставив за оркестратором роль координатора. По сути, локальный «гибрид», аналог того, что у меня сейчас в облаке через Codex.
Чистка лишних моделей
По ходу экспериментов я насоздавал кучу вариантов: qwen3-coder:30b, qwen3-coder:30b-32k, qwen3-coder:30b-128k, qwen3-coder:30b-256k. Когда я грепнул по реально работающим конфигам сервера, оказалось, что базовые теги 30b и 30b-32k нигде не используются — только в статических каталогах моделей UI-проекта. То есть их можно удалить.
Важная деталь: поскольку все варианты создавались FROM qwen3-coder:30b, веса у них общие через content-addressing. Удаление тега — это просто удаление имени-указателя, физические файлы остаются на диске, пока на них ссылается хотя бы один тег. Так устроены все системы версионирования образов — Docker работает по тому же принципу.
Результат и выводы
Чего удалось добиться. Гермес-бот теперь работает в гибридном режиме: оркестрация и сложные рассуждения уходят на Codex GPT-5.5 через OAuth-подписку, а массовые делегированные задачи (переводы, обработка списков, классификация) — на локальный Qwen3-Coder 30B с контекстом 128k. При этом Gemma продолжает обслуживать проект GPT WEB. Всё уживается в 125 ГБ RAM при разумной нагрузке.
Главный архитектурный вывод — не торопиться писать своё, если штатный механизм закрывает задачу. Я уже мысленно проектировал отдельный сервис-роутер, классификатор запросов, балансировку — а в итоге решение оказалось в одной секции YAML и встроенном инструменте delegate_task. Прежде чем городить инфраструктуру, стоит внимательно прочитать исходники того, что уже есть. Это особенно справедливо для зрелых агентских фреймворков, в которые уже зашит опыт построения многомодельных систем.
Второй вывод — про планирование памяти при работе с локальными LLM. На CPU всё держится в RAM, контекст линейно умножается на OLLAMA_NUM_PARALLEL, и нужно явно считать, сколько влезет. Простая таблица «контекст × лимит параллельности = реальная стоимость в RAM» помогает не нарваться на OOM в продакшене. Из этого же следует, что нативный максимум модели (например, 256k у Qwen) — это не то, на чём нужно её запускать. Реалистичный контекст определяется не моделью, а железом.
Третий урок касается общей среды. Когда на сервере живут несколько проектов, использующих одни и те же ресурсы (Ollama, GPU, RAM), любое действие нужно сверять с тем, что у других. Выгрузить модель «на минутку для теста» — звучит безобидно, но если эта модель прямо сейчас обслуживает чьи-то реальные запросы, минутка становится инцидентом. Правильная практика — настраивать лимиты Ollama (OLLAMA_MAX_LOADED_MODELS, OLLAMA_KEEP_ALIVE) явно и в конфиге, а не в моменте через ad-hoc API-вызовы.
Четвёртое — про выбор моделей. Тренды на Hugging Face показывают, что для CPU-инференса будущее за MoE-архитектурами. Открытие Qwen3-Coder-Next с его 80B/3B соотношением — наглядный пример. Если у вас сервер без GPU и много RAM, имеет смысл смотреть в первую очередь именно на такие модели, а не на плотные гиганты. Это меняет привычное правило «больше параметров = лучше» — теперь скорее «больше знаний на единицу активных параметров = лучше».
В ближайшее время планирую попробовать Qwen3-Coder-Next и заменить им текущий 30B-вариант. Если на CPU удастся держать стабильные ~10 секунд на запрос с качеством, сопоставимым с облачными моделями — можно будет ещё сильнее сместить баланс на локальную инфраструктуру. А облачные модели оставить там, где они правда нужны: для оркестрации, поиска в интернете и задач, требующих самого свежего знания мира.

AI-инженер, предприниматель, маркетолог. Основатель feberra.com и x10seo.ru. 13 лет в перфоманс-маркетинге, 3 года в системной интеграции AI в бизнес.