invarlock 0.3.6__py3-none-any.whl → 0.3.7__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 (55) hide show
  1. invarlock/__init__.py +2 -2
  2. invarlock/adapters/__init__.py +10 -14
  3. invarlock/adapters/auto.py +35 -40
  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_mixin.py +25 -4
  8. invarlock/adapters/{hf_bert.py → hf_mlm.py} +4 -11
  9. invarlock/adapters/{hf_t5.py → hf_seq2seq.py} +9 -9
  10. invarlock/cli/adapter_auto.py +31 -21
  11. invarlock/cli/app.py +73 -2
  12. invarlock/cli/commands/certify.py +600 -59
  13. invarlock/cli/commands/doctor.py +8 -10
  14. invarlock/cli/commands/plugins.py +13 -9
  15. invarlock/cli/commands/report.py +233 -69
  16. invarlock/cli/commands/run.py +907 -183
  17. invarlock/cli/commands/verify.py +76 -11
  18. invarlock/cli/config.py +1 -1
  19. invarlock/cli/doctor_helpers.py +4 -5
  20. invarlock/cli/output.py +193 -0
  21. invarlock/cli/provenance.py +1 -1
  22. invarlock/core/bootstrap.py +1 -1
  23. invarlock/core/registry.py +9 -11
  24. invarlock/core/runner.py +111 -25
  25. invarlock/edits/quant_rtn.py +65 -37
  26. invarlock/eval/bench.py +3 -3
  27. invarlock/eval/data.py +68 -23
  28. invarlock/eval/metrics.py +59 -1
  29. invarlock/eval/tasks/__init__.py +12 -0
  30. invarlock/eval/tasks/classification.py +48 -0
  31. invarlock/eval/tasks/qa.py +36 -0
  32. invarlock/eval/tasks/text_generation.py +102 -0
  33. invarlock/guards/invariants.py +19 -10
  34. invarlock/guards/rmt.py +2 -2
  35. invarlock/guards/variance.py +2 -2
  36. invarlock/model_profile.py +48 -27
  37. invarlock/observability/health.py +6 -6
  38. invarlock/observability/metrics.py +108 -0
  39. invarlock/reporting/certificate.py +159 -9
  40. invarlock/reporting/certificate_schema.py +1 -1
  41. invarlock/reporting/guards_analysis.py +154 -4
  42. invarlock/reporting/html.py +55 -5
  43. invarlock/reporting/normalizer.py +7 -0
  44. invarlock/reporting/render.py +791 -431
  45. invarlock/reporting/report.py +39 -3
  46. invarlock/reporting/report_types.py +6 -1
  47. invarlock/reporting/telemetry.py +86 -0
  48. {invarlock-0.3.6.dist-info → invarlock-0.3.7.dist-info}/METADATA +23 -9
  49. {invarlock-0.3.6.dist-info → invarlock-0.3.7.dist-info}/RECORD +53 -48
  50. {invarlock-0.3.6.dist-info → invarlock-0.3.7.dist-info}/WHEEL +1 -1
  51. {invarlock-0.3.6.dist-info → invarlock-0.3.7.dist-info}/entry_points.txt +5 -3
  52. invarlock/adapters/hf_gpt2.py +0 -404
  53. invarlock/adapters/hf_llama.py +0 -487
  54. {invarlock-0.3.6.dist-info → invarlock-0.3.7.dist-info}/licenses/LICENSE +0 -0
  55. {invarlock-0.3.6.dist-info → invarlock-0.3.7.dist-info}/top_level.txt +0 -0
