code-loader 1.0.204__tar.gz → 1.0.205.dev0__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.204 → code_loader-1.0.205.dev0}/PKG-INFO +1 -1
  2. {code_loader-1.0.204 → code_loader-1.0.205.dev0}/code_loader/inner_leap_binder/leapbinder.py +24 -10
  3. {code_loader-1.0.204 → code_loader-1.0.205.dev0}/code_loader/inner_leap_binder/leapbinder_decorators.py +57 -5
  4. {code_loader-1.0.204 → code_loader-1.0.205.dev0}/code_loader/utils.py +75 -0
  5. {code_loader-1.0.204 → code_loader-1.0.205.dev0}/pyproject.toml +1 -1
  6. {code_loader-1.0.204 → code_loader-1.0.205.dev0}/LICENSE +0 -0
  7. {code_loader-1.0.204 → code_loader-1.0.205.dev0}/README.md +0 -0
  8. {code_loader-1.0.204 → code_loader-1.0.205.dev0}/code_loader/__init__.py +0 -0
  9. {code_loader-1.0.204 → code_loader-1.0.205.dev0}/code_loader/contract/__init__.py +0 -0
  10. {code_loader-1.0.204 → code_loader-1.0.205.dev0}/code_loader/contract/datasetclasses.py +0 -0
  11. {code_loader-1.0.204 → code_loader-1.0.205.dev0}/code_loader/contract/enums.py +0 -0
  12. {code_loader-1.0.204 → code_loader-1.0.205.dev0}/code_loader/contract/exceptions.py +0 -0
  13. {code_loader-1.0.204 → code_loader-1.0.205.dev0}/code_loader/contract/mapping.py +0 -0
  14. {code_loader-1.0.204 → code_loader-1.0.205.dev0}/code_loader/contract/responsedataclasses.py +0 -0
  15. {code_loader-1.0.204 → code_loader-1.0.205.dev0}/code_loader/contract/sim_config.py +0 -0
  16. {code_loader-1.0.204 → code_loader-1.0.205.dev0}/code_loader/contract/visualizer_classes.py +0 -0
  17. {code_loader-1.0.204 → code_loader-1.0.205.dev0}/code_loader/default_losses.py +0 -0
  18. {code_loader-1.0.204 → code_loader-1.0.205.dev0}/code_loader/default_metrics.py +0 -0
  19. {code_loader-1.0.204 → code_loader-1.0.205.dev0}/code_loader/experiment_api/__init__.py +0 -0
  20. {code_loader-1.0.204 → code_loader-1.0.205.dev0}/code_loader/experiment_api/api.py +0 -0
  21. {code_loader-1.0.204 → code_loader-1.0.205.dev0}/code_loader/experiment_api/cli_config_utils.py +0 -0
  22. {code_loader-1.0.204 → code_loader-1.0.205.dev0}/code_loader/experiment_api/client.py +0 -0
  23. {code_loader-1.0.204 → code_loader-1.0.205.dev0}/code_loader/experiment_api/epoch.py +0 -0
  24. {code_loader-1.0.204 → code_loader-1.0.205.dev0}/code_loader/experiment_api/experiment.py +0 -0
  25. {code_loader-1.0.204 → code_loader-1.0.205.dev0}/code_loader/experiment_api/experiment_context.py +0 -0
  26. {code_loader-1.0.204 → code_loader-1.0.205.dev0}/code_loader/experiment_api/types.py +0 -0
  27. {code_loader-1.0.204 → code_loader-1.0.205.dev0}/code_loader/experiment_api/utils.py +0 -0
  28. {code_loader-1.0.204 → code_loader-1.0.205.dev0}/code_loader/experiment_api/workingspace_config_utils.py +0 -0
  29. {code_loader-1.0.204 → code_loader-1.0.205.dev0}/code_loader/inner_leap_binder/__init__.py +0 -0
  30. {code_loader-1.0.204 → code_loader-1.0.205.dev0}/code_loader/leaploader.py +0 -0
  31. {code_loader-1.0.204 → code_loader-1.0.205.dev0}/code_loader/leaploaderbase.py +0 -0
  32. {code_loader-1.0.204 → code_loader-1.0.205.dev0}/code_loader/mixpanel_tracker.py +0 -0
  33. {code_loader-1.0.204 → code_loader-1.0.205.dev0}/code_loader/plot_functions/__init__.py +0 -0
  34. {code_loader-1.0.204 → code_loader-1.0.205.dev0}/code_loader/plot_functions/plot_functions.py +0 -0
  35. {code_loader-1.0.204 → code_loader-1.0.205.dev0}/code_loader/plot_functions/visualize.py +0 -0
  36. {code_loader-1.0.204 → code_loader-1.0.205.dev0}/code_loader/visualizers/__init__.py +0 -0
  37. {code_loader-1.0.204 → code_loader-1.0.205.dev0}/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.204
