code-loader 1.0.197.dev0__tar.gz → 1.0.197.dev2__tar.gz

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.
Files changed (37) hide show
  1. {code_loader-1.0.197.dev0 → code_loader-1.0.197.dev2}/PKG-INFO +1 -1
  2. {code_loader-1.0.197.dev0 → code_loader-1.0.197.dev2}/code_loader/contract/datasetclasses.py +67 -10
  3. {code_loader-1.0.197.dev0 → code_loader-1.0.197.dev2}/code_loader/inner_leap_binder/leapbinder.py +98 -10
  4. {code_loader-1.0.197.dev0 → code_loader-1.0.197.dev2}/code_loader/inner_leap_binder/leapbinder_decorators.py +226 -64
  5. {code_loader-1.0.197.dev0 → code_loader-1.0.197.dev2}/code_loader/leaploader.py +302 -9
  6. {code_loader-1.0.197.dev0 → code_loader-1.0.197.dev2}/code_loader/leaploaderbase.py +4 -0
  7. {code_loader-1.0.197.dev0 → code_loader-1.0.197.dev2}/code_loader/utils.py +10 -0
  8. {code_loader-1.0.197.dev0 → code_loader-1.0.197.dev2}/pyproject.toml +1 -2
  9. {code_loader-1.0.197.dev0 → code_loader-1.0.197.dev2}/LICENSE +0 -0
  10. {code_loader-1.0.197.dev0 → code_loader-1.0.197.dev2}/README.md +0 -0
  11. {code_loader-1.0.197.dev0 → code_loader-1.0.197.dev2}/code_loader/__init__.py +0 -0
  12. {code_loader-1.0.197.dev0 → code_loader-1.0.197.dev2}/code_loader/contract/__init__.py +0 -0
  13. {code_loader-1.0.197.dev0 → code_loader-1.0.197.dev2}/code_loader/contract/enums.py +0 -0
  14. {code_loader-1.0.197.dev0 → code_loader-1.0.197.dev2}/code_loader/contract/exceptions.py +0 -0
  15. {code_loader-1.0.197.dev0 → code_loader-1.0.197.dev2}/code_loader/contract/mapping.py +0 -0
  16. {code_loader-1.0.197.dev0 → code_loader-1.0.197.dev2}/code_loader/contract/responsedataclasses.py +0 -0
  17. {code_loader-1.0.197.dev0 → code_loader-1.0.197.dev2}/code_loader/contract/sim_config.py +0 -0
  18. {code_loader-1.0.197.dev0 → code_loader-1.0.197.dev2}/code_loader/contract/visualizer_classes.py +0 -0
  19. {code_loader-1.0.197.dev0 → code_loader-1.0.197.dev2}/code_loader/default_losses.py +0 -0
  20. {code_loader-1.0.197.dev0 → code_loader-1.0.197.dev2}/code_loader/default_metrics.py +0 -0
  21. {code_loader-1.0.197.dev0 → code_loader-1.0.197.dev2}/code_loader/experiment_api/__init__.py +0 -0
  22. {code_loader-1.0.197.dev0 → code_loader-1.0.197.dev2}/code_loader/experiment_api/api.py +0 -0
  23. {code_loader-1.0.197.dev0 → code_loader-1.0.197.dev2}/code_loader/experiment_api/cli_config_utils.py +0 -0
  24. {code_loader-1.0.197.dev0 → code_loader-1.0.197.dev2}/code_loader/experiment_api/client.py +0 -0
  25. {code_loader-1.0.197.dev0 → code_loader-1.0.197.dev2}/code_loader/experiment_api/epoch.py +0 -0
  26. {code_loader-1.0.197.dev0 → code_loader-1.0.197.dev2}/code_loader/experiment_api/experiment.py +0 -0
  27. {code_loader-1.0.197.dev0 → code_loader-1.0.197.dev2}/code_loader/experiment_api/experiment_context.py +0 -0
  28. {code_loader-1.0.197.dev0 → code_loader-1.0.197.dev2}/code_loader/experiment_api/types.py +0 -0
  29. {code_loader-1.0.197.dev0 → code_loader-1.0.197.dev2}/code_loader/experiment_api/utils.py +0 -0
  30. {code_loader-1.0.197.dev0 → code_loader-1.0.197.dev2}/code_loader/experiment_api/workingspace_config_utils.py +0 -0
  31. {code_loader-1.0.197.dev0 → code_loader-1.0.197.dev2}/code_loader/inner_leap_binder/__init__.py +0 -0
  32. {code_loader-1.0.197.dev0 → code_loader-1.0.197.dev2}/code_loader/mixpanel_tracker.py +0 -0
  33. {code_loader-1.0.197.dev0 → code_loader-1.0.197.dev2}/code_loader/plot_functions/__init__.py +0 -0
  34. {code_loader-1.0.197.dev0 → code_loader-1.0.197.dev2}/code_loader/plot_functions/plot_functions.py +0 -0
  35. {code_loader-1.0.197.dev0 → code_loader-1.0.197.dev2}/code_loader/plot_functions/visualize.py +0 -0
  36. {code_loader-1.0.197.dev0 → code_loader-1.0.197.dev2}/code_loader/visualizers/__init__.py +0 -0
  37. {code_loader-1.0.197.dev0 → code_loader-1.0.197.dev2}/code_loader/visualizers/default_visualizers.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: code-loader
