Download tensor_roll/distill.py from Snapkitty/tensor-roll: direct link, hf CLI and curl.
- Browser
- Download file 8.63 kB
-
https://huggingface.co/Snapkitty/tensor-roll/resolve/main/tensor_roll/distill.py
- Command line
-
hf download hf://Snapkitty/tensor-roll/tensor_roll/distill.py
-
curl -L -o distill.py https://huggingface.co/Snapkitty/tensor-roll/resolve/main/tensor_roll/distill.py
8.63 kB
| # Tensor Roll — Recursive CUDA-Q Model Quantizer | |
| # Copyright (C) 2026 SnapKitty Collective | |
| # SPDX-License-Identifier: AGPL-3.0-or-later | |
| """Recursive distillation with the multi-objective loss. | |
| L = l1*L_logits + l2*L_hidden + l3*L_attention + l4*L_embedding | |
| + l5*L_task + l6*L_reconstruction + l7*L_roll | |
| All seven terms are real computed quantities (no placeholders): | |
| logits : tau^2 * KL(softmax(teacher/tau) || softmax(student/tau)) | |
| hidden : MSE(student_final @ Ph, teacher_final), Ph frozen random proj | |
| attention : MSE(mean-head attention probs), layer-aligned | |
| embedding : MSE(student_emb @ Pe, teacher_emb), Pe frozen random proj | |
| task : cross-entropy on the synthetic grammar | |
| reconstruction: MSE(student weights, projected-teacher init targets) | |
| roll : MSE(achieved per-tensor energy, roll-plan energy) — monitored, | |
| gradient detached (documented; proxy grad via reconstruction) | |
| Recursive distillation metrics (spec section 8) are computed per recursion | |
| level: model -> layer group -> tensor -> sub-block, each reporting teacher | |
| tensor, student tensor, relative error, cosine similarity, KL divergence, | |
| retained energy. | |
| """ | |
| from __future__ import annotations | |
| import numpy as np | |
| from . import core as C | |
| from .model import backward, ce_loss, forward, kl_loss | |
| DEFAULT_LAMBDAS = { | |
| "logits": 1.0, | |
| "hidden": 0.3, | |
| "attention": 0.3, | |
| "embedding": 0.1, | |
| "task": 1.0, | |
| "reconstruction": 0.1, | |
| "roll": 0.1, | |
| } | |
| def _align(a: np.ndarray, shape: tuple) -> np.ndarray: | |
| """Shape alignment for comparison only: truncate or zero-pad. This is a | |
| measurement convenience, documented here — not a claim about the model.""" | |
| a = np.asarray(a, dtype=np.float64) | |
| out = np.zeros(shape, dtype=np.float64) | |
| sl = tuple(slice(0, min(x, y)) for x, y in zip(a.shape, shape)) | |
| out[sl] = a[sl] | |
| return out | |
| def distill_step(s_params: dict, s_cfg, t_params: dict, t_cfg, | |
| ids: np.ndarray, *, lambdas=None, tau: float = 2.0, | |
| proj_h=None, proj_e=None, recon_targets=None, | |
| roll_plan_energies=None, achieved_energies=None): | |
| """One recursive-distillation step. Returns (total_loss, terms, grads).""" | |
| lambdas = {**DEFAULT_LAMBDAS, **(lambdas or {})} | |
| B = ids.shape[0] | |
| inputs, targets = ids[:, :-1], ids[:, 1:] | |
| t_logits, t_cache = forward(t_params, t_cfg, inputs) | |
| s_logits, s_cache = forward(s_params, s_cfg, inputs) | |
| terms = {} | |
| # L_task + L_logits -> dlogits | |
| terms["task"], dl_task = ce_loss(s_logits, targets) | |
| terms["logits"], dl_kl = kl_loss(s_logits, t_logits, tau) | |
| dlogits = lambdas["task"] * dl_task + lambdas["logits"] * dl_kl | |
| # L_attention: mean-over-heads, layer-aligned (first min(L) layers) | |
| d_attn = {} | |
| n_align = min(s_cfg.layers, t_cfg.layers) | |
| attn_num = 0.0 | |
| for l in range(n_align): | |
| ps = s_cache[f"L{l}"]["P"].mean(axis=1) # (B,T,T) | |
| pt = t_cache[f"L{l}"]["P"].mean(axis=1) | |
| diff = ps - pt | |
| terms[f"attention_l{l}"] = float((diff ** 2).mean()) | |
| attn_num += terms[f"attention_l{l}"] | |
| dmean = (2.0 * diff / diff.size) * lambdas["attention"] / max(n_align, 1) | |
| d_attn[l] = np.broadcast_to( | |
| dmean[:, None, :, :], s_cache[f"L{l}"]["P"].shape).copy() | |
| terms["attention"] = attn_num / max(n_align, 1) | |
| # L_hidden: final hidden, frozen random projection to teacher dim | |
| d_hiddens = {} | |
| if proj_h is not None: | |
| fs = s_cache["f"] # (B,T,ds) | |
| ft = t_cache["f"] # (B,T,dt) | |
| pred = fs @ proj_h | |
| diff = pred - ft | |
| terms["hidden"] = float((diff ** 2).mean()) | |
| d_hiddens[s_cfg.layers - 1] = ( | |
| (2.0 * diff / diff.size) @ proj_h.T) * lambdas["hidden"] | |
| else: | |
| terms["hidden"] = 0.0 | |
| grads = backward(s_params, s_cfg, s_cache, dlogits, | |
| d_hiddens=d_hiddens or None, d_attn=d_attn or None) | |
| # L_embedding | |
| if proj_e is not None: | |
| es = s_params["emb"].astype(np.float64) | |
| et = t_params["emb"].astype(np.float64) | |
| diff = es @ proj_e - et | |
| terms["embedding"] = float((diff ** 2).mean()) | |
| grads["emb"] = grads["emb"] + ( | |
| (2.0 * diff / diff.size) @ proj_e.T * lambdas["embedding"] | |
| ).astype(np.float32) | |
| else: | |
| terms["embedding"] = 0.0 | |
| # L_reconstruction: stay near the projected-teacher init targets | |
| if recon_targets: | |
| rn, rv = 0.0, 0 | |
| for k, tgt in recon_targets.items(): | |
| if k in grads and grads[k].shape == tgt.shape: | |
| w = s_params[k].astype(np.float64) | |
| diff = w - tgt.astype(np.float64) | |
| rn += float((diff ** 2).mean()) | |
| rv += 1 | |
| grads[k] = grads[k] + ( | |
| 2.0 * diff / diff.size * lambdas["reconstruction"]).astype(np.float32) | |
| terms["reconstruction"] = rn / max(rv, 1) | |
| else: | |
| terms["reconstruction"] = 0.0 | |
| # L_roll: achieved vs planned per-tensor retained energy (monitored) | |
| if roll_plan_energies and achieved_energies: | |
| vals = [(achieved_energies[k] - roll_plan_energies[k]) ** 2 | |
| for k in roll_plan_energies if k in achieved_energies] | |
| terms["roll"] = float(np.mean(vals)) if vals else 0.0 | |
| else: | |
| terms["roll"] = 0.0 | |
| total = sum(lambdas[k] * terms[k] for k in | |
| ("logits", "hidden", "attention", "embedding", "task", | |
| "reconstruction", "roll")) | |
| terms["total"] = float(total) | |
| return float(total), terms, grads | |
| def _kl_vec(p_logits: np.ndarray, q_logits: np.ndarray) -> float: | |
| p = p_logits.astype(np.float64).ravel() | |
| q = q_logits.astype(np.float64).ravel() | |
| p = p - p.max(); q = q - q.max() | |
| lp = p - np.log(np.exp(p).sum()); lq = q - np.log(np.exp(q).sum()) | |
| ep = np.exp(lp) | |
| return float((ep * (lp - lq)).sum()) | |
| def recursive_metrics(t_params: dict, s_params: dict) -> dict: | |
| """Per-recursion-level teacher/student comparison. | |
| Levels: model -> layer_group -> tensor -> subblock. Student tensors are | |
| shape-aligned to the teacher by truncate/zero-pad (comparison only). | |
| Each level reports: rel_err, cosine_sim, kl_div, retained_energy, | |
| teacher_params, student_params. | |
| """ | |
| out = {} | |
| def pack(params, keys): | |
| return np.concatenate([np.asarray(params[k]).ravel() for k in keys]) | |
| def level(name, t_keys, s_keys): | |
| tv = pack(t_params, t_keys).astype(np.float64) | |
| sv_raw = pack(s_params, s_keys).astype(np.float64) | |
| sv = _align(sv_raw, tv.shape) | |
| out[name] = { | |
| "teacher_params": int(tv.size), | |
| "student_params": int(sv_raw.size), | |
| "rel_err": C.relative_error(tv, sv), | |
| "cosine_sim": C.cosine_sim(tv, sv), | |
| "kl_div": _kl_vec(tv / (np.abs(tv).max() + 1e-9), | |
| sv / (np.abs(sv).max() + 1e-9)), | |
| "retained_energy": float((sv ** 2).sum() / ((tv ** 2).sum() + 1e-300)), | |
| } | |
| t_keys = sorted(t_params.keys()) | |
| s_keys = sorted(s_params.keys()) | |
| level("model", t_keys, s_keys) | |
| # layer groups | |
| layers = sorted({k.split(".")[0] for k in t_keys if k.startswith("L")}) | |
| for i, lg in enumerate(layers): | |
| tk = [k for k in t_keys if k.startswith(lg + ".")] | |
| sk = [k for k in s_keys if k.startswith(lg + ".")] | |
| if tk and sk: | |
| level(f"layer_group_{i}", tk, sk) | |
| # tensors (sample up to 12 for readability; full data in JSON) | |
| for k in t_keys[:12]: | |
| sk = [x for x in s_keys if x == k] | |
| if sk: | |
| level(f"tensor:{k}", [k], sk) | |
| # sub-blocks of the largest teacher tensor | |
| big = max(t_keys, key=lambda k: t_params[k].size) | |
| W = np.asarray(t_params[big], dtype=np.float64).ravel() | |
| sm = [x for x in s_keys if x == big] | |
| if sm: | |
| S = np.asarray(s_params[sm[0]], dtype=np.float64).ravel() | |
| for b, (ws, ss) in enumerate(zip(np.array_split(W, 4), | |
| np.array_split(S, 4))): | |
| sa = _align(ss, ws.shape) | |
| out[f"subblock:{big}[{b}]"] = { | |
| "teacher_params": int(ws.size), | |
| "student_params": int(ss.size), | |
| "rel_err": C.relative_error(ws, sa), | |
| "cosine_sim": C.cosine_sim(ws, sa), | |
| "kl_div": _kl_vec(ws / (np.abs(ws).max() + 1e-9), | |
| sa / (np.abs(sa).max() + 1e-9)), | |
| "retained_energy": float((sa ** 2).sum() / ((ws ** 2).sum() + 1e-300)), | |
| } | |
| return out | |