invarlock 0.2.0__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 (132) hide show
  1. invarlock/__init__.py +33 -0
  2. invarlock/__main__.py +10 -0
  3. invarlock/_data/runtime/profiles/ci_cpu.yaml +15 -0
  4. invarlock/_data/runtime/profiles/release.yaml +23 -0
  5. invarlock/_data/runtime/tiers.yaml +76 -0
  6. invarlock/adapters/__init__.py +102 -0
  7. invarlock/adapters/_capabilities.py +45 -0
  8. invarlock/adapters/auto.py +99 -0
  9. invarlock/adapters/base.py +530 -0
  10. invarlock/adapters/base_types.py +85 -0
  11. invarlock/adapters/hf_bert.py +852 -0
  12. invarlock/adapters/hf_gpt2.py +403 -0
  13. invarlock/adapters/hf_llama.py +485 -0
  14. invarlock/adapters/hf_mixin.py +383 -0
  15. invarlock/adapters/hf_onnx.py +112 -0
  16. invarlock/adapters/hf_t5.py +137 -0
  17. invarlock/adapters/py.typed +1 -0
  18. invarlock/assurance/__init__.py +43 -0
  19. invarlock/cli/__init__.py +8 -0
  20. invarlock/cli/__main__.py +8 -0
  21. invarlock/cli/_evidence.py +25 -0
  22. invarlock/cli/_json.py +75 -0
  23. invarlock/cli/adapter_auto.py +162 -0
  24. invarlock/cli/app.py +287 -0
  25. invarlock/cli/commands/__init__.py +26 -0
  26. invarlock/cli/commands/certify.py +403 -0
  27. invarlock/cli/commands/doctor.py +1358 -0
  28. invarlock/cli/commands/explain_gates.py +151 -0
  29. invarlock/cli/commands/export_html.py +100 -0
  30. invarlock/cli/commands/plugins.py +1331 -0
  31. invarlock/cli/commands/report.py +354 -0
  32. invarlock/cli/commands/run.py +4146 -0
  33. invarlock/cli/commands/verify.py +1040 -0
  34. invarlock/cli/config.py +396 -0
  35. invarlock/cli/constants.py +68 -0
  36. invarlock/cli/device.py +92 -0
  37. invarlock/cli/doctor_helpers.py +74 -0
  38. invarlock/cli/errors.py +6 -0
  39. invarlock/cli/overhead_utils.py +60 -0
  40. invarlock/cli/provenance.py +66 -0
  41. invarlock/cli/utils.py +41 -0
  42. invarlock/config.py +56 -0
  43. invarlock/core/__init__.py +62 -0
  44. invarlock/core/abi.py +15 -0
  45. invarlock/core/api.py +274 -0
  46. invarlock/core/auto_tuning.py +317 -0
  47. invarlock/core/bootstrap.py +226 -0
  48. invarlock/core/checkpoint.py +221 -0
  49. invarlock/core/contracts.py +73 -0
  50. invarlock/core/error_utils.py +64 -0
  51. invarlock/core/events.py +298 -0
  52. invarlock/core/exceptions.py +95 -0
  53. invarlock/core/registry.py +481 -0
  54. invarlock/core/retry.py +146 -0
  55. invarlock/core/runner.py +2041 -0
  56. invarlock/core/types.py +154 -0
  57. invarlock/edits/__init__.py +12 -0
  58. invarlock/edits/_edit_utils.py +249 -0
  59. invarlock/edits/_external_utils.py +268 -0
  60. invarlock/edits/noop.py +47 -0
  61. invarlock/edits/py.typed +1 -0
  62. invarlock/edits/quant_rtn.py +801 -0
  63. invarlock/edits/registry.py +166 -0
  64. invarlock/eval/__init__.py +23 -0
  65. invarlock/eval/bench.py +1207 -0
  66. invarlock/eval/bootstrap.py +50 -0
  67. invarlock/eval/data.py +2052 -0
  68. invarlock/eval/metrics.py +2167 -0
  69. invarlock/eval/primary_metric.py +767 -0
  70. invarlock/eval/probes/__init__.py +24 -0
  71. invarlock/eval/probes/fft.py +139 -0
  72. invarlock/eval/probes/mi.py +213 -0
  73. invarlock/eval/probes/post_attention.py +323 -0
  74. invarlock/eval/providers/base.py +67 -0
  75. invarlock/eval/providers/seq2seq.py +111 -0
  76. invarlock/eval/providers/text_lm.py +113 -0
  77. invarlock/eval/providers/vision_text.py +93 -0
  78. invarlock/eval/py.typed +1 -0
  79. invarlock/guards/__init__.py +18 -0
  80. invarlock/guards/_contracts.py +9 -0
  81. invarlock/guards/invariants.py +640 -0
  82. invarlock/guards/policies.py +805 -0
  83. invarlock/guards/py.typed +1 -0
  84. invarlock/guards/rmt.py +2097 -0
  85. invarlock/guards/spectral.py +1419 -0
  86. invarlock/guards/tier_config.py +354 -0
  87. invarlock/guards/variance.py +3298 -0
  88. invarlock/guards_ref/__init__.py +15 -0
  89. invarlock/guards_ref/rmt_ref.py +40 -0
  90. invarlock/guards_ref/spectral_ref.py +135 -0
  91. invarlock/guards_ref/variance_ref.py +60 -0
  92. invarlock/model_profile.py +353 -0
  93. invarlock/model_utils.py +221 -0
  94. invarlock/observability/__init__.py +10 -0
  95. invarlock/observability/alerting.py +535 -0
  96. invarlock/observability/core.py +546 -0
  97. invarlock/observability/exporters.py +565 -0
  98. invarlock/observability/health.py +588 -0
  99. invarlock/observability/metrics.py +457 -0
  100. invarlock/observability/py.typed +1 -0
  101. invarlock/observability/utils.py +553 -0
  102. invarlock/plugins/__init__.py +12 -0
  103. invarlock/plugins/hello_guard.py +33 -0
  104. invarlock/plugins/hf_awq_adapter.py +82 -0
  105. invarlock/plugins/hf_bnb_adapter.py +79 -0
  106. invarlock/plugins/hf_gptq_adapter.py +78 -0
  107. invarlock/plugins/py.typed +1 -0
  108. invarlock/py.typed +1 -0
  109. invarlock/reporting/__init__.py +7 -0
  110. invarlock/reporting/certificate.py +3221 -0
  111. invarlock/reporting/certificate_schema.py +244 -0
  112. invarlock/reporting/dataset_hashing.py +215 -0
  113. invarlock/reporting/guards_analysis.py +948 -0
  114. invarlock/reporting/html.py +32 -0
  115. invarlock/reporting/normalizer.py +235 -0
  116. invarlock/reporting/policy_utils.py +517 -0
  117. invarlock/reporting/primary_metric_utils.py +265 -0
  118. invarlock/reporting/render.py +1442 -0
  119. invarlock/reporting/report.py +903 -0
  120. invarlock/reporting/report_types.py +278 -0
  121. invarlock/reporting/utils.py +175 -0
  122. invarlock/reporting/validate.py +631 -0
  123. invarlock/security.py +176 -0
  124. invarlock/sparsity_utils.py +323 -0
  125. invarlock/utils/__init__.py +150 -0
  126. invarlock/utils/digest.py +45 -0
  127. invarlock-0.2.0.dist-info/METADATA +586 -0
  128. invarlock-0.2.0.dist-info/RECORD +132 -0
  129. invarlock-0.2.0.dist-info/WHEEL +5 -0
  130. invarlock-0.2.0.dist-info/entry_points.txt +20 -0
  131. invarlock-0.2.0.dist-info/licenses/LICENSE +201 -0
  132. invarlock-0.2.0.dist-info/top_level.txt +1 -0