3
- Version: 1.0.197.dev0
3
+ Version: 1.0.197.dev2
4
4
  Summary:
5
5
  Home-page: https://github.com/tensorleap/code-loader
6
6
  License: MIT
@@ -1,6 +1,6 @@
1
1
  import warnings
2
2
  from dataclasses import dataclass, field
3
- from typing import Any, Callable, List, Optional, Dict, Union, Type, Literal, Tuple
3
+ from typing import Any, Callable, List, Optional, Dict, Union, Type, Literal, Tuple, cast
4
4
  import re
5
5
  import numpy as np
6
6
  import numpy.typing as npt
@@ -15,6 +15,8 @@ custom_latent_space_attribute = "custom_latent_space"
15
15
 
16
16
  _simulation_context: Dict[str, bool] = {"active": False}
17
17
 
18
+ SampleId = Union[int, str]
19
+
18
20
 
19
21
  @dataclass
20
22
  class PreprocessResponse:
@@ -39,27 +41,54 @@ class PreprocessResponse:
39
41
  """
40
42
  length: Optional[int] = None # Deprecated. Please use sample_ids instead
41
43
  data: Any = None
42
- sample_ids: Optional[Union[List[str], List[int]]] = None
44
+ sample_ids: Optional[Union[List[SampleId], List[List[SampleId]]]] = None
43
45
  state: Optional[DataStateType] = None
44
46
  sample_id_type: Optional[Union[Type[str], Type[int]]] = None
45
47
  sample_ids_to_instance_mappings: Optional[Dict[str, List[str]]] = None # in use only for element instance
46
48
  instance_to_sample_ids_mappings: Optional[Dict[str, str]] = None # in use only for element instance
47
49
  tl_generated: bool = False
50
+ _grouped: bool = field(default=False, init=False, repr=False, compare=False)
51
+ _group_pos_cache: Optional[Dict[SampleId, Tuple[List[SampleId], int]]] = field(
52
+ default=None, init=False, repr=False, compare=False)
48
53
 
49
54
  def __post_init__(self) -> None:
50
55
  assert self.sample_ids_to_instance_mappings is None, f"Keep sample_ids_to_instance_mappings None when initializing PreprocessResponse"
51
56
  assert self.instance_to_sample_ids_mappings is None, f"Keep instance_to_sample_ids_mappings None when initializing PreprocessResponse"
52
57
 
53
58
  if self.length is not None and self.sample_ids is None:
54
- self.sample_ids = [i for i in range(self.length)]
59
+ self.sample_ids = cast(List[SampleId], [i for i in range(self.length)])
55
60
  self.sample_id_type = int
56
- elif self.length is None and self.sample_ids is not None:
57
- self.length = len(self.sample_ids)
58
- if self.sample_id_type is None:
59
- self.sample_id_type = str
60
- if self.sample_id_type == str:
61
- for sample_id in self.sample_ids:
62
- assert isinstance(sample_id, str), f"Sample id should be of type str. Got: {type(sample_id)}"
61
+ self._grouped = False
62
+ elif self.sample_ids is not None:
63
+ given_length = self.length
64
+ self._grouped = len(self.sample_ids) > 0 and isinstance(self.sample_ids[0], list)
65
+ if self._grouped:
66
+ groups = cast(List[List[SampleId]], self.sample_ids)
67
+ assert len(groups) >= 1, "Grouped PreprocessResponse must have at least one group."
68
+ for group in groups:
69
+ assert isinstance(group, list) and len(group) > 0, \
70
+ "Each group in a grouped PreprocessResponse must be a non-empty list."
71
+ self.sample_id_type = type(groups[0][0])
72
+ assert issubclass(self.sample_id_type, (str, int)), \
73
+ f"Sample id should be of type str or int. Got: {self.sample_id_type.__name__}"
74
+ for group in groups:
75
+ for sample_id in group:
76
+ assert isinstance(sample_id, self.sample_id_type), \
77
+ f"Sample id should be of type {self.sample_id_type.__name__}. Got: {type(sample_id)}"
78
+ self.length = sum(len(group) for group in groups)
79
+ else:
80
+ flat_ids = cast(List[SampleId], self.sample_ids)
81
+ self.length = len(flat_ids)
82
+ if self.sample_id_type is None:
83
+ self.sample_id_type = str
84
+ if self.sample_id_type == str:
85
+ for sample_id in flat_ids:
86
+ assert isinstance(sample_id, str), f"Sample id should be of type str. Got: {type(sample_id)}"
87
+ # dataclasses.replace() re-invokes __init__ with both fields already populated
88
+ # (length previously derived from sample_ids); only reject a genuine mismatch.
89
+ if given_length is not None:
90
+ assert given_length == self.length, \
91
+ f"Inconsistent PreprocessResponse: length={given_length} but sample_ids implies {self.length}."
63
92
  else:
64
93
  raise Exception("length is deprecated, please use sample_ids instead.")
65
94
 
@@ -79,9 +108,36 @@ class PreprocessResponse:
79
108
  return id(self)
80
109
 
81
110
  def __len__(self) -> int:
111
+ assert self.length is not None
112
+ return self.length
113
+
114
+ @property
115
+ def is_grouped(self) -> bool:
116
+ return self._grouped
117
+
118
+ @property
119
+ def num_groups(self) -> int:
120
+ # Grouped: the number of groups. Flat: the (flat) sample count, since sample_ids is the
121
+ # flat id list and there is one sample per "group".
82
122
  assert self.sample_ids is not None
83
123
  return len(self.sample_ids)
84
124
 
125
+ @property
126
+ def groups(self) -> Optional[List[List[SampleId]]]:
127
+ # The nested list of groups when grouped, otherwise None. Callers should
128
+ # branch on is_grouped before relying on this.
129
+ return cast(List[List[SampleId]], self.sample_ids) if self._grouped else None
130
+
131
+ @property
132
+ def flat_sample_ids(self) -> List[SampleId]:
133
+ # All sample ids in order: concatenation of the groups when grouped,
134
+ # otherwise the flat sample_ids list as-is.
135
+ assert self.sample_ids is not None
136
+ if self._grouped:
137
+ groups = cast(List[List[SampleId]], self.sample_ids)
138
+ return [sample_id for group in groups for sample_id in group]
139
+ return cast(List[SampleId], self.sample_ids)
140
+
85
141
  @dataclass
86
142
  class ElementInstance:
87
143
  name: str
@@ -252,6 +308,7 @@ class MetadataHandler:
252
308
  class CustomLatentSpaceHandler:
253
309
  function: SectionCallableInterface
254
310
  name: str = 'custom_latent_space'
311
+ use_ls_for_analysis: bool = False
255
312
 
256
313
 
257
314
  # How a chain's latent-space vectors are derived from its per-step forward passes.
@@ -1,6 +1,9 @@
1
1
 
2
+ import builtins
2
3
  import inspect
3
- from typing import Callable, List, Optional, Dict, Any, Type, Union, get_args
4
+ import os
5
+ from contextlib import contextmanager
6
+ from typing import Callable, List, Optional, Dict, Any, Type, Union, get_args, cast, Iterator, Set
4
7
 
5
8
  import numpy as np
6
9
  import numpy.typing as npt
@@ -16,7 +19,8 @@ from code_loader.contract.datasetclasses import SectionCallableInterface, InputH
16
19
  SimulationHandler, _simulation_context, AutoregressiveStepHandler, AutoregressiveStepCallableInterface, \
17
20
  AUTOREGRESSIVE_LATENT_SPACE_AGGREGATIONS, AUTOREGRESSIVE_IMPLICIT_ARG_NAMES, \
18
21
  AutoregressiveMetricHandler, AutoregressiveLossHandler, AutoregressiveVisualizerHandler
19
- from code_loader.contract.enums import LeapDataType, DataStateEnum, DataStateType, MetricDirection, DatasetMetadataType
22
+ from code_loader.contract.enums import LeapDataType, DataStateEnum, DataStateType, MetricDirection, DatasetMetadataType, \
23
+ TestingSectionEnum
20
24
  from code_loader.contract.mapping import NodeConnection, NodeMapping, NodeMappingType
21
25
  from code_loader.contract.responsedataclasses import DatasetTestResultPayload, LeapAnalysisConfiguration
22
26
  from code_loader.contract.visualizer_classes import map_leap_data_type_to_visualizer_class
@@ -33,6 +37,25 @@ from code_loader.visualizers.default_visualizers import DefaultVisualizer, \
33
37
  mapping_runtime_mode_env_var_mame = '__MAPPING_RUNTIME_MODE__'
34
38
 
35
39
 
40
+ @contextmanager
41
+ def _track_opened_files() -> Iterator[Set[str]]:
42
+ opened: Set[str] = set()
43
+ original_open = builtins.open
44
+
45
+ def tracking_open(file: Any, *args: Any, **kwargs: Any) -> Any:
46
+ mode = args[0] if args else kwargs.get('mode', 'r')
47
+ is_read_mode = isinstance(mode, str) and not any(c in mode for c in ('a', 'w', 'x'))
48
+ if is_read_mode and isinstance(file, (str, os.PathLike)):
49
+ opened.add(os.fspath(file))
50
+ return original_open(file, *args, **kwargs)
51
+
52
+ builtins.open = tracking_open
53
+ try:
54
+ yield opened
55
+ finally:
56
+ builtins.open = original_open
57
+
58
+
36
59
  def _stringized_annotation_type_name(annotation: Any) -> Optional[str]:
37
60
  """Bare type name if ``annotation`` is a stringized annotation (from ``from __future__
38
61
  import annotations`` or a quoted hint), else ``None``. code_loader inspects raw
