[Model] Remove Tarsier, Tarsier2 (#47143)

Signed-off-by: Harry Mellor <19981378+hmellor@users.noreply.github.com>

Harry Mellor committed Jun 30, 2026 at 14:20 UTC 5dc36a4fa507740c94ad30c8dcde3d7620e766d3
12 files changed +2 -872
docs/models/supported_models.md
-2
@@ -626,8 +626,6 @@ These models primarily accept the [`LLM.generate`](./generative_models.md#llmgen
626 | `Step3VLForConditionalGeneration` | Step3-VL | T + I<sup>+</sup> | `stepfun-ai/step3` | | ✅︎ |
627 | `StepVLForConditionalGeneration` | Step3-VL-10B | T + I<sup>+</sup> | `stepfun-ai/Step3-VL-10B` | | ✅︎ |
628 | `Step3p7ForConditionalGeneration` | Step-3.7-Flash | T + I<sup>+</sup> | `stepfun-ai/Step-3.7-Flash` | | ✅︎ |
629 -| `TarsierForConditionalGeneration` | Tarsier | T + I<sup>E+</sup> | `omni-search/Tarsier-7b`, `omni-search/Tarsier-34b` | | ✅︎ |
630 -| `Tarsier2ForConditionalGeneration`<sup>^</sup> | Tarsier2 | T + I<sup>E+</sup> + V<sup>E+</sup> | `omni-research/Tarsier2-Recap-7b`, `omni-research/Tarsier2-7b-0115` | | ✅︎ |
629 | `UltravoxModel` | Ultravox | T + A<sup>E+</sup> | `fixie-ai/ultravox-v0_5-llama-3_2-1b` | ✅︎ | ✅︎ |
630 | `UnlimitedOCRForCausalLM` | Unlimited-OCR | T + I<sup>+</sup> | `baidu/Unlimited-OCR`, etc. | ✅︎ | ✅︎ |
631
examples/generate/multimodal/vision_language_multi_image_offline.py
-51
@@ -1275,55 +1275,6 @@ def load_step_vl(question: str, image_urls: list[str]) -> ModelRequestData:
1275 )
1276
1277
1278 -def load_tarsier(question: str, image_urls: list[str]) -> ModelRequestData:
1279 - model_name = "omni-research/Tarsier-7b"
1280 -
1281 - engine_args = EngineArgs(
1282 - model=model_name,
1283 - trust_remote_code=True,
1284 - max_model_len=4096,
1285 - limit_mm_per_prompt={"image": len(image_urls)},
1286 - )
1287 -
1288 - prompt = f"USER: {'<image>' * len(image_urls)}\n{question}\n ASSISTANT:"
1289 - image_data = [fetch_image(url) for url in image_urls]
1290 -
1291 - return ModelRequestData(
1292 - engine_args=engine_args,
1293 - prompt=prompt,
1294 - image_data=image_data,
1295 - )
1296 -
1297 -
1298 -def load_tarsier2(question: str, image_urls: list[str]) -> ModelRequestData:
1299 - model_name = "omni-research/Tarsier2-Recap-7b"
1300 -
1301 - engine_args = EngineArgs(
1302 - model=model_name,
1303 - trust_remote_code=True,
1304 - max_model_len=32768,
1305 - limit_mm_per_prompt={"image": len(image_urls)},
1306 - hf_overrides={
1307 - "architectures": ["Tarsier2ForConditionalGeneration"],
1308 - "model_type": "tarsier2",
1309 - },
1310 - )
1311 -
1312 - prompt = (
1313 - "<|im_start|>system\nYou are a helpful assistant.<|im_end|>\n"
1314 - f"<|im_start|>user\n<|vision_start|>{'<|image_pad|>' * len(image_urls)}"
1315 - f"<|vision_end|>{question}<|im_end|>\n"
1316 - "<|im_start|>assistant\n"
1317 - )
1318 - image_data = [fetch_image(url) for url in image_urls]
1319 -
1320 - return ModelRequestData(
1321 - engine_args=engine_args,
1322 - prompt=prompt,
1323 - image_data=image_data,
1324 - )
1325 -
1326 -
1278 # GLM-4.1V
1279 def load_glm4_1v(question: str, image_urls: list[str]) -> ModelRequestData:
1280 model_name = "zai-org/GLM-4.1V-9B-Thinking"
@@ -1507,8 +1458,6 @@ model_example_map = {
1458 "smolvlm": load_smolvlm,
1459 "step3": load_step3,
1460 "stepvl": load_step_vl,
1510 - "tarsier": load_tarsier,
1511 - "tarsier2": load_tarsier2,
1461 "glm4_1v": load_glm4_1v,
1462 "glm4_5v": load_glm4_5v,
1463 "glm4_5v_fp8": load_glm4_5v_fp8,
examples/generate/multimodal/vision_language_offline.py
-61
@@ -2347,65 +2347,6 @@ def run_step_vl(questions: list[str], modality: str) -> ModelRequestData:
2347 )
2348
2349
2350 -# omni-research/Tarsier-7b
2351 -def run_tarsier(questions: list[str], modality: str) -> ModelRequestData:
2352 - assert modality == "image"
2353 - model_name = "omni-research/Tarsier-7b"
2354 -
2355 - engine_args = EngineArgs(
2356 - model=model_name,
2357 - trust_remote_code=True,
2358 - max_model_len=4096,
2359 - limit_mm_per_prompt={modality: 1},
2360 - )
2361 - prompts = [(f"USER: <image>\n{question} ASSISTANT:") for question in questions]
2362 -
2363 - return ModelRequestData(
2364 - engine_args=engine_args,
2365 - prompts=prompts,
2366 - )
2367 -
2368 -
2369 -def run_tarsier2(questions: list[str], modality: str) -> ModelRequestData:
2370 - model_name = "omni-research/Tarsier2-Recap-7b"
2371 -
2372 - mm_limit = {"image": 1, "video": 1} if modality == "image+video" else {modality: 1}
2373 - engine_args = EngineArgs(
2374 - model=model_name,
2375 - max_model_len=4096,
2376 - hf_overrides={
2377 - "architectures": ["Tarsier2ForConditionalGeneration"],
2378 - "model_type": "tarsier2",
2379 - },
2380 - limit_mm_per_prompt=mm_limit,
2381 - )
2382 -
2383 - image_placeholder = "<|vision_start|><|image_pad|><|vision_end|>"
2384 - video_placeholder = "<|vision_start|><|video_pad|><|vision_end|>"
2385 -
2386 - if modality == "image":
2387 - placeholder = image_placeholder
2388 - elif modality == "video":
2389 - placeholder = video_placeholder
2390 - elif modality == "image+video":
2391 - placeholder = image_placeholder + video_placeholder
2392 -
2393 - prompts = [
2394 - (
2395 - "<|im_start|>system\nYou are a helpful assistant.<|im_end|>\n"
2396 - f"<|im_start|>user\n{placeholder}"
2397 - f"{question}<|im_end|>\n"
2398 - "<|im_start|>assistant\n"
2399 - )
2400 - for question in questions
2401 - ]
2402 -
2403 - return ModelRequestData(
2404 - engine_args=engine_args,
2405 - prompts=prompts,
2406 - )
2407 -
2408 -
2350 model_example_map = {
2351 "aria": run_aria,
2352 "aya_vision": run_aya_vision,
@@ -2479,8 +2420,6 @@ model_example_map = {
2420 "smolvlm": run_smolvlm,
2421 "step3": run_step3,
2422 "stepvl": run_step_vl,
2482 - "tarsier": run_tarsier,
2483 - "tarsier2": run_tarsier2,
2423 }
2424
2425
tests/models/multimodal/generation/test_common.py
-25
@@ -993,31 +993,6 @@ VLM_TEST_SETTINGS = {
993 hf_output_post_proc=model_utils.smolvlm_trunc_hf_output,
994 num_logprobs=10,
995 ),
996 - "tarsier": VLMTestInfo(
997 - models=["omni-research/Tarsier-7b"],
998 - test_type=(VLMTestType.IMAGE, VLMTestType.MULTI_IMAGE),
999 - prompt_formatter=lambda img_prompt: f"USER: {img_prompt} ASSISTANT:",
1000 - max_model_len=4096,
1001 - max_num_seqs=2,
1002 - auto_cls=AutoModelForImageTextToText,
1003 - patch_hf_runner=model_utils.tarsier_patch_hf_runner,
1004 - ),
1005 - "tarsier2": VLMTestInfo(
1006 - models=["omni-research/Tarsier2-Recap-7b"],
1007 - test_type=(
1008 - VLMTestType.IMAGE,
1009 - VLMTestType.MULTI_IMAGE,
1010 - VLMTestType.VIDEO,
1011 - ),
1012 - prompt_formatter=lambda img_prompt: f"<|im_start|>system\nYou are a helpful assistant.<|im_end|>\n<|im_start|>user\n{img_prompt}<|im_end|>\n<|im_start|>assistant\n", # noqa: E501
1013 - img_idx_to_prompt=lambda idx: "<|vision_start|><|image_pad|><|vision_end|>",
1014 - video_idx_to_prompt=lambda idx: "<|vision_start|><|video_pad|><|vision_end|>",
1015 - max_model_len=4096,
1016 - max_num_seqs=2,
1017 - auto_cls=AutoModelForImageTextToText,
1018 - image_size_factors=[(0.25,), (0.25, 0.25, 0.25), (0.25, 0.2, 0.15)],
1019 - marks=[pytest.mark.skip("Model initialization hangs")],
1020 - ),
996 ### Tensor parallel / multi-gpu broadcast tests
997 "chameleon-broadcast": VLMTestInfo(
998 models=["facebook/chameleon-7b"],
tests/models/multimodal/generation/vlm_utils/model_utils.py
-12
@@ -1220,18 +1220,6 @@ def qwen3_vl_patch_hf_runner(hf_model: HfRunner) -> HfRunner:
1220 return hf_model
1221
1222
1223 -def tarsier_patch_hf_runner(hf_model: HfRunner) -> HfRunner:
1224 - from vllm.model_executor.models.tarsier import get_vision_encoder_info
1225 -
1226 - vision_encoder_info = get_vision_encoder_info(hf_model.config)
1227 -
1228 - hf_processor = hf_model.processor
1229 - if hf_processor.patch_size is None:
1230 - hf_processor.patch_size = vision_encoder_info.get_patch_size()
1231 -
1232 - return hf_model
1233 -
1234 -
1223 def voxtral_patch_hf_runner(hf_model: "HfRunner") -> "HfRunner":
1224 """Patch HfRunner for Voxtral's conversation-based processor.
1225
tests/models/registry.py
-16
@@ -1369,22 +1369,6 @@ _MULTIMODAL_EXAMPLE_MODELS = {
1369 "fixie-ai/ultravox-v0_5-llama-3_2-1b",
1370 trust_remote_code=True,
1371 ),
1372 - "TarsierForConditionalGeneration": _HfExamplesInfo("omni-research/Tarsier-7b"),
1373 - "Tarsier2ForConditionalGeneration": _HfExamplesInfo(
1374 - "omni-research/Tarsier2-Recap-7b",
1375 - hf_overrides={
1376 - "architectures": ["Tarsier2ForConditionalGeneration"],
1377 - "model_type": "tarsier2",
1378 - },
1379 - max_transformers_version="5.3",
1380 - transformers_version_reason={
1381 - "vllm": (
1382 - "Qwen2VLConfig was split into Qwen2VLConfig + "
1383 - "Qwen2VLTextConfig in transformers v5, breaking "
1384 - "attribute access (num_attention_heads, hidden_size, etc.)"
1385 - )
1386 - },
1387 - ),
1372 "VoxtralForConditionalGeneration": _HfExamplesInfo(
1373 "mistralai/Voxtral-Mini-3B-2507",
1374 tokenizer_mode="mistral",
vllm/model_executor/models/qwen2_vl.py
-82
@@ -41,7 +41,6 @@ from transformers.models.qwen2_vl.configuration_qwen2_vl import (
41 Qwen2VLVisionConfig,
42 )
43 from transformers.models.qwen2_vl.image_processing_qwen2_vl import smart_resize
44 -from transformers.models.qwen2_vl.video_processing_qwen2_vl import Qwen2VLVideoProcessor
44
45 from vllm.config import VllmConfig
46 from vllm.config.multimodal import BaseDummyOptions
@@ -86,7 +85,6 @@ from vllm.multimodal.processing import (
85 PromptUpdate,
86 )
87 from vllm.sequence import IntermediateTensors
89 -from vllm.tokenizers import TokenizerLike
88 from vllm.utils.tensor_schema import TensorSchema, TensorShape
89 from vllm.v1.attention.backends.registry import AttentionBackendEnum
90 from vllm.v1.worker.encoder_cudagraph_defs import EncoderCudaGraphReplayBuffers
@@ -1755,83 +1753,3 @@ class Qwen2VLForConditionalGeneration(
1753 vision_config = hf_config.vision_config
1754 merge_size = vision_config.spatial_merge_size
1755 return num_vision_tokens // merge_size**2
1758 -
1759 -
1760 -class Tarsier2MultiModalProcessor(Qwen2VLMultiModalProcessor):
1761 - pass
1762 -
1763 -
1764 -class Tarsier2ImageProcessor(Qwen2VLImageProcessor):
1765 - def __init__(
1766 - self,
1767 - size: dict[str, int] | None = None,
1768 - **kwargs,
1769 - ) -> None:
1770 - if size is not None and "min_pixels" in size and "max_pixels" in size:
1771 - # Remap if Tarsier2-specific format is provided
1772 - remapped_size = {
1773 - "shortest_edge": size["min_pixels"],
1774 - "longest_edge": size["max_pixels"],
1775 - }
1776 - super().__init__(size=remapped_size, **kwargs)
1777 - else:
1778 - super().__init__(size=size, **kwargs)
1779 -
1780 -
1781 -class Tarsier2Processor(Qwen2VLProcessor):
1782 - def __init__(
1783 - self,
1784 - image_processor: Tarsier2ImageProcessor,
1785 - tokenizer: TokenizerLike,
1786 - video_processor: Qwen2VLVideoProcessor,
1787 - **kwargs,
1788 - ):
1789 - super().__init__(
1790 - image_processor=image_processor,
1791 - tokenizer=tokenizer,
1792 - video_processor=video_processor,
1793 - chat_template=None,
1794 - **kwargs,
1795 - )
1796 -
1797 -
1798 -class Tarsier2ProcessingInfo(Qwen2VLProcessingInfo):
1799 - def get_hf_config(self) -> Qwen2VLConfig:
1800 - model_path = self.ctx.model_config.model
1801 - correct_config = Qwen2VLConfig.from_pretrained(model_path)
1802 -
1803 - return correct_config
1804 -
1805 - def get_hf_processor(self, **kwargs: object) -> Tarsier2Processor:
1806 - vision_config = self.ctx.get_hf_image_processor_config()
1807 - image_processor = Tarsier2ImageProcessor(**vision_config)
1808 - video_processor = Qwen2VLVideoProcessor(**vision_config)
1809 - return Tarsier2Processor(
1810 - image_processor=image_processor,
1811 - video_processor=video_processor,
1812 - tokenizer=self.get_tokenizer(),
1813 - **kwargs,
1814 - )
1815 -
1816 - def get_image_processor(self) -> Tarsier2ImageProcessor:
1817 - return Tarsier2ImageProcessor(**self.ctx.get_hf_image_processor_config())
1818 -
1819 -
1820 -@MULTIMODAL_REGISTRY.register_processor(
1821 - Tarsier2MultiModalProcessor,
1822 - info=Tarsier2ProcessingInfo,
1823 - dummy_inputs=Qwen2VLDummyInputsBuilder,
1824 -)
1825 -class Tarsier2ForConditionalGeneration(Qwen2VLForConditionalGeneration):
1826 - hf_to_vllm_mapper = WeightsMapper(
1827 - orig_to_new_prefix={
1828 - "vision_tower.": "visual.",
1829 - }
1830 - )
1831 -
1832 - def load_weights(self, weights: Iterable[tuple[str, torch.Tensor]]) -> set[str]:
1833 - skip_prefixes = []
1834 - if self.visual is None:
1835 - skip_prefixes.extend(["visual."])
1836 - loader = AutoWeightsLoader(self, skip_prefixes=skip_prefixes)
1837 - return loader.load_weights(weights, mapper=self.hf_to_vllm_mapper)
vllm/model_executor/models/registry.py
+2 -5
@@ -571,11 +571,6 @@ _MULTIMODAL_MODELS = {
571 "StepVLForConditionalGeneration": ("step_vl", "StepVLForConditionalGeneration"),
572 "Step3VLForConditionalGeneration": ("step3_vl", "Step3VLForConditionalGeneration"),
573 "Step3p7ForConditionalGeneration": ("step3p7", "Step3p7ForConditionalGeneration"),
574 - "TarsierForConditionalGeneration": ("tarsier", "TarsierForConditionalGeneration"),
575 - "Tarsier2ForConditionalGeneration": (
576 - "qwen2_vl",
577 - "Tarsier2ForConditionalGeneration",
578 - ),
574 "UltravoxModel": ("ultravox", "UltravoxModel"),
575 "VoxtralForConditionalGeneration": ("voxtral", "VoxtralForConditionalGeneration"),
576 "VoxtralRealtimeGeneration": ("voxtral_realtime", "VoxtralRealtimeGeneration"),
@@ -736,6 +731,8 @@ _PREVIOUSLY_SUPPORTED_MODELS = {
731 "AquilaForCausalLM": "0.24.0",
732 "Grok1ModelForCausalLM": "0.24.0",
733 "Grok1ForCausalLM": "0.24.0",
734 + "TarsierForConditionalGeneration": "0.24.0",
735 + "Tarsier2ForConditionalGeneration": "0.23.0", # last version with Transformers v4
736 }
737
738 _OOT_SUPPORTED_MODELS = {
vllm/model_executor/models/tarsier.py deleted
-591
@@ -1,591 +0,0 @@
1 -# SPDX-License-Identifier: Apache-2.0
2 -# SPDX-FileCopyrightText: Copyright contributors to the vLLM project
3 -
4 -import math
5 -from collections.abc import Iterable, Mapping, Sequence
6 -from typing import Annotated, Final, Literal, Protocol, TypeAlias, TypeVar
7 -
8 -import torch
9 -import torch.nn as nn
10 -from transformers import (
11 - BatchFeature,
12 - CLIPVisionConfig,
13 - PretrainedConfig,
14 - SiglipVisionConfig,
15 -)
16 -from transformers import LlavaConfig as HfLlavaConfig
17 -from transformers.image_utils import ImageInput, get_image_size, to_numpy_array
18 -from transformers.models.llava import LlavaProcessor
19 -from transformers.processing_utils import ProcessingKwargs, Unpack
20 -from transformers.tokenization_utils_base import PreTokenizedInput, TextInput
21 -
22 -from vllm.config import VllmConfig
23 -from vllm.model_executor.layers.activation import get_act_fn
24 -from vllm.model_executor.layers.linear import ColumnParallelLinear, RowParallelLinear
25 -from vllm.model_executor.layers.quantization import QuantizationConfig
26 -from vllm.model_executor.models.llava import LlavaDummyInputsBuilder
27 -from vllm.multimodal import MULTIMODAL_REGISTRY
28 -from vllm.multimodal.inputs import MultiModalFieldConfig, MultiModalKwargsItems
29 -from vllm.multimodal.parse import (
30 - ImageEmbeddingItems,
31 - ImageProcessorItems,
32 - ImageSize,
33 - MultiModalDataItems,
34 -)
35 -from vllm.multimodal.processing import (
36 - BaseMultiModalProcessor,
37 - BaseProcessingInfo,
38 - PromptReplacement,
39 - PromptUpdate,
40 -)
41 -from vllm.sequence import IntermediateTensors
42 -from vllm.utils.tensor_schema import TensorSchema, TensorShape
43 -
44 -from .clip import CLIPVisionModel
45 -from .interfaces import MultiModalEmbeddings, SupportsMultiModal, SupportsPP
46 -from .siglip import SiglipVisionModel
47 -from .utils import (
48 - AutoWeightsLoader,
49 - get_layer_index,
50 - init_vllm_registered_model,
51 - maybe_prefix,
52 -)
53 -from .vision import (
54 - VisionEncoderInfo,
55 - get_num_selected_vision_tokens,
56 - get_vision_encoder_info,
57 -)
58 -
59 -
60 -class TarsierImagePixelInputs(TensorSchema):
61 - """
62 - Dimensions:
63 - - bn: Batch size * number of images
64 - - c: Number of channels (3)
65 - - h: Height
66 - - w: Width
67 - """
68 -
69 - type: Literal["pixel_values"] = "pixel_values"
70 - pixel_values: Annotated[torch.Tensor, TensorShape("bn", 3, "h", "w")]
71 -
72 -
73 -class TarsierImageEmbeddingInputs(TensorSchema):
74 - """
75 - Dimensions:
76 - - bn: Batch size * number of images
77 - - ifs: Image feature size
78 - - hs: Hidden size (must match the hidden size of language model
79 - backbone)
80 - """
81 -
82 - type: Literal["image_embeds"] = "image_embeds"
83 - data: Annotated[torch.Tensor, TensorShape("bn", "ifs", "hs")]
84 -
85 -
86 -TarsierImageInputs: TypeAlias = TarsierImagePixelInputs | TarsierImageEmbeddingInputs
87 -
88 -
89 -class TarsierHfConfig(Protocol): # Based on the Tarsier's LlavaConfig
90 - vision_config: Final[PretrainedConfig]
91 - text_config: Final[PretrainedConfig] # Added from Tarsier's LlavaConfig
92 - image_token_index: Final[int]
93 - vision_feature_select_strategy: Final[str]
94 - vision_feature_layer: Final[int | list[int]]
95 - projector_hidden_act: Final[str]
96 - image_newline_idx: Final[int]
97 - image_new_idx: Final[int]
98 - multimodal_projector_bias: bool = True
99 -
100 -
101 -class TarsierProcessorKwargs(ProcessingKwargs, total=False):
102 - _defaults = {
103 - "text_kwargs": {
104 - "padding": False,
105 - },
106 - "images_kwargs": {},
107 - }
108 -
109 -
110 -class TarsierProcessor(LlavaProcessor):
111 - def __call__(
112 - self,
113 - images: ImageInput = None,
114 - text: TextInput
115 - | PreTokenizedInput
116 - | list[TextInput]
117 - | list[PreTokenizedInput] = None,
118 - audio=None,
119 - videos=None,
120 - **kwargs: Unpack[TarsierProcessorKwargs],
121 - ) -> BatchFeature:
122 - if images is None and text is None:
123 - raise ValueError("You have to specify at least one of `images` or `text`.")
124 -
125 - output_kwargs = self._merge_kwargs(
126 - TarsierProcessorKwargs,
127 - tokenizer_init_kwargs=self.tokenizer.init_kwargs,
128 - **kwargs,
129 - )
130 - if images is not None:
131 - image_inputs = self.image_processor(
132 - images, **output_kwargs["images_kwargs"]
133 - )
134 - else:
135 - image_inputs = {}
136 -
137 - if isinstance(text, str):
138 - text = [text]
139 - elif not isinstance(text, list) and not isinstance(text[0], str):
140 - raise ValueError(
141 - "Invalid input text. Please provide a string, or a list of strings"
142 - )
143 -
144 - # try to expand inputs in processing if we have the necessary parts
145 - prompt_strings = text
146 - if image_inputs.get("pixel_values") is not None:
147 - # Replace the image token with the expanded image token sequence
148 - pixel_values = image_inputs["pixel_values"]
149 - height, width = get_image_size(to_numpy_array(pixel_values[0]))
150 - num_image_tokens = (
151 - (height // self.patch_size) * (width // self.patch_size + 1)
152 - + self.num_additional_image_tokens
153 - + 1
154 - )
155 - if self.vision_feature_select_strategy == "default":
156 - num_image_tokens -= 1
157 -
158 - prompt_strings = []
159 - for sample in text:
160 - sample = sample.replace(
161 - self.image_token, self.image_token * num_image_tokens
162 - )
163 - prompt_strings.append(sample)
164 -
165 - return_tensors = output_kwargs["text_kwargs"].pop("return_tensors", None)
166 - text_inputs = self.tokenizer(prompt_strings, **output_kwargs["text_kwargs"])
167 - return BatchFeature(
168 - data={**text_inputs, **image_inputs}, tensor_type=return_tensors
169 - )
170 -
171 -
172 -class TarsierMultiModalProjector(nn.Module):
173 - def __init__(
174 - self,
175 - vision_hidden_size: int,
176 - text_hidden_size: int,
177 - projector_hidden_act: str,
178 - multimodal_projector_bias: bool,
179 - quant_config: QuantizationConfig | None = None,
180 - prefix: str = "",
181 - ):
182 - super().__init__()
183 -
184 - self.linear_1 = ColumnParallelLinear(
185 - vision_hidden_size,
186 - text_hidden_size,
187 - bias=multimodal_projector_bias,
188 - quant_config=quant_config,
189 - prefix=f"{prefix}.linear_1",
190 - )
191 - self.act = get_act_fn(projector_hidden_act)
192 - self.linear_2 = RowParallelLinear(
193 - text_hidden_size,
194 - text_hidden_size,
195 - bias=multimodal_projector_bias,
196 - quant_config=quant_config,
197 - prefix=f"{prefix}.linear_2",
198 - )
199 -
200 - def forward(self, image_features: torch.Tensor) -> torch.Tensor:
201 - hidden_states, _ = self.linear_1(image_features)
202 - hidden_states = self.act(hidden_states)
203 - hidden_states, _ = self.linear_2(hidden_states)
204 - return hidden_states
205 -
206 -
207 -class TarsierProcessingInfo(BaseProcessingInfo):
208 - def get_hf_config(self) -> TarsierHfConfig:
209 - return self.ctx.get_hf_config(HfLlavaConfig)
210 -
211 - def get_vision_encoder_info(self) -> VisionEncoderInfo:
212 - return get_vision_encoder_info(self.get_hf_config())
213 -
214 - def get_hf_processor(self, **kwargs: object) -> TarsierProcessor:
215 - vision_info = self.get_vision_encoder_info()
216 -
217 - kwargs.setdefault("patch_size", vision_info.get_patch_size())
218 -
219 - return self.ctx.get_hf_processor(TarsierProcessor, **kwargs)
220 -
221 - def get_supported_mm_limits(self) -> Mapping[str, int | None]:
222 - return {"image": None}
223 -
224 - def get_num_image_tokens(
225 - self,
226 - *,
227 - image_width: int,
228 - image_height: int,
229 - ) -> int:
230 - hf_config = self.get_hf_config()
231 - vision_encoder_info = self.get_vision_encoder_info()
232 - num_projected_patches = get_num_selected_vision_tokens(
233 - vision_encoder_info.get_num_image_tokens(
234 - image_width=image_width,
235 - image_height=image_height,
236 - ),
237 - hf_config.vision_feature_select_strategy,
238 - )
239 - if num_projected_patches <= 0:
240 - default_size = self.get_image_size_with_most_features()
241 - num_projected_patches_default = get_num_selected_vision_tokens(
242 - vision_encoder_info.get_num_image_tokens(
243 - image_width=default_size.width,
244 - image_height=default_size.height,
245 - ),
246 - hf_config.vision_feature_select_strategy,
247 - )
248 - if num_projected_patches_default <= 0:
249 - raise ValueError("Could not determine a valid number of image patches.")
250 - num_projected_patches = num_projected_patches_default
251 - num_height_patches = int(math.sqrt(num_projected_patches))
252 - total_image_tokens_for_llm = num_projected_patches + num_height_patches + 1
253 - return total_image_tokens_for_llm
254 -
255 - def get_image_size_with_most_features(self) -> ImageSize:
256 - vision_encoder_info = self.get_vision_encoder_info()
257 - width = height = vision_encoder_info.get_image_size()
258 - return ImageSize(width=width, height=height)
259 -
260 - def get_max_image_tokens(self) -> int:
261 - target_width, target_height = self.get_image_size_with_most_features()
262 - return self.get_num_image_tokens(
263 - image_width=target_width,
264 - image_height=target_height,
265 - )
266 -
267 - def get_image_newline_idx(self) -> int:
268 - return self.get_hf_config().image_newline_idx
269 -
270 - def get_image_new_idx(self) -> int:
271 - return self.get_hf_config().image_new_idx
272 -
273 -
274 -_I_Tarsier = TypeVar("_I_Tarsier", bound=TarsierProcessingInfo)
275 -
276 -
277 -class TarsierDummyInputsBuilder(LlavaDummyInputsBuilder[_I_Tarsier]):
278 - pass
279 -
280 -
281 -class TarsierMultiModalProcessor(BaseMultiModalProcessor[_I_Tarsier]):
282 - def _get_mm_fields_config(
283 - self,
284 - hf_inputs: BatchFeature,
285 - hf_processor_mm_kwargs: Mapping[str, object],
286 - ) -> Mapping[str, MultiModalFieldConfig]:
287 - return dict(
288 - pixel_values=MultiModalFieldConfig.batched("image"),
289 - image_embeds=MultiModalFieldConfig.batched("image"),
290 - )
291 -
292 - def _get_prompt_updates(
293 - self,
294 - mm_items: MultiModalDataItems,
295 - hf_processor_mm_kwargs: Mapping[str, object],
296 - out_mm_kwargs: MultiModalKwargsItems,
297 - ) -> Sequence[PromptUpdate]:
298 - hf_config = self.info.get_hf_config()
299 - image_token_id = hf_config.image_token_index # The <IMAGE> token ID
300 -
301 - def get_replacement(item_idx: int):
302 - images = mm_items.get_items(
303 - "image", (ImageEmbeddingItems, ImageProcessorItems)
304 - )
305 -
306 - if isinstance(images, ImageEmbeddingItems):
307 - num_projected_patches = images.get_feature_size(item_idx)
308 - # This assumes num_projected_patches is a perfect square
309 - num_height_patches = int(math.sqrt(num_projected_patches))
310 - num_final_image_tokens = num_projected_patches + num_height_patches + 1
311 - else:
312 - image_size = images.get_image_size(item_idx)
313 - num_final_image_tokens = self.info.get_num_image_tokens(
314 - image_width=image_size.width,
315 - image_height=image_size.height,
316 - )
317 -
318 - return [image_token_id] * num_final_image_tokens
319 -
320 - return [
321 - PromptReplacement(
322 - modality="image",
323 - target=[image_token_id], # Replace each single <IMAGE> token
324 - replacement=get_replacement,
325 - ),
326 - ]
327 -
328 -
329 -def init_vision_tower_for_tarsier(
330 - hf_config: TarsierHfConfig, # Use the Tarsier specific config protocol
331 - quant_config: QuantizationConfig | None,
332 - *,
333 - require_post_norm: bool | None = None,
334 - prefix: str = "",
335 -) -> CLIPVisionModel | SiglipVisionModel:
336 - vision_config = hf_config.vision_config
337 -
338 - feature_layers = hf_config.vision_feature_layer
339 - base_num_hidden_layers = vision_config.num_hidden_layers
340 -
341 - if isinstance(feature_layers, int):
342 - num_hidden_layers_to_init = get_layer_index(
343 - feature_layers, base_num_hidden_layers
344 - )
345 - elif isinstance(feature_layers, (list, tuple)):
346 - num_hidden_layers_to_init = max(
347 - get_layer_index(idx, base_num_hidden_layers) for idx in feature_layers
348 - )
349 - else:
350 - raise TypeError(
351 - f"vision_layer_feature type: {type(feature_layers)} is not supported"
352 - )
353 -
354 - if isinstance(vision_config, CLIPVisionConfig):
355 - return CLIPVisionModel(
356 - vision_config,
357 - quant_config=quant_config,
358 - num_hidden_layers_override=num_hidden_layers_to_init,
359 - require_post_norm=require_post_norm,
360 - prefix=prefix,
361 - )
362 - elif isinstance(vision_config, SiglipVisionConfig):
363 - return SiglipVisionModel(
364 - vision_config,
365 - quant_config=quant_config,
366 - num_hidden_layers_override=num_hidden_layers_to_init,
367 - require_post_norm=require_post_norm,
368 - prefix=prefix,
369 - )
370 -
371 - msg = f"Unsupported vision config for Tarsier: {type(vision_config)}"
372 - raise NotImplementedError(msg)
373 -
374 -
375 -@MULTIMODAL_REGISTRY.register_processor(
376 - TarsierMultiModalProcessor,
377 - info=TarsierProcessingInfo,
378 - dummy_inputs=TarsierDummyInputsBuilder,
379 -)
380 -class TarsierForConditionalGeneration(nn.Module, SupportsMultiModal, SupportsPP):
381 - packed_modules_mapping = {
382 - "qkv_proj": ["q_proj", "k_proj", "v_proj"],
383 - "gate_up_proj": ["gate_proj", "up_proj"],
384 - }
385 -
386 - @classmethod
387 - def get_placeholder_str(cls, modality: str, i: int) -> str | None:
388 - if modality.startswith("image"):
389 - return "<image>"
390 -
391 - raise ValueError("Only image modality is supported")
392 -
393 - def __init__(self, *, vllm_config: VllmConfig, prefix: str = "") -> None:
394 - super().__init__()
395 -
396 - config: TarsierHfConfig = vllm_config.model_config.hf_config
397 - quant_config = vllm_config.quant_config
398 -
399 - self.config = config # Storing the Tarsier-specific HF config
400 -
401 - with self._mark_tower_model(vllm_config, "image"):
402 - self.vision_tower = init_vision_tower_for_tarsier(
403 - config,
404 - quant_config=quant_config,
405 - require_post_norm=False,
406 - prefix=maybe_prefix(prefix, "vision_tower"),
407 - )
408 - projector_bias = getattr(config, "multimodal_projector_bias", True)
409 -
410 - self.multi_modal_projector = TarsierMultiModalProjector(
411 - vision_hidden_size=config.vision_config.hidden_size,
412 - text_hidden_size=config.text_config.hidden_size,
413 - projector_hidden_act=config.projector_hidden_act,
414 - multimodal_projector_bias=projector_bias,
415 - quant_config=quant_config,
416 - prefix=maybe_prefix(prefix, "multi_modal_projector"),
417 - )
418 - self.register_buffer(
419 - "image_newline_idx_tensor",
420 - torch.tensor([config.image_newline_idx], dtype=torch.long),
421 - persistent=False,
422 - )
423 - self.register_buffer(
424 - "image_new_idx_tensor",
425 - torch.tensor([config.image_new_idx], dtype=torch.long),
426 - persistent=False,
427 - )
428 -
429 - with self._mark_language_model(vllm_config):
430 - self.language_model = init_vllm_registered_model(
431 - vllm_config=vllm_config,
432 - # Use text_config from Tarsier's main config
433 - hf_config=config.text_config,
434 - prefix=maybe_prefix(prefix, "language_model"),
435 - )
436 -
437 - self.make_empty_intermediate_tensors = (
438 - self.language_model.make_empty_intermediate_tensors
439 - )
440 -
441 - def _parse_and_validate_image_input(
442 - self, **kwargs: object
443 - ) -> TarsierImageInputs | None:
444 - pixel_values = kwargs.pop("pixel_values", None)
445 - image_embeds = kwargs.pop("image_embeds", None)
446 -
447 - if pixel_values is None and image_embeds is None:
448 - return None
449 -
450 - if pixel_values is not None:
451 - return TarsierImagePixelInputs(
452 - type="pixel_values",
453 - pixel_values=pixel_values,
454 - )
455 -
456 - if image_embeds is not None:
457 - return TarsierImageEmbeddingInputs(
458 - type="image_embeds",
459 - data=image_embeds,
460 - )
461 -
462 - raise AssertionError("This line should be unreachable.")
463 -
464 - def _image_pixels_to_features(
465 - self,
466 - vision_tower: CLIPVisionModel | SiglipVisionModel,
467 - pixel_values: torch.Tensor | list[torch.Tensor],
468 - ) -> torch.Tensor | tuple[torch.Tensor, ...]:
469 - # From vLLM LLaVA, vision tower output handling
470 - return vision_tower(
471 - pixel_values,
472 - feature_select_strategy=self.config.vision_feature_select_strategy,
473 - )
474 -
475 - def _add_tarsier_split_tokens(
476 - self, projected_image_features: torch.Tensor
477 - ) -> torch.Tensor:
478 - """
479 - Implements Tarsier's `add_split_tokens` logic.
480 - """
481 - num_images, num_projected_patches, embed_dim = projected_image_features.shape
482 - num_height_patches = int(math.sqrt(num_projected_patches))
483 - num_width_patches = num_projected_patches // num_height_patches
484 - device = projected_image_features.device
485 - embedding_layer = self.language_model.model.embed_tokens
486 - image_newline_emb = embedding_layer(
487 - self.image_newline_idx_tensor.to(device)
488 - ).squeeze(0)
489 - image_new_emb = embedding_layer(self.image_new_idx_tensor.to(device)).squeeze(0)
490 - try:
491 - current_image_features_grid = projected_image_features.view(
492 - num_images, num_height_patches, num_width_patches, embed_dim
493 - )
494 - except RuntimeError as e:
495 - raise RuntimeError(
496 - "Cannot reshape projected_image_features"
497 - f" with shape {projected_image_features.shape} "
498 - f"to ({num_images}, {num_height_patches},"
499 - f" {num_width_patches}, {embed_dim}). "
500 - "Ensure num_projected_patches is compatible"
501 - " with a grid structure. "
502 - f"num_projected_patches={num_projected_patches}, "
503 - f"derived num_height_patches={num_height_patches}. "
504 - ) from e
505 -
506 - image_newline_expanded = image_newline_emb.expand(
507 - (num_images, num_height_patches, 1, embed_dim)
508 - )
509 - features_with_newlines = torch.cat(
510 - [current_image_features_grid, image_newline_expanded],
511 - dim=2, # Concatenate along width dim
512 - )
513 - new_num_patches_after_newline = num_projected_patches + num_height_patches
514 - features_with_newlines_flat = features_with_newlines.view(
515 - num_images, new_num_patches_after_newline, embed_dim
516 - )
517 - image_new_expanded = image_new_emb.expand((num_images, 1, embed_dim))
518 - final_image_features = torch.cat(
519 - [features_with_newlines_flat, image_new_expanded],
520 - dim=1, # Concatenate along patch sequence dim
521 - )
522 - return final_image_features
523 -
524 - def _process_image_pixels(
525 - self,
526 - inputs: TarsierImagePixelInputs,
527 - ) -> torch.Tensor | tuple[torch.Tensor, ...]:
528 - pixel_values = inputs["pixel_values"]
529 - image_features_selected = self._image_pixels_to_features(
530 - self.vision_tower, pixel_values
531 - ) # type: ignore
532 - if isinstance(image_features_selected, torch.Tensor):
533 - projected_features = self.multi_modal_projector(image_features_selected)
534 - final_features = self._add_tarsier_split_tokens(projected_features)
535 - return final_features
536 - else:
537 - raise TypeError(
538 - f"_image_pixels_to_features type:"
539 - f" {type(image_features_selected)} is not supported"
540 - )
541 -
542 - def _process_image_input(
543 - self,
544 - image_input: TarsierImageInputs,
545 - ) -> torch.Tensor | tuple[torch.Tensor, ...]:
546 - if image_input["type"] == "image_embeds":
547 - projected_features = image_input["data"]
548 - if isinstance(projected_features, torch.Tensor):
549 - return self._add_tarsier_split_tokens(projected_features)
550 - else:
551 - raise ValueError(
552 - "Incorrect type of image_embeds. "
553 - f"Got type: {type(projected_features)}. "
554 - )
555 -
556 - return self._process_image_pixels(image_input)
557 -
558 - def embed_multimodal(self, **kwargs: object) -> MultiModalEmbeddings:
559 - image_input = self._parse_and_validate_image_input(**kwargs)
560 - if image_input is None:
561 - return []
562 - return self._process_image_input(image_input)
563 -
564 - def forward(
565 - self,
566 - input_ids: torch.Tensor | None,
567 - positions: torch.Tensor,
568 - intermediate_tensors: IntermediateTensors | None = None,
569 - inputs_embeds: torch.Tensor | None = None,
570 - **kwargs: object,
571 - ) -> torch.Tensor | IntermediateTensors:
572 - if intermediate_tensors is not None:
573 - inputs_embeds = None
574 -
575 - hidden_states = self.language_model.model(
576 - input_ids=input_ids,
577 - positions=positions,
578 - intermediate_tensors=intermediate_tensors,
579 - inputs_embeds=inputs_embeds,
580 - )
581 - return hidden_states
582 -
583 - def compute_logits(
584 - self,
585 - hidden_states: torch.Tensor,
586 - ) -> torch.Tensor | None:
587 - return self.language_model.compute_logits(hidden_states)
588 -
589 - def load_weights(self, weights: Iterable[tuple[str, torch.Tensor]]) -> set[str]:
590 - loader = AutoWeightsLoader(self)
591 - return loader.load_weights(weights)
vllm/transformers_utils/config.py
-1
@@ -123,7 +123,6 @@ _CONFIG_REGISTRY: dict[str, type[PretrainedConfig]] = LazyConfigDict(
123 qwen3_5_moe="Qwen3_5MoeConfig",
124 laguna="LagunaConfig",
125 lfm2_moe="Lfm2MoeConfig",
126 - tarsier2="Tarsier2Config",
126 **{"unlimited-ocr": "UnlimitedOCRConfig"},
127 )
128
vllm/transformers_utils/configs/__init__.py
-2
@@ -86,7 +86,6 @@ _CLASS_TO_MODULE: dict[str, str] = {
86 "Qwen3_5TextConfig": "vllm.transformers_utils.configs.qwen3_5",
87 "Qwen3_5MoeConfig": "vllm.transformers_utils.configs.qwen3_5_moe",
88 "Qwen3_5MoeTextConfig": "vllm.transformers_utils.configs.qwen3_5_moe",
89 - "Tarsier2Config": "vllm.transformers_utils.configs.tarsier2",
89 # Special case: DeepseekV3Config is from HuggingFace Transformers
90 "DeepseekV3Config": "transformers",
91 }
@@ -161,7 +160,6 @@ __all__ = [
160 "Qwen3_5TextConfig",
161 "Qwen3_5MoeConfig",
162 "Qwen3_5MoeTextConfig",
164 - "Tarsier2Config",
163 ]
164
165
vllm/transformers_utils/configs/tarsier2.py deleted
-24
@@ -1,24 +0,0 @@
1 -# SPDX-License-Identifier: Apache-2.0
2 -# SPDX-FileCopyrightText: Copyright contributors to the vLLM project
3 -from transformers import Qwen2VLConfig
4 -
5 -
6 -class Tarsier2Config(Qwen2VLConfig):
7 - """
8 - Tarsier2's config.json is written such that AutoConfig.from_pretrained will create
9 - a deeply nested config consisting of:
10 -
11 - - LlavaConfig
12 - - Qwen2VLConfig
13 - - Qwen2VLTextConfig
14 - - Qwen2VLVisionConfig
15 - - Qwen2VLConfig
16 - - Qwen2VLTextConfig
17 - - Qwen2VLVisionConfig
18 -
19 - When it should really just be a single Qwen2VLConfig.
20 -
21 - This class is a hack to stop AutoConfig from creating the nested config structure.
22 - """
23 -
24 - model_type = "tarsier2"