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
@@ -0,0 +1,14 @@
from ray.train._internal.state.state_manager import TrainRunStateManager
try:
import pydantic # noqa: F401
except ImportError:
raise ModuleNotFoundError(
"pydantic isn't installed."
"To install pydantic, please run 'pip install pydantic'"
)
__all__ = [
"TrainRunStateManager",
]
+105
View File
@@ -0,0 +1,105 @@
from typing import Optional
from ray.core.generated.export_train_state_pb2 import (
ExportTrainRunAttemptEventData as ProtoTrainRunAttempt,
ExportTrainRunEventData as ProtoTrainRun,
)
from ray.train._internal.state.schema import (
ActorStatusEnum,
RunStatusEnum,
TrainRunInfo,
TrainWorkerInfo,
)
TRAIN_SCHEMA_VERSION = 1
RAY_TRAIN_VERSION = 1
# Status mapping dictionaries
_ACTOR_STATUS_MAP = {
ActorStatusEnum.ALIVE: ProtoTrainRunAttempt.ActorStatus.ALIVE,
ActorStatusEnum.DEAD: ProtoTrainRunAttempt.ActorStatus.DEAD,
}
_RUN_ATTEMPT_STATUS_MAP = {
RunStatusEnum.STARTED: ProtoTrainRunAttempt.RunAttemptStatus.PENDING,
RunStatusEnum.RUNNING: ProtoTrainRunAttempt.RunAttemptStatus.RUNNING,
RunStatusEnum.FINISHED: ProtoTrainRunAttempt.RunAttemptStatus.FINISHED,
RunStatusEnum.ERRORED: ProtoTrainRunAttempt.RunAttemptStatus.ERRORED,
RunStatusEnum.ABORTED: ProtoTrainRunAttempt.RunAttemptStatus.ABORTED,
}
_RUN_STATUS_MAP = {
RunStatusEnum.STARTED: ProtoTrainRun.RunStatus.INITIALIZING,
RunStatusEnum.RUNNING: ProtoTrainRun.RunStatus.RUNNING,
RunStatusEnum.FINISHED: ProtoTrainRun.RunStatus.FINISHED,
RunStatusEnum.ERRORED: ProtoTrainRun.RunStatus.ERRORED,
RunStatusEnum.ABORTED: ProtoTrainRun.RunStatus.ABORTED,
}
def _ms_to_ns(ms: Optional[int]) -> Optional[int]:
if ms is None:
return None
return ms * 1000000
# Helper conversion functions
def _to_proto_resources(resources: dict) -> ProtoTrainRunAttempt.TrainResources:
"""Convert resources dictionary to protobuf TrainResources."""
return ProtoTrainRunAttempt.TrainResources(resources=resources)
def _to_proto_worker(worker: TrainWorkerInfo) -> ProtoTrainRunAttempt.TrainWorker:
"""Convert TrainWorker to protobuf format."""
proto_worker = ProtoTrainRunAttempt.TrainWorker(
world_rank=worker.world_rank,
local_rank=worker.local_rank,
node_rank=worker.node_rank,
actor_id=bytes.fromhex(worker.actor_id),
node_id=bytes.fromhex(worker.node_id),
node_ip=worker.node_ip,
pid=worker.pid,
gpu_ids=worker.gpu_ids,
status=_ACTOR_STATUS_MAP[worker.status],
resources=_to_proto_resources(worker.resources),
)
return proto_worker
# Main conversion functions
def train_run_info_to_proto_run(run_info: TrainRunInfo) -> ProtoTrainRun:
"""Convert TrainRunInfo to TrainRun protobuf format."""
proto_run = ProtoTrainRun(
schema_version=TRAIN_SCHEMA_VERSION,
ray_train_version=RAY_TRAIN_VERSION,
id=run_info.id,
name=run_info.name,
job_id=bytes.fromhex(run_info.job_id),
controller_actor_id=bytes.fromhex(run_info.controller_actor_id),
status=_RUN_STATUS_MAP[run_info.run_status],
status_detail=run_info.status_detail,
start_time_ns=_ms_to_ns(run_info.start_time_ms),
end_time_ns=_ms_to_ns(run_info.end_time_ms),
)
return proto_run
def train_run_info_to_proto_attempt(run_info: TrainRunInfo) -> ProtoTrainRunAttempt:
"""Convert TrainRunInfo to TrainRunAttempt protobuf format."""
proto_attempt = ProtoTrainRunAttempt(
schema_version=TRAIN_SCHEMA_VERSION,
ray_train_version=RAY_TRAIN_VERSION,
run_id=run_info.id,
attempt_id=run_info.id, # Same as run_id
status=_RUN_ATTEMPT_STATUS_MAP[run_info.run_status],
status_detail=run_info.status_detail,
start_time_ns=_ms_to_ns(run_info.start_time_ms),
end_time_ns=_ms_to_ns(run_info.end_time_ms),
resources=[_to_proto_resources(r) for r in run_info.resources],
workers=[_to_proto_worker(worker) for worker in run_info.workers],
)
return proto_attempt
+165
View File
@@ -0,0 +1,165 @@
from enum import Enum
from typing import Dict, List, Optional
from ray._common.pydantic_compat import BaseModel, Field
from ray.dashboard.modules.job.pydantic_models import JobDetails
from ray.util.annotations import DeveloperAPI
MAX_ERROR_STACK_TRACE_LENGTH = 50000
@DeveloperAPI
class RunStatusEnum(str, Enum):
"""Enumeration for the status of a train run."""
# (Deprecated) Replaced by RUNNING.
# The train run has started
STARTED = "STARTED"
# The train run is running
RUNNING = "RUNNING"
# The train run was terminated as expected
FINISHED = "FINISHED"
# The train run was terminated early due to errors in the training function
ERRORED = "ERRORED"
# The train run was terminated early due to system errors or controller errors
ABORTED = "ABORTED"
@DeveloperAPI
class ActorStatusEnum(str, Enum):
DEAD = "DEAD"
ALIVE = "ALIVE"
@DeveloperAPI
class TrainWorkerInfo(BaseModel):
"""Metadata of a Ray Train worker."""
actor_id: str = Field(description="Actor ID of the worker.")
world_rank: int = Field(description="World rank of the worker.")
local_rank: int = Field(description="Local rank of the worker.")
node_rank: int = Field(description="Node rank of the worker.")
node_id: str = Field(description="ID of the node that the worker is running on.")
node_ip: str = Field(
description="IP address of the node that the worker is running on."
)
pid: int = Field(description="Process ID of the worker.")
gpu_ids: List[int] = Field(
description="A list of GPU ids allocated to that worker."
)
status: ActorStatusEnum = Field(
description="The status of the train worker actor. It can be ALIVE or DEAD."
)
resources: Dict[str, float] = Field(
description="The resources allocated to the worker."
)
@DeveloperAPI
class MemoryInfo(BaseModel):
rss: int
vms: int
pfaults: Optional[int] = None
pageins: Optional[int] = None
@DeveloperAPI
class ProcessStats(BaseModel):
cpuPercent: float
# total memory, free memory, memory used ratio
mem: Optional[List[int]] = None
memoryInfo: MemoryInfo
class ProcessGPUUsage(BaseModel):
# This gpu usage stats from a process
pid: int
gpuMemoryUsage: int
@DeveloperAPI
class GPUStats(BaseModel):
uuid: str
index: int
name: str
utilizationGpu: Optional[float] = None
memoryUsed: float
memoryTotal: float
processInfo: ProcessGPUUsage
@DeveloperAPI
class TrainWorkerInfoWithDetails(TrainWorkerInfo):
"""Metadata of a Ray Train worker."""
processStats: Optional[ProcessStats] = Field(
None, description="Process stats of the worker."
)
gpus: List[GPUStats] = Field(
default_factory=list,
description=(
"GPU stats of the worker. "
"Only returns GPUs that are attached to the worker process."
),
)
@DeveloperAPI
class TrainDatasetInfo(BaseModel):
name: str = Field(
description="The key of the dataset dict specified in Ray Train Trainer."
)
dataset_uuid: str = Field(description="The uuid of the dataset.")
dataset_name: Optional[str] = Field(None, description="The name of the dataset.")
@DeveloperAPI
class TrainRunInfo(BaseModel):
"""Metadata for a Ray Train run and information about its workers."""
name: str = Field(description="The name of the Train run.")
id: str = Field(description="The unique identifier for each Train run.")
job_id: str = Field(description="The Ray Job ID.")
controller_actor_id: str = Field(description="Actor Id of the Train controller.")
workers: List[TrainWorkerInfo] = Field(
description="A List of Train workers sorted by global ranks."
)
datasets: List[TrainDatasetInfo] = Field(
description="A List of dataset info for this Train run."
)
run_status: RunStatusEnum = Field(
description="The current status of the train run. It can be one of the "
"following: RUNNING, FINISHED, ERRORED, or ABORTED."
)
status_detail: str = Field(
description="Detailed information about the current run status, "
"such as error messages."
)
start_time_ms: int = Field(
description="The UNIX timestamp of the start time of this Train run."
)
end_time_ms: Optional[int] = Field(
None,
description="The UNIX timestamp of the end time of this Train run. "
"If null, the Train run has not ended yet.",
)
resources: List[Dict[str, float]] = Field(
description="The resources allocated to the worker."
)
@DeveloperAPI
class TrainRunInfoWithDetails(TrainRunInfo):
"""Metadata for a Ray Train run and information about its workers."""
workers: List[TrainWorkerInfoWithDetails] = Field(
description="A List of Train workers sorted by global ranks."
)
job_details: Optional[JobDetails] = Field(
None, description="Details of the job that started this Train run."
)
@DeveloperAPI
class TrainRunsResponse(BaseModel):
train_runs: List[TrainRunInfoWithDetails]
@@ -0,0 +1,152 @@
import logging
import os
import threading
from typing import Dict, Optional
import ray
from ray._private.event.export_event_logger import (
EventLogType,
check_export_api_enabled,
get_export_event_logger,
)
from ray.actor import ActorHandle
from ray.train._internal.state.schema import TrainRunInfo
logger = logging.getLogger(__name__)
@ray.remote(num_cpus=0)
class TrainStateActor:
def __init__(self):
self._run_infos: Dict[str, TrainRunInfo] = {}
(
self._export_logger,
self._is_train_run_export_api_enabled,
self._is_train_run_attempt_export_api_enabled,
) = self._init_export_logger()
def register_train_run(self, run_info: TrainRunInfo) -> None:
# Register a new train run.
self._run_infos[run_info.id] = run_info
self._maybe_export_train_run(run_info)
self._maybe_export_train_run_attempt(run_info)
def get_train_run(self, run_id: str) -> Optional[TrainRunInfo]:
# Retrieve a registered run with its id
return self._run_infos.get(run_id, None)
def get_all_train_runs(self) -> Dict[str, TrainRunInfo]:
# Retrieve all registered train runs
return self._run_infos
# ============================
# Export API
# ============================
def is_export_api_enabled(self) -> bool:
return self._export_logger is not None
def _init_export_logger(self) -> tuple[Optional[logging.Logger], bool, bool]:
"""Initialize the export logger and check if the export API is enabled.
Returns:
A tuple containing:
- The export logger (or None if export API is not enabled).
- A boolean indicating if the export API is enabled for train runs.
- A boolean indicating if the export API is enabled for train run attempts.
"""
# Proto schemas should be imported within the scope of TrainStateActor to
# prevent serialization errors.
from ray.core.generated.export_event_pb2 import ExportEvent
is_train_run_export_api_enabled = check_export_api_enabled(
ExportEvent.SourceType.EXPORT_TRAIN_RUN
)
is_train_run_attempt_export_api_enabled = check_export_api_enabled(
ExportEvent.SourceType.EXPORT_TRAIN_RUN_ATTEMPT
)
export_api_enabled = (
is_train_run_export_api_enabled or is_train_run_attempt_export_api_enabled
)
if not export_api_enabled:
return None, False, False
log_directory = os.path.join(
ray._private.worker._global_node.get_session_dir_path(), "logs"
)
logger = None
try:
logger = get_export_event_logger(
EventLogType.TRAIN_STATE,
log_directory,
)
except Exception:
logger.exception(
"Unable to initialize the export event logger, so no Train export "
"events will be written."
)
if logger is None:
return None, False, False
return (
logger,
is_train_run_export_api_enabled,
is_train_run_attempt_export_api_enabled,
)
def _maybe_export_train_run(self, run_info: TrainRunInfo) -> None:
if not self._is_train_run_export_api_enabled:
return
from ray.train._internal.state.export import train_run_info_to_proto_run
run_proto = train_run_info_to_proto_run(run_info)
self._export_logger.send_event(run_proto)
def _maybe_export_train_run_attempt(self, run_info: TrainRunInfo) -> None:
if not self._is_train_run_attempt_export_api_enabled:
return
from ray.train._internal.state.export import train_run_info_to_proto_attempt
run_attempt_proto = train_run_info_to_proto_attempt(run_info)
self._export_logger.send_event(run_attempt_proto)
TRAIN_STATE_ACTOR_NAME = "train_state_actor"
TRAIN_STATE_ACTOR_NAMESPACE = "_train_state_actor"
_state_actor_lock: threading.RLock = threading.RLock()
def get_or_create_state_actor() -> ActorHandle:
"""Get or create a `TrainStateActor` on the head node."""
with _state_actor_lock:
state_actor = TrainStateActor.options(
name=TRAIN_STATE_ACTOR_NAME,
namespace=TRAIN_STATE_ACTOR_NAMESPACE,
get_if_exists=True,
lifetime="detached",
resources={"node:__internal_head__": 0.001},
# Escape from the parent's placement group
scheduling_strategy="DEFAULT",
).remote()
# Ensure the state actor is ready
ray.get(state_actor.__ray_ready__.remote())
return state_actor
def get_state_actor() -> Optional[ActorHandle]:
"""Get the `TrainStateActor` if exists, otherwise return None."""
try:
return ray.get_actor(
name=TRAIN_STATE_ACTOR_NAME,
namespace=TRAIN_STATE_ACTOR_NAMESPACE,
)
except ValueError:
return None
@@ -0,0 +1,132 @@
import logging
import os
from collections import defaultdict
from typing import TYPE_CHECKING, Any, Dict, List
import ray
from ray.train._internal.state.schema import (
ActorStatusEnum,
RunStatusEnum,
TrainDatasetInfo,
TrainRunInfo,
TrainWorkerInfo,
)
from ray.train._internal.utils import check_for_failure
from ray.train._internal.worker_group import WorkerGroup
if TYPE_CHECKING:
from ray.data import Dataset
logger = logging.getLogger(__name__)
class TrainRunStateManager:
"""A class that aggregates and reports train run info to TrainStateActor.
This manager class is created on the train controller layer for each run.
"""
def __init__(self, state_actor) -> None:
self.state_actor = state_actor
self.train_run_info_dict = defaultdict(dict)
def register_train_run(
self,
run_id: str,
job_id: str,
run_name: str,
run_status: str,
controller_actor_id: str,
datasets: Dict[str, "Dataset"],
worker_group: WorkerGroup,
start_time_ms: float,
resources: List[Dict[str, float]],
status_detail: str = "",
) -> None:
"""Collect Train Run Info and report to StateActor."""
if not self.state_actor:
logger.warning(
"Unable to register train run since `TrainStateActor` is not started."
)
return
def collect_train_worker_info():
train_context = ray.train.get_context()
core_context = ray.runtime_context.get_runtime_context()
return TrainWorkerInfo(
world_rank=train_context.get_world_rank(),
local_rank=train_context.get_local_rank(),
node_rank=train_context.get_node_rank(),
actor_id=core_context.get_actor_id(),
node_id=core_context.get_node_id(),
node_ip=ray.util.get_node_ip_address(),
gpu_ids=ray.get_gpu_ids(),
pid=os.getpid(),
resources=resources[0],
status=ActorStatusEnum.ALIVE,
)
futures = [
worker_group.execute_single_async(index, collect_train_worker_info)
for index in range(len(worker_group))
]
success, exception = check_for_failure(futures)
if not success:
logger.error(
"Failed to collect run information from the Ray Train "
f"workers:\n{exception}"
)
return
worker_info_list = ray.get(futures)
worker_info_list = sorted(worker_info_list, key=lambda info: info.world_rank)
dataset_info_list = [
TrainDatasetInfo(
name=ds_name,
dataset_name=ds._dataset_name,
dataset_uuid=ds._uuid,
)
for ds_name, ds in datasets.items()
]
updates = dict(
id=run_id,
job_id=job_id,
name=run_name,
controller_actor_id=controller_actor_id,
workers=worker_info_list,
datasets=dataset_info_list,
start_time_ms=start_time_ms,
run_status=run_status,
status_detail=status_detail,
resources=resources,
)
# Clear the cached info to avoid registering the same run twice
self.train_run_info_dict[run_id] = {}
self._update_train_run_info(run_id, updates)
def end_train_run(
self,
run_id: str,
run_status: RunStatusEnum,
status_detail: str,
end_time_ms: int,
):
"""Update the train run status when the training is finished."""
updates = dict(
run_status=run_status,
status_detail=status_detail,
end_time_ms=end_time_ms,
)
self._update_train_run_info(run_id, updates)
def _update_train_run_info(self, run_id: str, updates: Dict[str, Any]) -> None:
"""Update specific fields of a registered TrainRunInfo instance."""
if run_id in self.train_run_info_dict:
self.train_run_info_dict[run_id].update(updates)
train_run_info = TrainRunInfo(**self.train_run_info_dict[run_id])
ray.get(self.state_actor.register_train_run.remote(train_run_info))