ZhengyangZhang commited on
Commit
4d4d2aa
·
verified ·
1 Parent(s): 09eb2a3

Add files using upload-large-folder tool

Browse files
This view is limited to 50 files because it contains too many changes.   See raw diff
Files changed (50) hide show
  1. lib/python3.12/site-packages/ray/air/__init__.py +21 -0
  2. lib/python3.12/site-packages/ray/air/__pycache__/__init__.cpython-312.pyc +0 -0
  3. lib/python3.12/site-packages/ray/air/__pycache__/config.cpython-312.pyc +0 -0
  4. lib/python3.12/site-packages/ray/air/__pycache__/constants.cpython-312.pyc +0 -0
  5. lib/python3.12/site-packages/ray/air/__pycache__/data_batch_type.cpython-312.pyc +0 -0
  6. lib/python3.12/site-packages/ray/air/__pycache__/result.cpython-312.pyc +0 -0
  7. lib/python3.12/site-packages/ray/air/__pycache__/session.cpython-312.pyc +0 -0
  8. lib/python3.12/site-packages/ray/air/_internal/__init__.py +0 -0
  9. lib/python3.12/site-packages/ray/air/_internal/__pycache__/__init__.cpython-312.pyc +0 -0
  10. lib/python3.12/site-packages/ray/air/_internal/__pycache__/config.cpython-312.pyc +0 -0
  11. lib/python3.12/site-packages/ray/air/_internal/__pycache__/filelock.cpython-312.pyc +0 -0
  12. lib/python3.12/site-packages/ray/air/_internal/__pycache__/json.cpython-312.pyc +0 -0
  13. lib/python3.12/site-packages/ray/air/_internal/__pycache__/mlflow.cpython-312.pyc +0 -0
  14. lib/python3.12/site-packages/ray/air/_internal/__pycache__/tensorflow_utils.cpython-312.pyc +0 -0
  15. lib/python3.12/site-packages/ray/air/_internal/__pycache__/torch_utils.cpython-312.pyc +0 -0
  16. lib/python3.12/site-packages/ray/air/_internal/__pycache__/uri_utils.cpython-312.pyc +0 -0
  17. lib/python3.12/site-packages/ray/air/_internal/__pycache__/usage.cpython-312.pyc +0 -0
  18. lib/python3.12/site-packages/ray/air/_internal/__pycache__/util.cpython-312.pyc +0 -0
  19. lib/python3.12/site-packages/ray/air/_internal/config.py +47 -0
  20. lib/python3.12/site-packages/ray/air/_internal/device_manager/__init__.py +92 -0
  21. lib/python3.12/site-packages/ray/air/_internal/device_manager/__pycache__/__init__.cpython-312.pyc +0 -0
  22. lib/python3.12/site-packages/ray/air/_internal/device_manager/__pycache__/cpu.cpython-312.pyc +0 -0
  23. lib/python3.12/site-packages/ray/air/_internal/device_manager/__pycache__/hpu.cpython-312.pyc +0 -0
  24. lib/python3.12/site-packages/ray/air/_internal/device_manager/__pycache__/npu.cpython-312.pyc +0 -0
  25. lib/python3.12/site-packages/ray/air/_internal/device_manager/__pycache__/nvidia_gpu.cpython-312.pyc +0 -0
  26. lib/python3.12/site-packages/ray/air/_internal/device_manager/__pycache__/torch_device_manager.cpython-312.pyc +0 -0
  27. lib/python3.12/site-packages/ray/air/_internal/device_manager/cpu.py +30 -0
  28. lib/python3.12/site-packages/ray/air/_internal/device_manager/hpu.py +50 -0
  29. lib/python3.12/site-packages/ray/air/_internal/device_manager/npu.py +104 -0
  30. lib/python3.12/site-packages/ray/air/_internal/device_manager/nvidia_gpu.py +79 -0
  31. lib/python3.12/site-packages/ray/air/_internal/device_manager/torch_device_manager.py +40 -0
  32. lib/python3.12/site-packages/ray/air/_internal/filelock.py +46 -0
  33. lib/python3.12/site-packages/ray/air/_internal/json.py +31 -0
  34. lib/python3.12/site-packages/ray/air/_internal/mlflow.py +342 -0
  35. lib/python3.12/site-packages/ray/air/_internal/tensorflow_utils.py +137 -0
  36. lib/python3.12/site-packages/ray/air/_internal/torch_utils.py +548 -0
  37. lib/python3.12/site-packages/ray/air/_internal/uri_utils.py +101 -0
  38. lib/python3.12/site-packages/ray/air/_internal/usage.py +274 -0
  39. lib/python3.12/site-packages/ray/air/_internal/util.py +125 -0
  40. lib/python3.12/site-packages/ray/air/config.py +695 -0
  41. lib/python3.12/site-packages/ray/air/constants.py +94 -0
  42. lib/python3.12/site-packages/ray/air/data_batch_type.py +11 -0
  43. lib/python3.12/site-packages/ray/air/examples/__init__.py +0 -0
  44. lib/python3.12/site-packages/ray/air/examples/__pycache__/__init__.cpython-312.pyc +0 -0
  45. lib/python3.12/site-packages/ray/air/execution/__init__.py +12 -0
  46. lib/python3.12/site-packages/ray/air/execution/__pycache__/__init__.cpython-312.pyc +0 -0
  47. lib/python3.12/site-packages/ray/air/execution/_internal/__init__.py +5 -0
  48. lib/python3.12/site-packages/ray/air/execution/_internal/__pycache__/__init__.cpython-312.pyc +0 -0
  49. lib/python3.12/site-packages/ray/air/execution/_internal/__pycache__/actor_manager.cpython-312.pyc +0 -0
  50. lib/python3.12/site-packages/ray/air/execution/_internal/__pycache__/barrier.cpython-312.pyc +0 -0
lib/python3.12/site-packages/ray/air/__init__.py ADDED
@@ -0,0 +1,21 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ from ray.air.config import (
2
+ CheckpointConfig,
3
+ FailureConfig,
4
+ RunConfig,
5
+ ScalingConfig,
6
+ )
7
+ from ray.air.data_batch_type import DataBatchType
8
+ from ray.air.execution.resources.request import AcquiredResources, ResourceRequest
9
+ from ray.air.result import Result
10
+ import ray.data # noqa: F401 # TODO: This is a hack to avoid circular import
11
+
12
+ __all__ = [
13
+ "DataBatchType",
14
+ "RunConfig",
15
+ "Result",
16
+ "ScalingConfig",
17
+ "FailureConfig",
18
+ "CheckpointConfig",
19
+ "AcquiredResources",
20
+ "ResourceRequest",
21
+ ]
lib/python3.12/site-packages/ray/air/__pycache__/__init__.cpython-312.pyc ADDED
Binary file (625 Bytes). View file
 
lib/python3.12/site-packages/ray/air/__pycache__/config.cpython-312.pyc ADDED
Binary file (30.4 kB). View file
 
lib/python3.12/site-packages/ray/air/__pycache__/constants.cpython-312.pyc ADDED
Binary file (1.41 kB). View file
 
lib/python3.12/site-packages/ray/air/__pycache__/data_batch_type.cpython-312.pyc ADDED
Binary file (463 Bytes). View file
 
lib/python3.12/site-packages/ray/air/__pycache__/result.cpython-312.pyc ADDED
Binary file (13.3 kB). View file
 
lib/python3.12/site-packages/ray/air/__pycache__/session.cpython-312.pyc ADDED
Binary file (234 Bytes). View file
 
lib/python3.12/site-packages/ray/air/_internal/__init__.py ADDED
File without changes
lib/python3.12/site-packages/ray/air/_internal/__pycache__/__init__.cpython-312.pyc ADDED
Binary file (196 Bytes). View file
 
lib/python3.12/site-packages/ray/air/_internal/__pycache__/config.cpython-312.pyc ADDED
Binary file (1.97 kB). View file
 
lib/python3.12/site-packages/ray/air/_internal/__pycache__/filelock.cpython-312.pyc ADDED
Binary file (2.87 kB). View file
 
lib/python3.12/site-packages/ray/air/_internal/__pycache__/json.cpython-312.pyc ADDED
Binary file (1.91 kB). View file
 
lib/python3.12/site-packages/ray/air/_internal/__pycache__/mlflow.cpython-312.pyc ADDED
Binary file (14.3 kB). View file
 
lib/python3.12/site-packages/ray/air/_internal/__pycache__/tensorflow_utils.cpython-312.pyc ADDED
Binary file (7.05 kB). View file
 
lib/python3.12/site-packages/ray/air/_internal/__pycache__/torch_utils.cpython-312.pyc ADDED
Binary file (23.9 kB). View file
 
lib/python3.12/site-packages/ray/air/_internal/__pycache__/uri_utils.cpython-312.pyc ADDED
Binary file (5.78 kB). View file
 
lib/python3.12/site-packages/ray/air/_internal/__pycache__/usage.cpython-312.pyc ADDED
Binary file (10.4 kB). View file
 
lib/python3.12/site-packages/ray/air/_internal/__pycache__/util.cpython-312.pyc ADDED
Binary file (5.63 kB). View file
 
lib/python3.12/site-packages/ray/air/_internal/config.py ADDED
@@ -0,0 +1,47 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import dataclasses
2
+ from typing import Iterable
3
+
4
+
5
+ def ensure_only_allowed_dataclass_keys_updated(
6
+ dataclass: dataclasses.dataclass,
7
+ allowed_keys: Iterable[str],
8
+ ):
9
+ """
10
+ Validate dataclass by raising an exception if any key not included in
11
+ ``allowed_keys`` differs from the default value.
12
+
13
+ A ``ValueError`` will also be raised if any of the ``allowed_keys``
14
+ is not present in ``dataclass.__dict__``.
15
+
16
+ Args:
17
+ dataclass: Dict or dataclass to check.
18
+ allowed_keys: dataclass attribute keys that can have a value different than
19
+ the default one.
20
+ """
21
+ default_data = dataclass.__class__()
22
+
23
+ allowed_keys = set(allowed_keys)
24
+
25
+ # TODO: split keys_not_in_dict validation to a separate function.
26
+ keys_not_in_dict = [key for key in allowed_keys if key not in default_data.__dict__]
27
+ if keys_not_in_dict:
28
+ raise ValueError(
29
+ f"Key(s) {keys_not_in_dict} are not present in "
30
+ f"{dataclass.__class__.__name__}. "
31
+ "Remove them from `allowed_keys`. "
32
+ f"Valid keys: {list(default_data.__dict__.keys())}"
33
+ )
34
+
35
+ # These keys should not have been updated in the `dataclass` object
36
+ prohibited_keys = set(default_data.__dict__) - allowed_keys
37
+
38
+ bad_keys = [
39
+ key
40
+ for key in prohibited_keys
41
+ if dataclass.__dict__[key] != default_data.__dict__[key]
42
+ ]
43
+ if bad_keys:
44
+ raise ValueError(
45
+ f"Key(s) {bad_keys} are not allowed to be updated in the current context. "
46
+ "Remove them from the dataclass."
47
+ )
lib/python3.12/site-packages/ray/air/_internal/device_manager/__init__.py ADDED
@@ -0,0 +1,92 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import logging
2
+ import threading
3
+ from typing import Optional
4
+
5
+ import ray
6
+ import ray._private.ray_constants as ray_constants
7
+ from ray.air._internal.device_manager.cpu import CPUTorchDeviceManager
8
+ from ray.air._internal.device_manager.hpu import HPUTorchDeviceManager
9
+ from ray.air._internal.device_manager.npu import NPUTorchDeviceManager
10
+ from ray.air._internal.device_manager.nvidia_gpu import CUDATorchDeviceManager
11
+ from ray.air._internal.device_manager.torch_device_manager import TorchDeviceManager
12
+
13
+ logger = logging.getLogger(__name__)
14
+
15
+
16
+ DEFAULT_TORCH_DEVICE_MANAGER_CLS = CPUTorchDeviceManager
17
+
18
+
19
+ SUPPORTED_ACCELERATOR_TORCH_DEVICE_MANAGER = {
20
+ ray_constants.GPU: CUDATorchDeviceManager,
21
+ ray_constants.HPU: HPUTorchDeviceManager,
22
+ ray_constants.NPU: NPUTorchDeviceManager,
23
+ }
24
+
25
+
26
+ def register_custom_torch_dist_backend(backend: Optional[str] = None) -> None:
27
+ if backend == "hccl":
28
+ # The name for the communication backend of Habana and torch-npu is the same.
29
+ HPUTorchDeviceManager.register_custom_torch_dist_backend()
30
+
31
+ NPUTorchDeviceManager.register_custom_torch_dist_backend()
32
+
33
+
34
+ _torch_device_manager = None
35
+ _torch_device_manager_lock = threading.Lock()
36
+
37
+
38
+ def get_torch_device_manager_by_context() -> TorchDeviceManager:
39
+ global _torch_device_manager
40
+
41
+ with _torch_device_manager_lock:
42
+ if not _torch_device_manager:
43
+ existing_device_manager_cls = None
44
+ resources = ray.get_runtime_context().get_accelerator_ids()
45
+
46
+ # select correct accelerator type from resources
47
+ for resource_type, resource_value in resources.items():
48
+ device_manager_cls = SUPPORTED_ACCELERATOR_TORCH_DEVICE_MANAGER.get(
49
+ resource_type, None
50
+ )
51
+ if resource_value and device_manager_cls:
52
+ # An error will raise when multiple accelerators are specified.
53
+ if existing_device_manager_cls:
54
+ raise RuntimeError(
55
+ "Unable to determine the appropriate DeviceManager "
56
+ f"for the specified resources {resources}."
57
+ )
58
+ else:
59
+ existing_device_manager_cls = device_manager_cls
60
+
61
+ device_manager_cls = (
62
+ existing_device_manager_cls or DEFAULT_TORCH_DEVICE_MANAGER_CLS
63
+ )
64
+
65
+ _torch_device_manager = device_manager_cls()
66
+
67
+ return _torch_device_manager
68
+
69
+
70
+ def get_torch_device_manager_by_device_type(device_type: str):
71
+ if device_type.lower() == ray_constants.GPU.lower() or device_type == "cuda":
72
+ return CUDATorchDeviceManager()
73
+ elif device_type.lower() == ray_constants.NPU.lower():
74
+ return NPUTorchDeviceManager()
75
+ elif device_type.lower() == ray_constants.HPU.lower():
76
+ return HPUTorchDeviceManager()
77
+ elif device_type.lower() == "cpu":
78
+ return CPUTorchDeviceManager()
79
+
80
+ raise RuntimeError(f"Device type {device_type} cannot be recognized.")
81
+
82
+
83
+ __all__ = [
84
+ TorchDeviceManager,
85
+ CPUTorchDeviceManager,
86
+ CUDATorchDeviceManager,
87
+ HPUTorchDeviceManager,
88
+ NPUTorchDeviceManager,
89
+ register_custom_torch_dist_backend,
90
+ get_torch_device_manager_by_context,
91
+ get_torch_device_manager_by_device_type,
92
+ ]
lib/python3.12/site-packages/ray/air/_internal/device_manager/__pycache__/__init__.cpython-312.pyc ADDED
Binary file (3.5 kB). View file
 
lib/python3.12/site-packages/ray/air/_internal/device_manager/__pycache__/cpu.cpython-312.pyc ADDED
Binary file (1.83 kB). View file
 
lib/python3.12/site-packages/ray/air/_internal/device_manager/__pycache__/hpu.cpython-312.pyc ADDED
Binary file (2.83 kB). View file
 
lib/python3.12/site-packages/ray/air/_internal/device_manager/__pycache__/npu.cpython-312.pyc ADDED
Binary file (5.11 kB). View file
 
lib/python3.12/site-packages/ray/air/_internal/device_manager/__pycache__/nvidia_gpu.cpython-312.pyc ADDED
Binary file (4.21 kB). View file
 
lib/python3.12/site-packages/ray/air/_internal/device_manager/__pycache__/torch_device_manager.cpython-312.pyc ADDED
Binary file (2.26 kB). View file
 
