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,265 @@
1
+ """
2
+ file: LNN_controller/cfc_backbone_parallel.py -- v1
3
+
4
+ CfC as a whole-sequence backbone for SplitGraphDNC, plus a factory that
5
+ builds ANY parallel backbone (or a stack of them) from a variant string.
6
+
7
+ CfCBackboneParallel: (B, L, in_dim) -> (B, L, d_model), same contract as
8
+ mamba_backbone_parallel.MambaBackboneParallel, and like it has NO notion of
9
+ DNC/Memory/read vectors (it only ever sees the raw input stream X).
10
+ NOTE: ncps' CfC loops over time internally (no parallel scan) and its backbone
11
+ MLP consumes the previous hidden state, so this is sequential inside. The
12
+ split-graph benefit is structural (backbone can never see reads), not a
13
+ Mamba-style scan speedup.
14
+ Block = pre-norm residual (Add -> LN -> CfC). Runs in real fp32 by default
15
+ (force_fp32=True), same policy as mamba_backbone_parallel._ParallelBlock.
16
+
17
+ build_parallel_backbone(variant=...): variant is "mamba1" | "mamba2" |
18
+ "mamba3" | "cfc", or a "+"-joined stack such as "mamba2+cfc" (stage 0 maps
19
+ in_dim -> d_model, later stages d_model -> d_model). num_blocks applies PER
20
+ STAGE. This is the single dispatch point for new parallel backbones.
21
+ """
22
+
23
+ from __future__ import annotations
24
+
25
+ import torch
26
+ import torch.nn as nn
27
+
28
+ from initium.LNN_controller.cfc_controller import CfC, _require_ncps
29
+ from initium.mamba_controller.mamba_backbone_parallel import MambaBackboneParallel
30
+ from initium.MoE.moe_layer import MoEBlock
31
+
32
+ _BACKBONE_KINDS = ("mamba1", "mamba2", "mamba3", "cfc")
33
+
34
+
35
+ class _CfCParallelBlock(nn.Module):
36
+ def __init__(
37
+ self,
38
+ d_model,
39
+ units,
40
+ mode,
41
+ backbone_units,
42
+ backbone_layers,
43
+ backbone_dropout,
44
+ activation,
45
+ mixed_memory,
46
+ residual,
47
+ force_fp32,
48
+ device=None,
49
+ dtype=None,
50
+ ):
51
+ super().__init__()
52
+ _require_ncps()
53
+ units = d_model if units is None else units
54
+ self.residual, self.force_fp32 = residual, force_fp32
55
+ self.norm = nn.LayerNorm(d_model, device=device, dtype=dtype)
56
+ self.cfc = CfC(
57
+ input_size=d_model,
58
+ units=units,
59
+ proj_size=None if units == d_model else d_model,
60
+ return_sequences=True,
61
+ batch_first=True,
62
+ mixed_memory=mixed_memory,
63
+ mode=mode,
64
+ activation=activation,
65
+ backbone_units=backbone_units,
66
+ backbone_layers=backbone_layers,
67
+ backbone_dropout=backbone_dropout,
68
+ )
69
+ if device is not None or dtype is not None:
70
+ self.cfc.to(device=device, dtype=dtype)
71
+
72
+ def forward(self, x): # x: (B, L, D)
73
+ if self.force_fp32:
74
+ with torch.autocast(device_type=x.device.type, enabled=False):
75
+ out, _ = self.cfc(self.norm(x.float()))
76
+ else:
77
+ out, _ = self.cfc(self.norm(x))
78
+ out = out.to(x.dtype)
79
+ return x + out if self.residual else out
80
+
81
+
82
+ class CfCBackboneParallel(nn.Module):
83
+ def __init__(
84
+ self,
85
+ in_dim,
86
+ d_model,
87
+ num_blocks=2,
88
+ units=None,
89
+ mode="default",
90
+ backbone_units=512,
91
+ backbone_layers=1,
92
+ backbone_dropout=0.0,
93
+ activation="lecun_tanh",
94
+ mixed_memory=False,
95
+ residual=True,
96
+ force_fp32=True,
97
+ moe_enabled: bool = False,
98
+ moe_num_experts: int = 8,
99
+ moe_expert_dim: int | None = None,
100
+ moe_top_k: int = 1,
101
+ moe_capacity_factor: float = 1.5,
102
+ moe_load_balance_alpha: float = 0.01,
103
+ device=None,
104
+ dtype=None,
105
+ ):
106
+ super().__init__()
107
+ self.d_model = d_model
108
+ self.in_adapter: nn.Module = (
109
+ nn.Identity()
110
+ if in_dim == d_model
111
+ else nn.Linear(in_dim, d_model, device=device, dtype=dtype)
112
+ )
113
+ self.blocks: nn.ModuleList = nn.ModuleList(
114
+ [
115
+ _CfCParallelBlock(
116
+ d_model,
117
+ units,
118
+ mode,
119
+ backbone_units,
120
+ backbone_layers,
121
+ backbone_dropout,
122
+ activation,
123
+ mixed_memory,
124
+ residual,
125
+ force_fp32,
126
+ device=device,
127
+ dtype=dtype,
128
+ )
129
+ for _ in range(num_blocks)
130
+ ]
131
+ )
132
+ # External interleave, applied to the WHOLE (B, L, d_model) sequence
133
+ # after each block (SwitchMoE reshapes any leading dims to a flat
134
+ # token batch, so this costs nothing extra to support here).
135
+ self.moe_enabled = moe_enabled
136
+ self.moe_blocks: nn.ModuleList | None = None
137
+ if moe_enabled:
138
+ self.moe_blocks = nn.ModuleList(
139
+ [
140
+ MoEBlock(
141
+ d_model,
142
+ num_experts=moe_num_experts,
143
+ expert_dim=moe_expert_dim,
144
+ top_k=moe_top_k,
145
+ capacity_factor=moe_capacity_factor,
146
+ load_balance_alpha=moe_load_balance_alpha,
147
+ device=device,
148
+ dtype=dtype,
149
+ )
150
+ for _ in range(num_blocks)
151
+ ]
152
+ )
153
+
154
+ def forward(self, x): # (B, L, in_dim) -> (B, L, d_model), called ONCE per training step
155
+ h = self.in_adapter(x)
156
+ for i, block in enumerate(self.blocks):
157
+ h = block(h)
158
+ if self.moe_enabled:
159
+ assert self.moe_blocks is not None
160
+ h = self.moe_blocks[i](h)
161
+ return h
162
+
163
+
164
+ def build_parallel_backbone(
165
+ in_dim,
166
+ d_model,
167
+ num_blocks=2,
168
+ variant="mamba1",
169
+ d_state=16,
170
+ d_conv=4,
171
+ expand=2,
172
+ headdim=64,
173
+ cfc_kwargs=None,
174
+ moe_enabled: bool = False,
175
+ moe_num_experts: int = 8,
176
+ moe_expert_dim: int | None = None,
177
+ moe_top_k: int = 1,
178
+ moe_capacity_factor: float = 1.5,
179
+ moe_load_balance_alpha: float = 0.01,
180
+ device=None,
181
+ dtype=None,
182
+ ):
183
+ kinds = [k.strip().lower() for k in variant.split("+")]
184
+ bad = [k for k in kinds if k not in _BACKBONE_KINDS]
185
+ if bad:
186
+ raise ValueError(
187
+ f"build_parallel_backbone: unknown variant part(s) {bad} in {variant!r}, "
188
+ f"expected '+'-joined parts of {_BACKBONE_KINDS}"
189
+ )
190
+ stages: list[nn.Module] = []
191
+ cur = in_dim
192
+ for kind in kinds:
193
+ if kind == "cfc":
194
+ stages.append(
195
+ CfCBackboneParallel(
196
+ cur,
197
+ d_model,
198
+ num_blocks=num_blocks,
199
+ device=device,
200
+ dtype=dtype,
201
+ moe_enabled=moe_enabled,
202
+ moe_num_experts=moe_num_experts,
203
+ moe_expert_dim=moe_expert_dim,
204
+ moe_top_k=moe_top_k,
205
+ moe_capacity_factor=moe_capacity_factor,
206
+ moe_load_balance_alpha=moe_load_balance_alpha,
207
+ **(cfc_kwargs or {}),
208
+ )
209
+ )
210
+ else:
211
+ stages.append(
212
+ MambaBackboneParallel(
213
+ in_dim=cur,
214
+ d_model=d_model,
215
+ num_blocks=num_blocks,
216
+ variant=kind,
217
+ d_state=d_state,
218
+ d_conv=d_conv,
219
+ expand=expand,
220
+ headdim=headdim,
221
+ device=device,
222
+ dtype=dtype,
223
+ moe_enabled=moe_enabled,
224
+ moe_num_experts=moe_num_experts,
225
+ moe_expert_dim=moe_expert_dim,
226
+ moe_top_k=moe_top_k,
227
+ moe_capacity_factor=moe_capacity_factor,
228
+ moe_load_balance_alpha=moe_load_balance_alpha,
229
+ )
230
+ )
231
+ cur = d_model
232
+ return stages[0] if len(stages) == 1 else nn.Sequential(*stages)
233
+
234
+
235
+ def collect_backbone_moe_layers(backbone: nn.Module) -> list:
236
+ """Flat list of every installed MoE sublayer on a backbone built by
237
+ build_parallel_backbone -- a single stage or a '+'-stacked nn.Sequential
238
+ of stages, each of which self-reports via .moe_enabled/.moe_blocks (same
239
+ convention every controller wrapper in this project uses)."""
240
+ stages = list(backbone) if isinstance(backbone, nn.Sequential) else [backbone]
241
+ moe_layers: list[nn.Module] = []
242
+ for stage in stages:
243
+ if getattr(stage, "moe_enabled", False):
244
+ blocks = stage.moe_blocks
245
+ if not isinstance(blocks, nn.ModuleList):
246
+ raise TypeError(
247
+ "MoE-enabled backbone stages must expose a ModuleList of moe_blocks"
248
+ )
249
+ moe_layers.extend(blocks)
250
+ return moe_layers
251
+
252
+
253
+ if (
254
+ __name__ == "__main__"
255
+ ): # smoke test: python -m LNN_controller.cfc_backbone_parallel (from project root)
256
+ B, L, in_dim, d = 4, 12, 40, 32
257
+ for variant in ("cfc", "cfc+cfc"):
258
+ m = build_parallel_backbone(
259
+ in_dim, d, num_blocks=1, variant=variant, cfc_kwargs=dict(backbone_units=64)
260
+ )
261
+ y = m(torch.randn(B, L, in_dim))
262
+ assert y.shape == (B, L, d), y.shape
263
+ y.pow(2).mean().backward()
264
+ print(f"{variant}: OK")
265
+ print("For mamba1/2/3+cfc stacks, run the pilot below on a GPU (Mamba kernels need CUDA).")
@@ -0,0 +1,325 @@
1
+ """
2
+ file: LNN_controller/cfc_controller.py -- v1
3
+
4
+ Standalone Closed-form Continuous-time (CfC) DNC controller (Hasani et al.,
5
+ "Closed-form Continuous-time Neural Networks", arXiv 2106.13898):
6
+ MambaDNC's rnn_type='cfc'. Uses the `ncps` package's CfC module as-is
7
+ (pip install ncps -> from ncps.torch import CfC), driven ONE DNC timestep at
8
+ a time as a length-1 sequence, interleaved with Memory reads/writes exactly
9
+ like mamba_controller.py's controllers. ncps' CfC forward is out-of-place
10
+ (returns new h), so it is BPTT-safe with no custom step() needed. Its state
11
+ is bounded (convex mix of tanh heads), so no fp32-forcing/clamp workarounds.
12
+ timespans are not used (ts=1.0 per step): this task has no timestamps.
13
+
14
+ CONTROLLER-WRAPPER PROTOCOL (what makes this composable -- see also
15
+ chained_controller.py):
16
+ wrapper.init_state(batch_size, device=None, dtype=None) -> state
17
+ wrapper(x: (B,1,in_dim), state) -> (out: (B,1,d_model), new_state)
18
+ wrapper.d_model, wrapper.in_adapter, wrapper.moe_enabled, wrapper.moe_blocks
19
+ Any object following this can be a DNC controller, a split-graph combiner,
20
+ or a stage in a ChainedControllerWrapper.
21
+
22
+ Block = pre-norm residual (Add -> LN -> CfC), same pattern as the Mamba blocks.
23
+ State per block: h (B, units) fp32, or (h, c) if mixed_memory=True (CfC-mmRNN).
24
+ """
25
+
26
+ from __future__ import annotations
27
+
28
+ from collections.abc import Iterable
29
+ from typing import cast
30
+
31
+ import torch
32
+ import torch.nn as nn
33
+
34
+ from initium.MoE.moe_layer import MoEBlock, MultiSourceMoEBlock
35
+
36
+ _NCPS_IMPORT_ERROR: str | None
37
+
38
+ try:
39
+ from ncps.torch import CfC
40
+ except ImportError as _e: # pragma: no cover - environment-dependent
41
+ CfC = None
42
+ _NCPS_IMPORT_ERROR = (
43
+ "cfc_controller.py requires the `ncps` package (`pip install ncps`). "
44
+ f"Original import error: {_e}"
45
+ )
46
+ else:
47
+ _NCPS_IMPORT_ERROR = None
48
+
49
+
50
+ def _require_ncps() -> None:
51
+ if CfC is None:
52
+ raise ImportError(_NCPS_IMPORT_ERROR)
53
+
54
+
55
+ def _state_to_fp32(state):
56
+ # Keep the recurrent state fp32 between steps (ncps' cell returns fp16 under autocast).
57
+ if isinstance(state, tuple):
58
+ return tuple(s.float() for s in state)
59
+ return state.float()
60
+
61
+
62
+ class CfCControllerBlock(nn.Module):
63
+ def __init__(
64
+ self,
65
+ d_model,
66
+ units=None,
67
+ mode="default",
68
+ backbone_units=512,
69
+ backbone_layers=1,
70
+ backbone_dropout=0.0,
71
+ activation="lecun_tanh",
72
+ mixed_memory=False,
73
+ residual=True,
74
+ device=None,
75
+ dtype=None,
76
+ ):
77
+ super().__init__()
78
+ _require_ncps()
79
+ units = d_model if units is None else units
80
+ self.d_model, self.units = d_model, units
81
+ self.mixed_memory, self.residual = mixed_memory, residual
82
+ self.norm = nn.LayerNorm(d_model, device=device, dtype=dtype)
83
+ self.cfc = CfC(
84
+ input_size=d_model,
85
+ units=units,
86
+ proj_size=None
87
+ if units == d_model
88
+ else d_model, # project back to d_model if state is wider/narrower
89
+ return_sequences=True,
90
+ batch_first=True,
91
+ mixed_memory=mixed_memory,
92
+ mode=mode,
93
+ activation=activation,
94
+ backbone_units=backbone_units,
95
+ backbone_layers=backbone_layers,
96
+ backbone_dropout=backbone_dropout,
97
+ )
98
+ if device is not None or dtype is not None:
99
+ self.cfc.to(device=device, dtype=dtype)
100
+
101
+ def init_state(self, batch_size, device=None, dtype=None): # dtype ignored: always fp32
102
+ device = device if device is not None else next(self.parameters()).device
103
+ h = torch.zeros(batch_size, self.units, device=device, dtype=torch.float32)
104
+ return (h, torch.zeros_like(h)) if self.mixed_memory else h
105
+
106
+ def step(self, x, state):
107
+ with torch.autocast(device_type=x.device.type, enabled=False):
108
+ out, new_state = self.cfc(
109
+ self.norm(x.float()).unsqueeze(1),
110
+ _state_to_fp32(state) if state is not None else None,
111
+ )
112
+ out = out.squeeze(1).clamp(min=-1e4, max=1e4)
113
+ y = x + out.to(x.dtype) if self.residual else out.to(x.dtype)
114
+ return y, _state_to_fp32(new_state)
115
+
116
+
117
+ class CfCControllerWrapper(nn.Module):
118
+ """Stack of `num_blocks` CfCControllerBlocks with the nn.LSTM-compatible
119
+ call convention DNC._layer_forward expects (see module docstring)."""
120
+
121
+ def __init__(
122
+ self,
123
+ in_dim,
124
+ d_model,
125
+ num_blocks=2,
126
+ units=None,
127
+ mode="default",
128
+ backbone_units=512,
129
+ backbone_layers=1,
130
+ backbone_dropout=0.0,
131
+ activation="lecun_tanh",
132
+ mixed_memory=False,
133
+ residual=True,
134
+ moe_enabled: bool = False,
135
+ moe_num_experts: int = 8,
136
+ moe_expert_dim: int | None = None,
137
+ moe_top_k: int = 1,
138
+ moe_capacity_factor: float = 1.5,
139
+ moe_load_balance_alpha: float = 0.01,
140
+ moe_source_dims: list[int] | None = None,
141
+ device=None,
142
+ dtype=None,
143
+ ):
144
+ super().__init__()
145
+ self.d_model, self.num_blocks = d_model, num_blocks
146
+
147
+ # moe_source_dims: when given (>=1 entries, each an input SOURCE's
148
+ # OWN raw width -- e.g. [hidden_size, read_vectors_size] for
149
+ # SplitGraphDNC's [backbone_output, prev_read_vector] combiner
150
+ # input), this wrapper is driven via forward_multi_source() instead
151
+ # of forward(); widths need not match each other, which is exactly
152
+ # the "variable-length inputs" property CfC must keep once MoE
153
+ # sits in front of it. sum(moe_source_dims) must still equal
154
+ # in_dim, since in_dim is what every other bookkeeping path in this
155
+ # project (checkpoints, dim-compat checks) reads as one number.
156
+ self.moe_source_dims = list(moe_source_dims) if moe_source_dims else None
157
+ if self.moe_source_dims is not None:
158
+ if sum(self.moe_source_dims) != in_dim:
159
+ raise ValueError(
160
+ f"CfCControllerWrapper: sum(moe_source_dims)="
161
+ f"{sum(self.moe_source_dims)} != in_dim={in_dim}"
162
+ )
163
+ if not moe_enabled:
164
+ raise ValueError("CfCControllerWrapper: moe_source_dims requires moe_enabled=True")
165
+
166
+ self.in_adapter: nn.Module = (
167
+ nn.Identity()
168
+ if in_dim == d_model
169
+ else nn.Linear(in_dim, d_model, device=device, dtype=dtype)
170
+ )
171
+ self.blocks: nn.ModuleList = nn.ModuleList(
172
+ [
173
+ CfCControllerBlock(
174
+ d_model,
175
+ units=units,
176
+ mode=mode,
177
+ backbone_units=backbone_units,
178
+ backbone_layers=backbone_layers,
179
+ backbone_dropout=backbone_dropout,
180
+ activation=activation,
181
+ mixed_memory=mixed_memory,
182
+ residual=residual,
183
+ device=device,
184
+ dtype=dtype,
185
+ )
186
+ for _ in range(num_blocks)
187
+ ]
188
+ )
189
+
190
+ self.moe_enabled = moe_enabled
191
+ self.moe_blocks: nn.ModuleList | None = None
192
+ self.source_in_adapters: nn.ModuleList | None = None
193
+ if moe_enabled and self.moe_source_dims is not None:
194
+ # Multi-source front-end: each raw source is projected to
195
+ # d_model, routed+combined by ONE source-aware Top-K MoE bank
196
+ # BEFORE entering the recurrent CfC stack (see
197
+ # forward_multi_source). self.moe_blocks holds this single
198
+ # MultiSourceMoEBlock so every existing "extend moe_layers from
199
+ # layer_controller.moe_blocks" call site (MambaDNC,
200
+ # ChainedControllerWrapper) keeps working with zero changes.
201
+ self.source_in_adapters = nn.ModuleList(
202
+ [
203
+ nn.Identity()
204
+ if w == d_model
205
+ else nn.Linear(w, d_model, device=device, dtype=dtype)
206
+ for w in self.moe_source_dims
207
+ ]
208
+ )
209
+ self.moe_blocks = nn.ModuleList(
210
+ [
211
+ MultiSourceMoEBlock(
212
+ d_model,
213
+ num_sources=len(self.moe_source_dims),
214
+ num_experts=moe_num_experts,
215
+ expert_dim=moe_expert_dim,
216
+ top_k=moe_top_k,
217
+ capacity_factor=moe_capacity_factor,
218
+ load_balance_alpha=moe_load_balance_alpha,
219
+ device=device,
220
+ dtype=dtype,
221
+ )
222
+ ]
223
+ )
224
+ elif moe_enabled:
225
+ # Plain per-block external interleave, same convention as
226
+ # MambaControllerWrapper / Mamba2ControllerWrapper.
227
+ self.moe_blocks = nn.ModuleList(
228
+ [
229
+ MoEBlock(
230
+ d_model,
231
+ num_experts=moe_num_experts,
232
+ expert_dim=moe_expert_dim,
233
+ top_k=moe_top_k,
234
+ capacity_factor=moe_capacity_factor,
235
+ load_balance_alpha=moe_load_balance_alpha,
236
+ device=device,
237
+ dtype=dtype,
238
+ )
239
+ for _ in range(num_blocks)
240
+ ]
241
+ )
242
+
243
+ def init_state(self, batch_size, device=None, dtype=None):
244
+ if device is None:
245
+ device = next(self.parameters()).device
246
+ blocks = cast(Iterable[CfCControllerBlock], self.blocks)
247
+ return [blk.init_state(batch_size, device=device) for blk in blocks]
248
+
249
+ def forward(self, input, hx):
250
+ assert input.dim() == 3 and input.size(1) == 1, (
251
+ "CfCControllerWrapper only supports single-timestep calls "
252
+ f"(got shape {tuple(input.shape)})."
253
+ )
254
+ if self.moe_source_dims is not None:
255
+ raise RuntimeError(
256
+ "CfCControllerWrapper was configured with moe_source_dims -- "
257
+ "call forward_multi_source(sources, hx) instead of forward()."
258
+ )
259
+ x = self.in_adapter(input.squeeze(1))
260
+ if hx is None:
261
+ hx = self.init_state(x.size(0), device=x.device)
262
+ new_hx = []
263
+ blocks = cast(Iterable[CfCControllerBlock], self.blocks)
264
+ for i, (block, state) in enumerate(zip(blocks, hx)):
265
+ x, new_state = block.step(x, state)
266
+ if self.moe_enabled:
267
+ assert self.moe_blocks is not None
268
+ x = self.moe_blocks[i](x)
269
+ new_hx.append(new_state)
270
+ return x.unsqueeze(1), new_hx
271
+
272
+ def forward_multi_source(self, sources: list[torch.Tensor], hx):
273
+ """Multi-source entry point (requires moe_source_dims). `sources[i]`
274
+ is a (B, moe_source_dims[i]) tensor -- e.g. SplitGraphDNC's
275
+ controller combiner passes [h_t, read_vec] directly instead of
276
+ pre-concatenating them. Each source is projected to d_model by its
277
+ own adapter, then routed+combined by the shared, source-aware
278
+ Top-K MoE bank BEFORE entering the recurrent CfC stack -- this is
279
+ what preserves CfC's "many inputs, specialized handling per input"
280
+ property once MoE sits in front of it, while the recurrent stack
281
+ itself still only ever sees one fused (B, d_model) vector per step
282
+ (CfC block internals are completely unchanged)."""
283
+ if self.moe_source_dims is None:
284
+ raise RuntimeError("forward_multi_source requires moe_source_dims to have been set")
285
+ assert self.source_in_adapters is not None and self.moe_blocks is not None
286
+ projected = [adapter(s) for adapter, s in zip(self.source_in_adapters, sources)]
287
+ fused_per_source = self.moe_blocks[0](projected) # list[Tensor], one per source
288
+ x = torch.stack(fused_per_source, dim=0).sum(dim=0) # combine into one fused input
289
+ if hx is None:
290
+ hx = self.init_state(x.size(0), device=x.device)
291
+ new_hx = []
292
+ blocks = cast(Iterable[CfCControllerBlock], self.blocks)
293
+ for block, state in zip(blocks, hx):
294
+ x, new_state = block.step(x, state)
295
+ new_hx.append(new_state)
296
+ return x.unsqueeze(1), new_hx
297
+
298
+
299
+ if (
300
+ __name__ == "__main__"
301
+ ): # smoke test: python -m LNN_controller.cfc_controller (from project root)
302
+ from initium.LNN_controller.chained_controller import ChainedControllerWrapper
303
+
304
+ B, T, in_dim, d = 4, 6, 40, 32
305
+ for mm in (False, True):
306
+ w = CfCControllerWrapper(in_dim, d, num_blocks=2, backbone_units=64, mixed_memory=mm)
307
+ hx, loss = w.init_state(B), torch.zeros(())
308
+ for _ in range(T):
309
+ out, hx = w(torch.randn(B, 1, in_dim), hx)
310
+ assert out.shape == (B, 1, d), out.shape
311
+ loss = loss + out.pow(2).mean()
312
+ loss.backward() # BPTT across chained steps must not raise
313
+ print(f"standalone mixed_memory={mm}: OK (loss {float(loss):.4f})")
314
+ ch = ChainedControllerWrapper(
315
+ [
316
+ CfCControllerWrapper(in_dim, d, 1, backbone_units=64),
317
+ CfCControllerWrapper(d, d, 1, backbone_units=64),
318
+ ]
319
+ )
320
+ hx, loss = ch.init_state(B), torch.zeros(())
321
+ for _ in range(T):
322
+ out, hx = ch(torch.randn(B, 1, in_dim), hx)
323
+ loss = loss + out.pow(2).mean()
324
+ loss.backward()
325
+ print("chained: OK")
@@ -0,0 +1,83 @@
1
+ """
2
+ file: LNN_controller/chained_controller.py -- v1
3
+
4
+ Generic composer: runs several controller wrappers back-to-back per DNC
5
+ timestep, each feeding the next (stage i's d_model must equal stage i+1's
6
+ input width). Works with ANY wrapper following the controller-wrapper
7
+ protocol in cfc_controller.py's docstring: MambaControllerWrapper,
8
+ Mamba2ControllerWrapper, Mamba3ControllerWrapper, CfCControllerWrapper, or
9
+ future ones. Example (Mamba backbone -> CfC head):
10
+
11
+ ChainedControllerWrapper([
12
+ MambaControllerWrapper(in_dim=in_dim, d_model=d, num_blocks=2),
13
+ CfCControllerWrapper(in_dim=d, d_model=d, num_blocks=1),
14
+ ])
15
+
16
+ State is a list with one entry per stage. Also usable as a SplitGraphDNC
17
+ combiner_wrapper (same protocol).
18
+ """
19
+
20
+ from __future__ import annotations
21
+
22
+ import torch.nn as nn
23
+
24
+
25
+ def _in_features(stage) -> int:
26
+ a = stage.in_adapter
27
+ return a.in_features if isinstance(a, nn.Linear) else stage.d_model
28
+
29
+
30
+ class ChainedControllerWrapper(nn.Module):
31
+ def __init__(self, stages, stage_kinds=None):
32
+ super().__init__()
33
+ stages = list(stages)
34
+ assert len(stages) >= 1, "ChainedControllerWrapper needs at least one stage"
35
+ for prev, nxt in zip(stages[:-1], stages[1:]):
36
+ assert _in_features(nxt) == prev.d_model, (
37
+ f"stage width mismatch: previous d_model={prev.d_model}, next stage input={_in_features(nxt)}"
38
+ )
39
+ self.stages = nn.ModuleList(stages)
40
+ self.d_model = stages[-1].d_model
41
+ self.in_adapter = stages[
42
+ 0
43
+ ].in_adapter # same object (not re-registered under a new path issue: shared reference)
44
+ # plain python list (NOT ModuleList): the MoE blocks are already registered inside their stages
45
+ self.moe_blocks = [
46
+ b for s in stages if getattr(s, "moe_enabled", False) for b in s.moe_blocks
47
+ ]
48
+ self.moe_enabled = len(self.moe_blocks) > 0
49
+ # Names each stage (e.g. ["mamba", "cfc"] for "mamba+cfc") so the
50
+ # training loop can log which stage a given ablation index refers
51
+ # to. None only for the [f"stage{i}"...] fallback below when a
52
+ # caller doesn't pass kinds.
53
+ self.stage_kinds = (
54
+ list(stage_kinds)
55
+ if stage_kinds is not None
56
+ else [f"stage{i}" for i in range(len(self.stages))]
57
+ )
58
+
59
+ def init_state(self, batch_size, device=None, dtype=None):
60
+ return [s.init_state(batch_size, device=device, dtype=dtype) for s in self.stages]
61
+
62
+ def forward(self, input, hx, skip_stages=None):
63
+ """skip_stages: optional set/list of stage indices to bypass, for
64
+ measuring each stage's contribution the same way pass_through_memory
65
+ measures Memory's contribution (see evaluate_traversal's
66
+ ablate_memory / SplitGraphDNC's combiner_skip_stages). Stage 0 still
67
+ gets its in_adapter applied (so downstream stages see the right
68
+ width) but skips its actual recurrent computation; every later index
69
+ is skipped entirely (its d_model-in == d_model-out, so identity is
70
+ exact)."""
71
+ if hx is None:
72
+ hx = [None] * len(self.stages)
73
+ skip_stages = set(skip_stages) if skip_stages else set()
74
+ x, new_hx = input, []
75
+ for i, (stage, state) in enumerate(zip(self.stages, hx)):
76
+ if i in skip_stages:
77
+ if i == 0:
78
+ x = stage.in_adapter(x.squeeze(1)).unsqueeze(1)
79
+ new_hx.append(state)
80
+ continue
81
+ x, new_state = stage(x, state)
82
+ new_hx.append(new_state)
83
+ return x, new_hx