code-loader 1.0.191.dev1__py3-none-any.whl → 1.0.193.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 +70 -55
- code_loader/inner_leap_binder/leapbinder.py +164 -17
- code_loader/inner_leap_binder/leapbinder_decorators.py +1063 -192
- code_loader/leaploader.py +286 -187
- code_loader/leaploaderbase.py +5 -0
- code_loader/utils.py +36 -5
- {code_loader-1.0.191.dev1.dist-info → code_loader-1.0.193.dev0.dist-info}/METADATA +1 -1
- {code_loader-1.0.191.dev1.dist-info → code_loader-1.0.193.dev0.dist-info}/RECORD +10 -10
- {code_loader-1.0.191.dev1.dist-info → code_loader-1.0.193.dev0.dist-info}/LICENSE +0 -0
- {code_loader-1.0.191.dev1.dist-info → code_loader-1.0.193.dev0.dist-info}/WHEEL +0 -0
code_loader/leaploader.py
CHANGED
|
@@ -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:
|
|
@@ -63,6 +64,9 @@ class LeapLoader(LeapLoaderBase):
|
|
|
63
64
|
os.environ[mapping_runtime_mode_env_var_mame] = 'TRUE'
|
|
64
65
|
self.evaluate_module()
|
|
65
66
|
if global_leap_binder.integration_test_func is not None:
|
|
67
|
+
from code_loader.inner_leap_binder.leapbinder_decorators import \
|
|
68
|
+
_reset_model_loop_state
|
|
69
|
+
_reset_model_loop_state()
|
|
66
70
|
global_leap_binder.integration_test_func(None, PreprocessResponse(state=DataStateType.training, length=0))
|
|
67
71
|
except TypeError as e:
|
|
68
72
|
import traceback
|
|
@@ -246,20 +250,16 @@ class LeapLoader(LeapLoaderBase):
|
|
|
246
250
|
)
|
|
247
251
|
|
|
248
252
|
preprocess_result = self._preprocess_result()
|
|
249
|
-
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:
|
|
250
254
|
self._preprocess_result(update_unlabeled_preprocess=True)
|
|
251
255
|
|
|
252
256
|
metadata, metadata_is_none = self.get_metadata(state, sample_id)
|
|
253
257
|
|
|
254
258
|
custom_latent_space = None
|
|
255
259
|
if global_leap_binder.setup_container.custom_latent_space is not None:
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
group_ids, pos = self._locate_group(preprocess_state, sample_id)
|
|
260
|
-
custom_latent_space = self._to_grouped_array(latent_fn(group_ids, preprocess_state))[pos]
|
|
261
|
-
else:
|
|
262
|
-
custom_latent_space = latent_fn(sample_id, preprocess_state)
|
|
260
|
+
custom_latent_space = global_leap_binder.setup_container.custom_latent_space.function(sample_id,
|
|
261
|
+
preprocess_result[
|
|
262
|
+
state])
|
|
263
263
|
instance_mask = self._get_instances_masks(state, sample_id, instance_id)
|
|
264
264
|
sample = DatasetSample(inputs=self._get_inputs(state, sample_id),
|
|
265
265
|
gt=None if state == DataStateEnum.unlabeled else self._get_gt(state, sample_id),
|
|
@@ -326,12 +326,16 @@ class LeapLoader(LeapLoaderBase):
|
|
|
326
326
|
|
|
327
327
|
integration_test_test_payload = self._check_integration_test_exists()
|
|
328
328
|
test_payloads.append(integration_test_test_payload)
|
|
329
|
+
global_leap_binder.validate_autoregressive_setup()
|
|
329
330
|
preprocess_test_payload = self._check_preprocess()
|
|
330
331
|
test_payloads.append(preprocess_test_payload)
|
|
331
332
|
handlers_test_payloads = self._check_handlers()
|
|
332
333
|
test_payloads.extend(handlers_test_payloads)
|
|
333
334
|
simulation_test_payloads = self._check_simulations()
|
|
334
335
|
test_payloads.extend(simulation_test_payloads)
|
|
336
|
+
autoregressive_test_payload = self._check_autoregressive_step()
|
|
337
|
+
if autoregressive_test_payload is not None:
|
|
338
|
+
test_payloads.append(autoregressive_test_payload)
|
|
335
339
|
is_valid = all([payload.is_passed for payload in test_payloads])
|
|
336
340
|
setup_response = self.get_dataset_setup_response(handlers_test_payloads)
|
|
337
341
|
|
|
@@ -386,7 +390,7 @@ class LeapLoader(LeapLoaderBase):
|
|
|
386
390
|
if self.get_sample_id_type() is str:
|
|
387
391
|
max_allowed_item_size = np.dtype('<U256').itemsize
|
|
388
392
|
for state, preprocess_response in preprocess_result.items():
|
|
389
|
-
sample_ids_array = np.array(preprocess_response.
|
|
393
|
+
sample_ids_array = np.array(preprocess_response.sample_ids)
|
|
390
394
|
if sample_ids_array.dtype.itemsize > max_allowed_item_size:
|
|
391
395
|
raise Exception(f"Sample id are too long. Max allowed length is 256 charecters.")
|
|
392
396
|
|
|
@@ -468,6 +472,85 @@ class LeapLoader(LeapLoaderBase):
|
|
|
468
472
|
result_payloads.append(test_result)
|
|
469
473
|
return result_payloads
|
|
470
474
|
|
|
475
|
+
def _check_autoregressive_step(self) -> Optional[DatasetTestResultPayload]:
|
|
476
|
+
handler = global_leap_binder.setup_container.autoregressive_step
|
|
477
|
+
if handler is None:
|
|
478
|
+
return None
|
|
479
|
+
test_result = DatasetTestResultPayload(handler.name)
|
|
480
|
+
try:
|
|
481
|
+
preprocess_result = self._preprocess_result()
|
|
482
|
+
input_shapes: Dict[str, List[int]] = {}
|
|
483
|
+
for state, preprocess_response in preprocess_result.items():
|
|
484
|
+
if preprocess_response.sample_ids_to_instance_mappings:
|
|
485
|
+
raise Exception('Element instances are not supported together with '
|
|
486
|
+
'tensorleap_autoregressive_step.')
|
|
487
|
+
sample_id = preprocess_response.sample_ids[0]
|
|
488
|
+
first_result = handler.function(sample_id, None, None, None, preprocess_response)
|
|
489
|
+
second_result = handler.function(sample_id, None, None, None, preprocess_response)
|
|
490
|
+
if not isinstance(first_result, tuple) or len(first_result) != 2:
|
|
491
|
+
raise Exception('The autoregressive step hook must return a (next_inputs, '
|
|
492
|
+
f'state) tuple, got {type(first_result)} '
|
|
493
|
+
f'(state: {state.name}).')
|
|
494
|
+
first_inputs, first_state = first_result
|
|
495
|
+
if not isinstance(first_inputs, dict) or not first_inputs:
|
|
496
|
+
raise Exception('The autoregressive step hook must return a dict of initial model '
|
|
497
|
+
f'inputs on its first call, got {type(first_inputs)} '
|
|
498
|
+
f'(state: {state.name}).')
|
|
499
|
+
validate_autoregressive_state_types(first_state)
|
|
500
|
+
if not isinstance(second_result, tuple) or len(second_result) != 2 or \
|
|
501
|
+
not isinstance(second_result[0], dict) or \
|
|
502
|
+
set(second_result[0].keys()) != set(first_inputs.keys()) or \
|
|
503
|
+
not autoregressive_nests_equal(first_state, second_result[1]):
|
|
504
|
+
raise Exception('The autoregressive step hook is not deterministic — two calls '
|
|
505
|
+
'with identical arguments returned different results '
|
|
506
|
+
f'(state: {state.name}). Seed any stochasticity from '
|
|
507
|
+
'sample_id: the engine replays steps after crash recovery '
|
|
508
|
+
'and relies on identical results.')
|
|
509
|
+
for key, value in first_inputs.items():
|
|
510
|
+
if key.startswith('_'):
|
|
511
|
+
raise Exception(f'The autoregressive step hook returned model input key '
|
|
512
|
+
f'"{key}" — underscore passthrough keys were replaced by '
|
|
513
|
+
f'the explicit state channel; move passthrough values '
|
|
514
|
+
f'into the returned state. State: {state.name}.')
|
|
515
|
+
if not isinstance(value, np.ndarray):
|
|
516
|
+
raise Exception(f'The autoregressive step hook returned a non-numpy value for '
|
|
517
|
+
f'model input "{key}" ({type(value)}). State: {state.name}.')
|
|
518
|
+
if key in input_shapes and input_shapes[key] != list(value.shape):
|
|
519
|
+
raise Exception(f'The autoregressive step hook returned different shapes for '
|
|
520
|
+
f'model input "{key}" across states: {input_shapes[key]} vs '
|
|
521
|
+
f'{list(value.shape)}. Shapes must be fixed.')
|
|
522
|
+
input_shapes[key] = list(value.shape)
|
|
523
|
+
if not np.array_equal(value, second_result[0][key]):
|
|
524
|
+
raise Exception('The autoregressive step hook is not deterministic — two calls '
|
|
525
|
+
'with identical arguments returned different outputs for model '
|
|
526
|
+
f'input "{key}". Seed any stochasticity from sample_id: the '
|
|
527
|
+
'engine replays steps after crash recovery and relies on '
|
|
528
|
+
'identical results.')
|
|
529
|
+
handler.input_shapes = input_shapes
|
|
530
|
+
except Exception as e:
|
|
531
|
+
line_number, file_name, stacktrace = get_root_exception_file_and_line_number()
|
|
532
|
+
test_result.display[TestingSectionEnum.Errors.name] = \
|
|
533
|
+
f"{repr(e)} in file {file_name}, line_number: {line_number}\nStacktrace:\n{stacktrace}"
|
|
534
|
+
test_result.is_passed = False
|
|
535
|
+
return test_result
|
|
536
|
+
|
|
537
|
+
def _ensure_autoregressive_input_shapes(self) -> Dict[str, List[int]]:
|
|
538
|
+
handler = global_leap_binder.setup_container.autoregressive_step
|
|
539
|
+
assert handler is not None
|
|
540
|
+
if handler.input_shapes is None:
|
|
541
|
+
preprocess_result = self._preprocess_result()
|
|
542
|
+
first_state_response = next(iter(preprocess_result.values()))
|
|
543
|
+
first_result = handler.function(first_state_response.sample_ids[0], None, None, None,
|
|
544
|
+
first_state_response)
|
|
545
|
+
if not isinstance(first_result, tuple) or len(first_result) != 2 or \
|
|
546
|
+
not isinstance(first_result[0], dict):
|
|
547
|
+
raise Exception('The autoregressive step hook must return a (next_inputs, state) '
|
|
548
|
+
'tuple with a dict of initial model inputs on its first call, '
|
|
549
|
+
f'got {type(first_result)}.')
|
|
550
|
+
handler.input_shapes = {key: list(value.shape) for key, value in first_result[0].items()
|
|
551
|
+
if isinstance(value, np.ndarray)}
|
|
552
|
+
return handler.input_shapes
|
|
553
|
+
|
|
471
554
|
def run_simulation(self, sim_name, params=None, n_samples=1, seed=0,
|
|
472
555
|
sample_ids=None, extend_preprocess=True):
|
|
473
556
|
# type: (str, Optional[Dict[str, Any]], int, int, Optional[List[str]], bool) -> Dict[str, Any]
|
|
@@ -658,6 +741,14 @@ class LeapLoader(LeapLoaderBase):
|
|
|
658
741
|
raise Exception(f"cant calculate shape for input, input name:{inp.name}")
|
|
659
742
|
inputs.append(DatasetInputInstance(name=inp.name, shape=inp.shape, channel_dim=inp.channel_dim))
|
|
660
743
|
|
|
744
|
+
if setup.autoregressive_step is not None:
|
|
745
|
+
# The hook is the sole input source; report its step-0 inputs as the dataset inputs so
|
|
746
|
+
# graph building, input-coverage validation and type dictionaries see real specs.
|
|
747
|
+
# channel_dim=-1 is intentional for AR inputs — there is no InputHandler to carry a
|
|
748
|
+
# user-declared value; expose it on the decorator if a use case ever needs otherwise.
|
|
749
|
+
for input_name, input_shape in self._ensure_autoregressive_input_shapes().items():
|
|
750
|
+
inputs.append(DatasetInputInstance(name=input_name, shape=input_shape, channel_dim=-1))
|
|
751
|
+
|
|
661
752
|
ground_truths = []
|
|
662
753
|
for gt in setup.ground_truths:
|
|
663
754
|
if gt.shape is None:
|
|
@@ -702,6 +793,21 @@ class LeapLoader(LeapLoaderBase):
|
|
|
702
793
|
metric_inst = MetricInstance(metric.metric_handler_data.name, metric.metric_handler_data.arg_names)
|
|
703
794
|
metrics.append(metric_inst)
|
|
704
795
|
|
|
796
|
+
# Autoregressive decorators are reported alongside the regular ones so the platform sees
|
|
797
|
+
# their names; the engine tells them apart by name via get_autoregressive_decorator_names.
|
|
798
|
+
for autoregressive_metric in setup.autoregressive_metrics:
|
|
799
|
+
metrics.append(MetricInstance(autoregressive_metric.metric_handler_data.name,
|
|
800
|
+
autoregressive_metric.metric_handler_data.arg_names))
|
|
801
|
+
for autoregressive_loss in setup.autoregressive_losses:
|
|
802
|
+
custom_losses.append(CustomLossInstance(
|
|
803
|
+
autoregressive_loss.custom_loss_handler_data.name,
|
|
804
|
+
autoregressive_loss.custom_loss_handler_data.arg_names))
|
|
805
|
+
for autoregressive_visualizer in setup.autoregressive_visualizers:
|
|
806
|
+
visualizers.append(VisualizerInstance(
|
|
807
|
+
autoregressive_visualizer.visualizer_handler_data.name,
|
|
808
|
+
autoregressive_visualizer.visualizer_handler_data.type,
|
|
809
|
+
autoregressive_visualizer.visualizer_handler_data.arg_names))
|
|
810
|
+
|
|
705
811
|
simulations = []
|
|
706
812
|
for sim in setup.simulations:
|
|
707
813
|
sim_config_serialized = {
|
|
@@ -747,57 +853,6 @@ class LeapLoader(LeapLoaderBase):
|
|
|
747
853
|
|
|
748
854
|
return sample_ids
|
|
749
855
|
|
|
750
|
-
@staticmethod
|
|
751
|
-
def _grouped_dict_keys(rows: List[Dict[str, Any]], handler_name: str) -> List[str]:
|
|
752
|
-
"""Keys for a group of dict-metadata rows, requiring every row to share the same keys.
|
|
753
|
-
A group must expose a uniform metadata schema; otherwise the per-key lists would silently
|
|
754
|
-
drop or KeyError on rows that differ from row 0."""
|
|
755
|
-
keys = list(rows[0].keys())
|
|
756
|
-
key_set = set(keys)
|
|
757
|
-
for pos, row in enumerate(rows):
|
|
758
|
-
if set(row.keys()) != key_set:
|
|
759
|
-
raise ValueError(
|
|
760
|
-
f"Metadata handler {handler_name!r} returned inconsistent dict keys across the "
|
|
761
|
-
f"group: row 0 has {sorted(key_set)} but row {pos} has {sorted(row.keys())}. "
|
|
762
|
-
f"All rows in a group must share the same metadata keys.")
|
|
763
|
-
return keys
|
|
764
|
-
|
|
765
|
-
@staticmethod
|
|
766
|
-
def _to_grouped_array(raw: Any) -> Union[npt.NDArray[np.float32], List[npt.NDArray[np.float32]]]:
|
|
767
|
-
"""Normalize a grouped encoder result. A list of uniformly-shaped per-sample arrays is
|
|
768
|
-
stacked into a single (B, *) array; a list whose samples differ in shape is kept as a
|
|
769
|
-
list of per-sample arrays (variable-dim groups); an already-grouped array is returned
|
|
770
|
-
as-is."""
|
|
771
|
-
if isinstance(raw, list):
|
|
772
|
-
shapes = {np.asarray(sample).shape for sample in raw}
|
|
773
|
-
if len(shapes) == 1:
|
|
774
|
-
return np.stack(raw, axis=0)
|
|
775
|
-
return raw
|
|
776
|
-
return raw
|
|
777
|
-
|
|
778
|
-
def _locate_group(self, preprocess_state: PreprocessResponse,
|
|
779
|
-
sample_id: Union[int, str]) -> Tuple[List[Union[int, str]], int]:
|
|
780
|
-
"""Map a sample id to (its group, its position in that group). Cached per
|
|
781
|
-
PreprocessResponse so grouped single-sample fetches don't rescan the groups."""
|
|
782
|
-
cache = getattr(self, '_group_pos_cache', None)
|
|
783
|
-
if cache is None:
|
|
784
|
-
cache = {}
|
|
785
|
-
self._group_pos_cache = cache
|
|
786
|
-
mapping = cache.get(id(preprocess_state))
|
|
787
|
-
if mapping is None:
|
|
788
|
-
mapping = {}
|
|
789
|
-
for group in preprocess_state.groups:
|
|
790
|
-
for pos, sid in enumerate(group):
|
|
791
|
-
if sid in mapping:
|
|
792
|
-
raise ValueError(
|
|
793
|
-
f"Duplicate sample id {sid!r} across groups in the preprocess response; "
|
|
794
|
-
f"sample ids must be unique within and across groups.")
|
|
795
|
-
mapping[sid] = (group, pos)
|
|
796
|
-
cache[id(preprocess_state)] = mapping
|
|
797
|
-
if sample_id not in mapping:
|
|
798
|
-
raise KeyError(f"Sample id {sample_id!r} is not present in any group of this preprocess response.")
|
|
799
|
-
return mapping[sample_id]
|
|
800
|
-
|
|
801
856
|
def _get_dataset_handlers(self, handlers: Iterable[DatasetBaseHandler],
|
|
802
857
|
state: DataStateEnum, sample_id: Union[int, str]) -> Dict[str, npt.NDArray[np.float32]]:
|
|
803
858
|
result_agg = {}
|
|
@@ -809,72 +864,30 @@ class LeapLoader(LeapLoaderBase):
|
|
|
809
864
|
sample_id = original_local_id
|
|
810
865
|
else:
|
|
811
866
|
preprocess_state = preprocess_result[state]
|
|
812
|
-
if preprocess_state.is_grouped:
|
|
813
|
-
# Grouped dataset: call the encoder once with the whole group, index out the sample.
|
|
814
|
-
group_ids, pos = self._locate_group(preprocess_state, sample_id)
|
|
815
|
-
for handler in handlers:
|
|
816
|
-
grouped = self._to_grouped_array(handler.function(group_ids, preprocess_state))
|
|
817
|
-
result_agg[handler.name] = grouped[pos]
|
|
818
|
-
return result_agg
|
|
819
867
|
for handler in handlers:
|
|
820
868
|
handler_result = handler.function(sample_id, preprocess_state)
|
|
821
869
|
handler_name = handler.name
|
|
822
870
|
result_agg[handler_name] = handler_result
|
|
823
871
|
return result_agg
|
|
824
872
|
|
|
825
|
-
def get_samples(self, state: DataStateEnum, group_ids: List[Union[int, str]]) -> DatasetSample:
|
|
826
|
-
"""Group-aware fetch: hand the whole group to each encoder in a single call and return a
|
|
827
|
-
grouped DatasetSample (inputs/gt as (B, *), metadata as per-key lists of length B, index =
|
|
828
|
-
the group). This is the path a group-aware engine calls to read a file once per group.
|
|
829
|
-
Row order matches group_ids exactly."""
|
|
830
|
-
self.exec_script()
|
|
831
|
-
preprocess_state = self._preprocess_result()[state]
|
|
832
|
-
if preprocess_state.is_grouped:
|
|
833
|
-
# All requested ids must belong to a single group (one file). A cross-group request
|
|
834
|
-
# would force the encoder to load multiple files, defeating the one-load-per-group
|
|
835
|
-
# contract; partitioning a scattered request by group is the engine's responsibility.
|
|
836
|
-
distinct_groups = {id(self._locate_group(preprocess_state, sid)[0]) for sid in group_ids}
|
|
837
|
-
assert len(distinct_groups) == 1, (
|
|
838
|
-
f"get_samples received sample ids spanning {len(distinct_groups)} groups; a request "
|
|
839
|
-
f"must be confined to a single group. The engine partitions cross-group requests and "
|
|
840
|
-
f"issues one get_samples call per group.")
|
|
841
|
-
inputs = {handler.name: self._to_grouped_array(handler.function(group_ids, preprocess_state))
|
|
842
|
-
for handler in global_leap_binder.setup_container.inputs}
|
|
843
|
-
gt = None
|
|
844
|
-
if state != DataStateEnum.unlabeled:
|
|
845
|
-
gt = {handler.name: self._to_grouped_array(handler.function(group_ids, preprocess_state))
|
|
846
|
-
for handler in global_leap_binder.setup_container.ground_truths}
|
|
847
|
-
|
|
848
|
-
metadata: Dict[str, Any] = {}
|
|
849
|
-
metadata_is_none: Dict[str, Any] = {}
|
|
850
|
-
for handler in global_leap_binder.setup_container.metadata:
|
|
851
|
-
rows = handler.function(group_ids, preprocess_state) # list of B scalars/dicts
|
|
852
|
-
if rows and isinstance(rows[0], dict):
|
|
853
|
-
for key in self._grouped_dict_keys(rows, handler.name):
|
|
854
|
-
name = "{}_{}".format(handler.name, key)
|
|
855
|
-
converted = [self._convert_metadata_to_correct_type(name, row[key]) for row in rows]
|
|
856
|
-
metadata[name] = [v for v, _ in converted]
|
|
857
|
-
metadata_is_none[name] = [is_none for _, is_none in converted]
|
|
858
|
-
else:
|
|
859
|
-
converted = [self._convert_metadata_to_correct_type(handler.name, row) for row in rows]
|
|
860
|
-
metadata[handler.name] = [v for v, _ in converted]
|
|
861
|
-
metadata_is_none[handler.name] = [is_none for _, is_none in converted]
|
|
862
|
-
|
|
863
|
-
# custom_latent_space is group-aware like the input/GT encoders: hand it the whole group in a
|
|
864
|
-
# single call so a file-backed latent fn loads the file once, and normalize to (B, d). This
|
|
865
|
-
# keeps the mandatory group path non-lossy. instance_masks stay None here: they are a
|
|
866
|
-
# per-(sample, instance) concern that needs an instance_id the group fetch does not carry.
|
|
867
|
-
custom_latent_space = None
|
|
868
|
-
if global_leap_binder.setup_container.custom_latent_space is not None:
|
|
869
|
-
latent_fn = global_leap_binder.setup_container.custom_latent_space.function
|
|
870
|
-
custom_latent_space = self._to_grouped_array(latent_fn(group_ids, preprocess_state))
|
|
871
|
-
|
|
872
|
-
return DatasetSample(inputs=inputs, gt=gt, metadata=metadata, metadata_is_none=metadata_is_none,
|
|
873
|
-
index=list(group_ids), state=state, custom_latent_space=custom_latent_space,
|
|
874
|
-
instance_masks=None)
|
|
875
|
-
|
|
876
873
|
def _get_inputs(self, state: DataStateEnum, sample_id: Union[int, str]) -> Dict[str, npt.NDArray[np.float32]]:
|
|
877
|
-
|
|
874
|
+
inputs = self._get_dataset_handlers(global_leap_binder.setup_container.inputs, state, sample_id)
|
|
875
|
+
autoregressive_handler = global_leap_binder.setup_container.autoregressive_step
|
|
876
|
+
if autoregressive_handler is not None:
|
|
877
|
+
# Autoregressive integrations have no input encoders — the hook's first call supplies
|
|
878
|
+
# the sample's (step-0) model inputs, so every sample-fetching flow (graph validation,
|
|
879
|
+
# analysis, import checks) sees a fully-populated inputs dict.
|
|
880
|
+
preprocess_response = self._preprocess_result()[state]
|
|
881
|
+
step_zero_result = autoregressive_handler.function(sample_id, None, None, None,
|
|
882
|
+
preprocess_response)
|
|
883
|
+
if not isinstance(step_zero_result, tuple) or len(step_zero_result) != 2 or \
|
|
884
|
+
not isinstance(step_zero_result[0], dict):
|
|
885
|
+
raise Exception(
|
|
886
|
+
'The autoregressive step hook must return a (next_inputs, state) tuple with a '
|
|
887
|
+
f'dict of initial model inputs on its first call, got {type(step_zero_result)} '
|
|
888
|
+
f'for sample {sample_id}.')
|
|
889
|
+
inputs.update(step_zero_result[0])
|
|
890
|
+
return inputs
|
|
878
891
|
|
|
879
892
|
def _get_instances_masks(self, state: DataStateEnum, sample_id: Union[int, str], instance_id: int) -> Optional[Dict[str, ElementInstance]]:
|
|
880
893
|
if instance_id is None:
|
|
@@ -897,6 +910,12 @@ class LeapLoader(LeapLoaderBase):
|
|
|
897
910
|
def _get_gt(self, state: DataStateEnum, sample_id: Union[int, str]) -> Dict[str, npt.NDArray[np.float32]]:
|
|
898
911
|
return self._get_dataset_handlers(global_leap_binder.setup_container.ground_truths, state, sample_id)
|
|
899
912
|
|
|
913
|
+
def get_gt(self, state: DataStateEnum, sample_id: Union[int, str]) -> Dict[str, npt.NDArray[np.float32]]:
|
|
914
|
+
"""Ground-truth tensors only ({} when no GT encoders are declared) — lets the engine's
|
|
915
|
+
rollout fetch a chain's GT without re-running the autoregressive hook via get_sample."""
|
|
916
|
+
self.exec_script()
|
|
917
|
+
return self._get_gt(state, sample_id)
|
|
918
|
+
|
|
900
919
|
@lru_cache()
|
|
901
920
|
def _metadata_name_to_type(self) -> Dict[str, DatasetMetadataType]:
|
|
902
921
|
global_leap_binder.check_preprocess(self._preprocess_result())
|
|
@@ -953,18 +972,12 @@ class LeapLoader(LeapLoaderBase):
|
|
|
953
972
|
sample_id = original_local_id
|
|
954
973
|
else:
|
|
955
974
|
preprocess_state = preprocess_result[state]
|
|
956
|
-
group_ids, pos = (None, None)
|
|
957
|
-
if preprocess_state.is_grouped:
|
|
958
|
-
group_ids, pos = self._locate_group(preprocess_state, sample_id)
|
|
959
975
|
for handler in global_leap_binder.setup_container.metadata:
|
|
960
976
|
if requested_metadata_names:
|
|
961
977
|
if not is_metadata_name_starts_with_handler_name(handler):
|
|
962
978
|
continue
|
|
963
979
|
|
|
964
|
-
|
|
965
|
-
handler_result = handler.function(group_ids, preprocess_state)[pos]
|
|
966
|
-
else:
|
|
967
|
-
handler_result = handler.function(sample_id, preprocess_state)
|
|
980
|
+
handler_result = handler.function(sample_id, preprocess_state)
|
|
968
981
|
if isinstance(handler_result, dict):
|
|
969
982
|
for single_metadata_name, single_metadata_result in handler_result.items():
|
|
970
983
|
handler_name = f'{handler.name}_{single_metadata_name}'
|
|
@@ -983,66 +996,6 @@ class LeapLoader(LeapLoaderBase):
|
|
|
983
996
|
|
|
984
997
|
return result_agg, is_none
|
|
985
998
|
|
|
986
|
-
def _grouped_metadata_for_group(self, preprocess_state: PreprocessResponse,
|
|
987
|
-
group_ids: List[Union[int, str]],
|
|
988
|
-
requested_metadata_names: Optional[List[str]]
|
|
989
|
-
) -> Tuple[Dict[str, List[Any]], Dict[str, List[bool]]]:
|
|
990
|
-
def is_wanted(_handler):
|
|
991
|
-
if not requested_metadata_names:
|
|
992
|
-
return True
|
|
993
|
-
for metadata_name in requested_metadata_names:
|
|
994
|
-
if metadata_name.startswith(_handler.name + '_') or metadata_name == _handler.name:
|
|
995
|
-
return True
|
|
996
|
-
return False
|
|
997
|
-
|
|
998
|
-
results: Dict[str, List[Any]] = {}
|
|
999
|
-
is_none: Dict[str, List[bool]] = {}
|
|
1000
|
-
for handler in global_leap_binder.setup_container.metadata:
|
|
1001
|
-
if requested_metadata_names and not is_wanted(handler):
|
|
1002
|
-
continue
|
|
1003
|
-
rows = handler.function(group_ids, preprocess_state)
|
|
1004
|
-
if rows and isinstance(rows[0], dict):
|
|
1005
|
-
for key in self._grouped_dict_keys(rows, handler.name):
|
|
1006
|
-
name = f'{handler.name}_{key}'
|
|
1007
|
-
if requested_metadata_names and name not in requested_metadata_names:
|
|
1008
|
-
continue
|
|
1009
|
-
converted = [self._convert_metadata_to_correct_type(name, row[key]) for row in rows]
|
|
1010
|
-
results[name] = [v for v, _ in converted]
|
|
1011
|
-
is_none[name] = [n for _, n in converted]
|
|
1012
|
-
else:
|
|
1013
|
-
name = handler.name
|
|
1014
|
-
if requested_metadata_names and name not in requested_metadata_names:
|
|
1015
|
-
continue
|
|
1016
|
-
converted = [self._convert_metadata_to_correct_type(name, row) for row in rows]
|
|
1017
|
-
results[name] = [v for v, _ in converted]
|
|
1018
|
-
is_none[name] = [n for _, n in converted]
|
|
1019
|
-
return results, is_none
|
|
1020
|
-
|
|
1021
|
-
def get_metadata_multiple_samples(self, state: DataStateEnum, sample_ids: Union[List[int], List[str]],
|
|
1022
|
-
requested_metadata_names: Optional[List[str]] = None
|
|
1023
|
-
) -> Tuple[Dict[str, Union[List[str], List[int], List[bool], List[float]]],
|
|
1024
|
-
Dict[str, List[bool]]]:
|
|
1025
|
-
preprocess_state = self._preprocess_result().get(state)
|
|
1026
|
-
if preprocess_state is None or not preprocess_state.is_grouped:
|
|
1027
|
-
return super().get_metadata_multiple_samples(state, sample_ids, requested_metadata_names)
|
|
1028
|
-
|
|
1029
|
-
sample_id_type = self.get_sample_id_type()
|
|
1030
|
-
aggregated_results: Dict[str, List[Any]] = {}
|
|
1031
|
-
aggregated_is_none: Dict[str, List[bool]] = {}
|
|
1032
|
-
group_cache: Dict[int, Tuple[Dict[str, List[Any]], Dict[str, List[bool]]]] = {}
|
|
1033
|
-
for sample_id in sample_ids:
|
|
1034
|
-
sample_id = sample_id_type(sample_id)
|
|
1035
|
-
group_ids, pos = self._locate_group(preprocess_state, sample_id)
|
|
1036
|
-
key = id(group_ids)
|
|
1037
|
-
if key not in group_cache:
|
|
1038
|
-
group_cache[key] = self._grouped_metadata_for_group(
|
|
1039
|
-
preprocess_state, group_ids, requested_metadata_names)
|
|
1040
|
-
group_results, group_is_none = group_cache[key]
|
|
1041
|
-
for name, values in group_results.items():
|
|
1042
|
-
aggregated_results.setdefault(name, []).append(values[pos])
|
|
1043
|
-
aggregated_is_none.setdefault(name, []).append(group_is_none[name][pos])
|
|
1044
|
-
return aggregated_results, aggregated_is_none
|
|
1045
|
-
|
|
1046
999
|
@lru_cache()
|
|
1047
1000
|
def get_sample_id_type(self) -> Type:
|
|
1048
1001
|
preprocess_results = list(self._preprocess_result().values())
|
|
@@ -1058,6 +1011,152 @@ class LeapLoader(LeapLoaderBase):
|
|
|
1058
1011
|
self.exec_script()
|
|
1059
1012
|
return global_leap_binder.setup_container.custom_latent_space is not None
|
|
1060
1013
|
|
|
1014
|
+
@lru_cache()
|
|
1015
|
+
def has_autoregressive_step(self) -> bool:
|
|
1016
|
+
self.exec_script()
|
|
1017
|
+
return global_leap_binder.setup_container.autoregressive_step is not None
|
|
1018
|
+
|
|
1019
|
+
@lru_cache()
|
|
1020
|
+
def get_autoregressive_latent_space_aggregation(self) -> str:
|
|
1021
|
+
self.exec_script()
|
|
1022
|
+
handler = global_leap_binder.setup_container.autoregressive_step
|
|
1023
|
+
if handler is None:
|
|
1024
|
+
return 'last_step'
|
|
1025
|
+
return handler.latent_space_aggregation
|
|
1026
|
+
|
|
1027
|
+
def run_autoregressive_step(self, sample_id: Union[int, str],
|
|
1028
|
+
prev_inputs: Optional[Dict[str, npt.NDArray[np.float32]]],
|
|
1029
|
+
prev_outputs: Optional[Dict[str, npt.NDArray[np.float32]]],
|
|
1030
|
+
chain_state: Any,
|
|
1031
|
+
state: DataStateEnum
|
|
1032
|
+
) -> Tuple[Optional[Dict[str, npt.NDArray[np.float32]]], Any]:
|
|
1033
|
+
"""Execute the user's autoregressive step hook against the in-process preprocess result.
|
|
1034
|
+
|
|
1035
|
+
This is the engine rollout's per-step entry point (invoked on the generic pod). The first
|
|
1036
|
+
call per chain passes prev_inputs=None, prev_outputs=None, chain_state=None and returns
|
|
1037
|
+
the initial model inputs plus the initial state; a None next_inputs ends the chain — the
|
|
1038
|
+
terminating call still returns the final state.
|
|
1039
|
+
"""
|
|
1040
|
+
self.exec_script()
|
|
1041
|
+
handler = global_leap_binder.setup_container.autoregressive_step
|
|
1042
|
+
if handler is None:
|
|
1043
|
+
raise Exception('run_autoregressive_step was called but the integration has no '
|
|
1044
|
+
'tensorleap_autoregressive_step hook.')
|
|
1045
|
+
preprocess_response = self._preprocess_result()[state]
|
|
1046
|
+
result = handler.function(sample_id, prev_inputs, prev_outputs, chain_state,
|
|
1047
|
+
preprocess_response)
|
|
1048
|
+
if not isinstance(result, tuple) or len(result) != 2:
|
|
1049
|
+
raise Exception('The autoregressive step hook must return a (next_inputs, state) '
|
|
1050
|
+
f'tuple, got {type(result)} for sample {sample_id}.')
|
|
1051
|
+
next_inputs, new_chain_state = result
|
|
1052
|
+
validate_autoregressive_state_types(new_chain_state)
|
|
1053
|
+
if next_inputs is None:
|
|
1054
|
+
return None, new_chain_state
|
|
1055
|
+
if not isinstance(next_inputs, dict):
|
|
1056
|
+
raise Exception('The autoregressive step hook must return a dict of model inputs or '
|
|
1057
|
+
f'None to end the chain, got {type(next_inputs)} for sample '
|
|
1058
|
+
f'{sample_id}.')
|
|
1059
|
+
# Parse-time validation only exercises step 0; enforce the fixed key-set/shape contract
|
|
1060
|
+
# on every step here so drift fails with an actionable message instead of a cryptic
|
|
1061
|
+
# engine error on a compiled graph.
|
|
1062
|
+
expected_shapes = self._ensure_autoregressive_input_shapes()
|
|
1063
|
+
if set(next_inputs) != set(expected_shapes):
|
|
1064
|
+
raise Exception('The autoregressive step hook must return the same model input keys '
|
|
1065
|
+
f'on every call. Expected {sorted(expected_shapes)}, got '
|
|
1066
|
+
f'{sorted(next_inputs)} for sample {sample_id}.')
|
|
1067
|
+
for key, value in next_inputs.items():
|
|
1068
|
+
if isinstance(value, np.ndarray) and list(value.shape) != expected_shapes[key]:
|
|
1069
|
+
raise Exception('The autoregressive step hook must return fixed tensor shapes on '
|
|
1070
|
+
f'every call (use fixed-length padding + mask for growing '
|
|
1071
|
+
f'sequences). Input "{key}" was {expected_shapes[key]} on the '
|
|
1072
|
+
f'first call and {list(value.shape)} now (sample {sample_id}).')
|
|
1073
|
+
return next_inputs, new_chain_state
|
|
1074
|
+
|
|
1075
|
+
@lru_cache()
|
|
1076
|
+
def autoregressive_visualizer_by_name(self) -> Dict[str, VisualizerHandlerData]:
|
|
1077
|
+
self.exec_script()
|
|
1078
|
+
return {handler.visualizer_handler_data.name: handler.visualizer_handler_data
|
|
1079
|
+
for handler in global_leap_binder.setup_container.autoregressive_visualizers}
|
|
1080
|
+
|
|
1081
|
+
@lru_cache()
|
|
1082
|
+
def get_autoregressive_decorator_names(self) -> Dict[str, List[str]]:
|
|
1083
|
+
self.exec_script()
|
|
1084
|
+
setup = global_leap_binder.setup_container
|
|
1085
|
+
return {
|
|
1086
|
+
'metrics': [handler.metric_handler_data.name
|
|
1087
|
+
for handler in setup.autoregressive_metrics],
|
|
1088
|
+
'losses': [handler.custom_loss_handler_data.name
|
|
1089
|
+
for handler in setup.autoregressive_losses],
|
|
1090
|
+
'visualizers': [handler.visualizer_handler_data.name
|
|
1091
|
+
for handler in setup.autoregressive_visualizers],
|
|
1092
|
+
}
|
|
1093
|
+
|
|
1094
|
+
@staticmethod
|
|
1095
|
+
def _autoregressive_callable_kwargs(function: Callable[..., Any],
|
|
1096
|
+
inputs: Dict[str, npt.NDArray[np.float32]],
|
|
1097
|
+
outputs: Dict[str, npt.NDArray[np.float32]],
|
|
1098
|
+
chain_state: Any,
|
|
1099
|
+
wired_args: Dict[str, npt.NDArray[np.float32]]
|
|
1100
|
+
) -> Dict[str, Any]:
|
|
1101
|
+
implicit_values = {'inputs': inputs, 'outputs': outputs, 'state': chain_state}
|
|
1102
|
+
kwargs: Dict[str, Any] = {}
|
|
1103
|
+
for arg_name in inspect.getfullargspec(function)[0]:
|
|
1104
|
+
if arg_name in implicit_values:
|
|
1105
|
+
kwargs[arg_name] = implicit_values[arg_name]
|
|
1106
|
+
elif arg_name in wired_args:
|
|
1107
|
+
kwargs[arg_name] = wired_args[arg_name]
|
|
1108
|
+
else:
|
|
1109
|
+
raise Exception(f'Autoregressive callable is missing wired argument '
|
|
1110
|
+
f'"{arg_name}" — the platform mapping supplies only '
|
|
1111
|
+
f'{sorted(wired_args)}.')
|
|
1112
|
+
return kwargs
|
|
1113
|
+
|
|
1114
|
+
def run_autoregressive_metric(self, name: str,
|
|
1115
|
+
inputs: Dict[str, npt.NDArray[np.float32]],
|
|
1116
|
+
outputs: Dict[str, npt.NDArray[np.float32]],
|
|
1117
|
+
chain_state: Any,
|
|
1118
|
+
wired_args: Dict[str, npt.NDArray[np.float32]]) -> Any:
|
|
1119
|
+
self.exec_script()
|
|
1120
|
+
for handler in global_leap_binder.setup_container.autoregressive_metrics:
|
|
1121
|
+
if handler.metric_handler_data.name == name:
|
|
1122
|
+
return handler.function(**self._autoregressive_callable_kwargs(
|
|
1123
|
+
handler.function, inputs, outputs, chain_state, wired_args))
|
|
1124
|
+
raise Exception(f'No autoregressive metric named "{name}" is registered.')
|
|
1125
|
+
|
|
1126
|
+
def run_autoregressive_loss(self, name: str,
|
|
1127
|
+
inputs: Dict[str, npt.NDArray[np.float32]],
|
|
1128
|
+
outputs: Dict[str, npt.NDArray[np.float32]],
|
|
1129
|
+
chain_state: Any,
|
|
1130
|
+
wired_args: Dict[str, npt.NDArray[np.float32]]) -> Any:
|
|
1131
|
+
self.exec_script()
|
|
1132
|
+
for handler in global_leap_binder.setup_container.autoregressive_losses:
|
|
1133
|
+
if handler.custom_loss_handler_data.name == name:
|
|
1134
|
+
return handler.function(**self._autoregressive_callable_kwargs(
|
|
1135
|
+
handler.function, inputs, outputs, chain_state, wired_args))
|
|
1136
|
+
raise Exception(f'No autoregressive loss named "{name}" is registered.')
|
|
1137
|
+
|
|
1138
|
+
def run_autoregressive_visualizer(self, name: str,
|
|
1139
|
+
inputs: Dict[str, npt.NDArray[np.float32]],
|
|
1140
|
+
outputs: Dict[str, npt.NDArray[np.float32]],
|
|
1141
|
+
chain_state: Any,
|
|
1142
|
+
wired_args: Dict[str, npt.NDArray[np.float32]]) -> Any:
|
|
1143
|
+
self.exec_script()
|
|
1144
|
+
for handler in global_leap_binder.setup_container.autoregressive_visualizers:
|
|
1145
|
+
if handler.visualizer_handler_data.name == name:
|
|
1146
|
+
return handler.function(**self._autoregressive_callable_kwargs(
|
|
1147
|
+
handler.function, inputs, outputs, chain_state, wired_args))
|
|
1148
|
+
raise Exception(f'No autoregressive visualizer named "{name}" is registered.')
|
|
1149
|
+
|
|
1150
|
+
def get_prediction_names_in_order(self) -> List[str]:
|
|
1151
|
+
"""Model output names by declared prediction-type order, used to key prev_outputs for the
|
|
1152
|
+
hook. AR integrations must declare prediction types (validate_autoregressive_setup) and
|
|
1153
|
+
the count is checked against the model's outputs at integration-test time; the declared
|
|
1154
|
+
ORDER matching the model's output order is the user's responsibility — it is not
|
|
1155
|
+
validated."""
|
|
1156
|
+
self.exec_script()
|
|
1157
|
+
return [prediction_type.name for prediction_type in
|
|
1158
|
+
global_leap_binder.setup_container.prediction_types]
|
|
1159
|
+
|
|
1061
1160
|
def get_instances_data(self, state: DataStateEnum) -> Tuple[Dict[str, List[str]], Dict[str, str]]:
|
|
1062
1161
|
preprocess_result = self._preprocess_result()
|
|
1063
1162
|
preprocess_state = preprocess_result.get(state)
|
code_loader/leaploaderbase.py
CHANGED
|
@@ -149,6 +149,11 @@ class LeapLoaderBase:
|
|
|
149
149
|
def has_custom_latent_space_decorator(self) -> bool:
|
|
150
150
|
pass
|
|
151
151
|
|
|
152
|
+
def has_autoregressive_step(self) -> bool:
|
|
153
|
+
# Default False so existing LeapLoaderBase implementations stay valid; the concrete
|
|
154
|
+
# loaders that can answer (LeapLoader in-process, LeapLoaderWithRedis via RPC) override.
|
|
155
|
+
return False
|
|
156
|
+
|
|
152
157
|
@abstractmethod
|
|
153
158
|
def get_heatmap_visualizer_raw_vis_input_arg_name(self, visualizer_name: str) -> Optional[str]:
|
|
154
159
|
pass
|
code_loader/utils.py
CHANGED
|
@@ -14,11 +14,6 @@ from code_loader.contract.enums import DatasetMetadataType
|
|
|
14
14
|
def to_numpy_return_wrapper(encoder_function: SectionCallableInterface) -> SectionCallableInterface:
|
|
15
15
|
def numpy_encoder_function(idx: Union[int, str], samples: PreprocessResponse) -> npt.NDArray[np.float32]:
|
|
16
16
|
result = encoder_function(idx, samples)
|
|
17
|
-
if isinstance(result, list) and len(result) > 0 and all(isinstance(sample, np.ndarray) for sample in result):
|
|
18
|
-
if len({sample.shape for sample in result}) > 1:
|
|
19
|
-
# Variable per-sample shapes within a group: keep the per-sample arrays as a list
|
|
20
|
-
# instead of forcing a stack that would raise an inhomogeneous-shape error.
|
|
21
|
-
return result
|
|
22
17
|
numpy_result: npt.NDArray[np.float32] = np.array(result)
|
|
23
18
|
return numpy_result
|
|
24
19
|
|
|
@@ -106,3 +101,39 @@ def get_metadata_type_from_variable(variable: Union[Optional[str], int, bool, Op
|
|
|
106
101
|
f"The return type should be one of [int, float, str, bool]. Got {metadata_type}")
|
|
107
102
|
return dataset_metadata_type
|
|
108
103
|
|
|
104
|
+
|
|
105
|
+
|
|
106
|
+
AUTOREGRESSIVE_STATE_LEAF_TYPES = (np.ndarray, np.generic, int, float, str, bool, type(None))
|
|
107
|
+
|
|
108
|
+
|
|
109
|
+
def validate_autoregressive_state_types(value: Any, path: str = 'state') -> None:
|
|
110
|
+
if isinstance(value, dict):
|
|
111
|
+
for key, item in value.items():
|
|
112
|
+
if not isinstance(key, str):
|
|
113
|
+
raise AssertionError(
|
|
114
|
+
f'autoregressive state validation failed: dict key {key!r} at {path} must be '
|
|
115
|
+
f'a string. Got {type(key).__name__}.')
|
|
116
|
+
validate_autoregressive_state_types(item, f'{path}[{key!r}]')
|
|
117
|
+
return
|
|
118
|
+
if isinstance(value, (list, tuple)):
|
|
119
|
+
for i, item in enumerate(value):
|
|
120
|
+
validate_autoregressive_state_types(item, f'{path}[{i}]')
|
|
121
|
+
return
|
|
122
|
+
if not isinstance(value, AUTOREGRESSIVE_STATE_LEAF_TYPES):
|
|
123
|
+
raise AssertionError(
|
|
124
|
+
f'autoregressive state validation failed: {path} holds a {type(value).__name__}. '
|
|
125
|
+
f'State must be built only from numpy arrays, numbers, strings, bools and nests of '
|
|
126
|
+
f'dicts/lists of those — it is serialized to the platform queue on every chain step.')
|
|
127
|
+
|
|
128
|
+
|
|
129
|
+
def autoregressive_nests_equal(a: Any, b: Any) -> bool:
|
|
130
|
+
if isinstance(a, dict) or isinstance(b, dict):
|
|
131
|
+
return isinstance(a, dict) and isinstance(b, dict) and set(a) == set(b) and \
|
|
132
|
+
all(autoregressive_nests_equal(a[key], b[key]) for key in a)
|
|
133
|
+
if isinstance(a, (list, tuple)) or isinstance(b, (list, tuple)):
|
|
134
|
+
return isinstance(a, (list, tuple)) and isinstance(b, (list, tuple)) and \
|
|
135
|
+
len(a) == len(b) and all(autoregressive_nests_equal(x, y) for x, y in zip(a, b))
|
|
136
|
+
if isinstance(a, np.ndarray) or isinstance(b, np.ndarray):
|
|
137
|
+
return isinstance(a, np.ndarray) and isinstance(b, np.ndarray) and \
|
|
138
|
+
a.shape == b.shape and bool(np.array_equal(a, b))
|
|
139
|
+
return bool(a == b)
|