Text Generation
Transformers
Safetensors
English
qwen2
verifier
answer-verification
llm-cascade
model-cascade
escalation
llm-router
orchestration
multi-agent
conversational
Eval Results (legacy)
text-generation-inference
Instructions to use podstack/Jedi with libraries, inference providers, notebooks, and local apps. Follow these links to get started.
- Libraries
- Transformers
How to use podstack/Jedi with Transformers:
# Use a pipeline as a high-level helper from transformers import pipeline pipe = pipeline("text-generation", model="podstack/Jedi") messages = [ {"role": "user", "content": "Who are you?"}, ] pipe(messages)# pip install -U transformers accelerate # Load model directly from transformers import AutoTokenizer, AutoModelForCausalLM tokenizer = AutoTokenizer.from_pretrained("podstack/Jedi") model = AutoModelForCausalLM.from_pretrained("podstack/Jedi", device_map="auto") messages = [ {"role": "user", "content": "Who are you?"}, ] inputs = tokenizer.apply_chat_template( messages, add_generation_prompt=True, tokenize=True, return_dict=True, return_tensors="pt", ).to(model.device) outputs = model.generate(**inputs, max_new_tokens=256) print(tokenizer.decode(outputs[0][inputs["input_ids"].shape[-1]:])) - Notebooks
- Google Colab
- Kaggle
- Local Apps Settings
- vLLM
How to use podstack/Jedi with vLLM:
Install from pip and serve model
# Install vLLM from pip: pip install vllm # Start the vLLM server: vllm serve "podstack/Jedi" # Call the server using curl (OpenAI-compatible API): curl -X POST "http://localhost:8000/v1/chat/completions" \ -H "Content-Type: application/json" \ --data '{ "model": "podstack/Jedi", "messages": [ { "role": "user", "content": "What is the capital of France?" } ] }'Use Docker
docker model run hf.co/podstack/Jedi
- SGLang
How to use podstack/Jedi with SGLang:
Install from pip and serve model
# Install SGLang from pip: pip install sglang # Start the SGLang server: python3 -m sglang.launch_server \ --model-path "podstack/Jedi" \ --host 0.0.0.0 \ --port 30000 # Call the server using curl (OpenAI-compatible API): curl -X POST "http://localhost:30000/v1/chat/completions" \ -H "Content-Type: application/json" \ --data '{ "model": "podstack/Jedi", "messages": [ { "role": "user", "content": "What is the capital of France?" } ] }'Use Docker images
docker run --gpus all \ --shm-size 32g \ -p 30000:30000 \ -v ~/.cache/huggingface:/root/.cache/huggingface \ --env "HF_TOKEN=<secret>" \ --ipc=host \ lmsysorg/sglang:latest \ python3 -m sglang.launch_server \ --model-path "podstack/Jedi" \ --host 0.0.0.0 \ --port 30000 # Call the server using curl (OpenAI-compatible API): curl -X POST "http://localhost:30000/v1/chat/completions" \ -H "Content-Type: application/json" \ --data '{ "model": "podstack/Jedi", "messages": [ { "role": "user", "content": "What is the capital of France?" } ] }' - Docker Model Runner
How to use podstack/Jedi with Docker Model Runner:
docker model run hf.co/podstack/Jedi
Jedi v1.0
Browse files- .gitattributes +1 -0
- README.md +357 -0
- build.py +67 -0
- calibration.json +6 -0
- chat_template.jinja +54 -0
- common.py +165 -0
- config.json +57 -0
- eval_results.json +709 -0
- evaluate.py +279 -0
- gen.py +194 -0
- generation_config.json +14 -0
- jedi.py +99 -0
- model.safetensors +3 -0
- tokenizer.json +3 -0
- tokenizer_config.json +30 -0
- train.py +18 -0
.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 |
+
tokenizer.json filter=lfs diff=lfs merge=lfs -text
|
README.md
ADDED
|
@@ -0,0 +1,357 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
---
|
| 2 |
+
base_model: Qwen/Qwen2.5-0.5B-Instruct
|
| 3 |
+
license: apache-2.0
|
| 4 |
+
language: [en]
|
| 5 |
+
library_name: transformers
|
| 6 |
+
pipeline_tag: text-generation
|
| 7 |
+
tags: [verifier, answer-verification, llm-cascade, model-cascade, escalation, llm-router, orchestration, multi-agent]
|
| 8 |
+
model-index:
|
| 9 |
+
- name: Jedi v1.0
|
| 10 |
+
results:
|
| 11 |
+
- task: {type: text-classification, name: answer verification (accept / escalate)}
|
| 12 |
+
dataset: {type: custom, name: held-out test answers (2,532 answers to 633 unseen tasks)}
|
| 13 |
+
metrics:
|
| 14 |
+
- {type: roc_auc, value: 0.9195, name: AUROC (correct vs wrong answers)}
|
| 15 |
+
- {type: ece, value: 0.0170, name: expected calibration error}
|
| 16 |
+
- task: {type: text-generation, name: LLM escalation ladder}
|
| 17 |
+
dataset: {type: custom, name: Shaktimaan held-out text and code tasks (491 tasks)}
|
| 18 |
+
metrics:
|
| 19 |
+
- {type: accuracy, value: 0.9185, name: end-to-end task accuracy (threshold 0.7)}
|
| 20 |
+
- {type: relative_cost, value: 0.4538, name: cost vs always-strongest agent}
|
| 21 |
+
---
|
| 22 |
+
# Jedi v1.0 (0.5B)
|
| 23 |
+
|
| 24 |
+
**Jedi** is a 0.5B-parameter **answer verifier** that runs an LLM escalation ladder. A cheap model answers first.
|
| 25 |
+
Jedi reads the task *and that answer* and returns a calibrated probability that the answer is correct. If the
|
| 26 |
+
probability is high enough, the answer is accepted. Otherwise the task goes to the next, more expensive model,
|
| 27 |
+
whose answer Jedi checks in turn. Jedi never answers tasks itself; it emits a single token.
|
| 28 |
+
|
| 29 |
+
Fine-tuned from [Qwen2.5-0.5B-Instruct](https://huggingface.co/Qwen/Qwen2.5-0.5B-Instruct) on 38,558 graded
|
| 30 |
+
answers from three LLMs. It is the successor to [Shaktimaan](https://huggingface.co/podstack/Shaktimaan), which
|
| 31 |
+
picks a model *before* any answer exists (see [How Jedi differs from Shaktimaan](#how-jedi-differs-from-shaktimaan)).
|
| 32 |
+
|
| 33 |
+
## At a glance
|
| 34 |
+
- **91.9%** end-to-end accuracy on 491 held-out text and code tasks at **0.45x** the cost of always using the
|
| 35 |
+
strongest model. Always using the strongest model gets 84.7%; Shaktimaan v1.0 gets 84.9% at 0.32x on the same tasks.
|
| 36 |
+
- Beats every single model in the pool. At threshold 0.3 it also beats both standard cascade checks (a format
|
| 37 |
+
check and self-consistency) on accuracy, cost and silent failures at once.
|
| 38 |
+
- Within 1.4 points of a *perfect* verifier (93.3%) on the same ladder.
|
| 39 |
+
- **AUROC 0.92** at telling correct answers from wrong ones on unseen tasks; calibrated confidence (ECE 0.017).
|
| 40 |
+
- One forward pass and **one output token** per check: ~56 ms at batch size 1, ~450 input tokens.
|
| 41 |
+
- **Weak outside its training domains**: AUROC 0.72 on BIG-Bench Hard and 0.59 on CommonsenseQA. See *Limitations*.
|
| 42 |
+
|
| 43 |
+
## How it works
|
| 44 |
+
|
| 45 |
+
```
|
| 46 |
+
task
|
| 47 |
+
|
|
| 48 |
+
v
|
| 49 |
+
+-----------------------+ answer +------------------+
|
| 50 |
+
| rung 1: glm-5.3-flash | ---------------> | Jedi | P(correct) >= threshold ?
|
| 51 |
+
| (cost 0.2) | | 0.5B, 1 token | --- yes --> return answer
|
| 52 |
+
+-----------------------+ +------------------+
|
| 53 |
+
| no (escalate)
|
| 54 |
+
v
|
| 55 |
+
+-----------------------+ answer +------------------+
|
| 56 |
+
| rung 2: qwen3.8-27b | ---------------> | Jedi | --- yes --> return answer
|
| 57 |
+
| (cost 0.5) | +------------------+
|
| 58 |
+
+-----------------------+
|
| 59 |
+
| no (escalate)
|
| 60 |
+
v
|
| 61 |
+
+-----------------------+
|
| 62 |
+
| rung 3: nemotron-ultra| -----------------------------------------------> return answer (always accepted)
|
| 63 |
+
| (cost 1.0) |
|
| 64 |
+
+-----------------------+
|
| 65 |
+
```
|
| 66 |
+
|
| 67 |
+
1. **What Jedi reads.** `common.judge_view()` builds a short header naming the agent, the task type, the answer
|
| 68 |
+
length and whether the answer was cut off at the token limit. It then appends the task (first 2,500 + last 1,000
|
| 69 |
+
characters) and the candidate's visible answer (first 1,500 + last 2,500 characters, with `<think>` blocks
|
| 70 |
+
removed). The system prompt is `common.JEDI_SYSTEM`.
|
| 71 |
+
2. **What Jedi outputs.** One word, `accept` or `escalate`. Jedi is never sampled. The runtime reads the logits of
|
| 72 |
+
those two tokens at the first output position and takes the gap `z = logit(accept) - logit(escalate)`.
|
| 73 |
+
3. **Calibration.** `P(correct) = sigmoid(a·z + b)`, with `a`, `b` fitted by Platt scaling on a held-out validation
|
| 74 |
+
split (`calibration.json`). This drops calibration error from 0.061 to 0.017 on the test split, so a threshold
|
| 75 |
+
of 0.7 means roughly "accept when at least 70% likely to be right".
|
| 76 |
+
4. **The single dial.** The threshold trades cost for accuracy. Raise it to escalate more and catch more wrong
|
| 77 |
+
answers; lower it to save money. The top rung is always accepted, so every task gets an answer.
|
| 78 |
+
|
| 79 |
+
## Quick start
|
| 80 |
+
|
| 81 |
+
### Score one answer
|
| 82 |
+
```python
|
| 83 |
+
import importlib.util, json, math, torch
|
| 84 |
+
from huggingface_hub import hf_hub_download
|
| 85 |
+
from transformers import AutoModelForCausalLM, AutoTokenizer
|
| 86 |
+
|
| 87 |
+
repo = "podstack/Jedi"
|
| 88 |
+
spec = importlib.util.spec_from_file_location("common", hf_hub_download(repo, "common.py"))
|
| 89 |
+
common = importlib.util.module_from_spec(spec); spec.loader.exec_module(common)
|
| 90 |
+
cal = json.load(open(hf_hub_download(repo, "calibration.json")))
|
| 91 |
+
tok = AutoTokenizer.from_pretrained(repo)
|
| 92 |
+
model = AutoModelForCausalLM.from_pretrained(repo, dtype=torch.bfloat16).eval()
|
| 93 |
+
|
| 94 |
+
def p_correct(task, answer, agent="glm-5.3-flash", kind="num", finish="stop"):
|
| 95 |
+
msgs = [{"role": "system", "content": common.JEDI_SYSTEM},
|
| 96 |
+
{"role": "user", "content": common.judge_view(task, answer, agent, kind, finish)}]
|
| 97 |
+
enc = tok.apply_chat_template(msgs, add_generation_prompt=True, return_tensors="pt", return_dict=True)
|
| 98 |
+
with torch.no_grad():
|
| 99 |
+
logits = model(**enc).logits[0, -1].float()
|
| 100 |
+
gap = (logits[cal["accept_token"]] - logits[cal["escalate_token"]]).item()
|
| 101 |
+
return 1 / (1 + math.exp(-(cal["a"] * gap + cal["b"])))
|
| 102 |
+
|
| 103 |
+
task = "A shop sells pencils at 3 for $1.20. How much do 15 pencils cost, in dollars?"
|
| 104 |
+
print(p_correct(task, "Each pencil costs 1.20 / 3 = 0.40. 15 x 0.40 = 6.00.\nFinal answer: 6"))
|
| 105 |
+
print(p_correct(task, "15 pencils is 15 / 3 = 5 groups. 5 x 1.20 = 5.00.\nFinal answer: 5"))
|
| 106 |
+
```
|
| 107 |
+
|
| 108 |
+
Always build the input with `common.judge_view(...)` and use `common.JEDI_SYSTEM` as the system prompt; Jedi was
|
| 109 |
+
trained on exactly that format. `kind` is the task type it was trained with: `num`, `math`, `mc`
|
| 110 |
+
(multiple choice), `code`, `exact`. `finish` is the agent's `finish_reason` (`"length"` means the answer was truncated).
|
| 111 |
+
|
| 112 |
+
### Run the full ladder
|
| 113 |
+
`jedi.py` runs the ladder end to end against OpenAI-compatible endpoints:
|
| 114 |
+
|
| 115 |
+
```bash
|
| 116 |
+
export JEDI_ENDPOINTS='{"glm-5.3-flash": "http://host-a:8000/v1", "qwen3.8-27b": "http://host-b:8000/v1",
|
| 117 |
+
"nemotron-ultra": "http://host-c:8000/v1"}'
|
| 118 |
+
python jedi.py "If a train travels 120 km in 1.5 hours, what is its average speed in km/h?"
|
| 119 |
+
python jedi.py --threshold 0.9 --code "Write a function that checks whether a string is a palindrome."
|
| 120 |
+
```
|
| 121 |
+
|
| 122 |
+
It prints the final answer to stdout and a per-rung trace to stderr, e.g.
|
| 123 |
+
`{"agent": "glm-5.3-flash", "verdict": "accept", "confidence": 0.97, "escalate_to": null}`. Rungs missing from
|
| 124 |
+
`JEDI_ENDPOINTS` are skipped, so a two-rung ladder works too.
|
| 125 |
+
|
| 126 |
+
## Agent pool (the ladder)
|
| 127 |
+
| rung | agent | model | relative cost / token |
|
| 128 |
+
|---|---|---|---|
|
| 129 |
+
| 1 | `glm-5.3-flash` | `zai-org/GLM-5.3-Flash` | 0.2 |
|
| 130 |
+
| 2 | `qwen3.8-27b` | `Qwen/Qwen3.8-27B` | 0.5 |
|
| 131 |
+
| 3 | `nemotron-ultra` | `nvidia/NVIDIA-Nemotron-3-Ultra-550B-A55B-NVFP4` | 1.0 |
|
| 132 |
+
|
| 133 |
+
`kimi-vl` (`Kimi-VL-A3B-Thinking`) is not on the ladder, but its answers are in the training data. It is the weakest
|
| 134 |
+
of the three answering models, so it supplied most of the wrong answers Jedi learned from. "Relative cost" is a
|
| 135 |
+
per-token weight for roughly relative serving cost, not a vendor price.
|
| 136 |
+
|
| 137 |
+
## How Jedi differs from Shaktimaan
|
| 138 |
+
Both models are 0.5B fine-tunes of Qwen2.5-0.5B-Instruct built for the same agent pool. They solve the same
|
| 139 |
+
problem, getting good answers without paying for the biggest model every time, at different moments.
|
| 140 |
+
|
| 141 |
+
| | **Shaktimaan v1.0** (router) | **Jedi v1.0** (verifier) |
|
| 142 |
+
|---|---|---|
|
| 143 |
+
| When it decides | **before** any model answers | **after** each answer |
|
| 144 |
+
| What it reads | the task only | the task **and** a specific candidate answer |
|
| 145 |
+
| What it outputs | route JSON: domain, difficulty, strategy, agent list (~30 tokens) | one token -> calibrated P(correct) |
|
| 146 |
+
| Question it learns | "which agent is likely to solve this kind of task?" | "is *this* answer right?" |
|
| 147 |
+
| Model calls per task | exactly the routed agent(s), all up front | 1 call, plus more only on escalation (1.15 on average at threshold 0.7) |
|
| 148 |
+
| Cost/quality control | fixed by training labels | one threshold, tunable at run time |
|
| 149 |
+
| Inputs it handles | text, code, images (routes to the vision agent), up to ~260k tokens (routes to long-context agents) | text and code; long inputs are truncated for judging |
|
| 150 |
+
| Extra latency | one router call before the agent | ~56 ms per check, plus the next rung's latency when it escalates (~14 s on average at 0.7) |
|
| 151 |
+
|
| 152 |
+
**Why the switch.** Shaktimaan's main finding was that, on ordinary text and code tasks, *which* mid-size model
|
| 153 |
+
solves a given task is largely sampling noise that cannot be predicted from the prompt. A router has to guess
|
| 154 |
+
blind. Jedi waits until a cheap answer exists and judges that answer: unsupported reasoning, arithmetic that doesn't
|
| 155 |
+
add up, truncation, hedging and code that won't pass its tests. Its errors are mostly answers that look right but
|
| 156 |
+
aren't, which a router cannot see at all.
|
| 157 |
+
|
| 158 |
+
**Head to head**, on the same 491 held-out tasks with the same fresh agent samples:
|
| 159 |
+
|
| 160 |
+
| policy | accuracy | relative cost (always-Ultra = 1.00) |
|
| 161 |
+
|---|---|---|
|
| 162 |
+
| Shaktimaan v1.0 | 84.9% | 0.323 |
|
| 163 |
+
| Jedi ladder, threshold 0.1 | 91.5% | 0.263 |
|
| 164 |
+
| Jedi ladder, threshold 0.7 | **91.9%** | 0.454 |
|
| 165 |
+
|
| 166 |
+
At threshold 0.1, Jedi is both more accurate (+6.5 points) and cheaper than Shaktimaan. (Shaktimaan's ensemble
|
| 167 |
+
routes are scored as the aggregating agent's own answer here, which is slightly conservative for Shaktimaan.)
|
| 168 |
+
|
| 169 |
+
**They are complementary.** Jedi's ladder is text-only with a 32k-context first rung; it has no notion of images or
|
| 170 |
+
256k-token inputs. A sensible deployment routes by capability first (images -> the vision agent, very long inputs
|
| 171 |
+
-> long-context agents, as Shaktimaan or simple rules do) and sends everything else through Jedi's ladder.
|
| 172 |
+
|
| 173 |
+
## Training data
|
| 174 |
+
Every task was answered by `glm-5.3-flash` (2 samples), `qwen3.8-27b` (1) and `kimi-vl` (1) at temperature 0.6
|
| 175 |
+
with a "be concise, end with `Final answer:`" prompt. Each answer was graded automatically: exact, numeric or
|
| 176 |
+
LaTeX-normalised matching, and execution of unit tests in a network-less Docker sandbox for code. That gave 28,296
|
| 177 |
+
graded answers on 7,074 tasks. A second **borderline pass** collected 8,580 more GLM and Qwen answers on the 2,145
|
| 178 |
+
tasks where the models disagreed. This is where the hard-to-judge answers are; those extra samples are used for
|
| 179 |
+
training only.
|
| 180 |
+
|
| 181 |
+
| source | category | tasks | grading | license |
|
| 182 |
+
|---|---|---|---|---|
|
| 183 |
+
| [GSM8K](https://huggingface.co/datasets/openai/gsm8k) | math word problems | 1,000 | numeric match | MIT |
|
| 184 |
+
| [MATH](https://huggingface.co/datasets/EleutherAI/hendrycks_math) | competition math | 1,000 | normalised LaTeX / numeric | MIT |
|
| 185 |
+
| [MMLU-Pro](https://huggingface.co/datasets/TIGER-Lab/MMLU-Pro) | knowledge MCQ (10 options) | 1,200 | option letter | MIT |
|
| 186 |
+
| [ARC-Challenge](https://huggingface.co/datasets/allenai/ai2_arc) | science MCQ | 800 | option letter | CC-BY-SA-4.0 |
|
| 187 |
+
| [MBPP](https://huggingface.co/datasets/google-research-datasets/mbpp) | Python coding | 700 | unit tests in sandbox | CC-BY-4.0 |
|
| 188 |
+
| [HumanEval](https://huggingface.co/datasets/openai/openai_humaneval) | Python coding | 164 | unit tests in sandbox | MIT |
|
| 189 |
+
| [AQuA-RAT](https://huggingface.co/datasets/deepmind/aqua_rat) | algebra word problems (MCQ) | 800 | option letter | Apache-2.0 |
|
| 190 |
+
| [CommonsenseQA](https://huggingface.co/datasets/tau/commonsense_qa) | commonsense MCQ | 600 | option letter | MIT |
|
| 191 |
+
| [BIG-Bench Hard](https://huggingface.co/datasets/lukaemon/bbh) | 27 reasoning tasks | 810 | exact match | MIT |
|
| 192 |
+
|
| 193 |
+
The first six sources use the same task ids as
|
| 194 |
+
[Shaktimaan-routing](https://huggingface.co/datasets/podstack/Shaktimaan-routing), so the two models can be
|
| 195 |
+
compared on identical tasks. Charts and long documents were left out.
|
| 196 |
+
|
| 197 |
+
**Solve rate per model** (share of answers graded correct):
|
| 198 |
+
|
| 199 |
+
| source | glm-5.3-flash | qwen3.8-27b | kimi-vl |
|
| 200 |
+
|---|---|---|---|
|
| 201 |
+
| GSM8K | 0.96 | 0.95 | 0.81 |
|
| 202 |
+
| MATH | 0.75 | 0.75 | 0.42 |
|
| 203 |
+
| MMLU-Pro | 0.81 | 0.80 | 0.49 |
|
| 204 |
+
| ARC-Challenge | 0.97 | 0.97 | 0.92 |
|
| 205 |
+
| MBPP | 0.93 | 0.82 | 0.65 |
|
| 206 |
+
| HumanEval | 0.94 | 0.91 | 0.81 |
|
| 207 |
+
| AQuA-RAT | 0.89 | 0.92 | 0.64 |
|
| 208 |
+
| CommonsenseQA | 0.80 | 0.81 | 0.75 |
|
| 209 |
+
| BIG-Bench Hard | 0.90 | 0.91 | 0.54 |
|
| 210 |
+
|
| 211 |
+
**Splits are by task, never by answer**, so no task appears in two splits:
|
| 212 |
+
|
| 213 |
+
| split | tasks | answers | wrong answers | used for |
|
| 214 |
+
|---|---|---|---|---|
|
| 215 |
+
| train | 5,261 | 38,558 | 40.0% (oversampled from ~18%) | fine-tuning |
|
| 216 |
+
| val | 370 | 1,480 | 17.6% | Platt calibration only |
|
| 217 |
+
| test | 633 | 2,532 | 17.6% | Shaktimaan's held-out test tasks + 10% of AQuA / CommonsenseQA |
|
| 218 |
+
| ood | 810 | 3,240 | 18.8% | all of BIG-Bench Hard, never trained on |
|
| 219 |
+
|
| 220 |
+
Each example's label is `accept` if the answer was graded correct and `escalate` otherwise. Held-out splits use
|
| 221 |
+
only the base samples, without the borderline extras, so every task is sampled the same way.
|
| 222 |
+
|
| 223 |
+
## Training
|
| 224 |
+
| setting | value |
|
| 225 |
+
|---|---|
|
| 226 |
+
| base model | Qwen2.5-0.5B-Instruct (494M parameters) |
|
| 227 |
+
| method | full fine-tune, supervised (TRL `SFTTrainer`); loss on the one-word verdict only |
|
| 228 |
+
| epochs | 2 (2,380 steps) |
|
| 229 |
+
| learning rate | 2e-5, cosine schedule, 30 warm-up steps |
|
| 230 |
+
| effective batch size | 32 |
|
| 231 |
+
| max sequence length | 2,048 tokens |
|
| 232 |
+
| precision | bf16, gradient checkpointing |
|
| 233 |
+
| hardware / time | one MIG 1g.67gb slice of an NVIDIA B300, 1 h 54 min |
|
| 234 |
+
| eval loss (val) | 0.095 after epoch 1, 0.142 after epoch 2 (the published weights are from epoch 2) |
|
| 235 |
+
|
| 236 |
+
## Results
|
| 237 |
+
|
| 238 |
+
### As a verifier
|
| 239 |
+
Calibrated P(correct) on answers to tasks Jedi never saw. *False accept* = share of wrong answers scored >= 0.5;
|
| 240 |
+
*false escalate* = share of correct answers scored < 0.5.
|
| 241 |
+
|
| 242 |
+
| split | answers | wrong | AUROC | false accept | false escalate | ECE | Brier |
|
| 243 |
+
|---|---|---|---|---|---|---|---|
|
| 244 |
+
| test (in-domain) | 2,532 | 17.6% | **0.920** | 35.9% | 1.9% | 0.017 | 0.063 |
|
| 245 |
+
| val | 1,480 | 17.6% | 0.884 | 42.3% | 1.3% | 0.017 | 0.072 |
|
| 246 |
+
| ood (BIG-Bench Hard) | 3,240 | 18.8% | 0.720 | 70.2% | 6.7% | 0.060 | 0.140 |
|
| 247 |
+
|
| 248 |
+
**Test split by answering model and by source:**
|
| 249 |
+
|
| 250 |
+
| slice | answers | wrong | AUROC | false accept |
|
| 251 |
+
|---|---|---|---|---|
|
| 252 |
+
| glm-5.3-flash | 1,266 | 11.8% | 0.910 | 41.6% |
|
| 253 |
+
| qwen3.8-27b | 633 | 12.3% | 0.915 | 50.0% |
|
| 254 |
+
| kimi-vl | 633 | 34.6% | 0.896 | 26.9% |
|
| 255 |
+
| HumanEval | 72 | 9.7% | 0.991 | 14.3% |
|
| 256 |
+
| MATH | 424 | 31.8% | 0.963 | 14.1% |
|
| 257 |
+
| MBPP | 268 | 9.7% | 0.955 | 38.5% |
|
| 258 |
+
| MMLU-Pro | 480 | 29.4% | 0.928 | 34.0% |
|
| 259 |
+
| AQuA-RAT | 316 | 16.8% | 0.896 | 30.2% |
|
| 260 |
+
| GSM8K | 420 | 6.2% | 0.854 | 53.8% |
|
| 261 |
+
| ARC-Challenge | 300 | 4.0% | 0.842 | 91.7% |
|
| 262 |
+
| CommonsenseQA | 252 | 18.3% | 0.593 | 89.1% |
|
| 263 |
+
|
| 264 |
+
Jedi is strongest where correctness can be checked from the answer itself: code, multi-step math and
|
| 265 |
+
knowledge questions with worked reasoning. It is weakest where a wrong answer reads exactly like a right one,
|
| 266 |
+
such as single-hop commonsense and easy science multiple choice.
|
| 267 |
+
|
| 268 |
+
### As the gate of the ladder
|
| 269 |
+
491 held-out tasks: Shaktimaan's test tasks from GSM8K, MATH, MMLU-Pro, ARC, MBPP and HumanEval. GLM and Qwen
|
| 270 |
+
answers are fresh samples; Ultra's outcome and token count come from its graded run in Shaktimaan-routing (same task
|
| 271 |
+
ids). Cost = sum over called agents of (relative cost x output tokens), normalised so always-Ultra = 1.00. *Silent
|
| 272 |
+
failure* = a wrong answer accepted by a lower rung, i.e. a mistake the ladder had a chance to escalate and didn't.
|
| 273 |
+
|
| 274 |
+
| policy | accuracy | relative cost | silent failures | calls / task |
|
| 275 |
+
|---|---|---|---|---|
|
| 276 |
+
| Always `glm-5.3-flash` | 88.8% | 0.115 | - | 1.00 |
|
| 277 |
+
| Always `qwen3.8-27b` | 87.4% | 0.421 | - | 1.00 |
|
| 278 |
+
| Always `nemotron-ultra` | 84.7% | 1.000 | - | 1.00 |
|
| 279 |
+
| Shaktimaan v1.0 routing | 84.9% | 0.323 | - | - |
|
| 280 |
+
| Ladder + format check (escalate truncated / unparseable answers) | 89.8% | 0.406 | 7.3% | 1.07 |
|
| 281 |
+
| Ladder + self-consistency (escalate if two samples disagree) | 90.4% | 0.554 | 6.5% | 1.14 |
|
| 282 |
+
| **Ladder + Jedi, threshold 0.7** | **91.9%** | **0.454** | **2.9%** | 1.15 |
|
| 283 |
+
| Ladder + perfect verifier (oracle, upper bound) | 93.3% | 0.473 | 0.0% | 1.19 |
|
| 284 |
+
|
| 285 |
+
**Threshold sweep** (same 491 tasks):
|
| 286 |
+
|
| 287 |
+
| threshold | accuracy | relative cost | silent failures | calls / task | extra latency / task |
|
| 288 |
+
|---|---|---|---|---|---|
|
| 289 |
+
| 0.05 | 91.0% | 0.244 | 7.5% | 1.09 | 8.9 s |
|
| 290 |
+
| 0.1 | 91.5% | 0.263 | 6.5% | 1.10 | 9.5 s |
|
| 291 |
+
| 0.3 | 91.5% | 0.323 | 5.5% | 1.11 | 10.7 s |
|
| 292 |
+
| 0.5 | 91.7% | 0.387 | 4.5% | 1.12 | 12.1 s |
|
| 293 |
+
| **0.7** | **91.9%** | **0.454** | **2.9%** | 1.15 | 14.3 s |
|
| 294 |
+
| 0.8 | 91.0% | 0.506 | 2.9% | 1.20 | 16.4 s |
|
| 295 |
+
| 0.9 | 90.2% | 0.609 | 1.6% | 1.32 | 20.8 s |
|
| 296 |
+
| 0.95 | 87.0% | 0.891 | 1.4% | 1.65 | 34.5 s |
|
| 297 |
+
|
| 298 |
+
Above 0.7, accuracy *falls* even as silent failures keep dropping. Escalating a correct answer is not free: the
|
| 299 |
+
next rung can get it wrong, and in this pool Ultra's recorded solve rate (84.7%) is below GLM's. Use **0.7** for
|
| 300 |
+
the best accuracy, or **0.1-0.3** for most of the gain at a quarter of always-Ultra's cost.
|
| 301 |
+
|
| 302 |
+
### Outside the training domains (two-rung ladder: GLM -> Qwen)
|
| 303 |
+
Cost is relative to always using Qwen.
|
| 304 |
+
|
| 305 |
+
| tasks | policy | accuracy | cost vs always-Qwen | silent failures |
|
| 306 |
+
|---|---|---|---|---|
|
| 307 |
+
| AQuA + CommonsenseQA test (142) | always GLM / always Qwen | 85.2% / 88.7% | 0.30 / 1.00 | - |
|
| 308 |
+
| | self-consistency | 88.7% | 1.05 | 7.0% |
|
| 309 |
+
| | Jedi @ 0.5 | 88.7% | 0.68 | 8.5% |
|
| 310 |
+
| | Jedi @ 0.7 | 87.3% | 0.72 | 8.5% |
|
| 311 |
+
| | oracle | 92.3% | 0.71 | 0.0% |
|
| 312 |
+
| BIG-Bench Hard, unseen (810) | always GLM / always Qwen | 90.1% / 90.6% | 0.14 / 1.00 | - |
|
| 313 |
+
| | self-consistency | 91.4% | 0.53 | 6.1% |
|
| 314 |
+
| | Jedi @ 0.7 | 91.1% | 0.27 | 7.8% |
|
| 315 |
+
| | Jedi @ 0.9 | 92.0% | 0.69 | 2.2% |
|
| 316 |
+
| | oracle | 94.6% | 0.37 | 0.0% |
|
| 317 |
+
|
| 318 |
+
On unseen task families the ladder still improves on both single models, because the cheap model is usually right
|
| 319 |
+
and Jedi rarely escalates correct answers. It catches far fewer wrong ones than it does in-domain, though. On the
|
| 320 |
+
CommonsenseQA-heavy new-domain set, Jedi at 0.7 does not beat always using Qwen.
|
| 321 |
+
|
| 322 |
+
### Overhead
|
| 323 |
+
~56 ms per check (batch size 1, one MIG 1g.67gb slice of a B300), ~451 input tokens, 1 output token. Checks are a
|
| 324 |
+
single forward pass and batch well.
|
| 325 |
+
|
| 326 |
+
## Limitations
|
| 327 |
+
- **Domain-bound.** Verification skill transfers poorly to unseen task families (BIG-Bench Hard AUROC 0.72) and is
|
| 328 |
+
near chance on CommonsenseQA (0.59). Measure it on your own traffic before relying on a threshold.
|
| 329 |
+
- **A third of wrong in-domain answers get through at 0.5.** False-accept rates are highest for the strongest
|
| 330 |
+
models, whose mistakes are rarer and more fluent. Jedi is a cost-saving filter, not a correctness guarantee.
|
| 331 |
+
- **Pool-specific.** Trained on answers from GLM-5.3-Flash, Qwen3.8-27B and Kimi-VL; the agent name is part of its
|
| 332 |
+
input. Other models' answers are out of distribution, and a new pool needs new data and retraining.
|
| 333 |
+
- **Text and code only**, English only, single-turn tasks with a checkable final answer. Not trained on open-ended
|
| 334 |
+
writing, agentic / tool use, multi-turn or image inputs. Long tasks and answers are truncated (head + tail) for judging.
|
| 335 |
+
- **Labels inherit grading noise** from automatic answer extraction and matching.
|
| 336 |
+
- **Ladder numbers use recorded Ultra outcomes**, a single sample per task from the Shaktimaan-routing runs, not
|
| 337 |
+
fresh calls.
|
| 338 |
+
- **Validation loss rose in epoch 2** (0.095 -> 0.142) while AUROC and calibration stayed good; a one-epoch model
|
| 339 |
+
may be marginally better.
|
| 340 |
+
|
| 341 |
+
## Files
|
| 342 |
+
| file | purpose |
|
| 343 |
+
|---|---|
|
| 344 |
+
| `model.safetensors`, `config.json`, tokenizer files | the verifier |
|
| 345 |
+
| `calibration.json` | Platt scaling `a`, `b` and the `accept` / `escalate` token ids |
|
| 346 |
+
| `common.py` | agent pool, ladder, prompts, `judge_view`, answer extraction and graders |
|
| 347 |
+
| `jedi.py` | end-to-end runtime (answer -> verify -> escalate) |
|
| 348 |
+
| `gen.py` | collects and grades agent answers (stage 1) |
|
| 349 |
+
| `build.py` | builds the accept / escalate dataset and task-level splits (stage 2) |
|
| 350 |
+
| `train.py` | fine-tuning script (stage 3) |
|
| 351 |
+
| `evaluate.py` | verifier metrics, ladder, baselines and threshold sweep (stage 4) |
|
| 352 |
+
| `eval_results.json` | raw evaluation output behind every number on this card |
|
| 353 |
+
|
| 354 |
+
The graded answer data is not published; `gen.py` and `build.py` regenerate it given endpoints for the three models.
|
| 355 |
+
|
| 356 |
+
## License
|
| 357 |
+
Model weights: Apache-2.0 (as the base model). Task sources keep their own licenses (table above).
|
build.py
ADDED
|
@@ -0,0 +1,67 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
"""Stage 2: turn graded answers into Jedi's verifier dataset.
|
| 2 |
+
|
| 3 |
+
One example per (task, agent answer): Jedi reads `judge_view(...)` and must reply `accept` (the answer was graded
|
| 4 |
+
correct) or `escalate` (graded wrong). Splits are by task, never by answer, so no task leaks across splits:
|
| 5 |
+
* test - the in-domain Shaktimaan-routing test tasks (text + code) plus 10% of the new-domain tasks;
|
| 6 |
+
* ood - every BIG-Bench Hard task (never trained on: measures generalisation to unseen task families);
|
| 7 |
+
* val - 6% of the remaining tasks, used only to calibrate confidence (Platt scaling);
|
| 8 |
+
* train - the rest. Wrong answers are the minority, so they are oversampled in train to ~40% of examples.
|
| 9 |
+
Extra samples from the borderline pass (gen.py BORDERLINE=1) are used for train only, so held-out splits keep the
|
| 10 |
+
same, unselected sampling for every task.
|
| 11 |
+
"""
|
| 12 |
+
import json, random
|
| 13 |
+
from collections import Counter
|
| 14 |
+
from datasets import Dataset, DatasetDict, load_dataset
|
| 15 |
+
from common import JEDI_SYSTEM, judge_view
|
| 16 |
+
|
| 17 |
+
random.seed(0)
|
| 18 |
+
WRONG_SHARE = 0.40
|
| 19 |
+
BASE_SAMPLES = {"glm-5.3-flash": 2, "qwen3.8-27b": 1, "kimi-vl": 1}
|
| 20 |
+
|
| 21 |
+
|
| 22 |
+
def main():
|
| 23 |
+
tasks = {t["id"]: t for t in map(json.loads, open("data/tasks.jsonl"))}
|
| 24 |
+
answers = [a for a in map(json.loads, open("data/answers.jsonl")) if not a.get("error") and not a.get("skipped")]
|
| 25 |
+
shak_test = set(load_dataset("podstack/Shaktimaan-routing", split="test")["id"])
|
| 26 |
+
split = {}
|
| 27 |
+
for tid, t in tasks.items():
|
| 28 |
+
if t["source"] == "bbh":
|
| 29 |
+
split[tid] = "ood"
|
| 30 |
+
elif tid in shak_test or (t["source"] in ("aqua", "csqa") and random.random() < 0.10):
|
| 31 |
+
split[tid] = "test"
|
| 32 |
+
else:
|
| 33 |
+
split[tid] = "val" if random.random() < 0.06 else "train"
|
| 34 |
+
rows = {k: [] for k in ("train", "val", "test", "ood")}
|
| 35 |
+
for a in answers:
|
| 36 |
+
t = tasks[a["task_id"]]
|
| 37 |
+
if split[t["id"]] != "train" and a["sample"] >= BASE_SAMPLES.get(a["agent"], 1):
|
| 38 |
+
continue
|
| 39 |
+
verdict = "accept" if a["correct"] else "escalate"
|
| 40 |
+
view = judge_view(t["prompt"], a["text"], a["agent"], t["kind"], a["finish"])
|
| 41 |
+
rows[split[t["id"]]].append(dict(
|
| 42 |
+
task_id=t["id"], source=t["source"], kind=t["kind"], agent=a["agent"], sample=a["sample"],
|
| 43 |
+
correct=bool(a["correct"]), finish=a["finish"] or "", completion_tokens=a["completion_tokens"],
|
| 44 |
+
latency=a["latency"], pred=a["pred"], gold=t["gold"], judge_input=view, verdict=verdict,
|
| 45 |
+
prompt=[{"role": "system", "content": JEDI_SYSTEM}, {"role": "user", "content": view}],
|
| 46 |
+
completion=[{"role": "assistant", "content": verdict}]))
|
| 47 |
+
train = rows["train"]
|
| 48 |
+
wrong = [r for r in train if not r["correct"]]
|
| 49 |
+
need = int(WRONG_SHARE * (len(train) - len(wrong)) / (1 - WRONG_SHARE)) - len(wrong)
|
| 50 |
+
train += random.choices(wrong, k=max(0, need))
|
| 51 |
+
random.shuffle(train)
|
| 52 |
+
dd = DatasetDict({k: Dataset.from_list(v) for k, v in rows.items()})
|
| 53 |
+
dd.save_to_disk("data/jedi_judge")
|
| 54 |
+
print(dd)
|
| 55 |
+
for k, v in rows.items():
|
| 56 |
+
print(f"{k:5s} tasks={len({r['task_id'] for r in v}):5d} answers={len(v):6d} "
|
| 57 |
+
f"wrong={sum(not r['correct'] for r in v) / len(v):.1%} "
|
| 58 |
+
f"agents={dict(Counter(r['agent'] for r in v))}")
|
| 59 |
+
print("solve rate by source x agent (all answers):")
|
| 60 |
+
by = Counter((tasks[a["task_id"]]["source"], a["agent"]) for a in answers)
|
| 61 |
+
ok = Counter((tasks[a["task_id"]]["source"], a["agent"]) for a in answers if a["correct"])
|
| 62 |
+
for src in sorted({s for s, _ in by}):
|
| 63 |
+
print(f" {src:10s} " + " ".join(f"{ag}={ok[src, ag] / by[src, ag]:.2f}" for s, ag in sorted(by) if s == src))
|
| 64 |
+
|
| 65 |
+
|
| 66 |
+
if __name__ == "__main__":
|
| 67 |
+
main()
|
calibration.json
ADDED
|
@@ -0,0 +1,6 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
{
|
| 2 |
+
"a": 0.32936498522758484,
|
| 3 |
+
"b": 0.13781841099262238,
|
| 4 |
+
"accept_token": 10330,
|
| 5 |
+
"escalate_token": 81082
|
| 6 |
+
}
|
chat_template.jinja
ADDED
|
@@ -0,0 +1,54 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
{%- if tools %}
|
| 2 |
+
{{- '<|im_start|>system\n' }}
|
| 3 |
+
{%- if messages[0]['role'] == 'system' %}
|
| 4 |
+
{{- messages[0]['content'] }}
|
| 5 |
+
{%- else %}
|
| 6 |
+
{{- 'You are Qwen, created by Alibaba Cloud. You are a helpful assistant.' }}
|
| 7 |
+
{%- endif %}
|
| 8 |
+
{{- "\n\n# Tools\n\nYou may call one or more functions to assist with the user query.\n\nYou are provided with function signatures within <tools></tools> XML tags:\n<tools>" }}
|
| 9 |
+
{%- for tool in tools %}
|
| 10 |
+
{{- "\n" }}
|
| 11 |
+
{{- tool | tojson }}
|
| 12 |
+
{%- endfor %}
|
| 13 |
+
{{- "\n</tools>\n\nFor each function call, return a json object with function name and arguments within <tool_call></tool_call> XML tags:\n<tool_call>\n{\"name\": <function-name>, \"arguments\": <args-json-object>}\n</tool_call><|im_end|>\n" }}
|
| 14 |
+
{%- else %}
|
| 15 |
+
{%- if messages[0]['role'] == 'system' %}
|
| 16 |
+
{{- '<|im_start|>system\n' + messages[0]['content'] + '<|im_end|>\n' }}
|
| 17 |
+
{%- else %}
|
| 18 |
+
{{- '<|im_start|>system\nYou are Qwen, created by Alibaba Cloud. You are a helpful assistant.<|im_end|>\n' }}
|
| 19 |
+
{%- endif %}
|
| 20 |
+
{%- endif %}
|
| 21 |
+
{%- for message in messages %}
|
| 22 |
+
{%- if (message.role == "user") or (message.role == "system" and not loop.first) or (message.role == "assistant" and not message.tool_calls) %}
|
| 23 |
+
{{- '<|im_start|>' + message.role + '\n' + message.content + '<|im_end|>' + '\n' }}
|
| 24 |
+
{%- elif message.role == "assistant" %}
|
| 25 |
+
{{- '<|im_start|>' + message.role }}
|
| 26 |
+
{%- if message.content %}
|
| 27 |
+
{{- '\n' + message.content }}
|
| 28 |
+
{%- endif %}
|
| 29 |
+
{%- for tool_call in message.tool_calls %}
|
| 30 |
+
{%- if tool_call.function is defined %}
|
| 31 |
+
{%- set tool_call = tool_call.function %}
|
| 32 |
+
{%- endif %}
|
| 33 |
+
{{- '\n<tool_call>\n{"name": "' }}
|
| 34 |
+
{{- tool_call.name }}
|
| 35 |
+
{{- '", "arguments": ' }}
|
| 36 |
+
{{- tool_call.arguments | tojson }}
|
| 37 |
+
{{- '}\n</tool_call>' }}
|
| 38 |
+
{%- endfor %}
|
| 39 |
+
{{- '<|im_end|>\n' }}
|
| 40 |
+
{%- elif message.role == "tool" %}
|
| 41 |
+
{%- if (loop.index0 == 0) or (messages[loop.index0 - 1].role != "tool") %}
|
| 42 |
+
{{- '<|im_start|>user' }}
|
| 43 |
+
{%- endif %}
|
| 44 |
+
{{- '\n<tool_response>\n' }}
|
| 45 |
+
{{- message.content }}
|
| 46 |
+
{{- '\n</tool_response>' }}
|
| 47 |
+
{%- if loop.last or (messages[loop.index0 + 1].role != "tool") %}
|
| 48 |
+
{{- '<|im_end|>\n' }}
|
| 49 |
+
{%- endif %}
|
| 50 |
+
{%- endif %}
|
| 51 |
+
{%- endfor %}
|
| 52 |
+
{%- if add_generation_prompt %}
|
| 53 |
+
{{- '<|im_start|>assistant\n' }}
|
| 54 |
+
{%- endif %}
|
common.py
ADDED
|
@@ -0,0 +1,165 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
"""Shared config for Jedi: the agent pool, the escalation ladder, prompts, capabilities, graders and the judge view."""
|
| 2 |
+
import json, os, re
|
| 3 |
+
|
| 4 |
+
# agent -> served model id, max concurrent requests we send, relative cost per token
|
| 5 |
+
POOL = {
|
| 6 |
+
"gemma-3-1b": dict(model="google/gemma-3-1b-it", concurrency=32, cost=0.02),
|
| 7 |
+
"kimi-vl": dict(model="Kimi-VL-A3B-Thinking", concurrency=64, cost=0.05),
|
| 8 |
+
"nemotron-lightning": dict(model="nvidia/NVIDIA-Nemotron-3.5-Lightning-30B-A3B-NVFP4", concurrency=64, cost=0.10),
|
| 9 |
+
"glm-5.3-flash": dict(model="zai-org/GLM-5.3-Flash", concurrency=32, cost=0.20),
|
| 10 |
+
"qwen3.8-27b": dict(model="Qwen/Qwen3.8-27B", concurrency=12, cost=0.50),
|
| 11 |
+
"nemotron-ultra": dict(model="nvidia/NVIDIA-Nemotron-3-Ultra-550B-A55B-NVFP4", concurrency=64, cost=1.00),
|
| 12 |
+
}
|
| 13 |
+
AGENTS = list(POOL) # ordered cheapest -> most expensive
|
| 14 |
+
STRONGEST = "nemotron-ultra"
|
| 15 |
+
CONTEXT = {"gemma-3-1b": 32768, "kimi-vl": 8192, "nemotron-lightning": 131072, "glm-5.3-flash": 32768,
|
| 16 |
+
"qwen3.8-27b": 32768, "nemotron-ultra": 262144}
|
| 17 |
+
VISION = {"kimi-vl"} # only agent served with image input enabled
|
| 18 |
+
MAX_OUT = 8192
|
| 19 |
+
|
| 20 |
+
|
| 21 |
+
LADDER = ["glm-5.3-flash", "qwen3.8-27b", "nemotron-ultra"] # cheap -> mid -> strongest; Jedi checks each rung
|
| 22 |
+
|
| 23 |
+
|
| 24 |
+
def endpoints() -> dict:
|
| 25 |
+
"""Agent -> OpenAI-compatible base URL (".../v1"), from $JEDI_ENDPOINTS: inline JSON or a path to a JSON file."""
|
| 26 |
+
raw = os.environ["JEDI_ENDPOINTS"]
|
| 27 |
+
return json.load(open(raw)) if os.path.exists(raw) else json.loads(raw)
|
| 28 |
+
|
| 29 |
+
SOLVER_SYSTEM = ("Solve the task. Be concise. End your response with a final line exactly of the form:\n"
|
| 30 |
+
"Final answer: <answer>\nFor multiple-choice questions, the answer is the option letter only.")
|
| 31 |
+
CODE_SYSTEM = ("Write a correct, self-contained Python solution. Put the complete code (imports, required function "
|
| 32 |
+
"names and signatures) in a single ```python code block. Do not include tests or example usage.")
|
| 33 |
+
|
| 34 |
+
JEDI_SYSTEM = (
|
| 35 |
+
"You are Jedi, a verifier. You are shown a task and a candidate answer written by an AI agent. Do not solve "
|
| 36 |
+
"the task yourself. Judge whether the candidate's final answer is correct: look for reasoning that does not hold "
|
| 37 |
+
"up, arithmetic that does not add up, code that would fail its tests, hedging, truncation or a wrong answer "
|
| 38 |
+
"format. Reply with one word: accept if the answer is correct, escalate if it is likely wrong."
|
| 39 |
+
)
|
| 40 |
+
VERDICTS = ("accept", "escalate")
|
| 41 |
+
|
| 42 |
+
|
| 43 |
+
def est_tokens(text: str) -> int:
|
| 44 |
+
"""Conservative token estimate (real tokenizers average ~3.5-4 chars/token on English)."""
|
| 45 |
+
return len(text) // 3 + 1
|
| 46 |
+
|
| 47 |
+
|
| 48 |
+
def max_out(agent: str, prompt_tokens: int) -> int:
|
| 49 |
+
return min(MAX_OUT, CONTEXT[agent] - prompt_tokens - 256)
|
| 50 |
+
|
| 51 |
+
|
| 52 |
+
def capable(agent: str, prompt: str, has_image: bool) -> bool:
|
| 53 |
+
if has_image and agent not in VISION:
|
| 54 |
+
return False
|
| 55 |
+
return max_out(agent, est_tokens(prompt) + 1500 * has_image) >= 1024
|
| 56 |
+
|
| 57 |
+
|
| 58 |
+
def truncate(prompt: str, head: int = 5000, tail: int = 1500) -> str:
|
| 59 |
+
return prompt if len(prompt) <= head + tail + 7 else prompt[:head] + "\n[...]\n" + prompt[-tail:]
|
| 60 |
+
|
| 61 |
+
|
| 62 |
+
def judge_view(prompt: str, answer: str, agent: str, kind: str, finish: str | None) -> str:
|
| 63 |
+
"""What Jedi reads: who answered and how the answer ended, then the task and the visible answer (head + tail)."""
|
| 64 |
+
answer = strip_think(answer)
|
| 65 |
+
head = f"[agent: {agent}; task type: {kind}; answer ~{est_tokens(answer)} tokens" + \
|
| 66 |
+
("; answer was cut off at the token limit" if finish == "length" else "") + "]"
|
| 67 |
+
return f"{head}\n### Task\n{truncate(prompt, 2500, 1000)}\n\n### Candidate answer\n{truncate(answer, 1500, 2500)}"
|
| 68 |
+
|
| 69 |
+
|
| 70 |
+
def strip_think(text: str) -> str:
|
| 71 |
+
text = text or ""
|
| 72 |
+
if "</think>" in text:
|
| 73 |
+
text = text.rsplit("</think>", 1)[1]
|
| 74 |
+
text = re.sub(r"◁think▷.*?◁/think▷", "", text, flags=re.S) # Kimi-VL thinking markers
|
| 75 |
+
return text.strip()
|
| 76 |
+
|
| 77 |
+
|
| 78 |
+
def extract_final(text: str) -> str:
|
| 79 |
+
text = strip_think(text)
|
| 80 |
+
m = re.findall(r"final answer\s*[::]\s*(.+)", text, flags=re.I)
|
| 81 |
+
if m:
|
| 82 |
+
return m[-1].strip()
|
| 83 |
+
m = re.findall(r"^\s*answer\s*[::]\s*(.+)", text, flags=re.I | re.M)
|
| 84 |
+
if m:
|
| 85 |
+
return m[-1].strip()
|
| 86 |
+
boxed = last_boxed(text)
|
| 87 |
+
if boxed is not None:
|
| 88 |
+
return boxed
|
| 89 |
+
lines = [l for l in text.splitlines() if l.strip()]
|
| 90 |
+
return lines[-1].strip() if lines else ""
|
| 91 |
+
|
| 92 |
+
|
| 93 |
+
def extract_code(text: str) -> str:
|
| 94 |
+
text = strip_think(text)
|
| 95 |
+
blocks = re.findall(r"```(?:python|py)?\s*\n(.*?)```", text, flags=re.S)
|
| 96 |
+
return blocks[-1] if blocks else ""
|
| 97 |
+
|
| 98 |
+
|
| 99 |
+
def last_boxed(s: str):
|
| 100 |
+
i = s.rfind("\\boxed{")
|
| 101 |
+
if i < 0:
|
| 102 |
+
return None
|
| 103 |
+
i += len("\\boxed{")
|
| 104 |
+
depth, j = 1, i
|
| 105 |
+
while j < len(s) and depth:
|
| 106 |
+
depth += {"{": 1, "}": -1}.get(s[j], 0)
|
| 107 |
+
j += 1
|
| 108 |
+
return s[i:j - 1] if depth == 0 else None
|
| 109 |
+
|
| 110 |
+
|
| 111 |
+
def _norm_math(s: str) -> str:
|
| 112 |
+
s = s.strip().strip("$").strip()
|
| 113 |
+
b = last_boxed(s)
|
| 114 |
+
if b is not None:
|
| 115 |
+
s = b
|
| 116 |
+
for a, b2 in [("\\left", ""), ("\\right", ""), ("\\!", ""), ("\\,", ""), ("\\;", ""), ("dfrac", "frac"),
|
| 117 |
+
("tfrac", "frac"), ("^\\circ", ""), ("^{\\circ}", ""), ("\\%", ""), ("%", ""), ("\\text{", "{"),
|
| 118 |
+
("\\mbox{", "{"), (" ", ""), ("\\$", "")]:
|
| 119 |
+
s = s.replace(a, b2)
|
| 120 |
+
s = re.sub(r"\\frac(\d)(\d)", r"\\frac{\1}{\2}", s)
|
| 121 |
+
s = s.rstrip(".")
|
| 122 |
+
if s.startswith("{") and s.endswith("}") and s.count("{") == 1:
|
| 123 |
+
s = s[1:-1]
|
| 124 |
+
return s
|
| 125 |
+
|
| 126 |
+
|
| 127 |
+
def _num(s: str):
|
| 128 |
+
s = s.replace(",", "").replace("$", "").strip()
|
| 129 |
+
m = re.findall(r"-?\d+(?:\.\d+)?", s)
|
| 130 |
+
return float(m[-1]) if m else None
|
| 131 |
+
|
| 132 |
+
|
| 133 |
+
def grade(kind: str, pred: str, gold: str) -> bool:
|
| 134 |
+
"""Grades extracted answers. Code is graded by execution (see gen.run_tests), not here."""
|
| 135 |
+
if not pred:
|
| 136 |
+
return False
|
| 137 |
+
if kind == "mc":
|
| 138 |
+
m = re.search(r"\b([A-J])\b", pred.upper().replace("(", " ").replace(")", " "))
|
| 139 |
+
return bool(m) and m.group(1) == gold.upper()
|
| 140 |
+
if kind == "num":
|
| 141 |
+
p, g = _num(pred), _num(gold)
|
| 142 |
+
return p is not None and g is not None and abs(p - g) < 1e-6 * max(1, abs(g))
|
| 143 |
+
if kind == "relaxed": # ChartQA convention: numbers within 5%, otherwise case-insensitive exact match
|
| 144 |
+
pred = last_boxed(pred) or pred
|
| 145 |
+
pred = re.sub(r"\\(?:mathbf|textbf|text|mathrm)\{([^}]*)\}", r"\1", pred).replace("\\(", "").replace("\\)", "")
|
| 146 |
+
p, g = _num(pred), _num(gold)
|
| 147 |
+
if g is not None and p is not None and re.fullmatch(r"[-\d.,%$ ]+", gold.strip()):
|
| 148 |
+
return abs(p - g) <= 0.05 * abs(g) if g else p == 0
|
| 149 |
+
return pred.strip().strip(".").lower() == gold.strip().lower()
|
| 150 |
+
if kind == "math":
|
| 151 |
+
p, g = _norm_math(pred), _norm_math(gold)
|
| 152 |
+
if p == g:
|
| 153 |
+
return True
|
| 154 |
+
try:
|
| 155 |
+
return abs(float(p) - float(g)) < 1e-6
|
| 156 |
+
except ValueError:
|
| 157 |
+
return False
|
| 158 |
+
if kind == "exact": # BIG-Bench Hard targets: an option like "(B)", or a short string / number / list
|
| 159 |
+
norm = lambda x: re.sub(r"\s+", " ", x.strip().strip("*").strip().rstrip(".").lower())
|
| 160 |
+
p, g = norm(pred), norm(gold)
|
| 161 |
+
if re.fullmatch(r"\([a-r]\)", g):
|
| 162 |
+
m = re.match(r"^\(?([a-r])\)?(?:\W|$)", p)
|
| 163 |
+
return bool(m) and m.group(1) == g[1]
|
| 164 |
+
return p == g
|
| 165 |
+
raise ValueError(kind)
|
config.json
ADDED
|
@@ -0,0 +1,57 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
{
|
| 2 |
+
"architectures": [
|
| 3 |
+
"Qwen2ForCausalLM"
|
| 4 |
+
],
|
| 5 |
+
"attention_dropout": 0.0,
|
| 6 |
+
"bos_token_id": 151643,
|
| 7 |
+
"dtype": "float32",
|
| 8 |
+
"eos_token_id": 151645,
|
| 9 |
+
"hidden_act": "silu",
|
| 10 |
+
"hidden_size": 896,
|
| 11 |
+
"initializer_range": 0.02,
|
| 12 |
+
"intermediate_size": 4864,
|
| 13 |
+
"layer_types": [
|
| 14 |
+
"full_attention",
|
| 15 |
+
"full_attention",
|
| 16 |
+
"full_attention",
|
| 17 |
+
"full_attention",
|
| 18 |
+
"full_attention",
|
| 19 |
+
"full_attention",
|
| 20 |
+
"full_attention",
|
| 21 |
+
"full_attention",
|
| 22 |
+
"full_attention",
|
| 23 |
+
"full_attention",
|
| 24 |
+
"full_attention",
|
| 25 |
+
"full_attention",
|
| 26 |
+
"full_attention",
|
| 27 |
+
"full_attention",
|
| 28 |
+
"full_attention",
|
| 29 |
+
"full_attention",
|
| 30 |
+
"full_attention",
|
| 31 |
+
"full_attention",
|
| 32 |
+
"full_attention",
|
| 33 |
+
"full_attention",
|
| 34 |
+
"full_attention",
|
| 35 |
+
"full_attention",
|
| 36 |
+
"full_attention",
|
| 37 |
+
"full_attention"
|
| 38 |
+
],
|
| 39 |
+
"max_position_embeddings": 32768,
|
| 40 |
+
"max_window_layers": 21,
|
| 41 |
+
"model_type": "qwen2",
|
| 42 |
+
"num_attention_heads": 14,
|
| 43 |
+
"num_hidden_layers": 24,
|
| 44 |
+
"num_key_value_heads": 2,
|
| 45 |
+
"pad_token_id": 151643,
|
| 46 |
+
"rms_norm_eps": 1e-06,
|
| 47 |
+
"rope_parameters": {
|
| 48 |
+
"rope_theta": 1000000.0,
|
| 49 |
+
"rope_type": "default"
|
| 50 |
+
},
|
| 51 |
+
"sliding_window": null,
|
| 52 |
+
"tie_word_embeddings": true,
|
| 53 |
+
"transformers_version": "5.19.0",
|
| 54 |
+
"use_cache": false,
|
| 55 |
+
"use_sliding_window": false,
|
| 56 |
+
"vocab_size": 151936
|
| 57 |
+
}
|
eval_results.json
ADDED
|
@@ -0,0 +1,709 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
{
|
| 2 |
+
"calibration": {
|
| 3 |
+
"a": 0.32936498522758484,
|
| 4 |
+
"b": 0.13781841099262238,
|
| 5 |
+
"accept_token": 10330,
|
| 6 |
+
"escalate_token": 81082
|
| 7 |
+
},
|
| 8 |
+
"verifier": {
|
| 9 |
+
"val": {
|
| 10 |
+
"all": {
|
| 11 |
+
"n": 1480,
|
| 12 |
+
"answers_wrong": 0.1757,
|
| 13 |
+
"auroc": 0.8839,
|
| 14 |
+
"false_accept_rate": 0.4231,
|
| 15 |
+
"false_escalate_rate": 0.0131,
|
| 16 |
+
"ece": 0.0173,
|
| 17 |
+
"brier": 0.0723
|
| 18 |
+
},
|
| 19 |
+
"uncalibrated_ece": 0.0724,
|
| 20 |
+
"glm-5.3-flash": {
|
| 21 |
+
"n": 740,
|
| 22 |
+
"answers_wrong": 0.123,
|
| 23 |
+
"auroc": 0.8637,
|
| 24 |
+
"false_accept_rate": 0.5604,
|
| 25 |
+
"false_escalate_rate": 0.0046,
|
| 26 |
+
"ece": 0.0223,
|
| 27 |
+
"brier": 0.0629
|
| 28 |
+
},
|
| 29 |
+
"kimi-vl": {
|
| 30 |
+
"n": 370,
|
| 31 |
+
"answers_wrong": 0.3243,
|
| 32 |
+
"auroc": 0.9005,
|
| 33 |
+
"false_accept_rate": 0.2583,
|
| 34 |
+
"false_escalate_rate": 0.04,
|
| 35 |
+
"ece": 0.0632,
|
| 36 |
+
"brier": 0.0928
|
| 37 |
+
},
|
| 38 |
+
"qwen3.8-27b": {
|
| 39 |
+
"n": 370,
|
| 40 |
+
"answers_wrong": 0.1324,
|
| 41 |
+
"auroc": 0.8666,
|
| 42 |
+
"false_accept_rate": 0.5714,
|
| 43 |
+
"false_escalate_rate": 0.0093,
|
| 44 |
+
"ece": 0.0299,
|
| 45 |
+
"brier": 0.0704
|
| 46 |
+
},
|
| 47 |
+
"source:aqua": {
|
| 48 |
+
"n": 212,
|
| 49 |
+
"answers_wrong": 0.184,
|
| 50 |
+
"auroc": 0.8519,
|
| 51 |
+
"false_accept_rate": 0.4359,
|
| 52 |
+
"false_escalate_rate": 0.0058,
|
| 53 |
+
"ece": 0.0353,
|
| 54 |
+
"brier": 0.0735
|
| 55 |
+
},
|
| 56 |
+
"source:arc": {
|
| 57 |
+
"n": 160,
|
| 58 |
+
"answers_wrong": 0.0625,
|
| 59 |
+
"auroc": 0.742,
|
| 60 |
+
"false_accept_rate": 1.0,
|
| 61 |
+
"false_escalate_rate": 0.0067,
|
| 62 |
+
"ece": 0.0419,
|
| 63 |
+
"brier": 0.0556
|
| 64 |
+
},
|
| 65 |
+
"source:csqa": {
|
| 66 |
+
"n": 116,
|
| 67 |
+
"answers_wrong": 0.25,
|
| 68 |
+
"auroc": 0.4935,
|
| 69 |
+
"false_accept_rate": 0.931,
|
| 70 |
+
"false_escalate_rate": 0.023,
|
| 71 |
+
"ece": 0.1432,
|
| 72 |
+
"brier": 0.203
|
| 73 |
+
},
|
| 74 |
+
"source:gsm8k": {
|
| 75 |
+
"n": 264,
|
| 76 |
+
"answers_wrong": 0.0417,
|
| 77 |
+
"auroc": 0.9677,
|
| 78 |
+
"false_accept_rate": 0.2727,
|
| 79 |
+
"false_escalate_rate": 0.004,
|
| 80 |
+
"ece": 0.0499,
|
| 81 |
+
"brier": 0.0147
|
| 82 |
+
},
|
| 83 |
+
"source:humaneval": {
|
| 84 |
+
"n": 36,
|
| 85 |
+
"answers_wrong": 0.0278,
|
| 86 |
+
"auroc": 1.0,
|
| 87 |
+
"false_accept_rate": 0.0,
|
| 88 |
+
"false_escalate_rate": 0.0,
|
| 89 |
+
"ece": 0.0512,
|
| 90 |
+
"brier": 0.0055
|
| 91 |
+
},
|
| 92 |
+
"source:math": {
|
| 93 |
+
"n": 248,
|
| 94 |
+
"answers_wrong": 0.3024,
|
| 95 |
+
"auroc": 0.9272,
|
| 96 |
+
"false_accept_rate": 0.1467,
|
| 97 |
+
"false_escalate_rate": 0.0173,
|
| 98 |
+
"ece": 0.0534,
|
| 99 |
+
"brier": 0.0539
|
| 100 |
+
},
|
| 101 |
+
"source:mbpp": {
|
| 102 |
+
"n": 168,
|
| 103 |
+
"answers_wrong": 0.131,
|
| 104 |
+
"auroc": 0.9692,
|
| 105 |
+
"false_accept_rate": 0.2273,
|
| 106 |
+
"false_escalate_rate": 0.0274,
|
| 107 |
+
"ece": 0.0483,
|
| 108 |
+
"brier": 0.0449
|
| 109 |
+
},
|
| 110 |
+
"source:mmlu_pro": {
|
| 111 |
+
"n": 276,
|
| 112 |
+
"answers_wrong": 0.2645,
|
| 113 |
+
"auroc": 0.8385,
|
| 114 |
+
"false_accept_rate": 0.5068,
|
| 115 |
+
"false_escalate_rate": 0.0197,
|
| 116 |
+
"ece": 0.0517,
|
| 117 |
+
"brier": 0.1229
|
| 118 |
+
},
|
| 119 |
+
"reliability": [
|
| 120 |
+
{
|
| 121 |
+
"bin": "0.0-0.1",
|
| 122 |
+
"n": 104,
|
| 123 |
+
"confidence": 0.029,
|
| 124 |
+
"accuracy": 0.0
|
| 125 |
+
},
|
| 126 |
+
{
|
| 127 |
+
"bin": "0.1-0.2",
|
| 128 |
+
"n": 20,
|
| 129 |
+
"confidence": 0.13,
|
| 130 |
+
"accuracy": 0.1
|
| 131 |
+
},
|
| 132 |
+
{
|
| 133 |
+
"bin": "0.2-0.3",
|
| 134 |
+
"n": 17,
|
| 135 |
+
"confidence": 0.263,
|
| 136 |
+
"accuracy": 0.176
|
| 137 |
+
},
|
| 138 |
+
{
|
| 139 |
+
"bin": "0.3-0.4",
|
| 140 |
+
"n": 11,
|
| 141 |
+
"confidence": 0.355,
|
| 142 |
+
"accuracy": 0.273
|
| 143 |
+
},
|
| 144 |
+
{
|
| 145 |
+
"bin": "0.4-0.5",
|
| 146 |
+
"n": 14,
|
| 147 |
+
"confidence": 0.464,
|
| 148 |
+
"accuracy": 0.571
|
| 149 |
+
},
|
| 150 |
+
{
|
| 151 |
+
"bin": "0.5-0.6",
|
| 152 |
+
"n": 23,
|
| 153 |
+
"confidence": 0.563,
|
| 154 |
+
"accuracy": 0.696
|
| 155 |
+
},
|
| 156 |
+
{
|
| 157 |
+
"bin": "0.6-0.7",
|
| 158 |
+
"n": 36,
|
| 159 |
+
"confidence": 0.66,
|
| 160 |
+
"accuracy": 0.778
|
| 161 |
+
},
|
| 162 |
+
{
|
| 163 |
+
"bin": "0.7-0.8",
|
| 164 |
+
"n": 84,
|
| 165 |
+
"confidence": 0.762,
|
| 166 |
+
"accuracy": 0.81
|
| 167 |
+
},
|
| 168 |
+
{
|
| 169 |
+
"bin": "0.8-0.9",
|
| 170 |
+
"n": 252,
|
| 171 |
+
"confidence": 0.858,
|
| 172 |
+
"accuracy": 0.857
|
| 173 |
+
},
|
| 174 |
+
{
|
| 175 |
+
"bin": "0.9-1.0",
|
| 176 |
+
"n": 919,
|
| 177 |
+
"confidence": 0.96,
|
| 178 |
+
"accuracy": 0.953
|
| 179 |
+
}
|
| 180 |
+
]
|
| 181 |
+
},
|
| 182 |
+
"test": {
|
| 183 |
+
"all": {
|
| 184 |
+
"n": 2532,
|
| 185 |
+
"answers_wrong": 0.1761,
|
| 186 |
+
"auroc": 0.9195,
|
| 187 |
+
"false_accept_rate": 0.3587,
|
| 188 |
+
"false_escalate_rate": 0.0187,
|
| 189 |
+
"ece": 0.017,
|
| 190 |
+
"brier": 0.0631
|
| 191 |
+
},
|
| 192 |
+
"uncalibrated_ece": 0.0614,
|
| 193 |
+
"glm-5.3-flash": {
|
| 194 |
+
"n": 1266,
|
| 195 |
+
"answers_wrong": 0.1177,
|
| 196 |
+
"auroc": 0.9102,
|
| 197 |
+
"false_accept_rate": 0.4161,
|
| 198 |
+
"false_escalate_rate": 0.0063,
|
| 199 |
+
"ece": 0.0106,
|
| 200 |
+
"brier": 0.0458
|
| 201 |
+
},
|
| 202 |
+
"kimi-vl": {
|
| 203 |
+
"n": 633,
|
| 204 |
+
"answers_wrong": 0.346,
|
| 205 |
+
"auroc": 0.896,
|
| 206 |
+
"false_accept_rate": 0.2694,
|
| 207 |
+
"false_escalate_rate": 0.0507,
|
| 208 |
+
"ece": 0.0418,
|
| 209 |
+
"brier": 0.1013
|
| 210 |
+
},
|
| 211 |
+
"qwen3.8-27b": {
|
| 212 |
+
"n": 633,
|
| 213 |
+
"answers_wrong": 0.1232,
|
| 214 |
+
"auroc": 0.9152,
|
| 215 |
+
"false_accept_rate": 0.5,
|
| 216 |
+
"false_escalate_rate": 0.0198,
|
| 217 |
+
"ece": 0.0248,
|
| 218 |
+
"brier": 0.0595
|
| 219 |
+
},
|
| 220 |
+
"source:aqua": {
|
| 221 |
+
"n": 316,
|
| 222 |
+
"answers_wrong": 0.1677,
|
| 223 |
+
"auroc": 0.8961,
|
| 224 |
+
"false_accept_rate": 0.3019,
|
| 225 |
+
"false_escalate_rate": 0.0304,
|
| 226 |
+
"ece": 0.0288,
|
| 227 |
+
"brier": 0.0627
|
| 228 |
+
},
|
| 229 |
+
"source:arc": {
|
| 230 |
+
"n": 300,
|
| 231 |
+
"answers_wrong": 0.04,
|
| 232 |
+
"auroc": 0.8423,
|
| 233 |
+
"false_accept_rate": 0.9167,
|
| 234 |
+
"false_escalate_rate": 0.0069,
|
| 235 |
+
"ece": 0.0286,
|
| 236 |
+
"brier": 0.0392
|
| 237 |
+
},
|
| 238 |
+
"source:csqa": {
|
| 239 |
+
"n": 252,
|
| 240 |
+
"answers_wrong": 0.1825,
|
| 241 |
+
"auroc": 0.5933,
|
| 242 |
+
"false_accept_rate": 0.8913,
|
| 243 |
+
"false_escalate_rate": 0.0583,
|
| 244 |
+
"ece": 0.087,
|
| 245 |
+
"brier": 0.1548
|
| 246 |
+
},
|
| 247 |
+
"source:gsm8k": {
|
| 248 |
+
"n": 420,
|
| 249 |
+
"answers_wrong": 0.0619,
|
| 250 |
+
"auroc": 0.8544,
|
| 251 |
+
"false_accept_rate": 0.5385,
|
| 252 |
+
"false_escalate_rate": 0.0025,
|
| 253 |
+
"ece": 0.0175,
|
| 254 |
+
"brier": 0.0335
|
| 255 |
+
},
|
| 256 |
+
"source:humaneval": {
|
| 257 |
+
"n": 72,
|
| 258 |
+
"answers_wrong": 0.0972,
|
| 259 |
+
"auroc": 0.9912,
|
| 260 |
+
"false_accept_rate": 0.1429,
|
| 261 |
+
"false_escalate_rate": 0.0,
|
| 262 |
+
"ece": 0.0366,
|
| 263 |
+
"brier": 0.0148
|
| 264 |
+
},
|
| 265 |
+
"source:math": {
|
| 266 |
+
"n": 424,
|
| 267 |
+
"answers_wrong": 0.3184,
|
| 268 |
+
"auroc": 0.9625,
|
| 269 |
+
"false_accept_rate": 0.1407,
|
| 270 |
+
"false_escalate_rate": 0.0035,
|
| 271 |
+
"ece": 0.033,
|
| 272 |
+
"brier": 0.0427
|
| 273 |
+
},
|
| 274 |
+
"source:mbpp": {
|
| 275 |
+
"n": 268,
|
| 276 |
+
"answers_wrong": 0.097,
|
| 277 |
+
"auroc": 0.9545,
|
| 278 |
+
"false_accept_rate": 0.3846,
|
| 279 |
+
"false_escalate_rate": 0.0124,
|
| 280 |
+
"ece": 0.041,
|
| 281 |
+
"brier": 0.0411
|
| 282 |
+
},
|
| 283 |
+
"source:mmlu_pro": {
|
| 284 |
+
"n": 480,
|
| 285 |
+
"answers_wrong": 0.2937,
|
| 286 |
+
"auroc": 0.9275,
|
| 287 |
+
"false_accept_rate": 0.3404,
|
| 288 |
+
"false_escalate_rate": 0.0354,
|
| 289 |
+
"ece": 0.0448,
|
| 290 |
+
"brier": 0.0935
|
| 291 |
+
},
|
| 292 |
+
"reliability": [
|
| 293 |
+
{
|
| 294 |
+
"bin": "0.0-0.1",
|
| 295 |
+
"n": 204,
|
| 296 |
+
"confidence": 0.03,
|
| 297 |
+
"accuracy": 0.005
|
| 298 |
+
},
|
| 299 |
+
{
|
| 300 |
+
"bin": "0.1-0.2",
|
| 301 |
+
"n": 32,
|
| 302 |
+
"confidence": 0.143,
|
| 303 |
+
"accuracy": 0.156
|
| 304 |
+
},
|
| 305 |
+
{
|
| 306 |
+
"bin": "0.2-0.3",
|
| 307 |
+
"n": 40,
|
| 308 |
+
"confidence": 0.248,
|
| 309 |
+
"accuracy": 0.175
|
| 310 |
+
},
|
| 311 |
+
{
|
| 312 |
+
"bin": "0.3-0.4",
|
| 313 |
+
"n": 23,
|
| 314 |
+
"confidence": 0.359,
|
| 315 |
+
"accuracy": 0.478
|
| 316 |
+
},
|
| 317 |
+
{
|
| 318 |
+
"bin": "0.4-0.5",
|
| 319 |
+
"n": 26,
|
| 320 |
+
"confidence": 0.454,
|
| 321 |
+
"accuracy": 0.577
|
| 322 |
+
},
|
| 323 |
+
{
|
| 324 |
+
"bin": "0.5-0.6",
|
| 325 |
+
"n": 33,
|
| 326 |
+
"confidence": 0.553,
|
| 327 |
+
"accuracy": 0.636
|
| 328 |
+
},
|
| 329 |
+
{
|
| 330 |
+
"bin": "0.6-0.7",
|
| 331 |
+
"n": 71,
|
| 332 |
+
"confidence": 0.657,
|
| 333 |
+
"accuracy": 0.746
|
| 334 |
+
},
|
| 335 |
+
{
|
| 336 |
+
"bin": "0.7-0.8",
|
| 337 |
+
"n": 153,
|
| 338 |
+
"confidence": 0.758,
|
| 339 |
+
"accuracy": 0.771
|
| 340 |
+
},
|
| 341 |
+
{
|
| 342 |
+
"bin": "0.8-0.9",
|
| 343 |
+
"n": 360,
|
| 344 |
+
"confidence": 0.86,
|
| 345 |
+
"accuracy": 0.878
|
| 346 |
+
},
|
| 347 |
+
{
|
| 348 |
+
"bin": "0.9-1.0",
|
| 349 |
+
"n": 1590,
|
| 350 |
+
"confidence": 0.961,
|
| 351 |
+
"accuracy": 0.968
|
| 352 |
+
}
|
| 353 |
+
]
|
| 354 |
+
},
|
| 355 |
+
"ood": {
|
| 356 |
+
"all": {
|
| 357 |
+
"n": 3240,
|
| 358 |
+
"answers_wrong": 0.1877,
|
| 359 |
+
"auroc": 0.72,
|
| 360 |
+
"false_accept_rate": 0.7023,
|
| 361 |
+
"false_escalate_rate": 0.0672,
|
| 362 |
+
"ece": 0.0602,
|
| 363 |
+
"brier": 0.1398
|
| 364 |
+
},
|
| 365 |
+
"uncalibrated_ece": 0.1358,
|
| 366 |
+
"glm-5.3-flash": {
|
| 367 |
+
"n": 1620,
|
| 368 |
+
"answers_wrong": 0.0963,
|
| 369 |
+
"auroc": 0.6469,
|
| 370 |
+
"false_accept_rate": 0.9487,
|
| 371 |
+
"false_escalate_rate": 0.0342,
|
| 372 |
+
"ece": 0.0679,
|
| 373 |
+
"brier": 0.1001
|
| 374 |
+
},
|
| 375 |
+
"kimi-vl": {
|
| 376 |
+
"n": 810,
|
| 377 |
+
"answers_wrong": 0.4642,
|
| 378 |
+
"auroc": 0.6174,
|
| 379 |
+
"false_accept_rate": 0.5851,
|
| 380 |
+
"false_escalate_rate": 0.1797,
|
| 381 |
+
"ece": 0.119,
|
| 382 |
+
"brier": 0.2476
|
| 383 |
+
},
|
| 384 |
+
"qwen3.8-27b": {
|
| 385 |
+
"n": 810,
|
| 386 |
+
"answers_wrong": 0.0938,
|
| 387 |
+
"auroc": 0.6742,
|
| 388 |
+
"false_accept_rate": 0.7763,
|
| 389 |
+
"false_escalate_rate": 0.0668,
|
| 390 |
+
"ece": 0.1386,
|
| 391 |
+
"brier": 0.1115
|
| 392 |
+
},
|
| 393 |
+
"source:bbh": {
|
| 394 |
+
"n": 3240,
|
| 395 |
+
"answers_wrong": 0.1877,
|
| 396 |
+
"auroc": 0.72,
|
| 397 |
+
"false_accept_rate": 0.7023,
|
| 398 |
+
"false_escalate_rate": 0.0672,
|
| 399 |
+
"ece": 0.0602,
|
| 400 |
+
"brier": 0.1398
|
| 401 |
+
},
|
| 402 |
+
"reliability": [
|
| 403 |
+
{
|
| 404 |
+
"bin": "0.0-0.1",
|
| 405 |
+
"n": 30,
|
| 406 |
+
"confidence": 0.066,
|
| 407 |
+
"accuracy": 0.033
|
| 408 |
+
},
|
| 409 |
+
{
|
| 410 |
+
"bin": "0.1-0.2",
|
| 411 |
+
"n": 56,
|
| 412 |
+
"confidence": 0.149,
|
| 413 |
+
"accuracy": 0.125
|
| 414 |
+
},
|
| 415 |
+
{
|
| 416 |
+
"bin": "0.2-0.3",
|
| 417 |
+
"n": 73,
|
| 418 |
+
"confidence": 0.254,
|
| 419 |
+
"accuracy": 0.589
|
| 420 |
+
},
|
| 421 |
+
{
|
| 422 |
+
"bin": "0.3-0.4",
|
| 423 |
+
"n": 82,
|
| 424 |
+
"confidence": 0.357,
|
| 425 |
+
"accuracy": 0.598
|
| 426 |
+
},
|
| 427 |
+
{
|
| 428 |
+
"bin": "0.4-0.5",
|
| 429 |
+
"n": 117,
|
| 430 |
+
"confidence": 0.449,
|
| 431 |
+
"accuracy": 0.658
|
| 432 |
+
},
|
| 433 |
+
{
|
| 434 |
+
"bin": "0.5-0.6",
|
| 435 |
+
"n": 199,
|
| 436 |
+
"confidence": 0.553,
|
| 437 |
+
"accuracy": 0.734
|
| 438 |
+
},
|
| 439 |
+
{
|
| 440 |
+
"bin": "0.6-0.7",
|
| 441 |
+
"n": 375,
|
| 442 |
+
"confidence": 0.654,
|
| 443 |
+
"accuracy": 0.773
|
| 444 |
+
},
|
| 445 |
+
{
|
| 446 |
+
"bin": "0.7-0.8",
|
| 447 |
+
"n": 525,
|
| 448 |
+
"confidence": 0.755,
|
| 449 |
+
"accuracy": 0.794
|
| 450 |
+
},
|
| 451 |
+
{
|
| 452 |
+
"bin": "0.8-0.9",
|
| 453 |
+
"n": 906,
|
| 454 |
+
"confidence": 0.854,
|
| 455 |
+
"accuracy": 0.87
|
| 456 |
+
},
|
| 457 |
+
{
|
| 458 |
+
"bin": "0.9-1.0",
|
| 459 |
+
"n": 877,
|
| 460 |
+
"confidence": 0.939,
|
| 461 |
+
"accuracy": 0.928
|
| 462 |
+
}
|
| 463 |
+
]
|
| 464 |
+
}
|
| 465 |
+
},
|
| 466 |
+
"ladder": [
|
| 467 |
+
"glm-5.3-flash",
|
| 468 |
+
"qwen3.8-27b",
|
| 469 |
+
"nemotron-ultra"
|
| 470 |
+
],
|
| 471 |
+
"n_ladder_tasks": 491,
|
| 472 |
+
"policies": {
|
| 473 |
+
"always:glm-5.3-flash": {
|
| 474 |
+
"accuracy": 0.888,
|
| 475 |
+
"relative_cost": 0.1146,
|
| 476 |
+
"silent_failure_rate": 0.0,
|
| 477 |
+
"calls_per_task": 1.0,
|
| 478 |
+
"extra_agent_latency_s": 0.0
|
| 479 |
+
},
|
| 480 |
+
"always:qwen3.8-27b": {
|
| 481 |
+
"accuracy": 0.8737,
|
| 482 |
+
"relative_cost": 0.4208,
|
| 483 |
+
"silent_failure_rate": 0.0,
|
| 484 |
+
"calls_per_task": 1.0,
|
| 485 |
+
"extra_agent_latency_s": 0.0
|
| 486 |
+
},
|
| 487 |
+
"always:nemotron-ultra": {
|
| 488 |
+
"accuracy": 0.8473,
|
| 489 |
+
"relative_cost": 1.0,
|
| 490 |
+
"silent_failure_rate": 0.0,
|
| 491 |
+
"calls_per_task": 1.0,
|
| 492 |
+
"extra_agent_latency_s": 0.0
|
| 493 |
+
},
|
| 494 |
+
"ladder:format-check": {
|
| 495 |
+
"accuracy": 0.8982,
|
| 496 |
+
"relative_cost": 0.4058,
|
| 497 |
+
"silent_failure_rate": 0.0733,
|
| 498 |
+
"calls_per_task": 1.073,
|
| 499 |
+
"extra_agent_latency_s": 10.95
|
| 500 |
+
},
|
| 501 |
+
"ladder:self-consistency": {
|
| 502 |
+
"accuracy": 0.9043,
|
| 503 |
+
"relative_cost": 0.5535,
|
| 504 |
+
"silent_failure_rate": 0.0652,
|
| 505 |
+
"calls_per_task": 1.141,
|
| 506 |
+
"extra_agent_latency_s": 15.29
|
| 507 |
+
},
|
| 508 |
+
"ladder:oracle": {
|
| 509 |
+
"accuracy": 0.9328,
|
| 510 |
+
"relative_cost": 0.473,
|
| 511 |
+
"silent_failure_rate": 0.0,
|
| 512 |
+
"calls_per_task": 1.185,
|
| 513 |
+
"extra_agent_latency_s": 15.89
|
| 514 |
+
},
|
| 515 |
+
"shaktimaan-v1.0": {
|
| 516 |
+
"accuracy": 0.8493,
|
| 517 |
+
"relative_cost": 0.3226
|
| 518 |
+
}
|
| 519 |
+
},
|
| 520 |
+
"jedi_curve": [
|
| 521 |
+
{
|
| 522 |
+
"threshold": 0.05,
|
| 523 |
+
"accuracy": 0.9104,
|
| 524 |
+
"relative_cost": 0.2441,
|
| 525 |
+
"silent_failure_rate": 0.0754,
|
| 526 |
+
"calls_per_task": 1.086,
|
| 527 |
+
"extra_agent_latency_s": 8.94
|
| 528 |
+
},
|
| 529 |
+
{
|
| 530 |
+
"threshold": 0.1,
|
| 531 |
+
"accuracy": 0.9145,
|
| 532 |
+
"relative_cost": 0.2634,
|
| 533 |
+
"silent_failure_rate": 0.0652,
|
| 534 |
+
"calls_per_task": 1.096,
|
| 535 |
+
"extra_agent_latency_s": 9.49
|
| 536 |
+
},
|
| 537 |
+
{
|
| 538 |
+
"threshold": 0.2,
|
| 539 |
+
"accuracy": 0.9124,
|
| 540 |
+
"relative_cost": 0.2746,
|
| 541 |
+
"silent_failure_rate": 0.0652,
|
| 542 |
+
"calls_per_task": 1.098,
|
| 543 |
+
"extra_agent_latency_s": 9.69
|
| 544 |
+
},
|
| 545 |
+
{
|
| 546 |
+
"threshold": 0.3,
|
| 547 |
+
"accuracy": 0.9145,
|
| 548 |
+
"relative_cost": 0.3229,
|
| 549 |
+
"silent_failure_rate": 0.055,
|
| 550 |
+
"calls_per_task": 1.108,
|
| 551 |
+
"extra_agent_latency_s": 10.73
|
| 552 |
+
},
|
| 553 |
+
{
|
| 554 |
+
"threshold": 0.4,
|
| 555 |
+
"accuracy": 0.9145,
|
| 556 |
+
"relative_cost": 0.3551,
|
| 557 |
+
"silent_failure_rate": 0.0509,
|
| 558 |
+
"calls_per_task": 1.114,
|
| 559 |
+
"extra_agent_latency_s": 11.42
|
| 560 |
+
},
|
| 561 |
+
{
|
| 562 |
+
"threshold": 0.5,
|
| 563 |
+
"accuracy": 0.9165,
|
| 564 |
+
"relative_cost": 0.3865,
|
| 565 |
+
"silent_failure_rate": 0.0448,
|
| 566 |
+
"calls_per_task": 1.122,
|
| 567 |
+
"extra_agent_latency_s": 12.08
|
| 568 |
+
},
|
| 569 |
+
{
|
| 570 |
+
"threshold": 0.6,
|
| 571 |
+
"accuracy": 0.9165,
|
| 572 |
+
"relative_cost": 0.4313,
|
| 573 |
+
"silent_failure_rate": 0.0387,
|
| 574 |
+
"calls_per_task": 1.13,
|
| 575 |
+
"extra_agent_latency_s": 13.14
|
| 576 |
+
},
|
| 577 |
+
{
|
| 578 |
+
"threshold": 0.7,
|
| 579 |
+
"accuracy": 0.9185,
|
| 580 |
+
"relative_cost": 0.4538,
|
| 581 |
+
"silent_failure_rate": 0.0285,
|
| 582 |
+
"calls_per_task": 1.147,
|
| 583 |
+
"extra_agent_latency_s": 14.32
|
| 584 |
+
},
|
| 585 |
+
{
|
| 586 |
+
"threshold": 0.8,
|
| 587 |
+
"accuracy": 0.9104,
|
| 588 |
+
"relative_cost": 0.5055,
|
| 589 |
+
"silent_failure_rate": 0.0285,
|
| 590 |
+
"calls_per_task": 1.196,
|
| 591 |
+
"extra_agent_latency_s": 16.37
|
| 592 |
+
},
|
| 593 |
+
{
|
| 594 |
+
"threshold": 0.85,
|
| 595 |
+
"accuracy": 0.9084,
|
| 596 |
+
"relative_cost": 0.5408,
|
| 597 |
+
"silent_failure_rate": 0.0224,
|
| 598 |
+
"calls_per_task": 1.238,
|
| 599 |
+
"extra_agent_latency_s": 17.9
|
| 600 |
+
},
|
| 601 |
+
{
|
| 602 |
+
"threshold": 0.9,
|
| 603 |
+
"accuracy": 0.9022,
|
| 604 |
+
"relative_cost": 0.6093,
|
| 605 |
+
"silent_failure_rate": 0.0163,
|
| 606 |
+
"calls_per_task": 1.316,
|
| 607 |
+
"extra_agent_latency_s": 20.84
|
| 608 |
+
},
|
| 609 |
+
{
|
| 610 |
+
"threshold": 0.95,
|
| 611 |
+
"accuracy": 0.8697,
|
| 612 |
+
"relative_cost": 0.8913,
|
| 613 |
+
"silent_failure_rate": 0.0143,
|
| 614 |
+
"calls_per_task": 1.654,
|
| 615 |
+
"extra_agent_latency_s": 34.46
|
| 616 |
+
},
|
| 617 |
+
{
|
| 618 |
+
"threshold": 0.98,
|
| 619 |
+
"accuracy": 0.8615,
|
| 620 |
+
"relative_cost": 1.2413,
|
| 621 |
+
"silent_failure_rate": 0.0,
|
| 622 |
+
"calls_per_task": 2.393,
|
| 623 |
+
"extra_agent_latency_s": 55.17
|
| 624 |
+
}
|
| 625 |
+
],
|
| 626 |
+
"two_rung": {
|
| 627 |
+
"new-domains (aqua, csqa)": {
|
| 628 |
+
"n_tasks": 142,
|
| 629 |
+
"always:glm-5.3-flash": {
|
| 630 |
+
"accuracy": 0.8521,
|
| 631 |
+
"cost_vs_always_mid": 0.2999,
|
| 632 |
+
"silent_failure_rate": 0.0
|
| 633 |
+
},
|
| 634 |
+
"always:qwen3.8-27b": {
|
| 635 |
+
"accuracy": 0.8873,
|
| 636 |
+
"cost_vs_always_mid": 1.0,
|
| 637 |
+
"silent_failure_rate": 0.0
|
| 638 |
+
},
|
| 639 |
+
"oracle": {
|
| 640 |
+
"accuracy": 0.9225,
|
| 641 |
+
"cost_vs_always_mid": 0.7111,
|
| 642 |
+
"silent_failure_rate": 0.0
|
| 643 |
+
},
|
| 644 |
+
"self-consistency": {
|
| 645 |
+
"accuracy": 0.8873,
|
| 646 |
+
"cost_vs_always_mid": 1.0475,
|
| 647 |
+
"silent_failure_rate": 0.0704
|
| 648 |
+
},
|
| 649 |
+
"jedi@0.5": {
|
| 650 |
+
"accuracy": 0.8873,
|
| 651 |
+
"cost_vs_always_mid": 0.6761,
|
| 652 |
+
"silent_failure_rate": 0.0845
|
| 653 |
+
},
|
| 654 |
+
"jedi@0.7": {
|
| 655 |
+
"accuracy": 0.8732,
|
| 656 |
+
"cost_vs_always_mid": 0.7191,
|
| 657 |
+
"silent_failure_rate": 0.0845
|
| 658 |
+
},
|
| 659 |
+
"jedi@0.9": {
|
| 660 |
+
"accuracy": 0.8873,
|
| 661 |
+
"cost_vs_always_mid": 0.8953,
|
| 662 |
+
"silent_failure_rate": 0.0493
|
| 663 |
+
}
|
| 664 |
+
},
|
| 665 |
+
"ood: bbh": {
|
| 666 |
+
"n_tasks": 810,
|
| 667 |
+
"always:glm-5.3-flash": {
|
| 668 |
+
"accuracy": 0.9012,
|
| 669 |
+
"cost_vs_always_mid": 0.1436,
|
| 670 |
+
"silent_failure_rate": 0.0
|
| 671 |
+
},
|
| 672 |
+
"always:qwen3.8-27b": {
|
| 673 |
+
"accuracy": 0.9062,
|
| 674 |
+
"cost_vs_always_mid": 1.0,
|
| 675 |
+
"silent_failure_rate": 0.0
|
| 676 |
+
},
|
| 677 |
+
"oracle": {
|
| 678 |
+
"accuracy": 0.9457,
|
| 679 |
+
"cost_vs_always_mid": 0.3743,
|
| 680 |
+
"silent_failure_rate": 0.0
|
| 681 |
+
},
|
| 682 |
+
"self-consistency": {
|
| 683 |
+
"accuracy": 0.9136,
|
| 684 |
+
"cost_vs_always_mid": 0.5322,
|
| 685 |
+
"silent_failure_rate": 0.0605
|
| 686 |
+
},
|
| 687 |
+
"jedi@0.5": {
|
| 688 |
+
"accuracy": 0.9049,
|
| 689 |
+
"cost_vs_always_mid": 0.178,
|
| 690 |
+
"silent_failure_rate": 0.0938
|
| 691 |
+
},
|
| 692 |
+
"jedi@0.7": {
|
| 693 |
+
"accuracy": 0.9111,
|
| 694 |
+
"cost_vs_always_mid": 0.2665,
|
| 695 |
+
"silent_failure_rate": 0.0778
|
| 696 |
+
},
|
| 697 |
+
"jedi@0.9": {
|
| 698 |
+
"accuracy": 0.9198,
|
| 699 |
+
"cost_vs_always_mid": 0.6854,
|
| 700 |
+
"silent_failure_rate": 0.0222
|
| 701 |
+
}
|
| 702 |
+
}
|
| 703 |
+
},
|
| 704 |
+
"overhead": {
|
| 705 |
+
"jedi_ms_per_check_batch1": 55.6,
|
| 706 |
+
"jedi_input_tokens_mean": 451,
|
| 707 |
+
"jedi_output_tokens": 1
|
| 708 |
+
}
|
| 709 |
+
}
|
evaluate.py
ADDED
|
@@ -0,0 +1,279 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
"""Stage 4: evaluate Jedi as a verifier and as the gate of an escalation ladder.
|
| 2 |
+
|
| 3 |
+
Jedi's confidence is P(accept) read from its first output token (accept vs escalate), Platt-calibrated on the val
|
| 4 |
+
split. Reports, on held-out data:
|
| 5 |
+
* verifier quality per split / agent: AUROC, false-accept rate (wrong answers it lets through), ECE, Brier;
|
| 6 |
+
* the ladder glm-5.3-flash -> qwen3.8-27b -> nemotron-ultra, with Jedi checking rungs 1 and 2 and the top rung's
|
| 7 |
+
answer always accepted, swept over the confidence threshold (accuracy, relative cost, silent-failure rate);
|
| 8 |
+
* baselines on exactly the same tasks: every single agent, Shaktimaan v1.0 routing, a format check (escalate only
|
| 9 |
+
truncated / unparseable answers), self-consistency (escalate when two cheap samples disagree) and the oracle
|
| 10 |
+
ladder (a perfect verifier);
|
| 11 |
+
* overhead: Jedi's own latency per check and the extra agent latency on escalated tasks.
|
| 12 |
+
Answers for glm / qwen are fresh samples (sample 0). Nemotron-ultra's outcome and token count come from the graded
|
| 13 |
+
record in the Shaktimaan-routing dataset (same task ids), as do agents Shaktimaan routes to that were not re-run.
|
| 14 |
+
Relative cost = sum over called agents of (agent price x completion tokens), divided by always-nemotron-ultra.
|
| 15 |
+
"""
|
| 16 |
+
import json, sys, time
|
| 17 |
+
from collections import defaultdict
|
| 18 |
+
import numpy as np
|
| 19 |
+
import torch
|
| 20 |
+
from datasets import load_dataset, load_from_disk
|
| 21 |
+
from transformers import AutoModelForCausalLM, AutoTokenizer
|
| 22 |
+
from common import LADDER, POOL, JEDI_SYSTEM, STRONGEST
|
| 23 |
+
|
| 24 |
+
path = sys.argv[1] if len(sys.argv) > 1 else "jedi-0.5b"
|
| 25 |
+
tok = AutoTokenizer.from_pretrained(path)
|
| 26 |
+
tok.padding_side = "left"
|
| 27 |
+
model = AutoModelForCausalLM.from_pretrained(path, dtype=torch.bfloat16).cuda().eval()
|
| 28 |
+
ds = load_from_disk("data/jedi_judge")
|
| 29 |
+
ACC, ESC = (tok.encode(w, add_special_tokens=False)[0] for w in ("accept", "escalate"))
|
| 30 |
+
assert ACC != ESC
|
| 31 |
+
CHEAP, MID = LADDER[0], LADDER[1]
|
| 32 |
+
|
| 33 |
+
|
| 34 |
+
def logit_gap(views, bs=16):
|
| 35 |
+
"""accept-vs-escalate logit gap at Jedi's first output token, for each judge input."""
|
| 36 |
+
out = []
|
| 37 |
+
order = np.argsort([len(v) for v in views]) # length-sorted batches: far less padding
|
| 38 |
+
for i in range(0, len(views), bs):
|
| 39 |
+
idx = order[i:i + bs]
|
| 40 |
+
msgs = [[{"role": "system", "content": JEDI_SYSTEM}, {"role": "user", "content": views[j]}] for j in idx]
|
| 41 |
+
enc = tok.apply_chat_template(msgs, add_generation_prompt=True, return_tensors="pt", padding=True,
|
| 42 |
+
return_dict=True).to("cuda")
|
| 43 |
+
with torch.no_grad():
|
| 44 |
+
last = model(**enc).logits[:, -1, :].float()
|
| 45 |
+
out += list(zip(idx, (last[:, ACC] - last[:, ESC]).tolist()))
|
| 46 |
+
gap = np.zeros(len(views))
|
| 47 |
+
for j, g in out:
|
| 48 |
+
gap[j] = g
|
| 49 |
+
return gap
|
| 50 |
+
|
| 51 |
+
|
| 52 |
+
def fit_platt(z, y):
|
| 53 |
+
a, b = torch.ones(1, requires_grad=True), torch.zeros(1, requires_grad=True)
|
| 54 |
+
zt, yt = torch.tensor(z, dtype=torch.float32), torch.tensor(y, dtype=torch.float32)
|
| 55 |
+
opt = torch.optim.LBFGS([a, b], max_iter=200)
|
| 56 |
+
|
| 57 |
+
def closure():
|
| 58 |
+
opt.zero_grad()
|
| 59 |
+
loss = torch.nn.functional.binary_cross_entropy_with_logits(a * zt + b, yt)
|
| 60 |
+
loss.backward()
|
| 61 |
+
return loss
|
| 62 |
+
opt.step(closure)
|
| 63 |
+
return a.item(), b.item()
|
| 64 |
+
|
| 65 |
+
|
| 66 |
+
def sigmoid(x):
|
| 67 |
+
return 1 / (1 + np.exp(-x))
|
| 68 |
+
|
| 69 |
+
|
| 70 |
+
def auroc(p, y):
|
| 71 |
+
p, y = np.asarray(p), np.asarray(y, bool)
|
| 72 |
+
if y.all() or not y.any():
|
| 73 |
+
return None
|
| 74 |
+
ranks = np.argsort(np.argsort(p)) + 1
|
| 75 |
+
return float((ranks[y].sum() - y.sum() * (y.sum() + 1) / 2) / (y.sum() * (~y).sum()))
|
| 76 |
+
|
| 77 |
+
|
| 78 |
+
def ece(p, y, bins=10):
|
| 79 |
+
p, y = np.asarray(p), np.asarray(y, float)
|
| 80 |
+
edges = np.linspace(0, 1, bins + 1)
|
| 81 |
+
total, table = 0.0, []
|
| 82 |
+
for lo, hi in zip(edges[:-1], edges[1:]):
|
| 83 |
+
m = (p >= lo) & ((p < hi) | (hi == 1))
|
| 84 |
+
if m.any():
|
| 85 |
+
total += m.mean() * abs(p[m].mean() - y[m].mean())
|
| 86 |
+
table.append(dict(bin=f"{lo:.1f}-{hi:.1f}", n=int(m.sum()), confidence=round(float(p[m].mean()), 3),
|
| 87 |
+
accuracy=round(float(y[m].mean()), 3)))
|
| 88 |
+
return float(total), table
|
| 89 |
+
|
| 90 |
+
|
| 91 |
+
def verifier_metrics(p, y, tau=0.5):
|
| 92 |
+
p, y = np.asarray(p), np.asarray(y, bool)
|
| 93 |
+
e, _ = ece(p, y)
|
| 94 |
+
return dict(n=int(len(y)), answers_wrong=round(float(1 - y.mean()), 4), auroc=round(auroc(p, y) or 0, 4),
|
| 95 |
+
false_accept_rate=round(float(((p >= tau) & ~y).sum() / max(1, (~y).sum())), 4),
|
| 96 |
+
false_escalate_rate=round(float(((p < tau) & y).sum() / max(1, y.sum())), 4),
|
| 97 |
+
ece=round(e, 4), brier=round(float(((p - y) ** 2).mean()), 4))
|
| 98 |
+
|
| 99 |
+
|
| 100 |
+
# ---------- score every held-out answer ----------
|
| 101 |
+
z_val = logit_gap(ds["val"]["judge_input"])
|
| 102 |
+
a, b = fit_platt(z_val, ds["val"]["correct"])
|
| 103 |
+
cal = dict(a=a, b=b, accept_token=ACC, escalate_token=ESC)
|
| 104 |
+
P = {}
|
| 105 |
+
report = {"calibration": cal, "verifier": {}}
|
| 106 |
+
for split in ("val", "test", "ood"):
|
| 107 |
+
d = ds[split]
|
| 108 |
+
z = z_val if split == "val" else logit_gap(d["judge_input"])
|
| 109 |
+
p_raw, p = sigmoid(z), sigmoid(a * z + b)
|
| 110 |
+
y = np.array(d["correct"])
|
| 111 |
+
rep = {"all": verifier_metrics(p, y), "uncalibrated_ece": round(ece(p_raw, y)[0], 4)}
|
| 112 |
+
for ag in sorted(set(d["agent"])):
|
| 113 |
+
m = np.array(d["agent"]) == ag
|
| 114 |
+
rep[ag] = verifier_metrics(p[m], y[m])
|
| 115 |
+
for src in sorted(set(d["source"])):
|
| 116 |
+
m = np.array(d["source"]) == src
|
| 117 |
+
rep[f"source:{src}"] = verifier_metrics(p[m], y[m])
|
| 118 |
+
rep["reliability"] = ece(p, y)[1]
|
| 119 |
+
report["verifier"][split] = rep
|
| 120 |
+
for r, pi in zip(d.select_columns(["task_id", "agent", "sample", "correct", "completion_tokens", "latency",
|
| 121 |
+
"finish", "pred", "kind"]), p):
|
| 122 |
+
P[r["task_id"], r["agent"], r["sample"]] = dict(r, p=float(pi), split=split)
|
| 123 |
+
print(split, json.dumps(rep["all"]), flush=True)
|
| 124 |
+
|
| 125 |
+
# ---------- ladder ----------
|
| 126 |
+
shak = {r["id"]: r for sp in ("train", "test") for r in load_dataset("podstack/Shaktimaan-routing", split=sp)}
|
| 127 |
+
|
| 128 |
+
|
| 129 |
+
def norm_pred(s):
|
| 130 |
+
return (s or "").strip().strip(".").lower()
|
| 131 |
+
|
| 132 |
+
|
| 133 |
+
def format_ok(ans):
|
| 134 |
+
return ans["finish"] != "length" and bool(ans["pred"].strip())
|
| 135 |
+
|
| 136 |
+
|
| 137 |
+
def run_ladder(tasks, rungs, accept, final_record=None):
|
| 138 |
+
"""tasks: task ids. rungs: agent names; accept(task_id, agent) -> (bool, extra_cost). The last rung is always
|
| 139 |
+
accepted; if final_record is given it supplies the last rung's (correct, tokens)."""
|
| 140 |
+
correct, cost, silent, calls, extra_latency = 0, 0.0, 0, 0, 0.0
|
| 141 |
+
for t in tasks:
|
| 142 |
+
for i, ag in enumerate(rungs):
|
| 143 |
+
last = i == len(rungs) - 1
|
| 144 |
+
if last and final_record:
|
| 145 |
+
c, toks, lat = final_record(t)
|
| 146 |
+
else:
|
| 147 |
+
ans = P[t, ag, 0]
|
| 148 |
+
c, toks, lat = ans["correct"], ans["completion_tokens"], ans["latency"]
|
| 149 |
+
cost += POOL[ag]["cost"] * toks
|
| 150 |
+
calls += 1
|
| 151 |
+
if i:
|
| 152 |
+
extra_latency += lat
|
| 153 |
+
if last:
|
| 154 |
+
correct += c
|
| 155 |
+
break
|
| 156 |
+
ok, extra = accept(t, ag)
|
| 157 |
+
cost += extra
|
| 158 |
+
if ok:
|
| 159 |
+
correct += c
|
| 160 |
+
silent += not c
|
| 161 |
+
break
|
| 162 |
+
n = len(tasks)
|
| 163 |
+
return dict(accuracy=correct / n, cost=cost, silent_failure_rate=silent / n, calls_per_task=calls / n,
|
| 164 |
+
extra_agent_latency_s=extra_latency / n)
|
| 165 |
+
|
| 166 |
+
|
| 167 |
+
def ultra_record(t):
|
| 168 |
+
r = json.loads(shak[t]["agent_results"])[STRONGEST]
|
| 169 |
+
return r["correct"], r["completion_tokens"], r["latency"]
|
| 170 |
+
|
| 171 |
+
|
| 172 |
+
def jedi(tau):
|
| 173 |
+
return lambda t, ag: (P[t, ag, 0]["p"] >= tau, 0.0)
|
| 174 |
+
|
| 175 |
+
|
| 176 |
+
def fmt(t, ag):
|
| 177 |
+
return format_ok(P[t, ag, 0]), 0.0
|
| 178 |
+
|
| 179 |
+
|
| 180 |
+
def self_consistency(t, ag):
|
| 181 |
+
s0, s1 = P[t, ag, 0], P.get((t, ag, 1))
|
| 182 |
+
if s1 is None or s0["kind"] == "code":
|
| 183 |
+
return format_ok(s0), 0.0
|
| 184 |
+
return format_ok(s0) and norm_pred(s0["pred"]) == norm_pred(s1["pred"]), POOL[ag]["cost"] * s1["completion_tokens"]
|
| 185 |
+
|
| 186 |
+
|
| 187 |
+
def oracle(t, ag):
|
| 188 |
+
return P[t, ag, 0]["correct"], 0.0
|
| 189 |
+
|
| 190 |
+
|
| 191 |
+
def ladder_tasks(split, sources):
|
| 192 |
+
return sorted({t for (t, _, _), v in P.items() if v["split"] == split and t.split("-")[0] in sources
|
| 193 |
+
and (t, CHEAP, 0) in P and (t, MID, 0) in P})
|
| 194 |
+
|
| 195 |
+
|
| 196 |
+
test_tasks = [t for t in ladder_tasks("test", {"gsm8k", "math", "mmlupro", "arc", "mbpp", "humaneval"}) if t in shak]
|
| 197 |
+
ultra_cost = sum(POOL[STRONGEST]["cost"] * ultra_record(t)[1] for t in test_tasks)
|
| 198 |
+
|
| 199 |
+
|
| 200 |
+
def rel(res):
|
| 201 |
+
return {"accuracy": round(res["accuracy"], 4), "relative_cost": round(res["cost"] / ultra_cost, 4),
|
| 202 |
+
"silent_failure_rate": round(res["silent_failure_rate"], 4),
|
| 203 |
+
"calls_per_task": round(res["calls_per_task"], 3),
|
| 204 |
+
"extra_agent_latency_s": round(res["extra_agent_latency_s"], 2)}
|
| 205 |
+
|
| 206 |
+
|
| 207 |
+
policies = {
|
| 208 |
+
f"always:{CHEAP}": rel(run_ladder(test_tasks, [CHEAP], None)),
|
| 209 |
+
f"always:{MID}": rel(run_ladder(test_tasks, [MID], None)),
|
| 210 |
+
f"always:{STRONGEST}": rel(run_ladder(test_tasks, [STRONGEST], None, ultra_record)),
|
| 211 |
+
"ladder:format-check": rel(run_ladder(test_tasks, LADDER, fmt, ultra_record)),
|
| 212 |
+
"ladder:self-consistency": rel(run_ladder(test_tasks, LADDER, self_consistency, ultra_record)),
|
| 213 |
+
"ladder:oracle": rel(run_ladder(test_tasks, LADDER, oracle, ultra_record)),
|
| 214 |
+
}
|
| 215 |
+
|
| 216 |
+
# Shaktimaan v1.0 on the same tasks: route with the published router, then score its choice.
|
| 217 |
+
rtok = AutoTokenizer.from_pretrained("podstack/Shaktimaan")
|
| 218 |
+
rtok.padding_side = "left"
|
| 219 |
+
router = AutoModelForCausalLM.from_pretrained("podstack/Shaktimaan", dtype=torch.bfloat16).cuda().eval()
|
| 220 |
+
routes = []
|
| 221 |
+
for i in range(0, len(test_tasks), 32):
|
| 222 |
+
msgs = [shak[t]["messages"][:2] for t in test_tasks[i:i + 32]]
|
| 223 |
+
enc = rtok.apply_chat_template(msgs, add_generation_prompt=True, return_tensors="pt", padding=True,
|
| 224 |
+
return_dict=True).to("cuda")
|
| 225 |
+
with torch.no_grad():
|
| 226 |
+
out = router.generate(**enc, max_new_tokens=96, do_sample=False)
|
| 227 |
+
routes += rtok.batch_decode(out[:, enc["input_ids"].shape[1]:], skip_special_tokens=True)
|
| 228 |
+
del router
|
| 229 |
+
s_correct, s_cost = 0, 0.0
|
| 230 |
+
for t, rt in zip(test_tasks, routes):
|
| 231 |
+
try:
|
| 232 |
+
agents = [x for x in json.loads(rt)["agents"] if x in POOL] or [STRONGEST]
|
| 233 |
+
except Exception: # noqa: BLE001
|
| 234 |
+
agents = [STRONGEST]
|
| 235 |
+
rec = json.loads(shak[t]["agent_results"])
|
| 236 |
+
for ag in agents: # fresh sample where one exists, otherwise the recorded run
|
| 237 |
+
fresh = P.get((t, ag, 0))
|
| 238 |
+
s_cost += POOL[ag]["cost"] * (fresh["completion_tokens"] if fresh else rec[ag]["completion_tokens"])
|
| 239 |
+
fin = P.get((t, agents[-1], 0)) # ensembles are scored as the aggregating agent's own answer (conservative)
|
| 240 |
+
s_correct += fin["correct"] if fin else rec[agents[-1]]["correct"]
|
| 241 |
+
policies["shaktimaan-v1.0"] = {"accuracy": round(s_correct / len(test_tasks), 4),
|
| 242 |
+
"relative_cost": round(s_cost / ultra_cost, 4)}
|
| 243 |
+
|
| 244 |
+
curve = []
|
| 245 |
+
for tau in [0.05, 0.1, 0.2, 0.3, 0.4, 0.5, 0.6, 0.7, 0.8, 0.85, 0.9, 0.95, 0.98]:
|
| 246 |
+
curve.append(dict(threshold=tau, **rel(run_ladder(test_tasks, LADDER, jedi(tau), ultra_record))))
|
| 247 |
+
|
| 248 |
+
# two-rung ladder (no Ultra record) on the new domains and on unseen BIG-Bench Hard
|
| 249 |
+
two_rung = {}
|
| 250 |
+
for name, split, srcs in [("new-domains (aqua, csqa)", "test", {"aqua", "csqa"}), ("ood: bbh", "ood", {"bbh"})]:
|
| 251 |
+
ids = ladder_tasks(split, srcs)
|
| 252 |
+
base = sum(POOL[MID]["cost"] * P[t, MID, 0]["completion_tokens"] for t in ids)
|
| 253 |
+
r = lambda res: {"accuracy": round(res["accuracy"], 4), "cost_vs_always_mid": round(res["cost"] / base, 4),
|
| 254 |
+
"silent_failure_rate": round(res["silent_failure_rate"], 4)}
|
| 255 |
+
two_rung[name] = {"n_tasks": len(ids),
|
| 256 |
+
f"always:{CHEAP}": r(run_ladder(ids, [CHEAP], None)),
|
| 257 |
+
f"always:{MID}": r(run_ladder(ids, [MID], None)),
|
| 258 |
+
"oracle": r(run_ladder(ids, [CHEAP, MID], oracle)),
|
| 259 |
+
"self-consistency": r(run_ladder(ids, [CHEAP, MID], self_consistency)),
|
| 260 |
+
**{f"jedi@{tau}": r(run_ladder(ids, [CHEAP, MID], jedi(tau))) for tau in (0.5, 0.7, 0.9)}}
|
| 261 |
+
|
| 262 |
+
# overhead: one check at a time, as a live ladder would issue it
|
| 263 |
+
views = ds["test"]["judge_input"][:200]
|
| 264 |
+
torch.cuda.synchronize()
|
| 265 |
+
t0 = time.time()
|
| 266 |
+
for v in views:
|
| 267 |
+
logit_gap([v], bs=1)
|
| 268 |
+
torch.cuda.synchronize()
|
| 269 |
+
n_tok = np.mean([len(tok(tok.apply_chat_template([{"role": "system", "content": JEDI_SYSTEM},
|
| 270 |
+
{"role": "user", "content": v}], add_generation_prompt=True,
|
| 271 |
+
tokenize=False)).input_ids) for v in views])
|
| 272 |
+
|
| 273 |
+
report.update({"ladder": LADDER, "n_ladder_tasks": len(test_tasks), "policies": policies, "jedi_curve": curve,
|
| 274 |
+
"two_rung": two_rung,
|
| 275 |
+
"overhead": {"jedi_ms_per_check_batch1": round((time.time() - t0) / len(views) * 1000, 1),
|
| 276 |
+
"jedi_input_tokens_mean": round(float(n_tok)), "jedi_output_tokens": 1}})
|
| 277 |
+
print(json.dumps({k: v for k, v in report.items() if k != "verifier"}, indent=2))
|
| 278 |
+
json.dump(report, open(f"{path}/eval_results.json", "w"), indent=2)
|
| 279 |
+
json.dump(cal, open(f"{path}/calibration.json", "w"), indent=2)
|
gen.py
ADDED
|
@@ -0,0 +1,194 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
"""Stage 1: collect graded (task, agent answer) pairs - the raw material for Jedi.
|
| 2 |
+
|
| 3 |
+
Tasks: the text and code sources Shaktimaan was built on (same ids: GSM8K, MATH, MMLU-Pro, ARC, MBPP, HumanEval) plus
|
| 4 |
+
new domains for coverage (AQuA-RAT, CommonsenseQA) and BIG-Bench Hard, which is kept out of training entirely as an
|
| 5 |
+
out-of-domain test. Every agent in $AGENTS answers every task it can take, $SAMPLES times (temperature 0.6), and the
|
| 6 |
+
full visible answer is stored with an automatic grade (exact / numeric / math-normalised match; code is graded by
|
| 7 |
+
running its tests in a no-network Docker sandbox).
|
| 8 |
+
|
| 9 |
+
Resumable: (task, agent, sample) triples already in data/answers.jsonl without an API error are skipped.
|
| 10 |
+
BORDERLINE=1 restricts the run to tasks whose existing answers disagree (some graded correct, some wrong) - used for
|
| 11 |
+
a second pass with more samples there, where the hard-to-judge answers are.
|
| 12 |
+
"""
|
| 13 |
+
import asyncio, json, os, random, sys, time
|
| 14 |
+
import httpx
|
| 15 |
+
from datasets import get_dataset_config_names, load_dataset
|
| 16 |
+
from tqdm import tqdm
|
| 17 |
+
from common import (CODE_SYSTEM, POOL, SOLVER_SYSTEM, capable, endpoints, est_tokens, extract_code, extract_final,
|
| 18 |
+
grade, last_boxed, max_out, strip_think, truncate)
|
| 19 |
+
|
| 20 |
+
OUT = "data/answers.jsonl"
|
| 21 |
+
AGENTS = os.environ.get("AGENTS", "glm-5.3-flash,qwen3.8-27b,kimi-vl").split(",")
|
| 22 |
+
SAMPLES = {a: int(n) for a, n in (x.split("=") for x in os.environ.get(
|
| 23 |
+
"SAMPLES", "glm-5.3-flash=2,qwen3.8-27b=1,kimi-vl=1").split(","))}
|
| 24 |
+
SIZES = {"gsm8k": 1000, "math": 1000, "mmlu_pro": 1200, "arc": 800, "mbpp": 700, "aqua": 800, "csqa": 600,
|
| 25 |
+
"bbh": 30} # bbh: per sub-task (27 sub-tasks)
|
| 26 |
+
random.seed(0)
|
| 27 |
+
|
| 28 |
+
|
| 29 |
+
def load_tasks():
|
| 30 |
+
tasks = []
|
| 31 |
+
# --- sources shared with Shaktimaan (identical loading, so ids line up with the Shaktimaan-routing dataset) ---
|
| 32 |
+
ds = load_dataset("openai/gsm8k", "main", split="train").shuffle(seed=0).select(range(SIZES["gsm8k"]))
|
| 33 |
+
for i, r in enumerate(ds):
|
| 34 |
+
tasks.append(dict(id=f"gsm8k-{i}", source="gsm8k", domain="math", kind="num",
|
| 35 |
+
prompt=r["question"], gold=r["answer"].split("####")[-1].strip()))
|
| 36 |
+
rows = []
|
| 37 |
+
for cfg in ["algebra", "counting_and_probability", "geometry", "intermediate_algebra",
|
| 38 |
+
"number_theory", "prealgebra", "precalculus"]:
|
| 39 |
+
rows += list(load_dataset("EleutherAI/hendrycks_math", cfg, split="train"))
|
| 40 |
+
rows = [r for r in rows if last_boxed(r["solution"])]
|
| 41 |
+
random.shuffle(rows)
|
| 42 |
+
for i, r in enumerate(rows[:SIZES["math"]]):
|
| 43 |
+
tasks.append(dict(id=f"math-{i}", source="math", domain="math", kind="math",
|
| 44 |
+
prompt=r["problem"], gold=last_boxed(r["solution"])))
|
| 45 |
+
ds = load_dataset("TIGER-Lab/MMLU-Pro", split="test").shuffle(seed=0).select(range(SIZES["mmlu_pro"]))
|
| 46 |
+
for i, r in enumerate(ds):
|
| 47 |
+
opts = "\n".join(f"{chr(65 + k)}. {o}" for k, o in enumerate(r["options"]))
|
| 48 |
+
tasks.append(dict(id=f"mmlupro-{i}", source="mmlu_pro", domain=r["category"], kind="mc",
|
| 49 |
+
prompt=f"{r['question']}\n\n{opts}", gold=r["answer"]))
|
| 50 |
+
ds = load_dataset("allenai/ai2_arc", "ARC-Challenge", split="train").shuffle(seed=0)
|
| 51 |
+
for i, r in enumerate(ds.select(range(min(SIZES["arc"], len(ds))))):
|
| 52 |
+
labels = r["choices"]["label"]
|
| 53 |
+
opts = "\n".join(f"{chr(65 + k)}. {t}" for k, t in enumerate(r["choices"]["text"]))
|
| 54 |
+
tasks.append(dict(id=f"arc-{i}", source="arc", domain="science", kind="mc",
|
| 55 |
+
prompt=f"{r['question']}\n\n{opts}", gold=chr(65 + labels.index(r["answerKey"]))))
|
| 56 |
+
mbpp = []
|
| 57 |
+
for split in ["train", "test", "validation", "prompt"]:
|
| 58 |
+
mbpp += list(load_dataset("google-research-datasets/mbpp", "full", split=split))
|
| 59 |
+
random.Random(2).shuffle(mbpp)
|
| 60 |
+
for r in mbpp[:SIZES["mbpp"]]:
|
| 61 |
+
tasks.append(dict(id=f"mbpp-{r['task_id']}", source="mbpp", domain="code", kind="code",
|
| 62 |
+
prompt=f"{r['text']}\nYour code should pass these tests:\n" + "\n".join(r["test_list"]),
|
| 63 |
+
gold=(r["test_setup_code"] or "") + "\n" + "\n".join(r["test_list"])))
|
| 64 |
+
for r in load_dataset("openai/openai_humaneval", split="test"):
|
| 65 |
+
tasks.append(dict(id=f"humaneval-{r['task_id'].split('/')[-1]}", source="humaneval", domain="code",
|
| 66 |
+
kind="code", prompt=f"Complete the following Python function:\n```python\n{r['prompt']}```",
|
| 67 |
+
gold=f"{r['test']}\ncheck({r['entry_point']})"))
|
| 68 |
+
# --- new domains ---
|
| 69 |
+
ds = load_dataset("deepmind/aqua_rat", "raw", split="train").shuffle(seed=0).select(range(SIZES["aqua"]))
|
| 70 |
+
for i, r in enumerate(ds):
|
| 71 |
+
opts = "\n".join(o.replace(")", ". ", 1) for o in r["options"])
|
| 72 |
+
tasks.append(dict(id=f"aqua-{i}", source="aqua", domain="math", kind="mc",
|
| 73 |
+
prompt=f"{r['question']}\n\n{opts}", gold=r["correct"]))
|
| 74 |
+
ds = load_dataset("tau/commonsense_qa", split="train").shuffle(seed=0).select(range(SIZES["csqa"]))
|
| 75 |
+
for r in ds:
|
| 76 |
+
opts = "\n".join(f"{l}. {t}" for l, t in zip(r["choices"]["label"], r["choices"]["text"]))
|
| 77 |
+
tasks.append(dict(id=f"csqa-{r['id']}", source="csqa", domain="commonsense", kind="mc",
|
| 78 |
+
prompt=f"{r['question']}\n\n{opts}", gold=r["answerKey"]))
|
| 79 |
+
# --- out-of-domain test only ---
|
| 80 |
+
for cfg in get_dataset_config_names("lukaemon/bbh"):
|
| 81 |
+
ds = load_dataset("lukaemon/bbh", cfg, split="test").shuffle(seed=0)
|
| 82 |
+
for i, r in enumerate(ds.select(range(min(SIZES["bbh"], len(ds))))):
|
| 83 |
+
tasks.append(dict(id=f"bbh-{cfg}-{i}", source="bbh", domain=f"bbh:{cfg}", kind="exact",
|
| 84 |
+
prompt=r["input"], gold=r["target"]))
|
| 85 |
+
for t in tasks:
|
| 86 |
+
t["est_tokens"] = est_tokens(t["prompt"])
|
| 87 |
+
return tasks
|
| 88 |
+
|
| 89 |
+
|
| 90 |
+
async def call(client, url, model, sem, messages, max_tokens):
|
| 91 |
+
"""POST a chat completion. HTTP 400 (e.g. context overflow) is returned immediately as `rejected`."""
|
| 92 |
+
async with sem:
|
| 93 |
+
t0, err = time.time(), None
|
| 94 |
+
for attempt in range(4):
|
| 95 |
+
try:
|
| 96 |
+
r = await client.post(url, json={"model": model, "max_tokens": max_tokens, "temperature": 0.6,
|
| 97 |
+
"messages": messages})
|
| 98 |
+
if r.status_code == 400:
|
| 99 |
+
return dict(text="", rejected=r.text[:200], prompt_tokens=0, completion_tokens=0,
|
| 100 |
+
latency=time.time() - t0)
|
| 101 |
+
r.raise_for_status()
|
| 102 |
+
j = r.json()
|
| 103 |
+
return dict(text=j["choices"][0]["message"].get("content") or "",
|
| 104 |
+
finish=j["choices"][0].get("finish_reason"),
|
| 105 |
+
prompt_tokens=j["usage"]["prompt_tokens"],
|
| 106 |
+
completion_tokens=j["usage"]["completion_tokens"], latency=time.time() - t0)
|
| 107 |
+
except Exception as e: # noqa: BLE001
|
| 108 |
+
err = repr(e)[:200]
|
| 109 |
+
await asyncio.sleep(2 ** attempt)
|
| 110 |
+
return dict(text="", error=err, prompt_tokens=0, completion_tokens=0, latency=time.time() - t0)
|
| 111 |
+
|
| 112 |
+
|
| 113 |
+
SANDBOX = asyncio.Semaphore(24)
|
| 114 |
+
|
| 115 |
+
|
| 116 |
+
async def run_tests(code: str, tests: str) -> bool:
|
| 117 |
+
"""Run model code + tests in a throwaway container: no network, 512 MB RAM, 1 CPU, 15 s."""
|
| 118 |
+
if not code.strip():
|
| 119 |
+
return False
|
| 120 |
+
async with SANDBOX:
|
| 121 |
+
proc = await asyncio.create_subprocess_exec(
|
| 122 |
+
"docker", "run", "--rm", "-i", "--network", "none", "--memory", "512m", "--cpus", "1",
|
| 123 |
+
"--pids-limit", "64", "--read-only", "--tmpfs", "/tmp:size=64m", "python:3.12-slim",
|
| 124 |
+
"timeout", "15", "python", "-", stdin=asyncio.subprocess.PIPE, stdout=asyncio.subprocess.DEVNULL,
|
| 125 |
+
stderr=asyncio.subprocess.DEVNULL)
|
| 126 |
+
try:
|
| 127 |
+
await asyncio.wait_for(proc.communicate((code + "\n\n" + tests).encode()), timeout=60)
|
| 128 |
+
except asyncio.TimeoutError:
|
| 129 |
+
proc.kill()
|
| 130 |
+
return False
|
| 131 |
+
return proc.returncode == 0
|
| 132 |
+
|
| 133 |
+
|
| 134 |
+
async def answer(client, urls, sems, task, agent, sample):
|
| 135 |
+
key = dict(task_id=task["id"], agent=agent, sample=sample)
|
| 136 |
+
if not capable(agent, task["prompt"], False):
|
| 137 |
+
return dict(key, skipped="context")
|
| 138 |
+
system = CODE_SYSTEM if task["kind"] == "code" else SOLVER_SYSTEM
|
| 139 |
+
o = await call(client, urls[agent], POOL[agent]["model"], sems[agent],
|
| 140 |
+
[{"role": "system", "content": system}, {"role": "user", "content": task["prompt"]}],
|
| 141 |
+
max_out(agent, task["est_tokens"]))
|
| 142 |
+
if task["kind"] == "code":
|
| 143 |
+
pred = extract_code(o["text"])
|
| 144 |
+
correct = await run_tests(pred, task["gold"])
|
| 145 |
+
else:
|
| 146 |
+
pred = extract_final(o["text"])
|
| 147 |
+
correct = grade(task["kind"], pred, task["gold"])
|
| 148 |
+
return dict(key, text=truncate(strip_think(o["text"]), 6000, 6000), pred=pred[:300], correct=correct,
|
| 149 |
+
finish=o.get("finish"), error=o.get("error"), skipped="rejected" if o.get("rejected") else None,
|
| 150 |
+
**{k: o[k] for k in ("prompt_tokens", "completion_tokens", "latency")})
|
| 151 |
+
|
| 152 |
+
|
| 153 |
+
async def alive(client, urls, agent):
|
| 154 |
+
try:
|
| 155 |
+
r = await client.post(urls[agent], timeout=120, json={
|
| 156 |
+
"model": POOL[agent]["model"], "max_tokens": 4, "messages": [{"role": "user", "content": "hi"}]})
|
| 157 |
+
return r.status_code == 200
|
| 158 |
+
except Exception: # noqa: BLE001
|
| 159 |
+
return False
|
| 160 |
+
|
| 161 |
+
|
| 162 |
+
async def main():
|
| 163 |
+
os.makedirs("data", exist_ok=True)
|
| 164 |
+
tasks = load_tasks()
|
| 165 |
+
with open("data/tasks.jsonl", "w") as f:
|
| 166 |
+
for t in tasks:
|
| 167 |
+
f.write(json.dumps(t) + "\n")
|
| 168 |
+
done, grades = set(), {}
|
| 169 |
+
if os.path.exists(OUT):
|
| 170 |
+
for r in map(json.loads, open(OUT)):
|
| 171 |
+
if not r.get("error"):
|
| 172 |
+
done.add((r["task_id"], r["agent"], r["sample"]))
|
| 173 |
+
if not r.get("skipped"):
|
| 174 |
+
grades.setdefault(r["task_id"], set()).add(bool(r["correct"]))
|
| 175 |
+
if os.environ.get("BORDERLINE"):
|
| 176 |
+
tasks = [t for t in tasks if len(grades.get(t["id"], ())) == 2 and t["source"] != "bbh"]
|
| 177 |
+
urls = {n: f"{base.rstrip('/')}/chat/completions" for n, base in endpoints().items()}
|
| 178 |
+
sems = {n: asyncio.Semaphore(int(os.environ.get(f"CONC_{n}", POOL[n]["concurrency"]))) for n in POOL}
|
| 179 |
+
limits = httpx.Limits(max_connections=400, max_keepalive_connections=400)
|
| 180 |
+
async with httpx.AsyncClient(timeout=httpx.Timeout(1800), limits=limits) as client:
|
| 181 |
+
up = [a for a, ok in zip(AGENTS, await asyncio.gather(*[alive(client, urls, a) for a in AGENTS])) if ok]
|
| 182 |
+
print(f"{len(tasks)} tasks; agents up: {up}; down: {sorted(set(AGENTS) - set(up))}", flush=True)
|
| 183 |
+
jobs = [(t, a, s) for t in tasks for a in up for s in range(SAMPLES.get(a, 1))
|
| 184 |
+
if (t["id"], a, s) not in done]
|
| 185 |
+
print(f"{len(done)} answers done, {len(jobs)} to run", flush=True)
|
| 186 |
+
with open(OUT, "a") as f:
|
| 187 |
+
futs = [answer(client, urls, sems, t, a, s) for t, a, s in jobs]
|
| 188 |
+
for fut in tqdm(asyncio.as_completed(futs), total=len(futs), mininterval=60, file=sys.stdout):
|
| 189 |
+
f.write(json.dumps(await fut) + "\n")
|
| 190 |
+
f.flush()
|
| 191 |
+
|
| 192 |
+
|
| 193 |
+
if __name__ == "__main__":
|
| 194 |
+
asyncio.run(main())
|
generation_config.json
ADDED
|
@@ -0,0 +1,14 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
{
|
| 2 |
+
"bos_token_id": 151643,
|
| 3 |
+
"do_sample": true,
|
| 4 |
+
"eos_token_id": [
|
| 5 |
+
151645,
|
| 6 |
+
151643
|
| 7 |
+
],
|
| 8 |
+
"pad_token_id": 151643,
|
| 9 |
+
"repetition_penalty": 1.1,
|
| 10 |
+
"temperature": 0.7,
|
| 11 |
+
"top_k": 20,
|
| 12 |
+
"top_p": 0.8,
|
| 13 |
+
"transformers_version": "5.19.0"
|
| 14 |
+
}
|
jedi.py
ADDED
|
@@ -0,0 +1,99 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
"""Jedi runtime: answer with the cheapest agent, let the 0.5B verifier check it, escalate up the ladder if needed.
|
| 2 |
+
|
| 3 |
+
Usage:
|
| 4 |
+
JEDI_ENDPOINTS='{"glm-5.3-flash": "http://host:8000/v1", ...}' python jedi.py [--threshold 0.7] [--code] "task"
|
| 5 |
+
|
| 6 |
+
The threshold is the single cost-vs-quality dial: raise it for accuracy (more escalations), lower it for savings.
|
| 7 |
+
Prints the final answer; the per-rung trace goes to stderr as JSON lines like
|
| 8 |
+
{"agent": "glm-5.3-flash", "verdict": "escalate", "confidence": 0.87, "escalate_to": "qwen3.8-27b"}
|
| 9 |
+
"""
|
| 10 |
+
import argparse, json, math, os, re, sys
|
| 11 |
+
import httpx
|
| 12 |
+
import torch
|
| 13 |
+
from transformers import AutoModelForCausalLM, AutoTokenizer
|
| 14 |
+
from common import CODE_SYSTEM, LADDER, POOL, JEDI_SYSTEM, SOLVER_SYSTEM, endpoints, est_tokens, judge_view, max_out
|
| 15 |
+
|
| 16 |
+
MODEL = os.environ.get("JEDI_MODEL", "podstack/Jedi")
|
| 17 |
+
|
| 18 |
+
|
| 19 |
+
class Jedi:
|
| 20 |
+
def __init__(self, path=MODEL):
|
| 21 |
+
self.tok = AutoTokenizer.from_pretrained(path)
|
| 22 |
+
self.model = AutoModelForCausalLM.from_pretrained(path, dtype=torch.bfloat16).eval()
|
| 23 |
+
if torch.cuda.is_available():
|
| 24 |
+
self.model.cuda()
|
| 25 |
+
try:
|
| 26 |
+
from huggingface_hub import hf_hub_download
|
| 27 |
+
cal_file = os.path.join(path, "calibration.json") if os.path.isdir(path) else \
|
| 28 |
+
hf_hub_download(path, "calibration.json")
|
| 29 |
+
cal = json.load(open(cal_file))
|
| 30 |
+
except Exception: # noqa: BLE001
|
| 31 |
+
cal = {"a": 1.0, "b": 0.0}
|
| 32 |
+
self.a, self.b = cal["a"], cal["b"]
|
| 33 |
+
self.ids = [self.tok.encode(w, add_special_tokens=False)[0] for w in ("accept", "escalate")]
|
| 34 |
+
|
| 35 |
+
def p_correct(self, task, answer, agent, kind, finish=None):
|
| 36 |
+
"""Calibrated probability that `answer` (written by `agent`) is correct."""
|
| 37 |
+
enc = self.tok.apply_chat_template(
|
| 38 |
+
[{"role": "system", "content": JEDI_SYSTEM},
|
| 39 |
+
{"role": "user", "content": judge_view(task, answer, agent, kind, finish)}],
|
| 40 |
+
add_generation_prompt=True, return_tensors="pt", return_dict=True).to(self.model.device)
|
| 41 |
+
with torch.no_grad():
|
| 42 |
+
logits = self.model(**enc).logits[0, -1].float()
|
| 43 |
+
gap = (logits[self.ids[0]] - logits[self.ids[1]]).item()
|
| 44 |
+
return 1 / (1 + math.exp(-(self.a * gap + self.b)))
|
| 45 |
+
|
| 46 |
+
|
| 47 |
+
def ask(agent, task, code):
|
| 48 |
+
base = endpoints()[agent].rstrip("/")
|
| 49 |
+
msgs = [{"role": "system", "content": CODE_SYSTEM if code else SOLVER_SYSTEM}, {"role": "user", "content": task}]
|
| 50 |
+
r = httpx.post(f"{base}/chat/completions", timeout=1800, json={
|
| 51 |
+
"model": POOL[agent]["model"], "messages": msgs, "max_tokens": max_out(agent, est_tokens(task))})
|
| 52 |
+
r.raise_for_status()
|
| 53 |
+
c = r.json()["choices"][0]
|
| 54 |
+
return c["message"].get("content") or "", c.get("finish_reason")
|
| 55 |
+
|
| 56 |
+
|
| 57 |
+
def infer_kind(task, code):
|
| 58 |
+
"""Task type shown to Jedi (its training data used: code, mc, num, math, exact)."""
|
| 59 |
+
if code:
|
| 60 |
+
return "code"
|
| 61 |
+
return "mc" if re.search(r"^\(?[A-J][.)]\s", task, flags=re.M) else "math"
|
| 62 |
+
|
| 63 |
+
|
| 64 |
+
def solve(task, threshold=0.7, code=False, kind=None, judge=None):
|
| 65 |
+
"""Run the ladder. Returns (final answer text, trace)."""
|
| 66 |
+
judge = judge or Jedi()
|
| 67 |
+
kind = kind or infer_kind(task, code)
|
| 68 |
+
rungs = [a for a in LADDER if a in endpoints()]
|
| 69 |
+
trace, text = [], ""
|
| 70 |
+
for i, agent in enumerate(rungs):
|
| 71 |
+
try:
|
| 72 |
+
text, finish = ask(agent, task, code)
|
| 73 |
+
except httpx.HTTPError as e:
|
| 74 |
+
if i == 0:
|
| 75 |
+
raise
|
| 76 |
+
trace.append({"agent": agent, "verdict": "unavailable", "error": repr(e)[:120]})
|
| 77 |
+
return text, trace # keep the best answer so far
|
| 78 |
+
if i == len(rungs) - 1:
|
| 79 |
+
trace.append({"agent": agent, "verdict": "final", "confidence": None, "escalate_to": None})
|
| 80 |
+
return text, trace
|
| 81 |
+
p = judge.p_correct(task, text, agent, kind, finish)
|
| 82 |
+
verdict = "accept" if p >= threshold else "escalate"
|
| 83 |
+
trace.append({"agent": agent, "verdict": verdict, "confidence": round(p if verdict == "accept" else 1 - p, 3),
|
| 84 |
+
"escalate_to": None if verdict == "accept" else rungs[i + 1]})
|
| 85 |
+
if verdict == "accept":
|
| 86 |
+
return text, trace
|
| 87 |
+
|
| 88 |
+
|
| 89 |
+
if __name__ == "__main__":
|
| 90 |
+
ap = argparse.ArgumentParser()
|
| 91 |
+
ap.add_argument("task", nargs="+")
|
| 92 |
+
ap.add_argument("--threshold", type=float, default=0.7, help="min P(correct) to accept an answer")
|
| 93 |
+
ap.add_argument("--code", action="store_true", help="ask agents for a Python code solution")
|
| 94 |
+
ap.add_argument("--kind", choices=["math", "num", "mc", "code", "exact"], help="task type (default: inferred)")
|
| 95 |
+
args = ap.parse_args()
|
| 96 |
+
answer, trace = solve(" ".join(args.task), args.threshold, args.code, args.kind)
|
| 97 |
+
for step in trace:
|
| 98 |
+
print(json.dumps(step), file=sys.stderr)
|
| 99 |
+
print(answer)
|
model.safetensors
ADDED
|
@@ -0,0 +1,3 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
version https://git-lfs.github.com/spec/v1
|
| 2 |
+
oid sha256:2319fb5813fe7baf027727e5ac7be4f46785e9835ec669317baf1c57496ab2d3
|
| 3 |
+
size 1976163472
|
tokenizer.json
ADDED
|
@@ -0,0 +1,3 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
version https://git-lfs.github.com/spec/v1
|
| 2 |
+
oid sha256:3fd169731d2cbde95e10bf356d66d5997fd885dd8dbb6fb4684da3f23b2585d8
|
| 3 |
+
size 11421892
|
tokenizer_config.json
ADDED
|
@@ -0,0 +1,30 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
{
|
| 2 |
+
"add_prefix_space": false,
|
| 3 |
+
"backend": "tokenizers",
|
| 4 |
+
"bos_token": null,
|
| 5 |
+
"clean_up_tokenization_spaces": false,
|
| 6 |
+
"eos_token": "<|im_end|>",
|
| 7 |
+
"errors": "replace",
|
| 8 |
+
"extra_special_tokens": [
|
| 9 |
+
"<|im_start|>",
|
| 10 |
+
"<|im_end|>",
|
| 11 |
+
"<|object_ref_start|>",
|
| 12 |
+
"<|object_ref_end|>",
|
| 13 |
+
"<|box_start|>",
|
| 14 |
+
"<|box_end|>",
|
| 15 |
+
"<|quad_start|>",
|
| 16 |
+
"<|quad_end|>",
|
| 17 |
+
"<|vision_start|>",
|
| 18 |
+
"<|vision_end|>",
|
| 19 |
+
"<|vision_pad|>",
|
| 20 |
+
"<|image_pad|>",
|
| 21 |
+
"<|video_pad|>"
|
| 22 |
+
],
|
| 23 |
+
"is_local": false,
|
| 24 |
+
"local_files_only": false,
|
| 25 |
+
"model_max_length": 131072,
|
| 26 |
+
"pad_token": "<|endoftext|>",
|
| 27 |
+
"split_special_tokens": false,
|
| 28 |
+
"tokenizer_class": "Qwen2Tokenizer",
|
| 29 |
+
"unk_token": null
|
| 30 |
+
}
|
train.py
ADDED
|
@@ -0,0 +1,18 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
"""Stage 3: full fine-tune of Qwen2.5-0.5B-Instruct into Jedi (loss on the one-word verdict only)."""
|
| 2 |
+
import os
|
| 3 |
+
from datasets import load_from_disk
|
| 4 |
+
from trl import SFTConfig, SFTTrainer
|
| 5 |
+
|
| 6 |
+
BASE = os.environ.get("BASE", "Qwen/Qwen2.5-0.5B-Instruct")
|
| 7 |
+
ds = load_from_disk("data/jedi_judge").select_columns(["prompt", "completion"])
|
| 8 |
+
|
| 9 |
+
cfg = SFTConfig(
|
| 10 |
+
output_dir="ckpt", num_train_epochs=2, per_device_train_batch_size=8, gradient_accumulation_steps=4,
|
| 11 |
+
learning_rate=2e-5, lr_scheduler_type="cosine", warmup_steps=30, weight_decay=0.0, bf16=True,
|
| 12 |
+
max_length=2048, completion_only_loss=True, logging_steps=20, eval_strategy="epoch", save_strategy="epoch",
|
| 13 |
+
save_total_limit=1, report_to=[], gradient_checkpointing=True, dataloader_num_workers=4,
|
| 14 |
+
)
|
| 15 |
+
trainer = SFTTrainer(model=BASE, args=cfg, train_dataset=ds["train"], eval_dataset=ds["val"])
|
| 16 |
+
trainer.train()
|
| 17 |
+
trainer.save_model("jedi-0.5b")
|
| 18 |
+
trainer.processing_class.save_pretrained("jedi-0.5b")
|