lexframe 0.1.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.
- lexframe/__init__.py +7 -0
- lexframe/_basis.py +166 -0
- lexframe/_calibrate.py +240 -0
- lexframe/_frame.py +470 -0
- lexframe/_model.py +151 -0
- lexframe/_transport.py +153 -0
- lexframe/py.typed +0 -0
- lexframe-0.1.0.dist-info/METADATA +71 -0
- lexframe-0.1.0.dist-info/RECORD +12 -0
- lexframe-0.1.0.dist-info/WHEEL +5 -0
- lexframe-0.1.0.dist-info/licenses/LICENSE +202 -0
- lexframe-0.1.0.dist-info/top_level.txt +1 -0
lexframe/__init__.py
ADDED
lexframe/_basis.py
ADDED
|
@@ -0,0 +1,166 @@
|
|
|
1
|
+
"""The lexical basis at a layer, s = J^+ y, solved by LSMR on Jacobian-vector products without forming J."""
|
|
2
|
+
|
|
3
|
+
import math
|
|
4
|
+
|
|
5
|
+
import torch
|
|
6
|
+
from torch.autograd import forward_ad as fwAD
|
|
7
|
+
from torch.utils.checkpoint import checkpoint
|
|
8
|
+
|
|
9
|
+
from . import _transport as TR
|
|
10
|
+
from ._model import block
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
def references(m, cal, xs, l, order):
|
|
14
|
+
L = int(m.n_layers)
|
|
15
|
+
B, Tn, d = xs[0].shape
|
|
16
|
+
pos = torch.arange(Tn, device=xs[0].device)
|
|
17
|
+
fi = torch.arange(0, B * Tn, Tn, device=xs[0].device)
|
|
18
|
+
if order is None:
|
|
19
|
+
return [TR.ref_kv(m, k, TR.innorm(m, k)(xs[k])) for k in range(l, L)]
|
|
20
|
+
low = [xs[l]] * order
|
|
21
|
+
out = []
|
|
22
|
+
with torch.no_grad():
|
|
23
|
+
for k in range(l, L):
|
|
24
|
+
norm = TR.innorm(m, k)
|
|
25
|
+
kv = [TR.ref_kv(m, k, norm(h)) for h in low]
|
|
26
|
+
out.append(kv[order - 1])
|
|
27
|
+
att = [TR.attend(m, k, norm(low[j]), pos, kv[j - 1]) for j in range(1, order)]
|
|
28
|
+
new = [TR._step(m, cal, k, low[0].reshape(-1, d), fi).view(B, Tn, d)]
|
|
29
|
+
for j in range(1, order):
|
|
30
|
+
a = att[j - 1].reshape(-1, d)
|
|
31
|
+
new.append(
|
|
32
|
+
block(m, k, low[j].reshape(-1, d), lambda u, s, a=a: a[s : s + u.shape[0]].to(u.dtype)).view(
|
|
33
|
+
B, Tn, d
|
|
34
|
+
)
|
|
35
|
+
)
|
|
36
|
+
low = new
|
|
37
|
+
return out
|
|
38
|
+
|
|
39
|
+
|
|
40
|
+
def stream(m, l, kv, X, ckpt=False):
|
|
41
|
+
L = int(m.n_layers)
|
|
42
|
+
b, Tn, d = X.shape
|
|
43
|
+
pos = torch.arange(Tn, device=X.device)
|
|
44
|
+
|
|
45
|
+
def step(h, k):
|
|
46
|
+
K, V = kv[k - l]
|
|
47
|
+
K, V = (K.expand(b, *K.shape[1:]), V.expand(b, *V.shape[1:])) if K.shape[0] != b else (K, V)
|
|
48
|
+
a = TR.attend(m, k, TR.innorm(m, k)(h), pos, (K, V)).reshape(-1, d)
|
|
49
|
+
return block(m, k, h.reshape(-1, d), lambda u, s: a[s : s + u.shape[0]].to(u.dtype)).view(b, Tn, d)
|
|
50
|
+
|
|
51
|
+
h = X
|
|
52
|
+
for k in range(l, L):
|
|
53
|
+
h = checkpoint(step, h, k, use_reentrant=False) if ckpt else step(h, k)
|
|
54
|
+
return m.final_norm(h).float()
|
|
55
|
+
|
|
56
|
+
|
|
57
|
+
def jvp(m, l, kv, X0, V):
|
|
58
|
+
dt = X0.dtype
|
|
59
|
+
with torch.no_grad(), fwAD.dual_level():
|
|
60
|
+
out = stream(m, l, kv, fwAD.make_dual(X0.expand(V.shape[0], -1, -1).contiguous(), V.to(dt)))
|
|
61
|
+
return fwAD.unpack_dual(out).tangent.float()
|
|
62
|
+
|
|
63
|
+
|
|
64
|
+
def vjp(m, l, kv, X0, U):
|
|
65
|
+
X = X0.expand(U.shape[0], -1, -1).clone().requires_grad_(True)
|
|
66
|
+
with torch.enable_grad():
|
|
67
|
+
return torch.autograd.grad(stream(m, l, kv, X, ckpt=True), X, U)[0].float()
|
|
68
|
+
|
|
69
|
+
|
|
70
|
+
def _ortho(a, b):
|
|
71
|
+
r = torch.sqrt(a * a + b * b)
|
|
72
|
+
z = r == 0
|
|
73
|
+
return (
|
|
74
|
+
torch.where(z, torch.ones_like(a), a / r.clamp_min(1e-30)),
|
|
75
|
+
torch.where(z, torch.zeros_like(a), b / r.clamp_min(1e-30)),
|
|
76
|
+
r,
|
|
77
|
+
)
|
|
78
|
+
|
|
79
|
+
|
|
80
|
+
def lsmr(Av, Atu, b, x0, damp, steps):
|
|
81
|
+
nrm = lambda u: u.norm(dim=-1, keepdim=True)
|
|
82
|
+
A = lambda v: (Av(v), damp * v)
|
|
83
|
+
At = lambda u: Atu(u[0]) + damp * u[1]
|
|
84
|
+
u = b - Av(x0), -damp * x0
|
|
85
|
+
beta = torch.sqrt(nrm(u[0]) ** 2 + nrm(u[1]) ** 2)
|
|
86
|
+
u = tuple(p / beta.clamp_min(1e-30) for p in u)
|
|
87
|
+
v = At(u)
|
|
88
|
+
alpha = nrm(v)
|
|
89
|
+
v = v / alpha.clamp_min(1e-30)
|
|
90
|
+
zetabar, alphabar = alpha * beta, alpha
|
|
91
|
+
one, zero = torch.ones_like(alpha), torch.zeros_like(alpha)
|
|
92
|
+
rho, rhobar, cbar, sbar, zeta = one, one, one, zero, zero
|
|
93
|
+
h, hbar, dx = v.clone(), torch.zeros_like(v), torch.zeros_like(v)
|
|
94
|
+
solved = alpha * beta == 0
|
|
95
|
+
for _ in range(0 if bool(solved.all()) else steps):
|
|
96
|
+
Avv = A(v)
|
|
97
|
+
u = Avv[0] - alpha * u[0], Avv[1] - alpha * u[1]
|
|
98
|
+
beta = torch.sqrt(nrm(u[0]) ** 2 + nrm(u[1]) ** 2)
|
|
99
|
+
u = tuple(p / beta.clamp_min(1e-30) for p in u)
|
|
100
|
+
v = At(u) - beta * v
|
|
101
|
+
alpha = nrm(v)
|
|
102
|
+
v = v / alpha.clamp_min(1e-30)
|
|
103
|
+
rhoold, rhobarold = rho, rhobar
|
|
104
|
+
c, s, rho = _ortho(alphabar, beta)
|
|
105
|
+
thetanew, alphabar = s * alpha, c * alpha
|
|
106
|
+
thetabar = sbar * rho
|
|
107
|
+
cbar, sbar, rhobar = _ortho(cbar * rho, thetanew)
|
|
108
|
+
zeta, zetabar = cbar * zetabar, -sbar * zetabar
|
|
109
|
+
hbar = h - thetabar * rho / (rhoold * rhobarold).clamp_min(1e-30) * hbar
|
|
110
|
+
step = zeta / (rho * rhobar).clamp_min(1e-30) * hbar
|
|
111
|
+
h = v - thetanew / rho.clamp_min(1e-30) * h
|
|
112
|
+
dx = dx + torch.where(solved, torch.zeros_like(step), step)
|
|
113
|
+
return x0 + dx
|
|
114
|
+
|
|
115
|
+
|
|
116
|
+
def auto_batch(m, probe, n, cap=64):
|
|
117
|
+
if m.device.type != "cuda":
|
|
118
|
+
return min(n, 8)
|
|
119
|
+
torch.cuda.synchronize()
|
|
120
|
+
torch.cuda.empty_cache()
|
|
121
|
+
base = torch.cuda.memory_allocated()
|
|
122
|
+
torch.cuda.reset_peak_memory_stats()
|
|
123
|
+
probe(2)
|
|
124
|
+
per = max(1, (torch.cuda.max_memory_allocated() - base) // 2)
|
|
125
|
+
free = torch.cuda.get_device_properties(m.device).total_memory - base
|
|
126
|
+
return max(1, min(n, cap, int(0.8 * free / per)))
|
|
127
|
+
|
|
128
|
+
|
|
129
|
+
def basis(m, cal, xs, LS, Y, order=None, ridge=0.001, steps=30, after=None, warm=False):
|
|
130
|
+
nr = Y.shape[0]
|
|
131
|
+
LS = sorted(LS, reverse=True)
|
|
132
|
+
X0s = {l: xs[l][:1] for l in LS}
|
|
133
|
+
K0 = references(m, cal, xs, min(LS), None) if order is None else None
|
|
134
|
+
kvof = lambda l: K0[l - min(LS) :] if order is None else references(m, cal, xs, l, order)
|
|
135
|
+
|
|
136
|
+
def run(rows, l, x0, kv):
|
|
137
|
+
X0 = X0s[l]
|
|
138
|
+
y = Y[rows].float()
|
|
139
|
+
Av = lambda v: jvp(m, l, kv, X0, v)
|
|
140
|
+
Atu = lambda u: vjp(m, l, kv, X0, u)
|
|
141
|
+
damp = math.sqrt(ridge) * Atu(y).norm(dim=-1, keepdim=True) / y.norm(dim=-1, keepdim=True).clamp_min(1e-30)
|
|
142
|
+
return lsmr(Av, Atu, y, x0, damp, steps)
|
|
143
|
+
|
|
144
|
+
def probe(n):
|
|
145
|
+
l = LS[-1]
|
|
146
|
+
v = Y[:1].float().expand(n, -1, -1)
|
|
147
|
+
jvp(m, l, kvof(l), X0s[l], v)
|
|
148
|
+
vjp(m, l, kvof(l), X0s[l], v)
|
|
149
|
+
|
|
150
|
+
b = auto_batch(m, probe, nr)
|
|
151
|
+
r0 = 0
|
|
152
|
+
while r0 < nr:
|
|
153
|
+
rows = slice(r0, min(nr, r0 + b))
|
|
154
|
+
try:
|
|
155
|
+
x = torch.zeros_like(Y[rows].float())
|
|
156
|
+
for l in LS:
|
|
157
|
+
kv = kvof(l)
|
|
158
|
+
x = run(rows, l, x if warm else torch.zeros_like(x), kv)
|
|
159
|
+
if after is not None:
|
|
160
|
+
after(l, X0s[l], kv, x, rows)
|
|
161
|
+
r0 = rows.stop
|
|
162
|
+
except torch.cuda.OutOfMemoryError:
|
|
163
|
+
if b == 1:
|
|
164
|
+
raise
|
|
165
|
+
b = max(1, b // 2)
|
|
166
|
+
torch.cuda.empty_cache()
|
lexframe/_calibrate.py
ADDED
|
@@ -0,0 +1,240 @@
|
|
|
1
|
+
"""The typical attention output of every block: a ridge regression of the attention output on its input."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
from typing import Iterable
|
|
6
|
+
|
|
7
|
+
import torch
|
|
8
|
+
|
|
9
|
+
from ._model import Model, forward
|
|
10
|
+
|
|
11
|
+
RHO = [10 ** (e / 8) for e in range(-56, 25)]
|
|
12
|
+
|
|
13
|
+
|
|
14
|
+
class _Moments:
|
|
15
|
+
|
|
16
|
+
def __init__(self, m: Model, batches: Iterable[torch.Tensor], folds: int = 2, work=torch.float32):
|
|
17
|
+
L, d = m.n_layers, m.hidden_dim
|
|
18
|
+
self.L, self.d, self.K, self.dev = L, d, folds, m.device
|
|
19
|
+
self.n = 0
|
|
20
|
+
acc = torch.float64 if 2 * folds * L * d * d * 8 < 4000000000.0 else torch.float32
|
|
21
|
+
z = lambda *s: torch.zeros(*s, dtype=acc, device=m.device)
|
|
22
|
+
self.C = [[[z(d, d), z(d, d)] for _ in range(folds)] for _ in range(L)]
|
|
23
|
+
self.cnt = [0] * folds
|
|
24
|
+
at = [0]
|
|
25
|
+
self.mu = torch.zeros(L, folds, d, dtype=torch.float64, device=m.device)
|
|
26
|
+
self.ma = torch.zeros_like(self.mu)
|
|
27
|
+
self.tr = torch.zeros(L, folds, dtype=torch.float64)
|
|
28
|
+
|
|
29
|
+
def cap(k, u, a):
|
|
30
|
+
g = (at[0] + torch.arange(u.shape[0], device=u.device)) % folds
|
|
31
|
+
for f in range(folds):
|
|
32
|
+
if not bool((g == f).any()):
|
|
33
|
+
continue
|
|
34
|
+
n2, mu2, ma2, C2, B2, t2 = _moments(
|
|
35
|
+
u[g == f].reshape(-1, d).to(work), a[g == f].reshape(-1, d).to(work)
|
|
36
|
+
)
|
|
37
|
+
n1 = self.cnt[f]
|
|
38
|
+
w = n1 * n2 / (n1 + n2)
|
|
39
|
+
du, da = mu2 - self.mu[k, f], ma2 - self.ma[k, f]
|
|
40
|
+
Cf = self.C[k][f]
|
|
41
|
+
Cf[0] += (C2 + w * torch.outer(du, du)).to(acc)
|
|
42
|
+
Cf[1] += (B2 + w * torch.outer(du, da)).to(acc)
|
|
43
|
+
self.tr[k, f] += t2 + w * float(da @ da)
|
|
44
|
+
self.mu[k, f] += du * (n2 / (n1 + n2))
|
|
45
|
+
self.ma[k, f] += da * (n2 / (n1 + n2))
|
|
46
|
+
if k == L - 1:
|
|
47
|
+
self.cnt[f] = n1 + n2
|
|
48
|
+
|
|
49
|
+
for ids in batches:
|
|
50
|
+
ids = m.ids(ids)
|
|
51
|
+
forward(m, ids, on_attention=cap)
|
|
52
|
+
at[0] += ids.shape[0]
|
|
53
|
+
self.n += ids.numel()
|
|
54
|
+
if at[0] < folds:
|
|
55
|
+
raise ValueError(f"calibration needs at least {folds} sequences")
|
|
56
|
+
|
|
57
|
+
def moments(self, k):
|
|
58
|
+
out = [
|
|
59
|
+
(
|
|
60
|
+
self.cnt[f],
|
|
61
|
+
self.mu[k, f],
|
|
62
|
+
self.ma[k, f],
|
|
63
|
+
self.C[k][f][0].double(),
|
|
64
|
+
self.C[k][f][1].double(),
|
|
65
|
+
float(self.tr[k, f]),
|
|
66
|
+
)
|
|
67
|
+
for f in range(self.K)
|
|
68
|
+
]
|
|
69
|
+
self.C[k] = None
|
|
70
|
+
return out
|
|
71
|
+
|
|
72
|
+
|
|
73
|
+
def _moments(U, A):
|
|
74
|
+
n = U.shape[0]
|
|
75
|
+
mu, ma = U.mean(0), A.mean(0)
|
|
76
|
+
Uc = U - mu
|
|
77
|
+
Ac = A - ma
|
|
78
|
+
return n, mu.double(), ma.double(), (Uc.T @ Uc).double(), (Uc.T @ Ac).double(), float((Ac.double() ** 2).sum())
|
|
79
|
+
|
|
80
|
+
|
|
81
|
+
def _merge(ms):
|
|
82
|
+
n = sum((m[0] for m in ms))
|
|
83
|
+
mu = sum((m[0] * m[1] for m in ms)) / n
|
|
84
|
+
ma = sum((m[0] * m[2] for m in ms)) / n
|
|
85
|
+
C = sum((m[3] + m[0] * torch.outer(m[1] - mu, m[1] - mu) for m in ms))
|
|
86
|
+
B = sum((m[4] + m[0] * torch.outer(m[1] - mu, m[2] - ma) for m in ms))
|
|
87
|
+
return n, mu, ma, C, B, sum((m[5] + m[0] * float((m[2] - ma) @ (m[2] - ma)) for m in ms))
|
|
88
|
+
|
|
89
|
+
|
|
90
|
+
class Calibration:
|
|
91
|
+
|
|
92
|
+
def __init__(self, ubar, abar, Q, lam, QA, rho, stats, model_id):
|
|
93
|
+
self.ubar, self.abar, self.Q, self.lam, self.QA, self.rho, self.stats, self.model_id = (
|
|
94
|
+
ubar,
|
|
95
|
+
abar,
|
|
96
|
+
Q,
|
|
97
|
+
lam,
|
|
98
|
+
QA,
|
|
99
|
+
rho,
|
|
100
|
+
stats,
|
|
101
|
+
model_id,
|
|
102
|
+
)
|
|
103
|
+
|
|
104
|
+
def __repr__(self) -> str:
|
|
105
|
+
return f"Calibration(model={self.model_id['architecture']}, layers={self.model_id['layers']})"
|
|
106
|
+
|
|
107
|
+
def typical(self, k: int):
|
|
108
|
+
ub, ab = self.ubar[k], self.abar[k]
|
|
109
|
+
dev = ub.device
|
|
110
|
+
Q, QA, lam, rid = self.Q[k].to(dev), self.QA[k].to(dev), self.lam[k], self.rho[k]
|
|
111
|
+
|
|
112
|
+
def fn(u, s):
|
|
113
|
+
uc = u.float() - ub
|
|
114
|
+
return (ab.expand_as(uc) + uc @ Q / (lam[None] + rid) @ QA).to(u.dtype)
|
|
115
|
+
|
|
116
|
+
return fn
|
|
117
|
+
|
|
118
|
+
def to(self, device):
|
|
119
|
+
mv = lambda t: t.to(device)
|
|
120
|
+
return Calibration(
|
|
121
|
+
mv(self.ubar),
|
|
122
|
+
mv(self.abar),
|
|
123
|
+
self.Q if self.Q.device.type == "cpu" else mv(self.Q),
|
|
124
|
+
mv(self.lam),
|
|
125
|
+
self.QA if self.QA.device.type == "cpu" else mv(self.QA),
|
|
126
|
+
mv(self.rho),
|
|
127
|
+
self.stats,
|
|
128
|
+
self.model_id,
|
|
129
|
+
)
|
|
130
|
+
|
|
131
|
+
def save(self, path: str) -> None:
|
|
132
|
+
torch.save(
|
|
133
|
+
{
|
|
134
|
+
"format": 1,
|
|
135
|
+
"model_id": self.model_id,
|
|
136
|
+
"stats": self.stats,
|
|
137
|
+
**{k: getattr(self, k).cpu() for k in ("ubar", "abar", "Q", "lam", "QA", "rho")},
|
|
138
|
+
},
|
|
139
|
+
path,
|
|
140
|
+
)
|
|
141
|
+
|
|
142
|
+
@staticmethod
|
|
143
|
+
def load(path: str, model: Model | None = None) -> Calibration:
|
|
144
|
+
z = torch.load(path, map_location="cpu", weights_only=True)
|
|
145
|
+
if z.get("format") != 1:
|
|
146
|
+
raise ValueError(f"{path}: not a lexframe calibration")
|
|
147
|
+
c = Calibration(z["ubar"], z["abar"], z["Q"], z["lam"], z["QA"], z["rho"], z["stats"], z["model_id"])
|
|
148
|
+
if model is not None:
|
|
149
|
+
if c.model_id != model_id(model):
|
|
150
|
+
raise ValueError(f"{path} was calibrated for {c.model_id}, not for {model_id(model)}")
|
|
151
|
+
c = c.to(model.device)
|
|
152
|
+
return c
|
|
153
|
+
|
|
154
|
+
|
|
155
|
+
def model_id(m: Model) -> dict:
|
|
156
|
+
w = lambda p: float(sum((t.double().sum() for t in p.parameters())))
|
|
157
|
+
return {
|
|
158
|
+
"architecture": type(m.hf).__name__,
|
|
159
|
+
"layers": m.n_layers,
|
|
160
|
+
"hidden": m.hidden_dim,
|
|
161
|
+
"checksum": round(w(m.layers[0]) + w(m.layers[-1]), 3),
|
|
162
|
+
}
|
|
163
|
+
|
|
164
|
+
|
|
165
|
+
def chunks(m: Model, texts: Iterable[str], length: int = 128, batch: int = 8, limit: int | None = None):
|
|
166
|
+
if m.tokenizer is None:
|
|
167
|
+
raise ValueError("this model has no tokenizer: pass token ids to calibrate")
|
|
168
|
+
eos = m.tokenizer.eos_token_id
|
|
169
|
+
buf, out, n = [], [], 0
|
|
170
|
+
for t in texts:
|
|
171
|
+
if not t or not t.strip():
|
|
172
|
+
continue
|
|
173
|
+
buf += m.tokenizer.encode(t, add_special_tokens=False) + ([eos] if eos is not None else [])
|
|
174
|
+
while len(buf) >= length:
|
|
175
|
+
out.append(buf[:length])
|
|
176
|
+
buf = buf[length:]
|
|
177
|
+
n += 1
|
|
178
|
+
if len(out) == batch:
|
|
179
|
+
yield torch.tensor(out)
|
|
180
|
+
out = []
|
|
181
|
+
if limit is not None and n >= limit:
|
|
182
|
+
if out:
|
|
183
|
+
yield torch.tensor(out)
|
|
184
|
+
return
|
|
185
|
+
if out:
|
|
186
|
+
yield torch.tensor(out)
|
|
187
|
+
|
|
188
|
+
|
|
189
|
+
@torch.no_grad()
|
|
190
|
+
def calibrate(m: Model, batches: Iterable, folds: int = 2, work=torch.float32) -> Calibration:
|
|
191
|
+
bank = _Moments(m, batches, folds, work)
|
|
192
|
+
L, d, dev, K = bank.L, bank.d, bank.dev, bank.K
|
|
193
|
+
f64 = torch.float64
|
|
194
|
+
store = "cpu" if L * d * d * 8 > 1500000000.0 else dev
|
|
195
|
+
ubar = torch.empty(L, d, device=dev)
|
|
196
|
+
abar = torch.empty(L, d, device=dev)
|
|
197
|
+
stats = []
|
|
198
|
+
Q = torch.empty(L, d, d, device=store)
|
|
199
|
+
QA = torch.empty(L, d, d, device=store)
|
|
200
|
+
lam = torch.empty(L, d, device=dev)
|
|
201
|
+
rho = torch.empty(L, device=dev)
|
|
202
|
+
grid = torch.tensor(RHO, dtype=f64, device=dev)
|
|
203
|
+
for k in range(L):
|
|
204
|
+
mo = bank.moments(k)
|
|
205
|
+
n, mu, ma, Cuu, Cua, _ = _merge(mo)
|
|
206
|
+
ubar[k], abar[k] = mu.float(), ma.float()
|
|
207
|
+
rg = grid * (Cuu.diagonal().sum() / d)
|
|
208
|
+
err = torch.zeros_like(rg)
|
|
209
|
+
tts = 0.0
|
|
210
|
+
for g in range(K):
|
|
211
|
+
_, mug, mag, Cg, Bg, _ = _merge([mo[i] for i in range(K) if i != g])
|
|
212
|
+
nf, _, _, Cf, Bf, tf = mo[g]
|
|
213
|
+
eu, ea = mo[g][1] - mug, mo[g][2] - mag
|
|
214
|
+
Duu = Cf + nf * torch.outer(eu, eu)
|
|
215
|
+
Dua = Bf + nf * torch.outer(eu, ea)
|
|
216
|
+
tt = tf + nf * float(ea @ ea)
|
|
217
|
+
tts += tt
|
|
218
|
+
ev, Qg = torch.linalg.eigh(Cg.to(work))
|
|
219
|
+
ev = ev.double().clamp_min(0)
|
|
220
|
+
QAg = Qg.T @ Bg.to(work)
|
|
221
|
+
Dr = Qg.T @ Duu.to(work) @ Qg
|
|
222
|
+
p = (Qg.T @ Dua.to(work) * QAg).sum(1).double()
|
|
223
|
+
KS = (QAg @ QAg.T * Dr).double()
|
|
224
|
+
W = 1.0 / (ev[None] + rg[:, None])
|
|
225
|
+
err += tt - 2 * (W @ p) + (W @ KS * W).sum(1)
|
|
226
|
+
j = int(err.argmin())
|
|
227
|
+
ev, Qk = torch.linalg.eigh(Cuu.to(work))
|
|
228
|
+
ev = ev.double().clamp_min(0)
|
|
229
|
+
QAk = Qk.T @ Cua.to(work)
|
|
230
|
+
rk = float(rg[j])
|
|
231
|
+
stats.append(
|
|
232
|
+
{
|
|
233
|
+
"rows": n,
|
|
234
|
+
"ridge_relative": RHO[j],
|
|
235
|
+
"dof": float((ev / (ev + rk)).sum()),
|
|
236
|
+
"r2_heldout": 1 - float(err[j]) / tts,
|
|
237
|
+
}
|
|
238
|
+
)
|
|
239
|
+
Q[k], QA[k], lam[k], rho[k] = Qk.float().to(store), QAk.float().to(store), ev.float(), rk
|
|
240
|
+
return Calibration(ubar, abar, Q, lam, QA, rho, stats, model_id(m))
|