Skip to content

Commit 00c46ef

Browse files
committed
feat(router): publish GMS placement from workers
Signed-off-by: Maksim Khadkevich <mkhadkevich@nvidia.com>
1 parent 78ea024 commit 00c46ef

13 files changed

Lines changed: 149 additions & 35 deletions

File tree

components/src/dynamo/router/__main__.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -203,6 +203,7 @@ async def worker(runtime: DistributedRuntime):
203203
f"router_track_output_blocks={config.router_track_output_blocks}, "
204204
f"router_assume_kv_reuse={config.router_assume_kv_reuse}, "
205205
f"router_track_prefill_tokens={config.router_track_prefill_tokens}, "
206+
f"router_gms_decode_transfer={config.router_gms_decode_transfer}, "
206207
f"router_ttl_secs={config.router_ttl_secs}"
207208
)
208209

components/src/dynamo/sglang/register.py

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -291,6 +291,10 @@ async def _get_runtime_config(
291291
dynamo_args.enable_local_indexer and not is_decode_worker
292292
)
293293

294+
if os.environ.get("GMS_SGLANG_DAEMON_SOCKET"):
295+
runtime_config.set_gms_placement_enabled()
296+
logging.info("Publishing GMS placement capability to discovery")
297+
294298
start_dp_rank, end_dp_rank = model_card_dp_rank_bounds(server_args)
295299
registered_dp_size = end_dp_rank - start_dp_rank
296300
runtime_config.data_parallel_start_rank = start_dp_rank

