A high-throughput and memory-efficient inference and serving engine for LLMs
Mirrored from https://github.com/tduncan-lum/Nvidia-Optimization-Stack---VLLM.git
Synced Nov 14, 05:30 UTC
[V1] [P/D] Refactor KV Connector Path (#21980)
Signed-off-by: David Ben-David <davidb@pliops.com> Co-authored-by: David Ben-David <davidb@pliops.com>
David Ben-David committed
Aug 3, 2025 at 14:03 UTC
aefeea0fde0fbe5871a0799fad583e6ed6fdf903
12 files changed
+141
-79
tests/v1/kv_connector/unit/test_output_aggreagator.py
+17
-3
@@ -4,7 +4,7 @@ from concurrent.futures import Future
4
from typing import Optional
5
6
from vllm.distributed.kv_transfer.kv_connector.utils import KVOutputAggregator
7
-from vllm.v1.outputs import ModelRunnerOutput
7
+from vllm.v1.outputs import KVConnectorOutput, ModelRunnerOutput
8
9
10
class DummyModelRunnerOutput(ModelRunnerOutput):
@@ -12,8 +12,16 @@ class DummyModelRunnerOutput(ModelRunnerOutput):
12
def __init__(self,
13
finished_sending: Optional[set[str]] = None,
14
finished_recving: Optional[set[str]] = None):
15
- self.finished_sending = finished_sending
16
- self.finished_recving = finished_recving
15
+ self.kv_connector_output = KVConnectorOutput(
16
+ finished_sending=finished_sending,
17
+ finished_recving=finished_recving,
18
+ )
19
+
20
+ def __repr__(self):
21
+ return (
22
+ f"DummyModelRunnerOutput("
23
+ f"finished_sending={self.kv_connector_output.finished_sending},"
24
+ f"finished_recving={self.kv_connector_output.finished_recving})")
25
26
27
def test_aggregate_workers_output():
@@ -27,6 +35,7 @@ def test_aggregate_workers_output():
35
aggregated = aggregator.aggregate([output1, output2])
36
37
assert aggregated is output1
38
+ aggregated = aggregated.kv_connector_output
39
assert aggregated.finished_sending is None
40
assert aggregated.finished_recving is None
41
@@ -38,6 +47,7 @@ def test_aggregate_workers_output():
47
aggregated = aggregator.aggregate([output1, output2])
48
49
assert aggregated is output1
50
+ aggregated = aggregated.kv_connector_output
51
assert aggregated.finished_sending == {'req1'}
52
assert aggregated.finished_recving is None
53
@@ -49,6 +59,7 @@ def test_aggregate_workers_output():
59
aggregated = aggregator.aggregate([output1, output2])
60
61
assert aggregated is output1
62
+ aggregated = aggregated.kv_connector_output
63
assert aggregated.finished_sending is None
64
assert aggregated.finished_recving == {'req2'}
65
@@ -70,6 +81,7 @@ def test_async_aggregate_workers_output():
81
assert result_future.done()
82
aggregated = result_future.result()
83
assert aggregated is output1
84
+ aggregated = aggregated.kv_connector_output
85
assert aggregated.finished_sending is None
86
assert aggregated.finished_recving is None
87
@@ -87,6 +99,7 @@ def test_async_aggregate_workers_output():
99
assert result_future.done()
100
aggregated = result_future.result()
101
assert aggregated is output1
102
+ aggregated = aggregated.kv_connector_output
103
assert aggregated.finished_sending == {'req1'}
104
assert aggregated.finished_recving is None
105
@@ -104,5 +117,6 @@ def test_async_aggregate_workers_output():
117
assert result_future.done()
118
aggregated = result_future.result()
119
assert aggregated is output1
120
+ aggregated = aggregated.kv_connector_output
121
assert aggregated.finished_sending is None
122
assert aggregated.finished_recving == {'req2'}
tests/v1/kv_connector/unit/test_remote_decode_lifecycle.py
+5
-3
@@ -2,7 +2,7 @@
2
# SPDX-FileCopyrightText: Copyright contributors to the vLLM project
3
import copy
4
5
-from vllm.v1.outputs import EMPTY_MODEL_RUNNER_OUTPUT
5
+from vllm.v1.outputs import EMPTY_MODEL_RUNNER_OUTPUT, KVConnectorOutput
6
from vllm.v1.request import FinishReason, RequestStatus
7
8
from .utils import (assert_scheduler_empty, create_model_runner_output,
@@ -86,7 +86,8 @@ def test_basic_lifecycle():
86
87
# (3b): execute_model()
88
model_runner_output = copy.deepcopy(EMPTY_MODEL_RUNNER_OUTPUT)
89
- model_runner_output.finished_sending = [request_id]
89
+ model_runner_output.kv_connector_output = KVConnectorOutput(
90
+ finished_sending=[request_id])
91
92
# (3c): update_from_output()
93
scheduler.update_from_output(scheduler_output, model_runner_output)
@@ -176,7 +177,8 @@ def test_prefix_cache_lifecycle():
177
scheduler_output = scheduler.schedule()
178
scheduler.schedule()
179
model_runner_output = copy.deepcopy(EMPTY_MODEL_RUNNER_OUTPUT)
179
- model_runner_output.finished_sending = [request_remote.request_id]
180
+ model_runner_output.kv_connector_output = KVConnectorOutput(
181
+ finished_sending=[request_remote.request_id])
182
scheduler.update_from_output(scheduler_output, model_runner_output)
183
_ = scheduler.schedule()
184
assert_scheduler_empty(scheduler)
tests/v1/kv_connector/unit/test_remote_prefill_lifecycle.py
+5
-3
@@ -2,7 +2,7 @@
2
# SPDX-FileCopyrightText: Copyright contributors to the vLLM project
3
import copy
4
5
-from vllm.v1.outputs import EMPTY_MODEL_RUNNER_OUTPUT
5
+from vllm.v1.outputs import EMPTY_MODEL_RUNNER_OUTPUT, KVConnectorOutput
6
from vllm.v1.request import FinishReason, RequestStatus
7
8
from .utils import (assert_scheduler_empty, create_model_runner_output,
@@ -72,7 +72,8 @@ def test_basic_lifecycle():
72
73
# (2b): forward(): request finishes recv.
74
model_runner_output = copy.deepcopy(EMPTY_MODEL_RUNNER_OUTPUT)
75
- model_runner_output.finished_recving = [request_id]
75
+ model_runner_output.kv_connector_output = KVConnectorOutput(
76
+ finished_recving=[request_id])
77
78
# (2c): update_from_output():
79
engine_core_outputs = scheduler.update_from_output(scheduler_output,
@@ -309,7 +310,8 @@ def test_full_block_prompt():
310
# # STEP (2): Recv.
311
scheduler_output = scheduler.schedule()
312
model_runner_output = copy.deepcopy(EMPTY_MODEL_RUNNER_OUTPUT)
312
- model_runner_output.finished_recving = [request_id]
313
+ model_runner_output.kv_connector_output = KVConnectorOutput(
314
+ finished_recving=[request_id])
315
scheduler.update_from_output(scheduler_output, model_runner_output)
316
assert len(scheduler.waiting) == 1
317
assert (request_id in scheduler.finished_recving_kv_req_ids)
tests/v1/kv_connector/unit/utils.py
+5
-3
@@ -17,7 +17,7 @@ from vllm.v1.core.kv_cache_manager import KVCacheBlocks
17
from vllm.v1.core.sched.scheduler import Scheduler
18
from vllm.v1.kv_cache_interface import (FullAttentionSpec, KVCacheConfig,
19
KVCacheGroupSpec)
20
-from vllm.v1.outputs import ModelRunnerOutput
20
+from vllm.v1.outputs import KVConnectorOutput, ModelRunnerOutput
21
from vllm.v1.request import Request
22
from vllm.v1.structured_output import StructuredOutputManager
23
@@ -188,8 +188,10 @@ def create_model_runner_output(
188
logprobs=None,
189
prompt_logprobs_dict={},
190
pooler_output=None,
191
- finished_sending=finished_sending,
192
- finished_recving=finished_recving,
191
+ kv_connector_output=KVConnectorOutput(
192
+ finished_sending=finished_sending,
193
+ finished_recving=finished_recving,
194
+ ),
195
)
196
197
vllm/distributed/kv_transfer/kv_connector/utils.py
+7
-9
@@ -16,7 +16,7 @@ from vllm.config import VllmConfig, get_current_vllm_config
16
from vllm.distributed.kv_transfer.kv_connector.factory import (
17
KVConnectorFactory)
18
from vllm.logger import init_logger
19
-from vllm.v1.outputs import ModelRunnerOutput
19
+from vllm.v1.outputs import KVConnectorOutput, ModelRunnerOutput
20
21
logger = init_logger(__name__)
22
@@ -129,7 +129,7 @@ class KVOutputAggregator:
129
def aggregate(self,
130
outputs: list[ModelRunnerOutput],
131
output_rank: int = 0) -> ModelRunnerOutput:
132
- # aggregate finished_sending, finished_recving from all workers
132
+ # aggregate kv_connector_output from all workers
133
134
def update_finished_set(req_ids: Optional[set[str]],
135
remaining_count_dict: dict[str, int],
@@ -143,6 +143,7 @@ class KVOutputAggregator:
143
finished_sending = set[str]()
144
finished_recving = set[str]()
145
for output in outputs:
146
+ output = output.kv_connector_output
147
update_finished_set(output.finished_sending,
148
self._send_remaining_count, finished_sending)
149
update_finished_set(output.finished_recving,
@@ -151,13 +152,10 @@ class KVOutputAggregator:
152
# select output of the worker specified by output_rank
153
output = outputs[output_rank]
154
154
- # set the aggregated finished_sending / finished_recving
155
- # if output.finished_sending/recving is not empty, but the other ranks
156
- # still have unfinished send/recv, we want to set the aggregated
157
- # finished_sending/recving to None until all ranks have finished
158
- # send/recv
159
- output.finished_sending = finished_sending if finished_sending else None
160
- output.finished_recving = finished_recving if finished_recving else None
155
+ output.kv_connector_output = KVConnectorOutput(
156
+ finished_sending=finished_sending or None,
157
+ finished_recving=finished_recving or None,
158
+ )
159
160
return output
161
vllm/sequence.py
+7
-6
@@ -10,7 +10,7 @@ from collections.abc import Mapping
10
from collections.abc import Sequence as GenericSequence
11
from dataclasses import dataclass, field
12
from functools import reduce
13
-from typing import Any, Callable, Optional, Union
13
+from typing import TYPE_CHECKING, Any, Callable, Optional, Union
14
15
import msgspec
16
import torch
@@ -21,6 +21,10 @@ from vllm.multimodal import MultiModalKwargs, MultiModalPlaceholderDict
21
from vllm.pooling_params import PoolingParams
22
from vllm.sampling_params import RequestOutputKind, SamplingParams
23
24
+if TYPE_CHECKING:
25
+ from vllm.v1.worker.kv_connector_model_runner_mixin import (
26
+ KVConnectorOutput)
27
+
28
VLLM_TOKEN_ID_ARRAY_TYPE = "l"
29
30
VLLM_INVALID_TOKEN_ID = -1
@@ -1159,14 +1163,11 @@ class IntermediateTensors:
1163
states and residuals to be sent to the next stage. This data structure
1164
contains the hidden states and residuals for a request.
1165
1162
- Each stage also needs to handle its own finished_sending and
1163
- finished_recving in case of kv transfer.
1166
+ Each stage also needs to handle its own kv_connector_output.
1167
"""
1168
1169
tensors: dict[str, torch.Tensor]
1167
- # [req_ids]
1168
- finished_sending: Optional[set[str]] = None
1169
- finished_recving: Optional[set[str]] = None
1170
+ kv_connector_output: Optional["KVConnectorOutput"]
1171
1172
def __init__(self, tensors):
1173
# manually define this function, so that
vllm/v1/core/sched/scheduler.py
+7
-5
@@ -30,7 +30,7 @@ from vllm.v1.engine import (EngineCoreEventType, EngineCoreOutput,
30
EngineCoreOutputs)
31
from vllm.v1.kv_cache_interface import KVCacheConfig
32
from vllm.v1.metrics.stats import SchedulerStats
33
-from vllm.v1.outputs import ModelRunnerOutput
33
+from vllm.v1.outputs import KVConnectorOutput, ModelRunnerOutput
34
from vllm.v1.request import Request, RequestStatus
35
from vllm.v1.spec_decode.metrics import SpecDecodingStats
36
from vllm.v1.structured_output import StructuredOutputManager
@@ -884,7 +884,9 @@ class Scheduler(SchedulerInterface):
884
self.waiting.remove_requests(stopped_preempted_reqs)
885
886
# KV Connector: update state for finished KV Transfers.
887
- self._update_from_kv_xfer_finished(model_runner_output)
887
+ if model_runner_output.kv_connector_output:
888
+ self._update_from_kv_xfer_finished(
889
+ model_runner_output.kv_connector_output)
890
891
# Create EngineCoreOutputs for all clients that have requests with
892
# outputs in this step.
@@ -1128,7 +1130,7 @@ class Scheduler(SchedulerInterface):
1130
return True
1131
1132
def _update_from_kv_xfer_finished(self,
1131
- model_runner_output: ModelRunnerOutput):
1133
+ kv_connector_output: KVConnectorOutput):
1134
"""
1135
KV Connector: update the scheduler state based on the output.
1136
@@ -1139,9 +1141,9 @@ class Scheduler(SchedulerInterface):
1141
scheduler the request during the next step.
1142
"""
1143
# KV Connector:: update recv and send status from last step.
1142
- for req_id in (model_runner_output.finished_recving or ()):
1144
+ for req_id in (kv_connector_output.finished_recving or ()):
1145
logger.debug("Finished recving KV transfer for request %s", req_id)
1146
self.finished_recving_kv_req_ids.add(req_id)
1145
- for req_id in (model_runner_output.finished_sending or ()):
1147
+ for req_id in (kv_connector_output.finished_sending or ()):
1148
logger.debug("Finished sending KV transfer for request %s", req_id)
1149
self._free_blocks(self.requests[req_id])
vllm/v1/outputs.py
+8
-5
@@ -71,6 +71,13 @@ class SamplerOutput:
71
logprobs_tensors: Optional[LogprobsTensors]
72
73
74
+@dataclass
75
+class KVConnectorOutput:
76
+ # [req_ids]
77
+ finished_sending: Optional[set[str]] = None
78
+ finished_recving: Optional[set[str]] = None
79
+
80
+
81
# ModelRunnerOutput is serialized and sent to the scheduler process.
82
# This is expensive for torch.Tensor so prefer to use list instead.
83
@dataclass
@@ -104,9 +111,7 @@ class ModelRunnerOutput:
111
# [num_reqs, hidden_size]
112
pooler_output: list[Optional[torch.Tensor]]
113
107
- # [req_ids]
108
- finished_sending: Optional[set[str]] = None
109
- finished_recving: Optional[set[str]] = None
114
+ kv_connector_output: Optional[KVConnectorOutput] = None
115
116
# req_id -> num_nans_in_logits
117
num_nans_in_logits: Optional[dict[str, int]] = None
@@ -119,6 +124,4 @@ EMPTY_MODEL_RUNNER_OUTPUT = ModelRunnerOutput(req_ids=[],
124
logprobs=None,
125
prompt_logprobs_dict={},
126
pooler_output=[],
122
- finished_sending=None,
123
- finished_recving=None,
127
num_nans_in_logits=None)
vllm/v1/worker/gpu_model_runner.py
+9
-19
@@ -69,7 +69,7 @@ from vllm.v1.spec_decode.metadata import SpecDecodeMetadata
69
from vllm.v1.spec_decode.ngram_proposer import NgramProposer
70
from vllm.v1.worker.gpu_input_batch import CachedRequestState, InputBatch
71
from vllm.v1.worker.kv_connector_model_runner_mixin import (
72
- KVConnectorModelRunnerMixin)
72
+ KVConnectorModelRunnerMixin, KVConnectorOutput)
73
from vllm.v1.worker.lora_model_runner_mixin import LoRAModelRunnerMixin
74
75
from ..sample.logits_processor import LogitsProcessorManager
@@ -1423,8 +1423,7 @@ class GPUModelRunner(LoRAModelRunnerMixin, KVConnectorModelRunnerMixin):
1423
hidden_states: torch.Tensor,
1424
num_scheduled_tokens: int,
1425
num_scheduled_tokens_np: np.ndarray,
1426
- finished_sending: Optional[set[str]],
1427
- finished_recving: Optional[set[str]],
1426
+ kv_connector_output: Optional[KVConnectorOutput],
1427
) -> ModelRunnerOutput:
1428
assert self.input_batch.num_reqs ==\
1429
len(self.input_batch.pooling_params), \
@@ -1459,8 +1458,7 @@ class GPUModelRunner(LoRAModelRunnerMixin, KVConnectorModelRunnerMixin):
1458
logprobs=None,
1459
prompt_logprobs_dict={},
1460
pooler_output=pooler_output,
1462
- finished_sending=finished_sending,
1463
- finished_recving=finished_recving,
1461
+ kv_connector_output=kv_connector_output,
1462
)
1463
1464
@torch.inference_mode()
@@ -1564,8 +1562,8 @@ class GPUModelRunner(LoRAModelRunnerMixin, KVConnectorModelRunnerMixin):
1562
num_tokens=num_input_tokens,
1563
num_tokens_across_dp=num_tokens_across_dp,
1564
skip_cuda_graphs=skip_cuda_graphs,
1567
- ):
1568
- self.maybe_setup_kv_connector(scheduler_output)
1565
+ ), self.maybe_get_kv_connector_output(
1566
+ scheduler_output) as kv_connector_output:
1567
1568
model_output = self.model(
1569
input_ids=input_ids,
@@ -1578,10 +1576,6 @@ class GPUModelRunner(LoRAModelRunnerMixin, KVConnectorModelRunnerMixin):
1576
),
1577
)
1578
1581
- self.maybe_wait_for_kv_save()
1582
- finished_sending, finished_recving = (
1583
- self.get_finished_kv_transfers(scheduler_output))
1584
-
1579
if self.use_aux_hidden_state_outputs:
1580
hidden_states, aux_hidden_states = model_output
1581
else:
@@ -1597,20 +1591,17 @@ class GPUModelRunner(LoRAModelRunnerMixin, KVConnectorModelRunnerMixin):
1591
== "external_launcher" and len(get_pp_group().ranks) > 0
1592
if not get_pp_group().is_last_rank:
1593
# For mid-pipeline stages, return the hidden states.
1594
+ assert isinstance(hidden_states, IntermediateTensors)
1595
if not broadcast_pp_output:
1601
- if finished_sending or finished_recving:
1602
- hidden_states.finished_sending = finished_sending
1603
- hidden_states.finished_recving = finished_recving
1596
+ hidden_states.kv_connector_output = kv_connector_output
1597
return hidden_states
1605
- assert isinstance(hidden_states, IntermediateTensors)
1598
get_pp_group().send_tensor_dict(hidden_states.tensors,
1599
all_gather_group=get_tp_group())
1600
logits = None
1601
else:
1602
if self.input_batch.pooling_params:
1603
return self._pool(hidden_states, num_scheduled_tokens,
1612
- num_scheduled_tokens_np, finished_sending,
1613
- finished_recving)
1604
+ num_scheduled_tokens_np, kv_connector_output)
1605
1606
sample_hidden_states = hidden_states[logits_indices]
1607
logits = self.model.compute_logits(sample_hidden_states, None)
@@ -1760,8 +1751,7 @@ class GPUModelRunner(LoRAModelRunnerMixin, KVConnectorModelRunnerMixin):
1751
logprobs=logprobs_lists,
1752
prompt_logprobs_dict=prompt_logprobs_dict,
1753
pooler_output=[],
1763
- finished_sending=finished_sending,
1764
- finished_recving=finished_recving,
1754
+ kv_connector_output=kv_connector_output,
1755
num_nans_in_logits=num_nans_in_logits,
1756
)
1757
vllm/v1/worker/gpu_worker.py
+12
-10
@@ -16,8 +16,7 @@ from vllm.config import VllmConfig
16
from vllm.distributed import (ensure_model_parallel_initialized,
17
init_distributed_environment,
18
set_custom_all_reduce)
19
-from vllm.distributed.kv_transfer import (ensure_kv_transfer_initialized,
20
- has_kv_transfer_group)
19
+from vllm.distributed.kv_transfer import ensure_kv_transfer_initialized
20
from vllm.distributed.parallel_state import get_pp_group, get_tp_group
21
from vllm.logger import init_logger
22
from vllm.lora.request import LoRARequest
@@ -369,17 +368,20 @@ class Worker(WorkerBase):
368
assert isinstance(output, IntermediateTensors)
369
get_pp_group().send_tensor_dict(output.tensors,
370
all_gather_group=get_tp_group())
372
- if not has_kv_transfer_group():
371
+
372
+ kv_connector_output = output.kv_connector_output
373
+ if not kv_connector_output:
374
return None
375
376
# In case of PP with kv transfer, we need to pass through the
376
- # finished_sending and finished_recving buffers.
377
- new_output = EMPTY_MODEL_RUNNER_OUTPUT
378
- if output.finished_sending or output.finished_recving:
379
- new_output = copy.copy(new_output)
380
- new_output.finished_sending = output.finished_sending
381
- new_output.finished_recving = output.finished_recving
382
- output = new_output
377
+ # kv_connector_output
378
+ if (not kv_connector_output.finished_sending
379
+ and not kv_connector_output.finished_recving):
380
+ return EMPTY_MODEL_RUNNER_OUTPUT
381
+
382
+ output = copy.copy(EMPTY_MODEL_RUNNER_OUTPUT)
383
+ output.kv_connector_output = kv_connector_output
384
+ return output
385
386
assert isinstance(output, ModelRunnerOutput)
387
return output
vllm/v1/worker/kv_connector_model_runner_mixin.py
+54
-9
@@ -4,6 +4,8 @@
4
Define KV connector functionality mixin for model runners.
5
"""
6
import copy
7
+from contextlib import AbstractContextManager, contextmanager, nullcontext
8
+from typing import Generator # noqa: UP035
9
from typing import TYPE_CHECKING, Optional
10
11
from vllm.config import VllmConfig
@@ -12,7 +14,8 @@ from vllm.distributed.kv_transfer import (get_kv_transfer_group,
14
from vllm.distributed.kv_transfer.kv_connector.v1 import KVConnectorBase_V1
15
from vllm.forward_context import get_forward_context, set_forward_context
16
from vllm.logger import init_logger
15
-from vllm.v1.outputs import EMPTY_MODEL_RUNNER_OUTPUT, ModelRunnerOutput
17
+from vllm.v1.outputs import (EMPTY_MODEL_RUNNER_OUTPUT, KVConnectorOutput,
18
+ ModelRunnerOutput)
19
20
if TYPE_CHECKING:
21
from vllm.v1.core.sched.output import SchedulerOutput
@@ -53,18 +56,60 @@ class KVConnectorModelRunnerMixin:
56
scheduler_output.finished_req_ids)
57
return None, None
58
56
- def kv_connector_no_forward(self, scheduler_output: "SchedulerOutput",
59
+ @staticmethod
60
+ def kv_connector_no_forward(scheduler_output: "SchedulerOutput",
61
vllm_config: VllmConfig) -> ModelRunnerOutput:
62
# KV send/recv even if no work to do.
59
- with set_forward_context(None, vllm_config):
60
- self.maybe_setup_kv_connector(scheduler_output)
61
- finished_sending, finished_recving = (
62
- self.get_finished_kv_transfers(scheduler_output))
63
+ with set_forward_context(
64
+ None, vllm_config
65
+ ), KVConnectorModelRunnerMixin._get_kv_connector_output(
66
+ scheduler_output, wait_for_save=False) as kv_connector_output:
67
+ pass
68
64
- if not finished_sending and not finished_recving:
69
+ if (not kv_connector_output.finished_sending
70
+ and not kv_connector_output.finished_recving):
71
return EMPTY_MODEL_RUNNER_OUTPUT
72
73
output = copy.copy(EMPTY_MODEL_RUNNER_OUTPUT)
68
- output.finished_sending = finished_sending
69
- output.finished_recving = finished_recving
74
+ output.kv_connector_output = kv_connector_output
75
return output
76
+
77
+ @staticmethod
78
+ def maybe_get_kv_connector_output(
79
+ scheduler_output: "SchedulerOutput"
80
+ ) -> AbstractContextManager[Optional[KVConnectorOutput]]:
81
+ return KVConnectorModelRunnerMixin._get_kv_connector_output(
82
+ scheduler_output) if has_kv_transfer_group() else nullcontext()
83
+
84
+ # This context manager must be used within an active forward context.
85
+ # It encapsulates the entire KV conector lifecycle within execute_model
86
+ @staticmethod
87
+ @contextmanager
88
+ def _get_kv_connector_output(
89
+ scheduler_output: "SchedulerOutput",
90
+ wait_for_save: bool = True
91
+ ) -> Generator[KVConnectorOutput, None, None]:
92
+ output = KVConnectorOutput()
93
+
94
+ # Update KVConnector with the KVConnector metadata forward().
95
+ kv_connector = get_kv_transfer_group()
96
+ assert isinstance(kv_connector, KVConnectorBase_V1)
97
+ assert scheduler_output.kv_connector_metadata is not None
98
+ kv_connector.bind_connector_metadata(
99
+ scheduler_output.kv_connector_metadata)
100
+
101
+ # Background KV cache transfers happen here.
102
+ # These transfers are designed to be async and the requests
103
+ # involved may be disjoint from the running requests.
104
+ # Do this here to save a collective_rpc.
105
+ kv_connector.start_load_kv(get_forward_context())
106
+ try:
107
+ yield output
108
+ finally:
109
+ if wait_for_save:
110
+ kv_connector.wait_for_save()
111
+
112
+ output.finished_sending, output.finished_recving = (
113
+ kv_connector.get_finished(scheduler_output.finished_req_ids))
114
+
115
+ kv_connector.clear_connector_metadata()
vllm/v1/worker/tpu_model_runner.py
+5
-4
@@ -51,7 +51,7 @@ from vllm.v1.outputs import (EMPTY_MODEL_RUNNER_OUTPUT, LogprobsLists,
51
from vllm.v1.sample.tpu.metadata import TPUSupportedSamplingMetadata
52
from vllm.v1.sample.tpu.sampler import Sampler as TPUSampler
53
from vllm.v1.worker.kv_connector_model_runner_mixin import (
54
- KVConnectorModelRunnerMixin)
54
+ KVConnectorModelRunnerMixin, KVConnectorOutput)
55
from vllm.v1.worker.lora_model_runner_mixin import LoRAModelRunnerMixin
56
from vllm.v1.worker.tpu_input_batch import CachedRequestState, InputBatch
57
@@ -1175,9 +1175,10 @@ class TPUModelRunner(LoRAModelRunnerMixin, KVConnectorModelRunnerMixin):
1175
logprobs=logprobs_lists,
1176
prompt_logprobs_dict=prompt_logprobs_dict,
1177
pooler_output=[],
1178
- finished_sending=finished_sending,
1179
- finished_recving=finished_recving,
1180
- )
1178
+ kv_connector_output=KVConnectorOutput(
1179
+ finished_sending=finished_sending,
1180
+ finished_recving=finished_recving,
1181
+ ))
1182
1183
# Check there are no new graphs compiled - all the graphs should be
1184
# captured and compiled during warm up.