«Сколько агентов выдержит ваша железка?» — вопрос, с которого начинается почти каждый разговор о локальном ИИ. Интуиция тянет отвечать в терминах гигагерцев и числа ядер, но на деле это вопрос про память и планирование: влезут ли в узел веса модели, KV-кэш под каждый активный запрос и служебные структуры — и как сервер инференса будет перемешивать запросы между собой.
У нас агентный стек живёт на своём железе, и на одном узле у нас одновременно работают восемь параллельных агентов. В этой статье разбираем математику, по которой мы выбрали это число: что именно съедает память, чем унифицированная память отличается от памяти на обычной видеокарте и почему после определённого числа потоков каждый новый не ускоряет систему, а замедляет всех остальных. Это не парадокс, а прямое следствие того, как устроен continuous batching, — и это же делает вопрос «сколько агентов выдержит узел» решаемым на бумажке до покупки железа.
Table Of Contents
Из чего складывается память
Память узла во время инференса делят три потребителя. Первый — веса модели: они лежат в памяти в одном экземпляре и от числа запросов не растут. Загрузили модель — эта часть бюджета занята до конца её работы, хоть с одним агентом, хоть с восемью.
Второй — KV-кэш: под каждый активный запрос сервер держит промежуточное состояние внимания — то, что позволяет модели не перечитывать весь диалог с нуля на каждом новом токене. Размер этого состояния растёт вместе с длиной контекста и умножается на число одновременных полос. Именно KV-кэш, а не характеристики процессора, определяет, сколько агентов можно держать активными одновременно: веса известны один раз, а кэш живёт своей жизнью и разрастается вместе с задачами.
Третий потребитель — служебное: активации, буферы для обмена данными между узлами, сам планировщик. Меньше первых двух, но из того же бюджета.
Отсюда простая рамка для любого разговора о «вместимости» узла: сколько весит модель, сколько контекста вы закладываете на запрос и сколько полос хотите держать одновременно. Гигагерцы в этой рамке не участвуют — участвуют гигабайты и умение планировщика делить их между запросами. Хорошая новость в том, что все три слагаемых можно посчитать заранее, до того как агенты начнут драться за память в бою.
Info
Веса — постоянный расход, KV-кэш — переменный. Восемь агентов с длинными контекстами легко требуют больше памяти, чем весит сама модель. Планируйте бюджет под кэш, а не под веса — иначе узел «кончится» именно в момент пиковой нагрузки.
Почему unified memory — это другое
128 ГБ унифицированной памяти — не то же самое, что 128 ГБ видеопамяти на отдельной карте. В классической связке процессор и видеокарта имеют раздельные памяти: GPU работает со своей, CPU — со своей, и данные приходится переливать туда-обратно, а модель влезать должна строго в видеопамять. В DGX Spark процессор и GPU видят одну память: модель целиком живёт в едином пуле, и не нужно гадать, поместится ли она «отдельно от системы».
У этого удобства есть оборотная сторона: пропускная способность у пула одна на всех. Память, которую занял KV-кэш восьми агентов, конкурирует с той, где крутится распознавание речи — отдельный сервис на том же узле. Соседей по узлу нельзя выкидывать из расчёта: их память и их нагрузка на общую шину — часть того же бюджета.
Практический вывод: единый пул сильно упрощает компоновку — крупная модель и вспомогательные сервисы помещаются на одном узле, — но требует считать бюджет заранее и честно. «Места много» превращается в «места нет» незаметно: не из-за весов, которые заняты один раз, а из-за растущего кэша и соседей, которых легко забыть в планировании.
Continuous batching: декод пачкой, prefill пакетом
Классический статический батчинг работает так: собрали пачку запросов, прогнали вместе, ждём, пока закончится самый медленный, и только потом берём следующую пачку. Если в пачке один короткий вопрос и один огромный документ, короткий простаивает и тормозит всех — пачка едет со скоростью самого тяжёлого пассажира.
vLLM, наш сервер инференса, работает иначе — в режиме непрерывного батчинга (continuous batching). Состав пакета меняется на лету: как только запрос закончился, на его место из очереди встаёт новый, без ожидания «полного состава». Декод идёт пачкой — генерация токенов у всех активных запросов перемешана в общий цикл, а prefill — чтение свежего контекста — выполняется пакетом между шагами декода. Для планировщика это конвейер, а не рейсы по расписанию: GPU почти не простаивает.
Оборотная сторона: чем больше пакет, тем выше суммарная скорость узла, но тем медленнее каждый отдельный ответ. Агенту нет дела до совокупной пропускной способности — он видит только свою полосу. Поэтому выбор числа полос — всегда компромисс: суммарно узел успевает больше, каждый агент в отдельности — меньше.
Отсюда же главный контринтуитивный вывод: после определённого числа потоков каждый новый не добавляет системе скорости — он отнимает её у остальных. Шестьдесят четыре потока на узле не быстрее восьми — они хуже восьми: очередь на prefill длиннее, полоса у каждого тоньше, а суммарная пропускная способность при этом не растёт. Лимитировать одновременные потоки — не поражение, а часть настройки.
Наш стенд: кластер DGX Spark, 200 Гбит/с и тензорный параллелизм
Наши площадки — кластеры из DGX Spark; ниже — про одну связку из двух узлов, на которой мы гоняем крупные модели. О самой платформе писали отдельно: DGX Spark. На каждом узле — GB10 со 128 ГБ унифицированной памяти, узлы связаны адаптерами ConnectX-7 на 200 Гбит/с.
Сервер инференса — vLLM. Крупные модели мы гоняем с тензорным параллелизмом между двумя узлами: слои модели делятся между узлами, промежуточные результаты бегут между ними по этой связи. DeepSeek V4.1 Flash 1017 живёт на паре узлов, Qwen3.8-Flash — на одном узле, мы рассказывали о ней в отдельной статье.
Рядом на том же узле — распознавание речи, отдельный сервис: агенты разбирают голосовые сообщения локально, без облака. У этого сервиса своя доля памяти и своя нагрузка на общую пропускную способность, поэтому при расчёте бюджета он такой же жилец, как агентные полосы. И наконец, экономика всей этой затеи — сколько стоит свой стенд против облака — у нас тоже посчитана отдельно: экономика агентного стенда.
Что чувствует пользователь: шесть полос и 24 токена в секунду
Теперь цифры нашего стенда. Наш замер: кодовая модель на одном узле выдаёт около 24 токенов/с на полосу при шести параллельных полосах. Для кодогенерации это режим, в котором агент отвечает живо, а узел при этом обслуживает все шесть полос сразу — никого не приходится ставить на паузу.
Восемь полос — сознательный компромисс между скоростью полосы и длиной очереди: суммарно узел успевает больше, каждый отдельный ответ — чуть медленнее. Вот как разное число полос выглядит с точки зрения пользователя:
| Полос на узле | Что чувствует пользователь |
|---|---|
| Меньше полос, чем агентов | Параллельность превращается в очередь: часть агентов стоит и ждёт, работа идёт последовательно |
| Шесть | Около 24 токенов/с на полосу на кодовой модели — наш рабочий режим одного узла: все полосы живые, очередь короткая |
| Восемь | Компромисс: суммарная пропускная способность выше, отдельные ответы медленнее, очередь на входе заметнее |
| Потоки «про запас», например 64 вместо 8 | Хуже для всех: каждый новый поток отнимает полосу у остальных, никто не становится быстрее |
Мораль таблицы: у узла есть точка, после которой полезнее ограничить число одновременных потоков, чем запускать всё, что пришло. Очередь на входе — не признак слабости стенда, а способ удержать скорость каждой полосы. Пользователю агента всё равно, сколько запросов узел обслуживает в сумме, — он чувствует только скорость своего ответа.
Наше правило: 10 ГБ KV-бюджета на поток
Когда мы планируем, сколько агентов поднимать на узле, счёт идёт от бюджета: из 128 ГБ вычитаем веса модели и служебное, остаток делим на потоки. Правило, которое у нас прижилось: на один поток — не меньше 10 ГБ KV-бюджета.
Подчеркну: это наше правило планирования, расчётная планка, а не измеренный факт и не рекомендация вендора. Мы пришли к ней из наших контекстов и наших моделей; если ваши диалоги короче или модель компактнее, ваша планка будет другой — считать нужно от своих весов и длин контекста, а не списывать чужую цифру.
Смысл планки в двух вещах. Первая: слишком тонкий KV-бюджет означает усечённый контекст — модель начинает забывать начало диалога раньше, чем агент успевает закончить задачу. Это падение качества, которое не видно в логах: ошибок нет, а агент медленно тупеет прямо посреди задачи. Вторая: планка заранее резервирует место соседям по узлу — распознаванию речи и следующим сервисам, которые иначе первыми пострадают, когда кэш разрастётся в пик нагрузки.
Warning
«Не меньше 10 ГБ KV-бюджета на поток» — наш расчёт для планирования, а не факт и не спецификация. Пересчитывайте планку под свои веса модели, длины контекстов и соседей по узлу — иначе либо потеряете контекст, либо память.
Итог
- Вопрос «сколько агентов выдержит железка» — это вопрос про память и батчинг, а не про гигагерцы: веса плюс KV-кэш плюс служебное против одного пула памяти.
- KV-кэш — переменная часть бюджета: он растёт с числом полос и длиной контекста. Планируйте память под кэш, а не под веса.
- Continuous batching поднимает суммарную скорость узла, но после определённого числа потоков каждый новый замедляет остальных: 64 потока хуже восьми.
- Наши рабочие цифры: около 24 токенов/с на полосу при шести полосах на одном узле; восемь полос — компромисс между скоростью полосы и длиной очереди.
- Планируйте по правилу «не меньше 10 ГБ KV-бюджета на поток» — как по расчётной планке, которую нужно пересчитать под ваши модели и контексты.
Мы строим такие агентные стенды на своём железе и на железе заказчиков — от выбора числа полос до планировщика очередей и сервисов вокруг модели. Если хотите обсудить, что из локального ИИ можно собрать под ваши задачи, загляните на страницу нашего направления ИИ.