components/src/dynamo/sglang/request_handlers/llm/decode_handler.py

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,10 @@
1414
from dynamo.common.constants import DisaggregationMode
1515
from dynamo.common.multimodal.image_loader import ImageLoader
1616
from dynamo.common.utils.engine_response import normalize_finish_reason
17+
from dynamo.gms_router_policy import (
18+
maybe_fetch_gms_placement,
19+
resolve_env_gms_daemon_socket,
20+
)
1721
from dynamo.sglang._compat import filter_supported_async_generate_kwargs
1822
from dynamo.sglang.args import Config
1923
from dynamo.sglang.publisher import DynamoSglangPublisher
@@ -444,6 +448,13 @@ async def generate(
444448
if lora_path:
445449
logging.debug(f"Request {context.id()} will use LoRA adapter: {lora_path}")
446450

451+
await maybe_fetch_gms_placement(
452+
request,
453+
resolve_env_gms_daemon_socket("GMS_SGLANG_DAEMON_SOCKET"),
454+
logger=logging.getLogger(__name__),
455+
request_id=context.id(),
456+
)
457+
447458
if self.serving_mode == DisaggregationMode.DECODE:
448459
# Check if bootstrap_info is pre-computed in the request (from frontend)
449460
bootstrap_info = request.get("bootstrap_info")

components/src/dynamo/trtllm/request_handlers/handler_base.py

Lines changed: 26 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -36,6 +36,10 @@
3636
from dynamo._core import Client, Context
3737
from dynamo.common.gms_failover import release_attached_gms_failover_lock
3838
from dynamo.common.utils.structural_tag import serialize_structural_tag
39+
from dynamo.gms_router_policy import (
40+
maybe_fetch_gms_placement,
41+
resolve_env_gms_daemon_socket,
42+
)
3943
from dynamo.health_check import HEALTH_CHECK_KEY
4044
from dynamo.llm.exceptions import EngineShutdown
4145
from dynamo.logits_processing.examples import HelloWorldLogitsProcessor
@@ -106,12 +110,20 @@ def _collective_rpc(self, method: str, rpc_tags: list[str]) -> None:
106110
"""Call TRT-LLM collective_rpc for KV cache sleep/wake."""
107111
rpc = getattr(self._engine.llm, "_collective_rpc", None)
108112
if rpc is None:
109-
logger.warning(
110-
"TRT-LLM does not expose _collective_rpc; skipping %s", method
113+
raise RuntimeError(
114+
f"TRT-LLM does not expose _collective_rpc; cannot {method} KV cache"
111115
)
112-
return
113116

114-
rpc(method, args=(rpc_tags,), kwargs={}, non_block=False)
117+
try:
118+
rpc(method, args=(rpc_tags,), kwargs={}, non_block=False)
119+
except NotImplementedError as exc:
120+
message = str(exc)
121+
if "MPI collective_rpc only supports model_world_size == 1" in message:
122+
raise RuntimeError(
123+
"TRT-LLM MPI executor does not support "
124+
f"collective_rpc({method}) for multi-rank KV cache control"
125+
) from exc
126+
raise
115127

116128
@staticmethod
117129
def _release_gms_weights() -> None:
@@ -1004,6 +1016,16 @@ async def _generate_locally_impl(
10041016
# Normalize OpenAI format to TRT-LLM internal format
10051017
self._normalize_request_format(request)
10061018

1019+
await maybe_fetch_gms_placement(
1020+
request,
1021+
resolve_env_gms_daemon_socket(
1022+
"GMS_TRTLLM_DAEMON_SOCKET",
1023+
default_when_cross_node="/tmp/gms.sock",
1024+
),
1025+
logger=logging.getLogger(__name__),
1026+
request_id=context.id(),
1027+
)
1028+
10071029
# Setup disaggregated params based on PREFILL/DECODE mode
10081030
(
10091031
disaggregated_params,

components/src/dynamo/trtllm/tests/test_trtllm_sleep_wake_handlers.py

Lines changed: 31 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -20,7 +20,10 @@
2020
allow_module_level=True,
2121
)
2222

23-
from dynamo.trtllm.request_handlers.handler_base import HandlerBase
23+
from dynamo.trtllm.request_handlers.handler_base import (
24+
HandlerBase,
25+
TRTLLMEngineQuiesceController,
26+
)
2427

2528
pytestmark = [
2629
pytest.mark.unit,
@@ -101,6 +104,33 @@ async def test_mark_request_finished_is_idempotent():
101104
assert handler._inflight_requests == 0
102105

103106

107+
@pytest.mark.asyncio
108+
async def test_trtllm_quiesce_fails_when_collective_rpc_missing():
109+
controller = TRTLLMEngineQuiesceController(SimpleNamespace(llm=SimpleNamespace()))
110+
111+
with pytest.raises(RuntimeError, match="_collective_rpc"):
112+
await controller.quiesce(["kv_cache"])
113+
114+
assert controller.is_quiesced is False
115+
116+
117+
@pytest.mark.asyncio
118+
async def test_trtllm_quiesce_fails_closed_for_unsupported_multi_rank_rpc():
119+
def collective_rpc(*args, **kwargs):
120+
raise NotImplementedError(
121+
"MPI collective_rpc only supports model_world_size == 1"
122+
)
123+
124+
controller = TRTLLMEngineQuiesceController(
125+
SimpleNamespace(llm=SimpleNamespace(_collective_rpc=collective_rpc))
126+
)
127+
128+
with pytest.raises(RuntimeError, match="multi-rank KV cache control"):
129+
await controller.quiesce(["kv_cache"])
130+
131+
assert controller.is_quiesced is False
132+
133+
104134
# ---------------------------------------------------------------------------
105135
# release_memory_occupation
106136
# ---------------------------------------------------------------------------

components/src/dynamo/trtllm/workers/llm_worker.py

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -49,6 +49,7 @@
4949
)
5050
from dynamo.common.utils.runtime import parse_endpoint
5151
from dynamo.common.utils.topology import apply_topology_config
52+
from dynamo.gms_router_policy import resolve_env_gms_daemon_socket
5253
from dynamo.llm import (
5354
KvEventPublisher,
5455
MediaDecoder,
@@ -634,6 +635,13 @@ async def init_llm_worker(
634635
config.enable_local_indexer
635636
and config.disaggregation_mode != DisaggregationMode.DECODE
636637
)
638+
gms_daemon_socket = resolve_env_gms_daemon_socket(
639+
"GMS_TRTLLM_DAEMON_SOCKET",
640+
default_when_cross_node="/tmp/gms.sock",
641+
)
642+
if gms_daemon_socket:
643+
runtime_config.set_gms_placement_enabled()
644+
logging.info("Publishing GMS placement capability to discovery")
637645
# Set data_parallel_size for attention DP mode
638646
# This enables the router's scheduler to correctly iterate over all dp_ranks
639647
# Need to name ADP as `data_parallel_size` for parity with other frameworks

components/src/dynamo/vllm/handlers.py

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -57,6 +57,10 @@
5757
from dynamo.common.utils.input_params import InputParamManager
5858
from dynamo.common.utils.structural_tag import serialize_structural_tag
5959
from dynamo.common.utils.time_section import time_and_log_code_section
60+
from dynamo.gms_router_policy import (
61+
maybe_fetch_gms_placement,
62+
resolve_vllm_gms_daemon_socket,
63+
)
6064
from dynamo.llm import (
6165
KvEventPublisher,
6266
ModelInput,
@@ -2303,6 +2307,13 @@ async def _generate_token_mode(self, request, context, request_id):
23032307

23042308
mm_processor_kwargs = self._get_mm_processor_kwargs(request)
23052309

2310+
await maybe_fetch_gms_placement(
2311+
request,
2312+
resolve_vllm_gms_daemon_socket(self.engine_client.vllm_config),
2313+
logger=logger,
2314+
request_id=request_id,
2315+
)
2316+
23062317
multi_modal_data: Dict[str, Any] | None = None
23072318
pre_rendered: Dict[str, Any] | None = None
23082319
if is_decode_only:

components/src/dynamo/vllm/main.py

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -91,6 +91,7 @@ def _maybe_isolate_jit_cache_dirs_by_container() -> dict[str, tuple[str | None,
9191
)
9292
from dynamo.common.utils.runtime import create_runtime
9393
from dynamo.common.utils.topology import apply_topology_config
94+
from dynamo.gms_router_policy import resolve_vllm_gms_daemon_socket
9495
from dynamo.llm import (
9596
KvEventPublisher,
9697
MediaDecoder,
@@ -947,6 +948,10 @@ async def register_vllm_model(
947948
if stream_interval is not None:
948949
runtime_config.set_engine_specific("stream_interval", str(stream_interval))
949950

951+
if resolve_vllm_gms_daemon_socket(vllm_config):
952+
runtime_config.set_gms_placement_enabled()
953+
logging.info("Publishing GMS placement capability to discovery")
954+
950955
runtime_config.data_parallel_start_rank = dp_range[0]
951956
runtime_config.data_parallel_size = dp_range[1]
952957

lib/llm/src/kv_router/publisher/batching.rs

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -96,6 +96,7 @@ impl BatchingState {
9696
data: KvCacheEventData::Removed(filtered),
9797
dp_rank,
9898
},
99+
None,
99100
)
100101
.await;
101102
emitted = true;
@@ -112,6 +113,7 @@ impl BatchingState {
112113
data: KvCacheEventData::Stored(data),
113114
dp_rank,
114115
},
116+
None,
115117
)
116118
.await;
117119
emitted = true;

lib/llm/src/kv_router/publisher/event_processor.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -153,6 +153,7 @@ pub(super) async fn run_event_processor_loop<P: RouterEventSink + Send + Sync +
153153
data: KvCacheEventData::Cleared,
154154
dp_rank: event.dp_rank,
155155
},
156+
None,
156157
)
157158
.await;
158159
batching_state.next_publish_id += 1;

0 commit comments

Comments
 (0)