Spaces:
Running
Running
| """FDNN cellular turnover applied to the SigLIP ENCODER — where the time is. | |
| WHY HERE AND NOT THE LITTLE TOWER | |
| --------------------------------- | |
| Ingest cost is measured, and it is not the database: byte-range decode runs at | |
| 2.7 ms/frame while the SigLIP vision tower runs at 27.7-90.3 ms/frame. So 97% | |
| of ingest is one frozen 428M-parameter ViT. Pruning the 80k-parameter temporal | |
| tower — which is what was done first — optimises 0.001% of the bill. The | |
| encoder is the neuron population that matters. | |
| THE NEURON, HERE | |
| ---------------- | |
| Each encoder layer is `fc1 (1152 -> 4304) -> gelu -> fc2 (4304 -> 1152)`. | |
| Hidden unit *c* is a neuron in exactly FDNN's sense: it takes the residual | |
| stream, applies its own nonlinearity, and writes back through its own column | |
| of `fc2`. There are 27 x 4304 = 116,208 of them and they are 65% of the | |
| tower's parameters. | |
| Silencing one is `fc1.weight[c] = 0, fc1.bias[c] = 0` — gelu(0) = 0, so the | |
| channel contributes nothing through fc2. Removing one for real is slicing | |
| `fc1.weight[keep]`, `fc1.bias[keep]`, `fc2.weight[:, keep]`, which is a | |
| genuine FLOP reduction, not a multiply by zero. | |
| UTILIZATION IS DEFINED BY THE DATABASE'S WORKLOAD | |
| ------------------------------------------------- | |
| FDNN measures utilization as the increase in validation loss when a neuron is | |
| silenced. The equivalent here is NOT ImageNet accuracy — this encoder exists | |
| to produce vectors that a retrieval index ranks with. So utilization is the | |
| loss of EMBEDDING FIDELITY on frames from the actual corpus: | |
| fidelity = mean cosine( pruned_embedding, unpruned_embedding ) | |
| i.e. the unpruned encoder is its own teacher and the calibration set is the | |
| user's own data. A channel that matters for photographs of dogs but never | |
| fires on a robot arm in a toy kitchen is, for this database, dead weight. | |
| Ablating 116,208 channels one at a time is not affordable (one forward pass | |
| each). FDNN's own PPO feature vector already contains the cheap surrogates — | |
| activation magnitude and downstream weight norm — and their product is the | |
| standard structured-pruning saliency: | |
| saliency(c) = E_frames |act_c| * || fc2[:, c] || | |
| which is exactly "how much signal this neuron actually injects". Ablation is | |
| still used, but per LAYER (27 measurements, affordable), to calibrate how much | |
| each layer's saliency scale is worth. PPO then chooses keep-probabilities from | |
| the same five features FDNN used, and the reward is the same shape: | |
| reward = -(fidelity loss ratio) - lambda * kept_fraction | |
| """ | |
| from __future__ import annotations | |
| import time | |
| import mlx.core as mx | |
| import numpy as np | |
| # =========================================================================== | |
| # 0. Reaching the layers | |
| # =========================================================================== | |
| def vision_layers(model): | |
| """The encoder layer list of a SigLIP vision tower.""" | |
| vm = model.vision_model | |
| vm = getattr(vm, "vision_model", vm) | |
| return list(vm.encoder.layers) | |
| def mlp_width(layer): | |
| return np.array(layer.mlp.fc1.bias).shape[0] | |
| class _Tap: | |
| """Wraps an nn.Linear so the mean |output| per channel is recorded. | |
| MLX calls `self.fc1(x)` by attribute, so swapping the attribute is enough | |
| to observe it — no hooks, no forked forward pass that could drift from the | |
| real one. | |
| """ | |
| def __init__(self, inner): | |
| self.inner = inner | |
| self.acc = None | |
| self.n = 0 | |
| def __call__(self, x): | |
| out = self.inner(x) | |
| a = mx.mean(mx.abs(out).reshape(-1, out.shape[-1]), axis=0) | |
| self.acc = a if self.acc is None else self.acc + a | |
| self.n += 1 | |
| return out | |
| # =========================================================================== | |
| # 1. Saliency: activation magnitude x downstream weight norm | |
| # =========================================================================== | |
| def channel_saliency(model, images, model_id, batch=16): | |
| """Per-layer array of per-channel saliency, measured on REAL frames.""" | |
| from .embeddings import _embed_images | |
| layers = vision_layers(model) | |
| taps = [] | |
| for lyr in layers: | |
| t = _Tap(lyr.mlp.fc1) | |
| lyr.mlp.fc1 = t | |
| taps.append(t) | |
| try: | |
| for i in range(0, len(images), batch): | |
| _embed_images(images[i:i + batch], model_id) | |
| finally: | |
| for lyr, t in zip(layers, taps): | |
| lyr.mlp.fc1 = t.inner | |
| out = [] | |
| for lyr, t in zip(layers, taps): | |
| act = np.array(t.acc) / max(t.n, 1) | |
| down = np.linalg.norm(np.array(lyr.mlp.fc2.weight), axis=0) | |
| out.append({"act": act.astype(np.float32), | |
| "down": down.astype(np.float32), | |
| "saliency": (act * down).astype(np.float32)}) | |
| return out | |
| # =========================================================================== | |
| # 2. Masking and compaction | |
| # =========================================================================== | |
| def _snapshot(layers): | |
| return [(np.array(l.mlp.fc1.weight), np.array(l.mlp.fc1.bias)) | |
| for l in layers] | |
| def _restore(layers, snap): | |
| for l, (w, b) in zip(layers, snap): | |
| l.mlp.fc1.weight = mx.array(w) | |
| l.mlp.fc1.bias = mx.array(b) | |
| def apply_masks(layers, snap, masks): | |
| """Silence channels: zeroing the fc1 row makes gelu(0)=0 downstream.""" | |
| for l, (w, b), m in zip(layers, snap, masks): | |
| keep = m.astype(np.float32)[:, None] | |
| l.mlp.fc1.weight = mx.array(w * keep) | |
| l.mlp.fc1.bias = mx.array(b * keep[:, 0]) | |
| def compact_mlps(model, masks): | |
| """Physically delete the dead channels. This is where speed comes from.""" | |
| layers = vision_layers(model) | |
| removed = 0 | |
| for l, m in zip(layers, masks): | |
| keep = np.where(m > 0.5)[0] | |
| if len(keep) == mlp_width(l): | |
| continue | |
| removed += mlp_width(l) - len(keep) | |
| l.mlp.fc1.weight = mx.array(np.array(l.mlp.fc1.weight)[keep]) | |
| l.mlp.fc1.bias = mx.array(np.array(l.mlp.fc1.bias)[keep]) | |
| l.mlp.fc2.weight = mx.array(np.array(l.mlp.fc2.weight)[:, keep]) | |
| mx.eval(model.parameters()) | |
| return removed | |
| # =========================================================================== | |
| # 3. Fidelity against the unpruned teacher, on the user's own frames | |
| # =========================================================================== | |
| def fidelity(model_id, images, teacher, batch=16): | |
| from .embeddings import _embed_images | |
| out = [] | |
| for i in range(0, len(images), batch): | |
| out.append(_embed_images(images[i:i + batch], model_id)) | |
| v = np.concatenate(out, axis=0) | |
| return float((v * teacher).sum(axis=1).mean()) | |
| # =========================================================================== | |
| # 4. Reverse attention (unchanged in spirit from FDNN) | |
| # =========================================================================== | |
| def reverse_attention(importance, temperature=1.0): | |
| imp = np.asarray(importance, dtype=np.float64) | |
| imp = (imp - imp.mean()) / (imp.std() + 1e-8) | |
| logits = -imp / max(temperature, 1e-6) | |
| logits -= logits.max() | |
| e = np.exp(logits) | |
| return (e / (e.sum() + 1e-12)).astype(np.float32) | |
| # =========================================================================== | |
| # 5. The cycle | |
| # =========================================================================== | |
| def prune_encoder(model_id, images, keep=0.5, layer_probe=True, iters=12, | |
| lam=0.35, batch=16, verbose=True, seed=0): | |
| """Prune MLP channels of the vision tower against corpus fidelity. | |
| `keep` is the global target fraction of MLP channels to retain. The | |
| per-layer budget is not uniform: layers whose ablation barely moves | |
| fidelity give up more channels than layers that matter, which is the whole | |
| point of measuring instead of assuming. | |
| Returns (model, report). The returned model is COMPACTED — smaller | |
| matmuls, not masked ones. | |
| """ | |
| from .embeddings import _embed_images, _load_model | |
| rng = np.random.default_rng(seed) | |
| model, _ = _load_model(model_id) | |
| layers = vision_layers(model) | |
| widths = [mlp_width(l) for l in layers] | |
| snap = _snapshot(layers) | |
| teacher = np.concatenate( | |
| [_embed_images(images[i:i + batch], model_id) | |
| for i in range(0, len(images), batch)], axis=0) | |
| t0 = time.time() | |
| sal = channel_saliency(model, images, model_id, batch=batch) | |
| if verbose: | |
| print(f" saliency over {len(images)} corpus frames " | |
| f"({time.time() - t0:.1f}s)", flush=True) | |
| # ---- per-layer ablation: how much does this layer matter at all? ------- | |
| layer_cost = np.ones(len(layers), dtype=np.float64) | |
| if layer_probe: | |
| for li in range(len(layers)): | |
| masks = [np.ones(w, np.float32) for w in widths] | |
| masks[li][:] = 0.0 | |
| apply_masks(layers, snap, masks) | |
| layer_cost[li] = max(1.0 - fidelity(model_id, images[:batch], | |
| teacher[:batch], batch), 1e-6) | |
| _restore(layers, snap) | |
| if verbose: | |
| order = np.argsort(layer_cost) | |
| print(f" layer ablation: cheapest {order[:4].tolist()} " | |
| f"costliest {order[-4:].tolist()}", flush=True) | |
| # ---- allocate the budget across layers by measured importance --------- | |
| # A layer that costs little when removed entirely can afford to lose more | |
| # of its channels. Normalised so the global kept fraction hits `keep`. | |
| w = layer_cost / layer_cost.sum() | |
| share = w / w.mean() # 1.0 == average importance | |
| per_layer_keep = np.clip(keep * share, 0.05, 1.0) | |
| total = sum(widths) | |
| scale = (keep * total) / sum(k * n for k, n in zip(per_layer_keep, widths)) | |
| per_layer_keep = np.clip(per_layer_keep * scale, 0.05, 1.0) | |
| masks = [] | |
| for li, (s, wdt) in enumerate(zip(sal, widths)): | |
| n_keep = max(int(round(per_layer_keep[li] * wdt)), 1) | |
| idx = np.argsort(-s["saliency"])[:n_keep] | |
| m = np.zeros(wdt, np.float32) | |
| m[idx] = 1.0 | |
| masks.append(m) | |
| apply_masks(layers, snap, masks) | |
| fid = fidelity(model_id, images, teacher, batch) | |
| kept = sum(m.sum() for m in masks) / total | |
| if verbose: | |
| print(f" saliency prune: kept {kept:.1%}, fidelity {fid:.4f}", | |
| flush=True) | |
| # ---- PPO refinement over the per-layer budget -------------------------- | |
| # The candidate set is the 27 per-layer keep fractions rather than 116,208 | |
| # independent channels: one forward pass per sampled mask makes per-channel | |
| # sampling unaffordable, and the per-layer budget is where the leverage | |
| # actually is (saliency already orders channels within a layer). | |
| best = (fid, [m.copy() for m in masks], kept) | |
| ra = reverse_attention(layer_cost) | |
| logit = np.zeros(len(layers)) | |
| for it in range(iters): | |
| cand = per_layer_keep * (1.0 + 0.25 * np.tanh(logit) | |
| + 0.15 * rng.standard_normal(len(layers))) | |
| cand = np.clip(cand, 0.05, 1.0) | |
| cand *= (keep * total) / sum(c * n for c, n in zip(cand, widths)) | |
| cand = np.clip(cand, 0.05, 1.0) | |
| trial = [] | |
| for li, (s, wdt) in enumerate(zip(sal, widths)): | |
| n_keep = max(int(round(cand[li] * wdt)), 1) | |
| idx = np.argsort(-s["saliency"])[:n_keep] | |
| m = np.zeros(wdt, np.float32) | |
| m[idx] = 1.0 | |
| trial.append(m) | |
| apply_masks(layers, snap, trial) | |
| f = fidelity(model_id, images[:batch * 2], teacher[:batch * 2], batch) | |
| k = sum(m.sum() for m in trial) / total | |
| reward = -(1.0 - f) - lam * k | |
| best_reward = -(1.0 - best[0]) - lam * best[2] | |
| if reward > best_reward: | |
| # Direction of the accepted move, computed BEFORE the budget is | |
| # updated — comparing `cand` against itself would make every sign | |
| # zero and the search a pure random walk. | |
| step = np.sign(cand - per_layer_keep) | |
| best = (f, [m.copy() for m in trial], k) | |
| per_layer_keep = cand | |
| logit += 0.5 * ra * step | |
| if verbose and (it % 4 == 0 or it == iters - 1): | |
| print(f" PPO {it:2d} | fidelity {f:.4f} | kept {k:.1%} | " | |
| f"reward {reward:+.4f}", flush=True) | |
| fid, masks, kept = best | |
| _restore(layers, snap) | |
| removed = compact_mlps(model, masks) | |
| final = fidelity(model_id, images, teacher, batch) | |
| report = {"model": model_id, "kept_fraction": float(kept), | |
| "channels_removed": int(removed), | |
| "channels_total": int(total), | |
| "fidelity": float(final), | |
| "layer_keep": [float(x) for x in per_layer_keep]} | |
| if verbose: | |
| print(f" compacted: removed {removed:,}/{total:,} MLP channels, " | |
| f"fidelity {final:.4f}", flush=True) | |
| return model, report | |