main
py 132 lines 4.95 KB
Raw
1 # SPDX-License-Identifier: Apache-2.0
2 # SPDX-FileCopyrightText: Copyright contributors to the vLLM project
3
4 import pytest
5
6 from vllm.distributed.kv_transfer.kv_connector.utils import KVOutputAggregator
7 from vllm.v1.outputs import KVConnectorOutput, ModelRunnerOutput
8
9 pytestmark = pytest.mark.cpu_test
10
11
12 class DummyModelRunnerOutput(ModelRunnerOutput):
13 def __init__(
14 self,
15 finished_sending: set[str] | None = None,
16 finished_recving: set[str] | None = None,
17 invalid_block_ids: set[int] | None = None,
18 failed_recving: set[str] | None = None,
19 expected_finished_count: int = 0,
20 ):
21 self.kv_connector_output = KVConnectorOutput(
22 finished_sending=finished_sending,
23 finished_recving=finished_recving,
24 invalid_block_ids=invalid_block_ids or set(),
25 failed_recving=failed_recving or set(),
26 expected_finished_count=expected_finished_count,
27 )
28
29 def __repr__(self):
30 return (
31 f"DummyModelRunnerOutput("
32 f"finished_sending={self.kv_connector_output.finished_sending},"
33 f"finished_recving={self.kv_connector_output.finished_recving})"
34 f"invalid_block_ids={self.kv_connector_output.invalid_block_ids})"
35 )
36
37
38 def test_aggregate_workers_output():
39 aggregator = KVOutputAggregator(expected_finished_count=2)
40
41 output1 = DummyModelRunnerOutput()
42 output2 = DummyModelRunnerOutput()
43
44 aggregated = aggregator.aggregate([output1, output2])
45
46 assert aggregated is output1
47 aggregated = aggregated.kv_connector_output
48 assert aggregated.finished_sending is None
49 assert aggregated.finished_recving is None
50 assert not aggregated.invalid_block_ids
51
52 output1 = DummyModelRunnerOutput(
53 finished_sending={"req1"}, finished_recving={"req2"}
54 )
55 output2 = DummyModelRunnerOutput(invalid_block_ids={1})
56
57 aggregated = aggregator.aggregate([output1, output2])
58
59 assert aggregated is output1
60 aggregated = aggregated.kv_connector_output
61 assert aggregated.finished_sending is None
62 assert aggregated.finished_recving is None
63 assert aggregated.invalid_block_ids == {1}
64
65 output1 = DummyModelRunnerOutput(invalid_block_ids={2})
66 output2 = DummyModelRunnerOutput(finished_sending={"req1"})
67
68 aggregated = aggregator.aggregate([output1, output2])
69
70 assert aggregated is output1
71 aggregated = aggregated.kv_connector_output
72 assert aggregated.finished_sending == {"req1"}
73 assert aggregated.finished_recving is None
74 assert aggregated.invalid_block_ids == {2}
75
76 output1 = DummyModelRunnerOutput(invalid_block_ids={3, 4})
77 output2 = DummyModelRunnerOutput(
78 finished_recving={"req2"},
79 invalid_block_ids={4, 5},
80 failed_recving={"req3"},
81 )
82
83 aggregated = aggregator.aggregate([output1, output2])
84
85 assert aggregated is output1
86 aggregated = aggregated.kv_connector_output
87 assert aggregated.finished_sending is None
88 assert aggregated.finished_recving == {"req2"}
89 assert aggregated.invalid_block_ids == {3, 4, 5}
90 assert not aggregated.failed_recving
91
92 output1 = DummyModelRunnerOutput(finished_recving={"req3"})
93 output2 = DummyModelRunnerOutput(finished_recving={"req3"})
94 aggregated = aggregator.aggregate([output1, output2])
95 assert aggregated.kv_connector_output.failed_recving == {"req3"}
96
97
98 def test_aggregate_workers_output_with_expected_finished_count():
99 # We create the aggregator expecting to collect from 4 workers
100 aggregator = KVOutputAggregator(expected_finished_count=4)
101 assert aggregator._expected_finished_count == 4
102 # Some request with default expected finished requests
103 output1 = DummyModelRunnerOutput(finished_sending={"req1"})
104 aggregated = aggregator.aggregate([output1])
105 # still expecting to collect from 4 workers
106 assert aggregator._send_remaining_count["req1"] == 3
107 assert not aggregated.kv_connector_output.finished_sending
108 assert not aggregated.kv_connector_output.finished_recving
109
110 # Workers discover and find that in this setup they only need to
111 # collect from 2
112 output1 = DummyModelRunnerOutput(
113 finished_sending={"req1"}, expected_finished_count=2
114 )
115 output2 = DummyModelRunnerOutput(
116 finished_recving={"req2"}, expected_finished_count=2
117 )
118 output3 = DummyModelRunnerOutput(finished_recving={"req2"})
119 # Req2 only needs 2 acks
120 aggregated = aggregator.aggregate([output1, output2, output3])
121 assert aggregated.kv_connector_output.expected_finished_count == 2
122
123 assert not aggregated.kv_connector_output.finished_sending
124
125 # Req2 is finished
126 assert "req2" not in aggregator._recv_remaining_count
127 assert aggregated.kv_connector_output.finished_recving == {"req2"}
128
129 # Req1 is still waiting for 2 more acks (expected_finished_count has no effect)
130 # NOTE: This is to showcase dynamic update. Workers are responsible for
131 # ensuring "req1" termination in this case
132 assert aggregator._send_remaining_count["req1"] == 2