From 8d06059d10d2015301eb3f3bccc7957a5e40d9cc Mon Sep 17 00:00:00 2001 From: Omega-Intel Date: Mon, 13 Jul 2026 22:37:01 +0200 Subject: [PATCH] Add OpenVINO export support for DINOv3 ConvNeXt (dinov3_convnext) - Register dinov3_convnext model_type in optimum/exporters/openvino/model_configs.py as a one-line pass-through of ViTOpenVINOConfig (same pattern as existing convnext). - Add tiny model test entry to tests/openvino/utils_tests.py. - Extend OVModelForCustomTasksIntegrationTest in tests/openvino/test_modeling.py with a vision feature-extraction comparison test (pixel_values -> last_hidden_state), since OVModelForFeatureExtraction is hard-coded for text inputs. - Document DINOv3 (ConvNeXt variant) in docs/source/openvino/models.mdx. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --- docs/source/openvino/models.mdx | 1 + optimum/exporters/openvino/model_configs.py | 6 +++- tests/openvino/test_modeling.py | 37 ++++++++++++++++++++- tests/openvino/utils_tests.py | 1 + 4 files changed, 43 insertions(+), 2 deletions(-) diff --git a/docs/source/openvino/models.mdx b/docs/source/openvino/models.mdx index db2701749f..98709c5479 100644 --- a/docs/source/openvino/models.mdx +++ b/docs/source/openvino/models.mdx @@ -51,6 +51,7 @@ Here is the list of the supported architectures : - DeepSeek - DeepSeek-V2 - DeepSeek-V3 +- DINOv3 (ConvNeXt variant) - DistilBERT - ERNIE 4.5 - ELECTRA diff --git a/optimum/exporters/openvino/model_configs.py b/optimum/exporters/openvino/model_configs.py index fcacba0534..ad8a16cf03 100644 --- a/optimum/exporters/openvino/model_configs.py +++ b/optimum/exporters/openvino/model_configs.py @@ -218,7 +218,6 @@ NormalizedVisionConfig, ) - COMMON_TEXT_TASKS = [ "feature-extraction", "fill-mask", @@ -5842,6 +5841,11 @@ class ConvNextOpenVINOConfig(ViTOpenVINOConfig): pass +@register_in_tasks_manager("dinov3_convnext", *["feature-extraction"]) +class DINOv3ConvNextOpenVINOConfig(ViTOpenVINOConfig): + pass + + @register_in_tasks_manager("resnet", *["feature-extraction", "image-classification"]) class ResNetOpenVINOConfig(ViTOpenVINOConfig): pass diff --git a/tests/openvino/test_modeling.py b/tests/openvino/test_modeling.py index 196d22a6a7..68a973d106 100644 --- a/tests/openvino/test_modeling.py +++ b/tests/openvino/test_modeling.py @@ -117,7 +117,6 @@ ) from optimum.utils.testing_utils import require_diffusers - os.environ["TOKENIZERS_PARALLELISM"] = "false" @@ -1545,6 +1544,9 @@ def test_compare_to_transformers(self, model_arch): class OVModelForCustomTasksIntegrationTest(unittest.TestCase): SUPPORTED_ARCHITECTURES_WITH_ATTENTION = ["vit-with-attentions"] SUPPORTED_ARCHITECTURES_WITH_HIDDEN_STATES = ["vit-with-hidden-states"] + # Vision feature-extraction backbones (pixel_values -> last_hidden_state, no classification + # head), unlike OVModelForFeatureExtraction which is hard-coded for text inputs. + SUPPORTED_ARCHITECTURES_VISION_FEATURE_EXTRACTION = ["dinov3_convnext"] def _get_sample_image(self): url = TEST_IMAGE_URL @@ -1627,6 +1629,39 @@ def test_compare_output_hidden_states(self, model_arch): del ov_model gc.collect() + @parameterized.expand(SUPPORTED_ARCHITECTURES_VISION_FEATURE_EXTRACTION) + def test_compare_output_vision_feature_extraction(self, model_arch): + model_id = MODEL_NAMES[model_arch] + + image = self._get_sample_image() + preprocessor = AutoImageProcessor.from_pretrained(model_id) + inputs = preprocessor(images=image, return_tensors="pt") + + transformers_model = AutoModel.from_pretrained(model_id) + transformers_model.eval() + with torch.no_grad(): + transformers_outputs = transformers_model(**inputs) + + ov_model = OVModelForCustomTasks.from_pretrained( + model_id, export=True, task="feature-extraction", ov_config=F32_CONFIG, device=OPENVINO_DEVICE + ) + self.assertIsInstance(ov_model.config, PretrainedConfig) + + for input_type in ["pt", "np"]: + inputs = preprocessor(images=image, return_tensors=input_type) + ov_outputs = ov_model(**inputs) + self.assertIn("last_hidden_state", ov_outputs) + self.assertIsInstance(ov_outputs.last_hidden_state, TENSOR_ALIAS_TO_TYPE[input_type]) + self.assertTrue( + torch.allclose( + torch.Tensor(ov_outputs.last_hidden_state), transformers_outputs.last_hidden_state, atol=1e-4 + ) + ) + + del transformers_model + del ov_model + gc.collect() + class OVModelForOpenCLIPZeroShortImageClassificationTest(unittest.TestCase): OV_MODEL_ID = HUB_MODEL_NAMES["open-clip"] diff --git a/tests/openvino/utils_tests.py b/tests/openvino/utils_tests.py index f0e32b265b..0f4ab0e490 100644 --- a/tests/openvino/utils_tests.py +++ b/tests/openvino/utils_tests.py @@ -192,6 +192,7 @@ def _create_tiny_kokoro_model(): "deit": "optimum-intel-internal-testing/tiny-random-DeiTModel", "convnext": "optimum-intel-internal-testing/tiny-random-convnext", "convnextv2": "optimum-intel-internal-testing/tiny-random-ConvNextV2Model", + "dinov3_convnext": "optimum-intel-internal-testing/tiny-random-dinov3_convnext", "distilbert": "optimum-intel-internal-testing/tiny-random-distilbert", "distilbert-ov": "optimum-intel-internal-testing/ov-tiny-random-distilbert", "donut": "optimum-internal-testing/tiny-random-VisionEncoderDecoderModel-donut",