main
py 147 lines 4.33 KB
Raw
1 # SPDX-License-Identifier: Apache-2.0
2 # SPDX-FileCopyrightText: Copyright contributors to the vLLM project
3
4 """Multimodal tests for the /render endpoints that expose prompt preprocessing."""
5
6 import httpx
7 import pytest
8 import pytest_asyncio
9
10 from tests.utils import RemoteOpenAIServer
11 from vllm.multimodal.utils import encode_image_url
12
13 VISION_MODEL_NAME = "Qwen/Qwen3-VL-2B-Instruct"
14
15
16 @pytest.fixture(scope="module")
17 def vision_server():
18 """Vision-capable server used for multimodal /render tests."""
19 args = [
20 "--enforce-eager",
21 "--max-model-len",
22 "100",
23 "--max-num-seqs",
24 "1",
25 "--limit-mm-per-prompt.image",
26 "1",
27 "--limit-mm-per-prompt.video",
28 "0",
29 "--enable-scale-out",
30 ]
31
32 with RemoteOpenAIServer(VISION_MODEL_NAME, args) as remote_server:
33 yield remote_server
34
35
36 @pytest_asyncio.fixture
37 async def vision_client(vision_server):
38 async with httpx.AsyncClient(
39 base_url=vision_server.url_for(""), timeout=60.0
40 ) as http_client:
41 yield http_client
42
43
44 @pytest.mark.asyncio
45 async def test_chat_completion_render_with_base64_image_url(
46 vision_client,
47 local_asset_server,
48 ):
49 """Render a multimodal chat request and verify tokens are returned."""
50 image = local_asset_server.get_image_asset("RGBA_comp.png")
51 data_url = encode_image_url(image, format="PNG")
52
53 assert data_url.startswith("data:image/")
54 assert ";base64," in data_url
55
56 response = await vision_client.post(
57 "/v1/chat/completions/render",
58 json={
59 "model": VISION_MODEL_NAME,
60 "messages": [
61 {
62 "role": "user",
63 "content": [
64 {"type": "image_url", "image_url": {"url": data_url}},
65 {"type": "text", "text": "What's in this image?"},
66 ],
67 }
68 ],
69 },
70 )
71
72 assert response.status_code == 200
73
74 data = response.json()
75 assert isinstance(data, dict)
76 assert "token_ids" in data
77 assert isinstance(data["token_ids"], list)
78 assert len(data["token_ids"]) > 0
79
80 # Verify multimodal features are populated
81 assert "features" in data
82 features = data["features"]
83 assert features is not None
84
85 # mm_hashes: should have an "image" key with a list of hash strings
86 assert "mm_hashes" in features
87 assert "image" in features["mm_hashes"]
88 image_hashes = features["mm_hashes"]["image"]
89 assert isinstance(image_hashes, list)
90 assert len(image_hashes) > 0
91 assert all(isinstance(h, str) for h in image_hashes)
92
93 # mm_placeholders: should have an "image" key with offset/length dicts
94 assert "mm_placeholders" in features
95 assert "image" in features["mm_placeholders"]
96 image_placeholders = features["mm_placeholders"]["image"]
97 assert isinstance(image_placeholders, list)
98 assert len(image_placeholders) > 0
99 for p in image_placeholders:
100 assert "offset" in p
101 assert "length" in p
102 assert isinstance(p["offset"], int)
103 assert isinstance(p["length"], int)
104 assert p["length"] > 0
105
106
107 @pytest.mark.asyncio
108 async def test_tokenize_matches_render_for_multimodal_input(
109 vision_client,
110 local_asset_server,
111 ):
112 """`/tokenize` should match `/v1/chat/completions/render` token output."""
113 image = local_asset_server.get_image_asset("RGBA_comp.png")
114 data_url = encode_image_url(image, format="PNG")
115
116 messages = [
117 {
118 "role": "user",
119 "content": [
120 {"type": "image_url", "image_url": {"url": data_url}},
121 {"type": "text", "text": "What's in this image?"},
122 ],
123 }
124 ]
125
126 render_response = await vision_client.post(
127 "/v1/chat/completions/render",
128 json={
129 "model": VISION_MODEL_NAME,
130 "messages": messages,
131 },
132 )
133 assert render_response.status_code == 200
134 render_data = render_response.json()
135
136 tokenize_response = await vision_client.post(
137 "/tokenize",
138 json={
139 "model": VISION_MODEL_NAME,
140 "messages": messages,
141 },
142 )
143 assert tokenize_response.status_code == 200
144 tokenize_data = tokenize_response.json()
145
146 assert tokenize_data["tokens"] == render_data["token_ids"]
147 assert tokenize_data["count"] == len(render_data["token_ids"])