main
py 207 lines 7.62 KB
Raw
1 # SPDX-License-Identifier: Apache-2.0
2 # SPDX-FileCopyrightText: Copyright contributors to the vLLM project
3 import os
4 from collections.abc import Callable
5 from concurrent.futures import Future
6 from multiprocessing import Lock
7 from typing import Any
8
9 import torch
10 import torch.distributed as dist
11
12 import vllm.envs as envs
13 from vllm.logger import init_logger
14 from vllm.platforms import current_platform
15 from vllm.utils.network_utils import (
16 aiter_requires_tcp_store,
17 get_distributed_init_method,
18 get_file_store_init_method,
19 get_ip,
20 get_open_port,
21 )
22 from vllm.v1.core.sched.output import GrammarOutput, SchedulerOutput
23 from vllm.v1.executor.abstract import Executor
24 from vllm.v1.executor.vllm_net_devices import set_worker_net_device
25 from vllm.v1.outputs import AsyncModelRunnerOutput, DraftTokenIds, ModelRunnerOutput
26 from vllm.v1.serial_utils import run_method
27 from vllm.v1.worker.worker_base import WorkerWrapperBase
28
29 logger = init_logger(__name__)
30
31
32 class AsyncOutputFuture(Future):
33 def __init__(self, async_output: AsyncModelRunnerOutput, single_value: bool):
34 self.async_output = async_output
35 self.single_value = single_value
36 super().__init__()
37
38 def result(self, timeout=None):
39 if timeout is not None:
40 raise RuntimeError("timeout not implemented")
41
42 if not super().done():
43 try:
44 output = self.async_output.get_output()
45 self.set_result(output if self.single_value else [output])
46 except Exception as e:
47 self.set_exception(e)
48 return super().result()
49
50
51 class UniProcExecutor(Executor):
52 def _init_executor(self) -> None:
53 """Initialize the worker and load the model."""
54 self.driver_worker = WorkerWrapperBase(rpc_rank=0)
55 distributed_init_method, rank, local_rank = self._distributed_args()
56 kwargs = dict(
57 vllm_config=self.vllm_config,
58 local_rank=local_rank,
59 rank=rank,
60 distributed_init_method=distributed_init_method,
61 is_driver_worker=True,
62 shared_worker_lock=Lock(),
63 )
64
65 # Set net device env vars for the worker if VLLM_GPU_NIC_PCIE_MAPPING is set
66 set_worker_net_device(local_rank, self.vllm_config)
67
68 self.driver_worker.init_worker(all_kwargs=[kwargs])
69 self.driver_worker.init_device()
70
71 if envs.VLLM_ELASTIC_EP_SCALE_UP_LAUNCH:
72 self.driver_worker.elastic_ep_execute("load_model")
73 else:
74 self.driver_worker.load_model()
75 current_platform.update_block_size_for_backend(self.vllm_config)
76
77 def _distributed_args(self) -> tuple[str, int, int]:
78 """Return (distributed_init_method, rank, local_rank)."""
79 if aiter_requires_tcp_store():
80 distributed_init_method = get_distributed_init_method(
81 get_ip(), get_open_port()
82 )
83 else:
84 distributed_init_method = get_file_store_init_method()
85 # set local rank as the device index if specified
86 device_info = self.vllm_config.device_config.device.__str__().split(":")
87 local_rank = int(device_info[1]) if len(device_info) > 1 else 0
88 return distributed_init_method, 0, local_rank
89
90 def collective_rpc( # type: ignore[override]
91 self,
92 method: str | Callable,
93 timeout: float | None = None,
94 args: tuple = (),
95 kwargs: dict | None = None,
96 non_block: bool = False,
97 single_value: bool = False,
98 ) -> Any:
99 if kwargs is None:
100 kwargs = {}
101
102 if not non_block:
103 result = run_method(self.driver_worker, method, args, kwargs)
104 if isinstance(result, AsyncModelRunnerOutput):
105 result = result.get_output()
106 return result if single_value else [result]
107
108 try:
109 result = run_method(self.driver_worker, method, args, kwargs)
110 if isinstance(result, AsyncModelRunnerOutput):
111 return AsyncOutputFuture(result, single_value)
112 future = Future[Any]()
113 future.set_result(result if single_value else [result])
114 except Exception as e:
115 future = Future[Any]()
116 future.set_exception(e)
117 return future
118
119 def execute_model( # type: ignore[override]
120 self, scheduler_output: SchedulerOutput, non_block: bool = False
121 ) -> ModelRunnerOutput | None | Future[ModelRunnerOutput | None]:
122 output = self.collective_rpc(
123 "execute_model",
124 args=(scheduler_output,),
125 non_block=non_block,
126 single_value=True,
127 )
128 # In non-blocking mode, surface any exception as early as possible.
129 if non_block and output.done():
130 # Raise the exception in-line if the task failed.
131 output.result()
132 return output
133
134 def sample_tokens( # type: ignore[override]
135 self, grammar_output: GrammarOutput | None, non_block: bool = False
136 ) -> ModelRunnerOutput | None | Future[ModelRunnerOutput | None]:
137 return self.collective_rpc(
138 "sample_tokens",
139 args=(grammar_output,),
140 non_block=non_block,
141 single_value=True,
142 )
143
144 def take_draft_token_ids(self) -> DraftTokenIds | None:
145 return self.collective_rpc("take_draft_token_ids", single_value=True)
146
147 def check_health(self) -> None:
148 # UniProcExecutor will always be healthy as long as
149 # it's running.
150 return
151
152 def shutdown(self) -> None:
153 if worker := self.driver_worker:
154 worker.shutdown()
155
156 @classmethod
157 def supports_async_scheduling(cls) -> bool:
158 return True
159
160
161 class ExecutorWithExternalLauncher(UniProcExecutor):
162 """An executor that uses external launchers to launch engines,
163 specially designed for torchrun-compatible launchers, for
164 offline inference with tensor parallelism.
165
166 see https://github.com/vllm-project/vllm/issues/11400 for
167 the motivation, and examples/features/torchrun/torchrun_example_offline.py
168 for the usage example.
169
170 The key idea: although it is tensor-parallel inference, we only
171 create one worker per executor, users will launch multiple
172 engines with torchrun-compatible launchers, and all these engines
173 work together to process the same prompts. When scheduling is
174 deterministic, all the engines will generate the same outputs,
175 and they don't need to synchronize the states with each other.
176 """
177
178 def _init_executor(self) -> None:
179 """Initialize the worker and load the model."""
180 assert not envs.VLLM_ENABLE_V1_MULTIPROCESSING, (
181 "To get deterministic execution, "
182 "please set VLLM_ENABLE_V1_MULTIPROCESSING=0"
183 )
184 super()._init_executor()
185
186 def _distributed_args(self) -> tuple[str, int, int]:
187 # engines are launched in torchrun-compatible launchers
188 # so we can use the env:// method.
189 # required env vars:
190 # - RANK
191 # - LOCAL_RANK
192 # - MASTER_ADDR
193 # - MASTER_PORT
194 distributed_init_method = "env://"
195 rank = int(os.environ["RANK"])
196 local_rank = int(os.environ["LOCAL_RANK"])
197 return distributed_init_method, rank, local_rank
198
199 def determine_available_memory(self) -> list[int]: # in bytes
200 # we need to get the min across all ranks.
201 memory = super().determine_available_memory()
202 from vllm.distributed.parallel_state import get_world_group
203
204 cpu_group = get_world_group().cpu_group
205 memory_tensor = torch.tensor([memory], device="cpu", dtype=torch.int64)
206 dist.all_reduce(memory_tensor, group=cpu_group, op=dist.ReduceOp.MIN)
207 return [memory_tensor.item()]