initium-deeplearning 0.1.0__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (71) hide show
  1. initium/LNN_controller/cfc_backbone_parallel.py +265 -0
  2. initium/LNN_controller/cfc_controller.py +325 -0
  3. initium/LNN_controller/chained_controller.py +83 -0
  4. initium/LNN_controller/hybrid_controller.py +90 -0
  5. initium/MoE/README.md +1 -0
  6. initium/MoE/moe_layer.py +552 -0
  7. initium/__init__.py +1 -0
  8. initium/config/controller_config.py +86 -0
  9. initium/config/train_config.py +64 -0
  10. initium/core_training.py +2182 -0
  11. initium/data/__init__.py +0 -0
  12. initium/data/audio/__init__.py +0 -0
  13. initium/data/audio/audio_dataset.py +96 -0
  14. initium/data/base_dataset.py +75 -0
  15. initium/data/common/__init__.py +0 -0
  16. initium/data/common/chain_task.py +542 -0
  17. initium/data/common/digit_codec.py +85 -0
  18. initium/data/common/graph_io.py +123 -0
  19. initium/data/common/real_data.py +747 -0
  20. initium/data/dataset_registry.py +59 -0
  21. initium/data/graph_traversal/__init__.py +0 -0
  22. initium/data/graph_traversal/graph_traversal.py +746 -0
  23. initium/data/multimodal/__init__.py +0 -0
  24. initium/data/multimodal/multimodal_dataset.py +401 -0
  25. initium/data/text/__init__.py +0 -0
  26. initium/data/text/text_dataset.py +79 -0
  27. initium/data/video/__init__.py +0 -0
  28. initium/data/video/video_dataset.py +85 -0
  29. initium/inference/__init__.py +1 -0
  30. initium/inference/cache/__init__.py +1 -0
  31. initium/inference/cache/base_cache.py +112 -0
  32. initium/inference/cache/cache_config.py +21 -0
  33. initium/inference/cache/cache_registry.py +99 -0
  34. initium/inference/cache/cached_engine.py +264 -0
  35. initium/inference/cache/keys.py +77 -0
  36. initium/inference/cache/prefix_cache.py +59 -0
  37. initium/inference/cache/result_cache.py +57 -0
  38. initium/inference/cache/state_utils.py +74 -0
  39. initium/inference/cache/stores.py +167 -0
  40. initium/inference/capabilities.py +86 -0
  41. initium/inference/checkpoint_io.py +63 -0
  42. initium/inference/engine.py +88 -0
  43. initium/inference/inference_config.py +22 -0
  44. initium/inference/metrics.py +103 -0
  45. initium/inference/model_loader.py +270 -0
  46. initium/inference/run_inference.py +526 -0
  47. initium/inference/run_logging.py +173 -0
  48. initium/inference/tasks/__init__.py +1 -0
  49. initium/inference/tasks/audio_task.py +114 -0
  50. initium/inference/tasks/base_task.py +60 -0
  51. initium/inference/tasks/graph_traversal_task.py +235 -0
  52. initium/inference/tasks/multimodal_task.py +74 -0
  53. initium/inference/tasks/task_registry.py +68 -0
  54. initium/inference/tasks/text_task.py +114 -0
  55. initium/inference/tasks/video_task.py +114 -0
  56. initium/mamba_controller/mamba2_controller.py +433 -0
  57. initium/mamba_controller/mamba3_controller.py +242 -0
  58. initium/mamba_controller/mamba_backbone_parallel.py +291 -0
  59. initium/mamba_controller/mamba_controller.py +983 -0
  60. initium/mamba_controller/split_graph_dnc.py +623 -0
  61. initium/memory_manipulation/dynamic_memory_resize.py +157 -0
  62. initium/memory_manipulation/dynamic_n_controller.py +152 -0
  63. initium/memory_manipulation/link_matrix_ablation.py +130 -0
  64. initium/memory_manipulation/nvrtc_compat.py +143 -0
  65. initium/memory_manipulation/stochastic_write_head_v2.py +545 -0
  66. initium/setup_wheels.py +175 -0
  67. initium_deeplearning-0.1.0.dist-info/METADATA +253 -0
  68. initium_deeplearning-0.1.0.dist-info/RECORD +71 -0
  69. initium_deeplearning-0.1.0.dist-info/WHEEL +4 -0
  70. initium_deeplearning-0.1.0.dist-info/entry_points.txt +3 -0
  71. initium_deeplearning-0.1.0.dist-info/licenses/LICENSE +674 -0
