#                🚨🚨🚨🚨🚨🚨🚨🚨🚨🚨🚨🚨🚨🚨🚨🚨🚨🚨🚨🚨🚨🚨🚨🚨🚨🚨🚨🚨🚨🚨🚨🚨🚨🚨🚨🚨🚨🚨🚨🚨🚨🚨🚨🚨🚨🚨🚨🚨
#           This file was automatically generated from src/transformers/models/molmo2/modular_molmo2.py.
#               Do NOT edit this file manually as any edits will be overwritten by the generation of
#             the file from the modular. If any change should be done, please apply the change to the
#                          modular_molmo2.py file directly. One of our CI enforces this.
#                🚨🚨🚨🚨🚨🚨🚨🚨🚨🚨🚨🚨🚨🚨🚨🚨🚨🚨🚨🚨🚨🚨🚨🚨🚨🚨🚨🚨🚨🚨🚨🚨🚨🚨🚨🚨🚨🚨🚨🚨🚨🚨🚨🚨🚨🚨🚨🚨
# Copyright 2026 The HuggingFace Team. All rights reserved.
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
#     http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.

from huggingface_hub.dataclasses import strict

from ...configuration_utils import PreTrainedConfig, SubConfigSpec
from ...modeling_rope_utils import RopeParameters
from ...utils import auto_docstring


@auto_docstring(checkpoint="allenai/Molmo2-8B")
@strict
class Molmo2VisionConfig(PreTrainedConfig):
    r"""
    image_size (`list[int]`, *optional*):
        Input image size as (height, width), `[378, 378]` when not provided.
    num_position_embeddings (`int`, *optional*):
        Number of positional embeddings, one per image patch. Derived from `image_size` and `patch_size` when not
        provided.
    """

    model_type = "molmo2_vision"
    base_config_key = "vision_config"

    hidden_size: int = 1152
    intermediate_size: int = 4304
    num_hidden_layers: int = 25
    num_attention_heads: int = 16
    num_key_value_heads: int = 16
    head_dim: int = 72
    hidden_act: str = "gelu_pytorch_tanh"
    layer_norm_eps: float = 1e-6
    image_size: list[int] | None = None
    patch_size: int = 14
    num_position_embeddings: int | None = None
    num_channels: int = 3
    attention_dropout: float = 0.0
    attention_bias: bool = True
    initializer_range: float = 0.02

    def __post_init__(self, **kwargs):
        # Keys of the released checkpoints' `config.json`.
        kwargs.pop("model_type", None)
        self.image_size = kwargs.pop("image_default_input_size", self.image_size)
        self.patch_size = kwargs.pop("image_patch_size", self.patch_size)
        self.num_position_embeddings = kwargs.pop("image_num_pos", self.num_position_embeddings)
        if self.image_size is None:
            self.image_size = [378, 378]
        if self.num_position_embeddings is None:
            self.num_position_embeddings = (self.image_size[0] // self.patch_size) * (
                self.image_size[1] // self.patch_size
            )
        super().__post_init__(**kwargs)


@auto_docstring(checkpoint="allenai/Molmo2-8B")
@strict
class Molmo2AdapterConfig(PreTrainedConfig):
    r"""
    vision_feature_layer (`list[int]`, *optional*):
        Indices of the ViT layers whose outputs are concatenated and pooled, `[24, 18]` when not provided. Negative
        indices count from the last layer.
    text_hidden_size (`int`, *optional*, defaults to 3584):
        Hidden size of the text model (used for projection).
    """

    model_type = "molmo2_adapter"
    base_config_key = "adapter_config"

    vision_feature_layer: list[int] | None = None
    hidden_size: int = 1152
    num_attention_heads: int = 16
    num_key_value_heads: int = 16
    head_dim: int = 72
    attention_dropout: float = 0.0
    attention_bias: bool = True
    hidden_act: str = "silu"
    intermediate_size: int = 18944
    text_hidden_size: int = 3584
    mlp_bias: bool = False
    initializer_range: float = 0.02

    def __post_init__(self, **kwargs):
        # Keys of the released checkpoints' `config.json`.
        kwargs.pop("model_type", None)
        self.vision_feature_layer = kwargs.pop("vit_layers", self.vision_feature_layer)
        if self.vision_feature_layer is None:
            self.vision_feature_layer = [24, 18]
        super().__post_init__(**kwargs)


