Перейти к содержанию
Публикация AiManual

Один агент за раз: как запускать супервизора и субагентов на слабой GPU без потери KV-кэша

Разбираем, почему параллельные агенты убивают KV-кэш и prefix caching на двух Tesla P40, какие цифры реально даёт Qwen 3.8 27B при одном процессе и как собрать

Коротко

Что будет в материале

  1. 01

    Почему на слабой GPU выгоднее один агент за раз

  2. 02

    Реальные цифры: что дают две Tesla P40 с Qwen 3.8 27B

  3. 03

    Как устроена последовательная оркестрация: супервизор, субагент и handoff

  4. 04

    Существует ли готовый harness для последовательного запуска агентов

Почему на слабой GPU выгоднее один агент за раз

На двух Tesla P40 с Qwen 3.8 27B параллельный запуск трёх агентов (оркестратор, фиксер, оракул) разрушает общий KV-кэш и prefix caching. Отклик теряет почти мгновенный характер, потому что GPU снова и снова пересчитывает то, что до этого брала из кэша. При ограниченной VRAM рабочим остаётся один режим: строго последовательный, один активный процесс за раз.

Причина в арифметике памяти. У каждой Tesla P40 24 ГБ VRAM. Веса Qwen 3.8 27B в квантизации съедают заметную часть бюджета, и на KV-кэш остаётся меньшая доля. Каждый дополнительный параллельный процесс просит свою порцию этого остатка, а запас тает быстро.

Как KV-кэш и prefix caching ускоряют инференс

Каждый слой внимания считает три тензора: запросы (Q), ключи (K) и значения (V). Для очередного токена модель не обязана пересчитывать K и V всех предыдущих токенов, достаточно держать их в памяти. Это и есть KV-кэш. Без него генерация n-го токена требовала бы повторного прохода по всей последовательности, и время на токен росло бы квадратично.

Prefix caching идёт дальше. Если два запроса начинаются одинаково (системный промпт, описание инструментов, шапка файла), вычисления для общего префикса переиспользуются. Аналогия простая: вместо того чтобы разбирать документ заново, вы оставляете закладку и продолжаете с неё. В llama.cpp этим поведением управляют параметр cache_prompt в запросе к серверу и настройки слотов (--parallel).

На слабой GPU экономия вычислений критична: у Pascal нет тензорных ядер, и всё упирается в FP32-производительность и пропускную способность памяти. Потеря кэша бьёт именно по этим двум ресурсам. Насколько дорогой бывает повторная оценка промпта, показывает пример форка llama.cpp с SSD-кэшированием KV: ускорение с 9,3 с до 0,41 с на повторном промпте в агентном пайплайне.

Что происходит при параллельном запуске оркестратора, фиксера и оракула

Типичная картина: сервер запущен с несколькими слотами под параллельные запросы, и три агента стреляют в него одновременно. У каждого свой системный промпт и свой набор инструментов, поэтому префиксы не совпадают. Prefix caching не срабатывает, а KV-кэш приходится держать сразу в нескольких экземплярах.

Дальше включается планировщик. Он делит память между слотами: чем больше слотов, тем короче доступный контекст на каждый. Когда свободной VRAM не хватает, кэш вытесняется, и префиксы считаются заново. Один агент ждёт, пока другой освободит слот, GPU переключается между батчами и чаще читает память.

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

Реальные цифры: что дают две Tesla P40 с Qwen 3.8 27B

В описанном кейсе с двумя Tesla P40 и Qwen 3.8 27B при одном активном процессе получается до 45 токенов/с генерации и около 450 токенов/с prefill на свежем контексте. Как только контекст переваливает за 150K, prefill падает до ~120 токенов/с, а генерация до 12-16 токенов/с.

СценарийPrefillГенерация
Свежий контекст, один процесс~450 токенов/сдо 45 токенов/с
Контекст 150K+, один процесс~120 токенов/с12-16 токенов/с

