hypernet-sp-distill / pooler_mlx.py
baya1116's picture
Super-squash branch 'main' using huggingface_hub
b5989f0
Raw
History Blame Contribute Delete
4.47 kB
"""MLX port of TSE.AttnPoolSP (forward + forward_with_mass), numerically matching the PyTorch one.
Loads pooler.pt (torch state_dict) into MLX arrays. MHA implemented manually to match
torch nn.MultiheadAttention (packed in_proj QKV, head-avg attn weights for eviction mass)."""
import math
import numpy as np
import mlx.core as mx
def _ln(x, w, b, eps=1e-5):
mu = x.mean(axis=-1, keepdims=True)
var = ((x - mu) ** 2).mean(axis=-1, keepdims=True)
return (x - mu) / mx.sqrt(var + eps) * w + b
def _gelu(x):
return 0.5 * x * (1.0 + mx.erf(x / math.sqrt(2.0)))
def _mha(q, k, v, P, heads, want_w):
# q:(B,Lq,H) k,v:(B,Lk,H); P holds packed in_proj (3H,H)+bias(3H), out_proj (H,H)+bias(H)
B, Lq, H = q.shape; Lk = k.shape[1]; hd = H // heads
Wi, bi, Wo, bo = P["in_w"], P["in_b"], P["out_w"], P["out_b"]
qp = q @ Wi[:H].T + bi[:H]
kp = k @ Wi[H:2 * H].T + bi[H:2 * H]
vp = v @ Wi[2 * H:].T + bi[2 * H:]
def split(t, L):
return t.reshape(B, L, heads, hd).transpose(0, 2, 1, 3) # (B,heads,L,hd)
qh, kh, vh = split(qp, Lq), split(kp, Lk), split(vp, Lk)
scores = (qh @ kh.transpose(0, 1, 3, 2)) / math.sqrt(hd) # (B,heads,Lq,Lk)
attn = mx.softmax(scores, axis=-1)
out = (attn @ vh).transpose(0, 2, 1, 3).reshape(B, Lq, H) # (B,Lq,H)
out = out @ Wo.T + bo
w = attn.mean(axis=1) if want_w else None # head-avg (B,Lq,Lk)
return out, w
def _sinusoidal(L, H):
pos = np.arange(L)[:, None].astype(np.float32)
i = np.arange(0, H, 2).astype(np.float32)
div = np.exp(-math.log(10000.0) * i / H)
pe = np.zeros((L, H), np.float32)
pe[:, 0::2] = np.sin(pos * div); pe[:, 1::2] = np.cos(pos * div)
return mx.array(pe)
class PoolerMLX:
def __init__(self, path):
import torch
ck = torch.load(path, map_location="cpu", weights_only=False)
sd = {k: v.float().numpy() for k, v in ck["pooler"].items()}
a = ck.get("args") or ck.get("src_args") or {}
self.A = {k: mx.array(v) for k, v in sd.items()}
self.query = self.A["query"] # (n_sp,H)
self.n_sp, self.H = self.query.shape
self.heads = a.get("heads", 8) or 8
self.layers = sum(1 for k in sd if k.endswith(".lnq1.weight"))
self.out_scale = self.A["out_scale"]
self._pe = None
def _blk_P(self, i, which):
p = f"blocks.{i}.{which}."
return {"in_w": self.A[p + "in_proj_weight"], "in_b": self.A[p + "in_proj_bias"],
"out_w": self.A[p + "out_proj.weight"], "out_b": self.A[p + "out_proj.bias"]}
def _ln_wb(self, i, name):
return self.A[f"blocks.{i}.{name}.weight"], self.A[f"blocks.{i}.{name}.bias"]
def pe(self, L):
if self._pe is None or self._pe.shape[0] < L:
self._pe = _sinusoidal(max(L, 1024), self.H)
return self._pe[:L]
def _run(self, past_emb, want_mass):
B, L = past_emb.shape[0], past_emb.shape[1]
past = past_emb + self.pe(L)[None] if L > 0 else past_emb
q = mx.broadcast_to(self.query[None], (B, self.n_sp, self.H))
mass = mx.zeros((B, L)) if want_mass else None
for i in range(self.layers):
w1, b1 = self._ln_wb(i, "lnq1"); wk, bk = self._ln_wb(i, "lnk")
if L > 0:
kn = _ln(past, wk, bk)
a, w = _mha(_ln(q, w1, b1), kn, kn, self._blk_P(i, "cross"), self.heads, want_mass)
q = q + a
if want_mass and w is not None:
mass = mass + w.sum(axis=1)
w2, b2 = self._ln_wb(i, "lnq2")
qn = _ln(q, w2, b2)
s, _ = _mha(qn, qn, qn, self._blk_P(i, "selfa"), self.heads, False)
q = q + s
w3, b3 = self._ln_wb(i, "lnq3")
h = _ln(q, w3, b3)
ff = (h @ self.A[f"blocks.{i}.ffn.0.weight"].T + self.A[f"blocks.{i}.ffn.0.bias"])
ff = _gelu(ff)
ff = ff @ self.A[f"blocks.{i}.ffn.2.weight"].T + self.A[f"blocks.{i}.ffn.2.bias"]
q = q + ff
sp = _ln(q, self.A["ln_out.weight"], self.A["ln_out.bias"])
norm = mx.sqrt((sp * sp).sum(axis=-1, keepdims=True))
sp = sp / mx.maximum(norm, 1e-6) * mx.abs(self.out_scale)
return (sp, mass) if want_mass else sp
def forward(self, past_emb):
return self._run(past_emb, False)
def forward_with_mass(self, past_emb):
return self._run(past_emb, True)