Skip to content
Merged
Show file tree
Hide file tree
Changes from 2 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
4 changes: 4 additions & 0 deletions .github/codeowners/areas.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,8 @@ areas:
- components/src/dynamo/frontend/tests/
- examples/chat_templates/
- lib/bindings/python/rust/parsers.rs
- lib/generate-format-catalog/
- lib/llm/src/generate_format.rs
Comment thread
Aphoh marked this conversation as resolved.
Resolved
- lib/llm/src/entrypoint.rs
- lib/llm/src/entrypoint/
- lib/llm/src/entrypoint/input.rs
Expand Down Expand Up @@ -91,6 +93,7 @@ areas:
- components/src/dynamo/vllm/tests/
- examples/backends/vllm/
- lib/sidecar/vllm/
- lib/generate-formats/vllm/
- label: backend-trtllm
github_team: '@ai-dynamo/dynamo-backend-trtllm-codeowners'
path_globs:
Expand All @@ -114,6 +117,7 @@ areas:
- examples/backends/sglang/
- lib/mocker/servers/sglang/
- lib/sidecar/sglang/
- lib/generate-formats/sglang/
- label: backend-triton
github_team: '@ai-dynamo/dynamo-backend-triton-codeowners'
path_globs:
Expand Down
4 changes: 4 additions & 0 deletions CODEOWNERS
Original file line number Diff line number Diff line change
Expand Up @@ -68,6 +68,7 @@

# === frontend (@ai-dynamo/dynamo-frontend-codeowners) ===
/examples/chat_templates/ @ai-dynamo/dynamo-frontend-codeowners
/lib/generate-format-catalog/ @ai-dynamo/dynamo-frontend-codeowners
/components/src/dynamo/frontend/ @ai-dynamo/dynamo-frontend-codeowners
/components/src/dynamo/frontend/tests/ @ai-dynamo/dynamo-frontend-codeowners

Expand Down Expand Up @@ -98,6 +99,7 @@
# === backend-vllm (@ai-dynamo/dynamo-backend-vllm-codeowners) ===
/lib/sidecar/vllm/ @ai-dynamo/dynamo-backend-vllm-codeowners
/examples/backends/vllm/ @ai-dynamo/dynamo-backend-vllm-codeowners
/lib/generate-formats/vllm/ @ai-dynamo/dynamo-backend-vllm-codeowners
/components/src/dynamo/vllm/ @ai-dynamo/dynamo-backend-vllm-codeowners
/components/src/dynamo/vllm/tests/ @ai-dynamo/dynamo-backend-vllm-codeowners

Expand All @@ -116,6 +118,7 @@
# === backend-sglang (@ai-dynamo/dynamo-backend-sglang-codeowners) ===
/lib/sidecar/sglang/ @ai-dynamo/dynamo-backend-sglang-codeowners
/examples/backends/sglang/ @ai-dynamo/dynamo-backend-sglang-codeowners
/lib/generate-formats/sglang/ @ai-dynamo/dynamo-backend-sglang-codeowners
/components/src/dynamo/sglang/ @ai-dynamo/dynamo-backend-sglang-codeowners
/components/src/dynamo/sglang/tests/ @ai-dynamo/dynamo-backend-sglang-codeowners
/components/src/dynamo/sglang/request_handlers/ @ai-dynamo/dynamo-backend-sglang-codeowners
Expand Down Expand Up @@ -528,6 +531,7 @@ requirements.txt
/lib/llm/src/protocols/common/ @ai-dynamo/dynamo-frontend-codeowners
/lib/llm/src/protocols/openai/ @ai-dynamo/dynamo-frontend-codeowners
/tests/fault_tolerance/deploy/ @ai-dynamo/dynamo-fault-tolerance-codeowners
/lib/llm/src/generate_format.rs @ai-dynamo/dynamo-frontend-codeowners
/lib/llm/src/kv_router/indexer/ @ai-dynamo/dynamo-router-codeowners
/tests/fault_tolerance/etcd_ha/ @ai-dynamo/dynamo-fault-tolerance-codeowners
/lib/llm/src/block_manager/pool/ @ai-dynamo/dynamo-kv-memory-codeowners
Expand Down
52 changes: 52 additions & 0 deletions Cargo.lock

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

