Q1z commited on
Commit
7c85c7e
·
verified ·
1 Parent(s): 14bf8c2

Expand Pivot model card, benchmarks, CPU tools and charts

Browse files
.gitattributes CHANGED
@@ -33,3 +33,4 @@ saved_model/**/* filter=lfs diff=lfs merge=lfs -text
33
  *.zip filter=lfs diff=lfs merge=lfs -text
34
  *.zst filter=lfs diff=lfs merge=lfs -text
35
  *tfevents* filter=lfs diff=lfs merge=lfs -text
 
 
33
  *.zip filter=lfs diff=lfs merge=lfs -text
34
  *.zst filter=lfs diff=lfs merge=lfs -text
35
  *tfevents* filter=lfs diff=lfs merge=lfs -text
36
+ evaluation/2026-09-24/performance_overview.png filter=lfs diff=lfs merge=lfs -text
README.md CHANGED
@@ -12,28 +12,85 @@ tags:
12
 
13
  # Pivot
14
 
15
- Pivot is a set-scored decision encoder trained with the reviewed DSBT method.
16
 
17
- Input is one context plus 2-16 semantic candidates. Output is a closed-set probability vector.
18
- The training recipe uses two CE warmup epochs followed by live multiclass Brier + PCGrad.
19
- Probabilities are computed as `softmax(logits.float())`.
20
 
21
- The package vendors the exact bidirectional LFM2 runtime used for Pivot and is verified in a
22
- fresh offline Python process before upload.
 
 
 
 
 
23
 
24
- ## Load
 
 
 
 
 
 
 
 
 
 
25
 
26
  ```python
 
27
  from transformers import AutoModel, AutoTokenizer
28
 
29
- tok = AutoTokenizer.from_pretrained("Q1z/Pivot", trust_remote_code=True)
30
- model = AutoModel.from_pretrained("Q1z/Pivot", trust_remote_code=True)
31
- result = model.choose(tok, "Customer says the invoice is wrong.", [
 
 
 
 
32
  "route to billing support",
33
  "route to technical support",
34
  "route to sales",
35
- ])
36
- print(result)
 
37
  ```
38
 
39
- Training/evaluation provenance is under `training/`.
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
12
 
13
  # Pivot
14
 
15
+ ### Fast closed-set decisions from context and candidate actions
16
 
17
+ Pivot is a **357.6M-parameter** bidirectional decision encoder. Give it a context and two or more candidate answers; it scores the set in one forward pass and returns the chosen answer, its position, and a probability for every candidate. It does not generate free-form text. The full FP32 checkpoint, tokenizer and Transformers custom runtime are stored in this model repository.
 
 
18
 
19
+ | Measured result | Pivot |
20
+ |---|---:|
21
+ | JevBench v1.4.1 public accuracy | **46.32%** (107 / 231) |
22
+ | NVIDIA H200 warm single-decision p50 / p95 | **15.8 / 19.9 ms** |
23
+ | NVIDIA H200 throughput, batch 32 | **545.3 decisions/s** |
24
+ | 4-thread Xeon CPU warm single-decision p50 / p95 | **797.6 / 1,089.0 ms** |
25
+ | 4-thread Xeon CPU throughput, batch 4 | **3.77 decisions/s** |
26
 
27
+ These are measurements on the pinned checkpoint, in FP32, including tokenization and scoring. GPU and CPU throughput used **different batch sizes**. The 46.32% figure is public-task accuracy; the **official JevBench v1.4 composite score has not been measured** because the sealed/judge portion and cost input are unavailable. [Protocol and limitations](docs/BENCHMARK.md) · [Full measurement details](docs/PERFORMANCE.md)
28
+
29
+ ![Public accuracy and warm local speed](evaluation/2026-09-24/performance_overview.png)
30
+
31
+ ## Start with one decision
32
+
33
+ Install a suitable PyTorch build and the runtime packages:
34
+
35
+ ```bash
36
+ python -m pip install "transformers==5.17.0" "safetensors==0.8.0"
37
+ ```
38
 
39
  ```python
40
+ import torch
41
  from transformers import AutoModel, AutoTokenizer
42
 
43
+ tokenizer = AutoTokenizer.from_pretrained("Q1z/Pivot", trust_remote_code=True)
44
+ model = AutoModel.from_pretrained(
45
+ "Q1z/Pivot", trust_remote_code=True, dtype=torch.float32
46
+ ).eval()
47
+
48
+ context = "CONTEXT:\nA customer disputes an invoice and asks for a correction."
49
+ options = [
50
  "route to billing support",
51
  "route to technical support",
52
  "route to sales",
53
+ ]
54
+ decision = model.choose(tokenizer, context, options)
55
+ print(decision) # {"choice": ..., "index": ..., "probs": [...]}
56
  ```
57
 
