Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
51 commits
Select commit Hold shift + click to select a range
e9602f0
done
jon-chuang Mar 29, 2025
1dd9ed7
fix
jon-chuang Mar 29, 2025
b65cb3d
improve
jon-chuang Mar 29, 2025
1266f5b
improve
jon-chuang Mar 29, 2025
664dde5
improve
jon-chuang Mar 29, 2025
8b8f56b
improve
jon-chuang Mar 29, 2025
bf923e8
improve
jon-chuang Mar 29, 2025
c043d14
update
jon-chuang Mar 29, 2025
e65b243
mypy
jon-chuang Mar 29, 2025
311ea5d
minor
jon-chuang Mar 29, 2025
2a5befb
log
jon-chuang Mar 29, 2025
070f78c
z
jon-chuang Mar 29, 2025
afccf7b
oops
jon-chuang Mar 29, 2025
8925e41
done
jon-chuang Mar 29, 2025
4ea445b
minor
jon-chuang Mar 29, 2025
0973a76
empty
jon-chuang Mar 30, 2025
5c124ca
logger.info
jon-chuang Apr 3, 2025
b000d39
Merge branch 'main' of https://github.com/ai-dynamo/dynamo into jon-c…
jon-chuang Apr 3, 2025
7be91d1
Merge branch 'main' into jon-chuang/extract-common-config
ishandhanani Apr 3, 2025
c4d9895
improve
jon-chuang Apr 7, 2025
8948fc8
Merge branch 'jon-chuang/extract-common-config' of https://github.com…
jon-chuang Apr 7, 2025
05630f1
fix
jon-chuang Apr 7, 2025
f86d2bd
fix
jon-chuang Apr 7, 2025
ec82dd5
Merge branch 'main' into jon-chuang/extract-common-config
jon-chuang Apr 7, 2025
41957ec
Merge branch 'main' into jon-chuang/extract-common-config
jon-chuang Apr 7, 2025
3dde003
Merge branch 'main' of https://github.com/ai-dynamo/dynamo into jon-c…
jon-chuang Apr 8, 2025
e5d7449
Merge branch 'jon-chuang/extract-common-config' of https://github.com…
jon-chuang Apr 8, 2025
6b49f62
add tests
jon-chuang Apr 8, 2025
76e9570
minor
jon-chuang Apr 8, 2025
9b4539c
oops
jon-chuang Apr 8, 2025
4fd1a83
done
jon-chuang Apr 8, 2025
513c486
done
jon-chuang Apr 8, 2025
83ae44e
done
jon-chuang Apr 8, 2025
d3af2ea
stricter
jon-chuang Apr 8, 2025
b8996f4
fix
jon-chuang Apr 8, 2025
d984167
Merge branch 'main' of https://github.com/ai-dynamo/dynamo into jon-c…
jon-chuang Apr 8, 2025
85d2e95
add backwards compat
jon-chuang Apr 8, 2025
765bb4b
simplify tests
jon-chuang Apr 8, 2025
5d24017
done
jon-chuang Apr 8, 2025
ef64586
Merge branch 'main' into jon-chuang/extract-common-config
ishandhanani Apr 8, 2025
fa0f1b6
mark pre-merge
jon-chuang Apr 8, 2025
702fd0c
Merge branch 'jon-chuang/extract-common-config' of https://github.com…
jon-chuang Apr 8, 2025
3c90836
oops
jon-chuang Apr 8, 2025
4eb8fea
rename tests so that pytest picks them up
jon-chuang Apr 9, 2025
2d06e3f
improve doc comment
jon-chuang Apr 9, 2025
25fc1ad
oops
jon-chuang Apr 9, 2025
f06e9e6
fix test
jon-chuang Apr 9, 2025
7cb53f0
try this
jon-chuang Apr 9, 2025
1ce1c81
hmm
jon-chuang Apr 9, 2025
bc6f8f6
relax
jon-chuang Apr 9, 2025
6ef0750
hmm
jon-chuang Apr 9, 2025
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
11 changes: 6 additions & 5 deletions deploy/dynamo/sdk/src/dynamo/sdk/cli/serve.py
Original file line number Diff line number Diff line change
Expand Up @@ -217,17 +217,17 @@ def serve(
# Initialize service_configs as empty dict if it's None
# Convert nested YAML structure to flat dict with dot notation
for service, configs in yaml_configs.items():
if service not in service_configs:
service_configs[service] = {}
for key, value in configs.items():
if service not in service_configs:
service_configs[service] = {}
service_configs[service][key] = value

# Process service-specific options
cmdline_overrides: t.Dict[str, t.Any] = _parse_service_args(ctx.args)
for service, configs in cmdline_overrides.items():
if service not in service_configs:
service_configs[service] = {}
for key, value in configs.items():
if service not in service_configs:
service_configs[service] = {}
service_configs[service][key] = value

# Process depends
Expand All @@ -243,11 +243,12 @@ def serve(
rich.print(f"DYNAMO_SERVICE_CONFIG={json.dumps(service_configs)}")
sys.exit(0)

configure_server_logging()
# Set environment variable with service configuration
if service_configs:
logger.info(f"Running dynamo serve with service configs {service_configs}")
os.environ["DYNAMO_SERVICE_CONFIG"] = json.dumps(service_configs)

configure_server_logging()
if working_dir is None:
if os.path.isdir(os.path.expanduser(bento)):
working_dir = os.path.expanduser(bento)
Expand Down
36 changes: 32 additions & 4 deletions deploy/dynamo/sdk/src/dynamo/sdk/lib/config.py
Original file line number Diff line number Diff line change
Expand Up @@ -14,8 +14,11 @@
# limitations under the License.

import json
import logging
import os

logger = logging.getLogger(__name__)


class ServiceConfig(dict):
"""Configuration store that inherits from dict for simpler access patterns"""
Expand Down Expand Up @@ -47,14 +50,27 @@ def require(self, service_name, key):
return self[service_name][key]

def as_args(self, service_name, prefix=""):
"""Extract configs as CLI args for a service, with optional prefix filtering"""
"""Extract configs as CLI args for a service, with optional prefix filtering.

Every component will additionally have the args in the `Common` configs
applied if it has subscribed to that config key, i.e. the given key is provided in
the component's `common-configs` setting, and that key has not been overriden by the
component's config.
"""
COMMON_CONFIG_SERVICE = "Common"
COMMON_CONFIG_KEY = "common-configs"

if service_name not in self:
return []

args = []
for key, value in self[service_name].items():
args: list[str] = []

def add_to_args(args: list[str], key: str, value):
if prefix and not key.startswith(prefix):
continue
return

if key.endswith(COMMON_CONFIG_KEY):
return

# Strip prefix if needed
arg_key = key[len(prefix) :] if prefix and key.startswith(prefix) else key
Expand All @@ -68,4 +84,16 @@ def as_args(self, service_name, prefix=""):
else:
args.extend([f"--{arg_key}", str(value)])

if (common := self.get(COMMON_CONFIG_SERVICE)) is not None and (
common_config_keys := self[service_name].get(COMMON_CONFIG_KEY)
) is not None:
for key in common_config_keys:
if key in common and key not in self[service_name]:
add_to_args(args, key, common[key])

for key, value in self[service_name].items():
add_to_args(args, key, value)

logger.info(f"Running {service_name} with {args=}")

return args
30 changes: 17 additions & 13 deletions deploy/dynamo/sdk/src/dynamo/sdk/tests/pipeline.py
Original file line number Diff line number Diff line change
Expand Up @@ -86,15 +86,15 @@ class Backend2:
backend = depends(Backend)

def __init__(self) -> None:
print("Starting middle2")
print("Starting backend2")

@dynamo_endpoint()
async def generate(self, req: RequestType):
"""Forward requests to backend."""

req_text = req.text
print(f"Middle2 received: {req_text}")
text = f"{req_text}-mid2"
print(f"Backend2 received: {req_text}")
text = f"{req_text}-back2"
next_request = RequestType(text=text).model_dump_json()
print(next_request)

Expand All @@ -117,8 +117,17 @@ async def generate(self, req: RequestType):
req_text = req.text
print(f"Middle received: {req_text}")
text = f"{req_text}-mid"
for token in text.split():
yield f"Mid: {token}"

txt = RequestType(text=text)

if self.backend:
async for back_resp in self.backend.generate(txt.model_dump_json()):
print(f"Frontend received back_resp: {back_resp}")
yield f"Frontend: {back_resp}"
else:
async for back_resp in self.backend2.generate(txt.model_dump_json()):
print(f"Frontend received back_resp: {back_resp}")
yield f"Frontend: {back_resp}"


@service(resources={"cpu": "1"}, traffic={"timeout": 60})
Expand All @@ -136,11 +145,6 @@ async def generate(self, text):
print(f"Frontend received type: {type(text)}")
txt = RequestType(text=text)
print(f"Frontend sending: {type(txt)}")
if self.backend:
async for back_resp in self.backend.generate(txt.model_dump_json()):
print(f"Frontend received back_resp: {back_resp}")
yield f"Frontend: {back_resp}"
else:
async for mid_resp in self.middle.generate(txt.model_dump_json()):
print(f"Frontend received mid_resp: {mid_resp}")
yield f"Frontend: {mid_resp}"
async for mid_resp in self.middle.generate(txt.model_dump_json()):
print(f"Frontend received mid_resp: {mid_resp}")
yield f"Frontend: {mid_resp}"
142 changes: 142 additions & 0 deletions deploy/dynamo/sdk/src/dynamo/sdk/tests/test_config.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,142 @@
# SPDX-FileCopyrightText: Copyright (c) 2025 NVIDIA CORPORATION & AFFILIATES. All rights reserved.
# SPDX-License-Identifier: Apache-2.0
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.

import os

import pytest

from dynamo.sdk.lib.config import ServiceConfig

pytestmark = pytest.mark.pre_merge


def test_service_config_with_common_configs():
# Reset singleton instance
ServiceConfig._instance = None

# Set environment variable with config that includes common-configs
os.environ[
"DYNAMO_SERVICE_CONFIG"
] = """
{
"Common": {
"model": "deepseek-ai/DeepSeek-R1-Distill-Llama-8B",
"block-size": 64,
"max-model-len": 16384
},
"VllmWorker": {
"enforce-eager": true,
"common-configs": ["model", "block-size", "max-model-len"]
}
}
"""

# Get arguments and verify common configs are included
service_config = ServiceConfig.get_instance()
vllm_worker_args = service_config.as_args("VllmWorker")

# Check that each common config appears in the arguments
for key in ["model", "block-size", "max-model-len"]:
assert f"--{key}" in vllm_worker_args


def test_service_config_without_common_configs():
# Reset singleton instance
ServiceConfig._instance = None

# Set environment variable with config that DOESN'T include common-configs
os.environ[
"DYNAMO_SERVICE_CONFIG"
] = """
{
"Common": {
"model": "deepseek-ai/DeepSeek-R1-Distill-Llama-8B",
"block-size": 64,
"max-model-len": 16384
},
"VllmWorker": {
"enforce-eager": true
}
}
"""

# Get arguments and verify common configs are NOT included
service_config = ServiceConfig.get_instance()
vllm_worker_args = service_config.as_args("VllmWorker")

# Check that none of the common configs appear in arguments
for key in ["model", "block-size", "max-model-len"]:
assert f"--{key}" not in vllm_worker_args


def test_service_config_with_direct_configs():
# Reset singleton instance
ServiceConfig._instance = None

# Set environment variable with direct configs (no Common section reference)
os.environ[
"DYNAMO_SERVICE_CONFIG"
] = """
{
"VllmWorker": {
"enforce-eager": true,
"model": "deepseek-ai/DeepSeek-R1-Distill-Llama-8B",
"block-size": 64,
"max-model-len": 16384
}
}
"""

# Get arguments and verify direct configs are included
service_config = ServiceConfig.get_instance()
vllm_worker_args = service_config.as_args("VllmWorker")

# Check that each config appears in the arguments
for key in ["model", "block-size", "max-model-len"]:
assert f"--{key}" in vllm_worker_args


def test_service_config_override_common_configs():
# Reset singleton instance
ServiceConfig._instance = None

# Set environment variable with config that includes common-configs
# overridden by the subscribing config
os.environ[
"DYNAMO_SERVICE_CONFIG"
] = """
{
"Common": {
"model": "deepseek-ai/DeepSeek-R1-Distill-Llama-8B",
"block-size": 64,
"max-model-len": 16384
},
"VllmWorker": {
"enforce-eager": true,
"block-size": 128,
"common-configs": ["model", "block-size", "max-model-len"]
}
}
"""

# Get arguments and verify common configs are included
service_config = ServiceConfig.get_instance()
vllm_worker_args = service_config.as_args("VllmWorker")

# Check that each common config appears in the arguments
for key in ["model", "block-size", "max-model-len"]:
assert f"--{key}" in vllm_worker_args

assert vllm_worker_args[vllm_worker_args.index("--block-size") + 1] == "128"
Original file line number Diff line number Diff line change
Expand Up @@ -79,4 +79,4 @@ async def test_pipeline():
if attempt == max_retries - 1:
raise
print(f"Attempt {attempt + 1} failed, retrying...")
await asyncio.sleep(1)
await asyncio.sleep(3)
22 changes: 11 additions & 11 deletions docs/guides/dynamo_serve.md
Original file line number Diff line number Diff line change
Expand Up @@ -162,36 +162,36 @@ This will print out something like
```bash
Service Configuration:
{
"Common": {
"model": "deepseek-ai/DeepSeek-R1-Distill-Llama-8B",
"block-size": 64,
"max-model-len": 16384,
},
"Frontend": {
"served_model_name": "deepseek-ai/DeepSeek-R1-Distill-Llama-8B",
"endpoint": "dynamo.Processor.chat/completions",
"port": 8000
},
"Processor": {
"model": "deepseek-ai/DeepSeek-R1-Distill-Llama-8B",
"block-size": 64,
"max-model-len": 16384,
"router": "round-robin"
"router": "round-robin",
"common-configs": [model, block-size, max-model-len]
},
"VllmWorker": {
"model": "deepseek-ai/DeepSeek-R1-Distill-Llama-8B",
"enforce-eager": true,
"block-size": 64,
"max-model-len": 16384,
"max-num-batched-tokens": 16384,
"enable-prefix-caching": true,
"router": "random",
"tensor-parallel-size": 1,
"ServiceArgs": {
"workers": 1
}
},
"common-configs": [model, block-size, max-model-len]
}
}

Environment Variable that would be set:
DYNAMO_SERVICE_CONFIG={"Frontend": {"served_model_name": "deepseek-ai/DeepSeek-R1-Distill-Llama-8B", "endpoint": "dynamo.Processor.chat/completions", "port": 8000}, "Processor": {"model": "deepseek-ai/DeepSeek-R1-Distill-Llama-8B", "block-size": 64,
"max-model-len": 16384, "router": "round-robin"}, "VllmWorker": {"model": "deepseek-ai/DeepSeek-R1-Distill-Llama-8B", "enforce-eager": true, "block-size": 64, "max-model-len": 16384, "max-num-batched-tokens": 16384, "enable-prefix-caching":
true, "router": "random", "tensor-parallel-size": 1, "ServiceArgs": {"workers": 1}}}
DYNAMO_SERVICE_CONFIG={"Common": {"model": "deepseek-ai/DeepSeek-R1-Distill-Llama-8B", "block-size": 64, "max-model-len": 16384}, "Frontend": {"served_model_name": "deepseek-ai/DeepSeek-R1-Distill-Llama-8B", "endpoint": "dynamo.Processor.chat/completions", "port": 8000}, "Processor": {"router": "round-robin", "common-configs": ["model", "block-size", "max-model-len"]}, "VllmWorker": {"enforce-eager": true, "max-num-batched-tokens": 16384, "enable-prefix-caching":
true, "router": "random", "tensor-parallel-size": 1, "ServiceArgs": {"workers": 1}, "common-configs": ["model", "block-size", "max-model-len"]}}
```

You can override any of these configuration options by passing in CLI flags to serve. For example, to change the routing strategy, you can run
Expand Down
2 changes: 1 addition & 1 deletion examples/llm/components/kv_router.py
Original file line number Diff line number Diff line change
Expand Up @@ -43,7 +43,7 @@ def parse_args(service_name, prefix) -> Namespace:
help="Minimum number of workers required before proceeding",
)
parser.add_argument(
"--model-name",
"--model",
type=str,
default="deepseek-ai/DeepSeek-R1-Distill-Llama-8B",
help="Model that is being served",
Expand Down
12 changes: 6 additions & 6 deletions examples/llm/configs/agg.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -12,23 +12,22 @@
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
Common:
model: deepseek-ai/DeepSeek-R1-Distill-Llama-8B
block-size: 64
max-model-len: 16384

Frontend:
served_model_name: deepseek-ai/DeepSeek-R1-Distill-Llama-8B
endpoint: dynamo.Processor.chat/completions
port: 8000

Processor:
model: deepseek-ai/DeepSeek-R1-Distill-Llama-8B
block-size: 64
max-model-len: 16384
router: round-robin
common-configs: [model, block-size, max-model-len]

VllmWorker:
model: deepseek-ai/DeepSeek-R1-Distill-Llama-8B
enforce-eager: true
block-size: 64
max-model-len: 16384
max-num-batched-tokens: 16384
enable-prefix-caching: true
router: random
Expand All @@ -37,3 +36,4 @@ VllmWorker:
workers: 1
resources:
gpu: 1
common-configs: [model, block-size, max-model-len]
Loading