code-loader 1.0.197.dev0__py3-none-any.whl → 1.0.197.dev2__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- code_loader/contract/datasetclasses.py +67 -10
- code_loader/inner_leap_binder/leapbinder.py +98 -10
- code_loader/inner_leap_binder/leapbinder_decorators.py +226 -64
- code_loader/leaploader.py +302 -9
- code_loader/leaploaderbase.py +4 -0
- code_loader/utils.py +10 -0
- {code_loader-1.0.197.dev0.dist-info → code_loader-1.0.197.dev2.dist-info}/METADATA +1 -1
- {code_loader-1.0.197.dev0.dist-info → code_loader-1.0.197.dev2.dist-info}/RECORD +10 -10
- {code_loader-1.0.197.dev0.dist-info → code_loader-1.0.197.dev2.dist-info}/LICENSE +0 -0
- {code_loader-1.0.197.dev0.dist-info → code_loader-1.0.197.dev2.dist-info}/WHEEL +0 -0
code_loader/leaploader.py
CHANGED
|
@@ -41,6 +41,11 @@ def _serialize_sim_bounds(bounds) -> dict:
|
|
|
41
41
|
|
|
42
42
|
|
|
43
43
|
class LeapLoader(LeapLoaderBase):
|
|
44
|
+
# Opt-in ceiling (env var) on samples materialized per get_samples call — the engine sets it to
|
|
45
|
+
# turn a mistaken whole-group ask into a clear error instead of a silent worker OOM. Unset => no
|
|
46
|
+
# ceiling. See get_samples and grouped-fetch-oom-bug.md.
|
|
47
|
+
_grouped_fetch_max_samples_env = "LEAP_GROUPED_FETCH_MAX_SAMPLES_PER_CALL"
|
|
48
|
+
|
|
44
49
|
def __init__(self, code_path: str, code_entry_name: str):
|
|
45
50
|
super().__init__(code_path, code_entry_name)
|
|
46
51
|
|
|
@@ -60,14 +65,36 @@ class LeapLoader(LeapLoaderBase):
|
|
|
60
65
|
|
|
61
66
|
@lru_cache()
|
|
62
67
|
def exec_script(self) -> None:
|
|
68
|
+
from code_loader.inner_leap_binder import leapbinder_decorators as _leap_dec
|
|
63
69
|
try:
|
|
64
70
|
os.environ[mapping_runtime_mode_env_var_mame] = 'TRUE'
|
|
65
71
|
self.evaluate_module()
|
|
72
|
+
|
|
73
|
+
# A grouped integration test may index a grouped input/gt (image_list[i]) to wire a
|
|
74
|
+
# single sample of a group; that indexing is only valid when the mapping graph is
|
|
75
|
+
# built in grouped mode. Detect grouped-ness so the placeholder pass below matches
|
|
76
|
+
# the dataset. Preprocess is stubbed while mapping mode is on, so run it with mapping
|
|
77
|
+
# mode momentarily disabled, and cache it so _preprocess_result() doesn't re-run it.
|
|
78
|
+
is_grouped = False
|
|
79
|
+
if global_leap_binder.integration_test_func is not None:
|
|
80
|
+
del os.environ[mapping_runtime_mode_env_var_mame]
|
|
81
|
+
try:
|
|
82
|
+
preprocess_result = global_leap_binder.get_preprocess_result()
|
|
83
|
+
self._preprocess_result_cached = preprocess_result
|
|
84
|
+
is_grouped = any(r.is_grouped for r in preprocess_result.values())
|
|
85
|
+
finally:
|
|
86
|
+
os.environ[mapping_runtime_mode_env_var_mame] = 'TRUE'
|
|
87
|
+
|
|
88
|
+
_leap_dec._mapping_dataset_is_grouped = is_grouped
|
|
66
89
|
if global_leap_binder.integration_test_func is not None:
|
|
67
90
|
from code_loader.inner_leap_binder.leapbinder_decorators import \
|
|
68
91
|
_reset_model_loop_state
|
|
69
92
|
_reset_model_loop_state()
|
|
70
|
-
|
|
93
|
+
mapping_preprocess = (
|
|
94
|
+
PreprocessResponse(sample_ids=[["__mapping_placeholder__"]], state=DataStateType.training)
|
|
95
|
+
if is_grouped else
|
|
96
|
+
PreprocessResponse(state=DataStateType.training, length=0))
|
|
97
|
+
global_leap_binder.integration_test_func(None, mapping_preprocess)
|
|
71
98
|
except TypeError as e:
|
|
72
99
|
import traceback
|
|
73
100
|
global_leap_binder.setup_container = DatasetIntegrationSetup()
|
|
@@ -80,6 +107,7 @@ class LeapLoader(LeapLoaderBase):
|
|
|
80
107
|
raise DatasetScriptException(getattr(e, 'message', repr(e))) from e
|
|
81
108
|
finally:
|
|
82
109
|
# ensure that the environment variable is removed after the script execution
|
|
110
|
+
_leap_dec._mapping_dataset_is_grouped = False
|
|
83
111
|
if mapping_runtime_mode_env_var_mame in os.environ:
|
|
84
112
|
del os.environ[mapping_runtime_mode_env_var_mame]
|
|
85
113
|
|
|
@@ -209,7 +237,7 @@ class LeapLoader(LeapLoaderBase):
|
|
|
209
237
|
additional = self._preprocess_result().get(DataStateEnum.additional)
|
|
210
238
|
if additional is None:
|
|
211
239
|
return set()
|
|
212
|
-
return set(additional.
|
|
240
|
+
return set(additional.flat_sample_ids)
|
|
213
241
|
|
|
214
242
|
def _resolve_synthetic(self, sample_id: Union[int, str],
|
|
215
243
|
state: Optional[DataStateEnum] = None
|
|
@@ -250,7 +278,7 @@ class LeapLoader(LeapLoaderBase):
|
|
|
250
278
|
)
|
|
251
279
|
|
|
252
280
|
preprocess_result = self._preprocess_result()
|
|
253
|
-
if state == DataStateEnum.unlabeled and sample_id not in preprocess_result[state].
|
|
281
|
+
if state == DataStateEnum.unlabeled and sample_id not in preprocess_result[state].flat_sample_ids:
|
|
254
282
|
self._preprocess_result(update_unlabeled_preprocess=True)
|
|
255
283
|
|
|
256
284
|
metadata, metadata_is_none = self.get_metadata(state, sample_id)
|
|
@@ -379,10 +407,10 @@ class LeapLoader(LeapLoaderBase):
|
|
|
379
407
|
test_result = DatasetTestResultPayload('preprocess')
|
|
380
408
|
try:
|
|
381
409
|
preprocess_result = self._preprocess_result()
|
|
382
|
-
if self.get_sample_id_type()
|
|
410
|
+
if issubclass(self.get_sample_id_type(), str):
|
|
383
411
|
max_allowed_item_size = np.dtype('<U256').itemsize
|
|
384
412
|
for state, preprocess_response in preprocess_result.items():
|
|
385
|
-
sample_ids_array = np.array(preprocess_response.
|
|
413
|
+
sample_ids_array = np.array(preprocess_response.flat_sample_ids)
|
|
386
414
|
if sample_ids_array.dtype.itemsize > max_allowed_item_size:
|
|
387
415
|
raise Exception(f"Sample id are too long. Max allowed length is 256 charecters.")
|
|
388
416
|
|
|
@@ -443,6 +471,10 @@ class LeapLoader(LeapLoaderBase):
|
|
|
443
471
|
preprocess_response.tl_generated = True
|
|
444
472
|
if preprocess_response.length < 1:
|
|
445
473
|
raise ValueError("Simulation returned PreprocessResponse with length < 1")
|
|
474
|
+
if preprocess_response.is_grouped:
|
|
475
|
+
raise ValueError(
|
|
476
|
+
"Simulation returned a grouped PreprocessResponse; simulations must "
|
|
477
|
+
"return a flat (non-grouped) PreprocessResponse.")
|
|
446
478
|
preprocess_response.sample_ids = [0]
|
|
447
479
|
for handler in global_leap_binder.setup_container.inputs:
|
|
448
480
|
out1 = handler.function(preprocess_response.sample_ids[0], preprocess_response)
|
|
@@ -478,7 +510,10 @@ class LeapLoader(LeapLoaderBase):
|
|
|
478
510
|
if preprocess_response.sample_ids_to_instance_mappings:
|
|
479
511
|
raise Exception('Element instances are not supported together with '
|
|
480
512
|
'tensorleap_autoregressive_step.')
|
|
481
|
-
|
|
513
|
+
# Grouped preprocess is fine: grouping only amortizes the shared base reads
|
|
514
|
+
# (GT/metadata/custom_latent) — the AR hook always drives a single scalar chain
|
|
515
|
+
# and is never handed a group.
|
|
516
|
+
sample_id = preprocess_response.flat_sample_ids[0]
|
|
482
517
|
first_result = handler.function(sample_id, None, None, None, preprocess_response)
|
|
483
518
|
second_result = handler.function(sample_id, None, None, None, preprocess_response)
|
|
484
519
|
if not isinstance(first_result, tuple) or len(first_result) != 2:
|
|
@@ -559,7 +594,7 @@ class LeapLoader(LeapLoaderBase):
|
|
|
559
594
|
if handler.input_shapes is None:
|
|
560
595
|
preprocess_result = self._preprocess_result()
|
|
561
596
|
first_state_response = next(iter(preprocess_result.values()))
|
|
562
|
-
first_sample_id = first_state_response.
|
|
597
|
+
first_sample_id = first_state_response.flat_sample_ids[0]
|
|
563
598
|
first_result = handler.function(first_sample_id, None, None, None,
|
|
564
599
|
first_state_response)
|
|
565
600
|
if not isinstance(first_result, tuple) or len(first_result) != 2 or \
|
|
@@ -898,6 +933,57 @@ class LeapLoader(LeapLoaderBase):
|
|
|
898
933
|
|
|
899
934
|
return sample_ids
|
|
900
935
|
|
|
936
|
+
@staticmethod
|
|
937
|
+
def _grouped_dict_keys(rows: List[Dict[str, Any]], handler_name: str) -> List[str]:
|
|
938
|
+
"""Keys for a group of dict-metadata rows, requiring every row to share the same keys.
|
|
939
|
+
A group must expose a uniform metadata schema; otherwise the per-key lists would silently
|
|
940
|
+
drop or KeyError on rows that differ from row 0."""
|
|
941
|
+
keys = list(rows[0].keys())
|
|
942
|
+
key_set = set(keys)
|
|
943
|
+
for pos, row in enumerate(rows):
|
|
944
|
+
if set(row.keys()) != key_set:
|
|
945
|
+
raise ValueError(
|
|
946
|
+
f"Metadata handler {handler_name!r} returned inconsistent dict keys across the "
|
|
947
|
+
f"group: row 0 has {sorted(key_set)} but row {pos} has {sorted(row.keys())}. "
|
|
948
|
+
f"All rows in a group must share the same metadata keys.")
|
|
949
|
+
return keys
|
|
950
|
+
|
|
951
|
+
@staticmethod
|
|
952
|
+
def _to_grouped_list(raw: Any) -> List[npt.NDArray[np.float32]]:
|
|
953
|
+
"""Normalize a grouped encoder result to a list of per-sample arrays.
|
|
954
|
+
|
|
955
|
+
Grouped results are NEVER stacked into a (B, *) batch. Grouping is only a storage
|
|
956
|
+
read-unit (one file load per group); assembling the model batch (B) is the engine's
|
|
957
|
+
single responsibility, so it stays the only place samples get batched. Keeping
|
|
958
|
+
per-sample arrays lets the engine batch to any B — including a B=1-only model — without
|
|
959
|
+
an intermediate stack/unstack. A user encoder that already returned a stacked (B, *)
|
|
960
|
+
array is split back into its per-sample rows so the contract is a list either way."""
|
|
961
|
+
if isinstance(raw, list):
|
|
962
|
+
return raw
|
|
963
|
+
return [row for row in np.asarray(raw)]
|
|
964
|
+
|
|
965
|
+
def _locate_group(self, preprocess_state: PreprocessResponse,
|
|
966
|
+
sample_id: Union[int, str]) -> Tuple[List[Union[int, str]], int]:
|
|
967
|
+
"""Map a sample id to (its group, its position in that group). Cached on the
|
|
968
|
+
PreprocessResponse itself (not keyed by id() on this loader) so grouped single-sample
|
|
969
|
+
fetches don't rescan the groups, and the cache can never go stale: when the response is
|
|
970
|
+
replaced (e.g. unlabeled preprocess refresh), the old object and its cache are simply
|
|
971
|
+
discarded together instead of lingering under a possibly-reused id()."""
|
|
972
|
+
mapping = preprocess_state._group_pos_cache
|
|
973
|
+
if mapping is None:
|
|
974
|
+
mapping = {}
|
|
975
|
+
for group in preprocess_state.groups:
|
|
976
|
+
for pos, sid in enumerate(group):
|
|
977
|
+
if sid in mapping:
|
|
978
|
+
raise ValueError(
|
|
979
|
+
f"Duplicate sample id {sid!r} across groups in the preprocess response; "
|
|
980
|
+
f"sample ids must be unique within and across groups.")
|
|
981
|
+
mapping[sid] = (group, pos)
|
|
982
|
+
preprocess_state._group_pos_cache = mapping
|
|
983
|
+
if sample_id not in mapping:
|
|
984
|
+
raise KeyError(f"Sample id {sample_id!r} is not present in any group of this preprocess response.")
|
|
985
|
+
return mapping[sample_id]
|
|
986
|
+
|
|
901
987
|
def _get_dataset_handlers(self, handlers: Iterable[DatasetBaseHandler],
|
|
902
988
|
state: DataStateEnum, sample_id: Union[int, str]) -> Dict[str, npt.NDArray[np.float32]]:
|
|
903
989
|
result_agg = {}
|
|
@@ -909,12 +995,127 @@ class LeapLoader(LeapLoaderBase):
|
|
|
909
995
|
sample_id = original_local_id
|
|
910
996
|
else:
|
|
911
997
|
preprocess_state = preprocess_result[state]
|
|
998
|
+
if preprocess_state.is_grouped:
|
|
999
|
+
# Grouped dataset, SINGLE-sample fetch: encode only THIS sample, never the whole group.
|
|
1000
|
+
# Grouping is an I/O read-unit, not an encode-unit — the integration's per-file cache
|
|
1001
|
+
# makes the group's file decode once across consecutive per-sample calls, so a singleton
|
|
1002
|
+
# ([sample_id]) is both a valid single-group subset of the get_samples contract and
|
|
1003
|
+
# correct (samples in a group are independent; see _to_grouped_list). Handing the encoder
|
|
1004
|
+
# the whole group here just to index one sample out materialized B encoded tensors per
|
|
1005
|
+
# sample — a driver of the grouped-fetch worker OOM (see grouped-fetch-oom-bug.md).
|
|
1006
|
+
self._locate_group(preprocess_state, sample_id) # validates the id belongs to a group
|
|
1007
|
+
for handler in handlers:
|
|
1008
|
+
result_agg[handler.name] = self._to_grouped_list(
|
|
1009
|
+
handler.function([sample_id], preprocess_state))[0]
|
|
1010
|
+
return result_agg
|
|
912
1011
|
for handler in handlers:
|
|
913
1012
|
handler_result = handler.function(sample_id, preprocess_state)
|
|
914
1013
|
handler_name = handler.name
|
|
915
1014
|
result_agg[handler_name] = handler_result
|
|
916
1015
|
return result_agg
|
|
917
1016
|
|
|
1017
|
+
def get_samples(self, state: DataStateEnum, group_ids: List[Union[int, str]]) -> DatasetSample:
|
|
1018
|
+
"""Group-aware fetch: hand the whole group to each encoder in a single call and return a
|
|
1019
|
+
grouped DatasetSample (inputs/gt as per-sample lists of length B, metadata as per-key lists
|
|
1020
|
+
of length B, index = the group). Grouped results are never stacked here — batching the
|
|
1021
|
+
samples into the model's B is the engine's job. This is the path a group-aware engine calls
|
|
1022
|
+
to read a file once per group. Row order matches group_ids exactly."""
|
|
1023
|
+
self.exec_script()
|
|
1024
|
+
preprocess_result = self._preprocess_result()
|
|
1025
|
+
if state == DataStateEnum.unlabeled and any(
|
|
1026
|
+
sid not in preprocess_result[state].flat_sample_ids for sid in group_ids):
|
|
1027
|
+
# Mirrors get_sample's refresh: the unlabeled preprocess can grow between calls, so a
|
|
1028
|
+
# group_id absent from the current snapshot may just not have been generated yet.
|
|
1029
|
+
self._preprocess_result(update_unlabeled_preprocess=True)
|
|
1030
|
+
preprocess_state = preprocess_result[state]
|
|
1031
|
+
assert preprocess_state.is_grouped, (
|
|
1032
|
+
"get_samples is the group-aware fetch path and requires a grouped preprocess "
|
|
1033
|
+
"response; call get_sample for a flat (non-grouped) dataset.")
|
|
1034
|
+
# All requested ids must belong to a single group (one file). A cross-group request
|
|
1035
|
+
# would force the encoder to load multiple files, defeating the one-load-per-group
|
|
1036
|
+
# contract; partitioning a scattered request by group is the engine's responsibility.
|
|
1037
|
+
distinct_groups = {id(self._locate_group(preprocess_state, sid)[0]) for sid in group_ids}
|
|
1038
|
+
assert len(distinct_groups) == 1, (
|
|
1039
|
+
f"get_samples received sample ids spanning {len(distinct_groups)} groups; a request "
|
|
1040
|
+
f"must be confined to a single group. The engine partitions cross-group requests and "
|
|
1041
|
+
f"issues one get_samples call per group.")
|
|
1042
|
+
# Opt-in safety net for the grouped-fetch OOM (grouped-fetch-oom-bug.md). This call
|
|
1043
|
+
# materializes len(group_ids) encoded samples at once; on a large group/file that peak is
|
|
1044
|
+
# what kernel-OOM-kills the worker. A group's file is decoded once by the integration's
|
|
1045
|
+
# per-file cache regardless of how the encode is chunked, so the engine should request
|
|
1046
|
+
# memory-bounded sub-chunks of a group (each a valid single-group subset, e.g.
|
|
1047
|
+
# get_samples(state, group_ids[i:j])) rather than the whole group. When the engine sets the
|
|
1048
|
+
# ceiling below, an over-large ask fails here with a message that names the lever instead of
|
|
1049
|
+
# a silent GENERIC_MEMORY_LIMIT kill. Unset => no ceiling (preserves current whole-group callers).
|
|
1050
|
+
max_per_call = os.environ.get(self._grouped_fetch_max_samples_env)
|
|
1051
|
+
if max_per_call is not None and len(group_ids) > int(max_per_call):
|
|
1052
|
+
raise ValueError(
|
|
1053
|
+
f"get_samples asked to materialize {len(group_ids)} samples in a single call, above "
|
|
1054
|
+
f"the configured ceiling {max_per_call} ({self._grouped_fetch_max_samples_env}). The "
|
|
1055
|
+
f"group's file is decoded once via the integration's per-file cache regardless of "
|
|
1056
|
+
f"chunking, so request memory-bounded sub-chunks of the group "
|
|
1057
|
+
f"(get_samples(state, group_ids[i:j])) instead of the whole group. "
|
|
1058
|
+
f"See grouped-fetch-oom-bug.md.")
|
|
1059
|
+
inputs = {handler.name: self._to_grouped_list(handler.function(group_ids, preprocess_state))
|
|
1060
|
+
for handler in global_leap_binder.setup_container.inputs}
|
|
1061
|
+
autoregressive_handler = global_leap_binder.setup_container.autoregressive_step
|
|
1062
|
+
if autoregressive_handler is not None:
|
|
1063
|
+
# AR integrations have no input encoders — the hook's first call supplies each
|
|
1064
|
+
# chain's step-0 model inputs. The hook is strictly per-scalar-chain (never handed
|
|
1065
|
+
# a group), so it is called once per flat id in the group and the per-sample results
|
|
1066
|
+
# are collected into per-key lists of length B, matching the grouped (never-stacked)
|
|
1067
|
+
# contract used for every other encoder here.
|
|
1068
|
+
step_zero_per_sample = []
|
|
1069
|
+
for sid in group_ids:
|
|
1070
|
+
step_zero_result = autoregressive_handler.function(sid, None, None, None,
|
|
1071
|
+
preprocess_state)
|
|
1072
|
+
if not isinstance(step_zero_result, tuple) or len(step_zero_result) != 2 or \
|
|
1073
|
+
not isinstance(step_zero_result[0], dict):
|
|
1074
|
+
raise Exception(
|
|
1075
|
+
'The autoregressive step hook must return a (next_inputs, state) tuple '
|
|
1076
|
+
f'with a dict of initial model inputs on its first call, got '
|
|
1077
|
+
f'{type(step_zero_result)} for sample {sid}.')
|
|
1078
|
+
self._validate_autoregressive_step_inputs(step_zero_result[0], sid)
|
|
1079
|
+
step_zero_per_sample.append(step_zero_result[0])
|
|
1080
|
+
if step_zero_per_sample:
|
|
1081
|
+
for key in self._grouped_dict_keys(step_zero_per_sample, autoregressive_handler.name):
|
|
1082
|
+
inputs[key] = [row[key] for row in step_zero_per_sample]
|
|
1083
|
+
gt = None
|
|
1084
|
+
if state != DataStateEnum.unlabeled:
|
|
1085
|
+
gt = {handler.name: self._to_grouped_list(handler.function(group_ids, preprocess_state))
|
|
1086
|
+
for handler in global_leap_binder.setup_container.ground_truths}
|
|
1087
|
+
|
|
1088
|
+
metadata: Dict[str, Any] = {}
|
|
1089
|
+
metadata_is_none: Dict[str, Any] = {}
|
|
1090
|
+
for handler in global_leap_binder.setup_container.metadata:
|
|
1091
|
+
rows = handler.function(group_ids, preprocess_state) # length-B sequence of scalars/dicts
|
|
1092
|
+
if len(rows) > 0 and isinstance(rows[0], dict):
|
|
1093
|
+
for key in self._grouped_dict_keys(rows, handler.name):
|
|
1094
|
+
name = "{}_{}".format(handler.name, key)
|
|
1095
|
+
converted = [self._convert_metadata_to_correct_type(name, row[key]) for row in rows]
|
|
1096
|
+
metadata[name] = [v for v, _ in converted]
|
|
1097
|
+
metadata_is_none[name] = [is_none for _, is_none in converted]
|
|
1098
|
+
else:
|
|
1099
|
+
converted = [self._convert_metadata_to_correct_type(handler.name, row) for row in rows]
|
|
1100
|
+
metadata[handler.name] = [v for v, _ in converted]
|
|
1101
|
+
metadata_is_none[handler.name] = [is_none for _, is_none in converted]
|
|
1102
|
+
|
|
1103
|
+
# custom_latent_spaces are group-aware like the input/GT encoders: hand each the whole group
|
|
1104
|
+
# in a single call so a file-backed latent fn loads the file once, and normalize to per-sample
|
|
1105
|
+
# lists. This keeps the mandatory group path non-lossy. instance_masks stay None here: they are
|
|
1106
|
+
# a per-(sample, instance) concern that needs an instance_id the group fetch does not carry.
|
|
1107
|
+
latent_handlers = global_leap_binder.setup_container.custom_latent_spaces
|
|
1108
|
+
custom_latent_spaces = None
|
|
1109
|
+
if latent_handlers:
|
|
1110
|
+
custom_latent_spaces = {
|
|
1111
|
+
name: self._to_grouped_list(handler.function(group_ids, preprocess_state))
|
|
1112
|
+
for name, handler in latent_handlers.items()
|
|
1113
|
+
}
|
|
1114
|
+
|
|
1115
|
+
return DatasetSample(inputs=inputs, gt=gt, metadata=metadata, metadata_is_none=metadata_is_none,
|
|
1116
|
+
index=list(group_ids), state=state, custom_latent_spaces=custom_latent_spaces,
|
|
1117
|
+
instance_masks=None)
|
|
1118
|
+
|
|
918
1119
|
def _get_inputs(self, state: DataStateEnum, sample_id: Union[int, str]) -> Dict[str, npt.NDArray[np.float32]]:
|
|
919
1120
|
inputs = self._get_dataset_handlers(global_leap_binder.setup_container.inputs, state, sample_id)
|
|
920
1121
|
autoregressive_handler = global_leap_binder.setup_container.autoregressive_step
|
|
@@ -1018,12 +1219,25 @@ class LeapLoader(LeapLoaderBase):
|
|
|
1018
1219
|
sample_id = original_local_id
|
|
1019
1220
|
else:
|
|
1020
1221
|
preprocess_state = preprocess_result[state]
|
|
1222
|
+
if preprocess_state.is_grouped:
|
|
1223
|
+
self._locate_group(preprocess_state, sample_id) # validates the id belongs to a group
|
|
1021
1224
|
for handler in global_leap_binder.setup_container.metadata:
|
|
1022
1225
|
if requested_metadata_names:
|
|
1023
1226
|
if not is_metadata_name_starts_with_handler_name(handler):
|
|
1024
1227
|
continue
|
|
1025
1228
|
|
|
1026
|
-
|
|
1229
|
+
if preprocess_state.is_grouped:
|
|
1230
|
+
# Single-sample metadata fetch: encode only THIS sample, never the whole group —
|
|
1231
|
+
# same memory rationale as _get_dataset_handlers / the get_sample latent branch
|
|
1232
|
+
# (grouped-fetch-oom-bug.md). Metadata values are not guaranteed small scalars: a
|
|
1233
|
+
# metadata encoder may return an np.asarray or read the sample's heavy data to
|
|
1234
|
+
# compute a statistic, so materializing all B rows just to index one out is the same
|
|
1235
|
+
# OOM path this fix closes. The batched get_metadata_multiple_samples correctly keeps
|
|
1236
|
+
# the whole-group encode (amortized across many ids via its group_cache); a single
|
|
1237
|
+
# id does not need it. A singleton is a valid single-group subset of the contract.
|
|
1238
|
+
handler_result = handler.function([sample_id], preprocess_state)[0]
|
|
1239
|
+
else:
|
|
1240
|
+
handler_result = handler.function(sample_id, preprocess_state)
|
|
1027
1241
|
if isinstance(handler_result, dict):
|
|
1028
1242
|
for single_metadata_name, single_metadata_result in handler_result.items():
|
|
1029
1243
|
handler_name = f'{handler.name}_{single_metadata_name}'
|
|
@@ -1042,6 +1256,66 @@ class LeapLoader(LeapLoaderBase):
|
|
|
1042
1256
|
|
|
1043
1257
|
return result_agg, is_none
|
|
1044
1258
|
|
|
1259
|
+
def _grouped_metadata_for_group(self, preprocess_state: PreprocessResponse,
|
|
1260
|
+
group_ids: List[Union[int, str]],
|
|
1261
|
+
requested_metadata_names: Optional[List[str]]
|
|
1262
|
+
) -> Tuple[Dict[str, List[Any]], Dict[str, List[bool]]]:
|
|
1263
|
+
def is_wanted(_handler):
|
|
1264
|
+
if not requested_metadata_names:
|
|
1265
|
+
return True
|
|
1266
|
+
for metadata_name in requested_metadata_names:
|
|
1267
|
+
if metadata_name.startswith(_handler.name + '_') or metadata_name == _handler.name:
|
|
1268
|
+
return True
|
|
1269
|
+
return False
|
|
1270
|
+
|
|
1271
|
+
results: Dict[str, List[Any]] = {}
|
|
1272
|
+
is_none: Dict[str, List[bool]] = {}
|
|
1273
|
+
for handler in global_leap_binder.setup_container.metadata:
|
|
1274
|
+
if requested_metadata_names and not is_wanted(handler):
|
|
1275
|
+
continue
|
|
1276
|
+
rows = handler.function(group_ids, preprocess_state)
|
|
1277
|
+
if len(rows) > 0 and isinstance(rows[0], dict):
|
|
1278
|
+
for key in self._grouped_dict_keys(rows, handler.name):
|
|
1279
|
+
name = f'{handler.name}_{key}'
|
|
1280
|
+
if requested_metadata_names and name not in requested_metadata_names:
|
|
1281
|
+
continue
|
|
1282
|
+
converted = [self._convert_metadata_to_correct_type(name, row[key]) for row in rows]
|
|
1283
|
+
results[name] = [v for v, _ in converted]
|
|
1284
|
+
is_none[name] = [n for _, n in converted]
|
|
1285
|
+
else:
|
|
1286
|
+
name = handler.name
|
|
1287
|
+
if requested_metadata_names and name not in requested_metadata_names:
|
|
1288
|
+
continue
|
|
1289
|
+
converted = [self._convert_metadata_to_correct_type(name, row) for row in rows]
|
|
1290
|
+
results[name] = [v for v, _ in converted]
|
|
1291
|
+
is_none[name] = [n for _, n in converted]
|
|
1292
|
+
return results, is_none
|
|
1293
|
+
|
|
1294
|
+
def get_metadata_multiple_samples(self, state: DataStateEnum, sample_ids: Union[List[int], List[str]],
|
|
1295
|
+
requested_metadata_names: Optional[List[str]] = None
|
|
1296
|
+
) -> Tuple[Dict[str, Union[List[str], List[int], List[bool], List[float]]],
|
|
1297
|
+
Dict[str, List[bool]]]:
|
|
1298
|
+
preprocess_state = self._preprocess_result().get(state)
|
|
1299
|
+
if preprocess_state is None or not preprocess_state.is_grouped:
|
|
1300
|
+
return super().get_metadata_multiple_samples(state, sample_ids, requested_metadata_names)
|
|
1301
|
+
|
|
1302
|
+
sample_id_type = self.get_sample_id_type()
|
|
1303
|
+
aggregated_results: Dict[str, List[Any]] = {}
|
|
1304
|
+
aggregated_is_none: Dict[str, List[bool]] = {}
|
|
1305
|
+
group_cache: Dict[int, Tuple[Dict[str, List[Any]], Dict[str, List[bool]]]] = {}
|
|
1306
|
+
for sample_id in sample_ids:
|
|
1307
|
+
sample_id = sample_id_type(sample_id)
|
|
1308
|
+
group_ids, pos = self._locate_group(preprocess_state, sample_id)
|
|
1309
|
+
key = id(group_ids)
|
|
1310
|
+
if key not in group_cache:
|
|
1311
|
+
group_cache[key] = self._grouped_metadata_for_group(
|
|
1312
|
+
preprocess_state, group_ids, requested_metadata_names)
|
|
1313
|
+
group_results, group_is_none = group_cache[key]
|
|
1314
|
+
for name, values in group_results.items():
|
|
1315
|
+
aggregated_results.setdefault(name, []).append(values[pos])
|
|
1316
|
+
aggregated_is_none.setdefault(name, []).append(group_is_none[name][pos])
|
|
1317
|
+
return aggregated_results, aggregated_is_none
|
|
1318
|
+
|
|
1045
1319
|
@lru_cache()
|
|
1046
1320
|
def get_sample_id_type(self) -> Type:
|
|
1047
1321
|
preprocess_results = list(self._preprocess_result().values())
|
|
@@ -1052,13 +1326,19 @@ class LeapLoader(LeapLoaderBase):
|
|
|
1052
1326
|
|
|
1053
1327
|
return id_type
|
|
1054
1328
|
|
|
1055
|
-
@staticmethod
|
|
1056
1329
|
def _get_custom_latent_spaces(
|
|
1330
|
+
self,
|
|
1057
1331
|
sample_id: Union[int, str],
|
|
1058
1332
|
preprocess: "PreprocessResponse") -> Optional[Dict[str, npt.NDArray[np.float32]]]:
|
|
1059
1333
|
handlers = global_leap_binder.setup_container.custom_latent_spaces
|
|
1060
1334
|
if not handlers:
|
|
1061
1335
|
return None
|
|
1336
|
+
if preprocess.is_grouped:
|
|
1337
|
+
# Single-sample fetch: encode this sample only, not the whole group (same
|
|
1338
|
+
# memory rationale as _get_dataset_handlers; see grouped-fetch-oom-bug.md).
|
|
1339
|
+
self._locate_group(preprocess, sample_id) # validates group membership
|
|
1340
|
+
return {name: self._to_grouped_list(handler.function([sample_id], preprocess))[0]
|
|
1341
|
+
for name, handler in handlers.items()}
|
|
1062
1342
|
return {name: handler.function(sample_id, preprocess) for name, handler in handlers.items()}
|
|
1063
1343
|
|
|
1064
1344
|
@lru_cache()
|
|
@@ -1076,6 +1356,19 @@ class LeapLoader(LeapLoaderBase):
|
|
|
1076
1356
|
self.exec_script()
|
|
1077
1357
|
return tuple(global_leap_binder.setup_container.custom_latent_spaces.keys())
|
|
1078
1358
|
|
|
1359
|
+
@lru_cache()
|
|
1360
|
+
def get_custom_latent_space_for_analysis(self) -> Optional[str]:
|
|
1361
|
+
"""Name of the custom latent space flagged with use_ls_for_analysis=True, if any.
|
|
1362
|
+
|
|
1363
|
+
The engine uses this latent space for the Out-Of-Distribution and Domain-Gap
|
|
1364
|
+
insights instead of the built-in defaults. Returns None when no custom latent
|
|
1365
|
+
space set the flag. At most one is flagged (enforced at registration time)."""
|
|
1366
|
+
self.exec_script()
|
|
1367
|
+
for name, handler in global_leap_binder.setup_container.custom_latent_spaces.items():
|
|
1368
|
+
if handler.use_ls_for_analysis:
|
|
1369
|
+
return name
|
|
1370
|
+
return None
|
|
1371
|
+
|
|
1079
1372
|
@lru_cache()
|
|
1080
1373
|
def has_autoregressive_step(self) -> bool:
|
|
1081
1374
|
self.exec_script()
|
code_loader/leaploaderbase.py
CHANGED
|
@@ -222,6 +222,10 @@ class LeapLoaderBase:
|
|
|
222
222
|
def get_custom_latent_space_names(self) -> Tuple[str, ...]:
|
|
223
223
|
pass
|
|
224
224
|
|
|
225
|
+
@abstractmethod
|
|
226
|
+
def get_custom_latent_space_for_analysis(self) -> Optional[str]:
|
|
227
|
+
pass
|
|
228
|
+
|
|
225
229
|
@abstractmethod
|
|
226
230
|
def get_heatmap_visualizer_raw_vis_input_arg_name(self, visualizer_name: str) -> Optional[str]:
|
|
227
231
|
pass
|
code_loader/utils.py
CHANGED
|
@@ -17,6 +17,16 @@ from code_loader.contract.enums import DatasetMetadataType
|
|
|
17
17
|
def to_numpy_return_wrapper(encoder_function: SectionCallableInterface) -> SectionCallableInterface:
|
|
18
18
|
def numpy_encoder_function(idx: Union[int, str], samples: PreprocessResponse) -> npt.NDArray[np.float32]:
|
|
19
19
|
result = encoder_function(idx, samples)
|
|
20
|
+
if isinstance(idx, list):
|
|
21
|
+
# Grouped call (idx is a list of sample ids): never stack the per-sample results into a
|
|
22
|
+
# (B, *) batch — grouping is only a storage read-unit; batching the group to the model's
|
|
23
|
+
# B is the engine's single responsibility. Gate on the call shape (idx is a list), not
|
|
24
|
+
# the result shape, so a flat encoder returning a list of arrays still stacks below.
|
|
25
|
+
# Normalize each sample independently (a user-stacked (B, *) array is split back into its
|
|
26
|
+
# rows) so a ragged group — variable per-sample shapes, e.g. token lists of differing
|
|
27
|
+
# length — is preserved instead of crashing np.array() on an inhomogeneous shape.
|
|
28
|
+
rows = result if isinstance(result, list) else np.asarray(result)
|
|
29
|
+
return [np.asarray(sample) for sample in rows]
|
|
20
30
|
numpy_result: npt.NDArray[np.float32] = np.array(result)
|
|
21
31
|
return numpy_result
|
|
22
32
|
|
|
@@ -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=
|
|
4
|
+
code_loader/contract/datasetclasses.py,sha256=LrhOsNirP3MRKUDQcI6u0r-GeSGHppdSUNzVEAkADeU,16816
|
|
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=
|
|
25
|
-
code_loader/inner_leap_binder/leapbinder_decorators.py,sha256=
|
|
26
|
-
code_loader/leaploader.py,sha256=
|
|
27
|
-
code_loader/leaploaderbase.py,sha256=
|
|
24
|
+
code_loader/inner_leap_binder/leapbinder.py,sha256=J-EjPz49kHqneaFhmgzid0MRokjxYb7kX6XkErxwtlc,58909
|
|
25
|
+
code_loader/inner_leap_binder/leapbinder_decorators.py,sha256=WE6gpZP8ckJp8OQ4CgWbz8BuF9KLwAeQYwDk-ua2jsY,186309
|
|
26
|
+
code_loader/leaploader.py,sha256=8q7JnsE1jx-PJNJN0oKak7i47X9dzenbz8lk-ezbjpw,88293
|
|
27
|
+
code_loader/leaploaderbase.py,sha256=Aa8wCcxojf8EBn_14W5I1gKI4OMBdpdEcNAZ_6E2eV4,10940
|
|
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=
|
|
32
|
+
code_loader/utils.py,sha256=Yj06BbS1UIjN9Cv2eQBjDfdDbpQlABAg6KwD3sB1BZ0,7496
|
|
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.197.
|
|
36
|
-
code_loader-1.0.197.
|
|
37
|
-
code_loader-1.0.197.
|
|
38
|
-
code_loader-1.0.197.
|
|
35
|
+
code_loader-1.0.197.dev2.dist-info/LICENSE,sha256=qIwWjdspQeSMTtnFZBC8MuT-95L02FPvzRUdWFxrwJY,1067
|
|
36
|
+
code_loader-1.0.197.dev2.dist-info/METADATA,sha256=Ep3bwfV6ttp9_4BD5nL8a5zAzu0C3hauxJIkLisVuDU,1095
|
|
37
|
+
code_loader-1.0.197.dev2.dist-info/WHEEL,sha256=sP946D7jFCHeNz5Iq4fL4Lu-PrWrFsgfLXbbkciIZwg,88
|
|
38
|
+
code_loader-1.0.197.dev2.dist-info/RECORD,,
|
|
File without changes
|
|
File without changes
|