# Задание A52: локальные модели — замена, надзиратель, пара кодеров с облачным судьёй ## Дата поступления 2026-08-31 (переработано в тот же день: добавлены части 1 и 3) ## База `origin/main` (`17b368a`). ``` git fetch origin --prune git checkout -b antigravity/a52-local-models origin/main ``` В `main` напрямую не пушить. ## Порядок исполнения Два прохода: **Flash** исполняет, **Pro** проводит аудит. Пункт **P0-10** написан для аудитора. Задание из трёх частей, и они идут **строго по порядку**: ``` Часть 1 замена моделей и замеры сдаётся отдельно, дальше по её числам Часть 2 надзиратель локальных моделей Часть 3 пара кодеров и облачный судья ``` Часть 3 планировать **по измеренным числам части 1**, а не заранее: без замера видеопамяти схема не проверяема. Связано с A49 (расстановка субагентов и память) и A51 (аккаунт реально используется). Выполнять после них: надзирателю нужны и место в графе, и работающее назначение. --- ## Что проверено ревьюером — заново не выяснять ### Видеопамять занята почти полностью ``` Qwen3.8-27B кодер @196K 25 488 МиБ qwen3-4b компрессор @32K 5 368 МиБ ────────── 30 856 из 32 768 свободно 1 912 ``` ### Измерено у кандидатов (A45, файлы и контрольные суммы сверены по диску) ``` Qwen3-Coder-30B-A3B @64K 21 368 МиБ 109,6 ток/с 83,3% MoE 30B/3B Qwen3-Coder-30B-A3B @32K 19 640 МиБ 110,2 ток/с Qwen2.5-Coder-32B @64K 27 938 МиБ 29,3 ток/с 83,3% плотная ``` Расход контекста у Qwen3-Coder — около 54 КиБ на токен. ### Качество на стенде из 12 задач (A40, признано) ``` Phi-4-14B 83,3% файл 8,28 ГиБ Qwen2.5-Coder-14B 75,0% файл 8,37 ГиБ Qwen3-4B-2507 83,3% файл 2,33 ГиБ Granite-4.2-8B 66,7% файл 5,16 ГиБ ``` **Расход видеопамяти у 14B при 64К не измерен ни разу.** В A45 их не было, столбец VRAM из A40 непригоден: он собрал занятость всей карты, а не процесса. ### Процессор годится для служебных ролей Замер ревьюера, 32 потока из 72, AVX2: ``` LFM2.5-2.6B промпт 1150,8 ток/с генерация 13,4 ток/с Qwen3-4B промпт 854,2 ток/с генерация 8,7 ток/с ``` Обработка промпта на процессоре быстрая, генерация медленная: она упирается в память и идёт последовательно. ### Сервер отдаёт всё нужное для честной работы Проверено на живом `127.0.0.1:8081`: ``` GET /props → default_generation_settings.n_ctx = 196608, total_slots = 1 POST /tokenize → точное число токенов («def add(a, b): return a + b» = 10) ``` Предел контекста и размер задания **измеряются**, а не прикидываются. ### Чего в коде нет ``` разбиения задачи на части — нет подсчёта токенов для планирования — нет; format_token_count только для показа model_registry.context_window = 128000 — статическое умолчание, а у модели владельца 196608 llama-swap — только ttl, групп нет: держать две модели резидентно не станет ``` ### Поправка к постановке владельца Оркестратор **не переставал** давать задачи локальной модели: ``` local_adapter.classify_error: "timeout", "timed out", "502", "503", "504" → ErrorCategory.TRANSIENT, retry_delay_seconds=2 ``` Профиль не помечается исчерпанным и из цепочки не выбывает. Происходит другое: на каждом запросе локальная модель забирает отведённые Hermes 180 секунд, не успевает, и работу доделывает следующий в цепочке — платный. Чинить надо **подачу работы**, а не возврат в цепочку. ### Физика, которую нельзя обойти **Две модели на одной V100 не работают вдвое быстрее.** Генерация упирается в пропускную способность памяти; две модели делят одну полосу. Вместе они выдадут примерно столько же, сколько одна. Значит «параллельно» здесь означает **два независимых решения**, а не выигрыш во времени. Ускорение в интерфейсе обещать нельзя. Оговорка: у MoE активны три миллиарда из тридцати, полосу они едят иначе. Два MoE могут ужиться лучше — **это гипотеза, её измеряют, а не закладывают**. --- # Часть 1. Замена моделей и замеры ## P0-1. Заменить кодер Поставить `Qwen3-Coder-30B-A3B-Instruct-Q4_K_M` вместо `Qwen3.8-27B`. Основание измерено: **109,6 против 30,3 ток/с**, то же качество 83,3%, и на 4 ГиБ меньше. 1. Контекст **не ниже 65536** — порог отбора у Hermes. 2. Условия запуска взять у нынешнего юнита: `--flash-attn on`, `--cache-type-k/v q8_0`, `--reasoning off`, `--parallel 1`. 3. **Прежний юнит сохранить**; откат одной командой описать и проверить. 4. Скорость и видеопамять замерить **на живой службе**, а не переносить из A45. ## P0-2. Заменить компрессор Поставить `LFM2.5-2.6B` вместо `Qwen3-4B` на порт 8082. 1. **Сначала замерить качество сжатия** на том же наборе, что у нынешнего. Быстрее — не значит лучше; сожмёт хуже, замену не делать и так и написать. 2. Рассмотреть запуск **на процессоре** (`-ngl 0`): освобождает видеопамять, а сжатие — чтение многого и запись малого, где процессор силён. Замерить оба варианта и дать владельцу числа для решения. ## P0-3. Измерить кандидатов в пару Замерить **расход видеопамяти по процессу при 64К** для `Phi-4-14B`, `Qwen2.5-Coder-14B`, `Qwen3-4B-2507`, `Granite-4.2-8B` — через `nvidia-smi --query-compute-apps=pid,used_memory`, а не по занятости карты. Затем **проверить запуском**, какие пары помещаются вместе с компрессором в 32 768 МиБ. Не расчётом. Отдельно замерить, **что происходит со скоростью при одновременной работе двух моделей**: суммарная выработка против одиночной. Это проверка утверждения о полосе памяти, и её результат решает, имеет ли смысл держать пару резидентно. **Часть 1 сдаётся отдельно.** --- # Часть 2. Надзиратель локальных моделей Владелец: «если выбирается локальная модель, должен появляться субагент, который мониторит подачу работы. Если у модели не хватает контекста, он разбивает задачу на куски и подаёт, пока не заработает. Потом формирует память, какой объём давать модели». ## P0-4. Роль надзирателя 1. **Новая каноническая роль** `local-supervisor` в реестре. 2. **Включается автоматически**, когда выбранный профиль локальный (`local`, `llama.cpp`, `ollama`, `vllm`). Вручную назначать не нужно. 3. **Не встаёт между ролью и платным провайдером.** 4. **Надзиратель — не модель, а распорядитель.** Считает, режет, подаёт, наблюдает; работу делает локальная модель. Тратить на него платный вызов нельзя. Основная его работа — счёт и разбор, а не рассуждение: токены считает `/tokenize`, предел даёт `/props`, границы кусков определяются разбором кода. Ставить сюда слабую модель значит сделать надзирателя менее надёжным. ## P0-5. Замер перед подачей, а не догадка 1. **Предел контекста брать у живого сервера** через `/props`. Умолчание `model_registry` = 128000 к модели владельца отношения не имеет. 2. **Размер задания считать через `/tokenize`** — точно. Оценка по символам допустима только запасным путём и должна быть помечена как оценка. 3. **Учитывать место под ответ**: в контекст входят задание, история и ожидаемый ответ. Запас обосновать. 4. **Не выдумывать пределы.** Сервер не ответил — так и записать. ## P0-6. Разбиение и подача 1. **Помещается — подавать целиком.** Резать без нужды вредно: теряется связность. 2. **Не помещается — резать по смысловым границам**: файл, функция, класс, раздел. Посреди выражения — нельзя. 3. **Подавать последовательно**, передавая накопленный результат, и собирать ответ. 4. **Неделимая задача — честный отказ**, а не разрез наугад. 5. **Число попыток ограничено** и настраивается. Бесконечный цикл на единственной видеокарте недопустим. 6. **После исчерпания попыток** — отказ с причиной, дальше обычная отказоустойчивость. Надзиратель **не прячет неудачу**, удерживая работу на локальной модели любой ценой. ## P0-7. Наблюдение за ходом 1. **Видеть, что модель работает, а не висит**: поток ответа или тайминги сервера. 2. **Различать три исхода**: успел, не успел, ответил ошибкой. Сейчас всё сваливается в «таймаут». 3. **Показывать ход** на «Обзоре»: какой кусок из скольких, сколько токенов подано. 4. **Отдельно ловить случай A39**: весь лимит ушёл на рассуждения, ответа нет. Измерено: 1500 токенов за 111 секунд и **ноль символов ответа**; с `enable_thinking: false` — ответ за 11 секунд. Признак — пустой ответ при полном расходе лимита; лечится `request_options`, механизм есть после A39. ## P0-8. Память: какой объём модель тянет 1. **Записывать по каждой модели**: при каком размере получался ответ, при каком нет, сколько занимало, какой кусок оказался рабочим. 2. **Хранить в общей памяти** (`/srv/projects/AI-Memory`, структура после A47). Запись подписывается: модель, когда, по какому заданию. 3. **Использовать при следующей подаче**: начинать с размера, который уже работал. 4. **Привязывать к имени сборки из метаданных GGUF**, а не к порту или имени профиля. Урок Tiel-Coder: в файле оказалась `Ornith-1.5-35B`. 5. **Показывать во вкладке «Память»** надзирателя, что он усвоил. --- # Часть 3. Пара кодеров и облачный судья Планировать **по числам части 1**. ## P0-9. Схема и её цена ``` задание → Кодер A (локальный) ┐ → Кодер B (локальный) ┘→ судья (облачная модель) ├ принято → дальше по конвейеру └ не принято → обоим на доработку ``` 1. **Кодеры не видят работу друг друга** до суда. Иначе второе решение не независимо и смысл теряется. 2. **Судья облачный**, видеопамяти не занимает. Это роль `developer-2` существующего конвейера; модель задаёт владелец в интерфейсе — **зашивать имя модели или провайдера в код нельзя**. 3. **Судья получает задание и оба решения**, возвращает: какое принято либо что доработать каждому. 4. **Ревьюер остаётся на своём месте** после судьи; конвейер не переделывать. 5. **Пара включается настройкой**; возврат к одному кодеру возможен. **Предел итераций обязателен.** Круг «пока не сделают правильно» тратит платную квоту судьи на каждом обороте. 6. **Предел кругов** настраивается, умолчание обосновать. Механизм ограничения итераций в конвейере уже есть — использовать его. 7. **Показывать номер круга.** 8. **Круги исчерпаны — честный отказ** с последним состоянием обеих работ и мнением судьи. Частичный результат за готовый не выдавать. 9. **Считать расход**: сколько вызовов судьи ушло на задачу. 10. **Круг без изменений — застревание.** Оба вернули то же, что и в прошлый раз — прекратить и сказать. --- ## P0-10. Аудит вторым проходом 1. **Числа части 1 сняты на живой службе**, а не перенесены из A45. 2. **Откат к прежнему кодеру** выполнен и проверен. 3. **Пара проверена запуском**, а не расчётом: обе модели подняты, памяти хватило, обе отвечают. 4. **Утверждение о полосе памяти** проверено: суммарная выработка двух моделей против одиночной. Результат записать, каким бы он ни был. 5. **Заведомо большая задача** разбита, подана и собрана; **неделимая** дала честный отказ. 6. **Предел попыток и предел кругов** проверены задачей, которая не выполнится никогда. 7. **Предел контекста взят у сервера**, счёт токенов сверен с `/tokenize` независимо. 8. **Независимость кодеров**: решение одного не попадает в контекст другого. 9. **Модель судьи задаётся из интерфейса**, а не зашита. 10. **Для платных провайдеров путь не изменился**, надзиратель туда не лезет. 11. **Службы владельца вернуть в рабочее состояние.** 12. **Побочные изменения** объяснить. 13. **Пропущенный пункт назвать пропущенным.** --- ## Ограничения - Видеокарта одна, сервер рабочий: окна для замеров согласовать, службы возвращать в строй. - Прежние юниты сохранять, откат описывать и проверять. - Учётные данные и `~/.hermes/agy_profiles/` не трогать. - Имена моделей и провайдеров в код не зашивать. - Конфигурацию Hermes не править. - Версию `0.1.1` не поднимать. - Правило честности без исключений: ни одного числа без замера; ускорение не обещать без подтверждения. ## Критерии приёмки **Часть 1** 1. Кодер заменён на Qwen3-Coder-30B-A3B, контекст не ниже 65536; скорость и видеопамять замерены на живой службе; откат проверен. 2. Компрессор замерен в обоих вариантах; замена сделана либо обоснованно отклонена. 3. Видеопамять четырёх кандидатов при 64К измерена по процессу. 4. Проверено запуском, какие пары помещаются; измерено, что со скоростью при одновременной работе. **Часть 2** 5. Роль `local-supervisor` включается автоматически для локальных профилей; для остальных путь не изменился. 6. Предел контекста берётся через `/props`; размер задания считается через `/tokenize`; проверено. 7. Большая задача разбивается по смысловым границам и собирается; неделимая даёт отказ. 8. Число попыток ограничено; бесконечного цикла нет. 9. Ход виден владельцу; случай «весь лимит на рассуждения» распознаётся отдельно от таймаута. 10. Рабочий объём записан в общую память с привязкой к имени сборки GGUF и используется при следующей подаче. **Часть 3** 11. Пара работает независимо; облачный судья сравнивает и возвращает на доработку. 12. Модель судьи задаётся из интерфейса. 13. Предел кругов работает; застревание распознаётся; расход вызовов судьи показан. 14. Пара включается и отключается настройкой. **Общее** 15. Неизмеренное показано как `Н/Д` с причиной; неудачи не скрываются. 16. `ruff check .` чисто; релизный гейт 10/10; тестов не меньше **517**. 17. Службы владельца работают. 18. Память проекта в AI-Memory обновлена. 19. Отчёт: `START_HEAD`, `FINAL_HEAD`, `origin/main`, `git status`, `X passed / Y skipped / Z failed`. ## Главное Сейчас локальная модель получает задачу целиком, не успевает за отведённое время и отдаёт работу платному провайдеру. Так на каждом запросе: она не выбывает из цепочки, она просто всякий раз проигрывает. Замена кодера окупается сама по себе — вчетверо быстрее при том же качестве и на четыре гигабайта меньше. Надзиратель делает подачу работы соразмерной модели: измеряет, а не предполагает, режет по смыслу и запоминает рабочий объём. Пара кодеров с облачным судьёй добавляет вторую независимую попытку — но её ценность в разных ошибках, а не в скорости, и каждый круг доработки стоит платного вызова. ## Порядок сдачи Передать точный `FINAL_COMMIT_SHA`.