3
+ Version: 1.0.205.dev0
4
4
  Summary:
5
5
  Home-page: https://github.com/tensorleap/code-loader
6
6
  License: MIT
@@ -1032,17 +1032,18 @@ class LeapBinder:
1032
1032
 
1033
1033
  Each entry must be one of:
1034
1034
  - "foreground" (the friendly name for the max-pooled default LS)
1035
+ - "balanced" (the friendly name for the universal fallback default LS),
1036
+ allowed only when a custom latent space is registered with
1037
+ use_ls_for_analysis=True — that flagged LS is what replaces it
1035
1038
  - An input name registered via set_input(name=...)
1036
1039
  - A prediction name registered via add_prediction(name=...)
1037
1040
 
1038
1041
  Forbidden tokens (raise with a clear message):
1039
- - "balanced" — the friendly name for the universal fallback default
1040
- LS; cannot be disabled.
1041
1042
  - "user_custom" — populated by set_custom_latent_space; if the user
1042
1043
  registered a custom LS they should use it, not ignore it.
1043
1044
 
1044
- Order-independent: this runs after all set_input / add_prediction calls
1045
- because it consults setup_container.inputs and prediction_types.
1045
+ Order-independent: this runs after all set_input / add_prediction /
1046
+ set_custom_latent_space calls because it consults setup_container.
1046
1047
  """
1047
1048
  if not self.leap_analysis_configuration:
1048
1049
  return
@@ -1050,24 +1051,37 @@ class LeapBinder:
1050
1051
  if not names:
1051
1052
  return
1052
1053
 
1053
- forbidden = {"balanced", "user_custom"}
1054
+ forbidden = {"user_custom"}
1054
1055
  input_names = {h.name for h in self.setup_container.inputs}
1055
1056
  prediction_names = {h.name for h in self.setup_container.prediction_types}
1056
- allowed_tokens = {"foreground"} | input_names | prediction_names
1057
+ allowed_tokens = {"foreground", "balanced"} | input_names | prediction_names
1058
+ analysis_ls_names = [
1059
+ ls_name
1060
+ for ls_name, handler in self.setup_container.custom_latent_spaces.items()
1061
+ if handler.use_ls_for_analysis
1062
+ ]
1057
1063
 
1058
1064
  for name in names:
1059
1065
  if name in forbidden:
1060
1066
  raise Exception(
1061
- f"Latent space '{name}' cannot be ignored. "
1062
- f"'balanced' is the universal fallback default LS and 'user_custom' is "
1063
- f"reserved for set_custom_latent_space."
1067
+ f"Latent space '{name}' cannot be ignored. 'user_custom' is reserved "
1068
+ f"for set_custom_latent_space."
1069
+ )
1070
+ if name == "balanced" and not analysis_ls_names:
1071
+ raise Exception(
1072
+ "Latent space 'balanced' (the model-derived default latent space) can "
1073
+ "only be ignored when a replacement is registered. Add "
1074
+ "@tensorleap_custom_latent_space(..., use_ls_for_analysis=True) to the "
1075
+ "latent space that should be analyzed instead, or drop 'balanced' from "
1076
+ "ignore_latent_spaces — ignoring it with nothing in its place would "
1077
+ "leave the analyses with no latent space."
1064
1078
  )
1065
1079
  if name not in allowed_tokens:
1066
1080
  raise Exception(
1067
1081
  f"Latent space name '{name}' in ignore_latent_spaces did not match "
1068
1082
  f"any registered input or prediction. Available names: "
1069
1083
  f"inputs={sorted(input_names)}, predictions={sorted(prediction_names)}, "
1070
- f"tokens=['foreground']."
1084
+ f"tokens=['foreground', 'balanced']."
1071
1085
  )
1072
1086
 
1073
1087
  def set_batch_size_to_validate(self, batch_size: int) -> None:
@@ -16,7 +16,9 @@ import numpy as np
16
16
  import numpy.typing as npt
17
17
 
18
18
  from code_loader.utils import get_metadata_type_from_variable, map_dict_to_metadata_types, \
19
- validate_autoregressive_state_types, autoregressive_nests_equal
19
+ validate_autoregressive_state_types, autoregressive_nests_equal, \
20
+ simulate_engine_float16_downcast_on_call_args, ENGINE_STORAGE_DTYPE, \
21
+ TL_DISABLE_ENGINE_FLOAT16_SIMULATION_ENV_VAR
20
22
 
21
23
  logger = logging.getLogger(__name__)
22
24
 
@@ -308,6 +310,35 @@ def _require_sample_preprocess_response_supplied(user_function: Callable, args:
308
310
  f"SamplePreprocessResponse(sample_id, preprocess)).")
309
311
 
310
312
 
313
+ def _simulate_engine_tensor_dtype(args: tuple, kwargs: dict):
314
+ """Hand a metric / loss the tensor dtype the platform will hand it.
315
+
316
+ The engine downcasts every tensor to float16 before pushing it to Redis, and metrics and
317
+ losses read their tensors back from there. A local run would otherwise pass the float32
318
+ arrays straight from the encoders and the model, so a float32-only loss (a Keras/TF loss
319
+ that refuses a float16 y_pred, say) passes the local test and only fails in evaluation.
320
+ Downcasting here makes the local test fail the same way, before the project is pushed.
321
+
322
+ No-op on the platform, where the tensors already arrive downcast. Returns the (possibly
323
+ rewritten) call args plus whether anything was actually downcast.
324
+ """
325
+ if _call_from_tl_platform:
326
+ return args, kwargs, False
327
+ downcast_args, downcast_kwargs = simulate_engine_float16_downcast_on_call_args(args, kwargs)
328
+ was_downcast = any(arg is not original for arg, original in zip(downcast_args, args)) or \
329
+ any(arg is not kwargs[arg_name] for arg_name, arg in downcast_kwargs.items())
330
+ return downcast_args, downcast_kwargs, was_downcast
331
+
332
+
333
+ def _print_engine_float16_simulation_hint(func_name: str) -> None:
334
+ print(f"note: {func_name}() received its tensors as {np.dtype(ENGINE_STORAGE_DTYPE).name} — "
335
+ f"the platform stores every tensor at that precision, so the local test feeds the same "
336
+ f"dtype the engine will. If the failure above is a dtype mismatch, cast inside "
337
+ f"{func_name}() (e.g. arr.astype(np.float32)) rather than relying on the caller's "
338
+ f"dtype. Set {TL_DISABLE_ENGINE_FLOAT16_SIMULATION_ENV_VAR}=true to opt out of this "
339
+ f"simulation locally.")
340
+
341
+
311
342
  def _warn_loss_inputs_not_wired_to_model() -> None:
312
343
  """Warn when a custom loss consumes an input encoder the model is not given.