@@ -1,404 +0,0 @@
1
- """
2
- HuggingFace GPT-2 Model Adapter
3
- ===============================
4
-
5
- ModelAdapter implementation for HuggingFace GPT-2 architecture models.
6
-
7
- This adapter provides enhanced HuggingFace integration including:
8
- - Better model detection for HF model variants
9
- - Proper handling of transformers library specifics
10
- - Device-aware state serialization with HF model handling
11
- - Weight tying preservation (lm_head ↔ wte)
12
- - Split size and layer naming convention support
13
- """
14
-
15
- import os
16
- from types import SimpleNamespace
17
- from typing import Any
18
-
19
- import torch
20
- import torch.nn as nn
21
-
22
- from invarlock.core.api import ModelAdapter
23
- from invarlock.core.error_utils import wrap_errors
24
- from invarlock.core.exceptions import AdapterError, DependencyError, ModelLoadError
25
-
26
- from .hf_mixin import HFAdapterMixin
27
-
28
- LIGHT_IMPORT = os.getenv("INVARLOCK_LIGHT_IMPORT", "").strip().lower() in {
29
- "1",
30
- "true",
31
- "yes",
32
- }
33
-
34
- TensorType = torch.Tensor
35
- ModuleType = nn.Module
36
-
37
-
38
- class HF_GPT2_Adapter(HFAdapterMixin, ModelAdapter):
39
- """
40
- HuggingFace-specific ModelAdapter implementation for GPT-2 models.
41
-
42
- Supports HuggingFace GPT2Model and GPT2LMHeadModel variants with:
43
- - Enhanced HF model detection and validation
44
- - Device-aware state serialization
45
- - Weight tying preservation across snapshot/restore cycles
46
- - Proper handling of Conv1D layers and split_size conventions
47
- """
48
-
49
- name = "hf_gpt2"
50
-
51
- def load_model(
52
- self, model_id: str, device: str = "auto", **kwargs: Any
53
- ) -> ModuleType | Any:
54
- """
55
- Load a HuggingFace GPT-2 model.
56
-
57
- Args:
58
- model_id: Model identifier (e.g. "gpt2", "gpt2-medium")
59
- device: Target device ("auto", "cuda", "mps", "cpu")
60
-
61
- Returns:
62
- Loaded GPT-2 model
63
- """
64
- # Lazy import to allow dependency mapping; in light-import mode fall back to a stub
65
- try:
66
- with wrap_errors(
67
- DependencyError,
68
- "E203",
69
- "DEPENDENCY-MISSING: transformers",
70
- lambda e: {"dependency": "transformers"},
71
- ):
72
- from transformers import AutoModelForCausalLM # type: ignore
73
-
74
- with wrap_errors(
75
- ModelLoadError,
76
- "E201",
77
- "MODEL-LOAD-FAILED: transformers AutoModelForCausalLM",
78
- lambda e: {"model_id": model_id},
79
- ):
80
- model = AutoModelForCausalLM.from_pretrained(model_id, **kwargs)
81
-
82
- return self._safe_to_device(model, device)
83
- except DependencyError:
84
- if LIGHT_IMPORT:
85
- # Minimal stand-in that satisfies downstream interface requirements
86
- stub = SimpleNamespace(name="hf_gpt2_stub")
87
- stub.to = lambda *_a, **_k: stub # type: ignore[attr-defined]
88
- return stub
89
- raise
90
-
91
- def can_handle(self, model: ModuleType | Any) -> bool:
92
- """
93
- Check if this adapter can handle the given model.
94
-
95
- Enhanced detection for HuggingFace GPT-2 models with validation
96
- of expected structure and configuration.
97
-
98
- Args:
99
- model: The model to check
100
-
101
- Returns:
102
- True if this is a HuggingFace GPT-2 compatible model
103
- """
104
- # Check for HuggingFace GPT-2 class names (avoid importing classes at module import time)
105
- model_name = model.__class__.__name__
106
- if model_name in ["GPT2Model", "GPT2LMHeadModel"]:
107
- # Verify it has HF config
108
- if hasattr(model, "config") and hasattr(model.config, "model_type"):
109
- return model.config.model_type == "gpt2"
110
-
111
- # Structural validation for GPT-2-like models
112
- if hasattr(model, "config") and hasattr(model, "transformer"):
113
- config = model.config
114
- transformer = model.transformer
115
-
116
- # Check for GPT-2 configuration attributes
117
- if (
118
- hasattr(config, "n_layer")
119
- and hasattr(config, "n_head")
120
- and hasattr(config, "hidden_size")
121
- and hasattr(transformer, "h")
122
- ):
123
- # Validate transformer structure
124
- try:
125
- h_layers = transformer.h
126
- if hasattr(h_layers, "__len__") and len(h_layers) > 0:
127
- layer = h_layers[0]
128
- elif hasattr(h_layers, "__iter__"):
129
- # Handle iterables without len() (like Mock objects in tests)
130
- try:
131
- layer = next(iter(h_layers))
132
- except (StopIteration, TypeError):
133
- return False
134
- else:
135
- return False
136
-
137
- # Check for GPT-2 layer structure with HF conventions
138
- if (
139
- hasattr(layer, "attn")
140
- and hasattr(layer, "mlp")
141
- and hasattr(layer.attn, "c_attn")
142
- and hasattr(layer.attn, "c_proj")
143
- and hasattr(layer.mlp, "c_fc")
144
- and hasattr(layer.mlp, "c_proj")
145
- ):
146
- return True
147
- except (AttributeError, TypeError):
148
- return False
149
-
150
- # Check for bare GPT2Model structure (less common but possible)
151
- if hasattr(model, "h") and hasattr(model, "config"):
152
- if hasattr(model.config, "n_layer") and len(model.h) > 0:
153
- layer = model.h[0]
154
- if (
155
- hasattr(layer, "attn")
156
- and hasattr(layer, "mlp")
157
- and hasattr(layer.attn, "c_attn")
158
- and hasattr(layer.mlp, "c_fc")
159
- ):
160
- return True
161
-
162
- return False
163
-
164
- def describe(self, model: ModuleType | Any) -> dict[str, Any]:
165
- """
166
- Get structural description of the HuggingFace GPT-2 model.
167
-
168
- Returns the required format for validation gates:
169
- - n_layer: int
170
- - heads_per_layer: List[int]
171
- - mlp_dims: List[int]
172
- - tying: Dict[str, str] (weight tying map)
173
-
174
- Args:
175
- model: The HuggingFace GPT-2 model to describe
176
-
177
- Returns:
178
- Dictionary with model structure info in required format
179
- """
180
- # Determine model structure
181
- if hasattr(model, "transformer"):
182
- # GPT2LMHeadModel structure
183
- transformer = model.transformer
184
- layers = transformer.h
185
- config = model.config
186
- elif hasattr(model, "h"):
187
- # Direct GPT2Model structure
188
- layers = model.h
189
- config = model.config
190
- transformer = model
191
- else:
192
- raise AdapterError(
193
- code="E202",
194
- message=(
195
- "ADAPTER-STRUCTURE-INVALID: unrecognized HuggingFace GPT-2 model structure"
196
- ),
197
- details={"model_class": model.__class__.__name__},
198
- )
199
-
200
- # Extract basic configuration
201
- n_layers = len(layers)
202
- n_heads = getattr(
203
- config, "n_head", getattr(config, "num_attention_heads", None)
204
- )
205
- hidden_size = getattr(config, "hidden_size", getattr(config, "d_model", None))
206
- vocab_size = getattr(config, "vocab_size", None)
207
-
208
- if n_heads is None or hidden_size is None:
209
- raise AdapterError(
210
- code="E202",
211
- message=(
212
- "ADAPTER-STRUCTURE-INVALID: missing n_heads or hidden_size in config"
213
- ),
214
- details={"model_class": model.__class__.__name__},
215
- )
216
-
217
- # Get device info
218
- try:
219
- device = next(model.parameters()).device
220
- except StopIteration:
221
- device = torch.device("cpu")
222
-
223
- # Calculate total parameters
224
- total_params = sum(p.numel() for p in model.parameters())
225
-
226
- # Get MLP dimensions for each layer
227
- mlp_dims = []
228
- heads_per_layer = []
229
-
230
- for layer_idx in range(n_layers):
231
- layer = layers[layer_idx]
232
-
233
- # For GPT-2, all layers have the same head count
234
- heads_per_layer.append(n_heads)
235
-
236
- # Get MLP intermediate dimension
237
- # HuggingFace GPT-2 uses Conv1D layers where weight shape is (in_features, out_features)
238
- if hasattr(layer.mlp.c_fc, "weight"):
239
- if hasattr(layer.mlp.c_fc, "nf"): # Conv1D layer
240
- mlp_dim = layer.mlp.c_fc.nf # out_features for Conv1D
241
- else:
242
- # Regular linear layer: (out_features, in_features)
243
- mlp_dim = layer.mlp.c_fc.weight.shape[0]
244
- else:
245
- # Fallback to config
246
- mlp_dim = getattr(config, "n_inner", hidden_size * 4)
247
-
248
- mlp_dims.append(mlp_dim)
249
-
250
- # Detect weight tying (lm_head ↔ wte)
251
- tying_map = {}
252
- if hasattr(model, "lm_head") and hasattr(transformer, "wte"):
253
- # Check if the weights are the same tensor (tied)
254
- if model.lm_head.weight is transformer.wte.weight:
255
- tying_map["lm_head.weight"] = "transformer.wte.weight"
256
-
257
- # Build the required description format
258
- description = {
259
- # Required fields for validation gates
260
- "n_layer": n_layers,
261
- "heads_per_layer": heads_per_layer,
262
- "mlp_dims": mlp_dims,
263
- "tying": tying_map, # Use 'tying' instead of 'weight_tying' as per spec
264
- # Additional useful information
265
- "model_type": "gpt2",
266
- "model_class": model.__class__.__name__,
267
- "n_heads": n_heads,
268
- "hidden_size": hidden_size,
269
- "vocab_size": vocab_size,
270
- "total_params": total_params,
271
- "device": str(device),
272
- # HuggingFace specific info
273
- "hf_model_type": getattr(config, "model_type", "gpt2"),
274
- "hf_config_class": config.__class__.__name__
275
- if hasattr(config, "__class__")
276
- else "unknown",
277
- # Architecture details
278
- "architecture": {
279
- "has_lm_head": hasattr(model, "lm_head"),
280
- "has_transformer_wrapper": hasattr(model, "transformer"),
281
- "layer_norm_type": "pre", # GPT-2 uses pre-layer norm
282
- "activation": getattr(config, "activation_function", "gelu_new"),
283
- "positional_encoding": "learned", # GPT-2 uses learned position embeddings
284
- "use_bias": getattr(config, "use_bias", True),
285
- "split_size": getattr(config, "split_size", None),
286
- },
287
- }
288
-
289
- return description
290
-
291
- def _extract_weight_tying_info(self, model: ModuleType | Any) -> dict[str, str]:
292
- """
293
- Extract weight tying relationships from the model.
294
-
295
- Args:
296
- model: The model to analyze
297
-
298
- Returns:
299
- Dictionary mapping tied parameter names to their source parameter names
300
- """
301
- tying_info = {}
302
-
303
- # Check for lm_head ↔ wte tying (most common in GPT-2)
304
- if hasattr(model, "lm_head") and hasattr(model, "transformer"):
305
- if hasattr(model.transformer, "wte"):
306
- if model.lm_head.weight is model.transformer.wte.weight:
307
- tying_info["lm_head.weight"] = "transformer.wte.weight"
308
-
309
- # Could be extended for other tying relationships
310
- return tying_info
311
-
312
- def _restore_weight_tying(
313
- self, model: ModuleType | Any, tied_param: str, source_param: str
314
- ) -> None:
315
- """
316
- Restore a weight tying relationship between parameters.
317
-
318
- Args:
319
- model: The model to modify
320
- tied_param: Name of the parameter that should be tied
321
- source_param: Name of the source parameter to tie to
322
- """
323
- # This is a placeholder for weight tying restoration logic
324
- # In practice, this would need to handle the specific tying relationships
325
- # For now, we just warn about broken tying
326
- print(
327
- f"Warning: Weight tying relationship {tied_param} -> {source_param} may have been broken during restore"
328
- )
329
-
330
- def validate_split_size(self, model: ModuleType | Any) -> bool:
331
- """
332
- Validate that split_size handling is correct for HuggingFace models.
333
-
334
- Args:
335
- model: The model to validate
336
-
337
- Returns:
338
- True if split_size is handled correctly
339
- """
340
- if not hasattr(model, "config"):
341
- return True # No config to validate
342
-
343
- config = model.config
344
- split_size = getattr(config, "split_size", None)
345
-
346
- if split_size is None:
347
- return True # No split_size specified
348
-
349
- # Validate that c_attn layers respect split_size
350
- try:
351
- desc = self.describe(model)
352
- if desc["n_layer"] > 0:
353
- # Check first layer as representative
354
- if hasattr(model, "transformer"):
355
- layer = model.transformer.h[0]
356
- else:
357
- layer = model.h[0]
358
-
359
- c_attn = layer.attn.c_attn
360
- if hasattr(c_attn, "weight"):
361
- # For Conv1D: weight shape is (in_features, out_features)
362
- # out_features should be 3 * hidden_size for combined Q,K,V
363
- expected_out = 3 * desc["hidden_size"]
364
- actual_out = (
365
- c_attn.weight.shape[1]
366
- if hasattr(c_attn, "nf")
367
- else c_attn.weight.shape[0]
368
- )
369
-
370
- return actual_out == expected_out
371
-
372
- return True
373
-
374
- except Exception:
375
- return False
376
-
377
- def get_layer_modules(
378
- self, model: ModuleType | Any, layer_idx: int
379
- ) -> dict[str, ModuleType | Any]:
380
- """
381
- Get the modules for a specific layer (utility method).
382
-
383
- Args:
384
- model: The HuggingFace GPT-2 model
385
- layer_idx: Index of the layer to get modules for
386
-
387
- Returns:
388
- Dictionary mapping module names to modules
389
- """
390
- if hasattr(model, "transformer"):
391
- layer = model.transformer.h[layer_idx]
392
- else:
393
- layer = model.h[layer_idx]
394
-
395
- modules = {
396
- "attn.c_attn": layer.attn.c_attn, # Combined Q,K,V projection
397
- "attn.c_proj": layer.attn.c_proj, # Output projection
398
- "mlp.c_fc": layer.mlp.c_fc, # Feed-forward expansion
399
- "mlp.c_proj": layer.mlp.c_proj, # Feed-forward projection
400
- "ln_1": layer.ln_1, # Layer norm 1
401
- "ln_2": layer.ln_2, # Layer norm 2
402
- }
403
-
404
- return modules