Skip to content

vllm.v1.executor.abstract

Classes:

  • Executor –

    Abstract base class for vLLM executors.

Executor

Bases: ABC

Abstract base class for vLLM executors.

An executor is responsible for executing the model on one device, or it can be a distributed executor that can execute the model on multiple devices.

Methods:

Source code in vllm/v1/executor/abstract.py
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
class Executor(ABC):
    """Abstract base class for vLLM executors.

    An executor is responsible for executing the model on one device,
    or it can be a distributed executor that can execute the model on multiple devices.
    """

    uses_ray: bool = False  # whether the executor uses Ray for orchestration.
    supports_pp: bool = False  # whether the executor supports PP

    @staticmethod
    def get_class(vllm_config: VllmConfig) -> type["Executor"]:
        executor_class: type[Executor]
        parallel_config = vllm_config.parallel_config
        distributed_executor_backend = parallel_config.distributed_executor_backend
        # distributed_executor_backend must be set in VllmConfig.__post_init__
        if isinstance(distributed_executor_backend, type):
            if not issubclass(distributed_executor_backend, Executor):
                raise TypeError(
                    "distributed_executor_backend must be a subclass of "
                    f"Executor. Got {distributed_executor_backend}."
                )
            executor_class = distributed_executor_backend
        elif distributed_executor_backend == "ray":
            if envs.VLLM_USE_RAY_V2_EXECUTOR_BACKEND:
                from vllm.v1.executor.ray_executor_v2 import RayExecutorV2

                executor_class = RayExecutorV2
            else:
                from vllm.v1.executor.ray_executor import RayDistributedExecutor

                executor_class = RayDistributedExecutor
        elif distributed_executor_backend == "mp":
            from vllm.v1.executor.multiproc_executor import MultiprocExecutor

            executor_class = MultiprocExecutor
        elif distributed_executor_backend == "uni":
            from vllm.v1.executor.uniproc_executor import UniProcExecutor

            executor_class = UniProcExecutor
        elif distributed_executor_backend == "external_launcher":
            # TODO: make v1 scheduling deterministic
            # to support external launcher
            executor_class = ExecutorWithExternalLauncher
        elif isinstance(distributed_executor_backend, str):
            executor_class = resolve_obj_by_qualname(distributed_executor_backend)
            if not issubclass(executor_class, Executor):
                raise TypeError(
                    "distributed_executor_backend must be a subclass of "
                    f"Executor. Got {executor_class}."
                )
        else:
            raise ValueError(
                f"Unknown distributed executor backend: {distributed_executor_backend}"
            )
        return executor_class

    @instrument(span_name="Executor init")
    def __init__(
        self,
        vllm_config: VllmConfig,
    ) -> None:
        self.vllm_config = vllm_config
        self.model_config = vllm_config.model_config
        self.cache_config = vllm_config.cache_config
        self.lora_config = vllm_config.lora_config
        self.load_config = vllm_config.load_config
        self.parallel_config = vllm_config.parallel_config
        self.scheduler_config = vllm_config.scheduler_config
        self.device_config = vllm_config.device_config
        self.speculative_config = vllm_config.speculative_config
        self.observability_config = vllm_config.observability_config
        self._init_executor()
        self.sleeping_tags: set[str] = set()
        self.kv_output_aggregator: KVOutputAggregator | None = None
        self.ec_output_aggregator: ECOutputAggregator | None = None

    @abstractmethod
    def _init_executor(self) -> None:
        raise NotImplementedError

    def initialize_from_config(self, kv_cache_configs: list[KVCacheConfig]) -> None:
        """Initialize the KV caches on the underlying workers."""
        self.collective_rpc("initialize_from_config", args=(kv_cache_configs,))

    def compile_or_warm_up_model(self) -> None:
        """Compile/warm up the model and capture cudagraphs on workers."""
        compilation_times: list[CompilationTimes] = self.collective_rpc(
            "compile_or_warm_up_model"
        )
        # Propagate compilation time from workers back to the main process.
        # With TP>1, compilation happens in worker processes, so the main
        # process config is never updated. Use max across workers since they
        # compile in parallel.
        if compilation_times:
            self.vllm_config.compilation_config.compilation_time = max(
                t.language_model for t in compilation_times
            )
            self.vllm_config.compilation_config.encoder_compilation_time = max(
                t.encoder for t in compilation_times
            )

    def register_failure_callback(self, callback: FailureCallback):  # noqa: B027
        """Register a function to be called if the executor enters a permanent
        failed state.
        """
        pass

    def determine_available_memory(self) -> list[int]:  # in bytes
        return self.collective_rpc("determine_available_memory")

    def get_kv_cache_specs(self) -> list[dict[str, KVCacheSpec]]:
        return self.collective_rpc("get_kv_cache_spec")

    def get_supported_kv_cache_layouts(self) -> list[list[str]]:
        """Layouts each worker's backends support, most preferred first."""
        return self.collective_rpc("get_supported_kv_cache_layouts")

    def set_kv_cache_layout(self, layout_name: str) -> None:
        """Publish the resolved KV cache layout to the workers."""
        self.collective_rpc("set_kv_cache_layout", args=(layout_name,))

    @overload
    def collective_rpc(
        self,
        method: str | Callable[[WorkerBase], _R],
        timeout: float | None = None,
        args: tuple = (),
        kwargs: dict | None = None,
        non_block: Literal[False] = False,
    ) -> list[_R]:
        pass

    @overload
    def collective_rpc(
        self,
        method: str | Callable[[WorkerBase], _R],
        timeout: float | None = None,
        args: tuple = (),
        kwargs: dict | None = None,
        non_block: Literal[True] = True,
    ) -> Future[list[_R]]:
        pass

    @abstractmethod
    def collective_rpc(
        self,
        method: str | Callable[[WorkerBase], _R],
        timeout: float | None = None,
        args: tuple = (),
        kwargs: dict | None = None,
        non_block: bool = False,
    ) -> list[_R] | Future[list[_R]]:
        """Execute an RPC call on all workers.

        Args:
            method: Name of the worker method to execute, or a callable that
                is serialized and sent to all workers to execute.

                If the method is a callable, it should accept an additional
                `self` argument, in addition to the arguments passed in `args`
                and `kwargs`. The `self` argument will be the worker object.
            timeout: Maximum time in seconds to wait for execution. Raises a
                [`TimeoutError`][] on timeout. `None` means wait indefinitely.
            args: Positional arguments to pass to the worker method.
            kwargs: Keyword arguments to pass to the worker method.
            non_block: If `True`, returns a list of Futures instead of waiting
                for the results.

        Returns:
            A list containing the results from each worker.

        Note:
            It is recommended to use this API to only pass control messages,
            and set up data-plane communication to pass data.

        """
        raise NotImplementedError

    def get_kv_connector_handshake_metadata(
        self,
    ) -> list[dict[tuple[int, int], KVConnectorHandshakeMetadata]]:
        return self.collective_rpc("get_kv_connector_handshake_metadata")

    @overload
    def execute_model(
        self, scheduler_output: SchedulerOutput, non_block: Literal[False] = False
    ) -> ModelRunnerOutput | None:
        pass

    @overload
    def execute_model(
        self, scheduler_output: SchedulerOutput, non_block: Literal[True] = True
    ) -> Future[ModelRunnerOutput | None]:
        pass

    def execute_model(
        self, scheduler_output: SchedulerOutput, non_block: bool = False
    ) -> ModelRunnerOutput | None | Future[ModelRunnerOutput | None]:
        output = self.collective_rpc(  # type: ignore[call-overload]
            "execute_model", args=(scheduler_output,), non_block=non_block
        )
        return output[0]

    @overload
    def sample_tokens(
        self, grammar_output: GrammarOutput | None, non_block: Literal[False] = False
    ) -> ModelRunnerOutput:
        pass

    @overload
    def sample_tokens(
        self, grammar_output: GrammarOutput | None, non_block: Literal[True] = True
    ) -> Future[ModelRunnerOutput]:
        pass

    def sample_tokens(
        self, grammar_output: GrammarOutput | None, non_block: bool = False
    ) -> ModelRunnerOutput | Future[ModelRunnerOutput]:
        output = self.collective_rpc(  # type: ignore[call-overload]
            "sample_tokens", args=(grammar_output,), non_block=non_block
        )
        return output[0]

    def execute_dummy_batch(self) -> None:
        self.collective_rpc("execute_dummy_batch")

    def take_draft_token_ids(self) -> DraftTokenIds | None:
        output: list[DraftTokenIds] = self.collective_rpc("take_draft_token_ids")
        return output[0]

    def profile(
        self,
        is_start: bool = True,
        profile_prefix: str | None = None,
        *,
        delay_iterations: int | None = None,
        max_iterations: int | None = None,
    ):
        kwargs: dict[str, int] = {}
        if delay_iterations is not None:
            kwargs["delay_iterations"] = delay_iterations
        if max_iterations is not None:
            kwargs["max_iterations"] = max_iterations
        if kwargs:
            # Reject before dispatch: a failing collective_rpc may leave
            # unread worker replies queued.
            _check_worker_profile_kwargs(
                self.vllm_config.parallel_config.worker_cls, kwargs
            )
        self.collective_rpc(
            "profile",
            args=(is_start, profile_prefix),
            kwargs=kwargs or None,
        )

    def save_sharded_state(
        self,
        path: str,
        pattern: str | None = None,
        max_size: int | None = None,
    ) -> None:
        self.collective_rpc(
            "save_sharded_state",
            kwargs=dict(path=path, pattern=pattern, max_size=max_size),
        )

    @abstractmethod
    def check_health(self) -> None:
        """Checks if the executor is healthy. If not, it should raise an
        exception."""
        raise NotImplementedError

    def shutdown(self) -> None:
        """Shutdown the executor."""
        self.collective_rpc("shutdown")

    def init_kv_output_aggregator(self, connector: "KVConnectorBase") -> None:
        """Init KVOutputAggregator."""
        self.kv_output_aggregator = KVOutputAggregator.from_connector(
            connector, self.parallel_config.world_size
        )

    def init_ec_output_aggregator(self) -> None:
        self.ec_output_aggregator = ECOutputAggregator()

    @cached_property  # Avoid unnecessary RPC calls
    def supported_tasks(self) -> tuple[SupportedTask, ...]:
        output: list[tuple[SupportedTask, ...]]
        output = self.collective_rpc("get_supported_tasks")
        return output[0]

    def supports_draft_weight_updates(self) -> bool:
        worker_support: list[bool] = self.collective_rpc(
            "supports_draft_weight_updates"
        )
        return all(worker_support)

    def add_lora(self, lora_request: LoRARequest) -> bool:
        assert lora_request.lora_int_id > 0, "lora_id must be greater than 0."
        return all(self.collective_rpc("add_lora", args=(lora_request,)))

    def remove_lora(self, lora_id: int) -> bool:
        assert lora_id > 0, "lora_id must be greater than 0."
        return all(self.collective_rpc("remove_lora", args=(lora_id,)))

    def pin_lora(self, lora_id: int) -> bool:
        assert lora_id > 0, "lora_id must be greater than 0."
        return all(self.collective_rpc("pin_lora", args=(lora_id,)))

    def list_loras(self) -> set[int]:
        sets: list[set[int]] = self.collective_rpc("list_loras")
        for s in sets:
            assert s == sets[0], "All workers should have the same LORAs."
        return sets[0]

    def reset_mm_cache(self) -> None:
        """Reset the multi-modal cache in each worker."""
        self.collective_rpc("reset_mm_cache")

    def reset_encoder_cache(self) -> None:
        """Reset the encoder cache in each worker to clear cached encoder outputs."""
        self.collective_rpc("reset_encoder_cache")

    @property
    def is_sleeping(self) -> bool:
        return bool(self.sleeping_tags)

    def sleep(self, level: int = 1):
        if "weights" in self.sleeping_tags:
            logger.warning("Executor is already sleeping.")
            return
        time_before_sleep = time.perf_counter()
        self.collective_rpc("sleep", kwargs=dict(level=level))
        time_after_sleep = time.perf_counter()
        self.sleeping_tags |= SLEEP_TAGS
        logger.info(
            "It took %.6f seconds to fall asleep.", time_after_sleep - time_before_sleep
        )

    def wake_up(self, tags: list[str] | None = None):
        if not self.is_sleeping:
            logger.warning("Executor is not sleeping.")
            return
        if tags:
            for tag in tags:
                if tag not in self.sleeping_tags:
                    logger.warning(
                        "Tag %s is not in sleeping tags %s", tag, self.sleeping_tags
                    )
                    return
        time_before_wakeup = time.perf_counter()
        self.collective_rpc("wake_up", kwargs=dict(tags=tags))
        time_after_wakeup = time.perf_counter()
        logger.info(
            "It took %.6f seconds to wake up tags %s.",
            time_after_wakeup - time_before_wakeup,
            tags if tags is not None else self.sleeping_tags,
        )
        if tags:
            for tag in tags:
                self.sleeping_tags.remove(tag)
        else:
            self.sleeping_tags.clear()

    def discard(self, tags: tuple[str, ...]) -> None:
        tags_to_discard = set(tags) - self.sleeping_tags
        if not tags_to_discard:
            logger.warning("Tags %s are already sleeping.", tags)
            return
        time_before_discard = time.perf_counter()
        try:
            self.collective_rpc("discard", args=(tuple(tags_to_discard),))
        finally:
            self.sleeping_tags |= tags_to_discard
        time_after_discard = time.perf_counter()
        logger.info(
            "It took %.6f seconds to discard tags %s.",
            time_after_discard - time_before_discard,
            tags_to_discard,
        )

    def reinitialize_distributed(
        self, reconfig_request: ReconfigureDistributedRequest
    ) -> None:
        raise NotImplementedError

    @classmethod
    def supports_async_scheduling(cls) -> bool:
        """Whether the executor supports async scheduling."""
        return False

