muthuishere commited on
Commit
d40936b
·
verified ·
1 Parent(s): 1dcb42c

Add openjevx.py: run the model from Python with the same answers as the server

Browse files
Files changed (1) hide show
  1. openjevx.py +122 -0
openjevx.py ADDED
@@ -0,0 +1,122 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ """OpenJevX in Python: the same answers as the OpenJevX server, without the server.
2
+
3
+ pip install onnxruntime tokenizers huggingface_hub
4
+
5
+ from openjevx import OpenJevX
6
+ jev = OpenJevX() # downloads openjevx.w8.onnx + tokenizer from Hugging Face once
7
+ jev.decide({"age": 17}, {
8
+ "adult": {"type": "noul", "instructions": "Is this person an adult (18 or older)?"},
9
+ })
10
+
11
+ A port of the server's internal/decide (prompt layout, option markers, temperature, softmax); the
12
+ request and answer shapes match POST /v1/systemone. The recommended way to run OpenJevX is still the
13
+ container (see the README); this is for notebooks and Python services that want it in-process.
14
+ """
15
+ import json
16
+ import math
17
+
18
+ import numpy as np
19
+ import onnxruntime as ort
20
+ from huggingface_hub import hf_hub_download
21
+ from tokenizers import Tokenizer
22
+
23
+ REPO = "muthuishere/openjevx"
24
+ CLS, SEP, MASK = 50281, 50282, 50284
25
+ MAX_LEN, HEAD_MAX = 1024, 256
26
+ TEMPERATURE = {0: 1.02553391456604, 1: 1.034013271331787, 2: 1.147902488708496} # choice, score, noul
27
+
28
+
29
+ def _criterion(v):
30
+ return v if isinstance(v, str) else json.dumps(v, separators=(",", ":"))
31
+
32
+
33
+ def _parse(q):
34
+ t, crit = q["type"], q.get("criteria")
35
+ if t == "choice":
36
+ pairs = [(_criterion(c), "") for c in crit] if isinstance(crit, list) else \
37
+ [(k, "" if v is None else _criterion(v)) for k, v in crit.items()]
38
+ return 0, [k for k, _ in pairs], [k if not v else f"{k}: {v}" for k, v in pairs]
39
+ if t == "score":
40
+ return 1, [str(i) for i in range(len(crit))], [f"level {i}: {_criterion(c)}" for i, c in enumerate(crit)]
41
+ if t == "noul":
42
+ f, tr = "no, the statement does not hold", "yes, the statement holds"
43
+ if isinstance(crit, dict):
44
+ f = crit.get("false") or f
45
+ tr = crit.get("true") or tr
46
+ return 2, ["false", "true"], [f"false: {f}", f"true: {tr}"]
47
+ raise ValueError(f"unknown type {t!r}")
48
+
49
+
50
+ class OpenJevX:
51
+ def __init__(self, model=None, tokenizer=None, providers=None):
52
+ model = model or hf_hub_download(REPO, "openjevx.w8.onnx")
53
+ tokenizer = tokenizer or hf_hub_download(REPO, "tokenizer/tokenizer.json")
54
+ self.tok = Tokenizer.from_file(tokenizer)
55
+ self.session = ort.InferenceSession(model, providers=providers or ["CPUExecutionProvider"])
56
+
57
+ def _enc(self, text):
58
+ return self.tok.encode(text, add_special_tokens=False).ids
59
+
60
+ def _item(self, state_ids, q):
61
+ qtype, keys, options = _parse(q)
62
+ head = self._enc(q["type"] + " question: " + q["instructions"].replace("[MASK]", " "))
63
+ opts = [[MASK] + self._enc(" " + o.replace("[MASK]", " "))[:48] for o in options]
64
+ budget = HEAD_MAX - sum(map(len, opts))
65
+ if budget < 16:
66
+ per = max(4, (HEAD_MAX - 16) // max(1, len(opts)))
67
+ opts = [o[:per] for o in opts]
68
+ budget = HEAD_MAX - sum(map(len, opts))
69
+ head = head[:max(8, budget)]
70
+ ids, markers = [CLS] + head + [SEP], []
71
+ for o in opts:
72
+ markers.append(len(ids))
73
+ ids += o
74
+ ids.append(SEP)
75
+ ids += state_ids[:max(0, MAX_LEN - len(ids) - 1)] + [SEP]
76
+ return ids[:MAX_LEN], [m for m in markers if m < MAX_LEN], qtype, keys
77
+
78
+ def decide(self, state, questions):
79
+ """state: dict/list (sent as JSON) or str; questions: {id: {type, instructions, criteria}}."""
80
+ text = state if isinstance(state, str) else json.dumps(state, separators=(",", ":"))
81
+ state_ids = self._enc(text.replace("[MASK]", " "))
82
+ qids = list(questions)
83
+ items = [self._item(state_ids, questions[i]) for i in qids]
84
+ n, length, width = len(items), max(len(i[0]) for i in items), max(len(i[1]) for i in items)
85
+ ids = np.zeros((n, length), np.int64); attn = np.zeros((n, length), np.int64)
86
+ pos = np.zeros((n, width), np.int64); pmask = np.zeros((n, width), bool)
87
+ qt = np.array([i[2] for i in items], np.int64)
88
+ for r, (tokens, markers, _, _) in enumerate(items):
89
+ ids[r, :len(tokens)] = tokens; attn[r, :len(tokens)] = 1
90
+ pos[r, :len(markers)] = markers; pmask[r, :len(markers)] = True
91
+ logits, act = self.session.run(["logits", "act_logits"], {
92
+ "input_ids": ids, "attention_mask": attn, "marker_pos": pos, "marker_mask": pmask, "qtype": qt})
93
+ out = {}
94
+ for r, qid in enumerate(qids):
95
+ _, markers, qtype, keys = items[r]
96
+ z = [float(x) / TEMPERATURE[qtype] for x in logits[r, :len(markers)]]
97
+ m = max(z); p = [math.exp(x - m) for x in z]; s = sum(p); p = [x / s for x in p]
98
+ best = max(range(len(p)), key=p.__getitem__)
99
+ ans = {"type": questions[qid]["type"], "probabilities": {k: round(p[j], 4) for j, k in enumerate(keys)},
100
+ "confidence": round(p[best], 4), "answer_confidence": round(p[best], 4)}
101
+ if qtype == 0:
102
+ ans["choice"] = keys[best]
103
+ elif qtype == 1:
104
+ ans["score"] = round(sum(j * pj for j, pj in enumerate(p)), 4)
105
+ else:
106
+ ans["noul"] = round(p[1], 4)
107
+ ans["confidence"] = round(max(p[1], 1 - p[1]), 4)
108
+ a0, a1 = float(act[r][0]), float(act[r][1]); am = max(a0, a1)
109
+ e0, e1 = math.exp(a0 - am), math.exp(a1 - am)
110
+ ans["action"] = {"act_probability": round(e0 / (e0 + e1), 4)}
111
+ out[qid] = ans
112
+ return {"answers": out}
113
+
114
+
115
+ if __name__ == "__main__":
116
+ jev = OpenJevX()
117
+ print(json.dumps(jev.decide({"age": 17}, {
118
+ "adult": {"type": "noul", "instructions": "Is this person an adult (18 or older)?"},
119
+ "team": {"type": "choice", "instructions": "Which team should handle this ticket?",
120
+ "criteria": {"billing": "payments and charges", "engineering": "bugs and outages"}},
121
+ "sev": {"type": "score", "instructions": "How severe is this?", "criteria": ["low", "medium", "high"]},
122
+ }), indent=2))