chore: import upstream snapshot with attribution

This commit is contained in:
wehub-resource-sync
2026-07-13 13:17:40 +08:00
commit f1825c8ceb
10096 changed files with 2364182 additions and 0 deletions
View File
+53
View File
@@ -0,0 +1,53 @@
from typing import Any, Dict, List, Optional
from ray.train import Checkpoint
from ray.train.v2._internal.execution.context import TrainRunContext
from ray.util.annotations import DeveloperAPI
@DeveloperAPI
class RayTrainCallback:
"""Base Ray Train callback interface."""
pass
@DeveloperAPI
class UserCallback(RayTrainCallback):
"""Callback interface for custom user-defined callbacks to handling events
during training.
This callback is called on the Ray Train controller process, not on the
worker processes.
"""
def after_report(
self,
run_context: TrainRunContext,
metrics: List[Dict[str, Any]],
checkpoint: Optional[Checkpoint],
):
"""Called after all workers have reported a metric + checkpoint
via `ray.train.report`.
Args:
run_context: The `TrainRunContext` for the current training run.
metrics: A list of metric dictionaries reported by workers,
where metrics[i] is the metrics dict reported by worker i.
checkpoint: A Checkpoint object that has been persisted to
storage. This is None if no workers reported a checkpoint
(e.g. `ray.train.report(metrics, checkpoint=None)`).
"""
pass
def after_exception(
self, run_context: TrainRunContext, worker_exceptions: Dict[int, Exception]
):
"""Called after one or more workers have raised an exception.
Args:
run_context: The `TrainRunContext` for the current training run.
worker_exceptions: A dict from worker world rank to the exception
raised by that worker.
"""
pass
+527
View File
@@ -0,0 +1,527 @@
import logging
from dataclasses import dataclass
from functools import cached_property
from pathlib import Path
from typing import TYPE_CHECKING, Dict, List, Literal, Optional, Tuple, Union
import pyarrow.fs
from ray.air.config import (
FailureConfig as FailureConfigV1,
ScalingConfig as ScalingConfigV1,
)
from ray.runtime_env import RuntimeEnv
from ray.train.v2._internal.constants import _DEPRECATED
from ray.train.v2._internal.execution.storage import StorageContext
from ray.train.v2._internal.migration_utils import (
FAIL_FAST_DEPRECATION_MESSAGE,
TRAINER_RESOURCES_DEPRECATION_MESSAGE,
)
from ray.train.v2._internal.util import date_str
from ray.util.annotations import PublicAPI
from ray.util.tpu import get_tpu_worker_resources
if TYPE_CHECKING:
from ray.train import UserCallback
logger = logging.getLogger(__name__)
@dataclass
class ScalingConfig(ScalingConfigV1):
"""Configuration for scaling training.
Args:
num_workers: The number of workers (Ray actors) to launch.
Each worker will reserve 1 CPU by default. The number of CPUs
reserved by each worker can be overridden with the
``resources_per_worker`` argument. If the number of workers is 0,
the training function will run in local mode, meaning the training
function runs in the same process. To enable elasticity, provide a
``(min_workers, max_workers)`` tuple of ints.
elastic_resize_monitor_interval_s: While the worker group is healthy,
consider resizing the worker group every
``elastic_resize_monitor_interval_s`` seconds.
use_gpu: If True, training will be done on GPUs (1 per worker).
Defaults to False. The number of GPUs reserved by each
worker can be overridden with the ``resources_per_worker``
argument.
resources_per_worker: If specified, the resources
defined in this Dict is reserved for each worker.
Define the ``"CPU"`` and ``"GPU"`` keys (case-sensitive) to
override the number of CPU or GPUs used by each worker.
Accepts the same resource keys that Ray uses for scheduling tasks
and actors (see :ref:`Resources <core-resources>`):
- ``"CPU"``: number of logical CPUs per worker.
- ``"GPU"``: number of logical GPUs per worker. Prefer setting
``use_gpu=True`` (which reserves 1 GPU per worker) and only
override this key when you need a different per-worker count.
- ``"TPU"``: number of logical TPUs per worker, when ``use_tpu=True``.
- ``"memory"``: heap memory reserved per worker, in bytes
(for example, ``"memory": 1e9`` reserves 1 GB per worker).
- Any :ref:`custom resource <custom-resources>` name configured on
your cluster (for example, ``"special_hardware": 1``).
Keys are case-sensitive: use ``"CPU"``, ``"GPU"``, and ``"TPU"``
(uppercase), and ``"memory"`` (lowercase).
placement_strategy: The placement strategy to use for the
placement group of the Ray actors. See :ref:`Placement Group
Strategies <pgroup-strategy>` for the possible options.
label_selector: A list of label selectors for Ray Train worker placement.
If a single label selector is provided, it will be applied to all Ray Train workers.
If a list is provided, it must be the same length as the max number of Ray Train workers.
accelerator_type: [Experimental] If specified, Ray Train will launch the
training coordinator and workers on the nodes with the specified type
of accelerators.
See :ref:`the available accelerator types <accelerator_types>`.
Ensure that your cluster has instances with the specified accelerator type
or is able to autoscale to fulfill the request. This field is required
when `use_tpu` is True and `num_workers` is greater than 1.
use_tpu: [Experimental] If True, training will be done on TPUs (1 TPU VM
per worker). Defaults to False. The number of TPUs reserved by each
worker can be overridden with the ``resources_per_worker``
argument. This arg enables SPMD execution of the training workload.
topology: [Experimental] If specified, Ray Train will launch the training
coordinator and workers on nodes with the specified topology. Topology is
auto-detected for TPUs and added as Ray node labels. This arg enables
SPMD execution of the training workload. This field is required
when `use_tpu` is True and `num_workers` is greater than 1.
"""
num_workers: Union[int, Tuple[int, int]] = 1
trainer_resources: Optional[dict] = None
label_selector: Optional[Union[Dict[str, str], List[Dict[str, str]]]] = None
# Accelerator specific fields.
use_tpu: Union[bool] = False
topology: Optional[str] = None
# Elasticity specific fields.
elastic_resize_monitor_interval_s: float = 60.0
def __post_init__(self):
if self.trainer_resources is not None:
raise DeprecationWarning(TRAINER_RESOURCES_DEPRECATION_MESSAGE)
is_fixed = isinstance(self.num_workers, int)
is_elastic = (
isinstance(self.num_workers, tuple)
and len(self.num_workers) == 2
and all(isinstance(x, int) for x in self.num_workers)
)
if not (is_fixed or is_elastic):
raise ValueError(
"ScalingConfig(num_workers) must be an int or a tuple of two ints."
)
if self.elastic_resize_monitor_interval_s < 0:
raise ValueError(
"ScalingConfig(elastic_resize_monitor_interval_s) must be non-negative."
)
if self.min_workers < 0:
raise ValueError(
f"Invalid ScalingConfig(num_workers={self.num_workers}): "
"Number of workers cannot be negative."
)
if self.min_workers > self.max_workers:
raise ValueError(
f"Invalid ScalingConfig(num_workers={self.num_workers}): "
f"min_workers={self.min_workers} must be <= max_workers={self.max_workers}."
)
self._validate_tpu_config()
if (
isinstance(self.label_selector, list)
and len(self.label_selector) != self.max_workers
):
raise ValueError(
"If `label_selector` is a list, it must be the same length as "
"`max_workers` (or `num_workers` when fixed)."
)
if self.num_workers == 0:
logger.info(
"Running in local mode. The training function will run in the same process. "
"If you are using it and running into issues please file a report at "
"https://github.com/ray-project/ray/issues."
)
super().__post_init__()
@property
def elasticity_enabled(self) -> bool:
return isinstance(self.num_workers, tuple)
@property
def min_workers(self) -> int:
return (
self.num_workers
if isinstance(self.num_workers, int)
else self.num_workers[0]
)
@property
def max_workers(self) -> int:
return (
self.num_workers
if isinstance(self.num_workers, int)
else self.num_workers[1]
)
def _label_selector_per_worker(
self, num_workers: int
) -> Optional[List[Dict[str, str]]]:
"""Normalize ``label_selector`` into a per-worker list of length ``num_workers``.
- ``None`` -> ``None`` (no constraint; downstream consumers — the
placement-group path and the autoscaling coordinator — both
accept ``None`` and treat it as "no label requirement").
- ``Dict`` -> the same dict replicated for each worker
- ``List`` -> the first ``num_workers`` entries (validated to be
``max_workers`` long in ``__post_init__``)
"""
if isinstance(self.label_selector, list):
return [s.copy() for s in self.label_selector[:num_workers]]
if isinstance(self.label_selector, dict):
return [self.label_selector.copy() for _ in range(num_workers)]
return None
@property
def total_resources(self):
"""Map of total resources required for training.
For elastic configs, this returns an upper bound based on max_workers.
"""
total_resource_map = dict(self._trainer_resources_not_none)
for k, value in self._resources_per_worker_not_none.items():
total_resource_map[k] = total_resource_map.get(k, 0.0) + (
value * self.max_workers
)
return total_resource_map
def _validate_tpu_config(self):
"""Validates configuration specifically for TPU usage."""
max_workers = self.max_workers
if self.use_gpu and self.use_tpu:
raise ValueError("Cannot specify both `use_gpu=True` and `use_tpu=True`.")
if not self.use_tpu:
if self.num_tpus_per_worker > 0:
raise ValueError(
"`use_tpu` is False but `TPU` was found in "
"`resources_per_worker`. Either set `use_tpu` to True or "
"remove `TPU` from `resources_per_worker."
)
# If not using TPU, we are done validating TPU-specific logic.
return
if self.num_tpus_per_worker == 0:
raise ValueError(
"`use_tpu` is True but `TPU` is set to 0 in "
"`resources_per_worker`. Either set `use_tpu` to False or "
"request a positive number of `TPU` in "
"`resources_per_worker."
)
if max_workers > 1:
if not self.topology:
raise ValueError(
"`topology` must be specified in ScalingConfig when `use_tpu=True` "
" and `num_workers` > 1."
)
if not self.accelerator_type:
raise ValueError(
"`accelerator_type` must be specified in ScalingConfig when "
"`use_tpu=True` and `num_workers` > 1."
)
if self.label_selector:
raise ValueError(
"Cannot set `label_selector` when `use_tpu=True` because "
"Ray Train automatically reserves a TPU slice with a predefined label."
)
# Validate TPU resources when both topology and accelerator type are specified.
if self.topology and self.accelerator_type:
try:
workers_per_slice, tpu_resources = get_tpu_worker_resources(
topology=self.topology,
accelerator_type=self.accelerator_type,
resources_per_unit=self.resources_per_worker,
num_slices=1,
)
except Exception as e:
raise ValueError(
f"Could not parse TPU topology details for "
f"type={self.accelerator_type}, "
f"topology={self.topology}. Error: {e}"
)
if workers_per_slice > 0 and max_workers % workers_per_slice != 0:
raise ValueError(
f"The configured `num_workers` ({self.num_workers}) must be a "
f"multiple of {workers_per_slice} for the specified topology ({self.topology}). "
"TPU workloads typically require symmetric resource distribution "
"across all slices to function correctly."
)
if workers_per_slice > 0 and self.min_workers % workers_per_slice != 0:
raise ValueError(
f"The configured `min_workers` ({self.min_workers}) must be a "
f"multiple of {workers_per_slice} for the specified topology ({self.topology}). "
"TPU workloads typically require symmetric resource distribution "
"across all slices to function correctly."
)
if self.resources_per_worker is None:
self.resources_per_worker = tpu_resources
@property
def _resources_per_worker_not_none(self):
if self.resources_per_worker is None:
if self.use_tpu:
return {"TPU": 1}
return super()._resources_per_worker_not_none
@property
def _trainer_resources_not_none(self):
return {}
@property
def num_tpus_per_worker(self):
"""The number of TPUs to set per worker."""
return self._resources_per_worker_not_none.get("TPU", 0)
@dataclass
@PublicAPI(stability="stable")
class CheckpointConfig:
"""Configuration for checkpointing.
Default behavior is to persist all checkpoints reported with
:meth:`ray.train.report` to disk. If ``num_to_keep`` is set,
the default retention policy is to keep the most recent checkpoints.
Args:
num_to_keep: The maximum number of checkpoints to keep.
If you report more checkpoints than this, the oldest
(or lowest-scoring, if ``checkpoint_score_attribute`` is set)
checkpoint will be deleted.
If this is ``None`` then all checkpoints will be kept. Must be >= 1.
checkpoint_score_attribute: The attribute that will be used to
score checkpoints to determine which checkpoints should be kept.
This attribute must be a key from the metrics dictionary
attached to the checkpoint. This attribute must have a numerical value.
checkpoint_score_order: Either "max" or "min".
If "max"/"min", then checkpoints with highest/lowest values of
the ``checkpoint_score_attribute`` will be kept. Defaults to "max".
checkpoint_frequency: [Deprecated]
checkpoint_at_end: [Deprecated]
"""
num_to_keep: Optional[int] = None
checkpoint_score_attribute: Optional[str] = None
checkpoint_score_order: Literal["max", "min"] = "max"
checkpoint_frequency: Union[Optional[int], Literal[_DEPRECATED]] = _DEPRECATED
checkpoint_at_end: Union[Optional[bool], Literal[_DEPRECATED]] = _DEPRECATED
def __post_init__(self):
if self.checkpoint_frequency != _DEPRECATED:
raise DeprecationWarning(
"`checkpoint_frequency` is deprecated since it does not "
"apply to user-defined training functions. "
"Please remove this argument from your CheckpointConfig."
)
if self.checkpoint_at_end != _DEPRECATED:
raise DeprecationWarning(
"`checkpoint_at_end` is deprecated since it does not "
"apply to user-defined training functions. "
"Please remove this argument from your CheckpointConfig."
)
if self.num_to_keep is not None and self.num_to_keep <= 0:
raise ValueError(
f"Received invalid num_to_keep: {self.num_to_keep}. "
"Must be None or an integer >= 1."
)
if self.checkpoint_score_order not in ("max", "min"):
raise ValueError(
f"Received invalid checkpoint_score_order: {self.checkpoint_score_order}. "
"Must be 'max' or 'min'."
)
@dataclass
class FailureConfig(FailureConfigV1):
"""Configuration related to failure handling of each training run.
Args:
max_failures: Tries to recover a run from training worker errors at least this many times.
Will recover from the latest checkpoint if present.
Setting to -1 will lead to infinite recovery retries.
Setting to 0 will disable retries. Defaults to 0.
controller_failure_limit: [DeveloperAPI] The maximum number of controller failures to tolerate.
Setting to -1 will lead to infinite controller retries.
Setting to 0 will disable controller retries. Defaults to -1.
"""
fail_fast: Union[bool, str] = _DEPRECATED
controller_failure_limit: int = -1
def __post_init__(self):
if self.fail_fast != _DEPRECATED:
raise DeprecationWarning(FAIL_FAST_DEPRECATION_MESSAGE)
@PublicAPI(stability="alpha")
@dataclass
class LoggingConfig:
"""Configuration for Ray Train's logging behavior.
Args:
log_level: The log level for Ray Train's internal ``ray.train`` logs
on console output and application-level log files. Accepts standard
Python logging level names. Defaults to ``"INFO"``.
System-level log files always capture all levels (DEBUG and above),
and the ``ray`` logger (set by ``ray.init()``) and root logger
are unaffected.
"""
log_level: str = "INFO"
def __post_init__(self):
valid_levels = set(logging._nameToLevel)
if (
not isinstance(self.log_level, str)
or self.log_level.upper() not in valid_levels
):
raise ValueError(
f"Invalid log_level: {self.log_level!r}. "
f"Must be one of: {', '.join(repr(x) for x in sorted(valid_levels))}."
)
self.log_level = self.log_level.upper()
@dataclass
@PublicAPI(stability="stable")
class RunConfig:
"""Runtime configuration for training runs.
Args:
name: Name of the trial or experiment. If not provided, will be deduced
from the Trainable.
storage_path: Path where all results and checkpoints are persisted.
Can be a local directory or a destination on cloud storage.
For multi-node training/tuning runs, this must be set to a
shared storage location (e.g., S3, NFS).
This defaults to the local ``~/ray_results`` directory.
storage_filesystem: A custom filesystem to use for storage.
If this is provided, `storage_path` should be a path with its
prefix stripped (e.g., `s3://bucket/path` -> `bucket/path`).
failure_config: Failure mode configuration.
checkpoint_config: Checkpointing configuration.
callbacks: [DeveloperAPI] A list of callbacks that the Ray Train controller
will invoke during training.
worker_runtime_env: [DeveloperAPI] Runtime environment configuration
for all Ray Train worker actors.
logging_config: Configuration for Ray Train's logging behavior.
See :class:`LoggingConfig` for details.
"""
name: Optional[str] = None
storage_path: Optional[str] = None
storage_filesystem: Optional[pyarrow.fs.FileSystem] = None
failure_config: Optional[FailureConfig] = None
checkpoint_config: Optional[CheckpointConfig] = None
callbacks: Optional[List["UserCallback"]] = None
worker_runtime_env: Optional[Union[dict, RuntimeEnv]] = None
logging_config: Optional[LoggingConfig] = None
sync_config: str = _DEPRECATED
verbose: str = _DEPRECATED
stop: str = _DEPRECATED
progress_reporter: str = _DEPRECATED
log_to_file: str = _DEPRECATED
def __post_init__(self):
from ray.train.constants import DEFAULT_STORAGE_PATH
if self.storage_path is None:
self.storage_path = DEFAULT_STORAGE_PATH
if not self.failure_config:
self.failure_config = FailureConfig()
if not self.checkpoint_config:
self.checkpoint_config = CheckpointConfig()
if not self.logging_config:
self.logging_config = LoggingConfig()
if isinstance(self.storage_path, Path):
self.storage_path = self.storage_path.as_posix()
run_config_deprecation_message = (
"`RunConfig({})` is deprecated. This configuration was a "
"Ray Tune API that did not support Ray Train usage well, "
"so we are dropping support going forward. "
"If you heavily rely on these configurations, "
"you can run Ray Train as a single Ray Tune trial. "
"See this issue for more context: "
"https://github.com/ray-project/ray/issues/49454"
)
unsupported_params = [
"sync_config",
"verbose",
"stop",
"progress_reporter",
"log_to_file",
]
for param in unsupported_params:
if getattr(self, param) != _DEPRECATED:
raise DeprecationWarning(run_config_deprecation_message.format(param))
if not self.name:
self.name = f"ray_train_run-{date_str()}"
self.callbacks = self.callbacks or []
self.worker_runtime_env = self.worker_runtime_env or {}
from ray.train.v2.api.callback import RayTrainCallback
if not all(isinstance(cb, RayTrainCallback) for cb in self.callbacks):
raise ValueError(
"All callbacks must be instances of `ray.train.UserCallback`. "
"Passing in a Ray Tune callback is no longer supported. "
"See this issue for more context: "
"https://github.com/ray-project/ray/issues/49454"
)
if not isinstance(self.checkpoint_config, CheckpointConfig):
raise ValueError(
f"Invalid `CheckpointConfig` type: {self.checkpoint_config.__class__}. "
"Use `ray.train.CheckpointConfig` instead. "
"See this issue for more context: "
"https://github.com/ray-project/ray/issues/49454"
)
if not isinstance(self.failure_config, FailureConfig):
raise ValueError(
f"Invalid `FailureConfig` type: {self.failure_config.__class__}. "
"Use `ray.train.FailureConfig` instead. "
"See this issue for more context: "
"https://github.com/ray-project/ray/issues/49454"
)
@cached_property
def storage_context(self) -> StorageContext:
return StorageContext(
storage_path=self.storage_path,
experiment_dir_name=self.name,
storage_filesystem=self.storage_filesystem,
)
+261
View File
@@ -0,0 +1,261 @@
from abc import ABC, abstractmethod
from typing import Any, Dict
from ray.train.v2._internal.execution.context import (
get_train_context as get_internal_train_context,
)
from ray.util.annotations import Deprecated, DeveloperAPI, PublicAPI
@PublicAPI(stability="stable")
class TrainContext(ABC):
"""Abstract interface for training context."""
@Deprecated
def get_metadata(self) -> Dict[str, Any]:
"""[Deprecated] User metadata dict passed to the Trainer constructor."""
from ray.train.context import _GET_METADATA_DEPRECATION_MESSAGE
raise DeprecationWarning(_GET_METADATA_DEPRECATION_MESSAGE)
@Deprecated
def get_trial_name(self) -> str:
"""[Deprecated] Trial name for the corresponding trial."""
from ray.train.context import _TUNE_SPECIFIC_CONTEXT_DEPRECATION_MESSAGE
raise DeprecationWarning(
_TUNE_SPECIFIC_CONTEXT_DEPRECATION_MESSAGE.format("get_trial_name")
)
@Deprecated
def get_trial_id(self) -> str:
"""[Deprecated] Trial id for the corresponding trial."""
from ray.train.context import _TUNE_SPECIFIC_CONTEXT_DEPRECATION_MESSAGE
raise DeprecationWarning(
_TUNE_SPECIFIC_CONTEXT_DEPRECATION_MESSAGE.format("get_trial_id")
)
@Deprecated
def get_trial_resources(self):
"""[Deprecated] Trial resources for the corresponding trial."""
from ray.train.context import _TUNE_SPECIFIC_CONTEXT_DEPRECATION_MESSAGE
raise DeprecationWarning(
_TUNE_SPECIFIC_CONTEXT_DEPRECATION_MESSAGE.format("get_trial_resources")
)
@Deprecated
def get_trial_dir(self) -> str:
"""[Deprecated] Log directory corresponding to the trial directory for a Tune session.
This is deprecated for Ray Train and should no longer be called in Ray Train workers.
If this directory is needed, please pass it into the `train_loop_config` directly.
"""
from ray.train.context import _TUNE_SPECIFIC_CONTEXT_DEPRECATION_MESSAGE
raise DeprecationWarning(
_TUNE_SPECIFIC_CONTEXT_DEPRECATION_MESSAGE.format("get_trial_dir")
)
@abstractmethod
def get_experiment_name(self) -> str:
"""Experiment name for the corresponding trial."""
pass
@abstractmethod
def get_world_size(self) -> int:
"""Get the current world size (i.e. total number of workers) for this run.
.. testcode::
import ray.train
from ray.train.torch import TorchTrainer
NUM_WORKERS = 2
def train_fn_per_worker(config):
assert ray.train.get_context().get_world_size() == NUM_WORKERS
trainer = TorchTrainer(
train_fn_per_worker,
scaling_config=ray.train.ScalingConfig(num_workers=NUM_WORKERS),
)
trainer.fit()
"""
pass
@abstractmethod
def get_world_rank(self) -> int:
"""Get the world rank of this worker.
.. testcode::
import ray.train
from ray.train.torch import TorchTrainer
def train_fn_per_worker(config):
if ray.train.get_context().get_world_rank() == 0:
print("Worker 0")
trainer = TorchTrainer(
train_fn_per_worker,
scaling_config=ray.train.ScalingConfig(num_workers=2),
)
trainer.fit()
"""
pass
@abstractmethod
def get_local_rank(self) -> int:
"""Get the local rank of this worker (rank of the worker on its node).
.. testcode::
import ray.train
from ray.train.torch import TorchTrainer
def train_fn_per_worker(config):
if ray.train.get_context().get_local_rank() == 0:
print("Local rank 0 worker")
trainer = TorchTrainer(
train_fn_per_worker,
scaling_config=ray.train.ScalingConfig(num_workers=2),
)
trainer.fit()
"""
pass
@abstractmethod
def get_local_world_size(self) -> int:
"""Get the local world size of this node (i.e. number of workers on this node).
Example:
.. testcode::
import ray.train
from ray.train.torch import TorchTrainer
def train_fn_per_worker():
print(ray.train.get_context().get_local_world_size())
trainer = TorchTrainer(
train_fn_per_worker,
scaling_config=ray.train.ScalingConfig(num_workers=2),
)
trainer.fit()
Returns:
The number of workers running on this node.
"""
pass
@abstractmethod
def get_node_rank(self) -> int:
"""Get the rank of this node.
Example:
.. testcode::
import ray.train
from ray.train.torch import TorchTrainer
def train_fn_per_worker():
print(ray.train.get_context().get_node_rank())
trainer = TorchTrainer(
train_fn_per_worker,
scaling_config=ray.train.ScalingConfig(num_workers=1),
)
trainer.fit()
Returns:
The rank of this node among the nodes participating in training.
"""
pass
@DeveloperAPI
@abstractmethod
def get_storage(self):
"""Returns the :class:`~ray.train._internal.storage.StorageContext` storage
context which gives advanced access to the filesystem and paths
configured through `RunConfig`.
NOTE: This is a DeveloperAPI, and the `StorageContext` interface may change
without notice between minor versions.
"""
pass
@DeveloperAPI
class DistributedTrainContext(TrainContext):
"""Implementation of TrainContext for distributed mode."""
def get_experiment_name(self) -> str:
return get_internal_train_context().get_experiment_name()
def get_world_size(self) -> int:
return get_internal_train_context().get_world_size()
def get_world_rank(self) -> int:
return get_internal_train_context().get_world_rank()
def get_local_rank(self) -> int:
return get_internal_train_context().get_local_rank()
def get_local_world_size(self) -> int:
return get_internal_train_context().get_local_world_size()
def get_node_rank(self) -> int:
return get_internal_train_context().get_node_rank()
def get_storage(self):
return get_internal_train_context().get_storage()
@DeveloperAPI
class LocalTrainContext(TrainContext):
"""Implementation of TrainContext for local mode."""
def __init__(
self,
experiment_name: str,
world_size: int = 1,
world_rank: int = 0,
local_rank: int = 0,
local_world_size: int = 1,
node_rank: int = 0,
):
self.experiment_name = experiment_name
self.world_size = world_size
self.world_rank = world_rank
self.local_rank = local_rank
self.local_world_size = local_world_size
self.node_rank = node_rank
def get_experiment_name(self) -> str:
return self.experiment_name
def get_world_size(self) -> int:
return self.world_size
def get_world_rank(self) -> int:
return self.world_rank
def get_local_rank(self) -> int:
return self.local_rank
def get_local_world_size(self) -> int:
return self.local_world_size
def get_node_rank(self) -> int:
return self.node_rank
def get_storage(self):
raise NotImplementedError("Local storage context not yet implemented. ")
@@ -0,0 +1,335 @@
import logging
import signal
import sys
import threading
from typing import Any, Callable, Dict, List, Optional, Union
import ray
from ray._common.constants import RAY_WARN_BLOCKING_GET_INSIDE_ASYNC_ENV_VAR
from ray._common.usage import usage_lib
from ray._private.ray_constants import env_bool
from ray.actor import ActorHandle
from ray.air._internal.usage import tag_train_v2_trainer
from ray.train import (
BackendConfig,
Checkpoint,
DataConfig,
Result,
RunConfig,
ScalingConfig,
)
from ray.train.base_trainer import (
_RESUME_FROM_CHECKPOINT_DEPRECATION_WARNING,
_TRAINER_RESTORE_DEPRECATION_WARNING,
)
from ray.train.constants import RAY_CHDIR_TO_TRIAL_DIR, RAY_TRAIN_ENABLE_STATE_TRACKING
from ray.train.context import _GET_METADATA_DEPRECATION_MESSAGE
from ray.train.v2._internal.callbacks import (
AcceleratorSetupCallback,
BackendSetupCallback,
DatasetsCallback,
WorkingDirectorySetupCallback,
)
from ray.train.v2._internal.callbacks.env_callback import _initialize_env_callbacks
from ray.train.v2._internal.callbacks.metrics import (
ControllerMetricsCallback,
WorkerMetricsCallback,
)
from ray.train.v2._internal.callbacks.placement_group_callback import (
PlacementGroupCleanerCallback,
)
from ray.train.v2._internal.callbacks.state_manager import StateManagerCallback
from ray.train.v2._internal.callbacks.user_callback import UserCallbackHandler
from ray.train.v2._internal.constants import (
DEFAULT_RAY_WARN_BLOCKING_GET_INSIDE_ASYNC_VALUE,
METRICS_ENABLED_ENV_VAR,
V2_ENABLED_ENV_VAR,
get_env_vars_to_propagate,
is_v2_enabled,
)
from ray.train.v2._internal.data_integration.interfaces import GenDataset
from ray.train.v2._internal.execution.callback import RayTrainCallback
from ray.train.v2._internal.execution.context import TrainRunContext
from ray.train.v2._internal.execution.controller import TrainController
from ray.train.v2._internal.execution.failure_handling import create_failure_policy
from ray.train.v2._internal.execution.local_mode.utils import LocalController
from ray.train.v2._internal.execution.scaling_policy import create_scaling_policy
from ray.train.v2._internal.util import ObjectRefWrapper, construct_train_func
from ray.train.v2.api.callback import UserCallback
from ray.train.v2.api.validation_config import ValidationConfig
from ray.util.annotations import Deprecated, DeveloperAPI
logger = logging.getLogger(__name__)
@DeveloperAPI
class DataParallelTrainer:
"""Base class for distributed data parallel training on Ray.
This class supports the SPMD parallelization pattern, where a single
training function is executed in parallel across multiple workers,
and different shards of data are processed by each worker.
"""
def __init__(
self,
train_loop_per_worker: Union[Callable[[], Any], Callable[[Dict], Any]],
*,
train_loop_config: Optional[Dict] = None,
backend_config: Optional[BackendConfig] = None,
scaling_config: Optional[ScalingConfig] = None,
run_config: Optional[RunConfig] = None,
datasets: Optional[Dict[str, GenDataset]] = None,
dataset_config: Optional[DataConfig] = None,
# TODO: [Deprecated] Remove in future release
resume_from_checkpoint: Optional[Checkpoint] = None,
metadata: Optional[Dict[str, Any]] = None,
validation_config: Optional[ValidationConfig] = None,
):
self.run_config = run_config or RunConfig()
self.train_loop_per_worker = train_loop_per_worker
self.validation_config = validation_config
self.train_loop_config = train_loop_config
self.scaling_config = scaling_config or ScalingConfig()
self.backend_config = backend_config or BackendConfig()
self.datasets = datasets or {}
self.data_config = dataset_config or DataConfig()
self.running_in_local_mode = self.scaling_config.num_workers == 0
self.train_run_context = TrainRunContext(
run_config=self.run_config,
train_loop_config=self.train_loop_config,
scaling_config=self.scaling_config,
backend_config=self.backend_config,
dataset_config=self.data_config,
)
if resume_from_checkpoint is not None:
raise DeprecationWarning(_RESUME_FROM_CHECKPOINT_DEPRECATION_WARNING)
if metadata is not None:
raise DeprecationWarning(_GET_METADATA_DEPRECATION_MESSAGE)
self._validate_configs()
usage_lib.record_library_usage("train")
tag_train_v2_trainer(self)
if self.scaling_config.elasticity_enabled:
usage_lib.record_extra_usage_tag(
usage_lib.TagKey.TRAIN_ELASTICITY_ENABLED, "1"
)
def _validate_configs(self):
if not is_v2_enabled():
raise ValueError(
f"Ray Train V2 must be enabled with `{V2_ENABLED_ENV_VAR}=1` "
"when using this V2 Trainer API."
)
from ray.train.v2.api.config import (
RunConfig as RunConfigV2,
ScalingConfig as ScalingConfigV2,
)
if not isinstance(self.run_config, RunConfigV2):
raise ValueError(
f"Invalid `RunConfig` type: {self.run_config.__class__}. "
"Use `ray.train.RunConfig` instead. "
"See this issue for more context: "
"https://github.com/ray-project/ray/issues/49454"
)
if not isinstance(self.scaling_config, ScalingConfigV2):
raise ValueError(
f"Invalid `ScalingConfig` type: {self.scaling_config.__class__}. "
"Use `ray.train.ScalingConfig` instead. "
"See this issue for more context: "
"https://github.com/ray-project/ray/issues/49454"
)
def _get_train_func(self) -> Callable[[], Any]:
return construct_train_func(
self.train_loop_per_worker,
config=self.train_loop_config,
train_func_context=self.backend_config.train_func_context,
fn_arg_name="train_loop_per_worker",
)
def fit(self) -> Result:
"""Launches the Ray Train controller to run training on workers.
Returns:
A Result object containing the training result.
Raises:
ray.train.TrainingFailedError: This is a union of the ControllerError and WorkerGroupError.
This returns a :class:`ray.train.ControllerError` if internal Ray Train controller logic
encounters a non-retryable error or reaches the controller failure limit configured in `FailureConfig`.
This returns a :class:`ray.train.WorkerGroupError` if one or more workers fail during
training and reaches the worker group failure limit configured in `FailureConfig(max_failures)`.
"""
train_fn = self._get_train_func()
if self.running_in_local_mode:
return self._initialize_and_run_local_controller(train_fn)
else:
train_fn_ref = ObjectRefWrapper(train_fn)
result = self._initialize_and_run_controller(
train_fn_ref=train_fn_ref,
scaling_policy=create_scaling_policy(self.scaling_config),
failure_policy=create_failure_policy(self.run_config.failure_config),
train_run_context=self.train_run_context,
callbacks=self._create_default_callbacks(),
validation_config=self.validation_config,
)
if result.error:
# NOTE: If the training run errored out, raise an error back to the
# user's driver script.
# For example, if the Train `FailurePolicy` runs out of retries,
# and one of the workers errors. The controller will exit, and
# the error will be raised here.
raise result.error
return result
def _get_local_controller(self) -> LocalController:
return LocalController(
experiment_name=self.run_config.name,
datasets=self.datasets,
)
def _create_default_callbacks(self) -> List[RayTrainCallback]:
# Initialize callbacks from environment variable
callbacks = _initialize_env_callbacks()
accelerator_setup_callback = AcceleratorSetupCallback(
self.backend_config, self.scaling_config
)
backend_setup_callback = BackendSetupCallback(self.backend_config)
datasets_callback = DatasetsCallback(
train_run_context=self.train_run_context,
datasets=self.datasets,
)
placement_group_cleaner_callback = PlacementGroupCleanerCallback()
callbacks.extend(
[
accelerator_setup_callback,
backend_setup_callback,
placement_group_cleaner_callback,
datasets_callback,
]
)
if env_bool(RAY_CHDIR_TO_TRIAL_DIR, True):
working_directory_setup_callback = WorkingDirectorySetupCallback()
callbacks.append(working_directory_setup_callback)
if env_bool(METRICS_ENABLED_ENV_VAR, True):
callbacks.append(ControllerMetricsCallback())
callbacks.append(WorkerMetricsCallback(self.train_run_context))
if env_bool(RAY_TRAIN_ENABLE_STATE_TRACKING, False):
callbacks.append(StateManagerCallback(datasets=self.datasets))
run_config_callbacks = (
self.run_config.callbacks if self.run_config.callbacks is not None else []
)
# Add internal callback that invokes all user-defined callbacks.
user_callbacks = [
cb for cb in run_config_callbacks if isinstance(cb, UserCallback)
]
callbacks.append(
UserCallbackHandler(
user_callbacks=user_callbacks, train_run_context=self.train_run_context
)
)
# Append all other callbacks to the full list. This allows custom workarounds
# built on top of internal callbacks to work.
callbacks.extend(
[cb for cb in run_config_callbacks if not isinstance(cb, UserCallback)]
)
return callbacks
def _initialize_and_run_local_controller(
self, train_func: Callable[[], Any]
) -> Result:
return self._get_local_controller().run(train_func)
def _initialize_and_run_controller(self, **controller_init_kwargs) -> Result:
env_vars = get_env_vars_to_propagate()
env_vars.setdefault(
RAY_WARN_BLOCKING_GET_INSIDE_ASYNC_ENV_VAR,
DEFAULT_RAY_WARN_BLOCKING_GET_INSIDE_ASYNC_VALUE,
)
# Attach the controller to the node running the driver script.
controller_actor_cls = ray.remote(
num_cpus=0,
label_selector={
ray._raylet.RAY_NODE_ID_KEY: ray.get_runtime_context().get_node_id()
},
# TODO: Extract env variables that affect controller behavior
# and pass them as explicit args
runtime_env={"env_vars": env_vars},
)(TrainController)
controller = controller_actor_cls.remote(**controller_init_kwargs)
# If this is not the main thread - as is the case when running in Tune -
# registering the SIGINT handler raises an exception.
if threading.current_thread() is threading.main_thread():
self._register_sigint_handler(controller)
ray.get(controller.run.remote())
return ray.get(controller.get_result.remote())
def _register_sigint_handler(self, controller: ActorHandle[TrainController]):
"""Register SIGINT handler so user Ctrl C gracefully aborts run."""
sigint_count = 0
def sigint_handler(signum, frame):
logger.info(
"Received SIGINT. Gracefully aborting the training run — this "
"may take a few seconds. To forcefully abort immediately, you "
"can send a different signal, such as SIGKILL."
)
nonlocal sigint_count
sigint_count += 1
if sigint_count >= 3:
logger.info(
"Received SIGINT at least 3 times. "
"Forcefully aborting the training run."
)
sys.exit(0)
if sigint_count <= 1:
try:
ray.get(controller.abort.remote())
except ray.exceptions.RayActorError:
# We catch the error and exit 0 to indicate graceful termination.
# However, for some reason the process still exits with 1.
sys.exit(0)
signal.signal(signal.SIGINT, sigint_handler)
@classmethod
@Deprecated
def restore(cls, *args, **kwargs):
"""[Deprecated] Restores a Train experiment from a previously
interrupted/failed run.
This method is deprecated and will be removed in a future release.
"""
raise DeprecationWarning(_TRAINER_RESTORE_DEPRECATION_WARNING)
@classmethod
@Deprecated
def can_restore(cls, *args, **kwargs):
"""[Deprecated] Checks if a Train experiment can be restored from
a previously interrupted/failed run.
This method is deprecated and will be removed in a future release.
"""
raise DeprecationWarning(_TRAINER_RESTORE_DEPRECATION_WARNING)
+49
View File
@@ -0,0 +1,49 @@
from typing import Dict
from ray.train.v2._internal.exceptions import RayTrainError
from ray.util.annotations import PublicAPI
@PublicAPI(stability="alpha")
class TrainingFailedError(RayTrainError):
"""Exception raised when training fails from a `trainer.fit()` call.
This is either :class:`ray.train.WorkerGroupError` or :class:`ray.train.ControllerError`.
"""
@PublicAPI(stability="alpha")
class WorkerGroupError(TrainingFailedError):
"""Exception raised from the worker group during training.
Args:
error_message: A human-readable error message describing the training worker failures.
worker_failures: A mapping from worker rank to the exception that
occurred on that worker during training.
"""
def __init__(self, error_message: str, worker_failures: Dict[int, Exception]):
super().__init__("Training failed due to worker errors:\n" + error_message)
self._error_message = error_message
self.worker_failures = worker_failures
def __reduce__(self):
return (self.__class__, (self._error_message, self.worker_failures))
@PublicAPI(stability="alpha")
class ControllerError(TrainingFailedError):
"""Exception raised when training fails due to a controller error.
Args:
controller_failure: The exception that occurred on the controller.
"""
def __init__(self, controller_failure: Exception):
super().__init__(
"Training failed due to controller error:\n" + str(controller_failure)
)
self.controller_failure = controller_failure
self.with_traceback(controller_failure.__traceback__)
def __reduce__(self):
return (self.__class__, (self.controller_failure,))
+36
View File
@@ -0,0 +1,36 @@
from enum import Enum
from ray.util.annotations import PublicAPI
@PublicAPI(stability="alpha")
class CheckpointUploadMode(Enum):
"""The manner in which we want to upload the checkpoint.
Members:
ASYNC: Upload checkpoint asynchronously.
SYNC: Upload checkpoint synchronously.
NO_UPLOAD: Do not upload checkpoint.
"""
ASYNC = "ASYNC"
SYNC = "SYNC"
NO_UPLOAD = "NO_UPLOAD"
def default_delete_local_checkpoint_after_upload(self) -> bool:
return self == CheckpointUploadMode.ASYNC
@PublicAPI(stability="alpha")
class CheckpointConsistencyMode(Enum):
"""Read semantics for checkpoint retrieval during an ongoing run.
Members:
COMMITTED: Block until the checkpoint from the latest ray.train.report
has been uploaded and committed.
VALIDATED: Block until the checkpoint from the latest ray.train.report
has been uploaded and validated.
"""
COMMITTED = "COMMITTED"
VALIDATED = "VALIDATED"
@@ -0,0 +1,43 @@
from dataclasses import dataclass
from enum import Enum
from typing import TYPE_CHECKING, Any, Dict
from ray.util.annotations import PublicAPI
if TYPE_CHECKING:
from ray.train import Checkpoint
@PublicAPI(stability="alpha")
class ReportedCheckpointStatus(Enum):
"""ReportedCheckpoint status enum.
* COMMITTED: The checkpoint is saved, and no validation was requested.
* PENDING_VALIDATION: The checkpoint is saved, and validation is in progress.
* VALIDATED: The checkpoint is saved, and validation is complete.
* VALIDATION_TIMEOUT: The checkpoint is saved, and validation is timed out according to
`ValidationTaskConfig(..., timeout_s=N)`.
* VALIDATION_FAILED: The checkpoint is saved, and validation failed.
"""
COMMITTED = "COMMITTED"
PENDING_VALIDATION = "PENDING_VALIDATION"
VALIDATED = "VALIDATED"
VALIDATION_TIMEOUT = "VALIDATION_TIMEOUT"
VALIDATION_FAILED = "VALIDATION_FAILED"
@dataclass
@PublicAPI(stability="alpha")
class ReportedCheckpoint:
"""A user-reported checkpoint and its associated metrics.
Attributes:
checkpoint: The checkpoint reported by the user.
metrics: The metrics associated with that checkpoint.
status: The status of the checkpoint.
"""
checkpoint: "Checkpoint"
metrics: Dict[str, Any]
status: ReportedCheckpointStatus
+162
View File
@@ -0,0 +1,162 @@
import logging
import os
from dataclasses import dataclass
from typing import Any, Dict, List, Optional, Tuple, Union
import pandas as pd
import pyarrow
import ray
from ray.air.result import Result as ResultV1
from ray.train import Checkpoint, CheckpointConfig
from ray.train.v2._internal.constants import CHECKPOINT_MANAGER_SNAPSHOT_FILENAME
from ray.train.v2._internal.execution.checkpoint.checkpoint_manager import (
CheckpointManager,
)
from ray.train.v2._internal.execution.storage import (
StorageContext,
_exists_at_fs_path,
get_fs_and_path,
)
from ray.train.v2.api.exceptions import TrainingFailedError
from ray.util.annotations import Deprecated, PublicAPI
logger = logging.getLogger(__name__)
@dataclass
class Result(ResultV1):
"""The output of ``trainer.fit()``.
Attributes:
metrics: The latest set of metrics reported by the training function
via :func:`ray.train.report`.
checkpoint: The latest checkpoint saved by the training function
via :func:`ray.train.report`.
return_value: The value returned by the user-defined training function on the
rank 0 worker, or ``None`` if no value was returned or if training did
not complete successfully. The return value must be serializable.
metrics_dataframe: A DataFrame of metrics from all checkpoints saved
during the run. Each row corresponds to a checkpoint.
best_checkpoints: A list of ``(checkpoint, metrics)`` tuples for the
best checkpoints saved during the run. The checkpoints retained
are determined by :class:`~ray.train.CheckpointConfig`
(by default, all checkpoints are kept).
path: Path pointing to the run output directory on persistent storage.
This can point to a remote storage location (e.g. S3) or to a
local location on the head node.
error: The execution error of the training run, if the run finished
in error. This is a :class:`~ray.train.v2.api.exceptions.TrainingFailedError`
wrapping the original exception.
"""
checkpoint: Optional[Checkpoint]
error: Optional[TrainingFailedError]
best_checkpoints: Optional[List[Tuple[Checkpoint, Dict[str, Any]]]] = None
return_value: Optional[Any] = None
@PublicAPI(stability="alpha")
def get_best_checkpoint(
self, metric: str, mode: str
) -> Optional["ray.train.Checkpoint"]:
return super().get_best_checkpoint(metric, mode)
@classmethod
def from_path(
cls,
path: Union[str, os.PathLike],
storage_filesystem: Optional[pyarrow.fs.FileSystem] = None,
) -> "Result":
"""Restore a training result from a previously saved training run path.
Args:
path: Path to the run output directory
storage_filesystem: Optional filesystem to use for accessing the path
Returns:
Result object with restored checkpoints and metrics
"""
fs, fs_path = get_fs_and_path(str(path), storage_filesystem)
# Validate that the experiment directory exists
if not _exists_at_fs_path(fs, fs_path):
raise RuntimeError(f"Experiment folder {fs_path} doesn't exist.")
# Remove trailing slashes to handle paths correctly
# os.path.basename() returns empty string for paths with trailing slashes
fs_path = fs_path.rstrip("/")
storage_path, experiment_dir_name = os.path.dirname(fs_path), os.path.basename(
fs_path
)
storage_context = StorageContext(
storage_path=storage_path,
experiment_dir_name=experiment_dir_name,
storage_filesystem=fs,
read_only=True,
)
# Validate that the checkpoint manager snapshot file exists
if not _exists_at_fs_path(
storage_context.storage_filesystem,
storage_context.checkpoint_manager_snapshot_path,
):
raise RuntimeError(
f"Failed to restore the Result object: "
f"{CHECKPOINT_MANAGER_SNAPSHOT_FILENAME} doesn't exist in the "
f"experiment folder. Make sure that this is an output directory created by a Ray Train run."
)
checkpoint_manager = CheckpointManager(
storage_context=storage_context,
checkpoint_config=CheckpointConfig(),
)
# When we build a Result object from checkpoints, the error is not loaded.
return cls._from_checkpoint_manager(
checkpoint_manager=checkpoint_manager,
storage_context=storage_context,
)
@classmethod
def _from_checkpoint_manager(
cls,
checkpoint_manager: CheckpointManager,
storage_context: StorageContext,
error: Optional[TrainingFailedError] = None,
) -> "Result":
"""Create a Result object from a CheckpointManager."""
latest_checkpoint_result = checkpoint_manager.latest_checkpoint_result
if latest_checkpoint_result:
latest_metrics = latest_checkpoint_result.metrics
latest_checkpoint = latest_checkpoint_result.checkpoint
else:
latest_metrics = None
latest_checkpoint = None
best_checkpoints = [
(r.checkpoint, r.metrics)
for r in checkpoint_manager.best_checkpoint_results
]
# Provide the history of metrics attached to checkpoints as a dataframe.
metrics_dataframe = None
if best_checkpoints:
metrics_dataframe = pd.DataFrame([m for _, m in best_checkpoints])
return Result(
metrics=latest_metrics,
checkpoint=latest_checkpoint,
error=error,
path=storage_context.experiment_fs_path,
best_checkpoints=best_checkpoints,
metrics_dataframe=metrics_dataframe,
_storage_filesystem=storage_context.storage_filesystem,
)
@property
@Deprecated
def config(self) -> Optional[Dict[str, Any]]:
raise DeprecationWarning(
"The `config` property for a `ray.train.Result` is deprecated, "
"since it is only relevant in the context of Ray Tune."
)
+297
View File
@@ -0,0 +1,297 @@
from typing import TYPE_CHECKING, Any, Callable, Dict, List, Optional, Union
from ray._common.usage.usage_lib import TagKey, record_extra_usage_tag
from ray.train.v2._internal.data_integration.interfaces import DatasetShardMetadata
from ray.train.v2._internal.execution.train_fn_utils import get_train_fn_utils
from ray.train.v2._internal.util import requires_train_worker
from ray.train.v2.api.context import TrainContext
from ray.train.v2.api.report_config import (
CheckpointConsistencyMode,
CheckpointUploadMode,
)
from ray.train.v2.api.validation_config import ValidationTaskConfig
from ray.util.annotations import PublicAPI
if TYPE_CHECKING:
from ray.data import DataIterator
from ray.train import Checkpoint
from ray.train.v2.api.reported_checkpoint import ReportedCheckpoint
@PublicAPI(stability="stable")
@requires_train_worker(raise_in_tune_session=True)
def report(
metrics: Dict[str, Any],
checkpoint: Optional["Checkpoint"] = None,
checkpoint_dir_name: Optional[str] = None,
checkpoint_upload_mode: CheckpointUploadMode = CheckpointUploadMode.SYNC,
delete_local_checkpoint_after_upload: Optional[bool] = None,
checkpoint_upload_fn: Optional[Callable[["Checkpoint", str], "Checkpoint"]] = None,
validation: Union[bool, ValidationTaskConfig] = False,
):
"""Report metrics and optionally save a checkpoint.
If a checkpoint is provided, it will be
:ref:`persisted to storage <persistent-storage-guide>`.
If this is called in multiple distributed training workers:
- Only the metrics reported by the rank 0 worker will be attached to the checkpoint.
- A checkpoint will be registered as long as one or more workers reports
checkpoint that is not None.
See the :ref:`checkpointing guide <train-dl-saving-checkpoints>`.
- Checkpoints from multiple workers will be merged into one directory
in persistent storage.
See :ref:`the distributed checkpointing guide <train-distributed-checkpointing>`.
.. warning::
All workers must call `ray.train.report` the same number of times
so that Ray Train can properly synchronize the training state across
workers. This method acts as a barrier across all workers, so be sure
that every worker reaches this method.
Example:
.. testcode::
:skipif: True
import tempfile
import ray.train
from ray.train.torch import TorchTrainer
def train_func(config):
start_epoch = 0
for epoch in range(start_epoch, config.get("num_epochs", 10)):
# Do training...
metrics = {"loss": ...}
with tempfile.TemporaryDirectory() as temp_checkpoint_dir:
# Save the checkpoint...
# torch.save(...)
checkpoint = ray.train.Checkpoint.from_directory(temp_checkpoint_dir)
# Example: Only the rank 0 worker uploads the checkpoint.
if ray.train.get_context().get_world_rank() == 0:
ray.train.report(metrics, checkpoint=checkpoint)
else:
ray.train.report(metrics, checkpoint=None)
trainer = TorchTrainer(
train_func, scaling_config=ray.train.ScalingConfig(num_workers=2)
)
Args:
metrics: The metrics you want to report.
checkpoint: The optional checkpoint you want to report.
checkpoint_dir_name: Custom name for the checkpoint directory.
If not provided, a unique directory name will be automatically generated.
If provided, it must be unique across all checkpoints per worker to avoid
naming collisions. Consider including identifiers such as the epoch or batch
index in the name.
checkpoint_upload_mode: The manner in which we want to upload the checkpoint.
Defaults to uploading the checkpoint synchronously.
This works when no checkpoint is provided but is not useful in that case.
delete_local_checkpoint_after_upload: Whether to delete the checkpoint after it is uploaded.
checkpoint_upload_fn: A user defined function that will be called with the
checkpoint to upload it. If not provided, defaults to using the `pyarrow.fs.copy_files`
utility for copying to the destination `storage_path`.
validation: [Alpha] If True, triggers validation with default kwargs from validation_config.
If a ValidationTaskConfig, validation is run using fn_kwargs merged with validation_config
defaults, with fn_kwargs taking precedence on conflicts. If False, no validation.
"""
if validation and not checkpoint:
raise ValueError("Validation requires a checkpoint to be provided.")
if delete_local_checkpoint_after_upload is None:
delete_local_checkpoint_after_upload = (
checkpoint_upload_mode.default_delete_local_checkpoint_after_upload()
)
if checkpoint:
record_extra_usage_tag(
TagKey.TRAIN_CHECKPOINT_MODE, checkpoint_upload_mode.value
)
if validation:
record_extra_usage_tag(TagKey.TRAIN_ASYNCHRONOUS_VALIDATION, "1")
get_train_fn_utils().report(
metrics=metrics,
checkpoint=checkpoint,
checkpoint_dir_name=checkpoint_dir_name,
checkpoint_upload_mode=checkpoint_upload_mode,
delete_local_checkpoint_after_upload=delete_local_checkpoint_after_upload,
checkpoint_upload_fn=checkpoint_upload_fn,
validation=validation,
)
@PublicAPI(stability="stable")
@requires_train_worker(raise_in_tune_session=True)
def get_context() -> TrainContext:
"""Get or create a singleton training context.
The context is only available within a function passed to Ray Train.
See the :class:`~ray.train.TrainContext` API reference to see available methods.
"""
return get_train_fn_utils().get_context()
@PublicAPI(stability="stable")
@requires_train_worker(raise_in_tune_session=True)
def get_checkpoint() -> Optional["Checkpoint"]:
"""Access the latest reported checkpoint to resume from if one exists.
See :ref:`the checkpoint loading guide <train-dl-loading-checkpoints>` for more details.
Example:
.. testcode::
:skipif: True
import tempfile
import ray.train
from ray.train.torch import TorchTrainer
def train_func(config):
start_epoch = 0
checkpoint = ray.train.get_checkpoint()
if checkpoint:
with checkpoint.as_directory() as checkpoint_dir:
# Load back training state
...
for epoch in range(start_epoch, config.get("num_epochs", 10)):
# Do training...
metrics = {"loss": ...}
with tempfile.TemporaryDirectory() as temp_checkpoint_dir:
# Save the checkpoint...
checkpoint = ray.train.Checkpoint.from_directory(temp_checkpoint_dir)
ray.train.report(metrics, checkpoint=checkpoint)
trainer = TorchTrainer(
train_func, scaling_config=ray.train.ScalingConfig(num_workers=2)
)
Returns:
Checkpoint object if the session is currently being resumed.
Otherwise, return None.
"""
return get_train_fn_utils().get_checkpoint()
@PublicAPI(stability="alpha")
@requires_train_worker()
def get_all_reported_checkpoints(
consistency_mode: CheckpointConsistencyMode = CheckpointConsistencyMode.VALIDATED,
timeout_s: Optional[float] = None,
) -> List["ReportedCheckpoint"]:
"""Get all the reported checkpoints so far.
Blocks until Ray Train has finished processing every in-flight `ray.train.report` call.
Example:
.. testcode::
import tempfile
import ray.train
from ray.train.torch import TorchTrainer
def train_func(config):
start_epoch = 0
for epoch in range(start_epoch, config.get("num_epochs", 2)):
# Do training...
metrics = {"loss": 0.1}
with tempfile.TemporaryDirectory() as temp_checkpoint_dir:
# Save the checkpoint...
checkpoint = ray.train.Checkpoint.from_directory(temp_checkpoint_dir)
ray.train.report(metrics, checkpoint=checkpoint)
reported_checkpoints = ray.train.get_all_reported_checkpoints()
# Report artifacts/metrics to experiment tracking framework...
trainer = TorchTrainer(
train_func, scaling_config=ray.train.ScalingConfig(num_workers=2)
)
trainer.fit()
Args:
consistency_mode: Read semantics for checkpoint retrieval during an ongoing run.
Defaults to CheckpointConsistencyMode.VALIDATED.
See :class:`~ray.train.CheckpointConsistencyMode` for more details.
timeout_s: Timeout in seconds to collecting checkpoint and validation information.
Defaults to None to wait indefinitely.
Returns:
List of ReportedCheckpoint objects that represent the checkpoints and
corresponding metrics reported by the workers.
"""
return get_train_fn_utils().get_all_reported_checkpoints(
consistency_mode=consistency_mode, timeout_s=timeout_s
)
@PublicAPI(stability="stable")
@requires_train_worker()
def get_dataset_shard(dataset_name: Optional[str] = None) -> Optional["DataIterator"]:
"""Returns the :class:`ray.data.DataIterator` shard for this worker.
Call :meth:`~ray.data.DataIterator.iter_torch_batches` or
:meth:`~ray.data.DataIterator.to_tf` on this shard to convert it to the
appropriate framework-specific data type.
.. testcode::
import ray.train
from ray.train.torch import TorchTrainer
def train_fn_per_worker(config):
...
for epoch in range(2):
# Trainer will automatically handle sharding.
data_shard = ray.train.get_dataset_shard("train")
for batch in data_shard.iter_torch_batches():
...
train_dataset = ray.data.read_csv("s3://anonymous@ray-example-data/iris.csv")
trainer = TorchTrainer(
train_fn_per_worker,
scaling_config=ray.train.ScalingConfig(num_workers=2),
datasets={"train": train_dataset}
)
trainer.fit()
Args:
dataset_name: If a Dictionary of Datasets was passed to ``Trainer``, then
specifies which dataset shard to return.
Returns:
The ``DataIterator`` shard to use for this worker.
If no dataset is passed into Trainer, then return None.
"""
train_fn_utils = get_train_fn_utils()
return train_fn_utils.get_dataset_shard(
DatasetShardMetadata(
dataset_name=dataset_name,
world_rank=train_fn_utils.get_context().get_world_rank(),
)
)
@@ -0,0 +1,74 @@
from dataclasses import dataclass
from typing import TYPE_CHECKING, Any, Dict, Optional, Protocol
from ray.util.annotations import PublicAPI
if TYPE_CHECKING:
from ray.train import Checkpoint
@PublicAPI(stability="alpha")
class ValidationFn(Protocol):
"""Protocol for a function that validates a checkpoint."""
def __call__(self, checkpoint: "Checkpoint", **kwargs: Any) -> Dict:
...
@dataclass
@PublicAPI(stability="alpha")
class ValidationTaskConfig:
"""Configuration for a specific validation task, passed to report().
Args:
fn_kwargs: json-serializable keyword arguments to pass to the validation function.
Note that we always pass `checkpoint` as the first argument to the
validation function.
timeout_s: Timeout in seconds for this validation task.
``None`` is no timeout.
"""
fn_kwargs: Optional[Dict[str, Any]] = None
timeout_s: Optional[float] = None
def __post_init__(self):
if self.fn_kwargs is None:
self.fn_kwargs = {}
assert (
self.timeout_s is None or self.timeout_s > 0
), f"The `timeout_s` should be None or greater than zero, actual value: {self.timeout_s}"
@PublicAPI(stability="alpha")
class ValidationConfig:
"""Configuration for validation, passed to the trainer.
Args:
fn: The validation function to run on checkpoints.
This function should accept a checkpoint as the first argument
and return a dictionary of metrics.
task_config: Default configuration for validation tasks.
The fn_kwargs in this config can be overridden by
ValidationTaskConfig passed to report().
ray_remote_kwargs: Keyword arguments to pass to `ray.remote()` for the validation task.
This can be used to specify resource requirements, number of retries, etc.
"""
def __init__(
self,
fn: ValidationFn,
task_config: Optional[ValidationTaskConfig] = None,
ray_remote_kwargs: Optional[Dict[str, Any]] = None,
):
self.fn = fn
if task_config is None:
self.task_config = ValidationTaskConfig()
else:
self.task_config = task_config
# TODO: ray_remote_kwargs is not json-serializable because retry_exceptions
# can be a list of exception types. If ray core makes ray_remote_kwargs json-serializable
# we can move this to ValidationTaskConfig.
if ray_remote_kwargs is None:
self.ray_remote_kwargs = {}
else:
self.ray_remote_kwargs = ray_remote_kwargs