58
+ The serving configuration now defaults to **512 context tokens and 128 option tokens**, matching the published public evaluation. It does not change the checkpoint weights. Run on CUDA with `model.to("cuda")` if your PyTorch build supports it. Review the repository's custom model code before enabling `trust_remote_code=True`.
59
+
60
+ ## Three ways to use Pivot
61
+
62
+ | Method | Input | Output |
63
+ |---|---|---|
64
+ | `model.choose(tokenizer, context, options)` | Context and ordered answer strings | Chosen answer, index, probability vector |
65
+ | `model.decide_native(tokenizer, context, candidates)` | Candidate IDs, semantic text, optional abstain action | Selected ID, relative confidence, per-candidate probabilities |
66
+ | `model.decide(tokenizer, state, questions)` | Several typed choice / yes-no / score questions | Typed decision response |
67
+
68
+ For repeated decisions using the same options, `encode_candidates`, `encode_context`, and `choose_cached` reuse candidate representations. This can avoid repeated candidate encoding; the measured throughput above uses the **uncached** path. Examples: [basic](examples/quickstart.py), [structured decisions](examples/native_decision.py), [cached candidates](examples/cached_candidates.py), and [full inference guide](docs/INFERENCE.md).
69
+
70
+ Pivot's probabilities are **relative to the supplied candidate set**. Give each candidate a clear, distinct meaning. An abstain route must be an explicit candidate; a high relative probability alone does not establish real-world correctness or safety.
71
+
72
+ ## Evaluated performance
73
+
74
+ The official JevBench v1.4.1 **public tasks** were scored with the official per-task scorer at commit `24b9b5c1609a7a9e8fa14f49e5985a836c9dc842`. The exact evaluated model commit was `14bf8c26bf344ebdf88e22a4b6152dc5f75f3578`.
75
+
76
+ | Public tier | Correct / tasks | Accuracy | ECE, 10 bins |
77
+ |---|---:|---:|---:|
78
+ | Original | 27 / 72 | 37.50% | 0.525 |
79
+ | Easy | 39 / 48 | 81.25% | 0.114 |
80
+ | Hard | 41 / 111 | 36.94% | 0.379 |
81
+ | **All public tasks** | **107 / 231** | **46.32%** | — |
82
+
83
+ The frozen evaluation format is a `CONTEXT:` prefix, task rubric descriptions where provided (otherwise humanized labels) in official label order, right truncation, 512 context tokens and 128 option tokens. Public accuracy is not an official leaderboard score. [Reproduce public results](benchmarks/README.md) or [run the CPU speed entry point](cpu-speed/README.md). The [CPU notebook](notebooks/Pivot_CPU_Speed.ipynb) provides an interactive alternative.
84
+
85
+ ## Repository guide
86
+
87
+ - [Inference methods and examples](docs/INFERENCE.md)
88
+ - [Serving interfaces and request formats](serving/README.md)
89
+ - [JevBench protocol, revision, and score scope](docs/BENCHMARK.md)
90
+ - [Results, hardware, charts, and caveats](docs/PERFORMANCE.md)
91
+ - [Reproducible public benchmark script](benchmarks/jevbench_public.py)
92
+ - [Standalone CPU speed script](cpu-speed/benchmark.py)
93
+ - [Pinned result files and chart](evaluation/2026-09-24/README.md)
94
+ - [What changed in this update](docs/UPDATE_NOTES.md)
95
+
96
+ The files `model.safetensors`, `modeling_pivot.py`, `modeling_lfm2_bidirectional.py`, `pivot_model.py` and `pivot_infer.py` contain the checkpoint and model runtime. [Package manifest](manifest.json) records file hashes. The original checkpoint and evaluated metrics remain anchored to the exact revision above.
benchmarks/README.md ADDED
@@ -0,0 +1,14 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # Reproduce Pivot's public results
2
+
3
+ Run the standalone `jevbench_public.py` script from this repository. It checks out the official JevBench v1.4.1 source at commit `24b9b5c1609a7a9e8fa14f49e5985a836c9dc842`, verifies a clean checkout, and uses its public tasks and official per-task scorer.
4
+
5
+ Install Python, Git, and a PyTorch build for your hardware. The requirements file records the versions used for the reported run. On Windows PowerShell:
6
+
7
+ ```powershell
8
+ py -m pip install -r benchmarks/requirements.txt
9
+ py benchmarks/jevbench_public.py --device cpu --threads 4 --batch-size 4 --output pivot_cpu_results.json
10
+ ```
11
+
12
+ On a CUDA machine use `--device cuda --batch-size 32`. If you only need timings, add `--mode speed`. For public task accuracy only, add `--mode accuracy`. The model is pinned to commit `14bf8c26bf344ebdf88e22a4b6152dc5f75f3578` unless you deliberately provide another full SHA via `--revision`. Allow time and disk space for the approximately 1.43 GB checkpoint.
13
+
14
+ The script uses the same candidate wording, input rendering, token limits, FP32 scoring, warmup count, and timing protocol as the published run. Timings still vary with hardware, software and background load. Only public JevBench tasks are available here; `official_jevbench_score` is always null. The full official score requires organizer-held sealed and judge tasks and cost measurement. See [the benchmark protocol](../docs/BENCHMARK.md).
benchmarks/jevbench_public.py ADDED
@@ -0,0 +1,296 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ """Standalone Pivot evaluation on the pinned official JevBench public tasks.
2
+
3
+ The sealed benchmark and cost component are unavailable here. This script only
4
+ reports public accuracy and local warm FP32 speed; it never fabricates a score.
5
+ """
6
+
7
+ from __future__ import annotations
8
+
9
+ import argparse
10
+ import json
11
+ import math
12
+ import os
13
+ import platform
14
+ import statistics
15
+ import subprocess
16
+ import sys
17
+ import time
18
+ from pathlib import Path
19
+
20
+ MODEL_ID = "Q1z/Pivot"
21
+ MODEL_REVISION = "14bf8c26bf344ebdf88e22a4b6152dc5f75f3578"
22
+ JEVBENCH_REVISION = "24b9b5c1609a7a9e8fa14f49e5985a836c9dc842" # v1.4.1
23
+ CONTRACT = {
24
+ "context_arm": "context_header",
25
+ "option_arm": "original",
26
+ "context_tokens": 512,
27
+ "option_tokens": 128,
28
+ "truncation": "right",
29
+ }
30
+
31
+
32
+ def _run(*args: str) -> str:
33
+ return subprocess.check_output(args, text=True).strip()
34
+
35
+
36
+ def checkout_jevbench(cache: Path) -> Path:
37
+ repo = cache / "jevbench"
38
+ created = not repo.exists()
39
+ if created:
40
+ repo.parent.mkdir(parents=True, exist_ok=True)
41
+ subprocess.run(
42
+ ["git", "clone", "--filter=blob:none", "--no-checkout",
43
+ "https://github.com/fstandhartinger/jevbench.git", str(repo)],
44
+ check=True,
45
+ )
46
+ if not (repo / ".git").exists():
47
+ raise RuntimeError(f"Expected a clean git checkout at {repo}")
48
+ if not created and _run("git", "-C", str(repo), "status", "--porcelain", "--untracked-files=all"):
49
+ raise RuntimeError("JevBench checkout has local changes; choose a clean cache")
50
+ if _run("git", "-C", str(repo), "rev-parse", "HEAD") != JEVBENCH_REVISION:
51
+ subprocess.run(["git", "-C", str(repo), "fetch", "--depth", "1", "origin", JEVBENCH_REVISION], check=True)
52
+ subprocess.run(["git", "-C", str(repo), "checkout", "--detach", JEVBENCH_REVISION], check=True)
53
+ required = [repo / "jevbench" / "tasks.py"] + [
54
+ repo / "datasets" / "public" / f"{tier}.jsonl"
55
+ for tier in ("original", "easy", "hard")
56
+ ]
57
+ if (_run("git", "-C", str(repo), "rev-parse", "HEAD") != JEVBENCH_REVISION
58
+ or _run("git", "-C", str(repo), "status", "--porcelain", "--untracked-files=all")
59
+ or not all(path.is_file() for path in required)):
60
+ raise RuntimeError("Official JevBench revision or public files differ")
61
+ for name in ("jevbench", "jevbench.tasks", "jevbench.scoring", "jevbench.metrics"):
62
+ module = sys.modules.get(name)
63
+ if module is not None and Path(getattr(module, "__file__", "") or "").resolve().is_relative_to(repo.resolve()) is False:
64
+ raise RuntimeError(f"{name} was imported from a different checkout")
65
+ sys.path.insert(0, str(repo))
66
+ return repo
67
+
68
+
69
+ def _clean(value: object) -> str:
70
+ return " ".join(str(value).split())
71
+
72
+
73
+ def _humanize(label: object) -> str:
74
+ return _clean(str(label).replace("_", " ").replace("-", " "))
75
+
76
+
77
+ def _render_state(value: object) -> str:
78
+ if isinstance(value, str):
79
+ return value.strip()
80
+ if value is None:
81
+ return "null"
82
+ if isinstance(value, (bool, int, float)):
83
+ return str(value)
84
+ if isinstance(value, list):
85
+ return "\n".join(f"- {_render_state(item)}" for item in value)
86
+ if isinstance(value, dict):
87
+ parts = []
88
+ for key, child in value.items():
89
+ rendered = _render_state(child)
90
+ if rendered:
91
+ parts.append(f"{_humanize(key).title()}: {rendered}")
92
+ return "\n\n".join(parts)
93
+ return json.dumps(value, ensure_ascii=False, sort_keys=True)
94
+
95
+
96
+ def prepare_rows(tasks: list) -> tuple[list[dict], list]:
97
+ """Same rendering and exclusions as the evaluated Pivot_test public run."""
98
+ rows, kept = [], []
99
+ for task in tasks:
100
+ if task.expected is None or task.provenance.get("exclude_reason"):
101
+ continue
102
+ labels = [str(item) for item in task.labels]
103
+ gold = str(task.expected)
104
+ if gold not in labels:
105
+ continue
106
+ criteria = task.question.get("criteria")
107
+ kind = str(task.question["type"])
108
+ options = []
109
+ for label in labels:
110
+ description = None
111
+ if isinstance(criteria, dict):
112
+ key = {"no": "false", "yes": "true"}.get(label.strip().lower(), label)
113
+ description = criteria.get(key)
114
+ if description is None:
115
+ description = criteria.get(label)
116
+ elif isinstance(criteria, list) and kind == "score":
117
+ try:
118
+ description = criteria[int(label)]
119
+ except (ValueError, IndexError, TypeError):
120
+ pass
121
+ text = _clean(description) if description is not None else _humanize(label)
122
+ if not text:
123
+ raise ValueError(f"Empty option for {label!r}")
124
+ options.append(text)
125
+ focus = str(task.question.get("instructions") or "").strip()
126
+ evidence = _render_state(task.state)
127
+ body = f"{focus}\n\n{evidence}".strip() if focus else evidence
128
+ rows.append({"context": "CONTEXT:\n" + body, "options": options})
129
+ kept.append(task)
130
+ return rows, kept
131
+
132
+
133
+ def score_rows(model, tokenizer, rows: list[dict], device: str, batch_size: int) -> list[list[float]]:
134
+ """Encode each context and option in FP32, then score the original set."""
135
+ import torch
136
+
137
+ output: list[list[float]] = []
138
+ previous = tokenizer.truncation_side
139
+ tokenizer.truncation_side = "right"
140
+ try:
141
+ with torch.inference_mode():
142
+ for start in range(0, len(rows), batch_size):
143
+ batch = rows[start:start + batch_size]
144
+ ctx = tokenizer([row["context"] for row in batch], padding=True,
145
+ truncation=True, max_length=512, return_tensors="pt")
146
+ opt = tokenizer([text for row in batch for text in row["options"]],
147
+ padding=True, truncation=True, max_length=128,
148
+ return_tensors="pt")
149
+ dev = torch.device(device)
150
+ h_ctx = model.network.encode(ctx["input_ids"].to(dev), ctx["attention_mask"].to(dev))
151
+ h_opt = model.network.encode(opt["input_ids"].to(dev), opt["attention_mask"].to(dev))
152
+ width = max(len(row["options"]) for row in batch)
153
+ pieces, masks, offset = [], [], 0
154
+ for row in batch:
155
+ count = len(row["options"])
156
+ part = h_opt[offset:offset + count]
157
+ offset += count
158
+ if count < width:
159
+ part = torch.cat([part, part.new_zeros(width - count, part.shape[-1])])
160
+ pieces.append(part)
161
+ mask = torch.zeros(width, dtype=torch.bool, device=dev)
162
+ mask[:count] = True
163
+ masks.append(mask)
164
+ result = model.network.score_preencoded(h_ctx, torch.stack(pieces), torch.stack(masks))
165
+ for idx, row in enumerate(batch):
166
+ probabilities = result.probs[idx, :len(row["options"])].float().cpu().tolist()
167
+ if (not all(math.isfinite(x) for x in probabilities)
168
+ or abs(sum(probabilities) - 1.0) > 1e-5):
169
+ raise ValueError(f"Invalid probabilities at row {start + idx}")
170
+ output.append(probabilities)
171
+ finally:
172
+ tokenizer.truncation_side = previous
173
+ return output
174
+
175
+
176
+ def speed(model, tokenizer, rows: list[dict], device: str, batch_size: int) -> dict:
177
+ """Mirror the original run: 5+2 warmups; 32 singles; 3x64 bulk."""
178
+ import torch
179
+
180
+ def timed(subset: list[dict], size: int) -> float:
181
+ if device == "cuda":
182
+ torch.cuda.synchronize()
183
+ start = time.perf_counter()
184
+ score_rows(model, tokenizer, subset, device, size)
185
+ if device == "cuda":
186
+ torch.cuda.synchronize()
187
+ return time.perf_counter() - start
188
+
189
+ samples = rows[:32]
190
+ for row in samples[:5]:
191
+ timed([row], 1)
192
+ latencies = [timed([row], 1) for row in samples]
193
+ bulk = rows[:64]
194
+ for _ in range(2):
195
+ timed(bulk, batch_size)
196
+ repeats = [timed(bulk, batch_size) for _ in range(3)]
197
+ return {
198
+ "measurement": "warm local FP32; includes tokenization, inference and scoring",
199
+ "hardware": torch.cuda.get_device_name(0) if device == "cuda" else platform.processor(),
200
+ "threads": torch.get_num_threads() if device == "cpu" else None,
201
+ "single_decision": {
202
+ "n": len(latencies), "batch_size": 1,
203
+ "p50_ms": 1000 * statistics.median(latencies),
204
+ "p95_ms": 1000 * sorted(latencies)[math.ceil(.95 * len(latencies)) - 1],
205
+ "mean_ms": 1000 * statistics.mean(latencies),
206
+ },
207
+ "throughput": {
208
+ "batch_size": batch_size, "decisions_per_repeat": len(bulk),
209
+ "repeats": 3, "repeat_seconds": repeats,
210
+ "decisions_per_second": len(bulk) / statistics.median(repeats),
211
+ },
212
+ }
213
+
214
+
215
+ def main(argv: list[str] | None = None) -> dict:
216
+ parser = argparse.ArgumentParser(description=__doc__)
217
+ parser.add_argument("--mode", choices=("speed", "accuracy", "all"), default="all")
218
+ parser.add_argument("--device", choices=("auto", "cpu", "cuda"), default="auto")
219
+ parser.add_argument("--batch-size", type=int, default=None)
220
+ parser.add_argument("--threads", type=int, default=0)
221
+ parser.add_argument("--revision", default=MODEL_REVISION)
222
+ parser.add_argument("--cache", type=Path, default=Path.home() / ".cache" / "pivot_jevbench")
223
+ parser.add_argument("--output", type=Path, default=Path("pivot_public_results.json"))
224
+ args = parser.parse_args(argv)
225
+ if args.batch_size is not None and args.batch_size < 1 or args.threads < 0:
226
+ parser.error("batch-size must be positive and threads nonnegative")
227
+ if len(args.revision) != 40 or any(ch not in "0123456789abcdef" for ch in args.revision):
228
+ parser.error("revision must be a full lowercase commit SHA")
229
+
230
+ import torch
231
+ from transformers import AutoModel, AutoTokenizer
232
+
233
+ if args.threads:
234
+ torch.set_num_threads(args.threads)
235
+ device = ("cuda" if torch.cuda.is_available() else "cpu") if args.device == "auto" else args.device
236
+ if device == "cuda" and not torch.cuda.is_available():
237
+ parser.error("CUDA is unavailable")
238
+ batch_size = args.batch_size or (32 if device == "cuda" else 4)
239
+ checkout = checkout_jevbench(args.cache)
240
+ from jevbench.tasks import load_jsonl
241
+ from jevbench.scoring import score_task
242
+ from jevbench.metrics import ece_top_label
243
+
244
+ torch.backends.cuda.matmul.allow_tf32 = False
245
+ torch.backends.cudnn.allow_tf32 = False
246
+ torch.manual_seed(42)
247
+ print("Loading checkpoint and tokenizer...", flush=True)
248
+ model = AutoModel.from_pretrained(MODEL_ID, revision=args.revision,
249
+ trust_remote_code=True, dtype=torch.float32)
250
+ model.to(device=torch.device(device), dtype=torch.float32).eval()
251
+ tokenizer = AutoTokenizer.from_pretrained(MODEL_ID, revision=args.revision)
252
+ root = checkout / "datasets" / "public"
253
+ results = {
254
+ "model": MODEL_ID, "model_revision": args.revision,
255
+ "benchmark_release": "v1.4.1", "benchmark_commit": JEVBENCH_REVISION,
256
+ "scope": "public tasks only", "official_jevbench_score": None,
257
+ "contract": CONTRACT, "device": device,
258
+ }
259
+ if args.mode in ("accuracy", "all"):
260
+ cohorts = {}
261
+ for name in ("original", "easy", "hard"):
262
+ tasks = load_jsonl(str(root / f"{name}.jsonl"))
263
+ rows, kept = prepare_rows(tasks)
264
+ scored = score_rows(model, tokenizer, rows, device, batch_size)
265
+ official = [score_task(dict(zip(map(str, task.labels), probs)), task)
266
+ for task, probs in zip(kept, scored)]
267
+ valid = [item for item in official if item["valid"]]
268
+ correct = sum(item["correct"] is True for item in official)
269
+ cohorts[name] = {
270
+ "n": len(official), "correct": correct,
271
+ "invalid": len(official) - len(valid),
272
+ "accuracy": correct / len(official) if official else None,
273
+ "ece_10": ece_top_label([(max(item["probs"].values()), item["correct"])
274
+ for item in valid])["ece"] if valid else None,
275
+ }
276
+ print(f"{name}: {correct}/{len(official)}", flush=True)
277
+ total = sum(item["n"] for item in cohorts.values())
278
+ results.update(cohorts=cohorts, public_n=total,
279
+ public_correct=sum(item["correct"] for item in cohorts.values()),
280
+ public_accuracy=sum(item["correct"] for item in cohorts.values()) / total)
281
+ if args.mode in ("speed", "all"):
282
+ tasks = load_jsonl(str(root / "original.jsonl"))
283
+ rows, _ = prepare_rows(tasks)
284
+ results["speed"] = speed(model, tokenizer, rows, device, batch_size)
285
+ print(json.dumps({"p50_ms": results["speed"]["single_decision"]["p50_ms"],
286
+ "p95_ms": results["speed"]["single_decision"]["p95_ms"],
287
+ "decisions_per_second": results["speed"]["throughput"]["decisions_per_second"]},
288
+ indent=2), flush=True)
289
+ args.output.parent.mkdir(parents=True, exist_ok=True)
290
+ args.output.write_text(json.dumps(results, indent=2, allow_nan=False) + "\n", encoding="utf-8")
291
+ print(f"Saved {args.output.resolve()}", flush=True)
292
+ return results
293
+
294
+
295
+ if __name__ == "__main__":
296
+ main()
benchmarks/requirements.txt ADDED
@@ -0,0 +1,5 @@
 
 
 
 
 
 
1
+ torch==2.8.0
2
+ transformers==5.17.0
3
+ safetensors==0.8.0
4
+ numpy==1.26.4
5
+ huggingface_hub>=0.26.0
config.json CHANGED
@@ -73,7 +73,7 @@
73
  },
