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