mirror of
https://github.com/elder-plinius/OBLITERATUS.git
synced 2026-09-21 08:50:42 +02:00
1830 lines
68 KiB
Python
1830 lines
68 KiB
Python
"""Admission and local probes for fixed-membership distributed preflight."""
|
|
|
|
from __future__ import annotations
|
|
|
|
import ctypes
|
|
import hashlib
|
|
import hmac
|
|
import json
|
|
import os
|
|
import platform
|
|
import re
|
|
import signal
|
|
import shutil
|
|
import stat
|
|
import threading
|
|
import time
|
|
from contextlib import contextmanager
|
|
from dataclasses import asdict, dataclass
|
|
from pathlib import Path
|
|
from typing import Protocol
|
|
|
|
import accelerate # type: ignore[import-untyped]
|
|
import safetensors
|
|
import torch
|
|
import torch.distributed as dist
|
|
import transformers
|
|
|
|
from obliteratus.distributed.config import DistributedPreflightConfig
|
|
from obliteratus.distributed.consensus import (
|
|
assert_rank_order,
|
|
gloo_all_gather_records,
|
|
require_record_consensus,
|
|
unanimous_vote,
|
|
)
|
|
from obliteratus.distributed.contracts import (
|
|
ContractError,
|
|
RankInventory,
|
|
RuntimeContractError,
|
|
RuntimeStage,
|
|
RunIdentity,
|
|
StageMessage,
|
|
TopologyPlan,
|
|
Vote,
|
|
advance_stage,
|
|
canonical_record,
|
|
contract_digest,
|
|
)
|
|
from obliteratus.distributed.evidence import (
|
|
PreflightEvidence,
|
|
read_evidence,
|
|
read_stage_message,
|
|
write_evidence,
|
|
write_stage_message,
|
|
)
|
|
from obliteratus.distributed.launcher import (
|
|
TorchrunEnvironment,
|
|
control_group,
|
|
validate_network_interface,
|
|
)
|
|
from obliteratus.checkpoint_errors import CheckpointContractError
|
|
from obliteratus.checkpoint_inspection import InspectionLimits, inspect_checkpoint
|
|
|
|
|
|
_DIGEST_RE = re.compile(r"^[0-9a-f]{64}$")
|
|
_COMMIT_RE = re.compile(r"^[0-9a-f]{40}$")
|
|
_ALLOWED_SOURCE_SUFFIXES = frozenset({".json", ".model", ".safetensors", ".tiktoken", ".txt"})
|
|
_FORBIDDEN_SOURCE_SUFFIXES = frozenset({".bin", ".ckpt", ".pkl", ".pickle", ".pt", ".pth", ".py"})
|
|
HASH_CHUNK_BYTES = 8 * 1024 * 1024
|
|
_ACTIVE_DEADLINE_DEPTH = 0
|
|
|
|
|
|
def _digest(value: object, name: str, pattern: re.Pattern[str] = _DIGEST_RE) -> None:
|
|
if not isinstance(value, str) or pattern.fullmatch(value) is None:
|
|
raise ContractError(f"{name} has an invalid format")
|
|
|
|
|
|
def _memory(value: object, name: str, *, allow_zero: bool = False) -> None:
|
|
minimum = 0 if allow_zero else 1
|
|
if isinstance(value, bool) or not isinstance(value, int) or not minimum <= value <= 2**63 - 1:
|
|
raise ContractError(f"{name} is outside its allowed range")
|
|
|
|
|
|
@dataclass(frozen=True)
|
|
class RankAttestation:
|
|
"""Redaction-safe, exact local facts submitted by one fixed worker."""
|
|
|
|
rank: int
|
|
local_rank: int
|
|
local_world_size: int
|
|
group_rank: int
|
|
world_size: int
|
|
host_digest: str
|
|
device_digest: str
|
|
device_profile_digest: str
|
|
device_config_digest: str
|
|
device_kind: str
|
|
total_device_memory_bytes: int
|
|
free_device_memory_bytes: int
|
|
total_host_memory_bytes: int
|
|
free_host_memory_bytes: int
|
|
free_staging_bytes: int
|
|
software_digest: str
|
|
storage_digest: str
|
|
source_digest: str
|
|
model_digest: str
|
|
tokenizer_digest: str
|
|
config_digest: str
|
|
commit_sha: str
|
|
code_digest: str
|
|
placement_plan_digest: str
|
|
network_interface_digest: str
|
|
|
|
def __post_init__(self) -> None:
|
|
RankInventory(
|
|
rank=self.rank,
|
|
local_rank=self.local_rank,
|
|
world_size=self.world_size,
|
|
host_digest=self.host_digest,
|
|
device_digest=self.device_digest,
|
|
device_kind=self.device_kind,
|
|
total_memory_bytes=self.total_device_memory_bytes,
|
|
free_memory_bytes=self.free_device_memory_bytes,
|
|
software_digest=self.software_digest,
|
|
storage_digest=self.storage_digest,
|
|
)
|
|
_memory(self.total_host_memory_bytes, "total_host_memory_bytes")
|
|
_memory(self.free_host_memory_bytes, "free_host_memory_bytes", allow_zero=True)
|
|
if self.free_host_memory_bytes > self.total_host_memory_bytes:
|
|
raise ContractError("free_host_memory_bytes cannot exceed total_host_memory_bytes")
|
|
_memory(self.free_staging_bytes, "free_staging_bytes", allow_zero=True)
|
|
for name in (
|
|
"device_profile_digest",
|
|
"device_config_digest",
|
|
"source_digest",
|
|
"model_digest",
|
|
"tokenizer_digest",
|
|
"config_digest",
|
|
"code_digest",
|
|
"placement_plan_digest",
|
|
"network_interface_digest",
|
|
):
|
|
_digest(getattr(self, name), name)
|
|
_digest(self.commit_sha, "commit_sha", _COMMIT_RE)
|
|
|
|
def to_dict(self) -> dict[str, object]:
|
|
return asdict(self)
|
|
|
|
@classmethod
|
|
def from_bytes(cls, payload: bytes) -> "RankAttestation":
|
|
def unique_object(pairs: list[tuple[str, object]]) -> dict[str, object]:
|
|
result: dict[str, object] = {}
|
|
for key, value in pairs:
|
|
if key in result:
|
|
raise ContractError("rank attestation contains a duplicate field")
|
|
result[key] = value
|
|
return result
|
|
|
|
try:
|
|
value = json.loads(payload.decode("utf-8"), object_pairs_hook=unique_object)
|
|
except (UnicodeError, json.JSONDecodeError) as exc:
|
|
raise ContractError("rank attestation must be strict UTF-8 JSON") from exc
|
|
if not isinstance(value, dict) or set(value) != set(cls.__dataclass_fields__):
|
|
raise ContractError("rank attestation fields do not match the closed schema")
|
|
result = cls(**value)
|
|
if payload != canonical_record(result):
|
|
raise ContractError("rank attestation is not canonical")
|
|
return result
|
|
|
|
|
|
def _stage_message_from_bytes(payload: bytes) -> StageMessage:
|
|
try:
|
|
return StageMessage.from_bytes(payload)
|
|
except RuntimeContractError:
|
|
raise
|
|
except ContractError as exc:
|
|
raise RuntimeContractError(
|
|
"LMS_CONSENSUS_RECORD_INVALID", "lifecycle message is invalid"
|
|
) from exc
|
|
|
|
|
|
def _validate_stage_messages(
|
|
messages: tuple[StageMessage, ...],
|
|
*,
|
|
run_id: str,
|
|
world_size: int,
|
|
stage: RuntimeStage,
|
|
sequence: int,
|
|
identity_digest: str,
|
|
vote: Vote | None,
|
|
) -> None:
|
|
if len(messages) != world_size or {item.rank for item in messages} != set(range(world_size)):
|
|
raise RuntimeContractError(
|
|
"LMS_MEMBERSHIP_INVALID", "lifecycle records do not cover the fixed world"
|
|
)
|
|
if any(
|
|
item.run_id != run_id
|
|
or item.stage is not stage
|
|
or item.sequence != sequence
|
|
or item.identity_digest != identity_digest
|
|
or item.vote is not vote
|
|
or item.error_code is not None
|
|
for item in messages
|
|
):
|
|
raise RuntimeContractError("LMS_LIFECYCLE_INVALID", "rank lifecycle records disagree")
|
|
|
|
|
|
@dataclass(frozen=True)
|
|
class SourceIdentity:
|
|
source_digest: str
|
|
model_digest: str
|
|
tokenizer_digest: str
|
|
file_count: int
|
|
total_bytes: int
|
|
|
|
|
|
@dataclass(frozen=True)
|
|
class LocalSnapshot:
|
|
host_identity: str
|
|
device_identity: str
|
|
device_name: str
|
|
compute_capability: str
|
|
device_kind: str
|
|
total_device_memory_bytes: int
|
|
free_device_memory_bytes: int
|
|
total_host_memory_bytes: int
|
|
free_host_memory_bytes: int
|
|
free_staging_bytes: int
|
|
storage_identity: str
|
|
source: SourceIdentity
|
|
software_versions: tuple[tuple[str, str], ...]
|
|
commit_sha: str
|
|
code_digest: str
|
|
|
|
|
|
@dataclass(frozen=True)
|
|
class PreflightResult:
|
|
"""Capability that later distributed code must require before allocation."""
|
|
|
|
identity: RunIdentity
|
|
topology: TopologyPlan
|
|
attestations: tuple[RankAttestation, ...]
|
|
identity_digest: str
|
|
attempt_path: Path
|
|
source_identity: SourceIdentity
|
|
lifecycle_identity_digest: str
|
|
created_messages: tuple[StageMessage, ...]
|
|
|
|
|
|
class PreflightProbes(Protocol):
|
|
def collect(
|
|
self, config: DistributedPreflightConfig, launch: TorchrunEnvironment
|
|
) -> LocalSnapshot: ...
|
|
|
|
|
|
def _hash_file(
|
|
path: Path,
|
|
*,
|
|
deadline: float,
|
|
max_bytes: int,
|
|
require_read_only: bool = True,
|
|
) -> tuple[int, str]:
|
|
descriptor: int | None = None
|
|
try:
|
|
descriptor = os.open(path, os.O_RDONLY | os.O_NOFOLLOW)
|
|
before = os.fstat(descriptor)
|
|
if not stat.S_ISREG(before.st_mode) or (require_read_only and before.st_mode & 0o222):
|
|
raise RuntimeContractError(
|
|
"LMS_SOURCE_BOUNDARY_VIOLATION",
|
|
"source files must be immutable regular files",
|
|
)
|
|
if before.st_nlink != 1:
|
|
raise RuntimeContractError(
|
|
"LMS_SOURCE_BOUNDARY_VIOLATION",
|
|
"source files cannot have hard-link aliases",
|
|
)
|
|
if before.st_size > max_bytes:
|
|
raise RuntimeContractError(
|
|
"LMS_RESOURCE_ADMISSION_DENIED",
|
|
"source file exceeds the configured byte bound",
|
|
)
|
|
digest = hashlib.sha256()
|
|
while chunk := os.read(descriptor, HASH_CHUNK_BYTES):
|
|
if time.monotonic() > deadline:
|
|
raise RuntimeContractError(
|
|
"LMS_STAGE_TIMEOUT",
|
|
"source inspection exceeded its explicit timeout",
|
|
)
|
|
digest.update(chunk)
|
|
after = os.fstat(descriptor)
|
|
final_path = path.stat(follow_symlinks=False)
|
|
except RuntimeContractError:
|
|
raise
|
|
except OSError as exc:
|
|
raise RuntimeContractError(
|
|
"LMS_SOURCE_BOUNDARY_VIOLATION",
|
|
"source files must be immutable regular files",
|
|
) from exc
|
|
finally:
|
|
if descriptor is not None:
|
|
os.close(descriptor)
|
|
identity = lambda item: (
|
|
item.st_dev,
|
|
item.st_ino,
|
|
item.st_size,
|
|
item.st_mtime_ns,
|
|
item.st_mode,
|
|
)
|
|
if identity(before) != identity(after) or identity(after) != identity(final_path):
|
|
raise RuntimeContractError(
|
|
"LMS_SOURCE_CHANGED", "source changed during preflight inspection"
|
|
)
|
|
return before.st_size, digest.hexdigest()
|
|
|
|
|
|
@contextmanager
|
|
def _stage_deadline(timeout_seconds: int, message: str):
|
|
"""Enforce a nestable stage bound with a process-level Linux timer."""
|
|
|
|
global _ACTIVE_DEADLINE_DEPTH
|
|
|
|
if (
|
|
not hasattr(signal, "SIGALRM")
|
|
or not hasattr(signal, "setitimer")
|
|
or threading.current_thread() is not threading.main_thread()
|
|
):
|
|
raise RuntimeContractError(
|
|
"LMS_RUNTIME_PROFILE_MISMATCH",
|
|
"the runtime cannot enforce the source inspection deadline",
|
|
)
|
|
previous_timer = signal.getitimer(signal.ITIMER_REAL)
|
|
if previous_timer[0] > 0 and _ACTIVE_DEADLINE_DEPTH == 0:
|
|
raise RuntimeContractError(
|
|
"LMS_RUNTIME_PROFILE_MISMATCH",
|
|
"the stage deadline conflicts with an active runtime timer",
|
|
)
|
|
previous_handler = signal.getsignal(signal.SIGALRM)
|
|
started = time.monotonic()
|
|
|
|
def expire(_signum: int, _frame: object) -> None:
|
|
raise RuntimeContractError("LMS_STAGE_TIMEOUT", message)
|
|
|
|
signal.signal(signal.SIGALRM, expire)
|
|
duration = (
|
|
min(float(timeout_seconds), previous_timer[0])
|
|
if previous_timer[0] > 0
|
|
else float(timeout_seconds)
|
|
)
|
|
signal.setitimer(signal.ITIMER_REAL, duration)
|
|
_ACTIVE_DEADLINE_DEPTH += 1
|
|
try:
|
|
yield
|
|
finally:
|
|
signal.setitimer(signal.ITIMER_REAL, 0)
|
|
_ACTIVE_DEADLINE_DEPTH -= 1
|
|
signal.signal(signal.SIGALRM, previous_handler)
|
|
if previous_timer[0] > 0:
|
|
remaining = previous_timer[0] - (time.monotonic() - started)
|
|
if remaining > 0:
|
|
signal.setitimer(signal.ITIMER_REAL, remaining, previous_timer[1])
|
|
|
|
|
|
def inspect_source(
|
|
path: Path,
|
|
*,
|
|
max_files: int = 100_000,
|
|
max_total_bytes: int = 2**44,
|
|
max_file_bytes: int = 2**41,
|
|
timeout_seconds: int = 3600,
|
|
) -> SourceIdentity:
|
|
"""Hash an immutable local HF safetensors tree without loading tensor payloads."""
|
|
if (
|
|
isinstance(timeout_seconds, bool)
|
|
or not isinstance(timeout_seconds, int)
|
|
or not 1 <= timeout_seconds <= 3600
|
|
):
|
|
raise RuntimeContractError(
|
|
"LMS_SOURCE_BOUNDARY_VIOLATION",
|
|
"timeout_seconds is outside the bounded source-inspection contract",
|
|
)
|
|
with _stage_deadline(timeout_seconds, "source inspection exceeded its explicit timeout"):
|
|
try:
|
|
return _inspect_source(
|
|
path,
|
|
max_files=max_files,
|
|
max_total_bytes=max_total_bytes,
|
|
max_file_bytes=max_file_bytes,
|
|
timeout_seconds=timeout_seconds,
|
|
)
|
|
except RuntimeContractError:
|
|
raise
|
|
except ContractError as exc:
|
|
raise RuntimeContractError(
|
|
"LMS_SOURCE_BOUNDARY_VIOLATION",
|
|
"source violates the bounded immutable input contract",
|
|
) from exc
|
|
|
|
|
|
def _inspect_source(
|
|
path: Path,
|
|
*,
|
|
max_files: int,
|
|
max_total_bytes: int,
|
|
max_file_bytes: int,
|
|
timeout_seconds: int,
|
|
) -> SourceIdentity:
|
|
if path.is_symlink() or not path.is_dir():
|
|
raise RuntimeContractError(
|
|
"LMS_SOURCE_BOUNDARY_VIOLATION",
|
|
"source must be a local non-symlink directory",
|
|
)
|
|
if path.stat(follow_symlinks=False).st_mode & 0o222:
|
|
raise RuntimeContractError(
|
|
"LMS_SOURCE_BOUNDARY_VIOLATION", "source directory must be immutable"
|
|
)
|
|
for value, name, maximum in (
|
|
(max_files, "max_files", 1_000_000),
|
|
(max_total_bytes, "max_total_bytes", 2**63 - 1),
|
|
(max_file_bytes, "max_file_bytes", 2**63 - 1),
|
|
(timeout_seconds, "timeout_seconds", 3600),
|
|
):
|
|
if isinstance(value, bool) or not isinstance(value, int) or not 1 <= value <= maximum:
|
|
raise RuntimeContractError(
|
|
"LMS_SOURCE_BOUNDARY_VIOLATION",
|
|
f"{name} is outside the bounded source-inspection contract",
|
|
)
|
|
if max_file_bytes > max_total_bytes:
|
|
raise RuntimeContractError(
|
|
"LMS_SOURCE_BOUNDARY_VIOLATION",
|
|
"max_file_bytes cannot exceed max_total_bytes",
|
|
)
|
|
deadline = time.monotonic() + timeout_seconds
|
|
files: list[Path] = []
|
|
observed_bytes = 0
|
|
observed_inodes: set[tuple[int, int]] = set()
|
|
for directory, directory_names, file_names in os.walk(path, topdown=True, followlinks=False):
|
|
if time.monotonic() > deadline:
|
|
raise RuntimeContractError(
|
|
"LMS_STAGE_TIMEOUT", "source inspection exceeded its explicit timeout"
|
|
)
|
|
directory_names.sort()
|
|
file_names.sort()
|
|
directory_path = Path(directory)
|
|
directory_metadata = directory_path.stat(follow_symlinks=False)
|
|
if not stat.S_ISDIR(directory_metadata.st_mode) or directory_metadata.st_mode & 0o222:
|
|
raise RuntimeContractError(
|
|
"LMS_SOURCE_BOUNDARY_VIOLATION",
|
|
"every source directory must be immutable",
|
|
)
|
|
for name in (*directory_names, *file_names):
|
|
item = directory_path / name
|
|
metadata = item.stat(follow_symlinks=False)
|
|
if stat.S_ISLNK(metadata.st_mode):
|
|
raise RuntimeContractError(
|
|
"LMS_SOURCE_BOUNDARY_VIOLATION",
|
|
"source cannot contain symbolic links",
|
|
)
|
|
for name in file_names:
|
|
item = directory_path / name
|
|
metadata = item.stat(follow_symlinks=False)
|
|
if not stat.S_ISREG(metadata.st_mode):
|
|
raise RuntimeContractError(
|
|
"LMS_SOURCE_BOUNDARY_VIOLATION",
|
|
"source can contain only regular files and directories",
|
|
)
|
|
if metadata.st_nlink != 1:
|
|
raise RuntimeContractError(
|
|
"LMS_SOURCE_BOUNDARY_VIOLATION",
|
|
"source files cannot have hard-link aliases",
|
|
)
|
|
files.append(item)
|
|
if len(files) > max_files:
|
|
raise RuntimeContractError(
|
|
"LMS_RESOURCE_ADMISSION_DENIED",
|
|
"source file count exceeds the configured bound",
|
|
)
|
|
if metadata.st_size > max_file_bytes:
|
|
raise RuntimeContractError(
|
|
"LMS_RESOURCE_ADMISSION_DENIED",
|
|
"source file exceeds the configured byte bound",
|
|
)
|
|
observed_bytes += metadata.st_size
|
|
if observed_bytes > max_total_bytes:
|
|
raise RuntimeContractError(
|
|
"LMS_RESOURCE_ADMISSION_DENIED",
|
|
"source exceeds the configured total-byte bound",
|
|
)
|
|
inode = (metadata.st_dev, metadata.st_ino)
|
|
if inode in observed_inodes:
|
|
raise RuntimeContractError(
|
|
"LMS_SOURCE_BOUNDARY_VIOLATION",
|
|
"source files cannot alias the same inode",
|
|
)
|
|
observed_inodes.add(inode)
|
|
if not files:
|
|
raise RuntimeContractError(
|
|
"LMS_SOURCE_BOUNDARY_VIOLATION",
|
|
"source file count is outside the bounded contract",
|
|
)
|
|
for item in files:
|
|
suffix = item.suffix.lower()
|
|
if suffix in _FORBIDDEN_SOURCE_SUFFIXES or suffix not in _ALLOWED_SOURCE_SUFFIXES:
|
|
raise RuntimeContractError(
|
|
"LMS_SOURCE_BOUNDARY_VIOLATION",
|
|
"source contains a serialization type outside the safetensors envelope",
|
|
)
|
|
try:
|
|
report = inspect_checkpoint(
|
|
path,
|
|
limits=InspectionLimits(
|
|
max_files=max_files,
|
|
max_directories=min(1_000_000, max_files + 1),
|
|
max_total_bytes=max_total_bytes,
|
|
max_json_bytes=min(max_file_bytes, 8 << 20),
|
|
max_safetensors_header_bytes=min(max_file_bytes, 64 << 20),
|
|
max_tensors=2_000_000,
|
|
hash_chunk_bytes=HASH_CHUNK_BYTES,
|
|
),
|
|
)
|
|
except CheckpointContractError as exc:
|
|
if exc.code == "DCI_SOURCE_CHANGED":
|
|
raise RuntimeContractError(
|
|
"LMS_SOURCE_CHANGED", "source changed during safe-structure inspection"
|
|
) from exc
|
|
if exc.code == "DCI_RESOURCE_LIMIT" and exc.detail == "deadline":
|
|
raise RuntimeContractError(
|
|
"LMS_STAGE_TIMEOUT", "source inspection exceeded its explicit timeout"
|
|
) from exc
|
|
raise RuntimeContractError(
|
|
"LMS_SOURCE_BOUNDARY_VIOLATION",
|
|
"source failed bounded safe-structure inspection",
|
|
) from exc
|
|
if time.monotonic() > deadline:
|
|
raise RuntimeContractError(
|
|
"LMS_STAGE_TIMEOUT", "source inspection exceeded its explicit timeout"
|
|
)
|
|
descriptor = report.to_dict()
|
|
component_formats = {component["format"] for component in descriptor["components"]}
|
|
if (
|
|
report.primary_format != "hf_safetensors"
|
|
or report.support_decision != "canonical_hf_ready"
|
|
or component_formats != {"hf_safetensors"}
|
|
):
|
|
raise RuntimeContractError(
|
|
"LMS_SOURCE_BOUNDARY_VIOLATION",
|
|
"source must be a canonical Hugging Face safetensors checkpoint without adapters",
|
|
)
|
|
inventory = descriptor["source_inventory"]
|
|
inventory_files = inventory["files"]
|
|
if len(inventory_files) != len(files) or inventory["total_bytes"] != observed_bytes:
|
|
raise RuntimeContractError(
|
|
"LMS_SOURCE_CHANGED", "source changed during safe-structure inspection"
|
|
)
|
|
records: list[dict[str, object]] = []
|
|
model_records: list[dict[str, object]] = []
|
|
tokenizer_records: list[dict[str, object]] = []
|
|
for observed in inventory_files:
|
|
relative_path = str(observed["relative_path"])
|
|
record = {
|
|
"path": relative_path,
|
|
"size_bytes": observed["size_bytes"],
|
|
"sha256": observed["sha256"],
|
|
}
|
|
records.append(record)
|
|
if relative_path.lower().endswith(".safetensors"):
|
|
model_records.append(record)
|
|
name = Path(relative_path).name.lower()
|
|
if any(marker in name for marker in ("token", "vocab", "merges")):
|
|
tokenizer_records.append(record)
|
|
if not model_records:
|
|
raise RuntimeContractError(
|
|
"LMS_SOURCE_BOUNDARY_VIOLATION",
|
|
"source must contain at least one safetensors file",
|
|
)
|
|
if not tokenizer_records:
|
|
raise RuntimeContractError(
|
|
"LMS_SOURCE_BOUNDARY_VIOLATION",
|
|
"source must contain immutable tokenizer metadata",
|
|
)
|
|
return SourceIdentity(
|
|
source_digest=contract_digest(records),
|
|
model_digest=contract_digest(model_records),
|
|
tokenizer_digest=contract_digest(tokenizer_records),
|
|
file_count=len(records),
|
|
total_bytes=observed_bytes,
|
|
)
|
|
|
|
|
|
def revalidate_source(
|
|
config: DistributedPreflightConfig, expected: SourceIdentity
|
|
) -> SourceIdentity:
|
|
"""Re-hash immediately before #60 allocation; replacement fails capability use."""
|
|
observed = inspect_source(
|
|
config.source_path,
|
|
max_files=config.max_source_files,
|
|
max_total_bytes=config.max_source_bytes,
|
|
max_file_bytes=config.max_source_file_bytes,
|
|
timeout_seconds=config.source_timeout_seconds,
|
|
)
|
|
if observed != expected:
|
|
raise RuntimeContractError(
|
|
"LMS_SOURCE_CHANGED",
|
|
"source identity changed after distributed preflight",
|
|
)
|
|
return observed
|
|
|
|
|
|
def _host_memory() -> tuple[int, int]:
|
|
page_size = os.sysconf("SC_PAGE_SIZE")
|
|
total = page_size * os.sysconf("SC_PHYS_PAGES")
|
|
available = page_size * os.sysconf("SC_AVPHYS_PAGES")
|
|
return total, available
|
|
|
|
|
|
def _cuda_driver_version() -> str:
|
|
try:
|
|
library = ctypes.CDLL("libcuda.so.1")
|
|
if library.cuInit(0) != 0:
|
|
return "unavailable"
|
|
value = ctypes.c_int()
|
|
if library.cuDriverGetVersion(ctypes.byref(value)) != 0:
|
|
return "unavailable"
|
|
return str(value.value)
|
|
except (AttributeError, OSError):
|
|
return "unavailable"
|
|
|
|
|
|
def _nccl_version() -> str:
|
|
try:
|
|
value = torch.cuda.nccl.version()
|
|
except Exception:
|
|
return "unavailable"
|
|
if isinstance(value, tuple):
|
|
return ".".join(str(item) for item in value)
|
|
return str(value)
|
|
|
|
|
|
def _bounded_text_file(path: Path, *, limit: int = 4096) -> str:
|
|
descriptor: int | None = None
|
|
try:
|
|
descriptor = os.open(path, os.O_RDONLY | os.O_NOFOLLOW)
|
|
metadata = os.fstat(descriptor)
|
|
if not stat.S_ISREG(metadata.st_mode) or not 1 <= metadata.st_size <= limit:
|
|
raise ContractError("checkout identity file is invalid")
|
|
raw = os.read(descriptor, limit + 1)
|
|
if len(raw) != metadata.st_size:
|
|
raise ContractError("checkout identity changed while it was read")
|
|
return raw.decode("utf-8").strip()
|
|
except ContractError:
|
|
raise
|
|
except (OSError, UnicodeError) as exc:
|
|
raise ContractError("checkout identity is unavailable") from exc
|
|
finally:
|
|
if descriptor is not None:
|
|
os.close(descriptor)
|
|
|
|
|
|
def checkout_commit(root: Path | None = None) -> str:
|
|
"""Resolve the checkout HEAD without invoking Git or another process."""
|
|
checkout = root or Path(__file__).resolve().parents[2]
|
|
dot_git = checkout / ".git"
|
|
if dot_git.is_dir() and not dot_git.is_symlink():
|
|
git_dir = dot_git
|
|
else:
|
|
pointer = _bounded_text_file(dot_git)
|
|
prefix = "gitdir: "
|
|
if not pointer.startswith(prefix):
|
|
raise ContractError("checkout identity is unavailable")
|
|
git_dir = Path(pointer[len(prefix) :])
|
|
if not git_dir.is_absolute():
|
|
git_dir = (checkout / git_dir).resolve()
|
|
head = _bounded_text_file(git_dir / "HEAD")
|
|
if _COMMIT_RE.fullmatch(head):
|
|
return head
|
|
prefix = "ref: "
|
|
if not head.startswith(prefix):
|
|
raise ContractError("checkout HEAD has an invalid format")
|
|
reference = head[len(prefix) :]
|
|
if not reference.startswith("refs/") or ".." in reference or "\\" in reference:
|
|
raise ContractError("checkout HEAD reference has an invalid format")
|
|
common_dir = git_dir
|
|
common_pointer = git_dir / "commondir"
|
|
if common_pointer.exists():
|
|
common_dir = (git_dir / _bounded_text_file(common_pointer)).resolve()
|
|
reference_path = common_dir / reference
|
|
if reference_path.exists():
|
|
commit = _bounded_text_file(reference_path)
|
|
else:
|
|
packed = _bounded_text_file(common_dir / "packed-refs", limit=1 << 20)
|
|
matches = [
|
|
line.split(" ", 1)[0]
|
|
for line in packed.splitlines()
|
|
if not line.startswith(("#", "^")) and line.endswith(f" {reference}")
|
|
]
|
|
if len(matches) != 1:
|
|
raise ContractError("checkout HEAD reference is unavailable")
|
|
commit = matches[0]
|
|
if _COMMIT_RE.fullmatch(commit) is None:
|
|
raise ContractError("checkout commit has an invalid format")
|
|
return commit
|
|
|
|
|
|
def checkout_code_digest(root: Path | None = None) -> str:
|
|
"""Hash the executable Python package independently of Git metadata."""
|
|
|
|
checkout = root or Path(__file__).resolve().parents[2]
|
|
package = checkout / "obliteratus"
|
|
records: list[dict[str, object]] = []
|
|
total_bytes = 0
|
|
for path in sorted(package.rglob("*.py")):
|
|
if len(records) >= 10_000:
|
|
raise RuntimeContractError(
|
|
"LMS_RESOURCE_ADMISSION_DENIED", "executable source exceeds its file bound"
|
|
)
|
|
if path.is_symlink() or not path.is_file():
|
|
raise RuntimeContractError(
|
|
"LMS_SOURCE_BOUNDARY_VIOLATION",
|
|
"executable source must contain only regular Python files",
|
|
)
|
|
size, digest = _hash_file(
|
|
path,
|
|
deadline=time.monotonic() + 60,
|
|
max_bytes=8 << 20,
|
|
require_read_only=False,
|
|
)
|
|
total_bytes += size
|
|
if total_bytes > 64 << 20:
|
|
raise RuntimeContractError(
|
|
"LMS_RESOURCE_ADMISSION_DENIED", "executable source exceeds its byte bound"
|
|
)
|
|
records.append(
|
|
{
|
|
"path": path.relative_to(checkout).as_posix(),
|
|
"size_bytes": size,
|
|
"sha256": digest,
|
|
}
|
|
)
|
|
if not records:
|
|
raise RuntimeContractError(
|
|
"LMS_SOURCE_BOUNDARY_VIOLATION", "executable source inventory is empty"
|
|
)
|
|
return contract_digest(records)
|
|
|
|
|
|
def storage_mount_digest(path: Path) -> str:
|
|
"""Measure the exact Linux mount backing a local staging directory."""
|
|
|
|
try:
|
|
resolved = path.resolve(strict=True)
|
|
if resolved != path.absolute() or path.is_symlink() or not path.is_dir():
|
|
raise RuntimeContractError(
|
|
"LMS_STORAGE_PROFILE_MISMATCH",
|
|
"staging must be a local non-symlink directory",
|
|
)
|
|
descriptor = os.open("/proc/self/mountinfo", os.O_RDONLY | os.O_NOFOLLOW)
|
|
try:
|
|
raw = os.read(descriptor, (4 << 20) + 1)
|
|
finally:
|
|
os.close(descriptor)
|
|
if len(raw) > 4 << 20:
|
|
raise RuntimeContractError(
|
|
"LMS_RESOURCE_ADMISSION_DENIED", "mount inventory exceeds its byte bound"
|
|
)
|
|
lines = raw.decode("utf-8").splitlines()
|
|
except RuntimeContractError:
|
|
raise
|
|
except (OSError, UnicodeError) as exc:
|
|
raise RuntimeContractError(
|
|
"LMS_STORAGE_PROFILE_MISMATCH", "staging mount identity is unavailable"
|
|
) from exc
|
|
|
|
def unescape(value: str) -> str:
|
|
return (
|
|
value.replace("\\040", " ")
|
|
.replace("\\011", "\t")
|
|
.replace("\\012", "\n")
|
|
.replace("\\134", "\\")
|
|
)
|
|
|
|
candidates: list[tuple[int, dict[str, object]]] = []
|
|
for line in lines:
|
|
if " - " not in line:
|
|
continue
|
|
left, right = line.split(" - ", 1)
|
|
left_fields = left.split()
|
|
right_fields = right.split()
|
|
if len(left_fields) < 6 or len(right_fields) < 3:
|
|
continue
|
|
mountpoint = Path(unescape(left_fields[4]))
|
|
try:
|
|
resolved.relative_to(mountpoint)
|
|
except ValueError:
|
|
continue
|
|
candidates.append(
|
|
(
|
|
len(mountpoint.parts),
|
|
{
|
|
"root": unescape(left_fields[3]),
|
|
"mountpoint": mountpoint.as_posix(),
|
|
"mount_options": sorted(left_fields[5].split(",")),
|
|
"filesystem": right_fields[0],
|
|
"source": unescape(right_fields[1]),
|
|
"super_options": sorted(right_fields[2].split(",")),
|
|
},
|
|
)
|
|
)
|
|
if not candidates:
|
|
raise RuntimeContractError(
|
|
"LMS_STORAGE_PROFILE_MISMATCH", "staging mount identity is unavailable"
|
|
)
|
|
return contract_digest(max(candidates, key=lambda item: item[0])[1])
|
|
|
|
|
|
class SystemProbes:
|
|
"""Production local probes; tests inject deterministic snapshots instead."""
|
|
|
|
def collect(
|
|
self, config: DistributedPreflightConfig, launch: TorchrunEnvironment
|
|
) -> LocalSnapshot:
|
|
source = inspect_source(
|
|
config.source_path,
|
|
max_files=config.max_source_files,
|
|
max_total_bytes=config.max_source_bytes,
|
|
max_file_bytes=config.max_source_file_bytes,
|
|
timeout_seconds=config.source_timeout_seconds,
|
|
)
|
|
if config.staging_path.is_symlink() or not config.staging_path.is_dir():
|
|
raise RuntimeContractError(
|
|
"LMS_STORAGE_PROFILE_MISMATCH",
|
|
"staging must be a local non-symlink directory",
|
|
)
|
|
disk = shutil.disk_usage(config.staging_path)
|
|
validate_network_interface(
|
|
config.network_interface,
|
|
config.allowed_master_cidrs,
|
|
config.master_addr,
|
|
coordinator=launch.rank == config.coordinator_rank,
|
|
)
|
|
host_total, host_free = _host_memory()
|
|
software = tuple(
|
|
sorted(
|
|
{
|
|
"python": platform.python_version(),
|
|
"platform": platform.platform(),
|
|
"machine": platform.machine(),
|
|
"torch": torch.__version__.split("+")[0],
|
|
"transformers": transformers.__version__,
|
|
"accelerate": accelerate.__version__,
|
|
"safetensors": safetensors.__version__,
|
|
"cuda": torch.version.cuda or "unavailable",
|
|
"nccl": _nccl_version(),
|
|
"driver": _cuda_driver_version(),
|
|
}.items()
|
|
)
|
|
)
|
|
if config.device_kind == "cuda":
|
|
if not torch.cuda.is_available():
|
|
raise RuntimeContractError(
|
|
"LMS_RUNTIME_PROFILE_MISMATCH",
|
|
"the configured CUDA device is unavailable",
|
|
)
|
|
torch.cuda.set_device(launch.local_rank)
|
|
properties = torch.cuda.get_device_properties(launch.local_rank)
|
|
device_uuid = getattr(properties, "uuid", None)
|
|
if not device_uuid:
|
|
raise RuntimeContractError(
|
|
"LMS_RUNTIME_PROFILE_MISMATCH",
|
|
"the CUDA device UUID is unavailable",
|
|
)
|
|
free_device, total_device = torch.cuda.mem_get_info(launch.local_rank)
|
|
device_identity = str(device_uuid)
|
|
device_name = properties.name
|
|
compute_capability = f"{properties.major}.{properties.minor}"
|
|
else:
|
|
total_device, free_device = host_total, host_free
|
|
device_identity = f"cpu:{platform.node()}:{platform.machine()}:{launch.local_rank}"
|
|
device_name = "cpu"
|
|
compute_capability = "none"
|
|
try:
|
|
commit_sha = checkout_commit()
|
|
code_digest = checkout_code_digest()
|
|
except RuntimeContractError:
|
|
raise
|
|
except ContractError as exc:
|
|
raise RuntimeContractError(
|
|
"LMS_RUNTIME_PROFILE_MISMATCH",
|
|
"checkout identity cannot be verified",
|
|
) from exc
|
|
return LocalSnapshot(
|
|
host_identity=platform.node(),
|
|
device_identity=device_identity,
|
|
device_name=device_name,
|
|
compute_capability=compute_capability,
|
|
device_kind=config.device_kind,
|
|
total_device_memory_bytes=total_device,
|
|
free_device_memory_bytes=free_device,
|
|
total_host_memory_bytes=host_total,
|
|
free_host_memory_bytes=host_free,
|
|
free_staging_bytes=disk.free,
|
|
storage_identity=storage_mount_digest(config.staging_path),
|
|
source=source,
|
|
software_versions=software,
|
|
commit_sha=commit_sha,
|
|
code_digest=code_digest,
|
|
)
|
|
|
|
|
|
def _opaque_identity(value: str, rendezvous_id: str) -> str:
|
|
return hmac.new(bytes.fromhex(rendezvous_id), value.encode(), hashlib.sha256).hexdigest()
|
|
|
|
|
|
def _attestation(
|
|
config: DistributedPreflightConfig,
|
|
launch: TorchrunEnvironment,
|
|
snapshot: LocalSnapshot,
|
|
) -> RankAttestation:
|
|
source = snapshot.source
|
|
return RankAttestation(
|
|
rank=launch.rank,
|
|
local_rank=launch.local_rank,
|
|
local_world_size=launch.local_world_size,
|
|
group_rank=launch.group_rank,
|
|
world_size=launch.world_size,
|
|
host_digest=_opaque_identity(snapshot.host_identity, launch.rendezvous_id),
|
|
device_digest=_opaque_identity(snapshot.device_identity, launch.rendezvous_id),
|
|
device_profile_digest=contract_digest(
|
|
{
|
|
"name": snapshot.device_name,
|
|
"compute_capability": snapshot.compute_capability,
|
|
"total_memory_bytes": snapshot.total_device_memory_bytes,
|
|
}
|
|
),
|
|
device_config_digest=contract_digest(
|
|
{
|
|
"kind": snapshot.device_kind,
|
|
"name": snapshot.device_name,
|
|
"compute_capability": snapshot.compute_capability,
|
|
}
|
|
),
|
|
device_kind=snapshot.device_kind,
|
|
total_device_memory_bytes=snapshot.total_device_memory_bytes,
|
|
free_device_memory_bytes=snapshot.free_device_memory_bytes,
|
|
total_host_memory_bytes=snapshot.total_host_memory_bytes,
|
|
free_host_memory_bytes=snapshot.free_host_memory_bytes,
|
|
free_staging_bytes=snapshot.free_staging_bytes,
|
|
software_digest=contract_digest(dict(snapshot.software_versions)),
|
|
storage_digest=snapshot.storage_identity,
|
|
source_digest=source.source_digest,
|
|
model_digest=source.model_digest,
|
|
tokenizer_digest=source.tokenizer_digest,
|
|
config_digest=config.digest,
|
|
commit_sha=snapshot.commit_sha,
|
|
code_digest=snapshot.code_digest,
|
|
placement_plan_digest=config.placement_plan_digest,
|
|
network_interface_digest=_opaque_identity(config.network_interface, launch.rendezvous_id),
|
|
)
|
|
|
|
|
|
def _prepare_attempt_directory(
|
|
config: DistributedPreflightConfig, launch: TorchrunEnvironment
|
|
) -> Path:
|
|
attempt = config.staging_path / config.run_id
|
|
staging_descriptor: int | None = None
|
|
attempt_descriptor: int | None = None
|
|
try:
|
|
staging_descriptor = os.open(
|
|
config.staging_path, os.O_RDONLY | os.O_DIRECTORY | os.O_NOFOLLOW
|
|
)
|
|
if launch.rank == config.coordinator_rank:
|
|
try:
|
|
os.mkdir(config.run_id, mode=0o700, dir_fd=staging_descriptor)
|
|
except FileExistsError as exc:
|
|
raise RuntimeContractError(
|
|
"LMS_ATTEMPT_IDENTITY_CONFLICT",
|
|
"distributed staging attempt cannot be reused",
|
|
) from exc
|
|
dist.barrier()
|
|
attempt_descriptor = os.open(
|
|
config.run_id,
|
|
os.O_RDONLY | os.O_DIRECTORY | os.O_NOFOLLOW,
|
|
dir_fd=staging_descriptor,
|
|
)
|
|
metadata = os.fstat(attempt_descriptor)
|
|
effective_uid = getattr(os, "geteuid", lambda: metadata.st_uid)()
|
|
if metadata.st_uid != effective_uid or metadata.st_mode & 0o077:
|
|
raise RuntimeContractError(
|
|
"LMS_STORAGE_PROFILE_MISMATCH",
|
|
"distributed staging attempt is not private and worker-owned",
|
|
)
|
|
dist.barrier()
|
|
return attempt
|
|
except RuntimeContractError:
|
|
raise
|
|
except OSError as exc:
|
|
raise RuntimeContractError(
|
|
"LMS_STORAGE_PROFILE_MISMATCH",
|
|
"distributed staging attempt is unavailable",
|
|
) from exc
|
|
finally:
|
|
if attempt_descriptor is not None:
|
|
os.close(attempt_descriptor)
|
|
if staging_descriptor is not None:
|
|
os.close(staging_descriptor)
|
|
|
|
|
|
def _probe_shared_staging(config: DistributedPreflightConfig, launch: TorchrunEnvironment) -> Path:
|
|
attempt = config.staging_path / config.run_id
|
|
pending = ".shared-probe.pending"
|
|
ready = ".shared-probe.ready"
|
|
nonce = contract_digest(
|
|
{"run_id": config.run_id, "rendezvous_id": config.rendezvous_id}
|
|
).encode()
|
|
staging_descriptor: int | None = None
|
|
attempt_descriptor: int | None = None
|
|
try:
|
|
staging_descriptor = os.open(
|
|
config.staging_path, os.O_RDONLY | os.O_DIRECTORY | os.O_NOFOLLOW
|
|
)
|
|
try:
|
|
attempt_descriptor = os.open(
|
|
config.run_id,
|
|
os.O_RDONLY | os.O_DIRECTORY | os.O_NOFOLLOW,
|
|
dir_fd=staging_descriptor,
|
|
)
|
|
except OSError as exc:
|
|
raise RuntimeContractError(
|
|
"LMS_STORAGE_PROFILE_MISMATCH",
|
|
"shared staging attempt is not visible to every rank",
|
|
) from exc
|
|
if launch.rank == config.coordinator_rank:
|
|
descriptor = os.open(
|
|
pending,
|
|
os.O_WRONLY | os.O_CREAT | os.O_EXCL | os.O_NOFOLLOW,
|
|
0o600,
|
|
dir_fd=attempt_descriptor,
|
|
)
|
|
try:
|
|
if os.write(descriptor, nonce) != len(nonce):
|
|
raise RuntimeContractError(
|
|
"LMS_STORAGE_PROFILE_MISMATCH",
|
|
"shared staging probe write was incomplete",
|
|
)
|
|
os.fsync(descriptor)
|
|
finally:
|
|
os.close(descriptor)
|
|
os.rename(
|
|
pending,
|
|
ready,
|
|
src_dir_fd=attempt_descriptor,
|
|
dst_dir_fd=attempt_descriptor,
|
|
)
|
|
os.fsync(attempt_descriptor)
|
|
dist.barrier()
|
|
descriptor = os.open(ready, os.O_RDONLY | os.O_NOFOLLOW, dir_fd=attempt_descriptor)
|
|
try:
|
|
observed = os.read(descriptor, len(nonce) + 1)
|
|
finally:
|
|
os.close(descriptor)
|
|
if observed != nonce:
|
|
raise RuntimeContractError(
|
|
"LMS_STORAGE_PROFILE_MISMATCH",
|
|
"shared staging probe identity disagrees",
|
|
)
|
|
dist.barrier()
|
|
if launch.rank == config.coordinator_rank:
|
|
os.unlink(ready, dir_fd=attempt_descriptor)
|
|
os.fsync(attempt_descriptor)
|
|
dist.barrier()
|
|
return attempt
|
|
except ContractError:
|
|
raise
|
|
except OSError as exc:
|
|
raise RuntimeContractError(
|
|
"LMS_STORAGE_PROFILE_MISMATCH", "shared staging probe failed"
|
|
) from exc
|
|
finally:
|
|
cleanup_failed = False
|
|
if attempt_descriptor is not None and launch.rank == config.coordinator_rank:
|
|
for marker in (pending, ready):
|
|
try:
|
|
os.unlink(marker, dir_fd=attempt_descriptor)
|
|
except FileNotFoundError:
|
|
pass
|
|
except OSError:
|
|
cleanup_failed = True
|
|
try:
|
|
os.fsync(attempt_descriptor)
|
|
except OSError:
|
|
cleanup_failed = True
|
|
if attempt_descriptor is not None:
|
|
os.close(attempt_descriptor)
|
|
if staging_descriptor is not None:
|
|
os.close(staging_descriptor)
|
|
if cleanup_failed:
|
|
raise RuntimeContractError("LMS_CLEANUP_INCOMPLETE", "shared staging cleanup failed")
|
|
|
|
|
|
def _run_identity(config: DistributedPreflightConfig) -> RunIdentity:
|
|
return RunIdentity(
|
|
run_id=config.run_id,
|
|
config_digest=config.digest,
|
|
source_digest=config.source_digest,
|
|
model_digest=config.model_digest,
|
|
tokenizer_digest=config.tokenizer_digest,
|
|
commit_sha=config.commit_sha,
|
|
world_size=config.world_size,
|
|
)
|
|
|
|
|
|
def _begin_lifecycle(
|
|
config: DistributedPreflightConfig,
|
|
launch: TorchrunEnvironment,
|
|
) -> tuple[RunIdentity, str, tuple[StageMessage, ...]]:
|
|
identity = _run_identity(config)
|
|
try:
|
|
require_record_consensus(identity)
|
|
except RuntimeContractError:
|
|
raise
|
|
except ContractError as exc:
|
|
raise RuntimeContractError("LMS_IDENTITY_MISMATCH", "rank run identities disagree") from exc
|
|
lifecycle_identity_digest = contract_digest(identity)
|
|
created = StageMessage(
|
|
run_id=config.run_id,
|
|
identity_digest=lifecycle_identity_digest,
|
|
rank=launch.rank,
|
|
sequence=0,
|
|
stage=RuntimeStage.CREATED,
|
|
)
|
|
created_messages = tuple(
|
|
_stage_message_from_bytes(item) for item in gloo_all_gather_records(created)
|
|
)
|
|
_validate_stage_messages(
|
|
created_messages,
|
|
run_id=config.run_id,
|
|
world_size=config.world_size,
|
|
stage=RuntimeStage.CREATED,
|
|
sequence=0,
|
|
identity_digest=lifecycle_identity_digest,
|
|
vote=None,
|
|
)
|
|
return identity, lifecycle_identity_digest, created_messages
|
|
|
|
|
|
def run_preflight(
|
|
config: DistributedPreflightConfig,
|
|
launch: TorchrunEnvironment,
|
|
*,
|
|
probes: PreflightProbes | None = None,
|
|
lifecycle: tuple[RunIdentity, str, tuple[StageMessage, ...]] | None = None,
|
|
) -> PreflightResult:
|
|
"""Admit the complete worker group; this function has no model allocation path."""
|
|
|
|
try:
|
|
config.validate()
|
|
except RuntimeContractError:
|
|
raise
|
|
except ContractError as exc:
|
|
raise RuntimeContractError(
|
|
"LMS_RUNTIME_PROFILE_MISMATCH",
|
|
"distributed runtime profile validation failed",
|
|
) from exc
|
|
identity, lifecycle_identity_digest, created_messages = lifecycle or _begin_lifecycle(
|
|
config, launch
|
|
)
|
|
attempt = _prepare_attempt_directory(config, launch)
|
|
with _stage_deadline(
|
|
min(config.source_timeout_seconds, config.collective_timeout_seconds),
|
|
"local preflight probes exceeded their explicit timeout",
|
|
):
|
|
snapshot = (probes or SystemProbes()).collect(config, launch)
|
|
local = _attestation(config, launch, snapshot)
|
|
try:
|
|
gathered = gloo_all_gather_records(local)
|
|
attestations = tuple(RankAttestation.from_bytes(item) for item in gathered)
|
|
except RuntimeContractError:
|
|
raise
|
|
except ContractError as exc:
|
|
raise RuntimeContractError(
|
|
"LMS_CONSENSUS_RECORD_INVALID", "rank attestation record is invalid"
|
|
) from exc
|
|
validate_attestations(
|
|
attestations,
|
|
world_size=config.world_size,
|
|
tensor_parallel_size=config.tensor_parallel_size,
|
|
dimension_divisors=config.dimension_divisors,
|
|
expected_device_kind=config.device_kind,
|
|
expected_device_config_digest=contract_digest(
|
|
{
|
|
"kind": config.device_kind,
|
|
"name": config.device_name,
|
|
"compute_capability": config.compute_capability,
|
|
}
|
|
),
|
|
expected_software_digest=config.software_digest,
|
|
expected_source_digest=config.source_digest,
|
|
expected_model_digest=config.model_digest,
|
|
expected_tokenizer_digest=config.tokenizer_digest,
|
|
expected_config_digest=config.digest,
|
|
expected_commit_sha=config.commit_sha,
|
|
expected_code_digest=config.code_digest,
|
|
expected_placement_plan_digest=config.placement_plan_digest,
|
|
expected_storage_digest=config.storage_digest,
|
|
expected_network_interface_digest=_opaque_identity(
|
|
config.network_interface, launch.rendezvous_id
|
|
),
|
|
local_world_size=config.local_world_size,
|
|
min_free_device_memory_bytes=config.min_free_device_memory_bytes,
|
|
min_free_host_memory_bytes=config.min_free_host_memory_bytes,
|
|
min_free_staging_bytes=config.min_free_staging_bytes,
|
|
)
|
|
if _probe_shared_staging(config, launch) != attempt:
|
|
raise RuntimeContractError(
|
|
"LMS_STORAGE_PROFILE_MISMATCH", "staging attempt identity changed"
|
|
)
|
|
if not unanimous_vote(0, True):
|
|
raise ContractError("preflight admission was not unanimous")
|
|
topology = TopologyPlan(
|
|
world_size=config.world_size,
|
|
coordinator_rank=config.coordinator_rank,
|
|
backend="gloo",
|
|
placement_plan_digest=config.placement_plan_digest,
|
|
)
|
|
identity_digest = contract_digest(
|
|
{
|
|
"identity": identity,
|
|
"topology": topology,
|
|
"attestations": attestations,
|
|
"created_messages": created_messages,
|
|
}
|
|
)
|
|
return PreflightResult(
|
|
identity,
|
|
topology,
|
|
attestations,
|
|
identity_digest,
|
|
attempt,
|
|
snapshot.source,
|
|
lifecycle_identity_digest,
|
|
created_messages,
|
|
)
|
|
|
|
|
|
def _failure_code(error: BaseException) -> str:
|
|
if isinstance(error, RuntimeContractError):
|
|
return error.code
|
|
if isinstance(error, KeyboardInterrupt):
|
|
return "LMS_ATTEMPT_CANCELLED"
|
|
if isinstance(error, TimeoutError):
|
|
return "LMS_STAGE_TIMEOUT"
|
|
if isinstance(error, ContractError):
|
|
return "LMS_LIFECYCLE_INVALID"
|
|
return "LMS_COLLECTIVE_FAILED"
|
|
|
|
|
|
_FAILURE_PRIORITY = (
|
|
"LMS_CLEANUP_INCOMPLETE",
|
|
"LMS_DIAGNOSTIC_REDACTION_FAILED",
|
|
"LMS_SECURITY_BASELINE_REVOKED",
|
|
"LMS_SECRET_INPUT_REJECTED",
|
|
"LMS_SOURCE_CHANGED",
|
|
"LMS_SOURCE_BOUNDARY_VIOLATION",
|
|
"LMS_IDENTITY_MISMATCH",
|
|
"LMS_STORAGE_PROFILE_MISMATCH",
|
|
"LMS_NETWORK_PROFILE_DENIED",
|
|
"LMS_RUNTIME_PROFILE_MISMATCH",
|
|
"LMS_RESOURCE_ADMISSION_DENIED",
|
|
"LMS_ATTEMPT_CANCELLED",
|
|
"LMS_ATTEMPT_IDENTITY_CONFLICT",
|
|
"LMS_MEMBERSHIP_INVALID",
|
|
"LMS_MEMBERSHIP_TIMEOUT",
|
|
"LMS_ELASTICITY_FORBIDDEN",
|
|
"LMS_LAUNCH_IDENTITY_INVALID",
|
|
"LMS_RANK_DEVICE_CONFLICT",
|
|
"LMS_TOPOLOGY_UNSUPPORTED",
|
|
"LMS_TRANSPORT_BOUNDARY_UNSATISFIED",
|
|
"LMS_FORBIDDEN_RUNTIME_CAPABILITY",
|
|
"LMS_ATOMIC_PROMOTION_UNAVAILABLE",
|
|
"LMS_STAGE_TIMEOUT",
|
|
"LMS_CONSENSUS_RECORD_INVALID",
|
|
"LMS_LIFECYCLE_INVALID",
|
|
"LMS_EVIDENCE_UNAVAILABLE",
|
|
"LMS_EVIDENCE_SCOPE_MISMATCH",
|
|
"LMS_CLOCK_PROFILE_MISMATCH",
|
|
"LMS_DISTRIBUTED_INTENT_REQUIRED",
|
|
"LMS_COLLECTIVE_FAILED",
|
|
)
|
|
|
|
|
|
def _select_failure_code(codes: set[str]) -> str:
|
|
for code in _FAILURE_PRIORITY:
|
|
if code in codes:
|
|
return code
|
|
return "LMS_COLLECTIVE_FAILED"
|
|
|
|
|
|
def _collective_aborting(
|
|
config: DistributedPreflightConfig,
|
|
launch: TorchrunEnvironment,
|
|
identity_digest: str,
|
|
local_code: str,
|
|
) -> tuple[str, tuple[StageMessage, ...]]:
|
|
advance_stage(RuntimeStage.CREATED, RuntimeStage.ABORTING)
|
|
local = StageMessage(
|
|
run_id=config.run_id,
|
|
identity_digest=identity_digest,
|
|
rank=launch.rank,
|
|
sequence=1,
|
|
stage=RuntimeStage.ABORTING,
|
|
vote=Vote.ABORT,
|
|
error_code=local_code,
|
|
)
|
|
try:
|
|
messages = tuple(_stage_message_from_bytes(item) for item in gloo_all_gather_records(local))
|
|
except RuntimeContractError:
|
|
raise
|
|
except (ContractError, RuntimeError) as exc:
|
|
raise RuntimeContractError(
|
|
"LMS_LIFECYCLE_INVALID",
|
|
"collective abort lifecycle could not be established",
|
|
) from exc
|
|
if len(messages) != config.world_size or {item.rank for item in messages} != set(
|
|
range(config.world_size)
|
|
):
|
|
raise RuntimeContractError(
|
|
"LMS_MEMBERSHIP_INVALID",
|
|
"abort lifecycle records do not cover the fixed world",
|
|
)
|
|
if any(
|
|
item.run_id != config.run_id
|
|
or item.identity_digest != identity_digest
|
|
or item.sequence != 1
|
|
or item.stage is not RuntimeStage.ABORTING
|
|
or item.vote is not Vote.ABORT
|
|
or item.error_code is None
|
|
for item in messages
|
|
):
|
|
raise RuntimeContractError("LMS_LIFECYCLE_INVALID", "rank abort lifecycle records disagree")
|
|
return _select_failure_code({item.error_code for item in messages if item.error_code}), messages
|
|
|
|
|
|
def _write_local_terminal_receipts(
|
|
attempt: Path,
|
|
*,
|
|
config: DistributedPreflightConfig,
|
|
launch: TorchrunEnvironment,
|
|
identity_digest: str,
|
|
code: str,
|
|
quarantined: bool,
|
|
) -> None:
|
|
aborting = StageMessage(
|
|
run_id=config.run_id,
|
|
identity_digest=identity_digest,
|
|
rank=launch.rank,
|
|
sequence=1,
|
|
stage=RuntimeStage.ABORTING,
|
|
vote=Vote.ABORT,
|
|
error_code=code,
|
|
)
|
|
terminal_stage = RuntimeStage.QUARANTINED if quarantined else RuntimeStage.ABORTED
|
|
advance_stage(RuntimeStage.ABORTING, terminal_stage)
|
|
terminal = StageMessage(
|
|
run_id=config.run_id,
|
|
identity_digest=identity_digest,
|
|
rank=launch.rank,
|
|
sequence=2,
|
|
stage=terminal_stage,
|
|
vote=Vote.ABORT,
|
|
error_code=code,
|
|
)
|
|
write_stage_message(attempt / f".rank-{launch.rank}.aborting.stage.json", aborting)
|
|
write_stage_message(attempt / f".rank-{launch.rank}.terminal.stage.json", terminal)
|
|
|
|
|
|
def _read_terminal_receipts(
|
|
attempt: Path,
|
|
*,
|
|
config: DistributedPreflightConfig,
|
|
identity_digest: str,
|
|
) -> tuple[tuple[StageMessage, ...], tuple[StageMessage, ...]]:
|
|
aborting = tuple(
|
|
read_stage_message(attempt / f".rank-{rank}.aborting.stage.json")
|
|
for rank in range(config.world_size)
|
|
)
|
|
terminal = tuple(
|
|
read_stage_message(attempt / f".rank-{rank}.terminal.stage.json")
|
|
for rank in range(config.world_size)
|
|
)
|
|
expected_ranks = list(range(config.world_size))
|
|
if [item.rank for item in aborting] != expected_ranks or [
|
|
item.rank for item in terminal
|
|
] != expected_ranks:
|
|
raise RuntimeContractError(
|
|
"LMS_MEMBERSHIP_INVALID", "terminal lifecycle receipts omit a fixed rank"
|
|
)
|
|
for before, after in zip(aborting, terminal, strict=True):
|
|
if (
|
|
before.run_id != config.run_id
|
|
or after.run_id != config.run_id
|
|
or before.identity_digest != identity_digest
|
|
or after.identity_digest != identity_digest
|
|
or before.sequence != 1
|
|
or after.sequence != 2
|
|
or before.stage is not RuntimeStage.ABORTING
|
|
or after.stage not in {RuntimeStage.ABORTED, RuntimeStage.QUARANTINED}
|
|
or before.vote is not Vote.ABORT
|
|
or after.vote is not Vote.ABORT
|
|
or before.error_code is None
|
|
or after.error_code != before.error_code
|
|
):
|
|
raise RuntimeContractError(
|
|
"LMS_LIFECYCLE_INVALID", "terminal lifecycle receipts disagree"
|
|
)
|
|
advance_stage(before.stage, after.stage)
|
|
return aborting, terminal
|
|
|
|
|
|
def _ensure_failure_attempt(config: DistributedPreflightConfig) -> Path:
|
|
attempt = config.staging_path / config.run_id
|
|
if attempt.exists():
|
|
return attempt
|
|
descriptor: int | None = None
|
|
try:
|
|
descriptor = os.open(config.staging_path, os.O_RDONLY | os.O_DIRECTORY | os.O_NOFOLLOW)
|
|
os.mkdir(config.run_id, mode=0o700, dir_fd=descriptor)
|
|
os.fsync(descriptor)
|
|
return attempt
|
|
except FileExistsError:
|
|
return attempt
|
|
except OSError as exc:
|
|
raise RuntimeContractError(
|
|
"LMS_EVIDENCE_UNAVAILABLE",
|
|
"failure evidence directory could not be established",
|
|
) from exc
|
|
finally:
|
|
if descriptor is not None:
|
|
os.close(descriptor)
|
|
|
|
|
|
def _wait_for_evidence(path: Path, *, timeout_seconds: int) -> PreflightEvidence:
|
|
deadline = time.monotonic() + timeout_seconds
|
|
while True:
|
|
try:
|
|
return read_evidence(path)
|
|
except FileNotFoundError:
|
|
if time.monotonic() >= deadline:
|
|
raise RuntimeContractError(
|
|
"LMS_EVIDENCE_UNAVAILABLE",
|
|
"terminal evidence was not published within its bound",
|
|
) from None
|
|
time.sleep(0.01)
|
|
|
|
|
|
def _write_terminal_evidence(path: Path, evidence: PreflightEvidence) -> None:
|
|
try:
|
|
write_evidence(path, evidence)
|
|
except Exception as exc:
|
|
raise RuntimeContractError(
|
|
"LMS_EVIDENCE_UNAVAILABLE", "terminal evidence could not be persisted"
|
|
) from exc
|
|
|
|
|
|
def execute_preflight(
|
|
config: DistributedPreflightConfig,
|
|
launch: TorchrunEnvironment,
|
|
*,
|
|
probes: PreflightProbes | None = None,
|
|
) -> PreflightEvidence:
|
|
"""Own the control group and emit a redacted terminal preflight record."""
|
|
|
|
result: PreflightResult | None = None
|
|
prepared_evidence: PreflightEvidence | None = None
|
|
lifecycle_identity_digest = contract_digest(_run_identity(config))
|
|
terminal_identity: str | None = None
|
|
local_code: str | None = None
|
|
try:
|
|
with control_group(config, launch):
|
|
try:
|
|
lifecycle = _begin_lifecycle(config, launch)
|
|
result = run_preflight(
|
|
config,
|
|
launch,
|
|
probes=probes,
|
|
lifecycle=lifecycle,
|
|
)
|
|
advance_stage(RuntimeStage.CREATED, RuntimeStage.PREFLIGHTED)
|
|
preflighted = StageMessage(
|
|
run_id=config.run_id,
|
|
identity_digest=result.lifecycle_identity_digest,
|
|
rank=launch.rank,
|
|
sequence=1,
|
|
stage=RuntimeStage.PREFLIGHTED,
|
|
vote=Vote.PREPARED,
|
|
)
|
|
preflighted_messages = tuple(
|
|
_stage_message_from_bytes(item) for item in gloo_all_gather_records(preflighted)
|
|
)
|
|
_validate_stage_messages(
|
|
preflighted_messages,
|
|
run_id=config.run_id,
|
|
world_size=config.world_size,
|
|
stage=RuntimeStage.PREFLIGHTED,
|
|
sequence=1,
|
|
identity_digest=result.lifecycle_identity_digest,
|
|
vote=Vote.PREPARED,
|
|
)
|
|
terminal_identity = contract_digest(
|
|
{
|
|
"admission_identity": result.identity_digest,
|
|
"preflighted_messages": preflighted_messages,
|
|
}
|
|
)
|
|
prepared_evidence = PreflightEvidence.success(
|
|
run_id=config.run_id,
|
|
config_digest=config.digest,
|
|
world_size=config.world_size,
|
|
evidence_tier=config.evidence_tier,
|
|
identity_digest=terminal_identity,
|
|
)
|
|
prepared_marker = StageMessage(
|
|
run_id=config.run_id,
|
|
identity_digest=terminal_identity,
|
|
rank=config.coordinator_rank,
|
|
sequence=1,
|
|
stage=RuntimeStage.PREFLIGHTED,
|
|
vote=Vote.PREPARED,
|
|
)
|
|
prepared_path = result.attempt_path / ".preflight.prepared.stage.json"
|
|
local_sink_ready = True
|
|
if launch.rank == config.coordinator_rank:
|
|
try:
|
|
write_stage_message(prepared_path, prepared_marker)
|
|
except Exception:
|
|
local_sink_ready = False
|
|
dist.barrier()
|
|
try:
|
|
observed = read_stage_message(prepared_path)
|
|
local_sink_ready = local_sink_ready and observed == prepared_marker
|
|
except Exception:
|
|
local_sink_ready = False
|
|
if not unanimous_vote(2, local_sink_ready):
|
|
raise RuntimeContractError(
|
|
"LMS_EVIDENCE_UNAVAILABLE",
|
|
"prepared lifecycle record was not verified by every rank",
|
|
)
|
|
except BaseException as error:
|
|
local_code = _failure_code(error)
|
|
try:
|
|
local_code, _messages = _collective_aborting(
|
|
config,
|
|
launch,
|
|
lifecycle_identity_digest,
|
|
local_code,
|
|
)
|
|
except BaseException as lifecycle_error:
|
|
local_code = _select_failure_code({local_code, _failure_code(lifecycle_error)})
|
|
raise RuntimeContractError(local_code, "distributed preflight aborted") from None
|
|
except BaseException as error:
|
|
local_code = _select_failure_code(
|
|
{code for code in (local_code, _failure_code(error)) if code is not None}
|
|
)
|
|
|
|
attempt = result.attempt_path if result is not None else config.staging_path / config.run_id
|
|
if launch.rank == config.coordinator_rank and not attempt.exists():
|
|
try:
|
|
attempt = _ensure_failure_attempt(config)
|
|
except RuntimeContractError as error:
|
|
local_code = _select_failure_code(
|
|
{code for code in (local_code, error.code) if code is not None}
|
|
)
|
|
|
|
if local_code is None and prepared_evidence is not None and terminal_identity is not None:
|
|
acknowledgement = StageMessage(
|
|
run_id=config.run_id,
|
|
identity_digest=terminal_identity,
|
|
rank=launch.rank,
|
|
sequence=2,
|
|
stage=RuntimeStage.PREFLIGHTED,
|
|
vote=Vote.COMMITTED,
|
|
)
|
|
try:
|
|
write_stage_message(
|
|
attempt / f".rank-{launch.rank}.teardown.stage.json",
|
|
acknowledgement,
|
|
)
|
|
except Exception:
|
|
local_code = "LMS_CLEANUP_INCOMPLETE"
|
|
else:
|
|
deadline = time.monotonic() + config.teardown_timeout_seconds
|
|
while not attempt.is_dir() and time.monotonic() < deadline:
|
|
time.sleep(0.01)
|
|
try:
|
|
_write_local_terminal_receipts(
|
|
attempt,
|
|
config=config,
|
|
launch=launch,
|
|
identity_digest=lifecycle_identity_digest,
|
|
code=local_code or "LMS_COLLECTIVE_FAILED",
|
|
quarantined=local_code == "LMS_CLEANUP_INCOMPLETE",
|
|
)
|
|
except Exception:
|
|
local_code = "LMS_CLEANUP_INCOMPLETE"
|
|
|
|
if launch.rank == config.coordinator_rank:
|
|
terminal: PreflightEvidence
|
|
if (
|
|
local_code is not None
|
|
or result is None
|
|
or prepared_evidence is None
|
|
or terminal_identity is None
|
|
):
|
|
deadline = time.monotonic() + config.teardown_timeout_seconds
|
|
receipts: tuple[tuple[StageMessage, ...], tuple[StageMessage, ...]] | None = None
|
|
while time.monotonic() < deadline:
|
|
try:
|
|
receipts = _read_terminal_receipts(
|
|
attempt,
|
|
config=config,
|
|
identity_digest=lifecycle_identity_digest,
|
|
)
|
|
break
|
|
except FileNotFoundError:
|
|
time.sleep(0.01)
|
|
except Exception:
|
|
break
|
|
terminal_code = local_code or "LMS_COLLECTIVE_FAILED"
|
|
if receipts is None:
|
|
terminal_code = "LMS_CLEANUP_INCOMPLETE"
|
|
else:
|
|
_aborting, completed = receipts
|
|
terminal_code = _select_failure_code(
|
|
{item.error_code for item in completed if item.error_code}
|
|
)
|
|
if any(item.stage is RuntimeStage.QUARANTINED for item in completed):
|
|
terminal_code = "LMS_CLEANUP_INCOMPLETE"
|
|
terminal = PreflightEvidence.failure(
|
|
run_id=config.run_id,
|
|
config_digest=config.digest,
|
|
code=terminal_code,
|
|
world_size=config.world_size,
|
|
evidence_tier=config.evidence_tier,
|
|
)
|
|
else:
|
|
deadline = time.monotonic() + config.teardown_timeout_seconds
|
|
all_acknowledged = False
|
|
while time.monotonic() < deadline:
|
|
try:
|
|
acknowledgements = tuple(
|
|
read_stage_message(
|
|
result.attempt_path / f".rank-{rank}.teardown.stage.json"
|
|
)
|
|
for rank in range(config.world_size)
|
|
)
|
|
except FileNotFoundError:
|
|
time.sleep(0.01)
|
|
continue
|
|
except Exception:
|
|
break
|
|
try:
|
|
_validate_stage_messages(
|
|
acknowledgements,
|
|
run_id=config.run_id,
|
|
world_size=config.world_size,
|
|
stage=RuntimeStage.PREFLIGHTED,
|
|
sequence=2,
|
|
identity_digest=terminal_identity,
|
|
vote=Vote.COMMITTED,
|
|
)
|
|
all_acknowledged = True
|
|
except RuntimeContractError:
|
|
all_acknowledged = False
|
|
break
|
|
terminal = (
|
|
prepared_evidence
|
|
if all_acknowledged
|
|
else PreflightEvidence.failure(
|
|
run_id=config.run_id,
|
|
config_digest=config.digest,
|
|
code="LMS_CLEANUP_INCOMPLETE",
|
|
world_size=config.world_size,
|
|
evidence_tier=config.evidence_tier,
|
|
)
|
|
)
|
|
_write_terminal_evidence(config.evidence_path, terminal)
|
|
|
|
terminal = _wait_for_evidence(
|
|
config.evidence_path,
|
|
timeout_seconds=min(3600, config.teardown_timeout_seconds * 2 + 1),
|
|
)
|
|
if terminal.result != "preflighted" or local_code is not None:
|
|
code = terminal.error_code or local_code or "LMS_LIFECYCLE_INVALID"
|
|
raise RuntimeContractError(code, "distributed preflight failed") from None
|
|
return terminal
|
|
|
|
|
|
def _require_expected(records: tuple[RankAttestation, ...], field: str, expected: object) -> None:
|
|
if any(getattr(record, field) != expected for record in records):
|
|
codes = {
|
|
"storage_digest": "LMS_STORAGE_PROFILE_MISMATCH",
|
|
"network_interface_digest": "LMS_NETWORK_PROFILE_DENIED",
|
|
"device_kind": "LMS_RUNTIME_PROFILE_MISMATCH",
|
|
"device_config_digest": "LMS_RUNTIME_PROFILE_MISMATCH",
|
|
"software_digest": "LMS_RUNTIME_PROFILE_MISMATCH",
|
|
}
|
|
raise RuntimeContractError(
|
|
codes.get(field, "LMS_IDENTITY_MISMATCH"),
|
|
f"rank {field} values disagree with the expected identity",
|
|
)
|
|
|
|
|
|
def validate_attestations(
|
|
records: tuple[RankAttestation, ...],
|
|
*,
|
|
world_size: int,
|
|
tensor_parallel_size: int,
|
|
dimension_divisors: tuple[int, ...],
|
|
expected_device_kind: str,
|
|
expected_device_config_digest: str,
|
|
expected_software_digest: str,
|
|
expected_source_digest: str,
|
|
expected_model_digest: str,
|
|
expected_tokenizer_digest: str,
|
|
expected_config_digest: str,
|
|
expected_commit_sha: str,
|
|
expected_code_digest: str,
|
|
expected_placement_plan_digest: str,
|
|
expected_storage_digest: str,
|
|
expected_network_interface_digest: str,
|
|
local_world_size: int,
|
|
min_free_device_memory_bytes: int,
|
|
min_free_host_memory_bytes: int,
|
|
min_free_staging_bytes: int,
|
|
) -> None:
|
|
"""Require one complete, homogeneous, capacity-admitted fixed world."""
|
|
if len(records) != world_size:
|
|
raise RuntimeContractError(
|
|
"LMS_MEMBERSHIP_INVALID",
|
|
"preflight requires exactly one attestation per rank",
|
|
)
|
|
assert_rank_order(records, world_size=world_size)
|
|
if tensor_parallel_size != world_size:
|
|
raise RuntimeContractError(
|
|
"LMS_TOPOLOGY_UNSUPPORTED",
|
|
"tensor_parallel_size must equal the fixed world_size",
|
|
)
|
|
if any(divisor % tensor_parallel_size for divisor in dimension_divisors):
|
|
raise RuntimeContractError(
|
|
"LMS_TOPOLOGY_UNSUPPORTED",
|
|
"each configured dimension divisor must divide by the TP world",
|
|
)
|
|
if len({record.device_digest for record in records}) != world_size:
|
|
raise RuntimeContractError(
|
|
"LMS_RANK_DEVICE_CONFLICT", "global device identities must be unique"
|
|
)
|
|
if len({record.device_profile_digest for record in records}) != 1:
|
|
raise RuntimeContractError("LMS_RUNTIME_PROFILE_MISMATCH", "rank device profiles disagree")
|
|
if len({(record.host_digest, record.local_rank) for record in records}) != world_size:
|
|
raise RuntimeContractError(
|
|
"LMS_RANK_DEVICE_CONFLICT",
|
|
"local ranks must be unique within each host",
|
|
)
|
|
group_count = world_size // local_world_size
|
|
if {record.group_rank for record in records} != set(range(group_count)):
|
|
raise RuntimeContractError(
|
|
"LMS_MEMBERSHIP_INVALID", "group ranks do not cover the fixed host groups"
|
|
)
|
|
host_groups: set[str] = set()
|
|
for group_rank in range(group_count):
|
|
members = tuple(record for record in records if record.group_rank == group_rank)
|
|
if (
|
|
len(members) != local_world_size
|
|
or {record.local_rank for record in members} != set(range(local_world_size))
|
|
or {record.rank for record in members}
|
|
!= set(
|
|
range(
|
|
group_rank * local_world_size,
|
|
(group_rank + 1) * local_world_size,
|
|
)
|
|
)
|
|
or len({record.host_digest for record in members}) != 1
|
|
):
|
|
raise RuntimeContractError(
|
|
"LMS_MEMBERSHIP_INVALID",
|
|
"local ranks do not match the fixed host-group mapping",
|
|
)
|
|
host_groups.add(members[0].host_digest)
|
|
if len(host_groups) != group_count:
|
|
raise RuntimeContractError(
|
|
"LMS_MEMBERSHIP_INVALID", "one host identity spans multiple fixed groups"
|
|
)
|
|
if len({record.software_digest for record in records}) != 1:
|
|
raise RuntimeContractError(
|
|
"LMS_RUNTIME_PROFILE_MISMATCH", "rank software identities disagree"
|
|
)
|
|
if len({record.storage_digest for record in records}) != 1:
|
|
raise RuntimeContractError(
|
|
"LMS_STORAGE_PROFILE_MISMATCH", "rank storage identities disagree"
|
|
)
|
|
expectations = {
|
|
"world_size": world_size,
|
|
"device_kind": expected_device_kind,
|
|
"device_config_digest": expected_device_config_digest,
|
|
"software_digest": expected_software_digest,
|
|
"source_digest": expected_source_digest,
|
|
"model_digest": expected_model_digest,
|
|
"tokenizer_digest": expected_tokenizer_digest,
|
|
"config_digest": expected_config_digest,
|
|
"commit_sha": expected_commit_sha,
|
|
"code_digest": expected_code_digest,
|
|
"placement_plan_digest": expected_placement_plan_digest,
|
|
"storage_digest": expected_storage_digest,
|
|
"network_interface_digest": expected_network_interface_digest,
|
|
"local_world_size": local_world_size,
|
|
}
|
|
for field, expected in expectations.items():
|
|
_require_expected(records, field, expected)
|
|
if any(record.free_device_memory_bytes < min_free_device_memory_bytes for record in records):
|
|
raise RuntimeContractError(
|
|
"LMS_RESOURCE_ADMISSION_DENIED",
|
|
"device memory headroom is below the configured floor",
|
|
)
|
|
if any(record.free_host_memory_bytes < min_free_host_memory_bytes for record in records):
|
|
raise RuntimeContractError(
|
|
"LMS_RESOURCE_ADMISSION_DENIED",
|
|
"host memory headroom is below the configured floor",
|
|
)
|
|
if any(record.free_staging_bytes < min_free_staging_bytes for record in records):
|
|
raise RuntimeContractError(
|
|
"LMS_RESOURCE_ADMISSION_DENIED",
|
|
"staging headroom is below the configured floor",
|
|
)
|