chore: import upstream snapshot with attribution
This commit is contained in:
@@ -0,0 +1,403 @@
|
||||
import os
|
||||
import time
|
||||
import uuid
|
||||
from pathlib import Path
|
||||
from typing import Dict, List, Optional
|
||||
from unittest.mock import MagicMock
|
||||
|
||||
import ray
|
||||
from ray.train import BackendConfig, Checkpoint
|
||||
from ray.train._internal.data_config import DataConfig
|
||||
from ray.train.backend import Backend
|
||||
from ray.train.context import TrainContext
|
||||
from ray.train.v2._internal.execution.context import (
|
||||
DistributedContext,
|
||||
TrainRunContext,
|
||||
)
|
||||
from ray.train.v2._internal.execution.failure_handling import (
|
||||
FailureDecision,
|
||||
FailurePolicy,
|
||||
)
|
||||
from ray.train.v2._internal.execution.scaling_policy import (
|
||||
NoopDecision,
|
||||
ScalingDecision,
|
||||
ScalingPolicy,
|
||||
)
|
||||
from ray.train.v2._internal.execution.storage import StorageContext
|
||||
from ray.train.v2._internal.execution.training_report import _TrainingReport
|
||||
from ray.train.v2._internal.execution.worker_group import (
|
||||
WorkerGroup,
|
||||
WorkerGroupContext,
|
||||
WorkerGroupPollStatus,
|
||||
WorkerGroupState,
|
||||
WorkerStatus,
|
||||
)
|
||||
from ray.train.v2._internal.execution.worker_group.execution_group import ReplicaGroup
|
||||
from ray.train.v2._internal.state.schema import (
|
||||
ActorStatus,
|
||||
BackendConfig as BackendConfigSchema,
|
||||
CheckpointConfig as CheckpointConfigSchema,
|
||||
DataConfig as DataConfigSchema,
|
||||
DataExecutionOptions,
|
||||
FailureConfig as FailureConfigSchema,
|
||||
RunAttemptStatus,
|
||||
RunConfig as RunConfigSchema,
|
||||
RunSettings,
|
||||
RunStatus,
|
||||
ScalingConfig as ScalingConfigSchema,
|
||||
TrainResources,
|
||||
TrainRun,
|
||||
TrainRunAttempt,
|
||||
TrainWorker,
|
||||
)
|
||||
from ray.train.v2._internal.state.util import execution_options_to_model
|
||||
from ray.train.v2._internal.util import ObjectRefWrapper, time_monotonic
|
||||
from ray.train.v2.api.exceptions import TrainingFailedError
|
||||
from ray.train.v2.api.validation_config import ValidationTaskConfig
|
||||
|
||||
|
||||
class MockReplicaGroupBackend(Backend):
|
||||
has_replica_groups = True
|
||||
|
||||
|
||||
class MockReplicaGroupBackendConfig(BackendConfig):
|
||||
@property
|
||||
def backend_cls(self):
|
||||
return MockReplicaGroupBackend
|
||||
|
||||
|
||||
@ray.remote
|
||||
class Counter:
|
||||
def __init__(self):
|
||||
self.count = 0
|
||||
|
||||
def increment(self):
|
||||
self.count += 1
|
||||
return self.count
|
||||
|
||||
def get_count(self):
|
||||
return self.count
|
||||
|
||||
|
||||
class DummyWorkerGroup(WorkerGroup):
|
||||
|
||||
_start_failure = None
|
||||
_poll_failure = None
|
||||
|
||||
# TODO: Clean this up and use Mocks instead.
|
||||
def __init__(
|
||||
self,
|
||||
train_run_context: TrainRunContext,
|
||||
worker_group_context: WorkerGroupContext,
|
||||
callbacks=None,
|
||||
):
|
||||
self._num_workers = worker_group_context.num_workers
|
||||
self._worker_group_state = None
|
||||
self._worker_statuses = {}
|
||||
self._replaced_replica_groups: List[int] = []
|
||||
self._replica_groups = None
|
||||
self._latest_poll_status: Optional[WorkerGroupPollStatus] = None
|
||||
|
||||
def poll_status(self, *args, **kwargs) -> WorkerGroupPollStatus:
|
||||
if self._poll_failure:
|
||||
raise self._poll_failure
|
||||
return WorkerGroupPollStatus(
|
||||
worker_statuses=self._worker_statuses,
|
||||
)
|
||||
|
||||
def _start(self):
|
||||
num_workers = self._num_workers
|
||||
if self._start_failure:
|
||||
raise self._start_failure
|
||||
|
||||
workers = [MagicMock() for i in range(num_workers)]
|
||||
self._worker_group_state = WorkerGroupState(
|
||||
start_time=time_monotonic(),
|
||||
workers=workers,
|
||||
placement_group_handle=MagicMock(),
|
||||
sync_actor=None,
|
||||
)
|
||||
|
||||
self._worker_statuses = {
|
||||
i: WorkerStatus(running=True, error=None) for i in range(num_workers)
|
||||
}
|
||||
|
||||
self._replica_groups = [
|
||||
ReplicaGroup([workers[i]], resources_per_worker={})
|
||||
for i in range(num_workers)
|
||||
]
|
||||
|
||||
def shutdown(self):
|
||||
self._worker_group_state = None
|
||||
|
||||
def abort(self):
|
||||
pass
|
||||
|
||||
def replace_replica_group(self, replica_group_index: int):
|
||||
self._replaced_replica_groups.append(replica_group_index)
|
||||
|
||||
# === Test methods ===
|
||||
def clear_worker(self):
|
||||
for worker_status in self._worker_statuses.values():
|
||||
worker_status.error = None
|
||||
worker_status.running = True
|
||||
|
||||
def error_worker(self, worker_index):
|
||||
status = self._worker_statuses[worker_index]
|
||||
status.error = RuntimeError(f"Worker {worker_index} failed")
|
||||
|
||||
def finish_worker(self, worker_index):
|
||||
status = self._worker_statuses[worker_index]
|
||||
status.running = False
|
||||
|
||||
@classmethod
|
||||
def set_start_failure(cls, start_failure):
|
||||
cls._start_failure = start_failure
|
||||
|
||||
@classmethod
|
||||
def set_poll_failure(cls, poll_failure):
|
||||
cls._poll_failure = poll_failure
|
||||
|
||||
|
||||
class MockScalingPolicy(ScalingPolicy):
|
||||
def __init__(self, scaling_config):
|
||||
self._recovery_decision_queue = []
|
||||
self._monitor_decision_queue = []
|
||||
|
||||
super().__init__(scaling_config)
|
||||
|
||||
def _get_num_workers_for_resource_request(self) -> int:
|
||||
return self.scaling_config.num_workers
|
||||
|
||||
def make_decision_for_non_running_worker_group(self) -> ScalingDecision:
|
||||
if self._recovery_decision_queue:
|
||||
return self._recovery_decision_queue.pop(0)
|
||||
return NoopDecision()
|
||||
|
||||
def make_decision_for_running_worker_group(
|
||||
self,
|
||||
worker_group_state: WorkerGroupState,
|
||||
worker_group_status: WorkerGroupPollStatus,
|
||||
) -> ScalingDecision:
|
||||
if self._monitor_decision_queue:
|
||||
return self._monitor_decision_queue.pop(0)
|
||||
return NoopDecision()
|
||||
|
||||
# === Test methods ===
|
||||
def queue_recovery_decision(self, decision):
|
||||
self._recovery_decision_queue.append(decision)
|
||||
|
||||
def queue_monitor_decision(self, decision):
|
||||
self._monitor_decision_queue.append(decision)
|
||||
|
||||
|
||||
class MockFailurePolicy(FailurePolicy):
|
||||
def __init__(self, failure_config):
|
||||
self._decision_queue = []
|
||||
|
||||
super().__init__(failure_config)
|
||||
|
||||
def make_decision(
|
||||
self, training_failed_error: TrainingFailedError
|
||||
) -> FailureDecision:
|
||||
if self._decision_queue:
|
||||
return self._decision_queue.pop(0)
|
||||
return FailureDecision.NOOP
|
||||
|
||||
# === Test methods ===
|
||||
def queue_decision(self, decision):
|
||||
self._decision_queue.append(decision)
|
||||
|
||||
|
||||
class DummyObjectRefWrapper(ObjectRefWrapper):
|
||||
"""Mock object that returns the object passed in without going through ray.put."""
|
||||
|
||||
def __init__(self, obj):
|
||||
self._obj = obj
|
||||
|
||||
def get(self):
|
||||
return self._obj
|
||||
|
||||
|
||||
_RUN_ID = "mock_run_id"
|
||||
|
||||
|
||||
def create_mock_train_run(
|
||||
status: RunStatus = RunStatus.RUNNING,
|
||||
controller_actor_id: Optional[str] = None,
|
||||
end_time_ns: Optional[int] = None,
|
||||
id: Optional[str] = None,
|
||||
status_detail: Optional[str] = None,
|
||||
train_loop_config: Optional[Dict] = None,
|
||||
):
|
||||
return TrainRun(
|
||||
schema_version=0,
|
||||
id=id or _RUN_ID,
|
||||
name="test_run",
|
||||
job_id=uuid.uuid4().hex,
|
||||
controller_actor_id=controller_actor_id or uuid.uuid4().hex,
|
||||
status=status,
|
||||
status_detail=status_detail,
|
||||
start_time_ns=time.time_ns(),
|
||||
end_time_ns=end_time_ns,
|
||||
controller_log_file_path="/tmp/ray/session_xxx/logs/train/ray-train-app-controller.log",
|
||||
framework_versions={"ray": ray.__version__},
|
||||
run_settings=RunSettings(
|
||||
train_loop_config=train_loop_config,
|
||||
backend_config=BackendConfigSchema(framework=None, config={}),
|
||||
scaling_config=ScalingConfigSchema(
|
||||
num_workers=1,
|
||||
use_gpu=False,
|
||||
resources_per_worker=None,
|
||||
placement_strategy="PACK",
|
||||
accelerator_type=None,
|
||||
use_tpu=False,
|
||||
topology=None,
|
||||
bundle_label_selector=None,
|
||||
),
|
||||
datasets=["dataset_1"],
|
||||
data_config=DataConfigSchema(
|
||||
datasets_to_split="all",
|
||||
data_execution_options=DataExecutionOptions(
|
||||
default=execution_options_to_model(
|
||||
DataConfig.default_ingest_options()
|
||||
),
|
||||
),
|
||||
enable_shard_locality=True,
|
||||
),
|
||||
run_config=RunConfigSchema(
|
||||
name="test_run",
|
||||
failure_config=FailureConfigSchema(
|
||||
max_failures=0, controller_failure_limit=-1
|
||||
),
|
||||
worker_runtime_env={"type": "conda"},
|
||||
checkpoint_config=CheckpointConfigSchema(
|
||||
num_to_keep=None,
|
||||
checkpoint_score_attribute=None,
|
||||
checkpoint_score_order="max",
|
||||
),
|
||||
storage_path="s3://bucket/path",
|
||||
storage_filesystem=None,
|
||||
),
|
||||
),
|
||||
)
|
||||
|
||||
|
||||
def create_mock_train_run_attempt(
|
||||
attempt_id: str = "mock_attempt_id",
|
||||
status: RunAttemptStatus = RunAttemptStatus.RUNNING,
|
||||
end_time_ns: Optional[int] = None,
|
||||
run_id: Optional[str] = None,
|
||||
worker_status: Optional[ActorStatus] = ActorStatus.ALIVE,
|
||||
status_detail: Optional[str] = None,
|
||||
):
|
||||
worker = TrainWorker(
|
||||
world_rank=0,
|
||||
local_rank=0,
|
||||
node_rank=0,
|
||||
actor_id=uuid.uuid4().hex,
|
||||
node_id=uuid.uuid4().hex,
|
||||
node_ip="127.0.0.1",
|
||||
pid=1234,
|
||||
gpu_ids=[0],
|
||||
status=worker_status,
|
||||
resources=TrainResources(resources={"CPU": 1}),
|
||||
log_file_path="/tmp/ray/session_xxx/logs/train/ray-train-app-worker.log",
|
||||
)
|
||||
|
||||
return TrainRunAttempt(
|
||||
schema_version=0,
|
||||
attempt_id=attempt_id,
|
||||
run_id=run_id or _RUN_ID,
|
||||
status=status,
|
||||
status_detail=status_detail,
|
||||
start_time_ns=time.time_ns(),
|
||||
resources=[TrainResources(resources={"CPU": 1})],
|
||||
workers=[worker],
|
||||
end_time_ns=end_time_ns,
|
||||
)
|
||||
|
||||
|
||||
def create_dummy_run_context(**kwargs: dict) -> TrainRunContext:
|
||||
"""Create a standardized TrainRunContext for testing.
|
||||
|
||||
Args:
|
||||
**kwargs: Optional overrides for the default configuration.
|
||||
|
||||
Returns:
|
||||
TrainRunContext: A standardized TrainRunContext instance for testing.
|
||||
"""
|
||||
from ray.train import BackendConfig, DataConfig
|
||||
from ray.train.v2._internal.execution.context import TrainRunContext
|
||||
from ray.train.v2.api.config import RunConfig, ScalingConfig
|
||||
|
||||
config = dict(
|
||||
run_config=RunConfig(name="test"),
|
||||
train_loop_config={},
|
||||
scaling_config=ScalingConfig(num_workers=1),
|
||||
backend_config=BackendConfig(),
|
||||
dataset_config=DataConfig(),
|
||||
)
|
||||
config.update(kwargs)
|
||||
return TrainRunContext(**config)
|
||||
|
||||
|
||||
class DummyTrainContext(TrainContext):
|
||||
"""A dummy TrainContext subclass for testing."""
|
||||
|
||||
def __init__(self):
|
||||
self.train_run_context = create_dummy_run_context()
|
||||
self.distributed_context = DistributedContext(
|
||||
world_rank=0,
|
||||
world_size=1,
|
||||
local_rank=0,
|
||||
local_world_size=1,
|
||||
node_rank=0,
|
||||
)
|
||||
# Mock everything else since we don't need the actual functionality
|
||||
self.execution_context = MagicMock()
|
||||
self.storage_context = MagicMock()
|
||||
self.dataset_shards = {}
|
||||
|
||||
def get_run_config(self):
|
||||
return self.train_run_context.run_config
|
||||
|
||||
|
||||
def create_dummy_train_context() -> TrainContext:
|
||||
"""Create a standardized TrainContext for testing.
|
||||
|
||||
Returns:
|
||||
TrainContext: A standardized TrainContext instance for testing.
|
||||
"""
|
||||
return DummyTrainContext()
|
||||
|
||||
|
||||
def create_dummy_training_reports(
|
||||
num_results: int,
|
||||
storage_context: StorageContext,
|
||||
include_metrics: bool = True,
|
||||
include_validation: bool = False,
|
||||
starting_checkpoint_index: int = 0,
|
||||
) -> List[_TrainingReport]:
|
||||
training_results = []
|
||||
for i in range(num_results):
|
||||
metrics = {"score": i} if include_metrics else {}
|
||||
validation = (
|
||||
ValidationTaskConfig(fn_kwargs={"arg": i}) if include_validation else False
|
||||
)
|
||||
checkpoint_path = os.path.join(
|
||||
storage_context.experiment_fs_path,
|
||||
f"checkpoint_{starting_checkpoint_index + i}",
|
||||
)
|
||||
os.makedirs(checkpoint_path, exist_ok=True)
|
||||
training_results.append(
|
||||
_TrainingReport(
|
||||
checkpoint=Checkpoint(
|
||||
path=Path(checkpoint_path).as_posix(),
|
||||
filesystem=storage_context.storage_filesystem,
|
||||
),
|
||||
metrics=metrics,
|
||||
validation=validation,
|
||||
)
|
||||
)
|
||||
return training_results
|
||||
Reference in New Issue
Block a user