diffract-core 0.2.1__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.
- diffract/__init__.py +52 -0
- diffract/configs/fast_speed_without_disk.ini +48 -0
- diffract/configs/hybrid.ini +74 -0
- diffract/configs/sqlite.ini +62 -0
- diffract/containers.py +405 -0
- diffract/core/__init__.py +23 -0
- diffract/core/cache/__init__.py +24 -0
- diffract/core/cache/containers.py +93 -0
- diffract/core/cache/interface.py +117 -0
- diffract/core/cache/redis_manager.py +464 -0
- diffract/core/cache/simple_manager.py +478 -0
- diffract/core/compute/__init__.py +46 -0
- diffract/core/compute/config.py +48 -0
- diffract/core/compute/containers.py +81 -0
- diffract/core/compute/decorator.py +125 -0
- diffract/core/compute/exceptions.py +31 -0
- diffract/core/compute/execution/__init__.py +14 -0
- diffract/core/compute/execution/_types.py +70 -0
- diffract/core/compute/execution/aggregation.py +74 -0
- diffract/core/compute/execution/aggregation_runner.py +362 -0
- diffract/core/compute/execution/enums.py +38 -0
- diffract/core/compute/execution/executor.py +125 -0
- diffract/core/compute/execution/parameter_runner.py +125 -0
- diffract/core/compute/execution/restrictions.py +61 -0
- diffract/core/compute/execution/strategy.py +118 -0
- diffract/core/compute/execution/utils.py +112 -0
- diffract/core/compute/extensions/__init__.py +0 -0
- diffract/core/compute/extensions/power_law.py +1051 -0
- diffract/core/compute/extensions/rmt.py +150 -0
- diffract/core/compute/extensions/utils.py +36 -0
- diffract/core/compute/field_signature.py +183 -0
- diffract/core/compute/kernels/__init__.py +48 -0
- diffract/core/compute/kernels/alignment.py +106 -0
- diffract/core/compute/kernels/heavy_tailed.py +394 -0
- diffract/core/compute/kernels/marchenko_pastur.py +99 -0
- diffract/core/compute/kernels/mat_decomposition.py +101 -0
- diffract/core/compute/kernels/mat_properties.py +53 -0
- diffract/core/compute/kernels/model_quality.py +27 -0
- diffract/core/compute/kernels/norms.py +88 -0
- diffract/core/compute/kernels/ranks.py +35 -0
- diffract/core/compute/kernels/tracy_widom.py +36 -0
- diffract/core/compute/metadata.py +67 -0
- diffract/core/compute/registry.py +485 -0
- diffract/core/constants.py +147 -0
- diffract/core/data/__init__.py +32 -0
- diffract/core/data/interface.py +304 -0
- diffract/core/data/metadata/__init__.py +15 -0
- diffract/core/data/metadata/containers.py +60 -0
- diffract/core/data/metadata/interface.py +208 -0
- diffract/core/data/metadata/sqlite_index.py +604 -0
- diffract/core/data/nn/__init__.py +43 -0
- diffract/core/data/nn/aggregates/__init__.py +35 -0
- diffract/core/data/nn/aggregates/metadata.py +96 -0
- diffract/core/data/nn/aggregates/proxy.py +57 -0
- diffract/core/data/nn/aggregates/repository.py +87 -0
- diffract/core/data/nn/aggregates/view.py +307 -0
- diffract/core/data/nn/containers.py +86 -0
- diffract/core/data/nn/extractors/__init__.py +49 -0
- diffract/core/data/nn/extractors/base.py +300 -0
- diffract/core/data/nn/extractors/factory.py +234 -0
- diffract/core/data/nn/extractors/flax.py +112 -0
- diffract/core/data/nn/extractors/handlers/__init__.py +39 -0
- diffract/core/data/nn/extractors/handlers/base.py +110 -0
- diffract/core/data/nn/extractors/handlers/flax_handlers.py +71 -0
- diffract/core/data/nn/extractors/handlers/numpy_handlers.py +63 -0
- diffract/core/data/nn/extractors/handlers/onnx_handlers.py +67 -0
- diffract/core/data/nn/extractors/handlers/tensorflow_handlers.py +82 -0
- diffract/core/data/nn/extractors/handlers/torch_handlers.py +124 -0
- diffract/core/data/nn/extractors/interface.py +56 -0
- diffract/core/data/nn/extractors/numpy.py +94 -0
- diffract/core/data/nn/extractors/onnx.py +108 -0
- diffract/core/data/nn/extractors/tensorflow.py +99 -0
- diffract/core/data/nn/extractors/torch.py +204 -0
- diffract/core/data/nn/params/__init__.py +27 -0
- diffract/core/data/nn/params/interface.py +402 -0
- diffract/core/data/nn/params/metadata.py +110 -0
- diffract/core/data/nn/params/proxy.py +93 -0
- diffract/core/data/nn/params/repository.py +45 -0
- diffract/core/data/nn/params/schema.py +82 -0
- diffract/core/data/nn/params/view.py +393 -0
- diffract/core/data/proxy.py +246 -0
- diffract/core/data/repository.py +227 -0
- diffract/core/data/utils.py +33 -0
- diffract/core/data/view.py +389 -0
- diffract/core/export/__init__.py +27 -0
- diffract/core/export/containers.py +47 -0
- diffract/core/export/exporters.py +191 -0
- diffract/core/export/formatters/__init__.py +23 -0
- diffract/core/export/formatters/base.py +68 -0
- diffract/core/export/formatters/dict_formatter.py +38 -0
- diffract/core/export/formatters/json_formatter.py +58 -0
- diffract/core/export/formatters/list_formatter.py +41 -0
- diffract/core/export/formatters/pandas_formatter.py +86 -0
- diffract/core/export/formatters/polars_formatter.py +86 -0
- diffract/core/export/formatters/registry.py +84 -0
- diffract/core/export/interface.py +105 -0
- diffract/core/parallel/__init__.py +21 -0
- diffract/core/parallel/containers.py +57 -0
- diffract/core/parallel/execution.py +91 -0
- diffract/core/parallel/runtime.py +91 -0
- diffract/core/storage/__init__.py +35 -0
- diffract/core/storage/base_manager.py +330 -0
- diffract/core/storage/containers.py +122 -0
- diffract/core/storage/hdf5_manager.py +856 -0
- diffract/core/storage/hybrid_manager.py +218 -0
- diffract/core/storage/interface.py +222 -0
- diffract/core/storage/metadata.py +89 -0
- diffract/core/storage/ram_manager.py +159 -0
- diffract/core/storage/sqlite_manager.py +1062 -0
- diffract/core/storage/zarr_manager.py +992 -0
- diffract/core/utils/__init__.py +45 -0
- diffract/core/utils/build.py +46 -0
- diffract/core/utils/exceptions.py +11 -0
- diffract/core/utils/hashing.py +90 -0
- diffract/core/utils/imports.py +292 -0
- diffract/core/utils/math.py +15 -0
- diffract/py.typed +0 -0
- diffract/session/__init__.py +24 -0
- diffract/session/errors.py +17 -0
- diffract/session/field_cache.py +216 -0
- diffract/session/namespaces/__init__.py +15 -0
- diffract/session/namespaces/compute/__init__.py +219 -0
- diffract/session/namespaces/models/__init__.py +282 -0
- diffract/session/namespaces/models/parameters/__init__.py +133 -0
- diffract/session/namespaces/models/parameters/meta_patcher.py +167 -0
- diffract/session/namespaces/results/__init__.py +378 -0
- diffract/session/namespaces/results/eraser.py +129 -0
- diffract/session/namespaces/results/injester.py +249 -0
- diffract/session/namespaces/utils/__init__.py +141 -0
- diffract/session/namespaces/utils/merger.py +295 -0
- diffract/session/namespaces/viz/__init__.py +91 -0
- diffract/session/namespaces/viz/_utils.py +14 -0
- diffract/session/namespaces/viz/box.py +207 -0
- diffract/session/namespaces/viz/grid.py +160 -0
- diffract/session/namespaces/viz/heatmap.py +164 -0
- diffract/session/namespaces/viz/scatter.py +202 -0
- diffract/session/namespaces/viz/sparkline.py +270 -0
- diffract/session/namespaces/viz/violin.py +212 -0
- diffract/session/session.py +260 -0
- diffract/session/utils.py +81 -0
- diffract/viz/__init__.py +42 -0
- diffract/viz/data/__init__.py +34 -0
- diffract/viz/data/detection.py +57 -0
- diffract/viz/data/extraction.py +117 -0
- diffract/viz/data/filtering.py +57 -0
- diffract/viz/data/ordering.py +129 -0
- diffract/viz/data/provider.py +99 -0
- diffract/viz/data/types.py +98 -0
- diffract/viz/plots/__init__.py +37 -0
- diffract/viz/plots/base/__init__.py +20 -0
- diffract/viz/plots/base/axis.py +219 -0
- diffract/viz/plots/base/coloraxis.py +133 -0
- diffract/viz/plots/base/configurator.py +18 -0
- diffract/viz/plots/base/jitter.py +368 -0
- diffract/viz/plots/base/line.py +197 -0
- diffract/viz/plots/base/marker.py +278 -0
- diffract/viz/plots/base/overlay.py +14 -0
- diffract/viz/plots/base/plot.py +110 -0
- diffract/viz/plots/base/update.py +50 -0
- diffract/viz/plots/boxplot.py +283 -0
- diffract/viz/plots/cluster.py +374 -0
- diffract/viz/plots/heatmap.py +168 -0
- diffract/viz/plots/scatter.py +233 -0
- diffract/viz/plots/sparkline.py +405 -0
- diffract/viz/plots/subplots/__init__.py +32 -0
- diffract/viz/plots/subplots/coloraxis.py +339 -0
- diffract/viz/plots/subplots/factory.py +713 -0
- diffract/viz/plots/subplots/grid.py +214 -0
- diffract/viz/plots/subplots/layout.py +370 -0
- diffract/viz/plots/subplots/spec.py +39 -0
- diffract/viz/plots/violin.py +298 -0
- diffract/viz/renderer.py +513 -0
- diffract/viz/styling/__init__.py +78 -0
- diffract/viz/styling/palettes/__init__.py +20 -0
- diffract/viz/styling/palettes/bundle.py +16 -0
- diffract/viz/styling/palettes/color.py +83 -0
- diffract/viz/styling/palettes/dashes.py +27 -0
- diffract/viz/styling/palettes/symbols.py +42 -0
- diffract/viz/styling/resolvers/__init__.py +11 -0
- diffract/viz/styling/resolvers/categorical.py +68 -0
- diffract/viz/styling/resolvers/color.py +77 -0
- diffract/viz/styling/resolvers/numeric.py +108 -0
- diffract/viz/styling/sources.py +27 -0
- diffract/viz/styling/theme/__init__.py +29 -0
- diffract/viz/styling/theme/applier.py +114 -0
- diffract/viz/styling/theme/components.py +66 -0
- diffract/viz/styling/theme/presets.py +58 -0
- diffract/viz/styling/theme/theme.py +36 -0
- diffract_core-0.2.1.dist-info/METADATA +530 -0
- diffract_core-0.2.1.dist-info/RECORD +193 -0
- diffract_core-0.2.1.dist-info/WHEEL +4 -0
- diffract_core-0.2.1.dist-info/licenses/LICENSE +201 -0
- diffract_core-0.2.1.dist-info/licenses/NOTICE +2 -0
|
@@ -0,0 +1,282 @@
|
|
|
1
|
+
"""Model management namespace for Session."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
import logging
|
|
6
|
+
from collections.abc import Callable
|
|
7
|
+
from dataclasses import replace
|
|
8
|
+
from typing import TYPE_CHECKING, Any
|
|
9
|
+
|
|
10
|
+
from dependency_injector.wiring import Provide, inject
|
|
11
|
+
|
|
12
|
+
from diffract.core.constants import TABLE_AGGREGATES, TABLE_PARAMETERS
|
|
13
|
+
from diffract.core.data.nn.extractors.base import ExtractorOverrides, ParameterOverrides
|
|
14
|
+
from diffract.core.utils.exceptions import format_exception_message
|
|
15
|
+
from diffract.session.errors import (
|
|
16
|
+
ModelAlreadyExistsError,
|
|
17
|
+
ModelNotFoundError,
|
|
18
|
+
SessionError,
|
|
19
|
+
)
|
|
20
|
+
from diffract.session.session import Session, SessionContext
|
|
21
|
+
|
|
22
|
+
from .parameters import ParametersNamespace
|
|
23
|
+
|
|
24
|
+
if TYPE_CHECKING:
|
|
25
|
+
from collections.abc import Iterable
|
|
26
|
+
|
|
27
|
+
from diffract.core.data.nn.aggregates.repository import AggregateRepository
|
|
28
|
+
from diffract.core.data.nn.extractors.handlers.base import ParameterHandler
|
|
29
|
+
from diffract.core.data.nn.extractors.interface import IParameterExtractor
|
|
30
|
+
from diffract.core.data.nn.params.interface import IParameterRepository
|
|
31
|
+
|
|
32
|
+
|
|
33
|
+
logger = logging.getLogger(__name__)
|
|
34
|
+
|
|
35
|
+
__all__ = [
|
|
36
|
+
"ModelsNamespace",
|
|
37
|
+
"ParametersNamespace",
|
|
38
|
+
]
|
|
39
|
+
|
|
40
|
+
|
|
41
|
+
class ModelsNamespace:
|
|
42
|
+
"""Model management API."""
|
|
43
|
+
|
|
44
|
+
@inject
|
|
45
|
+
def __init__(
|
|
46
|
+
self,
|
|
47
|
+
session_or_context: Session | SessionContext,
|
|
48
|
+
parameter_repository: IParameterRepository = Provide["nn.parameter_repository"],
|
|
49
|
+
aggregate_repository: AggregateRepository = Provide["nn.aggregate_repository"],
|
|
50
|
+
extractor_factory: Callable[..., IParameterExtractor] = Provide[
|
|
51
|
+
"nn.extractor_factory.provider"
|
|
52
|
+
],
|
|
53
|
+
) -> None:
|
|
54
|
+
self.__session_or_context = session_or_context
|
|
55
|
+
self.__param_repo = parameter_repository
|
|
56
|
+
self.__agg_repo = aggregate_repository
|
|
57
|
+
self.__extractor_factory = extractor_factory
|
|
58
|
+
|
|
59
|
+
self.parameters = ParametersNamespace(session_or_context)
|
|
60
|
+
|
|
61
|
+
def add(
|
|
62
|
+
self,
|
|
63
|
+
model: Any,
|
|
64
|
+
model_id: str | None = None,
|
|
65
|
+
parameter_overrides: dict[str, ParameterOverrides] | None = None,
|
|
66
|
+
custom_handlers: Iterable[ParameterHandler] | None = None,
|
|
67
|
+
) -> None:
|
|
68
|
+
"""Add a neural network model to the session.
|
|
69
|
+
|
|
70
|
+
Extracts parameters from the provided model and stores them for analysis.
|
|
71
|
+
Parameters are automatically persisted and can be retrieved in future sessions.
|
|
72
|
+
|
|
73
|
+
Args:
|
|
74
|
+
model: Neural network model object (PyTorch, TensorFlow, etc.)
|
|
75
|
+
or a dict mapping parameter names to NumPy arrays.
|
|
76
|
+
model_id: Unique identifier for the model. Auto-generated if None.
|
|
77
|
+
parameter_overrides: Custom parameter extraction overrides by name.
|
|
78
|
+
custom_handlers: Additional parameter handlers for custom parameter types.
|
|
79
|
+
|
|
80
|
+
Raises:
|
|
81
|
+
ModelAlreadyExistsError: If model_id already exists in the session.
|
|
82
|
+
SessionError: If parameter extraction fails or yields no parameters.
|
|
83
|
+
"""
|
|
84
|
+
with self.__session_or_context:
|
|
85
|
+
try:
|
|
86
|
+
extractor = self.__extractor_factory(
|
|
87
|
+
model=model,
|
|
88
|
+
overrides=self.__build_extractor_overrides(
|
|
89
|
+
model_id, parameter_overrides
|
|
90
|
+
),
|
|
91
|
+
custom_handlers=custom_handlers,
|
|
92
|
+
)
|
|
93
|
+
|
|
94
|
+
previous_len = len(self.__param_repo)
|
|
95
|
+
extractor.extract_parameters(self.__param_repo)
|
|
96
|
+
new_len = len(self.__param_repo)
|
|
97
|
+
|
|
98
|
+
self.__session_or_context._field_cache.invalidate()
|
|
99
|
+
|
|
100
|
+
except SessionError:
|
|
101
|
+
raise
|
|
102
|
+
except Exception as e:
|
|
103
|
+
msg = f"Failed to add model: {format_exception_message(e)}"
|
|
104
|
+
raise SessionError(msg) from e
|
|
105
|
+
|
|
106
|
+
if new_len == previous_len:
|
|
107
|
+
msg = (
|
|
108
|
+
f"Model '{model_id or type(model).__name__}' produced no "
|
|
109
|
+
"parameters. Unsupported parameter types are skipped during "
|
|
110
|
+
"extraction; supported inputs are PyTorch, TensorFlow, "
|
|
111
|
+
"Flax, and ONNX models (install the matching extra) and "
|
|
112
|
+
"dicts of 2D floating-point NumPy arrays."
|
|
113
|
+
)
|
|
114
|
+
raise SessionError(msg)
|
|
115
|
+
|
|
116
|
+
logger.info(
|
|
117
|
+
"Added model '%s' with %d parameters",
|
|
118
|
+
model_id or "<auto-generated>",
|
|
119
|
+
new_len - previous_len,
|
|
120
|
+
)
|
|
121
|
+
|
|
122
|
+
def rename(
|
|
123
|
+
self,
|
|
124
|
+
model_id: str,
|
|
125
|
+
new_model_id: str,
|
|
126
|
+
) -> None:
|
|
127
|
+
"""Rename a model in the session.
|
|
128
|
+
|
|
129
|
+
Args:
|
|
130
|
+
model_id: ID of the model to rename.
|
|
131
|
+
new_model_id: New ID for the model.
|
|
132
|
+
|
|
133
|
+
Raises:
|
|
134
|
+
ModelNotFoundError: If model_id does not exist.
|
|
135
|
+
ModelAlreadyExistsError: If new_model_id already exists.
|
|
136
|
+
"""
|
|
137
|
+
with self.__session_or_context:
|
|
138
|
+
params = self.__param_repo.create_view()
|
|
139
|
+
model_params = params.filter_by_model_id(model_id)
|
|
140
|
+
|
|
141
|
+
if not model_params:
|
|
142
|
+
msg = f"Model '{model_id}' not found"
|
|
143
|
+
raise ModelNotFoundError(msg)
|
|
144
|
+
|
|
145
|
+
new_model_params = params.filter_by_model_id(new_model_id)
|
|
146
|
+
if new_model_params:
|
|
147
|
+
msg = f"Model '{new_model_id}' already exists"
|
|
148
|
+
raise ModelAlreadyExistsError(msg)
|
|
149
|
+
|
|
150
|
+
param_uids = model_params.list_uids()
|
|
151
|
+
|
|
152
|
+
aggs = self.__agg_repo.create_view()
|
|
153
|
+
model_aggs = aggs.filter_by_context_models(model_id)
|
|
154
|
+
agg_uids = model_aggs.list_uids()
|
|
155
|
+
|
|
156
|
+
with self.__param_repo:
|
|
157
|
+
for uid in param_uids:
|
|
158
|
+
param = params[uid]
|
|
159
|
+
old_meta = param.meta
|
|
160
|
+
new_meta = replace(old_meta, model_id=new_model_id)
|
|
161
|
+
|
|
162
|
+
object.__setattr__(param, "meta", new_meta)
|
|
163
|
+
|
|
164
|
+
meta_dict = new_meta.to_dict()
|
|
165
|
+
meta_dict.pop("uid")
|
|
166
|
+
self.__param_repo.metadata_index.upsert(
|
|
167
|
+
TABLE_PARAMETERS,
|
|
168
|
+
uid=uid,
|
|
169
|
+
**meta_dict,
|
|
170
|
+
)
|
|
171
|
+
|
|
172
|
+
with self.__agg_repo:
|
|
173
|
+
for uid in agg_uids:
|
|
174
|
+
agg = aggs[uid]
|
|
175
|
+
old_meta = agg.meta
|
|
176
|
+
new_context_models = tuple(
|
|
177
|
+
new_model_id if cm == model_id else cm
|
|
178
|
+
for cm in old_meta.context_models
|
|
179
|
+
)
|
|
180
|
+
new_meta = replace(old_meta, context_models=new_context_models)
|
|
181
|
+
|
|
182
|
+
object.__setattr__(agg, "meta", new_meta)
|
|
183
|
+
|
|
184
|
+
meta_dict = new_meta.to_dict()
|
|
185
|
+
meta_dict.pop("uid")
|
|
186
|
+
self.__agg_repo.metadata_index.upsert(
|
|
187
|
+
TABLE_AGGREGATES,
|
|
188
|
+
uid=uid,
|
|
189
|
+
**meta_dict,
|
|
190
|
+
)
|
|
191
|
+
|
|
192
|
+
self.__session_or_context._field_cache.invalidate()
|
|
193
|
+
|
|
194
|
+
logger.info("Renamed model '%s' to '%s'", model_id, new_model_id)
|
|
195
|
+
|
|
196
|
+
def erase(
|
|
197
|
+
self,
|
|
198
|
+
*model_ids: str,
|
|
199
|
+
erase_all: bool = False,
|
|
200
|
+
) -> None:
|
|
201
|
+
"""Erase all parameters and results for specified models.
|
|
202
|
+
|
|
203
|
+
Removes model parameters from both memory and persistent storage.
|
|
204
|
+
Use with caution as this operation cannot be undone.
|
|
205
|
+
|
|
206
|
+
Args:
|
|
207
|
+
*model_ids: Model IDs to erase. Required if erase_all is False.
|
|
208
|
+
erase_all: If True, erases all models. model_ids must be empty.
|
|
209
|
+
|
|
210
|
+
Raises:
|
|
211
|
+
ValueError: If neither model_ids nor erase_all is specified,
|
|
212
|
+
or if both are specified.
|
|
213
|
+
ModelNotFoundError: If any specified model ID is not found.
|
|
214
|
+
"""
|
|
215
|
+
with self.__session_or_context:
|
|
216
|
+
if (not model_ids) ^ erase_all:
|
|
217
|
+
if not model_ids:
|
|
218
|
+
msg = "No model_ids provided and erase_all=False"
|
|
219
|
+
else:
|
|
220
|
+
msg = "Cannot specify both model_ids and erase_all=True"
|
|
221
|
+
raise ValueError(msg)
|
|
222
|
+
|
|
223
|
+
if erase_all:
|
|
224
|
+
model_ids = tuple(self.list())
|
|
225
|
+
|
|
226
|
+
params = self.__param_repo.create_view()
|
|
227
|
+
aggs = self.__agg_repo.create_view()
|
|
228
|
+
|
|
229
|
+
erased_uids: list[str] = []
|
|
230
|
+
for model_id in model_ids:
|
|
231
|
+
model_params = params.filter_by_model_id(model_id)
|
|
232
|
+
|
|
233
|
+
if not model_params:
|
|
234
|
+
msg = f"Model '{model_id}' not found"
|
|
235
|
+
raise ModelNotFoundError(msg)
|
|
236
|
+
|
|
237
|
+
model_aggs = aggs.filter_by_context_models(model_id)
|
|
238
|
+
|
|
239
|
+
erased_uids.extend(model_params.list_uids())
|
|
240
|
+
model_params.clear(erase=True)
|
|
241
|
+
if model_aggs:
|
|
242
|
+
model_aggs.clear(erase=True)
|
|
243
|
+
|
|
244
|
+
logger.info("Erased model '%s'", model_id)
|
|
245
|
+
|
|
246
|
+
self.__session_or_context._field_cache.remove_uids(erased_uids)
|
|
247
|
+
|
|
248
|
+
def list(self) -> list[str]:
|
|
249
|
+
"""List all model IDs currently in the session.
|
|
250
|
+
|
|
251
|
+
Returns:
|
|
252
|
+
Sorted list of unique model IDs present in the session.
|
|
253
|
+
"""
|
|
254
|
+
with self.__session_or_context:
|
|
255
|
+
if isinstance(self.__session_or_context, Session):
|
|
256
|
+
params = self.__param_repo.create_view()
|
|
257
|
+
else:
|
|
258
|
+
params = self.__session_or_context._param_filter_context
|
|
259
|
+
model_ids = {param.meta.model_id for param in params}
|
|
260
|
+
return sorted(model_ids)
|
|
261
|
+
|
|
262
|
+
def __build_extractor_overrides(
|
|
263
|
+
self,
|
|
264
|
+
model_id: str | None,
|
|
265
|
+
parameter_overrides: dict[str, ParameterOverrides] | None,
|
|
266
|
+
) -> ExtractorOverrides:
|
|
267
|
+
extractor_overrides_kwargs: dict[str, Any] = {}
|
|
268
|
+
|
|
269
|
+
if model_id:
|
|
270
|
+
params = self.__param_repo.create_view()
|
|
271
|
+
model_params = params.filter_by_model_id(model_id)
|
|
272
|
+
|
|
273
|
+
if model_params:
|
|
274
|
+
msg = f"Model with uid {model_id} already exists"
|
|
275
|
+
raise ModelAlreadyExistsError(msg)
|
|
276
|
+
|
|
277
|
+
extractor_overrides_kwargs["model_id"] = model_id
|
|
278
|
+
|
|
279
|
+
if parameter_overrides:
|
|
280
|
+
extractor_overrides_kwargs["parameter_overrides"] = parameter_overrides
|
|
281
|
+
|
|
282
|
+
return ExtractorOverrides(**extractor_overrides_kwargs)
|
|
@@ -0,0 +1,133 @@
|
|
|
1
|
+
"""Parameter management namespace for Session models."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
import logging
|
|
6
|
+
from collections.abc import Callable
|
|
7
|
+
from typing import TYPE_CHECKING, Any
|
|
8
|
+
|
|
9
|
+
from dependency_injector.wiring import Provide, inject
|
|
10
|
+
|
|
11
|
+
from diffract.core.parallel import ParallelContext
|
|
12
|
+
from diffract.session.errors import SessionError
|
|
13
|
+
from diffract.session.session import Session, SessionContext
|
|
14
|
+
|
|
15
|
+
from .meta_patcher import MetadataPatcher, MetadataPatchError
|
|
16
|
+
|
|
17
|
+
if TYPE_CHECKING:
|
|
18
|
+
from diffract.core.data.nn.params.interface import IParameterRepository
|
|
19
|
+
|
|
20
|
+
logger = logging.getLogger(__name__)
|
|
21
|
+
|
|
22
|
+
|
|
23
|
+
class ParametersNamespace:
|
|
24
|
+
"""Parameter management API for Session models."""
|
|
25
|
+
|
|
26
|
+
@inject
|
|
27
|
+
def __init__(
|
|
28
|
+
self,
|
|
29
|
+
session_or_context: Session | SessionContext,
|
|
30
|
+
parameter_repository: IParameterRepository = Provide["nn.parameter_repository"],
|
|
31
|
+
parallel_context_factory: Callable[[], ParallelContext] = Provide[
|
|
32
|
+
"parallel_singleton.thread_pool_context.provider"
|
|
33
|
+
],
|
|
34
|
+
) -> None:
|
|
35
|
+
self.__session_or_context = session_or_context
|
|
36
|
+
self.__param_repo = parameter_repository
|
|
37
|
+
self.__parallel_context_factory = parallel_context_factory
|
|
38
|
+
|
|
39
|
+
def list(
|
|
40
|
+
self,
|
|
41
|
+
*,
|
|
42
|
+
verbose: bool = False,
|
|
43
|
+
) -> list[dict[str, Any]]:
|
|
44
|
+
"""List parameter metadata for inspection.
|
|
45
|
+
|
|
46
|
+
Provides detailed information about parameters currently stored
|
|
47
|
+
in the session, including computed fields and metadata.
|
|
48
|
+
|
|
49
|
+
Returns:
|
|
50
|
+
List of dictionaries containing parameter metadata including
|
|
51
|
+
UID, name, model ID, type, available fields, and custom metadata.
|
|
52
|
+
"""
|
|
53
|
+
with self.__session_or_context:
|
|
54
|
+
if isinstance(self.__session_or_context, Session):
|
|
55
|
+
params = self.__param_repo.create_view()
|
|
56
|
+
else:
|
|
57
|
+
params = self.__session_or_context._param_filter_context
|
|
58
|
+
|
|
59
|
+
if not verbose:
|
|
60
|
+
return [
|
|
61
|
+
{
|
|
62
|
+
"uid": param.meta.uid,
|
|
63
|
+
"name": param.meta.name,
|
|
64
|
+
"model_id": param.meta.model_id,
|
|
65
|
+
"parameter_type": param.meta.ptype.name,
|
|
66
|
+
}
|
|
67
|
+
for param in params
|
|
68
|
+
]
|
|
69
|
+
|
|
70
|
+
if self.__session_or_context._field_cache.is_valid:
|
|
71
|
+
field_cache = self.__session_or_context._field_cache.get()
|
|
72
|
+
else:
|
|
73
|
+
field_cache = params.list_fields_by_uid(
|
|
74
|
+
parallel=self.__parallel_context_factory()
|
|
75
|
+
)
|
|
76
|
+
self.__session_or_context._field_cache.set(field_cache)
|
|
77
|
+
|
|
78
|
+
return [
|
|
79
|
+
{
|
|
80
|
+
"uid": param.meta.uid,
|
|
81
|
+
"name": param.meta.name,
|
|
82
|
+
"model_id": param.meta.model_id,
|
|
83
|
+
"parameter_type": param.meta.ptype.name,
|
|
84
|
+
"available_fields": field_cache.get(param.meta.uid, []),
|
|
85
|
+
"other_meta": param.meta.other_meta,
|
|
86
|
+
}
|
|
87
|
+
for param in params
|
|
88
|
+
]
|
|
89
|
+
|
|
90
|
+
def patch_meta(
|
|
91
|
+
self,
|
|
92
|
+
*,
|
|
93
|
+
updates: dict[str, Any],
|
|
94
|
+
force: bool = False,
|
|
95
|
+
) -> None:
|
|
96
|
+
"""Update custom metadata (`other_meta`) for selected parameters.
|
|
97
|
+
|
|
98
|
+
This method updates only the `other_meta` portion of parameter metadata.
|
|
99
|
+
Changes are persisted by rewriting the stored `__metadata__` field, so
|
|
100
|
+
subsequent sessions will see the updated metadata.
|
|
101
|
+
|
|
102
|
+
Args:
|
|
103
|
+
updates: Mapping of parameter UID to metadata updates. Each value
|
|
104
|
+
must be a dict of {key: value} pairs to apply to that
|
|
105
|
+
parameter's other_meta.
|
|
106
|
+
Example: {"uid1": {"dataset": "imagenet", "epoch": 10}}
|
|
107
|
+
force: If False (default), raise if any key already exists in
|
|
108
|
+
`other_meta`. If True, overwrite existing keys.
|
|
109
|
+
|
|
110
|
+
Raises:
|
|
111
|
+
SessionError: If conflicts are detected (force=False), invalid keys
|
|
112
|
+
are provided, or updates structure is invalid.
|
|
113
|
+
"""
|
|
114
|
+
with self.__session_or_context:
|
|
115
|
+
if not updates:
|
|
116
|
+
return
|
|
117
|
+
|
|
118
|
+
for uid, value in updates.items():
|
|
119
|
+
if not isinstance(value, dict):
|
|
120
|
+
msg = (
|
|
121
|
+
f"Invalid update for uid '{uid}': expected dict[str, Any], "
|
|
122
|
+
f"got {type(value).__name__}"
|
|
123
|
+
)
|
|
124
|
+
raise SessionError(msg)
|
|
125
|
+
|
|
126
|
+
param_view = self.__param_repo.create_view()
|
|
127
|
+
pending = param_view[updates.keys()]
|
|
128
|
+
patcher = MetadataPatcher()
|
|
129
|
+
|
|
130
|
+
try:
|
|
131
|
+
patcher.patch(updates=updates, parameters=pending, force=force)
|
|
132
|
+
except MetadataPatchError as e:
|
|
133
|
+
raise SessionError(str(e)) from e
|
|
@@ -0,0 +1,167 @@
|
|
|
1
|
+
"""Parameter management namespace for Session models."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
import logging
|
|
6
|
+
from dataclasses import replace
|
|
7
|
+
from typing import TYPE_CHECKING, Any
|
|
8
|
+
|
|
9
|
+
from tqdm.auto import tqdm
|
|
10
|
+
|
|
11
|
+
from diffract.core.constants import (
|
|
12
|
+
PROGRESS_BAR_DELAY_SEC,
|
|
13
|
+
PROGRESS_BAR_MIN_ITEMS,
|
|
14
|
+
TABLE_PARAMETERS,
|
|
15
|
+
)
|
|
16
|
+
|
|
17
|
+
if TYPE_CHECKING:
|
|
18
|
+
from diffract.core.data.nn.params.interface import IParameterView
|
|
19
|
+
from diffract.core.data.nn.params.proxy import ParameterDataProxy
|
|
20
|
+
|
|
21
|
+
logger = logging.getLogger(__name__)
|
|
22
|
+
|
|
23
|
+
_ERROR_PREVIEW_LIMIT = 10
|
|
24
|
+
|
|
25
|
+
|
|
26
|
+
class MetadataPatchError(Exception):
|
|
27
|
+
"""Error during metadata patching."""
|
|
28
|
+
|
|
29
|
+
|
|
30
|
+
class MetadataPatcher:
|
|
31
|
+
"""Utility for patching parameter metadata.
|
|
32
|
+
|
|
33
|
+
Handles conflict detection and applies updates to the other_meta
|
|
34
|
+
portion of parameter metadata.
|
|
35
|
+
|
|
36
|
+
Example:
|
|
37
|
+
>>> patcher = MetadataPatcher()
|
|
38
|
+
>>> patcher.patch(
|
|
39
|
+
... updates={"uid1": {"dataset": "imagenet", "epoch": 10}},
|
|
40
|
+
... parameters=session._get_view(),
|
|
41
|
+
... force=False,
|
|
42
|
+
... )
|
|
43
|
+
"""
|
|
44
|
+
|
|
45
|
+
def patch(
|
|
46
|
+
self,
|
|
47
|
+
*,
|
|
48
|
+
updates: dict[str, dict[str, Any]],
|
|
49
|
+
parameters: IParameterView,
|
|
50
|
+
force: bool = False,
|
|
51
|
+
) -> None:
|
|
52
|
+
"""Patch other_meta with conflict detection.
|
|
53
|
+
|
|
54
|
+
Args:
|
|
55
|
+
updates: Mapping of parameter uid -> {meta_key: value}.
|
|
56
|
+
parameters: Parameter view containing target parameters.
|
|
57
|
+
force: If True, overwrite existing metadata keys.
|
|
58
|
+
|
|
59
|
+
Raises:
|
|
60
|
+
MetadataPatchError: If unknown UIDs or conflicts are detected.
|
|
61
|
+
"""
|
|
62
|
+
if not updates:
|
|
63
|
+
logger.debug("patch called with empty updates, skipping")
|
|
64
|
+
return
|
|
65
|
+
|
|
66
|
+
resolved, unknown_uids = self._resolve_parameters(updates, parameters)
|
|
67
|
+
|
|
68
|
+
if unknown_uids:
|
|
69
|
+
preview = ", ".join(sorted(unknown_uids)[:_ERROR_PREVIEW_LIMIT])
|
|
70
|
+
suffix = "..." if len(unknown_uids) > _ERROR_PREVIEW_LIMIT else ""
|
|
71
|
+
raise MetadataPatchError(
|
|
72
|
+
f"Unknown parameter UIDs (not found in session): {preview}{suffix}"
|
|
73
|
+
)
|
|
74
|
+
|
|
75
|
+
conflicts = self._validate_conflicts(updates, resolved, force)
|
|
76
|
+
|
|
77
|
+
if conflicts:
|
|
78
|
+
preview = ", ".join(
|
|
79
|
+
f"{uid}:{k}" for uid, k in conflicts[:_ERROR_PREVIEW_LIMIT]
|
|
80
|
+
)
|
|
81
|
+
suffix = "..." if len(conflicts) > _ERROR_PREVIEW_LIMIT else ""
|
|
82
|
+
raise MetadataPatchError(
|
|
83
|
+
f"Metadata conflicts (existing keys) for {len(conflicts)} entries: "
|
|
84
|
+
f"{preview}{suffix}"
|
|
85
|
+
)
|
|
86
|
+
|
|
87
|
+
self._apply_updates(updates, resolved, parameters)
|
|
88
|
+
logger.info("Patched metadata for %d parameters", len(updates))
|
|
89
|
+
|
|
90
|
+
def _resolve_parameters(
|
|
91
|
+
self,
|
|
92
|
+
updates: dict[str, dict[str, Any]],
|
|
93
|
+
parameters: IParameterView,
|
|
94
|
+
) -> tuple[dict[str, ParameterDataProxy], list[str]]:
|
|
95
|
+
"""Resolve parameter proxies from UIDs."""
|
|
96
|
+
resolved: dict[str, ParameterDataProxy] = {}
|
|
97
|
+
unknown_uids: list[str] = []
|
|
98
|
+
|
|
99
|
+
for uid in updates:
|
|
100
|
+
try:
|
|
101
|
+
resolved[uid] = parameters[uid]
|
|
102
|
+
except KeyError:
|
|
103
|
+
unknown_uids.append(uid)
|
|
104
|
+
|
|
105
|
+
return resolved, unknown_uids
|
|
106
|
+
|
|
107
|
+
def _validate_conflicts(
|
|
108
|
+
self,
|
|
109
|
+
updates: dict[str, dict[str, Any]],
|
|
110
|
+
resolved: dict[str, ParameterDataProxy],
|
|
111
|
+
force: bool,
|
|
112
|
+
) -> list[tuple[str, str]]:
|
|
113
|
+
"""Check for conflicting metadata keys."""
|
|
114
|
+
conflicts: list[tuple[str, str]] = []
|
|
115
|
+
|
|
116
|
+
for uid, meta_map in updates.items():
|
|
117
|
+
param = resolved[uid]
|
|
118
|
+
conflicts.extend(
|
|
119
|
+
(param.meta.uid, key)
|
|
120
|
+
for key in meta_map
|
|
121
|
+
if (not force) and (key in param.meta.other_meta)
|
|
122
|
+
)
|
|
123
|
+
|
|
124
|
+
return conflicts
|
|
125
|
+
|
|
126
|
+
def _apply_updates(
|
|
127
|
+
self,
|
|
128
|
+
updates: dict[str, dict[str, Any]],
|
|
129
|
+
resolved: dict[str, ParameterDataProxy],
|
|
130
|
+
parameters: IParameterView,
|
|
131
|
+
) -> None:
|
|
132
|
+
"""Apply metadata updates to parameters.
|
|
133
|
+
|
|
134
|
+
Updates other_meta in both the proxy and the metadata index.
|
|
135
|
+
"""
|
|
136
|
+
total = len(updates)
|
|
137
|
+
repository = parameters._repository
|
|
138
|
+
|
|
139
|
+
with repository:
|
|
140
|
+
for uid, meta_map in tqdm(
|
|
141
|
+
updates.items(),
|
|
142
|
+
desc="Updating metadata...",
|
|
143
|
+
delay=PROGRESS_BAR_DELAY_SEC,
|
|
144
|
+
disable=total < PROGRESS_BAR_MIN_ITEMS,
|
|
145
|
+
total=total,
|
|
146
|
+
):
|
|
147
|
+
param = resolved[uid]
|
|
148
|
+
old_meta = param.meta
|
|
149
|
+
|
|
150
|
+
# Build updated other_meta
|
|
151
|
+
new_other_meta = dict(old_meta.other_meta)
|
|
152
|
+
new_other_meta.update(meta_map)
|
|
153
|
+
|
|
154
|
+
# Create new metadata with updated other_meta
|
|
155
|
+
new_meta = replace(old_meta, other_meta=new_other_meta)
|
|
156
|
+
|
|
157
|
+
# Update the proxy's meta reference (frozen, so we use
|
|
158
|
+
# object.__setattr__)
|
|
159
|
+
object.__setattr__(param, "meta", new_meta)
|
|
160
|
+
|
|
161
|
+
new_meta_dict = new_meta.to_dict()
|
|
162
|
+
new_meta_dict.pop("uid")
|
|
163
|
+
repository.metadata_index.upsert(
|
|
164
|
+
TABLE_PARAMETERS,
|
|
165
|
+
uid=uid,
|
|
166
|
+
**new_meta_dict,
|
|
167
|
+
)
|