CoolFace
Modelpublic

mlx-community/Molmo2-8B-4bit

sourceHugging Faceapache-2.0updated 9mo agoView on Hugging Face
2likes130downloads
processing_molmo2.py403 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,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        **kwargs92    ) -> None:93        super().__init__(94            image_processor,95            video_processor,96            tokenizer,97            chat_template=chat_template,98            image_use_col_tokens=image_use_col_tokens,99            use_single_crop_col_tokens=use_single_crop_col_tokens,100            use_single_crop_start_token=use_single_crop_start_token,101            video_use_col_tokens=video_use_col_tokens,102            use_frame_special_tokens=use_frame_special_tokens,103        )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 112    def get_image_tokens(self, image_grid: np.ndarray):113        resized_h, resized_w, height, width = image_grid114        per_row = np.full(width, IMAGE_PATCH_TOKEN)115        if self.image_use_col_tokens:116            per_row = np.concatenate([per_row, [IM_COL_TOKEN]], 0)117        joint = [118            [IM_START_TOKEN],119            np.tile(per_row, [height]),120            [IM_END_TOKEN],121        ]122        per_row = np.full(resized_w, IMAGE_PATCH_TOKEN)123        use_single_crop_col_tokens = (124            self.image_use_col_tokens125            if self.use_single_crop_col_tokens is None126            else self.use_single_crop_col_tokens127        )128        image_start_token = (129            LOW_RES_IMAGE_START_TOKEN130            if self.use_single_crop_start_token131            else IM_START_TOKEN132        )133        if use_single_crop_col_tokens:134            per_row = np.concatenate([per_row, [IM_COL_TOKEN]], 0)135        joint = [136            [image_start_token],137            np.tile(per_row, [resized_h]),138            [IM_END_TOKEN],139        ] + joint140 141        return np.concatenate(joint)142    143    def get_video_string(144        self,145        video_grid: np.ndarray,146        timestamps: np.ndarray,147    ):  148        if self.use_frame_special_tokens:149            start_token_id = FRAME_START_TOKEN150            end_token_id = FRAME_END_TOKEN151        else:152            start_token_id = IM_START_TOKEN153            end_token_id = IM_END_TOKEN154        155        num_frames, h, w = video_grid156        video_string: str = ""157        for frame_idx, frame_time in enumerate(timestamps):158            # `per-frame-compact` time mode159            prev_space = " " if frame_idx > 0 else ""160            frame_prefix = prev_space + f"{frame_time:.1f} " # explicit whitespace before/after image tokens161 162            video_string += frame_prefix163            per_row = np.full(w, IMAGE_PATCH_TOKEN)164            if self.video_use_col_tokens:165                per_row = np.concatenate([per_row, [IM_COL_TOKEN]], 0)166            extra_tokens = np.tile(per_row, [h])167            video_tokens = [168                [start_token_id],169                extra_tokens,170                [end_token_id],171            ]172            video_string += "".join(np.concatenate(video_tokens, 0))173 174        return video_string175 176    def insert_bos(177        self,178        input_ids: np.ndarray,179        attention_mask: np.ndarray,180        bos_token_id: int,181        pad_token_id: int,182    ):183        """184        Args:185            input_ids: [B, S] array with left padding186            attention_mask: [B, S] array (0 for pad, 1 for valid)187            bos_token_id: int188            pad_token_id: int189        Returns:190            input_ids_out: [B, S] or [B, S+1] array with bos inserted if needed191            attention_mask_out: same shape as input_ids_out192        """193 194        need_to_expand = len(input_ids.shape) == 1195        if need_to_expand:196            input_ids = input_ids[None, :]197            attention_mask = attention_mask[None, :]198 199        B, S = input_ids.shape200 201        # Handle zero-length sequence202        if S == 0:203            new_input_ids = np.full((B, 1), bos_token_id, dtype=input_ids.dtype)204            new_attention_mask = np.ones((B, 1), dtype=attention_mask.dtype)205            if need_to_expand:206                new_input_ids = new_input_ids[0]207                new_attention_mask = new_attention_mask[0]208            return new_input_ids, new_attention_mask209 210        first_valid_index = (attention_mask == 1).argmax(axis=-1)  # [B]211        bos_already_present = np.all(input_ids[np.arange(B), first_valid_index] == bos_token_id)212 213        if bos_already_present:214            if need_to_expand:215                input_ids = input_ids[0]216                attention_mask = attention_mask[0]217            return input_ids, attention_mask218        else:219            new_input_ids = np.full((B, S+1), pad_token_id, dtype=input_ids.dtype)220            new_attention_mask = np.zeros((B, S+1), dtype=attention_mask.dtype)221 222            src_idx = np.tile(np.arange(S), (B, 1))  # [B, S]223            valid_mask = src_idx >= first_valid_index[:, None]  # [B, S]224            tgt_idx = src_idx + 1  # shit right225            batch_idx = np.tile(np.arange(B)[:, None], (1, S))  # [B, S]226 227            # flatten valid_positions228            flat_vals = input_ids[valid_mask]229            flat_batch = batch_idx[valid_mask]230            flat_tgt = tgt_idx[valid_mask]231 232            new_input_ids[flat_batch, flat_tgt] = flat_vals233            new_attention_mask[flat_batch, flat_tgt] = 1234            235            insert_pos = first_valid_index236            new_input_ids[np.arange(B), insert_pos] = bos_token_id237            new_attention_mask[np.arange(B), insert_pos] = 1238 239            if need_to_expand:240                new_input_ids = new_input_ids[0]241                new_attention_mask = new_attention_mask[0]242 243            return new_input_ids, new_attention_mask244 245    def __call__(246        self,247        text: Union[TextInput, PreTokenizedInput, list[TextInput], list[PreTokenizedInput]] = None,248        images: ImageInput = None,249        videos: VideoInput = None,250        **kwargs: Unpack[Molmo2ProcessorKwargs],251    ) -> BatchFeature:252        """253 254        Args:255            text (`str`, `list[str]`, `list[list[str]]`):256                The sequence or batch of sequences to be encoded. Each sequence can be a string or a list of strings257                (pretokenized string). If the sequences are provided as list of strings (pretokenized), you must set258                `is_split_into_words=True` (to lift the ambiguity with a batch of sequences).259            images (`PIL.Image.Image`, `np.ndarray`, `torch.Tensor`, `list[PIL.Image.Image]`, `list[np.ndarray]`, `list[torch.Tensor]`):260                The image or batch of images to be prepared. Each image can be a PIL image, NumPy array or PyTorch261                tensor. Both channels-first and channels-last formats are supported.262            videos (`dict[str, Any]` or `list[dict[str, Any]]`):263                The video or batch of videos to be prepared. Each video can be a dictionary with the following keys:264                - `"frames"`: `np.ndarray` of shape (T, H, W, 3)265                - `"timestamps"`: `np.ndarray` of shape (T,)266                - `"sampled_fps"`: `float` (optional)267                - `"sampling_augmentation"`: `str` (optional)268            return_tensors (`str` or [`~utils.TensorType`], *optional*):269                If set, will return tensors of a particular framework. Acceptable values are:270                - `'tf'`: Return TensorFlow `tf.constant` objects.271                - `'pt'`: Return PyTorch `torch.Tensor` objects.272                - `'np'`: Return NumPy `np.ndarray` objects.273                - `'jax'`: Return JAX `jnp.ndarray` objects.274 275        Returns:276            `BatchFeature`: A [`BatchFeature`] with the following fields:277            - **input_ids** -- List of token ids to be fed to a model. Returned when `text` is not `None`.278            - **attention_mask** -- List of indices specifying which tokens should be attended to by the model (when279              `return_attention_mask=True` or if *"attention_mask"* is in `self.model_input_names` and if `text` is not `None`).280            - **pixel_values** -- Pixel values to be fed to a model. Returned when `images` is not `None`.281            - **image_token_pooling** -- Indices of the patches in `image_grids` to pool for each token in `image_tokens`.282              Returned when `images` is not `None`.283            - **image_grids** -- Grids of images. Returned when `images` is not `None`.284            - **image_num_crops** -- Number of crops for each image. Returned when `images` is not `None`.285            - **pixel_values_videos** -- Pixel values of videos to be fed to a model. Returned when `videos` is not `None`.286            - **video_token_pooling** -- Indices of the patches in `video_grids` to pool for each token in `video_tokens`.287              Returned when `videos` is not `None`.288            - **video_grids** -- Grids of videos. Returned when `videos` is not `None`.289        """290 291        output_kwargs = self._merge_kwargs(292            Molmo2ProcessorKwargs,293            tokenizer_init_kwargs=self.tokenizer.init_kwargs,294            **kwargs,295        )296 297        if images is not None:298            image_inputs = self.image_processor(images, **output_kwargs["images_kwargs"])299            image_grids = image_inputs["image_grids"]300        else:301            image_inputs = {}302            image_grids = None303 304        if videos is not None:305            videos_inputs = self.video_processor(videos=videos, **output_kwargs["videos_kwargs"])306            video_grids = videos_inputs["video_grids"]307            # If user has not requested video metadata, pop it308            if "return_metadata" not in kwargs:309                video_metadata = videos_inputs.pop("video_metadata")310            else:311                video_metadata = videos_inputs["video_metadata"]312        else:313            videos_inputs = {}314            video_grids = None315 316        if not isinstance(text, list):317            text = [text]318        319        text = text.copy() # below lines change text in-place320 321        if image_grids is not None:322            index = 0323            for i in range(len(text)):324                num_images = text[i].count(self.image_placeholder_token)325                image_grids_i = image_grids[index:index+num_images]326                for image_grid in image_grids_i:327                    image_tokens = self.get_image_tokens(image_grid)328                    image_string = "".join(image_tokens)329                    text[i] = text[i].replace(self.image_placeholder_token, image_string, 1)330                index += num_images331        332        if video_grids is not None:333            index = 0334            for i in range(len(text)):335                num_videos = text[i].count(self.video_placeholder_token)336                assert num_videos in {0, 1}, "At most one video is supported for now"337                video_grids_i = video_grids[index:index+num_videos]338                metadata_i = video_metadata[index:index+num_videos]339                for video_grid, metadata in zip(video_grids_i, metadata_i):340                    video_string = self.get_video_string(341                        video_grid,342                        metadata.timestamps,343                    )344                    text[i] = text[i].replace(self.video_placeholder_token, video_string, 1)345                index += num_videos346 347        return_tensors = output_kwargs["text_kwargs"].pop("return_tensors", None)348        return_mm_token_type_ids = output_kwargs["text_kwargs"].pop("return_mm_token_type_ids", False)349        text_inputs = self.tokenizer(text, **output_kwargs["text_kwargs"])350 351        input_ids = text_inputs["input_ids"]352        attention_mask = text_inputs["attention_mask"]353 354        input_ids = np.array(input_ids)355        attention_mask = np.array(attention_mask)356        357        bos = self.tokenizer.bos_token_id or self.tokenizer.eos_token_id358        input_ids, attention_mask = self.insert_bos(359            input_ids, attention_mask, bos, self.tokenizer.pad_token_id360        )361 362        if return_mm_token_type_ids:363            image_tokens = np.array(self.image_token_ids).astype(input_ids.dtype)364            token_type_ids = np.any(input_ids[:, :, None] == image_tokens[None, None, :], axis=-1)365            text_inputs["token_type_ids"] = token_type_ids.tolist()366        367        text_inputs["input_ids"] = input_ids.tolist()368        text_inputs["attention_mask"] = attention_mask.tolist()369 370        return BatchFeature(371            data={**text_inputs, **image_inputs, **videos_inputs},372            tensor_type=return_tensors,373        )374 375    def post_process_image_text_to_text(376        self, generated_outputs, skip_special_tokens=True, clean_up_tokenization_spaces=False, **kwargs377    ):378        """379        Post-process the output of the model to decode the text.380 381        Args:382            generated_outputs (`torch.Tensor` or `np.ndarray`):383                The output of the model `generate` function. The output is expected to be a tensor of shape `(batch_size, sequence_length)`384                or `(sequence_length,)`.385            skip_special_tokens (`bool`, *optional*, defaults to `True`):386                Whether or not to remove special tokens in the output. Argument passed to the tokenizer's `batch_decode` method.387            clean_up_tokenization_spaces (`bool`, *optional*, defaults to `False`):388                Whether or not to clean up the tokenization spaces. Argument passed to the tokenizer's `batch_decode` method.389            **kwargs:390                Additional arguments to be passed to the tokenizer's `batch_decode method`.391 392        Returns:393            `list[str]`: The decoded text.394        """395        return self.tokenizer.batch_decode(396            generated_outputs,397            skip_special_tokens=skip_special_tokens,398            clean_up_tokenization_spaces=clean_up_tokenization_spaces,399            **kwargs,400        )401 402 403Molmo2Processor.register_for_auto_class()