code-loader 1.0.203__tar.gz → 1.0.204.dev1__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.203 → code_loader-1.0.204.dev1}/PKG-INFO +3 -4
  2. {code_loader-1.0.203 → code_loader-1.0.204.dev1}/code_loader/contract/datasetclasses.py +3 -16
  3. {code_loader-1.0.203 → code_loader-1.0.204.dev1}/code_loader/inner_leap_binder/leapbinder.py +10 -64
  4. {code_loader-1.0.203 → code_loader-1.0.204.dev1}/code_loader/inner_leap_binder/leapbinder_decorators.py +89 -102
  5. {code_loader-1.0.203 → code_loader-1.0.204.dev1}/code_loader/leaploader.py +68 -36
  6. {code_loader-1.0.203 → code_loader-1.0.204.dev1}/code_loader/leaploaderbase.py +0 -10
  7. {code_loader-1.0.203 → code_loader-1.0.204.dev1}/pyproject.toml +1 -1
  8. {code_loader-1.0.203 → code_loader-1.0.204.dev1}/LICENSE +0 -0
  9. {code_loader-1.0.203 → code_loader-1.0.204.dev1}/README.md +0 -0
  10. {code_loader-1.0.203 → code_loader-1.0.204.dev1}/code_loader/__init__.py +0 -0
  11. {code_loader-1.0.203 → code_loader-1.0.204.dev1}/code_loader/contract/__init__.py +0 -0
  12. {code_loader-1.0.203 → code_loader-1.0.204.dev1}/code_loader/contract/enums.py +0 -0
  13. {code_loader-1.0.203 → code_loader-1.0.204.dev1}/code_loader/contract/exceptions.py +0 -0
  14. {code_loader-1.0.203 → code_loader-1.0.204.dev1}/code_loader/contract/mapping.py +0 -0
  15. {code_loader-1.0.203 → code_loader-1.0.204.dev1}/code_loader/contract/responsedataclasses.py +0 -0
  16. {code_loader-1.0.203 → code_loader-1.0.204.dev1}/code_loader/contract/sim_config.py +0 -0
  17. {code_loader-1.0.203 → code_loader-1.0.204.dev1}/code_loader/contract/visualizer_classes.py +0 -0
  18. {code_loader-1.0.203 → code_loader-1.0.204.dev1}/code_loader/default_losses.py +0 -0
  19. {code_loader-1.0.203 → code_loader-1.0.204.dev1}/code_loader/default_metrics.py +0 -0
  20. {code_loader-1.0.203 → code_loader-1.0.204.dev1}/code_loader/experiment_api/__init__.py +0 -0
  21. {code_loader-1.0.203 → code_loader-1.0.204.dev1}/code_loader/experiment_api/api.py +0 -0
  22. {code_loader-1.0.203 → code_loader-1.0.204.dev1}/code_loader/experiment_api/cli_config_utils.py +0 -0
  23. {code_loader-1.0.203 → code_loader-1.0.204.dev1}/code_loader/experiment_api/client.py +0 -0
  24. {code_loader-1.0.203 → code_loader-1.0.204.dev1}/code_loader/experiment_api/epoch.py +0 -0
  25. {code_loader-1.0.203 → code_loader-1.0.204.dev1}/code_loader/experiment_api/experiment.py +0 -0
  26. {code_loader-1.0.203 → code_loader-1.0.204.dev1}/code_loader/experiment_api/experiment_context.py +0 -0
  27. {code_loader-1.0.203 → code_loader-1.0.204.dev1}/code_loader/experiment_api/types.py +0 -0
  28. {code_loader-1.0.203 → code_loader-1.0.204.dev1}/code_loader/experiment_api/utils.py +0 -0
  29. {code_loader-1.0.203 → code_loader-1.0.204.dev1}/code_loader/experiment_api/workingspace_config_utils.py +0 -0
  30. {code_loader-1.0.203 → code_loader-1.0.204.dev1}/code_loader/inner_leap_binder/__init__.py +0 -0
  31. {code_loader-1.0.203 → code_loader-1.0.204.dev1}/code_loader/mixpanel_tracker.py +0 -0
  32. {code_loader-1.0.203 → code_loader-1.0.204.dev1}/code_loader/plot_functions/__init__.py +0 -0
  33. {code_loader-1.0.203 → code_loader-1.0.204.dev1}/code_loader/plot_functions/plot_functions.py +0 -0
  34. {code_loader-1.0.203 → code_loader-1.0.204.dev1}/code_loader/plot_functions/visualize.py +0 -0
  35. {code_loader-1.0.203 → code_loader-1.0.204.dev1}/code_loader/utils.py +0 -0
  36. {code_loader-1.0.203 → code_loader-1.0.204.dev1}/code_loader/visualizers/__init__.py +0 -0
  37. {code_loader-1.0.203 → code_loader-1.0.204.dev1}/code_loader/visualizers/default_visualizers.py +0 -0
@@ -1,9 +1,9 @@
1
- Metadata-Version: 2.4
1
+ Metadata-Version: 2.1
2
2
  Name: code-loader
3
- Version: 1.0.203
3
+ Version: 1.0.204.dev1
4
4
  Summary:
5
+ Home-page: https://github.com/tensorleap/code-loader
5
6
  License: MIT
6
- License-File: LICENSE
7
7
  Author: dorhar
8
8
  Author-email: doron.harnoy@tensorleap.ai
9
9
  Requires-Python: >=3.8,<3.13
@@ -20,7 +20,6 @@ Requires-Dist: numpy (>=2.3.2,<3.0.0) ; python_version >= "3.11" and python_vers
20
20
  Requires-Dist: psutil (>=5.9.5,<6.0.0)
21
21
  Requires-Dist: pyyaml (>=6.0.2,<7.0.0)
22
22
  Requires-Dist: requests (>=2.32.3,<3.0.0)
23
- Project-URL: Homepage, https://github.com/tensorleap/code-loader
24
23
  Project-URL: Repository, https://github.com/tensorleap/code-loader
25
24
  Description-Content-Type: text/markdown