Это замеры на конкретной конфигурации. Другая GPU, другая квантизация, другая длина контекста дадут другие числа. Значение имеет порядок величин: разрыв между свежим и длинным контекстом почти четырёхкратный по prefill и примерно трёхкратный по генерации.

Почему prefill и генерация деградируют на длинном контексте

Prefill это обработка входного промпта. Модель считает представления сразу для всех токенов, и объём работы растёт быстрее, чем линейно по длине последовательности. 150 000 токенов это уже серьёзная нагрузка даже для современных ускорителей, а P40 работает без тензорных ядер.

Генерация упирается в другое: в пропускную способность памяти. На каждом шаге нужно прочитать весь KV-кэш, чтобы посчитать внимание. Чем длиннее контекст, тем больше байт перекачивается из VRAM, тем ниже скорость на токен. Двадцать четыре гигабайта на карту это жёсткий бюджет, из которого ещё вычитаются веса модели.

Практический вывод: держите контекст короче там, где он не нужен, и выносите длинную работу в отдельные сессии. Что именно крутить в llama.cpp под длинный контекст и harness-режим (batch и ubatch, ctk/ctv, кэш промпта, замер реального wallclock), разобрано в материале про настройку Qwen 3.8 в llama.cpp для длинного контекста.

Как устроена последовательная оркестрация: супервизор, субагент и handoff

Схема, которая сохраняет кэш, выглядит так: в каждый момент с моделью работает ровно один процесс. Супервизор (он же оркестратор) ставит задачу, выбирает исполнителя и передаёт управление. Субагент (фиксер, оракул, любой другой) выполняет свою часть и записывает результат в handoff. Супервизор читает handoff и решает, что делать дальше.

Пример потока. Супервизор формирует задачу «исправить падение на пустом ответе API» и передаёт её фиксеру. Фиксер читает нужные файлы, получает ответ модели, правит код и кладёт в handoff патч, список изменений и пояснение. Супервизор читает артефакт, проверяет результат и либо принимает его, либо ставит новую задачу. Модель за всё это время обслуживает один запрос за раз, поэтому префиксы остаются в кэше.

Что должен содержать handoff, чтобы супервизор мог продолжить работу

Handoff нужен как контракт между агентами. Лог читают глазами, контракт разбирается кодом без догадок. Минимальный набор полей: идентификатор задачи, статус, результат, использованный контекст, ошибки и рекомендации.

{
  "task_id": "fix-0142",
  "status": "done",
  "agent": "fixer",
  "result": {
    "file": "src/api/client.py",
    "summary": "пустой ответ API больше не роняет парсер",
    "diff": "- data = resp.json()\n+ data = resp.json() if resp.text else {}"
  },
  "context_used": ["src/api/client.py", "tests/test_client.py"],
  "errors": [],
  "next_steps": ["прогнать тесты", "проверить лимит ретраев"]
}

Формат значения не имеет: JSON, YAML, TOML или markdown с фиксированной структурой. Важна однозначность. Если фиксер вернул патч без указания, какие файлы он смотрел, супервизор не поймёт, можно ли доверять правке, и пойдёт переспрашивать модель. Каждый такой круг это ещё один prefill.

Размер handoff тоже ограничивайте: результат плюс короткое пояснение. Полный дамп переписки раздувает контекст следующего шага и съедает тот самый кэш, ради которого всё затевалось.

Как избежать гонок и блокировок при последовательном запуске

Последовательный режим требует явной синхронизации. Простейший вариант: очередь задач и один воркер. Супервизор кладёт задачу, воркер её забирает, выполняет и кладёт результат, только после этого супервизор делает следующий запрос. Пока воркер держит блокировку, супервизор к модели не обращается.

Технически это решается файловой блокировкой (flock), семафором, именованным мьютексом или менеджером процессов. Выбор зависит от того, как запущены агенты: отдельные процессы, потоки в одном приложении или внешние CLI-утилиты. Правило одно: ни один агент не заходит в модель, пока другой не закрыл свою сессию.

