main
py 146 lines 5.29 KB
Raw
1 # SPDX-License-Identifier: Apache-2.0
2 # SPDX-FileCopyrightText: Copyright contributors to the vLLM project
3 import torch
4
5 from vllm.multimodal.inputs import (
6 MultiModalBatchedField,
7 MultiModalFeatureSpec,
8 MultiModalFieldElem,
9 MultiModalKwargsItem,
10 PlaceholderRange,
11 )
12 from vllm.multimodal.utils import strip_covered_mm_data
13 from vllm.v1.core.sched.output import NewRequestData
14
15
16 def _create_new_requests_data(prompt_embeds: torch.Tensor | None) -> NewRequestData:
17 return NewRequestData(
18 req_id="test_req",
19 prompt_token_ids=None,
20 mm_features=[],
21 sampling_params=None,
22 pooling_params=None,
23 block_ids=([],),
24 num_computed_tokens=0,
25 lora_request=None,
26 prompt_embeds=prompt_embeds,
27 )
28
29
30 def test_repr_with_none() -> None:
31 """Test repr when prompt_embeds is None."""
32 new_requests_data = _create_new_requests_data(None)
33
34 assert "prompt_embeds_shape=None" in repr(new_requests_data)
35 assert "prompt_embeds_shape=None" in new_requests_data.anon_repr()
36
37
38 def test_repr_with_multi_element_tensor() -> None:
39 """Test repr when prompt_embeds is a multi-element tensor."""
40 prompt_embeds = torch.randn(10, 768)
41 new_requests_data = _create_new_requests_data(prompt_embeds)
42
43 assert "prompt_embeds_shape=torch.Size([10, 768])" in repr(new_requests_data)
44 assert "prompt_embeds_shape=torch.Size([10, 768])" in new_requests_data.anon_repr()
45
46
47 def _mm_feature(offset: int, length: int) -> MultiModalFeatureSpec:
48 return MultiModalFeatureSpec(
49 data=MultiModalKwargsItem.dummy(),
50 mm_position=PlaceholderRange(offset=offset, length=length),
51 identifier=f"hash_{offset}",
52 modality="image",
53 )
54
55
56 def test_strip_covered_mm_data() -> None:
57 """Items fully inside the computed prefix lose their data; items touching
58 the uncomputed region keep it; already-None data stays None."""
59 from dataclasses import replace
60
61 covered = _mm_feature(offset=0, length=100)
62 boundary = _mm_feature(offset=150, length=100) # ends exactly at 250
63 uncovered = _mm_feature(offset=300, length=100)
64 already_none = replace(_mm_feature(offset=100, length=50), data=None)
65
66 stripped = strip_covered_mm_data(
67 [covered, boundary, uncovered, already_none], num_computed_tokens=250
68 )
69
70 assert stripped[0].data is None # fully covered -> stripped
71 assert stripped[1].data is None # span end == computed -> covered -> stripped
72 assert stripped[2].data is not None # extends past prefix -> kept
73 assert stripped[3].data is None # was already None
74 # non-data fields are preserved
75 assert stripped[0].identifier == covered.identifier
76 assert stripped[0].mm_position == covered.mm_position
77 # original list is not mutated
78 assert covered.data is not None
79
80
81 def test_strip_covered_mm_data_zero_computed() -> None:
82 """With no prefix hit nothing is stripped."""
83 features = [_mm_feature(offset=0, length=100)]
84 stripped = strip_covered_mm_data(features, num_computed_tokens=0)
85 assert stripped[0].data is not None
86
87
88 def _mm_feature_mixed(offset: int, length: int) -> MultiModalFeatureSpec:
89 data = MultiModalKwargsItem(
90 {
91 "pixel_values": MultiModalFieldElem(
92 data=torch.empty(4), field=MultiModalBatchedField()
93 ),
94 "image_grid_thw": MultiModalFieldElem(
95 data=torch.ones(1, 3, dtype=torch.long),
96 field=MultiModalBatchedField(keep_on_cpu=True),
97 ),
98 }
99 )
100 return MultiModalFeatureSpec(
101 data=data,
102 mm_position=PlaceholderRange(offset=offset, length=length),
103 identifier=f"hash_{offset}",
104 modality="image",
105 )
106
107
108 def test_strip_covered_mm_data_mrope() -> None:
109 """For M-RoPE models, covered items keep their keep_on_cpu metadata fields
110 (the worker needs them to compute positions); payload fields are dropped."""
111 covered = _mm_feature_mixed(offset=0, length=100)
112 uncovered = _mm_feature_mixed(offset=300, length=100)
113
114 stripped = strip_covered_mm_data(
115 [covered, uncovered], num_computed_tokens=250, uses_mrope=True
116 )
117
118 assert stripped[0].data is not None
119 assert list(stripped[0].data.keys()) == ["image_grid_thw"]
120 assert stripped[1].data is not None
121 assert set(stripped[1].data.keys()) == {"pixel_values", "image_grid_thw"}
122 # original list is not mutated
123 assert set(covered.data.keys()) == {"pixel_values", "image_grid_thw"}
124
125
126 def test_strip_covered_mm_data_shm_address_item() -> None:
127 """SHM address descriptors must survive stripping even when covered: the
128 worker needs the address to resolve the payload and to acknowledge the
129 sender's writer reference. Stripping it crashed the SHM receiver cache on
130 the second identical request (vllm-project/vllm#54994)."""
131 address_item = MultiModalKwargsItem(
132 {
133 "address": MultiModalFieldElem(data=4096, field=MultiModalBatchedField()),
134 "monotonic_id": MultiModalFieldElem(data=7, field=MultiModalBatchedField()),
135 }
136 )
137 feature = MultiModalFeatureSpec(
138 data=address_item,
139 mm_position=PlaceholderRange(offset=0, length=100),
140 identifier="shm_item",
141 modality="image",
142 )
143
144 stripped = strip_covered_mm_data([feature], num_computed_tokens=250)
145
146 assert stripped[0].data is address_item