Download f2.py from FrameXlabs/fragment-1: direct link, hf CLI and curl.
- Browser
- Download file 8.39 kB
-
https://huggingface.co/FrameXlabs/fragment-1/resolve/main/f2.py
- Command line
-
hf download hf://FrameXlabs/fragment-1/f2.py
-
curl -L -o f2.py https://huggingface.co/FrameXlabs/fragment-1/resolve/main/f2.py
8.39 kB
| #!/usr/bin/env python3 | |
| """f2.py — self-contained Python runtime for FrameXlabs/fragment-1 (v2). | |
| Loads the released safetensors checkpoint + BPE tokenizer and answers typed | |
| questions with calibrated probabilities in a single forward pass. | |
| Usage: | |
| from f2 import Fragment | |
| m = Fragment.from_pretrained("FrameXlabs/fragment-1") # hub or local dir | |
| res = m.decide( | |
| state="Hi, we were billed twice for March. Please refund the duplicate.", | |
| questions={ | |
| "refund": {"type": "noul", | |
| "instructions": "Does the user request a refund?"}, | |
| "urgency": {"type": "score", | |
| "instructions": "How urgent is this?", | |
| "criteria": ["not urgent", "soon", "critical"]}, | |
| "topic": {"type": "choice", | |
| "instructions": "Which department should handle this?", | |
| "criteria": {"billing": "invoices, payments, refunds", | |
| "technical": "bugs, outages, errors", | |
| "other": "everything else"}}, | |
| }) | |
| print(res["answers"]["refund"]["noul"]) # calibrated P(yes) | |
| Dependencies: torch, safetensors. | |
| """ | |
| import json | |
| import math | |
| import os | |
| import re | |
| from typing import Dict | |
| import torch | |
| import torch.nn as nn | |
| MAX_LEN = 192 | |
| class F2Tokenizer: | |
| def __init__(self, path: str): | |
| spec = json.load(open(path)) | |
| self.vocab = spec["vocab"] | |
| self.id_of = {v: i for i, v in enumerate(self.vocab)} | |
| self.ranks = {(a, b): i for i, (a, b) in enumerate(spec["merges"])} | |
| self.added = set(spec.get("added", [])) | |
| self.pad = self.id_of["[PAD]"] | |
| self.cls = self.id_of["[CLS]"] | |
| self.sep = self.id_of["[SEP]"] | |
| self.unk = self.id_of["[UNK]"] | |
| self._cache: Dict[str, list] = {} | |
| def _bpe(self, word: str): | |
| syms = list(word) | |
| while len(syms) >= 2: | |
| best, rank = None, None | |
| for a, b in zip(syms, syms[1:]): | |
| r = self.ranks.get((a, b)) | |
| if r is not None and (rank is None or r < rank): | |
| best, rank = (a, b), r | |
| if best is None: | |
| break | |
| a, b = best | |
| out, j = [], 0 | |
| while j < len(syms): | |
| if j < len(syms) - 1 and syms[j] == a and syms[j + 1] == b: | |
| out.append(a + b) | |
| j += 2 | |
| else: | |
| out.append(syms[j]) | |
| j += 1 | |
| syms = out | |
| return syms | |
| def _word_ids(self, word: str): | |
| cached = self._cache.get(word) | |
| if cached is not None: | |
| return cached | |
| ids = [self.id_of.get(s, self.unk) for s in self._bpe(word)] | |
| if len(self._cache) < 300_000: | |
| self._cache[word] = ids | |
| return ids | |
| def encode(self, text: str): | |
| text = text.lower() | |
| ids = [] | |
| for w in re.findall(r"@?[a-z0-9]+|[^\sa-z0-9]", text): | |
| if w in self.added: | |
| ids.append(self.id_of[w]) | |
| else: | |
| ids.extend(self._word_ids(w)) | |
| return ids | |
| class Block(nn.Module): | |
| def __init__(self, d, nhead, ff): | |
| super().__init__() | |
| self.ln1 = nn.LayerNorm(d) | |
| self.attn = nn.MultiheadAttention(d, nhead, batch_first=True) | |
| self.ln2 = nn.LayerNorm(d) | |
| self.fc1 = nn.Linear(d, ff) | |
| self.fc2 = nn.Linear(ff, d) | |
| def forward(self, x, pad): | |
| h = self.ln1(x) | |
| a, _ = self.attn(h, h, h, key_padding_mask=pad, need_weights=False) | |
| x = x + a | |
| h = self.ln2(x) | |
| return x + self.fc2(nn.functional.gelu(self.fc1(h))) | |
| class F2Net(nn.Module): | |
| def __init__(self, vocab, d, max_len, n_layers, n_heads, ff): | |
| super().__init__() | |
| self.scale = math.sqrt(d) | |
| self.tok = nn.Embedding(vocab, d, padding_idx=0) | |
| self.pos = nn.Embedding(max_len, d) | |
| self.blocks = nn.ModuleList([Block(d, n_heads, ff) for _ in range(n_layers)]) | |
| self.ln_f = nn.LayerNorm(d) | |
| self.head = nn.Linear(d, 1) | |
| def forward(self, ids): | |
| att = ids != 0 | |
| pad = ~att | |
| B, L = ids.shape | |
| h = self.tok(ids) * self.scale + self.pos(torch.arange(L))[None] | |
| for blk in self.blocks: | |
| h = blk(h, pad) | |
| return self.head(self.ln_f(h)).squeeze(-1).float() | |
| def _confidence(p): | |
| k = len(p) | |
| if k < 2: | |
| return 1.0 | |
| ent = -sum(x * math.log(max(x, 1e-12)) for x in p) | |
| return min(1.0, max(0.0, 1.0 - ent / math.log(k))) | |
| class Fragment: | |
| def __init__(self, root: str): | |
| self.cfg = json.load(open(os.path.join(root, "f2_config.json"))) | |
| self.tok = F2Tokenizer(os.path.join(root, "tokenizer.json")) | |
| from safetensors.torch import load_file | |
| sd = load_file(os.path.join(root, "model.safetensors")) | |
| arch = self.cfg["architecture"] | |
| self.net = F2Net(arch["vocab"], arch["d"], arch["max_len"], | |
| arch["n_layers"], arch["n_heads"], arch["ff"]) | |
| self.net.load_state_dict(sd, strict=True) | |
| self.net.eval() | |
| self.version = self.cfg.get("version", "2.0") | |
| self.temps = self.cfg.get("temperatures", {}) | |
| def from_pretrained(cls, src: str = "FrameXlabs/fragment-1") -> "Fragment": | |
| if os.path.isdir(src): | |
| return cls(src) | |
| from huggingface_hub import snapshot_download | |
| return cls(snapshot_download(repo_id=src)) | |
| def _options(self, q): | |
| t = q["type"] | |
| if t == "choice": | |
| crit = q.get("criteria") or {} | |
| return [f"@{i} {k}: {v}" if v else f"@{i} {k}" | |
| for i, (k, v) in enumerate(crit.items())], list(crit.keys()) | |
| if t == "score": | |
| crit = q.get("criteria") or [] | |
| return [f"@{i} level {i}: {c}" for i, c in enumerate(crit)], None | |
| return ["@no: no, the statement does not hold", | |
| "@yes: yes, the statement holds"], None | |
| def decide(self, state: str, questions: Dict) -> Dict: | |
| import time as _t | |
| t0 = _t.time() | |
| answers = {} | |
| for qid, q in questions.items(): | |
| opts, keys = self._options(q) | |
| pre = self.tok.encode(f"{q['instructions']} input:") | |
| opt_ids = self.tok.encode("options: " + "; ".join(opts)) | |
| markers = [o.split()[0].rstrip(":") for o in opts] | |
| marker_ids = [self.tok.id_of[m] for m in markers] | |
| positions, seen = [], set() | |
| for idx, t in enumerate(opt_ids): | |
| if t in marker_ids and t not in seen: | |
| seen.add(t) | |
| positions.append(idx) | |
| budget = MAX_LEN - 2 - len(pre) - len(opt_ids) | |
| body = self.tok.encode(" " + state)[: max(8, budget)] | |
| ids = [self.tok.cls] + pre + body + opt_ids + [self.tok.sep] | |
| ids = ids[:MAX_LEN] | |
| shift = 1 + len(pre) + len(body) | |
| abs_pos = [p + shift for p in positions] | |
| abs_pos = [p for p in abs_pos if p < len(ids) - 1] | |
| t = torch.tensor([ids]) | |
| logits = self.net(t)[0, abs_pos] | |
| temp = self.temps.get(q["type"], 1.0) or 1.0 | |
| p = torch.softmax(logits / temp, -1).tolist() | |
| conf = _confidence(p) | |
| base = {"type": q["type"], "question": q["instructions"], | |
| "probabilities": {}, "confidence": round(conf, 4)} | |
| if q["type"] == "choice": | |
| for i, k in enumerate(keys or []): | |
| base["probabilities"][k] = round(p[i], 4) | |
| base["choice"] = (keys or ["?"])[int(max(range(len(p)), key=lambda i: p[i]))] | |
| elif q["type"] == "score": | |
| exp = sum(i * p[i] for i in range(len(p))) | |
| for i in range(len(p)): | |
| base["probabilities"][str(i)] = round(p[i], 4) | |
| base["score"] = round(exp, 4) | |
| else: | |
| base["probabilities"] = {"false": round(p[0], 4), | |
| "true": round(p[1], 4)} | |
| base["noul"] = round(p[1], 4) | |
| answers[qid] = base | |
| return {"model": "fragment", "version": self.version, "answers": answers, | |
| "usage": {"latency_ms": round((_t.time() - t0) * 1000)}} | |