main
py 489 lines 18 KB
Raw
1 # SPDX-License-Identifier: Apache-2.0
2 # SPDX-FileCopyrightText: Copyright contributors to the vLLM project
3 """Unit tests for sequence and token pooler head classes."""
4
5 import torch
6 import torch.nn as nn
7
8 from vllm.model_executor.layers.pooler.activations import PoolerNormalize
9 from vllm.model_executor.layers.pooler.seqwise.heads import (
10 ClassifierPoolerHead,
11 EmbeddingPoolerHead,
12 )
13 from vllm.model_executor.layers.pooler.tokwise.heads import (
14 TokenClassifierPoolerHead,
15 TokenEmbeddingPoolerHead,
16 )
17 from vllm.pooling_params import PoolingParams
18 from vllm.tasks import PoolingTask
19 from vllm.v1.pool.metadata import PoolingMetadata, PoolingStates
20
21 _HIDDEN = 16
22 _BATCH = 3
23
24
25 def _make_params(
26 n: int,
27 *,
28 task: PoolingTask = "embed",
29 dimensions: int | None = None,
30 use_activation: bool | None = None,
31 ) -> list[PoolingParams]:
32 return [
33 PoolingParams(task=task, dimensions=dimensions, use_activation=use_activation)
34 for _ in range(n)
35 ]
36
37
38 def _make_metadata(pooling_params: list[PoolingParams]) -> PoolingMetadata:
39 n = len(pooling_params)
40 return PoolingMetadata(
41 prompt_lens=torch.ones(n, dtype=torch.long),
42 prompt_token_ids=None,
43 prompt_token_ids_cpu=None,
44 pooling_params=pooling_params,
45 pooling_states=[PoolingStates() for _ in range(n)],
46 )
47
48
49 def _linear(in_f: int, out_f: int) -> nn.Linear:
50 torch.manual_seed(42)
51 return nn.Linear(in_f, out_f, bias=False)
52
53
54 # ---------------------------------------------------------------------------
55 # EmbeddingPoolerHead
56 # ---------------------------------------------------------------------------
57 class TestEmbeddingPoolerHead:
58 def test_supported_tasks(self):
59 head = EmbeddingPoolerHead()
60 assert head.get_supported_tasks() == {"embed"}
61
62 def test_passthrough(self):
63 head = EmbeddingPoolerHead()
64 x = torch.randn(_BATCH, _HIDDEN)
65 meta = _make_metadata(_make_params(_BATCH))
66 out = head(x, meta)
67 assert torch.equal(out, x)
68
69 def test_head_dtype(self):
70 head = EmbeddingPoolerHead(head_dtype=torch.float16)
71 x = torch.randn(_BATCH, _HIDDEN)
72 meta = _make_metadata(_make_params(_BATCH))
73 out = head(x, meta)
74 assert out.dtype == torch.float16
75
76 def test_projector(self):
77 proj = _linear(_HIDDEN, 8)
78 head = EmbeddingPoolerHead(projector=proj)
79 x = torch.randn(_BATCH, _HIDDEN)
80 meta = _make_metadata(_make_params(_BATCH))
81 out = head(x, meta)
82 assert out.shape == (_BATCH, 8)
83 assert torch.allclose(out, proj(x))
84
85 def test_matryoshka_uniform(self):
86 head = EmbeddingPoolerHead()
87 x = torch.randn(_BATCH, _HIDDEN)
88 params = _make_params(_BATCH, dimensions=4)
89 meta = _make_metadata(params)
90 out = head(x, meta)
91 assert out.shape == (_BATCH, 4)
92 assert torch.equal(out, x[..., :4])
93
94 def test_matryoshka_mixed(self):
95 head = EmbeddingPoolerHead()
96 x = torch.randn(2, _HIDDEN)
97 params = [
98 PoolingParams(task="embed", dimensions=4),
99 PoolingParams(task="embed", dimensions=8),
100 ]
101 meta = _make_metadata(params)
102 out = head(x, meta)
103 assert isinstance(out, list)
104 assert len(out) == 2
105 assert out[0].shape[-1] == 4
106 assert out[1].shape[-1] == 8
107
108 def test_matryoshka_mixed_with_none(self):
109 head = EmbeddingPoolerHead()
110 x = torch.randn(2, _HIDDEN)
111 params = [
112 PoolingParams(task="embed", dimensions=4),
113 PoolingParams(task="embed", dimensions=None),
114 ]
115 meta = _make_metadata(params)
116 out = head(x, meta)
117 assert isinstance(out, list)
118 assert out[0].shape[-1] == 4
119 assert torch.equal(out[1], x[1])
120
121 def test_activation_uniform_true(self):
122 head = EmbeddingPoolerHead(activation=PoolerNormalize())
123 x = torch.randn(_BATCH, _HIDDEN)
124 params = _make_params(_BATCH, use_activation=True)
125 meta = _make_metadata(params)
126 out = head(x, meta)
127 norms = torch.linalg.norm(out, dim=-1)
128 assert torch.allclose(norms, torch.ones(_BATCH), atol=1e-5)
129
130 def test_activation_uniform_false(self):
131 head = EmbeddingPoolerHead(activation=PoolerNormalize())
132 x = torch.randn(_BATCH, _HIDDEN)
133 params = _make_params(_BATCH, use_activation=False)
134 meta = _make_metadata(params)
135 out = head(x, meta)
136 assert torch.equal(out, x)
137
138 def test_activation_mixed_flags(self):
139 head = EmbeddingPoolerHead(activation=PoolerNormalize())
140 x = torch.randn(2, _HIDDEN)
141 params = [
142 PoolingParams(task="embed", use_activation=True),
143 PoolingParams(task="embed", use_activation=False),
144 ]
145 meta = _make_metadata(params)
146 out = head(x, meta)
147 assert isinstance(out, list)
148 norm_0 = torch.linalg.norm(out[0], dim=-1)
149 assert torch.allclose(norm_0, torch.ones(1), atol=1e-5)
150 assert torch.equal(out[1], x[1])
151
152 def test_list_input_gets_stacked(self):
153 head = EmbeddingPoolerHead()
154 tensors = [torch.randn(_HIDDEN) for _ in range(_BATCH)]
155 meta = _make_metadata(_make_params(_BATCH))
156 out = head(tensors, meta)
157 assert out.shape == (_BATCH, _HIDDEN)
158 expected = torch.stack(tensors)
159 assert torch.equal(out, expected)
160
161 def test_projector_then_matryoshka(self):
162 proj = _linear(_HIDDEN, 8)
163 head = EmbeddingPoolerHead(projector=proj)
164 x = torch.randn(_BATCH, _HIDDEN)
165 params = _make_params(_BATCH, dimensions=4)
166 meta = _make_metadata(params)
167 out = head(x, meta)
168 assert out.shape == (_BATCH, 4)
169 assert torch.equal(out, proj(x)[..., :4])
170
171 def test_matryoshka_then_activation(self):
172 head = EmbeddingPoolerHead(activation=PoolerNormalize())
173 x = torch.randn(_BATCH, _HIDDEN)
174 params = _make_params(_BATCH, dimensions=4, use_activation=True)
175 meta = _make_metadata(params)
176 out = head(x, meta)
177 assert out.shape == (_BATCH, 4)
178 norms = torch.linalg.norm(out, dim=-1)
179 assert torch.allclose(norms, torch.ones(_BATCH), atol=1e-5)
180
181 def test_empty_batch(self):
182 head = EmbeddingPoolerHead()
183 x = torch.randn(0, _HIDDEN)
184 meta = _make_metadata([])
185 out = head(x, meta)
186 assert out.shape == (0, _HIDDEN)
187
188
189 # ---------------------------------------------------------------------------
190 # ClassifierPoolerHead
191 # ---------------------------------------------------------------------------
192 class TestClassifierPoolerHead:
193 def test_supported_tasks(self):
194 head = ClassifierPoolerHead()
195 assert head.get_supported_tasks() == {"classify"}
196
197 def test_passthrough(self):
198 head = ClassifierPoolerHead()
199 x = torch.randn(_BATCH, _HIDDEN)
200 meta = _make_metadata(_make_params(_BATCH, task="classify"))
201 out = head(x, meta)
202 assert torch.equal(out, x)
203
204 def test_head_dtype(self):
205 head = ClassifierPoolerHead(head_dtype=torch.float16)
206 x = torch.randn(_BATCH, _HIDDEN)
207 meta = _make_metadata(_make_params(_BATCH, task="classify"))
208 out = head(x, meta)
209 assert out.dtype == torch.float16
210
211 def test_classifier(self):
212 clf = _linear(_HIDDEN, 3)
213 head = ClassifierPoolerHead(classifier=clf)
214 x = torch.randn(_BATCH, _HIDDEN)
215 meta = _make_metadata(_make_params(_BATCH, task="classify"))
216 out = head(x, meta)
217 assert out.shape == (_BATCH, 3)
218 assert torch.allclose(out, clf(x))
219
220 def test_logit_mean(self):
221 head = ClassifierPoolerHead(logit_mean=2.0)
222 x = torch.randn(_BATCH, _HIDDEN)
223 meta = _make_metadata(_make_params(_BATCH, task="classify"))
224 out = head(x, meta)
225 assert torch.allclose(out, x - 2.0)
226
227 def test_logit_sigma(self):
228 head = ClassifierPoolerHead(logit_sigma=0.5)
229 x = torch.randn(_BATCH, _HIDDEN)
230 meta = _make_metadata(_make_params(_BATCH, task="classify"))
231 out = head(x, meta)
232 assert torch.allclose(out, x / 0.5)
233
234 def test_platt_scaling_combined(self):
235 head = ClassifierPoolerHead(logit_mean=1.0, logit_sigma=2.0)
236 x = torch.randn(_BATCH, _HIDDEN)
237 meta = _make_metadata(_make_params(_BATCH, task="classify"))
238 out = head(x, meta)
239 assert torch.allclose(out, (x - 1.0) / 2.0)
240
241 def test_activation_uniform_true(self):
242 head = ClassifierPoolerHead(activation=PoolerNormalize())
243 x = torch.randn(_BATCH, _HIDDEN)
244 params = _make_params(_BATCH, task="classify", use_activation=True)
245 meta = _make_metadata(params)
246 out = head(x, meta)
247 norms = torch.linalg.norm(out, dim=-1)
248 assert torch.allclose(norms, torch.ones(_BATCH), atol=1e-5)
249
250 def test_activation_uniform_false(self):
251 head = ClassifierPoolerHead(activation=PoolerNormalize())
252 x = torch.randn(_BATCH, _HIDDEN)
253 params = _make_params(_BATCH, task="classify", use_activation=False)
254 meta = _make_metadata(params)
255 out = head(x, meta)
256 assert torch.equal(out, x)
257
258 def test_activation_mixed_flags(self):
259 head = ClassifierPoolerHead(activation=PoolerNormalize())
260 x = torch.randn(2, _HIDDEN)
261 params = [
262 PoolingParams(task="classify", use_activation=True),
263 PoolingParams(task="classify", use_activation=False),
264 ]
265 meta = _make_metadata(params)
266 out = head(x, meta)
267 assert isinstance(out, list)
268 norm_0 = torch.linalg.norm(out[0], dim=-1)
269 assert torch.allclose(norm_0, torch.ones(1), atol=1e-5)
270 assert torch.equal(out[1], x[1])
271
272 def test_list_input_gets_stacked(self):
273 head = ClassifierPoolerHead()
274 tensors = [torch.randn(_HIDDEN) for _ in range(_BATCH)]
275 meta = _make_metadata(_make_params(_BATCH, task="classify"))
276 out = head(tensors, meta)
277 assert out.shape == (_BATCH, _HIDDEN)
278 expected = torch.stack(tensors)
279 assert torch.equal(out, expected)
280
281 def test_classifier_then_platt_scaling(self):
282 clf = _linear(_HIDDEN, 3)
283 head = ClassifierPoolerHead(classifier=clf, logit_mean=1.0, logit_sigma=2.0)
284 x = torch.randn(_BATCH, _HIDDEN)
285 meta = _make_metadata(_make_params(_BATCH, task="classify"))
286 out = head(x, meta)
287 expected = (clf(x) - 1.0) / 2.0
288 assert torch.allclose(out, expected)
289
290 def test_empty_batch(self):
291 head = ClassifierPoolerHead()
292 x = torch.randn(0, _HIDDEN)
293 meta = _make_metadata([])
294 out = head(x, meta)
295 assert out.shape == (0, _HIDDEN)
296
297
298 # ---------------------------------------------------------------------------
299 # TokenEmbeddingPoolerHead
300 # ---------------------------------------------------------------------------
301 class TestTokenEmbeddingPoolerHead:
302 def test_supported_tasks(self):
303 head = TokenEmbeddingPoolerHead()
304 assert head.get_supported_tasks() == {"token_embed"}
305
306 def test_passthrough(self):
307 head = TokenEmbeddingPoolerHead()
308 x = torch.randn(5, _HIDDEN)
309 param = PoolingParams(task="token_embed")
310 out = head.forward_chunk(x, param)
311 assert torch.equal(out, x)
312
313 def test_none_chunked_prefill(self):
314 head = TokenEmbeddingPoolerHead()
315 param = PoolingParams(task="token_embed")
316 out = head.forward_chunk(None, param)
317 assert out is None
318
319 def test_head_dtype(self):
320 head = TokenEmbeddingPoolerHead(head_dtype=torch.float16)
321 x = torch.randn(5, _HIDDEN)
322 param = PoolingParams(task="token_embed")
323 out = head.forward_chunk(x, param)
324 assert out is not None
325 assert out.dtype == torch.float16
326
327 def test_projector(self):
328 proj = _linear(_HIDDEN, 8)
329 head = TokenEmbeddingPoolerHead(projector=proj)
330 x = torch.randn(5, _HIDDEN)
331 param = PoolingParams(task="token_embed")
332 out = head.forward_chunk(x, param)
333 assert out is not None
334 assert out.shape == (5, 8)
335 assert torch.allclose(out, proj(x))
336
337 def test_matryoshka_truncation(self):
338 head = TokenEmbeddingPoolerHead()
339 x = torch.randn(5, _HIDDEN)
340 param = PoolingParams(task="token_embed", dimensions=4)
341 out = head.forward_chunk(x, param)
342 assert out is not None
343 assert out.shape == (5, 4)
344 assert torch.equal(out, x[..., :4])
345
346 def test_activation_true(self):
347 head = TokenEmbeddingPoolerHead(activation=PoolerNormalize())
348 x = torch.randn(5, _HIDDEN)
349 param = PoolingParams(task="token_embed", use_activation=True)
350 out = head.forward_chunk(x, param)
351 norms = torch.linalg.norm(out, dim=-1)
352 assert torch.allclose(norms, torch.ones(5), atol=1e-5)
353
354 def test_activation_false(self):
355 head = TokenEmbeddingPoolerHead(activation=PoolerNormalize())
356 x = torch.randn(5, _HIDDEN)
357 param = PoolingParams(task="token_embed", use_activation=False)
358 out = head.forward_chunk(x, param)
359 assert torch.equal(out, x)
360
361 def test_projector_then_matryoshka(self):
362 proj = _linear(_HIDDEN, 8)
363 head = TokenEmbeddingPoolerHead(projector=proj)
364 x = torch.randn(5, _HIDDEN)
365 param = PoolingParams(task="token_embed", dimensions=4)
366 out = head.forward_chunk(x, param)
367 assert out is not None
368 assert out.shape == (5, 4)
369 assert torch.equal(out, proj(x)[..., :4])
370
371 def test_matryoshka_then_activation(self):
372 head = TokenEmbeddingPoolerHead(activation=PoolerNormalize())
373 x = torch.randn(5, _HIDDEN)
374 param = PoolingParams(task="token_embed", dimensions=4, use_activation=True)
375 out = head.forward_chunk(x, param)
376 assert out is not None
377 assert out.shape == (5, 4)
378 norms = torch.linalg.norm(out, dim=-1)
379 assert torch.allclose(norms, torch.ones(5), atol=1e-5)
380
381 def test_forward_mixed_batch_chunked_prefill(self):
382 head = TokenEmbeddingPoolerHead()
383 pooled_data = [torch.randn(5, _HIDDEN), None, torch.randn(3, _HIDDEN)]
384 params = _make_params(3, task="token_embed")
385 meta = _make_metadata(params)
386 out = head(pooled_data, meta)
387 assert len(out) == 3
388 assert torch.equal(out[0], pooled_data[0])
389 assert out[1] is None
390 assert torch.equal(out[2], pooled_data[2])
391
392 def test_forward_empty_batch(self):
393 head = TokenEmbeddingPoolerHead()
394 meta = _make_metadata([])
395 out = head([], meta)
396 assert out == []
397
398
399 # ---------------------------------------------------------------------------
400 # TokenClassifierPoolerHead
401 # ---------------------------------------------------------------------------
402 class TestTokenClassifierPoolerHead:
403 def test_supported_tasks(self):
404 head = TokenClassifierPoolerHead()
405 assert head.get_supported_tasks() == {"token_classify"}
406
407 def test_passthrough(self):
408 head = TokenClassifierPoolerHead()
409 x = torch.randn(5, _HIDDEN)
410 param = PoolingParams(task="token_classify")
411 out = head.forward_chunk(x, param)
412 assert torch.equal(out, x)
413
414 def test_none_chunked_prefill(self):
415 head = TokenClassifierPoolerHead()
416 param = PoolingParams(task="token_classify")
417 out = head.forward_chunk(None, param)
418 assert out is None
419
420 def test_head_dtype(self):
421 head = TokenClassifierPoolerHead(head_dtype=torch.float16)
422 x = torch.randn(5, _HIDDEN)
423 param = PoolingParams(task="token_classify")
424 out = head.forward_chunk(x, param)
425 assert out is not None
426 assert out.dtype == torch.float16
427
428 def test_classifier(self):
429 clf = _linear(_HIDDEN, 3)
430 head = TokenClassifierPoolerHead(classifier=clf)
431 x = torch.randn(5, _HIDDEN)
432 param = PoolingParams(task="token_classify")
433 out = head.forward_chunk(x, param)
434 assert out is not None
435 assert out.shape == (5, 3)
436 assert torch.allclose(out, clf(x))
437
438 def test_logit_mean(self):
439 head = TokenClassifierPoolerHead(logit_mean=2.0)
440 x = torch.randn(5, _HIDDEN)
441 param = PoolingParams(task="token_classify")
442 out = head.forward_chunk(x, param)
443 assert torch.allclose(out, x - 2.0)
444
445 def test_logit_sigma(self):
446 head = TokenClassifierPoolerHead(logit_sigma=0.5)
447 x = torch.randn(5, _HIDDEN)
448 param = PoolingParams(task="token_classify")
449 out = head.forward_chunk(x, param)
450 assert torch.allclose(out, x / 0.5)
451
452 def test_platt_scaling_combined(self):
453 head = TokenClassifierPoolerHead(logit_mean=1.0, logit_sigma=2.0)
454 x = torch.randn(5, _HIDDEN)
455 param = PoolingParams(task="token_classify")
456 out = head.forward_chunk(x, param)
457 assert torch.allclose(out, (x - 1.0) / 2.0)
458
459 def test_activation_true(self):
460 head = TokenClassifierPoolerHead(activation=PoolerNormalize())
461 x = torch.randn(5, _HIDDEN)
462 param = PoolingParams(task="token_classify", use_activation=True)
463 out = head.forward_chunk(x, param)
464 norms = torch.linalg.norm(out, dim=-1)
465 assert torch.allclose(norms, torch.ones(5), atol=1e-5)
466
467 def test_activation_false(self):
468 head = TokenClassifierPoolerHead(activation=PoolerNormalize())
469 x = torch.randn(5, _HIDDEN)
470 param = PoolingParams(task="token_classify", use_activation=False)
471 out = head.forward_chunk(x, param)
472 assert torch.equal(out, x)
473
474 def test_forward_mixed_batch_chunked_prefill(self):
475 head = TokenClassifierPoolerHead()
476 pooled_data = [torch.randn(5, _HIDDEN), None, torch.randn(3, _HIDDEN)]
477 params = _make_params(3, task="token_classify")
478 meta = _make_metadata(params)
479 out = head(pooled_data, meta)
480 assert len(out) == 3
481 assert torch.equal(out[0], pooled_data[0])
482 assert out[1] is None
483 assert torch.equal(out[2], pooled_data[2])
484
485 def test_forward_empty_batch(self):
486 head = TokenClassifierPoolerHead()
487 meta = _make_metadata([])
488 out = head([], meta)
489 assert out == []