@@ -514,7 +537,8 @@ class LeapBinder:
514
537
  self.setup_container.metadata.append(MetadataHandler(name, function, metadata_type))
515
538
 
516
539
  def set_custom_latent_space(self, function: SectionCallableInterface,
517
- name: Optional[str] = None) -> None:
540
+ name: Optional[str] = None,
541
+ use_ls_for_analysis: bool = False) -> None:
518
542
  """
519
543
  Register a custom latent space function.
520
544
 
@@ -531,6 +555,10 @@ class LeapBinder:
531
555
  space vec of the sample.
532
556
  name (Optional[str]): Unique name for this custom latent space. Defaults to
533
557
  the reserved single-LS name for backward compatibility.
558
+ use_ls_for_analysis (bool): When True, the engine uses this custom latent
559
+ space for the Out-Of-Distribution and Domain-Gap insights instead of the
560
+ built-in defaults. At most one registered custom latent space may set this;
561
+ registering a second one with the flag raises.
534
562
  """
535
563
  if name is None:
536
564
  name = custom_latent_space_attribute
@@ -540,7 +568,22 @@ class LeapBinder:
540
568
  f"@tensorleap_custom_latent_space must have a unique name "
541
569
  f"(pass name='...' to distinguish them)."
542
570
  )
543
- self.setup_container.custom_latent_spaces[name] = CustomLatentSpaceHandler(function, name)
571
+ if use_ls_for_analysis:
572
+ already_flagged = [
573
+ existing_name
574
+ for existing_name, handler in self.setup_container.custom_latent_spaces.items()
575
+ if handler.use_ls_for_analysis
576
+ ]
577
+ if already_flagged:
578
+ raise Exception(
579
+ f"use_ls_for_analysis=True is already set on custom latent space "
580
+ f"'{already_flagged[0]}'. Only one custom latent space may set "
581
+ f"use_ls_for_analysis=True (it selects the latent space used for the "
582
+ f"Out-Of-Distribution and Domain-Gap insights). Set it on '{name}' "
583
+ f"or '{already_flagged[0]}', not both."
584
+ )
585
+ self.setup_container.custom_latent_spaces[name] = CustomLatentSpaceHandler(
586
+ function, name, use_ls_for_analysis)
544
587
 
