Voidstackloop commited on
Commit
0598ff3
·
verified ·
1 Parent(s): 2d25512

Add benchmark tables, scripts and report

Browse files
benchmarks/REPORT.md ADDED
@@ -0,0 +1,272 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # k448 ("Tessera", Qwen3.8-Flash-Next, 448 experts) - capability lab and speed report, 2026-10-25
2
+
3
+ All quality numbers: Colab L4 VM, llama.cpp, 120-task hard suite (HumanEval+, MATH-500, MMLU-Pro; 40 each; seed 1).
4
+ Wording rule used throughout: "beats Q4" only if the paired sign test is p < 0.05 in our favour.
5
+
6
+ ## 1. Verdict against Q4 (unsloth UD-IQ4_XS, 87 GiB)
7
+ - Hard test, no-think greedy: Q4 103/120, k448 101/120 (code 39/40 both, math 35/40 both, reasoning 29 vs 27).
8
+ - Paired sign test p = 0.754. **k448 MATCHES Q4 within noise. It does not beat it.**
9
+ - Speed with llama.cpp on the VM: 11.7 vs 8.2 tokens/s (+43%). Memory while running: 41.9 GiB resident, of which 6.3 GiB is real working memory and 35.5 GiB is the mapped model file; the 26.8 GiB n-gram table is lazy.
10
+ - Not compared: Strata's own ISTA-DASLab packs (IQ2_XS 39.2 GB, IQ3_XXS 47 GB) are the real size peers; k448 keeps about 47 GB resident.
11
+
12
+ ## 2. What raised capability (weights unchanged, run-time settings only)
13
+ | Setting | Result |
14
+ |---|---|
15
+ | Thinking, reasoning budget 4096, temp 0.6 / top_p 0.95 / top_k 20 | rescued 8 of 12 tasks the greedy run failed (math 5/5, reasoning 3/7); 9/15 on the full redo. About 3.8k tokens per task. |
16
+ | Resampling control (temp 0.6, no thinking) | 2/12 |
17
+ | 3-vote majority (temp 0.7) | 2/12, the same two tasks as resampling; no gain from voting |
18
+ | Thinking vs resampling, paired | 6 vs 0, exact sign test p = 0.031 (small n = 12, tasks pre-selected as failures) |
19
+ | 14 active experts instead of 10 | math 37 vs 35, reasoning 27 vs 27, p = 0.688: no significant gain; perplexity gap to Q4 shrinks but KLD to Q4 does not |
20
+ | MTP draft (llama.cpp) | x1.34 speed with identical answers |
21
+
22
+ Recommended profile: everyday use no-think greedy, 10 experts, MTP on. For hard math or reasoning turn thinking on with a 4096 budget (about twice the tokens). Do not use voting or 14 experts.
23
+
24
+ ## 3. Speed with Strata's engine (github.com/Niko1221/Strata, MIT, engine 0.1.51)
25
+ Same Colab VM (L4, 12 vCPU with AVX2 only, 54 GB RAM), k448 packed with Strata's own tool, no MTP head loaded:
26
+ | Run | Decode tokens/s |
27
+ |---|---|
28
+ | llama.cpp, no MTP | 11.7 |
29
+ | llama.cpp with MTP | about 16 |
30
+ | Strata, 256 tokens, `--spec 4` | 15.4 |
31
+ | Strata, 256 tokens, `--spec 2` | 18.4 |
32
+ | Strata, 1024 tokens, `--spec 2` | **27.2** |
33
+ - The 27.2 figure includes lookup drafting (37.5% of drafted tokens accepted) on a repetitive code answer; prose will gain less.
34
+ - Output of the 256-token run was coherent, correct Python. Quality of the Strata path has NOT been run through the hard suite.
35
+ - NOT measured on the RTX 4060 laptop. The VM's slow disk (12 MB/s random reads) and small RAM held Strata back here; the PC may differ either way.
36
+
37
+ Steps that worked on the VM (a source build; no installer scripts were run):
38
+ 1. `cmake -S . -B build -DCMAKE_BUILD_TYPE=Release -DSTRATA_ENABLE_CUDA=ON -DCMAKE_CUDA_ARCHITECTURES=89 && cmake --build build --target strata -j4`
39
+ 2. `STRATA_GGUF_PY=build/_deps/strata_llamacpp-src/gguf-py python3 tools/iq_pack.py --gguf <k448.gguf> --out packs/k448 --compat-bf16` (under a minute; 303 tensors served natively, 195 small tensors rounded to BF16, max error 0.0144)
40
+ 3. `python3 tools/make_profile.py --no-base --n-expert 448 --out k448-profile.bin` (the shipped profile is for 512 experts)
41
+ 4. `./build/strata --pack packs/k448 --tokens-file prompt.txt --native <k448.gguf> --ple-gguf <k448.gguf> --ple-io mmap --max-new 1024 --max-context 4096 --expert-profile k448-profile.bin --expert-cache auto --kv int8 --spec 2 --prefill 512`
42
+ (the binary takes no subcommand; native packs require `--native`, `--spec T>=2` and `--prefill`; on a fast NVMe drop `--ple-io mmap`)
43
+
44
+ ## 4. Open items
45
+ - Strata's MTP draft layer: needs about 5-6 GB fetched from the public Qwen/Qwen3.8-Flash-Next repo (tools/mtp_fetch.py). Not downloaded; awaiting the user's yes. Our llama.cpp-format MTP file cannot be used by Strata.
46
+ - Measure on the user's PC (the user downloads the k448 file themselves; no token is ever entered by Claude).
47
+ - Run the hard suite through Strata's server to confirm quality parity.
48
+ - Compare k448 against Strata's ISTA IQ3_XXS / IQ2_XS on the same tasks.
49
+ - Autonomous (agent-style) coding was never tested; only function-level tasks.
50
+
51
+ ## 5. The big test suite (`09_big_suite.py`, built 2026-10-25)
52
+ Why: 120 tasks only detect differences of about 10 points. This set has 4 domains x 300 tasks = 1200, all public benchmarks, every answer checked by code.
53
+
54
+ | Domain | 300 tasks = | Check |
55
+ |---|---|---|
56
+ | code | 120 HumanEval+ (all but HumanEval/32, whose own reference fails) + 180 MBPP sanitized | unit tests on the model's function |
57
+ | math | 225 MATH-500 integer answers (levels 2-5) + 75 AIME (AI-MO/aimo-validation-aime) | final line `#### <integer>` |
58
+ | reasoning | 200 MMLU-Pro (14 subjects round-robin, 10 options) + 100 BIG-Bench Hard (8 tasks) | final line `Answer: <x>` |
59
+ | English | 100 RACE-high reading comprehension + 100 CoLA grammar (half acceptable) + 100 writing prompts with machine-checked rules (15 rule types: word counts, paragraphs, bullets, case, keywords, start/end text, quotes, title, commas, P.S., letter count) | `Answer:` line, or the rules in code |
60
+
61
+ - **Nested**: the first 60 tasks of every domain are a prefix of the 300 (`--hard-n 60` = quick set, 240 tasks; `--hard-n 300` = full set). Results of the two can be pooled.
62
+ - **Checked**: selftest passes locally and on the VM (references pass, garbage fails, each rule checker passes its own good/bad example); all 420 code reference solutions run; the VM file has the same sha256 prefix as the local one (`5700b5f8682b8978`).
63
+ - **Run it**: `python3 07x_domain_eval.py run --suite big --hard-n 60 --seed 1 --no-think --name <n> --out results_<n>.json` plus the usual `--bin/--model/--ot/--extra` flags; compare with `07x_domain_eval.py compare a.json b.json` (paired sign test per domain).
64
+ - **Power** (paired exact sign test, 5%): about 5 points per domain at 300 tasks, about 10 points at 60, about 2.5 points when the four domains are pooled (1200). Treat the English rule-following prompts as a relative measure only: they are our own set, not IFEval, so they cannot be compared with published numbers.
65
+ - **Smoke run** (3 tasks per domain, llama.cpp + MTP, no-think, Colab L4): 12/12 pass, 4781 tokens in 471 s = about 400 tokens and 39 s per task, 10.2 tokens/s overall. That puts the quick set (240 tasks) at about 2.6 hours and the full set (1200 tasks) at about 13 hours for one setting and one model; thinking mode costs roughly 5-10x more tokens per task. A 12-task sample, so treat the times as rough; Strata at about 27 tokens/s on this machine would cut them by more than half, but it has not been run through this suite.
66
+ - **Known limits**: code is already near the ceiling (39/40 on HumanEval+), so code gains will be small; there is no agent-style coding test.
67
+
68
+ ## 6. Quick-set baseline (run 2026-10-25)
69
+ k448 + MTP draft, llama.cpp, no-think greedy, Colab L4, big suite `--hard-n 60` (60 tasks per domain = 240, seed 1). 137,690 tokens in 9,799 s (14.1 tokens/s overall, about 570 tokens and 41 s per task).
70
+
71
+ | Domain | Pass | Parts |
72
+ |---|---|---|
73
+ | code | 55/60 (91.7%) | HumanEval+ 22/24, MBPP 33/36 |
74
+ | math | 39/60 (65.0%) | MATH-500 39/45, AIME 0/15 |
75
+ | reasoning | 51/60 (85.0%) | MMLU-Pro 31/40, BBH 20/20 |
76
+ | English | 52/60 (86.7%) | RACE 18/20, CoLA 17/20, rule-following 17/20 |
77
+ | **All** | **197/240 (82.1%)** | |
78
+
79
+ - **AIME 0/15 is a token-cap result, not a measure of ability.** The no-think runs on this suite are capped at 2048 tokens (the harness raises `--max-tokens` to at least 2048), and the AIME answers used 2048 tokens on average, so every one ran to the cap. MATH-500 averaged 1,024 tokens, so some of its misses are probably cut off too. Math is the domain where thinking mode (budget 4096) should move the number most.
80
+ - Strongest domain is code (HumanEval+ and MBPP are near the ceiling, as noted in section 5); BBH is a clean 20/20.
81
+ - With 60 tasks per domain only differences of about 10 points are detectable (section 5), so this is a reference point for later changes, not a ranking. It has not been run on Q4 yet, so it says nothing about k448 vs Q4 on this suite.
82
+ - Raw per-task results: `results_big60_k448.json` on the L4 VM (`/content/q38/eval/`); compare later runs with `07x_domain_eval.py compare`.
83
+
84
+ ## 7. The harder coding suite (`10_code_hard.py`, built 2026-10-25)
85
+ Why: HumanEval+ and MBPP (92% for k448) are small textbook functions that top models already pass. These two are the harder, widely used replacements that we can run and score by code without a token.
86
+
87
+ | Benchmark | What it tests | In the suite |
88
+ |---|---|---|
89
+ | LiveCodeBench (code_generation_lite, release 6) | contest programming: the model writes a whole program (AtCoder, stdin/stdout) or a LeetCode class; every one of about 40 hidden tests must pass | 175 problems from 2025-01 to 2025-04 (newer than most training data); ids carry the difficulty (easy 43, medium 52, hard 80) |
90
+ | CRUXEval | code understanding: predict the output of a function for an input (CRUXEval-O), or find an input that gives a given output (CRUXEval-I) | 800 functions, half O and half I per task list |
91
+
92
+ - **Run it**: `python3 07x_domain_eval.py run --suite codehard --hard-n 60 --max-tokens 4096 --ctx 8192 --no-think --seed 1 ...` (60 = quick set: 60 LiveCodeBench + 60 CRUXEval; nested prefixes, so `--hard-n 100` extends it). New categories `lcb` and `crux` in the compare table.
93
+ - **Checked**: selftest passes locally and on the VM (lookup-table "solutions" pass every test of 4 stdin and 4 LeetCode-style problems and a wrong solution fails; CRUXEval references pass, garbage fails); all 800 CRUXEval references run; all 175 LiveCodeBench test sets decode (the private tests are a pickled string, loaded with a loader that refuses every global, so a dataset cannot run code). The VM file has the same sha256 prefix as the local one (`2c9c82f1effce90a`).
94
+ - **Expect low numbers**: contest problems are hard. Without thinking, most hard AtCoder problems will fail; thinking mode will matter far more here than on the old sets. The release is recent, but the model's training cutoff is unknown, so some problems may still be familiar.
95
+ - **Time**: LiveCodeBench answers are long (about 1-2k tokens). Roughly 2 hours for the 60+60 quick set and 3.5 hours for 100+100 at llama.cpp speeds; thinking mode several times that.
96
+ - **Not included, and why**: SWE-bench Verified and the Aider polyglot benchmark need a Docker or multi-language toolchain and an agent loop that edits real repositories, which the Colab VM cannot host; BigCodeBench-Hard (148 library-heavy tasks) needs dozens of heavy libraries installed first and is the next candidate if we want one more.
97
+
98
+ ## 8. Hard coding results (run 2026-10-26, Colab A100, llama.cpp + MTP draft, seed 1)
99
+ 60 LiveCodeBench problems + 60 CRUXEval tasks, the same 120 tasks in both runs. Thinking: budget 4,096 tokens, temperature 0.6, top-p 0.95, top-k 20. No-think: greedy. Both: 8,192-token answer cap, all experts on the GPU. Per-task tables: `results/k448_code_think_compact.csv`, `results/k448_code_nothink_a100_compact.csv`.
100
+
101
+ | | Thinking (4k budget) | No-think (greedy) |
102
+ |---|---|---|
103
+ | **All 120** | **99/120 (82.5%)** | **96/120 (80.0%)** |
104
+ | LiveCodeBench | 41/60 (68.3%) | 38/60 (63.3%) |
105
+ | - easy / medium / hard | 12/12, 15/20, 14/28 | 12/12, 15/20, 11/28 |
106
+ | CRUXEval | 58/60 (96.7%) | 58/60 (96.7%) |
107
+ | - CRUX-O / CRUX-I | 28/30, 30/30 | 28/30, 30/30 |
108
+ | Tokens (per task) | 277,274 (2,310) | 262,258 (2,185) |
109
+ | Time (per task), A100 | 3,689 s (31 s) | 2,888 s (24 s) |
110
+
111
+ - **Thinking does not beat no-think here.** Paired exact sign test: only thinking passes 6 tasks (CRUX-O 155; LCB hard 3777, abc388_g, arc192_d; LCB medium 3722, 3779), only no-think passes 3 (CRUX-O 445; LCB medium 3805, abc400_d), p = 0.508. Both runs are inside each other's noise; the earlier thinking gain (section 2) was on math and reasoning tasks that greedy had already failed, not on coding.
112
+ - **CRUXEval is at its ceiling** (96.7% both ways), so the room for improvement is in LiveCodeBench, mostly the hard problems (about half of the 60 problems, 46% of the pool).
113
+ - **Both modes lose their points by running out of room.** Thinking: all 19 LiveCodeBench failures used the whole 4,096-token budget (4,268 tokens or more); problems that finished thinking earlier went 18 for 18, budget-hit problems 23 for 42. No-think: 17 of its 22 LiveCodeBench failures ran to the 8,192-token answer cap (13 of the 28 hard problems and 5 of the 20 medium ones hit the cap). The budget is correlated with difficulty, so this shows where the failures sit, not by itself that more room would fix them; the 16k-budget rerun (below) tests that.
114
+ - **Why the 19 thinking-run LiveCodeBench failures failed** (re-running the problem's visible sample tests on the model's last answer): 12 give a wrong answer on a sample, 4 crash at run time, 2 pass the samples but fail hidden tests, 1 has no code block (it ran to the 8,192 cap). So 16 of 19 fail the problem's own examples, which the model never checked; this is the case a run-the-tests-and-repair loop (the Claude Code harness) is built for. The two that pass the samples get no feedback from `--retry`.
115
+ - **Speed.** Overall 75 tokens/s thinking and 91 tokens/s no-think on the A100 with all experts on the GPU; the L4 managed about 14 tokens/s overall and your PC has not been measured, so a 16k budget could cost many minutes per hard problem.
116
+ - **Caveat.** 60 problems per benchmark: the 95% interval on a LiveCodeBench rate is about +/-12 points, and six tasks of difference between two runs is within sampling noise.
117
+
118
+ ### 8.1 Rerun of the failed tasks with a 16,384-token thinking budget (run 2026-10-26, A100)
119
+ The 21 tasks the 4k-budget thinking run failed (19 LiveCodeBench + 2 CRUXEval) were run again with `--reasoning-budget 16384` (context 24,576, answer cap 20,480, same temperature and sampling). Table: `results/k448_code_think16k_redo_compact.csv`.
120
+
121
+ | | Rescued | Still failing |
122
+ |---|---|---|
123
+ | LiveCodeBench medium (5 failed before) | **5 of 5** | 0 |
124
+ | LiveCodeBench hard (14 failed before) | **4 of 14** | 10 |
125
+ | CRUXEval (2 failed before) | 0 of 2 | 2 (both finished early; the budget was not their problem) |
126
+ | **Total** | **9 of 21** | 12 |
127
+
128
+ - **Estimate, not a measurement of all 120:** adding the 9 rescues to the 99 earlier passes gives **108/120 (90%)**, with LiveCodeBench at **50/60 (83.3%, 95% interval about 72-91%)**, so 105/120 is reached on this basis. The 99 tasks that passed before were not rerun; some of the 23 that passed on the very edge of the 4k budget could flip either way under a different sample. A clean figure needs all 120 tasks rerun at 16k (about 2 hours of A100 time; not run).
129
+ - **Mostly the budget, not luck.** A plain resample rescued 2 of 12 failures in an earlier control (about 17%), which would predict about 3 of 19 here; 9 of 19 is well above that. Some of the 9 are still likely luck.
130
+ - **The budget gets used up either way.** All 19 LiveCodeBench tasks used 16,400 tokens or more (average 17,300), including the 9 that passed and one that hit the 20,480 cap. So 16k is not a "finish early" budget for hard problems; a larger budget (32k) may rescue more of the remaining 10, which has not been tested.
131
+ - **Cost.** 333,605 tokens in 4,516 s for the 21 tasks (238 s per LiveCodeBench task at about 74 tokens/s on the A100), about 4 times the tokens per task of the 4k run. On the L4 earlier (14 tokens/s) this would be about 20 minutes per hard problem; your PC has not been measured.
132
+
133
+ ### 8.2 Retry on top of the 16k budget (run 2026-10-26, A100)
134
+ The 10 LiveCodeBench problems still unsolved after the 16,384-token rerun were given up to 2 repair turns (`--retry 2`). After a failed answer the model is shown its first failing VISIBLE sample test (the problem's own examples; hidden tests are used only for the final score) and asked for a corrected solution, again with a 16,384-token thinking budget. The first attempt was the stored 16k answer, so any rescue comes from the repair turns. Table: `results/k448_code_think16k_retry_compact.csv`.
135
+
136
+ | Result | Tasks |
137
+ |---|---|
138
+ | Rescued by 1 repair turn | arc194_d, 3674 |
139
+ | Rescued by 2 repair turns | arc192_e, arc196_b |
140
+ | Still failing after 2 turns | arc196_d, abc398_g, arc190_c, arc193_b |
141
+ | No feedback possible (the code already passes the visible samples, so the loop has nothing to show) | abc392_f, abc387_f |
142
+
143
+ - **4 of 10 rescued (all hard problems).** Combined estimate: 99 (4k run) + 9 (16k budget) + 4 (repair turns) = **112/120 (93.3%)**, LiveCodeBench **54/60 (90.0%)**, CRUXEval 58/60. That is above the 105/120 target and, on this sample, close to the published LiveCodeBench v6 figures in section 9 (88.8 to 91.9).
144
+ - **Caveats, stated plainly.** (1) This is a chain of estimates: the 99 tasks that passed in the first run were never rerun under the 16k or retry settings. A clean number needs all 120 tasks rerun (run 5, below). (2) The repair turns are an agent-style loop with test feedback; the published single-shot figures are not, so the comparison favours k448. (3) Selection effect: only previously failed tasks were retried, and plain resampling alone rescues some (2 of 12 in an earlier control). (4) 60 problems: the 95% interval on 54/60 is about 80-95%.
145
+ - **The two failure kinds the loop cannot help** are visible in the data: 2 of 10 tasks produce code that passes the examples and fails hidden tests, and 4 of 10 never get right even with feedback.
146
+ - **Cost.** 240,734 tokens in 3,411 s for the 10 tasks (341 s per task on the A100), all of it in repair turns because the first answers were reused.
147
+
148
+ ### 8.3 Clean rerun of all 120 tasks with a 16,384-token thinking budget (run 2026-10-26, A100)
149
+ The same 120 tasks as section 8, all of them rerun with `--reasoning-budget 16384` (context 24,576, answer cap 20,480, temperature 0.6 / top-p 0.95 / top-k 20, single attempt, no repair turns). This replaces the chained estimate of 8.1. Table: `results/k448_code_think16k_full_compact.csv`.
150
+
151
+ | | 4k budget (section 8) | **16k budget, clean rerun** | No-think |
152
+ |---|---|---|---|
153
+ | **All 120** | 99 (82.5%) | **107 (89.2%)** | 96 (80.0%) |
154
+ | LiveCodeBench (60) | 41 (68.3%) | **49 (81.7%, interval about 70-90%)** | 38 (63.3%) |
155
+ | - easy / medium / hard | 12/12, 15/20, 14/28 | 12/12, 19/20, 18/28 | 12/12, 15/20, 11/28 |
156
+ | CRUXEval (60) | 58 | 58 | 58 |
157
+ | Tokens (per task) | 277,274 (2,310) | 723,767 (6,031) | 262,258 (2,185) |
158
+ | Time (per task), A100 | 3,689 s (31 s) | 9,992 s (83 s) | 2,888 s (24 s) |
159
+
160
+ - **A longer thinking budget helps, and the gain is statistically solid.** Paired exact sign test, 16k vs 4k on the same 120 tasks: only the 16k run passes 9 tasks, only the 4k run passes 1, **p = 0.021**. That is the first coding result in this report that clears p < 0.05. The earlier chained estimate (99 + 9 rescues = 108, section 8.1) held: the clean run gave 107.
161
+ - **The gain is all in LiveCodeBench, and mostly medium and hard problems** (medium 15 -> 19 of 20, hard 14 -> 18 of 28). CRUXEval did not move (58/60 at every setting), as expected: those answers finish early.
162
+ - **It costs 2.6 times the tokens**: about 6,000 tokens and 83 s per task on the A100 (about 16,000 tokens per medium or hard LiveCodeBench problem; easy problems and CRUXEval stay short). On the 14 tokens/s measured on the L4 earlier that is about 20 minutes per hard problem; your PC has not been measured.
163
+ - **11 LiveCodeBench problems still fail**: arc192_e, arc196_d, arc194_d, abc392_f, abc398_g, arc190_c, abc387_f, abc393_e, medium arc192_a, arc193_b, 3674. Three of them (arc192_e, arc194_d, 3674) were rescued by repair turns in the earlier retry run (section 8.2, a different sample of answers), so repair turns on top of 16k would probably add a few more tasks (roughly 110/120), but that combination was not rerun cleanly.
164
+ - **Noise.** Single run per setting; 60 problems per benchmark. Run-to-run sampling noise on LiveCodeBench is a few problems, which is why the paired test (p = 0.021) matters more than the raw 8-task gap.
165
+
166
+ ### 8.4 A 32,768-token budget on the problems that still fail at 16k (run 2026-10-26, A100)
167
+ The 11 LiveCodeBench problems that failed in the clean 16k rerun (section 8.3) were run again with `--reasoning-budget 32768` (context 40,960, answer cap 36,864, single attempt). Table: `results/k448_code_think32k_redo_compact.csv`.
168
+
169
+ - **2 of 11 pass** (`abc393_e` and the medium problem `arc192_a`). Both had passed in other runs of the same model at a smaller budget (`abc393_e` at 4k, `arc192_a` at 16k in the 21-task rerun), so they are better read as sampling luck on borderline problems than as a gain from the larger budget. The **nine hard problems that failed at 16k still fail at 32k**: arc192_e, arc196_d, arc194_d, abc392_f, abc398_g, arc190_c, abc387_f, arc193_b, 3674.
170
+ - **Every one of the 11 used the whole 32k budget** (33,000 to 35,300 tokens). The model keeps thinking until it is stopped rather than converging on an answer for these problems.
171
+ - **Cost:** 373,556 tokens in 5,438 s (about 34,000 tokens and 494 s per problem). Doubling the budget again cost twice as much per problem for no measurable gain.
172
+ - **Conclusion:** the budget gain stops between 16k and 32k on this set. 16k is the sensible setting; for the remaining hard problems the repair-turn loop of section 8.2 (feedback from the problem's own sample tests) rescued more (4 of 10) than a bigger budget did (0 of 9 hard ones).
173
+
174
+ ## 9. Comparison with strong models (published numbers, and where k448 sits)
175
+ Source: the Qwen3.8-Flash-Next model card on Hugging Face (August 2026). All numbers are vendor-reported, not independently verified; the card does not say whether a score is thinking or non-thinking (the model thinks by default) and does not state the thinking budget.
176
+
177
+ | Benchmark | Flash-Next (parent, full precision) | Qwen3.8-27B | Qwen3.7-Plus | DeepSeek-V4-Flash-0731 | Claude-Opus-4.6 (Max) |
178
+ |---|---|---|---|---|---|
179
+ | LiveCodeBench v6 | 91.9 | 90.3 | 89.6 | 90.6 | 88.8 |
180
+ | k448, thinking 4k budget (ours, 60-problem sample) | 68.3 (95% interval 56-79) | | | | |
181
+ | k448, no-think (ours, same 60 problems) | 63.3 (95% interval 51-74) | | | | |
182
+ | k448, 16k budget, clean rerun of all 60 problems (section 8.3) | **81.7 (interval about 70-90)** | | | | |
183
+ | k448, 16k budget on the failed tasks (estimate, section 8.1) | 83.3 (interval about 72-91) | | | | |
184
+ | k448, 16k budget + test-feedback repair turns (estimate, section 8.2; an agent-style loop, not single-shot) | 90.0 (interval about 80-95) | | | | |
185
+
186
+ - **Only LiveCodeBench lines up.** CRUXEval, HumanEval, MBPP, MATH-500, MMLU-Pro and BBH are not in the card, so no published figure for those is quoted here.
187
+ - **Not like for like.** Our LiveCodeBench is 60 problems sampled from a 175-problem slice (2025-01 to 2025-04) of the release 6 data, answered with a 4,096-token thinking budget and an 8,192-token answer cap; the vendor figure is the full v6 set, presumably with a far longer budget. With 60 problems one run has a 95% interval of roughly +/-12 points. Expect our number to sit below the published one; the gap mixes quantization, budget and sampling, and cannot be split without running the full-precision model (or Q4) through the same harness.
188
+ - **Where k448 sits.** With a 4,096-token thinking budget k448 scores 68.3% on LiveCodeBench against the published 91.9% for the full-precision parent and 88.8% for Claude-Opus-4.6 (Max). With a 16,384-token budget, rerun cleanly on all 60 problems (section 8.3), it scores **81.7% (interval about 70-90%)**: a significant improvement over 4k (paired p = 0.021), and the published 91.9 now sits just above the top of our interval, while the other published figures (88.8 to 90.6) sit inside it. So with a long enough thinking budget k448 is plausibly within the range of the published models on this benchmark, but 60 problems cannot separate it from them. Adding up to two repair turns with the problems' own sample tests (section 8.2) would add a few more problems, but that is an agent-style loop with feedback, which the published single-shot numbers do not use, and it was measured on a different sample of answers. What remains could be quantization, a still longer budget (32k test: section 8.4, which found no gain over 16k), or sampling noise; separating them needs a Q4 or full-precision run on the same harness.
189
+ - **We did not run the other models ourselves** (no API access from this setup), so every competitor number above is as published.
190
+
191
+ ## 10. Reasoning: does a bigger thinking budget help? (run 2026-10-26, A100)
192
+ Quick reasoning set from the big suite (`--hard-n 60 --only reason`): 40 MMLU-Pro questions (10 answer options, 14 subjects) and 20 BIG-Bench Hard tasks, seed 1, same tasks every run. The no-think run is the baseline; the 10 questions it failed were then redone with thinking at two budgets (the 50 it passed were not rerun). Tables: `results/k448_reason_nothink_compact.csv`, `..._think4k_compact.csv`, `..._think16k_compact.csv`.
193
+
194
+ | Setting | Result | Tokens (per task) |
195
+ |---|---|---|
196
+ | No-think, 2,048-token cap (60 tasks) | **50/60 (83.3%)**: BBH 20/20, MMLU-Pro 30/40 | 40,632 (677) in 468 s |
197
+ | Thinking, 4,096 budget, the 10 failures | **5 of 10 rescued** | 31,492 (3,149) in 393 s |
198
+ | Thinking, 16,384 budget, the 10 failures | **3 of 10 rescued** | 106,193 (10,619) in 1,315 s |
199
+
200
+ - **Every failure was an MMLU-Pro question.** BBH (logic puzzles, tracking, date and fallacy questions) was 20/20 without thinking; there is nothing to gain there. 6 of the 10 failures ran into the 2,048-token cap without producing an answer, so part of the no-think failure is just running out of room.
201
+ - **Thinking at 4k rescued 5 of the 10** (mmlu11759, 4613, 12187, 11789, 11236). Applied to the whole set that would be about 55/60 (91.7%), but only the failures were redone, so that is an estimate. The earlier lab on the L4 found the same thing (thinking at 4k rescued 3 of 7 reasoning failures).
202
+ - **A 16k budget did not help more; it rescued fewer (3 of 10)** at 3.4 times the tokens. Rescued at 16k: mmlu11759, 12187, 11236 (all three also rescued at 4k). On MMLU-Pro, which mostly tests knowledge and short multi-step reasoning, thinking longer than a few thousand tokens just does not buy accuracy; this is the opposite of the coding result (section 8.3), where the extra budget mattered.
203
+ - **How much is luck?** Resampling alone rescued 2 of 12 failures in an earlier control (about 17%). Against that rate, 5 of 10 is unlikely by chance (rough one-sided binomial p about 0.016) and 3 of 10 is not (p about 0.22). The 10 questions and the 17% control rate are both small, so treat this as suggestive, not proven. A proper test would rerun all 60 under each setting.
204
+ - **Two failures never flipped** (mmlu527, mmlu4987): short wrong answers in every setting, probably knowledge gaps or ambiguous questions rather than a budget problem.
205
+ - **Recommendation for reasoning:** thinking with a 4,096-token budget, not more. Use 16k only for coding (section 8.3).
206
+
207
+ ## 12. Agentic coding on SWE-bench Verified Mini through Claude Code (run 2026-10-26, A100)
208
+ The real Claude Code CLI (2.1.197, headless `claude -p`, run as an unprivileged user) was pointed at k448 (llama-server, `/v1/messages`, thinking off, 65,536-token context, MTP draft) and given 20 instances of SWE-bench Verified Mini: 10 django and 10 sphinx whose own gold patch reproduces without Docker here (37 of the 50 instances do: 25 django + 12 sphinx; the 13 old sphinx 3.x instances fail even with the gold patch and are excluded). Grading uses swebench 4.0.4's own log parsers on FAIL_TO_PASS and PASS_TO_PASS. Runner `13_swe_native.py`, proxy `14_msg_proxy.py`. Tables: `results/k448_swe_compact.csv` (50-turn pass) and `results/k448_swe_t100_compact.csv` (100-turn rerun of the 8 misses).
209
+
210
+ | | Result |
211
+ |---|---|
212
+ | As run, 50-turn cap | **12/20 (60%)**: django 8/10, sphinx 4/10 |
213
+ | The 8 misses rerun with a 100-turn cap | +3 (django-11790, sphinx-9229, sphinx-9230) |
214
+ | As run, combined (a second attempt for the misses) | **15/20 (75%)**, 95% interval about 53-89% |
215
+ | **Resolved with no lookup of the upstream project on the internet** | **7/20 (35%)**, interval about 18-57%: 6 at 50 turns (django 11880, 11951, 11999, 12039, 12050; sphinx 9367) plus django-11790 in the rerun |
216
+
217
+ ### The headline number is not clean: the agent went to the internet
218
+ The sandbox had open internet and Claude Code had its normal tools (Bash, WebFetch, WebSearch). The transcripts show it used them to look for the upstream fix:
219
+ - In the first django-11790 run the agent searched GitHub for the upstream pull request and went to fetch it (it wrote that it had found the upstream PR and would fetch the actual fix).
220
+ - **8 of the 15 resolved instances consulted the internet**: django-11848 (downloaded raw files from GitHub), 11885 (listed and cloned upstream django), 11964 (fetched django 3.1.3's `enums.py`, which already contains the fix), sphinx-10323 (listed and cloned upstream sphinx, one WebFetch), 10466 (WebFetch plus a GitHub issue lookup), 9320 (GitHub issue and timeline lookups), 9229 (fetched sphinx 4.1.0 source and searched issues), 9230 (nine WebFetch calls and an issue search). The other 7 resolved without any such lookup (django-11790's rerun only downloaded dependency packages).
221
+ - Not checked: whether the local git clone still holds future commits. Several transcripts ran `git log --all` or `git show <sha>`.
222
+ - So **7/20 is a floor** (a lookup does not prove it was needed) and **15/20 is a ceiling**; the true no-help rate is somewhere between. The 15 is also a best of two attempts for the 8 reruns.
223
+ - Official SWE-bench style runs do not give the agent the web, so the as-run number cannot be compared with leaderboard figures.
224
+
225
+ ### Numbers
226
+ - **Tool calls:** 912 in the 50-turn pass, 62 errors (6.8%); 694 in the rerun, 36 errors (5.2%).
227
+ - **Turns and time:** 45.8 turns and 343 s per instance on average at the 50-turn cap; 87.1 turns and 668 s in the rerun. The 28 agent runs took 12,191 s (3.4 h) of A100 time, about 23 compute units.
228
+ - **When the agent stopped on its own it was usually right:** 9 of 20 runs ended with the agent declaring it was done, and 8 of those 9 were resolved. The 11 runs cut off by the turn cap resolved 4.
229
+
230
+ ### Failure modes (per-instance results plus the transcripts I read)
231
+ 1. **Turn cap with the work unfinished:** 11 of 20 in the first pass, 7 of those unresolved.
232
+ 2. **Context overflow in long runs:** 5 of the 8 reruns (sphinx-10435, 10673, 11510, 8265, 9229) died with HTTP 400, "request (65,5xx to 66,3xx tokens) exceeds the available context size (65,536 tokens)". Claude Code does not know the server window is 65k, so it never compacts. The GPU holds 49.7 of 80 GB at 65k, so a 128k context fits.
233
+ 3. **A fix that passes the agent's own checks but not the target tests:** django-11815 (1 of 2 target tests, the same at 50 and 100 turns), sphinx-10673 (0/1, patches of 164 and 175 lines), 11510 (0/2), 8265 (0/1), 9230 at 50 turns (0/1).
234
+ 4. **Empty patch after long exploration:** django-11790 and sphinx-8265 and 9229 at 50 turns, sphinx-10435 in the rerun.
235
+ 5. **A malformed tool call:** one WebFetch call in sphinx-10466 had template markup (`</function></tool_call><tool_call><function=WebFetch>`) inside its URL argument; one example seen, not counted across all runs.
236
+ 6. **Environment friction:** in the first django-11790 transcript the agent spent turns finding a Python that works for the unprivileged user (one interpreter path returned permission denied) and imported Django through the system Python with a path hack.
237
+ 7. **An oversized patch:** the resolved sphinx-9229 patch is 1,657 lines (the other resolved ones are 12 to 215); it passed with no regressions but would not survive code review. Contents not inspected.
238
+
239
+ ### Setup caveats
240
+ - Thinking off (`enable_thinking` false), 65,536 context, sampling left to Claude Code and the server defaults.
241
+ - **Claude Code needs the rewriting proxy:** Qwen3.8's chat template raises "System message must be at the beginning" for the system-role entry Claude Code sends after the first user turn. The proxy folds it into the last user message; 1,594 requests went through, all 200 except the 5 context overflows.
242
+ - Docker-free environments, not the official images: uv venvs, `setuptools<70`, `Pygments==2.18.0`, Python 3.8 for django (the images use older versions), 37 of 50 instances reproduce.
243
+ - 20 instances: the interval is about +/-18-20 points, so 60% and 75% are not different from each other statistically.
244
+ - **Section 9's published agentic table (SWE-bench Pro, Multilingual, DeepSWE, NL2Repo, Toolathlon) is a different, harder set of benchmarks and is not comparable.** There is no published number for this exact setup.
245
+
246
+ ### Verdict
247
+ k448 under Claude Code can do small, localized bug fixes: the 7 clean resolves are short patches (12 to 215 lines) at about 6 minutes each on the A100, and a "done" from the agent was right 8 times out of 9. It is not shown to be a daily coding agent. The no-help rate is between 35% and 75% on 20 instances, hard tasks hit the turn cap or the 65k context, and the agent does look up upstream fixes when it can, so its own skill cannot be separated from the lookups in this run. Speed on your PC is not measured (14 tokens/s on the L4 earlier would make 6 minutes into about 30).
248
+
249
+ **Next experiment, proposed and not run:** the same 20 instances offline (network blocked for the agent user, WebFetch and WebSearch disallowed, future git objects removed), a 128k context, the same turn caps, and a paired comparison with this table. About 2.5 to 3 hours of A100, roughly 17 to 20 units.
250
+
251
+ ## 13. Overnight summary (2026-10-26, A100)
252
+ A100 time is from each run's own clock; units are time x 6.77 per hour, a rough figure that leaves out loading and idle time.
253
+
254
+ | # | Experiment | Result | Test | A100 time (about units) |
255
+ |---|---|---|---|---|
256
+ | 1 | Coding, 4k thinking budget (120 tasks) | 99/120 (LCB 41/60, CRUX 58/60) | baseline | 3,689 s (6.9) |
257
+ | 2 | Coding, no-think | 96/120 (LCB 38/60) | vs 4k: p = 0.508 | 2,888 s (5.4) |
258
+ | 3 | Redo of the 21 failures at 16k | 9 of 21 rescued | luck baseline about 17% | 4,516 s (8.5) |
259
+ | 4 | Two repair turns on the 10 still failing | 4 of 10 rescued (chained estimate 112/120) | estimate only | 3,411 s (6.4) |
260
+ | 5 | **Coding, clean rerun of all 120 at 16k** | **107/120 (89.2%), LCB 49/60** | vs 4k: p = 0.021 | 9,992 s (18.8) |
261
+ | 6 | 32k on the 11 still failing | 2 of 11 (likely luck) | none | 5,438 s (10.2) |
262
+ | 7 | Reasoning, no-think (60 tasks) | 50/60 | baseline | 468 s |
263
+ | 8 | Reasoning, 4k thinking on the 10 failures | 5 of 10 rescued | p about 0.016 vs luck baseline | 393 s |
264
+ | 9 | Reasoning, 16k thinking on the 10 failures | 3 of 10 rescued | p about 0.22 vs luck baseline | 1,315 s (rows 7 to 9: 4.1) |
265
+ | 10 | SWE-bench Verified Mini through Claude Code, 50 turns | 12/20 as run; 6/20 with no web lookup | none | 6,850 s (12.9) |
266
+ | 11 | Rerun of the 8 misses, 100 turns | +3 as run (+1 with no lookup) | none | 5,341 s (10.0) |
267
+
268
+ - **Coding:** a 16k thinking budget is the setting to use (107/120, significantly better than 4k); 32k adds nothing; repair turns on top rescue a few hard problems.
269
+ - **Reasoning:** thinking at 4k, not more; a bigger budget did not help.
270
+ - **Agentic:** 7 to 15 of 20, depending on whether web lookups count; not established until the offline rerun.
271
+ - **Units:** 84.10 at 06:20, 58.12 at 10:13, 51.37 at 11:13, **45.17 at 12:05**, burning 6.77 per hour while the runtime stays connected, idle or not.
272
+ - **A100 runtime:** disconnected and deleted at 12:10 on 2026-10-26 at your request, after the units reading of 45.17 at 12:05 (the balance cannot be read without a runtime, so the final figure is a little lower). The k448 and MTP files on that VM came from Hugging Face (download cache with the same commit hash), so they are re-downloadable; the raw SWE-bench transcripts, results JSONs and logs on the VM are gone (the compact CSVs and the report are saved locally). llama.cpp PR 30594 was not touched.
benchmarks/SPEED_PC.md ADDED
@@ -0,0 +1,27 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # One-token routed-expert matvec on this PC (2026-10-26)
2
+
3
+ Tool: `bench.c` (build with `build.bat`, run with `run.bat <threads>`), CPU backend of `C:\Projects\llama.cpp-strata\build-clang`
4
+ (AVX2 + AVX-VNNI, no AVX-512), i7-13620H, DDR5-5200, 10 threads. 10 of 128 experts per op, ids rotated so the weights stream from RAM.
5
+ Shapes are k448-like guesses: gate/up k=2048 m=1024, down k=1024 m=2048. A 78 GB download was running in the background (light CPU use).
6
+
7
+ | type | gate/up Gweights/s (GB/s) | down Gweights/s (GB/s) | note |
8
+ |---|---|---|---|
9
+ | **iq2_s** (k448 gate/up) | 96.7 (31.0) | 90.4 (29.0) | compute-bound: about half of what RAM allows |
10
+ | **iq4_nl** (k448 down) | 93.4 (52.5) | 101.2 (57.0) | close to RAM speed |
11
+ | iq3_xxs | 61.4 (23.5) | 70.3 (26.9) | the slow one (Strata's model type) |
12
+ | q2_K | 183.1 (60.1) | 174.7 (57.3) | what a RAM-speed 2-bit kernel looks like |
13
+ | q4_K | 115.9 (65.2) | 118.8 (66.8) | RAM ceiling is about 65 GB/s |
14
+ | q4_0 | 112.7 (63.4) | 114.6 (64.5) | |
15
+ | q6_K | 71.7 (58.8) | 63.0 (51.7) | |
16
+
17
+ Reading: with k448's types the CPU expert work is about 95 Gweights/s. An IQ2_S kernel at RAM speed would be about 190 Gweights/s.
18
+ Estimate (not measured on the model): 2.36 B routed weights per token / 95 G = about 25 ms per token if every expert ran on the CPU.
19
+
20
+ ## Server baseline on the model (Minecraft closed, Overwolf on)
21
+ no MTP 17.5 tok/s; MTP 1 draft 20.3-20.7; MTP 2 drafts 21-22 (acceptance 93%); GPU cache 645 MiB, hit rate about 14%. With Minecraft running: 11.7 no-MTP. Bigger cache + MTP draft fails to load (bug).
22
+
23
+ ## All-VRAM test
24
+ no MTP, cache 3200 MiB, margin 250, ubatch 512: 20.1-22.5 tok/s, hit 52%, VRAM 7947/8188. MTP2 + cache 1800 (margin 700): 21.4-22.4, hit 29%. Cache 2400+ with MTP: draft fails to load (VRAM). They do not stack.
25
+
26
+ ## Cache/draft, long prompts, kernel (2026-10-26)
27
+ Draft on CPU + cache 3000: 21.6 tok/s (no gain). Prefill: 5,198 tokens at 164 tok/s; repeat prompt 0.2 s. Decode on prose explanation 14-16 tok/s (draft acceptance 65% vs 93% for code). IQ2_S kernel already AVX2 with table gathers; est. upside under 10%, not written.
benchmarks/results/k448_code_nothink_a100_compact.csv ADDED
@@ -0,0 +1,121 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ id,pass,tokens
2
+ lcb_hard_3744,1,2060
3
+ lcb_easy_3778,1,85
4
+ lcb_easy_abc392_a,1,177
5
+ lcb_medium_arc195_a,1,3119
6
+ lcb_hard_abc397_g,0,8192
7
+ lcb_hard_arc192_e,0,7505
8
+ lcb_hard_arc196_d,0,8192
9
+ lcb_hard_abc400_e,1,1019
10
+ lcb_hard_arc194_d,0,8192
11
+ lcb_medium_3779,0,8192
12
+ lcb_hard_abc392_f,0,818
13
+ lcb_easy_abc395_a,1,155
14
+ lcb_medium_3786,1,1317
15
+ lcb_hard_arc195_e,0,8192
16
+ lcb_hard_abc398_d,1,3789
17
+ lcb_hard_abc398_g,0,8192
18
+ lcb_medium_abc392_d,1,512
19
+ lcb_easy_abc393_a,1,518
20
+ lcb_hard_abc391_g,1,1557
21
+ lcb_hard_abc394_g,0,8192
22
+ lcb_medium_3805,1,8192
23
+ lcb_hard_abc396_g,1,754
24
+ lcb_easy_3817,1,100
25
+ lcb_easy_3832,1,1697
26
+ lcb_hard_3777,0,8192
27
+ lcb_hard_abc393_d,1,423
28
+ lcb_hard_abc388_g,0,2049
29
+ lcb_hard_abc394_f,0,6907
30
+ lcb_hard_abc389_f,1,3545
31
+ lcb_medium_3677,1,692
32
+ lcb_medium_3722,0,2127
33
+ lcb_hard_arc190_c,0,8165
34
+ lcb_hard_abc387_f,0,8192
35
+ lcb_hard_3783,1,5241
36
+ lcb_hard_arc192_d,0,8192
37
+ lcb_hard_abc393_e,1,748
38
+ lcb_medium_abc388_c,1,863
39
+ lcb_easy_3753,1,152
40
+ lcb_hard_3697,1,3650
41
+ lcb_hard_arc196_b,0,8192
42
+ lcb_easy_3736,1,109
43
+ lcb_hard_3789,1,4919
44
+ lcb_medium_arc192_a,0,8192
45
+ lcb_medium_abc392_c,1,978
46
+ lcb_medium_3785,1,1206
47
+ lcb_medium_3737,1,1899
48
+ lcb_medium_3748,1,1066
49
+ lcb_easy_3731,1,93
50
+ lcb_medium_abc398_b,1,676
51
+ lcb_easy_abc400_b,1,803
52
+ lcb_hard_arc193_b,0,8192
53
+ lcb_medium_abc399_d,0,8192
54
+ lcb_medium_abc400_d,1,3457
55
+ lcb_medium_abc399_c,1,520
56
+ lcb_medium_3760,1,1475
57
+ lcb_medium_3776,0,8192
58
+ lcb_medium_3743,1,2328
59
+ lcb_easy_abc396_b,1,177
60
+ lcb_easy_3799,1,298
61
+ lcb_hard_3674,0,8192
62
+ crux_o_sample_595,1,203
63
+ crux_i_sample_123,1,3696
64
+ crux_o_sample_32,1,209
65
+ crux_i_sample_198,1,1311
66
+ crux_o_sample_630,1,284
67
+ crux_i_sample_423,1,6083
68
+ crux_o_sample_388,1,217
69
+ crux_i_sample_379,1,663
70
+ crux_o_sample_64,1,555
71
+ crux_i_sample_506,1,593
72
+ crux_o_sample_560,1,142
73
+ crux_i_sample_204,1,624
74
+ crux_o_sample_760,1,547
75
+ crux_i_sample_308,1,594
76
+ crux_o_sample_379,1,224
77
+ crux_i_sample_223,1,656
78
+ crux_o_sample_184,1,237
79
+ crux_i_sample_374,1,804
80
+ crux_o_sample_385,1,509
81
+ crux_i_sample_11,1,431
82
+ crux_o_sample_155,0,1004
83
+ crux_i_sample_39,1,339
84
+ crux_o_sample_387,1,221
85
+ crux_i_sample_544,1,760
86
+ crux_o_sample_445,1,460
87
+ crux_i_sample_257,1,950
88
+ crux_o_sample_144,1,137
89
+ crux_i_sample_610,1,2444
90
+ crux_o_sample_728,1,176
91
+ crux_i_sample_490,1,421
92
+ crux_o_sample_670,1,345
93
+ crux_i_sample_20,1,385
94
+ crux_o_sample_694,1,252
95
+ crux_i_sample_551,1,772
96
+ crux_o_sample_747,1,315
97
+ crux_i_sample_260,1,910
98
+ crux_o_sample_286,1,397
99
+ crux_i_sample_549,1,880
100
+ crux_o_sample_650,1,166
101
+ crux_i_sample_783,1,860
102
+ crux_o_sample_114,1,1400
103
+ crux_i_sample_457,1,942
104
+ crux_o_sample_611,1,202
105
+ crux_i_sample_515,1,617
106
+ crux_o_sample_405,1,314
107
+ crux_i_sample_336,1,515
108
+ crux_o_sample_349,1,301
109
+ crux_i_sample_663,1,751
110
+ crux_o_sample_599,1,505
111
+ crux_i_sample_149,1,1256
112
+ crux_o_sample_669,1,291
113
+ crux_i_sample_703,1,1390
114
+ crux_o_sample_136,0,590
115
+ crux_i_sample_608,1,563
116
+ crux_o_sample_210,1,198
117
+ crux_i_sample_673,1,568
118
+ crux_o_sample_682,1,220
119
+ crux_i_sample_291,1,1010
120
+ crux_o_sample_561,1,202
121
+ crux_i_sample_557,1,635
benchmarks/results/k448_code_think16k_full_compact.csv ADDED
@@ -0,0 +1,121 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ id,pass,tokens
2
+ lcb_hard_3744,1,13560
3
+ lcb_easy_3778,1,458
4
+ lcb_easy_abc392_a,1,919
5
+ lcb_medium_arc195_a,1,4474
6
+ lcb_hard_abc397_g,1,17532
7
+ lcb_hard_arc192_e,0,17527
8
+ lcb_hard_arc196_d,0,16399
9
+ lcb_hard_abc400_e,1,16663
10
+ lcb_hard_arc194_d,0,16934
11
+ lcb_medium_3779,1,16498
12
+ lcb_hard_abc392_f,0,16839
13
+ lcb_easy_abc395_a,1,370
14
+ lcb_medium_3786,1,16865
15
+ lcb_hard_arc195_e,1,17009
16
+ lcb_hard_abc398_d,1,9100
17
+ lcb_hard_abc398_g,0,16718
18
+ lcb_medium_abc392_d,1,16074
19
+ lcb_easy_abc393_a,1,779
20
+ lcb_hard_abc391_g,1,14693
21
+ lcb_hard_abc394_g,1,17640
22
+ lcb_medium_3805,1,16656
23
+ lcb_hard_abc396_g,1,17045
24
+ lcb_easy_3817,1,360
25
+ lcb_easy_3832,1,7308
26
+ lcb_hard_3777,1,17628
27
+ lcb_hard_abc393_d,1,4230
28
+ lcb_hard_abc388_g,1,17384
29
+ lcb_hard_abc394_f,1,16954
30
+ lcb_hard_abc389_f,1,17488
31
+ lcb_medium_3677,1,16896
32
+ lcb_medium_3722,1,12331
33
+ lcb_hard_arc190_c,0,17422
34
+ lcb_hard_abc387_f,0,17261
35
+ lcb_hard_3783,1,11649
36
+ lcb_hard_arc192_d,1,17481
37
+ lcb_hard_abc393_e,0,16979
38
+ lcb_medium_abc388_c,1,3173
39
+ lcb_easy_3753,1,448
40
+ lcb_hard_3697,1,16831
41
+ lcb_hard_arc196_b,1,17371
42
+ lcb_easy_3736,1,772
43
+ lcb_hard_3789,1,16774
44
+ lcb_medium_arc192_a,0,16900
45
+ lcb_medium_abc392_c,1,822
46
+ lcb_medium_3785,1,1293
47
+ lcb_medium_3737,1,6858
48
+ lcb_medium_3748,1,1953
49
+ lcb_easy_3731,1,1419
50
+ lcb_medium_abc398_b,1,562
51
+ lcb_easy_abc400_b,1,1476
52
+ lcb_hard_arc193_b,0,20480
53
+ lcb_medium_abc399_d,1,16745
54
+ lcb_medium_abc400_d,1,16900
55
+ lcb_medium_abc399_c,1,1324
56
+ lcb_medium_3760,1,6367
57
+ lcb_medium_3776,1,16989
58
+ lcb_medium_3743,1,16140
59
+ lcb_easy_abc396_b,1,510
60
+ lcb_easy_3799,1,3001
61
+ lcb_hard_3674,0,17132
62
+ crux_o_sample_595,1,564
63
+ crux_i_sample_123,1,2206
64
+ crux_o_sample_32,1,402
65
+ crux_i_sample_198,1,982
66
+ crux_o_sample_630,1,1180
67
+ crux_i_sample_423,1,4725
68
+ crux_o_sample_388,1,428
69
+ crux_i_sample_379,1,467
70
+ crux_o_sample_64,1,838
71
+ crux_i_sample_506,1,542
72
+ crux_o_sample_560,1,425
73
+ crux_i_sample_204,1,541
74
+ crux_o_sample_760,1,319
75
+ crux_i_sample_308,1,753
76
+ crux_o_sample_379,1,421
77
+ crux_i_sample_223,1,386
78
+ crux_o_sample_184,1,322
79
+ crux_i_sample_374,1,654
80
+ crux_o_sample_385,1,726
81
+ crux_i_sample_11,1,404
82
+ crux_o_sample_155,1,1422
83
+ crux_i_sample_39,1,346
84
+ crux_o_sample_387,1,195
85
+ crux_i_sample_544,1,2334
86
+ crux_o_sample_445,0,1312
87
+ crux_i_sample_257,1,999
88
+ crux_o_sample_144,1,164
89
+ crux_i_sample_610,1,2645
90
+ crux_o_sample_728,1,274
91
+ crux_i_sample_490,1,513
92
+ crux_o_sample_670,1,694
93
+ crux_i_sample_20,1,295
94
+ crux_o_sample_694,1,623
95
+ crux_i_sample_551,1,940
96
+ crux_o_sample_747,1,394
97
+ crux_i_sample_260,1,698
98
+ crux_o_sample_286,1,795
99
+ crux_i_sample_549,1,468
100
+ crux_o_sample_650,1,230
101
+ crux_i_sample_783,1,673
102
+ crux_o_sample_114,1,536
103
+ crux_i_sample_457,1,790
104
+ crux_o_sample_611,1,287
105
+ crux_i_sample_515,1,326
106
+ crux_o_sample_405,1,355
107
+ crux_i_sample_336,1,1078
108
+ crux_o_sample_349,1,867
109
+ crux_i_sample_663,1,302
110
+ crux_o_sample_599,1,999
111
+ crux_i_sample_149,1,1678
112
+ crux_o_sample_669,1,647
113
+ crux_i_sample_703,1,787
114
+ crux_o_sample_136,0,2757
115
+ crux_i_sample_608,1,633
116
+ crux_o_sample_210,1,379
117
+ crux_i_sample_673,1,792
118
+ crux_o_sample_682,1,505
119
+ crux_i_sample_291,1,1122
120
+ crux_o_sample_561,1,282
121
+ crux_i_sample_557,1,983
benchmarks/results/k448_code_think16k_redo_compact.csv ADDED
@@ -0,0 +1,22 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ id,pass,tokens
2
+ lcb_hard_abc397_g,1,17629
3
+ lcb_hard_arc192_e,0,17677
4
+ lcb_hard_arc196_d,0,16399
5
+ lcb_hard_arc194_d,0,16936
6
+ lcb_hard_abc392_f,0,16895
7
+ lcb_hard_arc195_e,1,16935
8
+ lcb_hard_abc398_g,0,16691
9
+ lcb_hard_abc394_g,1,17923
10
+ lcb_medium_3805,1,16656
11
+ lcb_hard_abc394_f,1,16982
12
+ lcb_hard_arc190_c,0,17264
13
+ lcb_hard_abc387_f,0,17305
14
+ lcb_hard_arc196_b,0,17359
15
+ lcb_medium_arc192_a,1,17108
16
+ lcb_hard_arc193_b,0,20480
17
+ lcb_medium_abc399_d,1,16745
18
+ lcb_medium_abc400_d,1,16900
19
+ lcb_medium_3776,1,16989
20
+ lcb_hard_3674,0,18361
21
+ crux_o_sample_445,0,1312
22
+ crux_o_sample_136,0,3059
benchmarks/results/k448_code_think16k_retry_compact.csv ADDED
@@ -0,0 +1,11 @@
 
 
 
 
 
 
 
 
 
 
 
 
1
+ id,pass,tokens,retries
2
+ lcb_hard_arc192_e,1,35351,2
3
+ lcb_hard_arc196_d,0,32807,2
4
+ lcb_hard_arc194_d,1,16997,1
5
+ lcb_hard_abc392_f,0,0,0
6
+ lcb_hard_abc398_g,0,33498,2
7
+ lcb_hard_arc190_c,0,34698,2
8
+ lcb_hard_abc387_f,0,0,0
9
+ lcb_hard_arc196_b,1,34387,2
10
+ lcb_hard_arc193_b,0,35784,2
11
+ lcb_hard_3674,1,17212,1
benchmarks/results/k448_code_think32k_redo_compact.csv ADDED
@@ -0,0 +1,12 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ id,pass,tokens
2
+ lcb_hard_arc192_e,0,34464
3
+ lcb_hard_arc196_d,0,34805
4
+ lcb_hard_arc194_d,0,33330
5
+ lcb_hard_abc392_f,0,33330
6
+ lcb_hard_abc398_g,0,33039
7
+ lcb_hard_arc190_c,0,35309
8
+ lcb_hard_abc387_f,0,34266
9
+ lcb_hard_abc393_e,1,33970
10
+ lcb_medium_arc192_a,1,33293
11
+ lcb_hard_arc193_b,0,33647
12
+ lcb_hard_3674,0,34103
benchmarks/results/k448_code_think_compact.csv ADDED
@@ -0,0 +1,121 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ id,pass,tokens
2
+ lcb_hard_3744,1,4410
3
+ lcb_easy_3778,1,458
4
+ lcb_easy_abc392_a,1,919
5
+ lcb_medium_arc195_a,1,4448
6
+ lcb_hard_abc397_g,0,6412
7
+ lcb_hard_arc192_e,0,6590
8
+ lcb_hard_arc196_d,0,4268
9
+ lcb_hard_abc400_e,1,4389
10
+ lcb_hard_arc194_d,0,4900
11
+ lcb_medium_3779,1,4274
12
+ lcb_hard_abc392_f,0,4437
13
+ lcb_easy_abc395_a,1,370
14
+ lcb_medium_3786,1,4515
15
+ lcb_hard_arc195_e,0,4552
16
+ lcb_hard_abc398_d,1,4364
17
+ lcb_hard_abc398_g,0,4413
18
+ lcb_medium_abc392_d,1,4493
19
+ lcb_easy_abc393_a,1,779
20
+ lcb_hard_abc391_g,1,4675
21
+ lcb_hard_abc394_g,0,4923
22
+ lcb_medium_3805,0,4727
23
+ lcb_hard_abc396_g,1,4706
24
+ lcb_easy_3817,1,360
25
+ lcb_easy_3832,1,4169
26
+ lcb_hard_3777,1,4719
27
+ lcb_hard_abc393_d,1,4230
28
+ lcb_hard_abc388_g,1,4779
29
+ lcb_hard_abc394_f,0,4531
30
+ lcb_hard_abc389_f,1,5117
31
+ lcb_medium_3677,1,4521
32
+ lcb_medium_3722,1,4362
33
+ lcb_hard_arc190_c,0,8192
34
+ lcb_hard_abc387_f,0,5298
35
+ lcb_hard_3783,1,4697
36
+ lcb_hard_arc192_d,1,5087
37
+ lcb_hard_abc393_e,1,4479
38
+ lcb_medium_abc388_c,1,3173
39
+ lcb_easy_3753,1,448
40
+ lcb_hard_3697,1,4452
41
+ lcb_hard_arc196_b,0,5168
42
+ lcb_easy_3736,1,772
43
+ lcb_hard_3789,1,4593
44
+ lcb_medium_arc192_a,0,5127
45
+ lcb_medium_abc392_c,1,822
46
+ lcb_medium_3785,1,1293
47
+ lcb_medium_3737,1,4344
48
+ lcb_medium_3748,1,1953
49
+ lcb_easy_3731,1,1419
50
+ lcb_medium_abc398_b,1,562
51
+ lcb_easy_abc400_b,1,1476
52
+ lcb_hard_arc193_b,0,4545
53
+ lcb_medium_abc399_d,0,4540
54
+ lcb_medium_abc400_d,0,4633
55
+ lcb_medium_abc399_c,1,1324
56
+ lcb_medium_3760,1,4249
57
+ lcb_medium_3776,0,5012
58
+ lcb_medium_3743,1,4275
59
+ lcb_easy_abc396_b,1,510
60
+ lcb_easy_3799,1,3001
61
+ lcb_hard_3674,0,8192
62
+ crux_o_sample_595,1,564
63
+ crux_i_sample_123,1,2206
64
+ crux_o_sample_32,1,402
65
+ crux_i_sample_198,1,982
66
+ crux_o_sample_630,1,1180
67
+ crux_i_sample_423,1,4149
68
+ crux_o_sample_388,1,428
69
+ crux_i_sample_379,1,467
70
+ crux_o_sample_64,1,838
71
+ crux_i_sample_506,1,542
72
+ crux_o_sample_560,1,425
73
+ crux_i_sample_204,1,541
74
+ crux_o_sample_760,1,319
75
+ crux_i_sample_308,1,753
76
+ crux_o_sample_379,1,421
77
+ crux_i_sample_223,1,386
78
+ crux_o_sample_184,1,322
79
+ crux_i_sample_374,1,654
80
+ crux_o_sample_385,1,726
81
+ crux_i_sample_11,1,404
82
+ crux_o_sample_155,1,1422
83
+ crux_i_sample_39,1,346
84
+ crux_o_sample_387,1,195
85
+ crux_i_sample_544,1,2334
86
+ crux_o_sample_445,0,1312
87
+ crux_i_sample_257,1,999
88
+ crux_o_sample_144,1,164
89
+ crux_i_sample_610,1,2645
90
+ crux_o_sample_728,1,274
91
+ crux_i_sample_490,1,513
92
+ crux_o_sample_670,1,694
93
+ crux_i_sample_20,1,295
94
+ crux_o_sample_694,1,623
95
+ crux_i_sample_551,1,940
96
+ crux_o_sample_747,1,394
97
+ crux_i_sample_260,1,698
98
+ crux_o_sample_286,1,795
99
+ crux_i_sample_549,1,468
100
+ crux_o_sample_650,1,230
101
+ crux_i_sample_783,1,673
102
+ crux_o_sample_114,1,536
103
+ crux_i_sample_457,1,790
104
+ crux_o_sample_611,1,287
105
+ crux_i_sample_515,1,326
106
+ crux_o_sample_405,1,355
107
+ crux_i_sample_336,1,1078
108
+ crux_o_sample_349,1,867
109
+ crux_i_sample_663,1,302
110
+ crux_o_sample_599,1,999
111
+ crux_i_sample_149,1,1678
112
+ crux_o_sample_669,1,647
113
+ crux_i_sample_703,1,787
114
+ crux_o_sample_136,0,2757
115
+ crux_i_sample_608,1,633
116
+ crux_o_sample_210,1,379
117
+ crux_i_sample_673,1,792
118
+ crux_o_sample_682,1,505
119
+ crux_i_sample_291,1,1122
120
+ crux_o_sample_561,1,282
121
+ crux_i_sample_557,1,983
benchmarks/results/k448_reason_nothink_compact.csv ADDED
@@ -0,0 +1,61 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ id,pass,tokens
2
+ reason_mmlu3028,1,719
3
+ reason_mmlu10879,1,271
4
+ reason_bbh_logical_deduction_five_objects_0,1,630
5
+ reason_mmlu11759,0,2048
6
+ reason_mmlu8693,1,302
7
+ reason_bbh_tracking_shuffled_objects_five_objects_0,1,494
8
+ reason_mmlu1760,1,742
9
+ reason_mmlu6359,1,603
10
+ reason_bbh_date_understanding_0,1,432
11
+ reason_mmlu4584,0,2048
12
+ reason_mmlu7301,1,685
13
+ reason_bbh_web_of_lies_0,1,263
14
+ reason_mmlu5893,1,259
15
+ reason_mmlu9408,1,739
16
+ reason_bbh_boolean_expressions_0,1,228
17
+ reason_mmlu4770,1,491
18
+ reason_mmlu2310,1,543
19
+ reason_bbh_navigate_0,1,128
20
+ reason_mmlu656,1,557
21
+ reason_mmlu10734,1,933
22
+ reason_bbh_formal_fallacies_0,1,1333
23
+ reason_mmlu4613,0,2048
24
+ reason_mmlu4984,1,733
25
+ reason_bbh_object_counting_0,1,191
26
+ reason_mmlu6184,0,2048
27
+ reason_mmlu3337,1,676
28
+ reason_bbh_logical_deduction_five_objects_1,1,512
29
+ reason_mmlu10806,1,168
30
+ reason_mmlu12187,0,2048
31
+ reason_bbh_tracking_shuffled_objects_five_objects_1,1,551
32
+ reason_mmlu2156,1,573
33
+ reason_mmlu527,0,282
34
+ reason_bbh_date_understanding_1,1,289
35
+ reason_mmlu10323,1,527
36
+ reason_mmlu5812,1,216
37
+ reason_bbh_web_of_lies_1,1,212
38
+ reason_mmlu962,1,714
39
+ reason_mmlu8463,1,445
40
+ reason_bbh_boolean_expressions_1,1,285
41
+ reason_mmlu10422,1,659
42
+ reason_mmlu7515,1,586
43
+ reason_bbh_navigate_1,1,158
44
+ reason_mmlu2640,1,446
45
+ reason_mmlu4987,0,567
46
+ reason_bbh_formal_fallacies_1,1,1616
47
+ reason_mmlu3166,1,552
48
+ reason_mmlu1366,0,830
49
+ reason_bbh_object_counting_1,1,263
50
+ reason_mmlu11789,0,2048
51
+ reason_mmlu5632,1,398
52
+ reason_bbh_logical_deduction_five_objects_2,1,673
53
+ reason_mmlu200,1,680
54
+ reason_mmlu8011,1,990
55
+ reason_bbh_tracking_shuffled_objects_five_objects_2,1,461
56
+ reason_mmlu6177,1,600
57
+ reason_mmlu10443,1,482
58
+ reason_bbh_date_understanding_2,1,244
59
+ reason_mmlu11236,0,454
60
+ reason_mmlu7172,1,680
61
+ reason_bbh_web_of_lies_2,1,279
benchmarks/results/k448_reason_think16k_compact.csv ADDED
@@ -0,0 +1,11 @@
 
 
 
 
 
 
 
 
 
 
 
 
1
+ id,pass,tokens
2
+ reason_mmlu11759,1,16721
3
+ reason_mmlu4584,0,12966
4
+ reason_mmlu4613,0,16697
5
+ reason_mmlu6184,0,16457
6
+ reason_mmlu12187,1,16565
7
+ reason_mmlu527,0,454
8
+ reason_mmlu4987,0,902
9
+ reason_mmlu1366,0,4612
10
+ reason_mmlu11789,0,20480
11
+ reason_mmlu11236,1,339
benchmarks/results/k448_reason_think4k_compact.csv ADDED
@@ -0,0 +1,11 @@
 
 
 
 
 
 
 
 
 
 
 
 
1
+ id,pass,tokens
2
+ reason_mmlu11759,1,4447
3
+ reason_mmlu4584,0,4199
4
+ reason_mmlu4613,1,4278
5
+ reason_mmlu6184,0,4159
6
+ reason_mmlu12187,1,4206
7
+ reason_mmlu527,0,454
8
+ reason_mmlu4987,0,902
9
+ reason_mmlu1366,0,4268
10
+ reason_mmlu11789,1,4240
11
+ reason_mmlu11236,1,339
benchmarks/results/k448_swe_compact.csv ADDED
@@ -0,0 +1,21 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ id,resolved,turns,tool_calls,tool_errors,patch_lines,secs
2
+ django__django-11790,0,51,50,4,0,316
3
+ django__django-11815,0,51,50,9,18,360
4
+ django__django-11848,1,22,21,2,64,177
5
+ django__django-11880,1,39,38,4,12,257
6
+ django__django-11885,1,51,50,1,215,340
7
+ django__django-11951,1,49,48,1,14,334
8
+ django__django-11964,1,58,57,3,19,379
9
+ django__django-11999,1,47,46,3,17,293
10
+ django__django-12039,1,51,50,1,32,306
11
+ django__django-12050,1,42,41,9,16,266
12
+ sphinx-doc__sphinx-10323,1,32,31,1,29,230
13
+ sphinx-doc__sphinx-10435,0,51,52,1,27,380
14
+ sphinx-doc__sphinx-10466,1,51,50,2,27,366
15
+ sphinx-doc__sphinx-10673,0,51,57,1,164,370
16
+ sphinx-doc__sphinx-11510,0,51,54,3,75,325
17
+ sphinx-doc__sphinx-8265,0,51,50,2,0,920
18
+ sphinx-doc__sphinx-9229,0,51,51,5,0,314
19
+ sphinx-doc__sphinx-9230,0,51,53,4,41,365
20
+ sphinx-doc__sphinx-9320,1,51,50,4,57,448
21
+ sphinx-doc__sphinx-9367,1,14,13,2,15,104
benchmarks/results/k448_swe_t100_compact.csv ADDED
@@ -0,0 +1,9 @@
 
 
 
 
 
 
 
 
 
 
1
+ id,resolved,turns,tool_calls,tool_errors,patch_lines,secs
2
+ django__django-11790,1,51,50,5,16,319
3
+ django__django-11815,0,101,100,4,26,641
4
+ sphinx-doc__sphinx-10435,0,94,93,9,0,800
5
+ sphinx-doc__sphinx-10673,0,80,79,2,175,557
6
+ sphinx-doc__sphinx-11510,0,96,95,5,162,640
7
+ sphinx-doc__sphinx-8265,0,98,97,2,39,794
8
+ sphinx-doc__sphinx-9229,1,76,75,4,1657,561
9
+ sphinx-doc__sphinx-9230,1,101,105,5,13,1029
benchmarks/scripts/07_domain_eval.py ADDED
@@ -0,0 +1,630 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ #!/usr/bin/env python3
2
+ # -*- coding: utf-8 -*-
3
+ """
4
+ 07_domain_eval.py - does a quantized Tessera keep what matters (Turkish, English, code, math)? Everything is scored against a
5
+ reference model (the Q4), because the original checkpoint cannot be run on the test machines.
6
+
7
+ run 56 checkable prompts sent to a llama-server (OpenAI API): 11 Python functions with hidden unit tests (3 asked in Turkish),
8
+ 16 math word problems (8 English + the same 8 in Turkish), 14 Turkish and 15 English language tasks (reading comprehension,
9
+ Turkish alphabet order, i/I casing, vowel-harmony plurals, instruction following, extraction, cross-language answers).
10
+ Problems with numbers are generated from a seed, so the answers are computed, not typed. Greedy decoding.
11
+ kld llama-perplexity per domain over the held-out text of 06_build_eval_set.py: mean KL divergence and same-top-token rate
12
+ against the reference model's saved logits (--save-base stores them for the reference)
13
+ compare table of result files; every task category as a % of the first file (the reference)
14
+ selftest the checkers accept the reference answers and reject garbage; no model needed
15
+
16
+ python 07_domain_eval.py run --bin <llama.cpp>/build/bin --model q4.gguf --ot '<-ot regex>' --name q4 --no-think (starts llama-server)
17
+ python 07_domain_eval.py run --bin <llama.cpp>/build/bin --model ours.gguf --ot '<-ot regex>' --name ours --no-think
18
+ python 07_domain_eval.py run --endpoint http://127.0.0.1:8080 --name x --no-think (a llama-server that is already running)
19
+ python 07_domain_eval.py compare results_q4.json results_ours.json
20
+ python 07_domain_eval.py kld --bin <llama.cpp>/build/bin --model q4.gguf --ot '<-ot regex>' --name q4 --save-base
21
+ python 07_domain_eval.py kld --bin <llama.cpp>/build/bin --model ours.gguf --ot '<-ot regex>' --name ours
22
+
23
+ `run` EXECUTES THE PYTHON THE MODEL WRITES (10 s limit, 2 GB, empty temp directory, nothing else isolated): use Colab or a throw-away
24
+ machine. 56 tasks are a smoke test: a difference of a few tasks is noise (one task is about 9 points of the 11 code tasks).
25
+ """
26
+ from __future__ import annotations
27
+
28
+ import argparse
29
+ import json
30
+ import math
31
+ import os
32
+ import random
33
+ import re
34
+ import shlex
35
+ import subprocess
36
+ import sys
37
+ import tempfile
38
+ import time
39
+ import unicodedata
40
+ import urllib.request
41
+ from fractions import Fraction as Fr
42
+ from pathlib import Path
43
+
44
+ CATS = ("code", "math", "tr", "en", "reason", "lcb", "crux")
45
+ TR_ALPHA = "abcçdefgğhıijklmnoöprsştuüvyz"
46
+
47
+
48
+ # ---------------------------------------------------------------------------------------------------------------- checkers
49
+ def strip_think(r: str) -> str:
50
+ return re.sub(r"<think>.*", "", re.sub(r"<think>.*?</think>", "", r, flags=re.S), flags=re.S).strip()
51
+
52
+
53
+ def numbers(text: str) -> list:
54
+ out = []
55
+ for tok in re.findall(r"-?\d[\d.,]*", text):
56
+ tok = tok.rstrip(".,")
57
+ tok = re.sub(r"[.,]", "", tok) if re.fullmatch(r"-?\d{1,3}([.,]\d{3})+", tok) else tok.replace(",", ".")
58
+ try:
59
+ out.append(Fr(tok))
60
+ except ValueError:
61
+ pass
62
+ return out
63
+
64
+
65
+ def num(ans):
66
+ def f(r):
67
+ if "####" in r:
68
+ n = numbers(r.rsplit("####", 1)[1])
69
+ return bool(n) and n[0] == ans
70
+ n = numbers(r)
71
+ return bool(n) and n[-1] == ans
72
+ return f
73
+
74
+
75
+ def has(*words): # every word present (case-insensitive)
76
+ return lambda r: all(w.lower() in r.lower() for w in words)
77
+
78
+
79
+ def ordered(words): # the words appear in this order
80
+ def f(r):
81
+ pos = 0
82
+ for w in words:
83
+ i = r.lower().find(w.lower(), pos)
84
+ if i < 0:
85
+ return False
86
+ pos = i + len(w)
87
+ return True
88
+ return f
89
+
90
+
91
+ def word(*ok): # the whole answer is one of these words
92
+ return lambda r: re.sub(r"[^\w]", "", r.casefold()) in ok
93
+
94
+
95
+ def bullets(n):
96
+ def f(r):
97
+ lines = [l for l in r.splitlines() if l.strip()]
98
+ return len(lines) == n and all(l.startswith("- ") for l in lines)
99
+ return f
100
+
101
+
102
+ def deaccent(s: str) -> str:
103
+ return "".join(c for c in unicodedata.normalize("NFD", s) if unicodedata.category(c) != "Mn").lower()
104
+
105
+
106
+ def as_json(want: dict):
107
+ def f(r):
108
+ m = re.search(r"\{.*\}", r, flags=re.S)
109
+ try:
110
+ d = json.loads(m.group(0))
111
+ except Exception:
112
+ return False
113
+ return all(deaccent(str(d.get(k, ""))) == deaccent(v) for k, v in want.items())
114
+ return f
115
+
116
+
117
+ def english_only(*words): # contains the words and no Turkish-specific letter
118
+ return lambda r: has(*words)(r) and not any(c in r for c in "çğıöşüÇĞİÖŞÜ")
119
+
120
+
121
+ def code_of(r: str) -> str:
122
+ blocks = [b for b in re.findall(r"```(?:python|py)?[ \t]*\n(.*?)```", r, flags=re.S) if re.search(r"^\s*(def|class) ", b, re.M)]
123
+ return blocks[0] if blocks else r
124
+
125
+
126
+ def run_code(code: str, tests: str, timeout: int = 10) -> bool:
127
+ pre = None
128
+ if os.name == "posix":
129
+ import resource
130
+ pre = lambda: resource.setrlimit(resource.RLIMIT_AS, (2 << 30, 2 << 30))
131
+ with tempfile.TemporaryDirectory() as d:
132
+ script = Path(d) / "task.py" # a file, not -c: the HumanEval+ tests are bigger than the 128 KiB limit of one argument
133
+ script.write_text(code + "\n\n" + tests, encoding="utf-8")
134
+ try:
135
+ return subprocess.run([sys.executable, "-I", str(script)], cwd=d, capture_output=True, timeout=timeout, preexec_fn=pre,
136
+ env={**os.environ, "OPENBLAS_NUM_THREADS": "1", "OMP_NUM_THREADS": "1"}).returncode == 0
137
+ except subprocess.TimeoutExpired:
138
+ return False
139
+
140
+
141
+ # ---------------------------------------------------------------------------------------------------------------- tasks
142
+ ONE_BLOCK_EN = " Reply with one Python code block only."
143
+ ONE_BLOCK_TR = " Yalnızca tek bir Python kod bloğu ile cevap ver."
144
+ CODE = [ # (id, prompt, tests, reference solution)
145
+ ("roman", "Write a Python function `roman_to_int(s)` that converts a Roman numeral (I V X L C D M, with subtractive pairs such as "
146
+ "IV and IX) to an integer." + ONE_BLOCK_EN,
147
+ "assert roman_to_int('III')==3 and roman_to_int('IV')==4 and roman_to_int('XLII')==42 and roman_to_int('MCMXCIV')==1994",
148
+ "def roman_to_int(s):\n v = {'I': 1, 'V': 5, 'X': 10, 'L': 50, 'C': 100, 'D': 500, 'M': 1000}\n return sum(-v[c] if i + 1 < len(s) and "
149
+ "v[c] < v[s[i + 1]] else v[c] for i, c in enumerate(s))"),
150
+ ("merge_tr", "Python'da `merge_intervals(intervals)` fonksiyonunu yaz: [başlangıç, bitiş] listelerinden oluşan bir listeyi alır, "
151
+ "kesişen veya uç uca değen aralıkları birleştirir ve başlangıca göre sıralı bir liste (iç içe listeler olarak) döndürür." + ONE_BLOCK_TR,
152
+ "assert merge_intervals([[1,3],[2,6],[8,10],[15,18]])==[[1,6],[8,10],[15,18]]\nassert merge_intervals([[1,4],[4,5]])==[[1,5]]\n"
153
+ "assert merge_intervals([])==[]\nassert merge_intervals([[5,6],[1,2]])==[[1,2],[5,6]]",
154
+ "def merge_intervals(iv):\n out = []\n for a, b in sorted(iv):\n if out and a <= out[-1][1]:\n"
155
+ " out[-1][1] = max(out[-1][1], b)\n else:\n out.append([a, b])\n return out"),
156
+ ("balanced_tr", "Python'da `is_balanced(s)` fonksiyonunu yaz: s içindeki (), [] ve {} parantezleri doğru sırada kapatılmışsa True, "
157
+ "değilse False döndürsün (s yalnızca bu altı karakterden oluşur, boş dize True'dur)." + ONE_BLOCK_TR,
158
+ "assert is_balanced('([]{})') and not is_balanced('(]') and is_balanced('') and not is_balanced('((') and not is_balanced('())')"
159
+ " and not is_balanced('([)]')",
160
+ "def is_balanced(s):\n st, pair = [], {')': '(', ']': '[', '}': '{'}\n for c in s:\n if c in pair:\n"
161
+ " if not st or st.pop() != pair[c]:\n return False\n else:\n st.append(c)\n return not st"),
162
+ ("flatten", "Write a Python function `flatten(x)` that turns an arbitrarily nested list into a flat list, keeping the order. "
163
+ "Strings are single items, not lists." + ONE_BLOCK_EN,
164
+ "assert flatten([1,[2,[3,[4]]],5])==[1,2,3,4,5] and flatten([])==[] and flatten([[],[[]]])==[] and flatten(['ab',[['c']]])==['ab','c']",
165
+ "def flatten(x):\n out = []\n for i in x:\n out += flatten(i) if isinstance(i, list) else [i]\n return out"),
166
+ ("word_freq", "Write a Python function `word_freq(text)` that returns a dict mapping each lowercase word to its count; words are the "
167
+ "maximal runs of ASCII letters after lowercasing, everything else is a separator." + ONE_BLOCK_EN,
168
+ "assert word_freq('Hello, hello world!')=={'hello':2,'world':1} and word_freq('')=={} and word_freq(\"It's it\")=={'it':2,'s':1}",
169
+ "import re\nfrom collections import Counter\ndef word_freq(t):\n return dict(Counter(re.findall('[a-z]+', t.lower())))"),
170
+ ("rle", "Write a Python function `rle_decode(s)` that decodes run-length encoding: each run is a decimal count (one or more digits) "
171
+ "followed by one non-digit character, e.g. '3a2b1c' -> 'aaabbc' and '12x' -> 'xxxxxxxxxxxx'." + ONE_BLOCK_EN,
172
+ "assert rle_decode('3a2b1c')=='aaabbc' and rle_decode('')=='' and rle_decode('12x')=='x'*12 and rle_decode('10a1b')=='a'*10+'b'",
173
+ "import re\ndef rle_decode(s):\n return ''.join(c * int(n) for n, c in re.findall(r'(\\d+)(\\D)', s))"),
174
+ ("moving_avg", "This function should return the averages of every window of k consecutive items of xs (an empty list when k > len(xs)), "
175
+ "but it has a bug. Return the corrected function.\n\n```python\ndef moving_average(xs, k):\n out = []\n for i in range(len(xs) - k):\n"
176
+ " out.append(sum(xs[i:i + k]) / k)\n return out\n```" + ONE_BLOCK_EN,
177
+ "assert moving_average([1,2,3,4,5],2)==[1.5,2.5,3.5,4.5] and moving_average([1,2,3],3)==[2.0] and moving_average([1,2],3)==[]",
178
+ "def moving_average(xs, k):\n return [sum(xs[i:i + k]) / k for i in range(len(xs) - k + 1)]"),
179
+ ("second_tr", "Python'da `ikinci_buyuk(xs)` fonksiyonunu yaz: sayı listesindeki en büyük ikinci FARKLI değeri döndürsün; böyle bir değer "
180
+ "yoksa None döndürsün." + ONE_BLOCK_TR,
181
+ "assert ikinci_buyuk([3,1,4,1,5])==4 and ikinci_buyuk([2,2]) is None and ikinci_buyuk([7]) is None and ikinci_buyuk([5,5,3])==3 "
182
+ "and ikinci_buyuk([]) is None",
183
+ "def ikinci_buyuk(xs):\n s = sorted(set(xs))\n return s[-2] if len(s) > 1 else None"),
184
+ ("two_sum", "Write a Python function `two_sum(nums, target)` that returns a list [i, j] with i < j and nums[i] + nums[j] == target "
185
+ "(exactly one such pair exists)." + ONE_BLOCK_EN,
186
+ "assert two_sum([2,7,11,15],9)==[0,1] and two_sum([3,2,4],6)==[1,2] and two_sum([3,3],6)==[0,1]",
187
+ "def two_sum(nums, target):\n seen = {}\n for j, x in enumerate(nums):\n if target - x in seen:\n"
188
+ " return [seen[target - x], j]\n seen[x] = j"),
189
+ ("ipv4", "Write a Python function `valid_ipv4(s)` that returns True only for a dotted-quad IPv4 address: exactly four decimal parts "
190
+ "separated by dots, digits only, each 0-255, no leading zeros (except the single digit '0')." + ONE_BLOCK_EN,
191
+ "assert valid_ipv4('192.168.1.1') and valid_ipv4('0.0.0.0') and not valid_ipv4('256.1.1.1') and not valid_ipv4('1.2.3') "
192
+ "and not valid_ipv4('01.2.3.4') and not valid_ipv4('1.2.3.4.5') and not valid_ipv4('a.b.c.d') and not valid_ipv4('1..2.3')",
193
+ "def valid_ipv4(s):\n p = s.split('.')\n return len(p) == 4 and all(x.isascii() and x.isdigit() and (x == '0' or x[0] != '0') "
194
+ "and int(x) <= 255 for x in p)"),
195
+ ("lru", "Write a Python class `LRUCache(capacity)` with `get(key)` (returns the value, or -1 if absent) and `put(key, value)`. When "
196
+ "a put would exceed the capacity, evict the least recently used key; both get and put count as a use." + ONE_BLOCK_EN,
197
+ "c = LRUCache(2); c.put(1,1); c.put(2,2); assert c.get(1)==1; c.put(3,3); assert c.get(2)==-1; c.put(4,4); assert c.get(1)==-1\n"
198
+ "assert c.get(3)==3 and c.get(4)==4\nc = LRUCache(1); c.put(1,1); c.put(1,5); assert c.get(1)==5; c.put(2,2); assert c.get(1)==-1",
199
+ "from collections import OrderedDict\nclass LRUCache:\n def __init__(self, capacity):\n self.c, self.n = OrderedDict(), capacity\n"
200
+ " def get(self, k):\n if k not in self.c:\n return -1\n self.c.move_to_end(k)\n return self.c[k]\n"
201
+ " def put(self, k, v):\n self.c[k] = v\n self.c.move_to_end(k)\n if len(self.c) > self.n:\n"
202
+ " self.c.popitem(last=False)"),
203
+ ]
204
+
205
+
206
+ # math: each template returns (English, Turkish, answer); the numbers are drawn so that the answers are whole and sensible
207
+ def m_shop(r):
208
+ p, n, a, b = r.randint(2, 9), r.randint(5, 15), r.randint(2, 9), r.randint(2, 6)
209
+ spend = a * p + b * n
210
+ m = (spend // 10 + 1) * 10 + r.choice([0, 10, 20])
211
+ return (f"A shop sells pencils for {p} dollars each and notebooks for {n} dollars each. Maria buys {a} pencils and {b} notebooks "
212
+ f"and pays with a {m}-dollar bill. How many dollars of change does she get?",
213
+ f"Bir kırtasiyede kalem tanesi {p} lira, defter tanesi {n} liradır. Maria {a} kalem ve {b} defter alıp {m} liralık "
214
+ f"banknotla ödeme yapıyor. Kaç lira para üstü alır?", m - spend)
215
+
216
+
217
+ def m_train(r):
218
+ v1, t1, v2, t2 = r.randint(40, 90), r.randint(2, 5), r.randint(60, 120), r.randint(1, 4)
219
+ return (f"A train travels at {v1} km/h for {t1} hours and then at {v2} km/h for {t2} hours. How many kilometers does it cover in total?",
220
+ f"Bir tren saatte {v1} km hızla {t1} saat, ardından saatte {v2} km hızla {t2} saat gidiyor. Toplam kaç km yol alır?",
221
+ v1 * t1 + v2 * t2)
222
+
223
+
224
+ def m_discount(r):
225
+ x, d, c = 20 * r.randint(5, 20), r.choice([10, 15, 20, 25, 30, 40]), r.randint(3, 15)
226
+ return (f"A jacket costs {x} dollars. It is discounted by {d}% and then a coupon takes another {c} dollars off the discounted price. "
227
+ f"What is the final price in dollars?",
228
+ f"Bir montun fiyatı {x} liradır. Önce %{d} indirim yapılıyor, sonra indirimli fiyattan {c} lira daha düşülüyor. "
229
+ f"Son fiyat kaç liradır?", Fr(x * (100 - d), 100) - c)
230
+
231
+
232
+ def m_age(r):
233
+ a, x, n = r.randint(8, 30), r.randint(2, 9), r.randint(2, 8)
234
+ s = 2 * a + x + 2 * n
235
+ return (f"Tom is {x} years older than Ann. In {n} years the sum of their ages will be {s}. How old is Ann now?",
236
+ f"Ali, Ayşe'den {x} yaş büyüktür. {n} yıl sonra ikisinin yaşları toplamı {s} olacaktır. Ayşe şimdi kaç yaşındadır?", a)
237
+
238
+
239
+ def m_work(r):
240
+ while True:
241
+ a, b = r.choice([2, 3, 4, 5, 6, 10, 12, 15]), r.choice([2, 3, 4, 5, 6, 10, 12, 15])
242
+ mins = Fr(60 * a * b, a + b)
243
+ if a != b and mins.denominator == 1:
244
+ return (f"Pipe A can fill a tank in {a} hours and pipe B can fill it in {b} hours. Working together, how many minutes do "
245
+ f"they need to fill the empty tank?",
246
+ f"A musluğu bir depoyu {a} saatte, B musluğu {b} saatte doldurabiliyor. İkisi birlikte çalışırsa boş depoyu kaç "
247
+ f"dakikada doldurur?", mins)
248
+
249
+
250
+ def m_rect(r):
251
+ w = r.randint(3, 20)
252
+ l, p = 2 * w + 3, 2 * (3 * w + 3)
253
+ return (f"The length of a rectangle is 3 more than twice its width. Its perimeter is {p} cm. What is its area in square cm?",
254
+ f"Bir dikdörtgenin uzun kenarı, kısa kenarının iki katından 3 cm fazladır. Çevresi {p} cm olduğuna göre alanı kaç cm²'dir?",
255
+ w * l)
256
+
257
+
258
+ def m_ratio(r):
259
+ a, b, c, k = r.randint(1, 4), r.randint(2, 6), r.randint(3, 8), r.randint(4, 15)
260
+ s = k * (a + b + c)
261
+ return (f"{s} dollars are shared among three friends in the ratio {a}:{b}:{c}. How many dollars does the friend with the largest "
262
+ f"share get?",
263
+ f"{s} lira, üç arkadaş arasında {a}:{b}:{c} oranında paylaştırılıyor. En büyük payı alan kaç lira alır?", k * max(a, b, c))
264
+
265
+
266
+ def m_codes(r):
267
+ n = r.randint(5, 9)
268
+ return (f"How many different 3-letter codes can be made from {n} distinct letters if no letter may be used twice in a code?",
269
+ f"{n} farklı harften, hiçbir harf bir şifrede iki kez kullanılmamak koşuluyla, kaç farklı 3 harfli şifre oluşturulabilir?",
270
+ n * (n - 1) * (n - 2))
271
+
272
+
273
+ MATH = [m_shop, m_train, m_discount, m_age, m_work, m_rect, m_ratio, m_codes]
274
+ HOW_EN = "\n\nThink step by step, then give the final answer on the last line as: #### <number>"
275
+ HOW_TR = "\n\nAdım adım düşün, sonra son satırda nihai cevabı şu biçimde yaz: #### <sayı>"
276
+ TR_PLURAL = [("kitap", "kitaplar"), ("göz", "gözler"), ("okul", "okullar"), ("kalem", "kalemler"), ("çiçek", "çiçekler"),
277
+ ("araba", "arabalar"), ("kedi", "kediler"), ("ağaç", "ağaçlar"), ("öğretmen", "öğretmenler"), ("dağ", "dağlar"),
278
+ ("deniz", "denizler"), ("masa", "masalar"), ("kuş", "kuşlar"), ("gül", "güller")]
279
+ TR_WORDS = ["cam", "çanta", "dağ", "ışık", "iyi", "orman", "ödev", "sandalye", "şeker", "uzay", "ümit", "zeytin", "gül", "hava", "kalem",
280
+ "lale", "yol", "yüz", "üzüm", "masa", "pencere", "çiçek", "şehir", "ılık", "ince", "öğle", "göl", "fırın", "tarla"]
281
+ MONTHS_EN = ["January", "March", "May", "July", "September", "November"]
282
+ MONTHS_TR = ["Ocak", "Mart", "Mayıs", "Temmuz", "Eylül", "Kasım"]
283
+ CITIES = ["Izmir", "Bursa", "Konya", "Trabzon", "Antalya", "Adana"]
284
+
285
+
286
+ def tr_key(w: str):
287
+ return [TR_ALPHA.index(c) for c in w]
288
+
289
+
290
+ def build(seed: int = 1) -> list:
291
+ """[(id, category, prompt, check(response) -> bool, reference response)]"""
292
+ T = []
293
+ for tid, prompt, tests, ref in CODE:
294
+ T.append((f"code_{tid}", "code", prompt, (lambda t: lambda r: run_code(code_of(r), t))(tests), f"```python\n{ref}\n```"))
295
+ for i, f in enumerate(MATH):
296
+ en, tr, ans = f(random.Random(f"{seed}m{i}"))
297
+ T.append((f"math_{f.__name__[2:]}_en", "math", en + HOW_EN, num(ans), f"#### {ans}"))
298
+ T.append((f"math_{f.__name__[2:]}_tr", "math", tr + HOW_TR, num(ans), f"#### {ans}"))
299
+ # Turkish
300
+ for i in range(3):
301
+ r = random.Random(f"{seed}ta{i}")
302
+ a, b, c = r.randint(2, 7), r.randint(2, 6), r.randint(15, 40)
303
+ T.append((f"tr_age{i}", "tr", f"Zeynep, Ahmet'ten {a} yaş büyüktür. Ahmet, Deniz'den {b} yaş küçüktür. Deniz {c} yaşındadır. "
304
+ f"Zeynep kaç yaşındadır? Yalnızca sayıyı yaz.", num(c - b + a), str(c - b + a)))
305
+ for i in range(2):
306
+ ws = random.Random(f"{seed}ts{i}").sample(TR_WORDS, 6)
307
+ want = sorted(ws, key=tr_key)
308
+ T.append((f"tr_sort{i}", "tr", "Şu kelimeleri Türk alfabesine göre alfabetik sıraya diz ve aralarına virgül koyarak tek satırda yaz: "
309
+ + ", ".join(ws), ordered(want), ", ".join(want)))
310
+ ph = random.Random(f"{seed}tu").choice(["istanbul ışığı", "ilkbahar ışıkları", "iğne ve ipek", "ılık süt içtim", "çiçekli bir bahçe"])
311
+ up = ph.replace("i", "İ").replace("ı", "I").upper()
312
+ T.append(("tr_upper", "tr", f"'{ph}' ifadesini Türkçe yazım kurallarına göre tamamen BÜYÜK harfe çevir. Yalnızca sonucu yaz.",
313
+ lambda r, up=up: up in r, up))
314
+ for i in range(2):
315
+ pairs = random.Random(f"{seed}tp{i}").sample(TR_PLURAL, 5)
316
+ T.append((f"tr_plural{i}", "tr", "Şu kelimelerin çoğul hâlini yaz (her biri için yalnızca çoğul biçimi, virgülle ayırarak): "
317
+ + ", ".join(w for w, _ in pairs), has(*[p for _, p in pairs]), ", ".join(p for _, p in pairs)))
318
+ T.append(("tr_bullets", "tr", "Ankara hakkında tam olarak 3 madde yaz. Her madde satır başında '- ' ile başlasın, başka hiçbir şey yazma.",
319
+ bullets(3), "- Başkenttir.\n- İç Anadolu'dadır.\n- Kalabalıktır."))
320
+ T.append(("tr_yesno", "tr", "Yalnızca 'evet' veya 'hayır' yaz: Türkiye'nin başkenti İstanbul mu?", word("hayır", "hayir"), "hayır"))
321
+ T.append(("tr_translate", "tr", "Şu cümleyi İngilizceye çevir, yalnızca çeviriyi yaz: 'Yarın sabah erken kalkıp işe gideceğim.'",
322
+ lambda r: sum(bool(re.search(p, r.lower())) for p in (r"tomorrow", r"morning", r"early", r"\bwork|\bjob|office",
323
+ r"wake|get up|rise|getting up")) >= 4,
324
+ "Tomorrow morning I will get up early and go to work."))
325
+ for i in range(2):
326
+ r = random.Random(f"{seed}te{i}")
327
+ day, mon, hh, mm, city = r.randint(2, 28), r.choice(MONTHS_TR), r.randint(9, 17), r.choice(["00", "15", "30", "45"]), r.choice(CITIES)
328
+ txt = f"Toplantı {day} {mon} 2025 tarihinde, saat {hh}:{mm} olarak, {city} şubesinde yapılacaktır."
329
+ if i == 0:
330
+ T.append((f"tr_extract{i}", "tr", f"Metin: {txt}\nSoru: Toplantı saat kaçta? Yalnızca saati yaz.",
331
+ lambda r, a=f"{hh}:{mm}", b=f"{hh}.{mm}": a in r or b in r, f"{hh}:{mm}"))
332
+ else:
333
+ T.append((f"tr_extract{i}", "tr", f"Metin: {txt}\nSoru: Toplantı hangi şehirde yapılacak? Yalnızca şehir adını yaz.",
334
+ has(city), city))
335
+ T.append(("tr_cross", "tr", "İngilizce olarak cevap ver, tek cümle: Fransa'nın başkenti neresidir?", english_only("paris"),
336
+ "The capital of France is Paris."))
337
+ # English
338
+ for i in range(3):
339
+ r = random.Random(f"{seed}ea{i}")
340
+ a, b, c = r.randint(2, 7), r.randint(2, 6), r.randint(15, 40)
341
+ T.append((f"en_age{i}", "en", f"Emma is {a} years older than Noah. Noah is {b} years younger than Liam. Liam is {c} years old. "
342
+ f"How old is Emma? Answer with just the number.", num(c - b + a), str(c - b + a)))
343
+ T.append(("en_bullets", "en", "Write exactly 3 bullet points about Paris. Start each line with '- ' and write nothing else.",
344
+ bullets(3), "- Capital of France.\n- On the Seine.\n- Home of the Louvre."))
345
+ T.append(("en_yesno1", "en", "Answer with only 'yes' or 'no': Is the Pacific Ocean the largest ocean on Earth?", word("yes"), "yes"))
346
+ T.append(("en_yesno2", "en", "Answer with only 'yes' or 'no': Is 91 a prime number?", word("no"), "no"))
347
+ T.append(("en_reverse", "en", "Reverse the order of the words in this sentence and output only the result: 'the quick brown fox jumps'",
348
+ lambda r: "jumps fox brown quick the" in re.sub(r"[^\w ]", "", r.lower()), "jumps fox brown quick the"))
349
+ for i in range(2):
350
+ r = random.Random(f"{seed}ee{i}")
351
+ day, mon, hh, mm, city = r.randint(2, 28), r.choice(MONTHS_EN), r.randint(9, 17), r.choice(["00", "15", "30", "45"]), r.choice(CITIES)
352
+ txt = f"The meeting will take place on {mon} {day}, 2025 at {hh}:{mm} in the {city} office."
353
+ if i == 0:
354
+ T.append((f"en_extract{i}", "en", f"Text: {txt}\nQuestion: At what time is the meeting? Answer with the time only.",
355
+ has(f"{hh}:{mm}"), f"{hh}:{mm}"))
356
+ else:
357
+ T.append((f"en_extract{i}", "en", f"Text: {txt}\nQuestion: In which city is the meeting? Answer with the city only.", has(city), city))
358
+ T.append(("en_logic1", "en", "All bloops are razzies. All razzies are lazzies. Is every bloop a lazzie? Answer only yes or no.",
359
+ word("yes"), "yes"))
360
+ T.append(("en_logic2", "en", "All birds have feathers. Penguins have feathers. Does it follow that penguins are birds? Answer only yes or no.",
361
+ word("no"), "no"))
362
+ T.append(("en_json1", "en", "Give the capital of Japan as a JSON object with keys \"city\" and \"country\". Output only the JSON.",
363
+ as_json({"city": "Tokyo", "country": "Japan"}), '{"city": "Tokyo", "country": "Japan"}'))
364
+ T.append(("en_json2", "en", "Give the capital of Canada as a JSON object with keys \"city\" and \"country\". Output only the JSON.",
365
+ as_json({"city": "Ottawa", "country": "Canada"}), '{"city": "Ottawa", "country": "Canada"}'))
366
+ T.append(("en_cross", "en", "Answer in Turkish, in one sentence: What is the capital of Turkey?",
367
+ lambda r: "ankara" in r.lower() and ("başkent" in r.lower() or "baskent" in r.lower()), "Türkiye'nin başkenti Ankara'dır."))
368
+ T.append(("en_words", "en", "Summarize in at most 12 words: 'The city council voted on Tuesday to build a new bridge over the river, "
369
+ "after years of debate about the cost.'", lambda r: len(r.split()) <= 12 and "bridge" in r.lower(),
370
+ "Council votes to build new river bridge after years of debate."))
371
+ return T
372
+
373
+
374
+ # ---------------------------------------------------------------------------------------------------------------- commands
375
+ def table(rows, head):
376
+ w = [max(len(str(x)) for x in col) for col in zip(head, *rows)]
377
+ for r in [head] + rows:
378
+ print(" ".join(str(x).ljust(n) for x, n in zip(r, w)))
379
+
380
+
381
+ def serve(a):
382
+ """starts llama-server on the model and returns once /health says ok"""
383
+ cmd = [str(Path(a.bin) / "llama-server"), "-m", a.model, "-ngl", "99", "-fa", "on", "-lzm", "on", "-t", str(a.threads), "-c", str(a.ctx),
384
+ "-np", "1", "--host", "127.0.0.1", "--port", str(a.port)] + (["-ot", a.ot] if a.ot else []) + shlex.split(a.extra)
385
+ log = open(f"server_{a.name}.log", "w")
386
+ p = subprocess.Popen(cmd, stdout=log, stderr=subprocess.STDOUT)
387
+ t0 = time.time()
388
+ while time.time() - t0 < a.load_timeout:
389
+ if p.poll() is not None:
390
+ sys.exit(f"llama-server exited early, see {log.name}")
391
+ try:
392
+ if urllib.request.urlopen(f"http://127.0.0.1:{a.port}/health", timeout=5).status == 200:
393
+ print(f"llama-server ready after {time.time() - t0:.0f} s", flush=True)
394
+ return p
395
+ except Exception:
396
+ time.sleep(5)
397
+ p.terminate()
398
+ sys.exit(f"llama-server not ready after {a.load_timeout} s, see {log.name}")
399
+
400
+
401
+ def cmd_run(a):
402
+ srv = serve(a) if a.model else None
403
+ if srv:
404
+ a.endpoint = f"http://127.0.0.1:{a.port}"
405
+ try:
406
+ run_tasks(a)
407
+ finally:
408
+ if srv:
409
+ srv.terminate()
410
+ srv.wait(60)
411
+
412
+
413
+ def run_tasks(a):
414
+ if a.suite != "basic": # public benchmarks from 08_hard_tasks.py / 09_big_suite.py / 10_code_hard.py; long answers need room
415
+ sys.path.insert(0, str(Path(__file__).resolve().parent))
416
+ import importlib
417
+ mod, fn = {"hard": ("08_hard_tasks", "build_hard"), "big": ("09_big_suite", "build_big"), "codehard": ("10_code_hard", "build_codehard")}[a.suite]
418
+ tasks, a.max_tokens = getattr(importlib.import_module(mod), fn)(a.seed, a.hard_n, run_code, code_of, num), max(a.max_tokens, 2048)
419
+ else:
420
+ tasks = build(a.seed)
421
+ tasks = [t for t in tasks if t[1] in a.only.split(",")]
422
+ if a.redo: # only the tasks that file failed (math and reasoning first): does a setting rescue them?
423
+ prev = json.loads(Path(a.redo).read_text(encoding="utf-8"))["results"]
424
+ order = {"math": 0, "reason": 1, "code": 2}
425
+ tasks = sorted([t for t in tasks if t[0] in prev and not prev[t[0]]["pass"]], key=lambda t: order.get(t[1], 3))[:a.redo_max]
426
+ res, t0, toks = {}, time.time(), 0
427
+ out = a.out or f"results_{a.name}.json"
428
+
429
+ def save(): # after every task, so a dropped session keeps what is done
430
+ Path(out).write_text(json.dumps({"kind": "tasks", "name": a.name, "seed": a.seed, "tokens": toks, "seconds": round(time.time() - t0),
431
+ "results": res}, indent=1, ensure_ascii=False), encoding="utf-8")
432
+ def ask(messages, v): # one chat request -> (reply text, completion tokens)
433
+ body = {"model": a.name, "messages": messages, "max_tokens": a.max_tokens,
434
+ "temperature": a.temperature if a.temperature is not None else (a.temp if a.votes > 1 else 0), "seed": a.seed * 1000 + v}
435
+ for key, val in (("top_p", a.top_p), ("top_k", a.top_k)):
436
+ if val is not None:
437
+ body[key] = val
438
+ if a.no_think:
439
+ body["chat_template_kwargs"] = {"enable_thinking": False}
440
+ elif a.think:
441
+ body["chat_template_kwargs"] = {"enable_thinking": True}
442
+ req = urllib.request.Request(a.endpoint.rstrip("/") + "/v1/chat/completions", json.dumps(body).encode(),
443
+ {"Content-Type": "application/json"})
444
+ try:
445
+ with urllib.request.urlopen(req, timeout=a.timeout) as f:
446
+ d = json.load(f)
447
+ return d["choices"][0]["message"].get("content") or "", d.get("usage", {}).get("completion_tokens", 0)
448
+ except Exception as e:
449
+ return f"[request failed: {e}]", 0
450
+
451
+ first = {k: v["resp"] for k, v in json.loads(Path(a.first_from).read_text(encoding="utf-8"))["results"].items()} if a.first_from else {}
452
+ for k, (tid, cat, prompt, check, _) in enumerate(tasks, 1):
453
+ resps, tt, tries = [], 0, 0
454
+ for v in range(a.votes):
455
+ text, n = (first[tid], 0) if tid in first else ask([{"role": "user", "content": prompt}], v) # --first-from: reuse the stored first answer
456
+ resps.append(text)
457
+ tt += n
458
+ oks = [bool(check(strip_think(r))) for r in resps]
459
+ if a.retry and a.votes == 1 and not oks[0] and hasattr(check, "feedback"): # repair loop: only visible sample tests are shown to the model
460
+ msgs = [{"role": "user", "content": prompt}]
461
+ for _try in range(a.retry):
462
+ fb = check.feedback(strip_think(resps[0]))
463
+ if fb is None: # the visible tests pass: nothing more to tell it
464
+ break
465
+ msgs += [{"role": "assistant", "content": strip_think(resps[0])}, {"role": "user", "content": fb}]
466
+ resps[0], n = ask(msgs, 0)
467
+ tt += n
468
+ tries += 1
469
+ oks = [bool(check(strip_think(resps[0])))]
470
+ if oks[0]:
471
+ break
472
+ if a.votes > 1 and cat == "math": # self-consistency: the most common final number wins, ties go to the first sample
473
+ ans = [(numbers(strip_think(r)) or [None])[-1] for r in resps]
474
+ best = max(ans, key=lambda x: (ans.count(x), -ans.index(x)))
475
+ ok = oks[ans.index(best)]
476
+ else: # no canonical answer to vote on: the majority of the samples must pass
477
+ ok = sum(oks) * 2 > len(oks)
478
+ toks += tt
479
+ res[tid] = {"cat": cat, "pass": ok, "frac": sum(oks) / len(oks), "toks": tt, "resp": resps[0], **({"retries": tries} if a.retry else {})}
480
+ print(f"{k:3d}/{len(tasks)} {tid:22s} {'PASS' if ok else 'fail'} {sum(oks)}/{len(oks)} {time.time() - t0:5.0f} s" + (f" retries {tries}" if a.retry else ""), flush=True)
481
+ save()
482
+ print(f"{toks} tokens in {time.time() - t0:.0f} s -> {out}")
483
+ cmd_compare(argparse.Namespace(files=[out]))
484
+
485
+
486
+ def cmd_compare(a):
487
+ docs = [json.loads(Path(f).read_text(encoding="utf-8")) for f in a.files]
488
+ tasks = [d for d in docs if d["kind"] == "tasks"]
489
+ if tasks:
490
+ def rate(d, c):
491
+ r = [v["pass"] for v in d["results"].values() if c is None or v["cat"] == c]
492
+ return sum(r), len(r)
493
+ ref = tasks[0]
494
+ rows = []
495
+ for c in list(CATS) + [None]:
496
+ if c and not rate(ref, c)[1]:
497
+ continue
498
+ row = [c or "ALL"]
499
+ for d in tasks:
500
+ k, n = rate(d, c)
501
+ rk, rn = rate(ref, c)
502
+ pct = f"{100 * (k / n) / (rk / rn):.0f}%" if n and rk else "-"
503
+ row.append(f"{k}/{n}" + ("" if d is ref else f" ({pct} of ref)"))
504
+ rows.append(row)
505
+ rows.append(["time / tokens"] + [f"{d.get('seconds', 0)} s / {d.get('tokens', 0)} tok ({d.get('tokens', 0) / max(d.get('seconds', 1), 1):.1f} t/s)" for d in tasks])
506
+ table(rows, ["tasks"] + [d["name"] + (" (ref)" if d is ref else "") for d in tasks])
507
+ for d in tasks[1:]: # paired sign test on the tasks only one of the two passes ("beats" needs p < 0.05 in its favour)
508
+ ids = [i for i in ref["results"] if i in d["results"]]
509
+ b = sum(ref["results"][i]["pass"] and not d["results"][i]["pass"] for i in ids)
510
+ c = sum(d["results"][i]["pass"] and not ref["results"][i]["pass"] for i in ids)
511
+ p = min(1.0, 2 * sum(math.comb(b + c, i) for i in range(min(b, c) + 1)) / 2 ** (b + c)) if b + c else 1.0
512
+ print(f"paired {d['name']} vs {ref['name']}: only {ref['name']} passes {b}, only {d['name']} passes {c}, sign test p = {p:.3f}")
513
+ klds = [d for d in docs if d["kind"] == "kld"]
514
+ if klds:
515
+ print()
516
+ rows = [[dom] + [f"KLD {d['domains'][dom]['kld']:.3f} top {d['domains'][dom]['top']:.1f}% pplx {d['domains'][dom]['ppl_ratio']:.3f}"
517
+ if dom in d["domains"] else "-" for d in klds] for dom in ("tr", "en", "code", "math")]
518
+ table(rows, ["domain"] + [d["name"] for d in klds])
519
+
520
+
521
+ def parse_kld(text: str) -> dict:
522
+ g = lambda p: float(re.search(p, text).group(1))
523
+ return {"kld": g(r"Mean\s+KLD:\s+([\d.]+)"), "top": g(r"Same top p:\s+([\d.]+)"),
524
+ "ppl_ratio": g(r"Mean PPL\(Q\)/PPL\(base\)\s*:\s+([\d.]+)"), "ppl": g(r"Mean PPL\(Q\)\s*:\s+([\d.]+)")}
525
+
526
+
527
+ def cmd_kld(a):
528
+ ev = Path(a.eval_dir)
529
+ res = {}
530
+ for dom in ("tr", "en", "code", "math"):
531
+ base = ev / f"base_{dom}.kld"
532
+ cmd = [str(Path(a.bin) / "llama-perplexity"), "-m", a.model, "-f", str(ev / f"eval_{dom}.txt"), "-c", "512", "-b", "512", "-ub", "512",
533
+ "--chunks", str(a.chunks), "-ngl", "99", "-fa", "on", "-lzm", "on", "-t", str(a.threads)]
534
+ cmd += (["-ot", a.ot] if a.ot else []) + shlex.split(a.extra) + ["--kl-divergence-base", str(base)]
535
+ cmd += [] if a.save_base else ["--kl-divergence"]
536
+ t0 = time.time()
537
+ p = subprocess.run(cmd, stdout=subprocess.PIPE, stderr=subprocess.STDOUT, text=True)
538
+ if p.returncode != 0:
539
+ sys.exit(f"{dom}: llama-perplexity failed ({p.returncode}):\n{p.stdout[-1500:]}")
540
+ if a.save_base:
541
+ m = re.search(r"Final estimate: PPL = ([\d.]+)", p.stdout)
542
+ res[dom] = {"ppl": float(m.group(1)) if m else None}
543
+ print(f"{dom:5s} base saved {base} ({time.time() - t0:.0f} s) PPL {res[dom]['ppl']}", flush=True)
544
+ continue
545
+ try:
546
+ res[dom] = parse_kld(p.stdout)
547
+ except AttributeError:
548
+ sys.exit(f"{dom}: no statistics in the output:\n{p.stdout[-1500:]}")
549
+ r = res[dom]
550
+ print(f"{dom:5s} KLD {r['kld']:.3f} same top {r['top']:.1f}% PPL ratio {r['ppl_ratio']:.3f} ({time.time() - t0:.0f} s)", flush=True)
551
+ if not a.save_base:
552
+ out = a.out or f"kld_{a.name}.json"
553
+ Path(out).write_text(json.dumps({"kind": "kld", "name": a.name, "domains": res}, indent=1), encoding="utf-8")
554
+ print("->", out)
555
+
556
+
557
+ def cmd_selftest(a):
558
+ T = build(a.seed)
559
+ bad = [t[0] for t in T if not t[3](t[4])]
560
+ junk = [t[0] for t in T if t[3]("zzz 0")]
561
+ code_wrong = run_code("def roman_to_int(s):\n return 0", CODE[0][2])
562
+ sample = "Mean PPL(Q) : 3.059708 ± 0.061630\nMean PPL(Q)/PPL(base) : 1.131630 ± 0.011859\n" \
563
+ "Mean KLD: 0.350462 ± 0.007497\nSame top p: 82.255 ± 0.378 %\n"
564
+ k = parse_kld(sample)
565
+ ids = [t[0] for t in T]
566
+ assert len(set(ids)) == len(ids), "duplicate task ids"
567
+ assert numbers("1.250 ve 3,5 ve 12") == [1250, Fr(7, 2), 12] and strip_think("<think>x</think>ok") == "ok" and strip_think("<think>x") == ""
568
+ assert not bad and not junk and not code_wrong, (bad, junk, code_wrong)
569
+ assert k == {"kld": 0.350462, "top": 82.255, "ppl_ratio": 1.13163, "ppl": 3.059708}, k
570
+ n = {c: sum(t[1] == c for t in T) for c in CATS}
571
+ print(f"selftest OK: {len(T)} tasks {n}; reference answers pass, garbage fails, kld parser reads the sample")
572
+
573
+
574
+ def main():
575
+ ap = argparse.ArgumentParser(description=__doc__, formatter_class=argparse.RawDescriptionHelpFormatter)
576
+ sub = ap.add_subparsers(dest="cmd", required=True)
577
+ r = sub.add_parser("run")
578
+ r.add_argument("--endpoint", default="http://127.0.0.1:8080")
579
+ r.add_argument("--name", required=True)
580
+ r.add_argument("--out")
581
+ r.add_argument("--seed", type=int, default=1)
582
+ r.add_argument("--only", default=",".join(CATS), help="categories, comma separated")
583
+ r.add_argument("--suite", choices=("basic", "hard", "big", "codehard"), default="basic", help="hard: HumanEval+, MATH-500, MMLU-Pro (08_hard_tasks.py); big: four domains, nested pools (09_big_suite.py); codehard: LiveCodeBench + CRUXEval (10_code_hard.py, use --max-tokens 4096 --ctx 8192); use --out")
584
+ r.add_argument("--hard-n", type=int, default=100, help="tasks per category in the hard / big suite (big: 60 = quick prefix, 300 = full)")
585
+ r.add_argument("--max-tokens", type=int, default=768)
586
+ r.add_argument("--think", action="store_true", help="send chat_template_kwargs enable_thinking=true")
587
+ r.add_argument("--redo", help="results file: run only the tasks it failed (same --suite/--seed/--hard-n)")
588
+ r.add_argument("--redo-max", type=int, default=1000)
589
+ r.add_argument("--first-from", help="with --retry: take each task's first answer from this results file (its 'resp') instead of generating it again")
590
+ r.add_argument("--retry", type=int, default=0, help="lcb tasks: after a failed answer, show the model its first failing VISIBLE sample test and ask again, up to N times (needs --ctx big enough for the extra turns)")
591
+ r.add_argument("--temperature", type=float, help="sampling temperature (default: 0, or --temp when --votes > 1)")
592
+ r.add_argument("--top-p", type=float)
593
+ r.add_argument("--top-k", type=int)
594
+ r.add_argument("--timeout", type=int, default=1200)
595
+ r.add_argument("--no-think", action="store_true", help="send chat_template_kwargs enable_thinking=false")
596
+ r.add_argument("--votes", type=int, default=1, help="samples per task; math: the most common final number wins, other categories: majority must pass")
597
+ r.add_argument("--temp", type=float, default=0.7, help="sampling temperature when --votes > 1 ")
598
+ r.add_argument("--model", help="start llama-server on this GGUF (needs --bin) instead of using --endpoint")
599
+ r.add_argument("--bin", help="directory with llama-server")
600
+ r.add_argument("--ot", default="", help="-ot overrides for the server")
601
+ r.add_argument("--extra", default="", help="more llama-server flags, one string: --extra='--flag value'")
602
+ r.add_argument("--threads", type=int, default=8)
603
+ r.add_argument("--ctx", type=int, default=4096)
604
+ r.add_argument("--port", type=int, default=18080) # 8080 is taken on Colab
605
+ r.add_argument("--load-timeout", type=int, default=1800)
606
+ r.set_defaults(f=cmd_run)
607
+ c = sub.add_parser("compare")
608
+ c.add_argument("files", nargs="+", help="result files; the first task file is the reference")
609
+ c.set_defaults(f=cmd_compare)
610
+ k = sub.add_parser("kld")
611
+ k.add_argument("--bin", required=True, help="directory with llama-perplexity")
612
+ k.add_argument("--model", required=True)
613
+ k.add_argument("--name", required=True)
614
+ k.add_argument("--eval-dir", default="evalset")
615
+ k.add_argument("--chunks", type=int, default=16)
616
+ k.add_argument("--ot", default="", help="-ot overrides (which tensors stay on the CPU)")
617
+ k.add_argument("--extra", default="", help="more llama-perplexity flags, one string: --extra='--flag value'")
618
+ k.add_argument("--threads", type=int, default=8)
619
+ k.add_argument("--save-base", action="store_true", help="this is the reference: store its logits")
620
+ k.add_argument("--out")
621
+ k.set_defaults(f=cmd_kld)
622
+ s = sub.add_parser("selftest")
623
+ s.add_argument("--seed", type=int, default=1)
624
+ s.set_defaults(f=cmd_selftest)
625
+ a = ap.parse_args()
626
+ a.f(a)
627
+
628
+
629
+ if __name__ == "__main__":
630
+ main()
benchmarks/scripts/09_big_suite.py ADDED
@@ -0,0 +1,309 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ #!/usr/bin/env python3
2
+ # -*- coding: utf-8 -*-
3
+ """
4
+ 09_big_suite.py - the large four-domain task set behind `07_domain_eval.py run --suite big --hard-n N` (N tasks per domain, up to the pool size).
5
+ Public benchmarks, English only, every answer checked by code; the tasks of a smaller N are the FIRST N of a bigger one (nested), so a quick run
6
+ (--hard-n 60) is a prefix of the full run (--hard-n 300) and the two can be pooled:
7
+ code HumanEval+ (164) and MBPP sanitized test (257), 2:3 mix; the model writes the function, the unit tests run on it
8
+ math MATH-500 integer answers of level 2-5 (278) and AIME (90, AI-MO/aimo-validation-aime), 3:1 mix; final line "#### <integer>"
9
+ reason MMLU-Pro (240, 14 subjects round-robin, 10 options) and BIG-Bench Hard (120, 8 tasks), 2:1 mix; final line "Answer: <x>"
10
+ en RACE-high reading comprehension (120), CoLA grammar acceptability (120, half acceptable), and 120 writing prompts with machine-checked
11
+ constraints (word counts, paragraphs, bullets, case, keywords, start/end text, quotes, title, commas, P.S.), 1:1:1 mix
12
+ Needs `pip install datasets` and network access; the model-written code is EXECUTED (see 07). Selftest (same needs):
13
+ python 09_big_suite.py selftest reference answers pass, garbage fails, every constraint checker passes its own good/bad example
14
+ python 09_big_suite.py validate runs EVERY reference solution (421 code tasks) and prints the ids to put in BAD_CODE
15
+ """
16
+ import random
17
+ import re
18
+ import sys
19
+ from fractions import Fraction as Fr
20
+
21
+ HOW = "\n\nThink step by step, then give the final answer on the last line as: #### <integer>"
22
+ LETTERS = "ABCDEFGHIJ"
23
+ BAD_CODE = {"code_he32"} # code task ids whose own reference solution fails its tests (`validate`: HumanEval/32 find_zero is float-sensitive)
24
+
25
+
26
+ def mix(pools, pattern):
27
+ """The pools interleaved by the digits of `pattern` ("00101" = two from pool 0, one from pool 1, ...) until every pool is used up."""
28
+ pos, out, i = [0] * len(pools), [], 0
29
+ while any(pos[k] < len(pools[k]) for k in range(len(pools))) and i < 10 ** 6:
30
+ k = int(pattern[i % len(pattern)])
31
+ i += 1
32
+ if pos[k] < len(pools[k]):
33
+ out.append(pools[k][pos[k]])
34
+ pos[k] += 1
35
+ return out
36
+
37
+
38
+ def letter(L, k):
39
+ """check: the last 'Answer: X' of the response is the letter L (X among the first k letters)."""
40
+ pat = rf"Answer:[\s*(]*([{LETTERS[:k]}])\b"
41
+ return lambda resp: (re.findall(pat, resp) or [None])[-1] == L
42
+
43
+
44
+ def token(target):
45
+ """check: the last 'Answer: X' of the response has the single word/number X equal to the target (case-insensitive, '(A)' = 'a')."""
46
+ want = re.sub(r"[^a-z0-9\-]", "", target.casefold())
47
+ return lambda resp: bool((got := re.findall(r"Answer:[\s*(\[]*([A-Za-z0-9\-]+)", resp))) and got[-1].casefold() == want
48
+
49
+
50
+ # ---------------------------------------------------------------------------------------------------------------------------------- code
51
+ def pool_code(rnd, run_code, code_of):
52
+ from datasets import load_dataset
53
+ he, mb = [], []
54
+ for r in load_dataset("evalplus/humanevalplus", split="test"):
55
+ tests = r["test"] + f"\n\ncheck({r['entry_point']})"
56
+ he.append((f"code_he{r['task_id'].split('/')[-1]}", "code",
57
+ "Complete this Python function. Reply with one Python code block containing the whole function (and any imports it needs).\n\n"
58
+ f"```python\n{r['prompt']}```",
59
+ (lambda t: lambda resp: run_code(code_of(resp), t, 20))(tests), f"```python\n{r['prompt']}{r['canonical_solution']}\n```"))
60
+ for r in load_dataset("google-research-datasets/mbpp", "sanitized", split="test"):
61
+ tests = "\n".join(r["test_imports"]) + "\n" + "\n".join(r["test_list"])
62
+ mb.append((f"code_mbpp{r['task_id']}", "code",
63
+ "Write a Python function for the task below. Reply with one Python code block containing the whole function (and any imports it "
64
+ f"needs).\n\nTask: {r['prompt']}\n\nYour function must pass these tests (they fix its name and signature):\n" + "\n".join(r["test_list"]),
65
+ (lambda t: lambda resp: run_code(code_of(resp), t, 20))(tests), f"```python\n{r['code']}\n```"))
66
+ he, mb = [t for t in he if t[0] not in BAD_CODE], [t for t in mb if t[0] not in BAD_CODE]
67
+ rnd.shuffle(he)
68
+ rnd.shuffle(mb)
69
+ return mix([he, mb], "00111") # 2 HumanEval+ : 3 MBPP per cycle of five
70
+
71
+
72
+ # ---------------------------------------------------------------------------------------------------------------------------------- math
73
+ def pool_math(rnd, num):
74
+ from datasets import load_dataset
75
+ ma, ai = [], []
76
+ for r in load_dataset("HuggingFaceH4/MATH-500", split="test"):
77
+ if re.fullmatch(r"-?\d+", str(r["answer"]).strip()) and int(r["level"]) >= 2:
78
+ ans = Fr(str(r["answer"]).strip())
79
+ ma.append((f"math_{r['unique_id'].replace('/', '_').removesuffix('.json')}", "math", r["problem"] + HOW, num(ans), f"#### {ans}"))
80
+ for r in load_dataset("AI-MO/aimo-validation-aime", split="train"):
81
+ if re.fullmatch(r"-?\d+", str(r["answer"]).strip()):
82
+ ans = Fr(str(r["answer"]).strip())
83
+ ai.append((f"math_aime{r['id']}", "math", r["problem"] + HOW, num(ans), f"#### {ans}"))
84
+ rnd.shuffle(ma)
85
+ rnd.shuffle(ai)
86
+ return mix([ma, ai], "0001") # 3 MATH-500 : 1 AIME
87
+
88
+
89
+ # ---------------------------------------------------------------------------------------------------------------------------------- reasoning
90
+ BBH = { # task -> the closing instruction that names the answer format
91
+ "logical_deduction_five_objects": "Answer: <option letter>", "tracking_shuffled_objects_five_objects": "Answer: <option letter>",
92
+ "date_understanding": "Answer: <option letter>", "web_of_lies": "Answer: Yes or Answer: No", "boolean_expressions": "Answer: True or Answer: False",
93
+ "navigate": "Answer: Yes or Answer: No", "formal_fallacies": "Answer: valid or Answer: invalid", "object_counting": "Answer: <integer>"}
94
+
95
+
96
+ def pool_reason(rnd):
97
+ from datasets import load_dataset
98
+ by = {}
99
+ for r in load_dataset("TIGER-Lab/MMLU-Pro", split="test"):
100
+ by.setdefault(r["category"], []).append(r)
101
+ cats = sorted(by)
102
+ for c in cats:
103
+ rnd.shuffle(by[c])
104
+ mm = []
105
+ for i in range(18): # round-robin over the 14 subjects, the subject order shuffled each round
106
+ order = cats[:]
107
+ rnd.shuffle(order)
108
+ for c in order:
109
+ r = by[c][i]
110
+ opts = "\n".join(f"{LETTERS[j]}. {o}" for j, o in enumerate(r["options"]))
111
+ mm.append((f"reason_mmlu{r['question_id']}", "reason", f"{r['question']}\n\n{opts}\n\nThink step by step, then end with exactly: Answer: <letter>",
112
+ letter(r["answer"], 10), f"Answer: {r['answer']}"))
113
+ mm = mm[:240]
114
+ bb = []
115
+ for task, fmt in BBH.items():
116
+ rows = list(load_dataset("lukaemon/bbh", task, split="test"))
117
+ rnd.shuffle(rows)
118
+ bb.append([(f"reason_bbh_{task}_{k}", "reason", f"{r['input']}\n\nThink step by step, then end with exactly: {fmt}", token(r["target"]),
119
+ f"Answer: {r['target'].strip('()')}") for k, r in enumerate(rows[:15])])
120
+ return mix([mm, mix(bb, "01234567")], "001") # 2 MMLU-Pro : 1 BBH
121
+
122
+
123
+ # ---------------------------------------------------------------------------------------------------------------------------------- English
124
+ def _words(r):
125
+ return len(re.findall(r"\w+", r))
126
+
127
+
128
+ C = {} # constraint name -> (tag, make); make(rnd) -> (rule sentence, check, a response that passes, a response that fails)
129
+ CLASH = [("lowercase", "end_phrase"), ("lowercase", "start_word"), ("lowercase", "postscript"), ("uppercase", "end_phrase"),
130
+ ("uppercase", "start_word"), ("wrap_quotes", "end_phrase"), ("wrap_quotes", "start_word"), ("wrap_quotes", "postscript"),
131
+ ("end_phrase", "postscript"), ("title", "start_word")]
132
+ KEYS = ("river", "lantern", "courage", "market", "silver", "window", "winter", "bridge")
133
+ BANNED = ("very", "really", "good", "nice", "thing")
134
+ ENDS = ("Is there anything else I can help with?", "That is all for now.", "Thank you for reading.")
135
+ STARTS = ("Today", "Imagine", "Remember", "Consider")
136
+
137
+
138
+ def cons(tag):
139
+ def deco(f):
140
+ C[f.__name__] = (tag, f)
141
+ return f
142
+ return deco
143
+
144
+
145
+ @cons("len")
146
+ def words_min(rnd):
147
+ n = rnd.choice((80, 120, 150, 200))
148
+ return f"Your response must contain at least {n} words.", lambda r: _words(r) >= n, "word " * n, "word " * (n - 1)
149
+
150
+
151
+ @cons("len")
152
+ def words_max(rnd):
153
+ n = rnd.choice((60, 80, 100, 120))
154
+ return f"Your response must contain fewer than {n} words.", lambda r: _words(r) < n, "word " * (n - 1), "word " * n
155
+
156
+
157
+ @cons("struct")
158
+ def paragraphs(rnd):
159
+ n = rnd.choice((2, 3, 4))
160
+ return (f"Write exactly {n} paragraphs, separated from each other by a line containing only ***.",
161
+ lambda r: len(p := re.split(r"(?m)^[ \t]*\*\*\*[ \t]*$", r.strip())) == n and all(x.strip() for x in p),
162
+ "\n***\n".join(["para"] * n), "\n***\n".join(["para"] * (n + 1)))
163
+
164
+
165
+ @cons("struct")
166
+ def bullets(rnd):
167
+ n = rnd.choice((3, 4, 5))
168
+ return (f"Use exactly {n} bullet points in markdown format, each line starting with \"* \", and no other bullets.",
169
+ lambda r: sum(1 for l in r.splitlines() if re.match(r"\s*\* ", l)) == n, "\n".join(["* item"] * n), "\n".join(["* item"] * (n + 1)))
170
+
171
+
172
+ @cons("kw")
173
+ def keywords(rnd):
174
+ a, b = rnd.sample(KEYS, 2)
175
+ return (f"Include the words \"{a}\" and \"{b}\".", lambda r: all(re.search(rf"\b{k}\b", r, re.I) for k in (a, b)), f"{a} and {b}", f"only {a}")
176
+
177
+
178
+ @cons("forbid")
179
+ def forbid(rnd):
180
+ w = rnd.choice(BANNED)
181
+ return f"Do not use the word \"{w}\" anywhere.", lambda r: not re.search(rf"\b{w}\b", r, re.I), "fine text", f"a {w} text"
182
+
183
+
184
+ @cons("case")
185
+ def lowercase(rnd):
186
+ return "Your entire response must be in lowercase letters, with no capital letters at all.", lambda r: r == r.lower(), "all lower", "Not lower"
187
+
188
+
189
+ @cons("case")
190
+ def uppercase(rnd):
191
+ return "Your entire response must be in ALL CAPITAL LETTERS.", lambda r: r == r.upper(), "ALL UPPER", "Not upper"
192
+
193
+
194
+ @cons("end")
195
+ def end_phrase(rnd):
196
+ p = rnd.choice(ENDS)
197
+ return f"Finish your response with the exact phrase \"{p}\" and add nothing after it.", lambda r: r.strip().endswith(p), f"text {p}", f"{p} text"
198
+
199
+
200
+ @cons("start")
201
+ def start_word(rnd):
202
+ w = rnd.choice(STARTS)
203
+ return f"Begin your response with the word \"{w}\".", lambda r: bool(re.match(rf"\W*{w}\b", r.strip())), f"{w} is a day", f"Then {w}"
204
+
205
+
206
+ @cons("wrap")
207
+ def wrap_quotes(rnd):
208
+ return ("Wrap your entire response in double quotation marks.", lambda r: len(r.strip()) > 1 and r.strip()[0] == '"' and r.strip()[-1] == '"',
209
+ '"text"', "text")
210
+
211
+
212
+ @cons("title")
213
+ def title(rnd):
214
+ return "Include a title wrapped in double angular brackets, like <<title>>.", lambda r: bool(re.search(r"<<[^<>\n]+>>", r)), "<<A title>> body", "A title body"
215
+
216
+
217
+ @cons("punct")
218
+ def no_commas(rnd):
219
+ return "Do not use any commas in your response.", lambda r: "," not in r, "no comma", "a, b"
220
+
221
+
222
+ @cons("ps")
223
+ def postscript(rnd):
224
+ return "At the end, add a postscript that starts with P.S.", lambda r: bool(re.search(r"(?m)^\s*P\.S\.", r)), "text\nP.S. more", "text"
225
+
226
+
227
+ @cons("letter")
228
+ def letter_z(rnd):
229
+ n = rnd.choice((3, 5))
230
+ return f"The letter \"z\" must appear at least {n} times.", lambda r: r.lower().count("z") >= n, "z" * n, "z" * (n - 1)
231
+
232
+
233
+ TOPICS = ("a rainy morning in a small town", "why sleep matters", "the history of tea", "a lost umbrella", "learning to swim as an adult", "a city without cars",
234
+ "the first day at a new job", "a village market", "the smell of fresh bread", "an old lighthouse", "a long train journey", "keeping a diary",
235
+ "a robot that learns to paint", "the quiet of a library", "planting a vegetable garden", "a broken clock", "moving to another country",
236
+ "the last bus home", "a mountain hike", "a neighbour who plays the violin", "saving money as a student", "a power cut at night",
237
+ "the invention of the bicycle", "a street musician", "cooking for a big family", "an unexpected letter", "the sound of the sea",
238
+ "a school sports day", "a very small apartment", "the first snow of winter")
239
+ FORMS = ("short story", "product description", "email to a colleague", "blog post", "poem", "short speech", "review", "letter to a friend",
240
+ "news summary", "explanation for a ten-year-old")
241
+
242
+
243
+ def pool_en(rnd):
244
+ from datasets import load_dataset
245
+ race = list(load_dataset("ehovy/race", "high", split="test"))
246
+ rnd.shuffle(race)
247
+ ra = []
248
+ for k, r in enumerate(race[:120]): # several questions share one passage id, so the position is part of the task id
249
+ opts = "\n".join(f"{LETTERS[j]}. {o}" for j, o in enumerate(r["options"]))
250
+ ra.append((f"en_race_{k:03d}_{r['example_id'].replace('/', '_').replace('.txt', '')}", "en",
251
+ f"Read the passage, then answer the question.\n\nPassage:\n{r['article']}\n\nQuestion: {r['question']}\n\n{opts}\n\n"
252
+ "Think briefly, then end with exactly: Answer: <letter>", letter(r["answer"], 4), f"Answer: {r['answer']}"))
253
+ cola = list(load_dataset("nyu-mll/glue", "cola", split="validation"))
254
+ yes, no = [r for r in cola if r["label"] == 1], [r for r in cola if r["label"] == 0]
255
+ rnd.shuffle(yes)
256
+ rnd.shuffle(no)
257
+ co = []
258
+ for r in mix([yes[:60], no[:60]], "01"):
259
+ co.append((f"en_cola_{r['idx']}", "en", "Is this sentence grammatically acceptable English, as a careful native speaker would judge it?\n\n"
260
+ f"Sentence: {r['sentence']}\n\nGive at most two sentences of reasoning, then end with exactly: Answer: Yes or Answer: No",
261
+ token("Yes" if r["label"] == 1 else "No"), f"Answer: {'Yes' if r['label'] == 1 else 'No'}"))
262
+ wr, names = [], sorted(C)
263
+ for i in range(120):
264
+ for _ in range(200):
265
+ pick = rnd.sample(names, 2 if rnd.random() < 0.7 else 3)
266
+ if len({C[n][0] for n in pick}) == len(pick) and not any(a in pick and b in pick for a, b in CLASH):
267
+ break
268
+ parts = [C[n][1](rnd) for n in pick]
269
+ wr.append((f"en_if{i:03d}", "en", f"Write a {rnd.choice(FORMS)} about {rnd.choice(TOPICS)}. Follow every rule below.\n" + "\n".join(f"- {p[0]}" for p in parts),
270
+ (lambda cs: lambda resp: all(c(resp) for c in cs))([p[1] for p in parts]), None))
271
+ return mix([ra, co, wr], "012")
272
+
273
+
274
+ # ---------------------------------------------------------------------------------------------------------------------------------- the suite
275
+ def build_big(seed, n, run_code, code_of, num):
276
+ """[(id, category, prompt, check(response) -> bool, reference response or None)]: the first n tasks of each pool (nested for any n)."""
277
+ T = []
278
+ for name, pool in (("code", lambda r: pool_code(r, run_code, code_of)), ("math", lambda r: pool_math(r, num)), ("reason", pool_reason), ("en", pool_en)):
279
+ T += pool(random.Random(f"{seed}big{name}"))[:n]
280
+ assert len({t[0] for t in T}) == len(T), "duplicate task ids"
281
+ return T
282
+
283
+
284
+ def constraint_unit_tests():
285
+ bad = []
286
+ for name, (tag, make) in C.items():
287
+ for s in range(8):
288
+ _, chk, good, worse = make(random.Random(s))
289
+ if not chk(good) or chk(worse):
290
+ bad.append((name, s))
291
+ return bad
292
+
293
+
294
+ if __name__ == "__main__" and sys.argv[1:] in (["selftest"], ["validate"]):
295
+ import importlib
296
+ sys.path.insert(0, __import__("pathlib").Path(__file__).resolve().parent.as_posix())
297
+ m = importlib.import_module("07_domain_eval")
298
+ if sys.argv[1] == "selftest":
299
+ assert not constraint_unit_tests(), constraint_unit_tests()
300
+ T = build_big(1, 12, m.run_code, m.code_of, m.num)
301
+ ref = [t for t in T if t[4] is not None]
302
+ bad = [t[0] for t in ref if not t[3](t[4])]
303
+ junk = [t[0] for t in T if t[4] is not None and t[3]("zzz 987654321")] # the constraint tasks have no reference and garbage may satisfy a rule
304
+ per = {c: sum(t[1] == c for t in T) for c in ("code", "math", "reason", "en")}
305
+ assert not bad and not junk and per == {c: 12 for c in per} and len({t[0] for t in T}) == len(T), (bad, junk, per)
306
+ print(f"selftest OK: {len(T)} tasks {per}, {len(ref)} references pass, garbage fails, {len(C)} constraint checkers pass their own examples")
307
+ else:
308
+ bad = [t[0] for t in pool_code(random.Random("v"), m.run_code, m.code_of) if not t[3](t[4])]
309
+ print(f"{len(bad)} reference solutions fail: BAD_CODE = {set(bad)!r}")
benchmarks/scripts/10_code_hard.py ADDED
@@ -0,0 +1,321 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ #!/usr/bin/env python3
2
+ # -*- coding: utf-8 -*-
3
+ """
4
+ 10_code_hard.py - harder coding benchmarks behind `07_domain_eval.py run --suite codehard --hard-n N` (N tasks per benchmark, nested prefixes):
5
+ lcb LiveCodeBench code_generation_lite, release 6: 175 AtCoder and LeetCode contest problems from 2025-01 to 2025-04 (newer than most training
6
+ data), about 40 hidden tests each; the model writes a whole program (stdin/stdout) or a LeetCode class, every test must pass.
7
+ The task id carries the difficulty: lcb_easy_..., lcb_medium_..., lcb_hard_...
8
+ crux CRUXEval (800 short Python functions), half output prediction (CRUXEval-O) and half input prediction (CRUXEval-I): code understanding,
9
+ checked by running `assert f(...) == ...` on the model's answer.
10
+ Public data, no token. Needs `pip install datasets` and network (the LiveCodeBench file is about 1 GB). THE MODEL-WRITTEN CODE IS EXECUTED (see 07).
11
+ Use `--max-tokens 4096 --ctx 8192` for the lcb tasks (long answers). Checks:
12
+ python 10_code_hard.py selftest lookup-table solutions pass every test of a few lcb problems, wrong ones fail; crux references pass
13
+ python 10_code_hard.py validate runs all 800 crux references and prints the ids to put in BAD_CRUX
14
+ """
15
+ import base64
16
+ import io
17
+ import json
18
+ import os
19
+ import pickle
20
+ import random
21
+ import re
22
+ import subprocess
23
+ import sys
24
+ import tempfile
25
+ import time
26
+ import zlib
27
+ from pathlib import Path
28
+
29
+ LCB_FILES = ("test6",) # release 6 (2025-01 .. 2025-04); add "test5" (2024-09 .. 2025-01) for a bigger pool
30
+ BAD_CRUX = set() # crux ids whose own reference fails (filled by `validate`)
31
+ HEADER = ("from string import *\nfrom re import *\nfrom datetime import *\nfrom collections import *\nfrom heapq import *\nfrom bisect import *\n"
32
+ "from copy import deepcopy\nfrom math import *\nfrom typing import *\nfrom functools import *\nfrom itertools import *\nfrom operator import *\n"
33
+ "from random import *\nfrom statistics import *\nimport sys, heapq, bisect, math, collections, itertools, functools, random, string, re, json, copy\n")
34
+ FUNC_RUNNER = '''import json, sys
35
+ ns = {}
36
+ exec(compile(open("sol.py", encoding="utf-8").read(), "sol.py", "exec"), ns)
37
+ f = getattr(ns["Solution"](), sys.argv[1])
38
+ for inp, out in json.load(open("tests.json", encoding="utf-8")):
39
+ got = f(*[json.loads(l) for l in inp.split("\\n") if l.strip()])
40
+ if json.loads(json.dumps(got)) != json.loads(out):
41
+ print("FAIL")
42
+ sys.exit(1)
43
+ print("OK")
44
+ '''
45
+
46
+
47
+ # ---------------------------------------------------------------------------------------------------------------------------------- LiveCodeBench
48
+ class _Plain(pickle.Unpickler):
49
+ """The private tests are a pickled JSON string; a string needs no globals, so refuse every one (a dataset must not run code while loading)."""
50
+ def find_class(self, module, name):
51
+ raise pickle.UnpicklingError(f"global {module}.{name} refused")
52
+
53
+
54
+ def decode_private(s):
55
+ return json.loads(_Plain(io.BytesIO(zlib.decompress(base64.b64decode(s)))).load())
56
+
57
+
58
+ def lcb_tests(row):
59
+ return json.loads(row["public_test_cases"]) + decode_private(row["private_test_cases"])
60
+
61
+
62
+ def _same(got, want):
63
+ g, w = [l.rstrip() for l in got.strip().splitlines()], [l.rstrip() for l in want.strip().splitlines()]
64
+ if len(g) != len(w):
65
+ return False
66
+ for a, b in zip(g, w):
67
+ if a == b:
68
+ continue
69
+ ta, tb = a.split(), b.split()
70
+ if len(ta) != len(tb):
71
+ return False
72
+ for x, y in zip(ta, tb):
73
+ if x == y:
74
+ continue
75
+ if re.fullmatch(r"-?\d+", x) and re.fullmatch(r"-?\d+", y): # integers are exact
76
+ return False
77
+ try:
78
+ if abs(float(x) - float(y)) > 1e-6 * max(1.0, abs(float(y))):
79
+ return False
80
+ except ValueError:
81
+ return False
82
+ return True
83
+
84
+
85
+ def _limits():
86
+ if os.name != "posix":
87
+ return None
88
+ import resource
89
+ return lambda: resource.setrlimit(resource.RLIMIT_AS, (2 << 30, 2 << 30))
90
+
91
+
92
+ def run_lcb(code, tests, func, total=90, per=8):
93
+ """True when `code` passes every test: func None = a stdin/stdout program (one process per test), else a LeetCode Solution method."""
94
+ env = {**os.environ, "OPENBLAS_NUM_THREADS": "1", "OMP_NUM_THREADS": "1"}
95
+ with tempfile.TemporaryDirectory() as d:
96
+ d = Path(d)
97
+ (d / "sol.py").write_text(HEADER + code, encoding="utf-8")
98
+ t0 = time.time()
99
+ if func is None:
100
+ for t in tests:
101
+ if time.time() - t0 > total:
102
+ return False
103
+ try:
104
+ r = subprocess.run([sys.executable, "-I", "sol.py"], input=t["input"], capture_output=True, text=True, timeout=per, cwd=d, env=env,
105
+ preexec_fn=_limits())
106
+ except (subprocess.TimeoutExpired, UnicodeDecodeError):
107
+ return False
108
+ if r.returncode != 0 or not _same(r.stdout, t["output"]):
109
+ return False
110
+ return True
111
+ (d / "tests.json").write_text(json.dumps([[t["input"], t["output"]] for t in tests]), encoding="utf-8")
112
+ (d / "run.py").write_text(FUNC_RUNNER, encoding="utf-8")
113
+ try:
114
+ r = subprocess.run([sys.executable, "-I", "run.py", func], capture_output=True, text=True, timeout=total, cwd=d, env=env, preexec_fn=_limits())
115
+ except (subprocess.TimeoutExpired, UnicodeDecodeError):
116
+ return False
117
+ return r.returncode == 0 and r.stdout.strip().endswith("OK")
118
+
119
+
120
+ FUNC_DEBUG = '''import json, sys, traceback
121
+ ns = {}
122
+ exec(compile(open("sol.py", encoding="utf-8").read(), "sol.py", "exec"), ns)
123
+ f = getattr(ns["Solution"](), sys.argv[1])
124
+ for inp, out in json.load(open("tests.json", encoding="utf-8")):
125
+ try:
126
+ got = f(*[json.loads(l) for l in inp.split("\\n") if l.strip()])
127
+ except Exception:
128
+ print("Input:\\n" + inp.strip()[:300] + "\\nRuntime error:\\n" + traceback.format_exc()[-600:])
129
+ sys.exit(1)
130
+ if json.loads(json.dumps(got)) != json.loads(out):
131
+ print("Input:\\n" + inp.strip()[:300] + "\\nExpected output:\\n" + out.strip()[:300] + "\\nYour output:\\n" + json.dumps(got)[:300])
132
+ sys.exit(1)
133
+ print("OK")
134
+ '''
135
+
136
+
137
+ def first_failure(code, tests, func, per=8):
138
+ """None when `code` passes every test in `tests` (the VISIBLE sample tests), else a short text on the first failure, for a repair prompt."""
139
+ env = {**os.environ, "OPENBLAS_NUM_THREADS": "1", "OMP_NUM_THREADS": "1"}
140
+ with tempfile.TemporaryDirectory() as d:
141
+ d = Path(d)
142
+ (d / "sol.py").write_text(HEADER + code, encoding="utf-8")
143
+ if func is None:
144
+ for t in tests:
145
+ shown = "Input:\n" + t["input"].strip()[:300]
146
+ try:
147
+ r = subprocess.run([sys.executable, "-I", "sol.py"], input=t["input"], capture_output=True, text=True, timeout=per, cwd=d, env=env,
148
+ preexec_fn=_limits())
149
+ except subprocess.TimeoutExpired:
150
+ return f"{shown}\nThe program did not finish within {per} seconds."
151
+ except UnicodeDecodeError:
152
+ return f"{shown}\nThe program printed bytes that are not valid text."
153
+ if r.returncode != 0:
154
+ return f"{shown}\nRuntime error:\n{r.stderr[-600:]}"
155
+ if not _same(r.stdout, t["output"]):
156
+ return f"{shown}\nExpected output:\n{t['output'].strip()[:300]}\nYour output:\n{r.stdout.strip()[:300]}"
157
+ return None
158
+ (d / "tests.json").write_text(json.dumps([[t["input"], t["output"]] for t in tests]), encoding="utf-8")
159
+ (d / "run.py").write_text(FUNC_DEBUG, encoding="utf-8")
160
+ try:
161
+ r = subprocess.run([sys.executable, "-I", "run.py", func], capture_output=True, text=True, timeout=per * 4, cwd=d, env=env, preexec_fn=_limits())
162
+ except subprocess.TimeoutExpired:
163
+ return "The code did not finish in time on the sample tests."
164
+ except UnicodeDecodeError:
165
+ return "The code printed bytes that are not valid text."
166
+ if r.returncode == 0 and r.stdout.strip().endswith("OK"):
167
+ return None
168
+ return (r.stdout or r.stderr).strip()[-800:] or "The code crashed."
169
+
170
+
171
+ def lcb_feedback(resp, row, func):
172
+ """What to tell the model after a failed attempt: only the visible sample tests are used (never the hidden ones); None = no new information."""
173
+ code = last_block(resp)
174
+ if not code.strip():
175
+ return "I could not find a ```python code block in your answer. Give the complete solution in one ```python block."
176
+ bad = first_failure(code, json.loads(row["public_test_cases"]), func)
177
+ if bad is None:
178
+ return None
179
+ return "Your code fails this sample test.\n\n" + bad + "\n\nFix the code. Reply with the complete corrected solution in one ```python block."
180
+
181
+
182
+ def last_block(resp):
183
+ blocks = re.findall(r"```(?:python|py|Python)?[ \t]*\n(.*?)```", resp, flags=re.S)
184
+ return blocks[-1] if blocks else ""
185
+
186
+
187
+ def lcb_prompt(row):
188
+ if row["starter_code"]:
189
+ fmt = "You will use the following starter code to write the solution to the problem and enclose your code within delimiters."
190
+ block = row["starter_code"]
191
+ else:
192
+ fmt = ("Read the inputs from stdin solve the problem and write the answer to stdout (do not directly test on the sample inputs). Enclose your "
193
+ "code within delimiters as follows. Ensure that when the python program runs, it reads the inputs, runs the algorithm and writes output "
194
+ "to STDOUT.")
195
+ block = "# YOUR CODE HERE"
196
+ return f"### Question:\n{row['question_content']}\n\n### Format: {fmt}\n```python\n{block}\n```\n\n### Answer: (use the provided format with backticks)\n"
197
+
198
+
199
+ def lcb_rows():
200
+ from datasets import load_dataset
201
+ rows = []
202
+ for f in LCB_FILES:
203
+ rows += list(load_dataset("json", data_files=f"hf://datasets/livecodebench/code_generation_lite/{f}.jsonl", split="train"))
204
+ return rows
205
+
206
+
207
+ def pool_lcb(rnd):
208
+ rows = lcb_rows()
209
+ rnd.shuffle(rows)
210
+ T = []
211
+ for r in rows:
212
+ func = json.loads(r["metadata"] or "{}").get("func_name") if r["starter_code"] else None
213
+
214
+ def check(resp, r=r, func=func, cache={}):
215
+ code = last_block(resp)
216
+ if not code.strip():
217
+ return False
218
+ if r["question_id"] not in cache:
219
+ cache.clear()
220
+ cache[r["question_id"]] = lcb_tests(r)
221
+ return run_lcb(code, cache[r["question_id"]], func)
222
+ check.feedback = lambda resp, r=r, func=func: lcb_feedback(resp, r, func) # used by `07 run --retry N`
223
+ T.append((f"lcb_{r['difficulty']}_{r['question_id']}", "lcb", lcb_prompt(r), check, None))
224
+ return T
225
+
226
+
227
+ # ---------------------------------------------------------------------------------------------------------------------------------- CRUXEval
228
+ def pool_crux(rnd, run_code):
229
+ from datasets import load_dataset
230
+ rows = [r for r in load_dataset("cruxeval-org/cruxeval", split="test")]
231
+ ro, ri = rows[:], rows[:]
232
+ rnd.shuffle(ro)
233
+ rnd.shuffle(ri)
234
+ O, I = [], []
235
+ for r in ro:
236
+ if f"crux_o_{r['id']}" in BAD_CRUX:
237
+ continue
238
+ pro = ("You are given a Python function and an assertion containing an input to the function. Complete the assertion with a literal (no "
239
+ "unsimplified expressions, no function calls) containing the output when executing the provided code on the given input, even if the "
240
+ "function is incorrect or incomplete. Think step by step, then end with exactly one line in this form: [ANSWER] assert f(...) == <literal> "
241
+ f"[/ANSWER]\n\n```python\n{r['code']}\nassert f({r['input']}) == ??\n```")
242
+
243
+ def check_o(resp, r=r):
244
+ m = re.findall(r"\[ANSWER\]\s*assert\s+f\(.*?\)\s*==\s*(.*?)\s*\[/ANSWER\]", resp, flags=re.S)
245
+ return bool(m) and run_code(r["code"], f"assert f({r['input']}) == ({m[-1]})", 10)
246
+ O.append((f"crux_o_{r['id']}", "crux", pro, check_o, f"[ANSWER] assert f({r['input']}) == {r['output']} [/ANSWER]"))
247
+ for r in ri:
248
+ if f"crux_i_{r['id']}" in BAD_CRUX:
249
+ continue
250
+ pri = ("You are given a Python function and an assertion containing an output of the function. Find any input such that executing the function "
251
+ "on the input leads to the given output. There may be several answers, give only one. Think step by step, then end with exactly one line "
252
+ f"in this form: [ANSWER] assert f(<your input>) == {r['output']} [/ANSWER]\n\n```python\n{r['code']}\nassert f(??) == {r['output']}\n```")
253
+
254
+ def check_i(resp, r=r):
255
+ m = re.findall(r"\[ANSWER\]\s*assert\s+f\((.*)\)\s*==", resp, flags=re.S)
256
+ return bool(m) and run_code(r["code"], f"assert f({m[-1]}) == {r['output']}", 10)
257
+ I.append((f"crux_i_{r['id']}", "crux", pri, check_i, f"[ANSWER] assert f({r['input']}) == {r['output']} [/ANSWER]"))
258
+ mixed, i = [], 0
259
+ while i < min(len(O), len(I)):
260
+ mixed += [O[i], I[i]]
261
+ i += 1
262
+ return mixed
263
+
264
+
265
+ # ---------------------------------------------------------------------------------------------------------------------------------- the suite
266
+ def build_codehard(seed, n, run_code, code_of, num):
267
+ """[(id, category, prompt, check(response) -> bool, reference response or None)]: the first n lcb problems and the first n crux tasks."""
268
+ T = pool_lcb(random.Random(f"{seed}lcb"))[:n] + pool_crux(random.Random(f"{seed}crux"), run_code)[:n]
269
+ assert len({t[0] for t in T}) == len(T), "duplicate task ids"
270
+ return T
271
+
272
+
273
+ def _lookup_solution(row, tests):
274
+ """A 'solution' that only looks the answers up: it passes iff the runner, the decoding and the comparison are right."""
275
+ if not row["starter_code"]:
276
+ return "import sys\nD = " + repr({t["input"]: t["output"] for t in tests}) + "\nprint(D[sys.stdin.read()], end='')\n", None
277
+ func = json.loads(row["metadata"])["func_name"]
278
+ D = {json.dumps([json.loads(l) for l in t["input"].split("\n") if l.strip()]): t["output"] for t in tests}
279
+ return f"import json\nD = {D!r}\nclass Solution:\n def {func}(self, *a):\n return json.loads(D[json.dumps(list(a))])\n", func
280
+
281
+
282
+ if __name__ == "__main__" and sys.argv[1:] in (["selftest"], ["validate"]):
283
+ import importlib
284
+ sys.path.insert(0, Path(__file__).resolve().parent.as_posix())
285
+ m = importlib.import_module("07_domain_eval")
286
+ if sys.argv[1] == "selftest":
287
+ rows = lcb_rows()
288
+ stdin, func = [r for r in rows if not r["starter_code"]][:4], [r for r in rows if r["starter_code"]][:4]
289
+ for r in stdin + func:
290
+ tests = lcb_tests(r)
291
+ code, fn = _lookup_solution(r, tests)
292
+ assert run_lcb(code, tests, fn), ("lookup solution failed", r["question_id"])
293
+ wrong = "print(0)\n" if fn is None else f"class Solution:\n def {fn}(self, *a):\n return None\n"
294
+ assert not run_lcb(wrong, tests, fn), ("wrong solution passed", r["question_id"])
295
+ assert last_block("text\n```python\nA\n```\nmore\n```python\nB\n```") == "B\n"
296
+ for r in stdin + func: # the repair feedback: a correct answer needs none, wrong / crashing / empty answers get a message
297
+ tests = json.loads(r["public_test_cases"])
298
+ good, fn = _lookup_solution(r, tests)
299
+ assert lcb_feedback("```python\n" + good + "```", r, fn) is None, ("feedback for a correct solution", r["question_id"])
300
+ bad_code = "print(0)\n" if fn is None else f"class Solution:\n def {fn}(self, *a):\n return None\n"
301
+ crash = "raise ValueError('boom')\n" if fn is None else f"class Solution:\n def {fn}(self, *a):\n raise ValueError('boom')\n"
302
+ assert "sample test" in (lcb_feedback("```python\n" + bad_code + "```", r, fn) or ""), ("no feedback for a wrong answer", r["question_id"])
303
+ assert "boom" in (lcb_feedback("```python\n" + crash + "```", r, fn) or ""), ("no error text for a crash", r["question_id"])
304
+ assert "could not find" in lcb_feedback("no code here", r, fn)
305
+ T = build_codehard(1, 10, m.run_code, m.code_of, m.num)
306
+ crux = [t for t in T if t[1] == "crux"]
307
+ bad = [t[0] for t in crux if not t[3](t[4])]
308
+ junk = [t[0] for t in T if t[1] == "crux" and t[3]("zzz 987654321")] + [t[0] for t in T if t[1] == "lcb" and t[3]("zzz 987654321")]
309
+ per = {c: sum(t[1] == c for t in T) for c in ("lcb", "crux")}
310
+ assert not bad and not junk and per == {"lcb": 10, "crux": 10}, (bad, junk, per)
311
+ print(f"selftest OK: {len(T)} tasks {per}; {len(stdin)} stdin + {len(func)} functional lcb problems pass their lookup solution and fail a wrong one; "
312
+ f"crux references pass, garbage fails")
313
+ else:
314
+ from datasets import load_dataset
315
+ bad = []
316
+ for r in load_dataset("cruxeval-org/cruxeval", split="test"):
317
+ if not m.run_code(r["code"], f"assert f({r['input']}) == ({r['output']})", 10):
318
+ bad += [f"crux_o_{r['id']}", f"crux_i_{r['id']}"]
319
+ print(f"{len(bad) // 2} crux references fail: BAD_CRUX = {set(bad)!r}")
320
+ n_ok = sum(1 for r in lcb_rows() if lcb_tests(r))
321
+ print(f"lcb: {n_ok} problems decode their tests")
benchmarks/scripts/13_swe_native.py ADDED
@@ -0,0 +1,288 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ #!/usr/bin/env python3
2
+ # -*- coding: utf-8 -*-
3
+ """
4
+ 13_swe_native.py - SWE-bench Verified (Mini) WITHOUT Docker or conda, on a plain Linux VM, driving the real Claude Code CLI.
5
+
6
+ It reuses swebench 4.0.4's own per-repo specs, setup/eval scripts and log parsers (pip install --no-deps --target /content/sb4 swebench==4.0.4),
7
+ translating the conda lines to `uv venv` environments, one /testbed checkout at a time.
8
+
9
+ python 13_swe_native.py list the instances of SWE-bench Verified Mini with their repo / version
10
+ python 13_swe_native.py gold INSTANCE_ID build the environment and check that the GOLD patch resolves the instance
11
+ python 13_swe_native.py agent --ids a,b,c --out results_swe_k448.json --endpoint http://127.0.0.1:18080 --model k448 --user agent
12
+ [--max-turns 60] [--timeout 2700] Claude Code solves each instance, the patch is graded by the official parsers
13
+
14
+ A resolved instance = every FAIL_TO_PASS test passes and no PASS_TO_PASS test breaks (the official SWE-bench definition). THE MODEL'S COMMANDS
15
+ AND THE REPOSITORIES' TESTS ARE EXECUTED: use a disposable VM; the agent runs as a separate unix user.
16
+ """
17
+ import argparse
18
+ import json
19
+ import os
20
+ import re
21
+ import shlex
22
+ import shutil
23
+ import subprocess
24
+ import sys
25
+ import time
26
+ from pathlib import Path
27
+
28
+ sys.path.insert(0, "/content/sb4")
29
+ ENVS, TESTBED, WORK = Path("/content/sb_envs"), Path("/testbed"), Path("/content/sb_work")
30
+ CONSTRAINTS = Path("/content/sb_constraints.txt")
31
+ PY_MAP = {"3.5": "3.8", "3.6": "3.8", "3.7": "3.8"} # uv offers CPython 3.8+ ; these old pins run fine on 3.8
32
+
33
+
34
+ def rows():
35
+ from datasets import load_dataset
36
+ return [dict(r) for r in load_dataset("MariusHobbhahn/swe-bench-verified-mini", split="test")]
37
+
38
+
39
+ def get(iid):
40
+ for r in rows():
41
+ if r["instance_id"] == iid:
42
+ return r
43
+ sys.exit(f"unknown instance {iid}")
44
+
45
+
46
+ def test_spec(inst):
47
+ from swebench.harness.test_spec.test_spec import make_test_spec
48
+ return make_test_spec(inst)
49
+
50
+
51
+ def env_dir(inst):
52
+ return ENVS / f"{inst['repo'].replace('/', '__')}__{inst['version']}__v4"
53
+
54
+
55
+ def adapt(script, venv, py):
56
+ """conda lines -> uv venv lines (the scripts are plain bash otherwise)."""
57
+ s = script.replace("source /opt/miniconda3/bin/activate\n", "")
58
+ s = re.sub(r"conda create -n testbed python=([\d.]+)\s+-y", lambda m: f"uv venv --seed --python {PY_MAP.get(m.group(1), m.group(1))} {venv} && {venv}/bin/python -m pip install 'setuptools<70'", s)
59
+ s = s.replace("conda activate testbed", f"source {venv}/bin/activate")
60
+ s = s.replace('echo "Current environment: $CONDA_DEFAULT_ENV"', "true")
61
+ # sphinx: tox would build its own interpreter without pytest here; run what its tox.ini runs, directly in the venv
62
+ s = re.sub(r"tox --current-env -e\S+ -v -- ", "PYTHONWARNINGS=all python -X dev -m pytest --durations 25 -rA ", s)
63
+ return s
64
+
65
+
66
+ def bash(script, log, timeout, env=None):
67
+ Path(log).parent.mkdir(parents=True, exist_ok=True)
68
+ f = Path(log).with_suffix(".sh")
69
+ f.write_text(script, encoding="utf-8")
70
+ try:
71
+ r = subprocess.run(["bash", str(f)], stdout=subprocess.PIPE, stderr=subprocess.STDOUT, text=True, timeout=timeout, errors="replace",
72
+ env={**os.environ, "PIP_CONSTRAINT": str(CONSTRAINTS), **(env or {})}) # one stream, in order: the grader reads the output between the START/END markers
73
+ out = r.stdout
74
+ code = r.returncode
75
+ except subprocess.TimeoutExpired as e:
76
+ out = ((e.stdout or b"").decode("utf-8", "replace") if isinstance(e.stdout, bytes) else (e.stdout or "")) + "\n[timeout]"
77
+ code = 124
78
+ Path(log).write_text(out, encoding="utf-8")
79
+ return code, out
80
+
81
+
82
+ def prepare(inst):
83
+ """Environment (cached per repo+version) and a fresh /testbed at the base commit with the repo installed."""
84
+ ts = test_spec(inst)
85
+ venv = env_dir(inst)
86
+ py = re.search(r"python=([\d.]+)", ts.setup_env_script)
87
+ py = PY_MAP.get(py.group(1), py.group(1)) if py else "3.9"
88
+ if not (venv / ".done").exists():
89
+ shutil.rmtree(venv, ignore_errors=True)
90
+ ENVS.mkdir(parents=True, exist_ok=True)
91
+ code, out = bash(adapt(ts.setup_env_script, venv, py), WORK / "logs" / f"{venv.name}_env.log", 3000)
92
+ if code != 0 or not (venv / "bin" / "python").exists():
93
+ raise RuntimeError(f"environment build failed (exit {code}): {out[-600:]}")
94
+ (venv / ".done").write_text("ok")
95
+ shutil.rmtree(TESTBED, ignore_errors=True)
96
+ code, out = bash(adapt(ts.install_repo_script, venv, py), WORK / "logs" / f"{inst['instance_id']}_repo.log", 2400)
97
+ if code != 0 or not (TESTBED / ".git").exists():
98
+ raise RuntimeError(f"repo install failed (exit {code}): {out[-600:]}")
99
+ return ts, venv
100
+
101
+
102
+ def alias_docstring_lines(out):
103
+ """Python 3.8 prints `test_x (mod.Class)` and the docstring line separately; add the one-line form that the dataset's test names use."""
104
+ res, prev = [], None
105
+ for line in out.split("\n"):
106
+ res.append(line)
107
+ t = line.strip()
108
+ m = re.match(r"^(test\S* \([\w.]+\))$", t)
109
+ if m:
110
+ prev = m.group(1)
111
+ elif prev and " ... " in t:
112
+ res.append(f"{prev} ... {t.rsplit(' ... ', 1)[1]}")
113
+ prev = None
114
+ else:
115
+ prev = None
116
+ return "\n".join(res)
117
+
118
+
119
+ def evaluate(inst, ts, venv, patch):
120
+ """Fresh checkout, apply the model patch, run the official eval script, grade with the official parser. -> (resolved, note)"""
121
+ from swebench.harness.grading import get_eval_report
122
+ py = re.search(r"python=([\d.]+)", ts.setup_env_script)
123
+ py = PY_MAP.get(py.group(1), py.group(1)) if py else "3.9"
124
+ subprocess.run("git reset --hard -q && git clean -fdq", shell=True, cwd=TESTBED)
125
+ if not patch.strip():
126
+ return False, "empty patch"
127
+ pf = WORK / f"{inst['instance_id']}.patch"
128
+ pf.write_text(patch if patch.endswith("\n") else patch + "\n", encoding="utf-8")
129
+ r = subprocess.run(["git", "apply", "-v", str(pf)], cwd=TESTBED, capture_output=True, text=True)
130
+ if r.returncode != 0:
131
+ return False, "patch does not apply: " + (r.stdout + r.stderr)[-200:]
132
+ code, out = bash(adapt(ts.eval_script, venv, py), WORK / "logs" / f"{inst['instance_id']}_eval.log", 2700)
133
+ logp = WORK / "logs" / f"{inst['instance_id']}_report.log"
134
+ logp.write_text(">>>>> Applied Patch\n" + alias_docstring_lines(out), encoding="utf-8")
135
+ try:
136
+ rep = get_eval_report(test_spec=ts, prediction={"instance_id": inst["instance_id"], "model_name_or_path": "k448", "model_patch": patch},
137
+ test_log_path=str(logp), include_tests_status=True)[inst["instance_id"]]
138
+ except Exception as e:
139
+ return False, f"grading error: {e!r}"
140
+ ts_ = rep.get("tests_status", {})
141
+ f2p, p2p = ts_.get("FAIL_TO_PASS", {}), ts_.get("PASS_TO_PASS", {})
142
+ note = f"F2P {len(f2p.get('success', []))}/{len(f2p.get('success', [])) + len(f2p.get('failure', []))}, P2P ok {len(p2p.get('success', []))} broken {len(p2p.get('failure', []))}"
143
+ return bool(rep.get("resolved")), note
144
+
145
+
146
+ def cmd_goldall(a):
147
+ """Gold-patch check of every instance: the ones that resolve are the runnable benchmark."""
148
+ out = Path(a.out)
149
+ res = json.loads(out.read_text(encoding="utf-8")) if out.exists() else {}
150
+ for inst in rows():
151
+ iid = inst["instance_id"]
152
+ if iid in res:
153
+ continue
154
+ t0 = time.time()
155
+ try:
156
+ ts, venv = prepare(inst)
157
+ ok, note = evaluate(inst, ts, venv, inst["patch"])
158
+ except Exception as e:
159
+ ok, note = False, f"error: {e!r}"[:300]
160
+ res[iid] = {"ok": ok, "note": note, "secs": round(time.time() - t0)}
161
+ out.write_text(json.dumps(res, indent=1), encoding="utf-8")
162
+ print(f"{iid:36s} gold_resolved={ok} {note[:100]} ({time.time() - t0:.0f} s)", flush=True)
163
+ print(f"runnable {sum(v['ok'] for v in res.values())}/{len(res)}")
164
+
165
+
166
+ def cmd_list(a):
167
+ for r in rows():
168
+ print(f"{r['instance_id']:36s} {r['repo']:20s} {r['version']:5s} F2P={len(json.loads(r['FAIL_TO_PASS']) if isinstance(r['FAIL_TO_PASS'], str) else r['FAIL_TO_PASS'])}")
169
+
170
+
171
+ def cmd_gold(a):
172
+ inst = get(a.instance)
173
+ t0 = time.time()
174
+ ts, venv = prepare(inst)
175
+ print(f"prepared in {time.time() - t0:.0f} s", flush=True)
176
+ ok, note = evaluate(inst, ts, venv, inst["patch"])
177
+ print(f"GOLD {inst['instance_id']}: resolved={ok} {note} ({time.time() - t0:.0f} s)")
178
+ return 0 if ok else 1
179
+
180
+
181
+ def agent_prompt(inst):
182
+ return (f"<uploaded_files>\n/testbed\n</uploaded_files>\nI've uploaded a python code repository in the directory /testbed. Consider the following issue "
183
+ f"description:\n\n<issue>\n{inst['problem_statement']}\n</issue>\n\nCan you help me implement the necessary changes to the repository so that the "
184
+ "requirements specified in the issue are met? I've already taken care of all changes to any of the test files described in the issue, so you do NOT "
185
+ "need to modify the testing logic or any of the tests in any way. Make the minimal changes to non-test files in /testbed so that the issue is resolved. "
186
+ "You can run the existing tests to check your work. When you are done, stop.")
187
+
188
+
189
+ def parse_stream(text):
190
+ calls = errs = 0
191
+ last, final = "", {}
192
+ for line in text.splitlines():
193
+ if not line.startswith("{"):
194
+ continue
195
+ try:
196
+ ev = json.loads(line)
197
+ except ValueError:
198
+ continue
199
+ t = ev.get("type")
200
+ if t == "assistant":
201
+ for b in (ev.get("message", {}).get("content") or []):
202
+ if b.get("type") == "tool_use":
203
+ calls += 1
204
+ elif b.get("type") == "text" and b.get("text", "").strip():
205
+ last = b["text"].strip()
206
+ elif t == "user":
207
+ c = ev.get("message", {}).get("content")
208
+ if isinstance(c, list):
209
+ errs += sum(1 for b in c if isinstance(b, dict) and b.get("type") == "tool_result" and b.get("is_error"))
210
+ elif t == "result":
211
+ final = ev
212
+ u = final.get("usage") or {}
213
+ return {"turns": final.get("num_turns", 0), "tool_calls": calls, "tool_errors": errs, "out_tok": u.get("output_tokens", 0),
214
+ "in_tok": u.get("input_tokens", 0) + u.get("cache_read_input_tokens", 0) + u.get("cache_creation_input_tokens", 0),
215
+ "stop": final.get("subtype") or ("no_result" if not final else "?"), "last": last[-300:]}
216
+
217
+
218
+ def cmd_agent(a):
219
+ ids = a.ids.split(",") if a.ids else [r["instance_id"] for r in rows()]
220
+ out = Path(a.out)
221
+ res = json.loads(out.read_text(encoding="utf-8")).get("results", {}) if out.exists() else {}
222
+ logs = Path(str(out.with_suffix("")) + "_logs")
223
+ logs.mkdir(parents=True, exist_ok=True)
224
+ home = WORK / "_home"
225
+ home.mkdir(parents=True, exist_ok=True)
226
+ t0 = time.time()
227
+ for k, iid in enumerate(ids, 1):
228
+ if iid in res:
229
+ continue
230
+ inst = get(iid)
231
+ s0 = time.time()
232
+ info = {"repo": inst["repo"], "version": inst["version"]}
233
+ try:
234
+ ts, venv = prepare(inst)
235
+ subprocess.run(["chown", "-R", f"{a.user}:{a.user}", str(TESTBED), str(home)], check=True)
236
+ env = {"PATH": f"{venv}/bin:" + os.environ.get("PATH", ""), "VIRTUAL_ENV": str(venv), "HOME": str(home), "LANG": "C.UTF-8",
237
+ "ANTHROPIC_BASE_URL": a.endpoint, "ANTHROPIC_AUTH_TOKEN": "local", "ANTHROPIC_API_KEY": "local", "ANTHROPIC_MODEL": a.model,
238
+ "ANTHROPIC_SMALL_FAST_MODEL": a.model, "ANTHROPIC_DEFAULT_HAIKU_MODEL": a.model, "ANTHROPIC_DEFAULT_SONNET_MODEL": a.model,
239
+ "ANTHROPIC_DEFAULT_OPUS_MODEL": a.model, "CLAUDE_CODE_DISABLE_NONESSENTIAL_TRAFFIC": "1", "DISABLE_TELEMETRY": "1",
240
+ "DISABLE_AUTOUPDATER": "1", "DISABLE_ERROR_REPORTING": "1", "IS_SANDBOX": "1"}
241
+ cmd = ["runuser", "-u", a.user, "--", "env"] + [f"{kk}={vv}" for kk, vv in env.items()] + shlex.split(a.claude) + [
242
+ "-p", agent_prompt(inst), "--output-format", "stream-json", "--verbose", "--dangerously-skip-permissions",
243
+ "--max-turns", str(a.max_turns), "--model", a.model]
244
+ try:
245
+ r = subprocess.run(cmd, cwd=TESTBED, capture_output=True, text=True, timeout=a.timeout, errors="replace", stdin=subprocess.DEVNULL)
246
+ text, err, timed_out = r.stdout, r.stderr, False
247
+ except subprocess.TimeoutExpired as e:
248
+ text = ((e.stdout or b"").decode("utf-8", "replace") if isinstance(e.stdout, bytes) else (e.stdout or ""))
249
+ err, timed_out = "timeout", True
250
+ (logs / f"{iid}.jsonl").write_text(text + ("\n[stderr]\n" + err[-2000:] if err else ""), encoding="utf-8")
251
+ info.update(parse_stream(text))
252
+ info["timed_out"] = timed_out
253
+ subprocess.run("git add -A && git diff --cached HEAD > /tmp/model.patch", shell=True, cwd=TESTBED)
254
+ patch = Path("/tmp/model.patch").read_text(encoding="utf-8", errors="replace")
255
+ info["patch_lines"] = patch.count("\n")
256
+ info["pass"], info["note"] = evaluate(inst, ts, venv, patch)
257
+ except Exception as e:
258
+ info.update({"pass": False, "note": f"harness error: {e!r}"[:300]})
259
+ info["secs"] = round(time.time() - s0)
260
+ res[iid] = info
261
+ out.write_text(json.dumps({"kind": "swe", "name": a.model, "seconds": round(time.time() - t0), "results": res}, indent=1), encoding="utf-8")
262
+ print(f"{k:3d}/{len(ids)} {iid:34s} {'RESOLVED' if info['pass'] else 'no':8s} turns={info.get('turns', 0):3d} calls={info.get('tool_calls', 0):3d} "
263
+ f"tool_errors={info.get('tool_errors', 0):2d} patch={info.get('patch_lines', 0):3d} lines {info['secs']:5d} s {info.get('note', '')[:80]}", flush=True)
264
+ n = len(res)
265
+ print(f"\nresolved {sum(v['pass'] for v in res.values())}/{n}")
266
+
267
+
268
+ if __name__ == "__main__":
269
+ ap = argparse.ArgumentParser(description=__doc__, formatter_class=argparse.RawDescriptionHelpFormatter)
270
+ sub = ap.add_subparsers(dest="cmd", required=True)
271
+ sub.add_parser("list")
272
+ ga = sub.add_parser("goldall")
273
+ ga.add_argument("--out", default="/content/gold_all.json")
274
+ g = sub.add_parser("gold")
275
+ g.add_argument("instance")
276
+ ag = sub.add_parser("agent")
277
+ ag.add_argument("--ids")
278
+ ag.add_argument("--out", default="results_swe.json")
279
+ ag.add_argument("--endpoint", default="http://127.0.0.1:18080")
280
+ ag.add_argument("--model", default="k448")
281
+ ag.add_argument("--user", default="agent")
282
+ ag.add_argument("--claude", default="claude")
283
+ ag.add_argument("--max-turns", type=int, default=60)
284
+ ag.add_argument("--timeout", type=int, default=2700)
285
+ a = ap.parse_args()
286
+ WORK.mkdir(parents=True, exist_ok=True)
287
+ CONSTRAINTS.write_text("Pygments==2.18.0\n", encoding="utf-8") # the version current when the official images were built
288
+ {"list": cmd_list, "gold": cmd_gold, "goldall": cmd_goldall, "agent": cmd_agent}[a.cmd](a)
benchmarks/scripts/14_msg_proxy.py ADDED
@@ -0,0 +1,87 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ #!/usr/bin/env python3
2
+ # -*- coding: utf-8 -*-
3
+ """
4
+ 14_msg_proxy.py - a small logging / rewriting proxy between Claude Code and llama-server's /v1/messages endpoint.
5
+
6
+ python 14_msg_proxy.py listens on 127.0.0.1:18082, forwards to 127.0.0.1:18080, streams the answer back, and appends every
7
+ /v1/messages request body to /content/proxy_log.jsonl. fix() is the hook for rewriting a request
8
+ (the chat template of Qwen3.8 rejects some shapes that Claude Code sends).
9
+ """
10
+ import http.client
11
+ import http.server
12
+ import json
13
+ import time
14
+
15
+ UP = ("127.0.0.1", 18080)
16
+ CRLF = bytes([13, 10])
17
+
18
+
19
+ def fix(d):
20
+ """Qwen3.8's chat template raises 'System message must be at the beginning' for a system-role entry in `messages`, which Claude Code sends
21
+ after the user turn. Fold every such entry into the last user message as an extra text block (nothing is dropped)."""
22
+ msgs = d.get("messages") or []
23
+ keep, extra = [], []
24
+ for m in msgs:
25
+ if m.get("role") == "system":
26
+ c = m.get("content")
27
+ text = c if isinstance(c, str) else chr(10).join(b.get("text", "") for b in c if isinstance(b, dict) and b.get("type") == "text")
28
+ if text.strip():
29
+ extra.append(text)
30
+ else:
31
+ keep.append(m)
32
+ if len(keep) != len(msgs):
33
+ block = dict(type="text", text=(chr(10) * 2).join(extra) if extra else "(no content)")
34
+ for m in reversed(keep):
35
+ if m.get("role") == "user":
36
+ c = m.get("content")
37
+ m["content"] = ([dict(type="text", text=c)] if isinstance(c, str) else list(c)) + [block]
38
+ break
39
+ else:
40
+ keep.append(dict(role="user", content=[block]))
41
+ d["messages"] = keep
42
+ return d
43
+
44
+
45
+ class H(http.server.BaseHTTPRequestHandler):
46
+ protocol_version = "HTTP/1.1"
47
+
48
+ def log_message(self, *a):
49
+ pass
50
+
51
+ def handle_any(self):
52
+ n = int(self.headers.get("Content-Length") or 0)
53
+ body = self.rfile.read(n) if n else b""
54
+ if self.path.startswith("/v1/messages") and body:
55
+ try:
56
+ d = json.loads(body)
57
+ with open("/content/proxy_log.jsonl", "a") as f:
58
+ f.write(json.dumps(dict(t=time.time(), path=self.path, req=d))[:600000] + "\n")
59
+ body = json.dumps(fix(d)).encode()
60
+ except Exception as e:
61
+ open("/content/proxy_err.log", "a").write(repr(e) + "\n")
62
+ c = http.client.HTTPConnection(*UP, timeout=7200)
63
+ hdr = dict((k, v) for k, v in self.headers.items() if k.lower() not in ("host", "content-length", "connection", "accept-encoding", "transfer-encoding"))
64
+ c.request(self.command, self.path, body, hdr)
65
+ r = c.getresponse()
66
+ with open("/content/proxy_status.log", "a") as f:
67
+ f.write(f"{time.time():.0f} {self.command} {self.path} {r.status}\n")
68
+ self.send_response(r.status)
69
+ for k, v in r.getheaders():
70
+ if k.lower() not in ("transfer-encoding", "connection", "content-length"):
71
+ self.send_header(k, v)
72
+ self.send_header("Transfer-Encoding", "chunked")
73
+ self.end_headers()
74
+ while True:
75
+ chunk = r.read1(65536)
76
+ if not chunk:
77
+ break
78
+ self.wfile.write(b"%x" % len(chunk) + CRLF + chunk + CRLF)
79
+ self.wfile.flush()
80
+ self.wfile.write(b"0" + CRLF + CRLF)
81
+ self.wfile.flush()
82
+
83
+ do_GET = do_POST = do_PUT = do_DELETE = handle_any
84
+
85
+
86
+ if __name__ == "__main__":
87
+ http.server.ThreadingHTTPServer(("127.0.0.1", 18082), H).serve_forever()