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,242 @@
|
|
|
1
|
+
"""
|
|
2
|
+
file: mamba3_controller.py -- v1
|
|
3
|
+
Interleaved Mamba-3 (SISO) DNC controller. Same Cell/Block/Wrapper split as
|
|
4
|
+
mamba2_controller.py. Math: paper Prop.1 (exp-trapezoidal) + Prop.4/Eq.11 (RoPE trick):
|
|
5
|
+
h_t = a_t h_{t-1} + b_t (k_{t-1} x_{t-1}^T) + g_t (k_t x_t^T), y_t = C_t^T h_t + D x_t
|
|
6
|
+
a=exp(DT*A), g=lam*DT, b=(1-lam)*DT*a, k/C = RMSNorm+bias then rotated by cumsum(angle*DT).
|
|
7
|
+
State per block: (angle_state, ssm_state, k_prev, v_prev), ALL fp32 (angle is a running sum).
|
|
8
|
+
No conv_state: Mamba-3 has no short conv. Everything after in_proj runs in real fp32.
|
|
9
|
+
"""
|
|
10
|
+
|
|
11
|
+
from __future__ import annotations
|
|
12
|
+
|
|
13
|
+
import torch
|
|
14
|
+
import torch.nn as nn
|
|
15
|
+
import torch.nn.functional as F
|
|
16
|
+
|
|
17
|
+
from initium.mamba_controller.mamba2_controller import Mamba2ControllerWrapper
|
|
18
|
+
from initium.MoE.moe_layer import MoEBlock
|
|
19
|
+
|
|
20
|
+
_MAMBA3_IMPORT_ERROR: str | None
|
|
21
|
+
|
|
22
|
+
try:
|
|
23
|
+
from mamba_ssm.modules.mamba3 import Mamba3
|
|
24
|
+
except ImportError as _e: # pragma: no cover
|
|
25
|
+
Mamba3 = None
|
|
26
|
+
_MAMBA3_IMPORT_ERROR = (
|
|
27
|
+
"mamba3_controller.py needs mamba-ssm installed from GitHub main "
|
|
28
|
+
f"(Mamba3 is not in the v2.3.1 wheel). Original error: {_e}"
|
|
29
|
+
)
|
|
30
|
+
else:
|
|
31
|
+
_MAMBA3_IMPORT_ERROR = None
|
|
32
|
+
|
|
33
|
+
|
|
34
|
+
def _require_mamba3_ssm() -> None:
|
|
35
|
+
if Mamba3 is None:
|
|
36
|
+
raise ImportError(_MAMBA3_IMPORT_ERROR)
|
|
37
|
+
|
|
38
|
+
|
|
39
|
+
def _heavy_tail(x): # copied from mamba_ssm.modules.mamba3.heavy_tail_activation
|
|
40
|
+
return x.clamp_min(0) + torch.reciprocal(1 - x.clamp_max(0))
|
|
41
|
+
|
|
42
|
+
|
|
43
|
+
def _rms(v, w, eps):
|
|
44
|
+
return v * torch.rsqrt(v.pow(2).mean(-1, keepdim=True) + eps) * w.float()
|
|
45
|
+
|
|
46
|
+
|
|
47
|
+
class Mamba3ControllerCell(nn.Module):
|
|
48
|
+
def __init__(
|
|
49
|
+
self,
|
|
50
|
+
d_model,
|
|
51
|
+
d_state=64,
|
|
52
|
+
expand=2,
|
|
53
|
+
headdim=64,
|
|
54
|
+
ngroups=1,
|
|
55
|
+
rope_fraction=0.5,
|
|
56
|
+
layer_idx=None,
|
|
57
|
+
device=None,
|
|
58
|
+
dtype=None,
|
|
59
|
+
):
|
|
60
|
+
super().__init__()
|
|
61
|
+
_require_mamba3_ssm()
|
|
62
|
+
assert ngroups == 1, "Mamba3ControllerCell supports ngroups==1 only"
|
|
63
|
+
# parameter container only; we never call .forward()/.step() on it
|
|
64
|
+
self.mamba3 = Mamba3(
|
|
65
|
+
d_model=d_model,
|
|
66
|
+
d_state=d_state,
|
|
67
|
+
expand=expand,
|
|
68
|
+
headdim=headdim,
|
|
69
|
+
ngroups=ngroups,
|
|
70
|
+
rope_fraction=rope_fraction,
|
|
71
|
+
is_mimo=False,
|
|
72
|
+
is_outproj_norm=False,
|
|
73
|
+
layer_idx=layer_idx,
|
|
74
|
+
device=device,
|
|
75
|
+
dtype=dtype,
|
|
76
|
+
)
|
|
77
|
+
m = self.mamba3
|
|
78
|
+
self.d_model, self.d_inner, self.nheads, self.headdim = (
|
|
79
|
+
d_model,
|
|
80
|
+
m.d_inner,
|
|
81
|
+
m.nheads,
|
|
82
|
+
m.headdim,
|
|
83
|
+
)
|
|
84
|
+
self.d_state, self.num_rope_angles = m.d_state, m.num_rope_angles
|
|
85
|
+
|
|
86
|
+
def init_state(self, batch_size, device=None, dtype=None): # dtype ignored: always fp32
|
|
87
|
+
device = device if device is not None else self.mamba3.in_proj.weight.device
|
|
88
|
+
H, P, N, S = self.nheads, self.headdim, self.d_state, self.num_rope_angles
|
|
89
|
+
f = dict(device=device, dtype=torch.float32)
|
|
90
|
+
return (
|
|
91
|
+
torch.zeros(batch_size, H, S, **f),
|
|
92
|
+
torch.zeros(batch_size, H, P, N, **f),
|
|
93
|
+
torch.zeros(batch_size, H, N, **f),
|
|
94
|
+
torch.zeros(batch_size, H, P, **f),
|
|
95
|
+
)
|
|
96
|
+
|
|
97
|
+
@staticmethod
|
|
98
|
+
def _rotate(v, cos, sin): # v: (b,H,N); rotate first 2S dims, adjacent pairs (SISO convention)
|
|
99
|
+
S = cos.shape[-1]
|
|
100
|
+
head, tail = v[..., : 2 * S], v[..., 2 * S :]
|
|
101
|
+
h0, h1 = head[..., 0::2], head[..., 1::2]
|
|
102
|
+
rot = torch.stack([h0 * cos - h1 * sin, h0 * sin + h1 * cos], dim=-1).flatten(-2)
|
|
103
|
+
return torch.cat([rot, tail], dim=-1)
|
|
104
|
+
|
|
105
|
+
def step(self, u, state):
|
|
106
|
+
angle_state, ssm_state, k_prev, v_prev = state
|
|
107
|
+
m = self.mamba3
|
|
108
|
+
H, P, N, S = self.nheads, self.headdim, self.d_state, self.num_rope_angles
|
|
109
|
+
dtype = u.dtype
|
|
110
|
+
with torch.autocast(device_type=u.device.type, enabled=False):
|
|
111
|
+
zxb = F.linear(u.float(), m.in_proj.weight.float())
|
|
112
|
+
z, x, B, C, dd_dt, dd_A, trap_proj, ang_proj = torch.split(
|
|
113
|
+
zxb, [self.d_inner, self.d_inner, N, N, H, H, H, S], dim=-1
|
|
114
|
+
)
|
|
115
|
+
A = (-_heavy_tail(dd_A)).clamp(max=-m.A_floor) # (b,H)
|
|
116
|
+
DT = F.softplus(dd_dt + m.dt_bias.float()).clamp(min=1e-6, max=100.0)
|
|
117
|
+
lam = torch.sigmoid(trap_proj)
|
|
118
|
+
alpha = torch.exp(DT * A)
|
|
119
|
+
gamma = lam * DT
|
|
120
|
+
beta = (1.0 - lam) * DT * alpha
|
|
121
|
+
B = _rms(B, m.B_norm.weight, m.B_norm.eps).unsqueeze(1) + m.B_bias.float().squeeze(
|
|
122
|
+
1
|
|
123
|
+
) # (b,H,N)
|
|
124
|
+
C = _rms(C, m.C_norm.weight, m.C_norm.eps).unsqueeze(1) + m.C_bias.float().squeeze(1)
|
|
125
|
+
angle_new = angle_state + ang_proj.unsqueeze(1) * DT.unsqueeze(
|
|
126
|
+
-1
|
|
127
|
+
) # (b,H,S) cumsum(angle*DT)
|
|
128
|
+
cos, sin = angle_new.cos(), angle_new.sin()
|
|
129
|
+
B, C = self._rotate(B, cos, sin), self._rotate(C, cos, sin)
|
|
130
|
+
x = x.reshape(-1, H, P)
|
|
131
|
+
z = z.reshape(-1, H, P)
|
|
132
|
+
new_ssm = (
|
|
133
|
+
ssm_state * alpha[..., None, None]
|
|
134
|
+
+ beta[..., None, None] * (v_prev.unsqueeze(-1) * k_prev.unsqueeze(-2))
|
|
135
|
+
+ gamma[..., None, None] * (x.unsqueeze(-1) * B.unsqueeze(-2))
|
|
136
|
+
)
|
|
137
|
+
new_ssm = new_ssm.clamp(min=-1e4, max=1e4) # same hard stop as Mamba-1/2 cells
|
|
138
|
+
y = torch.einsum("bhpn,bhn->bhp", new_ssm, C) + m.D.float().view(1, H, 1) * x
|
|
139
|
+
y = (y * F.silu(z)).reshape(-1, self.d_inner).clamp(min=-1e4, max=1e4)
|
|
140
|
+
out = m.out_proj(y.to(dtype)) # back under ambient autocast
|
|
141
|
+
return out, (angle_new, new_ssm, B, x)
|
|
142
|
+
|
|
143
|
+
|
|
144
|
+
class Mamba3ControllerBlock(nn.Module):
|
|
145
|
+
def __init__(
|
|
146
|
+
self,
|
|
147
|
+
d_model,
|
|
148
|
+
d_state=64,
|
|
149
|
+
expand=2,
|
|
150
|
+
headdim=64,
|
|
151
|
+
ngroups=1,
|
|
152
|
+
rope_fraction=0.5,
|
|
153
|
+
layer_idx=None,
|
|
154
|
+
device=None,
|
|
155
|
+
dtype=None,
|
|
156
|
+
):
|
|
157
|
+
super().__init__()
|
|
158
|
+
self.norm = nn.LayerNorm(d_model, device=device, dtype=dtype)
|
|
159
|
+
self.cell = Mamba3ControllerCell(
|
|
160
|
+
d_model,
|
|
161
|
+
d_state=d_state,
|
|
162
|
+
expand=expand,
|
|
163
|
+
headdim=headdim,
|
|
164
|
+
ngroups=ngroups,
|
|
165
|
+
rope_fraction=rope_fraction,
|
|
166
|
+
layer_idx=layer_idx,
|
|
167
|
+
device=device,
|
|
168
|
+
dtype=dtype,
|
|
169
|
+
)
|
|
170
|
+
|
|
171
|
+
def init_state(self, batch_size, device=None, dtype=None):
|
|
172
|
+
return self.cell.init_state(batch_size, device=device, dtype=dtype)
|
|
173
|
+
|
|
174
|
+
def step(self, x, state):
|
|
175
|
+
out, new_state = self.cell.step(self.norm(x), state)
|
|
176
|
+
return x + out, new_state
|
|
177
|
+
|
|
178
|
+
|
|
179
|
+
class Mamba3ControllerWrapper(Mamba2ControllerWrapper):
|
|
180
|
+
"""Reuses Mamba2ControllerWrapper.forward()/init_state() unchanged (they only touch
|
|
181
|
+
in_adapter / blocks / moe_blocks / moe_enabled); only __init__ differs."""
|
|
182
|
+
|
|
183
|
+
def __init__(
|
|
184
|
+
self,
|
|
185
|
+
in_dim,
|
|
186
|
+
d_model,
|
|
187
|
+
num_blocks=2,
|
|
188
|
+
moe_enabled=False,
|
|
189
|
+
moe_num_experts=8,
|
|
190
|
+
moe_expert_dim=None,
|
|
191
|
+
moe_capacity_factor=1.5,
|
|
192
|
+
moe_load_balance_alpha=0.01,
|
|
193
|
+
moe_top_k=1,
|
|
194
|
+
d_state=64,
|
|
195
|
+
expand=2,
|
|
196
|
+
headdim=64,
|
|
197
|
+
ngroups=1,
|
|
198
|
+
rope_fraction=0.5,
|
|
199
|
+
device=None,
|
|
200
|
+
dtype=None,
|
|
201
|
+
):
|
|
202
|
+
nn.Module.__init__(self)
|
|
203
|
+
self.d_model, self.num_blocks = d_model, num_blocks
|
|
204
|
+
self.in_adapter = (
|
|
205
|
+
nn.Identity()
|
|
206
|
+
if in_dim == d_model
|
|
207
|
+
else nn.Linear(in_dim, d_model, device=device, dtype=dtype)
|
|
208
|
+
)
|
|
209
|
+
self.blocks = nn.ModuleList(
|
|
210
|
+
[
|
|
211
|
+
Mamba3ControllerBlock(
|
|
212
|
+
d_model,
|
|
213
|
+
d_state=d_state,
|
|
214
|
+
expand=expand,
|
|
215
|
+
headdim=headdim,
|
|
216
|
+
ngroups=ngroups,
|
|
217
|
+
rope_fraction=rope_fraction,
|
|
218
|
+
layer_idx=i,
|
|
219
|
+
device=device,
|
|
220
|
+
dtype=dtype,
|
|
221
|
+
)
|
|
222
|
+
for i in range(num_blocks)
|
|
223
|
+
]
|
|
224
|
+
)
|
|
225
|
+
self.moe_enabled = moe_enabled
|
|
226
|
+
self.moe_blocks = None
|
|
227
|
+
if moe_enabled:
|
|
228
|
+
self.moe_blocks = nn.ModuleList(
|
|
229
|
+
[
|
|
230
|
+
MoEBlock(
|
|
231
|
+
d_model,
|
|
232
|
+
num_experts=moe_num_experts,
|
|
233
|
+
expert_dim=moe_expert_dim,
|
|
234
|
+
capacity_factor=moe_capacity_factor,
|
|
235
|
+
top_k=moe_top_k,
|
|
236
|
+
load_balance_alpha=moe_load_balance_alpha,
|
|
237
|
+
device=device,
|
|
238
|
+
dtype=dtype,
|
|
239
|
+
)
|
|
240
|
+
for _ in range(num_blocks)
|
|
241
|
+
]
|
|
242
|
+
)
|
|
@@ -0,0 +1,291 @@
|
|
|
1
|
+
"""
|
|
2
|
+
file: mamba_backbone_parallel.py
|
|
3
|
+
|
|
4
|
+
Alternate Phase 3, Step 2, Option 5 ("split the compute graph, don't try to
|
|
5
|
+
parallelize the addressing itself") -- see Experiment-Roadmap.md.
|
|
6
|
+
|
|
7
|
+
This module implements ONLY the "(a) Mamba backbone" half of the Option 5
|
|
8
|
+
split: a stack of Mamba (v1) or Mamba-2 blocks driven over the WHOLE
|
|
9
|
+
sequence at once via each block's own native, library-provided forward()
|
|
10
|
+
(the fast, parallel-scan / SSD path), with NO per-timestep interleaving of
|
|
11
|
+
memory reads.
|
|
12
|
+
|
|
13
|
+
This is a deliberate, much simpler sibling to mamba_controller.py's
|
|
14
|
+
MambaControllerCell/Block/Wrapper (Alternative Phase 3, Step 1). That file
|
|
15
|
+
had to hand-roll a BPTT-safe manual step() specifically because the DNC's
|
|
16
|
+
per-timestep interleaving loop needed to call the controller one token at a
|
|
17
|
+
time while chaining (conv_state, ssm_state) through a Python loop under
|
|
18
|
+
full backprop, and mamba_ssm's own .step() mutates its state tensors
|
|
19
|
+
in-place (fatal for BPTT -- see that file's module docstring for the full
|
|
20
|
+
diagnosis).
|
|
21
|
+
|
|
22
|
+
Option 5's entire point is to remove that constraint for the backbone: since
|
|
23
|
+
this backbone's per-token computation has NO dependency on M_{t-1} (the
|
|
24
|
+
memory's state at the previous timestep) -- it only ever consumes the raw
|
|
25
|
+
input sequence X, never a read vector -- there is nothing forcing it into a
|
|
26
|
+
sequential Python loop. It can therefore call mamba_ssm's OWN whole-sequence
|
|
27
|
+
forward() directly, which already uses the fast parallel selective-scan
|
|
28
|
+
(Mamba-1) or the chunked, matmul-based SSD algorithm (Mamba-2, Dao & Gu
|
|
29
|
+
2024) -- no custom step function, no in-place-op workaround needed, because
|
|
30
|
+
nothing here is chained through a live memory-state Python loop.
|
|
31
|
+
|
|
32
|
+
The other half of the Option 5 split -- the "genuinely sequential, but much
|
|
33
|
+
smaller and cheaper, memory read/write/addressing step" that consumes this
|
|
34
|
+
backbone's parallel output as an input stream -- lives in
|
|
35
|
+
split_graph_dnc.py, not here. This file only produces H = backbone(X); it
|
|
36
|
+
has no notion of DNC, Memory, or read vectors at all, by design (see
|
|
37
|
+
Concept 6 / LB-9's caution about structural-vs-functional separation --
|
|
38
|
+
keeping this file's API surface structurally incapable of touching memory
|
|
39
|
+
state is a guarantee, not just a convention).
|
|
40
|
+
|
|
41
|
+
Supports two variants, both from the mamba-ssm package (per the roadmap's
|
|
42
|
+
"add Mamba-2 first if Option 5 needs it, otherwise Mamba-1 is already
|
|
43
|
+
implemented" instruction -- Option 5 benefits directly from Mamba-2's own
|
|
44
|
+
SSD chunked-scan algorithm, since that algorithm is specifically optimized
|
|
45
|
+
for exactly the "process the whole sequence at once" call pattern Option 5
|
|
46
|
+
newly unlocks for the backbone -- see the SSD paper, Sections 6 and 9.3,
|
|
47
|
+
already in corpus):
|
|
48
|
+
|
|
49
|
+
- "mamba1": mamba_ssm.modules.mamba_simple.Mamba
|
|
50
|
+
- "mamba2": mamba_ssm.modules.mamba2.Mamba2
|
|
51
|
+
|
|
52
|
+
Both are used exactly as their own package intends (whole-sequence
|
|
53
|
+
forward(hidden_states) call, batch_first (B, L, D) tensors) -- this file
|
|
54
|
+
does not reimplement or bypass anything of either class, unlike
|
|
55
|
+
mamba_controller.py's cell.
|
|
56
|
+
|
|
57
|
+
NOTE on Mamba2 kwargs: mamba_ssm's Mamba2 additionally requires `headdim`
|
|
58
|
+
(d_model must be divisible by it -- SSD paper's own convention is
|
|
59
|
+
head_dim in {64, 128}). This has no analogue in Mamba-1 and is therefore
|
|
60
|
+
a new, Mamba2-only constructor argument here, same pattern as
|
|
61
|
+
mamba_d_state/mamba_d_conv/mamba_expand having no LSTM analogue in
|
|
62
|
+
mamba_controller.py.
|
|
63
|
+
"""
|
|
64
|
+
|
|
65
|
+
from __future__ import annotations
|
|
66
|
+
|
|
67
|
+
import os as _os
|
|
68
|
+
|
|
69
|
+
import torch
|
|
70
|
+
import torch.nn as nn
|
|
71
|
+
|
|
72
|
+
from initium.MoE.moe_layer import MoEBlock
|
|
73
|
+
|
|
74
|
+
_MAMBA1_IMPORT_ERROR: str | None
|
|
75
|
+
_MAMBA2_IMPORT_ERROR: str | None
|
|
76
|
+
_MAMBA3_IMPORT_ERROR: str | None
|
|
77
|
+
|
|
78
|
+
try:
|
|
79
|
+
from mamba_ssm.modules.mamba_simple import Mamba as Mamba1
|
|
80
|
+
except ImportError as _mamba1_err: # pragma: no cover - environment-dependent
|
|
81
|
+
Mamba1 = None
|
|
82
|
+
_MAMBA1_IMPORT_ERROR = (
|
|
83
|
+
"mamba_backbone_parallel.py requires the `mamba-ssm` package for "
|
|
84
|
+
f"variant='mamba1'. Original import error: {_mamba1_err}"
|
|
85
|
+
)
|
|
86
|
+
else:
|
|
87
|
+
_MAMBA1_IMPORT_ERROR = None
|
|
88
|
+
|
|
89
|
+
try:
|
|
90
|
+
from mamba_ssm.modules.mamba2 import Mamba2
|
|
91
|
+
except ImportError as _mamba2_err: # pragma: no cover - environment-dependent
|
|
92
|
+
Mamba2 = None
|
|
93
|
+
_MAMBA2_IMPORT_ERROR = (
|
|
94
|
+
"mamba_backbone_parallel.py requires the `mamba-ssm` package "
|
|
95
|
+
"(with Mamba-2 support -- mamba_ssm.modules.mamba2.Mamba2) for "
|
|
96
|
+
f"variant='mamba2'. Original import error: {_mamba2_err}"
|
|
97
|
+
)
|
|
98
|
+
else:
|
|
99
|
+
_MAMBA2_IMPORT_ERROR = None
|
|
100
|
+
|
|
101
|
+
try:
|
|
102
|
+
from mamba_ssm.modules.mamba3 import Mamba3
|
|
103
|
+
except ImportError as _mamba3_err: # pragma: no cover - environment-dependent
|
|
104
|
+
Mamba3 = None
|
|
105
|
+
_MAMBA3_IMPORT_ERROR = (
|
|
106
|
+
"mamba_backbone_parallel.py: variant='mamba3' needs mamba-ssm installed from GitHub main "
|
|
107
|
+
f"(Mamba3 is not in the v2.3.1 wheel). Original import error: {_mamba3_err}"
|
|
108
|
+
)
|
|
109
|
+
else:
|
|
110
|
+
_MAMBA3_IMPORT_ERROR = None
|
|
111
|
+
|
|
112
|
+
# bf16 (the repo's own precision) when the GPU supports it; otherwise the fp32-under-fp16-autocast trick.
|
|
113
|
+
_MAMBA3_BF16_OK = (
|
|
114
|
+
torch.cuda.is_available()
|
|
115
|
+
and torch.cuda.get_device_capability()[0] >= 8
|
|
116
|
+
and _os.environ.get("MAMBA3_FORCE_FP32", "0") != "1"
|
|
117
|
+
)
|
|
118
|
+
|
|
119
|
+
|
|
120
|
+
def _require_variant(variant: str) -> None:
|
|
121
|
+
if variant == "mamba1" and Mamba1 is None:
|
|
122
|
+
raise ImportError(_MAMBA1_IMPORT_ERROR)
|
|
123
|
+
if variant == "mamba2" and Mamba2 is None:
|
|
124
|
+
raise ImportError(_MAMBA2_IMPORT_ERROR)
|
|
125
|
+
if variant == "mamba3" and Mamba3 is None:
|
|
126
|
+
raise ImportError(_MAMBA3_IMPORT_ERROR)
|
|
127
|
+
if variant not in ("mamba1", "mamba2", "mamba3"):
|
|
128
|
+
raise ValueError(
|
|
129
|
+
f"mamba_backbone_parallel: unknown variant {variant!r}, expected 'mamba1' or 'mamba2'."
|
|
130
|
+
)
|
|
131
|
+
|
|
132
|
+
|
|
133
|
+
class _ParallelBlock(nn.Module):
|
|
134
|
+
"""Pre-norm residual wrapper around one whole-sequence Mamba block,
|
|
135
|
+
mirroring mamba_ssm's own Block (Add -> LN -> Mixer) and
|
|
136
|
+
mamba_controller.py's MambaControllerBlock, but calling the mixer's own
|
|
137
|
+
forward() over the full (B, L, D) sequence at once instead of a
|
|
138
|
+
per-timestep step(). No custom state threading of any kind -- the
|
|
139
|
+
mixer manages its own internal scan/chunking entirely within this one
|
|
140
|
+
call.
|
|
141
|
+
"""
|
|
142
|
+
|
|
143
|
+
def __init__(
|
|
144
|
+
self,
|
|
145
|
+
d_model: int,
|
|
146
|
+
variant: str,
|
|
147
|
+
mamba_kwargs: dict,
|
|
148
|
+
device: torch.device | None = None,
|
|
149
|
+
dtype: torch.dtype | None = None,
|
|
150
|
+
):
|
|
151
|
+
super().__init__()
|
|
152
|
+
_require_variant(variant)
|
|
153
|
+
self.norm = nn.LayerNorm(d_model, device=device, dtype=dtype)
|
|
154
|
+
self.variant = variant
|
|
155
|
+
if variant == "mamba3":
|
|
156
|
+
self.mixer = Mamba3(d_model=d_model, device=device, dtype=dtype, **mamba_kwargs)
|
|
157
|
+
elif variant == "mamba1":
|
|
158
|
+
self.mixer = Mamba1(d_model=d_model, device=device, dtype=dtype, **mamba_kwargs)
|
|
159
|
+
else:
|
|
160
|
+
self.mixer = Mamba2(d_model=d_model, device=device, dtype=dtype, **mamba_kwargs)
|
|
161
|
+
|
|
162
|
+
def forward(self, x: torch.Tensor) -> torch.Tensor:
|
|
163
|
+
# x: (B, L, D). mamba_ssm's own forward() handles the whole
|
|
164
|
+
# sequence's parallel scan / SSD chunked-scan internally -- this is
|
|
165
|
+
# the entire mechanism that makes Option 5 different from
|
|
166
|
+
# mamba_controller.py: one call per block, not one call per DNC
|
|
167
|
+
# timestep.
|
|
168
|
+
#
|
|
169
|
+
# FIX (chronic grad_norm nan under fp16 AMP, Mamba-2 variant only in
|
|
170
|
+
# practice but applied to both for symmetry): same wrapper trick
|
|
171
|
+
# already used by mamba_controller.py's MambaControllerCell.step()
|
|
172
|
+
# and mamba2_controller.py's Mamba2ControllerCell.step() -- force
|
|
173
|
+
# this call to run in real fp32, outside ambient fp16 autocast,
|
|
174
|
+
# rather than reimplementing the mixer's internal math by hand the
|
|
175
|
+
# way those cells do for their own per-timestep step(). Unlike those
|
|
176
|
+
# cells, self.mixer here is the library's own whole-sequence
|
|
177
|
+
# forward() (that's the entire point of the parallel backbone), so
|
|
178
|
+
# we can't insert clamps mid-computation the way step() does --
|
|
179
|
+
# instead we just deny it fp16 entirely. Mamba-2's SSD algorithm
|
|
180
|
+
# computes exp() of a cumulative sum of (dt * A) over a whole chunk
|
|
181
|
+
# (paper Section 6, Listing 1's segsum), which is far more prone to
|
|
182
|
+
# fp16 underflow/overflow than Mamba-1's fused selective-scan
|
|
183
|
+
# kernel -- this is the direct cause of the amp_scale-collapse-to-
|
|
184
|
+
# 0.0 pattern observed in mamba2-backbone split-graph runs that
|
|
185
|
+
# mamba1-backbone runs didn't show. Costs some speed relative to
|
|
186
|
+
# running under fp16 (this block no longer benefits from tensor-core
|
|
187
|
+
# fp16 matmuls), but matches every other Mamba SSM computation path
|
|
188
|
+
# in this project, all of which already force fp32 for this reason.
|
|
189
|
+
if self.variant == "mamba3" and _MAMBA3_BF16_OK:
|
|
190
|
+
with torch.autocast(device_type=x.device.type, dtype=torch.bfloat16, enabled=True):
|
|
191
|
+
out = self.mixer(self.norm(x.float()).to(torch.bfloat16))
|
|
192
|
+
else:
|
|
193
|
+
with torch.autocast(device_type=x.device.type, enabled=False):
|
|
194
|
+
out = self.mixer(self.norm(x.float()))
|
|
195
|
+
return x + out.to(x.dtype)
|
|
196
|
+
|
|
197
|
+
|
|
198
|
+
class MambaBackboneParallel(nn.Module):
|
|
199
|
+
"""Stack of `num_blocks` whole-sequence Mamba blocks. Pure sequence-to-
|
|
200
|
+
sequence map (B, L, in_dim) -> (B, L, d_model), with NO awareness of
|
|
201
|
+
DNC, Memory, or read vectors -- see module docstring.
|
|
202
|
+
|
|
203
|
+
variant: "mamba1" (mamba_ssm.modules.mamba_simple.Mamba, default
|
|
204
|
+
Mamba-1 hyperparameters d_state=16/d_conv=4/expand=2, this
|
|
205
|
+
project's original Alternate-Phase-3 choice) or "mamba2"
|
|
206
|
+
(mamba_ssm.modules.mamba2.Mamba2, needs `headdim` in addition;
|
|
207
|
+
default 64, matching the SSD paper's own convention of head_dim in
|
|
208
|
+
{64, 128}).
|
|
209
|
+
"""
|
|
210
|
+
|
|
211
|
+
def __init__(
|
|
212
|
+
self,
|
|
213
|
+
in_dim: int,
|
|
214
|
+
d_model: int,
|
|
215
|
+
num_blocks: int = 2,
|
|
216
|
+
variant: str = "mamba1",
|
|
217
|
+
d_state: int = 16,
|
|
218
|
+
d_conv: int = 4,
|
|
219
|
+
expand: int = 2,
|
|
220
|
+
headdim: int = 64, # mamba2-only
|
|
221
|
+
moe_enabled: bool = False,
|
|
222
|
+
moe_num_experts: int = 8,
|
|
223
|
+
moe_expert_dim: int | None = None,
|
|
224
|
+
moe_top_k: int = 1,
|
|
225
|
+
moe_capacity_factor: float = 1.5,
|
|
226
|
+
moe_load_balance_alpha: float = 0.01,
|
|
227
|
+
device: torch.device | None = None,
|
|
228
|
+
dtype: torch.dtype | None = None,
|
|
229
|
+
):
|
|
230
|
+
super().__init__()
|
|
231
|
+
_require_variant(variant)
|
|
232
|
+
self.variant = variant
|
|
233
|
+
self.d_model = d_model
|
|
234
|
+
self.moe_enabled = moe_enabled
|
|
235
|
+
self.moe_blocks: nn.ModuleList | None = None
|
|
236
|
+
|
|
237
|
+
# Same role as MambaControllerWrapper's in_adapter: DNC's raw
|
|
238
|
+
# per-timestep input (input_size) generally differs from
|
|
239
|
+
# hidden_size/d_model. Unlike the interleaved wrapper, this is
|
|
240
|
+
# applied to the WHOLE sequence at once (a single
|
|
241
|
+
# (B, L, in_dim) -> (B, L, d_model) matmul), not per-timestep.
|
|
242
|
+
self.in_adapter: nn.Module = (
|
|
243
|
+
nn.Identity()
|
|
244
|
+
if in_dim == d_model
|
|
245
|
+
else nn.Linear(in_dim, d_model, device=device, dtype=dtype)
|
|
246
|
+
)
|
|
247
|
+
|
|
248
|
+
mamba_kwargs = dict(d_state=d_state, d_conv=d_conv, expand=expand)
|
|
249
|
+
if variant == "mamba2":
|
|
250
|
+
mamba_kwargs["headdim"] = headdim
|
|
251
|
+
if variant == "mamba3":
|
|
252
|
+
mamba_kwargs["headdim"] = headdim
|
|
253
|
+
mamba_kwargs["chunk_size"] = (
|
|
254
|
+
64 if _MAMBA3_BF16_OK else 32
|
|
255
|
+
) # repo: 64 bf16 / 32 otherwise
|
|
256
|
+
|
|
257
|
+
self.blocks = nn.ModuleList(
|
|
258
|
+
[
|
|
259
|
+
_ParallelBlock(d_model, variant, mamba_kwargs, device=device, dtype=dtype)
|
|
260
|
+
for _ in range(num_blocks)
|
|
261
|
+
]
|
|
262
|
+
)
|
|
263
|
+
if moe_enabled:
|
|
264
|
+
self.moe_blocks = nn.ModuleList(
|
|
265
|
+
[
|
|
266
|
+
MoEBlock(
|
|
267
|
+
d_model,
|
|
268
|
+
num_experts=moe_num_experts,
|
|
269
|
+
expert_dim=moe_expert_dim,
|
|
270
|
+
top_k=moe_top_k,
|
|
271
|
+
capacity_factor=moe_capacity_factor,
|
|
272
|
+
load_balance_alpha=moe_load_balance_alpha,
|
|
273
|
+
device=device,
|
|
274
|
+
dtype=dtype,
|
|
275
|
+
)
|
|
276
|
+
for _ in range(num_blocks)
|
|
277
|
+
]
|
|
278
|
+
)
|
|
279
|
+
|
|
280
|
+
def forward(self, x: torch.Tensor) -> torch.Tensor:
|
|
281
|
+
# x: (B, L, in_dim) -> (B, L, d_model). Called EXACTLY ONCE per
|
|
282
|
+
# training step (not once per DNC timestep, unlike
|
|
283
|
+
# MambaControllerWrapper) -- this single call is the entire
|
|
284
|
+
# "backbone half" of the Option 5 split.
|
|
285
|
+
h = self.in_adapter(x)
|
|
286
|
+
for i, block in enumerate(self.blocks):
|
|
287
|
+
h = block(h)
|
|
288
|
+
if self.moe_enabled:
|
|
289
|
+
assert self.moe_blocks is not None
|
|
290
|
+
h = self.moe_blocks[i](h)
|
|
291
|
+
return h
|