CoolFace
Apppublic

Aluode/PerceptionLabPortable

sourceHugging Faceupdated 9mo agoView on Hugging Face
0likes
processing_idefics3.py405 linesDownload Raw Back to idefics3
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 Idefics3.17"""18 19import re20from itertools import accumulate21from typing import TYPE_CHECKING, Optional, Union22 23import numpy as np24 25from ...feature_extraction_utils import BatchFeature26from ...image_utils import ImageInput, is_valid_image, load_image27from ...processing_utils import ImagesKwargs, MultiModalData, ProcessingKwargs, ProcessorMixin, Unpack28from ...tokenization_utils_base import AddedToken, BatchEncoding, TextInput29from ...utils import logging30 31 32if TYPE_CHECKING:33    from ...tokenization_utils_base import PreTokenizedInput34 35logger = logging.get_logger(__name__)36 37 38def is_url(val) -> bool:39    return isinstance(val, str) and val.startswith("http")40 41 42def is_image_or_image_url(elem):43    return is_url(elem) or is_valid_image(elem)44 45 46def _prompt_split_image(image_seq_len, image_rows, image_cols, fake_token_around_image, image_token, global_img_token):47    """Prompt with expanded image tokens for when the image is split into patches."""48    text_split_images = ""49    for n_h in range(image_rows):50        for n_w in range(image_cols):51            text_split_images += (52                f"{fake_token_around_image}" + f"<row_{n_h + 1}_col_{n_w + 1}>" + f"{image_token}" * image_seq_len53            )54        text_split_images += "\n"55 56    text_split_images += (57        f"\n{fake_token_around_image}"58        + f"{global_img_token}"59        + f"{image_token}" * image_seq_len60        + f"{fake_token_around_image}"61    )62    return text_split_images63 64 65def _prompt_single_image(image_seq_len, fake_token_around_image, image_token, global_img_token):66    """Prompt with expanded image tokens for a single image."""67    return (68        f"{fake_token_around_image}"69        + f"{global_img_token}"70        + f"{image_token}" * image_seq_len71        + f"{fake_token_around_image}"72    )73 74 75def get_image_prompt_string(76    image_rows, image_cols, image_seq_len, fake_token_around_image, image_token, global_img_token77):78    if image_rows == 0 and image_cols == 0:79        return _prompt_single_image(80            image_seq_len,81            fake_token_around_image=fake_token_around_image,82            image_token=image_token,83            global_img_token=global_img_token,84        )85    return _prompt_split_image(86        image_seq_len, image_rows, image_cols, fake_token_around_image, image_token, global_img_token87    )88 89 90class Idefics3ImagesKwargs(ImagesKwargs, total=False):91    return_row_col_info: Optional[bool]92    max_image_size: Optional[dict[str, int]]93 94 95class Idefics3ProcessorKwargs(ProcessingKwargs, total=False):96    images_kwargs: Idefics3ImagesKwargs97 98    _defaults = {99        "text_kwargs": {100            "add_special_tokens": True,101            "padding": False,102            "is_split_into_words": False,103            "return_mm_token_type_ids": False,104        },105        "images_kwargs": {106            "return_row_col_info": True,107        },108    }109 110 111class Idefics3Processor(ProcessorMixin):112    r"""113    Constructs a Idefics3 processor which wraps a LLama tokenizer and Idefics3 image processor into a single processor.114 115    [`Idefics3Processor`] offers all the functionalities of [`Idefics3ImageProcessor`] and [`Idefics3TokenizerFast`]. See116    the docstring of [`~IdeficsProcessor.__call__`] and [`~IdeficsProcessor.decode`] for more information.117 118    Args:119        image_processor (`Idefics3ImageProcessor`):120            An instance of [`Idefics3ImageProcessor`]. The image processor is a required input.121        tokenizer (`PreTrainedTokenizerBase`, *optional*):122            An instance of [`PreTrainedTokenizerBase`]. This should correspond with the model's text model. The tokenizer is a required input.123        image_seq_len (`int`, *optional*, defaults to 169):124            The length of the image sequence i.e. the number of <image> tokens per image in the input.125            This parameter is used to build the string from the input prompt and image tokens and should match the126            value the model used. It is computed as: image_seq_len = int(((image_size // patch_size) ** 2) / (scale_factor**2))127        chat_template (`str`, *optional*): A Jinja template which will be used to convert lists of messages128            in a chat into a tokenizable string.129    """130 131    attributes = ["image_processor", "tokenizer"]132    image_processor_class = "Idefics3ImageProcessor"133    tokenizer_class = "AutoTokenizer"134 135    def __init__(136        self, image_processor, tokenizer=None, image_seq_len: int = 169, chat_template: Optional[str] = None, **kwargs137    ):138        self.fake_image_token = AddedToken("<fake_token_around_image>", normalized=False, special=True).content139        self.image_token = AddedToken("<image>", normalized=False, special=True).content140        self.end_of_utterance_token = AddedToken("<end_of_utterance>", normalized=False, special=True).content141        self.global_image_tag = "<global-img>"  # https://github.com/huggingface/transformers/pull/32473/files/8063e5e17362571b693f1db95167f5443a3be1b2#r1734825341142        self.image_seq_len = image_seq_len143        self.image_token_id = tokenizer.convert_tokens_to_ids(self.image_token)144        self.fake_image_token_id = tokenizer.convert_tokens_to_ids(self.fake_image_token)145        self.global_image_token_id = tokenizer.convert_tokens_to_ids(self.global_image_tag)146        self.row_col_ids = [147            tokenizer.convert_tokens_to_ids(f"<row_{i + 1}_col_{j + 1}>") for i in range(6) for j in range(6)148        ]149 150        # This regex matches one or more occurrences of <global-img> tags (optionally surrounded by newline characters)151        # or <row_x_col_y> tags (where x and y are digits, also optionally surrounded by newline characters).152        self._regex_to_remove_extra_special_tokens = re.compile(r"(\n?<global-img>\n?|<row_\d+_col_\d+>\n?)+")153 154        tokens_to_add = {155            "additional_special_tokens": [156                self.fake_image_token,157                self.image_token,158                self.end_of_utterance_token,159            ]160        }161        tokenizer.add_special_tokens(tokens_to_add)162        self.image_token_id = tokenizer.convert_tokens_to_ids(self.image_token)163 164        super().__init__(image_processor, tokenizer, chat_template=chat_template, **kwargs)165 166    def _extract_images_from_prompts(self, prompts):167        prompt_images = []168        for prompt in prompts:169            images = []170            for elem in prompt:171                if is_valid_image(elem):172                    images.append(elem)173                elif is_url(elem):174                    images.append(load_image(elem))175            prompt_images.append(images)176        return prompt_images177 178    def __call__(179        self,180        images: Union[ImageInput, list[ImageInput], list[list[ImageInput]]] = None,181        text: Union[TextInput, "PreTokenizedInput", list[TextInput], list["PreTokenizedInput"]] = None,182        audio=None,183        videos=None,184        image_seq_len: Optional[int] = None,185        **kwargs: Unpack[Idefics3ProcessorKwargs],186    ) -> BatchEncoding:187        """188        Processes the input prompts and returns a BatchEncoding.189 190        Example:191 192        ```python193        >>> import requests194        >>> from transformers import Idefics3Processor195        >>> from transformers.image_utils import load_image196 197        >>> processor = Idefics3Processor.from_pretrained("HuggingFaceM4/Idefics3-8B-Llama3")198        >>> processor.image_processor.do_image_splitting = False  # Force as False to simplify the example199 200        >>> url1 = "https://cdn.britannica.com/61/93061-050-99147DCE/Statue-of-Liberty-Island-New-York-Bay.jpg"201        >>> url2 = "https://cdn.britannica.com/59/94459-050-DBA42467/Skyline-Chicago.jpg"202 203        >>> image1, image2 = load_image(url1), load_image(url2)204        >>> images = [[image1], [image2]]205 206        >>> text = [207        ...     "<image>In this image, we see",208        ...     "bla bla bla<image>",209        ... ]210        >>> outputs = processor(images=images, text=text, return_tensors="pt", padding=True)211        >>> input_ids = outputs.input_ids212        >>> input_tokens = processor.tokenizer.batch_decode(input_ids)213        >>> print(input_tokens)214        ['<|begin_of_text|><fake_token_around_image><global-img>((<image>)*169)<fake_token_around_image> In this image, we see', '<|reserved_special_token_0|><|reserved_special_token_0|><|reserved_special_token_0|><|begin_of_text|>bla bla bla<fake_token_around_image><global-img>((<image>)*169)<fake_token_around_image>']215        ```216 217        Args:218            images (`PIL.Image.Image`, `np.ndarray`, `torch.Tensor`, `list[PIL.Image.Image]`, `list[np.ndarray]`, `list[torch.Tensor]`, *optional*):219                The image or batch of images to be prepared. Each image can be a PIL image, NumPy array or PyTorch220                tensor. If is of type `list[ImageInput]`, it's assumed that this is for a single prompt i.e. of batch size 1.221            text (`Union[TextInput, PreTokenizedInput, list[TextInput], list[PreTokenizedInput]]`, *optional*):222                The sequence or batch of sequences to be encoded. Each sequence can be a string or a list of strings223                (pretokenized string). If the sequences are provided as list of strings (pretokenized), you must set224                `is_split_into_words=True` (to lift the ambiguity with a batch of sequences).225                Wherever an image token, `<image>` is encountered it is expanded to226                `<fake_token_around_image>` + `<row_x_col_y>` + `<image>` * `image_seq_len` * <fake_token_around_image>`.227            image_seq_len (`int`, *optional*):228                The length of the image sequence. If not provided, the default value of self.image_seq_len is used.229                image_seq_len should be equal to int(((image_size // patch_size) ** 2) / (scale_factor**2))230            return_tensors (`Union[str, TensorType]`, *optional*):231                If set, will return tensors of a particular framework. See [`PreTrainedTokenizerFast.__call__`] for more232                information.233        """234        if text is None and images is None:235            raise ValueError("You must provide either `text` or `images`.")236 237        output_kwargs = self._merge_kwargs(238            Idefics3ProcessorKwargs,239            tokenizer_init_kwargs=self.tokenizer.init_kwargs,240            **kwargs,241        )242 243        image_seq_len = image_seq_len if image_seq_len is not None else self.image_seq_len244        return_mm_token_type_ids = output_kwargs["text_kwargs"].pop("return_mm_token_type_ids", False)245        return_tensors = output_kwargs["text_kwargs"].pop("return_tensors", None)246 247        n_images_in_text = []248        n_images_in_images = []249        inputs = {}250 251        if text is not None:252            if isinstance(text, str):253                text = [text]254            elif not isinstance(text, list) and not isinstance(text[0], str):255                raise ValueError("Invalid input text. Please provide a string, or a list of strings")256            n_images_in_text = [sample.count(self.image_token) for sample in text]257 258        if images is not None:259            if is_image_or_image_url(images):260                images = [[images]]261            elif isinstance(images, (list, tuple)) and is_image_or_image_url(images[0]):262                if text is not None:263                    if sum(n_images_in_text) != len(images):264                        raise ValueError(265                            f"The total number of {self.image_token} tokens in the prompts should be the same as the number of images passed."266                            f" Found {sum(n_images_in_text)} {self.image_token} tokens and {len(images)} images."267                        )268                    # Reorganize the images to match the prompts269                    cumsum_images_in_text = [0] + list(accumulate(n_images_in_text))270                    images = [271                        images[cumsum_images_in_text[i] : cumsum_images_in_text[i + 1]]272                        for i in range(len(n_images_in_text))273                    ]274                else:275                    images = [images]276            elif (277                not isinstance(images, (list, tuple))278                and not isinstance(images[0], (list, tuple))279                and not is_image_or_image_url(images[0][0])280            ):281                raise ValueError(282                    "Invalid input images. Please provide a single image or a list of images or a list of list of images."283                )284            n_images_in_images = [len(sample) for sample in images]285 286            # Load images if they are URLs287            images = [[load_image(im) if is_url(im) else im for im in sample] for sample in images]288 289            image_inputs = self.image_processor(images, **output_kwargs["images_kwargs"])290            inputs.update(image_inputs)291 292            if text is not None:293                if n_images_in_images != n_images_in_text:294                    raise ValueError(295                        f"The number of images in the text {n_images_in_text} and images {n_images_in_images} should be the same."296                    )297 298                image_rows = inputs.pop("rows", [[0] * len(text)])299                image_cols = inputs.pop("cols", [[0] * len(text)])300 301                fake_image_token = self.fake_image_token302                image_token = self.image_token303                global_img_token = self.global_image_tag304 305                prompt_strings = []306                batch_image_seq_lengths = []307                for sample, sample_rows, sample_cols in zip(text, image_rows, image_cols):308                    # Replace the image token with fake tokens around the expanded image token sequence of length `image_seq_len`309                    image_prompt_strings = []310                    image_seq_lengths = []311                    for n_rows, n_cols in zip(sample_rows, sample_cols):312                        image_prompt_string = get_image_prompt_string(313                            n_rows,314                            n_cols,315                            image_seq_len,316                            image_token=image_token,317                            fake_token_around_image=fake_image_token,318                            global_img_token=global_img_token,319                        )320                        # Add +2 and +3 for special BOI/EOI/fake_image_wrapper tokens321                        row_length = (self.image_seq_len + 2) * n_cols + 1322                        image_seq_lengths.append((self.image_seq_len + 3) + row_length * n_rows)323                        image_prompt_strings.append(image_prompt_string)324 325                    batch_image_seq_lengths.append(image_seq_lengths)326                    split_sample = sample.split(image_token)327                    if len(split_sample) == 0:328                        raise ValueError("The image token should be present in the text.")329 330                    # Place in the image prompt strings where the image tokens are331                    sample = split_sample[0]332                    for i, image_prompt_string in enumerate(image_prompt_strings):333                        sample += image_prompt_string + split_sample[i + 1]334                    prompt_strings.append(sample)335 336                text_inputs = self.tokenizer(prompt_strings, **output_kwargs["text_kwargs"])337                self._check_special_mm_tokens(prompt_strings, text_inputs, modalities=["image"])338                inputs.update(text_inputs)339 340        elif text is not None:341            if any(n_images_in_text):342                raise ValueError(343                    f"Found {sum(n_images_in_text)} {self.image_token} tokens in the text but no images were passed."344                )345            text_inputs = self.tokenizer(text=text, **output_kwargs["text_kwargs"])346            inputs.update(text_inputs)347 348        if return_mm_token_type_ids:349            array_ids = np.array(inputs["input_ids"])350            mm_token_type_ids = np.zeros_like(array_ids)351            for i, seq_lengths in enumerate(batch_image_seq_lengths):352                image_start_positions = np.where(array_ids[i] == self.fake_image_token_id)[0]353                j = 0354                for seq_len in seq_lengths:355                    if j >= len(image_start_positions):356                        break357                    start = image_start_positions[j]358                    end = start + seq_len359                    mm_token_type_ids[i, start:end] = 1360                    j = np.searchsorted(image_start_positions, end)361 362            inputs["mm_token_type_ids"] = mm_token_type_ids.tolist()363 364        return BatchFeature(data=inputs, tensor_type=return_tensors)365 366    def _get_num_multimodal_tokens(self, image_sizes=None, **kwargs):367        """368        Computes the number of placeholder tokens needed for multimodal inputs with the given sizes.369 370        Args:371            image_sizes (`list[list[int]]`, *optional*):372                The input sizes formatted as (height, width) per each image.373 374        Returns:375            `MultiModalData`: A `MultiModalData` object holding number of tokens per each of the provided376            input modalities, along with other useful data.377        """378 379        vision_data = {}380        if image_sizes is not None:381            images_kwargs = Idefics3ProcessorKwargs._defaults.get("images_kwargs", {})382            images_kwargs.update(kwargs)383 384            num_image_row_cols = [385                self.image_processor.get_number_of_image_patches(*image_size, images_kwargs)386                for image_size in image_sizes387            ]388 389            base_image_length = self.image_seq_len + 3390            col_length = self.image_seq_len + 2391            num_image_tokens = []392            num_image_patches = []393 394            for num_patches, num_rows, num_cols in num_image_row_cols:395                row_length = col_length * num_cols + 1396                num_image_tokens.append(base_image_length + (row_length * num_rows))397                num_image_patches.append(num_patches)398 399            vision_data.update({"num_image_tokens": num_image_tokens, "num_image_patches": num_image_patches})400 401        return MultiModalData(**vision_data)402 403 404__all__ = ["Idefics3Processor"]405