main
py 514 lines 17.6 KB
Raw
1 # SPDX-License-Identifier: Apache-2.0
2 # SPDX-FileCopyrightText: Copyright contributors to the vLLM project
3 from itertools import product
4 from math import ceil
5
6 import numpy as np
7 import torch
8 from PIL import Image
9 from torchvision import transforms
10 from torchvision.transforms.functional import InterpolationMode
11 from transformers import (
12 BatchFeature,
13 ImageProcessingMixin,
14 ProcessorMixin,
15 TensorType,
16 )
17
18 from vllm.tokenizers import TokenizerLike
19
20 MAX_IMAGE_SIZE: int = 3024
21
22 ImageWithPatches = tuple[Image.Image, list[Image.Image], list[bool]]
23
24
25 class Step3VisionProcessor:
26 def __init__(self, size, interpolation_mode="bicubic", patch_size=None):
27 mean = [0.48145466, 0.4578275, 0.40821073]
28 std = [0.26862954, 0.26130258, 0.27577711]
29 patch_size = patch_size if patch_size is not None else size
30
31 self.transform = transforms.Compose(
32 [
33 transforms.ToTensor(),
34 transforms.Normalize(mean, std),
35 transforms.Resize(
36 (size, size),
37 interpolation=InterpolationMode.BICUBIC
38 if interpolation_mode == "bicubic"
39 else InterpolationMode.BILINEAR,
40 antialias=True,
41 ),
42 ]
43 )
44
45 self.patch_transform = (
46 transforms.Compose(
47 [
48 transforms.ToTensor(),
49 transforms.Normalize(mean, std),
50 transforms.Resize(
51 (patch_size, patch_size),
52 interpolation=InterpolationMode.BICUBIC
53 if interpolation_mode == "bicubic"
54 else InterpolationMode.BILINEAR,
55 antialias=True,
56 ),
57 ]
58 )
59 if patch_size is not None
60 else None
61 )
62
63 def __call__(self, image, is_patch=False):
64 if is_patch:
65 assert self.patch_transform is not None
66 return {"pixel_values": self.patch_transform(image).unsqueeze(0)}
67
68 return {"pixel_values": self.transform(image).unsqueeze(0)}
69
70
71 class ImagePatcher:
72 def __init__(self, enable_patch: bool = True) -> None:
73 self.enable_patch = enable_patch
74
75 def determine_window_size(self, long: int, short: int) -> int:
76 if long < 728:
77 return short if long / short > 1.5 else 0
78 return min(short, 504) if long / short > 4 else 504
79
80 def slide_window(
81 self,
82 width: int,
83 height: int,
84 sizes: list[tuple[int, int]],
85 steps: list[tuple[int, int]],
86 img_rate_thr: float = 0.6,
87 ) -> tuple[list[tuple[int, int, int, int]], tuple[int, int]]:
88 assert 1 >= img_rate_thr >= 0, "The `in_rate_thr` should lie in 0~1"
89 windows = []
90 # Sliding windows.
91 for size, step in zip(sizes, steps):
92 size_w, size_h = size
93 step_w, step_h = step
94
95 x_num = 1 if width <= size_w else ceil((width - size_w) / step_w + 1)
96 x_start = [step_w * i for i in range(x_num)]
97 if len(x_start) > 1 and x_start[-1] + size_w > width:
98 x_start[-1] = width - size_w
99
100 y_num = 1 if height <= size_h else ceil((height - size_h) / step_h + 1)
101 y_start = [step_h * i for i in range(y_num)]
102 if len(y_start) > 1 and y_start[-1] + size_h > height:
103 y_start[-1] = height - size_h
104
105 start = np.array(list(product(y_start, x_start)), dtype=int)
106 start[:, [0, 1]] = start[:, [1, 0]]
107 windows.append(np.concatenate([start, start + size], axis=1))
108 windows = np.concatenate(windows, axis=0)
109
110 return [
111 (int(box[0]), int(box[1]), int(box[2] - box[0]), int(box[3] - box[1]))
112 for box in windows
113 ], (x_num, y_num)
114
115 def square_pad(self, img: Image.Image) -> Image.Image:
116 w, h = img.size
117 if w == h:
118 return img
119 size = max(w, h)
120 padded = Image.new(img.mode, (size, size), 0)
121 padded.paste(img, (0, 0))
122 return padded
123
124 def get_image_size_for_padding(
125 self, img_width: int, img_height: int
126 ) -> tuple[int, int]:
127 ratio = img_width / img_height
128 if min(img_height, img_width) < 32 and (ratio > 4 or ratio < 1 / 4):
129 new_size = max(img_height, img_width)
130 return new_size, new_size
131 return img_width, img_height
132
133 def get_image_size_for_preprocess(
134 self, img_width: int, img_height: int
135 ) -> tuple[int, int]:
136 if max(img_height, img_width) > MAX_IMAGE_SIZE:
137 scale_factor = MAX_IMAGE_SIZE / max(img_height, img_width)
138 img_width = int(img_width * scale_factor)
139 img_height = int(img_height * scale_factor)
140 return img_width, img_height
141
142 def get_image_size_for_crop(
143 self, img_width: int, img_height: int, window_size: int
144 ):
145 w_ratio = img_width / window_size
146 h_ratio = img_height / window_size
147
148 if w_ratio < 1:
149 width_new = img_width
150 else:
151 decimal_w = w_ratio - img_width // window_size
152 w_ratio = int(w_ratio) + 1 if decimal_w > 0.2 else int(w_ratio)
153 width_new = window_size * w_ratio
154 if h_ratio < 1:
155 height_new = img_height
156 else:
157 decimal_h = h_ratio - img_height // window_size
158 h_ratio = int(h_ratio) + 1 if decimal_h > 0.2 else int(h_ratio)
159 height_new = window_size * h_ratio
160 return int(width_new), int(height_new)
161
162 def patch_crop(self, img: Image.Image, i: int, j: int, th: int, tw: int):
163 target = img.crop((j, i, j + tw, i + th))
164 return target
165
166 def get_num_patches(self, img_width: int, img_height: int) -> tuple[int, int]:
167 img_width, img_height = self.get_image_size_for_padding(img_width, img_height)
168 img_width, img_height = self.get_image_size_for_preprocess(
169 img_width, img_height
170 )
171 window_size = self.determine_window_size(
172 max(img_height, img_width), min(img_height, img_width)
173 )
174 if window_size == 0 or not self.enable_patch:
175 return 0, 0
176 else:
177 img_width, img_height = self.get_image_size_for_crop(
178 img_width, img_height, window_size
179 )
180 center_list, (x_num, y_num) = self.slide_window(
181 img_width,
182 img_height,
183 [(window_size, window_size)],
184 [(window_size, window_size)],
185 )
186 full_rows = (len(center_list) - 1) // x_num + 1
187 if len(center_list) > 0 and len(center_list) % x_num == 0:
188 full_rows -= 1
189 return len(center_list), full_rows
190
191 def __call__(
192 self, img: Image.Image
193 ) -> tuple[Image.Image, list[Image.Image], list[bool]]:
194 img_width, img_height = img.size
195 new_img_width, new_img_height = self.get_image_size_for_padding(
196 img_width, img_height
197 )
198 if new_img_width != img_width or new_img_height != img_height:
199 img = self.square_pad(img)
200 img_width, img_height = img.size
201
202 new_img_width, new_img_height = self.get_image_size_for_preprocess(
203 img_width, img_height
204 )
205 img = img.resize((new_img_width, new_img_height), Image.Resampling.BILINEAR)
206 window_size = self.determine_window_size(
207 max(new_img_height, new_img_width), min(new_img_height, new_img_width)
208 )
209
210 if window_size == 0 or not self.enable_patch:
211 return img, [], []
212 else:
213 new_img_width, new_img_height = self.get_image_size_for_crop(
214 new_img_width, new_img_height, window_size
215 )
216 if (new_img_width, new_img_height) != (img_width, img_height):
217 img_for_crop = img.resize(
218 (new_img_width, new_img_height), Image.Resampling.BILINEAR
219 )
220 else:
221 img_for_crop = img
222
223 patches = []
224 newlines = []
225 center_list, (x_num, y_num) = self.slide_window(
226 new_img_width,
227 new_img_height,
228 [(window_size, window_size)],
229 [(window_size, window_size)],
230 )
231 for patch_id, center_lf_point in enumerate(center_list):
232 x, y, patch_w, patch_h = center_lf_point
233 big_patch = self.patch_crop(img_for_crop, y, x, patch_h, patch_w)
234 patches.append(big_patch)
235 if (patch_id + 1) % x_num == 0:
236 newlines.append(patch_id)
237
238 if newlines and newlines[-1] == len(patches) - 1:
239 newlines.pop()
240
241 return (
242 img,
243 patches,
244 [i in newlines for i in range(len(patches))],
245 )
246
247
248 class Step3VLImageProcessor(ImageProcessingMixin):
249 def __init__(
250 self,
251 image_size: int = 728,
252 patch_size: int = 504,
253 num_image_feature_size: int = 169,
254 num_patch_feature_size: int = 81,
255 enable_patch: bool = True,
256 ) -> None:
257 self.image_size = image_size
258 self.patch_size = patch_size
259 self.num_image_feature_size = num_image_feature_size
260 self.num_patch_feature_size = num_patch_feature_size
261 self.image_preprocessor = Step3VisionProcessor(
262 image_size, "bilinear", patch_size
263 )
264 self.patcher = ImagePatcher(enable_patch=enable_patch)
265
266 def get_num_image_tokens(self, img_width: int, img_height: int) -> int:
267 num_patches, num_newlines = self.patcher.get_num_patches(img_width, img_height)
268
269 return (
270 num_patches * (self.num_patch_feature_size + 2)
271 + self.num_image_feature_size
272 + 2
273 + num_newlines
274 )
275
276 def _split_images(self, images: list[Image.Image]) -> list[ImageWithPatches]:
277 result = []
278 for img in images:
279 result.append(self.patcher(img))
280 return result
281
282 def _convert_images_to_pixel_values(
283 self,
284 images: list[Image.Image],
285 is_patch: bool = False,
286 ) -> list[torch.Tensor]:
287 return [
288 self.image_preprocessor(img, is_patch=is_patch)["pixel_values"]
289 for img in images
290 ]
291
292 def __call__(
293 self,
294 images: Image.Image | list[Image.Image],
295 return_tensors: str | TensorType | None = None,
296 ) -> BatchFeature:
297 if not isinstance(images, list):
298 images = [images]
299
300 split_images_data = self._split_images(images)
301 pixel_values_lst = []
302 patch_pixel_values_lst = []
303 patch_newline_mask_lst = []
304 num_patches = []
305 for raw_img, img_patches, patch_newline_mask in split_images_data:
306 pixel_values_lst.extend(self._convert_images_to_pixel_values([raw_img]))
307 num_patches.append(len(img_patches))
308 patch_pixel_values_lst.extend(
309 self._convert_images_to_pixel_values(img_patches, is_patch=True)
310 )
311 patch_newline_mask_lst.extend(patch_newline_mask)
312
313 pixel_values = torch.cat(pixel_values_lst)
314 patch_size = self.patch_size
315 image_inputs = {
316 "pixel_values": pixel_values,
317 "num_patches": num_patches,
318 "patch_pixel_values": (
319 torch.cat(patch_pixel_values_lst)
320 if patch_pixel_values_lst
321 else pixel_values.new_empty((0, 3, patch_size, patch_size))
322 ),
323 "patch_newline_mask": torch.tensor(
324 patch_newline_mask_lst, dtype=torch.bool
325 ),
326 }
327 return BatchFeature(image_inputs, tensor_type=return_tensors)
328
329
330 class Step3VLProcessor(ProcessorMixin):
331 attributes = ["image_processor", "tokenizer"]
332
333 def __init__(
334 self,
335 image_processor: Step3VLImageProcessor,
336 tokenizer: TokenizerLike,
337 ) -> None:
338 self.image_processor = image_processor
339 self.tokenizer = tokenizer
340
341 self.image_start_token = image_start_token = "<im_start>"
342 self.image_end_token = image_end_token = "<im_end>"
343 self.patch_start_token = patch_start_token = "<patch_start>"
344 self.patch_end_token = patch_end_token = "<patch_end>"
345 self.patch_newline_token = patch_newline_token = "<patch_newline>"
346 self.image_start_token_id = tokenizer.convert_tokens_to_ids(image_start_token)
347 self.image_end_token_id = tokenizer.convert_tokens_to_ids(image_end_token)
348 self.patch_start_token_id = tokenizer.convert_tokens_to_ids(patch_start_token)
349 self.patch_end_token_id = tokenizer.convert_tokens_to_ids(patch_end_token)
350 self.patch_newline_token_id = tokenizer.convert_tokens_to_ids(
351 patch_newline_token
352 )
353
354 self.image_token = image_token = "<im_patch>"
355 self.image_feature_tokens = image_token * image_processor.num_image_feature_size
356 self.patch_feature_tokens = image_token * image_processor.num_patch_feature_size
357
358 self.image_token_id = image_token_id = tokenizer.convert_tokens_to_ids(
359 image_token
360 )
361 self.image_feature_token_ids = [
362 image_token_id
363 ] * image_processor.num_image_feature_size
364 self.patch_feature_token_ids = [
365 image_token_id
366 ] * image_processor.num_patch_feature_size
367
368 def _get_patch_repl_text(
369 self,
370 num_patches: int,
371 patch_newline_mask: list[bool],
372 ) -> str:
373 assert len(patch_newline_mask) == num_patches
374
375 parts = []
376 for i in range(num_patches):
377 parts.extend(
378 [
379 self.patch_start_token,
380 self.patch_feature_tokens,
381 self.patch_end_token,
382 ]
383 )
384 if patch_newline_mask[i]:
385 parts.append(self.patch_newline_token)
386
387 return "".join(parts)
388
389 def _get_patch_repl_ids(
390 self,
391 num_patches: int,
392 patch_newline_mask: list[bool],
393 ) -> list[int]:
394 assert len(patch_newline_mask) == num_patches
395
396 parts = []
397 for i in range(num_patches):
398 parts.extend(
399 [
400 self.patch_start_token_id,
401 *self.patch_feature_token_ids,
402 self.patch_end_token_id,
403 ]
404 )
405 if patch_newline_mask[i]:
406 parts.append(self.patch_newline_token_id)
407
408 return parts
409
410 def _get_image_repl_text(
411 self,
412 num_images: int,
413 ) -> str:
414 parts = [
415 self.image_start_token,
416 self.image_feature_tokens,
417 self.image_end_token,
418 ] * num_images
419
420 return "".join(parts)
421
422 def _get_image_repl_ids(
423 self,
424 num_images: int,
425 ) -> list[int]:
426 part = [
427 self.image_start_token_id,
428 *self.image_feature_token_ids,
429 self.image_end_token_id,
430 ]
431 return part * num_images
432
433 def get_image_repl_feature_text(
434 self,
435 num_images: int,
436 num_patches: int,
437 patch_new_line_idx: list[bool],
438 ) -> str:
439 patch_repl = self._get_patch_repl_text(num_patches, patch_new_line_idx)
440 image_repl = self._get_image_repl_text(num_images)
441 return patch_repl + image_repl
442
443 def get_image_repl_feature_ids(
444 self,
445 num_images: int,
446 num_patches: int,
447 patch_new_line_idx: list[bool],
448 ) -> list[int]:
449 patch_repl = self._get_patch_repl_ids(num_patches, patch_new_line_idx)
450 image_repl = self._get_image_repl_ids(num_images)
451 return patch_repl + image_repl
452
453 def replace_placeholder(self, text: str, placeholder: str, repls: list[str]) -> str:
454 parts = text.split(placeholder)
455
456 if len(parts) - 1 != len(repls):
457 raise ValueError(
458 "The number of placeholders does not match the number of replacements."
459 )
460
461 result = [parts[0]]
462 for i, repl in enumerate(repls):
463 result.append(repl)
464 result.append(parts[i + 1])
465
466 return "".join(result)
467
468 def __call__(
469 self,
470 text: str | list[str] | None = None,
471 images: Image.Image | list[Image.Image] | None = None,
472 return_tensors: str | TensorType | None = None,
473 ) -> BatchFeature:
474 if images is not None:
475 image_inputs = self.image_processor(
476 images=images,
477 return_tensors=return_tensors,
478 )
479 num_patches = image_inputs["num_patches"]
480 patch_newline_mask = image_inputs["patch_newline_mask"]
481 else:
482 image_inputs = {}
483 num_patches = []
484 patch_newline_mask = []
485
486 if text is not None:
487 if not isinstance(text, list):
488 text = [text]
489
490 if image_inputs:
491 image_token = self.image_token
492 image_repl_str_lst = []
493 start = 0
494 for n_patches in num_patches:
495 image_repl_str = self.get_image_repl_feature_text(
496 1, n_patches, patch_newline_mask[start : start + n_patches]
497 )
498 image_repl_str_lst.append(image_repl_str)
499
500 start += n_patches
501
502 text = [
503 self.replace_placeholder(t, image_token, image_repl_str_lst)
504 for t in text
505 ]
506
507 text_inputs = self.tokenizer(text)
508 else:
509 text_inputs = {}
510
511 return BatchFeature(
512 data={**text_inputs, **image_inputs},
513 tensor_type=return_tensors,
514 )