Плата за это - пропускная способность. Три задачи, которые могли бы идти одновременно, идут одна за другой. На двух P40 с Qwen 3.8 27B такой обмен оправдан: вы теряете параллелизм и получаете обратно мгновенный отклик. Отсюда же растёт приём с handover-документами, когда длинную задачу разбивают на сессии и переносят состояние текстом. В разборе про автоматизацию длительных задач с локальными LLM описаны пять подсистем harness, порог 80% заполнения контекста и автоматический handover-документ.

Существует ли готовый harness для последовательного запуска агентов

Короткий ответ: готового harness, который делал бы ровно это (супервизор делегирует задачу, субагент пишет handoff, супервизор возобновляет работу), в разобранных материалах нет. Есть инструменты с близкой архитектурой, из которых можно собрать своё.

Felo SuperAgent и Pi Coding Agent: что они дают

Felo SuperAgent для Pi держит длительную работу на сохраняемом холсте, а не в сессии. Вы не сидите внутри задачи, а поглядываете на неё; агент останавливается ради вашего решения. Пример из описания: генерация трёх направлений иконки, показ вариантов в 16 px и 256 px и остановка до экспорта, пока пользователь не выберет. Идейно это близко к точкам остановки, которые нужны супервизору.

Pi Coding Agent построен на намеренно маленьком ядре и расширяется через навыки. Навык добавляет охват, не раздувая ядро и не увеличивая список инструментов в обслуживании. Подключение делается копированием папки:

cp -r felo-skills/felo-superagent ~/.pi/agent/skills/

Либо через pi install npm:@scope/pkg. Важная деталь: в Pi нет песочницы, поэтому SKILL.md читают до подключения. Ни один из этих инструментов не даёт последовательной оркестрации на слабой GPU из коробки, но оба показывают полезный принцип: состояние живёт вне сессии, а остановка это нормальный режим работы, а не сбой.

На что смотреть при выборе или сборке своего harness

  • Сохраняемое состояние. Супервизор умеет закрыть сессию и продолжить позже, не теряя нить задачи.
  • Явные точки остановки. Возможность вмешаться до того, как агент сделает необратимое.
  • Механизм handoff. Структурированная передача результата, пригодная для чтения кодом.
  • Гарантия одного процесса. Очередь или блокировка, которая не пускает второго агента к модели.
  • Эскалация по порогу качества. Если результат не дотягивает, задача уходит к более сильной модели или получает дополнительный контекст.
  • Проверяемость навыков. Отсутствие песочницы означает, что SKILL.md и код навыка читают до подключения.

Заранее решите, что считать успехом. Метрика «полезный результат задачи к затраченным вычислениям» честнее, чем «сэкономленные токены»: можно сжечь меньше токенов и получить бесполезный ответ. Сравнение легковесных обвязок под ограниченные ресурсы и чек-лист выбора агентного инструментария собраны в обзоре LM Studio и Hermes Agent для локальных LLM до 9B.

Как оптимизировать данные до дорогого инференса

Слой оптимизации между данными и моделью снимает нагрузку с GPU до того, как она возникнет. Значительная часть того, что вы собираетесь отправить в контекст, оказывается нерелевантной задаче, считаемой детерминированно, извлекаемой из структурированной базы, посильной меньшей модели или сжимаемой без потери смысла ответа.

Пример из описания такого слоя: рабочая нагрузка объёмом 100 ГБ, где задача затрагивает малую часть информации. Сначала выявляется нужное, затем идут компрессия или retrieval, и только потом модель получает необходимое.

Фильтрация, дедупликация и retrieval перед отправкой в модель

Фильтрация убирает фрагменты, не относящиеся к вопросу. Дедупликация выкидывает повторы: в логах и документации одни и те же блоки встречаются десятками. Retrieval достаёт только те документы, которые реально нужны. Если данные лежат в SQL, обычный запрос справится быстрее и точнее, чем векторный поиск по тем же строкам.

