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.
- initium/LNN_controller/cfc_backbone_parallel.py +265 -0
- initium/LNN_controller/cfc_controller.py +325 -0
- initium/LNN_controller/chained_controller.py +83 -0
- initium/LNN_controller/hybrid_controller.py +90 -0
- initium/MoE/README.md +1 -0
- initium/MoE/moe_layer.py +552 -0
- initium/__init__.py +1 -0
- initium/config/controller_config.py +86 -0
- initium/config/train_config.py +64 -0
- initium/core_training.py +2182 -0
- initium/data/__init__.py +0 -0
- initium/data/audio/__init__.py +0 -0
- initium/data/audio/audio_dataset.py +96 -0
- initium/data/base_dataset.py +75 -0
- initium/data/common/__init__.py +0 -0
- initium/data/common/chain_task.py +542 -0
- initium/data/common/digit_codec.py +85 -0
- initium/data/common/graph_io.py +123 -0
- initium/data/common/real_data.py +747 -0
- initium/data/dataset_registry.py +59 -0
- initium/data/graph_traversal/__init__.py +0 -0
- initium/data/graph_traversal/graph_traversal.py +746 -0
- initium/data/multimodal/__init__.py +0 -0
- initium/data/multimodal/multimodal_dataset.py +401 -0
- initium/data/text/__init__.py +0 -0
- initium/data/text/text_dataset.py +79 -0
- initium/data/video/__init__.py +0 -0
- initium/data/video/video_dataset.py +85 -0
- initium/inference/__init__.py +1 -0
- initium/inference/cache/__init__.py +1 -0
- initium/inference/cache/base_cache.py +112 -0
- initium/inference/cache/cache_config.py +21 -0
- initium/inference/cache/cache_registry.py +99 -0
- initium/inference/cache/cached_engine.py +264 -0
- initium/inference/cache/keys.py +77 -0
- initium/inference/cache/prefix_cache.py +59 -0
- initium/inference/cache/result_cache.py +57 -0
- initium/inference/cache/state_utils.py +74 -0
- initium/inference/cache/stores.py +167 -0
- initium/inference/capabilities.py +86 -0
- initium/inference/checkpoint_io.py +63 -0
- initium/inference/engine.py +88 -0
- initium/inference/inference_config.py +22 -0
- initium/inference/metrics.py +103 -0
- initium/inference/model_loader.py +270 -0
- initium/inference/run_inference.py +526 -0
- initium/inference/run_logging.py +173 -0
- initium/inference/tasks/__init__.py +1 -0
- initium/inference/tasks/audio_task.py +114 -0
- initium/inference/tasks/base_task.py +60 -0
- initium/inference/tasks/graph_traversal_task.py +235 -0
- initium/inference/tasks/multimodal_task.py +74 -0
- initium/inference/tasks/task_registry.py +68 -0
- initium/inference/tasks/text_task.py +114 -0
- initium/inference/tasks/video_task.py +114 -0
- initium/mamba_controller/mamba2_controller.py +433 -0
- initium/mamba_controller/mamba3_controller.py +242 -0
- initium/mamba_controller/mamba_backbone_parallel.py +291 -0
- initium/mamba_controller/mamba_controller.py +983 -0
- initium/mamba_controller/split_graph_dnc.py +623 -0
- initium/memory_manipulation/dynamic_memory_resize.py +157 -0
- initium/memory_manipulation/dynamic_n_controller.py +152 -0
- initium/memory_manipulation/link_matrix_ablation.py +130 -0
- initium/memory_manipulation/nvrtc_compat.py +143 -0
- initium/memory_manipulation/stochastic_write_head_v2.py +545 -0
- initium/setup_wheels.py +175 -0
- initium_deeplearning-0.1.0.dist-info/METADATA +253 -0
- initium_deeplearning-0.1.0.dist-info/RECORD +71 -0
- initium_deeplearning-0.1.0.dist-info/WHEEL +4 -0
- initium_deeplearning-0.1.0.dist-info/entry_points.txt +3 -0
- 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
|