74
  "serving": {
75
  "max_context_tokens": 512,
76
- "max_option_tokens": 64
77
  }
78
  },
79
  "encoder_config": {
@@ -162,4 +162,4 @@
162
  "method": "CE warmup -> live multiclass Brier + PCGrad",
163
  "probability_contract": "softmax(logits.float())"
164
  }
165
- }
 
73
  },
74
  "serving": {
75
  "max_context_tokens": 512,
76
+ "max_option_tokens": 128
77
  }
78
  },
79
  "encoder_config": {
 
162
  "method": "CE warmup -> live multiclass Brier + PCGrad",
163
  "probability_contract": "softmax(logits.float())"
164
  }
165
+ }
cpu-speed/README.md ADDED
@@ -0,0 +1,14 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # Pivot CPU speed
2
+
3
+ This folder is a CPU-only entry point to the standalone public JevBench runner. It downloads the exact measured Pivot checkpoint and the pinned official public JevBench source; no private account or token is needed for the public files.
4
+
5
+ From the model repository root on Windows PowerShell:
6
+
7
+ ```powershell
8
+ py -m pip install -r benchmarks/requirements.txt
9
+ py cpu-speed/benchmark.py --threads 4 --batch-size 4 --output pivot_cpu_results.json
10
+ ```
11
+
12
+ The run measures warm FP32 latency for 32 individual decisions and throughput over three sets of 64 decisions. It includes tokenization, inference, and scoring. It reports p50, p95, mean, decisions per second, thread count, hardware, pinned revisions, and the contract. To evaluate public accuracy too, run `py benchmarks/jevbench_public.py --device cpu --threads 4 --mode all`.
13
+
14
+ The published 4-thread Xeon result is p50 **797.6 ms**, p95 **1,089.0 ms**, and **3.77 decisions/s** at batch 4. Your CPU result will differ. GPU throughput used batch 32, so these throughput values are not a matched batch comparison. See [archived measurements](../evaluation/2026-09-24/).
cpu-speed/benchmark.py ADDED
@@ -0,0 +1,9 @@
 
 
 
 
 
 
 
 
 
 
1
+ """CPU-only wrapper for the pinned public JevBench speed protocol."""
2
+ from pathlib import Path
3
+ import sys
4
+
5
+ sys.path.insert(0, str(Path(__file__).resolve().parents[1] / "benchmarks"))
6
+ from jevbench_public import main
7
+
8
+ if __name__ == "__main__":
9
+ main([*sys.argv[1:], "--device", "cpu", "--mode", "speed"])
docs/BENCHMARK.md ADDED
@@ -0,0 +1,26 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # JevBench public evaluation
2
+
3
+ ## Exact identity
4
+
5
+ | Item | Pinned value |
6
+ |---|---|
7
+ | Model | `Q1z/Pivot` |
8
+ | Evaluated model commit | `14bf8c26bf344ebdf88e22a4b6152dc5f75f3578` |
9
+ | Official JevBench release | v1.4.1 |
10
+ | JevBench commit | `24b9b5c1609a7a9e8fa14f49e5985a836c9dc842` |
11
+ | Tasks | Public original, easy, hard; 72 + 48 + 111 |
12
+ | Precision | FP32 |
13
+ | Rendering | `CONTEXT:` prefix, rubric text where present, official label order |
14
+ | Limits | 512 context and 128 option tokens, right truncation |
15
+
16
+ Pivot encodes every context and candidate, scores the candidate set, then passes probabilities indexed by each official task's labels to JevBench's `score_task`. The candidate text is the task rubric description when available, otherwise a humanized label. The published public accuracy is 107 / 231 = **46.32%**. Use [the standalone script](../benchmarks/jevbench_public.py) to repeat the calculation. It checks out the pinned official source, refuses a modified benchmark checkout, and writes results including both revisions and the frozen input contract.
17
+
18
+ ## Meaning of the score
19
+
20
+ The **official JevBench v1.4 composite score is unavailable**. Public tasks do not include the organizer-held sealed and judge components or an official cost input. The 46.32% figure is a measured public accuracy, not a held-out score, official composite, rank, or cost-adjusted value. No such missing numbers are estimated in the result files.
21
+
22
+ The scripts also measure **local** warm single-decision latency and batched throughput on the public original tasks. These timings include tokenization, model inference and scoring, and depend on CPU/GPU, batch size, software and load. They are not the benchmark's adjusted speed/cost metric.
23
+
24
+ The runtime serving option limit in this update is now 128, matching the frozen benchmark. This is a default inference setting change, while checkpoint weights and the original evaluated commit remain intact. The original measurements cannot automatically predict the speed of every serving input.
25
+
26
+ See [results and charts](../evaluation/2026-09-24/README.md) and [reproduction instructions](../benchmarks/README.md).
docs/INFERENCE.md ADDED
@@ -0,0 +1,72 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # Using Pivot
2
+
3
+ Pivot evaluates a **supplied set of options** for each context. It is a bidirectional encoder with a set scorer, so its answer is an index into your options, not generated text. Use 2–16 distinct candidate meanings for the structured native interface. A single plain `choose()` call accepts two or more options; large candidate lists cost more memory and time.
4
+
5
+ ## Load once
6
+
7
+ ```python
8
+ import torch
9
+ from transformers import AutoModel, AutoTokenizer
10
+
11
+ repo = "Q1z/Pivot"
12
+ tokenizer = AutoTokenizer.from_pretrained(repo, trust_remote_code=True)
13
+ model = AutoModel.from_pretrained(repo, trust_remote_code=True, dtype=torch.float32).eval()
14
+ ```
15
+
16
+ This repository ships custom Transformers code. Inspect it before loading with `trust_remote_code=True` in a production environment. A CPU-only PyTorch build works. For CUDA, call `model.to("cuda")`. Keep the tokenizer and model loaded across requests.
17
+
18
+ ## Plain choice
19
+
20
+ ```python
21
+ context = "CONTEXT:\nA user requests a refund for an incorrect charge."
22
+ options = ["route to billing", "route to technical support", "route to sales"]
23
+ result = model.choose(tokenizer, context, options)
24
+ assert result["choice"] == options[result["index"]]
25
+ assert len(result["probs"]) == len(options)
26
+ ```
27
+
28
+ The probability vector follows the original option order and sums to approximately one. Changing the option wording or adding an option changes the set being scored. The public benchmark uses the `CONTEXT:` prefix, a 512-token context limit and a 128-token option limit.
29
+
30
+ ## Native structured choice
31
+
32
+ `decide_native()` accepts a nonempty context and 2–16 objects with unique `id` and distinct `text` values. Optional `kind: "abstain"` is allowed on at most one candidate. Machine IDs stay outside the option encoder.
33
+
34
+ ```python
35
+ result = model.decide_native(
36
+ tokenizer,
37
+ "A customer disputes an invoice.",
38
+ [
39
+ {"id": "billing", "text": "route to billing"},
40
+ {"id": "technical", "text": "route to technical support"},
41
+ {"id": "unknown", "text": "none of the routes fits", "kind": "abstain"},
42
+ ],
43
+ )
44
+ print(result["selected"]["id"], result["prob_vector"])
45
+ ```
46
+
47
+ The abstain option competes with the other supplied options; it is not an independently calibrated refusal threshold.
48
+
49
+ ## Several typed decisions
50
+
51
+ ```python
52
+ result = model.decide(tokenizer, "Customer invoice disputed", [
53
+ {"id": "route", "primitive": "choice", "options": ["billing", "tech", "sales"]},
54
+ {"id": "approve", "primitive": "noul", "options": ["true", "false"]},
55
+ {"id": "severity", "primitive": "score", "options": ["0", "1", "2", "3"]},
56
+ ])
57
+ ```
58
+
59
+ The typed method scores each question in turn. The examples under [`serving/`](../serving/) show request and response shapes.
60
+
61
+ ## Cache repeated candidates
62
+
63
+ For repeated contexts against an unchanged option set:
64
+
65
+ ```python
66
+ options = ["billing support", "technical support", "sales"]
67
+ candidate_vectors = model.encode_candidates(tokenizer, options)
68
+ context_vector = model.encode_context(tokenizer, "Invoice is incorrect")
69
+ result = model.choose_cached(context_vector, candidate_vectors, options)
70
+ ```
71
+
72
+ Cache embeddings only with the same model, tokenizer, precision and option text. This path can avoid repeated option encoding. Published speed measurements instead use the uncached end-to-end path. [Runnable examples](../examples/cached_candidates.py).
docs/PERFORMANCE.md ADDED
@@ -0,0 +1,32 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # Pivot performance
2
+
3
+ All figures here refer to checkpoint `14bf8c26bf344ebdf88e22a4b6152dc5f75f3578`, public JevBench v1.4.1 commit `24b9b5c1609a7a9e8fa14f49e5985a836c9dc842`, FP32 and the same frozen 512/128-token input contract.
4
+
5
+ ## Accuracy on public tasks
6
+
7
+ | Tier | Correct | Tasks | Accuracy | Top-label ECE, 10 bins |
8
+ |---|---:|---:|---:|---:|
9
+ | Original | 27 | 72 | 37.50% | 0.5247 |
10
+ | Easy | 39 | 48 | 81.25% | 0.1139 |
11
+ | Hard | 41 | 111 | 36.94% | 0.3792 |
12
+ | **Total** | **107** | **231** | **46.32%** | — |
13
+
14
+ ![Public-tier accuracy](../evaluation/2026-09-24/accuracy.png)
15
+
16
+ The official JevBench composite score is **unavailable** because the sealed and judge tasks and official cost input were not measured.
17
+
18
+ ## Local speed
19
+
20
+ | Warm local FP32 measure | H200 GPU | Xeon CPU, 4 threads |
21
+ |---|---:|---:|
22
+ | Single decision p50, 32 measured | 15.7668 ms | 797.5558 ms |
23
+ | Single decision p95, 32 measured | 19.8708 ms | 1089.0266 ms |
24
+ | Single decision mean | 16.1614 ms | 770.7159 ms |
25
+ | Batch size for throughput | 32 | 4 |
26
+ | Throughput, median of 3 × 64 decisions | 545.2833 decisions/s | 3.7708 decisions/s |
27
+
28
+ ![Warm local latency and throughput](../evaluation/2026-09-24/latency_throughput.png)
29
+
30
+ The CPU p50 single-decision time is **50.6×** the H200 p50 for these two machines. CPU and GPU batch throughput used different batch sizes and should not be read as a same-batch comparison. The timings cover tokenizer + inference + scoring with 5 warmup singles and 2 warmup bulk passes. Reproduce them on your own hardware using the [CPU script](../cpu-speed/README.md) or [full public runner](../benchmarks/README.md).
31
+
32
+ Source: [full structured summary](../evaluation/2026-09-24/performance.json), [original public benchmark result](../evaluation/2026-09-24/jevbench_public.json), and [original CPU measurement](../evaluation/2026-09-24/cpu_speed.json).
docs/UPDATE_NOTES.md ADDED
@@ -0,0 +1,9 @@
 
 
 
 
 
 
 
 
 
 
1
+ # Model repository update — 2026-09-24
2
+
3
+ This upload replaces the short model card with a full model overview, measured public accuracy, CPU/GPU timing, usage methods and reproducibility links. It adds inference examples, a serving guide, a standalone public JevBench v1.4.1 runner, a CPU-specific entry point, a CPU notebook, two focused charts, a combined chart, and the exact results in JSON.
4
+
5
+ The `config.json` serving option limit changes from **64 to 128** tokens to align the default API with the measured evaluation input limit. The checkpoint and runtime source files are not modified. `manifest.json` is regenerated for every changed or new repository file; existing weight and runtime hashes are preserved.
6
+
7
+ The reported public accuracy is **107 / 231 = 46.32%** at the pinned model revision `14bf8c26bf344ebdf88e22a4b6152dc5f75f3578`. The official JevBench composite score is unavailable because the sealed/judge portion and cost input are not included. Local CPU and GPU speeds are measured at distinct batch sizes and are identified as such.
8
+
9
+ Upload the contents of this package to the **root** of `Q1z/Pivot`, replacing the names it contains. No delete operation is required. Model weight files, tokenizer files, and pre-existing serving request/response examples stay in the Hub repository.
evaluation/2026-09-24/README.md ADDED
@@ -0,0 +1,14 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # Evaluated checkpoint and measured results
2
+
3
+ Checkpoint: `14bf8c26bf344ebdf88e22a4b6152dc5f75f3578`. Official JevBench public tasks: v1.4.1, commit `24b9b5c1609a7a9e8fa14f49e5985a836c9dc842`.
4
+
5
+ - [`performance.json`](performance.json): compact accuracy, speed, hardware, revisions, and limitations.
6
+ - [`jevbench_public.json`](jevbench_public.json): original GPU public JevBench result with official per-task scoring.
7
+ - [`cpu_speed.json`](cpu_speed.json): original four-thread CPU timing result.
8
+ - [`performance_overview.png`](performance_overview.png): combined chart.
9
+ - [`accuracy.png`](accuracy.png): public task accuracy.
10
+ - [`latency_throughput.png`](latency_throughput.png): GPU and CPU speed.
11
+
12
+ Public accuracy: **107 / 231 = 46.32%**. Warm single-decision p50: **15.8 ms** on H200, **797.6 ms** on Xeon CPU. GPU throughput uses batch 32, CPU throughput batch 4. The official sealed JevBench composite score and cost metric were not measured.
13
+
14
+ This update changes the default serving option limit from 64 to 128, while the checkpoint and archived measurements remain anchored to the prior exact commit. See [the full protocol](../../docs/BENCHMARK.md).
evaluation/2026-09-24/accuracy.png ADDED
evaluation/2026-09-24/cpu_speed.json ADDED
@@ -0,0 +1,39 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ {
2
+ "scope": "public original tasks; warm local inference including tokenization and scoring",
3
+ "device": "cpu",
4
+ "hardware": "x86_64",
5
+ "dtype": "float32",
6
+ "single_decision": {
7
+ "batch_size": 1,
8
+ "n": 32,
9
+ "p50_ms": 797.5558204999516,
10
+ "p95_ms": 1089.0265890000137,
11
+ "mean_ms": 770.7159108749977
12
+ },
13
+ "throughput": {
14
+ "batch_size": 4,
15
+ "decisions_per_repeat": 64,
16
+ "repeats": 3,
17
+ "decisions_per_second": 3.7708397043400965,
18
+ "repeat_seconds": [
19
+ 16.58172093600001,
20
+ 17.219981521999898,
21
+ 16.97234701499997
22
+ ]
23
+ },
24
+ "note": "Local warm FP32 timing; not official adjusted JevBench Speed. No cost estimate without a specified hardware price.",
25
+ "cpu_model": "Intel(R) Xeon(R) CPU @ 2.60GHz",
26
+ "cpu_threads": 4,
27
+ "logical_cpu_count": 4,
28
+ "model_revision": "14bf8c26bf344ebdf88e22a4b6152dc5f75f3578",
29
+ "benchmark_commit": "24b9b5c1609a7a9e8fa14f49e5985a836c9dc842",
30
+ "benchmark_release": "v1.4.1",
31
+ "contract": {
32
+ "context_arm": "context_header",
33
+ "option_arm": "original",
34
+ "context_tokens": 512,
35
+ "option_tokens": 128,
36
+ "truncation": "right"
37
+ },
38
+ "test_scope": "speed only; no accuracy or official composite score computed"
39
+ }
evaluation/2026-09-24/jevbench_public.json ADDED
@@ -0,0 +1,585 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ {
2
+ "benchmark_commit": "24b9b5c1609a7a9e8fa14f49e5985a836c9dc842",
3
+ "benchmark_release": "v1.4.1",
4
+ "scope": "public_subset_only",
5
+ "official_held_out": null,
6
+ "official_jevbench_score": null,
7
+ "public_accuracy": 0.46320346320346323,
8
+ "public_n": 231,
9
+ "speed": {
10
+ "scope": "public original tasks; warm local inference including tokenization and scoring",
11
+ "device": "cuda",
12
+ "hardware": "NVIDIA H200",
13
+ "dtype": "float32",
14
+ "single_decision": {
15
+ "batch_size": 1,
16
+ "n": 32,
17
+ "p50_ms": 15.766771499897914,
18
+ "p95_ms": 19.87079100013034,
19
+ "mean_ms": 16.16144687499599
20
+ },
21
+ "throughput": {
22
+ "batch_size": 32,
23
+ "decisions_per_repeat": 64,
24
+ "repeats": 3,
25
+ "decisions_per_second": 545.2832753857399,
26
+ "repeat_seconds": [
27
+ 0.12318335300005856,
28
+ 0.1173701870000059,
29
+ 0.11606121599993457
30
+ ]
31
+ },
32
+ "note": "Local warm FP32 timing; not official adjusted JevBench Speed. No cost estimate without a specified hardware price."
33
+ },
34
+ "contract": {
35
+ "context_arm": "context_header",
36
+ "option_arm": "original",
37
+ "context_tokens": 512,
38
+ "option_tokens": 128,
39
+ "truncation": "right",
40
+ "selection_arm": "option_len:128",
41
+ "dev_sample_macro_accuracy": 0.7741666666666666,
42
+ "dev_sample_macro_nll": 0.9652628596545907,
43
+ "verified_as_scored": true,
44
+ "run_id": "f9f472314d223516818433b6f731ed3a05c999bd0be412134ad671d5ddc7808c"
45
+ },
46
+ "cohorts": {
47
+ "original": {
48
+ "n_tasks_file": 72,
49
+ "n_scored": 72,
50
+ "official_public": {
51
+ "n": 72,
52
+ "correct": 27,
53
+ "invalid": 0,
54
+ "accuracy": 0.375,
55
+ "ece_10": 0.5247104664643605
56
+ },
57
+ "metrics": {
58
+ "macro_accuracy": 0.375,
59
+ "macro_nll": 5.007958399361118,
60
+ "micro": {
61
+ "n": 72,
62
+ "accuracy": 0.375,
63
+ "brier_mean": 0.30594551421877425,
64
+ "brier_sum": 1.1185596241362858,
65
+ "ece_15": 0.5325825545522902,
66
+ "nll": 5.007958399361118,
67
+ "entropy": 0.23031783027727182,
68
+ "margin": 4.621087094148,
69
+ "predicted_positions": {
70
+ "0": 23,
71
+ "1": 19,
72
+ "3": 14,
73
+ "4": 2,
74
+ "2": 14
75
+ },
76
+ "gold_positions": {
77
+ "0": 24,
78
+ "1": 20,
79
+ "2": 12,
80
+ "3": 10,
81
+ "4": 4,
82
+ "5": 2
83
+ }
84
+ },
85
+ "by_family": {
86
+ "adequacy": {
87
+ "n": 12,
88
+ "accuracy": 0.3333333333333333,
89
+ "brier_mean": 0.6032912907957645,
90
+ "brier_sum": 1.206582581591529,
91
+ "ece_15": 0.674643059571584,
92
+ "nll": 2.811368828710537,
93
+ "entropy": 0.35683153857926314,
94
+ "margin": 2.7182071606318154,
95
+ "predicted_positions": {
96
+ "1": 8,
97
+ "0": 4
98
+ },
99
+ "gold_positions": {
100
+ "1": 6,
101
+ "0": 6
102
+ }
103
+ },
104
+ "extraction": {
105
+ "n": 12,
106
+ "accuracy": 0.16666666666666666,
107
+ "brier_mean": 0.33462631759767253,
108
+ "brier_sum": 1.3385052703906901,
109
+ "ece_15": 0.7197072158257167,
110
+ "nll": 5.611249398437437,
111
+ "entropy": 0.25773084664473744,
112
+ "margin": 3.4950687090555825,
113
+ "predicted_positions": {
114
+ "0": 9,
115
+ "1": 3
116
+ },
117
+ "gold_positions": {
118
+ "1": 2,
119
+ "3": 4,
120
+ "0": 4,
121
+ "2": 2
122
+ }
123
+ },
124
+ "intent": {
125
+ "n": 12,
126
+ "accuracy": 0.5,
127
+ "brier_mean": 0.193049684303942,
128
+ "brier_sum": 0.9652484215197101,
129
+ "ece_15": 0.49123724798361457,
130
+ "nll": 6.693062391390858,
131
+ "entropy": 0.16276790362346089,
132
+ "margin": 5.134465376536052,
133
+ "predicted_positions": {
134
+ "1": 4,
135
+ "3": 4,
136
+ "4": 2,
137
+ "0": 2
138
+ },
139
+ "gold_positions": {
140
+ "1": 2,
141
+ "2": 4,
142
+ "3": 2,
143
+ "0": 2,
144
+ "4": 2
145
+ }
146
+ },
147
+ "ordinal": {
148
+ "n": 12,
149
+ "accuracy": 0.25,
150
+ "brier_mean": 0.3469416665898746,
151
+ "brier_sum": 1.3877666663594983,
152
+ "ece_15": 0.7055613746245701,
153
+ "nll": 5.438651597684344,
154
+ "entropy": 0.1746427791074148,
155
+ "margin": 5.965866764386495,
156
+ "predicted_positions": {
157
+ "3": 10,
158
+ "2": 2
159
+ },
160
+ "gold_positions": {
161
+ "0": 4,
162
+ "1": 2,
163
+ "2": 4,
164
+ "3": 2
165
+ }
166
+ },
167
+ "policy": {
168
+ "n": 12,
169
+ "accuracy": 0.8333333333333334,
170
+ "brier_mean": 0.08333248779901603,
171
+ "brier_sum": 0.16666497559803206,
172
+ "ece_15": 0.16530502339204153,
173
+ "nll": 0.2814556436290131,
174
+ "entropy": 0.40680371489783257,
175
+ "margin": 1.9209110339482625,
176
+ "predicted_positions": {
177
+ "0": 8,
178
+ "1": 4
179
+ },
180
+ "gold_positions": {
181
+ "0": 6,
182
+ "1": 6
183
+ }
184
+ },
185
+ "routing": {
186
+ "n": 12,
187
+ "accuracy": 0.16666666666666666,
188
+ "brier_mean": 0.27443163822637595,
189
+ "brier_sum": 1.6465898293582557,
190
+ "ece_15": 0.8280483782291412,
191
+ "nll": 9.211962536314518,
192
+ "entropy": 0.023130198810922015,
193
+ "margin": 8.492003520329794,
194
+ "predicted_positions": {
195
+ "2": 12
196
+ },
197
+ "gold_positions": {
198
+ "0": 2,
199
+ "1": 2,
200
+ "2": 2,
201
+ "3": 2,
202
+ "4": 2,
203
+ "5": 2
204
+ }
205
+ }
206
+ }
207
+ }
208
+ },
209
+ "easy": {
210
+ "n_tasks_file": 48,
211
+ "n_scored": 48,
212
+ "official_public": {
213
+ "n": 48,
214
+ "correct": 39,
215
+ "invalid": 0,
216
+ "accuracy": 0.8125,
217
+ "ece_10": 0.1139311231672764
218
+ },
219
+ "metrics": {
220
+ "macro_accuracy": 0.8125,
221
+ "macro_nll": 1.0037392714069566,
222
+ "micro": {
223
+ "n": 48,
224
+ "accuracy": 0.8125,
225
+ "brier_mean": 0.08969436292922732,
226
+ "brier_sum": 0.3277746810853164,
227
+ "ece_15": 0.17484351620078087,
228
+ "nll": 1.0037392714069566,
229
+ "entropy": 0.27674493139652984,
230
+ "margin": 5.322023505965869,
231
+ "predicted_positions": {
232
+ "0": 13,
233
+ "1": 16,
234
+ "2": 9,
235
+ "3": 7,
236
+ "4": 3
237
+ },
238
+ "gold_positions": {
239
+ "0": 15,
240
+ "1": 12,
241
+ "2": 8,
242
+ "3": 8,
243
+ "4": 5
244
+ }
245
+ },
246
+ "by_family": {
247
+ "extraction": {
248
+ "n": 12,
249
+ "accuracy": 0.5,
250
+ "brier_mean": 0.20218808597619678,
251
+ "brier_sum": 0.8518348596262159,
252
+ "ece_15": 0.4715841561555862,
253
+ "nll": 3.1491165426466026,
254
+ "entropy": 0.32298662421478147,
255
+ "margin": 3.7243812878926597,
256
+ "predicted_positions": {
257
+ "1": 3,
258
+ "2": 3,
259
+ "3": 3,
260
+ "0": 3
261
+ },
262
+ "gold_positions": {
263
+ "1": 2,
264
+ "3": 4,
265
+ "2": 2,
266
+ "0": 3,
267
+ "4": 1
268
+ }
269
+ },
270
+ "fact": {
271
+ "n": 12,
272
+ "accuracy": 0.8333333333333334,
273
+ "brier_mean": 0.10789432132950423,
274
+ "brier_sum": 0.21578864265900846,
275
+ "ece_15": 0.21092924475669866,
276
+ "nll": 0.3680396559776326,
277
+ "entropy": 0.5416488102436233,
278
+ "margin": 1.0842649737993877,
279
+ "predicted_positions": {
280
+ "1": 8,
281
+ "0": 4
282
+ },
283
+ "gold_positions": {
284
+ "1": 6,
285
+ "0": 6
286
+ }
287
+ },
288
+ "intent": {
289
+ "n": 12,
290
+ "accuracy": 0.9166666666666666,
291
+ "brier_mean": 0.04868382763629129,
292
+ "brier_sum": 0.24341913818145647,
293
+ "ece_15": 0.18598496417204538,
294
+ "nll": 0.49580653492545496,
295
+ "entropy": 0.23149866629527152,
296
+ "margin": 5.446817874908447,
297
+ "predicted_positions": {
298
+ "0": 4,
299
+ "1": 3,
300
+ "2": 2,
301
+ "3": 2,
302
+ "4": 1
303
+ },
304
+ "gold_positions": {
305
+ "0": 4,
306
+ "1": 2,
307
+ "2": 2,
308
+ "3": 2,
309
+ "4": 2
310
+ }
311
+ },
312
+ "tool_selection": {
313
+ "n": 12,
314
+ "accuracy": 1.0,
315
+ "brier_mean": 1.1216774916947182e-05,
316
+ "brier_sum": 5.6083874584735916e-05,
317
+ "ece_15": 0.0019801706075668335,
318
+ "nll": 0.0019943520781363655,
319
+ "entropy": 0.010845624832442837,
320
+ "margin": 11.03262988726298,
321
+ "predicted_positions": {
322
+ "0": 2,
323
+ "1": 2,
324
+ "2": 4,
325
+ "3": 2,
326
+ "4": 2
327
+ },
328
+ "gold_positions": {
329
+ "0": 2,
330
+ "1": 2,
331
+ "2": 4,
332
+ "3": 2,
333
+ "4": 2
334
+ }
335
+ }
336
+ }
337
+ }
338
+ },
339
+ "hard": {
340
+ "n_tasks_file": 111,
341
+ "n_scored": 111,
342
+ "official_public": {
343
+ "n": 111,
344
+ "correct": 41,
345
+ "invalid": 0,
346
+ "accuracy": 0.36936936936936937,
347
+ "ece_10": 0.3791593909263611
348
+ },
349
+ "metrics": {
350
+ "macro_accuracy": 0.38833493046341344,
351
+ "macro_nll": 2.1149551345876114,
352
+ "micro": {
353
+ "n": 111,
354
+ "accuracy": 0.36936936936936937,
355
+ "brier_mean": 0.3142712859450615,
356
+ "brier_sum": 0.951039241082317,
357
+ "ece_15": 0.37663869814829787,
358
+ "nll": 2.2420954526317662,
359
+ "entropy": 0.6359462567449854,
360
+ "margin": 1.9191036170667357,
361
+ "predicted_positions": {
362
+ "4": 5,
363
+ "3": 11,
364
+ "1": 50,
365
+ "0": 34,
366
+ "2": 11
367
+ },
368
+ "gold_positions": {
369
+ "3": 12,
370
+ "1": 46,
371
+ "2": 20,
372
+ "0": 31,
373
+ "4": 2
374
+ }
375
+ },
376
+ "by_family": {
377
+ "adversarial": {
378
+ "n": 6,
379
+ "accuracy": 0.3333333333333333,
380
+ "brier_mean": 0.329005561541213,
381
+ "brier_sum": 1.0360720430251906,
382
+ "ece_15": 0.6766607860724131,
383
+ "nll": 2.9314609134083995,
384
+ "entropy": 0.524170048996028,
385
+ "margin": 1.9538231293360393,
386
+ "predicted_positions": {
387
+ "0": 3,
388
+ "1": 2,
389
+ "2": 1
390
+ },
391
+ "gold_positions": {
392
+ "1": 2,
393
+ "0": 3,
394
+ "2": 1
395
+ }
396
+ },
397
+ "ambiguous": {
398
+ "n": 7,
399
+ "accuracy": 0.2857142857142857,
400
+ "brier_mean": 0.35496685184985993,
401
+ "brier_sum": 1.1069028860902297,
402
+ "ece_15": 0.523924435888018,
403
+ "nll": 2.373063987899727,
404
+ "entropy": 0.601728218515645,
405
+ "margin": 1.9064587184361048,
406
+ "predicted_positions": {
407
+ "2": 2,
408
+ "1": 3,
409
+ "0": 2
410
+ },
411
+ "gold_positions": {
412
+ "1": 2,
413
+ "0": 4,
414
+ "2": 1
415
+ }
416
+ },
417
+ "judge_hard": {
418
+ "n": 17,
419
+ "accuracy": 0.47058823529411764,
420
+ "brier_mean": 0.4486379756320042,
421
+ "brier_sum": 0.8972759512640084,
422
+ "ece_15": 0.48566271978266107,
423
+ "nll": 2.037071307444988,
424
+ "entropy": 0.3662706503467713,
425
+ "margin": 2.5419286559609806,
426
+ "predicted_positions": {
427
+ "1": 12,
428
+ "0": 5
429
+ },
430
+ "gold_positions": {
431
+ "1": 7,
432
+ "0": 10
433
+ }
434
+ },
435
+ "long_policy": {
436
+ "n": 19,
437
+ "accuracy": 0.2631578947368421,
438
+ "brier_mean": 0.27236642425885704,
439
+ "brier_sum": 0.9291074031175274,
440
+ "ece_15": 0.5016953207944569,
441
+ "nll": 2.2953665892409245,
442
+ "entropy": 0.8144000351229689,
443
+ "margin": 1.7862926752943742,
444
+ "predicted_positions": {
445
+ "4": 2,
446
+ "3": 4,
447
+ "1": 8,
448
+ "0": 3,
449
+ "2": 2
450
+ },
451
+ "gold_positions": {
452
+ "3": 4,
453
+ "1": 9,
454
+ "2": 3,
455
+ "0": 2,
456
+ "4": 1
457
+ }
458
+ },
459
+ "multi_hop": {
460
+ "n": 18,
461
+ "accuracy": 0.3888888888888889,
462
+ "brier_mean": 0.2834510893430854,
463
+ "brier_sum": 1.0717193129324514,
464
+ "ece_15": 0.5134819232755237,
465
+ "nll": 2.763476913613433,
466
+ "entropy": 0.5936464927522234,
467
+ "margin": 2.359899971220228,
468
+ "predicted_positions": {
469
+ "3": 7,
470
+ "0": 7,
471
+ "2": 2,
472
+ "1": 2
473
+ },
474
+ "gold_positions": {
475
+ "2": 7,
476
+ "0": 2,
477
+ "1": 4,
478
+ "3": 5
479
+ }
480
+ },
481
+ "probability": {
482
+ "n": 10,
483
+ "accuracy": 0.1,
484
+ "brier_mean": 0.4951685533837183,
485
+ "brier_sum": 1.3519257679233312,
486
+ "ece_15": 0.7558035969734191,
487
+ "nll": 3.180345622394031,
488
+ "entropy": 0.4637652637685445,
489
+ "margin": 2.383845841884613,
490
+ "predicted_positions": {
491
+ "1": 4,
492
+ "0": 4,
493
+ "2": 2
494
+ },
495
+ "gold_positions": {
496
+ "1": 6,
497
+ "0": 1,
498
+ "2": 3
499
+ }
500
+ },
501
+ "routing_hard": {
502
+ "n": 5,
503
+ "accuracy": 0.6,
504
+ "brier_mean": 0.09874917209342585,
505
+ "brier_sum": 0.4937458604671292,
506
+ "ece_15": 0.3892843127250671,
507
+ "nll": 0.7523306657503555,
508
+ "entropy": 0.7205350702007266,
509
+ "margin": 1.654204750061035,
510
+ "predicted_positions": {
511
+ "1": 3,
512
+ "0": 2
513
+ },
514
+ "gold_positions": {
515
+ "1": 5
516
+ }
517
+ },
518
+ "temporal_numeric": {
519
+ "n": 15,
520
+ "accuracy": 0.4,
521
+ "brier_mean": 0.22509232850928135,
522
+ "brier_sum": 0.8210278367875746,
523
+ "ece_15": 0.266293736298879,
524
+ "nll": 1.984593750841293,
525
+ "entropy": 0.95446657543888,
526
+ "margin": 1.2332109769185384,
527
+ "predicted_positions": {
528
+ "1": 8,
529
+ "0": 3,
530
+ "2": 2,
531
+ "4": 2
532
+ },
533
+ "gold_positions": {
534
+ "1": 5,
535
+ "0": 4,
536
+ "2": 3,
537
+ "3": 3
538
+ }
539
+ },
540
+ "tradeoff": {
541
+ "n": 6,
542
+ "accuracy": 0.6666666666666666,
543
+ "brier_mean": 0.11440804834587807,
544
+ "brier_sum": 0.395298481023521,
545
+ "ece_15": 0.3396676282087962,
546
+ "nll": 0.6505642991908996,
547
+ "entropy": 0.5314685338007524,
548
+ "margin": 1.6512227058410645,
549
+ "predicted_positions": {
550
+ "0": 2,
551
+ "1": 3,
552
+ "4": 1
553
+ },
554
+ "gold_positions": {
555
+ "2": 1,
556
+ "1": 4,
557
+ "4": 1
558
+ }
559
+ },
560
+ "trap": {
561
+ "n": 8,
562
+ "accuracy": 0.375,
563
+ "brier_mean": 0.37663994260610417,
564
+ "brier_sum": 1.0909660520406792,
565
+ "ece_15": 0.685746856033802,
566
+ "nll": 2.1812772960920648,
567
+ "entropy": 0.6376175324074207,
568
+ "margin": 0.9758526459336281,
569
+ "predicted_positions": {
570
+ "1": 5,
571
+ "0": 3
572
+ },
573
+ "gold_positions": {
574
+ "0": 5,
575
+ "1": 2,
576
+ "2": 1
577
+ }
578
+ }
579
+ }
580
+ }
581
+ }
582
+ },
583
+ "note": "The public-only result is not an official JevBench v1.4 score. Sealed and judge tasks are unavailable; cost is not measured.",
584
+ "run_id": "f9f472314d223516818433b6f731ed3a05c999bd0be412134ad671d5ddc7808c"
585
+ }
evaluation/2026-09-24/latency_throughput.png ADDED
evaluation/2026-09-24/performance.json ADDED
@@ -0,0 +1,96 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ {
2
+ "model": "Q1z/Pivot",
3
+ "model_revision": "14bf8c26bf344ebdf88e22a4b6152dc5f75f3578",
4
+ "benchmark": {
5
+ "name": "JevBench",
6
+ "release": "v1.4.1",
7
+ "commit": "24b9b5c1609a7a9e8fa14f49e5985a836c9dc842",
8
+ "scope": "public tasks only",
9
+ "official_v1_4_score": null,
10
+ "public_accuracy": 0.46320346320346323,
11
+ "public_correct": 107,
12
+ "public_n": 231,
13
+ "cohorts": {
14
+ "original": {
15
+ "n": 72,
16
+ "accuracy": 0.375,
17
+ "ece_10": 0.5247104664643605,
18
+ "invalid": 0
19
+ },
20
+ "easy": {
21
+ "n": 48,
22
+ "accuracy": 0.8125,
23
+ "ece_10": 0.1139311231672764,
24
+ "invalid": 0
25
+ },
26
+ "hard": {
27
+ "n": 111,
28
+ "accuracy": 0.36936936936936937,
29
+ "ece_10": 0.3791593909263611,
30
+ "invalid": 0
31
+ }
32
+ },
33
+ "input_contract": {
34
+ "context_arm": "context_header",
35
+ "option_arm": "original",
36
+ "context_tokens": 512,
37
+ "option_tokens": 128,
38
+ "truncation": "right"
39
+ }
40
+ },
41
+ "speed": {
42
+ "precision": "float32",
43
+ "measurement": "warm local end-to-end; includes tokenization, inference, and scoring",
44
+ "h200": {
45
+ "hardware": "NVIDIA H200",
46
+ "single_decision": {
47
+ "batch_size": 1,
48
+ "n": 32,
49
+ "p50_ms": 15.766771499897914,
50
+ "p95_ms": 19.87079100013034,
51
+ "mean_ms": 16.16144687499599
52
+ },
53
+ "throughput": {
54
+ "batch_size": 32,
55
+ "decisions_per_repeat": 64,
56
+ "repeats": 3,
57
+ "decisions_per_second": 545.2832753857399,
58
+ "repeat_seconds": [
59
+ 0.12318335300005856,
60
+ 0.1173701870000059,
61
+ 0.11606121599993457
62
+ ]
63
+ }
64
+ },
65
+ "cpu": {
66
+ "hardware": "Intel(R) Xeon(R) CPU @ 2.60GHz",
67
+ "threads": 4,
68
+ "single_decision": {
69
+ "batch_size": 1,
70
+ "n": 32,
71
+ "p50_ms": 797.5558204999516,
72
+ "p95_ms": 1089.0265890000137,
73
+ "mean_ms": 770.7159108749977
74
+ },
75
+ "throughput": {
76
+ "batch_size": 4,
77
+ "decisions_per_repeat": 64,
78
+ "repeats": 3,
79
+ "decisions_per_second": 3.7708397043400965,
80
+ "repeat_seconds": [
81
+ 16.58172093600001,
82
+ 17.219981521999898,
83
+ 16.97234701499997
84
+ ]
85
+ }
86
+ }
87
+ },
88
+ "comparison": {
89
+ "cpu_to_h200_single_decision_p50_ratio": 50.584599421962544,
90
+ "throughput_batches_differ": true
91
+ },
92
+ "limits": {
93
+ "official_score": "Requires organizer-held sealed evaluation and cost; not available from public tasks.",
94
+ "latency": "These are local timings on specific machines. Batch 32 GPU and batch 4 CPU throughput should not be compared as equal configurations."
95
+ }
96
+ }
evaluation/2026-09-24/performance_overview.png ADDED

