# 🚨🚨🚨🚨🚨🚨🚨🚨🚨🚨🚨🚨🚨🚨🚨🚨🚨🚨🚨🚨🚨🚨🚨🚨🚨🚨🚨🚨🚨🚨🚨🚨🚨🚨🚨🚨🚨🚨🚨🚨🚨🚨🚨🚨🚨🚨🚨🚨 # This file was automatically generated from src/transformers/models/gemma4_unified/modular_gemma4_unified.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_gemma4_unified.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 collections import UserDict from collections.abc import Callable from dataclasses import dataclass from typing import Optional import torch from torch import nn from torch.nn import init from ...activations import ACT2FN from ...cache_utils import Cache, DynamicCache from ...configuration_utils import PreTrainedConfig from ...generation import GenerationMixin from ...masking_utils import create_causal_mask, create_masks_for_generate, create_sliding_window_causal_mask from ...modeling_flash_attention_utils import FlashAttentionKwargs from ...modeling_layers import GradientCheckpointingLayer from ...modeling_outputs import BaseModelOutputWithPast, ModelOutput from ...modeling_rope_utils import ROPE_INIT_FUNCTIONS, dynamic_rope_update from ...modeling_utils import ALL_ATTENTION_FUNCTIONS, PreTrainedModel from ...processing_utils import Unpack from ...utils import ( TransformersKwargs, auto_docstring, can_return_tuple, torch_compilable_check, ) from ...utils.generic import maybe_autocast, merge_with_config_defaults from ...utils.output_capturing import capture_outputs from ..auto.modeling_auto import AutoModel from .configuration_gemma4_unified import ( Gemma4UnifiedAudioConfig, Gemma4UnifiedConfig, Gemma4UnifiedTextConfig, Gemma4UnifiedVisionConfig, ) @auto_docstring @dataclass class Gemma4UnifiedAudioModelOutput(ModelOutput): r""" pooler_output (`torch.FloatTensor` of shape `(batch_size, ..., hidden_size)`): Last hidden state that went through the audio specific multimodal projectors. attention_mask (`torch.BoolTensor`, *optional*): A torch.BoolTensor of shape `(batch_size, num_frames)`. True for valid positions, False for padding. """ pooler_output: torch.FloatTensor | None = None attention_mask: torch.BoolTensor | None = None @auto_docstring @dataclass class Gemma4UnifiedVisionModelOutput(ModelOutput): r""" pooler_output (`torch.FloatTensor` of shape `(batch_size, ..., hidden_size)`): Last hidden state that went through the vision specific multimodal projectors. """ pooler_output: torch.FloatTensor | None = None @dataclass class Gemma4UnifiedTextModelOutputWithPast(BaseModelOutputWithPast): """ BaseModelOutputWithPast extended with shared_kv_states for KV sharing. Args: shared_kv_states (`dict`, *optional*): Dictionary mapping layer type strings to tuples of (key_states, value_states) tensors. Used to pass shared KV states between layers during KV sharing. """ shared_kv_states: dict[str, tuple[torch.Tensor, torch.Tensor]] | None = None @auto_docstring( custom_intro=""" Base class for Gemma4Unified causal language model (or autoregressive) outputs. """ ) @dataclass class Gemma4UnifiedCausalLMOutputWithPast(ModelOutput): r""" loss (`torch.FloatTensor` of shape `(1,)`, *optional*, returned when `labels` is provided): Language modeling loss (for next-token prediction). logits (`torch.FloatTensor` of shape `(batch_size, sequence_length, config.text_config.vocab_size)`): Prediction scores of the language modeling head (scores for each vocabulary token before SoftMax). past_key_values (`Cache`, *optional*, returned when `use_cache=True` is passed or when `config.use_cache=True`): It is a [`~cache_utils.Cache`] instance. For more details, see our [kv cache guide](https://huggingface.co/docs/transformers/en/kv_cache). Contains pre-computed hidden-states (key and values in the self-attention blocks) that can be used (see `past_key_values` input) to speed up sequential decoding. image_hidden_states (`torch.FloatTensor`, *optional*): A `torch.FloatTensor` of size `(batch_size, num_images, sequence_length, hidden_size)`. image_hidden_states of the model produced by the vision encoder after projecting last hidden state. audio_hidden_states (`torch.FloatTensor`, *optional*): A `torch.FloatTensor` of size `(batch_size, num_images, sequence_length, hidden_size)`. audio_hidden_states of the model produced by the audio encoder and after projecting the last hidden state. shared_kv_states (`dict`, *optional*): Dictionary mapping layer type strings to tuples of (key_states, value_states) tensors. Used to pass shared KV states between layers during KV sharing. """ loss: torch.FloatTensor | None = None logits: torch.FloatTensor | None = None past_key_values: Cache | None = None hidden_states: tuple[torch.FloatTensor] | None = None attentions: tuple[torch.FloatTensor] | None = None image_hidden_states: torch.FloatTensor | None = None audio_hidden_states: torch.FloatTensor | None = None shared_kv_states: dict[str, tuple[torch.Tensor, torch.Tensor]] | None = None @auto_docstring( custom_intro=""" Base class for Gemma4Unified outputs, with hidden states and attentions. """ ) @dataclass class Gemma4UnifiedModelOutputWithPast(BaseModelOutputWithPast): r""" past_key_values (`Cache`, *optional*, returned when `use_cache=True` is passed or when `config.use_cache=True`): It is a [`~cache_utils.Cache`] instance. For more details, see our [kv cache guide](https://huggingface.co/docs/transformers/en/kv_cache). Contains pre-computed hidden-states (key and values in the self-attention blocks) that can be used (see `past_key_values` input) to speed up sequential decoding. image_hidden_states (`torch.FloatTensor`, *optional*): A `torch.FloatTensor` of size `(batch_size, num_images, sequence_length, hidden_size)`. image_hidden_states of the model produced by the vision encoder and after projecting the last hidden state. audio_hidden_states (`torch.FloatTensor`, *optional*): A `torch.FloatTensor` of size `(batch_size, num_images, sequence_length, hidden_size)`. audio_hidden_states of the model produced by the audio encoder and after projecting the last hidden state. shared_kv_states (`dict`, *optional*): Dictionary mapping layer type strings to tuples of (key_states, value_states) tensors. Used to pass shared KV states between layers during KV sharing. """ image_hidden_states: torch.FloatTensor | None = None audio_hidden_states: torch.FloatTensor | None = None shared_kv_states: dict[str, tuple[torch.Tensor, torch.Tensor]] | None = None class Gemma4UnifiedRMSNorm(nn.Module): def __init__(self, dim: int, eps: float = 1e-6, with_scale: bool = True): super().__init__() self.eps = eps self.with_scale = with_scale if self.with_scale: self.weight = nn.Parameter(torch.ones(dim), requires_grad=True) def _norm(self, hidden_states: torch.Tensor): mean_squared = hidden_states.pow(2).mean(-1, keepdim=True) + self.eps # Use torch.pow() (over torch.sqrt() or torch.rsqrt()) to addess compiler differences between Torch and JAX return hidden_states * torch.pow(mean_squared, -0.5) def forward(self, hidden_states: torch.Tensor) -> torch.Tensor: normed_output = self._norm(hidden_states.float()) if self.with_scale: normed_output = normed_output * self.weight.float() return normed_output.type_as(hidden_states) class Gemma4UnifiedTextRotaryEmbedding(nn.Module): inv_freq: torch.Tensor # fix linting for `register_buffer` def __init__(self, config: Gemma4UnifiedTextConfig, device=None, layer_type=None): super().__init__() self.max_seq_len_cached = config.max_position_embeddings self.original_max_seq_len = config.max_position_embeddings self.config = config self.layer_types = set(config.layer_types) self.rope_init_fns: dict[str, Callable[..., tuple[torch.Tensor, float]]] = {} self.rope_type: dict[str, str] = {} for layer_type in self.layer_types: rope_params = self.config.rope_parameters[layer_type] if rope_params is None: continue if (rope_type := rope_params["rope_type"]) != "default": rope_init_fn = ROPE_INIT_FUNCTIONS[rope_type] else: rope_init_fn = self.compute_default_rope_parameters self.rope_init_fns[layer_type] = rope_init_fn self.rope_type[layer_type] = rope_type rope_init_fn_kwargs = {"device": device, "layer_type": layer_type} if layer_type == "full_attention" and rope_type == "proportional": rope_init_fn_kwargs["head_dim_key"] = "global_head_dim" curr_inv_freq, curr_attention_scaling = rope_init_fn(self.config, **rope_init_fn_kwargs) self.register_buffer(f"{layer_type}_inv_freq", curr_inv_freq, persistent=False) self.register_buffer(f"{layer_type}_original_inv_freq", curr_inv_freq.clone(), persistent=False) setattr(self, f"{layer_type}_attention_scaling", curr_attention_scaling) @staticmethod def compute_default_rope_parameters( config: Gemma4UnifiedTextConfig | None = None, device: Optional["torch.device"] = None, seq_len: int | None = None, layer_type: str | None = None, ) -> tuple["torch.Tensor", float]: """ Computes the inverse frequencies according to the original RoPE implementation Args: config ([`~transformers.PreTrainedConfig`]): The model configuration. device (`torch.device`): The device to use for initialization of the inverse frequencies. seq_len (`int`, *optional*): The current sequence length. Unused for this type of RoPE. layer_type (`str`, *optional*): The current layer type if the model has different RoPE parameters per type. Should not be used unless `config.layer_types is not None` Returns: Tuple of (`torch.Tensor`, `float`), containing the inverse frequencies for the RoPE embeddings and the post-processing scaling factor applied to the computed cos/sin (unused in this type of RoPE). """ # For backward compatibility standardize the `rope_parameters_dict` if it uses old format base = config.rope_parameters[layer_type]["rope_theta"] dim = getattr(config, "head_dim", None) or config.hidden_size // config.num_attention_heads attention_factor = 1.0 # Unused in this type of RoPE # Compute the inverse frequencies inv_freq = 1.0 / ( base ** (torch.arange(0, dim, 2, dtype=torch.int64).to(device=device, dtype=torch.float) / dim) ) return inv_freq, attention_factor @torch.no_grad() @dynamic_rope_update # power user: used with advanced RoPE types (e.g. dynamic rope) def forward(self, x, position_ids, layer_type=None): inv_freq = getattr(self, f"{layer_type}_inv_freq") attention_scaling = getattr(self, f"{layer_type}_attention_scaling") inv_freq_expanded = inv_freq[None, :, None].float().expand(position_ids.shape[0], -1, 1).to(x.device) position_ids_expanded = position_ids[:, None, :].float() device_type = x.device.type if isinstance(x.device.type, str) and x.device.type != "mps" else "cpu" with maybe_autocast(device_type=device_type, enabled=False): # Force float32 freqs = (inv_freq_expanded.float() @ position_ids_expanded.float()).transpose(1, 2) emb = torch.cat((freqs, freqs), dim=-1) cos = emb.cos() * attention_scaling sin = emb.sin() * attention_scaling return cos.to(dtype=x.dtype), sin.to(dtype=x.dtype) def rotate_half(x): """Rotates half the hidden dims of the input.""" x1 = x[..., : x.shape[-1] // 2] x2 = x[..., x.shape[-1] // 2 :] return torch.cat((-x2, x1), dim=-1) def apply_rotary_pos_emb(x: torch.Tensor, cos: torch.Tensor, sin: torch.Tensor, unsqueeze_dim: int = 1): """Applies Rotary Position Embedding to the query and key tensors. Args: x (`torch.Tensor`): The tensor to embed. cos (`torch.Tensor`): The cosine part of the rotary embedding. sin (`torch.Tensor`): The sine part of the rotary embedding. unsqueeze_dim (`int`, *optional*, defaults to 1): The 'unsqueeze_dim' argument specifies the dimension along which to unsqueeze cos[position_ids] and sin[position_ids] so that they can be properly broadcasted to the dimensions of q and k. For example, note that cos[position_ids] and sin[position_ids] have the shape [batch_size, seq_len, head_dim]. Then, if q and k have the shape [batch_size, heads, seq_len, head_dim], then setting unsqueeze_dim=1 makes cos[position_ids] and sin[position_ids] broadcastable to the shapes of q and k. Similarly, if q and k have the shape [batch_size, seq_len, heads, head_dim], then set unsqueeze_dim=2. Returns: `tuple(torch.Tensor)` comprising of the query and key tensors rotated using the Rotary Position Embedding. """ cos = cos.unsqueeze(unsqueeze_dim) sin = sin.unsqueeze(unsqueeze_dim) return (x * cos) + (rotate_half(x) * sin) def repeat_kv(hidden_states: torch.Tensor, n_rep: int) -> torch.Tensor: """ This is the equivalent of torch.repeat_interleave(x, dim=1, repeats=n_rep). The hidden states go from (batch, num_key_value_heads, seqlen, head_dim) to (batch, num_attention_heads, seqlen, head_dim) """ batch, num_key_value_heads, slen, head_dim = hidden_states.shape if n_rep == 1: return hidden_states hidden_states = hidden_states[:, :, None, :, :].expand(batch, num_key_value_heads, n_rep, slen, head_dim) return hidden_states.reshape(batch, num_key_value_heads * n_rep, slen, head_dim) def eager_attention_forward( module: nn.Module, query: torch.Tensor, key: torch.Tensor, value: torch.Tensor, attention_mask: torch.Tensor | None, dropout: float | int = 0.0, scaling: float | None = None, softcap: float | None = None, **kwargs, ) -> tuple[torch.Tensor, torch.Tensor]: if scaling is None: scaling = module.head_dim**-0.5 key_states = repeat_kv(key, module.num_key_value_groups) value_states = repeat_kv(value, module.num_key_value_groups) attn_weights = torch.matmul(query, key_states.transpose(2, 3)) * scaling if softcap is not None: attn_weights = attn_weights / softcap attn_weights = torch.tanh(attn_weights) attn_weights = attn_weights * softcap if attention_mask is not None: attn_weights = attn_weights + attention_mask # upcast attention to fp32 attn_weights = nn.functional.softmax(attn_weights, dim=-1, dtype=torch.float32).to(query.dtype) attn_weights = nn.functional.dropout(attn_weights, p=dropout, training=module.training) attn_output = torch.matmul(attn_weights, value_states) attn_output = attn_output.transpose(1, 2).contiguous() return attn_output, attn_weights class Gemma4UnifiedTextAttention(nn.Module): """Multi-headed attention from 'Attention Is All You Need' paper""" def __init__(self, config: Gemma4UnifiedTextConfig, layer_idx: int): super().__init__() self.layer_type = config.layer_types[layer_idx] if hasattr(config, "layer_types") else None self.config = config self.layer_idx = layer_idx self.is_sliding = self.layer_type == "sliding_attention" self.sliding_window = config.sliding_window if self.is_sliding else None self.head_dim = config.global_head_dim if not self.is_sliding and config.global_head_dim else config.head_dim self.use_alternative_attention = config.attention_k_eq_v and not self.is_sliding num_key_value_heads = ( config.num_global_key_value_heads if self.use_alternative_attention else config.num_key_value_heads ) self.num_key_value_groups = config.num_attention_heads // num_key_value_heads self.scaling = 1.0 self.attention_dropout = self.config.attention_dropout self.is_causal = config.use_bidirectional_attention != "all" # Shared kv cache first_kv_shared_layer_idx = self.config.num_hidden_layers - getattr(self.config, "num_kv_shared_layers", 0) self.is_kv_shared_layer = layer_idx >= first_kv_shared_layer_idx >= 0 prev_layers = config.layer_types[:first_kv_shared_layer_idx] self.store_full_length_kv = not self.is_kv_shared_layer and layer_idx == len(prev_layers) - 1 - prev_layers[ ::-1 ].index(config.layer_types[layer_idx]) self.q_proj = nn.Linear( config.hidden_size, config.num_attention_heads * self.head_dim, bias=config.attention_bias ) self.q_norm = Gemma4UnifiedRMSNorm(dim=self.head_dim, eps=config.rms_norm_eps) # Layers sharing kv states don't need any weight matrices if not self.is_kv_shared_layer: self.k_norm = Gemma4UnifiedRMSNorm(dim=self.head_dim, eps=config.rms_norm_eps) self.v_norm = Gemma4UnifiedRMSNorm(self.head_dim, eps=config.rms_norm_eps, with_scale=False) self.k_proj = nn.Linear( config.hidden_size, num_key_value_heads * self.head_dim, bias=config.attention_bias ) self.v_proj = ( nn.Linear(config.hidden_size, num_key_value_heads * self.head_dim, bias=config.attention_bias) if not self.use_alternative_attention else None ) self.o_proj = nn.Linear( config.num_attention_heads * self.head_dim, config.hidden_size, bias=config.attention_bias ) def forward( self, hidden_states: torch.Tensor, position_embeddings: torch.Tensor, attention_mask: torch.Tensor | None, shared_kv_states: dict[str, tuple[torch.Tensor, torch.Tensor]], past_key_values: Cache | None = None, **kwargs: Unpack[FlashAttentionKwargs], ) -> tuple[torch.Tensor, torch.Tensor | None]: input_shape = hidden_states.shape[:-1] hidden_shape = (*input_shape, -1, self.head_dim) cos, sin = position_embeddings query_states = self.q_proj(hidden_states).view(hidden_shape) query_states = self.q_norm(query_states) query_states = apply_rotary_pos_emb(query_states, cos, sin, unsqueeze_dim=2) query_states = query_states.transpose(1, 2) # For layers with shared KV (from kv sharing point onwards), we reuse the same keys/values states as the last non-sharing layer. # We cannot simply reuse the cached state if we have a Cache, as sliding layers will not remember the full states in their Cache # once we are past the sliding window - so we always use `shared_kv_states` instead, even when past_key_values is not None if self.is_kv_shared_layer: key_states, value_states = shared_kv_states[self.layer_type] # Device of past layer may be different from current one key_states = key_states.to(query_states.device) value_states = value_states.to(query_states.device) else: key_states = self.k_proj(hidden_states).view(hidden_shape) value_states = self.v_proj(hidden_states).view(hidden_shape) if self.v_proj is not None else key_states key_states = self.k_norm(key_states) key_states = apply_rotary_pos_emb(key_states, cos, sin, unsqueeze_dim=2) key_states = key_states.transpose(1, 2) value_states = self.v_norm(value_states) value_states = value_states.transpose(1, 2) if past_key_values is not None and not self.is_kv_shared_layer: key_states, value_states = past_key_values.update(key_states, value_states, self.layer_idx) if self.store_full_length_kv: shared_kv_states[self.layer_type] = key_states, value_states attention_interface: Callable = ALL_ATTENTION_FUNCTIONS.get_interface( self.config._attn_implementation, eager_attention_forward ) attn_output, attn_weights = attention_interface( self, query_states, key_states, value_states, attention_mask, dropout=self.attention_dropout if self.training else 0.0, scaling=self.scaling, sliding_window=self.sliding_window, **kwargs, ) attn_output = attn_output.reshape(*input_shape, -1).contiguous() attn_output = self.o_proj(attn_output) return attn_output, attn_weights class Gemma4UnifiedTextMLP(nn.Module): def __init__(self, config: Gemma4UnifiedTextConfig, layer_idx: int): super().__init__() first_kv_shared_layer_idx = config.num_hidden_layers - config.num_kv_shared_layers is_kv_shared_layer = layer_idx >= first_kv_shared_layer_idx > 0 use_double_wide_mlp = config.use_double_wide_mlp and is_kv_shared_layer self.config = config self.hidden_size = config.hidden_size self.intermediate_size = config.intermediate_size * (2 if use_double_wide_mlp else 1) self.gate_proj = nn.Linear(self.hidden_size, self.intermediate_size, bias=False) self.up_proj = nn.Linear(self.hidden_size, self.intermediate_size, bias=False) self.down_proj = nn.Linear(self.intermediate_size, self.hidden_size, bias=False) self.act_fn = ACT2FN[config.hidden_activation] def forward(self, x): down_proj = self.down_proj(self.act_fn(self.gate_proj(x)) * self.up_proj(x)) return down_proj class Gemma4UnifiedTextDecoderLayer(GradientCheckpointingLayer): def __init__(self, config: Gemma4UnifiedTextConfig | Gemma4UnifiedVisionConfig, layer_idx: int): super().__init__() self.hidden_size = config.hidden_size self.config = config self.self_attn = Gemma4UnifiedTextAttention(config=config, layer_idx=layer_idx) self.mlp = Gemma4UnifiedTextMLP(config, layer_idx) self.input_layernorm = Gemma4UnifiedRMSNorm(config.hidden_size, eps=config.rms_norm_eps) self.post_attention_layernorm = Gemma4UnifiedRMSNorm(config.hidden_size, eps=config.rms_norm_eps) self.pre_feedforward_layernorm = Gemma4UnifiedRMSNorm(config.hidden_size, eps=config.rms_norm_eps) self.post_feedforward_layernorm = Gemma4UnifiedRMSNorm(config.hidden_size, eps=config.rms_norm_eps) self.layer_idx = layer_idx self.register_buffer("layer_scalar", torch.ones(1)) def forward( self, hidden_states: torch.Tensor, shared_kv_states: dict[str, tuple[torch.Tensor, torch.Tensor]] | None = None, position_embeddings: torch.Tensor = None, attention_mask: torch.Tensor | None = None, position_ids: torch.LongTensor | None = None, past_key_values: Cache | None = None, **kwargs, ) -> torch.Tensor: residual = hidden_states hidden_states = self.input_layernorm(hidden_states) # Self Attention hidden_states, _ = self.self_attn( hidden_states=hidden_states, position_embeddings=position_embeddings, attention_mask=attention_mask, shared_kv_states=shared_kv_states, position_ids=position_ids, past_key_values=past_key_values, **kwargs, ) hidden_states = self.post_attention_layernorm(hidden_states) hidden_states = residual + hidden_states # Fully Connected residual = hidden_states hidden_states = self.pre_feedforward_layernorm(hidden_states) hidden_states = self.mlp(hidden_states) hidden_states = self.post_feedforward_layernorm(hidden_states) hidden_states = residual + hidden_states hidden_states *= self.layer_scalar return hidden_states class Gemma4UnifiedTextScaledWordEmbedding(nn.Embedding): """ This module overrides nn.Embeddings' forward by multiplying with embeddings scale. """ def __init__(self, num_embeddings: int, embedding_dim: int, padding_idx: int, embed_scale: float = 1.0): super().__init__(num_embeddings, embedding_dim, padding_idx) self.scalar_embed_scale = embed_scale self.register_buffer("embed_scale", torch.tensor(embed_scale), persistent=False) def forward(self, input_ids: torch.Tensor): return super().forward(input_ids) * self.embed_scale.to(self.weight.dtype) @auto_docstring class Gemma4UnifiedPreTrainedModel(PreTrainedModel): config: Gemma4UnifiedConfig base_model_prefix = "model" supports_gradient_checkpointing = True _no_split_modules = ["Gemma4UnifiedTextDecoderLayer"] _skip_keys_device_placement = ["past_key_values", "shared_kv_states"] _supports_flash_attn = True _supports_sdpa = True _supports_flex_attn = True _can_compile_fullgraph = True _supports_attention_backend = True _can_record_outputs = None # override input_modalities = ("image", "text", "video", "audio") @torch.no_grad() def _init_weights(self, module): super()._init_weights(module) if isinstance(module, Gemma4UnifiedTextRotaryEmbedding): for layer_type, rope_init_fn in module.rope_init_fns.items(): rope_init_fn_kwargs = {"layer_type": layer_type} if layer_type == "full_attention" and module.rope_type[layer_type] == "proportional": rope_init_fn_kwargs["head_dim_key"] = "global_head_dim" curr_inv_freq, _ = rope_init_fn(module.config, **rope_init_fn_kwargs) getattr(module, f"{layer_type}_inv_freq").copy_(curr_inv_freq) getattr(module, f"{layer_type}_original_inv_freq").copy_(curr_inv_freq) elif isinstance(module, Gemma4UnifiedTextScaledWordEmbedding): init.constant_(module.embed_scale, module.scalar_embed_scale) elif isinstance(module, Gemma4UnifiedTextDecoderLayer): init.ones_(module.layer_scalar) elif isinstance(module, Gemma4UnifiedVisionEmbedder): init.normal_(module.pos_embedding, mean=0.0, std=self.config.vision_config.initializer_range) @auto_docstring(custom_intro="The base Gemma 4 unified language model without a language modeling head.") class Gemma4UnifiedTextModel(Gemma4UnifiedPreTrainedModel): config: Gemma4UnifiedTextConfig input_modalities = ("text",) _can_record_outputs = { "hidden_states": Gemma4UnifiedTextDecoderLayer, "attentions": Gemma4UnifiedTextAttention, } def __init__(self, config: Gemma4UnifiedTextConfig): super().__init__(config) self.padding_idx = config.pad_token_id self.vocab_size = config.vocab_size # Gemma4Unified downcasts the below to bfloat16, causing sqrt(3072)=55.4256 to become 55.5. See https://github.com/huggingface/transformers/pull/29402 self.embed_tokens = Gemma4UnifiedTextScaledWordEmbedding( config.vocab_size, config.hidden_size, self.padding_idx, embed_scale=self.config.hidden_size**0.5 ) self.layers = nn.ModuleList( [Gemma4UnifiedTextDecoderLayer(config, layer_idx) for layer_idx in range(config.num_hidden_layers)] ) self.norm = Gemma4UnifiedRMSNorm(config.hidden_size, eps=config.rms_norm_eps) self.rotary_emb = Gemma4UnifiedTextRotaryEmbedding(config=config) self.gradient_checkpointing = False self.unique_layer_types = set(self.config.layer_types) # Initialize weights and apply final processing self.post_init() @merge_with_config_defaults @capture_outputs @auto_docstring def forward( self, input_ids: torch.LongTensor | None = None, attention_mask: torch.Tensor | None = None, position_ids: torch.LongTensor | None = None, past_key_values: Cache | None = None, inputs_embeds: torch.FloatTensor | None = None, use_cache: bool | None = None, **kwargs: Unpack[TransformersKwargs], ) -> Gemma4UnifiedTextModelOutputWithPast: if (input_ids is None) ^ (inputs_embeds is not None): raise ValueError("You must specify exactly one of input_ids or inputs_embeds") if input_ids is not None: inputs_embeds = self.embed_tokens(input_ids) if use_cache and past_key_values is None: past_key_values = DynamicCache(config=self.config) if position_ids is None: past_seen_tokens = past_key_values.get_seq_length() if past_key_values is not None else 0 position_ids = torch.arange(inputs_embeds.shape[1], device=inputs_embeds.device) + past_seen_tokens position_ids = position_ids.unsqueeze(0) # It may already have been prepared by e.g. `generate` if not isinstance(causal_mask_mapping := attention_mask, dict): # Prepare mask arguments mask_kwargs = { "config": self.config, "inputs_embeds": inputs_embeds, "attention_mask": attention_mask, "past_key_values": past_key_values, "position_ids": position_ids, } # Create the masks causal_mask_mapping = { "full_attention": create_causal_mask(**mask_kwargs), "sliding_attention": create_sliding_window_causal_mask(**mask_kwargs), } # embed positions hidden_states = inputs_embeds position_embeddings = {} for layer_type in self.unique_layer_types: position_embeddings[layer_type] = self.rotary_emb(hidden_states, position_ids, layer_type) # Initialize as empty dict, or reuse past shared states. We use a UserDict instead of built-in dict (it behaves # the same) for fsdp2 support (otherwise, `_apply_to_tensors` rebuilds every dict it recurses into, and `shared_kv_states` # is not correctly shared, see https://github.com/pytorch/pytorch/blob/v2.10.0/torch/distributed/utils.py#L223-L255) shared_kv_states = kwargs.pop("shared_kv_states", UserDict()) # decoder layers for i, decoder_layer in enumerate(self.layers[: self.config.num_hidden_layers]): hidden_states = decoder_layer( hidden_states, shared_kv_states=shared_kv_states, position_embeddings=position_embeddings[self.config.layer_types[i]], attention_mask=causal_mask_mapping[self.config.layer_types[i]], position_ids=position_ids, past_key_values=past_key_values, **kwargs, ) hidden_states = self.norm(hidden_states) return Gemma4UnifiedTextModelOutputWithPast( last_hidden_state=hidden_states, past_key_values=past_key_values, shared_kv_states=shared_kv_states if kwargs.get("return_shared_kv_states", False) else None, ) @auto_docstring(custom_intro="The base Gemma 4 language model with a language modeling head.") class Gemma4UnifiedForCausalLM(Gemma4UnifiedPreTrainedModel, GenerationMixin): _tied_weights_keys = {"lm_head.weight": "model.embed_tokens.weight"} _tp_plan = {"lm_head": "colwise_gather_output"} _pp_plan = {"lm_head": (["hidden_states"], ["logits"])} config: Gemma4UnifiedTextConfig base_model_prefix = "model" def __init__(self, config: Gemma4UnifiedTextConfig): super().__init__(config) self.model = Gemma4UnifiedTextModel(config) self.vocab_size = config.vocab_size self.lm_head = nn.Linear(config.hidden_size, config.vocab_size, bias=False) # Initialize weights and apply final processing self.post_init() @can_return_tuple @auto_docstring def forward( self, input_ids: torch.LongTensor | None = None, attention_mask: torch.Tensor | None = None, position_ids: torch.LongTensor | None = None, past_key_values: Cache | None = None, inputs_embeds: torch.FloatTensor | None = None, labels: torch.LongTensor | None = None, use_cache: bool | None = None, logits_to_keep: int | torch.Tensor = 0, **kwargs: Unpack[TransformersKwargs], ) -> Gemma4UnifiedCausalLMOutputWithPast: r""" Example: ```python >>> from transformers import AutoTokenizer, Gemma4UnifiedForCausalLM >>> model = Gemma4UnifiedForCausalLM.from_pretrained("google/gemma-4-12B-it") >>> tokenizer = AutoTokenizer.from_pretrained("google/gemma-4-12B-it") >>> prompt = "What is your favorite condiment?" >>> inputs = tokenizer(prompt, return_tensors="pt") >>> # Generate >>> generate_ids = model.generate(inputs.input_ids, max_length=30) >>> tokenizer.batch_decode(generate_ids, skip_special_tokens=True, clean_up_tokenization_spaces=False)[0] "What is your favorite condiment?" ```""" # decoder outputs consists of (dec_features, layer_state, dec_hidden, dec_attn) outputs: Gemma4UnifiedTextModelOutputWithPast = self.model( input_ids=input_ids, attention_mask=attention_mask, position_ids=position_ids, past_key_values=past_key_values, inputs_embeds=inputs_embeds, use_cache=use_cache, **kwargs, ) hidden_states = outputs.last_hidden_state # Only compute necessary logits, and do not upcast them to float if we are not computing the loss slice_indices = slice(-logits_to_keep, None) if isinstance(logits_to_keep, int) else logits_to_keep logits = self.lm_head(hidden_states[:, slice_indices, :]) if self.config.final_logit_softcapping is not None: logits = logits / self.config.final_logit_softcapping logits = torch.tanh(logits) logits = logits * self.config.final_logit_softcapping loss = None if labels is not None: loss = self.loss_function(logits, labels, self.vocab_size, **kwargs) return Gemma4UnifiedCausalLMOutputWithPast( loss=loss, logits=logits, past_key_values=outputs.past_key_values, hidden_states=outputs.hidden_states, attentions=outputs.attentions, shared_kv_states=outputs.shared_kv_states, ) class Gemma4UnifiedVisionEmbedder(nn.Module): """Encoder-free vision embedder: projects raw merged pixel patches into LM space. This replaces the entire SigLIP-like vision tower. Instead of attention layers, it uses a simple Dense projection with LayerNorm and factorized positional embeddings. Pipeline: raw_patches → LN₁ → Dense → LN₂ → +factorized_posemb → LN₃ → RMSNorm → Linear """ def __init__(self, vision_config: Gemma4UnifiedVisionConfig, text_config: Gemma4UnifiedTextConfig): super().__init__() patch_dim = vision_config.model_patch_size**2 * 3 # 48*48*3 = 6912 mm_embed_dim = vision_config.mm_embed_dim # Patch embedding: LN₁ → Dense → LN₂ # Matches mm_patch_embed_ln1, mm_patch_embed_dense, mm_patch_embed_ln2 in EFEmbedder self.patch_ln1 = nn.LayerNorm(patch_dim) self.patch_dense = nn.Linear(patch_dim, mm_embed_dim) self.patch_ln2 = nn.LayerNorm(mm_embed_dim) # Factorized 2D positional embedding # Matches mm_pos_embedding in EFEmbedder: shape (mm_posemb_size, 2, mm_embed_dim) self.pos_embedding = nn.Parameter(torch.zeros(vision_config.mm_posemb_size, 2, mm_embed_dim)) self.pos_norm = nn.LayerNorm(mm_embed_dim) # Final multimodal projection (same as for audio): RMSNorm → Linear self.multimodal_embedder = Gemma4UnifiedMultimodalEmbedder(vision_config, text_config) def forward( self, pixel_values: torch.Tensor, image_position_ids: torch.Tensor, ) -> torch.Tensor: """ Args: pixel_values: (batch, num_patches, model_patch_size²*3) — raw merged pixel patches. image_position_ids: (batch, num_patches, 2) — integer XY positions (-1 for padding). Returns: (batch, num_patches, mm_embed_dim) — embedded features (including padding positions). """ # Step 1: Patch embedding (LN → Dense → LN) hidden_states = self.patch_ln1(pixel_values.to(self.patch_dense.weight.dtype)) hidden_states = self.patch_dense(hidden_states) hidden_states = self.patch_ln2(hidden_states) # Step 2: Add factorized positional embeddings + LN clamped = image_position_ids.clamp(min=0).long() valid = (image_position_ids != -1).to(self.pos_embedding.dtype).unsqueeze(-1) axes = torch.arange(2, device=image_position_ids.device) pos_embs = (self.pos_embedding[clamped, axes] * valid).sum(-2) hidden_states = hidden_states + pos_embs hidden_states = self.pos_norm(hidden_states) # Step 3: Base multimodal embedder (RMSNorm → Dense) hidden_states = self.multimodal_embedder(hidden_states) return hidden_states class Gemma4UnifiedMultimodalEmbedder(nn.Module): """Embeds token ids or soft tokens for multimodal content into language model space.""" def __init__( self, multimodal_config: Gemma4UnifiedAudioConfig | Gemma4UnifiedVisionConfig, text_config: Gemma4UnifiedTextConfig, ): super().__init__() # Both AudioConfig and VisionConfig define output_proj_dims directly self.multimodal_hidden_size = multimodal_config.output_proj_dims self.eps = multimodal_config.rms_norm_eps self.text_hidden_size = text_config.hidden_size self.embedding_projection = nn.Linear(self.multimodal_hidden_size, self.text_hidden_size, bias=False) self.embedding_pre_projection_norm = Gemma4UnifiedRMSNorm( self.multimodal_hidden_size, eps=self.eps, with_scale=False ) def forward(self, inputs_embeds: torch.Tensor) -> torch.Tensor: """Embeds token ids or soft tokens for multimodal content into language model space. Args: inputs_embeds: A torch.Tensor containing the soft tokens to embed. Returns: A torch.Tensor of embeddings with shape `[batch_size, seq_len, self.config.text_config.hidden_size]`. """ # Additional dtype casting inputs_embeds = inputs_embeds.to(self.embedding_projection.weight.dtype) embs_normed = self.embedding_pre_projection_norm(inputs_embeds) return self.embedding_projection(embs_normed) def get_block_sequence_ids_for_mask(mm_token_type_ids: torch.Tensor, device: torch.device) -> torch.Tensor: mm_token_type_ids = mm_token_type_ids.to(device) is_vision = (mm_token_type_ids == 1) | (mm_token_type_ids == 2) is_prev_vision = torch.roll(is_vision, shifts=1, dims=-1) is_prev_vision[..., 0] = False new_vision_starts = is_vision & ~is_prev_vision vision_group_ids = torch.cumsum(new_vision_starts.int(), dim=1) - 1 block_sequence_ids = torch.where(is_vision, vision_group_ids, -1) return block_sequence_ids @auto_docstring( custom_intro=""" The base Gemma 4 model comprising a vision backbone, an audio backbone, and a language model without a language modeling head. """ ) class Gemma4UnifiedModel(Gemma4UnifiedPreTrainedModel): """Encoder-free multimodal model. This model has **no vision tower** and **no audio tower**. Vision inputs are handled via the `Gemma4UnifiedVisionEmbedder` (raw patches → LN → Dense → LN → +posemb → LN → RMSNorm → Linear) and audio inputs are raw waveform frames projected directly through the multimodal embedder (RMSNorm → Linear). """ # we are filtering the logits/labels so we shouldn't divide the loss based on num_items_in_batch accepts_loss_kwargs = False def __init__(self, config: Gemma4UnifiedConfig): super().__init__(config) self.vocab_size = config.text_config.vocab_size language_model = AutoModel.from_config(config=config.text_config) self.language_model = language_model self.embed_vision = ( Gemma4UnifiedVisionEmbedder(config.vision_config, config.text_config) if config.vision_config is not None else None ) self.embed_audio = ( Gemma4UnifiedMultimodalEmbedder(config.audio_config, config.text_config) if config.audio_config is not None else None ) self.post_init() @can_return_tuple @auto_docstring(custom_intro="Projects raw pixel patches into language model space via the unified pipeline.") def get_image_features( self, pixel_values: torch.FloatTensor, image_position_ids: torch.LongTensor | None = None, **kwargs, ) -> Gemma4UnifiedVisionModelOutput: r""" image_position_ids (`torch.LongTensor` of shape `(batch_size, max_patches, 2)`, *optional*): The patch positions as (x, y) coordinates in the image. Padding patches are indicated by (-1, -1). """ vision_outputs = self.embed_vision(pixel_values, image_position_ids) # Strip padding patches before scattering into text sequence. # Padding patches have position_ids == -1 on both axes. # We only scatter non-padding patches into the placeholder token positions. padding_mask = (image_position_ids == -1).all(dim=-1).to(vision_outputs.device) # (batch, num_patches) # Flatten valid patches: keep only non-padding patches across the batch vision_outputs = vision_outputs[~padding_mask] # (total_valid_patches, text_hidden_size) return Gemma4UnifiedVisionModelOutput( pooler_output=vision_outputs, ) def get_placeholder_mask( self, input_ids: torch.LongTensor | None = None, inputs_embeds: torch.FloatTensor | None = None, ) -> tuple[torch.BoolTensor, torch.BoolTensor, torch.BoolTensor]: """ Obtains mask for multimodal placeholders (replaced by soft tokens) and hard text tokens. Masks will be obtained from `mm_token_type_ids`, `input_ids`, or `inputs_embeds` as available and in that precedence order. If passing `input_ids` or `inputs_embeds`, the image mask will be derived using `config.image_token_id`. Same goes for audio and video masks Args: input_ids: A tensor containing the hard token IDs from the text tokenizer. inputs_embeds: A tensor containing the embeddings for all hard text tokens. Returns: image_mask, video_mask, audio_mask """ if input_ids is not None: special_image_mask = input_ids == self.config.image_token_id special_video_mask = input_ids == self.config.video_token_id special_audio_mask = input_ids == self.config.audio_token_id else: special_image_mask = ( inputs_embeds == self.get_input_embeddings()( torch.tensor(self.config.image_token_id, dtype=torch.long, device=inputs_embeds.device) ) ).all(-1) special_video_mask = ( inputs_embeds == self.get_input_embeddings()( torch.tensor(self.config.video_token_id, dtype=torch.long, device=inputs_embeds.device) ) ).all(-1) special_audio_mask = ( inputs_embeds == self.get_input_embeddings()( torch.tensor(self.config.audio_token_id, dtype=torch.long, device=inputs_embeds.device) ) ).all(-1) return special_image_mask, special_video_mask, special_audio_mask @merge_with_config_defaults @can_return_tuple @auto_docstring def forward( self, input_ids: torch.LongTensor | None = None, pixel_values: torch.FloatTensor | None = None, pixel_values_videos: torch.FloatTensor | None = None, input_features: torch.FloatTensor | None = None, attention_mask: torch.Tensor | None = None, input_features_mask: torch.Tensor | None = None, position_ids: torch.LongTensor | None = None, past_key_values: Cache | None = None, mm_token_type_ids: torch.LongTensor | None = None, inputs_embeds: torch.FloatTensor | None = None, use_cache: bool | None = None, image_position_ids: torch.LongTensor | None = None, video_position_ids: torch.LongTensor | None = None, **kwargs: Unpack[TransformersKwargs], ) -> Gemma4UnifiedModelOutputWithPast: r""" input_features_mask (`torch.FloatTensor]` of shape `(num_images, seq_length)`): The attention mask for the input audio. image_position_ids (`torch.LongTensor` of shape `(batch_size, max_patches, 2)`, *optional*): 2D patch position coordinates from the image processor, with `(-1, -1)` indicating padding. Passed through to the vision encoder for positional embedding computation. video_position_ids (`torch.LongTensor` of shape `(num_videos, num_frames, max_patches, 2)`, *optional*): 2D patch position coordinates from the video processor, with `(-1, -1)` indicating padding. Passed through to the vision encoder for positional embedding computation. """ if (input_ids is None) ^ (inputs_embeds is not None): raise ValueError("You must specify exactly one of input_ids or inputs_embeds") image_mask, video_mask, audio_mask = self.get_placeholder_mask(input_ids, inputs_embeds) multimodal_mask = image_mask | video_mask | audio_mask # Replace image id with PAD if the image token if OOV, to avoid index-errors llm_input_ids = None if inputs_embeds is None: llm_input_ids = input_ids.clone() llm_input_ids = torch.where(multimodal_mask, self.config.text_config.pad_token_id, llm_input_ids) inputs_embeds = self.get_input_embeddings()(llm_input_ids) # Merge text and images if pixel_values is not None: image_features = self.get_image_features(pixel_values, image_position_ids, return_dict=True).pooler_output image_features = image_features.to(inputs_embeds.device, inputs_embeds.dtype) # Confirm the number of soft tokens from the vision tower matches the number of slots in the embeddings. n_image_tokens = image_mask.sum() image_mask = image_mask.unsqueeze(-1).expand_as(inputs_embeds).to(inputs_embeds.device) torch_compilable_check( inputs_embeds[image_mask].numel() == image_features.numel(), f"Image features and image tokens do not match, tokens: {n_image_tokens}, features:" f" {image_features.shape[0]}", ) inputs_embeds = inputs_embeds.masked_scatter( image_mask.to(inputs_embeds.device), image_features.to(inputs_embeds.device) ) if pixel_values_videos is not None: video_features = self.get_video_features( pixel_values_videos, video_position_ids, return_dict=True ).pooler_output video_features = video_features.to(inputs_embeds.device, inputs_embeds.dtype) # Confirm the number of soft tokens from the vision tower matches the number of slots in the embeddings. n_video_tokens = video_mask.sum() video_mask = video_mask.unsqueeze(-1).expand_as(inputs_embeds).to(inputs_embeds.device) torch_compilable_check( inputs_embeds[video_mask].numel() == video_features.numel(), f"Video features and video tokens do not match, tokens: {n_video_tokens}, features:" f" {video_features.shape[0]}", ) inputs_embeds = inputs_embeds.masked_scatter( video_mask.to(inputs_embeds.device), video_features.to(inputs_embeds.device) ) # Merge text and audio if input_features is not None and input_features_mask is not None: audio_output = self.get_audio_features(input_features, input_features_mask, return_dict=True) audio_features = audio_output.pooler_output audio_mask_from_encoder = audio_output.attention_mask # True = valid # Strip padding tokens: only keep real (non-padding) audio soft tokens. # audio_mask_from_encoder is True for valid positions, False for padding tokens. # This mirrors the vision encoder's padding stripping (see Gemma4UnifiedVisionEncoder.forward). audio_features = audio_features[audio_mask_from_encoder.to(audio_features.device)] n_audio_tokens = audio_mask.sum() audio_mask = audio_mask.unsqueeze(-1).expand_as(inputs_embeds).to(inputs_embeds.device) torch_compilable_check( inputs_embeds[audio_mask].numel() == audio_features.numel(), f"Audio features and audio tokens do not match, tokens: {n_audio_tokens}, features:" f" {audio_features.shape[0] * audio_features.shape[1]}", ) inputs_embeds = inputs_embeds.masked_scatter( audio_mask.to(inputs_embeds.device), audio_features.to(inputs_embeds.device) ) # It may already have been prepared by, e.g., `generate` if position_ids is None: past_seen_tokens = past_key_values.get_seq_length() if past_key_values is not None else 0 position_ids = torch.arange(inputs_embeds.shape[1], device=inputs_embeds.device) + past_seen_tokens position_ids = position_ids.unsqueeze(0) if not isinstance(causal_mask_mapping := attention_mask, dict): mask_kwargs = { "config": self.config.get_text_config(), "inputs_embeds": inputs_embeds, "attention_mask": attention_mask, "past_key_values": past_key_values, "position_ids": position_ids, } # Larger Gemma 4 models use Gemma 3's bidirectional attention mask for vision inputs # Smaller Gemma models use a conventional casual attention mask if self.config.get_text_config().use_bidirectional_attention == "vision": block_sequence_ids = torch.full([*inputs_embeds.size()[:-1]], -1, device=inputs_embeds.device) if mm_token_type_ids is not None: block_sequence_ids = get_block_sequence_ids_for_mask( mm_token_type_ids, device=inputs_embeds.device ) mask_kwargs["block_sequence_ids"] = block_sequence_ids # Create the masks causal_mask_mapping = create_masks_for_generate(**mask_kwargs) outputs = self.language_model( attention_mask=causal_mask_mapping, position_ids=position_ids, past_key_values=past_key_values, inputs_embeds=inputs_embeds, use_cache=use_cache, return_dict=True, **kwargs, ) return Gemma4UnifiedModelOutputWithPast( last_hidden_state=outputs.last_hidden_state, past_key_values=outputs.past_key_values, hidden_states=outputs.hidden_states, attentions=outputs.attentions, image_hidden_states=image_features if pixel_values is not None else None, audio_hidden_states=audio_features if input_features is not None else None, shared_kv_states=outputs.shared_kv_states, ) @can_return_tuple @auto_docstring( custom_intro="Projects raw audio waveform features directly into language model space (no audio tower)." ) def get_audio_features( self, input_features: torch.Tensor, input_features_mask: torch.Tensor | None = None, **kwargs, ) -> Gemma4UnifiedAudioModelOutput: r""" input_features (`torch.FloatTensor` of shape `(num_audios, num_tokens, audio_embed_dim)`): Raw waveform features. Each token is a frame of `audio_samples_per_token` (640) raw audio samples. For unified models this replaces the mel spectrogram + conformer pipeline. input_features_mask (`torch.BoolTensor` of shape `(num_audios, num_tokens)`, *optional*): Mask indicating valid (non-padding) audio tokens. `True` = valid, `False` = padding. """ if self.embed_audio is None: raise ValueError( "Audio features were requested, but the model was initialized without an audio_config. " "Cannot process audio without an audio embedder." ) audio_outputs = self.embed_audio(inputs_embeds=input_features) return Gemma4UnifiedAudioModelOutput( pooler_output=audio_outputs, attention_mask=input_features_mask.to(audio_outputs.device) if input_features_mask is not None else None, ) @can_return_tuple @auto_docstring(custom_intro="Projects video frames into language model space via the unified pipeline.") def get_video_features( self, pixel_values_videos: torch.FloatTensor, video_position_ids: torch.LongTensor | None = None, **kwargs, ) -> Gemma4UnifiedVisionModelOutput: r""" video_position_ids (`torch.LongTensor` of shape `(num_videos, num_frames, max_patches, 2)`, *optional*): 2D patch position coordinates from the video processor, with `(-1, -1)` indicating padding. """ # Flatten video frames: (num_videos, num_frames, ...) → (num_videos*num_frames, ...) pixel_values_videos = pixel_values_videos.flatten(0, 1) video_position_ids = video_position_ids.flatten(0, 1) # Use the same unified pipeline as images return self.get_image_features(pixel_values_videos, video_position_ids, **kwargs) @auto_docstring( custom_intro=""" The base Gemma 4 model comprising a vision backbone, an audio backbone, a language model, and a language modeling head. """ ) class Gemma4UnifiedForConditionalGeneration(Gemma4UnifiedPreTrainedModel, GenerationMixin): _tied_weights_keys = {"lm_head.weight": "model.language_model.embed_tokens.weight"} accepts_loss_kwargs = False base_model_prefix = "model" def __init__(self, config: Gemma4UnifiedConfig): super().__init__(config) self.model = Gemma4UnifiedModel(config) self.lm_head = nn.Linear(config.text_config.hidden_size, config.text_config.vocab_size, bias=False) self.post_init() @auto_docstring def get_image_features( self, pixel_values: torch.FloatTensor, image_position_ids: torch.LongTensor | None = None, **kwargs: Unpack[TransformersKwargs], ): r""" image_position_ids (`torch.LongTensor` of shape `(batch_size, max_patches, 2)`, *optional*): 2D patch position coordinates from the image processor, with `(-1, -1)` indicating padding. Passed through to the vision encoder for positional embedding computation. """ return self.model.get_image_features(pixel_values, image_position_ids, **kwargs) @can_return_tuple @auto_docstring def forward( self, input_ids: torch.LongTensor | None = None, pixel_values: torch.FloatTensor | None = None, pixel_values_videos: torch.FloatTensor | None = None, input_features: torch.FloatTensor | None = None, attention_mask: torch.Tensor | None = None, input_features_mask: torch.Tensor | None = None, position_ids: torch.LongTensor | None = None, image_position_ids: torch.LongTensor | None = None, video_position_ids: torch.LongTensor | None = None, past_key_values: Cache | None = None, mm_token_type_ids: torch.LongTensor | None = None, inputs_embeds: torch.FloatTensor | None = None, labels: torch.LongTensor | None = None, use_cache: bool | None = None, logits_to_keep: int | torch.Tensor = 0, **kwargs: Unpack[TransformersKwargs], ) -> Gemma4UnifiedCausalLMOutputWithPast: r""" input_features_mask (`torch.FloatTensor]` of shape `(num_images, seq_length)`): The attention mask for the input audio. image_position_ids (`torch.LongTensor` of shape `(batch_size, max_patches, 2)`, *optional*): 2D patch position coordinates from the image processor, with `(-1, -1)` indicating padding. Passed through to the vision encoder for positional embedding computation. video_position_ids (`torch.LongTensor` of shape `(num_videos, num_frames, max_patches, 2)`, *optional*): 2D patch position coordinates from the video processor, with `(-1, -1)` indicating padding. Passed through to the vision encoder for positional embedding computation. """ outputs = self.model( input_ids=input_ids, pixel_values=pixel_values, pixel_values_videos=pixel_values_videos, input_features=input_features, attention_mask=attention_mask, input_features_mask=input_features_mask, position_ids=position_ids, past_key_values=past_key_values, mm_token_type_ids=mm_token_type_ids, inputs_embeds=inputs_embeds, labels=labels, use_cache=use_cache, image_position_ids=image_position_ids, video_position_ids=video_position_ids, return_dict=True, **kwargs, ) hidden_states = outputs.last_hidden_state # Only compute necessary logits, and do not upcast them to float if we are not computing the loss slice_indices = slice(-logits_to_keep, None) if isinstance(logits_to_keep, int) else logits_to_keep logits = self.lm_head(hidden_states[:, slice_indices, :]) if (final_logit_softcapping := self.config.get_text_config().final_logit_softcapping) is not None: logits = logits / final_logit_softcapping logits = torch.tanh(logits) logits = logits * final_logit_softcapping loss = None if labels is not None: loss = self.loss_function(logits, labels, self.config.get_text_config().vocab_size, **kwargs) return Gemma4UnifiedCausalLMOutputWithPast( loss=loss, logits=logits, past_key_values=outputs.past_key_values, hidden_states=outputs.hidden_states, attentions=outputs.attentions, image_hidden_states=outputs.image_hidden_states, audio_hidden_states=outputs.audio_hidden_states, shared_kv_states=outputs.shared_kv_states, ) def prepare_inputs_for_generation( self, input_ids, past_key_values=None, inputs_embeds=None, position_ids=None, pixel_values=None, pixel_values_videos=None, input_features=None, attention_mask=None, input_features_mask=None, token_type_ids=None, use_cache=True, logits_to_keep=None, labels=None, is_first_iteration=False, **kwargs, ): # Overwritten -- custom `position_ids` and `pixel_values` handling model_inputs = super().prepare_inputs_for_generation( input_ids, past_key_values=past_key_values, inputs_embeds=inputs_embeds, attention_mask=attention_mask, position_ids=position_ids, use_cache=use_cache, logits_to_keep=logits_to_keep, token_type_ids=token_type_ids, is_first_iteration=is_first_iteration, **kwargs, ) # If we're in cached decoding stage, multimodal inputs are already cached and can be dropped if is_first_iteration or not use_cache: model_inputs["pixel_values"] = pixel_values model_inputs["pixel_values_videos"] = pixel_values_videos model_inputs["input_features"] = input_features model_inputs["input_features_mask"] = input_features_mask else: # Don't pass to not apply bidirectional mask on top model_inputs["mm_token_type_ids"] = None return model_inputs @staticmethod def create_masks_for_generate( config: PreTrainedConfig, inputs_embeds: torch.Tensor, attention_mask: torch.Tensor | None, past_key_values: Cache | None, position_ids: torch.Tensor | None, mm_token_type_ids: torch.Tensor | None = None, is_first_iteration: bool | None = False, **kwargs, ) -> dict: mask_kwargs = { "config": config.get_text_config(), "inputs_embeds": inputs_embeds, "attention_mask": attention_mask, "past_key_values": past_key_values, "position_ids": position_ids, } # Larger Gemma 4 models use Gemma 3's bidirectional attention mask for vision inputs # Smaller Gemma models use a conventional casual attention mask if getattr(config.get_text_config(), "use_bidirectional_attention", None) == "vision": block_sequence_ids = torch.full([*inputs_embeds.size()[:-1]], -1, device=inputs_embeds.device) if mm_token_type_ids is not None: block_sequence_ids = get_block_sequence_ids_for_mask(mm_token_type_ids, device=inputs_embeds.device) mask_kwargs["block_sequence_ids"] = block_sequence_ids return create_masks_for_generate(**mask_kwargs) __all__ = [ "Gemma4UnifiedForCausalLM", "Gemma4UnifiedForConditionalGeneration", "Gemma4UnifiedModel", "Gemma4UnifiedPreTrainedModel", "Gemma4UnifiedTextModel", ]