invarlock 0.3.0__py3-none-any.whl → 0.3.2__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.
Files changed (33) hide show
  1. invarlock/__init__.py +1 -1
  2. invarlock/_data/runtime/profiles/ci_cpu.yaml +5 -0
  3. invarlock/_data/runtime/tiers.yaml +61 -0
  4. invarlock/adapters/hf_loading.py +97 -0
  5. invarlock/calibration/__init__.py +6 -0
  6. invarlock/calibration/spectral_null.py +301 -0
  7. invarlock/calibration/variance_ve.py +154 -0
  8. invarlock/cli/app.py +15 -0
  9. invarlock/cli/commands/calibrate.py +576 -0
  10. invarlock/cli/commands/doctor.py +16 -4
  11. invarlock/cli/commands/explain_gates.py +53 -9
  12. invarlock/cli/commands/plugins.py +12 -2
  13. invarlock/cli/commands/run.py +323 -81
  14. invarlock/cli/commands/verify.py +40 -0
  15. invarlock/cli/determinism.py +237 -0
  16. invarlock/core/auto_tuning.py +215 -17
  17. invarlock/core/registry.py +9 -4
  18. invarlock/eval/bench.py +467 -141
  19. invarlock/eval/bench_regression.py +12 -0
  20. invarlock/eval/data.py +29 -7
  21. invarlock/guards/spectral.py +216 -9
  22. invarlock/guards/variance.py +6 -3
  23. invarlock/reporting/certificate.py +403 -51
  24. invarlock/reporting/certificate_schema.py +4 -1
  25. invarlock/reporting/guards_analysis.py +108 -10
  26. invarlock/reporting/normalizer.py +21 -1
  27. invarlock/reporting/policy_utils.py +100 -16
  28. {invarlock-0.3.0.dist-info → invarlock-0.3.2.dist-info}/METADATA +12 -10
  29. {invarlock-0.3.0.dist-info → invarlock-0.3.2.dist-info}/RECORD +33 -26
  30. {invarlock-0.3.0.dist-info → invarlock-0.3.2.dist-info}/WHEEL +0 -0
  31. {invarlock-0.3.0.dist-info → invarlock-0.3.2.dist-info}/entry_points.txt +0 -0
  32. {invarlock-0.3.0.dist-info → invarlock-0.3.2.dist-info}/licenses/LICENSE +0 -0
  33. {invarlock-0.3.0.dist-info → invarlock-0.3.2.dist-info}/top_level.txt +0 -0
@@ -4,7 +4,7 @@ from __future__ import annotations
4
4
  import math
5
5
  from typing import Any, no_type_check
6
6
 
7
- from invarlock.core.auto_tuning import TIER_POLICIES
7
+ from invarlock.core.auto_tuning import get_tier_policies
8
8
 
9
9
  from .policy_utils import _promote_legacy_multiple_testing_key, _resolve_policy_tier
10
10
  from .report_types import RunReport
@@ -133,7 +133,8 @@ def _extract_spectral_analysis(
133
133
  report: RunReport, baseline: dict[str, Any]
134
134
  ) -> dict[str, Any]:
135
135
  tier = _resolve_policy_tier(report)
136
- tier_defaults = TIER_POLICIES.get(tier, TIER_POLICIES.get("balanced", {}))
136
+ tier_policies = get_tier_policies()
137
+ tier_defaults = tier_policies.get(tier, tier_policies.get("balanced", {}))
137
138
  spectral_defaults = tier_defaults.get("spectral", {}) if tier_defaults else {}
138
139
  default_sigma_quantile = spectral_defaults.get("sigma_quantile", 0.95)
139
140
  default_deadband = spectral_defaults.get("deadband", 0.1)
