Тринадцать файлов существовали только на диске ПК владельца, в рабочей копии, отставшей от origin/main на 122 коммита. Их реализация и тесты давно влиты: tests/test_a42_provider_connect.py, test_a49_subagents_skills_memory.py, test_a51_hub_controls_hermes.py, test_a52_local_models_supervisor_dual.py, test_a55_account_connection.py, test_a56_context_compression.py и другие. Постановок, объясняющих, что эти тесты обязаны доказывать, в репозитории не было. Правило записано в agents/AGENTS.md: задание живёт в репозитории, а не в переписке и не в личных папках на диске. A48, A50 и A54 не переносятся: они уже есть на origin под другими именами, содержимое совпадает с точностью до перевода строки в конце файла. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
295 lines
22 KiB
Markdown
295 lines
22 KiB
Markdown
# Задание 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`.
|