code-loader 1.0.195.dev0__py3-none-any.whl → 1.0.195.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 +64 -51
- code_loader/inner_leap_binder/leapbinder.py +24 -101
- code_loader/inner_leap_binder/leapbinder_decorators.py +277 -903
- code_loader/leaploader.py +255 -203
- code_loader/leaploaderbase.py +3 -67
- code_loader/utils.py +5 -51
- {code_loader-1.0.195.dev0.dist-info → code_loader-1.0.195.dev1.dist-info}/METADATA +1 -1
- {code_loader-1.0.195.dev0.dist-info → code_loader-1.0.195.dev1.dist-info}/RECORD +10 -10
- {code_loader-1.0.195.dev0.dist-info → code_loader-1.0.195.dev1.dist-info}/LICENSE +0 -0
- {code_loader-1.0.195.dev0.dist-info → code_loader-1.0.195.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,
|
|
10
|
+
from typing import Dict, List, Iterable, Set, Union, Any, Type, Optional, Callable, Tuple
|
|
11
11
|
|
|
12
12
|
import numpy as np
|
|
13
13
|
import numpy.typing as npt
|
|
@@ -27,8 +27,7 @@ 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
|
|
31
|
-
validate_autoregressive_state_types, autoregressive_nests_equal
|
|
30
|
+
from code_loader.utils import get_root_exception_file_and_line_number, get_metadata_type_from_variable
|
|
32
31
|
|
|
33
32
|
|
|
34
33
|
def _serialize_sim_bounds(bounds) -> dict:
|
|
@@ -60,14 +59,35 @@ class LeapLoader(LeapLoaderBase):
|
|
|
60
59
|
|
|
61
60
|
@lru_cache()
|
|
62
61
|
def exec_script(self) -> None:
|
|
62
|
+
from code_loader.inner_leap_binder import leapbinder_decorators as _leap_dec
|
|
63
63
|
try:
|
|
64
64
|
os.environ[mapping_runtime_mode_env_var_mame] = 'TRUE'
|
|
65
65
|
self.evaluate_module()
|
|
66
|
+
|
|
67
|
+
# A grouped integration test may index a grouped input/gt (image_list[i]) to wire a
|
|
68
|
+
# single sample of a group; that indexing is only valid when the mapping graph is
|
|
69
|
+
# built in grouped mode. Detect grouped-ness so the placeholder pass below matches
|
|
70
|
+
# the dataset. Preprocess is stubbed while mapping mode is on, so run it with mapping
|
|
71
|
+
# mode momentarily disabled, and cache it so _preprocess_result() doesn't re-run it.
|
|
72
|
+
is_grouped = False
|
|
66
73
|
if global_leap_binder.integration_test_func is not None:
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
74
|
+
del os.environ[mapping_runtime_mode_env_var_mame]
|
|
75
|
+
try:
|
|
76
|
+
preprocess_result = global_leap_binder.get_preprocess_result()
|
|
77
|
+
self._preprocess_result_cached = preprocess_result
|
|
78
|
+
is_grouped = any(r.is_grouped for r in preprocess_result.values())
|
|
79
|
+
except Exception:
|
|
80
|
+
is_grouped = False
|
|
81
|
+
finally:
|
|
82
|
+
os.environ[mapping_runtime_mode_env_var_mame] = 'TRUE'
|
|
83
|
+
|
|
84
|
+
_leap_dec._mapping_dataset_is_grouped = is_grouped
|
|
85
|
+
if global_leap_binder.integration_test_func is not None:
|
|
86
|
+
mapping_preprocess = (
|
|
87
|
+
PreprocessResponse(sample_ids=[["__mapping_placeholder__"]], state=DataStateType.training)
|
|
88
|
+
if is_grouped else
|
|
89
|
+
PreprocessResponse(state=DataStateType.training, length=0))
|
|
90
|
+
global_leap_binder.integration_test_func(None, mapping_preprocess)
|
|
71
91
|
except TypeError as e:
|
|
72
92
|
import traceback
|
|
73
93
|
global_leap_binder.setup_container = DatasetIntegrationSetup()
|
|
@@ -80,6 +100,7 @@ class LeapLoader(LeapLoaderBase):
|
|
|
80
100
|
raise DatasetScriptException(getattr(e, 'message', repr(e))) from e
|
|
81
101
|
finally:
|
|
82
102
|
# ensure that the environment variable is removed after the script execution
|
|
103
|
+
_leap_dec._mapping_dataset_is_grouped = False
|
|
83
104
|
if mapping_runtime_mode_env_var_mame in os.environ:
|
|
84
105
|
del os.environ[mapping_runtime_mode_env_var_mame]
|
|
85
106
|
|
|
@@ -250,16 +271,20 @@ class LeapLoader(LeapLoaderBase):
|
|
|
250
271
|
)
|
|
251
272
|
|
|
252
273
|
preprocess_result = self._preprocess_result()
|
|
253
|
-
if state == DataStateEnum.unlabeled and sample_id not in preprocess_result[state].
|
|
274
|
+
if state == DataStateEnum.unlabeled and sample_id not in preprocess_result[state].flat_sample_ids:
|
|
254
275
|
self._preprocess_result(update_unlabeled_preprocess=True)
|
|
255
276
|
|
|
256
277
|
metadata, metadata_is_none = self.get_metadata(state, sample_id)
|
|
257
278
|
|
|
258
279
|
custom_latent_space = None
|
|
259
280
|
if global_leap_binder.setup_container.custom_latent_space is not None:
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
281
|
+
latent_fn = global_leap_binder.setup_container.custom_latent_space.function
|
|
282
|
+
preprocess_state = preprocess_result[state]
|
|
283
|
+
if preprocess_state.is_grouped:
|
|
284
|
+
group_ids, pos = self._locate_group(preprocess_state, sample_id)
|
|
285
|
+
custom_latent_space = self._to_grouped_list(latent_fn(group_ids, preprocess_state))[pos]
|
|
286
|
+
else:
|
|
287
|
+
custom_latent_space = latent_fn(sample_id, preprocess_state)
|
|
263
288
|
instance_mask = self._get_instances_masks(state, sample_id, instance_id)
|
|
264
289
|
sample = DatasetSample(inputs=self._get_inputs(state, sample_id),
|
|
265
290
|
gt=None if state == DataStateEnum.unlabeled else self._get_gt(state, sample_id),
|
|
@@ -390,7 +415,7 @@ class LeapLoader(LeapLoaderBase):
|
|
|
390
415
|
if self.get_sample_id_type() is str:
|
|
391
416
|
max_allowed_item_size = np.dtype('<U256').itemsize
|
|
392
417
|
for state, preprocess_response in preprocess_result.items():
|
|
393
|
-
sample_ids_array = np.array(preprocess_response.
|
|
418
|
+
sample_ids_array = np.array(preprocess_response.flat_sample_ids)
|
|
394
419
|
if sample_ids_array.dtype.itemsize > max_allowed_item_size:
|
|
395
420
|
raise Exception(f"Sample id are too long. Max allowed length is 256 charecters.")
|
|
396
421
|
|
|
@@ -480,49 +505,31 @@ class LeapLoader(LeapLoaderBase):
|
|
|
480
505
|
try:
|
|
481
506
|
preprocess_result = self._preprocess_result()
|
|
482
507
|
input_shapes: Dict[str, List[int]] = {}
|
|
483
|
-
expected_keys: Optional[FrozenSet[str]] = None
|
|
484
|
-
expected_keys_state = ''
|
|
485
508
|
for state, preprocess_response in preprocess_result.items():
|
|
486
509
|
if preprocess_response.sample_ids_to_instance_mappings:
|
|
487
510
|
raise Exception('Element instances are not supported together with '
|
|
488
511
|
'tensorleap_autoregressive_step.')
|
|
489
512
|
sample_id = preprocess_response.sample_ids[0]
|
|
490
|
-
first_result = handler.function(sample_id, None, None,
|
|
491
|
-
second_result = handler.function(sample_id, None, None,
|
|
492
|
-
if not isinstance(first_result,
|
|
493
|
-
raise Exception('The autoregressive step hook must return a (next_inputs, '
|
|
494
|
-
f'state) tuple, got {type(first_result)} '
|
|
495
|
-
f'(state: {state.name}).')
|
|
496
|
-
first_inputs, first_state = first_result
|
|
497
|
-
if not isinstance(first_inputs, dict) or not first_inputs:
|
|
513
|
+
first_result = handler.function(sample_id, None, None, preprocess_response)
|
|
514
|
+
second_result = handler.function(sample_id, None, None, preprocess_response)
|
|
515
|
+
if not isinstance(first_result, dict):
|
|
498
516
|
raise Exception('The autoregressive step hook must return a dict of initial model '
|
|
499
|
-
f'inputs on its first call, got {type(
|
|
517
|
+
f'inputs on its first call, got {type(first_result)} '
|
|
500
518
|
f'(state: {state.name}).')
|
|
501
|
-
|
|
502
|
-
|
|
503
|
-
not isinstance(second_result[0], dict) or \
|
|
504
|
-
set(second_result[0].keys()) != set(first_inputs.keys()) or \
|
|
505
|
-
not autoregressive_nests_equal(first_state, second_result[1]):
|
|
519
|
+
if not isinstance(second_result, dict) or \
|
|
520
|
+
set(second_result.keys()) != set(first_result.keys()):
|
|
506
521
|
raise Exception('The autoregressive step hook is not deterministic — two calls '
|
|
507
522
|
'with identical arguments returned different results '
|
|
508
523
|
f'(state: {state.name}). Seed any stochasticity from '
|
|
509
524
|
'sample_id: the engine replays steps after crash recovery '
|
|
510
525
|
'and relies on identical results.')
|
|
511
|
-
|
|
512
|
-
|
|
513
|
-
|
|
514
|
-
|
|
515
|
-
|
|
516
|
-
f'
|
|
517
|
-
|
|
518
|
-
f'({state.name}). The key set must be identical for every '
|
|
519
|
-
f'state.')
|
|
520
|
-
for key, value in first_inputs.items():
|
|
521
|
-
if key.startswith('_'):
|
|
522
|
-
raise Exception(f'The autoregressive step hook returned model input key '
|
|
523
|
-
f'"{key}" — underscore passthrough keys were replaced by '
|
|
524
|
-
f'the explicit state channel; move passthrough values '
|
|
525
|
-
f'into the returned state. State: {state.name}.')
|
|
526
|
+
model_input_items = {key: value for key, value in first_result.items()
|
|
527
|
+
if not key.startswith('_')}
|
|
528
|
+
if not model_input_items:
|
|
529
|
+
raise Exception('The autoregressive step hook returned no model inputs on its first '
|
|
530
|
+
'call — every key starts with "_" (underscore keys are passthrough '
|
|
531
|
+
f'state, never fed to the model). State: {state.name}.')
|
|
532
|
+
for key, value in model_input_items.items():
|
|
526
533
|
if not isinstance(value, np.ndarray):
|
|
527
534
|
raise Exception(f'The autoregressive step hook returned a non-numpy value for '
|
|
528
535
|
f'model input "{key}" ({type(value)}). State: {state.name}.')
|
|
@@ -531,7 +538,7 @@ class LeapLoader(LeapLoaderBase):
|
|
|
531
538
|
f'model input "{key}" across states: {input_shapes[key]} vs '
|
|
532
539
|
f'{list(value.shape)}. Shapes must be fixed.')
|
|
533
540
|
input_shapes[key] = list(value.shape)
|
|
534
|
-
if not
|
|
541
|
+
if not np.array_equal(value, second_result[key]):
|
|
535
542
|
raise Exception('The autoregressive step hook is not deterministic — two calls '
|
|
536
543
|
'with identical arguments returned different outputs for model '
|
|
537
544
|
f'input "{key}". Seed any stochasticity from sample_id: the '
|
|
@@ -545,39 +552,19 @@ class LeapLoader(LeapLoaderBase):
|
|
|
545
552
|
test_result.is_passed = False
|
|
546
553
|
return test_result
|
|
547
554
|
|
|
548
|
-
@staticmethod
|
|
549
|
-
def _validate_autoregressive_step_inputs(step_inputs: Dict[str, Any],
|
|
550
|
-
sample_id: Union[int, str]) -> None:
|
|
551
|
-
# The parse-time check only covers the first sample of each state; the engine runtime
|
|
552
|
-
# paths must enforce the same key/value contract for every sample they touch.
|
|
553
|
-
for key, value in step_inputs.items():
|
|
554
|
-
if key.startswith('_'):
|
|
555
|
-
raise Exception(f'The autoregressive step hook returned model input key "{key}" '
|
|
556
|
-
f'for sample {sample_id} — underscore passthrough keys were '
|
|
557
|
-
'replaced by the explicit state channel; move passthrough values '
|
|
558
|
-
'into the returned state.')
|
|
559
|
-
if not isinstance(value, np.ndarray):
|
|
560
|
-
raise Exception('The autoregressive step hook returned a non-numpy value for '
|
|
561
|
-
f'model input "{key}" ({type(value).__name__}) for sample '
|
|
562
|
-
f'{sample_id}.')
|
|
563
|
-
|
|
564
555
|
def _ensure_autoregressive_input_shapes(self) -> Dict[str, List[int]]:
|
|
565
556
|
handler = global_leap_binder.setup_container.autoregressive_step
|
|
566
557
|
assert handler is not None
|
|
567
558
|
if handler.input_shapes is None:
|
|
568
559
|
preprocess_result = self._preprocess_result()
|
|
569
560
|
first_state_response = next(iter(preprocess_result.values()))
|
|
570
|
-
|
|
571
|
-
first_result = handler.function(first_sample_id, None, None, None,
|
|
561
|
+
first_result = handler.function(first_state_response.sample_ids[0], None, None,
|
|
572
562
|
first_state_response)
|
|
573
|
-
if not isinstance(first_result,
|
|
574
|
-
|
|
575
|
-
|
|
576
|
-
|
|
577
|
-
|
|
578
|
-
self._validate_autoregressive_step_inputs(first_result[0], first_sample_id)
|
|
579
|
-
handler.input_shapes = {key: list(value.shape)
|
|
580
|
-
for key, value in first_result[0].items()}
|
|
563
|
+
if not isinstance(first_result, dict):
|
|
564
|
+
raise Exception('The autoregressive step hook must return a dict of initial model '
|
|
565
|
+
f'inputs on its first call, got {type(first_result)}.')
|
|
566
|
+
handler.input_shapes = {key: list(value.shape) for key, value in first_result.items()
|
|
567
|
+
if not key.startswith('_') and isinstance(value, np.ndarray)}
|
|
581
568
|
return handler.input_shapes
|
|
582
569
|
|
|
583
570
|
def run_simulation(self, sim_name, params=None, n_samples=1, seed=0,
|
|
@@ -846,21 +833,6 @@ class LeapLoader(LeapLoaderBase):
|
|
|
846
833
|
metric_inst = MetricInstance(metric.metric_handler_data.name, metric.metric_handler_data.arg_names)
|
|
847
834
|
metrics.append(metric_inst)
|
|
848
835
|
|
|
849
|
-
# Autoregressive decorators are reported alongside the regular ones so the platform sees
|
|
850
|
-
# their names; the engine tells them apart by name via get_autoregressive_decorator_names.
|
|
851
|
-
for autoregressive_metric in setup.autoregressive_metrics:
|
|
852
|
-
metrics.append(MetricInstance(autoregressive_metric.metric_handler_data.name,
|
|
853
|
-
autoregressive_metric.metric_handler_data.arg_names))
|
|
854
|
-
for autoregressive_loss in setup.autoregressive_losses:
|
|
855
|
-
custom_losses.append(CustomLossInstance(
|
|
856
|
-
autoregressive_loss.custom_loss_handler_data.name,
|
|
857
|
-
autoregressive_loss.custom_loss_handler_data.arg_names))
|
|
858
|
-
for autoregressive_visualizer in setup.autoregressive_visualizers:
|
|
859
|
-
visualizers.append(VisualizerInstance(
|
|
860
|
-
autoregressive_visualizer.visualizer_handler_data.name,
|
|
861
|
-
autoregressive_visualizer.visualizer_handler_data.type,
|
|
862
|
-
autoregressive_visualizer.visualizer_handler_data.arg_names))
|
|
863
|
-
|
|
864
836
|
simulations = []
|
|
865
837
|
for sim in setup.simulations:
|
|
866
838
|
sim_config_serialized = {
|
|
@@ -906,6 +878,58 @@ class LeapLoader(LeapLoaderBase):
|
|
|
906
878
|
|
|
907
879
|
return sample_ids
|
|
908
880
|
|
|
881
|
+
@staticmethod
|
|
882
|
+
def _grouped_dict_keys(rows: List[Dict[str, Any]], handler_name: str) -> List[str]:
|
|
883
|
+
"""Keys for a group of dict-metadata rows, requiring every row to share the same keys.
|
|
884
|
+
A group must expose a uniform metadata schema; otherwise the per-key lists would silently
|
|
885
|
+
drop or KeyError on rows that differ from row 0."""
|
|
886
|
+
keys = list(rows[0].keys())
|
|
887
|
+
key_set = set(keys)
|
|
888
|
+
for pos, row in enumerate(rows):
|
|
889
|
+
if set(row.keys()) != key_set:
|
|
890
|
+
raise ValueError(
|
|
891
|
+
f"Metadata handler {handler_name!r} returned inconsistent dict keys across the "
|
|
892
|
+
f"group: row 0 has {sorted(key_set)} but row {pos} has {sorted(row.keys())}. "
|
|
893
|
+
f"All rows in a group must share the same metadata keys.")
|
|
894
|
+
return keys
|
|
895
|
+
|
|
896
|
+
@staticmethod
|
|
897
|
+
def _to_grouped_list(raw: Any) -> List[npt.NDArray[np.float32]]:
|
|
898
|
+
"""Normalize a grouped encoder result to a list of per-sample arrays.
|
|
899
|
+
|
|
900
|
+
Grouped results are NEVER stacked into a (B, *) batch. Grouping is only a storage
|
|
901
|
+
read-unit (one file load per group); assembling the model batch (B) is the engine's
|
|
902
|
+
single responsibility, so it stays the only place samples get batched. Keeping
|
|
903
|
+
per-sample arrays lets the engine batch to any B — including a B=1-only model — without
|
|
904
|
+
an intermediate stack/unstack. A user encoder that already returned a stacked (B, *)
|
|
905
|
+
array is split back into its per-sample rows so the contract is a list either way."""
|
|
906
|
+
if isinstance(raw, list):
|
|
907
|
+
return raw
|
|
908
|
+
return [row for row in np.asarray(raw)]
|
|
909
|
+
|
|
910
|
+
def _locate_group(self, preprocess_state: PreprocessResponse,
|
|
911
|
+
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 per
|
|
913
|
+
PreprocessResponse so grouped single-sample fetches don't rescan the groups."""
|
|
914
|
+
cache = getattr(self, '_group_pos_cache', None)
|
|
915
|
+
if cache is None:
|
|
916
|
+
cache = {}
|
|
917
|
+
self._group_pos_cache = cache
|
|
918
|
+
mapping = cache.get(id(preprocess_state))
|
|
919
|
+
if mapping is None:
|
|
920
|
+
mapping = {}
|
|
921
|
+
for group in preprocess_state.groups:
|
|
922
|
+
for pos, sid in enumerate(group):
|
|
923
|
+
if sid in mapping:
|
|
924
|
+
raise ValueError(
|
|
925
|
+
f"Duplicate sample id {sid!r} across groups in the preprocess response; "
|
|
926
|
+
f"sample ids must be unique within and across groups.")
|
|
927
|
+
mapping[sid] = (group, pos)
|
|
928
|
+
cache[id(preprocess_state)] = mapping
|
|
929
|
+
if sample_id not in mapping:
|
|
930
|
+
raise KeyError(f"Sample id {sample_id!r} is not present in any group of this preprocess response.")
|
|
931
|
+
return mapping[sample_id]
|
|
932
|
+
|
|
909
933
|
def _get_dataset_handlers(self, handlers: Iterable[DatasetBaseHandler],
|
|
910
934
|
state: DataStateEnum, sample_id: Union[int, str]) -> Dict[str, npt.NDArray[np.float32]]:
|
|
911
935
|
result_agg = {}
|
|
@@ -917,12 +941,71 @@ class LeapLoader(LeapLoaderBase):
|
|
|
917
941
|
sample_id = original_local_id
|
|
918
942
|
else:
|
|
919
943
|
preprocess_state = preprocess_result[state]
|
|
944
|
+
if preprocess_state.is_grouped:
|
|
945
|
+
# Grouped dataset: call the encoder once with the whole group, index out the sample.
|
|
946
|
+
group_ids, pos = self._locate_group(preprocess_state, sample_id)
|
|
947
|
+
for handler in handlers:
|
|
948
|
+
grouped = self._to_grouped_list(handler.function(group_ids, preprocess_state))
|
|
949
|
+
result_agg[handler.name] = grouped[pos]
|
|
950
|
+
return result_agg
|
|
920
951
|
for handler in handlers:
|
|
921
952
|
handler_result = handler.function(sample_id, preprocess_state)
|
|
922
953
|
handler_name = handler.name
|
|
923
954
|
result_agg[handler_name] = handler_result
|
|
924
955
|
return result_agg
|
|
925
956
|
|
|
957
|
+
def get_samples(self, state: DataStateEnum, group_ids: List[Union[int, str]]) -> DatasetSample:
|
|
958
|
+
"""Group-aware fetch: hand the whole group to each encoder in a single call and return a
|
|
959
|
+
grouped DatasetSample (inputs/gt as per-sample lists of length B, metadata as per-key lists
|
|
960
|
+
of length B, index = the group). Grouped results are never stacked here — batching the
|
|
961
|
+
samples into the model's B is the engine's job. This is the path a group-aware engine calls
|
|
962
|
+
to read a file once per group. Row order matches group_ids exactly."""
|
|
963
|
+
self.exec_script()
|
|
964
|
+
preprocess_state = self._preprocess_result()[state]
|
|
965
|
+
if preprocess_state.is_grouped:
|
|
966
|
+
# All requested ids must belong to a single group (one file). A cross-group request
|
|
967
|
+
# would force the encoder to load multiple files, defeating the one-load-per-group
|
|
968
|
+
# contract; partitioning a scattered request by group is the engine's responsibility.
|
|
969
|
+
distinct_groups = {id(self._locate_group(preprocess_state, sid)[0]) for sid in group_ids}
|
|
970
|
+
assert len(distinct_groups) == 1, (
|
|
971
|
+
f"get_samples received sample ids spanning {len(distinct_groups)} groups; a request "
|
|
972
|
+
f"must be confined to a single group. The engine partitions cross-group requests and "
|
|
973
|
+
f"issues one get_samples call per group.")
|
|
974
|
+
inputs = {handler.name: self._to_grouped_list(handler.function(group_ids, preprocess_state))
|
|
975
|
+
for handler in global_leap_binder.setup_container.inputs}
|
|
976
|
+
gt = None
|
|
977
|
+
if state != DataStateEnum.unlabeled:
|
|
978
|
+
gt = {handler.name: self._to_grouped_list(handler.function(group_ids, preprocess_state))
|
|
979
|
+
for handler in global_leap_binder.setup_container.ground_truths}
|
|
980
|
+
|
|
981
|
+
metadata: Dict[str, Any] = {}
|
|
982
|
+
metadata_is_none: Dict[str, Any] = {}
|
|
983
|
+
for handler in global_leap_binder.setup_container.metadata:
|
|
984
|
+
rows = handler.function(group_ids, preprocess_state) # list of B scalars/dicts
|
|
985
|
+
if rows and isinstance(rows[0], dict):
|
|
986
|
+
for key in self._grouped_dict_keys(rows, handler.name):
|
|
987
|
+
name = "{}_{}".format(handler.name, key)
|
|
988
|
+
converted = [self._convert_metadata_to_correct_type(name, row[key]) for row in rows]
|
|
989
|
+
metadata[name] = [v for v, _ in converted]
|
|
990
|
+
metadata_is_none[name] = [is_none for _, is_none in converted]
|
|
991
|
+
else:
|
|
992
|
+
converted = [self._convert_metadata_to_correct_type(handler.name, row) for row in rows]
|
|
993
|
+
metadata[handler.name] = [v for v, _ in converted]
|
|
994
|
+
metadata_is_none[handler.name] = [is_none for _, is_none in converted]
|
|
995
|
+
|
|
996
|
+
# custom_latent_space is group-aware like the input/GT encoders: hand it the whole group in a
|
|
997
|
+
# single call so a file-backed latent fn loads the file once, and normalize to (B, d). This
|
|
998
|
+
# keeps the mandatory group path non-lossy. instance_masks stay None here: they are a
|
|
999
|
+
# per-(sample, instance) concern that needs an instance_id the group fetch does not carry.
|
|
1000
|
+
custom_latent_space = None
|
|
1001
|
+
if global_leap_binder.setup_container.custom_latent_space is not None:
|
|
1002
|
+
latent_fn = global_leap_binder.setup_container.custom_latent_space.function
|
|
1003
|
+
custom_latent_space = self._to_grouped_list(latent_fn(group_ids, preprocess_state))
|
|
1004
|
+
|
|
1005
|
+
return DatasetSample(inputs=inputs, gt=gt, metadata=metadata, metadata_is_none=metadata_is_none,
|
|
1006
|
+
index=list(group_ids), state=state, custom_latent_space=custom_latent_space,
|
|
1007
|
+
instance_masks=None)
|
|
1008
|
+
|
|
926
1009
|
def _get_inputs(self, state: DataStateEnum, sample_id: Union[int, str]) -> Dict[str, npt.NDArray[np.float32]]:
|
|
927
1010
|
inputs = self._get_dataset_handlers(global_leap_binder.setup_container.inputs, state, sample_id)
|
|
928
1011
|
autoregressive_handler = global_leap_binder.setup_container.autoregressive_step
|
|
@@ -931,16 +1014,13 @@ class LeapLoader(LeapLoaderBase):
|
|
|
931
1014
|
# the sample's (step-0) model inputs, so every sample-fetching flow (graph validation,
|
|
932
1015
|
# analysis, import checks) sees a fully-populated inputs dict.
|
|
933
1016
|
preprocess_response = self._preprocess_result()[state]
|
|
934
|
-
|
|
935
|
-
|
|
936
|
-
if not isinstance(step_zero_result, tuple) or len(step_zero_result) != 2 or \
|
|
937
|
-
not isinstance(step_zero_result[0], dict):
|
|
1017
|
+
step_zero_inputs = autoregressive_handler.function(sample_id, None, None, preprocess_response)
|
|
1018
|
+
if not isinstance(step_zero_inputs, dict):
|
|
938
1019
|
raise Exception(
|
|
939
|
-
'The autoregressive step hook must return a
|
|
940
|
-
f'
|
|
941
|
-
|
|
942
|
-
|
|
943
|
-
inputs.update(step_zero_result[0])
|
|
1020
|
+
'The autoregressive step hook must return a dict of initial model inputs on its '
|
|
1021
|
+
f'first call, got {type(step_zero_inputs)} for sample {sample_id}.')
|
|
1022
|
+
inputs.update({key: value for key, value in step_zero_inputs.items()
|
|
1023
|
+
if not key.startswith('_')})
|
|
944
1024
|
return inputs
|
|
945
1025
|
|
|
946
1026
|
def _get_instances_masks(self, state: DataStateEnum, sample_id: Union[int, str], instance_id: int) -> Optional[Dict[str, ElementInstance]]:
|
|
@@ -1026,12 +1106,18 @@ class LeapLoader(LeapLoaderBase):
|
|
|
1026
1106
|
sample_id = original_local_id
|
|
1027
1107
|
else:
|
|
1028
1108
|
preprocess_state = preprocess_result[state]
|
|
1109
|
+
group_ids, pos = (None, None)
|
|
1110
|
+
if preprocess_state.is_grouped:
|
|
1111
|
+
group_ids, pos = self._locate_group(preprocess_state, sample_id)
|
|
1029
1112
|
for handler in global_leap_binder.setup_container.metadata:
|
|
1030
1113
|
if requested_metadata_names:
|
|
1031
1114
|
if not is_metadata_name_starts_with_handler_name(handler):
|
|
1032
1115
|
continue
|
|
1033
1116
|
|
|
1034
|
-
|
|
1117
|
+
if preprocess_state.is_grouped:
|
|
1118
|
+
handler_result = handler.function(group_ids, preprocess_state)[pos]
|
|
1119
|
+
else:
|
|
1120
|
+
handler_result = handler.function(sample_id, preprocess_state)
|
|
1035
1121
|
if isinstance(handler_result, dict):
|
|
1036
1122
|
for single_metadata_name, single_metadata_result in handler_result.items():
|
|
1037
1123
|
handler_name = f'{handler.name}_{single_metadata_name}'
|
|
@@ -1050,6 +1136,66 @@ class LeapLoader(LeapLoaderBase):
|
|
|
1050
1136
|
|
|
1051
1137
|
return result_agg, is_none
|
|
1052
1138
|
|
|
1139
|
+
def _grouped_metadata_for_group(self, preprocess_state: PreprocessResponse,
|
|
1140
|
+
group_ids: List[Union[int, str]],
|
|
1141
|
+
requested_metadata_names: Optional[List[str]]
|
|
1142
|
+
) -> Tuple[Dict[str, List[Any]], Dict[str, List[bool]]]:
|
|
1143
|
+
def is_wanted(_handler):
|
|
1144
|
+
if not requested_metadata_names:
|
|
1145
|
+
return True
|
|
1146
|
+
for metadata_name in requested_metadata_names:
|
|
1147
|
+
if metadata_name.startswith(_handler.name + '_') or metadata_name == _handler.name:
|
|
1148
|
+
return True
|
|
1149
|
+
return False
|
|
1150
|
+
|
|
1151
|
+
results: Dict[str, List[Any]] = {}
|
|
1152
|
+
is_none: Dict[str, List[bool]] = {}
|
|
1153
|
+
for handler in global_leap_binder.setup_container.metadata:
|
|
1154
|
+
if requested_metadata_names and not is_wanted(handler):
|
|
1155
|
+
continue
|
|
1156
|
+
rows = handler.function(group_ids, preprocess_state)
|
|
1157
|
+
if rows and isinstance(rows[0], dict):
|
|
1158
|
+
for key in self._grouped_dict_keys(rows, handler.name):
|
|
1159
|
+
name = f'{handler.name}_{key}'
|
|
1160
|
+
if requested_metadata_names and name not in requested_metadata_names:
|
|
1161
|
+
continue
|
|
1162
|
+
converted = [self._convert_metadata_to_correct_type(name, row[key]) for row in rows]
|
|
1163
|
+
results[name] = [v for v, _ in converted]
|
|
1164
|
+
is_none[name] = [n for _, n in converted]
|
|
1165
|
+
else:
|
|
1166
|
+
name = handler.name
|
|
1167
|
+
if requested_metadata_names and name not in requested_metadata_names:
|
|
1168
|
+
continue
|
|
1169
|
+
converted = [self._convert_metadata_to_correct_type(name, row) for row in rows]
|
|
1170
|
+
results[name] = [v for v, _ in converted]
|
|
1171
|
+
is_none[name] = [n for _, n in converted]
|
|
1172
|
+
return results, is_none
|
|
1173
|
+
|
|
1174
|
+
def get_metadata_multiple_samples(self, state: DataStateEnum, sample_ids: Union[List[int], List[str]],
|
|
1175
|
+
requested_metadata_names: Optional[List[str]] = None
|
|
1176
|
+
) -> Tuple[Dict[str, Union[List[str], List[int], List[bool], List[float]]],
|
|
1177
|
+
Dict[str, List[bool]]]:
|
|
1178
|
+
preprocess_state = self._preprocess_result().get(state)
|
|
1179
|
+
if preprocess_state is None or not preprocess_state.is_grouped:
|
|
1180
|
+
return super().get_metadata_multiple_samples(state, sample_ids, requested_metadata_names)
|
|
1181
|
+
|
|
1182
|
+
sample_id_type = self.get_sample_id_type()
|
|
1183
|
+
aggregated_results: Dict[str, List[Any]] = {}
|
|
1184
|
+
aggregated_is_none: Dict[str, List[bool]] = {}
|
|
1185
|
+
group_cache: Dict[int, Tuple[Dict[str, List[Any]], Dict[str, List[bool]]]] = {}
|
|
1186
|
+
for sample_id in sample_ids:
|
|
1187
|
+
sample_id = sample_id_type(sample_id)
|
|
1188
|
+
group_ids, pos = self._locate_group(preprocess_state, sample_id)
|
|
1189
|
+
key = id(group_ids)
|
|
1190
|
+
if key not in group_cache:
|
|
1191
|
+
group_cache[key] = self._grouped_metadata_for_group(
|
|
1192
|
+
preprocess_state, group_ids, requested_metadata_names)
|
|
1193
|
+
group_results, group_is_none = group_cache[key]
|
|
1194
|
+
for name, values in group_results.items():
|
|
1195
|
+
aggregated_results.setdefault(name, []).append(values[pos])
|
|
1196
|
+
aggregated_is_none.setdefault(name, []).append(group_is_none[name][pos])
|
|
1197
|
+
return aggregated_results, aggregated_is_none
|
|
1198
|
+
|
|
1053
1199
|
@lru_cache()
|
|
1054
1200
|
def get_sample_id_type(self) -> Type:
|
|
1055
1201
|
preprocess_results = list(self._preprocess_result().values())
|
|
@@ -1081,15 +1227,12 @@ class LeapLoader(LeapLoaderBase):
|
|
|
1081
1227
|
def run_autoregressive_step(self, sample_id: Union[int, str],
|
|
1082
1228
|
prev_inputs: Optional[Dict[str, npt.NDArray[np.float32]]],
|
|
1083
1229
|
prev_outputs: Optional[Dict[str, npt.NDArray[np.float32]]],
|
|
1084
|
-
|
|
1085
|
-
state: DataStateEnum
|
|
1086
|
-
) -> Tuple[Optional[Dict[str, npt.NDArray[np.float32]]], Any]:
|
|
1230
|
+
state: DataStateEnum) -> Optional[Dict[str, npt.NDArray[np.float32]]]:
|
|
1087
1231
|
"""Execute the user's autoregressive step hook against the in-process preprocess result.
|
|
1088
1232
|
|
|
1089
1233
|
This is the engine rollout's per-step entry point (invoked on the generic pod). The first
|
|
1090
|
-
call per chain passes prev_inputs=None, prev_outputs=None
|
|
1091
|
-
|
|
1092
|
-
terminating call still returns the final state.
|
|
1234
|
+
call per chain passes prev_inputs=None, prev_outputs=None and returns the initial model
|
|
1235
|
+
inputs; a None return ends the chain.
|
|
1093
1236
|
"""
|
|
1094
1237
|
self.exec_script()
|
|
1095
1238
|
handler = global_leap_binder.setup_container.autoregressive_step
|
|
@@ -1097,119 +1240,28 @@ class LeapLoader(LeapLoaderBase):
|
|
|
1097
1240
|
raise Exception('run_autoregressive_step was called but the integration has no '
|
|
1098
1241
|
'tensorleap_autoregressive_step hook.')
|
|
1099
1242
|
preprocess_response = self._preprocess_result()[state]
|
|
1100
|
-
result = handler.function(sample_id, prev_inputs, prev_outputs,
|
|
1101
|
-
|
|
1102
|
-
|
|
1103
|
-
|
|
1104
|
-
f'tuple, got {type(result)} for sample {sample_id}.')
|
|
1105
|
-
next_inputs, new_chain_state = result
|
|
1106
|
-
validate_autoregressive_state_types(new_chain_state)
|
|
1107
|
-
if next_inputs is None:
|
|
1108
|
-
if prev_inputs is None:
|
|
1109
|
-
raise Exception('The autoregressive step hook returned None for next_inputs on '
|
|
1110
|
-
f'the first call of the chain (sample {sample_id}) — the first '
|
|
1111
|
-
'call must return the initial model inputs; None would produce '
|
|
1112
|
-
'an empty chain with no forward pass.')
|
|
1113
|
-
return None, new_chain_state
|
|
1114
|
-
if not isinstance(next_inputs, dict):
|
|
1243
|
+
result = handler.function(sample_id, prev_inputs, prev_outputs, preprocess_response)
|
|
1244
|
+
if result is None:
|
|
1245
|
+
return None
|
|
1246
|
+
if not isinstance(result, dict):
|
|
1115
1247
|
raise Exception('The autoregressive step hook must return a dict of model inputs or '
|
|
1116
|
-
f'None to end the chain, got {type(
|
|
1117
|
-
f'{sample_id}.')
|
|
1248
|
+
f'None to end the chain, got {type(result)} for sample {sample_id}.')
|
|
1118
1249
|
# Parse-time validation only exercises step 0; enforce the fixed key-set/shape contract
|
|
1119
1250
|
# on every step here so drift fails with an actionable message instead of a cryptic
|
|
1120
1251
|
# engine error on a compiled graph.
|
|
1121
1252
|
expected_shapes = self._ensure_autoregressive_input_shapes()
|
|
1122
|
-
|
|
1253
|
+
model_inputs = {key: value for key, value in result.items() if not key.startswith('_')}
|
|
1254
|
+
if set(model_inputs) != set(expected_shapes):
|
|
1123
1255
|
raise Exception('The autoregressive step hook must return the same model input keys '
|
|
1124
1256
|
f'on every call. Expected {sorted(expected_shapes)}, got '
|
|
1125
|
-
f'{sorted(
|
|
1126
|
-
for key, value in
|
|
1127
|
-
if
|
|
1128
|
-
raise Exception('The autoregressive step hook must return numpy arrays for every '
|
|
1129
|
-
f'model input on every call — input "{key}" is a '
|
|
1130
|
-
f'{type(value).__name__} for sample {sample_id} (forgot .numpy() '
|
|
1131
|
-
'on a fed-back model output?).')
|
|
1132
|
-
if list(value.shape) != expected_shapes[key]:
|
|
1257
|
+
f'{sorted(model_inputs)} for sample {sample_id}.')
|
|
1258
|
+
for key, value in model_inputs.items():
|
|
1259
|
+
if isinstance(value, np.ndarray) and list(value.shape) != expected_shapes[key]:
|
|
1133
1260
|
raise Exception('The autoregressive step hook must return fixed tensor shapes on '
|
|
1134
1261
|
f'every call (use fixed-length padding + mask for growing '
|
|
1135
1262
|
f'sequences). Input "{key}" was {expected_shapes[key]} on the '
|
|
1136
1263
|
f'first call and {list(value.shape)} now (sample {sample_id}).')
|
|
1137
|
-
return
|
|
1138
|
-
|
|
1139
|
-
@lru_cache()
|
|
1140
|
-
def autoregressive_visualizer_by_name(self) -> Dict[str, VisualizerHandlerData]:
|
|
1141
|
-
self.exec_script()
|
|
1142
|
-
return {handler.visualizer_handler_data.name: handler.visualizer_handler_data
|
|
1143
|
-
for handler in global_leap_binder.setup_container.autoregressive_visualizers}
|
|
1144
|
-
|
|
1145
|
-
@lru_cache()
|
|
1146
|
-
def get_autoregressive_decorator_names(self) -> Dict[str, List[str]]:
|
|
1147
|
-
self.exec_script()
|
|
1148
|
-
setup = global_leap_binder.setup_container
|
|
1149
|
-
return {
|
|
1150
|
-
'metrics': [handler.metric_handler_data.name
|
|
1151
|
-
for handler in setup.autoregressive_metrics],
|
|
1152
|
-
'losses': [handler.custom_loss_handler_data.name
|
|
1153
|
-
for handler in setup.autoregressive_losses],
|
|
1154
|
-
'visualizers': [handler.visualizer_handler_data.name
|
|
1155
|
-
for handler in setup.autoregressive_visualizers],
|
|
1156
|
-
}
|
|
1157
|
-
|
|
1158
|
-
@staticmethod
|
|
1159
|
-
def _autoregressive_callable_kwargs(function: Callable[..., Any],
|
|
1160
|
-
inputs: Dict[str, npt.NDArray[np.float32]],
|
|
1161
|
-
outputs: Dict[str, npt.NDArray[np.float32]],
|
|
1162
|
-
chain_state: Any,
|
|
1163
|
-
wired_args: Dict[str, npt.NDArray[np.float32]]
|
|
1164
|
-
) -> Dict[str, Any]:
|
|
1165
|
-
implicit_values = {'inputs': inputs, 'outputs': outputs, 'state': chain_state}
|
|
1166
|
-
kwargs: Dict[str, Any] = {}
|
|
1167
|
-
for arg_name in inspect.getfullargspec(function)[0]:
|
|
1168
|
-
if arg_name in implicit_values:
|
|
1169
|
-
kwargs[arg_name] = implicit_values[arg_name]
|
|
1170
|
-
elif arg_name in wired_args:
|
|
1171
|
-
kwargs[arg_name] = wired_args[arg_name]
|
|
1172
|
-
else:
|
|
1173
|
-
raise Exception(f'Autoregressive callable is missing wired argument '
|
|
1174
|
-
f'"{arg_name}" — the platform mapping supplies only '
|
|
1175
|
-
f'{sorted(wired_args)}.')
|
|
1176
|
-
return kwargs
|
|
1177
|
-
|
|
1178
|
-
def run_autoregressive_metric(self, name: str,
|
|
1179
|
-
inputs: Dict[str, npt.NDArray[np.float32]],
|
|
1180
|
-
outputs: Dict[str, npt.NDArray[np.float32]],
|
|
1181
|
-
chain_state: Any,
|
|
1182
|
-
wired_args: Dict[str, npt.NDArray[np.float32]]) -> Any:
|
|
1183
|
-
self.exec_script()
|
|
1184
|
-
for handler in global_leap_binder.setup_container.autoregressive_metrics:
|
|
1185
|
-
if handler.metric_handler_data.name == name:
|
|
1186
|
-
return handler.function(**self._autoregressive_callable_kwargs(
|
|
1187
|
-
handler.function, inputs, outputs, chain_state, wired_args))
|
|
1188
|
-
raise Exception(f'No autoregressive metric named "{name}" is registered.')
|
|
1189
|
-
|
|
1190
|
-
def run_autoregressive_loss(self, name: str,
|
|
1191
|
-
inputs: Dict[str, npt.NDArray[np.float32]],
|
|
1192
|
-
outputs: Dict[str, npt.NDArray[np.float32]],
|
|
1193
|
-
chain_state: Any,
|
|
1194
|
-
wired_args: Dict[str, npt.NDArray[np.float32]]) -> Any:
|
|
1195
|
-
self.exec_script()
|
|
1196
|
-
for handler in global_leap_binder.setup_container.autoregressive_losses:
|
|
1197
|
-
if handler.custom_loss_handler_data.name == name:
|
|
1198
|
-
return handler.function(**self._autoregressive_callable_kwargs(
|
|
1199
|
-
handler.function, inputs, outputs, chain_state, wired_args))
|
|
1200
|
-
raise Exception(f'No autoregressive loss named "{name}" is registered.')
|
|
1201
|
-
|
|
1202
|
-
def run_autoregressive_visualizer(self, name: str,
|
|
1203
|
-
inputs: Dict[str, npt.NDArray[np.float32]],
|
|
1204
|
-
outputs: Dict[str, npt.NDArray[np.float32]],
|
|
1205
|
-
chain_state: Any,
|
|
1206
|
-
wired_args: Dict[str, npt.NDArray[np.float32]]) -> Any:
|
|
1207
|
-
self.exec_script()
|
|
1208
|
-
for handler in global_leap_binder.setup_container.autoregressive_visualizers:
|
|
1209
|
-
if handler.visualizer_handler_data.name == name:
|
|
1210
|
-
return handler.function(**self._autoregressive_callable_kwargs(
|
|
1211
|
-
handler.function, inputs, outputs, chain_state, wired_args))
|
|
1212
|
-
raise Exception(f'No autoregressive visualizer named "{name}" is registered.')
|
|
1264
|
+
return result
|
|
1213
1265
|
|
|
1214
1266
|
def get_prediction_names_in_order(self) -> List[str]:
|
|
1215
1267
|
"""Model output names by declared prediction-type order, used to key prev_outputs for the
|