check_health() abstractmethod

Checks if the executor is healthy. If not, it should raise an exception.

Source code in vllm/v1/executor/abstract.py
@abstractmethod
def check_health(self) -> None:
    """Checks if the executor is healthy. If not, it should raise an
    exception."""
    raise NotImplementedError

collective_rpc(method, timeout=None, args=(), kwargs=None, non_block=False) abstractmethod

collective_rpc(
    method: str | Callable[[WorkerBase], _R],
    timeout: float | None = None,
    args: tuple = (),
    kwargs: dict | None = None,
    non_block: Literal[False] = False,
) -> list[_R]
collective_rpc(
    method: str | Callable[[WorkerBase], _R],
    timeout: float | None = None,
    args: tuple = (),
    kwargs: dict | None = None,
    non_block: Literal[True] = True,
) -> Future[list[_R]]

Execute an RPC call on all workers.

Parameters:

  • method

    (str | Callable[[WorkerBase], _R]) –

    Name of the worker method to execute, or a callable that is serialized and sent to all workers to execute.

    If the method is a callable, it should accept an additional self argument, in addition to the arguments passed in args and kwargs. The self argument will be the worker object.

  • timeout

    (float | None, default: None ) –

    Maximum time in seconds to wait for execution. Raises a TimeoutError on timeout. None means wait indefinitely.

  • args

    (tuple, default: () ) –

    Positional arguments to pass to the worker method.

  • kwargs

    (dict | None, default: None ) –

    Keyword arguments to pass to the worker method.

  • non_block

    (bool, default: False ) –

    If True, returns a list of Futures instead of waiting for the results.

