saturncube 0.7.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.
saturncube/__init__.py ADDED
@@ -0,0 +1,69 @@
1
+ """saturncube: one fit/predict/evaluate interface across many model families."""
2
+ from .base import BaseModel, REGISTRY, available, create, register, warmup
3
+ from .vector import (MLP, DeepFeedforward, ResNet, DenseNet, Highway, SqueezeExcitation,
4
+ MixtureOfExperts, GatedMixtureOfExperts, Hypernetwork, ImplicitNetwork,
5
+ DeepEquilibrium, KAN, RBF, ExtremeLearningMachine, RandomNeuralNetwork)
6
+
7
+ from .rules import (FeedbackAlignment, DirectFeedbackAlignment, ForwardGradient, LocalErrorLearning,
8
+ SyntheticGradients, EquilibriumPropagation, ContrastiveHebbian, PredictiveCoding)
9
+ from .optim import OPTIMIZERS, Lion, LAMB, make_optimizer
10
+
11
+ from .unsupervised import (Autoencoder, DenoisingAutoencoder, VAE, MaskedPrediction, Contrastive,
12
+ BYOL, DeepClustering, HebbianLearner, TemporalContrastive, LatentPrediction,
13
+ EnergyBasedModel)
14
+ from .strategies import EWC, LwF, Replay, Distillation, distill, query
15
+ from .rl import (GridWorld, PotentialShaping, QLearning, SARSA, ExpectedSARSA, MonteCarloControl,
16
+ BernoulliBandit, EpsilonGreedyBandit, UCB1, ThompsonBandit, LinUCB, run_bandit,
17
+ OneHotObs, PointMass, SARSALambda, DynaQ, MaxEntIRL)
18
+ from .deeprl import (DQN, DoubleDQN, DuelingDQN, PrioritizedDQN, REINFORCE, ActorCritic, A2C, PPO, TRPO, SAC,
19
+ ModelBasedMPC, PreferenceRewardModel, LearnedRewardEnv, ReplayBuffer)
20
+
21
+ from .sequence import (SimpleRNN, ElmanNetwork, LSTM, GRU, BidirectionalRNN, BidirectionalLSTM,
22
+ BidirectionalGRU, ContinuousTimeRNN, NeuralStateSpace, S4, Mamba, PlasticRNN,
23
+ JordanNetwork, EchoStateNetwork, LiquidStateMachine, RTRLRNN)
24
+
25
+ from .attention import (MultiHeadAttention, LinearAttention, TransformerLayer, TransformerEncoder,
26
+ TransformerDecoder, LinearAttentionTransformer, LocalAttentionTransformer,
27
+ SparseAttentionTransformer, MultiQueryTransformer, GroupedQueryTransformer,
28
+ UniversalTransformer, MemoryAugmentedTransformer, Perceiver, SSMAttentionHybrid,
29
+ RecurrentAttention, RetrievalAugmented, EncoderDecoderTransformer)
30
+
31
+ from .memory import (HopfieldNetwork, ModernHopfield, AssociativeMemory, ContentAddressableMemory,
32
+ ProductKeyMemory, ProductKeyMemoryNet, MemoryNetwork, KeyValueMemoryNetwork,
33
+ NeuralTuringMachine, DifferentiableNeuralComputer)
34
+
35
+ from .metric import (Siamese, TripletNetwork, TwinTower, ContrastiveEmbedding, PrototypicalNetwork,
36
+ RelationNetwork, MatchingNetwork)
37
+ from .meta import (Task, SineTasks, GaussianTasks, MAML, Reptile, transfer, fit_curriculum, fit_self_paced,
38
+ GenerativeReplay)
39
+
40
+ from .graph import (Graph, Tree, GraphConvolutionalNetwork, GraphAttentionNetwork, GraphSAGE,
41
+ GraphIsomorphismNetwork, MessagePassingNetwork, RelationalGCN, GraphTransformer,
42
+ GatedGraphNetwork, RecurrentGraphNetwork, HypergraphNeuralNetwork, ProgramGraphNetwork,
43
+ TreeLSTM, TreeStructuredNetwork, PointerNetwork, PointerGenerator)
44
+
45
+ from .pycode import check_python, run_py, PythonSandbox, python_facts, ExecutionError
46
+ from .search import GeneticAlgorithm, SuccessiveHalving, RandomSearch, SimulatedAnnealing
47
+ from .symbolic import (walk, tree_size, to_python, ParseError, StepLimitExceeded, EnumerativeSynthesizer, Datalog,
48
+ REACHABILITY_RULES)
49
+
50
+ from .retrieval import BM25Index, HashingEmbedder, VectorStore, CaseLibrary, tokenize_text
51
+ from .cognition import SM2Scheduler, InterleavedPractice, RevisionLoop, MetacognitiveMonitor
52
+ from .verify import (UnitTests, TestReport, check_property, differential_test, ints, lists, IntGen,
53
+ ListGen, PropertyResult)
54
+
55
+ from .codegen import GrammarGenerator, PY_GRAMMAR, TemplateFiller, TEMPLATES, BPETokenizer
56
+ from .experiment import (ArchitectureSpec, RandomArchitectureGenerator, MODULE_KINDS, validate, ablate_module,
57
+ ablate_edge, execute, latin_hypercube, full_factorial, bootstrap_ci, permutation_test,
58
+ holm_correction)
59
+ from .classical import DecisionTree, RandomForest, KMeans, NaiveBayes
60
+
61
+ from .vision import CNN, CNNNet
62
+ from .spiking import SpikingNetwork, SelfOrganizingMap, LIFLayer
63
+
64
+ from .data import (load_dataset, Dataset, Prepared, Pipeline, TabularPreprocessor, Report, auto_model, AutoResult,
65
+ load_pipeline, check_arrays, split_indices)
66
+ from .evaluation import benchmark, BenchmarkResult
67
+ from .text import TextVectorizer, TokenEncoder, TextModel
68
+
69
+ __version__ = "0.7.0"
saturncube/_docs.py ADDED
@@ -0,0 +1,89 @@
1
+ """Builds the README's list of registered entries from the registry, so the two cannot drift apart.
2
+
3
+ Run ``python scripts/update_readme.py`` after adding or changing a registered class; the test suite fails if the
4
+ README block no longer matches (``python scripts/update_readme.py --check`` does the same from the command line).
5
+ """
6
+ from __future__ import annotations
7
+
8
+ import re
9
+ from collections import Counter
10
+
11
+ from .base import REGISTRY, BaseModel, available
12
+
13
+ START = "<!-- registry-table:start -->"
14
+ END = "<!-- registry-table:end -->"
15
+
16
+ # Input conventions, only for families where one sentence is accurate for every entry.
17
+ FAMILY_NOTES = {
18
+ "vector": "Input: 2-D array `(n_samples, n_features)`.",
19
+ "classical": "Pure NumPy; input: 2-D array `(n_samples, n_features)`.",
20
+ "sequence": "Input: `(n, T, d)` floats, or integer tokens `(n, T)` with `vocab_size=`.",
21
+ "vision": "Input: images as `(n, C, H, W)` or `(n, H, W)`.",
22
+ "graph": "Input: `Graph` / `Tree` containers (dense adjacency, so small and medium graphs).",
23
+ "metric": "Few-shot: `predict(X, support=(X_support, y_support))` works on unseen classes.",
24
+ }
25
+
26
+ FAMILY_ORDER = ["vector", "vision", "bio", "sequence", "memory", "metric", "graph", "unsupervised", "classical",
27
+ "agent", "bandit", "search", "symbolic", "cognition", "verification", "codegen", "experiment"]
28
+
29
+
30
+ def kind_of(cls):
31
+ """model: shared fit/predict/evaluate/save interface; classical: NumPy learner with fit/predict;
32
+ agent: learns by interacting with an environment or bandit; tool: a plain Python helper class."""
33
+ if issubclass(cls, BaseModel):
34
+ return "model"
35
+ if cls.family == "classical":
36
+ return "classical"
37
+ if cls.family in ("agent", "bandit"):
38
+ return "agent"
39
+ return "tool"
40
+
41
+
42
+ def summary_of(cls):
43
+ """First sentence of the class's OWN docstring (never an inherited one)."""
44
+ doc = (cls.__dict__.get("__doc__") or "").strip()
45
+ if not doc:
46
+ return ""
47
+ para = " ".join(doc.split("\n\n")[0].split())
48
+ m = re.search(r"(?<=[a-z0-9\)`])\. (?=[A-Z])", para)
49
+ if m and m.start() >= 25:
50
+ para = para[:m.start()]
51
+ return para.rstrip(".").replace("|", "\\|")
52
+
53
+
54
+ def registry_markdown():
55
+ names = available()
56
+ kinds = Counter(kind_of(REGISTRY[n]) for n in names)
57
+ out = ["%d registered entries (`sc.available()`): %d models, %d classical learners, %d agents and bandits, "
58
+ "%d tools." % (len(names), kinds["model"], kinds["classical"], kinds["agent"], kinds["tool"]),
59
+ "",
60
+ "`model` entries share one `fit` / `predict` / `evaluate` / `save` / `load` interface and expose "
61
+ "`.module`. `classical` entries have `fit` / `predict` only. `agent` entries learn from an "
62
+ "environment or bandit instead of a dataset. `tool` entries are plain Python classes with their "
63
+ "own methods. Build any of them with `sc.create(name, **kwargs)`.", ""]
64
+ families = [f for f in FAMILY_ORDER if any(REGISTRY[n].family == f for n in names)]
65
+ families += sorted({REGISTRY[n].family for n in names} - set(families)) # a new family still shows up
66
+ for fam in families:
67
+ members = [n for n in names if REGISTRY[n].family == fam]
68
+ out.append("### %s (%d)" % (fam, len(members)))
69
+ out.append("")
70
+ if fam in FAMILY_NOTES:
71
+ out += [FAMILY_NOTES[fam], ""]
72
+ out += ["| Registry name | Class | Kind | Description |", "|---|---|---|---|"]
73
+ for n in members:
74
+ cls = REGISTRY[n]
75
+ out.append("| `%s` | `%s` | %s | %s |" % (n, cls.__name__, kind_of(cls), summary_of(cls)))
76
+ out.append("")
77
+ return "\n".join(out).rstrip() + "\n"
78
+
79
+
80
+ def render_block():
81
+ return START + "\n" + registry_markdown() + END
82
+
83
+
84
+ def splice(readme_text):
85
+ """README text with the generated block replaced (or raises if the markers are missing)."""
86
+ i, j = readme_text.find(START), readme_text.find(END)
87
+ if i < 0 or j < i:
88
+ raise ValueError("README is missing the %s ... %s markers" % (START, END))
89
+ return readme_text[:i] + render_block() + readme_text[j + len(END):]
@@ -0,0 +1,451 @@
1
+ """Attention and Transformer family (A3)."""
2
+ from __future__ import annotations
3
+
4
+ import math
5
+
6
+ import numpy as np
7
+ import torch
8
+ import torch.nn as nn
9
+ import torch.nn.functional as F
10
+
11
+ from .base import register
12
+ from .sequence import Core, MambaBlock, SequenceModel, SeqNet, _CoreModel
13
+
14
+
15
+ def sinusoid(T, d, device):
16
+ pos = torch.arange(T, device=device).float()[:, None]
17
+ div = torch.exp(-math.log(10000.0) * torch.arange(0, d, 2, device=device).float() / d)
18
+ pe = torch.zeros(T, d, device=device)
19
+ pe[:, 0::2], pe[:, 1::2] = torch.sin(pos * div), torch.cos(pos * div)[:, : d // 2]
20
+ return pe
21
+
22
+
23
+ def make_mask(T, causal=False, window=None, stride=None, device=None):
24
+ """Boolean (T, T) mask, True = may attend. window -> local band; stride -> every
25
+ stride-th earlier/later position as well (sparse, Child et al.)."""
26
+ i = torch.arange(T, device=device)[:, None]
27
+ j = torch.arange(T, device=device)[None, :]
28
+ allowed = torch.ones(T, T, dtype=torch.bool, device=device)
29
+ if window is not None or stride is not None:
30
+ allowed = (i - j).abs() <= window if window is not None else torch.zeros_like(allowed)
31
+ if stride:
32
+ allowed = allowed | ((i - j) % stride == 0)
33
+ return allowed & (j <= i) if causal else allowed
34
+
35
+
36
+ class MultiHeadAttention(nn.Module):
37
+ """Multi-head (n_kv_heads=n_heads), multi-query (1) or grouped-query (1 < g < heads)
38
+ attention. Pass `context` for cross-attention."""
39
+
40
+ def __init__(self, d, n_heads=4, n_kv_heads=None):
41
+ super().__init__()
42
+ kv = n_kv_heads or n_heads
43
+ if d % n_heads or n_heads % kv:
44
+ raise ValueError("d must divide by n_heads and n_heads by n_kv_heads")
45
+ self.h, self.kv, self.dh = n_heads, kv, d // n_heads
46
+ self.q, self.k, self.v = nn.Linear(d, d), nn.Linear(d, kv * self.dh), nn.Linear(d, kv * self.dh)
47
+ self.o = nn.Linear(d, d)
48
+
49
+ def forward(self, x, context=None, mask=None):
50
+ ctx = x if context is None else context
51
+ B, Tq, _ = x.shape
52
+ Tk = ctx.shape[1]
53
+ q = self.q(x).view(B, Tq, self.h, self.dh).transpose(1, 2)
54
+ k = self.k(ctx).view(B, Tk, self.kv, self.dh).transpose(1, 2)
55
+ v = self.v(ctx).view(B, Tk, self.kv, self.dh).transpose(1, 2)
56
+ if self.kv != self.h:
57
+ k, v = k.repeat_interleave(self.h // self.kv, 1), v.repeat_interleave(self.h // self.kv, 1)
58
+ y = F.scaled_dot_product_attention(q, k, v, attn_mask=mask)
59
+ return self.o(y.transpose(1, 2).reshape(B, Tq, -1))
60
+
61
+
62
+ class LinearAttention(nn.Module):
63
+ """Linear attention (Katharopoulos et al.): softmax replaced by a positive feature map,
64
+ so cost is linear in sequence length. causal=True uses running sums."""
65
+
66
+ def __init__(self, d, n_heads=4, causal=False):
67
+ super().__init__()
68
+ self.h, self.dh, self.causal = n_heads, d // n_heads, causal
69
+ self.q, self.k, self.v, self.o = (nn.Linear(d, d) for _ in range(4))
70
+
71
+ def forward(self, x, context=None, mask=None):
72
+ B, T, _ = x.shape
73
+ split = lambda t: t.view(B, T, self.h, self.dh).transpose(1, 2)
74
+ q, k, v = (split(f(x)) for f in (self.q, self.k, self.v))
75
+ q, k = F.elu(q) + 1, F.elu(k) + 1
76
+ if self.causal:
77
+ S = torch.cumsum(torch.einsum("bhtd,bhte->bhtde", k, v), dim=2)
78
+ z = torch.cumsum(k, dim=2)
79
+ y = torch.einsum("bhtd,bhtde->bhte", q, S) / (torch.einsum("bhtd,bhtd->bht", q, z)[..., None] + 1e-6)
80
+ else:
81
+ y = torch.einsum("bhtd,bhde->bhte", q, torch.einsum("bhtd,bhte->bhde", k, v))
82
+ y = y / (torch.einsum("bhtd,bhd->bht", q, k.sum(2))[..., None] + 1e-6)
83
+ return self.o(y.transpose(1, 2).reshape(B, T, -1))
84
+
85
+
86
+ class TransformerLayer(nn.Module):
87
+ """Pre-norm layer: self-attention (full / local / sparse / linear), optional
88
+ cross-attention to `context`, feed-forward."""
89
+
90
+ def __init__(self, d, n_heads=4, n_kv_heads=None, ff_mult=2, attention="full", window=None,
91
+ stride=None, causal=False, cross=False, dropout=0.0):
92
+ super().__init__()
93
+ if attention == "local" and window is None:
94
+ raise ValueError("attention='local' needs window=")
95
+ if attention == "sparse" and (window is None or stride is None):
96
+ raise ValueError("attention='sparse' needs window= and stride=")
97
+ self.attention, self.causal = attention, causal
98
+ self.window = window if attention in ("local", "sparse") else None
99
+ self.stride = stride if attention == "sparse" else None
100
+ self.attn = (LinearAttention(d, n_heads, causal) if attention == "linear"
101
+ else MultiHeadAttention(d, n_heads, n_kv_heads))
102
+ self.cross = MultiHeadAttention(d, n_heads, n_kv_heads) if cross else None
103
+ self.ln1, self.ln2, self.lnc = nn.LayerNorm(d), nn.LayerNorm(d), nn.LayerNorm(d)
104
+ self.ff = nn.Sequential(nn.Linear(d, ff_mult * d), nn.GELU(), nn.Linear(ff_mult * d, d))
105
+ self.drop = nn.Dropout(dropout)
106
+
107
+ def forward(self, x, context=None, mask=None):
108
+ if mask is None and self.attention != "linear" and (self.causal or self.window or self.stride):
109
+ mask = make_mask(x.shape[1], self.causal, self.window, self.stride, x.device)
110
+ x = x + self.drop(self.attn(self.ln1(x), mask=mask))
111
+ if self.cross is not None and context is not None:
112
+ x = x + self.drop(self.cross(self.lnc(x), context=context))
113
+ return x + self.drop(self.ff(self.ln2(x)))
114
+
115
+
116
+ # ----------------------------------------------------------------------- cores ---
117
+ class TransformerCore(Core):
118
+ def __init__(self, d, n_layers=2, n_heads=4, n_kv_heads=None, attention="full", window=None,
119
+ stride=None, causal=False, dropout=0.0):
120
+ super().__init__()
121
+ self.layers = nn.ModuleList([TransformerLayer(d, n_heads, n_kv_heads, 2, attention, window,
122
+ stride, causal, False, dropout) for _ in range(n_layers)])
123
+ self.ln, self.out_dim = nn.LayerNorm(d), d
124
+
125
+ def forward(self, x):
126
+ x = x + sinusoid(x.shape[1], x.shape[2], x.device)
127
+ for layer in self.layers:
128
+ x = layer(x)
129
+ return self.ln(x)
130
+
131
+
132
+ class UniversalCore(Core):
133
+ """Universal Transformer: one layer applied repeatedly (shared weights, depth recurrence)
134
+ with a per-step embedding. Fixed number of steps (no adaptive halting)."""
135
+
136
+ def __init__(self, d, n_steps=4, n_heads=4, causal=False):
137
+ super().__init__()
138
+ self.layer = TransformerLayer(d, n_heads, causal=causal)
139
+ self.step_emb = nn.Parameter(torch.randn(n_steps, d) * 0.1)
140
+ self.ln, self.out_dim = nn.LayerNorm(d), d
141
+
142
+ def forward(self, x):
143
+ x = x + sinusoid(x.shape[1], x.shape[2], x.device)
144
+ for e in self.step_emb:
145
+ x = self.layer(x + e)
146
+ return self.ln(x)
147
+
148
+
149
+ class MemoryCore(Core):
150
+ """Memory-augmented Transformer: learned memory slots are prepended and take part in
151
+ attention in every layer, giving the model a persistent scratchpad."""
152
+
153
+ def __init__(self, d, n_mem=8, n_layers=2, n_heads=4, causal=False):
154
+ super().__init__()
155
+ self.mem = nn.Parameter(torch.randn(n_mem, d) * 0.1)
156
+ self.layers = nn.ModuleList([TransformerLayer(d, n_heads) for _ in range(n_layers)])
157
+ self.ln, self.out_dim, self.n_mem, self.causal = nn.LayerNorm(d), d, n_mem, causal
158
+
159
+ def forward(self, x):
160
+ B, T, d = x.shape
161
+ n = self.n_mem
162
+ x = torch.cat([self.mem.expand(B, -1, -1), x + sinusoid(T, d, x.device)], 1)
163
+ mask = None
164
+ if self.causal:
165
+ mask = torch.zeros(n + T, n + T, dtype=torch.bool, device=x.device)
166
+ mask[:, :n] = True
167
+ mask[n:, n:] = make_mask(T, True, device=x.device)
168
+ for layer in self.layers:
169
+ x = layer(x, mask=mask)
170
+ return self.ln(x[:, n:])
171
+
172
+
173
+ class PerceiverCore(Core):
174
+ """Perceiver: a small latent array cross-attends to the (possibly long) input, then
175
+ processes it with self-attention. Cost does not grow quadratically with input length."""
176
+ no_all = True
177
+
178
+ def __init__(self, d, n_latents=8, n_layers=2, n_heads=4):
179
+ super().__init__()
180
+ self.latents = nn.Parameter(torch.randn(n_latents, d) * 0.1)
181
+ self.cross, self.ln_l, self.ln_i = MultiHeadAttention(d, n_heads), nn.LayerNorm(d), nn.LayerNorm(d)
182
+ self.ff, self.ln_f = nn.Sequential(nn.Linear(d, 2 * d), nn.GELU(), nn.Linear(2 * d, d)), nn.LayerNorm(d)
183
+ self.layers = nn.ModuleList([TransformerLayer(d, n_heads) for _ in range(n_layers)])
184
+ self.out_dim = d
185
+
186
+ def forward(self, x):
187
+ x = self.ln_i(x + sinusoid(x.shape[1], x.shape[2], x.device))
188
+ lat = self.latents.expand(x.shape[0], -1, -1)
189
+ lat = lat + self.cross(self.ln_l(lat), context=x)
190
+ lat = lat + self.ff(self.ln_f(lat))
191
+ for layer in self.layers:
192
+ lat = layer(lat)
193
+ return lat
194
+
195
+ def pool(self, y, mode):
196
+ return y.mean(1)
197
+
198
+
199
+ class HybridCore(Core):
200
+ """State-space / attention hybrid: Mamba-style blocks alternate with attention layers."""
201
+
202
+ def __init__(self, d, n_layers=2, n_heads=4, state_dim=8, causal=False):
203
+ super().__init__()
204
+ self.ssm = nn.ModuleList([MambaBlock(d, state_dim) for _ in range(n_layers)])
205
+ self.norms = nn.ModuleList([nn.LayerNorm(d) for _ in range(n_layers)])
206
+ self.attn = nn.ModuleList([TransformerLayer(d, n_heads, causal=causal) for _ in range(n_layers)])
207
+ self.out_dim = d
208
+
209
+ def forward(self, x):
210
+ for s, n, a in zip(self.ssm, self.norms, self.attn):
211
+ x = a(x + s(n(x)))
212
+ return x
213
+
214
+
215
+ class RecurrentAttentionCore(Core):
216
+ """Recurrent attention: a GRU controller repeatedly queries the whole input
217
+ ('glimpses'), reading a context vector each step."""
218
+ no_all = True
219
+
220
+ def __init__(self, d, n_glimpses=4, n_heads=4):
221
+ super().__init__()
222
+ self.attn, self.cell = MultiHeadAttention(d, n_heads), nn.GRUCell(d, d)
223
+ self.n_glimpses, self.out_dim, self.d = n_glimpses, d, d
224
+
225
+ def forward(self, x):
226
+ x = x + sinusoid(x.shape[1], x.shape[2], x.device)
227
+ h, hs = torch.zeros(x.shape[0], self.d, device=x.device), []
228
+ for _ in range(self.n_glimpses):
229
+ h = self.cell(self.attn(h.unsqueeze(1), context=x).squeeze(1), h)
230
+ hs.append(h)
231
+ return torch.stack(hs, 1)
232
+
233
+
234
+ # ---------------------------------------------------------------------- models ---
235
+ class _AttnModel(_CoreModel):
236
+ attention, n_kv_heads_default, causal_default = "full", None, False
237
+
238
+ def __init__(self, hidden=32, n_layers=2, n_heads=4, n_kv_heads=None, window=None, stride=None,
239
+ causal=None, dropout=0.0, **kw):
240
+ kw.setdefault("readout", "mean")
241
+ super().__init__(hidden=hidden, **kw)
242
+ self.n_layers, self.n_heads, self.dropout = n_layers, n_heads, dropout
243
+ self.n_kv_heads = n_kv_heads or self.n_kv_heads_default
244
+ self.window, self.stride = window, stride
245
+ self.causal = self.causal_default if causal is None else causal
246
+
247
+ def make_core(self):
248
+ return TransformerCore(self.hidden, self.n_layers, self.n_heads, self.n_kv_heads, self.attention,
249
+ self.window, self.stride, self.causal, self.dropout)
250
+
251
+
252
+ @register("transformer_encoder", family="sequence")
253
+ class TransformerEncoder(_AttnModel):
254
+ """Transformer encoder: bidirectional self-attention with a mean-pooled readout over the sequence."""
255
+ pass
256
+
257
+
258
+ @register("transformer_decoder", family="sequence")
259
+ class TransformerDecoder(_AttnModel):
260
+ """Causal Transformer; use fit(tokens) with vocab_size for language modelling."""
261
+ causal_default = True
262
+
263
+
264
+ @register("linear_attention", family="sequence")
265
+ class LinearAttentionTransformer(_AttnModel):
266
+ """Transformer with linear attention (positive feature map instead of softmax), linear in sequence length."""
267
+ attention = "linear"
268
+
269
+
270
+ @register("local_attention", family="sequence")
271
+ class LocalAttentionTransformer(_AttnModel):
272
+ """Transformer whose attention is restricted to a local window (`window`)."""
273
+ attention = "local"
274
+
275
+ def __init__(self, window=3, **kw):
276
+ super().__init__(window=window, **kw)
277
+
278
+
279
+ @register("sparse_attention", family="sequence")
280
+ class SparseAttentionTransformer(_AttnModel):
281
+ """Transformer with sparse attention: a local window plus every `stride`-th position."""
282
+ attention = "sparse"
283
+
284
+ def __init__(self, window=2, stride=4, **kw):
285
+ super().__init__(window=window, stride=stride, **kw)
286
+
287
+
288
+ @register("multi_query", family="sequence")
289
+ class MultiQueryTransformer(_AttnModel):
290
+ """Transformer with multi-query attention: all query heads share one key/value head."""
291
+ n_kv_heads_default = 1
292
+
293
+
294
+ @register("grouped_query", family="sequence")
295
+ class GroupedQueryTransformer(_AttnModel):
296
+ """Transformer with grouped-query attention: query heads share `n_kv_heads` key/value heads (2 by default)."""
297
+ n_kv_heads_default = 2
298
+
299
+
300
+ class _OwnCore(_CoreModel):
301
+ def __init__(self, hidden=32, n_layers=2, n_heads=4, causal=False, **kw):
302
+ kw.setdefault("readout", "mean")
303
+ super().__init__(hidden=hidden, **kw)
304
+ self.n_layers, self.n_heads, self.causal = n_layers, n_heads, causal
305
+
306
+
307
+ @register("universal_transformer", family="sequence")
308
+ class UniversalTransformer(_OwnCore):
309
+ """Universal Transformer: one shared layer applied repeatedly (`n_steps`)."""
310
+ def __init__(self, n_steps=4, **kw):
311
+ super().__init__(**kw)
312
+ self.n_steps = n_steps
313
+
314
+ def make_core(self):
315
+ return UniversalCore(self.hidden, self.n_steps, self.n_heads, self.causal)
316
+
317
+
318
+ @register("memory_transformer", family="sequence")
319
+ class MemoryAugmentedTransformer(_OwnCore):
320
+ """Memory-augmented Transformer: learned memory slots (`n_memory`) take part in attention."""
321
+ def __init__(self, n_memory=8, **kw):
322
+ super().__init__(**kw)
323
+ self.n_memory = n_memory
324
+
325
+ def make_core(self):
326
+ return MemoryCore(self.hidden, self.n_memory, self.n_layers, self.n_heads, self.causal)
327
+
328
+
329
+ @register("perceiver", family="sequence")
330
+ class Perceiver(_OwnCore):
331
+ """Perceiver: a small latent array (`n_latents`) cross-attends to the input."""
332
+ def __init__(self, n_latents=8, **kw):
333
+ super().__init__(**kw)
334
+ self.n_latents = n_latents
335
+
336
+ def make_core(self):
337
+ return PerceiverCore(self.hidden, self.n_latents, self.n_layers, self.n_heads)
338
+
339
+
340
+ @register("ssm_attention_hybrid", family="sequence")
341
+ class SSMAttentionHybrid(_OwnCore):
342
+ """Hybrid of Mamba-style state-space blocks and attention layers."""
343
+ def make_core(self):
344
+ return HybridCore(self.hidden, self.n_layers, self.n_heads, causal=self.causal)
345
+
346
+
347
+ @register("recurrent_attention", family="sequence")
348
+ class RecurrentAttention(_OwnCore):
349
+ """Recurrent attention: a GRU controller repeatedly queries the whole input (`n_glimpses`)."""
350
+ def __init__(self, n_glimpses=4, **kw):
351
+ super().__init__(**kw)
352
+ self.n_glimpses = n_glimpses
353
+
354
+ def make_core(self):
355
+ return RecurrentAttentionCore(self.hidden, self.n_glimpses, self.n_heads)
356
+
357
+
358
+ # ------------------------------------------------------------ retrieval-augmented ---
359
+ class RetrievalNet(nn.Module):
360
+ """The input sequence (mean-pooled) is a query into an external key/value knowledge
361
+ base; the top-k entries are retrieved by cosine similarity and read by cross-attention."""
362
+
363
+ def __init__(self, d_in, hidden, d_out, keys, values, k, n_heads):
364
+ super().__init__()
365
+ self.register_buffer("keys", F.normalize(torch.as_tensor(keys, dtype=torch.float32), dim=1))
366
+ self.register_buffer("values", torch.as_tensor(values, dtype=torch.float32))
367
+ self.q_embed = nn.Linear(d_in, hidden)
368
+ self.v_embed = nn.Linear(self.values.shape[1], hidden)
369
+ self.read = MultiHeadAttention(hidden, n_heads)
370
+ self.head = nn.Sequential(nn.Linear(2 * hidden, hidden), nn.GELU(), nn.Linear(hidden, d_out))
371
+ self.k = min(k, len(self.keys))
372
+
373
+ def forward(self, x):
374
+ q = x.mean(1)
375
+ idx = (F.normalize(q, dim=1) @ self.keys.T).topk(self.k, dim=1).indices # (B, k)
376
+ retrieved = self.v_embed(self.values[idx]) # (B, k, hidden)
377
+ qe = self.q_embed(q).unsqueeze(1)
378
+ return self.head(torch.cat([qe, self.read(qe, context=retrieved)], -1).squeeze(1))
379
+
380
+
381
+ @register("retrieval_augmented", family="sequence")
382
+ class RetrievalAugmented(SequenceModel):
383
+ """Retrieval-augmented model. `knowledge=(keys, values)`: keys (M, d_in) live in the same
384
+ space as the (unnormalised) input features, values (M, d_v) are what the model may read."""
385
+ def __init__(self, knowledge, hidden=32, k=3, n_heads=4, **kw):
386
+ kw.setdefault("normalize", False)
387
+ super().__init__(hidden=hidden, **kw)
388
+ self.knowledge, self.k, self.n_heads = knowledge, k, n_heads
389
+
390
+ def build(self, d_in, d_out):
391
+ keys, values = self.knowledge
392
+ return RetrievalNet(d_in, self.hidden, d_out, keys, values, self.k, self.n_heads)
393
+
394
+
395
+ # --------------------------------------------------------- encoder-decoder model ---
396
+ class Seq2SeqNet(nn.Module):
397
+ def __init__(self, vocab, d=32, n_layers=2, n_heads=4):
398
+ super().__init__()
399
+ self.src_emb, self.tgt_emb = nn.Embedding(vocab, d), nn.Embedding(vocab + 1, d)
400
+ self.enc = TransformerCore(d, n_layers, n_heads)
401
+ self.dec = nn.ModuleList([TransformerLayer(d, n_heads, causal=True, cross=True) for _ in range(n_layers)])
402
+ self.ln, self.head, self.bos = nn.LayerNorm(d), nn.Linear(d, vocab), vocab
403
+
404
+ def forward(self, src, tgt_in):
405
+ mem = self.enc(self.src_emb(src))
406
+ x = self.tgt_emb(tgt_in)
407
+ x = x + sinusoid(x.shape[1], x.shape[2], x.device)
408
+ for layer in self.dec:
409
+ x = layer(x, context=mem)
410
+ return self.head(self.ln(x))
411
+
412
+ @torch.no_grad()
413
+ def decode(self, src, length):
414
+ tgt = torch.full((len(src), 1), self.bos, device=src.device)
415
+ for _ in range(length):
416
+ tgt = torch.cat([tgt, self(src, tgt)[:, -1].argmax(-1, keepdim=True)], 1)
417
+ return tgt[:, 1:]
418
+
419
+
420
+ @register("encoder_decoder", family="sequence")
421
+ class EncoderDecoderTransformer(SequenceModel):
422
+ """Sequence-to-sequence Transformer over integer tokens: fit(src, tgt), predict(src)."""
423
+ def __init__(self, vocab_size, hidden=32, n_layers=2, n_heads=4, **kw):
424
+ super().__init__(hidden=hidden, vocab_size=vocab_size, **kw)
425
+ self.n_layers, self.n_heads, self.tgt_len = n_layers, n_heads, None
426
+
427
+ def build(self, d_in, d_out):
428
+ return Seq2SeqNet(self.vocab_size, self.hidden, self.n_layers, self.n_heads)
429
+
430
+ def fit(self, X, y, epochs=None, val_data=None):
431
+ y = np.asarray(y)
432
+ self.tgt_len, self.task = y.shape[1], "classification"
433
+ self._declared_classes = np.arange(self.vocab_size)
434
+ return super().fit(X, y, epochs=epochs)
435
+
436
+ def _train_batch(self, m, opt, xb, yb):
437
+ tgt_in = torch.cat([torch.full((len(yb), 1), m.bos, device=yb.device), yb[:, :-1]], 1)
438
+ opt.zero_grad()
439
+ loss = self._loss(m(xb, tgt_in), yb)
440
+ loss.backward()
441
+ opt.step()
442
+ return loss.item()
443
+
444
+ def predict(self, X):
445
+ self._check_fitted()
446
+ self._module.eval()
447
+ return self._module.decode(self._norm(self._as_x(X)), self.tgt_len).cpu().numpy()
448
+
449
+ def evaluate(self, X, y):
450
+ pred, y = self.predict(X), np.asarray(y)
451
+ return {"token_accuracy": float((pred == y).mean()), "sequence_accuracy": float((pred == y).all(1).mean())}