CoolFace
Modelpublic

mlx-community/MolmoPoint-8B-fp16

sourceHugging Faceapache-2.0updated 6mo agoView on Hugging Face
0likes14downloads
processing_molmo2.py428 linesDownload Raw Back to root
1"""2Processor class for Molmo2.3"""4from typing import Optional, Union5import dataclasses6 7import numpy as np8 9from transformers.image_utils import ImageInput10from transformers.video_utils import VideoInput11from transformers.processing_utils import (12    Unpack,13    ProcessingKwargs,14    ProcessorMixin, AllKwargsForChatTemplate,15)16from transformers.feature_extraction_utils import BatchFeature17from transformers.tokenization_utils_base import TextInput, PreTokenizedInput18from transformers.utils import logging19 20from transformers import AutoTokenizer21from .image_processing_molmo2 import Molmo2ImagesKwargs, Molmo2ImageProcessor22from .video_processing_molmo2 import Molmo2VideoProcessorKwargs, Molmo2VideoProcessor23 24 25logger = logging.get_logger(__name__)26 27 28# Special tokens, these should be present in any tokenizer we use since the preprocessor uses them29IMAGE_PATCH_TOKEN = f"<im_patch>"  # Where to insert high-res tokens30IMAGE_LOW_RES_TOKEN = f"<im_low>"  # Where to insert low-res tokens31IM_START_TOKEN = f"<im_start>"32LOW_RES_IMAGE_START_TOKEN = f"<low_res_im_start>"33FRAME_START_TOKEN = f"<frame_start>"34IM_END_TOKEN = f"<im_end>"35FRAME_END_TOKEN= f"<frame_end>"36IM_COL_TOKEN = f"<im_col>"37IMAGE_PROMPT = "<|image|>"38VIDEO_PROMPT = "<|video|>"39 40IMAGE_TOKENS = [41    IMAGE_PATCH_TOKEN,42    IM_COL_TOKEN,43    IM_START_TOKEN,44    LOW_RES_IMAGE_START_TOKEN,45    FRAME_START_TOKEN,46    IM_END_TOKEN,47    FRAME_END_TOKEN,48    IMAGE_LOW_RES_TOKEN,49]50 51 52class Molmo2ProcessorKwargs(ProcessingKwargs, total=False):53    """Molmo2 processor kwargs"""54    images_kwargs: Molmo2ImagesKwargs55    videos_kwargs: Molmo2VideoProcessorKwargs56    _defaults = {57        "text_kwargs": {58            "padding": False,59            "return_mm_token_type_ids": True,60        },61        "videos_kwargs": {"return_metadata": True},62    }63 64 65class Molmo2Processor(ProcessorMixin):66    attributes = ["image_processor", "video_processor", "tokenizer"]67    optional_attributes = [68        "chat_template",69        "time_mode",70        "image_use_col_tokens",71        "use_single_crop_col_tokens",72        "use_single_crop_start_token",73        "video_use_col_tokens",74        "use_frame_special_tokens",75    ]76    image_processor_class = "AutoImageProcessor"77    video_processor_class = "AutoVideoProcessor"78    tokenizer_class = "AutoTokenizer"79 80    def __init__(81        self,82        image_processor: Molmo2ImageProcessor = None,83        video_processor: Molmo2VideoProcessor = None,84        tokenizer: AutoTokenizer = None,85        chat_template: Optional[str] = None,86        image_use_col_tokens: Optional[bool] = True,87        use_single_crop_col_tokens: Optional[bool] = None,88        use_single_crop_start_token: Optional[bool] = True,89        video_use_col_tokens: Optional[bool] = False,90        use_frame_special_tokens: Optional[bool] = True,91        use_low_res_token_for_global_crops: bool = False,92        **kwargs93    ) -> None:94        super().__init__(95            image_processor,96            video_processor,97            tokenizer,98            chat_template=chat_template,99            image_use_col_tokens=image_use_col_tokens,100            use_single_crop_col_tokens=use_single_crop_col_tokens,101            use_single_crop_start_token=use_single_crop_start_token,102            video_use_col_tokens=video_use_col_tokens,103            use_frame_special_tokens=use_frame_special_tokens,104        )105        self.image_placeholder_token = IMAGE_PROMPT106        self.video_placeholder_token = VIDEO_PROMPT107        self.image_token_ids = [108            tokenizer.convert_tokens_to_ids(token)109            for token in IMAGE_TOKENS110        ]111        self.use_low_res_token_for_global_crops = use_low_res_token_for_global_crops112        self._patch_metadata = None113 114    def get_image_tokens(self, image_grid: np.ndarray):115        resized_h, resized_w, height, width = image_grid116        per_row = np.full(width, IMAGE_PATCH_TOKEN)117        if self.image_use_col_tokens:118            per_row = np.concatenate([per_row, [IM_COL_TOKEN]], 0)119        joint = [120            [IM_START_TOKEN],121            np.tile(per_row, [height]),122            [IM_END_TOKEN],123        ]124        if self.use_low_res_token_for_global_crops:125            per_row = np.full(resized_w, IMAGE_LOW_RES_TOKEN)126        else:127            per_row = np.full(resized_w, IMAGE_PATCH_TOKEN)128        use_single_crop_col_tokens = (129            self.image_use_col_tokens130            if self.use_single_crop_col_tokens is None131            else self.use_single_crop_col_tokens132        )133        image_start_token = (134            LOW_RES_IMAGE_START_TOKEN135            if self.use_single_crop_start_token136            else IM_START_TOKEN137        )138        if use_single_crop_col_tokens:139            per_row = np.concatenate([per_row, [IM_COL_TOKEN]], 0)140        joint = [141            [image_start_token],142            np.tile(per_row, [resized_h]),143            [IM_END_TOKEN],144        ] + joint145 146        return np.concatenate(joint)147    148    def get_video_string(149        self,150        video_grid: np.ndarray,151        timestamps: np.ndarray,152    ):  153        if self.use_frame_special_tokens:154            start_token_id = FRAME_START_TOKEN155            end_token_id = FRAME_END_TOKEN156        else:157            start_token_id = IM_START_TOKEN158            end_token_id = IM_END_TOKEN159        160        num_frames, h, w = video_grid161        video_string: str = ""162        for frame_idx, frame_time in enumerate(timestamps):163            # `per-frame-compact` time mode164            prev_space = " " if frame_idx > 0 else ""165            frame_prefix = prev_space + f"{frame_time:.1f} " # explicit whitespace before/after image tokens166 167            video_string += frame_prefix168            per_row = np.full(w, IMAGE_PATCH_TOKEN)169            if self.video_use_col_tokens:170                per_row = np.concatenate([per_row, [IM_COL_TOKEN]], 0)171            extra_tokens = np.tile(per_row, [h])172            video_tokens = [173                [start_token_id],174                extra_tokens,175                [end_token_id],176            ]177            video_string += "".join(np.concatenate(video_tokens, 0))178 179        return video_string180 181    def insert_bos(182        self,183        input_ids: np.ndarray,184        attention_mask: np.ndarray,185        bos_token_id: int,186        pad_token_id: int,187    ):188        """189        Args:190            input_ids: [B, S] array with left padding191            attention_mask: [B, S] array (0 for pad, 1 for valid)192            bos_token_id: int193            pad_token_id: int194        Returns:195            input_ids_out: [B, S] or [B, S+1] array with bos inserted if needed196            attention_mask_out: same shape as input_ids_out197        """198 199        need_to_expand = len(input_ids.shape) == 1200        if need_to_expand:201            input_ids = input_ids[None, :]202            attention_mask = attention_mask[None, :]203 204        B, S = input_ids.shape205 206        # Handle zero-length sequence207        if S == 0:208            new_input_ids = np.full((B, 1), bos_token_id, dtype=input_ids.dtype)209            new_attention_mask = np.ones((B, 1), dtype=attention_mask.dtype)210            if need_to_expand:211                new_input_ids = new_input_ids[0]212                new_attention_mask = new_attention_mask[0]213            return new_input_ids, new_attention_mask214 215        first_valid_index = (attention_mask == 1).argmax(axis=-1)  # [B]216        bos_already_present = np.all(input_ids[np.arange(B), first_valid_index] == bos_token_id)217 218        if bos_already_present:219            if need_to_expand:220                input_ids = input_ids[0]221                attention_mask = attention_mask[0]222            return input_ids, attention_mask223        else:224            new_input_ids = np.full((B, S+1), pad_token_id, dtype=input_ids.dtype)225            new_attention_mask = np.zeros((B, S+1), dtype=attention_mask.dtype)226 227            src_idx = np.tile(np.arange(S), (B, 1))  # [B, S]228            valid_mask = src_idx >= first_valid_index[:, None]  # [B, S]229            tgt_idx = src_idx + 1  # shit right230            batch_idx = np.tile(np.arange(B)[:, None], (1, S))  # [B, S]231 232            # flatten valid_positions233            flat_vals = input_ids[valid_mask]234            flat_batch = batch_idx[valid_mask]235            flat_tgt = tgt_idx[valid_mask]236 237            new_input_ids[flat_batch, flat_tgt] = flat_vals238            new_attention_mask[flat_batch, flat_tgt] = 1239            240            insert_pos = first_valid_index241            new_input_ids[np.arange(B), insert_pos] = bos_token_id242            new_attention_mask[np.arange(B), insert_pos] = 1243 244            if need_to_expand:245                new_input_ids = new_input_ids[0]246                new_attention_mask = new_attention_mask[0]247 248            return new_input_ids, new_attention_mask249 250    def __call__(251        self,252        text: Union[TextInput, PreTokenizedInput, list[TextInput], list[PreTokenizedInput]] = None,253        images: ImageInput = None,254        videos: VideoInput = None,255        return_pointing_metadata: bool = False,256        use_low_res_token_for_global_crops: bool = False,257        **kwargs: Unpack[Molmo2ProcessorKwargs],258    ) -> BatchFeature:259        """260 261        Args:262            text (`str`, `list[str]`, `list[list[str]]`):263                The sequence or batch of sequences to be encoded. Each sequence can be a string or a list of strings264                (pretokenized string). If the sequences are provided as list of strings (pretokenized), you must set265                `is_split_into_words=True` (to lift the ambiguity with a batch of sequences).266            images (`PIL.Image.Image`, `np.ndarray`, `torch.Tensor`, `list[PIL.Image.Image]`, `list[np.ndarray]`, `list[torch.Tensor]`):267                The image or batch of images to be prepared. Each image can be a PIL image, NumPy array or PyTorch268                tensor. Both channels-first and channels-last formats are supported.269            videos (`dict[str, Any]` or `list[dict[str, Any]]`):270                The video or batch of videos to be prepared. Each video can be a dictionary with the following keys:271                - `"frames"`: `np.ndarray` of shape (T, H, W, 3)272                - `"timestamps"`: `np.ndarray` of shape (T,)273                - `"sampled_fps"`: `float` (optional)274                - `"sampling_augmentation"`: `str` (optional)275            return_tensors (`str` or [`~utils.TensorType`], *optional*):276                If set, will return tensors of a particular framework. Acceptable values are:277                - `'tf'`: Return TensorFlow `tf.constant` objects.278                - `'pt'`: Return PyTorch `torch.Tensor` objects.279                - `'np'`: Return NumPy `np.ndarray` objects.280                - `'jax'`: Return JAX `jnp.ndarray` objects.281 282        Returns:283            `BatchFeature`: A [`BatchFeature`] with the following fields:284            - **input_ids** -- List of token ids to be fed to a model. Returned when `text` is not `None`.285            - **attention_mask** -- List of indices specifying which tokens should be attended to by the model (when286              `return_attention_mask=True` or if *"attention_mask"* is in `self.model_input_names` and if `text` is not `None`).287            - **pixel_values** -- Pixel values to be fed to a model. Returned when `images` is not `None`.288            - **image_token_pooling** -- Indices of the patches in `image_grids` to pool for each token in `image_tokens`.289              Returned when `images` is not `None`.290            - **image_grids** -- Grids of images. Returned when `images` is not `None`.291            - **image_num_crops** -- Number of crops for each image. Returned when `images` is not `None`.292            - **pixel_values_videos** -- Pixel values of videos to be fed to a model. Returned when `videos` is not `None`.293            - **video_token_pooling** -- Indices of the patches in `video_grids` to pool for each token in `video_tokens`.294              Returned when `videos` is not `None`.295            - **video_grids** -- Grids of videos. Returned when `videos` is not `None`.296        """297        output_kwargs = self._merge_kwargs(298            Molmo2ProcessorKwargs,299            tokenizer_init_kwargs=self.tokenizer.init_kwargs,300            **kwargs,301        )302        patch_metadata = {}303        if images is not None:304            image_inputs = self.image_processor(images, **output_kwargs["images_kwargs"],305                                                return_pointing_metadata=return_pointing_metadata)306            if return_pointing_metadata:307                patch_metadata["token_pooling"] = image_inputs.pop("image_token_pooling_np")308                patch_metadata["subpatch_mapping"] = image_inputs.pop("subpatch_mapping")309                patch_metadata["image_sizes"] = image_inputs.pop("image_sizes")310            image_grids = image_inputs["image_grids"]311        else:312            image_inputs = {}313            image_grids = None314 315        if videos is not None:316            videos_inputs = self.video_processor(317                videos=videos, **output_kwargs["videos_kwargs"],318                return_pointing_metadata=return_pointing_metadata319            )320            if return_pointing_metadata:321                assert len(videos_inputs['video_metadata']) == 1322                vd_metadata = videos_inputs['video_metadata'][0]323                patch_metadata["token_pooling"] = videos_inputs.pop("video_token_pooling_np")324                patch_metadata["subpatch_mapping"] = videos_inputs.pop("subpatch_mapping")325                patch_metadata["timestamps"] = vd_metadata.timestamps326                patch_metadata["video_size"] = (vd_metadata.width, vd_metadata.height)327 328            video_grids = videos_inputs["video_grids"]329            # If user has not requested video metadata, pop it330            if "return_metadata" not in kwargs:331                video_metadata = videos_inputs.pop("video_metadata")332            else:333                video_metadata = videos_inputs["video_metadata"]334        else:335            videos_inputs = {}336            video_grids = None337 338        if not isinstance(text, list):339            text = [text]340        341        text = text.copy() # below lines change text in-place342 343        if image_grids is not None:344            index = 0345            for i in range(len(text)):346                num_images = text[i].count(self.image_placeholder_token)347                image_grids_i = image_grids[index:index+num_images]348                for image_grid in image_grids_i:349                    image_tokens = self.get_image_tokens(image_grid)350                    image_string = "".join(image_tokens)351                    text[i] = text[i].replace(self.image_placeholder_token, image_string, 1)352                index += num_images353        354        if video_grids is not None:355            index = 0356            for i in range(len(text)):357                num_videos = text[i].count(self.video_placeholder_token)358                assert num_videos in {0, 1}, "At most one video is supported for now"359                video_grids_i = video_grids[index:index+num_videos]360                metadata_i = video_metadata[index:index+num_videos]361                for video_grid, metadata in zip(video_grids_i, metadata_i):362                    video_string = self.get_video_string(363                        video_grid,364                        metadata.timestamps,365                    )366                    text[i] = text[i].replace(self.video_placeholder_token, video_string, 1)367                index += num_videos368 369        return_tensors = output_kwargs["text_kwargs"].pop("return_tensors", None)370        return_mm_token_type_ids = output_kwargs["text_kwargs"].pop("return_mm_token_type_ids", False)371        text_inputs = self.tokenizer(text, **output_kwargs["text_kwargs"])372 373        input_ids = text_inputs["input_ids"]374        attention_mask = text_inputs["attention_mask"]375 376        input_ids = np.array(input_ids)377        attention_mask = np.array(attention_mask)378        379        bos = self.tokenizer.bos_token_id or self.tokenizer.eos_token_id380        input_ids, attention_mask = self.insert_bos(381            input_ids, attention_mask, bos, self.tokenizer.pad_token_id382        )383 384        if return_mm_token_type_ids:385            image_tokens = np.array(self.image_token_ids).astype(input_ids.dtype)386            token_type_ids = np.any(input_ids[:, :, None] == image_tokens[None, None, :], axis=-1)387            text_inputs["token_type_ids"] = token_type_ids.tolist()388        389        text_inputs["input_ids"] = input_ids.tolist()390        text_inputs["attention_mask"] = attention_mask.tolist()391 392        features = BatchFeature(393            data={**text_inputs, **image_inputs, **videos_inputs},394            tensor_type=return_tensors,395        )396        if return_pointing_metadata:397            features["metadata"] = patch_metadata398        return features399 400    def post_process_image_text_to_text(401        self, generated_outputs, skip_special_tokens=True, clean_up_tokenization_spaces=False, **kwargs402    ):403        """404        Post-process the output of the model to decode the text.405 406        Args:407            generated_outputs (`torch.Tensor` or `np.ndarray`):408                The output of the model `generate` function. The output is expected to be a tensor of shape `(batch_size, sequence_length)`409                or `(sequence_length,)`.410            skip_special_tokens (`bool`, *optional*, defaults to `True`):411                Whether or not to remove special tokens in the output. Argument passed to the tokenizer's `batch_decode` method.412            clean_up_tokenization_spaces (`bool`, *optional*, defaults to `False`):413                Whether or not to clean up the tokenization spaces. Argument passed to the tokenizer's `batch_decode` method.414            **kwargs:415                Additional arguments to be passed to the tokenizer's `batch_decode method`.416 417        Returns:418            `list[str]`: The decoded text.419        """420        return self.tokenizer.batch_decode(421            generated_outputs,422            skip_special_tokens=skip_special_tokens,423            clean_up_tokenization_spaces=clean_up_tokenization_spaces,424            **kwargs,425        )426 427 428Molmo2Processor.register_for_auto_class()