A high-throughput and memory-efficient inference and serving engine for LLMs
Mirrored from https://github.com/tduncan-lum/Nvidia-Optimization-Stack---VLLM.git
Synced Nov 15, 17:15 UTC
[Model] Support Skywork-R1V (#15397)
Signed-off-by: jiacai.liu <932997367@qq.com> Co-authored-by: jiacai.liu <932997367@qq.com>
pengyuange committed
Mar 29, 2025 at 11:39 UTC
de1cb38769e9eb9812fa425c4fdfcf8faa3c420e
12 files changed
+1194
-7
docs/source/models/supported_models.md
+7
@@ -921,6 +921,13 @@ See [this page](#generative-models) for more information on how to use generativ
921
* ✅︎
922
* ✅︎
923
* ✅︎
924
+- * `SkyworkR1VChatModel`
925
+ * Skywork-R1V-38B
926
+ * T + I
927
+ * `Skywork/Skywork-R1V-38B`
928
+ *
929
+ * ✅︎
930
+ * ✅︎
931
- * `UltravoxModel`
932
* Ultravox
933
* T + A<sup>E+</sup>
examples/offline_inference/vision_language.py
+36
@@ -804,6 +804,41 @@ def run_qwen2_5_vl(questions: list[str], modality: str) -> ModelRequestData:
804
)
805
806
807
+# SkyworkR1V
808
+def run_skyworkr1v(questions: list[str], modality: str) -> ModelRequestData:
809
+ assert modality == "image"
810
+
811
+ model_name = "Skywork/Skywork-R1V-38B"
812
+
813
+ engine_args = EngineArgs(
814
+ model=model_name,
815
+ trust_remote_code=True,
816
+ max_model_len=4096,
817
+ disable_mm_preprocessor_cache=args.disable_mm_preprocessor_cache,
818
+ )
819
+
820
+ tokenizer = AutoTokenizer.from_pretrained(model_name,
821
+ trust_remote_code=True)
822
+ messages = [[{
823
+ 'role': 'user',
824
+ 'content': f"<image>\n{question}"
825
+ }] for question in questions]
826
+ prompts = tokenizer.apply_chat_template(messages,
827
+ tokenize=False,
828
+ add_generation_prompt=True)
829
+
830
+ # Stop tokens for SkyworkR1V
831
+ # https://huggingface.co/Skywork/Skywork-R1V-38B/blob/main/conversation.py
832
+ stop_tokens = ["<|end▁of▁sentence|>", "<|endoftext|>"]
833
+ stop_token_ids = [tokenizer.convert_tokens_to_ids(i) for i in stop_tokens]
834
+
835
+ return ModelRequestData(
836
+ engine_args=engine_args,
837
+ prompts=prompts,
838
+ stop_token_ids=stop_token_ids,
839
+ )
840
+
841
+
842
model_example_map = {
843
"aria": run_aria,
844
"blip-2": run_blip2,
@@ -834,6 +869,7 @@ model_example_map = {
869
"qwen_vl": run_qwen_vl,
870
"qwen2_vl": run_qwen2_vl,
871
"qwen2_5_vl": run_qwen2_5_vl,
872
+ "skywork_chat": run_skyworkr1v,
873
}
874
875
tests/models/decoder_only/vision_language/test_models.py
+14
@@ -474,6 +474,20 @@ VLM_TEST_SETTINGS = {
474
vllm_output_post_proc=model_utils.qwen_vllm_to_hf_output,
475
prompt_path_encoder=model_utils.qwen_prompt_path_encoder,
476
),
477
+ "skywork_r1v": VLMTestInfo(
478
+ models=["Skywork/Skywork-R1V-38B"],
479
+ test_type=(VLMTestType.IMAGE, VLMTestType.MULTI_IMAGE),
480
+ prompt_formatter=lambda img_prompt: f"<|begin▁of▁sentence|><|User|>\n{img_prompt}<|Assistant|><think>\n", # noqa: E501
481
+ single_image_prompts=IMAGE_ASSETS.prompts({
482
+ "stop_sign": "<image>\nWhat's the content in the center of the image?", # noqa: E501
483
+ "cherry_blossom": "<image>\nWhat is the season?",
484
+ }),
485
+ multi_image_prompt="<image>\n<image>\nDescribe the two images in short.", # noqa: E501
486
+ max_model_len=4096,
487
+ use_tokenizer_eos=True,
488
+ patch_hf_runner=model_utils.skyworkr1v_patch_hf_runner,
489
+ marks=[large_gpu_mark(min_gb=80)],
490
+ ),
491
### Tensor parallel / multi-gpu broadcast tests
492
"chameleon-broadcast": VLMTestInfo(
493
models=["facebook/chameleon-7b"],
tests/models/decoder_only/vision_language/vlm_utils/model_utils.py
+57
@@ -376,6 +376,63 @@ def h2ovl_patch_hf_runner(hf_model: HfRunner) -> HfRunner:
376
return hf_model
377
378
379
+def skyworkr1v_patch_hf_runner(hf_model: HfRunner) -> HfRunner:
380
+ """Patches and returns an instance of the HfRunner to use for SkyworkR1V."""
381
+
382
+ class SkyworkR1VProcessor:
383
+ """A simple processor for SkyworkR1V."""
384
+
385
+ def __init__(self, hf_runner: HfRunner):
386
+ self.num_image_token = hf_runner.model.num_image_token
387
+ self.tokenizer = hf_runner.tokenizer
388
+
389
+ self.config = AutoConfig.from_pretrained(hf_runner.model_name,
390
+ trust_remote_code=True)
391
+ self.vision_config = self.config.vision_config
392
+ self.use_thumbnail = self.config.use_thumbnail
393
+ self.min_num = self.config.min_dynamic_patch
394
+ self.max_num = self.config.max_dynamic_patch
395
+ self.image_size = self.vision_config.image_size
396
+
397
+ def __call__(self, text: str, images: Union[Image, list[Image]],
398
+ **kwargs):
399
+ from vllm.model_executor.models.skyworkr1v import (
400
+ IMG_CONTEXT, IMG_END, IMG_START,
401
+ image_to_pixel_values_skyworkr1v)
402
+ images = [images] if isinstance(images, Image) else images
403
+ pixel_values = [
404
+ image_to_pixel_values_skyworkr1v(
405
+ image,
406
+ input_size=self.image_size,
407
+ min_num=self.min_num,
408
+ max_num=self.max_num,
409
+ use_thumbnail=self.use_thumbnail,
410
+ ) for image in images
411
+ ]
412
+ num_patches_list = [
413
+ pixel_value.shape[0] for pixel_value in pixel_values
414
+ ]
415
+ pixel_values = torch.cat(pixel_values, dim=0)
416
+ for num_patches in num_patches_list:
417
+ context_tokens = IMG_CONTEXT * self.num_image_token \
418
+ * num_patches
419
+ image_tokens = IMG_START + context_tokens + IMG_END
420
+ text = text.replace('<image>', image_tokens, 1)
421
+ prompt = self.tokenizer(text, return_tensors="pt")
422
+ prompt.update({"pixel_values": pixel_values})
423
+ return prompt
424
+
425
+ img_context_token_id = hf_model.tokenizer.convert_tokens_to_ids(
426
+ "<IMG_CONTEXT>")
427
+ hf_model.model.img_context_token_id = img_context_token_id
428
+ hf_model.processor = SkyworkR1VProcessor(hf_model)
429
+ hf_model.model.get_output_embeddings = lambda: \
430
+ hf_model.model.language_model.get_output_embeddings()
431
+ hf_model.model.generate = types.MethodType(_internvl_generate,
432
+ hf_model.model)
433
+ return hf_model
434
+
435
+
436
def internvl_patch_hf_runner(hf_model: HfRunner) -> HfRunner:
437
"""Patches and returns an instance of the HfRunner to use for InternVL."""
438
tests/models/multimodal/processing/test_common.py
+5
-4
@@ -262,22 +262,23 @@ def _test_processing_correctness_mistral(
262
"llava-hf/llava-onevision-qwen2-0.5b-ov-hf",
263
"meta-llama/Llama-3.2-11B-Vision-Instruct",
264
"TIGER-Lab/Mantis-8B-siglip-llama3",
265
- "mistralai/Pixtral-12B-2409",
266
- "mistral-community/pixtral-12b",
265
"openbmb/MiniCPM-Llama3-V-2_5",
266
"openbmb/MiniCPM-o-2_6",
267
"openbmb/MiniCPM-V-2_6",
268
"allenai/Molmo-7B-D-0924",
269
"allenai/Molmo-7B-O-0924",
270
"nvidia/NVLM-D-72B",
271
+ "google/paligemma-3b-mix-224",
272
+ "google/paligemma2-3b-ft-docci-448",
273
+ "mistralai/Pixtral-12B-2409",
274
+ "mistral-community/pixtral-12b",
275
"Qwen/Qwen-VL-Chat",
276
"Qwen/Qwen2-VL-2B-Instruct",
277
"Qwen/Qwen2.5-VL-3B-Instruct",
278
"Qwen/Qwen2-Audio-7B-Instruct",
279
+ "Skywork/Skywork-R1V-38B",
280
"fixie-ai/ultravox-v0_5-llama-3_2-1b",
281
"openai/whisper-large-v3",
279
- "google/paligemma-3b-mix-224",
280
- "google/paligemma2-3b-ft-docci-448",
282
])
283
@pytest.mark.parametrize("hit_rate", [0.3, 0.5, 1.0])
284
@pytest.mark.parametrize("num_batches", [32])
tests/models/registry.py
+1
@@ -294,6 +294,7 @@ _MULTIMODAL_EXAMPLE_MODELS = {
294
"Qwen2VLForConditionalGeneration": _HfExamplesInfo("Qwen/Qwen2-VL-2B-Instruct"), # noqa: E501
295
"Qwen2_5_VLForConditionalGeneration": _HfExamplesInfo("Qwen/Qwen2.5-VL-3B-Instruct", # noqa: E501
296
min_transformers_version="4.49"), # noqa: E501
297
+ "SkyworkR1VChatModel": _HfExamplesInfo("Skywork/Skywork-R1V-38B"),
298
"UltravoxModel": _HfExamplesInfo("fixie-ai/ultravox-v0_5-llama-3_2-1b", # noqa: E501
299
trust_remote_code=True),
300
# [Encoder-decoder]
vllm/entrypoints/chat_utils.py
+1
-1
@@ -496,7 +496,7 @@ class BaseMultiModalItemTracker(ABC, Generic[_T]):
496
return self._cached_token_str(self._tokenizer,
497
hf_config.image_token_index)
498
if model_type in ("chameleon", "deepseek_vl_v2", "internvl_chat",
499
- "NVLM_D", "h2ovl_chat"):
499
+ "skywork_chat", "NVLM_D", "h2ovl_chat"):
500
return "<image>"
501
if model_type == "mllama":
502
return "<|image|>"
vllm/model_executor/models/registry.py
+1
@@ -190,6 +190,7 @@ _MULTIMODAL_MODELS = {
190
# [Encoder-decoder]
191
"Florence2ForConditionalGeneration": ("florence2", "Florence2ForConditionalGeneration"), # noqa: E501
192
"MllamaForConditionalGeneration": ("mllama", "MllamaForConditionalGeneration"), # noqa: E501
193
+ "SkyworkR1VChatModel": ("skyworkr1v", "SkyworkR1VChatModel"),
194
"WhisperForConditionalGeneration": ("whisper", "WhisperForConditionalGeneration"), # noqa: E501
195
}
196
vllm/model_executor/models/skyworkr1v.py
new
+1014
@@ -0,0 +1,1014 @@
1
+# SPDX-License-Identifier: Apache-2.0
2
+
3
+# adapted from https://huggingface.co/Skywork/Skywork-R1V-38B/blob/main/modeling_skywork_chat.py
4
+# --------------------------------------------------------
5
+# SkyworkR1V
6
+# Copyright (c) 2025 Skywork
7
+# Licensed under The MIT License [see LICENSE for details]
8
+# --------------------------------------------------------
9
+from abc import ABC, abstractmethod
10
+from collections.abc import Iterable, Mapping, Sequence
11
+from functools import cached_property
12
+from typing import Literal, Optional, Set, Tuple, TypedDict, TypeVar, Union
13
+
14
+import torch
15
+import torch.nn as nn
16
+import torchvision.transforms as T
17
+from PIL import Image
18
+from transformers import BatchEncoding, PretrainedConfig, TensorType
19
+
20
+from vllm.config import VllmConfig
21
+from vllm.model_executor.layers.linear import ReplicatedLinear
22
+from vllm.model_executor.layers.quantization import QuantizationConfig
23
+from vllm.model_executor.layers.quantization.awq import AWQConfig
24
+from vllm.model_executor.layers.sampler import SamplerOutput, get_sampler
25
+from vllm.model_executor.models.intern_vit import (InternVisionModel,
26
+ InternVisionPatchModel)
27
+from vllm.model_executor.sampling_metadata import SamplingMetadata
28
+from vllm.multimodal import MULTIMODAL_REGISTRY
29
+from vllm.multimodal.inputs import (MultiModalFieldConfig, MultiModalKwargs,
30
+ NestedTensors)
31
+from vllm.multimodal.parse import (ImageEmbeddingItems, ImageProcessorItems,
32
+ ImageSize, MultiModalDataItems)
33
+from vllm.multimodal.processing import (BaseMultiModalProcessor,
34
+ BaseProcessingInfo, PromptReplacement,
35
+ PromptUpdate, PromptUpdateDetails)
36
+from vllm.multimodal.profiling import BaseDummyInputsBuilder, ProcessorInputs
37
+from vllm.sequence import IntermediateTensors
38
+from vllm.transformers_utils.tokenizer import AnyTokenizer
39
+
40
+from .interfaces import MultiModalEmbeddings, SupportsMultiModal, SupportsPP
41
+from .utils import (AutoWeightsLoader, flatten_bn, init_vllm_registered_model,
42
+ maybe_prefix, merge_multimodal_embeddings)
43
+from .vision import scatter_patch_features, select_patch_features
44
+
45
+IMG_START = '<img>'
46
+IMG_END = '</img>'
47
+IMG_CONTEXT = '<IMG_CONTEXT>'
48
+
49
+IMAGENET_MEAN = (0.485, 0.456, 0.406)
50
+IMAGENET_STD = (0.229, 0.224, 0.225)
51
+
52
+
53
+class SkyworkR1VImagePixelInputs(TypedDict):
54
+ type: Literal["pixel_values"]
55
+ pixel_values_flat: torch.Tensor
56
+ """
57
+ Shape:
58
+ `(batch_size * num_images * (1 + num_patches), num_channels, height, width)`
59
+ """
60
+
61
+ num_patches: torch.Tensor
62
+ """Shape: `(batch_size * num_images)`"""
63
+
64
+ embed_is_patch: Union[torch.Tensor, list[torch.Tensor]]
65
+ """
66
+ A boolean mask indicating which image embeddings correspond
67
+ to patch tokens.
68
+
69
+ Shape: `(batch_size * num_images, num_embeds)`
70
+ """
71
+
72
+
73
+class SkyworkR1VImageEmbeddingInputs(TypedDict):
74
+ type: Literal["image_embeds"]
75
+ data: Union[torch.Tensor, list[torch.Tensor]]
76
+ """
77
+ A tensor of shape `(num_images, total_image_feature_size, hidden_size)`
78
+ or a list of tensors of shape `(total_image_feature_size, hidden_size)`
79
+
80
+ `hidden_size` must match the hidden size of language model backbone.
81
+ """
82
+
83
+
84
+SkyworkR1VImageInputs = Union[SkyworkR1VImagePixelInputs,
85
+ SkyworkR1VImageEmbeddingInputs]
86
+
87
+
88
+# adapted from https://huggingface.co/Skywork/Skywork-R1V-38B/
89
+def build_transform(input_size: int):
90
+ MEAN, STD = IMAGENET_MEAN, IMAGENET_STD
91
+ return T.Compose([
92
+ T.Lambda(lambda img: img.convert('RGB') if img.mode != 'RGB' else img),
93
+ T.Resize((input_size, input_size),
94
+ interpolation=T.InterpolationMode.BICUBIC),
95
+ T.ToTensor(),
96
+ T.Normalize(mean=MEAN, std=STD)
97
+ ])
98
+
99
+
100
+# adapted from https://huggingface.co/Skywork/Skywork-R1V-38B/
101
+def find_closest_aspect_ratio(
102
+ aspect_ratio: float,
103
+ target_ratios: list[tuple[int, int]],
104
+ *,
105
+ width: int,
106
+ height: int,
107
+ image_size: int,
108
+) -> tuple[int, int]:
109
+ best_ratio_diff = float('inf')
110
+ best_ratio = (1, 1)
111
+ area = width * height
112
+ for ratio in target_ratios:
113
+ target_aspect_ratio = ratio[0] / ratio[1]
114
+ ratio_diff = abs(aspect_ratio - target_aspect_ratio)
115
+ if ratio_diff < best_ratio_diff:
116
+ best_ratio_diff = ratio_diff
117
+ best_ratio = ratio
118
+ elif ratio_diff == best_ratio_diff:
119
+ if area > 0.5 * image_size * image_size * ratio[0] * ratio[1]:
120
+ best_ratio = ratio
121
+ return best_ratio
122
+
123
+
124
+def resolve_skyworkr1v_min_max_num(
125
+ *,
126
+ min_dynamic_patch: int,
127
+ max_dynamic_patch: int,
128
+ dynamic_image_size: bool,
129
+ use_thumbnail: bool,
130
+) -> tuple[int, int]:
131
+ min_dynamic_patch = min_dynamic_patch if dynamic_image_size else 1
132
+ max_dynamic_patch = max_dynamic_patch if dynamic_image_size else 1
133
+
134
+ if use_thumbnail and max_dynamic_patch != 1:
135
+ max_dynamic_patch += 1
136
+
137
+ return min_dynamic_patch, max_dynamic_patch
138
+
139
+
140
+def get_skyworkr1v_target_ratios(
141
+ min_num: int,
142
+ max_num: int,
143
+) -> list[tuple[int, int]]:
144
+ target_ratios = {(i, j)
145
+ for n in range(min_num, max_num + 1)
146
+ for i in range(1, n + 1)
147
+ for j in range(1, n + 1) if min_num <= i * j <= max_num}
148
+ return sorted(target_ratios, key=lambda x: x[0] * x[1])
149
+
150
+
151
+def calculate_skyworkr1v_targets(
152
+ *,
153
+ orig_width: int,
154
+ orig_height: int,
155
+ target_ratios: list[tuple[int, int]],
156
+ image_size: int,
157
+ use_thumbnail: bool,
158
+) -> tuple[int, int, int]:
159
+ aspect_ratio = orig_width / orig_height
160
+
161
+ # find the closest aspect ratio to the target
162
+ target_aspect_ratio = find_closest_aspect_ratio(
163
+ aspect_ratio,
164
+ target_ratios,
165
+ width=orig_width,
166
+ height=orig_height,
167
+ image_size=image_size,
168
+ )
169
+
170
+ # calculate the target width and height
171
+ target_width = image_size * target_aspect_ratio[0]
172
+ target_height = image_size * target_aspect_ratio[1]
173
+ blocks = target_aspect_ratio[0] * target_aspect_ratio[1]
174
+
175
+ # add thumbnail image if num_blocks != 1
176
+ if use_thumbnail and blocks != 1:
177
+ blocks += 1
178
+
179
+ return blocks, target_width, target_height
180
+
181
+
182
+def dynamic_preprocess_skyworkr1v(
183
+ image: Image.Image,
184
+ *,
185
+ target_ratios: list[tuple[int, int]],
186
+ image_size: int,
187
+ use_thumbnail: bool,
188
+) -> list[Image.Image]:
189
+ orig_width, orig_height = image.size
190
+
191
+ # calculate the number of blocks without thumbnail
192
+ blocks, target_width, target_height = calculate_skyworkr1v_targets(
193
+ orig_width=orig_width,
194
+ orig_height=orig_height,
195
+ target_ratios=target_ratios,
196
+ image_size=image_size,
197
+ use_thumbnail=False,
198
+ )
199
+
200
+ # resize the image
201
+ resized_img = image.resize((target_width, target_height))
202
+ processed_images = []
203
+ for i in range(blocks):
204
+ box = ((i % (target_width // image_size)) * image_size,
205
+ (i // (target_width // image_size)) * image_size,
206
+ ((i % (target_width // image_size)) + 1) * image_size,
207
+ ((i // (target_width // image_size)) + 1) * image_size)
208
+ # split the image
209
+ split_img = resized_img.crop(box)
210
+ processed_images.append(split_img)
211
+
212
+ assert len(processed_images) == blocks
213
+
214
+ if use_thumbnail and len(processed_images) != 1:
215
+ thumbnail_img = image.resize((image_size, image_size))
216
+ processed_images.append(thumbnail_img)
217
+
218
+ return processed_images
219
+
220
+
221
+# adapted from https://huggingface.co/Skywork/Skywork-R1V-38B
222
+def image_to_pixel_values_skyworkr1v(
223
+ image: Image.Image,
224
+ *,
225
+ input_size: int,
226
+ min_num: int,
227
+ max_num: int,
228
+ use_thumbnail: bool,
229
+) -> torch.Tensor:
230
+ target_ratios = get_skyworkr1v_target_ratios(min_num, max_num)
231
+
232
+ transform = build_transform(input_size=input_size)
233
+ images = dynamic_preprocess_skyworkr1v(
234
+ image,
235
+ target_ratios=target_ratios,
236
+ image_size=input_size,
237
+ use_thumbnail=use_thumbnail,
238
+ )
239
+
240
+ pixel_values = torch.stack([transform(image) for image in images])
241
+ return pixel_values
242
+
243
+
244
+class BaseSkyworkR1VProcessor(ABC):
245
+ """
246
+ This model doesn't define its own HF processor,
247
+ so we implement our own one here.
248
+
249
+ The code to insert image tokens is based on:
250
+ https://huggingface.co/Skywork/Skywork-R1V-38B/blob/main/modeling_skywork_chat.py#L252
251
+ """
252
+
253
+ def __init__(
254
+ self,
255
+ config: PretrainedConfig,
256
+ tokenizer: AnyTokenizer,
257
+ *,
258
+ min_dynamic_patch: Optional[int] = None,
259
+ max_dynamic_patch: Optional[int] = None,
260
+ dynamic_image_size: Optional[bool] = None,
261
+ ) -> None:
262
+ super().__init__()
263
+
264
+ self.config = config
265
+ self.tokenizer = tokenizer
266
+
267
+ image_size: int = config.vision_config.image_size
268
+ patch_size: int = config.vision_config.patch_size
269
+
270
+ if min_dynamic_patch is None:
271
+ min_dynamic_patch = config.min_dynamic_patch
272
+ assert isinstance(min_dynamic_patch, int)
273
+
274
+ if max_dynamic_patch is None:
275
+ max_dynamic_patch = config.max_dynamic_patch
276
+ assert isinstance(max_dynamic_patch, int)
277
+
278
+ if dynamic_image_size is None:
279
+ dynamic_image_size = config.dynamic_image_size
280
+ assert isinstance(dynamic_image_size, bool)
281
+
282
+ self.num_image_token = int(
283
+ (image_size // patch_size)**2 * (config.downsample_ratio**2))
284
+ self.image_size = image_size
285
+ self.min_dynamic_patch = min_dynamic_patch
286
+ self.max_dynamic_patch = max_dynamic_patch
287
+ self.dynamic_image_size = dynamic_image_size
288
+ self.use_thumbnail: bool = config.use_thumbnail
289
+
290
+ @property
291
+ @abstractmethod
292
+ def image_token_id(self) -> int:
293
+ raise NotImplementedError
294
+
295
+ @abstractmethod
296
+ def get_image_repl(
297
+ self,
298
+ feature_size: int,
299
+ num_patches: Optional[int],
300
+ ) -> PromptUpdateDetails[str]:
301
+ raise NotImplementedError
302
+
303
+ def resolve_min_max_num(
304
+ self,
305
+ *,
306
+ min_dynamic_patch: Optional[int] = None,
307
+ max_dynamic_patch: Optional[int] = None,
308
+ dynamic_image_size: Optional[bool] = None,
309
+ use_thumbnail: Optional[bool] = None,
310
+ ) -> tuple[int, int]:
311
+ min_dynamic_patch = (self.min_dynamic_patch if min_dynamic_patch
312
+ is None else min_dynamic_patch)
313
+ max_dynamic_patch = (self.max_dynamic_patch if max_dynamic_patch
314
+ is None else max_dynamic_patch)
315
+ dynamic_image_size = (self.dynamic_image_size if dynamic_image_size
316
+ is None else dynamic_image_size)
317
+ use_thumbnail = (self.use_thumbnail
318
+ if use_thumbnail is None else use_thumbnail)
319
+
320
+ return resolve_skyworkr1v_min_max_num(
321
+ min_dynamic_patch=min_dynamic_patch,
322
+ max_dynamic_patch=max_dynamic_patch,
323
+ dynamic_image_size=dynamic_image_size,
324
+ use_thumbnail=use_thumbnail,
325
+ )
326
+
327
+ def resolve_target_ratios(
328
+ self,
329
+ *,
330
+ min_dynamic_patch: Optional[int] = None,
331
+ max_dynamic_patch: Optional[int] = None,
332
+ dynamic_image_size: Optional[bool] = None,
333
+ use_thumbnail: Optional[bool] = None,
334
+ ) -> list[tuple[int, int]]:
335
+ min_num, max_num = self.resolve_min_max_num(
336
+ min_dynamic_patch=min_dynamic_patch,
337
+ max_dynamic_patch=max_dynamic_patch,
338
+ dynamic_image_size=dynamic_image_size,
339
+ use_thumbnail=use_thumbnail,
340
+ )
341
+
342
+ return get_skyworkr1v_target_ratios(min_num, max_num)
343
+
344
+ def get_num_image_tokens(
345
+ self,
346
+ *,
347
+ image_width: int,
348
+ image_height: int,
349
+ ) -> int:
350
+ target_ratios = self.resolve_target_ratios(
351
+ use_thumbnail=False, # Applied in calculate_targets
352
+ )
353
+
354
+ num_patches, _, _ = calculate_skyworkr1v_targets(
355
+ orig_width=image_width,
356
+ orig_height=image_height,
357
+ image_size=self.image_size,
358
+ target_ratios=target_ratios,
359
+ use_thumbnail=self.use_thumbnail,
360
+ )
361
+
362
+ return num_patches * self.num_image_token
363
+
364
+ def _images_to_pixel_values_lst(
365
+ self,
366
+ images: list[Image.Image],
367
+ min_dynamic_patch: Optional[int] = None,
368
+ max_dynamic_patch: Optional[int] = None,
369
+ dynamic_image_size: Optional[bool] = None,
370
+ ) -> list[torch.Tensor]:
371
+ min_num, max_num = self.resolve_min_max_num(
372
+ min_dynamic_patch=min_dynamic_patch,
373
+ max_dynamic_patch=max_dynamic_patch,
374
+ dynamic_image_size=dynamic_image_size,
375
+ use_thumbnail=False, # Applied in image_to_pixel_values
376
+ )
377
+
378
+ return [
379
+ image_to_pixel_values_skyworkr1v(
380
+ image,
381
+ input_size=self.image_size,
382
+ min_num=min_num,
383
+ max_num=max_num,
384
+ use_thumbnail=self.use_thumbnail,
385
+ ) for image in images
386
+ ]
387
+
388
+ def __call__(
389
+ self,
390
+ text: Optional[Union[str, list[str]]] = None,
391
+ images: Optional[Union[Image.Image, list[Image.Image]]] = None,
392
+ min_dynamic_patch: Optional[int] = None,
393
+ max_dynamic_patch: Optional[int] = None,
394
+ dynamic_image_size: Optional[bool] = None,
395
+ return_tensors: Optional[Union[str, TensorType]] = None,
396
+ ) -> Mapping[str, NestedTensors]:
397
+ if text is None:
398
+ text = []
399
+ if not isinstance(text, list):
400
+ text = [text]
401
+ if images is None:
402
+ images = []
403
+ if not isinstance(images, list):
404
+ images = [images]
405
+
406
+ if len(images) == 0:
407
+ image_inputs = {}
408
+ else:
409
+ pixel_values_lst = self._images_to_pixel_values_lst(
410
+ images,
411
+ min_dynamic_patch=min_dynamic_patch,
412
+ max_dynamic_patch=max_dynamic_patch,
413
+ dynamic_image_size=dynamic_image_size,
414
+ )
415
+ image_inputs: dict[str, NestedTensors] = {
416
+ "pixel_values_flat":
417
+ torch.cat(pixel_values_lst),
418
+ "image_num_patches":
419
+ torch.tensor([len(item) for item in pixel_values_lst]),
420
+ }
421
+
422
+ tokenizer = self.tokenizer
423
+ image_token_id = self.image_token_id
424
+
425
+ embed_is_patch = list[torch.Tensor]()
426
+
427
+ for pixel_values in pixel_values_lst:
428
+ num_patches = pixel_values.shape[0]
429
+ feature_size = num_patches * self.num_image_token
430
+
431
+ image_repl = self.get_image_repl(feature_size, num_patches)
432
+ feature_tokens = tokenizer.encode(image_repl.features,
433
+ add_special_tokens=False)
434
+
435
+ text = [t.replace('<image>', image_repl.full, 1) for t in text]
436
+ embed_is_patch.append(
437
+ torch.tensor(feature_tokens) == image_token_id)
438
+
439
+ image_inputs["embed_is_patch"] = embed_is_patch
440
+
441
+ text_inputs = self.tokenizer(text)
442
+
443
+ return {
444
+ **BatchEncoding(text_inputs, tensor_type=return_tensors),
445
+ **image_inputs,
446
+ }
447
+
448
+
449
+class SkyworkR1VProcessor(BaseSkyworkR1VProcessor):
450
+
451
+ @property
452
+ def image_token_id(self) -> int:
453
+ return self.tokenizer.get_vocab()[IMG_CONTEXT]
454
+
455
+ def get_image_repl(
456
+ self,
457
+ feature_size: int,
458
+ num_patches: Optional[int],
459
+ ) -> PromptUpdateDetails[str]:
460
+ repl_features = IMG_CONTEXT * feature_size
461
+ repl_full = IMG_START + repl_features + IMG_END
462
+
463
+ return PromptUpdateDetails(full=repl_full, features=repl_features)
464
+
465
+
466
+class BaseSkyworkR1VProcessingInfo(BaseProcessingInfo):
467
+
468
+ @abstractmethod
469
+ def get_hf_processor(
470
+ self,
471
+ *,
472
+ min_dynamic_patch: Optional[int] = None,
473
+ max_dynamic_patch: Optional[int] = None,
474
+ dynamic_image_size: Optional[bool] = None,
475
+ **kwargs: object,
476
+ ) -> BaseSkyworkR1VProcessor:
477
+ raise NotImplementedError
478
+
479
+ def get_supported_mm_limits(self) -> Mapping[str, Optional[int]]:
480
+ return {"image": None}
481
+
482
+ def get_mm_max_tokens_per_item(
483
+ self,
484
+ seq_len: int,
485
+ mm_counts: Mapping[str, int],
486
+ ) -> Mapping[str, int]:
487
+ return {"image": self.get_max_image_tokens()}
488
+
489
+ def get_num_image_tokens(
490
+ self,
491
+ *,
492
+ image_width: int,
493
+ image_height: int,
494
+ processor: Optional[BaseSkyworkR1VProcessor],
495
+ ) -> int:
496
+ if processor is None:
497
+ processor = self.get_hf_processor()
498
+
499
+ return processor.get_num_image_tokens(
500
+ image_width=image_width,
501
+ image_height=image_height,
502
+ )
503
+
504
+ def get_max_image_tokens(self) -> int:
505
+ target_width, target_height = self.get_image_size_with_most_features()
506
+
507
+ return self.get_num_image_tokens(
508
+ image_width=target_width,
509
+ image_height=target_height,
510
+ processor=None,
511
+ )
512
+
513
+ def get_image_size_with_most_features(self) -> ImageSize:
514
+ processor = self.get_hf_processor()
515
+
516
+ base_size = processor.image_size
517
+ target_ratios = processor.resolve_target_ratios()
518
+
519
+ largest_feature_size, largest_feature_pinpoint = 0, None
520
+ for wr, hr in target_ratios:
521
+ width, height = base_size * wr, base_size * hr
522
+
523
+ feat_size = self.get_num_image_tokens(
524
+ image_width=width,
525
+ image_height=height,
526
+ processor=processor,
527
+ )
528
+ if feat_size > largest_feature_size:
529
+ largest_feature_size = feat_size
530
+ largest_feature_pinpoint = ImageSize(width=width,
531
+ height=height)
532
+
533
+ if largest_feature_size == 0 or largest_feature_pinpoint is None:
534
+ raise ValueError("Cannot have a largest feature size of 0!")
535
+
536
+ return largest_feature_pinpoint
537
+
538
+
539
+_I = TypeVar("_I", bound=BaseSkyworkR1VProcessingInfo)
540
+
541
+
542
+class SkyworkR1VDummyInputsBuilder(BaseDummyInputsBuilder[_I]):
543
+
544
+ def get_dummy_processor_inputs(
545
+ self,
546
+ seq_len: int,
547
+ mm_counts: Mapping[str, int],
548
+ ) -> ProcessorInputs:
549
+ target_width, target_height = \
550
+ self.info.get_image_size_with_most_features()
551
+ num_images = mm_counts.get("image", 0)
552
+
553
+ mm_data = {
554
+ "image":
555
+ self._get_dummy_images(width=target_width,
556
+ height=target_height,
557
+ num_images=num_images)
558
+ }
559
+
560
+ return ProcessorInputs(
561
+ prompt_text="<image>" * num_images,
562
+ mm_data=mm_data,
563
+ )
564
+
565
+
566
+class SkyworkR1VMultiModalProcessor(BaseMultiModalProcessor[_I]):
567
+
568
+ def _call_hf_processor(
569
+ self,
570
+ prompt: str,
571
+ mm_data: Mapping[str, object],
572
+ mm_kwargs: Mapping[str, object],
573
+ ) -> Mapping[str, NestedTensors]:
574
+ processed_outputs = super()._call_hf_processor(
575
+ prompt=prompt,
576
+ mm_data=mm_data,
577
+ mm_kwargs=mm_kwargs,
578
+ )
579
+
580
+ hf_processor = self.info.get_hf_processor(**mm_kwargs)
581
+ image_token_id = hf_processor.image_token_id
582
+
583
+ # Since there may be extra tokens in the feature placeholders,
584
+ # we need to pass the image token ID to the model to select the
585
+ # tokens to merge from the vision encoder outputs
586
+ processed_outputs["image_token_id"] = torch.tensor(image_token_id)
587
+
588
+ return processed_outputs
589
+
590
+ def _get_mm_fields_config(
591
+ self,
592
+ hf_inputs: Mapping[str, NestedTensors],
593
+ hf_processor_mm_kwargs: Mapping[str, object],
594
+ ) -> Mapping[str, MultiModalFieldConfig]:
595
+ image_num_patches = hf_inputs.get("image_num_patches", torch.empty(0))
596
+ num_images = len(image_num_patches)
597
+
598
+ return dict(
599
+ pixel_values_flat=MultiModalFieldConfig.flat_from_sizes(
600
+ "image", image_num_patches),
601
+ image_num_patches=MultiModalFieldConfig.batched("image"),
602
+ embed_is_patch=MultiModalFieldConfig.batched("image"),
603
+ image_embeds=MultiModalFieldConfig.batched("image"),
604
+ image_token_id=MultiModalFieldConfig.shared("image", num_images),
605
+ )
606
+
607
+ def _get_prompt_updates(
608
+ self,
609
+ mm_items: MultiModalDataItems,
610
+ hf_processor_mm_kwargs: Mapping[str, object],
611
+ out_mm_kwargs: MultiModalKwargs,
612
+ ) -> Sequence[PromptUpdate]:
613
+ hf_processor = self.info.get_hf_processor(**hf_processor_mm_kwargs)
614
+
615
+ if "image_num_patches" in out_mm_kwargs:
616
+ image_num_patches = out_mm_kwargs["image_num_patches"]
617
+ assert isinstance(image_num_patches, torch.Tensor)
618
+ image_num_patches = image_num_patches.tolist()
619
+ elif "image_embeds" in out_mm_kwargs:
620
+ # TODO: Use image size information in dictionary embedding inputs
621
+ # to compute num_patches (similar to Qwen2-VL)
622
+ image_num_patches = [None] * len(out_mm_kwargs["image_embeds"])
623
+ else:
624
+ image_num_patches = []
625
+
626
+ def get_replacement_skyworkr1v(item_idx: int):
627
+ images = mm_items.get_items(
628
+ "image", (ImageEmbeddingItems, ImageProcessorItems))
629
+
630
+ if isinstance(images, ImageEmbeddingItems):
631
+ feature_size = images.get_feature_size(item_idx)
632
+ else:
633
+ image_size = images.get_image_size(item_idx)
634
+ feature_size = self.info.get_num_image_tokens(
635
+ image_width=image_size.width,
636
+ image_height=image_size.height,
637
+ processor=hf_processor,
638
+ )
639
+
640
+ num_patches = image_num_patches[item_idx]
641
+ if num_patches is not None:
642
+ assert isinstance(num_patches, int)
643
+
644
+ return hf_processor.get_image_repl(feature_size, num_patches)
645
+
646
+ return [
647
+ PromptReplacement(
648
+ modality="image",
649
+ target="<image>",
650
+ replacement=get_replacement_skyworkr1v,
651
+ )
652
+ ]
653
+
654
+
655
+class SkyworkR1VProcessingInfo(BaseSkyworkR1VProcessingInfo):
656
+
657
+ def get_hf_processor(
658
+ self,
659
+ *,
660
+ min_dynamic_patch: Optional[int] = None,
661
+ max_dynamic_patch: Optional[int] = None,
662
+ dynamic_image_size: Optional[bool] = None,
663
+ **kwargs: object,
664
+ ) -> SkyworkR1VProcessor:
665
+ if min_dynamic_patch is not None:
666
+ kwargs["min_dynamic_patch"] = min_dynamic_patch
667
+ if max_dynamic_patch is not None:
668
+ kwargs["max_dynamic_patch"] = max_dynamic_patch
669
+ if dynamic_image_size is not None:
670
+ kwargs["dynamic_image_size"] = dynamic_image_size
671
+
672
+ return self.ctx.init_processor(
673
+ SkyworkR1VProcessor,
674
+ config=self.get_hf_config(),
675
+ tokenizer=self.get_tokenizer(),
676
+ **kwargs,
677
+ )
678
+
679
+
680
+@MULTIMODAL_REGISTRY.register_processor(
681
+ SkyworkR1VMultiModalProcessor,
682
+ info=SkyworkR1VProcessingInfo,
683
+ dummy_inputs=SkyworkR1VDummyInputsBuilder)
684
+class SkyworkR1VChatModel(nn.Module, SupportsMultiModal, SupportsPP):
685
+
686
+ def __init__(self, *, vllm_config: VllmConfig, prefix: str = "") -> None:
687
+ super().__init__()
688
+
689
+ config = vllm_config.model_config.hf_config
690
+ quant_config = vllm_config.quant_config
691
+ multimodal_config = vllm_config.model_config.multimodal_config
692
+
693
+ self.config = config
694
+ self.multimodal_config = multimodal_config
695
+ self._patch_quant_config(config, quant_config)
696
+
697
+ image_size = config.force_image_size or config.vision_config.image_size
698
+ patch_size = config.vision_config.patch_size
699
+ self.patch_size = patch_size
700
+ self.num_image_token = int(
701
+ (image_size // patch_size)**2 * (config.downsample_ratio**2))
702
+ self.downsample_ratio = config.downsample_ratio
703
+ self.ps_version = config.ps_version
704
+
705
+ self.llm_arch_name = config.text_config.architectures[0]
706
+ self.is_mono = self.llm_arch_name == 'SkyworkLM2VEForCausalLM'
707
+ self.vision_model = self._init_vision_model(
708
+ config,
709
+ quant_config=quant_config,
710
+ is_mono=self.is_mono,
711
+ prefix=maybe_prefix(prefix, "vision_model"),
712
+ )
713
+
714
+ self.language_model = init_vllm_registered_model(
715
+ vllm_config=vllm_config,
716
+ hf_config=config.text_config,
717
+ prefix=maybe_prefix(prefix, "language_model"),
718
+ )
719
+
720
+ self.mlp1 = self._init_mlp1(config)
721
+
722
+ self.img_context_token_id = None
723
+ self.visual_token_mask = None
724
+ self.make_empty_intermediate_tensors = (
725
+ self.language_model.make_empty_intermediate_tensors)
726
+
727
+ def _patch_quant_config(self, config: PretrainedConfig,
728
+ quant_config: QuantizationConfig):
729
+ # the awq models from OpenGVLab missing `modules_to_not_convert`
730
+ # patch the quant_config to add `modules_to_not_convert` back
731
+ if isinstance(quant_config, AWQConfig):
732
+ text_config = config.text_config
733
+ llm_quant_config = getattr(text_config, "quantization_config",
734
+ None)
735
+ if (not quant_config.modules_to_not_convert) and \
736
+ (llm_quant_config is not None):
737
+ quant_config.modules_to_not_convert.append("vision_model")
738
+
739
+ @cached_property
740
+ def sampler(self):
741
+ if hasattr(self.language_model, "sampler"):
742
+ return self.language_model.sampler
743
+
744
+ return get_sampler()
745
+
746
+ def _init_vision_model(
747
+ self,
748
+ config: PretrainedConfig,
749
+ quant_config: Optional[QuantizationConfig],
750
+ *,
751
+ is_mono: bool,
752
+ prefix: str,
753
+ ):
754
+ if not is_mono:
755
+ vision_feature_layer = config.select_layer
756
+ if vision_feature_layer < 0:
757
+ num_hidden_layers = config.vision_config.num_hidden_layers \
758
+ + vision_feature_layer + 1
759
+ else:
760
+ num_hidden_layers = vision_feature_layer + 1
761
+
762
+ return InternVisionModel(
763
+ config.vision_config,
764
+ quant_config=quant_config,
765
+ num_hidden_layers_override=num_hidden_layers,
766
+ prefix=prefix,
767
+ )
768
+ else:
769
+ return InternVisionPatchModel(config.vision_config)
770
+
771
+ def _init_mlp1(self, config: PretrainedConfig) -> nn.Sequential:
772
+ vit_hidden_size = config.vision_config.hidden_size
773
+ llm_hidden_size = config.text_config.hidden_size
774
+
775
+ return nn.Sequential(
776
+ nn.LayerNorm(vit_hidden_size * int(1 / self.downsample_ratio)**2),
777
+ ReplicatedLinear(vit_hidden_size *
778
+ int(1 / self.downsample_ratio)**2,
779
+ llm_hidden_size,
780
+ return_bias=False),
781
+ nn.GELU(),
782
+ ReplicatedLinear(llm_hidden_size,
783
+ llm_hidden_size,
784
+ return_bias=False),
785
+ )
786
+
787
+ def pixel_shuffle(self, x, scale_factor=0.5):
788
+ n, w, h, c = x.size()
789
+ # N, W, H, C --> N, W, H * scale, C // scale
790
+ x = x.view(n, w, int(h * scale_factor), int(c / scale_factor))
791
+ # N, W, H * scale, C // scale --> N, H * scale, W, C // scale
792
+ x = x.permute(0, 2, 1, 3).contiguous()
793
+ x = x.view(n, int(h * scale_factor), int(w * scale_factor),
794
+ int(c / (scale_factor * scale_factor)))
795
+ if self.ps_version == 'v1':
796
+ pass
797
+ else:
798
+ x = x.permute(0, 2, 1, 3).contiguous()
799
+ return x
800
+
801
+ def extract_feature(self, pixel_values: torch.Tensor) -> torch.Tensor:
802
+ vit_embeds = self.vision_model(pixel_values=pixel_values)
803
+ vit_embeds = vit_embeds[:, 1:, :]
804
+
805
+ h = w = int(vit_embeds.shape[1]**0.5)
806
+ vit_embeds = vit_embeds.reshape(vit_embeds.shape[0], h, w, -1)
807
+ vit_embeds = self.pixel_shuffle(vit_embeds,
808
+ scale_factor=self.downsample_ratio)
809
+ vit_embeds = vit_embeds.reshape(vit_embeds.shape[0], -1,
810
+ vit_embeds.shape[-1])
811
+ vit_embeds = self.mlp1(vit_embeds)
812
+ return vit_embeds
813
+
814
+ def _validate_pixel_values(self, data: torch.Tensor) -> torch.Tensor:
815
+
816
+ h = w = self.config.vision_config.image_size
817
+ expected_dims = (3, h, w)
818
+
819
+ def _validate_shape(d: torch.Tensor):
820
+ actual_dims = tuple(d.shape)
821
+
822
+ if actual_dims != expected_dims:
823
+ expected_expr = str(expected_dims)
824
+ raise ValueError(
825
+ "The expected shape of pixel values per image per batch "
826
+ f" per patch is {expected_expr}. "
827
+ f"You supplied {tuple(d.shape)}.")
828
+
829
+ for d in data:
830
+ _validate_shape(d)
831
+
832
+ return data
833
+
834
+ def _parse_and_validate_image_input(
835
+ self, **kwargs: object) -> Optional[SkyworkR1VImageInputs]:
836
+ pixel_values_flat = kwargs.pop("pixel_values_flat", None)
837
+ image_num_patches = kwargs.pop("image_num_patches", None)
838
+ embed_is_patch = kwargs.pop("embed_is_patch", None)
839
+ image_embeds = kwargs.pop("image_embeds", None)
840
+
841
+ if pixel_values_flat is None and image_embeds is None:
842
+ return None
843
+
844
+ if image_embeds is not None:
845
+ if not isinstance(image_embeds, (torch.Tensor, list)):
846
+ raise ValueError("Incorrect type of image embeddings. "
847
+ f"Got type: {type(image_embeds)}")
848
+
849
+ return SkyworkR1VImageEmbeddingInputs(
850
+ type="image_embeds",
851
+ data=flatten_bn(image_embeds),
852
+ )
853
+
854
+ image_token_id = kwargs["image_token_id"]
855
+ assert isinstance(image_token_id, torch.Tensor)
856
+ self.img_context_token_id = image_token_id.flatten().unique().item()
857
+
858
+ if pixel_values_flat is not None:
859
+ if not isinstance(pixel_values_flat, (torch.Tensor, list)):
860
+ raise ValueError("Incorrect type of pixel values. "
861
+ f"Got type: {type(pixel_values_flat)}")
862
+
863
+ if not isinstance(image_num_patches, (torch.Tensor, list)):
864
+ raise ValueError("Incorrect type of image_num_patches. "
865
+ f"Got type: {type(image_num_patches)}")
866
+
867
+ if not isinstance(embed_is_patch, (torch.Tensor, list)):
868
+ raise ValueError("Incorrect type of embed_is_patch. "
869
+ f"Got type: {type(embed_is_patch)}")
870
+
871
+ pixel_values_flat = flatten_bn(pixel_values_flat, concat=True)
872
+ image_num_patches = flatten_bn(image_num_patches, concat=True)
873
+ embed_is_patch = flatten_bn(embed_is_patch)
874
+
875
+ return SkyworkR1VImagePixelInputs(
876
+ type="pixel_values",
877
+ pixel_values_flat=self._validate_pixel_values(
878
+ pixel_values_flat),
879
+ num_patches=image_num_patches,
880
+ embed_is_patch=embed_is_patch,
881
+ )
882
+
883
+ raise AssertionError("This line should be unreachable.")
884
+
885
+ def _process_image_input(
886
+ self,
887
+ image_input: SkyworkR1VImageInputs,
888
+ ) -> Union[torch.Tensor, list[torch.Tensor], tuple[torch.Tensor, ...]]:
889
+ if image_input["type"] == "image_embeds":
890
+ return image_input["data"]
891
+
892
+ assert self.vision_model is not None
893
+
894
+ image_embeds = self.extract_feature(image_input["pixel_values_flat"])
895
+
896
+ num_patches = image_input["num_patches"]
897
+
898
+ # Only one image in the current batch
899
+ if len(num_patches) == 1:
900
+ return image_embeds.view(
901
+ -1, self.config.text_config.hidden_size).unsqueeze(0)
902
+
903
+ # NOTE: Image embeddings are split into separate tensors for each image
904
+ # by the size of each embedding.
905
+ feature_size = image_embeds.shape[1]
906
+ image_embeds = image_embeds.view(-1,
907
+ self.config.text_config.hidden_size)
908
+ image_feature_sizes = [
909
+ num_patches * feature_size for num_patches in num_patches
910
+ ]
911
+ return image_embeds.split(image_feature_sizes)
912
+
913
+ def _set_visual_token_mask(self, input_ids: torch.Tensor) -> None:
914
+ if self.is_mono:
915
+ self.visual_token_mask = (
916
+ input_ids == self.img_context_token_id).reshape(-1, 1)
917
+ else:
918
+ self.visual_token_mask = None
919
+
920
+ def get_multimodal_embeddings(
921
+ self, **kwargs: object) -> Optional[MultiModalEmbeddings]:
922
+ image_input = self._parse_and_validate_image_input(**kwargs)
923
+ if image_input is None:
924
+ return None
925
+
926
+ image_features = self._process_image_input(image_input)
927
+
928
+ if image_input["type"] != "pixel_values":
929
+ return image_features
930
+
931
+ return scatter_patch_features(
932
+ image_features,
933
+ image_input["embed_is_patch"],
934
+ )
935
+
936
+ def get_input_embeddings(
937
+ self,
938
+ input_ids: torch.Tensor,
939
+ multimodal_embeddings: Optional[MultiModalEmbeddings] = None,
940
+ ) -> torch.Tensor:
941
+ inputs_embeds = self.language_model.get_input_embeddings(input_ids)
942
+ if multimodal_embeddings is not None:
943
+ assert self.img_context_token_id is not None
944
+ self._set_visual_token_mask(input_ids)
945
+ inputs_embeds = merge_multimodal_embeddings(
946
+ input_ids,
947
+ inputs_embeds,
948
+ select_patch_features(multimodal_embeddings),
949
+ self.img_context_token_id,
950
+ )
951
+ return inputs_embeds
952
+
953
+ def forward(
954
+ self,
955
+ input_ids: torch.Tensor,
956
+ positions: torch.Tensor,
957
+ intermediate_tensors: Optional[IntermediateTensors] = None,
958
+ inputs_embeds: Optional[torch.Tensor] = None,
959
+ **kwargs: object,
960
+ ) -> Union[SamplerOutput, IntermediateTensors]:
961
+
962
+ if intermediate_tensors is not None:
963
+ input_ids = None
964
+ inputs_embeds = None
965
+
966
+ # NOTE: In v1, inputs_embeds is always generated at model runner, this
967
+ # condition is for v0 compatibility.
968
+ elif inputs_embeds is None:
969
+ vision_embeddings = self.get_multimodal_embeddings(**kwargs)
970
+ inputs_embeds = self.get_input_embeddings(input_ids,
971
+ vision_embeddings)
972
+ input_ids = None
973
+
974
+ forward_kwargs = {
975
+ "input_ids": input_ids,
976
+ "positions": positions,
977
+ "intermediate_tensors": intermediate_tensors,
978
+ "inputs_embeds": inputs_embeds,
979
+ }
980
+
981
+ # Only required if the model is mono-architecture
982
+ if self.visual_token_mask is not None:
983
+ forward_kwargs.update(
984
+ {"visual_token_mask": self.visual_token_mask})
985
+ self.visual_token_mask = None
986
+
987
+ hidden_states = self.language_model.model(**forward_kwargs)
988
+ return hidden_states
989
+
990
+ def compute_logits(
991
+ self,
992
+ hidden_states: torch.Tensor,
993
+ sampling_metadata: SamplingMetadata,
994
+ ) -> Optional[torch.Tensor]:
995
+ return self.language_model.compute_logits(hidden_states,
996
+ sampling_metadata)
997
+
998
+ def sample(
999
+ self,
1000
+ logits: torch.Tensor,
1001
+ sampling_metadata: SamplingMetadata,
1002
+ ) -> Optional[SamplerOutput]:
1003
+ return self.language_model.sample(logits, sampling_metadata)
1004
+
1005
+ def load_weights(self, weights: Iterable[Tuple[str,
1006
+ torch.Tensor]]) -> Set[str]:
1007
+ skip_prefixes = [
1008
+ "action_embed", "temporal_embed", "track_embed",
1009
+ "track_embed_decoder", "box_token", "cg_criterion", "cg_model",
1010
+ "loc_encoder", "loc_decoder", "sam", "temporal_token",
1011
+ "track_token"
1012
+ ]
1013
+ loader = AutoWeightsLoader(self, skip_prefixes=skip_prefixes)
1014
+ return loader.load_weights(weights)
vllm/transformers_utils/config.py
+3
-2
@@ -37,8 +37,8 @@ from vllm.transformers_utils.configs import (ChatGLMConfig, Cohere2Config,
37
MLPSpeculatorConfig, MPTConfig,
38
NemotronConfig, NVLM_D_Config,
39
Olmo2Config, RWConfig,
40
- SolarConfig, Telechat2Config,
41
- UltravoxConfig)
40
+ SkyworkR1VChatConfig, SolarConfig,
41
+ Telechat2Config, UltravoxConfig)
42
# yapf: enable
43
from vllm.transformers_utils.utils import check_gguf_file
44
from vllm.utils import resolve_obj_by_qualname
@@ -76,6 +76,7 @@ _CONFIG_REGISTRY: Dict[str, Type[PretrainedConfig]] = {
76
"NVLM_D": NVLM_D_Config,
77
"olmo2": Olmo2Config,
78
"solar": SolarConfig,
79
+ "skywork_chat": SkyworkR1VChatConfig,
80
"telechat": Telechat2Config,
81
"ultravox": UltravoxConfig,
82
**_CONFIG_REGISTRY_OVERRIDE_HF
vllm/transformers_utils/configs/__init__.py
+2
@@ -20,6 +20,7 @@ from vllm.transformers_utils.configs.mpt import MPTConfig
20
from vllm.transformers_utils.configs.nemotron import NemotronConfig
21
from vllm.transformers_utils.configs.nvlm_d import NVLM_D_Config
22
from vllm.transformers_utils.configs.olmo2 import Olmo2Config
23
+from vllm.transformers_utils.configs.skyworkr1v import SkyworkR1VChatConfig
24
from vllm.transformers_utils.configs.solar import SolarConfig
25
from vllm.transformers_utils.configs.telechat2 import Telechat2Config
26
from vllm.transformers_utils.configs.ultravox import UltravoxConfig
@@ -42,6 +43,7 @@ __all__ = [
43
"NemotronConfig",
44
"NVLM_D_Config",
45
"Olmo2Config",
46
+ "SkyworkR1VChatConfig",
47
"SolarConfig",
48
"Telechat2Config",
49
"UltravoxConfig",
vllm/transformers_utils/configs/skyworkr1v.py
new
+53
@@ -0,0 +1,53 @@
1
+# SPDX-License-Identifier: Apache-2.0
2
+
3
+# Adapted from
4
+# https://huggingface.co/Skywork/Skywork-R1V-38B/blob/main/configuration_skywork_chat.py
5
+# --------------------------------------------------------
6
+# SkyworkR1V
7
+# Copyright (c) 2025 Skywork
8
+# Licensed under The MIT License [see LICENSE for details]
9
+# --------------------------------------------------------
10
+from transformers.configuration_utils import PretrainedConfig
11
+
12
+
13
+class SkyworkR1VChatConfig(PretrainedConfig):
14
+ model_type = 'internvl_chat'
15
+ is_composition = True
16
+
17
+ def __init__(self,
18
+ vision_config=None,
19
+ llm_config=None,
20
+ use_backbone_lora=0,
21
+ use_llm_lora=0,
22
+ select_layer=-1,
23
+ force_image_size=None,
24
+ downsample_ratio=0.5,
25
+ template=None,
26
+ dynamic_image_size=False,
27
+ use_thumbnail=False,
28
+ ps_version='v1',
29
+ min_dynamic_patch=1,
30
+ max_dynamic_patch=6,
31
+ **kwargs):
32
+ super().__init__(**kwargs)
33
+
34
+ if vision_config is None:
35
+ vision_config = {}
36
+
37
+ if llm_config is None:
38
+ llm_config = {}
39
+
40
+ self.vision_config = PretrainedConfig(**vision_config)
41
+ self.text_config = PretrainedConfig(**llm_config)
42
+
43
+ self.use_backbone_lora = use_backbone_lora
44
+ self.use_llm_lora = use_llm_lora
45
+ self.select_layer = select_layer
46
+ self.force_image_size = force_image_size
47
+ self.downsample_ratio = downsample_ratio
48
+ self.template = template
49
+ self.dynamic_image_size = dynamic_image_size
50
+ self.use_thumbnail = use_thumbnail
51
+ self.ps_version = ps_version # pixel shuffle version
52
+ self.min_dynamic_patch = min_dynamic_patch
53
+ self.max_dynamic_patch = max_dynamic_patch