Shared Kai/Eos runtime, release parity report, link to Eos
Browse files- README.md +5 -3
- conversion/check_release.py +11 -3
- decision2_coreml.py +92 -21
- reports/typed-decisions-test.txt +5 -9
README.md
CHANGED
|
@@ -52,9 +52,9 @@ Against the upstream runtime (fp32, Transformers 5.18) on the **typed-decisions
|
|
| 52 |
|
| 53 |
| | Upstream | Core ML fp16 |
|
| 54 |
|---|---:|---:|
|
| 55 |
-
| Choice accuracy (600) | 0.423 | 0.
|
| 56 |
| Yes/No accuracy (600) | 0.628 | 0.628 |
|
| 57 |
-
| Score accuracy (800) | 0.398 | 0.
|
| 58 |
| Answers that differ from upstream | — | 5 / 2,000 (all near-ties: upstream top-2 margin ≤ 0.009) |
|
| 59 |
| Max probability difference | — | 0.0082 |
|
| 60 |
|
|
@@ -67,7 +67,7 @@ comparison with upstream: they are not the model card's benchmark protocol.
|
|
| 67 |
|---|---:|---:|---:|
|
| 68 |
| Card quickstart, 3 questions | 263 ms | 89 ms | **28 ms** |
|
| 69 |
| typed-decisions request, 5 questions (median) | — | 418 ms | **61 ms** |
|
| 70 |
-
| One email, 64 questions | 9,514 ms | 3,650 ms | **
|
| 71 |
|
| 72 |
Times are full requests (tokenize, pack, predict, answer) and exclude model loading. Per call: L256 14 ms, L512 28 ms,
|
| 73 |
L1024 58 ms, L2048 142 ms. The upstream runtime on a Mac has none of its GPU fast paths (HIP graphs, fused kernels),
|
|
@@ -81,6 +81,8 @@ See `coreml_config.json`. `input_ids`, `position_ids` [1, L] int32; `mask` [1, 1
|
|
| 81 |
|
| 82 |
## Conversion
|
| 83 |
|
|
|
|
|
|
|
| 84 |
`conversion/`: `kai_graph.py` (traceable Qwen3 backbone + decision head, loads the source safetensors),
|
| 85 |
`convert.py L N` (torch 2.7.0, coremltools 9.0), `combine.py` (multifunction package), `pack.py` + `typed_eval.py` +
|
| 86 |
`typed_ref.py` (parity against the upstream runtime; `pack.py` imports the source repo's vendored encoder from `kai/`).
|
|
|
|
| 52 |
|
| 53 |
| | Upstream | Core ML fp16 |
|
| 54 |
|---|---:|---:|
|
| 55 |
+
| Choice accuracy (600) | 0.423 | 0.422 |
|
| 56 |
| Yes/No accuracy (600) | 0.628 | 0.628 |
|
| 57 |
+
| Score accuracy (800) | 0.398 | 0.398 |
|
| 58 |
| Answers that differ from upstream | — | 5 / 2,000 (all near-ties: upstream top-2 margin ≤ 0.009) |
|
| 59 |
| Max probability difference | — | 0.0082 |
|
| 60 |
|
|
|
|
| 67 |
|---|---:|---:|---:|
|
| 68 |
| Card quickstart, 3 questions | 263 ms | 89 ms | **28 ms** |
|
| 69 |
| typed-decisions request, 5 questions (median) | — | 418 ms | **61 ms** |
|
| 70 |
+
| One email, 64 questions | 9,514 ms | 3,650 ms | **442 ms** (7 calls) |
|
| 71 |
|
| 72 |
Times are full requests (tokenize, pack, predict, answer) and exclude model loading. Per call: L256 14 ms, L512 28 ms,
|
| 73 |
L1024 58 ms, L2048 142 ms. The upstream runtime on a Mac has none of its GPU fast paths (HIP graphs, fused kernels),
|
|
|
|
| 81 |
|
| 82 |
## Conversion
|
| 83 |
|
| 84 |
+
Decision-2.0-Eos-0.8B (Qwen3.5 hybrid) is at [FluidInference/decision-2.0-eos-coreml](https://huggingface.co/FluidInference/decision-2.0-eos-coreml), with the same runtime.
|
| 85 |
+
|
| 86 |
`conversion/`: `kai_graph.py` (traceable Qwen3 backbone + decision head, loads the source safetensors),
|
| 87 |
`convert.py L N` (torch 2.7.0, coremltools 9.0), `combine.py` (multifunction package), `pack.py` + `typed_eval.py` +
|
| 88 |
`typed_ref.py` (parity against the upstream runtime; `pack.py` imports the source repo's vendored encoder from `kai/`).
|
conversion/check_release.py
CHANGED
|
@@ -1,15 +1,17 @@
|
|
| 1 |
import json, sys, time
|
| 2 |
import numpy as np, pandas as pd
|
| 3 |
-
sys.path.insert(0,
|
| 4 |
from decision2_coreml import Decision2CoreML, encode as r_encode, question_to_row as r_row
|
| 5 |
from transformers import AutoTokenizer
|
| 6 |
import pack, q64ctx
|
| 7 |
-
ref_root, refpath = sys.argv[1], sys.argv[2]
|
| 8 |
tok = AutoTokenizer.from_pretrained(ref_root)
|
| 9 |
-
m = Decision2CoreML(
|
| 10 |
d = pd.read_parquet("data/typed/all/test-00000-of-00001.parquet")
|
| 11 |
ref = {j["id"]: j["answers"] for j in map(json.loads, open(refpath))}
|
| 12 |
def lab(a): return ("true" if a["noul"] >= .5 else "false") if a["type"] == "noul" else max(a["probabilities"], key=a["probabilities"].get)
|
|
|
|
|
|
|
| 13 |
tokdiff = flips = n = 0; worst = 0; ts = []
|
| 14 |
for r in d.itertuples():
|
| 15 |
st, qs = json.loads(r.state), json.loads(r.questions)
|
|
@@ -20,10 +22,16 @@ for r in d.itertuples():
|
|
| 20 |
for q in qs:
|
| 21 |
rq, aq = ref[r.id][q], a[q]; n += 1; flips += lab(rq) != lab(aq)
|
| 22 |
pr = rq.get("probabilities", {"true": rq.get("noul")}); pa = aq.get("probabilities", {"true": aq.get("noul")})
|
|
|
|
|
|
|
|
|
|
|
|
|
| 23 |
worst = max(worst, max(abs(pr[k] - pa[k]) for k in pr))
|
| 24 |
for f in ("confidence", "score"):
|
| 25 |
if f in rq: assert abs(rq[f] - aq[f]) < 0.05, (r.id, q, f)
|
| 26 |
assert set(rq) == set(aq), (set(rq) ^ set(aq))
|
|
|
|
|
|
|
| 27 |
print(f"typed: token mismatches {tokdiff}, {n} decisions, flips {flips}, max|dp| {worst:.4f}, request p50 {np.median(ts):.1f} ms")
|
| 28 |
t = time.time(); a = m.system_one(state=q64ctx.state, questions=q64ctx.Q); m.system_one(state=q64ctx.state, questions=q64ctx.Q)
|
| 29 |
ts = []
|
|
|
|
| 1 |
import json, sys, time
|
| 2 |
import numpy as np, pandas as pd
|
| 3 |
+
sys.path.insert(0, sys.argv[3])
|
| 4 |
from decision2_coreml import Decision2CoreML, encode as r_encode, question_to_row as r_row
|
| 5 |
from transformers import AutoTokenizer
|
| 6 |
import pack, q64ctx
|
| 7 |
+
ref_root, refpath, rel = sys.argv[1], sys.argv[2], sys.argv[3]
|
| 8 |
tok = AutoTokenizer.from_pretrained(ref_root)
|
| 9 |
+
m = Decision2CoreML(rel)
|
| 10 |
d = pd.read_parquet("data/typed/all/test-00000-of-00001.parquet")
|
| 11 |
ref = {j["id"]: j["answers"] for j in map(json.loads, open(refpath))}
|
| 12 |
def lab(a): return ("true" if a["noul"] >= .5 else "false") if a["type"] == "noul" else max(a["probabilities"], key=a["probabilities"].get)
|
| 13 |
+
from collections import defaultdict
|
| 14 |
+
acc = defaultdict(lambda: [0, 0, 0]); margins = []
|
| 15 |
tokdiff = flips = n = 0; worst = 0; ts = []
|
| 16 |
for r in d.itertuples():
|
| 17 |
st, qs = json.loads(r.state), json.loads(r.questions)
|
|
|
|
| 22 |
for q in qs:
|
| 23 |
rq, aq = ref[r.id][q], a[q]; n += 1; flips += lab(rq) != lab(aq)
|
| 24 |
pr = rq.get("probabilities", {"true": rq.get("noul")}); pa = aq.get("probabilities", {"true": aq.get("noul")})
|
| 25 |
+
g = json.loads(r.gold)[q]["label"]; s_ = acc[rq["type"]]; s_[0] += 1; s_[1] += lab(rq) == g; s_[2] += lab(aq) == g
|
| 26 |
+
if lab(rq) != lab(aq):
|
| 27 |
+
ps = sorted(pr.values(), reverse=True) if len(pr) > 1 else sorted([pr["true"], 1 - pr["true"]], reverse=True)
|
| 28 |
+
margins.append(round(ps[0] - ps[1], 4))
|
| 29 |
worst = max(worst, max(abs(pr[k] - pa[k]) for k in pr))
|
| 30 |
for f in ("confidence", "score"):
|
| 31 |
if f in rq: assert abs(rq[f] - aq[f]) < 0.05, (r.id, q, f)
|
| 32 |
assert set(rq) == set(aq), (set(rq) ^ set(aq))
|
| 33 |
+
print("flip margins (upstream top-2)", margins)
|
| 34 |
+
for k, (c, h1, h2) in sorted(acc.items()): print(f" {k:6s} n={c} acc upstream {h1/c:.3f} coreml {h2/c:.3f}")
|
| 35 |
print(f"typed: token mismatches {tokdiff}, {n} decisions, flips {flips}, max|dp| {worst:.4f}, request p50 {np.median(ts):.1f} ms")
|
| 36 |
t = time.time(); a = m.system_one(state=q64ctx.state, questions=q64ctx.Q); m.system_one(state=q64ctx.state, questions=q64ctx.Q)
|
| 37 |
ts = []
|
decision2_coreml.py
CHANGED
|
@@ -1,5 +1,7 @@
|
|
| 1 |
"""Decision 2.0 System One on Core ML: every question of a request in one packed call.
|
| 2 |
|
|
|
|
|
|
|
| 3 |
Needs only numpy, tokenizers and coremltools. Prompt rendering, option endpoints, Score
|
| 4 |
offsets and answer normalization follow the upstream runtime (vllm-sr Decision 2.0,
|
| 5 |
Apache-2.0: decision2/_vendor/dev2model/{decision_model,infer,score_bias}.py).
|
|
@@ -117,6 +119,65 @@ def pack(encoded: list[dict[str, Any]], L: int, N: int, pad_id: int) -> tuple[di
|
|
| 117 |
}, owner
|
| 118 |
|
| 119 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 120 |
def answer(kind: str, keys: list[str], logits: list[float], descriptions: list[Any]) -> dict[str, Any]:
|
| 121 |
top = max(logits)
|
| 122 |
exps = [math.exp(v - top) for v in logits]
|
|
@@ -134,6 +195,10 @@ def answer(kind: str, keys: list[str], logits: list[float], descriptions: list[A
|
|
| 134 |
return {"type": "choice", "choice": keys[probs.index(max(probs))], "probabilities": pmap, "confidence": confidence}
|
| 135 |
|
| 136 |
|
|
|
|
|
|
|
|
|
|
|
|
|
| 137 |
class Decision2CoreML:
|
| 138 |
def __init__(self, root: str | Path = ".", package: str | None = None,
|
| 139 |
compute_units: ct.ComputeUnit = ct.ComputeUnit.CPU_AND_GPU):
|
|
@@ -141,50 +206,56 @@ class Decision2CoreML:
|
|
| 141 |
self.config = json.loads((root / "coreml_config.json").read_text())
|
| 142 |
self.tokenizer = Tokenizer.from_file(str(root / "tokenizer.json"))
|
| 143 |
self.pad_id = self.config["pad_token_id"]
|
| 144 |
-
|
| 145 |
-
|
|
|
|
|
|
|
| 146 |
path = str(root / (package or self.config["package"]))
|
| 147 |
self.cost_ms = self.config["functions"]
|
| 148 |
-
self.buckets = [
|
| 149 |
-
|
| 150 |
-
L, N = (int(x[1:]) for x in name.split("_"))
|
| 151 |
-
self.buckets.append((L, N, ct.models.MLModel(path, function_name=name, compute_units=compute_units)))
|
| 152 |
|
| 153 |
-
def
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 154 |
"""Fewest estimated milliseconds: one call in the smallest bucket that fits, or greedy chunks."""
|
| 155 |
best = None
|
| 156 |
-
for
|
| 157 |
groups, group = [], []
|
| 158 |
for j in range(len(encoded)):
|
| 159 |
-
|
| 160 |
-
|
| 161 |
-
if not group:
|
| 162 |
break # this question alone does not fit this bucket
|
| 163 |
groups.append(group)
|
| 164 |
group = []
|
| 165 |
-
T, C = packed_size([encoded[j]])
|
| 166 |
-
if T > L or C > N:
|
| 167 |
-
break
|
| 168 |
group.append(j)
|
| 169 |
else:
|
| 170 |
groups.append(group)
|
| 171 |
-
cost = len(groups) * self.cost_ms[
|
| 172 |
if best is None or cost < best[0]:
|
| 173 |
-
best = (cost, [(
|
| 174 |
if best is None:
|
| 175 |
-
|
| 176 |
-
raise ValueError(f"a question exceeds the largest bucket L{L}_N{N}")
|
| 177 |
return best[1]
|
| 178 |
|
| 179 |
def logits(self, encoded: list[dict[str, Any]]) -> list[list[float]]:
|
| 180 |
per: list[list[float]] = [[] for _ in encoded]
|
| 181 |
-
for
|
| 182 |
-
x, owner =
|
| 183 |
out = m.predict(x)["logits"]
|
| 184 |
for k, o in enumerate(owner):
|
| 185 |
per[group[o]].append(float(out[k]))
|
| 186 |
return per
|
| 187 |
-
|
| 188 |
def system_one(self, state: Any, questions: dict[str, dict[str, Any]]) -> dict[str, Any]:
|
| 189 |
rows, encoded, answers = {}, {}, {}
|
| 190 |
for qid, q in questions.items():
|
|
|
|
| 1 |
"""Decision 2.0 System One on Core ML: every question of a request in one packed call.
|
| 2 |
|
| 3 |
+
Supports the Qwen3 package (Kai, `L*_N*` functions) and the Qwen3.5 hybrid package (Eos, `S*_C*_N*` functions).
|
| 4 |
+
|
| 5 |
Needs only numpy, tokenizers and coremltools. Prompt rendering, option endpoints, Score
|
| 6 |
offsets and answer normalization follow the upstream runtime (vllm-sr Decision 2.0,
|
| 7 |
Apache-2.0: decision2/_vendor/dev2model/{decision_model,infer,score_bias}.py).
|
|
|
|
| 119 |
}, owner
|
| 120 |
|
| 121 |
|
| 122 |
+
def pack_hybrid(encoded: list[dict[str, Any]], S: int, P: int, N: int, pad_id: int, rope: dict[str, Any],
|
| 123 |
+
lags: int = 3, chunk: int = 64) -> tuple[dict[str, np.ndarray], list[int]]:
|
| 124 |
+
"""Qwen3.5 (Gated DeltaNet hybrid) graph inputs: the shared prefix right-padded to S, the questions' suffixes
|
| 125 |
+
packed in P, plus the masks that make every suffix restart from the prefix's state (see README)."""
|
| 126 |
+
pre = _prefix(encoded)
|
| 127 |
+
total = sum(len(e["ids"]) - pre for e in encoded)
|
| 128 |
+
options = sum(len(e["keys"]) for e in encoded)
|
| 129 |
+
if not 1 <= pre <= S or total > P or options > N:
|
| 130 |
+
raise ValueError(f"prefix {pre} / {total} question tokens / {options} options exceed S{S}_C{P}_N{N}")
|
| 131 |
+
ids = np.full(S + P, pad_id, dtype=np.int32)
|
| 132 |
+
pos = np.zeros(S + P, dtype=np.float64)
|
| 133 |
+
ids[:pre] = encoded[0]["ids"][:pre]
|
| 134 |
+
pos[:pre] = np.arange(pre)
|
| 135 |
+
pos[pre:] = pre
|
| 136 |
+
valid = np.zeros(S, dtype=np.float16)
|
| 137 |
+
valid[:pre] = 1
|
| 138 |
+
tail = np.zeros((lags, S), dtype=np.float16)
|
| 139 |
+
for i in range(lags):
|
| 140 |
+
if pre - lags + i >= 0:
|
| 141 |
+
tail[i, pre - lags + i] = 1
|
| 142 |
+
segment = np.eye(P, dtype=np.float16)
|
| 143 |
+
keep = np.zeros((lags, P), dtype=np.float16)
|
| 144 |
+
lag_tail = np.zeros((lags, P, lags), dtype=np.float16)
|
| 145 |
+
cand, qry, owner = [], [], []
|
| 146 |
+
start = 0
|
| 147 |
+
for j, e in enumerate(encoded):
|
| 148 |
+
n = len(e["ids"]) - pre
|
| 149 |
+
ids[S + start : S + start + n] = e["ids"][pre:]
|
| 150 |
+
pos[S + start : S + start + n] = np.arange(pre, pre + n)
|
| 151 |
+
segment[start : start + n, start : start + n] = np.tril(np.ones((n, n)))
|
| 152 |
+
for p in range(n):
|
| 153 |
+
for s in range(1, lags + 1):
|
| 154 |
+
if p >= s:
|
| 155 |
+
keep[s - 1, start + p] = 1
|
| 156 |
+
else:
|
| 157 |
+
lag_tail[s - 1, start + p, lags + p - s] = 1
|
| 158 |
+
for c in e["candidate_positions"]:
|
| 159 |
+
cand.append(start + c - pre)
|
| 160 |
+
qry.append(start + e["query_position"] - pre)
|
| 161 |
+
owner.append(j)
|
| 162 |
+
start += n
|
| 163 |
+
dim = rope["rotary_dim"]
|
| 164 |
+
inv_freq = 1.0 / (rope["rope_theta"] ** (np.arange(0, dim, 2, dtype=np.float64) / dim))
|
| 165 |
+
freqs = pos[:, None] * inv_freq[None, :] # text-only M-RoPE: all three axes share the position
|
| 166 |
+
emb = np.concatenate([freqs, freqs], axis=-1)
|
| 167 |
+
M = P // chunk
|
| 168 |
+
starts = np.argmax(segment > 0, axis=1)
|
| 169 |
+
pad = N - len(cand)
|
| 170 |
+
return {
|
| 171 |
+
"input_ids": ids[None], "cos": np.cos(emb).astype(np.float16), "sin": np.sin(emb).astype(np.float16),
|
| 172 |
+
"valid": valid, "tail_onehot": tail, "segment": segment, "lag_keep": keep, "lag_tail": lag_tail,
|
| 173 |
+
"seg_chunks": np.stack([segment[i * chunk : (i + 1) * chunk, i * chunk : (i + 1) * chunk] for i in range(M)]),
|
| 174 |
+
"cont": (starts < (np.arange(P) // chunk) * chunk).astype(np.float16),
|
| 175 |
+
"last_seg": np.stack([segment[(i + 1) * chunk - 1, i * chunk : (i + 1) * chunk] for i in range(M)]),
|
| 176 |
+
"cand_idx": np.array(cand + [0] * pad, dtype=np.int32),
|
| 177 |
+
"query_idx": np.array(qry + [0] * pad, dtype=np.int32),
|
| 178 |
+
}, owner
|
| 179 |
+
|
| 180 |
+
|
| 181 |
def answer(kind: str, keys: list[str], logits: list[float], descriptions: list[Any]) -> dict[str, Any]:
|
| 182 |
top = max(logits)
|
| 183 |
exps = [math.exp(v - top) for v in logits]
|
|
|
|
| 195 |
return {"type": "choice", "choice": keys[probs.index(max(probs))], "probabilities": pmap, "confidence": confidence}
|
| 196 |
|
| 197 |
|
| 198 |
+
def _dims(name: str) -> dict[str, int]:
|
| 199 |
+
return {part[0]: int(part[1:]) for part in name.split("_")}
|
| 200 |
+
|
| 201 |
+
|
| 202 |
class Decision2CoreML:
|
| 203 |
def __init__(self, root: str | Path = ".", package: str | None = None,
|
| 204 |
compute_units: ct.ComputeUnit = ct.ComputeUnit.CPU_AND_GPU):
|
|
|
|
| 206 |
self.config = json.loads((root / "coreml_config.json").read_text())
|
| 207 |
self.tokenizer = Tokenizer.from_file(str(root / "tokenizer.json"))
|
| 208 |
self.pad_id = self.config["pad_token_id"]
|
| 209 |
+
self.hybrid = self.config.get("backbone", "qwen3") == "qwen3_5"
|
| 210 |
+
bias = root / "score_bias.json"
|
| 211 |
+
offsets = json.loads(bias.read_text())["offsets"] if bias.exists() else {}
|
| 212 |
+
self.score_bias = {int(k): v for k, v in offsets.items()}
|
| 213 |
path = str(root / (package or self.config["package"]))
|
| 214 |
self.cost_ms = self.config["functions"]
|
| 215 |
+
self.buckets = [(name, _dims(name), ct.models.MLModel(path, function_name=name, compute_units=compute_units))
|
| 216 |
+
for name in self.config["functions"]] # smallest first
|
|
|
|
|
|
|
| 217 |
|
| 218 |
+
def _fits(self, d: dict[str, int], encoded: list[dict[str, Any]]) -> bool:
|
| 219 |
+
options = sum(len(e["keys"]) for e in encoded)
|
| 220 |
+
if self.hybrid:
|
| 221 |
+
pre = _prefix(encoded)
|
| 222 |
+
return pre <= d["S"] and sum(len(e["ids"]) - pre for e in encoded) <= d["C"] and options <= d["N"]
|
| 223 |
+
return packed_size(encoded)[0] <= d["L"] and options <= d["N"]
|
| 224 |
+
|
| 225 |
+
def _inputs(self, d: dict[str, int], encoded: list[dict[str, Any]]):
|
| 226 |
+
if self.hybrid:
|
| 227 |
+
return pack_hybrid(encoded, d["S"], d["C"], d["N"], self.pad_id, self.config["rope"])
|
| 228 |
+
return pack(encoded, d["L"], d["N"], self.pad_id)
|
| 229 |
+
|
| 230 |
+
def _calls(self, encoded: list[dict[str, Any]]) -> list[tuple[Any, dict[str, int], list[int]]]:
|
| 231 |
"""Fewest estimated milliseconds: one call in the smallest bucket that fits, or greedy chunks."""
|
| 232 |
best = None
|
| 233 |
+
for name, d, m in self.buckets:
|
| 234 |
groups, group = [], []
|
| 235 |
for j in range(len(encoded)):
|
| 236 |
+
if not self._fits(d, [encoded[i] for i in group + [j]]):
|
| 237 |
+
if not group or not self._fits(d, [encoded[j]]):
|
|
|
|
| 238 |
break # this question alone does not fit this bucket
|
| 239 |
groups.append(group)
|
| 240 |
group = []
|
|
|
|
|
|
|
|
|
|
| 241 |
group.append(j)
|
| 242 |
else:
|
| 243 |
groups.append(group)
|
| 244 |
+
cost = len(groups) * self.cost_ms[name]
|
| 245 |
if best is None or cost < best[0]:
|
| 246 |
+
best = (cost, [(m, d, g) for g in groups])
|
| 247 |
if best is None:
|
| 248 |
+
raise ValueError(f"a question exceeds the largest function {self.buckets[-1][0]}")
|
|
|
|
| 249 |
return best[1]
|
| 250 |
|
| 251 |
def logits(self, encoded: list[dict[str, Any]]) -> list[list[float]]:
|
| 252 |
per: list[list[float]] = [[] for _ in encoded]
|
| 253 |
+
for m, d, group in self._calls(encoded):
|
| 254 |
+
x, owner = self._inputs(d, [encoded[i] for i in group])
|
| 255 |
out = m.predict(x)["logits"]
|
| 256 |
for k, o in enumerate(owner):
|
| 257 |
per[group[o]].append(float(out[k]))
|
| 258 |
return per
|
|
|
|
| 259 |
def system_one(self, state: Any, questions: dict[str, dict[str, Any]]) -> dict[str, Any]:
|
| 260 |
rows, encoded, answers = {}, {}, {}
|
| 261 |
for qid, q in questions.items():
|
reports/typed-decisions-test.txt
CHANGED
|
@@ -1,10 +1,6 @@
|
|
| 1 |
-
|
| 2 |
-
|
| 3 |
-
flip customer_service_000075/action choice upstream escalate_to_human coreml request_information upstream top-2 margin 0.0003
|
| 4 |
-
flip invoice_processing_000020/duplicate noul upstream true coreml false upstream top-2 margin 0.0008
|
| 5 |
-
flip security_incidents_000023/true_positive noul upstream false coreml true upstream top-2 margin 0.0088
|
| 6 |
-
ALL: 2000 decisions, flips 5, max|dp| 0.0082, request p50 61.2 ms p95 63.8 ms, calls 407
|
| 7 |
-
bucket use {1024: 400}
|
| 8 |
-
choice n=600 acc upstream 0.423 coreml 0.420
|
| 9 |
noul n=600 acc upstream 0.628 coreml 0.628
|
| 10 |
-
score n=800 acc upstream 0.398 coreml 0.
|
|
|
|
|
|
|
|
|
| 1 |
+
flip margins (upstream top-2) [0.0011, 0.0004, 0.0003, 0.0008, 0.0088]
|
| 2 |
+
choice n=600 acc upstream 0.423 coreml 0.422
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 3 |
noul n=600 acc upstream 0.628 coreml 0.628
|
| 4 |
+
score n=800 acc upstream 0.398 coreml 0.398
|
| 5 |
+
typed: token mismatches 0, 2000 decisions, flips 5, max|dp| 0.0082, request p50 60.9 ms
|
| 6 |
+
64 questions: 7 calls, p50 442 ms end to end
|