Skip to content

feat(connector): add PegaPdConnector for vLLM PD disaggregated serving#174

Open
wz1qqx wants to merge 2 commits into
novitalabs:masterfrom
wz1qqx:feat/pd-connector
Open

feat(connector): add PegaPdConnector for vLLM PD disaggregated serving#174
wz1qqx wants to merge 2 commits into
novitalabs:masterfrom
wz1qqx:feat/pd-connector

Conversation

@wz1qqx

@wz1qqx wz1qqx commented Mar 25, 2026

Copy link
Copy Markdown
Contributor

Summary

  • Add PegaPdConnector extending MultiConnector to combine PegaKVConnector + NixlConnector for Dynamo PD disaggregated inference
  • Register PegaPdConnector in KVConnectorFactory via the vLLM plugin entry point
  • dynamo integrate with pegaflow https://github.com/ai-dynamo/dynamo/pull/7624/changes
  • docker images:image.paigpu.com/library/dynamo-vllm:kimi-pd-pegaflow-v1
  • k8s yaml:

Design

PegaPdConnector delegates:

  • Prefix matching → PegaKVConnector only (knows L2 cache state)
  • Block allocation → PegaKVConnector gets real blocks, NixlConnector gets empty blocks
  • Handshake → NixlConnector only (PegaFlow has no P/D handshake)
  • Save/load → both connectors via inherited MultiConnector behavior

Usage in --kv-transfer-config:

{
  "kv_connector": "PegaPdConnector",
  "kv_connector_module_path": "pegaflow.connector.pd_connector",
  "kv_connector_extra_config": {
    "connectors": [
      {"kv_connector": "PegaKVConnector", "kv_role": "kv_both"},
      {"kv_connector": "NixlConnector", "kv_role": "kv_both"}
    ]
  }
}

Test plan

  • Tested with Kimi K2.5 (MLA, TP8) on 2P+DP16 RoCE cluster with Dynamo
  • PegaFlow save: 190K+ RPC calls, all success, avg 0.4ms/save
  • PegaFlow query_prefetch: 1.1K+ calls, functional
  • PegaFlow load: 80 calls via L2 cache onboard
  • mtb multi-turn benchmark: 90% cache hit, 1.16 req/s throughput at 0.4 arrival rate

🤖 Generated with Claude Code

Add PegaPdConnector that extends MultiConnector to combine PegaKVConnector
(L2 CPU/SSD cache) with NixlConnector (RDMA KV transfer) for prefill-decode
disaggregated inference with Dynamo.

Delegation logic:
- Prefix matching: PegaKVConnector only (knows the L2 cache state)
- Block allocation: PegaKVConnector gets real blocks, NixlConnector empty
- Handshake: NixlConnector only (PegaFlow has no P/D handshake)
- Save/load: both connectors participate via MultiConnector

Also registers PegaPdConnector in KVConnectorFactory via the vLLM plugin
entry point, so it can be used via --kv-transfer-config:
  {"kv_connector": "PegaPdConnector", ...}

Tested with Kimi K2.5 (MLA, TP8) on 2P2D RoCE cluster:
- PegaFlow save/load/query_prefetch all functional
- 90% cache hit rate with approximate KV-aware routing (temperature=0)
- TTFT avg 2.16s at 0.2 req/s arrival rate
Copilot AI review requested due to automatic review settings March 25, 2026 07:29

Copilot AI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Pull request overview

Adds a new vLLM KV connector composition (PegaPdConnector) to support Dynamo prefill/decode disaggregated serving by combining PegaFlow’s L2 KV cache management with NIXL-based P→D KV transfer, and registers it via the existing vLLM general plugin hook.

Changes:

  • Register PegaPdConnector in KVConnectorFactory through the vllm.general_plugins entry point.
  • Introduce PegaPdConnector (a MultiConnector) that delegates prefix matching/allocation to PegaKVConnector and handshake to NixlConnector.

Reviewed changes

Copilot reviewed 2 out of 2 changed files in this pull request and generated 3 comments.

