main
py 317 lines 9.9 KB
Raw
1 # SPDX-License-Identifier: Apache-2.0
2 # SPDX-FileCopyrightText: Copyright contributors to the vLLM project
3
4 from abc import ABC, abstractmethod
5 from collections.abc import AsyncGenerator, Iterable, Mapping
6 from dataclasses import dataclass
7 from typing import TYPE_CHECKING, Any
8
9 from vllm.config import ModelConfig, VllmConfig
10 from vllm.config.kv_events import KVEventsConfig
11 from vllm.distributed.weight_transfer.base import (
12 WeightTransferInitRequest,
13 WeightTransferUpdateRequest,
14 )
15 from vllm.inputs import EngineInput, PromptType
16 from vllm.lora.request import LoRARequest
17 from vllm.outputs import PoolingRequestOutput, RequestOutput
18 from vllm.pooling_params import PoolingParams
19 from vllm.renderers import BaseRenderer
20 from vllm.sampling_params import SamplingParams
21 from vllm.tasks import SupportedTask
22 from vllm.v1.engine import EngineCoreRequest
23 from vllm.v1.engine.input_processor import InputProcessor
24 from vllm.v1.fault_tolerance.utils import FaultToleranceRequest, FaultToleranceResult
25 from vllm.v1.kv_hints import KvHintsEnvelope
26
27 if TYPE_CHECKING:
28 from vllm.v1.engine import PauseMode
29
30
31 @dataclass
32 class StreamingInput:
33 """Input data for a streaming generation request.
34
35 This is used with generate() to support multi-turn streaming sessions
36 where inputs are provided via an async generator.
37 """
38
39 prompt: EngineInput
40 sampling_params: SamplingParams | None = None
41
42
43 class EngineClient(ABC):
44 """Protocol class for Clients to Engine."""
45
46 vllm_config: VllmConfig
47 model_config: ModelConfig
48 renderer: BaseRenderer
49 input_processor: InputProcessor
50
51 @property
52 @abstractmethod
53 def is_running(self) -> bool: ...
54
55 @property
56 @abstractmethod
57 def is_stopped(self) -> bool: ...
58
59 @property
60 @abstractmethod
61 def errored(self) -> bool: ...
62
63 @property
64 @abstractmethod
65 def dead_error(self) -> BaseException: ...
66
67 def get_kv_event_sources(self) -> dict[int, KVEventsConfig]:
68 """KV-event publisher config of each engine, keyed by DP rank."""
69 return {}
70
71 def check_admission( # noqa: B027
72 self, n: int = 1, request_id: str | None = None
73 ) -> None:
74 """Reject the request up front if it would exceed queue limits.
75
76 Called before a response is started so that overload rejections can
77 carry an HTTP status, which is not possible once a streaming response
78 has begun. Engines without admission control accept everything.
79
80 Args:
81 n: Number of sequences the request will occupy.
82 request_id: Request id, used for logging only.
83
84 Raises:
85 GracefulHTTPError: If the request cannot be admitted.
86
87 """
88
89 @abstractmethod
90 def generate(
91 self,
92 prompt: EngineCoreRequest
93 | PromptType
94 | EngineInput
95 | AsyncGenerator[StreamingInput, None],
96 sampling_params: SamplingParams,
97 request_id: str,
98 *,
99 prompt_text: str | None = None,
100 lora_request: LoRARequest | None = None,
101 tokenization_kwargs: dict[str, Any] | None = None,
102 trace_headers: Mapping[str, str] | None = None,
103 priority: int = 0,
104 data_parallel_rank: int | None = None,
105 session_id: str | None = None,
106 kv_hints: KvHintsEnvelope | None = None,
107 reasoning_ended: bool | None = None,
108 reasoning_parser_kwargs: dict[str, Any] | None = None,
109 ) -> AsyncGenerator[RequestOutput, None]:
110 """Generate outputs for a request."""
111 ...
112
113 @abstractmethod
114 def encode(
115 self,
116 prompt: PromptType | EngineInput,
117 pooling_params: PoolingParams,
118 request_id: str,
119 lora_request: LoRARequest | None = None,
120 trace_headers: Mapping[str, str] | None = None,
121 priority: int = 0,
122 tokenization_kwargs: dict[str, Any] | None = None,
123 reasoning_ended: bool | None = None,
124 ) -> AsyncGenerator[PoolingRequestOutput, None]:
125 """Generate outputs for a request from a pooling model."""
126 ...
127
128 @abstractmethod
129 async def abort(self, request_id: str | Iterable[str]) -> None:
130 """Abort a request.
131
132 Args:
133 request_id: The unique id of the request,
134 or an iterable of such ids.
135
136 """
137 ...
138
139 @abstractmethod
140 async def notify_kv_transfer_request_rejected(
141 self,
142 request_id: str,
143 kv_transfer_params: dict[str, Any],
144 *,
145 data_parallel_rank: int | None = None,
146 ) -> None:
147 """Notify the engine that a KV-transfer request was rejected before
148 engine admission, so connector-side cleanup can run (e.g. free
149 prefill blocks pinned on the P node).
150 """
151 ...
152
153 @abstractmethod
154 async def is_tracing_enabled(self) -> bool: ...
155
156 @abstractmethod
157 async def do_log_stats(self) -> None: ...
158
159 @abstractmethod
160 async def check_health(self) -> None:
161 """Raise if unhealthy."""
162 ...
163
164 @abstractmethod
165 async def start_profile(self) -> None:
166 """Start profiling the engine."""
167 ...
168
169 @abstractmethod
170 async def stop_profile(self) -> None:
171 """Stop profiling the engine."""
172 ...
173
174 @abstractmethod
175 async def reset_mm_cache(self) -> None:
176 """Reset the multi-modal cache."""
177 ...
178
179 @abstractmethod
180 async def reset_encoder_cache(self) -> None:
181 """Reset the encoder cache."""
182 ...
183
184 @abstractmethod
185 async def reset_prefix_cache(
186 self, reset_running_requests: bool = False, reset_connector: bool = False
187 ) -> bool:
188 """Reset the prefix cache and optionally any configured connector cache."""
189 ...
190
191 @abstractmethod
192 async def sleep(self, level: int = 1, mode: "PauseMode" = "abort") -> None:
193 """Sleep the engine."""
194 ...
195
196 @abstractmethod
197 async def release_kv_cache_memory(self) -> None:
198 """Discard KV cache physical GPU memory. Requires a completed pause."""
199 ...
200
201 @abstractmethod
202 async def wake_up(self, tags: list[str] | None = None) -> bool:
203 """Wake the tagged resources; return whether every engine is fully awake."""
204 ...
205
206 @abstractmethod
207 async def is_sleeping(self) -> bool:
208 """Check whether the engine is sleeping."""
209 ...
210
211 @abstractmethod
212 async def add_lora(self, lora_request: LoRARequest) -> bool:
213 """Load a new LoRA adapter into the engine for future requests."""
214 ...
215
216 @abstractmethod
217 async def pause_generation(
218 self,
219 *,
220 mode: "PauseMode" = "abort",
221 wait_for_inflight_requests: bool = False,
222 clear_cache: bool = True,
223 ) -> None:
224 """Pause new generation/encoding requests.
225
226 Args:
227 mode: How to handle in-flight requests:
228 - ``"abort"``: Abort all in-flight requests immediately
229 and return partial results with "abort" reason (default).
230 - ``"wait"``: Wait for in-flight requests to complete.
231 - ``"keep"``: Freeze requests in queue; they resume on
232 :meth:`resume_generation`.
233 wait_for_inflight_requests: DEPRECATED. Use ``mode="wait"`` instead.
234 clear_cache: DEPRECATED. Whether to clear KV and prefix caches
235 after draining.
236
237 """
238 ...
239
240 @abstractmethod
241 async def resume_generation(self) -> None:
242 """Resume accepting generation/encoding requests."""
243 ...
244
245 @abstractmethod
246 async def is_paused(self) -> bool:
247 """Return whether the engine is currently paused."""
248 ...
249
250 @abstractmethod
251 def shutdown(self, timeout: float | None = None) -> None:
252 """Shutdown the engine with optional timeout."""
253 ...
254
255 async def scale_elastic_ep(
256 self, new_data_parallel_size: int, drain_timeout: int = 300
257 ) -> None:
258 """Scale the engine."""
259 raise NotImplementedError
260
261 async def collective_rpc(
262 self,
263 method: str,
264 timeout: float | None = None,
265 args: tuple = (),
266 kwargs: dict | None = None,
267 ):
268 """Perform a collective RPC call to the given path."""
269 raise NotImplementedError
270
271 async def compute_weight_checksums(self) -> list[dict[str, str]]:
272 """Return rank-qualified weight digests from every worker of every engine."""
273 raise NotImplementedError
274
275 async def handle_fault(
276 self, fault_tolerance_request: FaultToleranceRequest
277 ) -> FaultToleranceResult:
278 """Send fault tolerance instruction to the engine."""
279 raise NotImplementedError
280
281 async def get_status(self):
282 """Get fault tolerance status of all engines."""
283 raise NotImplementedError
284
285 async def get_supported_tasks(self) -> tuple[SupportedTask, ...]:
286 """Get supported tasks."""
287 raise NotImplementedError
288
289 async def init_weight_transfer_engine(
290 self, init_request: WeightTransferInitRequest
291 ) -> None:
292 """Initialize weight transfer for RL training."""
293 raise NotImplementedError
294
295 async def start_weight_update(self) -> None:
296 """Start a new weight update."""
297 raise NotImplementedError
298
299 async def start_draft_weight_update(self) -> None:
300 """Start a new weight update targeting the speculative draft model."""
301 raise NotImplementedError
302
303 async def update_weights(self, request: WeightTransferUpdateRequest) -> None:
304 """Batched weight update for RL training."""
305 raise NotImplementedError
306
307 async def finish_weight_update(self, weight_version: str | None = None) -> None:
308 """Finish the weight update and set its version if provided."""
309 raise NotImplementedError
310
311 async def update_weight_version(self, new_version: str) -> None:
312 """Set the weight version without updating weights."""
313 raise NotImplementedError
314
315 async def get_weight_version(self) -> str:
316 """Return the latest committed weight version."""
317 raise NotImplementedError