NVIDIA Dynamo · Hands-on guide

Deploy and tune Dynamo

From a laptop to a Kubernetes cluster, then faster: recipes, the right knobs, and agents that tune for you.

👆 Click a stop on the map to start from there

💻Localpip · container ☸️Kubernetesoperator · DGD 📖Recipestested configs 🚀Optimizeknobs · agents
Start here

Four ways in. Pick the one that matches where you are.

Local · install

Install: one container or one wheel

Method
Backend

        
# verify the install
python3 -m dynamo.frontend --help
python3 dev/sanity_check.py
# optional: etcd + NATS for KV events and routing
docker compose -f dev/docker-compose.yml up -d
Requirements · v1.6.0
OS
Ubuntu 22.04 / 24.04 · x86_64 or ARM64
CUDA
12.9+ or 13.0+ (B300 / GB300 need 13)
Driver
575.51.03+ (CUDA 12) · 580.00.03+ (CUDA 13)
Python
3.11 / 3.12 (TRT-LLM: 3.12)
Engines
vLLM 0.31.0 · SGLang 0.5.21 · TRT-LLM 1.3.0rc29

No etcd or NATS needed to start: --discovery-backend file uses the filesystem. Add them when you want KV-aware routing locally.

Local · first request

Two processes and a curl

curlOpenAI API frontend:8000route · tokenize workerdynamo.vllm GPU discovery: --discovery-backend file
# terminal 1 · frontend on :8000
python3 -m dynamo.frontend --discovery-backend file

# terminal 2 · a worker (pick your engine)
python3 -m dynamo.vllm --model Qwen/Qwen3-0.6B \
    --discovery-backend file
#   dynamo.sglang / dynamo.trtllm take --model-path

# terminal 3 · ask it something
curl -sf localhost:8000/health && echo OK
curl localhost:8000/v1/chat/completions \
  -H "Content-Type: application/json" \
  -d '{"model":"Qwen/Qwen3-0.6B",
       "messages":[{"role":"user","content":"Hello!"}],
       "max_tokens":50}'

Press → to start the worker, then send a request.

Local · scale the shape

Launch presets: from one worker to disaggregated with a KV router

frontend round-robinKV router workerprefill + decode1 worker Apublishes KV events worker Bpublishes KV events prefill decode P1 D1 KV · NIXL P2D2
quickstart · 1 GPU

One aggregated worker does prefill and decode. Fine for a smoke test and small models.

launch/agg_router.sh · 2 GPUs

Two workers behind the KV-aware router: requests go to the worker that already holds the prefix. vLLM needs PYTHONHASHSEED=0 for matching block hashes.

TRT-LLM preset: 1 worker.

launch/disagg.sh · 2 GPUs

1 prefill + 1 decode worker. KV moves prefill → decode over NIXL. Workers take --disaggregation-mode prefill|decode; vLLM needs a unique VLLM_NIXL_SIDE_CHANNEL_PORT per worker.

launch/disagg_router.sh · 4 GPUs

2 prefill + 2 decode with the KV router picking the prefill worker. TRT-LLM preset: 1P + 1D on 2 GPUs.

# presets live per backend
cd examples/backends/vllm   # or sglang, trtllm
./launch/disagg_router.sh
Kubernetes · install

Install the platform once, add what your workload needs

# 1 · GPU Operator
helm upgrade --install gpu-operator nvidia/gpu-operator \
  -n gpu-operator --create-namespace --wait
# 2 · Dynamo platform: operator + NATS
helm upgrade --install dynamo-platform \
  "$CHARTS/dynamo-platform-${DYNAMO_VERSION}.tgz" \
  -n dynamo-system --create-namespace --wait
# 3 · Hugging Face token (this exact name)
kubectl create secret generic hf-token-secret \
  --from-literal=HF_TOKEN=...
# 4 · check
kubectl get crd | grep dynamo
./deploy/pre-deployment/pre-deployment-check.sh
I need
GPU nodes · Kubernetes ≥ 1.30 GPU Operator dynamo-platform operator · CRDsNATS your deployments DGDDGDRrecipe Grove + KAIor LWS + Volcano Network OperatorRDMA for KV kube-prometheuslive metrics RWX PVCshared model cache

Pick what you need to see which add-on it brings in, and why.

Kubernetes · DynamoGraphDeployment

One DGD describes the whole serving graph

Backend
apiVersion: nvidia.com/v1beta1
kind: DynamoGraphDeployment
metadata: {name: qwen3-32b-agg}
spec:
  components:
  - name: Frontend
    type: frontend
  - name: worker
    type: worker
    replicas: 1
    podTemplate:
      spec:
        containers:
        - name: main
          image: nvcr.io/nvidia/ai-dynamo/vllm-runtime:1.6.0
          envFrom: [{secretRef: {name: hf-token-secret}}]
          command: [python3, -m, dynamo.vllm]
          args: [--model, Qwen/Qwen3-32B, --tensor-parallel-size, "2"]
          resources: {limits: {nvidia.com/gpu: "2"}}
DGD qwen3-32b-agg FrontendService<name>-frontend:8000 worker × replicas dynamo.vllm TP = 2 GPUGPU image: vllm-runtime:1.6.0
GPU limit = TP × PPcontainer must be named mainTP inside a node · PP + multinode.nodeCount across

Hover the YAML to see what each part creates.

Kubernetes · auto-deploy

Not sure how to size it? Ask for an SLA and let the profiler pick

apiVersion: nvidia.com/v1beta1
kind: DynamoGraphDeploymentRequest
metadata: {name: qwen3-quickstart}
spec:
  model: Qwen/Qwen3-0.6B   # the only required field
  backend: auto
  searchStrategy: rapid     # or thorough
  workload: {isl: 4000, osl: 1000}
  sla: {ttft: 2000, itl: 30}  # ms
  autoApply: true
