main
py 414 lines 14 KB
Raw
1 # SPDX-License-Identifier: Apache-2.0
2 # SPDX-FileCopyrightText: Copyright contributors to the vLLM project
3
4 # adapted from https://huggingface.co/Skywork/Skywork-R1V-38B/blob/main/modeling_skywork_chat.py
5 # --------------------------------------------------------
6 # SkyworkR1V
7 # Copyright (c) 2025 Skywork
8 # Licensed under The MIT License [see LICENSE for details]
9 # --------------------------------------------------------
10 from collections.abc import Iterable
11 from typing import Annotated, Literal, TypeAlias
12
13 import torch
14 import torch.nn as nn
15 from transformers import PreTrainedConfig
16
17 from vllm.config import VllmConfig
18 from vllm.model_executor.layers.linear import ReplicatedLinear
19 from vllm.model_executor.layers.quantization import QuantizationConfig
20 from vllm.model_executor.layers.quantization.auto_awq import AutoAWQConfig
21 from vllm.model_executor.models.intern_vit import (
22 InternVisionModel,
23 )
24 from vllm.multimodal import MULTIMODAL_REGISTRY
25 from vllm.sequence import IntermediateTensors
26 from vllm.transformers_utils.processors.internvl import (
27 InternVLImageProcessor,
28 InternVLProcessor,
29 )
30 from vllm.utils.tensor_schema import TensorSchema, TensorShape
31
32 from .interfaces import (
33 MultiModalEmbeddings,
34 SupportsMultiModal,
35 SupportsPP,
36 supports_pp,
37 )
38 from .internvl import (
39 BaseInternVLDummyInputsBuilder,
40 BaseInternVLMultiModalProcessor,
41 BaseInternVLProcessingInfo,
42 )
43 from .utils import (
44 AutoWeightsLoader,
45 WeightsMapper,
46 init_vllm_registered_model,
47 maybe_prefix,
48 )
49
50
51 class SkyworkR1VImagePixelInputs(TensorSchema):
52 """Dimensions:
53 - bnp: Batch size * number of images * (1 + num_patches)
54 - c: Number of channels (3)
55 - h: Height
56 - w: Width
57 - bn: Batch size * number of images
58 """
59
60 type: Literal["pixel_values"] = "pixel_values"
61
62 pixel_values_flat: Annotated[
63 torch.Tensor,
64 TensorShape("bnp", 3, "h", "w"),
65 ]
66
67 num_patches: Annotated[
68 torch.Tensor,
69 TensorShape("bn"),
70 ]
71
72
73 class SkyworkR1VImageEmbeddingInputs(TensorSchema):
74 """Dimensions:
75 - ni: Number of images
76 - ifs: Image feature size
77 - hs: Hidden size (must match the hidden size of language model
78 backbone)
79 """
80
81 type: Literal["image_embeds"] = "image_embeds"
82
83 data: Annotated[
84 torch.Tensor | list[torch.Tensor],
85 TensorShape("ni", "ifs", "hs"),
86 ]
87
88
89 SkyworkR1VImageInputs: TypeAlias = (
90 SkyworkR1VImagePixelInputs | SkyworkR1VImageEmbeddingInputs
91 )
92
93
94 class SkyworkR1VProcessingInfo(BaseInternVLProcessingInfo):
95 def get_image_processor(self, **kwargs):
96 config = self.get_hf_config()
97 vision_config = config.vision_config
98
99 kwargs = self.ctx.get_merged_mm_kwargs(kwargs)
100 kwargs.setdefault("image_size", vision_config.image_size)
101 kwargs.setdefault("min_dynamic_patch", config.min_dynamic_patch)
102 kwargs.setdefault("max_dynamic_patch", config.max_dynamic_patch)
103 kwargs.setdefault("dynamic_image_size", config.dynamic_image_size)
104 kwargs.setdefault("use_thumbnail", config.use_thumbnail)
105
106 return InternVLImageProcessor(**kwargs)
107
108 def get_hf_processor(self, **kwargs: object) -> InternVLProcessor:
109 config = self.get_hf_config()
110 vision_config = config.vision_config
111
112 image_processor = self.get_image_processor(**kwargs)
113 image_size = image_processor.image_size
114 patch_size = vision_config.patch_size
115 downsample_ratio = config.downsample_ratio
116 image_seq_length = int((image_size // patch_size) ** 2 * (downsample_ratio**2))
117
118 return InternVLProcessor(
119 tokenizer=self.get_tokenizer(),
120 image_processor=image_processor,
121 image_seq_length=image_seq_length,
122 )
123
124
125 @MULTIMODAL_REGISTRY.register_processor(
126 BaseInternVLMultiModalProcessor,
127 info=SkyworkR1VProcessingInfo,
128 dummy_inputs=BaseInternVLDummyInputsBuilder,
129 )
130 class SkyworkR1VChatModel(nn.Module, SupportsMultiModal, SupportsPP):
131 hf_to_vllm_mapper = WeightsMapper(
132 orig_to_new_prefix=dict.fromkeys(
133 [
134 "action_embed",
135 "temporal_embed",
136 "track_embed",
137 "track_embed_decoder",
138 "box_token",
139 "cg_criterion",
140 "cg_model",
141 "loc_encoder",
142 "loc_decoder",
143 "sam",
144 "temporal_token",
145 "track_token",
146 ]
147 )
148 )
149
150 @classmethod
151 def get_placeholder_str(cls, modality: str, i: int) -> str | None:
152 if modality.startswith("image"):
153 return "<image>"
154
155 raise ValueError("Only image modality is supported")
156
157 def __init__(self, *, vllm_config: VllmConfig, prefix: str = "") -> None:
158 super().__init__()
159
160 config = vllm_config.model_config.hf_config
161 quant_config = vllm_config.quant_config
162 multimodal_config = vllm_config.model_config.get_multimodal_config()
163
164 self.config = config
165 self.multimodal_config = multimodal_config
166 self._patch_quant_config(config, quant_config)
167
168 image_size = config.force_image_size or config.vision_config.image_size
169 patch_size = config.vision_config.patch_size
170 self.patch_size = patch_size
171 self.num_image_token = int(
172 (image_size // patch_size) ** 2 * (config.downsample_ratio**2)
173 )
174 self.downsample_ratio = config.downsample_ratio
175 self.ps_version = config.ps_version
176
177 with self._mark_tower_model(vllm_config, "image"):
178 self.vision_model = self._init_vision_model(
179 config,
180 quant_config=quant_config,
181 prefix=maybe_prefix(prefix, "vision_model"),
182 )
183 self.mlp1 = self._init_mlp1(
184 config, quant_config, prefix=maybe_prefix(prefix, "mlp1")
185 )
186
187 with self._mark_language_model(vllm_config):
188 language_model = init_vllm_registered_model(
189 vllm_config=vllm_config,
190 hf_config=config.text_config,
191 prefix=maybe_prefix(prefix, "language_model"),
192 )
193 self.language_model = language_model
194 assert supports_pp(language_model)
195
196 self.img_context_token_id: int | None = None
197 self.visual_token_mask = None
198 self.make_empty_intermediate_tensors = (
199 language_model.make_empty_intermediate_tensors
200 )
201
202 def _patch_quant_config(
203 self, config: PreTrainedConfig, quant_config: QuantizationConfig | None
204 ):
205 # the awq models from OpenGVLab missing `modules_to_not_convert`
206 # patch the quant_config to add `modules_to_not_convert` back
207 if isinstance(quant_config, AutoAWQConfig):
208 text_config = config.text_config
209 llm_quant_config = getattr(text_config, "quantization_config", None)
210 if (not quant_config.modules_to_not_convert) and (
211 llm_quant_config is not None
212 ):
213 quant_config.modules_to_not_convert.append("vision_model")
214
215 def _init_vision_model(
216 self,
217 config: PreTrainedConfig,
218 quant_config: QuantizationConfig | None,
219 *,
220 prefix: str,
221 ):
222 vision_feature_layer = config.select_layer
223 if vision_feature_layer < 0:
224 num_hidden_layers = (
225 config.vision_config.num_hidden_layers + vision_feature_layer + 1
226 )
227 else:
228 num_hidden_layers = vision_feature_layer + 1
229
230 return InternVisionModel(
231 config.vision_config,
232 quant_config=quant_config,
233 num_hidden_layers_override=num_hidden_layers,
234 prefix=prefix,
235 )
236
237 def _init_mlp1(
238 self,
239 config: PreTrainedConfig,
240 quant_config: QuantizationConfig | None,
241 prefix: str = "",
242 ) -> nn.Module:
243 vit_hidden_size = config.vision_config.hidden_size
244 llm_hidden_size = config.text_config.hidden_size
245
246 return nn.Sequential(
247 nn.LayerNorm(vit_hidden_size * int(1 / self.downsample_ratio) ** 2),
248 ReplicatedLinear(
249 vit_hidden_size * int(1 / self.downsample_ratio) ** 2,
250 llm_hidden_size,
251 return_bias=False,
252 quant_config=quant_config,
253 prefix=f"{prefix}.1",
254 ),
255 nn.GELU(),
256 ReplicatedLinear(
257 llm_hidden_size,
258 llm_hidden_size,
259 return_bias=False,
260 quant_config=quant_config,
261 prefix=f"{prefix}.3",
262 ),
263 )
264
265 def pixel_shuffle(self, x, scale_factor=0.5):
266 n, w, h, c = x.size()
267 # N, W, H, C --> N, W, H * scale, C // scale
268 x = x.view(n, w, int(h * scale_factor), int(c / scale_factor))
269 # N, W, H * scale, C // scale --> N, H * scale, W, C // scale
270 x = x.permute(0, 2, 1, 3).contiguous()
271 x = x.view(
272 n,
273 int(h * scale_factor),
274 int(w * scale_factor),
275 int(c / (scale_factor * scale_factor)),
276 )
277 if self.ps_version == "v1":
278 pass
279 else:
280 x = x.permute(0, 2, 1, 3).contiguous()
281 return x
282
283 def extract_feature(self, pixel_values: torch.Tensor) -> torch.Tensor:
284 vit_embeds = self.vision_model(pixel_values=pixel_values)
285 vit_embeds = vit_embeds[:, 1:, :]
286
287 h = w = int(vit_embeds.shape[1] ** 0.5)
288 vit_embeds = vit_embeds.reshape(vit_embeds.shape[0], h, w, -1)
289 vit_embeds = self.pixel_shuffle(vit_embeds, scale_factor=self.downsample_ratio)
290 vit_embeds = vit_embeds.reshape(vit_embeds.shape[0], -1, vit_embeds.shape[-1])
291 vit_embeds = self.mlp1(vit_embeds)
292 return vit_embeds
293
294 def _parse_and_validate_image_input(
295 self, **kwargs: object
296 ) -> SkyworkR1VImageInputs | None:
297 pixel_values_flat = kwargs.pop("pixel_values_flat", None)
298 image_num_patches = kwargs.pop("image_num_patches", None)
299 image_embeds = kwargs.pop("image_embeds", None)
300
301 if pixel_values_flat is None and image_embeds is None:
302 return None
303
304 if image_embeds is not None:
305 return SkyworkR1VImageEmbeddingInputs(
306 type="image_embeds",
307 data=image_embeds,
308 )
309
310 image_token_id = kwargs["image_token_id"]
311 if isinstance(image_token_id, torch.Tensor):
312 image_token_id = image_token_id.flatten().unique().item()
313
314 assert isinstance(image_token_id, int)
315 self.img_context_token_id = image_token_id
316
317 if pixel_values_flat is not None:
318 return SkyworkR1VImagePixelInputs(
319 type="pixel_values",
320 pixel_values_flat=pixel_values_flat,
321 num_patches=image_num_patches,
322 resolve_bindings={
323 "h": self.config.vision_config.image_size,
324 "w": self.config.vision_config.image_size,
325 },
326 )
327
328 raise AssertionError("This line should be unreachable.")
329
330 def _process_image_input(
331 self,
332 image_input: SkyworkR1VImageInputs,
333 ) -> torch.Tensor | list[torch.Tensor] | tuple[torch.Tensor, ...]:
334 if image_input["type"] == "image_embeds":
335 return image_input["data"]
336
337 image_embeds = self.extract_feature(image_input["pixel_values_flat"])
338
339 num_patches = image_input["num_patches"]
340
341 # Only one image in the current batch
342 if len(num_patches) == 1:
343 return image_embeds.view(-1, self.config.text_config.hidden_size).unsqueeze(
344 0
345 )
346
347 # NOTE: Image embeddings are split into separate tensors for each image
348 # by the size of each embedding.
349 feature_size = image_embeds.shape[1]
350 image_embeds = image_embeds.view(-1, self.config.text_config.hidden_size)
351 image_feature_sizes = [
352 num_patches * feature_size for num_patches in num_patches
353 ]
354 return image_embeds.split(image_feature_sizes)
355
356 def embed_multimodal(self, **kwargs: object) -> MultiModalEmbeddings:
357 image_input = self._parse_and_validate_image_input(**kwargs)
358 if image_input is None:
359 return []
360
361 return self._process_image_input(image_input)
362
363 def embed_input_ids(
364 self,
365 input_ids: torch.Tensor,
366 multimodal_embeddings: MultiModalEmbeddings | None = None,
367 *,
368 is_multimodal: torch.Tensor | None = None,
369 ) -> torch.Tensor:
370 # This is to satisfy the type checker for each overload
371 if multimodal_embeddings is None or is_multimodal is None:
372 return super().embed_input_ids(input_ids)
373
374 return super().embed_input_ids(
375 input_ids,
376 multimodal_embeddings=multimodal_embeddings,
377 is_multimodal=is_multimodal,
378 )
379
380 def forward(
381 self,
382 input_ids: torch.Tensor | None,
383 positions: torch.Tensor,
384 intermediate_tensors: IntermediateTensors | None = None,
385 inputs_embeds: torch.Tensor | None = None,
386 **kwargs: object,
387 ) -> IntermediateTensors:
388 if intermediate_tensors is not None:
389 inputs_embeds = None
390
391 forward_kwargs = {
392 "input_ids": input_ids,
393 "positions": positions,
394 "intermediate_tensors": intermediate_tensors,
395 "inputs_embeds": inputs_embeds,
396 }
397
398 # Only required if the model is mono-architecture
399 if self.visual_token_mask is not None:
400 forward_kwargs.update({"visual_token_mask": self.visual_token_mask})
401 self.visual_token_mask = None
402
403 hidden_states = self.language_model.model(**forward_kwargs)
404 return hidden_states
405
406 def compute_logits(
407 self,
408 hidden_states: torch.Tensor,
409 ) -> torch.Tensor | None:
410 return self.language_model.compute_logits(hidden_states)
411
412 def load_weights(self, weights: Iterable[tuple[str, torch.Tensor]]) -> set[str]:
413 loader = AutoWeightsLoader(self)
414 return loader.load_weights(weights, mapper=self.hf_to_vllm_mapper)