229 lines
7.1 KiB
Python
229 lines
7.1 KiB
Python
# SPDX-License-Identifier: Apache-2.0
|
|
# SPDX-FileCopyrightText: Copyright contributors to the vLLM-Omni project
|
|
"""Provider and consumer protocols for Host Weight Runtime extensions."""
|
|
|
|
from __future__ import annotations
|
|
|
|
import math
|
|
from dataclasses import dataclass, field
|
|
from enum import Enum
|
|
from types import TracebackType
|
|
from typing import TYPE_CHECKING, Protocol
|
|
|
|
import torch
|
|
|
|
from .identity import CanonicalJson, WeightArtifactIdentity
|
|
from .manifest import TensorKind
|
|
|
|
if TYPE_CHECKING:
|
|
from .config import ValidationLevel
|
|
from .lease import HostWeightLease
|
|
from .outcomes import StoreResult
|
|
|
|
|
|
class ProductionSourceMode(str, Enum):
|
|
FINALIZED_MODEL = "finalized_model"
|
|
CHECKPOINT_STREAM = "checkpoint_stream"
|
|
|
|
|
|
class CoordinationScope(str, Enum):
|
|
SINGLE_PROCESS = "single_process"
|
|
GROUP_COHORT = "group_cohort"
|
|
ARTIFACT_BUNDLE = "artifact_bundle"
|
|
|
|
|
|
class LookupPhase(str, Enum):
|
|
PRE_LOAD_SAFE = "pre_load_safe"
|
|
POST_LOAD_ONLY = "post_load_only"
|
|
|
|
|
|
@dataclass(frozen=True)
|
|
class TensorWriteSpec:
|
|
name: str
|
|
shape: tuple[int, ...]
|
|
dtype: torch.dtype
|
|
kind: TensorKind = TensorKind.PARAMETER
|
|
role: str = "weight"
|
|
|
|
def __post_init__(self) -> None:
|
|
if not isinstance(self.name, str) or not self.name or not isinstance(self.role, str) or not self.role:
|
|
raise ValueError("tensor write name and role must not be empty")
|
|
if not isinstance(self.shape, tuple) or any(
|
|
isinstance(dimension, bool) or not isinstance(dimension, int) or dimension < 0 for dimension in self.shape
|
|
):
|
|
raise ValueError(f"tensor {self.name!r} has a negative shape dimension")
|
|
if not isinstance(self.dtype, torch.dtype):
|
|
raise ValueError(f"tensor {self.name!r} must declare one torch dtype")
|
|
if not isinstance(self.kind, TensorKind):
|
|
raise ValueError(f"tensor {self.name!r} must use TensorKind")
|
|
|
|
@property
|
|
def numel(self) -> int:
|
|
return math.prod(self.shape) if self.shape else 1
|
|
|
|
@property
|
|
def nbytes(self) -> int:
|
|
return self.numel * torch.empty((), dtype=self.dtype).element_size()
|
|
|
|
|
|
class TensorFileWriter(Protocol):
|
|
def write_tensor(self, name: str, tensor: torch.Tensor) -> None: ...
|
|
|
|
def write_rows(self, name: str, start_row: int, tensor: torch.Tensor) -> None: ...
|
|
|
|
def close(self) -> None: ...
|
|
|
|
def __enter__(self) -> TensorFileWriter: ...
|
|
|
|
def __exit__(
|
|
self,
|
|
exc_type: type[BaseException] | None,
|
|
exc: BaseException | None,
|
|
_traceback: TracebackType | None,
|
|
) -> bool | None: ...
|
|
|
|
|
|
class ArtifactWriter(Protocol):
|
|
def open_tensor_file(
|
|
self,
|
|
relative_name: str,
|
|
specs: tuple[TensorWriteSpec, ...],
|
|
) -> TensorFileWriter: ...
|
|
|
|
|
|
@dataclass(frozen=True)
|
|
class WeightProductionSpec:
|
|
producer_id: str
|
|
outputs: tuple[WeightArtifactIdentity, ...]
|
|
source_mode: ProductionSourceMode
|
|
coordination_scope: CoordinationScope = CoordinationScope.SINGLE_PROCESS
|
|
lookup_phase: LookupPhase = LookupPhase.PRE_LOAD_SAFE
|
|
|
|
def __post_init__(self) -> None:
|
|
if not isinstance(self.producer_id, str) or not self.producer_id or not self.outputs:
|
|
raise ValueError("producer_id and outputs must not be empty")
|
|
if not isinstance(self.outputs, tuple) or any(
|
|
not isinstance(output, WeightArtifactIdentity) for output in self.outputs
|
|
):
|
|
raise ValueError("producer outputs must be an immutable tuple of artifact identities")
|
|
if not isinstance(self.source_mode, ProductionSourceMode):
|
|
raise ValueError("producer source mode must use ProductionSourceMode")
|
|
if not isinstance(self.coordination_scope, CoordinationScope):
|
|
raise ValueError("producer coordination scope must use CoordinationScope")
|
|
if not isinstance(self.lookup_phase, LookupPhase):
|
|
raise ValueError("producer lookup phase must use LookupPhase")
|
|
if len({output.key for output in self.outputs}) != len(self.outputs):
|
|
raise ValueError("producer outputs must have unique semantic identities")
|
|
if any(output.producer.producer_id != self.producer_id for output in self.outputs):
|
|
raise ValueError("producer outputs must name the declaring producer_id")
|
|
|
|
|
|
@dataclass(frozen=True)
|
|
class ProductionMetadata:
|
|
producer_schema: str
|
|
restorer_schema: str
|
|
format_metadata: CanonicalJson = field(default_factory=CanonicalJson.empty)
|
|
|
|
def __post_init__(self) -> None:
|
|
if (
|
|
not isinstance(self.producer_schema, str)
|
|
or not self.producer_schema
|
|
or not isinstance(self.restorer_schema, str)
|
|
or not self.restorer_schema
|
|
):
|
|
raise ValueError("producer and restorer schema identifiers must not be empty")
|
|
if not isinstance(self.format_metadata, CanonicalJson):
|
|
raise ValueError("production format metadata must use CanonicalJson")
|
|
|
|
|
|
class WeightProducer(Protocol):
|
|
"""Synchronous V1 producer; callers do not preempt an active build."""
|
|
|
|
@property
|
|
def spec(self) -> WeightProductionSpec: ...
|
|
|
|
def produce(self, writer: ArtifactWriter) -> ProductionMetadata:
|
|
"""Produce one exact artifact synchronously or raise."""
|
|
...
|
|
|
|
|
|
@dataclass(frozen=True)
|
|
class BuildRequest:
|
|
identity: WeightArtifactIdentity
|
|
|
|
|
|
class WeightRestorePlan(Protocol):
|
|
"""A validated, one-shot model mutation plan."""
|
|
|
|
def commit(self) -> None:
|
|
"""Mutate the target model exactly once or raise."""
|
|
...
|
|
|
|
|
|
class WeightRestorer(Protocol):
|
|
"""Validate a lease-to-model restore without mutating either input."""
|
|
|
|
@property
|
|
def schema(self) -> str: ...
|
|
|
|
def plan_restore(self, model: object, lease: HostWeightLease) -> WeightRestorePlan:
|
|
"""Return a validated plan; all model mutation is deferred to commit()."""
|
|
...
|
|
|
|
|
|
class HostWeightStore(Protocol):
|
|
"""Exact artifact access whose deadlines bound lock acquisition only."""
|
|
|
|
def lookup(
|
|
self,
|
|
identity: WeightArtifactIdentity,
|
|
*,
|
|
validation: ValidationLevel,
|
|
deadline: float | None = None,
|
|
) -> StoreResult: ...
|
|
|
|
def get_or_build(
|
|
self,
|
|
request: BuildRequest,
|
|
producer: WeightProducer,
|
|
*,
|
|
validation: ValidationLevel,
|
|
deadline: float,
|
|
) -> StoreResult: ...
|
|
|
|
|
|
@dataclass(frozen=True)
|
|
class RemoteArtifactDescriptor:
|
|
identity: WeightArtifactIdentity
|
|
provider_id: str
|
|
manifest_sha256: str
|
|
content_sha256: str
|
|
|
|
|
|
class RemoteArtifactProvider(Protocol):
|
|
@property
|
|
def provider_id(self) -> str: ...
|
|
|
|
def probe(self, identity: WeightArtifactIdentity) -> RemoteArtifactDescriptor | None: ...
|
|
|
|
def fetch(self, descriptor: RemoteArtifactDescriptor, writer: ArtifactWriter) -> ProductionMetadata: ...
|
|
|
|
|
|
__all__ = [
|
|
"ArtifactWriter",
|
|
"BuildRequest",
|
|
"CoordinationScope",
|
|
"HostWeightStore",
|
|
"LookupPhase",
|
|
"ProductionMetadata",
|
|
"ProductionSourceMode",
|
|
"RemoteArtifactDescriptor",
|
|
"RemoteArtifactProvider",
|
|
"TensorFileWriter",
|
|
"TensorWriteSpec",
|
|
"WeightProducer",
|
|
"WeightProductionSpec",
|
|
"WeightRestorePlan",
|
|
"WeightRestorer",
|
|
]
|