Spaces:
Running
Running
File size: 7,397 Bytes
a1dd5ba | 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 | """CHANNEL STUDENTS: an FDNN head per teacher, over vectors already computed.
The teachers - PE, SigLIP2 so400m, InternVideo2-1B, XCLIP, V-JEPA2 -
were built for accuracy and they are not deployable. Measured per
episode at write time: pe 1.17 s, sig2 0.70, iv2 1.14, xclip 0.49,
before V-JEPA2 runs twice more. That is the retrieval path costing an
order of magnitude more than the entire rest of the write, which makes
the teachers part of the product instead of part of the training.
A teacher's job is to be RIGHT. A student's job is to be THERE.
THE INPUT IS ALREADY PAID FOR. The write path streams every frame
through FDNN-V and stores a 1152-d vector per frame, at 0.020 s per
episode. Every channel output is a function of the same pixels, so
every channel can be a small head over those vectors instead of a
second pass over the video. The teacher is run ONCE, offline, to make
training pairs; after that it never touches an ingest again.
SKELETON, not a bag of layers. Each teacher pools a SPAN of frames into
one vector - a window for PE and SigLIP2, a whole clip for IV2, XCLIP
and V-JEPA2 - so the student mirrors that shape:
attention pool over the span's frame vectors (one learned query,
the teacher's own
aggregation step)
pre-norm residual MLP -> the teacher's dimension
L2 normalise (every consumer
compares by cosine,
so the norm is not
part of the signal)
Mean pooling was the obvious first choice and it is wrong for the same
reason it is wrong in the teachers: a window's meaning is carried by a
few frames, and averaging buries them under the static ones.
Inference is deliberately NUMPY. The head is two small matmuls; running
them under a training framework costs more in dispatch than the
arithmetic, and the write path should not have to import one.
"""
from __future__ import annotations
import json
from pathlib import Path
import numpy as np
# teacher output dimensions, by channel
DIMS = {"pe": 1024, "sig2": 1152, "iv2": 512, "xclip": 768,
"vjepa": 1024, "act": 174}
FDNNV_D = 1152
def _l2(x, axis=-1):
return x / (np.linalg.norm(x, axis=axis, keepdims=True) + 1e-8)
class Head:
"""A distilled channel. Loads from .npz, runs in numpy."""
def __init__(self, w: dict):
self.q = w["q"] # (d_in,) pooling query
self.W1, self.b1 = w["W1"], w["b1"]
self.W2, self.b2 = w["W2"], w["b2"]
self.Wp, self.bp = w["Wp"], w["bp"]
self.g, self.beta = w["g"], w["beta"]
@staticmethod
def load(path):
z = np.load(path)
return Head({k: z[k] for k in z.files})
def pool(self, X):
"""Attention-pool (n_frames, d) -> (d,) with one learned query."""
if len(X) == 0:
return np.zeros(self.q.shape[0], np.float32)
s = X @ self.q
s -= s.max()
a = np.exp(s)
a /= a.sum() + 1e-8
return (a[:, None] * X).sum(0)
def __call__(self, X):
"""(n_frames, 1152) frame vectors of one span -> teacher vector."""
h = self.pool(np.asarray(X, np.float32))
# pre-norm: the pooled vector's scale varies with span length
h = self.g * (h - h.mean()) / (h.std() + 1e-6) + self.beta
z = np.maximum(h @ self.W1 + self.b1, 0) # ReLU
h = h + z @ self.W2 + self.b2 # residual
return _l2(h @ self.Wp + self.bp)
def batch(self, spans):
"""Many spans at once - the write path's actual call shape."""
return np.stack([self(X) for X in spans]) if spans else \
np.zeros((0, self.Wp.shape[1]), np.float32)
def pairs(store, channel, frames_table="frame_vectors"):
"""(list of frame-vector spans, teacher matrix) for one channel.
A teacher row carries ts..t1; the student sees exactly the frame
vectors inside that span, which is the same evidence the teacher
had and no more. Spans with no frames are dropped rather than
zero-filled - a student taught to map emptiness to a real vector
learns to hallucinate.
"""
import pyarrow.compute as pc
tv = store.table(f"{channel}_vectors").scan()
fv = store.table(frames_table).scan()
fts = np.asarray(fv.column("ts").to_pylist(), np.int64)
fst = np.asarray([str(s) for s in fv.column("stream").to_pylist()])
F = np.asarray(fv.column("vector").to_pylist(), np.float32)
order = np.argsort(fts, kind="stable")
fts, fst, F = fts[order], fst[order], F[order]
t0 = np.asarray(tv.column("ts").to_pylist(), np.int64)
t1 = np.asarray(tv.column("t1").to_pylist(), np.int64)
tst = np.asarray([str(s) for s in tv.column("stream").to_pylist()])
T = _l2(np.asarray(tv.column("vector").to_pylist(), np.float32))
X, Y, keys = [], [], []
for i in range(len(t0)):
lo = np.searchsorted(fts, t0[i], "left")
hi = np.searchsorted(fts, t1[i], "right")
if hi <= lo:
continue
sel = slice(lo, hi)
m = fst[sel] == tst[i]
if not m.any():
continue
X.append(F[sel][m])
Y.append(T[i])
keys.append((tst[i], int(t0[i])))
return X, (np.stack(Y) if Y else np.zeros((0, 1), np.float32)), keys
def init(d_in=FDNNV_D, d_out=1024, hidden=512, seed=0):
r = np.random.default_rng(seed)
def n(*s):
return (r.normal(size=s) / np.sqrt(s[0])).astype(np.float32)
return {"q": n(d_in), "W1": n(d_in, hidden), "b1": np.zeros(hidden, np.float32),
"W2": n(hidden, d_in), "b2": np.zeros(d_in, np.float32),
"Wp": n(d_in, d_out), "bp": np.zeros(d_out, np.float32),
"g": np.ones(d_in, np.float32), "beta": np.zeros(d_in, np.float32)}
def fidelity(head, X, Y):
"""Cosine to the teacher, ITS TRIVIAL BASELINE, and rank agreement.
Cosine alone is not weak, it is actively misleading here, and the
first PE student proved it: 0.9168 test cosine, which reads like a
working student until you compute what a CONSTANT prediction of the
corpus mean scores - 0.8629. The teacher's space is anisotropic
(mean pairwise cosine 0.885), so almost all of that 0.92 is the
shared mean and almost none of it is the episode. Nearest-neighbour
agreement was 0.005.
So every report carries `mean_baseline` next to `cosine`, and the
number that decides whether a student ships is rank agreement -
ranking is the only thing a retrieval channel is ever used for.
"""
P = head.batch(X)
cos = float(np.mean(np.sum(P * Y, 1)))
mu = _l2(Y.mean(0))
base = float(np.mean(Y @ mu))
n = min(len(P), 400)
Sp, St = P[:n] @ P[:n].T, Y[:n] @ Y[:n].T
np.fill_diagonal(Sp, -9); np.fill_diagonal(St, -9)
top1 = float(np.mean(Sp.argmax(1) == St.argmax(1)))
k = min(10, n - 1)
rp = np.argsort(-Sp, 1)[:, :k]
rt = np.argsort(-St, 1)[:, :k]
rec = float(np.mean([len(set(a) & set(b)) / k for a, b in zip(rp, rt)]))
return {"cosine": round(cos, 4), "mean_baseline": round(base, 4),
"lift_over_mean": round(cos - base, 4),
"nn_top1": round(top1, 4), "nn_recall@10": round(rec, 4)}
|