initium-deeplearning 0.1.0__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- initium/LNN_controller/cfc_backbone_parallel.py +265 -0
- initium/LNN_controller/cfc_controller.py +325 -0
- initium/LNN_controller/chained_controller.py +83 -0
- initium/LNN_controller/hybrid_controller.py +90 -0
- initium/MoE/README.md +1 -0
- initium/MoE/moe_layer.py +552 -0
- initium/__init__.py +1 -0
- initium/config/controller_config.py +86 -0
- initium/config/train_config.py +64 -0
- initium/core_training.py +2182 -0
- initium/data/__init__.py +0 -0
- initium/data/audio/__init__.py +0 -0
- initium/data/audio/audio_dataset.py +96 -0
- initium/data/base_dataset.py +75 -0
- initium/data/common/__init__.py +0 -0
- initium/data/common/chain_task.py +542 -0
- initium/data/common/digit_codec.py +85 -0
- initium/data/common/graph_io.py +123 -0
- initium/data/common/real_data.py +747 -0
- initium/data/dataset_registry.py +59 -0
- initium/data/graph_traversal/__init__.py +0 -0
- initium/data/graph_traversal/graph_traversal.py +746 -0
- initium/data/multimodal/__init__.py +0 -0
- initium/data/multimodal/multimodal_dataset.py +401 -0
- initium/data/text/__init__.py +0 -0
- initium/data/text/text_dataset.py +79 -0
- initium/data/video/__init__.py +0 -0
- initium/data/video/video_dataset.py +85 -0
- initium/inference/__init__.py +1 -0
- initium/inference/cache/__init__.py +1 -0
- initium/inference/cache/base_cache.py +112 -0
- initium/inference/cache/cache_config.py +21 -0
- initium/inference/cache/cache_registry.py +99 -0
- initium/inference/cache/cached_engine.py +264 -0
- initium/inference/cache/keys.py +77 -0
- initium/inference/cache/prefix_cache.py +59 -0
- initium/inference/cache/result_cache.py +57 -0
- initium/inference/cache/state_utils.py +74 -0
- initium/inference/cache/stores.py +167 -0
- initium/inference/capabilities.py +86 -0
- initium/inference/checkpoint_io.py +63 -0
- initium/inference/engine.py +88 -0
- initium/inference/inference_config.py +22 -0
- initium/inference/metrics.py +103 -0
- initium/inference/model_loader.py +270 -0
- initium/inference/run_inference.py +526 -0
- initium/inference/run_logging.py +173 -0
- initium/inference/tasks/__init__.py +1 -0
- initium/inference/tasks/audio_task.py +114 -0
- initium/inference/tasks/base_task.py +60 -0
- initium/inference/tasks/graph_traversal_task.py +235 -0
- initium/inference/tasks/multimodal_task.py +74 -0
- initium/inference/tasks/task_registry.py +68 -0
- initium/inference/tasks/text_task.py +114 -0
- initium/inference/tasks/video_task.py +114 -0
- initium/mamba_controller/mamba2_controller.py +433 -0
- initium/mamba_controller/mamba3_controller.py +242 -0
- initium/mamba_controller/mamba_backbone_parallel.py +291 -0
- initium/mamba_controller/mamba_controller.py +983 -0
- initium/mamba_controller/split_graph_dnc.py +623 -0
- initium/memory_manipulation/dynamic_memory_resize.py +157 -0
- initium/memory_manipulation/dynamic_n_controller.py +152 -0
- initium/memory_manipulation/link_matrix_ablation.py +130 -0
- initium/memory_manipulation/nvrtc_compat.py +143 -0
- initium/memory_manipulation/stochastic_write_head_v2.py +545 -0
- initium/setup_wheels.py +175 -0
- initium_deeplearning-0.1.0.dist-info/METADATA +253 -0
- initium_deeplearning-0.1.0.dist-info/RECORD +71 -0
- initium_deeplearning-0.1.0.dist-info/WHEEL +4 -0
- initium_deeplearning-0.1.0.dist-info/entry_points.txt +3 -0
- initium_deeplearning-0.1.0.dist-info/licenses/LICENSE +674 -0
|
@@ -0,0 +1,526 @@
|
|
|
1
|
+
"""
|
|
2
|
+
file: initium/inference/run_inference.py
|
|
3
|
+
|
|
4
|
+
CLI entrypoint (counterpart of core_training.py's __main__). Flow:
|
|
5
|
+
load checkpoint -> detect capabilities -> REFUSE if requested dataset type /
|
|
6
|
+
dims are unsupported -> build task -> rebuild model + load weights ->
|
|
7
|
+
(optional) set up caches -> run -> summarize/log.
|
|
8
|
+
|
|
9
|
+
Examples (from the project root):
|
|
10
|
+
python -m initium.inference.run_inference CKPT.pt
|
|
11
|
+
python -m initium.inference.run_inference CKPT.pt --inspect
|
|
12
|
+
python -m initium.inference.run_inference CKPT.pt --no-reset-experience --loop 50 --window 10 --compare-fresh
|
|
13
|
+
python -m initium.inference.run_inference CKPT.pt --dataset-link my_graph.csv --path-length 3 8
|
|
14
|
+
# caching (see initium/inference/cache/):
|
|
15
|
+
python -m initium.inference.run_inference CKPT.pt --shared-context --cache prefix
|
|
16
|
+
python -m initium.inference.run_inference CKPT.pt --shared-context --num-contexts 3 --cache prefix,result --cache-dir ./inference_cache
|
|
17
|
+
python -m initium.inference.run_inference CKPT.pt --no-reset-experience --loop 50 --cache result --cache-dir ./inference_cache
|
|
18
|
+
"""
|
|
19
|
+
|
|
20
|
+
import os
|
|
21
|
+
import sys
|
|
22
|
+
|
|
23
|
+
_ROOT = os.path.dirname(os.path.dirname(os.path.abspath(__file__)))
|
|
24
|
+
if _ROOT not in sys.path:
|
|
25
|
+
sys.path.insert(0, _ROOT)
|
|
26
|
+
|
|
27
|
+
import argparse
|
|
28
|
+
import json
|
|
29
|
+
import random
|
|
30
|
+
import time
|
|
31
|
+
|
|
32
|
+
import torch
|
|
33
|
+
|
|
34
|
+
from initium.inference.cache.cache_config import (
|
|
35
|
+
DEFAULT_CACHE,
|
|
36
|
+
DEFAULT_CACHE_DISK_MB,
|
|
37
|
+
DEFAULT_CACHE_RAM_MB,
|
|
38
|
+
DEFAULT_VERIFY_HITS,
|
|
39
|
+
)
|
|
40
|
+
from initium.inference.cache.cache_registry import parse_cache_spec, setup_caches
|
|
41
|
+
from initium.inference.cache.cached_engine import CachedInferenceEngine
|
|
42
|
+
from initium.inference.capabilities import (
|
|
43
|
+
IncompatibleModelError,
|
|
44
|
+
detect_capabilities,
|
|
45
|
+
require_dims_match,
|
|
46
|
+
require_type_supported,
|
|
47
|
+
)
|
|
48
|
+
from initium.inference.checkpoint_io import describe_checkpoint, load_checkpoint
|
|
49
|
+
from initium.inference.inference_config import (
|
|
50
|
+
DEFAULT_DATASET_LINK,
|
|
51
|
+
DEFAULT_DATASET_TYPE,
|
|
52
|
+
DEFAULT_NUM_EPISODES,
|
|
53
|
+
DEFAULT_SEED,
|
|
54
|
+
DEFAULT_VERBOSE_N,
|
|
55
|
+
DEFAULT_WINDOW,
|
|
56
|
+
INFERENCE_LOG_DIR,
|
|
57
|
+
PROGRESS_EVERY,
|
|
58
|
+
)
|
|
59
|
+
from initium.inference.model_loader import load_model
|
|
60
|
+
from initium.inference.tasks.task_registry import get_task
|
|
61
|
+
from initium.memory_manipulation.nvrtc_compat import patch_prod_jiterator
|
|
62
|
+
|
|
63
|
+
patch_prod_jiterator() # environment workaround - see that module's docstring
|
|
64
|
+
from initium.inference import run_logging as rl
|
|
65
|
+
from initium.inference.engine import InferenceEngine
|
|
66
|
+
from initium.inference.metrics import adaptation_trend, aggregate, windowed
|
|
67
|
+
|
|
68
|
+
|
|
69
|
+
def parse_args(argv=None):
|
|
70
|
+
p = argparse.ArgumentParser(
|
|
71
|
+
description="Checkpoint-driven inference. Refuses to run if the requested dataset "
|
|
72
|
+
"type is not supported by the checkpoint."
|
|
73
|
+
)
|
|
74
|
+
p.add_argument(
|
|
75
|
+
"checkpoint", type=str, help="path to a core_training checkpoint (.pt), periodic or final"
|
|
76
|
+
)
|
|
77
|
+
p.add_argument(
|
|
78
|
+
"--dataset-type",
|
|
79
|
+
type=str,
|
|
80
|
+
default=DEFAULT_DATASET_TYPE,
|
|
81
|
+
help="task type to run (graph | text | audio | video). Must be supported by the checkpoint.",
|
|
82
|
+
)
|
|
83
|
+
p.add_argument(
|
|
84
|
+
"--dataset-link",
|
|
85
|
+
type=str,
|
|
86
|
+
default=DEFAULT_DATASET_LINK,
|
|
87
|
+
help="test data location. Omit for the built-in London Underground graph; "
|
|
88
|
+
"for graph, a .csv/.tsv/.txt/.json edge file (src,dst,line).",
|
|
89
|
+
)
|
|
90
|
+
p.add_argument(
|
|
91
|
+
"--reset-experience",
|
|
92
|
+
action=argparse.BooleanOptionalAction,
|
|
93
|
+
default=True,
|
|
94
|
+
help="--reset-experience (default): every episode starts fresh (independent test). "
|
|
95
|
+
"--no-reset-experience: memory + controller state persist across episodes.",
|
|
96
|
+
)
|
|
97
|
+
p.add_argument(
|
|
98
|
+
"--loop",
|
|
99
|
+
type=int,
|
|
100
|
+
default=None,
|
|
101
|
+
help="persistent mode only: number of consecutive episodes with state carried over.",
|
|
102
|
+
)
|
|
103
|
+
p.add_argument(
|
|
104
|
+
"--num-episodes",
|
|
105
|
+
type=int,
|
|
106
|
+
default=None,
|
|
107
|
+
help=f"number of episodes (default {DEFAULT_NUM_EPISODES}); in persistent mode "
|
|
108
|
+
"use --loop instead.",
|
|
109
|
+
)
|
|
110
|
+
p.add_argument(
|
|
111
|
+
"--window",
|
|
112
|
+
type=int,
|
|
113
|
+
default=DEFAULT_WINDOW,
|
|
114
|
+
help="persistent mode: episodes per window in the adaptation table.",
|
|
115
|
+
)
|
|
116
|
+
p.add_argument(
|
|
117
|
+
"--compare-fresh",
|
|
118
|
+
action="store_true",
|
|
119
|
+
help="persistent mode: also replay the SAME episodes with reset each episode and report the delta.",
|
|
120
|
+
)
|
|
121
|
+
p.add_argument(
|
|
122
|
+
"--path-length",
|
|
123
|
+
type=int,
|
|
124
|
+
nargs=2,
|
|
125
|
+
metavar=("MIN", "MAX"),
|
|
126
|
+
default=None,
|
|
127
|
+
help="graph task: walk length range (default: the training-time OOD range).",
|
|
128
|
+
)
|
|
129
|
+
p.add_argument(
|
|
130
|
+
"--perturbation-severity",
|
|
131
|
+
type=float,
|
|
132
|
+
default=None,
|
|
133
|
+
help="Robustness probe (Layer D): if set, episodes are built with "
|
|
134
|
+
"perturbation={'severity': X} - e.g. text/audio/video token-corruption / "
|
|
135
|
+
"additive-noise / frame-dropping severity in [0,1]. Graph task ignores this "
|
|
136
|
+
"(no perturbation hook wired for it yet).",
|
|
137
|
+
)
|
|
138
|
+
p.add_argument(
|
|
139
|
+
"--shared-context",
|
|
140
|
+
action="store_true",
|
|
141
|
+
help="graph task: every episode = ONE fixed edge listing (static prefix) + a fresh "
|
|
142
|
+
"random query. Required for the prefix cache to have anything to reuse.",
|
|
143
|
+
)
|
|
144
|
+
p.add_argument(
|
|
145
|
+
"--num-contexts",
|
|
146
|
+
type=int,
|
|
147
|
+
default=1,
|
|
148
|
+
help="with --shared-context: number of distinct fixed edge listings, used round-robin.",
|
|
149
|
+
)
|
|
150
|
+
p.add_argument("--seed", type=int, default=DEFAULT_SEED)
|
|
151
|
+
p.add_argument(
|
|
152
|
+
"--device", type=str, default=None, help="default: cuda:0 if available, else cpu"
|
|
153
|
+
)
|
|
154
|
+
p.add_argument(
|
|
155
|
+
"--deterministic-write",
|
|
156
|
+
action="store_true",
|
|
157
|
+
help="disable write-vector sampling (use mu only), even if the model was trained with beta>0.",
|
|
158
|
+
)
|
|
159
|
+
p.add_argument(
|
|
160
|
+
"--ablate-memory",
|
|
161
|
+
action="store_true",
|
|
162
|
+
help="skip memory read+write (functional-usage check).",
|
|
163
|
+
)
|
|
164
|
+
p.add_argument(
|
|
165
|
+
"--ablate-combiner-stage",
|
|
166
|
+
type=str,
|
|
167
|
+
nargs="+",
|
|
168
|
+
default=None,
|
|
169
|
+
help="split-graph checkpoints with a hybrid controller combiner only "
|
|
170
|
+
"(--split-graph-combiner-mode controller, variant like 'mamba+cfc'): "
|
|
171
|
+
"bypass one or more stages by name (as listed by --inspect, e.g. 'cfc') "
|
|
172
|
+
"or 0-based index, to measure that stage's contribution on real held-out "
|
|
173
|
+
"episodes. Compare against a run with this flag omitted for the delta.",
|
|
174
|
+
)
|
|
175
|
+
p.add_argument(
|
|
176
|
+
"--report-moe-stats",
|
|
177
|
+
action="store_true",
|
|
178
|
+
help="after the run, print each installed MoE sublayer's routing "
|
|
179
|
+
"diagnostics (CV(load), CV(importance), max load fraction) from its "
|
|
180
|
+
"last forward call -- confirms MoE is actually routing/balancing on "
|
|
181
|
+
"this data, independent of task accuracy.",
|
|
182
|
+
)
|
|
183
|
+
p.add_argument(
|
|
184
|
+
"--verbose-n",
|
|
185
|
+
type=int,
|
|
186
|
+
default=DEFAULT_VERBOSE_N,
|
|
187
|
+
help="print per-item predictions for the first N episodes.",
|
|
188
|
+
)
|
|
189
|
+
p.add_argument("--log-dir", type=str, default=INFERENCE_LOG_DIR)
|
|
190
|
+
p.add_argument("--run-id-suffix", type=str, default=None)
|
|
191
|
+
p.add_argument("--no-log", action="store_true", help="do not write CSV/JSON files.")
|
|
192
|
+
p.add_argument(
|
|
193
|
+
"--model-config-override",
|
|
194
|
+
type=str,
|
|
195
|
+
default=None,
|
|
196
|
+
help="JSON dict merged into the checkpoint model_config, e.g. "
|
|
197
|
+
"'{\"split_graph_num_blocks\": 3}' (for legacy checkpoints).",
|
|
198
|
+
)
|
|
199
|
+
p.add_argument(
|
|
200
|
+
"--inspect",
|
|
201
|
+
action="store_true",
|
|
202
|
+
help="print the checkpoint's config + capabilities and exit.",
|
|
203
|
+
)
|
|
204
|
+
|
|
205
|
+
g = p.add_argument_group("caching (initium/inference/cache/)")
|
|
206
|
+
g.add_argument(
|
|
207
|
+
"--cache",
|
|
208
|
+
type=str,
|
|
209
|
+
default=DEFAULT_CACHE,
|
|
210
|
+
help="off | prefix | result | prefix,result | all. 'prefix' = snapshot the model state "
|
|
211
|
+
"after a static prefix and resume queries from it (needs --shared-context and "
|
|
212
|
+
"reset-experience). 'result' = memoize whole-episode outputs (deterministic models).",
|
|
213
|
+
)
|
|
214
|
+
g.add_argument("--no-cache", action="store_true", help="force caching off (overrides --cache).")
|
|
215
|
+
g.add_argument(
|
|
216
|
+
"--cache-dir",
|
|
217
|
+
type=str,
|
|
218
|
+
default=None,
|
|
219
|
+
help="enable the disk tier here, so caches survive across CLI invocations "
|
|
220
|
+
"(one sub-folder per model fingerprint).",
|
|
221
|
+
)
|
|
222
|
+
g.add_argument(
|
|
223
|
+
"--cache-ram-mb", type=float, default=DEFAULT_CACHE_RAM_MB, help="RAM tier size cap."
|
|
224
|
+
)
|
|
225
|
+
g.add_argument(
|
|
226
|
+
"--cache-disk-mb", type=float, default=DEFAULT_CACHE_DISK_MB, help="disk tier size cap."
|
|
227
|
+
)
|
|
228
|
+
g.add_argument(
|
|
229
|
+
"--cache-verify",
|
|
230
|
+
type=int,
|
|
231
|
+
default=DEFAULT_VERIFY_HITS,
|
|
232
|
+
help="recompute the first N cache hits without the cache and compare "
|
|
233
|
+
"(0 = off). On mismatch the cache is disabled for the rest of the run.",
|
|
234
|
+
)
|
|
235
|
+
g.add_argument(
|
|
236
|
+
"--cache-clear", action="store_true", help="wipe this model's disk cache before running."
|
|
237
|
+
)
|
|
238
|
+
g.add_argument(
|
|
239
|
+
"--cache-allow-stochastic",
|
|
240
|
+
action="store_true",
|
|
241
|
+
help="allow caching even if the model samples its write vectors (results are then "
|
|
242
|
+
"one random draw reused; verification is skipped).",
|
|
243
|
+
)
|
|
244
|
+
args = p.parse_args(argv)
|
|
245
|
+
|
|
246
|
+
if args.loop is not None and args.reset_experience:
|
|
247
|
+
p.error("--loop requires --no-reset-experience (state must persist across episodes).")
|
|
248
|
+
if args.compare_fresh and args.reset_experience:
|
|
249
|
+
p.error("--compare-fresh requires --no-reset-experience.")
|
|
250
|
+
if args.loop is not None and args.num_episodes is not None:
|
|
251
|
+
p.error("use either --loop (persistent) or --num-episodes, not both.")
|
|
252
|
+
for name in ("loop", "num_episodes"):
|
|
253
|
+
v = getattr(args, name)
|
|
254
|
+
if v is not None and v < 1:
|
|
255
|
+
p.error(f"--{name.replace('_', '-')} must be >= 1")
|
|
256
|
+
if args.num_contexts > 1 and not args.shared_context:
|
|
257
|
+
p.error("--num-contexts requires --shared-context")
|
|
258
|
+
try:
|
|
259
|
+
parse_cache_spec(args.cache)
|
|
260
|
+
except ValueError as e:
|
|
261
|
+
p.error(str(e))
|
|
262
|
+
if args.model_config_override is not None:
|
|
263
|
+
try:
|
|
264
|
+
args.model_config_override = json.loads(args.model_config_override)
|
|
265
|
+
assert isinstance(args.model_config_override, dict)
|
|
266
|
+
except (json.JSONDecodeError, AssertionError):
|
|
267
|
+
p.error("--model-config-override must be a JSON object")
|
|
268
|
+
return args
|
|
269
|
+
|
|
270
|
+
|
|
271
|
+
def main(argv=None) -> int:
|
|
272
|
+
args = parse_args(argv)
|
|
273
|
+
device = torch.device(args.device or ("cuda:0" if torch.cuda.is_available() else "cpu"))
|
|
274
|
+
print(f"[inference] device: {device}")
|
|
275
|
+
|
|
276
|
+
ckpt = load_checkpoint(args.checkpoint, args.model_config_override)
|
|
277
|
+
caps = detect_capabilities(ckpt)
|
|
278
|
+
print(f"[inference] checkpoint: {args.checkpoint}\n{describe_checkpoint(ckpt)}")
|
|
279
|
+
print(f"[inference] capabilities: {caps.describe()}")
|
|
280
|
+
if args.inspect:
|
|
281
|
+
return 0
|
|
282
|
+
|
|
283
|
+
# ---- compatibility gates (cheap, BEFORE building anything) --------------
|
|
284
|
+
try:
|
|
285
|
+
canon = require_type_supported(caps, args.dataset_type, args.checkpoint)
|
|
286
|
+
task = get_task(
|
|
287
|
+
canon,
|
|
288
|
+
args.dataset_link,
|
|
289
|
+
path_length_range=args.path_length,
|
|
290
|
+
shared_context=args.shared_context,
|
|
291
|
+
num_contexts=args.num_contexts,
|
|
292
|
+
)
|
|
293
|
+
require_dims_match(caps, task, args.checkpoint)
|
|
294
|
+
except (IncompatibleModelError, NotImplementedError, ValueError, FileNotFoundError) as e:
|
|
295
|
+
raise SystemExit(f"[inference] ABORT: {e}") from e
|
|
296
|
+
print(f"[inference] task: {task.describe()}")
|
|
297
|
+
|
|
298
|
+
torch.manual_seed(args.seed)
|
|
299
|
+
loaded = load_model(ckpt, device, deterministic_write=args.deterministic_write)
|
|
300
|
+
print(f"[inference] model loaded (step {loaded.step}, sampled_writes={loaded.sampled_writes})")
|
|
301
|
+
if loaded.combiner_stage_kinds:
|
|
302
|
+
print(f"[inference] combiner stages: {list(enumerate(loaded.combiner_stage_kinds))}")
|
|
303
|
+
|
|
304
|
+
combiner_skip_stages = None
|
|
305
|
+
if args.ablate_combiner_stage:
|
|
306
|
+
kinds = loaded.combiner_stage_kinds
|
|
307
|
+
if not kinds:
|
|
308
|
+
raise SystemExit(
|
|
309
|
+
"[inference] ABORT: --ablate-combiner-stage requires a split-graph "
|
|
310
|
+
"checkpoint with --split-graph-combiner-mode controller; this "
|
|
311
|
+
"checkpoint has no combiner stages to ablate."
|
|
312
|
+
)
|
|
313
|
+
idx = set()
|
|
314
|
+
for tok in args.ablate_combiner_stage:
|
|
315
|
+
if tok.isdigit() and 0 <= int(tok) < len(kinds):
|
|
316
|
+
idx.add(int(tok))
|
|
317
|
+
elif tok in kinds:
|
|
318
|
+
idx.add(kinds.index(tok))
|
|
319
|
+
else:
|
|
320
|
+
raise SystemExit(
|
|
321
|
+
f"[inference] ABORT: unknown combiner stage {tok!r}; "
|
|
322
|
+
f"this checkpoint's stages are {list(enumerate(kinds))}"
|
|
323
|
+
)
|
|
324
|
+
combiner_skip_stages = frozenset(idx)
|
|
325
|
+
print(
|
|
326
|
+
f"[inference] ablating combiner stage(s) "
|
|
327
|
+
f"{[(i, kinds[i]) for i in sorted(combiner_skip_stages)]}"
|
|
328
|
+
)
|
|
329
|
+
|
|
330
|
+
reset = args.reset_experience
|
|
331
|
+
if reset:
|
|
332
|
+
n = args.num_episodes if args.num_episodes is not None else DEFAULT_NUM_EPISODES
|
|
333
|
+
else:
|
|
334
|
+
n = (
|
|
335
|
+
args.loop
|
|
336
|
+
if args.loop is not None
|
|
337
|
+
else (args.num_episodes if args.num_episodes is not None else DEFAULT_NUM_EPISODES)
|
|
338
|
+
)
|
|
339
|
+
|
|
340
|
+
rng = random.Random(args.seed)
|
|
341
|
+
perturbation = (
|
|
342
|
+
{"severity": args.perturbation_severity} if args.perturbation_severity is not None else None
|
|
343
|
+
)
|
|
344
|
+
episodes = task.build_episodes(
|
|
345
|
+
n, rng, perturbation=perturbation
|
|
346
|
+
) # materialized -> identical episodes for --compare-fresh
|
|
347
|
+
|
|
348
|
+
# ---- caches ---------------------------------------------------------------
|
|
349
|
+
caches, cache_notes, cache_fp = [], [], None
|
|
350
|
+
if not args.no_cache:
|
|
351
|
+
caches, cache_notes, cache_fp = setup_caches(
|
|
352
|
+
args.cache,
|
|
353
|
+
ckpt=ckpt,
|
|
354
|
+
device=device,
|
|
355
|
+
sampled_writes=loaded.sampled_writes,
|
|
356
|
+
deterministic_write=args.deterministic_write,
|
|
357
|
+
ablate_memory=args.ablate_memory,
|
|
358
|
+
combiner_skip_stages=combiner_skip_stages,
|
|
359
|
+
cache_dir=args.cache_dir,
|
|
360
|
+
ram_mb=args.cache_ram_mb,
|
|
361
|
+
disk_mb=args.cache_disk_mb,
|
|
362
|
+
clear=args.cache_clear,
|
|
363
|
+
allow_stochastic=args.cache_allow_stochastic,
|
|
364
|
+
)
|
|
365
|
+
for note in cache_notes:
|
|
366
|
+
print(f"[inference] cache: {note}")
|
|
367
|
+
if caches:
|
|
368
|
+
assert cache_fp is not None
|
|
369
|
+
print(
|
|
370
|
+
f"[inference] cache: {', '.join(c.name for c in caches)} | model fingerprint "
|
|
371
|
+
f"{cache_fp[:12]} | RAM {args.cache_ram_mb:.0f} MB"
|
|
372
|
+
+ (f" | disk {args.cache_dir}" if args.cache_dir else "")
|
|
373
|
+
)
|
|
374
|
+
if any(c.wants_boundaries for c in caches) and not any(
|
|
375
|
+
ep.cache_boundaries for ep in episodes
|
|
376
|
+
):
|
|
377
|
+
cache_notes.append(
|
|
378
|
+
"prefix cache is on but no episode declares a static prefix "
|
|
379
|
+
"(use --shared-context); it will not hit."
|
|
380
|
+
)
|
|
381
|
+
print(f"[inference] cache: {cache_notes[-1]}")
|
|
382
|
+
|
|
383
|
+
engine: InferenceEngine | CachedInferenceEngine
|
|
384
|
+
if caches:
|
|
385
|
+
engine = CachedInferenceEngine(
|
|
386
|
+
loaded.rnn,
|
|
387
|
+
loaded.output_proj,
|
|
388
|
+
task,
|
|
389
|
+
device,
|
|
390
|
+
caches,
|
|
391
|
+
ablate_memory=args.ablate_memory,
|
|
392
|
+
combiner_skip_stages=combiner_skip_stages,
|
|
393
|
+
verify_hits=(0 if loaded.sampled_writes else args.cache_verify),
|
|
394
|
+
)
|
|
395
|
+
else:
|
|
396
|
+
engine = InferenceEngine(
|
|
397
|
+
loaded.rnn,
|
|
398
|
+
loaded.output_proj,
|
|
399
|
+
task,
|
|
400
|
+
device,
|
|
401
|
+
ablate_memory=args.ablate_memory,
|
|
402
|
+
combiner_skip_stages=combiner_skip_stages,
|
|
403
|
+
)
|
|
404
|
+
|
|
405
|
+
t0 = time.time()
|
|
406
|
+
print(f"[inference] running {n} episodes | reset_experience={reset}")
|
|
407
|
+
results = engine.run(
|
|
408
|
+
episodes, reset_experience=reset, verbose_n=args.verbose_n, progress_every=PROGRESS_EVERY
|
|
409
|
+
)
|
|
410
|
+
scores = [r.score for r in results]
|
|
411
|
+
|
|
412
|
+
tags = [
|
|
413
|
+
args.run_id_suffix,
|
|
414
|
+
("cache-" + "+".join(c.name for c in caches)) if caches else None,
|
|
415
|
+
"sharedctx" if args.shared_context else None,
|
|
416
|
+
]
|
|
417
|
+
suffix = "_".join(t for t in tags if t) or None
|
|
418
|
+
|
|
419
|
+
summary = {
|
|
420
|
+
"run_id": rl.make_run_id(args.checkpoint, task.name, reset, n, suffix),
|
|
421
|
+
"checkpoint": args.checkpoint,
|
|
422
|
+
"checkpoint_step": loaded.step,
|
|
423
|
+
"controller": caps.controller_type,
|
|
424
|
+
"supported_types": caps.supported_types,
|
|
425
|
+
"capabilities_source": caps.source,
|
|
426
|
+
"dataset_type": canon,
|
|
427
|
+
"dataset_link": args.dataset_link,
|
|
428
|
+
"task": task.describe(),
|
|
429
|
+
"mode": "fresh" if reset else "persistent",
|
|
430
|
+
"reset_experience": reset,
|
|
431
|
+
"seed": args.seed,
|
|
432
|
+
"sampled_writes": loaded.sampled_writes,
|
|
433
|
+
"ablate_memory": args.ablate_memory,
|
|
434
|
+
"result": aggregate(scores),
|
|
435
|
+
"mean_episode_ms": sum(r.elapsed_ms for r in results) / max(len(results), 1),
|
|
436
|
+
}
|
|
437
|
+
if caches:
|
|
438
|
+
assert isinstance(engine, CachedInferenceEngine)
|
|
439
|
+
summary["cache"] = {
|
|
440
|
+
"requested": args.cache,
|
|
441
|
+
"active": [c.name for c in caches],
|
|
442
|
+
"model_fingerprint": cache_fp,
|
|
443
|
+
"disk_dir": args.cache_dir,
|
|
444
|
+
"report": engine.cache_report(reset=True),
|
|
445
|
+
"verify": dict(engine.verify),
|
|
446
|
+
"store": caches[0].store.info(),
|
|
447
|
+
"notes": cache_notes + engine.notes,
|
|
448
|
+
}
|
|
449
|
+
elif cache_notes:
|
|
450
|
+
summary["cache"] = {
|
|
451
|
+
"requested": args.cache,
|
|
452
|
+
"active": [],
|
|
453
|
+
"notes": cache_notes,
|
|
454
|
+
"report": {},
|
|
455
|
+
"disk_dir": None,
|
|
456
|
+
}
|
|
457
|
+
if not reset:
|
|
458
|
+
summary["windows"] = windowed(scores, args.window)
|
|
459
|
+
summary["adaptation"] = adaptation_trend(scores, args.window)
|
|
460
|
+
if args.compare_fresh:
|
|
461
|
+
print("[inference] replaying the same episodes with reset_experience=True (baseline)")
|
|
462
|
+
fresh = engine.run(episodes, reset_experience=True, progress_every=0, label=":fresh")
|
|
463
|
+
summary["fresh_baseline"] = aggregate([r.score for r in fresh])
|
|
464
|
+
summary["persistent_minus_fresh_item_acc"] = (
|
|
465
|
+
summary["result"]["item_acc"] - summary["fresh_baseline"]["item_acc"]
|
|
466
|
+
)
|
|
467
|
+
if caches:
|
|
468
|
+
assert isinstance(engine, CachedInferenceEngine)
|
|
469
|
+
summary["cache"]["baseline_report"] = engine.cache_report(reset=True)
|
|
470
|
+
summary["cache"]["notes"] = cache_notes + engine.notes
|
|
471
|
+
summary["elapsed_sec"] = time.time() - t0
|
|
472
|
+
|
|
473
|
+
if not args.no_log:
|
|
474
|
+
os.makedirs(args.log_dir, exist_ok=True)
|
|
475
|
+
base = os.path.join(args.log_dir, summary["run_id"])
|
|
476
|
+
files = [f"{base}_episodes.csv", f"{base}_summary.json"]
|
|
477
|
+
rl.write_episode_csv(files[0], results)
|
|
478
|
+
if not reset:
|
|
479
|
+
files.insert(1, f"{base}_windows.csv")
|
|
480
|
+
rl.write_window_csv(files[1], summary["windows"])
|
|
481
|
+
summary["log_files"] = files
|
|
482
|
+
rl.write_summary_json(files[-1], summary)
|
|
483
|
+
|
|
484
|
+
if args.report_moe_stats:
|
|
485
|
+
moe_layers = getattr(loaded.rnn, "moe_layers", None)
|
|
486
|
+
if not moe_layers:
|
|
487
|
+
print("[inference] --report-moe-stats: this checkpoint has no MoE layers installed.")
|
|
488
|
+
else:
|
|
489
|
+
print(
|
|
490
|
+
f"[inference] MoE routing diagnostics ({len(moe_layers)} sublayer(s), "
|
|
491
|
+
f"from the LAST forward call only):"
|
|
492
|
+
)
|
|
493
|
+
for i, layer in enumerate(moe_layers):
|
|
494
|
+
diag = layer.last_diagnostics()
|
|
495
|
+
if not diag:
|
|
496
|
+
print(f" [{i}] no diagnostics recorded")
|
|
497
|
+
continue
|
|
498
|
+
tag = (
|
|
499
|
+
" (multi-source combiner)" if hasattr(layer, "last_source_diagnostics") else ""
|
|
500
|
+
)
|
|
501
|
+
print(
|
|
502
|
+
f" [{i}]{tag} cv_load={diag['cv_load']:.4f} "
|
|
503
|
+
f"cv_importance={diag['cv_importance']:.4f} "
|
|
504
|
+
f"max_load_frac={diag['max_load_frac']:.4f}"
|
|
505
|
+
)
|
|
506
|
+
src_diag = (
|
|
507
|
+
layer.last_source_diagnostics()
|
|
508
|
+
if hasattr(layer, "last_source_diagnostics")
|
|
509
|
+
else None
|
|
510
|
+
)
|
|
511
|
+
if src_diag:
|
|
512
|
+
for s_idx, s in enumerate(src_diag):
|
|
513
|
+
frac_str = ", ".join(
|
|
514
|
+
f"e{e}:{f:.2f}" for e, f in enumerate(s["expert_frac"])
|
|
515
|
+
)
|
|
516
|
+
print(
|
|
517
|
+
f" source {s_idx}: top_expert={s['top_expert']} "
|
|
518
|
+
f"({s['top_expert_frac']:.2f}) | dist [{frac_str}]"
|
|
519
|
+
)
|
|
520
|
+
|
|
521
|
+
rl.print_summary(summary)
|
|
522
|
+
return 0
|
|
523
|
+
|
|
524
|
+
|
|
525
|
+
if __name__ == "__main__":
|
|
526
|
+
sys.exit(main())
|
|
@@ -0,0 +1,173 @@
|
|
|
1
|
+
"""
|
|
2
|
+
file: inference/run_logging.py
|
|
3
|
+
|
|
4
|
+
Output for an inference run: per-episode CSV, window CSV (persistent mode),
|
|
5
|
+
summary JSON, and the console summary. (Named run_logging to avoid shadowing
|
|
6
|
+
the stdlib `logging` module.)
|
|
7
|
+
"""
|
|
8
|
+
|
|
9
|
+
from __future__ import annotations
|
|
10
|
+
|
|
11
|
+
import csv
|
|
12
|
+
import json
|
|
13
|
+
import os
|
|
14
|
+
import re
|
|
15
|
+
|
|
16
|
+
from initium.inference.metrics import EpisodeResult
|
|
17
|
+
|
|
18
|
+
|
|
19
|
+
def make_run_id(
|
|
20
|
+
ckpt_path: str,
|
|
21
|
+
task_name: str,
|
|
22
|
+
reset_experience: bool,
|
|
23
|
+
n_episodes: int,
|
|
24
|
+
suffix: str | None = None,
|
|
25
|
+
) -> str:
|
|
26
|
+
stem = os.path.splitext(os.path.basename(ckpt_path))[0]
|
|
27
|
+
mode = "fresh" if reset_experience else f"persist{n_episodes}"
|
|
28
|
+
rid = f"infer_{stem}_{task_name}_{mode}" + (f"_{suffix}" if suffix else "")
|
|
29
|
+
return re.sub(r"[^A-Za-z0-9_.-]", "_", rid)
|
|
30
|
+
|
|
31
|
+
|
|
32
|
+
def write_episode_csv(path: str, results: list[EpisodeResult]) -> None:
|
|
33
|
+
names = sorted({n for r in results for n in r.score.fields})
|
|
34
|
+
header = [
|
|
35
|
+
"episode",
|
|
36
|
+
"reset_experience",
|
|
37
|
+
"n_items",
|
|
38
|
+
"n_correct",
|
|
39
|
+
"item_acc",
|
|
40
|
+
"perfect",
|
|
41
|
+
"group",
|
|
42
|
+
"elapsed_ms",
|
|
43
|
+
"cache_event",
|
|
44
|
+
]
|
|
45
|
+
for n in names:
|
|
46
|
+
header += [f"{n}_correct", f"{n}_total"]
|
|
47
|
+
with open(path, "w", newline="") as f:
|
|
48
|
+
w = csv.writer(f)
|
|
49
|
+
w.writerow(header)
|
|
50
|
+
for r in results:
|
|
51
|
+
s = r.score
|
|
52
|
+
row = [
|
|
53
|
+
r.index,
|
|
54
|
+
int(r.reset_experience),
|
|
55
|
+
s.n_items,
|
|
56
|
+
s.n_correct,
|
|
57
|
+
s.item_acc,
|
|
58
|
+
int(s.perfect),
|
|
59
|
+
s.group,
|
|
60
|
+
r.elapsed_ms,
|
|
61
|
+
r.cache_event,
|
|
62
|
+
]
|
|
63
|
+
for n in names:
|
|
64
|
+
c, t = s.fields.get(n, ("", ""))
|
|
65
|
+
row += [c, t]
|
|
66
|
+
w.writerow(row)
|
|
67
|
+
|
|
68
|
+
|
|
69
|
+
def write_window_csv(path: str, windows: list[dict]) -> None:
|
|
70
|
+
names = sorted({n for w in windows for n in w["field_acc"]})
|
|
71
|
+
with open(path, "w", newline="") as f:
|
|
72
|
+
wr = csv.writer(f)
|
|
73
|
+
wr.writerow(
|
|
74
|
+
[
|
|
75
|
+
"window_index",
|
|
76
|
+
"start_episode",
|
|
77
|
+
"end_episode",
|
|
78
|
+
"n_episodes",
|
|
79
|
+
"item_acc",
|
|
80
|
+
"perfect_frac",
|
|
81
|
+
]
|
|
82
|
+
+ [f"{n}_acc" for n in names]
|
|
83
|
+
)
|
|
84
|
+
for w in windows:
|
|
85
|
+
wr.writerow(
|
|
86
|
+
[
|
|
87
|
+
w["window_index"],
|
|
88
|
+
w["start_episode"],
|
|
89
|
+
w["end_episode"],
|
|
90
|
+
w["n_episodes"],
|
|
91
|
+
w["item_acc"],
|
|
92
|
+
w["perfect_frac"],
|
|
93
|
+
]
|
|
94
|
+
+ [w["field_acc"].get(n, "") for n in names]
|
|
95
|
+
)
|
|
96
|
+
|
|
97
|
+
|
|
98
|
+
def write_summary_json(path: str, summary: dict) -> None:
|
|
99
|
+
with open(path, "w") as f:
|
|
100
|
+
json.dump(summary, f, indent=2, default=str)
|
|
101
|
+
|
|
102
|
+
|
|
103
|
+
def print_summary(s: dict) -> None:
|
|
104
|
+
r = s["result"]
|
|
105
|
+
print("\n===== Inference summary =====")
|
|
106
|
+
print(f"run_id : {s['run_id']}")
|
|
107
|
+
print(
|
|
108
|
+
f"checkpoint : {s['checkpoint']} (step {s['checkpoint_step']}, controller {s['controller']})"
|
|
109
|
+
)
|
|
110
|
+
print(f"task : {s['task']}")
|
|
111
|
+
print(
|
|
112
|
+
f"mode : {s['mode']} (reset_experience={s['reset_experience']}) | "
|
|
113
|
+
f"{r['n_episodes']} episodes | seed {s['seed']} | "
|
|
114
|
+
f"sampled_writes={s['sampled_writes']} | ablate_memory={s['ablate_memory']}"
|
|
115
|
+
)
|
|
116
|
+
print(f"overall : item acc {r['item_acc']:.2f}% | perfect {r['perfect_frac']:.2f}%")
|
|
117
|
+
if r["by_group"]:
|
|
118
|
+
print(
|
|
119
|
+
"by group : "
|
|
120
|
+
+ " | ".join(
|
|
121
|
+
f"{g}: {v['item_acc']:.1f}% (n={v['n_episodes']})" for g, v in r["by_group"].items()
|
|
122
|
+
)
|
|
123
|
+
)
|
|
124
|
+
if r["field_acc"]:
|
|
125
|
+
print("fields : " + " | ".join(f"{n} {a:.1f}%" for n, a in r["field_acc"].items()))
|
|
126
|
+
if s.get("windows"):
|
|
127
|
+
print(
|
|
128
|
+
"windows : "
|
|
129
|
+
+ " | ".join(
|
|
130
|
+
f"[{w['start_episode']}-{w['end_episode']}] {w['item_acc']:.1f}%"
|
|
131
|
+
for w in s["windows"]
|
|
132
|
+
)
|
|
133
|
+
)
|
|
134
|
+
a = s.get("adaptation")
|
|
135
|
+
if a:
|
|
136
|
+
print(
|
|
137
|
+
f"adaptation : first {a['window']} eps {a['first_window_item_acc']:.2f}% -> "
|
|
138
|
+
f"last {a['window']} eps {a['last_window_item_acc']:.2f}% "
|
|
139
|
+
f"(delta {a['delta_item_acc']:+.2f}) | slope {a['slope_acc_per_episode']:+.4f} acc-pts/episode"
|
|
140
|
+
)
|
|
141
|
+
fb = s.get("fresh_baseline")
|
|
142
|
+
if fb:
|
|
143
|
+
print(
|
|
144
|
+
f"fresh base : item acc {fb['item_acc']:.2f}% | perfect {fb['perfect_frac']:.2f}% "
|
|
145
|
+
f"(same episodes, reset each) -> persistent minus fresh: "
|
|
146
|
+
f"{s['persistent_minus_fresh_item_acc']:+.2f} acc-pts"
|
|
147
|
+
)
|
|
148
|
+
c = s.get("cache")
|
|
149
|
+
if c:
|
|
150
|
+
print(
|
|
151
|
+
f"cache : requested={c['requested']} | active={c['active'] or 'none'}"
|
|
152
|
+
+ (f" | disk={c['disk_dir']}" if c.get("disk_dir") else "")
|
|
153
|
+
)
|
|
154
|
+
for label, key in (("run", "report"), ("fresh baseline", "baseline_report")):
|
|
155
|
+
for name, r in (c.get(key) or {}).items():
|
|
156
|
+
print(
|
|
157
|
+
f" [{label}] {name}: {r['hits']}/{r['lookups']} hits ({r['hit_rate']:.0f}%) "
|
|
158
|
+
f"| ram {r['ram_hits']} disk {r['disk_hits']} | stores {r['stores']} "
|
|
159
|
+
f"| est. compute saved ~{r['est_saved_ms']:.0f} ms"
|
|
160
|
+
)
|
|
161
|
+
v = c.get("verify")
|
|
162
|
+
if v and v["checked"]:
|
|
163
|
+
print(
|
|
164
|
+
f" verify: {v['checked']} hit(s) recomputed uncached | max |d output| "
|
|
165
|
+
f"{v['max_output_diff']:.2e} | max |d state| {v['max_state_diff']:.2e} "
|
|
166
|
+
f"| failures {v['failures']}"
|
|
167
|
+
)
|
|
168
|
+
for note in c.get("notes", []):
|
|
169
|
+
print(f" note: {note}")
|
|
170
|
+
print(f"speed : mean {s['mean_episode_ms']:.1f} ms/episode")
|
|
171
|
+
print(f"elapsed : {s['elapsed_sec']:.1f}s")
|
|
172
|
+
if s.get("log_files"):
|
|
173
|
+
print("logs : " + ", ".join(s["log_files"]))
|