invarlock 0.3.6__py3-none-any.whl → 0.3.8__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 (73) hide show
  1. invarlock/__init__.py +4 -4
  2. invarlock/adapters/__init__.py +10 -14
  3. invarlock/adapters/auto.py +37 -50
  4. invarlock/adapters/capabilities.py +2 -2
  5. invarlock/adapters/hf_causal.py +418 -0
  6. invarlock/adapters/{hf_onnx.py → hf_causal_onnx.py} +3 -3
  7. invarlock/adapters/hf_loading.py +7 -7
  8. invarlock/adapters/hf_mixin.py +53 -9
  9. invarlock/adapters/{hf_bert.py → hf_mlm.py} +4 -11
  10. invarlock/adapters/{hf_t5.py → hf_seq2seq.py} +9 -9
  11. invarlock/assurance/__init__.py +15 -23
  12. invarlock/cli/adapter_auto.py +32 -26
  13. invarlock/cli/app.py +128 -27
  14. invarlock/cli/commands/__init__.py +2 -2
  15. invarlock/cli/commands/calibrate.py +48 -4
  16. invarlock/cli/commands/doctor.py +8 -10
  17. invarlock/cli/commands/evaluate.py +986 -0
  18. invarlock/cli/commands/explain_gates.py +25 -17
  19. invarlock/cli/commands/export_html.py +11 -9
  20. invarlock/cli/commands/plugins.py +13 -9
  21. invarlock/cli/commands/report.py +326 -92
  22. invarlock/cli/commands/run.py +1160 -228
  23. invarlock/cli/commands/verify.py +157 -97
  24. invarlock/cli/config.py +1 -1
  25. invarlock/cli/determinism.py +1 -1
  26. invarlock/cli/doctor_helpers.py +4 -5
  27. invarlock/cli/output.py +193 -0
  28. invarlock/cli/provenance.py +4 -4
  29. invarlock/core/bootstrap.py +1 -1
  30. invarlock/core/registry.py +9 -11
  31. invarlock/core/retry.py +14 -14
  32. invarlock/core/runner.py +112 -26
  33. invarlock/edits/noop.py +2 -2
  34. invarlock/edits/quant_rtn.py +67 -39
  35. invarlock/eval/__init__.py +1 -1
  36. invarlock/eval/bench.py +14 -10
  37. invarlock/eval/data.py +68 -23
  38. invarlock/eval/metrics.py +59 -1
  39. invarlock/eval/primary_metric.py +1 -1
  40. invarlock/eval/tasks/__init__.py +12 -0
  41. invarlock/eval/tasks/classification.py +48 -0
  42. invarlock/eval/tasks/qa.py +36 -0
  43. invarlock/eval/tasks/text_generation.py +102 -0
  44. invarlock/guards/invariants.py +19 -10
  45. invarlock/guards/rmt.py +2 -2
  46. invarlock/guards/spectral.py +1 -1
  47. invarlock/guards/variance.py +2 -2
  48. invarlock/model_profile.py +64 -62
  49. invarlock/observability/health.py +6 -6
  50. invarlock/observability/metrics.py +108 -0
  51. invarlock/plugins/hf_bnb_adapter.py +32 -21
  52. invarlock/reporting/__init__.py +18 -4
  53. invarlock/reporting/guards_analysis.py +154 -4
  54. invarlock/reporting/html.py +61 -11
  55. invarlock/reporting/normalizer.py +9 -2
  56. invarlock/reporting/policy_utils.py +1 -1
  57. invarlock/reporting/primary_metric_utils.py +11 -11
  58. invarlock/reporting/render.py +876 -510
  59. invarlock/reporting/report.py +72 -30
  60. invarlock/reporting/{certificate.py → report_builder.py} +252 -99
  61. invarlock/reporting/{certificate_schema.py → report_schema.py} +22 -22
  62. invarlock/reporting/report_types.py +6 -1
  63. invarlock/reporting/telemetry.py +86 -0
  64. invarlock-0.3.8.dist-info/METADATA +283 -0
  65. {invarlock-0.3.6.dist-info → invarlock-0.3.8.dist-info}/RECORD +69 -64
  66. {invarlock-0.3.6.dist-info → invarlock-0.3.8.dist-info}/WHEEL +1 -1
  67. {invarlock-0.3.6.dist-info → invarlock-0.3.8.dist-info}/entry_points.txt +5 -3
  68. invarlock/adapters/hf_gpt2.py +0 -404
  69. invarlock/adapters/hf_llama.py +0 -487
  70. invarlock/cli/commands/certify.py +0 -422
  71. invarlock-0.3.6.dist-info/METADATA +0 -588
  72. {invarlock-0.3.6.dist-info → invarlock-0.3.8.dist-info}/licenses/LICENSE +0 -0
  73. {invarlock-0.3.6.dist-info → invarlock-0.3.8.dist-info}/top_level.txt +0 -0
