AutoModel.from_pretrained raises AttributeError: model_class has no attribute 'config_class' for remote-code models
Author: atharv1945
Pull request: no
Labels: name: bug

### System Info

- `transformers` version: 5.13.0.dev0
- Platform: Linux-6.6.122+-x86_64-with-glibc2.35
- Python version: 3.12.13
- Huggingface_hub version: 1.18.0
- Safetensors version: 0.8.0
- Accelerate version: 1.13.0
- Accelerate config: not found
- DeepSpeed version: not installed
- PyTorch version (accelerator?): 2.11.0+cu128 (CUDA)
- Using distributed or parallel set-up in script?: No
- Using GPU in script?: Yes
- GPU type: Tesla T4

### Who can help?

@zucchini-nlp

### Information

- [ ] The official example scripts
- [x] My own modified scripts

### Tasks

- [ ] An officially supported task in the `examples` folder (such as GLUE/SQuAD, ...)
- [ ] My own task or dataset (give details below)

### Reproduction

```
import torch
from transformers import AutoModel

model = AutoModel.from_pretrained(
    "jinaai/jina-embeddings-v5-text-nano",
    trust_remote_code=True,
    attn_implementation="flash_attention_2",
    dtype=torch.bfloat16,
)
print(type(model))
```

On transformers 5.10.2, this raises:
```
---------------------------------------------------------------------------
AttributeError                            Traceback (most recent call last)
[/tmp/ipykernel_1037/2621395598.py](https://localhost:8080/#) in <cell line: 0>()
----> 1 model = AutoModel.from_pretrained(
      2     "jinaai/jina-embeddings-v5-text-nano",
      3     trust_remote_code=True,
      4     attn_implementation="flash_attention_2",
      5     dtype=torch.bfloat16,

[/usr/local/lib/python3.12/dist-packages/transformers/models/auto/auto_factory.py](https://localhost:8080/#) in from_pretrained(cls, pretrained_model_name_or_path, *model_args, **kwargs)
    392         elif has_local_code:
    393             model_class = _get_model_class(config, cls._model_mapping)
--> 394             if model_class.config_class == config.sub_configs.get("text_config", None):
    395                 # TODO: Validate that copying the parent quantization config to the text sub-config preserves
    396                 # modules_to_not_convert and skip-module matching when composite-model module prefixes differ.

AttributeError: type object 'JinaEmbeddingsV5Model' has no attribute 'config_class'
```

Link: https://colab.research.google.com/drive/1DlVxUqpER_YKd-3khWn2Yui1FqNb5Vaw?usp=sharing

Analysis: 
The error originates in src/transformers/models/auto/auto_factory.py, in the elif has_local_code: branch of from_pretrained (and an identical pattern exists in from_config):
```
if model_class.config_class == config.sub_configs.get("text_config", None):
```
model_class resolves to JinaEmbeddingsV5Model, a remote-code class loaded via get_class_from_dynamic_module. Remote-code classes are not required to define config_class as a class attribute, so accessing model_class.config_class raises AttributeError instead of evaluating to False.
This appears related to the composite-config handling discussed in #38258.
A candidate fix:
```
text_config_class = config.sub_configs.get("text_config", None)
if text_config_class is not None and getattr(model_class, "config_class", None) == text_config_class:
```
This preserves the existing behavior for composite models (where config.sub_configs["text_config"] is set and model_class.config_class matches it) while avoiding the AttributeError for remote-code/non-composite classes, where text_config_class is None and the branch is correctly skipped via short-circuit evaluation.


### Expected behavior