lib/python3.12/site-packages/ray/air/_internal/device_manager/cpu.py ADDED
@@ -0,0 +1,30 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ from contextlib import contextmanager
2
+ from typing import List
3
+
4
+ import torch
5
+
6
+ from ray.air._internal.device_manager.torch_device_manager import TorchDeviceManager
7
+
8
+
9
+ class CPUTorchDeviceManager(TorchDeviceManager):
10
+ """CPU device manager"""
11
+
12
+ def is_available(self) -> bool():
13
+ return True
14
+
15
+ def get_devices(self) -> List[torch.device]:
16
+ """Gets the correct torch device list configured for this process."""
17
+ return [torch.device("cpu")]
18
+
19
+ def supports_stream(self) -> bool:
20
+ """Validate if the device type support create a stream"""
21
+ return False
22
+
23
+ def get_stream_context(self, stream):
24
+ """Return empty context mananger for CPU."""
25
+
26
+ @contextmanager
27
+ def default_context_manager():
28
+ yield
29
+
30
+ return default_context_manager()
lib/python3.12/site-packages/ray/air/_internal/device_manager/hpu.py ADDED
@@ -0,0 +1,50 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ from contextlib import contextmanager
2
+ from typing import List, Union
3
+
4
+ import torch
5
+
6
+ from ray._private.accelerators.hpu import HPU_PACKAGE_AVAILABLE
7
+ from ray.air._internal.device_manager.torch_device_manager import TorchDeviceManager
8
+
9
+ if HPU_PACKAGE_AVAILABLE:
10
+ import habana_frameworks.torch.hpu as torch_hpu
11
+
12
+
13
+ class HPUTorchDeviceManager(TorchDeviceManager):
14
+ """HPU device manager"""
15
+
16
+ @staticmethod
17
+ def register_custom_torch_dist_backend():
18
+ if HPU_PACKAGE_AVAILABLE:
19
+ import habana_frameworks.torch.core # noqa: F401
20
+ import habana_frameworks.torch.distributed.hccl # noqa: F401
21
+
22
+ def is_available(self) -> bool():
23
+ if not HPU_PACKAGE_AVAILABLE:
24
+ return False
25
+
26
+ return torch_hpu.is_available()
27
+
28
+ def get_devices(self) -> List[torch.device]:
29
+ if not self.is_available():
30
+ raise RuntimeError(
31
+ "Using HPUTorchDeviceManager but torch hpu is not available."
32
+ )
33
+
34
+ return [torch.device("hpu")]
35
+
36
+ def set_device(self, device: Union[torch.device, int, str, None]):
37
+ torch_hpu.set_device(device)
38
+
39
+ def supports_stream(self) -> bool:
40
+ """Validate if the device type support create a stream"""
41
+ return False
42
+
43
+ def get_stream_context(self, stream):
44
+ """Get HPU stream context manager, empty so far."""
45
+
46
+ @contextmanager
47
+ def default_context_manager():
48
+ yield
49
+
50
+ return default_context_manager()
lib/python3.12/site-packages/ray/air/_internal/device_manager/npu.py ADDED
@@ -0,0 +1,104 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import os
2
+ from importlib.util import find_spec
3
+ from typing import List, Union
4
+
5
+ import torch
6
+
7
+ import ray
8
+ import ray._private.ray_constants as ray_constants
9
+ from ray._private.accelerators.npu import ASCEND_RT_VISIBLE_DEVICES_ENV_VAR
10
+ from ray.air._internal.device_manager.torch_device_manager import TorchDeviceManager
11
+
12
+
13
+ def is_package_present(package_name: str) -> bool:
14
+ try:
15
+ return find_spec(package_name) is not None
16
+ except ModuleNotFoundError:
17
+ return False
18
+
19
+
20
+ NPU_TORCH_PACKAGE_AVAILABLE = is_package_present("torch_npu")
21
+
22
+
23
+ if NPU_TORCH_PACKAGE_AVAILABLE:
24
+ import torch_npu # noqa: F401
25
+
26
+
27
+ class NPUTorchDeviceManager(TorchDeviceManager):
28
+ """Ascend NPU device manager"""
29
+
30
+ @staticmethod
31
+ def register_custom_torch_dist_backend():
32
+ if NPU_TORCH_PACKAGE_AVAILABLE:
33
+ import torch_npu # noqa: F401, F811
34
+
35
+ def is_available(self) -> bool:
36
+ if not NPU_TORCH_PACKAGE_AVAILABLE:
37
+ return False
38
+
39
+ return torch.npu.is_available()
40
+
41
+ def get_devices(self) -> List[torch.device]:
42
+ """Gets the correct torch device list configured for this process.
43
+
44
+ Returns a list of torch NPU devices allocated for the current worker.
45
+ If no NPUs are assigned, then it returns a list with a single CPU device.
46
+ """
47
+ if NPU_TORCH_PACKAGE_AVAILABLE and torch.npu.is_available():
48
+ npu_ids = [
49
+ str(id)
50
+ for id in ray.get_runtime_context().get_accelerator_ids()[
51
+ ray_constants.NPU
52
+ ]
53
+ ]
54
+
55
+ device_ids = []
56
+
57
+ if len(npu_ids) > 0:
58
+ npu_visible_str = os.environ.get(ASCEND_RT_VISIBLE_DEVICES_ENV_VAR, "")
59
+ if npu_visible_str and npu_visible_str != "NoDevFiles":
60
+ npu_visible_list = npu_visible_str.split(",")
61
+ else:
62
+ npu_visible_list = []
63
+
64
+ for npu_id in npu_ids:
65
+ try:
66
+ device_ids.append(npu_visible_list.index(npu_id))
67
+ except IndexError:
68
+ raise RuntimeError(
69
+ "ASCEND_RT_VISIBLE_DEVICES set incorrectly. "
70
+ f"Got {npu_visible_str}, expected to include {npu_id}. "
71
+ "Did you override the `ASCEND_RT_VISIBLE_DEVICES` "
72
+ "environment variable?"
73
+ )
74
+ else:
75
+ # If called on the driver or outside of Ray Train, return the
76
+ # 0th device.
77
+ device_ids.append(0)
78
+
79
+ devices = [torch.device(f"npu:{device_id}") for device_id in device_ids]
80
+ else:
81
+ raise RuntimeError(
82
+ "Using NPUTorchDeviceManager but torch npu is not available."
83
+ )
84
+
85
+ return devices
86
+
87
+ def set_device(self, device: Union[torch.device, int]):
88
+ torch.npu.set_device(device)
89
+
90
+ def supports_stream(self) -> bool:
91
+ """Validate if the device type support to create a stream"""
92
+ return True
93
+
94
+ def create_stream(self, device):
95
+ """Create a stream on NPU device"""
96
+ return torch.npu.Stream(device)
97
+
98
+ def get_stream_context(self, stream):
99
+ """Get a torch.stream context on NPU device"""
100
+ return torch.npu.stream(stream)
101
+
102
+ def get_current_stream(self):
103
+ """Get current stream for NPU device"""
104
+ return torch.npu.current_stream()
lib/python3.12/site-packages/ray/air/_internal/device_manager/nvidia_gpu.py ADDED
@@ -0,0 +1,79 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import os
2
+ from typing import List, Union
3
+
4
+ import torch
5
+
6
+ import ray
7
+ from ray.air._internal.device_manager.torch_device_manager import TorchDeviceManager
8
+
9
+
10
+ class CUDATorchDeviceManager(TorchDeviceManager):
11
+ """CUDA device manager"""
12
+
13
+ def is_available(self) -> bool():
14
+ return torch.cuda.is_available()
15
+
16
+ def get_devices(self) -> List[torch.device]:
17
+ """Gets the correct torch device list configured for this process.
18
+
19
+ Returns a list of torch CUDA devices allocated for the current worker.
20
+ If no GPUs are assigned, then it returns a list with a single CPU device.
21
+
22
+ Assumes that `CUDA_VISIBLE_DEVICES` is set and is a
23
+ superset of the `ray.get_gpu_ids()`.
24
+ """
25
+
26
+ # GPU IDs are assigned by Ray after you specify "use_gpu"
27
+ # GPU `ray.get_gpu_ids()` may return ints or may return strings.
28
+ # We should always convert to strings.
29
+ gpu_ids = [str(id) for id in ray.get_gpu_ids()]
30
+
31
+ device_ids = []
32
+
33
+ if len(gpu_ids) > 0:
34
+ cuda_visible_str = os.environ.get("CUDA_VISIBLE_DEVICES", "")
35
+ if cuda_visible_str and cuda_visible_str != "NoDevFiles":
36
+ cuda_visible_list = cuda_visible_str.split(",")
37
+ else:
38
+ cuda_visible_list = []
39
+
40
+ # By default, there should only be one GPU ID if `use_gpu=True`.
41
+ # If there are multiple GPUs, return a list of devices.
42
+ # If using fractional GPUs, these IDs are not guaranteed
43
+ # to be unique across different processes.
44
+ for gpu_id in gpu_ids:
45
+ try:
46
+ device_ids.append(cuda_visible_list.index(gpu_id))
47
+ except IndexError:
48
+ raise RuntimeError(
49
+ "CUDA_VISIBLE_DEVICES set incorrectly. "
50
+ f"Got {cuda_visible_str}, expected to include {gpu_id}. "
51
+ "Did you override the `CUDA_VISIBLE_DEVICES` environment"
52
+ " variable? If not, please help file an issue on Github."
53
+ )
54
+
55
+ else:
56
+ # If called on the driver or outside of Ray Train, return the
57
+ # 0th device.
58
+ device_ids.append(0)
59
+
60
+ return [torch.device(f"cuda:{device_id}") for device_id in device_ids]
61
+
62
+ def set_device(self, device: Union[torch.device, int, str, None]):
63
+ torch.cuda.set_device(device)
64
+
65
+ def supports_stream(self) -> bool:
66
+ """Validate if the device type support create a stream"""
67
+ return True
68
+
69
+ def create_stream(self, device: torch.device) -> torch.cuda.Stream:
70
+ """Create a stream on cuda device"""
71
+ return torch.cuda.Stream(device)
72
+
73
+ def get_stream_context(self, stream):
74
+ """Get a stream context for cuda device"""
75
+ return torch.cuda.stream(stream)
76
+
77
+ def get_current_stream(self) -> torch.cuda.Stream:
78
+ """Get current stream for cuda device"""
79
+ return torch.cuda.current_stream()
lib/python3.12/site-packages/ray/air/_internal/device_manager/torch_device_manager.py ADDED
@@ -0,0 +1,40 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ from abc import ABC
2
+ from typing import List, Union
3
+
4
+ import torch
5
+
6
+
7
+ class TorchDeviceManager(ABC):
8
+ """This class contains the function needed for supporting
9
+ an acclerator family in Ray AI Library.
10
+ """
11
+
12
+ def is_available(self) -> bool:
13
+ """Validate if device is available."""
14
+ ...
15
+
16
+ def get_devices(self) -> List[torch.device]:
17
+ """Gets the correct torch device configured for this process"""
18
+ ...
19
+
20
+ def set_device(self, device: Union[torch.device, int, str, None]):
21
+ """Set the correct device for this process"""
22
+ ...
23
+
24
+ def supports_stream(self) -> bool:
25
+ """Validate if the device type support create a stream"""
26
+ ...
27
+
28
+ def create_stream(self, device: torch.device):
29
+ """Create a device stream"""
30
+ ...
31
+
32
+ def get_stream_context(self, stream):
33
+ """Get a stream context of device. If device didn't support stream,
34
+ this should return a empty context manager instead of None.
35
+ """
36
+ ...
37
+
38
+ def get_current_stream(self):
39
+ """Get current stream on accelerators like torch.cuda.current_stream"""
40
+ ...
lib/python3.12/site-packages/ray/air/_internal/filelock.py ADDED
@@ -0,0 +1,46 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import hashlib
2
+ import os
3
+ from pathlib import Path
4
+
5
+ from filelock import FileLock
6
+
7
+ import ray
8
+
9
+ RAY_LOCKFILE_DIR = "_ray_lockfiles"
10
+
11
+
12
+ class TempFileLock:
13
+ """FileLock wrapper that uses temporary file locks.
14
+
15
+ The temporary directory that these locks are saved to can be configured via
16
+ the `RAY_TMPDIR` environment variable.
17
+
18
+ Args:
19
+ path: The file path that this temporary file lock is used for.
20
+ This will be used to generate the lockfile filename.
21
+ Ex: For concurrent writes to a file, this is the common filepath
22
+ that multiple processes are writing to.
23
+ **kwargs: Additional keyword arguments to pass to the underlying `FileLock`.
24
+ """
25
+
26
+ def __init__(self, path: str, **kwargs):
27
+ self.path = path
28
+ temp_dir = Path(ray._common.utils.get_user_temp_dir()).resolve()
29
+ self._lock_dir = temp_dir / RAY_LOCKFILE_DIR
30
+ self._path_hash = hashlib.sha1(
31
+ str(Path(self.path).resolve()).encode("utf-8")
32
+ ).hexdigest()
33
+ self._lock_path = self._lock_dir / f"{self._path_hash}.lock"
34
+
35
+ os.makedirs(str(self._lock_dir), exist_ok=True)
36
+ self._lock = FileLock(self._lock_path, **kwargs)
37
+
38
+ def __enter__(self):
39
+ self._lock.acquire()
40
+ return self
41
+
42
+ def __exit__(self, type, value, traceback):
43
+ self._lock.release()
44
+
45
+ def __getattr__(self, name):
46
+ return getattr(self._lock, name)
lib/python3.12/site-packages/ray/air/_internal/json.py ADDED
@@ -0,0 +1,31 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import json
2
+ import numbers
3
+
4
+ import numpy as np
5
+
6
+
7
+ class SafeFallbackEncoder(json.JSONEncoder):
8
+ def __init__(self, nan_str="null", **kwargs):
9
+ super(SafeFallbackEncoder, self).__init__(**kwargs)
10
+ self.nan_str = nan_str
11
+
12
+ def default(self, value):
13
+ try:
14
+ if type(value).__module__ == np.__name__ and isinstance(value, np.ndarray):
15
+ return value.tolist()
16
+
17
+ if isinstance(value, np.bool_):
18
+ return bool(value)
19
+
20
+ if np.isnan(value):
21
+ return self.nan_str
22
+
23
+ if issubclass(type(value), numbers.Integral):
24
+ return int(value)
25
+ if issubclass(type(value), numbers.Number):
26
+ return float(value)
27
+
28
+ return super(SafeFallbackEncoder, self).default(value)
29
+
30
+ except Exception:
31
+ return str(value) # give up, just stringify it (ok for logs)
lib/python3.12/site-packages/ray/air/_internal/mlflow.py ADDED
@@ -0,0 +1,342 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import logging
2
+ import os
3
+ from copy import deepcopy
4
+ from typing import TYPE_CHECKING, Dict, Optional
5
+
6
+ from packaging import version
7
+
8
+ from ray._private.dict import flatten_dict
9
+
10
+ if TYPE_CHECKING:
11
+ from mlflow.entities import Run
12
+ from mlflow.tracking import MlflowClient
13
+
14
+ logger = logging.getLogger(__name__)
15
+
16
+
17
+ class _MLflowLoggerUtil:
18
+ """Util class for setting up and logging to MLflow.
19
+
20
+ Use this util for any library that needs MLflow logging/tracking logic
21
+ such as Ray Tune or Ray Train.
22
+ """
23
+
24
+ def __init__(self):
25
+ import mlflow
26
+
27
+ self._mlflow = mlflow
28
+ self.experiment_id = None
29
+
30
+ def __deepcopy__(self, memo=None):
31
+ # mlflow is a module, and thus cannot be copied
32
+ _mlflow = self._mlflow
33
+ self.__dict__.pop("_mlflow")
34
+ dict_copy = deepcopy(self.__dict__, memo)
35
+ copied_object = _MLflowLoggerUtil()
36
+ copied_object.__dict__.update(dict_copy)
37
+ self._mlflow = _mlflow
38
+ copied_object._mlflow = _mlflow
39
+ return copied_object
40
+
41
+ def setup_mlflow(
42
+ self,
43
+ tracking_uri: Optional[str] = None,
44
+ registry_uri: Optional[str] = None,
45
+ experiment_id: Optional[str] = None,
46
+ experiment_name: Optional[str] = None,
47
+ tracking_token: Optional[str] = None,
48
+ artifact_location: Optional[str] = None,
49
+ create_experiment_if_not_exists: bool = True,
50
+ ):
51
+ """
52
+ Sets up MLflow.
53
+
54
+ Sets the Mlflow tracking uri & token, and registry URI. Also sets
55
+ the MLflow experiment that the logger should use, and possibly
56
+ creates new experiment if it does not exist.
57
+
58
+ Args:
59
+ tracking_uri: The tracking URI for the MLflow tracking
60
+ server.
61
+ registry_uri: The registry URI for the MLflow model registry.
62
+ experiment_id: The id of an already existing MLflow
63
+ experiment to use for logging. If None is passed in
64
+ here and the MFLOW_EXPERIMENT_ID is not set, or the
65
+ experiment with this id does not exist,
66
+ ``experiment_name`` will be used instead. This argument takes
67
+ precedence over ``experiment_name`` if both are passed in.
68
+ experiment_name: The experiment name to use for logging.
69
+ If None is passed in here, the MLFLOW_EXPERIMENT_NAME environment
70
+ variable is used to determine the experiment name.
71
+ If the experiment with the name already exists with MLflow,
72
+ it will be reused. If not, a new experiment will be created
73
+ with the provided name if
74
+ ``create_experiment_if_not_exists`` is set to True.
75
+ artifact_location: The location to store run artifacts.
76
+ If not provided, MLFlow picks an appropriate default.
77
+ Ignored if experiment already exists.
78
+ tracking_token: Tracking token used to authenticate with MLflow.
79
+ create_experiment_if_not_exists: Whether to create an
80
+ experiment with the provided name if it does not already
81
+ exist. Defaults to True.
82
+
83
+ Returns:
84
+ Whether setup is successful.
85
+ """
86
+ if tracking_token:
87
+ os.environ["MLFLOW_TRACKING_TOKEN"] = tracking_token
88
+
89
+ self._mlflow.set_tracking_uri(tracking_uri)
90
+ self._mlflow.set_registry_uri(registry_uri)
91
+
92
+ # First check experiment_id.
93
+ experiment_id = (
94
+ experiment_id
95
+ if experiment_id is not None
96
+ else os.environ.get("MLFLOW_EXPERIMENT_ID")
97
+ )
98
+ if experiment_id is not None:
99
+ from mlflow.exceptions import MlflowException
100
+
101
+ try:
102
+ self._mlflow.get_experiment(experiment_id=experiment_id)
103
+ logger.debug(
104
+ f"Experiment with provided id {experiment_id} "
105
+ "exists. Setting that as the experiment."
106
+ )
107
+ self.experiment_id = experiment_id
108
+ return
109
+ except MlflowException:
110
+ pass
111
+
112
+ # Then check experiment_name.
113
+ experiment_name = (
114
+ experiment_name
115
+ if experiment_name is not None
116
+ else os.environ.get("MLFLOW_EXPERIMENT_NAME")
117
+ )
118
+ if experiment_name is not None and self._mlflow.get_experiment_by_name(
119
+ name=experiment_name
120
+ ):
121
+ logger.debug(
122
+ f"Experiment with provided name {experiment_name} "
123
+ "exists. Setting that as the experiment."
124
+ )
125
+ self.experiment_id = self._mlflow.get_experiment_by_name(
126
+ experiment_name
127
+ ).experiment_id
128
+ return
129
+
130
+ # An experiment with the provided id or name does not exist.
131
+ # Create a new experiment if applicable.
132
+ if experiment_name and create_experiment_if_not_exists:
133
+ logger.debug(
134
+ "Existing experiment not found. Creating new "
135
+ f"experiment with name: {experiment_name}"
136
+ )
137
+ self.experiment_id = self._mlflow.create_experiment(
138
+ name=experiment_name, artifact_location=artifact_location
139
+ )
140
+ return
141
+
142
+ if create_experiment_if_not_exists:
143
+ raise ValueError(
144
+ f"Experiment with the provided experiment_id: "
145
+ f"{experiment_id} does not exist and no "
146
+ f"experiment_name provided. At least one of "
147
+ f"these has to be provided."
148
+ )
149
+ else:
150
+ raise ValueError(
151
+ f"Experiment with the provided experiment_id: "
152
+ f"{experiment_id} or experiment_name: "
153
+ f"{experiment_name} does not exist. Please "
154
+ f"create an MLflow experiment and provide "
155
+ f"either its id or name."
156
+ )
157
+
158
+ def _parse_dict(self, dict_to_log: Dict) -> Dict:
159
+ """Parses provided dict to convert all values to float.
160
+
161
+ MLflow can only log metrics that are floats. This does not apply to
162
+ logging parameters or artifacts.
163
+
164
+ Args:
165
+ dict_to_log: The dictionary containing the metrics to log.
166
+
167
+ Returns:
168
+ A dictionary containing the metrics to log with all values being
169
+ converted to floats, or skipped if not able to be converted.
170
+ """
171
+ new_dict = {}
172
+ for key, value in dict_to_log.items():
173
+ try:
174
+ value = float(value)
175
+ new_dict[key] = value
176
+ except (ValueError, TypeError):
177
+ logger.debug(
178
+ "Cannot log key {} with value {} since the "
179
+ "value cannot be converted to float.".format(key, value)
180
+ )
181
+ continue
182
+
183
+ return new_dict
184
+
185
+ def start_run(
186
+ self,
187
+ run_name: Optional[str] = None,
188
+ tags: Optional[Dict] = None,
189
+ set_active: bool = False,
190
+ ) -> "Run":
191
+ """Starts a new run and possibly sets it as the active run.
192
+
193
+ Args:
194
+ tags: Tags to set for the new run.
195
+ set_active: Whether to set the new run as the active run.
196
+ If an active run already exists, then that run is returned.
197
+
198
+ Returns:
199
+ The newly created MLflow run.
200
+ """
201
+ import mlflow
202
+ from mlflow.utils.mlflow_tags import MLFLOW_RUN_NAME
203
+
204
+ if tags is None:
205
+ tags = {}
206
+
207
+ if set_active:
208
+ return self._start_active_run(run_name=run_name, tags=tags)
209
+
210
+ client = self._get_client()
211
+ # If `mlflow==1.30.0` and we don't use `run_name`, then MLflow might error. For
212
+ # more information, see #29749.
213
+ if version.parse(mlflow.__version__) >= version.parse("1.30.0"):
214
+ run = client.create_run(
215
+ run_name=run_name, experiment_id=self.experiment_id, tags=tags
216
+ )
217
+ else:
218
+ tags[MLFLOW_RUN_NAME] = run_name
219
+ run = client.create_run(experiment_id=self.experiment_id, tags=tags)
220
+
221
+ return run
222
+
223
+ def _start_active_run(
224
+ self, run_name: Optional[str] = None, tags: Optional[Dict] = None
225
+ ) -> "Run":
226
+ """Starts a run and sets it as the active run if one does not exist.
227
+
228
+ If an active run already exists, then returns it.
229
+ """
230
+ active_run = self._mlflow.active_run()
231
+ if active_run:
232
+ return active_run
233
+
234
+ return self._mlflow.start_run(
235
+ run_name=run_name, experiment_id=self.experiment_id, tags=tags
236
+ )
237
+
238
+ def _run_exists(self, run_id: str) -> bool:
239
+ """Check if run with the provided id exists."""
240
+ from mlflow.exceptions import MlflowException
241
+
242
+ try:
243
+ self._mlflow.get_run(run_id=run_id)
244
+ return True
245
+ except MlflowException:
246
+ return False
247
+
248
+ def _get_client(self) -> "MlflowClient":
249
+ """Returns an ml.tracking.MlflowClient instance to use for logging."""
250
+ tracking_uri = self._mlflow.get_tracking_uri()
251
+ registry_uri = self._mlflow.get_registry_uri()
252
+
253
+ from mlflow.tracking import MlflowClient
254
+
255
+ return MlflowClient(tracking_uri=tracking_uri, registry_uri=registry_uri)
256
+
257
+ def log_params(self, params_to_log: Dict, run_id: Optional[str] = None):
258
+ """Logs the provided parameters to the run specified by run_id.
259
+
260
+ If no ``run_id`` is passed in, then logs to the current active run.
261
+ If there is not active run, then creates a new run and sets it as
262
+ the active run.
263
+
264
+ Args:
265
+ params_to_log: Dictionary of parameters to log.
266
+ run_id (Optional[str]): The ID of the run to log to.
267
+ """
268
+ params_to_log = flatten_dict(params_to_log)
269
+
270
+ if run_id and self._run_exists(run_id):
271
+ client = self._get_client()
272
+ for key, value in params_to_log.items():
273
+ client.log_param(run_id=run_id, key=key, value=value)
274
+
275
+ else:
276
+ for key, value in params_to_log.items():
277
+ self._mlflow.log_param(key=key, value=value)
278
+
279
+ def log_metrics(self, step, metrics_to_log: Dict, run_id: Optional[str] = None):
280
+ """Logs the provided metrics to the run specified by run_id.
281
+
282
+
283
+ If no ``run_id`` is passed in, then logs to the current active run.
284
+ If there is not active run, then creates a new run and sets it as
285
+ the active run.
286
+
287
+ Args:
288
+ metrics_to_log: Dictionary of metrics to log.
289
+ run_id (Optional[str]): The ID of the run to log to.
290
+ """
291
+ metrics_to_log = flatten_dict(metrics_to_log)
292
+ metrics_to_log = self._parse_dict(metrics_to_log)
293
+
294
+ if run_id and self._run_exists(run_id):
295
+ client = self._get_client()
296
+ for key, value in metrics_to_log.items():
297
+ client.log_metric(run_id=run_id, key=key, value=value, step=step)
298
+
299
+ else:
300
+ for key, value in metrics_to_log.items():
301
+ self._mlflow.log_metric(key=key, value=value, step=step)
302
+
303
+ def save_artifacts(self, dir: str, run_id: Optional[str] = None):
304
+ """Saves directory as artifact to the run specified by run_id.
305
+
306
+ If no ``run_id`` is passed in, then saves to the current active run.
307
+ If there is not active run, then creates a new run and sets it as
308
+ the active run.
309
+
310
+ Args:
311
+ dir: Path to directory containing the files to save.
312
+ run_id (Optional[str]): The ID of the run to log to.
313
+ """
314
+ if run_id and self._run_exists(run_id):
315
+ client = self._get_client()
316
+ client.log_artifacts(run_id=run_id, local_dir=dir)
317
+ else:
318
+ self._mlflow.log_artifacts(local_dir=dir)
319
+
320
+ def end_run(self, status: Optional[str] = None, run_id=None):
321
+ """Terminates the run specified by run_id.
322
+
323
+ If no ``run_id`` is passed in, then terminates the
324
+ active run if one exists.
325
+
326
+ Args:
327
+ status (Optional[str]): The status to set when terminating the run.
328
+ run_id (Optional[str]): The ID of the run to terminate.
329
+
330
+ """
331
+ if (
332
+ run_id
333
+ and self._run_exists(run_id)
334
+ and not (
335
+ self._mlflow.active_run()
336
+ and self._mlflow.active_run().info.run_id == run_id
337
+ )
338
+ ):
339
+ client = self._get_client()
340
+ client.set_terminated(run_id=run_id, status=status)
341
+ else:
342
+ self._mlflow.end_run(status=status)
lib/python3.12/site-packages/ray/air/_internal/tensorflow_utils.py ADDED
@@ -0,0 +1,137 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ from typing import TYPE_CHECKING, Dict, List, Optional, Tuple, Union
2
+
3
+ import numpy as np
4
+ import pyarrow
5
+ import tensorflow as tf
6
+
7
+ from ray.air.util.data_batch_conversion import _unwrap_ndarray_object_type_if_needed
8
+ from ray.air.util.tensor_extensions.arrow import get_arrow_extension_tensor_types
9
+
10
+ if TYPE_CHECKING:
11
+ from ray.data._internal.pandas_block import PandasBlockSchema
12
+
13
+
14
+ def convert_ndarray_to_tf_tensor(
15
+ ndarray: np.ndarray,
16
+ dtype: Optional[tf.dtypes.DType] = None,
17
+ type_spec: Optional[tf.TypeSpec] = None,
18
+ ) -> tf.Tensor:
19
+ """Convert a NumPy ndarray to a TensorFlow Tensor.
20
+
21
+ Args:
22
+ ndarray: A NumPy ndarray that we wish to convert to a TensorFlow Tensor.
23
+ dtype: A TensorFlow dtype for the created tensor; if None, the dtype will be
24
+ inferred from the NumPy ndarray data.
25
+ type_spec: A type spec that specifies the shape and dtype of the returned
26
+ tensor. If you specify ``dtype``, the dtype stored in the type spec is
27
+ ignored.
28
+
29
+ Returns: A TensorFlow Tensor.
30
+ """
31
+ if dtype is None and type_spec is not None:
32
+ dtype = type_spec.dtype
33
+
34
+ is_ragged = isinstance(type_spec, tf.RaggedTensorSpec)
35
+ ndarray = _unwrap_ndarray_object_type_if_needed(ndarray)
36
+ if is_ragged:
37
+ return tf.ragged.constant(ndarray, dtype=dtype)
38
+ else:
39
+ return tf.convert_to_tensor(ndarray, dtype=dtype)
40
+
41
+
42
+ def convert_ndarray_batch_to_tf_tensor_batch(
43
+ ndarrays: Union[np.ndarray, Dict[str, np.ndarray]],
44
+ dtypes: Optional[Union[tf.dtypes.DType, Dict[str, tf.dtypes.DType]]] = None,
45
+ ) -> Union[tf.Tensor, Dict[str, tf.Tensor]]:
46
+ """Convert a NumPy ndarray batch to a TensorFlow Tensor batch.
47
+
48
+ Args:
49
+ ndarray: A (dict of) NumPy ndarray(s) that we wish to convert to a TensorFlow
50
+ Tensor.
51
+ dtype: A (dict of) TensorFlow dtype(s) for the created tensor; if None, the
52
+ dtype will be inferred from the NumPy ndarray data.
53
+
54
+ Returns: A (dict of) TensorFlow Tensor(s).
55
+ """
56
+ if isinstance(ndarrays, np.ndarray):
57
+ # Single-tensor case.
58
+ if isinstance(dtypes, dict):
59
+ if len(dtypes) != 1:
60
+ raise ValueError(
61
+ "When constructing a single-tensor batch, only a single dtype "
62
+ f"should be given, instead got: {dtypes}"
63
+ )
64
+ dtypes = next(iter(dtypes.values()))
65
+ batch = convert_ndarray_to_tf_tensor(ndarrays, dtypes)
66
+ else:
67
+ # Multi-tensor case.
68
+ batch = {
69
+ col_name: convert_ndarray_to_tf_tensor(
70
+ col_ndarray,
71
+ dtype=dtypes[col_name] if isinstance(dtypes, dict) else dtypes,
72
+ )
73
+ for col_name, col_ndarray in ndarrays.items()
74
+ }
75
+
76
+ return batch
77
+
78
+
79
+ def get_type_spec(
80
+ schema: Union["pyarrow.lib.Schema", "PandasBlockSchema"],
81
+ columns: Union[str, List[str]],
82
+ ) -> Union[tf.TypeSpec, Dict[str, tf.TypeSpec]]:
83
+ import pyarrow as pa
84
+
85
+ from ray.data.extensions import TensorDtype
86
+
87
+ tensor_extension_types = get_arrow_extension_tensor_types()
88
+
89
+ assert not isinstance(schema, type)
90
+
91
+ dtypes: Dict[str, Union[np.dtype, pa.DataType]] = dict(
92
+ zip(schema.names, schema.types)
93
+ )
94
+
95
+ def get_dtype(dtype: Union[np.dtype, pa.DataType]) -> tf.dtypes.DType:
96
+ if isinstance(dtype, pa.ListType):
97
+ dtype = dtype.value_type
98
+ if isinstance(dtype, pa.DataType):
99
+ dtype = dtype.to_pandas_dtype()
100
+ if isinstance(dtype, TensorDtype):
101
+ dtype = dtype.element_dtype
102
+ res = tf.dtypes.as_dtype(dtype)
103
+ return res
104
+
105
+ def get_shape(dtype: Union[np.dtype, pa.DataType]) -> Tuple[int, ...]:
106
+ shape = (None,)
107
+ if isinstance(dtype, tensor_extension_types):
108
+ dtype = dtype.to_pandas_dtype()
109
+ if isinstance(dtype, pa.ListType):
110
+ shape += (None,)
111
+ elif isinstance(dtype, TensorDtype):
112
+ shape += dtype.element_shape
113
+ return shape
114
+
115
+ def get_tensor_spec(
116
+ dtype: Union[np.dtype, pa.DataType], *, name: str
117
+ ) -> tf.TypeSpec:
118
+
119
+ shape, dtype = get_shape(dtype), get_dtype(dtype)
120
+ # Batch dimension is always `None`. So, if there's more than one `None`-valued
121
+ # dimension, then the tensor is ragged.
122
+ is_ragged = sum(dim is None for dim in shape) > 1
123
+ if is_ragged:
124
+ type_spec = tf.RaggedTensorSpec(shape, dtype=dtype)
125
+ else:
126
+ type_spec = tf.TensorSpec(shape, dtype=dtype, name=name)
127
+ return type_spec
128
+
129
+ if isinstance(columns, str):
130
+ name, dtype = columns, dtypes[columns]
131
+ return get_tensor_spec(dtype, name=name)
132
+
133
+ return {
134
+ name: get_tensor_spec(dtype, name=name)
135
+ for name, dtype in dtypes.items()
136
+ if name in columns
137
+ }
lib/python3.12/site-packages/ray/air/_internal/torch_utils.py ADDED
@@ -0,0 +1,548 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import warnings
2
+ from typing import Any, Dict, List, Optional, Sequence, Union
3
+
4
+ import numpy as np
5
+ import pandas as pd
6
+ import pyarrow
7
+ import torch
8
+
9
+ from ray._private.ray_constants import env_bool
10
+ from ray.air._internal.device_manager import get_torch_device_manager_by_context
11
+ from ray.air.util.data_batch_conversion import _unwrap_ndarray_object_type_if_needed
12
+ from ray.data.collate_fn import (
13
+ TensorBatchReturnType,
14
+ TensorBatchType,
15
+ _is_nested_tensor_sequence,
16
+ _is_tensor,
17
+ _is_tensor_mapping,
18
+ _is_tensor_sequence,
19
+ _is_tensor_sequence_mapping,
20
+ )
21
+
22
+ # Default non-blocking transfer for tensors.
23
+ DEFAULT_TENSOR_NON_BLOCKING_TRANSFER = env_bool(
24
+ "RAY_AIR_DEFAULT_TENSOR_NON_BLOCKING_TRANSFER",
25
+ True,
26
+ )
27
+
28
+
29
+ def get_devices() -> List[torch.device]:
30
+ """Gets the correct torch device list configured for this process.
31
+
32
+ Returns a list of torch accelerator (GPU, HPU, NPU...) devices allocated for
33
+ the current worker.
34
+ If no accelerators are assigned, then it returns a list with a single CPU device.
35
+ """
36
+ return get_torch_device_manager_by_context().get_devices()
37
+
38
+
39
+ def convert_pandas_to_torch_tensor(
40
+ data_batch: pd.DataFrame,
41
+ columns: Optional[Union[List[str], List[List[str]]]] = None,
42
+ column_dtypes: Optional[Union[torch.dtype, List[torch.dtype]]] = None,
43
+ unsqueeze: bool = True,
44
+ ) -> Union[torch.Tensor, List[torch.Tensor]]:
45
+ """Converts a Pandas dataframe to a torch Tensor or list of torch Tensors.
46
+
47
+ The format of the return type will match the format of ``columns``. If a
48
+ list of columns is provided, the return type will be a single tensor. If
49
+ ``columns`` is a list of lists, then the return type will be a list of
50
+ tensors.
51
+
52
+ Args:
53
+ data_batch: The pandas dataframe to convert to a
54
+ torch tensor.
55
+ columns:
56
+ The names of the columns in the dataframe to include in the
57
+ torch tensor. If this arg is a List[List[str]], then the return
58
+ type will be a List of tensors. This is useful for multi-input
59
+ models. If None, then use all columns in the ``data_batch``.
60
+ column_dtype: The
61
+ torch dtype to use for the tensor. If set to None,
62
+ then automatically infer the dtype.
63
+ unsqueeze: If set to True, the tensors
64
+ will be unsqueezed (reshaped to (N, 1)) before being concatenated into
65
+ the final tensor. Otherwise, they will be left as is, that is
66
+ (N, ). Defaults to True.
67
+
68
+ Returns:
69
+ Either a torch tensor of size (N, len(columns)) where N is the
70
+ number of rows in the ``data_batch`` Dataframe, or a list of
71
+ tensors, where the size of item i is (N, len(columns[i])).
72
+
73
+ """
74
+
75
+ multi_input = columns and (isinstance(columns[0], (list, tuple)))
76
+
77
+ if not multi_input and column_dtypes and not isinstance(column_dtypes, torch.dtype):
78
+ raise TypeError(
79
+ "If `columns` is a list of strings, "
80
+ "`column_dtypes` must be None or a single `torch.dtype`."
81
+ f"Got {type(column_dtypes)} instead."
82
+ )
83
+
84
+ columns = columns if columns else []
85
+
86
+ def tensorize(vals, dtype):
87
+ """This recursive function allows to convert pyarrow List dtypes
88
+ to multi-dimensional tensors."""
89
+ if isinstance(vals, pd.api.extensions.ExtensionArray):
90
+ # torch.as_tensor() does not yet support the __array__ protocol, so we need
91
+ # to convert extension arrays to ndarrays manually before converting to a
92
+ # Torch tensor.
93
+ # See https://github.com/pytorch/pytorch/issues/51156.
94
+ vals = vals.to_numpy()
95
+
96
+ if vals.dtype.type is np.object_:
97
+ # Column has an object dtype which Torch can't handle, so we try to
98
+ # tensorize each column element and then stack the resulting tensors.
99
+ tensors = [tensorize(x, dtype) for x in vals]
100
+ try:
101
+ return torch.stack(tensors)
102
+ except RuntimeError:
103
+ # NOTE: RuntimeError is raised when trying to stack ragged tensors.
104
+ # Try to coerce the tensor to a nested tensor, if possible.
105
+ # If this fails, the exception will be propagated up to the caller.
106
+ return torch.nested_tensor(tensors)
107
+ else:
108
+ return torch.as_tensor(vals, dtype=dtype)
109
+
110
+ def get_tensor_for_columns(columns, dtype):
111
+ feature_tensors = []
112
+
113
+ if columns:
114
+ batch = data_batch[columns]
115
+ else:
116
+ batch = data_batch
117
+
118
+ for col in batch.columns:
119
+ col_vals = batch[col].values
120
+ try:
121
+ t = tensorize(col_vals, dtype=dtype)
122
+ except Exception as e:
123
+ raise ValueError(
124
+ f"Failed to convert column {col} to a Torch Tensor of dtype "
125
+ f"{dtype}. See above exception chain for the exact failure."
126
+ ) from e
127
+ if unsqueeze:
128
+ t = t.unsqueeze(1)
129
+ feature_tensors.append(t)
130
+
131
+ if len(feature_tensors) > 1:
132
+ feature_tensor = torch.cat(feature_tensors, dim=1)
133
+ else:
134
+ feature_tensor = feature_tensors[0]
135
+ return feature_tensor
136
+
137
+ if multi_input:
138
+ if type(column_dtypes) not in [list, tuple]:
139
+ column_dtypes = [column_dtypes] * len(columns)
140
+ return [
141
+ get_tensor_for_columns(columns=subcolumns, dtype=dtype)
142
+ for subcolumns, dtype in zip(columns, column_dtypes)
143
+ ]
144
+ else:
145
+ return get_tensor_for_columns(columns=columns, dtype=column_dtypes)
146
+
147
+
148
+ def convert_ndarray_to_torch_tensor(
149
+ ndarray: np.ndarray,
150
+ dtype: Optional[torch.dtype] = None,
151
+ device: Optional[Union[str, "torch.device"]] = None,
152
+ pin_memory: bool = False,
153
+ ) -> torch.Tensor:
154
+ """Convert a NumPy ndarray to a Torch Tensor.
155
+
156
+ Args:
157
+ ndarray: A NumPy ndarray that we wish to convert to a Torch Tensor.
158
+ dtype: A Torch dtype for the created tensor; if None, the dtype will be
159
+ inferred from the NumPy ndarray data.
160
+ device: The device on which the tensor(s) should be placed; if None, the Torch
161
+ tensor(s) will be constructed on the CPU.
162
+ pin_memory: Whether to pin the memory of the created tensors.
163
+
164
+ Returns: A Torch Tensor.
165
+ """
166
+ ndarray = _unwrap_ndarray_object_type_if_needed(ndarray)
167
+
168
+ # Object dtype cannot be converted into PyTorch Tensor.
169
+ if ndarray.dtype.type is np.object_:
170
+ raise RuntimeError(
171
+ "Numpy array of object dtype cannot be converted to a Torch Tensor. This "
172
+ "may because the numpy array is a ragged tensor--it contains items of "
173
+ "different sizes. If using `iter_torch_batches()` API, you can pass in a "
174
+ "`collate_fn` argument to specify custom logic to convert the Numpy array "
175
+ "batch to a Torch tensor batch."
176
+ )
177
+
178
+ # The numpy array is not always writeable as it can come from the Ray object store.
179
+ # Numpy will throw a verbose warning here, which we suppress, as we don't write
180
+ # to the tensors. We also don't want to copy the array to avoid memory overhead.
181
+ # Original warning: https://github.com/pytorch/pytorch/blob/v1.13.0/
182
+ # torch/csrc/utils/tensor_numpy.cpp#L198-L206
183
+ with warnings.catch_warnings():
184
+ warnings.simplefilter("ignore")
185
+ result = torch.as_tensor(ndarray, dtype=dtype, device=device)
186
+
187
+ if pin_memory:
188
+ assert result.device.type == "cpu", (
189
+ "Pin memory is only supported for CPU tensors. "
190
+ f"Got device: {result.device} and pin_memory: {pin_memory}."
191
+ )
192
+ result = result.pin_memory()
193
+
194
+ return result
195
+
196
+
197
+ def convert_ndarray_batch_to_torch_tensor_batch(
198
+ ndarrays: Union[np.ndarray, Dict[str, np.ndarray]],
199
+ dtypes: Optional[Union[torch.dtype, Dict[str, torch.dtype]]] = None,
200
+ device: Optional[Union[str, "torch.device"]] = None,
201
+ pin_memory: bool = False,
202
+ ) -> Union[torch.Tensor, Dict[str, torch.Tensor]]:
203
+ """Convert a NumPy ndarray batch to a Torch Tensor batch.
204
+
205
+ Args:
206
+ ndarray: A (dict of) NumPy ndarray(s) that we wish to convert to a Torch Tensor.
207
+ dtype: A (dict of) Torch dtype(s) for the created tensor; if None, the dtype
208
+ will be inferred from the NumPy ndarray data.
209
+ device: The device on which the tensor(s) should be placed; if None, the Torch
210
+ tensor(s) will be constructed on the CPU.
211
+ pin_memory: Whether to pin the memory of the created tensors.
212
+
213
+ Returns: A (dict of) Torch Tensor(s).
214
+ """
215
+ if isinstance(ndarrays, np.ndarray):
216
+ # Single-tensor case.
217
+ if isinstance(dtypes, dict):
218
+ if len(dtypes) != 1:
219
+ raise ValueError(
220
+ "When constructing a single-tensor batch, only a single dtype "
221
+ f"should be given, instead got: {dtypes}"
222
+ )
223
+ dtypes = next(iter(dtypes.values()))
224
+ batch = convert_ndarray_to_torch_tensor(
225
+ ndarrays,
226
+ dtype=dtypes,
227
+ device=device,
228
+ pin_memory=pin_memory,
229
+ )
230
+ else:
231
+ # Multi-tensor case.
232
+ batch = {
233
+ col_name: convert_ndarray_to_torch_tensor(
234
+ col_ndarray,
235
+ dtype=dtypes[col_name] if isinstance(dtypes, dict) else dtypes,
236
+ device=device,
237
+ pin_memory=pin_memory,
238
+ )
239
+ for col_name, col_ndarray in ndarrays.items()
240
+ }
241
+
242
+ return batch
243
+
244
+
245
+ def load_torch_model(
246
+ saved_model: Union[torch.nn.Module, Dict],
247
+ model_definition: Optional[torch.nn.Module] = None,
248
+ ) -> torch.nn.Module:
249
+ """Loads a PyTorch model from the provided ``saved_model``.
250
+
251
+ ``model_definition`` is only used when ``saved_model`` is
252
+ a torch state dict, which will be loaded into ``model_definition``.
253
+ Otherwise, ``model_definition`` is discarded.
254
+ """
255
+ if isinstance(saved_model, torch.nn.Module):
256
+ return saved_model
257
+ elif isinstance(saved_model, dict):
258
+ if not model_definition:
259
+ raise ValueError(
260
+ "Attempting to load torch model from a "
261
+ "state_dict, but no `model_definition` was "
262
+ "provided."
263
+ )
264
+ model_definition.load_state_dict(saved_model)
265
+ return model_definition
266
+ else:
267
+ raise ValueError(
268
+ f"Saved model is of type {type(saved_model)}. "
269
+ f"The model saved in the checkpoint is expected "
270
+ f"to be of type `torch.nn.Module`, or a model "
271
+ f"state dict of type dict."
272
+ )
273
+
274
+
275
+ def contains_tensor(obj):
276
+ if isinstance(obj, torch.Tensor):
277
+ return True
278
+ elif isinstance(obj, dict):
279
+ for k, v in obj.items():
280
+ if contains_tensor(k):
281
+ return True
282
+ if contains_tensor(v):
283
+ return True
284
+ elif isinstance(obj, (list, tuple)):
285
+ for v in obj:
286
+ if contains_tensor(v):
287
+ return True
288
+ return False
289
+
290
+
291
+ # Not present in torch<=1.7.0
292
+ # Adapted from https://github.com/pytorch/pytorch/blob/\
293
+ # c18da597e0bb1c1aecc97c77a73fed1849057fa4/torch/nn/modules/utils.py
294
+ def consume_prefix_in_state_dict_if_present_not_in_place(
295
+ state_dict: Dict[str, Any], prefix: str
296
+ ) -> Dict[str, Any]:
297
+ """Strip the prefix in state_dict, if any and return a new dict.
298
+
299
+ Adapted from https://github.com/pytorch/pytorch/blob/\
300
+ c18da597e0bb1c1aecc97c77a73fed1849057fa4/torch/nn/modules/utils.py
301
+ The original method modified the dict in-place.
302
+
303
+ Args:
304
+ state_dict: a state-dict to be loaded to the model.
305
+ prefix: prefix.
306
+
307
+ """
308
+ copied = False
309
+
310
+ for key in state_dict:
311
+ if key.startswith(prefix):
312
+ newkey = key[len(prefix) :]
313
+ if not copied:
314
+ # We are doing shallow copies here, so the performance
315
+ # impact should be negligible anyway, but this is
316
+ # a simple optimization.
317
+ state_dict = state_dict.copy()
318
+ copied = True
319
+ state_dict[newkey] = state_dict.pop(key)
320
+
321
+ if "_metadata" in state_dict:
322
+ state_dict["_metadata"] = state_dict["_metadata"].copy()
323
+ metadata = state_dict["_metadata"]
324
+ for key in metadata:
325
+ if len(key) == 0:
326
+ continue
327
+ newkey = key[len(prefix) :]
328
+ metadata[newkey] = metadata.pop(key)
329
+
330
+ return state_dict
331
+
332
+
333
+ def convert_ndarray_list_to_torch_tensor_list(
334
+ ndarrays: Dict[str, List[np.ndarray]],
335
+ dtypes: Optional[Union[torch.dtype, Dict[str, torch.dtype]]] = None,
336
+ device: Optional[Union[str, "torch.device"]] = None,
337
+ pin_memory: bool = False,
338
+ ) -> Dict[str, List[torch.Tensor]]:
339
+ """Convert a dict mapping column names to lists of ndarrays to Torch Tensors.
340
+
341
+ Args:
342
+ ndarrays: A dict mapping column names to lists of ndarrays that we wish to convert
343
+ to Torch Tensors.
344
+ dtypes: A (dict of) Torch dtype(s) for the created tensors; if None, the dtype
345
+ will be inferred from the NumPy ndarray data.
346
+ device: The device on which the tensor(s) should be placed; if None, the Torch
347
+ tensor(s) will be constructed on the CPU.
348
+ pin_memory: Whether to pin the memory of the created tensors.
349
+
350
+ Returns:
351
+ A dict mapping column names to lists of Tensors.
352
+ """
353
+ return {
354
+ col_name: [
355
+ convert_ndarray_batch_to_torch_tensor_batch(
356
+ ndarray,
357
+ dtypes=dtypes[col_name] if isinstance(dtypes, dict) else dtypes,
358
+ device=device,
359
+ pin_memory=pin_memory,
360
+ )
361
+ for ndarray in col_ndarrays
362
+ ]
363
+ for col_name, col_ndarrays in ndarrays.items()
364
+ }
365
+
366
+
367
+ def arrow_batch_to_tensors(
368
+ batch: pyarrow.Table,
369
+ dtypes: Optional[Union[torch.dtype, Dict[str, torch.dtype]]] = None,
370
+ combine_chunks: bool = False,
371
+ pin_memory: bool = False,
372
+ ) -> Dict[str, List[torch.Tensor]]:
373
+ """Convert PyArrow batch to PyTorch tensors.
374
+
375
+ Args:
376
+ batch: PyArrow batch to convert
377
+ dtypes: A (dict of) Torch dtype(s) for the created tensors; if None, the dtype
378
+ will be inferred from the NumPy ndarray data.
379
+ combine_chunks: If True, combine chunks in Arrow batch before converting to
380
+ tensors.
381
+ pin_memory: Whether to pin the memory of the created tensors.
382
+
383
+ Returns:
384
+ A dictionary of column name to list of tensors. For non-chunked columns,
385
+ the list will contain a single tensor.
386
+ """
387
+ from ray.data._internal.arrow_block import ArrowBlockAccessor
388
+ from ray.data._internal.arrow_ops import transform_pyarrow
389
+
390
+ if combine_chunks:
391
+ numpy_batch = ArrowBlockAccessor(batch).to_batch_format("numpy")
392
+ return {
393
+ col_name: convert_ndarray_batch_to_torch_tensor_batch(
394
+ col_array,
395
+ dtypes=dtypes[col_name] if isinstance(dtypes, dict) else dtypes,
396
+ pin_memory=pin_memory,
397
+ )
398
+ for col_name, col_array in numpy_batch.items()
399
+ }
400
+ else:
401
+ numpy_list = transform_pyarrow.table_to_numpy_dict_chunked(
402
+ batch,
403
+ )
404
+ return convert_ndarray_list_to_torch_tensor_list(
405
+ numpy_list,
406
+ dtypes=dtypes,
407
+ pin_memory=pin_memory,
408
+ )
409
+
410
+
411
+ @torch.no_grad()
412
+ def concat_tensors_to_device(
413
+ tensor_sequence: Sequence[torch.Tensor],
414
+ device: Optional[Union[str, "torch.device"]] = None,
415
+ non_blocking: bool = DEFAULT_TENSOR_NON_BLOCKING_TRANSFER,
416
+ ) -> torch.Tensor:
417
+ """Stack sequence of tensors into a contiguous GPU tensor.
418
+
419
+ Args:
420
+ tensor_sequence: Sequence of tensors to stack
421
+ device: The device to move tensors to
422
+ non_blocking: If True, perform device transfer without forcing a
423
+ synchronization.
424
+
425
+ Returns:
426
+ A contiguous tensor on the target device
427
+ """
428
+ # Assumes tensors have the same shape/dtype
429
+ assert (
430
+ tensor_sequence
431
+ ), f"Cannot stack empty sequence of tensors. Received: {tensor_sequence}"
432
+
433
+ assert all(
434
+ isinstance(t, torch.Tensor) for t in tensor_sequence
435
+ ), "All items must be torch.Tensor. Found invalid types: " + str(
436
+ [type(t) for t in tensor_sequence if not isinstance(t, torch.Tensor)]
437
+ )
438
+
439
+ first_dtype = tensor_sequence[0].dtype
440
+ assert all(t.dtype == first_dtype for t in tensor_sequence), (
441
+ "All tensors must have the same dtype. "
442
+ f"Expected: {first_dtype}, got: {[t.dtype for t in tensor_sequence]}"
443
+ )
444
+
445
+ first_shape = tensor_sequence[0].shape[1:]
446
+ assert all(t.shape[1:] == first_shape for t in tensor_sequence), (
447
+ "All tensors must have the same shape[1:]. "
448
+ f"Expected: {first_shape}, got: {[t.shape[1:] for t in tensor_sequence]}"
449
+ )
450
+
451
+ first = tensor_sequence[0]
452
+ dtype = first.dtype
453
+ shape_tail = first.shape[1:]
454
+ total_rows = sum(t.shape[0] for t in tensor_sequence)
455
+
456
+ # Allocate an empty Tensor on device
457
+ result = torch.empty((total_rows, *shape_tail), dtype=dtype, device=device)
458
+
459
+ row_start = 0
460
+ for t in tensor_sequence:
461
+ row_end = row_start + t.shape[0]
462
+ result[row_start:row_end].copy_(t, non_blocking=non_blocking)
463
+ row_start = row_end
464
+
465
+ return result
466
+
467
+
468
+ def _get_type_str(batch: Any) -> str:
469
+ """Get a string representation of the possibly nested type of the batch.
470
+
471
+ >>> import torch
472
+ >>> _get_type_str([1, 2, "???"])
473
+ 'list[int | str]'
474
+ >>> _get_type_str({"a": [1, 2, 3], "b": 4})
475
+ 'dict[str, int | list[int]]'
476
+ >>> _get_type_str({"a": torch.tensor(1), "b": [torch.tensor(2)]})
477
+ 'dict[str, Tensor | list[Tensor]]'
478
+ >>> _get_type_str({"a": torch.tensor(1), "b": {"c": torch.tensor(2)}})
479
+ 'dict[str, Tensor | dict[str, Tensor]]'
480
+ """
481
+ curr_type = type(batch).__name__
482
+ if isinstance(batch, (list, tuple)):
483
+ val_types = " | ".join(sorted({_get_type_str(v) for v in batch}))
484
+ invalid_type_str = f"{curr_type}[{val_types}]"
485
+ elif isinstance(batch, dict):
486
+ val_types = " | ".join(sorted({_get_type_str(v) for v in batch.values()}))
487
+ invalid_type_str = f"{curr_type}[str, {val_types}]"
488
+ else:
489
+ invalid_type_str = curr_type
490
+ return invalid_type_str
491
+
492
+
493
+ @torch.no_grad()
494
+ def move_tensors_to_device(
495
+ batch: TensorBatchType,
496
+ device: Optional[Union[str, "torch.device"]] = None,
497
+ non_blocking: bool = DEFAULT_TENSOR_NON_BLOCKING_TRANSFER,
498
+ ) -> TensorBatchReturnType:
499
+ """Move tensors to the specified device.
500
+
501
+ Concatenate nested lists/tuples of tensors along the first (batch) dimension.
502
+ For example, for the input
503
+ ((feature_0_chunk_0,), (feature_1_chunk_0, feature_1_chunk_1))
504
+ the output will be (feature_0_chunk_0, feature_1_chunk_0+1)
505
+ where each feature is concatenated along the batch dimension.
506
+
507
+ Args:
508
+ batch: A tensor or collection of tensors to move to device. Can be:
509
+ - A single tensor
510
+ - A sequence of tensors
511
+ - A sequence of sequences of tensors. The inner sequence of tensors is
512
+ combined during GPU transfer.
513
+ - A mapping (e.g., dict) of keys to tensors or sequences of tensors. The
514
+ sequence of tensors is combined during GPU transfer.
515
+ device: The device to move tensors to. If None, tensors are not moved.
516
+ non_blocking: If True, perform device transfer without forcing a
517
+ synchronization.
518
+
519
+ Returns:
520
+ The input tensors moved to the specified device
521
+ """
522
+ if device is None:
523
+ return batch
524
+
525
+ if _is_tensor(batch):
526
+ return batch.to(device, non_blocking=non_blocking)
527
+ elif _is_tensor_sequence(batch):
528
+ return type(batch)([t.to(device, non_blocking=non_blocking) for t in batch])
529
+ elif _is_nested_tensor_sequence(batch):
530
+ return type(batch)(
531
+ [concat_tensors_to_device(t, device, non_blocking) for t in batch]
532
+ )
533
+ elif _is_tensor_mapping(batch):
534
+ return {k: t.to(device, non_blocking=non_blocking) for k, t in batch.items()}
535
+ elif _is_tensor_sequence_mapping(batch):
536
+ return {
537
+ k: concat_tensors_to_device(v, device, non_blocking)
538
+ for k, v in batch.items()
539
+ }
540
+ else:
541
+ raise ValueError(
542
+ f"Invalid input type: {_get_type_str(batch)}.\n"
543
+ "Expected one of the following: "
544
+ "torch.Tensor, "
545
+ "List/Tuple[torch.Tensor], "
546
+ "Dict[str, torch.Tensor], "
547
+ "Mapping[str, List/Tuple[torch.Tensor]]"
548
+ )
lib/python3.12/site-packages/ray/air/_internal/uri_utils.py ADDED
@@ -0,0 +1,101 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import os
2
+ import urllib.parse
3
+ from pathlib import Path
4
+ from typing import Union
5
+
6
+
7
+ class URI:
8
+ """Represents a URI, supporting path appending and retrieving parent URIs.
9
+
10
+ Example Usage:
11
+
12
+ >>> s3_uri = URI("s3://bucket/a?scheme=http&param=1")
13
+ >>> s3_uri
14
+ URI<s3://bucket/a?scheme=http&param=1>
15
+ >>> str(s3_uri / "b" / "c")
16
+ 's3://bucket/a/b/c?scheme=http&param=1'
17
+ >>> str(s3_uri.parent)
18
+ 's3://bucket?scheme=http&param=1'
19
+ >>> str(s3_uri)
20
+ 's3://bucket/a?scheme=http&param=1'
21
+ >>> s3_uri.parent.name, s3_uri.name
22
+ ('bucket', 'a')
23
+ >>> local_path = URI("/tmp/local")
24
+ >>> str(local_path)
25
+ '/tmp/local'
26
+ >>> str(local_path.parent)
27
+ '/tmp'
28
+ >>> str(local_path / "b" / "c")
29
+ '/tmp/local/b/c'
30
+
31
+ Args:
32
+ uri: The URI to represent.
33
+ Ex: s3://bucket?scheme=http&endpoint_override=localhost%3A900
34
+ Ex: file:///a/b/c/d
35
+ """
36
+
37
+ def __init__(self, uri: str):
38
+ self._parsed = urllib.parse.urlparse(uri)
39
+ if not self._parsed.scheme:
40
+ # Just treat this as a regular path
41
+ self._path = Path(uri)
42
+ else:
43
+ self._path = Path(os.path.normpath(self._parsed.netloc + self._parsed.path))
44
+
45
+ def rstrip_subpath(self, subpath: Path) -> "URI":
46
+ """Returns a new URI that strips the given subpath from the end of this URI.
47
+
48
+ Example:
49
+ >>> uri = URI("s3://bucket/a/b/c/?param=1")
50
+ >>> str(uri.rstrip_subpath(Path("b/c")))
51
+ 's3://bucket/a?param=1'
52
+
53
+ >>> uri = URI("/tmp/a/b/c/")
54
+ >>> str(uri.rstrip_subpath(Path("/b/c/.//")))
55
+ '/tmp/a'
56
+
57
+ """
58
+ assert str(self._path).endswith(str(subpath)), (self._path, subpath)
59
+ stripped_path = str(self._path).replace(str(subpath), "")
60
+ return URI(self._get_str_representation(self._parsed, stripped_path))
61
+
62
+ @property
63
+ def name(self) -> str:
64
+ return self._path.name
65
+
66
+ @property
67
+ def parent(self) -> "URI":
68
+ assert self._path.parent != ".", f"{str(self)} has no valid parent URI"
69
+ return URI(self._get_str_representation(self._parsed, self._path.parent))
70
+
71
+ @property
72
+ def scheme(self) -> str:
73
+ return self._parsed.scheme
74
+
75
+ @property
76
+ def path(self) -> str:
77
+ return str(self._path)
78
+
79
+ def __truediv__(self, path_to_append):
80
+ assert isinstance(path_to_append, str)
81
+ return URI(
82
+ self._get_str_representation(self._parsed, self._path / path_to_append)
83
+ )
84
+
85
+ @classmethod
86
+ def _get_str_representation(
87
+ cls, parsed_uri: urllib.parse.ParseResult, path: Union[str, Path]
88
+ ) -> str:
89
+ if not parsed_uri.scheme:
90
+ return str(path)
91
+ return parsed_uri._replace(netloc=str(path), path="").geturl()
92
+
93
+ def __repr__(self):
94
+ return f"URI<{str(self)}>"
95
+
96
+ def __str__(self):
97
+ return self._get_str_representation(self._parsed, self._path)
98
+
99
+
100
+ def is_uri(path: str) -> bool:
101
+ return bool(urllib.parse.urlparse(path).scheme)
lib/python3.12/site-packages/ray/air/_internal/usage.py ADDED
@@ -0,0 +1,274 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import collections
2
+ import json
3
+ import os
4
+ from enum import Enum
5
+ from typing import TYPE_CHECKING, Dict, List, Optional, Set, Union
6
+
7
+ from ray._common.usage.usage_lib import TagKey, record_extra_usage_tag
8
+
9
+ if TYPE_CHECKING:
10
+ from ray.train._internal.storage import StorageContext
11
+ from ray.train.trainer import BaseTrainer
12
+ from ray.tune import Callback
13
+ from ray.tune.schedulers import TrialScheduler
14
+ from ray.tune.search import BasicVariantGenerator, Searcher
15
+
16
+
17
+ AIR_TRAINERS = {
18
+ "HorovodTrainer",
19
+ "LightGBMTrainer",
20
+ "TensorflowTrainer",
21
+ "TorchTrainer",
22
+ "XGBoostTrainer",
23
+ }
24
+
25
+ TRAIN_V2_TRAINERS = {
26
+ "DataParallelTrainer",
27
+ "JaxTrainer",
28
+ "LightGBMTrainer",
29
+ "TensorflowTrainer",
30
+ "TorchTrainer",
31
+ "XGBoostTrainer",
32
+ }
33
+
34
+ # searchers implemented by Ray Tune.
35
+ TUNE_SEARCHERS = {
36
+ "AxSearch",
37
+ "BayesOptSearch",
38
+ "TuneBOHB",
39
+ "HEBOSearch",
40
+ "HyperOptSearch",
41
+ "NevergradSearch",
42
+ "OptunaSearch",
43
+ "ZOOptSearch",
44
+ }
45
+
46
+ # These are just wrappers around real searchers.
47
+ # We don't want to double tag in this case, otherwise, the real tag
48
+ # will be overwritten.
49
+ TUNE_SEARCHER_WRAPPERS = {
50
+ "ConcurrencyLimiter",
51
+ "Repeater",
52
+ }
53
+
54
+ TUNE_SCHEDULERS = {
55
+ "FIFOScheduler",
56
+ "AsyncHyperBandScheduler",
57
+ "MedianStoppingRule",
58
+ "HyperBandScheduler",
59
+ "HyperBandForBOHB",
60
+ "PopulationBasedTraining",
61
+ "PopulationBasedTrainingReplay",
62
+ "PB2",
63
+ "ResourceChangingScheduler",
64
+ }
65
+
66
+
67
+ class AirEntrypoint(Enum):
68
+ TUNER = "Tuner.fit"
69
+ TRAINER = "Trainer.fit"
70
+ TUNE_RUN = "tune.run"
71
+ TUNE_RUN_EXPERIMENTS = "tune.run_experiments"
72
+
73
+
74
+ def _find_class_name(obj, allowed_module_path_prefix: str, whitelist: Set[str]):
75
+ """Find the class name of the object. If the object is not
76
+ under `allowed_module_path_prefix` or if its class is not in the whitelist,
77
+ return "Custom".
78
+
79
+ Args:
80
+ obj: The object under inspection.
81
+ allowed_module_path_prefix: If the `obj`'s class is not under
82
+ the `allowed_module_path_prefix`, its class name will be anonymized.
83
+ whitelist: If the `obj`'s class is not in the `whitelist`,
84
+ it will be anonymized.
85
+ Returns:
86
+ The class name to be tagged with telemetry.
87
+ """
88
+ module_path = obj.__module__
89
+ cls_name = obj.__class__.__name__
90
+ if module_path.startswith(allowed_module_path_prefix) and cls_name in whitelist:
91
+ return cls_name
92
+ else:
93
+ return "Custom"
94
+
95
+
96
+ def tag_air_trainer(trainer: "BaseTrainer"):
97
+ from ray.train.trainer import BaseTrainer
98
+
99
+ assert isinstance(trainer, BaseTrainer)
100
+ trainer_name = _find_class_name(trainer, "ray.train", AIR_TRAINERS)
101
+ record_extra_usage_tag(TagKey.AIR_TRAINER, trainer_name)
102
+
103
+
104
+ def tag_train_v2_trainer(trainer):
105
+ from ray.train.v2.api.data_parallel_trainer import DataParallelTrainer
106
+
107
+ assert isinstance(trainer, DataParallelTrainer)
108
+ trainer_name = _find_class_name(trainer, "ray.train", TRAIN_V2_TRAINERS)
109
+ record_extra_usage_tag(TagKey.TRAIN_TRAINER, trainer_name)
110
+
111
+
112
+ def tag_searcher(searcher: Union["BasicVariantGenerator", "Searcher"]):
113
+ from ray.tune.search import BasicVariantGenerator, Searcher
114
+
115
+ if isinstance(searcher, BasicVariantGenerator):
116
+ # Note this could be highly inflated as all train flows are treated
117
+ # as using BasicVariantGenerator.
118
+ record_extra_usage_tag(TagKey.TUNE_SEARCHER, "BasicVariantGenerator")
119
+ elif isinstance(searcher, Searcher):
120
+ searcher_name = _find_class_name(
121
+ searcher, "ray.tune.search", TUNE_SEARCHERS.union(TUNE_SEARCHER_WRAPPERS)
122
+ )
123
+ if searcher_name in TUNE_SEARCHER_WRAPPERS:
124
+ # ignore to avoid double tagging with wrapper name.
125
+ return
126
+ record_extra_usage_tag(TagKey.TUNE_SEARCHER, searcher_name)
127
+ else:
128
+ assert False, (
129
+ "Not expecting a non-BasicVariantGenerator, "
130
+ "non-Searcher type passed in for `tag_searcher`."
131
+ )
132
+
133
+
134
+ def tag_scheduler(scheduler: "TrialScheduler"):
135
+ from ray.tune.schedulers import TrialScheduler
136
+
137
+ assert isinstance(scheduler, TrialScheduler)
138
+ scheduler_name = _find_class_name(scheduler, "ray.tune.schedulers", TUNE_SCHEDULERS)
139
+ record_extra_usage_tag(TagKey.TUNE_SCHEDULER, scheduler_name)
140
+
141
+
142
+ def tag_setup_wandb():
143
+ record_extra_usage_tag(TagKey.AIR_SETUP_WANDB_INTEGRATION_USED, "1")
144
+
145
+
146
+ def tag_setup_mlflow():
147
+ record_extra_usage_tag(TagKey.AIR_SETUP_MLFLOW_INTEGRATION_USED, "1")
148
+
149
+
150
+ def _count_callbacks(callbacks: Optional[List["Callback"]]) -> Dict[str, int]:
151
+ """Creates a map of callback class name -> count given a list of callbacks."""
152
+ from ray.air.integrations.comet import CometLoggerCallback
153
+ from ray.air.integrations.mlflow import MLflowLoggerCallback
154
+ from ray.air.integrations.wandb import WandbLoggerCallback
155
+ from ray.tune import Callback
156
+ from ray.tune.logger import LoggerCallback
157
+ from ray.tune.logger.aim import AimLoggerCallback
158
+ from ray.tune.utils.callback import DEFAULT_CALLBACK_CLASSES
159
+
160
+ built_in_callbacks = (
161
+ WandbLoggerCallback,
162
+ MLflowLoggerCallback,
163
+ CometLoggerCallback,
164
+ AimLoggerCallback,
165
+ ) + DEFAULT_CALLBACK_CLASSES
166
+
167
+ callback_names = [callback_cls.__name__ for callback_cls in built_in_callbacks]
168
+ callback_counts = collections.defaultdict(int)
169
+
170
+ callbacks = callbacks or []
171
+ for callback in callbacks:
172
+ if not isinstance(callback, Callback):
173
+ # This will error later, but don't include this as custom usage.
174
+ continue
175
+
176
+ callback_name = callback.__class__.__name__
177
+
178
+ if callback_name in callback_names:
179
+ callback_counts[callback_name] += 1
180
+ elif isinstance(callback, LoggerCallback):
181
+ callback_counts["CustomLoggerCallback"] += 1
182
+ else:
183
+ callback_counts["CustomCallback"] += 1
184
+
185
+ return callback_counts
186
+
187
+
188
+ def tag_callbacks(callbacks: Optional[List["Callback"]]) -> bool:
189
+ """Records built-in callback usage via a JSON str representing a
190
+ dictionary mapping callback class name -> counts.
191
+
192
+ User-defined callbacks will increment the count under the `CustomLoggerCallback`
193
+ or `CustomCallback` key depending on which of the provided interfaces they subclass.
194
+ NOTE: This will NOT track the name of the user-defined callback,
195
+ nor its implementation.
196
+
197
+ This will NOT report telemetry if no callbacks are provided by the user.
198
+
199
+ Returns:
200
+ bool: True if usage was recorded, False otherwise.
201
+ """
202
+ if not callbacks:
203
+ # User didn't pass in any callbacks -> no usage recorded.
204
+ return False
205
+
206
+ callback_counts = _count_callbacks(callbacks)
207
+
208
+ if callback_counts:
209
+ callback_counts_str = json.dumps(callback_counts)
210
+ record_extra_usage_tag(TagKey.AIR_CALLBACKS, callback_counts_str)
211
+
212
+
213
+ def tag_storage_type(storage: "StorageContext"):
214
+ """Records the storage configuration of an experiment.
215
+
216
+ The storage configuration is set by `RunConfig(storage_path, storage_filesystem)`.
217
+
218
+ The possible storage types (defined by `pyarrow.fs.FileSystem.type_name`) are:
219
+ - 'local' = pyarrow.fs.LocalFileSystem. This includes NFS usage.
220
+ - 'mock' = pyarrow.fs._MockFileSystem. This is used for testing.
221
+ - ('s3', 'gcs', 'abfs', 'hdfs'): Various remote storage schemes
222
+ with default implementations in pyarrow.
223
+ - 'custom' = All other storage schemes, which includes ALL cases where a
224
+ custom `storage_filesystem` is provided.
225
+ - 'other' = catches any other cases not explicitly handled above.
226
+ """
227
+ whitelist = {"local", "mock", "s3", "gcs", "abfs", "hdfs"}
228
+
229
+ if storage.custom_fs_provided:
230
+ storage_config_tag = "custom"
231
+ elif storage.storage_filesystem.type_name in whitelist:
232
+ storage_config_tag = storage.storage_filesystem.type_name
233
+ else:
234
+ storage_config_tag = "other"
235
+
236
+ record_extra_usage_tag(TagKey.AIR_STORAGE_CONFIGURATION, storage_config_tag)
237
+
238
+
239
+ def tag_ray_air_env_vars() -> bool:
240
+ """Records usage of environment variables exposed by the Ray AIR libraries.
241
+
242
+ NOTE: This does not track the values of the environment variables, nor
243
+ does this track environment variables not explicitly included in the
244
+ `all_ray_air_env_vars` allow-list.
245
+
246
+ Returns:
247
+ bool: True if at least one environment var is supplied by the user.
248
+ """
249
+ from ray.air.constants import AIR_ENV_VARS
250
+ from ray.train.constants import TRAIN_ENV_VARS
251
+ from ray.tune.constants import TUNE_ENV_VARS
252
+
253
+ all_ray_air_env_vars = sorted(
254
+ set().union(AIR_ENV_VARS, TUNE_ENV_VARS, TRAIN_ENV_VARS)
255
+ )
256
+
257
+ user_supplied_env_vars = []
258
+
259
+ for env_var in all_ray_air_env_vars:
260
+ if env_var in os.environ:
261
+ user_supplied_env_vars.append(env_var)
262
+
263
+ if user_supplied_env_vars:
264
+ env_vars_str = json.dumps(user_supplied_env_vars)
265
+ record_extra_usage_tag(TagKey.AIR_ENV_VARS, env_vars_str)
266
+ return True
267
+
268
+ return False
269
+
270
+
271
+ def tag_air_entrypoint(entrypoint: AirEntrypoint) -> None:
272
+ """Records the entrypoint to an AIR training run."""
273
+ assert entrypoint in AirEntrypoint
274
+ record_extra_usage_tag(TagKey.AIR_ENTRYPOINT, entrypoint.value)
lib/python3.12/site-packages/ray/air/_internal/util.py ADDED
@@ -0,0 +1,125 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import copy
2
+ import logging
3
+ import os
4
+ import queue
5
+ import threading
6
+ from typing import Optional
7
+
8
+ import numpy as np
9
+
10
+ from ray.air.constants import _ERROR_REPORT_TIMEOUT
11
+
12
+ logger = logging.getLogger(__name__)
13
+
14
+
15
+ def is_nan(value):
16
+ return np.isnan(value)
17
+
18
+
19
+ def is_nan_or_inf(value):
20
+ return is_nan(value) or np.isinf(value)
21
+
22
+
23
+ class StartTraceback(Exception):
24
+ """These exceptions (and their tracebacks) can be skipped with `skip_exceptions`"""
25
+
26
+ pass
27
+
28
+
29
+ class StartTracebackWithWorkerRank(StartTraceback):
30
+ def __init__(self, worker_rank: int) -> None:
31
+ super().__init__()
32
+ self.worker_rank = worker_rank
33
+
34
+ def __reduce__(self):
35
+ return (self.__class__, (self.worker_rank,))
36
+
37
+
38
+ def skip_exceptions(exc: Optional[Exception]) -> Exception:
39
+ """Skip all contained `StartTracebacks` to reduce traceback output.
40
+
41
+ Returns a shallow copy of the exception with all `StartTracebacks` removed.
42
+
43
+ If the RAY_AIR_FULL_TRACEBACKS environment variable is set,
44
+ the original exception (not a copy) is returned.
45
+ """
46
+ should_not_shorten = bool(int(os.environ.get("RAY_AIR_FULL_TRACEBACKS", "0")))
47
+
48
+ if should_not_shorten:
49
+ return exc
50
+
51
+ if isinstance(exc, StartTraceback):
52
+ # If this is a StartTraceback, skip
53
+ return skip_exceptions(exc.__cause__)
54
+
55
+ # Perform a shallow copy to prevent recursive __cause__/__context__.
56
+ new_exc = copy.copy(exc).with_traceback(exc.__traceback__)
57
+
58
+ # Make sure nested exceptions are properly skipped.
59
+ cause = getattr(exc, "__cause__", None)
60
+ if cause:
61
+ new_exc.__cause__ = skip_exceptions(cause)
62
+
63
+ return new_exc
64
+
65
+
66
+ def exception_cause(exc: Optional[Exception]) -> Optional[Exception]:
67
+ if not exc:
68
+ return None
69
+
70
+ return getattr(exc, "__cause__", None)
71
+
72
+
73
+ class RunnerThread(threading.Thread):
74
+ """Supervisor thread that runs your script."""
75
+
76
+ def __init__(self, *args, error_queue, **kwargs):
77
+ threading.Thread.__init__(self, *args, **kwargs)
78
+ self._error_queue = error_queue
79
+ self._ret = None
80
+
81
+ def _propagate_exception(self, e: BaseException):
82
+ try:
83
+ # report the error but avoid indefinite blocking which would
84
+ # prevent the exception from being propagated in the unlikely
85
+ # case that something went terribly wrong
86
+ self._error_queue.put(e, block=True, timeout=_ERROR_REPORT_TIMEOUT)
87
+ except queue.Full:
88
+ logger.critical(
89
+ (
90
+ "Runner Thread was unable to report error to main "
91
+ "function runner thread. This means a previous error "
92
+ "was not processed. This should never happen."
93
+ )
94
+ )
95
+
96
+ def run(self):
97
+ try:
98
+ self._ret = self._target(*self._args, **self._kwargs)
99
+ except StopIteration:
100
+ logger.debug(
101
+ (
102
+ "Thread runner raised StopIteration. Interpreting it as a "
103
+ "signal to terminate the thread without error."
104
+ )
105
+ )
106
+ except SystemExit as e:
107
+ # Do not propagate up for graceful termination.
108
+ if e.code == 0:
109
+ logger.debug(
110
+ (
111
+ "Thread runner raised SystemExit with error code 0. "
112
+ "Interpreting it as a signal to terminate the thread "
113
+ "without error."
114
+ )
115
+ )
116
+ else:
117
+ # If non-zero exit code, then raise exception to main thread.
118
+ self._propagate_exception(e)
119
+ except BaseException as e:
120
+ # Propagate all other exceptions to the main thread.
121
+ self._propagate_exception(e)
122
+
123
+ def join(self, timeout=None):
124
+ super(RunnerThread, self).join(timeout)
125
+ return self._ret
lib/python3.12/site-packages/ray/air/config.py ADDED
@@ -0,0 +1,695 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import logging
2
+ import os
3
+ import warnings
4
+ from collections import Counter, defaultdict
5
+ from dataclasses import _MISSING_TYPE, dataclass, fields
6
+ from pathlib import Path
7
+ from typing import (
8
+ TYPE_CHECKING,
9
+ Any,
10
+ Callable,
11
+ Dict,
12
+ List,
13
+ Mapping,
14
+ Optional,
15
+ Tuple,
16
+ Union,
17
+ )
18
+
19
+ import pyarrow.fs
20
+
21
+ import ray
22
+ from ray._common.utils import RESOURCE_CONSTRAINT_PREFIX
23
+ from ray._private.thirdparty.tabulate.tabulate import tabulate
24
+ from ray.util.annotations import PublicAPI, RayDeprecationWarning
25
+ from ray.widgets import Template, make_table_html_repr
26
+
27
+ if TYPE_CHECKING:
28
+ import ray.tune.progress_reporter
29
+ from ray.tune.callback import Callback
30
+ from ray.tune.execution.placement_groups import PlacementGroupFactory
31
+ from ray.tune.experimental.output import AirVerbosity
32
+ from ray.tune.search.sample import Domain
33
+ from ray.tune.stopper import Stopper
34
+ from ray.tune.utils.log import Verbosity
35
+
36
+
37
+ # Dict[str, List] is to support `tune.grid_search`:
38
+ # TODO(sumanthratna/matt): Upstream this to Tune.
39
+ SampleRange = Union["Domain", Dict[str, List]]
40
+
41
+
42
+ MAX = "max"
43
+ MIN = "min"
44
+ _DEPRECATED_VALUE = "DEPRECATED"
45
+
46
+
47
+ logger = logging.getLogger(__name__)
48
+
49
+
50
+ def _repr_dataclass(obj, *, default_values: Optional[Dict[str, Any]] = None) -> str:
51
+ """A utility function to elegantly represent dataclasses.
52
+
53
+ In contrast to the default dataclass `__repr__`, which shows all parameters, this
54
+ function only shows parameters with non-default values.
55
+
56
+ Args:
57
+ obj: The dataclass to represent.
58
+ default_values: An optional dictionary that maps field names to default values.
59
+ Use this parameter to specify default values that are generated dynamically
60
+ (e.g., in `__post_init__` or by a `default_factory`). If a default value
61
+ isn't specified in `default_values`, then the default value is inferred from
62
+ the `dataclass`.
63
+
64
+ Returns:
65
+ A representation of the dataclass.
66
+ """
67
+ if default_values is None:
68
+ default_values = {}
69
+
70
+ non_default_values = {} # Maps field name to value.
71
+
72
+ def equals(value, default_value):
73
+ # We need to special case None because of a bug in pyarrow:
74
+ # https://github.com/apache/arrow/issues/38535
75
+ if value is None and default_value is None:
76
+ return True
77
+ if value is None or default_value is None:
78
+ return False
79
+ return value == default_value
80
+
81
+ for field in fields(obj):
82
+ value = getattr(obj, field.name)
83
+ default_value = default_values.get(field.name, field.default)
84
+ is_required = isinstance(field.default, _MISSING_TYPE)
85
+ if is_required or not equals(value, default_value):
86
+ non_default_values[field.name] = value
87
+
88
+ string = f"{obj.__class__.__name__}("
89
+ string += ", ".join(
90
+ f"{name}={value!r}" for name, value in non_default_values.items()
91
+ )
92
+ string += ")"
93
+
94
+ return string
95
+
96
+
97
+ @dataclass
98
+ @PublicAPI(stability="stable")
99
+ class ScalingConfig:
100
+ """Configuration for scaling training.
101
+
102
+ For more details, see :ref:`train_scaling_config`.
103
+
104
+ Args:
105
+ trainer_resources: Resources to allocate for the training coordinator.
106
+ The training coordinator launches the worker group and executes
107
+ the training function per worker, and this process does NOT require
108
+ GPUs. The coordinator is always scheduled on the same node as the
109
+ rank 0 worker, so one example use case is to set a minimum amount
110
+ of resources (e.g. CPU memory) required by the rank 0 node.
111
+ By default, this assigns 1 CPU to the training coordinator.
112
+ num_workers: The number of workers (Ray actors) to launch.
113
+ Each worker will reserve 1 CPU by default. The number of CPUs
114
+ reserved by each worker can be overridden with the
115
+ ``resources_per_worker`` argument.
116
+ use_gpu: If True, training will be done on GPUs (1 per worker).
117
+ Defaults to False. The number of GPUs reserved by each
118
+ worker can be overridden with the ``resources_per_worker``
119
+ argument.
120
+ resources_per_worker: If specified, the resources
121
+ defined in this Dict is reserved for each worker.
122
+ Define the ``"CPU"`` key (case-sensitive) to
123
+ override the number of CPUs used by each worker.
124
+ This can also be used to request :ref:`custom resources <custom-resources>`.
125
+ placement_strategy: The placement strategy to use for the
126
+ placement group of the Ray actors. See :ref:`Placement Group
127
+ Strategies <pgroup-strategy>` for the possible options.
128
+ accelerator_type: [Experimental] If specified, Ray Train will launch the
129
+ training coordinator and workers on the nodes with the specified type
130
+ of accelerators.
131
+ See :ref:`the available accelerator types <accelerator_types>`.
132
+ Ensure that your cluster has instances with the specified accelerator type
133
+ or is able to autoscale to fulfill the request.
134
+
135
+ Example:
136
+
137
+ .. code-block:: python
138
+
139
+ from ray.train import ScalingConfig
140
+ scaling_config = ScalingConfig(
141
+ # Number of distributed workers.
142
+ num_workers=2,
143
+ # Turn on/off GPU.
144
+ use_gpu=True,
145
+ # Assign extra CPU/GPU/custom resources per worker.
146
+ resources_per_worker={"GPU": 1, "CPU": 1, "memory": 1e9, "custom": 1.0},
147
+ # Try to schedule workers on different nodes.
148
+ placement_strategy="SPREAD",
149
+ )
150
+
151
+ """
152
+
153
+ trainer_resources: Optional[Union[Dict, SampleRange]] = None
154
+ num_workers: Union[int, SampleRange] = 1
155
+ use_gpu: Union[bool, SampleRange] = False
156
+ resources_per_worker: Optional[Union[Dict, SampleRange]] = None
157
+ placement_strategy: Union[str, SampleRange] = "PACK"
158
+ accelerator_type: Optional[str] = None
159
+
160
+ def __post_init__(self):
161
+ if self.resources_per_worker:
162
+ if not self.use_gpu and self.num_gpus_per_worker > 0:
163
+ raise ValueError(
164
+ "`use_gpu` is False but `GPU` was found in "
165
+ "`resources_per_worker`. Either set `use_gpu` to True or "
166
+ "remove `GPU` from `resources_per_worker."
167
+ )
168
+
169
+ if self.use_gpu and self.num_gpus_per_worker == 0:
170
+ raise ValueError(
171
+ "`use_gpu` is True but `GPU` is set to 0 in "
172
+ "`resources_per_worker`. Either set `use_gpu` to False or "
173
+ "request a positive number of `GPU` in "
174
+ "`resources_per_worker."
175
+ )
176
+
177
+ def __repr__(self):
178
+ return _repr_dataclass(self)
179
+
180
+ def _repr_html_(self) -> str:
181
+ return make_table_html_repr(obj=self, title=type(self).__name__)
182
+
183
+ def __eq__(self, o: "ScalingConfig") -> bool:
184
+ if not isinstance(o, type(self)):
185
+ return False
186
+ return self.as_placement_group_factory() == o.as_placement_group_factory()
187
+
188
+ @property
189
+ def _resources_per_worker_not_none(self):
190
+ if self.resources_per_worker is None:
191
+ if self.use_gpu:
192
+ # Note that we don't request any CPUs, which avoids possible
193
+ # scheduling contention. Generally nodes have many more CPUs than
194
+ # GPUs, so not requesting a CPU does not lead to oversubscription.
195
+ resources_per_worker = {"GPU": 1}
196
+ else:
197
+ resources_per_worker = {"CPU": 1}
198
+ else:
199
+ resources_per_worker = {
200
+ k: v for k, v in self.resources_per_worker.items() if v != 0
201
+ }
202
+
203
+ if self.use_gpu:
204
+ resources_per_worker.setdefault("GPU", 1)
205
+
206
+ if self.accelerator_type:
207
+ accelerator = f"{RESOURCE_CONSTRAINT_PREFIX}{self.accelerator_type}"
208
+ resources_per_worker.setdefault(accelerator, 0.001)
209
+ return resources_per_worker
210
+
211
+ @property
212
+ def _trainer_resources_not_none(self):
213
+ if self.trainer_resources is None:
214
+ if self.num_workers:
215
+ # For Google Colab, don't allocate resources to the base Trainer.
216
+ # Colab only has 2 CPUs, and because of this resource scarcity,
217
+ # we have to be careful on where we allocate resources. Since Colab
218
+ # is not distributed, the concern about many parallel Ray Tune trials
219
+ # leading to all Trainers being scheduled on the head node if we set
220
+ # `trainer_resources` to 0 is no longer applicable.
221
+ try:
222
+ import google.colab # noqa: F401
223
+
224
+ trainer_num_cpus = 0
225
+ except ImportError:
226
+ trainer_num_cpus = 1
227
+ else:
228
+ # If there are no additional workers, then always reserve 1 CPU for
229
+ # the Trainer.
230
+ trainer_num_cpus = 1
231
+
232
+ trainer_resources = {"CPU": trainer_num_cpus}
233
+ else:
234
+ trainer_resources = {
235
+ k: v for k, v in self.trainer_resources.items() if v != 0
236
+ }
237
+
238
+ return trainer_resources
239
+
240
+ @property
241
+ def total_resources(self):
242
+ """Map of total resources required for the trainer."""
243
+ total_resource_map = defaultdict(float, self._trainer_resources_not_none)
244
+ for k, value in self._resources_per_worker_not_none.items():
245
+ total_resource_map[k] += value * self.num_workers
246
+ return dict(total_resource_map)
247
+
248
+ @property
249
+ def num_cpus_per_worker(self):
250
+ """The number of CPUs to set per worker."""
251
+ return self._resources_per_worker_not_none.get("CPU", 0)
252
+
253
+ @property
254
+ def num_gpus_per_worker(self):
255
+ """The number of GPUs to set per worker."""
256
+ return self._resources_per_worker_not_none.get("GPU", 0)
257
+
258
+ @property
259
+ def additional_resources_per_worker(self):
260
+ """Resources per worker, not including CPU or GPU resources."""
261
+ return {
262
+ k: v
263
+ for k, v in self._resources_per_worker_not_none.items()
264
+ if k not in ["CPU", "GPU"]
265
+ }
266
+
267
+ def as_placement_group_factory(self) -> "PlacementGroupFactory":
268
+ """Returns a PlacementGroupFactory to specify resources for Tune."""
269
+ from ray.tune.execution.placement_groups import PlacementGroupFactory
270
+
271
+ trainer_bundle = self._trainer_resources_not_none
272
+ worker_bundle = self._resources_per_worker_not_none
273
+
274
+ # Colocate Trainer and rank0 worker by merging their bundles
275
+ # Note: This empty bundle is required so that the Tune actor manager schedules
276
+ # the Trainable onto the combined bundle while taking none of its resources,
277
+ # rather than a non-empty head bundle.
278
+ combined_bundle = dict(Counter(trainer_bundle) + Counter(worker_bundle))
279
+ bundles = [{}, combined_bundle] + [worker_bundle] * (self.num_workers - 1)
280
+ return PlacementGroupFactory(bundles, strategy=self.placement_strategy)
281
+
282
+ @classmethod
283
+ def from_placement_group_factory(
284
+ cls, pgf: "PlacementGroupFactory"
285
+ ) -> "ScalingConfig":
286
+ """Create a ScalingConfig from a Tune's PlacementGroupFactory
287
+
288
+ Note that this is only needed for ResourceChangingScheduler, which
289
+ modifies a trial's PlacementGroupFactory but doesn't propagate
290
+ the changes to ScalingConfig. TrainTrainable needs to reconstruct
291
+ a ScalingConfig from on the trial's PlacementGroupFactory.
292
+ """
293
+
294
+ # pgf.bundles = [{trainer + worker}, {worker}, ..., {worker}]
295
+ num_workers = len(pgf.bundles)
296
+ combined_resources = pgf.bundles[0]
297
+ resources_per_worker = pgf.bundles[-1]
298
+ use_gpu = bool(resources_per_worker.get("GPU", False))
299
+ placement_strategy = pgf.strategy
300
+
301
+ # In `as_placement_group_factory`, we merged the trainer resource into the
302
+ # first worker resources bundle. We need to calculate the resources diff to
303
+ # get the trainer resources.
304
+ # Note: If there's only one worker, we won't be able to calculate the diff.
305
+ # We'll have empty trainer bundle and assign all resources to the worker.
306
+ trainer_resources = dict(
307
+ Counter(combined_resources) - Counter(resources_per_worker)
308
+ )
309
+
310
+ return ScalingConfig(
311
+ trainer_resources=trainer_resources,
312
+ num_workers=num_workers,
313
+ use_gpu=use_gpu,
314
+ resources_per_worker=resources_per_worker,
315
+ placement_strategy=placement_strategy,
316
+ )
317
+
318
+
319
+ @dataclass
320
+ @PublicAPI(stability="stable")
321
+ class FailureConfig:
322
+ """Configuration related to failure handling of each training/tuning run.
323
+
324
+ Args:
325
+ max_failures: Tries to recover a run at least this many times.
326
+ Will recover from the latest checkpoint if present.
327
+ Setting to -1 will lead to infinite recovery retries.
328
+ Setting to 0 will disable retries. Defaults to 0.
329
+ fail_fast: Whether to fail upon the first error.
330
+ If fail_fast='raise' provided, the original error during training will be
331
+ immediately raised. fail_fast='raise' can easily leak resources and
332
+ should be used with caution.
333
+ """
334
+
335
+ max_failures: int = 0
336
+ fail_fast: Union[bool, str] = False
337
+
338
+ def __post_init__(self):
339
+ # Same check as in TuneController
340
+ if not (isinstance(self.fail_fast, bool) or self.fail_fast.upper() == "RAISE"):
341
+ raise ValueError(
342
+ "fail_fast must be one of {bool, 'raise'}. " f"Got {self.fail_fast}."
343
+ )
344
+
345
+ # Same check as in tune.run
346
+ if self.fail_fast and self.max_failures != 0:
347
+ raise ValueError(
348
+ f"max_failures must be 0 if fail_fast={repr(self.fail_fast)}."
349
+ )
350
+
351
+ def __repr__(self):
352
+ return _repr_dataclass(self)
353
+
354
+ def _repr_html_(self):
355
+ return Template("scrollableTable.html.j2").render(
356
+ table=tabulate(
357
+ {
358
+ "Setting": ["Max failures", "Fail fast"],
359
+ "Value": [self.max_failures, self.fail_fast],
360
+ },
361
+ tablefmt="html",
362
+ showindex=False,
363
+ headers="keys",
364
+ ),
365
+ max_height="none",
366
+ )
367
+
368
+
369
+ @dataclass
370
+ @PublicAPI(stability="stable")
371
+ class CheckpointConfig:
372
+ """Configurable parameters for defining the checkpointing strategy.
373
+
374
+ Default behavior is to persist all checkpoints to disk. If
375
+ ``num_to_keep`` is set, the default retention policy is to keep the
376
+ checkpoints with maximum timestamp, i.e. the most recent checkpoints.
377
+
378
+ Args:
379
+ num_to_keep: The number of checkpoints to keep
380
+ on disk for this run. If a checkpoint is persisted to disk after
381
+ there are already this many checkpoints, then an existing
382
+ checkpoint will be deleted. If this is ``None`` then checkpoints
383
+ will not be deleted. Must be >= 1.
384
+ checkpoint_score_attribute: The attribute that will be used to
385
+ score checkpoints to determine which checkpoints should be kept
386
+ on disk when there are greater than ``num_to_keep`` checkpoints.
387
+ This attribute must be a key from the checkpoint
388
+ dictionary which has a numerical value. Per default, the last
389
+ checkpoints will be kept.
390
+ checkpoint_score_order: Either "max" or "min".
391
+ If "max", then checkpoints with highest values of
392
+ ``checkpoint_score_attribute`` will be kept.
393
+ If "min", then checkpoints with lowest values of
394
+ ``checkpoint_score_attribute`` will be kept.
395
+ checkpoint_frequency: Number of iterations between checkpoints. If 0
396
+ this will disable checkpointing.
397
+ Please note that most trainers will still save one checkpoint at
398
+ the end of training.
399
+ This attribute is only supported
400
+ by trainers that don't take in custom training loops.
401
+ checkpoint_at_end: If True, will save a checkpoint at the end of training.
402
+ This attribute is only supported by trainers that don't take in
403
+ custom training loops. Defaults to True for trainers that support it
404
+ and False for generic function trainables.
405
+ _checkpoint_keep_all_ranks: This experimental config is deprecated.
406
+ This behavior is now controlled by reporting `checkpoint=None`
407
+ in the workers that shouldn't persist a checkpoint.
408
+ For example, if you only want the rank 0 worker to persist a checkpoint
409
+ (e.g., in standard data parallel training), then you should save and
410
+ report a checkpoint if `ray.train.get_context().get_world_rank() == 0`
411
+ and `None` otherwise.
412
+ _checkpoint_upload_from_workers: This experimental config is deprecated.
413
+ Uploading checkpoint directly from the worker is now the default behavior.
414
+ """
415
+
416
+ num_to_keep: Optional[int] = None
417
+ checkpoint_score_attribute: Optional[str] = None
418
+ checkpoint_score_order: Optional[str] = MAX
419
+ checkpoint_frequency: Optional[int] = 0
420
+ checkpoint_at_end: Optional[bool] = None
421
+ _checkpoint_keep_all_ranks: Optional[bool] = _DEPRECATED_VALUE
422
+ _checkpoint_upload_from_workers: Optional[bool] = _DEPRECATED_VALUE
423
+
424
+ def __post_init__(self):
425
+ if self._checkpoint_keep_all_ranks != _DEPRECATED_VALUE:
426
+ raise DeprecationWarning(
427
+ "The experimental `_checkpoint_keep_all_ranks` config is deprecated. "
428
+ "This behavior is now controlled by reporting `checkpoint=None` "
429
+ "in the workers that shouldn't persist a checkpoint. "
430
+ "For example, if you only want the rank 0 worker to persist a "
431
+ "checkpoint (e.g., in standard data parallel training), "
432
+ "then you should save and report a checkpoint if "
433
+ "`ray.train.get_context().get_world_rank() == 0` "
434
+ "and `None` otherwise."
435
+ )
436
+
437
+ if self._checkpoint_upload_from_workers != _DEPRECATED_VALUE:
438
+ raise DeprecationWarning(
439
+ "The experimental `_checkpoint_upload_from_workers` config is "
440
+ "deprecated. Uploading checkpoint directly from the worker is "
441
+ "now the default behavior."
442
+ )
443
+
444
+ if self.num_to_keep is not None and self.num_to_keep <= 0:
445
+ raise ValueError(
446
+ f"Received invalid num_to_keep: "
447
+ f"{self.num_to_keep}. "
448
+ f"Must be None or an integer >= 1."
449
+ )
450
+ if self.checkpoint_score_order not in (MAX, MIN):
451
+ raise ValueError(
452
+ f"checkpoint_score_order must be either " f'"{MAX}" or "{MIN}".'
453
+ )
454
+
455
+ if self.checkpoint_frequency < 0:
456
+ raise ValueError(
457
+ f"checkpoint_frequency must be >=0, got {self.checkpoint_frequency}"
458
+ )
459
+
460
+ def __repr__(self):
461
+ return _repr_dataclass(self)
462
+
463
+ def _repr_html_(self) -> str:
464
+ if self.num_to_keep is None:
465
+ num_to_keep_repr = "All"
466
+ else:
467
+ num_to_keep_repr = self.num_to_keep
468
+
469
+ if self.checkpoint_score_attribute is None:
470
+ checkpoint_score_attribute_repr = "Most recent"
471
+ else:
472
+ checkpoint_score_attribute_repr = self.checkpoint_score_attribute
473
+
474
+ if self.checkpoint_at_end is None:
475
+ checkpoint_at_end_repr = ""
476
+ else:
477
+ checkpoint_at_end_repr = self.checkpoint_at_end
478
+
479
+ return Template("scrollableTable.html.j2").render(
480
+ table=tabulate(
481
+ {
482
+ "Setting": [
483
+ "Number of checkpoints to keep",
484
+ "Checkpoint score attribute",
485
+ "Checkpoint score order",
486
+ "Checkpoint frequency",
487
+ "Checkpoint at end",
488
+ ],
489
+ "Value": [
490
+ num_to_keep_repr,
491
+ checkpoint_score_attribute_repr,
492
+ self.checkpoint_score_order,
493
+ self.checkpoint_frequency,
494
+ checkpoint_at_end_repr,
495
+ ],
496
+ },
497
+ tablefmt="html",
498
+ showindex=False,
499
+ headers="keys",
500
+ ),
501
+ max_height="none",
502
+ )
503
+
504
+ @property
505
+ def _tune_legacy_checkpoint_score_attr(self) -> Optional[str]:
506
+ """Same as ``checkpoint_score_attr`` in ``tune.run``.
507
+
508
+ Only used for Legacy API compatibility.
509
+ """
510
+ if self.checkpoint_score_attribute is None:
511
+ return self.checkpoint_score_attribute
512
+ prefix = ""
513
+ if self.checkpoint_score_order == MIN:
514
+ prefix = "min-"
515
+ return f"{prefix}{self.checkpoint_score_attribute}"
516
+
517
+
518
+ @dataclass
519
+ @PublicAPI(stability="stable")
520
+ class RunConfig:
521
+ """Runtime configuration for training and tuning runs.
522
+
523
+ Upon resuming from a training or tuning run checkpoint,
524
+ Ray Train/Tune will automatically apply the RunConfig from
525
+ the previously checkpointed run.
526
+
527
+ Args:
528
+ name: Name of the trial or experiment. If not provided, will be deduced
529
+ from the Trainable.
530
+ storage_path: [Beta] Path where all results and checkpoints are persisted.
531
+ Can be a local directory or a destination on cloud storage.
532
+ For multi-node training/tuning runs, this must be set to a
533
+ shared storage location (e.g., S3, NFS).
534
+ This defaults to the local ``~/ray_results`` directory.
535
+ storage_filesystem: [Beta] A custom filesystem to use for storage.
536
+ If this is provided, `storage_path` should be a path with its
537
+ prefix stripped (e.g., `s3://bucket/path` -> `bucket/path`).
538
+ failure_config: Failure mode configuration.
539
+ checkpoint_config: Checkpointing configuration.
540
+ sync_config: Configuration object for syncing. See train.SyncConfig.
541
+ verbose: 0, 1, or 2. Verbosity mode.
542
+ 0 = silent, 1 = default, 2 = verbose. Defaults to 1.
543
+ If the ``RAY_AIR_NEW_OUTPUT=1`` environment variable is set,
544
+ uses the old verbosity settings:
545
+ 0 = silent, 1 = only status updates, 2 = status and brief
546
+ results, 3 = status and detailed results.
547
+ stop: Stop conditions to consider. Refer to ray.tune.stopper.Stopper
548
+ for more info. Stoppers should be serializable.
549
+ callbacks: [DeveloperAPI] Callbacks to invoke.
550
+ Refer to ray.tune.callback.Callback for more info.
551
+ Callbacks should be serializable.
552
+ Currently only stateless callbacks are supported for resumed runs.
553
+ (any state of the callback will not be checkpointed by Tune
554
+ and thus will not take effect in resumed runs).
555
+ progress_reporter: [DeveloperAPI] Progress reporter for reporting
556
+ intermediate experiment progress. Defaults to CLIReporter if
557
+ running in command-line, or JupyterNotebookReporter if running in
558
+ a Jupyter notebook.
559
+ log_to_file: [DeveloperAPI] Log stdout and stderr to files in
560
+ trial directories. If this is `False` (default), no files
561
+ are written. If `true`, outputs are written to `trialdir/stdout`
562
+ and `trialdir/stderr`, respectively. If this is a single string,
563
+ this is interpreted as a file relative to the trialdir, to which
564
+ both streams are written. If this is a Sequence (e.g. a Tuple),
565
+ it has to have length 2 and the elements indicate the files to
566
+ which stdout and stderr are written, respectively.
567
+
568
+ """
569
+
570
+ name: Optional[str] = None
571
+ storage_path: Optional[str] = None
572
+ storage_filesystem: Optional[pyarrow.fs.FileSystem] = None
573
+ failure_config: Optional[FailureConfig] = None
574
+ checkpoint_config: Optional[CheckpointConfig] = None
575
+ sync_config: Optional["ray.train.SyncConfig"] = None
576
+ verbose: Optional[Union[int, "AirVerbosity", "Verbosity"]] = None
577
+ stop: Optional[Union[Mapping, "Stopper", Callable[[str, Mapping], bool]]] = None
578
+ callbacks: Optional[List["Callback"]] = None
579
+ progress_reporter: Optional["ray.tune.progress_reporter.ProgressReporter"] = None
580
+ log_to_file: Union[bool, str, Tuple[str, str]] = False
581
+
582
+ # Deprecated
583
+ local_dir: Optional[str] = None
584
+
585
+ def __post_init__(self):
586
+ from ray.train import SyncConfig
587
+ from ray.train.constants import DEFAULT_STORAGE_PATH
588
+ from ray.tune.experimental.output import AirVerbosity, get_air_verbosity
589
+
590
+ if self.local_dir is not None:
591
+ raise DeprecationWarning(
592
+ "The `RunConfig(local_dir)` argument is deprecated. "
593
+ "You should set the `RunConfig(storage_path)` instead."
594
+ "See the docs: https://docs.ray.io/en/latest/train/user-guides/"
595
+ "persistent-storage.html#setting-the-local-staging-directory"
596
+ )
597
+
598
+ if self.storage_path is None:
599
+ self.storage_path = DEFAULT_STORAGE_PATH
600
+
601
+ # TODO(justinvyu): [Deprecated]
602
+ ray_storage_uri: Optional[str] = os.environ.get("RAY_STORAGE")
603
+ if ray_storage_uri is not None:
604
+ logger.info(
605
+ "Using configured Ray Storage URI as the `storage_path`: "
606
+ f"{ray_storage_uri}"
607
+ )
608
+ warnings.warn(
609
+ "The `RAY_STORAGE` environment variable is deprecated. "
610
+ "Please use `RunConfig(storage_path)` instead.",
611
+ RayDeprecationWarning,
612
+ stacklevel=2,
613
+ )
614
+ self.storage_path = ray_storage_uri
615
+
616
+ if not self.failure_config:
617
+ self.failure_config = FailureConfig()
618
+
619
+ if not self.sync_config:
620
+ self.sync_config = SyncConfig()
621
+
622
+ if not self.checkpoint_config:
623
+ self.checkpoint_config = CheckpointConfig()
624
+
625
+ # Save the original verbose value to check for deprecations
626
+ self._verbose = self.verbose
627
+ if self.verbose is None:
628
+ # Default `verbose` value. For new output engine,
629
+ # this is AirVerbosity.DEFAULT.
630
+ # For old output engine, this is Verbosity.V3_TRIAL_DETAILS
631
+ # Todo (krfricke): Currently uses number to pass test_configs::test_repr
632
+ self.verbose = get_air_verbosity(AirVerbosity.DEFAULT) or 3
633
+
634
+ if isinstance(self.storage_path, Path):
635
+ self.storage_path = self.storage_path.as_posix()
636
+
637
+ def __repr__(self):
638
+ from ray.train import SyncConfig
639
+
640
+ return _repr_dataclass(
641
+ self,
642
+ default_values={
643
+ "failure_config": FailureConfig(),
644
+ "sync_config": SyncConfig(),
645
+ "checkpoint_config": CheckpointConfig(),
646
+ },
647
+ )
648
+
649
+ def _repr_html_(self) -> str:
650
+ reprs = []
651
+ if self.failure_config is not None:
652
+ reprs.append(
653
+ Template("title_data_mini.html.j2").render(
654
+ title="Failure Config", data=self.failure_config._repr_html_()
655
+ )
656
+ )
657
+ if self.sync_config is not None:
658
+ reprs.append(
659
+ Template("title_data_mini.html.j2").render(
660
+ title="Sync Config", data=self.sync_config._repr_html_()
661
+ )
662
+ )
663
+ if self.checkpoint_config is not None:
664
+ reprs.append(
665
+ Template("title_data_mini.html.j2").render(
666
+ title="Checkpoint Config", data=self.checkpoint_config._repr_html_()
667
+ )
668
+ )
669
+
670
+ # Create a divider between each displayed repr
671
+ subconfigs = [Template("divider.html.j2").render()] * (2 * len(reprs) - 1)
672
+ subconfigs[::2] = reprs
673
+
674
+ settings = Template("scrollableTable.html.j2").render(
675
+ table=tabulate(
676
+ {
677
+ "Name": self.name,
678
+ "Local results directory": self.local_dir,
679
+ "Verbosity": self.verbose,
680
+ "Log to file": self.log_to_file,
681
+ }.items(),
682
+ tablefmt="html",
683
+ headers=["Setting", "Value"],
684
+ showindex=False,
685
+ ),
686
+ max_height="300px",
687
+ )
688
+
689
+ return Template("title_data.html.j2").render(
690
+ title="RunConfig",
691
+ data=Template("run_config.html.j2").render(
692
+ subconfigs=subconfigs,
693
+ settings=settings,
694
+ ),
695
+ )
lib/python3.12/site-packages/ray/air/constants.py ADDED
@@ -0,0 +1,94 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # Key to denote the preprocessor in the checkpoint dict.
2
+ PREPROCESSOR_KEY = "_preprocessor"
3
+
4
+ # Key to denote the model in the checkpoint dict.
5
+ MODEL_KEY = "model"
6
+
7
+ # Key to denote which dataset is the evaluation dataset.
8
+ # Only used in trainers which do not support multiple
9
+ # evaluation datasets.
10
+ EVALUATION_DATASET_KEY = "evaluation"
11
+
12
+ # Key to denote which dataset is the training dataset.
13
+ # This is the dataset that the preprocessor is fit on.
14
+ TRAIN_DATASET_KEY = "train"
15
+
16
+ # Name to use for the column when representing tensors in table format.
17
+ TENSOR_COLUMN_NAME = "__value__"
18
+
19
+ # The maximum length of strings returned by `__repr__` for AIR objects constructed with
20
+ # default values.
21
+ MAX_REPR_LENGTH = int(80 * 1.5)
22
+
23
+ # Timeout used when putting exceptions raised by runner thread into the queue.
24
+ _ERROR_REPORT_TIMEOUT = 10
25
+
26
+ # Timeout when fetching new results after signaling the training function to continue.
27
+ _RESULT_FETCH_TIMEOUT = 0.2
28
+
29
+ # Timeout for fetching exceptions raised by the training function.
30
+ _ERROR_FETCH_TIMEOUT = 1
31
+
32
+ # The key used to identify whether we have already warned about ray.air.session
33
+ # functions being used outside of the session
34
+ SESSION_MISUSE_LOG_ONCE_KEY = "air_warn_session_misuse"
35
+
36
+ # Name of attribute in Checkpoint storing current Tune ID for restoring
37
+ # training with Ray Train
38
+ CHECKPOINT_ID_ATTR = "_current_checkpoint_id"
39
+
40
+ # Name of the marker dropped by the Trainable. If a worker detects
41
+ # the presence of the marker in the trial dir, it will use lazy
42
+ # checkpointing.
43
+ LAZY_CHECKPOINT_MARKER_FILE = ".lazy_checkpoint_marker"
44
+
45
+
46
+ # The timestamp of when the result is generated.
47
+ # Default to when the result is processed by tune.
48
+ TIMESTAMP = "timestamp"
49
+
50
+ # (Auto-filled) Time in seconds this iteration took to run.
51
+ # This may be overridden to override the system-computed time difference.
52
+ TIME_THIS_ITER_S = "time_this_iter_s"
53
+
54
+ # (Auto-filled) The index of this training iteration.
55
+ TRAINING_ITERATION = "training_iteration"
56
+
57
+ # File that stores parameters of the trial.
58
+ EXPR_PARAM_FILE = "params.json"
59
+
60
+ # Pickle File that stores parameters of the trial.
61
+ EXPR_PARAM_PICKLE_FILE = "params.pkl"
62
+
63
+ # File that stores the progress of the trial.
64
+ EXPR_PROGRESS_FILE = "progress.csv"
65
+
66
+ # File that stores results of the trial.
67
+ EXPR_RESULT_FILE = "result.json"
68
+
69
+ # File that stores the pickled error file
70
+ EXPR_ERROR_PICKLE_FILE = "error.pkl"
71
+
72
+ # File that stores the error file
73
+ EXPR_ERROR_FILE = "error.txt"
74
+
75
+ # File that stores the checkpoint metadata
76
+ CHECKPOINT_TUNE_METADATA_FILE = ".tune_metadata"
77
+
78
+ # ==================================================
79
+ # Environment Variables
80
+ # ==================================================
81
+
82
+ # Integer value which if set will copy files in reported AIR directory
83
+ # checkpoints instead of moving them (if worker is on the same node as Trainable)
84
+ COPY_DIRECTORY_CHECKPOINTS_INSTEAD_OF_MOVING_ENV = (
85
+ "TRAIN_COPY_DIRECTORY_CHECKPOINTS_INSTEAD_OF_MOVING"
86
+ )
87
+
88
+ # NOTE: When adding a new environment variable, please track it in this list.
89
+ # TODO(ml-team): Most env var constants should get moved here.
90
+ AIR_ENV_VARS = {
91
+ COPY_DIRECTORY_CHECKPOINTS_INSTEAD_OF_MOVING_ENV,
92
+ "RAY_AIR_FULL_TRACEBACKS",
93
+ "RAY_AIR_NEW_OUTPUT",
94
+ }
lib/python3.12/site-packages/ray/air/data_batch_type.py ADDED
@@ -0,0 +1,11 @@
 
 
 
 
 
 
 
 
 
 
 
 
1
+ from typing import TYPE_CHECKING, Dict, Union
2
+
3
+ if TYPE_CHECKING:
4
+ import numpy
5
+ import pandas # noqa: F401
6
+ import pyarrow
7
+
8
+ # TODO de-dup with ray.data.block.DataBatch
9
+ DataBatchType = Union[
10
+ "numpy.ndarray", "pyarrow.Table" "pandas.DataFrame", Dict[str, "numpy.ndarray"]
11
+ ]
lib/python3.12/site-packages/ray/air/examples/__init__.py ADDED
File without changes
lib/python3.12/site-packages/ray/air/examples/__pycache__/__init__.cpython-312.pyc ADDED
Binary file (195 Bytes). View file
 
