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

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (37) hide show
  1. {code_loader-1.0.193.dev0 → code_loader-1.0.193.dev2}/PKG-INFO +1 -1
  2. {code_loader-1.0.193.dev0 → code_loader-1.0.193.dev2}/code_loader/inner_leap_binder/leapbinder_decorators.py +39 -21
  3. {code_loader-1.0.193.dev0 → code_loader-1.0.193.dev2}/code_loader/leaploader.py +46 -6
  4. {code_loader-1.0.193.dev0 → code_loader-1.0.193.dev2}/code_loader/leaploaderbase.py +67 -3
  5. {code_loader-1.0.193.dev0 → code_loader-1.0.193.dev2}/code_loader/utils.py +11 -2
  6. {code_loader-1.0.193.dev0 → code_loader-1.0.193.dev2}/pyproject.toml +1 -1
  7. {code_loader-1.0.193.dev0 → code_loader-1.0.193.dev2}/LICENSE +0 -0
  8. {code_loader-1.0.193.dev0 → code_loader-1.0.193.dev2}/README.md +0 -0
  9. {code_loader-1.0.193.dev0 → code_loader-1.0.193.dev2}/code_loader/__init__.py +0 -0
  10. {code_loader-1.0.193.dev0 → code_loader-1.0.193.dev2}/code_loader/contract/__init__.py +0 -0
  11. {code_loader-1.0.193.dev0 → code_loader-1.0.193.dev2}/code_loader/contract/datasetclasses.py +0 -0
  12. {code_loader-1.0.193.dev0 → code_loader-1.0.193.dev2}/code_loader/contract/enums.py +0 -0
  13. {code_loader-1.0.193.dev0 → code_loader-1.0.193.dev2}/code_loader/contract/exceptions.py +0 -0
  14. {code_loader-1.0.193.dev0 → code_loader-1.0.193.dev2}/code_loader/contract/mapping.py +0 -0
  15. {code_loader-1.0.193.dev0 → code_loader-1.0.193.dev2}/code_loader/contract/responsedataclasses.py +0 -0
  16. {code_loader-1.0.193.dev0 → code_loader-1.0.193.dev2}/code_loader/contract/sim_config.py +0 -0
  17. {code_loader-1.0.193.dev0 → code_loader-1.0.193.dev2}/code_loader/contract/visualizer_classes.py +0 -0
  18. {code_loader-1.0.193.dev0 → code_loader-1.0.193.dev2}/code_loader/default_losses.py +0 -0
  19. {code_loader-1.0.193.dev0 → code_loader-1.0.193.dev2}/code_loader/default_metrics.py +0 -0
  20. {code_loader-1.0.193.dev0 → code_loader-1.0.193.dev2}/code_loader/experiment_api/__init__.py +0 -0
  21. {code_loader-1.0.193.dev0 → code_loader-1.0.193.dev2}/code_loader/experiment_api/api.py +0 -0
  22. {code_loader-1.0.193.dev0 → code_loader-1.0.193.dev2}/code_loader/experiment_api/cli_config_utils.py +0 -0
  23. {code_loader-1.0.193.dev0 → code_loader-1.0.193.dev2}/code_loader/experiment_api/client.py +0 -0
  24. {code_loader-1.0.193.dev0 → code_loader-1.0.193.dev2}/code_loader/experiment_api/epoch.py +0 -0
  25. {code_loader-1.0.193.dev0 → code_loader-1.0.193.dev2}/code_loader/experiment_api/experiment.py +0 -0
  26. {code_loader-1.0.193.dev0 → code_loader-1.0.193.dev2}/code_loader/experiment_api/experiment_context.py +0 -0
  27. {code_loader-1.0.193.dev0 → code_loader-1.0.193.dev2}/code_loader/experiment_api/types.py +0 -0
  28. {code_loader-1.0.193.dev0 → code_loader-1.0.193.dev2}/code_loader/experiment_api/utils.py +0 -0
  29. {code_loader-1.0.193.dev0 → code_loader-1.0.193.dev2}/code_loader/experiment_api/workingspace_config_utils.py +0 -0
  30. {code_loader-1.0.193.dev0 → code_loader-1.0.193.dev2}/code_loader/inner_leap_binder/__init__.py +0 -0
  31. {code_loader-1.0.193.dev0 → code_loader-1.0.193.dev2}/code_loader/inner_leap_binder/leapbinder.py +0 -0
  32. {code_loader-1.0.193.dev0 → code_loader-1.0.193.dev2}/code_loader/mixpanel_tracker.py +0 -0
  33. {code_loader-1.0.193.dev0 → code_loader-1.0.193.dev2}/code_loader/plot_functions/__init__.py +0 -0
  34. {code_loader-1.0.193.dev0 → code_loader-1.0.193.dev2}/code_loader/plot_functions/plot_functions.py +0 -0
  35. {code_loader-1.0.193.dev0 → code_loader-1.0.193.dev2}/code_loader/plot_functions/visualize.py +0 -0
  36. {code_loader-1.0.193.dev0 → code_loader-1.0.193.dev2}/code_loader/visualizers/__init__.py +0 -0
  37. {code_loader-1.0.193.dev0 → code_loader-1.0.193.dev2}/code_loader/visualizers/default_visualizers.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: code-loader
