@@ -460,7 +460,7 @@ index 641709f..121f060 100644
460460 def get_mooncake_worker_env(self, infra_node_ip: str, local_hostname: str) -> dict[str, str]:
461461 """Get mooncake env vars to inject on a specific worker.
462462diff --git a/src/srtctl/backends/vllm.py b/src/srtctl/backends/vllm.py
463- index 048627a..5d47ae1 100644
463+ index 048627a..79168fd 100644
464464--- a/src/srtctl/backends/vllm.py
465465+++ b/src/srtctl/backends/vllm.py
466466@@ -35,6 +35,7 @@ from srtctl.ports import (
@@ -480,7 +480,21 @@ index 048627a..5d47ae1 100644
480480 # Can be overridden per role by setting "connector" in roles.<role>.args; connector_for_mode resolves it.
481481 # "moriio" (ROCm MoRI-IO) registers workers with the vLLM Router and needs frontend.type: vllm-router.
482482 # dynamo 1.0.0+: translated to --kv-transfer-config (--connector was removed).
483- @@ -1756,6 +1757,8 @@ class KVConnector:
483+ @@ -1278,9 +1279,10 @@ class VLLMProtocol:
484+ overridden = pop_vllm_orchestration_flags(config)
485+ config.setdefault("served-model-name", served_model_name)
486+
487+ - # A prefill/decode worker gets its KV connector; an aggregate worker has none.
488+ - config.pop("connector", None)
489+ - if mode in {"prefill", "decode"}:
490+ + # A prefill/decode worker gets its KV connector. An aggregate worker gets
491+ + # only the one its role names (e.g. lmcache-mp offload), not the P/D default.
492+ + role_connector = config.pop("connector", None)
493+ + if mode in {"prefill", "decode"} or role_connector is not None:
494+ kv_transfer_config = self.kv_transfer_config(mode, process, runtime)
495+ if kv_transfer_config is not None:
496+ config.setdefault("kv-transfer-config", kv_transfer_config)
497+ @@ -1756,6 +1758,8 @@ class KVConnector:
484498 kv_role: str | None = "kv_both"
485499 module_path: str | None = None
486500 discovery: bool = False
@@ -489,7 +503,7 @@ index 048627a..5d47ae1 100644
489503
490504 def transfer_config(self, mode: WorkerMode) -> dict[str, Any]:
491505 """The ``--kv-transfer-config`` payload for a worker mode, before any topology-derived extras."""
492- @@ -1763,6 +1766 ,8 @@ class KVConnector:
506+ @@ -1763,6 +1767 ,8 @@ class KVConnector:
493507 if self.module_path is not None:
494508 payload["kv_connector_module_path"] = self.module_path
495509 payload["kv_role"] = self.kv_role or ("kv_producer" if mode == "prefill" else "kv_consumer")
@@ -498,7 +512,7 @@ index 048627a..5d47ae1 100644
498512 return payload
499513
500514
501- @@ -1770,6 +1775 ,12 @@ class KVConnector:
515+ @@ -1770,6 +1776 ,12 @@ class KVConnector:
502516 _CONNECTOR_MAP: dict[str, KVConnector] = {
503517 "nixl": KVConnector("NixlConnector"),
504518 "lmcache": KVConnector("LMCacheConnectorV1"),
@@ -785,7 +799,7 @@ index 0000000..52f3f8d
785799+
786800+ assert backend.get_process_environment(_process()) == {}
787801diff --git a/tests/test_vllm_connectors.py b/tests/test_vllm_connectors.py
788- index 691990b..4bb0974 100644
802+ index 691990b..1dfe69e 100644
789803--- a/tests/test_vllm_connectors.py
790804+++ b/tests/test_vllm_connectors.py
791805@@ -23,6 +23,14 @@ def test_table_presets_serialize_exactly_as_before():
@@ -803,3 +817,43 @@ index 691990b..4bb0974 100644
803817 assert VLLMProtocol(connector="kvbm").kv_transfer_config("decode") == json.dumps(
804818 {
805819 "kv_connector": "DynamoConnector",
820+ @@ -67,3 +75,39 @@ def test_a_mode_dependent_role_follows_the_worker_mode():
821+ assert row.transfer_config("prefill")["kv_role"] == "kv_producer"
822+ assert row.transfer_config("decode")["kv_role"] == "kv_consumer"
823+ assert KVConnector("SomeConnector").transfer_config("prefill")["kv_role"] == "kv_both"
824+ +
825+ +
826+ + @pytest.mark.parametrize(
827+ + ("aggregated", "expected"),
828+ + [
829+ + ({}, None),
830+ + ({"connector": "none"}, None),
831+ + ({"connector": "lmcache-mp"}, _CONNECTOR_MAP["lmcache-mp"].transfer_config("agg")),
832+ + ],
833+ + )
834+ + def test_direct_aggregate_worker_runs_only_its_role_connector(aggregated, expected):
835+ + """A direct `vllm serve` aggregate worker skips the P/D default connector but keeps the one its role names."""
836+ + from pathlib import Path
837+ + from unittest.mock import MagicMock
838+ +
839+ + from srtctl.core.topology import Process
840+ +
841+ + backend = VLLMProtocol(connector="nixl", vllm_config=VLLMServerConfig(aggregated=aggregated))
842+ + process = Process(
843+ + node="node0",
844+ + gpu_indices=frozenset(range(8)),
845+ + sys_port=8081,
846+ + http_port=0,
847+ + endpoint_mode="agg",
848+ + endpoint_index=0,
849+ + node_rank=0,
850+ + )
851+ + runtime = MagicMock(model_path=Path("/model"), is_hf_model=False, frontend_port=9000)
852+ +
853+ + cmd = backend.build_worker_command(
854+ + process=process, endpoint_processes=[process], runtime=runtime, frontend_type="vllm"
855+ + )
856+ +
857+ + assert "--connector" not in cmd
858+ + kv_config = json.loads(cmd[cmd.index("--kv-transfer-config") + 1]) if "--kv-transfer-config" in cmd else None
859+ + assert kv_config == expected
0 commit comments