Каждый убранный килобайт контекста ускоряет prefill. На конфигурации, где prefill падает до ~120 токенов/с, это заметнее, чем на быстрой GPU: сократить промпт вдвое значит вернуть часть потерянной скорости.

Компрессия и предвычисления: когда они помогают

Компрессия полезна, когда в контексте много избыточного: длинная история диалога, простыня логов, многостраничный документ. Предвычисления это всё, что можно посчитать заранее: эмбеддинги, агрегаты, статистику, готовые фрагменты кода. Модель получает результат, а не задачу на его вычисление.

Ограничение у обоих методов одно: сжатие может выкинуть деталь, которая решает дело. После изменения пайплайна сравните качество ответов на наборе типовых задач, а не только скорость. Меньшая и более дешёвая модель, классификатор или специализированный ускоритель способны заранее решить, нужно ли вообще AI-рассуждение и какой модели достаточно.

Ограничения и подводные камни последовательного режима

Последовательный режим честно обменивает пропускную способность на скорость отклика. В каждый момент работает один агент, остальные ждут. Для интерактивной работы это нормально, для пакетной обработки нет.

Когда последовательный режим проигрывает параллельному

Если GPU несколько или VRAM хватает на несколько KV-кэшей, параллельные агенты дают выигрыш: независимые запросы обрабатываются одновременно, суммарная пропускная способность растёт. Пример, где это оправдано: несколько пользователей шлют несвязанные запросы. Пример, где не работает: 24 ГБ на карту, Qwen 3.8 27B и три агента на одном кэше. Здесь параллелизм приводит к деградации отклика.

Второй минус: длинная задача держит модель всё время выполнения. Если субагент задумался на десять минут генерации, супервизор ждёт все десять минут. Таймауты и лимиты на длину шага обязательны, иначе один зависший процесс блокирует систему целиком.

Риски готовых решений: отсутствие песочницы и проверка навыков

В Pi Coding Agent нет песочницы. Навык, подключённый копированием папки, получает доступ к файловой системе и сети. Спецификация в SKILL.md это единственное, что описывает его поведение, и читать её нужно до подключения. Навык может выполнять произвольный код, обращаться к внешним адресам, менять файлы проекта.

Это общий риск работы с агентами, а не особенность одного инструмента. Если агент умеет запускать команды, ограничения ставят на уровне окружения: отдельный пользователь, контейнер, права только на рабочую папку. Латентность при этом растёт ещё и от того, что каждый шаг проходит проверку, и это тоже нужно закладывать в ожидания.

Практические сценарии: кому подходит такая схема

Схема с одним активным процессом окупается там, где важны скорость отклика и качество, а не число одновременных пользователей.

Локальный AI-сервер для автоматизации рутинных задач

Рефакторинг кода, ответы по документации, обработка входящих сообщений, генерация тестов. Супервизор ставит задачу, субагент выполняет, результат возвращается в handoff. Для интерактивной работы это критично: ответ, который приходит мгновенно благодаря prefix caching, ощущается совсем иначе, чем ответ после пересчёта контекста на 150K токенов.

Домашняя лаборатория и RAG-системы на ограниченном железе

Для RAG важны быстрая обработка запроса и точное извлечение документов. Последовательный режим сохраняет KV-кэш, а retrieval сокращает prefill до нескольких тысяч токенов вместо сотен тысяч. Пример: система отвечает на вопросы по базе знаний, извлекая релевантные абзацы и обслуживая один запрос за раз.

Если локальной модели не хватает на часть задач, разумный вариант - гибрид: мелкая модель на 2B-7B локально для рутины, облачный API для сложных шагов. Практическая архитектура такого разделения и оптимизация затрат разобраны в статье про гибридные AI-агенты с локальными и облачными моделями.

Старт простой: запустите один процесс, снимите свои цифры prefill и генерации на свежем и на длинном контексте, и только потом добавляйте второго агента. Если скорость отклика упала, вы уже знаете, что ответ на вопрос о параллелизме на вашем железе отрицательный.

Подписаться на канал