Перейти к основному содержимому

Чтобы получить чистую 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]] с maxlenVecDeque<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)
Авторитетное состояниеТолько bufferRadixTree (buffer — это слой оптимизации)
Сжатие / dedupEvents хранятся как есть (pre-serialized)RadixTree сжимает общие префиксы между последовательностями
Истечение срокаНеявно через eviction по maxlenTTL expiration через PruneManager
ТранспортZMQ PUB/SUB + ROUTER/REQRPC службы 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 в фоновом потоке:

  1. PUB socket (по умолчанию tcp://*:5557): передает сообщения KVEventBatch, помеченные монотонным sequence number.
  2. 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 дает легковесную отправную точку.

См. также