Настройка локального кодинг-агента: oh-my-pi и Qwen3.8-27B на двух RTX 3090
Настройки кодинг-агента по умолчанию рассчитаны на большую облачную модель с большим запасом мощности. На домашней машине они решают, ответит агент за пять секунд или за пять минут. Автор потратил день, записывая трафик собственных сессий, и нашёл пять параметров, которые дали почти весь прирост скорости. Схема: oh-my-pi (в тексте — omp) как агент, Qwen3.8-27B как модель, vLLM как сервер на двух подержанных RTX 3090.
| Параметр | Было | Стало |
|---|---|---|
| Доля промпта из кэша | 55 % | 95 % |
| Среднее ожидание первого слова | 26–28 с | 7,3 с |
| Худшее ожидание | 514 с | 54 с |
| Самые медленные 10 % ходов | от 390 с | от 77 с |
Локальная модель или облачная
Облачная модель работает в чужом дата-центре, локальная — на вашей машине.
| Локальная модель | Облачная модель | |
|---|---|---|
| Куда уходит код | остаётся на машине | уходит провайдеру с каждым запросом |
| За что платите | железо и электричество сразу | подписка или оплата по токенам |
| Ограничения | сколько выдерживают видеокарты | лимиты запросов и квоты |
| Качество | хорошие открытые модели, на сложных задачах уступают лучшим облачным | самые сильные доступные модели |
| Скорость в длинных диалогах | медленнее и очень зависит от настроек | быстро, провайдер всё настроил за вас |
| Кто следит за работой | вы | провайдер |
| Смена модели | когда вы решите | когда решит провайдер |
Локальный запуск даёт приватность, предсказуемую стоимость и контроль: код не покидает сеть, а лимит запросов не обрывает задачу на середине. Плата за это — качество: фронтир-модели Anthropic и OpenAI намного крупнее того, что помещается под стол. С моделью на 27B параметров результат лучше, когда вы даёте агенту узкую задачу и чаще проверяете его работу. Домашняя конфигурация автора выдаёт 54–60 токенов в секунду, когда видеокарта отдана одному запросу, и 21–37 токенов в секунду, когда запросов несколько.
Префикс-кэш решает почти всё
У языковой модели нет памяти между ходами. Каждый раз сервер отдаёт ей весь диалог целиком: инструкции, описания инструментов, открытые файлы и вывод команд. Сначала модель проходит по этому тексту — это префилл, — а потом печатает ответ по одному токену. Этот этап называют декод.
Представьте человека, который перечитывает длинную ветку письма с самого начала перед каждым ответом. Префилл — это перечитывание, декод — это печать. На домашней видеокарте медленно именно перечитывание: сервер обрабатывает примерно 700–900 токенов в секунду на один запрос, а диалог агента легко доходит до 100 000 токенов. Полное перечитывание занимает от полутора до трёх минут.
Префикс-кэш снимает почти всю эту работу. Сервер сохраняет то, что уже посчитал для начала диалога. Если следующий ход начинается с точно такого же текста плюс новые сообщения, пересчитывать нужно только новую часть. Попавший в кэш ход стартует за секунды, не попавший — за минуты. Поэтому автор выбрал vLLM: он обслуживает несколько агентов из одной копии модели, держит большой префикс-кэш между ними и принимает бюджет размышлений на каждый запрос.
Пять настроек, которые дали основной эффект
- Явный уровень усилия на каждой роли, чтобы запрос не проваливался в самый дорогой режим модели.
- Бюджет размышлений 6000–7500 токенов, который уходит как
thinking_token_budget. maxTokensподнят с 8192 до 32768, чтобы запись файлов успевала закончиться.- Вывод инструментов больше 10 КБ уходит в файл, а не вставляется в диалог целиком.
- Не больше 4 субагентов сразу и режим контекста «только дописывать», который не ломает кэш.
Куда уходило время
Агент думал пять минут, ничего не делая
Qwen3.8 — рассуждающая модель. Перед ответом она пишет приватную цепочку размышлений, а объём размышлений задаёт уровень усилия: low, medium или xhigh. Утром самые медленные 10 % ходов шли по 390 секунд и дольше, а худший — 1110 секунд. Скорость декода была обычной, просто модель производила слишком много текста: 89 % её вывода приходилось на размышления.
Часть маленьких помощников внутри omp вообще не отправляла уровень усилия. Официальный шаблон диалога модели трактует запрос без уровня как xhigh, самый дорогой режим: по тесту сообщества xhigh тратит примерно в 6 раз больше токенов и в 7 раз больше времени, чем medium. При этом уровень усилия — только подсказка в промпте, и ничто не ограничивало длину размышлений.
Помогают обе вещи: явный уровень усилия на каждой роли и бюджет размышлений — жёсткий предел на число токенов размышлений в одном ответе, который omp передаёт серверу как thinking_token_budget. Сервер должен его поддерживать: vLLM умеет, если запущен с парсером рассуждений и с настройкой рассуждений. На значении 64 модель прекращала размышления на 63 токенах и нормально давала ответ.
При уровне усилия medium по умолчанию и бюджете в 6000 токенов:
| До | После | |
|---|---|---|
| Средняя длина ответа | 2481 токен | 867 токенов |
| Самые медленные 10 % ходов | от 390 с | от 77 с |
Позже бюджет подняли до 7500, а субагентам оставили low.
Файлы записывались наполовину
maxTokens — это ограничение длины одного ответа в omp. Оно покрывает и размышления, и видимый ответ, и содержимое любого файла, который агент пишет через инструмент. При бюджете в 6000 токенов на всё остальное оставалось около 2200, а файл примерно на 8000 символов требует 2500–3000 токенов.
Шесть ответов остановились ровно на 8192 токенах посреди записи файла. Сервер отметил их как завершённые вызовы инструментов, а не как обрыв, и omp выполнил сломанный вывод всё равно: два файла сохранились наполовину с сообщением «Successfully wrote», а ещё четыре вызова упали с запутанными ошибками.
Лечение простое: maxTokens = 32768. Размышления по-прежнему ограничены бюджетом, а ответы и запись файлов получают место, чтобы закончиться. В следующем прогоне обрезанных записей не было, включая ответы на 10 286 и 13 964 токена, которые раньше были бы обрезаны.
Вывод инструментов забивал диалог
Всё, что возвращает инструмент, становится частью промпта, который модели нужно перечитать. omp держит результат прямо в диалоге, пока он не больше 50 КБ. В худших ходах это добавляло по три-пять результатов примерно по 40 000 символов и 10–50 секунд префилла до начала ответа.
Снижение порога до 5 КБ не сломало работу: вывод инструментов на ход уполовинился, а полную версию агент забирал только для 14–17 % сокращённых результатов. Сейчас автор держит 10 КБ — и как порог, и как размер начала и конца, которые остаются в диалоге.
Агент «забывал» то, что только что прочитал
Несколько раз в час ход, который должен был почти целиком попасть в кэш, возвращался почти без кэша и минутами думал над первым словом. Кэш работает только когда новый промпт начинается ровно с того же текста: меняется что-то в начале — сервер пересчитывает всё, что после этого места. Таких мест автор нашёл три:
- Субагенты omp получают список других агентов и их статусы (работает, простаивает, отложен) прямо в середине инструкций. Каждая смена статуса меняла промпт в начале. В худшем случае главный агент вернулся через 24 минуты к промпту примерно на 580 000 символов и получил из кэша только 14 %.
- Сжатие контекста — это когда omp пересказывает старую историю, потому что диалог стал слишком длинным. Оно заменяет начало диалога пересказом и по замыслу приводит к полному пересчёту.
- Описания инструментов приходили с полями в разном порядке в 8 % ходов, хотя сами инструменты не менялись. Это оказался баг в Bifrost — шлюзе, который стоит между omp и vLLM.
Живой список статусов отключить нельзя, он встроен в промпт субагента. Зато можно сделать сжатие редким: указать модели полное контекстное окно (262 144 токена для Qwen3.8), чтобы агент не считал диалог заполненным раньше времени, и поднять порог сжатия. И включить appendOnlyContext — его автоматический режим работает только для localhost и приватных IP, так что для сервера с обычным именем нужно ставить on.
Слишком много помощников сразу
По умолчанию omp разрешает до 32 субагентов одновременно. Для облачного сервиса это разумно, но у домашней видеокарты скорость префилла фиксированная: десять субагентов, которые читают большие файлы, закончат работу не быстрее трёх, потому что делят одну и ту же скорость на всех.
В лучшем прогоне сервер был занят всего тремя запросами, а память заполнена максимум на 37 % — запас был. Агенты, которые в основном читают файлы и ищут, проводят время в префилле, поэтому 4 — хорошее начало, а те, что пишут код, можно поднять чуть выше. Плюс длинный таймаут на первое слово ответа: отказ на старте выбрасывает всю работу. А если между omp и сервером есть прослойка, неисправные запросы должен повторять только один слой — два слоя удваивают нагрузку.
Железо, сервер и разделение моделей
Модель Qwen3.8-27B работает на двух подержанных RTX 3090 по 24 ГБ, без NVLink, в обычных слотах PCIe. Это 4-битная версия W4A16 AutoRound, которая помещается на одну карту; автор делит её между обеими. На каждую карту помещается два-три длинных агентских диалога одновременно. Карты ограничены 400 и 450 Вт, вместе это 850 Вт.
Сервером может быть любой с OpenAI-совместимым API. Автор использует vLLM в образе syv-ai для RTX 3090 и дальше считает сервер чёрным ящиком.
omp умеет отправлять разные задачи разным моделям: облачная фронтир-модель планирует и делает сложные ревью, а локальная водит субагентов, которые читают файлы и делают рутинные правки. У себя автор разделил так же, только большую роль играет Qwen3.8 Flash Next — модель постарше в том же семействе, которая ведёт главного агента, планирование и ревью. Она работает на мини-ПК Minisforum MS-S1 Max с 128 ГБ единой памяти под управлением Halogen — закрытого движка, написанного под это семейство моделей.
Маршрутизация идёт через шлюз Bifrost: главный агент, планирование и ревью уходят на Flash Next, субагенты и мелкие задачи — на vLLM с Qwen3.8-27B на двух RTX 3090.
Установка и настройка
Установить omp можно любым из трёх способов:
curl -fsSL https://omp.sh/install | sh
brew install can1357/tap/omp
bun install -g @oh-my-pi/pi-coding-agent
Собственных провайдеров omp читает из ~/.omp/agent/models.yml. Адрес в примере нужно заменить на свой, а id должен совпадать с именем модели, которое отдаёт сервер.
providers:
local:
baseUrl: http://localhost:8000/v1 # OpenAI-совместимый URL
auth: none # локально ключ не нужен
api: openai-completions
models:
- id: qwen3.8-27b # имя модели на сервере
name: qwen3.8-27b
contextWindow: 262144 # полное окно, сжатие позже
maxTokens: 32768 # размышления плюс большие записи файлов
reasoning: true
# только за Bifrost, см. ниже
# headers:
# x-bf-passthrough-extra-params: S0
thinking:
mode: effort
efforts: [low, medium, xhigh]
defaultLevel: medium
compat:
extraBody:
thinking_token_budget: 7500 # требует поддержки сервера
Проверить, что omp видит модель, можно командой omp models local. У автора Bifrost молча выбрасывал thinking_token_budget, пока omp не начал слать заголовок x-bf-passthrough-extra-params, который в конфиге выше закомментирован; когда omp говорит с vLLM напрямую, заголовок убирают. Замечание к редакции от 23 сентября 2026 года: в более ранней версии этой записи был ключ timeout: 900, но такого ключа на уровне модели в omp нет — он просто игнорировался. Работают два таймаута потока в config.yml.
Остальное живёт в ~/.omp/agent/config.yml. Суффикс :level в селекторе роли важнее defaultLevel из записи модели и defaultThinkingLevel, которые применяются только к запросам без уровня. Чтобы отправить роль на облачную модель, вместо её селектора ставят модель того провайдера.
modelRoles:
default: local/qwen3.8-27b:medium # главный агент
plan: local/qwen3.8-27b:xhigh # режим планирования, думает один раз долго
slow: local/qwen3.8-27b:xhigh # ревьюер
task: local/qwen3.8-27b:low # общие субагенты
smol: local/qwen3.8-27b:low # разведка, простые правки
tiny: local/qwen3.8-27b:low # заголовки, память, фон
commit: local/qwen3.8-27b:low # сообщения коммитов
defaultThinkingLevel: low # запросы, которые не покрыл ни одна роль
task:
maxConcurrency: 4 # субагентов одновременно, на одной GPU меньше
tools:
artifactSpillThreshold: 10 # КБ, большие результаты идут в файл
artifactHeadBytes: 10 # КБ начала, которые остаются в диалоге
artifactTailBytes: 10 # КБ конца, которые остаются в диалоге
compaction:
thresholdTokens: 200000 # пересказывать только почти полный диалог
provider:
appendOnlyContext: "on" # только дописывать в диалог
providers:
streamFirstEventTimeoutSeconds: 900 # 15 минут на первое слово
streamIdleTimeoutSeconds: 900 # и на паузы в ответе
Как проверить, что всё работает
Первый ход нового диалога обычно самый медленный: в кэше пока почти ничего нет. Дальше ходы должны стартовать за секунды даже когда диалог вырос больше 100 000 токенов. Если ход в глубине длинного диалога думает минутами, значит изменилось что-то в начале промпта — скорее всего сжатие контекста или новый субагент.
Если сервер показывает долю токенов промпта из кэша, за ней стоит следить: у автора она выросла с 55 % до 95 %, а любое резкое падение стоит разбирать. Ответ, длина которого равна maxTokens в момент, когда агент пишет файл, почти наверняка означает, что файл обрезали.