545
588
  def set_autoregressive_step(self, function: AutoregressiveStepCallableInterface,
546
589
  latent_space_aggregation: str = 'last_step') -> None:
@@ -565,7 +608,9 @@ class LeapBinder:
565
608
  # Builtin chain metadata, declared at parse time so it survives the reporter's
566
609
  # metadata type mapping; the placeholder values are overwritten by the engine when a
567
610
  # chain finalizes (realized length, truncated-by-safety-cap flag).
568
- def builtin_chain_metadata(idx: Any, preprocess: PreprocessResponse) -> Dict[str, Any]:
611
+ def builtin_chain_metadata(idx: Any, preprocess: PreprocessResponse) -> Any:
612
+ if isinstance(idx, list):
613
+ return [{'length': -1, 'truncated': False} for _ in idx]
569
614
  return {'length': -1, 'truncated': False}
570
615
 
571
616
  self.set_metadata(builtin_chain_metadata, 'builtin_chain')
@@ -729,6 +774,15 @@ class LeapBinder:
729
774
  if state_enum in preprocess_result_dict:
730
775
  preprocess_result_dict_in_correct_order[state_enum] = preprocess_result_dict[state_enum]
731
776
 
777
+ # All-or-none: every state must agree on grouped (nested sample_ids) vs flat.
778
+ if len({r.is_grouped for r in preprocess_result_dict_in_correct_order.values()}) > 1:
779
+ shapes = ', '.join(
780
+ f"{state.name}={'grouped' if r.is_grouped else 'flat'}"
781
+ for state, r in preprocess_result_dict_in_correct_order.items())
782
+ raise Exception(
783
+ "All preprocess states must be either all grouped (nested sample_ids) or all flat, "
784
+ f"but got a mix: {shapes}.")
785
+
732
786
  return preprocess_result_dict_in_correct_order
