main
py 207 lines 6.28 KB
Raw
1 # SPDX-License-Identifier: Apache-2.0
2 # SPDX-FileCopyrightText: Copyright contributors to the vLLM project
3
4 import asyncio
5 import os
6 from collections.abc import Callable
7 from concurrent.futures import Future
8 from typing import Any
9
10 import pytest
11
12 from vllm.distributed.ec_transfer.ec_connector.utils import ECOutputAggregator
13 from vllm.distributed.kv_transfer.kv_connector.utils import KVOutputAggregator
14 from vllm.engine.arg_utils import AsyncEngineArgs, EngineArgs
15 from vllm.sampling_params import SamplingParams
16 from vllm.v1.engine.async_llm import AsyncLLM
17 from vllm.v1.engine.llm_engine import LLMEngine
18 from vllm.v1.executor import multiproc_executor as multiproc_executor_module
19 from vllm.v1.executor.abstract import Executor
20 from vllm.v1.executor.multiproc_executor import MultiprocExecutor
21 from vllm.v1.executor.uniproc_executor import (
22 ExecutorWithExternalLauncher,
23 UniProcExecutor,
24 )
25
26
27 class Mock: ...
28
29
30 def test_supports_async_scheduling_base_executor():
31 assert Executor.supports_async_scheduling() is False
32
33
34 def test_supports_async_scheduling_uniproc_executor():
35 assert UniProcExecutor.supports_async_scheduling() is True
36
37
38 def test_supports_async_scheduling_executor_with_external_launcher():
39 # ExecutorWithExternalLauncher inherits from UniProcExecutor and does not
40 # override supports_async_scheduling, so it should return True.
41 assert ExecutorWithExternalLauncher.supports_async_scheduling() is True
42
43
44 def test_supports_async_scheduling_multiproc_executor():
45 assert MultiprocExecutor.supports_async_scheduling() is True
46
47
48 class _FakeClock:
49 def __init__(self) -> None:
50 self.now = 0.0
51
52 def time(self) -> float:
53 return self.now
54
55 def sleep(self, seconds: float) -> None:
56 self.now += seconds
57
58
59 class _FakeProcess:
60 def __init__(self, clock: _FakeClock, exits_at: float) -> None:
61 self.clock = clock
62 self.exits_at = exits_at
63 self.terminate_called = False
64
65 def is_alive(self) -> bool:
66 return self.clock.time() < self.exits_at
67
68 def terminate(self) -> None:
69 self.terminate_called = True
70
71
72 @pytest.mark.parametrize(
73 ("timeout", "exits_at", "expected_terminate"),
74 [
75 pytest.param(6, 5, False, id="worker-exits-before-timeout"),
76 pytest.param(6, 7, True, id="worker-exceeds-timeout"),
77 ],
78 )
79 def test_multiproc_executor_worker_termination_timeout(
80 monkeypatch, timeout, exits_at, expected_terminate
81 ):
82 monkeypatch.setenv("VLLM_WORKER_SHUTDOWN_TIMEOUT_SECONDS", str(timeout))
83 clock = _FakeClock()
84 monkeypatch.setattr(multiproc_executor_module.time, "time", clock.time)
85 monkeypatch.setattr(multiproc_executor_module.time, "sleep", clock.sleep)
86 executor = MultiprocExecutor.__new__(MultiprocExecutor)
87 proc = _FakeProcess(clock, exits_at=exits_at)
88 executor._ensure_worker_termination([proc]) # type: ignore[list-item]
89 assert proc.terminate_called is expected_terminate
90
91
92 class CustomMultiprocExecutor(MultiprocExecutor):
93 def collective_rpc( # type: ignore[override]
94 self,
95 method: str | Callable,
96 timeout: float | None = None,
97 args: tuple = (),
98 kwargs: dict | None = None,
99 non_block: bool = False,
100 unique_reply_rank: int | None = None,
101 kv_output_aggregator: KVOutputAggregator | None = None,
102 ec_output_aggregator: ECOutputAggregator | None = None,
103 ) -> Any | list[Any] | Future[Any | list[Any]]:
104 # Drop marker to show that this was run
105 with open(".marker", "w"):
106 ...
107 return super().collective_rpc(
108 method,
109 timeout,
110 args,
111 kwargs,
112 non_block,
113 unique_reply_rank,
114 kv_output_aggregator,
115 ec_output_aggregator,
116 )
117
118
119 CustomMultiprocExecutorAsync = CustomMultiprocExecutor
120 MODEL = "Qwen/Qwen3-0.6B"
121
122
123 def test_custom_executor_type_checking():
124 with pytest.raises(ValueError):
125 engine_args = EngineArgs(
126 model=MODEL,
127 gpu_memory_utilization=0.2,
128 max_model_len=8192,
129 distributed_executor_backend=Mock,
130 )
131 LLMEngine.from_engine_args(engine_args)
132 with pytest.raises(ValueError):
133 engine_args = AsyncEngineArgs(
134 model=MODEL,
135 gpu_memory_utilization=0.2,
136 max_model_len=8192,
137 distributed_executor_backend=Mock,
138 )
139 AsyncLLM.from_engine_args(engine_args)
140
141
142 @pytest.mark.parametrize(
143 "distributed_executor_backend",
144 [
145 CustomMultiprocExecutor,
146 "tests.v1.executor.test_executor.CustomMultiprocExecutor",
147 ],
148 )
149 def test_custom_executor(distributed_executor_backend, tmp_path):
150 cwd = os.path.abspath(".")
151 os.chdir(tmp_path)
152 try:
153 assert not os.path.exists(".marker")
154
155 engine_args = EngineArgs(
156 model=MODEL,
157 gpu_memory_utilization=0.2,
158 max_model_len=8192,
159 distributed_executor_backend=distributed_executor_backend,
160 enforce_eager=True, # reduce test time
161 )
162 engine = LLMEngine.from_engine_args(engine_args)
163 sampling_params = SamplingParams(max_tokens=1)
164
165 engine.add_request("0", "foo", sampling_params)
166 engine.step()
167
168 assert os.path.exists(".marker")
169 finally:
170 os.chdir(cwd)
171
172
173 @pytest.mark.parametrize(
174 "distributed_executor_backend",
175 [
176 CustomMultiprocExecutorAsync,
177 "tests.v1.executor.test_executor.CustomMultiprocExecutorAsync",
178 ],
179 )
180 def test_custom_executor_async(distributed_executor_backend, tmp_path):
181 cwd = os.path.abspath(".")
182 os.chdir(tmp_path)
183 try:
184 assert not os.path.exists(".marker")
185
186 engine_args = AsyncEngineArgs(
187 model=MODEL,
188 gpu_memory_utilization=0.2,
189 max_model_len=8192,
190 distributed_executor_backend=distributed_executor_backend,
191 enforce_eager=True, # reduce test time
192 )
193 engine = AsyncLLM.from_engine_args(engine_args)
194 sampling_params = SamplingParams(max_tokens=1)
195
196 async def t():
197 stream = engine.generate(
198 request_id="0", prompt="foo", sampling_params=sampling_params
199 )
200 async for x in stream:
201 ...
202
203 asyncio.run(t())
204
205 assert os.path.exists(".marker")
206 finally:
207 os.chdir(cwd)