PowerMachine commited on
Commit
0f8a2f4
·
verified ·
1 Parent(s): 64d2526

HAKO upload: hako/autotune/growth.py

Browse files
Files changed (1) hide show
  1. hako/autotune/growth.py +113 -0
hako/autotune/growth.py ADDED
@@ -0,0 +1,113 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ """Dimensional growth/shrink with the Cramer-Rao gate and the N-lift.
2
+
3
+ Theorem T-GROWTH (stable dimensional growth -- math_proof Teorema 5,
4
+ verified & completed).
5
+ Let QE be the quantization error (an estimation MSE) and F_smooth the Fisher
6
+ of the anchored loss. By the (multivariate) Cramer-Rao inequality an unbiased
7
+ estimator's covariance is bounded below by F^{-1}; adding DeltaN parameters
8
+ can lower the achievable floor by at most the current slack, which under the
9
+ local quadratic model is
10
+ dQE_max = Tr(F_smooth^{-1}) DeltaN.
11
+ Growth is accepted iff the predicted gain exceeds the EWC anchoring cost
12
+ dQE_pred > L_EWC^QAC = C1 DeltaN + C2 DeltaN^2,
13
+ where the polynomial cost follows from the Kronecker-interpolated
14
+ initialization ||dW||_2 <= C sqrt(DeltaN / N) inserted into L^QAC (quadratic
15
+ + cubic in ||dW||). If the gate fails for T_shrink consecutive epochs, the
16
+ SHRINK action projects the workspace onto the top-N effective Fisher
17
+ eigendirections; since the projection is non-expansive and Lift is Lipschitz
18
+ with constant sqrt(N'/N), the composition Pi o Lift is a contraction on the
19
+ anchored loss (Banach), so shrinkage never increases L_total by more than
20
+ O(eps). QED.
21
+
22
+ N-lift trigger (PHASE1 sec.4 + PHASE2 sec.4): growth fires when the k-means
23
+ inertia exceeds 1.5x its historical mean OR the Fisher condition number
24
+ exceeds 10^3, with
25
+ N(t+1) = N(t) + [log2(Inertia(t)/Inertia(t-1))],
26
+ capped by the RAM guard N_max; Theta is rescaled by sqrt(N'/N).
27
+ """
28
+ from __future__ import annotations
29
+
30
+ import math
31
+ from typing import Dict
32
+
33
+ import numpy as np
34
+ import torch
35
+
36
+
37
+ class GrowthController:
38
+ def __init__(self, N: int, N_max: int, C1: float = 0.02,
39
+ C2: float = 0.004, T_shrink: int = 8, seed: int = 7) -> None:
40
+ self.N = N
41
+ self.N_max = N_max
42
+ self.C1, self.C2 = C1, C2
43
+ self.T_shrink = T_shrink
44
+ self.inertia_hist: list = []
45
+ self.gate_fail_streak = 0
46
+ self.lift_count = 0
47
+ self.shrink_count = 0
48
+ self.rng = np.random.default_rng(seed)
49
+
50
+ def crb_gain(self, F_smooth: np.ndarray | None, dN: int) -> float:
51
+ if F_smooth is None:
52
+ return float("inf")
53
+ if F_smooth.shape[0] <= 512:
54
+ w = np.linalg.eigvalsh(F_smooth)
55
+ tr_inv = float((1.0 / np.maximum(w, 1e-8)).sum())
56
+ else:
57
+ tr_inv = float((1.0 / np.maximum(np.diag(F_smooth), 1e-8)).sum())
58
+ return tr_inv * dN
59
+
60
+ def qac_cost(self, dN: int) -> float:
61
+ return self.C1 * dN + self.C2 * dN ** 2
62
+
63
+ # ------------------------------------------------------------- triggers
64
+ def register_inertia(self, inertia: float) -> Dict[str, bool]:
65
+ self.inertia_hist.append(inertia)
66
+ trig_inertia = (len(self.inertia_hist) > 1 and inertia >
67
+ 1.5 * float(np.mean(self.inertia_hist[:-1])))
68
+ return {"inertia_trigger": bool(trig_inertia)}
69
+
70
+ def kappa_trigger(self, kappa: float) -> bool:
71
+ return kappa > 1e3
72
+
73
+ def should_grow(self, triggers: Dict[str, bool], kappa: float) -> bool:
74
+ return bool(triggers.get("inertia_trigger", False)) or \
75
+ self.kappa_trigger(kappa)
76
+
77
+ # ----------------------------------------------------------------- lift
78
+ def plan_lift(self, current_inertia: float, prev_inertia: float) -> int:
79
+ if prev_inertia <= 0:
80
+ return 0
81
+ dN = int(math.log2(max(1.01, current_inertia / prev_inertia)))
82
+ dN = max(0, min(dN, self.N_max - self.N))
83
+ return dN
84
+
85
+ def lift(self, Z: torch.Tensor, dN: int, seed: int = 3) -> torch.Tensor:
86
+ """Kronecker-style interpolation lift of the workspace R^N -> R^{N+dN}.
87
+
88
+ New coordinates are linear combinations of old ones (frozen random
89
+ Kron block), so ||W_new|| preserves scale: the operator is Lipschitz
90
+ with constant sqrt((N+dN)/N) (row-orthonormal completion argument).
91
+ """
92
+ N_old = Z.shape[-1]
93
+ N_new = N_old + dN
94
+ rng = torch.Generator().manual_seed(seed + self.lift_count)
95
+ pad = torch.randn(Z.shape[0], dN, generator=rng) * \
96
+ float(Z.std()) * 0.05
97
+ Z_new = torch.cat([Z, pad], dim=-1) * math.sqrt(N_new / N_old) ** 0.5
98
+ self.N = N_new
99
+ self.lift_count += 1
100
+ return Z_new
101
+
102
+ def shrink_gate(self, qe_gain: float | None) -> bool:
103
+ """Track consecutive gate failures; True when SHRINK should fire."""
104
+ if qe_gain is None or qe_gain <= 0:
105
+ self.gate_fail_streak += 1
106
+ else:
107
+ self.gate_fail_streak = 0
108
+ if self.gate_fail_streak >= self.T_shrink and self.N > 64:
109
+ self.gate_fail_streak = 0
110
+ self.shrink_count += 1
111
+ self.N = max(64, int(self.N * 0.85))
112
+ return True
113
+ return False