Skip to content
Merged
Show file tree
Hide file tree
Changes from 8 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
26 changes: 12 additions & 14 deletions Cargo.lock

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

9 changes: 7 additions & 2 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -66,7 +66,10 @@ dynamo-runtime = { path = "lib/runtime", version = "1.6.0" }
dynamo-llm = { path = "lib/llm", version = "1.6.0" }
dynamo-rl = { path = "lib/rl", version = "1.6.0" }
dynamo-tokenizers = { version = "=3.1.0" }
dynamo-renderer = { version = "=8.0.1" }
# Beam release pin: renderer 8.1.0 (the Beam `builtin_tools` rendering view) comes from the
# frontend-crates `release/1.6.0-beam-1.0-501b-dev.1` line and is not published to crates.io.
# Same revision as `dynamo-parsers-v2` below, so one immutable commit covers both crates.
dynamo-renderer = { git = "https://github.com/ai-dynamo/frontend-crates.git", rev = "ef8f401393a730cae8ac5c96fe1d564dcf48a017", version = "=8.1.0" }
dynamo-tokens = { path = "lib/tokens", version = "1.6.0" }
dynamo-truthy = { path = "lib/truthy", version = "1.6.0" }
dynamo-data-gen = { path = "lib/data-gen", version = "1.6.0" }
Expand All @@ -81,7 +84,9 @@ dynamo-parsers = { version = "=10.0.2" }
# web: https://crates.io/crates/dynamo-parsers-v2/versions
# sparse index: curl https://index.crates.io/dy/na/dynamo-parsers-v2 (one JSON line per release; read the "vers" field)
# Native count-before-string early streaming still needs a frontend-crates fix.
dynamo-parsers-v2 = { version = "=0.7.21" }
# Beam release pin: parsers-v2 0.10.0 (the Beam unified family) comes from the frontend-crates
# `release/1.6.0-beam-1.0-501b-dev.1` line and is not published to crates.io.
dynamo-parsers-v2 = { git = "https://github.com/ai-dynamo/frontend-crates.git", rev = "ef8f401393a730cae8ac5c96fe1d564dcf48a017", version = "=0.10.0" }
dynamo-backend-common = { path = "lib/backend-common", version = "1.6.0" }
dynamo-sidecar-common = { path = "lib/sidecar/common", version = "1.6.0" }

Expand Down
8 changes: 7 additions & 1 deletion components/src/dynamo/frontend/prepost.py
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,13 @@
from functools import lru_cache, partial
from typing import TYPE_CHECKING, Any, Protocol, TypeGuard, cast