Git LFS Details

  • SHA256: e91059540c6d0e03bcc855a2a009edb8672e61cf99fcd7081ef8dfd354342a04
  • Pointer size: 131 Bytes
  • Size of remote file: 186 kB
evaluation/render_charts.py ADDED
@@ -0,0 +1,94 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ """Render the published charts from evaluation/2026-09-24/performance.json.
2
+
3
+ Usage: python evaluation/render_charts.py
4
+ """
5
+ import json
6
+ from pathlib import Path
7
+
8
+ import matplotlib
9
+ matplotlib.use("Agg")
10
+ import matplotlib.pyplot as plt
11
+ import numpy as np
12
+
13
+ RESULTS = Path(__file__).resolve().parent / "2026-09-24"
14
+ data = json.loads((RESULTS / "performance.json").read_text(encoding="utf-8"))
15
+ bench, speed = data["benchmark"], data["speed"]
16
+ cohorts = bench["cohorts"]
17
+ names = list(cohorts)
18
+ accuracies = [100 * cohorts[name]["accuracy"] for name in names]
19
+ ece = [cohorts[name]["ece_10"] for name in names]
20
+ palette = ["#2a58d7", "#129184", "#dc8240"]
21
+
22
+ plt.rcParams.update({
23
+ "font.family": "DejaVu Sans", "font.size": 11, "axes.spines.top": False,
24
+ "axes.spines.right": False, "axes.edgecolor": "#c8d3e4",
25
+ "axes.facecolor": "#f5f7fb", "text.color": "#182640",
26
+ })
27
+
28
+ def public_chart(ax):
29
+ bars = ax.barh(names[::-1], accuracies[::-1], color=palette[::-1], height=.58)
30
+ ax.set_xlim(0, 105)
31
+ ax.set_xlabel("Accuracy (%)")
32
+ ax.set_title("JevBench v1.4.1 public tasks", loc="left", fontweight="bold")
33
+ for bar, name, value in zip(bars, names[::-1], accuracies[::-1]):
34
+ ax.text(value + 2, bar.get_y() + bar.get_height() / 2,
35
+ f"{value:.2f}% (n={cohorts[name]['n']})", va="center", fontsize=10)
36
+ ax.text(.02, -.18, f"Total {bench['public_correct']} / {bench['public_n']} = {100*bench['public_accuracy']:.2f}%",
37
+ transform=ax.transAxes, fontweight="bold")
38
+
39
+ def latency_chart(ax):
40
+ x = np.arange(2)
41
+ p50 = [speed[key]["single_decision"]["p50_ms"] for key in ("h200", "cpu")]
42
+ p95 = [speed[key]["single_decision"]["p95_ms"] for key in ("h200", "cpu")]
43
+ ax.bar(x - .18, p50, .35, color=palette[0], label="p50")
44
+ ax.bar(x + .18, p95, .35, color=palette[2], label="p95")
45
+ ax.set_yscale("log")
46
+ ax.set_ylim(5, 2100)
47
+ ax.set_xticks(x, ["H200 GPU", "Xeon CPU"])
48
+ ax.set_ylabel("Milliseconds (log scale)")
49
+ ax.set_title("Warm single-decision latency", loc="left", fontweight="bold")
50
+ ax.legend(frameon=False)
51
+ for pos, values, offset in ((0, p50, -.18), (1, p95, .18)):
52
+ for index, value in enumerate(values):
53
+ ax.text(index + offset, value * 1.15, f"{value:.1f} ms", ha="center", fontsize=9)
54
+
55
+ def throughput_chart(ax):
56
+ values = [speed[key]["throughput"]["decisions_per_second"] for key in ("h200", "cpu")]
57
+ batches = [speed[key]["throughput"]["batch_size"] for key in ("h200", "cpu")]
58
+ ax.bar([f"H200\nbatch {batches[0]}", f"Xeon CPU\nbatch {batches[1]}"],
59
+ values, color=palette[:2], width=.58)
60
+ ax.set_yscale("log")
61
+ ax.set_ylim(1, 2000)
62
+ ax.set_ylabel("Decisions/s (log scale)")
63
+ ax.set_title("Warm batched throughput", loc="left", fontweight="bold")
64
+ for index, value in enumerate(values):
65
+ ax.text(index, value * 1.15, f"{value:.2f}/s", ha="center", fontsize=10)
66
+
67
+ fig, ax = plt.subplots(figsize=(8.2, 4.8), layout="constrained")
68
+ public_chart(ax)
69
+ fig.savefig(RESULTS / "accuracy.png", dpi=180)
70
+ plt.close(fig)
71
+
72
+ fig, axes = plt.subplots(1, 2, figsize=(11, 4.7), layout="constrained")
73
+ latency_chart(axes[0])
74
+ throughput_chart(axes[1])
75
+ fig.suptitle("Pivot | local FP32 inference", fontsize=16, fontweight="bold")
76
+ fig.savefig(RESULTS / "latency_throughput.png", dpi=180)
77
+ plt.close(fig)
78
+
79
+ fig, axes = plt.subplots(2, 2, figsize=(12.6, 8.8), layout="constrained")
80
+ public_chart(axes[0, 0])
81
+ axes[0, 1].barh(names[::-1], ece[::-1], color=palette[::-1], height=.58)
82
+ axes[0, 1].set_xlim(0, .68)
83
+ axes[0, 1].set_xlabel("ECE, 10 bins (lower is better)")
84
+ axes[0, 1].set_title("Public calibration", loc="left", fontweight="bold")
85
+ for index, value in enumerate(ece[::-1]):
86
+ axes[0, 1].text(value + .01, index, f"{value:.3f}", va="center")
87
+ latency_chart(axes[1, 0])
88
+ throughput_chart(axes[1, 1])
89
+ fig.suptitle("Pivot | measured public performance", fontsize=19, fontweight="bold")
90
+ fig.text(.02, -.015, "Pinned checkpoint | warm local tokenization + inference + scoring | throughput batch sizes differ",
91
+ fontsize=9, color="#53637b")
92
+ fig.savefig(RESULTS / "performance_overview.png", dpi=180, bbox_inches="tight")
93
+ plt.close(fig)
94
+ print(f"Saved charts in {RESULTS}")
examples/cached_candidates.py ADDED
@@ -0,0 +1,21 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ """Reuse candidate embeddings across multiple calls with the same options."""
2
+ import torch
3
+ from transformers import AutoModel, AutoTokenizer
4
+
5
+ MODEL = "Q1z/Pivot"
6
+ tokenizer = AutoTokenizer.from_pretrained(MODEL, trust_remote_code=True)
7
+ model = AutoModel.from_pretrained(MODEL, trust_remote_code=True, dtype=torch.float32).eval()
8
+
9
+ options = [
10
+ "route to billing support",
11
+ "route to technical support",
12
+ "route to sales",
13
+ ]
14
+ candidate_vectors = model.encode_candidates(tokenizer, options)
15
+
16
+ for context in (
17
+ "A customer disputes an invoice amount.",
18
+ "A customer reports a failed software update.",
19
+ ):
20
+ context_vector = model.encode_context(tokenizer, context)
21
+ print(model.choose_cached(context_vector, candidate_vectors, options))
examples/native_decision.py ADDED
@@ -0,0 +1,20 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ """Structured decisions keep candidate IDs out of the option encoder."""
2
+ import torch
3
+ from transformers import AutoModel, AutoTokenizer
4
+
5
+ MODEL = "Q1z/Pivot"
6
+ tokenizer = AutoTokenizer.from_pretrained(MODEL, trust_remote_code=True)
7
+ model = AutoModel.from_pretrained(MODEL, trust_remote_code=True, dtype=torch.float32).eval()
8
+
9
+ decision = model.decide_native(
10
+ tokenizer,
11
+ "A customer disputes an invoice and asks for a billing correction.",
12
+ [
13
+ {"id": "billing", "text": "route to billing support"},
14
+ {"id": "technical", "text": "route to technical support"},
15
+ {"id": "sales", "text": "route to sales"},
16
+ {"id": "abstain", "text": "none of these routes is appropriate", "kind": "abstain"},
17
+ ],
18
+ )
19
+ print(decision["selected"])
20
+ print(decision["prob_vector"])
examples/quickstart.py ADDED
@@ -0,0 +1,16 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ """One context, multiple candidate answers; no text generation."""
2
+ import torch
3
+ from transformers import AutoModel, AutoTokenizer
4
+
5
+ MODEL = "Q1z/Pivot"
6
+ tokenizer = AutoTokenizer.from_pretrained(MODEL, trust_remote_code=True)
7
+ model = AutoModel.from_pretrained(MODEL, trust_remote_code=True, dtype=torch.float32).eval()
8
+
9
+ context = "CONTEXT:\nA customer disputes an invoice and asks for a billing correction."
10
+ options = [
11
+ "route to billing support",
12
+ "route to technical support",
13
+ "route to sales",
14
+ ]
15
+ decision = model.choose(tokenizer, context, options)
16
+ print(decision) # choice, index, probabilities in supplied candidate order
manifest.json CHANGED
@@ -7,12 +7,12 @@
7
  "encoder_runtime_sha256": "f171f518be2a07da48b17fdea5655cad0a2452ab548e90e8ae903143686647e2",