26
25
 
@@ -147,6 +147,8 @@ class ElementInstance:
147
147
  SectionCallableInterface = Callable[[Union[int, str], PreprocessResponse], npt.NDArray[np.float32]]
148
148
  InstanceCallableInterface = Callable[[Union[int, str], PreprocessResponse, int], Optional[ElementInstance]]
149
149
  InstanceLengthCallableInterface = Callable[[Union[int, str], PreprocessResponse], int]
150
+ InstanceSectionCallableInterface = Callable[
151
+ [Union[int, str], PreprocessResponse, Optional[int]], npt.NDArray[np.float32]]
150
152
 
151
153
  # (sample_id, prev_inputs, prev_outputs, state, preprocess) -> (next model inputs | None, state).
152
154
  # First call per chain receives prev_inputs=None, prev_outputs=None, state=None and returns the
@@ -310,6 +312,7 @@ class CustomLatentSpaceHandler:
310
312
  function: SectionCallableInterface
311
313
  name: str = 'custom_latent_space'
312
314
  use_ls_for_analysis: bool = False
315
+ instance_aware: bool = False
313
316
 
314
317
 
315
318
  # How a chain's latent-space vectors are derived from its per-step forward passes.
@@ -318,11 +321,6 @@ class CustomLatentSpaceHandler:
318
321
  # dominates the model inputs). 'mean': every latent space is the elementwise mean over all steps.
319
322
  AUTOREGRESSIVE_LATENT_SPACE_AGGREGATIONS = ('last_step', 'mean')
320
323
 
321
- # Default axis of a KV-cache tensor that indexes token positions: (batch, heads, position,
322
- # head_dim), the layout HF-style exports produce. Overridable per integration via
323
- # tensorleap_autoregressive_step(cache_seq_axis=...).
324
- DEFAULT_CACHE_SEQ_AXIS = 2
325
-
326
324
  # Reserved argument names of autoregressive metrics/losses/visualizers, fed implicitly by the
327
325
  # platform from the finished chain (per-chain dicts, no batch axis). Any other argument is wired
328
326
  # to a ground-truth encoder through the integration test.
@@ -337,17 +335,6 @@ class AutoregressiveStepHandler:
337
335
  # time. Fills the role InputHandler.shape plays for input encoders.
338
336
  input_shapes: Optional[Dict[str, List[int]]] = None
339
337
  latent_space_aggregation: str = 'last_step'
340
- # {model_input_name: model_output_name} for a KV-cache (or any threaded state): inputs whose
341
- # value is the named output from the previous step. The platform threads these itself — they
342
- # are never passed to the hook and never reported — so the hook must NOT return them. Empty
343
- # when the model has no cache, in which case the chain re-feeds its full prompt each step.
344
- cache_tensors: Optional[Dict[str, str]] = None
345
- # Which axis of a cache tensor indexes token positions, i.e. where its capacity is read from.
346
- # 2 matches the (batch, heads, position, head_dim) layout every HF-style export uses. Declared
347
- # rather than inferred because the platform cannot tell positions from heads or head_dim — in
348
- # a (1, 3, 64, 64) tensor two of the dims are 64 — and reading the wrong axis silently caps
349
- # chains at the number of heads instead of the cache capacity.
350
- cache_seq_axis: int = DEFAULT_CACHE_SEQ_AXIS
351
338
 
352
339
 
353
340
  # Per-chain, unbatched callables: called once per finished chain with the final step's tensors —
@@ -15,10 +15,10 @@ from code_loader.contract.datasetclasses import SectionCallableInterface, InputH
15
15
  CustomCallableInterfaceMultiArgs, ConfusionMatrixCallableInterfaceMultiArgs, LeapData, \
16
16
  CustomMultipleReturnCallableInterfaceMultiArgs, DatasetBaseHandler, custom_latent_space_attribute, \
17
17
  RawInputsForHeatmap, VisualizerHandlerData, MetricHandlerData, CustomLossHandlerData, SamplePreprocessResponse, \
18
- ElementInstanceMasksHandler, InstanceCallableInterface, CustomLatentSpaceHandler, InstanceMetricHandler, \
18
+ ElementInstanceMasksHandler, InstanceCallableInterface, InstanceSectionCallableInterface, \
19
+ CustomLatentSpaceHandler, InstanceMetricHandler, \
19
20
  SimulationHandler, _simulation_context, AutoregressiveStepHandler, AutoregressiveStepCallableInterface, \
20
21
  AUTOREGRESSIVE_LATENT_SPACE_AGGREGATIONS, AUTOREGRESSIVE_IMPLICIT_ARG_NAMES, \
21
- DEFAULT_CACHE_SEQ_AXIS, \
22
22
  AutoregressiveMetricHandler, AutoregressiveLossHandler, AutoregressiveVisualizerHandler
23
23
  from code_loader.contract.enums import LeapDataType, DataStateEnum, DataStateType, MetricDirection, DatasetMetadataType, \
24
24
  TestingSectionEnum
@@ -542,9 +542,10 @@ class LeapBinder:
542
542
  f"@tensorleap_metadata('my_metadata', {{'key': DatasetMetadataType.int}}).")
543
543
  self.setup_container.metadata.append(MetadataHandler(name, function, metadata_type))
544
544
 
545
- def set_custom_latent_space(self, function: SectionCallableInterface,
545
+ def set_custom_latent_space(self, function: Union[SectionCallableInterface, InstanceSectionCallableInterface],
546
546
  name: Optional[str] = None,
547
- use_ls_for_analysis: bool = False) -> None:
547
+ use_ls_for_analysis: bool = False,
548
+ instance_aware: bool = False) -> None:
548
549
  """
549
550
  Register a custom latent space function.
550
551
 
@@ -565,6 +566,8 @@ class LeapBinder:
565
566
  space for the Out-Of-Distribution and Domain-Gap insights instead of the
566
567
  built-in defaults. At most one registered custom latent space may set this;
567
568
  registering a second one with the flag raises.
569
+ instance_aware (bool): When True, `function` takes a third `instance_id` argument
570
+ and is called once per element-instance row instead of once per sample.
568
571
  """