733
787
 
734
788
  def get_preprocess_unlabeled_result(self) -> Optional[PreprocessResponse]:
@@ -749,7 +803,34 @@ class LeapBinder:
749
803
  preprocess_response: PreprocessResponse, test_result: List[DatasetTestResultPayload],
750
804
  dataset_base_handler: Union[DatasetBaseHandler, MetadataHandler], state: DataStateEnum) -> List[DatasetTestResultPayload]:
751
805
  assert preprocess_response.sample_ids is not None
752
- raw_result = dataset_base_handler.function(preprocess_response.sample_ids[0], preprocess_response)
806
+ opened_files: Optional[Set[str]] = None
807
+ if preprocess_response.is_grouped:
808
+ # Grouped: probe with the first group, then reduce to a single sample's result so the
809
+ # recorded shape/type is per-sample (matching the flat case), not the (B, *) batch.
810
+ # (casts: sample_ids[0] is a group list here, and the grouped result is a per-sample list.)
811
+ group = preprocess_response.sample_ids[0]
812
+ with _track_opened_files() as opened_files:
813
+ raw_result = cast(Any, dataset_base_handler.function(cast(Any, group), preprocess_response))[0]
814
+ else:
815
+ raw_result = dataset_base_handler.function(
816
+ cast(Union[int, str], preprocess_response.sample_ids[0]), preprocess_response)
817
+
818
+ def _warn_if_group_spans_multiple_files(payloads: List[DatasetTestResultPayload]) -> None:
819
+ if opened_files is None or len(opened_files) <= 1:
820
+ return
821
+ shown = sorted(opened_files)[:5]
822
+ suffix = f" (+{len(opened_files) - 5} more)" if len(opened_files) > 5 else ""
823
+ warning = (
824
+ f"Encoding declared group 0 (size {len(cast(List[Any], group))}) for '{dataset_base_handler.name}' opened "
825
+ f"{len(opened_files)} distinct files: {shown}{suffix}. If this group is meant to be one "
826
+ f"physical source (e.g. one Parquet file), it may be grouped incorrectly - check your "
827
+ f"preprocess grouping logic. (Only files opened via Python's open() are tracked, so this "
828
+ f"can miss other I/O paths or be a false positive if grouping isn't meant to reflect file "
829
+ f"locality.)")
830
+ for payload in payloads:
831
+ existing = payload.display.get(TestingSectionEnum.Errors.name, '')
832
+ payload.display[TestingSectionEnum.Errors.name] = (existing + '\n' if existing else '') + warning
833
+
753
834
  handler_type = 'metadata' if isinstance(dataset_base_handler, MetadataHandler) else None