With the fix above applied, the same reproduction (on transformers 5.13.0.dev0) proceeds past the auto_factory.py check without an AttributeError, and instead reaches the model's own __init__, where it fails with a separate, unrelated error:
```
---------------------------------------------------------------------------
ValueError                                Traceback (most recent call last)
[/tmp/ipykernel_2692/2303428619.py](https://colab.research.google.com/drive/18OEFU3oimcgR7GbMHmJEodPhiTXheFmf#) in <cell line: 0>()
      1 import torch
      2 from transformers import AutoModel
----> 3 model = AutoModel.from_pretrained(
      4     "jinaai/jina-embeddings-v5-text-nano",
      5     trust_remote_code=True,

7 frames[/content/transformers_repo/src/transformers/models/auto/auto_factory.py](https://colab.research.google.com/drive/18OEFU3oimcgR7GbMHmJEodPhiTXheFmf#) in from_pretrained(cls, pretrained_model_name_or_path, *model_args, **kwargs)
    406                 if parent_quant is not None:
    407                     config.quantization_config = parent_quant
--> 408             return model_class.from_pretrained(
    409                 pretrained_model_name_or_path, *model_args, config=config, **hub_kwargs, **kwargs
    410             )

[~/.cache/huggingface/modules/transformers_modules/jinaai/jina_hyphen_embeddings_hyphen_v5_hyphen_text_hyphen_nano/8a7f00aac812071b69403df470f1038ec85f8925/modeling_jina_embeddings_v5.py](https://colab.research.google.com/drive/18OEFU3oimcgR7GbMHmJEodPhiTXheFmf#) in from_pretrained(cls, pretrained_model_name_or_path, *args, **kwargs)
     26                 pretrained_model_name_or_path,
     27             )
---> 28         base_model = EuroBertModel.from_pretrained(
     29             pretrained_model_name_or_path,
     30             config=base_config,

[/content/transformers_repo/src/transformers/modeling_utils.py](https://colab.research.google.com/drive/18OEFU3oimcgR7GbMHmJEodPhiTXheFmf#) in from_pretrained(cls, pretrained_model_name_or_path, config, cache_dir, ignore_mismatched_sizes, force_download, local_files_only, token, revision, use_safetensors, weights_only, fusion_config, disable_mmap, *model_args, **kwargs)
   4281         config = copy.deepcopy(config)  # We do not want to modify the config inplace in from_pretrained.
   4282         with ContextManagers(model_init_context):
-> 4283             model = cls(config, *model_args, **model_kwargs)
   4284             patch_output_recorders(model)
   4285 

[~/.cache/huggingface/modules/transformers_modules/jinaai/jina_hyphen_embeddings_hyphen_v5_hyphen_text_hyphen_nano/8a7f00aac812071b69403df470f1038ec85f8925/modeling_eurobert.py](https://colab.research.google.com/drive/18OEFU3oimcgR7GbMHmJEodPhiTXheFmf#) in __init__(self, config)
    497 
    498     def __init__(self, config: EuroBertConfig):
--> 499         super().__init__(config)
    500         self.padding_idx = config.pad_token_id
    501         self.vocab_size = config.vocab_size

[/content/transformers_repo/src/transformers/modeling_utils.py](https://colab.research.google.com/drive/18OEFU3oimcgR7GbMHmJEodPhiTXheFmf#) in __init__(self, config, *inputs, **kwargs)
   1353         # Check the attention implementation is supported, or set it if not yet set (on the internal attr, to avoid
   1354         # setting it recursively)
-> 1355         self.config._attn_implementation_internal = self._check_and_adjust_attn_implementation(
   1356             self.config._attn_implementation,
   1357             is_init_check=True,

[/content/transformers_repo/src/transformers/modeling_utils.py](https://colab.research.google.com/drive/18OEFU3oimcgR7GbMHmJEodPhiTXheFmf#) in _check_and_adjust_attn_implementation(self, attn_implementation, is_init_check, allow_all_kernels)
   1992                 raise e
   1993         else:
-> 1994             applicable_attn_implementation = self.get_correct_attn_implementation(
   1995                 applicable_attn_implementation, is_init_check
   1996             )

[/content/transformers_repo/src/transformers/modeling_utils.py](https://colab.research.google.com/drive/18OEFU3oimcgR7GbMHmJEodPhiTXheFmf#) in get_correct_attn_implementation(self, requested_attention, is_init_check)
   2039         ):
   2040             fa_version = int(fa_matched.group(1))  # last digit
-> 2041             self._flash_attn_can_dispatch(flash_attn_version=fa_version, is_init_check=is_init_check)
   2042         elif "flex_attention" in applicable_attention:
   2043             self._flex_attn_can_dispatch(is_init_check)

[/content/transformers_repo/src/transformers/modeling_utils.py](https://colab.research.google.com/drive/18OEFU3oimcgR7GbMHmJEodPhiTXheFmf#) in _flash_attn_can_dispatch(self, flash_attn_version, is_init_check)
   1758         """
   1759         if not self._supports_flash_attn:
-> 1760             raise ValueError(
   1761                 f"{self.__class__.__name__} does not support Flash Attention {flash_attn_version} yet. Please request to add support where"
   1762                 f" the model is hosted, on its model hub page: [https://huggingface.co/{self.config._name_or_path}/discussions/new](https://huggingface.co/%7Bself.config._name_or_path%7D/discussions/new)"

ValueError: EuroBertModel does not support Flash Attention 2 yet. Please request to add support where the model is hosted, on its model hub page: https://huggingface.co/jinaai/jina-embeddings-v5-text-nano/discussions/new or in the Transformers GitHub repo: https://github.com/huggingface/transformers/issues/new
```