File Description
python/pegaflow/vllm_plugin.py Extends plugin registration to include PegaPdConnector lazily in KVConnectorFactory.
python/pegaflow/connector/pd_connector.py Adds PegaPdConnector implementation combining PegaFlow + NIXL behaviors for PD disaggregated serving.

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Skips MultiConnector.bind_connector_metadata() to avoid double-binding
children. Calls KVConnectorBase_V1 directly for has_connector_metadata().
"""
assert isinstance(connector_metadata, PegaPdConnectorMetadata)

Copilot AI Mar 25, 2026

Copy link

Choose a reason for hiding this comment

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

bind_connector_metadata uses an assert for runtime type checking. Assertions can be stripped with python -O, which would allow an unexpected metadata type through and likely fail later in less obvious ways. Prefer an explicit isinstance check that raises TypeError (or ValueError) when the metadata is not PegaPdConnectorMetadata.

Suggested change
assert isinstance(connector_metadata, PegaPdConnectorMetadata)
if not isinstance(connector_metadata, PegaPdConnectorMetadata):
raise TypeError(
"Expected PegaPdConnectorMetadata for connector_metadata, "
f"got {type(connector_metadata).__name__}"
)

Copilot uses AI. Check for mistakes.
assert isinstance(connector_metadata, PegaPdConnectorMetadata)
KVConnectorBase_V1.bind_connector_metadata(self, connector_metadata)
if connector_metadata.extra_async_saves:
self._extra_async_saves.update(connector_metadata.extra_async_saves)

Copilot AI Mar 25, 2026

Copy link

Choose a reason for hiding this comment

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

for c, cm in zip(self._connectors, connector_metadata.metadata) will silently ignore extra connectors or extra metadata entries if their lengths differ. That can lead to only a subset of child connectors being bound, causing hard-to-debug behavior. Add an explicit length check (and raise) before iterating so misconfiguration is detected immediately.

Suggested change
self._extra_async_saves.update(connector_metadata.extra_async_saves)
self._extra_async_saves.update(connector_metadata.extra_async_saves)
# Ensure we do not silently drop connectors or metadata entries.
num_connectors = len(self._connectors)
num_metadata = len(connector_metadata.metadata)
if num_connectors != num_metadata:
raise ValueError(
"Mismatch between number of connectors and metadata entries: "
f"{num_connectors} connector(s) vs {num_metadata} metadata entr"
"ies in PegaPdConnector.bind_connector_metadata"
)

Copilot uses AI. Check for mistakes.
Comment on lines +86 to +100
if len(self._connectors) != 2:
raise ValueError(
"PegaPdConnector requires exactly two connectors "
f"(got {len(self._connectors)})"
)

if not isinstance(self._connectors[0], PegaKVConnector):
raise TypeError(
"Expected first connector to be PegaKVConnector, "
f"got {type(self._connectors[0]).__name__}"
)
if not isinstance(self._connectors[1], NixlConnector):
raise TypeError(
"Expected second connector to be NixlConnector, "
f"got {type(self._connectors[1]).__name__}"

Copilot AI Mar 25, 2026

Copy link

Choose a reason for hiding this comment

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

This implementation relies on MultiConnector internals (self._connectors, self._extra_async_saves). Because these are private attributes of an external dependency, they may change across vLLM versions and break this connector unexpectedly. If vLLM exposes public accessors/hooks for these, prefer those; otherwise consider adding a short compatibility note (or version constraint) and defensive checks (e.g., hasattr) to fail fast with a clear error.

Suggested change
if len(self._connectors) != 2:
raise ValueError(
"PegaPdConnector requires exactly two connectors "
f"(got {len(self._connectors)})"
)
if not isinstance(self._connectors[0], PegaKVConnector):
raise TypeError(
"Expected first connector to be PegaKVConnector, "
f"got {type(self._connectors[0]).__name__}"
)
if not isinstance(self._connectors[1], NixlConnector):
raise TypeError(
"Expected second connector to be NixlConnector, "
f"got {type(self._connectors[1]).__name__}"
# Defensive check against changes in vLLM's MultiConnector internals.
if not hasattr(self, "_connectors"):
raise RuntimeError(
"PegaPdConnector is incompatible with this vLLM version: "
"MultiConnector does not expose a '_connectors' attribute."
)
connectors = self._connectors
if len(connectors) != 2:
raise ValueError(
"PegaPdConnector requires exactly two connectors "
f"(got {len(connectors)})"
)
if not isinstance(connectors[0], PegaKVConnector):
raise TypeError(
"Expected first connector to be PegaKVConnector, "
f"got {type(connectors[0]).__name__}"
)
if not isinstance(connectors[1], NixlConnector):
raise TypeError(
"Expected second connector to be NixlConnector, "
f"got {type(connectors[1]).__name__}"

Copilot uses AI. Check for mistakes.
- Remove quoted type annotations (UP037): use bare types with
  `from __future__ import annotations`
- Add `strict=True` to `zip()` call (B905)
@wz1qqx wz1qqx changed the title feat(connector): add PegaPdConnector for Dynamo PD disaggregated serving feat(connector): add PegaPdConnector for vLLM PD disaggregated serving Mar 25, 2026
@wz1qqx wz1qqx requested a review from Copilot March 25, 2026 07:58

Copilot AI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Pull request overview

Copilot reviewed 2 out of 2 changed files in this pull request and generated no new comments.


💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

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

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants