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

Чтобы получить чистую 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 дает легковесную отправную точку.

См. также​