main
py 116 lines 3.87 KB
Raw
1 # SPDX-License-Identifier: Apache-2.0
2 # SPDX-FileCopyrightText: Copyright contributors to the vLLM project
3 import uuid
4
5 import vllm.distributed.ec_transfer.ec_connector.cpu.scheduler as sched_mod
6 from tests.v1.ec_connector.unit.utils import create_ec_vllm_config
7 from vllm.distributed.ec_transfer.ec_connector.cpu.ec_shared_region import (
8 ECSharedRegion,
9 )
10 from vllm.distributed.ec_transfer.ec_connector.cpu.scheduler import ECCPUScheduler
11
12 _N, _BS = 16, 64
13
14
15 class _Pos:
16 def __init__(self, offset, length):
17 self.offset, self.length = offset, length
18
19
20 class _Feature:
21 def __init__(self, mm_hash, length=1):
22 self.mm_hash = mm_hash
23 self.identifier = mm_hash
24 self.mm_position = _Pos(0, length)
25 self.data = None
26 self.modality = "image"
27
28
29 class _Request:
30 def __init__(self, features, req_id="r1"):
31 self.mm_features = features
32 self.request_id = req_id
33
34
35 def _sched_gate_off(monkeypatch):
36 def _region(cfg):
37 return ECSharedRegion(
38 engine_id="eng-" + str(uuid.uuid4()), num_blocks=_N, block_size_bytes=_BS
39 )
40
41 monkeypatch.setattr(sched_mod, "create_ec_shared_region", _region)
42 return ECCPUScheduler(create_ec_vllm_config(ec_role="ec_both"))
43
44
45 def test_request_finished_gate_off_returns_none(monkeypatch):
46 s = _sched_gate_off(monkeypatch)
47 assert s.request_finished(_Request([_Feature("h1")])) == (False, None)
48 s.shutdown()
49
50
51 def test_request_finished_producer_emits_params(monkeypatch):
52 s = _sched_gate_off(monkeypatch)
53 # Simulate NIXL-enabled producer bookkeeping without building real NIXL.
54 s._nixl_enabled = True
55 s._peer_host, s._peer_port = "1.2.3.4", 5601
56 # _setup_nixl normally computes these from model_config; set them
57 # directly since this test builds gate-off then flips fields on.
58 s._hidden_dim, s._element_size = 32, 2
59 # feature length=2, hidden_dim=32, element_size=2 -> 128 bytes -> 2 blocks.
60 entry = s._cache.alloc("h1", 2)
61 assert entry is not None
62 s._cache.mark_ready("h1")
63
64 delay, params = s.request_finished(_Request([_Feature("h1", length=2)]))
65 assert delay is False
66 assert entry.evictable
67 assert params == {
68 "h1": {
69 "metadata": {},
70 "item_indices": [0],
71 "peer_host": "1.2.3.4",
72 "peer_port": 5601,
73 "size_bytes": 2 * 32 * 2,
74 }
75 }
76 s.shutdown()
77
78
79 def test_request_finished_announces_not_ready_entry(monkeypatch):
80 """A save whose GPU->mmap copy hasn't been confirmed complete yet is
81 still announced: the entry can't be evicted before it's ready, and a read
82 arriving too early is NACKed NACK_NOT_READY for the consumer to retry —
83 protecting it is not this method's job."""
84 s = _sched_gate_off(monkeypatch)
85 s._nixl_enabled = True
86 s._peer_host, s._peer_port = "1.2.3.4", 5601
87 s._hidden_dim, s._element_size = 32, 2
88 s._cache.alloc("h1", 2) # allocated but not marked ready
89
90 delay, params = s.request_finished(_Request([_Feature("h1", length=2)]))
91 assert delay is False
92 assert params == {
93 "h1": {
94 "metadata": {},
95 "item_indices": [0],
96 "peer_host": "1.2.3.4",
97 "peer_port": 5601,
98 "size_bytes": 2 * 32 * 2,
99 }
100 }
101 s.shutdown()
102
103
104 def test_request_finished_skips_unallocated_entry(monkeypatch):
105 s = _sched_gate_off(monkeypatch)
106 s._nixl_enabled = True
107 s._peer_host, s._peer_port = "1.2.3.4", 5601
108 s._hidden_dim, s._element_size = 32, 2
109 # No alloc() for "h1" at all — e.g. the cache was full at save time.
110
111 delay, params = s.request_finished(_Request([_Feature("h1", length=2)]))
112 assert delay is False
113 # The item's placeholder metadata is still reported even though there's
114 # no cache entry to transfer (empty "metadata": no fields, no transfer).
115 assert params == {"h1": {"metadata": {}, "item_indices": [0]}}
116 s.shutdown()