569
572
  if name is None:
570
573
  name = custom_latent_space_attribute
@@ -589,59 +592,10 @@ class LeapBinder:
589
592
  f"or '{already_flagged[0]}', not both."
590
593
  )
591
594
  self.setup_container.custom_latent_spaces[name] = CustomLatentSpaceHandler(
592
- function, name, use_ls_for_analysis)
593
-
594
- @staticmethod
595
- def _validated_cache_tensors(cache_tensors: Optional[Dict[str, str]]
596
- ) -> Optional[Dict[str, str]]:
597
- """Structural checks only — shape/dtype/existence are validated by the platform, which
598
- is the side that has the model graph. This catches the typos that are cheap to catch
599
- here, at integration-test time, instead of at push."""
600
- if not cache_tensors:
601
- return None
602
- if not isinstance(cache_tensors, dict):
603
- raise Exception(f'tensorleap_autoregressive_step: cache_tensors must be a dict of '
604
- f'{{model_input_name: model_output_name}}, got '
605
- f'{type(cache_tensors).__name__}.')
606
- seen_outputs: Dict[str, str] = {}
607
- for input_name, output_name in cache_tensors.items():
608
- for role, name in (('key', input_name), ('value', output_name)):
609
- if not isinstance(name, str) or not name:
610
- raise Exception(
611
- f'tensorleap_autoregressive_step: cache_tensors {role}s must be '
612
- f'non-empty model tensor names, got {name!r}.')
613
- if input_name == output_name:
614
- raise Exception(
615
- f'tensorleap_autoregressive_step: cache_tensors maps "{input_name}" to '
616
- f'itself. A model input and output cannot share a name — the mapping is '
617
- f'{{input_name: output_name}}.')
618
- if output_name in seen_outputs:
619
- raise Exception(
620
- f'tensorleap_autoregressive_step: model output "{output_name}" is declared '
621
- f'as the cache source for both "{seen_outputs[output_name]}" and '
622
- f'"{input_name}". Each cache output feeds exactly one input.')
623
- seen_outputs[output_name] = input_name
624
- return dict(cache_tensors)
625
-
626
- @staticmethod
627
- def _validated_cache_seq_axis(cache_seq_axis: int,
628
- cache_tensors: Optional[Dict[str, str]]) -> int:
629
- """The axis must be a non-negative int, and declaring one without a cache is a mistake
630
- worth naming — it reads as if it had an effect."""
631
- if not isinstance(cache_seq_axis, int) or isinstance(cache_seq_axis, bool) \
632
- or cache_seq_axis < 0:
633
- raise Exception(f'tensorleap_autoregressive_step: cache_seq_axis must be a '
634
- f'non-negative int axis index, got {cache_seq_axis!r}.')
635
- if not cache_tensors and cache_seq_axis != DEFAULT_CACHE_SEQ_AXIS:
636
- raise Exception('tensorleap_autoregressive_step: cache_seq_axis was declared without '
637
- 'cache_tensors. The axis describes the declared cache tensors, so it '
638
- 'has no meaning on its own.')
639
- return cache_seq_axis
595
+ function=function, name=name, use_ls_for_analysis=use_ls_for_analysis, instance_aware=instance_aware)
640
596
 
641
597
  def set_autoregressive_step(self, function: AutoregressiveStepCallableInterface,
642
- latent_space_aggregation: str = 'last_step',
643
- cache_tensors: Optional[Dict[str, str]] = None,
644
- cache_seq_axis: int = DEFAULT_CACHE_SEQ_AXIS) -> None:
598
+ latent_space_aggregation: str = 'last_step') -> None:
645
599
  """
646
600
  Set the autoregressive step hook — the feedback function that drives a chain:
647
601
  it supplies the model's initial inputs on its first call (prev_inputs=None, prev_outputs=None)
@@ -649,11 +603,6 @@ class LeapBinder:
649
603
  An autoregressive integration has no input encoders; the hook is the sole input source.
650
604
  latent_space_aggregation declares how the chain's latent-space vectors are derived from
651
605
  its steps (see AUTOREGRESSIVE_LATENT_SPACE_AGGREGATIONS).
652
- cache_tensors declares a KV-cache as {model_input_name: model_output_name}; the platform
653
- threads those tensors itself and the hook neither receives nor returns them.
654
- cache_seq_axis declares which axis of those tensors indexes token positions, i.e. where
655
- the platform reads the cache capacity from (default 2, for (batch, heads, position,
656
- head_dim)).
657
606
  """
658
607
  if self.setup_container.autoregressive_step is not None:
659
608
  raise Exception('tensorleap_autoregressive_step is already defined. '
@@ -662,11 +611,8 @@ class LeapBinder:
662
611
  raise Exception(f'tensorleap_autoregressive_step: unknown latent_space_aggregation '
663
612
  f'{latent_space_aggregation!r}. Supported values: '
664
613
  f'{", ".join(AUTOREGRESSIVE_LATENT_SPACE_AGGREGATIONS)}.')
665
- cache_tensors = self._validated_cache_tensors(cache_tensors)
666
- cache_seq_axis = self._validated_cache_seq_axis(cache_seq_axis, cache_tensors)
667
614
  self.setup_container.autoregressive_step = AutoregressiveStepHandler(
668
- function, latent_space_aggregation=latent_space_aggregation,
669
- cache_tensors=cache_tensors, cache_seq_axis=cache_seq_axis)
615
+ function, latent_space_aggregation=latent_space_aggregation)
670
616
 
671
617
  # Builtin chain metadata, declared at parse time so it survives the reporter's
672
618
  # metadata type mapping; the placeholder values are overwritten by the engine when a
@@ -24,7 +24,7 @@ from code_loader.contract.datasetclasses import CustomCallableInterfaceMultiArgs
24
24
  CustomMultipleReturnCallableInterfaceMultiArgs, ConfusionMatrixCallableInterfaceMultiArgs, CustomCallableInterface, \