754
835
  if isinstance(dataset_base_handler, MetadataHandler):
755
836
  if isinstance(raw_result, dict):
@@ -789,6 +870,7 @@ class LeapBinder:
789
870
  else:
790
871
  if raw_result is None:
791
872
  if state != DataStateEnum.training:
873
+ _warn_if_group_spans_multiple_files(test_result)
792
874
  return test_result
793
875
 
794
876
  if dataset_base_handler.metadata_type is None:
@@ -810,6 +892,7 @@ class LeapBinder:
810
892
  # setting shape in setup for all encoders
811
893
  if isinstance(dataset_base_handler, (InputHandler, GroundTruthHandler)):
812
894
  dataset_base_handler.shape = result_shape
895
+ _warn_if_group_spans_multiple_files(test_result)
813
896
  return test_result
814
897
 
815
898
  def check_handlers(self, preprocess_result: Dict[DataStateEnum, PreprocessResponse]) -> None:
@@ -845,10 +928,15 @@ class LeapBinder:
845
928
  preprocess_response.tl_generated = True
846
929
  if not preprocess_response.length or preprocess_response.length < 1:
847
930
  raise Exception("Simulation '{}' returned PreprocessResponse with length < 1".format(sim.name))
848
- preprocess_response.sample_ids = [0]
931
+ if preprocess_response.is_grouped:
932
+ raise Exception(
933
+ "Simulation '{}' returned a grouped PreprocessResponse; simulations must "
934
+ "return a flat (non-grouped) PreprocessResponse.".format(sim.name))
935
+ preprocess_response.sample_ids = cast(List[Union[int, str]], [0])
936
+ sim_sample_ids = cast(List[Union[int, str]], preprocess_response.sample_ids)
849
937
  for handler in self.setup_container.inputs:
850
- out1 = handler.function(preprocess_response.sample_ids[0], preprocess_response)
851
- out2 = handler.function(preprocess_response.sample_ids[0], preprocess_response)
938
+ out1 = handler.function(sim_sample_ids[0], preprocess_response)
939
+ out2 = handler.function(sim_sample_ids[0], preprocess_response)
852
940
  if not np.allclose(out1, out2):
853
941
  raise Exception(
854
942
  "Simulation '{}': encoder '{}' is non-deterministic — consecutive calls with seed=0 returned different outputs".format(
@@ -906,7 +994,7 @@ class LeapBinder:
906
994
  "Element instances are not supported together with tensorleap_autoregressive_step: "
907
995
  "instance generation masks the sample's encoded inputs, which do not exist in an "
908
996
  "autoregressive integration. Remove the instance encoders or the autoregressive hook.")
909
- if self.setup_container.custom_latent_space is not None:
997
+ if self.setup_container.custom_latent_spaces:
910
998
  raise Exception(
911
999
  "tensorleap_custom_latent_space is not supported together with "
912
1000
  "tensorleap_autoregressive_step: the custom latent space function only sees "