@@ -50,7 +50,7 @@ def resolve_trust_remote_code(
50
50
  return default
51
51
 
52
52
 
53
- def default_torch_dtype() -> torch.dtype:
53
+ def default_dtype() -> torch.dtype:
54
54
  """Pick a safe default dtype for HF loads based on hardware."""
55
55
  if torch.cuda.is_available():
56
56
  try:
@@ -69,10 +69,10 @@ def default_torch_dtype() -> torch.dtype:
69
69
  return torch.float32
70
70
 
71
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")
72
+ def resolve_dtype(kwargs: dict[str, Any] | None = None) -> torch.dtype | str:
73
+ """Resolve dtype from kwargs or choose a hardware-aware default."""
74
+ if kwargs and "dtype" in kwargs:
75
+ val = kwargs.get("dtype")
76
76
  if isinstance(val, torch.dtype):
77
77
  return val
78
78
  if isinstance(val, str):
@@ -91,7 +91,7 @@ def resolve_torch_dtype(kwargs: dict[str, Any] | None = None) -> torch.dtype | s
91
91
  if s in mapping:
92
92
  return mapping[s]
93
93
 
94
- return default_torch_dtype()
94
+ return default_dtype()
95
95
 
96
96
 
97
- __all__ = ["resolve_trust_remote_code", "default_torch_dtype", "resolve_torch_dtype"]
97
+ __all__ = ["resolve_trust_remote_code", "default_dtype", "resolve_dtype"]
@@ -490,18 +490,39 @@ class HFAdapterMixin:
490
490
  """Return mapping of tied parameter names to source parameter names."""
491
491
 
492
492
  tying: dict[str, str] = {}
493
- param_names = set(dict(model.named_parameters()).keys())
493
+ try:
494
+ named = model.named_parameters(remove_duplicate=False) # type: ignore[call-arg]
495
+ except TypeError: # pragma: no cover - torch version dependent
496
+ named = model.named_parameters()
497
+ params = dict(named)
498
+
499
+ def _is_tied(name_a: str, name_b: str) -> bool:
500
+ a = params.get(name_a)
501
+ b = params.get(name_b)
502
+ if a is None or b is None:
503
+ return False
504
+ try:
505
+ if a is b:
506
+ return True
507
+ if hasattr(a, "data_ptr") and hasattr(b, "data_ptr"):
508
+ return int(a.data_ptr()) == int(b.data_ptr())
509
+ except Exception:
510
+ return False
511
+ return False
494
512
 
495
- if "lm_head.weight" in param_names and "transformer.wte.weight" in param_names:
513
+ if _is_tied("lm_head.weight", "transformer.wte.weight"):
496
514
  tying["lm_head.weight"] = "transformer.wte.weight"
497
515
 
516
+ if _is_tied("lm_head.weight", "model.embed_tokens.weight"):
517
+ tying["lm_head.weight"] = "model.embed_tokens.weight"
518
+
498
519
  decoder_name = "cls.predictions.decoder.weight"
499
- if decoder_name in param_names:
520
+ if decoder_name in params:
500
521
  for candidate in (
501
522
  "bert.embeddings.word_embeddings.weight",
502
523
  "embeddings.word_embeddings.weight",
503
524
  ):
504
- if candidate in param_names:
525
+ if _is_tied(decoder_name, candidate):
505
526
  tying[decoder_name] = candidate
506
527
  break
507
528
 
@@ -562,22 +583,45 @@ class HFAdapterMixin:
562
583
  def _serialize_config(self, config: Any) -> dict[str, Any]:
563
584
  """Serialize HuggingFace config fields into simple Python types."""
564
585
 
586
+ def _collect(data: dict[str, Any]) -> dict[str, Any]:
587
+ out: dict[str, Any] = {}
588
+ for key, value in data.items():
589
+ if key.startswith("_") or key in {"method_calls"}:
590
+ continue
591
+ if value is None or isinstance(value, SCALAR_TYPES):
592
+ out[key] = value
593
+ elif isinstance(value, list | dict):
594
+ out[key] = value
595
+ return out
596
+
597
+ to_dict = getattr(config, "to_dict", None)
598
+ if callable(to_dict):
599
+ try:
600
+ data = to_dict()
601
+ except Exception:
602
+ data = None
603
+ if isinstance(data, dict):
604
+ return _collect(data)
605
+
606
+ try:
607
+ data = vars(config)
608
+ except TypeError:
609
+ data = None
610
+ if isinstance(data, dict):
611
+ return _collect(data)
612
+
565
613
  result: dict[str, Any] = {}
566
614
  for key in dir(config):
567
- if key.startswith("_"):
615
+ if key.startswith("_") or key in {"torch_dtype"}:
568
616
  continue
569
-
570
617
  try:
571
618
  value = getattr(config, key)
572
619
  except AttributeError:
573
620
  continue
574
-
575
621
  if callable(value):
576
622
  continue
577
-
578
623
  if value is None or isinstance(value, SCALAR_TYPES):
579
624
  result[key] = value
580
625
  elif isinstance(value, list | dict):
581
626
  result[key] = value
582
-
583
627
  return result
@@ -1,15 +1,8 @@
1
1
  """
2
- HuggingFace BERT Model Adapter
2
+ HuggingFace masked LM adapter.
3
3
  ==============================
4
4
 
5
- ModelAdapter implementation for HuggingFace BERT architecture models.
6
-
7
- This adapter provides BERT-specific integration including:
8
- - Support for BERT, RoBERTa, DistilBERT, and other BERT variants
9
- - Proper handling of bidirectional attention layers
10
- - Support for classification heads and pooling layers
11
- - Token type embeddings and position embeddings handling
12
- - Proper device-aware state serialization
5
+ ModelAdapter implementation for HuggingFace masked language models.
13
6
  """
14
7
 
15
8
  from typing import Any
@@ -27,7 +20,7 @@ TensorType = torch.Tensor
27
20
  ModuleType = nn.Module
28
21
 
29
22
 
30
- class HF_BERT_Adapter(HFAdapterMixin, ModelAdapter):
23
+ class HF_MLM_Adapter(HFAdapterMixin, ModelAdapter):
31
24
  """
32
25
  HuggingFace-specific ModelAdapter implementation for BERT models.
33
26
 
@@ -39,7 +32,7 @@ class HF_BERT_Adapter(HFAdapterMixin, ModelAdapter):
39
32
  - Device-aware state serialization
40
33
  """
41
34
 
42
- name = "hf_bert"
35
+ name = "hf_mlm"
43
36
 
44
37
  def load_model(
45
38
  self, model_id: str, device: str = "auto", **kwargs: Any
@@ -1,11 +1,11 @@
1
1
  """
2
- HuggingFace T5 Model Adapter
3
- ============================
2
+ HuggingFace encoder-decoder adapter.
3
+ ===================================
4
4
 
5
- ModelAdapter implementation for HuggingFace T5 encoder-decoder models.
5
+ ModelAdapter implementation for HuggingFace encoder-decoder (seq2seq) models.
6
6
 
7
- Loads AutoModelForSeq2SeqLM (e.g., t5-small/base/large) and exposes a minimal
8
- describe() sufficient for guard policies and reporting.
7
+ Loads AutoModelForSeq2SeqLM and exposes a minimal describe() sufficient for
8
+ guard policies and reporting.
9
9
  """
10
10
 
11
11
  from __future__ import annotations
@@ -25,10 +25,10 @@ TensorType = torch.Tensor
25
25
  ModuleType = nn.Module
26
26
 
27
27
 
28
- class HF_T5_Adapter(HFAdapterMixin, ModelAdapter):
29
- """HuggingFace T5 adapter using AutoModelForSeq2SeqLM."""
28
+ class HF_Seq2Seq_Adapter(HFAdapterMixin, ModelAdapter):
29
+ """HuggingFace encoder-decoder adapter using AutoModelForSeq2SeqLM."""
30
30
 
31
- name = "hf_t5"
31
+ name = "hf_seq2seq"
32
32
 
33
33
  def load_model( # type: ignore[override]
34
34
  self, model_id: str, device: str = "auto", **kwargs: Any
@@ -136,4 +136,4 @@ class HF_T5_Adapter(HFAdapterMixin, ModelAdapter):
136
136
  return super().restore(model, blob)
137
137
 
138
138
 
139
- __all__ = ["HF_T5_Adapter"]
139
+ __all__ = ["HF_Seq2Seq_Adapter"]
@@ -1,8 +1,4 @@
1
- """Assurance namespace (`invarlock.assurance`).
2
-
3
- This namespace groups safety-certificate related surfaces. For now it forwards
4
- to `invarlock.eval` and guard modules; future work may move implementations here.
5
- """
1
+ """Assurance namespace (`invarlock.assurance`)."""
6
2
 
7
3
  from __future__ import annotations
8
4
 
@@ -11,33 +7,29 @@ from typing import Any
11
7
  from invarlock.reporting.report_types import RunReport
12
8
 
13
9
  try: # pragma: no cover - shim to reporting modules
14
- from invarlock.reporting.certificate import (
15
- CERTIFICATE_SCHEMA_VERSION,
16
- make_certificate,
17
- validate_certificate,
18
- )
19
-
20
10
  # Prefer direct import from render for rendering APIs
21
- from invarlock.reporting.render import render_certificate_markdown
11
+ from invarlock.reporting.render import render_report_markdown
12
+ from invarlock.reporting.report_builder import make_report
13
+ from invarlock.reporting.report_schema import REPORT_SCHEMA_VERSION, validate_report
22
14
  except Exception: # pragma: no cover - provide soft stubs
23
- CERTIFICATE_SCHEMA_VERSION = "v1"
15
+ REPORT_SCHEMA_VERSION = "v1"
24
16
 
25
- def make_certificate(
17
+ def make_report(
26
18
  report: RunReport,
27
19
  baseline: RunReport | dict[str, Any],
28
20
  ) -> dict[str, Any]:
29
- raise ImportError("invarlock.reporting.certificate not available")
21
+ raise ImportError("invarlock.reporting.report_builder not available")
30
22
 
31
- def render_certificate_markdown(certificate: dict[str, Any]) -> str:
32
- raise ImportError("invarlock.reporting.certificate not available")
23
+ def render_report_markdown(evaluation_report: dict[str, Any]) -> str:
24
+ raise ImportError("invarlock.reporting.report_builder not available")
33
25
 
34
- def validate_certificate(certificate: dict[str, Any]) -> bool:
35
- raise ImportError("invarlock.reporting.certificate not available")
26
+ def validate_report(report: dict[str, Any]) -> bool:
27
+ raise ImportError("invarlock.reporting.report_schema not available")
36
28
 
37
29
 
38
30
  __all__ = [
39
- "CERTIFICATE_SCHEMA_VERSION",
40
- "make_certificate",
41
- "render_certificate_markdown",
42
- "validate_certificate",
31
+ "REPORT_SCHEMA_VERSION",
32
+ "make_report",
33
+ "render_report_markdown",
34
+ "validate_report",
43
35
  ]
@@ -2,7 +2,8 @@
2
2
  Auto adapter resolution utilities.
3
3
 
4
4
  These helpers map a model identifier (HF directory or Hub ID) to a
5
- concrete built-in adapter name (hf_gpt2, hf_llama, hf_bert) without
5
+ concrete built-in adapter name (hf_causal, hf_mlm, hf_seq2seq, hf_causal_onnx)
6
+ without
6
7
  adding a hard dependency on Transformers.
7
8
  """
8
9
 
@@ -46,11 +47,7 @@ def _detect_quant_family_from_cfg(cfg: dict[str, Any]) -> str | None:
46
47
  return "hf_gptq"
47
48
  if any(tok in method for tok in ("awq",)):
48
49
  return "hf_awq"
49
- # BitsAndBytes style
50
- if any(
51
- str(q.get(k, "")).lower() in {"true", "1"}
52
- for k in ("load_in_4bit", "load_in_8bit")
53
- ) or any("bitsandbytes" in str(v).lower() for v in q.values()):
50
+ if "bitsandbytes" in method or "bnb" in method:
54
51
  return "hf_bnb"
55
52
  except Exception:
56
53
  return None
@@ -58,15 +55,15 @@ def _detect_quant_family_from_cfg(cfg: dict[str, Any]) -> str | None:
58
55
 
59
56
 
60
57
  def resolve_auto_adapter(
61
- model_id: str | os.PathLike[str], default: str = "hf_gpt2"
58
+ model_id: str | os.PathLike[str], default: str = "hf_causal"
62
59
  ) -> str:
63
60
  """Resolve an appropriate built-in adapter name for a model.
64
61
 
65
62
  Heuristics:
66
63
  - Prefer local config.json (no network). Inspect `model_type` and
67
- `architectures` to classify LLaMA/Mistral vs BERT vs GPT-like.
64
+ `architectures` to classify causal vs masked-LM vs seq2seq.
68
65
  - Fallback to simple name heuristics on the model_id string.
69
- - Default to `hf_gpt2` when unsure.
66
+ - Default to `hf_causal` when unsure.
70
67
  """
71
68
  cfg = _read_local_hf_config(model_id)
72
69
  model_id_str = str(model_id)
@@ -77,32 +74,41 @@ def resolve_auto_adapter(
77
74
  if fam:
78
75
  return fam
79
76
  mt = str(c.get("model_type", "")).lower()
77
+ if bool(c.get("is_encoder_decoder", False)):
78
+ return "hf_seq2seq"
80
79
  archs = [str(a) for a in c.get("architectures", []) if isinstance(a, str)]
81
80
  arch_blob = " ".join(archs)
82
- if (
83
- mt in {"llama", "mistral", "qwen", "yi"}
84
- or "Llama" in arch_blob
85
- or "Mistral" in arch_blob
86
- ):
87
- return "hf_llama"
81
+ if "ConditionalGeneration" in arch_blob or "Seq2SeqLM" in arch_blob:
82
+ return "hf_seq2seq"
88
83
  # Treat masked-LM families as BERT-like
89
84
  if (
90
85
  mt in {"bert", "roberta", "distilbert", "albert", "deberta", "deberta-v2"}
91
86
  or "MaskedLM" in arch_blob
92
87
  ):
93
- return "hf_bert"
94
- # Generic causal LM
95
- if "CausalLM" in arch_blob or mt in {
88
+ return "hf_mlm"
89
+ # Causal LM families (best-effort; structural validation happens in the adapter).
90
+ if "CausalLM" in arch_blob or "ForCausalLM" in arch_blob:
91
+ return "hf_causal"
92
+ if mt in {
93
+ "mistral",
94
+ "mixtral",
95
+ "qwen",
96
+ "qwen2",
97
+ "qwen2_moe",
98
+ "yi",
96
99
  "gpt2",
97
100
  "gpt_neox",
98
101
  "opt",
99
102
  "gptj",
100
- "gptj8bit",
103
+ "phi",
104
+ "falcon",
105
+ "glm",
106
+ "deepseek",
101
107
  }:
102
- return "hf_gpt2"
108
+ return "hf_causal"
103
109
  return None
104
110
 
105
- # If local directory contains ONNX model files, prefer hf_onnx
111
+ # If local directory contains ONNX model files, prefer the ONNX causal adapter.
106
112
  try:
107
113
  p = Path(model_id)
108
114
  if p.exists() and p.is_dir():
@@ -114,7 +120,7 @@ def resolve_auto_adapter(
114
120
  "encoder_model.onnx",
115
121
  ]
116
122
  if any((p / fname).exists() for fname in onnx_files):
117
- return "hf_onnx"
123
+ return "hf_causal_onnx"
118
124
  except Exception:
119
125
  pass
120
126
 
@@ -134,10 +140,10 @@ def resolve_auto_adapter(
134
140
  k in lower_id for k in ["bnb", "bitsandbytes", "-4bit", "-8bit", "4bit", "8bit"]
135
141
  ):
136
142
  return "hf_bnb"
137
- if any(k in lower_id for k in ["llama", "mistral", "qwen", "yi"]):
138
- return "hf_llama"
143
+ if any(k in lower_id for k in ["t5", "bart"]):
144
+ return "hf_seq2seq"
139
145
  if any(k in lower_id for k in ["bert", "roberta", "albert", "deberta"]):
140
- return "hf_bert"
146
+ return "hf_mlm"
141
147
  return default
142
148
 
143
149
 
@@ -148,7 +154,7 @@ def apply_auto_adapter_if_needed(cfg: Any) -> Any:
148
154
  """
149
155
  try:
150
156
  adapter = str(getattr(cfg.model, "adapter", ""))
151
- if adapter.strip().lower() not in {"auto", "hf_auto", "auto_hf"}:
157
+ if adapter.strip().lower() not in {"auto", "auto_hf"}:
152
158
  return cfg
153
159
  model_id = str(getattr(cfg.model, "id", ""))
154
160
  resolved = resolve_auto_adapter(model_id)