Returns:

  • list[_R] | Future[list[_R]] –

    A list containing the results from each worker.

Note

It is recommended to use this API to only pass control messages, and set up data-plane communication to pass data.

Source code in vllm/v1/executor/abstract.py
@abstractmethod
def collective_rpc(
    self,
    method: str | Callable[[WorkerBase], _R],
    timeout: float | None = None,
    args: tuple = (),
    kwargs: dict | None = None,
    non_block: bool = False,
) -> list[_R] | Future[list[_R]]:
    """Execute an RPC call on all workers.

    Args:
        method: Name of the worker method to execute, or a callable that
            is serialized and sent to all workers to execute.

            If the method is a callable, it should accept an additional
            `self` argument, in addition to the arguments passed in `args`
            and `kwargs`. The `self` argument will be the worker object.
        timeout: Maximum time in seconds to wait for execution. Raises a
            [`TimeoutError`][] on timeout. `None` means wait indefinitely.
        args: Positional arguments to pass to the worker method.
        kwargs: Keyword arguments to pass to the worker method.
        non_block: If `True`, returns a list of Futures instead of waiting
            for the results.

    Returns:
        A list containing the results from each worker.

    Note:
        It is recommended to use this API to only pass control messages,
        and set up data-plane communication to pass data.

    """
    raise NotImplementedError

