"vllm/model_executor/models/deepseek_v3.py" did not exist on "55fb97f7bd61273fe8464a72866a72eaa88b5759"
h2ovl.py 17.8 KB
Newer Older
1
2
# SPDX-License-Identifier: Apache-2.0

3
4
5
6
7
8
9
# adapted from https://huggingface.co/h2oai/h2ovl-mississippi-2b/blob/main/modeling_h2ovl_chat.py
# https://huggingface.co/h2oai/h2ovl-mississippi-2b/blob/main/image_process.py
# --------------------------------------------------------
# H2OVL-Mississippi
# Copyright (c) 2024 H2O.AI
# Licensed under Apache 2.0 License [see LICENSE for details]
# --------------------------------------------------------
10
from typing import Mapping, Optional
11
12
13
14
15

import torch
from PIL import Image
from transformers import PretrainedConfig

16
from vllm.logger import init_logger
17
from vllm.model_executor.layers.quantization import QuantizationConfig
18
19
20
21
22
23
24
25
from vllm.multimodal import MULTIMODAL_REGISTRY
from vllm.multimodal.inputs import MultiModalKwargs
from vllm.multimodal.parse import (ImageEmbeddingItems, ImageProcessorItems,
                                   MultiModalDataItems)
from vllm.multimodal.processing import (ProcessingCache, PromptReplacement,
                                        PromptReplacementDetails)
from vllm.multimodal.profiling import BaseDummyInputsBuilder
from vllm.transformers_utils.tokenizer import AnyTokenizer
26
27

from .intern_vit import InternVisionModel
28
29
30
31
32
from .internvl import (IMG_CONTEXT, IMG_END, IMG_START,
                       BaseInternVLProcessingInfo, BaseInternVLProcessor,
                       InternVLChatModel, InternVLDummyInputsBuilder,
                       InternVLMultiModalProcessor, build_transform,
                       find_closest_aspect_ratio, get_internvl_target_ratios)
33

34
logger = init_logger(__name__)
35

36
37
38
39
40
41

def resolve_h2ovl_min_max_num(
    *,
    min_dynamic_patch: int,
    max_dynamic_patch: int,
    dynamic_image_size: bool,
42
    use_thumbnail: bool,
43
44
45
46
47
) -> tuple[int, int]:
    max_dynamic_patch = max_dynamic_patch if dynamic_image_size else 1

    if use_thumbnail and max_dynamic_patch != 1:
        max_dynamic_patch += 1
48

49
50
51
52
53
54
55
56
57
58
    return min_dynamic_patch, max_dynamic_patch


def get_h2ovl_target_ratios(
    min_num: int,
    max_num: int,
    *,
    prior_aspect_ratio: Optional[tuple[int, int]],
) -> list[tuple[int, int]]:
    target_ratios = get_internvl_target_ratios(min_num, max_num)
59
60
61
62
63
64
65
66

    # if prior_aspect_ratio is provided, filter the target ratios
    if prior_aspect_ratio is not None:
        target_ratios = [
            ratio for ratio in target_ratios if prior_aspect_ratio[0] %
            ratio[0] != 0 and prior_aspect_ratio[1] % ratio[1] != 0
        ]

67
68
69
70
71
72
73
74
75
76
77
78
79
80
    return target_ratios


# modified to include blocks generated in second pass
def calculate_h2ovl_targets(
    *,
    orig_width: int,
    orig_height: int,
    target_ratios: list[tuple[int, int]],
    image_size: int,
    use_thumbnail: bool,
) -> tuple[int, int, int, tuple[int, int]]:
    aspect_ratio = orig_width / orig_height

81
    # find the closest aspect ratio to the target
82
83
84
85
86
87
88
    target_aspect_ratio = find_closest_aspect_ratio(
        aspect_ratio,
        target_ratios,
        width=orig_width,
        height=orig_height,
        image_size=image_size,
    )
