Skip to content

Commit 24f7d7a

Browse files
un-defclaude
andauthored
Rework Dynamo docs around a dedicated etcd group (#4402)
Update the Dynamo example and the Dynamo tabs in concepts/services.md to run etcd as its own replica group that the frontend and workers reach via ${{ groups[0].replicas[0].IP_ADDRESS }}. etcd and NATS no longer run inside the router job, so the docs no longer depend on DSTACK_ROUTER_INTERNAL_IP, which is going to be deprecated. The examples now use Dynamo 1.5.1 with vLLM workers. Dynamo's default ZMQ event plane removes the need for NATS, and the frontend uses vLLM's GLM parsers because Dynamo's glm47 parser drops GLM-4.5 tool call arguments. Prefill workers publish KV events for KV-aware routing. The Router section's Dynamo tab switches from Qwen3.8-Flash-Next to GLM-4.5-Air-FP8, which Dynamo 1.5.1 supports. Also fix the dynamo router description and a code comment that said workers are discovered via etcd/NATS. Co-authored-by: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
1 parent e99ed86 commit 24f7d7a

4 files changed

Lines changed: 165 additions & 182 deletions

File tree

‎mkdocs/docs/concepts/services.md‎

Lines changed: 96 additions & 116 deletions
Original file line numberDiff line numberDiff line change
@@ -170,7 +170,7 @@ both are set per group.
170170
Router sits in front of inference backend workers (SGLang, vLLM, TensorRT-LLM) and decides
171171
which worker serves each request based on routing policy.
172172

173-
Below is an example for running `Qwen/Qwen3.8-Flash-Next` on `H200`:
173+
Below are examples for running `Qwen/Qwen3.8-Flash-Next` with SMG and `zai-org/GLM-4.5-Air-FP8` with Dynamo on `H200`:
174174

175175
=== "SMG"
176176

@@ -234,77 +234,64 @@ Below is an example for running `Qwen/Qwen3.8-Flash-Next` on `H200`:
234234

235235
```yaml
236236
type: service
237-
name: qwen38-flash-next-dynamo
237+
name: glm-45-air-dynamo
238238

239239
env:
240240
- HF_TOKEN
241-
- MODEL_ID=Qwen/Qwen3.8-Flash-Next
241+
- MODEL_ID=zai-org/GLM-4.5-Air-FP8
242242

243243
groups:
244-
- replicas: 1
245-
docker: true
244+
# The frontend and the workers discover each other via etcd
245+
- name: etcd
246+
replicas: 1
246247
commands:
247-
- apt-get update
248-
- apt-get install -y python3-dev python3-venv
249-
- python3 -m venv ~/dyn-venv
250-
- source ~/dyn-venv/bin/activate
251-
- pip install -U pip
252-
- pip install "ai-dynamo[sglang]==1.1.1"
253-
- git clone https://github.com/ai-dynamo/dynamo.git
254-
# Brings up the NATS / etcd compose stack and runs the Dynamo HTTP frontend.
255-
- docker compose -f dynamo/dev/docker-compose.yml up -d
256248
- |
257-
python3 -m dynamo.frontend \
258-
--http-host 0.0.0.0 --http-port 8000 \
259-
--discovery-backend etcd --router-mode kv \
260-
--kv-cache-block-size 64
249+
curl -fsSL https://github.com/etcd-io/etcd/releases/download/v3.5.33/etcd-v3.5.33-linux-amd64.tar.gz \
250+
| tar -xz -C /usr/local/bin --strip-components=1 etcd-v3.5.33-linux-amd64/etcd
251+
- |
252+
etcd \
253+
--data-dir /tmp/etcd \
254+
--listen-client-urls http://0.0.0.0:2379 \
255+
--advertise-client-urls http://$DSTACK_MASTER_NODE_IP:2379
261256
resources:
262-
cpu: 4
257+
cpu: 2
258+
259+
- name: frontend
260+
replicas: 1
261+
# The vLLM image provides vLLM's GLM tool call and reasoning parsers
262+
image: nvcr.io/nvidia/ai-dynamo/vllm-runtime:1.5.1
263+
commands:
264+
- export ETCD_ENDPOINTS=http://${{ groups[0].replicas[0].IP_ADDRESS }}:2379
265+
- |
266+
python3 -m dynamo.frontend \
267+
--http-port 8000 \
268+
--router-mode kv \
269+
--dyn-chat-processor vllm \
270+
--enable-auto-tool-choice \
271+
--tool-call-parser glm45 \
272+
--reasoning-parser glm45
263273
router:
264274
type: dynamo
275+
resources:
276+
cpu: 4
265277

266-
- replicas: 2
267-
python: "3.12"
268-
nvcc: true
278+
- name: worker
279+
replicas: 2
280+
image: nvcr.io/nvidia/ai-dynamo/vllm-runtime:1.5.1
269281
commands:
270-
# dstack injects DSTACK_ROUTER_INTERNAL_IP after the router replica
271-
# is provisioned. Compose the etcd/NATS endpoints from it.
272-
- export ETCD_ENDPOINTS="http://$DSTACK_ROUTER_INTERNAL_IP:2379"
273-
- export NATS_SERVER="nats://$DSTACK_ROUTER_INTERNAL_IP:4222"
274-
# Set to enable /health endpoint required by dstack probes.
275-
- export DYN_SYSTEM_PORT="8000"
276-
# Wait until the router's etcd and NATS ports are actually accepting connections.
277-
- |
278-
until curl -fsS "http://$DSTACK_ROUTER_INTERNAL_IP:2379/health" \
279-
&& curl -fsS "http://$DSTACK_ROUTER_INTERNAL_IP:8222/healthz"; do
280-
echo "waiting for etcd/NATS on $DSTACK_ROUTER_INTERNAL_IP..."; sleep 3
281-
done
282-
- pip install "ai-dynamo[sglang]==1.1.1"
282+
- export ETCD_ENDPOINTS=http://${{ groups[0].replicas[0].IP_ADDRESS }}:2379
283+
# vLLM binds the KV events endpoint only if it's a wildcard address, otherwise it connects
283284
- |
284-
python3 -m dynamo.sglang \
285-
--model-path $MODEL_ID \
286-
--served-model-name $MODEL_ID \
287-
--discovery-backend etcd \
288-
--host 0.0.0.0 \
289-
--page-size 64 \
290-
--tp $DSTACK_GPUS_NUM \
291-
--ep $DSTACK_GPUS_NUM \
292-
--mem-fraction-static 0.85 \
293-
--chunked-prefill-size 8192 \
294-
--linear-attn-prefill-backend flashinfer \
295-
--linear-attn-decode-backend flashinfer \
296-
--mamba-ssm-dtype bfloat16 \
297-
--reasoning-parser auto
285+
python3 -m dynamo.vllm \
286+
--model $MODEL_ID \
287+
--max-model-len 65536 \
288+
--kv-events-config '{"publisher":"zmq","topic":"kv-events","endpoint":"tcp://*:20080","enable_kv_cache_events":true}'
298289
resources:
299-
gpu: H200:4
290+
gpu: H200
291+
disk: 200GB..
300292

301293
port: 8000
302-
model: Qwen/Qwen3.8-Flash-Next
303-
304-
probes:
305-
- type: http
306-
url: /health
307-
interval: 15s
294+
model: zai-org/GLM-4.5-Air-FP8
308295
```
309296

310297
</div>
@@ -418,84 +405,77 @@ Below is an example for running `zai-org/GLM-4.5-Air-FP8` on `H200`:
418405
- MODEL_ID=zai-org/GLM-4.5-Air-FP8
419406

420407
groups:
421-
- replicas: 1
422-
docker: true
408+
# The frontend and the workers discover each other via etcd
409+
- name: etcd
410+
replicas: 1
423411
commands:
424-
- apt-get update
425-
- apt-get install -y python3-dev python3-venv
426-
- python3 -m venv ~/dyn-venv
427-
- source ~/dyn-venv/bin/activate
428-
- pip install -U pip
429-
- pip install "ai-dynamo[sglang]==1.1.1"
430-
- git clone https://github.com/ai-dynamo/dynamo.git
431-
# Brings up the NATS / etcd compose stack and runs the Dynamo HTTP frontend.
432-
- docker compose -f dynamo/dev/docker-compose.yml up -d
433412
- |
434-
python3 -m dynamo.frontend \
435-
--http-host 0.0.0.0 --http-port 8000 \
436-
--discovery-backend etcd --router-mode kv \
437-
--kv-cache-block-size 64
413+
curl -fsSL https://github.com/etcd-io/etcd/releases/download/v3.5.33/etcd-v3.5.33-linux-amd64.tar.gz \
414+
| tar -xz -C /usr/local/bin --strip-components=1 etcd-v3.5.33-linux-amd64/etcd
415+
- |
416+
etcd \
417+
--data-dir /tmp/etcd \
418+
--listen-client-urls http://0.0.0.0:2379 \
419+
--advertise-client-urls http://$DSTACK_MASTER_NODE_IP:2379
438420
resources:
439-
cpu: 4
421+
cpu: 2
422+
423+
- name: frontend
424+
replicas: 1
425+
# The vLLM image provides vLLM's GLM tool call and reasoning parsers
426+
image: nvcr.io/nvidia/ai-dynamo/vllm-runtime:1.5.1
427+
commands:
428+
- export ETCD_ENDPOINTS=http://${{ groups[0].replicas[0].IP_ADDRESS }}:2379
429+
- |
430+
python3 -m dynamo.frontend \
431+
--http-port 8000 \
432+
--router-mode kv \
433+
--dyn-chat-processor vllm \
434+
--enable-auto-tool-choice \
435+
--tool-call-parser glm45 \
436+
--reasoning-parser glm45
440437
router:
441438
type: dynamo
439+
resources:
440+
cpu: 4
442441

443-
- replicas: 4
444-
python: "3.12"
445-
nvcc: true
442+
# Only prefill workers publish KV events, as Dynamo's decode routing ignores prefix overlap
443+
- name: prefill-worker
444+
replicas: 4
445+
image: nvcr.io/nvidia/ai-dynamo/vllm-runtime:1.5.1
446446
commands:
447-
# dstack injects DSTACK_ROUTER_INTERNAL_IP after the router replica
448-
# is provisioned. Compose the etcd/NATS endpoints from it.
449-
- export ETCD_ENDPOINTS="http://$DSTACK_ROUTER_INTERNAL_IP:2379"
450-
- export NATS_SERVER="nats://$DSTACK_ROUTER_INTERNAL_IP:4222"
451-
# Set to enable /health endpoint required by dstack probes.
452-
- export DYN_SYSTEM_PORT="8000"
453-
# Wait until the router's etcd and NATS are healthy.
447+
- export ETCD_ENDPOINTS=http://${{ groups[0].replicas[0].IP_ADDRESS }}:2379
448+
- export VLLM_NIXL_SIDE_CHANNEL_HOST=$DSTACK_MASTER_NODE_IP
449+
# vLLM binds the KV events endpoint only if it's a wildcard address, otherwise it connects
454450
- |
455-
until curl -fsS "http://$DSTACK_ROUTER_INTERNAL_IP:2379/health" \
456-
&& curl -fsS "http://$DSTACK_ROUTER_INTERNAL_IP:8222/healthz"; do
457-
echo "waiting for etcd/NATS on $DSTACK_ROUTER_INTERNAL_IP..."; sleep 3
458-
done
459-
- pip install "ai-dynamo[sglang]==1.1.1"
460-
- |
461-
python3 -m dynamo.sglang \
462-
--model-path $MODEL_ID --served-model-name $MODEL_ID \
463-
--discovery-backend etcd --host 0.0.0.0 \
464-
--page-size 64 \
465-
--disaggregation-mode prefill --disaggregation-transfer-backend nixl
451+
python3 -m dynamo.vllm \
452+
--model $MODEL_ID \
453+
--max-model-len 65536 \
454+
--disaggregation-mode prefill \
455+
--kv-transfer-config '{"kv_connector":"NixlConnector","kv_role":"kv_producer"}' \
456+
--kv-events-config '{"publisher":"zmq","topic":"kv-events","endpoint":"tcp://*:20080","enable_kv_cache_events":true}'
466457
resources:
467458
gpu: H200
459+
disk: 200GB..
468460

469-
- replicas: 8
470-
python: "3.12"
471-
nvcc: true
461+
- name: decode-worker
462+
replicas: 8
463+
image: nvcr.io/nvidia/ai-dynamo/vllm-runtime:1.5.1
472464
commands:
473-
- export ETCD_ENDPOINTS="http://$DSTACK_ROUTER_INTERNAL_IP:2379"
474-
- export NATS_SERVER="nats://$DSTACK_ROUTER_INTERNAL_IP:4222"
475-
- export DYN_SYSTEM_PORT="8000"
476-
- |
477-
until curl -fsS "http://$DSTACK_ROUTER_INTERNAL_IP:2379/health" \
478-
&& curl -fsS "http://$DSTACK_ROUTER_INTERNAL_IP:8222/healthz"; do
479-
echo "waiting for etcd/NATS on $DSTACK_ROUTER_INTERNAL_IP..."; sleep 3
480-
done
481-
- pip install "ai-dynamo[sglang]==1.1.1"
465+
- export ETCD_ENDPOINTS=http://${{ groups[0].replicas[0].IP_ADDRESS }}:2379
466+
- export VLLM_NIXL_SIDE_CHANNEL_HOST=$DSTACK_MASTER_NODE_IP
482467
- |
483-
python3 -m dynamo.sglang \
484-
--model-path $MODEL_ID --served-model-name $MODEL_ID \
485-
--discovery-backend etcd --host 0.0.0.0 \
486-
--page-size 64 \
487-
--disaggregation-mode decode --disaggregation-transfer-backend nixl
468+
python3 -m dynamo.vllm \
469+
--model $MODEL_ID \
470+
--max-model-len 65536 \
471+
--disaggregation-mode decode \
472+
--kv-transfer-config '{"kv_connector":"NixlConnector","kv_role":"kv_consumer"}'
488473
resources:
489474
gpu: H200
475+
disk: 200GB..
490476

491477
port: 8000
492478
model: zai-org/GLM-4.5-Air-FP8
493-
494-
# Custom probe is required for PD disaggregation.
495-
probes:
496-
- type: http
497-
url: /health
498-
interval: 15s
499479
```
500480

501481
</div>

0 commit comments

Comments
 (0)