Skip to content

feat: add native vLLM gRPC sidecar backend - #11804

Open
biswapanda wants to merge 10 commits into
ai-dynamo:mainfrom
biswapanda:feat/dyn-pi-sidecar-v2
Open

feat: add native vLLM gRPC sidecar backend#11804
biswapanda wants to merge 10 commits into
ai-dynamo:mainfrom
biswapanda:feat/dyn-pi-sidecar-v2

Conversation

@biswapanda

@biswapanda biswapanda commented Jul 16, 2026

Copy link
Copy Markdown
Contributor

Overview

Add an out-of-process vLLM backend that connects Dynamo to vLLM's existing native Rust gRPC/token-in-token-out service. The sidecar supports aggregated and disaggregated generation, multimodal inputs, prompt/completion logprobs, KV handoff, runtime LoRA, metrics, session affinity, and opt-in worker discovery for external RL trainers.

Companion PRs:

Published, cross-repository tested heads:

Repository Commit
Dynamo b0b51b2783346a6ff6566fcaecc7d35fcd2ee80b
Prime-RL 5a8827cee5021a6021707517c93a3d9c61e3cd01
vLLM 2d2bfbe3765cfb5c05567ad8b2338564faedf5e3

Out-of-process native gRPC backend

  • Add dynamo-vllm-sidecar as a standalone Rust backend connected to a separately managed vLLM EngineCore/Rust server.
  • Keep Dynamo responsible for frontend routing, distributed discovery, P/D orchestration, worker/system endpoints, and metrics.
  • Keep vLLM responsible for scheduling, generation, worker extensions, and engine control.
  • Support aggregated, managed-DP, and prefill/decode-disaggregated topologies.
  • Keep native gRPC pod-local while publishing the vLLM HTTP administration address required by the external trainer.

This is a sibling of the in-process Python VllmLLMEngine; it does not replace that backend.

Request/response parity

The sidecar maps Dynamo's generate contract to the native vLLM protocol, including:

  • token IDs, canonical request/model identity, priority, cache salt, and DP-rank hints;
  • temperature, top-p/top-k, min-p, penalties, stops, logit bias, allowed token IDs, bad words, structured-output fields, and vLLM xargs;
  • prompt and completion logprobs;
  • streaming usage and terminal finish metadata;
  • image, audio, video, and already-preprocessed multimodal inputs;
  • routed experts and KV-transfer metadata.

Unsupported or malformed fields fail explicitly instead of being silently discarded. KV-transfer merge collisions also fail explicitly: framework-owned P/D handoff keys cannot be overridden by a caller because accepting two owners for routing metadata is unsafe.

Disaggregated KV serving

  • Configure native hybrid-state transfer for P/D deployments.
  • Preserve KV connector metadata across frontend, sidecar, and vLLM.
  • Compose prompt logprobs across prefill and decode.
  • Propagate aborts as cancellation/error instead of publishing an incomplete prefill handoff.
  • Preserve large KV-transfer identifiers losslessly by rejecting values that cannot cross the protobuf Struct number representation exactly.
  • Export NIXL/KV-transfer traffic and failure metrics.

RL worker discovery

Discovery is opt-in and served on a separate listener:

DYN_ENABLE_RL=true
DYN_RL_PORT=8001

GET /v1/rl/workers returns one record per engine endpoint:

{
  "protocol_version": 1,
  "namespace": "training",
  "workers": [
    {
      "component": "backend",
      "instance_id": 10,
      "model": "Qwen/Qwen3-0.6B",
      "admin_base_url": "http://worker:8120",
      "system_url": "http://worker:8181",
      "world_size": 2,
      "system_routes": ["update/load_lora"],
      "error": null
    }
  ]
}

component=backend is the aggregate/decode cohort; disaggregated prefill engines publish component=prefill. Each record represents one engine endpoint, not one GPU.

  • admin_base_url identifies vLLM's direct HTTP control endpoint.
  • world_size is the number of inference ranks owned by that endpoint.
  • system_url and system_routes describe Dynamo-owned mutations such as LoRA loading.
  • Per-worker probe failures appear as record-level errors so a trainer can reject and retry the complete snapshot.
  • Probe concurrency and client reuse are bounded.
  • The sidecar requires an explicit model identity and verifies discovery metadata at registration.

Protocol v1 intentionally does not publish trainer-session-specific rank_start values or claim an atomic topology epoch. Prime computes cumulative offsets from one accepted snapshot and fences the total against its configured inference world size.

Session-aware routing

Prime forwards each trajectory ID as X-Dynamo-Session-ID. When frontend session affinity is enabled, Dynamo keeps successive R2E turns on the same decode worker for the configured idle TTL, while the normal router remains the fallback for requests without a session key.

A configurable compatibility header may be selected, but the canonical X-Dynamo-Session-ID header always takes precedence.

Runtime LoRA

  • Advertise load/unload/list capabilities through Dynamo system routes.
  • Preserve LoRA capability metadata across Python↔Rust registration.
  • Support filesystem URI loading and atomic in-place adapter replacement.
  • Keep base-model identity and adapter publication synchronized.
  • Reject mixed-capability discovery snapshots on the Prime side.

Review fixes in the current head

  • Pass engine-API enablement explicitly through the Python/Rust binding instead of writing parsed configuration back to the environment.
  • Resolve the native vLLM generate API at module load and use the repository-pinned package path.
  • Prevent Encode workers from advertising generate capability.
  • Skip vLLM-dependent Python tests cleanly when the optional package is absent.
  • Remove invalid sidecar working directories and atomically replace generated arguments in deployment examples.
  • Thread profile-derived GPU-memory arguments and configurable model/port values through launch scripts.
  • Preserve LoRA registration metadata.
  • Enforce RPC deadlines for already-connected control calls.
  • Reject aborted prefill handoff and lossy protobuf-number conversion.
  • Use shared process cleanup and dynamically allocated/injected parity-test ports.
  • Make queue-pressure and worker-metric assertions deterministic.
  • Roll back endpoint registration when post-attach initialization fails.

Where reviewers should start

  • lib/vllm-sidecar/src/request.rs — Dynamo → vLLM request conversion
  • lib/vllm-sidecar/src/wire.rs — native protocol boundary
  • lib/vllm-sidecar/src/engine.rs — generation lifecycle
  • lib/vllm-sidecar/src/discovery.rs — engine capability discovery
  • lib/vllm-sidecar/src/tests/ — conversion, lifecycle, generation, and P/D tests
  • lib/rl/src/lib.rs — frontend /v1/rl/workers API
  • lib/backend-common/src/rl.rs — worker metadata endpoint
  • lib/llm/src/http/service/generate.rs and generate/ — frontend generate contract
  • examples/backends/vllm/deploy/sidecar_agg.yaml
  • examples/backends/vllm/deploy/sidecar_disagg.yaml

Validation

At the published Dynamo head:

cargo test -p dynamo-vllm-sidecar --locked: 71 passed
focused ARM64 Python suite: 56 passed

Exact-head GPU validation used the three published commits above for the two Qwen gates. The GLM runs used the immutable image lineage described after the list:

  • Qwen3-0.6B disaggregated 1-prefill/1-decode: three trainer/rollout steps, v0/v1 NCCL updates on both engines, post-v1 generation, PASS.
  • Qwen3-0.6B aggregated DP2: discovered world_size=2, rank 0/1 mapping, v0/v1 updates on both ranks, post-v1 generation, PASS.
  • GLM-5.2 FP8 Math DGD: five steps, 18/18 generation requests, no worker restarts or fatal logs, PASS.
  • GLM-5.2 FP8 standalone R2E through Dynamo with local agents.x-k8s.io sandbox: reward 1.0, 35 turns, agent_completed, PASS.

The GLM image was built from Prime abcb05c34c0bdacfcfda3e0d4223f23ac5f88bd2, Dynamo 6c688b197512e86e3b5b957f2c9b54d9b223bba3, and vLLM 2d2bfbe3765cfb5c05567ad8b2338564faedf5e3. The Prime and Dynamo commits are ancestors of the current published heads; the later review fixes were independently validated by the exact-head Qwen aggregate and disaggregate gates.

The exact b0b51b278 ARM64 sidecar binary was built with DIND and used as an on-disk overlay. The validated base runtime image was rebuilt and pushed as:

nvcr.io/nvidian/dynamo-dev/biswa@sha256:47e6e8301f3bfdfebd80b2d364b7532341816610e6e46a5d77b4ba39abef54ca

Compatibility and scope

  • RL discovery is disabled unless DYN_ENABLE_RL is set.
  • Existing in-process vLLM and non-RL Dynamo deployments remain supported.
  • The sidecar consumes vLLM's native protocol; it does not depend on openengine.v1.
  • This PR adds no Prime-RL trainer/orchestrator or Helm-chart code.
  • Active NCCL communicators are not elastic; topology epochs, worker replacement, and communicator reconstruction remain follow-up work.

@biswapanda
biswapanda requested review from a team as code owners July 16, 2026 22:11
@biswapanda
biswapanda temporarily deployed to external_collaborator July 16, 2026 22:11 — with GitHub Actions Inactive
@biswapanda
biswapanda temporarily deployed to external_collaborator July 16, 2026 22:11 — with GitHub Actions Inactive
@github-actions

github-actions Bot commented Jul 16, 2026

Copy link
Copy Markdown
Contributor

@github-actions github-actions Bot added external-contribution Pull request is from an external contributor documentation Improvements or additions to documentation backend::vllm Relates to the vllm backend frontend `python -m dynamo.frontend` and `dynamo-run in=http|text|grpc` router Relates to routing, KV-aware routing, etc. labels Jul 16, 2026
@biswapanda biswapanda changed the title Feat/dyn pi sidecar v2 dynamo-prime-integration-pr Jul 16, 2026

@devin-ai-integration devin-ai-integration Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Devin Review found 2 potential issues.

Open in Devin Review

Comment thread components/src/dynamo/frontend/main.py Outdated
Comment on lines +183 to +185
os.environ[VLLM_ENABLE_INFERENCE_V1_GENERATE_ENV] = (
"1" if config.enable_engine_apis else "0"
)

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

🟡 Frontend writes a parsed setting back into an environment variable instead of passing it explicitly

