Download modeling_kenga_embed.py from GermannM/kenga-embed-prophet-instruct: direct link, hf CLI and curl.
- Browser
- Download file 6.48 kB
-
https://huggingface.co/GermannM/kenga-embed-prophet-instruct/resolve/main/modeling_kenga_embed.py
- Command line
-
hf download hf://GermannM/kenga-embed-prophet-instruct/modeling_kenga_embed.py
-
curl -L -o modeling_kenga_embed.py https://huggingface.co/GermannM/kenga-embed-prophet-instruct/resolve/main/modeling_kenga_embed.py
6.48 kB
| # -*- coding: utf-8 -*- | |
| """Self-contained Kenga instruct encoder. Byte UTF-8, bidirectional. | |
| Usage (retrieval, Giga-style prefix on the query only): | |
| from modeling_kenga_embed import KengaEmbed | |
| m = KengaEmbed.from_pretrained("GermannM/kenga-embed-prophet-instruct") | |
| q = m.encode_queries(["Where is the capital of Russia?"]) | |
| p = m.encode_documents(["Moscow is the capital of Russia."]) | |
| STS (prefix on both sentences): | |
| a = m.encode_sts(["The cat sits on the mat."]) | |
| b = m.encode_sts(["A cat is sitting on a mat."]) | |
| """ | |
| import json | |
| import os | |
| import torch | |
| import torch.nn as nn | |
| import torch.nn.functional as F | |
| INSTR_RETR = "Instruct: Given a query, retrieve relevant passages\nQuery: " | |
| INSTR_STS = "Instruct: Retrieve semantically similar text.\nQuery: " | |
| class BL(nn.Module): | |
| def __init__(self, in_f, out_f, k): | |
| super().__init__() | |
| kk = min(k, in_f, out_f) | |
| self.U = nn.Parameter(torch.zeros(out_f, kk)) | |
| self.V = nn.Parameter(torch.zeros(in_f, kk)) | |
| self.S = nn.Parameter(torch.ones(kk)) | |
| self.b = nn.Parameter(torch.zeros(out_f)) | |
| self.register_buffer("mask", torch.ones(kk)) | |
| def set_active(self, k_t): | |
| m = torch.zeros_like(self.mask) | |
| m[: min(int(k_t), self.mask.numel())] = 1 | |
| self.mask.copy_(m) | |
| def forward(self, x): | |
| return ((x @ self.V) * (self.S * self.mask)) @ self.U.T + self.b | |
| class BAttn(nn.Module): | |
| def __init__(self, d, heads, k_attn): | |
| super().__init__() | |
| self.h = heads | |
| self.dh = d // heads | |
| self.scale = self.dh ** -0.5 | |
| self.q = BL(d, d, k_attn) | |
| self.k_ = BL(d, d, k_attn) | |
| self.v = BL(d, d, k_attn) | |
| self.o = BL(d, d, k_attn) | |
| def set_rank(self, ka): | |
| for bl in (self.q, self.k_, self.v, self.o): | |
| bl.set_active(ka) | |
| def forward(self, x, pad): | |
| B, T, _ = x.shape | |
| Q = self.q(x).view(B, T, self.h, self.dh).transpose(1, 2) | |
| K = self.k_(x).view(B, T, self.h, self.dh).transpose(1, 2) | |
| V = self.v(x).view(B, T, self.h, self.dh).transpose(1, 2) | |
| att = (Q.float() @ K.float().transpose(-2, -1)) * self.scale | |
| att = att.masked_fill(pad, float("-inf")).softmax(-1) | |
| return self.o((att @ V).transpose(1, 2).reshape(B, T, x.size(-1))) | |
| class BBlock(nn.Module): | |
| def __init__(self, d, dff, heads, k_attn, k_ff): | |
| super().__init__() | |
| self.ln1 = nn.LayerNorm(d) | |
| self.ln2 = nn.LayerNorm(d) | |
| self.attn = BAttn(d, heads, k_attn) | |
| self.fc1 = BL(d, dff, k_ff) | |
| self.fc2 = BL(dff, d, k_ff) | |
| def set_rank(self, ka, kf): | |
| self.attn.set_rank(ka) | |
| self.fc1.set_active(kf) | |
| self.fc2.set_active(kf) | |
| def forward(self, x, pad): | |
| x = x + self.attn(self.ln1(x), pad) | |
| return x + self.fc2(F.gelu(self.fc1(self.ln2(x)))) | |
| class KengaEmbedEnc(nn.Module): | |
| def __init__(self, d=768, layers=8, heads=12, dff=3072, seq=160, | |
| v=256, k_attn=192, k_ff=512): | |
| super().__init__() | |
| self.d = d | |
| self.seq = seq | |
| self.tok = nn.Embedding(v, d) | |
| self.pos = nn.Parameter(torch.zeros(1, seq, d)) | |
| self.blocks = nn.ModuleList( | |
| [BBlock(d, dff, heads, k_attn, k_ff) for _ in range(layers)] | |
| ) | |
| self.ln = nn.LayerNorm(d) | |
| self.proj = nn.Linear(d, d) | |
| def set_rank(self, ka, kf): | |
| for b in self.blocks: | |
| b.set_rank(ka, kf) | |
| def forward(self, idx, lengths=None): | |
| B, T = idx.shape | |
| x = self.tok(idx) + self.pos[:, :T] | |
| pad = idx.eq(0) | |
| mask = pad[:, None, None, :].expand(B, 1, T, T) | |
| for blk in self.blocks: | |
| x = blk(x, mask) | |
| x = self.ln(x) | |
| m = (~pad).unsqueeze(-1).float() | |
| pooled = (x * m).sum(1) / m.sum(1).clamp(min=1) | |
| return F.normalize(self.proj(pooled), dim=-1) | |
| class KengaEmbed: | |
| def __init__(self, model, seq, instruct=True, device="cpu"): | |
| self.model = model.to(device).eval() | |
| self.seq = seq | |
| self.instruct = instruct | |
| self.device = device | |
| def from_pretrained(cls, path, device="cpu"): | |
| cfg = json.load(open(os.path.join(path, "config.json"), encoding="utf-8")) | |
| m = KengaEmbedEnc( | |
| d=cfg.get("d", 768), | |
| layers=cfg.get("layers", 8), | |
| heads=cfg.get("heads", 12), | |
| dff=cfg.get("dff", 3072), | |
| seq=cfg.get("seq", 160), | |
| v=cfg.get("v", 256), | |
| k_attn=cfg.get("k_attn", 192), | |
| k_ff=cfg.get("k_ff", 512), | |
| ) | |
| blob = torch.load(os.path.join(path, "pytorch_model.bin"), | |
| map_location="cpu", weights_only=False) | |
| sd = blob["model"] if isinstance(blob, dict) and "model" in blob else blob | |
| m.load_state_dict(sd, strict=True) | |
| rank = cfg.get("rank") or blob.get("rank") | |
| if rank: | |
| m.set_rank(*list(rank)) | |
| return cls(m, seq=cfg.get("seq", 160), | |
| instruct=bool(cfg.get("instruct", True)), device=device) | |
| def _ids(self, text): | |
| raw = (text or "").encode("utf-8", errors="ignore")[: self.seq] | |
| ids = [b if b else 1 for b in raw][: self.seq] | |
| n = len(ids) | |
| ids = ids + [0] * (self.seq - n) | |
| return ids, n | |
| def _prefix(self, text, kind): | |
| if not self.instruct or not text: | |
| return text | |
| if text.startswith("Instruct:"): | |
| return text | |
| p = INSTR_STS if kind == "sts" else INSTR_RETR | |
| return p + text | |
| def encode(self, texts, kind="doc", bs=32): | |
| vecs = [] | |
| with torch.no_grad(): | |
| for i in range(0, len(texts), bs): | |
| chunk = texts[i : i + bs] | |
| ids, ln = [], [] | |
| for t in chunk: | |
| s = t if kind == "doc" else self._prefix(t, kind) | |
| ii, n = self._ids(s) | |
| ids.append(ii) | |
| ln.append(n) | |
| x = torch.tensor(ids, device=self.device) | |
| l = torch.tensor(ln, device=self.device) | |
| vecs.append(self.model(x, l).cpu()) | |
| return torch.cat(vecs, 0) | |
| def encode_queries(self, texts, **kw): | |
| return self.encode(texts, kind="retr", **kw) | |
| def encode_documents(self, texts, **kw): | |
| return self.encode(texts, kind="doc", **kw) | |
| def encode_sts(self, texts, **kw): | |
| return self.encode(texts, kind="sts", **kw) | |