multimodalart's picture
multimodalart HF Staff
Upload folder using huggingface_hub
ba6c090 verified
Raw History Blame Contribute Delete
7.83 kB
"""PyTorch Mamba-3 recurrence for CPU and CUDA inference.
Adapted by Continuous Intelligence from Mamba's module and
SISO/MIMO reference calculations:
https://github.com/state-spaces/mamba/tree/a14b1dff0454a3bc27d9eb31355dc01e4b2490ec
RMS normalisation is adapted from xLSTM Large:
https://github.com/NX-AI/xlstm/blob/ab22eadbd245f293dd8dec38ed29963d73758a12/xlstm/xlstm_large/components.py
Copyright (c) 2026, Dao AI Lab, Goombalab.
Copyright 2024 NXAI GmbH and its affiliates 2024
Upstream portions are licensed under Apache-2.0; see NOTICE.
"""
from __future__ import annotations
import math
import torch
import torch.nn.functional as F
from einops import rearrange
from torch import Tensor, nn
class RMSNorm(nn.Module):
"""RMS norm with optional float32 reductions."""
def __init__(
self,
num_features: int,
eps: float = 1e-06,
force_float32_reductions: bool = True,
use_bias: bool = False,
) -> None:
super().__init__()
self.eps = eps
self.force_float32_reductions = force_float32_reductions
self.weight = nn.Parameter(torch.ones(num_features))
self.bias = nn.Parameter(torch.zeros(num_features)) if use_bias else None
def forward(self, x: Tensor) -> Tensor:
in_dtype = x.dtype
if self.force_float32_reductions:
x = x.float()
x = x * torch.rsqrt(x.pow(2).mean(dim=-1, keepdim=True) + self.eps)
x = x.to(in_dtype)
x = x * self.weight
if self.bias is not None:
x = x + self.bias
return x
def _rotate_pairs(x: Tensor, cos: Tensor, sin: Tensor, interleaved: bool) -> Tensor:
"""Rotate pairs of the last axis; angles beyond ``cos`` leave their pairs unrotated."""
if interleaved:
pairs = x.view(*x.shape[:-1], -1, 2)
first, second = (pairs[..., 0], pairs[..., 1])
else:
half = x.shape[-1] // 2
first, second = (x[..., :half], x[..., half:])
if cos.shape[-1] < first.shape[-1]:
pad = first.shape[-1] - cos.shape[-1]
cos = F.pad(cos, (0, pad), value=1.0)
sin = F.pad(sin, (0, pad), value=0.0)
rotated_first = first * cos - second * sin
rotated_second = first * sin + second * cos
if interleaved:
return torch.stack([rotated_first, rotated_second], dim=-1).view_as(x)
return torch.cat([rotated_first, rotated_second], dim=-1)
class Mamba3Reference(nn.Module):
"""Mamba-3 block evaluated step by step in plain PyTorch."""
def __init__(
self,
d_model: int,
d_state: int = 128,
expand: int = 2,
headdim: int = 64,
is_mimo: bool = False,
mimo_rank: int = 4,
) -> None:
super().__init__()
if type(d_state) is not int or d_state < 4 or d_state % 2:
raise ValueError("mamba_state_dim must be an even integer of at least 4")
self.d_state = d_state
self.is_mimo = is_mimo
self.mimo_rank = mimo_rank if is_mimo else 1
self.d_inner = expand * d_model
if self.d_inner % headdim != 0:
raise ValueError(f"expand * d_model {self.d_inner} not divisible by headdim {headdim}")
self.headdim = headdim
self.nheads = self.d_inner // headdim
split = d_state // 2 - d_state // 2 % 2
self.num_rope_angles = split // 2
bc = self.d_state * self.mimo_rank
d_in_proj = 2 * self.d_inner + 2 * bc + 3 * self.nheads + self.num_rope_angles
self.in_proj = nn.Linear(d_model, d_in_proj, bias=False)
dt = torch.exp(
torch.rand(self.nheads) * (math.log(0.1) - math.log(0.001)) + math.log(0.001)
).clamp(min=1e-4)
self.dt_bias = nn.Parameter(dt + torch.log(-torch.expm1(-dt)))
self.B_bias = nn.Parameter(torch.ones(self.nheads, self.mimo_rank, d_state))
self.C_bias = nn.Parameter(torch.ones(self.nheads, self.mimo_rank, d_state))
self.B_norm = RMSNorm(d_state, eps=1e-05)
self.C_norm = RMSNorm(d_state, eps=1e-05)
if is_mimo:
shape = (self.nheads, self.mimo_rank, headdim)
self.mimo_x = nn.Parameter(torch.ones(shape) / self.mimo_rank)
self.mimo_z = nn.Parameter(torch.ones(shape))
self.mimo_o = nn.Parameter(torch.ones(shape) / self.mimo_rank)
self.D = nn.Parameter(torch.ones(self.nheads))
self.out_proj = nn.Linear(self.d_inner, d_model, bias=False)
def forward(self, u: Tensor) -> Tensor:
batch, seq, _ = u.shape
bc = self.d_state * self.mimo_rank
z, x, B, C, dd_dt, dd_A, trap, angles = torch.split(
self.in_proj(u),
[
self.d_inner,
self.d_inner,
bc,
bc,
self.nheads,
self.nheads,
self.nheads,
self.num_rope_angles,
],
dim=-1,
)
z = rearrange(z, "b l (h p) -> b l h p", p=self.headdim).to(torch.float32)
x = rearrange(x, "b l (h p) -> b l h p", p=self.headdim).to(torch.float32)
B = rearrange(B, "b l (r g n) -> b l r g n", r=self.mimo_rank, g=1)
C = rearrange(C, "b l (r g n) -> b l r g n", r=self.mimo_rank, g=1)
A = torch.clamp(-F.softplus(dd_A.to(torch.float32)), max=-1e-4)
DT = F.softplus(dd_dt.to(torch.float32) + self.dt_bias)
alpha = torch.exp(A * DT)
gate = torch.sigmoid(trap.to(torch.float32))
beta = (1.0 - gate) * DT * alpha
gamma = gate * DT
k = self.B_norm(B).expand(-1, -1, -1, self.nheads, -1).to(torch.float32)
q = self.C_norm(C).expand(-1, -1, -1, self.nheads, -1).to(torch.float32)
k = k + rearrange(self.B_bias, "h r n -> r h n")
q = q + rearrange(self.C_bias, "h r n -> r h n")
turns = torch.tanh(angles.to(torch.float32))[:, :, None, :] * DT[..., None] * math.pi
cumulative = torch.cumsum(turns, dim=1)
cos, sin = (torch.cos(cumulative)[:, :, None], torch.sin(cumulative)[:, :, None])
q = _rotate_pairs(q, cos, sin, interleaved=not self.is_mimo)
k = _rotate_pairs(k, cos, sin, interleaved=not self.is_mimo)
if self.is_mimo:
v = torch.einsum("blhp,hrp->blrhp", x, self.mimo_x.to(torch.float32))
gates = torch.einsum("blhp,hrp->blrhp", z, self.mimo_z.to(torch.float32))
else:
v, gates = (x[:, :, None], z[:, :, None])
state = u.new_zeros((batch, self.nheads, self.headdim, self.d_state), dtype=torch.float32)
previous = torch.zeros_like(state)
outputs = []
for t in range(seq):
current = torch.einsum("brhn,brhp->bhpn", k[:, t], v[:, t])
state = (
alpha[:, t, :, None, None] * state
+ beta[:, t, :, None, None] * previous
+ gamma[:, t, :, None, None] * current
)
previous = current
step = torch.einsum("bhpn,brhn->brhp", state, q[:, t])
step = step + self.D[None, None, :, None] * v[:, t]
step = step * F.silu(gates[:, t])
if self.is_mimo:
step = torch.einsum("brhp,hrp->bhp", step, self.mimo_o.to(torch.float32))
else:
step = step[:, 0]
outputs.append(step)
y = rearrange(torch.stack(outputs, dim=1), "b l h p -> b l (h p)")
return self.out_proj(y.to(u.dtype))
class Mamba3(Mamba3Reference):
"""Checkpoint-compatible cell with fused CUDA inference when configured by the loader."""
fused = False
def forward(self, u: Tensor) -> Tensor:
if self.fused and u.is_cuda and not torch.is_grad_enabled():
from ._cuda import mamba3_forward
return mamba3_forward(self, u)
return super().forward(u)