25
25
  VisualizerCallableInterface, MetadataSectionCallableInterface, PreprocessResponse, SectionCallableInterface, \
26
26
  ConfusionMatrixElement, SamplePreprocessResponse, PredictionTypeHandler, InstanceCallableInterface, ElementInstance, \
27
- InstanceLengthCallableInterface, AutoregressiveStepCallableInterface, DEFAULT_CACHE_SEQ_AXIS
27
+ InstanceLengthCallableInterface, InstanceSectionCallableInterface, AutoregressiveStepCallableInterface
28
28
  from code_loader.contract.enums import MetricDirection, LeapDataType, DatasetMetadataType, DataStateType
29
29
  from code_loader import leap_binder, LeapLoader
30
30
  from code_loader.contract.mapping import NodeMapping, NodeMappingType, NodeConnection
@@ -560,22 +560,12 @@ def tensorleap_load_model(prediction_types: Optional[List[PredictionTypeHandler]
560
560
  return result
561
561
 
562
562
  def validate_declared_prediction_types(self, ret):
563
- n_outputs = len(ret) if isinstance(ret, list) else 1
564
- # A declared KV-cache is plumbing: those outputs are fed straight back into
565
- # the model and never reported, so they carry no prediction type. Count only
566
- # the rest, or every cached model would demand 60 meaningless declarations.
567
- step = leap_binder.setup_container.autoregressive_step
568
- n_cache = len((step.cache_tensors if step else None) or {})
569
- if len(self.prediction_types) != 0 and \
570
- n_outputs - n_cache != len(self.prediction_types):
563
+ if not (len(self.prediction_types) == len(ret) if isinstance(ret, list) else 1) and len(
564
+ self.prediction_types) != 0:
571
565
  if not _call_from_tl_platform:
572
566
  update_env_params_func("tensorleap_load_model", "x")
573
- cache_note = (f' ({n_cache} of them are the declared KV-cache, which '
574
- f'needs none)' if n_cache else '')
575
567
  raise Exception(
576
- f"tensorleap_load_model validation failed: number of declared "
577
- f"prediction types({len(prediction_types)}) != number of model "
578
- f"outputs({n_outputs}){cache_note}")
568
+ f"tensorleap_load_model validation failed: number of declared prediction types({len(prediction_types)}) != number of model outputs({len(ret) if isinstance(ret, list) else 1})")
579
569
 
580
570
  def _convert_onnx_inputs_to_correct_type(
581
571
  self, float_arrays_inputs: Dict[str, np.ndarray]
@@ -733,22 +723,12 @@ def tensorleap_load_model(prediction_types: Optional[List[PredictionTypeHandler]
733
723
  assert isinstance(input_dict, dict), \
734
724
  f'Expected input_dict to be a dict, got {type(input_dict)} instead.'
735
725
  seen: set = set()
736
- # A declared KV-cache is threaded by the platform, not wired from an input
737
- # encoder, so those slots have no mapping node to assign — the loop feeds
738
- # them real arrays even in mapping mode. Skip them, and count only the wired
739
- # inputs so the remaining slot indices stay contiguous.
740
- step_handler = leap_binder.setup_container.autoregressive_step
741
- cache_tensors = (step_handler.cache_tensors if step_handler else None) or {}
742
- slot = 0
743
- for input_key, elem in input_dict.items():
744
- if input_key in cache_tensors:
745
- continue
726
+ for i, (input_key, elem) in enumerate(input_dict.items()):
746
727
  self._reject_reused_input_source(elem, seen)
747
728
  if isinstance(input_key, NodeMappingType):
748
729
  elem.node_mapping.type = input_key
749
730
  else:
750
- elem.node_mapping.type = _safe_get_item(slot)
751
- slot += 1
731
+ elem.node_mapping.type = _safe_get_item(i)
752
732
 
753
733
  return ModelOutputPlaceholder()
754
734
 
@@ -1807,6 +1787,73 @@ def tensorleap_custom_latent_space(name: Optional[str] = None, use_ls_for_analys
1807
1787
  return decorating_function
1808
1788
 
1809
1789
 
1790
+ def tensorleap_instance_custom_latent_space(name: Optional[str] = None, use_ls_for_analysis: bool = False):
1791
+ assert isinstance(use_ls_for_analysis, bool), \
1792
+ ("tensorleap_instance_custom_latent_space validation failed: use_ls_for_analysis must be a bool. "
1793
+ f"Got {type(use_ls_for_analysis)}.")
1794
+
1795
+ def decorating_function(user_function: InstanceSectionCallableInterface):
1796
+ ls_name = name if name is not None else user_function.__name__
1797
+
1798
+ argspec = inspect.getfullargspec(user_function)
1799
+ assert len(argspec.args) == 3, \
1800
+ (f'tensorleap_instance_custom_latent_space validation failed: '
1801
+ f'The function should take exactly 3 positional parameters '
1802
+ f'(sample_id, preprocess_response, instance_id). Got {len(argspec.args)}.')
1803
+
1804
+ def _validate_input_args(sample_id: Union[int, str], preprocess_response: PreprocessResponse,
1805
+ instance_id: int):
1806
+ _validate_id_or_group(sample_id, preprocess_response, 'tensorleap_instance_custom_latent_space')
1807
+ assert isinstance(instance_id, (int, np.integer)), \
1808
+ (f'tensorleap_instance_custom_latent_space validation failed: '
1809
+ f'Argument instance_id should be int. Got {type(instance_id)}.')
1810
+
1811
+ def _validate_result(result):
1812
+ assert isinstance(result, np.ndarray), \
1813
+ (f'tensorleap_instance_custom_latent_space validation failed: '
1814
+ f'The return type should be a numpy array. Got {type(result)}.')
1815
+ if result.ndim > 1:
1816
+ flat_dim = int(np.prod(result.shape))
1817
+ store_general_warning(
1818
+ key=("tensorleap_instance_custom_latent_space_flatten", ls_name, tuple(result.shape)),
1819
+ message=(
1820
+ f"tensorleap_instance_custom_latent_space '{ls_name}' returned per-sample shape "
1821
+ f"{tuple(result.shape)} (ndim={result.ndim}). Tensorleap assumes per-sample shape "
1822
+ f"(d, ...) and will flatten to ({flat_dim},) before downstream visualization and "
1823
+ f"clustering. If you want a different aggregation, do it inside your function."
1824
+ ),
1825
+ )
1826
+
1827
+ def inner_without_validate(sample_id, preprocess_response, instance_id):
1828
+ global _called_from_inside_tl_decorator
1829
+ _called_from_inside_tl_decorator += 1
1830
+
1831
+ try:
1832
+ result = user_function(sample_id, preprocess_response, instance_id)
1833
+ finally:
1834
+ _called_from_inside_tl_decorator -= 1
1835
+
1836
+ return result
1837
+
1838
+ leap_binder.set_custom_latent_space(inner_without_validate, ls_name,
1839
+ use_ls_for_analysis=use_ls_for_analysis, instance_aware=True)
1840
+
1841
+ def inner(sample_id, preprocess_response, instance_id):
1842
+ if os.environ.get(mapping_runtime_mode_env_var_mame):
1843
+ return None
1844
+
1845
+ _validate_input_args(sample_id, preprocess_response, instance_id)
1846
+
1847
+ result = inner_without_validate(sample_id, preprocess_response, instance_id)
1848
+
1849
+ _validate_result(result)
1850
+ return result
1851
+
1852
+ return inner
1853
+
1854
+ return decorating_function
1855
+
1856
+
1810
1857
  _MODEL_LOOP_MAX_STEPS = 1000
1811
1858
 
1812
1859
  _active_model_loop = None
@@ -1904,16 +1951,11 @@ class _MappingStatePlaceholder:
1904
1951
 
1905
1952
 
1906
1953
  class _ModelLoopContext:
1907
- def __init__(self, sample_id, preprocess_response, prediction_names, is_mapping,
1908
- cache_tensors=None):
1954
+ def __init__(self, sample_id, preprocess_response, prediction_names, is_mapping):
1909
1955
  self.sample_id = sample_id
1910
1956
  self.preprocess_response = preprocess_response
1911
1957
  self.prediction_names = prediction_names
1912
1958
  self.is_mapping = is_mapping
1913
- # Declared KV-cache, {model_input_name: model_output_name}. These tensors are supplied
1914
- # by the loop (and by the platform at runtime), NOT by the hook, so both the fed-inputs
1915
- # check and the output-count check have to account for them.
1916
- self.cache_tensors = dict(cache_tensors or {})
1917
1959
  self.phase = 'awaiting_first_hook'
1918
1960
  self.steps = 0
1919
1961
  self.fed_inputs = None
@@ -1922,9 +1964,6 @@ class _ModelLoopContext:
1922
1964
  self.last_outputs_fingerprint = None
1923
1965
  self.last_state = None
1924
1966
  self.last_state_fingerprint = None
1925
- # Declared-cache outputs of the last model call, in graph order (predictions first, so
1926
- # this is the tail). Read by the loop through the model proxy.
1927
- self.last_cache_outputs = []
1928
1967
 
1929
1968
  def _fail(self, message):
1930
1969
  raise LeapValidationError(f'tensorleap_model_loop validation failed: {message}')
@@ -2009,19 +2048,12 @@ class _ModelLoopContext:
2009
2048
  'next_inputs the loop must exit without further hook or model calls.')
2010
2049
  if self.is_mapping:
2011
2050
  return
2012
- # A declared KV-cache is threaded by the platform, so the hook neither receives nor
2013
- # returns it and the loop is expected to supply it. Exclude those keys from the
2014
- # identity check rather than demanding the hook produce tensors it cannot see.
2015
- if isinstance(fed, dict) and self.cache_tensors:
2016
- fed = {key: value for key, value in fed.items() if key not in self.cache_tensors}
2017
2051
  fed_ids = {id(leaf) for leaf in _ndarray_leaves(fed)}
2018
2052
  hook_ids = {id(value) for value in self.fed_inputs.values()}
2019
2053
  if fed_ids != hook_ids:
2020
- extra = (' (the declared KV-cache inputs are exempt — those are threaded for you)'
2021
- if self.cache_tensors else '')
2022
2054
  self._fail('the model must be fed exactly the tensors the last hook call returned — '
2023
2055
  'any computation between the hook and the model is invisible to the '
2024
- f'platform. Move it into the hook.{extra}')
2056
+ 'platform. Move it into the hook.')
2025
2057
  if _nest_fingerprint(self.fed_inputs) != self.fed_inputs_fingerprint:
2026
2058
  self._fail('the model inputs were mutated in place after the hook returned them — '
2027
2059
  'the platform feeds the model the tensors exactly as the hook returned '
@@ -2033,24 +2065,12 @@ class _ModelLoopContext:
2033
2065
  self.last_outputs = raw_outputs
2034
2066
  return raw_outputs
2035
2067
  outputs_list = raw_outputs if isinstance(raw_outputs, list) else [raw_outputs]
2036
- # Cache outputs are plumbing, not predictions: they are fed straight back into the model
2037
- # and never reported, so they need no prediction type. Only the remainder is keyed.
2038
- n_cache_outputs = len(self.cache_tensors)
2039
- if len(outputs_list) - n_cache_outputs != len(self.prediction_names):
2040
- cache_note = (f' ({n_cache_outputs} of them are the declared KV-cache, which needs '
2041
- f'no prediction types)' if n_cache_outputs else '')
2068
+ if len(outputs_list) != len(self.prediction_names):
2042
2069
  self._fail(f'the model returned {len(outputs_list)} outputs but '
2043
2070
  f'{len(self.prediction_names)} prediction types are declared on '
2044
- f'tensorleap_load_model{cache_note} — declare one prediction type per '
2045
- f'non-cache model output.')
2046
- # Predictions come first in graph order, as the export declares them; the cache follows.
2047
- named_outputs = {name: np.asarray(output) for name, output
2048
- in zip(self.prediction_names, outputs_list)}
2049
- # The hook must receive predictions only, exactly as on the platform, and must receive
2050
- # this very dict (on_hook_call checks identity). The loop still needs the cache, because
2051
- # locally it stands in for the platform and threads it by hand — so that goes out on the
2052
- # proxy rather than being mixed into the hook's view.
2053
- self.last_cache_outputs = list(outputs_list[len(self.prediction_names):])
2071
+ f'tensorleap_load_model — declare one prediction type per model output.')
2072
+ named_outputs = {name: np.asarray(output)
2073
+ for name, output in zip(self.prediction_names, outputs_list)}
2054
2074
  self.last_outputs = named_outputs
2055
2075
  self.last_outputs_fingerprint = _nest_fingerprint(named_outputs)
2056
2076
  return named_outputs
@@ -2072,20 +2092,8 @@ class _ModelLoopModelProxy:
2072
2092
  def get_inputs(self):
2073
2093
  return self._model.get_inputs()
2074
2094
 
2075
- @property
2076
- def cache_outputs(self):
2077
- """Declared-cache outputs of the last model call, in graph order.
2078
2095
 
2079
- Only the local loop needs these: it stands in for the platform, which threads the cache
2080
- for you at runtime. They are kept off the hook's `prev_outputs` deliberately — the hook
2081
- must not see the cache in either place, or the local run would diverge from the real one.
2082
- """
2083
- return self._context.last_cache_outputs
2084
-
2085
-
2086
- def tensorleap_autoregressive_step(latent_space_aggregation: str = 'last_step',
2087
- cache_tensors: Optional[Dict[str, str]] = None,
2088
- cache_seq_axis: int = DEFAULT_CACHE_SEQ_AXIS):
2096
+ def tensorleap_autoregressive_step(latent_space_aggregation: str = 'last_step'):
2089
2097
  """The feedback hook that drives an autoregressive chain.
2090
2098
 
2091
2099
  Signature of the decorated function:
@@ -2109,29 +2117,6 @@ def tensorleap_autoregressive_step(latent_space_aggregation: str = 'last_step',
2109
2117
  space comes from the final step's forward pass, except input-kind latent spaces which come
2110
2118
  from the first step — the original sample, before generated content dominates the model
2111
2119
  inputs. 'mean': every latent space is the elementwise mean over all steps of the chain.
2112
-
2113
- cache_tensors declares a KV-cache (or any threaded state) as
2114
- {model_input_name: model_output_name} — model inputs whose value is the named output from
2115
- the previous step. The platform threads those tensors itself and holds them on the GPU, so
2116
- the hook neither receives them in prev_inputs/prev_outputs nor returns them in next_inputs;
2117
- return only the inputs you actually compute (e.g. input_ids, position_ids, attention_mask).
2118
- The cache must be fixed-capacity: the declared input and output must have the same, fully
2119
- concrete shape, since chains at different steps are batched together and the cache is
2120
- preallocated. Exports with a growing cache (past length P in, P+1 out) are rejected at push.
2121
-
2122
- @tensorleap_autoregressive_step(cache_tensors={
2123
- f'past_key_values.{i}.{kv}': f'present.{i}.{kv}'
2124
- for i in range(30) for kv in ('key', 'value')
2125
- })
2126
-
2127
- Omit it for a model with no cache — the chain then re-feeds its whole prompt every step.
2128
-
2129
- cache_seq_axis declares which axis of those cache tensors indexes token positions — the axis
2130
- whose length IS the cache capacity, which the platform reads to cap chain length. The default
2131
- 2 matches (batch, heads, position, head_dim), the layout HF-style exports produce. Set it
2132
- when yours differs, e.g. 1 for (batch, position, heads, head_dim). It cannot be inferred: in
2133
- a (1, 3, 64, 64) tensor two dims are 64, so a wrong guess would silently cap chains at the
2134
- number of heads and truncate every chain a few steps in.
2135
2120
  """
2136
2121
  assert isinstance(latent_space_aggregation, str), \
2137
2122
  ('tensorleap_autoregressive_step must be called with parentheses: '
@@ -2279,9 +2264,7 @@ def tensorleap_autoregressive_step(latent_space_aggregation: str = 'last_step',
2279
2264
  return result
2280
2265
 
2281
2266
  leap_binder.set_autoregressive_step(inner_without_validate,
2282
- latent_space_aggregation=latent_space_aggregation,
2283
- cache_tensors=cache_tensors,
2284
- cache_seq_axis=cache_seq_axis)
2267
+ latent_space_aggregation=latent_space_aggregation)
2285
2268
 
2286
2269
  def inner(*args, **kwargs):
2287
2270
  if not _call_from_tl_platform:
@@ -2481,9 +2464,8 @@ def tensorleap_model_loop():
2481
2464
  if not prediction_names:
2482
2465
  _fail('tensorleap_model_loop requires prediction types declared on '
2483
2466
  'tensorleap_load_model — the loop keys the model outputs by their names.')
2484
- context = _ModelLoopContext(
2485
- sample_id, preprocess_response, prediction_names, is_mapping,
2486
- cache_tensors=leap_binder.setup_container.autoregressive_step.cache_tensors)
2467
+ context = _ModelLoopContext(sample_id, preprocess_response, prediction_names,
2468
+ is_mapping)
2487
2469
  _active_model_loop = context
2488
2470
  try:
2489
2471
  result = user_function(_ModelLoopModelProxy(model, context), sample_id,
@@ -2973,7 +2955,12 @@ def tensorleap_element_instance_preprocess(
2973
2955
  instances_length = instance_length_encoder(sample_id, preprocess_response)
2974
2956
  instances_ids = [f'{sample_id}_{instance_id}' for instance_id in range(instances_length)]
2975
2957
  sample_ids_to_instance_mappings[sample_id] = instances_ids
2976
- instance_to_sample_ids_mappings[sample_id] = sample_id
2958
+ # Only instance ids belong in instance_to_sample_ids_mappings: the engine uses
2959
+ # membership in it as "is this row an element instance?" (custometl.py,
2960
+ # samplesgenerator.py). Mapping an original id to itself made that test true for
2961
+ # every image row, routing it into construct_instance_sample, where looking the
2962
+ # original up in its own instance list raises
2963
+ # "Index <id> with sample_id: <id> cannot be found!".
2977
2964
  for idx, instance_id in enumerate(instances_ids):
2978
2965
  if not found_instance_metadata:
2979
2966
  element_instance = instance_mask_encoder(sample_id, preprocess_response, idx)
@@ -16,8 +16,7 @@ from code_loader.contract.datasetclasses import DatasetSample, DatasetBaseHandle
16
16
  PreprocessResponse, VisualizerHandler, LeapData, \
17
17
  PredictionTypeHandler, MetadataHandler, CustomLayerHandler, MetricHandler, VisualizerHandlerData, MetricHandlerData, \
18
18
  MetricCallableReturnType, CustomLossHandlerData, CustomLossHandler, RawInputsForHeatmap, SamplePreprocessResponse, \
19
- ElementInstance, custom_latent_space_attribute, DatasetIntegrationSetup, InstanceMetricHandler, \
20
- _simulation_context, DEFAULT_CACHE_SEQ_AXIS
19
+ ElementInstance, custom_latent_space_attribute, DatasetIntegrationSetup, InstanceMetricHandler, _simulation_context
21
20
  from code_loader.contract.enums import DataStateEnum, TestingSectionEnum, DataStateType, DatasetMetadataType
22
21
  from code_loader.contract.exceptions import DatasetScriptException
23
22
  from code_loader.contract.responsedataclasses import DatasetIntegParseResult, DatasetTestResultPayload, \
@@ -284,7 +283,7 @@ class LeapLoader(LeapLoaderBase):
284
283
 
285
284
  metadata, metadata_is_none = self.get_metadata(state, sample_id)
286
285
 
287
- custom_latent_spaces = self._get_custom_latent_spaces(sample_id, preprocess_result[state])
286
+ custom_latent_spaces = self._get_custom_latent_spaces(sample_id, preprocess_result[state], instance_id)
288
287
  instance_mask = self._get_instances_masks(state, sample_id, instance_id)
289
288
  sample = DatasetSample(inputs=self._get_inputs(state, sample_id),
290
289
  gt=None if state == DataStateEnum.unlabeled else self._get_gt(state, sample_id),
@@ -350,6 +349,9 @@ class LeapLoader(LeapLoaderBase):
350
349
  global_leap_binder.validate_autoregressive_setup()
351
350
  preprocess_test_payload = self._check_preprocess()
352
351
  test_payloads.append(preprocess_test_payload)
352
+ instance_ls_test_payload = self._check_instance_custom_latent_spaces()
353
+ if instance_ls_test_payload is not None:
354
+ test_payloads.append(instance_ls_test_payload)
353
355
  handlers_test_payloads = self._check_handlers()
354
356
  test_payloads.extend(handlers_test_payloads)
355
357
  simulation_test_payloads = self._check_simulations()
@@ -423,6 +425,37 @@ class LeapLoader(LeapLoaderBase):
423
425
  test_result.is_passed = False
424
426
  return test_result
425
427
 
428
+ def _check_instance_custom_latent_spaces(self) -> Optional[DatasetTestResultPayload]:
429
+ instance_aware_names = [
430
+ name for name, handler in global_leap_binder.setup_container.custom_latent_spaces.items()
431
+ if handler.instance_aware
432
+ ]
433
+ if not instance_aware_names:
434
+ return None
435
+
436
+ test_result = DatasetTestResultPayload('instance_custom_latent_space')
437
+ errors = []
438
+ if not global_leap_binder.setup_container.instance_masks:
439
+ errors.append(
440
+ f"Instance-aware custom latent space(s) {instance_aware_names} require "
441
+ f"@tensorleap_instances_masks_encoder to be registered."
442
+ )
443
+ preprocess_result = self._preprocess_result()
444
+ has_instance_mappings = any(
445
+ preprocess_response.instance_to_sample_ids_mappings
446
+ for preprocess_response in preprocess_result.values()
447
+ )
448
+ if not has_instance_mappings:
449
+ errors.append(
450
+ f"Instance-aware custom latent space(s) {instance_aware_names} require the preprocess "
451
+ f"function to be decorated with @tensorleap_element_instance_preprocess("
452
+ f"instance_length_encoder, instance_mask_encoder)."
453
+ )
454
+ if errors:
455
+ test_result.is_passed = False
456
+ test_result.display[TestingSectionEnum.Errors.name] = "\n".join(errors)
457
+ return test_result
458
+
426
459
  def _check_handlers(self) -> List[DatasetTestResultPayload]:
427
460
  preprocess_result = self._preprocess_result()
428
461
  result_payloads: List[DatasetTestResultPayload] = []
@@ -1108,6 +1141,9 @@ class LeapLoader(LeapLoaderBase):
1108
1141
  latent_handlers = global_leap_binder.setup_container.custom_latent_spaces
1109
1142
  custom_latent_spaces = None
1110
1143
  if latent_handlers:
1144
+ if any(handler.instance_aware for handler in latent_handlers.values()):
1145
+ raise Exception(
1146
+ "instance-aware custom latent spaces are not supported with grouped preprocess responses")
1111
1147
  custom_latent_spaces = {
1112
1148
  name: self._to_grouped_list(handler.function(group_ids, preprocess_state))
1113
1149
  for name, handler in latent_handlers.items()
@@ -1330,17 +1366,29 @@ class LeapLoader(LeapLoaderBase):
1330
1366
  def _get_custom_latent_spaces(
1331
1367
  self,
1332
1368
  sample_id: Union[int, str],
1333
- preprocess: "PreprocessResponse") -> Optional[Dict[str, npt.NDArray[np.float32]]]:
1369
+ preprocess: "PreprocessResponse",
1370
+ instance_id: Optional[int] = None) -> Optional[Dict[str, npt.NDArray[np.float32]]]:
1334
1371
  handlers = global_leap_binder.setup_container.custom_latent_spaces
1335
1372
  if not handlers:
1336
1373
  return None
1337
1374
  if preprocess.is_grouped:
1338
1375
  # Single-sample fetch: encode this sample only, not the whole group (same
1339
1376
  # memory rationale as _get_dataset_handlers; see grouped-fetch-oom-bug.md).
1377
+ if any(handler.instance_aware for handler in handlers.values()):
1378
+ raise Exception(
1379
+ "instance-aware custom latent spaces are not supported with grouped preprocess responses")
1340
1380
  self._locate_group(preprocess, sample_id) # validates group membership
1341
1381
  return {name: self._to_grouped_list(handler.function([sample_id], preprocess))[0]
1342
1382
  for name, handler in handlers.items()}
1343
- return {name: handler.function(sample_id, preprocess) for name, handler in handlers.items()}
1383
+ result: Dict[str, npt.NDArray[np.float32]] = {}
1384
+ for name, handler in handlers.items():
1385
+ if handler.instance_aware:
1386
+ if instance_id is None:
1387
+ continue
1388
+ result[name] = handler.function(sample_id, preprocess, instance_id)
1389
+ else:
1390
+ result[name] = handler.function(sample_id, preprocess)
1391
+ return result
1344
1392
 
1345
1393
  @lru_cache()
1346
1394
  def has_custom_latent_space_decorator(self) -> bool:
@@ -1349,14 +1397,26 @@ class LeapLoader(LeapLoaderBase):
1349
1397
 
1350
1398
  @lru_cache()
1351
1399
  def get_custom_latent_space_names(self) -> Tuple[str, ...]:
1352
- """Ordered names of all registered custom latent spaces.
1400
+ """Names of all registered custom latent spaces, in registration order.
1353
1401
 
1354
- Registration order is the canonical index mapping consumed by the engine
1355
- (name i -> user_custom_i). Returns a tuple so the lru_cache value is hashable.
1402
+ The engine resolves each name to its `user_custom_i` index by sorted name, not
1403
+ registration order, so this order is not the index mapping. Returns a tuple so the
1404
+ lru_cache value is hashable.
1356
1405
  """
1357
1406
  self.exec_script()
1358
1407
  return tuple(global_leap_binder.setup_container.custom_latent_spaces.keys())
1359
1408
 
1409
+ @lru_cache()
1410
+ def get_instance_custom_latent_space_names(self) -> Tuple[str, ...]:
1411
+ """Names of registered custom latent spaces that are instance-aware.
1412
+
1413
+ Names, not indices — the engine resolves each name to its `user_custom_i` index
1414
+ by sorted name.
1415
+ """
1416
+ self.exec_script()
1417
+ return tuple(name for name, handler in global_leap_binder.setup_container.custom_latent_spaces.items()
1418
+ if handler.instance_aware)
1419
+
1360
1420
  @lru_cache()
1361
1421
  def get_custom_latent_space_for_analysis(self) -> Optional[str]:
1362
1422
  """Name of the custom latent space flagged with use_ls_for_analysis=True, if any.
@@ -1383,34 +1443,6 @@ class LeapLoader(LeapLoaderBase):
1383
1443
  return 'last_step'
1384
1444
  return handler.latent_space_aggregation
1385
1445
 
1386
- @lru_cache()
1387
- def get_autoregressive_cache_tensors(self) -> Dict[str, str]:
1388
- """Declared KV-cache pairing, {model_input_name: model_output_name}.
1389
-
1390
- Empty when nothing is declared — the platform then runs the chain uncached. The platform
1391
- validates these names against the real model graph at push time; only structural checks
1392
- happen here.
1393
- """
1394
- self.exec_script()
1395
- handler = global_leap_binder.setup_container.autoregressive_step
1396
- if handler is None or not handler.cache_tensors:
1397
- return {}
1398
- return dict(handler.cache_tensors)
1399
-
1400
- @lru_cache()
1401
- def get_autoregressive_cache_seq_axis(self) -> int:
1402
- """Which axis of the declared cache tensors indexes token positions.
1403
-
1404
- The platform reads the cache capacity off this axis to cap chain length. Defaults to 2
1405
- ((batch, heads, position, head_dim)) — including when nothing is declared, where it is
1406
- simply unused.
1407
- """
1408
- self.exec_script()
1409
- handler = global_leap_binder.setup_container.autoregressive_step
1410
- if handler is None:
1411
- return DEFAULT_CACHE_SEQ_AXIS
1412
- return int(handler.cache_seq_axis)
1413
-
1414
1446
  def run_autoregressive_step(self, sample_id: Union[int, str],
1415
1447
  prev_inputs: Optional[Dict[str, npt.NDArray[np.float32]]],
1416
1448
  prev_outputs: Optional[Dict[str, npt.NDArray[np.float32]]],
@@ -176,16 +176,6 @@ class LeapLoaderBase:
176
176
  raise NotImplementedError(f'{type(self).__name__} does not implement '
177
177
  'get_autoregressive_latent_space_aggregation.')
178
178
 
179
- @abstractmethod
180
- def get_autoregressive_cache_tensors(self) -> Dict[str, str]:
181
- raise NotImplementedError(f'{type(self).__name__} does not implement '
182
- 'get_autoregressive_cache_tensors.')
183
-
184
- @abstractmethod
185
- def get_autoregressive_cache_seq_axis(self) -> int:
186
- raise NotImplementedError(f'{type(self).__name__} does not implement '
187
- 'get_autoregressive_cache_seq_axis.')
188
-
189
179
  @abstractmethod
190
180
  def get_autoregressive_decorator_names(self) -> Dict[str, List[str]]:
191
181
  raise NotImplementedError(f'{type(self).__name__} does not implement '
@@ -1,6 +1,6 @@
1
1
  [tool.poetry]
2
2
  name = "code-loader"
3
- version = "1.0.203"
3
+ version = "1.0.204.dev1"
4
4
  description = ""
5
5
  authors = ["dorhar <doron.harnoy@tensorleap.ai>"]
6
6
  license = "MIT"