Aluode/PerceptionLabPortable
0
1# Copyright 2022 The HuggingFace Inc. team.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"""15Processing saving/loading class for common processors.16"""17 18import bisect19import copy20import inspect21import json22import os23import sys24import typing25import warnings26from dataclasses import dataclass27from pathlib import Path28from typing import Any, Optional, TypedDict, TypeVar, Union29 30import numpy as np31import typing_extensions32from huggingface_hub.errors import EntryNotFoundError33 34from .audio_utils import AudioInput, load_audio35from .dynamic_module_utils import custom_object_save36from .feature_extraction_utils import BatchFeature37from .image_utils import ChannelDimension, ImageInput, is_vision_available38from .utils.chat_template_utils import render_jinja_template39from .video_utils import VideoInput, VideoMetadata40 41 42if is_vision_available():43 from .image_utils import PILImageResampling44 45 46from .tokenization_utils_base import (47 PaddingStrategy,48 PreTokenizedInput,49 PreTrainedTokenizerBase,50 TextInput,51 TruncationStrategy,52)53from .utils import (54 AUDIO_TOKENIZER_NAME,55 CHAT_TEMPLATE_DIR,56 CHAT_TEMPLATE_FILE,57 LEGACY_PROCESSOR_CHAT_TEMPLATE_FILE,58 PROCESSOR_NAME,59 PushToHubMixin,60 TensorType,61 cached_file,62 copy_func,63 direct_transformers_import,64 download_url,65 is_offline_mode,66 is_remote_url,67 is_torch_available,68 list_repo_templates,69 logging,70)71from .utils.deprecation import deprecate_kwarg72 73 74if is_torch_available():75 from .modeling_utils import PreTrainedAudioTokenizerBase76 77 78logger = logging.get_logger(__name__)79 80# type hinting: specifying the type of processor class that inherits from ProcessorMixin81SpecificProcessorType = TypeVar("SpecificProcessorType", bound="ProcessorMixin")82 83# Dynamically import the Transformers module to grab the attribute classes of the processor from their names.84transformers_module = direct_transformers_import(Path(__file__).parent)85 86 87AUTO_TO_BASE_CLASS_MAPPING = {88 "AutoTokenizer": "PreTrainedTokenizerBase",89 "AutoFeatureExtractor": "FeatureExtractionMixin",90 "AutoImageProcessor": "ImageProcessingMixin",91 "AutoVideoProcessor": "BaseVideoProcessor",92}93 94if sys.version_info >= (3, 11):95 Unpack = typing.Unpack96else:97 Unpack = typing_extensions.Unpack98 99 100class TextKwargs(TypedDict, total=False):101 """102 Keyword arguments for text processing. For extended documentation, check out tokenization_utils_base methods and103 docstrings associated.104 105 Attributes:106 add_special_tokens (`bool`, *optional*)107 Whether or not to add special tokens when encoding the sequences.108 padding (`bool`, `str` or [`~utils.PaddingStrategy`], *optional*)109 Activates and controls padding.110 truncation (`bool`, `str` or [`~tokenization_utils_base.TruncationStrategy`], *optional*):111 Activates and controls truncation.112 max_length (`int`, *optional*):113 Controls the maximum length to use by one of the truncation/padding parameters.114 stride (`int`, *optional*):115 If set, the overflowing tokens will contain some tokens from the end of the truncated sequence.116 is_split_into_words (`bool`, *optional*):117 Whether or not the input is already pre-tokenized.118 pad_to_multiple_of (`int`, *optional*):119 If set, will pad the sequence to a multiple of the provided value.120 return_token_type_ids (`bool`, *optional*):121 Whether to return token type IDs.122 return_attention_mask (`bool`, *optional*):123 Whether to return the attention mask.124 return_overflowing_tokens (`bool`, *optional*):125 Whether or not to return overflowing token sequences.126 return_special_tokens_mask (`bool`, *optional*):127 Whether or not to return special tokens mask information.128 return_offsets_mapping (`bool`, *optional*):129 Whether or not to return `(char_start, char_end)` for each token.130 return_length (`bool`, *optional*):131 Whether or not to return the lengths of the encoded inputs.132 verbose (`bool`, *optional*):133 Whether or not to print more information and warnings.134 padding_side (`str`, *optional*):135 The side on which padding will be applied.136 return_mm_token_type_ids (`bool`, *optional*):137 Whether to return multimodal token type ids indicating mm placeholder token positions.138 """139 140 text_pair: Optional[Union[TextInput, PreTokenizedInput, list[TextInput], list[PreTokenizedInput]]]141 text_target: Union[TextInput, PreTokenizedInput, list[TextInput], list[PreTokenizedInput]]142 text_pair_target: Optional[Union[TextInput, PreTokenizedInput, list[TextInput], list[PreTokenizedInput]]]143 add_special_tokens: Optional[bool]144 padding: Union[bool, str, PaddingStrategy]145 truncation: Union[bool, str, TruncationStrategy]146 max_length: Optional[int]147 stride: Optional[int]148 is_split_into_words: Optional[bool]149 pad_to_multiple_of: Optional[int]150 return_token_type_ids: Optional[bool]151 return_attention_mask: Optional[bool]152 return_overflowing_tokens: Optional[bool]153 return_special_tokens_mask: Optional[bool]154 return_offsets_mapping: Optional[bool]155 return_length: Optional[bool]156 verbose: Optional[bool]157 padding_side: Optional[str]158 return_mm_token_type_ids: Optional[bool]159 160 161class ImagesKwargs(TypedDict, total=False):162 """163 Keyword arguments for image processing. For extended documentation, check the appropriate ImageProcessor164 class methods and docstrings.165 166 Attributes:167 do_resize (`bool`, *optional*):168 Whether to resize the image.169 size (`dict[str, int]`, *optional*):170 Resize the shorter side of the input to `size["shortest_edge"]`.171 crop_size (`dict[str, int]`, *optional*):172 Desired output size when applying center-cropping.173 resample (`PILImageResampling`, *optional*):174 Resampling filter to use if resizing the image.175 do_rescale (`bool`, *optional*):176 Whether to rescale the image by the specified scale `rescale_factor`.177 rescale_factor (`int` or `float`, *optional*):178 Scale factor to use if rescaling the image.179 do_normalize (`bool`, *optional*):180 Whether to normalize the image.181 image_mean (`float` or `list[float]`, *optional*):182 Mean to use if normalizing the image.183 image_std (`float` or `list[float]`, *optional*):184 Standard deviation to use if normalizing the image.185 do_pad (`bool`, *optional*):186 Whether to pad the image to the `(max_height, max_width)` of the images in the batch.187 pad_size (`dict[str, int]`, *optional*):188 The size `{"height": int, "width" int}` to pad the images to.189 do_center_crop (`bool`, *optional*):190 Whether to center crop the image.191 data_format (`ChannelDimension` or `str`, *optional*):192 The channel dimension format for the output image.193 input_data_format (`ChannelDimension` or `str`, *optional*):194 The channel dimension format for the input image.195 device (`str`, *optional*):196 The device to use for processing (e.g. "cpu", "cuda"), only relevant for fast image processing.197 """198 199 do_resize: Optional[bool]200 size: Optional[dict[str, int]]201 crop_size: Optional[dict[str, int]]202 resample: Optional[Union["PILImageResampling", int]]203 do_rescale: Optional[bool]204 rescale_factor: Optional[float]205 do_normalize: Optional[bool]206 image_mean: Optional[Union[float, list[float]]]207 image_std: Optional[Union[float, list[float]]]208 do_pad: Optional[bool]209 pad_size: Optional[dict[str, int]]210 do_center_crop: Optional[bool]211 data_format: Optional[ChannelDimension]212 input_data_format: Optional[Union[str, ChannelDimension]]213 device: Optional[str]214 215 216class VideosKwargs(TypedDict, total=False):217 """218 Keyword arguments for video processing.219 220 Attributes:221 do_convert_rgb (`bool`):222 Whether to convert the video to RGB format.223 do_resize (`bool`):224 Whether to resize the video.225 size (`dict[str, int]`, *optional*):226 Resize the shorter side of the input to `size["shortest_edge"]`.227 default_to_square (`bool`, *optional*, defaults to `self.default_to_square`):228 Whether to default to a square when resizing, if size is an int.229 resample (`PILImageResampling`, *optional*):230 Resampling filter to use if resizing the video.231 do_rescale (`bool`, *optional*):232 Whether to rescale the video by the specified scale `rescale_factor`.233 rescale_factor (`int` or `float`, *optional*):234 Scale factor to use if rescaling the video.235 do_normalize (`bool`, *optional*):236 Whether to normalize the video.237 image_mean (`float` or `list[float]`, *optional*):238 Mean to use if normalizing the video.239 image_std (`float` or `list[float]`, *optional*):240 Standard deviation to use if normalizing the video.241 do_center_crop (`bool`, *optional*):242 Whether to center crop the video.243 do_sample_frames (`bool`, *optional*):244 Whether to sample frames from the video before processing or to process the whole video.245 video_metadata (`Union[VideoMetadata, dict]`, *optional*):246 Metadata of the video containing information about total duration, fps and total number of frames.247 num_frames (`int`, *optional*):248 Maximum number of frames to sample when `do_sample_frames=True`.249 fps (`int` or `float`, *optional*):250 Target frames to sample per second when `do_sample_frames=True`.251 crop_size (`dict[str, int]`, *optional*):252 Desired output size when applying center-cropping.253 data_format (`ChannelDimension` or `str`, *optional*):254 The channel dimension format for the output video.255 input_data_format (`ChannelDimension` or `str`, *optional*):256 The channel dimension format for the input video.257 return_metadata (`ChannelDimension` or `str`, *optional*):258 Whether to return video metadata or not.259 """260 261 do_convert_rgb: Optional[bool]262 do_resize: Optional[bool]263 size: Optional[dict[str, int]]264 default_to_square: Optional[bool]265 resample: Optional["PILImageResampling"]266 do_rescale: Optional[bool]267 rescale_factor: Optional[float]268 do_normalize: Optional[bool]269 image_mean: Optional[Union[float, list[float]]]270 image_std: Optional[Union[float, list[float]]]271 do_center_crop: Optional[bool]272 crop_size: Optional[dict[str, int]]273 data_format: Optional[ChannelDimension]274 input_data_format: Optional[Union[str, ChannelDimension]]275 device: Optional[str]276 do_sample_frames: Optional[bool]277 video_metadata: Optional[Union[VideoMetadata, dict]]278 fps: Optional[Union[int, float]]279 num_frames: Optional[int]280 return_metadata: Optional[bool]281 282 283class AudioKwargs(TypedDict, total=False):284 """285 Keyword arguments for audio processing.286 287 Attributes:288 sampling_rate (`int`, *optional*):289 The sampling rate at which the `raw_speech` input was sampled.290 raw_speech (`np.ndarray`, `list[float]`, `list[np.ndarray]`, `list[list[float]]`):291 The sequence or batch of sequences to be padded. Each sequence can be a numpy array, a list of float292 values, a list of numpy arrays or a list of list of float values. Must be mono channel audio, not293 stereo, i.e. single float per timestep.294 padding (`bool`, `str` or [`~utils.PaddingStrategy`], *optional*):295 Select a strategy to pad the returned sequences (according to the model's padding side and padding296 index) among:297 298 - `True` or `'longest'`: Pad to the longest sequence in the batch (or no padding if only a single299 sequence if provided).300 - `'max_length'`: Pad to a maximum length specified with the argument `max_length` or to the maximum301 acceptable input length for the model if that argument is not provided.302 - `False` or `'do_not_pad'`303 max_length (`int`, *optional*):304 Maximum length of the returned list and optionally padding length (see above).305 truncation (`bool`, *optional*):306 Activates truncation to cut input sequences longer than *max_length* to *max_length*.307 pad_to_multiple_of (`int`, *optional*):308 If set, will pad the sequence to a multiple of the provided value.309 return_attention_mask (`bool`, *optional*):310 Whether or not [`~ASTFeatureExtractor.__call__`] should return `attention_mask`.311 """312 313 sampling_rate: Optional[int]314 raw_speech: Optional[Union[np.ndarray, list[float], list[np.ndarray], list[list[float]]]]315 padding: Optional[Union[bool, str, PaddingStrategy]]316 max_length: Optional[int]317 truncation: Optional[bool]318 pad_to_multiple_of: Optional[int]319 return_attention_mask: Optional[bool]320 321 322class CommonKwargs(TypedDict, total=False):323 return_tensors: Optional[Union[str, TensorType]]324 325 326class ProcessingKwargs(TypedDict, total=False):327 """328 Base class for kwargs passing to processors.329 In case a model has specific kwargs that are not present in the base class or default values for existing keys,330 it should have its own `ModelProcessorKwargs` class that inherits from `ProcessingKwargs` to provide:331 1) Additional typed keys and that this model requires to process inputs.332 2) Default values for existing keys under a `_defaults` attribute.333 New keys have to be defined as follows to ensure type hinting is done correctly.334 335 ```python336 # adding a new image kwarg for this model337 class ModelImagesKwargs(ImagesKwargs, total=False):338 new_image_kwarg: Optional[bool]339 340 class ModelProcessorKwargs(ProcessingKwargs, total=False):341 images_kwargs: ModelImagesKwargs342 _defaults = {343 "images_kwargs: {344 "new_image_kwarg": False,345 }346 "text_kwargs": {347 "padding": "max_length",348 },349 }350 351 ```352 353 For Python 3.8 compatibility, when inheriting from this class and overriding one of the kwargs,354 you need to manually update the __annotations__ dictionary. This can be done as follows:355 356 ```python357 class CustomProcessorKwargs(ProcessingKwargs, total=False):358 images_kwargs: CustomImagesKwargs359 360 CustomProcessorKwargs.__annotations__["images_kwargs"] = CustomImagesKwargs # python 3.8 compatibility361 ```python362 363 """364 365 _defaults = {}366 367 common_kwargs: CommonKwargs = {368 **CommonKwargs.__annotations__,369 }370 text_kwargs: TextKwargs = {371 **TextKwargs.__annotations__,372 }373 images_kwargs: ImagesKwargs = {374 **ImagesKwargs.__annotations__,375 }376 videos_kwargs: VideosKwargs = {377 **VideosKwargs.__annotations__,378 }379 audio_kwargs: AudioKwargs = {380 **AudioKwargs.__annotations__,381 }382 383 384class TokenizerChatTemplateKwargs(TypedDict, total=False):385 """386 Keyword arguments for tokenizer's `apply_chat_template`, when it is called from within a processor.387 388 tools (`list[Dict]`, *optional*):389 A list of tools (callable functions) that will be accessible to the model. If the template does not390 support function calling, this argument will have no effect. Each tool should be passed as a JSON Schema,391 giving the name, description and argument types for the tool. See our392 [chat templating guide](https://huggingface.co/docs/transformers/main/en/chat_templating#automated-function-conversion-for-tool-use)393 for more information.394 documents (`list[dict[str, str]]`, *optional*):395 A list of dicts representing documents that will be accessible to the model if it is performing RAG396 (retrieval-augmented generation). If the template does not support RAG, this argument will have no397 effect. We recommend that each document should be a dict containing "title" and "text" keys. Please398 see the RAG section of the [chat templating guide](https://huggingface.co/docs/transformers/main/en/chat_templating#arguments-for-RAG)399 for examples of passing documents with chat templates.400 add_generation_prompt (bool, *optional*):401 If this is set, a prompt with the token(s) that indicate402 the start of an assistant message will be appended to the formatted output. This is useful when you want to generate a response from the model.403 Note that this argument will be passed to the chat template, and so it must be supported in the404 template for this argument to have any effect.405 continue_final_message (bool, *optional*):406 If this is set, the chat will be formatted so that the final407 message in the chat is open-ended, without any EOS tokens. The model will continue this message408 rather than starting a new one. This allows you to "prefill" part of409 the model's response for it. Cannot be used at the same time as `add_generation_prompt`.410 return_assistant_tokens_mask (`bool`, defaults to `False`):411 Whether to return a mask of the assistant generated tokens. For tokens generated by the assistant,412 the mask will contain 1. For user and system tokens, the mask will contain 0.413 This functionality is only available for chat templates that support it via the `{% generation %}` keyword.414 """415 416 tools: Optional[list[dict]] = None417 documents: Optional[list[dict[str, str]]] = None418 add_generation_prompt: Optional[bool] = False419 continue_final_message: Optional[bool] = False420 return_assistant_tokens_mask: Optional[bool] = False421 422 423class ChatTemplateLoadKwargs(TypedDict, total=False):424 """425 Keyword arguments used to load multimodal data in processor chat templates.426 427 num_frames (`int`, *optional*):428 Number of frames to sample uniformly. If not passed, the whole video is loaded.429 load_audio_from_video (`bool`, *optional*):430 Whether to use the audio track of input video. If `True` the audio track will be loaded and passed to the431 processor. This flag has no effect if the model doesn't support audio modality.432 """433 434 sampling_rate: Optional[int] = 16_000435 load_audio_from_video: Optional[bool] = False436 437 438class ProcessorChatTemplateKwargs(ChatTemplateLoadKwargs, TokenizerChatTemplateKwargs, total=False):439 """440 Keyword arguments for processor's `apply_chat_template`.441 442 tokenize (`bool`, *optional*, defaults to `False`):443 Whether to tokenize the output or not.444 return_dict (`bool`, defaults to `False`):445 Whether to return a dictionary with named outputs. Has no effect if tokenize is `False`.446 """447 448 tokenize: Optional[bool] = False449 return_dict: Optional[bool] = False450 451 452class AllKwargsForChatTemplate(TypedDict, total=False):453 processor_kwargs: ProcessingKwargs454 mm_load_kwargs: ChatTemplateLoadKwargs455 template_kwargs: ProcessorChatTemplateKwargs456 457 458@dataclass459class MultiModalData:460 """461 Dataclass that holds extra useful data for processing462 multimodal data. Processors currently cannot return keys,463 unless it is used in model's forward. Thus we have helper464 methods that calculate and return useful data from processing465 input multimodals (images/videos).466 Note that this dataclass is aimed to be used only in vLLM467 and we might change its API in the future.468 """469 470 num_image_tokens: Optional[list[int]] = None471 num_video_tokens: Optional[list[int]] = None472 num_audio_tokens: Optional[list[int]] = None473 num_image_patches: Optional[list[int]] = None474 475 def __contains__(self, key):476 return hasattr(self, key) and getattr(self, key) is not None477 478 def __getitem__(self, key):479 if hasattr(self, key):480 return getattr(self, key)481 raise AttributeError(f"{self.__class__.__name__} has no attribute {key}")482 483 484class ProcessorMixin(PushToHubMixin):485 """486 This is a mixin used to provide saving/loading functionality for all processor classes.487 """488 489 attributes = ["feature_extractor", "tokenizer"]490 optional_attributes = ["chat_template", "audio_tokenizer"]491 optional_call_args: list[str] = []492 # Names need to be attr_class for attr in attributes493 feature_extractor_class = None494 tokenizer_class = None495 _auto_class = None496 valid_processor_kwargs = ProcessingKwargs497 498 # args have to match the attributes class attribute499 def __init__(self, *args, **kwargs):500 # First, extract optional attributes from kwargs if present501 # Optional attributes can never be positional arguments502 for optional_attribute in self.optional_attributes:503 optional_attribute_value = kwargs.pop(optional_attribute, None)504 setattr(self, optional_attribute, optional_attribute_value)505 506 # Check audio tokenizer for its class but do not treat it as attr to avoid saving weights507 if optional_attribute == "audio_tokenizer" and optional_attribute_value is not None:508 proper_class = self.check_argument_for_proper_class(optional_attribute, optional_attribute_value)509 510 if not (is_torch_available() and isinstance(optional_attribute_value, PreTrainedAudioTokenizerBase)):511 raise ValueError(512 f"Tried to use `{proper_class}` for audio tokenization. However, this class is not"513 " registered for audio tokenization."514 )515 516 # Sanitize args and kwargs517 for key in kwargs:518 if key not in self.attributes:519 raise TypeError(f"Unexpected keyword argument {key}.")520 for arg, attribute_name in zip(args, self.attributes):521 if attribute_name in kwargs:522 raise TypeError(f"Got multiple values for argument {attribute_name}.")523 else:524 kwargs[attribute_name] = arg525 526 if len(kwargs) != len(self.attributes):527 raise ValueError(528 f"This processor requires {len(self.attributes)} arguments: {', '.join(self.attributes)}. Got "529 f"{len(args)} arguments instead."530 )531 532 # Check each arg is of the proper class (this will also catch a user initializing in the wrong order)533 for attribute_name, arg in kwargs.items():534 self.check_argument_for_proper_class(attribute_name, arg)535 setattr(self, attribute_name, arg)536 537 def __call__(538 self,539 images: Optional[ImageInput] = None,540 text: Optional[Union[TextInput, PreTokenizedInput, list[TextInput], list[PreTokenizedInput]]] = None,541 videos: Optional[VideoInput] = None,542 audio: Optional[AudioInput] = None,543 **kwargs: Unpack[ProcessingKwargs],544 ):545 """546 Main method to prepare for model inputs. This method forwards the each modality argument to its own processor547 along with `kwargs`. Please refer to the docstring of the each processor attributes for more information.548 549 Args:550 images (`PIL.Image.Image`, `np.ndarray`, `torch.Tensor`, `list[PIL.Image.Image]`, `list[np.ndarray]`, `list[torch.Tensor]`):551 The image or batch of images to be prepared. Each image can be a PIL image, NumPy array or PyTorch552 tensor. Both channels-first and channels-last formats are supported.553 text (`TextInput`, `PreTokenizedInput`, `list[TextInput]`, `list[PreTokenizedInput]`, *optional*):554 The sequence or batch of sequences to be encoded. Each sequence can be a string or a list of strings555 (pretokenized string). If the sequences are provided as list of strings (pretokenized), you must set556 `is_split_into_words=True` (to lift the ambiguity with a batch of sequences).557 videos (`np.ndarray`, `torch.Tensor`, `List[np.ndarray]`, `List[torch.Tensor]`):558 The video or batch of videos to be prepared. Each video can be a 4D NumPy array or PyTorch559 tensor, or a nested list of 3D frames. Both channels-first and channels-last formats are supported.560 audio (`np.ndarray`, `torch.Tensor`, `list[np.ndarray]`, `list[torch.Tensor]`):561 The audio or batch of audio to be prepared. Each audio can be a NumPy array or PyTorch562 tensor.563 return_tensors (`str` or [`~utils.TensorType`], *optional*):564 If set, will return tensors of a particular framework. Acceptable values are:565 566 - `'tf'`: Return TensorFlow `tf.constant` objects.567 - `'pt'`: Return PyTorch `torch.Tensor` objects.568 - `'np'`: Return NumPy `np.ndarray` objects.569 - `'jax'`: Return JAX `jnp.ndarray` objects.570 571 Returns:572 [`BatchFeature`]: A [`BatchFeature`] object with processed inputs in a dict format.573 """574 if images is None and text is None and videos is None and audio is None:575 raise ValueError(f"You need to provide at least one input to call {self.__class__.__name__}")576 577 kwargs = self._merge_kwargs(578 self.valid_processor_kwargs,579 tokenizer_init_kwargs=self.tokenizer.init_kwargs if hasattr(self, "tokenizer") else {},580 **kwargs,581 )582 583 attribute_to_kwargs = {584 "tokenizer": (text, "text_kwargs"),585 "image_processor": (images, "images_kwargs"),586 "video_processor": (videos, "videos_kwargs"),587 "feature_extractor": (audio, "audio_kwargs"),588 }589 outputs = {}590 for attribute_name in self.attributes:591 attribute = getattr(self, attribute_name, None)592 input_data, input_kwargs = attribute_to_kwargs[attribute_name]593 if input_data is not None and attribute is not None:594 attribute_output = attribute(input_data, **kwargs[input_kwargs])595 outputs.update(attribute_output)596 597 return BatchFeature(outputs)598 599 def check_argument_for_proper_class(self, argument_name, argument):600 """601 Checks the passed argument's class against the expected transformers class. In case of an unexpected602 mismatch between expected and actual class, an error is raise. Otherwise, the proper retrieved class603 is returned.604 """605 class_name = getattr(self, f"{argument_name}_class")606 # Nothing is ever going to be an instance of "AutoXxx", in that case we check the base class.607 class_name = AUTO_TO_BASE_CLASS_MAPPING.get(class_name, class_name)608 if isinstance(class_name, tuple):609 proper_class = tuple(self.get_possibly_dynamic_module(n) for n in class_name if n is not None)610 else:611 proper_class = self.get_possibly_dynamic_module(class_name)612 613 if not isinstance(argument, proper_class):614 raise TypeError(615 f"Received a {type(argument).__name__} for argument {argument_name}, but a {class_name} was expected."616 )617 618 return proper_class619 620 def to_dict(self, legacy_serialization=True) -> dict[str, Any]:621 """622 Serializes this instance to a Python dictionary.623 624 Returns:625 `dict[str, Any]`: Dictionary of all the attributes that make up this processor instance.626 """627 output = copy.deepcopy(self.__dict__)628 629 # Get the kwargs in `__init__`.630 sig = inspect.signature(self.__init__)631 # Only save the attributes that are presented in the kwargs of `__init__`.632 attrs_to_save = list(sig.parameters)633 # extra attributes to be kept634 attrs_to_save += ["auto_map"]635 636 if legacy_serialization:637 # Don't save attributes like `tokenizer`, `image processor` etc. in processor config if `legacy=True`638 attrs_to_save = [x for x in attrs_to_save if x not in self.__class__.attributes]639 640 if "tokenizer" in output:641 del output["tokenizer"]642 if "qformer_tokenizer" in output:643 del output["qformer_tokenizer"]644 if "protein_tokenizer" in output:645 del output["protein_tokenizer"]646 if "chat_template" in output:647 del output["chat_template"]648 649 def cast_array_to_list(dictionary):650 """651 Numpy arrays are not serialiazable but can be in pre-processing dicts.652 This function casts arrays to list, recusring through the nested configs as well.653 """654 for key, value in dictionary.items():655 if isinstance(value, np.ndarray):656 dictionary[key] = value.tolist()657 elif isinstance(value, dict):658 dictionary[key] = cast_array_to_list(value)659 return dictionary660 661 # Serialize attributes as a dict662 output = {663 k: v.to_dict() if isinstance(v, PushToHubMixin) else v664 for k, v in output.items()665 if (666 k in attrs_to_save # keep all attributes that have to be serialized667 and v.__class__.__name__ != "BeamSearchDecoderCTC" # remove attributes with that are objects668 and (669 (legacy_serialization and not isinstance(v, PushToHubMixin)) or not legacy_serialization670 ) # remove `PushToHubMixin` objects671 )672 }673 output = cast_array_to_list(output)674 675 # Special case, add `audio_tokenizer` dict which points to model weights and path676 if not legacy_serialization and "audio_tokenizer" in output:677 audio_tokenizer_dict = {678 "audio_tokenizer_class": self.audio_tokenizer.__class__.__name__,679 "audio_tokenizer_name_or_path": self.audio_tokenizer.name_or_path,680 }681 # Update or overwrite, what do audio tokenizers expect when loading?682 output["audio_tokenizer"] = audio_tokenizer_dict683 684 output["processor_class"] = self.__class__.__name__685 686 return output687 688 def to_json_string(self, legacy_serialization=True) -> str:689 """690 Serializes this instance to a JSON string.691 692 Returns:693 `str`: String containing all the attributes that make up this feature_extractor instance in JSON format.694 """695 dictionary = self.to_dict(legacy_serialization=legacy_serialization)696 697 return json.dumps(dictionary, indent=2, sort_keys=True) + "\n"698 699 def to_json_file(self, json_file_path: Union[str, os.PathLike], legacy_serialization=True):700 """701 Save this instance to a JSON file.702 703 Args:704 json_file_path (`str` or `os.PathLike`):705 Path to the JSON file in which this processor instance's parameters will be saved.706 """707 with open(json_file_path, "w", encoding="utf-8") as writer:708 writer.write(self.to_json_string(legacy_serialization=legacy_serialization))709 710 def __repr__(self):711 attributes_repr = [f"- {name}: {repr(getattr(self, name))}" for name in self.attributes]712 attributes_repr = "\n".join(attributes_repr)713 return f"{self.__class__.__name__}:\n{attributes_repr}\n\n{self.to_json_string()}"714 715 def save_pretrained(self, save_directory, push_to_hub: bool = False, legacy_serialization: bool = True, **kwargs):716 """717 Saves the attributes of this processor (feature extractor, tokenizer...) in the specified directory so that it718 can be reloaded using the [`~ProcessorMixin.from_pretrained`] method.719 720 <Tip>721 722 This class method is simply calling [`~feature_extraction_utils.FeatureExtractionMixin.save_pretrained`] and723 [`~tokenization_utils_base.PreTrainedTokenizerBase.save_pretrained`]. Please refer to the docstrings of the724 methods above for more information.725 726 </Tip>727 728 Args:729 save_directory (`str` or `os.PathLike`):730 Directory where the feature extractor JSON file and the tokenizer files will be saved (directory will731 be created if it does not exist).732 push_to_hub (`bool`, *optional*, defaults to `False`):733 Whether or not to push your model to the Hugging Face model hub after saving it. You can specify the734 repository you want to push to with `repo_id` (will default to the name of `save_directory` in your735 namespace).736 legacy_serialization (`bool`, *optional*, defaults to `True`):737 Whether or not to save processor attributes in separate config files (legacy) or in processor's config738 file as a nested dict. Saving all attributes in a single dict will become the default in future versions.739 Set to `legacy_serialization=True` until then.740 kwargs (`dict[str, Any]`, *optional*):741 Additional key word arguments passed along to the [`~utils.PushToHubMixin.push_to_hub`] method.742 """743 use_auth_token = kwargs.pop("use_auth_token", None)744 745 if use_auth_token is not None:746 warnings.warn(747 "The `use_auth_token` argument is deprecated and will be removed in v5 of Transformers. Please use `token` instead.",748 FutureWarning,749 )750 if kwargs.get("token") is not None:751 raise ValueError(752 "`token` and `use_auth_token` are both specified. Please set only the argument `token`."753 )754 kwargs["token"] = use_auth_token755 756 os.makedirs(save_directory, exist_ok=True)757 758 if push_to_hub:759 commit_message = kwargs.pop("commit_message", None)760 repo_id = kwargs.pop("repo_id", save_directory.split(os.path.sep)[-1])761 repo_id = self._create_repo(repo_id, **kwargs)762 files_timestamps = self._get_files_timestamps(save_directory)763 # If we have a custom config, we copy the file defining it in the folder and set the attributes so it can be764 # loaded from the Hub.765 if self._auto_class is not None:766 attrs = [getattr(self, attribute_name) for attribute_name in self.attributes]767 configs = [(a.init_kwargs if isinstance(a, PreTrainedTokenizerBase) else a) for a in attrs]768 configs.append(self)769 custom_object_save(self, save_directory, config=configs)770 771 save_jinja_files = kwargs.get("save_jinja_files", True)772 773 for attribute_name in self.attributes:774 # Save the tokenizer in its own vocab file. The other attributes are saved as part of `processor_config.json`775 if attribute_name == "tokenizer":776 attribute = getattr(self, attribute_name)777 if hasattr(attribute, "_set_processor_class"):778 attribute._set_processor_class(self.__class__.__name__)779 780 # Propagate save_jinja_files to tokenizer to ensure we don't get conflicts781 attribute.save_pretrained(save_directory, save_jinja_files=save_jinja_files)782 elif legacy_serialization:783 attribute = getattr(self, attribute_name)784 # Include the processor class in attribute config so this processor can then be reloaded with `AutoProcessor` API.785 if hasattr(attribute, "_set_processor_class"):786 attribute._set_processor_class(self.__class__.__name__)787 attribute.save_pretrained(save_directory)788 789 if self._auto_class is not None:790 # We added an attribute to the init_kwargs of the tokenizers, which needs to be cleaned up.791 for attribute_name in self.attributes:792 attribute = getattr(self, attribute_name)793 if isinstance(attribute, PreTrainedTokenizerBase):794 del attribute.init_kwargs["auto_map"]795 796 # If we save using the predefined names, we can load using `from_pretrained`797 # plus we save chat_template in its own file798 output_processor_file = os.path.join(save_directory, PROCESSOR_NAME)799 output_chat_template_file_jinja = os.path.join(save_directory, CHAT_TEMPLATE_FILE)800 output_chat_template_file_legacy = os.path.join(801 save_directory, LEGACY_PROCESSOR_CHAT_TEMPLATE_FILE802 ) # Legacy filename803 chat_template_dir = os.path.join(save_directory, CHAT_TEMPLATE_DIR)804 805 # Save `chat_template` in its own file. We can't get it from `processor_dict` as we popped it in `to_dict`806 # to avoid serializing chat template in json config file. So let's get it from `self` directly807 if self.chat_template is not None:808 save_jinja_files = kwargs.get("save_jinja_files", True)809 is_single_template = isinstance(self.chat_template, str)810 if save_jinja_files and is_single_template:811 # New format for single templates is to save them as chat_template.jinja812 with open(output_chat_template_file_jinja, "w", encoding="utf-8") as f:813 f.write(self.chat_template)814 logger.info(f"chat template saved in {output_chat_template_file_jinja}")815 elif save_jinja_files and not is_single_template:816 # New format for multiple templates is to save the default as chat_template.jinja817 # and the other templates in the chat_templates/ directory818 for template_name, template in self.chat_template.items():819 if template_name == "default":820 with open(output_chat_template_file_jinja, "w", encoding="utf-8") as f:821 f.write(self.chat_template["default"])822 logger.info(f"chat template saved in {output_chat_template_file_jinja}")823 else:824 os.makedirs(chat_template_dir, exist_ok=True)825 template_filepath = os.path.join(chat_template_dir, f"{template_name}.jinja")826 with open(template_filepath, "w", encoding="utf-8") as f:827 f.write(template)828 logger.info(f"chat template saved in {template_filepath}")829 elif is_single_template:830 # Legacy format for single templates: Put them in chat_template.json831 chat_template_json_string = (832 json.dumps({"chat_template": self.chat_template}, indent=2, sort_keys=True) + "\n"833 )834 with open(output_chat_template_file_legacy, "w", encoding="utf-8") as writer:835 writer.write(chat_template_json_string)836 logger.info(f"chat template saved in {output_chat_template_file_legacy}")837 elif self.chat_template is not None:838 # At this point we have multiple templates in the legacy format, which is not supported839 # chat template dicts are saved to chat_template.json as lists of dicts with fixed key names.840 raise ValueError(841 "Multiple chat templates are not supported in the legacy format. Please save them as "842 "separate files using the `save_jinja_files` argument."843 )844 845 if legacy_serialization:846 output_audio_tokenizer_file = os.path.join(save_directory, AUDIO_TOKENIZER_NAME)847 processor_dict = self.to_dict()848 849 # For now, let's not save to `processor_config.json` if the processor doesn't have extra attributes and850 # `auto_map` is not specified.851 if set(processor_dict.keys()) != {"processor_class"}:852 self.to_json_file(output_processor_file)853 logger.info(f"processor saved in {output_processor_file}")854 855 if set(processor_dict.keys()) == {"processor_class"}:856 return_files = []857 else:858 return_files = [output_processor_file]859 860 if self.audio_tokenizer is not None:861 audio_tokenizer_class = self.audio_tokenizer.__class__.__name__862 audio_tokenizer_name_or_path = self.audio_tokenizer.name_or_path863 audio_tokenizer_dict = {864 "audio_tokenizer_class": audio_tokenizer_class,865 "audio_tokenizer_name_or_path": audio_tokenizer_name_or_path,866 }867 audio_tokenizer_json = json.dumps(audio_tokenizer_dict, indent=2, sort_keys=True) + "\n"868 with open(output_audio_tokenizer_file, "w", encoding="utf-8") as writer:869 writer.write(audio_tokenizer_json)870 871 # Create a unified `preprocessor_config.json` and save all attributes as a composite config, except for tokenizers872 # NOTE: this will become the default way to save all processor attrbiutes in future versions. Toggled off for now to give873 # us time for smoother transition874 else:875 self.to_json_file(output_processor_file, legacy_serialization=False)876 logger.info(f"processor saved in {output_processor_file}")877 return_files = [output_processor_file]878 879 if push_to_hub:880 self._upload_modified_files(881 save_directory,882 repo_id,883 files_timestamps,884 commit_message=commit_message,885 token=kwargs.get("token"),886 )887 888 return return_files889 890 @classmethod891 def get_processor_dict(892 cls, pretrained_model_name_or_path: Union[str, os.PathLike], **kwargs893 ) -> tuple[dict[str, Any], dict[str, Any]]:894 """895 From a `pretrained_model_name_or_path`, resolve to a dictionary of parameters, to be used for instantiating a896 processor of type [`~processing_utils.ProcessingMixin`] using `from_args_and_dict`.897 898 Parameters:899 pretrained_model_name_or_path (`str` or `os.PathLike`):900 The identifier of the pre-trained checkpoint from which we want the dictionary of parameters.901 subfolder (`str`, *optional*, defaults to `""`):902 In case the relevant files are located inside a subfolder of the model repo on huggingface.co, you can903 specify the folder name here.904 905 Returns:906 `tuple[Dict, Dict]`: The dictionary(ies) that will be used to instantiate the processor object.907 """908 # holding a copy for optionally loading the audio tokenizer (if available)909 audio_tokenizer_kwargs = copy.deepcopy(kwargs)910 911 cache_dir = kwargs.pop("cache_dir", None)912 force_download = kwargs.pop("force_download", False)913 resume_download = kwargs.pop("resume_download", None)914 proxies = kwargs.pop("proxies", None)915 token = kwargs.pop("token", None)916 local_files_only = kwargs.pop("local_files_only", False)917 revision = kwargs.pop("revision", None)918 subfolder = kwargs.pop("subfolder", "")919 920 from_pipeline = kwargs.pop("_from_pipeline", None)921 from_auto_class = kwargs.pop("_from_auto", False)922 923 user_agent = {"file_type": "processor", "from_auto_class": from_auto_class}924 if from_pipeline is not None:925 user_agent["using_pipeline"] = from_pipeline926 927 if is_offline_mode() and not local_files_only:928 logger.info("Offline mode: forcing local_files_only=True")929 local_files_only = True930 931 pretrained_model_name_or_path = str(pretrained_model_name_or_path)932 is_local = os.path.isdir(pretrained_model_name_or_path)933 if os.path.isdir(pretrained_model_name_or_path):934 processor_file = os.path.join(pretrained_model_name_or_path, PROCESSOR_NAME)935 936 additional_chat_template_files = {}937 resolved_additional_chat_template_files = {}938 if os.path.isfile(pretrained_model_name_or_path):939 resolved_processor_file = pretrained_model_name_or_path940 # can't load chat-template and audio tokenizer when given a file as pretrained_model_name_or_path941 resolved_chat_template_file = None942 resolved_raw_chat_template_file = None943 resolved_audio_tokenizer_file = None944 is_local = True945 elif is_remote_url(pretrained_model_name_or_path):946 processor_file = pretrained_model_name_or_path947 resolved_processor_file = download_url(pretrained_model_name_or_path)948 # can't load chat-template and audio tokenizer when given a file url as pretrained_model_name_or_path949 resolved_chat_template_file = None950 resolved_raw_chat_template_file = None951 resolved_audio_tokenizer_file = None952 else:953 if is_local:954 template_dir = Path(pretrained_model_name_or_path, CHAT_TEMPLATE_DIR)955 if template_dir.is_dir():956 for template_file in template_dir.glob("*.jinja"):957 template_name = template_file.stem958 additional_chat_template_files[template_name] = f"{CHAT_TEMPLATE_DIR}/{template_file.name}"959 else:960 try:961 for template in list_repo_templates(962 pretrained_model_name_or_path,963 local_files_only=local_files_only,964 revision=revision,965 cache_dir=cache_dir,966 token=token,967 ):968 additional_chat_template_files[template] = f"{CHAT_TEMPLATE_DIR}/{template}.jinja"969 except EntryNotFoundError:970 pass # No template dir means no template files971 processor_file = PROCESSOR_NAME972 973 try:974 # Load from local folder or from cache or download from model Hub and cache975 resolved_processor_file = cached_file(976 pretrained_model_name_or_path,977 processor_file,978 cache_dir=cache_dir,979 force_download=force_download,980 proxies=proxies,981 resume_download=resume_download,982 local_files_only=local_files_only,983 token=token,984 user_agent=user_agent,985 revision=revision,986 subfolder=subfolder,987 _raise_exceptions_for_missing_entries=False,988 )989 990 # chat_template.json is a legacy file used by the processor class991 # a raw chat_template.jinja is preferred in future992 resolved_chat_template_file = cached_file(993 pretrained_model_name_or_path,994 LEGACY_PROCESSOR_CHAT_TEMPLATE_FILE,995 cache_dir=cache_dir,996 force_download=force_download,997 proxies=proxies,998 resume_download=resume_download,999 local_files_only=local_files_only,1000 token=token,1001 user_agent=user_agent,1002 revision=revision,1003 subfolder=subfolder,1004 _raise_exceptions_for_missing_entries=False,1005 )1006 1007 resolved_raw_chat_template_file = cached_file(1008 pretrained_model_name_or_path,1009 CHAT_TEMPLATE_FILE,1010 cache_dir=cache_dir,1011 force_download=force_download,1012 proxies=proxies,1013 resume_download=resume_download,1014 local_files_only=local_files_only,1015 token=token,1016 user_agent=user_agent,1017 revision=revision,1018 subfolder=subfolder,1019 _raise_exceptions_for_missing_entries=False,1020 )1021 1022 resolved_additional_chat_template_files = {1023 template_name: cached_file(1024 pretrained_model_name_or_path,1025 template_file,1026 cache_dir=cache_dir,1027 force_download=force_download,1028 proxies=proxies,1029 resume_download=resume_download,1030 local_files_only=local_files_only,1031 token=token,1032 user_agent=user_agent,1033 revision=revision,1034 subfolder=subfolder,1035 _raise_exceptions_for_missing_entries=False,1036 )1037 for template_name, template_file in additional_chat_template_files.items()1038 }1039 1040 resolved_audio_tokenizer_file = cached_file(1041 pretrained_model_name_or_path,1042 AUDIO_TOKENIZER_NAME,1043 cache_dir=cache_dir,1044 force_download=force_download,1045 proxies=proxies,1046 resume_download=resume_download,1047 local_files_only=local_files_only,1048 token=token,1049 user_agent=user_agent,1050 revision=revision,1051 subfolder=subfolder,1052 _raise_exceptions_for_missing_entries=False,1053 )1054 except OSError:1055 # Raise any environment error raise by `cached_file`. It will have a helpful error message adapted to1056 # the original exception.1057 raise1058 except Exception:1059 # For any other exception, we throw a generic error.1060 raise OSError(1061 f"Can't load processor for '{pretrained_model_name_or_path}'. If you were trying to load"1062 " it from 'https://huggingface.co/models', make sure you don't have a local directory with the"1063 f" same name. Otherwise, make sure '{pretrained_model_name_or_path}' is the correct path to a"1064 f" directory containing a {PROCESSOR_NAME} file"1065 )1066 1067 # Add chat template as kwarg before returning because most models don't have processor config1068 if resolved_chat_template_file is not None:1069 # This is the legacy path1070 with open(resolved_chat_template_file, encoding="utf-8") as reader:1071 chat_template_json = json.loads(reader.read())1072 chat_templates = {"default": chat_template_json["chat_template"]}1073 if resolved_additional_chat_template_files:1074 raise ValueError(1075 "Cannot load chat template due to conflicting files - this checkpoint combines "1076 "a legacy chat_template.json file with separate template files, which is not "1077 "supported. To resolve this error, replace the legacy chat_template.json file "1078 "with a modern chat_template.jinja file."1079 )1080 else:1081 chat_templates = {1082 template_name: open(template_file, "r", encoding="utf-8").read()1083 for template_name, template_file in resolved_additional_chat_template_files.items()1084 }1085 if resolved_raw_chat_template_file is not None:1086 with open(resolved_raw_chat_template_file, "r", encoding="utf-8") as reader:1087 chat_templates["default"] = reader.read()1088 if isinstance(chat_templates, dict) and "default" in chat_templates and len(chat_templates) == 1:1089 chat_templates = chat_templates["default"] # Flatten when we just have a single template/file1090 1091 if chat_templates:1092 kwargs["chat_template"] = chat_templates1093 1094 # Existing processors on the Hub created before #27761 being merged don't have `processor_config.json` (if not1095 # updated afterward), and we need to keep `from_pretrained` work. So here it fallbacks to the empty dict.1096 # (`cached_file` called using `_raise_exceptions_for_missing_entries=False` to avoid exception)1097 # However, for models added in the future, we won't get the expected error if this file is missing.1098 if resolved_processor_file is None:1099 # In any case we need to pass `chat_template` if it is available1100 processor_dict = {}1101 else:1102 try:1103 # Load processor dict1104 with open(resolved_processor_file, encoding="utf-8") as reader:1105 text = reader.read()1106 processor_dict = json.loads(text)1107 1108 except json.JSONDecodeError:1109 raise OSError(1110 f"It looks like the config file at '{resolved_processor_file}' is not a valid JSON file."1111 )1112 1113 if is_local:1114 logger.info(f"loading configuration file {resolved_processor_file}")1115 else:1116 logger.info(f"loading configuration file {processor_file} from cache at {resolved_processor_file}")1117 1118 if "chat_template" in processor_dict and processor_dict["chat_template"] is not None:1119 logger.warning_once(1120 "Chat templates should be in a 'chat_template.jinja' file but found key='chat_template' "1121 "in the processor's config. Make sure to move your template to its own file."1122 )1123 1124 if "chat_template" in kwargs:1125 processor_dict["chat_template"] = kwargs.pop("chat_template")1126 1127 # Audio tokenizer needs to load the model checkpoint first, because the saved1128 # json file contains only references to the model path and repo id1129 if resolved_audio_tokenizer_file is not None or "audio_tokenizer" in processor_dict:1130 if resolved_audio_tokenizer_file is not None:1131 reader = open(resolved_audio_tokenizer_file, "r", encoding="utf-8")1132 audio_tokenizer_dict = reader.read()1133 audio_tokenizer_dict = json.loads(audio_tokenizer_dict)1134 else:1135 audio_tokenizer_dict = processor_dict["audio_tokenizer"]1136 1137 audio_tokenizer_class = cls.get_possibly_dynamic_module(audio_tokenizer_dict["audio_tokenizer_class"])1138 audio_tokenizer_path = audio_tokenizer_dict["audio_tokenizer_name_or_path"]1139 processor_dict["audio_tokenizer"] = audio_tokenizer_class.from_pretrained(1140 audio_tokenizer_path, **audio_tokenizer_kwargs1141 )1142 1143 # Pop attributes if saved in a single processor dict, they are loaded in `_get_arguments_from_pretrained`1144 for attribute in cls.attributes:1145 processor_dict.pop(attribute, None)1146 1147 return processor_dict, kwargs1148 1149 @classmethod1150 def from_args_and_dict(cls, args, processor_dict: dict[str, Any], **kwargs):1151 """1152 Instantiates a type of [`~processing_utils.ProcessingMixin`] from a Python dictionary of parameters.1153 1154 Args:1155 processor_dict (`dict[str, Any]`):1156 Dictionary that will be used to instantiate the processor object. Such a dictionary can be1157 retrieved from a pretrained checkpoint by leveraging the1158 [`~processing_utils.ProcessingMixin.to_dict`] method.1159 kwargs (`dict[str, Any]`):1160 Additional parameters from which to initialize the processor object.1161 1162 Returns:1163 [`~processing_utils.ProcessingMixin`]: The processor object instantiated from those1164 parameters.1165 """1166 processor_dict = processor_dict.copy()1167 return_unused_kwargs = kwargs.pop("return_unused_kwargs", False)1168 1169 # We have to pop up some unused (but specific) kwargs and then validate that it doesn't contain unused kwargs1170 # If we don't pop, some specific kwargs will raise a warning1171 if "processor_class" in processor_dict:1172 del processor_dict["processor_class"]1173 1174 if "auto_map" in processor_dict:1175 del processor_dict["auto_map"]1176 1177 # override processor_dict with given kwargs1178 processor_dict.update(kwargs)1179 1180 # check if there is an overlap between args and processor_dict1181 accepted_args_and_kwargs = cls.__init__.__code__.co_varnames[: cls.__init__.__code__.co_argcount][1:]1182 1183 # validate both processor_dict and given kwargs1184 unused_kwargs, valid_kwargs = cls.validate_init_kwargs(1185 processor_config=processor_dict, valid_kwargs=accepted_args_and_kwargs1186 )1187 1188 # update args that are already in processor_dict to avoid duplicate arguments1189 args_to_update = {1190 i: valid_kwargs.pop(arg)1191 for i, arg in enumerate(accepted_args_and_kwargs)1192 if (arg in valid_kwargs and i < len(args))1193 }1194 args = [args_to_update.get(i, arg) for i, arg in enumerate(args)]1195 1196 # instantiate processor with used (and valid) kwargs only1197 processor = cls(*args, **valid_kwargs)1198 1199 logger.info(f"Processor {processor}")1200 if return_unused_kwargs: