Merino-Pro-4bit / modeling_baa.py
tomkay's picture
Upload baa-ai-Embedding-Reranker-v1
802a28a verified
Raw
History Blame Contribute Delete
3.77 kB
"""baa-ai-Embedding-Reranker-v1 (4-bit) — standalone embedder+reranker.
Group-64 int4-packed Linear weights (fp16 for embeddings/LayerNorm/classifier/pooler), dequantized to fp16 at
load. Pure torch + transformers + safetensors; runs on CPU / Apple MPS / CUDA. API: BaaEmbeddingReranker."""
import os, json, numpy as np, torch, torch.nn.functional as F
from safetensors.torch import load_file
from transformers import AutoConfig, AutoModel, AutoModelForSequenceClassification, AutoTokenizer
GROUP = 64
def _dequant(st, fk):
p = st[fk + "::q"].numpy().astype(np.uint8)
L = int(st[fk + "::len"][0]); out_, in_ = int(st[fk + "::shape"][0]), int(st[fk + "::shape"][1])
q = np.empty(L, dtype=np.uint8); q[0::2] = p >> 4; q[1::2] = p & 0xF
s = st[fk + "::s"].float().numpy().reshape(-1, 1); m = st[fk + "::m"].float().numpy().reshape(-1, 1)
w = (q.reshape(-1, GROUP).astype(np.float32) * s + m).reshape(out_, in_)
return torch.from_numpy(w)
class BaaEmbeddingReranker:
def __init__(self, path=None, device=None):
path = path or os.path.dirname(os.path.abspath(__file__))
self.device = device or ("mps" if torch.backends.mps.is_available()
else "cuda" if torch.cuda.is_available() else "cpu")
qc = json.load(open(os.path.join(path, "quant_config.json")))
st = load_file(os.path.join(path, "weights_q4.safetensors"))
shared = st["shared::word_embeddings"]
self.emb = AutoModel.from_config(AutoConfig.from_pretrained(os.path.join(path, "embedder")))
self.rr = AutoModelForSequenceClassification.from_config(
AutoConfig.from_pretrained(os.path.join(path, "reranker")))
self._fill(self.emb, st, "emb", "embeddings.word_embeddings.weight", shared, set(qc["emb_q4"]))
self._fill(self.rr, st, "rr", "roberta.embeddings.word_embeddings.weight", shared, set(qc["rr_q4"]))
self.emb = self.emb.half().to(self.device).eval()
self.rr = self.rr.half().to(self.device).eval()
self.emb_tok = AutoTokenizer.from_pretrained(os.path.join(path, "embedder"))
self.rr_tok = AutoTokenizer.from_pretrained(os.path.join(path, "reranker"))
def _fill(self, model, st, ns, wemb_key, shared, q4):
sd = dict(model.state_dict())
for k in list(sd.keys()):
if k == wemb_key:
sd[k] = shared.to(sd[k].dtype); continue
fk = f"{ns}::{k}"
if k in q4:
sd[k] = _dequant(st, fk).to(sd[k].dtype)
elif fk in st:
sd[k] = st[fk].to(sd[k].dtype)
model.load_state_dict(sd)
@torch.no_grad()
def embed(self, texts, is_query=False, batch_size=32):
pref = "query: " if is_query else ""
out = []
for i in range(0, len(texts), batch_size):
enc = self.emb_tok([pref + t for t in texts[i:i+batch_size]], padding=True, truncation=True,
max_length=512, return_tensors="pt").to(self.device)
h = self.emb(**enc).last_hidden_state[:, 0] # CLS
out.append(F.normalize(h, dim=-1).float().cpu().numpy())
return np.vstack(out)
@torch.no_grad()
def rerank(self, query, docs, top_k=None, batch_size=32):
scores = []
for i in range(0, len(docs), batch_size):
enc = self.rr_tok([(query, d[:2000]) for d in docs[i:i+batch_size]], padding=True,
truncation=True, max_length=512, return_tensors="pt").to(self.device)
scores.extend(self.rr(**enc).logits[:, 0].float().cpu().tolist())
order = sorted(range(len(docs)), key=lambda j: -scores[j])
if top_k:
order = order[:top_k]
return [(docs[j], scores[j]) for j in order]