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