3
- Version: 1.0.193.dev0
3
+ Version: 1.0.193.dev2
4
4
  Summary:
5
5
  Home-page: https://github.com/tensorleap/code-loader
6
6
  License: MIT
@@ -36,6 +36,10 @@ from code_loader.mixpanel_tracker import clear_integration_events, AnalyticsEven
36
36
 
37
37
  _called_from_inside_tl_decorator = 0
38
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
39
43
  _call_from_tl_platform = os.environ.get('IS_TENSORLEAP_PLATFORM') == 'true'
40
44
 
41
45
  # ---- warnings store (module-level) ----
@@ -299,7 +303,8 @@ def tensorleap_integration_test():
299
303
  func_name='integration_test', expected_names=["idx", "preprocess"], **kwargs)
300
304
  _validate_input_args(*args, **kwargs)
301
305
 
302
- global _called_from_inside_tl_integration_test_decorator
306
+ global _called_from_inside_tl_integration_test_decorator, _integration_test_started
307
+ _integration_test_started = True
303
308
  # Clear integration test events for new test
304
309
  try:
305
310
  clear_integration_events()
@@ -2050,7 +2055,7 @@ def tensorleap_autoregressive_step(latent_space_aggregation: str = 'last_step'):
2050
2055
  for key, value in first_inputs.items():
2051
2056
  if not isinstance(value, np.ndarray):
2052
2057
  continue
2053
- assert np.array_equal(value, second_inputs[key]), error_message
2058
+ assert _nest_equal(value, second_inputs[key]), error_message
2054
2059
  assert _nest_equal(first_state, second_state), error_message
2055
2060
 
2056
2061
  def _squeeze_test_batch_dim(tensors_dict):
@@ -2087,19 +2092,24 @@ def tensorleap_autoregressive_step(latent_space_aggregation: str = 'last_step'):
2087
2092
  sample_id, prev_inputs, prev_outputs, state, preprocess_response = \
2088
2093
  _normalize_args(args, kwargs)
2089
2094
 
