main
py 132 lines 3.96 KB
Raw
1 # SPDX-License-Identifier: Apache-2.0
2 # SPDX-FileCopyrightText: Copyright contributors to the vLLM project
3 from collections.abc import Iterable, Sequence
4
5 import pytest
6 import regex as re
7 from transformers import AutoModel
8
9 from tests.models.utils import check_logprobs_close
10 from vllm.assets.image import ImageAsset
11 from vllm.logprobs import Logprob, SampleLogprobs
12 from vllm.tokenizers import TokenizerLike
13
14 from ....conftest import HfRunner, PromptImageInput, VllmRunner
15
16 IMAGE = ImageAsset("paper-11").pil_image_ext(ext="png").convert("RGB")
17 PROMPT = (
18 "</s><s><predict_bbox><predict_classes><output_markdown><predict_no_text_in_pic>"
19 )
20
21
22 class DummyLogprobs(dict[int, Logprob]):
23 def __init__(self, vocab_ids: Iterable[int]):
24 super().__init__(dict.fromkeys(vocab_ids, Logprob(0.0)))
25
26 def __repr__(self):
27 return "DummyLogprobs()"
28
29
30 def mask_bbox_tokens(
31 output: tuple[list[int], str, SampleLogprobs],
32 tokenizer: TokenizerLike,
33 ) -> tuple[list[int], str, SampleLogprobs]:
34 """Always pass check_logprobs_close check for bounding box tokens
35 because it is reasonable for them to differ slightly.
36 """
37 ignore_pattern = r"<[xy]_[\d.]+>"
38 vocab = tokenizer.get_vocab()
39
40 output_ids, output_str, out_logprobs = output
41
42 masked_logprobs = list[dict[int, Logprob]]()
43 for token, logprobs in zip(output_ids, out_logprobs):
44 if re.match(ignore_pattern, tokenizer.decode(token)):
45 masked_logprobs.append(DummyLogprobs(vocab.values()))
46 else:
47 masked_logprobs.append(logprobs)
48
49 return output_ids, output_str, masked_logprobs
50
51
52 def run_test(
53 hf_runner: type[HfRunner],
54 vllm_runner: type[VllmRunner],
55 inputs: Sequence[tuple[list[str], PromptImageInput]],
56 model: str,
57 *,
58 dtype: str,
59 max_tokens: int,
60 num_logprobs: int,
61 ) -> None:
62 """Verify that the inference result is the same between hf and vllm."""
63 with vllm_runner(
64 model,
65 dtype=dtype,
66 max_num_seqs=64,
67 limit_mm_per_prompt={"image": 1},
68 trust_remote_code=True,
69 ) as vllm_model:
70 vllm_outputs_per_case = [
71 vllm_model.generate_greedy_logprobs(
72 prompts,
73 max_tokens,
74 num_logprobs=num_logprobs,
75 images=images,
76 )
77 for prompts, images in inputs
78 ]
79
80 tokenizer = vllm_model.llm.get_tokenizer()
81
82 with hf_runner(model, dtype=dtype, auto_cls=AutoModel) as hf_model:
83 hf_outputs_per_case = [
84 hf_model.generate_greedy_logprobs_limit(
85 prompts,
86 max_tokens,
87 num_logprobs=num_logprobs,
88 images=images,
89 tokenization_kwargs={"add_special_tokens": False},
90 )
91 for prompts, images in inputs
92 ]
93
94 for hf_outputs, vllm_outputs in zip(hf_outputs_per_case, vllm_outputs_per_case):
95 check_logprobs_close(
96 outputs_0_lst=[
97 mask_bbox_tokens(output, tokenizer) for output in hf_outputs
98 ],
99 outputs_1_lst=[
100 mask_bbox_tokens(output, tokenizer) for output in vllm_outputs
101 ],
102 name_0="hf",
103 name_1="vllm",
104 )
105
106
107 @pytest.mark.parametrize(
108 "model",
109 [
110 "nvidia/NVIDIA-Nemotron-Parse-v1.2",
111 # v2.0's checkpoint ties lm_head to the decoder's input embeddings
112 # instead of shipping a separate lm_head.weight tensor; this
113 # exercises that path (see tie_weights() in nemotron_parse.py).
114 "nvidia/NVIDIA-Nemotron-Parse-2.0",
115 ],
116 )
117 @pytest.mark.parametrize("dtype", ["bfloat16"])
118 @pytest.mark.parametrize("num_logprobs", [5])
119 def test_models(
120 hf_runner, vllm_runner, model: str, dtype: str, num_logprobs: int
121 ) -> None:
122 run_test(
123 hf_runner,
124 vllm_runner,
125 inputs=[
126 ([PROMPT] * 10, [IMAGE] * 10),
127 ],
128 model=model,
129 dtype=dtype,
130 max_tokens=100,
131 num_logprobs=num_logprobs,
132 )