lib/python3.12/site-packages/ray/air/execution/__init__.py ADDED
@@ -0,0 +1,12 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ from ray.air.execution.resources.fixed import FixedResourceManager
2
+ from ray.air.execution.resources.placement_group import PlacementGroupResourceManager
3
+ from ray.air.execution.resources.request import AcquiredResources, ResourceRequest
4
+ from ray.air.execution.resources.resource_manager import ResourceManager
5
+
6
+ __all__ = [
7
+ "ResourceRequest",
8
+ "AcquiredResources",
9
+ "ResourceManager",
10
+ "FixedResourceManager",
11
+ "PlacementGroupResourceManager",
12
+ ]
lib/python3.12/site-packages/ray/air/execution/__pycache__/__init__.cpython-312.pyc ADDED
Binary file (615 Bytes). View file
 
lib/python3.12/site-packages/ray/air/execution/_internal/__init__.py ADDED
@@ -0,0 +1,5 @@
 
 
 
 
 
 
1
+ from ray.air.execution._internal.actor_manager import RayActorManager
2
+ from ray.air.execution._internal.barrier import Barrier
3
+ from ray.air.execution._internal.tracked_actor import TrackedActor
4
+
5
+ __all__ = ["Barrier", "RayActorManager", "TrackedActor"]
lib/python3.12/site-packages/ray/air/execution/_internal/__pycache__/__init__.cpython-312.pyc ADDED
Binary file (476 Bytes). View file
 
lib/python3.12/site-packages/ray/air/execution/_internal/__pycache__/actor_manager.cpython-312.pyc ADDED
Binary file (34.4 kB). View file
 
lib/python3.12/site-packages/ray/air/execution/_internal/__pycache__/barrier.cpython-312.pyc ADDED
Binary file (4.03 kB). View file