This confirms the AttributeError was the actual blocker for this auto_factory.py path. The ValueError above is a separate issue related to jinaai's bundled remote-code modeling_eurobert.py (out of scope for this report).


Comments:
I have a candidate fix for this (described above) and I'm happy to open a PR if useful, let me know if this analysis looks correct.

Hmm, how come we ended up in local-code branch with a remote model? The above script loads for me on main branch and doesn't trigger the `local_code` path 

Can you check your env and see what happened in detail? I'm happy to check `getattr(model, "config_class"))` though all local models are guaranteed to have it and seems like the issue you faced is different

You're right, on a single fresh call, it takes the has_remote_code/trust_remote_code branch and doesn't hit config_class (confirmed, traceback now shows line 391, ending in the FA2 ValueError as expected). The AttributeError only appears on a second AutoModel.from_pretrained(...) call for this model in the same Python session. The first call's cls.register(config.__class__, model_class, exist_ok=True) (lines 388-389) registers JinaEmbeddingsV5Model into cls._model_mapping, so the second call's _get_model_class(...) finds it, has_local_code becomes True, and model_class.config_class (on the now-registered remote class, which has no config_class) raises AttributeError. I'll update the repro to show both calls. The getattr/is not None guard fix should still apply, but the trigger is "load the same trust_remote_code=True model twice in one session," not a single load.

Here's a minimal reproducer showing both calls in a single fresh session:

```
import torch
from transformers import AutoModel

MODEL_ID = "jinaai/jina-embeddings-v5-text-nano"

print("=== First call (trust_remote_code=True) ===")
try:
    model = AutoModel.from_pretrained(
        MODEL_ID,
        trust_remote_code=True,
        attn_implementation="flash_attention_2",
        dtype=torch.bfloat16,
    )
    print("Loaded OK ->", type(model))
except Exception as e:
    print(f"First call failed: {type(e).__name__} - {e}")

print()
print("=== Second call (same process, same args) ===")
try:
    model = AutoModel.from_pretrained(
        MODEL_ID,
        trust_remote_code=True,
        attn_implementation="flash_attention_2",
        dtype=torch.bfloat16,
    )
    print("Loaded OK ->", type(model))
except Exception as e:
    print(f"Second call failed: {type(e).__name__} - {e}")
``` 
Output: 
```
=== First call (trust_remote_code=True) ===
First call failed: ValueError - EuroBertModel does not support Flash Attention 2 yet. Please request to add support where the model is hosted, on its model hub page: https://huggingface.co/jinaai/jina-embeddings-v5-text-nano/discussions/new or in the Transformers GitHub repo: https://github.com/huggingface/transformers/issues/new

=== Second call (same process, same args) ===
Second call failed: AttributeError - type object 'JinaEmbeddingsV5Model' has no attribute 'config_class'
```

So the first call correctly takes the has_remote_code branch (matches what you saw) and fails with the unrelated FA2 error. The first call's cls.register(config.__class__, model_class, exist_ok=True) then registers JinaEmbeddingsV5Model into cls._model_mapping. On the second call, _get_model_class resolves via this registration, has_local_code becomes True, and model_class.config_class (on JinaEmbeddingsV5Model, which has no config_class) raises the AttributeError.
So the trigger is: load the same trust_remote_code=True model twice in one session. The getattr(model_class, "config_class", None) guard (with the is not None check on text_config_class) should still resolve this — happy to open a PR if useful.

Ah I see, thanks for detailed repro. Yeah, please feel free to submit a PR and add an in-line code comment on why the `getattr`