from vllm.entrypoints.chat_utils import make_tool_call_id
try:
from vllm.entrypoints.chat_utils import make_tool_call_id
except ImportError:
# Newer vLLM moved chat_utils under vllm.renderers (renderers refactor,
# upstream main after 2026-10). Remove this fallback once every supported
# vLLM pin ships vllm.renderers.chat_utils.
from vllm.renderers.chat_utils.tool_call import make_tool_call_id
from vllm.entrypoints.openai.chat_completion.protocol import (
ChatCompletionNamedFunction,
ChatCompletionNamedToolChoiceParam,
Expand Down
10 changes: 9 additions & 1 deletion components/src/dynamo/frontend/vllm_processor.py
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,15 @@

from msgspec.structs import replace as msgspec_replace
from vllm.config import CacheConfig, LoadConfig, ModelConfig, VllmConfig
from vllm.entrypoints.chat_utils import load_chat_template

try:
from vllm.entrypoints.chat_utils import load_chat_template
except ImportError:
# Newer vLLM moved chat_utils under vllm.renderers (renderers refactor,
# upstream main after 2026-10). Remove this fallback once every supported
# vLLM pin ships vllm.renderers.chat_utils.
from vllm.renderers.chat_utils.chat_template import load_chat_template

from vllm.exceptions import VLLMClientError
from vllm.reasoning import ReasoningParser, ReasoningParserManager
from vllm.sampling_params import RequestOutputKind, SamplingParams
Expand Down
42 changes: 39 additions & 3 deletions components/src/dynamo/vllm/embedding_worker_processes.py
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@
from __future__ import annotations

import copy
import importlib
import json
import logging
import os
Expand All @@ -29,9 +30,43 @@
from vllm.config import VllmConfig
from vllm.usage.usage_lib import UsageContext
from vllm.v1.engine.async_llm import AsyncLLM
from vllm.v1.engine.utils import get_engine_zmq_addresses, launch_core_engines
from vllm.v1.executor import Executor

# Resolved on first use, not at import: vLLM renamed get_engine_zmq_addresses in
# vllm-project/vllm#54113, and this module is imported by every dynamo.vllm worker
# while only --embedding-worker-processes > 1 calls into it. Module attributes so
# tests can substitute them.
get_engine_zmq_addresses: Callable[..., Any] | None = None
launch_core_engines: Callable[..., Any] | None = None


def _resolve_engine_utils() -> tuple[Callable[..., Any], Callable[..., Any]]:
"""Bind the vLLM engine-launch helpers this pool needs, failing with a clear message.

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This private helper docstring duplicates the immediately preceding explanation and the function signature/body: _resolve_engine_utils already shows that it binds and returns the two helpers, raises a tailored error, and synchronizes the module attributes used by tests. It adds no lasting constraint beyond that nearby documentation.

🤖 AI Fix

Remove the redundant _resolve_engine_utils docstring.


Returns ``(get_engine_zmq_addresses, launch_core_engines)`` so callers hold
non-optional references; the module attributes are kept in sync for tests.
"""
global get_engine_zmq_addresses, launch_core_engines
engine_utils = importlib.import_module("vllm.v1.engine.utils")

if launch_core_engines is None:
launch_core_engines = engine_utils.launch_core_engines
if get_engine_zmq_addresses is None:
resolved = getattr(engine_utils, "get_engine_zmq_addresses", None)
if resolved is None:
# vllm#54113: the supervisor now binds the frontend listeners up front and
# children adopt them; this pool still lets children bind the addresses
# themselves, so refuse rather than hand out addresses that are already bound.
raise RuntimeError(
"--embedding-worker-processes > 1 is not yet supported on this vLLM: "
f"vLLM {vllm.__version__} has no get_engine_zmq_addresses (it binds "
"frontend listeners in the supervisor since vllm-project/vllm#54113). "
"Run with --embedding-worker-processes 1 until the pool adopts inherited listeners."
)
get_engine_zmq_addresses = resolved
return get_engine_zmq_addresses, launch_core_engines


logger = logging.getLogger(__name__)

_ROLE_ENV = "DYN_VLLM_EMBEDDING_PROCESS_ROLE"
Expand Down Expand Up @@ -463,7 +498,8 @@ def create_shared_embedding_engine_client(
vllm_config.parallel_config._api_process_count = process_count
vllm_config.parallel_config._api_process_rank = -1
executor_class = Executor.get_class(vllm_config)
addresses = get_engine_zmq_addresses(vllm_config, process_count)
engine_zmq_addresses, core_engines_launcher = _resolve_engine_utils()
addresses = engine_zmq_addresses(vllm_config, process_count)

parent_logger_config = _client_config(
vllm_config,
Expand All @@ -476,7 +512,7 @@ def create_shared_embedding_engine_client(
with ThreadPoolExecutor(
max_workers=1, thread_name_prefix="dynamo-embedding-client"
) as client_pool:
with launch_core_engines(
with core_engines_launcher(
vllm_config,
executor_class,
not disable_log_stats,
Expand Down
9 changes: 8 additions & 1 deletion components/src/dynamo/vllm/realtime/factories.py
Original file line number Diff line number Diff line change
Expand Up @@ -89,7 +89,14 @@ def build_realtime_text_factories(
consumed without starting speculative generation.
"""
from vllm.engine.protocol import StreamingInput
from vllm.entrypoints.chat_utils import load_chat_template

try:
from vllm.entrypoints.chat_utils import load_chat_template
except ImportError:
# Newer vLLM moved chat_utils under vllm.renderers (renderers refactor,
# upstream main after 2026-10). Remove this fallback once every
# supported vLLM pin ships vllm.renderers.chat_utils.
from vllm.renderers.chat_utils.chat_template import load_chat_template
from vllm.entrypoints.openai.chat_completion.protocol import ChatCompletionRequest
from vllm.entrypoints.openai.chat_completion.serving import OpenAIServingChat
from vllm.entrypoints.serve.engine.protocol import ErrorResponse
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -71,6 +71,7 @@ endpoint:
| --- | --- | --- | --- |
| `continue_final_message` | vLLM OpenAI-compatible extension | Dynamo-handled | Dynamo uses the value while preparing the chat template. The original field does not need to reach the engine after preprocessing. |
| `temperature` | OpenAI Chat Completions API | Backend-handled | Backend sampling behavior can use a typed frontend representation. Typed storage does not make the sampling semantics frontend-owned. |
| `echo` | OpenAI Completions API | Jointly handled | The backend scores the prompt positions (its prompt logprobs); Dynamo rebuilds the echoed prompt text and per-token `logprobs` entries ahead of the completion from the tokens it sent. Unary responses prepend the prompt; a stream carries it in the chunk that delivers the engine's prompt logprobs. |
| `prompt_logprobs` | vLLM OpenAI-compatible extension | Jointly handled | The backend computes the values while Dynamo carries, aggregates, and exposes the response payload. Document the streaming and non-streaming projections separately. |
| `bad_words_token_ids` | Dynamo token-level extension | Backend-handled | On supported vLLM paths, Dynamo validates and transports the token sequences while vLLM applies the sampling constraint. |
| `nvext.extra_fields` | Dynamo `nvext` extension | Dynamo-handled | This Dynamo-owned option selects response metadata. It is not a catch-all request map. |
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -251,7 +251,7 @@ A worker that uses a ModelExpress build with the RL cold-start policy declares t
| [NeMo RL](nemo-rl.md) | Experimental | NeMo RL-managed Dynamo/vLLM fleet on Slurm and Ray | Fixed non-colocated fleet with framework-owned NCCL refit; not an external Dynamo deployment. |
| [Slime](slime.md) | Experimental fixed worker set | Dynamo sidecars manage stock SGLang engines for [streaming external rollouts](https://github.com/THUDM/slime/pull/2272) | The [fixed-worker-set example](https://github.com/ai-dynamo/dynamo/blob/main/examples/README.md#integration-examples) sends generation through the Dynamo frontend. Slime uses static engine Services for control and weight updates. The example does not support elastic discovery or external-engine recovery. |
| [Prime-RL](https://github.com/PrimeIntellect-ai/prime-rl) | Router available; full integration in progress | Prime-RL documents the Dynamo router as a drop-in option; a proposed Dynamo/vLLM sidecar adds worker discovery and external updates | Routing can be evaluated today, but the full adapter remains in upstream development and is not a released compatibility surface. |
| [OpenRLHF](https://github.com/OpenRLHF/OpenRLHF), [Miles](https://github.com/fleet-ai/miles-fleet), [SkyRL](https://github.com/NovaSky-AI/SkyRL), and [Polar](https://github.com/NVIDIA-NeMo/ProRL-Agent-Server) | No Dynamo guide | No maintained Dynamo adapter was found in the reviewed public sources | Add a guide only after a maintained integration completes the same generation, update, failure, and ownership checks. |
| [OpenRLHF](https://github.com/OpenRLHF/OpenRLHF), [Miles](https://github.com/radixark/miles), [SkyRL](https://github.com/NovaSky-AI/SkyRL), and [Polar](https://github.com/NVIDIA-NeMo/ProRL-Agent-Server) | No Dynamo guide | No maintained Dynamo adapter was found in the reviewed public sources | Add a guide only after a maintained integration completes the same generation, update, failure, and ownership checks. |

A status reflects the documented integration, not whether the framework can call a generic HTTP endpoint.

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,7 @@ Some models emit reasoning separately from their final response. Dynamo can spli
- `moonshotai/Kimi-K2.5*` / Kimi K2.6 format-compatible outputs: `--dyn-tool-call-parser kimi_k2 --dyn-reasoning-parser kimi_k25`
- `google/gemma-4-*` thinking models: `--dyn-tool-call-parser gemma4 --dyn-reasoning-parser gemma4 --custom-jinja-template examples/chat_templates/gemma4_tool.jinja`
- `Qwen/Qwen3.5*`: `--dyn-tool-call-parser qwen3_coder --dyn-reasoning-parser qwen3`
- Beam channel-grammar models: `--dyn-tool-call-parser beam --dyn-reasoning-parser beam`
- MiniMax M2 style outputs: `--dyn-tool-call-parser minimax_m2 --dyn-reasoning-parser minimax_m2`
- MiniMax M3 style outputs: `--dyn-tool-call-parser minimax_m3 --dyn-reasoning-parser minimax_m3`

Expand Down Expand Up @@ -339,6 +340,20 @@ Parsers marked **Force-reasoning: Yes** emit reasoning content from token one wi
</AccordionGroup>
</Accordion>

<Accordion title="Beam">
<AccordionGroup>
<Accordion title="Beam channel-grammar models">
**Parser:** `beam`

**Upstream name:** none (Dynamo-native unified family)

**Force-reasoning:** <Badge intent="note" minimal>No</Badge>

**Notes:** `<\|channel\|>analysis<\|message\|>...<\|end\|>` is reasoning; the final channel is the answer. Pair with `--dyn-tool-call-parser beam` (both flags required). Supplied prior reasoning is always rendered back into the history (`preserve_thinking: true` and omitted are the same; `false` is accepted and has no effect); history turns without reasoning render an empty analysis block (`allow_missing_thinking` defaults to true on this route).
</Accordion>
</AccordionGroup>
</Accordion>

<Accordion title="gpt-oss">
<AccordionGroup>
<Accordion title="gpt-oss-20b and gpt-oss-120b">
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -269,6 +269,20 @@ The **Upstream name** column shows where the vLLM or SGLang parser name differs
</AccordionGroup>
</Accordion>

<Accordion title="Beam">
<AccordionGroup>
<Accordion title="Beam channel-grammar models">
**Parser:** `beam`

**Upstream name:** none (Dynamo-native, served by the `dynamo-parsers-v2` unified family)

**Notes:** Channel grammar: reasoning in `<\|channel\|>analysis<\|message\|>...<\|end\|>`, the answer in `<\|channel\|>final<\|message\|>...<\|return\|>`, and one `<\|start\|>assistant to=NAME<\|channel\|>analysis <\|constrain\|>xml<\|message\|>...<\|call\|>` message per tool call with `<rfl:parameter name="KEY">VALUE</rfl:parameter>` arguments typed from the tool schema. Pair with `--dyn-reasoning-parser beam`; both flags are required and the pair routes to the unified parser without `DYN_PARSER_VERSION=2`. The route accepts the request field `preserve_thinking` (supplied prior assistant reasoning always stays in the rendered history; `true` and omitted are the same, `false` is accepted and has no effect — omit `reasoning_content` to drop it) and defaults `allow_missing_thinking=true` so history turns without reasoning render an empty analysis block instead of failing and `chat_template_kwargs.builtin_tools` (names resolved against `tools` for rendering only).

**Per-turn grammar:** on the Beam route the frontend holds every chat turn to this channel grammar with a structural tag (a non-empty analysis channel, then either tool calls whose `rfl:parameter` values are held to their JSON-schema types or the final answer), mirroring the model builder's SGLang serving layer; `required` and named tool choices reuse the same tag, so they are enforced on the Harmony wire. A `response_format` (`json_object` or `json_schema`) is held inside the final channel behind the analysis, so the response keeps its reasoning and the channel structure the parser expects; a forced tool choice ignores `response_format`, and the `nvext` guided-decoding fields (`guided_json`, `guided_regex`, ...) stay bare constraints from the first token. Set `DYN_BEAM_TURN_GRAMMAR=0` on the frontend to disable it for automatic tool choice (forced choices keep theirs; `response_format` then falls back to the bare JSON constraint). A tool declared `strict: true` whose schema is outside OpenAI's strict subset is served as non-strict on this route (logged as a warning) instead of the HTTP 400 other routes return; the grammar holds every typeable tool either way.
</Accordion>
</AccordionGroup>
</Accordion>

<Accordion title="gpt-oss">
<AccordionGroup>
<Accordion title="gpt-oss-20b and gpt-oss-120b">
Expand Down
Loading
Loading