NVIDIA Dynamo · experimental · v1.5 → 1.6

Dynamo Sidecar

Take Dynamo out of the engine process. Keep everything Dynamo is good at.

pod Dynamosidecar CPU · Rust discoveryKV eventshealth Stockengine GPU · upstream vLLMSGLangTensorRT-LLM gRPC
Before · integrated backend

Today, Dynamo lives inside the engine

one container · one process · vllm-runtime image Dynamo runtime (Rust)discovery · request plane · event plane PyO3 boundary · one shared GIL dynamo.vllm Python workerhandlers · KV publisher · metrics bridge vLLM AsyncLLM, imported as a librarytouches engine internals CUDA · NCCL · NIXL · Python depsDynamo's and the engine's, pinned together 1 A wrapper per enginevLLM, SGLang and TRT-LLM each need backend code 2 Private engine APIsengine upgrades break the integration 3 A custom image per enginevllm-runtime · sglang-runtime · trtllm-runtime 4 A different CLI to learnpython -m dynamo.vllm instead of vllm serve 5 GIL contention across PyO3runtime and engine share one interpreter
The idea

Split the worker in two

python -m dynamo.vllm: everything in one address spacetwo processes, two containers, one pod, connected over the engine's native gRPC

Dynamo side

runtime: Dynamo sidecar CPU · Rust

Register the engine in discovery Forward KV-cache events Watch health, drive lifecycle Relay requests (today) System metrics on :9090
native gRPC
loopback / UDS

Engine side

main: stock engine GPU · upstream

Native serve CLI and flags Scheduling and batching Sampling and token generation KV cache and NIXL transfer GPU execution
Blast radius

Only one tile in the system changes

Client FrontendOpenAI APItemplate · tokenize✓ unchanged KV Routerprefix overlap + loadPrefillRouter✓ unchanged Plannerscales prefill / decode✓ unchanged Discoveryetcd · K8s DynamoWorkerMetadata Event planeZMQ / NATS · KV events · load NIXL · KVCR✓ reused Dynamo Python worker+ engine in the same processpython -m dynamo.vllmimage: vllm-runtime SidecarCPUregistersforwards StockengineGPUvllm serve ← the only tile that changes
Request path

Three generations of the hop to the engine

Client Frontend + Routertokenize · pick workerstream · detokenize worker pod one process Dynamo Python workerRust runtime ↔ PyO3 ↔ handlers Engine as a libraryAsyncLLM · same GIL Python calls request plane Dynamo sidecarRust · no GIL Stock engine gRPC servere.g. vllm serve --grpc request plane gRPC loopback · token IDs relays every request discovery · health direct gRPC Health.Watch · metadata registrar only
1 hoprouter → worker, then Python calls in-process
Rust ↔ PythonPyO3 crossing on the hot path, shared GIL
Custom imageengine pinned to Dynamo's release
2 hopsrequest plane, then loopback gRPC
Process boundaryno shared interpreter or GIL
Stock imagevllm/vllm-openai, including nightly
1 hoprouter calls the engine's gRPC directly
Sidecar off the request pathstill the frontend’s link for discovery and health
In progressPR #13292 · KV-aware routing on this path not yet done
Kubernetes

Inside the worker pod

Pod · DynamoGraphDeployment worker / prefill / decode runtimeinitContainerrestartPolicy: Always dynamo-sidecar imageCPU only · uid 1000DYN_* env, discovery :9090/live /health/metrics mainregular containervllm/vllm-openai GPUs · /dev/shmcompile cacheupstream flags :30001native gRPCHealth.Check probe loopback K8s ≥ 1.29 native sidecar: starts first, stops last
initContainers:
- name: runtime        # Dynamo
  image: dynamo-sidecar:1.6.0
  command: [dynamo-vllm-sidecar]
  args: [--grpc-endpoint, 127.0.0.1:30001]
  restartPolicy: Always
containers:
- name: main           # engine
  image: vllm/vllm-openai:<ver>
Lifecycle · DEP #14657 + #14897

From pod start to routable, and back

1 · start
2 · load
3 · ready
4 · prepare
5 · register
6 · shutdown
Kubelet
start runtime first
start main
SIGTERM main first
Sidecar
/live ✓ before engine
Health.Watch · waiting
sees SERVING
read engine ID · build KV index
register model + endpoint
deregister · exit last
Engine
load weights · NOT_SERVING
SERVING
KV event sources
NOT_SERVING · drain
Router
worker routable ✓
worker removed
Press → to walk through the lifecycle. The sidecar's HTTP server comes up before it waits for the engine, which avoids a startup deadlock. The engine loads weights. The sidecar subscribes to the standard grpc.health.v1 Watch stream. The engine reports SERVING. Kubernetes probes the same state with Health.Check. The engine ID tells the sidecar whether its KV index belongs to this engine or a replaced one. The sidecar registers only after SERVING and KV prep. From here the router can pick this worker. The engine owns draining. The sidecar deregisters and exits without adding any grace period of its own.
Fault isolation · try it

