CoolFace
Apppublic

Aluode/PerceptionLabPortable

sourceHugging Faceupdated 9mo agoView on Hugging Face
0likes
processing_llava_onevision.py334 linesDownload Raw Back to llava_onevision
1# coding=utf-82# Copyright 2024 The HuggingFace Inc. team.3#4# Licensed under the Apache License, Version 2.0 (the "License");5# you may not use this file except in compliance with the License.6# You may obtain a copy of the License at7#8#     http://www.apache.org/licenses/LICENSE-2.09#10# Unless required by applicable law or agreed to in writing, software11# distributed under the License is distributed on an "AS IS" BASIS,12# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.13# See the License for the specific language governing permissions and14# limitations under the License.15"""16Processor class for LLaVa-Onevision.17"""18 19import math20from collections.abc import Iterable21from typing import Optional, Union22 23import numpy as np24 25from ...feature_extraction_utils import BatchFeature26from ...image_processing_utils import select_best_resolution27from ...image_utils import ImageInput, get_image_size, to_numpy_array28from ...processing_utils import MultiModalData, ProcessingKwargs, ProcessorMixin, Unpack29from ...tokenization_utils_base import PreTokenizedInput, TextInput30from ...utils import logging31from ...video_utils import VideoInput32 33 34logger = logging.get_logger(__name__)35 36 37class LlavaOnevisionProcessorKwargs(ProcessingKwargs, total=False):38    # see processing_utils.ProcessingKwargs documentation for usage.39    _defaults = {40        "text_kwargs": {41            "padding": False,42            "return_mm_token_type_ids": False,43        },44        "image_kwargs": {},45        "videos_kwargs": {},46    }47 48 49class LlavaOnevisionProcessor(ProcessorMixin):50    r"""51    Constructs a LLaVa-Onevision processor which wraps a LLaVa-Onevision video processor, LLaVa-NeXT image processor and a LLaMa tokenizer into a single processor.52 53    [`LlavaNextProcessor`] offers all the functionalities of [`LlavaOnevisionVideoProcessor`], [`LlavaOnevisionImageProcessor`] and [`LlamaTokenizerFast`]. See the54    [`~LlavaOnevisionVideoProcessor.__call__`], [`~LlavaNextProcessor.__call__`] and [`~LlavaNextProcessor.decode`] for more information.55 56    Args:57        image_processor ([`LlavaOnevisionImageProcessor`], *optional*):58            The image processor is a required input.59        tokenizer ([`LlamaTokenizerFast`], *optional*):60            The tokenizer is a required input.61        video_processor ([`LlavaOnevisionVideoProcessor`], *optional*):62            The video processor is a required input.63        num_image_tokens (`int`, *optional*):64            Number of image tokens for one imagethat will be returned by vision tower.65        vision_feature_select_strategy (`str`, *optional*):66            The feature selection strategy used to select the vision feature from the vision backbone.67            Should be same as in model's config68        chat_template (`str`, *optional*): A Jinja template which will be used to convert lists of messages69            in a chat into a tokenizable string.70        image_token (`str`, *optional*, defaults to `"<image>"`):71            Special token used to denote image location.72        video_token (`str`, *optional*, defaults to `"<video>"`):73            Special token used to denote video location.74        vision_aspect_ratio (`str`, *optional*, defaults to `"anyres_max_9"`):75            Aspect ratio used when processong image features. The default value is "anyres_max_9".76    """77 78    attributes = ["image_processor", "tokenizer", "video_processor"]79    image_processor_class = "AutoImageProcessor"80    tokenizer_class = "AutoTokenizer"81    video_processor_class = "AutoVideoProcessor"82 83    def __init__(84        self,85        image_processor=None,86        tokenizer=None,87        video_processor=None,88        num_image_tokens=None,89        vision_feature_select_strategy=None,90        chat_template=None,91        image_token="<image>",92        video_token="<video>",93        vision_aspect_ratio="anyres_max_9",94        **kwargs,95    ):96        self.num_image_tokens = num_image_tokens97        self.vision_feature_select_strategy = vision_feature_select_strategy98        self.image_token = tokenizer.image_token if hasattr(tokenizer, "image_token") else image_token99        self.video_token = tokenizer.video_token if hasattr(tokenizer, "video_token") else video_token100        self.image_token_id = (101            tokenizer.image_token_id102            if getattr(tokenizer, "image_token_id", None)103            else tokenizer.convert_tokens_to_ids(self.image_token)104        )105        self.video_token_id = (106            tokenizer.video_token_id107            if getattr(tokenizer, "video_token_id", None)108            else tokenizer.convert_tokens_to_ids(self.video_token)109        )110        self.vision_aspect_ratio = vision_aspect_ratio111        super().__init__(image_processor, tokenizer, video_processor, chat_template=chat_template)112 113    def __call__(114        self,115        images: Optional[ImageInput] = None,116        text: Union[TextInput, PreTokenizedInput, list[TextInput], list[PreTokenizedInput]] = None,117        audio=None,118        videos: Optional[VideoInput] = None,119        **kwargs: Unpack[LlavaOnevisionProcessorKwargs],120    ) -> BatchFeature:121        """122        Main method to prepare for the model one or several sequences(s) and image(s). This method forwards the `text`123        and `kwargs` arguments to LlamaTokenizerFast's [`~LlamaTokenizerFast.__call__`] if `text` is not `None` to encode124        the text. To prepare the image(s), this method forwards the `images` and `kwargs` arguments to125        LlavaNextImageProcessor's [`~LlavaNextImageProcessor.__call__`] if `images` is not `None`. Please refer to the docstring126        of the above two methods for more information.127 128        Args:129            images (`PIL.Image.Image`, `np.ndarray`, `torch.Tensor`, `list[PIL.Image.Image]`, `list[np.ndarray]`, `list[torch.Tensor]`):130                The image or batch of images to be prepared. Each image can be a PIL image, NumPy array or PyTorch131                tensor. Both channels-first and channels-last formats are supported.132            text (`str`, `list[str]`, `list[list[str]]`):133                The sequence or batch of sequences to be encoded. Each sequence can be a string or a list of strings134                (pretokenized string). If the sequences are provided as list of strings (pretokenized), you must set135                `is_split_into_words=True` (to lift the ambiguity with a batch of sequences).136            videos (`np.ndarray`, `torch.Tensor`, `list[np.ndarray]`, `list[torch.Tensor]`):137                The image or batch of videos to be prepared. Each video can be a 4D NumPy array or PyTorch138 139        Returns:140            [`BatchFeature`]: A [`BatchFeature`] with the following fields:141 142            - **input_ids** -- List of token ids to be fed to a model. Returned when `text` is not `None`.143            - **attention_mask** -- List of indices specifying which tokens should be attended to by the model (when144              `return_attention_mask=True` or if *"attention_mask"* is in `self.model_input_names` and if `text` is not145              `None`).146            - **pixel_values** -- Pixel values to be fed to a model. Returned when `images` is not `None`.147            - **pixel_values_videos** -- Pixel values of a video input to be fed to a model. Returned when `videos` is not `None`.148            - **image_sizes** -- Size of each image that will be used to unpad an image. Returned when `images` is not `None`.149        """150 151        output_kwargs = self._merge_kwargs(152            LlavaOnevisionProcessorKwargs,153            tokenizer_init_kwargs=self.tokenizer.init_kwargs,154            **kwargs,155        )156 157        if isinstance(text, str):158            text = [text]159        elif not isinstance(text, list) and not isinstance(text[0], str):160            raise TypeError("Invalid input text. Please provide a string, or a list of strings")161 162        image_inputs = video_inputs = {}163 164        if images is not None:165            image_inputs = self.image_processor(images, **output_kwargs["images_kwargs"])166 167            batch_num_images = iter(image_inputs["batch_num_images"])168            image_sizes = iter(image_inputs["image_sizes"])169            height, width = get_image_size(170                to_numpy_array(image_inputs["pixel_values"][0][0]),171                channel_dim=output_kwargs["images_kwargs"].get("data_format"),172            )173            text, num_image_tokens = self._expand_image_tokens(174                text, image_sizes, height, width, self.image_token, batch_num_images175            )176 177        if videos is not None:178            video_inputs = self.video_processor(videos, **output_kwargs["videos_kwargs"])179 180            one_video = video_inputs.get("pixel_values_videos")[0]181            if isinstance(video_inputs.get("pixel_values_videos")[0], (list, tuple)):182                one_video = np.array(one_video)183            else:184                one_video = to_numpy_array(one_video)185            height, width = get_image_size(one_video[0], channel_dim=output_kwargs["images_kwargs"].get("data_format"))186            num_frames = one_video.shape[0]  # frame dim is always after batch dim187            patches_height_width = int(math.sqrt(self.num_image_tokens))188            pooled_height_width = math.ceil(patches_height_width / 2)189            num_video_tokens = (num_frames * pooled_height_width * pooled_height_width) + 1  # +1 for newline token190            text = [sample.replace(self.video_token, self.video_token * num_video_tokens) for sample in text]191 192        return_tensors = output_kwargs["text_kwargs"].pop("return_tensors", None)193        return_mm_token_type_ids = output_kwargs["text_kwargs"].pop("return_mm_token_type_ids", None)194        text_inputs = self.tokenizer(text, **output_kwargs["text_kwargs"])195        self._check_special_mm_tokens(text, text_inputs, modalities=["image"])196 197        if return_mm_token_type_ids:198            array_ids = np.array(text_inputs["input_ids"])199            mm_token_type_ids = np.zeros_like(text_inputs["input_ids"])200            mm_token_type_ids[array_ids == self.image_token_id] = 1201            text_inputs["mm_token_type_ids"] = mm_token_type_ids.tolist()202 203        return BatchFeature(data={**text_inputs, **image_inputs, **video_inputs}, tensor_type=return_tensors)204 205    def _expand_image_tokens(206        self,207        text: list[TextInput],208        image_sizes: Iterable[Union[list[int], int]],209        height: int,210        width: int,211        special_token: str,212        batch_num_images: Iterable[int],213    ):214        prompt_strings = []215        max_num_vision_tokens = 0216        for sample in text:217            if special_token in sample:218                num_images = next(batch_num_images)  # should consume iterable219                is_multi_image = num_images != 1220            else:221                is_multi_image = False222            while special_token in sample:223                original_size = next(image_sizes)  # should consume iterable224                if is_multi_image:225                    num_image_tokens = self.num_image_tokens + 1  # one for image_newline226                else:227                    if not isinstance(original_size, (list, tuple)):228                        # cast to list to avoid numerical precision errors when calculating unpadding229                        original_size = original_size.tolist()230                    orig_height, orig_width = original_size231                    num_image_tokens = self._get_number_of_features(orig_height, orig_width, height, width)232                max_num_vision_tokens = max(max_num_vision_tokens, num_image_tokens)233                if self.vision_feature_select_strategy == "default":234                    num_image_tokens -= 1235                sample = sample.replace(special_token, "<placeholder>" * num_image_tokens, 1)236            prompt_strings.append(sample)237        text = [sample.replace("<placeholder>", special_token) for sample in prompt_strings]238        return text, max_num_vision_tokens239 240    def _get_number_of_features(self, orig_height: int, orig_width: int, height: int, width: int) -> int:241        image_grid_pinpoints = self.image_processor.image_grid_pinpoints242 243        height_best_resolution, width_best_resolution = select_best_resolution(244            [orig_height, orig_width], image_grid_pinpoints245        )246        scale_height, scale_width = height_best_resolution // height, width_best_resolution // width247 248        patches_height = patches_width = int(math.sqrt(self.num_image_tokens))249        unpadded_features, newline_features = self._get_unpadded_features(250            orig_height, orig_width, patches_height, patches_width, scale_height, scale_width251        )252 253        # The base patch covers the entire image (no CLS for SigLIP)254        base_features = self.num_image_tokens255        num_image_tokens = unpadded_features + newline_features + base_features256        return num_image_tokens257 258    # Adapted from transformers.models.llava_next.processing_llava_next.LlavaNextProcessor._get_unpadded_features259    def _get_unpadded_features(self, height, width, patches_height, patches_width, scale_height, scale_width):260        """261        Get number of features for a given image with height/width. LLaVA-NeXT is different from LLaVA262        because it divided each image into patches depending on its resolution. Therefore we need to calculate how many263        patches an image is divided into and get the number of features from that.264        """265        current_height = patches_height * scale_height266        current_width = patches_width * scale_width267 268        original_aspect_ratio = width / height269        current_aspect_ratio = current_width / current_height270        if original_aspect_ratio > current_aspect_ratio:271            new_height = int(round(height * (current_width / width), 7))272            padding = (current_height - new_height) // 2273            current_height -= padding * 2274        else:275            new_width = int(round(width * (current_height / height), 7))276            padding = (current_width - new_width) // 2277            current_width -= padding * 2278 279        unpadded_features = current_height * current_width280        newline_features = current_height281 282        max_num_patches = int(self.vision_aspect_ratio.strip("anyres_max_"))283        ratio = math.sqrt(current_height * current_width / (max_num_patches * patches_height**2))284        if ratio > 1.1:285            unpadded_features = int(current_height // ratio) * int(current_width // ratio)286            newline_features = int(current_height // ratio)287 288        return (unpadded_features, newline_features)289 290    def _get_num_multimodal_tokens(self, image_sizes=None, video_sizes=None, **kwargs):291        """292        Computes the number of placeholder tokens needed for multimodal inputs with the given sizes.293        Args:294            image_sizes (list[list[str]], *optional*):295                The input sizes formatted as (height, width) per each image.296            video_sizes (list[list[str]], *optional*):297                The input sizes formatted as (num_frames, height, width) per each video.298            audio_lengths (list[int], *optional*):299                The input length formatted as per each audio.300        Returns:301            dict[str, list[int]]: A dictionary mapping each modality ("image", "video", "audio")302            to a list containing the number of placeholder tokens required. If the model doesn't accept303            a certain modality or no input sizes are provided, the dict value is set to an empty list.304        """305        vision_data = {}306        if image_sizes is not None:307            images_kwargs = LlavaOnevisionProcessorKwargs._defaults.get("images_kwargs", {})308            images_kwargs.update(kwargs)309 310            size = images_kwargs.get("size", None) or self.image_processor.size311            size = (312                (size["shortest_edge"], size["shortest_edge"])313                if "shortest_edge" in size314                else (min(size["height"], size["width"]), min(size["height"], size["width"]))315            )316            processed_height, processed_width = size317 318            batch_num_image_tokens = []319            num_image_patches = [1] * len(image_sizes)  # llava-ov doesn't batch pixels as Idefics, thus `1` patch`320            for image_size in image_sizes:321                orig_height, orig_width = image_size322                num_image_tokens = self._get_number_of_features(323                    orig_height, orig_width, processed_height, processed_width324                )325                if self.vision_feature_select_strategy == "default":326                    num_image_tokens -= 1327                batch_num_image_tokens.append(num_image_tokens)328            vision_data.update({"num_image_tokens": batch_num_image_tokens, "num_image_patches": num_image_patches})329 330        return MultiModalData(**vision_data)331 332 333__all__ = ["LlavaOnevisionProcessor"]334 
Aluode/PerceptionLabPortable · CoolFace