Skip to content
GitLab
Menu
Projects
Groups
Snippets
Loading...
Help
Help
Support
Community forum
Keyboard shortcuts
?
Submit feedback
Contribute to GitLab
Sign in
Toggle navigation
Menu
Open sidebar
OpenDAS
dynamo
Commits
68ac71c4
Unverified
Commit
68ac71c4
authored
May 27, 2025
by
Biswa Panda
Committed by
GitHub
May 28, 2025
Browse files
feat: portable dynamo build (#1215)
parent
0594235b
Changes
13
Expand all
Show whitespace changes
Inline
Side-by-side
Showing
13 changed files
with
714 additions
and
76 deletions
+714
-76
deploy/cloud/operator/internal/dynamo/graph.go
deploy/cloud/operator/internal/dynamo/graph.go
+1
-1
deploy/sdk/src/dynamo/sdk/cli/Dockerfile.template
deploy/sdk/src/dynamo/sdk/cli/Dockerfile.template
+44
-0
deploy/sdk/src/dynamo/sdk/cli/build.py
deploy/sdk/src/dynamo/sdk/cli/build.py
+607
-0
deploy/sdk/src/dynamo/sdk/cli/cli.py
deploy/sdk/src/dynamo/sdk/cli/cli.py
+2
-1
deploy/sdk/src/dynamo/sdk/cli/deployment.py
deploy/sdk/src/dynamo/sdk/cli/deployment.py
+1
-1
deploy/sdk/src/dynamo/sdk/cli/serve.py
deploy/sdk/src/dynamo/sdk/cli/serve.py
+1
-1
deploy/sdk/src/dynamo/sdk/cli/utils.py
deploy/sdk/src/dynamo/sdk/cli/utils.py
+3
-7
deploy/sdk/src/dynamo/sdk/core/lib.py
deploy/sdk/src/dynamo/sdk/core/lib.py
+6
-16
deploy/sdk/src/dynamo/sdk/core/protocol/deployment.py
deploy/sdk/src/dynamo/sdk/core/protocol/deployment.py
+4
-0
deploy/sdk/src/dynamo/sdk/core/protocol/interface.py
deploy/sdk/src/dynamo/sdk/core/protocol/interface.py
+38
-28
deploy/sdk/src/dynamo/sdk/core/runner/dynamo.py
deploy/sdk/src/dynamo/sdk/core/runner/dynamo.py
+3
-12
deploy/sdk/src/dynamo/sdk/lib/loader.py
deploy/sdk/src/dynamo/sdk/lib/loader.py
+2
-2
deploy/sdk/src/dynamo/sdk/tests/test_resources.py
deploy/sdk/src/dynamo/sdk/tests/test_resources.py
+2
-7
No files found.
deploy/cloud/operator/internal/dynamo/graph.go
View file @
68ac71c4
...
...
@@ -196,7 +196,7 @@ func RetrieveDynamoGraphConfigurationFile(ctx context.Context, url string) (*byt
}
// Extract the YAML file
yamlFileName
:=
"
bent
o.yaml"
yamlFileName
:=
"
dynam
o.yaml"
yamlContent
,
err
:=
archive
.
ExtractFileFromTar
(
tarData
,
yamlFileName
)
if
err
!=
nil
{
return
nil
,
err
...
...
deploy/sdk/src/dynamo/sdk/cli/Dockerfile.template
0 → 100644
View file @
68ac71c4
# Use ARG to allow base image to be specified at build time
ARG BASE_IMAGE=__BASE_IMAGE__
FROM ${BASE_IMAGE}
# Build arguments for user configuration
ARG USER_ID=1024
ARG GROUP_ID=1024
ARG USERNAME=dynamo
ARG GROUPNAME=dynamo
ARG HOME_DIR=/home/${USERNAME}
# Set environment variables
ENV PYTHONUNBUFFERED=1
ENV PYTHONDONTWRITEBYTECODE=1
ENV PATH="${HOME_DIR}/.local/bin:$PATH"
ENV PYTHONPATH="${HOME_DIR}/app:$PYTHONPATH"
# Create group and user
RUN if [ "$(id -u)" != "0" ]; then \
echo "Using sudo for user/group creation"; \
sudo groupadd --gid ${GROUP_ID} ${GROUPNAME} \
&& sudo useradd --uid ${USER_ID} --gid ${GROUP_ID} --create-home --shell /bin/bash ${USERNAME} \
&& sudo mkdir -p ${HOME_DIR}/app \
&& sudo mkdir -p ${HOME_DIR}/.local/bin \
&& sudo mkdir -p ${HOME_DIR}/.cache/pip \
&& sudo chown -R ${USERNAME}:${GROUPNAME} ${HOME_DIR}; \
else \
echo "Running as root, no sudo needed"; \
groupadd --gid ${GROUP_ID} ${GROUPNAME} \
&& useradd --uid ${USER_ID} --gid ${GROUP_ID} --create-home --shell /bin/bash ${USERNAME} \
&& mkdir -p ${HOME_DIR}/app \
&& mkdir -p ${HOME_DIR}/.local/bin \
&& mkdir -p ${HOME_DIR}/.cache/pip \
&& chown -R ${USERNAME}:${GROUPNAME} ${HOME_DIR}; \
fi
# Switch to non-root user
USER ${USERNAME}
WORKDIR ${HOME_DIR}/app
# Copy application code
COPY --chown=${USERNAME}:${GROUPNAME} . .
RUN chmod +x ${HOME_DIR}/app
deploy/sdk/src/dynamo/sdk/cli/build.py
0 → 100644
View file @
68ac71c4
This diff is collapsed.
Click to expand it.
deploy/sdk/src/dynamo/sdk/cli/cli.py
View file @
68ac71c4
...
...
@@ -22,10 +22,11 @@ import importlib.metadata
import
typer
from
rich.console
import
Console
from
dynamo.sdk.cli.build
import
build
from
dynamo.sdk.cli.deployment
import
app
as
deployment_app
from
dynamo.sdk.cli.deployment
import
deploy
from
dynamo.sdk.cli.env
import
env
from
dynamo.sdk.cli.pipeline
import
build
,
get
from
dynamo.sdk.cli.pipeline
import
get
from
dynamo.sdk.cli.run
import
run
from
dynamo.sdk.cli.serve
import
serve
...
...
deploy/sdk/src/dynamo/sdk/cli/deployment.py
View file @
68ac71c4
...
...
@@ -140,7 +140,7 @@ def _handle_deploy_create(
# TODO: hardcoding this is a hack to get the services for the deployment
# we should find a better way to do this once build is finished/generic
configure_target_environment
(
TargetEnum
.
BENT
O
)
configure_target_environment
(
TargetEnum
.
DYNAM
O
)
entry_service
=
load_entry_service
(
pipeline
)
deployment_manager
=
get_deployment_manager
(
target
,
endpoint
)
...
...
deploy/sdk/src/dynamo/sdk/cli/serve.py
View file @
68ac71c4
...
...
@@ -164,7 +164,7 @@ def serve(
sys
.
path
.
insert
(
0
,
working_dir_str
)
svc
=
find_and_load_service
(
dynamo_pipeline
,
working_dir
=
working_dir
)
logger
.
info
(
f
"Loaded service:
{
svc
.
name
}
"
)
logger
.
debug
(
f
"Loaded service:
{
svc
.
name
}
"
)
logger
.
debug
(
"Dependencies: %s"
,
[
dep
.
on
.
name
for
dep
in
svc
.
dependencies
.
values
()])
LinkedServices
.
remove_unused_edges
()
...
...
deploy/sdk/src/dynamo/sdk/cli/utils.py
View file @
68ac71c4
...
...
@@ -363,11 +363,7 @@ def resolve_service_config(
def
configure_target_environment
(
target
:
TargetEnum
):
from
dynamo.sdk.core.lib
import
set_target
if
target
==
TargetEnum
.
BENTO
:
from
dynamo.sdk.core.runner.bentoml
import
BentoDeploymentTarget
target
=
BentoDeploymentTarget
()
elif
target
==
TargetEnum
.
DYNAMO
:
if
target
==
TargetEnum
.
DYNAMO
:
from
dynamo.sdk.core.runner.dynamo
import
LocalDeploymentTarget
target
=
LocalDeploymentTarget
()
...
...
@@ -393,7 +389,7 @@ def is_local_planner_enabled(svc: Any, service_configs: dict) -> bool:
planners
=
[
node
for
node
in
nodes
if
node
.
config
.
get
(
"
dynamo
"
,
{}).
get
(
"
component_type
"
)
==
ComponentType
.
PLANNER
if
node
.
config
.
dynamo
.
component_type
==
ComponentType
.
PLANNER
]
if
len
(
planners
)
>
1
:
...
...
@@ -429,7 +425,7 @@ def raise_local_planner_warning(svc: Any, service_configs: dict) -> None:
nodes
.
append
(
svc
)
worker_names
=
(
"PrefillWorker"
,
"VllmWorker"
)
worker_counts_greater_than_one
=
[
node
.
config
.
get
(
"
workers
"
,
1
)
>
1
for
node
in
nodes
if
node
.
name
in
worker_names
node
.
config
.
workers
>
1
for
node
in
nodes
if
node
.
name
in
worker_names
]
if
any
(
worker_counts_greater_than_one
)
and
not
no_op
:
...
...
deploy/sdk/src/dynamo/sdk/core/lib.py
View file @
68ac71c4
...
...
@@ -14,15 +14,15 @@
# limitations under the License.
# Modifications Copyright (c) 2025 NVIDIA CORPORATION & AFFILIATES
import
logging
import
os
from
typing
import
Any
,
Callable
,
Dict
,
Optional
,
Type
,
TypeVar
,
Union
from
typing
import
Any
,
Callable
,
Optional
,
Type
,
TypeVar
from
fastapi
import
FastAPI
from
dynamo.sdk.core.protocol.interface
import
(
DependencyInterface
,
DeploymentTarget
,
DynamoConfig
,
ServiceConfig
,
ServiceInterface
,
)
...
...
@@ -33,6 +33,7 @@ G = TypeVar("G", bound=Callable[..., Any])
# this should be set to a concrete implementation of the DeploymentTarget interface
_target
:
DeploymentTarget
logger
=
logging
.
getLogger
(
__name__
)
DYNAMO_IMAGE
=
os
.
getenv
(
"DYNAMO_IMAGE"
,
"dynamo:latest-vllm"
)
...
...
@@ -49,36 +50,25 @@ def get_target() -> DeploymentTarget:
return
_target
# TODO: dynamo_component
def
service
(
inner
:
Optional
[
Type
[
G
]]
=
None
,
/
,
*
,
dynamo
:
Optional
[
Union
[
Dict
[
str
,
Any
],
DynamoConfig
]]
=
None
,
app
:
Optional
[
FastAPI
]
=
None
,
system_app
:
Optional
[
FastAPI
]
=
None
,
**
kwargs
:
Any
,
)
->
Any
:
"""Service decorator that's adapter-agnostic"""
config
=
ServiceConfig
(
kwargs
)
# Parse dict into DynamoConfig object
dynamo_config
:
Optional
[
DynamoConfig
]
=
None
if
dynamo
is
not
None
:
if
isinstance
(
dynamo
,
dict
):
dynamo_config
=
DynamoConfig
(
**
dynamo
)
else
:
dynamo_config
=
dynamo
assert
isinstance
(
dynamo_config
,
DynamoConfig
)
config
=
ServiceConfig
(
**
kwargs
)
logger
.
info
(
f
"inner:
{
inner
}
config:
{
config
}
"
)
def
decorator
(
inner
:
Type
[
G
])
->
ServiceInterface
[
G
]:
provider
=
get_target
()
if
inner
is
not
None
:
dynamo_config
.
name
=
inner
.
__name__
config
.
dynamo
.
name
=
inner
.
__name__
return
provider
.
create_service
(
service_cls
=
inner
,
config
=
config
,
dynamo_config
=
dynamo_config
,
app
=
app
,
system_app
=
system_app
,
**
kwargs
,
...
...
deploy/sdk/src/dynamo/sdk/core/protocol/deployment.py
View file @
68ac71c4
...
...
@@ -97,12 +97,16 @@ class DeploymentStatus(str, Enum):
@
dataclass
class
ScalingPolicy
:
"""Scaling policy."""
policy
:
str
parameters
:
t
.
Dict
[
str
,
t
.
Union
[
int
,
float
,
str
]]
=
field
(
default_factory
=
dict
)
@
dataclass
class
Env
:
"""Environment variable."""
name
:
str
value
:
str
=
""
...
...
deploy/sdk/src/dynamo/sdk/core/protocol/interface.py
View file @
68ac71c4
...
...
@@ -16,17 +16,39 @@
from
abc
import
ABC
,
abstractmethod
from
collections
import
defaultdict
from
dataclasses
import
dataclass
from
enum
import
Enum
,
auto
from
typing
import
Any
,
Dict
,
Generic
,
List
,
Optional
,
Set
,
Tuple
,
Type
,
TypeVar
from
fastapi
import
FastAPI
from
pydantic
import
BaseModel
from
dynamo.sdk.core.protocol.deployment
import
Env
T
=
TypeVar
(
"T"
,
bound
=
object
)
class
LeaseConfig
(
BaseModel
):
"""Configuration for custom dynamo leases"""
ttl
:
int
=
1
# seconds
class
ComponentType
:
"""Types of Dynamo components"""
PLANNER
=
"planner"
class
DynamoConfig
(
BaseModel
):
"""Configuration for Dynamo components"""
enabled
:
bool
=
True
name
:
str
|
None
=
None
namespace
:
str
|
None
=
None
custom_lease
:
LeaseConfig
|
None
=
None
component_type
:
str
|
None
=
None
# Indicates if this is a meta/system component
class
DynamoTransport
(
Enum
):
"""Transport types supported by Dynamo services"""
...
...
@@ -34,10 +56,23 @@ class DynamoTransport(Enum):
HTTP
=
auto
()
class
ServiceConfig
(
Dict
[
str
,
Any
]):
class
ResourceConfig
(
BaseModel
):
"""Configuration for Dynamo resources"""
cpu
:
int
=
1
memory
:
str
=
"100Mi"
gpu
:
str
=
"0"
class
ServiceConfig
(
BaseModel
):
"""Base service configuration that can be extended by adapters"""
pass
dynamo
:
DynamoConfig
resource
:
ResourceConfig
=
ResourceConfig
()
workers
:
int
=
1
image
:
str
|
None
=
None
envs
:
List
[
Env
]
|
None
=
None
labels
:
Dict
[
str
,
str
]
|
None
=
None
class
DynamoEndpointInterface
(
ABC
):
...
...
@@ -157,30 +192,6 @@ class ServiceInterface(Generic[T], ABC):
raise
NotImplementedError
()
@
dataclass
class
LeaseConfig
:
"""Configuration for custom dynamo leases"""
ttl
:
int
=
1
# seconds
class
ComponentType
:
"""Types of Dynamo components"""
PLANNER
=
"planner"
@
dataclass
class
DynamoConfig
:
"""Configuration for Dynamo components"""
enabled
:
bool
=
True
name
:
str
|
None
=
None
namespace
:
str
|
None
=
None
custom_lease
:
LeaseConfig
|
None
=
None
component_type
:
str
|
None
=
None
# Indicates if this is a meta/system component
class
DeploymentTarget
(
ABC
):
"""Interface for service provider implementations"""
...
...
@@ -189,7 +200,6 @@ class DeploymentTarget(ABC):
self
,
service_cls
:
Type
[
T
],
config
:
ServiceConfig
,
dynamo_config
:
Optional
[
DynamoConfig
]
=
None
,
app
:
Optional
[
FastAPI
]
=
None
,
**
kwargs
,
)
->
ServiceInterface
[
T
]:
...
...
deploy/sdk/src/dynamo/sdk/core/runner/dynamo.py
View file @
68ac71c4
...
...
@@ -19,7 +19,6 @@ import logging
import
os
import
shlex
import
sys
from
dataclasses
import
asdict
from
typing
import
Any
,
Dict
,
List
,
Optional
,
Set
,
Type
,
TypeVar
import
psutil
...
...
@@ -33,7 +32,6 @@ from dynamo.sdk.core.protocol.deployment import Env
from
dynamo.sdk.core.protocol.interface
import
(
DependencyInterface
,
DeploymentTarget
,
DynamoConfig
,
DynamoEndpointInterface
,
DynamoTransport
,
LinkedServices
,
...
...
@@ -71,20 +69,15 @@ class LocalService(ServiceMixin, ServiceInterface[T]):
self
,
inner_cls
:
Type
[
T
],
config
:
ServiceConfig
,
dynamo_config
:
Optional
[
DynamoConfig
]
=
None
,
watcher
:
Optional
[
Watcher
]
=
None
,
socket
:
Optional
[
CircusSocket
]
=
None
,
app
:
Optional
[
FastAPI
]
=
None
,
system_app
:
Optional
[
FastAPI
]
=
None
,
):
self
.
_inner_cls
=
inner_cls
self
.
_config
=
config
name
=
inner_cls
.
__name__
self
.
_dynamo_config
=
dynamo_config
or
DynamoConfig
(
name
=
name
,
namespace
=
"default"
)
# Add the dynamo config to the service config
self
.
_config
[
"dynamo"
]
=
asdict
(
self
.
_dynamo_
config
)
self
.
_config
=
config
self
.
_watcher
=
watcher
self
.
_socket
=
socket
self
.
app
=
app
or
FastAPI
(
title
=
name
)
...
...
@@ -120,7 +113,7 @@ class LocalService(ServiceMixin, ServiceInterface[T]):
@
property
def
envs
(
self
)
->
List
[
Env
]:
return
self
.
_config
.
get
(
"
envs
"
,
[]
)
return
self
.
_config
.
envs
or
[]
@
property
def
inner
(
self
)
->
Type
[
T
]:
...
...
@@ -148,7 +141,7 @@ class LocalService(ServiceMixin, ServiceInterface[T]):
del
self
.
_dependencies
[
dep_key
]
def
dynamo_address
(
self
)
->
tuple
[
str
,
str
]:
return
(
self
.
_
dynamo_config
.
namespace
,
self
.
_
dynamo_config
.
name
)
return
(
self
.
_
config
.
dynamo
.
namespace
,
self
.
_
config
.
dynamo
.
name
)
@
property
def
dependencies
(
self
)
->
dict
[
str
,
"DependencyInterface"
]:
...
...
@@ -217,7 +210,6 @@ class LocalDeploymentTarget(DeploymentTarget):
self
,
service_cls
:
Type
[
T
],
config
:
ServiceConfig
,
dynamo_config
:
Optional
[
DynamoConfig
]
=
None
,
app
:
Optional
[
FastAPI
]
=
None
,
system_app
:
Optional
[
FastAPI
]
=
None
,
**
kwargs
,
...
...
@@ -261,7 +253,6 @@ class LocalDeploymentTarget(DeploymentTarget):
return
LocalService
(
inner_cls
=
service_cls
,
config
=
config
,
dynamo_config
=
dynamo_config
,
watcher
=
watcher
,
socket
=
socket
,
)
...
...
deploy/sdk/src/dynamo/sdk/lib/loader.py
View file @
68ac71c4
...
...
@@ -208,7 +208,7 @@ def _get_dir_size(path: str) -> int:
def
load_entry_service
(
pipeline_tag
:
str
,
build_dir
:
str
=
"~/
bentoml/bento
s"
pipeline_tag
:
str
,
build_dir
:
str
=
"~/
.dynamo/package
s"
)
->
Service
:
"""
Given a built pipeline tag (e.g. frontend:2uk2fwzvqsswvs7t), load the entry service as a deployment Service instance.
...
...
@@ -220,7 +220,7 @@ def load_entry_service(
if
not
os
.
path
.
isdir
(
graph_dir
):
raise
FileNotFoundError
(
f
"Pipeline directory not found:
{
graph_dir
}
"
)
config_path
=
os
.
path
.
join
(
graph_dir
,
"
bent
o.yaml"
)
config_path
=
os
.
path
.
join
(
graph_dir
,
"
dynam
o.yaml"
)
if
not
os
.
path
.
isfile
(
config_path
):
raise
FileNotFoundError
(
f
"Pipeline config (bento.yaml) not found in
{
graph_dir
}
"
...
...
deploy/sdk/src/dynamo/sdk/tests/test_resources.py
View file @
68ac71c4
...
...
@@ -23,14 +23,12 @@ pytestmark = pytest.mark.pre_merge
@
pytest
.
fixture
(
scope
=
"module"
,
autouse
=
True
)
def
setup_and_teardown
():
configure_target_environment
(
TargetEnum
.
BENTO
)
yield
configure_target_environment
(
TargetEnum
.
DYNAMO
)
yield
def
test_gpu_resources
(
setup_and_teardown
):
"""Test resource configurations"""
from
_bentoml_sdk
import
Service
as
BentoService
from
dynamo.sdk
import
service
...
...
@@ -42,7 +40,4 @@ def test_gpu_resources(setup_and_teardown):
def
__init__
(
self
)
->
None
:
pass
svc
:
BentoService
=
MyService
.
get_bentoml_service
()
# type: ignore
assert
svc
.
config
[
"resources"
][
"cpu"
]
==
"2"
assert
svc
.
config
[
"resources"
][
"gpu"
]
==
"1"
assert
svc
.
config
[
"resources"
][
"memory"
]
==
"4Gi"
assert
MyService
.
config
is
not
None
# type: ignore
Write
Preview
Markdown
is supported
0%
Try again
or
attach a new file
.
Attach a file
Cancel
You are about to add
0
people
to the discussion. Proceed with caution.
Finish editing this message first!
Cancel
Please
register
or
sign in
to comment