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.
Files changed (71) hide show
  1. initium/LNN_controller/cfc_backbone_parallel.py +265 -0
  2. initium/LNN_controller/cfc_controller.py +325 -0
  3. initium/LNN_controller/chained_controller.py +83 -0
  4. initium/LNN_controller/hybrid_controller.py +90 -0
  5. initium/MoE/README.md +1 -0
  6. initium/MoE/moe_layer.py +552 -0
  7. initium/__init__.py +1 -0
  8. initium/config/controller_config.py +86 -0
  9. initium/config/train_config.py +64 -0
  10. initium/core_training.py +2182 -0
  11. initium/data/__init__.py +0 -0
  12. initium/data/audio/__init__.py +0 -0
  13. initium/data/audio/audio_dataset.py +96 -0
  14. initium/data/base_dataset.py +75 -0
  15. initium/data/common/__init__.py +0 -0
  16. initium/data/common/chain_task.py +542 -0
  17. initium/data/common/digit_codec.py +85 -0
  18. initium/data/common/graph_io.py +123 -0
  19. initium/data/common/real_data.py +747 -0
  20. initium/data/dataset_registry.py +59 -0
  21. initium/data/graph_traversal/__init__.py +0 -0
  22. initium/data/graph_traversal/graph_traversal.py +746 -0
  23. initium/data/multimodal/__init__.py +0 -0
  24. initium/data/multimodal/multimodal_dataset.py +401 -0
  25. initium/data/text/__init__.py +0 -0
  26. initium/data/text/text_dataset.py +79 -0
  27. initium/data/video/__init__.py +0 -0
  28. initium/data/video/video_dataset.py +85 -0
  29. initium/inference/__init__.py +1 -0
  30. initium/inference/cache/__init__.py +1 -0
  31. initium/inference/cache/base_cache.py +112 -0
  32. initium/inference/cache/cache_config.py +21 -0
  33. initium/inference/cache/cache_registry.py +99 -0
  34. initium/inference/cache/cached_engine.py +264 -0
  35. initium/inference/cache/keys.py +77 -0
  36. initium/inference/cache/prefix_cache.py +59 -0
  37. initium/inference/cache/result_cache.py +57 -0
  38. initium/inference/cache/state_utils.py +74 -0
  39. initium/inference/cache/stores.py +167 -0
  40. initium/inference/capabilities.py +86 -0
  41. initium/inference/checkpoint_io.py +63 -0
  42. initium/inference/engine.py +88 -0
  43. initium/inference/inference_config.py +22 -0
  44. initium/inference/metrics.py +103 -0
  45. initium/inference/model_loader.py +270 -0
  46. initium/inference/run_inference.py +526 -0
  47. initium/inference/run_logging.py +173 -0
  48. initium/inference/tasks/__init__.py +1 -0
  49. initium/inference/tasks/audio_task.py +114 -0
  50. initium/inference/tasks/base_task.py +60 -0
  51. initium/inference/tasks/graph_traversal_task.py +235 -0
  52. initium/inference/tasks/multimodal_task.py +74 -0
  53. initium/inference/tasks/task_registry.py +68 -0
  54. initium/inference/tasks/text_task.py +114 -0
  55. initium/inference/tasks/video_task.py +114 -0
  56. initium/mamba_controller/mamba2_controller.py +433 -0
  57. initium/mamba_controller/mamba3_controller.py +242 -0
  58. initium/mamba_controller/mamba_backbone_parallel.py +291 -0
  59. initium/mamba_controller/mamba_controller.py +983 -0
  60. initium/mamba_controller/split_graph_dnc.py +623 -0
  61. initium/memory_manipulation/dynamic_memory_resize.py +157 -0
  62. initium/memory_manipulation/dynamic_n_controller.py +152 -0
  63. initium/memory_manipulation/link_matrix_ablation.py +130 -0
  64. initium/memory_manipulation/nvrtc_compat.py +143 -0
  65. initium/memory_manipulation/stochastic_write_head_v2.py +545 -0
  66. initium/setup_wheels.py +175 -0
  67. initium_deeplearning-0.1.0.dist-info/METADATA +253 -0
  68. initium_deeplearning-0.1.0.dist-info/RECORD +71 -0
  69. initium_deeplearning-0.1.0.dist-info/WHEEL +4 -0
  70. initium_deeplearning-0.1.0.dist-info/entry_points.txt +3 -0
  71. initium_deeplearning-0.1.0.dist-info/licenses/LICENSE +674 -0
