CoolFace
Modelpublic

ecoxial2007/CheX-Phi4MM-SFT

sourceHugging Facemitupdated 9mo agoView on Hugging Face
0likes14downloads
processing_phi4mm.py735 linesDownload Raw Back to root
1# Copyright 2024 Microsoft and the HuggingFace Inc. team. All rights reserved.2#3# Licensed under the Apache License, Version 2.0 (the "License");4# you may not use this file except in compliance with the License.5# You may obtain a copy of the License at6#7#     http://www.apache.org/licenses/LICENSE-2.08#9# Unless required by applicable law or agreed to in writing, software10# distributed under the License is distributed on an "AS IS" BASIS,11# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.12# See the License for the specific language governing permissions and13# limitations under the License.14 15"""16Processor class for Phi4MM17"""18import re19from typing import List, Optional, Tuple, Union20import math21from enum import Enum22 23import numpy as np24import scipy25import torch26import torchvision27 28from transformers import AutoFeatureExtractor, AutoImageProcessor29from transformers.feature_extraction_sequence_utils import SequenceFeatureExtractor30from transformers.image_processing_utils import BaseImageProcessor, BatchFeature31from transformers.image_utils import (32    ImageInput,33    make_list_of_images,34    valid_images,35)36from transformers.processing_utils import ProcessorMixin37from transformers.tokenization_utils_base import PaddingStrategy, TextInput, TruncationStrategy38from transformers.utils import TensorType, logging39from torch.nn.utils.rnn import pad_sequence40 41 42logger = logging.get_logger(__name__)43 44# Special tokens45_COMPATIBLE_IMAGE_SPECIAL_TOKEN_PATTERN = r'<\|image_\d+\|>'  # For backward compatibility46_COMPATIBLE_AUDIO_SPECIAL_TOKEN_PATTERN = r'<\|audio_\d+\|>'  # For backward compatibility47_IMAGE_SPECIAL_TOKEN = '<|endoftext10|>'48_AUDIO_SPECIAL_TOKEN = '<|endoftext11|>'49_IMAGE_SPECIAL_TOKEN_ID = 200010  # '<|endoftext10|>', or we can better name it (in `tokenizer_config.json`)50_AUDIO_SPECIAL_TOKEN_ID = 200011  # '<|endoftext11|>'51 52 53class InputMode(Enum):54    LANGUAGE = 055    VISION = 156    SPEECH = 257    VISION_SPEECH = 358 59 60class Phi4MMImageProcessor(BaseImageProcessor):61    r"""62    Constructs a Phi4MM image processor.63    """64    model_input_names = ["input_image_embeds", "image_sizes", "image_attention_mask"]65 66    def __init__(67        self,68        dynamic_hd,69        **kwargs,70    ) -> None:71        super().__init__(**kwargs)72        self.dynamic_hd = dynamic_hd73 74    def find_closest_aspect_ratio(self, aspect_ratio, target_ratios, width, height, image_size):75        best_ratio_diff = float('inf')76        best_ratio = (1, 1)77        area = width * height78        for ratio in target_ratios:79            target_aspect_ratio = ratio[0] / ratio[1]80            ratio_diff = abs(aspect_ratio - target_aspect_ratio)81            if ratio_diff < best_ratio_diff:82                best_ratio_diff = ratio_diff83                best_ratio = ratio84            elif ratio_diff == best_ratio_diff:85                if area > 0.5 * image_size * image_size * ratio[0] * ratio[1]:86                    best_ratio = ratio87        return best_ratio88 89    def dynamic_preprocess(self, image, min_num=1, max_num=12, image_size=384, mask_size=27, use_thumbnail=True):90        orig_width, orig_height = image.size91 92        w_crop_num = math.ceil(orig_width/float(image_size))93        h_crop_num = math.ceil(orig_height/float(image_size))94        if w_crop_num * h_crop_num > max_num:95 96            aspect_ratio = orig_width / orig_height97 98            # calculate the existing image aspect ratio99            target_ratios = set(100                (i, j) for n in range(min_num, max_num + 1) for i in range(1, n + 1) for j in range(1, n + 1) if101                i * j <= max_num and i * j >= min_num)102            target_ratios = sorted(target_ratios, key=lambda x: x[0] * x[1])103 104            # find the closest aspect ratio to the target105            target_aspect_ratio = self.find_closest_aspect_ratio(106                aspect_ratio, target_ratios, orig_width, orig_height, image_size)107 108            # calculate the target width and height109            target_width = image_size * target_aspect_ratio[0]110            target_height = image_size * target_aspect_ratio[1]111        else:112            target_width = image_size * w_crop_num113            target_height = image_size * h_crop_num114            target_aspect_ratio = (w_crop_num, h_crop_num)115 116        # Calculate the ratio117        ratio_width = target_width / orig_width118        ratio_height = target_height / orig_height119        if ratio_width < ratio_height:120            new_size = (target_width, int(orig_height * ratio_width))121            padding_width = 0122            padding_height = target_height - int(orig_height * ratio_width)123        else:124            new_size = (int(orig_width * ratio_height), target_height)125            padding_width = target_width - int(orig_width * ratio_height)126            padding_height = 0127 128        attention_mask = torch.ones((int(mask_size*target_aspect_ratio[1]), int(mask_size*target_aspect_ratio[0])))129        if padding_width >= 14:130            attention_mask[:, -math.floor(padding_width/14):] = 0131        if padding_height >= 14:132            attention_mask[-math.floor(padding_height/14):,:] = 0133        assert attention_mask.sum() > 0134 135        if min(new_size[1], target_height) < 10 or min(new_size[0], target_width) < 10:136            raise ValueError(f'the aspect ratio is very extreme {new_size}')137 138        image = torchvision.transforms.functional.resize(image, [new_size[1], new_size[0]],)139 140        resized_img = torchvision.transforms.functional.pad(image, [0, 0, padding_width, padding_height], fill=[255,255,255])141 142        return resized_img, attention_mask143 144    def pad_to_max_num_crops(self, images, max_crops=5):145        """146        images: B x 3 x H x W, B<=max_crops147        """148        B, _, H, W = images.shape149        if B < max_crops:150            pad = torch.zeros(max_crops - B, 3, H, W, dtype=images.dtype, device=images.device)151            images = torch.cat([images, pad], dim=0)152        return images153 154    def pad_mask_to_max_num_crops(self, masks, max_crops=5):155        B, H, W = masks.shape156        if B < max_crops:157            pad = torch.ones(max_crops - B, H, W, dtype=masks.dtype, device=masks.device)158            masks = torch.cat([masks, pad], dim=0)159        return masks160 161    def preprocess(162        self,163        images: ImageInput,164        return_tensors: Optional[Union[str, TensorType]] = None,165    ):166        """167        Args:168            images (`ImageInput`):169                Image to preprocess. Expects a single or batch of images with pixel values ranging from 0 to 255. If170                passing in images with pixel values between 0 and 1, set `do_rescale=False`.171            return_tensors (`str` or `TensorType`, *optional*):172                The type of tensors to return. Can be one of:173                - Unset: Return a list of `np.ndarray`.174                - `TensorType.TENSORFLOW` or `'tf'`: Return a batch of type `tf.Tensor`.175                - `TensorType.PYTORCH` or `'pt'`: Return a batch of type `torch.Tensor`.176                - `TensorType.NUMPY` or `'np'`: Return a batch of type `np.ndarray`.177                - `TensorType.JAX` or `'jax'`: Return a batch of type `jax.numpy.ndarray`.178        """179        images = make_list_of_images(images)180 181        if not valid_images(images):182            raise ValueError(183                "Invalid image type. Must be of type PIL.Image.Image, numpy.ndarray, "184                "torch.Tensor, tf.Tensor or jax.ndarray."185            )186 187        # Basic settings.188        img_processor = torchvision.transforms.Compose([189            torchvision.transforms.ToTensor(),190            torchvision.transforms.Normalize(191                (0.5, 0.5, 0.5),192                (0.5, 0.5, 0.5)193            ),194        ])195        dyhd_base_resolution = 448196 197        # Dynamic HD198        base_resolution = dyhd_base_resolution199        images = [image.convert('RGB') for image in images]200        # cover 384 and 448 resolution201        mask_resolution = base_resolution // 14202        elems, image_attention_masks = [], []203        for im in images:204            elem, attention_mask = self.dynamic_preprocess(im, max_num=self.dynamic_hd, image_size=base_resolution, mask_size=mask_resolution)205            elems.append(elem)206            image_attention_masks.append(attention_mask)207        hd_images = [img_processor(im) for im in elems]208        global_image = [torch.nn.functional.interpolate(im.unsqueeze(0).float(), size=(base_resolution, base_resolution), mode='bicubic',).to(im.dtype) for im in hd_images]209        shapes = [[im.size(1), im.size(2)] for im in hd_images]210        mask_shapes = [[mask.size(0), mask.size(1)] for mask in image_attention_masks]211        global_attention_mask = [torch.ones((1, mask_resolution, mask_resolution)) for _ in hd_images]212        hd_images_reshape = [im.reshape(1, 3,213                                            h//base_resolution,214                                            base_resolution,215                                            w//base_resolution,216                                            base_resolution217                                            ).permute(0,2,4,1,3,5).reshape(-1, 3, base_resolution, base_resolution).contiguous() for im, (h, w) in zip(hd_images, shapes)]218        attention_masks_reshape = [mask.reshape(1,219                                            h//mask_resolution,220                                            mask_resolution,221                                            w//mask_resolution,222                                            mask_resolution223                                            ).permute(0,1,3,2,4).reshape(-1, mask_resolution, mask_resolution).contiguous() for mask, (h, w) in zip(image_attention_masks, mask_shapes)]224        downsample_attention_masks = [mask[:,0::2,0::2].reshape(1,225                                            h//mask_resolution,226                                            w//mask_resolution,227                                            mask_resolution//2+mask_resolution%2,228                                            mask_resolution//2+mask_resolution%2229                                            ).permute(0,1,3,2,4) for mask, (h,w) in zip(attention_masks_reshape, mask_shapes)]230        downsample_attention_masks = [mask.reshape(mask.size(1)*mask.size(2), mask.size(3)*mask.size(4))for mask in downsample_attention_masks]231        # 计算 num_img_tokens232        num_img_tokens = [256 + 1 + int(mask.sum().item()) + int(mask[:,0].sum().item()) + 16 for mask in downsample_attention_masks]233 234        hd_images_reshape = [torch.cat([_global_image] + [_im], dim=0) for _global_image, _im in zip(global_image, hd_images_reshape)]235        hd_masks_reshape = [torch.cat([_global_mask] + [_mask], dim=0) for _global_mask, _mask in zip(global_attention_mask, attention_masks_reshape)]236        max_crops = max([img.size(0) for img in hd_images_reshape])237        image_transformed = [self.pad_to_max_num_crops(im, max_crops) for im in hd_images_reshape]238        image_transformed = torch.stack(image_transformed, dim=0)239        mask_transformed = [self.pad_mask_to_max_num_crops(mask, max_crops) for mask in hd_masks_reshape]240        mask_transformed = torch.stack(mask_transformed, dim=0)241 242        returned_input_image_embeds = image_transformed243        returned_image_sizes = torch.tensor(shapes, dtype=torch.long)244        returned_image_attention_mask = mask_transformed245        returned_num_img_tokens = num_img_tokens246 247        data = {248            "input_image_embeds": returned_input_image_embeds,249            "image_sizes": returned_image_sizes,250            "image_attention_mask": returned_image_attention_mask,251            "num_img_tokens": returned_num_img_tokens,252        }253 254        return BatchFeature(data=data, tensor_type=return_tensors)255 256 257AudioInput = Tuple[Union[np.ndarray, torch.Tensor], int]258AudioInputs = List[AudioInput]259 260 261def speechlib_mel(sample_rate, n_fft, n_mels, fmin=None, fmax=None):262    """Create a Mel filter-bank the same as SpeechLib FbankFC.263 264    Args:265        sample_rate (int): Sample rate in Hz. number > 0 [scalar]266        n_fft (int): FFT size. int > 0 [scalar]267        n_mel (int): Mel filter size. int > 0 [scalar]268        fmin (float): lowest frequency (in Hz). If None use 0.0.269            float >= 0 [scalar]270        fmax: highest frequency (in Hz). If None use sample_rate / 2.271            float >= 0 [scalar]272 273    Returns274        out (numpy.ndarray): Mel transform matrix275            [shape=(n_mels, 1 + n_fft/2)]276    """277 278    bank_width = int(n_fft // 2 + 1)279    if fmax is None:280        fmax = sample_rate / 2281    if fmin is None:282        fmin = 0283    assert fmin >= 0, "fmin cannot be negtive"284    assert fmin < fmax <= sample_rate / 2, "fmax must be between (fmin, samplerate / 2]"285 286    def mel(f):287        return 1127.0 * np.log(1.0 + f / 700.0)288 289    def bin2mel(fft_bin):290        return 1127.0 * np.log(1.0 + fft_bin * sample_rate / (n_fft * 700.0))291 292    def f2bin(f):293        return int((f * n_fft / sample_rate) + 0.5)294 295    # Spec 1: FFT bin range [f2bin(fmin) + 1, f2bin(fmax) - 1]296    klo = f2bin(fmin) + 1297    khi = f2bin(fmax)298 299    khi = max(khi, klo)300 301    # Spec 2: SpeechLib uses trianges in Mel space302    mlo = mel(fmin)303    mhi = mel(fmax)304    m_centers = np.linspace(mlo, mhi, n_mels + 2)305    ms = (mhi - mlo) / (n_mels + 1)306 307    matrix = np.zeros((n_mels, bank_width), dtype=np.float32)308    for m in range(0, n_mels):309        left = m_centers[m]310        center = m_centers[m + 1]311        right = m_centers[m + 2]312        for fft_bin in range(klo, khi):313            mbin = bin2mel(fft_bin)314            if left < mbin < right:315                matrix[m, fft_bin] = 1.0 - abs(center - mbin) / ms316 317    return matrix318 319 320class Phi4MMAudioFeatureExtractor(SequenceFeatureExtractor):321    model_input_names = ["input_audio_embeds", "audio_embed_sizes", "audio_attention_mask"]322 323    def __init__(self, audio_compression_rate, audio_downsample_rate, audio_feat_stride, **kwargs):324        feature_size = 80325        sampling_rate = 16000326        padding_value = 0.0327        super().__init__(feature_size, sampling_rate, padding_value, **kwargs)328 329        self.compression_rate = audio_compression_rate330        self.qformer_compression_rate = audio_downsample_rate331        self.feat_stride = audio_feat_stride332 333        self._eightk_method = "fillzero"334        self._mel = speechlib_mel(16000, 512, 80, fmin=None, fmax=7690).T335 336        self._hamming400 = np.hamming(400)  # for 16k audio337        self._hamming200 = np.hamming(200)  # for 8k audio338 339    def duration_to_frames(self, duration):340        """duration in s, estimated frames"""341        frame_rate = 10342 343        num_frames = duration * 1000 // frame_rate344        return num_frames345 346    def __call__(347        self,348        audios: List[AudioInput],349        return_tensors: Optional[Union[str, TensorType]] = None,350    ):351        # Ref: https://github.com/huggingface/transformers/blob/v4.47.0/src/transformers/models/audio_spectrogram_transformer/feature_extraction_audio_spectrogram_transformer.py#L161352        returned_input_audio_embeds = []353        returned_audio_embed_sizes = []354        audio_frames_list = []355 356        for audio_data, sample_rate in audios:357            audio_embeds = self._extract_features(audio_data, sample_rate)358            audio_frames = len(audio_embeds) * self.feat_stride359            audio_embed_size = self._compute_audio_embed_size(audio_frames)360 361            returned_input_audio_embeds.append(torch.tensor(audio_embeds))362            returned_audio_embed_sizes.append(torch.tensor(audio_embed_size).long())363            audio_frames_list.append(audio_frames)364 365        returned_input_audio_embeds = pad_sequence(366            returned_input_audio_embeds, batch_first=True367        )368        returned_audio_embed_sizes = torch.stack(returned_audio_embed_sizes, dim=0)369        audio_frames = torch.tensor(audio_frames_list)370        returned_audio_attention_mask = torch.arange(0, audio_frames.max()).unsqueeze(0) < audio_frames.unsqueeze(1) if len(audios) > 1 else None371 372        data = {373            "input_audio_embeds": returned_input_audio_embeds,374            "audio_embed_sizes": returned_audio_embed_sizes,375        }376        if returned_audio_attention_mask is not None:377            data["audio_attention_mask"] = returned_audio_attention_mask378 379        return BatchFeature(data=data, tensor_type=return_tensors)380 381    def _extract_spectrogram(self, wav, fs):382        """Extract spectrogram features from waveform.383        Args:384            wav (1D array): waveform of the input385            fs (int): sampling rate of the waveform, 16000 or 8000.386                If fs=8000, the waveform will be resampled to 16000Hz.387        Output:388            log_fbank (2D array): a TxD matrix of log Mel filterbank features.389                D=80, and T is the number of frames.390        """391        if wav.ndim > 1:392            wav = np.squeeze(wav)393 394        # by default, we extract the mean if stereo395        if len(wav.shape) == 2:396            wav = wav.mean(1)397 398        # Resample to 16000 or 8000 if needed399        if fs > 16000:400            wav = scipy.signal.resample_poly(wav, 1, fs // 16000)401            fs = 16000402        elif 8000 < fs < 16000:403            wav = scipy.signal.resample_poly(wav, 1, fs // 8000)404            fs = 8000405        elif fs < 8000:406            raise RuntimeError(f"Unsupported sample rate {fs}")407 408        if fs == 8000:409            if self._eightk_method == "resample":410                # Input audio is 8 kHz. Convert to 16 kHz before feature411                # extraction412                wav = scipy.signal.resample_poly(wav, 2, 1)413                fs = 16000414            # Do nothing here for fillzero method415        elif fs != 16000:416            # Input audio is not a supported sample rate.417            raise RuntimeError(f"Input data using an unsupported sample rate: {fs}")418 419        preemphasis = 0.97420 421        if fs == 8000:422            n_fft = 256423            win_length = 200424            hop_length = 80425            fft_window = self._hamming200426        elif fs == 16000:427            n_fft = 512428            win_length = 400429            hop_length = 160430            fft_window = self._hamming400431 432        # Spec 1: SpeechLib cut remaining sample insufficient for a hop433        n_batch = (wav.shape[0] - win_length) // hop_length + 1434        # Here we don't use stride_tricks since the input array may not satisfy435        # memory layout requirement and we need writeable output436        # Here we only use list of views before copy to desination437        # so it is more efficient than broadcasting438        y_frames = np.array(439            [wav[_stride : _stride + win_length] for _stride in range(0, hop_length * n_batch, hop_length)],440            dtype=np.float32,441        )442 443        # Spec 2: SpeechLib applies preemphasis within each batch444        y_frames_prev = np.roll(y_frames, 1, axis=1)445        y_frames_prev[:, 0] = y_frames_prev[:, 1]446        y_frames = (y_frames - preemphasis * y_frames_prev) * 32768447 448        S = np.fft.rfft(fft_window * y_frames, n=n_fft, axis=1).astype(np.complex64)449 450        if fs == 8000:451            # Need to pad the output to look like 16 kHz data but with zeros in452            # the 4 to 8 kHz bins.453            frames, bins = S.shape454            padarray = np.zeros((frames, bins))455            S = np.concatenate((S[:, 0:-1], padarray), axis=1)  # Nyquist bin gets set to zero456 457        spec = np.abs(S).astype(np.float32)458        return spec459 460    def _extract_features(self, wav, fs):461        """Extract log filterbank features from waveform.462        Args:463            wav (1D array): waveform of the input464            fs (int): sampling rate of the waveform, 16000 or 8000.465                If fs=8000, the waveform will be resampled to 16000Hz.466        Output:467            log_fbank (2D array): a TxD matrix of log Mel filterbank features.468                D=80, and T is the number of frames.469        """470        spec = self._extract_spectrogram(wav, fs)471        spec_power = spec**2472 473        fbank_power = np.clip(spec_power.dot(self._mel), 1.0, None)474        log_fbank = np.log(fbank_power).astype(np.float32)475 476        return log_fbank477 478    def _compute_audio_embed_size(self, audio_frames):479        integer = audio_frames // self.compression_rate480        remainder = audio_frames % self.compression_rate481 482        result = integer if remainder == 0 else integer + 1483 484        integer = result // self.qformer_compression_rate485        remainder = result % self.qformer_compression_rate486        result = integer if remainder == 0 else integer + 1  # qformer compression487 488        return result489 490 491class Phi4MMProcessor(ProcessorMixin):492    r"""493    Constructs a Phi4MM processor which raps an image processor, a audio processor, and a GPT tokenizer into a single processor.494 495    [`Phi4MMProcessor`] offers all the functionalities of [`Phi4MMImageProcessor`] and [`GPT2Tokenizer`]. See the496    [`~Phi4MMProcessor.__call__`] and [`~Phi4MMProcessor.decode`] for more information.497 498    Args:499        image_processor ([`Phi4MMImageProcessor`], *optional*):500            The image processor is a required input.501        tokenizer ([`GPT2Tokenizer`], *optional*):502            The tokenizer is a required input.503    """504 505    attributes = ["image_processor", "audio_processor", "tokenizer"]506    tokenizer_class = "GPT2TokenizerFast"507    image_processor_class = "AutoImageProcessor"  # Phi4MMImageProcessor will be registered later508    audio_processor_class = "AutoFeatureExtractor"  # Phi4MMAudioFeatureExtractor will be registered later509 510    def __init__(self, image_processor, audio_processor, tokenizer):511        self.image_processor = image_processor512        self.audio_processor = audio_processor513        self.tokenizer = tokenizer514 515    def __call__(516        self,517        text: Union[TextInput, List[TextInput]],518        images: Optional[ImageInput] = None,519        audios: Optional[AudioInputs] = None,520        padding: Union[bool, str, PaddingStrategy] = False,521        truncation: Optional[Union[bool, str, TruncationStrategy]] = None,522        max_length=None,523        return_tensors: Optional[Union[str, TensorType]] = TensorType.PYTORCH,524    ) -> BatchFeature:525        """526        Main method to prepare for the model one or several sequences(s) and image(s). This method forards the `text`527        and `kwargs` arguments to GPT2Tokenizer's [`~GPT2Tokenizer.__call__`] if `text` is not `None` to encode528        the text. To prepare the image(s), this method forwards the `images` and `kwrags` arguments to529        Phi4MMImageProcessor's [`~Phi4MMImageProcessor.__call__`] if `images` is not `None`. Please refer to the doctsring530        of the above two methods for more information.531 532        Args:533            text (`str`, `List[str]`, `List[List[str]]`):534                The sequence or batch of sequences to be encoded. Each sequence can be a string or a list of strings535                (pretokenized string). If the sequences are provided as list of strings (pretokenized), you must set536                `is_split_into_words=True` (to lift the ambiguity with a batch of sequences).537            images (`PIL.Image.Image`, `np.ndarray`, `torch.Tensor`, `List[PIL.Image.Image]`, `List[np.ndarray]`, `List[torch.Tensor]`):538                The image or batch of images to be prepared. Each image can be a PIL image, NumPy array or PyTorch539                tensor. Both channels-first and channels-last formats are supported.540            padding (`bool`, `str` or [`~utils.PaddingStrategy`], *optional*, defaults to `False`):541                Select a strategy to pad the returned sequences (according to the model's padding side and padding542                index) among:543                - `True` or `'longest'`: Pad to the longest sequence in the batch (or no padding if only a single544                  sequence if provided).545                - `'max_length'`: Pad to a maximum length specified with the argument `max_length` or to the maximum546                  acceptable input length for the model if that argument is not provided.547                - `False` or `'do_not_pad'` (default): No padding (i.e., can output a batch with sequences of different548                  lengths).549            max_length (`int`, *optional*):550                Maximum length of the returned list and optionally padding length (see above).551            truncation (`bool`, *optional*):552                Activates truncation to cut input sequences longer than `max_length` to `max_length`.553            return_tensors (`str` or [`~utils.TensorType`], *optional*):554                If set, will return tensors of a particular framework. Acceptable values are:555 556                - `'tf'`: Return TensorFlow `tf.constant` objects.557                - `'pt'`: Return PyTorch `torch.Tensor` objects.558                - `'np'`: Return NumPy `np.ndarray` objects.559                - `'jax'`: Return JAX `jnp.ndarray` objects.560 561        Returns:562            [`BatchFeature`]: A [`BatchFeature`] with the following fields:563 564            - **input_ids** -- List of token ids to be fed to a model.565            - **input_image_embeds** -- Pixel values to be fed to a model.566            - **image_sizes** -- List of tuples specifying the size of each image in `input_image_embeds`.567            - **image_attention_mask** -- List of attention masks for each image in `input_image_embeds`.568            - **input_audio_embeds** -- Audio embeddings to be fed to a model.569            - **audio_embed_sizes** -- List of integers specifying the size of each audio in `input_audio_embeds`.570            - **attention_mask** -- List of indices specifying which tokens should be attended to by the model.571        """572        image_inputs = self.image_processor(images, return_tensors=return_tensors) if images is not None else {}573        audio_inputs = self.audio_processor(audios, return_tensors=return_tensors) if audios is not None else {}574        inputs = self._convert_images_audios_text_to_inputs(575            image_inputs,576            audio_inputs,577            text,578            padding=padding,579            truncation=truncation,580            max_length=max_length,581            return_tensors=return_tensors,582        )583 584        # idenfity the input mode585        if len(image_inputs) > 0 and len(audio_inputs) > 0:586            input_mode = InputMode.VISION_SPEECH587        elif len(image_inputs) > 0:588            input_mode = InputMode.VISION589        elif len(audio_inputs) > 0:590            input_mode = InputMode.SPEECH591        else:592            input_mode = InputMode.LANGUAGE593        inputs["input_mode"] = torch.tensor([input_mode.value], dtype=torch.long)594 595        return inputs596 597    @property598    def special_image_token_id(self):599        return self.tokenizer.convert_tokens_to_ids(self.special_image_token)600 601    def get_special_image_token_id(self):602        return self.tokenizer.convert_tokens_to_ids(self.special_image_token)603 604    @property605    def chat_template(self):606        return self.tokenizer.chat_template607 608    def _convert_images_audios_text_to_inputs(609        self, images, audios, text, padding=False, truncation=None, max_length=None, return_tensors=None610    ):611        # prepare image id to image input ids612        if len(images) > 0:613            input_image_embeds = images["input_image_embeds"]614            image_sizes = images["image_sizes"]615            image_attention_mask = images["image_attention_mask"]616            num_img_tokens = images['num_img_tokens']617        else:618            input_image_embeds = torch.tensor([])619            image_sizes = torch.tensor([])620            image_attention_mask = torch.tensor([])621            num_img_tokens = []622 623        # prepare audio id to audio input ids624        if len(audios) > 0:625            input_audio_embeds = audios["input_audio_embeds"]626            audio_embed_sizes = audios["audio_embed_sizes"]627            audio_attention_mask = audios.get("audio_attention_mask", None)628        else:629            input_audio_embeds = torch.tensor([])630            audio_embed_sizes = torch.tensor([])631            audio_attention_mask = None632 633        # Replace certain special tokens for compatibility634        # Ref: https://stackoverflow.com/questions/11475885/python-replace-regex635        if isinstance(text, str):636            text = [text]637        assert isinstance(text, list)638        processed_text = [re.sub(_COMPATIBLE_IMAGE_SPECIAL_TOKEN_PATTERN, _IMAGE_SPECIAL_TOKEN, t) for t in text]639        processed_text = [re.sub(_COMPATIBLE_AUDIO_SPECIAL_TOKEN_PATTERN, _AUDIO_SPECIAL_TOKEN, t) for t in processed_text]640 641        input_ids_list = [self.tokenizer(t).input_ids for t in processed_text]642 643        img_cnt, audio_cnt = 0, 0  # only needed for later assertion644        image_token_count_iter = iter(num_img_tokens)645        audio_embed_size_iter = iter(audio_embed_sizes.tolist())646        new_input_ids_list = []647        for input_ids in input_ids_list:648            i = 0649            while i < len(input_ids):650                token_id = input_ids[i]651                if token_id == _AUDIO_SPECIAL_TOKEN_ID:652                    token_count = next(audio_embed_size_iter)653                    audio_cnt += 1654                elif token_id == _IMAGE_SPECIAL_TOKEN_ID:655                    token_count = next(image_token_count_iter)656                    img_cnt += 1657                else:658                    i += 1659                    continue660                tokens = [token_id] * token_count661                input_ids = input_ids[:i] + tokens + input_ids[i + 1:]662                i += token_count663            input_ids = torch.tensor(input_ids, dtype=torch.long)664            new_input_ids_list.append(input_ids)665        lengths = torch.tensor([len(input_ids) for input_ids in new_input_ids_list])666        max_len = lengths.max()667        input_ids = input_ids.new_full((len(new_input_ids_list), max_len), self.tokenizer.pad_token_id)668        # batched inference requires left padding669        for i in range(len(new_input_ids_list)):670            input_ids[i, max_len - len(new_input_ids_list[i]):] = new_input_ids_list[i]671 672        # If the below assertion fails, it might be that input pure-text673        # messages contain image/audio special tokens literally674        # (<|endoftext10|>, <|endoftext11|>).675        assert (676            img_cnt == len(num_img_tokens)677        ), (678            f"Number of image tokens in prompt_token_ids ({img_cnt}) "679            f"does not match number of images ({len(num_img_tokens)})"680        )681        assert (682            audio_cnt == len(audio_embed_sizes)683        ), (684            f"Number of audio tokens in prompt_token_ids ({audio_cnt}) "685            f"does not match number of audios ({len(audio_embed_sizes)})"686        )687 688        # prepare attention mask689        seq_range = torch.arange(max_len - 1, -1, -1)690        attention_mask = seq_range.unsqueeze(0) < lengths.unsqueeze(1)691 692        # prepare batch feature693        data = {694            "input_ids": input_ids,695            "input_image_embeds": input_image_embeds,696            "image_sizes": image_sizes,697            "image_attention_mask": image_attention_mask,698            "input_audio_embeds": input_audio_embeds,699            "audio_embed_sizes": audio_embed_sizes,700            "audio_attention_mask": audio_attention_mask,701            "attention_mask": attention_mask,702        }703 704        return BatchFeature(705            data=data706        )707 708    # Copied from transformers.models.clip.processing_clip.CLIPProcessor.batch_decode with CLIP->Llama709    def batch_decode(self, *args, **kwargs):710        """711        This method forwards all its arguments to GPT2Tokenizer's [`~PreTrainedTokenizer.batch_decode`]. Please712        refer to the docstring of this method for more information.713        """714        return self.tokenizer.batch_decode(*args, **kwargs)715 716    # Copied from transformers.models.clip.processing_clip.CLIPProcessor.decode with CLIP->Llama717    def decode(self, *args, **kwargs):718        """719        This method forwards all its arguments to GPT2Tokenizer's [`~PreTrainedTokenizer.decode`]. Please refer to720        the docstring of this method for more information.721        """722        return self.tokenizer.decode(*args, **kwargs)723 724    @property725    # Copied from transformers.models.clip.processing_clip.CLIPProcessor.model_input_names726    def model_input_names(self):727        tokenizer_input_names = self.tokenizer.model_input_names728        image_processor_input_names = self.image_processor.model_input_names729        audio_processor_input_names = self.audio_processor.model_input_names730        return list(dict.fromkeys(tokenizer_input_names + image_processor_input_names + audio_processor_input_names))731 732 733AutoImageProcessor.register("Phi4MMImageProcessor", Phi4MMImageProcessor)734AutoFeatureExtractor.register("Phi4MMAudioFeatureExtractor", Phi4MMAudioFeatureExtractor)735