main
py 107 lines 4.29 KB
Raw
1 # SPDX-License-Identifier: Apache-2.0
2 # SPDX-FileCopyrightText: Copyright contributors to the vLLM project
3 """Define KV connector functionality mixin for model runners."""
4
5 from collections.abc import Generator
6 from contextlib import AbstractContextManager, contextmanager, nullcontext
7 from typing import TYPE_CHECKING
8
9 from vllm.config import VllmConfig
10 from vllm.distributed.kv_transfer import get_kv_transfer_group, has_kv_transfer_group
11 from vllm.distributed.kv_transfer.kv_connector.base import KVConnectorBase
12 from vllm.forward_context import get_forward_context, set_forward_context
13 from vllm.v1.outputs import (
14 KVConnectorOutput,
15 ModelRunnerOutput,
16 )
17
18 if TYPE_CHECKING:
19 from vllm.v1.core.sched.output import SchedulerOutput
20
21
22 # Defined as a kv connector functionality mixin for ModelRunner (GPU, TPU)
23 class KVConnectorModelRunnerMixin:
24 @staticmethod
25 def kv_connector_no_forward(
26 scheduler_output: "SchedulerOutput", vllm_config: VllmConfig
27 ) -> ModelRunnerOutput:
28 # KV send/recv even if no work to do.
29 with (
30 set_forward_context(None, vllm_config),
31 KVConnectorModelRunnerMixin._get_kv_connector_output(
32 scheduler_output
33 ) as kv_connector_output,
34 ):
35 pass
36
37 return ModelRunnerOutput.with_kv_conn_output_only(kv_connector_output)
38
39 @staticmethod
40 def maybe_get_kv_connector_output(
41 scheduler_output: "SchedulerOutput",
42 defer_finalize: bool = False,
43 ) -> AbstractContextManager[KVConnectorOutput | None]:
44 return (
45 KVConnectorModelRunnerMixin._get_kv_connector_output(
46 scheduler_output, defer_finalize=defer_finalize
47 )
48 if has_kv_transfer_group()
49 else nullcontext()
50 )
51
52 @staticmethod
53 def finalize_kv_connector() -> None:
54 """Finalize the KV connector: wait_for_save and clear metadata.
55
56 Call after draft model forward when defer_finalize=True was used.
57 """
58 if has_kv_transfer_group():
59 kv_connector = get_kv_transfer_group()
60 kv_connector.wait_for_save()
61 kv_connector.clear_connector_metadata()
62
63 # This context manager must be used within an active forward context.
64 # It encapsulates the entire KV connector lifecycle within execute_model
65 @staticmethod
66 @contextmanager
67 def _get_kv_connector_output(
68 scheduler_output: "SchedulerOutput",
69 defer_finalize: bool = False,
70 ) -> Generator[KVConnectorOutput, None, None]:
71 output = KVConnectorOutput()
72
73 # Update KVConnector with the KVConnector metadata forward().
74 kv_connector = get_kv_transfer_group()
75 assert isinstance(kv_connector, KVConnectorBase)
76 assert scheduler_output.kv_connector_metadata is not None
77 kv_connector.bind_connector_metadata(scheduler_output.kv_connector_metadata)
78
79 # Start this step's KV loads, ordered after any in-flight KV block
80 # zeroing. Sync loads feed this step's forward so must precede it;
81 # otherwise start (async) loads after the forward launch, keeping
82 # their host-side submission cost off the critical path.
83 start_after_forward = not scheduler_output.has_sync_kv_loads
84 if not start_after_forward:
85 kv_connector.start_load_kv(get_forward_context())
86 try:
87 yield output
88 finally:
89 if start_after_forward:
90 kv_connector.start_load_kv(get_forward_context())
91 if not defer_finalize:
92 kv_connector.wait_for_save()
93
94 transfer_results = kv_connector.get_transfer_results(
95 scheduler_output.finished_req_ids
96 )
97 output.finished_sending = transfer_results.finished_sending
98 output.finished_recving = transfer_results.finished_recving
99 output.failed_recving = transfer_results.failed_recving
100 output.invalid_block_ids = kv_connector.get_block_ids_with_load_errors()
101
102 output.kv_connector_stats = kv_connector.get_kv_connector_stats()
103 output.kv_cache_events = kv_connector.get_kv_connector_kv_cache_events()
104 output.kv_connector_worker_meta = kv_connector.build_connector_worker_meta()
105
106 if not defer_finalize:
107 kv_connector.clear_connector_metadata()