6 changes: 6 additions & 0 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,9 @@ members = [
"lib/global-view-receiver",
"lib/router-plugins/catalog",
"lib/router-plugins/builtin",
"lib/generate-format-catalog",
"lib/generate-formats/sglang",
"lib/generate-formats/vllm",
"examples/router/custom-policy-example/soft-pin-repin",
"examples/router/custom-policy-example/simple-filter-score-pick",
"examples/router/custom-policy-example/catalog",
Expand Down Expand Up @@ -59,12 +62,15 @@ keywords = ["llm", "genai", "inference", "nvidia", "distributed"]

[workspace.dependencies]
dynamo-custom-policy-builtin = { path = "lib/router-plugins/builtin", version = "1.7.0" }
dynamo-generate-format-catalog = { path = "lib/generate-format-catalog", version = "1.7.0" }
# Published separately by https://github.com/ai-dynamo/aisimulate.
aisimulate-core = { version = "=0.13.0-dev.202610290000000087" }

# Local crates
dynamo-runtime = { path = "lib/runtime", version = "1.7.0" }
dynamo-llm = { path = "lib/llm", version = "1.7.0", default-features = false }
dynamo-sglang-generate = { path = "lib/generate-formats/sglang", version = "1.7.0" }
dynamo-vllm-generate = { path = "lib/generate-formats/vllm", version = "1.7.0" }
dynamo-rl = { path = "lib/rl", version = "1.7.0" }
dynamo-tokenizers = { version = "=3.1.3" }
dynamo-renderer = { version = "=8.2.0" }
Expand Down
4 changes: 2 additions & 2 deletions components/src/dynamo/sglang/AGENTS.md
Original file line number Diff line number Diff line change
Expand Up @@ -36,8 +36,8 @@ more reliable since SGLang's internal layout doesn't always match the version st

**When you bump SGLang**: the frontend joins native `/generate` responses (streamed
chunks into one unary response, prefill's prompt-side outputs into decode's) with rules
in `lib/llm/src/protocols/sglang/merge_rules.json`. Those rules copy SGLang code listed
in the header of `lib/llm/src/protocols/sglang/merge.rs`. Re-check that code, and run
in `lib/generate-formats/sglang/src/merge_rules.json`. Those rules copy SGLang code listed
in the header of `lib/generate-formats/sglang/src/merge.rs`. Re-check that code, and run
`tests/test_sglang_generate_merge_contract.py`, which fails on any unclassified response
field or request flag.

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,8 +5,8 @@

The frontend joins SGLang's streamed chunks into one unary response, and joins
prefill's prompt-side outputs into decode's response, using the rules in
``lib/llm/src/protocols/sglang/merge_rules.json``. Those rules copy SGLang code
listed in the header of ``lib/llm/src/protocols/sglang/merge.rs``.
``lib/generate-formats/sglang/src/merge_rules.json``. Those rules copy SGLang code
listed in the header of ``lib/generate-formats/sglang/src/merge.rs``.

These tests fail when an SGLang upgrade adds, renames, or changes a field the
rules must know about. To fix a failure, read the upstream code named in
Expand Down Expand Up @@ -34,16 +34,29 @@

RULES_PATH = (
Path(__file__).resolve().parents[5]
/ "lib/llm/src/protocols/sglang/merge_rules.json"
/ "lib/generate-formats/sglang/src/merge_rules.json"
)
CATEGORIES = ("append", "take_prefill", "overlay_base64_prefix", "take_last")
FIX = f"Classify it in {RULES_PATH.name} (see lib/llm/src/protocols/sglang/merge.rs)."
FIX = (
f"Classify it in {RULES_PATH.name} (see lib/generate-formats/sglang/src/merge.rs)."
)


@pytest.fixture(scope="module")
def rules() -> dict:
assert RULES_PATH.is_file(), f"merge rules not found at {RULES_PATH}"
return json.loads(RULES_PATH.read_text())
rules = json.loads(RULES_PATH.read_text())
# Chunk joins by field class: deltas append, prompt values come from
# prefill, routed-expert rows overlay, and everything else keeps the last
# value. Nested pointers refine a field classified above them.
joins = {"delta": "append", "prompt": "take_prefill"}
joins["sequence_final"] = "overlay_base64_prefix"
for category in CATEGORIES:
rules[category] = []
for path, field_class in rules["fields"].items():
if path.count("/") <= 2:
rules[joins.get(field_class, "take_last")].append(path)
return rules


def _sglang_tree(relative: str) -> ast.Module:
Expand Down
42 changes: 42 additions & 0 deletions lib/bindings/python/Cargo.lock

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

2 changes: 2 additions & 0 deletions lib/bindings/python/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -101,6 +101,8 @@ uuid = { version = "1.18.1" }
dynamo-kv-router = { path = "../../kv-router" }
dynamo-custom-policy-builtin = { path = "../../router-plugins/builtin" }
dynamo-worker-selection-policy-catalog = { path = "../../router-plugins/catalog", optional = true }
# The replaceable /generate format catalog; custom images point this name at their own.
dynamo-generate-format-catalog = { path = "../../generate-format-catalog" }
clap = { version = "4.5", features = ["derive"], optional = true }
tracing-subscriber = { version = "0.3", features = ["env-filter"], optional = true }

Expand Down
13 changes: 10 additions & 3 deletions lib/bindings/python/rust/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -12,9 +12,7 @@ use dynamo_runtime::storage::kv;
use futures::StreamExt;
use once_cell::sync::OnceCell;
use pyo3::IntoPyObjectExt;
#[cfg(feature = "custom-policy")]
use pyo3::exceptions::PyRuntimeError;
use pyo3::exceptions::{PyStopAsyncIteration, PyTimeoutError, PyValueError};
use pyo3::exceptions::{PyRuntimeError, PyStopAsyncIteration, PyTimeoutError, PyValueError};
use pyo3::types::PyCapsule;
use pyo3::types::{PyDict, PyString};
use pyo3::{exceptions::PyException, prelude::*};
Expand Down Expand Up @@ -326,6 +324,15 @@ fn register_core(m: &Bound<'_, PyModule>) -> PyResult<()> {
rs::logging::init();
}

let mut formats = dynamo_llm::generate_format::Registry::default();
dynamo_generate_format_catalog::register(&mut formats)
.map_err(|error| PyRuntimeError::new_err(error.to_string()))?;
if !dynamo_llm::generate_format::install(formats) {
return Err(PyRuntimeError::new_err(
"generate format registry already installed",
));
}

// Size the runtime the bridge may build for itself.
//
// `DistributedRuntime::new` gives the bridge a configured runtime, but only when it gets
Expand Down
30 changes: 30 additions & 0 deletions lib/generate-format-catalog/Cargo.toml
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
# SPDX-FileCopyrightText: Copyright (c) 2026 NVIDIA CORPORATION & AFFILIATES. All rights reserved.
# SPDX-License-Identifier: Apache-2.0

[package]
name = "dynamo-generate-format-catalog"
version.workspace = true
edition.workspace = true
authors.workspace = true
license.workspace = true
homepage.workspace = true
repository.workspace = true
description = "Default build-time /generate format catalog for Dynamo"

[dependencies]
dynamo-llm = { workspace = true }
anyhow = { workspace = true }
dynamo-sglang-generate = { workspace = true }
dynamo-vllm-generate = { workspace = true }

[dev-dependencies]
dynamo-sglang-generate = { workspace = true, features = ["testkit"] }
dynamo-runtime = { workspace = true }
axum = { workspace = true }
async-trait = { workspace = true }
futures = { workspace = true }
serde_json = { workspace = true }
tokio = { workspace = true }
tower = { workspace = true }
prometheus = { workspace = true }
uuid = { workspace = true }
23 changes: 23 additions & 0 deletions lib/generate-format-catalog/src/lib.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
// SPDX-FileCopyrightText: Copyright (c) 2026 NVIDIA CORPORATION & AFFILIATES. All rights reserved.
// SPDX-License-Identifier: Apache-2.0

//! Default catalog of client-facing `/generate` formats.
//!
//! Backend maintainers own each implementation in `lib/generate-formats/`.
//! A custom frontend replaces this dependency in `lib/bindings/python/Cargo.toml`.
//! Its catalog can depend on this crate under an alias, call [`register`], then
//! add formats under distinct names. The frontend installs the resulting registry.
//!
//! `DYN_GENERATE_FORMAT` selects `/generate`'s format (default `sglang`).
//! `/inference/v1/generate` selects `vllm`. Enable engine APIs to expose the routes.
//! Unknown names and duplicate registrations fail at initialization.

use dynamo_llm::generate_format::Registry;
use std::sync::Arc;

/// Register the built-in formats. Custom catalogs may extend this registry.
pub fn register(registry: &mut Registry) -> anyhow::Result<()> {
registry.register(Arc::new(dynamo_sglang_generate::SglangFormat::native()))?;
registry.register(Arc::new(dynamo_vllm_generate::VllmFormat))?;
Ok(())
}
Loading
Loading