feat: checkpoint distributed gguf runtime stories
This commit is contained in:
@@ -20,9 +20,17 @@ import time
|
||||
from dataclasses import dataclass
|
||||
from typing import Any, Callable
|
||||
|
||||
from .capability import CapabilityReport
|
||||
from . import __version__ as _PACKAGE_VERSION
|
||||
from .capability import CapabilityReport, config_fingerprint
|
||||
from .doctor import DoctorSelection
|
||||
from .recipe_manifest import Recipe, RecipeManifest
|
||||
from .runtime_recipe import (
|
||||
build_artifact_identity,
|
||||
build_runtime_recipe_identity,
|
||||
compatibility_fingerprint,
|
||||
fingerprint_payload,
|
||||
)
|
||||
from .gguf_ownership import authoritative_dense_llama_ownership
|
||||
|
||||
# How long a passing report stays usable. Startup normally validates in-process
|
||||
# (age ≈ 0); this bounds how far a report written by an earlier `doctor` run can
|
||||
@@ -39,6 +47,7 @@ REASON_MODEL_MISMATCH = "model-mismatch"
|
||||
REASON_SHARD_MISMATCH = "shard-mismatch"
|
||||
REASON_RECIPE_MISMATCH = "recipe-mismatch"
|
||||
REASON_BACKEND_MISMATCH = "backend-mismatch"
|
||||
REASON_COMPATIBILITY_MISMATCH = "compatibility-mismatch"
|
||||
|
||||
|
||||
class CapabilityAdmissionError(RuntimeError):
|
||||
@@ -77,6 +86,7 @@ class AdmissionRequirement:
|
||||
recipe_version: str
|
||||
backend_id: str
|
||||
device: str
|
||||
compatibility_fingerprint: str
|
||||
max_age_seconds: float = DEFAULT_MAX_REPORT_AGE_SECONDS
|
||||
|
||||
@classmethod
|
||||
@@ -94,6 +104,9 @@ class AdmissionRequirement:
|
||||
recipe_version=context.recipe.version,
|
||||
backend_id=context.recipe.backend_id,
|
||||
device=context.device,
|
||||
compatibility_fingerprint=_compatibility_fingerprint_for_context(
|
||||
context
|
||||
),
|
||||
max_age_seconds=max_age_seconds,
|
||||
)
|
||||
|
||||
@@ -165,6 +178,16 @@ def admit(
|
||||
f"{requirement.backend_id} on {requirement.device}",
|
||||
)
|
||||
|
||||
if report.compatibility_fingerprint != requirement.compatibility_fingerprint:
|
||||
raise CapabilityAdmissionError(
|
||||
REASON_COMPATIBILITY_MISMATCH,
|
||||
f"capability proof fingerprint {report.compatibility_fingerprint!r} "
|
||||
f"does not match the expected compatibility fingerprint for "
|
||||
f"{requirement.model_id} {requirement.shard_label}; the artifact, "
|
||||
f"tokenizer, architecture, boundary schema, activation recipe or "
|
||||
f"cache layout differs",
|
||||
)
|
||||
|
||||
if not report.passed:
|
||||
raise CapabilityAdmissionError(
|
||||
REASON_NOT_PASSED,
|
||||
@@ -223,3 +246,157 @@ def probe_capability(context: CapabilityContext) -> CapabilityReport:
|
||||
context.recipe,
|
||||
context.manifest,
|
||||
).report
|
||||
|
||||
|
||||
def _compatibility_fingerprint_for_context(context: CapabilityContext) -> str:
|
||||
backend = context.backend
|
||||
selection = context.selection
|
||||
recipe = context.recipe
|
||||
model_config = getattr(getattr(backend, "model", None), "config", None)
|
||||
model_config_payload = (
|
||||
model_config.to_dict() if hasattr(model_config, "to_dict") else model_config
|
||||
)
|
||||
runtime_versions = _runtime_versions()
|
||||
runtime_version = _PACKAGE_VERSION
|
||||
ownership = authoritative_dense_llama_ownership(backend, selection)
|
||||
artifact = build_artifact_identity(
|
||||
model_id=selection.model_id,
|
||||
revision=getattr(getattr(backend, "model", None), "revision", None),
|
||||
model_config=model_config_payload,
|
||||
shard_start=ownership.start_layer,
|
||||
shard_end=ownership.end_layer,
|
||||
)
|
||||
runtime_recipe = build_runtime_recipe_identity(
|
||||
model_id=selection.model_id,
|
||||
revision=getattr(getattr(backend, "model", None), "revision", None),
|
||||
model_config=model_config_payload,
|
||||
recipe_params=recipe.params,
|
||||
weight_quantization=selection.quantization,
|
||||
backend_id=recipe.backend_id,
|
||||
runtime_version=runtime_version,
|
||||
activation_dtype="bfloat16",
|
||||
compute_dtype=_backend_compute_dtype(backend),
|
||||
kv_dtype=_backend_kv_dtype(backend),
|
||||
kv_layout=_backend_kv_layout(backend),
|
||||
tokenizer_revision=_backend_tokenizer_revision(backend, selection),
|
||||
architecture_adapter=_backend_architecture_adapter(backend, recipe.backend_id),
|
||||
boundary_schema_version=1,
|
||||
cache_layout=_backend_cache_layout(backend, recipe.params),
|
||||
)
|
||||
return compatibility_fingerprint(
|
||||
fingerprint_payload(
|
||||
model={
|
||||
"model_id": selection.model_id,
|
||||
"revision": getattr(getattr(backend, "model", None), "revision", None),
|
||||
"config_fingerprint": config_fingerprint(model_config_payload),
|
||||
},
|
||||
shard={
|
||||
"start": ownership.start_layer,
|
||||
"end": ownership.end_layer,
|
||||
"owns_embedding": ownership.owns_embedding,
|
||||
"owns_final_head": ownership.owns_final_head,
|
||||
},
|
||||
recipe={
|
||||
"recipe_id": recipe.id,
|
||||
"recipe_version": recipe.version,
|
||||
"catalogue_version": context.manifest.catalogue_version,
|
||||
},
|
||||
backend={
|
||||
"backend_id": recipe.backend_id,
|
||||
"device": context.device,
|
||||
"device_name": _backend_device_name(context.device),
|
||||
"quantization": selection.quantization,
|
||||
"runtime": runtime_versions,
|
||||
},
|
||||
artifact=artifact.to_dict(),
|
||||
runtime_recipe=runtime_recipe.to_dict(),
|
||||
)
|
||||
)
|
||||
|
||||
|
||||
def _runtime_versions() -> dict[str, str]:
|
||||
versions: dict[str, str] = {}
|
||||
for name in ("torch", "transformers"):
|
||||
try:
|
||||
module = __import__(name)
|
||||
except Exception:
|
||||
continue
|
||||
version = getattr(module, "__version__", None)
|
||||
if version:
|
||||
versions[name] = str(version)
|
||||
return versions
|
||||
|
||||
|
||||
def _backend_compute_dtype(backend: Any) -> str:
|
||||
config = getattr(getattr(backend, "model", None), "config", None)
|
||||
for candidate in (config, getattr(config, "text_config", None)):
|
||||
if candidate is None:
|
||||
continue
|
||||
for attr in ("dtype", "torch_dtype"):
|
||||
value = getattr(candidate, attr, None)
|
||||
if value is None:
|
||||
continue
|
||||
return str(value).removeprefix("torch.")
|
||||
return "bfloat16"
|
||||
|
||||
|
||||
def _backend_kv_dtype(backend: Any) -> str:
|
||||
return _backend_compute_dtype(backend)
|
||||
|
||||
|
||||
def _backend_kv_layout(backend: Any) -> str:
|
||||
return "session-cache" if getattr(backend, "supports_kv_cache", False) else "stateless"
|
||||
|
||||
|
||||
def _backend_tokenizer_revision(backend: Any, selection: DoctorSelection) -> str:
|
||||
model = getattr(backend, "model", None)
|
||||
revision = getattr(model, "revision", None)
|
||||
if isinstance(revision, str) and revision.strip():
|
||||
return revision
|
||||
tokenizer = getattr(backend, "tokenizer", None)
|
||||
for attr in ("revision", "model_id"):
|
||||
value = getattr(tokenizer, attr, None)
|
||||
if isinstance(value, str) and value.strip():
|
||||
return value
|
||||
return selection.model_id
|
||||
|
||||
|
||||
def _backend_architecture_adapter(backend: Any, default: str) -> str:
|
||||
config = getattr(getattr(backend, "model", None), "config", None)
|
||||
for candidate in (config, getattr(config, "text_config", None)):
|
||||
if candidate is None:
|
||||
continue
|
||||
for attr in ("architecture_adapter", "model_type"):
|
||||
value = getattr(candidate, attr, None)
|
||||
if isinstance(value, str) and value.strip():
|
||||
return value
|
||||
architectures = getattr(candidate, "architectures", None)
|
||||
if isinstance(architectures, (list, tuple)) and architectures:
|
||||
first = architectures[0]
|
||||
if isinstance(first, str) and first.strip():
|
||||
return first
|
||||
return default
|
||||
|
||||
|
||||
def _backend_device_name(device: str) -> str | None:
|
||||
if device != "cuda":
|
||||
return None
|
||||
from .hardware import detect_hardware
|
||||
|
||||
try:
|
||||
return detect_hardware().get("gpu_name") or None
|
||||
except Exception:
|
||||
return None
|
||||
|
||||
|
||||
def _backend_cache_layout(backend: Any, recipe_params: dict[str, Any] | None) -> str:
|
||||
if getattr(backend, "supports_kv_cache", False) is False:
|
||||
return "stateless"
|
||||
if recipe_params is None:
|
||||
return "local-hot-kv"
|
||||
if recipe_params.get("use_cache") is False:
|
||||
return "stateless"
|
||||
value = recipe_params.get("cache_layout")
|
||||
if isinstance(value, str) and value.strip():
|
||||
return value
|
||||
return "local-hot-kv"
|
||||
|
||||
Reference in New Issue
Block a user