latest (dev)
Copy
Latest development documentation · Updated 2026-10-08
Source code for tensorplay.distributed.elastic.rendezvous.dynamic_rendezvous
"""Dynamic rendezvous state machine and store-backed handler."""
from __future__ import annotations
import logging
import os
import pickle
import socket
import threading
import time
import weakref
from abc import ABC, abstractmethod
from dataclasses import dataclass
from datetime import datetime, timedelta, timezone
from enum import Enum
from typing import Any, Callable
from tensorplay.distributed import PrefixStore, Store
from ..events import NodeState, construct_and_record_rdzv_event
from .api import (
RendezvousClosedError,
RendezvousError,
RendezvousGracefulExitError,
RendezvousHandler,
RendezvousInfo,
RendezvousParameters,
RendezvousStateError,
RendezvousStoreInfo,
RendezvousTimeout,
RendezvousTimeoutError,
)
from .core_rendezvous_backend import RendezvousBackend, Token, create_backend
from .utils import _delay, _PeriodicTimer
__all__ = [
"RendezvousBackend",
"RendezvousTimeout",
"RendezvousSettings",
"DynamicRendezvousHandler",
"create_handler",
]
logger = logging.getLogger(__name__)
def get_method_name(depth: int = 2) -> str:
"""Return the caller name when it is available."""
import inspect
stack = inspect.stack()
try:
return stack[depth].function if len(stack) > depth else "no_method_name"
finally:
del stack
[docs]
@dataclass(init=False)
class RendezvousSettings:
"""Configuration shared by the rendezvous state machine."""
run_id: str
min_nodes: int
max_nodes: int
timeout: RendezvousTimeout
keep_alive_interval: timedelta
keep_alive_max_attempt: int
def __init__(
self,
*args: Any,
join: timedelta | None = None,
last_call: timedelta | None = None,
close: timedelta | None = None,
heartbeat: timedelta | None = None,
min_nodes: int = 1,
max_nodes: int = 1,
run_id: str = "",
timeout: RendezvousTimeout | None = None,
keep_alive_interval: timedelta | None = None,
keep_alive_max_attempt: int = 3,
) -> None:
if args:
if isinstance(args[0], str):
run_id = args[0]
min_nodes = args[1]
max_nodes = args[2]
timeout = args[3] if len(args) > 3 else timeout
keep_alive_interval = args[4] if len(args) > 4 else keep_alive_interval
keep_alive_max_attempt = args[5] if len(args) > 5 else keep_alive_max_attempt
else:
values = list(args) + [None] * 6
join, last_call, close, heartbeat = values[:4]
min_nodes = values[4] if values[4] is not None else min_nodes
max_nodes = values[5] if values[5] is not None else max_nodes
self.run_id = run_id
self.min_nodes = int(min_nodes)
self.max_nodes = int(max_nodes)
self.timeout = timeout or RendezvousTimeout(
join=join, last_call=last_call, close=close, heartbeat=heartbeat
)
self.keep_alive_interval = keep_alive_interval or (
self.timeout.heartbeat / 3
)
self.keep_alive_max_attempt = int(keep_alive_max_attempt)
@property
def join(self) -> timedelta:
return self.timeout.join
@property
def last_call(self) -> timedelta:
return self.timeout.last_call
@property
def close(self) -> timedelta:
return self.timeout.close
@property
def heartbeat(self) -> timedelta:
return self.timeout.heartbeat
@dataclass(frozen=True, order=True)
class _NodeDesc:
"""Identify one handler instance."""
addr: str
pid: int
local_id: int
def __repr__(self) -> str:
return f"{self.addr}_{self.pid}_{self.local_id}"
class _NodeDescGenerator:
"""Generate process-unique node descriptors."""
def __init__(self) -> None:
self._lock = threading.Lock()
self._local_id = 0
def generate(self, local_addr: str | None = None) -> _NodeDesc:
with self._lock:
local_id = self._local_id
self._local_id += 1
return _NodeDesc(local_addr or socket.getfqdn(), os.getpid(), local_id)
def _node_desc(
addr: str | None = None,
pid: int | None = None,
local_id: str | None = None,
) -> _NodeDesc:
"""Build a descriptor for callers using the legacy helper."""
value = int(local_id, 16) if local_id else 0
return _NodeDesc(addr or "localhost", pid or os.getpid(), value)
class _RendezvousState:
"""Serializable state for one rendezvous round."""
def __init__(self) -> None:
self.round = 0
self.complete = False
self.deadline: datetime | None = None
self.closed = False
self.participants: dict[_NodeDesc, int] = {}
self.wait_list: set[_NodeDesc] = set()
self.redundancy_list: set[_NodeDesc] = set()
self.last_heartbeats: dict[_NodeDesc, datetime] = {}
def prune_dead_nodes(self, expire_ms: float) -> list[_NodeDesc]:
now = datetime.now(timezone.utc)
expire = timedelta(milliseconds=expire_ms)
dead = [
node
for node, heartbeat in self.last_heartbeats.items()
if now - heartbeat > expire
]
for node in dead:
self.last_heartbeats.pop(node, None)
self.participants.pop(node, None)
self.wait_list.discard(node)
self.redundancy_list.discard(node)
return dead
def _remove_participant_epilogue(
state: _RendezvousState, settings: RendezvousSettings
) -> None:
if state.complete:
if not state.participants:
state.complete = False
state.round += 1
elif len(state.participants) < settings.min_nodes:
state.deadline = None
class _RendezvousStateHolder(ABC):
@property
@abstractmethod
def state(self) -> _RendezvousState:
raise NotImplementedError
@abstractmethod
def sync(self) -> bool | None:
raise NotImplementedError
@abstractmethod
def mark_dirty(self) -> None:
raise NotImplementedError
class _BackendRendezvousStateHolder(_RendezvousStateHolder):
"""Cache, sanitize, and conditionally persist rendezvous state."""
def __init__(
self,
backend: RendezvousBackend,
settings: RendezvousSettings,
cache_duration: int = 1,
) -> None:
self._backend = backend
self._state = _RendezvousState()
self._settings = settings
self._cache_duration = max(0, int(cache_duration))
self._token: Token | None = None
self._dirty = False
self._last_sync_time = -1.0
self._dead_nodes: list[_NodeDesc] = []
def _record(self, message: str, node_state: NodeState = NodeState.RUNNING) -> None:
construct_and_record_rdzv_event(
node_state=node_state,
run_id=self._settings.run_id,
message=message,
)
@property
def state(self) -> _RendezvousState:
return self._state
def sync(self) -> bool | None:
state_bits: bytes | None = None
token: Token | None = None
has_set: bool | None
if self._dirty:
candidate = pickle.dumps(self._state)
response = self._backend.set_state(candidate, self._token)
has_set = False
if response is not None:
if len(response) == 3:
state_bits, token, has_set = response
else:
state_bits, token = response
has_set = state_bits == candidate
else:
has_set = None
if self._cache_duration and self._last_sync_time >= max(
time.monotonic() - self._cache_duration, 0
):
return None
response = self._backend.get_state()
if response is not None:
state_bits, token = response
if state_bits is None:
self._state = _RendezvousState()
else:
try:
value = pickle.loads(state_bits)
except (pickle.PickleError, EOFError, AttributeError, ValueError) as exc:
raise RendezvousStateError(
"The rendezvous state is corrupt. See inner exception for details."
) from exc
if not isinstance(value, _RendezvousState):
raise RendezvousStateError("The rendezvous state has an invalid type")
self._state = value
self._token = token
self._dirty = False
self._last_sync_time = time.monotonic()
self._sanitize()
return has_set
def _sanitize(self) -> None:
expire = self._settings.keep_alive_interval * self._settings.keep_alive_max_attempt
now = datetime.now(timezone.utc)
self._dead_nodes = [
node
for node, heartbeat in self._state.last_heartbeats.items()
if heartbeat < now - expire
]
removed = False
for node in self._dead_nodes:
self._state.last_heartbeats.pop(node, None)
if node in self._state.participants:
del self._state.participants[node]
removed = True
self._state.wait_list.discard(node)
self._state.redundancy_list.discard(node)
if removed:
_remove_participant_epilogue(self._state, self._settings)
self._dirty = True
def mark_dirty(self) -> None:
self._dirty = True
class _Action(Enum):
KEEP_ALIVE = 1
ADD_TO_PARTICIPANTS = 2
ADD_TO_WAIT_LIST = 3
ADD_TO_REDUNDANCY_LIST = 4
REMOVE_FROM_PARTICIPANTS = 5
REMOVE_FROM_WAIT_LIST = 6
REMOVE_FROM_REDUNDANCY_LIST = 7
MARK_RENDEZVOUS_COMPLETE = 8
MARK_RENDEZVOUS_CLOSED = 9
SYNC = 10
ERROR_CLOSED = 11
ERROR_TIMEOUT = 12
FINISH = 13
@dataclass
class _RendezvousContext:
node: _NodeDesc
state: _RendezvousState
settings: RendezvousSettings
def __init__(
self, node: _NodeDesc, state: _RendezvousState, settings: RendezvousSettings
) -> None:
self.node = node
self.state = state
self.settings = settings
class _RendezvousOpExecutor(ABC):
@abstractmethod
def run(
self,
state_handler: Callable[[_RendezvousContext, float], _Action],
deadline: float,
update_deadline: Callable[[timedelta], float] | None = None,
) -> None:
raise NotImplementedError
class _DistributedRendezvousOpExecutor(_RendezvousOpExecutor):
"""Apply state transitions until an operation reaches its terminal state."""
def __init__(
self,
node: _NodeDesc,
state_holder: _RendezvousStateHolder,
settings: RendezvousSettings,
) -> None:
self._node = node
self._state_holder = state_holder
self._settings = settings
def _record(self, message: str, node_state: NodeState = NodeState.RUNNING) -> None:
construct_and_record_rdzv_event(
node_state=node_state,
run_id=self._settings.run_id,
message=message,
hostname=self._node.addr,
pid=self._node.pid,
local_id=self._node.local_id,
)
def run(
self,
state_handler: Callable[[_RendezvousContext, float], _Action],
deadline: float,
update_deadline: Callable[[timedelta], float] | None = None,
) -> None:
action: _Action | None = None
while action != _Action.FINISH:
self._state_holder.sync()
ctx = _RendezvousContext(
self._node, self._state_holder.state, self._settings
)
action = state_handler(ctx, deadline)
if action == _Action.FINISH:
continue
if action == _Action.ERROR_CLOSED:
raise RendezvousClosedError
if action == _Action.ERROR_TIMEOUT:
raise RendezvousTimeoutError
if action == _Action.SYNC:
_delay(1)
continue
if action == _Action.KEEP_ALIVE:
self._keep_alive()
elif action == _Action.ADD_TO_PARTICIPANTS:
self._add_to_participants()
elif action == _Action.ADD_TO_WAIT_LIST:
self._add_to_wait_list()
elif action == _Action.ADD_TO_REDUNDANCY_LIST:
self._add_to_redundancy_list()
elif action == _Action.REMOVE_FROM_PARTICIPANTS:
self._remove_from_participants()
elif action == _Action.REMOVE_FROM_WAIT_LIST:
self._remove_from_wait_list()
elif action == _Action.REMOVE_FROM_REDUNDANCY_LIST:
self._remove_from_redundancy_list()
if update_deadline:
deadline = update_deadline(self._settings.timeout.join)
elif action == _Action.MARK_RENDEZVOUS_COMPLETE:
self._mark_rendezvous_complete()
elif action == _Action.MARK_RENDEZVOUS_CLOSED:
self._mark_rendezvous_closed()
self._state_holder.mark_dirty()
def _keep_alive(self) -> None:
self._state_holder.state.last_heartbeats[self._node] = datetime.now(
timezone.utc
)
def _add_to_participants(self) -> None:
state = self._state_holder.state
state.wait_list.discard(self._node)
state.participants[self._node] = 0
self._keep_alive()
if len(state.participants) == self._settings.min_nodes:
state.deadline = datetime.now(timezone.utc) + self._settings.timeout.last_call
if len(state.participants) == self._settings.max_nodes:
self._mark_rendezvous_complete()
def _add_to_wait_list(self) -> None:
state = self._state_holder.state
state.redundancy_list.discard(self._node)
state.wait_list.add(self._node)
self._keep_alive()
def _add_to_redundancy_list(self) -> None:
state = self._state_holder.state
state.redundancy_list.add(self._node)
self._keep_alive()
def _remove_from_participants(self) -> None:
state = self._state_holder.state
state.participants.pop(self._node, None)
state.last_heartbeats.pop(self._node, None)
_remove_participant_epilogue(state, self._settings)
def _remove_from_wait_list(self) -> None:
state = self._state_holder.state
state.wait_list.discard(self._node)
state.last_heartbeats.pop(self._node, None)
def _remove_from_redundancy_list(self) -> None:
state = self._state_holder.state
state.redundancy_list.discard(self._node)
state.last_heartbeats.pop(self._node, None)
def _mark_rendezvous_complete(self) -> None:
state = self._state_holder.state
state.complete = True
state.deadline = None
for rank, node in enumerate(sorted(state.participants)):
state.participants[node] = rank
def _mark_rendezvous_closed(self) -> None:
self._state_holder.state.closed = True
def _should_keep_alive(ctx: _RendezvousContext) -> bool:
heartbeat = ctx.state.last_heartbeats.get(ctx.node)
return heartbeat is not None and heartbeat <= datetime.now(timezone.utc) - ctx.settings.keep_alive_interval
class _RendezvousExitOp:
def __call__(self, ctx: _RendezvousContext, deadline: float) -> _Action:
if ctx.node not in ctx.state.participants:
return _Action.FINISH
return _Action.REMOVE_FROM_PARTICIPANTS if time.monotonic() <= deadline else _Action.ERROR_TIMEOUT
class _RendezvousJoinOp:
def __call__(self, ctx: _RendezvousContext, deadline: float) -> _Action:
state = ctx.state
if state.closed:
if ctx.node in state.redundancy_list:
raise RendezvousGracefulExitError
return _Action.ERROR_CLOSED
if ctx.node in state.redundancy_list:
if len(state.participants) == ctx.settings.max_nodes:
return _Action.KEEP_ALIVE if _should_keep_alive(ctx) else _Action.SYNC
return _Action.REMOVE_FROM_REDUNDANCY_LIST
is_participant = ctx.node in state.participants
if state.complete and is_participant:
return _Action.FINISH
now = time.monotonic()
if now > deadline:
if now <= deadline + 5:
if is_participant:
return _Action.REMOVE_FROM_PARTICIPANTS
if ctx.node in state.wait_list:
return _Action.REMOVE_FROM_WAIT_LIST
return _Action.ERROR_TIMEOUT
if state.complete:
if len(state.participants) < ctx.settings.max_nodes:
if ctx.node not in state.wait_list:
return _Action.ADD_TO_WAIT_LIST
elif ctx.node not in state.redundancy_list and ctx.node not in state.wait_list:
return _Action.ADD_TO_REDUNDANCY_LIST
elif is_participant:
if (
ctx.settings.min_nodes <= len(state.participants) <= ctx.settings.max_nodes
and state.deadline is not None
):
if state.deadline < datetime.now(timezone.utc):
return _Action.MARK_RENDEZVOUS_COMPLETE
else:
return _Action.ADD_TO_PARTICIPANTS
return _Action.KEEP_ALIVE if _should_keep_alive(ctx) else _Action.SYNC
class _RendezvousCloseOp:
def __call__(self, ctx: _RendezvousContext, deadline: float) -> _Action:
if ctx.state.closed:
return _Action.FINISH
return _Action.ERROR_TIMEOUT if time.monotonic() > deadline else _Action.MARK_RENDEZVOUS_CLOSED
class _RendezvousKeepAliveOp:
def __call__(self, ctx: _RendezvousContext, deadline: float) -> _Action:
if not _should_keep_alive(ctx):
return _Action.FINISH
return _Action.KEEP_ALIVE if time.monotonic() <= deadline else _Action.ERROR_TIMEOUT
[docs]
class DynamicRendezvousHandler(RendezvousHandler):
"""Coordinate membership, ranks, heartbeats, and round transitions."""
_node_desc_generator = _NodeDescGenerator()
@classmethod
def from_backend(cls, run_id: str, *args: Any, **kwargs: Any) -> "DynamicRendezvousHandler":
source_style = len(args) >= 4 and not isinstance(
args[0], (str, bytes, os.PathLike)
)
if not source_style and "store" in kwargs and "backend" in kwargs:
args = (
kwargs.pop("store"),
kwargs.pop("backend"),
kwargs.pop("min_nodes"),
kwargs.pop("max_nodes"),
kwargs.pop("local_addr", None),
kwargs.pop("timeout", None),
kwargs.pop("keep_alive_interval", 5),
kwargs.pop("keep_alive_max_attempt", 3),
)
source_style = True
if source_style:
store = args[0]
backend = args[1]
min_nodes = args[2]
max_nodes = args[3]
local_addr = args[4] if len(args) > 4 else None
timeout = args[5] if len(args) > 5 else None
keep_alive_interval = args[6] if len(args) > 6 else 5
keep_alive_max_attempt = args[7] if len(args) > 7 else 3
settings = RendezvousSettings(
run_id=run_id,
min_nodes=min_nodes,
max_nodes=max_nodes,
timeout=timeout,
keep_alive_interval=timedelta(seconds=keep_alive_interval)
if not isinstance(keep_alive_interval, timedelta)
else keep_alive_interval,
keep_alive_max_attempt=keep_alive_max_attempt,
)
return cls(
backend=backend,
settings=settings,
local_addr=local_addr,
node_rank=0,
run_id=run_id,
store=store,
backend_name=backend.name,
)
endpoint = kwargs.pop("endpoint", args[0] if args else "")
settings = kwargs.pop("settings", None)
local_addr = kwargs.pop("local_addr", None)
node_rank = int(kwargs.pop("node_rank", 0))
store = kwargs.pop("store", None)
backend = kwargs.pop("backend", None)
store_type = kwargs.pop("store_type", "tcp")
if settings is None:
settings = RendezvousSettings(
run_id=run_id,
min_nodes=int(kwargs.pop("min_nodes", 1)),
max_nodes=int(kwargs.pop("max_nodes", 1)),
timeout=kwargs.pop("timeout", None),
)
else:
settings.run_id = run_id
if backend is None:
params = RendezvousParameters(
backend="core",
endpoint=endpoint,
run_id=run_id,
local_addr=local_addr,
node_rank=node_rank,
local_world_size=int(kwargs.pop("local_world_size", 1)),
config=dict(kwargs.pop("config", {})),
**kwargs,
)
backend, created_store = create_backend(params, store_type=store_type)
store = store or created_store
return cls(
backend=backend,
settings=settings,
local_addr=local_addr,
node_rank=node_rank,
run_id=run_id,
store=store,
backend_name=backend.name,
)
def __init__(
self,
backend: RendezvousBackend | _NodeDesc | None = None,
settings: RendezvousSettings | None = None,
local_addr: str | None = None,
node_rank: int = 0,
run_id: str = "",
store: Store | None = None,
backend_name: str = "core",
state_holder: _RendezvousStateHolder | None = None,
node: _NodeDesc | None = None,
) -> None:
if isinstance(backend, _NodeDesc):
node = backend
backend_name = str(local_addr)
store = node_rank if isinstance(node_rank, Store) else store
state_holder = run_id if isinstance(run_id, _RendezvousStateHolder) else state_holder
backend = getattr(state_holder, "_backend", None)
local_addr = node.addr
run_id = ""
node_rank = 0
if backend is None or settings is None:
raise TypeError("backend and settings are required")
if node is not None:
self._this_node = node
if not run_id:
run_id = settings.run_id
if not run_id:
raise ValueError("The run id must be a non-empty string.")
if settings.min_nodes < 1:
raise ValueError("The minimum number of nodes must be greater than zero.")
if settings.max_nodes < settings.min_nodes:
raise ValueError("The maximum number of nodes must be >= min_nodes.")
settings.run_id = run_id
self._backend = backend
self._backend_name = backend_name
self._settings = settings
self._node_rank = int(node_rank)
self._run_id = run_id
self._store = store if store is not None else getattr(backend, "_store", None)
if self._store is None:
raise ValueError("A rendezvous store is required")
if node is None:
self._this_node = self._node_desc_generator.generate(local_addr)
self._node = self._this_node
self._local_addr = local_addr
self._state_holder = state_holder or _BackendRendezvousStateHolder(
backend, settings
)
self._executor = _DistributedRendezvousOpExecutor(
self._this_node, self._state_holder, settings
)
self._op_executor = self._executor
self._heartbeat_lock = threading.Lock()
self._keep_alive_timer: _PeriodicTimer | None = None
self._bootstrap_store_info: RendezvousStoreInfo | None = None
self._shared_tcp_store_server: Store | None = None
@property
def settings(self) -> RendezvousSettings:
return self._settings
def get_backend(self) -> str:
return self._backend_name
def _record(
self,
message: str,
node_state: NodeState = NodeState.RUNNING,
rank: int | None = None,
) -> None:
construct_and_record_rdzv_event(
node_state=node_state,
run_id=self._settings.run_id,
message=message,
hostname=self._this_node.addr,
pid=self._this_node.pid,
local_id=self._this_node.local_id,
rank=rank,
)
def _create_tcp_store_server(self, master_addr: str, master_port: int):
from tensorplay.distributed import TCPStore
return TCPStore(
master_addr,
master_port,
world_size=1,
is_master=True,
wait_for_workers=False,
)
@property
def use_agent_store(self) -> bool:
return os.getenv("TORCH_DISABLE_SHARE_RDZV_TCP_STORE", "0") != "1"
def next_rendezvous(self) -> RendezvousInfo:
self._stop_heartbeats()
if self._state_holder.state.round == 0:
_delay((0, 0.3))
deadline = self._get_deadline(self._settings.timeout.join)
self._executor.run(_RendezvousExitOp(), deadline)
self._executor.run(_RendezvousJoinOp(), deadline, self._get_deadline)
self._start_heartbeats()
rank, world_size = self._get_world()
store = self._get_store() if self.use_agent_store else self._store
if not self.use_agent_store:
bootstrap_store_info = RendezvousStoreInfo.build(
rank, self._store, local_addr=self._this_node.addr
)
return RendezvousInfo(
store=store,
rank=rank,
world_size=world_size,
bootstrap_store_info=bootstrap_store_info,
participants=sorted(self._state_holder.state.participants),
wait_list=sorted(self._state_holder.state.wait_list),
)
if self._bootstrap_store_info is None or (
rank == 0 and self._shared_tcp_store_server is None
):
server_port = 0
if rank == 0:
self._shared_tcp_store_server = self._create_tcp_store_server(
self._this_node.addr, server_port
)
server_port = getattr(self._shared_tcp_store_server, "port", 0)
try:
self._bootstrap_store_info = RendezvousStoreInfo.build(
rank,
self._store,
local_addr=self._this_node.addr,
server_port=server_port,
)
except RendezvousError:
self._bootstrap_store_info = None
return RendezvousInfo(
store=store,
rank=rank,
world_size=world_size,
bootstrap_store_info=self._bootstrap_store_info,
participants=sorted(self._state_holder.state.participants),
wait_list=sorted(self._state_holder.state.wait_list),
)
def is_closed(self) -> bool:
with self._heartbeat_lock:
self._state_holder.sync()
return self._state_holder.state.closed
def set_closed(self) -> None:
with self._heartbeat_lock:
self._close()
def num_nodes_waiting(self) -> int:
with self._heartbeat_lock:
self._state_holder.sync()
return len(self._state_holder.state.wait_list)
def get_run_id(self) -> str:
return self._run_id
def shutdown(self) -> bool:
self._stop_heartbeats()
try:
self._close()
except RendezvousError:
return False
return True
def _close(self) -> None:
deadline = self._get_deadline(self._settings.timeout.close)
self._executor.run(_RendezvousCloseOp(), deadline)
@staticmethod
def _keep_alive_weak(
weak_self: weakref.ReferenceType["DynamicRendezvousHandler"],
) -> None:
instance = weak_self()
if instance is not None:
instance._keep_alive()
def _keep_alive(self) -> None:
with self._heartbeat_lock:
deadline = self._get_deadline(self._settings.timeout.heartbeat)
self._executor.run(_RendezvousKeepAliveOp(), deadline)
def _start_heartbeats(self) -> None:
self._stop_heartbeats()
weak_self = weakref.ref(self)
timer = _PeriodicTimer(
self._settings.keep_alive_interval,
lambda: self._keep_alive_weak(weak_self),
name=f"RendezvousKeepAliveTimer_{self._this_node.local_id}",
)
self._keep_alive_timer = timer
timer.start()
def _stop_heartbeats(self) -> None:
if self._keep_alive_timer is not None:
self._keep_alive_timer.cancel()
self._keep_alive_timer = None
def _get_world(self) -> tuple[int, int]:
state = self._state_holder.state
return state.participants[self._this_node], len(state.participants)
def _wrap_store(self, store: Store) -> Store:
return PrefixStore(
f"tp_elastic/rendezvous/{self._settings.run_id}/{self._state_holder.state.round}",
store,
)
def _get_store(self) -> Store:
return self._wrap_store(self._store)
def _get_deadline(self, timeout: timedelta) -> float:
return time.monotonic() + timeout.total_seconds()
def _get_timeout(params: RendezvousParameters, key: str) -> timedelta | None:
value = params.get_as_int(f"{key}_timeout")
return timedelta(seconds=value) if value is not None else None
def create_handler(*args: Any) -> DynamicRendezvousHandler:
"""Create a dynamic handler from rendezvous parameters."""
if len(args) == 1:
params = args[0]
store = backend = None
elif len(args) == 3:
store, backend, params = args
else:
raise TypeError("create_handler expects parameters or store, backend, parameters")
timeout = RendezvousTimeout(
join=_get_timeout(params, "join"),
last_call=_get_timeout(params, "last_call"),
close=_get_timeout(params, "close"),
heartbeat=_get_timeout(params, "heartbeat"),
)
settings = RendezvousSettings(
run_id=params.run_id,
min_nodes=params.min_nodes,
max_nodes=params.max_nodes,
timeout=timeout,
keep_alive_interval=timedelta(
seconds=params.get_as_int("keep_alive_interval", 5) or 5
),
keep_alive_max_attempt=params.get_as_int("keep_alive_max_attempt", 3) or 3,
)
if store is not None and backend is not None:
return DynamicRendezvousHandler.from_backend(
params.run_id,
store,
backend,
params.min_nodes,
params.max_nodes,
params.local_addr,
timeout,
keep_alive_interval=params.get_as_int("keep_alive_interval", 5),
keep_alive_max_attempt=params.get_as_int("keep_alive_max_attempt", 3),
)
return DynamicRendezvousHandler.from_backend(
params.run_id,
endpoint=params.endpoint,
settings=settings,
local_addr=params.local_addr,
node_rank=params.node_rank,
local_world_size=params.local_world_size,
store_type=str(params.get("store_type", "tcp")),
config=dict(params.config),
**params.kwargs,
)Help improve this page
Found an error, an unclear step, or a missing example?
Was this page helpful?

