invarlock 0.3.1__tar.gz → 0.3.3__tar.gz

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 (146) hide show
  1. {invarlock-0.3.1/src/invarlock.egg-info → invarlock-0.3.3}/PKG-INFO +12 -10
  2. {invarlock-0.3.1 → invarlock-0.3.3}/README.md +11 -9
  3. {invarlock-0.3.1 → invarlock-0.3.3}/pyproject.toml +2 -1
  4. {invarlock-0.3.1 → invarlock-0.3.3}/src/invarlock/__init__.py +1 -1
  5. {invarlock-0.3.1 → invarlock-0.3.3}/src/invarlock/_data/runtime/tiers.yaml +61 -0
  6. invarlock-0.3.3/src/invarlock/adapters/hf_loading.py +97 -0
  7. invarlock-0.3.3/src/invarlock/calibration/__init__.py +6 -0
  8. invarlock-0.3.3/src/invarlock/calibration/spectral_null.py +301 -0
  9. invarlock-0.3.3/src/invarlock/calibration/variance_ve.py +154 -0
  10. {invarlock-0.3.1 → invarlock-0.3.3}/src/invarlock/cli/app.py +15 -0
  11. invarlock-0.3.3/src/invarlock/cli/commands/calibrate.py +576 -0
  12. {invarlock-0.3.1 → invarlock-0.3.3}/src/invarlock/cli/commands/doctor.py +9 -3
  13. {invarlock-0.3.1 → invarlock-0.3.3}/src/invarlock/cli/commands/explain_gates.py +53 -9
  14. {invarlock-0.3.1 → invarlock-0.3.3}/src/invarlock/cli/commands/plugins.py +12 -2
  15. {invarlock-0.3.1 → invarlock-0.3.3}/src/invarlock/cli/commands/run.py +181 -79
  16. {invarlock-0.3.1 → invarlock-0.3.3}/src/invarlock/cli/commands/verify.py +40 -0
  17. {invarlock-0.3.1 → invarlock-0.3.3}/src/invarlock/cli/config.py +11 -1
  18. invarlock-0.3.3/src/invarlock/cli/determinism.py +252 -0
  19. {invarlock-0.3.1 → invarlock-0.3.3}/src/invarlock/core/auto_tuning.py +215 -17
  20. {invarlock-0.3.1 → invarlock-0.3.3}/src/invarlock/core/bootstrap.py +137 -5
  21. {invarlock-0.3.1 → invarlock-0.3.3}/src/invarlock/core/registry.py +9 -4
  22. {invarlock-0.3.1 → invarlock-0.3.3}/src/invarlock/core/runner.py +305 -35
  23. {invarlock-0.3.1 → invarlock-0.3.3}/src/invarlock/eval/bench.py +467 -141
  24. invarlock-0.3.3/src/invarlock/eval/bench_regression.py +12 -0
  25. {invarlock-0.3.1 → invarlock-0.3.3}/src/invarlock/eval/bootstrap.py +3 -1
  26. {invarlock-0.3.1 → invarlock-0.3.3}/src/invarlock/eval/data.py +29 -7
  27. {invarlock-0.3.1 → invarlock-0.3.3}/src/invarlock/eval/primary_metric.py +20 -5
  28. {invarlock-0.3.1 → invarlock-0.3.3}/src/invarlock/guards/rmt.py +536 -46
  29. {invarlock-0.3.1 → invarlock-0.3.3}/src/invarlock/guards/spectral.py +217 -10
  30. {invarlock-0.3.1 → invarlock-0.3.3}/src/invarlock/guards/variance.py +124 -42
  31. {invarlock-0.3.1 → invarlock-0.3.3}/src/invarlock/reporting/certificate.py +476 -45
  32. {invarlock-0.3.1 → invarlock-0.3.3}/src/invarlock/reporting/certificate_schema.py +4 -1
  33. {invarlock-0.3.1 → invarlock-0.3.3}/src/invarlock/reporting/guards_analysis.py +108 -10
  34. {invarlock-0.3.1 → invarlock-0.3.3}/src/invarlock/reporting/normalizer.py +24 -1
  35. {invarlock-0.3.1 → invarlock-0.3.3}/src/invarlock/reporting/policy_utils.py +97 -15
  36. {invarlock-0.3.1 → invarlock-0.3.3}/src/invarlock/reporting/primary_metric_utils.py +17 -0
  37. {invarlock-0.3.1 → invarlock-0.3.3}/src/invarlock/reporting/validate.py +10 -10
  38. {invarlock-0.3.1 → invarlock-0.3.3/src/invarlock.egg-info}/PKG-INFO +12 -10
  39. {invarlock-0.3.1 → invarlock-0.3.3}/src/invarlock.egg-info/SOURCES.txt +7 -0
  40. {invarlock-0.3.1 → invarlock-0.3.3}/LICENSE +0 -0
  41. {invarlock-0.3.1 → invarlock-0.3.3}/MANIFEST.in +0 -0
  42. {invarlock-0.3.1 → invarlock-0.3.3}/setup.cfg +0 -0
  43. {invarlock-0.3.1 → invarlock-0.3.3}/src/invarlock/__main__.py +0 -0
  44. {invarlock-0.3.1 → invarlock-0.3.3}/src/invarlock/_data/runtime/profiles/ci_cpu.yaml +0 -0
  45. {invarlock-0.3.1 → invarlock-0.3.3}/src/invarlock/_data/runtime/profiles/release.yaml +0 -0
  46. {invarlock-0.3.1 → invarlock-0.3.3}/src/invarlock/adapters/__init__.py +0 -0
  47. {invarlock-0.3.1 → invarlock-0.3.3}/src/invarlock/adapters/_capabilities.py +0 -0
  48. {invarlock-0.3.1 → invarlock-0.3.3}/src/invarlock/adapters/auto.py +0 -0
  49. {invarlock-0.3.1 → invarlock-0.3.3}/src/invarlock/adapters/base.py +0 -0
  50. {invarlock-0.3.1 → invarlock-0.3.3}/src/invarlock/adapters/base_types.py +0 -0
  51. {invarlock-0.3.1 → invarlock-0.3.3}/src/invarlock/adapters/capabilities.py +0 -0
  52. {invarlock-0.3.1 → invarlock-0.3.3}/src/invarlock/adapters/hf_bert.py +0 -0
  53. {invarlock-0.3.1 → invarlock-0.3.3}/src/invarlock/adapters/hf_gpt2.py +0 -0
  54. {invarlock-0.3.1 → invarlock-0.3.3}/src/invarlock/adapters/hf_llama.py +0 -0
  55. {invarlock-0.3.1 → invarlock-0.3.3}/src/invarlock/adapters/hf_mixin.py +0 -0
  56. {invarlock-0.3.1 → invarlock-0.3.3}/src/invarlock/adapters/hf_onnx.py +0 -0
  57. {invarlock-0.3.1 → invarlock-0.3.3}/src/invarlock/adapters/hf_t5.py +0 -0
  58. {invarlock-0.3.1 → invarlock-0.3.3}/src/invarlock/adapters/py.typed +0 -0
  59. {invarlock-0.3.1 → invarlock-0.3.3}/src/invarlock/assurance/__init__.py +0 -0
  60. {invarlock-0.3.1 → invarlock-0.3.3}/src/invarlock/cli/__init__.py +0 -0
  61. {invarlock-0.3.1 → invarlock-0.3.3}/src/invarlock/cli/__main__.py +0 -0
  62. {invarlock-0.3.1 → invarlock-0.3.3}/src/invarlock/cli/_evidence.py +0 -0
  63. {invarlock-0.3.1 → invarlock-0.3.3}/src/invarlock/cli/_json.py +0 -0
  64. {invarlock-0.3.1 → invarlock-0.3.3}/src/invarlock/cli/adapter_auto.py +0 -0
  65. {invarlock-0.3.1 → invarlock-0.3.3}/src/invarlock/cli/commands/__init__.py +0 -0
  66. {invarlock-0.3.1 → invarlock-0.3.3}/src/invarlock/cli/commands/certify.py +0 -0
  67. {invarlock-0.3.1 → invarlock-0.3.3}/src/invarlock/cli/commands/export_html.py +0 -0
  68. {invarlock-0.3.1 → invarlock-0.3.3}/src/invarlock/cli/commands/report.py +0 -0
  69. {invarlock-0.3.1 → invarlock-0.3.3}/src/invarlock/cli/constants.py +0 -0
  70. {invarlock-0.3.1 → invarlock-0.3.3}/src/invarlock/cli/device.py +0 -0
  71. {invarlock-0.3.1 → invarlock-0.3.3}/src/invarlock/cli/doctor_helpers.py +0 -0
  72. {invarlock-0.3.1 → invarlock-0.3.3}/src/invarlock/cli/errors.py +0 -0
  73. {invarlock-0.3.1 → invarlock-0.3.3}/src/invarlock/cli/overhead_utils.py +0 -0
  74. {invarlock-0.3.1 → invarlock-0.3.3}/src/invarlock/cli/provenance.py +0 -0
  75. {invarlock-0.3.1 → invarlock-0.3.3}/src/invarlock/cli/utils.py +0 -0
  76. {invarlock-0.3.1 → invarlock-0.3.3}/src/invarlock/config.py +0 -0
  77. {invarlock-0.3.1 → invarlock-0.3.3}/src/invarlock/core/__init__.py +0 -0
  78. {invarlock-0.3.1 → invarlock-0.3.3}/src/invarlock/core/abi.py +0 -0
  79. {invarlock-0.3.1 → invarlock-0.3.3}/src/invarlock/core/api.py +0 -0
  80. {invarlock-0.3.1 → invarlock-0.3.3}/src/invarlock/core/checkpoint.py +0 -0
  81. {invarlock-0.3.1 → invarlock-0.3.3}/src/invarlock/core/contracts.py +0 -0
  82. {invarlock-0.3.1 → invarlock-0.3.3}/src/invarlock/core/error_utils.py +0 -0
  83. {invarlock-0.3.1 → invarlock-0.3.3}/src/invarlock/core/events.py +0 -0
  84. {invarlock-0.3.1 → invarlock-0.3.3}/src/invarlock/core/exceptions.py +0 -0
  85. {invarlock-0.3.1 → invarlock-0.3.3}/src/invarlock/core/retry.py +0 -0
  86. {invarlock-0.3.1 → invarlock-0.3.3}/src/invarlock/core/types.py +0 -0
  87. {invarlock-0.3.1 → invarlock-0.3.3}/src/invarlock/edits/__init__.py +0 -0
  88. {invarlock-0.3.1 → invarlock-0.3.3}/src/invarlock/edits/_edit_utils.py +0 -0
  89. {invarlock-0.3.1 → invarlock-0.3.3}/src/invarlock/edits/_external_utils.py +0 -0
  90. {invarlock-0.3.1 → invarlock-0.3.3}/src/invarlock/edits/noop.py +0 -0
  91. {invarlock-0.3.1 → invarlock-0.3.3}/src/invarlock/edits/py.typed +0 -0
  92. {invarlock-0.3.1 → invarlock-0.3.3}/src/invarlock/edits/quant_rtn.py +0 -0
  93. {invarlock-0.3.1 → invarlock-0.3.3}/src/invarlock/edits/registry.py +0 -0
  94. {invarlock-0.3.1 → invarlock-0.3.3}/src/invarlock/eval/__init__.py +0 -0
  95. {invarlock-0.3.1 → invarlock-0.3.3}/src/invarlock/eval/metrics.py +0 -0
  96. {invarlock-0.3.1 → invarlock-0.3.3}/src/invarlock/eval/probes/__init__.py +0 -0
  97. {invarlock-0.3.1 → invarlock-0.3.3}/src/invarlock/eval/probes/fft.py +0 -0
  98. {invarlock-0.3.1 → invarlock-0.3.3}/src/invarlock/eval/probes/mi.py +0 -0
  99. {invarlock-0.3.1 → invarlock-0.3.3}/src/invarlock/eval/probes/post_attention.py +0 -0
  100. {invarlock-0.3.1 → invarlock-0.3.3}/src/invarlock/eval/providers/base.py +0 -0
  101. {invarlock-0.3.1 → invarlock-0.3.3}/src/invarlock/eval/providers/seq2seq.py +0 -0
  102. {invarlock-0.3.1 → invarlock-0.3.3}/src/invarlock/eval/providers/text_lm.py +0 -0
  103. {invarlock-0.3.1 → invarlock-0.3.3}/src/invarlock/eval/providers/vision_text.py +0 -0
  104. {invarlock-0.3.1 → invarlock-0.3.3}/src/invarlock/eval/py.typed +0 -0
  105. {invarlock-0.3.1 → invarlock-0.3.3}/src/invarlock/guards/__init__.py +0 -0
  106. {invarlock-0.3.1 → invarlock-0.3.3}/src/invarlock/guards/_contracts.py +0 -0
  107. {invarlock-0.3.1 → invarlock-0.3.3}/src/invarlock/guards/invariants.py +0 -0
  108. {invarlock-0.3.1 → invarlock-0.3.3}/src/invarlock/guards/policies.py +0 -0
  109. {invarlock-0.3.1 → invarlock-0.3.3}/src/invarlock/guards/py.typed +0 -0
  110. {invarlock-0.3.1 → invarlock-0.3.3}/src/invarlock/guards/tier_config.py +0 -0
  111. {invarlock-0.3.1 → invarlock-0.3.3}/src/invarlock/guards_ref/__init__.py +0 -0
  112. {invarlock-0.3.1 → invarlock-0.3.3}/src/invarlock/guards_ref/rmt_ref.py +0 -0
  113. {invarlock-0.3.1 → invarlock-0.3.3}/src/invarlock/guards_ref/spectral_ref.py +0 -0
  114. {invarlock-0.3.1 → invarlock-0.3.3}/src/invarlock/guards_ref/variance_ref.py +0 -0
  115. {invarlock-0.3.1 → invarlock-0.3.3}/src/invarlock/model_profile.py +0 -0
  116. {invarlock-0.3.1 → invarlock-0.3.3}/src/invarlock/model_utils.py +0 -0
  117. {invarlock-0.3.1 → invarlock-0.3.3}/src/invarlock/observability/__init__.py +0 -0
  118. {invarlock-0.3.1 → invarlock-0.3.3}/src/invarlock/observability/alerting.py +0 -0
  119. {invarlock-0.3.1 → invarlock-0.3.3}/src/invarlock/observability/core.py +0 -0
  120. {invarlock-0.3.1 → invarlock-0.3.3}/src/invarlock/observability/exporters.py +0 -0
  121. {invarlock-0.3.1 → invarlock-0.3.3}/src/invarlock/observability/health.py +0 -0
  122. {invarlock-0.3.1 → invarlock-0.3.3}/src/invarlock/observability/metrics.py +0 -0
  123. {invarlock-0.3.1 → invarlock-0.3.3}/src/invarlock/observability/py.typed +0 -0
  124. {invarlock-0.3.1 → invarlock-0.3.3}/src/invarlock/observability/utils.py +0 -0
  125. {invarlock-0.3.1 → invarlock-0.3.3}/src/invarlock/plugins/__init__.py +0 -0
  126. {invarlock-0.3.1 → invarlock-0.3.3}/src/invarlock/plugins/hello_guard.py +0 -0
  127. {invarlock-0.3.1 → invarlock-0.3.3}/src/invarlock/plugins/hf_awq_adapter.py +0 -0
  128. {invarlock-0.3.1 → invarlock-0.3.3}/src/invarlock/plugins/hf_bnb_adapter.py +0 -0
  129. {invarlock-0.3.1 → invarlock-0.3.3}/src/invarlock/plugins/hf_gptq_adapter.py +0 -0
  130. {invarlock-0.3.1 → invarlock-0.3.3}/src/invarlock/plugins/py.typed +0 -0
  131. {invarlock-0.3.1 → invarlock-0.3.3}/src/invarlock/py.typed +0 -0
  132. {invarlock-0.3.1 → invarlock-0.3.3}/src/invarlock/reporting/__init__.py +0 -0
  133. {invarlock-0.3.1 → invarlock-0.3.3}/src/invarlock/reporting/dataset_hashing.py +0 -0
  134. {invarlock-0.3.1 → invarlock-0.3.3}/src/invarlock/reporting/html.py +0 -0
  135. {invarlock-0.3.1 → invarlock-0.3.3}/src/invarlock/reporting/render.py +0 -0
  136. {invarlock-0.3.1 → invarlock-0.3.3}/src/invarlock/reporting/report.py +0 -0
  137. {invarlock-0.3.1 → invarlock-0.3.3}/src/invarlock/reporting/report_types.py +0 -0
  138. {invarlock-0.3.1 → invarlock-0.3.3}/src/invarlock/reporting/utils.py +0 -0
  139. {invarlock-0.3.1 → invarlock-0.3.3}/src/invarlock/security.py +0 -0
  140. {invarlock-0.3.1 → invarlock-0.3.3}/src/invarlock/sparsity_utils.py +0 -0
  141. {invarlock-0.3.1 → invarlock-0.3.3}/src/invarlock/utils/__init__.py +0 -0
  142. {invarlock-0.3.1 → invarlock-0.3.3}/src/invarlock/utils/digest.py +0 -0
  143. {invarlock-0.3.1 → invarlock-0.3.3}/src/invarlock.egg-info/dependency_links.txt +0 -0
  144. {invarlock-0.3.1 → invarlock-0.3.3}/src/invarlock.egg-info/entry_points.txt +0 -0
  145. {invarlock-0.3.1 → invarlock-0.3.3}/src/invarlock.egg-info/requires.txt +0 -0
  146. {invarlock-0.3.1 → invarlock-0.3.3}/src/invarlock.egg-info/top_level.txt +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: invarlock
