transformers
527 строк · 23.0 Кб
1# coding=utf-8
2# Copyright 2023 The HuggingFace Inc. team. All rights reserved.
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 at
7#
8# http://www.apache.org/licenses/LICENSE-2.0
9#
10# Unless required by applicable law or agreed to in writing, software
11# 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 and
14# limitations under the License.
15
16import copy17import json18import os19import random20import unittest21from pathlib import Path22
23from transformers.testing_utils import (24is_pipeline_test,25require_decord,26require_pytesseract,27require_timm,28require_torch,29require_torch_or_tf,30require_vision,31)
32from transformers.utils import direct_transformers_import, logging33
34from .pipelines.test_pipelines_audio_classification import AudioClassificationPipelineTests35from .pipelines.test_pipelines_automatic_speech_recognition import AutomaticSpeechRecognitionPipelineTests36from .pipelines.test_pipelines_conversational import ConversationalPipelineTests37from .pipelines.test_pipelines_depth_estimation import DepthEstimationPipelineTests38from .pipelines.test_pipelines_document_question_answering import DocumentQuestionAnsweringPipelineTests39from .pipelines.test_pipelines_feature_extraction import FeatureExtractionPipelineTests40from .pipelines.test_pipelines_fill_mask import FillMaskPipelineTests41from .pipelines.test_pipelines_image_classification import ImageClassificationPipelineTests42from .pipelines.test_pipelines_image_feature_extraction import ImageFeatureExtractionPipelineTests43from .pipelines.test_pipelines_image_segmentation import ImageSegmentationPipelineTests44from .pipelines.test_pipelines_image_to_image import ImageToImagePipelineTests45from .pipelines.test_pipelines_image_to_text import ImageToTextPipelineTests46from .pipelines.test_pipelines_mask_generation import MaskGenerationPipelineTests47from .pipelines.test_pipelines_object_detection import ObjectDetectionPipelineTests48from .pipelines.test_pipelines_question_answering import QAPipelineTests49from .pipelines.test_pipelines_summarization import SummarizationPipelineTests50from .pipelines.test_pipelines_table_question_answering import TQAPipelineTests51from .pipelines.test_pipelines_text2text_generation import Text2TextGenerationPipelineTests52from .pipelines.test_pipelines_text_classification import TextClassificationPipelineTests53from .pipelines.test_pipelines_text_generation import TextGenerationPipelineTests54from .pipelines.test_pipelines_text_to_audio import TextToAudioPipelineTests55from .pipelines.test_pipelines_token_classification import TokenClassificationPipelineTests56from .pipelines.test_pipelines_translation import TranslationPipelineTests57from .pipelines.test_pipelines_video_classification import VideoClassificationPipelineTests58from .pipelines.test_pipelines_visual_question_answering import VisualQuestionAnsweringPipelineTests59from .pipelines.test_pipelines_zero_shot import ZeroShotClassificationPipelineTests60from .pipelines.test_pipelines_zero_shot_audio_classification import ZeroShotAudioClassificationPipelineTests61from .pipelines.test_pipelines_zero_shot_image_classification import ZeroShotImageClassificationPipelineTests62from .pipelines.test_pipelines_zero_shot_object_detection import ZeroShotObjectDetectionPipelineTests63
64
65pipeline_test_mapping = {66"audio-classification": {"test": AudioClassificationPipelineTests},67"automatic-speech-recognition": {"test": AutomaticSpeechRecognitionPipelineTests},68"conversational": {"test": ConversationalPipelineTests},69"depth-estimation": {"test": DepthEstimationPipelineTests},70"document-question-answering": {"test": DocumentQuestionAnsweringPipelineTests},71"feature-extraction": {"test": FeatureExtractionPipelineTests},72"fill-mask": {"test": FillMaskPipelineTests},73"image-classification": {"test": ImageClassificationPipelineTests},74"image-feature-extraction": {"test": ImageFeatureExtractionPipelineTests},75"image-segmentation": {"test": ImageSegmentationPipelineTests},76"image-to-image": {"test": ImageToImagePipelineTests},77"image-to-text": {"test": ImageToTextPipelineTests},78"mask-generation": {"test": MaskGenerationPipelineTests},79"object-detection": {"test": ObjectDetectionPipelineTests},80"question-answering": {"test": QAPipelineTests},81"summarization": {"test": SummarizationPipelineTests},82"table-question-answering": {"test": TQAPipelineTests},83"text2text-generation": {"test": Text2TextGenerationPipelineTests},84"text-classification": {"test": TextClassificationPipelineTests},85"text-generation": {"test": TextGenerationPipelineTests},86"text-to-audio": {"test": TextToAudioPipelineTests},87"token-classification": {"test": TokenClassificationPipelineTests},88"translation": {"test": TranslationPipelineTests},89"video-classification": {"test": VideoClassificationPipelineTests},90"visual-question-answering": {"test": VisualQuestionAnsweringPipelineTests},91"zero-shot": {"test": ZeroShotClassificationPipelineTests},92"zero-shot-audio-classification": {"test": ZeroShotAudioClassificationPipelineTests},93"zero-shot-image-classification": {"test": ZeroShotImageClassificationPipelineTests},94"zero-shot-object-detection": {"test": ZeroShotObjectDetectionPipelineTests},95}
96
97for task, task_info in pipeline_test_mapping.items():98test = task_info["test"]99task_info["mapping"] = {100"pt": getattr(test, "model_mapping", None),101"tf": getattr(test, "tf_model_mapping", None),102}103
104
105# The default value `hf-internal-testing` is for running the pipeline testing against the tiny models on the Hub.
106# For debugging purpose, we can specify a local path which is the `output_path` argument of a previous run of
107# `utils/create_dummy_models.py`.
108TRANSFORMERS_TINY_MODEL_PATH = os.environ.get("TRANSFORMERS_TINY_MODEL_PATH", "hf-internal-testing")109if TRANSFORMERS_TINY_MODEL_PATH == "hf-internal-testing":110TINY_MODEL_SUMMARY_FILE_PATH = os.path.join(Path(__file__).parent.parent, "tests/utils/tiny_model_summary.json")111else:112TINY_MODEL_SUMMARY_FILE_PATH = os.path.join(TRANSFORMERS_TINY_MODEL_PATH, "reports", "tiny_model_summary.json")113with open(TINY_MODEL_SUMMARY_FILE_PATH) as fp:114tiny_model_summary = json.load(fp)115
116
117PATH_TO_TRANSFORMERS = os.path.join(Path(__file__).parent.parent, "src/transformers")118
119
120# Dynamically import the Transformers module to grab the attribute classes of the processor form their names.
121transformers_module = direct_transformers_import(PATH_TO_TRANSFORMERS)122
123logger = logging.get_logger(__name__)124
125
126class PipelineTesterMixin:127model_tester = None128pipeline_model_mapping = None129supported_frameworks = ["pt", "tf"]130
131def run_task_tests(self, task):132"""Run pipeline tests for a specific `task`133
134Args:
135task (`str`):
136A task name. This should be a key in the mapping `pipeline_test_mapping`.
137"""
138if task not in self.pipeline_model_mapping:139self.skipTest(140f"{self.__class__.__name__}::test_pipeline_{task.replace('-', '_')} is skipped: `{task}` is not in "141f"`self.pipeline_model_mapping` for `{self.__class__.__name__}`."142)143
144model_architectures = self.pipeline_model_mapping[task]145if not isinstance(model_architectures, tuple):146model_architectures = (model_architectures,)147if not isinstance(model_architectures, tuple):148raise ValueError(f"`model_architectures` must be a tuple. Got {type(model_architectures)} instead.")149
150for model_architecture in model_architectures:151model_arch_name = model_architecture.__name__152
153# Get the canonical name154for _prefix in ["Flax", "TF"]:155if model_arch_name.startswith(_prefix):156model_arch_name = model_arch_name[len(_prefix) :]157break158
159tokenizer_names = []160processor_names = []161commit = None162if model_arch_name in tiny_model_summary:163tokenizer_names = tiny_model_summary[model_arch_name]["tokenizer_classes"]164processor_names = tiny_model_summary[model_arch_name]["processor_classes"]165if "sha" in tiny_model_summary[model_arch_name]:166commit = tiny_model_summary[model_arch_name]["sha"]167# Adding `None` (if empty) so we can generate tests168tokenizer_names = [None] if len(tokenizer_names) == 0 else tokenizer_names169processor_names = [None] if len(processor_names) == 0 else processor_names170
171repo_name = f"tiny-random-{model_arch_name}"172if TRANSFORMERS_TINY_MODEL_PATH != "hf-internal-testing":173repo_name = model_arch_name174
175self.run_model_pipeline_tests(176task, repo_name, model_architecture, tokenizer_names, processor_names, commit177)178
179def run_model_pipeline_tests(self, task, repo_name, model_architecture, tokenizer_names, processor_names, commit):180"""Run pipeline tests for a specific `task` with the give model class and tokenizer/processor class names181
182Args:
183task (`str`):
184A task name. This should be a key in the mapping `pipeline_test_mapping`.
185repo_name (`str`):
186A model repository id on the Hub.
187model_architecture (`type`):
188A subclass of `PretrainedModel` or `PretrainedModel`.
189tokenizer_names (`List[str]`):
190A list of names of a subclasses of `PreTrainedTokenizerFast` or `PreTrainedTokenizer`.
191processor_names (`List[str]`):
192A list of names of subclasses of `BaseImageProcessor` or `FeatureExtractionMixin`.
193"""
194# Get an instance of the corresponding class `XXXPipelineTests` in order to use `get_test_pipeline` and195# `run_pipeline_test`.196pipeline_test_class_name = pipeline_test_mapping[task]["test"].__name__197
198for tokenizer_name in tokenizer_names:199for processor_name in processor_names:200if self.is_pipeline_test_to_skip(201pipeline_test_class_name,202model_architecture.config_class,203model_architecture,204tokenizer_name,205processor_name,206):207logger.warning(208f"{self.__class__.__name__}::test_pipeline_{task.replace('-', '_')} is skipped: test is "209f"currently known to fail for: model `{model_architecture.__name__}` | tokenizer "210f"`{tokenizer_name}` | processor `{processor_name}`."211)212continue213self.run_pipeline_test(task, repo_name, model_architecture, tokenizer_name, processor_name, commit)214
215def run_pipeline_test(self, task, repo_name, model_architecture, tokenizer_name, processor_name, commit):216"""Run pipeline tests for a specific `task` with the give model class and tokenizer/processor class name217
218The model will be loaded from a model repository on the Hub.
219
220Args:
221task (`str`):
222A task name. This should be a key in the mapping `pipeline_test_mapping`.
223repo_name (`str`):
224A model repository id on the Hub.
225model_architecture (`type`):
226A subclass of `PretrainedModel` or `PretrainedModel`.
227tokenizer_name (`str`):
228The name of a subclass of `PreTrainedTokenizerFast` or `PreTrainedTokenizer`.
229processor_name (`str`):
230The name of a subclass of `BaseImageProcessor` or `FeatureExtractionMixin`.
231"""
232repo_id = f"{TRANSFORMERS_TINY_MODEL_PATH}/{repo_name}"233if TRANSFORMERS_TINY_MODEL_PATH != "hf-internal-testing":234model_type = model_architecture.config_class.model_type235repo_id = os.path.join(TRANSFORMERS_TINY_MODEL_PATH, model_type, repo_name)236
237tokenizer = None238if tokenizer_name is not None:239tokenizer_class = getattr(transformers_module, tokenizer_name)240tokenizer = tokenizer_class.from_pretrained(repo_id, revision=commit)241
242processor = None243if processor_name is not None:244processor_class = getattr(transformers_module, processor_name)245# If the required packages (like `Pillow` or `torchaudio`) are not installed, this will fail.246try:247processor = processor_class.from_pretrained(repo_id, revision=commit)248except Exception:249logger.warning(250f"{self.__class__.__name__}::test_pipeline_{task.replace('-', '_')} is skipped: Could not load the "251f"processor from `{repo_id}` with `{processor_name}`."252)253return254
255# TODO: Maybe not upload such problematic tiny models to Hub.256if tokenizer is None and processor is None:257logger.warning(258f"{self.__class__.__name__}::test_pipeline_{task.replace('-', '_')} is skipped: Could not find or load "259f"any tokenizer / processor from `{repo_id}`."260)261return262
263# TODO: We should check if a model file is on the Hub repo. instead.264try:265model = model_architecture.from_pretrained(repo_id, revision=commit)266except Exception:267logger.warning(268f"{self.__class__.__name__}::test_pipeline_{task.replace('-', '_')} is skipped: Could not find or load "269f"the model from `{repo_id}` with `{model_architecture}`."270)271return272
273pipeline_test_class_name = pipeline_test_mapping[task]["test"].__name__274if self.is_pipeline_test_to_skip_more(pipeline_test_class_name, model.config, model, tokenizer, processor):275logger.warning(276f"{self.__class__.__name__}::test_pipeline_{task.replace('-', '_')} is skipped: test is "277f"currently known to fail for: model `{model_architecture.__name__}` | tokenizer "278f"`{tokenizer_name}` | processor `{processor_name}`."279)280return281
282# validate283validate_test_components(self, task, model, tokenizer, processor)284
285if hasattr(model, "eval"):286model = model.eval()287
288# Get an instance of the corresponding class `XXXPipelineTests` in order to use `get_test_pipeline` and289# `run_pipeline_test`.290task_test = pipeline_test_mapping[task]["test"]()291
292pipeline, examples = task_test.get_test_pipeline(model, tokenizer, processor)293if pipeline is None:294# The test can disable itself, but it should be very marginal295# Concerns: Wav2Vec2ForCTC without tokenizer test (FastTokenizer don't exist)296logger.warning(297f"{self.__class__.__name__}::test_pipeline_{task.replace('-', '_')} is skipped: Could not get the "298"pipeline for testing."299)300return301
302task_test.run_pipeline_test(pipeline, examples)303
304def run_batch_test(pipeline, examples):305# Need to copy because `Conversation` are stateful306if pipeline.tokenizer is not None and pipeline.tokenizer.pad_token_id is None:307return # No batching for this and it's OK308
309# 10 examples with batch size 4 means there needs to be a unfinished batch310# which is important for the unbatcher311def data(n):312for _ in range(n):313# Need to copy because Conversation object is mutated314yield copy.deepcopy(random.choice(examples))315
316out = []317if task == "conversational":318for item in pipeline(data(10), batch_size=4, max_new_tokens=5):319out.append(item)320else:321for item in pipeline(data(10), batch_size=4):322out.append(item)323self.assertEqual(len(out), 10)324
325run_batch_test(pipeline, examples)326
327@is_pipeline_test328def test_pipeline_audio_classification(self):329self.run_task_tests(task="audio-classification")330
331@is_pipeline_test332def test_pipeline_automatic_speech_recognition(self):333self.run_task_tests(task="automatic-speech-recognition")334
335@is_pipeline_test336def test_pipeline_conversational(self):337self.run_task_tests(task="conversational")338
339@is_pipeline_test340@require_vision341@require_timm342@require_torch343def test_pipeline_depth_estimation(self):344self.run_task_tests(task="depth-estimation")345
346@is_pipeline_test347@require_pytesseract348@require_torch349@require_vision350def test_pipeline_document_question_answering(self):351self.run_task_tests(task="document-question-answering")352
353@is_pipeline_test354def test_pipeline_feature_extraction(self):355self.run_task_tests(task="feature-extraction")356
357@is_pipeline_test358def test_pipeline_fill_mask(self):359self.run_task_tests(task="fill-mask")360
361@is_pipeline_test362@require_torch_or_tf363@require_vision364def test_pipeline_image_classification(self):365self.run_task_tests(task="image-classification")366
367@is_pipeline_test368@require_vision369@require_timm370@require_torch371def test_pipeline_image_segmentation(self):372self.run_task_tests(task="image-segmentation")373
374@is_pipeline_test375@require_vision376def test_pipeline_image_to_text(self):377self.run_task_tests(task="image-to-text")378
379@is_pipeline_test380@require_timm381@require_vision382@require_torch383def test_pipeline_image_feature_extraction(self):384self.run_task_tests(task="image-feature-extraction")385
386@unittest.skip(reason="`run_pipeline_test` is currently not implemented.")387@is_pipeline_test388@require_vision389@require_torch390def test_pipeline_mask_generation(self):391self.run_task_tests(task="mask-generation")392
393@is_pipeline_test394@require_vision395@require_timm396@require_torch397def test_pipeline_object_detection(self):398self.run_task_tests(task="object-detection")399
400@is_pipeline_test401def test_pipeline_question_answering(self):402self.run_task_tests(task="question-answering")403
404@is_pipeline_test405def test_pipeline_summarization(self):406self.run_task_tests(task="summarization")407
408@is_pipeline_test409def test_pipeline_table_question_answering(self):410self.run_task_tests(task="table-question-answering")411
412@is_pipeline_test413def test_pipeline_text2text_generation(self):414self.run_task_tests(task="text2text-generation")415
416@is_pipeline_test417def test_pipeline_text_classification(self):418self.run_task_tests(task="text-classification")419
420@is_pipeline_test421@require_torch_or_tf422def test_pipeline_text_generation(self):423self.run_task_tests(task="text-generation")424
425@is_pipeline_test426@require_torch427def test_pipeline_text_to_audio(self):428self.run_task_tests(task="text-to-audio")429
430@is_pipeline_test431def test_pipeline_token_classification(self):432self.run_task_tests(task="token-classification")433
434@is_pipeline_test435def test_pipeline_translation(self):436self.run_task_tests(task="translation")437
438@is_pipeline_test439@require_torch_or_tf440@require_vision441@require_decord442def test_pipeline_video_classification(self):443self.run_task_tests(task="video-classification")444
445@is_pipeline_test446@require_torch447@require_vision448def test_pipeline_visual_question_answering(self):449self.run_task_tests(task="visual-question-answering")450
451@is_pipeline_test452def test_pipeline_zero_shot(self):453self.run_task_tests(task="zero-shot")454
455@is_pipeline_test456@require_torch457def test_pipeline_zero_shot_audio_classification(self):458self.run_task_tests(task="zero-shot-audio-classification")459
460@is_pipeline_test461@require_vision462def test_pipeline_zero_shot_image_classification(self):463self.run_task_tests(task="zero-shot-image-classification")464
465@is_pipeline_test466@require_vision467@require_torch468def test_pipeline_zero_shot_object_detection(self):469self.run_task_tests(task="zero-shot-object-detection")470
471# This contains the test cases to be skipped without model architecture being involved.472def is_pipeline_test_to_skip(473self, pipeline_test_casse_name, config_class, model_architecture, tokenizer_name, processor_name474):475"""Skip some tests based on the classes or their names without the instantiated objects.476
477This is to avoid calling `from_pretrained` (so reducing the runtime) if we already know the tests will fail.
478"""
479# No fix is required for this case.480if (481pipeline_test_casse_name == "DocumentQuestionAnsweringPipelineTests"482and tokenizer_name is not None483and not tokenizer_name.endswith("Fast")484):485# `DocumentQuestionAnsweringPipelineTests` requires a fast tokenizer.486return True487
488return False489
490def is_pipeline_test_to_skip_more(self, pipeline_test_casse_name, config, model, tokenizer, processor): # noqa491"""Skip some more tests based on the information from the instantiated objects."""492# No fix is required for this case.493if (494pipeline_test_casse_name == "QAPipelineTests"495and tokenizer is not None496and getattr(tokenizer, "pad_token", None) is None497and not tokenizer.__class__.__name__.endswith("Fast")498):499# `QAPipelineTests` doesn't work with a slow tokenizer that has no pad token.500return True501
502return False503
504
505def validate_test_components(test_case, task, model, tokenizer, processor):506# TODO: Move this to tiny model creation script507# head-specific (within a model type) necessary changes to the config508# 1. for `BlenderbotForCausalLM`509if model.__class__.__name__ == "BlenderbotForCausalLM":510model.config.encoder_no_repeat_ngram_size = 0511
512# TODO: Change the tiny model creation script: don't create models with problematic tokenizers513# Avoid `IndexError` in embedding layers514CONFIG_WITHOUT_VOCAB_SIZE = ["CanineConfig"]515if tokenizer is not None:516config_vocab_size = getattr(model.config, "vocab_size", None)517# For CLIP-like models518if config_vocab_size is None:519if hasattr(model.config, "text_config"):520config_vocab_size = getattr(model.config.text_config, "vocab_size", None)521elif hasattr(model.config, "text_encoder"):522config_vocab_size = getattr(model.config.text_encoder, "vocab_size", None)523
524if config_vocab_size is None and model.config.__class__.__name__ not in CONFIG_WITHOUT_VOCAB_SIZE:525raise ValueError(526"Could not determine `vocab_size` from model configuration while `tokenizer` is not `None`."527)528