code-loader 1.0.195.dev1__py3-none-any.whl → 1.0.196.dev1__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 +45 -10
- code_loader/inner_leap_binder/leapbinder.py +146 -5
- code_loader/inner_leap_binder/leapbinder_decorators.py +859 -105
- code_loader/leaploader.py +247 -64
- code_loader/leaploaderbase.py +67 -3
- code_loader/utils.py +57 -4
- {code_loader-1.0.195.dev1.dist-info → code_loader-1.0.196.dev1.dist-info}/METADATA +1 -1
- {code_loader-1.0.195.dev1.dist-info → code_loader-1.0.196.dev1.dist-info}/RECORD +10 -10
- {code_loader-1.0.195.dev1.dist-info → code_loader-1.0.196.dev1.dist-info}/LICENSE +0 -0
- {code_loader-1.0.195.dev1.dist-info → code_loader-1.0.196.dev1.dist-info}/WHEEL +0 -0
|
@@ -1,4 +1,6 @@
|
|
|
1
1
|
# mypy: ignore-errors
|
|
2
|
+
import copy
|
|
3
|
+
import hashlib
|
|
2
4
|
import inspect
|
|
3
5
|
import os
|
|
4
6
|
import warnings
|
|
@@ -13,7 +15,8 @@ from typing import Optional, Union, Callable, List, Dict, get_args, get_origin,
|
|
|
13
15
|
import numpy as np
|
|
14
16
|
import numpy.typing as npt
|
|
15
17
|
|
|
16
|
-
from code_loader.utils import get_metadata_type_from_variable, map_dict_to_metadata_types
|
|
18
|
+
from code_loader.utils import get_metadata_type_from_variable, map_dict_to_metadata_types, \
|
|
19
|
+
validate_autoregressive_state_types, autoregressive_nests_equal
|
|
17
20
|
|
|
18
21
|
logger = logging.getLogger(__name__)
|
|
19
22
|
|
|
@@ -26,12 +29,17 @@ from code_loader.contract.enums import MetricDirection, LeapDataType, DatasetMet
|
|
|
26
29
|
from code_loader import leap_binder, LeapLoader
|
|
27
30
|
from code_loader.contract.mapping import NodeMapping, NodeMappingType, NodeConnection
|
|
28
31
|
from code_loader.contract.visualizer_classes import LeapImage, LeapImageMask, LeapTextMask, LeapText, LeapGraph, \
|
|
29
|
-
LeapHorizontalBar, LeapImageWithBBox, LeapImageWithHeatmap, LeapVideo, LeapValidationError
|
|
32
|
+
LeapHorizontalBar, LeapImageWithBBox, LeapImageWithHeatmap, LeapVideo, LeapValidationError, \
|
|
33
|
+
map_leap_data_type_to_visualizer_class
|
|
30
34
|
from code_loader.inner_leap_binder.leapbinder import mapping_runtime_mode_env_var_mame
|
|
31
35
|
from code_loader.mixpanel_tracker import clear_integration_events, AnalyticsEvent, emit_integration_event_once
|
|
32
36
|
|
|
33
37
|
_called_from_inside_tl_decorator = 0
|
|
34
38
|
_called_from_inside_tl_integration_test_decorator = False
|
|
39
|
+
# Whether any integration test wrapper was entered in this process. The crash reporter uses it
|
|
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
43
|
_mapping_dataset_is_grouped = False
|
|
36
44
|
_call_from_tl_platform = os.environ.get('IS_TENSORLEAP_PLATFORM') == 'true'
|
|
37
45
|
|
|
@@ -231,12 +239,15 @@ def _validate_id_or_group(sample_id, preprocess_response, func_name):
|
|
|
231
239
|
def _validate_grouped_result(result, group_size, func_name, validate_single):
|
|
232
240
|
"""A grouped (group) encoder result must be either a single ndarray with leading dim
|
|
233
241
|
== 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).
|
|
242
|
+
validate_single). Metadata validates its grouped result separately (list only).
|
|
243
|
+
An `(B, *)` array is validated per-sample slice (not as the whole batched array) so
|
|
244
|
+
per-sample checks (e.g. channel_dim <= rank) use the sample shape, not the batch shape."""
|
|
235
245
|
if isinstance(result, np.ndarray):
|
|
236
246
|
assert len(result.shape) > 0 and result.shape[0] == group_size, \
|
|
237
247
|
(f'{func_name}() validation failed: expected a grouped output for a group of '
|
|
238
248
|
f'{group_size}: an array with leading dim {group_size}, got shape {result.shape}.')
|
|
239
|
-
|
|
249
|
+
for single in result:
|
|
250
|
+
validate_single(single)
|
|
240
251
|
elif isinstance(result, list):
|
|
241
252
|
assert len(result) == group_size, \
|
|
242
253
|
(f'{func_name}() validation failed: expected a grouped output for a group of '
|
|
@@ -332,11 +343,23 @@ def tensorleap_integration_test():
|
|
|
332
343
|
def inner(*args, **kwargs):
|
|
333
344
|
if not _call_from_tl_platform:
|
|
334
345
|
set_current('tensorleap_integration_test')
|
|
346
|
+
# Keyword calls must work with the user function's OWN parameter names — the local
|
|
347
|
+
# run template calls integration_test(sample_id=..., preprocess=...) against a
|
|
348
|
+
# signature the user chose, not the canonical placeholder names.
|
|
349
|
+
expected_names = inspect.getfullargspec(integration_test_function)[0][:2]
|
|
350
|
+
if len(expected_names) < 2:
|
|
351
|
+
expected_names = ['idx', 'preprocess']
|
|
335
352
|
validate_args_structure(*args, types_order=[Union[int, str, list], PreprocessResponse],
|
|
336
|
-
func_name='integration_test', expected_names=
|
|
353
|
+
func_name='integration_test', expected_names=expected_names,
|
|
354
|
+
**kwargs)
|
|
355
|
+
sample_id, preprocess_response = (
|
|
356
|
+
args[i] if i < len(args) else kwargs[name]
|
|
357
|
+
for i, name in enumerate(expected_names))
|
|
358
|
+
args, kwargs = (sample_id, preprocess_response), {}
|
|
337
359
|
_validate_input_args(*args, **kwargs)
|
|
338
360
|
|
|
339
|
-
global _called_from_inside_tl_integration_test_decorator
|
|
361
|
+
global _called_from_inside_tl_integration_test_decorator, _integration_test_started
|
|
362
|
+
_integration_test_started = True
|
|
340
363
|
# Clear integration test events for new test
|
|
341
364
|
try:
|
|
342
365
|
clear_integration_events()
|
|
@@ -347,12 +370,14 @@ def tensorleap_integration_test():
|
|
|
347
370
|
if not _call_from_tl_platform:
|
|
348
371
|
update_env_params_func("tensorleap_integration_test",
|
|
349
372
|
"v") # put here because otherwise it will become v only if it finishes all the script
|
|
373
|
+
_reset_model_loop_state()
|
|
350
374
|
ret = integration_test_function(*args, **kwargs)
|
|
351
375
|
|
|
352
376
|
global _mapping_dataset_is_grouped
|
|
353
377
|
_mapping_dataset_is_grouped = args[1].is_grouped
|
|
354
378
|
try:
|
|
355
379
|
os.environ[mapping_runtime_mode_env_var_mame] = 'True'
|
|
380
|
+
_reset_model_loop_state()
|
|
356
381
|
integration_test_function(None, PreprocessResponse(state=DataStateType.training, length=0))
|
|
357
382
|
except Exception as e:
|
|
358
383
|
import traceback
|
|
@@ -466,7 +491,7 @@ def tensorleap_load_model(prediction_types: Optional[List[PredictionTypeHandler]
|
|
|
466
491
|
pass
|
|
467
492
|
|
|
468
493
|
@lru_cache()
|
|
469
|
-
def
|
|
494
|
+
def _cached_inner(*args, **kwargs):
|
|
470
495
|
if not _call_from_tl_platform:
|
|
471
496
|
set_current('tensorleap_load_model')
|
|
472
497
|
validate_args_structure(*args, types_order=[],
|
|
@@ -624,14 +649,32 @@ def tensorleap_load_model(prediction_types: Optional[List[PredictionTypeHandler]
|
|
|
624
649
|
update_env_params_func("tensorleap_load_model", "v")
|
|
625
650
|
return model_placeholder
|
|
626
651
|
|
|
652
|
+
def inner(*args, **kwargs):
|
|
653
|
+
model_placeholder = _cached_inner(*args, **kwargs)
|
|
654
|
+
# Re-assert on every call, outside the cache: each mapping rerun's load_model
|
|
655
|
+
# overwrites this global with its mapping placeholder, and on a cache hit the
|
|
656
|
+
# body above never runs — a second integration test would otherwise fail the
|
|
657
|
+
# model loop's identity check against the stale mapping placeholder.
|
|
658
|
+
global _last_loaded_model
|
|
659
|
+
_last_loaded_model = model_placeholder
|
|
660
|
+
return model_placeholder
|
|
661
|
+
|
|
627
662
|
def mapping_inner():
|
|
628
663
|
class ModelOutputPlaceholder:
|
|
629
664
|
def __init__(self):
|
|
630
665
|
self.node_mapping = NodeMapping('', NodeMappingType.Prediction0)
|
|
631
666
|
|
|
632
667
|
def __getitem__(self, key):
|
|
633
|
-
assert isinstance(key, int), \
|
|
634
|
-
f'Expected key to be an int, got {type(key)} instead.'
|
|
668
|
+
assert isinstance(key, (int, str)), \
|
|
669
|
+
f'Expected key to be an int or a prediction-type name, got {type(key)} instead.'
|
|
670
|
+
if isinstance(key, str):
|
|
671
|
+
declared_names = [prediction_type.name for prediction_type
|
|
672
|
+
in leap_binder.setup_container.prediction_types]
|
|
673
|
+
if key not in declared_names:
|
|
674
|
+
raise Exception(
|
|
675
|
+
f'Unknown prediction name "{key}" — declared prediction types: '
|
|
676
|
+
f'{declared_names}.')
|
|
677
|
+
key = declared_names.index(key)
|
|
635
678
|
|
|
636
679
|
ret = TempMapping()
|
|
637
680
|
try:
|
|
@@ -685,7 +728,10 @@ def tensorleap_load_model(prediction_types: Optional[List[PredictionTypeHandler]
|
|
|
685
728
|
|
|
686
729
|
return FollowInputIndex()
|
|
687
730
|
|
|
688
|
-
|
|
731
|
+
model_placeholder = ModelPlaceholder()
|
|
732
|
+
global _last_loaded_model
|
|
733
|
+
_last_loaded_model = model_placeholder
|
|
734
|
+
return model_placeholder
|
|
689
735
|
|
|
690
736
|
def final_inner(*args, **kwargs):
|
|
691
737
|
if os.environ.get(mapping_runtime_mode_env_var_mame):
|
|
@@ -1712,18 +1758,263 @@ def tensorleap_custom_latent_space():
|
|
|
1712
1758
|
return decorating_function
|
|
1713
1759
|
|
|
1714
1760
|
|
|
1761
|
+
_MODEL_LOOP_MAX_STEPS = 1000
|
|
1762
|
+
|
|
1763
|
+
_active_model_loop = None
|
|
1764
|
+
_model_loop_run_in_current_test = False
|
|
1765
|
+
_last_loaded_model = None
|
|
1766
|
+
_chain_artifact_provenance: Dict[int, tuple] = {}
|
|
1767
|
+
|
|
1768
|
+
_validate_state_types = validate_autoregressive_state_types
|
|
1769
|
+
_nest_equal = autoregressive_nests_equal
|
|
1770
|
+
|
|
1771
|
+
|
|
1772
|
+
def _nest_fingerprint(value):
|
|
1773
|
+
digest = hashlib.sha1()
|
|
1774
|
+
|
|
1775
|
+
def _feed(item):
|
|
1776
|
+
if isinstance(item, dict):
|
|
1777
|
+
digest.update(b'{')
|
|
1778
|
+
for key in sorted(item):
|
|
1779
|
+
digest.update(str(key).encode())
|
|
1780
|
+
digest.update(b':')
|
|
1781
|
+
_feed(item[key])
|
|
1782
|
+
digest.update(b'}')
|
|
1783
|
+
elif isinstance(item, (list, tuple)):
|
|
1784
|
+
digest.update(b'[')
|
|
1785
|
+
for element in item:
|
|
1786
|
+
_feed(element)
|
|
1787
|
+
digest.update(b']')
|
|
1788
|
+
elif isinstance(item, np.ndarray):
|
|
1789
|
+
digest.update(str(item.dtype).encode())
|
|
1790
|
+
digest.update(str(item.shape).encode())
|
|
1791
|
+
digest.update(np.ascontiguousarray(item).tobytes())
|
|
1792
|
+
else:
|
|
1793
|
+
digest.update(repr(item).encode())
|
|
1794
|
+
|
|
1795
|
+
_feed(value)
|
|
1796
|
+
return digest.hexdigest()
|
|
1797
|
+
|
|
1798
|
+
|
|
1799
|
+
def _ndarray_leaves(value):
|
|
1800
|
+
if isinstance(value, np.ndarray):
|
|
1801
|
+
return [value]
|
|
1802
|
+
if isinstance(value, dict):
|
|
1803
|
+
return [leaf for item in value.values() for leaf in _ndarray_leaves(item)]
|
|
1804
|
+
if isinstance(value, (list, tuple)):
|
|
1805
|
+
return [leaf for item in value for leaf in _ndarray_leaves(item)]
|
|
1806
|
+
return []
|
|
1807
|
+
|
|
1808
|
+
|
|
1809
|
+
def _squeeze_leading_batch_axis(tensors_dict):
|
|
1810
|
+
if tensors_dict is None:
|
|
1811
|
+
return None
|
|
1812
|
+
squeezed = {}
|
|
1813
|
+
for key, value in tensors_dict.items():
|
|
1814
|
+
if isinstance(value, np.ndarray) and value.ndim > 0 and value.shape[0] == 1:
|
|
1815
|
+
squeezed[key] = value[0]
|
|
1816
|
+
else:
|
|
1817
|
+
squeezed[key] = value
|
|
1818
|
+
return squeezed
|
|
1819
|
+
|
|
1820
|
+
|
|
1821
|
+
def _register_chain_artifact(obj, kind):
|
|
1822
|
+
# The object reference is kept in the registry entry so its id() cannot be recycled by the GC.
|
|
1823
|
+
_chain_artifact_provenance[id(obj)] = (kind, _nest_fingerprint(obj), obj)
|
|
1824
|
+
|
|
1825
|
+
|
|
1826
|
+
def _lookup_chain_artifact(obj):
|
|
1827
|
+
entry = _chain_artifact_provenance.get(id(obj))
|
|
1828
|
+
if entry is None or entry[2] is not obj:
|
|
1829
|
+
return None
|
|
1830
|
+
return entry
|
|
1831
|
+
|
|
1832
|
+
|
|
1833
|
+
def _reset_model_loop_state():
|
|
1834
|
+
global _active_model_loop, _model_loop_run_in_current_test
|
|
1835
|
+
_active_model_loop = None
|
|
1836
|
+
_model_loop_run_in_current_test = False
|
|
1837
|
+
_chain_artifact_provenance.clear()
|
|
1838
|
+
|
|
1839
|
+
|
|
1840
|
+
class _MappingStatePlaceholder:
|
|
1841
|
+
def _unsupported(self):
|
|
1842
|
+
raise LeapValidationError(
|
|
1843
|
+
'The autoregressive state is opaque inside the integration test — pass it whole '
|
|
1844
|
+
'between the hook, the model loop and the autoregressive metrics. Read or derive '
|
|
1845
|
+
'state content inside the hook or inside the metric/visualizer body.')
|
|
1846
|
+
|
|
1847
|
+
def __getitem__(self, key):
|
|
1848
|
+
self._unsupported()
|
|
1849
|
+
|
|
1850
|
+
def __getattr__(self, name):
|
|
1851
|
+
raise AttributeError(name)
|
|
1852
|
+
|
|
1853
|
+
def __iter__(self):
|
|
1854
|
+
self._unsupported()
|
|
1855
|
+
|
|
1856
|
+
|
|
1857
|
+
class _ModelLoopContext:
|
|
1858
|
+
def __init__(self, sample_id, preprocess_response, prediction_names, is_mapping):
|
|
1859
|
+
self.sample_id = sample_id
|
|
1860
|
+
self.preprocess_response = preprocess_response
|
|
1861
|
+
self.prediction_names = prediction_names
|
|
1862
|
+
self.is_mapping = is_mapping
|
|
1863
|
+
self.phase = 'awaiting_first_hook'
|
|
1864
|
+
self.steps = 0
|
|
1865
|
+
self.fed_inputs = None
|
|
1866
|
+
self.fed_inputs_fingerprint = None
|
|
1867
|
+
self.last_outputs = None
|
|
1868
|
+
self.last_outputs_fingerprint = None
|
|
1869
|
+
self.last_state = None
|
|
1870
|
+
self.last_state_fingerprint = None
|
|
1871
|
+
|
|
1872
|
+
def _fail(self, message):
|
|
1873
|
+
raise LeapValidationError(f'tensorleap_model_loop validation failed: {message}')
|
|
1874
|
+
|
|
1875
|
+
def on_hook_call(self, sample_id, prev_inputs, prev_outputs, state, preprocess_response):
|
|
1876
|
+
if self.phase == 'ended':
|
|
1877
|
+
self._fail('the chain already ended — once the autoregressive step returns None for '
|
|
1878
|
+
'next_inputs the loop must exit without further hook or model calls.')
|
|
1879
|
+
if self.phase == 'awaiting_model':
|
|
1880
|
+
self._fail('two autoregressive step calls in a row — the model must run exactly once '
|
|
1881
|
+
'between hook calls.')
|
|
1882
|
+
if sample_id != self.sample_id:
|
|
1883
|
+
self._fail('the hook must be called with the sample_id the model loop received.')
|
|
1884
|
+
if preprocess_response is not self.preprocess_response:
|
|
1885
|
+
self._fail('the hook must be called with the preprocess response the model loop '
|
|
1886
|
+
'received.')
|
|
1887
|
+
if self.phase == 'awaiting_first_hook':
|
|
1888
|
+
if prev_inputs is not None or prev_outputs is not None or state is not None:
|
|
1889
|
+
self._fail('the first hook call inside the model loop must be the initial call: '
|
|
1890
|
+
'prev_inputs=None, prev_outputs=None, state=None.')
|
|
1891
|
+
return
|
|
1892
|
+
if prev_inputs is not self.fed_inputs:
|
|
1893
|
+
self._fail('prev_inputs must be exactly the inputs dict the previous hook call '
|
|
1894
|
+
'returned (the one fed to the model) — do not copy, rebuild or slice it.')
|
|
1895
|
+
if prev_outputs is not self.last_outputs:
|
|
1896
|
+
self._fail('prev_outputs must be exactly the outputs dict the model call returned — '
|
|
1897
|
+
'do not copy, rebuild or slice it.')
|
|
1898
|
+
if state is not self.last_state:
|
|
1899
|
+
self._fail('state must be exactly the state object the previous hook call returned.')
|
|
1900
|
+
if _nest_fingerprint(prev_inputs) != self.fed_inputs_fingerprint:
|
|
1901
|
+
self._fail('the model inputs were mutated in place after being fed to the model — '
|
|
1902
|
+
'the platform re-reads the stored step tensors, so local mutations diverge '
|
|
1903
|
+
'from platform results. Compute new tensors inside the hook instead.')
|
|
1904
|
+
if _nest_fingerprint(prev_outputs) != self.last_outputs_fingerprint:
|
|
1905
|
+
self._fail('the model outputs were mutated in place after the model call — the '
|
|
1906
|
+
'platform re-reads the stored step tensors, so local mutations diverge '
|
|
1907
|
+
'from platform results. Compute derived tensors inside the hook instead.')
|
|
1908
|
+
if _nest_fingerprint(state) != self.last_state_fingerprint:
|
|
1909
|
+
self._fail('the state object was mutated between hook calls — state may only change '
|
|
1910
|
+
'inside the hook body.')
|
|
1911
|
+
|
|
1912
|
+
def on_hook_return(self, next_inputs, state):
|
|
1913
|
+
self.steps += 1
|
|
1914
|
+
if self.steps > _MODEL_LOOP_MAX_STEPS:
|
|
1915
|
+
self._fail(f'the chain exceeded {_MODEL_LOOP_MAX_STEPS} steps without the '
|
|
1916
|
+
f'autoregressive step returning None — the loop would never terminate on '
|
|
1917
|
+
f'the platform.')
|
|
1918
|
+
self.last_state = state
|
|
1919
|
+
self.last_state_fingerprint = _nest_fingerprint(state)
|
|
1920
|
+
if next_inputs is None:
|
|
1921
|
+
self.phase = 'ended'
|
|
1922
|
+
else:
|
|
1923
|
+
self.fed_inputs = next_inputs
|
|
1924
|
+
self.fed_inputs_fingerprint = _nest_fingerprint(next_inputs)
|
|
1925
|
+
self.phase = 'awaiting_model'
|
|
1926
|
+
|
|
1927
|
+
def mapping_hook_call(self, inputs_placeholder, state_placeholder):
|
|
1928
|
+
if self.phase == 'ended':
|
|
1929
|
+
self._fail('the chain already ended — once the autoregressive step returns None for '
|
|
1930
|
+
'next_inputs the loop must exit without further hook or model calls.')
|
|
1931
|
+
if self.phase == 'awaiting_model':
|
|
1932
|
+
self._fail('two autoregressive step calls in a row — the model must run exactly once '
|
|
1933
|
+
'between hook calls.')
|
|
1934
|
+
self.last_state = state_placeholder
|
|
1935
|
+
if self.phase == 'awaiting_first_hook':
|
|
1936
|
+
self.fed_inputs = inputs_placeholder
|
|
1937
|
+
self.phase = 'awaiting_model'
|
|
1938
|
+
return inputs_placeholder, state_placeholder
|
|
1939
|
+
self.phase = 'ended'
|
|
1940
|
+
return None, state_placeholder
|
|
1941
|
+
|
|
1942
|
+
def on_model_call(self, fed):
|
|
1943
|
+
if self.phase == 'awaiting_first_hook':
|
|
1944
|
+
self._fail('the model was called before the autoregressive step produced the initial '
|
|
1945
|
+
'inputs — the loop must start with the initial hook call '
|
|
1946
|
+
'(prev_inputs=None, prev_outputs=None, state=None).')
|
|
1947
|
+
if self.phase == 'awaiting_hook':
|
|
1948
|
+
self._fail('the model was called twice in a row — the autoregressive step must run '
|
|
1949
|
+
'between model calls.')
|
|
1950
|
+
if self.phase == 'ended':
|
|
1951
|
+
self._fail('the chain already ended — once the autoregressive step returns None for '
|
|
1952
|
+
'next_inputs the loop must exit without further hook or model calls.')
|
|
1953
|
+
if self.is_mapping:
|
|
1954
|
+
return
|
|
1955
|
+
fed_ids = {id(leaf) for leaf in _ndarray_leaves(fed)}
|
|
1956
|
+
hook_ids = {id(value) for value in self.fed_inputs.values()}
|
|
1957
|
+
if fed_ids != hook_ids:
|
|
1958
|
+
self._fail('the model must be fed exactly the tensors the last hook call returned — '
|
|
1959
|
+
'any computation between the hook and the model is invisible to the '
|
|
1960
|
+
'platform. Move it into the hook.')
|
|
1961
|
+
if _nest_fingerprint(self.fed_inputs) != self.fed_inputs_fingerprint:
|
|
1962
|
+
self._fail('the model inputs were mutated in place after the hook returned them — '
|
|
1963
|
+
'the platform feeds the model the tensors exactly as the hook returned '
|
|
1964
|
+
'them. Compute new tensors inside the hook instead.')
|
|
1965
|
+
|
|
1966
|
+
def on_model_return(self, raw_outputs):
|
|
1967
|
+
self.phase = 'awaiting_hook'
|
|
1968
|
+
if self.is_mapping:
|
|
1969
|
+
self.last_outputs = raw_outputs
|
|
1970
|
+
return raw_outputs
|
|
1971
|
+
outputs_list = raw_outputs if isinstance(raw_outputs, list) else [raw_outputs]
|
|
1972
|
+
if len(outputs_list) != len(self.prediction_names):
|
|
1973
|
+
self._fail(f'the model returned {len(outputs_list)} outputs but '
|
|
1974
|
+
f'{len(self.prediction_names)} prediction types are declared on '
|
|
1975
|
+
f'tensorleap_load_model — declare one prediction type per model output.')
|
|
1976
|
+
named_outputs = {name: np.asarray(output)
|
|
1977
|
+
for name, output in zip(self.prediction_names, outputs_list)}
|
|
1978
|
+
self.last_outputs = named_outputs
|
|
1979
|
+
self.last_outputs_fingerprint = _nest_fingerprint(named_outputs)
|
|
1980
|
+
return named_outputs
|
|
1981
|
+
|
|
1982
|
+
|
|
1983
|
+
class _ModelLoopModelProxy:
|
|
1984
|
+
def __init__(self, model, context):
|
|
1985
|
+
self._model = model
|
|
1986
|
+
self._context = context
|
|
1987
|
+
|
|
1988
|
+
def __call__(self, inputs):
|
|
1989
|
+
self._context.on_model_call(inputs)
|
|
1990
|
+
return self._context.on_model_return(self._model(inputs))
|
|
1991
|
+
|
|
1992
|
+
def run(self, output_names, input_dict):
|
|
1993
|
+
self._context.on_model_call(input_dict)
|
|
1994
|
+
return self._context.on_model_return(self._model.run(output_names, input_dict))
|
|
1995
|
+
|
|
1996
|
+
def get_inputs(self):
|
|
1997
|
+
return self._model.get_inputs()
|
|
1998
|
+
|
|
1999
|
+
|
|
1715
2000
|
def tensorleap_autoregressive_step(latent_space_aggregation: str = 'last_step'):
|
|
1716
2001
|
"""The feedback hook that drives an autoregressive chain.
|
|
1717
2002
|
|
|
1718
2003
|
Signature of the decorated function:
|
|
1719
|
-
(sample_id, prev_inputs, prev_outputs, preprocess)
|
|
1720
|
-
|
|
1721
|
-
|
|
1722
|
-
|
|
1723
|
-
|
|
1724
|
-
|
|
1725
|
-
|
|
1726
|
-
the
|
|
2004
|
+
(sample_id, prev_inputs, prev_outputs, state, preprocess)
|
|
2005
|
+
-> (Optional[Dict[str, np.ndarray]], state)
|
|
2006
|
+
|
|
2007
|
+
First call per chain: prev_inputs=None, prev_outputs=None, state=None — returns the initial
|
|
2008
|
+
model inputs and the initial state. Later calls receive the previous step's model inputs and
|
|
2009
|
+
outputs (unbatched, per-sample; outputs keyed by the declared prediction-type names) plus the
|
|
2010
|
+
state returned by the previous call, and return the next step's full input dict together with
|
|
2011
|
+
the updated state; returning (None, state) ends the chain — the terminating call still
|
|
2012
|
+
returns state, so the final step participates in state aggregation. State is never fed to
|
|
2013
|
+
the model; any picklable nest of builtin/numpy values works (dicts, lists, tuples, sets,
|
|
2014
|
+
numbers, strings, arrays — not user-defined classes, lambdas or open resources, since the
|
|
2015
|
+
platform deserializes state without the integration code), and it rides the platform queue
|
|
2016
|
+
on every step — accumulate reductions, not raw per-step tensors. The hook must be stateless and deterministically seeded (seed stochasticity from
|
|
2017
|
+
sample_id) — the engine replays steps after crash recovery and relies on identical results.
|
|
1727
2018
|
|
|
1728
2019
|
latent_space_aggregation ('last_step' | 'mean') declares how the chain's latent-space
|
|
1729
2020
|
vectors are derived from its per-step forward passes. 'last_step' (default): every latent
|
|
@@ -1742,62 +2033,95 @@ def tensorleap_autoregressive_step(latent_space_aggregation: str = 'last_step'):
|
|
|
1742
2033
|
first_call_record: Dict[str, Any] = {'keys': None, 'shapes': None}
|
|
1743
2034
|
|
|
1744
2035
|
def _normalize_args(args, kwargs):
|
|
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)
|
|
2036
|
+
expected_names = ["sample_id", "prev_inputs", "prev_outputs", "state", "preprocess"]
|
|
1751
2037
|
normalized = []
|
|
1752
|
-
for i,
|
|
1753
|
-
|
|
2038
|
+
for i, arg_name in enumerate(expected_names):
|
|
2039
|
+
if i < len(args):
|
|
2040
|
+
normalized.append(args[i])
|
|
2041
|
+
elif arg_name in kwargs:
|
|
2042
|
+
normalized.append(kwargs[arg_name])
|
|
2043
|
+
else:
|
|
2044
|
+
raise AssertionError(
|
|
2045
|
+
f'{user_function.__name__} validation failed: missing required argument '
|
|
2046
|
+
f"'{arg_name}'. Expected arguments: {expected_names}.")
|
|
2047
|
+
if len(args) + len(kwargs) != len(expected_names):
|
|
2048
|
+
raise AssertionError(
|
|
2049
|
+
f'{user_function.__name__} validation failed: expected exactly '
|
|
2050
|
+
f'{len(expected_names)} arguments {expected_names}, got '
|
|
2051
|
+
f'{len(args) + len(kwargs)}.')
|
|
2052
|
+
sample_id, prev_inputs, prev_outputs, state, preprocess_response = normalized
|
|
2053
|
+
assert isinstance(sample_id, (int, str)), \
|
|
2054
|
+
(f'{user_function.__name__}() validation failed: '
|
|
2055
|
+
f'sample_id must be an int or str. Got {type(sample_id).__name__}.')
|
|
2056
|
+
assert prev_inputs is None or isinstance(prev_inputs, dict), \
|
|
2057
|
+
(f'{user_function.__name__}() validation failed: '
|
|
2058
|
+
f'prev_inputs must be a dict or None. Got {type(prev_inputs).__name__}.')
|
|
2059
|
+
assert prev_outputs is None or isinstance(prev_outputs, dict), \
|
|
2060
|
+
(f'{user_function.__name__}() validation failed: '
|
|
2061
|
+
f'prev_outputs must be a dict or None. Got {type(prev_outputs).__name__}.')
|
|
2062
|
+
assert isinstance(preprocess_response, PreprocessResponse), \
|
|
2063
|
+
(f'{user_function.__name__}() validation failed: '
|
|
2064
|
+
f'preprocess must be a PreprocessResponse. Got '
|
|
2065
|
+
f'{type(preprocess_response).__name__}.')
|
|
1754
2066
|
return normalized
|
|
1755
2067
|
|
|
1756
|
-
def _validate_input_args(sample_id, prev_inputs, prev_outputs, preprocess_response):
|
|
2068
|
+
def _validate_input_args(sample_id, prev_inputs, prev_outputs, state, preprocess_response):
|
|
1757
2069
|
assert (prev_inputs is None) == (prev_outputs is None), \
|
|
1758
2070
|
(f'{user_function.__name__}() validation failed: '
|
|
1759
2071
|
f'prev_inputs and prev_outputs must both be None (first call) or both be dicts '
|
|
1760
2072
|
f'(subsequent calls). Got prev_inputs={type(prev_inputs).__name__}, '
|
|
1761
2073
|
f'prev_outputs={type(prev_outputs).__name__}.')
|
|
2074
|
+
assert not (prev_inputs is None and state is not None), \
|
|
2075
|
+
(f'{user_function.__name__}() validation failed: '
|
|
2076
|
+
f'the first call (prev_inputs=None, prev_outputs=None) must pass state=None. '
|
|
2077
|
+
f'Got state of type {type(state).__name__}.')
|
|
1762
2078
|
assert type(sample_id) == preprocess_response.sample_id_type, \
|
|
1763
2079
|
(f'{user_function.__name__}() validation failed: '
|
|
1764
2080
|
f'Argument sample_id should be as the same type as defined in the preprocess response '
|
|
1765
2081
|
f'{preprocess_response.sample_id_type}. Got {type(sample_id)}.')
|
|
1766
2082
|
|
|
1767
2083
|
def _validate_result(result, is_first_call):
|
|
1768
|
-
|
|
2084
|
+
assert isinstance(result, tuple) and len(result) == 2, \
|
|
2085
|
+
(f'{user_function.__name__}() validation failed: '
|
|
2086
|
+
f'the hook must return a (next_inputs, state) tuple — next_inputs is a dict of '
|
|
2087
|
+
f'named model input tensors, or None to end the chain; state rides to the next '
|
|
2088
|
+
f'call. Got {type(result).__name__}.')
|
|
2089
|
+
next_inputs, state = result
|
|
2090
|
+
if next_inputs is None:
|
|
1769
2091
|
assert not is_first_call, \
|
|
1770
2092
|
(f'{user_function.__name__}() validation failed: '
|
|
1771
|
-
f'the first call (prev_inputs=None, prev_outputs=None) must return
|
|
1772
|
-
f'model inputs dict — returning None on the first call would
|
|
2093
|
+
f'the first call (prev_inputs=None, prev_outputs=None, state=None) must return '
|
|
2094
|
+
f'the initial model inputs dict — returning None on the first call would '
|
|
2095
|
+
f'produce an empty chain.')
|
|
1773
2096
|
return
|
|
1774
|
-
assert isinstance(
|
|
2097
|
+
assert isinstance(next_inputs, dict), \
|
|
1775
2098
|
(f'{user_function.__name__}() validation failed: '
|
|
1776
|
-
f'
|
|
1777
|
-
f'chain. Got {type(
|
|
1778
|
-
|
|
1779
|
-
assert model_input_keys, \
|
|
2099
|
+
f'next_inputs must be a dict of named model input tensors, or None to end the '
|
|
2100
|
+
f'chain. Got {type(next_inputs)}.')
|
|
2101
|
+
assert next_inputs, \
|
|
1780
2102
|
(f'{user_function.__name__}() validation failed: '
|
|
1781
|
-
f'
|
|
1782
|
-
f'
|
|
1783
|
-
for key, value in
|
|
2103
|
+
f'next_inputs is an empty dict — return the full model input dict, or None to '
|
|
2104
|
+
f'end the chain.')
|
|
2105
|
+
for key, value in next_inputs.items():
|
|
1784
2106
|
assert isinstance(key, str), \
|
|
1785
2107
|
(f'{user_function.__name__}() validation failed: '
|
|
1786
|
-
f'all keys in
|
|
1787
|
-
|
|
1788
|
-
|
|
2108
|
+
f'all keys in next_inputs must be strings. Got {type(key)}.')
|
|
2109
|
+
assert not key.startswith('_'), \
|
|
2110
|
+
(f'{user_function.__name__}() validation failed: '
|
|
2111
|
+
f'next_inputs key "{key}" starts with "_" — underscore passthrough keys were '
|
|
2112
|
+
f'replaced by the explicit state channel. Move passthrough values into the '
|
|
2113
|
+
f'returned state.')
|
|
1789
2114
|
assert isinstance(value, np.ndarray), \
|
|
1790
2115
|
(f'{user_function.__name__}() validation failed: '
|
|
1791
2116
|
f'value for model input "{key}" must be a numpy array. Got {type(value)}.')
|
|
1792
2117
|
|
|
1793
|
-
def _validate_consistency(
|
|
1794
|
-
# Rules 2-3 of the autoregressive contract: identical key set and identical
|
|
1795
|
-
#
|
|
1796
|
-
if
|
|
2118
|
+
def _validate_consistency(next_inputs):
|
|
2119
|
+
# Rules 2-3 of the autoregressive contract: identical key set and identical shapes on
|
|
2120
|
+
# every call — compiled graphs need static shapes.
|
|
2121
|
+
if next_inputs is None:
|
|
1797
2122
|
return
|
|
1798
|
-
keys = set(
|
|
1799
|
-
shapes = {key: tuple(value.shape) for key, value in
|
|
1800
|
-
if not key.startswith('_') and isinstance(value, np.ndarray)}
|
|
2123
|
+
keys = set(next_inputs.keys())
|
|
2124
|
+
shapes = {key: tuple(value.shape) for key, value in next_inputs.items()}
|
|
1801
2125
|
if first_call_record['keys'] is None:
|
|
1802
2126
|
first_call_record['keys'] = keys
|
|
1803
2127
|
first_call_record['shapes'] = shapes
|
|
@@ -1819,36 +2143,26 @@ def tensorleap_autoregressive_step(latent_space_aggregation: str = 'last_step'):
|
|
|
1819
2143
|
f'the hook is not deterministic — two calls with identical arguments returned different '
|
|
1820
2144
|
f'outputs. Seed any stochasticity (e.g. initial noise) from sample_id: the engine '
|
|
1821
2145
|
f'replays steps after crash recovery and relies on identical results.')
|
|
1822
|
-
|
|
1823
|
-
|
|
1824
|
-
|
|
1825
|
-
|
|
1826
|
-
|
|
1827
|
-
|
|
1828
|
-
|
|
1829
|
-
assert
|
|
1830
|
-
|
|
1831
|
-
|
|
1832
|
-
|
|
1833
|
-
|
|
1834
|
-
|
|
1835
|
-
|
|
1836
|
-
|
|
1837
|
-
return None
|
|
1838
|
-
squeezed = {}
|
|
1839
|
-
for key, value in tensors_dict.items():
|
|
1840
|
-
if not key.startswith('_') and isinstance(value, np.ndarray) \
|
|
1841
|
-
and value.ndim > 0 and value.shape[0] == 1:
|
|
1842
|
-
squeezed[key] = value[0]
|
|
1843
|
-
else:
|
|
1844
|
-
squeezed[key] = value
|
|
1845
|
-
return squeezed
|
|
1846
|
-
|
|
1847
|
-
def inner_without_validate(sample_id, prev_inputs, prev_outputs, preprocess_response):
|
|
2146
|
+
assert isinstance(first_result, tuple) and isinstance(second_result, tuple) and \
|
|
2147
|
+
len(first_result) == 2 and len(second_result) == 2, error_message
|
|
2148
|
+
first_inputs, first_state = first_result
|
|
2149
|
+
second_inputs, second_state = second_result
|
|
2150
|
+
if first_inputs is None or second_inputs is None:
|
|
2151
|
+
assert first_inputs is None and second_inputs is None, error_message
|
|
2152
|
+
else:
|
|
2153
|
+
assert set(first_inputs.keys()) == set(second_inputs.keys()), error_message
|
|
2154
|
+
for key, value in first_inputs.items():
|
|
2155
|
+
if not isinstance(value, np.ndarray):
|
|
2156
|
+
continue
|
|
2157
|
+
assert _nest_equal(value, second_inputs[key]), error_message
|
|
2158
|
+
assert _nest_equal(first_state, second_state), error_message
|
|
2159
|
+
|
|
2160
|
+
def inner_without_validate(sample_id, prev_inputs, prev_outputs, state, preprocess_response):
|
|
1848
2161
|
global _called_from_inside_tl_decorator
|
|
1849
2162
|
_called_from_inside_tl_decorator += 1
|
|
1850
2163
|
try:
|
|
1851
|
-
result = user_function(sample_id, prev_inputs, prev_outputs,
|
|
2164
|
+
result = user_function(sample_id, prev_inputs, prev_outputs, state,
|
|
2165
|
+
preprocess_response)
|
|
1852
2166
|
finally:
|
|
1853
2167
|
_called_from_inside_tl_decorator -= 1
|
|
1854
2168
|
return result
|
|
@@ -1859,43 +2173,69 @@ def tensorleap_autoregressive_step(latent_space_aggregation: str = 'last_step'):
|
|
|
1859
2173
|
def inner(*args, **kwargs):
|
|
1860
2174
|
if not _call_from_tl_platform:
|
|
1861
2175
|
set_current('tensorleap_autoregressive_step')
|
|
1862
|
-
sample_id, prev_inputs, prev_outputs, preprocess_response =
|
|
2176
|
+
sample_id, prev_inputs, prev_outputs, state, preprocess_response = \
|
|
2177
|
+
_normalize_args(args, kwargs)
|
|
1863
2178
|
|
|
1864
|
-
|
|
2179
|
+
is_top_level = _called_from_inside_tl_decorator == 0
|
|
2180
|
+
is_top_level_in_test = (is_top_level
|
|
1865
2181
|
and _called_from_inside_tl_integration_test_decorator)
|
|
2182
|
+
loop_context = _active_model_loop
|
|
2183
|
+
if is_top_level_in_test and loop_context is None:
|
|
2184
|
+
raise LeapValidationError(
|
|
2185
|
+
f'{user_function.__name__}() validation failed: inside the integration '
|
|
2186
|
+
f'test the autoregressive step may only be called from within the '
|
|
2187
|
+
f'tensorleap_model_loop function — including the initial call '
|
|
2188
|
+
f'(prev_inputs=None). The platform drives the chain through the model '
|
|
2189
|
+
f'loop only, so a hook call outside it would not run on the platform.')
|
|
2190
|
+
# Outside the integration test a model loop may still be running (local debugging);
|
|
2191
|
+
# the loop's phase machine must advance there too, or its model proxy always raises.
|
|
2192
|
+
notify_loop = is_top_level and loop_context is not None
|
|
2193
|
+
if notify_loop:
|
|
2194
|
+
loop_context.on_hook_call(sample_id, prev_inputs, prev_outputs, state,
|
|
2195
|
+
preprocess_response)
|
|
1866
2196
|
if is_top_level_in_test:
|
|
1867
|
-
|
|
1868
|
-
|
|
2197
|
+
# The wrapper adds the batch axis to returned inputs and model outputs come back
|
|
2198
|
+
# batched; the hook body must see the same unbatched per-sample tensors it will
|
|
2199
|
+
# see at engine runtime.
|
|
2200
|
+
prev_inputs = _squeeze_leading_batch_axis(prev_inputs)
|
|
2201
|
+
prev_outputs = _squeeze_leading_batch_axis(prev_outputs)
|
|
1869
2202
|
|
|
1870
|
-
_validate_input_args(sample_id, prev_inputs, prev_outputs, preprocess_response)
|
|
2203
|
+
_validate_input_args(sample_id, prev_inputs, prev_outputs, state, preprocess_response)
|
|
1871
2204
|
|
|
1872
|
-
|
|
2205
|
+
# The deep copy keeps the determinism double-call independent: a hook that
|
|
2206
|
+
# accumulates into state in place would otherwise see its own first-call mutations.
|
|
2207
|
+
state_for_second_call = copy.deepcopy(state) if is_top_level_in_test else None
|
|
2208
|
+
result = inner_without_validate(sample_id, prev_inputs, prev_outputs, state,
|
|
2209
|
+
preprocess_response)
|
|
1873
2210
|
|
|
1874
2211
|
if is_top_level_in_test:
|
|
1875
2212
|
# Rule 7: determinism double-call. An unseeded sampler fails here, on the user's
|
|
1876
2213
|
# machine, instead of silently forking a chain on engine crash-replay.
|
|
1877
2214
|
second_result = inner_without_validate(sample_id, prev_inputs, prev_outputs,
|
|
1878
|
-
preprocess_response)
|
|
2215
|
+
state_for_second_call, preprocess_response)
|
|
1879
2216
|
_assert_deterministic(result, second_result)
|
|
1880
2217
|
|
|
1881
2218
|
_validate_result(result, is_first_call=prev_inputs is None)
|
|
1882
|
-
|
|
2219
|
+
next_inputs, new_state = result
|
|
2220
|
+
_validate_consistency(next_inputs)
|
|
2221
|
+
_validate_state_types(new_state)
|
|
1883
2222
|
|
|
1884
|
-
if is_top_level_in_test and
|
|
1885
|
-
|
|
1886
|
-
|
|
1887
|
-
|
|
2223
|
+
if is_top_level_in_test and next_inputs is not None:
|
|
2224
|
+
next_inputs = {key: np.expand_dims(value, axis=0)
|
|
2225
|
+
for key, value in next_inputs.items()}
|
|
2226
|
+
if notify_loop:
|
|
2227
|
+
loop_context.on_hook_return(next_inputs, new_state)
|
|
1888
2228
|
|
|
1889
2229
|
if not _call_from_tl_platform:
|
|
1890
2230
|
update_env_params_func("tensorleap_autoregressive_step", "v")
|
|
1891
|
-
return
|
|
2231
|
+
return next_inputs, new_state
|
|
1892
2232
|
|
|
1893
2233
|
class _MappingInputsPlaceholder:
|
|
1894
|
-
"""Mapping-mode stand-in for the hook's returned dict.
|
|
2234
|
+
"""Mapping-mode stand-in for the hook's returned inputs dict.
|
|
1895
2235
|
|
|
1896
2236
|
Indexing it by a model-input name yields a placeholder whose node_mapping the model
|
|
1897
2237
|
placeholder tags Input0/Input1/... — exactly how input encoders wire the graph. One
|
|
1898
|
-
NodeConnection is recorded per
|
|
2238
|
+
NodeConnection is recorded per key, once.
|
|
1899
2239
|
"""
|
|
1900
2240
|
def __init__(self):
|
|
1901
2241
|
self._items: Dict[str, Any] = {}
|
|
@@ -1908,8 +2248,7 @@ def tensorleap_autoregressive_step(latent_space_aggregation: str = 'last_step'):
|
|
|
1908
2248
|
pass
|
|
1909
2249
|
ret = TempMapping()
|
|
1910
2250
|
ret.node_mapping = NodeMapping(key, NodeMappingType.Input)
|
|
1911
|
-
|
|
1912
|
-
leap_binder.mapping_connections.append(NodeConnection(ret.node_mapping, None))
|
|
2251
|
+
leap_binder.mapping_connections.append(NodeConnection(ret.node_mapping, None))
|
|
1913
2252
|
self._items[key] = ret
|
|
1914
2253
|
return self._items[key]
|
|
1915
2254
|
|
|
@@ -1934,9 +2273,17 @@ def tensorleap_autoregressive_step(latent_space_aggregation: str = 'last_step'):
|
|
|
1934
2273
|
self._unsupported_iteration()
|
|
1935
2274
|
|
|
1936
2275
|
mapping_placeholder = _MappingInputsPlaceholder()
|
|
2276
|
+
mapping_state_placeholder = _MappingStatePlaceholder()
|
|
1937
2277
|
|
|
1938
2278
|
def mapping_inner(*args, **kwargs):
|
|
1939
|
-
|
|
2279
|
+
loop_context = _active_model_loop
|
|
2280
|
+
if loop_context is None:
|
|
2281
|
+
raise LeapValidationError(
|
|
2282
|
+
f'{user_function.__name__}() validation failed: inside the integration test '
|
|
2283
|
+
f'the autoregressive step may only be called from within the '
|
|
2284
|
+
f'tensorleap_model_loop function — including the initial call '
|
|
2285
|
+
f'(prev_inputs=None).')
|
|
2286
|
+
return loop_context.mapping_hook_call(mapping_placeholder, mapping_state_placeholder)
|
|
1940
2287
|
|
|
1941
2288
|
def final_inner(*args, **kwargs):
|
|
1942
2289
|
if os.environ.get(mapping_runtime_mode_env_var_mame):
|
|
@@ -1952,6 +2299,396 @@ def tensorleap_autoregressive_step(latent_space_aggregation: str = 'last_step'):
|
|
|
1952
2299
|
return decorating_function
|
|
1953
2300
|
|
|
1954
2301
|
|
|
2302
|
+
def tensorleap_model_loop():
|
|
2303
|
+
"""The user-authored autoregressive rollout loop.
|
|
2304
|
+
|
|
2305
|
+
Signature of the decorated function:
|
|
2306
|
+
(model, sample_id, preprocess) -> (final_inputs, final_outputs, final_state)
|
|
2307
|
+
|
|
2308
|
+
The function drives the chain locally, exactly as the platform will: it makes the initial
|
|
2309
|
+
hook call (prev_inputs=None, prev_outputs=None, state=None) inside the loop, alternates
|
|
2310
|
+
model forwards with hook calls, feeds the model exactly the tensors the hook returned, and
|
|
2311
|
+
exits when the hook returns None for next_inputs — returning the last inputs dict fed to the
|
|
2312
|
+
model, the last model outputs (a dict keyed by the declared prediction-type names) and the
|
|
2313
|
+
final state. The loop body itself never runs on the platform — the engine drives the chain —
|
|
2314
|
+
so the decorator validates, while the loop runs, that its behavior matches the engine loop;
|
|
2315
|
+
any computation in the loop body outside the hook and the model is invisible to the platform
|
|
2316
|
+
and fails validation. Canonical shape:
|
|
2317
|
+
|
|
2318
|
+
@tensorleap_model_loop()
|
|
2319
|
+
def model_loop(model, sample_id, preprocess):
|
|
2320
|
+
inputs, state = generate_next_inputs(sample_id, None, None, None, preprocess)
|
|
2321
|
+
while True:
|
|
2322
|
+
outputs = model(inputs)
|
|
2323
|
+
next_inputs, state = generate_next_inputs(sample_id, inputs, outputs, state,
|
|
2324
|
+
preprocess)
|
|
2325
|
+
if next_inputs is None:
|
|
2326
|
+
return inputs, outputs, state
|
|
2327
|
+
inputs = next_inputs
|
|
2328
|
+
"""
|
|
2329
|
+
|
|
2330
|
+
def decorating_function(user_function):
|
|
2331
|
+
|
|
2332
|
+
def _normalize_args(args, kwargs):
|
|
2333
|
+
expected_names = ["model", "sample_id", "preprocess"]
|
|
2334
|
+
normalized = []
|
|
2335
|
+
for i, arg_name in enumerate(expected_names):
|
|
2336
|
+
if i < len(args):
|
|
2337
|
+
normalized.append(args[i])
|
|
2338
|
+
elif arg_name in kwargs:
|
|
2339
|
+
normalized.append(kwargs[arg_name])
|
|
2340
|
+
else:
|
|
2341
|
+
raise AssertionError(
|
|
2342
|
+
f'{user_function.__name__} validation failed: missing required argument '
|
|
2343
|
+
f"'{arg_name}'. Expected arguments: {expected_names}.")
|
|
2344
|
+
if len(args) + len(kwargs) != len(expected_names):
|
|
2345
|
+
raise AssertionError(
|
|
2346
|
+
f'{user_function.__name__} validation failed: expected exactly '
|
|
2347
|
+
f'{len(expected_names)} arguments {expected_names}, got '
|
|
2348
|
+
f'{len(args) + len(kwargs)}.')
|
|
2349
|
+
return normalized
|
|
2350
|
+
|
|
2351
|
+
def _fail(message):
|
|
2352
|
+
raise LeapValidationError(f'tensorleap_model_loop validation failed: {message}')
|
|
2353
|
+
|
|
2354
|
+
def _run_with_context(model, sample_id, preprocess_response, is_mapping):
|
|
2355
|
+
global _active_model_loop, _model_loop_run_in_current_test
|
|
2356
|
+
in_test = bool(_called_from_inside_tl_integration_test_decorator)
|
|
2357
|
+
if _active_model_loop is not None:
|
|
2358
|
+
_fail('model loops cannot nest or run concurrently.')
|
|
2359
|
+
if in_test and _model_loop_run_in_current_test:
|
|
2360
|
+
_fail('only one model loop run is allowed per integration test.')
|
|
2361
|
+
if model is None or model is not _last_loaded_model:
|
|
2362
|
+
_fail('the model argument must be the object returned by the '
|
|
2363
|
+
'tensorleap_load_model function, unchanged.')
|
|
2364
|
+
if leap_binder.setup_container.autoregressive_step is None:
|
|
2365
|
+
_fail('tensorleap_model_loop requires a tensorleap_autoregressive_step hook.')
|
|
2366
|
+
prediction_names = [prediction_type.name for prediction_type
|
|
2367
|
+
in leap_binder.setup_container.prediction_types]
|
|
2368
|
+
if not prediction_names:
|
|
2369
|
+
_fail('tensorleap_model_loop requires prediction types declared on '
|
|
2370
|
+
'tensorleap_load_model — the loop keys the model outputs by their names.')
|
|
2371
|
+
context = _ModelLoopContext(sample_id, preprocess_response, prediction_names,
|
|
2372
|
+
is_mapping)
|
|
2373
|
+
_active_model_loop = context
|
|
2374
|
+
try:
|
|
2375
|
+
result = user_function(_ModelLoopModelProxy(model, context), sample_id,
|
|
2376
|
+
preprocess_response)
|
|
2377
|
+
finally:
|
|
2378
|
+
_active_model_loop = None
|
|
2379
|
+
if in_test:
|
|
2380
|
+
_model_loop_run_in_current_test = True
|
|
2381
|
+
if context.phase == 'awaiting_first_hook':
|
|
2382
|
+
_fail('the loop never called the autoregressive step — it must start with the '
|
|
2383
|
+
'initial hook call (prev_inputs=None, prev_outputs=None, state=None).')
|
|
2384
|
+
if context.phase != 'ended':
|
|
2385
|
+
_fail('the loop returned before the autoregressive step signaled the chain end — '
|
|
2386
|
+
'it must run until the hook returns None for next_inputs and then return '
|
|
2387
|
+
'(final_inputs, final_outputs, final_state).')
|
|
2388
|
+
if not isinstance(result, tuple) or len(result) != 3:
|
|
2389
|
+
_fail('the loop must return (final_inputs, final_outputs, final_state). Got '
|
|
2390
|
+
f'{type(result).__name__}.')
|
|
2391
|
+
final_inputs, final_outputs, final_state = result
|
|
2392
|
+
if final_inputs is not context.fed_inputs:
|
|
2393
|
+
_fail('final_inputs must be exactly the last inputs dict fed to the model — the '
|
|
2394
|
+
'one the hook returned before the terminating call.')
|
|
2395
|
+
if final_outputs is not context.last_outputs:
|
|
2396
|
+
_fail('final_outputs must be exactly the outputs dict of the last model call.')
|
|
2397
|
+
if final_state is not context.last_state:
|
|
2398
|
+
_fail('final_state must be exactly the state returned by the terminating hook '
|
|
2399
|
+
'call.')
|
|
2400
|
+
return context, final_inputs, final_outputs, final_state
|
|
2401
|
+
|
|
2402
|
+
def inner(*args, **kwargs):
|
|
2403
|
+
if not _call_from_tl_platform:
|
|
2404
|
+
set_current('tensorleap_model_loop')
|
|
2405
|
+
model, sample_id, preprocess_response = _normalize_args(args, kwargs)
|
|
2406
|
+
in_test = _called_from_inside_tl_integration_test_decorator
|
|
2407
|
+
context, final_inputs, final_outputs, final_state = _run_with_context(
|
|
2408
|
+
model, sample_id, preprocess_response, is_mapping=False)
|
|
2409
|
+
if _nest_fingerprint(final_inputs) != context.fed_inputs_fingerprint:
|
|
2410
|
+
_fail('the final inputs were mutated in place after the last model call.')
|
|
2411
|
+
if _nest_fingerprint(final_outputs) != context.last_outputs_fingerprint:
|
|
2412
|
+
_fail('the final outputs were mutated in place after the last model call.')
|
|
2413
|
+
if _nest_fingerprint(final_state) != context.last_state_fingerprint:
|
|
2414
|
+
_fail('the final state was mutated in place after the terminating hook call.')
|
|
2415
|
+
if not in_test:
|
|
2416
|
+
if not _call_from_tl_platform:
|
|
2417
|
+
update_env_params_func("tensorleap_model_loop", "v")
|
|
2418
|
+
return final_inputs, final_outputs, final_state
|
|
2419
|
+
# Inside the integration test the chain tensors carry a batch-1 axis; the platform
|
|
2420
|
+
# feeds autoregressive metrics per-chain unbatched dicts, so strip it here.
|
|
2421
|
+
squeezed_inputs = _squeeze_leading_batch_axis(final_inputs)
|
|
2422
|
+
squeezed_outputs = _squeeze_leading_batch_axis(final_outputs)
|
|
2423
|
+
_register_chain_artifact(squeezed_inputs, 'final_inputs')
|
|
2424
|
+
_register_chain_artifact(squeezed_outputs, 'final_outputs')
|
|
2425
|
+
_register_chain_artifact(final_state, 'final_state')
|
|
2426
|
+
if not _call_from_tl_platform:
|
|
2427
|
+
update_env_params_func("tensorleap_model_loop", "v")
|
|
2428
|
+
return squeezed_inputs, squeezed_outputs, final_state
|
|
2429
|
+
|
|
2430
|
+
def mapping_inner(*args, **kwargs):
|
|
2431
|
+
model, sample_id, preprocess_response = _normalize_args(args, kwargs)
|
|
2432
|
+
context, final_inputs, final_outputs, final_state = _run_with_context(
|
|
2433
|
+
model, sample_id, preprocess_response, is_mapping=True)
|
|
2434
|
+
_register_chain_artifact(final_inputs, 'final_inputs')
|
|
2435
|
+
_register_chain_artifact(final_outputs, 'final_outputs')
|
|
2436
|
+
_register_chain_artifact(final_state, 'final_state')
|
|
2437
|
+
return final_inputs, final_outputs, final_state
|
|
2438
|
+
|
|
2439
|
+
def final_inner(*args, **kwargs):
|
|
2440
|
+
if os.environ.get(mapping_runtime_mode_env_var_mame):
|
|
2441
|
+
return mapping_inner(*args, **kwargs)
|
|
2442
|
+
else:
|
|
2443
|
+
return inner(*args, **kwargs)
|
|
2444
|
+
|
|
2445
|
+
if not _call_from_tl_platform:
|
|
2446
|
+
register_decorator_row("tensorleap_model_loop")
|
|
2447
|
+
|
|
2448
|
+
return final_inner
|
|
2449
|
+
|
|
2450
|
+
return decorating_function
|
|
2451
|
+
|
|
2452
|
+
|
|
2453
|
+
_IMPLICIT_ARG_TO_ARTIFACT_KIND = {'inputs': 'final_inputs', 'outputs': 'final_outputs',
|
|
2454
|
+
'state': 'final_state'}
|
|
2455
|
+
_ARTIFACT_KIND_DESCRIPTIONS = {
|
|
2456
|
+
'final_inputs': 'final inputs dict returned by the model loop',
|
|
2457
|
+
'final_outputs': 'final outputs dict returned by the model loop',
|
|
2458
|
+
'final_state': 'final state returned by the model loop',
|
|
2459
|
+
'ground_truth': 'direct return value of a ground-truth encoder',
|
|
2460
|
+
}
|
|
2461
|
+
_AUTOREGRESSIVE_SCALAR_TYPES = (int, float, np.floating, np.integer, np.bool_)
|
|
2462
|
+
|
|
2463
|
+
|
|
2464
|
+
def _normalize_autoregressive_call_args(func_name, full_arg_names, args, kwargs):
|
|
2465
|
+
if len(args) + len(kwargs) != len(full_arg_names):
|
|
2466
|
+
raise AssertionError(
|
|
2467
|
+
f'{func_name} validation failed: expected exactly {len(full_arg_names)} arguments '
|
|
2468
|
+
f'{full_arg_names}, got {len(args) + len(kwargs)}.')
|
|
2469
|
+
normalized = {}
|
|
2470
|
+
for i, arg_name in enumerate(full_arg_names):
|
|
2471
|
+
if i < len(args):
|
|
2472
|
+
normalized[arg_name] = args[i]
|
|
2473
|
+
elif arg_name in kwargs:
|
|
2474
|
+
normalized[arg_name] = kwargs[arg_name]
|
|
2475
|
+
else:
|
|
2476
|
+
raise AssertionError(
|
|
2477
|
+
f'{func_name} validation failed: missing required argument \'{arg_name}\'. '
|
|
2478
|
+
f'Expected arguments: {full_arg_names}.')
|
|
2479
|
+
return normalized
|
|
2480
|
+
|
|
2481
|
+
|
|
2482
|
+
def _validate_autoregressive_arg_provenance(func_name, normalized):
|
|
2483
|
+
for arg_name, value in normalized.items():
|
|
2484
|
+
expected_kind = _IMPLICIT_ARG_TO_ARTIFACT_KIND.get(arg_name, 'ground_truth')
|
|
2485
|
+
entry = _lookup_chain_artifact(value)
|
|
2486
|
+
if entry is None:
|
|
2487
|
+
raise LeapValidationError(
|
|
2488
|
+
f'{func_name} validation failed: argument "{arg_name}" must be the '
|
|
2489
|
+
f'{_ARTIFACT_KIND_DESCRIPTIONS[expected_kind]}, passed whole and unmodified — '
|
|
2490
|
+
f'the platform feeds it from the finished chain, so anything else would not '
|
|
2491
|
+
f'exist at platform runtime. Slice or derive inside the function body instead.')
|
|
2492
|
+
kind, fingerprint, _ = entry
|
|
2493
|
+
if kind != expected_kind:
|
|
2494
|
+
raise LeapValidationError(
|
|
2495
|
+
f'{func_name} validation failed: argument "{arg_name}" received the '
|
|
2496
|
+
f'{_ARTIFACT_KIND_DESCRIPTIONS[kind]} but must be the '
|
|
2497
|
+
f'{_ARTIFACT_KIND_DESCRIPTIONS[expected_kind]}.')
|
|
2498
|
+
if fingerprint != _nest_fingerprint(value):
|
|
2499
|
+
raise LeapValidationError(
|
|
2500
|
+
f'{func_name} validation failed: argument "{arg_name}" was mutated in place '
|
|
2501
|
+
f'after it was produced — the platform feeds the original values, so local '
|
|
2502
|
+
f'mutations diverge from platform results.')
|
|
2503
|
+
|
|
2504
|
+
|
|
2505
|
+
def _squeeze_wired_autoregressive_args(normalized):
|
|
2506
|
+
squeezed = {}
|
|
2507
|
+
for arg_name, value in normalized.items():
|
|
2508
|
+
if arg_name not in _IMPLICIT_ARG_TO_ARTIFACT_KIND and isinstance(value, np.ndarray) \
|
|
2509
|
+
and value.ndim > 0 and value.shape[0] == 1:
|
|
2510
|
+
squeezed[arg_name] = value[0]
|
|
2511
|
+
else:
|
|
2512
|
+
squeezed[arg_name] = value
|
|
2513
|
+
return squeezed
|
|
2514
|
+
|
|
2515
|
+
|
|
2516
|
+
def _validate_autoregressive_scalar_result(func_name, result, allow_dict):
|
|
2517
|
+
def _is_scalar(value):
|
|
2518
|
+
return isinstance(value, _AUTOREGRESSIVE_SCALAR_TYPES) or \
|
|
2519
|
+
(isinstance(value, np.ndarray) and value.ndim == 0)
|
|
2520
|
+
|
|
2521
|
+
if _is_scalar(result):
|
|
2522
|
+
return
|
|
2523
|
+
if allow_dict and isinstance(result, dict) and result and \
|
|
2524
|
+
all(isinstance(key, str) for key in result) and \
|
|
2525
|
+
all(_is_scalar(value) for value in result.values()):
|
|
2526
|
+
return
|
|
2527
|
+
expected = 'a scalar number' + (' or a dict of named scalar numbers' if allow_dict else '')
|
|
2528
|
+
raise AssertionError(
|
|
2529
|
+
f'{func_name} validation failed: autoregressive results are per-chain — the function '
|
|
2530
|
+
f'must return {expected} for the single chain it received. Got {type(result).__name__}.')
|
|
2531
|
+
|
|
2532
|
+
|
|
2533
|
+
def _autoregressive_mapping_call(mapping_inner_func, args, kwargs, node_mapping_type):
|
|
2534
|
+
user_unique_name = mapping_inner_func.name
|
|
2535
|
+
if 'user_unique_name' in kwargs:
|
|
2536
|
+
kwargs = dict(kwargs)
|
|
2537
|
+
user_unique_name = kwargs.pop('user_unique_name')
|
|
2538
|
+
if user_unique_name in mapping_inner_func.name_to_unique_name[mapping_inner_func.name]:
|
|
2539
|
+
user_unique_name = \
|
|
2540
|
+
f'{user_unique_name}_{len(mapping_inner_func.name_to_unique_name[mapping_inner_func.name])}'
|
|
2541
|
+
mapping_inner_func.name_to_unique_name[mapping_inner_func.name].add(user_unique_name)
|
|
2542
|
+
|
|
2543
|
+
normalized = _normalize_autoregressive_call_args(mapping_inner_func.name,
|
|
2544
|
+
mapping_inner_func.full_arg_names, args,
|
|
2545
|
+
kwargs)
|
|
2546
|
+
for arg_name, value in normalized.items():
|
|
2547
|
+
if arg_name in _IMPLICIT_ARG_TO_ARTIFACT_KIND:
|
|
2548
|
+
entry = _lookup_chain_artifact(value)
|
|
2549
|
+
expected_kind = _IMPLICIT_ARG_TO_ARTIFACT_KIND[arg_name]
|
|
2550
|
+
if entry is None or entry[0] != expected_kind:
|
|
2551
|
+
raise LeapValidationError(
|
|
2552
|
+
f'{mapping_inner_func.name} validation failed: argument "{arg_name}" must '
|
|
2553
|
+
f'be the {_ARTIFACT_KIND_DESCRIPTIONS[expected_kind]}, passed whole.')
|
|
2554
|
+
elif not hasattr(value, 'node_mapping'):
|
|
2555
|
+
raise LeapValidationError(
|
|
2556
|
+
f'{mapping_inner_func.name} validation failed: argument "{arg_name}" must be '
|
|
2557
|
+
f'the direct return value of a ground-truth encoder.')
|
|
2558
|
+
ordered_connections = [normalized[arg_name] for arg_name in mapping_inner_func.arg_names]
|
|
2559
|
+
_add_mapping_connection(user_unique_name, ordered_connections, mapping_inner_func.arg_names,
|
|
2560
|
+
mapping_inner_func.name, node_mapping_type)
|
|
2561
|
+
return None
|
|
2562
|
+
|
|
2563
|
+
|
|
2564
|
+
def _make_autoregressive_decorator_inner(user_function, name, decorator_env_name, handler_data,
|
|
2565
|
+
node_mapping_type, validate_result):
|
|
2566
|
+
full_arg_names = inspect.getfullargspec(user_function)[0]
|
|
2567
|
+
|
|
2568
|
+
def inner_without_validate(*args, **kwargs):
|
|
2569
|
+
global _called_from_inside_tl_decorator
|
|
2570
|
+
_called_from_inside_tl_decorator += 1
|
|
2571
|
+
try:
|
|
2572
|
+
result = user_function(*args, **kwargs)
|
|
2573
|
+
finally:
|
|
2574
|
+
_called_from_inside_tl_decorator -= 1
|
|
2575
|
+
return result
|
|
2576
|
+
|
|
2577
|
+
def inner(*args, **kwargs):
|
|
2578
|
+
if not _call_from_tl_platform:
|
|
2579
|
+
set_current(decorator_env_name)
|
|
2580
|
+
normalized = _normalize_autoregressive_call_args(user_function.__name__, full_arg_names,
|
|
2581
|
+
args, kwargs)
|
|
2582
|
+
is_top_level_in_test = (_called_from_inside_tl_decorator == 0
|
|
2583
|
+
and _called_from_inside_tl_integration_test_decorator)
|
|
2584
|
+
if is_top_level_in_test:
|
|
2585
|
+
_validate_autoregressive_arg_provenance(user_function.__name__, normalized)
|
|
2586
|
+
normalized = _squeeze_wired_autoregressive_args(normalized)
|
|
2587
|
+
result = inner_without_validate(**normalized)
|
|
2588
|
+
validate_result(user_function.__name__, result)
|
|
2589
|
+
if not _call_from_tl_platform:
|
|
2590
|
+
update_env_params_func(decorator_env_name, "v")
|
|
2591
|
+
return result
|
|
2592
|
+
|
|
2593
|
+
def mapping_inner(*args, **kwargs):
|
|
2594
|
+
return _autoregressive_mapping_call(mapping_inner, args, kwargs, node_mapping_type)
|
|
2595
|
+
|
|
2596
|
+
mapping_inner.name = name
|
|
2597
|
+
mapping_inner.full_arg_names = full_arg_names
|
|
2598
|
+
mapping_inner.arg_names = handler_data.arg_names
|
|
2599
|
+
mapping_inner.name_to_unique_name = defaultdict(set)
|
|
2600
|
+
|
|
2601
|
+
def final_inner(*args, **kwargs):
|
|
2602
|
+
if os.environ.get(mapping_runtime_mode_env_var_mame):
|
|
2603
|
+
return mapping_inner(*args, **kwargs)
|
|
2604
|
+
else:
|
|
2605
|
+
return inner(*args, **kwargs)
|
|
2606
|
+
|
|
2607
|
+
if not _call_from_tl_platform:
|
|
2608
|
+
register_decorator_row(decorator_env_name)
|
|
2609
|
+
|
|
2610
|
+
return final_inner
|
|
2611
|
+
|
|
2612
|
+
|
|
2613
|
+
def tensorleap_autoregressive_metric(name: str,
|
|
2614
|
+
direction: Union[MetricDirection, Dict[str, MetricDirection]]
|
|
2615
|
+
= MetricDirection.Downward,
|
|
2616
|
+
compute_insights: Optional[Union[bool, Dict[str, bool]]] = None):
|
|
2617
|
+
"""A per-chain metric over a finished autoregressive chain.
|
|
2618
|
+
|
|
2619
|
+
The reserved arguments inputs / outputs / state are fed implicitly by the platform from the
|
|
2620
|
+
chain's final step (unbatched dicts; outputs keyed by the declared prediction-type names;
|
|
2621
|
+
state as the terminating hook call returned it). Declare whichever of them the metric needs.
|
|
2622
|
+
Every other argument is wired to a ground-truth encoder through the integration test. Runs
|
|
2623
|
+
once per chain and must return a scalar number (or a dict of named scalar numbers).
|
|
2624
|
+
"""
|
|
2625
|
+
|
|
2626
|
+
def decorating_function(user_function):
|
|
2627
|
+
leap_binder.add_autoregressive_metric(user_function, name, direction, compute_insights)
|
|
2628
|
+
handler_data = \
|
|
2629
|
+
leap_binder.setup_container.autoregressive_metrics[-1].metric_handler_data
|
|
2630
|
+
|
|
2631
|
+
def validate_result(func_name, result):
|
|
2632
|
+
_validate_autoregressive_scalar_result(func_name, result, allow_dict=True)
|
|
2633
|
+
|
|
2634
|
+
return _make_autoregressive_decorator_inner(
|
|
2635
|
+
user_function, name, 'tensorleap_autoregressive_metric', handler_data,
|
|
2636
|
+
NodeMappingType.Metric, validate_result)
|
|
2637
|
+
|
|
2638
|
+
return decorating_function
|
|
2639
|
+
|
|
2640
|
+
|
|
2641
|
+
def tensorleap_autoregressive_loss(name: str):
|
|
2642
|
+
"""A per-chain loss over a finished autoregressive chain.
|
|
2643
|
+
|
|
2644
|
+
Same argument contract as tensorleap_autoregressive_metric; must return a single scalar
|
|
2645
|
+
number for the chain it received.
|
|
2646
|
+
"""
|
|
2647
|
+
|
|
2648
|
+
def decorating_function(user_function):
|
|
2649
|
+
leap_binder.add_autoregressive_loss(user_function, name)
|
|
2650
|
+
handler_data = \
|
|
2651
|
+
leap_binder.setup_container.autoregressive_losses[-1].custom_loss_handler_data
|
|
2652
|
+
|
|
2653
|
+
def validate_result(func_name, result):
|
|
2654
|
+
_validate_autoregressive_scalar_result(func_name, result, allow_dict=False)
|
|
2655
|
+
|
|
2656
|
+
return _make_autoregressive_decorator_inner(
|
|
2657
|
+
user_function, name, 'tensorleap_autoregressive_loss', handler_data,
|
|
2658
|
+
NodeMappingType.CustomLoss, validate_result)
|
|
2659
|
+
|
|
2660
|
+
return decorating_function
|
|
2661
|
+
|
|
2662
|
+
|
|
2663
|
+
def tensorleap_autoregressive_visualizer(name: str, visualizer_type: LeapDataType):
|
|
2664
|
+
"""A per-chain visualizer over a finished autoregressive chain.
|
|
2665
|
+
|
|
2666
|
+
Same argument contract as tensorleap_autoregressive_metric; must return a LeapData object
|
|
2667
|
+
matching visualizer_type.
|
|
2668
|
+
"""
|
|
2669
|
+
|
|
2670
|
+
def decorating_function(user_function):
|
|
2671
|
+
assert isinstance(visualizer_type, LeapDataType), \
|
|
2672
|
+
(f'{user_function.__name__} validation failed: visualizer_type should be of type '
|
|
2673
|
+
f'{LeapDataType.__name__} but got {type(visualizer_type)}')
|
|
2674
|
+
leap_binder.add_autoregressive_visualizer(user_function, name, visualizer_type)
|
|
2675
|
+
handler_data = \
|
|
2676
|
+
leap_binder.setup_container.autoregressive_visualizers[-1].visualizer_handler_data
|
|
2677
|
+
|
|
2678
|
+
def validate_result(func_name, result):
|
|
2679
|
+
expected_class = map_leap_data_type_to_visualizer_class[visualizer_type.value]
|
|
2680
|
+
assert isinstance(result, expected_class), \
|
|
2681
|
+
(f'{func_name} validation failed: the return type should be '
|
|
2682
|
+
f'{expected_class.__name__} (visualizer_type={visualizer_type}). '
|
|
2683
|
+
f'Got {type(result).__name__}.')
|
|
2684
|
+
|
|
2685
|
+
return _make_autoregressive_decorator_inner(
|
|
2686
|
+
user_function, name, 'tensorleap_autoregressive_visualizer', handler_data,
|
|
2687
|
+
NodeMappingType.Visualizer, validate_result)
|
|
2688
|
+
|
|
2689
|
+
return decorating_function
|
|
2690
|
+
|
|
2691
|
+
|
|
1955
2692
|
def tensorleap_preprocess():
|
|
1956
2693
|
def decorating_function(user_function: Callable[[], List[PreprocessResponse]]):
|
|
1957
2694
|
leap_binder.set_preprocess(user_function)
|
|
@@ -2378,10 +3115,12 @@ def tensorleap_input_encoder(name: str, channel_dim=_UNSET, model_input_index=No
|
|
|
2378
3115
|
if _called_from_inside_tl_decorator == 0 and _called_from_inside_tl_integration_test_decorator:
|
|
2379
3116
|
if grouped:
|
|
2380
3117
|
# 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).
|
|
2382
|
-
#
|
|
2383
|
-
|
|
2384
|
-
|
|
3118
|
+
# B=1-only model can be fed one sample at a time). A user may also return a
|
|
3119
|
+
# supported (B, *) array; split it back into per-sample rows first (mirrors
|
|
3120
|
+
# LeapLoader._to_grouped_list) so both forms match the runtime path. Then add
|
|
3121
|
+
# a batch dim to each sample, mirroring the flat path's single-sample expand_dims.
|
|
3122
|
+
rows = result if isinstance(result, list) else [row for row in np.asarray(result)]
|
|
3123
|
+
result = [np.expand_dims(r, axis=0) for r in rows]
|
|
2385
3124
|
else:
|
|
2386
3125
|
batch_warning(result, user_function.__name__)
|
|
2387
3126
|
result = np.expand_dims(result, axis=0)
|
|
@@ -2492,13 +3231,16 @@ def tensorleap_gt_encoder(name: str):
|
|
|
2492
3231
|
if _called_from_inside_tl_decorator == 0 and _called_from_inside_tl_integration_test_decorator:
|
|
2493
3232
|
if grouped:
|
|
2494
3233
|
# 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).
|
|
2496
|
-
#
|
|
2497
|
-
|
|
2498
|
-
|
|
3234
|
+
# B=1-only model can be fed one sample at a time). A user may also return a
|
|
3235
|
+
# supported (B, *) array; split it back into per-sample rows first (mirrors
|
|
3236
|
+
# LeapLoader._to_grouped_list) so both forms match the runtime path. Then add
|
|
3237
|
+
# a batch dim to each sample, mirroring the flat path's single-sample expand_dims.
|
|
3238
|
+
rows = result if isinstance(result, list) else [row for row in np.asarray(result)]
|
|
3239
|
+
result = [np.expand_dims(r, axis=0) for r in rows]
|
|
2499
3240
|
else:
|
|
2500
3241
|
batch_warning(result, user_function.__name__)
|
|
2501
3242
|
result = np.expand_dims(result, axis=0)
|
|
3243
|
+
_register_chain_artifact(result, 'ground_truth')
|
|
2502
3244
|
# Emit integration test event once per test
|
|
2503
3245
|
try:
|
|
2504
3246
|
emit_integration_event_once(AnalyticsEvent.GT_ENCODER_INTEGRATION_TEST, {
|
|
@@ -2772,6 +3514,12 @@ def tensorleap_status_table():
|
|
|
2772
3514
|
if not started_from("leap_integration.py"):
|
|
2773
3515
|
return
|
|
2774
3516
|
|
|
3517
|
+
if leap_binder.setup_container.autoregressive_step is not None:
|
|
3518
|
+
# Input encoders are forbidden in autoregressive integrations — the step hook is
|
|
3519
|
+
# the sole source of model inputs — so the row is not applicable: it must not be
|
|
3520
|
+
# printed, gate readiness, or be recommended as a next step.
|
|
3521
|
+
table[:] = [row for row in table if row["name"] != "tensorleap_input_encoder"]
|
|
3522
|
+
|
|
2775
3523
|
ready_mess = "\nAll parts have been successfully set. If no errors accured, you can now push the project to the Tensorleap system."
|
|
2776
3524
|
not_ready_mess = "\nSome mandatory components have not yet been added to the Integration test. Recommended next interface to add is: "
|
|
2777
3525
|
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: "
|
|
@@ -2803,7 +3551,11 @@ def tensorleap_status_table():
|
|
|
2803
3551
|
return
|
|
2804
3552
|
|
|
2805
3553
|
if _crashed["value"]:
|
|
2806
|
-
|
|
3554
|
+
crashed_at = _current_func['name']
|
|
3555
|
+
if crashed_at:
|
|
3556
|
+
print(f"\nScript crashed before completing all steps. crashed at function '{crashed_at}'.")
|
|
3557
|
+
else:
|
|
3558
|
+
print("\nScript crashed before completing all steps.")
|
|
2807
3559
|
return
|
|
2808
3560
|
|
|
2809
3561
|
print(ready_mess) if ready else print(
|
|
@@ -2878,9 +3630,11 @@ def tensorleap_status_table():
|
|
|
2878
3630
|
row = _find_row(crashed_name)
|
|
2879
3631
|
if row:
|
|
2880
3632
|
row["Added to integration"] = CROSS
|
|
2881
|
-
elif not crashed_name:
|
|
2882
|
-
# Crash with no decorator currently running
|
|
2883
|
-
# code flow itself failed; report that rather than
|
|
3633
|
+
elif not crashed_name and _integration_test_started:
|
|
3634
|
+
# Crash with no decorator currently running while an integration test has been
|
|
3635
|
+
# entered = the test body / code flow itself failed; report that rather than
|
|
3636
|
+
# blaming a decorator. Without a started test (a module-level crash between
|
|
3637
|
+
# decorator registrations) the plain script-crash message is the truthful one.
|
|
2884
3638
|
code_mapping_failure[0] = 1
|
|
2885
3639
|
|
|
2886
3640
|
traceback.print_exception(exc_type, exc_value, exc_traceback)
|