CoolFace
Modelpublic

lemuralabs/Step-3.7-Flash-OptiQ-3.7bpw-mlx

sourceHugging Faceapache-2.0updated 2mo agoView on Hugging Face
3likes94downloads
processing_step3.py476 linesDownload Raw Back to root
1from transformers import BaseImageProcessor, ImageProcessingMixin2from transformers.processing_utils import ImagesKwargs, MultiModalData, ProcessingKwargs, ProcessorMixin, Unpack, VideosKwargs3import math4from typing import Iterable, Optional, Tuple, List, TypedDict, Literal, Union, overload5 6from PIL import Image7import torch8import numpy as np9import torchvision10from torch import nn11from torch.nn import functional as F, LayerNorm12from torchvision.transforms.functional import InterpolationMode13from transformers.activations import ACT2FN14from torchvision import transforms15from torchvision.transforms.functional import InterpolationMode16from transformers.feature_extraction_utils import BatchFeature, TensorType17from transformers.image_utils import ImageInput18from transformers.processing_utils import ProcessingKwargs, ProcessorMixin, Unpack19from transformers.tokenization_utils_tokenizers import TokenizersBackend20from math import ceil21from itertools import product22 23 24 25MAX_IMAGE_SIZE: int = 302426 27class Step3VLImagePixelInputs(TypedDict):28    type: Literal["pixel_values"]29    pixel_values: torch.Tensor30    patch_pixel_values: Optional[torch.Tensor]31    num_patches: list[int]32 33 34class Step3VLImageEmbeddingInputs(TypedDict):35    type: Literal["image_embeds"]36    image_embeds: torch.Tensor37 38 39ImageWithPatches = tuple[Image.Image, list[Image.Image], list[int] | None]40 41 42class GPUToTensor(torch.nn.Module):43 44    def forward(self, raw_image: Union[np.ndarray,45                                       Image.Image]) -> torch.Tensor:46        if isinstance(raw_image, Image.Image):47            return transforms.ToTensor()(raw_image)48        if raw_image.ndim == 2:49            raw_image = raw_image[:, :, None].repeat(3, -1)50        if torch.cuda.is_available():51            device = torch.device("cuda")52        else:53            device = torch.device("cpu")54        image_tensor = torch.from_numpy(raw_image).to(device)55        image_tensor = torch.permute(image_tensor, (2, 0, 1)).contiguous()56        if image_tensor.dtype == torch.uint8:57            image_tensor = image_tensor.to(torch.float32).div(255)58        return image_tensor59 60class Step3VisionProcessor(BaseImageProcessor):61 62    def __init__(self, size, interpolation_mode="bicubic", patch_size=None):63        mean = [0.48145466, 0.4578275, 0.40821073]64        std = [0.26862954, 0.26130258, 0.27577711]65        patch_size = patch_size if patch_size is not None else size66 67        self.transform = transforms.Compose([68            GPUToTensor(),69            transforms.Normalize(mean, std),70            transforms.Resize(71                (size, size),72                interpolation=InterpolationMode.BICUBIC if interpolation_mode73                == "bicubic" else InterpolationMode.BILINEAR,74                antialias=True),75        ])76 77        self.patch_transform = transforms.Compose([78            GPUToTensor(),79            transforms.Normalize(mean, std),80            transforms.Resize(81                (patch_size, patch_size),82                interpolation=InterpolationMode.BICUBIC if interpolation_mode83                == "bicubic" else InterpolationMode.BILINEAR,84                antialias=True),85        ]) if patch_size is not None else None86 87    def __call__(self, image, is_patch=False):88        if is_patch:89            return {"pixel_values": self.patch_transform(image).unsqueeze(0)}90        else:91            return {"pixel_values": self.transform(image).unsqueeze(0)}92 93class ImagePatcher:94    def determine_window_size(self, long: int, short: int) -> int:95        if long <= 728:96            return short if long / short > 1.5 else 097        return min(short, 504) if long / short > 4 else 50498    def slide_window(99        self,100        width: int,101        height: int,102        sizes: list[tuple[int, int]],103        steps: list[tuple[int, int]],104        img_rate_thr: float = 0.6,105    ) -> tuple[list[tuple[int, int, int, int]], tuple[int, int]]:106        assert 1 >= img_rate_thr >= 0, "The `in_rate_thr` should lie in 0~1"107        windows = []108        # Sliding windows.109        for size, step in zip(sizes, steps):110            size_w, size_h = size111            step_w, step_h = step112 113            x_num = 1 if width <= size_w else ceil((width - size_w) / step_w +114                                                   1)115            x_start = [step_w * i for i in range(x_num)]116            if len(x_start) > 1 and x_start[-1] + size_w > width:117                x_start[-1] = width - size_w118 119            y_num = 1 if height <= size_h else ceil((height - size_h) /120                                                    step_h + 1)121            y_start = [step_h * i for i in range(y_num)]122            if len(y_start) > 1 and y_start[-1] + size_h > height:123                y_start[-1] = height - size_h124 125            start = np.array(list(product(y_start, x_start)), dtype=int)126            start[:, [0, 1]] = start[:, [1, 0]]127            windows.append(np.concatenate([start, start + size], axis=1))128        windows = np.concatenate(windows, axis=0)129 130        return [(int(box[0]), int(box[1]), int(box[2] - box[0]),131                 int(box[3] - box[1])) for box in windows], (x_num, y_num)132 133    def square_pad(self, img: Image.Image) -> Image.Image:134        w, h = img.size135        if w == h:136            return img137        size = max(w, h)138        padded = Image.new(img.mode, (size, size), 0)139        padded.paste(img, (0, 0))140        return padded141 142    def get_image_size_for_padding(self, img_width: int,143                                   img_height: int) -> tuple[int, int]:144        ratio = img_width / img_height145        if min(img_height, img_width) < 32 and (ratio > 4 or ratio < 1 / 4):146            new_size = max(img_height, img_width)147            return new_size, new_size148        return img_width, img_height149 150    def get_image_size_for_preprocess(self, img_width: int,151                                      img_height: int) -> tuple[int, int]:152 153        if max(img_height, img_width) > MAX_IMAGE_SIZE:154            scale_factor = MAX_IMAGE_SIZE / max(img_height, img_width)155            img_width = int(img_width * scale_factor)156            img_height = int(img_height * scale_factor)157        return img_width, img_height158 159    def get_image_size_for_crop(self, img_width: int, img_height: int,160                                window_size: int):161        w_ratio = img_width / window_size162        h_ratio = img_height / window_size163 164        if w_ratio < 1:165            width_new = img_width166        else:167            decimal_w = w_ratio - img_width // window_size168            w_ratio = int(w_ratio) + 1 if decimal_w > 0.2 else int(w_ratio)169            width_new = window_size * w_ratio170        if h_ratio < 1:171            height_new = img_height172        else:173            decimal_h = h_ratio - img_height // window_size174            h_ratio = int(h_ratio) + 1 if decimal_h > 0.2 else int(h_ratio)175            height_new = window_size * h_ratio176        return int(width_new), int(height_new)177 178    def patch_crop(self, img: Image.Image, i: int, j: int, th: int, tw: int):179        target = img.crop((j, i, j + tw, i + th))180        return target181 182    def get_num_patches(self, img_width: int,183                        img_height: int) -> tuple[int, int]:184        img_width, img_height = self.get_image_size_for_padding(185            img_width, img_height)186        img_width, img_height = self.get_image_size_for_preprocess(187            img_width, img_height)188        window_size = self.determine_window_size(max(img_height, img_width),189                                                 min(img_height, img_width))190        if window_size == 0:191            return 0, 0192        else:193            img_width, img_height = self.get_image_size_for_crop(194                img_width, img_height, window_size)195            center_list, (x_num, y_num) = self.slide_window(196                img_width, img_height, [(window_size, window_size)],197                [(window_size, window_size)])198            full_rows = (len(center_list) - 1) // x_num + 1199            if len(center_list) > 0 and len(center_list) % x_num == 0:200                full_rows -= 1201            return len(center_list), full_rows202 203    def __call__(204        self, img: Image.Image205    ) -> tuple[Image.Image, list[Image.Image], list[bool] | None]:206        img_width, img_height = img.size207        new_img_width, new_img_height = self.get_image_size_for_padding(208            img_width, img_height)209        if new_img_width != img_width or new_img_height != img_height:210            img = self.square_pad(img)211            img_width, img_height = img.size212 213        new_img_width, new_img_height = self.get_image_size_for_preprocess(214            img_width, img_height)215        img = img.resize((new_img_width, new_img_height),216                         Image.Resampling.BILINEAR)217        window_size = self.determine_window_size(218            max(new_img_height, new_img_width),219            min(new_img_height, new_img_width))220        # return img, [], None221        if window_size == 0:222            return img, [], None223        else:224            new_img_width, new_img_height = self.get_image_size_for_crop(225                new_img_width, new_img_height, window_size)226            if (new_img_width, new_img_height) != (img_width, img_height):227                img_for_crop = img.resize((new_img_width, new_img_height),228                                          Image.Resampling.BILINEAR)229            else:230                img_for_crop = img231 232            patches = []233            newlines = []234            center_list, (x_num, y_num) = self.slide_window(235                new_img_width, new_img_height, [(window_size, window_size)],236                [(window_size, window_size)])237            for patch_id, center_lf_point in enumerate(center_list):238                x, y, patch_w, patch_h = center_lf_point239                big_patch = self.patch_crop(img_for_crop, y, x, patch_h,240                                            patch_w)241                patches.append(big_patch)242                if (patch_id + 1) % x_num == 0:243                    newlines.append(patch_id)244 245            if newlines and newlines[-1] == len(patches) - 1:246                newlines.pop()247 248            return img, patches, [i in newlines for i in range(len(patches))] if len(patches) > 0 else None249 250 251 252 253class Step3VLProcessor(ProcessorMixin):254    # Align ProcessorMixin with our custom components.255    # We only have an image processor (not a feature extractor) plus a tokenizer.256    attributes = ["tokenizer"]257    tokenizer_class = "AutoTokenizer"258 259    @classmethod260    def _load_tokenizer_from_pretrained(261        cls, sub_processor_type, pretrained_model_name_or_path, subfolder="", **kwargs262    ):263        return TokenizersBackend.from_pretrained(264            pretrained_model_name_or_path,265            subfolder=subfolder,266            **kwargs,267        )268 269    def __init__(270        self,271        tokenizer=None,272        chat_template=None,273        **kwargs274    ) -> None:275        self.image_size = 728276        self.patch_size = 504277 278        self.image_preprocessor = Step3VisionProcessor(self.image_size,279                                                       "bilinear",280                                                       self.patch_size)281 282        self.num_image_feature_size = 169283        self.num_patch_feature_size = 81284        self.image_token = "<im_patch>"285        self.image_feature_placeholder = (self.image_token *286                                          self.num_image_feature_size)287        self.patch_feature_placeholder = (self.image_token *288                                          self.num_patch_feature_size)289        super().__init__(tokenizer=tokenizer, chat_template=chat_template, **kwargs)290        self.patcher = ImagePatcher()291        292    @property293    def image_token_id(self) -> int:294        return self.tokenizer.get_vocab()[self.image_token]295 296    def get_num_image_tokens(self, img_width: int, img_height: int) -> int:297        num_patches, num_newlines = self.patcher.get_num_patches(298            img_width, img_height)299 300        return num_patches * (301            self.num_patch_feature_size +302            2) + self.num_image_feature_size + 2 + num_newlines303 304    def _split_images(self,305                      images: list[Image.Image]) -> list[ImageWithPatches]:306        result = []307        for img in images:308            result.append(self.patcher(img))309        return result310 311    def _convert_images_to_pixel_values(312        self,313        images: list[Image.Image],314        is_patch: bool = False,315    ) -> list[torch.Tensor]:316        return [317            self.image_preprocessor(img, is_patch=is_patch)["pixel_values"]318            for img in images319        ]320 321    def _get_patch_repl(322        self,323        num_patches: int,324        patch_newline_mask: list[bool] | None,325    ) -> tuple[str, list[int]]:326        text = ""327        token_ids = []328        for i in range(num_patches):329            assert len(patch_newline_mask) == num_patches330            text += f"<patch_start>{self.patch_feature_placeholder}<patch_end>"331            token_ids.extend(332                [self.tokenizer.convert_tokens_to_ids("<patch_start>")] +333                [self.image_token_id] * self.num_patch_feature_size +334                [self.tokenizer.convert_tokens_to_ids("<patch_end>")])335            if patch_newline_mask and patch_newline_mask[i]:336                text += "<patch_newline>"337                token_ids.append(338                    self.tokenizer.convert_tokens_to_ids("<patch_newline>"))339        return text, token_ids340 341    def _get_image_repl(342        self,343        num_images: int,344    ) -> tuple[str, list[int]]:345        text = f"<im_start>{self.image_feature_placeholder}<im_end>"346        token_ids = [347            self.tokenizer.convert_tokens_to_ids("<im_start>")348        ] + [self.image_token_id] * self.num_image_feature_size + [349            self.tokenizer.convert_tokens_to_ids("<im_end>")350        ]351        return text * num_images, token_ids * num_images352 353    def _get_image_repl_features(354        self,355        num_images: int,356        num_patches: int,357        patch_new_line_idx: Optional[list[bool]],358    ) -> tuple[str, list[int]]:359        if num_patches > 0:360            patch_repl, patch_repl_ids = self._get_patch_repl(361                num_patches, patch_new_line_idx)362        else:363            patch_repl = ""364            patch_repl_ids = []365        image_repl, image_repl_ids = self._get_image_repl(num_images)366        return patch_repl + image_repl, patch_repl_ids + image_repl_ids367 368    def replace_placeholder(self, text: str, placeholder: str,369                            repls: list[str]) -> str:370        parts = text.split(placeholder)371 372        if len(parts) - 1 != len(repls):373            raise ValueError(374                "The number of placeholders does not match the number of replacements."  # noqa: E501375            )376 377        result = [parts[0]]378        for i, repl in enumerate(repls):379            result.append(repl)380            result.append(parts[i + 1])381 382        return "".join(result)383 384    def __call__(385        self,386        text: Optional[Union[str, list[str]]] = None,387        images: ImageInput | None = None,388        return_tensors: Optional[Union[str, TensorType]] = None,389        **kwargs,390    ) -> BatchFeature:391 392        if images is not None:393            images = self.image_preprocessor.fetch_images(images)394        if text is None:395            text = []396        if not isinstance(text, list):397            text = [text]398        if images is None:399            images = []400        elif not isinstance(images, list):401            images = [images]402        elif isinstance(images[0], list):403            images = images[0]404 405        if len(images) == 0:406            image_inputs = {}407            text_inputs = self.tokenizer(text)408        else:409            splitted_images_data = self._split_images(images)410            pixel_values_lst = []411            patch_pixel_values_lst = []412            patch_newline_mask_lst = []413            image_repl_str_lst = []414            image_repl_ids_lst = []415            num_patches = []416            for raw_img, img_patches, patch_newline_mask in splitted_images_data:  # noqa: E501417                pixel_values_lst.extend(418                    self._convert_images_to_pixel_values([raw_img]))419 420                if len(img_patches) > 0:421                    patch_pixel_values_lst.extend(422                        self._convert_images_to_pixel_values(img_patches,423                                                             is_patch=True))424                num_patches.append(len(img_patches))425 426                image_repl_str, image_repl_ids = self._get_image_repl_features(427                    1, len(img_patches), patch_newline_mask)428                image_repl_str_lst.append(image_repl_str)429                image_repl_ids_lst.extend(image_repl_ids)430 431                if patch_newline_mask is not None:432                    patch_newline_mask_lst.extend(patch_newline_mask)433 434            image_inputs = {435                "pixel_values": torch.cat(pixel_values_lst),436                "num_patches": num_patches,437            }438            if patch_pixel_values_lst:439                image_inputs["patch_pixel_values"] = torch.cat(440                    patch_pixel_values_lst)441            if patch_newline_mask_lst:442                image_inputs["patch_newline_mask"] = torch.tensor(443                    patch_newline_mask_lst, dtype=torch.bool)444 445            text = [446                self.replace_placeholder(t, self.image_token,447                                         image_repl_str_lst) for t in text448            ]449            text_inputs = self.tokenizer(text)450 451        return BatchFeature(452            {453                **text_inputs,454                **image_inputs,455            },456            tensor_type=return_tensors,457        )458        459    # Copied from transformers.models.clip.processing_clip.CLIPProcessor.batch_decode with CLIP->Gemma460    def batch_decode(self, *args, **kwargs):461        """462        This method forwards all its arguments to GemmaTokenizerFast's [`~PreTrainedTokenizer.batch_decode`]. Please463        refer to the docstring of this method for more information.464        """465        return self.tokenizer.batch_decode(*args, **kwargs)466 467    # Copied from transformers.models.clip.processing_clip.CLIPProcessor.decode with CLIP->Gemma468    def decode(self, *args, **kwargs):469        """470        This method forwards all its arguments to GemmaTokenizerFast's [`~PreTrainedTokenizer.decode`]. Please refer to471        the docstring of this method for more information.472        """473        return self.tokenizer.decode(*args, **kwargs)474        475__all__ = ["Step3VLProcessor"]476