compile_or_warm_up_model()

Compile/warm up the model and capture cudagraphs on workers.

Source code in vllm/v1/executor/abstract.py
def compile_or_warm_up_model(self) -> None:
    """Compile/warm up the model and capture cudagraphs on workers."""
    compilation_times: list[CompilationTimes] = self.collective_rpc(
        "compile_or_warm_up_model"
    )
    # Propagate compilation time from workers back to the main process.
    # With TP>1, compilation happens in worker processes, so the main
    # process config is never updated. Use max across workers since they
    # compile in parallel.
    if compilation_times:
        self.vllm_config.compilation_config.compilation_time = max(
            t.language_model for t in compilation_times
        )
        self.vllm_config.compilation_config.encoder_compilation_time = max(
            t.encoder for t in compilation_times
        )

get_supported_kv_cache_layouts()

Layouts each worker's backends support, most preferred first.

Source code in vllm/v1/executor/abstract.py
def get_supported_kv_cache_layouts(self) -> list[list[str]]:
    """Layouts each worker's backends support, most preferred first."""
    return self.collective_rpc("get_supported_kv_cache_layouts")

init_kv_output_aggregator(connector)

Init KVOutputAggregator.

Source code in vllm/v1/executor/abstract.py
def init_kv_output_aggregator(self, connector: "KVConnectorBase") -> None:
    """Init KVOutputAggregator."""
    self.kv_output_aggregator = KVOutputAggregator.from_connector(
        connector, self.parallel_config.world_size
    )