3
- Version: 0.3.1
3
+ Version: 0.3.3
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.1 (pre‑1.0). Until 1.0, **minor** releases may be
115
+ > **Status:** 0.3.3 (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`).
@@ -6,7 +6,7 @@ they don’t, roll back safely.
6
6
  Technical: edit‑agnostic guard pipeline (invariants → spectral → RMT →
7
7
  variance) producing a machine‑readable Safety Certificate.
8
8
 
9
- > **Status:** 0.3.1 (pre‑1.0). Until 1.0, **minor** releases may be
9
+ > **Status:** 0.3.3 (pre‑1.0). Until 1.0, **minor** releases may be
10
10
  > breaking. See CLI help and the CHANGELOG for updates.
11
11
 
12
12
  [![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)
@@ -183,14 +183,16 @@ pip install "invarlock[hf]"
183
183
 
184
184
  ## 💻 Support Matrix
185
185
 
186
- | Platform | Status | Notes |
187
- | ---------------------- | --------------- | ------------------------------------------ |
188
- | Python 3.12+ | Required | |
189
- | Linux | ✅ Full | Primary dev target |
190
- | macOS (Intel/M-series) | ✅ Full | MPS supported (default on Apple Silicon) |
191
- | Windows | Not supported | Use WSL2 or a Linux container if required |
192
- | CUDA | Recommended | For larger models |
193
- | CPU | ✅ Fallback | Slower but functional |
186
+ <!-- markdownlint-disable MD060 -->
187
+ | Platform | Status | Notes |
188
+ | ---------------------- | --------------- | ----------------------------------------- |
189
+ | Python 3.12+ | ✅ Required | |
190
+ | Linux | ✅ Full | Primary dev target |
191
+ | macOS (Intel/M-series) | Full | MPS supported (default on Apple Silicon) |
192
+ | Windows | Not supported | Use WSL2 or a Linux container if required |
193
+ | CUDA | ✅ Recommended | For larger models |
194
+ | CPU | ✅ Fallback | Slower but functional |
195
+ <!-- markdownlint-enable MD060 -->
194
196
 
195
197
  **Device selection:** CUDA → MPS → CPU (auto). Override with torch env if
196
198
  needed (e.g., `CUDA_VISIBLE_DEVICES`).
@@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
4
4
 
5
5
  [project]
6
6
  name = "invarlock"
7
- version = "0.3.1"
7
+ version = "0.3.3"
8
8
  description = "Edit‑agnostic robustness certificates for weight edits (InvarLock framework)"
9
9
  authors = [{ name = "InvarLock Team", email = "oss@invarlock.dev" }]
10
10
  maintainers = [{ name = "InvarLock Maintainers", email = "support@invarlock.dev" }]
@@ -313,6 +313,7 @@ precision = 2
313
313
  include = [
314
314
  "src/invarlock/eval/*",
315
315
  "src/invarlock/guards/*",
316
+ "src/invarlock/calibration/*",
316
317
  "src/invarlock/cli/*",
317
318
  "src/invarlock/reporting/*",
318
319
  "src/invarlock/core/*",
@@ -12,7 +12,7 @@ For torch-dependent functionality, see subpackages under `invarlock.*`:
12
12
  - `invarlock.eval`: Metrics, guard-overhead checks, and certification
13
13
  """
14
14
 
15
- __version__ = "0.3.1"
15
+ __version__ = "0.3.3"
16
16
 
17
17
  # Core exports - torch-independent
18
18
  from .config import CFG, Defaults, get_default_config
@@ -5,6 +5,17 @@
5
5
  # embedded in certificates and referenced by automation documentation.
6
6
 
7
7
  balanced:
8
+ metrics:
9
+ pm_ratio:
10
+ ratio_limit_base: 1.10
11
+ min_tokens: 50000
12
+ hysteresis_ratio: 0.002
13
+ min_token_fraction: 0.01
14
+ accuracy:
15
+ delta_min_pp: -1.0
16
+ min_examples: 200
17
+ hysteresis_delta_pp: 0.1
18
+ min_examples_fraction: 0.01
8
19
  variance_guard:
9
20
  deadband: 0.02
10
21
  min_abs_adjust: 0.012
@@ -41,6 +52,17 @@ balanced:
41
52
  other: 0.12
42
53
 
43
54
  conservative:
55
+ metrics:
56
+ pm_ratio:
57
+ ratio_limit_base: 1.05
58
+ min_tokens: 20000
59
+ hysteresis_ratio: 0.002
60
+ min_token_fraction: 0.01
61
+ accuracy:
62
+ delta_min_pp: -0.5
63
+ min_examples: 200
64
+ hysteresis_delta_pp: 0.1
65
+ min_examples_fraction: 0.01
44
66
  variance_guard:
45
67
  deadband: 0.03
46
68
  min_abs_adjust: 0.02
@@ -74,3 +96,42 @@ conservative:
74
96
  attn: 0.05
75
97
  embed: 0.07
76
98
  other: 0.07
99
+
100
+ aggressive:
101
+ metrics:
102
+ pm_ratio:
103
+ ratio_limit_base: 1.20
104
+ min_tokens: 50000
105
+ hysteresis_ratio: 0.002
106
+ min_token_fraction: 0.01
107
+ accuracy:
108
+ delta_min_pp: -2.0
109
+ min_examples: 200
110
+ hysteresis_delta_pp: 0.1
111
+ min_examples_fraction: 0.01
112
+ variance_guard:
113
+ deadband: 0.12
114
+ min_effect_lognll: 0.0005
115
+ spectral_guard:
116
+ sigma_quantile: 0.98
117
+ deadband: 0.15
118
+ scope: ffn
119
+ max_caps: 8
120
+ family_caps:
121
+ ffn: 3.0
122
+ attn: 3.5
123
+ embed: 2.5
124
+ other: 3.5
125
+ multiple_testing:
126
+ method: bh
127
+ alpha: 0.1
128
+ m: 4
129
+ rmt_guard:
130
+ deadband: 0.15
131
+ margin: 1.8
132
+ epsilon_default: 0.15
133
+ epsilon_by_family:
134
+ ffn: 0.15
135
+ attn: 0.15
136
+ embed: 0.15
137
+ other: 0.15
@@ -0,0 +1,97 @@
1
+ """Helpers for Hugging Face model loading.
2
+
3
+ Centralizes security- and performance-sensitive defaults used by HF adapters.
4
+ """
5
+
6
+ from __future__ import annotations
7
+
8
+ import os
9
+ from typing import Any
10
+
11
+ import torch
12
+
13
+ _TRUE = {"1", "true", "yes", "on"}
14
+ _FALSE = {"0", "false", "no", "off"}
15
+
16
+
17
+ def _coerce_bool(val: Any) -> bool | None:
18
+ if isinstance(val, bool):
19
+ return val
20
+ if isinstance(val, int):
21
+ return bool(val)
22
+ if isinstance(val, str):
23
+ s = val.strip().lower()
24
+ if s in _TRUE:
25
+ return True
26
+ if s in _FALSE:
27
+ return False
28
+ return None
29
+
30
+
31
+ def resolve_trust_remote_code(
32
+ kwargs: dict[str, Any] | None = None, *, default: bool = False
33
+ ) -> bool:
34
+ """Resolve trust_remote_code with config override and env opt-in."""
35
+ if kwargs and "trust_remote_code" in kwargs:
36
+ coerced = _coerce_bool(kwargs.get("trust_remote_code"))
37
+ if coerced is not None:
38
+ return coerced
39
+
40
+ for env_name in (
41
+ "INVARLOCK_TRUST_REMOTE_CODE",
42
+ "TRUST_REMOTE_CODE_BOOL",
43
+ "ALLOW_REMOTE_CODE",
44
+ ):
45
+ env_val = os.environ.get(env_name)
46
+ coerced = _coerce_bool(env_val)
47
+ if coerced is not None:
48
+ return coerced
49
+
50
+ return default
51
+
52
+
53
+ def default_torch_dtype() -> torch.dtype:
54
+ """Pick a safe default dtype for HF loads based on hardware."""
55
+ if torch.cuda.is_available():
56
+ try:
57
+ if (
58
+ hasattr(torch.cuda, "is_bf16_supported")
59
+ and torch.cuda.is_bf16_supported()
60
+ ):
61
+ return torch.bfloat16
62
+ except Exception:
63
+ pass
64
+ return torch.float16
65
+
66
+ if hasattr(torch.backends, "mps") and torch.backends.mps.is_available():
67
+ return torch.float16
68
+
69
+ return torch.float32
70
+
71
+
72
+ def resolve_torch_dtype(kwargs: dict[str, Any] | None = None) -> torch.dtype | str:
73
+ """Resolve torch_dtype from kwargs or choose a hardware-aware default."""
74
+ if kwargs and "torch_dtype" in kwargs:
75
+ val = kwargs.get("torch_dtype")
76
+ if isinstance(val, torch.dtype):
77
+ return val
78
+ if isinstance(val, str):
79
+ s = val.strip().lower()
80
+ if s == "auto":
81
+ return "auto"
82
+ mapping = {
83
+ "float16": torch.float16,
84
+ "fp16": torch.float16,
85
+ "half": torch.float16,
86
+ "bfloat16": torch.bfloat16,
87
+ "bf16": torch.bfloat16,
88
+ "float32": torch.float32,
89
+ "fp32": torch.float32,
90
+ }
91
+ if s in mapping:
92
+ return mapping[s]
93
+
94
+ return default_torch_dtype()
95
+
96
+
97
+ __all__ = ["resolve_trust_remote_code", "default_torch_dtype", "resolve_torch_dtype"]
@@ -0,0 +1,6 @@
1
+ """Calibration helpers and sweep harness utilities."""
2
+
3
+ __all__ = [
4
+ "spectral_null",
5
+ "variance_ve",
6
+ ]
@@ -0,0 +1,301 @@
1
+ from __future__ import annotations
2
+
3
+ import math
4
+ from collections import Counter, defaultdict
5
+ from typing import Any
6
+
7
+
8
+ def _finite01(value: Any) -> bool:
9
+ try:
10
+ f = float(value)
11
+ return math.isfinite(f) and 0.0 <= f <= 1.0
12
+ except Exception:
13
+ return False
14
+
15
+
16
+ def _bh_reject_families(
17
+ family_pvals: dict[str, float],
18
+ *,
19
+ alpha: float,
20
+ m: int,
21
+ ) -> set[str]:
22
+ if not family_pvals:
23
+ return set()
24
+ try:
25
+ alpha_f = float(alpha)
26
+ except Exception:
27
+ return set()
28
+ if not (0.0 < alpha_f <= 1.0):
29
+ return set()
30
+
31
+ names = list(family_pvals.keys())
32
+ pvals = [family_pvals[name] for name in names]
33
+ n = len(pvals)
34
+ m_eff = max(int(m) if isinstance(m, int) else 0, n, 1)
35
+
36
+ order = sorted(
37
+ range(n),
38
+ key=lambda idx: (float("inf") if not _finite01(pvals[idx]) else pvals[idx]),
39
+ )
40
+ max_k = 0
41
+ for rank, idx in enumerate(order, start=1):
42
+ p = pvals[idx]
43
+ if not _finite01(p):
44
+ continue
45
+ if p <= (alpha_f * rank) / m_eff:
46
+ max_k = rank
47
+ if max_k <= 0:
48
+ return set()
49
+ cutoff = (alpha_f * max_k) / m_eff
50
+ selected: set[str] = set()
51
+ for idx in order:
52
+ p = pvals[idx]
53
+ if _finite01(p) and p <= cutoff:
54
+ selected.add(names[idx])
55
+ return selected
56
+
57
+
58
+ def _bonferroni_reject_families(
59
+ family_pvals: dict[str, float],
60
+ *,
61
+ alpha: float,
62
+ m: int,
63
+ ) -> set[str]:
64
+ if not family_pvals:
65
+ return set()
66
+ try:
67
+ alpha_f = float(alpha)
68
+ except Exception:
69
+ return set()
70
+ if not (0.0 < alpha_f <= 1.0):
71
+ return set()
72
+ m_eff = max(int(m) if isinstance(m, int) else 0, len(family_pvals), 1)
73
+ cutoff = alpha_f / m_eff
74
+ return {fam for fam, p in family_pvals.items() if _finite01(p) and p <= cutoff}
75
+
76
+
77
+ def _extract_guard(report: dict[str, Any], name: str) -> dict[str, Any] | None:
78
+ guards = report.get("guards")
79
+ if isinstance(guards, list):
80
+ for item in guards:
81
+ if isinstance(item, dict) and item.get("name") == name:
82
+ return item
83
+ return None
84
+
85
+
86
+ def _extract_family_max_z(metrics: dict[str, Any]) -> dict[str, float]:
87
+ out: dict[str, float] = {}
88
+ summary = metrics.get("family_z_summary")
89
+ if isinstance(summary, dict):
90
+ for fam, vals in summary.items():
91
+ if not isinstance(vals, dict):
92
+ continue
93
+ z = vals.get("max")
94
+ try:
95
+ if z is not None and math.isfinite(float(z)):
96
+ out[str(fam)] = float(z)
97
+ except Exception:
98
+ continue
99
+ q = metrics.get("family_z_quantiles")
100
+ if isinstance(q, dict):
101
+ for fam, vals in q.items():
102
+ if not isinstance(vals, dict):
103
+ continue
104
+ z = vals.get("max")
105
+ try:
106
+ if z is not None and math.isfinite(float(z)):
107
+ out[str(fam)] = max(out.get(str(fam), float("-inf")), float(z))
108
+ except Exception:
109
+ continue
110
+ return out
111
+
112
+
113
+ def _extract_multiple_testing(metrics: dict[str, Any]) -> dict[str, Any]:
114
+ mt = metrics.get("multiple_testing")
115
+ if not isinstance(mt, dict):
116
+ return {}
117
+ out: dict[str, Any] = {}
118
+ method = mt.get("method")
119
+ if isinstance(method, str) and method.strip():
120
+ out["method"] = method.strip().lower()
121
+ try:
122
+ alpha = mt.get("alpha")
123
+ if alpha is not None:
124
+ out["alpha"] = float(alpha)
125
+ except Exception:
126
+ pass
127
+ try:
128
+ m_val = mt.get("m")
129
+ if m_val is not None:
130
+ out["m"] = int(m_val)
131
+ except Exception:
132
+ pass
133
+ return out
134
+
135
+
136
+ def _selected_families_for_alpha(
137
+ pvals: dict[str, float],
138
+ *,
139
+ method: str,
140
+ alpha: float,
141
+ m: int,
142
+ ) -> set[str]:
143
+ meth = (method or "").strip().lower()
144
+ if meth == "bonferroni":
145
+ return _bonferroni_reject_families(pvals, alpha=alpha, m=m)
146
+ # Default: BH
147
+ return _bh_reject_families(pvals, alpha=alpha, m=m)
148
+
149
+
150
+ def summarize_null_sweep_reports(
151
+ reports: list[dict[str, Any]],
152
+ *,
153
+ tier: str,
154
+ safety_margin: float = 0.05,
155
+ target_any_warning_rate: float = 0.01,
156
+ ) -> dict[str, Any]:
157
+ """Summarize spectral null-sweep results and recommend κ/alpha.
158
+
159
+ Inputs are run report dicts produced by `invarlock run` (or equivalent).
160
+ """
161
+
162
+ tier_norm = (tier or "").strip().lower() or "balanced"
163
+ margin = float(safety_margin or 0.0)
164
+ if not (0.0 <= margin <= 1.0):
165
+ margin = 0.05
166
+ target = float(target_any_warning_rate or 0.0)
167
+ if not (0.0 <= target <= 1.0):
168
+ target = 0.01
169
+
170
+ family_max_z: dict[str, float] = defaultdict(lambda: float("-inf"))
171
+ has_warning_default: list[bool] = []
172
+ run_pvals: list[dict[str, float]] = []
173
+
174
+ mt_method = "bh"
175
+ mt_alpha = 0.05
176
+ mt_m = 4
177
+
178
+ selected_by_family: Counter[str] = Counter()
179
+ candidate_by_family: Counter[str] = Counter()
180
+
181
+ for report in reports:
182
+ if not isinstance(report, dict):
183
+ continue
184
+ g = _extract_guard(report, "spectral") or {}
185
+ metrics = g.get("metrics", {}) if isinstance(g.get("metrics"), dict) else {}
186
+ mt = _extract_multiple_testing(metrics)
187
+ if mt:
188
+ mt_method = str(mt.get("method", mt_method))
189
+ if mt.get("alpha") is not None:
190
+ mt_alpha = float(mt.get("alpha"))
191
+ if mt.get("m") is not None:
192
+ mt_m = int(mt.get("m"))
193
+
194
+ fam_z = _extract_family_max_z(metrics)
195
+ for fam, z in fam_z.items():
196
+ family_max_z[fam] = max(family_max_z[fam], float(z))
197
+
198
+ selection = (
199
+ metrics.get("multiple_testing_selection")
200
+ if isinstance(metrics.get("multiple_testing_selection"), dict)
201
+ else {}
202
+ )
203
+ pvals = selection.get("family_pvalues")
204
+ if not isinstance(pvals, dict):
205
+ pvals = {}
206
+ parsed_pvals: dict[str, float] = {}
207
+ for fam, p in pvals.items():
208
+ try:
209
+ pf = float(p)
210
+ except Exception:
211
+ continue
212
+ if _finite01(pf):
213
+ parsed_pvals[str(fam)] = pf
214
+ run_pvals.append(parsed_pvals)
215
+
216
+ families_selected = selection.get("families_selected")
217
+ if isinstance(families_selected, list):
218
+ for fam in families_selected:
219
+ selected_by_family[str(fam)] += 1
220
+
221
+ fam_counts = selection.get("family_violation_counts")
222
+ if isinstance(fam_counts, dict):
223
+ for fam, count in fam_counts.items():
224
+ try:
225
+ candidate_by_family[str(fam)] += int(count)
226
+ except Exception:
227
+ continue
228
+
229
+ caps_applied = metrics.get("caps_applied")
230
+ try:
231
+ caps_applied_int = int(caps_applied) if caps_applied is not None else 0
232
+ except Exception:
233
+ caps_applied_int = 0
234
+ violations = g.get("violations", [])
235
+ has_warning_default.append(bool(caps_applied_int) or bool(violations))
236
+
237
+ n = max(len(has_warning_default), 1)
238
+ observed_any_rate = sum(1 for v in has_warning_default if v) / float(n)
239
+
240
+ # κ recommendation: max observed z per family (+ margin), rounded for stable tiers.yaml diffs.
241
+ rec_caps: dict[str, float] = {}
242
+ for fam, z in sorted(family_max_z.items()):
243
+ if not math.isfinite(z):
244
+ continue
245
+ kappa = z * (1.0 + margin)
246
+ rec_caps[fam] = float(round(kappa, 3))
247
+
248
+ # α calibration: choose the largest alpha that meets target_any_warning_rate.
249
+ # This uses per-run family p-values (from spectral.multiple_testing_selection).
250
+ def _rate_for_alpha(alpha: float) -> float:
251
+ any_sel = 0
252
+ for pvals in run_pvals:
253
+ selected = _selected_families_for_alpha(
254
+ pvals, method=mt_method, alpha=alpha, m=mt_m
255
+ )
256
+ any_sel += 1 if selected else 0
257
+ return any_sel / float(max(len(run_pvals), 1))
258
+
259
+ recommended_alpha = float(mt_alpha)
260
+ if run_pvals and observed_any_rate > target:
261
+ # Halving search is stable/deterministic and avoids dependency-heavy optimizers.
262
+ alpha_grid: list[float] = []
263
+ a = float(mt_alpha)
264
+ for _ in range(20):
265
+ if a <= 1e-6:
266
+ break
267
+ alpha_grid.append(a)
268
+ a *= 0.5
269
+ alpha_grid.append(1e-6)
270
+ best = None
271
+ for candidate in alpha_grid:
272
+ rate = _rate_for_alpha(candidate)
273
+ if rate <= target:
274
+ best = candidate
275
+ break
276
+ if best is not None:
277
+ recommended_alpha = float(best)
278
+
279
+ return {
280
+ "tier": tier_norm,
281
+ "n_runs": int(len(has_warning_default)),
282
+ "observed": {
283
+ "any_warning_rate": float(observed_any_rate),
284
+ "selected_by_family_runs": dict(selected_by_family),
285
+ "candidate_violations_by_family_total": dict(candidate_by_family),
286
+ "family_max_z": {
287
+ k: float(v) for k, v in sorted(family_max_z.items()) if math.isfinite(v)
288
+ },
289
+ },
290
+ "recommendations": {
291
+ "family_caps": rec_caps,
292
+ "multiple_testing": {
293
+ "method": str(mt_method),
294
+ "alpha": float(recommended_alpha),
295
+ "m": int(mt_m),
296
+ },
297
+ },
298
+ }
299
+
300
+
301
+ __all__ = ["summarize_null_sweep_reports"]