Чтобы получить чистую Markdown-версию этой страницы, добавьте
.mdк этому URL. Полный индекс документации см. в https://docs.nvidia.com/dynamo/llms.txt. Полное содержимое, включая справочник API и примеры SDK, см. в https://docs.nvidia.com/dynamo/llms-full.txt.
Реплей KV-событий — Dynamo против vLLM
Обзор
Dynamo и vLLM публикуют события KV cache (block stored, block removed и т. д.) через transport fire-and-forget (ZMQ PUB/SUB). Поскольку PUB/SUB может терять сообщения, обеим системам нужен механизм, который позволяет потребителям обнаруживать пропущенные сообщения и восстанавливаться. В этом документе сравниваются два подхода.
Проблема
Потребитель событий KV (router, cache coordinator) подписывается на live stream событий blocks от workers. События несут монотонно возрастающие sequence numbers. Когда потребитель обнаруживает gap в последовательности (например, получил seq 42, а затем seq 45), он должен восстановить пропущенные события, иначе его представление о состоянии KV cache worker станет устаревшим и неверным.
Сравнение архитектур
| Буфер реплея vLLM | Локальный индексатор Dynamo | |
|---|---|---|
| Основной буфер | collections.deque[tuple[int, bytes]] с maxlen | VecDeque<RouterEvent> с max_buffer_size |
| Семантика буфера | FIFO ring, старые записи тихо удаляются | FIFO ring, старые записи тихо удаляются |
| Порядок событий | Монотонный sequence number (целое число на 8 байт) | Монотонный event_id с проверкой последовательных ID |
| Поиск | Линейный проход (for seq, buf in buffer) | Бинарный поиск (binary_search_by_key) |
| Сериализация | Предварительно сериализованные bytes msgpack хранятся в буфере | Структурированные events хранятся; сериализуются по запросу |
| Fallback при слишком старом буфере | Consumer должен перестроиться вне системы | Dump полного состояния дерева RadixTree |
| Начальная синхронизация | Не встроено — потребитель стартует из живого потока | Dump дерева (запрос с start_event_id=None) |
| Авторитетное состояние | Только buffer | RadixTree (buffer — это слой оптимизации) |
| Сжатие / dedup | Events хранятся как есть (pre-serialized) | RadixTree сжимает общие префиксы между последовательностями |
| Истечение срока | Неявно через eviction по maxlen | TTL expiration через PruneManager |
| Транспорт | ZMQ PUB/SUB + ROUTER/REQ | RPC службы Dynamo (request/response) |
| Несколько rank | Смещение порта для каждого DP rank | Отдельный query endpoint для каждого DP rank |
| Модель потоков | Фоновый поток с очередью | Однопоточный tokio runtime на выделенном потоке ОС |
| Гарантия доставки | At-least-once (потребитель выполняет dedup) | At-least-once (router dedup через tracking ID событий) |
| Ответственность за dedup | Потребитель должен фильтровать по seq number | Обрабатывается внутри infrastructure indexer |
Как работает каждая система
vLLM: реплей только из буфера
ZmqEventPublisher в vLLM (в vllm/distributed/kv_events.py) запускает два ZMQ socket в фоновом потоке:
- PUB socket (по умолчанию
tcp://*:5557): передает сообщенияKVEventBatch, помеченные монотонным sequence number. - ROUTER socket (необязательный, например
tcp://*:5558): обрабатывает replay requests от потребителей.
Publisher хранит deque последних buffer_steps (по умолчанию 10 000) сериализованных batches. Когда потребитель обнаруживает gap, он отправляет недостающий start sequence number в ROUTER socket. Publisher линейно просматривает буфер и возвращает все batches начиная с этой sequence, завершая sentinel (seq=-1, payload=empty).
Компромиссы:
- Легковесно — кроме самого buffer ничего не хранится; это легко понимать и разворачивать.
- Если gap старше окна buffer, потребитель должен восстановить state другими средствами (например, перезапуском и повторным обнаружением).
- Нет встроенной начальной синхронизации состояния — потребитель, подключившийся после публикации событий, стартует с пустым представлением.
- Линейный проход при каждом replay request (без индексации внутри buffer).
- Потребитель выполняет dedup, проверяя
replay_seq > last_seq.
Dynamo: buffer + indexer с fallback через tree dump
LocalKvIndexer в Dynamo (в lib/kv-router/src/indexer/local.rs) оборачивает KvIndexer (на базе RadixTree) в циклический буфер событий:
LocalKvIndexer
├── indexer: KvIndexer // Authoritative state (RadixTree)
├── event_buffer: VecDeque // Circular buffer for fast replay
└── max_buffer_size: usize
Когда router запрашивает у worker события через get_events_in_id_range(start_id, end_id), local indexer возвращает один из трех вариантов ответа:
| Ответ | Когда | Что происходит |
|---|---|---|
Events | Запрошенный диапазон находится в буфере | Возвращает buffered events напрямую (binary search границ среза) |
TreeDump | Диапазон слишком старый или это initial sync (start_id=None) | Выгружает полный RadixTree как synthetic events — полный snapshot состояния |
TooNew | Потребитель опережает producer | Ответ с ошибкой; заполнять gap не нужно |
Fallback tree dump означает, что когда buffer не может удовлетворить запрос, indexer откатывается к выгрузке всего состояния дерева. Это делает "buffer too old" recoverable condition ценой дополнительной сложности и памяти на поддержку дерева.
Обнаружение gaps
Обе системы обнаруживают gaps одинаково: потребитель отслеживает последний sequence/event ID, который он обработал, и сравнивает его со следующим полученным.
vLLM (из examples/online_serving/kv_events_subscriber.py):
if last_seq >= 0 and seq > last_seq + 1:
missed = seq - last_seq - 1
replay.send((last_seq + 1).to_bytes(8, "big"))
# ... receive and process replayed events
Dynamo (из lib/llm/src/kv_router/worker_query.rs):
Router отслеживает last_recovered_event_id для каждого worker и запрашивает recover_from_worker(worker_id, dp_rank, start_event_id, end_event_id), когда обнаруживает gap или при первоначальном обнаружении. Local indexer берет на себя сложность определения того, нужно ли replay из buffer или dump tree.
Когда использовать что
Встроенный replay vLLM подходит, когда:
- Вы запускаете автономный vLLM и хотите базовое восстановление после gap без дополнительной инфраструктуры.
- Ваш потребитель долго живет и редко отключается — основная проблема это кратковременные gaps.
- Вы строите собственный внешний router или cache coordinator и хотите потреблять события KV напрямую из vLLM, не оборачивая его в другой фреймворк.
Local indexer Dynamo подходит, когда:
- Вам нужно надежное восстановление, включая начальную синхронизацию состояния для недавно подключившихся router или потребителей, которые долго были офлайн.
- Вы запускаете несколько реплик router, которые могут стартовать в разное время и должны сходиться к согласованному представлению состояния cache.
- Вы хотите, чтобы dedup и recovery обрабатывались инфраструктурой, а не реализовывались в каждом потребителе.
Оба подхода разделяют одну и ту же базовую идею — FIFO ring buffer для компенсации небольших кратковременных gaps. Dynamo добавляет под ним RadixTree как авторитетное состояние, что позволяет использовать tree dump fallback для полного восстановления состояния ценой дополнительной памяти и сложности. vLLM оставляет все проще, используя только buffer, и этого достаточно, когда потребители стабильны, а gaps короткоживущие.
В развертываниях, использующих KV-aware routing Dynamo, local indexer применяется автоматически. Для автономных развертываний vLLM, где вы хотите построить собственного потребителя событий, replay buffer vLLM дает легковесную отправную точку.
См. также
- Структуры данных индекса KV Router: внутреннее устройство
RadixTree,ConcurrentRadixTreeиPositionalIndexer - Руководство по Router: режимы развертывания и quick start для KV-aware routing
- Конфигурация и настройка: router flags и детали настройки
- Проектирование Router: детали архитектуры и режимы transport событий