@auto_docstring(checkpoint="allenai/Molmo2-8B")
@strict
class Molmo2TextConfig(PreTrainedConfig):
    r"""
    additional_vocab_size (`int`, *optional*, defaults to 128):
        Number of additional vocabulary tokens beyond the base vocabulary.
    qk_norm_type (`str`, *optional*, defaults to `"qwen3"`):
        Query/key normalization layout used by the checkpoint. `"qwen3"` normalizes per head; `"olmo"` normalizes the
        full projected query/key tensors.
    rope_scaling_layers (`list[int]`, *optional*):
        Indices of the layers that apply the scaled RoPE described by `rope_parameters`. The remaining layers use an
        unscaled RoPE with the same theta. All layers are scaled when not provided.
    norm_after (`bool`, *optional*, defaults to `False`):
        Whether to apply layer normalization after the attention/FFN blocks instead of before.
    """

    model_type = "molmo2_text"
    base_config_key = "text_config"
    keys_to_ignore_at_inference = ["past_key_values"]
    attribute_map = {"qkv_bias": "attention_bias", "layer_norm_eps": "rms_norm_eps"}
    base_model_pp_plan = {
        "embed_tokens": (["input_ids"], ["inputs_embeds"]),
        "layers": (["hidden_states", "attention_mask"], ["hidden_states"]),
        "norm": (["hidden_states"], ["hidden_states"]),
    }

    hidden_size: int = 4096
    num_attention_heads: int = 32
    num_key_value_heads: int = 8
    head_dim: int = 128
    vocab_size: int = 151936
    additional_vocab_size: int = 128
    attention_bias: bool = False
    qk_norm_type: str = "qwen3"
    num_hidden_layers: int = 36
    intermediate_size: int = 12288
    hidden_act: str = "silu"
    attention_dropout: float = 0.0
    max_position_embeddings: int = 36864
    rope_parameters: RopeParameters | dict | None = None
    rope_scaling_layers: list[int] | None = None
    rms_norm_eps: float = 1e-6
    norm_after: bool = False
    initializer_range: float = 0.02
    use_cache: bool = True
    tie_word_embeddings: bool = False

    def __post_init__(self, **kwargs):
        if self.rope_scaling_layers is None:
            self.rope_scaling_layers = list(range(self.num_hidden_layers))
        super().__post_init__(**kwargs)

    def validate_architecture(self):
        super().validate_architecture()
        if self.qk_norm_type not in ("qwen3", "olmo"):
            raise ValueError(f"Unsupported `qk_norm_type`: {self.qk_norm_type}")
        if self.additional_vocab_size < 0:
            raise ValueError(f"`additional_vocab_size` must be >= 0, got {self.additional_vocab_size}")


@auto_docstring(checkpoint="allenai/Molmo2-8B")
@strict
class Molmo2Config(PreTrainedConfig):
    r"""
    vision_config (`Molmo2VisionConfig`, *optional*):
        Configuration for the vision transformer backbone.
    adapter_config (`Molmo2AdapterConfig`, *optional*):
        Configuration for the vision-to-language adapter.
    image_start_token_id (`int`, *optional*, defaults to 151936):
        Token ID marking the start of an image region.
    low_res_image_start_token_id (`int`, *optional*, defaults to 151940):
        Token ID marking the start of a low-resolution image crop.
    image_end_token_id (`int`, *optional*, defaults to 151937):
        Token ID marking the end of an image region.
    image_patch_id (`int`, *optional*, defaults to 151938):
        Token ID for image patches.
    frame_start_token_id (`int`, *optional*, defaults to 151943):
        Token ID marking the start of a video frame.
    frame_end_token_id (`int`, *optional*, defaults to 151944):
        Token ID marking the end of a video frame.
    tie_word_embeddings (`bool`, *optional*, defaults to `False`):
        Whether the model's input and output word embeddings should be tied.
    """

    model_type = "molmo2"
    attribute_map = {"image_token_id": "image_patch_id", "video_token_id": "image_patch_id"}
    sub_configs_defaults = {
        "text_config": SubConfigSpec(config_class=Molmo2TextConfig),
        "vision_config": SubConfigSpec(config_class=Molmo2VisionConfig),
        "adapter_config": SubConfigSpec(config_class=Molmo2AdapterConfig),
    }

    vision_config: dict | PreTrainedConfig | None = None
    adapter_config: dict | PreTrainedConfig | None = None
    text_config: dict | PreTrainedConfig | None = None
    image_start_token_id: int = 151936
    low_res_image_start_token_id: int = 151940
    image_end_token_id: int = 151937
    image_patch_id: int = 151938
    frame_start_token_id: int = 151943
    frame_end_token_id: int = 151944
    initializer_range: float = 0.02
    tie_word_embeddings: bool = False

    def __post_init__(self, **kwargs):
        # Checkpoints serialized before the rename store the vision sub-config under `vit_config`.
        legacy_vision_config = kwargs.pop("vit_config", None)
        if self.vision_config is None and legacy_vision_config is not None:
            self.vision_config = legacy_vision_config

        super().__post_init__(**kwargs)

        # The hub `config.json` declares a 27-layer ViT, but the released weights stop at layer 24, the deepest one
        # `vit_layers` reads. Without this trim, layers 25-26 are built with random weights. Do not remove.
        if legacy_vision_config is not None:
            num_vit_layers = self.vision_config.num_hidden_layers
            self.adapter_config.vision_feature_layer = [
                layer % num_vit_layers for layer in self.adapter_config.vision_feature_layer
            ]
            self.vision_config.num_hidden_layers = max(self.adapter_config.vision_feature_layer) + 1

    def validate_architecture(self):
        super().validate_architecture()
        num_vit_layers = self.vision_config.num_hidden_layers
        if not all(-num_vit_layers <= layer < num_vit_layers for layer in self.adapter_config.vision_feature_layer):
            raise ValueError(
                f"`adapter_config.vision_feature_layer` {self.adapter_config.vision_feature_layer} must index the "
                f"{num_vit_layers} layers of `vision_config`."
            )


__all__ = ["Molmo2AdapterConfig", "Molmo2Config", "Molmo2TextConfig", "Molmo2VisionConfig"]