initialize_from_config(kv_cache_configs)

Initialize the KV caches on the underlying workers.

Source code in vllm/v1/executor/abstract.py
def initialize_from_config(self, kv_cache_configs: list[KVCacheConfig]) -> None:
    """Initialize the KV caches on the underlying workers."""
    self.collective_rpc("initialize_from_config", args=(kv_cache_configs,))

register_failure_callback(callback)

Register a function to be called if the executor enters a permanent failed state.

Source code in vllm/v1/executor/abstract.py
def register_failure_callback(self, callback: FailureCallback):  # noqa: B027
    """Register a function to be called if the executor enters a permanent
    failed state.
    """
    pass

reset_encoder_cache()

Reset the encoder cache in each worker to clear cached encoder outputs.

Source code in vllm/v1/executor/abstract.py
def reset_encoder_cache(self) -> None:
    """Reset the encoder cache in each worker to clear cached encoder outputs."""
    self.collective_rpc("reset_encoder_cache")

reset_mm_cache()

Reset the multi-modal cache in each worker.

Source code in vllm/v1/executor/abstract.py
def reset_mm_cache(self) -> None:
    """Reset the multi-modal cache in each worker."""
    self.collective_rpc("reset_mm_cache")

set_kv_cache_layout(layout_name)

Publish the resolved KV cache layout to the workers.

Source code in vllm/v1/executor/abstract.py
def set_kv_cache_layout(self, layout_name: str) -> None:
    """Publish the resolved KV cache layout to the workers."""
    self.collective_rpc("set_kv_cache_layout", args=(layout_name,))

shutdown()

Shutdown the executor.

Source code in vllm/v1/executor/abstract.py
def shutdown(self) -> None:
    """Shutdown the executor."""
    self.collective_rpc("shutdown")

supports_async_scheduling() classmethod

Whether the executor supports async scheduling.

Source code in vllm/v1/executor/abstract.py
@classmethod
def supports_async_scheduling(cls) -> bool:
    """Whether the executor supports async scheduling."""
    return False

_check_worker_profile_kwargs(worker_cls, kwargs)

Raise if the worker's profile cannot accept the given overrides.

Source code in vllm/v1/executor/abstract.py
def _check_worker_profile_kwargs(worker_cls: str, kwargs: dict[str, int]) -> None:
    """Raise if the worker's ``profile`` cannot accept the given overrides."""
    params = inspect.signature(resolve_obj_by_qualname(worker_cls).profile).parameters
    if any(p.kind is inspect.Parameter.VAR_KEYWORD for p in params.values()):
        return
    if unsupported := [name for name in kwargs if name not in params]:
        raise ValueError(
            f"Worker class {worker_cls} does not support per-session profiling "
            f"overrides: {', '.join(unsupported)}"
        )