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
Four ways in. Pick the one that matches where you are.
Install: one container or one wheel
# 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
- 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.
Two processes and a curl
# 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.
Launch presets: from one worker to disaggregated with a KV router
One aggregated worker does prefill and decode. Fine for a smoke test and small models.
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.
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.
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
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
Pick what you need to see which add-on it brings in, and why.
One DGD describes the whole serving graph
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"}}
Hover the YAML to see what each part creates.
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
# review before deploying: set autoApply: false, then
kubectl get dgdr my-model -o jsonpath=\
'{.status.profilingResults.selectedConfig}' > my-dgd.yaml
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
# 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
Find your model: providers in the docs' order, most popular first
| Backend | Mode | Hardware | Numbers |
|---|
Recipes publish numbers: compare shapes before you deploy
output tokens/s per GPU from each recipe's expected performance · agentic trace replays at the stated per-user speed · compare within a card, not across models
Measure, change one thing, measure again
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/
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.
Start from the symptom, not the knob
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 ITLprefill_load_scaleRaise → when long prompts saturate workersoverlap_score_credit1.0Split prefill and decode, but only when your workload calls for it
- Long prompts
- High concurrency
- Different TP per phase
- Small models
- Short prompts
- No fast fabric
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
# 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
Keep KV instead of recomputing it. Let the Planner follow traffic.
--kv-transfer-config '{"kv_connector":"LMCacheConnectorV1","kv_role":"kv_both",…}'--enable-hierarchical-cache --hicache-ratio 2 --hicache-write-policy write_throughcross-node KV sharing · see the KVCR deckDYNAMO_USE_FLEXKV=1 · FLEXKV_CPU_CACHE_GBSize the pod memory limit to hold the host cache.
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
Hand the loop to a coding agent, with guardrails
# 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."
Everything on one page
python3 -m dynamo.frontend python3 -m dynamo.vllm --model …Local Guide → ☸️ Platform
helm upgrade --install \ dynamo-platform …Kubernetes Guide → 🎯 Auto-size
kind: DynamoGraphDeploymentRequest searchStrategy: rapidDGDR + Profiler → 📖 Recipe
kubectl apply -f \ <model>/<backend>/<mode>/deploy.yamlrecipes/ → 📊 Benchmark
aiperf profile --url … \ --concurrency 10 --streamingAIPerf → 🤖 Agents
"Optimize for tok/s/user under a 200 ms TTFT SLO".agents/skills →