invarlock 0.2.0__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- invarlock/__init__.py +33 -0
- invarlock/__main__.py +10 -0
- invarlock/_data/runtime/profiles/ci_cpu.yaml +15 -0
- invarlock/_data/runtime/profiles/release.yaml +23 -0
- invarlock/_data/runtime/tiers.yaml +76 -0
- invarlock/adapters/__init__.py +102 -0
- invarlock/adapters/_capabilities.py +45 -0
- invarlock/adapters/auto.py +99 -0
- invarlock/adapters/base.py +530 -0
- invarlock/adapters/base_types.py +85 -0
- invarlock/adapters/hf_bert.py +852 -0
- invarlock/adapters/hf_gpt2.py +403 -0
- invarlock/adapters/hf_llama.py +485 -0
- invarlock/adapters/hf_mixin.py +383 -0
- invarlock/adapters/hf_onnx.py +112 -0
- invarlock/adapters/hf_t5.py +137 -0
- invarlock/adapters/py.typed +1 -0
- invarlock/assurance/__init__.py +43 -0
- invarlock/cli/__init__.py +8 -0
- invarlock/cli/__main__.py +8 -0
- invarlock/cli/_evidence.py +25 -0
- invarlock/cli/_json.py +75 -0
- invarlock/cli/adapter_auto.py +162 -0
- invarlock/cli/app.py +287 -0
- invarlock/cli/commands/__init__.py +26 -0
- invarlock/cli/commands/certify.py +403 -0
- invarlock/cli/commands/doctor.py +1358 -0
- invarlock/cli/commands/explain_gates.py +151 -0
- invarlock/cli/commands/export_html.py +100 -0
- invarlock/cli/commands/plugins.py +1331 -0
- invarlock/cli/commands/report.py +354 -0
- invarlock/cli/commands/run.py +4146 -0
- invarlock/cli/commands/verify.py +1040 -0
- invarlock/cli/config.py +396 -0
- invarlock/cli/constants.py +68 -0
- invarlock/cli/device.py +92 -0
- invarlock/cli/doctor_helpers.py +74 -0
- invarlock/cli/errors.py +6 -0
- invarlock/cli/overhead_utils.py +60 -0
- invarlock/cli/provenance.py +66 -0
- invarlock/cli/utils.py +41 -0
- invarlock/config.py +56 -0
- invarlock/core/__init__.py +62 -0
- invarlock/core/abi.py +15 -0
- invarlock/core/api.py +274 -0
- invarlock/core/auto_tuning.py +317 -0
- invarlock/core/bootstrap.py +226 -0
- invarlock/core/checkpoint.py +221 -0
- invarlock/core/contracts.py +73 -0
- invarlock/core/error_utils.py +64 -0
- invarlock/core/events.py +298 -0
- invarlock/core/exceptions.py +95 -0
- invarlock/core/registry.py +481 -0
- invarlock/core/retry.py +146 -0
- invarlock/core/runner.py +2041 -0
- invarlock/core/types.py +154 -0
- invarlock/edits/__init__.py +12 -0
- invarlock/edits/_edit_utils.py +249 -0
- invarlock/edits/_external_utils.py +268 -0
- invarlock/edits/noop.py +47 -0
- invarlock/edits/py.typed +1 -0
- invarlock/edits/quant_rtn.py +801 -0
- invarlock/edits/registry.py +166 -0
- invarlock/eval/__init__.py +23 -0
- invarlock/eval/bench.py +1207 -0
- invarlock/eval/bootstrap.py +50 -0
- invarlock/eval/data.py +2052 -0
- invarlock/eval/metrics.py +2167 -0
- invarlock/eval/primary_metric.py +767 -0
- invarlock/eval/probes/__init__.py +24 -0
- invarlock/eval/probes/fft.py +139 -0
- invarlock/eval/probes/mi.py +213 -0
- invarlock/eval/probes/post_attention.py +323 -0
- invarlock/eval/providers/base.py +67 -0
- invarlock/eval/providers/seq2seq.py +111 -0
- invarlock/eval/providers/text_lm.py +113 -0
- invarlock/eval/providers/vision_text.py +93 -0
- invarlock/eval/py.typed +1 -0
- invarlock/guards/__init__.py +18 -0
- invarlock/guards/_contracts.py +9 -0
- invarlock/guards/invariants.py +640 -0
- invarlock/guards/policies.py +805 -0
- invarlock/guards/py.typed +1 -0
- invarlock/guards/rmt.py +2097 -0
- invarlock/guards/spectral.py +1419 -0
- invarlock/guards/tier_config.py +354 -0
- invarlock/guards/variance.py +3298 -0
- invarlock/guards_ref/__init__.py +15 -0
- invarlock/guards_ref/rmt_ref.py +40 -0
- invarlock/guards_ref/spectral_ref.py +135 -0
- invarlock/guards_ref/variance_ref.py +60 -0
- invarlock/model_profile.py +353 -0
- invarlock/model_utils.py +221 -0
- invarlock/observability/__init__.py +10 -0
- invarlock/observability/alerting.py +535 -0
- invarlock/observability/core.py +546 -0
- invarlock/observability/exporters.py +565 -0
- invarlock/observability/health.py +588 -0
- invarlock/observability/metrics.py +457 -0
- invarlock/observability/py.typed +1 -0
- invarlock/observability/utils.py +553 -0
- invarlock/plugins/__init__.py +12 -0
- invarlock/plugins/hello_guard.py +33 -0
- invarlock/plugins/hf_awq_adapter.py +82 -0
- invarlock/plugins/hf_bnb_adapter.py +79 -0
- invarlock/plugins/hf_gptq_adapter.py +78 -0
- invarlock/plugins/py.typed +1 -0
- invarlock/py.typed +1 -0
- invarlock/reporting/__init__.py +7 -0
- invarlock/reporting/certificate.py +3221 -0
- invarlock/reporting/certificate_schema.py +244 -0
- invarlock/reporting/dataset_hashing.py +215 -0
- invarlock/reporting/guards_analysis.py +948 -0
- invarlock/reporting/html.py +32 -0
- invarlock/reporting/normalizer.py +235 -0
- invarlock/reporting/policy_utils.py +517 -0
- invarlock/reporting/primary_metric_utils.py +265 -0
- invarlock/reporting/render.py +1442 -0
- invarlock/reporting/report.py +903 -0
- invarlock/reporting/report_types.py +278 -0
- invarlock/reporting/utils.py +175 -0
- invarlock/reporting/validate.py +631 -0
- invarlock/security.py +176 -0
- invarlock/sparsity_utils.py +323 -0
- invarlock/utils/__init__.py +150 -0
- invarlock/utils/digest.py +45 -0
- invarlock-0.2.0.dist-info/METADATA +586 -0
- invarlock-0.2.0.dist-info/RECORD +132 -0
- invarlock-0.2.0.dist-info/WHEEL +5 -0
- invarlock-0.2.0.dist-info/entry_points.txt +20 -0
- invarlock-0.2.0.dist-info/licenses/LICENSE +201 -0
- invarlock-0.2.0.dist-info/top_level.txt +1 -0
|
@@ -0,0 +1,66 @@
|
|
|
1
|
+
"""Adapter-lite provenance extraction utilities.
|
|
2
|
+
|
|
3
|
+
Provides a tiny, versioned schema describing the adapter family and the
|
|
4
|
+
underlying library versions. This does not perform any edits; it only reads
|
|
5
|
+
environment and import metadata to annotate reports/certificates.
|
|
6
|
+
"""
|
|
7
|
+
|
|
8
|
+
from __future__ import annotations
|
|
9
|
+
|
|
10
|
+
from dataclasses import asdict, dataclass
|
|
11
|
+
from importlib.metadata import version as pkg_version
|
|
12
|
+
from typing import Any
|
|
13
|
+
|
|
14
|
+
|
|
15
|
+
@dataclass
|
|
16
|
+
class AdapterProvenance:
|
|
17
|
+
family: str
|
|
18
|
+
library: str
|
|
19
|
+
version: str | None
|
|
20
|
+
supported: bool
|
|
21
|
+
tested: list[str]
|
|
22
|
+
message: str | None = None
|
|
23
|
+
|
|
24
|
+
def to_dict(self) -> dict[str, Any]:
|
|
25
|
+
return asdict(self)
|
|
26
|
+
|
|
27
|
+
|
|
28
|
+
_FAMILY_MAP: dict[str, tuple[str, str, list[str]]] = {
|
|
29
|
+
# name -> (family, library, tested_versions)
|
|
30
|
+
"hf_gptq": ("gptq", "auto-gptq", []),
|
|
31
|
+
"hf_awq": ("awq", "autoawq", []),
|
|
32
|
+
"hf_bnb": ("bnb", "bitsandbytes", []),
|
|
33
|
+
# ONNX stack (requires extras: invarlock[onnx])
|
|
34
|
+
"hf_onnx": ("onnx", "onnxruntime", []),
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
|
|
38
|
+
def extract_adapter_provenance(adapter_name: str) -> AdapterProvenance:
|
|
39
|
+
name = (adapter_name or "").strip().lower()
|
|
40
|
+
family, library, tested = _FAMILY_MAP.get(name, ("hf", "transformers", []))
|
|
41
|
+
|
|
42
|
+
ver: str | None
|
|
43
|
+
try:
|
|
44
|
+
ver = pkg_version(library)
|
|
45
|
+
supported = True if (not tested or ver in tested) else False
|
|
46
|
+
msg = (
|
|
47
|
+
None
|
|
48
|
+
if supported
|
|
49
|
+
else f"Use Compare & Certify (BYOE); {library} version unsupported (tested: {tested})"
|
|
50
|
+
)
|
|
51
|
+
except Exception: # Package not installed or version unknown
|
|
52
|
+
ver = None
|
|
53
|
+
supported = False
|
|
54
|
+
msg = f"{library} not available; prefer Compare & Certify (BYOE) or install extras."
|
|
55
|
+
|
|
56
|
+
return AdapterProvenance(
|
|
57
|
+
family=family,
|
|
58
|
+
library=library,
|
|
59
|
+
version=ver,
|
|
60
|
+
supported=supported,
|
|
61
|
+
tested=tested,
|
|
62
|
+
message=msg,
|
|
63
|
+
)
|
|
64
|
+
|
|
65
|
+
|
|
66
|
+
__all__ = ["extract_adapter_provenance", "AdapterProvenance"]
|
invarlock/cli/utils.py
ADDED
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
from typing import Any
|
|
4
|
+
|
|
5
|
+
|
|
6
|
+
def coerce_option(value: Any, fallback: Any | None = None) -> Any:
|
|
7
|
+
"""Return a Typer option's concrete value.
|
|
8
|
+
|
|
9
|
+
- If `value` looks like a Typer OptionInfo, return its `.default`.
|
|
10
|
+
- Otherwise, return `value` unless it is None, in which case `fallback`.
|
|
11
|
+
This keeps CLI command functions callable both by Typer and directly in tests.
|
|
12
|
+
"""
|
|
13
|
+
try:
|
|
14
|
+
# Typer OptionInfo typically exposes a `.default` attribute
|
|
15
|
+
default = getattr(value, "default", None)
|
|
16
|
+
# If it's an OptionInfo-like object, prefer its default
|
|
17
|
+
if default is not None or value.__class__.__name__ == "OptionInfo":
|
|
18
|
+
return default if default is not None else fallback
|
|
19
|
+
except Exception:
|
|
20
|
+
pass
|
|
21
|
+
return value if value is not None else fallback
|
|
22
|
+
|
|
23
|
+
|
|
24
|
+
def coerce_float(value: Any, default: float) -> float:
|
|
25
|
+
"""Coerce arbitrary input to float, falling back to `default` on error."""
|
|
26
|
+
try:
|
|
27
|
+
if value is None:
|
|
28
|
+
return default
|
|
29
|
+
return float(value)
|
|
30
|
+
except (TypeError, ValueError):
|
|
31
|
+
return default
|
|
32
|
+
|
|
33
|
+
|
|
34
|
+
def coerce_int(value: Any, default: int) -> int:
|
|
35
|
+
"""Coerce arbitrary input to int, falling back to `default` on error."""
|
|
36
|
+
try:
|
|
37
|
+
if value is None:
|
|
38
|
+
return default
|
|
39
|
+
return int(value)
|
|
40
|
+
except (TypeError, ValueError):
|
|
41
|
+
return default
|
invarlock/config.py
ADDED
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
from dataclasses import dataclass
|
|
2
|
+
|
|
3
|
+
|
|
4
|
+
@dataclass
|
|
5
|
+
class Defaults:
|
|
6
|
+
"""
|
|
7
|
+
Global default parameters for the InvarLock framework. This dataclass
|
|
8
|
+
centralizes hyperparameters for different lenses, especially for the
|
|
9
|
+
'auto' calibration modes.
|
|
10
|
+
"""
|
|
11
|
+
|
|
12
|
+
# --- Lens 1: FFT Energy ---
|
|
13
|
+
# In 'auto' mode, keep heads that constitute this percentage of cumulative energy.
|
|
14
|
+
fft_energy_keep: float = 0.95
|
|
15
|
+
|
|
16
|
+
# --- Lens 2: Mutual Information ---
|
|
17
|
+
# In 'auto' mode, keep neurons whose cumulative MI constitutes this percentage of the total.
|
|
18
|
+
mi_info_keep: float = 0.90
|
|
19
|
+
|
|
20
|
+
# --- Lens 3: Stability (Koopman) ---
|
|
21
|
+
# Default spectral norm margin when not using auto-calibration.
|
|
22
|
+
koopman_margin: float = 1.05 # A safe default slightly above 1.0
|
|
23
|
+
|
|
24
|
+
# --- Lens 4: RMT Clipping ---
|
|
25
|
+
# Default alpha for upper spectral edge clipping.
|
|
26
|
+
mp_alpha: float = 1.5
|
|
27
|
+
# Default beta for lower spectral edge clipping.
|
|
28
|
+
# mp_beta: float = 0.10
|
|
29
|
+
|
|
30
|
+
# --- Global Compression Target ---
|
|
31
|
+
# Fraction of original trainable parameters to aim for.
|
|
32
|
+
# e.g., 0.70 -> aim for ~70% of the baseline parameter count.
|
|
33
|
+
# This is a high-level objective that the auto-mode translates
|
|
34
|
+
# into concrete head and neuron pruning ratios.
|
|
35
|
+
target_param_keep: float = 0.70
|
|
36
|
+
|
|
37
|
+
# --- General ---
|
|
38
|
+
# Default seed for all deterministic operations.
|
|
39
|
+
seed: int = 42
|
|
40
|
+
|
|
41
|
+
# Variance Equalization minimum gain threshold
|
|
42
|
+
ve_min_gain: float = 0.30
|
|
43
|
+
|
|
44
|
+
|
|
45
|
+
# Create a singleton instance for easy import
|
|
46
|
+
CFG = Defaults()
|
|
47
|
+
|
|
48
|
+
|
|
49
|
+
def get_default_config():
|
|
50
|
+
"""
|
|
51
|
+
Get the default configuration for InvarLock.
|
|
52
|
+
|
|
53
|
+
Returns:
|
|
54
|
+
Defaults: A dataclass instance with default configuration values
|
|
55
|
+
"""
|
|
56
|
+
return Defaults()
|
|
@@ -0,0 +1,62 @@
|
|
|
1
|
+
"""
|
|
2
|
+
InvarLock Core Module
|
|
3
|
+
=================
|
|
4
|
+
|
|
5
|
+
Core torch-independent interfaces and coordination logic.
|
|
6
|
+
|
|
7
|
+
This module provides the foundational abstractions and orchestration
|
|
8
|
+
for the InvarLock framework without requiring heavy dependencies.
|
|
9
|
+
"""
|
|
10
|
+
|
|
11
|
+
from .abi import INVARLOCK_CORE_ABI
|
|
12
|
+
from .api import Guard, ModelAdapter, ModelEdit, RunConfig, RunReport
|
|
13
|
+
from .checkpoint import CheckpointManager
|
|
14
|
+
from .events import EventLogger
|
|
15
|
+
from .exceptions import InvarlockError
|
|
16
|
+
from .registry import PluginInfo, get_registry
|
|
17
|
+
from .types import (
|
|
18
|
+
EditInfo,
|
|
19
|
+
EditType,
|
|
20
|
+
GuardResult,
|
|
21
|
+
GuardType,
|
|
22
|
+
LogLevel,
|
|
23
|
+
ModelInfo,
|
|
24
|
+
RunStatus,
|
|
25
|
+
)
|
|
26
|
+
|
|
27
|
+
__all__ = [
|
|
28
|
+
# Core interfaces
|
|
29
|
+
"ModelAdapter",
|
|
30
|
+
"ModelEdit",
|
|
31
|
+
"Guard",
|
|
32
|
+
# ABI contract
|
|
33
|
+
"INVARLOCK_CORE_ABI",
|
|
34
|
+
"RunConfig",
|
|
35
|
+
"RunReport",
|
|
36
|
+
# Exceptions
|
|
37
|
+
"InvarlockError",
|
|
38
|
+
# Types and enums
|
|
39
|
+
"EditType",
|
|
40
|
+
"GuardType",
|
|
41
|
+
"RunStatus",
|
|
42
|
+
"LogLevel",
|
|
43
|
+
"ModelInfo",
|
|
44
|
+
"EditInfo",
|
|
45
|
+
"GuardResult",
|
|
46
|
+
# Registry and discovery
|
|
47
|
+
"get_registry",
|
|
48
|
+
"PluginInfo",
|
|
49
|
+
# Supporting services
|
|
50
|
+
"EventLogger",
|
|
51
|
+
"CheckpointManager",
|
|
52
|
+
]
|
|
53
|
+
|
|
54
|
+
|
|
55
|
+
# Lazy import CoreRunner to avoid importing heavy dependencies (e.g., NumPy)
|
|
56
|
+
# during lightweight operations such as registry inspection or CLI startup.
|
|
57
|
+
def __getattr__(name: str): # pragma: no cover - simple lazy import shim
|
|
58
|
+
if name == "CoreRunner":
|
|
59
|
+
from .runner import CoreRunner as _CoreRunner
|
|
60
|
+
|
|
61
|
+
return _CoreRunner
|
|
62
|
+
raise AttributeError(name)
|
invarlock/core/abi.py
ADDED
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Core ABI contract for InvarLock plugins.
|
|
3
|
+
|
|
4
|
+
Third-party plugins may declare a matching `INVARLOCK_CORE_ABI` attribute to signal
|
|
5
|
+
compatibility with this release line. Minor bumps may introduce breaking changes
|
|
6
|
+
in plugin contracts; match exactly for stability.
|
|
7
|
+
"""
|
|
8
|
+
|
|
9
|
+
from __future__ import annotations
|
|
10
|
+
|
|
11
|
+
# Plugin ABI for the core interfaces used by adapters/edits/guards.
|
|
12
|
+
# Increment when the plugin-facing contracts change.
|
|
13
|
+
INVARLOCK_CORE_ABI = "0.1"
|
|
14
|
+
|
|
15
|
+
__all__ = ["INVARLOCK_CORE_ABI"]
|
invarlock/core/api.py
ADDED
|
@@ -0,0 +1,274 @@
|
|
|
1
|
+
"""
|
|
2
|
+
InvarLock Core API
|
|
3
|
+
==============
|
|
4
|
+
|
|
5
|
+
Core interfaces and data structures - torch-free abstractions.
|
|
6
|
+
|
|
7
|
+
This module defines the fundamental abstractions used throughout InvarLock:
|
|
8
|
+
- ModelAdapter: Model-specific operations interface
|
|
9
|
+
- ModelEdit: Edit operation interface
|
|
10
|
+
- Guard: Safety validation interface
|
|
11
|
+
- RunConfig: Pipeline configuration
|
|
12
|
+
- RunReport: Execution results
|
|
13
|
+
"""
|
|
14
|
+
|
|
15
|
+
from __future__ import annotations
|
|
16
|
+
|
|
17
|
+
from abc import ABC, abstractmethod
|
|
18
|
+
from dataclasses import dataclass, field
|
|
19
|
+
from pathlib import Path
|
|
20
|
+
from typing import Any
|
|
21
|
+
|
|
22
|
+
|
|
23
|
+
class ModelAdapter(ABC):
|
|
24
|
+
"""
|
|
25
|
+
Abstract interface for model-specific operations.
|
|
26
|
+
|
|
27
|
+
Adapters provide model-agnostic access to different architectures
|
|
28
|
+
(HuggingFace, custom models, etc.) without requiring torch imports
|
|
29
|
+
in the core API.
|
|
30
|
+
"""
|
|
31
|
+
|
|
32
|
+
name: str
|
|
33
|
+
|
|
34
|
+
@abstractmethod
|
|
35
|
+
def can_handle(self, model: Any) -> bool:
|
|
36
|
+
"""Check if this adapter can handle the given model."""
|
|
37
|
+
pass
|
|
38
|
+
|
|
39
|
+
@abstractmethod
|
|
40
|
+
def describe(self, model: Any) -> dict[str, Any]:
|
|
41
|
+
"""
|
|
42
|
+
Get model structure description.
|
|
43
|
+
|
|
44
|
+
Returns:
|
|
45
|
+
Dict with keys: n_layer, heads_per_layer, mlp_dims, tying
|
|
46
|
+
"""
|
|
47
|
+
pass
|
|
48
|
+
|
|
49
|
+
@abstractmethod
|
|
50
|
+
def snapshot(self, model: Any) -> bytes:
|
|
51
|
+
"""Create serialized snapshot of model state."""
|
|
52
|
+
pass
|
|
53
|
+
|
|
54
|
+
@abstractmethod
|
|
55
|
+
def restore(self, model: Any, blob: bytes) -> None:
|
|
56
|
+
"""Restore model state from snapshot."""
|
|
57
|
+
pass
|
|
58
|
+
|
|
59
|
+
|
|
60
|
+
class ModelEdit(ABC):
|
|
61
|
+
"""
|
|
62
|
+
Abstract interface for model editing operations.
|
|
63
|
+
|
|
64
|
+
Edits modify model parameters/structure while maintaining
|
|
65
|
+
the model's interface and basic functionality.
|
|
66
|
+
"""
|
|
67
|
+
|
|
68
|
+
name: str
|
|
69
|
+
|
|
70
|
+
@abstractmethod
|
|
71
|
+
def can_edit(self, model_desc: dict[str, Any]) -> bool:
|
|
72
|
+
"""Check if this edit can be applied to the described model."""
|
|
73
|
+
pass
|
|
74
|
+
|
|
75
|
+
@abstractmethod
|
|
76
|
+
def apply(self, model: Any, adapter: ModelAdapter, **kwargs) -> dict[str, Any]:
|
|
77
|
+
"""
|
|
78
|
+
Apply the edit to the model.
|
|
79
|
+
|
|
80
|
+
Args:
|
|
81
|
+
model: The model to edit
|
|
82
|
+
adapter: Adapter for model-specific operations
|
|
83
|
+
**kwargs: Edit-specific parameters
|
|
84
|
+
|
|
85
|
+
Returns:
|
|
86
|
+
Dict with edit metadata and statistics
|
|
87
|
+
"""
|
|
88
|
+
pass
|
|
89
|
+
|
|
90
|
+
|
|
91
|
+
class Guard(ABC):
|
|
92
|
+
"""
|
|
93
|
+
Abstract interface for safety guards.
|
|
94
|
+
|
|
95
|
+
Guards validate model state and behavior to ensure
|
|
96
|
+
edits don't cause unacceptable degradation.
|
|
97
|
+
"""
|
|
98
|
+
|
|
99
|
+
name: str
|
|
100
|
+
|
|
101
|
+
@abstractmethod
|
|
102
|
+
def validate(
|
|
103
|
+
self, model: Any, adapter: ModelAdapter, context: dict[str, Any]
|
|
104
|
+
) -> dict[str, Any]:
|
|
105
|
+
"""
|
|
106
|
+
Validate model state/behavior.
|
|
107
|
+
|
|
108
|
+
Args:
|
|
109
|
+
model: The model to validate
|
|
110
|
+
adapter: Adapter for model operations
|
|
111
|
+
context: Validation context (baseline metrics, etc.)
|
|
112
|
+
|
|
113
|
+
Returns:
|
|
114
|
+
Dict with validation results and status
|
|
115
|
+
"""
|
|
116
|
+
pass
|
|
117
|
+
|
|
118
|
+
|
|
119
|
+
class GuardChain:
|
|
120
|
+
"""
|
|
121
|
+
Manages a chain of guards with policy-based execution.
|
|
122
|
+
|
|
123
|
+
Provides lifecycle management (prepare, before_edit, after_edit, finalize)
|
|
124
|
+
and aggregates results across multiple guards.
|
|
125
|
+
"""
|
|
126
|
+
|
|
127
|
+
def __init__(self, guards: list[Guard], policy: Any | None = None):
|
|
128
|
+
"""
|
|
129
|
+
Initialize guard chain.
|
|
130
|
+
|
|
131
|
+
Args:
|
|
132
|
+
guards: List of guard instances
|
|
133
|
+
policy: Policy configuration for guard execution
|
|
134
|
+
"""
|
|
135
|
+
self.guards = guards
|
|
136
|
+
self.policy = policy
|
|
137
|
+
|
|
138
|
+
def prepare_all(
|
|
139
|
+
self,
|
|
140
|
+
model: Any,
|
|
141
|
+
adapter: ModelAdapter,
|
|
142
|
+
calib: Any,
|
|
143
|
+
policy_config: dict[str, Any],
|
|
144
|
+
) -> dict[str, Any]:
|
|
145
|
+
"""Prepare all guards."""
|
|
146
|
+
results = {}
|
|
147
|
+
for guard in self.guards:
|
|
148
|
+
if hasattr(guard, "prepare"):
|
|
149
|
+
results[guard.name] = guard.prepare(
|
|
150
|
+
model, adapter, calib, policy_config
|
|
151
|
+
)
|
|
152
|
+
else:
|
|
153
|
+
results[guard.name] = {"ready": True}
|
|
154
|
+
return results
|
|
155
|
+
|
|
156
|
+
def before_edit_all(self, model: Any) -> list[Any]:
|
|
157
|
+
"""Execute before_edit on all guards."""
|
|
158
|
+
results = []
|
|
159
|
+
for guard in self.guards:
|
|
160
|
+
if hasattr(guard, "before_edit"):
|
|
161
|
+
result = guard.before_edit(model)
|
|
162
|
+
if result is not None:
|
|
163
|
+
results.append(result)
|
|
164
|
+
return results
|
|
165
|
+
|
|
166
|
+
def after_edit_all(self, model: Any) -> list[Any]:
|
|
167
|
+
"""Execute after_edit on all guards."""
|
|
168
|
+
results = []
|
|
169
|
+
for guard in self.guards:
|
|
170
|
+
if hasattr(guard, "after_edit"):
|
|
171
|
+
result = guard.after_edit(model)
|
|
172
|
+
if result is not None:
|
|
173
|
+
results.append(result)
|
|
174
|
+
return results
|
|
175
|
+
|
|
176
|
+
def finalize_all(self, model: Any) -> list[Any]:
|
|
177
|
+
"""Finalize all guards and return outcomes."""
|
|
178
|
+
results = []
|
|
179
|
+
for guard in self.guards:
|
|
180
|
+
if hasattr(guard, "finalize"):
|
|
181
|
+
result = guard.finalize(model)
|
|
182
|
+
results.append(result)
|
|
183
|
+
return results
|
|
184
|
+
|
|
185
|
+
def all_passed(self, outcomes: list[Any]) -> bool:
|
|
186
|
+
"""Check if all guard outcomes passed."""
|
|
187
|
+
for outcome in outcomes:
|
|
188
|
+
if hasattr(outcome, "passed") and not outcome.passed:
|
|
189
|
+
return False
|
|
190
|
+
return True
|
|
191
|
+
|
|
192
|
+
def get_worst_action(self, outcomes: list[Any]) -> str:
|
|
193
|
+
"""Get the worst action from all outcomes."""
|
|
194
|
+
actions = []
|
|
195
|
+
for outcome in outcomes:
|
|
196
|
+
if hasattr(outcome, "action"):
|
|
197
|
+
actions.append(outcome.action)
|
|
198
|
+
|
|
199
|
+
# Import from types to avoid circular dependency
|
|
200
|
+
from .types import get_worst_action
|
|
201
|
+
|
|
202
|
+
return get_worst_action(actions)
|
|
203
|
+
|
|
204
|
+
|
|
205
|
+
# Type alias for calibration data
|
|
206
|
+
CalibrationData = Any
|
|
207
|
+
|
|
208
|
+
|
|
209
|
+
@dataclass
|
|
210
|
+
class RunConfig:
|
|
211
|
+
"""
|
|
212
|
+
Configuration for a InvarLock pipeline run.
|
|
213
|
+
|
|
214
|
+
Contains all parameters needed to execute the full pipeline:
|
|
215
|
+
prepare → edit → guards → eval → finalize/rollback.
|
|
216
|
+
"""
|
|
217
|
+
|
|
218
|
+
# Device configuration
|
|
219
|
+
device: str = "auto"
|
|
220
|
+
|
|
221
|
+
# Safety thresholds
|
|
222
|
+
max_pm_ratio: float = 1.5
|
|
223
|
+
spike_threshold: float = 2.0 # Catastrophic spike threshold for immediate rollback
|
|
224
|
+
|
|
225
|
+
# Output configuration
|
|
226
|
+
event_path: Path | None = None
|
|
227
|
+
checkpoint_interval: int = 0 # 0 = disabled
|
|
228
|
+
|
|
229
|
+
# Execution flags
|
|
230
|
+
dry_run: bool = False
|
|
231
|
+
verbose: bool = False
|
|
232
|
+
|
|
233
|
+
# Context for guards/eval
|
|
234
|
+
context: dict[str, Any] = field(default_factory=dict)
|
|
235
|
+
|
|
236
|
+
|
|
237
|
+
@dataclass
|
|
238
|
+
class RunReport:
|
|
239
|
+
"""
|
|
240
|
+
Results from a InvarLock pipeline execution.
|
|
241
|
+
|
|
242
|
+
Contains comprehensive information about what was executed
|
|
243
|
+
and the outcomes, suitable for analysis and certification.
|
|
244
|
+
"""
|
|
245
|
+
|
|
246
|
+
# Execution metadata
|
|
247
|
+
meta: dict[str, Any] = field(default_factory=dict)
|
|
248
|
+
|
|
249
|
+
# Edit information
|
|
250
|
+
edit: dict[str, Any] = field(default_factory=dict)
|
|
251
|
+
|
|
252
|
+
# Guard validation results
|
|
253
|
+
guards: dict[str, dict[str, Any]] = field(default_factory=dict)
|
|
254
|
+
|
|
255
|
+
# Evaluation metrics
|
|
256
|
+
metrics: dict[str, Any] = field(default_factory=dict)
|
|
257
|
+
|
|
258
|
+
# Captured evaluation windows and auxiliary data
|
|
259
|
+
evaluation_windows: dict[str, Any] = field(default_factory=dict)
|
|
260
|
+
|
|
261
|
+
# Execution status
|
|
262
|
+
status: str = "pending" # pending, success, failed, rollback
|
|
263
|
+
|
|
264
|
+
# Error information (if any)
|
|
265
|
+
error: str | None = None
|
|
266
|
+
|
|
267
|
+
# Additional context
|
|
268
|
+
context: dict[str, Any] = field(default_factory=dict)
|
|
269
|
+
|
|
270
|
+
|
|
271
|
+
# Type aliases for common patterns
|
|
272
|
+
ModelType = Any # Actual model type (torch.nn.Module, etc.)
|
|
273
|
+
DeviceType = str | Any # Device specification
|
|
274
|
+
MetricsDict = dict[str, float | int | str | bool]
|