2090
- is_top_level_in_test = (_called_from_inside_tl_decorator == 0
2095
+ is_top_level = _called_from_inside_tl_decorator == 0
2096
+ is_top_level_in_test = (is_top_level
2091
2097
  and _called_from_inside_tl_integration_test_decorator)
2092
2098
  loop_context = _active_model_loop
2093
- if is_top_level_in_test:
2094
- if loop_context is None:
2095
- raise LeapValidationError(
2096
- f'{user_function.__name__}() validation failed: inside the integration '
2097
- f'test the autoregressive step may only be called from within the '
2098
- f'tensorleap_model_loop function including the initial call '
2099
- f'(prev_inputs=None). The platform drives the chain through the model '
2100
- f'loop only, so a hook call outside it would not run on the platform.')
2099
+ if is_top_level_in_test and loop_context is None:
2100
+ raise LeapValidationError(
2101
+ f'{user_function.__name__}() validation failed: inside the integration '
2102
+ f'test the autoregressive step may only be called from within the '
2103
+ f'tensorleap_model_loop function including the initial call '
2104
+ f'(prev_inputs=None). The platform drives the chain through the model '
2105
+ f'loop only, so a hook call outside it would not run on the platform.')
2106
+ # Outside the integration test a model loop may still be running (local debugging);
2107
+ # the loop's phase machine must advance there too, or its model proxy always raises.
2108
+ notify_loop = is_top_level and loop_context is not None
2109
+ if notify_loop:
2101
2110
  loop_context.on_hook_call(sample_id, prev_inputs, prev_outputs, state,
2102
2111
  preprocess_response)
2112
+ if is_top_level_in_test:
2103
2113
  prev_inputs = _squeeze_test_batch_dim(prev_inputs)
2104
2114
  prev_outputs = _squeeze_test_batch_dim(prev_outputs)
2105
2115
 
@@ -2123,10 +2133,10 @@ def tensorleap_autoregressive_step(latent_space_aggregation: str = 'last_step'):
2123
2133
  _validate_consistency(next_inputs)
2124
2134
  _validate_state_types(new_state)
2125
2135
 
2126
- if is_top_level_in_test:
2127
- if next_inputs is not None:
2128
- next_inputs = {key: np.expand_dims(value, axis=0)
2129
- for key, value in next_inputs.items()}
2136
+ if is_top_level_in_test and next_inputs is not None:
2137
+ next_inputs = {key: np.expand_dims(value, axis=0)
2138
+ for key, value in next_inputs.items()}
2139
+ if notify_loop:
2130
2140
  loop_context.on_hook_return(next_inputs, new_state)
2131
2141
 
2132
2142
  if not _call_from_tl_platform:
@@ -2256,9 +2266,10 @@ def tensorleap_model_loop():
2256
2266
 
2257
2267
  def _run_with_context(model, sample_id, preprocess_response, is_mapping):
2258
2268
  global _active_model_loop, _model_loop_run_in_current_test
2269
+ in_test = bool(_called_from_inside_tl_integration_test_decorator)
2259
2270
  if _active_model_loop is not None:
2260
2271
  _fail('model loops cannot nest or run concurrently.')
2261
- if _model_loop_run_in_current_test:
2272
+ if in_test and _model_loop_run_in_current_test:
2262
2273
  _fail('only one model loop run is allowed per integration test.')
2263
2274
  if model is None or model is not _last_loaded_model:
2264
2275
  _fail('the model argument must be the object returned by the '
@@ -2278,7 +2289,8 @@ def tensorleap_model_loop():
2278
2289
  preprocess_response)
2279
2290
  finally:
2280
2291
  _active_model_loop = None
2281
- _model_loop_run_in_current_test = True
2292
+ if in_test:
2293
+ _model_loop_run_in_current_test = True
2282
2294
  if context.phase == 'awaiting_first_hook':
2283
2295
  _fail('the loop never called the autoregressive step — it must start with the '
2284
2296
  'initial hook call (prev_inputs=None, prev_outputs=None, state=None).')
@@ -3395,7 +3407,11 @@ def tensorleap_status_table():
3395
3407
  return
3396
3408
 
3397
3409
  if _crashed["value"]:
3398
- print(f"\nScript crashed before completing all steps. crashed at function '{_current_func['name']}'.")
3410
+ crashed_at = _current_func['name']
3411
+ if crashed_at:
3412
+ print(f"\nScript crashed before completing all steps. crashed at function '{crashed_at}'.")
3413
+ else:
3414
+ print("\nScript crashed before completing all steps.")
3399
3415
  return
3400
3416
 
3401
3417
  print(ready_mess) if ready else print(
@@ -3470,9 +3486,11 @@ def tensorleap_status_table():
3470
3486
  row = _find_row(crashed_name)
3471
3487
  if row:
3472
3488
  row["Added to integration"] = CROSS
3473
- elif not crashed_name:
3474
- # Crash with no decorator currently running = the integration-test body /
3475
- # code flow itself failed; report that rather than blaming a decorator.
3489
+ elif not crashed_name and _integration_test_started:
3490
+ # Crash with no decorator currently running while an integration test has been
3491
+ # entered = the test body / code flow itself failed; report that rather than
3492
+ # blaming a decorator. Without a started test (a module-level crash between
3493
+ # decorator registrations) the plain script-crash message is the truthful one.
3476
3494
  code_mapping_failure[0] = 1
3477
3495
 
3478
3496
  traceback.print_exception(exc_type, exc_value, exc_traceback)
@@ -7,7 +7,7 @@ import sys
7
7
  from contextlib import redirect_stdout
8
8
  from functools import lru_cache
9
9
  from pathlib import Path
10
- from typing import Dict, List, Iterable, Set, Union, Any, Type, Optional, Callable, Tuple
10
+ from typing import Dict, List, Iterable, Set, FrozenSet, Union, Any, Type, Optional, Callable, Tuple
11
11
 
12
12
  import numpy as np
13
13
  import numpy.typing as npt
@@ -480,6 +480,8 @@ class LeapLoader(LeapLoaderBase):
480
480
  try:
481
481
  preprocess_result = self._preprocess_result()
482
482
  input_shapes: Dict[str, List[int]] = {}
483
+ expected_keys: Optional[FrozenSet[str]] = None
484
+ expected_keys_state = ''
483
485
  for state, preprocess_response in preprocess_result.items():
484
486
  if preprocess_response.sample_ids_to_instance_mappings:
485
487
  raise Exception('Element instances are not supported together with '
@@ -506,6 +508,15 @@ class LeapLoader(LeapLoaderBase):
506
508
  f'(state: {state.name}). Seed any stochasticity from '
507
509
  'sample_id: the engine replays steps after crash recovery '
508
510
  'and relies on identical results.')
511
+ if expected_keys is None:
512
+ expected_keys = frozenset(first_inputs)
513
+ expected_keys_state = state.name
514
+ elif frozenset(first_inputs) != expected_keys:
515
+ raise Exception(f'The autoregressive step hook returned different model input '
516
+ f'keys across states: {sorted(expected_keys)} '
517
+ f'({expected_keys_state}) vs {sorted(first_inputs)} '
518
+ f'({state.name}). The key set must be identical for every '
519
+ f'state.')
509
520
  for key, value in first_inputs.items():
510
521
  if key.startswith('_'):
511
522
  raise Exception(f'The autoregressive step hook returned model input key '
@@ -520,7 +531,7 @@ class LeapLoader(LeapLoaderBase):
520
531
  f'model input "{key}" across states: {input_shapes[key]} vs '
521
532
  f'{list(value.shape)}. Shapes must be fixed.')
522
533
  input_shapes[key] = list(value.shape)
523
- if not np.array_equal(value, second_result[0][key]):
534
+ if not autoregressive_nests_equal(value, second_result[0][key]):
524
535
  raise Exception('The autoregressive step hook is not deterministic — two calls '
525
536
  'with identical arguments returned different outputs for model '
526
537
  f'input "{key}". Seed any stochasticity from sample_id: the '
@@ -534,21 +545,39 @@ class LeapLoader(LeapLoaderBase):
534
545
  test_result.is_passed = False
535
546
  return test_result
536
547
 
548
+ @staticmethod
549
+ def _validate_autoregressive_step_inputs(step_inputs: Dict[str, Any],
550
+ sample_id: Union[int, str]) -> None:
551
+ # The parse-time check only covers the first sample of each state; the engine runtime
552
+ # paths must enforce the same key/value contract for every sample they touch.
553
+ for key, value in step_inputs.items():
554
+ if key.startswith('_'):
555
+ raise Exception(f'The autoregressive step hook returned model input key "{key}" '
556
+ f'for sample {sample_id} — underscore passthrough keys were '
557
+ 'replaced by the explicit state channel; move passthrough values '
558
+ 'into the returned state.')
559
+ if not isinstance(value, np.ndarray):
560
+ raise Exception('The autoregressive step hook returned a non-numpy value for '
561
+ f'model input "{key}" ({type(value).__name__}) for sample '
562
+ f'{sample_id}.')
563
+
537
564
  def _ensure_autoregressive_input_shapes(self) -> Dict[str, List[int]]:
538
565
  handler = global_leap_binder.setup_container.autoregressive_step
539
566
  assert handler is not None
540
567
  if handler.input_shapes is None:
541
568
  preprocess_result = self._preprocess_result()
542
569
  first_state_response = next(iter(preprocess_result.values()))
543
- first_result = handler.function(first_state_response.sample_ids[0], None, None, None,
570
+ first_sample_id = first_state_response.sample_ids[0]
571
+ first_result = handler.function(first_sample_id, None, None, None,
544
572
  first_state_response)
545
573
  if not isinstance(first_result, tuple) or len(first_result) != 2 or \
546
574
  not isinstance(first_result[0], dict):
547
575
  raise Exception('The autoregressive step hook must return a (next_inputs, state) '
548
576
  'tuple with a dict of initial model inputs on its first call, '
549
577
  f'got {type(first_result)}.')
550
- handler.input_shapes = {key: list(value.shape) for key, value in first_result[0].items()
551
- if isinstance(value, np.ndarray)}
578
+ self._validate_autoregressive_step_inputs(first_result[0], first_sample_id)
579
+ handler.input_shapes = {key: list(value.shape)
580
+ for key, value in first_result[0].items()}
552
581
  return handler.input_shapes
553
582
 
554
583
  def run_simulation(self, sim_name, params=None, n_samples=1, seed=0,
@@ -886,6 +915,7 @@ class LeapLoader(LeapLoaderBase):
886
915
  'The autoregressive step hook must return a (next_inputs, state) tuple with a '
887
916
  f'dict of initial model inputs on its first call, got {type(step_zero_result)} '
888
917
  f'for sample {sample_id}.')
918
+ self._validate_autoregressive_step_inputs(step_zero_result[0], sample_id)
889
919
  inputs.update(step_zero_result[0])
890
920
  return inputs
891
921
 
@@ -1051,6 +1081,11 @@ class LeapLoader(LeapLoaderBase):
1051
1081
  next_inputs, new_chain_state = result
1052
1082
  validate_autoregressive_state_types(new_chain_state)
1053
1083
  if next_inputs is None:
1084
+ if prev_inputs is None:
1085
+ raise Exception('The autoregressive step hook returned None for next_inputs on '
1086
+ f'the first call of the chain (sample {sample_id}) — the first '
1087
+ 'call must return the initial model inputs; None would produce '
1088
+ 'an empty chain with no forward pass.')
1054
1089
  return None, new_chain_state
1055
1090
  if not isinstance(next_inputs, dict):
1056
1091
  raise Exception('The autoregressive step hook must return a dict of model inputs or '
@@ -1065,7 +1100,12 @@ class LeapLoader(LeapLoaderBase):
1065
1100
  f'on every call. Expected {sorted(expected_shapes)}, got '
1066
1101
  f'{sorted(next_inputs)} for sample {sample_id}.')
1067
1102
  for key, value in next_inputs.items():
1068
- if isinstance(value, np.ndarray) and list(value.shape) != expected_shapes[key]:
1103
+ if not isinstance(value, np.ndarray):
1104
+ raise Exception('The autoregressive step hook must return numpy arrays for every '
1105
+ f'model input on every call — input "{key}" is a '
1106
+ f'{type(value).__name__} for sample {sample_id} (forgot .numpy() '
1107
+ 'on a fed-back model output?).')
1108
+ if list(value.shape) != expected_shapes[key]:
1069
1109
  raise Exception('The autoregressive step hook must return fixed tensor shapes on '
1070
1110
  f'every call (use fixed-length padding + mask for growing '
1071
1111
  f'sequences). Input "{key}" was {expected_shapes[key]} on the '
@@ -149,10 +149,74 @@ class LeapLoaderBase:
149
149
  def has_custom_latent_space_decorator(self) -> bool:
150
150
  pass
151
151
 
152
+ # The autoregressive entry points raise instead of `pass`-ing like the abstract methods
153
+ # above: this class has no ABCMeta, so @abstractmethod is not enforced at runtime and an
154
+ # un-overridden `pass` body would return None — has_autoregressive_step would silently
155
+ # report False for an AR integration and the engine would skip the chain rollout with no
156
+ # error. A stale loader subclass keeps working for non-AR flows and fails loudly here the
157
+ # moment an AR flow reaches it.
158
+ @abstractmethod
152
159
  def has_autoregressive_step(self) -> bool:
153
- # Default False so existing LeapLoaderBase implementations stay valid; the concrete
154
- # loaders that can answer (LeapLoader in-process, LeapLoaderWithRedis via RPC) override.
155
- return False
160
+ raise NotImplementedError(f'{type(self).__name__} does not implement '
161
+ 'has_autoregressive_step it must be overridden '
162
+ '(LeapLoader in-process, LeapLoaderWithRedis via RPC).')
163
+
164
+ @abstractmethod
165
+ def get_gt(self, state: DataStateEnum, sample_id: Union[int, str]
166
+ ) -> Dict[str, npt.NDArray[np.float32]]:
167
+ raise NotImplementedError(f'{type(self).__name__} does not implement get_gt.')
168
+
169
+ @abstractmethod
170
+ def get_prediction_names_in_order(self) -> List[str]:
171
+ raise NotImplementedError(f'{type(self).__name__} does not implement '
172
+ 'get_prediction_names_in_order.')
173
+
174
+ @abstractmethod
175
+ def get_autoregressive_latent_space_aggregation(self) -> str:
176
+ raise NotImplementedError(f'{type(self).__name__} does not implement '
177
+ 'get_autoregressive_latent_space_aggregation.')
178
+
179
+ @abstractmethod
180
+ def get_autoregressive_decorator_names(self) -> Dict[str, List[str]]:
181
+ raise NotImplementedError(f'{type(self).__name__} does not implement '
182
+ 'get_autoregressive_decorator_names.')
183
+
184
+ @abstractmethod
185
+ def run_autoregressive_step(self, sample_id: Union[int, str],
186
+ prev_inputs: Optional[Dict[str, npt.NDArray[np.float32]]],
187
+ prev_outputs: Optional[Dict[str, npt.NDArray[np.float32]]],
188
+ chain_state: Any,
189
+ state: DataStateEnum
190
+ ) -> Tuple[Optional[Dict[str, npt.NDArray[np.float32]]], Any]:
191
+ raise NotImplementedError(f'{type(self).__name__} does not implement '
192
+ 'run_autoregressive_step.')
193
+
194
+ @abstractmethod
195
+ def run_autoregressive_metric(self, name: str, inputs: Dict[str, npt.NDArray[np.float32]],
196
+ outputs: Dict[str, npt.NDArray[np.float32]], chain_state: Any,
197
+ wired_args: Dict[str, npt.NDArray[np.float32]]) -> Any:
198
+ raise NotImplementedError(f'{type(self).__name__} does not implement '
199
+ 'run_autoregressive_metric.')
200
+
201
+ @abstractmethod
202
+ def run_autoregressive_loss(self, name: str, inputs: Dict[str, npt.NDArray[np.float32]],
203
+ outputs: Dict[str, npt.NDArray[np.float32]], chain_state: Any,
204
+ wired_args: Dict[str, npt.NDArray[np.float32]]) -> Any:
205
+ raise NotImplementedError(f'{type(self).__name__} does not implement '
206
+ 'run_autoregressive_loss.')
207
+
208
+ @abstractmethod
209
+ def run_autoregressive_visualizer(self, name: str, inputs: Dict[str, npt.NDArray[np.float32]],
210
+ outputs: Dict[str, npt.NDArray[np.float32]],
211
+ chain_state: Any,
212
+ wired_args: Dict[str, npt.NDArray[np.float32]]) -> Any:
213
+ raise NotImplementedError(f'{type(self).__name__} does not implement '
214
+ 'run_autoregressive_visualizer.')
215
+
216
+ @abstractmethod
217
+ def autoregressive_visualizer_by_name(self) -> Dict[str, VisualizerHandlerData]:
218
+ raise NotImplementedError(f'{type(self).__name__} does not implement '
219
+ 'autoregressive_visualizer_by_name.')
156
220
 
157
221
  @abstractmethod
158
222
  def get_heatmap_visualizer_raw_vis_input_arg_name(self, visualizer_name: str) -> Optional[str]:
@@ -1,3 +1,4 @@
1
+ import math
1
2
  import sys
2
3
  from pathlib import Path
3
4
  from types import TracebackType
@@ -134,6 +135,14 @@ def autoregressive_nests_equal(a: Any, b: Any) -> bool:
134
135
  return isinstance(a, (list, tuple)) and isinstance(b, (list, tuple)) and \
135
136
  len(a) == len(b) and all(autoregressive_nests_equal(x, y) for x, y in zip(a, b))
136
137
  if isinstance(a, np.ndarray) or isinstance(b, np.ndarray):
137
- return isinstance(a, np.ndarray) and isinstance(b, np.ndarray) and \
138
- a.shape == b.shape and bool(np.array_equal(a, b))
138
+ if not (isinstance(a, np.ndarray) and isinstance(b, np.ndarray) and a.shape == b.shape):
139
+ return False
140
+ # NaN leaves must equal themselves: this comparison backs the determinism checks, where
141
+ # NaN padding / running stats in a perfectly deterministic hook are legitimate.
142
+ if a.dtype.kind in 'fc' and b.dtype.kind in 'fc':
143
+ return bool(np.array_equal(a, b, equal_nan=True))
144
+ return bool(np.array_equal(a, b))
145
+ if isinstance(a, (float, np.floating)) and isinstance(b, (float, np.floating)) and \
146
+ math.isnan(a) and math.isnan(b):
147
+ return True
139
148
  return bool(a == b)
@@ -1,7 +1,7 @@
1
1
  [tool.poetry]
2
2
  name = "code-loader"
3
3
 
4
- version = "1.0.193.dev0"
4
+ version = "1.0.193.dev2"
5
5
  description = ""
6
6
  authors = ["dorhar <doron.harnoy@tensorleap.ai>"]
7
7
  license = "MIT"