@@ -0,0 +1,66 @@
1
+ """Adapter-lite provenance extraction utilities.
2
+
3
+ Provides a tiny, versioned schema describing the adapter family and the
4
+ underlying library versions. This does not perform any edits; it only reads
5
+ environment and import metadata to annotate reports/certificates.
6
+ """
7
+
8
+ from __future__ import annotations
9
+
10
+ from dataclasses import asdict, dataclass
11
+ from importlib.metadata import version as pkg_version
12
+ from typing import Any
13
+
14
+
15
+ @dataclass
16
+ class AdapterProvenance:
17
+ family: str
18
+ library: str
19
+ version: str | None
20
+ supported: bool
21
+ tested: list[str]
22
+ message: str | None = None
23
+
24
+ def to_dict(self) -> dict[str, Any]:
25
+ return asdict(self)
26
+
27
+
28
+ _FAMILY_MAP: dict[str, tuple[str, str, list[str]]] = {
29
+ # name -> (family, library, tested_versions)
30
+ "hf_gptq": ("gptq", "auto-gptq", []),
31
+ "hf_awq": ("awq", "autoawq", []),
32
+ "hf_bnb": ("bnb", "bitsandbytes", []),
33
+ # ONNX stack (requires extras: invarlock[onnx])
34
+ "hf_onnx": ("onnx", "onnxruntime", []),
35
+ }
36
+
37
+
38
+ def extract_adapter_provenance(adapter_name: str) -> AdapterProvenance:
39
+ name = (adapter_name or "").strip().lower()
40
+ family, library, tested = _FAMILY_MAP.get(name, ("hf", "transformers", []))
41
+
42
+ ver: str | None
43
+ try:
44
+ ver = pkg_version(library)
45
+ supported = True if (not tested or ver in tested) else False
46
+ msg = (
47
+ None
48
+ if supported
49
+ else f"Use Compare & Certify (BYOE); {library} version unsupported (tested: {tested})"
50
+ )
51
+ except Exception: # Package not installed or version unknown
52
+ ver = None
53
+ supported = False
54
+ msg = f"{library} not available; prefer Compare & Certify (BYOE) or install extras."
55
+
56
+ return AdapterProvenance(
57
+ family=family,
58
+ library=library,
59
+ version=ver,
60
+ supported=supported,
61
+ tested=tested,
62
+ message=msg,
63
+ )
64
+
65
+
66
+ __all__ = ["extract_adapter_provenance", "AdapterProvenance"]
invarlock/cli/utils.py ADDED
@@ -0,0 +1,41 @@
1
+ from __future__ import annotations
2
+
3
+ from typing import Any
4
+
5
+
6
+ def coerce_option(value: Any, fallback: Any | None = None) -> Any:
7
+ """Return a Typer option's concrete value.
8
+
9
+ - If `value` looks like a Typer OptionInfo, return its `.default`.
10
+ - Otherwise, return `value` unless it is None, in which case `fallback`.
11
+ This keeps CLI command functions callable both by Typer and directly in tests.
12
+ """
13
+ try:
14
+ # Typer OptionInfo typically exposes a `.default` attribute
15
+ default = getattr(value, "default", None)
16
+ # If it's an OptionInfo-like object, prefer its default
17
+ if default is not None or value.__class__.__name__ == "OptionInfo":
18
+ return default if default is not None else fallback
19
+ except Exception:
20
+ pass
21
+ return value if value is not None else fallback
22
+
23
+
24
+ def coerce_float(value: Any, default: float) -> float:
25
+ """Coerce arbitrary input to float, falling back to `default` on error."""
26
+ try:
27
+ if value is None:
28
+ return default
29
+ return float(value)
30
+ except (TypeError, ValueError):
31
+ return default
32
+
33
+
34
+ def coerce_int(value: Any, default: int) -> int:
35
+ """Coerce arbitrary input to int, falling back to `default` on error."""
36
+ try:
37
+ if value is None:
38
+ return default
39
+ return int(value)
40
+ except (TypeError, ValueError):
41
+ return default
invarlock/config.py ADDED
@@ -0,0 +1,56 @@
1
+ from dataclasses import dataclass
2
+
3
+
4
+ @dataclass
5
+ class Defaults:
6
+ """
7
+ Global default parameters for the InvarLock framework. This dataclass
8
+ centralizes hyperparameters for different lenses, especially for the
9
+ 'auto' calibration modes.
10
+ """
11
+
12
+ # --- Lens 1: FFT Energy ---
13
+ # In 'auto' mode, keep heads that constitute this percentage of cumulative energy.
14
+ fft_energy_keep: float = 0.95
15
+
16
+ # --- Lens 2: Mutual Information ---
17
+ # In 'auto' mode, keep neurons whose cumulative MI constitutes this percentage of the total.
18
+ mi_info_keep: float = 0.90
19
+
20
+ # --- Lens 3: Stability (Koopman) ---
21
+ # Default spectral norm margin when not using auto-calibration.
22
+ koopman_margin: float = 1.05 # A safe default slightly above 1.0
23
+
24
+ # --- Lens 4: RMT Clipping ---
25
+ # Default alpha for upper spectral edge clipping.
26
+ mp_alpha: float = 1.5
27
+ # Default beta for lower spectral edge clipping.
28
+ # mp_beta: float = 0.10
29
+
30
+ # --- Global Compression Target ---
31
+ # Fraction of original trainable parameters to aim for.
32
+ # e.g., 0.70 -> aim for ~70% of the baseline parameter count.
33
+ # This is a high-level objective that the auto-mode translates
34
+ # into concrete head and neuron pruning ratios.
35
+ target_param_keep: float = 0.70
36
+
37
+ # --- General ---
38
+ # Default seed for all deterministic operations.
39
+ seed: int = 42
40
+
41
+ # Variance Equalization minimum gain threshold
42
+ ve_min_gain: float = 0.30
43
+
44
+
45
+ # Create a singleton instance for easy import
46
+ CFG = Defaults()
47
+
48
+
49
+ def get_default_config():
50
+ """
51
+ Get the default configuration for InvarLock.
52
+
53
+ Returns:
54
+ Defaults: A dataclass instance with default configuration values
55
+ """
56
+ return Defaults()
@@ -0,0 +1,62 @@
1
+ """
2
+ InvarLock Core Module
3
+ =================
4
+
5
+ Core torch-independent interfaces and coordination logic.
6
+
7
+ This module provides the foundational abstractions and orchestration
8
+ for the InvarLock framework without requiring heavy dependencies.
9
+ """
10
+
11
+ from .abi import INVARLOCK_CORE_ABI
12
+ from .api import Guard, ModelAdapter, ModelEdit, RunConfig, RunReport
13
+ from .checkpoint import CheckpointManager
14
+ from .events import EventLogger
15
+ from .exceptions import InvarlockError
16
+ from .registry import PluginInfo, get_registry
17
+ from .types import (
18
+ EditInfo,
19
+ EditType,
20
+ GuardResult,
21
+ GuardType,
22
+ LogLevel,
23
+ ModelInfo,
24
+ RunStatus,
25
+ )
26
+
27
+ __all__ = [
28
+ # Core interfaces
29
+ "ModelAdapter",
30
+ "ModelEdit",
31
+ "Guard",
32
+ # ABI contract
33
+ "INVARLOCK_CORE_ABI",
34
+ "RunConfig",
35
+ "RunReport",
36
+ # Exceptions
37
+ "InvarlockError",
38
+ # Types and enums
39
+ "EditType",
40
+ "GuardType",
41
+ "RunStatus",
42
+ "LogLevel",
43
+ "ModelInfo",
44
+ "EditInfo",
45
+ "GuardResult",
46
+ # Registry and discovery
47
+ "get_registry",
48
+ "PluginInfo",
49
+ # Supporting services
50
+ "EventLogger",
51
+ "CheckpointManager",
52
+ ]
53
+
54
+
55
+ # Lazy import CoreRunner to avoid importing heavy dependencies (e.g., NumPy)
56
+ # during lightweight operations such as registry inspection or CLI startup.
57
+ def __getattr__(name: str): # pragma: no cover - simple lazy import shim
58
+ if name == "CoreRunner":
59
+ from .runner import CoreRunner as _CoreRunner
60
+
61
+ return _CoreRunner
62
+ raise AttributeError(name)
invarlock/core/abi.py ADDED
@@ -0,0 +1,15 @@
1
+ """
2
+ Core ABI contract for InvarLock plugins.
3
+
4
+ Third-party plugins may declare a matching `INVARLOCK_CORE_ABI` attribute to signal
5
+ compatibility with this release line. Minor bumps may introduce breaking changes
6
+ in plugin contracts; match exactly for stability.
7
+ """
8
+
9
+ from __future__ import annotations
10
+
11
+ # Plugin ABI for the core interfaces used by adapters/edits/guards.
12
+ # Increment when the plugin-facing contracts change.
13
+ INVARLOCK_CORE_ABI = "0.1"
14
+
15
+ __all__ = ["INVARLOCK_CORE_ABI"]
invarlock/core/api.py ADDED
@@ -0,0 +1,274 @@
1
+ """
2
+ InvarLock Core API
3
+ ==============
4
+
5
+ Core interfaces and data structures - torch-free abstractions.
6
+
7
+ This module defines the fundamental abstractions used throughout InvarLock:
8
+ - ModelAdapter: Model-specific operations interface
9
+ - ModelEdit: Edit operation interface
10
+ - Guard: Safety validation interface
11
+ - RunConfig: Pipeline configuration
12
+ - RunReport: Execution results
13
+ """
14
+
15
+ from __future__ import annotations
16
+
17
+ from abc import ABC, abstractmethod
18
+ from dataclasses import dataclass, field
19
+ from pathlib import Path
20
+ from typing import Any
21
+
22
+
23
+ class ModelAdapter(ABC):
24
+ """
25
+ Abstract interface for model-specific operations.
26
+
27
+ Adapters provide model-agnostic access to different architectures
28
+ (HuggingFace, custom models, etc.) without requiring torch imports
29
+ in the core API.
30
+ """
31
+
32
+ name: str
33
+
34
+ @abstractmethod
35
+ def can_handle(self, model: Any) -> bool:
36
+ """Check if this adapter can handle the given model."""
37
+ pass
38
+
39
+ @abstractmethod
40
+ def describe(self, model: Any) -> dict[str, Any]:
41
+ """
42
+ Get model structure description.
43
+
44
+ Returns:
45
+ Dict with keys: n_layer, heads_per_layer, mlp_dims, tying
46
+ """
47
+ pass
48
+
49
+ @abstractmethod
50
+ def snapshot(self, model: Any) -> bytes:
51
+ """Create serialized snapshot of model state."""
52
+ pass
53
+
54
+ @abstractmethod
55
+ def restore(self, model: Any, blob: bytes) -> None:
56
+ """Restore model state from snapshot."""
57
+ pass
58
+
59
+
60
+ class ModelEdit(ABC):
61
+ """
62
+ Abstract interface for model editing operations.
63
+
64
+ Edits modify model parameters/structure while maintaining
65
+ the model's interface and basic functionality.
66
+ """
67
+
68
+ name: str
69
+
70
+ @abstractmethod
71
+ def can_edit(self, model_desc: dict[str, Any]) -> bool:
72
+ """Check if this edit can be applied to the described model."""
73
+ pass
74
+
75
+ @abstractmethod
76
+ def apply(self, model: Any, adapter: ModelAdapter, **kwargs) -> dict[str, Any]:
77
+ """
78
+ Apply the edit to the model.
79
+
80
+ Args:
81
+ model: The model to edit
82
+ adapter: Adapter for model-specific operations
83
+ **kwargs: Edit-specific parameters
84
+
85
+ Returns:
86
+ Dict with edit metadata and statistics
87
+ """
88
+ pass
89
+
90
+
91
+ class Guard(ABC):
92
+ """
93
+ Abstract interface for safety guards.
94
+
95
+ Guards validate model state and behavior to ensure
96
+ edits don't cause unacceptable degradation.
97
+ """
98
+
99
+ name: str
100
+
101
+ @abstractmethod
102
+ def validate(
103
+ self, model: Any, adapter: ModelAdapter, context: dict[str, Any]
104
+ ) -> dict[str, Any]:
105
+ """
106
+ Validate model state/behavior.
107
+
108
+ Args:
109
+ model: The model to validate
110
+ adapter: Adapter for model operations
111
+ context: Validation context (baseline metrics, etc.)
112
+
113
+ Returns:
114
+ Dict with validation results and status
115
+ """
116
+ pass
117
+
118
+
119
+ class GuardChain:
120
+ """
121
+ Manages a chain of guards with policy-based execution.
122
+
123
+ Provides lifecycle management (prepare, before_edit, after_edit, finalize)
124
+ and aggregates results across multiple guards.
125
+ """
126
+
127
+ def __init__(self, guards: list[Guard], policy: Any | None = None):
128
+ """
129
+ Initialize guard chain.
130
+
131
+ Args:
132
+ guards: List of guard instances
133
+ policy: Policy configuration for guard execution
134
+ """
135
+ self.guards = guards
136
+ self.policy = policy
137
+
138
+ def prepare_all(
139
+ self,
140
+ model: Any,
141
+ adapter: ModelAdapter,
142
+ calib: Any,
143
+ policy_config: dict[str, Any],
144
+ ) -> dict[str, Any]:
145
+ """Prepare all guards."""
146
+ results = {}
147
+ for guard in self.guards:
148
+ if hasattr(guard, "prepare"):
149
+ results[guard.name] = guard.prepare(
150
+ model, adapter, calib, policy_config
151
+ )
152
+ else:
153
+ results[guard.name] = {"ready": True}
154
+ return results
155
+
156
+ def before_edit_all(self, model: Any) -> list[Any]:
157
+ """Execute before_edit on all guards."""
158
+ results = []
159
+ for guard in self.guards:
160
+ if hasattr(guard, "before_edit"):
161
+ result = guard.before_edit(model)
162
+ if result is not None:
163
+ results.append(result)
164
+ return results
165
+
166
+ def after_edit_all(self, model: Any) -> list[Any]:
167
+ """Execute after_edit on all guards."""
168
+ results = []
169
+ for guard in self.guards:
170
+ if hasattr(guard, "after_edit"):
171
+ result = guard.after_edit(model)
172
+ if result is not None:
173
+ results.append(result)
174
+ return results
175
+
176
+ def finalize_all(self, model: Any) -> list[Any]:
177
+ """Finalize all guards and return outcomes."""
178
+ results = []
179
+ for guard in self.guards:
180
+ if hasattr(guard, "finalize"):
181
+ result = guard.finalize(model)
182
+ results.append(result)
183
+ return results
184
+
185
+ def all_passed(self, outcomes: list[Any]) -> bool:
186
+ """Check if all guard outcomes passed."""
187
+ for outcome in outcomes:
188
+ if hasattr(outcome, "passed") and not outcome.passed:
189
+ return False
190
+ return True
191
+
192
+ def get_worst_action(self, outcomes: list[Any]) -> str:
193
+ """Get the worst action from all outcomes."""
194
+ actions = []
195
+ for outcome in outcomes:
196
+ if hasattr(outcome, "action"):
197
+ actions.append(outcome.action)
198
+
199
+ # Import from types to avoid circular dependency
200
+ from .types import get_worst_action
201
+
202
+ return get_worst_action(actions)
203
+
204
+
205
+ # Type alias for calibration data
206
+ CalibrationData = Any
207
+
208
+
209
+ @dataclass
210
+ class RunConfig:
211
+ """
212
+ Configuration for a InvarLock pipeline run.
213
+
214
+ Contains all parameters needed to execute the full pipeline:
215
+ prepare → edit → guards → eval → finalize/rollback.
216
+ """
217
+
218
+ # Device configuration
219
+ device: str = "auto"
220
+
221
+ # Safety thresholds
222
+ max_pm_ratio: float = 1.5
223
+ spike_threshold: float = 2.0 # Catastrophic spike threshold for immediate rollback
224
+
225
+ # Output configuration
226
+ event_path: Path | None = None
227
+ checkpoint_interval: int = 0 # 0 = disabled
228
+
229
+ # Execution flags
230
+ dry_run: bool = False
231
+ verbose: bool = False
232
+
233
+ # Context for guards/eval
234
+ context: dict[str, Any] = field(default_factory=dict)
235
+
236
+
237
+ @dataclass
238
+ class RunReport:
239
+ """
240
+ Results from a InvarLock pipeline execution.
241
+
242
+ Contains comprehensive information about what was executed
243
+ and the outcomes, suitable for analysis and certification.
244
+ """
245
+
246
+ # Execution metadata
247
+ meta: dict[str, Any] = field(default_factory=dict)
248
+
249
+ # Edit information
250
+ edit: dict[str, Any] = field(default_factory=dict)
251
+
252
+ # Guard validation results
253
+ guards: dict[str, dict[str, Any]] = field(default_factory=dict)
254
+
255
+ # Evaluation metrics
256
+ metrics: dict[str, Any] = field(default_factory=dict)
257
+
258
+ # Captured evaluation windows and auxiliary data
259
+ evaluation_windows: dict[str, Any] = field(default_factory=dict)
260
+
261
+ # Execution status
262
+ status: str = "pending" # pending, success, failed, rollback
263
+
264
+ # Error information (if any)
265
+ error: str | None = None
266
+
267
+ # Additional context
268
+ context: dict[str, Any] = field(default_factory=dict)
269
+
270
+
271
+ # Type aliases for common patterns
272
+ ModelType = Any # Actual model type (torch.nn.Module, etc.)
273
+ DeviceType = str | Any # Device specification
274
+ MetricsDict = dict[str, float | int | str | bool]