A high-throughput and memory-efficient inference and serving engine for LLMs
Mirrored from https://github.com/tduncan-lum/Nvidia-Optimization-Stack---VLLM.git
Synced Nov 15, 07:15 UTC
Allow AsyncLLMEngine.generate to target a specific DP rank (#19102)
Signed-off-by: Jon Swenson <jmswen@gmail.com>
jmswen committed
Jun 4, 2025 at 08:26 UTC
c8dcc159214a20650451dcd64b226f56671763f1
10 files changed
+97
-5
examples/online_serving/multi_instance_data_parallel.py
new
+58
@@ -0,0 +1,58 @@
1
+# SPDX-License-Identifier: Apache-2.0
2
+import asyncio
3
+from typing import Optional
4
+
5
+from vllm.engine.arg_utils import AsyncEngineArgs
6
+from vllm.engine.async_llm_engine import AsyncLLMEngine
7
+from vllm.outputs import RequestOutput
8
+from vllm.sampling_params import SamplingParams
9
+
10
+"""
11
+To run this example, run the following commands simultaneously with
12
+different CUDA_VISIBLE_DEVICES:
13
+ python examples/online_serving/multi_instance_data_parallel.py
14
+
15
+ vllm serve ibm-research/PowerMoE-3b -dp 2 -dpr 1 \
16
+ --data-parallel-address 127.0.0.1 --data-parallel-rpc-port 62300 \
17
+ --data-parallel-size-local 1 --enforce-eager --headless
18
+
19
+Once both instances have completed the handshake, this example will
20
+send a request to the instance with DP rank 1.
21
+"""
22
+
23
+
24
+async def main():
25
+ engine_args = AsyncEngineArgs(
26
+ model="ibm-research/PowerMoE-3b",
27
+ data_parallel_size=2,
28
+ dtype="auto",
29
+ max_model_len=2048,
30
+ data_parallel_address="127.0.0.1",
31
+ data_parallel_rpc_port=62300,
32
+ data_parallel_size_local=1,
33
+ enforce_eager=True,
34
+ )
35
+
36
+ engine_client = AsyncLLMEngine.from_engine_args(engine_args)
37
+
38
+ sampling_params = SamplingParams(
39
+ temperature=0.7,
40
+ top_p=0.9,
41
+ max_tokens=100,
42
+ )
43
+
44
+ prompt = "Who won the 2004 World Series?"
45
+ final_output: Optional[RequestOutput] = None
46
+ async for output in engine_client.generate(
47
+ prompt=prompt,
48
+ sampling_params=sampling_params,
49
+ request_id="abcdef",
50
+ data_parallel_rank=1,
51
+ ):
52
+ final_output = output
53
+ if final_output:
54
+ print(final_output.outputs[0].text)
55
+
56
+
57
+if __name__ == "__main__":
58
+ asyncio.run(main())
tests/tokenization/test_detokenize.py
+2
-1
@@ -70,7 +70,8 @@ def _run_incremental_decode(tokenizer,
70
None,
71
0.0,
72
None,
73
- cache_salt=None)
73
+ cache_salt=None,
74
+ data_parallel_rank=None)
75
76
if fast is None:
77
detokenizer = IncrementalDetokenizer.from_new_request(
tests/v1/engine/test_engine_core.py
+1
@@ -42,6 +42,7 @@ def make_request() -> EngineCoreRequest:
42
arrival_time=time.time(),
43
lora_request=None,
44
cache_salt=None,
45
+ data_parallel_rank=None,
46
)
47
48
tests/v1/engine/test_engine_core_client.py
+1
@@ -56,6 +56,7 @@ def make_request(
56
arrival_time=time.time(),
57
lora_request=None,
58
cache_salt=None,
59
+ data_parallel_rank=None,
60
)
61
62
tests/v1/engine/test_output_processor.py
+5
@@ -59,6 +59,7 @@ def test_incremental_detokenization(request_output_kind: RequestOutputKind,
59
eos_token_id=None,
60
lora_request=None,
61
cache_salt=None,
62
+ data_parallel_rank=None,
63
sampling_params=SamplingParams(
64
skip_special_tokens=False,
65
spaces_between_special_tokens=False,
@@ -406,6 +407,7 @@ def test_logprobs_processor(request_output_kind: RequestOutputKind,
407
eos_token_id=None,
408
lora_request=None,
409
cache_salt=None,
410
+ data_parallel_rank=None,
411
sampling_params=SamplingParams(
412
skip_special_tokens=False,
413
spaces_between_special_tokens=False,
@@ -569,6 +571,7 @@ def test_stop_token(include_stop_str_in_output: bool,
571
eos_token_id=eos_token_id,
572
lora_request=None,
573
cache_salt=None,
574
+ data_parallel_rank=None,
575
sampling_params=SamplingParams(
576
skip_special_tokens=False,
577
spaces_between_special_tokens=False,
@@ -666,6 +669,7 @@ def test_stop_string(include_stop_str_in_output: bool,
669
eos_token_id=None,
670
lora_request=None,
671
cache_salt=None,
672
+ data_parallel_rank=None,
673
sampling_params=SamplingParams(
674
skip_special_tokens=False,
675
spaces_between_special_tokens=False,
@@ -780,6 +784,7 @@ def test_iteration_stats(dummy_test_vectors):
784
eos_token_id=None,
785
lora_request=None,
786
cache_salt=None,
787
+ data_parallel_rank=None,
788
sampling_params=SamplingParams(),
789
) for idx, prompt_tokens in enumerate(dummy_test_vectors.prompt_tokens)
790
]
vllm/engine/async_llm_engine.py
+11
-1
@@ -442,6 +442,7 @@ class _AsyncLLMEngine(LLMEngine):
442
trace_headers: Optional[Mapping[str, str]] = None,
443
prompt_adapter_request: Optional[PromptAdapterRequest] = None,
444
priority: int = 0,
445
+ data_parallel_rank: Optional[int] = None,
446
) -> None:
447
...
448
@@ -456,6 +457,7 @@ class _AsyncLLMEngine(LLMEngine):
457
trace_headers: Optional[Mapping[str, str]] = None,
458
prompt_adapter_request: Optional[PromptAdapterRequest] = None,
459
priority: int = 0,
460
+ data_parallel_rank: Optional[int] = None,
461
) -> None:
462
...
463
@@ -473,6 +475,7 @@ class _AsyncLLMEngine(LLMEngine):
475
trace_headers: Optional[Mapping[str, str]] = None,
476
prompt_adapter_request: Optional[PromptAdapterRequest] = None,
477
priority: int = 0,
478
+ data_parallel_rank: Optional[int] = None,
479
*,
480
inputs: Optional[PromptType] = None, # DEPRECATED
481
) -> None:
@@ -902,6 +905,7 @@ class AsyncLLMEngine(EngineClient):
905
trace_headers: Optional[Mapping[str, str]] = None,
906
prompt_adapter_request: Optional[PromptAdapterRequest] = None,
907
priority: int = 0,
908
+ data_parallel_rank: Optional[int] = None,
909
) -> Coroutine[None, None, AsyncGenerator[Union[
910
RequestOutput, PoolingRequestOutput], None]]:
911
...
@@ -917,6 +921,7 @@ class AsyncLLMEngine(EngineClient):
921
trace_headers: Optional[Mapping[str, str]] = None,
922
prompt_adapter_request: Optional[PromptAdapterRequest] = None,
923
priority: int = 0,
924
+ data_parallel_rank: Optional[int] = None,
925
) -> Coroutine[None, None, AsyncGenerator[Union[
926
RequestOutput, PoolingRequestOutput], None]]:
927
...
@@ -935,6 +940,7 @@ class AsyncLLMEngine(EngineClient):
940
trace_headers: Optional[Mapping[str, str]] = None,
941
prompt_adapter_request: Optional[PromptAdapterRequest] = None,
942
priority: int = 0,
943
+ data_parallel_rank: Optional[int] = None,
944
*,
945
inputs: Optional[PromptType] = None, # DEPRECATED
946
) -> AsyncGenerator[Union[RequestOutput, PoolingRequestOutput], None]:
@@ -967,6 +973,7 @@ class AsyncLLMEngine(EngineClient):
973
trace_headers=trace_headers,
974
prompt_adapter_request=prompt_adapter_request,
975
priority=priority,
976
+ data_parallel_rank=data_parallel_rank,
977
)
978
979
return stream.generator()
@@ -980,6 +987,7 @@ class AsyncLLMEngine(EngineClient):
987
trace_headers: Optional[Mapping[str, str]] = None,
988
prompt_adapter_request: Optional[PromptAdapterRequest] = None,
989
priority: int = 0,
990
+ data_parallel_rank: Optional[int] = None,
991
) -> AsyncGenerator[RequestOutput, None]:
992
"""Generate outputs for a request.
993
@@ -999,7 +1007,8 @@ class AsyncLLMEngine(EngineClient):
1007
for generation, if any.
1008
priority: The priority of the request.
1009
Only applicable with priority scheduling.
1002
-
1010
+ data_parallel_rank: The (global) data parallel rank that must
1011
+ handle this request. Only applicable if DP is enabled.
1012
Yields:
1013
The output `RequestOutput` objects from the LLMEngine
1014
for the request.
@@ -1057,6 +1066,7 @@ class AsyncLLMEngine(EngineClient):
1066
trace_headers=trace_headers,
1067
prompt_adapter_request=prompt_adapter_request,
1068
priority=priority,
1069
+ data_parallel_rank=data_parallel_rank,
1070
):
1071
yield LLMEngine.validate_output(output, RequestOutput)
1072
except asyncio.CancelledError:
vllm/v1/engine/__init__.py
+1
@@ -55,6 +55,7 @@ class EngineCoreRequest(
55
arrival_time: float
56
lora_request: Optional[LoRARequest]
57
cache_salt: Optional[str]
58
+ data_parallel_rank: Optional[int]
59
60
# Index of the client, used to ensure outputs are sent back to the same
61
# client for this request when scaling out the front-end.
vllm/v1/engine/async_llm.py
+4
-1
@@ -229,6 +229,7 @@ class AsyncLLM(EngineClient):
229
trace_headers: Optional[Mapping[str, str]] = None,
230
prompt_adapter_request: Optional[PromptAdapterRequest] = None,
231
priority: int = 0,
232
+ data_parallel_rank: Optional[int] = None,
233
) -> RequestOutputCollector:
234
"""Add new request to the AsyncLLM."""
235
@@ -245,7 +246,7 @@ class AsyncLLM(EngineClient):
246
prompt_str, request = self.processor.process_inputs(
247
request_id, prompt, params, arrival_time, lora_request,
248
tokenization_kwargs, trace_headers, prompt_adapter_request,
248
- priority)
249
+ priority, data_parallel_rank)
250
251
if params.n == 1:
252
await self._add_request(request, prompt_str, None, 0, queue)
@@ -291,6 +292,7 @@ class AsyncLLM(EngineClient):
292
trace_headers: Optional[Mapping[str, str]] = None,
293
prompt_adapter_request: Optional[PromptAdapterRequest] = None,
294
priority: int = 0,
295
+ data_parallel_rank: Optional[int] = None,
296
) -> AsyncGenerator[RequestOutput, None]:
297
"""
298
Main function called by the API server to kick off a request
@@ -321,6 +323,7 @@ class AsyncLLM(EngineClient):
323
trace_headers=trace_headers,
324
prompt_adapter_request=prompt_adapter_request,
325
priority=priority,
326
+ data_parallel_rank=data_parallel_rank,
327
)
328
329
# The output_handler task pushes items into the queue.
vllm/v1/engine/core_client.py
+12
-2
@@ -982,7 +982,16 @@ class DPAsyncMPClient(AsyncMPClient):
982
resources.stats_update_task = asyncio.create_task(
983
run_engine_stats_update_task())
984
985
- def get_core_engine_for_request(self) -> CoreEngine:
985
+ def get_core_engine_for_request(self,
986
+ dp_rank: Optional[int] = None
987
+ ) -> CoreEngine:
988
+ if dp_rank is not None:
989
+ # engines are already in rank order
990
+ if dp_rank < 0 or dp_rank >= len(self.core_engines):
991
+ raise ValueError(f"Requested DP rank {dp_rank} is out of "
992
+ f"range [0, {len(self.core_engines)})")
993
+ return self.core_engines[dp_rank]
994
+
995
if not self.lb_engines:
996
return self.core_engines[0]
997
# TODO use P2C alg for larger DP sizes
@@ -1018,7 +1027,8 @@ class DPAsyncMPClient(AsyncMPClient):
1027
request.current_wave = self.current_wave
1028
request.client_index = self.client_index
1029
1021
- chosen_engine = self.get_core_engine_for_request()
1030
+ chosen_engine = self.get_core_engine_for_request(
1031
+ request.data_parallel_rank)
1032
self.reqs_in_flight[request.request_id] = chosen_engine
1033
1034
to_await = self._send_input(EngineCoreRequestType.ADD, request,
vllm/v1/engine/processor.py
+2
@@ -212,6 +212,7 @@ class Processor:
212
trace_headers: Optional[Mapping[str, str]] = None,
213
prompt_adapter_request: Optional[PromptAdapterRequest] = None,
214
priority: int = 0,
215
+ data_parallel_rank: Optional[int] = None,
216
) -> tuple[Optional[str], EngineCoreRequest]:
217
218
# TODO(woosuk): Support pooling models.
@@ -328,6 +329,7 @@ class Processor:
329
arrival_time=arrival_time,
330
lora_request=lora_request,
331
cache_salt=decoder_inputs.get("cache_salt"),
332
+ data_parallel_rank=data_parallel_rank,
333
)
334
335
def _validate_model_inputs(self,