rapid~30 ssimulated with AISim · no GPUs
thorough2–4 hreal GPUs + AIPerf · disagg only
Pending Profiling sweep prefill · sweep decodeselect config · build curvesgenerate DGD Ready Deploying Deployed ✓→ a regular DGD you can read and edit
# review before deploying: set autoApply: false, then
kubectl get dgdr my-model -o jsonpath=\
  '{.status.profilingResults.selectedConfig}' > my-dgd.yaml
Recipes

Recipes: a tested deployment for a model, a backend and a mode

recipes/
└─ qwen3-32b/                # model
   ├─ model-cache/
   │  ├─ model-cache.yaml    # PVC
   │  └─ model-download.yaml # Job
   └─ vllm/                  # backend
      ├─ agg-round-robin/     # mode
      └─ disagg-kv-router/
         ├─ deploy.yaml       # full DGD
         └─ perf.yaml         # AIPerf Job
15model providers
31models
191tested targets
# 1 · token + model cache (edit storageClassName first)
kubectl create secret generic hf-token-secret \
  --from-literal=HF_TOKEN=... -n $NS
kubectl apply -f qwen3-32b/model-cache/ -n $NS
kubectl wait --for=condition=Complete \
  job/model-download -n $NS --timeout=6000s

# 2 · deploy
kubectl apply -n $NS \
  -f qwen3-32b/vllm/disagg-kv-router/deploy.yaml

# 3 · talk to it
kubectl port-forward svc/<name>-frontend 8000:8000

# 4 · benchmark it (optional)
kubectl apply -n $NS -f .../disagg-kv-router/perf.yaml
Recipes · browse

Find your model: providers in the docs' order, most popular first

BackendModeHardwareNumbers
Deploy this target

        
Recipe page in the docs →
Recipes · expected numbers

Recipes publish numbers: compare shapes before you deploy

Optimize · method

Measure, change one thing, measure again

1 · Baselinerecipe or DGDR 2 · BenchmarkAIPerf in-cluster 3 · ReadTTFT · ITL · tok/s/GPU 4 · One changea single knob
pip install aiperf
aiperf profile --model Qwen/Qwen3-32B-FP8 \
  --url http://localhost:8000 --endpoint-type chat \
  --streaming --concurrency 10 --request-count 100 \
  --synthetic-input-tokens-mean 2000 \
  --output-tokens-mean 256
# results → artifacts/
Rules that keep results honest

Pick the SLA first: TTFT and ITL targets decide what "better" means.

One variable per run: agg vs disagg, KV vs round-robin, replicas, TP/PP.

Benchmark inside the cluster, not through a port-forward, then sweep concurrency.

Optimize · what to change

Start from the symptom, not the knob

Lever · KV-aware routing

Route each request to the worker that is cheapest to finish it

# frontend: turn it on (default round-robin)
--router-mode kv
# vLLM worker: publish KV events
--kv-events-config '{"publisher":"zmq",
  "endpoint":"tcp://*:20080",
  "enable_kv_cache_events":true}'
# tune through a policy file
--router-policy-config worker-selection.yaml
overlap_score_creditLower → spread load, lower ITL
prefill_load_scaleRaise → when long prompts saturate workers
Score by
cachedto computealready queued· an 8-block prompt arrives
overlap_score_credit1.0

Lever · disaggregation

Split prefill and decode, but only when your workload calls for it

✅ Use it for
  • Long prompts
  • High concurrency
  • Different TP per phase
⛔ Skip it for
  • Small models
  • Short prompts
  • No fast fabric
🔮 Not sure? Project it first

AISim compares agg vs disagg for your model, traffic and SLA in ~30 s. No GPUs.

pip install aisimulate
aisimulate recommend -c my-model.yaml  # agg vs disagg
🔌 Deployed? Test the transfer path
# from the dynamo-interconnect-check skill
check_interconnect.py env  <recipe>        # NIXL/UCX/NCCL
check_interconnect.py node --pod <worker>  # IB, RDMA, NVLink
check_interconnect.py nixl --pod <worker>  # transfer test
Prefill workers1
Decode workers2
★ best for this load: · live toy model, 6 req/s
TTFT p90–
SLA 1 s
ITL–
SLA 35 ms
Goodput / GPU–

Lever · memory and capacity

Keep KV instead of recomputing it. Let the Planner follow traffic.

KV offloading · pick one backend
LMCache--kv-transfer-config '{"kv_connector":"LMCacheConnectorV1","kv_role":"kv_both",…}'
SGLang HiCache--enable-hierarchical-cache --hicache-ratio 2 --hicache-write-policy write_through
KVCRcross-node KV sharing · see the KVCR deck
FlexKVDYNAMO_USE_FLEXKV=1 · FLEXKV_CPU_CACHE_GB

Size the pod memory limit to hold the host cache.

Planner · in the DGDR
features:
  planner:
    mode: disagg
    optimization_target: sla  # default: throughput
    ttft_ms: 500.0
    itl_ms: 50.0
    enable_throughput_scaling: true  # every 180 s
    enable_load_scaling: true        # every 5 s
    advisory: true   # recommend only
step 1advisorywatch its recommendations
step 2throughputlet it scale
step 3sla+ Prometheus, both scalers
Agent skills

Hand the loop to a coding agent, with guardrails

Interviewworkload · baseline Deploysmoke test MeasureAIPerf Proposeone knob Challengeapprove · reject Stop?ledger · budget
# just state the outcome; skills are picked by intent
"Optimize this deployment for output tokens/s per user
 under a 200 ms TTFT SLO. Budget 8 GPU-hours and stop
 after three failed deployments."
Cheat sheet

Everything on one page

Speaker notes

← → step · N notes · F fullscreen