@@ -0,0 +1,242 @@
1
+ """
2
+ file: mamba3_controller.py -- v1
3
+ Interleaved Mamba-3 (SISO) DNC controller. Same Cell/Block/Wrapper split as
4
+ mamba2_controller.py. Math: paper Prop.1 (exp-trapezoidal) + Prop.4/Eq.11 (RoPE trick):
5
+ h_t = a_t h_{t-1} + b_t (k_{t-1} x_{t-1}^T) + g_t (k_t x_t^T), y_t = C_t^T h_t + D x_t
6
+ a=exp(DT*A), g=lam*DT, b=(1-lam)*DT*a, k/C = RMSNorm+bias then rotated by cumsum(angle*DT).
7
+ State per block: (angle_state, ssm_state, k_prev, v_prev), ALL fp32 (angle is a running sum).
8
+ No conv_state: Mamba-3 has no short conv. Everything after in_proj runs in real fp32.
9
+ """
10
+
11
+ from __future__ import annotations
12
+
13
+ import torch
14
+ import torch.nn as nn
15
+ import torch.nn.functional as F
16
+
17
+ from initium.mamba_controller.mamba2_controller import Mamba2ControllerWrapper
18
+ from initium.MoE.moe_layer import MoEBlock
19
+
20
+ _MAMBA3_IMPORT_ERROR: str | None
21
+
22
+ try:
23
+ from mamba_ssm.modules.mamba3 import Mamba3
24
+ except ImportError as _e: # pragma: no cover
25
+ Mamba3 = None
26
+ _MAMBA3_IMPORT_ERROR = (
27
+ "mamba3_controller.py needs mamba-ssm installed from GitHub main "
28
+ f"(Mamba3 is not in the v2.3.1 wheel). Original error: {_e}"
29
+ )
30
+ else:
31
+ _MAMBA3_IMPORT_ERROR = None
32
+
33
+
34
+ def _require_mamba3_ssm() -> None:
35
+ if Mamba3 is None:
36
+ raise ImportError(_MAMBA3_IMPORT_ERROR)
37
+
38
+
39
+ def _heavy_tail(x): # copied from mamba_ssm.modules.mamba3.heavy_tail_activation
40
+ return x.clamp_min(0) + torch.reciprocal(1 - x.clamp_max(0))
41
+
42
+
43
+ def _rms(v, w, eps):
44
+ return v * torch.rsqrt(v.pow(2).mean(-1, keepdim=True) + eps) * w.float()
45
+
46
+
47
+ class Mamba3ControllerCell(nn.Module):
48
+ def __init__(
49
+ self,
50
+ d_model,
51
+ d_state=64,
52
+ expand=2,
53
+ headdim=64,
54
+ ngroups=1,
55
+ rope_fraction=0.5,
56
+ layer_idx=None,
57
+ device=None,
58
+ dtype=None,
59
+ ):
60
+ super().__init__()
61
+ _require_mamba3_ssm()
62
+ assert ngroups == 1, "Mamba3ControllerCell supports ngroups==1 only"
63
+ # parameter container only; we never call .forward()/.step() on it
64
+ self.mamba3 = Mamba3(
65
+ d_model=d_model,
66
+ d_state=d_state,
67
+ expand=expand,
68
+ headdim=headdim,
69
+ ngroups=ngroups,
70
+ rope_fraction=rope_fraction,
71
+ is_mimo=False,
72
+ is_outproj_norm=False,
73
+ layer_idx=layer_idx,
74
+ device=device,
75
+ dtype=dtype,
76
+ )
77
+ m = self.mamba3
78
+ self.d_model, self.d_inner, self.nheads, self.headdim = (
79
+ d_model,
80
+ m.d_inner,
81
+ m.nheads,
82
+ m.headdim,
83
+ )
84
+ self.d_state, self.num_rope_angles = m.d_state, m.num_rope_angles
85
+
86
+ def init_state(self, batch_size, device=None, dtype=None): # dtype ignored: always fp32
87
+ device = device if device is not None else self.mamba3.in_proj.weight.device
88
+ H, P, N, S = self.nheads, self.headdim, self.d_state, self.num_rope_angles
89
+ f = dict(device=device, dtype=torch.float32)
90
+ return (
91
+ torch.zeros(batch_size, H, S, **f),
92
+ torch.zeros(batch_size, H, P, N, **f),
93
+ torch.zeros(batch_size, H, N, **f),
94
+ torch.zeros(batch_size, H, P, **f),
95
+ )
96
+
97
+ @staticmethod
98
+ def _rotate(v, cos, sin): # v: (b,H,N); rotate first 2S dims, adjacent pairs (SISO convention)
99
+ S = cos.shape[-1]
100
+ head, tail = v[..., : 2 * S], v[..., 2 * S :]
101
+ h0, h1 = head[..., 0::2], head[..., 1::2]
102
+ rot = torch.stack([h0 * cos - h1 * sin, h0 * sin + h1 * cos], dim=-1).flatten(-2)
103
+ return torch.cat([rot, tail], dim=-1)
104
+
105
+ def step(self, u, state):
106
+ angle_state, ssm_state, k_prev, v_prev = state
107
+ m = self.mamba3
108
+ H, P, N, S = self.nheads, self.headdim, self.d_state, self.num_rope_angles
109
+ dtype = u.dtype
110
+ with torch.autocast(device_type=u.device.type, enabled=False):
111
+ zxb = F.linear(u.float(), m.in_proj.weight.float())
112
+ z, x, B, C, dd_dt, dd_A, trap_proj, ang_proj = torch.split(
113
+ zxb, [self.d_inner, self.d_inner, N, N, H, H, H, S], dim=-1
114
+ )
115
+ A = (-_heavy_tail(dd_A)).clamp(max=-m.A_floor) # (b,H)
116
+ DT = F.softplus(dd_dt + m.dt_bias.float()).clamp(min=1e-6, max=100.0)
117
+ lam = torch.sigmoid(trap_proj)
118
+ alpha = torch.exp(DT * A)
119
+ gamma = lam * DT
120
+ beta = (1.0 - lam) * DT * alpha
121
+ B = _rms(B, m.B_norm.weight, m.B_norm.eps).unsqueeze(1) + m.B_bias.float().squeeze(
122
+ 1
123
+ ) # (b,H,N)
124
+ C = _rms(C, m.C_norm.weight, m.C_norm.eps).unsqueeze(1) + m.C_bias.float().squeeze(1)
125
+ angle_new = angle_state + ang_proj.unsqueeze(1) * DT.unsqueeze(
126
+ -1
127
+ ) # (b,H,S) cumsum(angle*DT)
128
+ cos, sin = angle_new.cos(), angle_new.sin()
129
+ B, C = self._rotate(B, cos, sin), self._rotate(C, cos, sin)
130
+ x = x.reshape(-1, H, P)
131
+ z = z.reshape(-1, H, P)
132
+ new_ssm = (
133
+ ssm_state * alpha[..., None, None]
134
+ + beta[..., None, None] * (v_prev.unsqueeze(-1) * k_prev.unsqueeze(-2))
135
+ + gamma[..., None, None] * (x.unsqueeze(-1) * B.unsqueeze(-2))
136
+ )
137
+ new_ssm = new_ssm.clamp(min=-1e4, max=1e4) # same hard stop as Mamba-1/2 cells
138
+ y = torch.einsum("bhpn,bhn->bhp", new_ssm, C) + m.D.float().view(1, H, 1) * x
139
+ y = (y * F.silu(z)).reshape(-1, self.d_inner).clamp(min=-1e4, max=1e4)
140
+ out = m.out_proj(y.to(dtype)) # back under ambient autocast
141
+ return out, (angle_new, new_ssm, B, x)
142
+
143
+
144
+ class Mamba3ControllerBlock(nn.Module):
145
+ def __init__(
146
+ self,
147
+ d_model,
148
+ d_state=64,
149
+ expand=2,
150
+ headdim=64,
151
+ ngroups=1,
152
+ rope_fraction=0.5,
153
+ layer_idx=None,
154
+ device=None,
155
+ dtype=None,
156
+ ):
157
+ super().__init__()
158
+ self.norm = nn.LayerNorm(d_model, device=device, dtype=dtype)
159
+ self.cell = Mamba3ControllerCell(
160
+ d_model,
161
+ d_state=d_state,
162
+ expand=expand,
163
+ headdim=headdim,
164
+ ngroups=ngroups,
165
+ rope_fraction=rope_fraction,
166
+ layer_idx=layer_idx,
167
+ device=device,
168
+ dtype=dtype,
169
+ )
170
+
171
+ def init_state(self, batch_size, device=None, dtype=None):
172
+ return self.cell.init_state(batch_size, device=device, dtype=dtype)
173
+
174
+ def step(self, x, state):
175
+ out, new_state = self.cell.step(self.norm(x), state)
176
+ return x + out, new_state
177
+
178
+
179
+ class Mamba3ControllerWrapper(Mamba2ControllerWrapper):
180
+ """Reuses Mamba2ControllerWrapper.forward()/init_state() unchanged (they only touch
181
+ in_adapter / blocks / moe_blocks / moe_enabled); only __init__ differs."""
182
+
183
+ def __init__(
184
+ self,
185
+ in_dim,
186
+ d_model,
187
+ num_blocks=2,
188
+ moe_enabled=False,
189
+ moe_num_experts=8,
190
+ moe_expert_dim=None,
191
+ moe_capacity_factor=1.5,
192
+ moe_load_balance_alpha=0.01,
193
+ moe_top_k=1,
194
+ d_state=64,
195
+ expand=2,
196
+ headdim=64,
197
+ ngroups=1,
198
+ rope_fraction=0.5,
199
+ device=None,
200
+ dtype=None,
201
+ ):
202
+ nn.Module.__init__(self)
203
+ self.d_model, self.num_blocks = d_model, num_blocks
204
+ self.in_adapter = (
205
+ nn.Identity()
206
+ if in_dim == d_model
207
+ else nn.Linear(in_dim, d_model, device=device, dtype=dtype)
208
+ )
209
+ self.blocks = nn.ModuleList(
210
+ [
211
+ Mamba3ControllerBlock(
212
+ d_model,
213
+ d_state=d_state,
214
+ expand=expand,
215
+ headdim=headdim,
216
+ ngroups=ngroups,
217
+ rope_fraction=rope_fraction,
218
+ layer_idx=i,
219
+ device=device,
220
+ dtype=dtype,
221
+ )
222
+ for i in range(num_blocks)
223
+ ]
224
+ )
225
+ self.moe_enabled = moe_enabled
226
+ self.moe_blocks = None
227
+ if moe_enabled:
228
+ self.moe_blocks = nn.ModuleList(
229
+ [
230
+ MoEBlock(
231
+ d_model,
232
+ num_experts=moe_num_experts,
233
+ expert_dim=moe_expert_dim,
234
+ capacity_factor=moe_capacity_factor,
235
+ top_k=moe_top_k,
236
+ load_balance_alpha=moe_load_balance_alpha,
237
+ device=device,
238
+ dtype=dtype,
239
+ )
240
+ for _ in range(num_blocks)
241
+ ]
242
+ )
@@ -0,0 +1,291 @@
1
+ """
2
+ file: mamba_backbone_parallel.py
3
+
4
+ Alternate Phase 3, Step 2, Option 5 ("split the compute graph, don't try to
5
+ parallelize the addressing itself") -- see Experiment-Roadmap.md.
6
+
7
+ This module implements ONLY the "(a) Mamba backbone" half of the Option 5
8
+ split: a stack of Mamba (v1) or Mamba-2 blocks driven over the WHOLE
9
+ sequence at once via each block's own native, library-provided forward()
10
+ (the fast, parallel-scan / SSD path), with NO per-timestep interleaving of
11
+ memory reads.
12
+
13
+ This is a deliberate, much simpler sibling to mamba_controller.py's
14
+ MambaControllerCell/Block/Wrapper (Alternative Phase 3, Step 1). That file
15
+ had to hand-roll a BPTT-safe manual step() specifically because the DNC's
16
+ per-timestep interleaving loop needed to call the controller one token at a
17
+ time while chaining (conv_state, ssm_state) through a Python loop under
18
+ full backprop, and mamba_ssm's own .step() mutates its state tensors
19
+ in-place (fatal for BPTT -- see that file's module docstring for the full
20
+ diagnosis).
21
+
22
+ Option 5's entire point is to remove that constraint for the backbone: since
23
+ this backbone's per-token computation has NO dependency on M_{t-1} (the
24
+ memory's state at the previous timestep) -- it only ever consumes the raw
25
+ input sequence X, never a read vector -- there is nothing forcing it into a
26
+ sequential Python loop. It can therefore call mamba_ssm's OWN whole-sequence
27
+ forward() directly, which already uses the fast parallel selective-scan
28
+ (Mamba-1) or the chunked, matmul-based SSD algorithm (Mamba-2, Dao & Gu
29
+ 2024) -- no custom step function, no in-place-op workaround needed, because
30
+ nothing here is chained through a live memory-state Python loop.
31
+
32
+ The other half of the Option 5 split -- the "genuinely sequential, but much
33
+ smaller and cheaper, memory read/write/addressing step" that consumes this
34
+ backbone's parallel output as an input stream -- lives in
35
+ split_graph_dnc.py, not here. This file only produces H = backbone(X); it
36
+ has no notion of DNC, Memory, or read vectors at all, by design (see
37
+ Concept 6 / LB-9's caution about structural-vs-functional separation --
38
+ keeping this file's API surface structurally incapable of touching memory
39
+ state is a guarantee, not just a convention).
40
+
41
+ Supports two variants, both from the mamba-ssm package (per the roadmap's
42
+ "add Mamba-2 first if Option 5 needs it, otherwise Mamba-1 is already
43
+ implemented" instruction -- Option 5 benefits directly from Mamba-2's own
44
+ SSD chunked-scan algorithm, since that algorithm is specifically optimized
45
+ for exactly the "process the whole sequence at once" call pattern Option 5
46
+ newly unlocks for the backbone -- see the SSD paper, Sections 6 and 9.3,
47
+ already in corpus):
48
+
49
+ - "mamba1": mamba_ssm.modules.mamba_simple.Mamba
50
+ - "mamba2": mamba_ssm.modules.mamba2.Mamba2
51
+
52
+ Both are used exactly as their own package intends (whole-sequence
53
+ forward(hidden_states) call, batch_first (B, L, D) tensors) -- this file
54
+ does not reimplement or bypass anything of either class, unlike
55
+ mamba_controller.py's cell.
56
+
57
+ NOTE on Mamba2 kwargs: mamba_ssm's Mamba2 additionally requires `headdim`
58
+ (d_model must be divisible by it -- SSD paper's own convention is
59
+ head_dim in {64, 128}). This has no analogue in Mamba-1 and is therefore
60
+ a new, Mamba2-only constructor argument here, same pattern as
61
+ mamba_d_state/mamba_d_conv/mamba_expand having no LSTM analogue in
62
+ mamba_controller.py.
63
+ """
64
+
65
+ from __future__ import annotations
66
+
67
+ import os as _os
68
+
69
+ import torch
70
+ import torch.nn as nn
71
+
72
+ from initium.MoE.moe_layer import MoEBlock
73
+
74
+ _MAMBA1_IMPORT_ERROR: str | None
75
+ _MAMBA2_IMPORT_ERROR: str | None
76
+ _MAMBA3_IMPORT_ERROR: str | None
77
+
78
+ try:
79
+ from mamba_ssm.modules.mamba_simple import Mamba as Mamba1
80
+ except ImportError as _mamba1_err: # pragma: no cover - environment-dependent
81
+ Mamba1 = None
82
+ _MAMBA1_IMPORT_ERROR = (
83
+ "mamba_backbone_parallel.py requires the `mamba-ssm` package for "
84
+ f"variant='mamba1'. Original import error: {_mamba1_err}"
85
+ )
86
+ else:
87
+ _MAMBA1_IMPORT_ERROR = None
88
+
89
+ try:
90
+ from mamba_ssm.modules.mamba2 import Mamba2
91
+ except ImportError as _mamba2_err: # pragma: no cover - environment-dependent
92
+ Mamba2 = None
93
+ _MAMBA2_IMPORT_ERROR = (
94
+ "mamba_backbone_parallel.py requires the `mamba-ssm` package "
95
+ "(with Mamba-2 support -- mamba_ssm.modules.mamba2.Mamba2) for "
96
+ f"variant='mamba2'. Original import error: {_mamba2_err}"
97
+ )
98
+ else:
99
+ _MAMBA2_IMPORT_ERROR = None
100
+
101
+ try:
102
+ from mamba_ssm.modules.mamba3 import Mamba3
103
+ except ImportError as _mamba3_err: # pragma: no cover - environment-dependent
104
+ Mamba3 = None
105
+ _MAMBA3_IMPORT_ERROR = (
106
+ "mamba_backbone_parallel.py: variant='mamba3' needs mamba-ssm installed from GitHub main "
107
+ f"(Mamba3 is not in the v2.3.1 wheel). Original import error: {_mamba3_err}"
108
+ )
109
+ else:
110
+ _MAMBA3_IMPORT_ERROR = None
111
+
112
+ # bf16 (the repo's own precision) when the GPU supports it; otherwise the fp32-under-fp16-autocast trick.
113
+ _MAMBA3_BF16_OK = (
114
+ torch.cuda.is_available()
115
+ and torch.cuda.get_device_capability()[0] >= 8
116
+ and _os.environ.get("MAMBA3_FORCE_FP32", "0") != "1"
117
+ )
118
+
119
+
120
+ def _require_variant(variant: str) -> None:
121
+ if variant == "mamba1" and Mamba1 is None:
122
+ raise ImportError(_MAMBA1_IMPORT_ERROR)
123
+ if variant == "mamba2" and Mamba2 is None:
124
+ raise ImportError(_MAMBA2_IMPORT_ERROR)
125
+ if variant == "mamba3" and Mamba3 is None:
126
+ raise ImportError(_MAMBA3_IMPORT_ERROR)
127
+ if variant not in ("mamba1", "mamba2", "mamba3"):
128
+ raise ValueError(
129
+ f"mamba_backbone_parallel: unknown variant {variant!r}, expected 'mamba1' or 'mamba2'."
130
+ )
131
+
132
+
133
+ class _ParallelBlock(nn.Module):
134
+ """Pre-norm residual wrapper around one whole-sequence Mamba block,
135
+ mirroring mamba_ssm's own Block (Add -> LN -> Mixer) and
136
+ mamba_controller.py's MambaControllerBlock, but calling the mixer's own
137
+ forward() over the full (B, L, D) sequence at once instead of a
138
+ per-timestep step(). No custom state threading of any kind -- the
139
+ mixer manages its own internal scan/chunking entirely within this one
140
+ call.
141
+ """
142
+
143
+ def __init__(
144
+ self,
145
+ d_model: int,
146
+ variant: str,
147
+ mamba_kwargs: dict,
148
+ device: torch.device | None = None,
149
+ dtype: torch.dtype | None = None,
150
+ ):
151
+ super().__init__()
152
+ _require_variant(variant)
153
+ self.norm = nn.LayerNorm(d_model, device=device, dtype=dtype)
154
+ self.variant = variant
155
+ if variant == "mamba3":
156
+ self.mixer = Mamba3(d_model=d_model, device=device, dtype=dtype, **mamba_kwargs)
157
+ elif variant == "mamba1":
158
+ self.mixer = Mamba1(d_model=d_model, device=device, dtype=dtype, **mamba_kwargs)
159
+ else:
160
+ self.mixer = Mamba2(d_model=d_model, device=device, dtype=dtype, **mamba_kwargs)
161
+
162
+ def forward(self, x: torch.Tensor) -> torch.Tensor:
163
+ # x: (B, L, D). mamba_ssm's own forward() handles the whole
164
+ # sequence's parallel scan / SSD chunked-scan internally -- this is
165
+ # the entire mechanism that makes Option 5 different from
166
+ # mamba_controller.py: one call per block, not one call per DNC
167
+ # timestep.
168
+ #
169
+ # FIX (chronic grad_norm nan under fp16 AMP, Mamba-2 variant only in
170
+ # practice but applied to both for symmetry): same wrapper trick
171
+ # already used by mamba_controller.py's MambaControllerCell.step()
172
+ # and mamba2_controller.py's Mamba2ControllerCell.step() -- force
173
+ # this call to run in real fp32, outside ambient fp16 autocast,
174
+ # rather than reimplementing the mixer's internal math by hand the
175
+ # way those cells do for their own per-timestep step(). Unlike those
176
+ # cells, self.mixer here is the library's own whole-sequence
177
+ # forward() (that's the entire point of the parallel backbone), so
178
+ # we can't insert clamps mid-computation the way step() does --
179
+ # instead we just deny it fp16 entirely. Mamba-2's SSD algorithm
180
+ # computes exp() of a cumulative sum of (dt * A) over a whole chunk
181
+ # (paper Section 6, Listing 1's segsum), which is far more prone to
182
+ # fp16 underflow/overflow than Mamba-1's fused selective-scan
183
+ # kernel -- this is the direct cause of the amp_scale-collapse-to-
184
+ # 0.0 pattern observed in mamba2-backbone split-graph runs that
185
+ # mamba1-backbone runs didn't show. Costs some speed relative to
186
+ # running under fp16 (this block no longer benefits from tensor-core
187
+ # fp16 matmuls), but matches every other Mamba SSM computation path
188
+ # in this project, all of which already force fp32 for this reason.
189
+ if self.variant == "mamba3" and _MAMBA3_BF16_OK:
190
+ with torch.autocast(device_type=x.device.type, dtype=torch.bfloat16, enabled=True):
191
+ out = self.mixer(self.norm(x.float()).to(torch.bfloat16))
192
+ else:
193
+ with torch.autocast(device_type=x.device.type, enabled=False):
194
+ out = self.mixer(self.norm(x.float()))
195
+ return x + out.to(x.dtype)
196
+
197
+
198
+ class MambaBackboneParallel(nn.Module):
199
+ """Stack of `num_blocks` whole-sequence Mamba blocks. Pure sequence-to-
200
+ sequence map (B, L, in_dim) -> (B, L, d_model), with NO awareness of
201
+ DNC, Memory, or read vectors -- see module docstring.
202
+
203
+ variant: "mamba1" (mamba_ssm.modules.mamba_simple.Mamba, default
204
+ Mamba-1 hyperparameters d_state=16/d_conv=4/expand=2, this
205
+ project's original Alternate-Phase-3 choice) or "mamba2"
206
+ (mamba_ssm.modules.mamba2.Mamba2, needs `headdim` in addition;
207
+ default 64, matching the SSD paper's own convention of head_dim in
208
+ {64, 128}).
209
+ """
210
+
211
+ def __init__(
212
+ self,
213
+ in_dim: int,
214
+ d_model: int,
215
+ num_blocks: int = 2,
216
+ variant: str = "mamba1",
217
+ d_state: int = 16,
218
+ d_conv: int = 4,
219
+ expand: int = 2,
220
+ headdim: int = 64, # mamba2-only
221
+ moe_enabled: bool = False,
222
+ moe_num_experts: int = 8,
223
+ moe_expert_dim: int | None = None,
224
+ moe_top_k: int = 1,
225
+ moe_capacity_factor: float = 1.5,
226
+ moe_load_balance_alpha: float = 0.01,
227
+ device: torch.device | None = None,
228
+ dtype: torch.dtype | None = None,
229
+ ):
230
+ super().__init__()
231
+ _require_variant(variant)
232
+ self.variant = variant
233
+ self.d_model = d_model
234
+ self.moe_enabled = moe_enabled
235
+ self.moe_blocks: nn.ModuleList | None = None
236
+
237
+ # Same role as MambaControllerWrapper's in_adapter: DNC's raw
238
+ # per-timestep input (input_size) generally differs from
239
+ # hidden_size/d_model. Unlike the interleaved wrapper, this is
240
+ # applied to the WHOLE sequence at once (a single
241
+ # (B, L, in_dim) -> (B, L, d_model) matmul), not per-timestep.
242
+ self.in_adapter: nn.Module = (
243
+ nn.Identity()
244
+ if in_dim == d_model
245
+ else nn.Linear(in_dim, d_model, device=device, dtype=dtype)
246
+ )
247
+
248
+ mamba_kwargs = dict(d_state=d_state, d_conv=d_conv, expand=expand)
249
+ if variant == "mamba2":
250
+ mamba_kwargs["headdim"] = headdim
251
+ if variant == "mamba3":
252
+ mamba_kwargs["headdim"] = headdim
253
+ mamba_kwargs["chunk_size"] = (
254
+ 64 if _MAMBA3_BF16_OK else 32
255
+ ) # repo: 64 bf16 / 32 otherwise
256
+
257
+ self.blocks = nn.ModuleList(
258
+ [
259
+ _ParallelBlock(d_model, variant, mamba_kwargs, device=device, dtype=dtype)
260
+ for _ in range(num_blocks)
261
+ ]
262
+ )
263
+ if moe_enabled:
264
+ self.moe_blocks = nn.ModuleList(
265
+ [
266
+ MoEBlock(
267
+ d_model,
268
+ num_experts=moe_num_experts,
269
+ expert_dim=moe_expert_dim,
270
+ top_k=moe_top_k,
271
+ capacity_factor=moe_capacity_factor,
272
+ load_balance_alpha=moe_load_balance_alpha,
273
+ device=device,
274
+ dtype=dtype,
275
+ )
276
+ for _ in range(num_blocks)
277
+ ]
278
+ )
279
+
280
+ def forward(self, x: torch.Tensor) -> torch.Tensor:
281
+ # x: (B, L, in_dim) -> (B, L, d_model). Called EXACTLY ONCE per
282
+ # training step (not once per DNC timestep, unlike
283
+ # MambaControllerWrapper) -- this single call is the entire
284
+ # "backbone half" of the Option 5 split.
285
+ h = self.in_adapter(x)
286
+ for i, block in enumerate(self.blocks):
287
+ h = block(h)
288
+ if self.moe_enabled:
289
+ assert self.moe_blocks is not None
290
+ h = self.moe_blocks[i](h)
291
+ return h