deepx-embedding-v09 / modeling /mamba2_block.py
tungns2408's picture
Upload folder using huggingface_hub
05b48c6 verified
Raw
History Blame Contribute Delete
8.37 kB
"""
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})"
)
# Input projection: x → (z, x_ssm)
self.in_proj = nn.Linear(d_model, self.d_inner * 2, bias=False)
# Conv1d for local context
self.conv1d = nn.Conv1d(
self.d_inner, self.d_inner,
kernel_size=d_conv, padding=d_conv - 1,
groups=self.d_inner, bias=True,
)
# SSM parameters
self.dt_proj = nn.Linear(self.d_inner, self.nheads, bias=True)
# A must be negative for SSM stability: A = -exp(A_log)
# Init A_log ~ log(uniform(1, 16)) per Mamba paper
A_init = torch.log(torch.rand(self.nheads) * 15 + 1) # log(U(1,16))
self.A_log = nn.Parameter(A_init)
self.D = nn.Parameter(torch.ones(self.nheads))
# B, C projections (input-dependent)
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)
# Output projection
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) # (B, T, nheads)
A = -torch.exp(A) # (nheads,)
# 1. Compute dA: A_bar[t] = dt[t] * A
log_dA = dt.unsqueeze(-1) * A.view(1, 1, nheads, 1) # (B, T, H, 1)
# 2. Compute cumulative product of dA using cumsum of logs
# M[t, i] = prod_{j=i+1}^t exp(log_dA[j]) = exp(cumsum(log_dA)[t] - cumsum(log_dA)[i])
cum_log_dA = torch.cumsum(log_dA, dim=1) # (B, T, H, 1)
# clg: (B, H, T, 1)
clg = cum_log_dA.permute(0, 2, 1, 3)
# diff[t, i] = cum_log_dA[t] - cum_log_dA[i] -> (B, H, T, T)
diff = clg - clg.transpose(-1, -2)
decay_matrix = torch.exp(diff)
# Causal mask
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)
# 3. Compute B_bar * x: (B, T, H, S) * (B, T, H, D) -> states
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) # (B, T, H, S)
# dB_x: (B, T, H, S, D)
dB = dt.unsqueeze(-1) * B # (B, T, H, S)
bx = torch.einsum("bths,bthd->bthsd", dB, x)
# 4. Apply decay matrix to states: h_t = sum_{i=0}^t M[t, i] * (dB_i * x_i)
# bx_flat: (B, H, T, S*D)
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)
# 5. Output: y_t = C_t * h_t
C = C.view(B_seq, T, self.ngroups, self.d_state)
C = C.repeat_interleave(heads_per_group, dim=2) # (B, T, H, S)
# h_perm: (B, T, H, S, D)
h_perm = h.permute(0, 2, 1, 3, 4)
# y: (B, T, H, D)
y = torch.einsum("bths,bthsd->bthd", C, h_perm)
# Add D skip connection
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
# Input projection
xz = self.in_proj(x) # (B, T, 2*d_inner)
x_ssm, z = xz.chunk(2, dim=-1)
# Conv1d
x_conv = x_ssm.transpose(1, 2) # (B, d_inner, T)
x_conv = self.conv1d(x_conv)[:, :, :T] # trim padding
x_conv = x_conv.transpose(1, 2) # (B, T, d_inner)
x_conv = F.silu(x_conv)
# SSM parameters from input
dt = self.dt_proj(x_conv)
B_param = self.B_proj(x_conv)
C_param = self.C_proj(x_conv)
# SSM scan
y = self._ssm_scan(x_conv, dt, self.A_log, B_param, C_param, self.D)
# Gate with z
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
# Re-apply mask: SSM hidden state decay để lại artifact tại padding positions
if mask is not None:
out = out * mask
return out