main
py 225 lines 6.96 KB
Raw
1 # SPDX-License-Identifier: Apache-2.0
2 # SPDX-FileCopyrightText: Copyright contributors to the vLLM project
3 import json
4 from dataclasses import asdict
5 from typing import TYPE_CHECKING, Any
6
7 import pytest
8 import torch
9
10 from vllm import SamplingParams
11 from vllm.logprobs import Logprob, SampleLogprobs
12 from vllm.model_executor.models.pixtral import _make_packed_sequence_metadata
13 from vllm.platforms import current_platform
14 from vllm.v1.attention.backends.registry import AttentionBackendEnum
15
16 from ....utils import VLLM_PATH, large_gpu_test
17 from ...utils import check_logprobs_close
18
19 if TYPE_CHECKING:
20 from _typeshed import StrPath
21
22 PIXTRAL_ID = "mistralai/Pixtral-12B-2409"
23 MISTRAL_SMALL_3_1_ID = "mistralai/Mistral-Small-3.1-24B-Instruct-2503"
24 MINISTRAL_3B_ID = "mistralai/Ministral-3-3B-Instruct-2512"
25
26 MODELS = [PIXTRAL_ID, MISTRAL_SMALL_3_1_ID]
27
28 IMG_URLS = [
29 "237-400x300.jpg", # "https://huggingface.co/datasets/Isotr0py/mistral-test-images/resolve/main/237-400x300.jpg",
30 "231-200x300.jpg", # "https://huggingface.co/datasets/Isotr0py/mistral-test-images/resolve/main/237-400x300.jpg",
31 "27-500x500.jpg", # "https://huggingface.co/datasets/Isotr0py/mistral-test-images/resolve/main/237-400x300.jpg",
32 "17-150x600.jpg", # "https://huggingface.co/datasets/Isotr0py/mistral-test-images/resolve/main/237-400x300.jpg",
33 ]
34 PROMPT = "Describe each image in one short sentence."
35
36
37 def _create_msg_format(urls: list[str]) -> list[dict[str, Any]]:
38 return [
39 {
40 "role": "user",
41 "content": [
42 {
43 "type": "text",
44 "text": PROMPT,
45 }
46 ]
47 + [{"type": "image_url", "image_url": {"url": url}} for url in urls],
48 }
49 ]
50
51
52 SAMPLING_PARAMS = SamplingParams(max_tokens=512, temperature=0.0, logprobs=5)
53 LIMIT_MM_PER_PROMPT = dict(image=4)
54
55 MAX_MODEL_LEN = [8192, 65536]
56
57 FIXTURES_PATH = VLLM_PATH / "tests/models/fixtures"
58 assert FIXTURES_PATH.exists()
59
60 FIXTURE_LOGPROBS_CHAT = {
61 PIXTRAL_ID: FIXTURES_PATH / "pixtral_chat.json",
62 MISTRAL_SMALL_3_1_ID: FIXTURES_PATH / "mistral_small_3_chat.json",
63 MINISTRAL_3B_ID: FIXTURES_PATH / "ministral_3b_chat.json",
64 }
65
66 OutputsLogprobs = list[tuple[list[int], str, SampleLogprobs | None]]
67
68
69 @pytest.mark.parametrize(
70 "backend",
71 [
72 AttentionBackendEnum.FLASH_ATTN,
73 AttentionBackendEnum.FLASHINFER,
74 AttentionBackendEnum.TORCH_SDPA,
75 ],
76 )
77 def test_packed_sequence_metadata(backend: AttentionBackendEnum) -> None:
78 cu_seqlens, max_seqlen, sequence_lengths = _make_packed_sequence_metadata(
79 [4, 6],
80 backend,
81 hidden_size=64,
82 tp_size=1,
83 device=torch.device("cpu"),
84 )
85
86 assert cu_seqlens.dtype == torch.int32
87 assert max_seqlen.dtype == torch.int32
88 if backend == AttentionBackendEnum.FLASHINFER:
89 assert max_seqlen.item() >= 6
90 assert sequence_lengths is not None
91 assert sequence_lengths.dtype == torch.int32
92 assert len(cu_seqlens) % 2 == 0
93 else:
94 expected_max_seqlen = 6 if backend == AttentionBackendEnum.FLASH_ATTN else 0
95 assert max_seqlen.item() == expected_max_seqlen
96 assert cu_seqlens.tolist() == [0, 4, 10]
97 assert sequence_lengths is None
98
99
100 # For the test author to store golden output in JSON
101 def _dump_outputs_w_logprobs(
102 outputs: OutputsLogprobs,
103 filename: "StrPath",
104 ) -> None:
105 json_data = [
106 (
107 tokens,
108 text,
109 [
110 {k: asdict(v) for k, v in token_logprobs.items()}
111 for token_logprobs in (logprobs or [])
112 ],
113 )
114 for tokens, text, logprobs in outputs
115 ]
116
117 with open(filename, "w") as f:
118 json.dump(json_data, f)
119
120
121 def load_outputs_w_logprobs(filename: "StrPath") -> OutputsLogprobs:
122 with open(filename, "rb") as f:
123 json_data = json.load(f)
124
125 return [
126 (
127 tokens,
128 text,
129 [
130 {int(k): Logprob(**v) for k, v in token_logprobs.items()}
131 for token_logprobs in logprobs
132 ],
133 )
134 for tokens, text, logprobs in json_data
135 ]
136
137
138 @large_gpu_test(min_gb=80)
139 @pytest.mark.parametrize("model", MODELS)
140 @pytest.mark.parametrize("max_model_len", MAX_MODEL_LEN)
141 @pytest.mark.parametrize("dtype", ["bfloat16"])
142 def test_chat(
143 vllm_runner, max_model_len: int, model: str, dtype: str, local_asset_server
144 ) -> None:
145 if (
146 model == MISTRAL_SMALL_3_1_ID
147 and max_model_len == 65536
148 and current_platform.is_rocm()
149 ):
150 pytest.skip(
151 "OOM on ROCm: 24B model with 65536 context length exceeds GPU memory"
152 )
153
154 EXPECTED_CHAT_LOGPROBS = load_outputs_w_logprobs(FIXTURE_LOGPROBS_CHAT[model])
155 with vllm_runner(
156 model,
157 dtype=dtype,
158 tokenizer_mode="mistral",
159 load_format="mistral",
160 config_format="mistral",
161 max_model_len=max_model_len,
162 limit_mm_per_prompt=LIMIT_MM_PER_PROMPT,
163 ) as vllm_model:
164 outputs = []
165
166 urls_all = [local_asset_server.url_for(u) for u in IMG_URLS]
167 msgs = [
168 _create_msg_format(urls_all[:1]),
169 _create_msg_format(urls_all[:2]),
170 _create_msg_format(urls_all),
171 ]
172 for msg in msgs:
173 output = vllm_model.llm.chat(msg, sampling_params=SAMPLING_PARAMS)
174
175 outputs.extend(output)
176
177 logprobs = vllm_runner._final_steps_generate_w_logprobs(outputs)
178 # Remove last `None` prompt_logprobs to compare with fixture
179 for i in range(len(logprobs)):
180 assert logprobs[i][-1] is None
181 logprobs[i] = logprobs[i][:-1]
182 check_logprobs_close(
183 outputs_0_lst=EXPECTED_CHAT_LOGPROBS,
184 outputs_1_lst=logprobs,
185 name_0="h100_ref",
186 name_1="output",
187 )
188
189
190 @large_gpu_test(min_gb=16)
191 @pytest.mark.parametrize("dtype", ["bfloat16"])
192 def test_chat_consolidated(vllm_runner, dtype: str, local_asset_server) -> None:
193 EXPECTED_CHAT_LOGPROBS = load_outputs_w_logprobs(
194 FIXTURE_LOGPROBS_CHAT[MINISTRAL_3B_ID]
195 )
196 with vllm_runner(
197 MINISTRAL_3B_ID,
198 dtype=dtype,
199 tokenizer_mode="mistral",
200 load_format="mistral",
201 config_format="mistral",
202 max_model_len=8192,
203 limit_mm_per_prompt=LIMIT_MM_PER_PROMPT,
204 ) as vllm_model:
205 outputs = []
206 urls_all = [local_asset_server.url_for(u) for u in IMG_URLS]
207 msgs = [
208 _create_msg_format(urls_all[:1]),
209 _create_msg_format(urls_all[:2]),
210 _create_msg_format(urls_all),
211 ]
212 for msg in msgs:
213 output = vllm_model.llm.chat(msg, sampling_params=SAMPLING_PARAMS)
214 outputs.extend(output)
215
216 logprobs = vllm_runner._final_steps_generate_w_logprobs(outputs)
217 for i in range(len(logprobs)):
218 assert logprobs[i][-1] is None
219 logprobs[i] = logprobs[i][:-1]
220 check_logprobs_close(
221 outputs_0_lst=EXPECTED_CHAT_LOGPROBS,
222 outputs_1_lst=logprobs,
223 name_0="h100_ref",
224 name_1="output",
225 )