313
344
 
@@ -900,7 +931,10 @@ def tensorleap_custom_metric(name: str,
900
931
  )
901
932
 
902
933
  else:
903
- assert all(isinstance(v, (float, int, type(None), np.float32)) for v in single_metric_result), (
934
+ # np.floating, not just np.float32: metric inputs are downcast to the
935
+ # engine's float16, so a metric that returns its computed values as numpy
936
+ # scalars now yields np.float16 ones.
937
+ assert all(isinstance(v, (float, int, type(None), np.floating)) for v in single_metric_result), (
904
938
  f"{supported_types_message}\n"
905
939
  f"Got {'a dict where the value of ' + str(key) + ' is of type ' if key is not None else ''}"
906
940
  f"List[{', '.join(type(v).__name__ for v in single_metric_result)}]."
@@ -1019,9 +1053,15 @@ def tensorleap_custom_metric(name: str,
1019
1053
  def inner(*args, **kwargs):
1020
1054
  if not _call_from_tl_platform:
1021
1055
  set_current('tensorleap_custom_metric')
1056
+ args, kwargs, was_downcast = _simulate_engine_tensor_dtype(args, kwargs)
1022
1057
  _validate_input_args(*args, **kwargs)
1023
1058
 
1024
- result = inner_without_validate(*args, **kwargs)
1059
+ try:
1060
+ result = inner_without_validate(*args, **kwargs)
1061
+ except Exception:
1062
+ if was_downcast:
1063
+ _print_engine_float16_simulation_hint(user_function.__name__)
1064
+ raise
1025
1065
 
1026
1066
  _validate_result(result)
1027
1067
  if not _call_from_tl_platform:
@@ -1229,9 +1269,15 @@ def tensorleap_custom_instances_metric(name: str,
1229
1269
  def inner(*args, **kwargs):
1230
1270
  if not _call_from_tl_platform:
1231
1271
  set_current('tensorleap_custom_instances_metric')
1272
+ args, kwargs, was_downcast = _simulate_engine_tensor_dtype(args, kwargs)
1232
1273
  _validate_input_args(*args, **kwargs)
1233
1274
 
1234
- result = inner_without_validate(*args, **kwargs)
1275
+ try:
1276
+ result = inner_without_validate(*args, **kwargs)
1277
+ except Exception:
1278
+ if was_downcast:
1279
+ _print_engine_float16_simulation_hint(user_function.__name__)
1280
+ raise
1235
1281
 
1236
1282
  _validate_result(result)
1237
1283
  if not _call_from_tl_platform:
@@ -3454,9 +3500,15 @@ def tensorleap_custom_loss(name: str, connects_to=None):
3454
3500
  def inner(*args, **kwargs):
3455
3501
  if not _call_from_tl_platform:
3456
3502
  set_current("tensorleap_custom_loss")
3503
+ args, kwargs, was_downcast = _simulate_engine_tensor_dtype(args, kwargs)
3457
3504
  _validate_input_args(*args, **kwargs)
3458
3505
 
3459
- result = inner_without_validate(*args, **kwargs)
3506
+ try:
3507
+ result = inner_without_validate(*args, **kwargs)
3508
+ except Exception:
3509
+ if was_downcast:
3510
+ _print_engine_float16_simulation_hint(user_function.__name__)
3511
+ raise
3460
3512
 
3461
3513
  _validate_result(result)
3462
3514
  if not _call_from_tl_platform:
@@ -1,5 +1,6 @@
1
1
  import io
2
2
  import math
3
+ import os
3
4
  import pickle
4
5
  import sys
5
6
  from pathlib import Path
@@ -42,6 +43,80 @@ def to_numpy_return_masks_wrapper(encoder_function: InstanceCallableInterface) -
42
43
  return numpy_encoder_function
43
44
 
44
45
 
46
+ # The engine downcasts every tensor to float16 before pushing it to Redis, and metrics and losses
47
+ # read their tensors back from there — so on the platform they are handed float16 arrays. A local
48
+ # run feeds them the float32 arrays straight from the encoders and the model, which is why a
49
+ # float32-only loss (e.g. a Keras/TF loss that refuses a float16 y_pred) passes the local test and
50
+ # then fails in evaluation. Mirror the engine locally so the failure surfaces before the push.
51
+ ENGINE_STORAGE_DTYPE = np.float16
52
+ # The engine's cast is range-guarded (redis_metrics_queue_manager.py): only a non-empty float32 /
53
+ # float64 array whose abs-max fits float16's finite range is cast, so an array holding large values
54
+ # (big logits, say) keeps its original dtype instead of overflowing to inf. Arrays holding NaN are
55
+ # left alone too — their abs-max is NaN, and `NaN <= max` is False.
56
+ ENGINE_STORAGE_DTYPE_MAX = 65504.0 # float16 finite max
57
+ ENGINE_DOWNCAST_SOURCE_DTYPES = (np.float32, np.float64)
58
+ TL_DISABLE_ENGINE_FLOAT16_SIMULATION_ENV_VAR = 'TL_DISABLE_ENGINE_FLOAT16_SIMULATION'
59
+
60
+
61
+ def is_engine_float16_simulation_disabled() -> bool:
62
+ return os.environ.get(TL_DISABLE_ENGINE_FLOAT16_SIMULATION_ENV_VAR, '').lower() in \
63
+ ('1', 'true', 'yes')
64
+
65
+
66
+ def simulate_engine_float16_downcast(value: Any) -> Any:
67
+ """Return ``value`` with its float tensors downcast the way the engine downcasts them.
68
+
69
+ Recurses into the containers a handler argument can arrive in (a grouped input is a list of
70
+ arrays). Only the arrays the engine would actually cast are touched — see
71
+ :func:`_downcast_array_to_engine_dtype`. Ints, bools and strings survive the engine's storage
72
+ unchanged, and anything that is not a tensor or a container of tensors
73
+ (SamplePreprocessResponse, for instance) is returned as is.
74
+ """
75
+ if is_engine_float16_simulation_disabled():
76
+ return value
77
+ return _downcast_to_engine_dtype(value)
78
+
79
+
80
+ def _downcast_array_to_engine_dtype(array: npt.NDArray[Any]) -> npt.NDArray[Any]:
81
+ """Mirror the engine's guarded cast for a single array.
82
+
83
+ The engine casts an array to float16 only when it is a non-empty float32 / float64 array whose
84
+ whole-array abs-max fits float16's finite range; anything else is stored at its original dtype.
85
+ Casting unconditionally would diverge the other way — a local run would hand a handler ``inf``
86
+ where the platform hands it a finite float32.
87
+ """
88
+ if array.dtype not in ENGINE_DOWNCAST_SOURCE_DTYPES or not array.size:
89
+ return array
90
+ # NaN fails this comparison, which is how the engine ends up leaving NaN-bearing arrays alone.
91
+ if not float(np.abs(array).max()) <= ENGINE_STORAGE_DTYPE_MAX:
92
+ return array
93
+ return array.astype(ENGINE_STORAGE_DTYPE)
94
+
95
+
96
+ def _downcast_to_engine_dtype(value: Any) -> Any:
97
+ # Containers return the original object when no leaf changed, so callers can tell whether a
98
+ # downcast actually happened by identity.
99
+ if isinstance(value, np.ndarray):
100
+ return _downcast_array_to_engine_dtype(value)
101
+ if isinstance(value, (list, tuple)):
102
+ downcast = [_downcast_to_engine_dtype(item) for item in value]
103
+ if all(item is original for item, original in zip(downcast, value)):
104
+ return value
105
+ return downcast if isinstance(value, list) else tuple(downcast)
106
+ if isinstance(value, dict):
107
+ downcast_dict = {key: _downcast_to_engine_dtype(item) for key, item in value.items()}
108
+ if all(item is value[key] for key, item in downcast_dict.items()):
109
+ return value
110
+ return downcast_dict
111
+ return value
112
+
113
+
114
+ def simulate_engine_float16_downcast_on_call_args(
115
+ args: Tuple[Any, ...], kwargs: Dict[str, Any]) -> Tuple[Tuple[Any, ...], Dict[str, Any]]:
116
+ return (tuple(simulate_engine_float16_downcast(arg) for arg in args),
117
+ {arg_name: simulate_engine_float16_downcast(arg) for arg_name, arg in kwargs.items()})
118
+
119
+
45
120
  def get_root_traceback(exc_tb: TracebackType) -> TracebackType:
46
121
  return_traceback = exc_tb
47
122
  while return_traceback.tb_next is not None:
@@ -1,6 +1,6 @@
1
1
  [tool.poetry]
2
2
  name = "code-loader"
3
- version = "1.0.204"
3
+ version = "1.0.205.dev0"
4
4
  description = ""
5
5
  authors = ["dorhar <doron.harnoy@tensorleap.ai>"]
6
6
  license = "MIT"