Skip to content
16 changes: 16 additions & 0 deletions docs/source/en/modular_transformers.md
Original file line number Diff line number Diff line change
Expand Up @@ -389,6 +389,22 @@ class GemmaTokenizer(LlamaTokenizer):
raise AttributeError("Not needed for Gemma")
```

### Inheriting from a class in the same modular file

A modeling block inheriting from a class defined earlier in the same modular file is unpacked like a cross-model parent, from the parent's generated code. `super()` calls, `del`, and `AttributeError` work the same, and chains work too. Below, `MyModelKVSharedAttention` is generated as a standalone `nn.Module` without `k_proj`.

```py
class MyModelAttention(LlamaAttention):
...

class MyModelKVSharedAttention(MyModelAttention):
def __init__(self, config, layer_idx):
super().__init__(config, layer_idx)
del self.k_proj
```

`PreTrainedModel` subclasses, configs, processors and thin `*args` wrappers (such as deprecated aliases) keep regular inheritance.

### Overriding decorated methods

When you override a decorated parent method, the parent's decorator carries over automatically. If you add your own decorator, it replaces the parent's.
Expand Down
8 changes: 7 additions & 1 deletion src/transformers/models/aimv2/modular_aimv2.py
Original file line number Diff line number Diff line change
Expand Up @@ -136,6 +136,10 @@ class Aimv2Config(SiglipConfig):
projection_dim: int = 512
logit_scale_init_value: float = 2.6592
max_logit_scale: float = 100.0
sub_configs_defaults = {
"vision_config": Aimv2VisionConfig,
"text_config": Aimv2TextConfig,
}


class Aimv2Output(SiglipOutput):
Expand Down Expand Up @@ -229,7 +233,9 @@ def forward(


class Aimv2Encoder(SiglipEncoder):
pass
def __init__(self, config: Aimv2Config):
super().__init__(config)
self.layers = nn.ModuleList([Aimv2EncoderLayer(config) for _ in range(config.num_hidden_layers)])


class Aimv2AttentionPoolingHead(nn.Module):
Expand Down
87 changes: 85 additions & 2 deletions src/transformers/models/altclip/modular_altclip.py
Original file line number Diff line number Diff line change
Expand Up @@ -17,14 +17,15 @@
from huggingface_hub.dataclasses import strict

from ... import initialization as init
from ...configuration_utils import PreTrainedConfig
from ...masking_utils import create_bidirectional_mask
from ...modeling_outputs import (
BaseModelOutputWithPooling,
BaseModelOutputWithPoolingAndProjection,
)
from ...modeling_utils import PreTrainedModel
from ...processing_utils import Unpack
from ...utils import TransformersKwargs, auto_docstring, can_return_tuple
from ...utils import TransformersKwargs, auto_docstring, can_return_tuple, logging
from ...utils.generic import merge_with_config_defaults
from ...utils.output_capturing import capture_outputs
from ..chinese_clip.modeling_chinese_clip import (
Expand Down Expand Up @@ -55,6 +56,9 @@
)


logger = logging.get_logger(__name__)


@auto_docstring(checkpoint="BAAI/AltCLIP")
@strict
class AltCLIPTextConfig(CLIPTextConfig):
Expand Down Expand Up @@ -144,6 +148,83 @@ class AltCLIPConfig(CLIPConfig):
```"""

projection_dim: int = 768
sub_configs_defaults = {
"vision_config": AltCLIPVisionConfig,
"text_config": AltCLIPTextConfig,
}

def __post_init__(self, **kwargs):
# For backward compatibility check keyword args
# Instead of simply assigning `[text|vision]_config_dict` to `[text|vision]_config`, we use the values in
# `[text|vision]_config_dict` to update the values in `[text|vision]_config`. The values should be same in most
# cases, but we don't want to break anything regarding `_config_dict` that existed before commit `8827e1b2`.
if (text_config_dict := kwargs.pop("text_config_dict", None)) is not None:
# This is the complete result when using `text_config_dict`.
_text_config_dict = AltCLIPTextConfig(**text_config_dict).to_dict()
text_config = (
self.text_config.to_dict() if isinstance(self.text_config, PreTrainedConfig) else self.text_config
)
text_config = text_config or {}

# Give a warning if the values exist in both `_text_config_dict` and `text_config` but being different.
for key, value in _text_config_dict.items():
if key in text_config and value != text_config[key] and key != "transformers_version":
# If specified in `text_config_dict`
if key in text_config_dict:
message = (
f"`{key}` is found in both `text_config_dict` and `text_config` but with different values. "
f'The value `text_config_dict["{key}"]` will be used instead.'
)
# If inferred from default argument values (just to be super careful)
else:
message = (
f"`text_config_dict` is provided which will be used to initialize `CLIPTextConfig`. The "
f'value `text_config["{key}"]` will be overridden.'
)
logger.info(message)

# Update all values in `text_config` with the ones in `_text_config_dict`.
text_config.update(_text_config_dict)
self.text_config = text_config

if (vision_config_dict := kwargs.pop("vision_config_dict", None)) is not None:
# This is the complete result when using `vision_config_dict`.
_vision_config_dict = AltCLIPVisionConfig(**vision_config_dict).to_dict()
vision_config = (
self.vision_config.to_dict()
if isinstance(self.vision_config, PreTrainedConfig)
else self.vision_config
)
vision_config = vision_config or {}

# convert keys to string instead of integer
if "id2label" in _vision_config_dict:
_vision_config_dict["id2label"] = {
str(key): value for key, value in _vision_config_dict["id2label"].items()
}

# Give a warning if the values exist in both `_vision_config_dict` and `vision_config` but being different.
for key, value in _vision_config_dict.items():
if key in vision_config and value != vision_config[key] and key != "transformers_version":
# If specified in `vision_config_dict`
if key in vision_config_dict:
message = (
f"`{key}` is found in both `vision_config_dict` and `vision_config` but with different "
f'values. The value `vision_config_dict["{key}"]` will be used instead.'
)
# If inferred from default argument values (just to be super careful)
else:
message = (
f"`vision_config_dict` is provided which will be used to initialize `CLIPVisionConfig`. "
f'The value `vision_config["{key}"]` will be overridden.'
)
logger.info(message)

# Update all values in `vision_config` with the ones in `_vision_config_dict`.
vision_config.update(_vision_config_dict)
self.vision_config = vision_config

PreTrainedConfig.__post_init__(self, **kwargs)


class AltCLIPOutput(CLIPOutput):
Expand Down Expand Up @@ -219,7 +300,9 @@ def __init__(self, config: AltCLIPVisionConfig):


class AltCLIPEncoder(CLIPEncoder):
pass
def __init__(self, config: AltCLIPConfig):
super().__init__(config)
self.layers = nn.ModuleList([AltCLIPEncoderLayer(config) for _ in range(config.num_hidden_layers)])


class AltCLIPVisionEmbeddings(CLIPVisionEmbeddings):
Expand Down
15 changes: 13 additions & 2 deletions src/transformers/models/apertus/modular_apertus.py
Original file line number Diff line number Diff line change
Expand Up @@ -189,6 +189,10 @@ def __init__(self, config: ApertusConfig, layer_idx: int):
del self.input_layernorm
del self.post_attention_layernorm

self.self_attn = ApertusAttention(config=config, layer_idx=layer_idx)

self.mlp = ApertusMLP(config)

def forward(
self,
hidden_states: torch.Tensor,
Expand Down Expand Up @@ -221,11 +225,18 @@ def forward(


class ApertusPreTrainedModel(LlamaPreTrainedModel):
pass
_can_record_outputs = {
"hidden_states": ApertusDecoderLayer,
"attentions": ApertusAttention,
}


class ApertusModel(LlamaModel):
pass
def __init__(self, config: ApertusConfig):
super().__init__(config)
self.layers = nn.ModuleList(
[ApertusDecoderLayer(config, layer_idx) for layer_idx in range(config.num_hidden_layers)]
)


class ApertusForCausalLM(LlamaForCausalLM):
Expand Down
9 changes: 9 additions & 0 deletions src/transformers/models/aria/modular_aria.py
Original file line number Diff line number Diff line change
Expand Up @@ -751,6 +751,10 @@ class AriaPreTrainedModel(LlamaPreTrainedModel):
_can_compile_fullgraph = False # MoE models don't work with torch.compile (dynamic slicing)
_supports_attention_backend = True
input_modalities = ("image", "text")
_can_record_outputs = {
"hidden_states": AriaTextDecoderLayer,
"attentions": AriaTextAttention,
}

@torch.no_grad()
def _init_weights(self, module):
Expand All @@ -766,6 +770,7 @@ def __init__(self, config: AriaTextConfig):
[AriaTextDecoderLayer(config, layer_idx) for layer_idx in range(config.num_hidden_layers)]
)
self.gradient_checkpointing = False
self.norm = AriaTextRMSNorm(config.hidden_size, eps=config.rms_norm_eps)
self.post_init()


Expand Down Expand Up @@ -903,6 +908,10 @@ def forward(
class AriaForConditionalGeneration(LlavaForConditionalGeneration):
_tied_weights_keys = {"lm_head.weight": "model.language_model.embed_tokens.weight"}

def __init__(self, config: AriaConfig):
super().__init__(config)
self.model = AriaModel(config)

@auto_docstring
def get_image_features(
self,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -188,6 +188,7 @@ class AudioFlamingo3Model(VoxtralModel):

def __init__(self, config: AudioFlamingo3Config):
super().__init__(config)
self.multi_modal_projector = AudioFlamingo3MultiModalProjector(config)

@can_return_tuple
@auto_docstring(
Expand Down
18 changes: 16 additions & 2 deletions src/transformers/models/axk1/modular_axk1.py
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,7 @@
from ...modeling_utils import ALL_ATTENTION_FUNCTIONS, PreTrainedModel
from ...processing_utils import Unpack
from ...utils import auto_docstring, logging
from ...utils.output_capturing import OutputRecorder
from ..deepseek_v3.configuration_deepseek_v3 import DeepseekV3Config
from ..deepseek_v3.modeling_deepseek_v3 import (
DeepseekV3Attention,
Expand Down Expand Up @@ -242,11 +243,20 @@ def forward(


class AXK1DecoderLayer(Glm4MoeLiteDecoderLayer):
pass
def __init__(self, config: AXK1Config, layer_idx: int):
super().__init__(config, layer_idx)
self.self_attn = AXK1Attention(config, layer_idx)

self.mlp = AXK1MoE(config) if config.mlp_layer_types[layer_idx] == "sparse" else AXK1MLP(config)


class AXK1PreTrainedModel(DeepseekV3PreTrainedModel):
_keys_to_ignore_on_load_unexpected = ["inv_freq"]
_can_record_outputs = {
"hidden_states": AXK1DecoderLayer,
"attentions": AXK1Attention,
"router_logits": OutputRecorder(AXK1TopkRouter, index=0),
}

@torch.no_grad()
def _init_weights(self, module):
Expand All @@ -260,7 +270,11 @@ def _init_weights(self, module):


class AXK1Model(DeepseekV3Model):
pass
def __init__(self, config: AXK1Config):
super().__init__(config)
self.layers = nn.ModuleList(
[AXK1DecoderLayer(config, layer_idx) for layer_idx in range(config.num_hidden_layers)]
)


class AXK1ForCausalLM(DeepseekV3ForCausalLM):
Expand Down
17 changes: 15 additions & 2 deletions src/transformers/models/axk2/modular_axk2.py
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,7 @@
from ...modeling_utils import ALL_ATTENTION_FUNCTIONS, PreTrainedModel
from ...processing_utils import Unpack
from ...utils import auto_docstring, logging
from ...utils.output_capturing import OutputRecorder
from ..clip.modeling_clip import CLIPMLP
from ..deepseek_v3.modeling_deepseek_v3 import (
DeepseekV3RMSNorm,
Expand Down Expand Up @@ -261,7 +262,9 @@ class AXK2Experts(DeepseekV32Experts):


class AXK2MoE(DeepseekV32MoE):
pass
def __init__(self, config: AXK2Config):
super().__init__(config)
self.gate = AXK2TopkRouter(config)


class AXK2Attention(DeepseekV32Attention):
Expand Down Expand Up @@ -407,10 +410,16 @@ def __init__(self, config: AXK2Config, layer_idx: int):
if config.mlp_layer_types[layer_idx] == "sparse"
else AXK2RMSNorm(config.hidden_size, eps=config.rms_norm_eps)
)
self.self_attn = AXK2Attention(config, layer_idx)


class AXK2PreTrainedModel(DeepseekV32PreTrainedModel):
_keys_to_ignore_on_load_unexpected = ["inv_freq"]
_can_record_outputs = {
"hidden_states": AXK2DecoderLayer,
"attentions": AXK2Attention,
"router_logits": OutputRecorder(AXK2TopkRouter, index=0),
}

@torch.no_grad()
def _init_weights(self, module):
Expand All @@ -424,7 +433,11 @@ def _init_weights(self, module):


class AXK2Model(DeepseekV32Model):
pass
def __init__(self, config: AXK2Config):
super().__init__(config)
self.layers = nn.ModuleList(
[AXK2DecoderLayer(config, layer_idx) for layer_idx in range(config.num_hidden_layers)]
)


class AXK2ForCausalLM(DeepseekV32ForCausalLM):
Expand Down
9 changes: 9 additions & 0 deletions src/transformers/models/aya_vision/modular_aya_vision.py
Original file line number Diff line number Diff line change
Expand Up @@ -99,6 +99,11 @@ class AyaVisionModelOutputWithPast(LlavaModelOutputWithPast):


class AyaVisionModel(LlavaModel):
def __init__(self, config: AyaVisionConfig):
super().__init__(config)

self.multi_modal_projector = AyaVisionMultiModalProjector(config)

# Unlike LLaVA, the model doesn't have to deal with Pixtral-style image states
@merge_with_config_defaults
@can_return_tuple
Expand Down Expand Up @@ -202,6 +207,10 @@ def forward(


class AyaVisionForConditionalGeneration(LlavaForConditionalGeneration):
def __init__(self, config: AyaVisionConfig):
super().__init__(config)
self.model = AyaVisionModel(config)

def forward(
self,
input_ids: torch.LongTensor | None = None,
Expand Down
21 changes: 19 additions & 2 deletions src/transformers/models/bailing_hybrid/modular_bailing_hybrid.py
Original file line number Diff line number Diff line change
Expand Up @@ -402,7 +402,14 @@ class BailingHybridMoE(DeepseekV3MoE):


class BailingHybridDecoderLayer(KimiLinearDecoderLayer):
pass
def __init__(self, config: BailingHybridConfig, layer_idx: int):
super().__init__(config, layer_idx)
self.self_attn = (
# CODEPATH: TODO: remove this once the mlinter rule is relaxed
BailingHybridAttention(config, layer_idx)
if config.layer_types[layer_idx] == "full_attention"
else BailingHybridDeltaAttention(config, layer_idx)
)


@auto_docstring
Expand Down Expand Up @@ -441,12 +448,22 @@ def _init_weights(self, module):

@auto_docstring
class BailingHybridModel(Qwen3NextModel):
pass
def __init__(self, config: BailingHybridConfig):
super().__init__(config)
self.layers = nn.ModuleList(
[BailingHybridDecoderLayer(config, layer_idx) for layer_idx in range(config.num_hidden_layers)]
)
self.norm = BailingHybridRMSNorm(config.hidden_size, eps=config.rms_norm_eps)
self.rotary_emb = BailingHybridRotaryEmbedding(config=config)


class BailingHybridForCausalLM(DeepseekV3ForCausalLM, GenerationMixin):
_tied_weights_keys = {}

def __init__(self, config: BailingHybridConfig):
super().__init__(config)
self.model = BailingHybridModel(config)


__all__ = [
"BailingHybridConfig",
Expand Down
1 change: 1 addition & 0 deletions src/transformers/models/bamba/modular_bamba.py
Original file line number Diff line number Diff line change
Expand Up @@ -353,6 +353,7 @@ class BambaForCausalLM(LlamaForCausalLM):
def __init__(self, config: BambaConfig):
super().__init__(config)
self.z_loss_coefficient = config.z_loss_coefficient
self.model = BambaModel(config)

# Initialize weights and apply final processing
self.post_init()
Expand Down
Loading
Loading