Upload folder using huggingface_hub
Browse files- code/aubin_mini/__init__.py +142 -0
- code/aubin_mini/__main__.py +59 -0
- code/eval_kev.py +92 -0
- code/pyproject.toml +17 -0
code/aubin_mini/__init__.py
ADDED
|
@@ -0,0 +1,142 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
"""AUBIN Mini by Norovox — on-device typed decisions (choice / yes-no) with calibrated probabilities.
|
| 2 |
+
|
| 3 |
+
A 395M-parameter cross-encoder: every option is read together with the question and the state, scored once,
|
| 4 |
+
and the option scores of a question are turned into probabilities with a fitted temperature.
|
| 5 |
+
Runs on CPU with ONNX Runtime only (no torch, no GPU).
|
| 6 |
+
|
| 7 |
+
from aubin_mini import AubinMini
|
| 8 |
+
m = AubinMini.from_pretrained("emrevrg/AUBIN-Mini") # phone build, 255 MB
|
| 9 |
+
m.decide({"ticket": "Package arrived broken, customer wants money back"},
|
| 10 |
+
{"route": {"type": "choice", "instructions": "Which team?",
|
| 11 |
+
"criteria": {"billing": "refunds", "shipping": "delivery", "tech": "software"}},
|
| 12 |
+
"urgent": {"type": "noul", "instructions": "Is this urgent?"}})
|
| 13 |
+
"""
|
| 14 |
+
import json
|
| 15 |
+
import os
|
| 16 |
+
|
| 17 |
+
import numpy as np
|
| 18 |
+
|
| 19 |
+
__version__ = "0.1.0"
|
| 20 |
+
__all__ = ["AubinMini", "options_of"]
|
| 21 |
+
|
| 22 |
+
VARIANTS = { # file name, size on disk, accuracy on the same 300 Kev development questions
|
| 23 |
+
"phone": "aubin_mini_nb4_g8.onnx", # 4-bit block weights + 8-bit embeddings, 255 MB, dev 0.833
|
| 24 |
+
"nb4": "aubin_mini_nb4.onnx", # 4-bit block weights, 402 MB, dev 0.840
|
| 25 |
+
}
|
| 26 |
+
|
| 27 |
+
|
| 28 |
+
def options_of(q):
|
| 29 |
+
"""Question → (answer keys, option texts). Same rendering the model was trained with."""
|
| 30 |
+
t, c = q.get("type", "choice"), q.get("criteria")
|
| 31 |
+
if t == "noul":
|
| 32 |
+
if isinstance(c, dict):
|
| 33 |
+
return [False, True], [f"false — {c.get('false', 'no')}", f"true — {c.get('true', 'yes')}"]
|
| 34 |
+
return [False, True], ["false — no", "true — yes"]
|
| 35 |
+
if isinstance(c, list):
|
| 36 |
+
return list(range(len(c))), [str(v) for v in c]
|
| 37 |
+
if not isinstance(c, dict) or not c:
|
| 38 |
+
raise ValueError("a 'choice' question needs a non-empty 'criteria' dict {option: description}")
|
| 39 |
+
return list(c.keys()), [f"{k}: {v}" for k, v in c.items()]
|
| 40 |
+
|
| 41 |
+
|
| 42 |
+
def _state_text(state):
|
| 43 |
+
return state if isinstance(state, str) else json.dumps(state, ensure_ascii=False)
|
| 44 |
+
|
| 45 |
+
|
| 46 |
+
class AubinMini:
|
| 47 |
+
def __init__(self, model_dir, variant="phone", threads=None, max_len=512, batch=32):
|
| 48 |
+
import onnxruntime as ort
|
| 49 |
+
from tokenizers import Tokenizer
|
| 50 |
+
cfg_path = os.path.join(model_dir, "aubin_mini.json")
|
| 51 |
+
self.cfg = json.load(open(cfg_path, encoding="utf-8")) if os.path.exists(cfg_path) else {}
|
| 52 |
+
self.temperature = float(self.cfg.get("temperature", 1.0))
|
| 53 |
+
self.max_len, self.batch = max_len, batch
|
| 54 |
+
self.tok = Tokenizer.from_file(os.path.join(model_dir, "tokenizer.json"))
|
| 55 |
+
self.tok.enable_truncation(max_length=max_len, strategy="only_second") # the state is cut, never the question/option
|
| 56 |
+
self.tok.no_padding()
|
| 57 |
+
self.tok_long = Tokenizer.from_file(os.path.join(model_dir, "tokenizer.json")) # only when question+option alone exceed max_len
|
| 58 |
+
self.tok_long.enable_truncation(max_length=max_len, strategy="longest_first")
|
| 59 |
+
self.tok_long.no_padding()
|
| 60 |
+
pad = self.cfg.get("pad_token", "[PAD]")
|
| 61 |
+
self.pad_id = self.tok.token_to_id(pad) or 0
|
| 62 |
+
so = ort.SessionOptions()
|
| 63 |
+
so.graph_optimization_level = ort.GraphOptimizationLevel.ORT_ENABLE_ALL
|
| 64 |
+
if threads:
|
| 65 |
+
so.intra_op_num_threads = int(threads)
|
| 66 |
+
path = os.path.join(model_dir, VARIANTS.get(variant, variant))
|
| 67 |
+
self.sess = ort.InferenceSession(path, so, providers=["CPUExecutionProvider"])
|
| 68 |
+
self.variant = variant
|
| 69 |
+
|
| 70 |
+
@classmethod
|
| 71 |
+
def from_pretrained(cls, repo_or_dir="emrevrg/AUBIN-Mini", variant="phone", token=None, **kw):
|
| 72 |
+
if os.path.isdir(repo_or_dir):
|
| 73 |
+
return cls(repo_or_dir, variant, **kw)
|
| 74 |
+
from huggingface_hub import snapshot_download
|
| 75 |
+
name = VARIANTS.get(variant, variant)
|
| 76 |
+
d = snapshot_download(repo_or_dir, token=token,
|
| 77 |
+
allow_patterns=[name, "tokenizer.json", "tokenizer_config.json", "aubin_mini.json"])
|
| 78 |
+
return cls(d, variant, **kw)
|
| 79 |
+
|
| 80 |
+
# ---- scoring -------------------------------------------------------------------------------------------
|
| 81 |
+
def _scores(self, pairs):
|
| 82 |
+
"""[(question+option, state)] → raw scores, length-sorted batches (less padding)."""
|
| 83 |
+
try:
|
| 84 |
+
enc = self.tok.encode_batch(pairs)
|
| 85 |
+
except Exception:
|
| 86 |
+
enc = []
|
| 87 |
+
for p in pairs:
|
| 88 |
+
try:
|
| 89 |
+
enc.append(self.tok.encode(*p))
|
| 90 |
+
except Exception:
|
| 91 |
+
enc.append(self.tok_long.encode(*p))
|
| 92 |
+
order = sorted(range(len(enc)), key=lambda i: len(enc[i].ids))
|
| 93 |
+
out = np.zeros(len(enc), dtype=np.float32)
|
| 94 |
+
for k in range(0, len(order), self.batch):
|
| 95 |
+
idx = order[k:k + self.batch]
|
| 96 |
+
T = max(len(enc[i].ids) for i in idx)
|
| 97 |
+
ids = np.full((len(idx), T), self.pad_id, dtype=np.int64)
|
| 98 |
+
am = np.zeros((len(idx), T), dtype=np.int64)
|
| 99 |
+
for b, i in enumerate(idx):
|
| 100 |
+
n = len(enc[i].ids)
|
| 101 |
+
ids[b, :n] = enc[i].ids
|
| 102 |
+
am[b, :n] = 1
|
| 103 |
+
s = self.sess.run(["score"], {"input_ids": ids, "attention_mask": am})[0]
|
| 104 |
+
out[idx] = np.asarray(s, dtype=np.float32).reshape(-1)
|
| 105 |
+
return out
|
| 106 |
+
|
| 107 |
+
def log_probs(self, question, option_texts, state=""):
|
| 108 |
+
"""Calibrated log-probabilities over option_texts (any number of options)."""
|
| 109 |
+
st = "STATE: " + _state_text(state)
|
| 110 |
+
s = self._scores([(f"QUESTION: {question}\nOPTION: {t}", st) for t in option_texts]) / self.temperature
|
| 111 |
+
s = s - s.max()
|
| 112 |
+
return s - np.log(np.exp(s).sum())
|
| 113 |
+
|
| 114 |
+
def choose(self, question, options, state=""):
|
| 115 |
+
"""options: dict {key: description} or list of texts → {answer, confidence, probabilities}."""
|
| 116 |
+
q = {"type": "choice", "criteria": options}
|
| 117 |
+
keys, texts = options_of(q)
|
| 118 |
+
return self._pack(keys, self.log_probs(question, texts, state))
|
| 119 |
+
|
| 120 |
+
def decide(self, state, questions):
|
| 121 |
+
"""Kev/AUBIN typed-decision JSON: {qid: {type: choice|noul, instructions, criteria}} → {qid: result}."""
|
| 122 |
+
flat, spans = [], {}
|
| 123 |
+
st = "STATE: " + _state_text(state)
|
| 124 |
+
for qid, q in questions.items():
|
| 125 |
+
keys, texts = options_of(q)
|
| 126 |
+
ins = q.get("instructions") or qid
|
| 127 |
+
spans[qid] = (len(flat), keys)
|
| 128 |
+
flat += [(f"QUESTION: {ins}\nOPTION: {t}", st) for t in texts]
|
| 129 |
+
s = self._scores(flat) / self.temperature
|
| 130 |
+
res = {}
|
| 131 |
+
for qid, (a, keys) in spans.items():
|
| 132 |
+
x = s[a:a + len(keys)]
|
| 133 |
+
x = x - x.max()
|
| 134 |
+
res[qid] = self._pack(keys, x - np.log(np.exp(x).sum()))
|
| 135 |
+
return res
|
| 136 |
+
|
| 137 |
+
@staticmethod
|
| 138 |
+
def _pack(keys, lp):
|
| 139 |
+
p = np.exp(lp)
|
| 140 |
+
j = int(p.argmax())
|
| 141 |
+
return {"answer": keys[j], "confidence": round(float(p[j]), 4),
|
| 142 |
+
"probabilities": {str(k).lower() if isinstance(k, bool) else str(k): round(float(v), 4) for k, v in zip(keys, p)}}
|
code/aubin_mini/__main__.py
ADDED
|
@@ -0,0 +1,59 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
"""aubin-mini command line.
|
| 2 |
+
|
| 3 |
+
aubin-mini decide request.json # {"state": ..., "questions": {...}} → JSON result
|
| 4 |
+
aubin-mini serve --port 8009 # POST /decide with the same JSON, GET /health
|
| 5 |
+
"""
|
| 6 |
+
import argparse
|
| 7 |
+
import json
|
| 8 |
+
import sys
|
| 9 |
+
from http.server import BaseHTTPRequestHandler, ThreadingHTTPServer
|
| 10 |
+
|
| 11 |
+
from . import AubinMini, __version__
|
| 12 |
+
|
| 13 |
+
|
| 14 |
+
def main(argv=None):
|
| 15 |
+
ap = argparse.ArgumentParser(prog="aubin-mini")
|
| 16 |
+
ap.add_argument("--model", default="emrevrg/AUBIN-Mini", help="HF repo or local directory")
|
| 17 |
+
ap.add_argument("--variant", default="phone", choices=["phone", "nb4"])
|
| 18 |
+
ap.add_argument("--threads", type=int, default=None)
|
| 19 |
+
sub = ap.add_subparsers(dest="cmd", required=True)
|
| 20 |
+
d = sub.add_parser("decide"); d.add_argument("request", help="JSON file, or - for stdin")
|
| 21 |
+
s = sub.add_parser("serve"); s.add_argument("--host", default="127.0.0.1"); s.add_argument("--port", type=int, default=8009)
|
| 22 |
+
a = ap.parse_args(argv)
|
| 23 |
+
m = AubinMini.from_pretrained(a.model, a.variant, threads=a.threads)
|
| 24 |
+
|
| 25 |
+
if a.cmd == "decide":
|
| 26 |
+
req = json.load(sys.stdin if a.request == "-" else open(a.request, encoding="utf-8"))
|
| 27 |
+
print(json.dumps(m.decide(req.get("state", ""), req["questions"]), ensure_ascii=False, indent=2))
|
| 28 |
+
return
|
| 29 |
+
|
| 30 |
+
class H(BaseHTTPRequestHandler):
|
| 31 |
+
def _send(self, code, obj):
|
| 32 |
+
b = json.dumps(obj, ensure_ascii=False).encode("utf-8")
|
| 33 |
+
self.send_response(code); self.send_header("Content-Type", "application/json; charset=utf-8")
|
| 34 |
+
self.send_header("Content-Length", str(len(b))); self.end_headers(); self.wfile.write(b)
|
| 35 |
+
|
| 36 |
+
def do_GET(self):
|
| 37 |
+
if self.path == "/health":
|
| 38 |
+
self._send(200, {"ok": True, "model": "AUBIN Mini", "version": __version__, "variant": m.variant})
|
| 39 |
+
else:
|
| 40 |
+
self._send(404, {"error": "not found"})
|
| 41 |
+
|
| 42 |
+
def do_POST(self):
|
| 43 |
+
if self.path != "/decide":
|
| 44 |
+
return self._send(404, {"error": "not found"})
|
| 45 |
+
try:
|
| 46 |
+
req = json.loads(self.rfile.read(int(self.headers.get("Content-Length", 0))) or b"{}")
|
| 47 |
+
self._send(200, m.decide(req.get("state", ""), req["questions"]))
|
| 48 |
+
except (KeyError, ValueError, TypeError) as e:
|
| 49 |
+
self._send(400, {"error": str(e)})
|
| 50 |
+
|
| 51 |
+
def log_message(self, *args):
|
| 52 |
+
pass
|
| 53 |
+
|
| 54 |
+
print(f"AUBIN Mini {__version__} ({m.variant}) on http://{a.host}:{a.port} POST /decide", flush=True)
|
| 55 |
+
ThreadingHTTPServer((a.host, a.port), H).serve_forever()
|
| 56 |
+
|
| 57 |
+
|
| 58 |
+
if __name__ == "__main__":
|
| 59 |
+
main()
|
code/eval_kev.py
ADDED
|
@@ -0,0 +1,92 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
"""Reproduce AUBIN Mini's numbers on Kev's public locked suites (jaredpalmer/kev-suites, Apache-2.0).
|
| 2 |
+
|
| 3 |
+
pip install ./code && python code/eval_kev.py --model emrevrg/AUBIN-Mini --variant phone --out kev_phone.json
|
| 4 |
+
python code/eval_kev.py --shard 0/4 ... # split the work across processes / machines, then --merge a.json b.json ...
|
| 5 |
+
|
| 6 |
+
Same items, same metrics as the AUBIN/Kev protocol: accuracy, Brier, NLL, 15-bin ECE (overall and per source).
|
| 7 |
+
decision-v7 test = sources Kev trained on; transfer-v4 test = 764 records from sources Kev never trained on.
|
| 8 |
+
"""
|
| 9 |
+
import argparse
|
| 10 |
+
import json
|
| 11 |
+
import math
|
| 12 |
+
import time
|
| 13 |
+
|
| 14 |
+
import numpy as np
|
| 15 |
+
|
| 16 |
+
from aubin_mini import AubinMini, options_of
|
| 17 |
+
|
| 18 |
+
SUITES = {"kev_test": "v7/decision-v7/test.jsonl", "kev_transfer_test": "v4/transfer-v4/test.jsonl",
|
| 19 |
+
"kev_dev": "v7/decision-v7/development.jsonl", "kev_transfer_dev": "v4/transfer-v4/development.jsonl"}
|
| 20 |
+
|
| 21 |
+
|
| 22 |
+
def load(name):
|
| 23 |
+
from huggingface_hub import hf_hub_download
|
| 24 |
+
p = hf_hub_download("jaredpalmer/kev-suites", SUITES[name], repo_type="dataset")
|
| 25 |
+
items = []
|
| 26 |
+
for i, line in enumerate(open(p, encoding="utf-8")):
|
| 27 |
+
r = json.loads(line)
|
| 28 |
+
for qid, q in r["questions"].items():
|
| 29 |
+
keys, texts = options_of(q)
|
| 30 |
+
lab = q["label"]
|
| 31 |
+
lab = str(lab).lower() if isinstance(lab, bool) else str(lab)
|
| 32 |
+
skeys = [str(k).lower() if isinstance(k, bool) else str(k) for k in keys]
|
| 33 |
+
if lab not in skeys:
|
| 34 |
+
continue
|
| 35 |
+
items.append({"src": q.get("src") or r.get("_meta", {}).get("source") or name, "state": r["state"],
|
| 36 |
+
"q": q.get("instructions") or qid, "texts": texts, "y": skeys.index(lab)})
|
| 37 |
+
return items
|
| 38 |
+
|
| 39 |
+
|
| 40 |
+
def metrics(rows):
|
| 41 |
+
by, allr = {}, []
|
| 42 |
+
for src, y, lp in rows:
|
| 43 |
+
p = np.exp(np.asarray(lp)); oh = np.zeros(len(p)); oh[y] = 1
|
| 44 |
+
r = (int(p.argmax()) == y, float(((p - oh) ** 2).sum()), float(p.max()), float(-math.log(max(p[y], 1e-12))))
|
| 45 |
+
allr.append(r); by.setdefault(src, []).append(r)
|
| 46 |
+
|
| 47 |
+
def m(v):
|
| 48 |
+
ece = 0.0
|
| 49 |
+
for j in range(15):
|
| 50 |
+
bb = [x for x in v if j / 15 < x[2] <= (j + 1) / 15]
|
| 51 |
+
if bb:
|
| 52 |
+
ece += len(bb) / len(v) * abs(np.mean([x[2] for x in bb]) - np.mean([x[0] for x in bb]))
|
| 53 |
+
return {"n": len(v), "accuracy": round(float(np.mean([x[0] for x in v])), 4), "brier": round(float(np.mean([x[1] for x in v])), 4),
|
| 54 |
+
"nll": round(float(np.mean([x[3] for x in v])), 4), "ece15": round(float(ece), 4)}
|
| 55 |
+
return {"all": m(allr), "by_source": {k: m(v) for k, v in sorted(by.items())}}
|
| 56 |
+
|
| 57 |
+
|
| 58 |
+
def main():
|
| 59 |
+
ap = argparse.ArgumentParser()
|
| 60 |
+
ap.add_argument("--model", default="emrevrg/AUBIN-Mini"); ap.add_argument("--variant", default="phone")
|
| 61 |
+
ap.add_argument("--suites", default="kev_test,kev_transfer_test"); ap.add_argument("--threads", type=int, default=None)
|
| 62 |
+
ap.add_argument("--shard", default="0/1"); ap.add_argument("--out", default="aubin_mini_kev.json")
|
| 63 |
+
ap.add_argument("--merge", nargs="*", default=None, help="merge shard outputs instead of running")
|
| 64 |
+
a = ap.parse_args()
|
| 65 |
+
if a.merge:
|
| 66 |
+
rows = {}
|
| 67 |
+
for f in a.merge:
|
| 68 |
+
for s, v in json.load(open(f))["rows"].items():
|
| 69 |
+
rows.setdefault(s, []).extend(v)
|
| 70 |
+
R = {s: metrics(v) for s, v in rows.items()}
|
| 71 |
+
json.dump(R, open(a.out, "w"), indent=1); print(json.dumps({s: R[s]["all"] for s in R}))
|
| 72 |
+
return
|
| 73 |
+
k, n = map(int, a.shard.split("/"))
|
| 74 |
+
m = AubinMini.from_pretrained(a.model, a.variant, threads=a.threads)
|
| 75 |
+
out, ms = {"variant": a.variant, "rows": {}}, []
|
| 76 |
+
for s in a.suites.split(","):
|
| 77 |
+
its = load(s)[k::n]
|
| 78 |
+
rows = []
|
| 79 |
+
for it in its:
|
| 80 |
+
t = time.perf_counter()
|
| 81 |
+
lp = m.log_probs(it["q"], it["texts"], it["state"])
|
| 82 |
+
ms.append((time.perf_counter() - t) * 1000)
|
| 83 |
+
rows.append((it["src"], it["y"], [round(float(x), 6) for x in lp]))
|
| 84 |
+
out["rows"][s] = rows
|
| 85 |
+
print(s, json.dumps(metrics(rows)["all"]), flush=True)
|
| 86 |
+
out["ms_per_question"] = {"median": round(float(np.median(ms)), 1), "p95": round(float(np.percentile(ms, 95)), 1)}
|
| 87 |
+
json.dump(out, open(a.out, "w"))
|
| 88 |
+
print("ms/question", out["ms_per_question"], flush=True)
|
| 89 |
+
|
| 90 |
+
|
| 91 |
+
if __name__ == "__main__":
|
| 92 |
+
main()
|
code/pyproject.toml
ADDED
|
@@ -0,0 +1,17 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
[build-system]
|
| 2 |
+
requires = ["setuptools>=61"]
|
| 3 |
+
build-backend = "setuptools.build_meta"
|
| 4 |
+
|
| 5 |
+
[project]
|
| 6 |
+
name = "aubin-mini"
|
| 7 |
+
version = "0.1.0"
|
| 8 |
+
description = "AUBIN Mini by Norovox — on-device typed decisions with calibrated probabilities (ONNX Runtime, CPU)"
|
| 9 |
+
requires-python = ">=3.9"
|
| 10 |
+
license = {text = "Apache-2.0"}
|
| 11 |
+
dependencies = ["onnxruntime>=1.18", "tokenizers>=0.19", "numpy", "huggingface_hub"]
|
| 12 |
+
|
| 13 |
+
[project.scripts]
|
| 14 |
+
aubin-mini = "aubin_mini.__main__:main"
|
| 15 |
+
|
| 16 |
+
[tool.setuptools]
|
| 17 |
+
packages = ["aubin_mini"]
|