The engine-APIs setting is written back into a process environment variable (os.environ[VLLM_ENABLE_INFERENCE_V1_GENERATE_ENV] = ... at components/src/dynamo/frontend/main.py:183-185) after being parsed into the frontend config, so the configuration flows in a cycle from Python out to the environment and back into the Rust layer rather than being passed directly.
Impact: This violates the frontend's stated configuration-boundary rule and reintroduces the exact drift risk (a re-read environment value diverging from the parsed setting) the rule exists to prevent.

Boundary rule and consumer path

components/src/dynamo/frontend/CLAUDE.md:7-9 explicitly states: "Do not write parsed FrontendConfig values back to os.environ for Rust to re-read. That creates a cyclic Python -> env -> Rust contract where CLI overrides can diverge from Rust behavior." It further requires (CLAUDE.md:10-11) passing such values "through explicit PyO3 binding parameters or config structs."

Here config.enable_engine_apis is parsed from CLI/env in frontend_args.py (env var DYN_VLLM_ENABLE_INFERENCE_V1_GENERATE), then written back into os.environ under that same name. The Rust builder re-reads it via env_is_truthy(VLLM_ENABLE_INFERENCE_V1_GENERATE_ENV) at lib/llm/src/http/service/service_v2.rs:897-898. The PyO3 HttpService binding (lib/bindings/python/rust/http.rs:27-36) exposes no enable_engine_apis parameter, so the required explicit transport path does not exist and the env write-back is the only mechanism used — the pattern the rule prohibits.

Prompt for agents
The frontend must not write the parsed config.enable_engine_apis value back to os.environ for the Rust HTTP service to re-read, per components/src/dynamo/frontend/CLAUDE.md. Instead, add an explicit transport path: expose an enable_engine_apis parameter on the PyO3 HttpService binding (lib/bindings/python/rust/http.rs) that forwards to the HttpServiceConfigBuilder.enable_engine_apis(...) method (lib/llm/src/http/service/service_v2.rs), then pass config.enable_engine_apis through that explicit binding when the frontend constructs the HTTP service, and remove the os.environ write in main.py:183-185.
Open in Devin Review

Was this helpful? React with 👍 or 👎 to provide feedback.

