tensor-roll / tensor_roll /search.py
SNAPKITTYWEST's picture
October 2026 main drop: mirror from GitHub
9e551a1 verified
Raw History Blame Contribute Delete
9.47 kB
# Tensor Roll — Recursive CUDA-Q Model Quantizer
# Copyright (C) 2026 SnapKitty Collective
# SPDX-License-Identifier: AGPL-3.0-or-later
"""Representation search: 30B -> 8B (parameterized).
Pipeline:
1. Per-tensor TensorRoll analysis (core.roll_tree) -> measured options.
2. Global greedy selection under the hard parameter budget.
3. Student *architecture* search over (d, heads, dff, layers): each
candidate is scored by projected retained energy, where every teacher
tensor is mapped to the candidate shape by truncated-SVD projection
and its retained singular energy is measured. No hard-coded answer —
the argmax under the budget wins.
4. Student weight init from the teacher via the winning projections
(Xavier where no projection exists), with per-tensor provenance.
The budget is P_target <= 8.5B for the real 30B path. At small scale the
same ratio applies: budget = teacher_params * (8.5/30).
"""
from __future__ import annotations
import itertools
import numpy as np
from . import core as C
from .model import Config, count_params, init_params
from . import qsim as Q
BUDGET_RATIO = 8.5 / 30.0 # hard architectural budget, same at every scale
ABS_BUDGET_CAP = 8_500_000_000
def _svd_cache(params: dict) -> dict:
cache = {}
for k, v in params.items():
v = np.asarray(v, dtype=np.float64)
if v.ndim == 2 and min(v.shape) > 1:
U, s, Vt = np.linalg.svd(v, full_matrices=False)
cache[k] = (U, s, Vt)
return cache
def project_matrix(W: np.ndarray, out_shape: tuple, svd=None):
"""Truncated-SVD projection of W to out_shape (both dims <= input dims).
Returns (W_proj, retained_energy_fraction). Optimal rank-k approximation,
cropped — the classical baseline every exotic option is measured against.
"""
W = np.asarray(W, dtype=np.float64)
mo, no = out_shape
m, n = W.shape
assert mo <= m and no <= n, f"projection only shrinks: {W.shape} -> {out_shape}"
if svd is None:
U, s, Vt = np.linalg.svd(W, full_matrices=False)
else:
U, s, Vt = svd
k = min(mo, no)
Wp = (U[:mo, :k] * s[:k]) @ Vt[:k, :no]
total = float((s ** 2).sum())
energy = float((s[:k] ** 2).sum() / total) if total > 0 else 1.0
return Wp.astype(np.float32), energy
def _candidate_configs(teacher_cfg: Config):
"""Small grid of student architectures to search (real 30B path uses a
wider grid over the same code)."""
ds = sorted({teacher_cfg.d // 2, max(8, teacher_cfg.d * 3 // 4)})
hs = sorted({max(1, teacher_cfg.heads - 1), teacher_cfg.heads})
dffs = sorted({teacher_cfg.dff // 2, max(16, teacher_cfg.dff * 3 // 4)})
ls = sorted({max(1, teacher_cfg.layers - 1), teacher_cfg.layers})
for d, h, dff, l in itertools.product(ds, hs, dffs, ls):
if d % h == 0:
yield Config(vocab=teacher_cfg.vocab, d=d, heads=h,
layers=l, dff=dff, seq=teacher_cfg.seq)
def _config_param_count(cfg: Config) -> int:
# mirrors init_params structure without allocating
n = cfg.vocab * cfg.d
per_layer = (4 * cfg.d * cfg.d + 4 * cfg.d # q,k,v,o + biases
+ 4 * cfg.d # two layernorms
+ 2 * cfg.d * cfg.dff + cfg.dff + cfg.d) # mlp
n += cfg.layers * per_layer
n += 2 * cfg.d + cfg.d * cfg.vocab + cfg.vocab
return n
def search_student_config(teacher_params: dict, teacher_cfg: Config,
budget_params: float):
"""Returns (best_cfg, scored_list). scored_list entries are
(cfg, est_energy, param_count), sorted by energy desc."""
svd = _svd_cache(teacher_params)
scored = []
for cfg in _candidate_configs(teacher_cfg):
pc = _config_param_count(cfg)
if pc > budget_params:
continue
energies, weights = [], []
for k, v in teacher_params.items():
v = np.asarray(v)
if v.ndim != 2 or k not in svd:
continue
# map teacher key -> student key shape
sk = _student_key(k, cfg, teacher_cfg)
if sk is None:
continue
sshape = _student_shape(k, v.shape, cfg, teacher_cfg)
if sshape is None or sshape[0] > v.shape[0] or sshape[1] > v.shape[1]:
continue
_, e = project_matrix(v, sshape, svd[k])
energies.append(e)
weights.append(v.size)
est = float(np.average(energies, weights=weights)) if energies else 0.0
scored.append({"cfg": cfg.to_dict(), "est_energy": est, "params": pc})
scored.sort(key=lambda s: -s["est_energy"])
if not scored:
raise RuntimeError("no student config fits the budget; cannot proceed honestly")
best = Config.from_dict(scored[0]["cfg"])
return best, scored
def _student_key(k: str, cfg: Config, tcfg: Config):
# student uses identical key names; layer count may shrink
if k.startswith("L"):
l = int(k[1:].split(".")[0])
if l >= cfg.layers:
return None
return k
def _student_shape(k: str, tshape: tuple, cfg: Config, tcfg: Config):
d, td = cfg.d, tcfg.d
if k == "emb":
return (cfg.vocab, d)
if k in ("lnfg", "lnfb"):
return (d,)
if k == "Whead":
return (d, cfg.vocab)
if k == "bhead":
return (cfg.vocab,)
if k.startswith("L"):
rest = k.split(".", 1)[1]
if rest in ("ln1g", "ln1b", "ln2g", "ln2b"):
return (d,)
if rest in ("Wq", "Wk", "Wv", "Wo"):
return (d, d)
if rest in ("bq", "bk", "bv", "bo"):
return (d,)
if rest == "W1":
return (d, cfg.dff)
if rest == "b1":
return (cfg.dff,)
if rest == "W2":
return (cfg.dff, d)
if rest == "b2":
return (d,)
return None
def init_student_from_teacher(teacher_params: dict, teacher_cfg: Config,
student_cfg: Config, rng: np.random.Generator):
"""Project teacher weights into the student shapes; Xavier elsewhere.
Returns (student_params, provenance, achieved_energies)."""
student = init_params(rng, student_cfg) # Xavier baseline everywhere
svd = _svd_cache(teacher_params)
provenance, energies = {}, {}
for k, v in teacher_params.items():
sk = _student_key(k, student_cfg, teacher_cfg)
if sk is None or sk not in student:
provenance[k] = "dropped-by-architecture-search"
continue
sv = student[sk]
v = np.asarray(v)
if (v.ndim == 2 and sv.ndim == 2 and k in svd
and sv.shape[0] <= v.shape[0] and sv.shape[1] <= v.shape[1]):
proj, e = project_matrix(v, sv.shape, svd[k])
student[sk] = proj
provenance[k] = f"truncated-svd-projection energy={e:.4f}"
energies[k] = e
elif v.shape == sv.shape:
student[sk] = v.astype(np.float32)
provenance[k] = "copied-exact"
energies[k] = 1.0
else:
provenance[k] = "xavier-init(shape-mismatch)"
energies[k] = 0.0
return student, provenance, energies
def head_consolidation_report(teacher_params: dict, teacher_cfg: Config,
student_cfg: Config) -> dict:
"""Which attention heads survived consolidation, by measured spectral
energy of each head's QK block."""
report = {}
for l in range(teacher_cfg.layers):
if l >= student_cfg.layers:
continue
Wq = np.asarray(teacher_params[f"L{l}.Wq"], dtype=np.float64)
dh = teacher_cfg.dh
energies = []
for h in range(teacher_cfg.heads):
blk = Wq[:, h * dh:(h + 1) * dh]
s = np.linalg.svd(blk, compute_uv=False)
energies.append(float((s ** 2).sum()))
order = sorted(range(len(energies)), key=lambda h: -energies[h])
kept = sorted(order[:student_cfg.heads])
report[f"layer_{l}"] = {
"head_energies": [round(e, 4) for e in energies],
"kept_heads": kept,
"dropped_heads": sorted(order[student_cfg.heads:]),
}
return report
def run_roll_analysis(teacher_params: dict, *, depth: int = 3,
quantum_policy: str = "off", rank: int = 0,
stats: dict = None):
"""Run the first-class TensorRoll(T, axis, depth, rank, quantum_policy)
operator over every teacher tensor. Returns (trees, leaves).
`stats`: optional {tensor_name: {"act_rms", "grad_norm", "grad_rel"}}
from model.measure_tensor_stats; attached to each leaf's metrics so the
search sees measured activation/gradient contributions.
"""
trees, leaves = {}, []
for k in sorted(teacher_params.keys()):
W = np.asarray(teacher_params[k])
res = C.TensorRoll(W, 0, depth, rank, quantum_policy,
qsim_encode=Q.qsim_encode_for_roll
if quantum_policy != "off" else None,
name=k)
trees[k] = res["tree"]
for leaf in C.iter_leaves(res["tree"]):
if stats and k in stats:
leaf.metrics["act_rms"] = stats[k]["act_rms"]
leaf.metrics["grad_norm"] = stats[k]["grad_norm"]
leaf.metrics["grad_rel"] = stats[k]["grad_rel"]
leaves.append(leaf)
return trees, leaves