@@ -166,9 +167,15 @@ def _extract_spectral_analysis(
166
167
  caps_exceeded = (
167
168
  bool(guard_metrics.get("caps_exceeded", False)) if guard_metrics else False
168
169
  )
169
- max_caps = guard_policy.get("max_caps") if guard_policy else None
170
+ max_caps = guard_metrics.get("max_caps") if guard_metrics else None
171
+ if max_caps is None and guard_policy:
172
+ max_caps = guard_policy.get("max_caps")
170
173
  if max_caps is None:
171
174
  max_caps = default_max_caps
175
+ try:
176
+ max_caps = int(max_caps)
177
+ except Exception:
178
+ max_caps = int(default_max_caps)
172
179
 
173
180
  try:
174
181
  max_spectral_norm = float(
@@ -618,10 +625,15 @@ def _extract_rmt_analysis(
618
625
  report: RunReport, baseline: dict[str, Any]
619
626
  ) -> dict[str, Any]:
620
627
  tier = _resolve_policy_tier(report)
621
- tier_defaults = TIER_POLICIES.get(tier, TIER_POLICIES.get("balanced", {}))
628
+ tier_policies = get_tier_policies()
629
+ tier_defaults = tier_policies.get(tier, tier_policies.get("balanced", {}))
622
630
  default_epsilon_map = (
623
- tier_defaults.get("rmt", {}).get("epsilon", {}) if tier_defaults else {}
631
+ tier_defaults.get("rmt", {}).get("epsilon_by_family")
632
+ if isinstance(tier_defaults, dict)
633
+ else {}
624
634
  )
635
+ if not default_epsilon_map and isinstance(tier_defaults, dict):
636
+ default_epsilon_map = (tier_defaults.get("rmt", {}) or {}).get("epsilon", {})
625
637
  default_epsilon_map = {
626
638
  str(family): float(value)
627
639
  for family, value in (default_epsilon_map or {}).items()
@@ -631,6 +643,16 @@ def _extract_rmt_analysis(
631
643
  outliers_guarded = 0
632
644
  outliers_bare = 0
633
645
  epsilon_default = 0.1
646
+ try:
647
+ eps_def = (
648
+ tier_defaults.get("rmt", {}).get("epsilon_default")
649
+ if isinstance(tier_defaults, dict)
650
+ else None
651
+ )
652
+ if isinstance(eps_def, int | float) and math.isfinite(float(eps_def)):
653
+ epsilon_default = float(eps_def)
654
+ except Exception:
655
+ pass
634
656
  stable = True
635
657
  explicit_stability = False
636
658
  max_ratio = 0.0
@@ -640,19 +662,54 @@ def _extract_rmt_analysis(
640
662
  baseline_outliers_per_family: dict[str, int] = {}
641
663
  outliers_per_family: dict[str, int] = {}
642
664
  epsilon_violations: list[Any] = []
665
+ margin_used = None
666
+ deadband_used = None
667
+ policy_out: dict[str, Any] | None = None
643
668
 
644
669
  for guard in report.get("guards", []) or []:
645
670
  if str(guard.get("name", "")).lower() == "rmt":
646
671
  guard_metrics = guard.get("metrics", {}) or {}
647
672
  guard_policy = guard.get("policy", {}) or {}
673
+ if isinstance(guard_policy, dict) and guard_policy:
674
+ policy_out = dict(guard_policy)
675
+ if "epsilon_by_family" not in policy_out and isinstance(
676
+ policy_out.get("epsilon"), dict
677
+ ):
678
+ policy_out["epsilon_by_family"] = dict(policy_out["epsilon"])
679
+ if isinstance(policy_out.get("margin"), int | float) and math.isfinite(
680
+ float(policy_out.get("margin"))
681
+ ):
682
+ margin_used = float(policy_out.get("margin"))
683
+ if isinstance(
684
+ policy_out.get("deadband"), int | float
685
+ ) and math.isfinite(float(policy_out.get("deadband"))):
686
+ deadband_used = float(policy_out.get("deadband"))
687
+ if isinstance(
688
+ policy_out.get("epsilon_default"), int | float
689
+ ) and math.isfinite(float(policy_out.get("epsilon_default"))):
690
+ epsilon_default = float(policy_out.get("epsilon_default"))
691
+ if isinstance(
692
+ guard_metrics.get("epsilon_default"), int | float
693
+ ) and math.isfinite(float(guard_metrics.get("epsilon_default"))):
694
+ epsilon_default = float(guard_metrics.get("epsilon_default"))
648
695
  outliers_guarded = guard_metrics.get(
649
696
  "rmt_outliers", guard_metrics.get("layers_flagged", outliers_guarded)
650
697
  )
651
698
  max_ratio = guard_metrics.get("max_ratio", 0.0)
652
- epsilon_default = guard_policy.get(
653
- "deadband", guard_metrics.get("deadband_used", epsilon_default)
654
- )
655
699
  epsilon_map = guard_metrics.get("epsilon_by_family", {}) or epsilon_map
700
+ if not epsilon_map and isinstance(guard_policy, dict):
701
+ eps_src = guard_policy.get("epsilon_by_family") or guard_policy.get(
702
+ "epsilon"
703
+ )
704
+ if isinstance(eps_src, dict):
705
+ try:
706
+ epsilon_map = {
707
+ str(k): float(v)
708
+ for k, v in eps_src.items()
709
+ if isinstance(v, int | float) and math.isfinite(float(v))
710
+ }
711
+ except Exception:
712
+ pass
656
713
  baseline_outliers_per_family = (
657
714
  guard_metrics.get("baseline_outliers_per_family", {})
658
715
  or baseline_outliers_per_family
@@ -844,7 +901,7 @@ def _extract_rmt_analysis(
844
901
  }
845
902
  delta_per_family = {str(k): _to_int(v) for k, v in delta_per_family.items()}
846
903
 
847
- return {
904
+ result = {
848
905
  "outliers_bare": outliers_bare,
849
906
  "outliers_guarded": outliers_guarded,
850
907
  "epsilon": epsilon_scalar,
@@ -862,6 +919,13 @@ def _extract_rmt_analysis(
862
919
  "mean_deviation_ratio": mean_deviation_ratio,
863
920
  "families": family_breakdown,
864
921
  }
922
+ if margin_used is not None:
923
+ result["margin"] = float(margin_used)
924
+ if deadband_used is not None:
925
+ result["deadband"] = float(deadband_used)
926
+ if policy_out:
927
+ result["policy"] = policy_out
928
+ return result
865
929
 
866
930
 
867
931
  @no_type_check
@@ -873,10 +937,14 @@ def _extract_variance_analysis(report: RunReport) -> dict[str, Any]:
873
937
  ratio_ci = None
874
938
  calibration = {}
875
939
  guard_metrics: dict[str, Any] = {}
940
+ guard_policy: dict[str, Any] | None = None
876
941
  for guard in report.get("guards", []) or []:
877
942
  if "variance" in str(guard.get("name", "")).lower():
878
943
  metrics = guard.get("metrics", {}) or {}
879
944
  guard_metrics = metrics
945
+ gp = guard.get("policy", {}) or {}
946
+ if isinstance(gp, dict) and gp:
947
+ guard_policy = dict(gp)
880
948
  ve_enabled = metrics.get("ve_enabled", bool(metrics))
881
949
  gain = metrics.get("ab_gain", metrics.get("gain", None))
882
950
  ppl_no_ve = metrics.get("ppl_no_ve", None)
@@ -932,11 +1000,41 @@ def _extract_variance_analysis(report: RunReport) -> dict[str, Any]:
932
1000
  if guard_metrics.get("ab_windows_used") is not None:
933
1001
  ab_section["windows_used"] = guard_metrics["ab_windows_used"]
934
1002
  if guard_metrics.get("ab_provenance"):
935
- ab_section["provenance"] = guard_metrics["ab_provenance"]
1003
+ prov = guard_metrics["ab_provenance"]
1004
+ if isinstance(prov, dict):
1005
+ prov_out = dict(prov)
1006
+
1007
+ # Normalize a top-level `window_ids` list for docs + auditability.
1008
+ if "window_ids" not in prov_out:
1009
+ window_ids: set[int] = set()
1010
+
1011
+ def _collect(node: Any) -> None:
1012
+ if isinstance(node, dict):
1013
+ ids = node.get("window_ids")
1014
+ if isinstance(ids, list):
1015
+ for wid in ids:
1016
+ if isinstance(wid, int | float):
1017
+ window_ids.add(int(wid))
1018
+ for v in node.values():
1019
+ _collect(v)
1020
+ return
1021
+ if isinstance(node, list):
1022
+ for v in node:
1023
+ _collect(v)
1024
+
1025
+ _collect(prov_out)
1026
+ if window_ids:
1027
+ prov_out["window_ids"] = sorted(window_ids)
1028
+
1029
+ ab_section["provenance"] = prov_out
1030
+ else:
1031
+ ab_section["provenance"] = prov
936
1032
  if guard_metrics.get("ab_point_estimates"):
937
1033
  ab_section["point_estimates"] = guard_metrics["ab_point_estimates"]
938
1034
  if ab_section:
939
1035
  result["ab_test"] = ab_section
1036
+ if guard_policy:
1037
+ result["policy"] = guard_policy
940
1038
  return result
941
1039
 
942
1040
 
@@ -40,15 +40,34 @@ def normalize_run_report(report: Mapping[str, Any] | RunReport) -> RunReport:
40
40
  # ---- meta ----
41
41
  meta_in = _as_mapping(src.get("meta"))
42
42
  ts = _str(meta_in.get("ts") or datetime.now().isoformat())
43
+ try:
44
+ seed_value = int(meta_in.get("seed", 42))
45
+ except Exception:
46
+ seed_value = 42
43
47
  meta_dict: dict[str, Any] = {
44
48
  "model_id": _str(meta_in.get("model_id")),
45
49
  "adapter": _str(meta_in.get("adapter")),
46
50
  "commit": _str(meta_in.get("commit")),
47
- "seed": int(meta_in.get("seed", 42) or 42),
51
+ "seed": seed_value,
48
52
  "device": _str(meta_in.get("device", "cpu")),
49
53
  "ts": ts,
50
54
  "auto": meta_in.get("auto") if isinstance(meta_in.get("auto"), dict) else None,
51
55
  }
56
+ # Preserve additional provenance knobs used by certificate/digests.
57
+ for key in (
58
+ "policy_overrides",
59
+ "overrides",
60
+ "plugins",
61
+ "config",
62
+ "seeds",
63
+ "determinism",
64
+ "env_flags",
65
+ "cuda_flags",
66
+ "tokenizer_hash",
67
+ "model_profile",
68
+ ):
69
+ if key in meta_in:
70
+ meta_dict[key] = meta_in.get(key)
52
71
  meta = cast(MetaData, meta_dict)
53
72
 
54
73
  # ---- data ----
@@ -164,6 +183,7 @@ def normalize_run_report(report: Mapping[str, Any] | RunReport) -> RunReport:
164
183
  "spectral",
165
184
  "rmt",
166
185
  "invariants",
186
+ "logloss_delta_ci",
167
187
  "bootstrap",
168
188
  "reduction",
169
189
  "moe",
@@ -6,7 +6,7 @@ import hashlib
6
6
  import json
7
7
  from typing import Any
8
8
 
9
- from invarlock.core.auto_tuning import TIER_POLICIES
9
+ from invarlock.core.auto_tuning import get_tier_policies, resolve_tier_policies
10
10
 
11
11
  from .report_types import RunReport
12
12
 
@@ -38,17 +38,40 @@ def _compute_thresholds_payload(
38
38
  from .certificate import TIER_RATIO_LIMITS # local to avoid cycles
39
39
 
40
40
  tier_lc = (tier or "balanced").lower()
41
- ratio_limit_base = float(TIER_RATIO_LIMITS.get(tier_lc, 1.10))
42
- tier_policy = TIER_POLICIES.get(tier_lc, {}) if isinstance(tier_lc, str) else {}
43
41
  metrics_policy = (
44
- tier_policy.get("metrics", {}) if isinstance(tier_policy, dict) else {}
45
- )
46
- pm_policy = (
47
- metrics_policy.get("pm_ratio", {}) if isinstance(metrics_policy, dict) else {}
48
- )
49
- acc_policy = (
50
- metrics_policy.get("accuracy", {}) if isinstance(metrics_policy, dict) else {}
42
+ resolved_policy.get("metrics", {}) if isinstance(resolved_policy, dict) else {}
51
43
  )
44
+ if not isinstance(metrics_policy, dict):
45
+ metrics_policy = {}
46
+
47
+ pm_policy = metrics_policy.get("pm_ratio", {})
48
+ if not isinstance(pm_policy, dict):
49
+ pm_policy = {}
50
+
51
+ acc_policy = metrics_policy.get("accuracy", {})
52
+ if not isinstance(acc_policy, dict):
53
+ acc_policy = {}
54
+
55
+ ratio_limit_base = pm_policy.get("ratio_limit_base")
56
+ try:
57
+ if ratio_limit_base is not None:
58
+ ratio_limit_base = float(ratio_limit_base)
59
+ except Exception:
60
+ ratio_limit_base = None
61
+ if ratio_limit_base is None:
62
+ tier_defaults = get_tier_policies().get(tier_lc, {})
63
+ fallback_pm = (
64
+ (tier_defaults.get("metrics") or {}).get("pm_ratio")
65
+ if isinstance(tier_defaults, dict)
66
+ else {}
67
+ )
68
+ ratio_limit_base = float(
69
+ (fallback_pm or {}).get(
70
+ "ratio_limit_base", TIER_RATIO_LIMITS.get(tier_lc, 1.10)
71
+ )
72
+ if isinstance(fallback_pm, dict)
73
+ else TIER_RATIO_LIMITS.get(tier_lc, 1.10)
74
+ )
52
75
  variance_policy = (
53
76
  resolved_policy.get("variance", {}) if isinstance(resolved_policy, dict) else {}
54
77
  )
@@ -154,11 +177,21 @@ def _format_epsilon_map(epsilon_map: Any) -> dict[str, float]:
154
177
 
155
178
 
156
179
  def _build_resolved_policies(
157
- tier: str, spectral: dict[str, Any], rmt: dict[str, Any], variance: dict[str, Any]
180
+ tier: str,
181
+ spectral: dict[str, Any],
182
+ rmt: dict[str, Any],
183
+ variance: dict[str, Any],
184
+ *,
185
+ profile: str | None = None,
186
+ explicit_overrides: dict[str, dict[str, Any]] | None = None,
158
187
  ) -> dict[str, Any]:
159
188
  """Merge tier defaults with observed policies to surface the resolved configuration."""
160
189
  tier_key = (tier or "balanced").lower()
161
- base = copy.deepcopy(TIER_POLICIES.get(tier_key, TIER_POLICIES.get("balanced", {})))
190
+ if tier_key == "none":
191
+ tier_key = "balanced"
192
+ base = resolve_tier_policies(
193
+ tier_key, edit_name=None, explicit_overrides=explicit_overrides, profile=profile
194
+ )
162
195
 
163
196
  resolved: dict[str, Any] = {}
164
197
 
@@ -280,6 +313,37 @@ def _build_resolved_policies(
280
313
  variance_resolved: dict[str, Any] = {}
281
314
  if isinstance(base_variance, dict):
282
315
  variance_resolved.update(base_variance)
316
+
317
+ observed_variance_policy = (
318
+ variance.get("policy") if isinstance(variance, dict) else None
319
+ )
320
+ if isinstance(observed_variance_policy, dict) and observed_variance_policy:
321
+ for key in (
322
+ "deadband",
323
+ "min_abs_adjust",
324
+ "max_scale_step",
325
+ "min_effect_lognll",
326
+ "predictive_one_sided",
327
+ "topk_backstop",
328
+ "max_adjusted_modules",
329
+ "tap",
330
+ "predictive_gate",
331
+ "scope",
332
+ "clamp",
333
+ "min_gain",
334
+ "min_rel_gain",
335
+ "max_calib",
336
+ "seed",
337
+ "mode",
338
+ "alpha",
339
+ "tie_breaker_deadband",
340
+ "calibration",
341
+ ):
342
+ if (
343
+ key in observed_variance_policy
344
+ and observed_variance_policy.get(key) is not None
345
+ ):
346
+ variance_resolved[key] = observed_variance_policy.get(key)
283
347
  predictive_gate = variance.get("predictive_gate", {})
284
348
  predictive_one_sided = variance_resolved.get("predictive_one_sided")
285
349
  if isinstance(predictive_gate, dict) and "sided" in predictive_gate:
@@ -290,6 +354,10 @@ def _build_resolved_policies(
290
354
  variance_resolved["min_effect_lognll"] = _safe_float(
291
355
  variance_resolved.get("min_effect_lognll", 0.0), 0.0
292
356
  )
357
+ if "topk_backstop" in variance_resolved:
358
+ variance_resolved["topk_backstop"] = _safe_int(
359
+ variance_resolved.get("topk_backstop", 0), 0
360
+ )
293
361
  variance_resolved["max_adjusted_modules"] = _safe_int(
294
362
  variance_resolved.get("max_adjusted_modules", 0), 0
295
363
  )
@@ -307,10 +375,24 @@ def _build_resolved_policies(
307
375
  )
308
376
  resolved["variance"] = variance_resolved
309
377
 
310
- # Confidence thresholds (optional policy knobs)
378
+ # Metric gates (PM ratio, accuracy, confidence, etc.)
311
379
  try:
312
380
  metrics = base.get("metrics", {}) if isinstance(base, dict) else {}
313
- conf = metrics.get("confidence") if isinstance(metrics, dict) else None
381
+ if isinstance(metrics, dict) and metrics:
382
+ resolved["metrics"] = copy.deepcopy(metrics)
383
+ except Exception:
384
+ pass
385
+
386
+ # Confidence thresholds (optional policy knobs)
387
+ try:
388
+ conf = None
389
+ metrics = (
390
+ resolved.get("metrics")
391
+ if isinstance(resolved.get("metrics"), dict)
392
+ else None
393
+ )
394
+ if isinstance(metrics, dict):
395
+ conf = metrics.get("confidence")
314
396
  if isinstance(conf, dict) and conf:
315
397
  resolved["confidence"] = {}
316
398
  if "ppl_ratio_width_max" in conf:
@@ -428,7 +510,7 @@ def _extract_effective_policies(report: RunReport) -> dict[str, Any]:
428
510
  guard_policy[key] = original_policy[key]
429
511
  policies[guard_name] = dict(guard_policy)
430
512
 
431
- tier_defaults = TIER_POLICIES.get(_resolve_policy_tier(report), {})
513
+ tier_defaults = get_tier_policies().get(_resolve_policy_tier(report), {})
432
514
 
433
515
  def _merge_defaults(target: dict[str, Any], defaults: dict[str, Any]) -> None:
434
516
  for key, value in defaults.items():
@@ -497,7 +579,9 @@ def _extract_policy_overrides(report: RunReport) -> list[str]:
497
579
 
498
580
 
499
581
  def _compute_policy_digest(policy: dict[str, Any]) -> str:
500
- canonical = json.dumps(policy, sort_keys=True, default=str)
582
+ canonical = json.dumps(
583
+ policy, sort_keys=True, default=str, separators=(",", ":"), ensure_ascii=True
584
+ )
501
585
  return hashlib.sha256(canonical.encode()).hexdigest()[:16]
502
586
 
503
587
 
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: invarlock
3
- Version: 0.3.0
3
+ Version: 0.3.2
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.3.0 (pre‑1.0). Until 1.0, **minor** releases may be
115
+ > **Status:** 0.3.2 (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
  [![CI](https://img.shields.io/github/actions/workflow/status/invarlock/invarlock/ci.yml?branch=main&logo=github&label=CI)](https://github.com/invarlock/invarlock/actions/workflows/ci.yml)
@@ -289,14 +289,16 @@ pip install "invarlock[hf]"
289
289
 
290
290
  ## 💻 Support Matrix
291
291
 
292
- | Platform | Status | Notes |
293
- | ---------------------- | --------------- | ------------------------------------------ |
294
- | Python 3.12+ | Required | |
295
- | Linux | ✅ Full | Primary dev target |
296
- | macOS (Intel/M-series) | ✅ Full | MPS supported (default on Apple Silicon) |
297
- | Windows | Not supported | Use WSL2 or a Linux container if required |
298
- | CUDA | Recommended | For larger models |
299
- | CPU | ✅ Fallback | Slower but functional |
292
+ <!-- markdownlint-disable MD060 -->
293
+ | Platform | Status | Notes |
294
+ | ---------------------- | --------------- | ----------------------------------------- |
295
+ | Python 3.12+ | ✅ Required | |
296
+ | Linux | ✅ Full | Primary dev target |
297
+ | macOS (Intel/M-series) | Full | MPS supported (default on Apple Silicon) |
298
+ | Windows | Not supported | Use WSL2 or a Linux container if required |
299
+ | CUDA | ✅ Recommended | For larger models |
300
+ | CPU | ✅ Fallback | Slower but functional |
301
+ <!-- markdownlint-enable MD060 -->
300
302
 
301
303
  **Device selection:** CUDA → MPS → CPU (auto). Override with torch env if
302
304
  needed (e.g., `CUDA_VISIBLE_DEVICES`).
@@ -1,4 +1,4 @@
1
- invarlock/__init__.py,sha256=Oan5J3YATdRnh-9M-k6dQh6JAExLsg5SrU2gfoau9dE,1268
1
+ invarlock/__init__.py,sha256=AqBlkMDd1RwHMQQgIPAwCa7-Oyh8YCBQ8sM7yY0pEcM,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
@@ -6,8 +6,8 @@ invarlock/model_utils.py,sha256=mEl9KOF2yOJgx-6r38PbD9SNFeAScamy9K-csxtl40Q,6395
6
6
  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
- invarlock/_data/runtime/tiers.yaml,sha256=A0NMcz5o-TXJp1gZq2Sd7uYAQSsMQcG0ZV1FuLxl5jA,1700
10
- invarlock/_data/runtime/profiles/ci_cpu.yaml,sha256=N_ur2CkyL_g9-jwTwATniMhdrv0SL64lnHCQvjq-WWQ,324
9
+ invarlock/_data/runtime/tiers.yaml,sha256=xVHPi-Nopxc-Ye-5lGIer9k-lxiiZZ2cmSnzZE-EHIA,2989
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
12
  invarlock/adapters/__init__.py,sha256=Bwj8aKjhFxCzvcdxTIl-nG7IXyIE4L3Nd_fsIghbZxA,3418
13
13
  invarlock/adapters/_capabilities.py,sha256=FmzUR5BHsxWe92Z9W1As-G5_5wG1PvqF2sUpjZ2_CdY,1483
@@ -18,19 +18,24 @@ invarlock/adapters/capabilities.py,sha256=oAK_zgCzAFmss8qAU2zgc8kcahtadGtbPTYR7S
18
18
  invarlock/adapters/hf_bert.py,sha256=DkUXCile7ALlHVZvMkNLAl_YrhHpdmQmomzNyIAPBEo,35547
19
19
  invarlock/adapters/hf_gpt2.py,sha256=zuNapMDj4kdzlpGJY11tMOr9dh0V1C0qkOTRwi1xCnQ,14814
20
20
  invarlock/adapters/hf_llama.py,sha256=TbE9wII1GAQG7gbtiZCZ4S92rKoWRM7VH5vCnqEHc-4,19102
21
+ invarlock/adapters/hf_loading.py,sha256=6hdSFRz_JMtBzQfHcwvyDlIVP2y-KwLwhDorg73DZ6c,2742
21
22
  invarlock/adapters/hf_mixin.py,sha256=rhm0MbjzoHtEAl54tmW3T7tf1C_VxTtcPSdQ7mQ0CIU,18279
22
23
  invarlock/adapters/hf_onnx.py,sha256=kEqgQEEdGUeaXDF0EgaMHOQQMhc1xIvur0bQvdky-AY,4446
23
24
  invarlock/adapters/hf_t5.py,sha256=2SpDKPyERrrkTWXcDJDo0J5NNjFLePuC965e1zy2tus,4738
24
25
  invarlock/adapters/py.typed,sha256=LCPmZeE_vANVVJDNvuq9A07i7jg9Nxrq6f10UeuNfZc,37
25
26
  invarlock/assurance/__init__.py,sha256=SFDT2klaUaKZejulL5cfBTW29ehJxyW5srE-LuqP7z0,1388
27
+ invarlock/calibration/__init__.py,sha256=M5lbkNQtLBuQjQJGeRzHovkpYI87fEWIm7a0b23jSp4,110
28
+ invarlock/calibration/spectral_null.py,sha256=968168sQDgtZ2tATx7JkEtwtWw0Y7RFSk7OyQxwXBsE,9419
29
+ invarlock/calibration/variance_ve.py,sha256=sdO-a9FUSDSvOI9T8qXAanw4GWOV7RZ_vv53N5ilEg4,4549
26
30
  invarlock/cli/__init__.py,sha256=mRjYDVmNykhxJxfQBxJMzhFfmNhmvP6OWI-vJL-ehUA,196
27
31
  invarlock/cli/__main__.py,sha256=spgMrx0eJQSEf4rcd0_mB2B8rHOc3iWlTFO3P_qEvoM,175
28
32
  invarlock/cli/_evidence.py,sha256=38QMfFlPUT_qIRAZmpDROkwSIpHGiNKRWDyXfZ9xI_U,769
29
33
  invarlock/cli/_json.py,sha256=AKrRNFza76tH3GBVHO8gSA3klUVQ9XpDo_aYh8l-7j8,2367
30
34
  invarlock/cli/adapter_auto.py,sha256=Qwh4hJXm_vGexCXPt3iP70rQPC7ozwlEzSTYay0svQ8,5284
31
- invarlock/cli/app.py,sha256=8Frg2V3tmYlek4G56s8apRzK2wifedBePk61l2jc_VQ,8506
35
+ invarlock/cli/app.py,sha256=iWO4tTwglZTH2CGEW_SXIQ95MyFTw6NYxlBujhpZQRg,9179
32
36
  invarlock/cli/config.py,sha256=i_qShwyc9GiWya8VcWffcMOEI-rVJzep43aCayx5SL4,12930
33
37
  invarlock/cli/constants.py,sha256=RuXxG82pukfBnEuJB2CVF5dyu4lEi-l8eAxkR314yuk,2107
38
+ invarlock/cli/determinism.py,sha256=oW6nc4WaE5tmECZE0M53TUfrk6_djCzFdhBV0JEONho,7702
34
39
  invarlock/cli/device.py,sha256=5X0j2yiZbSKX9-SlDaAbSeHCoWhfEQ74YWPTb8WRk8k,3165
35
40
  invarlock/cli/doctor_helpers.py,sha256=e3A6rWYAcs1Q1WVFWt3OOGgqd5iMsKASD34TzI98wqA,2542
36
41
  invarlock/cli/errors.py,sha256=IzFikxe5gthlZ27wrRYUiM_SJsd2Sa5onUUhjm8e2kA,189
@@ -38,25 +43,26 @@ invarlock/cli/overhead_utils.py,sha256=Ygvl192UDTroAbVAd7v35jgigmmfVxNkCIGYlWfh6
38
43
  invarlock/cli/provenance.py,sha256=2E_8MxpSVEcxe5UewTb5Hlz6x4jXFwGd1567F4nIbEc,1939
39
44
  invarlock/cli/utils.py,sha256=R6IN21lGfko2lGUgspmkZyjA6Nl_dVRaP8F8nRtN5yw,1393
40
45
  invarlock/cli/commands/__init__.py,sha256=afpKpU8hJI41Ol46oXxVwQgPwFBy4OmLaa_v4AAOdts,712
46
+ invarlock/cli/commands/calibrate.py,sha256=b_75wIuHuhQ-frVcKkSuA8W5AMN3acc63k6q9tZvNEE,20780
41
47
  invarlock/cli/commands/certify.py,sha256=2dYMrZYX1fACOIHbHshPoM7leCizQDRTPZ3Gc8s7FNo,14903
42
- invarlock/cli/commands/doctor.py,sha256=EVMxjyA2gq-LctknrhgdmGwD_xyMNDxdwlEmsDBq5sE,56349
43
- invarlock/cli/commands/explain_gates.py,sha256=vAiszKjHBrXQ1K4xyzBIgV_gszVS38Zg254STPK8mnA,6057
48
+ invarlock/cli/commands/doctor.py,sha256=cz4uH8v77M74YhwlWEMgnmEpmqbsV5CN5-zkuDIBLS8,56897
49
+ invarlock/cli/commands/explain_gates.py,sha256=CajKcu6rNi57yBsM_EUhi8rlmTl-ymT1kUaznXzALSE,7624
44
50
  invarlock/cli/commands/export_html.py,sha256=oUGtt6EpKhjlywdtz_0FqYqcmA219H4eSjPuSMDShgY,3095
45
- invarlock/cli/commands/plugins.py,sha256=9SVs-2YZENsp10uZrWWT_uZUSFztrIDZizO_9WGP4xA,53044
51
+ invarlock/cli/commands/plugins.py,sha256=u5E6ThcWE5RUs2YKH-m6x7n-5K1jt2IudkF9kl9T5xg,53486
46
52
  invarlock/cli/commands/report.py,sha256=I-B11k6cLLNUIaNiKlsWrGgZ2cTQQJsarN3oNf9Dt84,12988
47
- invarlock/cli/commands/run.py,sha256=5T1zaRVRLaLRWTW2WDnj9gCaCXByFM8k7tb1xAayLUA,172745
48
- invarlock/cli/commands/verify.py,sha256=iSnqXB1369aQUdxSAWeexyLcByeU3uKaE-LRgPTNdpA,44336
53
+ invarlock/cli/commands/run.py,sha256=9C9AzA5NZqN0KOZRekWwGMo6m45t7JAwJfpzDusXvjg,181622
54
+ invarlock/cli/commands/verify.py,sha256=k7_H8vfjYUz7pzoEvRZe2xTSnTy4-HnbiAAsgBZXSOU,45883
49
55
  invarlock/core/__init__.py,sha256=4wb83Xv7NE5M1FgvaFUviiNtVSTVATiPH3gqavNmp2w,1490
50
56
  invarlock/core/abi.py,sha256=gmU1F7LDd2HTcF4kcznn8vgG4zj9UCHSqhBk9jyu05k,484
51
57
  invarlock/core/api.py,sha256=dg76wZaULo-T-_nZtq6rmDtWwSNLCSYXiQKVYySV6Fg,7545
52
- invarlock/core/auto_tuning.py,sha256=PPVyr-tZcYsZ5P9Xz8_3Qq05s6jWROuq1YrqXxKnaBY,10486
58
+ invarlock/core/auto_tuning.py,sha256=ulk_bLAokQJFHgttj5x9QmkwSx7IPVhX2J1GBPWS6PA,17383
53
59
  invarlock/core/bootstrap.py,sha256=Z7gtiVsOHTnrhy62W3870ZWbxPofmrFYn7gBUBAaJHk,6695
54
60
  invarlock/core/checkpoint.py,sha256=a78L-mZd3j2kC1az2eTScRxTHjrULuYs-UPfWUcDgqM,6933
55
61
  invarlock/core/contracts.py,sha256=9j55WVwMrEsUqxWlzAdMsHtkzgfkSftdizhcLiJBauw,2165
56
62
  invarlock/core/error_utils.py,sha256=T23-p5ONQ-SeVuMR4Ts0cWyupsSa-0DAgsejRTfxeCg,1782
57
63
  invarlock/core/events.py,sha256=8XBAi-9A7ys7QJQwqlz8PVlfxF0TM_TvLqjcPtDwZm4,9428
58
64
  invarlock/core/exceptions.py,sha256=b4OszJ0Fd0Ezy8s99AzprS7lAkqdZYGXaSj9fYaln4E,2077
59
- invarlock/core/registry.py,sha256=vgKmhekteJS2oRu8g3tBHm48z_Eda5oPKq2ajQnDDc4,18884
65
+ invarlock/core/registry.py,sha256=a5hYHXl74MLnaPFbmpwJ-1OsJgY3SSe2Jisbzfr2rLM,19137
60
66
  invarlock/core/retry.py,sha256=KTVkrTnWs60jwATOZDHinERH56GnOGjsKR0lmohagEo,4503
61
67
  invarlock/core/runner.py,sha256=IWp6cL21IzP_7k26AqcCFHg772f3XHO_PK2PB_-DS0s,81448
62
68
  invarlock/core/types.py,sha256=nVLMP4yqlxwhE1moQU7FWVeGJqTuud-cvTZiutdBGKk,3585
@@ -68,9 +74,10 @@ invarlock/edits/py.typed,sha256=LCPmZeE_vANVVJDNvuq9A07i7jg9Nxrq6f10UeuNfZc,37
68
74
  invarlock/edits/quant_rtn.py,sha256=8rseGbF_-EFuL8MWCuzgGXYfzp9LsONhV72-OK56mlk,30684
69
75
  invarlock/edits/registry.py,sha256=MmUfJhhvc3WxB03wQkPxFMS6nkT7YcXFPQqhbksfOUc,4523
70
76
  invarlock/eval/__init__.py,sha256=GsKoszCysh3TT_UHiuJcqeoiXT7AUNZoqMuBmefnWtY,755
71
- invarlock/eval/bench.py,sha256=z9aY_GuBCCH0L8VmwuivprVLZ3yZzmePpFHNfS5lu6M,42640
77
+ invarlock/eval/bench.py,sha256=85tGywzAeJSVlZ1Qsebk8FwIGUYhq9g6TxOW5XnL5X8,56335
78
+ invarlock/eval/bench_regression.py,sha256=-92lJbx2BP4utGIsIyahNQtouz56ghuuTEvj1yCaEoY,456
72
79
  invarlock/eval/bootstrap.py,sha256=hE1-CCc6b7ogk8Tuob7IhmOVhOI7DVlG6So0PL-Rlvo,1671
73
- invarlock/eval/data.py,sha256=v2xUf9HhW4mat4c3-UjIi0bq-FKJPUCBfgHMGoekst4,74162
80
+ invarlock/eval/data.py,sha256=nKx1XIkwmVKQyDP7aa40SFWk5GLVNxHI5gpmUWID3bE,75126
74
81
  invarlock/eval/metrics.py,sha256=3ay3mqsFTa0V5a66gKdk7BOOJjKWXlub5lrTpcex9DQ,73946
75
82
  invarlock/eval/primary_metric.py,sha256=VDeDFuLnsmYpL8JoauabmE99CGDw-kSAe5NaxmcEQqw,28031
76
83
  invarlock/eval/py.typed,sha256=LCPmZeE_vANVVJDNvuq9A07i7jg9Nxrq6f10UeuNfZc,37
@@ -88,9 +95,9 @@ invarlock/guards/invariants.py,sha256=2mD5WSJMCksjO-DodAac5pvZN0nl29NKHhqwqH_gyC
88
95
  invarlock/guards/policies.py,sha256=sbZkddUSnkx3ZsFm4vV4DfSU81QUh9R8S4vuAtPF11I,25466
89
96
  invarlock/guards/py.typed,sha256=LCPmZeE_vANVVJDNvuq9A07i7jg9Nxrq6f10UeuNfZc,37
90
97
  invarlock/guards/rmt.py,sha256=f_fZEKZyp_xJcmc7wAMNSrzs702JCztfq3zcK4CSJgk,78710
91
- invarlock/guards/spectral.py,sha256=tOHBoAm23xkPKUYLdr41ZQYHc806pxrhVXwsmoDLiKE,51132
98
+ invarlock/guards/spectral.py,sha256=xuUVwF2a3v4WzbwtsjrM7NU0mKK0gsv0WvnkjkN-05U,58613
92
99
  invarlock/guards/tier_config.py,sha256=_WJIQ4qvIOc8EI1ygBhpBqbZPt072RT6H6Oh9-LqNWY,10706
93
- invarlock/guards/variance.py,sha256=LziyAF5QewT-dtCbRJGb7IklVTIp1IfwTqZhE3yJtv4,133080
100
+ invarlock/guards/variance.py,sha256=3OYprEvWKRfq5UYSH82AJrp163gEtGyCoUIgL3wotls,133255
94
101
  invarlock/guards_ref/__init__.py,sha256=jLnyFqdqQaheG1qQMlU4Gx7R118rkkQHPqFVF3_1ih0,366
95
102
  invarlock/guards_ref/rmt_ref.py,sha256=md-aSzLCxPL3OXmrA5NtI9wK7cVSyd2xw8WtSodcGQY,1246
96
103
  invarlock/guards_ref/spectral_ref.py,sha256=FdwFfrs5hxEEUIfBV3CvAJvTX78gAM00mKLEXyZ0zJo,4386
@@ -110,13 +117,13 @@ invarlock/plugins/hf_bnb_adapter.py,sha256=g0ysWEi8dQzLtJy8iCszfTsYCOACuZMFYnTLM
110
117
  invarlock/plugins/hf_gptq_adapter.py,sha256=ysugAcnjLqF5sqpijSNiim1xUpRmoIgBrG053X3S2hE,3743
111
118
  invarlock/plugins/py.typed,sha256=LCPmZeE_vANVVJDNvuq9A07i7jg9Nxrq6f10UeuNfZc,37
112
119
  invarlock/reporting/__init__.py,sha256=A0541EqxcdTpslNbZEWIO4q-LCqzCQcadev2IBKEBbM,232
113
- invarlock/reporting/certificate.py,sha256=3xlAryynUh31iGenN7us3L3VGYB8vW722uVYGQiXdrM,124085
114
- invarlock/reporting/certificate_schema.py,sha256=RXWc5RAabY7Jj_0xFOv2Op6dtNRpImsWJyI0QORcHNg,8817
120
+ invarlock/reporting/certificate.py,sha256=vgmLWLq1epkChmawOgGRwEBqtuK80rmrRkQo8HXYdzY,136891
121
+ invarlock/reporting/certificate_schema.py,sha256=ft58LrF4lfdXT08P0jjtiNvRPVs6TdgPU_cp9K4TMPw,8939
115
122
  invarlock/reporting/dataset_hashing.py,sha256=b9LM2rtOtD0-1gQq_oJ0yI6oLleempwKinQtLupajwI,8680
116
- invarlock/reporting/guards_analysis.py,sha256=L8WdIFi17LrqWwggc11b-wzzT0O9Huo0zUxi9CkC-F4,38266
123
+ invarlock/reporting/guards_analysis.py,sha256=7TTlF-OwgT7wGQ8zZ8oHHPuMdUMvKw7836WvO0J1GUc,42465
117
124
  invarlock/reporting/html.py,sha256=d3LQ_brDGYW7GLR_Kj4rimIfGZsAgVE9XQtIjqsJ1rw,1029
118
- invarlock/reporting/normalizer.py,sha256=STJujvNLb6TpChPjX4cFhOhAEStxywevqCoZndqS9ng,7513
119
- invarlock/reporting/policy_utils.py,sha256=c5eOOqpJ3U-LLH5mbxQj1fKGpNF6n_4yrDDLgbGa-V0,20649
125
+ invarlock/reporting/normalizer.py,sha256=7PN3OmczVmsSSFoWXz8wb6F4r46q1luI_Od9Ld3h2wg,8011
126
+ invarlock/reporting/policy_utils.py,sha256=pRuOrz7NB1CYMxB4Cy0L240vwSDdUJbtqFAv04payhA,23136
120
127
  invarlock/reporting/primary_metric_utils.py,sha256=K6DVoGHibrFdcpI0LwroHlffTcgBRVejekUK0vriWP4,11406
121
128
  invarlock/reporting/render.py,sha256=zukCOm6Qm81nDkpOur50TKrlgRpnt7Q53TEU7eYFivg,59976
122
129
  invarlock/reporting/report.py,sha256=kyoqTiqtYUQfgpwm6RApV-LbHHIyryfiIPIRhy-z47Q,32293
@@ -125,9 +132,9 @@ invarlock/reporting/utils.py,sha256=1aLYgSUR4XvgmhDvU9YK9ICd7W5sjft1qdsZC9JJSRY,
125
132
  invarlock/reporting/validate.py,sha256=396Fe4SPII4zaD1adGtH2hsl5asQOCDqDL4W3YixEXU,22453
126
133
  invarlock/utils/__init__.py,sha256=DR2pBrgddLH2PW-6ninOE8CM7DNvlvgyYsCkckozbPU,4276
127
134
  invarlock/utils/digest.py,sha256=sfnqGFRiRf7l950MjSIrWO1XbUfXlcEfNLeWFbBUr8I,1290
128
- invarlock-0.3.0.dist-info/licenses/LICENSE,sha256=uFddaXYY02nEFdPpS7bam_bnm0st41BibzD0jHULPXw,10413
129
- invarlock-0.3.0.dist-info/METADATA,sha256=MtsIbRufSXZ3xxDzNbxjDrYH0oDhIb1xDQdlElAsdjw,21783
130
- invarlock-0.3.0.dist-info/WHEEL,sha256=_zCd3N1l69ArxyTb8rzEoP9TpbYXkqRFSNOD5OuxnTs,91
131
- invarlock-0.3.0.dist-info/entry_points.txt,sha256=i0e4ZzmJNMBGG-69lbgP-muEcn1je2TUIWwl9SJERm0,670
132
- invarlock-0.3.0.dist-info/top_level.txt,sha256=GXfftc_YDHHcQC2vQgYbZ5cTO82YuWY3HusHMT3DuKs,10
133
- invarlock-0.3.0.dist-info/RECORD,,
135
+ invarlock-0.3.2.dist-info/licenses/LICENSE,sha256=uFddaXYY02nEFdPpS7bam_bnm0st41BibzD0jHULPXw,10413
136
+ invarlock-0.3.2.dist-info/METADATA,sha256=Ic7I45Qn8rk2ac9IzwswJZMcr5daDbleaOiBEN8xbmA,21845
137
+ invarlock-0.3.2.dist-info/WHEEL,sha256=_zCd3N1l69ArxyTb8rzEoP9TpbYXkqRFSNOD5OuxnTs,91
138
+ invarlock-0.3.2.dist-info/entry_points.txt,sha256=i0e4ZzmJNMBGG-69lbgP-muEcn1je2TUIWwl9SJERm0,670
139
+ invarlock-0.3.2.dist-info/top_level.txt,sha256=GXfftc_YDHHcQC2vQgYbZ5cTO82YuWY3HusHMT3DuKs,10
140
+ invarlock-0.3.2.dist-info/RECORD,,