code-loader 1.0.193.dev4__py3-none-any.whl → 1.0.193.dev5__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 +64 -51
- code_loader/inner_leap_binder/leapbinder.py +24 -101
- code_loader/inner_leap_binder/leapbinder_decorators.py +260 -895
- code_loader/leaploader.py +255 -203
- code_loader/leaploaderbase.py +3 -67
- code_loader/utils.py +5 -45
- {code_loader-1.0.193.dev4.dist-info → code_loader-1.0.193.dev5.dist-info}/METADATA +1 -1
- {code_loader-1.0.193.dev4.dist-info → code_loader-1.0.193.dev5.dist-info}/RECORD +10 -10
- {code_loader-1.0.193.dev4.dist-info → code_loader-1.0.193.dev5.dist-info}/LICENSE +0 -0
- {code_loader-1.0.193.dev4.dist-info → code_loader-1.0.193.dev5.dist-info}/WHEEL +0 -0
|
@@ -1,6 +1,4 @@
|
|
|
1
1
|
# mypy: ignore-errors
|
|
2
|
-
import copy
|
|
3
|
-
import hashlib
|
|
4
2
|
import inspect
|
|
5
3
|
import os
|
|
6
4
|
import warnings
|
|
@@ -15,8 +13,7 @@ from typing import Optional, Union, Callable, List, Dict, get_args, get_origin,
|
|
|
15
13
|
import numpy as np
|
|
16
14
|
import numpy.typing as npt
|
|
17
15
|
|
|
18
|
-
from code_loader.utils import get_metadata_type_from_variable, map_dict_to_metadata_types
|
|
19
|
-
validate_autoregressive_state_types, autoregressive_nests_equal
|
|
16
|
+
from code_loader.utils import get_metadata_type_from_variable, map_dict_to_metadata_types
|
|
20
17
|
|
|
21
18
|
logger = logging.getLogger(__name__)
|
|
22
19
|
|
|
@@ -29,17 +26,13 @@ from code_loader.contract.enums import MetricDirection, LeapDataType, DatasetMet
|
|
|
29
26
|
from code_loader import leap_binder, LeapLoader
|
|
30
27
|
from code_loader.contract.mapping import NodeMapping, NodeMappingType, NodeConnection
|
|
31
28
|
from code_loader.contract.visualizer_classes import LeapImage, LeapImageMask, LeapTextMask, LeapText, LeapGraph, \
|
|
32
|
-
LeapHorizontalBar, LeapImageWithBBox, LeapImageWithHeatmap, LeapVideo, LeapValidationError
|
|
33
|
-
map_leap_data_type_to_visualizer_class
|
|
29
|
+
LeapHorizontalBar, LeapImageWithBBox, LeapImageWithHeatmap, LeapVideo, LeapValidationError
|
|
34
30
|
from code_loader.inner_leap_binder.leapbinder import mapping_runtime_mode_env_var_mame
|
|
35
31
|
from code_loader.mixpanel_tracker import clear_integration_events, AnalyticsEvent, emit_integration_event_once
|
|
36
32
|
|
|
37
33
|
_called_from_inside_tl_decorator = 0
|
|
38
34
|
_called_from_inside_tl_integration_test_decorator = False
|
|
39
|
-
|
|
40
|
-
# to tell a raise in the test body (report as code-flow failure) from a module-level crash
|
|
41
|
-
# before any test ran (report as a plain script crash).
|
|
42
|
-
_integration_test_started = False
|
|
35
|
+
_mapping_dataset_is_grouped = False
|
|
43
36
|
_call_from_tl_platform = os.environ.get('IS_TENSORLEAP_PLATFORM') == 'true'
|
|
44
37
|
|
|
45
38
|
# ---- warnings store (module-level) ----
|
|
@@ -213,6 +206,50 @@ def batch_warning(result, func_name):
|
|
|
213
206
|
)
|
|
214
207
|
|
|
215
208
|
|
|
209
|
+
def _validate_id_or_group(sample_id, preprocess_response, func_name):
|
|
210
|
+
"""Accept either a single sample id (flat dataset) or a group — a list of ids (grouped
|
|
211
|
+
dataset). The argument shape must match the dataset: a group requires a grouped
|
|
212
|
+
PreprocessResponse and a single id requires a flat one. Every id must match the declared
|
|
213
|
+
sample_id_type."""
|
|
214
|
+
is_group = isinstance(sample_id, list)
|
|
215
|
+
if is_group and not preprocess_response.is_grouped:
|
|
216
|
+
raise AssertionError(
|
|
217
|
+
f'{func_name}() validation failed: got a group (list of sample ids) but the '
|
|
218
|
+
f'PreprocessResponse is not grouped. Pass a single sample id for a flat dataset.')
|
|
219
|
+
if not is_group and preprocess_response.is_grouped:
|
|
220
|
+
raise AssertionError(
|
|
221
|
+
f'{func_name}() validation failed: got a single sample id but the PreprocessResponse '
|
|
222
|
+
f'is grouped. Pass a group (list of sample ids) for a grouped dataset.')
|
|
223
|
+
ids = sample_id if is_group else [sample_id]
|
|
224
|
+
for sid in ids:
|
|
225
|
+
assert type(sid) == preprocess_response.sample_id_type, \
|
|
226
|
+
(f'{func_name}() validation failed: '
|
|
227
|
+
f'Argument sample_id should be as the same type as defined in the preprocess response '
|
|
228
|
+
f'{preprocess_response.sample_id_type}. Got {type(sid)}.')
|
|
229
|
+
|
|
230
|
+
|
|
231
|
+
def _validate_grouped_result(result, group_size, func_name, validate_single):
|
|
232
|
+
"""A grouped (group) encoder result must be either a single ndarray with leading dim
|
|
233
|
+
== group_size, or a list of `group_size` per-sample results (each validated by
|
|
234
|
+
validate_single). Metadata validates its grouped result separately (list only)."""
|
|
235
|
+
if isinstance(result, np.ndarray):
|
|
236
|
+
assert len(result.shape) > 0 and result.shape[0] == group_size, \
|
|
237
|
+
(f'{func_name}() validation failed: expected a grouped output for a group of '
|
|
238
|
+
f'{group_size}: an array with leading dim {group_size}, got shape {result.shape}.')
|
|
239
|
+
validate_single(result)
|
|
240
|
+
elif isinstance(result, list):
|
|
241
|
+
assert len(result) == group_size, \
|
|
242
|
+
(f'{func_name}() validation failed: expected a grouped output for a group of '
|
|
243
|
+
f'{group_size}: a list of {group_size} arrays, got {len(result)}.')
|
|
244
|
+
for r in result:
|
|
245
|
+
validate_single(r)
|
|
246
|
+
else:
|
|
247
|
+
raise AssertionError(
|
|
248
|
+
f'{func_name}() validation failed: expected a grouped output for a group of '
|
|
249
|
+
f'{group_size}: either an array with leading dim {group_size} or a list of '
|
|
250
|
+
f'{group_size} arrays, got {type(result)}.')
|
|
251
|
+
|
|
252
|
+
|
|
216
253
|
def _add_mapping_connection(user_unique_name, connection_destinations, arg_names, name, node_mapping_type):
|
|
217
254
|
connection_destinations = [connection_destination for connection_destination in connection_destinations
|
|
218
255
|
if not isinstance(connection_destination, SamplePreprocessResponse)]
|
|
@@ -290,32 +327,16 @@ def tensorleap_integration_test():
|
|
|
290
327
|
|
|
291
328
|
def _validate_input_args(*args, **kwargs):
|
|
292
329
|
sample_id, preprocess_response = args
|
|
293
|
-
|
|
294
|
-
f"tensorleap_integration_test validation failed: "
|
|
295
|
-
f"sample_id type ({type(sample_id).__name__}) does not match the expected "
|
|
296
|
-
f"type ({preprocess_response.sample_id_type}) from the PreprocessResponse."
|
|
297
|
-
)
|
|
330
|
+
_validate_id_or_group(sample_id, preprocess_response, 'tensorleap_integration_test')
|
|
298
331
|
|
|
299
332
|
def inner(*args, **kwargs):
|
|
300
333
|
if not _call_from_tl_platform:
|
|
301
334
|
set_current('tensorleap_integration_test')
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
# signature the user chose, not the canonical placeholder names.
|
|
305
|
-
expected_names = inspect.getfullargspec(integration_test_function)[0][:2]
|
|
306
|
-
if len(expected_names) < 2:
|
|
307
|
-
expected_names = ['idx', 'preprocess']
|
|
308
|
-
validate_args_structure(*args, types_order=[Union[int, str], PreprocessResponse],
|
|
309
|
-
func_name='integration_test', expected_names=expected_names,
|
|
310
|
-
**kwargs)
|
|
311
|
-
sample_id, preprocess_response = (
|
|
312
|
-
args[i] if i < len(args) else kwargs[name]
|
|
313
|
-
for i, name in enumerate(expected_names))
|
|
314
|
-
args, kwargs = (sample_id, preprocess_response), {}
|
|
335
|
+
validate_args_structure(*args, types_order=[Union[int, str, list], PreprocessResponse],
|
|
336
|
+
func_name='integration_test', expected_names=["idx", "preprocess"], **kwargs)
|
|
315
337
|
_validate_input_args(*args, **kwargs)
|
|
316
338
|
|
|
317
|
-
global _called_from_inside_tl_integration_test_decorator
|
|
318
|
-
_integration_test_started = True
|
|
339
|
+
global _called_from_inside_tl_integration_test_decorator
|
|
319
340
|
# Clear integration test events for new test
|
|
320
341
|
try:
|
|
321
342
|
clear_integration_events()
|
|
@@ -326,12 +347,12 @@ def tensorleap_integration_test():
|
|
|
326
347
|
if not _call_from_tl_platform:
|
|
327
348
|
update_env_params_func("tensorleap_integration_test",
|
|
328
349
|
"v") # put here because otherwise it will become v only if it finishes all the script
|
|
329
|
-
_reset_model_loop_state()
|
|
330
350
|
ret = integration_test_function(*args, **kwargs)
|
|
331
351
|
|
|
352
|
+
global _mapping_dataset_is_grouped
|
|
353
|
+
_mapping_dataset_is_grouped = args[1].is_grouped
|
|
332
354
|
try:
|
|
333
355
|
os.environ[mapping_runtime_mode_env_var_mame] = 'True'
|
|
334
|
-
_reset_model_loop_state()
|
|
335
356
|
integration_test_function(None, PreprocessResponse(state=DataStateType.training, length=0))
|
|
336
357
|
except Exception as e:
|
|
337
358
|
import traceback
|
|
@@ -349,6 +370,7 @@ def tensorleap_integration_test():
|
|
|
349
370
|
f'Integration test is only allowed to call Tensorleap decorators. '
|
|
350
371
|
f'Ensure any arithmetics, external library use, Python logic is placed within Tensorleap decoders')
|
|
351
372
|
finally:
|
|
373
|
+
_mapping_dataset_is_grouped = False
|
|
352
374
|
if mapping_runtime_mode_env_var_mame in os.environ:
|
|
353
375
|
del os.environ[mapping_runtime_mode_env_var_mame]
|
|
354
376
|
finally:
|
|
@@ -444,7 +466,7 @@ def tensorleap_load_model(prediction_types: Optional[List[PredictionTypeHandler]
|
|
|
444
466
|
pass
|
|
445
467
|
|
|
446
468
|
@lru_cache()
|
|
447
|
-
def
|
|
469
|
+
def inner(*args, **kwargs):
|
|
448
470
|
if not _call_from_tl_platform:
|
|
449
471
|
set_current('tensorleap_load_model')
|
|
450
472
|
validate_args_structure(*args, types_order=[],
|
|
@@ -602,32 +624,14 @@ def tensorleap_load_model(prediction_types: Optional[List[PredictionTypeHandler]
|
|
|
602
624
|
update_env_params_func("tensorleap_load_model", "v")
|
|
603
625
|
return model_placeholder
|
|
604
626
|
|
|
605
|
-
def inner(*args, **kwargs):
|
|
606
|
-
model_placeholder = _cached_inner(*args, **kwargs)
|
|
607
|
-
# Re-assert on every call, outside the cache: each mapping rerun's load_model
|
|
608
|
-
# overwrites this global with its mapping placeholder, and on a cache hit the
|
|
609
|
-
# body above never runs — a second integration test would otherwise fail the
|
|
610
|
-
# model loop's identity check against the stale mapping placeholder.
|
|
611
|
-
global _last_loaded_model
|
|
612
|
-
_last_loaded_model = model_placeholder
|
|
613
|
-
return model_placeholder
|
|
614
|
-
|
|
615
627
|
def mapping_inner():
|
|
616
628
|
class ModelOutputPlaceholder:
|
|
617
629
|
def __init__(self):
|
|
618
630
|
self.node_mapping = NodeMapping('', NodeMappingType.Prediction0)
|
|
619
631
|
|
|
620
632
|
def __getitem__(self, key):
|
|
621
|
-
assert isinstance(key,
|
|
622
|
-
f'Expected key to be an int
|
|
623
|
-
if isinstance(key, str):
|
|
624
|
-
declared_names = [prediction_type.name for prediction_type
|
|
625
|
-
in leap_binder.setup_container.prediction_types]
|
|
626
|
-
if key not in declared_names:
|
|
627
|
-
raise Exception(
|
|
628
|
-
f'Unknown prediction name "{key}" — declared prediction types: '
|
|
629
|
-
f'{declared_names}.')
|
|
630
|
-
key = declared_names.index(key)
|
|
633
|
+
assert isinstance(key, int), \
|
|
634
|
+
f'Expected key to be an int, got {type(key)} instead.'
|
|
631
635
|
|
|
632
636
|
ret = TempMapping()
|
|
633
637
|
try:
|
|
@@ -681,10 +685,7 @@ def tensorleap_load_model(prediction_types: Optional[List[PredictionTypeHandler]
|
|
|
681
685
|
|
|
682
686
|
return FollowInputIndex()
|
|
683
687
|
|
|
684
|
-
|
|
685
|
-
global _last_loaded_model
|
|
686
|
-
_last_loaded_model = model_placeholder
|
|
687
|
-
return model_placeholder
|
|
688
|
+
return ModelPlaceholder()
|
|
688
689
|
|
|
689
690
|
def final_inner(*args, **kwargs):
|
|
690
691
|
if os.environ.get(mapping_runtime_mode_env_var_mame):
|
|
@@ -1561,13 +1562,10 @@ def tensorleap_metadata(
|
|
|
1561
1562
|
raise Exception(f'Metadata with name {name} already exists. '
|
|
1562
1563
|
f'Please choose another')
|
|
1563
1564
|
|
|
1564
|
-
def _validate_input_args(sample_id: Union[int, str], preprocess_response: PreprocessResponse):
|
|
1565
|
-
|
|
1566
|
-
(f'{user_function.__name__}() validation failed: '
|
|
1567
|
-
f'Argument sample_id should be as the same type as defined in the preprocess response '
|
|
1568
|
-
f'{preprocess_response.sample_id_type}. Got {type(sample_id)}.')
|
|
1565
|
+
def _validate_input_args(sample_id: Union[int, str, list], preprocess_response: PreprocessResponse):
|
|
1566
|
+
_validate_id_or_group(sample_id, preprocess_response, user_function.__name__)
|
|
1569
1567
|
|
|
1570
|
-
def
|
|
1568
|
+
def _validate_single(result):
|
|
1571
1569
|
supported_result_types = (type(None), int, str, bool, float, dict, np.floating,
|
|
1572
1570
|
np.bool_, np.unsignedinteger, np.signedinteger, np.integer)
|
|
1573
1571
|
if isinstance(result, tuple):
|
|
@@ -1585,6 +1583,20 @@ def tensorleap_metadata(
|
|
|
1585
1583
|
(f'{user_function.__name__}() validation failed: '
|
|
1586
1584
|
f'Values in the return dict should be of type {str(supported_result_types)}. Got {type(value)}.')
|
|
1587
1585
|
|
|
1586
|
+
def _validate_result(result, grouped=False, group_size=None):
|
|
1587
|
+
if not grouped:
|
|
1588
|
+
_validate_single(result)
|
|
1589
|
+
return
|
|
1590
|
+
# Grouped metadata: a list of `group_size` per-sample scalars/dicts.
|
|
1591
|
+
assert isinstance(result, list), \
|
|
1592
|
+
(f'{user_function.__name__}() validation failed: expected a grouped output for a group '
|
|
1593
|
+
f'of {group_size}: a list of {group_size} metadata values, got {type(result)}.')
|
|
1594
|
+
assert len(result) == group_size, \
|
|
1595
|
+
(f'{user_function.__name__}() validation failed: expected a grouped output for a group '
|
|
1596
|
+
f'of {group_size}: a list of {group_size} metadata values, got {len(result)}.')
|
|
1597
|
+
for value in result:
|
|
1598
|
+
_validate_single(value)
|
|
1599
|
+
|
|
1588
1600
|
def inner_without_validate(sample_id, preprocess_response):
|
|
1589
1601
|
|
|
1590
1602
|
global _called_from_inside_tl_decorator
|
|
@@ -1604,14 +1616,16 @@ def tensorleap_metadata(
|
|
|
1604
1616
|
set_current('tensorleap_metadata')
|
|
1605
1617
|
if os.environ.get(mapping_runtime_mode_env_var_mame):
|
|
1606
1618
|
return None
|
|
1607
|
-
validate_args_structure(*args, types_order=[Union[int, str], PreprocessResponse],
|
|
1619
|
+
validate_args_structure(*args, types_order=[Union[int, str, list], PreprocessResponse],
|
|
1608
1620
|
func_name=user_function.__name__, expected_names=["idx", "preprocess"], **kwargs)
|
|
1609
1621
|
sample_id, preprocess_response = args if len(args) != 0 else kwargs.values()
|
|
1610
1622
|
_validate_input_args(sample_id, preprocess_response)
|
|
1611
1623
|
|
|
1624
|
+
grouped = isinstance(sample_id, list)
|
|
1625
|
+
group_size = len(sample_id) if grouped else None
|
|
1612
1626
|
result = inner_without_validate(sample_id, preprocess_response)
|
|
1613
1627
|
|
|
1614
|
-
_validate_result(result)
|
|
1628
|
+
_validate_result(result, grouped, group_size)
|
|
1615
1629
|
if not _call_from_tl_platform:
|
|
1616
1630
|
update_env_params_func("tensorleap_metadata", "v")
|
|
1617
1631
|
return result
|
|
@@ -1623,35 +1637,50 @@ def tensorleap_metadata(
|
|
|
1623
1637
|
|
|
1624
1638
|
def tensorleap_custom_latent_space():
|
|
1625
1639
|
def decorating_function(user_function: SectionCallableInterface):
|
|
1626
|
-
def _validate_input_args(sample_id: Union[int, str], preprocess_response: PreprocessResponse):
|
|
1627
|
-
|
|
1628
|
-
(f'tensorleap_custom_latent_space validation failed: '
|
|
1629
|
-
f'Argument sample_id should be either int or str. Got {type(sample_id)}.')
|
|
1630
|
-
assert isinstance(preprocess_response, PreprocessResponse), \
|
|
1631
|
-
(f'tensorleap_custom_latent_space validation failed: '
|
|
1632
|
-
f'Argument preprocess_response should be a PreprocessResponse. Got {type(preprocess_response)}.')
|
|
1633
|
-
assert type(sample_id) == preprocess_response.sample_id_type, \
|
|
1634
|
-
(f'tensorleap_custom_latent_space validation failed: '
|
|
1635
|
-
f'Argument sample_id should be as the same type as defined in the preprocess response '
|
|
1636
|
-
f'{preprocess_response.sample_id_type}. Got {type(sample_id)}.')
|
|
1640
|
+
def _validate_input_args(sample_id: Union[int, str, list], preprocess_response: PreprocessResponse):
|
|
1641
|
+
_validate_id_or_group(sample_id, preprocess_response, 'tensorleap_custom_latent_space')
|
|
1637
1642
|
|
|
1638
|
-
def
|
|
1639
|
-
assert isinstance(
|
|
1643
|
+
def _validate_single(single_result):
|
|
1644
|
+
assert isinstance(single_result, np.ndarray), \
|
|
1640
1645
|
(f'tensorleap_custom_latent_space validation failed: '
|
|
1641
|
-
f'The return type should be a numpy array. Got {type(
|
|
1642
|
-
if
|
|
1643
|
-
flat_dim = int(np.prod(
|
|
1646
|
+
f'The return type should be a numpy array. Got {type(single_result)}.')
|
|
1647
|
+
if single_result.ndim > 1:
|
|
1648
|
+
flat_dim = int(np.prod(single_result.shape))
|
|
1644
1649
|
store_general_warning(
|
|
1645
|
-
key=("tensorleap_custom_latent_space_flatten", tuple(
|
|
1650
|
+
key=("tensorleap_custom_latent_space_flatten", tuple(single_result.shape)),
|
|
1646
1651
|
message=(
|
|
1647
|
-
f"tensorleap_custom_latent_space returned per-sample shape {tuple(
|
|
1648
|
-
f"(ndim={
|
|
1652
|
+
f"tensorleap_custom_latent_space returned per-sample shape {tuple(single_result.shape)} "
|
|
1653
|
+
f"(ndim={single_result.ndim}). Tensorleap assumes per-sample shape (d, ...) and will "
|
|
1649
1654
|
f"flatten to ({flat_dim},) before downstream visualization and clustering. "
|
|
1650
1655
|
f"If you want a different aggregation (e.g. global average pooling), do it "
|
|
1651
1656
|
f"inside your function."
|
|
1652
1657
|
),
|
|
1653
1658
|
)
|
|
1654
1659
|
|
|
1660
|
+
def _validate_result(result, grouped=False, group_size=None):
|
|
1661
|
+
if not grouped:
|
|
1662
|
+
_validate_single(result)
|
|
1663
|
+
return
|
|
1664
|
+
# Grouped: (B, *) array or list of B per-sample arrays. Validate each per-sample slice so
|
|
1665
|
+
# the per-sample flatten check/warning uses the sample shape, not the (B, *) batch shape.
|
|
1666
|
+
if isinstance(result, np.ndarray):
|
|
1667
|
+
assert result.ndim >= 1 and result.shape[0] == group_size, \
|
|
1668
|
+
(f'tensorleap_custom_latent_space validation failed: expected a grouped output for a '
|
|
1669
|
+
f'group of {group_size}: an array with leading dim {group_size}, got shape {result.shape}.')
|
|
1670
|
+
for single in result:
|
|
1671
|
+
_validate_single(single)
|
|
1672
|
+
elif isinstance(result, list):
|
|
1673
|
+
assert len(result) == group_size, \
|
|
1674
|
+
(f'tensorleap_custom_latent_space validation failed: expected a grouped output for a '
|
|
1675
|
+
f'group of {group_size}: a list of {group_size} arrays, got {len(result)}.')
|
|
1676
|
+
for single in result:
|
|
1677
|
+
_validate_single(single)
|
|
1678
|
+
else:
|
|
1679
|
+
raise AssertionError(
|
|
1680
|
+
f'tensorleap_custom_latent_space validation failed: expected a grouped output for a '
|
|
1681
|
+
f'group of {group_size}: either an array with leading dim {group_size} or a list of '
|
|
1682
|
+
f'{group_size} arrays, got {type(result)}.')
|
|
1683
|
+
|
|
1655
1684
|
def inner_without_validate(sample_id, preprocess_response):
|
|
1656
1685
|
global _called_from_inside_tl_decorator
|
|
1657
1686
|
_called_from_inside_tl_decorator += 1
|
|
@@ -1671,9 +1700,11 @@ def tensorleap_custom_latent_space():
|
|
|
1671
1700
|
|
|
1672
1701
|
_validate_input_args(sample_id, preprocess_response)
|
|
1673
1702
|
|
|
1703
|
+
grouped = isinstance(sample_id, list)
|
|
1704
|
+
group_size = len(sample_id) if grouped else None
|
|
1674
1705
|
result = inner_without_validate(sample_id, preprocess_response)
|
|
1675
1706
|
|
|
1676
|
-
_validate_result(result)
|
|
1707
|
+
_validate_result(result, grouped, group_size)
|
|
1677
1708
|
return result
|
|
1678
1709
|
|
|
1679
1710
|
return inner
|
|
@@ -1681,260 +1712,18 @@ def tensorleap_custom_latent_space():
|
|
|
1681
1712
|
return decorating_function
|
|
1682
1713
|
|
|
1683
1714
|
|
|
1684
|
-
_MODEL_LOOP_MAX_STEPS = 1000
|
|
1685
|
-
|
|
1686
|
-
_active_model_loop = None
|
|
1687
|
-
_model_loop_run_in_current_test = False
|
|
1688
|
-
_last_loaded_model = None
|
|
1689
|
-
_chain_artifact_provenance: Dict[int, tuple] = {}
|
|
1690
|
-
|
|
1691
|
-
_validate_state_types = validate_autoregressive_state_types
|
|
1692
|
-
_nest_equal = autoregressive_nests_equal
|
|
1693
|
-
|
|
1694
|
-
|
|
1695
|
-
def _nest_fingerprint(value):
|
|
1696
|
-
digest = hashlib.sha1()
|
|
1697
|
-
|
|
1698
|
-
def _feed(item):
|
|
1699
|
-
if isinstance(item, dict):
|
|
1700
|
-
digest.update(b'{')
|
|
1701
|
-
for key in sorted(item):
|
|
1702
|
-
digest.update(str(key).encode())
|
|
1703
|
-
digest.update(b':')
|
|
1704
|
-
_feed(item[key])
|
|
1705
|
-
digest.update(b'}')
|
|
1706
|
-
elif isinstance(item, (list, tuple)):
|
|
1707
|
-
digest.update(b'[')
|
|
1708
|
-
for element in item:
|
|
1709
|
-
_feed(element)
|
|
1710
|
-
digest.update(b']')
|
|
1711
|
-
elif isinstance(item, np.ndarray):
|
|
1712
|
-
digest.update(str(item.dtype).encode())
|
|
1713
|
-
digest.update(str(item.shape).encode())
|
|
1714
|
-
digest.update(np.ascontiguousarray(item).tobytes())
|
|
1715
|
-
else:
|
|
1716
|
-
digest.update(repr(item).encode())
|
|
1717
|
-
|
|
1718
|
-
_feed(value)
|
|
1719
|
-
return digest.hexdigest()
|
|
1720
|
-
|
|
1721
|
-
|
|
1722
|
-
def _ndarray_leaves(value):
|
|
1723
|
-
if isinstance(value, np.ndarray):
|
|
1724
|
-
return [value]
|
|
1725
|
-
if isinstance(value, dict):
|
|
1726
|
-
return [leaf for item in value.values() for leaf in _ndarray_leaves(item)]
|
|
1727
|
-
if isinstance(value, (list, tuple)):
|
|
1728
|
-
return [leaf for item in value for leaf in _ndarray_leaves(item)]
|
|
1729
|
-
return []
|
|
1730
|
-
|
|
1731
|
-
|
|
1732
|
-
def _squeeze_leading_batch_axis(tensors_dict):
|
|
1733
|
-
squeezed = {}
|
|
1734
|
-
for key, value in tensors_dict.items():
|
|
1735
|
-
if isinstance(value, np.ndarray) and value.ndim > 0 and value.shape[0] == 1:
|
|
1736
|
-
squeezed[key] = value[0]
|
|
1737
|
-
else:
|
|
1738
|
-
squeezed[key] = value
|
|
1739
|
-
return squeezed
|
|
1740
|
-
|
|
1741
|
-
|
|
1742
|
-
def _register_chain_artifact(obj, kind):
|
|
1743
|
-
# The object reference is kept in the registry entry so its id() cannot be recycled by the GC.
|
|
1744
|
-
_chain_artifact_provenance[id(obj)] = (kind, _nest_fingerprint(obj), obj)
|
|
1745
|
-
|
|
1746
|
-
|
|
1747
|
-
def _lookup_chain_artifact(obj):
|
|
1748
|
-
entry = _chain_artifact_provenance.get(id(obj))
|
|
1749
|
-
if entry is None or entry[2] is not obj:
|
|
1750
|
-
return None
|
|
1751
|
-
return entry
|
|
1752
|
-
|
|
1753
|
-
|
|
1754
|
-
def _reset_model_loop_state():
|
|
1755
|
-
global _active_model_loop, _model_loop_run_in_current_test
|
|
1756
|
-
_active_model_loop = None
|
|
1757
|
-
_model_loop_run_in_current_test = False
|
|
1758
|
-
_chain_artifact_provenance.clear()
|
|
1759
|
-
|
|
1760
|
-
|
|
1761
|
-
class _MappingStatePlaceholder:
|
|
1762
|
-
def _unsupported(self):
|
|
1763
|
-
raise LeapValidationError(
|
|
1764
|
-
'The autoregressive state is opaque inside the integration test — pass it whole '
|
|
1765
|
-
'between the hook, the model loop and the autoregressive metrics. Read or derive '
|
|
1766
|
-
'state content inside the hook or inside the metric/visualizer body.')
|
|
1767
|
-
|
|
1768
|
-
def __getitem__(self, key):
|
|
1769
|
-
self._unsupported()
|
|
1770
|
-
|
|
1771
|
-
def __getattr__(self, name):
|
|
1772
|
-
raise AttributeError(name)
|
|
1773
|
-
|
|
1774
|
-
def __iter__(self):
|
|
1775
|
-
self._unsupported()
|
|
1776
|
-
|
|
1777
|
-
|
|
1778
|
-
class _ModelLoopContext:
|
|
1779
|
-
def __init__(self, sample_id, preprocess_response, prediction_names, is_mapping):
|
|
1780
|
-
self.sample_id = sample_id
|
|
1781
|
-
self.preprocess_response = preprocess_response
|
|
1782
|
-
self.prediction_names = prediction_names
|
|
1783
|
-
self.is_mapping = is_mapping
|
|
1784
|
-
self.phase = 'awaiting_first_hook'
|
|
1785
|
-
self.steps = 0
|
|
1786
|
-
self.fed_inputs = None
|
|
1787
|
-
self.fed_inputs_fingerprint = None
|
|
1788
|
-
self.last_outputs = None
|
|
1789
|
-
self.last_outputs_fingerprint = None
|
|
1790
|
-
self.last_state = None
|
|
1791
|
-
self.last_state_fingerprint = None
|
|
1792
|
-
|
|
1793
|
-
def _fail(self, message):
|
|
1794
|
-
raise LeapValidationError(f'tensorleap_model_loop validation failed: {message}')
|
|
1795
|
-
|
|
1796
|
-
def on_hook_call(self, sample_id, prev_inputs, prev_outputs, state, preprocess_response):
|
|
1797
|
-
if self.phase == 'ended':
|
|
1798
|
-
self._fail('the chain already ended — once the autoregressive step returns None for '
|
|
1799
|
-
'next_inputs the loop must exit without further hook or model calls.')
|
|
1800
|
-
if self.phase == 'awaiting_model':
|
|
1801
|
-
self._fail('two autoregressive step calls in a row — the model must run exactly once '
|
|
1802
|
-
'between hook calls.')
|
|
1803
|
-
if sample_id != self.sample_id:
|
|
1804
|
-
self._fail('the hook must be called with the sample_id the model loop received.')
|
|
1805
|
-
if preprocess_response is not self.preprocess_response:
|
|
1806
|
-
self._fail('the hook must be called with the preprocess response the model loop '
|
|
1807
|
-
'received.')
|
|
1808
|
-
if self.phase == 'awaiting_first_hook':
|
|
1809
|
-
if prev_inputs is not None or prev_outputs is not None or state is not None:
|
|
1810
|
-
self._fail('the first hook call inside the model loop must be the initial call: '
|
|
1811
|
-
'prev_inputs=None, prev_outputs=None, state=None.')
|
|
1812
|
-
return
|
|
1813
|
-
if prev_inputs is not self.fed_inputs:
|
|
1814
|
-
self._fail('prev_inputs must be exactly the inputs dict the previous hook call '
|
|
1815
|
-
'returned (the one fed to the model) — do not copy, rebuild or slice it.')
|
|
1816
|
-
if prev_outputs is not self.last_outputs:
|
|
1817
|
-
self._fail('prev_outputs must be exactly the outputs dict the model call returned — '
|
|
1818
|
-
'do not copy, rebuild or slice it.')
|
|
1819
|
-
if state is not self.last_state:
|
|
1820
|
-
self._fail('state must be exactly the state object the previous hook call returned.')
|
|
1821
|
-
if _nest_fingerprint(prev_inputs) != self.fed_inputs_fingerprint:
|
|
1822
|
-
self._fail('the model inputs were mutated in place after being fed to the model — '
|
|
1823
|
-
'the platform re-reads the stored step tensors, so local mutations diverge '
|
|
1824
|
-
'from platform results. Compute new tensors inside the hook instead.')
|
|
1825
|
-
if _nest_fingerprint(prev_outputs) != self.last_outputs_fingerprint:
|
|
1826
|
-
self._fail('the model outputs were mutated in place after the model call — the '
|
|
1827
|
-
'platform re-reads the stored step tensors, so local mutations diverge '
|
|
1828
|
-
'from platform results. Compute derived tensors inside the hook instead.')
|
|
1829
|
-
if _nest_fingerprint(state) != self.last_state_fingerprint:
|
|
1830
|
-
self._fail('the state object was mutated between hook calls — state may only change '
|
|
1831
|
-
'inside the hook body.')
|
|
1832
|
-
|
|
1833
|
-
def on_hook_return(self, next_inputs, state):
|
|
1834
|
-
self.steps += 1
|
|
1835
|
-
if self.steps > _MODEL_LOOP_MAX_STEPS:
|
|
1836
|
-
self._fail(f'the chain exceeded {_MODEL_LOOP_MAX_STEPS} steps without the '
|
|
1837
|
-
f'autoregressive step returning None — the loop would never terminate on '
|
|
1838
|
-
f'the platform.')
|
|
1839
|
-
self.last_state = state
|
|
1840
|
-
self.last_state_fingerprint = _nest_fingerprint(state)
|
|
1841
|
-
if next_inputs is None:
|
|
1842
|
-
self.phase = 'ended'
|
|
1843
|
-
else:
|
|
1844
|
-
self.fed_inputs = next_inputs
|
|
1845
|
-
self.fed_inputs_fingerprint = _nest_fingerprint(next_inputs)
|
|
1846
|
-
self.phase = 'awaiting_model'
|
|
1847
|
-
|
|
1848
|
-
def mapping_hook_call(self, inputs_placeholder, state_placeholder):
|
|
1849
|
-
if self.phase == 'ended':
|
|
1850
|
-
self._fail('the chain already ended — once the autoregressive step returns None for '
|
|
1851
|
-
'next_inputs the loop must exit without further hook or model calls.')
|
|
1852
|
-
if self.phase == 'awaiting_model':
|
|
1853
|
-
self._fail('two autoregressive step calls in a row — the model must run exactly once '
|
|
1854
|
-
'between hook calls.')
|
|
1855
|
-
self.last_state = state_placeholder
|
|
1856
|
-
if self.phase == 'awaiting_first_hook':
|
|
1857
|
-
self.fed_inputs = inputs_placeholder
|
|
1858
|
-
self.phase = 'awaiting_model'
|
|
1859
|
-
return inputs_placeholder, state_placeholder
|
|
1860
|
-
self.phase = 'ended'
|
|
1861
|
-
return None, state_placeholder
|
|
1862
|
-
|
|
1863
|
-
def on_model_call(self, fed):
|
|
1864
|
-
if self.phase == 'awaiting_first_hook':
|
|
1865
|
-
self._fail('the model was called before the autoregressive step produced the initial '
|
|
1866
|
-
'inputs — the loop must start with the initial hook call '
|
|
1867
|
-
'(prev_inputs=None, prev_outputs=None, state=None).')
|
|
1868
|
-
if self.phase == 'awaiting_hook':
|
|
1869
|
-
self._fail('the model was called twice in a row — the autoregressive step must run '
|
|
1870
|
-
'between model calls.')
|
|
1871
|
-
if self.phase == 'ended':
|
|
1872
|
-
self._fail('the chain already ended — once the autoregressive step returns None for '
|
|
1873
|
-
'next_inputs the loop must exit without further hook or model calls.')
|
|
1874
|
-
if self.is_mapping:
|
|
1875
|
-
return
|
|
1876
|
-
fed_ids = {id(leaf) for leaf in _ndarray_leaves(fed)}
|
|
1877
|
-
hook_ids = {id(value) for value in self.fed_inputs.values()}
|
|
1878
|
-
if fed_ids != hook_ids:
|
|
1879
|
-
self._fail('the model must be fed exactly the tensors the last hook call returned — '
|
|
1880
|
-
'any computation between the hook and the model is invisible to the '
|
|
1881
|
-
'platform. Move it into the hook.')
|
|
1882
|
-
if _nest_fingerprint(self.fed_inputs) != self.fed_inputs_fingerprint:
|
|
1883
|
-
self._fail('the model inputs were mutated in place after the hook returned them — '
|
|
1884
|
-
'the platform feeds the model the tensors exactly as the hook returned '
|
|
1885
|
-
'them. Compute new tensors inside the hook instead.')
|
|
1886
|
-
|
|
1887
|
-
def on_model_return(self, raw_outputs):
|
|
1888
|
-
self.phase = 'awaiting_hook'
|
|
1889
|
-
if self.is_mapping:
|
|
1890
|
-
self.last_outputs = raw_outputs
|
|
1891
|
-
return raw_outputs
|
|
1892
|
-
outputs_list = raw_outputs if isinstance(raw_outputs, list) else [raw_outputs]
|
|
1893
|
-
if len(outputs_list) != len(self.prediction_names):
|
|
1894
|
-
self._fail(f'the model returned {len(outputs_list)} outputs but '
|
|
1895
|
-
f'{len(self.prediction_names)} prediction types are declared on '
|
|
1896
|
-
f'tensorleap_load_model — declare one prediction type per model output.')
|
|
1897
|
-
named_outputs = {name: np.asarray(output)
|
|
1898
|
-
for name, output in zip(self.prediction_names, outputs_list)}
|
|
1899
|
-
self.last_outputs = named_outputs
|
|
1900
|
-
self.last_outputs_fingerprint = _nest_fingerprint(named_outputs)
|
|
1901
|
-
return named_outputs
|
|
1902
|
-
|
|
1903
|
-
|
|
1904
|
-
class _ModelLoopModelProxy:
|
|
1905
|
-
def __init__(self, model, context):
|
|
1906
|
-
self._model = model
|
|
1907
|
-
self._context = context
|
|
1908
|
-
|
|
1909
|
-
def __call__(self, inputs):
|
|
1910
|
-
self._context.on_model_call(inputs)
|
|
1911
|
-
return self._context.on_model_return(self._model(inputs))
|
|
1912
|
-
|
|
1913
|
-
def run(self, output_names, input_dict):
|
|
1914
|
-
self._context.on_model_call(input_dict)
|
|
1915
|
-
return self._context.on_model_return(self._model.run(output_names, input_dict))
|
|
1916
|
-
|
|
1917
|
-
def get_inputs(self):
|
|
1918
|
-
return self._model.get_inputs()
|
|
1919
|
-
|
|
1920
|
-
|
|
1921
1715
|
def tensorleap_autoregressive_step(latent_space_aggregation: str = 'last_step'):
|
|
1922
1716
|
"""The feedback hook that drives an autoregressive chain.
|
|
1923
1717
|
|
|
1924
1718
|
Signature of the decorated function:
|
|
1925
|
-
(sample_id, prev_inputs, prev_outputs,
|
|
1926
|
-
|
|
1927
|
-
|
|
1928
|
-
|
|
1929
|
-
|
|
1930
|
-
|
|
1931
|
-
|
|
1932
|
-
the
|
|
1933
|
-
returns state, so the final step participates in state aggregation. State is never fed to
|
|
1934
|
-
the model; it may be any nest of dicts/lists holding numpy arrays, numbers, strings or bools,
|
|
1935
|
-
and it rides the platform queue on every step — accumulate reductions, not raw per-step
|
|
1936
|
-
tensors. The hook must be stateless and deterministically seeded (seed stochasticity from
|
|
1937
|
-
sample_id) — the engine replays steps after crash recovery and relies on identical results.
|
|
1719
|
+
(sample_id, prev_inputs, prev_outputs, preprocess) -> Optional[Dict[str, np.ndarray]]
|
|
1720
|
+
|
|
1721
|
+
First call per chain: prev_inputs=None, prev_outputs=None — returns the initial model inputs.
|
|
1722
|
+
Later calls receive the previous step's model inputs and outputs (unbatched, per-sample) and
|
|
1723
|
+
return the next step's full input dict; returning None ends the chain. Keys starting with '_'
|
|
1724
|
+
are passthrough state: never fed to the model, handed back verbatim in prev_inputs next call.
|
|
1725
|
+
The hook must be stateless and deterministically seeded (seed stochasticity from sample_id) —
|
|
1726
|
+
the engine replays steps after crash recovery and relies on identical results.
|
|
1938
1727
|
|
|
1939
1728
|
latent_space_aggregation ('last_step' | 'mean') declares how the chain's latent-space
|
|
1940
1729
|
vectors are derived from its per-step forward passes. 'last_step' (default): every latent
|
|
@@ -1953,95 +1742,62 @@ def tensorleap_autoregressive_step(latent_space_aggregation: str = 'last_step'):
|
|
|
1953
1742
|
first_call_record: Dict[str, Any] = {'keys': None, 'shapes': None}
|
|
1954
1743
|
|
|
1955
1744
|
def _normalize_args(args, kwargs):
|
|
1956
|
-
expected_names = ["sample_id", "prev_inputs", "prev_outputs", "
|
|
1745
|
+
expected_names = ["sample_id", "prev_inputs", "prev_outputs", "preprocess"]
|
|
1746
|
+
validate_args_structure(
|
|
1747
|
+
*args,
|
|
1748
|
+
types_order=[Union[int, str], Union[dict, type(None)], Union[dict, type(None)],
|
|
1749
|
+
PreprocessResponse],
|
|
1750
|
+
func_name=user_function.__name__, expected_names=expected_names, **kwargs)
|
|
1957
1751
|
normalized = []
|
|
1958
|
-
for i,
|
|
1959
|
-
if i < len(args)
|
|
1960
|
-
normalized.append(args[i])
|
|
1961
|
-
elif arg_name in kwargs:
|
|
1962
|
-
normalized.append(kwargs[arg_name])
|
|
1963
|
-
else:
|
|
1964
|
-
raise AssertionError(
|
|
1965
|
-
f'{user_function.__name__} validation failed: missing required argument '
|
|
1966
|
-
f"'{arg_name}'. Expected arguments: {expected_names}.")
|
|
1967
|
-
if len(args) + len(kwargs) != len(expected_names):
|
|
1968
|
-
raise AssertionError(
|
|
1969
|
-
f'{user_function.__name__} validation failed: expected exactly '
|
|
1970
|
-
f'{len(expected_names)} arguments {expected_names}, got '
|
|
1971
|
-
f'{len(args) + len(kwargs)}.')
|
|
1972
|
-
sample_id, prev_inputs, prev_outputs, state, preprocess_response = normalized
|
|
1973
|
-
assert isinstance(sample_id, (int, str)), \
|
|
1974
|
-
(f'{user_function.__name__}() validation failed: '
|
|
1975
|
-
f'sample_id must be an int or str. Got {type(sample_id).__name__}.')
|
|
1976
|
-
assert prev_inputs is None or isinstance(prev_inputs, dict), \
|
|
1977
|
-
(f'{user_function.__name__}() validation failed: '
|
|
1978
|
-
f'prev_inputs must be a dict or None. Got {type(prev_inputs).__name__}.')
|
|
1979
|
-
assert prev_outputs is None or isinstance(prev_outputs, dict), \
|
|
1980
|
-
(f'{user_function.__name__}() validation failed: '
|
|
1981
|
-
f'prev_outputs must be a dict or None. Got {type(prev_outputs).__name__}.')
|
|
1982
|
-
assert isinstance(preprocess_response, PreprocessResponse), \
|
|
1983
|
-
(f'{user_function.__name__}() validation failed: '
|
|
1984
|
-
f'preprocess must be a PreprocessResponse. Got '
|
|
1985
|
-
f'{type(preprocess_response).__name__}.')
|
|
1752
|
+
for i, name in enumerate(expected_names):
|
|
1753
|
+
normalized.append(args[i] if i < len(args) else kwargs[name])
|
|
1986
1754
|
return normalized
|
|
1987
1755
|
|
|
1988
|
-
def _validate_input_args(sample_id, prev_inputs, prev_outputs,
|
|
1756
|
+
def _validate_input_args(sample_id, prev_inputs, prev_outputs, preprocess_response):
|
|
1989
1757
|
assert (prev_inputs is None) == (prev_outputs is None), \
|
|
1990
1758
|
(f'{user_function.__name__}() validation failed: '
|
|
1991
1759
|
f'prev_inputs and prev_outputs must both be None (first call) or both be dicts '
|
|
1992
1760
|
f'(subsequent calls). Got prev_inputs={type(prev_inputs).__name__}, '
|
|
1993
1761
|
f'prev_outputs={type(prev_outputs).__name__}.')
|
|
1994
|
-
assert not (prev_inputs is None and state is not None), \
|
|
1995
|
-
(f'{user_function.__name__}() validation failed: '
|
|
1996
|
-
f'the first call (prev_inputs=None, prev_outputs=None) must pass state=None. '
|
|
1997
|
-
f'Got state of type {type(state).__name__}.')
|
|
1998
1762
|
assert type(sample_id) == preprocess_response.sample_id_type, \
|
|
1999
1763
|
(f'{user_function.__name__}() validation failed: '
|
|
2000
1764
|
f'Argument sample_id should be as the same type as defined in the preprocess response '
|
|
2001
1765
|
f'{preprocess_response.sample_id_type}. Got {type(sample_id)}.')
|
|
2002
1766
|
|
|
2003
1767
|
def _validate_result(result, is_first_call):
|
|
2004
|
-
|
|
2005
|
-
(f'{user_function.__name__}() validation failed: '
|
|
2006
|
-
f'the hook must return a (next_inputs, state) tuple — next_inputs is a dict of '
|
|
2007
|
-
f'named model input tensors, or None to end the chain; state rides to the next '
|
|
2008
|
-
f'call. Got {type(result).__name__}.')
|
|
2009
|
-
next_inputs, state = result
|
|
2010
|
-
if next_inputs is None:
|
|
1768
|
+
if result is None:
|
|
2011
1769
|
assert not is_first_call, \
|
|
2012
1770
|
(f'{user_function.__name__}() validation failed: '
|
|
2013
|
-
f'the first call (prev_inputs=None, prev_outputs=None
|
|
2014
|
-
f'
|
|
2015
|
-
f'produce an empty chain.')
|
|
1771
|
+
f'the first call (prev_inputs=None, prev_outputs=None) must return the initial '
|
|
1772
|
+
f'model inputs dict — returning None on the first call would produce an empty chain.')
|
|
2016
1773
|
return
|
|
2017
|
-
assert isinstance(
|
|
1774
|
+
assert isinstance(result, dict), \
|
|
2018
1775
|
(f'{user_function.__name__}() validation failed: '
|
|
2019
|
-
f'
|
|
2020
|
-
f'chain. Got {type(
|
|
2021
|
-
|
|
1776
|
+
f'the return value must be a dict of named model input tensors, or None to end the '
|
|
1777
|
+
f'chain. Got {type(result)}.')
|
|
1778
|
+
model_input_keys = [key for key in result if not key.startswith('_')]
|
|
1779
|
+
assert model_input_keys, \
|
|
2022
1780
|
(f'{user_function.__name__}() validation failed: '
|
|
2023
|
-
f'
|
|
2024
|
-
f'
|
|
2025
|
-
for key, value in
|
|
1781
|
+
f'the returned dict contains no model inputs — every key starts with "_" '
|
|
1782
|
+
f'(underscore keys are passthrough state, never fed to the model).')
|
|
1783
|
+
for key, value in result.items():
|
|
2026
1784
|
assert isinstance(key, str), \
|
|
2027
1785
|
(f'{user_function.__name__}() validation failed: '
|
|
2028
|
-
f'all keys in
|
|
2029
|
-
|
|
2030
|
-
|
|
2031
|
-
f'next_inputs key "{key}" starts with "_" — underscore passthrough keys were '
|
|
2032
|
-
f'replaced by the explicit state channel. Move passthrough values into the '
|
|
2033
|
-
f'returned state.')
|
|
1786
|
+
f'all keys in the returned dict must be strings. Got {type(key)}.')
|
|
1787
|
+
if key.startswith('_'):
|
|
1788
|
+
continue
|
|
2034
1789
|
assert isinstance(value, np.ndarray), \
|
|
2035
1790
|
(f'{user_function.__name__}() validation failed: '
|
|
2036
1791
|
f'value for model input "{key}" must be a numpy array. Got {type(value)}.')
|
|
2037
1792
|
|
|
2038
|
-
def _validate_consistency(
|
|
2039
|
-
# Rules 2-3 of the autoregressive contract: identical key set and identical
|
|
2040
|
-
# every call — compiled graphs need static shapes.
|
|
2041
|
-
if
|
|
1793
|
+
def _validate_consistency(result):
|
|
1794
|
+
# Rules 2-3 of the autoregressive contract: identical key set and identical
|
|
1795
|
+
# (non-passthrough) shapes on every call — compiled graphs need static shapes.
|
|
1796
|
+
if result is None:
|
|
2042
1797
|
return
|
|
2043
|
-
keys = set(
|
|
2044
|
-
shapes = {key: tuple(value.shape) for key, value in
|
|
1798
|
+
keys = set(result.keys())
|
|
1799
|
+
shapes = {key: tuple(value.shape) for key, value in result.items()
|
|
1800
|
+
if not key.startswith('_') and isinstance(value, np.ndarray)}
|
|
2045
1801
|
if first_call_record['keys'] is None:
|
|
2046
1802
|
first_call_record['keys'] = keys
|
|
2047
1803
|
first_call_record['shapes'] = shapes
|
|
@@ -2063,19 +1819,14 @@ def tensorleap_autoregressive_step(latent_space_aggregation: str = 'last_step'):
|
|
|
2063
1819
|
f'the hook is not deterministic — two calls with identical arguments returned different '
|
|
2064
1820
|
f'outputs. Seed any stochasticity (e.g. initial noise) from sample_id: the engine '
|
|
2065
1821
|
f'replays steps after crash recovery and relies on identical results.')
|
|
2066
|
-
|
|
2067
|
-
|
|
2068
|
-
|
|
2069
|
-
|
|
2070
|
-
|
|
2071
|
-
|
|
2072
|
-
|
|
2073
|
-
assert
|
|
2074
|
-
for key, value in first_inputs.items():
|
|
2075
|
-
if not isinstance(value, np.ndarray):
|
|
2076
|
-
continue
|
|
2077
|
-
assert _nest_equal(value, second_inputs[key]), error_message
|
|
2078
|
-
assert _nest_equal(first_state, second_state), error_message
|
|
1822
|
+
if first_result is None or second_result is None:
|
|
1823
|
+
assert first_result is None and second_result is None, error_message
|
|
1824
|
+
return
|
|
1825
|
+
assert set(first_result.keys()) == set(second_result.keys()), error_message
|
|
1826
|
+
for key, value in first_result.items():
|
|
1827
|
+
if key.startswith('_') or not isinstance(value, np.ndarray):
|
|
1828
|
+
continue
|
|
1829
|
+
assert np.array_equal(value, second_result[key]), error_message
|
|
2079
1830
|
|
|
2080
1831
|
def _squeeze_test_batch_dim(tensors_dict):
|
|
2081
1832
|
# Inside the integration test the model works on batch-1 tensors (this wrapper adds the
|
|
@@ -2086,18 +1837,18 @@ def tensorleap_autoregressive_step(latent_space_aggregation: str = 'last_step'):
|
|
|
2086
1837
|
return None
|
|
2087
1838
|
squeezed = {}
|
|
2088
1839
|
for key, value in tensors_dict.items():
|
|
2089
|
-
if isinstance(value, np.ndarray)
|
|
1840
|
+
if not key.startswith('_') and isinstance(value, np.ndarray) \
|
|
1841
|
+
and value.ndim > 0 and value.shape[0] == 1:
|
|
2090
1842
|
squeezed[key] = value[0]
|
|
2091
1843
|
else:
|
|
2092
1844
|
squeezed[key] = value
|
|
2093
1845
|
return squeezed
|
|
2094
1846
|
|
|
2095
|
-
def inner_without_validate(sample_id, prev_inputs, prev_outputs,
|
|
1847
|
+
def inner_without_validate(sample_id, prev_inputs, prev_outputs, preprocess_response):
|
|
2096
1848
|
global _called_from_inside_tl_decorator
|
|
2097
1849
|
_called_from_inside_tl_decorator += 1
|
|
2098
1850
|
try:
|
|
2099
|
-
result = user_function(sample_id, prev_inputs, prev_outputs,
|
|
2100
|
-
preprocess_response)
|
|
1851
|
+
result = user_function(sample_id, prev_inputs, prev_outputs, preprocess_response)
|
|
2101
1852
|
finally:
|
|
2102
1853
|
_called_from_inside_tl_decorator -= 1
|
|
2103
1854
|
return result
|
|
@@ -2108,66 +1859,43 @@ def tensorleap_autoregressive_step(latent_space_aggregation: str = 'last_step'):
|
|
|
2108
1859
|
def inner(*args, **kwargs):
|
|
2109
1860
|
if not _call_from_tl_platform:
|
|
2110
1861
|
set_current('tensorleap_autoregressive_step')
|
|
2111
|
-
sample_id, prev_inputs, prev_outputs,
|
|
2112
|
-
_normalize_args(args, kwargs)
|
|
1862
|
+
sample_id, prev_inputs, prev_outputs, preprocess_response = _normalize_args(args, kwargs)
|
|
2113
1863
|
|
|
2114
|
-
|
|
2115
|
-
is_top_level_in_test = (is_top_level
|
|
1864
|
+
is_top_level_in_test = (_called_from_inside_tl_decorator == 0
|
|
2116
1865
|
and _called_from_inside_tl_integration_test_decorator)
|
|
2117
|
-
loop_context = _active_model_loop
|
|
2118
|
-
if is_top_level_in_test and loop_context is None:
|
|
2119
|
-
raise LeapValidationError(
|
|
2120
|
-
f'{user_function.__name__}() validation failed: inside the integration '
|
|
2121
|
-
f'test the autoregressive step may only be called from within the '
|
|
2122
|
-
f'tensorleap_model_loop function — including the initial call '
|
|
2123
|
-
f'(prev_inputs=None). The platform drives the chain through the model '
|
|
2124
|
-
f'loop only, so a hook call outside it would not run on the platform.')
|
|
2125
|
-
# Outside the integration test a model loop may still be running (local debugging);
|
|
2126
|
-
# the loop's phase machine must advance there too, or its model proxy always raises.
|
|
2127
|
-
notify_loop = is_top_level and loop_context is not None
|
|
2128
|
-
if notify_loop:
|
|
2129
|
-
loop_context.on_hook_call(sample_id, prev_inputs, prev_outputs, state,
|
|
2130
|
-
preprocess_response)
|
|
2131
1866
|
if is_top_level_in_test:
|
|
2132
1867
|
prev_inputs = _squeeze_test_batch_dim(prev_inputs)
|
|
2133
1868
|
prev_outputs = _squeeze_test_batch_dim(prev_outputs)
|
|
2134
1869
|
|
|
2135
|
-
_validate_input_args(sample_id, prev_inputs, prev_outputs,
|
|
1870
|
+
_validate_input_args(sample_id, prev_inputs, prev_outputs, preprocess_response)
|
|
2136
1871
|
|
|
2137
|
-
|
|
2138
|
-
# accumulates into state in place would otherwise see its own first-call mutations.
|
|
2139
|
-
state_for_second_call = copy.deepcopy(state) if is_top_level_in_test else None
|
|
2140
|
-
result = inner_without_validate(sample_id, prev_inputs, prev_outputs, state,
|
|
2141
|
-
preprocess_response)
|
|
1872
|
+
result = inner_without_validate(sample_id, prev_inputs, prev_outputs, preprocess_response)
|
|
2142
1873
|
|
|
2143
1874
|
if is_top_level_in_test:
|
|
2144
1875
|
# Rule 7: determinism double-call. An unseeded sampler fails here, on the user's
|
|
2145
1876
|
# machine, instead of silently forking a chain on engine crash-replay.
|
|
2146
1877
|
second_result = inner_without_validate(sample_id, prev_inputs, prev_outputs,
|
|
2147
|
-
|
|
1878
|
+
preprocess_response)
|
|
2148
1879
|
_assert_deterministic(result, second_result)
|
|
2149
1880
|
|
|
2150
1881
|
_validate_result(result, is_first_call=prev_inputs is None)
|
|
2151
|
-
|
|
2152
|
-
_validate_consistency(next_inputs)
|
|
2153
|
-
_validate_state_types(new_state)
|
|
1882
|
+
_validate_consistency(result)
|
|
2154
1883
|
|
|
2155
|
-
if is_top_level_in_test and
|
|
2156
|
-
|
|
2157
|
-
|
|
2158
|
-
|
|
2159
|
-
loop_context.on_hook_return(next_inputs, new_state)
|
|
1884
|
+
if is_top_level_in_test and result is not None:
|
|
1885
|
+
result = {key: (np.expand_dims(value, axis=0)
|
|
1886
|
+
if not key.startswith('_') and isinstance(value, np.ndarray) else value)
|
|
1887
|
+
for key, value in result.items()}
|
|
2160
1888
|
|
|
2161
1889
|
if not _call_from_tl_platform:
|
|
2162
1890
|
update_env_params_func("tensorleap_autoregressive_step", "v")
|
|
2163
|
-
return
|
|
1891
|
+
return result
|
|
2164
1892
|
|
|
2165
1893
|
class _MappingInputsPlaceholder:
|
|
2166
|
-
"""Mapping-mode stand-in for the hook's returned
|
|
1894
|
+
"""Mapping-mode stand-in for the hook's returned dict.
|
|
2167
1895
|
|
|
2168
1896
|
Indexing it by a model-input name yields a placeholder whose node_mapping the model
|
|
2169
1897
|
placeholder tags Input0/Input1/... — exactly how input encoders wire the graph. One
|
|
2170
|
-
NodeConnection is recorded per key, once.
|
|
1898
|
+
NodeConnection is recorded per non-passthrough key, once.
|
|
2171
1899
|
"""
|
|
2172
1900
|
def __init__(self):
|
|
2173
1901
|
self._items: Dict[str, Any] = {}
|
|
@@ -2180,7 +1908,8 @@ def tensorleap_autoregressive_step(latent_space_aggregation: str = 'last_step'):
|
|
|
2180
1908
|
pass
|
|
2181
1909
|
ret = TempMapping()
|
|
2182
1910
|
ret.node_mapping = NodeMapping(key, NodeMappingType.Input)
|
|
2183
|
-
|
|
1911
|
+
if not key.startswith('_'):
|
|
1912
|
+
leap_binder.mapping_connections.append(NodeConnection(ret.node_mapping, None))
|
|
2184
1913
|
self._items[key] = ret
|
|
2185
1914
|
return self._items[key]
|
|
2186
1915
|
|
|
@@ -2205,17 +1934,9 @@ def tensorleap_autoregressive_step(latent_space_aggregation: str = 'last_step'):
|
|
|
2205
1934
|
self._unsupported_iteration()
|
|
2206
1935
|
|
|
2207
1936
|
mapping_placeholder = _MappingInputsPlaceholder()
|
|
2208
|
-
mapping_state_placeholder = _MappingStatePlaceholder()
|
|
2209
1937
|
|
|
2210
1938
|
def mapping_inner(*args, **kwargs):
|
|
2211
|
-
|
|
2212
|
-
if loop_context is None:
|
|
2213
|
-
raise LeapValidationError(
|
|
2214
|
-
f'{user_function.__name__}() validation failed: inside the integration test '
|
|
2215
|
-
f'the autoregressive step may only be called from within the '
|
|
2216
|
-
f'tensorleap_model_loop function — including the initial call '
|
|
2217
|
-
f'(prev_inputs=None).')
|
|
2218
|
-
return loop_context.mapping_hook_call(mapping_placeholder, mapping_state_placeholder)
|
|
1939
|
+
return mapping_placeholder
|
|
2219
1940
|
|
|
2220
1941
|
def final_inner(*args, **kwargs):
|
|
2221
1942
|
if os.environ.get(mapping_runtime_mode_env_var_mame):
|
|
@@ -2231,396 +1952,6 @@ def tensorleap_autoregressive_step(latent_space_aggregation: str = 'last_step'):
|
|
|
2231
1952
|
return decorating_function
|
|
2232
1953
|
|
|
2233
1954
|
|
|
2234
|
-
def tensorleap_model_loop():
|
|
2235
|
-
"""The user-authored autoregressive rollout loop.
|
|
2236
|
-
|
|
2237
|
-
Signature of the decorated function:
|
|
2238
|
-
(model, sample_id, preprocess) -> (final_inputs, final_outputs, final_state)
|
|
2239
|
-
|
|
2240
|
-
The function drives the chain locally, exactly as the platform will: it makes the initial
|
|
2241
|
-
hook call (prev_inputs=None, prev_outputs=None, state=None) inside the loop, alternates
|
|
2242
|
-
model forwards with hook calls, feeds the model exactly the tensors the hook returned, and
|
|
2243
|
-
exits when the hook returns None for next_inputs — returning the last inputs dict fed to the
|
|
2244
|
-
model, the last model outputs (a dict keyed by the declared prediction-type names) and the
|
|
2245
|
-
final state. The loop body itself never runs on the platform — the engine drives the chain —
|
|
2246
|
-
so the decorator validates, while the loop runs, that its behavior matches the engine loop;
|
|
2247
|
-
any computation in the loop body outside the hook and the model is invisible to the platform
|
|
2248
|
-
and fails validation. Canonical shape:
|
|
2249
|
-
|
|
2250
|
-
@tensorleap_model_loop()
|
|
2251
|
-
def model_loop(model, sample_id, preprocess):
|
|
2252
|
-
inputs, state = generate_next_inputs(sample_id, None, None, None, preprocess)
|
|
2253
|
-
while True:
|
|
2254
|
-
outputs = model(inputs)
|
|
2255
|
-
next_inputs, state = generate_next_inputs(sample_id, inputs, outputs, state,
|
|
2256
|
-
preprocess)
|
|
2257
|
-
if next_inputs is None:
|
|
2258
|
-
return inputs, outputs, state
|
|
2259
|
-
inputs = next_inputs
|
|
2260
|
-
"""
|
|
2261
|
-
|
|
2262
|
-
def decorating_function(user_function):
|
|
2263
|
-
|
|
2264
|
-
def _normalize_args(args, kwargs):
|
|
2265
|
-
expected_names = ["model", "sample_id", "preprocess"]
|
|
2266
|
-
normalized = []
|
|
2267
|
-
for i, arg_name in enumerate(expected_names):
|
|
2268
|
-
if i < len(args):
|
|
2269
|
-
normalized.append(args[i])
|
|
2270
|
-
elif arg_name in kwargs:
|
|
2271
|
-
normalized.append(kwargs[arg_name])
|
|
2272
|
-
else:
|
|
2273
|
-
raise AssertionError(
|
|
2274
|
-
f'{user_function.__name__} validation failed: missing required argument '
|
|
2275
|
-
f"'{arg_name}'. Expected arguments: {expected_names}.")
|
|
2276
|
-
if len(args) + len(kwargs) != len(expected_names):
|
|
2277
|
-
raise AssertionError(
|
|
2278
|
-
f'{user_function.__name__} validation failed: expected exactly '
|
|
2279
|
-
f'{len(expected_names)} arguments {expected_names}, got '
|
|
2280
|
-
f'{len(args) + len(kwargs)}.')
|
|
2281
|
-
return normalized
|
|
2282
|
-
|
|
2283
|
-
def _fail(message):
|
|
2284
|
-
raise LeapValidationError(f'tensorleap_model_loop validation failed: {message}')
|
|
2285
|
-
|
|
2286
|
-
def _run_with_context(model, sample_id, preprocess_response, is_mapping):
|
|
2287
|
-
global _active_model_loop, _model_loop_run_in_current_test
|
|
2288
|
-
in_test = bool(_called_from_inside_tl_integration_test_decorator)
|
|
2289
|
-
if _active_model_loop is not None:
|
|
2290
|
-
_fail('model loops cannot nest or run concurrently.')
|
|
2291
|
-
if in_test and _model_loop_run_in_current_test:
|
|
2292
|
-
_fail('only one model loop run is allowed per integration test.')
|
|
2293
|
-
if model is None or model is not _last_loaded_model:
|
|
2294
|
-
_fail('the model argument must be the object returned by the '
|
|
2295
|
-
'tensorleap_load_model function, unchanged.')
|
|
2296
|
-
if leap_binder.setup_container.autoregressive_step is None:
|
|
2297
|
-
_fail('tensorleap_model_loop requires a tensorleap_autoregressive_step hook.')
|
|
2298
|
-
prediction_names = [prediction_type.name for prediction_type
|
|
2299
|
-
in leap_binder.setup_container.prediction_types]
|
|
2300
|
-
if not prediction_names:
|
|
2301
|
-
_fail('tensorleap_model_loop requires prediction types declared on '
|
|
2302
|
-
'tensorleap_load_model — the loop keys the model outputs by their names.')
|
|
2303
|
-
context = _ModelLoopContext(sample_id, preprocess_response, prediction_names,
|
|
2304
|
-
is_mapping)
|
|
2305
|
-
_active_model_loop = context
|
|
2306
|
-
try:
|
|
2307
|
-
result = user_function(_ModelLoopModelProxy(model, context), sample_id,
|
|
2308
|
-
preprocess_response)
|
|
2309
|
-
finally:
|
|
2310
|
-
_active_model_loop = None
|
|
2311
|
-
if in_test:
|
|
2312
|
-
_model_loop_run_in_current_test = True
|
|
2313
|
-
if context.phase == 'awaiting_first_hook':
|
|
2314
|
-
_fail('the loop never called the autoregressive step — it must start with the '
|
|
2315
|
-
'initial hook call (prev_inputs=None, prev_outputs=None, state=None).')
|
|
2316
|
-
if context.phase != 'ended':
|
|
2317
|
-
_fail('the loop returned before the autoregressive step signaled the chain end — '
|
|
2318
|
-
'it must run until the hook returns None for next_inputs and then return '
|
|
2319
|
-
'(final_inputs, final_outputs, final_state).')
|
|
2320
|
-
if not isinstance(result, tuple) or len(result) != 3:
|
|
2321
|
-
_fail('the loop must return (final_inputs, final_outputs, final_state). Got '
|
|
2322
|
-
f'{type(result).__name__}.')
|
|
2323
|
-
final_inputs, final_outputs, final_state = result
|
|
2324
|
-
if final_inputs is not context.fed_inputs:
|
|
2325
|
-
_fail('final_inputs must be exactly the last inputs dict fed to the model — the '
|
|
2326
|
-
'one the hook returned before the terminating call.')
|
|
2327
|
-
if final_outputs is not context.last_outputs:
|
|
2328
|
-
_fail('final_outputs must be exactly the outputs dict of the last model call.')
|
|
2329
|
-
if final_state is not context.last_state:
|
|
2330
|
-
_fail('final_state must be exactly the state returned by the terminating hook '
|
|
2331
|
-
'call.')
|
|
2332
|
-
return context, final_inputs, final_outputs, final_state
|
|
2333
|
-
|
|
2334
|
-
def inner(*args, **kwargs):
|
|
2335
|
-
if not _call_from_tl_platform:
|
|
2336
|
-
set_current('tensorleap_model_loop')
|
|
2337
|
-
model, sample_id, preprocess_response = _normalize_args(args, kwargs)
|
|
2338
|
-
in_test = _called_from_inside_tl_integration_test_decorator
|
|
2339
|
-
context, final_inputs, final_outputs, final_state = _run_with_context(
|
|
2340
|
-
model, sample_id, preprocess_response, is_mapping=False)
|
|
2341
|
-
if _nest_fingerprint(final_inputs) != context.fed_inputs_fingerprint:
|
|
2342
|
-
_fail('the final inputs were mutated in place after the last model call.')
|
|
2343
|
-
if _nest_fingerprint(final_outputs) != context.last_outputs_fingerprint:
|
|
2344
|
-
_fail('the final outputs were mutated in place after the last model call.')
|
|
2345
|
-
if _nest_fingerprint(final_state) != context.last_state_fingerprint:
|
|
2346
|
-
_fail('the final state was mutated in place after the terminating hook call.')
|
|
2347
|
-
if not in_test:
|
|
2348
|
-
if not _call_from_tl_platform:
|
|
2349
|
-
update_env_params_func("tensorleap_model_loop", "v")
|
|
2350
|
-
return final_inputs, final_outputs, final_state
|
|
2351
|
-
# Inside the integration test the chain tensors carry a batch-1 axis; the platform
|
|
2352
|
-
# feeds autoregressive metrics per-chain unbatched dicts, so strip it here.
|
|
2353
|
-
squeezed_inputs = _squeeze_leading_batch_axis(final_inputs)
|
|
2354
|
-
squeezed_outputs = _squeeze_leading_batch_axis(final_outputs)
|
|
2355
|
-
_register_chain_artifact(squeezed_inputs, 'final_inputs')
|
|
2356
|
-
_register_chain_artifact(squeezed_outputs, 'final_outputs')
|
|
2357
|
-
_register_chain_artifact(final_state, 'final_state')
|
|
2358
|
-
if not _call_from_tl_platform:
|
|
2359
|
-
update_env_params_func("tensorleap_model_loop", "v")
|
|
2360
|
-
return squeezed_inputs, squeezed_outputs, final_state
|
|
2361
|
-
|
|
2362
|
-
def mapping_inner(*args, **kwargs):
|
|
2363
|
-
model, sample_id, preprocess_response = _normalize_args(args, kwargs)
|
|
2364
|
-
context, final_inputs, final_outputs, final_state = _run_with_context(
|
|
2365
|
-
model, sample_id, preprocess_response, is_mapping=True)
|
|
2366
|
-
_register_chain_artifact(final_inputs, 'final_inputs')
|
|
2367
|
-
_register_chain_artifact(final_outputs, 'final_outputs')
|
|
2368
|
-
_register_chain_artifact(final_state, 'final_state')
|
|
2369
|
-
return final_inputs, final_outputs, final_state
|
|
2370
|
-
|
|
2371
|
-
def final_inner(*args, **kwargs):
|
|
2372
|
-
if os.environ.get(mapping_runtime_mode_env_var_mame):
|
|
2373
|
-
return mapping_inner(*args, **kwargs)
|
|
2374
|
-
else:
|
|
2375
|
-
return inner(*args, **kwargs)
|
|
2376
|
-
|
|
2377
|
-
if not _call_from_tl_platform:
|
|
2378
|
-
register_decorator_row("tensorleap_model_loop")
|
|
2379
|
-
|
|
2380
|
-
return final_inner
|
|
2381
|
-
|
|
2382
|
-
return decorating_function
|
|
2383
|
-
|
|
2384
|
-
|
|
2385
|
-
_IMPLICIT_ARG_TO_ARTIFACT_KIND = {'inputs': 'final_inputs', 'outputs': 'final_outputs',
|
|
2386
|
-
'state': 'final_state'}
|
|
2387
|
-
_ARTIFACT_KIND_DESCRIPTIONS = {
|
|
2388
|
-
'final_inputs': 'final inputs dict returned by the model loop',
|
|
2389
|
-
'final_outputs': 'final outputs dict returned by the model loop',
|
|
2390
|
-
'final_state': 'final state returned by the model loop',
|
|
2391
|
-
'ground_truth': 'direct return value of a ground-truth encoder',
|
|
2392
|
-
}
|
|
2393
|
-
_AUTOREGRESSIVE_SCALAR_TYPES = (int, float, np.floating, np.integer, np.bool_)
|
|
2394
|
-
|
|
2395
|
-
|
|
2396
|
-
def _normalize_autoregressive_call_args(func_name, full_arg_names, args, kwargs):
|
|
2397
|
-
if len(args) + len(kwargs) != len(full_arg_names):
|
|
2398
|
-
raise AssertionError(
|
|
2399
|
-
f'{func_name} validation failed: expected exactly {len(full_arg_names)} arguments '
|
|
2400
|
-
f'{full_arg_names}, got {len(args) + len(kwargs)}.')
|
|
2401
|
-
normalized = {}
|
|
2402
|
-
for i, arg_name in enumerate(full_arg_names):
|
|
2403
|
-
if i < len(args):
|
|
2404
|
-
normalized[arg_name] = args[i]
|
|
2405
|
-
elif arg_name in kwargs:
|
|
2406
|
-
normalized[arg_name] = kwargs[arg_name]
|
|
2407
|
-
else:
|
|
2408
|
-
raise AssertionError(
|
|
2409
|
-
f'{func_name} validation failed: missing required argument \'{arg_name}\'. '
|
|
2410
|
-
f'Expected arguments: {full_arg_names}.')
|
|
2411
|
-
return normalized
|
|
2412
|
-
|
|
2413
|
-
|
|
2414
|
-
def _validate_autoregressive_arg_provenance(func_name, normalized):
|
|
2415
|
-
for arg_name, value in normalized.items():
|
|
2416
|
-
expected_kind = _IMPLICIT_ARG_TO_ARTIFACT_KIND.get(arg_name, 'ground_truth')
|
|
2417
|
-
entry = _lookup_chain_artifact(value)
|
|
2418
|
-
if entry is None:
|
|
2419
|
-
raise LeapValidationError(
|
|
2420
|
-
f'{func_name} validation failed: argument "{arg_name}" must be the '
|
|
2421
|
-
f'{_ARTIFACT_KIND_DESCRIPTIONS[expected_kind]}, passed whole and unmodified — '
|
|
2422
|
-
f'the platform feeds it from the finished chain, so anything else would not '
|
|
2423
|
-
f'exist at platform runtime. Slice or derive inside the function body instead.')
|
|
2424
|
-
kind, fingerprint, _ = entry
|
|
2425
|
-
if kind != expected_kind:
|
|
2426
|
-
raise LeapValidationError(
|
|
2427
|
-
f'{func_name} validation failed: argument "{arg_name}" received the '
|
|
2428
|
-
f'{_ARTIFACT_KIND_DESCRIPTIONS[kind]} but must be the '
|
|
2429
|
-
f'{_ARTIFACT_KIND_DESCRIPTIONS[expected_kind]}.')
|
|
2430
|
-
if fingerprint != _nest_fingerprint(value):
|
|
2431
|
-
raise LeapValidationError(
|
|
2432
|
-
f'{func_name} validation failed: argument "{arg_name}" was mutated in place '
|
|
2433
|
-
f'after it was produced — the platform feeds the original values, so local '
|
|
2434
|
-
f'mutations diverge from platform results.')
|
|
2435
|
-
|
|
2436
|
-
|
|
2437
|
-
def _squeeze_wired_autoregressive_args(normalized):
|
|
2438
|
-
squeezed = {}
|
|
2439
|
-
for arg_name, value in normalized.items():
|
|
2440
|
-
if arg_name not in _IMPLICIT_ARG_TO_ARTIFACT_KIND and isinstance(value, np.ndarray) \
|
|
2441
|
-
and value.ndim > 0 and value.shape[0] == 1:
|
|
2442
|
-
squeezed[arg_name] = value[0]
|
|
2443
|
-
else:
|
|
2444
|
-
squeezed[arg_name] = value
|
|
2445
|
-
return squeezed
|
|
2446
|
-
|
|
2447
|
-
|
|
2448
|
-
def _validate_autoregressive_scalar_result(func_name, result, allow_dict):
|
|
2449
|
-
def _is_scalar(value):
|
|
2450
|
-
return isinstance(value, _AUTOREGRESSIVE_SCALAR_TYPES) or \
|
|
2451
|
-
(isinstance(value, np.ndarray) and value.ndim == 0)
|
|
2452
|
-
|
|
2453
|
-
if _is_scalar(result):
|
|
2454
|
-
return
|
|
2455
|
-
if allow_dict and isinstance(result, dict) and result and \
|
|
2456
|
-
all(isinstance(key, str) for key in result) and \
|
|
2457
|
-
all(_is_scalar(value) for value in result.values()):
|
|
2458
|
-
return
|
|
2459
|
-
expected = 'a scalar number' + (' or a dict of named scalar numbers' if allow_dict else '')
|
|
2460
|
-
raise AssertionError(
|
|
2461
|
-
f'{func_name} validation failed: autoregressive results are per-chain — the function '
|
|
2462
|
-
f'must return {expected} for the single chain it received. Got {type(result).__name__}.')
|
|
2463
|
-
|
|
2464
|
-
|
|
2465
|
-
def _autoregressive_mapping_call(mapping_inner_func, args, kwargs, node_mapping_type):
|
|
2466
|
-
user_unique_name = mapping_inner_func.name
|
|
2467
|
-
if 'user_unique_name' in kwargs:
|
|
2468
|
-
kwargs = dict(kwargs)
|
|
2469
|
-
user_unique_name = kwargs.pop('user_unique_name')
|
|
2470
|
-
if user_unique_name in mapping_inner_func.name_to_unique_name[mapping_inner_func.name]:
|
|
2471
|
-
user_unique_name = \
|
|
2472
|
-
f'{user_unique_name}_{len(mapping_inner_func.name_to_unique_name[mapping_inner_func.name])}'
|
|
2473
|
-
mapping_inner_func.name_to_unique_name[mapping_inner_func.name].add(user_unique_name)
|
|
2474
|
-
|
|
2475
|
-
normalized = _normalize_autoregressive_call_args(mapping_inner_func.name,
|
|
2476
|
-
mapping_inner_func.full_arg_names, args,
|
|
2477
|
-
kwargs)
|
|
2478
|
-
for arg_name, value in normalized.items():
|
|
2479
|
-
if arg_name in _IMPLICIT_ARG_TO_ARTIFACT_KIND:
|
|
2480
|
-
entry = _lookup_chain_artifact(value)
|
|
2481
|
-
expected_kind = _IMPLICIT_ARG_TO_ARTIFACT_KIND[arg_name]
|
|
2482
|
-
if entry is None or entry[0] != expected_kind:
|
|
2483
|
-
raise LeapValidationError(
|
|
2484
|
-
f'{mapping_inner_func.name} validation failed: argument "{arg_name}" must '
|
|
2485
|
-
f'be the {_ARTIFACT_KIND_DESCRIPTIONS[expected_kind]}, passed whole.')
|
|
2486
|
-
elif not hasattr(value, 'node_mapping'):
|
|
2487
|
-
raise LeapValidationError(
|
|
2488
|
-
f'{mapping_inner_func.name} validation failed: argument "{arg_name}" must be '
|
|
2489
|
-
f'the direct return value of a ground-truth encoder.')
|
|
2490
|
-
ordered_connections = [normalized[arg_name] for arg_name in mapping_inner_func.arg_names]
|
|
2491
|
-
_add_mapping_connection(user_unique_name, ordered_connections, mapping_inner_func.arg_names,
|
|
2492
|
-
mapping_inner_func.name, node_mapping_type)
|
|
2493
|
-
return None
|
|
2494
|
-
|
|
2495
|
-
|
|
2496
|
-
def _make_autoregressive_decorator_inner(user_function, name, decorator_env_name, handler_data,
|
|
2497
|
-
node_mapping_type, validate_result):
|
|
2498
|
-
full_arg_names = inspect.getfullargspec(user_function)[0]
|
|
2499
|
-
|
|
2500
|
-
def inner_without_validate(*args, **kwargs):
|
|
2501
|
-
global _called_from_inside_tl_decorator
|
|
2502
|
-
_called_from_inside_tl_decorator += 1
|
|
2503
|
-
try:
|
|
2504
|
-
result = user_function(*args, **kwargs)
|
|
2505
|
-
finally:
|
|
2506
|
-
_called_from_inside_tl_decorator -= 1
|
|
2507
|
-
return result
|
|
2508
|
-
|
|
2509
|
-
def inner(*args, **kwargs):
|
|
2510
|
-
if not _call_from_tl_platform:
|
|
2511
|
-
set_current(decorator_env_name)
|
|
2512
|
-
normalized = _normalize_autoregressive_call_args(user_function.__name__, full_arg_names,
|
|
2513
|
-
args, kwargs)
|
|
2514
|
-
is_top_level_in_test = (_called_from_inside_tl_decorator == 0
|
|
2515
|
-
and _called_from_inside_tl_integration_test_decorator)
|
|
2516
|
-
if is_top_level_in_test:
|
|
2517
|
-
_validate_autoregressive_arg_provenance(user_function.__name__, normalized)
|
|
2518
|
-
normalized = _squeeze_wired_autoregressive_args(normalized)
|
|
2519
|
-
result = inner_without_validate(**normalized)
|
|
2520
|
-
validate_result(user_function.__name__, result)
|
|
2521
|
-
if not _call_from_tl_platform:
|
|
2522
|
-
update_env_params_func(decorator_env_name, "v")
|
|
2523
|
-
return result
|
|
2524
|
-
|
|
2525
|
-
def mapping_inner(*args, **kwargs):
|
|
2526
|
-
return _autoregressive_mapping_call(mapping_inner, args, kwargs, node_mapping_type)
|
|
2527
|
-
|
|
2528
|
-
mapping_inner.name = name
|
|
2529
|
-
mapping_inner.full_arg_names = full_arg_names
|
|
2530
|
-
mapping_inner.arg_names = handler_data.arg_names
|
|
2531
|
-
mapping_inner.name_to_unique_name = defaultdict(set)
|
|
2532
|
-
|
|
2533
|
-
def final_inner(*args, **kwargs):
|
|
2534
|
-
if os.environ.get(mapping_runtime_mode_env_var_mame):
|
|
2535
|
-
return mapping_inner(*args, **kwargs)
|
|
2536
|
-
else:
|
|
2537
|
-
return inner(*args, **kwargs)
|
|
2538
|
-
|
|
2539
|
-
if not _call_from_tl_platform:
|
|
2540
|
-
register_decorator_row(decorator_env_name)
|
|
2541
|
-
|
|
2542
|
-
return final_inner
|
|
2543
|
-
|
|
2544
|
-
|
|
2545
|
-
def tensorleap_autoregressive_metric(name: str,
|
|
2546
|
-
direction: Union[MetricDirection, Dict[str, MetricDirection]]
|
|
2547
|
-
= MetricDirection.Downward,
|
|
2548
|
-
compute_insights: Optional[Union[bool, Dict[str, bool]]] = None):
|
|
2549
|
-
"""A per-chain metric over a finished autoregressive chain.
|
|
2550
|
-
|
|
2551
|
-
The reserved arguments inputs / outputs / state are fed implicitly by the platform from the
|
|
2552
|
-
chain's final step (unbatched dicts; outputs keyed by the declared prediction-type names;
|
|
2553
|
-
state as the terminating hook call returned it). Declare whichever of them the metric needs.
|
|
2554
|
-
Every other argument is wired to a ground-truth encoder through the integration test. Runs
|
|
2555
|
-
once per chain and must return a scalar number (or a dict of named scalar numbers).
|
|
2556
|
-
"""
|
|
2557
|
-
|
|
2558
|
-
def decorating_function(user_function):
|
|
2559
|
-
leap_binder.add_autoregressive_metric(user_function, name, direction, compute_insights)
|
|
2560
|
-
handler_data = \
|
|
2561
|
-
leap_binder.setup_container.autoregressive_metrics[-1].metric_handler_data
|
|
2562
|
-
|
|
2563
|
-
def validate_result(func_name, result):
|
|
2564
|
-
_validate_autoregressive_scalar_result(func_name, result, allow_dict=True)
|
|
2565
|
-
|
|
2566
|
-
return _make_autoregressive_decorator_inner(
|
|
2567
|
-
user_function, name, 'tensorleap_autoregressive_metric', handler_data,
|
|
2568
|
-
NodeMappingType.Metric, validate_result)
|
|
2569
|
-
|
|
2570
|
-
return decorating_function
|
|
2571
|
-
|
|
2572
|
-
|
|
2573
|
-
def tensorleap_autoregressive_loss(name: str):
|
|
2574
|
-
"""A per-chain loss over a finished autoregressive chain.
|
|
2575
|
-
|
|
2576
|
-
Same argument contract as tensorleap_autoregressive_metric; must return a single scalar
|
|
2577
|
-
number for the chain it received.
|
|
2578
|
-
"""
|
|
2579
|
-
|
|
2580
|
-
def decorating_function(user_function):
|
|
2581
|
-
leap_binder.add_autoregressive_loss(user_function, name)
|
|
2582
|
-
handler_data = \
|
|
2583
|
-
leap_binder.setup_container.autoregressive_losses[-1].custom_loss_handler_data
|
|
2584
|
-
|
|
2585
|
-
def validate_result(func_name, result):
|
|
2586
|
-
_validate_autoregressive_scalar_result(func_name, result, allow_dict=False)
|
|
2587
|
-
|
|
2588
|
-
return _make_autoregressive_decorator_inner(
|
|
2589
|
-
user_function, name, 'tensorleap_autoregressive_loss', handler_data,
|
|
2590
|
-
NodeMappingType.CustomLoss, validate_result)
|
|
2591
|
-
|
|
2592
|
-
return decorating_function
|
|
2593
|
-
|
|
2594
|
-
|
|
2595
|
-
def tensorleap_autoregressive_visualizer(name: str, visualizer_type: LeapDataType):
|
|
2596
|
-
"""A per-chain visualizer over a finished autoregressive chain.
|
|
2597
|
-
|
|
2598
|
-
Same argument contract as tensorleap_autoregressive_metric; must return a LeapData object
|
|
2599
|
-
matching visualizer_type.
|
|
2600
|
-
"""
|
|
2601
|
-
|
|
2602
|
-
def decorating_function(user_function):
|
|
2603
|
-
assert isinstance(visualizer_type, LeapDataType), \
|
|
2604
|
-
(f'{user_function.__name__} validation failed: visualizer_type should be of type '
|
|
2605
|
-
f'{LeapDataType.__name__} but got {type(visualizer_type)}')
|
|
2606
|
-
leap_binder.add_autoregressive_visualizer(user_function, name, visualizer_type)
|
|
2607
|
-
handler_data = \
|
|
2608
|
-
leap_binder.setup_container.autoregressive_visualizers[-1].visualizer_handler_data
|
|
2609
|
-
|
|
2610
|
-
def validate_result(func_name, result):
|
|
2611
|
-
expected_class = map_leap_data_type_to_visualizer_class[visualizer_type.value]
|
|
2612
|
-
assert isinstance(result, expected_class), \
|
|
2613
|
-
(f'{func_name} validation failed: the return type should be '
|
|
2614
|
-
f'{expected_class.__name__} (visualizer_type={visualizer_type}). '
|
|
2615
|
-
f'Got {type(result).__name__}.')
|
|
2616
|
-
|
|
2617
|
-
return _make_autoregressive_decorator_inner(
|
|
2618
|
-
user_function, name, 'tensorleap_autoregressive_visualizer', handler_data,
|
|
2619
|
-
NodeMappingType.Visualizer, validate_result)
|
|
2620
|
-
|
|
2621
|
-
return decorating_function
|
|
2622
|
-
|
|
2623
|
-
|
|
2624
1955
|
def tensorleap_preprocess():
|
|
2625
1956
|
def decorating_function(user_function: Callable[[], List[PreprocessResponse]]):
|
|
2626
1957
|
leap_binder.set_preprocess(user_function)
|
|
@@ -2651,6 +1982,15 @@ def tensorleap_preprocess():
|
|
|
2651
1982
|
assert len(set(result)) == len(result), \
|
|
2652
1983
|
(f'{user_function.__name__}() validation failed: '
|
|
2653
1984
|
f'The return list should not contain duplicate PreprocessResponse objects.')
|
|
1985
|
+
# All-or-none: a dataset is either all grouped (nested sample_ids) or all flat.
|
|
1986
|
+
if len({response.is_grouped for response in result}) != 1:
|
|
1987
|
+
shapes = ', '.join(
|
|
1988
|
+
f"#{i}={'grouped' if response.is_grouped else 'flat'}"
|
|
1989
|
+
for i, response in enumerate(result))
|
|
1990
|
+
raise AssertionError(
|
|
1991
|
+
f'{user_function.__name__}() validation failed: '
|
|
1992
|
+
f'All PreprocessResponses must be either all grouped (nested sample_ids) '
|
|
1993
|
+
f'or all flat, but got a mix: {shapes}.')
|
|
2654
1994
|
|
|
2655
1995
|
def inner(*args, **kwargs):
|
|
2656
1996
|
if not _call_from_tl_platform:
|
|
@@ -2988,14 +2328,10 @@ def tensorleap_input_encoder(name: str, channel_dim=_UNSET, model_input_index=No
|
|
|
2988
2328
|
f"channel axis in the batched tensor (batch = axis 0), so 0 is invalid. Use 1 "
|
|
2989
2329
|
f"for channel-first (NCHW) and -1 for channel-last (NHWC).")
|
|
2990
2330
|
|
|
2991
|
-
def _validate_input_args(sample_id: Union[int, str], preprocess_response: PreprocessResponse):
|
|
2992
|
-
|
|
2993
|
-
(f'{user_function.__name__}() validation failed: '
|
|
2994
|
-
f'Argument sample_id should be as the same type as defined in the preprocess response '
|
|
2995
|
-
f'{preprocess_response.sample_id_type}. Got {type(sample_id)}.')
|
|
2331
|
+
def _validate_input_args(sample_id: Union[int, str, list], preprocess_response: PreprocessResponse):
|
|
2332
|
+
_validate_id_or_group(sample_id, preprocess_response, user_function.__name__)
|
|
2996
2333
|
|
|
2997
|
-
def
|
|
2998
|
-
validate_output_structure(result, func_name=user_function.__name__, expected_type_name="np.ndarray")
|
|
2334
|
+
def _validate_single(result):
|
|
2999
2335
|
assert isinstance(result, np.ndarray), \
|
|
3000
2336
|
(f'{user_function.__name__}() validation failed: '
|
|
3001
2337
|
f'Unsupported return type. Should be a numpy array. Got {type(result)}.')
|
|
@@ -3005,6 +2341,13 @@ def tensorleap_input_encoder(name: str, channel_dim=_UNSET, model_input_index=No
|
|
|
3005
2341
|
assert channel_dim - 1 <= len(result.shape), (f'{user_function.__name__}() validation failed: '
|
|
3006
2342
|
f'The channel_dim ({channel_dim}) should be <= to the rank of the resulting input rank ({len(result.shape)}).')
|
|
3007
2343
|
|
|
2344
|
+
def _validate_result(result, grouped=False, group_size=None):
|
|
2345
|
+
if not grouped:
|
|
2346
|
+
validate_output_structure(result, func_name=user_function.__name__, expected_type_name="np.ndarray")
|
|
2347
|
+
_validate_single(result)
|
|
2348
|
+
return
|
|
2349
|
+
_validate_grouped_result(result, group_size, user_function.__name__, _validate_single)
|
|
2350
|
+
|
|
3008
2351
|
def inner_without_validate(sample_id, preprocess_response):
|
|
3009
2352
|
global _called_from_inside_tl_decorator
|
|
3010
2353
|
_called_from_inside_tl_decorator += 1
|
|
@@ -3021,18 +2364,27 @@ def tensorleap_input_encoder(name: str, channel_dim=_UNSET, model_input_index=No
|
|
|
3021
2364
|
def inner(*args, **kwargs):
|
|
3022
2365
|
if not _call_from_tl_platform:
|
|
3023
2366
|
set_current("tensorleap_input_encoder")
|
|
3024
|
-
validate_args_structure(*args, types_order=[Union[int, str], PreprocessResponse],
|
|
2367
|
+
validate_args_structure(*args, types_order=[Union[int, str, list], PreprocessResponse],
|
|
3025
2368
|
func_name=user_function.__name__, expected_names=["idx", "preprocess"], **kwargs)
|
|
3026
2369
|
sample_id, preprocess_response = args if len(args) != 0 else kwargs.values()
|
|
3027
2370
|
_validate_input_args(sample_id, preprocess_response)
|
|
3028
2371
|
|
|
2372
|
+
grouped = isinstance(sample_id, list)
|
|
2373
|
+
group_size = len(sample_id) if grouped else None
|
|
3029
2374
|
result = inner_without_validate(sample_id, preprocess_response)
|
|
3030
2375
|
|
|
3031
|
-
_validate_result(result)
|
|
2376
|
+
_validate_result(result, grouped, group_size)
|
|
3032
2377
|
|
|
3033
2378
|
if _called_from_inside_tl_decorator == 0 and _called_from_inside_tl_integration_test_decorator:
|
|
3034
|
-
|
|
3035
|
-
|
|
2379
|
+
if grouped:
|
|
2380
|
+
# A grouped result is a list of per-sample arrays (never stacked, so a
|
|
2381
|
+
# B=1-only model can be fed one sample at a time). Add a batch dim to each
|
|
2382
|
+
# sample, mirroring the flat path's single-sample expand_dims.
|
|
2383
|
+
if isinstance(result, list):
|
|
2384
|
+
result = [np.expand_dims(r, axis=0) for r in result]
|
|
2385
|
+
else:
|
|
2386
|
+
batch_warning(result, user_function.__name__)
|
|
2387
|
+
result = np.expand_dims(result, axis=0)
|
|
3036
2388
|
# Emit integration test event once per test
|
|
3037
2389
|
try:
|
|
3038
2390
|
emit_integration_event_once(AnalyticsEvent.INPUT_ENCODER_INTEGRATION_TEST, {
|
|
@@ -3053,8 +2405,15 @@ def tensorleap_input_encoder(name: str, channel_dim=_UNSET, model_input_index=No
|
|
|
3053
2405
|
inner.node_mapping = NodeMapping(name, node_mapping_type)
|
|
3054
2406
|
|
|
3055
2407
|
def mapping_inner(*args, **kwargs):
|
|
3056
|
-
|
|
3057
|
-
|
|
2408
|
+
if _mapping_dataset_is_grouped:
|
|
2409
|
+
class TempMapping:
|
|
2410
|
+
def __getitem__(self, key):
|
|
2411
|
+
# Indexing a grouped input group (input_list[i]) selects a sample from the
|
|
2412
|
+
# same input source; the model wiring is identical to the un-grouped input.
|
|
2413
|
+
return self
|
|
2414
|
+
else:
|
|
2415
|
+
class TempMapping:
|
|
2416
|
+
pass
|
|
3058
2417
|
|
|
3059
2418
|
ret = TempMapping()
|
|
3060
2419
|
ret.node_mapping = mapping_inner.node_mapping
|
|
@@ -3084,15 +2443,10 @@ def tensorleap_gt_encoder(name: str):
|
|
|
3084
2443
|
raise Exception(f'GT with name {name} already exists. '
|
|
3085
2444
|
f'Please choose another')
|
|
3086
2445
|
|
|
3087
|
-
def _validate_input_args(sample_id: Union[int, str], preprocess_response: PreprocessResponse):
|
|
3088
|
-
|
|
3089
|
-
(f'{user_function.__name__}() validation failed: '
|
|
3090
|
-
f'Argument sample_id should be as the same type as defined in the preprocess response '
|
|
3091
|
-
f'{preprocess_response.sample_id_type}. Got {type(sample_id)}.')
|
|
2446
|
+
def _validate_input_args(sample_id: Union[int, str, list], preprocess_response: PreprocessResponse):
|
|
2447
|
+
_validate_id_or_group(sample_id, preprocess_response, user_function.__name__)
|
|
3092
2448
|
|
|
3093
|
-
def
|
|
3094
|
-
validate_output_structure(result, func_name=user_function.__name__, expected_type_name="np.ndarray",
|
|
3095
|
-
gt_flag=True)
|
|
2449
|
+
def _validate_single(result):
|
|
3096
2450
|
assert isinstance(result, np.ndarray), \
|
|
3097
2451
|
(f'{user_function.__name__}() validation failed: '
|
|
3098
2452
|
f'Unsupported return type. Should be a numpy array. Got {type(result)}.')
|
|
@@ -3100,6 +2454,14 @@ def tensorleap_gt_encoder(name: str):
|
|
|
3100
2454
|
(f'{user_function.__name__}() validation failed: '
|
|
3101
2455
|
f'The return type should be a numpy array of type float32. Got {result.dtype}.')
|
|
3102
2456
|
|
|
2457
|
+
def _validate_result(result, grouped=False, group_size=None):
|
|
2458
|
+
if not grouped:
|
|
2459
|
+
validate_output_structure(result, func_name=user_function.__name__, expected_type_name="np.ndarray",
|
|
2460
|
+
gt_flag=True)
|
|
2461
|
+
_validate_single(result)
|
|
2462
|
+
return
|
|
2463
|
+
_validate_grouped_result(result, group_size, user_function.__name__, _validate_single)
|
|
2464
|
+
|
|
3103
2465
|
def inner_without_validate(sample_id, preprocess_response):
|
|
3104
2466
|
global _called_from_inside_tl_decorator
|
|
3105
2467
|
_called_from_inside_tl_decorator += 1
|
|
@@ -3116,19 +2478,27 @@ def tensorleap_gt_encoder(name: str):
|
|
|
3116
2478
|
def inner(*args, **kwargs):
|
|
3117
2479
|
if not _call_from_tl_platform:
|
|
3118
2480
|
set_current("tensorleap_gt_encoder")
|
|
3119
|
-
validate_args_structure(*args, types_order=[Union[int, str], PreprocessResponse],
|
|
2481
|
+
validate_args_structure(*args, types_order=[Union[int, str, list], PreprocessResponse],
|
|
3120
2482
|
func_name=user_function.__name__, expected_names=["idx", "preprocess"], **kwargs)
|
|
3121
2483
|
sample_id, preprocess_response = args
|
|
3122
2484
|
_validate_input_args(sample_id, preprocess_response)
|
|
3123
2485
|
|
|
2486
|
+
grouped = isinstance(sample_id, list)
|
|
2487
|
+
group_size = len(sample_id) if grouped else None
|
|
3124
2488
|
result = inner_without_validate(sample_id, preprocess_response)
|
|
3125
2489
|
|
|
3126
|
-
_validate_result(result)
|
|
2490
|
+
_validate_result(result, grouped, group_size)
|
|
3127
2491
|
|
|
3128
2492
|
if _called_from_inside_tl_decorator == 0 and _called_from_inside_tl_integration_test_decorator:
|
|
3129
|
-
|
|
3130
|
-
|
|
3131
|
-
|
|
2493
|
+
if grouped:
|
|
2494
|
+
# A grouped result is a list of per-sample arrays (never stacked, so a
|
|
2495
|
+
# B=1-only model can be fed one sample at a time). Add a batch dim to each
|
|
2496
|
+
# sample, mirroring the flat path's single-sample expand_dims.
|
|
2497
|
+
if isinstance(result, list):
|
|
2498
|
+
result = [np.expand_dims(r, axis=0) for r in result]
|
|
2499
|
+
else:
|
|
2500
|
+
batch_warning(result, user_function.__name__)
|
|
2501
|
+
result = np.expand_dims(result, axis=0)
|
|
3132
2502
|
# Emit integration test event once per test
|
|
3133
2503
|
try:
|
|
3134
2504
|
emit_integration_event_once(AnalyticsEvent.GT_ENCODER_INTEGRATION_TEST, {
|
|
@@ -3143,8 +2513,15 @@ def tensorleap_gt_encoder(name: str):
|
|
|
3143
2513
|
inner.node_mapping = NodeMapping(name, NodeMappingType.GroundTruth)
|
|
3144
2514
|
|
|
3145
2515
|
def mapping_inner(*args, **kwargs):
|
|
3146
|
-
|
|
3147
|
-
|
|
2516
|
+
if _mapping_dataset_is_grouped:
|
|
2517
|
+
class TempMapping:
|
|
2518
|
+
def __getitem__(self, key):
|
|
2519
|
+
# Indexing a grouped GT group (gt_list[i]) selects a sample from the same
|
|
2520
|
+
# ground-truth source; wiring is identical to the un-grouped GT node.
|
|
2521
|
+
return self
|
|
2522
|
+
else:
|
|
2523
|
+
class TempMapping:
|
|
2524
|
+
pass
|
|
3148
2525
|
|
|
3149
2526
|
ret = TempMapping()
|
|
3150
2527
|
ret.node_mapping = mapping_inner.node_mapping
|
|
@@ -3395,12 +2772,6 @@ def tensorleap_status_table():
|
|
|
3395
2772
|
if not started_from("leap_integration.py"):
|
|
3396
2773
|
return
|
|
3397
2774
|
|
|
3398
|
-
if leap_binder.setup_container.autoregressive_step is not None:
|
|
3399
|
-
# Input encoders are forbidden in autoregressive integrations — the step hook is
|
|
3400
|
-
# the sole source of model inputs — so the row is not applicable: it must not be
|
|
3401
|
-
# printed, gate readiness, or be recommended as a next step.
|
|
3402
|
-
table[:] = [row for row in table if row["name"] != "tensorleap_input_encoder"]
|
|
3403
|
-
|
|
3404
2775
|
ready_mess = "\nAll parts have been successfully set. If no errors accured, you can now push the project to the Tensorleap system."
|
|
3405
2776
|
not_ready_mess = "\nSome mandatory components have not yet been added to the Integration test. Recommended next interface to add is: "
|
|
3406
2777
|
mandatory_ready_mess = "\nAll mandatory parts have been successfully set. If no errors accured, you can now push the project to the Tensorleap system or continue to the next optional reccomeded interface,adding: "
|
|
@@ -3432,11 +2803,7 @@ def tensorleap_status_table():
|
|
|
3432
2803
|
return
|
|
3433
2804
|
|
|
3434
2805
|
if _crashed["value"]:
|
|
3435
|
-
|
|
3436
|
-
if crashed_at:
|
|
3437
|
-
print(f"\nScript crashed before completing all steps. crashed at function '{crashed_at}'.")
|
|
3438
|
-
else:
|
|
3439
|
-
print("\nScript crashed before completing all steps.")
|
|
2806
|
+
print(f"\nScript crashed before completing all steps. crashed at function '{_current_func['name']}'.")
|
|
3440
2807
|
return
|
|
3441
2808
|
|
|
3442
2809
|
print(ready_mess) if ready else print(
|
|
@@ -3511,11 +2878,9 @@ def tensorleap_status_table():
|
|
|
3511
2878
|
row = _find_row(crashed_name)
|
|
3512
2879
|
if row:
|
|
3513
2880
|
row["Added to integration"] = CROSS
|
|
3514
|
-
elif not crashed_name
|
|
3515
|
-
# Crash with no decorator currently running
|
|
3516
|
-
#
|
|
3517
|
-
# blaming a decorator. Without a started test (a module-level crash between
|
|
3518
|
-
# decorator registrations) the plain script-crash message is the truthful one.
|
|
2881
|
+
elif not crashed_name:
|
|
2882
|
+
# Crash with no decorator currently running = the integration-test body /
|
|
2883
|
+
# code flow itself failed; report that rather than blaming a decorator.
|
|
3519
2884
|
code_mapping_failure[0] = 1
|
|
3520
2885
|
|
|
3521
2886
|
traceback.print_exception(exc_type, exc_value, exc_traceback)
|