Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
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
2 changes: 1 addition & 1 deletion .github/workflows/copyright-check.ps1
Original file line number Diff line number Diff line change
Expand Up @@ -84,7 +84,7 @@ $global:copyright_results = @{

$ignored_files = @('.clang-format', '.gitattributes', '.gitignore', '.gitkeep', '.patch', 'Cargo.lock', 'LICENSE', 'uv.lock', 'rust-toolchain.toml', 'codespell.txt', 'exclusions.txt')
write-debug "<copyright-check> ignored_files = ['$($ignored_files -join "','")']."
$ignored_paths = @('.github', '.mypy_cache', '.pytest_cache', 'lib/llm/tests/data/sample-models', 'lib/llm/tests/data/deepseek-v3.2', 'lib/llm/tests/data/deepseek-v4', 'container/compliance/spdx_licenses', 'lib/sidecar/vllm/proto/vllm_grpc.proto')
$ignored_paths = @('.github', '.mypy_cache', '.pytest_cache', 'lib/llm/tests/data/sample-models', 'lib/llm/tests/data/deepseek-v3.2', 'lib/llm/tests/data/deepseek-v4', 'container/compliance/spdx_licenses', 'lib/sidecar/vllm/proto/control.proto', 'lib/sidecar/vllm/proto/inference.proto')
write-debug "<copyright-check> ignored_paths = ['$($ignored_paths -join "','")']."
$ignored_types = @('.bat', '.gif', '.ico', '.ipynb', '.jpg', '.jpeg', '.patch', '.png', '.pyc', '.pyi', '.rst', '.zip', '.md', '.json')
write-debug "<copyright-check> ignored_types = ['$($ignored_types -join "', '")']."
Expand Down
2 changes: 1 addition & 1 deletion .pre-commit-config.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@
# limitations under the License.

default_install_hook_types: [pre-commit, commit-msg]
exclude: ^(src/grpc_generated|.*\.patch$|.*/connect/.*\.py|components/src/dynamo/planner/plugins/proto/v1/plugin_pb2(_grpc)?\.pyi?$|lib/sidecar/vllm/proto/vllm_grpc\.proto$|lib/sidecar/trtllm/proto/trtllm_service\.proto$)
exclude: ^(src/grpc_generated|.*\.patch$|.*/connect/.*\.py|components/src/dynamo/planner/plugins/proto/v1/plugin_pb2(_grpc)?\.pyi?$|lib/sidecar/vllm/proto/(control|inference)\.proto$|lib/sidecar/trtllm/proto/trtllm_service\.proto$)
repos:
- repo: https://github.com/timothycrosley/isort
rev: 5.12.0
Expand Down
2 changes: 2 additions & 0 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

10 changes: 6 additions & 4 deletions components/src/dynamo/common/backend/engine.py
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@
import os
from abc import ABC, abstractmethod
from collections.abc import AsyncGenerator, Sequence
from dataclasses import dataclass
from dataclasses import dataclass, field
from typing import TYPE_CHECKING, Any, Callable, Optional, TypedDict

from typing_extensions import Required
Expand Down Expand Up @@ -137,9 +137,9 @@ class LlmRegistration:
class EngineConfig:
"""Registration metadata returned by an engine's :meth:`start`.

The neutral fields (``model``, ``served_model_name``, ``runtime_data``)
apply to every modality; token-pipeline metadata lives in the optional
:attr:`llm` sub-record, which raw media engines leave ``None``.
The neutral fields (``model``, ``served_model_name``, ``model_aliases``,
``runtime_data``) apply to every modality; token-pipeline metadata lives in
the optional :attr:`llm` sub-record, which raw media engines leave ``None``.
"""

model: str
Expand All @@ -148,6 +148,8 @@ class EngineConfig:
# Token-pipeline registration metadata (KV cache, DP, bootstrap).
# ``Some`` for LLMEngines; ``None`` for RawEngines.
llm: Optional[LlmRegistration] = None
# Kept after existing fields to preserve positional-constructor compatibility.
model_aliases: list[str] = field(default_factory=list)


class BaseEngine(ABC):
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -67,13 +67,15 @@ def test_engine_config_required_model_only():
cfg = backend.EngineConfig(model="m1")
assert cfg.model == "m1"
assert cfg.served_model_name is None
assert cfg.model_aliases == []
assert cfg.llm is None


def test_engine_config_full_kwargs_round_trip_through_getters():
cfg = backend.EngineConfig(
model="m2",
served_model_name="m2-serving",
model_aliases=["m2-alias"],
runtime_data={"sglang_worker_group_id": "group-a"},
llm=backend.LlmRegistration(
context_length=2048,
Expand All @@ -85,6 +87,7 @@ def test_engine_config_full_kwargs_round_trip_through_getters():
)
assert cfg.model == "m2"
assert cfg.served_model_name == "m2-serving"
assert cfg.model_aliases == ["m2-alias"]
assert cfg.runtime_data == {"sglang_worker_group_id": "group-a"}
llm = cfg.llm
assert llm.context_length == 2048
Expand Down
1 change: 1 addition & 0 deletions lib/backend-common/examples/mocker/src/engine.rs
Original file line number Diff line number Diff line change
Expand Up @@ -353,6 +353,7 @@ impl LLMEngine for MockerBackend {
Ok(EngineConfig {
model: self.model_name.clone(),
served_model_name: Some(self.model_name.clone()),
model_aliases: Vec::new(),
runtime_data: Default::default(),
llm: Some(LlmRegistration {
context_length: Some(self.context_length),
Expand Down
8 changes: 5 additions & 3 deletions lib/backend-common/src/engine.rs
Original file line number Diff line number Diff line change
Expand Up @@ -145,15 +145,17 @@ pub struct LlmRegistration {
///
/// `Worker` consumes this to build a `ModelDeploymentCard` and register the
/// model with discovery. The neutral fields (`model`, `served_model_name`,
/// `runtime_data`) apply to every modality; the token-pipeline metadata lives
/// in the optional [`llm`](Self::llm) sub-record, which raw media engines
/// leave `None`.
/// `model_aliases`, `runtime_data`) apply to every modality; the token-pipeline
/// metadata lives in the optional [`llm`](Self::llm) sub-record, which raw
/// media engines leave `None`.
#[derive(Clone, Debug, Default)]
pub struct EngineConfig {
/// Canonical model identifier (e.g. HF repo name).
pub model: String,
/// Public-facing model name advertised to clients. Defaults to `model`.
pub served_model_name: Option<String>,
/// Additional public-facing model names accepted by the engine.
pub model_aliases: Vec<String>,
/// Engine-specific metadata copied into `ModelRuntimeConfig.runtime_data`.
pub runtime_data: HashMap<String, serde_json::Value>,
/// Token-pipeline registration metadata (KV cache, DP, bootstrap).
Expand Down
3 changes: 3 additions & 0 deletions lib/backend-common/src/worker.rs
Original file line number Diff line number Diff line change
Expand Up @@ -1683,6 +1683,7 @@ async fn build_local_model(
let mut builder = LocalModelBuilder::default();
builder
.model_name(served_name)
.model_aliases(engine_config.model_aliases.clone())
.kv_cache_block_size(llm.kv_cache_block_size)
.custom_template_path(config.custom_jinja_template.clone())
.media_decoder(config.media_decoder.clone())
Expand Down Expand Up @@ -2030,6 +2031,7 @@ mod tests {
};
let engine_config = EngineConfig {
model: "media-config-test".to_string(),
model_aliases: vec!["media-alias".to_string()],
..EngineConfig::default()
};

Expand All @@ -2039,6 +2041,7 @@ mod tests {

assert!(local_model.card().media_decoder.is_some());
assert!(local_model.card().media_fetcher.is_some());
assert_eq!(local_model.card().aliases, ["media-alias"]);
}

#[test]
Expand Down
11 changes: 9 additions & 2 deletions lib/bindings/python/rust/backend.rs
Original file line number Diff line number Diff line change
Expand Up @@ -134,7 +134,7 @@ impl From<DisaggregationMode> for RsDisaggregationMode {
// EngineConfig — mirror of `dynamo_backend_common::EngineConfig`.
//
// Engines may return either this pyclass or any object with the canonical
// attributes `model` / `served_model_name` / `runtime_data` / `llm`; the
// attributes `model` / `served_model_name` / `model_aliases` / `runtime_data` / `llm`; the
// bridge's `start()` extraction accepts both. Note `llm` is a nested record
// (LlmRegistration), NOT flat fields — an object exposing flat `context_length`
// etc. (the pre-split shape) registers with `llm=None`, i.e. no KV/DP/bootstrap
Expand Down Expand Up @@ -236,12 +236,13 @@ pub struct EngineConfig {
#[pymethods]
impl EngineConfig {
#[new]
#[pyo3(signature = (model, served_model_name = None, runtime_data = None, llm = None))]
#[pyo3(signature = (model, served_model_name = None, runtime_data = None, llm = None, model_aliases = None))]
fn new(
model: String,
served_model_name: Option<String>,
runtime_data: Option<&Bound<'_, PyDict>>,
llm: Option<LlmRegistration>,
model_aliases: Option<Vec<String>>,
) -> PyResult<Self> {
let runtime_data = runtime_data
.map(|dict| depythonize::<HashMap<String, serde_json::Value>>(dict))
Expand All @@ -253,6 +254,7 @@ impl EngineConfig {
inner: RsEngineConfig {
model,
served_model_name,
model_aliases: model_aliases.unwrap_or_default(),
runtime_data,
llm: llm.map(|l| l.inner),
},
Expand All @@ -268,6 +270,10 @@ impl EngineConfig {
self.inner.served_model_name.as_deref()
}
#[getter]
fn model_aliases(&self) -> &[String] {
&self.inner.model_aliases
}
#[getter]
fn llm(&self) -> Option<LlmRegistration> {
self.inner
.llm
Expand Down Expand Up @@ -862,6 +868,7 @@ impl PyEngineCore {
Ok(RsEngineConfig {
model: bound.getattr("model")?.extract()?,
served_model_name: opt_attr::<String>(bound, "served_model_name")?,
model_aliases: opt_attr::<Vec<String>>(bound, "model_aliases")?.unwrap_or_default(),
runtime_data: match bound.getattr("runtime_data") {
Ok(value) if !value.is_none() => depythonize(&value).map_err(to_pyerr)?,
Ok(_) => HashMap::new(),
Expand Down
3 changes: 3 additions & 0 deletions lib/bindings/python/src/dynamo/_core.pyi
Original file line number Diff line number Diff line change
Expand Up @@ -3324,12 +3324,15 @@ class backend:
served_model_name: Optional[str] = None,
runtime_data: Optional[Dict[str, Any]] = None,
llm: Optional["backend.LlmRegistration"] = None,
model_aliases: Optional[List[str]] = None,
) -> None: ...
@property
def model(self) -> str: ...
@property
def served_model_name(self) -> Optional[str]: ...
@property
def model_aliases(self) -> List[str]: ...
@property
def runtime_data(self) -> Dict[str, Any]: ...
@property
def llm(self) -> Optional["backend.LlmRegistration"]: ...
Expand Down
3 changes: 2 additions & 1 deletion lib/mocker/servers/vllm/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ authors.workspace = true
license.workspace = true
homepage.workspace = true
repository.workspace = true
description = "Mocker-backed implementation of vLLM's native Generate gRPC API"
description = "Mocker-backed implementation of vLLM's native gRPC API"
readme = "README.md"

[[bin]]
Expand All @@ -28,6 +28,7 @@ futures = { workspace = true }
prost-types = { workspace = true }
tokio = { workspace = true }
tonic = { workspace = true }
tonic-health = { workspace = true }
tracing = { workspace = true }
tracing-subscriber = { workspace = true }
uuid = { workspace = true }
Expand Down
18 changes: 7 additions & 11 deletions lib/mocker/servers/vllm/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -5,14 +5,9 @@ SPDX-License-Identifier: Apache-2.0

# Mocker-backed vLLM gRPC server

`dynamo-vllm-mocker-server` implements vLLM's native `Generate` and
`GenerateStream` RPCs on CPU, using the Dynamo Mocker scheduler for batching,
KV-capacity, prefix-cache, and timing behavior. Its primary purpose is fast,
repeatable testing of `dynamo-vllm-sidecar` without a model or GPU.
`dynamo-vllm-mocker-server` implements vLLM's native Inference and Control services plus standard gRPC health on CPU. It uses the Dynamo Mocker scheduler for batching, KV capacity, prefix cache, and timing behavior.

The mock server temporarily imports the generated types exposed by
`dynamo-vllm-sidecar`, whose proto is vendored unchanged from vLLM v0.25.1.
Both consumers will move to vLLM's upstream package once it is published.
The mock server imports the generated types exposed by `dynamo-vllm-sidecar`. The proto files are vendored unchanged from vLLM.

## Aggregated serving

Expand All @@ -29,8 +24,7 @@ Point the existing Dynamo sidecar at it:

```bash
cargo run -p dynamo-vllm-sidecar --bin dynamo-vllm-sidecar -- \
--vllm-endpoint 127.0.0.1:50051 \
--model-path mocker-model
--vllm-endpoint 127.0.0.1:50051
```

`--extra-engine-args` accepts inline JSON or a JSON file path. The values use
Expand Down Expand Up @@ -62,14 +56,16 @@ Then start one sidecar for each endpoint:

```bash
cargo run -p dynamo-vllm-sidecar --bin dynamo-vllm-sidecar -- \
--vllm-endpoint 127.0.0.1:50051 --model-path mocker-model \
--vllm-endpoint 127.0.0.1:50051 \
--disaggregation-mode prefill

cargo run -p dynamo-vllm-sidecar --bin dynamo-vllm-sidecar -- \
--vllm-endpoint 127.0.0.1:50052 --model-path mocker-model \
--vllm-endpoint 127.0.0.1:50052 \
--disaggregation-mode decode
```

The sidecar discovers model identity through Control. Keep `--disaggregation-mode` for prefill and decode because the current discovery API does not report engine role.

The prefill endpoint returns an opaque vLLM-shaped `kv_transfer_params`
payload, and the decode endpoint validates that the sidecar forwarded it
verbatim — including a non-rendezvous sentinel field, so a dropped opaque field
Expand Down
19 changes: 14 additions & 5 deletions lib/mocker/servers/vllm/src/main.rs
Original file line number Diff line number Diff line change
Expand Up @@ -7,20 +7,20 @@ use anyhow::Context;
use clap::Parser;
use dynamo_mocker::common::protocols::MockEngineArgs;
use dynamo_vllm_mocker::{MockerServerConfig, ServerMode, VllmMockerService};
use dynamo_vllm_sidecar::proto::generate_server::GenerateServer;
use dynamo_vllm_sidecar::proto::control_server::ControlServer;
use dynamo_vllm_sidecar::proto::inference_server::InferenceServer;

#[derive(Parser, Debug)]
#[command(
name = "dynamo-vllm-mocker-server",
about = "Run a CPU-only, Mocker-backed implementation of vLLM's native Generate gRPC API"
about = "Run a CPU-only, Mocker-backed implementation of vLLM's native gRPC API"
)]
struct Args {
/// Address on which to expose the vLLM-compatible gRPC service.
#[arg(long, default_value = "127.0.0.1:50051")]
listen: SocketAddr,

/// Model name accepted in Generate requests. The empty model used by the
/// Dynamo vLLM sidecar is always accepted.
/// Model name exposed by the mock server.
#[arg(long, default_value = "mocker-model")]
model: String,

Expand Down Expand Up @@ -81,8 +81,17 @@ async fn main() -> anyhow::Result<()> {
mode = %service.config().mode,
"starting Mocker-backed vLLM gRPC server"
);
let (health, health_service) = tonic_health::server::health_reporter();
health
.set_serving::<ControlServer<VllmMockerService>>()
.await;
health
.set_serving::<InferenceServer<VllmMockerService>>()
.await;
tonic::transport::Server::builder()
.add_service(GenerateServer::new(service))
.add_service(InferenceServer::new(service.clone()))
.add_service(ControlServer::new(service))
.add_service(health_service)
.serve_with_shutdown(args.listen, async {
let _ = tokio::signal::ctrl_c().await;
})
Expand Down
Loading
Loading