What happens when something breaks?

Routerroutable set runtime · sidecar uprestarting… registeredderegistered main · engine SERVING · weights in GPUrestarting · reload weightsNOT_SERVING · paused engine ID: Aengine ID: A → B Health.Watch
  • Sidecar watches engine health
  • Worker is routable
  • Separate logs and probes
  • In-process: one crash takes down both
  • Deregistered immediately
  • Only the engine restarts
  • New engine ID: KV index reset
  • Re-registers once SERVING
  • Engine, weights and KV cache survive
  • Unroutable until the sidecar is back
  • In-flight requests fail or migrate
  • KV index is lost (recovery: WIP)
  • Engine reports NOT_SERVING (e.g. RL weight update)
  • Deregistered while paused
  • KV index kept
  • Re-registers once SERVING
Router integration

KV-aware routing still works

KV Routerindexer: block hash → workersscore = prefix overlap − load worker A · DP rank 182% prefix hit · low load Event planeZMQ / NATS Dynamo sidecardiscovers KV sourcesrepublishes events Stock engine · DP = 2 rank 0KV blocks rank 1KV blocks Control / GetServerInfo ZMQ stored / removed blocks request + x-data-parallel-rank: 1via sidecar today · direct later
Press → to follow a KV event and then a routed request. At startup the sidecar asks the engine where its KV event publishers are: vLLM's Control service, SGLang's GetServerInfo. Each DP rank publishes block stored/removed events over ZMQ. The sidecar forwards them to the event plane. The router's indexer matches the prompt's block hashes against what each worker and rank holds. The chosen DP rank travels as gRPC metadata, so the request lands on the rank that holds the cache.
Disaggregated serving

Prefill → decode, with engines doing the KV transfer

PrefillRouterin the frontendtwo-phase dispatch prefill pod sidecar · DYN_COMPONENT=prefill enginecomputes KV for the prompt decode pod sidecar · DYN_COMPONENT=decode enginegenerates tokens ① prefill ② transfer params ③ decode + params ④ NIXLGPU → GPU ⑤ stream tokens

vLLM ✓

typed kv_transfer_params handoff

SGLang ✓

bootstrap connection handoff

TRT-LLM ✗

gRPC lacks disagg fields; Phase 1 blocker

Performance · mechanism

Why it can be faster: streaming stops being single-threaded

96illustrative model, not a benchmark
–GPU idle, in-process
–GPU idle, sidecar
–sidecar throughput vs in-process
Performance

Where the sidecar is faster

Engine output2.0×
Sidecar · multithreaded Rust + gRPC, scales outIn-process · one Python process, GIL-bound

Costs to watch

  • KV events: one more hop to the router
  • No forward-pass metrics for load-based planning
Minor in practiceloopback over localhostgRPC ser/de across cores

Before it becomes the default

  • Per-token overhead stays small
  • KV-aware routing works as well as today
  • Same features as in-process
  • Benchmarked against in-process

vLLM, SGLang and TRT-LLM switch together · DEP #10842

The boundary · DEP #10838

How the sidecar talks to each engine

Dynamo sidecarRust · CPU only vLLMInference + Control services SGLang≥ 0.5.18 · GetServerInfo TRT-LLMmoving to OpenEngine engine’s own gRPCengine’s own gRPC OpenEngineone versioned contract
Engine-specificfast to ship · code per engine
OpenEngineone surface for every engine: GenerateGetModelInfoGetLoadHealthAbortDrainSubscribeKvEvents
Where it stands · Oct 2026

Readiness and roadmap

AggDisaggKV routingK8s
vLLM✓✓✓✓
SGLang✓✓✓✓
TRT-LLM✓✗~agg

validated launch topologies, not full parity

Open gaps

Metrics & tracing
forward-pass metricsKV load gaugesengine metric re-exporttrace propagation
Not supported yet
multinode on K8sGMS · failover · checkpoint
Operator + CRDsingle-node agg / disagg · in progress
Observabilitymetrics, traces
MultinodeTP / PP
ResilienceGMS, failover, checkpoint
Try it

Run a sidecar in two minutes

# local, single GPU: no etcd or NATS
export DYN_DISCOVERY_BACKEND=file
./lib/sidecar/vllm/launch/agg.sh

# kubernetes
kubectl apply -f lib/sidecar/vllm/deploy/agg.yaml

# image (1.6.0+)
docker pull nvcr.io/nvidia/ai-dynamo/dynamo-sidecar:1.6.0

Read more

  • docs · Sidecar Backends
  • DEP #10835 · umbrella
  • DEP #10838 · engine RPC
  • DEP #14657 · operator / CRD
  • DEP #14897 · engine health
  • DEP #16468 · observability
  • PR #13292 · direct dispatch

Speaker notes

← → step · N notes · F fullscreen