For clean Markdown content of this page, append .md to this URL. For the complete documentation index, see https://docs.nvidia.com/dynamo/llms.txt. For full content including API reference and SDK examples, see https://docs.nvidia.com/dynamo/llms-full.txt.
Как Dynamo оптимизирует agentic-нагрузки на трех уровнях: frontend API, router и управление KV-cache.
Полностековые оптимизации для agentic inference в Dynamo
Coding agents начинают писать production-код в промышленном масштабе. Stripe сообщает, что agents генерируют более 1300 PR в неделю. Ramp относит 30% слитых PR к agents. Spotify сообщает о 650+ PR, сгенерированных agents, в месяц. Инструменты вроде Claude Code и Codex делают сотни API-вызовов за одну coding-сессию, и каждый из них несет полную историю разговора. За каждым таким workflow стоит inference-стек, испытывающий значительное давление на KV-cache.

Возьмем Claude Code как пример. После первого API-вызова, который записывает префикс разговора в KV-cache, каждый последующий вызов к тому же worker'у попадает в cache на 85-97%. Agent-команды (или swarms) доводят это до 97.2% суммарного cache hit rate на четырех teammates Opus. Соотношение чтений к записям 11.7x означает, что система читает из cache почти 12 раз на каждый записанный токен. Это шаблон доступа write-once-read-many (WORM): системный prompt и растущий префикс разговора вычисляются один раз, а затем отдаются из cache при каждом следующем вызове. Максимизация повторного использования cache между всеми worker'ами и поддержание KV-блоков в горячем и маршрутизируемом состоянии - центральная задача оптимизации agentic inference.
Эти числа приходят из управляемой API-инфраструктуры, где провайдер контролирует сопоставление префиксов, размещение cache и eviction. Для команд, которые запускают open-source модели на собственных GPU, всего этого нет "из коробки". Мы строим Dynamo, чтобы закрыть этот разрыв. Этот материал показывает, как мы делаем Dynamo agent-native на трех уровнях: frontend API, router и управление KV-cache.
По всему материалу мы последовательно используем три термина:
- Harness: agent framework, который управляет workflow (Claude Code, Codex, OpenClaw, OpenCode и т. д.)
- Orchestrator: слой маршрутизации, планирования и управления cache в Dynamo
- Runtime: inference engine, который выполняет модель и владеет менеджером kv cache (SGLang, vLLM, TRT-LLM)
Уровень 1: Frontend
Поддержка нескольких протоколов
Agent harness'ы все чаще переходят с v1/chat/completions на v1/responses и v1/messages, чтобы корректно обрабатывать новые паттерны, включая перемежающееся thinking и tool calls. Ключевое отличие между этими API - структурное. В v1/chat/completions содержимое сообщения - это плоская строка, а tool calls добавляются как отдельное поле. Например, обратите внимание, как API GLM и MiniMax по-разному обрабатывают перемежающееся thinking, когда модель обслуживается через endpoint v1/chat/completions. API v1/responses и v1/messages используют типизированные блоки контента, поэтому один assistant-turn может содержать thinking, tool calls и текст как отдельные объекты. Это важно для inference, потому что orchestrator видит границы блоков, может выполнять оптимизации prompt и применять разные политики cache и планирования для каждого типа блока. Dynamo обслуживает все три endpoint через общее внутреннее представление, поэтому одно развертывание может выступать inference backend'ом для любого harness. Наша команда уже запускает внутреннее развертывание Dynamo для GLM-5 и MiniMax2.5, чтобы поддерживать наши harness'ы Codex и Claude Code. Это позволяет сравнивать наши backend-реализации с закрытой inference-системой, стремясь к паритету по повторному использованию cache. В ближайшие недели мы опубликуем полный разбор и несколько оптимизированных рецептов развертывания обеих моделей.
Обслуживание Claude Code с Dynamo | Обслуживание Codex с Dynamo |
Мы также с самого начала вложились в поддержку парсинга tool call и reasoning для различных open-source моделей. Если вы обнаружите, что какая-то модель не поддерживается, пожалуйста, откройте issue или используйте skill tool-call-parser-generator, чтобы сгенерировать его под нужный вам harness.
Agent Hints: интерфейс Harness-Orchestrator
Сегодня inference-серверы видят анонимные токенизированные запросы. Но agent harness'ы обладают глобальным контекстом, который инфраструктура никогда не видит: какие agents заблокированы на tool calls, какие только что запущены, сколько turns осталось в сессии и является ли текущий вызов быстрым lookup или длинной синтезирующей операцией. При работе с coding agents пользователь ждет конечный результат, а не отдельные потоки токенов, поэтому orchestrator может переупорядочивать и приоритизировать запросы между agents без влияния на пользовательский опыт. Сессии длятся от минут до нескольких дней с длинными паузами между tool calls. Этого достаточно, чтобы оптимизировать планирование inference так, как традиционное обслуживание делать не может.
Новое расширение agent hints в Dynamo создано, чтобы закрыть этот разрыв. Оно позволяет любому harness прикреплять структурированные hints к запросу во всех трех API endpoint, давая router'у и runtime контекст, необходимый для agent-aware решений по планированию и cache. Это API v1, которое мы активно проектируем вместе с сообществом, и нам важна обратная связь от команд, строящих agent harness'ы, о том, какие сигналы наиболее полезны. Если у вас есть идеи или обратная связь, напишите нам!
{
"model": "MiniMaxAI/MiniMax-M2.5",
"messages": [...],
"tools": [...],
"nvext": {
"agent_hints": {
"osl": 256,
"speculative_prefill": true,
"priority": 10
},
"cache_control": {
"type": "ephemeral",
"ttl": "1h"
}
}
}
Поля agent_hints:
priorityуправляет планированием и в router, и в engine. Более высокие значения означают "более важно" на уровне Dynamo API; Dynamo преобразует это в порядок очереди router'а и приоритет engine, специфичный для backend'а.osl(output sequence length) - это оценка harness'а того, сколько токенов сгенерирует запрос. Router использует ее, чтобы понимать, как долго worker будет занят, что улучшает балансировку нагрузки. Harness может научиться этому со временем, отслеживая среднюю длину ответа по типам tool call.speculative_prefillсигнализирует orchestrator'у начать кэшировать префикс этого запроса на вероятном worker'е еще до того, как полный запрос будет готов. Это полезно, когда harness знает, что tool call вот-вот вернется, и хочет заранее прогреть cache.
Поле cache_control будет знакомо всем, кто использовал Anthropic prompt caching API. Оно говорит orchestrator'у закрепить вычисленный префикс на worker'е на указанный TTL, защищая его от eviction во время пауз между tool call. Сейчас единственный поддерживаемый тип - ephemeral (чтобы соответствовать API Anthropic). Ниже мы обсуждаем, как это работает в разделе про удержание cache. Полную документацию по agent hints можно найти здесь.
Уровень 2: Router
Coding agent следует последовательному паттерну: длинный prefill, tool call, расширение префикса, повтор. Multi-agent harness распределяет работу по параллельным subagent'ам с короткими независимыми контекстами. Стандартная round-robin маршрутизация не видит ни один из этих паттернов - она не учитывает локальность cache, приоритет запроса или структуру сессии. Router в Dynamo закрывает этот разрыв тремя механизмами: размещением с учетом KV, приоритизацией очереди и расширяемыми стратегиями маршрутизации.
Размещение с учетом KV
Без маршрутизации с учетом cache второй turn разговора имеет вероятность около ~1/N попасть на того же worker'а, что и первый. Каждый промах - это полное пересчитывание префикса, что становится серьезным узким местом производительности и очень дорого обходится пользователю. Router Dynamo хранит глобальный индекс того, какие KV cache-блоки находятся на каких worker'ах. В материале про Flash Indexer описаны шесть итераций, которые довели этот indexer до 170M ops/s (планетарный масштаб KV routing). На каждом запросе router обращается к индексу за оценками overlap по каждому worker'у и выбирает worker, который минимизирует совокупную стоимость промаха cache и текущей decode-нагрузки. Эта cost function настраивается, и ниже мы покажем, как команды могут строить поверх нее собственные agent-aware стратегии маршрутизации.
Приоритизация в очереди
priority - единственный пользовательский рычаг планирования. Более высокие значения означают "более важно" на уровне Dynamo API. Dynamo использует этот один hint на обоих уровнях:
- На router запросы с более высоким приоритетом смещаются ближе к началу очереди, когда включен
--router-queue-threshold. - В engine Dynamo нормализует знак, специфичный для backend'а, и передает запрос для упорядочивания очереди, preemption и eviction KV-cache.
На router входящие запросы попадают в BinaryHeap<QueueEntry>, упорядоченный по эффективному времени прихода. Более высокий priority заставляет запрос выглядеть так, будто он пришел раньше, помещая его перед менее приоритетной работой. Запросы попадают в очередь только тогда, когда все worker'ы превышают настраиваемый порог нагрузки. Ниже этого порога они полностью обходят очередь и сразу переходят к выбору worker'а. Когда мощность освобождается (prefill завершается или запрос заканчивается), очередь сначала выгружает записи с наивысшим приоритетом.
После dispatch SGLang, vLLM и TRT-LLM могут по-разному интерпретировать engine priority, поэтому Dynamo нормализует значение, передаваемое engine, для каждого backend отдельно. Engines вроде SGLang также могут использовать eviction radix cache на основе приоритета, где сначала удаляются блоки с более низким приоритетом при нехватке памяти.
Стратегии маршрутизации для agentic-нагрузок
Исследовательскому agent'у с окном контекста 200K нужны worker'ы с достаточным свободным KV capacity, чтобы удержать все его состояние. Стандартная cost function router'а (overlap score + decode load) покрывает обычный случай, но команды со специфическими доменными нагрузками могут использовать Python bindings router'а для реализации собственных стратегий маршрутизации. Базовый класс KvRouter предоставляет best_worker() для запроса решений маршрутизации, get_potential_loads() для просмотра нагрузки по worker'ам и generate() для маршрутизации и dispatch одним вызовом. Пользовательские router'ы регистрируются в той же service mesh, что и компоненты по умолчанию, и могут переопределять конфигурацию маршрутизации на уровне каждого запроса:
# Получить нагрузку и overlap по каждому worker для собственной логики маршрутизации
loads = await router.get_potential_loads(token_ids)
# Переопределить конфигурацию маршрутизации на основе свойств запроса
# Длинные контексты выигрывают от более сильного учета overlap
config = {"overlap_score_credit": 1.0} if len(token_ids) > 8192 else {}
worker_id, dp_rank, overlap = await router.best_worker(
token_ids,
request_id="req-123",
update_indexer=True,
router_config_override=config
)
# Или полностью обойти стандартный selector, если harness
# имеет собственную логику выбора worker (например, session affinity)
stream = await router.generate(
token_ids, model=model, worker_id=chosen_worker
)
Команда NeMo Agent Toolkit (NAT) использовала эти API, чтобы построить собственный online-learning agentic router. Их router извлекает метаданные сессии из аннотаций nvext и подает их в bandit-кост-функцию в стиле Thompson Sampling, которая учится, какие worker'ы лучше всего работают для каких prefix patterns под нагрузкой. По сравнению со стандартной маршрутизацией Dynamo они измерили 4-кратное снижение p50 TTFT и 1.5-кратный рост p50 tokens-per-second. Пометка запросов, чувствительных к задержке, приоритетом дала до 63% снижения p50 TTFT при умеренном давлении на память. Подробности реализации см. в примере интеграции NAT с Dynamo. Скоро мы сделаем это доступным в Dynamo как стратегию маршрутизации.
Уровень 3: Управление KV-cache
Agentic-нагрузки создают блоки с очень разной ценностью повторного использования - системные prompt'ы используются на каждом turn, reasoning-токены больше никогда не переиспользуются, - но стандартная eviction по LRU обращается со всеми блоками одинаково. Пауза на tool call в 2-30 секунд может привести к старению всего префикса agent'а, и при возобновлении потребуется полное пересчитывание. Cache должен понимать ценность блока, поддерживать совместное использование между worker'ами и уважать границы жизненного цикла agent'а.
Проблема унифицированного eviction
| Тип блока | Паттерн повторного использования | Ценность |
|---|---|---|
| Системный prompt + определения tools | Каждый turn | Наивысшая |
| История разговора | Последующие turns, монотонно растет | Высокая |
| Thinking/reasoning tokens | Обычно не переиспользуются после закрытия цикла reasoning (значительная часть вывода) | Почти нулевая |
| KV subagent'а | Несколько turns, затем agent завершает работу. Сохранять не нужно | Почти нулевая |
LRU видит только давность использования. В условиях высокой нагрузки ожидание завершения вызванного tool (2-30 секунд, пока agent ждет внешний API) может привести к старению блоков agent'а, и когда agent возобновится, весь префикс придется пересчитать заново. Чтобы решить эту проблему, нужно дать orchestrator'у API для управления тем, какие блоки следует сохранять, где они должны храниться и как долго.
KV-cache как общий ресурс
Сегодня KV-cache рассматривается как локальный, временный ресурс на каждом worker'е. Системный prompt и определения tools агента на ~32K токенов вычисляются независимо на каждом worker'е, который обслуживает его запросы. Когда основной agent порождает 4 subagent'а, каждый с перекрывающимися определениями tools, этот общий префикс пересчитывается 4 раза, если subagent'ы попадают на разные worker'ы. В нашем анализе сессий Claude Code team мы измерили это напрямую: teammates в среднем имели 79.4% cache hit rate против 91.3% у explore subagent'ов основного agent'а (соотношение чтений к записям 5.0x против 11.7x), а разрыв был обусловлен почти полностью cold-start записями при первом вызове каждого teammate. Цель - сделать высокоценные KV cache-блоки доступными всем worker'ам в кластере. По сути, они записываются один раз при cold start, а затем читаются любым worker'ом в любое время.
Решения вроде HiCache от SGLang и Dynamo KV Block Manager (KVBM) строятся в сторону 4-уровневой иерархии памяти:
Блоки следуют по write-through пути: когда worker вычисляет KV для префикса, блоки автоматически проходят от GPU к CPU и на диск. Каждый блок дедуплицируется по sequence hash в глобальном реестре. После регистрации блок становится неизменяемым и доступным любому worker'у, который может достичь уровня storage.
Это напрямую решает проблему cold start у subagent'ов. Когда основной agent вычисляет определения tools и системный prompt, эти блоки write-through попадают в shared storage. Когда subagent 1 запускается на другом worker'е, router обращается к Flash Indexer, находит блоки в shared storage, и worker загружает их через NIXL (RDMA read) вместо пересчета с нуля. Subagent 2 делает то же самое. Четыре избыточных вычисления prefill превращаются в одно вычисление и три загрузки. Этот же механизм решает проблему согласованности cache в раздельном prefill-decode обслуживании. В disagg-режиме worker prefill вычисляет KV и передает его worker'у decode через NIXL. Worker decode генерирует токены, создавая новое KV-состояние. На следующем turn worker prefill нуждается и в исходном префиксе, и в токенах, сгенерированных на первом turn, но они находятся только на worker'е decode. При общем storage worker decode записывает свои новые блоки в общий уровень, и любой worker prefill может получить их на следующем turn.
Многоуровневое хранилище решает задачу общего использования и сохранности, но блоки все равно попадают на GPU только после того, как запрос достигает worker'а. Недостающий элемент для agentic-систем - prefetch: harness может использовать исторические данные о таймингах, чтобы предсказать, когда вернется tool call agent'а, а значит знает, какие блоки понадобятся и когда. Мы создаем prefetch hooks, чтобы harness мог сигнализировать: "перенесите эти блоки из storage на GPU до следующего запроса". В сочетании с API удержания cache (ниже) это дает harness'у полный контроль над жизненным циклом: закреплять блоки, чтобы предотвратить eviction, задавать приоритет для управления порядком eviction и заранее prefetch'ить блоки до того, как они понадобятся.
Выборочное удержание cache
Глобальная доступность блоков решает проблему общего доступа, но не решает eviction. SGLang и vLLM поддерживают eviction на основе приоритета через priority heap, где harness назначает числовой приоритет каждому запросу, а блоки с более низким приоритетом удаляются первыми. TensorRT-LLM идет дальше с TokenRangeRetentionConfig (спроектированным и реализованным участником команды Dynamo @jthomson04), который позволяет управлять отдельными диапазонами внутри одного запроса.
Запрос несет ноль или более директив. Блоки без директив идут по стандартному пути LRU без накладных расходов. Evictor превращается в систему из двух структур: free list LRU для неаннотированных блоков (O(1), без изменений) и очереди приоритетов для аннотированных блоков. Harness может выразить: "блоки системного prompt'а удалять последними (priority: 100); контекст разговора должен пережить паузу tool call на 30 секунд (duration: 45s); decode-токены удалять первыми (priority: 1)" - и engine не должен понимать, почему это так.
Anthropic prompt caching позволяет помечать префиксы как кэшируемые на их инфраструктуре. API cache_control в Dynamo приносит ту же семантику в self-hosted inference. Когда запрос содержит cache_control: { type: "ephemeral", ttl: "1h" }, router закрепляет соответствующие узлы префикса в radix tree worker'а на этот TTL, защищая их от eviction в L2 storage worker'а.
Следующий шаг - связать retention с distributed cache. Сегодня директивы retention применяются только к локальному cache одного worker'а. Когда блок закреплен на worker A, а следующий запрос уходит на worker B, закрепление не переносится. Расширение семантики retention на shared storage tier HiCache/KVBM означает, что harness может закрепить блок один раз, и он сохранится между worker'ами: метаданные priority и TTL путешествуют вместе с блоком по write-through пути, а любой worker, загружающий блок из shared storage, наследует политику retention. В сочетании с описанными выше prefetch hooks это дает harness'у полный контроль над жизненным циклом во всей иерархии памяти.
Осведомленность о жизненном цикле agent'а
Рассмотрим типичную сессию Claude Code. Основной agent работает более 20 turns, накапливая растущий префикс разговора. По ходу он создает explore subagent'ов, каждый из которых работает 1-3 turns и завершается. Он может создать команду из 4 специалистов, которые параллельно работают над разными подзадачами и затем завершаются. В середине сессии agent достигает лимита контекста и суммирует историю, сжимая ~175K токенов до ~40K. Каждый из этих событий создает временный KV: блоки, которые больше никогда не будут использованы. Завершение subagent'а, суммаризация контекста и закрытие циклов reasoning - все это создает ephemeral KV, который занимает ту же память, что и высокоценные блоки вроде системного prompt'а. Reasoning-модели усиливают этот эффект: блоки <think>...</think> составляют около 40% сгенерированных токенов, но становятся временными в момент закрытия цикла reasoning. Без учета жизненного цикла cache одинаково обращается со всеми этими блоками.
Примитивы retention из раздела выше (priority, TTL, token ranges) дают нам строительные блоки. Не хватает возможности связать их с сессиями. Если harness может пометить запросы subagent'а как относящиеся к сессии и обозначить KV этой сессии как ephemeral, evictor может сначала нацеливаться на эти блоки и вообще не записывать их в shared storage. Когда subagent завершается, блоки его сессии первыми становятся кандидатами на освобождение. Тот же механизм применим к thinking tokens: engine может обнаруживать границы <think> во время генерации и помечать такие блоки как ephemeral уже в момент вставки, чтобы они не записывались обратно в L2 и удалялись раньше обычных блоков без какого-либо внешнего сигнала. Пространство решений здесь широкое: разметка сессий на стороне harness, семантическое обнаружение на стороне engine, гибридные подходы, сочетающие оба. Мы активно исследуем несколько направлений и ожидаем, что правильный ответ будет различаться в зависимости от нагрузки и framework.
Сокращение разрыва
Самая большая поверхность оптимизации в agentic inference - это разрыв между тем, что знает harness, и тем, что видит инфраструктура. Какие agents заблокированы, какие вот-вот возобновятся, какой KV стоит сохранять, а какой можно выбросить - весь этот контекст существует на уровне harness, но никогда не пересекает API boundary. nvext.agent_hints - наш первый шаг к закрытию этого разрыва: небольшой набор структурированных сигналов, который позволяет orchestrator'у принимать обоснованные решения по маршрутизации, планированию и управлению cache вместо того, чтобы рассматривать каждый запрос как анонимные токены. Это API v1, и мы активно его развиваем. Если вы строите agent harness'ы, запускаете open-source модели для agentic-нагрузок или думаете о cache-aware inference, мы хотим знать, какие сигналы наиболее важны для вашего сценария. Свяжитесь с нами в GitHub или отметьте нас в X: @0xishand, @KranenKyle, @flowpow123.