code-loader 1.0.195.dev1__py3-none-any.whl → 1.0.197.dev0__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 +53 -66
- code_loader/inner_leap_binder/leapbinder.py +122 -29
- code_loader/inner_leap_binder/leapbinder_decorators.py +906 -279
- code_loader/leaploader.py +224 -265
- code_loader/leaploaderbase.py +71 -3
- code_loader/utils.py +51 -5
- {code_loader-1.0.195.dev1.dist-info → code_loader-1.0.197.dev0.dist-info}/METADATA +1 -1
- {code_loader-1.0.195.dev1.dist-info → code_loader-1.0.197.dev0.dist-info}/RECORD +10 -10
- {code_loader-1.0.195.dev1.dist-info → code_loader-1.0.197.dev0.dist-info}/LICENSE +0 -0
- {code_loader-1.0.195.dev1.dist-info → code_loader-1.0.197.dev0.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:
|
|
@@ -59,35 +60,14 @@ class LeapLoader(LeapLoaderBase):
|
|
|
59
60
|
|
|
60
61
|
@lru_cache()
|
|
61
62
|
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
|
|
73
|
-
if global_leap_binder.integration_test_func is not None:
|
|
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
66
|
if global_leap_binder.integration_test_func is not None:
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
global_leap_binder.integration_test_func(None, mapping_preprocess)
|
|
67
|
+
from code_loader.inner_leap_binder.leapbinder_decorators import \
|
|
68
|
+
_reset_model_loop_state
|
|
69
|
+
_reset_model_loop_state()
|
|
70
|
+
global_leap_binder.integration_test_func(None, PreprocessResponse(state=DataStateType.training, length=0))
|
|
91
71
|
except TypeError as e:
|
|
92
72
|
import traceback
|
|
93
73
|
global_leap_binder.setup_container = DatasetIntegrationSetup()
|
|
@@ -100,7 +80,6 @@ class LeapLoader(LeapLoaderBase):
|
|
|
100
80
|
raise DatasetScriptException(getattr(e, 'message', repr(e))) from e
|
|
101
81
|
finally:
|
|
102
82
|
# ensure that the environment variable is removed after the script execution
|
|
103
|
-
_leap_dec._mapping_dataset_is_grouped = False
|
|
104
83
|
if mapping_runtime_mode_env_var_mame in os.environ:
|
|
105
84
|
del os.environ[mapping_runtime_mode_env_var_mame]
|
|
106
85
|
|
|
@@ -271,20 +250,12 @@ class LeapLoader(LeapLoaderBase):
|
|
|
271
250
|
)
|
|
272
251
|
|
|
273
252
|
preprocess_result = self._preprocess_result()
|
|
274
|
-
if state == DataStateEnum.unlabeled and sample_id not in preprocess_result[state].
|
|
253
|
+
if state == DataStateEnum.unlabeled and sample_id not in preprocess_result[state].sample_ids:
|
|
275
254
|
self._preprocess_result(update_unlabeled_preprocess=True)
|
|
276
255
|
|
|
277
256
|
metadata, metadata_is_none = self.get_metadata(state, sample_id)
|
|
278
257
|
|
|
279
|
-
|
|
280
|
-
if global_leap_binder.setup_container.custom_latent_space is not None:
|
|
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)
|
|
258
|
+
custom_latent_spaces = self._get_custom_latent_spaces(sample_id, preprocess_result[state])
|
|
288
259
|
instance_mask = self._get_instances_masks(state, sample_id, instance_id)
|
|
289
260
|
sample = DatasetSample(inputs=self._get_inputs(state, sample_id),
|
|
290
261
|
gt=None if state == DataStateEnum.unlabeled else self._get_gt(state, sample_id),
|
|
@@ -292,7 +263,7 @@ class LeapLoader(LeapLoaderBase):
|
|
|
292
263
|
metadata_is_none=metadata_is_none,
|
|
293
264
|
index=sample_id,
|
|
294
265
|
state=state,
|
|
295
|
-
|
|
266
|
+
custom_latent_spaces=custom_latent_spaces,
|
|
296
267
|
instance_masks=instance_mask)
|
|
297
268
|
return sample
|
|
298
269
|
|
|
@@ -323,11 +294,7 @@ class LeapLoader(LeapLoaderBase):
|
|
|
323
294
|
handler.name, handler_result
|
|
324
295
|
)
|
|
325
296
|
|
|
326
|
-
|
|
327
|
-
if global_leap_binder.setup_container.custom_latent_space is not None:
|
|
328
|
-
custom_latent_space = global_leap_binder.setup_container.custom_latent_space.function(
|
|
329
|
-
original_sample_id, preprocess
|
|
330
|
-
)
|
|
297
|
+
custom_latent_spaces = self._get_custom_latent_spaces(original_sample_id, preprocess)
|
|
331
298
|
|
|
332
299
|
return DatasetSample(
|
|
333
300
|
inputs=inputs,
|
|
@@ -336,7 +303,7 @@ class LeapLoader(LeapLoaderBase):
|
|
|
336
303
|
metadata_is_none=metadata_is_none,
|
|
337
304
|
index=synthetic_index,
|
|
338
305
|
state=DataStateEnum.additional,
|
|
339
|
-
|
|
306
|
+
custom_latent_spaces=custom_latent_spaces,
|
|
340
307
|
instance_masks=None,
|
|
341
308
|
)
|
|
342
309
|
|
|
@@ -415,7 +382,7 @@ class LeapLoader(LeapLoaderBase):
|
|
|
415
382
|
if self.get_sample_id_type() is str:
|
|
416
383
|
max_allowed_item_size = np.dtype('<U256').itemsize
|
|
417
384
|
for state, preprocess_response in preprocess_result.items():
|
|
418
|
-
sample_ids_array = np.array(preprocess_response.
|
|
385
|
+
sample_ids_array = np.array(preprocess_response.sample_ids)
|
|
419
386
|
if sample_ids_array.dtype.itemsize > max_allowed_item_size:
|
|
420
387
|
raise Exception(f"Sample id are too long. Max allowed length is 256 charecters.")
|
|
421
388
|
|
|
@@ -505,31 +472,49 @@ class LeapLoader(LeapLoaderBase):
|
|
|
505
472
|
try:
|
|
506
473
|
preprocess_result = self._preprocess_result()
|
|
507
474
|
input_shapes: Dict[str, List[int]] = {}
|
|
475
|
+
expected_keys: Optional[FrozenSet[str]] = None
|
|
476
|
+
expected_keys_state = ''
|
|
508
477
|
for state, preprocess_response in preprocess_result.items():
|
|
509
478
|
if preprocess_response.sample_ids_to_instance_mappings:
|
|
510
479
|
raise Exception('Element instances are not supported together with '
|
|
511
480
|
'tensorleap_autoregressive_step.')
|
|
512
481
|
sample_id = preprocess_response.sample_ids[0]
|
|
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,
|
|
482
|
+
first_result = handler.function(sample_id, None, None, None, preprocess_response)
|
|
483
|
+
second_result = handler.function(sample_id, None, None, None, preprocess_response)
|
|
484
|
+
if not isinstance(first_result, tuple) or len(first_result) != 2:
|
|
485
|
+
raise Exception('The autoregressive step hook must return a (next_inputs, '
|
|
486
|
+
f'state) tuple, got {type(first_result)} '
|
|
487
|
+
f'(state: {state.name}).')
|
|
488
|
+
first_inputs, first_state = first_result
|
|
489
|
+
if not isinstance(first_inputs, dict) or not first_inputs:
|
|
516
490
|
raise Exception('The autoregressive step hook must return a dict of initial model '
|
|
517
|
-
f'inputs on its first call, got {type(
|
|
491
|
+
f'inputs on its first call, got {type(first_inputs)} '
|
|
518
492
|
f'(state: {state.name}).')
|
|
519
|
-
|
|
520
|
-
|
|
493
|
+
validate_autoregressive_state_types(first_state)
|
|
494
|
+
if not isinstance(second_result, tuple) or len(second_result) != 2 or \
|
|
495
|
+
not isinstance(second_result[0], dict) or \
|
|
496
|
+
set(second_result[0].keys()) != set(first_inputs.keys()) or \
|
|
497
|
+
not autoregressive_nests_equal(first_state, second_result[1]):
|
|
521
498
|
raise Exception('The autoregressive step hook is not deterministic — two calls '
|
|
522
499
|
'with identical arguments returned different results '
|
|
523
500
|
f'(state: {state.name}). Seed any stochasticity from '
|
|
524
501
|
'sample_id: the engine replays steps after crash recovery '
|
|
525
502
|
'and relies on identical results.')
|
|
526
|
-
|
|
527
|
-
|
|
528
|
-
|
|
529
|
-
|
|
530
|
-
|
|
531
|
-
f'
|
|
532
|
-
|
|
503
|
+
if expected_keys is None:
|
|
504
|
+
expected_keys = frozenset(first_inputs)
|
|
505
|
+
expected_keys_state = state.name
|
|
506
|
+
elif frozenset(first_inputs) != expected_keys:
|
|
507
|
+
raise Exception(f'The autoregressive step hook returned different model input '
|
|
508
|
+
f'keys across states: {sorted(expected_keys)} '
|
|
509
|
+
f'({expected_keys_state}) vs {sorted(first_inputs)} '
|
|
510
|
+
f'({state.name}). The key set must be identical for every '
|
|
511
|
+
f'state.')
|
|
512
|
+
for key, value in first_inputs.items():
|
|
513
|
+
if key.startswith('_'):
|
|
514
|
+
raise Exception(f'The autoregressive step hook returned model input key '
|
|
515
|
+
f'"{key}" — underscore passthrough keys were replaced by '
|
|
516
|
+
f'the explicit state channel; move passthrough values '
|
|
517
|
+
f'into the returned state. State: {state.name}.')
|
|
533
518
|
if not isinstance(value, np.ndarray):
|
|
534
519
|
raise Exception(f'The autoregressive step hook returned a non-numpy value for '
|
|
535
520
|
f'model input "{key}" ({type(value)}). State: {state.name}.')
|
|
@@ -538,7 +523,7 @@ class LeapLoader(LeapLoaderBase):
|
|
|
538
523
|
f'model input "{key}" across states: {input_shapes[key]} vs '
|
|
539
524
|
f'{list(value.shape)}. Shapes must be fixed.')
|
|
540
525
|
input_shapes[key] = list(value.shape)
|
|
541
|
-
if not
|
|
526
|
+
if not autoregressive_nests_equal(value, second_result[0][key]):
|
|
542
527
|
raise Exception('The autoregressive step hook is not deterministic — two calls '
|
|
543
528
|
'with identical arguments returned different outputs for model '
|
|
544
529
|
f'input "{key}". Seed any stochasticity from sample_id: the '
|
|
@@ -552,19 +537,39 @@ class LeapLoader(LeapLoaderBase):
|
|
|
552
537
|
test_result.is_passed = False
|
|
553
538
|
return test_result
|
|
554
539
|
|
|
540
|
+
@staticmethod
|
|
541
|
+
def _validate_autoregressive_step_inputs(step_inputs: Dict[str, Any],
|
|
542
|
+
sample_id: Union[int, str]) -> None:
|
|
543
|
+
# The parse-time check only covers the first sample of each state; the engine runtime
|
|
544
|
+
# paths must enforce the same key/value contract for every sample they touch.
|
|
545
|
+
for key, value in step_inputs.items():
|
|
546
|
+
if key.startswith('_'):
|
|
547
|
+
raise Exception(f'The autoregressive step hook returned model input key "{key}" '
|
|
548
|
+
f'for sample {sample_id} — underscore passthrough keys were '
|
|
549
|
+
'replaced by the explicit state channel; move passthrough values '
|
|
550
|
+
'into the returned state.')
|
|
551
|
+
if not isinstance(value, np.ndarray):
|
|
552
|
+
raise Exception('The autoregressive step hook returned a non-numpy value for '
|
|
553
|
+
f'model input "{key}" ({type(value).__name__}) for sample '
|
|
554
|
+
f'{sample_id}.')
|
|
555
|
+
|
|
555
556
|
def _ensure_autoregressive_input_shapes(self) -> Dict[str, List[int]]:
|
|
556
557
|
handler = global_leap_binder.setup_container.autoregressive_step
|
|
557
558
|
assert handler is not None
|
|
558
559
|
if handler.input_shapes is None:
|
|
559
560
|
preprocess_result = self._preprocess_result()
|
|
560
561
|
first_state_response = next(iter(preprocess_result.values()))
|
|
561
|
-
|
|
562
|
+
first_sample_id = first_state_response.sample_ids[0]
|
|
563
|
+
first_result = handler.function(first_sample_id, None, None, None,
|
|
562
564
|
first_state_response)
|
|
563
|
-
if not isinstance(first_result,
|
|
564
|
-
|
|
565
|
-
|
|
566
|
-
|
|
567
|
-
|
|
565
|
+
if not isinstance(first_result, tuple) or len(first_result) != 2 or \
|
|
566
|
+
not isinstance(first_result[0], dict):
|
|
567
|
+
raise Exception('The autoregressive step hook must return a (next_inputs, state) '
|
|
568
|
+
'tuple with a dict of initial model inputs on its first call, '
|
|
569
|
+
f'got {type(first_result)}.')
|
|
570
|
+
self._validate_autoregressive_step_inputs(first_result[0], first_sample_id)
|
|
571
|
+
handler.input_shapes = {key: list(value.shape)
|
|
572
|
+
for key, value in first_result[0].items()}
|
|
568
573
|
return handler.input_shapes
|
|
569
574
|
|
|
570
575
|
def run_simulation(self, sim_name, params=None, n_samples=1, seed=0,
|
|
@@ -833,6 +838,21 @@ class LeapLoader(LeapLoaderBase):
|
|
|
833
838
|
metric_inst = MetricInstance(metric.metric_handler_data.name, metric.metric_handler_data.arg_names)
|
|
834
839
|
metrics.append(metric_inst)
|
|
835
840
|
|
|
841
|
+
# Autoregressive decorators are reported alongside the regular ones so the platform sees
|
|
842
|
+
# their names; the engine tells them apart by name via get_autoregressive_decorator_names.
|
|
843
|
+
for autoregressive_metric in setup.autoregressive_metrics:
|
|
844
|
+
metrics.append(MetricInstance(autoregressive_metric.metric_handler_data.name,
|
|
845
|
+
autoregressive_metric.metric_handler_data.arg_names))
|
|
846
|
+
for autoregressive_loss in setup.autoregressive_losses:
|
|
847
|
+
custom_losses.append(CustomLossInstance(
|
|
848
|
+
autoregressive_loss.custom_loss_handler_data.name,
|
|
849
|
+
autoregressive_loss.custom_loss_handler_data.arg_names))
|
|
850
|
+
for autoregressive_visualizer in setup.autoregressive_visualizers:
|
|
851
|
+
visualizers.append(VisualizerInstance(
|
|
852
|
+
autoregressive_visualizer.visualizer_handler_data.name,
|
|
853
|
+
autoregressive_visualizer.visualizer_handler_data.type,
|
|
854
|
+
autoregressive_visualizer.visualizer_handler_data.arg_names))
|
|
855
|
+
|
|
836
856
|
simulations = []
|
|
837
857
|
for sim in setup.simulations:
|
|
838
858
|
sim_config_serialized = {
|
|
@@ -878,58 +898,6 @@ class LeapLoader(LeapLoaderBase):
|
|
|
878
898
|
|
|
879
899
|
return sample_ids
|
|
880
900
|
|
|
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
|
-
|
|
933
901
|
def _get_dataset_handlers(self, handlers: Iterable[DatasetBaseHandler],
|
|
934
902
|
state: DataStateEnum, sample_id: Union[int, str]) -> Dict[str, npt.NDArray[np.float32]]:
|
|
935
903
|
result_agg = {}
|
|
@@ -941,71 +909,12 @@ class LeapLoader(LeapLoaderBase):
|
|
|
941
909
|
sample_id = original_local_id
|
|
942
910
|
else:
|
|
943
911
|
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
|
|
951
912
|
for handler in handlers:
|
|
952
913
|
handler_result = handler.function(sample_id, preprocess_state)
|
|
953
914
|
handler_name = handler.name
|
|
954
915
|
result_agg[handler_name] = handler_result
|
|
955
916
|
return result_agg
|
|
956
917
|
|
|
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
|
-
|
|
1009
918
|
def _get_inputs(self, state: DataStateEnum, sample_id: Union[int, str]) -> Dict[str, npt.NDArray[np.float32]]:
|
|
1010
919
|
inputs = self._get_dataset_handlers(global_leap_binder.setup_container.inputs, state, sample_id)
|
|
1011
920
|
autoregressive_handler = global_leap_binder.setup_container.autoregressive_step
|
|
@@ -1014,13 +923,16 @@ class LeapLoader(LeapLoaderBase):
|
|
|
1014
923
|
# the sample's (step-0) model inputs, so every sample-fetching flow (graph validation,
|
|
1015
924
|
# analysis, import checks) sees a fully-populated inputs dict.
|
|
1016
925
|
preprocess_response = self._preprocess_result()[state]
|
|
1017
|
-
|
|
1018
|
-
|
|
926
|
+
step_zero_result = autoregressive_handler.function(sample_id, None, None, None,
|
|
927
|
+
preprocess_response)
|
|
928
|
+
if not isinstance(step_zero_result, tuple) or len(step_zero_result) != 2 or \
|
|
929
|
+
not isinstance(step_zero_result[0], dict):
|
|
1019
930
|
raise Exception(
|
|
1020
|
-
'The autoregressive step hook must return a
|
|
1021
|
-
f'first call, got {type(
|
|
1022
|
-
|
|
1023
|
-
|
|
931
|
+
'The autoregressive step hook must return a (next_inputs, state) tuple with a '
|
|
932
|
+
f'dict of initial model inputs on its first call, got {type(step_zero_result)} '
|
|
933
|
+
f'for sample {sample_id}.')
|
|
934
|
+
self._validate_autoregressive_step_inputs(step_zero_result[0], sample_id)
|
|
935
|
+
inputs.update(step_zero_result[0])
|
|
1024
936
|
return inputs
|
|
1025
937
|
|
|
1026
938
|
def _get_instances_masks(self, state: DataStateEnum, sample_id: Union[int, str], instance_id: int) -> Optional[Dict[str, ElementInstance]]:
|
|
@@ -1106,18 +1018,12 @@ class LeapLoader(LeapLoaderBase):
|
|
|
1106
1018
|
sample_id = original_local_id
|
|
1107
1019
|
else:
|
|
1108
1020
|
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)
|
|
1112
1021
|
for handler in global_leap_binder.setup_container.metadata:
|
|
1113
1022
|
if requested_metadata_names:
|
|
1114
1023
|
if not is_metadata_name_starts_with_handler_name(handler):
|
|
1115
1024
|
continue
|
|
1116
1025
|
|
|
1117
|
-
|
|
1118
|
-
handler_result = handler.function(group_ids, preprocess_state)[pos]
|
|
1119
|
-
else:
|
|
1120
|
-
handler_result = handler.function(sample_id, preprocess_state)
|
|
1026
|
+
handler_result = handler.function(sample_id, preprocess_state)
|
|
1121
1027
|
if isinstance(handler_result, dict):
|
|
1122
1028
|
for single_metadata_name, single_metadata_result in handler_result.items():
|
|
1123
1029
|
handler_name = f'{handler.name}_{single_metadata_name}'
|
|
@@ -1136,66 +1042,6 @@ class LeapLoader(LeapLoaderBase):
|
|
|
1136
1042
|
|
|
1137
1043
|
return result_agg, is_none
|
|
1138
1044
|
|
|
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
|
-
|
|
1199
1045
|
@lru_cache()
|
|
1200
1046
|
def get_sample_id_type(self) -> Type:
|
|
1201
1047
|
preprocess_results = list(self._preprocess_result().values())
|
|
@@ -1206,10 +1052,29 @@ class LeapLoader(LeapLoaderBase):
|
|
|
1206
1052
|
|
|
1207
1053
|
return id_type
|
|
1208
1054
|
|
|
1055
|
+
@staticmethod
|
|
1056
|
+
def _get_custom_latent_spaces(
|
|
1057
|
+
sample_id: Union[int, str],
|
|
1058
|
+
preprocess: "PreprocessResponse") -> Optional[Dict[str, npt.NDArray[np.float32]]]:
|
|
1059
|
+
handlers = global_leap_binder.setup_container.custom_latent_spaces
|
|
1060
|
+
if not handlers:
|
|
1061
|
+
return None
|
|
1062
|
+
return {name: handler.function(sample_id, preprocess) for name, handler in handlers.items()}
|
|
1063
|
+
|
|
1209
1064
|
@lru_cache()
|
|
1210
1065
|
def has_custom_latent_space_decorator(self) -> bool:
|
|
1211
1066
|
self.exec_script()
|
|
1212
|
-
return global_leap_binder.setup_container.
|
|
1067
|
+
return len(global_leap_binder.setup_container.custom_latent_spaces) > 0
|
|
1068
|
+
|
|
1069
|
+
@lru_cache()
|
|
1070
|
+
def get_custom_latent_space_names(self) -> Tuple[str, ...]:
|
|
1071
|
+
"""Ordered names of all registered custom latent spaces.
|
|
1072
|
+
|
|
1073
|
+
Registration order is the canonical index mapping consumed by the engine
|
|
1074
|
+
(name i -> user_custom_i). Returns a tuple so the lru_cache value is hashable.
|
|
1075
|
+
"""
|
|
1076
|
+
self.exec_script()
|
|
1077
|
+
return tuple(global_leap_binder.setup_container.custom_latent_spaces.keys())
|
|
1213
1078
|
|
|
1214
1079
|
@lru_cache()
|
|
1215
1080
|
def has_autoregressive_step(self) -> bool:
|
|
@@ -1227,12 +1092,15 @@ class LeapLoader(LeapLoaderBase):
|
|
|
1227
1092
|
def run_autoregressive_step(self, sample_id: Union[int, str],
|
|
1228
1093
|
prev_inputs: Optional[Dict[str, npt.NDArray[np.float32]]],
|
|
1229
1094
|
prev_outputs: Optional[Dict[str, npt.NDArray[np.float32]]],
|
|
1230
|
-
|
|
1095
|
+
chain_state: Any,
|
|
1096
|
+
state: DataStateEnum
|
|
1097
|
+
) -> Tuple[Optional[Dict[str, npt.NDArray[np.float32]]], Any]:
|
|
1231
1098
|
"""Execute the user's autoregressive step hook against the in-process preprocess result.
|
|
1232
1099
|
|
|
1233
1100
|
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
|
|
1101
|
+
call per chain passes prev_inputs=None, prev_outputs=None, chain_state=None and returns
|
|
1102
|
+
the initial model inputs plus the initial state; a None next_inputs ends the chain — the
|
|
1103
|
+
terminating call still returns the final state.
|
|
1236
1104
|
"""
|
|
1237
1105
|
self.exec_script()
|
|
1238
1106
|
handler = global_leap_binder.setup_container.autoregressive_step
|
|
@@ -1240,28 +1108,119 @@ class LeapLoader(LeapLoaderBase):
|
|
|
1240
1108
|
raise Exception('run_autoregressive_step was called but the integration has no '
|
|
1241
1109
|
'tensorleap_autoregressive_step hook.')
|
|
1242
1110
|
preprocess_response = self._preprocess_result()[state]
|
|
1243
|
-
result = handler.function(sample_id, prev_inputs, prev_outputs,
|
|
1244
|
-
|
|
1245
|
-
|
|
1246
|
-
|
|
1111
|
+
result = handler.function(sample_id, prev_inputs, prev_outputs, chain_state,
|
|
1112
|
+
preprocess_response)
|
|
1113
|
+
if not isinstance(result, tuple) or len(result) != 2:
|
|
1114
|
+
raise Exception('The autoregressive step hook must return a (next_inputs, state) '
|
|
1115
|
+
f'tuple, got {type(result)} for sample {sample_id}.')
|
|
1116
|
+
next_inputs, new_chain_state = result
|
|
1117
|
+
validate_autoregressive_state_types(new_chain_state)
|
|
1118
|
+
if next_inputs is None:
|
|
1119
|
+
if prev_inputs is None:
|
|
1120
|
+
raise Exception('The autoregressive step hook returned None for next_inputs on '
|
|
1121
|
+
f'the first call of the chain (sample {sample_id}) — the first '
|
|
1122
|
+
'call must return the initial model inputs; None would produce '
|
|
1123
|
+
'an empty chain with no forward pass.')
|
|
1124
|
+
return None, new_chain_state
|
|
1125
|
+
if not isinstance(next_inputs, dict):
|
|
1247
1126
|
raise Exception('The autoregressive step hook must return a dict of model inputs or '
|
|
1248
|
-
f'None to end the chain, got {type(
|
|
1127
|
+
f'None to end the chain, got {type(next_inputs)} for sample '
|
|
1128
|
+
f'{sample_id}.')
|
|
1249
1129
|
# Parse-time validation only exercises step 0; enforce the fixed key-set/shape contract
|
|
1250
1130
|
# on every step here so drift fails with an actionable message instead of a cryptic
|
|
1251
1131
|
# engine error on a compiled graph.
|
|
1252
1132
|
expected_shapes = self._ensure_autoregressive_input_shapes()
|
|
1253
|
-
|
|
1254
|
-
if set(model_inputs) != set(expected_shapes):
|
|
1133
|
+
if set(next_inputs) != set(expected_shapes):
|
|
1255
1134
|
raise Exception('The autoregressive step hook must return the same model input keys '
|
|
1256
1135
|
f'on every call. Expected {sorted(expected_shapes)}, got '
|
|
1257
|
-
f'{sorted(
|
|
1258
|
-
for key, value in
|
|
1259
|
-
if isinstance(value, np.ndarray)
|
|
1136
|
+
f'{sorted(next_inputs)} for sample {sample_id}.')
|
|
1137
|
+
for key, value in next_inputs.items():
|
|
1138
|
+
if not isinstance(value, np.ndarray):
|
|
1139
|
+
raise Exception('The autoregressive step hook must return numpy arrays for every '
|
|
1140
|
+
f'model input on every call — input "{key}" is a '
|
|
1141
|
+
f'{type(value).__name__} for sample {sample_id} (forgot .numpy() '
|
|
1142
|
+
'on a fed-back model output?).')
|
|
1143
|
+
if list(value.shape) != expected_shapes[key]:
|
|
1260
1144
|
raise Exception('The autoregressive step hook must return fixed tensor shapes on '
|
|
1261
1145
|
f'every call (use fixed-length padding + mask for growing '
|
|
1262
1146
|
f'sequences). Input "{key}" was {expected_shapes[key]} on the '
|
|
1263
1147
|
f'first call and {list(value.shape)} now (sample {sample_id}).')
|
|
1264
|
-
return
|
|
1148
|
+
return next_inputs, new_chain_state
|
|
1149
|
+
|
|
1150
|
+
@lru_cache()
|
|
1151
|
+
def autoregressive_visualizer_by_name(self) -> Dict[str, VisualizerHandlerData]:
|
|
1152
|
+
self.exec_script()
|
|
1153
|
+
return {handler.visualizer_handler_data.name: handler.visualizer_handler_data
|
|
1154
|
+
for handler in global_leap_binder.setup_container.autoregressive_visualizers}
|
|
1155
|
+
|
|
1156
|
+
@lru_cache()
|
|
1157
|
+
def get_autoregressive_decorator_names(self) -> Dict[str, List[str]]:
|
|
1158
|
+
self.exec_script()
|
|
1159
|
+
setup = global_leap_binder.setup_container
|
|
1160
|
+
return {
|
|
1161
|
+
'metrics': [handler.metric_handler_data.name
|
|
1162
|
+
for handler in setup.autoregressive_metrics],
|
|
1163
|
+
'losses': [handler.custom_loss_handler_data.name
|
|
1164
|
+
for handler in setup.autoregressive_losses],
|
|
1165
|
+
'visualizers': [handler.visualizer_handler_data.name
|
|
1166
|
+
for handler in setup.autoregressive_visualizers],
|
|
1167
|
+
}
|
|
1168
|
+
|
|
1169
|
+
@staticmethod
|
|
1170
|
+
def _autoregressive_callable_kwargs(function: Callable[..., Any],
|
|
1171
|
+
inputs: Dict[str, npt.NDArray[np.float32]],
|
|
1172
|
+
outputs: Dict[str, npt.NDArray[np.float32]],
|
|
1173
|
+
chain_state: Any,
|
|
1174
|
+
wired_args: Dict[str, npt.NDArray[np.float32]]
|
|
1175
|
+
) -> Dict[str, Any]:
|
|
1176
|
+
implicit_values = {'inputs': inputs, 'outputs': outputs, 'state': chain_state}
|
|
1177
|
+
kwargs: Dict[str, Any] = {}
|
|
1178
|
+
for arg_name in inspect.getfullargspec(function)[0]:
|
|
1179
|
+
if arg_name in implicit_values:
|
|
1180
|
+
kwargs[arg_name] = implicit_values[arg_name]
|
|
1181
|
+
elif arg_name in wired_args:
|
|
1182
|
+
kwargs[arg_name] = wired_args[arg_name]
|
|
1183
|
+
else:
|
|
1184
|
+
raise Exception(f'Autoregressive callable is missing wired argument '
|
|
1185
|
+
f'"{arg_name}" — the platform mapping supplies only '
|
|
1186
|
+
f'{sorted(wired_args)}.')
|
|
1187
|
+
return kwargs
|
|
1188
|
+
|
|
1189
|
+
def run_autoregressive_metric(self, name: str,
|
|
1190
|
+
inputs: Dict[str, npt.NDArray[np.float32]],
|
|
1191
|
+
outputs: Dict[str, npt.NDArray[np.float32]],
|
|
1192
|
+
chain_state: Any,
|
|
1193
|
+
wired_args: Dict[str, npt.NDArray[np.float32]]) -> Any:
|
|
1194
|
+
self.exec_script()
|
|
1195
|
+
for handler in global_leap_binder.setup_container.autoregressive_metrics:
|
|
1196
|
+
if handler.metric_handler_data.name == name:
|
|
1197
|
+
return handler.function(**self._autoregressive_callable_kwargs(
|
|
1198
|
+
handler.function, inputs, outputs, chain_state, wired_args))
|
|
1199
|
+
raise Exception(f'No autoregressive metric named "{name}" is registered.')
|
|
1200
|
+
|
|
1201
|
+
def run_autoregressive_loss(self, name: str,
|
|
1202
|
+
inputs: Dict[str, npt.NDArray[np.float32]],
|
|
1203
|
+
outputs: Dict[str, npt.NDArray[np.float32]],
|
|
1204
|
+
chain_state: Any,
|
|
1205
|
+
wired_args: Dict[str, npt.NDArray[np.float32]]) -> Any:
|
|
1206
|
+
self.exec_script()
|
|
1207
|
+
for handler in global_leap_binder.setup_container.autoregressive_losses:
|
|
1208
|
+
if handler.custom_loss_handler_data.name == name:
|
|
1209
|
+
return handler.function(**self._autoregressive_callable_kwargs(
|
|
1210
|
+
handler.function, inputs, outputs, chain_state, wired_args))
|
|
1211
|
+
raise Exception(f'No autoregressive loss named "{name}" is registered.')
|
|
1212
|
+
|
|
1213
|
+
def run_autoregressive_visualizer(self, name: str,
|
|
1214
|
+
inputs: Dict[str, npt.NDArray[np.float32]],
|
|
1215
|
+
outputs: Dict[str, npt.NDArray[np.float32]],
|
|
1216
|
+
chain_state: Any,
|
|
1217
|
+
wired_args: Dict[str, npt.NDArray[np.float32]]) -> Any:
|
|
1218
|
+
self.exec_script()
|
|
1219
|
+
for handler in global_leap_binder.setup_container.autoregressive_visualizers:
|
|
1220
|
+
if handler.visualizer_handler_data.name == name:
|
|
1221
|
+
return handler.function(**self._autoregressive_callable_kwargs(
|
|
1222
|
+
handler.function, inputs, outputs, chain_state, wired_args))
|
|
1223
|
+
raise Exception(f'No autoregressive visualizer named "{name}" is registered.')
|
|
1265
1224
|
|
|
1266
1225
|
def get_prediction_names_in_order(self) -> List[str]:
|
|
1267
1226
|
"""Model output names by declared prediction-type order, used to key prev_outputs for the
|