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,623 @@
1
+ """
2
+ file: split_graph_dnc.py
3
+
4
+ Alternate Phase 3, Step 2, Option 5 -- "A genuine, higher-payoff Idea
5
+ candidate: split the compute graph, don't try to parallelize the addressing
6
+ itself" (see Experiment-Roadmap.md, Idea 5 and its failure-mode table).
7
+
8
+ DISABLED BY DEFAULT AT EVERY LEVEL (module has no global toggle of its own
9
+ -- it's simply not constructed unless the training script's
10
+ SPLIT_GRAPH_ENABLED / --split-graph is explicitly set). The roadmap
11
+ explicitly flags this mechanism as having NO corpus-established failure
12
+ mode yet ("Honesty check first... there is no corpus-established failure
13
+ mode for this option yet") and requiring its own isolated ablation before
14
+ being trusted -- treat every run of this module as an open experiment, not
15
+ a validated component.
16
+
17
+ --- Design (mirrors the roadmap's own description of Idea 5) -------------
18
+ Two halves, matching "(a) the Mamba backbone's own token-level state
19
+ updates, which have no dependency on M_{t-1} and can run through the
20
+ standard parallel scan" and "(b) a genuinely sequential, but much smaller
21
+ and cheaper, memory read/write/addressing step layered on top, consuming
22
+ the backbone's parallel output as an input stream":
23
+
24
+ (a) self.backbone (MambaBackboneParallel, see mamba_backbone_parallel.py)
25
+ processes the RAW input sequence X -- never concatenated with a read
26
+ vector -- in ONE call, using mamba_ssm's own whole-sequence
27
+ forward(). This is what makes it parallel: it has no dependency on
28
+ any memory state, by construction, so there is nothing forcing a
29
+ Python loop.
30
+
31
+ (b) A per-timestep loop that: (i) combines this timestep's backbone
32
+ output h_t with the PREVIOUS timestep's read vector read_{t-1} via a
33
+ small Linear ("combiner") to produce the interface vector xi_t fed
34
+ into Memory, then (ii) calls the existing dnc.memory.Memory exactly
35
+ the same way every other controller in this project already does.
36
+ This is the "read now, decide next hop immediately" loop the
37
+ roadmap's own diagnosis of DEER's and chunking's failures both
38
+ center on -- it is preserved exactly, per-step, sequential, never
39
+ frozen for a window. The ONLY thing removed from the sequential
40
+ critical path is the backbone's own internal computation; Memory
41
+ itself was already the sequential part in every prior architecture
42
+ this project has built, so this doesn't add a new bottleneck, it
43
+ just stops making the (much more expensive) backbone share it.
44
+
45
+ Requirement from the roadmap's own methodological note on this option: "do
46
+ not layer them all at once... Concept 16 / Suspected Pattern SP-10". This
47
+ module therefore deliberately does NOT wire in Option 4's MoE (moe_enabled
48
+ raises if set) -- Option 5 must be run and ablated on its own first.
49
+
50
+ --- Built-in ablation for the roadmap's own required verification --------
51
+ "Verify [the design] preserves the exact 'read now, decide next hop
52
+ immediately' dependency, not just an approximation of it... treat as an
53
+ open risk requiring its own isolated ablation before adoption." The
54
+ combiner can be disabled entirely (combine_reads=False), which makes
55
+ xi_t = h_t for every t -- i.e. addressing becomes a pure function of the
56
+ backbone output and NEVER sees any read vector at all. Comparing
57
+ combine_reads=True vs. False on the SAME architecture is the cheapest
58
+ possible direct test of whether that dependency actually matters here,
59
+ rather than assuming it does because the roadmap says so.
60
+
61
+ --- Functional-usage check (LB-9 / Concept 6) is inherited, not special-
62
+ cased here. This module implements the SAME `pass_through_memory` kwarg
63
+ contract every other controller in this project's forward() already
64
+ honors, so the training script's existing standing verification step
65
+ (evaluate_traversal(..., ablate_memory=True), wired into
66
+ Alter_PHASE3_mamba.py's run() at every EVAL_EVERY cadence, logged to
67
+ run_{run_id}_memory_dependency.csv) applies to this controller with ZERO
68
+ additional code -- see the "elif pass_through_memory is False" branch in
69
+ forward() below.
70
+
71
+ --- API-compatibility notes (why this is written the way it is) ----------
72
+ This does NOT subclass dnc.DNC or MambaDNC -- the whole point of Option 5
73
+ is that the forward pass's control flow is fundamentally different (one
74
+ parallel backbone call, THEN a sequential loop, instead of one interleaved
75
+ step per timestep), so there's no meaningful DNC.forward() to defer to.
76
+ Instead this file reproduces, by hand, every piece of the surrounding
77
+ project's expected surface area that other files depend on:
78
+ - self.memories: a plain python list (NOT nn.ModuleList) holding one
79
+ dnc.memory.Memory, matching MambaDNC's exact convention, because
80
+ dynamic_memory_resize.resize_memory() does `model.memories[layer] =
81
+ new_memory` and link_matrix_ablation.patch_link_matrix() iterates
82
+ `model.memories` -- both assume this exact shape regardless of
83
+ controller class.
84
+ - setattr(self, "rnn_layer_memory_shared", self.memories[0]): the ONLY
85
+ reason the Memory instance is also a genuine, autograd-registered
86
+ submodule (needed for install_stochastic_write_heads()'s
87
+ named_modules() walk in stochastic_write_head_v2.py, and for
88
+ resize_memory()'s "find the attribute name pointing at old_memory"
89
+ swap in dynamic_memory_resize.py). Same convention MambaDNC uses.
90
+ - forward(input, hx=(chx, mhx, last_read), reset_experience,
91
+ pass_through_memory) -> (output, (chx, mhx, last_read)), with output
92
+ shaped (T, B, input_size) -- matching the (T, B, C) convention the
93
+ rest of this project's training/eval code already expects from
94
+ rnn(...)'s output (see the .transpose(0,1) calls immediately after
95
+ every rnn(...) call in Alter_PHASE3_mamba.py), and mhx wrapped as a
96
+ single-entry list (`mhx = [mem_state]`) so Dynamic-N's existing
97
+ `mem_hidden[0] if isinstance(mem_hidden, list) else mem_hidden` read
98
+ of hidden[1]["usage_vector"] keeps working unmodified.
99
+
100
+ ASSUMPTION FLAGGED FOR VERIFICATION: this file calls
101
+ `self.memories[0](xi_t, mem_state)` expecting it to return
102
+ `(read_vectors, updated_mem_state)`, inferred from this project's existing
103
+ usage of dnc.memory.Memory (`.reset()`, `write_vector_transform` swapped
104
+ inside install_stochastic_write_heads()) rather than from having the
105
+ literal pytorch-dnc source in hand. Run one batch through this module and
106
+ confirm read_vectors.shape == (B, read_heads*cell_size) before trusting a
107
+ full run -- if the installed library's Memory.forward returns the pair in
108
+ the opposite order, this is the one line to fix.
109
+ """
110
+
111
+ from __future__ import annotations
112
+
113
+ from typing import Any
114
+
115
+ import torch
116
+ import torch.nn as nn
117
+ from dnc.memory import Memory
118
+
119
+ from initium.LNN_controller.cfc_backbone_parallel import (
120
+ build_parallel_backbone,
121
+ collect_backbone_moe_layers,
122
+ )
123
+ from initium.LNN_controller.cfc_controller import CfCControllerWrapper
124
+ from initium.LNN_controller.hybrid_controller import ( # v15: hybrid combiner
125
+ build_hybrid_controller,
126
+ is_hybrid_rnn_type,
127
+ )
128
+ from initium.mamba_controller.mamba2_controller import Mamba2ControllerWrapper
129
+ from initium.mamba_controller.mamba3_controller import Mamba3ControllerWrapper
130
+ from initium.mamba_controller.mamba_controller import MambaControllerWrapper
131
+
132
+
133
+ class SplitGraphDNC(nn.Module):
134
+ """Alternate Phase 3, Step 2, Option 5 controller+memory assembly.
135
+ See module docstring for the full design rationale and the explicit
136
+ "not validated yet" framing.
137
+ """
138
+
139
+ def __init__(
140
+ self,
141
+ input_size: int,
142
+ hidden_size: int,
143
+ nr_cells: int = 256,
144
+ cell_size: int = 64,
145
+ read_heads: int = 4,
146
+ num_backbone_blocks: int = 2,
147
+ mamba_variant: str = "mamba1",
148
+ mamba_d_state: int = 16,
149
+ mamba_d_conv: int = 4,
150
+ mamba_expand: int = 2,
151
+ cfc_kwargs: dict
152
+ | None = None, # v14: CfC hyperparameters for variants containing "cfc" (None -> defaults)
153
+ mamba_headdim: int = 64, # mamba2-only
154
+ combine_reads: bool = True, # built-in ablation switch, see module docstring
155
+ # v11: combiner mechanism for the sequential addressing step. This is
156
+ # ORTHOGONAL to the backbone (mamba_variant above, still parallel,
157
+ # unchanged) -- it only changes how xi_t is produced from (h_t,
158
+ # read_{t-1}) inside the per-timestep loop.
159
+ # "linear" (default): the original plain nn.Linear combiner --
160
+ # stateless, one matmul per timestep. This is the config that
161
+ # produced the "marvelous efficiency" mamba1-backbone result;
162
+ # leaving this as default means every existing call site is
163
+ # byte-for-byte unaffected.
164
+ # "controller": xi_t is instead produced by a real interleaved
165
+ # controller cell (MambaControllerWrapper for
166
+ # combiner_variant="mamba1", Mamba2ControllerWrapper for
167
+ # "mamba2") carrying its own (conv_state, ssm_state) across
168
+ # the whole episode -- i.e. the sequential step gets genuine
169
+ # recurrent SSM capacity instead of a stateless projection,
170
+ # while the backbone stays exactly as parallel as before.
171
+ combiner_mode: str = "linear",
172
+ combiner_variant: str = "mamba1", # "mamba1" | "mamba2", only used when combiner_mode="controller"
173
+ combiner_num_blocks: int = 1, # kept small on purpose -- see module docstring's
174
+ # "much smaller and cheaper" framing of this step
175
+ combiner_d_state: int | None = None, # None -> variant's own paper default (16 / 64)
176
+ combiner_d_conv: int = 4,
177
+ combiner_expand: int = 2,
178
+ combiner_headdim: int = 64, # mamba2 combiner_variant only
179
+ combiner_ngroups: int = 1, # mamba2 combiner_variant only
180
+ independent_linears: bool = True,
181
+ device: torch.device | None = None,
182
+ moe_enabled: bool = False,
183
+ moe_num_experts: int = 8,
184
+ moe_expert_dim: int | None = None,
185
+ moe_top_k: int = 1,
186
+ moe_capacity_factor: float = 1.5,
187
+ moe_load_balance_alpha: float = 0.01,
188
+ # When combiner_variant == "cfc" (standalone, not part of a hybrid
189
+ # combiner chain) and moe_enabled, route the combiner's two natural
190
+ # inputs -- backbone output h_t and the previous read vector --
191
+ # through per-source specialized experts (see
192
+ # cfc_controller.CfCControllerWrapper's moe_source_dims) instead of
193
+ # a single pre-concatenated MoE pass. True is the recommended
194
+ # default per this project's own CfC-property requirement; set
195
+ # False to fall back to plain per-block MoE on the pre-concatenated
196
+ # input, for direct A/B comparison.
197
+ moe_cfc_multi_source: bool = True,
198
+ ):
199
+ super().__init__()
200
+ if not independent_linears:
201
+ raise ValueError(
202
+ "SplitGraphDNC requires independent_linears=True, same "
203
+ "requirement as install_stochastic_write_heads() (Phase 1) "
204
+ "-- it needs Memory to expose a standalone "
205
+ "write_vector_transform Linear to swap out."
206
+ )
207
+
208
+ self.input_size = input_size
209
+ self.hidden_size = hidden_size
210
+ self.output_size = hidden_size
211
+ self.nr_cells = nr_cells
212
+ self.cell_size = cell_size
213
+ self.read_heads = read_heads
214
+ self.w = cell_size
215
+ self.r = read_heads
216
+ self.read_vectors_size = read_heads * cell_size
217
+ self.nn_output_size = hidden_size + self.read_vectors_size
218
+ self.device = device
219
+ self.mamba_variant = mamba_variant
220
+ self.combine_reads = combine_reads
221
+
222
+ # Mamba bookkeeping, for checkpoint self-description -- same
223
+ # rationale/convention as MambaDNC (mamba_controller.py).
224
+ self.mamba_d_state = mamba_d_state
225
+ self.mamba_d_conv = mamba_d_conv
226
+ self.mamba_expand = mamba_expand
227
+ self.mamba_headdim = mamba_headdim
228
+
229
+ self.backbone = build_parallel_backbone(
230
+ in_dim=input_size,
231
+ d_model=hidden_size,
232
+ num_blocks=num_backbone_blocks,
233
+ variant=mamba_variant,
234
+ d_state=mamba_d_state,
235
+ d_conv=mamba_d_conv,
236
+ expand=mamba_expand,
237
+ headdim=mamba_headdim,
238
+ cfc_kwargs=cfc_kwargs,
239
+ moe_enabled=moe_enabled,
240
+ moe_num_experts=moe_num_experts,
241
+ moe_expert_dim=moe_expert_dim,
242
+ moe_top_k=moe_top_k,
243
+ moe_capacity_factor=moe_capacity_factor,
244
+ moe_load_balance_alpha=moe_load_balance_alpha,
245
+ device=device,
246
+ )
247
+
248
+ if combiner_mode not in ("linear", "controller"):
249
+ raise ValueError(
250
+ f"SplitGraphDNC: unknown combiner_mode {combiner_mode!r}, "
251
+ "expected 'linear' or 'controller'."
252
+ )
253
+ self.combiner_mode = combiner_mode
254
+ self.combiner_variant = combiner_variant
255
+ self.combiner = None
256
+ # These wrappers share the runtime interface but have unrelated
257
+ # concrete classes (and the hybrid wrapper is composed dynamically).
258
+ # Keep the heterogeneous dispatch boundary dynamic; type their
259
+ # component classes independently at construction sites.
260
+ self.combiner_wrapper: Any = None
261
+
262
+ if combiner_mode == "linear":
263
+ if combine_reads:
264
+ self.combiner = nn.Linear(
265
+ hidden_size + self.read_vectors_size, hidden_size, device=device
266
+ )
267
+ # Zero-init: at step 0 of training, xi_t == h_t exactly (the
268
+ # split model starts by ignoring the previous read vector
269
+ # entirely and has to learn to use it). Same "start equal to
270
+ # the simpler/prior baseline" philosophy already used
271
+ # elsewhere in this project (StochasticWriteHead's zero-init
272
+ # logvar head, mu_transform copied from the original Linear --
273
+ # stochastic_write_head_v2.py).
274
+ nn.init.zeros_(self.combiner.weight)
275
+ nn.init.zeros_(self.combiner.bias)
276
+ else: # combiner_mode == "controller"
277
+ # v11: real interleaved cell as the combiner. combine_reads is
278
+ # implicitly True here -- a controller combiner is read-
279
+ # dependent by construction (its whole input is [h_t, read_{t-1}]
280
+ # every step), so there is no meaningful "ablated" variant of
281
+ # this mode the way the Linear combiner has combine_reads=False.
282
+ # Use combiner_mode="linear", combine_reads=False for that check
283
+ # instead (unchanged, still available).
284
+ if combiner_variant == "mamba2":
285
+ print(
286
+ "[SplitGraphDNC] WARNING: combiner_mode='controller' with "
287
+ "combiner_variant='mamba2' constructs a genuine per-timestep "
288
+ "interleaved Mamba-2 cell for the addressing step. This is "
289
+ "known to be inefficient (same reason standalone "
290
+ "--controller mamba2 runs aren't being pursued) and is wired "
291
+ "here only for completeness/future comparison -- "
292
+ "combiner_variant='mamba1' is the configuration expected to "
293
+ "actually be used."
294
+ )
295
+ _d_state = (
296
+ combiner_d_state
297
+ if combiner_d_state is not None
298
+ else (64 if combiner_variant in ("mamba2", "mamba3") else 16)
299
+ )
300
+ combiner_in_dim = hidden_size + self.read_vectors_size
301
+ if combiner_variant == "mamba2":
302
+ self.combiner_wrapper = Mamba2ControllerWrapper(
303
+ in_dim=combiner_in_dim,
304
+ d_model=hidden_size,
305
+ num_blocks=combiner_num_blocks,
306
+ d_state=_d_state,
307
+ d_conv=combiner_d_conv,
308
+ expand=combiner_expand,
309
+ headdim=combiner_headdim,
310
+ ngroups=combiner_ngroups,
311
+ moe_enabled=moe_enabled,
312
+ moe_num_experts=moe_num_experts,
313
+ moe_expert_dim=moe_expert_dim,
314
+ moe_top_k=moe_top_k,
315
+ moe_capacity_factor=moe_capacity_factor,
316
+ moe_load_balance_alpha=moe_load_balance_alpha,
317
+ device=device,
318
+ )
319
+ elif combiner_variant == "mamba3":
320
+ self.combiner_wrapper = Mamba3ControllerWrapper(
321
+ in_dim=combiner_in_dim,
322
+ d_model=hidden_size,
323
+ num_blocks=combiner_num_blocks,
324
+ d_state=_d_state,
325
+ expand=combiner_expand,
326
+ headdim=combiner_headdim,
327
+ moe_enabled=moe_enabled,
328
+ moe_num_experts=moe_num_experts,
329
+ moe_expert_dim=moe_expert_dim,
330
+ moe_top_k=moe_top_k,
331
+ moe_capacity_factor=moe_capacity_factor,
332
+ moe_load_balance_alpha=moe_load_balance_alpha,
333
+ device=device,
334
+ )
335
+ elif is_hybrid_rnn_type(
336
+ combiner_variant
337
+ ): # v15: e.g. "mamba+cfc" (hybrid_controller kind names: mamba, not mamba1)
338
+ self.combiner_wrapper = build_hybrid_controller(
339
+ combiner_variant.lower(),
340
+ in_dim=combiner_in_dim,
341
+ d_model=hidden_size,
342
+ blocks_per_kind={
343
+ k: combiner_num_blocks for k in ("mamba", "mamba2", "mamba3", "cfc")
344
+ },
345
+ kwargs_per_kind={
346
+ "mamba": dict(
347
+ d_state=16,
348
+ d_conv=combiner_d_conv,
349
+ expand=combiner_expand,
350
+ moe_enabled=moe_enabled,
351
+ moe_num_experts=moe_num_experts,
352
+ moe_expert_dim=moe_expert_dim,
353
+ moe_top_k=moe_top_k,
354
+ moe_capacity_factor=moe_capacity_factor,
355
+ moe_load_balance_alpha=moe_load_balance_alpha,
356
+ ),
357
+ "mamba2": dict(
358
+ d_state=64,
359
+ d_conv=combiner_d_conv,
360
+ expand=combiner_expand,
361
+ headdim=combiner_headdim,
362
+ ngroups=combiner_ngroups,
363
+ moe_enabled=moe_enabled,
364
+ moe_num_experts=moe_num_experts,
365
+ moe_expert_dim=moe_expert_dim,
366
+ moe_top_k=moe_top_k,
367
+ moe_capacity_factor=moe_capacity_factor,
368
+ moe_load_balance_alpha=moe_load_balance_alpha,
369
+ ),
370
+ "mamba3": dict(
371
+ d_state=64,
372
+ expand=combiner_expand,
373
+ headdim=combiner_headdim,
374
+ moe_enabled=moe_enabled,
375
+ moe_num_experts=moe_num_experts,
376
+ moe_expert_dim=moe_expert_dim,
377
+ moe_top_k=moe_top_k,
378
+ moe_capacity_factor=moe_capacity_factor,
379
+ moe_load_balance_alpha=moe_load_balance_alpha,
380
+ ),
381
+ "cfc": dict(cfc_kwargs or {}),
382
+ },
383
+ device=device,
384
+ )
385
+ elif combiner_variant == "cfc":
386
+ self.combiner_wrapper = CfCControllerWrapper(
387
+ in_dim=combiner_in_dim,
388
+ d_model=hidden_size,
389
+ num_blocks=combiner_num_blocks,
390
+ moe_enabled=moe_enabled,
391
+ moe_num_experts=moe_num_experts,
392
+ moe_expert_dim=moe_expert_dim,
393
+ moe_top_k=moe_top_k,
394
+ moe_capacity_factor=moe_capacity_factor,
395
+ moe_load_balance_alpha=moe_load_balance_alpha,
396
+ # Preserve CfC's "distinct input sources" property: the
397
+ # combiner's two natural sources are backbone output
398
+ # (hidden_size) and the previous read vector
399
+ # (read_vectors_size), routed by a shared source-aware
400
+ # Top-K expert bank rather than a single flattened
401
+ # concat -- see moe_layer.MultiSourceMoEBlock.
402
+ moe_source_dims=(
403
+ [hidden_size, self.read_vectors_size]
404
+ if (moe_enabled and moe_cfc_multi_source)
405
+ else None
406
+ ),
407
+ device=device,
408
+ )
409
+ elif combiner_variant == "mamba1":
410
+ self.combiner_wrapper = MambaControllerWrapper(
411
+ in_dim=combiner_in_dim,
412
+ d_model=hidden_size,
413
+ num_blocks=combiner_num_blocks,
414
+ d_state=_d_state,
415
+ d_conv=combiner_d_conv,
416
+ expand=combiner_expand,
417
+ moe_enabled=moe_enabled,
418
+ moe_num_experts=moe_num_experts,
419
+ moe_expert_dim=moe_expert_dim,
420
+ moe_top_k=moe_top_k,
421
+ moe_capacity_factor=moe_capacity_factor,
422
+ moe_load_balance_alpha=moe_load_balance_alpha,
423
+ device=device,
424
+ )
425
+ else:
426
+ raise ValueError(
427
+ f"SplitGraphDNC: unknown combiner_variant {combiner_variant!r}, "
428
+ "expected 'mamba1' or 'mamba2'."
429
+ )
430
+
431
+ _ad = (
432
+ self.combiner_wrapper.in_adapter
433
+ ) # read-vector columns start at 0, like the zero-init linear combiner
434
+ if isinstance(_ad, nn.Linear):
435
+ with torch.no_grad():
436
+ _ad.weight[:, hidden_size:].zero_()
437
+
438
+ self.memories = []
439
+ self.memories.append(
440
+ Memory(
441
+ input_size=self.output_size,
442
+ nr_cells=self.nr_cells,
443
+ cell_size=self.w,
444
+ read_heads=self.r,
445
+ device=self.device,
446
+ independent_linears=independent_linears,
447
+ )
448
+ )
449
+ # See module docstring's API-compatibility section for why this
450
+ # setattr (not just the list append above) is required.
451
+ self.rnn_layer_memory_shared = self.memories[0]
452
+
453
+ self.output = nn.Linear(self.nn_output_size, self.input_size, device=device)
454
+ torch.nn.init.kaiming_uniform_(self.output.weight)
455
+
456
+ # Option 4 (MoE) interoperability: kept OFF and deliberately NOT
457
+ # wired into the backbone blocks here (unlike mamba_controller.py's
458
+ # MambaControllerWrapper) -- Concept 16/SP-10 (non-additive
459
+ # combination) is why Option 5 is being built and ablated on its
460
+ # own first. moe_layers stays an empty list so
461
+ # pop_total_moe_aux_loss(...) (moe_layer.py) is still safe to call
462
+ # unconditionally from the training script regardless of which
463
+ # controller is active this run.
464
+ self.moe_enabled = moe_enabled
465
+ self.moe_cfc_multi_source = moe_cfc_multi_source
466
+ # Flat list every pop_total_moe_aux_loss() call sums over: the
467
+ # backbone's own per-block MoE sublayers, PLUS the combiner's (a
468
+ # single MultiSourceMoEBlock for a standalone cfc combiner, a
469
+ # ChainedControllerWrapper's own aggregated moe_blocks for a hybrid
470
+ # combiner, or a per-block list for any other controller combiner).
471
+ self.moe_layers = collect_backbone_moe_layers(self.backbone)
472
+ if self.combiner_wrapper is not None and getattr(
473
+ self.combiner_wrapper, "moe_enabled", False
474
+ ):
475
+ assert self.combiner_wrapper.moe_blocks is not None
476
+ self.moe_layers.extend(list(self.combiner_wrapper.moe_blocks))
477
+
478
+ if self.device is not None and getattr(self.device, "type", None) == "cuda":
479
+ self.to(self.device)
480
+
481
+ def forward(
482
+ self,
483
+ input: torch.Tensor,
484
+ hx=(None, None, None),
485
+ reset_experience: bool = False,
486
+ pass_through_memory: bool = True,
487
+ combiner_skip_stages=None,
488
+ start_step: int = 0, # v20: resume the sequential loop mid-sequence (inference caching)
489
+ ):
490
+ """
491
+ input: (B, T, input_size) -- batch-first, the whole padded episode
492
+ at once (this project always calls rnn(input_seq, ...) with the
493
+ full sequence already assembled -- see Alter_PHASE3_mamba.py's
494
+ training loop and evaluate_traversal()).
495
+ hx: (chx, mhx, last_read). chx is unused by this controller (the
496
+ combiner has no recurrent state of its own -- it's a plain
497
+ per-timestep Linear, not a cell) and is passed through
498
+ untouched. mhx follows dnc.DNC's own convention: None on a
499
+ fresh call, or a single-entry list `[mem_state_dict]` on a
500
+ resumed/continuing call.
501
+ pass_through_memory: when False, skips Memory read AND write for
502
+ EVERY timestep and substitutes zero read-vectors -- this is
503
+ the functional-usage ablation check
504
+ (evaluate_traversal(..., ablate_memory=True)) already wired
505
+ into this project's training loop; see module docstring.
506
+
507
+ Returns (output, (chx, mhx, last_read)) with output shaped
508
+ (T, B, input_size) -- see module docstring for why this shape,
509
+ not (B, T, input_size).
510
+ """
511
+ chx, mhx, last_read = hx
512
+ B, T, _ = input.shape
513
+ if start_step:
514
+ # Backbone still runs over the FULL input (cheap, parallel, stateless across
515
+ # calls); only the sequential memory loop is skipped for t < start_step.
516
+ # hx must then carry the state as of step `start_step`.
517
+ if not (0 < start_step < T):
518
+ raise ValueError(f"start_step={start_step} must be in (0, T={T})")
519
+ if mhx is None:
520
+ raise ValueError("start_step>0 requires a resumed hx=(chx, mhx, last_read)")
521
+ device = input.device
522
+
523
+ # ---- (a) PARALLEL BACKBONE ------------------------------------
524
+ # Single call, whole sequence, zero dependency on memory state.
525
+ # This is the entire "removed from the sequential critical path"
526
+ # half of Option 5.
527
+ H = self.backbone(input) # (B, T, hidden_size)
528
+
529
+ # ---- memory hidden-state init (byte-identical convention to
530
+ # dnc.DNC._init_hidden / MambaDNC._init_hidden's memory branch) ---
531
+ if mhx is None:
532
+ mhx = [self.memories[0].reset(B, erase=reset_experience)]
533
+ else:
534
+ if len(mhx) == 0 or mhx[0] is None:
535
+ mhx = [self.memories[0].reset(B, erase=reset_experience)]
536
+ else:
537
+ mhx = [self.memories[0].reset(B, mhx[0], erase=reset_experience)]
538
+ mem_state = mhx[0]
539
+
540
+ if last_read is None:
541
+ read_vec = torch.zeros(B, self.w * self.r, device=device, dtype=H.dtype)
542
+ else:
543
+ read_vec = last_read
544
+
545
+ # ---- (b) SEQUENTIAL ADDRESSING STEP ----------------------------
546
+ # The only sequential part left: a cheap per-timestep combine
547
+ # (h_t, read_{t-1}) -> xi_t, followed by the existing
548
+ # Memory.forward() call (already-sequential, already-cheap
549
+ # relative to a full backbone block -- this project never made
550
+ # Memory itself parallel, it only ever removed the BACKBONE from
551
+ # the sequential critical path). Preserves the "read now, decide
552
+ # next hop immediately" loop exactly, per-step.
553
+ # v11: local per-episode state for the controller combiner, if
554
+ # active -- analogous to mem_state above, NOT threaded through chx
555
+ # (chx stays "unused, pass through untouched" per this class's
556
+ # existing convention, since every call site in this project
557
+ # constructs fresh episodes with reset_experience=True). If chx was
558
+ # given (e.g. a future caller that does want continuity), reuse it
559
+ # as the starting state instead of a fresh zero-init.
560
+ combiner_hx = None
561
+ if self.combiner_mode == "controller":
562
+ assert self.combiner_wrapper is not None
563
+ combiner_hx = (
564
+ chx
565
+ if chx is not None
566
+ else self.combiner_wrapper.init_state(B, device=device, dtype=H.dtype)
567
+ )
568
+
569
+ outputs = []
570
+ for t in range(start_step, T):
571
+ h_t = H[:, t, :] # (B, hidden_size)
572
+
573
+ if self.combiner_mode == "controller":
574
+ assert self.combiner_wrapper is not None
575
+ _cfc_multi_source = (
576
+ self.combiner_variant == "cfc"
577
+ and getattr(self.combiner_wrapper, "moe_source_dims", None) is not None
578
+ )
579
+ if _cfc_multi_source:
580
+ xi_out, combiner_hx = self.combiner_wrapper.forward_multi_source(
581
+ [h_t, read_vec], combiner_hx
582
+ )
583
+ else:
584
+ combiner_in = torch.cat([h_t, read_vec], dim=-1).unsqueeze(
585
+ 1
586
+ ) # (B, 1, hidden+read)
587
+ if combiner_skip_stages is not None:
588
+ xi_out, combiner_hx = self.combiner_wrapper(
589
+ combiner_in, combiner_hx, skip_stages=combiner_skip_stages
590
+ )
591
+ else:
592
+ xi_out, combiner_hx = self.combiner_wrapper(combiner_in, combiner_hx)
593
+ xi_t = h_t + xi_out.squeeze(1)
594
+ elif self.combine_reads:
595
+ assert self.combiner is not None
596
+ xi_t = h_t + self.combiner(torch.cat([h_t, read_vec], dim=-1))
597
+ else:
598
+ # Built-in ablation (see module docstring): addressing
599
+ # never sees any read vector at all.
600
+ xi_t = h_t
601
+
602
+ if pass_through_memory:
603
+ read_vec, mem_state = self.memories[0](xi_t, mem_state)
604
+ read_vec = read_vec.reshape(
605
+ B, -1
606
+ ) # dnc.memory.Memory returns (B, r, w) unflattened
607
+ else:
608
+ # LB-9/Concept 6 functional-usage check: skip memory read
609
+ # AND write for this timestep, matching every other
610
+ # controller's ablate_memory contract in this project.
611
+ read_vec = torch.zeros(B, self.w * self.r, device=device, dtype=H.dtype)
612
+
613
+ step_out = self.output(torch.cat([xi_t, read_vec], dim=-1)) # (B, input_size)
614
+ outputs.append(step_out)
615
+
616
+ if self.combiner_mode == "controller":
617
+ chx = combiner_hx # v11: expose the controller combiner's final state, same
618
+ # convention as mem_state -> mhx just above
619
+
620
+ mhx = [mem_state]
621
+ output = torch.stack(outputs, dim=0) # (T, B, input_size)
622
+
623
+ return output, (chx, mhx, read_vec)