@@ -0,0 +1,21 @@
1
+ """
2
+ file: inference/cache/cache_config.py
3
+
4
+ Cache-side constants. Pure constants, imported explicitly (same convention as
5
+ inference_config.py).
6
+ """
7
+
8
+ # "off" | "prefix" | "result" | "prefix,result" | "all". Default off: the default episodes
9
+ # reshuffle the edge list every time (nothing to reuse), and persistent-mode experiments
10
+ # should not change behaviour silently. Set to "all" to make caching on-by-default.
11
+ DEFAULT_CACHE = "off"
12
+
13
+ DEFAULT_CACHE_RAM_MB = 512
14
+ DEFAULT_CACHE_DISK_MB = 2048
15
+
16
+ # Number of cache hits that are re-computed WITHOUT the cache and compared. On a
17
+ # mismatch the cache is disabled for the rest of the run (correctness over speed).
18
+ DEFAULT_VERIFY_HITS = 2
19
+ DEFAULT_VERIFY_TOL = 1e-3 # max abs diff on scored outputs and on carried state
20
+
21
+ ROOT_CHAIN = "root" # history-chain seed (persistent-mode result keys)
@@ -0,0 +1,99 @@
1
+ """
2
+ file: inference/cache/cache_registry.py
3
+
4
+ Single entry point used by run_inference.py (counterpart of data/dataset_registry.py):
5
+ caches, notes, fingerprint = setup_caches(spec, ...)
6
+ Adding a cache type = subclass BaseCache + one line in CACHE_CLASSES.
7
+ """
8
+
9
+ from __future__ import annotations
10
+
11
+ import os
12
+
13
+ from initium.inference.cache.base_cache import BaseCache
14
+ from initium.inference.cache.keys import model_fingerprint
15
+ from initium.inference.cache.prefix_cache import PrefixStateCache
16
+ from initium.inference.cache.result_cache import ResultCache
17
+ from initium.inference.cache.stores import DiskStore, MemoryStore, TieredStore
18
+
19
+ CACHE_CLASSES = {"prefix": PrefixStateCache, "result": ResultCache}
20
+ _OFF = {"", "off", "none", "false", "0", "no"}
21
+
22
+
23
+ def parse_cache_spec(spec: str) -> list[str]:
24
+ s = (spec or "off").strip().lower()
25
+ if s in _OFF:
26
+ return []
27
+ if s in ("all", "on"):
28
+ return list(CACHE_CLASSES)
29
+ names = []
30
+ for part in s.replace("+", ",").split(","):
31
+ part = part.strip()
32
+ if not part:
33
+ continue
34
+ if part not in CACHE_CLASSES:
35
+ raise ValueError(
36
+ f"unknown cache type {part!r}; expected off, all, or a comma list of "
37
+ f"{sorted(CACHE_CLASSES)}"
38
+ )
39
+ if part not in names:
40
+ names.append(part)
41
+ return names
42
+
43
+
44
+ def setup_caches(
45
+ spec,
46
+ *,
47
+ ckpt,
48
+ device,
49
+ sampled_writes,
50
+ deterministic_write,
51
+ ablate_memory,
52
+ combiner_skip_stages=None,
53
+ cache_dir=None,
54
+ ram_mb=512.0,
55
+ disk_mb=2048.0,
56
+ clear=False,
57
+ allow_stochastic=False,
58
+ ):
59
+ """-> (caches, notes, model_fingerprint|None). Returns no caches (with a note) when
60
+ the requested caching cannot be done safely."""
61
+ notes: list[str] = []
62
+ names = parse_cache_spec(spec)
63
+ if not names:
64
+ return [], notes, None
65
+
66
+ if sampled_writes and not allow_stochastic:
67
+ notes.append(
68
+ "caching disabled: this model samples its write vectors (beta>0 without "
69
+ "--deterministic-write), so every forward pass is random and cached "
70
+ "results/states would not be reproducible. Use --deterministic-write, or "
71
+ "--cache-allow-stochastic to override."
72
+ )
73
+ return [], notes, None
74
+ if sampled_writes:
75
+ notes.append(
76
+ "WARNING: caching a stochastic model -- cached entries are ONE random draw "
77
+ "reused for every hit (and across runs if --cache-dir is set); "
78
+ "verification is skipped."
79
+ )
80
+
81
+ print("[cache] fingerprinting model weights (once) ...")
82
+ fp = model_fingerprint(
83
+ ckpt,
84
+ {
85
+ "deterministic_write": bool(deterministic_write),
86
+ "sampled_writes": bool(sampled_writes),
87
+ "ablate_memory": bool(ablate_memory),
88
+ "combiner_skip_stages": sorted(combiner_skip_stages) if combiner_skip_stages else None,
89
+ },
90
+ )
91
+ disk = None
92
+ if cache_dir:
93
+ disk = DiskStore(os.path.join(cache_dir, fp), int(disk_mb * 1e6))
94
+ if clear:
95
+ disk.clear()
96
+ notes.append(f"disk cache cleared: {disk.root}")
97
+ store = TieredStore(MemoryStore(int(ram_mb * 1e6)), disk)
98
+ caches: list[BaseCache] = [CACHE_CLASSES[n](store, fp, device) for n in names]
99
+ return caches, notes, fp
@@ -0,0 +1,264 @@
1
+ """
2
+ file: inference/cache/cached_engine.py
3
+
4
+ InferenceEngine + caches. Only used when at least one cache is active, so with caching
5
+ off the plain engine (and its behaviour) is untouched.
6
+
7
+ Per episode:
8
+ 1. episode-level lookups (ResultCache: skip the forward pass entirely)
9
+ 2. else compute, resuming from the longest cached prefix snapshot (PrefixStateCache)
10
+ and snapshotting every not-yet-cached boundary on the way, in segments
11
+ 3. store the episode result in the episode-level caches
12
+
13
+ Correctness net: for the first `verify_hits` cache hits the episode is ALSO recomputed
14
+ without any cache and compared (scored outputs + carried state, tolerance verify_tol).
15
+ A mismatch (or NaN) prints a warning, uses the recomputed values, and disables all
16
+ caches for the remainder of the run.
17
+
18
+ Segment execution:
19
+ MambaDNC / stock DNC : model(x[start:end], hidden, reset_experience=False) (state passes through)
20
+ SplitGraphDNC : model(x[:end], hidden, ..., start_step=start) -- the parallel
21
+ backbone is recomputed over the whole prefix (cheap, stateless
22
+ across calls, keeps outputs exact); only the sequential memory
23
+ loop is skipped for t < start.
24
+ """
25
+
26
+ from __future__ import annotations
27
+
28
+ import inspect
29
+ import time
30
+ from typing import Any
31
+
32
+ import torch
33
+
34
+ from initium.inference.cache.base_cache import EpisodeCtx
35
+ from initium.inference.cache.cache_config import DEFAULT_VERIFY_HITS, DEFAULT_VERIFY_TOL, ROOT_CHAIN
36
+ from initium.inference.cache.keys import chain_next, hash_tensor
37
+ from initium.inference.cache.state_utils import clone_tree, tree_max_abs_diff
38
+ from initium.inference.engine import FRESH_HIDDEN, InferenceEngine
39
+ from initium.inference.metrics import EpisodeResult
40
+
41
+
42
+ def model_supports_resume(model) -> bool:
43
+ if hasattr(model, "backbone"): # SplitGraphDNC
44
+ return "start_step" in inspect.signature(model.forward).parameters
45
+ return True # MambaDNC / stock dnc: hx passes through
46
+
47
+
48
+ class CachedInferenceEngine(InferenceEngine):
49
+ def __init__(
50
+ self,
51
+ model,
52
+ output_proj,
53
+ task,
54
+ device,
55
+ caches,
56
+ ablate_memory: bool = False,
57
+ combiner_skip_stages: frozenset | None = None,
58
+ verify_hits: int = DEFAULT_VERIFY_HITS,
59
+ verify_tol: float = DEFAULT_VERIFY_TOL,
60
+ ):
61
+ super().__init__(model, output_proj, task, device, ablate_memory, combiner_skip_stages)
62
+ self.caches = list(caches)
63
+ self.resumable = model_supports_resume(model)
64
+ self.verify_hits, self.verify_tol = verify_hits, verify_tol
65
+ self.verify = {
66
+ "checked": 0,
67
+ "failures": 0,
68
+ "max_output_diff": 0.0,
69
+ "max_state_diff": 0.0,
70
+ "disabled_after_failure": False,
71
+ }
72
+ self.notes: list[str] = []
73
+ self._disabled = False
74
+ self._warned_bounds = False
75
+
76
+ # ---- small utils -----------------------------------------------------------
77
+ def _note(self, msg: str) -> None:
78
+ if msg not in self.notes:
79
+ self.notes.append(msg)
80
+ print(f"[cache] {msg}")
81
+
82
+ def _sync(self) -> None:
83
+ if self.device.type == "cuda":
84
+ torch.cuda.synchronize(self.device)
85
+
86
+ def cache_report(self, reset: bool = False) -> dict:
87
+ return {c.name: c.report(reset=reset) for c in self.caches}
88
+
89
+ def _select_active(self, reset_experience: bool):
90
+ active = []
91
+ for c in self.caches:
92
+ ok, why = c.applicable(reset_experience=reset_experience, resumable=self.resumable)
93
+ if ok:
94
+ active.append(c)
95
+ else:
96
+ self._note(f"{c.name} cache inactive: {why}")
97
+ return active
98
+
99
+ def _valid_boundaries(self, ep) -> list[int]:
100
+ T = ep.input_seq.shape[0]
101
+ bs = sorted({int(b) for b in ep.cache_boundaries})
102
+ ok = [b for b in bs if 0 < b < T and float(ep.mask[:b].sum()) == 0.0]
103
+ if len(ok) != len(bs) and not self._warned_bounds:
104
+ self._warned_bounds = True
105
+ self._note(
106
+ "some declared cache boundaries were dropped (out of range, or a scored "
107
+ "step lies before them)"
108
+ )
109
+ return ok
110
+
111
+ # ---- model execution ---------------------------------------------------------
112
+ @torch.no_grad()
113
+ def _forward_segment(
114
+ self, x_full: torch.Tensor, start: int, end: int, hidden, reset_experience: bool
115
+ ):
116
+ kw: dict[str, Any] = dict(
117
+ reset_experience=reset_experience, pass_through_memory=not self.ablate_memory
118
+ )
119
+ if self.combiner_skip_stages:
120
+ kw["combiner_skip_stages"] = self.combiner_skip_stages
121
+ if hasattr(self.model, "backbone"): # SplitGraphDNC
122
+ x = x_full[:end].unsqueeze(0).to(self.device)
123
+ if start > 0:
124
+ kw["start_step"] = start
125
+ else:
126
+ x = x_full[start:end].unsqueeze(0).to(self.device)
127
+ output, new_hidden = self.model(x, hidden, **kw)
128
+ output = output.transpose(0, 1).contiguous().squeeze(0)
129
+ output = self.output_proj(output)
130
+ return output.float().cpu(), new_hidden
131
+
132
+ def _compute_full(self, ctx: EpisodeCtx, hidden):
133
+ """Uncached reference: one forward over the whole episode."""
134
+ x = ctx.episode.input_seq
135
+ t0 = time.perf_counter()
136
+ out, hid = self._forward_segment(x, 0, x.shape[0], hidden, ctx.reset_experience)
137
+ self._sync()
138
+ return out, hid, (time.perf_counter() - t0) * 1000.0
139
+
140
+ def _compute(self, ctx: EpisodeCtx, hidden, active):
141
+ x = ctx.episode.input_seq
142
+ T = x.shape[0]
143
+ use_bounds = ctx.reset_experience and self.resumable and bool(ctx.boundaries)
144
+ resume = None
145
+ if use_bounds:
146
+ for c in active:
147
+ r = c.find_resume(ctx)
148
+ if r is not None and (resume is None or r.position > resume.position):
149
+ resume = r
150
+ start = resume.position if resume is not None else 0
151
+ hid = resume.hidden if resume is not None else hidden
152
+ seg_reset = ctx.reset_experience and resume is None
153
+ cuts = [b for b in ctx.boundaries if b > start] if use_bounds else []
154
+ cuts.append(T)
155
+
156
+ cold_ms = resume.saved_ms if resume is not None else 0.0 # cost as if computed from scratch
157
+ segs, pos = [], start
158
+ for cut in cuts:
159
+ t0 = time.perf_counter()
160
+ seg, hid = self._forward_segment(x, pos, cut, hid, seg_reset)
161
+ self._sync()
162
+ cold_ms += (time.perf_counter() - t0) * 1000.0
163
+ segs.append(seg)
164
+ if cut < T: # snapshot BEFORE the next segment mutates state
165
+ for c in active:
166
+ c.store_boundary(ctx, cut, hid, cold_ms)
167
+ pos, seg_reset = cut, False
168
+ out = torch.cat(segs, dim=0)
169
+ if start > 0: # prefix rows are never scored (validated)
170
+ out = torch.cat([torch.zeros(start, out.shape[1]), out], dim=0)
171
+ return out, hid, resume, cold_ms
172
+
173
+ # ---- verification -------------------------------------------------------------
174
+ def _maybe_verify(self, ctx, incoming_hidden, out, new_hidden, event, state_valid):
175
+ v = self.verify
176
+ if self._disabled or v["checked"] >= self.verify_hits:
177
+ return out, new_hidden, event
178
+ ref_out, ref_hidden, _ = self._compute_full(ctx, clone_tree(incoming_hidden))
179
+ mask = ctx.episode.mask == 1
180
+ d_out = (out[mask] - ref_out[mask]).abs().max().item() if bool(mask.any()) else 0.0
181
+ d_state = tree_max_abs_diff(new_hidden, ref_hidden) if state_valid else 0.0
182
+ v["checked"] += 1
183
+ v["max_output_diff"] = max(v["max_output_diff"], d_out)
184
+ v["max_state_diff"] = max(v["max_state_diff"], d_state)
185
+ if d_out <= self.verify_tol and d_state <= self.verify_tol: # NaN-safe form
186
+ return out, new_hidden, event
187
+ v["failures"] += 1
188
+ v["disabled_after_failure"] = True
189
+ self._disabled = True
190
+ self._note(
191
+ f"VERIFICATION FAILED at episode {ctx.index} ({event}): max|d output|={d_out:.3e}, "
192
+ f"max|d state|={d_state:.3e} > tol {self.verify_tol:g}. Caches disabled for the "
193
+ f"rest of this run; using the recomputed result."
194
+ )
195
+ return ref_out, ref_hidden, event + ":VERIFY-FAILED"
196
+
197
+ # ---- one episode --------------------------------------------------------------
198
+ def _run_one(self, ctx: EpisodeCtx, hidden, active):
199
+ for c in active:
200
+ hit = c.lookup_episode(ctx)
201
+ if hit is not None:
202
+ new_hidden = FRESH_HIDDEN if ctx.reset_experience else hit.hidden
203
+ return self._maybe_verify(
204
+ ctx,
205
+ hidden,
206
+ hit.output,
207
+ new_hidden,
208
+ f"hit:{c.name}",
209
+ state_valid=not ctx.reset_experience,
210
+ )
211
+ out, new_hidden, resume, cold_ms = self._compute(ctx, hidden, active)
212
+ event = "miss"
213
+ if resume is not None:
214
+ event = f"hit:{resume.cache_name}@{resume.position}"
215
+ out, new_hidden, event = self._maybe_verify(
216
+ ctx, hidden, out, new_hidden, event, state_valid=True
217
+ )
218
+ if not self._disabled:
219
+ for c in active:
220
+ c.store_episode(ctx, out, new_hidden, cold_ms)
221
+ return out, new_hidden, event
222
+
223
+ def run(
224
+ self,
225
+ episodes,
226
+ reset_experience: bool,
227
+ verbose_n: int = 0,
228
+ progress_every: int = 0,
229
+ label: str = "",
230
+ ) -> list[EpisodeResult]:
231
+ active = self._select_active(reset_experience)
232
+ want_bounds = any(c.wants_boundaries for c in active)
233
+ hidden, chain = FRESH_HIDDEN, ROOT_CHAIN
234
+ results: list[EpisodeResult] = []
235
+ run_items = run_correct = 0
236
+ for i, ep in enumerate(episodes):
237
+ if reset_experience:
238
+ hidden = FRESH_HIDDEN
239
+ t0 = time.perf_counter()
240
+ act = [] if self._disabled else active
241
+ ctx = EpisodeCtx(
242
+ index=i,
243
+ episode=ep,
244
+ reset_experience=reset_experience,
245
+ in_hash=hash_tensor(ep.input_seq) if act else "",
246
+ chain=chain,
247
+ boundaries=self._valid_boundaries(ep) if (want_bounds and act) else [],
248
+ )
249
+ out, hidden, event = self._run_one(ctx, hidden, act)
250
+ self._sync()
251
+ elapsed_ms = (time.perf_counter() - t0) * 1000.0
252
+ if not reset_experience: # fresh mode: chain stays "root" (state is always fresh)
253
+ chain = chain_next(chain, ctx.in_hash)
254
+
255
+ score = self.task.score_episode(out, ep, verbose=(i < verbose_n))
256
+ results.append(EpisodeResult(i, score, elapsed_ms, reset_experience, cache_event=event))
257
+ run_items += score.n_items
258
+ run_correct += score.n_correct
259
+ if progress_every and ((i + 1) % progress_every == 0 or i + 1 == len(episodes)):
260
+ print(
261
+ f"[engine{label}] episode {i + 1}/{len(episodes)} | "
262
+ f"running item acc {100.0 * run_correct / max(run_items, 1):.2f}%"
263
+ )
264
+ return results
@@ -0,0 +1,77 @@
1
+ """
2
+ file: inference/cache/keys.py
3
+
4
+ Hashing for cache keys. blake2b (fast, stdlib). A cache key always contains the
5
+ MODEL FINGERPRINT (hash of every weight + config + the flags that change the
6
+ forward pass), so entries can never leak between different checkpoints.
7
+ """
8
+
9
+ from __future__ import annotations
10
+
11
+ import hashlib
12
+ import json
13
+ from typing import Any
14
+
15
+ import torch
16
+
17
+
18
+ def new_hasher():
19
+ return hashlib.blake2b(digest_size=16)
20
+
21
+
22
+ def update_with_tensor(h, t: torch.Tensor) -> None:
23
+ t = t.detach().cpu().contiguous()
24
+ h.update(str(t.dtype).encode())
25
+ h.update(str(tuple(t.shape)).encode())
26
+ if t.numel():
27
+ h.update(t.reshape(-1).view(torch.uint8).numpy())
28
+
29
+
30
+ def hash_tensor(t: torch.Tensor) -> str:
31
+ h = new_hasher()
32
+ update_with_tensor(h, t)
33
+ return h.hexdigest()
34
+
35
+
36
+ def digest(*parts: Any) -> str:
37
+ h = new_hasher()
38
+ for p in parts:
39
+ h.update(str(p).encode())
40
+ h.update(b"\x1f")
41
+ return h.hexdigest()
42
+
43
+
44
+ def chain_next(chain: str, in_hash: str) -> str:
45
+ """Persistent mode: the state after episode k is a function of (initial state,
46
+ inputs 0..k). Hashing the inputs into a chain therefore identifies the state
47
+ exactly, without hashing the (large) memory tensors every episode."""
48
+ return digest(chain, in_hash)
49
+
50
+
51
+ def _update_obj(h, obj: Any) -> None:
52
+ if isinstance(obj, torch.Tensor):
53
+ update_with_tensor(h, obj)
54
+ elif isinstance(obj, dict):
55
+ for k in sorted(obj, key=str):
56
+ h.update(str(k).encode())
57
+ _update_obj(h, obj[k])
58
+ elif isinstance(obj, list | tuple):
59
+ for v in obj:
60
+ _update_obj(h, v)
61
+ else:
62
+ h.update(repr(obj).encode())
63
+
64
+
65
+ def model_fingerprint(ckpt: dict, extras: dict) -> str:
66
+ """Hash of weights (rnn + output_proj + prior buffers) + model_config + `extras`
67
+ (flags that alter the forward pass: deterministic_write, ablate_memory, ...).
68
+ Takes a couple of seconds for a large model; only computed when caching is on.
69
+ Deliberately NOT included: device / torch version (tiny numeric differences are
70
+ what --cache-verify is for)."""
71
+ h = new_hasher()
72
+ _update_obj(h, ckpt["rnn_state_dict"])
73
+ _update_obj(h, ckpt["output_proj_state_dict"])
74
+ _update_obj(h, ckpt.get("prior_state"))
75
+ h.update(json.dumps(ckpt["model_config"], sort_keys=True, default=str).encode())
76
+ h.update(json.dumps(extras, sort_keys=True, default=str).encode())
77
+ return h.hexdigest()
@@ -0,0 +1,59 @@
1
+ """
2
+ file: inference/cache/prefix_cache.py
3
+
4
+ Prefix STATE cache (the DNC analogue of a KV / context cache). After the static
5
+ prefix of an episode (e.g. the whole edge listing) has been processed, the complete
6
+ state (controller state, Memory dict incl. link matrix / usage / weights, last read
7
+ vector) is snapshotted, keyed by (model fingerprint, hash of the prefix tokens).
8
+ Later episodes with the SAME prefix restore it and process only their suffix.
9
+
10
+ Valid because the model is causal and deterministic: state after step p depends only
11
+ on inputs [0, p). Requires reset_experience=True (identical start state) and a task
12
+ that declares cache_boundaries. If several boundaries are declared, the LONGEST cached
13
+ one is used (longest-prefix match) and the remaining ones are stored on the way.
14
+ """
15
+
16
+ from __future__ import annotations
17
+
18
+ from initium.inference.cache.base_cache import BaseCache, EpisodeCtx, ResumePoint
19
+ from initium.inference.cache.state_utils import restore, snapshot
20
+
21
+
22
+ class PrefixStateCache(BaseCache):
23
+ name = "prefix"
24
+ wants_boundaries = True
25
+
26
+ def applicable(self, *, reset_experience: bool, resumable: bool) -> tuple[bool, str]:
27
+ if not reset_experience:
28
+ return False, (
29
+ "needs reset_experience=True: in persistent mode every episode starts "
30
+ "from a different state, so a prefix snapshot is never reusable"
31
+ )
32
+ if not resumable:
33
+ return False, (
34
+ "this model cannot resume mid-sequence (SplitGraphDNC needs the "
35
+ "start_step edit in split_graph_dnc.py)"
36
+ )
37
+ return True, ""
38
+
39
+ def _key_for(self, ctx: EpisodeCtx, boundary: int) -> str:
40
+ return self._key(boundary, ctx.prefix_hash(boundary))
41
+
42
+ def find_resume(self, ctx: EpisodeCtx) -> ResumePoint | None:
43
+ if not ctx.boundaries:
44
+ return None
45
+ self.stats.lookups += 1
46
+ for b in reversed(ctx.boundaries): # longest prefix first
47
+ got = self._get(self._key_for(ctx, b))
48
+ if got is None:
49
+ continue
50
+ entry, tier = got
51
+ self.stats.hit(tier, entry["compute_ms"])
52
+ return ResumePoint(
53
+ b, restore(entry["payload"], self.device), entry["compute_ms"], self.name
54
+ )
55
+ self.stats.misses += 1
56
+ return None
57
+
58
+ def store_boundary(self, ctx: EpisodeCtx, boundary: int, hidden, compute_ms: float) -> None:
59
+ self._put(self._key_for(ctx, boundary), snapshot(hidden), compute_ms)
@@ -0,0 +1,57 @@
1
+ """
2
+ file: inference/cache/result_cache.py
3
+
4
+ Result / lookup cache (GATI-style): memoizes the model output of a whole episode.
5
+
6
+ fresh mode : key = (model fp, "F", hash of the episode inputs). A hit skips the
7
+ forward pass entirely.
8
+ persistent mode : the output also depends on the carried-over state, so the key adds
9
+ a history CHAIN hash (see keys.chain_next) and the entry also stores
10
+ the resulting state, so the run can continue after a hit.
11
+
12
+ Exact-match only, deterministic models only (the setup gate refuses stochastic write
13
+ heads unless overridden).
14
+ """
15
+
16
+ from __future__ import annotations
17
+
18
+ import torch
19
+
20
+ from initium.inference.cache.base_cache import BaseCache, EpisodeCtx, EpisodeHit
21
+ from initium.inference.cache.state_utils import restore, snapshot
22
+
23
+
24
+ class ResultCache(BaseCache):
25
+ name = "result"
26
+
27
+ def applicable(self, *, reset_experience: bool, resumable: bool) -> tuple[bool, str]:
28
+ return True, ""
29
+
30
+ def _key_for(self, ctx: EpisodeCtx) -> str:
31
+ return self._key("F" if ctx.reset_experience else "P", ctx.chain, ctx.in_hash)
32
+
33
+ def lookup_episode(self, ctx: EpisodeCtx) -> EpisodeHit | None:
34
+ self.stats.lookups += 1
35
+ got = self._get(self._key_for(ctx))
36
+ if got is None:
37
+ self.stats.misses += 1
38
+ return None
39
+ entry, tier = got
40
+ payload = entry["payload"]
41
+ if not ctx.reset_experience and payload.get("hidden") is None:
42
+ self.stats.misses += 1
43
+ return None
44
+ self.stats.hit(tier, entry["compute_ms"])
45
+ hidden = (
46
+ restore(payload["hidden"], self.device) if payload.get("hidden") is not None else None
47
+ )
48
+ return EpisodeHit(payload["output"].clone(), hidden, entry["compute_ms"])
49
+
50
+ def store_episode(
51
+ self, ctx: EpisodeCtx, output: torch.Tensor, hidden_after, compute_ms: float
52
+ ) -> None:
53
+ payload = {
54
+ "output": output.detach().cpu().clone(),
55
+ "hidden": None if ctx.reset_experience else snapshot(hidden_after),
56
+ }
57
+ self._put(self._key_for(ctx), payload, compute_ms)
@@ -0,0 +1,74 @@
1
+ """
2
+ file: inference/cache/state_utils.py
3
+
4
+ Helpers for the nested model state (chx, mhx, last_read): controller states
5
+ (tuples/lists), the Memory dict, tensors, None. Snapshots are stored on CPU and
6
+ ALWAYS deep-copied on both sides: Memory.reset(..., hidden) rebinds entries of the
7
+ dict it is given and the write path rebinds them again, so a cached dict passed
8
+ straight into forward() would silently change under us.
9
+ """
10
+
11
+ from __future__ import annotations
12
+
13
+ import math
14
+ from collections.abc import Callable
15
+ from typing import Any
16
+
17
+ import torch
18
+
19
+
20
+ def tree_map(fn: Callable[[torch.Tensor], Any], obj: Any) -> Any:
21
+ if isinstance(obj, torch.Tensor):
22
+ return fn(obj)
23
+ if isinstance(obj, dict):
24
+ return {k: tree_map(fn, v) for k, v in obj.items()}
25
+ if isinstance(obj, list):
26
+ return [tree_map(fn, v) for v in obj]
27
+ if isinstance(obj, tuple):
28
+ items = [tree_map(fn, v) for v in obj]
29
+ return type(obj)(*items) if hasattr(obj, "_fields") else tuple(items)
30
+ return obj # None, numbers, strings
31
+
32
+
33
+ def snapshot(tree: Any) -> Any: # -> independent CPU copy
34
+ return tree_map(lambda t: t.detach().to("cpu", copy=True), tree)
35
+
36
+
37
+ def restore(tree: Any, device: torch.device) -> Any: # -> independent copy on `device`
38
+ return tree_map(lambda t: t.detach().to(device, copy=True), tree)
39
+
40
+
41
+ def clone_tree(tree: Any) -> Any:
42
+ return tree_map(lambda t: t.detach().clone(), tree)
43
+
44
+
45
+ def tree_nbytes(obj: Any) -> int:
46
+ if isinstance(obj, torch.Tensor):
47
+ return obj.numel() * obj.element_size()
48
+ if isinstance(obj, dict):
49
+ return sum(tree_nbytes(v) for v in obj.values())
50
+ if isinstance(obj, list | tuple):
51
+ return sum(tree_nbytes(v) for v in obj)
52
+ return 0
53
+
54
+
55
+ def tree_max_abs_diff(a: Any, b: Any) -> float:
56
+ """Max |a-b| over all tensors; inf on any structural mismatch; NaN propagates
57
+ (callers must test `diff <= tol`, never `diff > tol`)."""
58
+ if isinstance(a, torch.Tensor) and isinstance(b, torch.Tensor):
59
+ if a.shape != b.shape:
60
+ return math.inf
61
+ if a.numel() == 0:
62
+ return 0.0
63
+ return (a.detach().float() - b.detach().float().to(a.device)).abs().max().item()
64
+ if isinstance(a, dict) and isinstance(b, dict):
65
+ if a.keys() != b.keys():
66
+ return math.inf
67
+ return max([0.0] + [tree_max_abs_diff(a[k], b[k]) for k in a])
68
+ if isinstance(a, list | tuple) and isinstance(b, list | tuple):
69
+ if len(a) != len(b):
70
+ return math.inf
71
+ return max([0.0] + [tree_max_abs_diff(x, y) for x, y in zip(a, b)])
72
+ if a is None and b is None:
73
+ return 0.0
74
+ return 0.0 if a == b else math.inf