| """ |
| 2026.4.9 |
| 2026.4.8 |
| 5.5.0 |
| 0.24.0 |
| __UNSLOTH_VERSIONING__ |
| """ |
|
|
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
|
|
|
|
| import os |
| import sys |
| import torch |
| import importlib.util |
| import math |
| if importlib.util.find_spec("unsloth_studio") is None: |
| UNSLOTH_STUDIO_ENABLED = False |
| else: |
| UNSLOTH_STUDIO_ENABLED = os.environ.get("UNSLOTH_STUDIO_DISABLED", "0") == "0" |
| pass |
| from typing import Any, List, Optional, Tuple, Union, Dict, Set, Callable |
| import math |
|
|
| UNSLOTH_ENABLE_LOGGING = os.environ.get("UNSLOTH_ENABLE_LOGGING", "0") == "1" |
| UNSLOTH_ENABLE_CCE = os.environ.get("UNSLOTH_ENABLE_CCE", "1") == "1" |
| UNSLOTH_COMPILE_DISABLE = os.environ.get("UNSLOTH_COMPILE_DISABLE", "0") in ("1", "partial",) |
| UNSLOTH_COMPILE_LOCATION = os.environ.get("UNSLOTH_COMPILE_LOCATION", "unsloth_compiled_cache") |
| if UNSLOTH_COMPILE_LOCATION not in sys.path: |
| sys.path.insert(0, UNSLOTH_COMPILE_LOCATION) |
|
|
| import logging |
| logger_compiler = logging.getLogger(__name__) |
| if UNSLOTH_ENABLE_LOGGING: |
| logger_compiler.setLevel(logging.DEBUG) |
|
|
| global INFERENCE_RUNS |
| INFERENCE_RUNS = 0 |
|
|
| try: |
| import torch._dynamo.eval_frame as torch_dynamo_eval_frame |
| torch_dynamo_eval_frame._stance.stance |
| torch_compiler_set_stance = torch.compiler.set_stance |
| except: |
| torch_dynamo_eval_frame = None |
| torch_compiler_set_stance = None |
| pass |
|
|
| from unsloth_zoo import DEVICE_TYPE_TORCH, DEVICE_COUNT |
|
|
|
|
| from unsloth_zoo.loss_utils import ( |
| fused_linear_cross_entropy, |
| unsloth_fused_ce_loss, |
| ) |
|
|
| scaled_dot_product_attention = torch.nn.functional.scaled_dot_product_attention |
| @torch.compiler.disable(recursive = False) |
| def disable_compile_scaled_dot_product_attention(*args, **kwargs): |
| return scaled_dot_product_attention(*args, **kwargs) |
| pass |
|
|
|
|
| from transformers.modeling_flash_attention_utils import is_flash_attn_available |
|
|
| if is_flash_attn_available(): |
| try: |
| from transformers.modeling_flash_attention_utils import flash_attn_supports_top_left_mask |
| except: |
| flash_attn_supports_top_left_mask = None |
| try: |
| from transformers.modeling_flash_attention_utils import _flash_attention_forward |
| except: |
| _flash_attention_forward = None |
| try: |
| from transformers.modeling_flash_attention_utils import FlashAttentionKwargs |
| except: |
| FlashAttentionKwargs = None |
| try: |
| from transformers.modeling_flash_attention_utils import flash_attn_varlen_func |
| except: |
| flash_attn_varlen_func = None |
| else: |
| flash_attn_supports_top_left_mask = None |
| _flash_attention_forward = None |
| FlashAttentionKwargs = None |
| flash_attn_varlen_func = None |
| pass |
|
|
|
|
| torch_compile_options = {'epilogue_fusion': True, 'max_autotune': False, 'shape_padding': True, 'trace.enabled': False, 'triton.cudagraphs': False, 'debug': False, 'dce': True, 'memory_planning': True, 'coordinate_descent_tuning': False, 'trace.graph_diagram': False, 'compile_threads': 32, 'group_fusion': True, 'disable_progress': True, 'verbose_progress': False, 'triton.multi_kernel': 0, 'triton.use_block_ptr': False, 'triton.enable_persistent_tma_matmul': True, 'triton.autotune_at_compile_time': False, 'triton.cooperative_reductions': False, 'cuda.compile_opt_level': '-O2', 'cuda.enable_cuda_lto': True, 'combo_kernels': False, 'benchmark_combo_kernel': True, 'combo_kernel_foreach_dynamic_shapes': True} |
|
|
| from torch.nn import CrossEntropyLoss |
|
|
| @torch.compile(fullgraph = True, dynamic = True, options = torch_compile_options) |
| def normal_cross_entropy_loss(self, hidden_states, labels): |
| logits = self.lm_head(hidden_states) |
| logits = logits.float() |
| |
| shift_logits = logits[..., :-1, :].contiguous() |
| shift_labels = labels[..., 1:].contiguous() |
| |
| loss_fct = CrossEntropyLoss() |
| shift_logits = shift_logits.view(-1, self.config.vocab_size) |
| shift_labels = shift_labels.view(-1) |
| |
| shift_labels = shift_labels.to(shift_logits.device) |
| loss = loss_fct(shift_logits, shift_labels) |
| return loss, logits |
| pass |
|
|
| |
| |
| LOGITS_ERROR_STRING = \ |
| "Unsloth: Logits are empty from 2024.11 onwards. To get raw logits again, please "\ |
| 'set the environment variable `UNSLOTH_RETURN_LOGITS` to `"1" BEFORE starting to train ie before `trainer.train()`. For example:\n'\ |
| "```\nimport os\n"\ |
| "os.environ['UNSLOTH_RETURN_LOGITS'] = '1'\n"\ |
| "trainer.train()\n```\n"\ |
| "No need to restart your console - just add `os.environ['UNSLOTH_RETURN_LOGITS'] = '1'` before trainer.train() and re-run the cell!" |
|
|
| def raise_logits_error(*args, **kwargs): raise NotImplementedError(LOGITS_ERROR_STRING) |
| def return_none(*args, **kwargs): return None |
| class EmptyLogits: |
| def __init__(self): return |
| def raise_getattr_error(self, attr): return return_none if attr == "to" else raise_logits_error |
| __getitem__ = raise_logits_error |
| __getattr__ = raise_getattr_error |
| def __repr__(self): return LOGITS_ERROR_STRING |
| def __str__ (self): return LOGITS_ERROR_STRING |
| pass |
| EMPTY_LOGITS = EmptyLogits() |
| functions = dir(torch.Tensor) |
| for j, function in enumerate(functions): |
| if function.startswith("__") and function.endswith("__"): |
| exec(f"def raise_{j}(*args, **kwargs): print('{function}')", globals(), locals()) |
| try: exec(f"EMPTY_LOGITS.{function} = raise_{j}", globals(), locals()) |
| except: continue |
| pass |
|
|
|
|
| def mask_attention_mask_out(labels = None, attention_mask = None): |
| if labels is not None and attention_mask is not None: |
| attention_mask = attention_mask.to(device = labels.device) |
| labels[attention_mask == 0] = -100 |
| return labels |
| pass |
|
|
|
|
| from torch import Tensor |
| import torch |
| import torch.nn as nn |
| from torch.nn import functional as F |
| from unsloth_zoo.temporary_patches.common import torch_compile |
| from typing import Any, List, Optional, Tuple, Union, Dict, Set, Callable |
| from transformers.models.gemma4.modeling_gemma4 import (__name__, F, math, Callable, Optional, torch, nn, init, ACT2FN, Cache, PreTrainedConfig, GenerationMixin, create_causal_mask, create_sliding_window_causal_mask, FlashAttentionKwargs, BaseModelOutputWithPast, ModelOutput, CausalLMOutputWithPast, ROPE_INIT_FUNCTIONS, dynamic_rope_update, ALL_ATTENTION_FUNCTIONS, PreTrainedModel, Unpack, TransformersKwargs, can_return_tuple, maybe_autocast, Gemma4AudioConfig, Gemma4Config, Gemma4TextConfig, Gemma4VisionConfig, Gemma4Model, Gemma4CausalLMOutputWithPast, Gemma4AudioCausalConv1d, Gemma4PreTrainedModel, Gemma4TextModel, Gemma4ForCausalLM, Gemma4ForConditionalGeneration, Gemma4TextExperts, create_causal_mask, create_masks_for_generate, create_sliding_window_causal_mask) |
|
|
| @torch.compile(fullgraph = False, dynamic = True, options = torch_compile_options) |
| def Gemma4ClippableLinear_forward(self, hidden_states: torch.Tensor) -> torch.Tensor: |
| if self.use_clipped_linears: |
| hidden_states = torch.clamp(hidden_states, self.input_min, self.input_max) |
|
|
| hidden_states = self.linear(hidden_states) |
|
|
| if self.use_clipped_linears: |
| hidden_states = torch.clamp(hidden_states, self.output_min, self.output_max) |
|
|
| return hidden_states |
|
|
| class Gemma4ClippableLinear(nn.Module): |
| def __init__( |
| self, |
| config: Gemma4VisionConfig | Gemma4AudioConfig, |
| in_features: int, |
| out_features: int, |
| ) -> None: |
| super().__init__() |
| self.use_clipped_linears = config.use_clipped_linears |
| self.linear = nn.Linear(in_features, out_features, bias=False) |
|
|
| if self.use_clipped_linears: |
| self.register_buffer("input_min", torch.tensor(-float("inf"))) |
| self.register_buffer("input_max", torch.tensor(float("inf"))) |
| self.register_buffer("output_min", torch.tensor(-float("inf"))) |
| self.register_buffer("output_max", torch.tensor(float("inf"))) |
|
|
| def forward(self, hidden_states: torch.Tensor) -> torch.Tensor: |
| return Gemma4ClippableLinear_forward(self, hidden_states=hidden_states) |
|
|
|
|
| @torch.compile(fullgraph = True, dynamic = True, options = torch_compile_options) |
| def Gemma4RMSNorm_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 Gemma4RMSNorm(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 |
| |
| return hidden_states * torch.pow(mean_squared, -0.5) |
|
|
| def forward(self, hidden_states: torch.Tensor) -> torch.Tensor: |
| return Gemma4RMSNorm_forward(self, hidden_states=hidden_states) |
|
|
|
|
| @torch.compile(fullgraph = True, dynamic = True, options = torch_compile_options) |
| @torch.no_grad() |
| def Gemma4AudioRelPositionalEncoding_forward(self, hidden_states: torch.Tensor) -> torch.Tensor: |
| position_ids = torch.arange(12, -1, -1, device=hidden_states.device) |
| position_ids = position_ids[..., None] |
| scaled_time = position_ids * self.inv_timescales.to(device=hidden_states.device) |
| pos_embed = torch.cat([torch.sin(scaled_time), torch.cos(scaled_time)], dim=-1) |
| return pos_embed.to(dtype=hidden_states.dtype) |
|
|
| class Gemma4AudioRelPositionalEncoding(nn.Module): |
| """Sinusoidal relative positional encoding for the audio encoder. |
| |
| Produces position embeddings of shape [1, 2*context_size - 1, hidden_size] with |
| concatenated [sin..., cos...] layout matching the original Gemma4 convention. |
| """ |
|
|
| inv_timescales: torch.Tensor |
|
|
| def __init__(self, config: Gemma4AudioConfig): |
| super().__init__() |
| self.hidden_size = config.hidden_size |
| self.context_size = ( |
| config.attention_chunk_size + config.attention_context_left - 1 + config.attention_context_right |
| ) |
| min_timescale = 1.0 |
| max_timescale = 10000.0 |
| num_timescales = self.hidden_size // 2 |
| log_timescale_increment = math.log(max_timescale / min_timescale) / max(num_timescales - 1, 1) |
| inv_timescales = min_timescale * torch.exp(torch.arange(num_timescales) * -log_timescale_increment) |
| self.register_buffer("inv_timescales", inv_timescales.unsqueeze(0).unsqueeze(0), persistent=False) |
|
|
|
|
| def forward(self, hidden_states: torch.Tensor) -> torch.Tensor: |
| return Gemma4AudioRelPositionalEncoding_forward(self, hidden_states=hidden_states) |
|
|
|
|
| @torch.compile(fullgraph = True, dynamic = True, options = torch_compile_options) |
| def Gemma4AudioSubSampleConvProjectionLayer_forward(self, hidden_states: torch.Tensor, mask: torch.Tensor | None = None): |
| if mask is not None: |
| mask = mask.to(device=hidden_states.device) |
| hidden_states = hidden_states * mask[:, None, :, None] |
|
|
| hidden_states = self.conv(hidden_states.to(self.conv.weight.dtype)) |
| hidden_states = self.act(self.norm(hidden_states.permute(0, 2, 3, 1)).permute(0, 3, 1, 2).contiguous()) |
|
|
| if mask is not None: |
| mask = mask[:, ::2] |
|
|
| return hidden_states, mask |
|
|
| class Gemma4AudioSubSampleConvProjectionLayer(nn.Module): |
| def __init__(self, in_channels, out_channels, norm_eps): |
| super().__init__() |
| self.conv = nn.Conv2d( |
| in_channels=in_channels, |
| out_channels=out_channels, |
| kernel_size=(3, 3), |
| stride=(2, 2), |
| padding=1, |
| bias=False, |
| ) |
| self.norm = nn.LayerNorm(out_channels, eps=norm_eps, elementwise_affine=True, bias=False) |
| self.act = nn.ReLU() |
|
|
| def forward(self, hidden_states: torch.Tensor, mask: torch.Tensor | None = None): |
| return Gemma4AudioSubSampleConvProjectionLayer_forward(self, hidden_states=hidden_states, mask=mask) |
|
|
|
|
| @torch.compile(fullgraph = False, dynamic = True, options = torch_compile_options) |
| def Gemma4AudioSubSampleConvProjection_forward( |
| self, |
| input_features: torch.Tensor, |
| input_features_mask: torch.Tensor | None = None, |
| ) -> tuple[torch.Tensor, torch.Tensor]: |
| hidden_states = input_features.unsqueeze(1) |
| hidden_states, mask = self.layer0(hidden_states, input_features_mask) |
| hidden_states, mask = self.layer1(hidden_states, mask) |
|
|
| batch_size, _, seq_len, _ = hidden_states.shape |
| hidden_states = hidden_states.permute(0, 2, 3, 1).contiguous().reshape(batch_size, seq_len, -1) |
| return self.input_proj_linear(hidden_states), mask |
|
|
| class Gemma4AudioSubSampleConvProjection(nn.Module): |
| def __init__(self, config: Gemma4AudioConfig): |
| super().__init__() |
| self.layer0 = Gemma4AudioSubSampleConvProjectionLayer( |
| in_channels=1, |
| out_channels=config.subsampling_conv_channels[0], |
| norm_eps=config.rms_norm_eps, |
| ) |
| self.layer1 = Gemma4AudioSubSampleConvProjectionLayer( |
| in_channels=config.subsampling_conv_channels[0], |
| out_channels=config.subsampling_conv_channels[1], |
| norm_eps=config.rms_norm_eps, |
| ) |
| proj_input_dim = (config.subsampling_conv_channels[0] // 4) * config.subsampling_conv_channels[1] |
| self.input_proj_linear = nn.Linear(proj_input_dim, config.hidden_size, bias=False) |
|
|
| def forward( |
| self, |
| input_features: torch.Tensor, |
| input_features_mask: torch.Tensor | None = None, |
| ) -> tuple[torch.Tensor, torch.Tensor]: |
| return Gemma4AudioSubSampleConvProjection_forward(self, input_features=input_features, input_features_mask=input_features_mask) |
|
|
|
|
| @torch.compile(fullgraph = False, dynamic = True, options = torch_compile_options) |
| def Gemma4AudioFeedForward_forward(self, hidden_states: torch.Tensor) -> torch.Tensor: |
| |
| gradient_clipping = min(self.gradient_clipping, torch.finfo(self.ffw_layer_1.linear.weight.dtype).max) |
|
|
| residual = hidden_states |
| hidden_states = torch.clamp(hidden_states, -gradient_clipping, gradient_clipping) |
| hidden_states = self.pre_layer_norm(hidden_states) |
|
|
| hidden_states = self.ffw_layer_1(hidden_states) |
| hidden_states = self.act_fn(hidden_states) |
| hidden_states = self.ffw_layer_2(hidden_states) |
|
|
| hidden_states = torch.clamp(hidden_states, -gradient_clipping, gradient_clipping) |
| hidden_states = self.post_layer_norm(hidden_states) |
| hidden_states *= self.post_layer_scale |
| hidden_states += residual |
|
|
| return hidden_states |
|
|
| class Gemma4AudioFeedForward(nn.Module): |
| def __init__(self, config: Gemma4AudioConfig): |
| super().__init__() |
| self.config = config |
|
|
| self.ffw_layer_1 = Gemma4ClippableLinear(config, config.hidden_size, config.hidden_size * 4) |
| self.ffw_layer_2 = Gemma4ClippableLinear(config, config.hidden_size * 4, config.hidden_size) |
|
|
| self.pre_layer_norm = Gemma4RMSNorm(config.hidden_size) |
| self.post_layer_norm = Gemma4RMSNorm(config.hidden_size) |
| self.act_fn = ACT2FN[config.hidden_act] |
|
|
| self.gradient_clipping = config.gradient_clipping |
| self.post_layer_scale = config.residual_weight |
|
|
| def forward(self, hidden_states: torch.Tensor) -> torch.Tensor: |
| return Gemma4AudioFeedForward_forward(self, hidden_states=hidden_states) |
|
|
|
|
| @torch.compile(fullgraph = False, dynamic = True, options = torch_compile_options) |
| def Gemma4AudioLightConv1d_forward(self, hidden_states: torch.Tensor) -> torch.Tensor: |
| residual = hidden_states |
|
|
| hidden_states = self.pre_layer_norm(hidden_states) |
| hidden_states = self.linear_start(hidden_states) |
| hidden_states = nn.functional.glu(hidden_states, dim=-1) |
|
|
| hidden_states = self.depthwise_conv1d(hidden_states.transpose(1, 2)).transpose(1, 2) |
|
|
| |
| gradient_clipping = min(self.gradient_clipping, torch.finfo(self.linear_start.linear.weight.dtype).max) |
| hidden_states = torch.clamp(hidden_states, -gradient_clipping, gradient_clipping) |
| hidden_states = self.conv_norm(hidden_states) |
|
|
| hidden_states = self.act_fn(hidden_states) |
| hidden_states = self.linear_end(hidden_states) |
| hidden_states += residual |
| return hidden_states |
|
|
| class Gemma4AudioLightConv1d(nn.Module): |
| def __init__(self, config: Gemma4AudioConfig): |
| super().__init__() |
| self.config = config |
|
|
| self.linear_start = Gemma4ClippableLinear(config, config.hidden_size, config.hidden_size * 2) |
| self.linear_end = Gemma4ClippableLinear(config, config.hidden_size, config.hidden_size) |
| self.depthwise_conv1d = Gemma4AudioCausalConv1d( |
| in_channels=config.hidden_size, |
| out_channels=config.hidden_size, |
| kernel_size=config.conv_kernel_size, |
| groups=config.hidden_size, |
| bias=False, |
| ) |
|
|
| self.pre_layer_norm = Gemma4RMSNorm(config.hidden_size, eps=config.rms_norm_eps, with_scale=True) |
| self.conv_norm = Gemma4RMSNorm(config.hidden_size, eps=config.rms_norm_eps, with_scale=True) |
| self.act_fn = ACT2FN[config.hidden_act] |
|
|
| self.gradient_clipping = config.gradient_clipping |
|
|
| def forward(self, hidden_states: torch.Tensor) -> torch.Tensor: |
| return Gemma4AudioLightConv1d_forward(self, hidden_states=hidden_states) |
|
|
|
|
| @torch.compile(fullgraph = False, dynamic = True, options = torch_compile_options) |
| def Gemma4VisionMLP_forward(self, x): |
| down_proj = self.down_proj(self.act_fn(self.gate_proj(x)) * self.up_proj(x)) |
| return down_proj |
|
|
| class Gemma4VisionMLP(nn.Module): |
| def __init__(self, config: Gemma4VisionConfig): |
| super().__init__() |
| self.config = config |
| self.hidden_size = config.hidden_size |
| self.intermediate_size = config.intermediate_size |
| self.gate_proj = Gemma4ClippableLinear(config, self.hidden_size, self.intermediate_size) |
| self.up_proj = Gemma4ClippableLinear(config, self.hidden_size, self.intermediate_size) |
| self.down_proj = Gemma4ClippableLinear(config, self.intermediate_size, self.hidden_size) |
| self.act_fn = ACT2FN[config.hidden_activation] |
|
|
| def forward(self, x): |
| return Gemma4VisionMLP_forward(self, x=x) |
|
|
|
|
| @torch.compile(fullgraph = False, dynamic = True, options = torch_compile_options) |
| @torch.no_grad() |
| @dynamic_rope_update |
| def Gemma4VisionRotaryEmbedding_forward(self, x, position_ids): |
| inv_freq_expanded = self.inv_freq[None, :, None].float().expand(position_ids.shape[0], -1, 1).to(x.device) |
| device_type = x.device.type if isinstance(x.device.type, str) and x.device.type != "mps" else "cpu" |
|
|
| |
| all_cos, all_sin = [], [] |
| for i in range(2): |
| dim_position_ids = position_ids[:, :, i] |
| dim_position_ids_expanded = dim_position_ids[:, None, :].float() |
|
|
| with maybe_autocast(device_type=device_type, enabled=False): |
| freqs = (inv_freq_expanded.float() @ dim_position_ids_expanded.float()).transpose(1, 2) |
| emb = torch.cat((freqs, freqs), dim=-1) |
| cos = emb.cos() * self.attention_scaling |
| sin = emb.sin() * self.attention_scaling |
| all_cos.append(cos) |
| all_sin.append(sin) |
|
|
| cos = torch.cat(all_cos, dim=-1).to(dtype=x.dtype) |
| sin = torch.cat(all_sin, dim=-1).to(dtype=x.dtype) |
| return cos, sin |
|
|
| class Gemma4VisionRotaryEmbedding(nn.Module): |
| inv_freq: torch.Tensor |
|
|
| def __init__(self, config: Gemma4VisionConfig, device=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.rope_type = self.config.rope_parameters["rope_type"] |
| rope_init_fn: Callable = self.compute_default_rope_parameters |
| if self.rope_type != "default": |
| rope_init_fn = ROPE_INIT_FUNCTIONS[self.rope_type] |
| inv_freq, self.attention_scaling = rope_init_fn(self.config, device) |
|
|
| self.register_buffer("inv_freq", inv_freq, persistent=False) |
| self.register_buffer("original_inv_freq", inv_freq.clone(), persistent=False) |
|
|
| @staticmethod |
| def compute_default_rope_parameters( |
| config: Gemma4VisionConfig | None = None, |
| device: torch.device | None = None, |
| seq_len: int | 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. |
| 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). |
| """ |
| base = config.rope_parameters["rope_theta"] |
| dim = getattr(config, "head_dim", None) or config.hidden_size // config.num_attention_heads |
|
|
| |
| |
| |
| |
| spatial_dim = dim // 2 |
|
|
| attention_factor = 1.0 |
| inv_freq = 1.0 / ( |
| base |
| ** (torch.arange(0, spatial_dim, 2, dtype=torch.int64).to(device=device, dtype=torch.float) / spatial_dim) |
| ) |
| return inv_freq, attention_factor |
|
|
|
|
| def forward(self, x, position_ids): |
| return Gemma4VisionRotaryEmbedding_forward(self, x=x, position_ids=position_ids) |
|
|
|
|
| @torch.compile(fullgraph = True, dynamic = True, options = torch_compile_options) |
| 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) |
|
|
|
|
| @torch.compile(fullgraph = True, dynamic = True, options = torch_compile_options) |
| 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) |
|
|
|
|
| @torch.compile(fullgraph = True, dynamic = True, options = torch_compile_options) |
| 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) |
|
|
|
|
| @torch.compile(fullgraph = True, dynamic = True, options = torch_compile_options) |
| 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: |
|
|
| if isinstance(attention_mask, dict): |
|
|
| attention_mask = attention_mask.get(getattr(module, 'layer_type', None), None) |
|
|
| if attention_mask is not None: |
|
|
| attn_weights = attn_weights + attention_mask |
|
|
| |
| attn_weights = nn.functional.softmax(attn_weights, dim=-1, dtype = torch.float32).to(attn_weights.dtype).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 |
|
|
|
|
| @torch.compile(fullgraph = True, dynamic = True, options = torch_compile_options) |
| def apply_multidimensional_rope( |
| x: torch.Tensor, |
| cos: torch.Tensor, |
| sin: torch.Tensor, |
| position_ids: torch.Tensor, |
| unsqueeze_dim: int = 2, |
| ) -> torch.Tensor: |
| """Applies multidimensional RoPE to inputs. |
| |
| 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. |
| position_ids (`torch.Tensor`, *optional*): |
| If position_ids.ndim + 2 == x.ndim, then this function passes through to `apply_rotary_pos_emb()`. |
| Otherwise, position_ids is used to split the inputs, x, into multiple pieces, where each piece is fed to |
| `apply_rotary_pos_emb()`, and then concatenated back together. |
| 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: |
| Tensor of shape [B, L, N, H] with RoPE applied. |
| """ |
| ndim = position_ids.shape[-1] |
| num_input_channels = x.shape[-1] |
| num_rotated_channels_per_dim = 2 * (num_input_channels // (2 * ndim)) |
|
|
| if num_rotated_channels_per_dim <= 0: |
| raise ValueError( |
| "Invalid configuration: num_rotated_channels_per_dim must be > 0, got" |
| f" {num_rotated_channels_per_dim} (num_input_channels={num_input_channels}," |
| f" ndim={ndim})" |
| ) |
|
|
| |
| split_sizes = [num_rotated_channels_per_dim] * ndim |
| x_parts = torch.split(x, split_sizes, dim=-1) |
| cos_parts = torch.split(cos, split_sizes, dim=-1) |
| sin_parts = torch.split(sin, split_sizes, dim=-1) |
| y_parts = [ |
| apply_rotary_pos_emb( |
| x=x_parts[k], |
| cos=cos_parts[k], |
| sin=sin_parts[k], |
| unsqueeze_dim=unsqueeze_dim, |
| ) |
| for k in range(ndim) |
| ] |
| return torch.cat(y_parts, dim=-1) |
|
|
|
|
| @torch.compiler.disable(recursive = False) |
| def Gemma4VisionAttention_forward( |
| self, |
| hidden_states: torch.Tensor, |
| position_embeddings: torch.Tensor = None, |
| attention_mask: torch.Tensor | None = None, |
| position_ids: torch.LongTensor | None = None, |
| **kwargs: Unpack[TransformersKwargs], |
| ) -> tuple[torch.Tensor, torch.Tensor | None, tuple[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_multidimensional_rope(query_states, cos, sin, position_ids) |
| query_states = query_states.transpose(1, 2) |
|
|
| key_states = self.k_proj(hidden_states).view(hidden_shape) |
| key_states = self.k_norm(key_states) |
| key_states = apply_multidimensional_rope(key_states, cos, sin, position_ids) |
| key_states = key_states.transpose(1, 2) |
|
|
| value_states = self.v_proj(hidden_states).view(hidden_shape) |
| value_states = self.v_norm(value_states) |
| value_states = value_states.transpose(1, 2) |
|
|
| 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, |
| **kwargs, |
| ) |
|
|
| attn_output = attn_output.reshape(*input_shape, -1).contiguous() |
| attn_output = self.o_proj(attn_output) |
| return attn_output, attn_weights |
|
|
| class Gemma4VisionAttention(nn.Module): |
| """Multi-headed attention from 'Attention Is All You Need' paper""" |
|
|
| def __init__(self, config: Gemma4VisionConfig, 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.head_dim = getattr(config, "head_dim", config.hidden_size // config.num_attention_heads) |
| self.num_key_value_groups = config.num_attention_heads // config.num_key_value_heads |
| self.scaling = 1.0 |
| self.attention_dropout = self.config.attention_dropout |
| self.is_causal = False |
| self.q_proj = Gemma4ClippableLinear(config, config.hidden_size, config.num_attention_heads * self.head_dim) |
| self.k_proj = Gemma4ClippableLinear(config, config.hidden_size, config.num_key_value_heads * self.head_dim) |
| self.v_proj = Gemma4ClippableLinear(config, config.hidden_size, config.num_key_value_heads * self.head_dim) |
| self.o_proj = Gemma4ClippableLinear(config, config.num_attention_heads * self.head_dim, config.hidden_size) |
|
|
| self.q_norm = Gemma4RMSNorm(dim=config.head_dim, eps=config.rms_norm_eps) |
| self.k_norm = Gemma4RMSNorm(dim=config.head_dim, eps=config.rms_norm_eps) |
| self.v_norm = Gemma4RMSNorm(self.head_dim, eps=config.rms_norm_eps, with_scale=False) |
|
|
| def forward( |
| self, |
| hidden_states: torch.Tensor, |
| position_embeddings: torch.Tensor = None, |
| attention_mask: torch.Tensor | None = None, |
| position_ids: torch.LongTensor | None = None, |
| **kwargs: Unpack[TransformersKwargs], |
| ) -> tuple[torch.Tensor, torch.Tensor | None, tuple[torch.Tensor] | None]: |
| return Gemma4VisionAttention_forward(self, hidden_states=hidden_states, position_embeddings=position_embeddings, attention_mask=attention_mask, position_ids=position_ids, **kwargs) |
|
|
|
|
| @torch.compile(fullgraph = False, dynamic = True, options = torch_compile_options) |
| def Gemma4TextMLP_forward(self, x): |
| gate = self.gate_proj(x) |
| |
| if gate.dtype != torch.float16: |
| return self.down_proj(self.act_fn(gate) * self.up_proj(x)) |
| product = self.act_fn(gate.float()) * self.up_proj(x).float() |
| product = torch.clamp(product, min=-_SAFE_FP16, max=_SAFE_FP16) |
| out = self.down_proj(product.to(gate.dtype)) |
| |
| return torch.nan_to_num(out, nan=0.0, posinf=0.0, neginf=0.0) |
|
|
| class Gemma4TextMLP(nn.Module): |
| def __init__(self, config: Gemma4TextConfig, 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 |
|
|
|
|
| @torch.compile(fullgraph = False, dynamic = True, options = torch_compile_options) |
| @torch.no_grad() |
| @dynamic_rope_update |
| def Gemma4TextRotaryEmbedding_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): |
| 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) |
|
|
| class Gemma4TextRotaryEmbedding(nn.Module): |
| inv_freq: torch.Tensor |
|
|
| def __init__(self, config: Gemma4TextConfig, 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: Gemma4TextConfig | 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). |
| """ |
| |
| 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 |
|
|
| |
| 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 |
|
|
|
|
| def forward(self, x, position_ids, layer_type=None): |
| return Gemma4TextRotaryEmbedding_forward(self, x=x, position_ids=position_ids, layer_type=layer_type) |
|
|
|
|
| @torch.compiler.disable(recursive = False) |
| def Gemma4TextAttention_forward( |
| self, |
| hidden_states: torch.Tensor, |
| position_embeddings: torch.Tensor, |
| attention_mask: torch.Tensor | None, |
| 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) |
|
|
| |
| if self.is_kv_shared_layer and past_key_values is not None: |
| key_states, value_states = past_key_values.shared_layers[self.kv_shared_layer_index] |
| |
| 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: |
| if 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: |
| if not hasattr(past_key_values, "shared_layers"): |
| past_key_values.shared_layers = {} |
| past_key_values.shared_layers[self.layer_idx] = key_states, value_states |
|
|
| attention_interface: Callable = eager_attention_forward |
| if self.config._attn_implementation != "eager": |
| attention_interface = ALL_ATTENTION_FUNCTIONS[self.config._attn_implementation] |
|
|
| 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 Gemma4TextAttention(nn.Module): |
| """Multi-headed attention from 'Attention Is All You Need' paper""" |
|
|
| def __init__(self, config: Gemma4TextConfig, 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" |
|
|
| |
| 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] |
| if self.is_kv_shared_layer: |
| |
| self.kv_shared_layer_index = len(prev_layers) - 1 - prev_layers[::-1].index(config.layer_types[layer_idx]) |
| self.store_full_length_kv = False |
| else: |
| self.kv_shared_layer_index = None |
| |
| self.store_full_length_kv = layer_idx == len(prev_layers) - 1 - prev_layers[::-1].index( |
| config.layer_types[layer_idx] |
| ) |
|
|
| self.q_norm = Gemma4RMSNorm(dim=self.head_dim, eps=config.rms_norm_eps) |
| self.k_norm = Gemma4RMSNorm(dim=self.head_dim, eps=config.rms_norm_eps) |
| self.v_norm = Gemma4RMSNorm(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.q_proj = nn.Linear( |
| config.hidden_size, config.num_attention_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, |
| past_key_values: Cache | None = None, |
| **kwargs: Unpack[FlashAttentionKwargs], |
| ) -> tuple[torch.Tensor, torch.Tensor | None]: |
| return Gemma4TextAttention_forward(self, hidden_states=hidden_states, position_embeddings=position_embeddings, attention_mask=attention_mask, past_key_values=past_key_values, **kwargs) |
|
|
|
|
| @torch.compiler.disable(recursive = False) |
| def Gemma4TextExperts_forward( |
| self, |
| hidden_states: torch.Tensor, |
| top_k_index: torch.Tensor, |
| top_k_weights: torch.Tensor, |
| ) -> torch.Tensor: |
| final_hidden_states = torch.zeros_like(hidden_states) |
| with torch.no_grad(): |
| expert_mask = torch.nn.functional.one_hot(top_k_index, num_classes=self.num_experts) |
| expert_mask = expert_mask.permute(2, 1, 0) |
| expert_hit = torch.greater(expert_mask.sum(dim=(-1, -2)), 0).nonzero() |
|
|
| for expert_idx in expert_hit: |
| expert_idx = expert_idx[0] |
| if expert_idx == self.num_experts: |
| continue |
| top_k_pos, token_idx = torch.where(expert_mask[expert_idx]) |
| current_state = hidden_states[token_idx] |
| gate, up = nn.functional.linear(current_state, self.gate_up_proj[expert_idx]).chunk(2, dim=-1) |
| current_hidden_states = self.act_fn(gate) * up |
| current_hidden_states = nn.functional.linear(current_hidden_states, self.down_proj[expert_idx]) |
| current_hidden_states = current_hidden_states * top_k_weights[token_idx, top_k_pos, None] |
| final_hidden_states.index_add_(0, token_idx, current_hidden_states.to(final_hidden_states.dtype)) |
|
|
| return final_hidden_states |
|
|
| class Gemma4TextExperts(nn.Module): |
| """Collection of expert weights stored as 3D tensors.""" |
|
|
| def __init__(self, config: Gemma4TextConfig): |
| super().__init__() |
| self.num_experts = config.num_experts |
| self.hidden_dim = config.hidden_size |
| self.intermediate_dim = config.moe_intermediate_size |
| self.gate_up_proj = nn.Parameter(torch.empty(self.num_experts, 2 * self.intermediate_dim, self.hidden_dim)) |
| self.down_proj = nn.Parameter(torch.empty(self.num_experts, self.hidden_dim, self.intermediate_dim)) |
| self.act_fn = ACT2FN[config.hidden_activation] |
|
|
| def forward( |
| self, |
| hidden_states: torch.Tensor, |
| top_k_index: torch.Tensor, |
| top_k_weights: torch.Tensor, |
| ) -> torch.Tensor: |
| return Gemma4TextExperts_forward(self, hidden_states=hidden_states, top_k_index=top_k_index, top_k_weights=top_k_weights) |
|
|
|
|
| @torch.compile(fullgraph = False, dynamic = True, options = torch_compile_options) |
| def Gemma4TextRouter_forward(self, hidden_states: torch.Tensor) -> tuple[torch.Tensor, torch.Tensor]: |
| hidden_states = self.norm(hidden_states) |
| hidden_states = hidden_states * self.scale * self.scalar_root_size |
|
|
| expert_scores = self.proj(hidden_states) |
| router_probabilities = nn.functional.softmax(expert_scores, dim=-1, dtype = torch.float32).to(expert_scores.dtype).to(expert_scores.dtype) |
|
|
| |
| top_k_weights, top_k_index = torch.topk( |
| router_probabilities, |
| k=self.config.top_k_experts, |
| dim=-1, |
| ) |
|
|
| |
| top_k_weights /= top_k_weights.sum(dim=-1, keepdim=True) |
|
|
| |
| top_k_weights = top_k_weights * self.per_expert_scale[top_k_index] |
|
|
| return router_probabilities, top_k_weights, top_k_index |
|
|
| class Gemma4TextRouter(nn.Module): |
| def __init__(self, config: Gemma4TextConfig): |
| super().__init__() |
| self.config = config |
| self.hidden_size = config.hidden_size |
| self.scalar_root_size = self.hidden_size**-0.5 |
| self.eps = config.rms_norm_eps |
|
|
| self.norm = Gemma4RMSNorm(self.hidden_size, eps=self.eps, with_scale=False) |
| self.proj = nn.Linear(config.hidden_size, config.num_experts, bias=False) |
| self.scale = nn.Parameter(torch.ones(self.hidden_size)) |
| self.per_expert_scale = nn.Parameter(torch.ones(config.num_experts)) |
|
|
| def forward(self, hidden_states: torch.Tensor) -> tuple[torch.Tensor, torch.Tensor]: |
| return Gemma4TextRouter_forward(self, hidden_states=hidden_states) |
|
|
|
|
| @torch.compiler.disable(recursive = False) |
| @can_return_tuple |
| def Gemma4ForCausalLM_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], |
| ) -> CausalLMOutputWithPast: |
| r""" |
| Example: |
| |
| ```python |
| >>> from transformers import AutoTokenizer, Gemma4ForCausalLM |
| |
| >>> model = Gemma4ForCausalLM.from_pretrained("google/gemma-2-9b") |
| >>> tokenizer = AutoTokenizer.from_pretrained("google/gemma-2-9b") |
| |
| >>> 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?" |
| ```""" |
| |
| outputs: BaseModelOutputWithPast = 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 |
| |
| 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 os.environ.get('UNSLOTH_RETURN_LOGITS', '0') == '1' else EMPTY_LOGITS |
| loss = None |
| NOT_RETURN_LOGITS = os.environ.get('UNSLOTH_RETURN_LOGITS', '0') == '0' |
| RETURN_HIDDEN_STATES = os.environ.get("UNSLOTH_RETURN_HIDDEN_STATES", "0") == "1" |
| |
| n_items = None |
| if (kwargs) != () and type(kwargs) is dict: |
| n_items = (kwargs).get("num_items_in_batch", None) |
| if n_items is None: n_items = (kwargs).get("n_items", None) |
| if n_items is None: |
| all_locals = locals() |
| if 'loss_kwargs' in all_locals: |
| __kwargs = all_locals['loss_kwargs'] |
| if type(__kwargs) is dict: |
| n_items = __kwargs.get("num_items_in_batch", None) |
| if n_items is None: n_items = __kwargs.get("n_items", None) |
| if n_items is None and 'kwargs' in all_locals: |
| __kwargs = all_locals['kwargs'] |
| if type(__kwargs) is dict: |
| n_items = __kwargs.get("num_items_in_batch", None) |
| if n_items is None: n_items = __kwargs.get("n_items", None) |
| if n_items is None: |
| all_locals = all_locals.values() |
| for __kwargs in all_locals: |
| if type(__kwargs) is dict: |
| n_items = __kwargs.get("num_items_in_batch", None) |
| if n_items is None: n_items = __kwargs.get("n_items", None) |
| break |
| pass |
| |
| requires_grad_ = self.lm_head.weight.requires_grad |
| requires_grad_ = requires_grad_ or self.lm_head.weight.dtype == torch.float32 |
| |
| if RETURN_HIDDEN_STATES: |
| logits = hidden_states[:, slice_indices, :] |
| elif labels is None: |
| |
| |
| |
| global INFERENCE_RUNS |
| if torch_dynamo_eval_frame is not None: |
| old_stance = torch_dynamo_eval_frame._stance.stance |
| else: |
| old_stance = None |
| if old_stance is not None and INFERENCE_RUNS == 1: |
| |
| torch_compiler_set_stance(stance = "eager_on_recompile", skip_guard_eval_unsafe = False) |
| if UNSLOTH_ENABLE_LOGGING: |
| logger_compiler.info( |
| f"Unsloth: Removing compiler guards after 1 inference run. "\ |
| f"DYNAMO_STANCE.stance = {torch_dynamo_eval_frame._stance.stance} "\ |
| f"DYNAMO_STANCE.skip_guard_eval_unsafe = {torch_dynamo_eval_frame._stance.skip_guard_eval_unsafe}" |
| ) |
| elif old_stance == "eager_on_recompile": |
| pass |
| elif old_stance == "default" and INFERENCE_RUNS > 1: |
| |
| torch_compiler_set_stance(stance = "default", skip_guard_eval_unsafe = False) |
| if UNSLOTH_ENABLE_LOGGING: |
| logger_compiler.info( |
| f"Unsloth: Reseting guards. "\ |
| f"DYNAMO_STANCE.stance = {torch_dynamo_eval_frame._stance.stance} "\ |
| f"DYNAMO_STANCE.skip_guard_eval_unsafe = {torch_dynamo_eval_frame._stance.skip_guard_eval_unsafe}" |
| ) |
| INFERENCE_RUNS = 0 |
| INFERENCE_RUNS += 1 |
| |
| logits = self.lm_head(hidden_states[:, slice_indices, :]) |
| elif (() == () and () == ()) and (UNSLOTH_ENABLE_CCE) and NOT_RETURN_LOGITS and self.loss_function.__name__.endswith("ForCausalLMLoss") and labels is not None and not requires_grad_: |
| loss = fused_linear_cross_entropy( |
| hidden_states = hidden_states[:, slice_indices, :], |
| lm_weight = self.lm_head.weight, |
| labels = labels.to(self.lm_head.weight.device), |
| num_items_in_batch = n_items, |
| logit_softcapping = None if (self.config.final_logit_softcapping) == () else (self.config.final_logit_softcapping), |
| ) |
| elif self.loss_function.__name__.endswith("ForCausalLMLoss") and labels is not None: |
| lm_head_weight = self.lm_head.weight |
| lm_head_bias = getattr(self.lm_head, "bias", None) |
| |
| |
| _hidden_states = hidden_states[:, slice_indices, :] |
| torch._dynamo.mark_dynamic(_hidden_states, 1) |
| torch._dynamo.mark_dynamic(labels, 1) |
| loss = unsloth_fused_ce_loss( |
| trainer = None, |
| hidden_states = _hidden_states, |
| lm_head_weight = lm_head_weight, |
| lm_head_bias = lm_head_bias, |
| labels = labels, |
| mask = None, |
| n_items = n_items, |
| scaling = getattr(self, "accelerator_scaler", None), |
| target_gb = None, |
| torch_compile = not UNSLOTH_COMPILE_DISABLE, |
| logit_scale_multiply = () if () != () else 0, |
| logit_scale_divide = () if () != () else 0, |
| logit_softcapping = (self.config.final_logit_softcapping) if (self.config.final_logit_softcapping) != () else 0, |
| ) |
| else: |
| logits = self.lm_head(hidden_states[:, slice_indices, :]) |
| if () != (): |
| logits = logits * () |
| if () != (): |
| logits = logits / () |
| if (self.config.final_logit_softcapping) not in (None, (),): |
| logits = logits / (self.config.final_logit_softcapping) |
| logits = torch.tanh(logits) |
| logits = logits * (self.config.final_logit_softcapping) |
| loss = self.loss_function(logits, labels.to(self.lm_head.weight.device), vocab_size=self.vocab_size, **kwargs) |
|
|
|
|
| return CausalLMOutputWithPast( |
| loss=loss, |
| logits=logits, |
| past_key_values=outputs.past_key_values, |
| hidden_states=outputs.hidden_states, |
| attentions=outputs.attentions, |
| ) |
|
|
| class Gemma4ForCausalLM(Gemma4PreTrainedModel, 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: Gemma4TextConfig |
| base_model_prefix = "model" |
|
|
| def __init__(self, config: Gemma4TextConfig): |
| super().__init__(config) |
| self.model = Gemma4TextModel(config) |
| self.vocab_size = config.vocab_size |
| self.lm_head = nn.Linear(config.hidden_size, config.vocab_size, bias=False) |
|
|
| |
| self.post_init() |
|
|
|
|
| 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], |
| ) -> CausalLMOutputWithPast: |
| return Gemma4ForCausalLM_forward(self, input_ids=input_ids, attention_mask=attention_mask, position_ids=position_ids, past_key_values=past_key_values, inputs_embeds=inputs_embeds, labels=labels, use_cache=use_cache, logits_to_keep=logits_to_keep, **kwargs) |
|
|
|
|
| def sliding_window_mask_function(sliding_window: tuple[int, int]) -> Callable: |
| """ |
| This creates uni/bidirectional attention mask with sliding window. |
| """ |
|
|
| def inner_mask(batch_idx: int, head_idx: int, q_idx: int, kv_idx: int) -> bool: |
| left_window_size, right_window_size = sliding_window |
|
|
| dist = q_idx - kv_idx |
| left_mask = (dist >= 0) & (dist < left_window_size) |
| right_mask = (dist < 0) & (-dist < right_window_size) |
| return left_mask | right_mask |
|
|
| return inner_mask |
|
|
|
|
| @torch.compiler.disable(recursive = False) |
| @can_return_tuple |
| def Gemma4ForConditionalGeneration_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], |
| ) -> Gemma4CausalLMOutputWithPast: |
| 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 |
| |
| 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 os.environ.get('UNSLOTH_RETURN_LOGITS', '0') == '1' else EMPTY_LOGITS |
| loss = None |
| NOT_RETURN_LOGITS = os.environ.get('UNSLOTH_RETURN_LOGITS', '0') == '0' |
| RETURN_HIDDEN_STATES = os.environ.get("UNSLOTH_RETURN_HIDDEN_STATES", "0") == "1" |
| |
| all_locals = locals() |
| n_items = None |
| if 'loss_kwargs' in all_locals: |
| __kwargs = all_locals['loss_kwargs'] |
| if type(__kwargs) is dict: |
| n_items = __kwargs.get("num_items_in_batch", None) |
| if n_items is None: n_items = __kwargs.get("n_items", None) |
| if n_items is None and 'kwargs' in all_locals: |
| __kwargs = all_locals['kwargs'] |
| if type(__kwargs) is dict: |
| n_items = __kwargs.get("num_items_in_batch", None) |
| if n_items is None: n_items = __kwargs.get("n_items", None) |
| if n_items is None: |
| all_locals = all_locals.values() |
| for __kwargs in all_locals: |
| if type(__kwargs) is dict: |
| n_items = __kwargs.get("num_items_in_batch", None) |
| if n_items is None: n_items = __kwargs.get("n_items", None) |
| break |
| pass |
| |
| requires_grad_ = self.lm_head.weight.requires_grad |
| requires_grad_ = requires_grad_ or self.lm_head.weight.dtype == torch.float32 |
| |
| if RETURN_HIDDEN_STATES: |
| logits = hidden_states[:, slice_indices, :] |
| elif labels is None: |
| |
| |
| |
| global INFERENCE_RUNS |
| if torch_dynamo_eval_frame is not None: |
| old_stance = torch_dynamo_eval_frame._stance.stance |
| else: |
| old_stance = None |
| if old_stance is not None and INFERENCE_RUNS == 1: |
| |
| torch_compiler_set_stance(stance = "eager_on_recompile", skip_guard_eval_unsafe = False) |
| if UNSLOTH_ENABLE_LOGGING: |
| logger_compiler.info( |
| f"Unsloth: Removing compiler guards after 1 inference run. "\ |
| f"DYNAMO_STANCE.stance = {torch_dynamo_eval_frame._stance.stance} "\ |
| f"DYNAMO_STANCE.skip_guard_eval_unsafe = {torch_dynamo_eval_frame._stance.skip_guard_eval_unsafe}" |
| ) |
| elif old_stance == "eager_on_recompile": |
| pass |
| elif old_stance == "default" and INFERENCE_RUNS > 1: |
| |
| torch_compiler_set_stance(stance = "default", skip_guard_eval_unsafe = False) |
| if UNSLOTH_ENABLE_LOGGING: |
| logger_compiler.info( |
| f"Unsloth: Reseting guards. "\ |
| f"DYNAMO_STANCE.stance = {torch_dynamo_eval_frame._stance.stance} "\ |
| f"DYNAMO_STANCE.skip_guard_eval_unsafe = {torch_dynamo_eval_frame._stance.skip_guard_eval_unsafe}" |
| ) |
| INFERENCE_RUNS = 0 |
| INFERENCE_RUNS += 1 |
| |
| logits = self.lm_head(hidden_states[:, slice_indices, :]) |
| else: |
| lm_head_weight = self.lm_head.weight |
| lm_head_bias = getattr(self.lm_head, "bias", None) |
| |
| |
| _hidden_states = hidden_states[:, slice_indices, :] |
| torch._dynamo.mark_dynamic(_hidden_states, 1) |
| torch._dynamo.mark_dynamic(labels, 1) |
| if attention_mask is not None: |
| torch._dynamo.mark_dynamic(attention_mask, 1) |
| loss = unsloth_fused_ce_loss( |
| trainer = None, |
| hidden_states = _hidden_states, |
| lm_head_weight = lm_head_weight, |
| lm_head_bias = lm_head_bias, |
| labels = labels, |
| mask = attention_mask, |
| n_items = n_items, |
| scaling = getattr(self, "accelerator_scaler", None), |
| target_gb = None, |
| torch_compile = not UNSLOTH_COMPILE_DISABLE, |
| logit_scale_multiply = () if () != () else 0, |
| logit_scale_divide = () if () != () else 0, |
| logit_softcapping = (self.config.get_text_config().final_logit_softcapping) if (self.config.get_text_config().final_logit_softcapping) != () else 0, |
| ) |
|
|
|
|
| return Gemma4CausalLMOutputWithPast( |
| 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, |
| ) |
|
|
| class Gemma4ForConditionalGeneration(Gemma4PreTrainedModel, GenerationMixin): |
| _tied_weights_keys = {"lm_head.weight": "model.language_model.embed_tokens.weight"} |
| base_model_prefix = "model" |
|
|
| def __init__(self, config: Gemma4Config): |
| super().__init__(config) |
| self.model = Gemma4Model(config) |
| self.lm_head = nn.Linear(config.text_config.hidden_size, config.text_config.vocab_size, bias=False) |
| self.post_init() |
|
|
| def get_input_embeddings(self): |
| return self.model.get_input_embeddings() |
|
|
| def set_input_embeddings(self, value): |
| self.model.set_input_embeddings(value) |
|
|
| 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) |
|
|
|
|
| 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], |
| ) -> Gemma4CausalLMOutputWithPast: |
| return Gemma4ForConditionalGeneration_forward(self, 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, image_position_ids=image_position_ids, video_position_ids=video_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, logits_to_keep=logits_to_keep, **kwargs) |
|
|
| 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, |
| ): |
| |
| 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 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 |
|
|
| 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: |
| if getattr(config.get_text_config(), "use_bidirectional_attention", None) == "vision": |
| |
| return create_causal_mask_mapping( |
| config, |
| inputs_embeds, |
| attention_mask, |
| past_key_values, |
| position_ids, |
| mm_token_type_ids, |
| is_first_iteration=is_first_iteration, |
| **{k: v for k, v in kwargs.items() if k != "pixel_values"}, |
| ) |
| else: |
| |
| return create_masks_for_generate( |
| config, inputs_embeds, attention_mask, past_key_values, position_ids, **kwargs |
| ) |
|
|
|
|
| @torch.compile(fullgraph = False, dynamic = True, options = torch_compile_options) |
| def Gemma4MultimodalEmbedder_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]`. |
| """ |
| embs_normed = self.embedding_pre_projection_norm(inputs_embeds) |
| return self.embedding_projection(embs_normed) |
|
|
| class Gemma4MultimodalEmbedder(nn.Module): |
| """Embeds token ids or soft tokens for multimodal content into language model space.""" |
|
|
| def __init__( |
| self, |
| multimodal_config: Gemma4AudioConfig | Gemma4VisionConfig, |
| text_config: Gemma4TextConfig, |
| ): |
| super().__init__() |
|
|
| self.multimodal_hidden_size = getattr(multimodal_config, "output_proj_dims", multimodal_config.hidden_size) |
| 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 = Gemma4RMSNorm(self.multimodal_hidden_size, eps=self.eps, with_scale=False) |
|
|
| def forward(self, inputs_embeds: torch.Tensor) -> torch.Tensor: |
| return Gemma4MultimodalEmbedder_forward(self, inputs_embeds=inputs_embeds) |
|
|
|
|
| def token_type_ids_mask_function( |
| token_type_ids: torch.Tensor | None, |
| image_group_ids: torch.Tensor | None, |
| ) -> Callable | None: |
| """ |
| This function adds the correct offsets to the `q_idx` and `kv_idx` as the torch API can only accept lengths, |
| not start and end indices. |
| """ |
| |
| if token_type_ids is None: |
| return None |
|
|
| def inner_mask(batch_idx: int, head_idx: int, q_idx: int, kv_idx: int) -> bool: |
| seq_length = image_group_ids.shape[-1] |
|
|
| |
| q_idx_clamped = q_idx.clamp(max=seq_length - 1) |
| kv_idx_clamped = kv_idx.clamp(max=seq_length - 1) |
|
|
| |
| q_group = image_group_ids[batch_idx, q_idx_clamped] |
| kv_group = image_group_ids[batch_idx, kv_idx_clamped] |
| q_group = torch.where(q_idx < seq_length, q_group, -1) |
| kv_group = torch.where(kv_idx < seq_length, kv_group, -1) |
| return (q_group == kv_group) & (q_group >= 0) |
|
|
| return inner_mask |
|
|
|
|
| def create_causal_mask_mapping( |
| 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, |
| pixel_values: torch.FloatTensor | None = None, |
| is_training: bool = False, |
| is_first_iteration: bool | None = None, |
| **kwargs, |
| ) -> dict: |
| """ |
| Overwrites the base `create_masks_for_generate` with `token_type_ids` masking to create the causal mask mapping |
| for all kinds of forward passes. Gemma4 uses a bidirectional mask for images. |
| |
| Uses `pixel_values` as an optional input to disambiguate edge cases. |
| """ |
| if is_training and mm_token_type_ids is None: |
| raise ValueError("`mm_token_type_ids` is required as a model input when training") |
|
|
| 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, |
| } |
| sliding_mask_kwargs = mask_kwargs.copy() |
|
|
| |
| |
| |
| is_first_iteration = ( |
| is_first_iteration |
| if is_first_iteration is not None |
| else (past_key_values is None or not past_key_values.is_initialized or pixel_values is not None) |
| ) |
| if mm_token_type_ids is not None and is_first_iteration: |
| |
| |
|
|
| |
| |
| 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 |
| vision_group_ids = torch.where(is_vision, vision_group_ids, -1) |
| sliding_mask_kwargs["or_mask_function"] = token_type_ids_mask_function( |
| mm_token_type_ids.to(inputs_embeds.device), vision_group_ids |
| ) |
|
|
| return { |
| "full_attention": create_causal_mask(**mask_kwargs), |
| "sliding_attention": create_sliding_window_causal_mask(**sliding_mask_kwargs), |
| } |
|
|