main
py 146 lines 5.13 KB
Raw
1 # SPDX-License-Identifier: Apache-2.0
2 # SPDX-FileCopyrightText: Copyright contributors to the vLLM project
3 from typing import TYPE_CHECKING, Any
4
5 import torch
6
7 from vllm.config import VllmConfig
8 from vllm.distributed.kv_transfer import (
9 get_kv_transfer_group,
10 has_kv_transfer_group,
11 kv_transfer_state,
12 )
13 from vllm.distributed.kv_transfer.kv_connector.utils import copy_kv_blocks
14 from vllm.forward_context import (
15 get_forward_context,
16 is_forward_context_available,
17 set_forward_context,
18 )
19 from vllm.v1.outputs import (
20 EMPTY_MODEL_RUNNER_OUTPUT,
21 KVConnectorOutput,
22 ModelRunnerOutput,
23 )
24
25 if TYPE_CHECKING:
26 from vllm.v1.core.sched.output import SchedulerOutput
27
28
29 class KVConnector:
30 """KVConnector interface used by GPUModelRunner."""
31
32 def pre_forward(self, scheduler_output: "SchedulerOutput", **kwargs: Any) -> None:
33 pass
34
35 def finish_forward(self) -> None:
36 pass
37
38 def post_forward(self, finished_req_ids: set[str]) -> KVConnectorOutput | None:
39 return None
40
41 def no_forward(self, scheduler_output: "SchedulerOutput") -> ModelRunnerOutput:
42 return EMPTY_MODEL_RUNNER_OUTPUT
43
44 def set_disabled(self, disabled: bool) -> None:
45 pass
46
47 def reset_capture_state(self) -> None:
48 pass
49
50
51 class ActiveKVConnector(KVConnector):
52 def __init__(
53 self, vllm_config: VllmConfig, kv_caches_dict: dict[str, torch.Tensor]
54 ):
55 self.vllm_config = vllm_config
56 self.kv_connector = get_kv_transfer_group()
57 # Register kv caches with KV Connector if applicable.
58 self.kv_connector.register_kv_caches(kv_caches_dict)
59 self.kv_connector.set_host_xfer_buffer_ops(copy_kv_blocks)
60
61 self._pending_load_kwargs: dict[str, Any] | None = None
62 self._disabled = False
63
64 def pre_forward(self, scheduler_output: "SchedulerOutput", **kwargs: Any) -> None:
65 if self._disabled:
66 return
67
68 kv_connector_metadata = scheduler_output.kv_connector_metadata
69 assert kv_connector_metadata is not None
70 self.kv_connector.handle_preemptions(kv_connector_metadata)
71 self.kv_connector.bind_connector_metadata(kv_connector_metadata)
72 self._pending_load_kwargs = kwargs
73
74 if scheduler_output.has_sync_kv_loads:
75 # Sync loads need to run before this step's forward.
76 self._start_load_kv()
77 # Otherwise start the async load after forward to keep submission
78 # off the critical path.
79
80 def _start_load_kv(self) -> None:
81 load_kwargs = self._pending_load_kwargs
82 assert load_kwargs is not None
83 self._pending_load_kwargs = None
84 # TODO: sort out KV Connectors' use of forward_context
85 if is_forward_context_available():
86 self.kv_connector.start_load_kv(get_forward_context(), **load_kwargs)
87 else:
88 with set_forward_context(None, self.vllm_config):
89 self.kv_connector.start_load_kv(get_forward_context(), **load_kwargs)
90
91 def finish_forward(self) -> None:
92 if not self._disabled:
93 self.kv_connector.finish_forward()
94
95 def reset_capture_state(self) -> None:
96 self.kv_connector.reset_capture_state()
97
98 def post_forward(self, finished_req_ids: set[str]) -> KVConnectorOutput | None:
99 if self._disabled:
100 return None
101
102 if self._pending_load_kwargs is not None:
103 self._start_load_kv()
104
105 output = KVConnectorOutput()
106 self.kv_connector.wait_for_save()
107 transfer_results = self.kv_connector.get_transfer_results(finished_req_ids)
108 output.finished_sending = transfer_results.finished_sending or None
109 output.finished_recving = transfer_results.finished_recving or None
110 output.failed_recving = transfer_results.failed_recving
111 output.invalid_block_ids = self.kv_connector.get_block_ids_with_load_errors()
112 output.kv_connector_stats = self.kv_connector.get_kv_connector_stats()
113 output.kv_cache_events = self.kv_connector.get_kv_connector_kv_cache_events()
114 output.kv_connector_worker_meta = (
115 self.kv_connector.build_connector_worker_meta()
116 )
117 self.kv_connector.clear_connector_metadata()
118 return output
119
120 def no_forward(self, scheduler_output: "SchedulerOutput") -> ModelRunnerOutput:
121 if self._disabled:
122 return EMPTY_MODEL_RUNNER_OUTPUT
123
124 self.pre_forward(scheduler_output)
125 self.finish_forward()
126 finished_req_ids = scheduler_output.finished_req_ids
127 kv_connector_output = self.post_forward(finished_req_ids)
128 return ModelRunnerOutput.with_kv_conn_output_only(kv_connector_output)
129
130 def set_disabled(self, disabled: bool) -> None:
131 # Ensure that layer-wise connector hooks aren't called when disabled.
132 kv_transfer_state._KV_CONNECTOR_AGENT = None if disabled else self.kv_connector
133 self._disabled = disabled
134
135
136 NO_OP_KV_CONNECTOR = KVConnector()
137
138
139 def get_kv_connector(
140 vllm_config: VllmConfig, kv_caches_dict: dict[str, torch.Tensor]
141 ) -> KVConnector:
142 if not has_kv_transfer_group():
143 # No-op connector.
144 return NO_OP_KV_CONNECTOR
145
146 return ActiveKVConnector(vllm_config, kv_caches_dict)