code-loader 1.0.195.dev1__py3-none-any.whl → 1.0.196.dev1__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.
- code_loader/contract/datasetclasses.py +45 -10
- code_loader/inner_leap_binder/leapbinder.py +146 -5
- code_loader/inner_leap_binder/leapbinder_decorators.py +859 -105
- code_loader/leaploader.py +247 -64
- code_loader/leaploaderbase.py +67 -3
- code_loader/utils.py +57 -4
- {code_loader-1.0.195.dev1.dist-info → code_loader-1.0.196.dev1.dist-info}/METADATA +1 -1
- {code_loader-1.0.195.dev1.dist-info → code_loader-1.0.196.dev1.dist-info}/RECORD +10 -10
- {code_loader-1.0.195.dev1.dist-info → code_loader-1.0.196.dev1.dist-info}/LICENSE +0 -0
- {code_loader-1.0.195.dev1.dist-info → code_loader-1.0.196.dev1.dist-info}/WHEEL +0 -0
code_loader/leaploader.py
CHANGED
|
@@ -7,7 +7,7 @@ import sys
|
|
|
7
7
|
from contextlib import redirect_stdout
|
|
8
8
|
from functools import lru_cache
|
|
9
9
|
from pathlib import Path
|
|
10
|
-
from typing import Dict, List, Iterable, Set, Union, Any, Type, Optional, Callable, Tuple
|
|
10
|
+
from typing import Dict, List, Iterable, Set, FrozenSet, Union, Any, Type, Optional, Callable, Tuple
|
|
11
11
|
|
|
12
12
|
import numpy as np
|
|
13
13
|
import numpy.typing as npt
|
|
@@ -27,7 +27,8 @@ from code_loader.contract.sim_config import FloatBounds, IntBounds, CategoricalB
|
|
|
27
27
|
from code_loader.inner_leap_binder import global_leap_binder
|
|
28
28
|
from code_loader.inner_leap_binder.leapbinder import mapping_runtime_mode_env_var_mame
|
|
29
29
|
from code_loader.leaploaderbase import LeapLoaderBase
|
|
30
|
-
from code_loader.utils import get_root_exception_file_and_line_number, get_metadata_type_from_variable
|
|
30
|
+
from code_loader.utils import get_root_exception_file_and_line_number, get_metadata_type_from_variable, \
|
|
31
|
+
validate_autoregressive_state_types, autoregressive_nests_equal
|
|
31
32
|
|
|
32
33
|
|
|
33
34
|
def _serialize_sim_bounds(bounds) -> dict:
|
|
@@ -76,13 +77,14 @@ class LeapLoader(LeapLoaderBase):
|
|
|
76
77
|
preprocess_result = global_leap_binder.get_preprocess_result()
|
|
77
78
|
self._preprocess_result_cached = preprocess_result
|
|
78
79
|
is_grouped = any(r.is_grouped for r in preprocess_result.values())
|
|
79
|
-
except Exception:
|
|
80
|
-
is_grouped = False
|
|
81
80
|
finally:
|
|
82
81
|
os.environ[mapping_runtime_mode_env_var_mame] = 'TRUE'
|
|
83
82
|
|
|
84
83
|
_leap_dec._mapping_dataset_is_grouped = is_grouped
|
|
85
84
|
if global_leap_binder.integration_test_func is not None:
|
|
85
|
+
from code_loader.inner_leap_binder.leapbinder_decorators import \
|
|
86
|
+
_reset_model_loop_state
|
|
87
|
+
_reset_model_loop_state()
|
|
86
88
|
mapping_preprocess = (
|
|
87
89
|
PreprocessResponse(sample_ids=[["__mapping_placeholder__"]], state=DataStateType.training)
|
|
88
90
|
if is_grouped else
|
|
@@ -230,7 +232,7 @@ class LeapLoader(LeapLoaderBase):
|
|
|
230
232
|
additional = self._preprocess_result().get(DataStateEnum.additional)
|
|
231
233
|
if additional is None:
|
|
232
234
|
return set()
|
|
233
|
-
return set(additional.
|
|
235
|
+
return set(additional.flat_sample_ids)
|
|
234
236
|
|
|
235
237
|
def _resolve_synthetic(self, sample_id: Union[int, str],
|
|
236
238
|
state: Optional[DataStateEnum] = None
|
|
@@ -505,31 +507,52 @@ class LeapLoader(LeapLoaderBase):
|
|
|
505
507
|
try:
|
|
506
508
|
preprocess_result = self._preprocess_result()
|
|
507
509
|
input_shapes: Dict[str, List[int]] = {}
|
|
510
|
+
expected_keys: Optional[FrozenSet[str]] = None
|
|
511
|
+
expected_keys_state = ''
|
|
508
512
|
for state, preprocess_response in preprocess_result.items():
|
|
509
513
|
if preprocess_response.sample_ids_to_instance_mappings:
|
|
510
514
|
raise Exception('Element instances are not supported together with '
|
|
511
515
|
'tensorleap_autoregressive_step.')
|
|
512
|
-
|
|
513
|
-
|
|
514
|
-
|
|
515
|
-
|
|
516
|
+
# Grouped preprocess is fine: grouping only amortizes the shared base reads
|
|
517
|
+
# (GT/metadata/custom_latent) — the AR hook always drives a single scalar chain
|
|
518
|
+
# and is never handed a group.
|
|
519
|
+
sample_id = preprocess_response.flat_sample_ids[0]
|
|
520
|
+
first_result = handler.function(sample_id, None, None, None, preprocess_response)
|
|
521
|
+
second_result = handler.function(sample_id, None, None, None, preprocess_response)
|
|
522
|
+
if not isinstance(first_result, tuple) or len(first_result) != 2:
|
|
523
|
+
raise Exception('The autoregressive step hook must return a (next_inputs, '
|
|
524
|
+
f'state) tuple, got {type(first_result)} '
|
|
525
|
+
f'(state: {state.name}).')
|
|
526
|
+
first_inputs, first_state = first_result
|
|
527
|
+
if not isinstance(first_inputs, dict) or not first_inputs:
|
|
516
528
|
raise Exception('The autoregressive step hook must return a dict of initial model '
|
|
517
|
-
f'inputs on its first call, got {type(
|
|
529
|
+
f'inputs on its first call, got {type(first_inputs)} '
|
|
518
530
|
f'(state: {state.name}).')
|
|
519
|
-
|
|
520
|
-
|
|
531
|
+
validate_autoregressive_state_types(first_state)
|
|
532
|
+
if not isinstance(second_result, tuple) or len(second_result) != 2 or \
|
|
533
|
+
not isinstance(second_result[0], dict) or \
|
|
534
|
+
set(second_result[0].keys()) != set(first_inputs.keys()) or \
|
|
535
|
+
not autoregressive_nests_equal(first_state, second_result[1]):
|
|
521
536
|
raise Exception('The autoregressive step hook is not deterministic — two calls '
|
|
522
537
|
'with identical arguments returned different results '
|
|
523
538
|
f'(state: {state.name}). Seed any stochasticity from '
|
|
524
539
|
'sample_id: the engine replays steps after crash recovery '
|
|
525
540
|
'and relies on identical results.')
|
|
526
|
-
|
|
527
|
-
|
|
528
|
-
|
|
529
|
-
|
|
530
|
-
|
|
531
|
-
f'
|
|
532
|
-
|
|
541
|
+
if expected_keys is None:
|
|
542
|
+
expected_keys = frozenset(first_inputs)
|
|
543
|
+
expected_keys_state = state.name
|
|
544
|
+
elif frozenset(first_inputs) != expected_keys:
|
|
545
|
+
raise Exception(f'The autoregressive step hook returned different model input '
|
|
546
|
+
f'keys across states: {sorted(expected_keys)} '
|
|
547
|
+
f'({expected_keys_state}) vs {sorted(first_inputs)} '
|
|
548
|
+
f'({state.name}). The key set must be identical for every '
|
|
549
|
+
f'state.')
|
|
550
|
+
for key, value in first_inputs.items():
|
|
551
|
+
if key.startswith('_'):
|
|
552
|
+
raise Exception(f'The autoregressive step hook returned model input key '
|
|
553
|
+
f'"{key}" — underscore passthrough keys were replaced by '
|
|
554
|
+
f'the explicit state channel; move passthrough values '
|
|
555
|
+
f'into the returned state. State: {state.name}.')
|
|
533
556
|
if not isinstance(value, np.ndarray):
|
|
534
557
|
raise Exception(f'The autoregressive step hook returned a non-numpy value for '
|
|
535
558
|
f'model input "{key}" ({type(value)}). State: {state.name}.')
|
|
@@ -538,7 +561,7 @@ class LeapLoader(LeapLoaderBase):
|
|
|
538
561
|
f'model input "{key}" across states: {input_shapes[key]} vs '
|
|
539
562
|
f'{list(value.shape)}. Shapes must be fixed.')
|
|
540
563
|
input_shapes[key] = list(value.shape)
|
|
541
|
-
if not
|
|
564
|
+
if not autoregressive_nests_equal(value, second_result[0][key]):
|
|
542
565
|
raise Exception('The autoregressive step hook is not deterministic — two calls '
|
|
543
566
|
'with identical arguments returned different outputs for model '
|
|
544
567
|
f'input "{key}". Seed any stochasticity from sample_id: the '
|
|
@@ -552,19 +575,39 @@ class LeapLoader(LeapLoaderBase):
|
|
|
552
575
|
test_result.is_passed = False
|
|
553
576
|
return test_result
|
|
554
577
|
|
|
578
|
+
@staticmethod
|
|
579
|
+
def _validate_autoregressive_step_inputs(step_inputs: Dict[str, Any],
|
|
580
|
+
sample_id: Union[int, str]) -> None:
|
|
581
|
+
# The parse-time check only covers the first sample of each state; the engine runtime
|
|
582
|
+
# paths must enforce the same key/value contract for every sample they touch.
|
|
583
|
+
for key, value in step_inputs.items():
|
|
584
|
+
if key.startswith('_'):
|
|
585
|
+
raise Exception(f'The autoregressive step hook returned model input key "{key}" '
|
|
586
|
+
f'for sample {sample_id} — underscore passthrough keys were '
|
|
587
|
+
'replaced by the explicit state channel; move passthrough values '
|
|
588
|
+
'into the returned state.')
|
|
589
|
+
if not isinstance(value, np.ndarray):
|
|
590
|
+
raise Exception('The autoregressive step hook returned a non-numpy value for '
|
|
591
|
+
f'model input "{key}" ({type(value).__name__}) for sample '
|
|
592
|
+
f'{sample_id}.')
|
|
593
|
+
|
|
555
594
|
def _ensure_autoregressive_input_shapes(self) -> Dict[str, List[int]]:
|
|
556
595
|
handler = global_leap_binder.setup_container.autoregressive_step
|
|
557
596
|
assert handler is not None
|
|
558
597
|
if handler.input_shapes is None:
|
|
559
598
|
preprocess_result = self._preprocess_result()
|
|
560
599
|
first_state_response = next(iter(preprocess_result.values()))
|
|
561
|
-
|
|
600
|
+
first_sample_id = first_state_response.flat_sample_ids[0]
|
|
601
|
+
first_result = handler.function(first_sample_id, None, None, None,
|
|
562
602
|
first_state_response)
|
|
563
|
-
if not isinstance(first_result,
|
|
564
|
-
|
|
565
|
-
|
|
566
|
-
|
|
567
|
-
|
|
603
|
+
if not isinstance(first_result, tuple) or len(first_result) != 2 or \
|
|
604
|
+
not isinstance(first_result[0], dict):
|
|
605
|
+
raise Exception('The autoregressive step hook must return a (next_inputs, state) '
|
|
606
|
+
'tuple with a dict of initial model inputs on its first call, '
|
|
607
|
+
f'got {type(first_result)}.')
|
|
608
|
+
self._validate_autoregressive_step_inputs(first_result[0], first_sample_id)
|
|
609
|
+
handler.input_shapes = {key: list(value.shape)
|
|
610
|
+
for key, value in first_result[0].items()}
|
|
568
611
|
return handler.input_shapes
|
|
569
612
|
|
|
570
613
|
def run_simulation(self, sim_name, params=None, n_samples=1, seed=0,
|
|
@@ -833,6 +876,21 @@ class LeapLoader(LeapLoaderBase):
|
|
|
833
876
|
metric_inst = MetricInstance(metric.metric_handler_data.name, metric.metric_handler_data.arg_names)
|
|
834
877
|
metrics.append(metric_inst)
|
|
835
878
|
|
|
879
|
+
# Autoregressive decorators are reported alongside the regular ones so the platform sees
|
|
880
|
+
# their names; the engine tells them apart by name via get_autoregressive_decorator_names.
|
|
881
|
+
for autoregressive_metric in setup.autoregressive_metrics:
|
|
882
|
+
metrics.append(MetricInstance(autoregressive_metric.metric_handler_data.name,
|
|
883
|
+
autoregressive_metric.metric_handler_data.arg_names))
|
|
884
|
+
for autoregressive_loss in setup.autoregressive_losses:
|
|
885
|
+
custom_losses.append(CustomLossInstance(
|
|
886
|
+
autoregressive_loss.custom_loss_handler_data.name,
|
|
887
|
+
autoregressive_loss.custom_loss_handler_data.arg_names))
|
|
888
|
+
for autoregressive_visualizer in setup.autoregressive_visualizers:
|
|
889
|
+
visualizers.append(VisualizerInstance(
|
|
890
|
+
autoregressive_visualizer.visualizer_handler_data.name,
|
|
891
|
+
autoregressive_visualizer.visualizer_handler_data.type,
|
|
892
|
+
autoregressive_visualizer.visualizer_handler_data.arg_names))
|
|
893
|
+
|
|
836
894
|
simulations = []
|
|
837
895
|
for sim in setup.simulations:
|
|
838
896
|
sim_config_serialized = {
|
|
@@ -909,13 +967,12 @@ class LeapLoader(LeapLoaderBase):
|
|
|
909
967
|
|
|
910
968
|
def _locate_group(self, preprocess_state: PreprocessResponse,
|
|
911
969
|
sample_id: Union[int, str]) -> Tuple[List[Union[int, str]], int]:
|
|
912
|
-
"""Map a sample id to (its group, its position in that group). Cached
|
|
913
|
-
PreprocessResponse so grouped single-sample
|
|
914
|
-
|
|
915
|
-
|
|
916
|
-
|
|
917
|
-
|
|
918
|
-
mapping = cache.get(id(preprocess_state))
|
|
970
|
+
"""Map a sample id to (its group, its position in that group). Cached on the
|
|
971
|
+
PreprocessResponse itself (not keyed by id() on this loader) so grouped single-sample
|
|
972
|
+
fetches don't rescan the groups, and the cache can never go stale: when the response is
|
|
973
|
+
replaced (e.g. unlabeled preprocess refresh), the old object and its cache are simply
|
|
974
|
+
discarded together instead of lingering under a possibly-reused id()."""
|
|
975
|
+
mapping = preprocess_state._group_pos_cache
|
|
919
976
|
if mapping is None:
|
|
920
977
|
mapping = {}
|
|
921
978
|
for group in preprocess_state.groups:
|
|
@@ -925,7 +982,7 @@ class LeapLoader(LeapLoaderBase):
|
|
|
925
982
|
f"Duplicate sample id {sid!r} across groups in the preprocess response; "
|
|
926
983
|
f"sample ids must be unique within and across groups.")
|
|
927
984
|
mapping[sid] = (group, pos)
|
|
928
|
-
|
|
985
|
+
preprocess_state._group_pos_cache = mapping
|
|
929
986
|
if sample_id not in mapping:
|
|
930
987
|
raise KeyError(f"Sample id {sample_id!r} is not present in any group of this preprocess response.")
|
|
931
988
|
return mapping[sample_id]
|
|
@@ -961,18 +1018,47 @@ class LeapLoader(LeapLoaderBase):
|
|
|
961
1018
|
samples into the model's B is the engine's job. This is the path a group-aware engine calls
|
|
962
1019
|
to read a file once per group. Row order matches group_ids exactly."""
|
|
963
1020
|
self.exec_script()
|
|
964
|
-
|
|
965
|
-
if
|
|
966
|
-
|
|
967
|
-
#
|
|
968
|
-
#
|
|
969
|
-
|
|
970
|
-
|
|
971
|
-
|
|
972
|
-
|
|
973
|
-
|
|
1021
|
+
preprocess_result = self._preprocess_result()
|
|
1022
|
+
if state == DataStateEnum.unlabeled and any(
|
|
1023
|
+
sid not in preprocess_result[state].flat_sample_ids for sid in group_ids):
|
|
1024
|
+
# Mirrors get_sample's refresh: the unlabeled preprocess can grow between calls, so a
|
|
1025
|
+
# group_id absent from the current snapshot may just not have been generated yet.
|
|
1026
|
+
self._preprocess_result(update_unlabeled_preprocess=True)
|
|
1027
|
+
preprocess_state = preprocess_result[state]
|
|
1028
|
+
assert preprocess_state.is_grouped, (
|
|
1029
|
+
"get_samples is the group-aware fetch path and requires a grouped preprocess "
|
|
1030
|
+
"response; call get_sample for a flat (non-grouped) dataset.")
|
|
1031
|
+
# All requested ids must belong to a single group (one file). A cross-group request
|
|
1032
|
+
# would force the encoder to load multiple files, defeating the one-load-per-group
|
|
1033
|
+
# contract; partitioning a scattered request by group is the engine's responsibility.
|
|
1034
|
+
distinct_groups = {id(self._locate_group(preprocess_state, sid)[0]) for sid in group_ids}
|
|
1035
|
+
assert len(distinct_groups) == 1, (
|
|
1036
|
+
f"get_samples received sample ids spanning {len(distinct_groups)} groups; a request "
|
|
1037
|
+
f"must be confined to a single group. The engine partitions cross-group requests and "
|
|
1038
|
+
f"issues one get_samples call per group.")
|
|
974
1039
|
inputs = {handler.name: self._to_grouped_list(handler.function(group_ids, preprocess_state))
|
|
975
1040
|
for handler in global_leap_binder.setup_container.inputs}
|
|
1041
|
+
autoregressive_handler = global_leap_binder.setup_container.autoregressive_step
|
|
1042
|
+
if autoregressive_handler is not None:
|
|
1043
|
+
# AR integrations have no input encoders — the hook's first call supplies each
|
|
1044
|
+
# chain's step-0 model inputs. The hook is strictly per-scalar-chain (never handed
|
|
1045
|
+
# a group), so it is called once per flat id in the group and the per-sample results
|
|
1046
|
+
# are collected into per-key lists of length B, matching the grouped (never-stacked)
|
|
1047
|
+
# contract used for every other encoder here.
|
|
1048
|
+
step_zero_per_sample = []
|
|
1049
|
+
for sid in group_ids:
|
|
1050
|
+
step_zero_result = autoregressive_handler.function(sid, None, None, None,
|
|
1051
|
+
preprocess_state)
|
|
1052
|
+
if not isinstance(step_zero_result, tuple) or len(step_zero_result) != 2 or \
|
|
1053
|
+
not isinstance(step_zero_result[0], dict):
|
|
1054
|
+
raise Exception(
|
|
1055
|
+
'The autoregressive step hook must return a (next_inputs, state) tuple '
|
|
1056
|
+
f'with a dict of initial model inputs on its first call, got '
|
|
1057
|
+
f'{type(step_zero_result)} for sample {sid}.')
|
|
1058
|
+
self._validate_autoregressive_step_inputs(step_zero_result[0], sid)
|
|
1059
|
+
step_zero_per_sample.append(step_zero_result[0])
|
|
1060
|
+
for key in (step_zero_per_sample[0].keys() if step_zero_per_sample else []):
|
|
1061
|
+
inputs[key] = [row[key] for row in step_zero_per_sample]
|
|
976
1062
|
gt = None
|
|
977
1063
|
if state != DataStateEnum.unlabeled:
|
|
978
1064
|
gt = {handler.name: self._to_grouped_list(handler.function(group_ids, preprocess_state))
|
|
@@ -1014,13 +1100,16 @@ class LeapLoader(LeapLoaderBase):
|
|
|
1014
1100
|
# the sample's (step-0) model inputs, so every sample-fetching flow (graph validation,
|
|
1015
1101
|
# analysis, import checks) sees a fully-populated inputs dict.
|
|
1016
1102
|
preprocess_response = self._preprocess_result()[state]
|
|
1017
|
-
|
|
1018
|
-
|
|
1103
|
+
step_zero_result = autoregressive_handler.function(sample_id, None, None, None,
|
|
1104
|
+
preprocess_response)
|
|
1105
|
+
if not isinstance(step_zero_result, tuple) or len(step_zero_result) != 2 or \
|
|
1106
|
+
not isinstance(step_zero_result[0], dict):
|
|
1019
1107
|
raise Exception(
|
|
1020
|
-
'The autoregressive step hook must return a
|
|
1021
|
-
f'first call, got {type(
|
|
1022
|
-
|
|
1023
|
-
|
|
1108
|
+
'The autoregressive step hook must return a (next_inputs, state) tuple with a '
|
|
1109
|
+
f'dict of initial model inputs on its first call, got {type(step_zero_result)} '
|
|
1110
|
+
f'for sample {sample_id}.')
|
|
1111
|
+
self._validate_autoregressive_step_inputs(step_zero_result[0], sample_id)
|
|
1112
|
+
inputs.update(step_zero_result[0])
|
|
1024
1113
|
return inputs
|
|
1025
1114
|
|
|
1026
1115
|
def _get_instances_masks(self, state: DataStateEnum, sample_id: Union[int, str], instance_id: int) -> Optional[Dict[str, ElementInstance]]:
|
|
@@ -1227,12 +1316,15 @@ class LeapLoader(LeapLoaderBase):
|
|
|
1227
1316
|
def run_autoregressive_step(self, sample_id: Union[int, str],
|
|
1228
1317
|
prev_inputs: Optional[Dict[str, npt.NDArray[np.float32]]],
|
|
1229
1318
|
prev_outputs: Optional[Dict[str, npt.NDArray[np.float32]]],
|
|
1230
|
-
|
|
1319
|
+
chain_state: Any,
|
|
1320
|
+
state: DataStateEnum
|
|
1321
|
+
) -> Tuple[Optional[Dict[str, npt.NDArray[np.float32]]], Any]:
|
|
1231
1322
|
"""Execute the user's autoregressive step hook against the in-process preprocess result.
|
|
1232
1323
|
|
|
1233
1324
|
This is the engine rollout's per-step entry point (invoked on the generic pod). The first
|
|
1234
|
-
call per chain passes prev_inputs=None, prev_outputs=None and returns
|
|
1235
|
-
inputs; a None
|
|
1325
|
+
call per chain passes prev_inputs=None, prev_outputs=None, chain_state=None and returns
|
|
1326
|
+
the initial model inputs plus the initial state; a None next_inputs ends the chain — the
|
|
1327
|
+
terminating call still returns the final state.
|
|
1236
1328
|
"""
|
|
1237
1329
|
self.exec_script()
|
|
1238
1330
|
handler = global_leap_binder.setup_container.autoregressive_step
|
|
@@ -1240,28 +1332,119 @@ class LeapLoader(LeapLoaderBase):
|
|
|
1240
1332
|
raise Exception('run_autoregressive_step was called but the integration has no '
|
|
1241
1333
|
'tensorleap_autoregressive_step hook.')
|
|
1242
1334
|
preprocess_response = self._preprocess_result()[state]
|
|
1243
|
-
result = handler.function(sample_id, prev_inputs, prev_outputs,
|
|
1244
|
-
|
|
1245
|
-
|
|
1246
|
-
|
|
1335
|
+
result = handler.function(sample_id, prev_inputs, prev_outputs, chain_state,
|
|
1336
|
+
preprocess_response)
|
|
1337
|
+
if not isinstance(result, tuple) or len(result) != 2:
|
|
1338
|
+
raise Exception('The autoregressive step hook must return a (next_inputs, state) '
|
|
1339
|
+
f'tuple, got {type(result)} for sample {sample_id}.')
|
|
1340
|
+
next_inputs, new_chain_state = result
|
|
1341
|
+
validate_autoregressive_state_types(new_chain_state)
|
|
1342
|
+
if next_inputs is None:
|
|
1343
|
+
if prev_inputs is None:
|
|
1344
|
+
raise Exception('The autoregressive step hook returned None for next_inputs on '
|
|
1345
|
+
f'the first call of the chain (sample {sample_id}) — the first '
|
|
1346
|
+
'call must return the initial model inputs; None would produce '
|
|
1347
|
+
'an empty chain with no forward pass.')
|
|
1348
|
+
return None, new_chain_state
|
|
1349
|
+
if not isinstance(next_inputs, dict):
|
|
1247
1350
|
raise Exception('The autoregressive step hook must return a dict of model inputs or '
|
|
1248
|
-
f'None to end the chain, got {type(
|
|
1351
|
+
f'None to end the chain, got {type(next_inputs)} for sample '
|
|
1352
|
+
f'{sample_id}.')
|
|
1249
1353
|
# Parse-time validation only exercises step 0; enforce the fixed key-set/shape contract
|
|
1250
1354
|
# on every step here so drift fails with an actionable message instead of a cryptic
|
|
1251
1355
|
# engine error on a compiled graph.
|
|
1252
1356
|
expected_shapes = self._ensure_autoregressive_input_shapes()
|
|
1253
|
-
|
|
1254
|
-
if set(model_inputs) != set(expected_shapes):
|
|
1357
|
+
if set(next_inputs) != set(expected_shapes):
|
|
1255
1358
|
raise Exception('The autoregressive step hook must return the same model input keys '
|
|
1256
1359
|
f'on every call. Expected {sorted(expected_shapes)}, got '
|
|
1257
|
-
f'{sorted(
|
|
1258
|
-
for key, value in
|
|
1259
|
-
if isinstance(value, np.ndarray)
|
|
1360
|
+
f'{sorted(next_inputs)} for sample {sample_id}.')
|
|
1361
|
+
for key, value in next_inputs.items():
|
|
1362
|
+
if not isinstance(value, np.ndarray):
|
|
1363
|
+
raise Exception('The autoregressive step hook must return numpy arrays for every '
|
|
1364
|
+
f'model input on every call — input "{key}" is a '
|
|
1365
|
+
f'{type(value).__name__} for sample {sample_id} (forgot .numpy() '
|
|
1366
|
+
'on a fed-back model output?).')
|
|
1367
|
+
if list(value.shape) != expected_shapes[key]:
|
|
1260
1368
|
raise Exception('The autoregressive step hook must return fixed tensor shapes on '
|
|
1261
1369
|
f'every call (use fixed-length padding + mask for growing '
|
|
1262
1370
|
f'sequences). Input "{key}" was {expected_shapes[key]} on the '
|
|
1263
1371
|
f'first call and {list(value.shape)} now (sample {sample_id}).')
|
|
1264
|
-
return
|
|
1372
|
+
return next_inputs, new_chain_state
|
|
1373
|
+
|
|
1374
|
+
@lru_cache()
|
|
1375
|
+
def autoregressive_visualizer_by_name(self) -> Dict[str, VisualizerHandlerData]:
|
|
1376
|
+
self.exec_script()
|
|
1377
|
+
return {handler.visualizer_handler_data.name: handler.visualizer_handler_data
|
|
1378
|
+
for handler in global_leap_binder.setup_container.autoregressive_visualizers}
|
|
1379
|
+
|
|
1380
|
+
@lru_cache()
|
|
1381
|
+
def get_autoregressive_decorator_names(self) -> Dict[str, List[str]]:
|
|
1382
|
+
self.exec_script()
|
|
1383
|
+
setup = global_leap_binder.setup_container
|
|
1384
|
+
return {
|
|
1385
|
+
'metrics': [handler.metric_handler_data.name
|
|
1386
|
+
for handler in setup.autoregressive_metrics],
|
|
1387
|
+
'losses': [handler.custom_loss_handler_data.name
|
|
1388
|
+
for handler in setup.autoregressive_losses],
|
|
1389
|
+
'visualizers': [handler.visualizer_handler_data.name
|
|
1390
|
+
for handler in setup.autoregressive_visualizers],
|
|
1391
|
+
}
|
|
1392
|
+
|
|
1393
|
+
@staticmethod
|
|
1394
|
+
def _autoregressive_callable_kwargs(function: Callable[..., Any],
|
|
1395
|
+
inputs: Dict[str, npt.NDArray[np.float32]],
|
|
1396
|
+
outputs: Dict[str, npt.NDArray[np.float32]],
|
|
1397
|
+
chain_state: Any,
|
|
1398
|
+
wired_args: Dict[str, npt.NDArray[np.float32]]
|
|
1399
|
+
) -> Dict[str, Any]:
|
|
1400
|
+
implicit_values = {'inputs': inputs, 'outputs': outputs, 'state': chain_state}
|
|
1401
|
+
kwargs: Dict[str, Any] = {}
|
|
1402
|
+
for arg_name in inspect.getfullargspec(function)[0]:
|
|
1403
|
+
if arg_name in implicit_values:
|
|
1404
|
+
kwargs[arg_name] = implicit_values[arg_name]
|
|
1405
|
+
elif arg_name in wired_args:
|
|
1406
|
+
kwargs[arg_name] = wired_args[arg_name]
|
|
1407
|
+
else:
|
|
1408
|
+
raise Exception(f'Autoregressive callable is missing wired argument '
|
|
1409
|
+
f'"{arg_name}" — the platform mapping supplies only '
|
|
1410
|
+
f'{sorted(wired_args)}.')
|
|
1411
|
+
return kwargs
|
|
1412
|
+
|
|
1413
|
+
def run_autoregressive_metric(self, name: str,
|
|
1414
|
+
inputs: Dict[str, npt.NDArray[np.float32]],
|
|
1415
|
+
outputs: Dict[str, npt.NDArray[np.float32]],
|
|
1416
|
+
chain_state: Any,
|
|
1417
|
+
wired_args: Dict[str, npt.NDArray[np.float32]]) -> Any:
|
|
1418
|
+
self.exec_script()
|
|
1419
|
+
for handler in global_leap_binder.setup_container.autoregressive_metrics:
|
|
1420
|
+
if handler.metric_handler_data.name == name:
|
|
1421
|
+
return handler.function(**self._autoregressive_callable_kwargs(
|
|
1422
|
+
handler.function, inputs, outputs, chain_state, wired_args))
|
|
1423
|
+
raise Exception(f'No autoregressive metric named "{name}" is registered.')
|
|
1424
|
+
|
|
1425
|
+
def run_autoregressive_loss(self, name: str,
|
|
1426
|
+
inputs: Dict[str, npt.NDArray[np.float32]],
|
|
1427
|
+
outputs: Dict[str, npt.NDArray[np.float32]],
|
|
1428
|
+
chain_state: Any,
|
|
1429
|
+
wired_args: Dict[str, npt.NDArray[np.float32]]) -> Any:
|
|
1430
|
+
self.exec_script()
|
|
1431
|
+
for handler in global_leap_binder.setup_container.autoregressive_losses:
|
|
1432
|
+
if handler.custom_loss_handler_data.name == name:
|
|
1433
|
+
return handler.function(**self._autoregressive_callable_kwargs(
|
|
1434
|
+
handler.function, inputs, outputs, chain_state, wired_args))
|
|
1435
|
+
raise Exception(f'No autoregressive loss named "{name}" is registered.')
|
|
1436
|
+
|
|
1437
|
+
def run_autoregressive_visualizer(self, name: str,
|
|
1438
|
+
inputs: Dict[str, npt.NDArray[np.float32]],
|
|
1439
|
+
outputs: Dict[str, npt.NDArray[np.float32]],
|
|
1440
|
+
chain_state: Any,
|
|
1441
|
+
wired_args: Dict[str, npt.NDArray[np.float32]]) -> Any:
|
|
1442
|
+
self.exec_script()
|
|
1443
|
+
for handler in global_leap_binder.setup_container.autoregressive_visualizers:
|
|
1444
|
+
if handler.visualizer_handler_data.name == name:
|
|
1445
|
+
return handler.function(**self._autoregressive_callable_kwargs(
|
|
1446
|
+
handler.function, inputs, outputs, chain_state, wired_args))
|
|
1447
|
+
raise Exception(f'No autoregressive visualizer named "{name}" is registered.')
|
|
1265
1448
|
|
|
1266
1449
|
def get_prediction_names_in_order(self) -> List[str]:
|
|
1267
1450
|
"""Model output names by declared prediction-type order, used to key prev_outputs for the
|
code_loader/leaploaderbase.py
CHANGED
|
@@ -149,10 +149,74 @@ class LeapLoaderBase:
|
|
|
149
149
|
def has_custom_latent_space_decorator(self) -> bool:
|
|
150
150
|
pass
|
|
151
151
|
|
|
152
|
+
# The autoregressive entry points raise instead of `pass`-ing like the abstract methods
|
|
153
|
+
# above: this class has no ABCMeta, so @abstractmethod is not enforced at runtime and an
|
|
154
|
+
# un-overridden `pass` body would return None — has_autoregressive_step would silently
|
|
155
|
+
# report False for an AR integration and the engine would skip the chain rollout with no
|
|
156
|
+
# error. A stale loader subclass keeps working for non-AR flows and fails loudly here the
|
|
157
|
+
# moment an AR flow reaches it.
|
|
158
|
+
@abstractmethod
|
|
152
159
|
def has_autoregressive_step(self) -> bool:
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
160
|
+
raise NotImplementedError(f'{type(self).__name__} does not implement '
|
|
161
|
+
'has_autoregressive_step — it must be overridden '
|
|
162
|
+
'(LeapLoader in-process, LeapLoaderWithRedis via RPC).')
|
|
163
|
+
|
|
164
|
+
@abstractmethod
|
|
165
|
+
def get_gt(self, state: DataStateEnum, sample_id: Union[int, str]
|
|
166
|
+
) -> Dict[str, npt.NDArray[np.float32]]:
|
|
167
|
+
raise NotImplementedError(f'{type(self).__name__} does not implement get_gt.')
|
|
168
|
+
|
|
169
|
+
@abstractmethod
|
|
170
|
+
def get_prediction_names_in_order(self) -> List[str]:
|
|
171
|
+
raise NotImplementedError(f'{type(self).__name__} does not implement '
|
|
172
|
+
'get_prediction_names_in_order.')
|
|
173
|
+
|
|
174
|
+
@abstractmethod
|
|
175
|
+
def get_autoregressive_latent_space_aggregation(self) -> str:
|
|
176
|
+
raise NotImplementedError(f'{type(self).__name__} does not implement '
|
|
177
|
+
'get_autoregressive_latent_space_aggregation.')
|
|
178
|
+
|
|
179
|
+
@abstractmethod
|
|
180
|
+
def get_autoregressive_decorator_names(self) -> Dict[str, List[str]]:
|
|
181
|
+
raise NotImplementedError(f'{type(self).__name__} does not implement '
|
|
182
|
+
'get_autoregressive_decorator_names.')
|
|
183
|
+
|
|
184
|
+
@abstractmethod
|
|
185
|
+
def run_autoregressive_step(self, sample_id: Union[int, str],
|
|
186
|
+
prev_inputs: Optional[Dict[str, npt.NDArray[np.float32]]],
|
|
187
|
+
prev_outputs: Optional[Dict[str, npt.NDArray[np.float32]]],
|
|
188
|
+
chain_state: Any,
|
|
189
|
+
state: DataStateEnum
|
|
190
|
+
) -> Tuple[Optional[Dict[str, npt.NDArray[np.float32]]], Any]:
|
|
191
|
+
raise NotImplementedError(f'{type(self).__name__} does not implement '
|
|
192
|
+
'run_autoregressive_step.')
|
|
193
|
+
|
|
194
|
+
@abstractmethod
|
|
195
|
+
def run_autoregressive_metric(self, name: str, inputs: Dict[str, npt.NDArray[np.float32]],
|
|
196
|
+
outputs: Dict[str, npt.NDArray[np.float32]], chain_state: Any,
|
|
197
|
+
wired_args: Dict[str, npt.NDArray[np.float32]]) -> Any:
|
|
198
|
+
raise NotImplementedError(f'{type(self).__name__} does not implement '
|
|
199
|
+
'run_autoregressive_metric.')
|
|
200
|
+
|
|
201
|
+
@abstractmethod
|
|
202
|
+
def run_autoregressive_loss(self, name: str, inputs: Dict[str, npt.NDArray[np.float32]],
|
|
203
|
+
outputs: Dict[str, npt.NDArray[np.float32]], chain_state: Any,
|
|
204
|
+
wired_args: Dict[str, npt.NDArray[np.float32]]) -> Any:
|
|
205
|
+
raise NotImplementedError(f'{type(self).__name__} does not implement '
|
|
206
|
+
'run_autoregressive_loss.')
|
|
207
|
+
|
|
208
|
+
@abstractmethod
|
|
209
|
+
def run_autoregressive_visualizer(self, name: str, inputs: Dict[str, npt.NDArray[np.float32]],
|
|
210
|
+
outputs: Dict[str, npt.NDArray[np.float32]],
|
|
211
|
+
chain_state: Any,
|
|
212
|
+
wired_args: Dict[str, npt.NDArray[np.float32]]) -> Any:
|
|
213
|
+
raise NotImplementedError(f'{type(self).__name__} does not implement '
|
|
214
|
+
'run_autoregressive_visualizer.')
|
|
215
|
+
|
|
216
|
+
@abstractmethod
|
|
217
|
+
def autoregressive_visualizer_by_name(self) -> Dict[str, VisualizerHandlerData]:
|
|
218
|
+
raise NotImplementedError(f'{type(self).__name__} does not implement '
|
|
219
|
+
'autoregressive_visualizer_by_name.')
|
|
156
220
|
|
|
157
221
|
@abstractmethod
|
|
158
222
|
def get_heatmap_visualizer_raw_vis_input_arg_name(self, visualizer_name: str) -> Optional[str]:
|
code_loader/utils.py
CHANGED
|
@@ -1,3 +1,6 @@
|
|
|
1
|
+
import io
|
|
2
|
+
import math
|
|
3
|
+
import pickle
|
|
1
4
|
import sys
|
|
2
5
|
from pathlib import Path
|
|
3
6
|
from types import TracebackType
|
|
@@ -14,10 +17,12 @@ from code_loader.contract.enums import DatasetMetadataType
|
|
|
14
17
|
def to_numpy_return_wrapper(encoder_function: SectionCallableInterface) -> SectionCallableInterface:
|
|
15
18
|
def numpy_encoder_function(idx: Union[int, str], samples: PreprocessResponse) -> npt.NDArray[np.float32]:
|
|
16
19
|
result = encoder_function(idx, samples)
|
|
17
|
-
if isinstance(
|
|
18
|
-
|
|
19
|
-
#
|
|
20
|
-
#
|
|
20
|
+
if isinstance(idx, list) and isinstance(result, list) and len(result) > 0 \
|
|
21
|
+
and all(isinstance(sample, np.ndarray) for sample in result):
|
|
22
|
+
# Grouped call (idx is a list of sample ids): keep the per-sample list, never stack
|
|
23
|
+
# it into a (B, *) batch — grouping is only a storage read-unit; batching the group to
|
|
24
|
+
# the model's B is the engine's single responsibility. Gate on the call shape (idx is a
|
|
25
|
+
# list), not the result shape, so a flat encoder returning a list of arrays still stacks.
|
|
21
26
|
return result
|
|
22
27
|
numpy_result: npt.NDArray[np.float32] = np.array(result)
|
|
23
28
|
return numpy_result
|
|
@@ -106,3 +111,51 @@ def get_metadata_type_from_variable(variable: Union[Optional[str], int, bool, Op
|
|
|
106
111
|
f"The return type should be one of [int, float, str, bool]. Got {metadata_type}")
|
|
107
112
|
return dataset_metadata_type
|
|
108
113
|
|
|
114
|
+
|
|
115
|
+
|
|
116
|
+
# The platform queue transports state as pickled payloads which the engine deserializes
|
|
117
|
+
# WITHOUT the integration code loaded, so a class defined in the user's modules round-trips
|
|
118
|
+
# locally but crashes on the engine. Only modules importable on the engine may resolve.
|
|
119
|
+
_STATE_SAFE_PICKLE_MODULES = ('builtins', 'copyreg', 'collections', 'numpy', 'datetime')
|
|
120
|
+
|
|
121
|
+
|
|
122
|
+
class _EngineSafeUnpickler(pickle.Unpickler):
|
|
123
|
+
def find_class(self, module: str, name: str) -> Any:
|
|
124
|
+
if module.split('.')[0] in _STATE_SAFE_PICKLE_MODULES:
|
|
125
|
+
return super().find_class(module, name)
|
|
126
|
+
raise pickle.UnpicklingError(
|
|
127
|
+
f'{module}.{name} is defined in integration code, which is not importable on the '
|
|
128
|
+
f'platform engine')
|
|
129
|
+
|
|
130
|
+
|
|
131
|
+
def validate_autoregressive_state_types(value: Any, path: str = 'state') -> None:
|
|
132
|
+
try:
|
|
133
|
+
_EngineSafeUnpickler(io.BytesIO(pickle.dumps(value))).load()
|
|
134
|
+
except Exception as e:
|
|
135
|
+
raise AssertionError(
|
|
136
|
+
f'autoregressive state validation failed: {path} is not serializable ({e}). State '
|
|
137
|
+
'is serialized to the platform queue on every chain step and deserialized without '
|
|
138
|
+
'the integration code — any picklable nest of builtin/numpy values works (dicts, '
|
|
139
|
+
'lists, tuples, sets, numbers, strings, arrays), but user-defined classes, lambdas '
|
|
140
|
+
'and open resources do not.') from e
|
|
141
|
+
|
|
142
|
+
|
|
143
|
+
def autoregressive_nests_equal(a: Any, b: Any) -> bool:
|
|
144
|
+
if isinstance(a, dict) or isinstance(b, dict):
|
|
145
|
+
return isinstance(a, dict) and isinstance(b, dict) and set(a) == set(b) and \
|
|
146
|
+
all(autoregressive_nests_equal(a[key], b[key]) for key in a)
|
|
147
|
+
if isinstance(a, (list, tuple)) or isinstance(b, (list, tuple)):
|
|
148
|
+
return isinstance(a, (list, tuple)) and isinstance(b, (list, tuple)) and \
|
|
149
|
+
len(a) == len(b) and all(autoregressive_nests_equal(x, y) for x, y in zip(a, b))
|
|
150
|
+
if isinstance(a, np.ndarray) or isinstance(b, np.ndarray):
|
|
151
|
+
if not (isinstance(a, np.ndarray) and isinstance(b, np.ndarray) and a.shape == b.shape):
|
|
152
|
+
return False
|
|
153
|
+
# NaN leaves must equal themselves: this comparison backs the determinism checks, where
|
|
154
|
+
# NaN padding / running stats in a perfectly deterministic hook are legitimate.
|
|
155
|
+
if a.dtype.kind in 'fc' and b.dtype.kind in 'fc':
|
|
156
|
+
return bool(np.array_equal(a, b, equal_nan=True))
|
|
157
|
+
return bool(np.array_equal(a, b))
|
|
158
|
+
if isinstance(a, (float, np.floating)) and isinstance(b, (float, np.floating)) and \
|
|
159
|
+
math.isnan(a) and math.isnan(b):
|
|
160
|
+
return True
|
|
161
|
+
return bool(a == b)
|