PyIntell 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.
@@ -0,0 +1,313 @@
1
+ """Model serialization and named-model management."""
2
+
3
+ import json
4
+ import os
5
+ import pickle
6
+ from pathlib import Path
7
+
8
+ _REGISTRY_PATH = Path(os.path.expanduser("~/.pyintell/models.json"))
9
+ _CURRENT_MODEL = None
10
+
11
+
12
+ def set_current_model(model):
13
+ """Set the model used by save_model when no explicit model is supplied."""
14
+ global _CURRENT_MODEL
15
+ _CURRENT_MODEL = model
16
+ return model
17
+
18
+
19
+ def state_dict(model):
20
+ return {
21
+ name: value.copy()
22
+ for name, value in vars(model).items()
23
+ if hasattr(value, "shape") and hasattr(value, "copy")
24
+ }
25
+
26
+
27
+ def load_state_dict(model, state):
28
+ for name, value in state.items():
29
+ setattr(model, name, value)
30
+ return model
31
+
32
+
33
+ def save_weights(model, path):
34
+ import numpy as np
35
+ np.savez(path, **state_dict(model))
36
+
37
+
38
+ def load_weights(model, path):
39
+ import numpy as np
40
+ with np.load(path) as data:
41
+ return load_state_dict(model, {key: data[key] for key in data.files})
42
+
43
+
44
+ def _read_registry():
45
+ try:
46
+ with _REGISTRY_PATH.open("r", encoding="utf-8") as file:
47
+ data = json.load(file)
48
+ return data if isinstance(data, dict) else {}
49
+ except (FileNotFoundError, json.JSONDecodeError, OSError):
50
+ return {}
51
+
52
+
53
+ def _write_registry(registry):
54
+ _REGISTRY_PATH.parent.mkdir(parents=True, exist_ok=True)
55
+ temporary = _REGISTRY_PATH.with_suffix(".tmp")
56
+ with temporary.open("w", encoding="utf-8") as file:
57
+ json.dump(registry, file, ensure_ascii=False, indent=2)
58
+ os.replace(temporary, _REGISTRY_PATH)
59
+
60
+
61
+ def _validate_model_name(model_name):
62
+ if not isinstance(model_name, str) or not model_name.strip():
63
+ raise TypeError("model_name must be a non-empty string")
64
+ name = model_name.strip()
65
+ if name in {".", ".."} or any(char in name for char in "\\/"):
66
+ raise ValueError("model_name must not contain path separators")
67
+ return name
68
+
69
+
70
+ def _model_file(directory, name):
71
+ """Return the canonical saved-model path inside a directory."""
72
+ directory = Path(directory).expanduser()
73
+ directory.mkdir(parents=True, exist_ok=True)
74
+ if not directory.is_dir():
75
+ raise NotADirectoryError(f"path must be a directory: {directory}")
76
+ return directory / f"{name}.pyintell"
77
+
78
+
79
+ def save_model(model_name, path=None, model=None):
80
+ """Save a model under a unique name.
81
+
82
+ ``path`` is the directory where ``<model_name>.pyintell`` is created.
83
+ If ``model`` is omitted, the most recently built/loaded model is used.
84
+ """
85
+ global _CURRENT_MODEL
86
+ name = _validate_model_name(model_name)
87
+ if model is None:
88
+ model = _CURRENT_MODEL
89
+ if model is None:
90
+ raise RuntimeError("no active model; build or load a model first")
91
+ if path is None:
92
+ raise TypeError("path is required")
93
+
94
+ registry = _read_registry()
95
+ if name in registry:
96
+ raise FileExistsError(f"model name '{name}' already exists")
97
+
98
+ model_path = _model_file(path, name)
99
+ if model_path.exists():
100
+ raise FileExistsError(f"model file already exists: {model_path}")
101
+
102
+ # Store the name on the model so the active-model state is unambiguous.
103
+ model.model_name = name
104
+
105
+ temporary = model_path.with_suffix(model_path.suffix + ".tmp")
106
+ try:
107
+ with temporary.open("wb") as file:
108
+ pickle.dump(model, file, protocol=pickle.HIGHEST_PROTOCOL)
109
+ os.replace(temporary, model_path)
110
+
111
+ registry[name] = {"path": str(model_path.resolve())}
112
+ try:
113
+ _write_registry(registry)
114
+ except Exception:
115
+ try:
116
+ model_path.unlink()
117
+ except OSError:
118
+ pass
119
+ raise
120
+ finally:
121
+ try:
122
+ temporary.unlink()
123
+ except OSError:
124
+ pass
125
+
126
+ _CURRENT_MODEL = model
127
+ return str(model_path)
128
+
129
+
130
+ def load_model(model_name):
131
+ """Load a named model and make it the active model."""
132
+ global _CURRENT_MODEL
133
+ name = _validate_model_name(model_name)
134
+ registry = _read_registry()
135
+ if name not in registry:
136
+ raise FileNotFoundError(f"model '{name}' was not found")
137
+
138
+ model_path = Path(registry[name].get("path", "")).expanduser()
139
+ if not model_path.is_file():
140
+ raise FileNotFoundError(f"model file for '{name}' no longer exists: {model_path}")
141
+
142
+ with model_path.open("rb") as file:
143
+ model = pickle.load(file)
144
+ model.model_name = name
145
+ _CURRENT_MODEL = model
146
+ return model
147
+
148
+
149
+ def get_model(model_name):
150
+ """Load a named model without changing the active model."""
151
+ name = _validate_model_name(model_name)
152
+ registry = _read_registry()
153
+ if name not in registry:
154
+ raise FileNotFoundError(f"model '{name}' was not found")
155
+ model_path = Path(registry[name].get("path", "")).expanduser()
156
+ if not model_path.is_file():
157
+ raise FileNotFoundError(f"model file for '{name}' no longer exists: {model_path}")
158
+ with model_path.open("rb") as file:
159
+ model = pickle.load(file)
160
+ model.model_name = name
161
+ return model
162
+
163
+
164
+ def delete_model(model_name):
165
+ """Delete a named model from disk and remove it from the registry."""
166
+ global _CURRENT_MODEL
167
+ name = _validate_model_name(model_name)
168
+ registry = _read_registry()
169
+ if name not in registry:
170
+ raise FileNotFoundError(f"model '{name}' was not found")
171
+
172
+ model_path = Path(registry[name].get("path", "")).expanduser()
173
+ if not model_path.is_file():
174
+ del registry[name]
175
+ _write_registry(registry)
176
+ raise FileNotFoundError(f"model file for '{name}' no longer exists: {model_path}")
177
+
178
+ model_path.unlink()
179
+ del registry[name]
180
+ _write_registry(registry)
181
+
182
+ if _CURRENT_MODEL is not None and getattr(_CURRENT_MODEL, "model_name", None) == name:
183
+ _CURRENT_MODEL = None
184
+ return True
185
+
186
+
187
+ def edit_model(model_name, **changes):
188
+ """Edit supported metadata of a saved model."""
189
+ global _CURRENT_MODEL
190
+ name = _validate_model_name(model_name)
191
+ registry = _read_registry()
192
+ if name not in registry:
193
+ raise FileNotFoundError(f"model '{name}' was not found")
194
+ if not changes:
195
+ raise ValueError("at least one model field must be provided")
196
+
197
+ model_path = Path(registry[name].get("path", "")).expanduser()
198
+ if not model_path.is_file():
199
+ raise FileNotFoundError(f"model file for '{name}' no longer exists: {model_path}")
200
+
201
+ with model_path.open("rb") as file:
202
+ model = pickle.load(file)
203
+
204
+ allowed = {"focus", "parameters", "settings", "model_name"}
205
+ unknown = set(changes) - allowed
206
+ if unknown:
207
+ raise TypeError(f"unsupported model field(s): {', '.join(sorted(unknown))}")
208
+
209
+ if "focus" in changes:
210
+ focus = changes["focus"]
211
+ if isinstance(focus, str):
212
+ model.focus = (focus,)
213
+ elif isinstance(focus, (list, tuple, set)) and focus:
214
+ model.focus = tuple(focus)
215
+ else:
216
+ raise TypeError("focus must be a non-empty string, list, tuple, or set")
217
+
218
+ if "parameters" in changes:
219
+ parameters = changes["parameters"]
220
+ if isinstance(parameters, bool) or not isinstance(parameters, (int, float)):
221
+ raise TypeError("parameters must be a number")
222
+ if parameters < 0:
223
+ raise ValueError("parameters must not be negative")
224
+ model.parameters = int(parameters)
225
+
226
+ if "settings" in changes:
227
+ settings = changes["settings"]
228
+ if not isinstance(settings, dict):
229
+ raise TypeError("settings must be a dictionary")
230
+ protected = {"layers", "heads", "embedding_size", "hidden_size", "context_length"}
231
+ changed_architecture = protected.intersection(settings)
232
+ if changed_architecture:
233
+ raise ValueError("cannot edit architecture settings in place; rebuild the model instead")
234
+ model.settings.update(settings)
235
+
236
+ new_name = name
237
+ if "model_name" in changes:
238
+ new_name = _validate_model_name(changes["model_name"])
239
+ if new_name != name and new_name in registry:
240
+ raise FileExistsError(f"model name '{new_name}' already exists")
241
+ new_path = model_path.with_name(f"{new_name}.pymodel")
242
+ if new_path != model_path and new_path.exists():
243
+ raise FileExistsError(f"model file already exists: {new_path}")
244
+ else:
245
+ new_path = model_path
246
+
247
+ model.model_name = new_name
248
+ temporary = new_path.with_suffix(new_path.suffix + ".tmp")
249
+ with temporary.open("wb") as file:
250
+ pickle.dump(model, file, protocol=pickle.HIGHEST_PROTOCOL)
251
+ os.replace(temporary, new_path)
252
+
253
+ if new_name != name:
254
+ del registry[name]
255
+ registry[new_name] = {"path": str(new_path.resolve())}
256
+ if model_path != new_path and model_path.exists():
257
+ model_path.unlink()
258
+ else:
259
+ registry[name] = {"path": str(new_path.resolve())}
260
+
261
+ _write_registry(registry)
262
+ _CURRENT_MODEL = model
263
+ return model
264
+
265
+
266
+ def save(model, path):
267
+ """Backward-compatible anonymous model save."""
268
+ with open(path, "wb") as file:
269
+ pickle.dump(model, file, protocol=pickle.HIGHEST_PROTOCOL)
270
+
271
+
272
+ def load(path):
273
+ """Backward-compatible anonymous model load."""
274
+ with open(path, "rb") as file:
275
+ return pickle.load(file)
276
+
277
+
278
+ def checkpoint(model, path):
279
+ return save(model, path)
280
+
281
+
282
+ def save_checkpoint(model, path):
283
+ return save(model, path)
284
+
285
+
286
+ def load_checkpoint(path):
287
+ return load(path)
288
+
289
+
290
+ def save_vocab(vocabulary, path):
291
+ with open(path, "w", encoding="utf-8") as file:
292
+ json.dump(vocabulary, file, ensure_ascii=False, indent=2)
293
+
294
+
295
+ def load_vocab(path):
296
+ with open(path, "r", encoding="utf-8") as file:
297
+ return json.load(file)
298
+
299
+
300
+ def serialize(value):
301
+ return pickle.dumps(value)
302
+
303
+
304
+ def deserialize(value):
305
+ return pickle.loads(value)
306
+
307
+
308
+ def export_model(model, path):
309
+ return save(model, path)
310
+
311
+
312
+ def import_model(path):
313
+ return load(path)
pyintell/system.py ADDED
@@ -0,0 +1,122 @@
1
+ """System resource inspection used by model builders."""
2
+
3
+ import os
4
+ import platform
5
+ import shutil
6
+
7
+ try:
8
+ import psutil
9
+ except ImportError:
10
+ psutil = None
11
+
12
+
13
+ def ram():
14
+ """Return total, available, and used system RAM in bytes."""
15
+ if psutil is not None:
16
+ try:
17
+ info = psutil.virtual_memory()
18
+ return {"total": info.total, "available": info.available, "used": info.used}
19
+ except Exception:
20
+ pass
21
+
22
+ # Android/Pydroid may not expose virtual memory through psutil.
23
+ try:
24
+ values = {}
25
+ with open("/proc/meminfo", "r", encoding="utf-8") as file:
26
+ for line in file:
27
+ key, value = line.split(":", 1)
28
+ parts = value.strip().split()
29
+ if parts:
30
+ values[key] = int(parts[0]) * 1024
31
+
32
+ total = values.get("MemTotal")
33
+ available = values.get("MemAvailable")
34
+ if available is None:
35
+ available = values.get("MemFree", 0) + values.get("Buffers", 0) + values.get("Cached", 0)
36
+ used = total - available if total is not None and available is not None else None
37
+ return {"total": total, "available": available, "used": used}
38
+ except (OSError, ValueError, IndexError):
39
+ return {"total": None, "available": None, "used": None}
40
+
41
+
42
+ def cpu_info():
43
+ return {"name": platform.processor(), "count": os.cpu_count(), "architecture": platform.machine()}
44
+
45
+
46
+ def storage_info(path="."):
47
+ usage = shutil.disk_usage(path)
48
+ return {"total": usage.total, "free": usage.free, "used": usage.used}
49
+
50
+
51
+ def memory_info():
52
+ process = None
53
+ if psutil is not None:
54
+ try:
55
+ process = psutil.Process(os.getpid()).memory_info().rss
56
+ except Exception:
57
+ pass
58
+ return {"ram": ram(), "process": process}
59
+
60
+
61
+ def device_info():
62
+ return {"cpu": cpu_info(), "gpu": gpu_info()}
63
+
64
+
65
+ def device_count():
66
+ return os.cpu_count() or 1
67
+
68
+
69
+ def gpu_info():
70
+ try:
71
+ import torch
72
+ if torch.cuda.is_available():
73
+ return {"available": True, "count": torch.cuda.device_count(), "name": torch.cuda.get_device_name(0)}
74
+ except Exception:
75
+ pass
76
+ return {"available": False, "count": 0, "name": None}
77
+
78
+
79
+ def is_gpu_available():
80
+ return bool(gpu_info()["available"])
81
+
82
+
83
+ def cuda():
84
+ return is_gpu_available()
85
+
86
+
87
+ def cpu():
88
+ return True
89
+
90
+
91
+ def clear_cache():
92
+ try:
93
+ import torch
94
+ if torch.cuda.is_available():
95
+ torch.cuda.empty_cache()
96
+ except Exception:
97
+ pass
98
+
99
+
100
+ def free_memory():
101
+ return ram().get("available")
102
+
103
+
104
+ def gpu_memory():
105
+ return gpu_info()
106
+
107
+
108
+ def cpu_memory():
109
+ return ram()
110
+
111
+
112
+ def system_info():
113
+ return {
114
+ "platform": platform.platform(),
115
+ "system": platform.system(),
116
+ "machine": platform.machine(),
117
+ "processor": platform.processor(),
118
+ "cpu_count": os.cpu_count(),
119
+ "ram": ram(),
120
+ "storage": storage_info(),
121
+ "gpu": gpu_info(),
122
+ }
@@ -0,0 +1,147 @@
1
+ """Basic vocabulary and tokenization utilities."""
2
+
3
+
4
+ def tokenizer(text):
5
+ if not isinstance(text, str):
6
+ raise TypeError("text must be a string")
7
+ return text.split()
8
+
9
+
10
+ def tokenize(text):
11
+ return tokenizer(text)
12
+
13
+
14
+ def detokenize(tokens):
15
+ return " ".join(tokens)
16
+
17
+
18
+ def vocab(tokens):
19
+ if isinstance(tokens, str):
20
+ tokens = tokenizer(tokens)
21
+ if tokens and isinstance(tokens[0], (list, tuple)):
22
+ tokens = [t for seq in tokens for t in seq]
23
+ result = {}
24
+ for token in tokens:
25
+ if token not in result:
26
+ result[token] = len(result)
27
+ return result
28
+
29
+
30
+ def build_vocab(tokens):
31
+ return vocab(tokens)
32
+
33
+
34
+ def update_vocab(vocabulary, tokens):
35
+ result = dict(vocabulary)
36
+ new = vocab(tokens)
37
+ for token in new:
38
+ if token not in result:
39
+ result[token] = len(result)
40
+ return result
41
+
42
+
43
+ def merge_vocab(*vocabularies):
44
+ result = {}
45
+ for vocabulary in vocabularies:
46
+ for token in vocabulary:
47
+ if token not in result:
48
+ result[token] = len(result)
49
+ return result
50
+
51
+
52
+ def reverse_vocab(vocabulary):
53
+ return {index: token for token, index in vocabulary.items()}
54
+
55
+
56
+ def vocab_size(vocabulary):
57
+ return len(vocabulary)
58
+
59
+
60
+ def token_id(token, vocabulary, default=None):
61
+ return vocabulary.get(token, default)
62
+
63
+
64
+ def id_token(index, reverse):
65
+ return reverse[index]
66
+
67
+
68
+ def token_exists(token, vocabulary):
69
+ return token in vocabulary
70
+
71
+
72
+ def add_token(vocabulary, token):
73
+ result = dict(vocabulary)
74
+ if token not in result:
75
+ result[token] = len(result)
76
+ return result
77
+
78
+
79
+ def remove_token(vocabulary, token):
80
+ result = dict(vocabulary)
81
+ result.pop(token, None)
82
+ return result
83
+
84
+
85
+ def special_tokens(pad="<PAD>", unk="<UNK>", bos="<BOS>", eos="<EOS>"):
86
+ return {"pad": pad, "unk": unk, "bos": bos, "eos": eos}
87
+
88
+
89
+ def add_special_token(vocabulary, token):
90
+ return add_token(vocabulary, token)
91
+
92
+
93
+ def encode(text, vocabulary, unknown_token=None):
94
+ tokens = tokenizer(text)
95
+ unknown_id = vocabulary.get(unknown_token) if unknown_token is not None else None
96
+ return [vocabulary[token] if token in vocabulary else unknown_id for token in tokens]
97
+
98
+
99
+ def decode(ids, vocabulary):
100
+ reverse = vocabulary if vocabulary and all(isinstance(key, int) for key in vocabulary) else reverse_vocab(vocabulary)
101
+ return " ".join(reverse[index] for index in ids)
102
+
103
+
104
+ def encode_batch(texts, vocabulary, unknown_token=None):
105
+ return [encode(text, vocabulary, unknown_token) for text in texts]
106
+
107
+
108
+ def decode_batch(ids, reverse):
109
+ return [decode(item, reverse) for item in ids]
110
+
111
+
112
+ def tokenize_batch(texts):
113
+ return [tokenizer(text) for text in texts]
114
+
115
+
116
+ def detokenize_batch(batch):
117
+ return [detokenize(tokens) for tokens in batch]
118
+
119
+
120
+ def normalize_text(text):
121
+ return " ".join(str(text).split())
122
+
123
+
124
+ def clean_text(text):
125
+ return normalize_text(text)
126
+
127
+
128
+ def split_text(text, separator=None):
129
+ return str(text).split(separator)
130
+
131
+
132
+ def truncate(text, length):
133
+ return str(text)[:length]
134
+
135
+
136
+ def pad_sequence(sequence, length, value=0, pad_id=None):
137
+ """Pad or truncate a sequence to ``length``.
138
+
139
+ ``value`` is the original padding argument. ``pad_id`` is supported as
140
+ an API-compatible alias used by the public test suite and is preferred
141
+ when supplied.
142
+ """
143
+ if pad_id is not None:
144
+ value = pad_id
145
+
146
+ result = list(sequence)[:length]
147
+ return result + [value] * max(0, length - len(result))
pyintell/training.py ADDED
@@ -0,0 +1,15 @@
1
+ """Training helpers."""
2
+
3
+
4
+ def train(model, dataset, epochs=1, **kwargs):
5
+ """Run a model's training method when available."""
6
+ if hasattr(model, "train"):
7
+ return model.train(dataset, epochs=epochs, **kwargs)
8
+ raise TypeError("model must provide a train() method")
9
+
10
+
11
+ def evaluate(model, dataset, **kwargs):
12
+ """Evaluate a model using its evaluate() method."""
13
+ if hasattr(model, "evaluate"):
14
+ return model.evaluate(dataset, **kwargs)
15
+ raise TypeError("model must provide an evaluate() method")
@@ -0,0 +1,90 @@
1
+ """Persistent, parameterized Transformer building blocks."""
2
+
3
+ import numpy as np
4
+
5
+ from .attention import multihead_attention, cross_attention, causal_mask
6
+
7
+
8
+ def _norm(x, eps=1e-5):
9
+ x = np.asarray(x, dtype=np.float32)
10
+ return (x - x.mean(axis=-1, keepdims=True)) / np.sqrt(x.var(axis=-1, keepdims=True) + eps)
11
+
12
+
13
+ def _gelu(x):
14
+ return 0.5 * x * (1.0 + np.tanh(np.sqrt(2.0 / np.pi) * (x + 0.044715 * x ** 3)))
15
+
16
+
17
+ def init_block(d_model, heads, hidden_size, rng=None):
18
+ """Create one persistent Transformer block state."""
19
+ rng = np.random.default_rng() if rng is None else rng
20
+ scale = 1.0 / np.sqrt(max(d_model, 1))
21
+ return {
22
+ "q": (rng.standard_normal((d_model, d_model)) * scale).astype(np.float32),
23
+ "k": (rng.standard_normal((d_model, d_model)) * scale).astype(np.float32),
24
+ "v": (rng.standard_normal((d_model, d_model)) * scale).astype(np.float32),
25
+ "o": (rng.standard_normal((d_model, d_model)) * scale).astype(np.float32),
26
+ "ff1": (rng.standard_normal((d_model, hidden_size)) * scale).astype(np.float32),
27
+ "ff2": (rng.standard_normal((hidden_size, d_model)) * (1.0 / np.sqrt(hidden_size))).astype(np.float32),
28
+ "ff1_bias": np.zeros(hidden_size, dtype=np.float32),
29
+ "ff2_bias": np.zeros(d_model, dtype=np.float32),
30
+ "q_bias": np.zeros(d_model, dtype=np.float32),
31
+ "k_bias": np.zeros(d_model, dtype=np.float32),
32
+ "v_bias": np.zeros(d_model, dtype=np.float32),
33
+ "o_bias": np.zeros(d_model, dtype=np.float32),
34
+ "norm1_scale": np.ones(d_model, dtype=np.float32),
35
+ "norm1_bias": np.zeros(d_model, dtype=np.float32),
36
+ "norm2_scale": np.ones(d_model, dtype=np.float32),
37
+ "norm2_bias": np.zeros(d_model, dtype=np.float32),
38
+ }
39
+
40
+
41
+ def transformer_block(x, heads=8, hidden_size=None, causal=True, weights=None):
42
+ """Apply one pre-norm Transformer block."""
43
+ d_model = x.shape[-1]
44
+ hidden_size = int(hidden_size or d_model * 4)
45
+ if weights is None:
46
+ weights = init_block(d_model, heads, hidden_size)
47
+ n1 = _norm(x) * weights["norm1_scale"] + weights["norm1_bias"]
48
+ mask = causal_mask(x.shape[-2]) if causal else None
49
+ x = x + multihead_attention(n1, heads=heads, mask=mask, weights=weights)
50
+ n2 = _norm(x) * weights["norm2_scale"] + weights["norm2_bias"]
51
+ ff = _gelu(n2 @ weights["ff1"] + weights["ff1_bias"]) @ weights["ff2"] + weights["ff2_bias"]
52
+ return x + ff
53
+
54
+
55
+ def transformer(x, layers=4, heads=8, hidden_size=None, causal=True, weights=None):
56
+ """Stack persistent Transformer blocks."""
57
+ hidden_size = int(hidden_size or x.shape[-1] * 4)
58
+ if weights is None:
59
+ weights = [init_block(x.shape[-1], heads, hidden_size) for _ in range(int(layers))]
60
+ if len(weights) != int(layers):
61
+ raise ValueError("number of Transformer weight sets must equal layers")
62
+ for block in weights:
63
+ x = transformer_block(x, heads=heads, hidden_size=hidden_size, causal=causal, weights=block)
64
+ return _norm(x)
65
+
66
+
67
+ def encoder(x, layers=4, heads=8, hidden_size=None, weights=None):
68
+ for _ in []:
69
+ pass
70
+ hidden_size = int(hidden_size or x.shape[-1] * 4)
71
+ if weights is None:
72
+ weights = [init_block(x.shape[-1], heads, hidden_size) for _ in range(int(layers))]
73
+ for block in weights:
74
+ x = transformer_block(x, heads=heads, hidden_size=hidden_size, causal=False, weights=block)
75
+ return _norm(x)
76
+
77
+
78
+ def decoder(x, context=None, memory=None, layers=4, heads=8, hidden_size=None, weights=None):
79
+ if context is not None and memory is not None:
80
+ raise ValueError("provide either context or memory, not both")
81
+ if memory is not None:
82
+ context = memory
83
+ hidden_size = int(hidden_size or x.shape[-1] * 4)
84
+ if weights is None:
85
+ weights = [init_block(x.shape[-1], heads, hidden_size) for _ in range(int(layers))]
86
+ for block in weights:
87
+ x = transformer_block(x, heads=heads, hidden_size=hidden_size, causal=True, weights=block)
88
+ if context is not None:
89
+ x = x + cross_attention(_norm(x), context)
90
+ return _norm(x)