main
py 1,416 lines 50.7 KB
Raw
1 # SPDX-License-Identifier: Apache-2.0
2 # SPDX-FileCopyrightText: Copyright contributors to the vLLM project
3 """Common utility functions relating to different models that are useful
4 for manipulating the input / output of HF & vLLM test runners, which are
5 typically specific to a small subset of models.
6 """
7
8 import types
9
10 import numpy as np
11 import numpy.typing as npt
12 import PIL.Image
13 import pytest
14 import regex as re
15 import torch
16 from PIL.Image import Image
17 from transformers import (
18 AutoConfig,
19 AutoTokenizer,
20 BatchFeature,
21 GenerationConfig,
22 GenerationMixin,
23 )
24 from transformers.masking_utils import create_causal_mask
25 from transformers.video_utils import VideoMetadata
26
27 from vllm.logprobs import SampleLogprobs
28 from vllm.platforms import current_platform
29 from vllm.utils.collection_utils import is_list_of
30
31 from .....conftest import HfRunner, ImageTestAssets
32 from .types import RunnerOutput
33
34
35 ####### vLLM output processors functions
36 def blip2_vllm_to_hf_output(vllm_output: RunnerOutput, model: str) -> RunnerOutput:
37 """Sanitize vllm output [blip2 models] to be comparable with hf output."""
38 _, output_str, out_logprobs = vllm_output
39
40 hf_output_str = output_str + "\n"
41
42 tokenizer = AutoTokenizer.from_pretrained(model)
43 hf_output_ids = tokenizer.encode(hf_output_str)
44 assert hf_output_ids[0] == tokenizer.bos_token_id
45 hf_output_ids = hf_output_ids[1:]
46
47 return hf_output_ids, hf_output_str, out_logprobs
48
49
50 def qwen2_vllm_to_hf_output(
51 vllm_output: RunnerOutput, model: str
52 ) -> tuple[list[int], str, SampleLogprobs | None]:
53 """Sanitize vllm output [qwen2 models] to be comparable with hf output."""
54 output_ids, output_str, out_logprobs = vllm_output
55
56 hf_output_str = output_str + "<|im_end|>"
57
58 return output_ids, hf_output_str, out_logprobs
59
60
61 def kimiv_vl_vllm_to_hf_output(
62 vllm_output: RunnerOutput, model: str
63 ) -> tuple[list[int], str, SampleLogprobs | None]:
64 """Sanitize vllm output [kimi_vl models] to be comparable with hf output."""
65 output_ids, output_str, out_logprobs = vllm_output
66
67 hf_output_str = output_str + "<|im_end|>[EOS]"
68
69 return output_ids, hf_output_str, out_logprobs
70
71
72 def llava_image_vllm_to_hf_output(
73 vllm_output: RunnerOutput, model: str
74 ) -> RunnerOutput:
75 config = AutoConfig.from_pretrained(model)
76 mm_token_id = config.image_token_index
77 return _llava_vllm_to_hf_output(vllm_output, model, mm_token_id)
78
79
80 def llava_video_vllm_to_hf_output(
81 vllm_output: RunnerOutput, model: str
82 ) -> tuple[list[int], str, SampleLogprobs | None]:
83 config = AutoConfig.from_pretrained(model)
84 mm_token_id = config.video_token_index
85 return _llava_vllm_to_hf_output(vllm_output, model, mm_token_id)
86
87
88 def _llava_vllm_to_hf_output(
89 vllm_output: RunnerOutput, model: str, mm_token_id: int
90 ) -> RunnerOutput:
91 """Sanitize vllm output [Llava models] to be comparable with hf output."""
92 output_ids, output_str, out_logprobs = vllm_output
93
94 tokenizer = AutoTokenizer.from_pretrained(model)
95 eos_token_id = tokenizer.eos_token_id
96
97 hf_output_ids = [
98 token_id
99 for idx, token_id in enumerate(output_ids)
100 if token_id != mm_token_id or output_ids[idx - 1] != mm_token_id
101 ]
102
103 # output_str[0] is not " " in some cases, e.g., Granite Vision,
104 # but for most llava based models, this is the case
105 hf_output_str = output_str[1:] if output_str[0] == " " else output_str
106
107 if hf_output_ids[-1] == eos_token_id:
108 hf_output_str = hf_output_str + tokenizer.decode(eos_token_id)
109
110 return hf_output_ids, hf_output_str, out_logprobs
111
112
113 def llava_onevision_hf_model_kwargs(model: str) -> dict:
114 """Workaround to fix the sliding window issue in llava_onevision."""
115 config = AutoConfig.from_pretrained(model)
116 config.text_config.sliding_window = None
117 return config.to_dict()
118
119
120 def llava_onevision_vllm_to_hf_output(
121 vllm_output: RunnerOutput, model: str
122 ) -> RunnerOutput:
123 """Sanitize vllm output [llava-onevision] to compare with hf output."""
124 output_ids, output_str, out_logprobs = vllm_output
125
126 config = AutoConfig.from_pretrained(model)
127 video_token_id = config.video_token_index
128
129 tokenizer = AutoTokenizer.from_pretrained(model)
130 eos_token_id = tokenizer.eos_token_id
131
132 hf_output_ids = [
133 token_id
134 for idx, token_id in enumerate(output_ids)
135 if token_id != video_token_id or output_ids[idx - 1] != video_token_id
136 ]
137
138 hf_output_str = output_str
139 if hf_output_ids[-1] == eos_token_id:
140 hf_output_str = hf_output_str + tokenizer.decode(eos_token_id)
141
142 return hf_output_ids, hf_output_str, out_logprobs
143
144
145 def phi3v_vllm_to_hf_output(vllm_output: RunnerOutput, model: str) -> RunnerOutput:
146 """Sanitize vllm output [phi3v] to be comparable with hf output."""
147 _, output_str, out_logprobs = vllm_output
148
149 output_str_without_image = re.sub(r"(<\|image_\d+\|>)+", "", output_str)
150 assert output_str_without_image[0] == " "
151 output_str_without_image = output_str_without_image[1:]
152
153 hf_output_str = output_str_without_image + "<|end|><|endoftext|>"
154
155 tokenizer = AutoTokenizer.from_pretrained(model)
156 hf_output_ids = tokenizer.encode(output_str_without_image)
157 assert hf_output_ids[0] == 1
158 hf_output_ids = hf_output_ids[1:]
159
160 return hf_output_ids, hf_output_str, out_logprobs
161
162
163 def paligemma_vllm_to_hf_output(vllm_output: RunnerOutput, model: str) -> RunnerOutput:
164 """Sanitize vllm output to be comparable with hf output."""
165 output_ids, output_str, out_logprobs = vllm_output
166
167 config = AutoConfig.from_pretrained(model)
168 image_token_id = config.image_token_index
169
170 tokenizer = AutoTokenizer.from_pretrained(model)
171 eos_token_id = tokenizer.eos_token_id
172
173 hf_output_ids = [
174 token_id
175 for idx, token_id in enumerate(output_ids)
176 if token_id != image_token_id or output_ids[idx - 1] != image_token_id
177 ]
178
179 hf_output_str = output_str
180
181 if hf_output_ids[-1] == eos_token_id:
182 hf_output_str = hf_output_str + tokenizer.decode(eos_token_id)
183
184 return hf_output_ids, hf_output_str, out_logprobs
185
186
187 ####### Post-processors for HF outputs
188 def deepseekvl2_trunc_hf_output(hf_output: RunnerOutput, model: str) -> RunnerOutput:
189 output_ids, output_str, out_logprobs = hf_output
190 if output_str.endswith("<|end▁of▁sentence|>"):
191 output_str = output_str.split("<|end▁of▁sentence|>")[0]
192 return output_ids, output_str, out_logprobs
193
194
195 def idefics3_trunc_hf_output(hf_output: RunnerOutput, model: str) -> RunnerOutput:
196 output_ids, output_str, out_logprobs = hf_output
197 if output_str.endswith("<end_of_utterance>"):
198 output_str = output_str.split("<end_of_utterance>")[0]
199 return output_ids, output_str, out_logprobs
200
201
202 def smolvlm_trunc_hf_output(hf_output: RunnerOutput, model: str) -> RunnerOutput:
203 # Based on Idefics3
204 return idefics3_trunc_hf_output(hf_output, model)
205
206
207 def minicpmv_trunc_hf_output(hf_output: RunnerOutput, model: str) -> RunnerOutput:
208 output_ids, output_str, out_logprobs = hf_output
209 if output_str.endswith("<|eot_id|>"):
210 output_str = output_str.split("<|eot_id|>")[0]
211 return output_ids, output_str, out_logprobs
212
213
214 def ultravox_trunc_hf_output(hf_output: RunnerOutput, model: str) -> RunnerOutput:
215 output_ids, output_str, out_logprobs = hf_output
216
217 tokenizer = AutoTokenizer.from_pretrained(model)
218 eos_token_id = tokenizer.eos_token_id
219 eos_token = tokenizer.decode(eos_token_id)
220 if output_str.endswith(eos_token):
221 output_str = output_str.split(eos_token)[0]
222 return output_ids, output_str, out_logprobs
223
224
225 ####### Functions for converting image assets to embeddings
226 def get_llava_embeddings(image_assets: ImageTestAssets):
227 return [asset.image_embeds for asset in image_assets]
228
229
230 ####### Model-specific HuggingFace runner patchers
231 def deepseekvl2_patch_hf_runner(hf_model: HfRunner) -> HfRunner:
232 """Patches and returns an instance of the HfRunner to use for GLM4."""
233 hf_processor = hf_model.processor
234
235 def processor(*args, text="", images=None, **kwargs):
236 if isinstance(images, Image):
237 images = [images]
238 # inputs is a custom class instead of dict or BatchFeature
239 inputs = hf_processor(
240 *args,
241 prompt=text,
242 images=images,
243 **kwargs,
244 )
245 inputs = {
246 k: inputs[k]
247 for k in inputs.keys() # noqa
248 if k not in ("seq_lens", "sft_format")
249 }
250 return BatchFeature(data=inputs, tensor_type="pt")
251
252 hf_model.processor = processor
253 hf_model.model.get_output_embeddings = (
254 lambda: hf_model.model.language.model.embed_tokens
255 )
256 return hf_model
257
258
259 def gemma3_patch_hf_runner(hf_model: HfRunner) -> HfRunner:
260 """Patches and returns an instance of the HfRunner to use for Gemma 3."""
261 hf_processor = hf_model.processor
262
263 def processor(*args, **kwargs):
264 return hf_processor(*args, do_pan_and_scan=True, **kwargs)
265
266 hf_model.processor = processor
267
268 return hf_model
269
270
271 def gemma3_vllm_to_hf_output(vllm_output: RunnerOutput, model: str) -> RunnerOutput:
272 """Sanitize vllm output [gemma-3] to compare with hf output."""
273 output_ids, output_str, out_logprobs = vllm_output
274
275 config = AutoConfig.from_pretrained(model)
276 image_token_id = config.image_token_id
277
278 tokenizer = AutoTokenizer.from_pretrained(model)
279 eos_token_id = tokenizer.eos_token_id
280
281 hf_output_ids = [
282 token_id
283 for idx, token_id in enumerate(output_ids)
284 if token_id != image_token_id
285 ]
286
287 hf_output_str = output_str
288 if hf_output_ids[-1] == eos_token_id:
289 hf_output_str = hf_output_str + tokenizer.decode(eos_token_id)
290
291 return hf_output_ids, hf_output_str, out_logprobs
292
293
294 def glm4v_patch_hf_runner(hf_model: HfRunner) -> HfRunner:
295 """Patches and returns an instance of the HfRunner to use for GLM4V."""
296 if current_platform.is_rocm():
297 import types
298
299 config = hf_model.model.config
300 if hasattr(config, "num_layers") and not hasattr(config, "num_hidden_layers"):
301 config.num_hidden_layers = config.num_layers
302 config.output_hidden_states = True
303
304 def patched_prepare_cache(
305 self, generation_config, model_kwargs, *args, **kwargs
306 ):
307 model_kwargs["past_key_values"] = None
308 model_kwargs["use_cache"] = False
309 return model_kwargs
310
311 hf_model.model._prepare_cache_for_generation = types.MethodType(
312 patched_prepare_cache, hf_model.model
313 )
314 original_generate = hf_model.model.generate
315
316 def patched_generate(*args, **kwargs):
317 kwargs["output_hidden_states"] = True
318 kwargs["return_dict_in_generate"] = True
319 return original_generate(*args, **kwargs)
320
321 hf_model.model.generate = patched_generate
322 original_forward = hf_model.model.forward
323
324 def patched_forward(*args, **kwargs):
325 kwargs["output_hidden_states"] = True
326 return original_forward(*args, **kwargs)
327
328 hf_model.model.forward = patched_forward
329
330 hf_processor = hf_model.processor
331
332 def processor(*args, text="", images=None, **kwargs):
333 if images is None:
334 return hf_processor(*args, **kwargs)
335
336 images = [images] if isinstance(images, Image) else images
337
338 contents = re.findall(
339 r"<\|begin_of_image\|><\|endoftext\|><\|end_of_image\|>(.*?)<\|assistant\|>",
340 text,
341 )
342 assert len(contents) == len(images)
343
344 return hf_processor.apply_chat_template(
345 [
346 {"role": "user", "image": image, "content": content}
347 for image, content in zip(images, contents)
348 ],
349 add_generation_prompt=True,
350 tokenize=True,
351 return_dict=True,
352 **kwargs,
353 )
354
355 hf_model.processor = processor
356 hf_model.model.get_output_embeddings = (
357 lambda: hf_model.model.transformer.output_layer
358 )
359 return hf_model
360
361
362 def glm4_1v_patch_hf_runner(hf_model: HfRunner) -> HfRunner:
363 """Patches and returns an instance of the HfRunner to use for GLM4.1V."""
364 hf_processor = hf_model.processor
365
366 def processor(*args, videos=None, **kwargs):
367 if videos is not None and is_list_of(videos, tuple):
368 # If videos is a list of tuples, we assume each tuple contains
369 # (video_array, metadata) as in the case of GLM4.1V.
370 # Filter out 'do_sample_frames' as it's not a valid VideoMetadata arg
371 video_metadata = [
372 [
373 VideoMetadata(
374 **{k: v for k, v in video[1].items() if k != "do_sample_frames"}
375 )
376 ]
377 for video in videos
378 ]
379 videos = [[video[0]] for video in videos]
380 else:
381 video_metadata = None
382
383 return hf_processor(
384 *args, videos=videos, video_metadata=video_metadata, **kwargs
385 )
386
387 hf_model.processor = processor
388 return hf_model
389
390
391 def h2ovl_patch_hf_runner(hf_model: HfRunner) -> HfRunner:
392 """Patches and returns an instance of the HfRunner to use for H2OVL."""
393
394 class H2OVLProcessor:
395 """A simple processor for H2OVL models."""
396
397 def __init__(self, hf_runner: HfRunner):
398 self.num_image_token = hf_runner.model.num_image_token
399 self.tokenizer = hf_runner.tokenizer
400
401 self.config = AutoConfig.from_pretrained(
402 hf_runner.model_name, trust_remote_code=True
403 )
404 self.vision_config = self.config.vision_config
405 self.use_thumbnail = self.config.use_thumbnail
406 self.use_msac = self.config.use_msac
407 self.min_num = self.config.min_dynamic_patch
408 self.max_num = self.config.max_dynamic_patch
409 self.image_size = self.vision_config.image_size
410
411 def __call__(self, text: str, images: Image | list[Image], **kwargs):
412 from vllm.transformers_utils.processors.h2ovl import (
413 image_to_pixel_values_h2ovl,
414 )
415
416 IMG_START = "<img>"
417 IMG_END = "</img>"
418 IMG_CONTEXT = "<IMG_CONTEXT>"
419
420 images = [images] if isinstance(images, Image) else images
421 pixel_values = [
422 image_to_pixel_values_h2ovl(
423 image,
424 input_size=self.image_size,
425 min_num=self.min_num,
426 max_num=self.max_num,
427 use_thumbnail=self.use_thumbnail,
428 use_msac=self.use_msac,
429 )
430 for image in images
431 ]
432 num_patches_list = [pixel_value.shape[0] for pixel_value in pixel_values]
433 pixel_values = torch.cat(pixel_values, dim=0)
434 for num_patches in num_patches_list:
435 context_tokens = IMG_CONTEXT * self.num_image_token * num_patches
436 image_tokens = IMG_START + context_tokens + IMG_END
437 text = text.replace("<image>", image_tokens, 1)
438 prompt = self.tokenizer(text, return_tensors="pt")
439 prompt.update({"pixel_values": pixel_values})
440 return prompt
441
442 img_context_token_id = hf_model.tokenizer.convert_tokens_to_ids("<IMG_CONTEXT>")
443 hf_model.model.img_context_token_id = img_context_token_id
444 hf_model.processor = H2OVLProcessor(hf_model)
445 hf_model.model.get_output_embeddings = (
446 lambda: hf_model.model.language_model.get_output_embeddings()
447 )
448 hf_model.model.generate = types.MethodType(_internvl_generate, hf_model.model)
449 return hf_model
450
451
452 def isaac_patch_hf_runner(hf_model: HfRunner) -> HfRunner:
453 """Patch HF runner for Isaac:
454 1) Move processor outputs to model device
455 2) Ensure IsaacModel.forward returns hidden_states
456 for compatibility with hidden_states_to_seq_logprobs()
457 """
458 from perceptron.tensorstream import TextType
459 from perceptron.tensorstream.ops import compute_mrope_pos_tensor, modality_mask
460 from transformers.modeling_outputs import BaseModelOutputWithPast
461
462 def compute_position_ids_input_ids(input_ids: torch.Tensor) -> torch.Tensor:
463 """Create 3D positional indices for token input."""
464 batch_size, seq_length = input_ids.shape
465 position_ids = torch.arange(seq_length, device=input_ids.device)
466 position_ids = position_ids.view(1, -1).expand(batch_size, -1)
467 position_ids = position_ids.unsqueeze(2).expand(-1, -1, 3) # Add 3D for MRoPE
468 return position_ids
469
470 model_device = next(hf_model.model.parameters()).device
471
472 # ----------------------------
473 # 1) Patch processor: move BatchFeature input_ids and TensorStream to model device
474 # ----------------------------
475 original_processor = hf_model.processor
476
477 def patched_processor(*args, **kwargs):
478 result = original_processor(*args, **kwargs)
479 for k, v in result.data.items():
480 result[k] = v.to(model_device)
481 return result
482
483 hf_model.processor = patched_processor
484
485 tokenizer = AutoTokenizer.from_pretrained(
486 hf_model.model_name, trust_remote_code=True
487 )
488
489 original_generate = hf_model.model.generate
490
491 def patched_generate(*args, **kwargs):
492 kwargs["pad_token_id"] = tokenizer.eos_token_id
493 kwargs["eos_token_id"] = tokenizer.eos_token_id
494 return original_generate(*args, **kwargs)
495
496 hf_model.model.generate = patched_generate
497
498 # ----------------------------
499 # 2) Patch IsaacModel.forward: add hidden_states to the output
500 # ----------------------------
501 isaac_model = hf_model.model.model
502
503 def patched_forward(
504 self,
505 input_ids=None,
506 tensor_stream=None,
507 attention_mask=None,
508 position_ids=None,
509 modality_tensor=None,
510 past_key_values=None,
511 inputs_embeds=None,
512 use_cache=None,
513 output_hidden_states=None,
514 return_dict=None,
515 cache_position=None,
516 **kwargs,
517 ):
518 """Forward pass with MRoPE position embeddings.
519 Computes position embeddings once and passes them through all layers.
520 """
521 output_hidden_states = (
522 output_hidden_states
523 if output_hidden_states is not None
524 else self.config.output_hidden_states
525 )
526 use_cache = use_cache if use_cache is not None else self.config.use_cache
527 return_dict = (
528 return_dict if return_dict is not None else self.config.use_return_dict
529 )
530
531 # Get inputs
532 if tensor_stream is not None and inputs_embeds is not None:
533 raise ValueError("You cannot specify both tensor_stream and inputs_embeds")
534 elif tensor_stream is not None:
535 # Embed TensorStream directly
536 inputs_embeds = self.embed_stream(tensor_stream)
537 # Create modality tensor if not provided
538 if modality_tensor is None:
539 modality_tensor = modality_mask(tensor_stream)
540 elif input_ids is not None and inputs_embeds is not None:
541 raise ValueError(
542 "You cannot specify both input_ids and inputs_embeds at the same time"
543 )
544 elif input_ids is not None:
545 inputs_embeds = self.embed_tokens(input_ids)
546 # Create text modality tensor if not provided
547 if modality_tensor is None:
548 batch_size, seq_length = input_ids.shape
549 modality_tensor = torch.full(
550 (batch_size, seq_length),
551 TextType.text.value,
552 device=input_ids.device,
553 dtype=torch.long,
554 )
555 elif inputs_embeds is None:
556 raise ValueError(
557 "You have to specify either tensor_stream, input_ids or inputs_embeds"
558 )
559
560 # Create default position_ids if not provided
561 if position_ids is None:
562 if tensor_stream is not None:
563 position_ids = compute_mrope_pos_tensor(tensor_stream) # (B,L,3)
564 else:
565 position_ids = compute_position_ids_input_ids(input_ids)
566
567 # Compute MRoPE position embeddings if we have custom rotary_emb
568 cos, sin = self.rotary_emb(position_ids, modality_tensor)
569 cos = cos.to(inputs_embeds.dtype)
570 sin = sin.to(inputs_embeds.dtype)
571
572 # Prepare attention mask
573 attention_mask = create_causal_mask(
574 config=self.config,
575 input_embeds=inputs_embeds,
576 attention_mask=attention_mask,
577 past_key_values=past_key_values,
578 position_ids=position_ids,
579 cache_position=cache_position,
580 )
581
582 # Initialize and collect hidden states
583 hidden_states = inputs_embeds
584 hidden_states_list: list[torch.Tensor] = []
585
586 if output_hidden_states:
587 hidden_states_list.append(hidden_states)
588
589 for decoder_layer in self.layers:
590 layer_outputs = decoder_layer(
591 hidden_states,
592 attention_mask=attention_mask,
593 position_ids=position_ids,
594 past_key_value=past_key_values,
595 use_cache=use_cache,
596 cache_position=cache_position,
597 position_embeddings=(cos, sin),
598 **kwargs,
599 )
600
601 hidden_states = (
602 layer_outputs[0] if isinstance(layer_outputs, tuple) else layer_outputs
603 )
604
605 if output_hidden_states:
606 hidden_states_list.append(hidden_states)
607
608 # Final layer norm
609 hidden_states = self.norm(hidden_states)
610
611 if output_hidden_states:
612 hidden_states_list.append(hidden_states)
613
614 # Convert to tuple or None
615 all_hidden_states = tuple(hidden_states_list) if output_hidden_states else None
616
617 # Include hidden_states for compatibility with hidden_states_to_seq_logprobs()
618 return BaseModelOutputWithPast(
619 last_hidden_state=hidden_states,
620 past_key_values=past_key_values,
621 hidden_states=all_hidden_states,
622 )
623
624 isaac_model.forward = types.MethodType(patched_forward, isaac_model)
625
626 return hf_model
627
628
629 def skyworkr1v_patch_hf_runner(hf_model: HfRunner) -> HfRunner:
630 """Patches and returns an instance of the HfRunner to use for SkyworkR1V."""
631
632 class SkyworkR1VProcessor:
633 """A simple processor for SkyworkR1V."""
634
635 def __init__(self, hf_runner: HfRunner):
636 self.num_image_token = hf_runner.model.num_image_token
637 self.tokenizer = hf_runner.tokenizer
638
639 self.config = AutoConfig.from_pretrained(
640 hf_runner.model_name, trust_remote_code=True
641 )
642 self.vision_config = self.config.vision_config
643 self.use_thumbnail = self.config.use_thumbnail
644 self.min_num = self.config.min_dynamic_patch
645 self.max_num = self.config.max_dynamic_patch
646 self.image_size = self.vision_config.image_size
647
648 def __call__(self, text: str, images: Image | list[Image], **kwargs):
649 from vllm.transformers_utils.processors.internvl import (
650 image_to_pixel_values_internvl,
651 )
652
653 IMG_START = "<img>"
654 IMG_END = "</img>"
655 IMG_CONTEXT = "<IMG_CONTEXT>"
656
657 images = [images] if isinstance(images, Image) else images
658 pixel_values = [
659 image_to_pixel_values_internvl(
660 image,
661 input_size=self.image_size,
662 min_num=self.min_num,
663 max_num=self.max_num,
664 use_thumbnail=self.use_thumbnail,
665 )
666 for image in images
667 ]
668 num_patches_list = [pixel_value.shape[0] for pixel_value in pixel_values]
669 pixel_values = torch.cat(pixel_values, dim=0)
670 for num_patches in num_patches_list:
671 context_tokens = IMG_CONTEXT * self.num_image_token * num_patches
672 image_tokens = IMG_START + context_tokens + IMG_END
673 text = text.replace("<image>", image_tokens, 1)
674 prompt = self.tokenizer(text, return_tensors="pt")
675 prompt.update({"pixel_values": pixel_values})
676 return prompt
677
678 img_context_token_id = hf_model.tokenizer.convert_tokens_to_ids("<IMG_CONTEXT>")
679 hf_model.model.img_context_token_id = img_context_token_id
680 hf_model.processor = SkyworkR1VProcessor(hf_model)
681 hf_model.model.get_output_embeddings = (
682 lambda: hf_model.model.language_model.get_output_embeddings()
683 )
684 hf_model.model.generate = types.MethodType(_internvl_generate, hf_model.model)
685 return hf_model
686
687
688 def internvl_patch_hf_runner(hf_model: HfRunner) -> HfRunner:
689 """Patches and returns an instance of the HfRunner to use for InternVL."""
690
691 class InternVLProcessor:
692 """A simple processor for InternVL2 which misses a processor."""
693
694 def __init__(self, hf_runner: HfRunner):
695 self.num_image_token = hf_runner.model.num_image_token
696 self.tokenizer = hf_runner.tokenizer
697
698 self.config = AutoConfig.from_pretrained(
699 hf_runner.model_name, trust_remote_code=True
700 )
701 self.vision_config = self.config.vision_config
702 self.use_thumbnail = self.config.use_thumbnail
703 self.min_num = self.config.min_dynamic_patch
704 self.max_num = self.config.max_dynamic_patch
705 self.image_size = self.vision_config.image_size
706
707 def __call__(
708 self,
709 text: str,
710 images: Image | list[Image] = None,
711 videos: npt.NDArray | list[npt.NDArray] = None,
712 **kwargs,
713 ):
714 from vllm.transformers_utils.processors.internvl import (
715 image_to_pixel_values_internvl,
716 video_to_pixel_values_internvl,
717 )
718
719 IMG_START = "<img>"
720 IMG_END = "</img>"
721 IMG_CONTEXT = "<IMG_CONTEXT>"
722
723 images = [images] if isinstance(images, Image) else images
724 videos = [videos] if isinstance(videos, np.ndarray) else videos
725 if images is not None:
726 pixel_values_images = [
727 image_to_pixel_values_internvl(
728 image,
729 input_size=self.image_size,
730 min_num=self.min_num,
731 max_num=self.max_num,
732 use_thumbnail=self.use_thumbnail,
733 )
734 for image in images
735 ]
736 num_patches_images = [
737 pixel_value.shape[0] for pixel_value in pixel_values_images
738 ]
739 else:
740 pixel_values_images, num_patches_images = [], []
741
742 if videos is not None:
743 pixel_values_videos = [
744 video_to_pixel_values_internvl(
745 video,
746 input_size=self.image_size,
747 min_num=1,
748 max_num=1,
749 use_thumbnail=False,
750 )
751 for video in videos
752 ]
753 num_patches_videos = [
754 pixel_value.shape[0] for pixel_value in pixel_values_videos
755 ]
756 else:
757 pixel_values_videos, num_patches_videos = [], []
758
759 pixel_values = []
760 while ("<image>" in text) or ("<video>" in text):
761 image_index = text.find("<image>")
762 video_index = text.find("<video>")
763 if image_index == -1 or (
764 video_index > -1 and video_index < image_index
765 ):
766 num_patches = num_patches_videos.pop(0)
767 pixel_values.append(pixel_values_videos.pop(0))
768 context_tokens = (
769 IMG_START + IMG_CONTEXT * self.num_image_token + IMG_END
770 )
771 video_tokens = "".join(
772 [f"Frame{i + 1}: {context_tokens}" for i in range(num_patches)]
773 )
774 text = text.replace("<video>", video_tokens, 1)
775 else:
776 num_patches = num_patches_images.pop(0)
777 pixel_values.append(pixel_values_images.pop(0))
778 context_tokens = IMG_CONTEXT * self.num_image_token * num_patches
779 image_tokens = IMG_START + context_tokens + IMG_END
780 text = text.replace("<image>", image_tokens, 1)
781 pixel_values = torch.cat(pixel_values, dim=0)
782
783 prompt = self.tokenizer(text, return_tensors="pt")
784 prompt.update({"pixel_values": pixel_values})
785 return prompt
786
787 img_context_token_id = hf_model.tokenizer.convert_tokens_to_ids("<IMG_CONTEXT>")
788 hf_model.model.img_context_token_id = img_context_token_id
789 hf_model.processor = InternVLProcessor(hf_model)
790 hf_model.model.get_output_embeddings = (
791 lambda: hf_model.model.language_model.get_output_embeddings()
792 )
793 hf_model.model.generate = types.MethodType(_internvl_generate, hf_model.model)
794 return hf_model
795
796
797 def _internvl_generate(
798 self,
799 pixel_values: torch.FloatTensor,
800 input_ids: torch.FloatTensor,
801 attention_mask: torch.LongTensor | None = None,
802 **generate_kwargs,
803 ) -> torch.LongTensor:
804 """Generate method for InternVL2 model without fixed use_cache."""
805 assert self.img_context_token_id is not None
806 target_dtype = next(self.parameters()).dtype
807 vit_embeds = self.extract_feature(pixel_values.to(target_dtype))
808 input_embeds = self.language_model.get_input_embeddings()(input_ids)
809 B, N, C = input_embeds.shape
810 input_embeds = input_embeds.reshape(B * N, C)
811
812 input_ids = input_ids.reshape(B * N)
813 selected = input_ids == self.img_context_token_id
814 assert selected.sum() != 0
815 input_embeds[selected] = vit_embeds.reshape(-1, C).to(input_embeds.device)
816
817 input_embeds = input_embeds.reshape(B, N, C)
818
819 forward_kwargs = dict(
820 inputs_embeds=input_embeds,
821 attention_mask=attention_mask,
822 )
823 if getattr(self, "use_visual_token_mask", False):
824 visual_token_mask = selected.reshape(B, N, 1).to(input_embeds.dtype)
825 forward_kwargs["visual_token_mask"] = visual_token_mask
826
827 # e.g. InternVL2-2B
828 if not isinstance(self.language_model, GenerationMixin):
829 pytest.skip("HF impl is not compatible with current transformers")
830
831 outputs = self.language_model.generate(
832 **forward_kwargs,
833 **generate_kwargs,
834 )
835
836 return outputs
837
838
839 def _restore_resampler_pos_cache(hf_model: HfRunner) -> None:
840 """Recompute the resampler's 2D sin/cos position cache after loading.
841
842 `from_pretrained` materializes non-persistent buffers as uninitialized
843 memory, leaving `pos_embed` as zeros or NaN instead of the values computed
844 in `__init__`.
845 """
846 restored = 0
847 for module in hf_model.model.modules():
848 if not hasattr(module, "_set_2d_pos_cache"):
849 continue
850 module._set_2d_pos_cache(module.max_size, module.pos_embed.device)
851 restored += 1
852 assert restored, "no resampler pos cache found to restore"
853
854
855 def minicpmv_25_patch_hf_runner(hf_model: HfRunner) -> HfRunner:
856 orig_generate = hf_model.model.generate
857
858 def _generate(
859 self,
860 *args,
861 input_ids=None,
862 pixel_values=None,
863 image_sizes=None,
864 image_bound=None,
865 tgt_sizes=None,
866 **kwargs,
867 ):
868 model_inputs = {
869 "input_ids": input_ids,
870 "pixel_values": pixel_values,
871 "image_sizes": image_sizes,
872 "image_bound": image_bound,
873 "tgt_sizes": tgt_sizes,
874 }
875 for k in list(model_inputs.keys()):
876 if model_inputs[k] is None:
877 model_inputs.pop(k)
878
879 return orig_generate(model_inputs, *args, decode_text=False, **kwargs)
880
881 hf_model.model.generate = types.MethodType(_generate, hf_model.model)
882
883 return hf_model
884
885
886 def minicpmo_26_patch_hf_runner(hf_model: HfRunner) -> HfRunner:
887 orig_generate = hf_model.model.generate
888
889 def _generate(self, *args, image_sizes=None, **kwargs):
890 return orig_generate(*args, decode_text=False, **kwargs)
891
892 hf_model.model.generate = types.MethodType(_generate, hf_model.model)
893
894 return hf_model
895
896
897 def minicpmv_26_patch_hf_runner(hf_model: HfRunner) -> HfRunner:
898 _restore_resampler_pos_cache(hf_model)
899
900 orig_generate = hf_model.model.generate
901
902 def _generate(self, *args, image_sizes=None, **kwargs):
903 return orig_generate(*args, decode_text=False, **kwargs)
904
905 hf_model.model.generate = types.MethodType(_generate, hf_model.model)
906
907 return hf_model
908
909
910 def molmo_patch_hf_runner(hf_model: HfRunner) -> HfRunner:
911 """Patches and returns an instance of the HfRunner to use for Molmo."""
912 hf_processor = hf_model.processor
913
914 def _processor(*args, **kwargs):
915 return hf_processor.process(*args, **kwargs)
916
917 hf_model.processor = _processor
918
919 def _generate(self, max_new_tokens=None, do_sample=None, **kwargs):
920 batch = {
921 k: kwargs.pop(k).unsqueeze(0)
922 for k in ("input_ids", "images", "image_input_idx", "image_masks")
923 if k in kwargs
924 }
925 batch = BatchFeature(batch).to(dtype=self.dtype)
926
927 return self.generate_from_batch(
928 batch,
929 generation_config=GenerationConfig(
930 max_new_tokens=max_new_tokens,
931 stop_strings="<|endoftext|>",
932 do_sample=do_sample,
933 ),
934 **kwargs,
935 )
936
937 hf_model.model.generate = types.MethodType(_generate, hf_model.model)
938
939 return hf_model
940
941
942 def ovis_patch_hf_runner(hf_model: HfRunner) -> HfRunner:
943 """Patches and returns an instance of the HfRunner to use for Ovis2."""
944 hf_model.model.get_output_embeddings = (
945 lambda: hf_model.model.llm.get_output_embeddings()
946 )
947
948 def processor(*args, text="", images=None, **kwargs):
949 text_tokenizer = hf_model.model.get_text_tokenizer()
950 images = [images] if isinstance(images, Image) else images
951
952 prompt_start_and_end = {
953 "qwen2": ("<|im_start|>user\n", "<|im_end|>\n"),
954 "llama": ("<|start_header_id|>user<|end_header_id|>\n\n", "<|eot_id|>"),
955 "gemma2": ("<start_of_turn>user\n", "<end_of_turn>\n"),
956 }
957 for start, end in prompt_start_and_end.values():
958 if start in text and end in text:
959 text = text.split(start)[1].split(end)[0]
960 break
961
962 prompt, input_ids, pixel_values = hf_model.model.preprocess_inputs(
963 text_or_conversations=text, images=images
964 )
965 attention_mask = torch.ne(input_ids, text_tokenizer.pad_token_id)
966
967 inputs = {
968 "inputs": input_ids.unsqueeze(0),
969 "pixel_values": pixel_values.unsqueeze(0),
970 "attention_mask": attention_mask.unsqueeze(0),
971 }
972 return BatchFeature(data=inputs, tensor_type="pt")
973
974 hf_model.processor = processor
975 return hf_model
976
977
978 def ovis2_5_patch_hf_runner(hf_model: HfRunner) -> HfRunner:
979 """Patches and returns an instance of the HfRunner to use for Ovis2."""
980 hf_model.model.get_output_embeddings = (
981 lambda: hf_model.model.llm.get_output_embeddings()
982 )
983
984 def processor(*args, text="", images=None, videos=None, **kwargs):
985 if images is None:
986 images = []
987 else:
988 images = [images] if isinstance(images, Image) else images
989 if videos is None:
990 videos = []
991 else:
992 videos = [videos] if isinstance(videos, np.ndarray) else videos
993 videos = [[PIL.Image.fromarray(frame) for frame in vid] for vid in videos]
994
995 prompt_start_and_end = {
996 "qwen2": ("<|im_start|>user\n", "<|im_end|>\n"),
997 "llama": ("<|start_header_id|>user<|end_header_id|>\n\n", "<|eot_id|>"),
998 "gemma2": ("<start_of_turn>user\n", "<end_of_turn>\n"),
999 }
1000 for start, end in prompt_start_and_end.values():
1001 if start in text and end in text:
1002 text = text.split(start)[1].split(end)[0]
1003 break
1004
1005 images_message = [{"type": "image", "image": img} for img in images]
1006 videos_message = [{"type": "video", "video": vid} for vid in videos]
1007
1008 messages = [
1009 {
1010 "role": "user",
1011 "content": [
1012 *images_message,
1013 *videos_message,
1014 {"type": "text", "text": text},
1015 ],
1016 }
1017 ]
1018
1019 input_ids, pixel_values, grid_thws = hf_model.model.preprocess_inputs(
1020 messages=messages, enable_thinking=True
1021 )
1022 inputs = {
1023 "inputs": input_ids,
1024 "pixel_values": pixel_values,
1025 "grid_thws": grid_thws,
1026 }
1027 return BatchFeature(data=inputs, tensor_type="pt")
1028
1029 hf_model.processor = processor
1030 return hf_model
1031
1032
1033 def paddleocr_vl_patch_hf_runner(hf_model: HfRunner) -> HfRunner:
1034 """Patches the HfRunner to fix create_causal_mask API mismatch.
1035
1036 The PaddleOCR-VL HF model passes `inputs_embeds` to create_causal_mask,
1037 but transformers renamed this parameter to `input_embeds`.
1038 """
1039 import sys
1040
1041 model_module = sys.modules.get(type(hf_model.model.model).__module__)
1042 if model_module is None:
1043 return hf_model
1044
1045 original_create_causal_mask = getattr(model_module, "create_causal_mask", None)
1046 if original_create_causal_mask is None:
1047 return hf_model
1048
1049 def patched_create_causal_mask(*args, **kwargs):
1050 if "inputs_embeds" in kwargs:
1051 kwargs["input_embeds"] = kwargs.pop("inputs_embeds")
1052 return original_create_causal_mask(*args, **kwargs)
1053
1054 model_module.create_causal_mask = patched_create_causal_mask # type: ignore[attr-defined]
1055 return hf_model
1056
1057
1058 def qwen2_5_omni_patch_hf_runner(hf_model: HfRunner) -> HfRunner:
1059 """Patches and returns an instance of the HfRunner for Qwen2.5-Omni."""
1060 thinker = hf_model.model.thinker
1061 thinker.get_output_embeddings = lambda: thinker.lm_head
1062 hf_model.model = thinker
1063 return hf_model
1064
1065
1066 def qwen3_vl_patch_hf_runner(hf_model: HfRunner) -> HfRunner:
1067 """Patches and returns an instance of the HfRunner to use for GLM4.1V."""
1068 hf_processor = hf_model.processor
1069
1070 def processor(*args, videos=None, **kwargs):
1071 if videos is not None and is_list_of(videos, tuple):
1072 # batched multi videos
1073 do_sample_frames = {video[1]["do_sample_frames"] for video in videos}
1074 assert len(do_sample_frames) == 1
1075 if kwargs.get("do_sample_frames") is None:
1076 kwargs["do_sample_frames"] = do_sample_frames
1077 video_metadata = [
1078 [
1079 VideoMetadata(
1080 **{k: v for k, v in video[1].items() if k != "do_sample_frames"}
1081 )
1082 ]
1083 for video in videos
1084 ]
1085 videos = [[video[0]] for video in videos]
1086 elif videos is not None and isinstance(videos, tuple):
1087 # single video
1088 do_sample_frames = videos[1]["do_sample_frames"]
1089 if kwargs.get("do_sample_frames") is None:
1090 kwargs["do_sample_frames"] = do_sample_frames
1091 video_metadata = [
1092 [
1093 VideoMetadata(
1094 **{
1095 k: v
1096 for k, v in videos[1].items()
1097 if k != "do_sample_frames"
1098 }
1099 )
1100 ]
1101 ]
1102 videos = [[videos[0]]]
1103 else:
1104 video_metadata = None
1105
1106 return hf_processor(
1107 *args, videos=videos, video_metadata=video_metadata, **kwargs
1108 )
1109
1110 hf_model.processor = processor
1111 return hf_model
1112
1113
1114 def voxtral_patch_hf_runner(hf_model: "HfRunner") -> "HfRunner":
1115 """Patch HfRunner for Voxtral's conversation-based processor.
1116
1117 Two issues in HfRunner require patching:
1118
1119 1. VoxtralProcessor requires ``apply_chat_template()`` with conversation
1120 dicts (accepting ``url``, ``path``, or ``base64`` audio) rather than
1121 the standard ``processor(text=, audio=, sampling_rate=)`` interface.
1122 2. HfRunner.get_inputs cannot handle multi-audio per prompt because it
1123 incorrectly unpacks ``[(arr1, sr1), (arr2, sr2)]`` via a ``len == 2`` check.
1124
1125 We override ``get_inputs`` to build conversation dicts and call
1126 ``apply_chat_template`` directly, bypassing both issues. We also wrap
1127 ``model.generate`` to strip prompt tokens before decoding, since
1128 HfRunner.generate calls batch_decode on the full sequence (prompt +
1129 generated).
1130 """
1131 import io
1132
1133 import pybase64 as base64
1134 import soundfile as sf
1135
1136 processor = hf_model.processor
1137
1138 def _audio_to_base64(audio_array, sample_rate: int) -> str:
1139 """Encode a numpy audio array as a base64 WAV string."""
1140 buf = io.BytesIO()
1141 sf.write(buf, audio_array, int(sample_rate), format="WAV")
1142 return base64.b64encode(buf.getvalue()).decode("ascii")
1143
1144 def patched_get_inputs(prompts, images=None, videos=None, audios=None, **kwargs):
1145 all_inputs = []
1146 for i, prompt in enumerate(prompts):
1147 content: list[dict] = []
1148
1149 if audios is not None and audios[i] is not None:
1150 items = audios[i]
1151 if not isinstance(items, list):
1152 items = [items]
1153 for item in items:
1154 if isinstance(item, (list, tuple)) and len(item) == 2:
1155 arr, sr = item
1156 else:
1157 arr, sr = item, 16_000
1158 content.append(
1159 {
1160 "type": "audio",
1161 "base64": _audio_to_base64(arr, sr),
1162 }
1163 )
1164
1165 content.append({"type": "text", "text": prompt})
1166
1167 inputs = processor.apply_chat_template(
1168 [{"role": "user", "content": content}]
1169 )
1170 if hasattr(inputs, "to"):
1171 inputs = inputs.to(dtype=hf_model.dtype)
1172 all_inputs.append(inputs)
1173
1174 return all_inputs
1175
1176 _orig_generate = hf_model.model.generate
1177
1178 def patched_generate(*args, **kwargs):
1179 """Strip prompt tokens so only generated tokens are decoded."""
1180 input_ids = kwargs.get("input_ids")
1181 if input_ids is None and args:
1182 input_ids = args[0]
1183 prompt_len = input_ids.shape[1] if input_ids is not None else 0
1184
1185 output = _orig_generate(*args, **kwargs)
1186 if prompt_len:
1187 if isinstance(output, torch.Tensor):
1188 output = output[:, prompt_len:]
1189 else:
1190 # GenerateDecoderOnlyOutput - trim sequences but preserve
1191 # scores/logits so generate_greedy_logprobs_limit can
1192 # extract per-token logprobs.
1193 output.sequences = output.sequences[:, prompt_len:]
1194 return output
1195
1196 hf_model.get_inputs = patched_get_inputs # type: ignore[method-assign]
1197 hf_model.model.generate = patched_generate # type: ignore[method-assign]
1198 return hf_model
1199
1200
1201 def moondream3_processor(model: str):
1202 from vllm.transformers_utils.processors.moondream3 import Moondream3Processor
1203
1204 return Moondream3Processor.from_pretrained(model, trust_remote_code=True)
1205
1206
1207 def moondream3_patch_hf_runner(hf_model: HfRunner) -> HfRunner:
1208 """Patch HfRunner for Moondream3."""
1209 moondream_processor = hf_model.processor
1210
1211 def processor(*args, text="", images=None, **kwargs):
1212 if images is None:
1213 return moondream_processor(text=text, **kwargs)
1214
1215 images_list = [images] if isinstance(images, Image) else images
1216 return moondream_processor(images=images_list, text=text, **kwargs)
1217
1218 hf_model.processor = processor
1219
1220 # Expose the LM head for logprob extraction.
1221 hf_model.model.get_output_embeddings = lambda: hf_model.model.model.text.lm_head
1222
1223 native_model = hf_model.model.model # MoondreamModel instance
1224
1225 from torch.nn import functional as F
1226
1227 from vllm.model_executor.models.moondream3 import reconstruct_from_crops
1228
1229 # Resolve the placeholder tokens from the tokenizer instead of hard-coding.
1230 image_placeholder_ids = moondream_processor.tokenizer.encode(
1231 "<image>", add_special_tokens=False
1232 )
1233
1234 def _normalize_tiling(tilings):
1235 """Extract (h, w) tuple from various tiling container formats."""
1236 tiling = tilings
1237 if isinstance(tiling, torch.Tensor):
1238 tiling = tuple(tiling.squeeze().tolist())
1239 elif isinstance(tiling, (list, tuple)):
1240 t0 = tiling[0]
1241 if isinstance(t0, torch.Tensor):
1242 tiling = tuple(t0.tolist())
1243 elif isinstance(t0, (list, tuple)):
1244 tiling = tuple(t0)
1245 return tiling
1246
1247 def _encode_vision(pixel_values, tilings):
1248 """Run preprocessed crops through vision encoder + projection."""
1249 device = native_model.device
1250 dtype = native_model.vision.pos_emb.dtype
1251 config = native_model.config
1252
1253 pv = pixel_values
1254 while pv.dim() > 4:
1255 pv = pv.squeeze(0)
1256 pv = pv.to(device=device, dtype=dtype)
1257
1258 features = native_model._vis_enc(pv)
1259 grid_size = config.vision.crop_size // config.vision.enc_patch_size
1260 global_feat = features[0]
1261
1262 if features.shape[0] > 1 and tilings is not None:
1263 tiling = _normalize_tiling(tilings)
1264 local = features[1:].view(-1, grid_size, grid_size, config.vision.enc_dim)
1265 reconstructed = reconstruct_from_crops(
1266 local,
1267 tiling,
1268 config.vision.overlap_margin,
1269 patch_size=1,
1270 )
1271 else:
1272 reconstructed = global_feat.view(
1273 grid_size, grid_size, config.vision.enc_dim
1274 )
1275
1276 return native_model._vis_proj(global_feat, reconstructed)
1277
1278 def _find_subsequence(seq, subseq):
1279 """Find start index of subseq in seq, or None."""
1280 n = len(subseq)
1281 for i in range(len(seq) - n + 1):
1282 if seq[i : i + n] == subseq:
1283 return i
1284 return None
1285
1286 def _generate(
1287 self,
1288 input_ids=None,
1289 pixel_values=None,
1290 tilings=None,
1291 attention_mask=None,
1292 **kwargs,
1293 ):
1294 max_new_tokens = kwargs.get("max_new_tokens", 128)
1295 return_dict = kwargs.get("return_dict_in_generate", False)
1296 output_hs = kwargs.get("output_hidden_states", False)
1297
1298 if pixel_values is None:
1299 sequences = input_ids
1300 if return_dict:
1301 return types.SimpleNamespace(
1302 sequences=sequences,
1303 hidden_states=() if output_hs else None,
1304 )
1305 return sequences
1306
1307 # Processor may return lists; extract the single element.
1308 if isinstance(pixel_values, (list, tuple)):
1309 pixel_values = pixel_values[0]
1310 if (
1311 isinstance(tilings, (list, tuple))
1312 and tilings
1313 and not isinstance(tilings[0], int)
1314 ):
1315 tilings = tilings[0]
1316
1317 hf_model.model._setup_caches()
1318 native_model.use_flex_decoding = False
1319
1320 device = native_model.device
1321 config = native_model.config
1322
1323 with torch.inference_mode():
1324 for block in native_model.text.blocks:
1325 block.kv_cache.k_cache.zero_()
1326 block.kv_cache.v_cache.zero_()
1327
1328 img_emb = _encode_vision(pixel_values, tilings)
1329
1330 bos_emb = F.embedding(
1331 torch.tensor([[config.tokenizer.bos_id]], device=device),
1332 native_model.text.wte,
1333 )
1334 img_input = torch.cat([bos_emb, img_emb.unsqueeze(0)], dim=1)
1335 prefix_len = img_input.size(1)
1336
1337 mask = native_model.attn_mask[:, :, :prefix_len, :]
1338 pos_ids = torch.arange(prefix_len, dtype=torch.long, device=device)
1339 native_model._prefill(img_input, mask, pos_ids, None)
1340
1341 ids = input_ids.squeeze(0).tolist()
1342 img_start = _find_subsequence(ids, image_placeholder_ids)
1343
1344 if img_start is None:
1345 sequences = input_ids
1346 if return_dict:
1347 return types.SimpleNamespace(
1348 sequences=sequences,
1349 hidden_states=() if output_hs else None,
1350 )
1351 return sequences
1352
1353 prompt_tokens = ids[img_start + len(image_placeholder_ids) :]
1354
1355 if not prompt_tokens:
1356 sequences = input_ids
1357 if return_dict:
1358 return types.SimpleNamespace(
1359 sequences=sequences,
1360 hidden_states=() if output_hs else None,
1361 )
1362 return sequences
1363
1364 prompt_tensor = torch.tensor([prompt_tokens], device=device)
1365 prompt_emb = F.embedding(prompt_tensor, native_model.text.wte)
1366 prompt_len = prompt_emb.size(1)
1367
1368 mask = native_model.attn_mask[:, :, prefix_len : prefix_len + prompt_len, :]
1369 pos_ids = torch.arange(
1370 prefix_len,
1371 prefix_len + prompt_len,
1372 dtype=torch.long,
1373 device=device,
1374 )
1375 hidden = native_model._prefill(prompt_emb, mask, pos_ids, None)
1376 pos = prefix_len + prompt_len
1377
1378 hidden_last = native_model.text.post_ln(hidden[:, -1:, :])
1379 logits = native_model.text.lm_head(hidden_last.squeeze(1))
1380
1381 generated = []
1382 all_hidden_states = []
1383 # Record the hidden state that predicted each generated token.
1384 prev_hs = hidden_last
1385 for _ in range(max_new_tokens):
1386 next_token = logits.argmax(dim=-1).item()
1387 if next_token == 0:
1388 break
1389 generated.append(next_token)
1390 if output_hs:
1391 all_hidden_states.append((prev_hs,))
1392
1393 next_emb = F.embedding(
1394 torch.tensor([[next_token]], device=device),
1395 native_model.text.wte,
1396 )
1397 mask = native_model.attn_mask[:, :, pos : pos + 1, :]
1398 pos_ids_step = torch.tensor([pos], dtype=torch.long, device=device)
1399 hidden = native_model._prefill(next_emb, mask, pos_ids_step, None)
1400 hidden_last = native_model.text.post_ln(hidden[:, -1:, :])
1401 prev_hs = hidden_last
1402 logits = native_model.text.lm_head(hidden_last.squeeze(1))
1403 pos += 1
1404
1405 result_ids = ids + generated
1406 sequences = torch.tensor([result_ids], device=device)
1407
1408 if return_dict:
1409 return types.SimpleNamespace(
1410 sequences=sequences,
1411 hidden_states=tuple(all_hidden_states) if output_hs else None,
1412 )
1413 return sequences
1414
1415 hf_model.model.generate = types.MethodType(_generate, hf_model.model)
1416 return hf_model