main
py 378 lines 12.4 KB
Raw
1 # SPDX-License-Identifier: Apache-2.0
2 # SPDX-FileCopyrightText: Copyright contributors to the vLLM project
3 """ECConnectorBase Class for Distributed Encoder Cache &
4 P2P Encoder cache communication in V1
5
6 The class provides the following primitives:
7 Scheduler-side: runs in the scheduler, binds metadata, which
8 is used by the worker-side to load/save Encoder cache.
9 check_caches_exist() - Check whether Encoder cache of requests exist
10 update_state_after_alloc() - update ECConnector state after
11 allocate. This will decide to load the cache or not
12 request_finished() - called when a request is finished,
13 free the cache with the requests
14
15 Worker-side: runs in each worker, loads/saves Encoder Cache to/from
16 the Connector based on the metadata.
17 start_load_ec() - starts loading all ECs (maybe async)
18 wait_for_save() - blocks until all saves are done
19
20 get_finished() - called with ids of finished requests, returns
21 ids of requests that have completed async sending/recving.
22 build_connector_worker_meta() - builds metadata to be sent
23 back to the scheduler-side connector
24 """
25
26 import enum
27 from abc import ABC, abstractmethod
28 from typing import TYPE_CHECKING, Any
29
30 import torch
31
32 from vllm.logger import init_logger
33 from vllm.v1.core.sched.output import SchedulerOutput
34 from vllm.v1.outputs import ECConnectorOutput
35
36 if TYPE_CHECKING:
37 from vllm.config import VllmConfig
38 from vllm.distributed.ec_transfer.ec_connector.metrics import (
39 ECConnectorPromMetrics,
40 ECConnectorStats,
41 PromMetric,
42 PromMetricT,
43 )
44 from vllm.v1.request import Request
45
46 logger = init_logger(__name__)
47
48
49 class ECConnectorRole(enum.Enum):
50 # Connector running in the scheduler process
51 SCHEDULER = 0
52
53 # Connector running in the worker process
54 WORKER = 1
55
56
57 class ECConnectorMetadata(ABC): # noqa: B024
58 """Abstract Metadata used to communicate between the
59 Scheduler ECConnector and Worker ECConnector.
60 """
61
62 pass
63
64
65 class ECConnectorWorkerMetadata(ABC):
66 """Abstract Metadata used to communicate back
67 Worker ECConnector -> Scheduler ECConnector.
68
69 Each worker can output its own metadata.
70 For a single engine step, all metadata objects returned by workers
71 will be aggregated using the `aggregate` method below, before
72 being passed to the Scheduler ECConnector.
73 """
74
75 @abstractmethod
76 def aggregate(
77 self, other: "ECConnectorWorkerMetadata"
78 ) -> "ECConnectorWorkerMetadata":
79 """Aggregate metadata with another `ECConnectorWorkerMetadata` object."""
80 pass
81
82
83 class ECConnectorBase(ABC):
84 def __init__(self, vllm_config: "VllmConfig", role: ECConnectorRole):
85 self._connector_metadata: ECConnectorMetadata | None = None
86 self._vllm_config = vllm_config
87 self._role = role
88 if vllm_config.ec_transfer_config is not None:
89 self._is_producer = vllm_config.ec_transfer_config.is_ec_producer
90 self._is_consumer = vllm_config.ec_transfer_config.is_ec_consumer
91 else:
92 raise ValueError("ec_transfer_config must be set for ECConnectorBase")
93
94 @property
95 def role(self) -> ECConnectorRole:
96 return self._role
97
98 @property
99 def is_producer(self) -> bool:
100 return self._is_producer
101
102 @property
103 def is_consumer(self) -> bool:
104 return self._is_consumer
105
106 def shutdown(self) -> None:
107 """Shutdown the connector. This is called when the process
108 is shutting down to ensure that all the async operations are
109 completed and the connector is cleaned up properly.
110 """
111 return None
112
113 # ==============================
114 # Worker-side methods
115 # ==============================
116
117 def bind_connector_metadata(self, connector_metadata: ECConnectorMetadata) -> None:
118 """Set the connector metadata from the scheduler.
119
120 This function should be called by the model runner every time
121 before the model execution. The metadata will be used for runtime
122 EC cache loading.
123
124 Args:
125 connector_metadata (dict): the connector metadata.
126
127 """
128 self._connector_metadata = connector_metadata
129
130 def clear_connector_metadata(self) -> None:
131 """Clear the connector metadata.
132
133 This function should be called by the model runner every time
134 after the model execution.
135 """
136 self._connector_metadata = None
137
138 def _get_connector_metadata(self) -> ECConnectorMetadata:
139 """Get the connector metadata.
140
141 This function should only be called inside the connector.
142
143 Returns:
144 ConnectorMetadata: the connector metadata.
145
146 """
147 # Should only be called while set to valid metadata.
148 assert self._connector_metadata is not None
149 return self._connector_metadata
150
151 def register_caches(
152 self,
153 ec_caches: dict[str, torch.Tensor],
154 ):
155 """Initialize with the EC caches.
156
157 Args:
158 ec_caches: dictionary of encoder cache
159
160 """
161 # TODO: Implement this later for P2P feature
162 return
163
164 @abstractmethod
165 def start_load_caches(
166 self, encoder_cache: dict[str, torch.Tensor], **kwargs
167 ) -> None:
168 """Start loading the cache from the connector into vLLM's encoder cache.
169
170 This method loads the encoder cache based on metadata provided by the scheduler.
171 It is called before `_gather_mm_embeddings` for the EC Connector. For EC,
172 the `encoder_cache` and `mm_hash` are stored in `kwargs`.
173
174 Args:
175 encoder_cache (dict[str, torch.Tensor]): A dictionary mapping multimodal
176 data hashes (`mm_hash`) to encoder cache tensors.
177 kwargs (dict): Additional keyword arguments for the connector.
178
179 """
180 pass
181
182 @abstractmethod
183 def save_caches(
184 self, encoder_cache: dict[str, torch.Tensor], mm_hash: str, **kwargs
185 ) -> None:
186 """Save the encoder cache to the connector.
187
188 This method saves the encoder cache from the worker's local storage
189 to shared storage or another external connector.
190
191 Args:
192 encoder_cache (dict[str, torch.Tensor]): A dictionary mapping multimodal
193 data hashes (`mm_hash`) to encoder cache tensors.
194 mm_hash (str): The hash of the multimodal data whose cache is being saved.
195 kwargs (dict): Additional keyword arguments for the connector.
196
197 """
198 pass
199
200 def get_finished(
201 self, finished_req_ids: set[str]
202 ) -> tuple[set[str] | None, set[str] | None]:
203 """Notifies worker-side connector ids of requests that have
204 finished generating tokens on the worker.
205 The scheduler process (via the Executors) will use this output
206 to track which workers are done.
207
208 Returns:
209 ids of requests that have finished asynchronous transfer
210 (requests that previously returned True from request_finished()),
211 tuple of (sending/saving ids, recving/loading ids).
212 The finished saves/sends req ids must belong to a set provided in a
213 call to this method (this call or a prior one).
214
215 """
216 return None, None
217
218 def build_connector_worker_meta(self) -> ECConnectorWorkerMetadata | None:
219 """Build the ECConnector worker metadata for this engine step.
220
221 Returns:
222 ECConnectorWorkerMetadata: the worker metadata.
223 None if no worker metadata is available.
224
225 """
226 return None
227
228 def get_ec_connector_stats(self) -> "ECConnectorStats | None":
229 """Get the EC connector stats collected during the last interval.
230
231 Callable on either a worker-side or scheduler-side connector
232 instance: the worker-side instance reports stats gathered during
233 cache save/load, while the scheduler-side instance reports stats
234 gathered during scheduling decisions.
235 """
236 return None
237
238 # ==============================
239 # Scheduler-side methods
240 # ==============================
241
242 @abstractmethod
243 def has_cache_item(
244 self,
245 identifier: str,
246 ) -> bool:
247 """Check if a single encoder cache exists.
248
249 Args:
250 identifier (str): the identifier of the media.
251
252 Returns:
253 A bool where value is True if cache exist for
254 the media
255
256 """
257 pass
258
259 def ensure_cache_available(
260 self, request: "Request", num_computed_tokens: int
261 ) -> bool:
262 """Ensure encoder cache items are available for the given request.
263 May initiate asynchronous transfers for items not yet local.
264
265 Args:
266 request: the request whose multimodal features to check.
267 num_computed_tokens: tokens already covered by cached KV blocks.
268
269 Returns:
270 True if all items are ready or no transfer is needed.
271 False if any items are still in transit (request should be deferred).
272
273 """
274 return True
275
276 def start_save_caches(self, **kwargs: Any) -> None:
277 """Prepare this step's outbound pushes before the model runs."""
278 return
279
280 def start_worker_services(self) -> None:
281 """Start Worker-side services once the model is resident."""
282 return
283
284 def take_unavailable_requests(self) -> set[str]:
285 """Request IDs whose encoder inputs the connector can no longer obtain.
286
287 The Scheduler fails these; re-issuing the request re-runs the encode.
288 """
289 return set()
290
291 def update_state_after_free(self, request: "Request", index: int):
292 """Notify the connector that an encoder cache entry was released."""
293 return
294
295 @abstractmethod
296 def update_state_after_alloc(self, request: "Request", index: int):
297 """Update ECConnector state to decide allocate cache for requests.
298
299 Args:
300 request (Request): the request object.
301 index (int): index of the encoder cache entry being allocated.
302
303 """
304 pass
305
306 @abstractmethod
307 def build_connector_meta(
308 self, scheduler_output: SchedulerOutput
309 ) -> ECConnectorMetadata:
310 """Build the connector metadata for this step.
311
312 This function should NOT modify fields in the scheduler_output.
313 Also, calling this function will reset the state of the connector.
314
315 Args:
316 scheduler_output (SchedulerOutput): the scheduler output object.
317
318 """
319 pass
320
321 def update_connector_output(self, connector_output: ECConnectorOutput):
322 """Update ECConnector state from worker-side connectors output.
323
324 Args:
325 connector_output (ECConnectorOutput): the worker-side
326 connectors output.
327
328 """
329 return
330
331 def request_finished(
332 self, request: "Request"
333 ) -> tuple[bool, dict[str, Any] | None]:
334 """Called when a request has finished, before its encoder cache is freed.
335
336 Returns:
337 True if the request is being saved/sent asynchronously and cached
338 should not be freed until the request_id is returned from
339 get_finished().
340
341 """
342 return False, None
343
344 def has_pending_push_work(self) -> bool:
345 """Return True if the connector has push-mode work that requires
346 the engine main loop to keep stepping (e.g. for EPD,
347 Producer has push work when Xfer is in progress - Consumer
348 is reading it).
349 This mirrors exactly the KV Connector's has_pending_push_work().
350
351 Connectors that don't implement push-based EC transfer should
352 leave this as False.
353 """
354 return False
355
356 @classmethod
357 def build_ec_connector_stats(
358 cls, data: dict[str, Any] | None = None
359 ) -> "ECConnectorStats | None":
360 """ECConnectorStats resolution method. This method allows dynamically
361 registered connectors to return their own ECConnectorStats object,
362 which can implement custom aggregation logic on the data dict.
363 """
364 return None
365
366 @classmethod
367 def build_prom_metrics(
368 cls,
369 vllm_config: "VllmConfig",
370 metric_types: dict[type["PromMetric"], type["PromMetricT"]],
371 labelnames: list[str],
372 per_engine_labelvalues: dict[int, list[object]],
373 ) -> "ECConnectorPromMetrics | None":
374 """Create an ECConnectorPromMetrics subclass which should register
375 per-connector Prometheus metrics and implement observe() to
376 expose connector transfer stats via Prometheus.
377 """
378 return None