From f052eb35cceedbcef1726ba225e7a2a22e473686 Mon Sep 17 00:00:00 2001 From: Tai An Date: Mon, 20 Apr 2026 21:13:41 -0700 Subject: [PATCH 1/4] fix(exporters/tasks): support local timm model paths Previously, `get_model_from_task` always prepended `hf_hub:` when loading timm models, forcing them to be fetched from HF Hub even when a local directory was provided. This caused a `Repository Not Found` error for locally fine-tuned timm models. Fix: check if `model_name_or_path` is an existing local directory. If so, pass it directly to `create_model`; otherwise use the `hf_hub:` prefix for Hub models. Fixes #2423 --- optimum/exporters/tasks.py | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/optimum/exporters/tasks.py b/optimum/exporters/tasks.py index b841d52a6b..3cffdc22cf 100644 --- a/optimum/exporters/tasks.py +++ b/optimum/exporters/tasks.py @@ -1174,7 +1174,12 @@ def get_model_from_task( ) if library_name == "timm": - model = model_class(f"hf_hub:{model_name_or_path}", pretrained=True, exportable=True) + import os + + if os.path.isdir(model_name_or_path): + model = model_class(model_name_or_path, pretrained=True, exportable=True) + else: + model = model_class(f"hf_hub:{model_name_or_path}", pretrained=True, exportable=True) model = model.to(torch_dtype).to(device) elif library_name == "sentence_transformers": token = model_kwargs.pop("token", None) From 0120bee03cf8fe43bc8a10d87dd1f5ca5034e7a4 Mon Sep 17 00:00:00 2001 From: Tai An Date: Tue, 16 Jun 2026 09:19:54 -0700 Subject: [PATCH 2/4] test(exporters/tasks): cover local timm path skipping hf_hub prefix --- tests/exporters/common/test_tasks_manager.py | 59 ++++++++++++++++++++ 1 file changed, 59 insertions(+) diff --git a/tests/exporters/common/test_tasks_manager.py b/tests/exporters/common/test_tasks_manager.py index db037e2d2a..cb8831bd51 100644 --- a/tests/exporters/common/test_tasks_manager.py +++ b/tests/exporters/common/test_tasks_manager.py @@ -16,6 +16,7 @@ import inspect from typing import Optional, Set from unittest import TestCase +from unittest.mock import MagicMock, patch import pytest from transformers import BertConfig, Pix2StructForConditionalGeneration, VisualBertForQuestionAnswering @@ -184,3 +185,61 @@ def test_library_detection(self): ) self.assertEqual(TasksManager.infer_library_from_model("gpt2"), "transformers") self.assertEqual(TasksManager.infer_library_from_model("timm/mobilenetv3_large_100.ra_in1k"), "timm") + + def test_get_model_from_task_timm_local_dir_skips_hf_hub_prefix(self): + # Regression test for local timm checkpoint loading: a local directory must be + # passed to the timm model constructor as-is, while a Hub id keeps the + # "hf_hub:" prefix. Both paths are exercised without network access by mocking + # the resolved model class and os.path.isdir. + created = MagicMock() + created.to.return_value = created + model_class = MagicMock(return_value=created) + + with patch.object(TasksManager, "get_model_class_for_task", return_value=model_class): + with patch("os.path.isdir", return_value=True): + TasksManager.get_model_from_task( + "image-classification", + "/local/path/to/timm_model", + framework="pt", + library_name="timm", + ) + model_class.assert_called_once_with( + "/local/path/to/timm_model", pretrained=True, exportable=True + ) + + model_class.reset_mock() + with patch("os.path.isdir", return_value=False): + TasksManager.get_model_from_task( + "image-classification", + "timm/mobilenetv3_large_100.ra_in1k", + framework="pt", + library_name="timm", + ) + model_class.assert_called_once_with( + "hf_hub:timm/mobilenetv3_large_100.ra_in1k", pretrained=True, exportable=True + ) + + def test_standardize_sentence_transformers_readonly_config(self): + # sentence-transformers >= 5 makes `config` read-only, so the assignment must not raise. + class Transformer: + def __init__(self, config): + self.auto_model = type("AutoModel", (), {"config": config})() + + class SentenceTransformer: + def __init__(self, inner): + self._modules = [inner] + + def __getitem__(self, idx): + return self._modules[idx] + + @property + def config(self): + return self._modules[0].auto_model.config + + inner_config = BertConfig() + st_model = SentenceTransformer(Transformer(inner_config)) + + TasksManager.standardize_model_attributes(st_model, library_name="sentence_transformers") + + self.assertEqual(inner_config.export_model_type, "transformer") + self.assertIs(st_model.config, inner_config) From 2d9075497e1687f553b62d3b20e2be1b04749322 Mon Sep 17 00:00:00 2001 From: Tai An Date: Tue, 16 Jun 2026 09:21:11 -0700 Subject: [PATCH 3/4] test(exporters/tasks): cover local timm path skipping hf_hub prefix --- tests/exporters/common/test_tasks_manager.py | 25 -------------------- 1 file changed, 25 deletions(-) diff --git a/tests/exporters/common/test_tasks_manager.py b/tests/exporters/common/test_tasks_manager.py index cb8831bd51..5032bca3d2 100644 --- a/tests/exporters/common/test_tasks_manager.py +++ b/tests/exporters/common/test_tasks_manager.py @@ -218,28 +218,3 @@ def test_get_model_from_task_timm_local_dir_skips_hf_hub_prefix(self): model_class.assert_called_once_with( "hf_hub:timm/mobilenetv3_large_100.ra_in1k", pretrained=True, exportable=True ) - - def test_standardize_sentence_transformers_readonly_config(self): - # sentence-transformers >= 5 makes `config` read-only, so the assignment must not raise. - class Transformer: - def __init__(self, config): - self.auto_model = type("AutoModel", (), {"config": config})() - - class SentenceTransformer: - def __init__(self, inner): - self._modules = [inner] - - def __getitem__(self, idx): - return self._modules[idx] - - @property - def config(self): - return self._modules[0].auto_model.config - - inner_config = BertConfig() - st_model = SentenceTransformer(Transformer(inner_config)) - - TasksManager.standardize_model_attributes(st_model, library_name="sentence_transformers") - - self.assertEqual(inner_config.export_model_type, "transformer") - self.assertIs(st_model.config, inner_config) From e9f5b8ddd37724278fff346d342f8cb162424ff0 Mon Sep 17 00:00:00 2001 From: Tai An Date: Tue, 16 Jun 2026 09:23:26 -0700 Subject: [PATCH 4/4] test(exporters/tasks): cover local timm path skipping hf_hub prefix --- tests/exporters/common/test_tasks_manager.py | 20 ++++++++++---------- 1 file changed, 10 insertions(+), 10 deletions(-) diff --git a/tests/exporters/common/test_tasks_manager.py b/tests/exporters/common/test_tasks_manager.py index 5032bca3d2..c98d551a3a 100644 --- a/tests/exporters/common/test_tasks_manager.py +++ b/tests/exporters/common/test_tasks_manager.py @@ -176,16 +176,6 @@ def test_custom_class(self): model = TasksManager.get_model_from_task("question-answering", "uclanlp/visualbert-vqa") self.assertTrue(isinstance(model, VisualBertForQuestionAnswering)) - def test_library_detection(self): - self.assertEqual( - TasksManager.infer_library_from_model("intfloat/multilingual-e5-large"), "sentence_transformers" - ) - self.assertEqual( - TasksManager.infer_library_from_model("stabilityai/stable-diffusion-xl-base-1.0"), "diffusers" - ) - self.assertEqual(TasksManager.infer_library_from_model("gpt2"), "transformers") - self.assertEqual(TasksManager.infer_library_from_model("timm/mobilenetv3_large_100.ra_in1k"), "timm") - def test_get_model_from_task_timm_local_dir_skips_hf_hub_prefix(self): # Regression test for local timm checkpoint loading: a local directory must be # passed to the timm model constructor as-is, while a Hub id keeps the @@ -218,3 +208,13 @@ def test_get_model_from_task_timm_local_dir_skips_hf_hub_prefix(self): model_class.assert_called_once_with( "hf_hub:timm/mobilenetv3_large_100.ra_in1k", pretrained=True, exportable=True ) + + def test_library_detection(self): + self.assertEqual( + TasksManager.infer_library_from_model("intfloat/multilingual-e5-large"), "sentence_transformers" + ) + self.assertEqual( + TasksManager.infer_library_from_model("stabilityai/stable-diffusion-xl-base-1.0"), "diffusers" + ) + self.assertEqual(TasksManager.infer_library_from_model("gpt2"), "transformers") + self.assertEqual(TasksManager.infer_library_from_model("timm/mobilenetv3_large_100.ra_in1k"), "timm")