invarlock 0.2.0__py3-none-any.whl → 0.3.1__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 +1 -1
- invarlock/_data/runtime/profiles/ci_cpu.yaml +5 -0
- invarlock/adapters/__init__.py +13 -0
- invarlock/adapters/auto.py +149 -22
- invarlock/adapters/capabilities.py +421 -0
- invarlock/adapters/hf_llama.py +2 -2
- invarlock/adapters/hf_mixin.py +122 -1
- invarlock/cli/commands/doctor.py +7 -1
- invarlock/cli/commands/run.py +148 -2
- invarlock/core/registry.py +34 -6
- invarlock/guards/variance.py +41 -6
- invarlock/plugins/hf_awq_adapter.py +22 -1
- invarlock/plugins/hf_bnb_adapter.py +117 -22
- invarlock/plugins/hf_gptq_adapter.py +24 -1
- invarlock/reporting/certificate.py +155 -15
- {invarlock-0.2.0.dist-info → invarlock-0.3.1.dist-info}/METADATA +2 -2
- {invarlock-0.2.0.dist-info → invarlock-0.3.1.dist-info}/RECORD +21 -20
- {invarlock-0.2.0.dist-info → invarlock-0.3.1.dist-info}/WHEEL +0 -0
- {invarlock-0.2.0.dist-info → invarlock-0.3.1.dist-info}/entry_points.txt +0 -0
- {invarlock-0.2.0.dist-info → invarlock-0.3.1.dist-info}/licenses/LICENSE +0 -0
- {invarlock-0.2.0.dist-info → invarlock-0.3.1.dist-info}/top_level.txt +0 -0
|
@@ -5,18 +5,73 @@ HuggingFace BitsAndBytes Adapter (plugin)
|
|
|
5
5
|
Optional adapter for loading 4/8-bit quantized causal LMs via bitsandbytes
|
|
6
6
|
through Transformers. Requires GPU for practical use.
|
|
7
7
|
Install with the `gpu` extra on supported platforms.
|
|
8
|
+
|
|
9
|
+
This adapter handles both:
|
|
10
|
+
1. Fresh quantization of FP16 models (load_in_8bit/load_in_4bit)
|
|
11
|
+
2. Loading pre-quantized BNB checkpoints (auto-detected via quantization_config)
|
|
8
12
|
"""
|
|
9
13
|
|
|
10
14
|
from __future__ import annotations
|
|
11
15
|
|
|
16
|
+
from pathlib import Path
|
|
12
17
|
from typing import Any
|
|
13
18
|
|
|
19
|
+
from invarlock.adapters.capabilities import (
|
|
20
|
+
ModelCapabilities,
|
|
21
|
+
QuantizationMethod,
|
|
22
|
+
detect_quantization_from_config,
|
|
23
|
+
)
|
|
14
24
|
from invarlock.adapters.hf_mixin import HFAdapterMixin
|
|
15
25
|
from invarlock.core.api import ModelAdapter
|
|
16
26
|
from invarlock.core.error_utils import wrap_errors
|
|
17
27
|
from invarlock.core.exceptions import DependencyError, ModelLoadError
|
|
18
28
|
|
|
19
29
|
|
|
30
|
+
def _is_local_path(model_id: str) -> bool:
|
|
31
|
+
"""Check if model_id is a local filesystem path."""
|
|
32
|
+
return Path(model_id).exists()
|
|
33
|
+
|
|
34
|
+
|
|
35
|
+
def _detect_pre_quantized_bnb(model_id: str) -> tuple[bool, int]:
|
|
36
|
+
"""
|
|
37
|
+
Detect if a local checkpoint is pre-quantized with BNB.
|
|
38
|
+
|
|
39
|
+
Returns:
|
|
40
|
+
Tuple of (is_pre_quantized, bits) where bits is 8 or 4.
|
|
41
|
+
"""
|
|
42
|
+
if not _is_local_path(model_id):
|
|
43
|
+
return False, 0
|
|
44
|
+
|
|
45
|
+
config_path = Path(model_id) / "config.json"
|
|
46
|
+
if not config_path.exists():
|
|
47
|
+
return False, 0
|
|
48
|
+
|
|
49
|
+
try:
|
|
50
|
+
import json
|
|
51
|
+
|
|
52
|
+
config_data = json.loads(config_path.read_text())
|
|
53
|
+
quant_cfg = config_data.get("quantization_config", {})
|
|
54
|
+
|
|
55
|
+
if not quant_cfg:
|
|
56
|
+
return False, 0
|
|
57
|
+
|
|
58
|
+
# Check for BNB quantization
|
|
59
|
+
quant_method = quant_cfg.get("quant_method", "").lower()
|
|
60
|
+
if quant_method == "bitsandbytes" or "load_in_8bit" in quant_cfg:
|
|
61
|
+
if quant_cfg.get("load_in_8bit"):
|
|
62
|
+
return True, 8
|
|
63
|
+
if quant_cfg.get("load_in_4bit"):
|
|
64
|
+
return True, 4
|
|
65
|
+
# Fallback to bits field
|
|
66
|
+
bits = quant_cfg.get("bits", 8)
|
|
67
|
+
return True, bits
|
|
68
|
+
|
|
69
|
+
except Exception:
|
|
70
|
+
pass
|
|
71
|
+
|
|
72
|
+
return False, 0
|
|
73
|
+
|
|
74
|
+
|
|
20
75
|
class HF_BNB_Adapter(HFAdapterMixin, ModelAdapter):
|
|
21
76
|
name = "hf_bnb"
|
|
22
77
|
|
|
@@ -29,32 +84,72 @@ class HF_BNB_Adapter(HFAdapterMixin, ModelAdapter):
|
|
|
29
84
|
):
|
|
30
85
|
from transformers import AutoModelForCausalLM
|
|
31
86
|
|
|
32
|
-
#
|
|
33
|
-
|
|
34
|
-
load_in_4bit = bool(kwargs.pop("load_in_4bit", False))
|
|
87
|
+
# Check if this is a pre-quantized checkpoint
|
|
88
|
+
is_pre_quantized, pre_quant_bits = _detect_pre_quantized_bnb(model_id)
|
|
35
89
|
|
|
36
|
-
if
|
|
37
|
-
|
|
90
|
+
if is_pre_quantized:
|
|
91
|
+
# Load pre-quantized checkpoint WITHOUT re-applying quantization
|
|
92
|
+
with wrap_errors(
|
|
93
|
+
ModelLoadError,
|
|
94
|
+
"E201",
|
|
95
|
+
"MODEL-LOAD-FAILED: bitsandbytes/transformers (pre-quantized)",
|
|
96
|
+
lambda e: {"model_id": model_id, "pre_quantized_bits": pre_quant_bits},
|
|
97
|
+
):
|
|
98
|
+
model = AutoModelForCausalLM.from_pretrained(
|
|
99
|
+
model_id,
|
|
100
|
+
device_map="auto",
|
|
101
|
+
trust_remote_code=True,
|
|
102
|
+
# Do NOT pass load_in_8bit/load_in_4bit for pre-quantized
|
|
103
|
+
**{
|
|
104
|
+
k: v
|
|
105
|
+
for k, v in kwargs.items()
|
|
106
|
+
if k not in ("load_in_8bit", "load_in_4bit")
|
|
107
|
+
},
|
|
108
|
+
)
|
|
109
|
+
else:
|
|
110
|
+
# Fresh quantization of FP16 model
|
|
111
|
+
load_in_8bit = bool(kwargs.pop("load_in_8bit", True))
|
|
112
|
+
load_in_4bit = bool(kwargs.pop("load_in_4bit", False))
|
|
38
113
|
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
model_id,
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
114
|
+
if load_in_4bit:
|
|
115
|
+
load_in_8bit = False
|
|
116
|
+
|
|
117
|
+
with wrap_errors(
|
|
118
|
+
ModelLoadError,
|
|
119
|
+
"E201",
|
|
120
|
+
"MODEL-LOAD-FAILED: bitsandbytes/transformers",
|
|
121
|
+
lambda e: {"model_id": model_id},
|
|
122
|
+
):
|
|
123
|
+
model = AutoModelForCausalLM.from_pretrained(
|
|
124
|
+
model_id,
|
|
125
|
+
device_map="auto",
|
|
126
|
+
load_in_8bit=load_in_8bit,
|
|
127
|
+
load_in_4bit=load_in_4bit,
|
|
128
|
+
trust_remote_code=True,
|
|
129
|
+
**kwargs,
|
|
130
|
+
)
|
|
131
|
+
|
|
132
|
+
# BNB models handle their own device placement via device_map="auto"
|
|
133
|
+
# Do NOT call .to() on BNB models - it will raise an error
|
|
134
|
+
_ = self._resolve_device(device) # Validate device string only
|
|
56
135
|
return model
|
|
57
136
|
|
|
137
|
+
def get_capabilities(self, model: Any) -> ModelCapabilities:
|
|
138
|
+
"""Return capabilities for a BNB-quantized model."""
|
|
139
|
+
config = getattr(model, "config", None)
|
|
140
|
+
if config is not None:
|
|
141
|
+
quant_cfg = detect_quantization_from_config(config)
|
|
142
|
+
if quant_cfg.method == QuantizationMethod.BNB_8BIT:
|
|
143
|
+
return ModelCapabilities.for_bnb_8bit(from_checkpoint=True)
|
|
144
|
+
elif quant_cfg.method == QuantizationMethod.BNB_4BIT:
|
|
145
|
+
return ModelCapabilities.for_bnb_4bit(
|
|
146
|
+
from_checkpoint=True,
|
|
147
|
+
double_quant=quant_cfg.double_quant,
|
|
148
|
+
)
|
|
149
|
+
|
|
150
|
+
# Default to 8-bit if we can't determine
|
|
151
|
+
return ModelCapabilities.for_bnb_8bit()
|
|
152
|
+
|
|
58
153
|
def can_handle(self, model: Any) -> bool:
|
|
59
154
|
cfg = getattr(model, "config", None)
|
|
60
155
|
return hasattr(cfg, "n_layer") or hasattr(cfg, "num_hidden_layers")
|
|
@@ -4,12 +4,16 @@ HuggingFace GPTQ Adapter (plugin)
|
|
|
4
4
|
|
|
5
5
|
Optional adapter for loading AutoGPTQ-quantized causal LMs from the Hub.
|
|
6
6
|
Requires the `auto-gptq` extra on supported platforms (typically Linux/CUDA).
|
|
7
|
+
|
|
8
|
+
GPTQ models are pre-quantized and typically handle device placement internally
|
|
9
|
+
during loading. This adapter uses safe device movement to respect constraints.
|
|
7
10
|
"""
|
|
8
11
|
|
|
9
12
|
from __future__ import annotations
|
|
10
13
|
|
|
11
14
|
from typing import Any
|
|
12
15
|
|
|
16
|
+
from invarlock.adapters.capabilities import ModelCapabilities
|
|
13
17
|
from invarlock.adapters.hf_mixin import HFAdapterMixin
|
|
14
18
|
from invarlock.core.api import ModelAdapter
|
|
15
19
|
from invarlock.core.error_utils import wrap_errors
|
|
@@ -47,7 +51,26 @@ class HF_GPTQ_Adapter(HFAdapterMixin, ModelAdapter):
|
|
|
47
51
|
**{k: v for k, v in kwargs.items() if k not in {"device"}},
|
|
48
52
|
)
|
|
49
53
|
|
|
50
|
-
|
|
54
|
+
# GPTQ models are pre-quantized; use safe device movement
|
|
55
|
+
# which respects the model's device constraints
|
|
56
|
+
return self._safe_to_device(
|
|
57
|
+
model, device, capabilities=ModelCapabilities.for_gptq()
|
|
58
|
+
)
|
|
59
|
+
|
|
60
|
+
def get_capabilities(self, model: Any) -> ModelCapabilities:
|
|
61
|
+
"""Return capabilities for a GPTQ-quantized model."""
|
|
62
|
+
config = getattr(model, "config", None)
|
|
63
|
+
bits = 4 # Default GPTQ bits
|
|
64
|
+
group_size = 128 # Default GPTQ group size
|
|
65
|
+
if config is not None:
|
|
66
|
+
quant_cfg = getattr(config, "quantization_config", None)
|
|
67
|
+
if isinstance(quant_cfg, dict):
|
|
68
|
+
bits = quant_cfg.get("bits", 4)
|
|
69
|
+
group_size = quant_cfg.get("group_size", 128)
|
|
70
|
+
elif quant_cfg is not None:
|
|
71
|
+
bits = getattr(quant_cfg, "bits", 4)
|
|
72
|
+
group_size = getattr(quant_cfg, "group_size", 128)
|
|
73
|
+
return ModelCapabilities.for_gptq(bits=bits, group_size=group_size)
|
|
51
74
|
|
|
52
75
|
# ---- Introspection ----
|
|
53
76
|
def can_handle(self, model: Any) -> bool:
|
|
@@ -13,6 +13,7 @@ from __future__ import annotations
|
|
|
13
13
|
# mypy: ignore-errors
|
|
14
14
|
import copy
|
|
15
15
|
import hashlib
|
|
16
|
+
import inspect
|
|
16
17
|
import json
|
|
17
18
|
import math
|
|
18
19
|
import os
|
|
@@ -1322,24 +1323,36 @@ def make_certificate(
|
|
|
1322
1323
|
capacity_tokens = None
|
|
1323
1324
|
capacity_examples = None
|
|
1324
1325
|
|
|
1325
|
-
|
|
1326
|
-
|
|
1327
|
-
|
|
1328
|
-
|
|
1329
|
-
|
|
1330
|
-
|
|
1331
|
-
|
|
1332
|
-
auto.get("
|
|
1333
|
-
|
|
1334
|
-
|
|
1326
|
+
pm_acceptance_range = _resolve_pm_acceptance_range_from_report(report)
|
|
1327
|
+
|
|
1328
|
+
validation_kwargs = {
|
|
1329
|
+
"ppl": ppl_analysis,
|
|
1330
|
+
"spectral": spectral,
|
|
1331
|
+
"rmt": rmt,
|
|
1332
|
+
"invariants": invariants,
|
|
1333
|
+
"tier": auto.get("tier", "balanced"),
|
|
1334
|
+
"_ppl_metrics": ppl_metrics,
|
|
1335
|
+
"target_ratio": auto.get("target_pm_ratio"),
|
|
1336
|
+
"guard_overhead": guard_overhead_section,
|
|
1337
|
+
"primary_metric": report.get("metrics", {}).get("primary_metric")
|
|
1335
1338
|
if isinstance(report.get("metrics"), dict)
|
|
1336
1339
|
else None,
|
|
1337
|
-
moe_section,
|
|
1338
|
-
{
|
|
1340
|
+
"moe": moe_section,
|
|
1341
|
+
"dataset_capacity": {
|
|
1339
1342
|
"tokens_available": capacity_tokens,
|
|
1340
1343
|
"examples_available": capacity_examples,
|
|
1341
1344
|
},
|
|
1342
|
-
|
|
1345
|
+
}
|
|
1346
|
+
try:
|
|
1347
|
+
if (
|
|
1348
|
+
"pm_acceptance_range"
|
|
1349
|
+
in inspect.signature(_compute_validation_flags).parameters
|
|
1350
|
+
):
|
|
1351
|
+
validation_kwargs["pm_acceptance_range"] = pm_acceptance_range
|
|
1352
|
+
except Exception: # pragma: no cover - defensive against patched functions
|
|
1353
|
+
validation_kwargs["pm_acceptance_range"] = pm_acceptance_range
|
|
1354
|
+
|
|
1355
|
+
validation_flags = _compute_validation_flags(**validation_kwargs)
|
|
1343
1356
|
# Enforce validation key allow-list to prevent surface drift
|
|
1344
1357
|
_allowed_validation = _load_validation_allowlist()
|
|
1345
1358
|
validation_filtered = {
|
|
@@ -2537,6 +2550,103 @@ def _build_provenance_block(
|
|
|
2537
2550
|
return provenance
|
|
2538
2551
|
|
|
2539
2552
|
|
|
2553
|
+
def _resolve_pm_acceptance_range_from_report(
|
|
2554
|
+
report: dict[str, Any] | None,
|
|
2555
|
+
) -> dict[str, float]:
|
|
2556
|
+
"""Resolve primary-metric acceptance bounds from report context/meta/env."""
|
|
2557
|
+
|
|
2558
|
+
base_min = 0.95
|
|
2559
|
+
base_max = 1.10
|
|
2560
|
+
|
|
2561
|
+
def _safe_float(val: Any) -> float | None:
|
|
2562
|
+
try:
|
|
2563
|
+
if val is None:
|
|
2564
|
+
return None
|
|
2565
|
+
return float(val)
|
|
2566
|
+
except Exception:
|
|
2567
|
+
return None
|
|
2568
|
+
|
|
2569
|
+
cfg_min = None
|
|
2570
|
+
cfg_max = None
|
|
2571
|
+
ctx = report.get("context") if isinstance(report, dict) else None
|
|
2572
|
+
if isinstance(ctx, dict):
|
|
2573
|
+
pm_ctx = (
|
|
2574
|
+
ctx.get("primary_metric")
|
|
2575
|
+
if isinstance(ctx.get("primary_metric"), dict)
|
|
2576
|
+
else {}
|
|
2577
|
+
)
|
|
2578
|
+
if isinstance(pm_ctx, dict):
|
|
2579
|
+
cfg_min = _safe_float(pm_ctx.get("acceptance_range", {}).get("min"))
|
|
2580
|
+
cfg_max = _safe_float(pm_ctx.get("acceptance_range", {}).get("max"))
|
|
2581
|
+
if cfg_min is None or cfg_max is None:
|
|
2582
|
+
alt = ctx.get("pm_acceptance_range")
|
|
2583
|
+
if isinstance(alt, dict):
|
|
2584
|
+
cfg_min = (
|
|
2585
|
+
cfg_min if cfg_min is not None else _safe_float(alt.get("min"))
|
|
2586
|
+
)
|
|
2587
|
+
cfg_max = (
|
|
2588
|
+
cfg_max if cfg_max is not None else _safe_float(alt.get("max"))
|
|
2589
|
+
)
|
|
2590
|
+
|
|
2591
|
+
if (cfg_min is None or cfg_max is None) and isinstance(report, dict):
|
|
2592
|
+
meta = report.get("meta")
|
|
2593
|
+
if isinstance(meta, dict):
|
|
2594
|
+
meta_range = meta.get("pm_acceptance_range")
|
|
2595
|
+
if isinstance(meta_range, dict):
|
|
2596
|
+
cfg_min = (
|
|
2597
|
+
cfg_min
|
|
2598
|
+
if cfg_min is not None
|
|
2599
|
+
else _safe_float(meta_range.get("min"))
|
|
2600
|
+
)
|
|
2601
|
+
cfg_max = (
|
|
2602
|
+
cfg_max
|
|
2603
|
+
if cfg_max is not None
|
|
2604
|
+
else _safe_float(meta_range.get("max"))
|
|
2605
|
+
)
|
|
2606
|
+
|
|
2607
|
+
def _parse_env(name: str) -> float | None:
|
|
2608
|
+
try:
|
|
2609
|
+
raw = os.environ.get(name, "")
|
|
2610
|
+
if raw is None or str(raw).strip() == "":
|
|
2611
|
+
return None
|
|
2612
|
+
return float(raw)
|
|
2613
|
+
except Exception:
|
|
2614
|
+
return None
|
|
2615
|
+
|
|
2616
|
+
env_min = _parse_env("INVARLOCK_PM_ACCEPTANCE_MIN")
|
|
2617
|
+
env_max = _parse_env("INVARLOCK_PM_ACCEPTANCE_MAX")
|
|
2618
|
+
|
|
2619
|
+
has_explicit = any(v is not None for v in (cfg_min, cfg_max, env_min, env_max))
|
|
2620
|
+
if not has_explicit:
|
|
2621
|
+
return {}
|
|
2622
|
+
|
|
2623
|
+
min_val = (
|
|
2624
|
+
env_min if env_min is not None else cfg_min if cfg_min is not None else base_min
|
|
2625
|
+
)
|
|
2626
|
+
max_val = (
|
|
2627
|
+
env_max if env_max is not None else cfg_max if cfg_max is not None else base_max
|
|
2628
|
+
)
|
|
2629
|
+
|
|
2630
|
+
try:
|
|
2631
|
+
if min_val is not None and min_val <= 0:
|
|
2632
|
+
min_val = base_min
|
|
2633
|
+
except Exception:
|
|
2634
|
+
min_val = base_min
|
|
2635
|
+
try:
|
|
2636
|
+
if max_val is not None and max_val <= 0:
|
|
2637
|
+
max_val = base_max
|
|
2638
|
+
except Exception:
|
|
2639
|
+
max_val = base_max
|
|
2640
|
+
|
|
2641
|
+
try:
|
|
2642
|
+
if max_val is not None and min_val is not None and max_val < min_val:
|
|
2643
|
+
max_val = min_val
|
|
2644
|
+
except Exception:
|
|
2645
|
+
max_val = base_max
|
|
2646
|
+
|
|
2647
|
+
return {"min": float(min_val), "max": float(max_val)}
|
|
2648
|
+
|
|
2649
|
+
|
|
2540
2650
|
def _compute_validation_flags(
|
|
2541
2651
|
ppl: dict[str, Any],
|
|
2542
2652
|
spectral: dict[str, Any],
|
|
@@ -2549,6 +2659,7 @@ def _compute_validation_flags(
|
|
|
2549
2659
|
primary_metric: dict[str, Any] | None = None,
|
|
2550
2660
|
moe: dict[str, Any] | None = None,
|
|
2551
2661
|
dataset_capacity: dict[str, Any] | None = None,
|
|
2662
|
+
pm_acceptance_range: dict[str, float] | None = None,
|
|
2552
2663
|
) -> dict[str, bool]:
|
|
2553
2664
|
"""Compute validation flags for the certificate including canonical gates."""
|
|
2554
2665
|
tier = (tier or "balanced").lower()
|
|
@@ -2569,7 +2680,25 @@ def _compute_validation_flags(
|
|
|
2569
2680
|
"aggressive": 1.20,
|
|
2570
2681
|
"none": 1.10,
|
|
2571
2682
|
}
|
|
2572
|
-
|
|
2683
|
+
acceptance = pm_acceptance_range if isinstance(pm_acceptance_range, dict) else {}
|
|
2684
|
+
ratio_min_bound = None
|
|
2685
|
+
ratio_max_bound = None
|
|
2686
|
+
try:
|
|
2687
|
+
if acceptance.get("min") is not None:
|
|
2688
|
+
ratio_min_bound = float(acceptance.get("min"))
|
|
2689
|
+
except Exception:
|
|
2690
|
+
ratio_min_bound = None
|
|
2691
|
+
try:
|
|
2692
|
+
if acceptance.get("max") is not None:
|
|
2693
|
+
ratio_max_bound = float(acceptance.get("max"))
|
|
2694
|
+
except Exception:
|
|
2695
|
+
ratio_max_bound = None
|
|
2696
|
+
|
|
2697
|
+
ratio_limit = (
|
|
2698
|
+
ratio_max_bound
|
|
2699
|
+
if isinstance(ratio_max_bound, (int | float)) and math.isfinite(ratio_max_bound)
|
|
2700
|
+
else tier_thresholds.get(tier, 1.10)
|
|
2701
|
+
)
|
|
2573
2702
|
if isinstance(target_ratio, int | float) and target_ratio > 0:
|
|
2574
2703
|
ratio_limit = min(ratio_limit, float(target_ratio))
|
|
2575
2704
|
|
|
@@ -2636,9 +2765,18 @@ def _compute_validation_flags(
|
|
|
2636
2765
|
tokens_ok_eff = tokens_ok or _tiny_relax
|
|
2637
2766
|
# Apply hysteresis to ratio limit if needed
|
|
2638
2767
|
ratio_limit_with_hyst = ratio_limit + max(0.0, hysteresis_ratio)
|
|
2768
|
+
lower_bound_ok = True
|
|
2769
|
+
if ratio_min_bound is not None and isinstance(ratio_vs_baseline, (int | float)):
|
|
2770
|
+
try:
|
|
2771
|
+
lower_bound_ok = math.isfinite(float(ratio_vs_baseline)) and (
|
|
2772
|
+
float(ratio_vs_baseline) >= float(ratio_min_bound)
|
|
2773
|
+
)
|
|
2774
|
+
except Exception:
|
|
2775
|
+
lower_bound_ok = True
|
|
2639
2776
|
compression_acceptable = (
|
|
2640
2777
|
isinstance(ratio_vs_baseline, int | float)
|
|
2641
2778
|
and math.isfinite(ratio_vs_baseline)
|
|
2779
|
+
and lower_bound_ok
|
|
2642
2780
|
and ratio_vs_baseline <= ratio_limit_with_hyst
|
|
2643
2781
|
and tokens_ok_eff
|
|
2644
2782
|
)
|
|
@@ -2655,7 +2793,9 @@ def _compute_validation_flags(
|
|
|
2655
2793
|
and all(isinstance(x, int | float) and math.isfinite(x) for x in ratio_ci)
|
|
2656
2794
|
):
|
|
2657
2795
|
compression_acceptable = (
|
|
2658
|
-
compression_acceptable
|
|
2796
|
+
compression_acceptable
|
|
2797
|
+
and ratio_ci[1] <= ratio_limit_with_hyst
|
|
2798
|
+
and (ratio_min_bound is None or ratio_ci[0] >= ratio_min_bound)
|
|
2659
2799
|
)
|
|
2660
2800
|
|
|
2661
2801
|
# 3. RMT ε-rule compliance
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: invarlock
|
|
3
|
-
Version: 0.
|
|
3
|
+
Version: 0.3.1
|
|
4
4
|
Summary: Edit‑agnostic robustness certificates for weight edits (InvarLock framework)
|
|
5
5
|
Author-email: InvarLock Team <oss@invarlock.dev>
|
|
6
6
|
Maintainer-email: InvarLock Maintainers <support@invarlock.dev>
|
|
@@ -112,7 +112,7 @@ they don’t, roll back safely.
|
|
|
112
112
|
Technical: edit‑agnostic guard pipeline (invariants → spectral → RMT →
|
|
113
113
|
variance) producing a machine‑readable Safety Certificate.
|
|
114
114
|
|
|
115
|
-
> **Status:** 0.
|
|
115
|
+
> **Status:** 0.3.1 (pre‑1.0). Until 1.0, **minor** releases may be
|
|
116
116
|
> breaking. See CLI help and the CHANGELOG for updates.
|
|
117
117
|
|
|
118
118
|
[](https://github.com/invarlock/invarlock/actions/workflows/ci.yml)
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
invarlock/__init__.py,sha256=
|
|
1
|
+
invarlock/__init__.py,sha256=YQiNLCps9m3tcPWMQ2ikf9_v9aA8C-JGvJAAhHMBNSo,1268
|
|
2
2
|
invarlock/__main__.py,sha256=ffhoKctw89j-henmQXThbHDIdlvK9fBfsy8LpjhOEXc,146
|
|
3
3
|
invarlock/config.py,sha256=7BUOl7EW258YnsgRipjOx6lmWou5jNDzimREd35ewsQ,1725
|
|
4
4
|
invarlock/model_profile.py,sha256=sFHpK-1Q-1DjiZTWMolQBG4Dw9feLJgk3GnaD3ixgd8,12809
|
|
@@ -7,17 +7,18 @@ invarlock/py.typed,sha256=LCPmZeE_vANVVJDNvuq9A07i7jg9Nxrq6f10UeuNfZc,37
|
|
|
7
7
|
invarlock/security.py,sha256=xUjbHu-bHKUfFqDyN_21Hj00NBmAcGgnAg4JCjb0JOE,4861
|
|
8
8
|
invarlock/sparsity_utils.py,sha256=30SC3osptca2GmzxezbfX31EE6sRUhmEz8u3jn4vB2c,8281
|
|
9
9
|
invarlock/_data/runtime/tiers.yaml,sha256=A0NMcz5o-TXJp1gZq2Sd7uYAQSsMQcG0ZV1FuLxl5jA,1700
|
|
10
|
-
invarlock/_data/runtime/profiles/ci_cpu.yaml,sha256=
|
|
10
|
+
invarlock/_data/runtime/profiles/ci_cpu.yaml,sha256=FE5vxeemAFWW7oRxPvp_8hIcCO8qrJilvQpXuS3f6u0,389
|
|
11
11
|
invarlock/_data/runtime/profiles/release.yaml,sha256=xF0Qb0OTm904U6L3wK674JMTcDPegYvpKgwUB9pfq_w,482
|
|
12
|
-
invarlock/adapters/__init__.py,sha256=
|
|
12
|
+
invarlock/adapters/__init__.py,sha256=Bwj8aKjhFxCzvcdxTIl-nG7IXyIE4L3Nd_fsIghbZxA,3418
|
|
13
13
|
invarlock/adapters/_capabilities.py,sha256=FmzUR5BHsxWe92Z9W1As-G5_5wG1PvqF2sUpjZ2_CdY,1483
|
|
14
|
-
invarlock/adapters/auto.py,sha256=
|
|
14
|
+
invarlock/adapters/auto.py,sha256=XWENU5hi66AtMPlQDRuINk1SGW1Kym8Tot-2jXGFGcQ,7643
|
|
15
15
|
invarlock/adapters/base.py,sha256=szSh1bECeDSDGQSr5oIWhs5RlI587gE4gzdt5cnOJ1s,16100
|
|
16
16
|
invarlock/adapters/base_types.py,sha256=3IuHt63_RjGZqoTOdkMpfGPiZTGqcvXXDq1KU-8QemQ,1612
|
|
17
|
+
invarlock/adapters/capabilities.py,sha256=oAK_zgCzAFmss8qAU2zgc8kcahtadGtbPTYR7StXiCo,15360
|
|
17
18
|
invarlock/adapters/hf_bert.py,sha256=DkUXCile7ALlHVZvMkNLAl_YrhHpdmQmomzNyIAPBEo,35547
|
|
18
19
|
invarlock/adapters/hf_gpt2.py,sha256=zuNapMDj4kdzlpGJY11tMOr9dh0V1C0qkOTRwi1xCnQ,14814
|
|
19
|
-
invarlock/adapters/hf_llama.py,sha256=
|
|
20
|
-
invarlock/adapters/hf_mixin.py,sha256=
|
|
20
|
+
invarlock/adapters/hf_llama.py,sha256=TbE9wII1GAQG7gbtiZCZ4S92rKoWRM7VH5vCnqEHc-4,19102
|
|
21
|
+
invarlock/adapters/hf_mixin.py,sha256=rhm0MbjzoHtEAl54tmW3T7tf1C_VxTtcPSdQ7mQ0CIU,18279
|
|
21
22
|
invarlock/adapters/hf_onnx.py,sha256=kEqgQEEdGUeaXDF0EgaMHOQQMhc1xIvur0bQvdky-AY,4446
|
|
22
23
|
invarlock/adapters/hf_t5.py,sha256=2SpDKPyERrrkTWXcDJDo0J5NNjFLePuC965e1zy2tus,4738
|
|
23
24
|
invarlock/adapters/py.typed,sha256=LCPmZeE_vANVVJDNvuq9A07i7jg9Nxrq6f10UeuNfZc,37
|
|
@@ -38,12 +39,12 @@ invarlock/cli/provenance.py,sha256=2E_8MxpSVEcxe5UewTb5Hlz6x4jXFwGd1567F4nIbEc,1
|
|
|
38
39
|
invarlock/cli/utils.py,sha256=R6IN21lGfko2lGUgspmkZyjA6Nl_dVRaP8F8nRtN5yw,1393
|
|
39
40
|
invarlock/cli/commands/__init__.py,sha256=afpKpU8hJI41Ol46oXxVwQgPwFBy4OmLaa_v4AAOdts,712
|
|
40
41
|
invarlock/cli/commands/certify.py,sha256=2dYMrZYX1fACOIHbHshPoM7leCizQDRTPZ3Gc8s7FNo,14903
|
|
41
|
-
invarlock/cli/commands/doctor.py,sha256=
|
|
42
|
+
invarlock/cli/commands/doctor.py,sha256=D5QTYQLlOxM3ngtw2RkuV7QPU2vgKWuymuNIvtKRnCM,56593
|
|
42
43
|
invarlock/cli/commands/explain_gates.py,sha256=vAiszKjHBrXQ1K4xyzBIgV_gszVS38Zg254STPK8mnA,6057
|
|
43
44
|
invarlock/cli/commands/export_html.py,sha256=oUGtt6EpKhjlywdtz_0FqYqcmA219H4eSjPuSMDShgY,3095
|
|
44
45
|
invarlock/cli/commands/plugins.py,sha256=9SVs-2YZENsp10uZrWWT_uZUSFztrIDZizO_9WGP4xA,53044
|
|
45
46
|
invarlock/cli/commands/report.py,sha256=I-B11k6cLLNUIaNiKlsWrGgZ2cTQQJsarN3oNf9Dt84,12988
|
|
46
|
-
invarlock/cli/commands/run.py,sha256=
|
|
47
|
+
invarlock/cli/commands/run.py,sha256=BbXdhD5Vt8t0tg5ZAG6XPujZ3Je4J8ZExFrqv783KRs,177448
|
|
47
48
|
invarlock/cli/commands/verify.py,sha256=iSnqXB1369aQUdxSAWeexyLcByeU3uKaE-LRgPTNdpA,44336
|
|
48
49
|
invarlock/core/__init__.py,sha256=4wb83Xv7NE5M1FgvaFUviiNtVSTVATiPH3gqavNmp2w,1490
|
|
49
50
|
invarlock/core/abi.py,sha256=gmU1F7LDd2HTcF4kcznn8vgG4zj9UCHSqhBk9jyu05k,484
|
|
@@ -55,7 +56,7 @@ invarlock/core/contracts.py,sha256=9j55WVwMrEsUqxWlzAdMsHtkzgfkSftdizhcLiJBauw,2
|
|
|
55
56
|
invarlock/core/error_utils.py,sha256=T23-p5ONQ-SeVuMR4Ts0cWyupsSa-0DAgsejRTfxeCg,1782
|
|
56
57
|
invarlock/core/events.py,sha256=8XBAi-9A7ys7QJQwqlz8PVlfxF0TM_TvLqjcPtDwZm4,9428
|
|
57
58
|
invarlock/core/exceptions.py,sha256=b4OszJ0Fd0Ezy8s99AzprS7lAkqdZYGXaSj9fYaln4E,2077
|
|
58
|
-
invarlock/core/registry.py,sha256=
|
|
59
|
+
invarlock/core/registry.py,sha256=vgKmhekteJS2oRu8g3tBHm48z_Eda5oPKq2ajQnDDc4,18884
|
|
59
60
|
invarlock/core/retry.py,sha256=KTVkrTnWs60jwATOZDHinERH56GnOGjsKR0lmohagEo,4503
|
|
60
61
|
invarlock/core/runner.py,sha256=IWp6cL21IzP_7k26AqcCFHg772f3XHO_PK2PB_-DS0s,81448
|
|
61
62
|
invarlock/core/types.py,sha256=nVLMP4yqlxwhE1moQU7FWVeGJqTuud-cvTZiutdBGKk,3585
|
|
@@ -89,7 +90,7 @@ invarlock/guards/py.typed,sha256=LCPmZeE_vANVVJDNvuq9A07i7jg9Nxrq6f10UeuNfZc,37
|
|
|
89
90
|
invarlock/guards/rmt.py,sha256=f_fZEKZyp_xJcmc7wAMNSrzs702JCztfq3zcK4CSJgk,78710
|
|
90
91
|
invarlock/guards/spectral.py,sha256=tOHBoAm23xkPKUYLdr41ZQYHc806pxrhVXwsmoDLiKE,51132
|
|
91
92
|
invarlock/guards/tier_config.py,sha256=_WJIQ4qvIOc8EI1ygBhpBqbZPt072RT6H6Oh9-LqNWY,10706
|
|
92
|
-
invarlock/guards/variance.py,sha256=
|
|
93
|
+
invarlock/guards/variance.py,sha256=LziyAF5QewT-dtCbRJGb7IklVTIp1IfwTqZhE3yJtv4,133080
|
|
93
94
|
invarlock/guards_ref/__init__.py,sha256=jLnyFqdqQaheG1qQMlU4Gx7R118rkkQHPqFVF3_1ih0,366
|
|
94
95
|
invarlock/guards_ref/rmt_ref.py,sha256=md-aSzLCxPL3OXmrA5NtI9wK7cVSyd2xw8WtSodcGQY,1246
|
|
95
96
|
invarlock/guards_ref/spectral_ref.py,sha256=FdwFfrs5hxEEUIfBV3CvAJvTX78gAM00mKLEXyZ0zJo,4386
|
|
@@ -104,12 +105,12 @@ invarlock/observability/py.typed,sha256=LCPmZeE_vANVVJDNvuq9A07i7jg9Nxrq6f10UeuN
|
|
|
104
105
|
invarlock/observability/utils.py,sha256=1AC3ZEzbrDzTOvSIFxAtWlITy1BHEPZ032HKKzqK_10,16492
|
|
105
106
|
invarlock/plugins/__init__.py,sha256=aZqkpuTT0K3y0SX4702k3YpHnM3kng9fz0qO3XE43hY,260
|
|
106
107
|
invarlock/plugins/hello_guard.py,sha256=-peYJaZd52bbdw6y8cBxEd3CbtFWualDs8nX60CeXhI,896
|
|
107
|
-
invarlock/plugins/hf_awq_adapter.py,sha256=
|
|
108
|
-
invarlock/plugins/hf_bnb_adapter.py,sha256=
|
|
109
|
-
invarlock/plugins/hf_gptq_adapter.py,sha256=
|
|
108
|
+
invarlock/plugins/hf_awq_adapter.py,sha256=UGwzcqJyuqrYHWZ4F-vn-9LNfEDyolOgVDBi0jN35xc,3770
|
|
109
|
+
invarlock/plugins/hf_bnb_adapter.py,sha256=g0ysWEi8dQzLtJy8iCszfTsYCOACuZMFYnTLMAEVxs0,6011
|
|
110
|
+
invarlock/plugins/hf_gptq_adapter.py,sha256=ysugAcnjLqF5sqpijSNiim1xUpRmoIgBrG053X3S2hE,3743
|
|
110
111
|
invarlock/plugins/py.typed,sha256=LCPmZeE_vANVVJDNvuq9A07i7jg9Nxrq6f10UeuNfZc,37
|
|
111
112
|
invarlock/reporting/__init__.py,sha256=A0541EqxcdTpslNbZEWIO4q-LCqzCQcadev2IBKEBbM,232
|
|
112
|
-
invarlock/reporting/certificate.py,sha256=
|
|
113
|
+
invarlock/reporting/certificate.py,sha256=pM9ghpA8qSCMLVXBv7qNfHL9YOTsf3gz96ObiZZXhUA,128987
|
|
113
114
|
invarlock/reporting/certificate_schema.py,sha256=RXWc5RAabY7Jj_0xFOv2Op6dtNRpImsWJyI0QORcHNg,8817
|
|
114
115
|
invarlock/reporting/dataset_hashing.py,sha256=b9LM2rtOtD0-1gQq_oJ0yI6oLleempwKinQtLupajwI,8680
|
|
115
116
|
invarlock/reporting/guards_analysis.py,sha256=L8WdIFi17LrqWwggc11b-wzzT0O9Huo0zUxi9CkC-F4,38266
|
|
@@ -124,9 +125,9 @@ invarlock/reporting/utils.py,sha256=1aLYgSUR4XvgmhDvU9YK9ICd7W5sjft1qdsZC9JJSRY,
|
|
|
124
125
|
invarlock/reporting/validate.py,sha256=396Fe4SPII4zaD1adGtH2hsl5asQOCDqDL4W3YixEXU,22453
|
|
125
126
|
invarlock/utils/__init__.py,sha256=DR2pBrgddLH2PW-6ninOE8CM7DNvlvgyYsCkckozbPU,4276
|
|
126
127
|
invarlock/utils/digest.py,sha256=sfnqGFRiRf7l950MjSIrWO1XbUfXlcEfNLeWFbBUr8I,1290
|
|
127
|
-
invarlock-0.
|
|
128
|
-
invarlock-0.
|
|
129
|
-
invarlock-0.
|
|
130
|
-
invarlock-0.
|
|
131
|
-
invarlock-0.
|
|
132
|
-
invarlock-0.
|
|
128
|
+
invarlock-0.3.1.dist-info/licenses/LICENSE,sha256=uFddaXYY02nEFdPpS7bam_bnm0st41BibzD0jHULPXw,10413
|
|
129
|
+
invarlock-0.3.1.dist-info/METADATA,sha256=_i8VwWAUvfiqMRSPc-QmUp6SZlhZ0LdWblXRcmCMoao,21783
|
|
130
|
+
invarlock-0.3.1.dist-info/WHEEL,sha256=_zCd3N1l69ArxyTb8rzEoP9TpbYXkqRFSNOD5OuxnTs,91
|
|
131
|
+
invarlock-0.3.1.dist-info/entry_points.txt,sha256=i0e4ZzmJNMBGG-69lbgP-muEcn1je2TUIWwl9SJERm0,670
|
|
132
|
+
invarlock-0.3.1.dist-info/top_level.txt,sha256=GXfftc_YDHHcQC2vQgYbZ5cTO82YuWY3HusHMT3DuKs,10
|
|
133
|
+
invarlock-0.3.1.dist-info/RECORD,,
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|