8
  "files": {
9
  "README.md": {
10
- "sha256": "fa91584d2acb11a7a313d6467002e39f0b4ae02be3b61d2f885e84a326135b49",
11
- "bytes": 1097
12
  },
13
  "config.json": {
14
- "sha256": "7fc9a4faf690fb75956e8fe87d4a858dcbba3977840ce9c33d69e714cb82f47e",
15
- "bytes": 3881
16
  },
17
  "configuration_pivot.py": {
18
  "sha256": "a65e6ef32f3483dd0015dbbd6faaf75f456c3641a4692764cee7c0b7e39c6628",
@@ -89,6 +89,103 @@
89
  "training/train_report.json": {
90
  "sha256": "d597894ba21c47a7c959fca8e4b8a65f901da7647517e3ae66661cedec0d1412",
91
  "bytes": 4100
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
92
  }
93
- }
94
- }
 
 
 
 
 
 
7
  "encoder_runtime_sha256": "f171f518be2a07da48b17fdea5655cad0a2452ab548e90e8ae903143686647e2",
8
  "files": {
9
  "README.md": {
10
+ "sha256": "f9661656045863e2293a38b98d1b79c736ef4864c01bf419c47ede101a61c8f5",
11
+ "bytes": 5574
12
  },
13
  "config.json": {
14
+ "sha256": "225cf873bf254492dc6a791aeeb702149485b9df8902c43c056e5ae911b04b77",
15
+ "bytes": 3883
16
  },
17
  "configuration_pivot.py": {
18
  "sha256": "a65e6ef32f3483dd0015dbbd6faaf75f456c3641a4692764cee7c0b7e39c6628",
 
89
  "training/train_report.json": {
90
  "sha256": "d597894ba21c47a7c959fca8e4b8a65f901da7647517e3ae66661cedec0d1412",
91
  "bytes": 4100
92
+ },
93
+ "benchmarks/README.md": {
94
+ "sha256": "b8afec4eb9ef30b85f8fd1efe970ad7be707f1f749005eee38cec1c145283f29",
95
+ "bytes": 1427
96
+ },
97
+ "benchmarks/jevbench_public.py": {
98
+ "sha256": "de8bd8a3d867dcd1f9e443bc0820bcdda0ca7f0460832c076247c3e5745878ca",
99
+ "bytes": 13590
100
+ },
101
+ "benchmarks/requirements.txt": {
102
+ "sha256": "7397364a88101a1e34d246f1c4a828cf0a6a2a17e95da61afe9817ace943d7b1",
103
+ "bytes": 91
104
+ },
105
+ "cpu-speed/README.md": {
106
+ "sha256": "84157cf465febe80a4964c8dac0023e3f6b705bfc71470e5bc9c9ec0ddcbf68f",
107
+ "bytes": 1136
108
+ },
109
+ "cpu-speed/benchmark.py": {
110
+ "sha256": "22321de328e88fd646840f9266a9d092e72dddb290f6289926da24a72932aad0",
111
+ "bytes": 308
112
+ },
113
+ "docs/BENCHMARK.md": {
114
+ "sha256": "3bd6f3c535717c2f08378ec286a34cd83ded8b3920ad5ffda290f3f9d41394ba",
115
+ "bytes": 2151
116
+ },
117
+ "docs/INFERENCE.md": {
118
+ "sha256": "bc00ea8e2998072767208610a5062205ada4d1fe3bb0007d3318666933a2ea9f",
119
+ "bytes": 3349
120
+ },
121
+ "docs/PERFORMANCE.md": {
122
+ "sha256": "b2bdf733b584d9039b89d3301b1458f5cf5401c205405f8fc05ed6e9ea7be1e3",
123
+ "bytes": 1866
124
+ },
125
+ "docs/UPDATE_NOTES.md": {
126
+ "sha256": "bb3e44ddd2fc2f615034b9aa4b78cf90e0ec2cc51b57d36743100fe41355fa59",
127
+ "bytes": 1308
128
+ },
129
+ "evaluation/2026-09-24/README.md": {
130
+ "sha256": "9a1d611d964dd798b88d85e67a677943e25b240fb1a052a77193357421403959",
131
+ "bytes": 1156
132
+ },
133
+ "evaluation/2026-09-24/accuracy.png": {
134
+ "sha256": "0405c91c2408d049aac19940c30ff2db07a43ab605f9440645fd20d04643ecd1",
135
+ "bytes": 53007
136
+ },
137
+ "evaluation/2026-09-24/cpu_speed.json": {
138
+ "sha256": "b02c3c1f6b5ba01f0857ecbd6e3a6d50e9df2f4d0cfbbb34eb92b44f86b23697",
139
+ "bytes": 1203
140
+ },
141
+ "evaluation/2026-09-24/jevbench_public.json": {
142
+ "sha256": "b4c878e64a5c6f5203a5fe97e2b4b9c0e262ddd78feb7b634fe58ee88d1b9af7",
143
+ "bytes": 16603
144
+ },
145
+ "evaluation/2026-09-24/latency_throughput.png": {
146
+ "sha256": "fb50dfbb7c1e7a37f6da9636cdad56c14caff3b11685ea55c41bf9ce94400805",
147
+ "bytes": 89972
148
+ },
149
+ "evaluation/2026-09-24/performance.json": {
150
+ "sha256": "81642b8b7b85095d9758e3cc8fed1e595f23fe5744a3986c6c40ca2d397f61b2",
151
+ "bytes": 2644
152
+ },
153
+ "evaluation/2026-09-24/performance_overview.png": {
154
+ "sha256": "e91059540c6d0e03bcc855a2a009edb8672e61cf99fcd7081ef8dfd354342a04",
155
+ "bytes": 185988
156
+ },
157
+ "evaluation/render_charts.py": {
158
+ "sha256": "3b8bafdc9ddc7b86655238cf8d8378e57381308875cb13b2f68ac4054a3f3672",
159
+ "bytes": 4191
160
+ },
161
+ "examples/cached_candidates.py": {
162
+ "sha256": "892e6858506599da59a487b569559a94c23f253eb1ead1dbdfaf954f15c1559a",
163
+ "bytes": 751
164
+ },
165
+ "examples/native_decision.py": {
166
+ "sha256": "b9edf1839fb90426585908d0b702e7371d6b1b6d973e3fb3eb169fef9eea6a0c",
167
+ "bytes": 792
168
+ },
169
+ "examples/quickstart.py": {
170
+ "sha256": "355a0d2b5f12dc2016344bee7c8a24e27509911c7f9d1aa8d4501af71bcc48e1",
171
+ "bytes": 637
172
+ },
173
+ "notebooks/Pivot_CPU_Speed.ipynb": {
174
+ "sha256": "ee50b481a1fd841e06b91656e6febcc9422e7fcf9182de4192f19cbb761636d7",
175
+ "bytes": 3589
176
+ },
177
+ "serving/README.md": {
178
+ "sha256": "a23d3d61c64958bce4381a34c6308756edad918efc032741f59093d05cab68e5",
179
+ "bytes": 1081
180
+ },
181
+ "serving/typed_decisions.py": {
182
+ "sha256": "427cb1eac5a1e9bde56f592e6d5fefdb7d537d1aff6d374fd11d9cf1a7ae929d",
183
+ "bytes": 733
184
  }
185
+ },
186
+ "documentation_release": "2026-09-24",
187
+ "evaluated_model_revision": "14bf8c26bf344ebdf88e22a4b6152dc5f75f3578",
188
+ "benchmark_release": "JevBench v1.4.1 public tasks",
189
+ "benchmark_commit": "24b9b5c1609a7a9e8fa14f49e5985a836c9dc842",
190
+ "serving_option_tokens_default": 128
191
+ }
notebooks/Pivot_CPU_Speed.ipynb ADDED
@@ -0,0 +1,101 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ {
2
+ "nbformat": 4,
3
+ "nbformat_minor": 5,
4
+ "metadata": {
5
+ "kernelspec": {
6
+ "display_name": "Python 3",
7
+ "language": "python",
8
+ "name": "python3"
9
+ },
10
+ "language_info": {
11
+ "name": "python"
12
+ }
13
+ },
14
+ "cells": [
15
+ {
16
+ "cell_type": "markdown",
17
+ "metadata": {},
18
+ "source": [
19
+ "# Pivot — CPU speed\n",
20
+ "\n",
21
+ "Run this notebook in a Python CPU environment with Git installed. It asks for a Hugging Face key only if you choose to use one; public model files do not need a key. It pins the measured Pivot checkpoint and the official JevBench v1.4.1 public source, then writes a JSON result, chart and ZIP. The result is local speed, not an official composite score.\n"
22
+ ]
23
+ },
24
+ {
25
+ "cell_type": "code",
26
+ "metadata": {},
27
+ "source": [
28
+ "import sys, subprocess\n",
29
+ "subprocess.check_call([sys.executable, \"-m\", \"pip\", \"install\", \"-q\", \"torch==2.8.0\", \"transformers==5.17.0\", \"safetensors==0.8.0\", \"numpy==1.26.4\", \"huggingface_hub>=0.26.0\", \"matplotlib>=3.8,<4\"])\n"
30
+ ],
31
+ "execution_count": null,
32
+ "outputs": []
33
+ },
34
+ {
35
+ "cell_type": "code",
36
+ "metadata": {},
37
+ "source": [
38
+ "from getpass import getpass\n",
39
+ "from huggingface_hub import login\n",
40
+ "if input(\"Use a private Hugging Face key? (y/N): \").strip().lower() == \"y\":\n",
41
+ " login(token=getpass(\"HF token (hidden): \"), add_to_git_credential=False)\n"
42
+ ],
43
+ "execution_count": null,
44
+ "outputs": []
45
+ },
46
+ {
47
+ "cell_type": "code",
48
+ "metadata": {},
49
+ "source": [
50
+ "from pathlib import Path\n",
51
+ "from huggingface_hub import hf_hub_download\n",
52
+ "script = Path.cwd() / \"benchmarks\" / \"jevbench_public.py\"\n",
53
+ "if not script.is_file():\n",
54
+ " script = Path(hf_hub_download(\"Q1z/Pivot\", \"benchmarks/jevbench_public.py\"))\n",
55
+ "print(\"Benchmark runner:\", script)\n"
56
+ ],
57
+ "execution_count": null,
58
+ "outputs": []
59
+ },
60
+ {
61
+ "cell_type": "code",
62
+ "metadata": {},
63
+ "source": [
64
+ "import json\n",
65
+ "outdir = Path.cwd() / \"pivot_cpu_run\"\n",
66
+ "outdir.mkdir(exist_ok=True)\n",
67
+ "result = outdir / \"cpu_speed.json\"\n",
68
+ "subprocess.check_call([sys.executable, str(script), \"--mode\", \"speed\", \"--device\", \"cpu\", \"--threads\", \"4\", \"--batch-size\", \"4\", \"--output\", str(result)])\n",
69
+ "report = json.loads(result.read_text(encoding=\"utf-8\"))\n",
70
+ "report[\"speed\"]\n"
71
+ ],
72
+ "execution_count": null,
73
+ "outputs": []
74
+ },
75
+ {
76
+ "cell_type": "code",
77
+ "metadata": {},
78
+ "source": [
79
+ "import matplotlib.pyplot as plt\n",
80
+ "from zipfile import ZipFile, ZIP_DEFLATED\n",
81
+ "from IPython.display import display, FileLink\n",
82
+ "s = report[\"speed\"]\n",
83
+ "fig, axes = plt.subplots(1, 2, figsize=(9, 3.5), layout=\"constrained\")\n",
84
+ "axes[0].bar([\"p50\", \"p95\"], [s[\"single_decision\"][\"p50_ms\"], s[\"single_decision\"][\"p95_ms\"]], color=[\"#2959db\", \"#139684\"])\n",
85
+ "axes[0].set(title=\"CPU single-decision latency\", ylabel=\"ms\")\n",
86
+ "axes[1].bar([f\"batch {s['throughput']['batch_size']}\"], [s[\"throughput\"][\"decisions_per_second\"]], color=\"#2959db\")\n",
87
+ "axes[1].set(title=\"CPU throughput\", ylabel=\"decisions/s\")\n",
88
+ "chart = outdir / \"cpu_speed_chart.png\"\n",
89
+ "fig.savefig(chart, dpi=170)\n",
90
+ "plt.show()\n",
91
+ "archive = outdir / \"pivot_cpu_speed.zip\"\n",
92
+ "with ZipFile(archive, \"w\", ZIP_DEFLATED) as z:\n",
93
+ " z.write(result, result.name)\n",
94
+ " z.write(chart, chart.name)\n",
95
+ "display(FileLink(str(archive)))\n"
96
+ ],
97
+ "execution_count": null,
98
+ "outputs": []
99
+ }
100
+ ]
101
+ }
serving/README.md ADDED
@@ -0,0 +1,11 @@
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # Pivot serving interfaces
2
+
3
+ The model exposes three synchronous methods after loading `AutoModel` and `AutoTokenizer` with `trust_remote_code=True`:
4
+
5
+ 1. `choose(tokenizer, context, options)` returns a simple choice, index and probability vector.
6
+ 2. `decide_native(tokenizer, context, candidates)` accepts stable machine IDs, semantic candidate text and at most one explicit abstain candidate.
7
+ 3. `decide(tokenizer, state, questions)` returns a typed collection of choice, yes/no and score decisions.
8
+
9
+ The model operates on supplied options; it does not create new options or generate explanatory text. The default serving limits in this update are 512 context tokens and 128 tokens per option. Keep a stable option set if comparing scores between requests.
10
+
11
+ The pre-existing [typed request](example_request.json) and [response](example_response.json) and [native request](native_example_request.json) and [response](native_example_response.json) illustrate the wire shapes. Run [a typed Python example](typed_decisions.py) or read the [full inference guide](../docs/INFERENCE.md).
serving/typed_decisions.py ADDED
@@ -0,0 +1,20 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ """One context, three typed questions using Pivot's bundled runtime."""
2
+ import torch
3
+ from transformers import AutoModel, AutoTokenizer
4
+
5
+ repo = "Q1z/Pivot"
6
+ tokenizer = AutoTokenizer.from_pretrained(repo, trust_remote_code=True)
7
+ model = AutoModel.from_pretrained(repo, trust_remote_code=True, dtype=torch.float32).eval()
8
+ response = model.decide(
9
+ tokenizer,
10
+ "Customer dispute: invoice 120 vs PO 100, age=3d, region=US",
11
+ [
12
+ {"id": "route", "primitive": "choice",
13
+ "options": ["billing", "tech", "sales"]},
14
+ {"id": "approve", "primitive": "noul",
15
+ "options": ["true", "false"]},
16
+ {"id": "severity", "primitive": "score",
17
+ "options": ["0", "1", "2", "3"]},
18
+ ],
19
+ )
20
+ print(response)