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,983 @@
|
|
|
1
|
+
"""
|
|
2
|
+
mamba_controller.py -- v1
|
|
3
|
+
|
|
4
|
+
Alternate Phase 3, Step 1 (see Experiment-Roadmap.md, "Alternative Phase 3 -
|
|
5
|
+
fits better to the programs architecture"): wire a Mamba-1 (selective SSM)
|
|
6
|
+
controller into the pytorch-dnc `DNC` in place of the LSTM controller used
|
|
7
|
+
by Phase 2 (`PHASE_2_graph_traversal_KL_prior.py`), while leaving memory
|
|
8
|
+
addressing (content lookup, allocation, temporal link matrix, read modes)
|
|
9
|
+
and the Phase 1/2 stochastic-write-head KL machinery completely untouched.
|
|
10
|
+
|
|
11
|
+
Library used: `mamba-ssm` (per the roadmap's explicit instruction, Mamba-1
|
|
12
|
+
is tried first, not Mamba-2/S4, because it's the simplest option). This
|
|
13
|
+
file reuses `mamba_ssm.modules.mamba_simple.Mamba` for its parameters and
|
|
14
|
+
initialization (in_proj, conv1d, x_proj, dt_proj, A_log, D, out_proj -- the
|
|
15
|
+
exact S6 parameterization from the Mamba paper, Algorithm 2 / Section 3.6),
|
|
16
|
+
but does NOT use that class's own `.forward()` or `.step()` methods. Why,
|
|
17
|
+
and what this file does instead, is the one piece of non-obvious design in
|
|
18
|
+
here -- see "Why not just call Mamba.step()?" below before editing the
|
|
19
|
+
step() math in MambaControllerCell.
|
|
20
|
+
|
|
21
|
+
--------------------------------------------------------------------------
|
|
22
|
+
Why a separate file (as requested)
|
|
23
|
+
--------------------------------------------------------------------------
|
|
24
|
+
pytorch-dnc's `DNC.__init__` builds `self.rnns[layer]` as a `nn.LSTM` /
|
|
25
|
+
`nn.GRU` / `nn.RNN` directly inline (see `dnc/dnc.py` in
|
|
26
|
+
https://github.com/ixaxaar/pytorch-dnc), and `DNC._init_hidden` has an
|
|
27
|
+
LSTM-specific branch for constructing the initial controller hidden state.
|
|
28
|
+
Neither `rnn_type='mamba'` nor a `nn.Module` with a fundamentally different
|
|
29
|
+
hidden-state shape (Mamba needs a `(conv_state, ssm_state)` pair per block,
|
|
30
|
+
not an `(h, c)` LSTM pair) is supported by the stock class. Rather than
|
|
31
|
+
monkey-patching or forking pytorch-dnc, this file defines:
|
|
32
|
+
|
|
33
|
+
1. `MambaControllerCell` -- one Mamba-1 block, single-timestep, BPTT-safe.
|
|
34
|
+
2. `MambaControllerBlock` -- pre-norm residual wrapper around (1), mirroring
|
|
35
|
+
mamba_ssm's own `Block` (Add -> LN -> Mixer).
|
|
36
|
+
3. `MambaControllerWrapper`-- stacks N `MambaControllerBlock`s and exposes
|
|
37
|
+
the exact calling convention pytorch-dnc's
|
|
38
|
+
`DNC._layer_forward` expects from
|
|
39
|
+
`self.rnns[layer]`: callable as
|
|
40
|
+
`module(x_unsqueezed_at_dim1, hx) -> (out_unsqueezed, new_hx)`,
|
|
41
|
+
the same shape contract as `nn.LSTM(...,
|
|
42
|
+
batch_first=True)` called one timestep at a
|
|
43
|
+
time (`x.unsqueeze(1)`).
|
|
44
|
+
4. `MambaDNC` -- subclasses `dnc.DNC`. For
|
|
45
|
+
`rnn_type != 'mamba'` it defers to the stock
|
|
46
|
+
`DNC.__init__`/`_init_hidden` untouched (so
|
|
47
|
+
this class is a strict drop-in superset --
|
|
48
|
+
`rnn_type='lstm'` behaves byte-identically
|
|
49
|
+
to plain `dnc.DNC`, which is what makes an
|
|
50
|
+
LSTM-vs-Mamba A/B comparison a one-line
|
|
51
|
+
change in the training script). For
|
|
52
|
+
`rnn_type == 'mamba'` it builds
|
|
53
|
+
`self.rnns[layer]` as a `MambaControllerWrapper`
|
|
54
|
+
instead, and overrides `_init_hidden`'s
|
|
55
|
+
controller-state branch to build
|
|
56
|
+
`(conv_state, ssm_state)` pairs instead of
|
|
57
|
+
`(h, c)`. Memory construction
|
|
58
|
+
(`dnc.memory.Memory`), the output projection,
|
|
59
|
+
and `forward()`/`_layer_forward()` are all
|
|
60
|
+
inherited from `dnc.DNC` UNCHANGED -- this is
|
|
61
|
+
what guarantees addressing stays untouched
|
|
62
|
+
and that `install_stochastic_write_heads()`
|
|
63
|
+
from `stochastic_write_head_v2.py` keeps
|
|
64
|
+
working with zero modification (it only ever
|
|
65
|
+
looks for `dnc.memory.Memory` submodules and
|
|
66
|
+
patches `write_vector_transform`; it never
|
|
67
|
+
looks at the controller).
|
|
68
|
+
|
|
69
|
+
--------------------------------------------------------------------------
|
|
70
|
+
Why not just call Mamba.step()? (the one thing worth reading carefully)
|
|
71
|
+
--------------------------------------------------------------------------
|
|
72
|
+
`mamba_ssm.modules.mamba_simple.Mamba` ships two ways to run the model:
|
|
73
|
+
|
|
74
|
+
- `.forward(hidden_states)`: takes the WHOLE sequence at once (B, L, D)
|
|
75
|
+
and is only fast because it uses `causal_conv1d_fn` / `selective_scan_fn`
|
|
76
|
+
CUDA kernels (or an FFT-free full-sequence scan) that assume every
|
|
77
|
+
timestep's input is already available. This doesn't fit here: DNC has
|
|
78
|
+
to interleave one controller step with one memory read/write at a time
|
|
79
|
+
(the read vector from step t feeds the controller's input at step t+1),
|
|
80
|
+
so the whole point of wiring Mamba in as *DNC's controller* is defeated
|
|
81
|
+
if we can't get the model one token at a time.
|
|
82
|
+
|
|
83
|
+
- `.step(hidden_states, conv_state, ssm_state)`: the autoregressive
|
|
84
|
+
decoding path, exactly one token at a time -- this is the right
|
|
85
|
+
granularity. BUT it is written for inference, and mutates its own state
|
|
86
|
+
tensors IN PLACE via `conv_state.copy_(...)` / `ssm_state.copy_(...)`
|
|
87
|
+
(see mamba_ssm/modules/mamba_simple.py). That's fine under
|
|
88
|
+
`torch.no_grad()` for text generation, but it is NOT safe for
|
|
89
|
+
backprop-through-time: the in-place `.copy_()` overwrites the exact
|
|
90
|
+
tensor version autograd needs to compute the backward pass through the
|
|
91
|
+
*previous* timestep's `ssm_state * dA` multiply. Concretely, PyTorch
|
|
92
|
+
raises:
|
|
93
|
+
|
|
94
|
+
RuntimeError: one of the variables needed for gradient computation
|
|
95
|
+
has been modified by an inplace operation ...
|
|
96
|
+
|
|
97
|
+
the moment `.backward()` is called on a loss that was accumulated over
|
|
98
|
+
more than one `.step()` call chained through the same state tensors --
|
|
99
|
+
which is exactly what a T-step DNC unroll does. (Verified directly: a
|
|
100
|
+
minimal repro of this exact `state.copy_(new_state)`-across-a-python-
|
|
101
|
+
loop pattern throws this RuntimeError on `.backward()`, while the
|
|
102
|
+
identical recurrence written out-of-place -- `state = state * dA + ...`,
|
|
103
|
+
no `.copy_()` -- backpropagates cleanly through an arbitrary number of
|
|
104
|
+
chained steps.)
|
|
105
|
+
|
|
106
|
+
So: `MambaControllerCell.step()` below reimplements the SAME math as
|
|
107
|
+
`Mamba.step()`'s non-fast-path branch (the branch it falls back to when
|
|
108
|
+
`causal_conv1d_update`/`selective_state_update` aren't available -- i.e.
|
|
109
|
+
the portable, dependency-light path, matching the roadmap's "Mamba-1
|
|
110
|
+
first because it's simplest"), line-for-line equivalent, EXCEPT every
|
|
111
|
+
in-place `.copy_()` is replaced with a plain out-of-place tensor
|
|
112
|
+
expression that returns a NEW state tensor each call, exactly the same
|
|
113
|
+
pattern `nn.LSTM` already uses (a step returns new `(h, c)`, it doesn't
|
|
114
|
+
mutate the old `(h, c)` in place). This is what makes it safe to chain
|
|
115
|
+
inside pytorch-dnc's own per-timestep Python loop under full BPTT.
|
|
116
|
+
|
|
117
|
+
We still reuse the actual `mamba_ssm.modules.mamba_simple.Mamba` class
|
|
118
|
+
for parameter construction (`in_proj`, `conv1d`, `x_proj`, `dt_proj`,
|
|
119
|
+
`A_log`, `D`, `out_proj`) and all of its initialization logic (S4D-real
|
|
120
|
+
`A_log` init, the dt-bias inverse-softplus init, etc.) -- we only bypass
|
|
121
|
+
its `forward`/`step` methods, never its `__init__`.
|
|
122
|
+
|
|
123
|
+
--------------------------------------------------------------------------
|
|
124
|
+
Design choices specific to this wiring (Step 1 scope)
|
|
125
|
+
--------------------------------------------------------------------------
|
|
126
|
+
- `d_model` for every Mamba block is fixed to DNC's `hidden_size`
|
|
127
|
+
(`output_size` in pytorch-dnc's naming), since the controller's output
|
|
128
|
+
(post-clip) is fed directly to `Memory.forward()` as the interface vector
|
|
129
|
+
`ξ`, which must have exactly that width. A small `nn.Linear` "in_adapter"
|
|
130
|
+
handles the one dimension mismatch pytorch-dnc's own LSTM absorbs
|
|
131
|
+
implicitly in its first-layer weight matrix: `nn_input_size` (=
|
|
132
|
+
`input_size + read_vectors_size`) -> `hidden_size`, applied once before
|
|
133
|
+
the Mamba block stack. Every subsequent stacked block already operates at
|
|
134
|
+
`hidden_size`, so no further adapter is needed (identical in spirit to
|
|
135
|
+
how `nn.LSTM(num_layers=N)`'s layer-0 weight differs in shape from
|
|
136
|
+
layers 1..N-1).
|
|
137
|
+
- `num_hidden_layers` (pytorch-dnc's existing knob, previously "how many
|
|
138
|
+
stacked LSTM layers form this DNC layer's controller", default 2) is
|
|
139
|
+
reused, unchanged in meaning, as "how many stacked Mamba blocks form this
|
|
140
|
+
DNC layer's controller" -- so Phase 2's existing model-capacity constants
|
|
141
|
+
don't need a new knob to keep meaning "the controller has some depth".
|
|
142
|
+
- `mamba_d_state` / `mamba_d_conv` / `mamba_expand` are new, Mamba-specific
|
|
143
|
+
hyperparameters (defaults 16 / 4 / 2, the Mamba-1 paper's own defaults),
|
|
144
|
+
exposed as constructor kwargs on `MambaDNC` with no analogue in the LSTM
|
|
145
|
+
path -- there's nothing to keep in sync with, since LSTM has no such
|
|
146
|
+
parameters.
|
|
147
|
+
- Initial `(conv_state, ssm_state)` are zero-initialized (matching
|
|
148
|
+
`Mamba.allocate_inference_cache`'s own convention), NOT
|
|
149
|
+
`xavier_uniform_`-initialized the way pytorch-dnc seeds its LSTM `h0`.
|
|
150
|
+
This is a deliberate divergence, not an oversight: `xavier_uniform_` on a
|
|
151
|
+
conv/ssm state tensor has no principled meaning in this parameterization
|
|
152
|
+
(Mamba's own reference implementation always starts both at exactly
|
|
153
|
+
zero), so keeping the library's own convention here is more faithful
|
|
154
|
+
than forcing LSTM's init convention onto a structurally different state.
|
|
155
|
+
- Everything here targets Mamba-1 (`mamba_ssm.modules.mamba_simple.Mamba`)
|
|
156
|
+
specifically, not Mamba-2/S4, per the roadmap's explicit sequencing.
|
|
157
|
+
"""
|
|
158
|
+
|
|
159
|
+
from __future__ import annotations
|
|
160
|
+
|
|
161
|
+
from collections.abc import Iterable
|
|
162
|
+
from typing import Any, cast
|
|
163
|
+
|
|
164
|
+
import torch
|
|
165
|
+
import torch.nn as nn
|
|
166
|
+
import torch.nn.functional as F
|
|
167
|
+
from dnc import DNC
|
|
168
|
+
from dnc.memory import Memory
|
|
169
|
+
from dnc.util import cuda
|
|
170
|
+
|
|
171
|
+
from initium.MoE.moe_layer import MoEBlock, MoERNNWrapper
|
|
172
|
+
|
|
173
|
+
_MAMBA_IMPORT_ERROR: str | None
|
|
174
|
+
|
|
175
|
+
try:
|
|
176
|
+
from mamba_ssm.modules.mamba_simple import Mamba
|
|
177
|
+
except ImportError as _mamba_import_error: # pragma: no cover - environment-dependent
|
|
178
|
+
Mamba = None
|
|
179
|
+
_MAMBA_IMPORT_ERROR = (
|
|
180
|
+
"mamba_controller.py requires the `mamba-ssm` package "
|
|
181
|
+
"(`pip install mamba-ssm`, which needs a CUDA build toolchain / "
|
|
182
|
+
"nvcc available at install time -- see mamba-ssm's own install "
|
|
183
|
+
"instructions). Original import error: "
|
|
184
|
+
f"{_mamba_import_error}"
|
|
185
|
+
)
|
|
186
|
+
else:
|
|
187
|
+
_MAMBA_IMPORT_ERROR = None
|
|
188
|
+
|
|
189
|
+
|
|
190
|
+
def _require_mamba_ssm() -> None:
|
|
191
|
+
if Mamba is None:
|
|
192
|
+
raise ImportError(_MAMBA_IMPORT_ERROR)
|
|
193
|
+
|
|
194
|
+
|
|
195
|
+
from initium.LNN_controller.cfc_controller import ( # v13 (CfC / LNN controller)
|
|
196
|
+
CfCControllerWrapper,
|
|
197
|
+
_require_ncps,
|
|
198
|
+
)
|
|
199
|
+
from initium.LNN_controller.hybrid_controller import ( # v14 (Mamba -> CfC chains)
|
|
200
|
+
build_hybrid_controller,
|
|
201
|
+
is_hybrid_rnn_type,
|
|
202
|
+
)
|
|
203
|
+
from initium.mamba_controller.mamba2_controller import ( # v10 (Mamba-2 standalone controller)
|
|
204
|
+
Mamba2ControllerWrapper,
|
|
205
|
+
_require_mamba2_ssm,
|
|
206
|
+
)
|
|
207
|
+
from initium.mamba_controller.mamba3_controller import ( # v12 (Mamba-3 controller)
|
|
208
|
+
Mamba3ControllerWrapper,
|
|
209
|
+
_require_mamba3_ssm,
|
|
210
|
+
)
|
|
211
|
+
|
|
212
|
+
|
|
213
|
+
# ==========================================================================
|
|
214
|
+
# 1. MambaControllerCell -- one Mamba-1 block, single-timestep, BPTT-safe
|
|
215
|
+
# ==========================================================================
|
|
216
|
+
class MambaControllerCell(nn.Module):
|
|
217
|
+
"""One Mamba-1 (selective SSM) block, driven one timestep at a time.
|
|
218
|
+
|
|
219
|
+
Reuses `mamba_ssm.modules.mamba_simple.Mamba` purely as a parameter
|
|
220
|
+
container (correct shapes + the library's own initialization), and
|
|
221
|
+
reimplements the single-step S6 recurrence out-of-place so it can be
|
|
222
|
+
backpropagated through an arbitrary number of chained calls -- see the
|
|
223
|
+
module docstring's "Why not just call Mamba.step()?" section for why
|
|
224
|
+
the library's own `.step()` cannot be used here as-is.
|
|
225
|
+
|
|
226
|
+
State: `(conv_state, ssm_state)`, shapes `(B, d_inner, d_conv)` and
|
|
227
|
+
`(B, d_inner, d_state)` respectively -- same shapes as
|
|
228
|
+
`Mamba.allocate_inference_cache()` produces, so this stays a drop-in
|
|
229
|
+
match for the library's own state convention even though the state is
|
|
230
|
+
threaded functionally here rather than mutated in place.
|
|
231
|
+
"""
|
|
232
|
+
|
|
233
|
+
def __init__(
|
|
234
|
+
self,
|
|
235
|
+
d_model: int,
|
|
236
|
+
d_state: int = 16,
|
|
237
|
+
d_conv: int = 4,
|
|
238
|
+
expand: int = 2,
|
|
239
|
+
layer_idx: int | None = None,
|
|
240
|
+
device: torch.device | None = None,
|
|
241
|
+
dtype: torch.dtype | None = None,
|
|
242
|
+
):
|
|
243
|
+
super().__init__()
|
|
244
|
+
_require_mamba_ssm()
|
|
245
|
+
|
|
246
|
+
# use_fast_path=False: purely defensive/documentary. We never call
|
|
247
|
+
# `self.mamba.forward()` or `self.mamba.step()` on this instance --
|
|
248
|
+
# only its submodules (in_proj, conv1d, ...) are used, by `step()`
|
|
249
|
+
# below -- so this flag has no effect on us either way. Set False so
|
|
250
|
+
# nothing accidentally routes through the causal_conv1d/
|
|
251
|
+
# mamba_inner_fn CUDA fast path if some other code ever calls
|
|
252
|
+
# `.forward()` on `self.mamba` directly.
|
|
253
|
+
self.mamba = Mamba(
|
|
254
|
+
d_model=d_model,
|
|
255
|
+
d_state=d_state,
|
|
256
|
+
d_conv=d_conv,
|
|
257
|
+
expand=expand,
|
|
258
|
+
layer_idx=layer_idx,
|
|
259
|
+
use_fast_path=False,
|
|
260
|
+
device=device,
|
|
261
|
+
dtype=dtype,
|
|
262
|
+
)
|
|
263
|
+
self.d_model = d_model
|
|
264
|
+
self.d_inner = self.mamba.d_inner
|
|
265
|
+
self.d_state = self.mamba.d_state
|
|
266
|
+
self.d_conv = self.mamba.d_conv
|
|
267
|
+
self.dt_rank = self.mamba.dt_rank
|
|
268
|
+
|
|
269
|
+
def init_state(
|
|
270
|
+
self, batch_size: int, device: torch.device | None = None, dtype: torch.dtype | None = None
|
|
271
|
+
) -> tuple[torch.Tensor, torch.Tensor]:
|
|
272
|
+
"""Zero-initialized (conv_state, ssm_state), matching
|
|
273
|
+
`Mamba.allocate_inference_cache`'s shapes/convention (see module
|
|
274
|
+
docstring for why this stays zero-init rather than xavier-init)."""
|
|
275
|
+
conv_state = torch.zeros(batch_size, self.d_inner, self.d_conv, device=device, dtype=dtype)
|
|
276
|
+
ssm_state = torch.zeros(batch_size, self.d_inner, self.d_state, device=device, dtype=dtype)
|
|
277
|
+
return conv_state, ssm_state
|
|
278
|
+
|
|
279
|
+
def step(
|
|
280
|
+
self,
|
|
281
|
+
hidden_states: torch.Tensor,
|
|
282
|
+
conv_state: torch.Tensor,
|
|
283
|
+
ssm_state: torch.Tensor,
|
|
284
|
+
) -> tuple[torch.Tensor, torch.Tensor, torch.Tensor]:
|
|
285
|
+
"""Single-timestep S6 update. `hidden_states`: (B, d_model).
|
|
286
|
+
|
|
287
|
+
Line-for-line equivalent to `Mamba.step()`'s non-fast-path branch
|
|
288
|
+
(mamba_ssm/modules/mamba_simple.py), except every state update is
|
|
289
|
+
out-of-place (`new_state = ...`, no `.copy_()`), so this function is
|
|
290
|
+
safe to call repeatedly inside a Python loop under full
|
|
291
|
+
backprop-through-time -- see module docstring.
|
|
292
|
+
"""
|
|
293
|
+
m = self.mamba
|
|
294
|
+
dtype = hidden_states.dtype
|
|
295
|
+
|
|
296
|
+
# ---- input/gate projection --------------------------------------
|
|
297
|
+
xz = m.in_proj(hidden_states) # (B, 2*d_inner)
|
|
298
|
+
x, z = xz.chunk(2, dim=-1) # (B, d_inner) each
|
|
299
|
+
|
|
300
|
+
# ---- causal depthwise conv, functional (rolling window) ---------
|
|
301
|
+
# conv_state holds the last (d_conv - 1) x's; append the new x and
|
|
302
|
+
# drop the oldest column -- out-of-place shift, mirrors what
|
|
303
|
+
# Mamba.step() does via conv_state.copy_(roll(...)) but without
|
|
304
|
+
# mutating the input tensor.
|
|
305
|
+
new_conv_state = torch.cat([conv_state[:, :, 1:], x.unsqueeze(-1)], dim=-1)
|
|
306
|
+
conv_weight = m.conv1d.weight.squeeze(1) # (d_inner, 1, d_conv) -> (d_inner, d_conv)
|
|
307
|
+
x_conv = torch.sum(new_conv_state * conv_weight, dim=-1) # (B, d_inner)
|
|
308
|
+
if m.conv1d.bias is not None:
|
|
309
|
+
x_conv = x_conv + m.conv1d.bias
|
|
310
|
+
x_conv = m.act(x_conv).to(dtype=dtype)
|
|
311
|
+
|
|
312
|
+
# ---- input-dependent selection parameters (Delta, B, C) ---------
|
|
313
|
+
# FIX (chronic grad_norm nan in Mamba only, never in LSTM runs):
|
|
314
|
+
# x_proj/dt_proj/softplus/out_proj are plain nn.Linear calls, so
|
|
315
|
+
# under ambient autocast they still run (and can overflow) in fp16
|
|
316
|
+
# even though the scan below was already forced to fp32. Because
|
|
317
|
+
# ssm_state/conv_state persist across an entire episode (up to
|
|
318
|
+
# ~250+ timesteps, reset only at episode start), a single fp16
|
|
319
|
+
# overflow anywhere in this chain poisons every later step of that
|
|
320
|
+
# episode -- unlike the LSTM, whose gated hidden state is bounded
|
|
321
|
+
# by construction every step. Fix: run x_proj/dt_proj/softplus and
|
|
322
|
+
# the scan in real fp32, clamp dt's upper bound (its own dynamics
|
|
323
|
+
# only ever need ~0.001-0.1) and A_log's lower bound (a very
|
|
324
|
+
# negative A_log drives A -> 0, turning the SSM into an undamped
|
|
325
|
+
# accumulator), and hard-clamp the recurrent state and y before
|
|
326
|
+
# they're allowed to re-enter out_proj's autocast fp16 matmul.
|
|
327
|
+
with torch.autocast(device_type=hidden_states.device.type, enabled=False):
|
|
328
|
+
x_conv32 = x_conv.float()
|
|
329
|
+
x_db = m.x_proj(x_conv32) # (B, dt_rank + 2*d_state), real fp32
|
|
330
|
+
dt, B, C = torch.split(x_db, [self.dt_rank, self.d_state, self.d_state], dim=-1)
|
|
331
|
+
dt = F.linear(dt, m.dt_proj.weight.float()) # bias added below, inside softplus
|
|
332
|
+
dt = F.softplus(dt + m.dt_proj.bias.float()) # (B, d_inner), real fp32
|
|
333
|
+
dt32 = dt.clamp(min=1e-6, max=100.0) # upper bound: normal dt is 0.001-0.1
|
|
334
|
+
B32 = B.float()
|
|
335
|
+
C32 = C.float()
|
|
336
|
+
A_log_c = m.A_log.float().clamp(
|
|
337
|
+
min=-20.0, max=20.0
|
|
338
|
+
) # lower bound stops A -> 0 (undamped accumulator)
|
|
339
|
+
A32 = -torch.exp(A_log_c) # (d_inner, d_state)
|
|
340
|
+
dA = torch.exp(torch.einsum("bd,dn->bdn", dt32, A32))
|
|
341
|
+
dB = torch.einsum("bd,bn->bdn", dt32, B32)
|
|
342
|
+
new_ssm_state32 = (
|
|
343
|
+
ssm_state.float() * dA + x_conv32.unsqueeze(-1) * dB
|
|
344
|
+
) # (B, d_inner, d_state)
|
|
345
|
+
new_ssm_state32 = new_ssm_state32.clamp(
|
|
346
|
+
min=-1e4, max=1e4
|
|
347
|
+
) # hard stop on undamped growth
|
|
348
|
+
if new_ssm_state32.abs().max() > 9999.0:
|
|
349
|
+
print(
|
|
350
|
+
"[clamp-hit] ssm_state clamped, pre-clamp max would have exceeded bound"
|
|
351
|
+
) # If you see [clamp-hit] printed and NaNs still occur afterward in the same run, that proves the overflow is happening in the backward pass, not the forward activations — i.e., gradients blowing up through the dA = exp(dt·A) term over the ~250-step unroll even though forward values are now bounded. clamp() zeroes the gradient outside its range, so that specific tensor can't be the source once clamped; the remaining unclamped path (particularly dt32 and A32 before the einsum) is the next suspect, since gradients through exp() compounded across hundreds of BPTT steps can still explode independent of forward-value magnitude.
|
|
352
|
+
y32 = torch.einsum("bdn,bn->bd", new_ssm_state32, C32)
|
|
353
|
+
y32 = y32 + m.D.float() * x_conv32
|
|
354
|
+
y32 = y32 * m.act(z).float() # gated output
|
|
355
|
+
y32 = y32.clamp(
|
|
356
|
+
min=-1e4, max=1e4
|
|
357
|
+
) # stay well inside fp16 range before out_proj re-enters autocast
|
|
358
|
+
new_ssm_state = new_ssm_state32.to(dtype)
|
|
359
|
+
y = y32.to(dtype)
|
|
360
|
+
|
|
361
|
+
out = m.out_proj(
|
|
362
|
+
y
|
|
363
|
+
) # (B, d_model) -- back under ambient autocast, safe now that y is bounded
|
|
364
|
+
return out, new_conv_state, new_ssm_state
|
|
365
|
+
|
|
366
|
+
|
|
367
|
+
# ==========================================================================
|
|
368
|
+
# 2. MambaControllerBlock -- pre-norm residual wrapper around one cell
|
|
369
|
+
# ==========================================================================
|
|
370
|
+
class MambaControllerBlock(nn.Module):
|
|
371
|
+
"""Add -> LN -> Mixer residual block around one `MambaControllerCell`,
|
|
372
|
+
mirroring `mamba_ssm.modules.block.Block`'s own pattern (pre-norm,
|
|
373
|
+
residual returned separately upstream for fused-kernel reasons that
|
|
374
|
+
don't apply to us; here we just add it back directly since we're not
|
|
375
|
+
using the fused add+norm kernel)."""
|
|
376
|
+
|
|
377
|
+
def __init__(
|
|
378
|
+
self,
|
|
379
|
+
d_model: int,
|
|
380
|
+
d_state: int = 16,
|
|
381
|
+
d_conv: int = 4,
|
|
382
|
+
expand: int = 2,
|
|
383
|
+
layer_idx: int | None = None,
|
|
384
|
+
device: torch.device | None = None,
|
|
385
|
+
dtype: torch.dtype | None = None,
|
|
386
|
+
):
|
|
387
|
+
super().__init__()
|
|
388
|
+
self.norm = nn.LayerNorm(d_model, device=device, dtype=dtype)
|
|
389
|
+
self.cell = MambaControllerCell(
|
|
390
|
+
d_model,
|
|
391
|
+
d_state=d_state,
|
|
392
|
+
d_conv=d_conv,
|
|
393
|
+
expand=expand,
|
|
394
|
+
layer_idx=layer_idx,
|
|
395
|
+
device=device,
|
|
396
|
+
dtype=dtype,
|
|
397
|
+
)
|
|
398
|
+
|
|
399
|
+
def init_state(
|
|
400
|
+
self, batch_size: int, device: torch.device | None = None, dtype: torch.dtype | None = None
|
|
401
|
+
):
|
|
402
|
+
return self.cell.init_state(batch_size, device=device, dtype=dtype)
|
|
403
|
+
|
|
404
|
+
def step(self, x: torch.Tensor, state: tuple[torch.Tensor, torch.Tensor]):
|
|
405
|
+
conv_state, ssm_state = state
|
|
406
|
+
out, new_conv_state, new_ssm_state = self.cell.step(self.norm(x), conv_state, ssm_state)
|
|
407
|
+
return x + out, (new_conv_state, new_ssm_state)
|
|
408
|
+
|
|
409
|
+
|
|
410
|
+
# ==========================================================================
|
|
411
|
+
# 3. MambaControllerWrapper -- stack of blocks, nn.LSTM-compatible call API
|
|
412
|
+
# ==========================================================================
|
|
413
|
+
class MambaControllerWrapper(nn.Module):
|
|
414
|
+
"""Stacks `num_blocks` `MambaControllerBlock`s and exposes the exact
|
|
415
|
+
calling convention `dnc.dnc.DNC._layer_forward` expects from
|
|
416
|
+
`self.rnns[layer]`:
|
|
417
|
+
|
|
418
|
+
out_unsq, new_hx = wrapper(x_unsq, hx)
|
|
419
|
+
|
|
420
|
+
where `x_unsq` has shape (B, 1, in_dim) (DNC always calls with
|
|
421
|
+
`input.unsqueeze(1)`, one timestep at a time) and `out_unsq` has shape
|
|
422
|
+
(B, 1, d_model). `hx` is a list of `(conv_state, ssm_state)` tuples, one
|
|
423
|
+
per stacked block, or `None` for a fresh zero state.
|
|
424
|
+
"""
|
|
425
|
+
|
|
426
|
+
def __init__(
|
|
427
|
+
self,
|
|
428
|
+
in_dim: int,
|
|
429
|
+
d_model: int,
|
|
430
|
+
num_blocks: int = 2,
|
|
431
|
+
moe_enabled: bool = False,
|
|
432
|
+
moe_num_experts: int = 8,
|
|
433
|
+
moe_expert_dim: int | None = None,
|
|
434
|
+
moe_capacity_factor: float = 1.5,
|
|
435
|
+
moe_load_balance_alpha: float = 0.01,
|
|
436
|
+
moe_top_k: int = 1,
|
|
437
|
+
d_state: int = 16,
|
|
438
|
+
d_conv: int = 4,
|
|
439
|
+
expand: int = 2,
|
|
440
|
+
device: torch.device | None = None,
|
|
441
|
+
dtype: torch.dtype | None = None,
|
|
442
|
+
):
|
|
443
|
+
super().__init__()
|
|
444
|
+
self.d_model = d_model
|
|
445
|
+
self.num_blocks = num_blocks
|
|
446
|
+
# Dimension-matching adapter for the first block only -- mirrors how
|
|
447
|
+
# nn.LSTM(num_layers=N)'s layer-0 weight matrix differs in shape
|
|
448
|
+
# from layers 1..N-1 (layer 0 maps in_dim -> hidden, the rest map
|
|
449
|
+
# hidden -> hidden). Identity when already matched (in_dim == d_model),
|
|
450
|
+
# e.g. for DNC layers beyond the first when num_layers > 1.
|
|
451
|
+
self.in_adapter: nn.Module = (
|
|
452
|
+
nn.Identity()
|
|
453
|
+
if in_dim == d_model
|
|
454
|
+
else nn.Linear(in_dim, d_model, device=device, dtype=dtype)
|
|
455
|
+
)
|
|
456
|
+
self.blocks: nn.ModuleList = nn.ModuleList(
|
|
457
|
+
[
|
|
458
|
+
MambaControllerBlock(
|
|
459
|
+
d_model,
|
|
460
|
+
d_state=d_state,
|
|
461
|
+
d_conv=d_conv,
|
|
462
|
+
expand=expand,
|
|
463
|
+
layer_idx=i,
|
|
464
|
+
device=device,
|
|
465
|
+
dtype=dtype,
|
|
466
|
+
)
|
|
467
|
+
for i in range(num_blocks)
|
|
468
|
+
]
|
|
469
|
+
)
|
|
470
|
+
self.moe_enabled = moe_enabled
|
|
471
|
+
self.moe_blocks: nn.ModuleList | None = None
|
|
472
|
+
if moe_enabled:
|
|
473
|
+
# External interleaving only (Dead-End #43): one MoEBlock per
|
|
474
|
+
# MambaControllerBlock, applied AFTER that block's own output.
|
|
475
|
+
# Every block gets its own MoE sublayer (matches MoE-Mamba's own
|
|
476
|
+
# architecture, Figure 2: Mamba+MoE repeated N times) rather than
|
|
477
|
+
# every-other-layer -- their own Table 3 shows both converge to
|
|
478
|
+
# the same final perplexity for their best (16-expert) config,
|
|
479
|
+
# so defaulting to every layer costs nothing and is simpler.
|
|
480
|
+
self.moe_blocks = nn.ModuleList(
|
|
481
|
+
[
|
|
482
|
+
MoEBlock(
|
|
483
|
+
d_model,
|
|
484
|
+
num_experts=moe_num_experts,
|
|
485
|
+
expert_dim=moe_expert_dim,
|
|
486
|
+
capacity_factor=moe_capacity_factor,
|
|
487
|
+
load_balance_alpha=moe_load_balance_alpha,
|
|
488
|
+
top_k=moe_top_k,
|
|
489
|
+
device=device,
|
|
490
|
+
dtype=dtype,
|
|
491
|
+
)
|
|
492
|
+
for _ in range(num_blocks)
|
|
493
|
+
]
|
|
494
|
+
)
|
|
495
|
+
|
|
496
|
+
def init_state(
|
|
497
|
+
self, batch_size: int, device: torch.device | None = None, dtype: torch.dtype | None = None
|
|
498
|
+
):
|
|
499
|
+
if device is None or dtype is None:
|
|
500
|
+
p = next(self.parameters())
|
|
501
|
+
device = device if device is not None else p.device
|
|
502
|
+
dtype = dtype if dtype is not None else p.dtype
|
|
503
|
+
blocks = cast(Iterable[MambaControllerBlock], self.blocks)
|
|
504
|
+
return [blk.init_state(batch_size, device=device, dtype=dtype) for blk in blocks]
|
|
505
|
+
|
|
506
|
+
def forward(self, input: torch.Tensor, hx):
|
|
507
|
+
# input: (B, 1, in_dim) -- DNC's `_layer_forward` always calls with
|
|
508
|
+
# `input.unsqueeze(1)`, a single timestep at a time, since memory
|
|
509
|
+
# read/write must be interleaved between controller steps.
|
|
510
|
+
assert input.dim() == 3 and input.size(1) == 1, (
|
|
511
|
+
"MambaControllerWrapper only supports single-timestep calls "
|
|
512
|
+
f"(got shape {tuple(input.shape)}); this mirrors how DNC drives "
|
|
513
|
+
"nn.LSTM one step at a time, never a full sequence at once."
|
|
514
|
+
)
|
|
515
|
+
x = input.squeeze(1)
|
|
516
|
+
x = self.in_adapter(x)
|
|
517
|
+
|
|
518
|
+
if hx is None:
|
|
519
|
+
hx = self.init_state(x.size(0), device=x.device, dtype=x.dtype)
|
|
520
|
+
|
|
521
|
+
new_hx = []
|
|
522
|
+
blocks = cast(Iterable[MambaControllerBlock], self.blocks)
|
|
523
|
+
for i, (block, state) in enumerate(zip(blocks, hx)):
|
|
524
|
+
x, new_state = block.step(x, state)
|
|
525
|
+
if self.moe_enabled:
|
|
526
|
+
assert self.moe_blocks is not None
|
|
527
|
+
x = self.moe_blocks[i](x)
|
|
528
|
+
new_hx.append(new_state)
|
|
529
|
+
|
|
530
|
+
return x.unsqueeze(1), new_hx
|
|
531
|
+
|
|
532
|
+
|
|
533
|
+
# ==========================================================================
|
|
534
|
+
# 4. MambaDNC -- dnc.DNC subclass that can build a Mamba-1 controller
|
|
535
|
+
# ==========================================================================
|
|
536
|
+
class MambaDNC(DNC):
|
|
537
|
+
"""`dnc.DNC` subclass that adds `rnn_type='mamba'` as a third controller
|
|
538
|
+
option alongside pytorch-dnc's existing `'rnn'`/`'gru'`/`'lstm'`.
|
|
539
|
+
|
|
540
|
+
For any `rnn_type` other than `'mamba'`, `__init__`/`_init_hidden` defer
|
|
541
|
+
entirely to `dnc.DNC`'s own implementation -- this class is a strict
|
|
542
|
+
drop-in superset, so switching the Phase 2 training script between LSTM
|
|
543
|
+
and Mamba controllers is a one-line change (`rnn_type='lstm'` vs.
|
|
544
|
+
`rnn_type='mamba'`), not a code fork. All memory/addressing construction
|
|
545
|
+
(`dnc.memory.Memory`, the output projection) is copied verbatim from
|
|
546
|
+
`dnc.DNC.__init__` for the `'mamba'` branch below, to guarantee it can
|
|
547
|
+
never silently drift from upstream's addressing logic.
|
|
548
|
+
"""
|
|
549
|
+
|
|
550
|
+
def __init__(
|
|
551
|
+
self,
|
|
552
|
+
input_size: int,
|
|
553
|
+
hidden_size: int,
|
|
554
|
+
rnn_type: str = "lstm",
|
|
555
|
+
num_layers: int = 1,
|
|
556
|
+
num_hidden_layers: int = 2,
|
|
557
|
+
bias: bool = True,
|
|
558
|
+
batch_first: bool = True,
|
|
559
|
+
dropout: float = 0,
|
|
560
|
+
nr_cells: int = 5,
|
|
561
|
+
read_heads: int = 2,
|
|
562
|
+
cell_size: int = 10,
|
|
563
|
+
nonlinearity: str = "tanh",
|
|
564
|
+
independent_linears: bool = False,
|
|
565
|
+
share_memory_between_layers: bool = True,
|
|
566
|
+
debug: bool = False,
|
|
567
|
+
clip: float = 20,
|
|
568
|
+
device: torch.device | None = None,
|
|
569
|
+
# Mamba-specific hyperparameters (no LSTM/GRU/RNN analogue -- see
|
|
570
|
+
# module docstring's "Design choices" section). Defaults are
|
|
571
|
+
# Mamba-1's own paper defaults.
|
|
572
|
+
mamba_d_state: int = 16,
|
|
573
|
+
mamba_d_conv: int = 4,
|
|
574
|
+
mamba_expand: int = 2,
|
|
575
|
+
# Mamba-2-specific hyperparameters (rnn_type='mamba2' only -- see
|
|
576
|
+
# mamba2_controller.py's module docstring for how these differ from
|
|
577
|
+
# Mamba-1's mamba_d_state/mamba_d_conv/mamba_expand above). Defaults
|
|
578
|
+
# are Mamba-2's own paper defaults (Dao & Gu 2024, Section 7.2:
|
|
579
|
+
# head_dim in {64,128}); ngroups=1 matches every other Mamba-2 use in
|
|
580
|
+
# this project, e.g. mamba_backbone_parallel.py.
|
|
581
|
+
mamba2_d_state: int = 64,
|
|
582
|
+
mamba2_d_conv: int = 4,
|
|
583
|
+
mamba2_expand: int = 2,
|
|
584
|
+
mamba2_headdim: int = 64,
|
|
585
|
+
mamba2_ngroups: int = 1,
|
|
586
|
+
mamba3_d_state: int = 64,
|
|
587
|
+
mamba3_expand: int = 2,
|
|
588
|
+
mamba3_headdim: int = 64,
|
|
589
|
+
mamba3_rope_fraction: float = 0.5,
|
|
590
|
+
# v13: CfC (LNN) controller hyperparameters -- rnn_type='cfc' only.
|
|
591
|
+
cfc_mode: str = "default", # "default" | "pure" | "no_gate"
|
|
592
|
+
cfc_backbone_units: int = 512,
|
|
593
|
+
cfc_backbone_layers: int = 1,
|
|
594
|
+
cfc_backbone_dropout: float = 0.0,
|
|
595
|
+
cfc_activation: str = "lecun_tanh",
|
|
596
|
+
cfc_mixed_memory: bool = False, # True = CfC-mmRNN variant
|
|
597
|
+
cfc_residual: bool = True,
|
|
598
|
+
hybrid_cfc_num_blocks: int = 1, # v14: depth of the CfC stage in "<mamba*>+cfc" rnn_types
|
|
599
|
+
# (the Mamba stage's depth is num_hidden_layers)
|
|
600
|
+
moe_enabled: bool = False,
|
|
601
|
+
moe_num_experts: int = 8,
|
|
602
|
+
moe_expert_dim: int | None = None,
|
|
603
|
+
moe_capacity_factor: float = 1.5,
|
|
604
|
+
moe_load_balance_alpha: float = 0.01,
|
|
605
|
+
moe_top_k: int = 1,
|
|
606
|
+
):
|
|
607
|
+
if rnn_type.lower() not in ("mamba", "mamba2", "mamba3", "cfc") and not is_hybrid_rnn_type(
|
|
608
|
+
rnn_type
|
|
609
|
+
):
|
|
610
|
+
super().__init__(
|
|
611
|
+
input_size=input_size,
|
|
612
|
+
hidden_size=hidden_size,
|
|
613
|
+
rnn_type=rnn_type,
|
|
614
|
+
num_layers=num_layers,
|
|
615
|
+
num_hidden_layers=num_hidden_layers,
|
|
616
|
+
bias=bias,
|
|
617
|
+
batch_first=batch_first,
|
|
618
|
+
dropout=dropout,
|
|
619
|
+
nr_cells=nr_cells,
|
|
620
|
+
read_heads=read_heads,
|
|
621
|
+
cell_size=cell_size,
|
|
622
|
+
nonlinearity=nonlinearity,
|
|
623
|
+
independent_linears=independent_linears,
|
|
624
|
+
share_memory_between_layers=share_memory_between_layers,
|
|
625
|
+
debug=debug,
|
|
626
|
+
clip=clip,
|
|
627
|
+
device=device,
|
|
628
|
+
)
|
|
629
|
+
if moe_enabled:
|
|
630
|
+
# Basic MoE for the plain LSTM/GRU/RNN baseline (per the
|
|
631
|
+
# roadmap: the shallow baseline controller only needs a
|
|
632
|
+
# single Top-K MoE sublayer wrapped around its own output,
|
|
633
|
+
# not per-block/per-source specialization -- see
|
|
634
|
+
# moe_layer.MoERNNWrapper). ASSUMPTION FLAGGED FOR
|
|
635
|
+
# VERIFICATION: this assumes dnc.DNC registers each
|
|
636
|
+
# self.rnns[layer] as a genuine submodule under SOME
|
|
637
|
+
# attribute on `self` (every other external-library
|
|
638
|
+
# assumption in this project, e.g. split_graph_dnc.py's
|
|
639
|
+
# Memory-return-shape note, is flagged the same way) -- the
|
|
640
|
+
# sweep below finds and rebinds whichever attribute that is,
|
|
641
|
+
# the same technique dynamic_memory_resize.resize_memory()
|
|
642
|
+
# already uses for Memory swaps.
|
|
643
|
+
for layer in range(num_layers):
|
|
644
|
+
old_rnn = self.rnns[layer]
|
|
645
|
+
wrapped = MoERNNWrapper(
|
|
646
|
+
old_rnn,
|
|
647
|
+
d_model=hidden_size,
|
|
648
|
+
num_experts=moe_num_experts,
|
|
649
|
+
expert_dim=moe_expert_dim,
|
|
650
|
+
top_k=moe_top_k,
|
|
651
|
+
capacity_factor=moe_capacity_factor,
|
|
652
|
+
load_balance_alpha=moe_load_balance_alpha,
|
|
653
|
+
device=device,
|
|
654
|
+
)
|
|
655
|
+
self.rnns[layer] = wrapped
|
|
656
|
+
for attr_name, submodule in list(self._modules.items()):
|
|
657
|
+
if submodule is old_rnn:
|
|
658
|
+
setattr(self, attr_name, wrapped)
|
|
659
|
+
self.moe_layers: list[nn.Module] = []
|
|
660
|
+
for controller in self.rnns:
|
|
661
|
+
self.moe_layers.extend(cast(Any, controller).moe_blocks)
|
|
662
|
+
return
|
|
663
|
+
|
|
664
|
+
if rnn_type.lower() == "mamba2":
|
|
665
|
+
_require_mamba2_ssm()
|
|
666
|
+
elif rnn_type.lower() == "mamba3":
|
|
667
|
+
_require_mamba3_ssm()
|
|
668
|
+
elif is_hybrid_rnn_type(rnn_type):
|
|
669
|
+
_require_ncps()
|
|
670
|
+
elif rnn_type.lower() == "cfc":
|
|
671
|
+
_require_ncps()
|
|
672
|
+
else:
|
|
673
|
+
_require_mamba_ssm()
|
|
674
|
+
|
|
675
|
+
# ---- rnn_type in ("mamba", "mamba2"): custom construction --------
|
|
676
|
+
# Deliberately NOT calling DNC.__init__ (it hardcodes nn.RNN/GRU/LSTM
|
|
677
|
+
# construction inline with no extension point) -- instead, replicate
|
|
678
|
+
# its non-controller-specific bookkeeping verbatim (same attribute
|
|
679
|
+
# names, same Memory/output construction) and only swap in the
|
|
680
|
+
# Mamba controller for `self.rnns`.
|
|
681
|
+
nn.Module.__init__(self)
|
|
682
|
+
|
|
683
|
+
self.input_size = input_size
|
|
684
|
+
self.hidden_size = hidden_size
|
|
685
|
+
self.rnn_type = rnn_type
|
|
686
|
+
self.num_layers = num_layers
|
|
687
|
+
self.num_hidden_layers = num_hidden_layers
|
|
688
|
+
self.bias = bias
|
|
689
|
+
self.batch_first = batch_first
|
|
690
|
+
self.dropout = dropout
|
|
691
|
+
self.nr_cells = nr_cells
|
|
692
|
+
self.read_heads = read_heads
|
|
693
|
+
self.cell_size = cell_size
|
|
694
|
+
self.nonlinearity = nonlinearity
|
|
695
|
+
self.independent_linears = independent_linears
|
|
696
|
+
self.share_memory_between_layers = share_memory_between_layers
|
|
697
|
+
self.debug = debug
|
|
698
|
+
self.clip = clip
|
|
699
|
+
self.device = device
|
|
700
|
+
|
|
701
|
+
# Mamba-only bookkeeping, stored for __repr__ / checkpoint metadata.
|
|
702
|
+
self.mamba_d_state = mamba_d_state
|
|
703
|
+
self.mamba_d_conv = mamba_d_conv
|
|
704
|
+
self.mamba_expand = mamba_expand
|
|
705
|
+
|
|
706
|
+
# Mamba-2-only bookkeeping (rnn_type='mamba2'), same checkpoint-
|
|
707
|
+
# metadata rationale as the Mamba-1 fields above.
|
|
708
|
+
self.mamba2_d_state = mamba2_d_state
|
|
709
|
+
self.mamba2_d_conv = mamba2_d_conv
|
|
710
|
+
self.mamba2_expand = mamba2_expand
|
|
711
|
+
self.mamba2_headdim = mamba2_headdim
|
|
712
|
+
self.mamba2_ngroups = mamba2_ngroups
|
|
713
|
+
|
|
714
|
+
self.mamba3_d_state = mamba3_d_state
|
|
715
|
+
self.mamba3_expand = mamba3_expand
|
|
716
|
+
self.mamba3_headdim = mamba3_headdim
|
|
717
|
+
self.mamba3_rope_fraction = mamba3_rope_fraction
|
|
718
|
+
|
|
719
|
+
self.cfc_mode = cfc_mode
|
|
720
|
+
self.cfc_backbone_units = cfc_backbone_units
|
|
721
|
+
self.cfc_backbone_layers = cfc_backbone_layers
|
|
722
|
+
self.cfc_backbone_dropout = cfc_backbone_dropout
|
|
723
|
+
self.cfc_activation = cfc_activation
|
|
724
|
+
self.cfc_mixed_memory = cfc_mixed_memory
|
|
725
|
+
self.cfc_residual = cfc_residual
|
|
726
|
+
self.hybrid_cfc_num_blocks = hybrid_cfc_num_blocks
|
|
727
|
+
|
|
728
|
+
# Option 4 (MoE) bookkeeping -- stored for checkpoint metadata,
|
|
729
|
+
# same convention as the mamba_d_state/d_conv/expand fields above.
|
|
730
|
+
self.moe_enabled = moe_enabled
|
|
731
|
+
self.moe_num_experts = moe_num_experts
|
|
732
|
+
self.moe_expert_dim = moe_expert_dim
|
|
733
|
+
self.moe_capacity_factor = moe_capacity_factor
|
|
734
|
+
self.moe_load_balance_alpha = moe_load_balance_alpha
|
|
735
|
+
self.moe_top_k = moe_top_k
|
|
736
|
+
|
|
737
|
+
self.w = self.cell_size
|
|
738
|
+
self.r = self.read_heads
|
|
739
|
+
self.read_vectors_size = self.read_heads * self.cell_size
|
|
740
|
+
self.output_size = self.hidden_size
|
|
741
|
+
self.nn_input_size = self.input_size + self.read_vectors_size
|
|
742
|
+
self.nn_output_size = self.output_size + self.read_vectors_size
|
|
743
|
+
|
|
744
|
+
self.rnns: list[nn.Module] = []
|
|
745
|
+
self.memories: list[Memory] = []
|
|
746
|
+
|
|
747
|
+
for layer in range(self.num_layers):
|
|
748
|
+
in_dim = self.nn_input_size if layer == 0 else self.nn_output_size
|
|
749
|
+
if is_hybrid_rnn_type(rnn_type):
|
|
750
|
+
# Every kind in the chain gets the SAME moe_* settings
|
|
751
|
+
# (its own per-block MoE sublayers -- ChainedControllerWrapper
|
|
752
|
+
# already aggregates every stage's moe_blocks generically).
|
|
753
|
+
_moe_per_kind = (
|
|
754
|
+
dict(
|
|
755
|
+
moe_enabled=moe_enabled,
|
|
756
|
+
moe_num_experts=moe_num_experts,
|
|
757
|
+
moe_expert_dim=moe_expert_dim,
|
|
758
|
+
moe_capacity_factor=moe_capacity_factor,
|
|
759
|
+
moe_load_balance_alpha=moe_load_balance_alpha,
|
|
760
|
+
moe_top_k=moe_top_k,
|
|
761
|
+
)
|
|
762
|
+
if moe_enabled
|
|
763
|
+
else {}
|
|
764
|
+
)
|
|
765
|
+
controller = build_hybrid_controller(
|
|
766
|
+
rnn_type.lower(),
|
|
767
|
+
in_dim=in_dim,
|
|
768
|
+
d_model=self.output_size,
|
|
769
|
+
blocks_per_kind={
|
|
770
|
+
"mamba": self.num_hidden_layers,
|
|
771
|
+
"mamba2": self.num_hidden_layers,
|
|
772
|
+
"mamba3": self.num_hidden_layers,
|
|
773
|
+
"cfc": hybrid_cfc_num_blocks,
|
|
774
|
+
},
|
|
775
|
+
kwargs_per_kind={
|
|
776
|
+
"mamba": dict(
|
|
777
|
+
d_state=mamba_d_state,
|
|
778
|
+
d_conv=mamba_d_conv,
|
|
779
|
+
expand=mamba_expand,
|
|
780
|
+
**_moe_per_kind,
|
|
781
|
+
),
|
|
782
|
+
"mamba2": dict(
|
|
783
|
+
d_state=mamba2_d_state,
|
|
784
|
+
d_conv=mamba2_d_conv,
|
|
785
|
+
expand=mamba2_expand,
|
|
786
|
+
headdim=mamba2_headdim,
|
|
787
|
+
ngroups=mamba2_ngroups,
|
|
788
|
+
**_moe_per_kind,
|
|
789
|
+
),
|
|
790
|
+
"mamba3": dict(
|
|
791
|
+
d_state=mamba3_d_state,
|
|
792
|
+
expand=mamba3_expand,
|
|
793
|
+
headdim=mamba3_headdim,
|
|
794
|
+
rope_fraction=mamba3_rope_fraction,
|
|
795
|
+
**_moe_per_kind,
|
|
796
|
+
),
|
|
797
|
+
"cfc": dict(
|
|
798
|
+
mode=cfc_mode,
|
|
799
|
+
backbone_units=cfc_backbone_units,
|
|
800
|
+
backbone_layers=cfc_backbone_layers,
|
|
801
|
+
backbone_dropout=cfc_backbone_dropout,
|
|
802
|
+
activation=cfc_activation,
|
|
803
|
+
mixed_memory=cfc_mixed_memory,
|
|
804
|
+
residual=cfc_residual,
|
|
805
|
+
**_moe_per_kind,
|
|
806
|
+
),
|
|
807
|
+
},
|
|
808
|
+
device=device,
|
|
809
|
+
)
|
|
810
|
+
elif rnn_type.lower() == "cfc":
|
|
811
|
+
controller = CfCControllerWrapper(
|
|
812
|
+
in_dim=in_dim,
|
|
813
|
+
d_model=self.output_size,
|
|
814
|
+
num_blocks=self.num_hidden_layers,
|
|
815
|
+
mode=cfc_mode,
|
|
816
|
+
backbone_units=cfc_backbone_units,
|
|
817
|
+
backbone_layers=cfc_backbone_layers,
|
|
818
|
+
backbone_dropout=cfc_backbone_dropout,
|
|
819
|
+
activation=cfc_activation,
|
|
820
|
+
mixed_memory=cfc_mixed_memory,
|
|
821
|
+
residual=cfc_residual,
|
|
822
|
+
moe_enabled=moe_enabled,
|
|
823
|
+
moe_num_experts=moe_num_experts,
|
|
824
|
+
moe_expert_dim=moe_expert_dim,
|
|
825
|
+
moe_top_k=moe_top_k,
|
|
826
|
+
moe_capacity_factor=moe_capacity_factor,
|
|
827
|
+
moe_load_balance_alpha=moe_load_balance_alpha,
|
|
828
|
+
device=device,
|
|
829
|
+
)
|
|
830
|
+
elif rnn_type.lower() == "mamba3":
|
|
831
|
+
controller = Mamba3ControllerWrapper(
|
|
832
|
+
in_dim=in_dim,
|
|
833
|
+
d_model=self.output_size,
|
|
834
|
+
num_blocks=self.num_hidden_layers,
|
|
835
|
+
d_state=mamba3_d_state,
|
|
836
|
+
expand=mamba3_expand,
|
|
837
|
+
headdim=mamba3_headdim,
|
|
838
|
+
rope_fraction=mamba3_rope_fraction,
|
|
839
|
+
moe_enabled=moe_enabled,
|
|
840
|
+
moe_num_experts=moe_num_experts,
|
|
841
|
+
moe_expert_dim=moe_expert_dim,
|
|
842
|
+
moe_capacity_factor=moe_capacity_factor,
|
|
843
|
+
moe_load_balance_alpha=moe_load_balance_alpha,
|
|
844
|
+
moe_top_k=moe_top_k,
|
|
845
|
+
device=device,
|
|
846
|
+
)
|
|
847
|
+
elif rnn_type.lower() == "mamba2":
|
|
848
|
+
controller = Mamba2ControllerWrapper(
|
|
849
|
+
in_dim=in_dim,
|
|
850
|
+
d_model=self.output_size,
|
|
851
|
+
num_blocks=self.num_hidden_layers,
|
|
852
|
+
d_state=mamba2_d_state,
|
|
853
|
+
d_conv=mamba2_d_conv,
|
|
854
|
+
expand=mamba2_expand,
|
|
855
|
+
headdim=mamba2_headdim,
|
|
856
|
+
ngroups=mamba2_ngroups,
|
|
857
|
+
moe_enabled=moe_enabled,
|
|
858
|
+
moe_num_experts=moe_num_experts,
|
|
859
|
+
moe_expert_dim=moe_expert_dim,
|
|
860
|
+
moe_capacity_factor=moe_capacity_factor,
|
|
861
|
+
moe_load_balance_alpha=moe_load_balance_alpha,
|
|
862
|
+
moe_top_k=moe_top_k,
|
|
863
|
+
device=device,
|
|
864
|
+
)
|
|
865
|
+
else:
|
|
866
|
+
controller = MambaControllerWrapper(
|
|
867
|
+
in_dim=in_dim,
|
|
868
|
+
d_model=self.output_size,
|
|
869
|
+
num_blocks=self.num_hidden_layers,
|
|
870
|
+
d_state=mamba_d_state,
|
|
871
|
+
d_conv=mamba_d_conv,
|
|
872
|
+
expand=mamba_expand,
|
|
873
|
+
moe_enabled=moe_enabled,
|
|
874
|
+
moe_num_experts=moe_num_experts,
|
|
875
|
+
moe_expert_dim=moe_expert_dim,
|
|
876
|
+
moe_capacity_factor=moe_capacity_factor,
|
|
877
|
+
moe_load_balance_alpha=moe_load_balance_alpha,
|
|
878
|
+
moe_top_k=moe_top_k,
|
|
879
|
+
device=device,
|
|
880
|
+
)
|
|
881
|
+
self.rnns.append(controller)
|
|
882
|
+
# setattr so this shows up as a proper submodule for autograd /
|
|
883
|
+
# optimizer.parameters(), same trick dnc.DNC itself uses for its
|
|
884
|
+
# own self.rnns entries.
|
|
885
|
+
setattr(self, "mamba_layer_" + str(layer), controller)
|
|
886
|
+
|
|
887
|
+
# memories for each layer -- copied verbatim from dnc.DNC.__init__
|
|
888
|
+
if not self.share_memory_between_layers:
|
|
889
|
+
self.memories.append(
|
|
890
|
+
Memory(
|
|
891
|
+
input_size=self.output_size,
|
|
892
|
+
nr_cells=self.nr_cells,
|
|
893
|
+
cell_size=self.w,
|
|
894
|
+
read_heads=self.r,
|
|
895
|
+
device=self.device,
|
|
896
|
+
independent_linears=self.independent_linears,
|
|
897
|
+
)
|
|
898
|
+
)
|
|
899
|
+
setattr(self, "rnn_layer_memory_" + str(layer), self.memories[layer])
|
|
900
|
+
|
|
901
|
+
# only one memory shared by all layers -- copied verbatim from
|
|
902
|
+
# dnc.DNC.__init__
|
|
903
|
+
if self.share_memory_between_layers:
|
|
904
|
+
self.memories.append(
|
|
905
|
+
Memory(
|
|
906
|
+
input_size=self.output_size,
|
|
907
|
+
nr_cells=self.nr_cells,
|
|
908
|
+
cell_size=self.w,
|
|
909
|
+
read_heads=self.r,
|
|
910
|
+
device=self.device,
|
|
911
|
+
independent_linears=self.independent_linears,
|
|
912
|
+
)
|
|
913
|
+
)
|
|
914
|
+
self.rnn_layer_memory_shared = self.memories[0]
|
|
915
|
+
|
|
916
|
+
# Option 4 (MoE): flat list of every installed MoEBlock across all
|
|
917
|
+
# layers, for the training script's pop_total_moe_aux_loss() call --
|
|
918
|
+
# same "give the caller one flat list" convention
|
|
919
|
+
# install_stochastic_write_heads() already uses for stochastic
|
|
920
|
+
# write heads in stochastic_write_head_v2.py.
|
|
921
|
+
self.moe_layers = []
|
|
922
|
+
if self.moe_enabled:
|
|
923
|
+
for layer_controller in self.rnns:
|
|
924
|
+
self.moe_layers.extend(cast(Any, layer_controller).moe_blocks)
|
|
925
|
+
|
|
926
|
+
# final output layer -- copied verbatim from dnc.DNC.__init__
|
|
927
|
+
self.output = nn.Linear(self.nn_output_size, self.input_size)
|
|
928
|
+
torch.nn.init.kaiming_uniform_(self.output.weight)
|
|
929
|
+
|
|
930
|
+
if self.device is not None and self.device.type == "cuda":
|
|
931
|
+
self.to(self.device)
|
|
932
|
+
|
|
933
|
+
def _init_hidden(self, hx, batch_size: int, reset_experience: bool):
|
|
934
|
+
if self.rnn_type.lower() not in (
|
|
935
|
+
"mamba",
|
|
936
|
+
"mamba2",
|
|
937
|
+
"mamba3",
|
|
938
|
+
"cfc",
|
|
939
|
+
) and not is_hybrid_rnn_type(self.rnn_type):
|
|
940
|
+
return super()._init_hidden(hx, batch_size, reset_experience)
|
|
941
|
+
|
|
942
|
+
# ---- controller-state branch: Mamba (conv_state, ssm_state) ------
|
|
943
|
+
if hx is not None:
|
|
944
|
+
chx, mhx, last_read = hx
|
|
945
|
+
else:
|
|
946
|
+
chx, mhx, last_read = None, None, None
|
|
947
|
+
|
|
948
|
+
if chx is None:
|
|
949
|
+
chx = [
|
|
950
|
+
cast(Any, self.rnns[layer]).init_state(batch_size, device=self.device)
|
|
951
|
+
for layer in range(self.num_layers)
|
|
952
|
+
]
|
|
953
|
+
|
|
954
|
+
if last_read is None:
|
|
955
|
+
last_read = cuda(torch.zeros(batch_size, self.w * self.r), device=self.device)
|
|
956
|
+
|
|
957
|
+
# ---- memory-state branch: byte-identical to dnc.DNC._init_hidden -
|
|
958
|
+
# (copied verbatim -- this logic is controller-agnostic and must not
|
|
959
|
+
# drift from upstream's own resume/reset semantics)
|
|
960
|
+
if mhx is None:
|
|
961
|
+
if self.share_memory_between_layers:
|
|
962
|
+
mhx = [self.memories[0].reset(batch_size, erase=reset_experience)]
|
|
963
|
+
else:
|
|
964
|
+
mhx = [m.reset(batch_size, erase=reset_experience) for m in self.memories]
|
|
965
|
+
else:
|
|
966
|
+
if self.share_memory_between_layers:
|
|
967
|
+
if len(mhx) == 0 or mhx[0] is None:
|
|
968
|
+
mhx = [self.memories[0].reset(batch_size, erase=reset_experience)]
|
|
969
|
+
else:
|
|
970
|
+
mhx = [self.memories[0].reset(batch_size, mhx[0], erase=reset_experience)]
|
|
971
|
+
else:
|
|
972
|
+
if len(mhx) == 0:
|
|
973
|
+
mhx = [m.reset(batch_size, erase=reset_experience) for m in self.memories]
|
|
974
|
+
else:
|
|
975
|
+
new_mhx = []
|
|
976
|
+
for i, m in enumerate(self.memories):
|
|
977
|
+
if i < len(mhx) and mhx[i] is not None:
|
|
978
|
+
new_mhx.append(m.reset(batch_size, mhx[i], erase=reset_experience))
|
|
979
|
+
else:
|
|
980
|
+
new_mhx.append(m.reset(batch_size, erase=reset_experience))
|
|
981
|
+
mhx = new_mhx
|
|
982
|
+
|
|
983
|
+
return chx, mhx, last_read
|