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,114 @@
1
+ """
2
+ file: inference/tasks/video_task.py
3
+
4
+ Video-modality inference task. Delegates episode-building/decoding to
5
+ data.video.video_dataset.VideoChainDataset (same pattern graph_traversal_task.py
6
+ uses relative to graph_traversal.py - training and inference can never
7
+ silently drift apart).
8
+ """
9
+
10
+ from __future__ import annotations
11
+
12
+ import torch
13
+
14
+ from initium.data.video.video_dataset import VideoChainDataset
15
+ from initium.inference.metrics import EpisodeScore
16
+ from initium.inference.tasks.base_task import BaseInferenceTask, Episode
17
+
18
+ BUILTIN_LINKS = (None, "", "video-chain")
19
+
20
+
21
+ class VideoChainTask(BaseInferenceTask):
22
+ name = "video-chain"
23
+ dataset_type = "video"
24
+
25
+ def __init__(self, dataset_link=None, path_length_range=None, **kwargs):
26
+ # Inference always tests a trained model, so --dataset-link here plays the
27
+ # role of test_dataset_link on the training-side dataset class: a real
28
+ # video file used as the held-out fact source, instead of the synthetic
29
+ # seeded table. Omit (or pass one of BUILTIN_LINKS) for that default.
30
+ link = None if dataset_link in BUILTIN_LINKS else dataset_link
31
+ self._ds = VideoChainDataset(test_dataset_link=link)
32
+ self.input_dim, self.output_dim = self._ds.input_dim, self._ds.output_dim
33
+ self.query_range = (
34
+ tuple(path_length_range) if path_length_range else self._ds.ood_query_range
35
+ )
36
+ self.facts = self._ds.build_ood_facts()
37
+
38
+ def describe(self) -> str:
39
+ return f"{self.name} | {len(self.facts)} facts | queries {self.query_range}"
40
+
41
+ def build_episodes(self, n: int, rng, perturbation=None) -> list[Episode]:
42
+ severity = (
43
+ (perturbation or {}).get("severity", 0.0) if isinstance(perturbation, dict) else 0.0
44
+ )
45
+ keys, vals_map = [k for k, _ in self.facts], dict(self.facts)
46
+ NF = self._ds.codec.num_digits
47
+ episodes = []
48
+ for _ in range(n):
49
+ nq = rng.randint(*self.query_range)
50
+ query_keys = [rng.choice(keys) for _ in range(nq)]
51
+ facts = list(self.facts)
52
+ rng.shuffle(facts)
53
+ inputs, targets, mask = [], [], []
54
+ for i, (k, v) in enumerate(facts):
55
+ inputs.append(
56
+ self._ds._encode_fact(
57
+ k, v, 1.0 if i == 0 else 0.0, perturb=(severity or None), rng=rng
58
+ )
59
+ )
60
+ targets.append([0] * (2 * NF))
61
+ mask.append(0)
62
+ for i, k in enumerate(query_keys):
63
+ inputs.append(self._ds._encode_query(k, 1.0 if i == 0 else 0.0))
64
+ targets.append([0] * (2 * NF))
65
+ mask.append(0)
66
+ cumsum = 0
67
+ for i, k in enumerate(query_keys):
68
+ cumsum = (cumsum + vals_map[k]) % self._ds.label_range
69
+ inputs.append(self._ds._encode_answer(1.0 if i == 0 else 0.0))
70
+ targets.append(
71
+ self._ds.codec.label_to_digit_targets(vals_map[k])
72
+ + self._ds.codec.label_to_digit_targets(cumsum)
73
+ )
74
+ mask.append(1)
75
+ episodes.append(
76
+ Episode(
77
+ torch.stack(inputs),
78
+ torch.tensor(targets, dtype=torch.long),
79
+ torch.tensor(mask, dtype=torch.float32),
80
+ meta={"num_queries": nq},
81
+ )
82
+ )
83
+ return episodes
84
+
85
+ def score_episode(self, output, episode: Episode, verbose: bool = False) -> EpisodeScore:
86
+ codec, D = self._ds.codec, self._ds.codec.label_dim
87
+ answer_idx = (episode.mask == 1).nonzero(as_tuple=True)[0]
88
+ f = {"value": [0, 0], "cumsum": [0, 0]}
89
+ n_correct = 0
90
+ for idx in answer_idx:
91
+ v_pred, c_pred = (
92
+ codec.decode_field(output[idx][0:D]),
93
+ codec.decode_field(output[idx][D : 2 * D]),
94
+ )
95
+ td = episode.target[idx].tolist()
96
+ nd = codec.num_digits
97
+ v_tgt = int("".join(map(str, td[0:nd])))
98
+ c_tgt = int("".join(map(str, td[nd : 2 * nd])))
99
+ v_ok, c_ok = v_pred == v_tgt, c_pred == c_tgt
100
+ f["value"][0] += int(v_ok)
101
+ f["value"][1] += 1
102
+ f["cumsum"][0] += int(c_ok)
103
+ f["cumsum"][1] += 1
104
+ n_correct += int(v_ok) + int(c_ok)
105
+ if verbose:
106
+ print(f" value {v_pred}/{v_tgt} ok={v_ok} | cumsum {c_pred}/{c_tgt} ok={c_ok}")
107
+ n = 2 * len(answer_idx)
108
+ return EpisodeScore(
109
+ n_items=n,
110
+ n_correct=n_correct,
111
+ perfect=(n_correct == n),
112
+ group=len(answer_idx),
113
+ fields={k: (v[0], v[1]) for k, v in f.items()},
114
+ )
@@ -0,0 +1,433 @@
1
+ """
2
+ file: mamba2_controller.py -- v1
3
+
4
+ Companion to mamba_controller.py (Mamba-1), implementing a genuine,
5
+ standalone Mamba-2 DNC controller: MambaDNC's rnn_type='mamba2'. This is
6
+ NOT the same thing as split_graph_dnc.py's mamba_variant='mamba2' backbone
7
+ (mamba_backbone_parallel.py) -- that backbone drives mamba_ssm's own
8
+ Mamba2.forward() over the WHOLE sequence at once (Option 5's parallel
9
+ "no memory dependency" half). This file drives Mamba2 ONE DNC timestep at
10
+ a time, interleaved with dnc.memory.Memory reads/writes exactly like
11
+ mamba_controller.py's Mamba-1 controller does -- i.e. this is Alternate
12
+ Phase 3 Step 1's controller swap, generalized to Mamba-2, not Option 5's
13
+ split-graph mechanism. The two are orthogonal and can't substitute for
14
+ each other: split_graph_dnc.py never threads a read vector into its
15
+ backbone at all (by design -- see that file's module docstring), while
16
+ this controller's whole point, like mamba_controller.py's, is that the
17
+ read vector from step t-1 is part of this controller's input at step t.
18
+
19
+ Library used: `mamba-ssm`, specifically `mamba_ssm.modules.mamba2.Mamba2`
20
+ -- reused purely as a parameter container (in_proj, conv1d, A_log, D,
21
+ dt_bias, out_proj), exactly the same "construct via the library, bypass
22
+ its forward()/step()" strategy mamba_controller.py's MambaControllerCell
23
+ already uses for Mamba-1 -- see that file's "Why not just call
24
+ Mamba.step()?" section, which applies here unchanged: Mamba2's own
25
+ `.step()` (mamba_ssm/modules/mamba2.py) is the right per-token
26
+ granularity but mutates conv_state/ssm_state in place via `.copy_(...)`,
27
+ which is fine for `torch.no_grad()` autoregressive decoding but breaks
28
+ backprop-through-time the moment more than one `.step()` call is chained
29
+ under a shared loss. This file reimplements that same step's math
30
+ line-for-line, out-of-place, reusing the parameter container's submodules
31
+ and constants only.
32
+
33
+ ASSUMPTION FLAGGED FOR VERIFICATION (same convention as split_graph_dnc.py's
34
+ own flagged assumption about Memory's return shape): the exact attribute
35
+ names/shapes below (self.mamba2.d_ssm/nheads/headdim/ngroups/dt_bias/A_log/D,
36
+ in_proj's [z, xBC, dt] split sizes) are reconstructed from mamba_ssm's public
37
+ Mamba2 source, not verified against a live install. Run one batch through
38
+ this module and confirm out.shape == (B, d_model) and that no shape-mismatch
39
+ error fires before trusting a full run.
40
+
41
+ --------------------------------------------------------------------------
42
+ Differences from Mamba-1 that matter for this reimplementation
43
+ --------------------------------------------------------------------------
44
+ Mamba-2's SSD parameterization (Dao & Gu 2024) differs from Mamba-1's S6
45
+ in ways that change the per-step math, not just the constructor kwargs:
46
+ - A is a SCALAR PER HEAD (`A_log` has shape (nheads,)), not a per-
47
+ (channel, state) matrix like Mamba-1's `A_log` (d_inner, d_state) --
48
+ this is exactly the "scalar-identity SSM" structure the SSD paper's
49
+ Section 5.1 shows is dual to 1-semiseparable structured attention
50
+ (Section 5.2/5.3, Corollary 5.1). So `dA` here is `exp(dt * A)`
51
+ broadcast per head, not `exp(einsum("bd,dn->bdn", dt, A))` per
52
+ (channel, state) as in Mamba-1's cell.
53
+ - The recurrent state has an extra head axis: `ssm_state` is
54
+ `(B, nheads, headdim, d_state)` (Mamba-1's is `(B, d_inner, d_state)`,
55
+ with d_inner playing the role nheads*headdim plays here).
56
+ - `in_proj` produces `[z, x, B, C, dt]` in one shot (Mamba-2's "parallel
57
+ parameter projections", Section 7.1) rather than Mamba-1's
58
+ `in_proj -> [x, z]` followed by a separate `x_proj -> [dt, B, C]` on
59
+ the post-conv activation. Concretely, Mamba-2's `dt` is already
60
+ available before the conv step, and the conv only ever acts on
61
+ `[x, B, C]` (packed as `xBC`), never on `z` or `dt`.
62
+ - Mamba-2 has an extra gated RMSNorm (`self.norm`, Section 7.1 "Extra
63
+ Normalization") applied to `y` before `out_proj`, when `rmsnorm=True`.
64
+ We construct the underlying `Mamba2` with `rmsnorm=False` here -- same
65
+ rationale as `use_fast_path=False` on the Mamba-1 cell: `RMSNormGated`
66
+ (mamba_ssm.ops.triton.layernorm_gated) is a Triton kernel with no
67
+ CPU/no-Triton fallback, so keeping it out avoids a second hard
68
+ GPU-kernel dependency beyond `mamba_ssm` itself. With `rmsnorm=False`,
69
+ Mamba-2's own step() reduces to the same `y = y * silu(z)` gating
70
+ Mamba-1 uses -- only the extra post-hoc normalization layer is
71
+ skipped, not any of SSD's own math.
72
+ - `ngroups` (B/C sharing across heads, the MVA/MQA/MKA head-pattern
73
+ distinction of Section 7.2) is fixed at 1 here, matching every other
74
+ Mamba-2 use in this project (mamba_backbone_parallel.py also never
75
+ exposes it) -- Proposition 7.2 / Table 5 in the paper is what
76
+ motivates Mamba-1's own MVA pattern (B/C shared across all channels)
77
+ as the strongest single-head-pattern default; Mamba-2's ngroups=1
78
+ case reduces to that same sharing.
79
+ - `D_has_hdim=False` (Mamba-2's own default) is kept: `D` is per-head
80
+ (nheads,), not per-(head,headdim).
81
+
82
+ Everything else -- the Cell/Block/Wrapper split, the pre-norm residual
83
+ pattern, the nn.LSTM-compatible call convention, the fp32-forced SSM math
84
+ with dt/A_log clamping and hard state clamps (chronic-NaN fix, see
85
+ mamba_controller.py's step() for the original diagnosis) -- is copied as
86
+ directly as possible from mamba_controller.py, changed only where
87
+ Mamba-2's parameterization forces a different shape or formula.
88
+ """
89
+
90
+ from __future__ import annotations
91
+
92
+ from collections.abc import Iterable
93
+ from typing import cast
94
+
95
+ import torch
96
+ import torch.nn as nn
97
+ import torch.nn.functional as F
98
+
99
+ from initium.MoE.moe_layer import MoEBlock
100
+
101
+ _MAMBA2_IMPORT_ERROR: str | None
102
+
103
+ try:
104
+ from mamba_ssm.modules.mamba2 import Mamba2
105
+ except ImportError as _mamba2_import_error: # pragma: no cover - environment-dependent
106
+ Mamba2 = None
107
+ _MAMBA2_IMPORT_ERROR = (
108
+ "mamba2_controller.py requires the `mamba-ssm` package "
109
+ "(with Mamba-2 support -- mamba_ssm.modules.mamba2.Mamba2). "
110
+ "Original import error: "
111
+ f"{_mamba2_import_error}"
112
+ )
113
+ else:
114
+ _MAMBA2_IMPORT_ERROR = None
115
+
116
+
117
+ def _require_mamba2_ssm() -> None:
118
+ if Mamba2 is None:
119
+ raise ImportError(_MAMBA2_IMPORT_ERROR)
120
+
121
+
122
+ # ==========================================================================
123
+ # 1. Mamba2ControllerCell -- one Mamba-2 block, single-timestep, BPTT-safe
124
+ # ==========================================================================
125
+ class Mamba2ControllerCell(nn.Module):
126
+ """One Mamba-2 (SSD) block, driven one timestep at a time. See module
127
+ docstring for the full design rationale and how this differs from
128
+ mamba_controller.py's Mamba-1 MambaControllerCell.
129
+
130
+ State: `(conv_state, ssm_state)`, shapes `(B, conv_dim, d_conv)` and
131
+ `(B, nheads, headdim, d_state)` respectively, matching
132
+ `Mamba2.allocate_inference_cache()`'s own shapes (we don't call that
133
+ method -- construct zero state directly -- but stay shape-compatible
134
+ with it on purpose, same convention as the Mamba-1 cell).
135
+ """
136
+
137
+ def __init__(
138
+ self,
139
+ d_model: int,
140
+ d_state: int = 64,
141
+ d_conv: int = 4,
142
+ expand: int = 2,
143
+ headdim: int = 64,
144
+ ngroups: int = 1,
145
+ layer_idx: int | None = None,
146
+ device: torch.device | None = None,
147
+ dtype: torch.dtype | None = None,
148
+ ):
149
+ super().__init__()
150
+ _require_mamba2_ssm()
151
+
152
+ # rmsnorm=False, use_mem_eff_path=False: same "portable,
153
+ # dependency-light path" rationale as the Mamba-1 cell's
154
+ # use_fast_path=False -- see module docstring. We never call
155
+ # self.mamba2.forward()/.step(); only its submodules (in_proj,
156
+ # conv1d, A_log, D, dt_bias, out_proj) are used, by step() below.
157
+ self.mamba2 = Mamba2(
158
+ d_model=d_model,
159
+ d_state=d_state,
160
+ d_conv=d_conv,
161
+ expand=expand,
162
+ headdim=headdim,
163
+ ngroups=ngroups,
164
+ rmsnorm=False,
165
+ use_mem_eff_path=False,
166
+ layer_idx=layer_idx,
167
+ device=device,
168
+ dtype=dtype,
169
+ )
170
+ self.d_model = d_model
171
+ self.d_inner = self.mamba2.d_inner
172
+ self.d_ssm = self.mamba2.d_ssm # == d_inner here (d_ssm kwarg left at default None)
173
+ self.d_state = self.mamba2.d_state
174
+ self.d_conv = self.mamba2.d_conv
175
+ self.headdim = self.mamba2.headdim
176
+ self.ngroups = self.mamba2.ngroups
177
+ self.nheads = self.mamba2.nheads
178
+ self.conv_dim = self.d_ssm + 2 * self.ngroups * self.d_state
179
+
180
+ def init_state(
181
+ self, batch_size: int, device: torch.device | None = None, dtype: torch.dtype | None = None
182
+ ) -> tuple[torch.Tensor, torch.Tensor]:
183
+ """Zero-initialized (conv_state, ssm_state) -- same zero-init
184
+ rationale as the Mamba-1 cell (Mamba2.allocate_inference_cache's
185
+ own convention is also zero)."""
186
+ conv_state = torch.zeros(batch_size, self.conv_dim, self.d_conv, device=device, dtype=dtype)
187
+ ssm_state = torch.zeros(
188
+ batch_size, self.nheads, self.headdim, self.d_state, device=device, dtype=dtype
189
+ )
190
+ return conv_state, ssm_state
191
+
192
+ def step(
193
+ self,
194
+ hidden_states: torch.Tensor,
195
+ conv_state: torch.Tensor,
196
+ ssm_state: torch.Tensor,
197
+ ) -> tuple[torch.Tensor, torch.Tensor, torch.Tensor]:
198
+ """Single-timestep SSD update. `hidden_states`: (B, d_model).
199
+
200
+ Line-for-line equivalent to `Mamba2.step()`'s ngroups==1,
201
+ non-fast-path branch (mamba_ssm/modules/mamba2.py), except every
202
+ state update is out-of-place (`new_state = ...`, no `.copy_()`) --
203
+ see module docstring / mamba_controller.py's "Why not just call
204
+ Mamba.step()?" section for why this is required under BPTT.
205
+ """
206
+ m = self.mamba2
207
+ dtype = hidden_states.dtype
208
+
209
+ # ---- parallel [z, x, B, C, dt] projection ------------------------
210
+ zxbcdt = m.in_proj(
211
+ hidden_states
212
+ ) # (B, 2*d_ssm + 2*ngroups*d_state + nheads), d_mlp==0 here
213
+ z, xBC, dt = torch.split(
214
+ zxbcdt, [self.d_ssm, self.d_ssm + 2 * self.ngroups * self.d_state, self.nheads], dim=-1
215
+ )
216
+
217
+ # ---- causal depthwise conv over [x, B, C], functional (rolling
218
+ # window) -- same shift-and-append pattern as the Mamba-1 cell,
219
+ # just over the wider xBC channel set.
220
+ new_conv_state = torch.cat([conv_state[:, :, 1:], xBC.unsqueeze(-1)], dim=-1)
221
+ conv_weight = m.conv1d.weight.squeeze(1) # (conv_dim, 1, d_conv) -> (conv_dim, d_conv)
222
+ xBC = torch.sum(new_conv_state * conv_weight, dim=-1) # (B, conv_dim)
223
+ if m.conv1d.bias is not None:
224
+ xBC = xBC + m.conv1d.bias
225
+ xBC = m.act(xBC).to(dtype=dtype)
226
+
227
+ x, B, C = torch.split(
228
+ xBC, [self.d_ssm, self.ngroups * self.d_state, self.ngroups * self.d_state], dim=-1
229
+ )
230
+
231
+ # ---- SSD recurrence, forced fp32 (same chronic-NaN fix as the
232
+ # Mamba-1 cell's step() -- ssm_state persists across an entire
233
+ # episode, so a single fp16 overflow anywhere in this chain
234
+ # poisons every later step; see mamba_controller.py's FIX comment
235
+ # for the full diagnosis, which applies here unchanged).
236
+ with torch.autocast(device_type=hidden_states.device.type, enabled=False):
237
+ dt32 = dt.float()
238
+ x32 = x.float()
239
+ B32 = B.float()
240
+ C32 = C.float()
241
+ dt_bias32 = m.dt_bias.float()
242
+ A_log_c = m.A_log.float().clamp(
243
+ min=-20.0, max=20.0
244
+ ) # same lower-bound guard as Mamba-1's cell
245
+ A32 = -torch.exp(
246
+ A_log_c
247
+ ) # (nheads,) -- scalar-per-head, SSD's defining structure (paper Sec. 5.1)
248
+
249
+ dt32 = F.softplus(dt32 + dt_bias32) # (B, nheads)
250
+ dt32 = dt32.clamp(
251
+ min=1e-6, max=100.0
252
+ ) # normal dt is ~0.001-0.1, same bound as Mamba-1's cell
253
+ dA = torch.exp(
254
+ dt32 * A32
255
+ ) # (B, nheads) -- scalar decay per head, not per (channel,state)
256
+
257
+ x32 = x32.view(x32.shape[0], self.nheads, self.headdim) # (B, H, P)
258
+ # ngroups == 1: B/C are shared across every head (Mamba-2's own
259
+ # MVA-equivalent default here -- see module docstring).
260
+ B32 = B32.view(B32.shape[0], self.d_state) # (B, N)
261
+ C32 = C32.view(C32.shape[0], self.d_state) # (B, N)
262
+
263
+ dBx = torch.einsum("bh,bn,bhp->bhpn", dt32, B32, x32) # (B, H, P, N)
264
+ new_ssm_state32 = ssm_state.float() * dA.view(-1, self.nheads, 1, 1) + dBx
265
+ new_ssm_state32 = new_ssm_state32.clamp(
266
+ min=-1e4, max=1e4
267
+ ) # same hard stop as Mamba-1's cell
268
+
269
+ y32 = torch.einsum("bhpn,bn->bhp", new_ssm_state32, C32) # (B, H, P)
270
+ y32 = y32 + m.D.float().view(1, self.nheads, 1) * x32
271
+ y32 = y32.reshape(y32.shape[0], self.d_ssm) # (B, d_ssm)
272
+ y32 = y32 * m.act(z).float() # rmsnorm=False -> plain silu gate
273
+ y32 = y32.clamp(min=-1e4, max=1e4) # stay in fp16 range before out_proj
274
+ new_ssm_state = new_ssm_state32.to(dtype)
275
+ y = y32.to(dtype)
276
+
277
+ out = m.out_proj(y) # (B, d_model) -- back under ambient autocast
278
+ return out, new_conv_state, new_ssm_state
279
+
280
+
281
+ # ==========================================================================
282
+ # 2. Mamba2ControllerBlock -- pre-norm residual wrapper around one cell
283
+ # ==========================================================================
284
+ class Mamba2ControllerBlock(nn.Module):
285
+ """Add -> LN -> Mixer residual block around one `Mamba2ControllerCell`,
286
+ identical pattern to mamba_controller.py's `MambaControllerBlock`."""
287
+
288
+ def __init__(
289
+ self,
290
+ d_model: int,
291
+ d_state: int = 64,
292
+ d_conv: int = 4,
293
+ expand: int = 2,
294
+ headdim: int = 64,
295
+ ngroups: int = 1,
296
+ layer_idx: int | None = None,
297
+ device: torch.device | None = None,
298
+ dtype: torch.dtype | None = None,
299
+ ):
300
+ super().__init__()
301
+ self.norm = nn.LayerNorm(d_model, device=device, dtype=dtype)
302
+ self.cell = Mamba2ControllerCell(
303
+ d_model,
304
+ d_state=d_state,
305
+ d_conv=d_conv,
306
+ expand=expand,
307
+ headdim=headdim,
308
+ ngroups=ngroups,
309
+ layer_idx=layer_idx,
310
+ device=device,
311
+ dtype=dtype,
312
+ )
313
+
314
+ def init_state(
315
+ self, batch_size: int, device: torch.device | None = None, dtype: torch.dtype | None = None
316
+ ):
317
+ return self.cell.init_state(batch_size, device=device, dtype=dtype)
318
+
319
+ def step(self, x: torch.Tensor, state: tuple[torch.Tensor, torch.Tensor]):
320
+ conv_state, ssm_state = state
321
+ out, new_conv_state, new_ssm_state = self.cell.step(self.norm(x), conv_state, ssm_state)
322
+ return x + out, (new_conv_state, new_ssm_state)
323
+
324
+
325
+ # ==========================================================================
326
+ # 3. Mamba2ControllerWrapper -- stack of blocks, nn.LSTM-compatible call API
327
+ # ==========================================================================
328
+ class Mamba2ControllerWrapper(nn.Module):
329
+ """Stacks `num_blocks` `Mamba2ControllerBlock`s and exposes the exact
330
+ same `dnc.dnc.DNC._layer_forward`-compatible calling convention as
331
+ mamba_controller.py's `MambaControllerWrapper` -- see that class's
332
+ docstring for the exact shape contract. Identical MoE-interleaving
333
+ support (Option 4, moe_layer.py), same convention as the Mamba-1
334
+ wrapper, so `--moe` works unchanged for `--controller mamba2`.
335
+ """
336
+
337
+ def __init__(
338
+ self,
339
+ in_dim: int,
340
+ d_model: int,
341
+ num_blocks: int = 2,
342
+ moe_enabled: bool = False,
343
+ moe_num_experts: int = 8,
344
+ moe_expert_dim: int | None = None,
345
+ moe_capacity_factor: float = 1.5,
346
+ moe_load_balance_alpha: float = 0.01,
347
+ moe_top_k: int = 1,
348
+ d_state: int = 64,
349
+ d_conv: int = 4,
350
+ expand: int = 2,
351
+ headdim: int = 64,
352
+ ngroups: int = 1,
353
+ device: torch.device | None = None,
354
+ dtype: torch.dtype | None = None,
355
+ ):
356
+ super().__init__()
357
+ self.d_model = d_model
358
+ self.num_blocks = num_blocks
359
+ # Same first-block-only dimension-matching adapter as MambaControllerWrapper.
360
+ self.in_adapter: nn.Module = (
361
+ nn.Identity()
362
+ if in_dim == d_model
363
+ else nn.Linear(in_dim, d_model, device=device, dtype=dtype)
364
+ )
365
+ self.blocks: nn.ModuleList = nn.ModuleList(
366
+ [
367
+ Mamba2ControllerBlock(
368
+ d_model,
369
+ d_state=d_state,
370
+ d_conv=d_conv,
371
+ expand=expand,
372
+ headdim=headdim,
373
+ ngroups=ngroups,
374
+ layer_idx=i,
375
+ device=device,
376
+ dtype=dtype,
377
+ )
378
+ for i in range(num_blocks)
379
+ ]
380
+ )
381
+ self.moe_enabled = moe_enabled
382
+ self.moe_blocks: nn.ModuleList | None = None
383
+ if moe_enabled:
384
+ self.moe_blocks = nn.ModuleList(
385
+ [
386
+ MoEBlock(
387
+ d_model,
388
+ num_experts=moe_num_experts,
389
+ expert_dim=moe_expert_dim,
390
+ capacity_factor=moe_capacity_factor,
391
+ load_balance_alpha=moe_load_balance_alpha,
392
+ top_k=moe_top_k,
393
+ device=device,
394
+ dtype=dtype,
395
+ )
396
+ for _ in range(num_blocks)
397
+ ]
398
+ )
399
+
400
+ def init_state(
401
+ self, batch_size: int, device: torch.device | None = None, dtype: torch.dtype | None = None
402
+ ):
403
+ if device is None or dtype is None:
404
+ p = next(self.parameters())
405
+ device = device if device is not None else p.device
406
+ dtype = dtype if dtype is not None else p.dtype
407
+ blocks = cast(Iterable[Mamba2ControllerBlock], self.blocks)
408
+ return [blk.init_state(batch_size, device=device, dtype=dtype) for blk in blocks]
409
+
410
+ def forward(self, input: torch.Tensor, hx):
411
+ # input: (B, 1, in_dim) -- same single-timestep contract as
412
+ # MambaControllerWrapper.forward(); see that class's docstring.
413
+ assert input.dim() == 3 and input.size(1) == 1, (
414
+ "Mamba2ControllerWrapper only supports single-timestep calls "
415
+ f"(got shape {tuple(input.shape)}); this mirrors how DNC drives "
416
+ "nn.LSTM one step at a time, never a full sequence at once."
417
+ )
418
+ x = input.squeeze(1)
419
+ x = self.in_adapter(x)
420
+
421
+ if hx is None:
422
+ hx = self.init_state(x.size(0), device=x.device, dtype=x.dtype)
423
+
424
+ new_hx = []
425
+ blocks = cast(Iterable[Mamba2ControllerBlock], self.blocks)
426
+ for i, (block, state) in enumerate(zip(blocks, hx)):
427
+ x, new_state = block.step(x, state)
428
+ if self.moe_enabled:
429
+ assert self.moe_blocks is not None
430
+ x = self.moe_blocks[i](x)
431
+ new_hx.append(new_state)
432
+
433
+ return x.unsqueeze(1), new_hx