| """
|
| Mamba2 Block wrapper for hybrid architecture.
|
|
|
| Mamba2 improves on Mamba1 with:
|
| - Multi-head SSM (similar to multi-head attention)
|
| - Larger effective state size
|
| - Hardware-efficient SSD (Structured State Space Duality) algorithm
|
| - 2-8x faster than Mamba1
|
|
|
| For embedding (bidirectional), we run Mamba2 in both directions
|
| and combine the outputs.
|
| """
|
|
|
| import torch
|
| import torch.nn as nn
|
| import torch.nn.functional as F
|
| from .utils import RMSNorm
|
|
|
| try:
|
| from mamba_ssm import Mamba2 as Mamba2Core
|
| MAMBA2_AVAILABLE = True
|
| except ImportError:
|
| MAMBA2_AVAILABLE = False
|
|
|
|
|
| class Mamba2Fallback(nn.Module):
|
| """
|
| Pure PyTorch fallback when mamba_ssm is not installed.
|
| Implements simplified SSM: y = SSM(Conv1d(Linear(x)))
|
| Not as fast as CUDA kernel but functionally equivalent.
|
| """
|
|
|
| def __init__(
|
| self,
|
| d_model: int,
|
| d_state: int = 128,
|
| d_conv: int = 4,
|
| expand: int = 2,
|
| headdim: int = 64,
|
| ngroups: int = 8,
|
| ):
|
| super().__init__()
|
| self.d_model = d_model
|
| self.d_inner = d_model * expand
|
| self.d_state = d_state
|
| self.d_conv = d_conv
|
| self.headdim = headdim
|
| self.nheads = self.d_inner // headdim
|
| self.ngroups = ngroups
|
|
|
| assert self.d_inner % headdim == 0, (
|
| f"d_inner ({self.d_inner}) must be divisible by headdim ({headdim})"
|
| )
|
| assert self.nheads % ngroups == 0, (
|
| f"nheads ({self.nheads}) must be divisible by ngroups ({ngroups})"
|
| )
|
|
|
|
|
| self.in_proj = nn.Linear(d_model, self.d_inner * 2, bias=False)
|
|
|
|
|
| self.conv1d = nn.Conv1d(
|
| self.d_inner, self.d_inner,
|
| kernel_size=d_conv, padding=d_conv - 1,
|
| groups=self.d_inner, bias=True,
|
| )
|
|
|
|
|
| self.dt_proj = nn.Linear(self.d_inner, self.nheads, bias=True)
|
|
|
|
|
| A_init = torch.log(torch.rand(self.nheads) * 15 + 1)
|
| self.A_log = nn.Parameter(A_init)
|
| self.D = nn.Parameter(torch.ones(self.nheads))
|
|
|
|
|
| self.B_proj = nn.Linear(self.d_inner, self.ngroups * d_state, bias=False)
|
| self.C_proj = nn.Linear(self.d_inner, self.ngroups * d_state, bias=False)
|
|
|
|
|
| self.out_proj = nn.Linear(self.d_inner, d_model, bias=False)
|
| self.norm = nn.LayerNorm(self.d_inner)
|
|
|
| def _ssm_scan(self, x, dt, A, B, C, D):
|
| """
|
| Parallel Associative Scan implementation of SSD (Structured State Space Duality).
|
| Bypasses the slow O(T) Python loop and avoids Blackwell Triton hangs.
|
| """
|
| orig_dtype = x.dtype
|
| x = x.float()
|
| dt = dt.float()
|
| A = A.float()
|
| B = B.float()
|
| C = C.float()
|
|
|
| B_seq, T, d_inner = x.shape
|
| nheads = self.nheads
|
| headdim = self.headdim
|
|
|
| x = x.view(B_seq, T, nheads, headdim)
|
| dt = F.softplus(dt)
|
| A = -torch.exp(A)
|
|
|
|
|
| log_dA = dt.unsqueeze(-1) * A.view(1, 1, nheads, 1)
|
|
|
|
|
|
|
| cum_log_dA = torch.cumsum(log_dA, dim=1)
|
|
|
|
|
| clg = cum_log_dA.permute(0, 2, 1, 3)
|
|
|
| diff = clg - clg.transpose(-1, -2)
|
| decay_matrix = torch.exp(diff)
|
|
|
|
|
| m_idx = torch.arange(T, device=x.device)
|
| mask = (m_idx.view(-1, 1) >= m_idx.view(1, -1)).to(x.dtype)
|
| decay_matrix = decay_matrix * mask.view(1, 1, T, T)
|
|
|
|
|
| B = B.view(B_seq, T, self.ngroups, self.d_state)
|
| heads_per_group = nheads // self.ngroups
|
| B = B.repeat_interleave(heads_per_group, dim=2)
|
|
|
|
|
| dB = dt.unsqueeze(-1) * B
|
| bx = torch.einsum("bths,bthd->bthsd", dB, x)
|
|
|
|
|
|
|
| bx_flat = bx.permute(0, 2, 1, 3, 4).reshape(B_seq, nheads, T, -1)
|
| h_flat = torch.einsum("bhti,bhis->bhts", decay_matrix, bx_flat)
|
| h = h_flat.view(B_seq, nheads, T, self.d_state, headdim)
|
|
|
|
|
| C = C.view(B_seq, T, self.ngroups, self.d_state)
|
| C = C.repeat_interleave(heads_per_group, dim=2)
|
|
|
|
|
| h_perm = h.permute(0, 2, 1, 3, 4)
|
|
|
| y = torch.einsum("bths,bthsd->bthd", C, h_perm)
|
|
|
|
|
| y = y + D.view(1, 1, nheads, 1) * x
|
|
|
| return y.reshape(B_seq, T, d_inner).to(orig_dtype)
|
|
|
| def forward(self, x: torch.Tensor) -> torch.Tensor:
|
| if not hasattr(self, '_warned') and x.shape[1] > 512:
|
| import warnings
|
| warnings.warn(
|
| f"Mamba2Fallback: sequential SSM scan with seq_len={x.shape[1]} will be slow. "
|
| f"Install mamba_ssm for CUDA-accelerated scan.",
|
| stacklevel=2,
|
| )
|
| self._warned = True
|
|
|
| B, T, D = x.shape
|
|
|
|
|
| xz = self.in_proj(x)
|
| x_ssm, z = xz.chunk(2, dim=-1)
|
|
|
|
|
| x_conv = x_ssm.transpose(1, 2)
|
| x_conv = self.conv1d(x_conv)[:, :, :T]
|
| x_conv = x_conv.transpose(1, 2)
|
| x_conv = F.silu(x_conv)
|
|
|
|
|
| dt = self.dt_proj(x_conv)
|
| B_param = self.B_proj(x_conv)
|
| C_param = self.C_proj(x_conv)
|
|
|
|
|
| y = self._ssm_scan(x_conv, dt, self.A_log, B_param, C_param, self.D)
|
|
|
|
|
| y = y * F.silu(z)
|
| y = self.out_proj(self.norm(y))
|
|
|
| return y
|
|
|
|
|
| class Mamba2Block(nn.Module):
|
| """
|
| Bidirectional Mamba2 block for embedding.
|
| Runs Mamba2 forward + backward, combines outputs.
|
| """
|
|
|
| def __init__(
|
| self,
|
| d_model: int,
|
| d_state: int = 128,
|
| d_conv: int = 4,
|
| expand: int = 2,
|
| headdim: int = 64,
|
| ngroups: int = 8,
|
| bidirectional: bool = True,
|
| ):
|
| super().__init__()
|
| self.bidirectional = bidirectional
|
|
|
| mamba_cls = Mamba2Core if MAMBA2_AVAILABLE else Mamba2Fallback
|
| mamba_kwargs = dict(
|
| d_model=d_model,
|
| d_state=d_state,
|
| d_conv=d_conv,
|
| expand=expand,
|
| headdim=headdim,
|
| ngroups=ngroups,
|
| )
|
|
|
| self.forward_mamba = mamba_cls(**mamba_kwargs)
|
|
|
| if bidirectional:
|
| self.backward_mamba = mamba_cls(**mamba_kwargs)
|
| self.merge_proj = nn.Linear(d_model * 2, d_model, bias=False)
|
|
|
| def forward(self, x: torch.Tensor, attention_mask: torch.Tensor = None) -> torch.Tensor:
|
| mask = None
|
| if attention_mask is not None:
|
| mask = attention_mask.unsqueeze(-1).to(x.dtype)
|
| x = x * mask
|
|
|
| fwd_out = self.forward_mamba(x)
|
|
|
| if self.bidirectional:
|
| x_flip = x.flip(dims=[1])
|
| if mask is not None:
|
| x_flip = x_flip * mask.flip(dims=[1])
|
| bwd_out = self.backward_mamba(x_flip).flip(dims=[1])
|
| out = self.merge_proj(torch.cat([fwd_out, bwd_out], dim=-1))
|
| else:
|
| out = fwd_out
|
|
|
|
|
| if mask is not None:
|
| out = out * mask
|
|
|
| return out
|
|
|