main
py 387 lines 11.8 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/h2oai/h2ovl-mississippi-2b/blob/main/modeling_h2ovl_chat.py
5 # https://huggingface.co/h2oai/h2ovl-mississippi-2b/blob/main/image_process.py
6 # --------------------------------------------------------
7 # H2OVL-Mississippi
8 # Copyright (c) 2024 H2O.AI
9 # Licensed under Apache 2.0 License [see LICENSE for details]
10 # --------------------------------------------------------
11 import torch
12 from PIL import Image
13
14 from vllm.tokenizers.hf import HfTokenizer
15
16 from .internvl import (
17 InternVLImageProcessor,
18 InternVLProcessor,
19 build_transform,
20 find_closest_aspect_ratio,
21 get_internvl_target_ratios,
22 )
23
24
25 def resolve_h2ovl_min_max_num(
26 *,
27 min_dynamic_patch: int,
28 max_dynamic_patch: int,
29 dynamic_image_size: bool,
30 use_thumbnail: bool,
31 ) -> tuple[int, int]:
32 min_dynamic_patch = min_dynamic_patch if dynamic_image_size else 1
33 max_dynamic_patch = max_dynamic_patch if dynamic_image_size else 1
34
35 if use_thumbnail and max_dynamic_patch != 1:
36 max_dynamic_patch += 1
37
38 return min_dynamic_patch, max_dynamic_patch
39
40
41 def get_h2ovl_target_ratios(
42 min_num: int,
43 max_num: int,
44 *,
45 prior_aspect_ratio: tuple[int, int] | None,
46 ) -> list[tuple[int, int]]:
47 target_ratios = get_internvl_target_ratios(min_num, max_num)
48
49 # if prior_aspect_ratio is provided, filter the target ratios
50 if prior_aspect_ratio is not None:
51 target_ratios = [
52 ratio
53 for ratio in target_ratios
54 if prior_aspect_ratio[0] % ratio[0] != 0
55 and prior_aspect_ratio[1] % ratio[1] != 0
56 ]
57
58 return target_ratios
59
60
61 # modified to include blocks generated in second pass
62 def calculate_h2ovl_targets(
63 *,
64 orig_width: int,
65 orig_height: int,
66 target_ratios: list[tuple[int, int]],
67 image_size: int,
68 use_thumbnail: bool,
69 ) -> tuple[int, int, int, tuple[int, int]]:
70 aspect_ratio = orig_width / orig_height
71
72 # find the closest aspect ratio to the target
73 target_aspect_ratio = find_closest_aspect_ratio(
74 aspect_ratio,
75 target_ratios,
76 width=orig_width,
77 height=orig_height,
78 image_size=image_size,
79 )
80
81 # calculate the target width and height
82 target_width = image_size * target_aspect_ratio[0]
83 target_height = image_size * target_aspect_ratio[1]
84 blocks = target_aspect_ratio[0] * target_aspect_ratio[1]
85
86 # add thumbnail image if num_blocks != 1
87 if use_thumbnail and blocks != 1:
88 blocks += 1
89
90 return blocks, target_width, target_height, target_aspect_ratio
91
92
93 # adapted from https://huggingface.co/OpenGVLab/InternVL2-1B
94 # refactored to handle prior_aspect_ratio
95 def dynamic_preprocess_h2ovl(
96 image: Image.Image,
97 *,
98 target_ratios: list[tuple[int, int]],
99 image_size: int,
100 use_thumbnail: bool,
101 ) -> tuple[list[Image.Image], tuple[int, int]]:
102 orig_width, orig_height = image.size
103
104 # calculate the number of blocks without thumbnail
105 (
106 blocks,
107 target_width,
108 target_height,
109 target_aspect_ratio,
110 ) = calculate_h2ovl_targets(
111 orig_width=orig_width,
112 orig_height=orig_height,
113 target_ratios=target_ratios,
114 image_size=image_size,
115 use_thumbnail=False,
116 )
117
118 # resize the image
119 resized_img = image.resize((target_width, target_height))
120 processed_images = []
121 for i in range(blocks):
122 box = (
123 (i % (target_width // image_size)) * image_size,
124 (i // (target_width // image_size)) * image_size,
125 ((i % (target_width // image_size)) + 1) * image_size,
126 ((i // (target_width // image_size)) + 1) * image_size,
127 )
128 # split the image
129 split_img = resized_img.crop(box)
130 processed_images.append(split_img)
131
132 assert len(processed_images) == blocks
133
134 if use_thumbnail and len(processed_images) != 1:
135 thumbnail_img = image.resize((image_size, image_size))
136 processed_images.append(thumbnail_img)
137
138 return processed_images, target_aspect_ratio
139
140
141 def _preprocess_image(
142 image: Image.Image,
143 *,
144 input_size: int,
145 min_num: int,
146 max_num: int,
147 use_thumbnail: bool,
148 prior_aspect_ratio: tuple[int, int] | None,
149 ) -> tuple[torch.Tensor, tuple[int, int]]:
150 target_ratios = get_h2ovl_target_ratios(
151 min_num,
152 max_num,
153 prior_aspect_ratio=prior_aspect_ratio,
154 )
155
156 transform = build_transform(input_size=input_size)
157 images, target_aspect_ratio = dynamic_preprocess_h2ovl(
158 image,
159 image_size=input_size,
160 use_thumbnail=use_thumbnail,
161 target_ratios=target_ratios,
162 )
163
164 pixel_values = torch.stack([transform(image) for image in images])
165 return pixel_values, target_aspect_ratio
166
167
168 # refactored to use the _preprocess_image function
169 def image_to_pixel_values_h2ovl(
170 image: Image.Image,
171 *,
172 input_size: int,
173 min_num: int,
174 max_num: int,
175 use_thumbnail: bool,
176 use_msac: bool,
177 ) -> torch.Tensor:
178 # when MSAC is turned on, we need to process the image twice
179 if use_msac:
180 # first pass
181 pixel_values1, aspect_ratio1 = _preprocess_image(
182 image,
183 input_size=input_size,
184 min_num=1,
185 max_num=max_num,
186 use_thumbnail=True,
187 prior_aspect_ratio=None,
188 )
189 # second pass
190 pixel_values2, _ = _preprocess_image(
191 image,
192 input_size=input_size,
193 min_num=3,
194 max_num=max_num,
195 use_thumbnail=True,
196 prior_aspect_ratio=aspect_ratio1,
197 )
198 # combine pixel values
199 pixel_values = torch.cat(
200 [pixel_values2[:-1], pixel_values1[:-1], pixel_values2[-1:]], 0
201 )
202
203 else:
204 pixel_values, _ = _preprocess_image(
205 image,
206 input_size=input_size,
207 min_num=min_num,
208 max_num=max_num,
209 use_thumbnail=use_thumbnail,
210 prior_aspect_ratio=None,
211 )
212
213 return pixel_values
214
215
216 class H2OVLImageProcessor(InternVLImageProcessor):
217 def __init__(
218 self,
219 image_size: int,
220 min_dynamic_patch: int,
221 max_dynamic_patch: int,
222 dynamic_image_size: bool,
223 use_thumbnail: bool,
224 use_msac: bool,
225 ) -> None:
226 super().__init__(
227 image_size=image_size,
228 min_dynamic_patch=min_dynamic_patch,
229 max_dynamic_patch=max_dynamic_patch,
230 dynamic_image_size=dynamic_image_size,
231 use_thumbnail=use_thumbnail,
232 )
233
234 self.use_msac = use_msac
235
236 def resolve_min_max_num(
237 self,
238 *,
239 min_dynamic_patch: int | None = None,
240 max_dynamic_patch: int | None = None,
241 dynamic_image_size: bool | None = None,
242 use_thumbnail: bool | None = None,
243 ) -> tuple[int, int]:
244 if min_dynamic_patch is None:
245 min_dynamic_patch = self.min_dynamic_patch
246 if max_dynamic_patch is None:
247 max_dynamic_patch = self.max_dynamic_patch
248 if dynamic_image_size is None:
249 dynamic_image_size = self.dynamic_image_size
250 if use_thumbnail is None:
251 use_thumbnail = self.use_thumbnail
252
253 return resolve_h2ovl_min_max_num(
254 min_dynamic_patch=min_dynamic_patch,
255 max_dynamic_patch=max_dynamic_patch,
256 dynamic_image_size=dynamic_image_size,
257 use_thumbnail=use_thumbnail,
258 )
259
260 def _images_to_pixel_values_lst(
261 self,
262 images: list[Image.Image],
263 min_dynamic_patch: int | None = None,
264 max_dynamic_patch: int | None = None,
265 dynamic_image_size: bool | None = None,
266 ) -> list[torch.Tensor]:
267 use_msac = self.use_msac if len(images) == 1 else False
268
269 min_num, max_num = self.resolve_min_max_num(
270 min_dynamic_patch=min_dynamic_patch,
271 max_dynamic_patch=max_dynamic_patch,
272 dynamic_image_size=dynamic_image_size,
273 use_thumbnail=False, # Applied in image_to_pixel_values
274 )
275
276 return [
277 image_to_pixel_values_h2ovl(
278 image,
279 input_size=self.image_size,
280 min_num=min_num,
281 max_num=max_num,
282 use_thumbnail=self.use_thumbnail,
283 use_msac=use_msac,
284 )
285 for image in images
286 ]
287
288
289 class H2OVLProcessor(InternVLProcessor):
290 def __init__(
291 self,
292 image_processor: H2OVLImageProcessor,
293 tokenizer: HfTokenizer,
294 *,
295 image_seq_length: int,
296 start_image_token: str = "<img>",
297 end_image_token: str = "</img>",
298 ctx_image_token: str = "<IMG_CONTEXT>",
299 ) -> None:
300 super().__init__(
301 image_processor=image_processor,
302 tokenizer=tokenizer,
303 image_seq_length=image_seq_length,
304 start_image_token=start_image_token,
305 end_image_token=end_image_token,
306 ctx_image_token=ctx_image_token,
307 )
308
309 self.image_processor: H2OVLImageProcessor
310
311 def resolve_target_ratios(
312 self,
313 *,
314 min_dynamic_patch: int | None = None,
315 max_dynamic_patch: int | None = None,
316 dynamic_image_size: bool | None = None,
317 use_thumbnail: bool | None = None,
318 prior_aspect_ratio: tuple[int, int] | None = None,
319 override_min_num: int | None = None,
320 ) -> list[tuple[int, int]]:
321 min_num, max_num = self.image_processor.resolve_min_max_num(
322 min_dynamic_patch=min_dynamic_patch,
323 max_dynamic_patch=max_dynamic_patch,
324 dynamic_image_size=dynamic_image_size,
325 use_thumbnail=use_thumbnail,
326 )
327 if override_min_num is not None:
328 min_num = override_min_num
329
330 return get_h2ovl_target_ratios(
331 min_num,
332 max_num,
333 prior_aspect_ratio=prior_aspect_ratio,
334 )
335
336 def get_num_image_tokens(
337 self,
338 *,
339 image_width: int,
340 image_height: int,
341 use_msac: bool | None = None,
342 ) -> int:
343 image_processor = self.image_processor
344 use_msac = image_processor.use_msac if use_msac is None else use_msac
345
346 use_thumbnail = image_processor.use_thumbnail
347
348 if use_msac:
349 target_ratios_1 = self.resolve_target_ratios(
350 use_thumbnail=False, # Applied in calculate_targets
351 override_min_num=1,
352 )
353 num_patches_1, _, _, aspect_ratio_1 = calculate_h2ovl_targets(
354 orig_width=image_width,
355 orig_height=image_height,
356 image_size=image_processor.image_size,
357 target_ratios=target_ratios_1,
358 use_thumbnail=True,
359 )
360
361 target_ratios_2 = self.resolve_target_ratios(
362 use_thumbnail=False, # Applied in calculate_targets
363 prior_aspect_ratio=aspect_ratio_1,
364 override_min_num=3,
365 )
366 num_patches_2, _, _, _ = calculate_h2ovl_targets(
367 orig_width=image_width,
368 orig_height=image_height,
369 image_size=image_processor.image_size,
370 target_ratios=target_ratios_2,
371 use_thumbnail=True,
372 )
373
374 num_patches = num_patches_1 + num_patches_2 - 1
375 else:
376 target_ratios = self.resolve_target_ratios(
377 use_thumbnail=False, # Applied in calculate_targets
378 )
379 num_patches, _, _, _ = calculate_h2ovl_targets(
380 orig_width=image_width,
381 orig_height=image_height,
382 image_size=image_processor.image_size,
383 target_ratios=target_ratios,
384 use_thumbnail=use_thumbnail,
385 )
386
387 return num_patches * self.image_seq_length