main
py 105 lines 3.64 KB
Raw
1 # SPDX-License-Identifier: Apache-2.0
2 # SPDX-FileCopyrightText: Copyright contributors to the vLLM project
3 from collections.abc import Generator
4 from contextlib import contextmanager
5 from typing import TYPE_CHECKING
6
7 import torch
8
9 from vllm.config import VllmConfig
10 from vllm.distributed.ec_transfer import get_ec_transfer, has_ec_transfer
11 from vllm.distributed.ec_transfer.ec_connector.base import ECConnectorBase
12 from vllm.v1.outputs import (
13 EMPTY_MODEL_RUNNER_OUTPUT,
14 ECConnectorOutput,
15 ModelRunnerOutput,
16 )
17
18 if TYPE_CHECKING:
19 from vllm.v1.core.sched.output import SchedulerOutput
20 from vllm.v1.worker.gpu.mm.encoder_cache import EncoderCache
21
22
23 class ECConnector:
24 """EC connector interface used by the V2 GPU model runner."""
25
26 @contextmanager
27 def maybe_get_output(
28 self, scheduler_output: "SchedulerOutput"
29 ) -> Generator[ECConnectorOutput | None, None, None]:
30 yield None
31
32 def no_forward(self, scheduler_output: "SchedulerOutput") -> ModelRunnerOutput:
33 return EMPTY_MODEL_RUNNER_OUTPUT
34
35
36 class ActiveECConnector(ECConnector):
37 def __init__(
38 self,
39 vllm_config: VllmConfig,
40 encoder_cache: dict[str, torch.Tensor],
41 ) -> None:
42 self.encoder_cache = encoder_cache
43 self.ec_connector = get_ec_transfer()
44 assert isinstance(self.ec_connector, ECConnectorBase)
45 # Every producer offloads freshly computed encoder outputs, including
46 # an ec_both node that also reloads them.
47 self.save_new_caches = self.ec_connector.is_producer
48
49 @contextmanager
50 def maybe_get_output(
51 self, scheduler_output: "SchedulerOutput"
52 ) -> Generator[ECConnectorOutput | None, None, None]:
53 if scheduler_output.ec_connector_metadata is None:
54 yield None
55 return
56
57 output = ECConnectorOutput()
58 ec_connector = self.ec_connector
59 assert scheduler_output.ec_connector_metadata is not None
60 ec_connector.bind_connector_metadata(scheduler_output.ec_connector_metadata)
61
62 if ec_connector.is_producer:
63 ec_connector.start_save_caches(encoder_cache=self.encoder_cache)
64
65 if ec_connector.is_consumer:
66 ec_connector.start_load_caches(self.encoder_cache)
67
68 cached_hashes = set(self.encoder_cache) if self.save_new_caches else None
69 try:
70 yield output
71 if cached_hashes is not None:
72 for mm_hash in self.encoder_cache.keys() - cached_hashes:
73 ec_connector.save_caches(
74 encoder_cache=self.encoder_cache, mm_hash=mm_hash
75 )
76 finally:
77 output.finished_sending, output.finished_recving = (
78 ec_connector.get_finished(scheduler_output.finished_req_ids)
79 )
80 output.ec_connector_stats = ec_connector.get_ec_connector_stats()
81 output.ec_connector_worker_meta = ec_connector.build_connector_worker_meta()
82 ec_connector.clear_connector_metadata()
83
84 def no_forward(self, scheduler_output: "SchedulerOutput") -> ModelRunnerOutput:
85 # EC send/recv even if no work to do.
86 with self.maybe_get_output(scheduler_output) as ec_connector_output:
87 pass
88
89 return ModelRunnerOutput.with_ec_conn_output_only(ec_connector_output)
90
91
92 NO_OP_EC_CONNECTOR = ECConnector()
93
94
95 def get_ec_connector(
96 vllm_config: VllmConfig, encoder_cache: "EncoderCache | None"
97 ) -> ECConnector:
98 if (
99 not has_ec_transfer()
100 or vllm_config.model_config.is_encoder_decoder
101 or encoder_cache is None
102 ):
103 return NO_OP_EC_CONNECTOR
104
105 return ActiveECConnector(vllm_config, encoder_cache.encoder_outputs)