Files
OBLITERATUS/obliteratus/distributed/consensus.py

138 lines
5.6 KiB
Python

"""Bounded Gloo consensus primitives for the CPU protocol test lane."""
from __future__ import annotations
from collections.abc import Sequence
import torch
import torch.distributed as dist
from obliteratus.distributed.contracts import (
MAX_CONSENSUS_BYTES,
MAX_WORLD_SIZE,
ContractError,
canonical_record,
contract_digest,
)
_LENGTH_BYTES = 4
def encode_frame(payload: bytes, *, capacity: int = MAX_CONSENSUS_BYTES) -> torch.Tensor:
"""Encode bytes into one fixed-size uint8 frame with zero padding."""
if isinstance(capacity, bool) or not isinstance(capacity, int) or not 1 <= capacity <= MAX_CONSENSUS_BYTES:
raise ContractError(f"capacity must be between 1 and {MAX_CONSENSUS_BYTES}")
if not isinstance(payload, bytes):
raise ContractError("frame payload must be bytes")
if len(payload) > capacity:
raise ContractError(f"frame payload exceeds {capacity} bytes")
frame = bytearray(_LENGTH_BYTES + capacity)
frame[:_LENGTH_BYTES] = len(payload).to_bytes(_LENGTH_BYTES, byteorder="big")
frame[_LENGTH_BYTES : _LENGTH_BYTES + len(payload)] = payload
return torch.frombuffer(frame, dtype=torch.uint8).clone()
def decode_frame(frame: torch.Tensor) -> bytes:
"""Decode a canonical fixed-size frame and reject non-zero padding."""
if not isinstance(frame, torch.Tensor) or frame.dtype != torch.uint8 or frame.ndim != 1:
raise ContractError("frame must be a one-dimensional uint8 tensor")
capacity = frame.numel() - _LENGTH_BYTES
if not 1 <= capacity <= MAX_CONSENSUS_BYTES:
raise ContractError("frame has an invalid capacity")
raw = bytes(frame.detach().cpu().tolist())
size = int.from_bytes(raw[:_LENGTH_BYTES], byteorder="big")
if size > capacity:
raise ContractError("frame length exceeds its capacity")
if any(raw[_LENGTH_BYTES + size :]):
raise ContractError("frame padding must be zero")
return raw[_LENGTH_BYTES : _LENGTH_BYTES + size]
def _require_gloo(group: dist.ProcessGroup | None) -> tuple[int, int]:
if not dist.is_available() or not dist.is_initialized():
raise ContractError("a Gloo process group must be initialized")
backend = str(dist.get_backend(group)).lower()
if backend != "gloo":
raise ContractError("CPU protocol consensus requires the Gloo backend")
rank, world_size = dist.get_rank(group), dist.get_world_size(group)
if not 2 <= world_size <= MAX_WORLD_SIZE:
raise ContractError(f"Gloo world_size must be between 2 and {MAX_WORLD_SIZE}")
return rank, world_size
def gloo_all_gather_records(
record: object,
*,
group: dist.ProcessGroup | None = None,
capacity: int = MAX_CONSENSUS_BYTES,
) -> tuple[bytes, ...]:
"""Gather bounded canonical records without Python object collectives."""
payload = canonical_record(record, max_bytes=capacity)
_rank, world_size = _require_gloo(group)
frame = encode_frame(payload, capacity=capacity)
gathered = [torch.zeros_like(frame) for _ in range(world_size)]
dist.all_gather(gathered, frame, group=group)
return tuple(decode_frame(item) for item in gathered)
def require_consensus_digest(
digest: str,
*,
group: dist.ProcessGroup | None = None,
) -> str:
"""Require every rank to supply the same lowercase SHA-256 digest."""
if not isinstance(digest, str) or len(digest) != 64:
raise ContractError("digest must be 64 lowercase hexadecimal characters")
try:
payload = bytes.fromhex(digest)
except ValueError as exc:
raise ContractError("digest must be 64 lowercase hexadecimal characters") from exc
if digest != digest.lower() or payload.hex() != digest:
raise ContractError("digest must be 64 lowercase hexadecimal characters")
_rank, world_size = _require_gloo(group)
local = torch.tensor(tuple(payload), dtype=torch.uint8)
gathered = [torch.zeros_like(local) for _ in range(world_size)]
dist.all_gather(gathered, local, group=group)
if any(not torch.equal(local, item) for item in gathered):
raise ContractError("rank digests disagree")
return digest
def require_record_consensus(
record: object,
*,
group: dist.ProcessGroup | None = None,
) -> str:
"""Require every rank to present the same bounded canonical record."""
return require_consensus_digest(contract_digest(record), group=group)
def unanimous_vote(
sequence: int,
accepted: bool,
*,
group: dist.ProcessGroup | None = None,
) -> bool:
"""Return true only when every rank votes yes for the same sequence."""
if isinstance(sequence, bool) or not isinstance(sequence, int) or sequence < 0:
raise ContractError("vote sequence must be a non-negative integer")
if not isinstance(accepted, bool):
raise ContractError("accepted must be a boolean")
_rank, world_size = _require_gloo(group)
local = torch.tensor((sequence, int(accepted)), dtype=torch.int64)
gathered = [torch.zeros_like(local) for _ in range(world_size)]
dist.all_gather(gathered, local, group=group)
if any(int(item[0]) != sequence for item in gathered):
raise ContractError("rank vote sequences disagree")
return all(bool(int(item[1])) for item in gathered)
def assert_rank_order(records: Sequence[object], *, world_size: int) -> None:
"""Reject missing, duplicate, or reordered rank-bearing records."""
if len(records) != world_size:
raise ContractError("record count does not match world_size")
ranks = [getattr(record, "rank", None) for record in records]
if ranks != list(range(world_size)):
raise ContractError("records must appear once in global-rank order")