89
90
91
92
93

    # calculate the target width and height
    target_width = image_size * target_aspect_ratio[0]
    target_height = image_size * target_aspect_ratio[1]
    blocks = target_aspect_ratio[0] * target_aspect_ratio[1]
94
95
96

    # add thumbnail image if num_blocks != 1
    if use_thumbnail and blocks != 1:
97
        blocks += 1
98

99
100
101
102
    return blocks, target_width, target_height, target_aspect_ratio


# adapted from https://huggingface.co/OpenGVLab/InternVL2-1B
103
104
# refactored to handle prior_aspect_ratio
def dynamic_preprocess_h2ovl(
105
    image: Image.Image,
106
107
    *,
    target_ratios: list[tuple[int, int]],
108
109
    image_size: int,
    use_thumbnail: bool,
110
) -> tuple[list[Image.Image], tuple[int, int]]:
111
112
    orig_width, orig_height = image.size

113
114
115
116
117
118
119
120
121
122
123
124
125
126
    # calculate the number of blocks without thumbnail
    (
        blocks,
        target_width,
        target_height,
        target_aspect_ratio,
    ) = calculate_h2ovl_targets(
        orig_width=orig_width,
        orig_height=orig_height,
        target_ratios=target_ratios,
        image_size=image_size,
        use_thumbnail=False,
    )

