weightlab 0.1.0a1__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.
- weightlab/__init__.py +23 -0
- weightlab/analysis/__init__.py +0 -0
- weightlab/analysis/accumulate.py +311 -0
- weightlab/analysis/change_report.py +669 -0
- weightlab/analysis/compat.py +146 -0
- weightlab/analysis/diff.py +93 -0
- weightlab/analysis/lineage.py +245 -0
- weightlab/analysis/lora.py +483 -0
- weightlab/analysis/pyramid.py +779 -0
- weightlab/analysis/report_io.py +374 -0
- weightlab/analysis/roi.py +128 -0
- weightlab/analysis/roles.py +75 -0
- weightlab/analysis/sampling.py +221 -0
- weightlab/analysis/tile.py +516 -0
- weightlab/analysis/tile_codec.py +311 -0
- weightlab/api/__init__.py +1 -0
- weightlab/api/app.py +466 -0
- weightlab/api/catalog.py +327 -0
- weightlab/api/config.py +80 -0
- weightlab/api/errors.py +89 -0
- weightlab/api/relations.py +321 -0
- weightlab/api/roi.py +221 -0
- weightlab/api/schemas.py +295 -0
- weightlab/api/security.py +203 -0
- weightlab/api/server.py +46 -0
- weightlab/api/tiles.py +509 -0
- weightlab/cache.py +509 -0
- weightlab/cli.py +224 -0
- weightlab/core/__init__.py +0 -0
- weightlab/core/errors.py +40 -0
- weightlab/core/limits.py +29 -0
- weightlab/core/memory.py +121 -0
- weightlab/db/__init__.py +44 -0
- weightlab/db/migrations/0001_initial.sql +121 -0
- weightlab/db/migrations/0002_model_files_ctime.sql +11 -0
- weightlab/db/migrations/__init__.py +1 -0
- weightlab/db/schema.py +292 -0
- weightlab/db/store.py +418 -0
- weightlab/formats/__init__.py +0 -0
- weightlab/formats/base.py +83 -0
- weightlab/formats/checkpoint.py +497 -0
- weightlab/formats/dtypes.py +88 -0
- weightlab/formats/fileio.py +168 -0
- weightlab/formats/safetensors_reader.py +120 -0
- weightlab/formats/selection.py +140 -0
- weightlab/formats/shards.py +78 -0
- weightlab/jobs.py +386 -0
- weightlab/schemas/change_report.schema.json +101 -0
- weightlab/ui/__init__.py +72 -0
- weightlab/ui/static/assets/index-DQKuRsGl.js +9 -0
- weightlab/ui/static/assets/index-hkYppvgX.css +1 -0
- weightlab/ui/static/favicon.svg +1 -0
- weightlab/ui/static/index.html +17 -0
- weightlab-0.1.0a1.dist-info/METADATA +268 -0
- weightlab-0.1.0a1.dist-info/RECORD +57 -0
- weightlab-0.1.0a1.dist-info/WHEEL +4 -0
- weightlab-0.1.0a1.dist-info/entry_points.txt +3 -0
weightlab/__init__.py
ADDED
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
"""WeightLab: local-first, CPU-only LLM checkpoint explorer.
|
|
2
|
+
|
|
3
|
+
Supports strict SafeTensors reads, exact ROI values, local heatmaps, and
|
|
4
|
+
checkpoint/LoRA change reports. The graphical comparison UI is still planned.
|
|
5
|
+
"""
|
|
6
|
+
|
|
7
|
+
from weightlab.core.errors import ErrorCode, WeightLabError
|
|
8
|
+
from weightlab.formats.base import Consistency, TensorMeta
|
|
9
|
+
from weightlab.formats.checkpoint import Checkpoint, open_checkpoint
|
|
10
|
+
|
|
11
|
+
__version__ = "0.1.0a1"
|
|
12
|
+
PARSER_VERSION = 1
|
|
13
|
+
|
|
14
|
+
__all__ = [
|
|
15
|
+
"PARSER_VERSION",
|
|
16
|
+
"Checkpoint",
|
|
17
|
+
"Consistency",
|
|
18
|
+
"ErrorCode",
|
|
19
|
+
"TensorMeta",
|
|
20
|
+
"WeightLabError",
|
|
21
|
+
"__version__",
|
|
22
|
+
"open_checkpoint",
|
|
23
|
+
]
|
|
File without changes
|
|
@@ -0,0 +1,311 @@
|
|
|
1
|
+
"""Overflow-safe streaming accumulators for change metrics (doc 05 sections 3-4).
|
|
2
|
+
|
|
3
|
+
Sums of squares and dot products are kept as `mantissa * 2**exponent` with power-of-two chunk
|
|
4
|
+
scaling (LAPACK dnrm2 idea, but exact scaling), so a finite input never overflows an L2 norm or a
|
|
5
|
+
cosine; only a final value outside float64 range is reported as overflowed (None).
|
|
6
|
+
Every element is visited once; results are float64-rounded (`numeric_precision = rounded`).
|
|
7
|
+
"""
|
|
8
|
+
|
|
9
|
+
from __future__ import annotations
|
|
10
|
+
|
|
11
|
+
import math
|
|
12
|
+
from dataclasses import dataclass, field
|
|
13
|
+
|
|
14
|
+
import numpy as np
|
|
15
|
+
import numpy.typing as npt
|
|
16
|
+
|
|
17
|
+
F64 = npt.NDArray[np.float64]
|
|
18
|
+
|
|
19
|
+
|
|
20
|
+
@dataclass
|
|
21
|
+
class ExpSum:
|
|
22
|
+
"""Non-negative-or-signed running sum stored as m * 2**e with 0.5 <= |m| < 1 (or m == 0)."""
|
|
23
|
+
|
|
24
|
+
m: float = 0.0
|
|
25
|
+
e: int = 0
|
|
26
|
+
|
|
27
|
+
def add(self, mantissa: float, exponent: int) -> None:
|
|
28
|
+
if mantissa == 0.0:
|
|
29
|
+
return
|
|
30
|
+
if self.m == 0.0:
|
|
31
|
+
self.m, self.e = mantissa, exponent
|
|
32
|
+
elif exponent > self.e:
|
|
33
|
+
self.m = math.ldexp(self.m, self.e - exponent) + mantissa
|
|
34
|
+
self.e = exponent
|
|
35
|
+
else:
|
|
36
|
+
self.m += math.ldexp(mantissa, exponent - self.e)
|
|
37
|
+
fm, fe = math.frexp(self.m)
|
|
38
|
+
self.m, self.e = fm, self.e + fe
|
|
39
|
+
|
|
40
|
+
def merge(self, other: ExpSum) -> None:
|
|
41
|
+
self.add(other.m, other.e)
|
|
42
|
+
|
|
43
|
+
def add_squares(self, x: F64) -> None:
|
|
44
|
+
if x.size == 0:
|
|
45
|
+
return
|
|
46
|
+
peak = float(np.max(np.abs(x)))
|
|
47
|
+
if peak == 0.0:
|
|
48
|
+
return
|
|
49
|
+
k = math.frexp(peak)[1]
|
|
50
|
+
y = np.ldexp(x, -k)
|
|
51
|
+
self.add(float(np.dot(y, y)), 2 * k)
|
|
52
|
+
|
|
53
|
+
def add_products(self, a: F64, b: F64) -> None:
|
|
54
|
+
if a.size == 0:
|
|
55
|
+
return
|
|
56
|
+
pa, pb = float(np.max(np.abs(a))), float(np.max(np.abs(b)))
|
|
57
|
+
if pa == 0.0 or pb == 0.0:
|
|
58
|
+
return
|
|
59
|
+
ka, kb = math.frexp(pa)[1], math.frexp(pb)[1]
|
|
60
|
+
self.add(float(np.dot(np.ldexp(a, -ka), np.ldexp(b, -kb))), ka + kb)
|
|
61
|
+
|
|
62
|
+
def value(self) -> float | None:
|
|
63
|
+
return _ldexp(self.m, self.e)
|
|
64
|
+
|
|
65
|
+
def sqrt(self) -> float | None:
|
|
66
|
+
"""sqrt of a non-negative sum, overflow-safe."""
|
|
67
|
+
if self.m == 0.0:
|
|
68
|
+
return 0.0
|
|
69
|
+
m, e = (self.m, self.e) if self.e % 2 == 0 else (self.m * 2.0, self.e - 1)
|
|
70
|
+
return _ldexp(math.sqrt(m), e // 2)
|
|
71
|
+
|
|
72
|
+
|
|
73
|
+
def _scaled(x: F64, peak: float) -> tuple[F64, int] | None:
|
|
74
|
+
"""x * 2**-k with 2**(k-1) <= peak < 2**k (exact power-of-two scaling), or None if all zero."""
|
|
75
|
+
if peak == 0.0 or x.size == 0:
|
|
76
|
+
return None
|
|
77
|
+
k = math.frexp(peak)[1]
|
|
78
|
+
return np.ldexp(x, -k), k
|
|
79
|
+
|
|
80
|
+
|
|
81
|
+
def _ldexp(m: float, e: int) -> float | None:
|
|
82
|
+
try:
|
|
83
|
+
v = math.ldexp(m, e)
|
|
84
|
+
except OverflowError:
|
|
85
|
+
return None
|
|
86
|
+
return v if math.isfinite(v) else None
|
|
87
|
+
|
|
88
|
+
|
|
89
|
+
def sqrt_ratio(num: ExpSum, den: ExpSum) -> float | None:
|
|
90
|
+
"""sqrt(num / den) without forming either value; None when den is zero or the result overflows."""
|
|
91
|
+
if den.m == 0.0:
|
|
92
|
+
return None
|
|
93
|
+
if num.m == 0.0:
|
|
94
|
+
return 0.0
|
|
95
|
+
m, e = num.m / den.m, num.e - den.e
|
|
96
|
+
if e % 2:
|
|
97
|
+
m, e = m * 2.0, e - 1
|
|
98
|
+
return _ldexp(math.sqrt(m), e // 2)
|
|
99
|
+
|
|
100
|
+
|
|
101
|
+
def cosine(dot: ExpSum, ssq_a: ExpSum, ssq_b: ExpSum) -> float | None:
|
|
102
|
+
"""dot / (||a|| ||b||); None for a zero norm."""
|
|
103
|
+
if ssq_a.m == 0.0 or ssq_b.m == 0.0:
|
|
104
|
+
return None
|
|
105
|
+
m = dot.m / math.sqrt(ssq_a.m * ssq_b.m)
|
|
106
|
+
e2 = 2 * dot.e - ssq_a.e - ssq_b.e # exponent of the result, doubled
|
|
107
|
+
if e2 % 2:
|
|
108
|
+
m, e2 = m * math.sqrt(2.0), e2 - 1
|
|
109
|
+
v = _ldexp(m, e2 // 2)
|
|
110
|
+
return None if v is None else max(-1.0, min(1.0, v))
|
|
111
|
+
|
|
112
|
+
|
|
113
|
+
@dataclass
|
|
114
|
+
class PairStats:
|
|
115
|
+
"""Paired statistics of A (reference) and B, with delta = B - A in float64.
|
|
116
|
+
|
|
117
|
+
Pairs where either input is non-finite are excluded from every finite-pair metric and counted
|
|
118
|
+
in `invalid_pairs`. A finite pair whose delta overflows float64 is counted in `delta_overflow`
|
|
119
|
+
and makes every delta metric `overflowed` (withheld), never a silent Inf.
|
|
120
|
+
"""
|
|
121
|
+
|
|
122
|
+
elements: int = 0
|
|
123
|
+
finite_pairs: int = 0
|
|
124
|
+
invalid_pairs: int = 0
|
|
125
|
+
nan_a: int = 0
|
|
126
|
+
pos_inf_a: int = 0
|
|
127
|
+
neg_inf_a: int = 0
|
|
128
|
+
nan_b: int = 0
|
|
129
|
+
pos_inf_b: int = 0
|
|
130
|
+
neg_inf_b: int = 0
|
|
131
|
+
delta_overflow: int = 0
|
|
132
|
+
b_overflow: int = 0 # computed B (LoRA W + s B A) not representable although its inputs are finite
|
|
133
|
+
changed: int = 0
|
|
134
|
+
max_abs: float = 0.0
|
|
135
|
+
ssq_delta: ExpSum = field(default_factory=ExpSum)
|
|
136
|
+
ssq_a: ExpSum = field(default_factory=ExpSum)
|
|
137
|
+
ssq_b: ExpSum = field(default_factory=ExpSum)
|
|
138
|
+
dot_ab: ExpSum = field(default_factory=ExpSum)
|
|
139
|
+
|
|
140
|
+
def _count_nonfinite(self, arr: F64, finite: npt.NDArray[np.bool_], prefix: str) -> None:
|
|
141
|
+
bad = arr[~finite]
|
|
142
|
+
nan = int(np.isnan(bad).sum())
|
|
143
|
+
pos = int((bad > 0).sum())
|
|
144
|
+
setattr(self, f"nan_{prefix}", getattr(self, f"nan_{prefix}") + nan)
|
|
145
|
+
setattr(self, f"pos_inf_{prefix}", getattr(self, f"pos_inf_{prefix}") + pos)
|
|
146
|
+
setattr(self, f"neg_inf_{prefix}", getattr(self, f"neg_inf_{prefix}") + bad.size - nan - pos)
|
|
147
|
+
|
|
148
|
+
def update(self, a: F64, b: F64, delta: F64 | None = None) -> None:
|
|
149
|
+
"""Feed one aligned chunk. `delta`, when given (LoRA), is the exact-as-computed B - A.
|
|
150
|
+
|
|
151
|
+
Fast path: an all-finite chunk (the usual case) skips every mask; each input is scaled by
|
|
152
|
+
a power of two once and reused for its sum of squares and for the dot product.
|
|
153
|
+
"""
|
|
154
|
+
n = int(a.size)
|
|
155
|
+
self.elements += n
|
|
156
|
+
fa, fb = np.isfinite(a), np.isfinite(b)
|
|
157
|
+
all_a, all_b = bool(fa.all()), bool(fb.all())
|
|
158
|
+
if not all_a:
|
|
159
|
+
self._count_nonfinite(a, fa, "a")
|
|
160
|
+
if not all_b:
|
|
161
|
+
self._count_nonfinite(b, fb, "b")
|
|
162
|
+
ok: npt.NDArray[np.bool_] | None = None
|
|
163
|
+
if not (all_a and all_b):
|
|
164
|
+
ok = fa & fb
|
|
165
|
+
if delta is not None:
|
|
166
|
+
fd = np.isfinite(delta)
|
|
167
|
+
if not fd.all():
|
|
168
|
+
ok = fd if ok is None else ok & fd
|
|
169
|
+
if ok is not None:
|
|
170
|
+
a, b = a[ok], b[ok]
|
|
171
|
+
if delta is not None:
|
|
172
|
+
delta = delta[ok]
|
|
173
|
+
n_ok = int(a.size)
|
|
174
|
+
self.finite_pairs += n_ok
|
|
175
|
+
self.invalid_pairs += n - n_ok
|
|
176
|
+
if n_ok == 0:
|
|
177
|
+
return
|
|
178
|
+
peak_a, peak_b = float(np.max(np.abs(a))), float(np.max(np.abs(b)))
|
|
179
|
+
if delta is None:
|
|
180
|
+
if max(peak_a, peak_b) >= 2.0**1022: # only then can B - A overflow float64
|
|
181
|
+
with np.errstate(over="ignore", invalid="ignore"):
|
|
182
|
+
d = b - a
|
|
183
|
+
over = ~np.isfinite(d)
|
|
184
|
+
n_over = int(over.sum())
|
|
185
|
+
if n_over:
|
|
186
|
+
self.delta_overflow += n_over
|
|
187
|
+
d = d[~over]
|
|
188
|
+
else:
|
|
189
|
+
d = b - a
|
|
190
|
+
else:
|
|
191
|
+
d = delta
|
|
192
|
+
self.changed += int(np.count_nonzero(d))
|
|
193
|
+
peak_d = float(np.max(np.abs(d))) if d.size else 0.0
|
|
194
|
+
self.max_abs = max(self.max_abs, peak_d)
|
|
195
|
+
ya = _scaled(a, peak_a)
|
|
196
|
+
yb = _scaled(b, peak_b)
|
|
197
|
+
if ya is not None:
|
|
198
|
+
self.ssq_a.add(float(np.dot(ya[0], ya[0])), 2 * ya[1])
|
|
199
|
+
if yb is not None:
|
|
200
|
+
self.ssq_b.add(float(np.dot(yb[0], yb[0])), 2 * yb[1])
|
|
201
|
+
if ya is not None and yb is not None:
|
|
202
|
+
self.dot_ab.add(float(np.dot(ya[0], yb[0])), ya[1] + yb[1])
|
|
203
|
+
yd = _scaled(d, peak_d)
|
|
204
|
+
if yd is not None:
|
|
205
|
+
self.ssq_delta.add(float(np.dot(yd[0], yd[0])), 2 * yd[1])
|
|
206
|
+
|
|
207
|
+
def add_overflow(self, a: F64, n_delta: int, n_b: int) -> None:
|
|
208
|
+
"""Record finite reference values `a` at positions whose computed delta (`n_delta` of them)
|
|
209
|
+
and/or computed B (`n_b`) left float64 range. They are not source NaN/Inf: they count as
|
|
210
|
+
finite pairs, still contribute to ||A|| (which stays exact), and withhold every metric
|
|
211
|
+
that needs the unrepresentable values (delta metrics; norm_b and cosine when B overflowed)."""
|
|
212
|
+
self.elements += int(a.size)
|
|
213
|
+
self.finite_pairs += int(a.size)
|
|
214
|
+
self.delta_overflow += n_delta
|
|
215
|
+
self.b_overflow += n_b
|
|
216
|
+
self.ssq_a.add_squares(a)
|
|
217
|
+
|
|
218
|
+
# -- derived metrics (None = not defined or withheld) ---------------------------------
|
|
219
|
+
@property
|
|
220
|
+
def delta_withheld(self) -> bool:
|
|
221
|
+
return self.delta_overflow > 0 or self.b_overflow > 0
|
|
222
|
+
|
|
223
|
+
@property
|
|
224
|
+
def b_withheld(self) -> bool:
|
|
225
|
+
return self.b_overflow > 0
|
|
226
|
+
|
|
227
|
+
def delta_l2(self) -> float | None:
|
|
228
|
+
return None if self.delta_withheld else self.ssq_delta.sqrt()
|
|
229
|
+
|
|
230
|
+
def delta_rms(self) -> float | None:
|
|
231
|
+
if self.delta_withheld or self.finite_pairs == 0:
|
|
232
|
+
return None
|
|
233
|
+
return sqrt_ratio(self.ssq_delta, ExpSum(*math.frexp(float(self.finite_pairs))))
|
|
234
|
+
|
|
235
|
+
def relative_l2(self, eps_norm: float) -> float | None:
|
|
236
|
+
if self.delta_withheld or self.finite_pairs == 0:
|
|
237
|
+
return None
|
|
238
|
+
return relative_l2(self.ssq_delta, self.ssq_a, eps_norm)
|
|
239
|
+
|
|
240
|
+
def near_zero_reference(self, eps_norm: float) -> bool:
|
|
241
|
+
return near_zero(self.ssq_a, eps_norm)
|
|
242
|
+
|
|
243
|
+
def cosine(self) -> float | None:
|
|
244
|
+
return None if self.b_withheld else cosine(self.dot_ab, self.ssq_a, self.ssq_b)
|
|
245
|
+
|
|
246
|
+
def max_abs_delta(self) -> float | None:
|
|
247
|
+
if self.delta_withheld or self.finite_pairs == 0:
|
|
248
|
+
return None
|
|
249
|
+
return self.max_abs
|
|
250
|
+
|
|
251
|
+
|
|
252
|
+
def _ratio_or_none(num: float | None, den: float) -> float | None:
|
|
253
|
+
if num is None:
|
|
254
|
+
return None
|
|
255
|
+
v = num / den
|
|
256
|
+
return v if math.isfinite(v) else None
|
|
257
|
+
|
|
258
|
+
|
|
259
|
+
def near_zero(ssq_ref: ExpSum, eps_norm: float) -> bool:
|
|
260
|
+
norm = ssq_ref.sqrt()
|
|
261
|
+
return norm is not None and norm < eps_norm
|
|
262
|
+
|
|
263
|
+
|
|
264
|
+
def relative_l2(ssq_delta: ExpSum, ssq_ref: ExpSum, eps_norm: float) -> float | None:
|
|
265
|
+
"""||delta|| / max(||ref||, eps_norm): the one denominator policy for tensors, modules and
|
|
266
|
+
rollups. A zero or near-zero reference divides by eps_norm (never null for that reason)."""
|
|
267
|
+
if near_zero(ssq_ref, eps_norm):
|
|
268
|
+
return _ratio_or_none(ssq_delta.sqrt(), eps_norm)
|
|
269
|
+
return sqrt_ratio(ssq_delta, ssq_ref)
|
|
270
|
+
|
|
271
|
+
|
|
272
|
+
@dataclass
|
|
273
|
+
class Rollup:
|
|
274
|
+
"""Aggregate of several tensors' paired stats (layer, role or whole model)."""
|
|
275
|
+
|
|
276
|
+
tensors: int = 0
|
|
277
|
+
elements: int = 0
|
|
278
|
+
finite_pairs: int = 0
|
|
279
|
+
invalid_pairs: int = 0
|
|
280
|
+
withheld: int = 0 # member tensors whose delta overflowed
|
|
281
|
+
max_abs: float = 0.0
|
|
282
|
+
ssq_delta: ExpSum = field(default_factory=ExpSum)
|
|
283
|
+
ssq_a: ExpSum = field(default_factory=ExpSum)
|
|
284
|
+
|
|
285
|
+
def add(self, s: PairStats, ssq_delta: ExpSum | None = None) -> None:
|
|
286
|
+
self.tensors += 1
|
|
287
|
+
self.elements += s.elements
|
|
288
|
+
self.finite_pairs += s.finite_pairs
|
|
289
|
+
self.invalid_pairs += s.invalid_pairs
|
|
290
|
+
if s.delta_withheld:
|
|
291
|
+
self.withheld += 1
|
|
292
|
+
else:
|
|
293
|
+
self.ssq_delta.merge(ssq_delta if ssq_delta is not None else s.ssq_delta)
|
|
294
|
+
self.max_abs = max(self.max_abs, s.max_abs)
|
|
295
|
+
self.ssq_a.merge(s.ssq_a)
|
|
296
|
+
|
|
297
|
+
def delta_l2(self) -> float | None:
|
|
298
|
+
return None if self.withheld else self.ssq_delta.sqrt()
|
|
299
|
+
|
|
300
|
+
def delta_rms(self) -> float | None:
|
|
301
|
+
if self.withheld or self.finite_pairs == 0:
|
|
302
|
+
return None
|
|
303
|
+
return sqrt_ratio(self.ssq_delta, ExpSum(*math.frexp(float(self.finite_pairs))))
|
|
304
|
+
|
|
305
|
+
def relative_l2(self, eps_norm: float) -> float | None:
|
|
306
|
+
if self.withheld or self.finite_pairs == 0:
|
|
307
|
+
return None
|
|
308
|
+
return relative_l2(self.ssq_delta, self.ssq_a, eps_norm)
|
|
309
|
+
|
|
310
|
+
def near_zero_reference(self, eps_norm: float) -> bool:
|
|
311
|
+
return near_zero(self.ssq_a, eps_norm)
|