Comment on lines +468 to +480
sampling_params.extra_args = {}
# `do_remote_decode` is prefill's to own; merge caller-supplied
# values on top of the defaults so explicit overrides win.
kv_defaults = {
framework_kv = {
"do_remote_prefill": False,
"remote_engine_id": None,
"remote_block_ids": None,
"remote_host": None,
"remote_port": None,
}
caller_kv = sampling_params.extra_args.get("kv_transfer_params", {})
sampling_params.extra_args["kv_transfer_params"] = {
**kv_defaults,
**caller_kv,
"do_remote_decode": True,
}
caller_kv = sampling_params.extra_args.get("kv_transfer_params")
sampling_params.extra_args["kv_transfer_params"] = merge_kv_transfer_params(
caller_kv, framework_kv
)

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

🔍 Prefill kv_transfer_params merge became strict (collision now raises) versus prior caller-wins merge

In components/src/dynamo/vllm/llm_engine.py:468-480 the prefill dispatch switched from a caller-wins overlay ({**kv_defaults, **caller_kv, "do_remote_decode": True}) to merge_kv_transfer_params(caller_kv, framework_kv). merge_kv_transfer_params (engine_generate.py:92-107) raises ValueError on ANY key overlap between caller and framework. Previously, a caller-supplied do_remote_prefill/remote_engine_id/etc. silently overrode the framework default; now the same input fails the request. The same strict-merge semantics were applied to the decode path and to handlers.py (3127-3131, 3453-3456). This appears intentional (disjoint-namespace contract), and existing internal callers pass an empty/absent kv_transfer_params, but any external caller that historically set one of these keys will now hit a hard error instead of an override. Worth confirming no in-tree caller relies on the old override behavior.

Open in Devin Review

Was this helpful? React with 👍 or 👎 to provide feedback.

@datadog-official

datadog-official Bot commented Jul 16, 2026

Copy link
Copy Markdown

Pipelines

⚠️ Warnings

🚦 7 Pipeline jobs failed

PR | dynamo-runtime / mypy   View in Datadog   GitHub Actions

🔧 Fix in code. This looks caused by changes in this PR. Compilation error in components/src/dynamo/vllm/handlers.py:2905: incompatible types in assignment (expression has type 'dict[str, Any] | None', variable has type 'str | None') and 1 error found at argument in line 2910.

PR | vllm-runtime / Test cuda13.0, amd64   View in Datadog   GitHub Actions

🔧 Fix in code. This looks caused by changes in this PR. 1 failed test. AttributeError: 'types.SimpleNamespace' object has no attribute '_generate_with_lora_admission_lock' in test_engine_generate_worker_emits_native_terminal_metadata

PR | vllm-runtime / Test cuda13.0, arm64   View in Datadog   GitHub Actions

🔧 Fix in code. This looks caused by changes in this PR. AttributeError: 'types.SimpleNamespace' object has no attribute '_generate_with_lora_admission_lock' in test_engine_generate_worker_emits_native_terminal_metadata at line 212

View all 7 failed jobs.

📋 Copy prompt for your agent
CI on my pull request is failing. Help me find and fix the root cause of each failing job below — they were flagged as caused by changes in this PR, so focus on the diff. For each job, explain the failure and propose a fix.

Branch: feat/dyn-pi-sidecar-v2

PR | dynamo-runtime / mypy
Commit: 8b5acf856a8295f89c0d56167babd16b9129e447
Error (code / build):
Compilation error in components/src/dynamo/vllm/handlers.py:2905: incompatible types in assignment (expression has type 'dict[str, Any] | None', variable has type 'str | None') and 1 error found at argument in line 2910.
CI job: https://github.com/ai-dynamo/dynamo/actions/runs/30584715466/job/91015752245

PR | vllm-runtime / Test cuda13.0, amd64
Commit: 8b5acf856a8295f89c0d56167babd16b9129e447
Error (code / test):
1 failed test. AttributeError: 'types.SimpleNamespace' object has no attribute '_generate_with_lora_admission_lock' in test_engine_generate_worker_emits_native_terminal_metadata
CI job: https://github.com/ai-dynamo/dynamo/actions/runs/30584715466/job/91015585176

PR | vllm-runtime / Test cuda13.0, arm64
Commit: 8b5acf856a8295f89c0d56167babd16b9129e447
Error (code / test):
AttributeError: 'types.SimpleNamespace' object has no attribute '_generate_with_lora_admission_lock' in test_engine_generate_worker_emits_native_terminal_metadata at line 212
CI job: https://github.com/ai-dynamo/dynamo/actions/runs/30584715466/job/91015585154

ℹ️ Info

🎯 Code Coverage (details)
Patch Coverage: 27.47%
Overall Coverage: 38.75% (-8.44%)

Useful? React with 👍 / 👎

This comment will be updated automatically if new data arrives.
🔗 Commit SHA: 8b5acf8 | Docs | Datadog PR Page | Give us feedback!

@coderabbitai

coderabbitai Bot commented Jul 16, 2026

Copy link
Copy Markdown
Contributor

Review Change Stack

Walkthrough

Adds a Rust vLLM gRPC sidecar, native vLLM generate support, LoRA and lifecycle controls, KV-routing metadata, frontend configuration, deployment examples, and parity/metrics validation tooling.

Changes

vLLM serving and native generation

Layer / File(s) Summary
Frontend and native generate flow
components/src/dynamo/frontend/..., components/src/dynamo/vllm/...
Adds engine API toggles, JSON engine configuration loading, native prompt and sampling conversion, KV-transfer merging, logprob propagation, multimodal prompt handling, and tests.
Rust vLLM sidecar
lib/vllm-sidecar/..., Cargo.toml
Adds the protobuf contract, gRPC client pool, discovery, request/response conversion, lifecycle-aware VllmSidecarEngine, binary entry point, and integration tests.

Shared runtime and validation

Layer / File(s) Summary
Engine lifecycle, LoRA, and routing contracts
lib/backend-common/..., lib/llm/..., lib/kv-router/..., lib/runtime/...
Adds LoRA registration and control, drain/quiescence handling, authoritative KV DP-rank selection, multimodal routing updates, signed top_k, TTL configuration, and related tests.
Deployment and parity tooling
examples/backends/vllm/..., tests/serve/tito_parity/...
Adds sidecar Kubernetes and shell launchers, deployment documentation, token parity runners, seeded RL parity checks, and Prometheus metric verification.

Estimated code review effort: 5 (Critical) | ~120 minutes

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Description check ⚠️ Warning The PR is substantive, but it misses the required Related Issues section and doesn't follow the template's Details heading. Add the missing Details section and the required Related Issues block, using one of the template's two allowed issue paths.
✅ Passed checks (4 passed)
Check name Status Explanation
Docstring Coverage ✅ Passed Docstring coverage is 100.00% which is sufficient. The required threshold is 80.00%.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.
Title check ✅ Passed The title clearly names the main change: adding a native vLLM gRPC sidecar backend.

Comment @coderabbitai help to get the list of available commands.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Actionable comments posted: 13

Note

Due to the large number of review comments, Critical, Major severity comments were prioritized as inline comments.

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (3)
components/src/dynamo/vllm/llm_engine.py (1)

630-671: 🗄️ Data Integrity & Integration | 🟠 Major | ⚡ Quick win

Complete the native P/D prompt-logprob handoff.

Prompt logprobs must be computed by prefill, transferred to decode, and never recomputed on decode.

  • components/src/dynamo/vllm/llm_engine.py#L630-L671: include prompt_logprobs_payload in prefill disaggregated_params.
  • components/src/dynamo/vllm/llm_engine.py#L483-L505: read the transferred payload and clear sampling_params.prompt_logprobs.
  • components/src/dynamo/vllm/llm_engine.py#L553-L578: use the transferred payload on decode rather than collecting a new one.
  • components/src/dynamo/vllm/handlers.py#L453-L472: clear decode prompt-logprob computation even when the transferred payload is absent.
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@components/src/dynamo/vllm/llm_engine.py` around lines 630 - 671, Complete
the native P/D prompt-logprob handoff across
components/src/dynamo/vllm/llm_engine.py lines 630-671, 483-505, and 553-578,
plus components/src/dynamo/vllm/handlers.py lines 453-472: add
prompt_logprobs_payload to prefill disaggregated_params, read it on decode and
clear sampling_params.prompt_logprobs, reuse the transferred payload instead of
collecting decode prompt logprobs, and ensure handlers clear decode
prompt-logprob computation even when no payload is transferred.
lib/backend-common/src/worker.rs (1)

866-881: 🩺 Stability & Availability | 🟠 Major | ⚡ Quick win

Unregister the model when LoRA-controller initialization fails.

LoraController::new(...).await? runs after local_model.attach(). If listing or reconciling existing adapters fails, execution returns before orchestrator_steps(), while the outer safety net only cleans up the engine. This leaves a stale discovery entry routing traffic to a failed worker.

Wrap post-attach initialization with rollback that calls endpoint.unregister_endpoint_instance() before returning the error.

Also applies to: 898-906

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@lib/backend-common/src/worker.rs` around lines 866 - 881, Wrap the
post-attach initialization, including LoraController::new and the related setup
before orchestrator_steps(), with rollback handling. If any initialization step
fails, call endpoint.unregister_endpoint_instance() before propagating the
original error; preserve the existing successful path and ensure cleanup also
covers the additional referenced range.
lib/llm/src/kv_router/publisher/zmq_listener.rs (1)

18-22: 🎯 Functional Correctness | 🔴 Critical | ⚡ Quick win

Propagate the revised start_zmq_listener contract consistently. The new parameter leaves two test call paths uncompilable.

  • lib/llm/src/kv_router/publisher/zmq_listener.rs#L18-L22: update the remaining call in lib/llm/src/kv_router/publisher/tests.rs at Line 1191 by inserting None after worker_id.
  • lib/llm/src/kv_router/publisher/tests.rs#L1345-L1427: use UnboundedSender<Vec<PlacementEvent>>, then extract the received event from the batch.
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@lib/llm/src/kv_router/publisher/zmq_listener.rs` around lines 18 - 22, The
revised start_zmq_listener contract requires updating both test paths: in
lib/llm/src/kv_router/publisher/tests.rs at line 1191, pass None immediately
after worker_id; in the test covering lines 1345-1427, change the channel to
UnboundedSender<Vec<PlacementEvent>> and extract the expected event from the
received batch before asserting it.
🟡 Minor comments (9)
lib/vllm-sidecar/src/client.rs-48-91 (1)

48-91: 🩺 Stability & Availability | 🟡 Minor | ⚡ Quick win

Fail fast on malformed endpoints.
connect_channel retries deterministic Endpoint::from_shared failures until cfg.deadline. Even after normalize_endpoint(), malformed inputs can still reach this path and stall bootstrap for the full timeout. Parse once before the retry loop and return invalid_arg immediately.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@lib/vllm-sidecar/src/client.rs` around lines 48 - 91, Update connect_channel
to validate the endpoint once before entering its retry loop, using
Endpoint::from_shared and returning invalid_arg immediately for malformed URIs.
Refactor try_connect_once to reuse the validated endpoint or otherwise avoid
reparsing it on each attempt, while preserving retries for transient connection
failures.
lib/vllm-sidecar/src/discovery.rs-141-144 (1)

141-144: 🗄️ Data Integrity & Integration | 🟡 Minor | ⚡ Quick win

Preserve 0 as a valid data-parallel start rank. data_parallel_start_rank can be 0, so this check drops the first DP group’s metadata. Gate it on data_parallel_size != 0 instead.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@lib/vllm-sidecar/src/discovery.rs` around lines 141 - 144, Update the
data_parallel_start_rank assignment in the discovery metadata construction to
gate on parallelism.data_parallel_size != 0, rather than checking
data_parallel_start_rank != 0, so rank 0 remains preserved whenever data
parallelism is configured.
components/src/dynamo/vllm/CLAUDE.md-33-37 (1)

33-37: 📐 Maintainability & Code Quality | 🟡 Minor | ⚡ Quick win

Correct the entrypoint guidance.

This PR changes handlers.py and main.py for native generate, so saying sidecar work never touches Python and that this path “stays untouched” is already false. Describe ownership without prohibiting necessary cross-layer changes.

Also applies to: 65-71

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@components/src/dynamo/vllm/CLAUDE.md` around lines 33 - 37, Update the
sidecar ownership guidance in CLAUDE.md to remove the absolute claim that
sidecar work never touches Python or that the native path must remain untouched.
State that the sidecar is separate from the in-process VllmLLMEngine path while
allowing necessary coordinated changes across Rust and Python components.
components/src/dynamo/vllm/engine_generate.py-29-41 (1)

29-41: 🎯 Functional Correctness | 🟡 Minor | ⚡ Quick win

Do not silently discard routed-expert serialization failures.

except Exception converts serialization, allocation, and programming failures into missing client metadata. Remove the catch, or catch only expected conversion failures and re-raise unexpected errors.

Proposed fail-fast change
 def serialize_routed_experts(routed_experts: Any) -> str | None:
     """Encode routed experts using vLLM's base64-of-NumPy wire format."""
     if routed_experts is None:
         return None
-    try:
-        buffer = io.BytesIO()
-        np.save(buffer, routed_experts)
-        return base64.b64encode(buffer.getvalue()).decode("ascii")
-    except Exception:
-        logger.warning(
-            "Unable to encode routed_experts for generate API", exc_info=True
-        )
-        return None
+    buffer = io.BytesIO()
+    np.save(buffer, routed_experts)
+    return base64.b64encode(buffer.getvalue()).decode("ascii")

As per coding guidelines and path instructions, broad Exception handlers must not hide failures.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@components/src/dynamo/vllm/engine_generate.py` around lines 29 - 41, Update
serialize_routed_experts so serialization failures are not converted into a None
result: remove the broad Exception handler, or restrict handling to explicitly
expected conversion failures and re-raise all unexpected errors. Preserve the
existing None input behavior and successful base64 encoding path.

Sources: Coding guidelines, Path instructions, Linters/SAST tools

components/src/dynamo/vllm/tests/test_vllm_delta_streaming.py-474-476 (1)

474-476: 🎯 Functional Correctness | 🟡 Minor | ⚡ Quick win

Assert the preserved prompt-logprob payload, not only the key.

The final response supplies prompt_logprobs=[], so this passes even if the earlier non-empty payload is overwritten or discarded. Compare the final engine_data["prompt_logprobs"] with the expected serialized values.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@components/src/dynamo/vllm/tests/test_vllm_delta_streaming.py` around lines
474 - 476, Update the assertions in the vLLM delta streaming test to validate
the contents of the final engine_data["prompt_logprobs"] payload, comparing it
with the expected serialized non-empty values rather than only checking key
presence. Preserve the existing chunk and log_probs assertions.
examples/backends/vllm/launch/sidecar_agg.sh-25-26 (1)

25-26: 🎯 Functional Correctness | 🟡 Minor | ⚡ Quick win

Honor the standard MODEL environment override consistently.

  • examples/backends/vllm/launch/sidecar_agg.sh#L25-L26: initialize with ${MODEL:-Qwen/Qwen3-0.6B}.
  • examples/backends/vllm/launch/sidecar_agg_kvbm.sh#L26-L27: initialize with ${MODEL:-Qwen/Qwen3-0.6B}.
  • examples/backends/vllm/launch/sidecar_agg_multimodal.sh#L28-L33: prioritize MODEL, retaining DYN_MODEL_NAME as a compatibility fallback if required.

As per coding guidelines, launch scripts must expose MODEL as an environment variable with a sensible default.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@examples/backends/vllm/launch/sidecar_agg.sh` around lines 25 - 26, Update
model initialization in examples/backends/vllm/launch/sidecar_agg.sh (lines
25-26) and examples/backends/vllm/launch/sidecar_agg_kvbm.sh (lines 26-27) to
use MODEL when set, defaulting to Qwen/Qwen3-0.6B. In
examples/backends/vllm/launch/sidecar_agg_multimodal.sh (lines 28-33),
prioritize MODEL while retaining DYN_MODEL_NAME as the compatibility fallback if
needed.

Source: Coding guidelines

tests/serve/tito_parity/run_pd_three_way.py-522-522 (1)

522-522: 🎯 Functional Correctness | 🟡 Minor | ⚡ Quick win

Replace runtime assertions with explicit failures.

Under python -O, these checks disappear and nullable setup state can flow into request execution.

Proposed fix
-            assert groups is not None
+            if groups is None:
+                raise RuntimeError("request groups were not initialized")
...
-    assert groups is not None
-    assert requests_by_key is not None
+    if groups is None or requests_by_key is None:
+        raise RuntimeError("upstream P/D setup did not initialize request state")

As per coding guidelines, never use assert for runtime validation.

Also applies to: 533-534

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@tests/serve/tito_parity/run_pd_three_way.py` at line 522, Replace the runtime
assert checks around groups and the related setup state with explicit validation
that raises an appropriate failure when the values are None. Update all
referenced checks, including the checks at the later indicated lines, so
validation remains active under python -O before request execution.

Sources: Coding guidelines, Path instructions

tests/serve/tito_parity/verify_generate_metrics.py-355-375 (1)

355-375: 🎯 Functional Correctness | 🟡 Minor | ⚡ Quick win

Verify that P/D worker metrics were populated, not merely registered.

These checks pass when matching series already exist with zero or stale values. Compare against before, or require positive, finite values for the selected prefill/decode workers.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@tests/serve/tito_parity/verify_generate_metrics.py` around lines 355 - 375,
Update the disaggregated validation in the metrics verification flow around
worker_ttft, worker_input, and worker_itl so it confirms the selected
prefill/decode worker metrics were populated after the test, not merely present.
Compare the matching values from before and after, or validate that the after
values are positive and finite, while preserving the existing missing-series
assertion context.
tests/serve/tito_parity/README.md-3-5 (1)

3-5: 📐 Maintainability & Code Quality | 🟡 Minor | ⚡ Quick win

Align the vLLM version in tests/serve/tito_parity/README.md:3 The repo pins vllm==0.24.0 in pyproject.toml, but this README still says 0.23.0. Update the version callout or remove it if the runner isn’t meant to be version-specific.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@tests/serve/tito_parity/README.md` around lines 3 - 5, Update the upstream
vLLM version callout in the parity runner README from 0.23.0 to the
repository-pinned 0.24.0, keeping the description otherwise unchanged.
🧹 Nitpick comments (2)
examples/backends/vllm/launch/sidecar_disagg.sh (1)

25-25: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

Make the model configurable.

This script hardcodes MODEL, unlike the multimodal variant. Provide an environment override so users need not edit the launch script.

Proposed fix
-MODEL="Qwen/Qwen3-0.6B"
+MODEL="${DYN_MODEL_NAME:-Qwen/Qwen3-0.6B}"

As per coding guidelines, “Expose tunable parameters (MODEL, ...) as environment variables with sensible defaults.”

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@examples/backends/vllm/launch/sidecar_disagg.sh` at line 25, Update the MODEL
assignment in the sidecar launch script to read from an environment variable
while retaining Qwen/Qwen3-0.6B as the default when unset, matching the
configurability pattern used by the multimodal variant.

Source: Coding guidelines

tests/serve/tito_parity/run_parity.py (1)

625-664: 🩺 Stability & Availability | 🔵 Trivial | ⚡ Quick win

Gate READY_FILE on router readiness

  • tests/serve/tito_parity/launch_dynamo_disagg.sh: replace the fixed sleep 5 with an explicit check that the frontend has switched to the active PrefillRouter before touching READY_FILE.
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@tests/serve/tito_parity/run_parity.py` around lines 625 - 664, The requested
change applies to tests/serve/tito_parity/launch_dynamo_disagg.sh lines 64-66:
replace the fixed sleep before touching READY_FILE with an explicit readiness
check that waits until the frontend has switched to the active PrefillRouter,
then create or update READY_FILE. No direct change is required in
tests/serve/tito_parity/run_parity.py lines 625-664; its run_server readiness
flow remains unchanged.

Sources: Coding guidelines, Path instructions

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@components/src/dynamo/vllm/engine_generate.py`:
- Around line 44-61: Update _native_generate_api to import decode_mm_kwargs_item
and GenerateRequest directly from the vLLM 0.24.0 vllm.entrypoints.serve.disagg
modules at module scope, remove the lazy-loading wrapper and try/except
compatibility fallback, and retain the existing symbols for callers.

In `@components/src/dynamo/vllm/main.py`:
- Around line 75-78: Update should_publish_generate_capability to return true
only for WorkerType.Aggregated or WorkerType.Decode and non-Embedding models,
excluding WorkerType.Encode explicitly. Extend the related test matrix with a
ModelType.Chat and WorkerType.Encode case asserting the capability is not
published.

In `@components/src/dynamo/vllm/tests/test_vllm_engine_generate.py`:
- Around line 9-10: Guard the module-level vLLM import in
test_vllm_engine_generate.py with pytest.importorskip("vllm.sampling_params") so
collection is skipped when vLLM is unavailable, while preserving access to
RequestOutputKind and SamplingParams when the dependency exists.

In `@examples/backends/vllm/deploy/lora/sidecar_agg_lora.yaml`:
- Around line 51-53: Remove the workingDir setting from the mainContainer in
examples/backends/vllm/deploy/lora/sidecar_agg_lora.yaml#L51-L53 and
examples/backends/vllm/deploy/sidecar_agg.yaml#L56-L58, and from both decode and
prefill sidecars in examples/backends/vllm/deploy/sidecar_disagg.yaml#L57-L59
and `#L94-L96`. Leave the existing absolute sidecar commands and paths unchanged.
- Around line 63-67: The v1alpha1 sidecar manifests must atomically replace
generated worker arguments when overriding the command. Update the args override
contract at examples/backends/vllm/deploy/lora/sidecar_agg_lora.yaml:63-67,
examples/backends/vllm/deploy/sidecar_agg.yaml:59-63, and both decode and
prefill sites in examples/backends/vllm/deploy/sidecar_disagg.yaml:60-64 and
:97-101 so only the explicitly defined sidecar arguments are passed.

In `@examples/backends/vllm/launch/lora/sidecar_agg_lora.sh`:
- Around line 8-10: Source gpu_utils.sh and thread GPU_MEM_ARGS into the vllm-rs
serve launch flow in examples/backends/vllm/launch/lora/sidecar_agg_lora.sh
(lines 8-10), examples/backends/vllm/launch/sidecar_agg.sh (lines 22-23),
examples/backends/vllm/launch/sidecar_agg_kvbm.sh (lines 23-24), and
examples/backends/vllm/launch/sidecar_agg_multimodal.sh (lines 25-26), ensuring
_PROFILE_OVERRIDE_VLLM_KV_CACHE_BYTES reaches each sidecar launcher.

In `@lib/bindings/python/rust/backend.rs`:
- Around line 820-821: Preserve LoRA metadata across both conversion paths in
lib/bindings/python/rust/backend.rs:820-821 and
lib/bindings/python/rust/backend.rs:157-158. Update the duck-typed .llm
extraction to read supports_lora and max_loras with backward-compatible defaults
instead of forcing false/None, and extend the Python LlmRegistration constructor
and getters to store and expose both fields.

In `@lib/vllm-sidecar/src/tests/lifecycle.rs`:
- Around line 188-200: Update the TransportConfig used to construct
VllmSidecarEngine so it sets the RPC deadline to the required short duration
instead of only setting connect_timeout. Keep the existing test_transport
configuration and ensure list_loras() observes that RPC deadline while the
channel is already connected.

In `@lib/vllm-sidecar/src/wire.rs`:
- Around line 67-77: Update the terminal-event handling around the is_prefill
branch so GenerateEvent::PrefillReady is emitted only for explicitly successful
prefill reasons. When the terminal reason is Aborted, propagate
cancellation/error instead of performing the KV-parameter conversion or handoff,
while preserving the existing validation for successful responses.
- Around line 108-114: Update json_to_prost_value and its corresponding
number_to_json conversion to preserve KV-transfer numeric values without lossy
f64 conversion. Reject numbers that cannot be represented exactly rather than
rounding or saturating them, or consistently encode 64-bit identifiers as
strings across the contract; ensure large integers do not become incorrect
values such as i64::MAX.

In `@tests/serve/tito_parity/launch_dynamo_disagg.sh`:
- Around line 5-19: Replace raw process-group cleanup with the shared helpers:
in tests/serve/tito_parity/launch_dynamo_disagg.sh lines 5-19, validate
arguments and source launch_utils.sh before registering cleanup, then capture
the exit status in a cleanup function that removes READY_FILE and invokes
dynamo_reap_and_exit; in examples/backends/vllm/launch/sidecar_disagg.sh line 36
and examples/backends/vllm/launch/sidecar_disagg_multimodal_p_d.sh line 42,
replace the raw EXIT traps with trap dynamo_exit_trap EXIT.

In `@tests/serve/tito_parity/run_parity.py`:
- Around line 29-35: The parity runners currently use fixed ports, preventing
concurrent execution. In tests/serve/tito_parity/run_parity.py (lines 29-35),
use allocate_port()/allocate_ports() for upstream and Dynamo ports and propagate
them through commands and environment; in
tests/serve/tito_parity/run_pd_three_way.py (lines 19-20), allocate prefill and
decode ports instead of 8100/8200. Update
tests/serve/tito_parity/launch_dynamo_disagg.sh (lines 36-62) to accept injected
system, NIXL, event, and health ports, and update
examples/backends/vllm/launch/sidecar_disagg.sh (lines 68-73) plus
examples/backends/vllm/launch/sidecar_disagg_multimodal_p_d.sh (lines 95-100) to
read NIXL side-channel and KV-event ports from environment variables.

In `@tests/serve/tito_parity/verify_generate_metrics.py`:
- Around line 193-228: Update the request orchestration around the
ThreadPoolExecutor and metrics polling to saturate worker capacity, then submit
an additional Generate request before asserting queued_requests. Ensure the
extra request remains pending long enough to observe
dynamo_frontend_queued_requests above baseline, while preserving the existing
active and inflight observations and cleaning up all submitted futures.

---

Outside diff comments:
In `@components/src/dynamo/vllm/llm_engine.py`:
- Around line 630-671: Complete the native P/D prompt-logprob handoff across
components/src/dynamo/vllm/llm_engine.py lines 630-671, 483-505, and 553-578,
plus components/src/dynamo/vllm/handlers.py lines 453-472: add
prompt_logprobs_payload to prefill disaggregated_params, read it on decode and
clear sampling_params.prompt_logprobs, reuse the transferred payload instead of
collecting decode prompt logprobs, and ensure handlers clear decode
prompt-logprob computation even when no payload is transferred.

In `@lib/backend-common/src/worker.rs`:
- Around line 866-881: Wrap the post-attach initialization, including
LoraController::new and the related setup before orchestrator_steps(), with
rollback handling. If any initialization step fails, call
endpoint.unregister_endpoint_instance() before propagating the original error;
preserve the existing successful path and ensure cleanup also covers the
additional referenced range.

In `@lib/llm/src/kv_router/publisher/zmq_listener.rs`:
- Around line 18-22: The revised start_zmq_listener contract requires updating
both test paths: in lib/llm/src/kv_router/publisher/tests.rs at line 1191, pass
None immediately after worker_id; in the test covering lines 1345-1427, change
the channel to UnboundedSender<Vec<PlacementEvent>> and extract the expected
event from the received batch before asserting it.

---

Minor comments:
In `@components/src/dynamo/vllm/CLAUDE.md`:
- Around line 33-37: Update the sidecar ownership guidance in CLAUDE.md to
remove the absolute claim that sidecar work never touches Python or that the
native path must remain untouched. State that the sidecar is separate from the
in-process VllmLLMEngine path while allowing necessary coordinated changes
across Rust and Python components.

In `@components/src/dynamo/vllm/engine_generate.py`:
- Around line 29-41: Update serialize_routed_experts so serialization failures
are not converted into a None result: remove the broad Exception handler, or
restrict handling to explicitly expected conversion failures and re-raise all
unexpected errors. Preserve the existing None input behavior and successful
base64 encoding path.

In `@components/src/dynamo/vllm/tests/test_vllm_delta_streaming.py`:
- Around line 474-476: Update the assertions in the vLLM delta streaming test to
validate the contents of the final engine_data["prompt_logprobs"] payload,
comparing it with the expected serialized non-empty values rather than only
checking key presence. Preserve the existing chunk and log_probs assertions.

In `@examples/backends/vllm/launch/sidecar_agg.sh`:
- Around line 25-26: Update model initialization in
examples/backends/vllm/launch/sidecar_agg.sh (lines 25-26) and
examples/backends/vllm/launch/sidecar_agg_kvbm.sh (lines 26-27) to use MODEL
when set, defaulting to Qwen/Qwen3-0.6B. In
examples/backends/vllm/launch/sidecar_agg_multimodal.sh (lines 28-33),
prioritize MODEL while retaining DYN_MODEL_NAME as the compatibility fallback if
needed.

In `@lib/vllm-sidecar/src/client.rs`:
- Around line 48-91: Update connect_channel to validate the endpoint once before
entering its retry loop, using Endpoint::from_shared and returning invalid_arg
immediately for malformed URIs. Refactor try_connect_once to reuse the validated
endpoint or otherwise avoid reparsing it on each attempt, while preserving
retries for transient connection failures.

In `@lib/vllm-sidecar/src/discovery.rs`:
- Around line 141-144: Update the data_parallel_start_rank assignment in the
discovery metadata construction to gate on parallelism.data_parallel_size != 0,
rather than checking data_parallel_start_rank != 0, so rank 0 remains preserved
whenever data parallelism is configured.

In `@tests/serve/tito_parity/README.md`:
- Around line 3-5: Update the upstream vLLM version callout in the parity runner
README from 0.23.0 to the repository-pinned 0.24.0, keeping the description
otherwise unchanged.

In `@tests/serve/tito_parity/run_pd_three_way.py`:
- Line 522: Replace the runtime assert checks around groups and the related
setup state with explicit validation that raises an appropriate failure when the
values are None. Update all referenced checks, including the checks at the later
indicated lines, so validation remains active under python -O before request
execution.

In `@tests/serve/tito_parity/verify_generate_metrics.py`:
- Around line 355-375: Update the disaggregated validation in the metrics
verification flow around worker_ttft, worker_input, and worker_itl so it
confirms the selected prefill/decode worker metrics were populated after the
test, not merely present. Compare the matching values from before and after, or
validate that the after values are positive and finite, while preserving the
existing missing-series assertion context.

---

Nitpick comments:
In `@examples/backends/vllm/launch/sidecar_disagg.sh`:
- Line 25: Update the MODEL assignment in the sidecar launch script to read from
an environment variable while retaining Qwen/Qwen3-0.6B as the default when
unset, matching the configurability pattern used by the multimodal variant.

In `@tests/serve/tito_parity/run_parity.py`:
- Around line 625-664: The requested change applies to
tests/serve/tito_parity/launch_dynamo_disagg.sh lines 64-66: replace the fixed
sleep before touching READY_FILE with an explicit readiness check that waits
until the frontend has switched to the active PrefillRouter, then create or
update READY_FILE. No direct change is required in
tests/serve/tito_parity/run_parity.py lines 625-664; its run_server readiness
flow remains unchanged.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Enterprise

Run ID: bbfc4fbb-f970-4172-92b9-16881b15f884

📥 Commits

Reviewing files that changed from the base of the PR and between 01d2cb2 and 336b393.

⛔ Files ignored due to path filters (1)
  • Cargo.lock is excluded by !**/*.lock
📒 Files selected for processing (81)
  • Cargo.toml
  • components/src/dynamo/common/backend/publisher.py
  • components/src/dynamo/common/backend/tests/test_publisher.py
  • components/src/dynamo/frontend/frontend_args.py
  • components/src/dynamo/frontend/main.py
  • components/src/dynamo/frontend/tests/test_frontend_args.py
  • components/src/dynamo/vllm/CLAUDE.md
  • components/src/dynamo/vllm/args.py
  • components/src/dynamo/vllm/backend_args.py
  • components/src/dynamo/vllm/constants.py
  • components/src/dynamo/vllm/engine_generate.py
  • components/src/dynamo/vllm/handlers.py
  • components/src/dynamo/vllm/kv_event_utils.py
  • components/src/dynamo/vllm/llm_engine.py
  • components/src/dynamo/vllm/main.py
  • components/src/dynamo/vllm/tests/test_runtime_metadata.py
  • components/src/dynamo/vllm/tests/test_vllm_delta_streaming.py
  • components/src/dynamo/vllm/tests/test_vllm_engine_generate.py
  • components/src/dynamo/vllm/tests/test_vllm_unit.py
  • examples/backends/vllm/deploy/README.md
  • examples/backends/vllm/deploy/lora/sidecar_agg_lora.yaml
  • examples/backends/vllm/deploy/sidecar_agg.yaml
  • examples/backends/vllm/deploy/sidecar_disagg.yaml
  • examples/backends/vllm/launch/lora/README.md
  • examples/backends/vllm/launch/lora/sidecar_agg_lora.sh
  • examples/backends/vllm/launch/sidecar_agg.sh
  • examples/backends/vllm/launch/sidecar_agg_kvbm.sh
  • examples/backends/vllm/launch/sidecar_agg_multimodal.sh
  • examples/backends/vllm/launch/sidecar_disagg.sh
  • examples/backends/vllm/launch/sidecar_disagg_multimodal_p_d.sh
  • lib/backend-common/Cargo.toml
  • lib/backend-common/examples/mocker/src/engine.rs
  • lib/backend-common/src/drain.rs
  • lib/backend-common/src/engine.rs
  • lib/backend-common/src/lib.rs
  • lib/backend-common/src/lora.rs
  • lib/backend-common/src/publisher.rs
  • lib/backend-common/src/worker.rs
  • lib/bindings/python/rust/backend.rs
  • lib/bindings/python/rust/llm/kv.rs
  • lib/kv-router/src/zmq_wire/deserialize.rs
  • lib/kv-router/src/zmq_wire/extra_keys.rs
  • lib/kv-router/src/zmq_wire/mod.rs
  • lib/llm/src/discovery/model.rs
  • lib/llm/src/discovery/model_manager.rs
  • lib/llm/src/http/service/generate.rs
  • lib/llm/src/kv_router/publisher/mod.rs
  • lib/llm/src/kv_router/publisher/tests.rs
  • lib/llm/src/kv_router/publisher/zmq_listener.rs
  • lib/llm/src/local_model.rs
  • lib/llm/src/mocker.rs
  • lib/llm/src/protocols/openai/generate.rs
  • lib/runtime/src/slug.rs
  • lib/runtime/src/storage/kv/file.rs
  • lib/sglang-sidecar/src/engine.rs
  • lib/vllm-rs-backend/src/backend.rs
  • lib/vllm-sidecar/Cargo.toml
  • lib/vllm-sidecar/build.rs
  • lib/vllm-sidecar/proto/vllm_grpc.proto
  • lib/vllm-sidecar/src/args.rs
  • lib/vllm-sidecar/src/client.rs
  • lib/vllm-sidecar/src/discovery.rs
  • lib/vllm-sidecar/src/engine.rs
  • lib/vllm-sidecar/src/lib.rs
  • lib/vllm-sidecar/src/main.rs
  • lib/vllm-sidecar/src/proto.rs
  • lib/vllm-sidecar/src/request.rs
  • lib/vllm-sidecar/src/tests.rs
  • lib/vllm-sidecar/src/tests/bootstrap.rs
  • lib/vllm-sidecar/src/tests/conversion.rs
  • lib/vllm-sidecar/src/tests/disaggregation.rs
  • lib/vllm-sidecar/src/tests/generation.rs
  • lib/vllm-sidecar/src/tests/lifecycle.rs
  • lib/vllm-sidecar/src/wire.rs
  • tests/report_pytest_markers.py
  • tests/serve/tito_parity/README.md
  • tests/serve/tito_parity/launch_dynamo_disagg.sh
  • tests/serve/tito_parity/run_parity.py
  • tests/serve/tito_parity/run_pd_three_way.py
  • tests/serve/tito_parity/run_seeded_rl_parity.py
  • tests/serve/tito_parity/verify_generate_metrics.py

Comment on lines +44 to +61
@lru_cache(maxsize=1)
def _native_generate_api() -> tuple[Any, Any]:
"""Load the vLLM-native endpoint adapter only when `/generate` is used."""
try:
from vllm.entrypoints.scale_out.token_in_token_out.mm_serde import (
decode_mm_kwargs_item,
)
from vllm.entrypoints.scale_out.token_in_token_out.protocol import (
GenerateRequest,
)
except ModuleNotFoundError as exc:
expected_module = "vllm.entrypoints.scale_out.token_in_token_out"
if exc.name is None or not expected_module.startswith(exc.name):
raise
from vllm.entrypoints.serve.disagg.mm_serde import decode_mm_kwargs_item
from vllm.entrypoints.serve.disagg.protocol import GenerateRequest

return decode_mm_kwargs_item, GenerateRequest

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

📐 Maintainability & Code Quality | 🟠 Major | ⚡ Quick win

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
# Verify the repository's declared vLLM version and all endpoint-module imports.
fd -H -t f '^(pyproject\.toml|uv\.lock|requirements.*\.txt)$' . -0 |
  xargs -0 -r rg -n -C2 '\bvllm\b'

rg -n -C3 \
  'entrypoints\.(scale_out\.token_in_token_out|serve\.disagg)' \
  components/src/dynamo

Repository: ai-dynamo/dynamo

Length of output: 6724


🏁 Script executed:

#!/bin/bash
set -euo pipefail

# Inspect the file and nearby call sites to understand whether the lazy imports are required.
ast-grep outline components/src/dynamo/vllm/engine_generate.py --view expanded || true

echo '--- engine_generate.py (relevant slice) ---'
sed -n '1,220p' components/src/dynamo/vllm/engine_generate.py

echo '--- search for _native_generate_api usages ---'
rg -n -C3 '_native_generate_api|decode_mm_kwargs_item|GenerateRequest' components/src/dynamo/vllm components/src/dynamo | sed -n '1,220p'

Repository: ai-dynamo/dynamo

Length of output: 26205


🏁 Script executed:

#!/bin/bash
set -euo pipefail

# Check whether the vLLM 0.24.0 package layout in this repo expects the token_in_token_out path.
rg -n -C2 'scale_out\.token_in_token_out|serve\.disagg|generate' pyproject.toml components/src/dynamo/vllm container/deps/requirements.vllm.txt | sed -n '1,260p'

Repository: ai-dynamo/dynamo

Length of output: 20420


🏁 Script executed:

#!/bin/bash
set -euo pipefail

# Look for repository docs or code comments explaining the vLLM endpoint import split.
rg -n -C2 'token_in_token_out|disagg|scale_out|native generate|/generate' .ai components/src/dynamo | sed -n '1,260p'

Repository: ai-dynamo/dynamo

Length of output: 21184


🏁 Script executed:

#!/bin/bash
set -euo pipefail

# Resolve whether the repository treats vLLM imports as required or as backend-specific/optional in config.
sed -n '150,210p' pyproject.toml

Repository: ai-dynamo/dynamo

Length of output: 2849


🏁 Script executed:

#!/bin/bash
set -euo pipefail

# Search the repo for any mention of the two vLLM package layouts and version-specific compatibility notes.
rg -n -C3 'vllm-openai|0\.24\.0|token_in_token_out|serve\.disagg|scale_out' . | sed -n '1,260p'

Repository: ai-dynamo/dynamo

Length of output: 23272


🌐 Web query:

vLLM 0.24.0 token_in_token_out serve.disagg mm_serde GenerateRequest

💡 Result:

In vLLM 0.24.0, the components you referenced relate to the library's advanced serving infrastructure, specifically for disaggregated architectures and specialized communication protocols [1][2][3]. 1. serve.disagg: This refers to the vLLM namespace (vllm.entrypoints.serve.disagg) designed for disaggregated serving, a deployment pattern where prefill (prompt processing) and decode (generation) tasks are decoupled across different instances or clusters [2][3][4]. This architecture optimizes throughput and latency by scaling prefill and decode resources independently [4]. 2. GenerateRequest: This is a Pydantic BaseModel defined within vllm.entrypoints.serve.disagg.protocol.py [5][6]. It acts as the structured schema for inference requests handled by the disaggregated serving system [5]. Key fields in GenerateRequest include token_ids, multimodal features, sampling parameters, request ID, and metadata for KV cache transfer between instances [5][6]. 3. mm_serde: Located within the disagg and scale_out entrypoint modules, mm_serde provides utility functions for the serialization and deserialization (serde) of multimodal tensors and associated field metadata [7][3]. It ensures that complex multimodal inputs are correctly packaged and transmitted within the vLLM ecosystem [7]. 4. token_in_token_out: While often mentioned in the same context as disaggregated serving, this typically refers to a distinct entrypoint (vllm.entrypoints.scale_out.token_in_token_out) designed to provide specific token-level interfaces for external clients, enabling granular control over input token processing and output token generation in distributed environments [7][8][9]. vLLM 0.24.0 was released on June 29, 2026, and introduced significant improvements to model support (e.g., MiniMax-M3) and internal engine performance [1][10]. The modules you identified are part of the supporting infrastructure for these advanced deployment capabilities [2][3].

Citations:


🌐 Web query:

site:github.com/vllm-project/vllm "token_in_token_out" "serve.disagg" vllm 0.24.0

💡 Result:

In vLLM 0.24.0, disaggregated serving (often referred to as Prefill/Decode or P/D splitting) is a feature that allows running separate vLLM instances for prefill and decoding tasks, connected by a KV cache transfer mechanism [1][2]. Regarding your query about "token_in_token_out serve.disagg," the codebase includes an internal module vllm.entrypoints.serve.disagg [3][4] that facilitates handling requests in this distributed architecture. Key technical aspects as of version 0.24.0 include: 1. Architecture: The system functions by running a "KV producer" (prefill instance) and a "KV consumer" (decode instance) [1][5]. A connector is used to transfer KV caches between these instances [1][6]. 2. Connectors: vLLM supports various connectors for KV transfer, such as the Mooncake connector [7][2]. Note that the P2pNcclConnector was removed in version 0.24.0 [2]. 3. Configuration: Disaggregated serving is configured using the --kv-transfer-config flag during vLLM server startup [8][7][9]. This flag accepts a JSON string specifying the connector type, the instance role (e.g., kv_producer or kv_consumer), and additional necessary parameters [8][5][7]. 4. Proxying: Because the prefill and decode stages are separate, a proxy server is typically required to route incoming user requests to the prefill instance first, and then manage the transition or hand-off to the decode instance once prefill is complete [5][7][10]. For exact implementations and current best practices, you should refer to the examples/disaggregated/ directory in the vLLM repository, which provides scripts for setting up these multi-instance deployments [5][7][10]. The internal vllm/entrypoints/serve/disagg/ path is primarily intended for system-level request handling and internal testing [3][4].

Citations:


🌐 Web query:

vllm 0.24.0 "vllm.entrypoints.scale_out.token_in_token_out" "vllm.entrypoints.serve.disagg" exact package path

💡 Result:

In vLLM version 0.24.0, the requested namespaces correspond to the following file system paths: 1. vllm.entrypoints.serve.disagg: The exact package path is vllm/entrypoints/serve/disagg/ [1][2]. 2. vllm.entrypoints.scale_out.token_in_token_out: While you requested this path for v0.24.0, authoritative documentation indicates this module is located at vllm/entrypoints/scale_out/token_in_token_out/ [3][4][5]. Note that serving components within this namespace are found at vllm/entrypoints/scale_out/token_in_token_out/serving.py [4]. It is important to note that the vLLM codebase structure evolves across versions; documentation for v0.24.0 specifically lists the disaggregated serving modules under vllm/entrypoints/serve/disagg/ [1]. If you are looking for specific functionality such as the serving logic, it is typically located in a serving.py file within those respective directories [4][6][7].

Citations:


🌐 Web query:

site:github.com/vllm-project/vllm "v0.24.0" "token_in_token_out" "serve.disagg" "mm_serde"

💡 Result:

In vLLM v0.24.0, the components you referenced relate to the "Disaggregated Everything" architecture, which decouples request rendering from token-level inference [1][2][3]. The token_in_token_out directory structure, specifically under vllm/entrypoints/scale_out/token_in_token_out/, consolidates the entrypoints for this disaggregated serving flow [4]. This architecture enables a separation of duties where a "Renderer" node handles chat/prompt processing (producing token IDs and features) and a "Generate" node handles core model inference [5][2][3]. Key components in this flow include: 1. serve.disagg: Contains the logic for the disaggregated inference service. This includes the ServingTokens class (which manages the /generate endpoint) and protocol.py, which defines the GenerateRequest and GenerateResponse structures used for passing tokens and multimodal features between nodes [6][2][7]. 2. mm_serde: Short for "multimodal serialization/deserialization," this module is used to handle the efficient transmission of multimodal features (such as pixel_values or other tensor-based data) within the disaggregated request payloads [8][9]. Multimodal data is often serialized using base64-encoded MessagePack to be passed between the rendering and inference nodes [8]. In this workflow, the /v1/chat/completions/render endpoint processes requests into token IDs and serialized multimodal features (via mm_serde), which are then submitted to the /inference/v1/generate endpoint for model execution [5][8]. This design allows vLLM to scale components independently and move computationally intensive rendering tasks away from the primary inference engine [2][3]. Top results: 1, 2, 3, 5, 8

Citations:


🏁 Script executed:

#!/bin/bash
set -euo pipefail

sed -n '40,90p' components/src/dynamo/vllm/CLAUDE.md

Repository: ai-dynamo/dynamo

Length of output: 2447


🏁 Script executed:

#!/bin/bash
set -euo pipefail

# Inspect the exact import usage in the engine code to see whether any module-scope alternative exists.
sed -n '1,120p' components/src/dynamo/vllm/engine_generate.py

Repository: ai-dynamo/dynamo

Length of output: 4391


🌐 Web query:

site:docs.vllm.ai/en/v0.24.0 "token_in_token_out" vllm

💡 Result:

There is no specific technical feature, metric, or configuration option named "token_in_token_out" documented in the official vLLM framework [1][2][3][4][5]. It is possible that this term is a colloquial reference to standard request metrics—typically reported as prompt_tokens and completion_tokens (or output_tokens)—that describe the volume of data entering and exiting the inference engine during a generation task. If you are looking for information related to token handling in vLLM, the official documentation focuses on areas such as: 1. Token Embeddings and Token Classification: vLLM provides utilities for tasks like token-level embedding and classification, which are distinct from generative request metrics [1][3][5]. 2. Speculative Decoding and MTP: Advanced features like Multi-Token Prediction (MTP) manage how tokens are predicted and processed in batches [2]. 3. Structured Output: vLLM APIs include backends (such as Outlines) that process lists of tokens to advance finite state machines for structured generation [4]. If you are encountering this term in logs or external tools used alongside vLLM (such as Prometheus exporters or monitoring dashboards), it likely represents custom naming conventions for tracking input and output token usage, rather than a native vLLM internal command or configuration parameter.

Citations:


🌐 Web query:

site:docs.vllm.ai/en/v0.24.0 "serve.disagg" vllm

💡 Result:

In vLLM, vllm.entrypoints.serve.disagg refers to the module supporting disaggregated serving [1]. Disaggregated serving is a deployment architecture that separates different stages of the model inference process—specifically the prefill (prompt processing), decode (token generation), and encoding phases—into distinct, independently scalable components [2][3][4]. Key aspects include: - Scalability: By disaggregating these stages, users can allocate resources (such as different types of GPU hardware) more efficiently, as prefill and decode tasks have different computational intensity and memory requirements [2][3]. - Module Structure: The vllm.entrypoints.serve.disagg namespace contains the necessary protocol, serialization, and serving utilities required to coordinate these disaggregated components [1][5]. - Use Cases: This approach is particularly useful in production environments where optimizing throughput and latency for long-context or high-traffic LLM workloads is required [3][6]. For detailed implementation and configuration, refer to the official vLLM examples for disaggregated serving, which include guides on setting up disaggregated prefill and encoders [3][6][4].

Citations:


Import the vLLM-native adapter at module scope

components/src/dynamo/vllm/engine_generate.py:45-61
The repo is pinned to vLLM 0.24.0, which exposes vllm.entrypoints.serve.disagg; import that path directly here and drop the try/except ModuleNotFoundError shim.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@components/src/dynamo/vllm/engine_generate.py` around lines 44 - 61, Update
_native_generate_api to import decode_mm_kwargs_item and GenerateRequest
directly from the vLLM 0.24.0 vllm.entrypoints.serve.disagg modules at module
scope, remove the lazy-loading wrapper and try/except compatibility fallback,
and retain the existing symbols for callers.

Sources: Coding guidelines, Path instructions

Comment thread components/src/dynamo/vllm/main.py Outdated
Comment on lines +75 to +78
def should_publish_generate_capability(
model_type: ModelType, worker_type: WorkerType
) -> bool:
return worker_type != WorkerType.Prefill and model_type != ModelType.Embedding

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

🎯 Functional Correctness | 🟠 Major | ⚡ Quick win

Do not advertise generate capability from Encode workers.

WorkerType.Encode passes the current negative checks even though it is not a generative worker. Use an explicit allowlist for Aggregated and Decode, and add an Encode case to the test matrix.

Proposed fix
 def should_publish_generate_capability(
     model_type: ModelType, worker_type: WorkerType
 ) -> bool:
-    return worker_type != WorkerType.Prefill and model_type != ModelType.Embedding
+    return (
+        worker_type in (WorkerType.Aggregated, WorkerType.Decode)
+        and model_type != ModelType.Embedding
+    )
assert not should_publish_generate_capability(
    ModelType.Chat, WorkerType.Encode
)

Also applies to: 675-677

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@components/src/dynamo/vllm/main.py` around lines 75 - 78, Update
should_publish_generate_capability to return true only for WorkerType.Aggregated
or WorkerType.Decode and non-Embedding models, excluding WorkerType.Encode
explicitly. Extend the related test matrix with a ModelType.Chat and
WorkerType.Encode case asserting the capability is not published.

Comment on lines +9 to +10
import pytest
from vllm.sampling_params import RequestOutputKind, SamplingParams

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

🩺 Stability & Availability | 🟠 Major | ⚡ Quick win

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
set -euo pipefail

printf '\n== target file ==\n'
cat -n components/src/dynamo/vllm/tests/test_vllm_engine_generate.py

printf '\n== nearby vllm tests using importorskip ==\n'
rg -n "importorskip|vllm.sampling_params|pytestmark" components/src/dynamo/vllm/tests -g '*.py'

printf '\n== test file list ==\n'
git ls-files components/src/dynamo/vllm/tests

Repository: ai-dynamo/dynamo

Length of output: 21086


🏁 Script executed:

#!/bin/bash
set -euo pipefail

sed -n '1,220p' components/src/dynamo/vllm/tests/test_vllm_engine_generate.py
echo
find components/src/dynamo/vllm/tests -maxdepth 1 -name '*.py' -print0 | xargs -0 -n1 sh -c 'echo "---- $1"; rg -n "importorskip|vllm\.sampling_params|pytestmark" "$1"' sh
echo
rg -n '"vllm"|vllm' pyproject.toml components/src/dynamo -g 'pyproject.toml' -g '*.toml' -g '*.yaml' -g '*.yml'

Repository: ai-dynamo/dynamo

Length of output: 11111


Guard the vLLM import at module scope.

from vllm.sampling_params import RequestOutputKind, SamplingParams makes this test file fail collection in environments without vLLM. Move it behind pytest.importorskip("vllm.sampling_params") or another module-level guard.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@components/src/dynamo/vllm/tests/test_vllm_engine_generate.py` around lines 9
- 10, Guard the module-level vLLM import in test_vllm_engine_generate.py with
pytest.importorskip("vllm.sampling_params") so collection is skipped when vLLM
is unavailable, while preserving access to RequestOutputKind and SamplingParams
when the dependency exists.

Source: Learnings

Comment on lines +51 to +53
mainContainer:
image: my-registry/vllm-sidecar-runtime:my-tag
workingDir: /workspace/examples/backends/vllm

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

🩺 Stability & Availability | 🟠 Major | ⚡ Quick win

The manifests require a working directory absent from the documented image build. Remove it because all sidecar commands and paths are absolute.

  • examples/backends/vllm/deploy/lora/sidecar_agg_lora.yaml#L51-L53: remove workingDir.
  • examples/backends/vllm/deploy/sidecar_agg.yaml#L56-L58: remove workingDir.
  • examples/backends/vllm/deploy/sidecar_disagg.yaml#L57-L59: remove it from the decode sidecar.
  • examples/backends/vllm/deploy/sidecar_disagg.yaml#L94-L96: remove it from the prefill sidecar.
📍 Affects 3 files
  • examples/backends/vllm/deploy/lora/sidecar_agg_lora.yaml#L51-L53 (this comment)
  • examples/backends/vllm/deploy/sidecar_agg.yaml#L56-L58
  • examples/backends/vllm/deploy/sidecar_disagg.yaml#L57-L59
  • examples/backends/vllm/deploy/sidecar_disagg.yaml#L94-L96
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@examples/backends/vllm/deploy/lora/sidecar_agg_lora.yaml` around lines 51 -
53, Remove the workingDir setting from the mainContainer in
examples/backends/vllm/deploy/lora/sidecar_agg_lora.yaml#L51-L53 and
examples/backends/vllm/deploy/sidecar_agg.yaml#L56-L58, and from both decode and
prefill sidecars in examples/backends/vllm/deploy/sidecar_disagg.yaml#L57-L59
and `#L94-L96`. Leave the existing absolute sidecar commands and paths unchanged.

Comment on lines +63 to +67
command:
- dynamo-vllm-sidecar
args:
- --grpc-endpoint
- 127.0.0.1:50051

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

🩺 Stability & Availability | 🟠 Major | 🏗️ Heavy lift

The v1alpha1 manifests append sidecar arguments to generated worker arguments. Because each manifest replaces the worker command, the Rust binary may receive unrelated generated options and exit during parsing.

  • examples/backends/vllm/deploy/lora/sidecar_agg_lora.yaml#L63-L67: use an override contract that atomically replaces args.
  • examples/backends/vllm/deploy/sidecar_agg.yaml#L59-L63: replace rather than append the generated worker arguments.
  • examples/backends/vllm/deploy/sidecar_disagg.yaml#L60-L64: replace the decode worker’s generated arguments.
  • examples/backends/vllm/deploy/sidecar_disagg.yaml#L97-L101: replace the prefill worker’s generated arguments.
📍 Affects 3 files
  • examples/backends/vllm/deploy/lora/sidecar_agg_lora.yaml#L63-L67 (this comment)
  • examples/backends/vllm/deploy/sidecar_agg.yaml#L59-L63
  • examples/backends/vllm/deploy/sidecar_disagg.yaml#L60-L64
  • examples/backends/vllm/deploy/sidecar_disagg.yaml#L97-L101
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@examples/backends/vllm/deploy/lora/sidecar_agg_lora.yaml` around lines 63 -
67, The v1alpha1 sidecar manifests must atomically replace generated worker
arguments when overriding the command. Update the args override contract at
examples/backends/vllm/deploy/lora/sidecar_agg_lora.yaml:63-67,
examples/backends/vllm/deploy/sidecar_agg.yaml:59-63, and both decode and
prefill sites in examples/backends/vllm/deploy/sidecar_disagg.yaml:60-64 and
:97-101 so only the explicitly defined sidecar arguments are passed.

Comment thread lib/vllm-sidecar/src/wire.rs Outdated
Comment on lines +67 to +77
if is_prefill {
let params = finish
.kv_transfer_params
.as_ref()
.filter(|params| !params.fields.is_empty())
.ok_or_else(|| {
client::protocol_error("prefill response is missing kv_transfer_params")
})?;
events.push(GenerateEvent::PrefillReady(prost_struct_to_json(params)));
} else {
events.push(GenerateEvent::Finished(reason));

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

🗄️ Data Integrity & Integration | 🟠 Major | ⚡ Quick win

Do not convert an aborted prefill into a successful KV handoff.

Any terminal reason with KV parameters currently becomes PrefillReady, including Aborted. Explicitly accept successful prefill reasons and propagate aborted responses as cancellation/error to prevent decode from consuming incomplete KV state.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@lib/vllm-sidecar/src/wire.rs` around lines 67 - 77, Update the terminal-event
handling around the is_prefill branch so GenerateEvent::PrefillReady is emitted
only for explicitly successful prefill reasons. When the terminal reason is
Aborted, propagate cancellation/error instead of performing the KV-parameter
conversion or handoff, while preserving the existing validation for successful
responses.

Comment thread lib/vllm-sidecar/src/wire.rs Outdated
Comment on lines +108 to +114
fn json_to_prost_value(value: &serde_json::Value) -> prost_types::Value {
use prost_types::value::Kind;
let kind = match value {
serde_json::Value::Null => Kind::NullValue(prost_types::NullValue::NullValue as i32),
serde_json::Value::Bool(value) => Kind::BoolValue(*value),
serde_json::Value::Number(value) => Kind::NumberValue(value.as_f64().unwrap_or(0.0)),
serde_json::Value::String(value) => Kind::StringValue(value.clone()),

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

🗄️ Data Integrity & Integration | 🟠 Major | 🏗️ Heavy lift

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
# Inspect every affected conversion and KV-transfer call site.
ast-grep outline lib/vllm-sidecar/src/wire.rs --items all
rg -n -C3 '\b(json_to_prost_struct|prost_struct_to_json|kv_transfer_params)\b' lib/vllm-sidecar

Repository: ai-dynamo/dynamo

Length of output: 11731


🏁 Script executed:

#!/bin/bash
set -euo pipefail

sed -n '96,170p' lib/vllm-sidecar/src/wire.rs
printf '\n--- tests/conversion.rs ---\n'
sed -n '1,220p' lib/vllm-sidecar/src/tests/conversion.rs
printf '\n--- tests/disaggregation.rs ---\n'
sed -n '1,140p' lib/vllm-sidecar/src/tests/disaggregation.rs

Repository: ai-dynamo/dynamo

Length of output: 12627


🏁 Script executed:

#!/bin/bash
set -euo pipefail

python3 - <<'PY'
import json
from decimal import Decimal

# Probe the concrete edge cases mentioned in the review:
# 1) integers beyond 2^53 when represented as f64
# 2) reverse conversion of large numeric values back to JSON integers
cases = [
    2**53 - 1,
    2**53,
    2**53 + 1,
    2**63 - 1,
    2**63,
]
for n in cases:
    f = float(n)
    print(n, repr(f), int(f) if f.is_integer() else None, f == n)

print("--- json round-trip examples ---")
for s in ["9007199254740991", "9007199254740992", "9007199254740993", "9223372036854775808"]:
    n = json.loads(s)
    f = float(n)
    print(s, "->", repr(f), "->", json.dumps(int(f)) if f.is_integer() else "non-int")
PY

Repository: ai-dynamo/dynamo

Length of output: 742


🏁 Script executed:

#!/bin/bash
set -euo pipefail

rg -n -C3 '\bkv_transfer_params\b|\bdisaggregated_params\b|remote_block_ids|remote_port|remote_engine_id|tp_size|cache_salt' lib/vllm-sidecar
printf '\n--- proto excerpts ---\n'
sed -n '100,220p' lib/vllm-sidecar/proto/vllm_grpc.proto
printf '\n--- request.rs excerpts ---\n'
sed -n '120,180p' lib/vllm-sidecar/src/request.rs

Repository: ai-dynamo/dynamo

Length of output: 22063


Avoid lossy number conversion in KV-transfer Structs. json_to_prost_value rounds integers above 2^53, and number_to_json can turn large float-backed values back into i64::MAX. Reject non-exact numbers here, or carry 64-bit IDs as strings in the KV-transfer contract.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@lib/vllm-sidecar/src/wire.rs` around lines 108 - 114, Update
json_to_prost_value and its corresponding number_to_json conversion to preserve
KV-transfer numeric values without lossy f64 conversion. Reject numbers that
cannot be represented exactly rather than rounding or saturating them, or
consistently encode 64-bit identifiers as strings across the contract; ensure
large integers do not become incorrect values such as i64::MAX.

Comment on lines +5 to +19
set -euo pipefail
trap 'rm -f "${READY_FILE:-}"; kill 0' EXIT

if [[ $# -ne 4 ]]; then
echo "Usage: $0 MODEL ENGINE_CONFIG STARTUP_TIMEOUT READY_FILE" >&2
exit 2
fi

MODEL=$1
ENGINE_CONFIG=$2
STARTUP_TIMEOUT=$3
READY_FILE=$4

SCRIPT_DIR=$(dirname "$(readlink -f "$0")")
source "$SCRIPT_DIR/../../../examples/common/launch_utils.sh"

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

🩺 Stability & Availability | 🟠 Major | ⚡ Quick win

Use the shared process-group cleanup helper instead of raw kill 0. Raw EXIT traps do not preserve the failing status or explicitly reap children; the test launcher also arms its trap before validating arguments, so an invalid invocation can terminate its caller’s process group.

  • tests/serve/tito_parity/launch_dynamo_disagg.sh#L5-L19: validate arguments and source utilities first, then install a cleanup function that removes READY_FILE and calls dynamo_reap_and_exit with the captured status.
  • examples/backends/vllm/launch/sidecar_disagg.sh#L36-L36: replace the raw trap with trap dynamo_exit_trap EXIT.
  • examples/backends/vllm/launch/sidecar_disagg_multimodal_p_d.sh#L42-L42: replace the raw trap with trap dynamo_exit_trap EXIT.

Based on learnings, deferred trap registration is preferred when using dynamo_exit_trap; the shared helper preserves status and reaps the process group.

🧰 Tools
🪛 Shellcheck (0.11.0)

[info] 19-19: Not following: ./../../../examples/common/launch_utils.sh was not specified as input (see shellcheck -x).

(SC1091)

📍 Affects 3 files
  • tests/serve/tito_parity/launch_dynamo_disagg.sh#L5-L19 (this comment)
  • examples/backends/vllm/launch/sidecar_disagg.sh#L36-L36
  • examples/backends/vllm/launch/sidecar_disagg_multimodal_p_d.sh#L42-L42
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@tests/serve/tito_parity/launch_dynamo_disagg.sh` around lines 5 - 19, Replace
raw process-group cleanup with the shared helpers: in
tests/serve/tito_parity/launch_dynamo_disagg.sh lines 5-19, validate arguments
and source launch_utils.sh before registering cleanup, then capture the exit
status in a cleanup function that removes READY_FILE and invokes
dynamo_reap_and_exit; in examples/backends/vllm/launch/sidecar_disagg.sh line 36
and examples/backends/vllm/launch/sidecar_disagg_multimodal_p_d.sh line 42,
replace the raw EXIT traps with trap dynamo_exit_trap EXIT.

Source: Learnings

Comment thread tests/serve/tito_parity/run_parity.py Outdated
Comment on lines +29 to +35
REPO_ROOT = Path(__file__).resolve().parents[3]
GENERATE_PATH = "/inference/v1/generate"
UPSTREAM_PORT = 8000
DYNAMO_PORT = 8000
MODEL_MAX_LEN = 4096
MAX_NUM_SEQS = 4
KV_CACHE_BYTES = 4_294_967_296

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

🩺 Stability & Availability | 🟠 Major | 🏗️ Heavy lift

Allocate or inject every port to prevent cross-run collisions. The parity runners cannot execute concurrently and may fail when common local ports are already occupied.

  • tests/serve/tito_parity/run_parity.py#L29-L35: allocate upstream and Dynamo ports with the repository port utilities and pass them through commands and environment.
  • tests/serve/tito_parity/run_pd_three_way.py#L19-L20: allocate the prefill and decode ports instead of fixing them at 8100/8200.
  • tests/serve/tito_parity/launch_dynamo_disagg.sh#L36-L62: accept injected system, NIXL, event, and health ports from the Python harness.
  • examples/backends/vllm/launch/sidecar_disagg.sh#L68-L73: expose the NIXL side-channel and KV-event ports as environment variables.
  • examples/backends/vllm/launch/sidecar_disagg_multimodal_p_d.sh#L95-L100: expose the NIXL side-channel and KV-event ports as environment variables.

As per coding guidelines, tests must use allocate_port()/allocate_ports(), while launch-script ports must be injectable. Based on learnings, static script ports are portability hazards.

📍 Affects 5 files
  • tests/serve/tito_parity/run_parity.py#L29-L35 (this comment)
  • tests/serve/tito_parity/run_pd_three_way.py#L19-L20
  • tests/serve/tito_parity/launch_dynamo_disagg.sh#L36-L62
  • examples/backends/vllm/launch/sidecar_disagg.sh#L68-L73
  • examples/backends/vllm/launch/sidecar_disagg_multimodal_p_d.sh#L95-L100
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@tests/serve/tito_parity/run_parity.py` around lines 29 - 35, The parity
runners currently use fixed ports, preventing concurrent execution. In
tests/serve/tito_parity/run_parity.py (lines 29-35), use
allocate_port()/allocate_ports() for upstream and Dynamo ports and propagate
them through commands and environment; in
tests/serve/tito_parity/run_pd_three_way.py (lines 19-20), allocate prefill and
decode ports instead of 8100/8200. Update
tests/serve/tito_parity/launch_dynamo_disagg.sh (lines 36-62) to accept injected
system, NIXL, event, and health ports, and update
examples/backends/vllm/launch/sidecar_disagg.sh (lines 68-73) plus
examples/backends/vllm/launch/sidecar_disagg_multimodal_p_d.sh (lines 95-100) to
read NIXL side-channel and KV-event ports from environment variables.

Sources: Coding guidelines, Path instructions, Learnings

Comment on lines +193 to +228
with ThreadPoolExecutor(max_workers=1) as executor:
future = executor.submit(
parity.post_json,
parity.DYNAMO_PORT,
parity.GENERATE_PATH,
request,
timeout,
)
deadline = time.monotonic() + timeout
while not future.done() and time.monotonic() < deadline:
live_text, live = scrape_metrics(parity.DYNAMO_PORT)
active = metric_value_or_zero(
live, "dynamo_frontend_active_requests", {"model": model}
)
inflight = metric_value_or_zero(
live, "dynamo_frontend_inflight_requests", {"model": model}
)
queued = metric_value_or_zero(
live, "dynamo_frontend_queued_requests", {"model": model}
)
active_observed |= active > baseline_active
inflight_observed |= inflight > baseline_inflight
queue_observed |= queued > baseline_queue
if active_observed and inflight_observed and queue_observed:
break
time.sleep(0.005)
result = future.result(timeout=timeout)

(output_dir / "metrics-live.prom").write_text(live_text, encoding="utf-8")
if not active_observed:
raise AssertionError("active_requests never rose while Generate was running")
if not inflight_observed:
raise AssertionError("inflight_requests never rose while Generate was running")
if not queue_observed:
raise AssertionError("queued_requests never rose before the first output token")
return result

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

🩺 Stability & Availability | 🟠 Major | 🏗️ Heavy lift

Create deterministic queue pressure before asserting queued_requests.

A single request may leave the queue before the first metrics scrape, causing healthy deployments to fail nondeterministically. Saturate worker capacity and submit an additional request before observing the queue gauge.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@tests/serve/tito_parity/verify_generate_metrics.py` around lines 193 - 228,
Update the request orchestration around the ThreadPoolExecutor and metrics
polling to saturate worker capacity, then submit an additional Generate request
before asserting queued_requests. Ensure the extra request remains pending long
enough to observe dynamo_frontend_queued_requests above baseline, while
preserving the existing active and inflight observations and cleaning up all
submitted futures.

@biswapanda
biswapanda temporarily deployed to external_collaborator July 17, 2026 00:05 — with GitHub Actions Inactive
@dynamo-ops

Copy link
Copy Markdown
Contributor

/ok to test 517260c

@biswapanda
biswapanda temporarily deployed to external_collaborator July 17, 2026 07:14 — with GitHub Actions Inactive
@dynamo-ops

Copy link
Copy Markdown
Contributor

/ok to test 1a5f1e9

@biswapanda
biswapanda force-pushed the feat/dyn-pi-sidecar-v2 branch from 1a5f1e9 to 836fe81 Compare July 20, 2026 02:55
@biswapanda
biswapanda temporarily deployed to external_collaborator July 20, 2026 02:55 — with GitHub Actions Inactive
@dynamo-ops

Copy link
Copy Markdown
Contributor

/ok to test 836fe81

@dynamo-ops

Copy link
Copy Markdown
Contributor

/ok to test fc00f8e

@biswapanda
biswapanda temporarily deployed to external_collaborator July 23, 2026 10:47 — with GitHub Actions Inactive
@dynamo-ops

Copy link
Copy Markdown
Contributor

/ok to test b0b51b2

@biswapanda biswapanda changed the title dynamo-prime-integration-pr feat: add native vLLM gRPC sidecar backend Jul 23, 2026
@github-actions github-actions Bot added the feat label Jul 23, 2026
@biswapanda
biswapanda force-pushed the feat/dyn-pi-sidecar-v2 branch from b0b51b2 to 8b5acf8 Compare July 30, 2026 21:46
@biswapanda
biswapanda temporarily deployed to external_collaborator July 30, 2026 21:46 — with GitHub Actions Inactive
@dynamo-ops

Copy link
Copy Markdown
Contributor

/ok to test 8b5acf8

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

backend::vllm Relates to the vllm backend documentation Improvements or additions to documentation external-contribution Pull request is from an external contributor feat frontend `python -m dynamo.frontend` and `dynamo-run in=http|text|grpc` router Relates to routing, KV-aware routing, etc. size/XXL

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants