Add files using upload-large-folder tool
Browse filesThis view is limited to 50 files because it contains too many changes. See raw diff
- lib/python3.12/site-packages/ray/air/__init__.py +21 -0
- lib/python3.12/site-packages/ray/air/__pycache__/__init__.cpython-312.pyc +0 -0
- lib/python3.12/site-packages/ray/air/__pycache__/config.cpython-312.pyc +0 -0
- lib/python3.12/site-packages/ray/air/__pycache__/constants.cpython-312.pyc +0 -0
- lib/python3.12/site-packages/ray/air/__pycache__/data_batch_type.cpython-312.pyc +0 -0
- lib/python3.12/site-packages/ray/air/__pycache__/result.cpython-312.pyc +0 -0
- lib/python3.12/site-packages/ray/air/__pycache__/session.cpython-312.pyc +0 -0
- lib/python3.12/site-packages/ray/air/_internal/__init__.py +0 -0
- lib/python3.12/site-packages/ray/air/_internal/__pycache__/__init__.cpython-312.pyc +0 -0
- lib/python3.12/site-packages/ray/air/_internal/__pycache__/config.cpython-312.pyc +0 -0
- lib/python3.12/site-packages/ray/air/_internal/__pycache__/filelock.cpython-312.pyc +0 -0
- lib/python3.12/site-packages/ray/air/_internal/__pycache__/json.cpython-312.pyc +0 -0
- lib/python3.12/site-packages/ray/air/_internal/__pycache__/mlflow.cpython-312.pyc +0 -0
- lib/python3.12/site-packages/ray/air/_internal/__pycache__/tensorflow_utils.cpython-312.pyc +0 -0
- lib/python3.12/site-packages/ray/air/_internal/__pycache__/torch_utils.cpython-312.pyc +0 -0
- lib/python3.12/site-packages/ray/air/_internal/__pycache__/uri_utils.cpython-312.pyc +0 -0
- lib/python3.12/site-packages/ray/air/_internal/__pycache__/usage.cpython-312.pyc +0 -0
- lib/python3.12/site-packages/ray/air/_internal/__pycache__/util.cpython-312.pyc +0 -0
- lib/python3.12/site-packages/ray/air/_internal/config.py +47 -0
- lib/python3.12/site-packages/ray/air/_internal/device_manager/__init__.py +92 -0
- lib/python3.12/site-packages/ray/air/_internal/device_manager/__pycache__/__init__.cpython-312.pyc +0 -0
- lib/python3.12/site-packages/ray/air/_internal/device_manager/__pycache__/cpu.cpython-312.pyc +0 -0
- lib/python3.12/site-packages/ray/air/_internal/device_manager/__pycache__/hpu.cpython-312.pyc +0 -0
- lib/python3.12/site-packages/ray/air/_internal/device_manager/__pycache__/npu.cpython-312.pyc +0 -0
- lib/python3.12/site-packages/ray/air/_internal/device_manager/__pycache__/nvidia_gpu.cpython-312.pyc +0 -0
- lib/python3.12/site-packages/ray/air/_internal/device_manager/__pycache__/torch_device_manager.cpython-312.pyc +0 -0
- lib/python3.12/site-packages/ray/air/_internal/device_manager/cpu.py +30 -0
- lib/python3.12/site-packages/ray/air/_internal/device_manager/hpu.py +50 -0
- lib/python3.12/site-packages/ray/air/_internal/device_manager/npu.py +104 -0
- lib/python3.12/site-packages/ray/air/_internal/device_manager/nvidia_gpu.py +79 -0
- lib/python3.12/site-packages/ray/air/_internal/device_manager/torch_device_manager.py +40 -0
- lib/python3.12/site-packages/ray/air/_internal/filelock.py +46 -0
- lib/python3.12/site-packages/ray/air/_internal/json.py +31 -0
- lib/python3.12/site-packages/ray/air/_internal/mlflow.py +342 -0
- lib/python3.12/site-packages/ray/air/_internal/tensorflow_utils.py +137 -0
- lib/python3.12/site-packages/ray/air/_internal/torch_utils.py +548 -0
- lib/python3.12/site-packages/ray/air/_internal/uri_utils.py +101 -0
- lib/python3.12/site-packages/ray/air/_internal/usage.py +274 -0
- lib/python3.12/site-packages/ray/air/_internal/util.py +125 -0
- lib/python3.12/site-packages/ray/air/config.py +695 -0
- lib/python3.12/site-packages/ray/air/constants.py +94 -0
- lib/python3.12/site-packages/ray/air/data_batch_type.py +11 -0
- lib/python3.12/site-packages/ray/air/examples/__init__.py +0 -0
- lib/python3.12/site-packages/ray/air/examples/__pycache__/__init__.cpython-312.pyc +0 -0
- lib/python3.12/site-packages/ray/air/execution/__init__.py +12 -0
- lib/python3.12/site-packages/ray/air/execution/__pycache__/__init__.cpython-312.pyc +0 -0
- lib/python3.12/site-packages/ray/air/execution/_internal/__init__.py +5 -0
- lib/python3.12/site-packages/ray/air/execution/_internal/__pycache__/__init__.cpython-312.pyc +0 -0
- lib/python3.12/site-packages/ray/air/execution/_internal/__pycache__/actor_manager.cpython-312.pyc +0 -0
- 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¶m=1")
|
| 13 |
+
>>> s3_uri
|
| 14 |
+
URI<s3://bucket/a?scheme=http¶m=1>
|
| 15 |
+
>>> str(s3_uri / "b" / "c")
|
| 16 |
+
's3://bucket/a/b/c?scheme=http¶m=1'
|
| 17 |
+
>>> str(s3_uri.parent)
|
| 18 |
+
's3://bucket?scheme=http¶m=1'
|
| 19 |
+
>>> str(s3_uri)
|
| 20 |
+
's3://bucket/a?scheme=http¶m=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
|
|
|