main
py 207 lines 7.73 KB
Raw
1 # SPDX-License-Identifier: Apache-2.0
2 # SPDX-FileCopyrightText: Copyright contributors to the vLLM project
3 """Core test implementation to be shared across modalities."""
4
5 from collections.abc import Callable
6 from typing import Any
7
8 import torch
9 from transformers.models.auto.auto_factory import _BaseAutoModelClass
10
11 from vllm.config.model import RunnerOption
12 from vllm.tokenizers import TokenizerLike
13
14 from .....conftest import HfRunner, VllmRunner
15 from ....registry import HF_EXAMPLE_MODELS
16 from .types import PromptWithMultiModalInput, RunnerOutput
17
18
19 def run_test(
20 *,
21 hf_runner: type[HfRunner],
22 vllm_runner: type[VllmRunner],
23 inputs: list[PromptWithMultiModalInput],
24 model: str,
25 dtype: str,
26 max_tokens: int,
27 num_logprobs: int,
28 enforce_eager: bool,
29 max_model_len: int,
30 max_num_seqs: int,
31 hf_output_post_proc: Callable[[RunnerOutput, str], Any] | None,
32 vllm_output_post_proc: Callable[[RunnerOutput, str], Any] | None,
33 auto_cls: type[_BaseAutoModelClass],
34 use_tokenizer_eos: bool,
35 comparator: Callable[..., None],
36 get_stop_token_ids: Callable[[TokenizerLike], list[int]] | None,
37 stop_str: list[str] | None,
38 limit_mm_per_prompt: dict[str, int],
39 vllm_runner_kwargs: dict[str, Any] | None,
40 hf_model_kwargs: dict[str, Any] | None,
41 hf_processor: Callable[[str], Any] | None,
42 patch_hf_runner: Callable[[HfRunner], HfRunner] | None,
43 runner: RunnerOption = "auto",
44 distributed_executor_backend: str | None = None,
45 tensor_parallel_size: int = 1,
46 vllm_embeddings: torch.Tensor | None = None,
47 ):
48 """Modality agnostic test executor for comparing HF/vLLM outputs."""
49 # In the case of embeddings, vLLM takes separate input tensors
50 vllm_inputs = vllm_embeddings if vllm_embeddings is not None else inputs
51
52 model_info = HF_EXAMPLE_MODELS.find_hf_info(model)
53 model_info.check_available_online(on_fail="skip")
54 model_info.check_transformers_version(on_fail="skip")
55
56 # Disable other modalities to save memory
57 default_limits = {"image": 0, "video": 0, "audio": 0}
58 limit_mm_per_prompt = default_limits | limit_mm_per_prompt
59
60 vllm_outputs_per_mm = []
61 hf_outputs_per_mm = []
62
63 # NOTE: take care of the order. run vLLM first, and then run HF.
64 # vLLM needs a fresh new process without cuda initialization.
65 # if we run HF first, the cuda initialization will be done and it
66 # will hurt multiprocessing backend with fork method (the default method).
67
68 vllm_runner_kwargs_: dict[str, Any] = {"mm_processor_cache_gb": 0}
69 if model_info.tokenizer:
70 vllm_runner_kwargs_["tokenizer_name"] = model_info.tokenizer
71 if model_info.tokenizer_mode:
72 vllm_runner_kwargs_["tokenizer_mode"] = model_info.tokenizer_mode
73 if model_info.hf_overrides:
74 vllm_runner_kwargs_["hf_overrides"] = model_info.hf_overrides
75 if model_info.require_embed_inputs:
76 for k in ("skip_tokenizer_init", "enable_prompt_embeds", "enable_mm_embeds"):
77 vllm_runner_kwargs_[k] = model_info.require_embed_inputs
78 if not model_info.enable_prefix_caching:
79 vllm_runner_kwargs_["enable_prefix_caching"] = False
80
81 if vllm_runner_kwargs:
82 vllm_runner_kwargs_.update(vllm_runner_kwargs)
83
84 # Avoid passing limit_mm_per_prompt twice when vllm_runner_kwargs
85 # already contains it (e.g. gemma4 sets it via vllm_runner_kwargs).
86 if "limit_mm_per_prompt" in vllm_runner_kwargs_:
87 limit_mm_per_prompt = vllm_runner_kwargs_.pop("limit_mm_per_prompt")
88
89 with vllm_runner(
90 model,
91 max_model_len=max_model_len,
92 max_num_seqs=max_num_seqs,
93 dtype=dtype,
94 limit_mm_per_prompt=limit_mm_per_prompt,
95 tensor_parallel_size=tensor_parallel_size,
96 distributed_executor_backend=distributed_executor_backend,
97 enforce_eager=enforce_eager,
98 runner=runner,
99 **vllm_runner_kwargs_,
100 ) as vllm_model:
101 tokenizer = vllm_model.llm.get_tokenizer()
102
103 vllm_kwargs: dict[str, Any] = {}
104 if get_stop_token_ids is not None:
105 vllm_kwargs["stop_token_ids"] = get_stop_token_ids(tokenizer)
106 if stop_str:
107 vllm_kwargs["stop"] = stop_str
108
109 for prompts, image_data, video_data, audio_data in vllm_inputs:
110 mm_data = dict(images=image_data, videos=video_data, audios=audio_data)
111 vllm_kwargs_with_mm_data = vllm_kwargs | mm_data
112 vllm_output = vllm_model.generate_greedy_logprobs(
113 prompts,
114 max_tokens,
115 num_logprobs=num_logprobs,
116 **vllm_kwargs_with_mm_data,
117 )
118 vllm_outputs_per_mm.append(vllm_output)
119
120 hf_runner_kwargs: dict[str, Any] = {}
121 if model_info.tokenizer:
122 hf_runner_kwargs["tokenizer_name"] = model_info.tokenizer
123 if hf_processor is not None:
124 hf_runner_kwargs["processor"] = hf_processor(model)
125
126 hf_model = hf_runner(
127 model,
128 dtype=dtype,
129 auto_cls=auto_cls,
130 model_kwargs=hf_model_kwargs,
131 **hf_runner_kwargs,
132 )
133
134 # Some models need to patch things like the model processor, e.g., internvl
135 if patch_hf_runner is not None:
136 hf_model = patch_hf_runner(hf_model)
137
138 with hf_model, torch.no_grad():
139 tokenizer = hf_model.tokenizer
140
141 # Some models need to explicitly pass the eos_token_id off the tokenizer
142 # or processor for a good comparison;
143 # currently assume processor/tokenizer agree on the EOS, and pull it off
144 # the tokenizer if requested.
145 hf_kwargs = {}
146 if use_tokenizer_eos:
147 hf_kwargs["eos_token_id"] = tokenizer.eos_token_id
148 if stop_str:
149 hf_kwargs["stop_strings"] = stop_str
150
151 for prompts, image_data, video_data, audio_data in inputs:
152 mm_data = dict(images=image_data, videos=video_data, audios=audio_data)
153 hf_kwargs_with_mm_data = hf_kwargs | mm_data
154 hf_output = hf_model.generate_greedy_logprobs_limit(
155 prompts,
156 max_tokens,
157 num_logprobs=num_logprobs,
158 tokenizer=tokenizer,
159 **hf_kwargs_with_mm_data,
160 )
161 hf_outputs_per_mm.append(hf_output)
162
163 # Apply output processing / sanitation to the vLLM and HF runner results
164 hf_outputs_per_mm, vllm_outputs_per_mm = process_runner_outputs(
165 model,
166 first_runner_outputs=hf_outputs_per_mm,
167 second_runner_outputs=vllm_outputs_per_mm,
168 first_runner_processor=hf_output_post_proc,
169 second_runner_processor=vllm_output_post_proc,
170 )
171
172 for hf_outputs, vllm_outputs in zip(hf_outputs_per_mm, vllm_outputs_per_mm):
173 # This is usually check_logprobs_close, but it's passed through to
174 # allow things like check_outputs_equal where needed
175 comparator(
176 outputs_0_lst=hf_outputs,
177 outputs_1_lst=vllm_outputs,
178 name_0="hf",
179 name_1="vllm",
180 )
181
182
183 def process_runner_outputs(
184 model,
185 first_runner_outputs,
186 second_runner_outputs,
187 first_runner_processor=None,
188 second_runner_processor=None,
189 ):
190 """Applies the runner processor(s) to the runner outputs, if any."""
191 if first_runner_processor is not None:
192 first_runner_outputs = process_outputs(
193 first_runner_processor, model, first_runner_outputs
194 )
195 if second_runner_processor is not None:
196 second_runner_outputs = process_outputs(
197 second_runner_processor, model, second_runner_outputs
198 )
199 return first_runner_outputs, second_runner_outputs
200
201
202 def process_outputs(output_processor, model, outputs_per_image):
203 """Applies a model specific post-processor function to a runner's output."""
204 return [
205 [output_processor(res, model) for res in outputs]
206 for outputs in outputs_per_image
207 ]