Mamba.py 13 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389
  1. """Simple, minimal implementation of Mamba in one file of PyTorch.
  2. Suggest reading the following before/while reading the code:
  3. [1] Mamba: Linear-Time Sequence Modeling with Selective State Spaces (Albert Gu and Tri Dao)
  4. https://arxiv.org/abs/2312.00752
  5. [2] The Annotated S4 (Sasha Rush and Sidd Karamcheti)
  6. https://srush.github.io/annotated-s4
  7. Glossary:
  8. b: batch size (`B` in Mamba paper [1] Algorithm 2)
  9. l: sequence length (`L` in [1] Algorithm 2)
  10. d or d_model: hidden dim
  11. n or d_state: latent state dim (`N` in [1] Algorithm 2)
  12. expand: expansion factor (`E` in [1] Section 3.4)
  13. d_in or d_inner: d * expand (`D` in [1] Algorithm 2)
  14. A, B, C, D: state space parameters (See any state space representation formula)
  15. (B, C are input-dependent (aka selective, a key innovation in Mamba); A, D are not)
  16. Δ or delta: input-dependent step size
  17. dt_rank: rank of Δ (See [1] Section 3.6 "Parameterization of ∆")
  18. """
  19. from __future__ import annotations
  20. import math
  21. import torch
  22. import torch.nn as nn
  23. import torch.nn.functional as F
  24. from dataclasses import dataclass
  25. from einops import rearrange, repeat, einsum
  26. class Mamba(nn.Module):
  27. def __init__(
  28. self,
  29. d_model: int,
  30. n_layer: int,
  31. input_dim: int = 1,
  32. output_dim: int = 1,
  33. d_state: int = 16,
  34. expand: int = 2,
  35. dt_rank="auto",
  36. d_conv: int = 4,
  37. conv_bias: bool = True,
  38. bias: bool = False,
  39. return_last: bool = False,
  40. ):
  41. """
  42. Mamba for continuous time-series regression.
  43. Args:
  44. input_dim: 每个时间步的输入特征数,例如单变量就是 1,多变量就是 len(features)
  45. output_dim: 每个时间步的输出维度,一般回归预测为 1
  46. return_last:
  47. False -> 输出 [B, L, output_dim]
  48. True -> 输出 [B, output_dim],只取最后一个时间步
  49. """
  50. super().__init__()
  51. self.d_model = d_model
  52. self.n_layer = n_layer
  53. self.input_dim = input_dim
  54. self.output_dim = output_dim
  55. self.d_state = d_state
  56. self.expand = expand
  57. self.dt_rank = dt_rank
  58. self.d_conv = d_conv
  59. self.conv_bias = conv_bias
  60. self.bias = bias
  61. self.return_last = return_last
  62. self.d_inner = int(self.expand * self.d_model)
  63. if self.dt_rank == "auto":
  64. self.dt_rank = math.ceil(self.d_model / 16)
  65. # 连续气象/光伏特征输入:Linear 投影到 d_model
  66. self.input_proj = nn.Linear(self.input_dim, self.d_model)
  67. self.layers = nn.ModuleList([
  68. ResidualBlock(
  69. d_model=self.d_model,
  70. conv_bias=self.conv_bias,
  71. bias=self.bias,
  72. d_conv=self.d_conv,
  73. d_inner=self.d_inner,
  74. dt_rank=self.dt_rank,
  75. d_state=self.d_state
  76. )
  77. for _ in range(self.n_layer)
  78. ])
  79. self.norm_f = RMSNorm(self.d_model)
  80. # 回归输出层
  81. self.output_proj = nn.Linear(self.d_model, self.output_dim)
  82. def forward(self, x):
  83. """
  84. Args:
  85. x: shape [B, L] 或 [B, L, C]
  86. Returns:
  87. if return_last=False: [B, L, output_dim]
  88. if return_last=True: [B, output_dim]
  89. """
  90. x = x.float()
  91. # 如果输入是 [B, L],说明是单变量序列,自动扩展成 [B, L, 1]
  92. if x.dim() == 2:
  93. x = x.unsqueeze(-1)
  94. if x.dim() != 3:
  95. raise ValueError(f"Expected input shape [B, L, C], but got {x.shape}")
  96. if x.shape[-1] != self.input_dim:
  97. raise ValueError(
  98. f"Input feature dim mismatch: model expects input_dim={self.input_dim}, "
  99. f"but got x.shape[-1]={x.shape[-1]}. Full input shape: {x.shape}"
  100. )
  101. # [B, L, input_dim] -> [B, L, d_model]
  102. x = self.input_proj(x)
  103. for layer in self.layers:
  104. x = layer(x)
  105. x = self.norm_f(x)
  106. # [B, L, d_model] -> [B, L, output_dim]
  107. out = self.output_proj(x)
  108. if self.return_last:
  109. out = out[:, -1, :]
  110. B , L , C = out.shape
  111. out = out.reshape(B,-1)
  112. return out
  113. class ResidualBlock(nn.Module):
  114. def __init__(self, d_model, conv_bias, bias, d_conv, d_inner, dt_rank, d_state):
  115. """Simple block wrapping Mamba block with normalization and residual connection."""
  116. super().__init__()
  117. self.d_model = d_model # Fix: store d_model as attribute so it can be used in RMSNorm init below
  118. self.mixer = MambaBlock(d_model, conv_bias, bias, d_conv, d_inner, dt_rank, d_state)
  119. self.norm = RMSNorm(self.d_model)
  120. def forward(self, x):
  121. """
  122. Args:
  123. x: shape (b, l, d) (See Glossary at top for definitions of b, l, d_in, n...)
  124. Returns:
  125. output: shape (b, l, d)
  126. Official Implementation:
  127. Block.forward(), https://github.com/state-spaces/mamba/blob/main/mamba_ssm/modules/mamba_simple.py#L297
  128. Note: the official repo chains residual blocks that look like
  129. [Add -> Norm -> Mamba] -> [Add -> Norm -> Mamba] -> [Add -> Norm -> Mamba] -> ...
  130. where the first Add is a no-op. This is purely for performance reasons as this
  131. allows them to fuse the Add->Norm.
  132. We instead implement our blocks as the more familiar, simpler, and numerically equivalent
  133. [Norm -> Mamba -> Add] -> [Norm -> Mamba -> Add] -> [Norm -> Mamba -> Add] -> ....
  134. """
  135. output = self.mixer(self.norm(x)) + x
  136. return output
  137. class MambaBlock(nn.Module):
  138. def __init__(self,
  139. d_model,
  140. conv_bias,
  141. bias,
  142. d_conv,
  143. d_inner,
  144. dt_rank,
  145. d_state
  146. ):
  147. """A single Mamba block, as described in Figure 3 in Section 3.4 in the Mamba paper [1]."""
  148. super().__init__()
  149. self.d_model = d_model
  150. self.conv_bias = conv_bias
  151. self.bias = bias
  152. self.d_conv = d_conv
  153. self.d_inner = d_inner
  154. self.dt_rank = dt_rank
  155. self.d_state = d_state
  156. self.in_proj = nn.Linear(self.d_model, self.d_inner * 2, bias=self.bias)
  157. self.conv1d = nn.Conv1d(
  158. in_channels=self.d_inner,
  159. out_channels=self.d_inner,
  160. bias=self.conv_bias,
  161. kernel_size=self.d_conv,
  162. groups=self.d_inner,
  163. padding=self.d_conv - 1,
  164. )
  165. # x_proj takes in `x` and outputs the input-specific Δ, B, C
  166. self.x_proj = nn.Linear(self.d_inner, self.dt_rank + self.d_state * 2, bias=False)
  167. # dt_proj projects Δ from dt_rank to d_in
  168. self.dt_proj = nn.Linear(self.dt_rank, self.d_inner, bias=True)
  169. A = repeat(torch.arange(1, self.d_state + 1), 'n -> d n', d=self.d_inner)
  170. self.A_log = nn.Parameter(torch.log(A))
  171. self.D = nn.Parameter(torch.ones(self.d_inner))
  172. self.out_proj = nn.Linear(self.d_inner, self.d_model, bias=self.bias)
  173. def forward(self, x):
  174. """Mamba block forward. This looks the same as Figure 3 in Section 3.4 in the Mamba paper [1].
  175. Args:
  176. x: shape (b, l, d) (See Glossary at top for definitions of b, l, d_in, n...)
  177. Returns:
  178. output: shape (b, l, d)
  179. Official Implementation:
  180. class Mamba, https://github.com/state-spaces/mamba/blob/main/mamba_ssm/modules/mamba_simple.py#L119
  181. mamba_inner_ref(), https://github.com/state-spaces/mamba/blob/main/mamba_ssm/ops/selective_scan_interface.py#L311
  182. """
  183. (b, l, d) = x.shape
  184. x_and_res = self.in_proj(x) # shape (b, l, 2 * d_in)
  185. (x, res) = x_and_res.split(split_size=[self.d_inner, self.d_inner], dim=-1)
  186. x = rearrange(x, 'b l d_in -> b d_in l')
  187. x = self.conv1d(x)[:, :, :l]
  188. x = rearrange(x, 'b d_in l -> b l d_in')
  189. x = F.silu(x)
  190. y = self.ssm(x)
  191. y = y * F.silu(res)
  192. output = self.out_proj(y)
  193. return output
  194. def ssm(self, x):
  195. """Runs the SSM. See:
  196. - Algorithm 2 in Section 3.2 in the Mamba paper [1]
  197. - run_SSM(A, B, C, u) in The Annotated S4 [2]
  198. Args:
  199. x: shape (b, l, d_in) (See Glossary at top for definitions of b, l, d_in, n...)
  200. Returns:
  201. output: shape (b, l, d_in)
  202. Official Implementation:
  203. mamba_inner_ref(), https://github.com/state-spaces/mamba/blob/main/mamba_ssm/ops/selective_scan_interface.py#L311
  204. """
  205. (d_in, n) = self.A_log.shape
  206. # Compute ∆ A B C D, the state space parameters.
  207. # A, D are input independent (see Mamba paper [1] Section 3.5.2 "Interpretation of A" for why A isn't selective)
  208. # ∆, B, C are input-dependent (this is a key difference between Mamba and the linear time invariant S4,
  209. # and is why Mamba is called **selective** state spaces)
  210. A = -torch.exp(self.A_log.float()) # shape (d_in, n)
  211. D = self.D.float()
  212. x_dbl = self.x_proj(x) # (b, l, dt_rank + 2*n)
  213. # BUG FIX: typo, should be self.dt_rank (not self.self.dt_rank)
  214. (delta, B, C) = x_dbl.split(split_size=[self.dt_rank, n, n], dim=-1) # delta: (b, l, dt_rank). B, C: (b, l, n)
  215. delta = F.softplus(self.dt_proj(delta)) # (b, l, d_in)
  216. y = self.selective_scan(x, delta, A, B, C, D) # This is similar to run_SSM(A, B, C, u) in The Annotated S4 [2]
  217. return y
  218. def selective_scan(self, u, delta, A, B, C, D):
  219. """Does selective scan algorithm. See:
  220. - Section 2 State Space Models in the Mamba paper [1]
  221. - Algorithm 2 in Section 3.2 in the Mamba paper [1]
  222. - run_SSM(A, B, C, u) in The Annotated S4 [2]
  223. This is the classic discrete state space formula:
  224. x(t + 1) = Ax(t) + Bu(t)
  225. y(t) = Cx(t) + Du(t)
  226. except B and C (and the step size delta, which is used for discretization) are dependent on the input x(t).
  227. Args:
  228. u: shape (b, l, d_in) (See Glossary at top for definitions of b, l, d_in, n...)
  229. delta: shape (b, l, d_in)
  230. A: shape (d_in, n)
  231. B: shape (b, l, n)
  232. C: shape (b, l, n)
  233. D: shape (d_in,)
  234. Returns:
  235. output: shape (b, l, d_in)
  236. Official Implementation:
  237. selective_scan_ref(), https://github.com/state-spaces/mamba/blob/main/mamba_ssm/ops/selective_scan_interface.py#L86
  238. Note: I refactored some parts out of `selective_scan_ref` out, so the functionality doesn't match exactly.
  239. """
  240. (b, l, d_in) = u.shape
  241. n = A.shape[1]
  242. # Discretize continuous parameters (A, B)
  243. # - A is discretized using zero-order hold (ZOH) discretization (see Section 2 Equation 4 in the Mamba paper [1])
  244. # - B is discretized using a simplified Euler discretization instead of ZOH. From a discussion with authors:
  245. # "A is the more important term and the performance doesn't change much with the simplification on B"
  246. deltaA = torch.exp(einsum(delta, A, 'b l d_in, d_in n -> b l d_in n'))
  247. deltaB_u = einsum(delta, B, u, 'b l d_in, b l n, b l d_in -> b l d_in n')
  248. # Perform selective scan (see scan_SSM() in The Annotated S4 [2])
  249. # Note that the below is sequential, while the official implementation does a much faster parallel scan that
  250. # is additionally hardware-aware (like FlashAttention).
  251. x = torch.zeros((b, d_in, n), device=deltaA.device)
  252. ys = []
  253. for i in range(l):
  254. x = deltaA[:, i] * x + deltaB_u[:, i]
  255. y = einsum(x, C[:, i, :], 'b d_in n, b n -> b d_in')
  256. ys.append(y)
  257. y = torch.stack(ys, dim=1) # shape (b, l, d_in)
  258. y = y + u * D
  259. return y
  260. class RMSNorm(nn.Module):
  261. def __init__(self,
  262. d_model: int,
  263. eps: float = 1e-5):
  264. super().__init__()
  265. self.eps = eps
  266. self.weight = nn.Parameter(torch.ones(d_model))
  267. def forward(self, x):
  268. output = x * torch.rsqrt(x.pow(2).mean(-1, keepdim=True) + self.eps) * self.weight
  269. return output
  270. if __name__ == "__main__":
  271. # 定义模型参数
  272. d_model = 32
  273. n_layer = 2
  274. vocab_size = 10
  275. d_state = 16
  276. expand = 2
  277. d_conv = 3
  278. # 实例化Mamba模型
  279. model = Mamba(
  280. d_model = d_model,
  281. n_layer = n_layer,
  282. input_dim = vocab_size,
  283. output_dim = 1,
  284. d_state = 2,
  285. expand= 2,
  286. dt_rank="auto",
  287. d_conv = 4,
  288. conv_bias= True,
  289. bias= False,
  290. return_last= False,
  291. )
  292. # 构建一个随机输入张量 (batch_size, seq_length)
  293. batch_size = 32
  294. seq_length = 288
  295. # 构建一个随机输入张量 (batch_size, seq_length, vocab_size)
  296. input_ids = torch.randn(batch_size, seq_length, vocab_size)
  297. logits = model(input_ids)
  298. print("Input shape :", input_ids.shape)
  299. print("Logits shape:", logits.shape) # 应为 (batch_size, seq_length, vocab_size)