127
128
129
130
131
132
133
134
135
136
137
138
139
    # resize the image
    resized_img = image.resize((target_width, target_height))
    processed_images = []
    for i in range(blocks):
        box = (
            (i % (target_width // image_size)) * image_size,
            (i // (target_width // image_size)) * image_size,
            ((i % (target_width // image_size)) + 1) * image_size,
            ((i // (target_width // image_size)) + 1) * image_size,
        )
        # split the image
        split_img = resized_img.crop(box)
        processed_images.append(split_img)
140

141
    assert len(processed_images) == blocks
142

143
144
145
    if use_thumbnail and len(processed_images) != 1:
        thumbnail_img = image.resize((image_size, image_size))
        processed_images.append(thumbnail_img)
146

147
148
149
    return processed_images, target_aspect_ratio


150
def _preprocess_image(
151
    image: Image.Image,
152
153
154
155
156
157
158
159
160
161
162
163
164
    *,
    input_size: int,
    min_num: int,
    max_num: int,
    use_thumbnail: bool,
    prior_aspect_ratio: Optional[tuple[int, int]],
) -> tuple[torch.Tensor, tuple[int, int]]:
    target_ratios = get_h2ovl_target_ratios(
        min_num,
        max_num,
        prior_aspect_ratio=prior_aspect_ratio,
    )

165
    transform = build_transform(input_size=input_size)
166
    images, target_aspect_ratio = dynamic_preprocess_h2ovl(
167
168
169
        image,
        image_size=input_size,
        use_thumbnail=use_thumbnail,
170
        target_ratios=target_ratios,
171
    )
172
173

    pixel_values = torch.stack([transform(image) for image in images])
174
175
176
    return pixel_values, target_aspect_ratio


177
178
# refactored to use the _preprocess_image function
def image_to_pixel_values_h2ovl(
179
    image: Image.Image,
180
    *,
181
182
183
184
    input_size: int,
    min_num: int,
    max_num: int,
    use_thumbnail: bool,
185
    use_msac: bool,
186
187
) -> torch.Tensor:
    # when MSAC is turned on, we need to process the image twice
188
    if use_msac:
189
        # first pass
190
        pixel_values1, aspect_ratio1 = _preprocess_image(
191
192
193
194
195
            image,
            input_size=input_size,
            min_num=min_num,
            max_num=max_num,
            use_thumbnail=True,
196
            prior_aspect_ratio=None,
197
198
        )
        # second pass
199
        pixel_values2, _ = _preprocess_image(
200
201
            image,
            input_size=input_size,
202
            min_num=3,  # Hardcoded value
203
            max_num=max_num,
204
205
            use_thumbnail=True,
            prior_aspect_ratio=aspect_ratio1,
206
207
208
        )
        # combine pixel values
        pixel_values = torch.cat(
209
            [pixel_values2[:-1], pixel_values1[:-1], pixel_values2[-1:]], 0)
210
211

    else:
212
        pixel_values, _ = _preprocess_image(
213
214
215
216
217
            image,
            input_size=input_size,
            min_num=min_num,
            max_num=max_num,
            use_thumbnail=use_thumbnail,
218
            prior_aspect_ratio=None,
219
220
221
222
223
        )

    return pixel_values


224
class H2OVLProcessor(BaseInternVLProcessor):
225

226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
    def __init__(
        self,
        config: PretrainedConfig,
        tokenizer: AnyTokenizer,
        *,
        max_dynamic_patch: Optional[int] = None,
        dynamic_image_size: Optional[bool] = None,
        use_msac: Optional[bool] = None,
    ) -> None:
        super().__init__(
            config,
            tokenizer,
            max_dynamic_patch=max_dynamic_patch,
            dynamic_image_size=dynamic_image_size,
        )
241

242
243
244
        if use_msac is None:
            use_msac = config.use_msac
        assert isinstance(use_msac, bool)
245

246
        self.use_msac = use_msac
247

248
249
250
    @property
    def image_token_id(self) -> int:
        return self.tokenizer.get_vocab()[IMG_CONTEXT]
251

252
253
254
255
256
257
    def get_image_repl_features(
        self,
        feature_size: int,
        num_patches: Optional[int],
    ) -> str:
        return IMG_CONTEXT * feature_size
258

259
260
261
262
263
264
265
    def get_image_repl_full(
        self,
        feature_size: int,
        num_patches: Optional[int],
    ) -> str:
        features = self.get_image_repl_features(feature_size, num_patches)
        return IMG_START + features + IMG_END
266

267
    def resolve_min_max_num(
268
269
270
        self,
        *,
        max_dynamic_patch: Optional[int] = None,
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
        dynamic_image_size: Optional[bool] = None,
        use_thumbnail: Optional[bool] = None,
    ) -> tuple[int, int]:
        min_dynamic_patch = self.min_dynamic_patch
        max_dynamic_patch = (self.max_dynamic_patch if max_dynamic_patch
                             is None else max_dynamic_patch)
        dynamic_image_size = (self.dynamic_image_size if dynamic_image_size
                              is None else dynamic_image_size)
        use_thumbnail = (self.use_thumbnail
                         if use_thumbnail is None else use_thumbnail)

        return resolve_h2ovl_min_max_num(
            min_dynamic_patch=min_dynamic_patch,
            max_dynamic_patch=max_dynamic_patch,
            dynamic_image_size=dynamic_image_size,
            use_thumbnail=use_thumbnail,
        )
288

289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
    def resolve_target_ratios(
        self,
        *,
        max_dynamic_patch: Optional[int] = None,
        dynamic_image_size: Optional[bool] = None,
        use_thumbnail: Optional[bool] = None,
        prior_aspect_ratio: Optional[tuple[int, int]] = None,
    ) -> list[tuple[int, int]]:
        min_num, max_num = self.resolve_min_max_num(
            max_dynamic_patch=max_dynamic_patch,
            dynamic_image_size=dynamic_image_size,
            use_thumbnail=use_thumbnail,
        )
        if prior_aspect_ratio:  # hardcoded value for second pass of use_msac
            min_num = 3
304

305
306
307
308
        return get_h2ovl_target_ratios(
            min_num,
            max_num,
            prior_aspect_ratio=prior_aspect_ratio,
309
310
        )

311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
    def get_num_image_tokens(
        self,
        *,
        image_width: int,
        image_height: int,
        use_msac: Optional[bool] = None,
    ) -> int:
        use_msac = (self.use_msac if use_msac is None else use_msac)

        use_thumbnail = self.use_thumbnail

        if use_msac:
            target_ratios_1 = self.resolve_target_ratios(
                use_thumbnail=False,  # Applied in calculate_targets
            )
            num_patches_1, _, _, aspect_ratio_1 = calculate_h2ovl_targets(
                orig_width=image_width,
                orig_height=image_height,
                image_size=self.image_size,
                target_ratios=target_ratios_1,
                use_thumbnail=True,
            )

            target_ratios_2 = self.resolve_target_ratios(
                use_thumbnail=False,  # Applied in calculate_targets
                prior_aspect_ratio=aspect_ratio_1,
            )
            num_patches_2, _, _, _ = calculate_h2ovl_targets(
                orig_width=image_width,
                orig_height=image_height,
                image_size=self.image_size,
                target_ratios=target_ratios_2,
                use_thumbnail=True,
            )

            num_patches = num_patches_1 + num_patches_2 - 1
347
        else:
348
349
350
351
352
353
354
355
356
357
358
359
            target_ratios = self.resolve_target_ratios(
                use_thumbnail=False,  # Applied in calculate_targets
            )
            num_patches, _, _, _ = calculate_h2ovl_targets(
                orig_width=image_width,
                orig_height=image_height,
                image_size=self.image_size,
                target_ratios=target_ratios,
                use_thumbnail=use_thumbnail,
            )

        return num_patches * self.num_image_token
360

361
362
363
364
365
366
367
368
369
370
371
372
    def _images_to_pixel_values_lst(
        self,
        images: list[Image.Image],
        max_dynamic_patch: Optional[int] = None,
        dynamic_image_size: Optional[bool] = None,
    ) -> list[torch.Tensor]:
        use_msac = self.use_msac if len(images) == 1 else False

        min_num, max_num = self.resolve_min_max_num(
            max_dynamic_patch=max_dynamic_patch,
            dynamic_image_size=dynamic_image_size,
            use_thumbnail=False,  # Applied in image_to_pixel_values
373
374
        )

375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
        return [
            image_to_pixel_values_h2ovl(
                image,
                input_size=self.image_size,
                min_num=min_num,
                max_num=max_num,
                use_thumbnail=self.use_thumbnail,
                use_msac=use_msac,
            ) for image in images
        ]


class H2OVLProcessingInfo(BaseInternVLProcessingInfo):

    def get_hf_processor(
390
391
392
        self,
        *,
        max_dynamic_patch: Optional[int] = None,
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
        dynamic_image_size: Optional[bool] = None,
    ) -> H2OVLProcessor:
        return H2OVLProcessor(
            self.get_hf_config(),
            self.get_tokenizer(),
            max_dynamic_patch=max_dynamic_patch,
            dynamic_image_size=dynamic_image_size,
        )

    def get_mm_max_tokens_per_item(
        self,
        seq_len: int,
        mm_counts: Mapping[str, int],
    ) -> Mapping[str, int]:
        max_tokens_one_image = self.get_max_image_tokens(use_msac=None)
        if mm_counts.get("image", 0) <= 1:
            max_tokens_per_image = max_tokens_one_image
        else:
            max_tokens_per_image = self.get_max_image_tokens(use_msac=False)

        return {"image": max_tokens_per_image}
414

415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
    def get_num_image_tokens(
        self,
        *,
        image_width: int,
        image_height: int,
        processor: Optional[H2OVLProcessor],
        use_msac: Optional[bool] = None,
    ) -> int:
        if processor is None:
            processor = self.get_hf_processor()

        return processor.get_num_image_tokens(
            image_width=image_width,
            image_height=image_height,
            use_msac=use_msac,
        )
431

432
433
    def get_max_image_tokens(self, use_msac: Optional[bool] = None) -> int:
        target_width, target_height = self.get_image_size_with_most_features()
434

435
436
437
438
439
440
        return self.get_num_image_tokens(
            image_width=target_width,
            image_height=target_height,
            processor=None,
            use_msac=use_msac,
        )
441
442


443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
class H2OVLMultiModalProcessor(InternVLMultiModalProcessor[H2OVLProcessingInfo]
                               ):

    def __init__(self,
                 info: H2OVLProcessingInfo,
                 dummy_inputs: "BaseDummyInputsBuilder[H2OVLProcessingInfo]",
                 *,
                 cache: Optional[ProcessingCache] = None,
                 enable_sanity_checks: bool = True) -> None:
        super().__init__(
            info,
            dummy_inputs,
            cache=cache,
            enable_sanity_checks=enable_sanity_checks,
        )

        if self.cache is not None:
            # The processor output depends on the number of images passed,
            # making it incompatible with processing cache which is supposed
            # to be invariant of how many images are passed per prompt
            self.cache = None
            logger.warning_once(
                f"{type(self).__name__} does not support processing cache.")
466

467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
    def _get_prompt_replacements(
        self,
        mm_items: MultiModalDataItems,
        hf_processor_mm_kwargs: Mapping[str, object],
        out_mm_kwargs: MultiModalKwargs,
    ) -> list[PromptReplacement]:
        hf_processor = self.info.get_hf_processor(**hf_processor_mm_kwargs)

        if "image_num_patches" in out_mm_kwargs:
            image_num_patches = out_mm_kwargs["image_num_patches"]
            assert isinstance(image_num_patches, torch.Tensor)
            image_num_patches = image_num_patches.tolist()
        elif "image_embeds" in out_mm_kwargs:
            # TODO: Use image size information in dictionary embedding inputs
            # to compute num_patches (similar to Qwen2-VL)
            image_num_patches = [None] * len(out_mm_kwargs["image_embeds"])
483
        else:
484
485
486
            image_num_patches = []

        num_images = len(image_num_patches)
487

488
489
490
        def get_replacement_internvl(item_idx: int):
            images = mm_items.get_items(
                "image", (ImageEmbeddingItems, ImageProcessorItems))
491

492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
            if isinstance(images, ImageEmbeddingItems):
                feature_size = images.get_feature_size(item_idx)
            else:
                image_size = images.get_image_size(item_idx)
                feature_size = self.info.get_num_image_tokens(
                    image_width=image_size.width,
                    image_height=image_size.height,
                    processor=hf_processor,
                    use_msac=None if num_images == 1 else False,
                )

            num_patches = image_num_patches[item_idx]
            if num_patches is not None:
                assert isinstance(num_patches, int)

            return PromptReplacementDetails(
                full=hf_processor.get_image_repl_full(feature_size,
                                                      num_patches),
                features=hf_processor.get_image_repl_features(
                    feature_size, num_patches),
            )
513

514
515
516
517
518
519
520
        return [
            PromptReplacement(
                modality="image",
                target="<image>",
                replacement=get_replacement_internvl,
            )
        ]
521
522


523
524
525
526
@MULTIMODAL_REGISTRY.register_processor(
    H2OVLMultiModalProcessor,
    info=H2OVLProcessingInfo,
    dummy_inputs=InternVLDummyInputsBuilder)
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
class H2OVLChatModel(InternVLChatModel):

    def _init_vision_model(
        self,
        config: PretrainedConfig,
        quant_config: Optional[QuantizationConfig],
        *,
        is_mono: bool,
        prefix: str,
    ):
        if not is_mono:
            vision_feature_layer = config.select_layer
            if vision_feature_layer < 0:
                num_hidden_layers = (config.vision_config.num_hidden_layers +
                                     vision_feature_layer + 1)
            else:
                num_hidden_layers = vision_feature_layer + 1

            return InternVisionModel(
                config.vision_config,
                quant_config=quant_config,
                num_hidden_layers_override=num_hidden_layers,
                prefix=prefix,
            )
        else:
            msg = "Monolith mode is not applicable to H2OVL"
            raise NotImplementedError(msg)