# Copyright 2024 state-spaces/mamba2 org and HuggingFace Inc. team. # # 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. import math from dataclasses import dataclass import torch from torch import nn from transformers.modeling_utils import PreTrainedModel from transformers.utils import ModelOutput, logging from transformers.utils.deprecation import deprecate_kwarg from fla.layers.mamba2 import Mamba2 from fla.models.mamba2.configuration_mamba2 import Mamba2Config from fla.models.utils import FLAGenerationMixin from fla.modules import FusedCrossEntropyLoss, FusedLinearCrossEntropyLoss, RMSNorm from fla.modules.l2warp import l2_warp try: from torch.distributed.tensor import DTensor except (ImportError, AttributeError): DTensor = None try: from transformers.modeling_layers import GradientCheckpointingLayer except ImportError: from fla.models.modeling_layers import GradientCheckpointingLayer logger = logging.get_logger(__name__) class Mamba2Cache: """ Arguments: config: Mamba2Config batch_size: int dtype: torch.dtype device: torch.device Attributes: dtype: (`torch.dtype`): The default `dtype` used to initializing the cache. conv_kernel_size: (`int`): Model's convolution kernel size taken from config. n_groups: (`int`): Model's number of groups taken from the config - similar to tensor parallel in Transformer. state_size: (`int`): Model's SSM state size taken from config. num_heads: (`int`): The number of heads used in the linear attention / SSM. head_dim: (`int`): The respective dimension of the heads used in the linear attention / SSM. intermediate_size: (`int`): Model's intermediate_size based on (expand * hidden_dim) from config. conv_states: (`torch.Tensor`): A tensor of shape `[num_layers, batch_size, conv_kernel_size, intermediate_size + 2 * n_groups * state_size]` that holds convolutional states. ssm_states: (`torch.Tensor`): A tensor of shape `[num_layers, batch_size, num_heads, head_dim, state_size]` that holds ssm states. """ def __init__( self, config: Mamba2Config, batch_size: int, dtype: torch.dtype = torch.float16, device: str | None = None, ): self.dtype = dtype self.conv_kernel_size = config.conv_kernel self.n_groups = config.n_groups self.state_size = config.state_size self.num_heads = config.num_heads self.head_dim = config.head_dim self.intermediate_size = int(config.expand * config.hidden_size) self.conv_states = torch.zeros( config.num_hidden_layers, batch_size, self.intermediate_size + 2 * self.n_groups * self.state_size, self.conv_kernel_size, device=device, dtype=dtype, ) self.ssm_states = torch.zeros( config.num_hidden_layers, batch_size, self.num_heads, self.head_dim, self.state_size, device=device, dtype=dtype, ) def update_conv_state( self, layer_idx: int, new_conv_state: torch.Tensor, cache_init: bool = False, ) -> torch.Tensor: if cache_init: self.conv_states[layer_idx] = new_conv_state.to(self.conv_states.device) else: self.conv_states[layer_idx] = self.conv_states[layer_idx].roll(shifts=-1, dims=-1) self.conv_states[layer_idx][:, :, -1] = new_conv_state[:, 0, :].to(self.conv_states.device) return self.conv_states[layer_idx] def update_ssm_state(self, layer_idx: int, new_ssm_state: torch.Tensor): self.ssm_states[layer_idx] = new_ssm_state.to(self.ssm_states.device) return self.ssm_states[layer_idx] def reset(self): self.conv_states.zero_() self.ssm_states.zero_() class Mamba2Block(GradientCheckpointingLayer): def __init__(self, config, layer_idx): super().__init__() self.config = config self.layer_idx = layer_idx self.residual_in_fp32 = config.residual_in_fp32 self.norm = RMSNorm(config.hidden_size, eps=config.norm_eps) self.mixer = Mamba2( num_heads=config.num_heads, head_dim=config.head_dim, hidden_size=config.hidden_size, state_size=config.state_size, expand=config.expand, n_groups=config.n_groups, conv_kernel=config.conv_kernel, use_conv_bias=config.use_conv_bias, hidden_act=config.hidden_act, rms_norm=config.rms_norm, chunk_size=config.chunk_size, time_step_rank=config.time_step_rank, time_step_limit=config.time_step_limit, time_step_min=config.time_step_min, time_step_max=config.time_step_max, use_bias=config.use_bias, norm_eps=config.norm_eps, layer_idx=layer_idx, ) def forward( self, hidden_states, cache_params: Mamba2Cache | None = None, cache_position: torch.LongTensor | None = None, attention_mask: torch.Tensor | None = None, ): residual = hidden_states hidden_states = self.norm(hidden_states) if self.residual_in_fp32: residual = residual.to(torch.float32) hidden_states = self.mixer( hidden_states, cache_params=cache_params, cache_position=cache_position, attention_mask=attention_mask, ) hidden_states = residual + hidden_states if self.residual_in_fp32: hidden_states = hidden_states.to(dtype=self.norm.weight.dtype) return hidden_states class Mamba2PreTrainedModel(PreTrainedModel): """ An abstract class to handle weights initialization and a simple interface for downloading and loading pretrained models. """ config_class = Mamba2Config base_model_prefix = "backbone" _no_split_modules = ["Mamba2Block"] supports_gradient_checkpointing = True _is_stateful = True def _init_weights( self, module: nn.Module, num_residuals_per_layer: int = 1, ): """Initialize the weights.""" if isinstance(module, Mamba2): # --- A_log --- A = torch.arange(1, module.num_heads + 1) with torch.no_grad(): if not isinstance(module.A_log, DTensor): module.A_log.copy_(torch.log(A)) else: logger.warning_once("`A_log` is a DTensor, skipping initialization") module.A_log._no_weight_decay = True # --- D --- nn.init.ones_(module.D) module.D._no_weight_decay = True # --- dt_bias --- dt = torch.exp( torch.rand(self.config.num_heads) * (math.log(self.config.time_step_max) - math.log(self.config.time_step_min)) + math.log(self.config.time_step_min), ).clamp(min=self.config.time_step_floor) # Inverse of softplus: https://github.com/pytorch/pytorch/issues/72759 inv_dt = dt + torch.log(-torch.expm1(-dt)) with torch.no_grad(): if not isinstance(module.dt_bias, DTensor): module.dt_bias.copy_(inv_dt) else: logger.warning_once("`dt_bias` is a DTensor, skipping initialization") module.dt_bias._no_reinit = True elif isinstance(module, (nn.Linear, nn.Conv1d)): # Slightly different from the TF version which uses truncated_normal for initialization # cf https://github.com/pytorch/pytorch/pull/5617 nn.init.normal_(module.weight, mean=0.0, std=self.config.initializer_range) if module.bias is not None: nn.init.zeros_(module.bias) # guard against deprecated behavior if hasattr(module.bias, "_no_reinit"): raise ValueError("This is not supposed to happen") elif isinstance(module, nn.Embedding): nn.init.normal_(module.weight, mean=0.0, std=self.config.initializer_range) elif hasattr(module, 'reset_parameters'): module.reset_parameters() if self.config.rescale_prenorm_residual: # Reinitialize selected weights subject to the OpenAI GPT-2 Paper Scheme: # > A modified initialization which accounts for the accumulation on the residual path with model depth. Scale # > the weights of residual layers at initialization by a factor of 1/√N where N is the # of residual layers. # > -- GPT-2 :: https://openai.com/blog/better-language-models/ # # Reference (Megatron-LM): https://github.com/NVIDIA/Megatron-LM/blob/main/megatron/model/gpt_model.py p = None if hasattr(module, 'o_proj'): # p = module.o_proj.weight # guard against deprecated behavior raise ValueError("This is not supposed to happen") elif hasattr(module, 'out_proj'): p = module.out_proj.weight elif hasattr(module, 'down_proj'): p = module.down_proj.weight if p is not None: # Special Scaled Initialization --> There are 2 Layer Norms per Transformer Block # Following Pytorch init, except scale by 1/sqrt(2 * n_layer) # We need to reinit p since this code could be called multiple times # Having just p *= scale would repeatedly scale it down nn.init.kaiming_uniform_(p, a=math.sqrt(5)) with torch.no_grad(): p /= math.sqrt(num_residuals_per_layer * self.config.num_hidden_layers) @dataclass # Copied from transformers.models.mamba.modeling_mamba.MambaOutput with MAMBA->MAMBA2,Mamba->Mamba2 class Mamba2Output(ModelOutput): """ Class for the MAMBA2 model outputs. Args: last_hidden_state (`torch.FloatTensor` of shape `(batch_size, sequence_length, hidden_size)`): Sequence of hidden-states at the output of the last layer of the model. cache_params (`Mamba2Cache`): The state of the model at the last time step. Can be used in a forward method with the next `input_ids` to avoid providing the old `input_ids`. Includes both the State space model state matrices after the selective scan, and the Convolutional states hidden_states (`tuple(torch.FloatTensor)`, *optional*, returned when `output_hidden_states=True` is passed or when `config.output_hidden_states=True`): Tuple of `torch.FloatTensor` (one for the output of the embeddings, if the model has an embedding layer, + one for the output of each layer) of shape `(batch_size, sequence_length, hidden_size)`. Hidden-states of the model at the output of each layer plus the optional initial embedding outputs. """ last_hidden_state: torch.FloatTensor | None = None cache_params: Mamba2Cache | None = None hidden_states: tuple[torch.FloatTensor] | None = None @dataclass # Copied from transformers.models.mamba.modeling_mamba.MambaCausalLMOutput with Mamba->Mamba2 class Mamba2CausalLMOutput(ModelOutput): """ Base class for causal language model (or autoregressive) outputs. Args: 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.vocab_size)`): Prediction scores of the language modeling head (scores for each vocabulary token before SoftMax). cache_params (`Mamba2Cache`): The state of the model at the last time step. Can be used in a forward method with the next `input_ids` to avoid providing the old `input_ids`. Includes both the State space model state matrices after the selective scan, and the Convolutional states hidden_states (`tuple(torch.FloatTensor)`, *optional*, returned when `output_hidden_states=True` is passed or when `config.output_hidden_states=True`): Tuple of `torch.FloatTensor` (one for the output of the embeddings, if the model has an embedding layer, + one for the output of each layer) of shape `(batch_size, sequence_length, hidden_size)`. Hidden-states of the model at the output of each layer plus the optional initial embedding outputs. """ loss: torch.FloatTensor | None = None logits: torch.FloatTensor | None = None cache_params: Mamba2Cache | None = None hidden_states: tuple[torch.FloatTensor] | None = None class Mamba2Model(Mamba2PreTrainedModel): def __init__(self, config): super().__init__(config) self.embeddings = nn.Embedding(config.vocab_size, config.hidden_size) self.layers = nn.ModuleList([Mamba2Block(config, layer_idx=idx) for idx in range(config.num_hidden_layers)]) self.gradient_checkpointing = False self.norm_f = RMSNorm(config.hidden_size, eps=config.norm_eps) # Initialize weights and apply final processing self._register_load_state_dict_pre_hook(self.load_hook) self.post_init() def load_hook(self, state_dict, prefix, *args): for k in state_dict: if "embedding." in k: state_dict[k.replace("embedding.", "embeddings.")] = state_dict.pop(k) break def get_input_embeddings(self): return self.embeddings def set_input_embeddings(self, new_embeddings): self.embeddings = new_embeddings def forward( self, input_ids: torch.LongTensor | None = None, inputs_embeds: torch.LongTensor | None = None, cache_params: Mamba2Cache | None = None, use_cache: bool | None = None, output_hidden_states: bool | None = None, return_dict: bool | None = None, cache_position: torch.LongTensor | None = None, attention_mask: torch.Tensor | None = None, **kwargs, ) -> tuple | Mamba2Output: output_hidden_states = ( output_hidden_states if output_hidden_states is not None else self.config.output_hidden_states ) use_cache = use_cache if use_cache is not None else (self.config.use_cache if not self.training else False) return_dict = return_dict if return_dict is not None else self.config.use_return_dict if (input_ids is None) ^ (inputs_embeds is not None): # ^ is python for xor raise ValueError("You must specify exactly one of input_ids or inputs_embeds") if inputs_embeds is None: inputs_embeds = self.embeddings(input_ids) if use_cache: if cache_params is None: cache_params = Mamba2Cache( self.config, inputs_embeds.size(0), device=inputs_embeds.device, dtype=inputs_embeds.dtype, ) cache_position = torch.arange(0, self.config.conv_kernel, device=inputs_embeds.device) elif cache_position is None: # cases when we do manual forward instead of using `model.generate` which will initiate # `cache_position` and makes sure it is not None, throw error here instead of doing some # hack to conjecture the current cache position raise ValueError( "You have to specify the `cache_position` manually when `use_cache=True` and `cache_params` is passed, " "you don't have to pass a `cache_params` if you are in prefilling stage because in that case it will " "be initialized for you automatically", ) else: cache_params = None hidden_states = inputs_embeds all_hidden_states = () if output_hidden_states else None for mixer_block in self.layers: hidden_states = mixer_block( hidden_states, cache_params=cache_params, cache_position=cache_position, attention_mask=attention_mask, ) if output_hidden_states: all_hidden_states = all_hidden_states + (hidden_states,) hidden_states = self.norm_f(hidden_states) if output_hidden_states: all_hidden_states = all_hidden_states + (hidden_states,) if not return_dict: return tuple(v for v in [hidden_states, cache_params, all_hidden_states] if v is not None) return Mamba2Output( last_hidden_state=hidden_states, cache_params=cache_params if use_cache else None, hidden_states=all_hidden_states, ) class Mamba2ForCausalLM(Mamba2PreTrainedModel, FLAGenerationMixin): _tied_weights_keys = [] def __init__(self, config): super().__init__(config) self.backbone = Mamba2Model(config) self.lm_head = nn.Linear(config.hidden_size, config.vocab_size, bias=False) self.criterion = None # Initialize weights and apply final processing self.post_init() def get_output_embeddings(self): return self.lm_head def set_output_embeddings(self, new_embeddings): self.lm_head = new_embeddings def get_input_embeddings(self): return self.backbone.get_input_embeddings() def set_input_embeddings(self, new_embeddings): return self.backbone.set_input_embeddings(new_embeddings) @deprecate_kwarg("num_logits_to_keep", version="4.50", new_name="logits_to_keep") def forward( self, input_ids: torch.LongTensor | None = None, inputs_embeds: torch.FloatTensor | None = None, cache_params: Mamba2Cache | None = None, labels: torch.LongTensor | None = None, output_hidden_states: bool | None = None, return_dict: bool | None = None, use_cache: bool | None = None, cache_position: torch.Tensor | None = None, attention_mask: torch.Tensor | None = None, logits_to_keep: int | None = 0, **kwargs, # for now we need this for generation ) -> tuple | Mamba2CausalLMOutput: r""" labels (`torch.LongTensor` of shape `(batch_size, sequence_length)`, *optional*): Labels for language modeling. Note that the labels **are shifted** inside the model, i.e. you can set `labels = input_ids` Indices are selected in `[-100, 0, ..., config.vocab_size]` All labels set to `-100` are ignored (masked), the loss is only computed for labels in `[0, ..., config.vocab_size]` """ return_dict = return_dict if return_dict is not None else self.config.use_return_dict outputs = self.backbone( input_ids, cache_params=cache_params, inputs_embeds=inputs_embeds, output_hidden_states=output_hidden_states, return_dict=return_dict, use_cache=use_cache, cache_position=cache_position, attention_mask=attention_mask, ) hidden_states = outputs[0] loss, logits = None, None if not self.config.fuse_linear_cross_entropy or labels is None: logits = self.lm_head(hidden_states if logits_to_keep is None else hidden_states[:, -logits_to_keep:]) if labels is not None: if getattr(self, 'criterion', None) is None: if self.config.fuse_linear_cross_entropy: criterion = FusedLinearCrossEntropyLoss(use_l2warp=self.config.use_l2warp) elif self.config.fuse_cross_entropy: criterion = FusedCrossEntropyLoss(inplace_backward=True) else: criterion = nn.CrossEntropyLoss() else: criterion = self.criterion labels = labels.to(hidden_states.device) labels = torch.cat((labels[..., 1:], torch.full_like(labels[:, :1], criterion.ignore_index)), 1) if self.config.fuse_linear_cross_entropy: loss = criterion(hidden_states, labels, self.lm_head.weight, self.lm_head.bias) else: loss = criterion(logits.view(labels.numel(), -1), labels.view(-1)) loss = l2_warp(loss, logits) if self.config.use_l2warp else loss if not return_dict: output = (logits,) + outputs[1:] return (loss,) + output if loss is not None else output return Mamba2CausalLMOutput( loss=loss, logits=logits, cache_params=outputs.cache_params, hidden_states=outputs.hidden_states, )