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
|
File without changes
|
|
@@ -0,0 +1,401 @@
|
|
|
1
|
+
"""
|
|
2
|
+
file: data/multimodal/multimodal_dataset.py
|
|
3
|
+
|
|
4
|
+
Fuses two or more of {text, audio, video} chain datasets. The SAME (key,
|
|
5
|
+
value) facts/queries draw is shown through EACH active modality's own
|
|
6
|
+
encoding channel simultaneously, concatenated into one wider per-timestep
|
|
7
|
+
input vector (input_dim = sum of active modalities' input_dims). This is
|
|
8
|
+
what makes "which modality does the model actually use" answerable via a
|
|
9
|
+
direct ablation: zero one modality's channel slice and re-evaluate -
|
|
10
|
+
Delta_<modality>, the Dependency-layer (Layer C) analogue of
|
|
11
|
+
evaluate_id_ablated (memory) / evaluate_id_combiner_stage_ablated
|
|
12
|
+
(component), for modalities.
|
|
13
|
+
|
|
14
|
+
Only the FIRST listed modality's targets are scored ("primary") - this
|
|
15
|
+
tests whether auxiliary modalities help/are relied upon for the primary
|
|
16
|
+
modality's own recall+chain task, not multi-task multi-output prediction.
|
|
17
|
+
|
|
18
|
+
Real-data wiring: each modality in the `modalities` list passed to
|
|
19
|
+
__init__ may be a bare name (synthetic, unchanged default) or
|
|
20
|
+
"modality:path" to source that modality's own real fact pool from `path`
|
|
21
|
+
via its existing single-modality real-data pipeline (data/common/
|
|
22
|
+
real_data.py) -- see _parse_modality_specs() below. Repeating the SAME
|
|
23
|
+
path across modalities (e.g. "video:clip.mp4+audio:clip.mp4") draws both
|
|
24
|
+
modalities' real content from one shared file, such as a video that also
|
|
25
|
+
carries an audio track -- the flagship "upload one video, learn its
|
|
26
|
+
picture AND its soundtrack" case. See MultimodalDataset.__init__'s
|
|
27
|
+
`link_role` for how training vs. inference (MultimodalTask) route a
|
|
28
|
+
spec's path to the right underlying *ChainDataset kwarg.
|
|
29
|
+
"""
|
|
30
|
+
|
|
31
|
+
import random
|
|
32
|
+
|
|
33
|
+
import torch
|
|
34
|
+
|
|
35
|
+
from initium.data.audio.audio_dataset import AudioChainDataset
|
|
36
|
+
from initium.data.base_dataset import BaseDataset
|
|
37
|
+
from initium.data.common.chain_task import (
|
|
38
|
+
ChainCurriculum,
|
|
39
|
+
score_answer_steps,
|
|
40
|
+
value_cumsum_field_log_header,
|
|
41
|
+
write_value_cumsum_field_log,
|
|
42
|
+
)
|
|
43
|
+
from initium.data.text.text_dataset import TextChainDataset
|
|
44
|
+
from initium.data.video.video_dataset import VideoChainDataset
|
|
45
|
+
|
|
46
|
+
_MODALITY_CLASSES = {
|
|
47
|
+
"text": TextChainDataset,
|
|
48
|
+
"audio": AudioChainDataset,
|
|
49
|
+
"video": VideoChainDataset,
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
|
|
53
|
+
def _parse_modality_specs(modalities):
|
|
54
|
+
"""Each entry of `modalities` is either a bare modality name ("text",
|
|
55
|
+
"audio", "video") -- synthetic facts, unchanged default -- or
|
|
56
|
+
"modality:path" to source that modality's OWN real (key,value) fact
|
|
57
|
+
pool from `path`, via the exact same per-modality real-data pipeline
|
|
58
|
+
single-modality datasets already use (data/common/real_data.py). The
|
|
59
|
+
SAME path can be given to more than one modality -- e.g.
|
|
60
|
+
"video:clip.mp4+audio:clip.mp4" -- to draw both modalities' real
|
|
61
|
+
content from one shared file, such as a video that also carries an
|
|
62
|
+
audio track: video_token_stream (opencv) and audio_token_stream
|
|
63
|
+
(torchaudio) each open the container with their own library and only
|
|
64
|
+
ever read the stream they care about, so pointing both at the same
|
|
65
|
+
path is safe and is the intended way to say "learn from this video's
|
|
66
|
+
picture AND its soundtrack." -> [(name, path_or_None), ...].
|
|
67
|
+
"""
|
|
68
|
+
parsed = []
|
|
69
|
+
for spec in modalities:
|
|
70
|
+
spec = (spec or "").strip()
|
|
71
|
+
if not spec:
|
|
72
|
+
continue
|
|
73
|
+
name, _, path = spec.partition(":")
|
|
74
|
+
parsed.append((name.strip().lower(), path.strip() or None))
|
|
75
|
+
return parsed
|
|
76
|
+
|
|
77
|
+
|
|
78
|
+
class MultimodalDataset(BaseDataset):
|
|
79
|
+
# Real-data wiring: see _parse_modality_specs() above for the
|
|
80
|
+
# "modality[:path]" spec syntax and MultimodalTask (inference/tasks/
|
|
81
|
+
# multimodal_task.py) for how --dataset-link reaches here at inference.
|
|
82
|
+
def __init__(self, modalities, link_role: str = "dataset_link"):
|
|
83
|
+
"""
|
|
84
|
+
modalities: iterable of modality specs (see _parse_modality_specs).
|
|
85
|
+
link_role: "dataset_link" (default; training-side) -- a spec's path
|
|
86
|
+
becomes that modality's OWN training source (auto-split into a
|
|
87
|
+
disjoint train/test pool by that modality's dataset class,
|
|
88
|
+
exactly like a standalone *ChainDataset(dataset_link=...)), or
|
|
89
|
+
"test_dataset_link" (inference-side, see MultimodalTask) -- a
|
|
90
|
+
spec's path becomes a fixed, already-real TEST pool directly.
|
|
91
|
+
"""
|
|
92
|
+
if link_role not in ("dataset_link", "test_dataset_link"):
|
|
93
|
+
raise ValueError(
|
|
94
|
+
"MultimodalDataset: link_role must be 'dataset_link' "
|
|
95
|
+
f"or 'test_dataset_link', got {link_role!r}"
|
|
96
|
+
)
|
|
97
|
+
specs = _parse_modality_specs(modalities)
|
|
98
|
+
if len(specs) < 2:
|
|
99
|
+
raise ValueError(f"MultimodalDataset needs >=2 modalities, got {modalities!r}")
|
|
100
|
+
names = [n for n, _ in specs]
|
|
101
|
+
unknown = [n for n in names if n not in _MODALITY_CLASSES]
|
|
102
|
+
if unknown:
|
|
103
|
+
raise ValueError(
|
|
104
|
+
f"MultimodalDataset: unknown modalities {unknown}, "
|
|
105
|
+
f"expected a subset of {sorted(_MODALITY_CLASSES)}"
|
|
106
|
+
)
|
|
107
|
+
self.modalities = names
|
|
108
|
+
self.subs = [
|
|
109
|
+
_MODALITY_CLASSES[name](**({link_role: path} if path else {})) for name, path in specs
|
|
110
|
+
]
|
|
111
|
+
self.primary = self.subs[0]
|
|
112
|
+
self.name = "multimodal[" + "+".join(names) + "]"
|
|
113
|
+
|
|
114
|
+
off = 0
|
|
115
|
+
self._offsets = []
|
|
116
|
+
for s in self.subs:
|
|
117
|
+
self._offsets.append((off, off + s.input_dim))
|
|
118
|
+
off += s.input_dim
|
|
119
|
+
self.input_dim = off
|
|
120
|
+
self.output_dim = self.primary.output_dim
|
|
121
|
+
self.eval_batch_size = self.primary.eval_batch_size
|
|
122
|
+
self.advance_threshold = self.primary.advance_threshold
|
|
123
|
+
self._table = self.primary._table
|
|
124
|
+
self._lesson_nr_cells = self.primary._lesson_nr_cells
|
|
125
|
+
|
|
126
|
+
def _facts_pool(self, use_test_pool: bool = False):
|
|
127
|
+
"""Real (key,value) pool to draw a fused episode from, or None for
|
|
128
|
+
the fully-synthetic draw (unchanged default when no modality was
|
|
129
|
+
given a real source). use_test_pool selects each sub's held-out
|
|
130
|
+
`_test_fact_pool` (evaluate_ood) vs its training `_fact_pool`
|
|
131
|
+
(sample_batch / evaluate_id_ablated / evaluate_modality_ablated) --
|
|
132
|
+
same train/test convention every single-modality dataset already
|
|
133
|
+
uses. Values always come from the PRIMARY modality's pool (only the
|
|
134
|
+
primary is scored -- see class docstring); keys are restricted to
|
|
135
|
+
ones ALSO present in every OTHER real-sourced modality's pool, so
|
|
136
|
+
any channel built from real data genuinely has real content for
|
|
137
|
+
every key an episode can draw (e.g. a video file's frame-derived
|
|
138
|
+
pool intersected with that SAME file's audio-track-derived pool).
|
|
139
|
+
Falls back to the unrestricted primary pool if the intersection is
|
|
140
|
+
empty, rather than raising mid-run."""
|
|
141
|
+
attr = "_test_fact_pool" if use_test_pool else "_fact_pool"
|
|
142
|
+
primary_pool = getattr(self.primary, attr)
|
|
143
|
+
if primary_pool is None:
|
|
144
|
+
return None
|
|
145
|
+
other_pools = [p for p in (getattr(s, attr) for s in self.subs[1:]) if p is not None]
|
|
146
|
+
if not other_pools:
|
|
147
|
+
return primary_pool
|
|
148
|
+
shared_keys = set(k for k, _ in primary_pool)
|
|
149
|
+
for p in other_pools:
|
|
150
|
+
shared_keys &= set(k for k, _ in p)
|
|
151
|
+
restricted = [(k, v) for k, v in primary_pool if k in shared_keys]
|
|
152
|
+
return restricted or primary_pool
|
|
153
|
+
|
|
154
|
+
def make_curriculum(self):
|
|
155
|
+
return ChainCurriculum(self, self._table, self._lesson_nr_cells)
|
|
156
|
+
|
|
157
|
+
def set_output_proj(self, proj):
|
|
158
|
+
self._output_proj = proj
|
|
159
|
+
|
|
160
|
+
def _build_fused_episode(
|
|
161
|
+
self,
|
|
162
|
+
num_facts_range,
|
|
163
|
+
num_queries_range,
|
|
164
|
+
rng=None,
|
|
165
|
+
skip_modalities=None,
|
|
166
|
+
perturb=None,
|
|
167
|
+
use_test_pool=False,
|
|
168
|
+
):
|
|
169
|
+
rng = rng if rng is not None else random
|
|
170
|
+
skip_modalities = skip_modalities or set()
|
|
171
|
+
codec, label_range = self.primary.codec, self.primary.label_range
|
|
172
|
+
NF = codec.num_digits
|
|
173
|
+
|
|
174
|
+
def _as_range(x):
|
|
175
|
+
return x if isinstance(x, tuple) else (x, x)
|
|
176
|
+
|
|
177
|
+
num_facts_range = _as_range(num_facts_range)
|
|
178
|
+
num_queries_range = _as_range(num_queries_range)
|
|
179
|
+
|
|
180
|
+
num_facts = rng.randint(*num_facts_range)
|
|
181
|
+
pool = self._facts_pool(use_test_pool=use_test_pool)
|
|
182
|
+
if pool is not None:
|
|
183
|
+
num_facts = min(num_facts, len(pool))
|
|
184
|
+
facts = rng.sample(pool, num_facts)
|
|
185
|
+
else:
|
|
186
|
+
keys = rng.sample(range(label_range), num_facts)
|
|
187
|
+
vals = [rng.randrange(label_range) for _ in range(num_facts)]
|
|
188
|
+
facts = list(zip(keys, vals))
|
|
189
|
+
rng.shuffle(facts)
|
|
190
|
+
keys = [k for k, _ in facts]
|
|
191
|
+
num_queries = rng.randint(*num_queries_range)
|
|
192
|
+
query_keys = [rng.choice(keys) for _ in range(num_queries)]
|
|
193
|
+
|
|
194
|
+
def fused(build_fns):
|
|
195
|
+
return torch.cat(
|
|
196
|
+
[
|
|
197
|
+
torch.zeros(sub.input_dim) if i in skip_modalities else build_fns[i]()
|
|
198
|
+
for i, sub in enumerate(self.subs)
|
|
199
|
+
]
|
|
200
|
+
)
|
|
201
|
+
|
|
202
|
+
inputs, target_digits, answer_mask = [], [], []
|
|
203
|
+
for i, (k, v) in enumerate(facts):
|
|
204
|
+
pt = 1.0 if i == 0 else 0.0
|
|
205
|
+
inputs.append(
|
|
206
|
+
fused(
|
|
207
|
+
[
|
|
208
|
+
(lambda s=sub: s._encode_fact(k, v, pt, perturb=perturb, rng=rng))
|
|
209
|
+
for sub in self.subs
|
|
210
|
+
]
|
|
211
|
+
)
|
|
212
|
+
)
|
|
213
|
+
target_digits.append([0] * (2 * NF))
|
|
214
|
+
answer_mask.append(0)
|
|
215
|
+
for i, k in enumerate(query_keys):
|
|
216
|
+
pt = 1.0 if i == 0 else 0.0
|
|
217
|
+
inputs.append(fused([(lambda s=sub: s._encode_query(k, pt)) for sub in self.subs]))
|
|
218
|
+
target_digits.append([0] * (2 * NF))
|
|
219
|
+
answer_mask.append(0)
|
|
220
|
+
value_of, cumsum = dict(facts), 0
|
|
221
|
+
for i, k in enumerate(query_keys):
|
|
222
|
+
cumsum = (cumsum + value_of[k]) % label_range
|
|
223
|
+
pt = 1.0 if i == 0 else 0.0
|
|
224
|
+
inputs.append(fused([(lambda s=sub: s._encode_answer(pt)) for sub in self.subs]))
|
|
225
|
+
target_digits.append(
|
|
226
|
+
codec.label_to_digit_targets(value_of[k]) + codec.label_to_digit_targets(cumsum)
|
|
227
|
+
)
|
|
228
|
+
answer_mask.append(1)
|
|
229
|
+
return (
|
|
230
|
+
torch.stack(inputs),
|
|
231
|
+
torch.tensor(target_digits, dtype=torch.long),
|
|
232
|
+
torch.tensor(answer_mask, dtype=torch.float32),
|
|
233
|
+
num_queries,
|
|
234
|
+
)
|
|
235
|
+
|
|
236
|
+
def collate(self, batch):
|
|
237
|
+
max_len = max(len(x[0]) for x in batch)
|
|
238
|
+
B = len(batch)
|
|
239
|
+
NF = self.primary.codec.num_digits
|
|
240
|
+
padded_input = torch.zeros(B, max_len, self.input_dim)
|
|
241
|
+
padded_targets = torch.zeros(B, max_len, 2 * NF, dtype=torch.long)
|
|
242
|
+
padded_mask = torch.zeros(B, max_len)
|
|
243
|
+
for i, (inp, tgt, mask, _) in enumerate(batch):
|
|
244
|
+
T = inp.size(0)
|
|
245
|
+
padded_input[i, :T] = inp
|
|
246
|
+
padded_targets[i, :T] = tgt
|
|
247
|
+
padded_mask[i, :T] = mask
|
|
248
|
+
return padded_input, padded_targets, padded_mask
|
|
249
|
+
|
|
250
|
+
def sample_batch(self, curriculum, batch_size):
|
|
251
|
+
episodes = []
|
|
252
|
+
for _ in range(batch_size):
|
|
253
|
+
idx = (
|
|
254
|
+
random.randint(0, curriculum.lesson - 1)
|
|
255
|
+
if curriculum.lesson > 0 and random.random() < 0.10
|
|
256
|
+
else curriculum.lesson
|
|
257
|
+
)
|
|
258
|
+
nf, nq = curriculum.table[idx]
|
|
259
|
+
episodes.append(self._build_fused_episode(nf, nq))
|
|
260
|
+
return self.collate(episodes) # was self.primary.collate(episodes)
|
|
261
|
+
|
|
262
|
+
def loss(self, output, target, mask):
|
|
263
|
+
return self.primary.loss(output, target, mask)
|
|
264
|
+
|
|
265
|
+
def diversity(self, output, target, mask):
|
|
266
|
+
return self.primary.diversity(output, target, mask)
|
|
267
|
+
|
|
268
|
+
def evaluate_chain(
|
|
269
|
+
self,
|
|
270
|
+
model,
|
|
271
|
+
device,
|
|
272
|
+
num_episodes,
|
|
273
|
+
num_facts_range=None,
|
|
274
|
+
num_queries_range=None,
|
|
275
|
+
rng=None,
|
|
276
|
+
ablate_memory=False,
|
|
277
|
+
step_breakdown=False,
|
|
278
|
+
skip_modalities=None,
|
|
279
|
+
verbose_n=0,
|
|
280
|
+
use_test_pool=False,
|
|
281
|
+
skip_stages=None,
|
|
282
|
+
field_log=None,
|
|
283
|
+
):
|
|
284
|
+
model.eval()
|
|
285
|
+
total = correct_value = correct_cumsum = perfect_episodes = tested = 0
|
|
286
|
+
by_depth = {}
|
|
287
|
+
codec = self.primary.codec
|
|
288
|
+
with torch.no_grad():
|
|
289
|
+
while tested < num_episodes:
|
|
290
|
+
input_seq, target_digits, answer_mask, depth = self._build_fused_episode(
|
|
291
|
+
num_facts_range,
|
|
292
|
+
num_queries_range,
|
|
293
|
+
rng=rng,
|
|
294
|
+
skip_modalities=skip_modalities,
|
|
295
|
+
use_test_pool=use_test_pool,
|
|
296
|
+
)
|
|
297
|
+
input_seq = input_seq.unsqueeze(0).to(device)
|
|
298
|
+
output, _ = model(
|
|
299
|
+
input_seq,
|
|
300
|
+
(None, None, None),
|
|
301
|
+
reset_experience=True,
|
|
302
|
+
pass_through_memory=not ablate_memory,
|
|
303
|
+
**({"combiner_skip_stages": skip_stages} if skip_stages else {}),
|
|
304
|
+
)
|
|
305
|
+
output = self._output_proj(output.transpose(0, 1).contiguous().squeeze(0))
|
|
306
|
+
ep_total, ep_correct, episode_perfect, ep_cv, ep_cc, ep_n = score_answer_steps(
|
|
307
|
+
output,
|
|
308
|
+
target_digits,
|
|
309
|
+
answer_mask,
|
|
310
|
+
codec,
|
|
311
|
+
verbose=(tested < verbose_n),
|
|
312
|
+
field_log=field_log,
|
|
313
|
+
)
|
|
314
|
+
correct_value += ep_cv
|
|
315
|
+
correct_cumsum += ep_cc
|
|
316
|
+
total += ep_n
|
|
317
|
+
perfect_episodes += int(episode_perfect)
|
|
318
|
+
tested += 1
|
|
319
|
+
if step_breakdown:
|
|
320
|
+
acc = by_depth.setdefault(ep_total, [0, 0, 0, 0])
|
|
321
|
+
acc[0] += 2 * ep_total
|
|
322
|
+
acc[1] += ep_correct
|
|
323
|
+
acc[2] += 1
|
|
324
|
+
acc[3] += int(episode_perfect)
|
|
325
|
+
combined_acc = 100.0 * (correct_value + correct_cumsum) / max(2 * total, 1)
|
|
326
|
+
perfect_frac = 100.0 * perfect_episodes / max(tested, 1)
|
|
327
|
+
print(
|
|
328
|
+
f"Eval [{self.name}]: value {100.0 * correct_value / max(total, 1):.2f}% | "
|
|
329
|
+
f"cumsum {100.0 * correct_cumsum / max(total, 1):.2f}% | combined {combined_acc:.2f}% | "
|
|
330
|
+
f"perfect {perfect_frac:.2f}% ({tested} episodes)"
|
|
331
|
+
)
|
|
332
|
+
model.train()
|
|
333
|
+
if step_breakdown:
|
|
334
|
+
breakdown = {
|
|
335
|
+
d: (100.0 * c / max(t, 1), 100.0 * p / max(e, 1), e)
|
|
336
|
+
for d, (t, c, e, p) in sorted(by_depth.items())
|
|
337
|
+
}
|
|
338
|
+
return combined_acc, perfect_frac, breakdown
|
|
339
|
+
return combined_acc, perfect_frac
|
|
340
|
+
|
|
341
|
+
def evaluate_ood(self, model, device, num_episodes, rng, verbose_n=0, field_log=None):
|
|
342
|
+
return self.evaluate_chain(
|
|
343
|
+
model,
|
|
344
|
+
device,
|
|
345
|
+
num_episodes,
|
|
346
|
+
num_facts_range=(15, 20),
|
|
347
|
+
num_queries_range=self.primary.ood_query_range,
|
|
348
|
+
rng=rng,
|
|
349
|
+
step_breakdown=True,
|
|
350
|
+
use_test_pool=True,
|
|
351
|
+
verbose_n=verbose_n,
|
|
352
|
+
field_log=field_log,
|
|
353
|
+
)
|
|
354
|
+
|
|
355
|
+
def evaluate_id_ablated(self, model, device, curriculum, lesson_idx):
|
|
356
|
+
nf, nq = curriculum.table[lesson_idx]
|
|
357
|
+
return self.evaluate_chain(
|
|
358
|
+
model,
|
|
359
|
+
device,
|
|
360
|
+
self.eval_batch_size,
|
|
361
|
+
num_facts_range=nf,
|
|
362
|
+
num_queries_range=nq,
|
|
363
|
+
ablate_memory=True,
|
|
364
|
+
)
|
|
365
|
+
|
|
366
|
+
def evaluate_id_combiner_stage_ablated(
|
|
367
|
+
self, model, device, curriculum, lesson_idx, skip_stages
|
|
368
|
+
):
|
|
369
|
+
"""Combiner-stage-off eval on lesson `lesson_idx` -> (acc_pct, perfect_pct).
|
|
370
|
+
Mirrors data/common/chain_task.py's KVChainDataset implementation --
|
|
371
|
+
only meaningful when model.combiner_wrapper is a ChainedControllerWrapper
|
|
372
|
+
(e.g. --split-graph-combiner-variant mamba+cfc)."""
|
|
373
|
+
nf, nq = curriculum.table[lesson_idx]
|
|
374
|
+
return self.evaluate_chain(
|
|
375
|
+
model,
|
|
376
|
+
device,
|
|
377
|
+
self.eval_batch_size,
|
|
378
|
+
num_facts_range=nf,
|
|
379
|
+
num_queries_range=nq,
|
|
380
|
+
skip_stages=skip_stages,
|
|
381
|
+
)
|
|
382
|
+
|
|
383
|
+
def evaluate_modality_ablated(self, model, device, curriculum, lesson_idx, modality: str):
|
|
384
|
+
"""Delta_<modality> = full_acc - ablated_acc, per the research doc's
|
|
385
|
+
modality-dependency proposal."""
|
|
386
|
+
idx = self.modalities.index(modality)
|
|
387
|
+
nf, nq = curriculum.table[lesson_idx]
|
|
388
|
+
return self.evaluate_chain(
|
|
389
|
+
model,
|
|
390
|
+
device,
|
|
391
|
+
self.eval_batch_size,
|
|
392
|
+
num_facts_range=nf,
|
|
393
|
+
num_queries_range=nq,
|
|
394
|
+
skip_modalities={idx},
|
|
395
|
+
)
|
|
396
|
+
|
|
397
|
+
def field_log_header(self):
|
|
398
|
+
return value_cumsum_field_log_header()
|
|
399
|
+
|
|
400
|
+
def write_field_log(self, writer, file, step, lesson, eval_type, field_log):
|
|
401
|
+
write_value_cumsum_field_log(writer, file, step, lesson, eval_type, field_log)
|
|
File without changes
|
|
@@ -0,0 +1,79 @@
|
|
|
1
|
+
"""
|
|
2
|
+
file: data/text/text_dataset.py
|
|
3
|
+
|
|
4
|
+
Text modality dataset: TextChainDataset(KVChainDataset). Default (no
|
|
5
|
+
dataset_link): "facts" are token(key)->token(value) associations drawn
|
|
6
|
+
fresh each episode from the full label space (unchanged synthetic
|
|
7
|
+
behavior). With dataset_link (a path to a real text file): a byte-level
|
|
8
|
+
BPE tokenizer (data/common/real_data.py) is trained on the file, the
|
|
9
|
+
resulting token stream is turned into a real (key,value) fact pool, and
|
|
10
|
+
that pool is split into a disjoint TRAIN pool (used by build_episode
|
|
11
|
+
instead of random draws) and TEST pool (used by build_ood_facts instead of
|
|
12
|
+
the synthetic seeded table below) -- the text analogue of the graph
|
|
13
|
+
dataset's "train on synthetic graphs, test on the held-out London
|
|
14
|
+
Underground" split. test_dataset_link (a second, separate text file) can
|
|
15
|
+
be given to use an entirely different source as the test set instead of a
|
|
16
|
+
held-out slice of the same file.
|
|
17
|
+
|
|
18
|
+
Depth axis: number of queries (reasoning-chain-length / context-length
|
|
19
|
+
analogue). Fields: value (lookup), cumsum (chained state).
|
|
20
|
+
"""
|
|
21
|
+
|
|
22
|
+
import random
|
|
23
|
+
|
|
24
|
+
from initium.data.common.chain_task import KVChainDataset
|
|
25
|
+
from initium.data.common.real_data import build_text_kv_pool, split_train_test_facts
|
|
26
|
+
|
|
27
|
+
TEXT_CURRICULUM = [
|
|
28
|
+
(3, 2),
|
|
29
|
+
(3, 3),
|
|
30
|
+
(5, 3),
|
|
31
|
+
(5, 4),
|
|
32
|
+
(8, 4),
|
|
33
|
+
(8, 5),
|
|
34
|
+
(10, 5),
|
|
35
|
+
(10, 6),
|
|
36
|
+
(12, 6),
|
|
37
|
+
(12, 7),
|
|
38
|
+
(15, 8),
|
|
39
|
+
(15, 9),
|
|
40
|
+
(20, 10),
|
|
41
|
+
(20, 12),
|
|
42
|
+
]
|
|
43
|
+
TEXT_LESSON_NR_CELLS = [128, 128, 128, 128, 160, 160, 160, 160, 192, 192, 192, 192, 256, 256]
|
|
44
|
+
assert len(TEXT_CURRICULUM) == len(TEXT_LESSON_NR_CELLS)
|
|
45
|
+
|
|
46
|
+
|
|
47
|
+
class TextChainDataset(KVChainDataset):
|
|
48
|
+
name = "text-chain"
|
|
49
|
+
|
|
50
|
+
def __init__(self, dataset_link: str | None = None, test_dataset_link: str | None = None):
|
|
51
|
+
self._table = TEXT_CURRICULUM
|
|
52
|
+
self._lesson_nr_cells = TEXT_LESSON_NR_CELLS
|
|
53
|
+
self._fact_pool = None
|
|
54
|
+
self._test_fact_pool = None
|
|
55
|
+
if dataset_link is not None:
|
|
56
|
+
pool = build_text_kv_pool(dataset_link, self.label_range)
|
|
57
|
+
if test_dataset_link is not None:
|
|
58
|
+
self._fact_pool = pool
|
|
59
|
+
self._test_fact_pool = build_text_kv_pool(test_dataset_link, self.label_range)
|
|
60
|
+
else:
|
|
61
|
+
self._fact_pool, self._test_fact_pool = split_train_test_facts(pool)
|
|
62
|
+
print(
|
|
63
|
+
f"[text-chain] {len(self._fact_pool)} train facts from {dataset_link} | "
|
|
64
|
+
f"{len(self._test_fact_pool)} test facts"
|
|
65
|
+
+ (f" from {test_dataset_link}" if test_dataset_link else " (held-out split)")
|
|
66
|
+
)
|
|
67
|
+
elif test_dataset_link is not None:
|
|
68
|
+
# Inference-only: no training pool needed, just a fixed real test
|
|
69
|
+
# pool -- every inference/tasks/*_task.py passes --dataset-link
|
|
70
|
+
# through as test_dataset_link, since inference only ever tests
|
|
71
|
+
# an already-trained model (see this class's own docstring).
|
|
72
|
+
self._test_fact_pool = build_text_kv_pool(test_dataset_link, self.label_range)
|
|
73
|
+
print(f"[text-chain] {len(self._test_fact_pool)} test facts from {test_dataset_link}")
|
|
74
|
+
|
|
75
|
+
def _synthetic_ood_facts(self, n_facts: int = 20):
|
|
76
|
+
rng = random.Random(4242) # fixed held-out vocabulary
|
|
77
|
+
keys = rng.sample(range(self.label_range), n_facts)
|
|
78
|
+
vals = [rng.randrange(self.label_range) for _ in range(n_facts)]
|
|
79
|
+
return list(zip(keys, vals))
|
|
File without changes
|
|
@@ -0,0 +1,85 @@
|
|
|
1
|
+
"""
|
|
2
|
+
file: data/video/video_dataset.py
|
|
3
|
+
|
|
4
|
+
Video modality dataset: VideoChainDataset(KVChainDataset). Default (no
|
|
5
|
+
dataset_link): synthetic object-identity(key)->action/state-identity(value)
|
|
6
|
+
facts, drawn fresh each episode (unchanged behavior). With dataset_link (a
|
|
7
|
+
path to a real video file, needs opencv-python): each frame is downsampled
|
|
8
|
+
and quantized to an intensity-bin id (data/common/real_data.py
|
|
9
|
+
video_token_stream) and turned into a real fact pool, split into a
|
|
10
|
+
disjoint TRAIN/TEST pool the same way text_dataset.py does -- the video
|
|
11
|
+
analogue of the graph dataset's held-out London Underground graph.
|
|
12
|
+
test_dataset_link (a second video file) can be given to use a genuinely
|
|
13
|
+
different clip as the test set instead of a held-out slice of the same
|
|
14
|
+
one.
|
|
15
|
+
|
|
16
|
+
Depth axis: number of queried events (event-chain-length / temporal-
|
|
17
|
+
horizon analogue). Fields: value (object-action binding accuracy), cumsum
|
|
18
|
+
(chained event-ordering state).
|
|
19
|
+
Robustness axis: frame dropping / occlusion -- the WHOLE fact observation
|
|
20
|
+
(key AND value) is zeroed with prob=severity, rather than corrupted in
|
|
21
|
+
place (closer to "this frame was dropped/occluded" than to token noise).
|
|
22
|
+
"""
|
|
23
|
+
|
|
24
|
+
import random
|
|
25
|
+
|
|
26
|
+
import torch
|
|
27
|
+
|
|
28
|
+
from initium.data.common.chain_task import KVChainDataset
|
|
29
|
+
from initium.data.common.real_data import build_video_kv_pool, split_train_test_facts
|
|
30
|
+
|
|
31
|
+
VIDEO_CURRICULUM = [
|
|
32
|
+
(3, 2),
|
|
33
|
+
(3, 3),
|
|
34
|
+
(5, 3),
|
|
35
|
+
(5, 4),
|
|
36
|
+
(8, 4),
|
|
37
|
+
(8, 5),
|
|
38
|
+
(10, 5),
|
|
39
|
+
(10, 6),
|
|
40
|
+
(12, 6),
|
|
41
|
+
(12, 7),
|
|
42
|
+
(15, 8),
|
|
43
|
+
(15, 9),
|
|
44
|
+
(20, 10),
|
|
45
|
+
(20, 12),
|
|
46
|
+
]
|
|
47
|
+
VIDEO_LESSON_NR_CELLS = [128, 128, 128, 128, 160, 160, 160, 160, 192, 192, 192, 192, 256, 256]
|
|
48
|
+
assert len(VIDEO_CURRICULUM) == len(VIDEO_LESSON_NR_CELLS)
|
|
49
|
+
|
|
50
|
+
|
|
51
|
+
class VideoChainDataset(KVChainDataset):
|
|
52
|
+
name = "video-chain"
|
|
53
|
+
|
|
54
|
+
def __init__(self, dataset_link: str | None = None, test_dataset_link: str | None = None):
|
|
55
|
+
self._table = VIDEO_CURRICULUM
|
|
56
|
+
self._lesson_nr_cells = VIDEO_LESSON_NR_CELLS
|
|
57
|
+
self._fact_pool = None
|
|
58
|
+
self._test_fact_pool = None
|
|
59
|
+
if dataset_link is not None:
|
|
60
|
+
pool = build_video_kv_pool(dataset_link, self.label_range)
|
|
61
|
+
if test_dataset_link is not None:
|
|
62
|
+
self._fact_pool = pool
|
|
63
|
+
self._test_fact_pool = build_video_kv_pool(test_dataset_link, self.label_range)
|
|
64
|
+
else:
|
|
65
|
+
self._fact_pool, self._test_fact_pool = split_train_test_facts(pool)
|
|
66
|
+
print(
|
|
67
|
+
f"[video-chain] {len(self._fact_pool)} train facts from {dataset_link} | "
|
|
68
|
+
f"{len(self._test_fact_pool)} test facts"
|
|
69
|
+
+ (f" from {test_dataset_link}" if test_dataset_link else " (held-out split)")
|
|
70
|
+
)
|
|
71
|
+
elif test_dataset_link is not None:
|
|
72
|
+
# Inference-only real test pool -- see text_dataset.py's twin fix.
|
|
73
|
+
self._test_fact_pool = build_video_kv_pool(test_dataset_link, self.label_range)
|
|
74
|
+
print(f"[video-chain] {len(self._test_fact_pool)} test facts from {test_dataset_link}")
|
|
75
|
+
|
|
76
|
+
def _synthetic_ood_facts(self, n_facts: int = 20):
|
|
77
|
+
rng = random.Random(31337) # unseen object/action table
|
|
78
|
+
keys = rng.sample(range(self.label_range), n_facts)
|
|
79
|
+
vals = [rng.randrange(self.label_range) for _ in range(n_facts)]
|
|
80
|
+
return list(zip(keys, vals))
|
|
81
|
+
|
|
82
|
+
def perturb_fact(self, key_vec: torch.Tensor, value_vec: torch.Tensor, rng, severity: float):
|
|
83
|
+
if rng.random() < severity:
|
|
84
|
+
return torch.zeros_like(key_vec), torch.zeros_like(value_vec)
|
|
85
|
+
return key_vec, value_vec
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
"""Inference package: reusable, checkpoint-driven inference for the DNC project."""
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
"""Inference caches (prefix state / result), modular like data/ and inference/tasks/."""
|
|
@@ -0,0 +1,112 @@
|
|
|
1
|
+
"""
|
|
2
|
+
file: inference/cache/base_cache.py
|
|
3
|
+
|
|
4
|
+
Interface the cached engine talks to (counterpart of data/base_dataset.py). A new
|
|
5
|
+
cache type subclasses BaseCache, overrides only the hooks it needs, and registers
|
|
6
|
+
itself in cache_registry.py; the engine never imports anything cache-specific.
|
|
7
|
+
|
|
8
|
+
Two hook families:
|
|
9
|
+
episode-level : lookup_episode() / store_episode() (wrap the whole episode)
|
|
10
|
+
prefix-level : find_resume() / store_boundary() (accelerate the computation;
|
|
11
|
+
only used when the caches' `wants_boundaries` is True and the
|
|
12
|
+
episode declares cache_boundaries)
|
|
13
|
+
"""
|
|
14
|
+
|
|
15
|
+
from __future__ import annotations
|
|
16
|
+
|
|
17
|
+
from dataclasses import dataclass, field
|
|
18
|
+
from typing import Any
|
|
19
|
+
|
|
20
|
+
import torch
|
|
21
|
+
|
|
22
|
+
from initium.inference.cache.keys import digest, hash_tensor
|
|
23
|
+
from initium.inference.cache.state_utils import tree_nbytes
|
|
24
|
+
from initium.inference.cache.stores import CacheStats, TieredStore
|
|
25
|
+
|
|
26
|
+
|
|
27
|
+
@dataclass
|
|
28
|
+
class EpisodeCtx:
|
|
29
|
+
index: int
|
|
30
|
+
episode: Any # inference.tasks.base_task.Episode
|
|
31
|
+
reset_experience: bool
|
|
32
|
+
in_hash: str # hash of the episode's full input sequence
|
|
33
|
+
chain: str # "root" in fresh mode; history hash in persistent mode
|
|
34
|
+
boundaries: list[int] = field(default_factory=list) # validated cut points
|
|
35
|
+
_prefix_hashes: dict[int, str] = field(default_factory=dict, repr=False)
|
|
36
|
+
|
|
37
|
+
def prefix_hash(self, boundary: int) -> str:
|
|
38
|
+
h = self._prefix_hashes.get(boundary)
|
|
39
|
+
if h is None:
|
|
40
|
+
h = self._prefix_hashes[boundary] = hash_tensor(self.episode.input_seq[:boundary])
|
|
41
|
+
return h
|
|
42
|
+
|
|
43
|
+
|
|
44
|
+
@dataclass
|
|
45
|
+
class EpisodeHit:
|
|
46
|
+
output: torch.Tensor # (T, output_dim) CPU float
|
|
47
|
+
hidden: Any # restored state (persistent mode) or None
|
|
48
|
+
saved_ms: float
|
|
49
|
+
|
|
50
|
+
|
|
51
|
+
@dataclass
|
|
52
|
+
class ResumePoint:
|
|
53
|
+
position: int # steps [0, position) are already reflected in `hidden`
|
|
54
|
+
hidden: Any
|
|
55
|
+
saved_ms: float
|
|
56
|
+
cache_name: str
|
|
57
|
+
|
|
58
|
+
|
|
59
|
+
class BaseCache:
|
|
60
|
+
name: str = "base"
|
|
61
|
+
wants_boundaries: bool = False
|
|
62
|
+
|
|
63
|
+
def __init__(self, store: TieredStore, model_fp: str, device: torch.device):
|
|
64
|
+
self.store, self.model_fp, self.device = store, model_fp, device
|
|
65
|
+
self.stats = CacheStats()
|
|
66
|
+
|
|
67
|
+
# ---- hooks ----------------------------------------------------------------
|
|
68
|
+
def applicable(self, *, reset_experience: bool, resumable: bool) -> tuple[bool, str]:
|
|
69
|
+
return True, ""
|
|
70
|
+
|
|
71
|
+
def lookup_episode(self, ctx: EpisodeCtx) -> EpisodeHit | None:
|
|
72
|
+
return None
|
|
73
|
+
|
|
74
|
+
def store_episode(
|
|
75
|
+
self, ctx: EpisodeCtx, output: torch.Tensor, hidden_after: Any, compute_ms: float
|
|
76
|
+
) -> None:
|
|
77
|
+
pass
|
|
78
|
+
|
|
79
|
+
def find_resume(self, ctx: EpisodeCtx) -> ResumePoint | None:
|
|
80
|
+
return None
|
|
81
|
+
|
|
82
|
+
def store_boundary(
|
|
83
|
+
self, ctx: EpisodeCtx, boundary: int, hidden: Any, compute_ms: float
|
|
84
|
+
) -> None:
|
|
85
|
+
pass
|
|
86
|
+
|
|
87
|
+
# ---- helpers --------------------------------------------------------------
|
|
88
|
+
def report(self, reset: bool = False) -> dict:
|
|
89
|
+
d = self.stats.to_dict()
|
|
90
|
+
if reset:
|
|
91
|
+
self.stats = CacheStats()
|
|
92
|
+
return d
|
|
93
|
+
|
|
94
|
+
def _key(self, *parts: Any) -> str:
|
|
95
|
+
return f"{self.name}-{digest(self.model_fp, self.name, *parts)}"
|
|
96
|
+
|
|
97
|
+
def _get(self, key: str):
|
|
98
|
+
return self.store.get(key)
|
|
99
|
+
|
|
100
|
+
def _put(self, key: str, payload: Any, compute_ms: float) -> None:
|
|
101
|
+
nbytes = tree_nbytes(payload)
|
|
102
|
+
self.store.put(
|
|
103
|
+
key,
|
|
104
|
+
{
|
|
105
|
+
"payload": payload,
|
|
106
|
+
"compute_ms": float(compute_ms),
|
|
107
|
+
"nbytes": nbytes,
|
|
108
|
+
"kind": self.name,
|
|
109
|
+
},
|
|
110
|
+
)
|
|
111
|
+
self.stats.stores += 1
|
|
112
|
+
self.stats.bytes_stored += nbytes
|