bambamdevs commited on
Commit
03223d7
·
0 Parent(s):

Publish OpenJEV E4B 1.0

Browse files
This view is limited to 50 files because it contains too many changes.   See raw diff
Files changed (50) hide show
  1. .gitattributes +5 -0
  2. .gitignore +7 -0
  3. CHANGELOG.md +9 -0
  4. CITATION.cff +20 -0
  5. CONTRIBUTING.md +16 -0
  6. LICENSE +111 -0
  7. MANIFEST.sha256 +92 -0
  8. NOTICE +18 -0
  9. README.md +168 -0
  10. RELEASE_METADATA.json +39 -0
  11. REPRODUCIBILITY.md +21 -0
  12. SECURITY.md +12 -0
  13. config.json +6 -0
  14. docs/BENCHMARK_EXPOSURE.md +17 -0
  15. docs/EXPERIMENT_1_RESULTS.md +50 -0
  16. docs/MULTIMODAL_EXPERIMENTAL.md +54 -0
  17. docs/OpenJEV_E4B_Experiment_1_Whitepaper.pdf +3 -0
  18. docs/README.md +17 -0
  19. docs/source/OpenJEV_E4B_Experiment_1_Whitepaper.docx +0 -0
  20. docs/source/build_whitepaper.js +207 -0
  21. eval/analysis/contamination_audit.json +70 -0
  22. eval/analysis/contamination_audit.py +84 -0
  23. eval/analysis/paired_analysis.py +165 -0
  24. eval/choice_panel/README.md +11 -0
  25. eval/choice_panel/details_r7.jsonl +3 -0
  26. eval/choice_panel/details_raw.jsonl +3 -0
  27. eval/choice_panel/leak_audit.json +87 -0
  28. eval/choice_panel/rows.jsonl +3 -0
  29. eval/choice_panel/rows.sha256 +1 -0
  30. eval/choice_panel/run_choice_panel.py +159 -0
  31. eval/choice_panel/source_run.json +16 -0
  32. eval/choice_panel/summarize.py +120 -0
  33. eval/choice_panel/summary.json +486 -0
  34. eval/choice_panel/summary.md +22 -0
  35. eval/harness/benchmark_public.py +634 -0
  36. eval/harness/requirements-benchmark.txt +14 -0
  37. eval/harness/src/__init__.py +0 -0
  38. eval/harness/src/decision_head.py +73 -0
  39. eval/harness/src/formatting.py +211 -0
  40. eval/harness/src/metrics.py +223 -0
  41. eval/multimodal_smoke/lines.tsv +14 -0
  42. eval/multimodal_smoke/make_audio_windows.ps1 +17 -0
  43. eval/multimodal_smoke/results.json +1044 -0
  44. eval/multimodal_smoke/run_smoke.py +139 -0
  45. eval/openjev_1_0_public_benchmark.csv +6 -0
  46. eval/openjev_1_0_public_benchmark.json +74 -0
  47. eval/raw_e4b_nf4_control_run/details.jsonl +3 -0
  48. eval/raw_e4b_nf4_control_run/summary.csv +6 -0
  49. eval/raw_e4b_nf4_control_run/summary.json +52 -0
  50. eval/raw_e4b_nf4_control_summary.json +46 -0
.gitattributes ADDED
@@ -0,0 +1,5 @@
 
 
 
 
 
 
1
+ *.safetensors filter=lfs diff=lfs merge=lfs -text
2
+ *.pt filter=lfs diff=lfs merge=lfs -text
3
+ *.bin filter=lfs diff=lfs merge=lfs -text
4
+ *.jsonl filter=lfs diff=lfs merge=lfs -text
5
+ *.pdf filter=lfs diff=lfs merge=lfs -text
.gitignore ADDED
@@ -0,0 +1,7 @@
 
 
 
 
 
 
 
 
1
+ __pycache__/
2
+ *.pyc
3
+ .venv/
4
+ .env
5
+ *.tmp
6
+ .DS_Store
7
+ eval/multimodal_smoke/work/
CHANGELOG.md ADDED
@@ -0,0 +1,9 @@
 
 
 
 
 
 
 
 
 
 
1
+ # Changelog
2
+
3
+ ## OpenJEV E4B 1.0 (2026-09-27)
4
+
5
+ - Packages the Experiment 1 public checkpoint with the cumulative backbone delta, LoRA adapter, decision head, calibration, loader, and release hashes.
6
+ - Documents the frozen GPQA, GSM8K, and Chess legal choice panel, cyclic option rotation, and options-only control.
7
+ - Includes complete untouched-Gemma choice-panel results and the row-construction audit. Gemma and OpenJEV 1.0 have complete choice-panel results, options-only controls, and paired statistics.
8
+ - Reports five standard public tasks under their native-order protocol.
9
+ - Adds an optional, experimental image and audio path (`multimodal=True`). The text path is unchanged. See `docs/MULTIMODAL_EXPERIMENTAL.md` for the smoke test and limits.
CITATION.cff ADDED
@@ -0,0 +1,20 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ cff-version: 1.2.0
2
+ message: "If you use this model or methodology, please cite this release."
3
+ title: "OpenJEV E4B"
4
+ type: software
5
+ version: "1.0"
6
+ date-released: 2026-09-27
7
+ authors:
8
+ - name: "OpenJEV contributors"
9
+ license: Apache-2.0
10
+ abstract: >-
11
+ OpenJEV E4B 1.0 is a direct-decision model built on Gemma 4 E4B.
12
+ The Experiment 1 report documents the method, choice-panel evaluation,
13
+ results with confidence intervals, and reproduction protocol.
14
+ preferred-citation:
15
+ type: report
16
+ title: "OpenJEV E4B: Training a direct-decision readout on Gemma 4 E4B. Experiment 1 method, evaluation, and results"
17
+ authors:
18
+ - name: "OpenJEV contributors"
19
+ year: 2026
20
+ month: 9
CONTRIBUTING.md ADDED
@@ -0,0 +1,16 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # Contributing
2
+
3
+ Contributions that improve reproducibility, portability, evaluation, calibration,
4
+ or documentation are welcome.
5
+
6
+ Please keep changes auditable:
7
+
8
+ 1. Explain the experimental claim being changed.
9
+ 2. Include the exact model/data/code version used.
10
+ 3. Add or update tests when changing loading or scoring behavior.
11
+ 4. Do not replace measured results with estimates.
12
+ 5. Keep public benchmark rows out of training and checkpoint selection.
13
+ 6. Report regressions and failed experiments instead of deleting them from the record.
14
+
15
+ For model-quality changes, include row-level or task-level evidence sufficient to
16
+ compare against the frozen public 1.0 baseline.
LICENSE ADDED
@@ -0,0 +1,111 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ Apache License
2
+ Version 2.0, January 2004
3
+ http://www.apache.org/licenses/
4
+
5
+ TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
6
+
7
+ 1. Definitions.
8
+
9
+ "License" shall mean the terms and conditions for use, reproduction, and
10
+ distribution as defined by Sections 1 through 9 of this document.
11
+
12
+ "Licensor" shall mean the copyright owner or entity authorized by the
13
+ copyright owner that is granting the License.
14
+
15
+ "Legal Entity" shall mean the union of the acting entity and all other
16
+ entities that control, are controlled by, or are under common control with
17
+ that entity.
18
+
19
+ "You" (or "Your") shall mean an individual or Legal Entity exercising
20
+ permissions granted by this License.
21
+
22
+ "Source" form shall mean the preferred form for making modifications,
23
+ including but not limited to software source code, documentation source,
24
+ and configuration files.
25
+
26
+ "Object" form shall mean any form resulting from mechanical transformation
27
+ or translation of a Source form, including but not limited to compiled
28
+ object code, generated documentation, and conversions to other media types.
29
+
30
+ "Work" shall mean the work of authorship, whether in Source or Object form,
31
+ made available under the License, as indicated by a copyright notice that
32
+ is included in or attached to the work.
33
+
34
+ "Derivative Works" shall mean any work, whether in Source or Object form,
35
+ that is based on (or derived from) the Work and for which the editorial
36
+ revisions, annotations, elaborations, or other modifications represent, as
37
+ a whole, an original work of authorship.
38
+
39
+ "Contribution" shall mean any work of authorship, including the original
40
+ version of the Work and any modifications or additions to that Work or
41
+ Derivative Works thereof, that is intentionally submitted to Licensor for
42
+ inclusion in the Work by the copyright owner or by an individual or Legal
43
+ Entity authorized to submit on behalf of the copyright owner.
44
+
45
+ "Contributor" shall mean Licensor and any individual or Legal Entity on
46
+ behalf of whom a Contribution has been received by Licensor and
47
+ subsequently incorporated within the Work.
48
+
49
+ 2. Grant of Copyright License. Subject to the terms and conditions of this
50
+ License, each Contributor hereby grants to You a perpetual, worldwide,
51
+ non-exclusive, no-charge, royalty-free, irrevocable copyright license to
52
+ reproduce, prepare Derivative Works of, publicly display, publicly perform,
53
+ sublicense, and distribute the Work and such Derivative Works in Source or
54
+ Object form.
55
+
56
+ 3. Grant of Patent License. Subject to the terms and conditions of this
57
+ License, each Contributor hereby grants to You a perpetual, worldwide,
58
+ non-exclusive, no-charge, royalty-free, irrevocable (except as stated in
59
+ this section) patent license to make, have made, use, offer to sell, sell,
60
+ import, and otherwise transfer the Work, where such license applies only to
61
+ those patent claims licensable by such Contributor that are necessarily
62
+ infringed by their Contribution(s) alone or by combination of their
63
+ Contribution(s) with the Work to which such Contribution(s) was submitted.
64
+
65
+ 4. Redistribution. You may reproduce and distribute copies of the Work or
66
+ Derivative Works thereof in any medium, with or without modifications, and
67
+ in Source or Object form, provided that You meet the following conditions:
68
+
69
+ (a) You must give any other recipients of the Work or Derivative Works a
70
+ copy of this License; and
71
+
72
+ (b) You must cause any modified files to carry prominent notices stating
73
+ that You changed the files; and
74
+
75
+ (c) You must retain, in the Source form of any Derivative Works that You
76
+ distribute, all copyright, patent, trademark, and attribution notices from
77
+ the Source form of the Work, excluding those notices that do not pertain to
78
+ any part of the Derivative Works; and
79
+
80
+ (d) If the Work includes a "NOTICE" text file as part of its distribution,
81
+ then any Derivative Works that You distribute must include a readable copy
82
+ of the attribution notices contained within such NOTICE file.
83
+
84
+ 5. Submission of Contributions. Unless You explicitly state otherwise, any
85
+ Contribution intentionally submitted for inclusion in the Work by You to
86
+ the Licensor shall be under the terms and conditions of this License.
87
+
88
+ 6. Trademarks. This License does not grant permission to use the trade
89
+ names, trademarks, service marks, or product names of the Licensor, except
90
+ as required for reasonable and customary use in describing the origin of
91
+ the Work and reproducing the content of the NOTICE file.
92
+
93
+ 7. Disclaimer of Warranty. Unless required by applicable law or agreed to
94
+ in writing, Licensor provides the Work (and each Contributor provides its
95
+ Contributions) on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF
96
+ ANY KIND, either express or implied, including, without limitation, any
97
+ warranties or conditions of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or
98
+ FITNESS FOR A PARTICULAR PURPOSE.
99
+
100
+ 8. Limitation of Liability. In no event and under no legal theory, whether
101
+ in tort (including negligence), contract, or otherwise, unless required by
102
+ applicable law, shall any Contributor be liable to You for damages,
103
+ including any direct, indirect, special, incidental, or consequential
104
+ damages arising in any way out of the use of the Work.
105
+
106
+ 9. Accepting Warranty or Additional Liability. While redistributing the
107
+ Work or Derivative Works thereof, You may choose to offer support,
108
+ warranty, indemnity, or other liability obligations and/or rights
109
+ consistent with this License.
110
+
111
+ END OF TERMS AND CONDITIONS
MANIFEST.sha256 ADDED
@@ -0,0 +1,92 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ 9bf741ee1a38f7bf5c3f39d50453ed4f011b0fdadef359b5fa4e88e592167748 .gitattributes
2
+ 05baacc8d60e5c0f2a091cdddab6fe45edeb32026517f74f04925d5a2b68e334 .gitignore
3
+ f49dea2fe639b8cea13c17356ee9a0a66ddbb15a7256285bb3c05e09e7e6df6d CHANGELOG.md
4
+ 9fcfc57d54d95857c287749b02d08af2398cbb3e8aba0ae96251a6d6d60ae347 CITATION.cff
5
+ 5aae8718d2c58f3915010e5efb4552446a7c9557b996e728e06d25f5939cbcf8 config.json
6
+ 53faf45e8ffd467cd1d874cb91dcedfeb36425a47c8cf7f9c976746babb79ba7 CONTRIBUTING.md
7
+ feee7afe83109ad8d80d8ea95bea50f3c5a5e8b7f028f5ca28d452f89cb3d0f5 docs/BENCHMARK_EXPOSURE.md
8
+ 52a8ee50506d575479cdc7b24665d9610a6275e45a2398c5fb56b845ffc3d816 docs/EXPERIMENT_1_RESULTS.md
9
+ 508205b8d7e306f53ae68f3e4fa8ba562eac6a2a7fe2e53c9328d1c80cc7693a docs/MULTIMODAL_EXPERIMENTAL.md
10
+ f441acf38e8354254070f1d429fd9345dedbdd5ee6d30b4be278fc07dea8d504 docs/OpenJEV_E4B_Experiment_1_Whitepaper.pdf
11
+ 6305c9920344cb1371a7383f1ec49ac0c6c0fa23ddc3ba0f426cea86d9108fd6 docs/README.md
12
+ 1f9b93a9324841491e0b76a92c9c356bc948ab6d8391eda4b57e6f8280eeda09 docs/source/build_whitepaper.js
13
+ 9157ce0e76f34964cfeb12bc93e81202be4106cced28f472538c0523fa27d048 docs/source/OpenJEV_E4B_Experiment_1_Whitepaper.docx
14
+ 8782d216533d3b3ec14c1694972f966979eda0b4157d7bfcf1c3208c80ddc47e eval/analysis/contamination_audit.json
15
+ 0308834527b91bc711b83ed3e5a444a912b40af3bfe8e735ae9b5ba10ae7f10e eval/analysis/contamination_audit.py
16
+ dcbb78481aff6fb33f4d73f1aed758d368e01dd476f49b16d1e0de1b8825bb0b eval/analysis/paired_analysis.py
17
+ e905f991f9d5685ad851207bb0430b9f40074b84066bc67439c559f3ec7b8027 eval/choice_panel/details_r7.jsonl
18
+ 8bf0ec06599e55e743fcbdaca736ff211819bc63e03af765ae62e67c26e6605a eval/choice_panel/details_raw.jsonl
19
+ 7245eb4d52c07ff5bc31360c900970f805c0602cd9c8ce126400196f2ceaa29a eval/choice_panel/leak_audit.json
20
+ 3f163699cd0ed731553e6cbed2cfc39f7e56379615258c9469a4e40f693fb2bf eval/choice_panel/README.md
21
+ a8bbb72147f51b0144f4177ed9e60a9223ef9c5979694423d93348b064bc8bf4 eval/choice_panel/rows.jsonl
22
+ 4fa296303ec511caa449db9bfb1ae860d7d89eceaf216b5a06274d775019244f eval/choice_panel/rows.sha256
23
+ 5833b7d6fb43b909409ba15a01defa39835f73abc03ff29b33274bc14133f9f2 eval/choice_panel/run_choice_panel.py
24
+ 3c988c70b4194f9cdb61fb5f3cf0dcae0f35b85c16a6d76ded9ca7d97f5543e6 eval/choice_panel/source_run.json
25
+ cd66c31d9d332745a371402f99ca917089bb5af9a77638bd361541dbceb1182a eval/choice_panel/summarize.py
26
+ 492648665542917dff019f4246a038e54bd3eae21cf2e88e1e78c51564c51b15 eval/choice_panel/summary.json
27
+ 037d159f637e1c2f119aace9dd7da83a933658fe0777f11d9cb4544a2a409d93 eval/choice_panel/summary.md
28
+ 25c17b666e871b25de603f8fd701310d9151d6c6364dfcd902f5c1451de1cfba eval/harness/benchmark_public.py
29
+ 40f3ead728cef1a45214df414e1c7cce062e4ebf4cded90c50b8a43ddcf4b503 eval/harness/requirements-benchmark.txt
30
+ e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855 eval/harness/src/__init__.py
31
+ 4657fd261d435d508d0b63002f0563cb1f59cc6b49650b18ae278caeef2fca69 eval/harness/src/decision_head.py
32
+ 6d8acaa401dc7f5dc077833415d0215899447e7a7c626875a9ea99e7be42025e eval/harness/src/formatting.py
33
+ 0fbf9883835e895f3e5a286dc27ff34a7de4e16da22ea48a2af52b8ae9ea9e20 eval/harness/src/metrics.py
34
+ 8c226815684ea849980ed9134aa96ebf30bb133706ff2824254a838465b158df eval/multimodal_smoke/lines.tsv
35
+ afc14dde53a2f4b23f9319f355ef61b7f41ef69d3c310c27906080f9b92beabb eval/multimodal_smoke/make_audio_windows.ps1
36
+ 79cdc1f8b348e68925fd902ea6265207069b18c5c71e0c134a9c2b6d8260665e eval/multimodal_smoke/results.json
37
+ e799bf9ce01b0e4b07dfdc48dbeebefa2f47d79083ade8f0c144cd659fd80f36 eval/multimodal_smoke/run_smoke.py
38
+ 3287e51cb152dd175c2216100c2ba360e6cded099795dccd54072f0c63af3aaa eval/openjev_1_0_public_benchmark.csv
39
+ 2219e8973f1423817ae75e4bc83287c777b1d9f698a7f52465b11ba9316dccf5 eval/openjev_1_0_public_benchmark.json
40
+ 9cca1e4e95851c986e0f34a5ee9fb1b795feab6471ed211ae145c176dd8a3e03 eval/raw_e4b_nf4_control_run/details.jsonl
41
+ 14b411f19b958de954b6bf119ad9be1f9db76b110f802c3a4e9e9b79a3b205b1 eval/raw_e4b_nf4_control_run/summary.csv
42
+ 2841009c608c04ed49a286d615eef8862fdd1fee77fd1acb07a03c72cb0e2822 eval/raw_e4b_nf4_control_run/summary.json
43
+ e03337ca35d50eb49d2f6a1db72cae5e7f5163df43cae778e5bf4104929278d3 eval/raw_e4b_nf4_control_summary.json
44
+ 3dce5a53ada89fecbb215102089609e96f272769210fb78370f2ab49125f3d08 eval/raw_run/details.jsonl
45
+ cdc1a89bb657b5b39f8b1e085d8775d11ee70b46ebc22ee9facbc70e2b0bebbf eval/raw_run/summary.csv
46
+ 9cc1dc84eb75ac464b8b26db072e72fe2431a66819c46d6ed67673a4ee71e8a4 eval/raw_run/summary.json
47
+ a95bbb7a0d05643ab78eb5efa4fccd5cae0753f0aa9503be2dab1f307ec5086f examples/basic_choice.py
48
+ 9e35f6a4d74bf08a5f8ea7cdc403835b00af382f7d6884b6a1668fb09f736dec examples/multimodal_choice.py
49
+ d94433ab0cef6915ea4239c2bbae4f222694b8a66ad52a4acc7c1f31b7ac234b LICENSE
50
+ 0d9268a312eddebce113670cc73b80546b03e2dd707ee58d081063dc059d47bd model/adapter/adapter_config.json
51
+ e1fd49fa603b046028a3891d8843a1c4e9b727a17516b6ab7ee9a1e4cc6fbebe model/adapter/adapter_model.safetensors
52
+ 5e34c781c86c33c6b1d5ad75c27f5826537c3c822b353b4956e24f9282284779 model/backbone_delta.safetensors
53
+ 455d9f2f6e2235adf1e75b5311208bb5b75194b4e1e737ed0edf04f4c50363ad model/calibration.json
54
+ 9a74fe26596e5accf593a4dd5f752720e05c8dd3a7baeeef491b61439c8093f7 model/decision_head.safetensors
55
+ 63496153a5024b8168086e39058b78919d09b7fcf80e9e35ffcaeff7b1bde672 model/nli_aux_head.safetensors
56
+ 737eb56c02f043f111ae1b6544e483f12b7f45bef7b33839f23b6fbfc52ea847 model/openjev_config.json
57
+ 183875cf3d65fad5abb8efdef47a51157c6421af0681571d1decc6f5072e0033 NOTICE
58
+ 4fbd8a23d3d10e0f4318980b2a07b6751b6697e02d34c0772ddb78f32d403833 openjev/__init__.py
59
+ 4657fd261d435d508d0b63002f0563cb1f59cc6b49650b18ae278caeef2fca69 openjev/decision_head.py
60
+ 6d8acaa401dc7f5dc077833415d0215899447e7a7c626875a9ea99e7be42025e openjev/formatting.py
61
+ fd7e199470cb6149e155991c6bd873c6e3f732e88378019dc48e83377d6afc5f openjev/modeling.py
62
+ f455a0508b38e2f310871bc3f0f370387c394aed42e03003b3f6d422288da5db openjev/multimodal.py
63
+ a86fd0cf09042589c55d87b9e451d5a92fe9147e1f9414dc3a46a6f1a4109233 provenance/checkpoint_json/arena_manifest.json
64
+ 030f5b8b1017a99402d5c226458598a958e64b9284ce41f61a31a77a2b0f3345 provenance/checkpoint_json/arena_training_meta.json
65
+ 89295fe507c532955ac762999b7c79c5e6f4b27ac5c03feaaaed543c79202100 provenance/checkpoint_json/inherited_v132/benchmark_summary.json
66
+ 50bd17c6d57741934c93321a47972fc04570cf258be43db5a297e71115333898 provenance/checkpoint_json/inherited_v132/best_checkpoint.json
67
+ 296c17091f3f71ea27e747f98890e0a3178c0937f6185b3e456f235a52f5929d provenance/checkpoint_json/inherited_v132/calibration_console.json
68
+ 57ddc0c675278be9d11e79cebc4f99c685d6bbde6a66642bdb65868286776906 provenance/checkpoint_json/inherited_v132/data_summary.json
69
+ 5d278d737cc9595c5fea216ef04dcefcda031fc58173ad7d0645005810bd8368 provenance/checkpoint_json/inherited_v132/experiment_summary.json
70
+ 31f318bdccde051f1d5329544ec4e9679a00178e0f3460d01f65f1b28c15af74 provenance/checkpoint_json/inherited_v132/firewall_metrics.json
71
+ 5fd80e836e620ca67bf7c8df3114fc5a09887528404ab1b3f999448d120cf46c provenance/checkpoint_json/inherited_v132/legacy_metrics.json
72
+ 3a3d1267c97c5094b748fe3b0eaac9c13ecee746ed0ebac8ca8cc583e508adf6 provenance/checkpoint_json/inherited_v132/quality_gate.json
73
+ 05cf3f301abbdbbb770052f2defea209796904b5ce5609686676f371a0f64892 provenance/checkpoint_json/inherited_v132/robustness_metrics.json
74
+ 3da3f39697a3ef2b2603db9e30397c155823075d5feab1bd89c4589c0a457e31 provenance/checkpoint_json/inherited_v132/schema_metrics.json
75
+ a12f787c79210e2d191da1f1954bc8c2b1423407a5b8dba51066d7439f356ddc provenance/checkpoint_json/inherited_v132/seed_firewall_metrics.json
76
+ 661145aa05e36b87a017a838fb58e1d20cd721dcaa86daa4773834d89a482f9b provenance/checkpoint_json/inherited_v132/seed_legacy_metrics.json
77
+ f94482f2c5698350bb9cdb024930683134a3ddd36f7736fbb0e721cea9d3c999 provenance/checkpoint_json/inherited_v132/seed_robustness_metrics.json
78
+ b4ec424cf97d52521d9ee98c5069c4ad68a98c20eda8e4f7b3d82bddfee4c8d6 provenance/checkpoint_json/inherited_v132/seed_schema_metrics.json
79
+ 5ad8112c5fff01261ed5cf2227c4354b500336707daec51b0e1856187bd3418d provenance/checkpoint_json/inherited_v132/seed_validation_metrics.json
80
+ 75f9197b5e2d63757a86ae7e23f36f6bb00323615e87b5c0eb6e3b9e9389f957 provenance/checkpoint_json/inherited_v132/test_metrics.json
81
+ 4f65931e9e013f30baba9d6f94174ea08920d412e9353d2f41c2c9e74e25355b provenance/checkpoint_json/inherited_v132/training_state.json
82
+ f6e6b675a35974289334ce6beaf03627addacf6c3534ca2890d0494abcb5ae02 provenance/checkpoint_json/v142_selection.json
83
+ 9fe5706f9d10e804b512a0390d6782cd448c31e9c5cbc03242d8075880168d42 provenance/checkpoint_json/v142_tournament.json
84
+ ad17c564396c1c70607bf17ca2df405c0175ad3d40cd6f754d8296090da362d9 provenance/checkpoint_json/v142_train_meta.json
85
+ 1245f3c205e704c92a03ddf1859778a4af1dc269c3c26e35161d89972b2ea8ec provenance/checkpoint_json/v151_rlcd_meta.json
86
+ e9659f6fb8047c31e9707ca0e7b2eeb9bc3e91db01bd981e0fc381b75eb05325 provenance/checkpoint_json/v151_selection.json
87
+ 07f0d5752ed95f60d261f500d34cffba2bd55fc1e37fe47080933c1d829b8af2 provenance/README.md
88
+ a0483f9a2babbfed89ccf5260c4395dfed13eeb0aabe3fcb5a77ebe83f975246 README.md
89
+ 3d975199013cb15b90591b35d92066cbdb0a034783c6693cfd03fe656c9c2b18 RELEASE_METADATA.json
90
+ 24991e21a564c157026ab75febaf2c5142a6f65a5d01bc323f89325cbed29969 REPRODUCIBILITY.md
91
+ e5a17f6965d1620d20c1b6a7e660976759aedef64422a3dbdfb97fbf1acf7221 requirements.txt
92
+ b2a8fc65c6f004d3142b921036a2ba6c0fc3e9a071a59e446587967f333a46e0 SECURITY.md
NOTICE ADDED
@@ -0,0 +1,18 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ OpenJEV E4B 1.0
2
+
3
+ This repository contains OpenJEV-authored model deltas, decision-head weights,
4
+ supporting code, evaluation artifacts, and documentation.
5
+
6
+ Base model:
7
+ google/gemma-4-E4B-it
8
+ Copyright Google
9
+ Licensed under Apache License 2.0.
10
+ Base weights are NOT redistributed in this repository.
11
+
12
+ OpenJEV is an independent research project. "Jev" and related TypeSafe names
13
+ may be trademarks of their respective owners. This project is not affiliated
14
+ with or endorsed by TypeSafe AI.
15
+
16
+ Training datasets and public benchmark datasets retain their original
17
+ licenses, attribution requirements, and terms. See the whitepaper and
18
+ evaluation harness for provenance.
README.md ADDED
@@ -0,0 +1,168 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ ---
2
+ license: apache-2.0
3
+ library_name: openjev
4
+ pipeline_tag: zero-shot-classification
5
+ base_model: google/gemma-4-E4B-it
6
+ language:
7
+ - en
8
+ tags:
9
+ - openjev
10
+ - classification
11
+ - zero-shot-classification
12
+ - decision-model
13
+ - listwise
14
+ - peft
15
+ - gemma4
16
+ - research
17
+ ---
18
+
19
+ # OpenJEV E4B 1.0
20
+
21
+ OpenJEV turns Gemma 4 E4B into a direct-decision classifier. A caller supplies state, a question, and a closed set of options; the model returns a probability for each option without generating text. This is the public 1.0 release of Experiment 1.
22
+
23
+ OpenJEV was trained and evaluated on text. An optional, experimental path (`multimodal=True`) keeps Gemma's vision and audio encoders so a call can also take an image or an audio clip; see [Experimental: image and audio input](#experimental-image-and-audio-input).
24
+
25
+ ## Download and use
26
+
27
+ The release is hosted at [bambamdevs/openjev-e4b](https://huggingface.co/bambamdevs/openjev-e4b). Install CUDA-enabled PyTorch 2.6 or newer first on Windows ([PyTorch instructions](https://pytorch.org/get-started/locally/)). The release contains the adapter, cumulative backbone delta, decision head, calibration, and loader. The loader downloads the pinned `google/gemma-4-E4B-it` base revision on first use. Hugging Face login is optional for public downloads.
28
+
29
+ ```bash
30
+ python -m pip install --upgrade "huggingface_hub<2"
31
+ hf auth login
32
+ hf download bambamdevs/openjev-e4b --local-dir openjev-e4b
33
+ cd openjev-e4b
34
+ python -m pip install -r requirements.txt
35
+ python -m examples.basic_choice
36
+ ```
37
+
38
+ To score your own options:
39
+
40
+ ```python
41
+ from openjev import OpenJEV
42
+
43
+ model = OpenJEV.from_pretrained(".", device="cuda", load_mode="nf4")
44
+ result = model.choice(
45
+ state="Checkout fails after a deployment; customers cannot place orders.",
46
+ instruction="Which team should own this incident?",
47
+ options=["Billing", "Technical outages", "Sales"],
48
+ )
49
+ print(result["probabilities"], result["selected_option"])
50
+ ```
51
+
52
+ The NF4 path keeps frozen base layers quantized and restores delta-touched layers 37–41 in dense FP16. The decision head runs in FP32. `load_mode="bf16"` is available with enough memory; its public benchmarks have not been run.
53
+
54
+ ### Use as a classifier
55
+
56
+ Put the text to classify in `state`, the question in `instruction`, and your labels in `options`. Any label set of two or more works on each call; no retraining is needed.
57
+
58
+ ```python
59
+ r = model.choice(
60
+ state="I was charged twice for my subscription this month.",
61
+ instruction="Which support queue should handle this message?",
62
+ options=["billing", "technical support", "account access", "sales"],
63
+ )
64
+ r["selected_option"] # "billing"
65
+ r["probabilities"] # one probability per option, in the same order as options
66
+ ```
67
+
68
+ The same pattern covers sentiment, topic, intent, and entailment labels. Probabilities use the release calibration in `model/calibration.json`.
69
+
70
+ ### Experimental: image and audio input
71
+
72
+ Load with `multimodal=True` to pass `image=` (path or PIL image) or `audio=` (`.wav` path) to `choice()`:
73
+
74
+ ```python
75
+ model = OpenJEV.from_pretrained(".", device="cuda", load_mode="nf4", multimodal=True)
76
+ r = model.choice(
77
+ state="",
78
+ audio="call.wav",
79
+ instruction="Which support queue should handle the spoken message?",
80
+ options=["billing", "technical support", "account access", "sales"],
81
+ )
82
+ ```
83
+
84
+ The adapter, backbone delta, and decision head were trained on text only, so this path is experimental. In a small smoke test on clean synthetic inputs (NF4, RTX 3060), it scored 12/12 on image shape, 12/12 on image colour, 8/8 on printed messages, 16/16 on spoken messages, and 12/12 on spoken reviews. With the media removed, the same questions scored at chance. Image calls took about 5 s and audio calls about 2 s. This is not a benchmark. Details, limits, and the rerun script are in [`docs/MULTIMODAL_EXPERIMENTAL.md`](./docs/MULTIMODAL_EXPERIMENTAL.md).
85
+
86
+ ## Evaluation
87
+
88
+ The four choice-panel tasks use frozen rows and cyclic option rotations. For each row, probabilities are mapped back to the original option identities and averaged across rotations; accuracy is calculated from that mean. The table also reports mean accuracy for individual presentations, which exposes option-order sensitivity. A separate options-only condition removes the state and question. The five standard public tasks below were evaluated in their native option order. All model evaluations use NF4 on the Windows RTX 3060.
89
+
90
+ <!-- CHOICE_RESULTS_START -->
91
+ ### Choice-panel evaluation
92
+
93
+ The main score selects the largest probability after averaging each row's cyclic option rotations. Wilson 95% intervals use rows as observations. Both systems use the same frozen rows and Windows NF4 evaluation path.
94
+
95
+ | Task | Rows | Rotations | Chance | Gemma base [95% CI] | OpenJEV 1.0 [95% CI] | Difference [95% CI] |
96
+ |---|---:|---:|---:|---:|---:|---:|
97
+ | GPQA Diamond, shuffled | 198 | 4 | 25.0% | 32.8% [26.7, 39.6] | 32.8% [26.7, 39.6] | +0.00 [-7.07, +7.07] pp |
98
+ | Chess legal | 500 | 4 | 25.0% | 43.8% [39.5, 48.2] | 42.6% [38.3, 47.0] | -1.20 [-6.40, +3.80] pp |
99
+ | GSM8K, 4 choices | 1,319 | 4 | 25.0% | 43.7% [41.1, 46.4] | 53.8% [51.1, 56.5] | +10.08 [+6.44, +13.72] pp |
100
+ | GSM8K, 10 choices | 1,319 | 10 | 10.0% | 20.5% [18.5, 22.8] | 30.6% [28.2, 33.2] | +10.08 [+7.05, +13.04] pp |
101
+
102
+ Mean accuracy per presentation and share of rows with the same predicted option in every rotation:
103
+
104
+ | Task | Gemma per order / same answer | OpenJEV 1.0 per order / same answer |
105
+ |---|---:|---:|
106
+ | GPQA Diamond, shuffled | 31.3% / 10.1% | 33.7% / 23.2% |
107
+ | Chess legal | 31.5% / 1.4% | 40.1% / 46.6% |
108
+ | GSM8K, 4 choices | 36.7% / 6.9% | 51.5% / 43.3% |
109
+ | GSM8K, 10 choices | 16.5% / 0.6% | 28.6% / 14.9% |
110
+
111
+ ### Options-only control
112
+
113
+ State and question are replaced by neutral text. Four-choice tasks use four rotations; GSM8K-10 uses five evenly spaced rotations.
114
+
115
+ | Task | Chance | Gemma base [95% CI] | OpenJEV 1.0 [95% CI] |
116
+ |---|---:|---:|---:|
117
+ | GPQA Diamond, shuffled | 25.0% | 25.8% [20.2, 32.3] | 32.3% [26.2, 39.1] |
118
+ | Chess legal | 25.0% | 29.0% [25.2, 33.1] | 30.8% [26.9, 35.0] |
119
+ | GSM8K, 4 choices | 25.0% | 24.7% [22.5, 27.1] | 24.3% [22.1, 26.7] |
120
+ | GSM8K, 10 choices | 10.0% | 10.5% [9.0, 12.3] | 10.8% [9.3, 12.6] |
121
+
122
+ OpenJEV 1.0 scores 32.3% on GPQA and 30.8% on Chess legal with only the options present, both above the 25% chance rate. Its GPQA full-condition score is 32.8%; this result alone does not establish use of the question. The GSM8K options-only scores are near chance. The row-construction audit passes its chance gate, but the model control reveals additional choice-only signal. Chess legal tests move legality; the GSM8K tasks test closed-choice arithmetic. Gemma uses a native answer-letter readout and OpenJEV uses a learned decision head, so comparisons measure complete systems.
123
+
124
+ Evidence: [`eval/choice_panel/`](./eval/choice_panel/). The five standard public tasks below use native option order and are reported separately.
125
+ <!-- CHOICE_RESULTS_END -->
126
+
127
+ ### Standard public tasks
128
+
129
+ **Benchmark status: COMPLETE.** All five standard public tasks have both Gemma base NF4 control and OpenJEV 1.0 results.
130
+
131
+ | Benchmark | Gemma base NF4 | OpenJEV 1.0 | OpenJEV - Gemma |
132
+ |---|---:|---:|---:|
133
+ | ARC-Challenge | 87.37% | 87.88% | +0.51 pp |
134
+ | WinoGrande | 60.14% | 66.46% | +6.32 pp |
135
+ | ARC-Easy | 95.58% | 95.62% | +0.04 pp |
136
+ | HellaSwag | 76.51% | 89.77% | +13.26 pp |
137
+ | MMLU | 66.19% | 64.41% | -1.78 pp |
138
+
139
+ The untouched base uses native LM-head answer-label scoring; OpenJEV 1.0 uses a learned decision head. Their comparison measures the complete decision systems. ARC, HellaSwag, and MMLU have training-family exposure described in [`docs/BENCHMARK_EXPOSURE.md`](./docs/BENCHMARK_EXPOSURE.md).
140
+
141
+ ## Paper and evidence
142
+
143
+ [Experiment 1 whitepaper](./docs/OpenJEV_E4B_Experiment_1_Whitepaper.pdf) describes the model, training lineage, evaluation protocol, results, limitations, and reproduction steps. It accompanies this release.
144
+
145
+ ```text
146
+ model/ adapter, head, cumulative backbone delta, calibration
147
+ openjev/ loader and scoring implementation
148
+ eval/choice_panel/ frozen rows, audit, scoring code, row-level results
149
+ eval/harness/ standard public-task runner
150
+ docs/ whitepaper, source, and result notes
151
+ provenance/ checkpoint metadata
152
+ ```
153
+
154
+ OpenJEV 1.0 is a delta release; Google base weights are not republished. `backbone_delta.safetensors` is required to restore the model.
155
+
156
+ ## Training and limitations
157
+
158
+ The model evolved through readout and adapter training, then an Arena curriculum with exact-oracle examples and replay across 20 decision families. The public checkpoint also updates selected decoder layers and includes 65 cumulative backbone-delta tensors. The development sequence, promotion rule, and release hashes are in the whitepaper and `RELEASE_METADATA.json`.
159
+
160
+ The experiment has one training seed per stage. Internal populations changed during development. The choice-panel results measure accuracy on their stated tasks; they do not isolate the effect of the head, LoRA, backbone updates, or inference precision. GPQA and GSM8K are multiple-choice tasks, and Chess legal tests move legality rather than move quality.
161
+
162
+ ## Reproduce and license
163
+
164
+ See [`REPRODUCIBILITY.md`](./REPRODUCIBILITY.md), [`RELEASE_METADATA.json`](./RELEASE_METADATA.json), and [`MANIFEST.sha256`](./MANIFEST.sha256). OpenJEV-authored material is Apache-2.0. The Gemma base remains under Google's terms. Training code is not part of this release.
165
+
166
+ OpenJEV is independent research. It is not a TypeSafe AI product, is not affiliated with or endorsed by TypeSafe AI, and is unrelated to `AlexWortega/openjev`. Its design was inspired by System 1-style decision making: a fast, direct choice over a closed set of options, without step-by-step text generation.
167
+
168
+ Generated: 2026-09-27T17:07:53.606842+00:00
RELEASE_METADATA.json ADDED
@@ -0,0 +1,39 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ {
2
+ "model_line": "openjev-e4b",
3
+ "release": "1.0",
4
+ "version": "1.0",
5
+ "internal_checkpoint": "v1.6 R7",
6
+ "accepted_arena_score": 0.024174,
7
+ "base_model": "google/gemma-4-E4B-it",
8
+ "base_revision": "ee0ef6023621cff504d758262d4e04895a5af4a2",
9
+ "selected_sparse_layers": [
10
+ 37,
11
+ 41
12
+ ],
13
+ "cumulative_backbone_delta_tensors": 65,
14
+ "source_checkpoint": "local benchmark/checkpoints/round-07",
15
+ "source_benchmark_run": "local benchmark/results/full/20260926-112930",
16
+ "generated_utc": "2026-09-27T17:07:53.606842+00:00",
17
+ "source_hashes": {
18
+ "checkpoints/round-07/config.json": "df245eb519b57ffbdb0d04ee7898fb96c8c01eaed5bc5b66b455e1f6610af7c3",
19
+ "checkpoints/round-07/decision_head.pt": "73f7f985c894580a662c5afc3e8a9ab0c4911cea963c774c1ff279e36b8f54e5",
20
+ "checkpoints/round-07/backbone_delta.pt": "6b47f1787bcda39392c424f4fa9834a46dc45f097ee8a9b2b6655f3e9855095e",
21
+ "checkpoints/round-07/adapter/adapter_config.json": "0d9268a312eddebce113670cc73b80546b03e2dd707ee58d081063dc059d47bd",
22
+ "checkpoints/round-07/adapter/adapter_model.safetensors": "e1fd49fa603b046028a3891d8843a1c4e9b727a17516b6ab7ee9a1e4cc6fbebe",
23
+ "checkpoints/round-07/nli_aux_head.pt": "46baa95ffb8e1d0d8bf9f31ef67275330be46a10a21981f811801450d991df47"
24
+ },
25
+ "benchmark_complete": true,
26
+ "benchmark_tasks_present": [
27
+ "arc_challenge",
28
+ "winogrande",
29
+ "arc_easy",
30
+ "hellaswag",
31
+ "mmlu"
32
+ ],
33
+ "benchmark_tasks_missing": [],
34
+ "conversion": {
35
+ "decision_head": "PyTorch state_dict -> safetensors via torch.load(weights_only=True)",
36
+ "backbone_delta": "PyTorch state_dict -> safetensors via torch.load(weights_only=True)",
37
+ "base_weights_redistributed": false
38
+ }
39
+ }
REPRODUCIBILITY.md ADDED
@@ -0,0 +1,21 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # Reproducibility contract
2
+
3
+ ## Model identity
4
+
5
+ OpenJEV E4B 1.0 requires the `google/gemma-4-E4B-it` base at revision `ee0ef6023621cff504d758262d4e04895a5af4a2`, its LoRA adapter, hybrid pointer and span decision head, cumulative 65-tensor backbone delta for layers 37–41, calibration (`global_temperature = 1.0`), and the packing and scoring code in `openjev/`. The adapter alone does not restore the model. Every released file is hashed in `MANIFEST.sha256`; source-checkpoint hashes and the internal checkpoint identifier are in `RELEASE_METADATA.json`.
6
+
7
+ ## Choice-panel evaluation
8
+
9
+ The frozen row file is `eval/choice_panel/rows.jsonl`, with SHA-256 in `eval/choice_panel/rows.sha256`. The construction audit is `leak_audit.json`. GPQA answers are placed by a fixed per-row shuffle. GSM8K distractors are answers to other test problems of similar magnitude. Chess legal distractors are plain legal-move strings drawn from other positions and illegal in the current position.
10
+
11
+ The runner presents every row in all cyclic option rotations for the full condition. For options-only, it replaces state and question with neutral text and uses all four rotations on four-option tasks and five evenly spaced rotations on ten-option tasks. Output probabilities are mapped back to original option identities. The main accuracy is argmax of the mean mapped probabilities for each row; Wilson 95% intervals use rows as observations. Mean per-order accuracy and same-answer share are reported separately. See `eval/choice_panel/summary.json` and `docs/EXPERIMENT_1_RESULTS.md`.
12
+
13
+ Untouched Gemma uses native LM-head answer-letter scoring in NF4. OpenJEV 1.0 uses the frozen base in NF4, delta-touched layers in dense FP16, LoRA, and an FP32 head. The comparison measures complete decision systems and does not isolate any one component.
14
+
15
+ ## Standard public tasks
16
+
17
+ ARC-Easy, ARC-Challenge, WinoGrande, HellaSwag, and MMLU use the native-order runner in `eval/harness/benchmark_public.py`. Their row-level runs and summaries are included under `eval/`. ARC, HellaSwag, and MMLU have training-family exposure recorded in `docs/BENCHMARK_EXPOSURE.md`.
18
+
19
+ ## Training record
20
+
21
+ The whitepaper describes model evolution and the Arena promotion rule. Source checkpoint records and hashes are in `provenance/` and `RELEASE_METADATA.json`. Training code is not part of this release.
SECURITY.md ADDED
@@ -0,0 +1,12 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # Security
2
+
3
+ Do not report secrets, authentication tokens, private training data, or personally
4
+ identifying information in public issues.
5
+
6
+ This research model returns probabilities over caller-provided choices. Consumers
7
+ are responsible for validating inputs, enforcing authorization, bounding tool
8
+ actions, and applying independent controls before using model outputs in
9
+ security-sensitive or safety-critical workflows.
10
+
11
+ For a security issue in release code, use the repository owner's private security
12
+ reporting channel where available.
config.json ADDED
@@ -0,0 +1,6 @@
 
 
 
 
 
 
 
1
+ {
2
+ "_note": "Marker for Hugging Face download counting. The model configuration is model/openjev_config.json; load with openjev.OpenJEV.from_pretrained.",
3
+ "model_type": "openjev",
4
+ "base_model": "google/gemma-4-E4B-it",
5
+ "openjev_config": "model/openjev_config.json"
6
+ }
docs/BENCHMARK_EXPOSURE.md ADDED
@@ -0,0 +1,17 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # Benchmark exposure and protocol
2
+
3
+ Public evaluation rows were used after promotion and did not select a checkpoint. The training compilers nevertheless used other splits from some of the same dataset families.
4
+
5
+ | Task | Source and evaluation protocol | Training-family exposure | Row overlap with public rows* |
6
+ |---|---|---|---|
7
+ | MMLU | `cais/mmlu` all/test, native order | `auxiliary_train` in v1.1 and v1.3.2 | 19 question-text / 0 question+options |
8
+ | ARC-Easy / ARC-Challenge | `allenai/ai2_arc` test, native order | ARC train and ARC material in MMLU `auxiliary_train` | At most 9 question-text / 3 question+options |
9
+ | HellaSwag | `Rowan/hellaswag` validation, native order | HellaSwag train | 23 context / 0 context+endings |
10
+ | WinoGrande | XL validation, native order | No known direct source | Not measured |
11
+ | GPQA Diamond | 198 shuffled rows; four cyclic rotations | No known direct source | Not measured |
12
+ | GSM8K-4 / GSM8K-10 | 1,319 test questions; options drawn from other problems in a matching magnitude bucket; cyclic rotations | No known GSM8K source; Arena contains arithmetic families | Not measured |
13
+ | Chess legal | 500 synthetic positions; one legal SAN and three position-inapplicable SAN choices; four rotations | No known chess source | Not applicable |
14
+
15
+ \* Normalized-text hash matches against full upstream training splits, which are supersets of the sampled training rows. The audit output for the standard tasks is [`../eval/analysis/contamination_audit.json`](../eval/analysis/contamination_audit.json). The choice-panel option-string and answer-position audit is [`../eval/choice_panel/leak_audit.json`](../eval/choice_panel/leak_audit.json).
16
+
17
+ The standard-task family exposure is distributional even where exact row overlap is small. Chess legal asks which move is legal in the shown position. The GSM8K choices test selecting a numerical answer from options; they do not measure open-ended solution generation.
docs/EXPERIMENT_1_RESULTS.md ADDED
@@ -0,0 +1,50 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # Experiment 1 results — OpenJEV E4B 1.0
2
+
3
+ The public evaluation has two protocols. GPQA Diamond, Chess legal, GSM8K-4, and GSM8K-10 use frozen choice-panel rows and cyclic option rotations. Five standard public tasks retain their native option order. Gemma and OpenJEV 1.0 completed the choice-panel evaluation on the Windows RTX 3060. The completed untouched-Gemma results below come from [`../eval/choice_panel/summary.json`](../eval/choice_panel/summary.json).
4
+
5
+ ## Choice-panel evaluation
6
+
7
+ Probabilities from each cyclic rotation are mapped to original option identities and averaged per row. The main accuracy chooses the largest mean. Wilson 95% intervals treat rows as observations. Mean per-order accuracy averages individual presentations; same-answer rate is the share of rows whose chosen option never changes across rotations.
8
+
9
+ | Task | Rows | Rotations | Chance | Gemma base [95% CI] | OpenJEV 1.0 [95% CI] | Difference [95% CI] |
10
+ |---|---:|---:|---:|---:|---:|---:|
11
+ | GPQA Diamond, shuffled | 198 | 4 | 25.0% | 32.8% [26.7, 39.6] | 32.8% [26.7, 39.6] | +0.00 [-7.07, +7.07] pp |
12
+ | Chess legal | 500 | 4 | 25.0% | 43.8% [39.5, 48.2] | 42.6% [38.3, 47.0] | -1.20 [-6.40, +3.80] pp |
13
+ | GSM8K, 4 choices | 1,319 | 4 | 25.0% | 43.7% [41.1, 46.4] | 53.8% [51.1, 56.5] | +10.08 [+6.44, +13.72] pp |
14
+ | GSM8K, 10 choices | 1,319 | 10 | 10.0% | 20.5% [18.5, 22.8] | 30.6% [28.2, 33.2] | +10.08 [+7.05, +13.04] pp |
15
+
16
+ | Task | Gemma per order / same answer | OpenJEV 1.0 per order / same answer |
17
+ |---|---:|---:|
18
+ | GPQA Diamond, shuffled | 31.3% / 10.1% | 33.7% / 23.2% |
19
+ | Chess legal | 31.5% / 1.4% | 40.1% / 46.6% |
20
+ | GSM8K, 4 choices | 36.7% / 6.9% | 51.5% / 43.3% |
21
+ | GSM8K, 10 choices | 16.5% / 0.6% | 28.6% / 14.9% |
22
+
23
+ Paired intervals use 2,000 row bootstrap resamples. Exact McNemar p-values and flip counts are in [`../eval/choice_panel/summary.json`](../eval/choice_panel/summary.json).
24
+
25
+ ## Options-only control
26
+
27
+ State and question are replaced by neutral text. Ten-choice tasks use five evenly spaced rotations in this condition.
28
+
29
+ | Task | Chance | Gemma base [95% CI] | OpenJEV 1.0 [95% CI] |
30
+ |---|---:|---:|---:|
31
+ | GPQA Diamond, shuffled | 25.0% | 25.8% [20.2, 32.3] | 32.3% [26.2, 39.1] |
32
+ | Chess legal | 25.0% | 29.0% [25.2, 33.1] | 30.8% [26.9, 35.0] |
33
+ | GSM8K, 4 choices | 25.0% | 24.7% [22.5, 27.1] | 24.3% [22.1, 26.7] |
34
+ | GSM8K, 10 choices | 10.0% | 10.5% [9.0, 12.3] | 10.8% [9.3, 12.6] |
35
+
36
+ The frozen-row construction audit passes its chance gate on all four tasks; see [`../eval/choice_panel/leak_audit.json`](../eval/choice_panel/leak_audit.json). OpenJEV 1.0 nevertheless scores above chance with options only on GPQA (32.3%) and Chess legal (30.8%). Its GPQA full-condition score is 32.8%, so that result alone does not establish use of the question. The GSM8K options-only scores are near chance. Chess legal measures legality rather than move quality; the GSM8K tasks do not measure open-ended solution generation.
37
+
38
+ ## Standard public tasks
39
+
40
+ The following are single-order results on identical rows. They compare the complete native-readout Gemma system with OpenJEV 1.0. They do not isolate the training effect of the adapter or backbone delta. ARC, HellaSwag, and MMLU have training-family exposure; see [`BENCHMARK_EXPOSURE.md`](./BENCHMARK_EXPOSURE.md).
41
+
42
+ | Task | Gemma base NF4 | OpenJEV 1.0 | Difference [95% CI] |
43
+ |---|---:|---:|---:|
44
+ | ARC-Easy | 95.58% | 95.62% | +0.04 [-0.80, +0.84] pp |
45
+ | ARC-Challenge | 87.37% | 87.88% | +0.51 [-1.37, +2.39] pp |
46
+ | WinoGrande | 60.14% | 66.46% | +6.31 [+3.79, +9.00] pp |
47
+ | HellaSwag | 76.51% | 89.77% | +13.26 [+12.40, +14.06] pp |
48
+ | MMLU | 66.19% | 64.41% | -1.77 [-2.58, -1.01] pp |
49
+
50
+ The standard-task and choice-panel scores use different protocols. Cross-task averages require explicit weights and a statement of the protocol difference.
docs/MULTIMODAL_EXPERIMENTAL.md ADDED
@@ -0,0 +1,54 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # Experimental image and audio input
2
+
3
+ OpenJEV E4B 1.0 was trained and evaluated on text. The loader can optionally keep Gemma 4's own vision and audio encoders, so `choice()` also accepts an image or a short audio clip. This path is **experimental**: none of the adapter, backbone delta, or decision head was trained on image or audio input, and it has only passed the small smoke test below.
4
+
5
+ ## Use
6
+
7
+ ```python
8
+ from openjev import OpenJEV
9
+
10
+ model = OpenJEV.from_pretrained(".", device="cuda", load_mode="nf4", multimodal=True)
11
+
12
+ model.choice(state="", image="photo.png",
13
+ instruction="What shape is shown in the image?",
14
+ options=["square", "circle", "triangle"])
15
+
16
+ model.choice(state="", audio="call.wav",
17
+ instruction="Which support queue should handle the spoken message?",
18
+ options=["billing", "technical support", "account access", "sales"])
19
+ ```
20
+
21
+ - `image` is a file path or a PIL image. `audio` is a PCM `.wav` path, or an array plus `sampling_rate=`; it is converted to 16 kHz mono.
22
+ - `state` can hold extra text alongside the media. The media tokens are placed at the start of the state.
23
+ - Without `multimodal=True`, passing `image=` or `audio=` raises an error. Text calls behave the same in both modes.
24
+
25
+ ## How it works
26
+
27
+ The text-only loader discards Gemma's vision and audio encoders. With `multimodal=True`, it keeps them, wraps Gemma's text model in place with the OpenJEV adapter and backbone delta, and inserts Gemma's image or audio soft tokens after `State:` in the usual decision prompt. The decision head reads the same option and decision positions as for text.
28
+
29
+ In NF4 mode the encoders stay in FP16, because Gemma's audio encoder cannot run with 4-bit weights. The text layers are quantized exactly as in the text-only loader.
30
+
31
+ ## Smoke test
32
+
33
+ Run on 2026-09-27 with NF4 on an RTX 3060 12 GB. Images are generated shapes and printed messages. Audio is the same messages and short reviews spoken by the two built-in Windows voices. Every question was also asked with the media removed.
34
+
35
+ | Test | With media | Media removed |
36
+ |---|---:|---:|
37
+ | Image: shape, 3 options | 12/12 | 4/12 |
38
+ | Image: colour, 4 options | 12/12 | 3/12 |
39
+ | Image: printed support message → queue, 4 options | 8/8 | 2/8 |
40
+ | Audio: spoken support message → queue, 4 options | 16/16 | 2/8 |
41
+ | Audio: spoken review → sentiment, 2 options | 12/12 | 3/6 |
42
+
43
+ - For comparison, the same support messages given as text scored 7/8, and the reviews as text scored 6/6.
44
+ - For text-only input, the multimodal forward path gives the same probabilities as the text path (maximum difference 0).
45
+ - Median latency per call: about 4.9 s with an image, 2.3 s with audio, and 0.2 s for text. Peak GPU memory was 10.7 GB.
46
+
47
+ Full rows are in [`eval/multimodal_smoke/results.json`](../eval/multimodal_smoke/results.json). To rerun: run `eval/multimodal_smoke/make_audio_windows.ps1`, then `python -m eval.multimodal_smoke.run_smoke` from the release root.
48
+
49
+ ## Limits
50
+
51
+ - These inputs are clean, synthetic, and easy. The test shows that the path works end to end; it is not a benchmark, and there are no results on real photos, noisy audio, or public image or audio datasets.
52
+ - Probabilities use the text calibration, which has not been checked for image or audio input.
53
+ - Only NF4 on one GPU was tested; `load_mode="bf16"` with `multimodal=True` has not been run.
54
+ - One image and one audio clip per call. Video is not supported.
docs/OpenJEV_E4B_Experiment_1_Whitepaper.pdf ADDED
@@ -0,0 +1,3 @@
 
 
 
 
1
+ version https://git-lfs.github.com/spec/v1
2
+ oid sha256:f441acf38e8354254070f1d429fd9345dedbdd5ee6d30b4be278fc07dea8d504
3
+ size 56722
docs/README.md ADDED
@@ -0,0 +1,17 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # Documentation — OpenJEV E4B 1.0
2
+
3
+ ## Experiment 1 whitepaper
4
+
5
+ **OpenJEV E4B: Training a direct-decision readout on Gemma 4 E4B. Experiment 1 method, evaluation, and results.**
6
+
7
+ PDF: [`OpenJEV_E4B_Experiment_1_Whitepaper.pdf`](./OpenJEV_E4B_Experiment_1_Whitepaper.pdf)
8
+
9
+ Source: [`source/OpenJEV_E4B_Experiment_1_Whitepaper.docx`](./source/OpenJEV_E4B_Experiment_1_Whitepaper.docx) and [`source/build_whitepaper.js`](./source/build_whitepaper.js).
10
+
11
+ The report covers the direct-decision interface, development sequence and promotion rule, the choice-panel evaluation, five standard public tasks, limitations, release records, and reproduction steps. Gemma and OpenJEV 1.0 have complete same-row choice-panel results, including options-only controls and paired comparisons.
12
+
13
+ Current results are in [`EXPERIMENT_1_RESULTS.md`](./EXPERIMENT_1_RESULTS.md). Frozen rows, the construction audit, scoring code, and row-level outputs are in [`../eval/choice_panel/`](../eval/choice_panel/).
14
+
15
+ ## Experimental image and audio input
16
+
17
+ [`MULTIMODAL_EXPERIMENTAL.md`](./MULTIMODAL_EXPERIMENTAL.md) describes the optional `multimodal=True` loader path, its smoke test, and its limits.
docs/source/OpenJEV_E4B_Experiment_1_Whitepaper.docx ADDED
Binary file (17.5 kB). View file
 
docs/source/build_whitepaper.js ADDED
@@ -0,0 +1,207 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ // Build the Experiment 1 report from the packaged choice-panel summary.
2
+ // Usage: node build_whitepaper.js path/to/output.docx
3
+ const fs = require("fs");
4
+ const path = require("path");
5
+ const {
6
+ Document, Packer, Paragraph, TextRun, Table, TableRow, TableCell,
7
+ WidthType, BorderStyle, AlignmentType, HeadingLevel, Footer, PageNumber,
8
+ } = require("docx");
9
+
10
+ const OUT = process.argv[2] || "OpenJEV_E4B_Experiment_1_Whitepaper.docx";
11
+ const EVAL = path.resolve(__dirname, "../../eval/choice_panel");
12
+ const summary = JSON.parse(fs.readFileSync(path.join(EVAL, "summary.json"), "utf8"));
13
+ const audit = JSON.parse(fs.readFileSync(path.join(EVAL, "leak_audit.json"), "utf8"));
14
+ const rowsHash = fs.readFileSync(path.join(EVAL, "rows.sha256"), "utf8").trim().split(/\s+/)[0];
15
+ const W = 9360;
16
+ const body = [];
17
+
18
+ function P(value, options = {}) {
19
+ body.push(new Paragraph({
20
+ children: [new TextRun({ text: value, bold: !!options.bold, size: options.size || 20 })],
21
+ spacing: { after: options.after === undefined ? 135 : options.after, line: 285 },
22
+ keepNext: !!options.keepNext,
23
+ }));
24
+ }
25
+ function H1(value) { body.push(new Paragraph({ heading: HeadingLevel.HEADING_1, text: value })); }
26
+ function H2(value) { body.push(new Paragraph({ heading: HeadingLevel.HEADING_2, text: value })); }
27
+ function Bullets(values) {
28
+ for (const value of values) body.push(new Paragraph({
29
+ text: "• " + value, indent: { left: 320, hanging: 200 },
30
+ spacing: { after: 65, line: 275 },
31
+ }));
32
+ }
33
+ function T(headers, rows, widths, fontSize = 16) {
34
+ const total = widths.reduce((a, b) => a + b, 0);
35
+ const col = widths.map(x => Math.round(W * x / total));
36
+ col[col.length - 1] += W - col.reduce((a, b) => a + b, 0);
37
+ const border = { style: BorderStyle.SINGLE, color: "D9D9D9", size: 4 };
38
+ const borders = { top: border, bottom: border, left: border, right: border };
39
+ const cell = (value, i, header) => new TableCell({
40
+ width: { size: col[i], type: WidthType.DXA }, borders,
41
+ shading: { fill: header ? "DCE6F1" : "FFFFFF" },
42
+ margins: { top: 90, bottom: 90, left: 95, right: 95 },
43
+ children: String(value).split("\n").map(line => new Paragraph({
44
+ children: [new TextRun({ text: line, bold: header, size: fontSize })],
45
+ alignment: i === 0 ? AlignmentType.LEFT : AlignmentType.CENTER,
46
+ spacing: { after: 0, line: 235 },
47
+ })),
48
+ });
49
+ body.push(new Table({
50
+ width: { size: W, type: WidthType.DXA }, columnWidths: col,
51
+ rows: [
52
+ new TableRow({ tableHeader: true, cantSplit: true, children: headers.map((h, i) => cell(h, i, true)) }),
53
+ ...rows.map(r => new TableRow({ cantSplit: true, children: r.map((v, i) => cell(v, i, false)) })),
54
+ ],
55
+ }));
56
+ P("", { after: 70 });
57
+ }
58
+ function pct(value, digits = 1) { return (100 * value).toFixed(digits) + "%"; }
59
+ function score(model, stage, task, expected) {
60
+ const value = summary.tasks[model + "|" + stage + "|" + task];
61
+ return value && value.rows === expected ? value : null;
62
+ }
63
+ function fmt(value) {
64
+ return value ? pct(value.debiased_accuracy) + " [" + pct(value.debiased_ci95[0]) + ", " + pct(value.debiased_ci95[1]) + "]" : "In progress";
65
+ }
66
+ function delta(value) {
67
+ return value ? (value.delta_pp >= 0 ? "+" : "") + value.delta_pp.toFixed(2) + " [" +
68
+ value.ci95_pp[0].toFixed(2) + ", " + value.ci95_pp[1].toFixed(2) + "] pp" : "In progress";
69
+ }
70
+
71
+ const tasks = [
72
+ ["GPQA Diamond", "gpqa_v2", 198, 4],
73
+ ["Chess legal", "chess_legal_v2", 500, 4],
74
+ ["GSM8K 4 choices", "gsm8k_mc4_v2", 1319, 4],
75
+ ["GSM8K 10 choices", "gsm8k_mc10_v2", 1319, 10],
76
+ ];
77
+ const COMPLETE = ["raw", "r7"].every(model => tasks.every(([, id, n]) =>
78
+ score(model, "full", id, n) && score(model, "options_only", id, n)));
79
+
80
+ P("OpenJEV E4B 1.0", { bold: true, size: 46, after: 60 });
81
+ P("Training a direct-decision readout on Gemma 4 E4B", { size: 31, after: 120 });
82
+ P("Experiment 1 method, evaluation, and results", { size: 26, after: 320 });
83
+ P("27 September 2026 • Public release 1.0");
84
+ P("Technical report accompanying the bambamdevs/openjev-e4b model release. OpenJEV is independent research and is not affiliated with TypeSafe AI or the unrelated AlexWortega/openjev project.");
85
+
86
+ H1("Abstract");
87
+ P("OpenJEV E4B converts google/gemma-4-E4B-it into a closed-choice decision system. Given state, a question, and options, it returns a probability distribution from a direct head without generating text. Experiment 1 trained a LoRA adapter and hybrid decision head, then used exact-oracle Arena data, promotion gates, and selective decoder-layer updates. This report describes the checkpoint selected for public release 1.0.");
88
+ P("The choice-panel evaluation freezes shuffled GPQA rows, GSM8K options drawn from other problems, and a Chess legal-move task. All options are evaluated in cyclic order rotations. The main score chooses the largest probability after averaging over rotations; an options-only condition and construction audit test whether choice strings reveal the answer. On untouched Gemma, rotation-averaged accuracy is 32.8% on GPQA, 43.8% on Chess legal, 43.7% on GSM8K-4, and 20.5% on GSM8K-10. The same-answer rate across orders ranges from 0.6% to 10.1%. " +
89
+ (COMPLETE ? "OpenJEV 1.0 reaches 53.8% on GSM8K-4 and 30.6% on GSM8K-10, gains of about 10.1 percentage points on each task. GPQA is unchanged at 32.8% and Chess legal is 42.6% versus Gemma's 43.8%. Its options-only GPQA score is 32.3%, limiting what the full GPQA result can show about use of the question. Paired comparisons follow." : "OpenJEV 1.0 scoring is in progress; this report shows only completed choice-panel results."));
90
+ P("Five standard public tasks use native option order. They give a separate comparison between the native Gemma LM-head readout and the OpenJEV decision head. The report records family exposure and precision differences and states what the evidence can support.");
91
+
92
+ H1("1 Research question and model");
93
+ P("The research question is whether a small open base model can serve as a typed decision component: choose among caller-provided options and expose probabilities that software can inspect. A request contains State, Question, Options, and a final Decision marker. The decision query uses the hidden state at the final marker; each option uses its final-token state and a span mean.");
94
+ P("The model extracts the text decoder from Gemma 4 E4B and attaches LoRA with rank 16, alpha 32, and zero dropout. Its hybrid head combines a normalized pointer score and a span-residual MLP, then applies softmax with released temperature 1.0. Gemma shares key/value states across layers 24–41, so the adapter and cumulative delta have no key/value projections in those layers. The public checkpoint consists of the pinned Gemma base, adapter, head, 65-tensor cumulative backbone delta, calibration, and packing code. Restoring only the adapter does not restore the model.");
95
+
96
+ H2("1.1 Model evolution and promotion");
97
+ P("Development began with a direct readout and LoRA proof of concept, expanded to a production training mixture and hard negatives, compared readout designs, and selected a proper-scoring hybrid head plus LoRA. The later Arena curriculum generated 24,000 exact-oracle examples across 20 decision families per internal training cycle and mixed them with replay. Early Arena training changed the head and LoRA; later cycles also trained selected decoder layers. Accepted base changes accumulate into the public checkpoint's 65 delta tensors across layers 37–41. Internal evaluation populations changed during development, so their percentages are not a controlled public comparison.");
98
+ P("A challenger could be promoted only when validation accuracy, schema accuracy, firewall accuracy, order consistency, and validation log loss stayed within recorded preservation margins. The promotion score weighted gains in Arena performance, order consistency, schema, firewall, validation accuracy, and validation NLL; acceptance required all checks and a score above 0.001. The selected public checkpoint's stored score is 0.024174. The gate record preserves the score and pass flags, but not every component value.");
99
+
100
+ H1("2 Evaluation protocol");
101
+ H2("2.1 Choice-panel tasks");
102
+ T(["Task", "Rows", "Options", "Row construction"], [
103
+ ["GPQA Diamond", "198", "4", "Source answers shuffled with a fixed per-row seed"],
104
+ ["GSM8K 4", "1,319", "4", "Gold plus distinct answers to other test problems of similar magnitude"],
105
+ ["GSM8K 10", "1,319", "10", "Same construction with nine distractors"],
106
+ ["Chess legal", "500", "4", "One legal plain-SAN move and three plain-SAN moves from other positions, illegal here"],
107
+ ], [1.4, 0.7, 1.15, 6.15]);
108
+ P("Frozen rows are published as eval/choice_panel/rows.jsonl (SHA-256 " + rowsHash + "). GPQA comes from the 198-row Diamond mirror, GSM8K from the main test split, and Chess legal from seeded synthetic positions. Chess asks which move is legal in the position, not which is best. The GSM8K tasks are multiple-choice arithmetic, not open-ended solution generation.");
109
+ P("Every full-condition row is presented in all cyclic option rotations: four for GPQA, Chess legal, and GSM8K-4; ten for GSM8K-10. Probabilities are mapped to original option identities before averaging. The main accuracy applies argmax to that per-row mean. Mean per-order accuracy averages individual presentations. Same-answer rate measures whether the predicted option identity is unchanged in every rotation. Options-only replaces state and question with neutral text, using four rotations for four choices and five evenly spaced rotations for ten choices.");
110
+ P("The construction gate checks gold-position balance, fixed choice-string heuristics, and a cross-validated logistic scorer on choice-only features. The maximum heuristic score stays within its specified 95% chance band on all four tasks. That gate tests row construction; model options-only scores are reported separately.");
111
+ T(["Task", "Chance", "Gold-position range", "Highest construction heuristic", "Gate"],
112
+ tasks.map(([name, id, , k]) => {
113
+ const x = audit[id];
114
+ return [name, pct(1 / k), pct(Math.min(...x.gold_position_share)) + "–" + pct(Math.max(...x.gold_position_share)), pct(x.max_heuristic), x.leak_gate];
115
+ }), [2.1, 1, 2, 2.2, 0.9]);
116
+
117
+ H2("2.2 Systems and uncertainty");
118
+ P("Untouched Gemma uses native LM-head answer-letter scoring in NF4. OpenJEV 1.0 uses the frozen base in NF4, delta-touched decoder modules restored in dense FP16, its LoRA adapter, and an FP32 decision head. These are complete inference systems with different readouts. Wilson 95% intervals use rows as the unit of observation. Paired differences use identical rows, row bootstrap intervals, and exact McNemar tests; rotations of one row are not independent observations. Evaluation ran on a Windows RTX 3060 with local cached weights.");
119
+
120
+ H1("3 Choice-panel results");
121
+ T(["Task", "Gemma base [95% CI]", "OpenJEV 1.0 [95% CI]", "Gemma per order / same", "OpenJEV per order / same"],
122
+ tasks.map(([name, id, n]) => {
123
+ const raw = score("raw", "full", id, n), model = score("r7", "full", id, n);
124
+ const order = x => x ? pct(x.rotation_mean_accuracy) + " / " + pct(x.order_consistency) : "In progress";
125
+ return [name, fmt(raw), fmt(model), order(raw), order(model)];
126
+ }), [1.6, 2.3, 2.3, 1.9, 1.9], 15);
127
+ P("The main score uses one averaged probability vector per row. The per-order and same-answer columns show option-order sensitivity. Same-row paired differences appear in Section 3.2 when both systems are complete.");
128
+
129
+ H2("3.1 Options-only control");
130
+ T(["Task", "Chance", "Gemma base [95% CI]", "OpenJEV 1.0 [95% CI]"],
131
+ tasks.map(([name, id, n, k]) => [name, pct(1 / k), fmt(score("raw", "options_only", id, n)), fmt(score("r7", "options_only", id, n))]),
132
+ [2.1, 1, 2.7, 2.7], 15);
133
+ P("Gemma's options-only scores are close to chance on GPQA and the two GSM8K tasks; Chess legal is 29.0%. OpenJEV 1.0 scores 32.3% on GPQA and 30.8% on Chess legal with only options, both above 25% chance. Its full GPQA score is 32.8%, so this result alone does not establish use of the question. OpenJEV's GSM8K options-only scores are near chance. The construction audit passes its gate, but model behavior reveals additional choice-only signal. This control does not explain individual full-condition decisions.");
134
+ if (COMPLETE) {
135
+ H2("3.2 Paired comparisons");
136
+ T(["Task", "OpenJEV 1.0 − Gemma [95% CI]", "McNemar p"], tasks.map(([name, id]) => {
137
+ const pair = (summary.pairs || {})["raw->r7|full|" + id];
138
+ return [name, delta(pair), pair ? pair.mcnemar_p.toPrecision(2) : "In progress"];
139
+ }), [2.1, 5.1, 1.1], 15);
140
+ P("Intervals are paired row-bootstrap intervals with 2,000 resamples. Exact McNemar tests use discordant row-level correct/incorrect outcomes after rotation averaging. The comparison measures complete decision systems.");
141
+ }
142
+
143
+ H1("4 Standard public tasks");
144
+ P("ARC-Easy, ARC-Challenge, WinoGrande, HellaSwag, and MMLU use the public-task runner and native option order. The table compares identical rows under that protocol. ARC, HellaSwag, and MMLU have training-family exposure. These scores should not be pooled with the rotation-averaged choice-panel scores as though they shared one protocol.");
145
+ T(["Task", "Gemma NF4", "OpenJEV 1.0", "Difference [95% CI]"], [
146
+ ["ARC-Easy", "95.58%", "95.62%", "+0.04 [-0.80, +0.84] pp"],
147
+ ["ARC-Challenge", "87.37%", "87.88%", "+0.51 [-1.37, +2.39] pp"],
148
+ ["WinoGrande", "60.14%", "66.46%", "+6.31 [+3.79, +9.00] pp"],
149
+ ["HellaSwag", "76.51%", "89.77%", "+13.26 [+12.40, +14.06] pp"],
150
+ ["MMLU", "66.19%", "64.41%", "-1.77 [-2.58, -1.01] pp"],
151
+ ], [2, 1.5, 1.7, 4]);
152
+ P("Under native order, OpenJEV and Gemma are similar on ARC-Easy and ARC-Challenge; OpenJEV is higher on WinoGrande and HellaSwag and lower on MMLU. HellaSwag and ARC have same-family training exposure. OpenJEV's WinoGrande NLL is 0.873, above the uniform binary reference ln 2 = 0.693; the released temperature is 1.0, and calibration does not transfer uniformly across tasks.");
153
+
154
+ H1("5 Interpretation and next controls");
155
+ P("Experiment 1 produces a working direct-decision model and an inspectable release record. The choice panel is the evidence for GPQA, closed-choice GSM8K, and Chess legal. Standard tasks provide separate end-to-end comparisons under native order. Current comparisons cannot attribute differences to the head, adapter, backbone updates, or inference precision separately.");
156
+ Bullets([
157
+ ...(!COMPLETE ? ["Complete the OpenJEV 1.0 choice-panel run and analyze paired rows, options-only performance, and order consistency."] : []),
158
+ "Score a frozen Gemma backbone with a trained decision head to separate readout effects from adapter and backbone changes.",
159
+ "Match inference precision across controlled checkpoints before attributing a difference to selective backbone training.",
160
+ "Use an open-ended GSM8K reference to characterize the cost of a closed-choice interface on arithmetic generation.",
161
+ "Reserve a knowledge panel unused for model selection before making confirmatory preservation claims.",
162
+ ]);
163
+
164
+ H1("Appendix A Release and reproduction record");
165
+ P("The base is google/gemma-4-E4B-it at revision ee0ef6023621cff504d758262d4e04895a5af4a2. The public 1.0 release maps to internal checkpoint v1.6 R7, with stored Arena score 0.024174. It has 65 cumulative delta tensors in decoder layers 37–41 and global temperature 1.0. The repository includes the adapter, head, delta, loader, calibration, row-level evaluation artifacts, and a SHA-256 manifest. Training code is not included.");
166
+ P("The choice-panel row source and scoring scripts are in eval/choice_panel. The standard-task harness is in eval/harness. Source-checkpoint and release-file hashes are in RELEASE_METADATA.json and MANIFEST.sha256. Training-family overlap notes are in docs/BENCHMARK_EXPOSURE.md. The restoration and scoring contract is in REPRODUCIBILITY.md.");
167
+ P("Valid restoration requires the pinned base, adapter, head, cumulative delta, calibration, and packer. NF4 evaluation runs the frozen base in 4-bit with FP16 compute and touched decoder modules in dense FP16. Dense BF16 inference is supported by the loader but has not been publicly benchmarked.");
168
+
169
+ H1("References");
170
+ P("Google. Gemma 4 E4B-it model card. https://huggingface.co/google/gemma-4-E4B-it");
171
+ P("Rein et al. GPQA: A Graduate-Level Google-Proof Q&A Benchmark. arXiv:2311.12022, 2023.");
172
+ P("Cobbe et al. Training Verifiers to Solve Math Word Problems. arXiv:2110.14168, 2021.");
173
+ P("McNemar. Note on the sampling error of the difference between correlated proportions or percentages. Psychometrika 12, 1947.");
174
+ P("Guo et al. On Calibration of Modern Neural Networks. ICML 2017.");
175
+
176
+ const doc = new Document({
177
+ title: "OpenJEV E4B 1.0 Experiment 1 method, evaluation, and results",
178
+ creator: "OpenJEV E4B",
179
+ description: "OpenJEV E4B 1.0 Experiment 1 technical report",
180
+ styles: {
181
+ default: { document: { run: { font: "Calibri", size: 20, color: "000000" } } },
182
+ paragraphStyles: [
183
+ { id: "Heading1", name: "Heading 1", basedOn: "Normal", next: "Normal", quickFormat: true,
184
+ run: { font: "Calibri", size: 29, bold: true, color: "000000" },
185
+ paragraph: { spacing: { before: 300, after: 120 }, outlineLevel: 0 } },
186
+ { id: "Heading2", name: "Heading 2", basedOn: "Normal", next: "Normal", quickFormat: true,
187
+ run: { font: "Calibri", size: 24, bold: true, color: "000000" },
188
+ paragraph: { spacing: { before: 220, after: 90 }, outlineLevel: 1 } },
189
+ ],
190
+ },
191
+ sections: [{
192
+ properties: { page: { size: { width: 12240, height: 15840 },
193
+ margin: { top: 1260, right: 1440, bottom: 1260, left: 1440 } } },
194
+ footers: { default: new Footer({ children: [new Paragraph({
195
+ alignment: AlignmentType.CENTER,
196
+ children: [new TextRun({ text: "OpenJEV E4B • Experiment 1 • 1.0 • ", size: 16, color: "666666" }),
197
+ new TextRun({ children: [PageNumber.CURRENT], size: 16, color: "666666" })],
198
+ })] }) },
199
+ children: body,
200
+ }],
201
+ });
202
+
203
+ Packer.toBuffer(doc).then(buffer => {
204
+ fs.mkdirSync(path.dirname(path.resolve(OUT)), { recursive: true });
205
+ fs.writeFileSync(OUT, buffer);
206
+ console.log("Wrote " + OUT + " (" + buffer.length + " bytes)");
207
+ });
eval/analysis/contamination_audit.json ADDED
@@ -0,0 +1,70 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ {
2
+ "method": "Row-level text-overlap audit between training source splits and public panel rows.",
3
+ "sources": {
4
+ "mmlu_auxiliary_train": 98370,
5
+ "arc_train": 3361,
6
+ "hellaswag_train": 39799
7
+ },
8
+ "overlap": {
9
+ "mmlu": {
10
+ "n": 14042,
11
+ "mmlu_auxiliary_train": {
12
+ "question": 19,
13
+ "question_and_options": 0
14
+ },
15
+ "arc_train": {
16
+ "question": 1,
17
+ "question_and_options": 0
18
+ },
19
+ "hellaswag_train": {
20
+ "question": 0,
21
+ "question_and_options": 0
22
+ }
23
+ },
24
+ "arc_easy": {
25
+ "n": 2376,
26
+ "mmlu_auxiliary_train": {
27
+ "question": 8,
28
+ "question_and_options": 3
29
+ },
30
+ "arc_train": {
31
+ "question": 7,
32
+ "question_and_options": 3
33
+ },
34
+ "hellaswag_train": {
35
+ "question": 0,
36
+ "question_and_options": 0
37
+ }
38
+ },
39
+ "arc_challenge": {
40
+ "n": 1172,
41
+ "mmlu_auxiliary_train": {
42
+ "question": 9,
43
+ "question_and_options": 0
44
+ },
45
+ "arc_train": {
46
+ "question": 9,
47
+ "question_and_options": 0
48
+ },
49
+ "hellaswag_train": {
50
+ "question": 0,
51
+ "question_and_options": 0
52
+ }
53
+ },
54
+ "hellaswag": {
55
+ "n": 10042,
56
+ "mmlu_auxiliary_train": {
57
+ "question": 0,
58
+ "question_and_options": 0
59
+ },
60
+ "arc_train": {
61
+ "question": 0,
62
+ "question_and_options": 0
63
+ },
64
+ "hellaswag_train": {
65
+ "question": 23,
66
+ "question_and_options": 0
67
+ }
68
+ }
69
+ }
70
+ }
eval/analysis/contamination_audit.py ADDED
@@ -0,0 +1,84 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ """Row-level text-overlap audit between training source splits and public panel rows.
2
+
3
+ Training sources are the full upstream splits the compilers sample from
4
+ (src/compile_datasets.py): cais/mmlu auxiliary_train, allenai/ai2_arc train,
5
+ Rowan/hellaswag train. Using the full split is an upper bound on the sampled
6
+ training rows. Public rows are regenerated with eval/harness/benchmark_public.py.
7
+
8
+ A row matches when its normalized question text (lowercase, alphanumerics
9
+ only, collapsed whitespace) hashes identically. "question+options" additionally
10
+ requires the same normalized option multiset.
11
+ """
12
+
13
+ from __future__ import annotations
14
+
15
+ import hashlib
16
+ import json
17
+ import re
18
+ import sys
19
+ from pathlib import Path
20
+
21
+ from datasets import load_dataset
22
+
23
+ HARNESS = Path(__file__).resolve().parents[1] / "harness"
24
+ sys.path.insert(0, str(HARNESS))
25
+
26
+ import benchmark_public as bp # noqa: E402
27
+
28
+
29
+ def norm(text: str) -> str:
30
+ return " ".join(re.sub(r"[^a-z0-9]+", " ", str(text).lower()).split())
31
+
32
+
33
+ def h(text: str) -> str:
34
+ return hashlib.sha256(norm(text).encode()).hexdigest()
35
+
36
+
37
+ def hq(question: str, options) -> str:
38
+ return h(question) + ":" + h("|".join(sorted(norm(o) for o in options)))
39
+
40
+
41
+ def train_sources() -> dict[str, tuple[set[str], set[str]]]:
42
+ out = {}
43
+ aux = load_dataset("cais/mmlu", "all", split="auxiliary_train")
44
+ out["mmlu_auxiliary_train"] = (
45
+ {h(x["question"]) for x in aux},
46
+ {hq(x["question"], x["choices"]) for x in aux},
47
+ )
48
+ q, qo = set(), set()
49
+ for cfg in ("ARC-Challenge", "ARC-Easy"):
50
+ for x in load_dataset("allenai/ai2_arc", cfg, split="train"):
51
+ q.add(h(x["question"]))
52
+ qo.add(hq(x["question"], x["choices"]["text"]))
53
+ out["arc_train"] = (q, qo)
54
+ hs = load_dataset("Rowan/hellaswag", split="train")
55
+ out["hellaswag_train"] = (
56
+ {h(x["ctx"]) for x in hs},
57
+ {hq(x["ctx"], x["endings"]) for x in hs},
58
+ )
59
+ return out
60
+
61
+
62
+ def main() -> None:
63
+ sources = train_sources()
64
+ panel = {
65
+ "mmlu": bp.load_mmlu(),
66
+ "arc_easy": bp.load_arc("ARC-Easy"),
67
+ "arc_challenge": bp.load_arc("ARC-Challenge"),
68
+ "hellaswag": bp.load_hellaswag(),
69
+ }
70
+ report = {"method": __doc__.strip().splitlines()[0], "sources": {k: len(v[0]) for k, v in sources.items()}, "overlap": {}}
71
+ for task, items in panel.items():
72
+ report["overlap"][task] = {"n": len(items)}
73
+ for src, (qset, qoset) in sources.items():
74
+ # HellaSwag panel rows carry the context in the question field.
75
+ q_hits = sum(h(it["question"]) in qset or h(it["state"]) in qset for it in items)
76
+ qo_hits = sum(hq(it["question"], it["options"]) in qoset or hq(it["state"], it["options"]) in qoset for it in items)
77
+ report["overlap"][task][src] = {"question": q_hits, "question_and_options": qo_hits}
78
+ print(json.dumps(report, indent=2))
79
+ if len(sys.argv) > 1:
80
+ Path(sys.argv[1]).write_text(json.dumps(report, indent=2), encoding="utf-8")
81
+
82
+
83
+ if __name__ == "__main__":
84
+ main()
eval/analysis/paired_analysis.py ADDED
@@ -0,0 +1,165 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ """Row-level paired analysis for public-benchmark runs.
2
+
3
+ Usage:
4
+ python scripts/paired_analysis.py OUT_DIR NAME=details.jsonl [NAME=details.jsonl ...]
5
+
6
+ For every run and task: accuracy with a Wilson 95% CI, an exact binomial test
7
+ against chance, NLL against the ln(k) uniform reference, ECE (15 equal-width
8
+ bins, as in eval/harness) with a percentile-bootstrap 95% CI, and the
9
+ distribution of predicted positions next to the gold distribution.
10
+
11
+ For every pair of runs: the paired accuracy delta with a paired-bootstrap 95%
12
+ CI, the exact McNemar test, and the 2x2 flip matrix.
13
+ """
14
+
15
+ from __future__ import annotations
16
+
17
+ import itertools
18
+ import json
19
+ import math
20
+ import sys
21
+ from collections import Counter, defaultdict
22
+ from pathlib import Path
23
+
24
+ import numpy as np
25
+ from scipy.stats import binomtest
26
+
27
+ BOOTSTRAP = 2000
28
+ BINS = 15
29
+ SEED = 0
30
+
31
+
32
+ def wilson(k: int, n: int, z: float = 1.959964) -> tuple[float, float]:
33
+ p = k / n
34
+ d = 1 + z * z / n
35
+ c = (p + z * z / (2 * n)) / d
36
+ h = z * math.sqrt(p * (1 - p) / n + z * z / (4 * n * n)) / d
37
+ return c - h, c + h
38
+
39
+
40
+ def ece(conf: np.ndarray, correct: np.ndarray) -> float:
41
+ total = 0.0
42
+ for b in range(BINS):
43
+ lo, hi = b / BINS, (b + 1) / BINS
44
+ m = (conf >= lo) & ((conf < hi) if b < BINS - 1 else (conf <= hi))
45
+ if m.any():
46
+ total += m.mean() * abs(conf[m].mean() - correct[m].mean())
47
+ return float(total)
48
+
49
+
50
+ def mcnemar_exact(b: int, c: int) -> float:
51
+ n = b + c
52
+ return 1.0 if n == 0 else float(binomtest(min(b, c), n, 0.5).pvalue)
53
+
54
+
55
+ def load(path: Path) -> dict[str, dict[int, dict]]:
56
+ by: dict[str, dict[int, dict]] = defaultdict(dict)
57
+ with path.open(encoding="utf-8") as fp:
58
+ for line in fp:
59
+ r = json.loads(line)
60
+ by[r["task"]][int(r["index"])] = r
61
+ return by
62
+
63
+
64
+ def run_stats(rows: list[dict], rng: np.random.Generator) -> dict:
65
+ n = len(rows)
66
+ k = int(rows[0]["option_count"])
67
+ correct = np.array([bool(r["correct"]) for r in rows], dtype=float)
68
+ conf = np.array([float(r["confidence"]) for r in rows])
69
+ nll = np.array([-math.log(max(float(r["p_gold"]), 1e-12)) for r in rows])
70
+ hits = int(correct.sum())
71
+ lo, hi = wilson(hits, n)
72
+ idx = rng.integers(0, n, size=(BOOTSTRAP, n))
73
+ eces = [ece(conf[i], correct[i]) for i in idx]
74
+ gold = Counter(int(r["gold"]) for r in rows)
75
+ pred = Counter(int(r["pred"]) for r in rows)
76
+ return {
77
+ "n": n,
78
+ "k": k,
79
+ "accuracy": hits / n,
80
+ "accuracy_ci95": [lo, hi],
81
+ "chance": 1 / k,
82
+ "binom_p_vs_chance_two_sided": float(binomtest(hits, n, 1 / k).pvalue),
83
+ "nll": float(nll.mean()),
84
+ "ln_k": math.log(k),
85
+ "nll_worse_than_uniform": bool(nll.mean() > math.log(k)),
86
+ "ece_15bin": ece(conf, correct),
87
+ "ece_15bin_ci95": [float(np.percentile(eces, 2.5)), float(np.percentile(eces, 97.5))],
88
+ "gold_position_share": [gold[i] / n for i in range(k)],
89
+ "pred_position_share": [pred[i] / n for i in range(k)],
90
+ "max_pred_position_share": max(pred[i] / n for i in range(k)),
91
+ }
92
+
93
+
94
+ def pair_stats(a: list[dict], b: list[dict], rng: np.random.Generator) -> dict:
95
+ ca = np.array([bool(r["correct"]) for r in a], dtype=float)
96
+ cb = np.array([bool(r["correct"]) for r in b], dtype=float)
97
+ n = len(ca)
98
+ diff = cb - ca
99
+ idx = rng.integers(0, n, size=(BOOTSTRAP, n))
100
+ boots = diff[idx].mean(axis=1)
101
+ both = int(((ca == 1) & (cb == 1)).sum())
102
+ a_only = int(((ca == 1) & (cb == 0)).sum())
103
+ b_only = int(((ca == 0) & (cb == 1)).sum())
104
+ neither = int(((ca == 0) & (cb == 0)).sum())
105
+ return {
106
+ "n": n,
107
+ "delta_pp": 100 * float(diff.mean()),
108
+ "delta_pp_ci95": [100 * float(np.percentile(boots, 2.5)), 100 * float(np.percentile(boots, 97.5))],
109
+ "flip": {"both": both, "a_only": a_only, "b_only": b_only, "neither": neither},
110
+ "net_rows": b_only - a_only,
111
+ "mcnemar_exact_p": mcnemar_exact(a_only, b_only),
112
+ }
113
+
114
+
115
+ def main() -> None:
116
+ out_dir = Path(sys.argv[1])
117
+ runs = {}
118
+ for arg in sys.argv[2:]:
119
+ name, path = arg.split("=", 1)
120
+ runs[name] = load(Path(path))
121
+ out_dir.mkdir(parents=True, exist_ok=True)
122
+ rng = np.random.default_rng(SEED)
123
+
124
+ tasks = sorted(set.intersection(*(set(r) for r in runs.values())), key=list(next(iter(runs.values()))).index)
125
+ report = {"bootstrap": BOOTSTRAP, "ece_bins": BINS, "seed": SEED, "runs": {}, "pairs": {}}
126
+ for name, by in runs.items():
127
+ report["runs"][name] = {t: run_stats([by[t][i] for i in sorted(by[t])], rng) for t in tasks}
128
+ for a, b in itertools.combinations(runs, 2):
129
+ key = f"{a}->{b}"
130
+ report["pairs"][key] = {}
131
+ for t in tasks:
132
+ ids = sorted(set(runs[a][t]) & set(runs[b][t]))
133
+ for i in ids:
134
+ if runs[a][t][i]["gold"] != runs[b][t][i]["gold"]:
135
+ raise SystemExit(f"gold mismatch {t}:{i} between {a} and {b}")
136
+ report["pairs"][key][t] = pair_stats([runs[a][t][i] for i in ids], [runs[b][t][i] for i in ids], rng)
137
+
138
+ (out_dir / "paired_analysis.json").write_text(json.dumps(report, indent=2), encoding="utf-8")
139
+
140
+ lines = ["# Paired analysis", "", f"Bootstrap resamples: {BOOTSTRAP}; ECE: {BINS} equal-width bins; seed {SEED}.", ""]
141
+ for name, stats in report["runs"].items():
142
+ lines += [f"## {name}", "", "| Task | n | k | Acc | 95% CI | p vs chance | NLL | ln k | ECE (95% CI) | max pred-position share |",
143
+ "|---|---:|---:|---:|---|---:|---:|---:|---|---:|"]
144
+ for t, s in stats.items():
145
+ lines.append(
146
+ f"| {t} | {s['n']} | {s['k']} | {100*s['accuracy']:.2f}% | [{100*s['accuracy_ci95'][0]:.1f}, {100*s['accuracy_ci95'][1]:.1f}] "
147
+ f"| {s['binom_p_vs_chance_two_sided']:.2g} | {s['nll']:.3f}{' (>ln k)' if s['nll_worse_than_uniform'] else ''} | {s['ln_k']:.3f} "
148
+ f"| {s['ece_15bin']:.3f} [{s['ece_15bin_ci95'][0]:.3f}, {s['ece_15bin_ci95'][1]:.3f}] | {100*s['max_pred_position_share']:.0f}% |"
149
+ )
150
+ lines.append("")
151
+ for key, stats in report["pairs"].items():
152
+ a, b = key.split("->")
153
+ lines += [f"## {a} -> {b}", "", f"| Task | Δ pp | 95% CI | {a} only | {b} only | net rows | McNemar p |", "|---|---:|---|---:|---:|---:|---:|"]
154
+ for t, s in stats.items():
155
+ lines.append(
156
+ f"| {t} | {s['delta_pp']:+.2f} | [{s['delta_pp_ci95'][0]:+.2f}, {s['delta_pp_ci95'][1]:+.2f}] "
157
+ f"| {s['flip']['a_only']} | {s['flip']['b_only']} | {s['net_rows']:+d} | {s['mcnemar_exact_p']:.2g} |"
158
+ )
159
+ lines.append("")
160
+ (out_dir / "paired_analysis.md").write_text("\n".join(lines), encoding="utf-8")
161
+ print("\n".join(lines))
162
+
163
+
164
+ if __name__ == "__main__":
165
+ main()
eval/choice_panel/README.md ADDED
@@ -0,0 +1,11 @@
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # Choice-panel evaluation
2
+
3
+ `rows.jsonl` contains the frozen GPQA Diamond, GSM8K-4, GSM8K-10, and Chess legal rows. `rows.sha256` hashes this published four-task subset. `source_run.json` identifies the source run and larger local row-file hash. `leak_audit.json` records answer-position and choice-string checks. All four tasks pass the specified chance-band gate.
4
+
5
+ `details_raw.jsonl` contains untouched Gemma output. `details_r7.jsonl` is the OpenJEV 1.0 output under its internal checkpoint identifier. A detail file is included only when all four full-condition and options-only tasks finish. `summary.json` and `summary.md` give rotation-averaged accuracy, row-level Wilson intervals, per-order accuracy, order consistency, and paired comparisons when both systems are complete. The internal identifier maps to public release 1.0 in `RELEASE_METADATA.json`.
6
+
7
+ The full condition rotates all options: four presentations per row on GPQA, Chess legal and GSM8K-4, ten on GSM8K-10. The options-only condition removes state and question and uses four or five rotations. Probabilities are mapped back to original option identities before the per-row mean is taken. The main score is the argmax of that mean; every statistical observation is a row.
8
+
9
+ `run_choice_panel.py` is the internal scoring driver and `summarize.py` regenerates the summary. The driver expects the local OpenJEV benchmark bundle and untouched-Gemma benchmark bundle. Set `OPENJEV_BENCH_ROOT` and `RAW_GEMMA_BENCH_ROOT` to those directories, then run it with the same Python environment and cached Gemma weights. Released model files and checkpoint hashes are in `model/` and `RELEASE_METADATA.json`.
10
+
11
+ The row generator used GPQA's Diamond mirror, GSM8K's main test split, and python-chess. GPQA answer positions were shuffled with fixed row seeds. GSM8K distractors were sampled from the final answers of other test questions in matching magnitude buckets. Chess legal choices consist of one legal plain-SAN move and three plain-SAN strings drawn from other same-side positions that are illegal in the current position. The frozen rows and their hash are the evaluation identity.
eval/choice_panel/details_r7.jsonl ADDED
@@ -0,0 +1,3 @@
 
 
 
 
1
+ version https://git-lfs.github.com/spec/v1
2
+ oid sha256:e905f991f9d5685ad851207bb0430b9f40074b84066bc67439c559f3ec7b8027
3
+ size 11162773
eval/choice_panel/details_raw.jsonl ADDED
@@ -0,0 +1,3 @@
 
 
 
 
1
+ version https://git-lfs.github.com/spec/v1
2
+ oid sha256:8bf0ec06599e55e743fcbdaca736ff211819bc63e03af765ae62e67c26e6605a
3
+ size 11187992
eval/choice_panel/leak_audit.json ADDED
@@ -0,0 +1,87 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ {
2
+ "gpqa_v2": {
3
+ "n": 198,
4
+ "k": 4,
5
+ "chance": 0.25,
6
+ "chance_upper95": 0.310314830579873,
7
+ "gold_position_share": [
8
+ 0.2777777777777778,
9
+ 0.22727272727272727,
10
+ 0.23737373737373738,
11
+ 0.25757575757575757
12
+ ],
13
+ "heuristics": {
14
+ "first_slot": 0.2777777777777778
15
+ },
16
+ "max_heuristic": 0.2777777777777778,
17
+ "leak_gate": "PASS"
18
+ },
19
+ "chess_legal_v2": {
20
+ "n": 500,
21
+ "k": 4,
22
+ "chance": 0.25,
23
+ "chance_upper95": 0.2879552367928327,
24
+ "gold_position_share": [
25
+ 0.266,
26
+ 0.226,
27
+ 0.222,
28
+ 0.286
29
+ ],
30
+ "heuristics": {
31
+ "san_shape": 0.25,
32
+ "cv_logistic_features": 0.204
33
+ },
34
+ "max_heuristic": 0.25,
35
+ "leak_gate": "PASS"
36
+ },
37
+ "gsm8k_mc4_v2": {
38
+ "n": 1319,
39
+ "k": 4,
40
+ "chance": 0.25,
41
+ "chance_upper95": 0.27336868688896165,
42
+ "gold_position_share": [
43
+ 0.2539802880970432,
44
+ 0.2486732373009856,
45
+ 0.25928733889310085,
46
+ 0.23805913570887036
47
+ ],
48
+ "heuristics": {
49
+ "generator_explains": 0.25037907505686113,
50
+ "pick_max": 0.23881728582259287,
51
+ "pick_min": 0.26004548900682334,
52
+ "pick_median": 0.2505686125852919,
53
+ "pick_roundest": 0.25518069244377045,
54
+ "cv_logistic_features": 0.24184988627748294
55
+ },
56
+ "max_heuristic": 0.26004548900682334,
57
+ "leak_gate": "PASS"
58
+ },
59
+ "gsm8k_mc10_v2": {
60
+ "n": 1319,
61
+ "k": 10,
62
+ "chance": 0.1,
63
+ "chance_upper95": 0.11619030119914012,
64
+ "gold_position_share": [
65
+ 0.10614101592115238,
66
+ 0.09249431387414708,
67
+ 0.09628506444275967,
68
+ 0.10007581501137225,
69
+ 0.10538286580742987,
70
+ 0.10159211523881728,
71
+ 0.09552691432903715,
72
+ 0.09931766489764973,
73
+ 0.10462471569370735,
74
+ 0.09855951478392722
75
+ ],
76
+ "heuristics": {
77
+ "generator_explains": 0.10595779631033612,
78
+ "pick_max": 0.07505686125852919,
79
+ "pick_min": 0.09855951478392722,
80
+ "pick_median": 0.09969673995451099,
81
+ "pick_roundest": 0.0998221957471385,
82
+ "cv_logistic_features": 0.10159211523881728
83
+ },
84
+ "max_heuristic": 0.10595779631033612,
85
+ "leak_gate": "PASS"
86
+ }
87
+ }
eval/choice_panel/rows.jsonl ADDED
@@ -0,0 +1,3 @@
 
 
 
 
1
+ version https://git-lfs.github.com/spec/v1
2
+ oid sha256:a8bbb72147f51b0144f4177ed9e60a9223ef9c5979694423d93348b064bc8bf4
3
+ size 1511969
eval/choice_panel/rows.sha256 ADDED
@@ -0,0 +1 @@
 
 
1
+ a8bbb72147f51b0144f4177ed9e60a9223ef9c5979694423d93348b064bc8bf4 rows.jsonl
eval/choice_panel/run_choice_panel.py ADDED
@@ -0,0 +1,159 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ """Score one model on the frozen choice-panel rows, in every option rotation.
2
+
3
+ Stages
4
+ full tasks with state + question, all k cyclic rotations
5
+ options_only tasks with state and question removed
6
+ (all rotations for k <= 4, five evenly spaced for k = 10)
7
+
8
+ Models (all local, NF4 on this machine's GPU; nothing is downloaded or copied
9
+ from another host):
10
+ raw untouched google/gemma-4-E4B-it, native LM-head answer-letter scoring
11
+ r7 OpenJEV 1.0 checkpoint (local copy; r7 is its internal identifier)
12
+
13
+ Output: <out>/details_<model>.jsonl, one line per (stage, task, row, rotation),
14
+ with probabilities mapped back to the original option order. Resumable.
15
+ """
16
+
17
+ from __future__ import annotations
18
+
19
+ import argparse
20
+ import gc
21
+ import hashlib
22
+ import importlib.util
23
+ import json
24
+ import os
25
+ import sys
26
+ import time
27
+ from pathlib import Path
28
+
29
+ HERE = Path(__file__).resolve().parent
30
+ OPENJEV_BENCH = Path(os.environ.get("OPENJEV_BENCH_ROOT", "__set_OPENJEV_BENCH_ROOT__"))
31
+ RAW_BENCH = Path(os.environ.get("RAW_GEMMA_BENCH_ROOT", "__set_RAW_GEMMA_BENCH_ROOT__"))
32
+ CHECKPOINTS = {"r7": OPENJEV_BENCH / "checkpoints" / "round-07"}
33
+
34
+ FULL_TASKS = ["gpqa_v2", "chess_legal_v2", "gsm8k_mc4_v2", "gsm8k_mc10_v2"]
35
+ OPTIONS_ONLY_TASKS = ["gsm8k_mc4_v2", "gsm8k_mc10_v2", "chess_legal_v2", "gpqa_v2"]
36
+ NO_STATE = "(no context provided)"
37
+ NO_QUESTION = "Choose the correct option."
38
+
39
+
40
+ def _module(name, path):
41
+ spec = importlib.util.spec_from_file_location(name, path)
42
+ mod = importlib.util.module_from_spec(spec)
43
+ spec.loader.exec_module(mod)
44
+ return mod
45
+
46
+
47
+ def load_decider(model):
48
+ if model == "raw":
49
+ if not RAW_BENCH.is_dir():
50
+ raise SystemExit("Set RAW_GEMMA_BENCH_ROOT to the local untouched-Gemma benchmark bundle")
51
+ mod = _module("raw_bench", RAW_BENCH / "scripts" / "benchmark_raw_gemma.py")
52
+ return mod.load_raw_gemma_decider("google/gemma-4-E4B-it", local_files_only=True)
53
+ if not OPENJEV_BENCH.is_dir():
54
+ raise SystemExit("Set OPENJEV_BENCH_ROOT to the local OpenJEV benchmark bundle")
55
+ sys.path.insert(0, str(OPENJEV_BENCH)) # for the bench's src/ package
56
+ mod = _module("openjev_bench", OPENJEV_BENCH / "scripts" / "benchmark_public.py")
57
+ return mod.load_v13_decider(CHECKPOINTS[model])
58
+
59
+
60
+ def rotations(k, stage):
61
+ if stage == "full" or k <= 4:
62
+ return list(range(k))
63
+ return list(range(0, k, max(1, k // 5)))[:5]
64
+
65
+
66
+ def load_rows():
67
+ body = (HERE / "rows" / "rows.jsonl").read_bytes()
68
+ expected = (HERE / "rows" / "rows.sha256").read_text().split()[0]
69
+ if hashlib.sha256(body).hexdigest() != expected:
70
+ raise SystemExit("rows.jsonl does not match rows.sha256; rebuild with build_rows.py")
71
+ by_task = {}
72
+ for line in body.decode("utf-8").split("\n"):
73
+ if not line:
74
+ continue
75
+ r = json.loads(line)
76
+ by_task.setdefault(r["task"], []).append(r)
77
+ return by_task, expected
78
+
79
+
80
+ def main():
81
+ ap = argparse.ArgumentParser()
82
+ ap.add_argument("--model", required=True, choices=["raw", "r7"])
83
+ ap.add_argument("--out", required=True)
84
+ ap.add_argument("--stages", default="full,options_only")
85
+ ap.add_argument("--limit", type=int, default=None, help="rows per task (smoke test)")
86
+ args = ap.parse_args()
87
+
88
+ by_task, rows_sha = load_rows()
89
+ out = Path(args.out)
90
+ out.mkdir(parents=True, exist_ok=True)
91
+ details = out / f"details_{args.model}.jsonl"
92
+ done = set()
93
+ if details.exists():
94
+ for line in details.read_text(encoding="utf-8").split("\n"):
95
+ if line.strip():
96
+ d = json.loads(line)
97
+ done.add((d["stage"], d["task"], d["row"], d["rotation"]))
98
+
99
+ plan = []
100
+ for stage in args.stages.split(","):
101
+ for task in (FULL_TASKS if stage == "full" else OPTIONS_ONLY_TASKS):
102
+ rows = by_task[task][: args.limit] if args.limit else by_task[task]
103
+ for r in rows:
104
+ for rot in rotations(len(r["options"]), stage):
105
+ if (stage, task, r["row"], rot) not in done:
106
+ plan.append((stage, task, r, rot))
107
+ print(f"[plan] model={args.model} rows_sha256={rows_sha[:12]} todo={len(plan)} already_done={len(done)}", flush=True)
108
+ if not plan:
109
+ return
110
+
111
+ t_load = time.time()
112
+ decide = load_decider(args.model)
113
+ print(f"[load] {args.model} ready in {time.time() - t_load:.0f}s", flush=True)
114
+
115
+ t0 = time.time()
116
+ last_key = None
117
+ task_hits = task_n = 0
118
+
119
+ def task_done(key, hits, count):
120
+ if key and count:
121
+ print(f"[{args.model}] ===== DONE {key[0]}/{key[1]}: {100 * hits / count:.1f}% correct over "
122
+ f"{count} presentations (run status.py for the order-debiased score) =====", flush=True)
123
+
124
+ with details.open("a", encoding="utf-8") as fp:
125
+ for n, (stage, task, r, rot) in enumerate(plan, 1):
126
+ if (stage, task) != last_key:
127
+ task_done(last_key, task_hits, task_n)
128
+ task_hits = task_n = 0
129
+ opts, k = r["options"], len(r["options"])
130
+ rotated = opts[rot:] + opts[:rot]
131
+ state, question = (r["state"], r["question"]) if stage == "full" else (NO_STATE, NO_QUESTION)
132
+ t = time.time()
133
+ probs, temp = decide(state, question, rotated)
134
+ probs = [float(x) for x in probs][:k]
135
+ aligned = [probs[(j - rot) % k] for j in range(k)]
136
+ pred = max(range(k), key=aligned.__getitem__)
137
+ fp.write(json.dumps({
138
+ "model": args.model, "stage": stage, "task": task, "row": r["row"], "rotation": rot, "k": k,
139
+ "gold": r["gold"], "pred": pred, "correct": pred == r["gold"], "p_gold": aligned[r["gold"]],
140
+ "pred_position": (pred - rot) % k, "probs": [round(x, 6) for x in aligned],
141
+ "temperature": temp, "latency_s": round(time.time() - t, 4),
142
+ }) + "\n")
143
+ task_hits += pred == r["gold"]
144
+ task_n += 1
145
+ if n % 250 == 0 or n == len(plan) or (stage, task) != last_key:
146
+ fp.flush()
147
+ rate = n / max(time.time() - t0, 1e-6)
148
+ eta = (len(plan) - n) / rate / 60 if rate else 0
149
+ print(f"[{args.model}] {stage}/{task}: {100 * task_hits / task_n:5.1f}% correct so far "
150
+ f"({task_n} done) | model {n}/{len(plan)} | {rate:.1f} it/s | ETA {eta:.0f} min", flush=True)
151
+ last_key = (stage, task)
152
+ task_done(last_key, task_hits, task_n)
153
+ del decide
154
+ gc.collect()
155
+ print(f"[done] {args.model} {len(plan)} scored in {(time.time() - t0) / 60:.1f} min", flush=True)
156
+
157
+
158
+ if __name__ == "__main__":
159
+ main()
eval/choice_panel/source_run.json ADDED
@@ -0,0 +1,16 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ {
2
+ "source_run_id": "20260927-0741",
3
+ "source_rows_sha256": "0d424d10261f0804631fd540aeb3ae06e77c91a2bc6a19148d43409ba7337ec5",
4
+ "published_rows_sha256": "a8bbb72147f51b0144f4177ed9e60a9223ef9c5979694423d93348b064bc8bf4",
5
+ "published_tasks": [
6
+ "gpqa_v2",
7
+ "chess_legal_v2",
8
+ "gsm8k_mc4_v2",
9
+ "gsm8k_mc10_v2"
10
+ ],
11
+ "complete_models": [
12
+ "raw",
13
+ "r7"
14
+ ],
15
+ "source_summary_sha256": "cd51aa0029d60f90742c1cb486004f72273256e4c454881309f4ea1d251a90c1"
16
+ }
eval/choice_panel/summarize.py ADDED
@@ -0,0 +1,120 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ """Summarize a choice-panel run: rotation-averaged and position-debiased accuracy, order
2
+ consistency, NLL vs ln k, and paired model comparisons (exact McNemar + bootstrap CI).
3
+
4
+ Usage: python summarize.py results/<run>
5
+ Writes summary.json and summary.md into the run directory. Only rows with every
6
+ planned rotation present are counted, so it can be run while the benchmark is live.
7
+ """
8
+
9
+ from __future__ import annotations
10
+
11
+ import json
12
+ import math
13
+ import random
14
+ import sys
15
+ from collections import defaultdict
16
+ from pathlib import Path
17
+
18
+
19
+ def wilson(k, n, z=1.959964):
20
+ if n == 0:
21
+ return (float("nan"), float("nan"))
22
+ p = k / n
23
+ d = 1 + z * z / n
24
+ c = (p + z * z / (2 * n)) / d
25
+ h = z * math.sqrt(p * (1 - p) / n + z * z / (4 * n * n)) / d
26
+ return c - h, c + h
27
+
28
+
29
+ def mcnemar(b, c):
30
+ n = b + c
31
+ if n == 0:
32
+ return 1.0
33
+ tail = sum(math.comb(n, i) for i in range(0, min(b, c) + 1)) / 2 ** n
34
+ return min(1.0, 2 * tail)
35
+
36
+
37
+ def main():
38
+ run = Path(sys.argv[1])
39
+ recs = defaultdict(lambda: defaultdict(list)) # (model, stage, task) -> row -> [records]
40
+ for f in sorted(run.glob("details_*.jsonl")):
41
+ for line in f.read_text(encoding="utf-8").split("\n"):
42
+ if line.strip():
43
+ d = json.loads(line)
44
+ recs[(d["model"], d["stage"], d["task"])][d["row"]].append(d)
45
+
46
+ summary, rowwise = {}, {}
47
+ for (model, stage, task), rows in sorted(recs.items()):
48
+ nrot = max(len(v) for v in rows.values())
49
+ complete = {r: v for r, v in rows.items() if len(v) == nrot}
50
+ if not complete:
51
+ continue
52
+ k = next(iter(complete.values()))[0]["k"]
53
+ rot_acc, deb_hits, consistent, nll = [], {}, 0, 0.0
54
+ pos = [0] * k
55
+ for r, v in complete.items():
56
+ gold = v[0]["gold"]
57
+ rot_acc.append(sum(x["correct"] for x in v) / len(v))
58
+ mean = [sum(x["probs"][j] for x in v) / len(v) for j in range(k)]
59
+ deb_hits[r] = max(range(k), key=mean.__getitem__) == gold
60
+ consistent += len({x["pred"] for x in v}) == 1
61
+ nll += -math.log(max(mean[gold], 1e-12))
62
+ for x in v:
63
+ pos[x["pred_position"]] += 1
64
+ n = len(complete)
65
+ hits = sum(deb_hits.values())
66
+ lo, hi = wilson(hits, n)
67
+ summary[f"{model}|{stage}|{task}"] = {
68
+ "model": model, "stage": stage, "task": task, "rows": n, "rotations": nrot, "k": k,
69
+ "rotation_mean_accuracy": sum(rot_acc) / n,
70
+ "debiased_accuracy": hits / n, "debiased_ci95": [lo, hi],
71
+ "order_consistency": consistent / n,
72
+ "nll_mean_probs": nll / n, "ln_k": math.log(k),
73
+ "pred_position_share": [c / sum(pos) for c in pos],
74
+ }
75
+ rowwise[(model, stage, task)] = deb_hits
76
+
77
+ pairs = {}
78
+ models = sorted({m for m, _, _ in rowwise})
79
+ for a, b in (("raw", "r7"),):
80
+ if a not in models or b not in models:
81
+ continue
82
+ for (m, stage, task), ha in rowwise.items():
83
+ if m != a or (b, stage, task) not in rowwise:
84
+ continue
85
+ hb = rowwise[(b, stage, task)]
86
+ ids = sorted(set(ha) & set(hb))
87
+ if not ids:
88
+ continue
89
+ diff = [int(hb[i]) - int(ha[i]) for i in ids]
90
+ rng = random.Random(0)
91
+ boots = sorted(sum(diff[rng.randrange(len(ids))] for _ in ids) / len(ids) for _ in range(2000))
92
+ b_only = sum(1 for i in ids if hb[i] and not ha[i])
93
+ a_only = sum(1 for i in ids if ha[i] and not hb[i])
94
+ pairs[f"{a}->{b}|{stage}|{task}"] = {
95
+ "rows": len(ids), "delta_pp": 100 * sum(diff) / len(ids),
96
+ "ci95_pp": [100 * boots[49], 100 * boots[1949]],
97
+ f"{a}_only": a_only, f"{b}_only": b_only, "mcnemar_p": mcnemar(a_only, b_only),
98
+ }
99
+
100
+ (run / "summary.json").write_text(json.dumps({"tasks": summary, "pairs": pairs}, indent=2), encoding="utf-8")
101
+ lines = ["# Choice-panel benchmark summary", "",
102
+ "Debiased = argmax of probabilities averaged over rotations (mapped to original option order).", "",
103
+ "| Model | Stage | Task | Rows | Rot. | Rotation-mean acc | Debiased acc [95% CI] | Order consistency | NLL (ln k) |",
104
+ "|---|---|---|---:|---:|---:|---|---:|---|"]
105
+ for s in summary.values():
106
+ lines.append(f"| {s['model']} | {s['stage']} | {s['task']} | {s['rows']} | {s['rotations']} | "
107
+ f"{100*s['rotation_mean_accuracy']:.2f}% | {100*s['debiased_accuracy']:.2f}% "
108
+ f"[{100*s['debiased_ci95'][0]:.1f}, {100*s['debiased_ci95'][1]:.1f}] | "
109
+ f"{100*s['order_consistency']:.1f}% | {s['nll_mean_probs']:.3f} ({s['ln_k']:.3f}) |")
110
+ lines += ["", "| Pair | Stage | Task | Rows | Δ pp [95% CI] | McNemar p |", "|---|---|---|---:|---|---:|"]
111
+ for key, p in pairs.items():
112
+ pair, stage, task = key.split("|")
113
+ lines.append(f"| {pair} | {stage} | {task} | {p['rows']} | {p['delta_pp']:+.2f} "
114
+ f"[{p['ci95_pp'][0]:+.2f}, {p['ci95_pp'][1]:+.2f}] | {p['mcnemar_p']:.2g} |")
115
+ (run / "summary.md").write_text("\n".join(lines) + "\n", encoding="utf-8")
116
+ print("\n".join(lines))
117
+
118
+
119
+ if __name__ == "__main__":
120
+ main()
eval/choice_panel/summary.json ADDED
@@ -0,0 +1,486 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ {
2
+ "tasks": {
3
+ "r7|full|chess_legal_v2": {
4
+ "model": "r7",
5
+ "stage": "full",
6
+ "task": "chess_legal_v2",
7
+ "rows": 500,
8
+ "rotations": 4,
9
+ "k": 4,
10
+ "rotation_mean_accuracy": 0.4005,
11
+ "debiased_accuracy": 0.426,
12
+ "debiased_ci95": [
13
+ 0.38338257803751286,
14
+ 0.4697458243684626
15
+ ],
16
+ "order_consistency": 0.466,
17
+ "nll_mean_probs": 1.2895886544670165,
18
+ "ln_k": 1.3862943611198906,
19
+ "pred_position_share": [
20
+ 0.4155,
21
+ 0.2225,
22
+ 0.183,
23
+ 0.179
24
+ ]
25
+ },
26
+ "r7|full|gpqa_v2": {
27
+ "model": "r7",
28
+ "stage": "full",
29
+ "task": "gpqa_v2",
30
+ "rows": 198,
31
+ "rotations": 4,
32
+ "k": 4,
33
+ "rotation_mean_accuracy": 0.3371212121212121,
34
+ "debiased_accuracy": 0.3282828282828283,
35
+ "debiased_ci95": [
36
+ 0.2666857032513807,
37
+ 0.3964162165579131
38
+ ],
39
+ "order_consistency": 0.23232323232323232,
40
+ "nll_mean_probs": 1.3462829985829794,
41
+ "ln_k": 1.3862943611198906,
42
+ "pred_position_share": [
43
+ 0.2032828282828283,
44
+ 0.18686868686868688,
45
+ 0.2777777777777778,
46
+ 0.33207070707070707
47
+ ]
48
+ },
49
+ "r7|full|gsm8k_mc10_v2": {
50
+ "model": "r7",
51
+ "stage": "full",
52
+ "task": "gsm8k_mc10_v2",
53
+ "rows": 1319,
54
+ "rotations": 10,
55
+ "k": 10,
56
+ "rotation_mean_accuracy": 0.2858225928733891,
57
+ "debiased_accuracy": 0.3062926459438969,
58
+ "debiased_ci95": [
59
+ 0.282008848029071,
60
+ 0.3317014748752748
61
+ ],
62
+ "order_consistency": 0.14935557240333586,
63
+ "nll_mean_probs": 1.9325159750133525,
64
+ "ln_k": 2.302585092994046,
65
+ "pred_position_share": [
66
+ 0.06739954510993176,
67
+ 0.07816527672479151,
68
+ 0.08695981804397271,
69
+ 0.09833206974981046,
70
+ 0.10151630022744504,
71
+ 0.100303260045489,
72
+ 0.10962850644427596,
73
+ 0.11940864291129644,
74
+ 0.11834723275208492,
75
+ 0.1199393479909022
76
+ ]
77
+ },
78
+ "r7|full|gsm8k_mc4_v2": {
79
+ "model": "r7",
80
+ "stage": "full",
81
+ "task": "gsm8k_mc4_v2",
82
+ "rows": 1319,
83
+ "rotations": 4,
84
+ "k": 4,
85
+ "rotation_mean_accuracy": 0.515352539802881,
86
+ "debiased_accuracy": 0.5382865807429871,
87
+ "debiased_ci95": [
88
+ 0.5113101453003895,
89
+ 0.5650406519337808
90
+ ],
91
+ "order_consistency": 0.43290371493555724,
92
+ "nll_mean_probs": 1.096663031900488,
93
+ "ln_k": 1.3862943611198906,
94
+ "pred_position_share": [
95
+ 0.19143290371493557,
96
+ 0.23294162244124336,
97
+ 0.27369219105382864,
98
+ 0.30193328278999243
99
+ ]
100
+ },
101
+ "r7|options_only|chess_legal_v2": {
102
+ "model": "r7",
103
+ "stage": "options_only",
104
+ "task": "chess_legal_v2",
105
+ "rows": 500,
106
+ "rotations": 4,
107
+ "k": 4,
108
+ "rotation_mean_accuracy": 0.294,
109
+ "debiased_accuracy": 0.308,
110
+ "debiased_ci95": [
111
+ 0.26912574776130455,
112
+ 0.34980199902176684
113
+ ],
114
+ "order_consistency": 0.11,
115
+ "nll_mean_probs": 1.4122002220138676,
116
+ "ln_k": 1.3862943611198906,
117
+ "pred_position_share": [
118
+ 0.154,
119
+ 0.143,
120
+ 0.259,
121
+ 0.444
122
+ ]
123
+ },
124
+ "r7|options_only|gpqa_v2": {
125
+ "model": "r7",
126
+ "stage": "options_only",
127
+ "task": "gpqa_v2",
128
+ "rows": 198,
129
+ "rotations": 4,
130
+ "k": 4,
131
+ "rotation_mean_accuracy": 0.3068181818181818,
132
+ "debiased_accuracy": 0.32323232323232326,
133
+ "debiased_ci95": [
134
+ 0.2619851056969361,
135
+ 0.3912080470479252
136
+ ],
137
+ "order_consistency": 0.06565656565656566,
138
+ "nll_mean_probs": 1.4115891851171167,
139
+ "ln_k": 1.3862943611198906,
140
+ "pred_position_share": [
141
+ 0.15025252525252525,
142
+ 0.16540404040404041,
143
+ 0.3068181818181818,
144
+ 0.37752525252525254
145
+ ]
146
+ },
147
+ "r7|options_only|gsm8k_mc10_v2": {
148
+ "model": "r7",
149
+ "stage": "options_only",
150
+ "task": "gsm8k_mc10_v2",
151
+ "rows": 1319,
152
+ "rotations": 5,
153
+ "k": 10,
154
+ "rotation_mean_accuracy": 0.10947687642153177,
155
+ "debiased_accuracy": 0.10841546626231995,
156
+ "debiased_ci95": [
157
+ 0.0927599612966865,
158
+ 0.12634525114986767
159
+ ],
160
+ "order_consistency": 0.003032600454890068,
161
+ "nll_mean_probs": 2.377036472907572,
162
+ "ln_k": 2.302585092994046,
163
+ "pred_position_share": [
164
+ 0.011372251705837756,
165
+ 0.004397270659590599,
166
+ 0.014859742228961335,
167
+ 0.0357846853677028,
168
+ 0.047611827141774074,
169
+ 0.08809704321455648,
170
+ 0.1420773313115997,
171
+ 0.20272934040940105,
172
+ 0.18832448824867323,
173
+ 0.264746019711903
174
+ ]
175
+ },
176
+ "r7|options_only|gsm8k_mc4_v2": {
177
+ "model": "r7",
178
+ "stage": "options_only",
179
+ "task": "gsm8k_mc4_v2",
180
+ "rows": 1319,
181
+ "rotations": 4,
182
+ "k": 4,
183
+ "rotation_mean_accuracy": 0.2505686125852919,
184
+ "debiased_accuracy": 0.24336618650492797,
185
+ "debiased_ci95": [
186
+ 0.22097520606722637,
187
+ 0.2672476679175918
188
+ ],
189
+ "order_consistency": 0.05458680818802123,
190
+ "nll_mean_probs": 1.4384991214185934,
191
+ "ln_k": 1.3862943611198906,
192
+ "pred_position_share": [
193
+ 0.24298711144806673,
194
+ 0.07695223654283548,
195
+ 0.22915087187263078,
196
+ 0.45090978013646704
197
+ ]
198
+ },
199
+ "raw|full|chess_legal_v2": {
200
+ "model": "raw",
201
+ "stage": "full",
202
+ "task": "chess_legal_v2",
203
+ "rows": 500,
204
+ "rotations": 4,
205
+ "k": 4,
206
+ "rotation_mean_accuracy": 0.315,
207
+ "debiased_accuracy": 0.438,
208
+ "debiased_ci95": [
209
+ 0.39514834375489416,
210
+ 0.4817970744771394
211
+ ],
212
+ "order_consistency": 0.014,
213
+ "nll_mean_probs": 1.2759298211025412,
214
+ "ln_k": 1.3862943611198906,
215
+ "pred_position_share": [
216
+ 0.6895,
217
+ 0.29,
218
+ 0.0125,
219
+ 0.008
220
+ ]
221
+ },
222
+ "raw|full|gpqa_v2": {
223
+ "model": "raw",
224
+ "stage": "full",
225
+ "task": "gpqa_v2",
226
+ "rows": 198,
227
+ "rotations": 4,
228
+ "k": 4,
229
+ "rotation_mean_accuracy": 0.31313131313131315,
230
+ "debiased_accuracy": 0.3282828282828283,
231
+ "debiased_ci95": [
232
+ 0.2666857032513807,
233
+ 0.3964162165579131
234
+ ],
235
+ "order_consistency": 0.10101010101010101,
236
+ "nll_mean_probs": 1.554143105728764,
237
+ "ln_k": 1.3862943611198906,
238
+ "pred_position_share": [
239
+ 0.10984848484848485,
240
+ 0.63510101010101,
241
+ 0.13383838383838384,
242
+ 0.12121212121212122
243
+ ]
244
+ },
245
+ "raw|full|gsm8k_mc10_v2": {
246
+ "model": "raw",
247
+ "stage": "full",
248
+ "task": "gsm8k_mc10_v2",
249
+ "rows": 1319,
250
+ "rotations": 10,
251
+ "k": 10,
252
+ "rotation_mean_accuracy": 0.16467020470052868,
253
+ "debiased_accuracy": 0.20545868081880211,
254
+ "debiased_ci95": [
255
+ 0.18452439827071845,
256
+ 0.22810362696739636
257
+ ],
258
+ "order_consistency": 0.006065200909780136,
259
+ "nll_mean_probs": 2.160294692789794,
260
+ "ln_k": 2.302585092994046,
261
+ "pred_position_share": [
262
+ 0.17505686125852918,
263
+ 0.523502653525398,
264
+ 0.05473843821076573,
265
+ 0.054510993176648974,
266
+ 0.013722517058377558,
267
+ 0.03752843062926459,
268
+ 0.019257012888551934,
269
+ 0.06110689916603487,
270
+ 0.030780894617134194,
271
+ 0.02979529946929492
272
+ ]
273
+ },
274
+ "raw|full|gsm8k_mc4_v2": {
275
+ "model": "raw",
276
+ "stage": "full",
277
+ "task": "gsm8k_mc4_v2",
278
+ "rows": 1319,
279
+ "rotations": 4,
280
+ "k": 4,
281
+ "rotation_mean_accuracy": 0.36713419257012886,
282
+ "debiased_accuracy": 0.43745261561789234,
283
+ "debiased_ci95": [
284
+ 0.4109011521947761,
285
+ 0.4643673473731633
286
+ ],
287
+ "order_consistency": 0.06899166034874905,
288
+ "nll_mean_probs": 1.2742964931019587,
289
+ "ln_k": 1.3862943611198906,
290
+ "pred_position_share": [
291
+ 0.2579605761940864,
292
+ 0.5869977255496588,
293
+ 0.11694465504169825,
294
+ 0.03809704321455648
295
+ ]
296
+ },
297
+ "raw|options_only|chess_legal_v2": {
298
+ "model": "raw",
299
+ "stage": "options_only",
300
+ "task": "chess_legal_v2",
301
+ "rows": 500,
302
+ "rotations": 4,
303
+ "k": 4,
304
+ "rotation_mean_accuracy": 0.252,
305
+ "debiased_accuracy": 0.29,
306
+ "debiased_ci95": [
307
+ 0.2519474195915272,
308
+ 0.33125480345245706
309
+ ],
310
+ "order_consistency": 0.0,
311
+ "nll_mean_probs": 1.4114726259217971,
312
+ "ln_k": 1.3862943611198906,
313
+ "pred_position_share": [
314
+ 0.0,
315
+ 0.9815,
316
+ 0.0075,
317
+ 0.011
318
+ ]
319
+ },
320
+ "raw|options_only|gpqa_v2": {
321
+ "model": "raw",
322
+ "stage": "options_only",
323
+ "task": "gpqa_v2",
324
+ "rows": 198,
325
+ "rotations": 4,
326
+ "k": 4,
327
+ "rotation_mean_accuracy": 0.25757575757575757,
328
+ "debiased_accuracy": 0.25757575757575757,
329
+ "debiased_ci95": [
330
+ 0.20168506443256926,
331
+ 0.32269411647466895
332
+ ],
333
+ "order_consistency": 0.020202020202020204,
334
+ "nll_mean_probs": 1.5601990009952065,
335
+ "ln_k": 1.3862943611198906,
336
+ "pred_position_share": [
337
+ 0.015151515151515152,
338
+ 0.8434343434343434,
339
+ 0.07702020202020202,
340
+ 0.06439393939393939
341
+ ]
342
+ },
343
+ "raw|options_only|gsm8k_mc10_v2": {
344
+ "model": "raw",
345
+ "stage": "options_only",
346
+ "task": "gsm8k_mc10_v2",
347
+ "rows": 1319,
348
+ "rotations": 5,
349
+ "k": 10,
350
+ "rotation_mean_accuracy": 0.10553449583017482,
351
+ "debiased_accuracy": 0.10538286580742987,
352
+ "debiased_ci95": [
353
+ 0.08994299481674577,
354
+ 0.1231146297300721
355
+ ],
356
+ "order_consistency": 0.0,
357
+ "nll_mean_probs": 2.7989174437564013,
358
+ "ln_k": 2.302585092994046,
359
+ "pred_position_share": [
360
+ 0.0,
361
+ 0.13525398028809704,
362
+ 0.0037907505686125853,
363
+ 0.0,
364
+ 0.0012130401819560274,
365
+ 0.00030326004548900684,
366
+ 0.4363912054586808,
367
+ 0.0018195602729340408,
368
+ 0.3905989385898408,
369
+ 0.03062926459438969
370
+ ]
371
+ },
372
+ "raw|options_only|gsm8k_mc4_v2": {
373
+ "model": "raw",
374
+ "stage": "options_only",
375
+ "task": "gsm8k_mc4_v2",
376
+ "rows": 1319,
377
+ "rotations": 4,
378
+ "k": 4,
379
+ "rotation_mean_accuracy": 0.24981046247156938,
380
+ "debiased_accuracy": 0.24715693707354056,
381
+ "debiased_ci95": [
382
+ 0.22463441088141858,
383
+ 0.27114794797806996
384
+ ],
385
+ "order_consistency": 0.0,
386
+ "nll_mean_probs": 1.4600850953045421,
387
+ "ln_k": 1.3862943611198906,
388
+ "pred_position_share": [
389
+ 0.0,
390
+ 0.9474981046247157,
391
+ 0.041319181197877176,
392
+ 0.011182714177407127
393
+ ]
394
+ }
395
+ },
396
+ "pairs": {
397
+ "raw->r7|full|chess_legal_v2": {
398
+ "rows": 500,
399
+ "delta_pp": -1.2,
400
+ "ci95_pp": [
401
+ -6.4,
402
+ 3.8
403
+ ],
404
+ "raw_only": 90,
405
+ "r7_only": 84,
406
+ "mcnemar_p": 0.7047680231195156
407
+ },
408
+ "raw->r7|full|gpqa_v2": {
409
+ "rows": 198,
410
+ "delta_pp": 0.0,
411
+ "ci95_pp": [
412
+ -7.07070707070707,
413
+ 7.07070707070707
414
+ ],
415
+ "raw_only": 26,
416
+ "r7_only": 26,
417
+ "mcnemar_p": 1.0
418
+ },
419
+ "raw->r7|full|gsm8k_mc10_v2": {
420
+ "rows": 1319,
421
+ "delta_pp": 10.083396512509477,
422
+ "ci95_pp": [
423
+ 7.050796057619409,
424
+ 13.040181956027293
425
+ ],
426
+ "raw_only": 150,
427
+ "r7_only": 283,
428
+ "mcnemar_p": 1.625471377453816e-10
429
+ },
430
+ "raw->r7|full|gsm8k_mc4_v2": {
431
+ "rows": 1319,
432
+ "delta_pp": 10.083396512509477,
433
+ "ci95_pp": [
434
+ 6.444275966641396,
435
+ 13.722517058377559
436
+ ],
437
+ "raw_only": 226,
438
+ "r7_only": 359,
439
+ "mcnemar_p": 4.2453654446737796e-08
440
+ },
441
+ "raw->r7|options_only|chess_legal_v2": {
442
+ "rows": 500,
443
+ "delta_pp": 1.8,
444
+ "ci95_pp": [
445
+ -3.0,
446
+ 7.199999999999999
447
+ ],
448
+ "raw_only": 79,
449
+ "r7_only": 88,
450
+ "mcnemar_p": 0.5360050623796357
451
+ },
452
+ "raw->r7|options_only|gpqa_v2": {
453
+ "rows": 198,
454
+ "delta_pp": 6.565656565656566,
455
+ "ci95_pp": [
456
+ -1.0101010101010102,
457
+ 14.14141414141414
458
+ ],
459
+ "raw_only": 24,
460
+ "r7_only": 37,
461
+ "mcnemar_p": 0.12373144253809217
462
+ },
463
+ "raw->r7|options_only|gsm8k_mc10_v2": {
464
+ "rows": 1319,
465
+ "delta_pp": 0.3032600454890068,
466
+ "ci95_pp": [
467
+ -2.047005307050796,
468
+ 2.577710386656558
469
+ ],
470
+ "raw_only": 119,
471
+ "r7_only": 123,
472
+ "mcnemar_p": 0.8471289384306088
473
+ },
474
+ "raw->r7|options_only|gsm8k_mc4_v2": {
475
+ "rows": 1319,
476
+ "delta_pp": -0.37907505686125853,
477
+ "ci95_pp": [
478
+ -3.790750568612585,
479
+ 2.9567854435178167
480
+ ],
481
+ "raw_only": 261,
482
+ "r7_only": 256,
483
+ "mcnemar_p": 0.8603786002624875
484
+ }
485
+ }
486
+ }
eval/choice_panel/summary.md ADDED
@@ -0,0 +1,22 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # Choice-panel benchmark summary
2
+
3
+ Main accuracy is argmax of probabilities averaged over cyclic rotations, mapped to original option identities.
4
+
5
+ | Model | Stage | Task | Rows | Rotations | Debiased accuracy [95% CI] | Per-order accuracy | Same answer |
6
+ |---|---|---|---:|---:|---|---:|---:|
7
+ | OpenJEV 1.0 | full | chess_legal_v2 | 500 | 4 | 42.60% [38.3, 47.0] | 40.05% | 46.6% |
8
+ | OpenJEV 1.0 | full | gpqa_v2 | 198 | 4 | 32.83% [26.7, 39.6] | 33.71% | 23.2% |
9
+ | OpenJEV 1.0 | full | gsm8k_mc10_v2 | 1319 | 10 | 30.63% [28.2, 33.2] | 28.58% | 14.9% |
10
+ | OpenJEV 1.0 | full | gsm8k_mc4_v2 | 1319 | 4 | 53.83% [51.1, 56.5] | 51.54% | 43.3% |
11
+ | OpenJEV 1.0 | options_only | chess_legal_v2 | 500 | 4 | 30.80% [26.9, 35.0] | 29.40% | 11.0% |
12
+ | OpenJEV 1.0 | options_only | gpqa_v2 | 198 | 4 | 32.32% [26.2, 39.1] | 30.68% | 6.6% |
13
+ | OpenJEV 1.0 | options_only | gsm8k_mc10_v2 | 1319 | 5 | 10.84% [9.3, 12.6] | 10.95% | 0.3% |
14
+ | OpenJEV 1.0 | options_only | gsm8k_mc4_v2 | 1319 | 4 | 24.34% [22.1, 26.7] | 25.06% | 5.5% |
15
+ | Gemma base | full | chess_legal_v2 | 500 | 4 | 43.80% [39.5, 48.2] | 31.50% | 1.4% |
16
+ | Gemma base | full | gpqa_v2 | 198 | 4 | 32.83% [26.7, 39.6] | 31.31% | 10.1% |
17
+ | Gemma base | full | gsm8k_mc10_v2 | 1319 | 10 | 20.55% [18.5, 22.8] | 16.47% | 0.6% |
18
+ | Gemma base | full | gsm8k_mc4_v2 | 1319 | 4 | 43.75% [41.1, 46.4] | 36.71% | 6.9% |
19
+ | Gemma base | options_only | chess_legal_v2 | 500 | 4 | 29.00% [25.2, 33.1] | 25.20% | 0.0% |
20
+ | Gemma base | options_only | gpqa_v2 | 198 | 4 | 25.76% [20.2, 32.3] | 25.76% | 2.0% |
21
+ | Gemma base | options_only | gsm8k_mc10_v2 | 1319 | 5 | 10.54% [9.0, 12.3] | 10.55% | 0.0% |
22
+ | Gemma base | options_only | gsm8k_mc4_v2 | 1319 | 4 | 24.72% [22.5, 27.1] | 24.98% | 0.0% |
eval/harness/benchmark_public.py ADDED
@@ -0,0 +1,634 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ #!/usr/bin/env python
2
+ import argparse
3
+ import csv
4
+ import json
5
+ import math
6
+ import os
7
+ import random
8
+ import re
9
+ import runpy
10
+ import statistics
11
+ import sys
12
+ import time
13
+ from datetime import datetime
14
+ from pathlib import Path
15
+
16
+ REPO_ROOT = Path(__file__).resolve().parents[2]
17
+ sys.path.insert(0, str(REPO_ROOT))
18
+
19
+ import numpy as np
20
+ import torch
21
+ from datasets import load_dataset
22
+
23
+ TASK_ORDER = [
24
+ "arc_challenge",
25
+ "winogrande",
26
+ "arc_easy",
27
+ "hellaswag",
28
+ "mmlu",
29
+ ]
30
+ DISPLAY = {
31
+ "mmlu": "MMLU",
32
+ "gpqa": "GPQA Diamond",
33
+ "arc_easy": "ARC-Easy",
34
+ "arc_challenge": "ARC-Challenge",
35
+ "winogrande": "WinoGrande",
36
+ "hellaswag": "HellaSwag",
37
+ "gsm8k_mc4": "GSM8K 4 choices",
38
+ "gsm8k_mc10": "GSM8K 10 choices",
39
+ "chess": "Chess 4 moves",
40
+ }
41
+
42
+ # Frozen measured v1.1 NF4 baseline from the exact same benchmark harness.
43
+ V11_NF4 = {
44
+ "mmlu": {"accuracy": 0.6601, "nll": 0.8368, "ece": 0.0185},
45
+ "gpqa": {"accuracy": 0.3333, "nll": 1.3871, "ece": 0.1136},
46
+ "arc_easy": {"accuracy": 0.9575, "nll": 0.1487, "ece": 0.0365},
47
+ "arc_challenge": {"accuracy": 0.8814, "nll": 0.3334, "ece": 0.0419},
48
+ "winogrande": {"accuracy": 0.6417, "nll": 0.7466, "ece": 0.1704},
49
+ "hellaswag": {"accuracy": 0.9014, "nll": 0.2639, "ece": 0.0142},
50
+ "gsm8k_mc4": {"accuracy": 0.4556, "nll": 1.1917, "ece": 0.0496},
51
+ "gsm8k_mc10": {"accuracy": 0.2722, "nll": 1.9969, "ece": 0.0400},
52
+ "chess": {"accuracy": 0.3880, "nll": 1.2856, "ece": 0.0595},
53
+ }
54
+
55
+
56
+ def load_r7_decider(repo_dir, load_mode):
57
+ """Score with the public safetensors release and its published loader."""
58
+ from openjev import OpenJEVR7
59
+
60
+ jev = OpenJEVR7.from_pretrained(repo_dir, device="cuda", load_mode=load_mode)
61
+
62
+ def decide(state, question, options):
63
+ result = jev.choice(state=state, instruction=question, options=options)
64
+ torch.cuda.synchronize()
65
+ return result["probabilities"], result["temperature"]
66
+
67
+ return decide
68
+
69
+
70
+ def item(state, question, options, gold, source_id=None):
71
+ return {
72
+ "state": state,
73
+ "question": str(question).strip(),
74
+ "options": [str(x).strip() for x in options],
75
+ "gold": int(gold),
76
+ "source_id": source_id,
77
+ }
78
+
79
+
80
+ def load_mmlu():
81
+ ds = load_dataset("cais/mmlu", "all", split="test")
82
+ out = []
83
+ for i, ex in enumerate(ds):
84
+ # Matches the JEV-E4B training serialization for MMLU, but uses the
85
+ # official test rows used by the openjev radar.
86
+ out.append(item(
87
+ f"Subject: {ex.get('subject', 'general')}",
88
+ ex["question"],
89
+ ex["choices"],
90
+ int(ex["answer"]),
91
+ f"mmlu:test:{i}",
92
+ ))
93
+ return out
94
+
95
+
96
+ def load_gpqa():
97
+ # The canonical Idavidrein/gpqa repo is gated on Hugging Face.
98
+ # Use a public 198-row GPQA Diamond mirror with the original columns so
99
+ # this benchmark does not require HF authentication.
100
+ ds = load_dataset("JingzeShi/gpqa_diamond", split="train")
101
+ out = []
102
+ for i, ex in enumerate(ds):
103
+ opts = [
104
+ ex["Correct Answer"],
105
+ ex["Incorrect Answer 1"],
106
+ ex["Incorrect Answer 2"],
107
+ ex["Incorrect Answer 3"],
108
+ ]
109
+ # Keep the same protocol as the original openjev radar loader:
110
+ # correct answer in slot 0.
111
+ out.append(item(
112
+ "Advanced science question",
113
+ ex["Question"],
114
+ opts,
115
+ 0,
116
+ f"gpqa:diamond:{i}",
117
+ ))
118
+ return out
119
+
120
+
121
+ def load_arc(config):
122
+ ds = load_dataset("allenai/ai2_arc", config, split="test")
123
+ out = []
124
+ for i, ex in enumerate(ds):
125
+ labels = list(ex["choices"]["label"])
126
+ if ex["answerKey"] not in labels:
127
+ continue
128
+ out.append(item(
129
+ "Science question",
130
+ ex["question"],
131
+ list(ex["choices"]["text"]),
132
+ labels.index(ex["answerKey"]),
133
+ f"arc:{config}:{ex.get('id', i)}",
134
+ ))
135
+ return out
136
+
137
+
138
+ def load_winogrande():
139
+ ds = load_dataset("allenai/winogrande", "winogrande_xl", split="validation")
140
+ out = []
141
+ for i, ex in enumerate(ds):
142
+ out.append(item(
143
+ ex["sentence"],
144
+ "Which option best completes the blank in the sentence?",
145
+ [ex["option1"], ex["option2"]],
146
+ int(ex["answer"]) - 1,
147
+ f"winogrande:validation:{i}",
148
+ ))
149
+ return out
150
+
151
+
152
+ def load_hellaswag():
153
+ ds = load_dataset("Rowan/hellaswag", split="validation")
154
+ out = []
155
+ for i, ex in enumerate(ds):
156
+ # Matches the JEV-E4B HellaSwag training shape.
157
+ ctx = ex.get("ctx")
158
+ if not ctx:
159
+ ctx = (ex["ctx_a"] + " " + ex["ctx_b"].capitalize()).strip()
160
+ out.append(item(
161
+ ctx,
162
+ "Choose the most plausible continuation.",
163
+ ex["endings"],
164
+ int(ex["label"]),
165
+ f"hellaswag:validation:{ex.get('ind', i)}",
166
+ ))
167
+ return out
168
+
169
+
170
+ def extract_gsm_number(answer):
171
+ # GSM8K canonical final answer follows ####. Fall back to the last number.
172
+ if "####" in answer:
173
+ s = answer.rsplit("####", 1)[-1].strip().replace(",", "")
174
+ else:
175
+ nums = re.findall(r"-?\d+(?:,\d{3})*(?:\.\d+)?", answer)
176
+ if not nums:
177
+ raise ValueError(f"No numeric answer in: {answer!r}")
178
+ s = nums[-1].replace(",", "")
179
+ # Canonicalize integer-like floats.
180
+ v = float(s)
181
+ return str(int(v)) if v == int(v) else str(v)
182
+
183
+
184
+ def load_gsm8k_mc(k, seed=0):
185
+ rng = random.Random(seed)
186
+ ds = load_dataset("openai/gsm8k", "main", split="test")
187
+ out = []
188
+ for i, ex in enumerate(ds):
189
+ g = extract_gsm_number(ex["answer"])
190
+ gv = float(g)
191
+ cands = set()
192
+
193
+ generators = [
194
+ lambda: gv + rng.choice([1, 2, 3, 5, 10]),
195
+ lambda: gv - rng.choice([1, 2, 3, 5, 10]),
196
+ lambda: gv * 2,
197
+ lambda: gv / 2,
198
+ lambda: gv + rng.choice([4, 6, 7, 8, 9, 12, 15, 20, 25, 50]),
199
+ lambda: gv * 10,
200
+ lambda: gv * rng.choice([3, 4, 5]),
201
+ lambda: gv - rng.choice([4, 6, 7, 8, 9, 12, 15, 20, 25, 50]),
202
+ lambda: abs(gv) + rng.randint(100, 999),
203
+ ]
204
+ gi = 0
205
+ while len(cands) < k - 1 and gi < 200:
206
+ v = generators[gi % len(generators)]()
207
+ gi += 1
208
+ vs = str(int(v)) if float(v) == int(v) else f"{v:.2f}"
209
+ if vs != g and vs not in cands and v >= 0:
210
+ cands.add(vs)
211
+
212
+ if len(cands) < k - 1:
213
+ # Deterministic emergency fallback.
214
+ x = 1
215
+ while len(cands) < k - 1:
216
+ v = gv + x
217
+ vs = str(int(v)) if float(v) == int(v) else str(v)
218
+ if vs != g:
219
+ cands.add(vs)
220
+ x += 1
221
+
222
+ opts = [g] + sorted(cands, key=lambda _: rng.random())
223
+ rng.shuffle(opts)
224
+ out.append(item(
225
+ "Math word problem",
226
+ ex["question"],
227
+ opts,
228
+ opts.index(g),
229
+ f"gsm8k:test:{i}:k{k}",
230
+ ))
231
+ return out
232
+
233
+
234
+ def load_chess(n=500, seed=0):
235
+ import chess
236
+
237
+ rng = random.Random(seed)
238
+ out = []
239
+ while len(out) < n:
240
+ board = chess.Board()
241
+ for _ in range(rng.randint(6, 40)):
242
+ moves = list(board.legal_moves)
243
+ if not moves or board.is_game_over():
244
+ break
245
+ board.push(rng.choice(moves))
246
+
247
+ legal = list(board.legal_moves)
248
+ if len(legal) < 2 or board.is_game_over():
249
+ continue
250
+
251
+ legal_san = {board.san(m) for m in legal}
252
+ good = board.san(rng.choice(legal))
253
+ bad = set()
254
+ tries = 0
255
+
256
+ while len(bad) < 3 and tries < 500:
257
+ tries += 1
258
+ own = [
259
+ s for s in chess.SQUARES
260
+ if board.piece_at(s) and board.piece_at(s).color == board.turn
261
+ ]
262
+ if not own:
263
+ break
264
+ sq = rng.choice(own)
265
+ piece = board.piece_at(sq)
266
+ to = rng.choice(chess.SQUARES)
267
+ if to == sq:
268
+ continue
269
+ dst = board.piece_at(to)
270
+ if dst and dst.color == board.turn:
271
+ continue
272
+
273
+ capture = dst is not None
274
+ if piece.piece_type == chess.PAWN:
275
+ san = (
276
+ (chess.square_name(sq)[0] + "x" if capture else "")
277
+ + chess.square_name(to)
278
+ )
279
+ else:
280
+ san = (
281
+ chess.piece_symbol(piece.piece_type).upper()
282
+ + ("x" if capture else "")
283
+ + chess.square_name(to)
284
+ )
285
+
286
+ if san not in legal_san and san != good:
287
+ bad.add(san)
288
+
289
+ if len(bad) < 3:
290
+ continue
291
+
292
+ opts = [good] + sorted(bad)
293
+ rng.shuffle(opts)
294
+ pgn = chess.Board().variation_san(board.move_stack)
295
+ state = (
296
+ f"Chess position after the moves: {pgn}\n"
297
+ f"FEN: {board.fen()}\n"
298
+ f"{'White' if board.turn else 'Black'} to move."
299
+ )
300
+ out.append(item(
301
+ state,
302
+ "Which of the following moves is legal in this position?",
303
+ opts,
304
+ opts.index(good),
305
+ f"chess:synthetic:{len(out)}",
306
+ ))
307
+ return out
308
+
309
+
310
+ LOADERS = {
311
+ "mmlu": load_mmlu,
312
+ "gpqa": load_gpqa,
313
+ "arc_easy": lambda: load_arc("ARC-Easy"),
314
+ "arc_challenge": lambda: load_arc("ARC-Challenge"),
315
+ "winogrande": load_winogrande,
316
+ "hellaswag": load_hellaswag,
317
+ "gsm8k_mc4": lambda: load_gsm8k_mc(4),
318
+ "gsm8k_mc10": lambda: load_gsm8k_mc(10),
319
+ "chess": load_chess,
320
+ }
321
+
322
+
323
+ def normalize_probs(ps, n):
324
+ arr = np.asarray([float(x) for x in ps], dtype=np.float64)
325
+ if arr.shape != (n,):
326
+ raise ValueError(f"Expected {n} probabilities, got shape {arr.shape}")
327
+ if not np.all(np.isfinite(arr)):
328
+ raise ValueError(f"Non-finite probabilities: {arr}")
329
+ # The local model should already return a simplex. Only repair tiny
330
+ # numerical drift, not model semantics.
331
+ if np.any(arr < -1e-6):
332
+ raise ValueError(f"Negative probability: {arr}")
333
+ arr = np.clip(arr, 0.0, None)
334
+ s = float(arr.sum())
335
+ if s <= 0:
336
+ raise ValueError(f"Probability sum is {s}")
337
+ if abs(s - 1.0) > 1e-4:
338
+ raise ValueError(f"Probability sum {s:.8f} is not ~1.0")
339
+ return arr / s
340
+
341
+
342
+ def ece(rows, bins=15):
343
+ if not rows:
344
+ return float("nan")
345
+ conf = np.array([r["confidence"] for r in rows], dtype=float)
346
+ corr = np.array([1.0 if r["correct"] else 0.0 for r in rows], dtype=float)
347
+ total = len(rows)
348
+ val = 0.0
349
+ for b in range(bins):
350
+ lo = b / bins
351
+ hi = (b + 1) / bins
352
+ mask = (conf >= lo) & (conf < hi if b < bins - 1 else conf <= hi)
353
+ n = int(mask.sum())
354
+ if n:
355
+ val += (n / total) * abs(float(corr[mask].mean()) - float(conf[mask].mean()))
356
+ return val
357
+
358
+
359
+ def summarize(rows):
360
+ if not rows:
361
+ return {}
362
+ acc = sum(1 for r in rows if r["correct"]) / len(rows)
363
+ nll = sum(-math.log(max(r["p_gold"], 1e-12)) for r in rows) / len(rows)
364
+ brier = sum(r["brier"] for r in rows) / len(rows)
365
+ lat = sorted(r["latency_s"] for r in rows)
366
+ p50 = float(np.percentile(lat, 50))
367
+ p95 = float(np.percentile(lat, 95))
368
+ return {
369
+ "n": len(rows),
370
+ "accuracy": acc,
371
+ "nll": nll,
372
+ "brier": brier,
373
+ "ece": ece(rows),
374
+ "latency_p50_ms": p50 * 1000,
375
+ "latency_p95_ms": p95 * 1000,
376
+ "mean_confidence": sum(r["confidence"] for r in rows) / len(rows),
377
+ }
378
+
379
+
380
+ def run_task(task, items, decide, detail_fp, limit=None):
381
+ if limit:
382
+ items = items[:limit]
383
+ rows = []
384
+ t_task = time.perf_counter()
385
+ total = len(items)
386
+
387
+ print(f"\n{'='*72}")
388
+ print(f"{DISPLAY[task]} n={total}")
389
+ print(f"{'='*72}")
390
+
391
+ for i, ex in enumerate(items, 1):
392
+ t0 = time.perf_counter()
393
+ ps, temp = decide(ex["state"], ex["question"], ex["options"])
394
+ latency = time.perf_counter() - t0
395
+ ps = normalize_probs(ps, len(ex["options"]))
396
+
397
+ pred = int(np.argmax(ps))
398
+ gold = ex["gold"]
399
+ y = np.zeros(len(ps), dtype=float)
400
+ y[gold] = 1.0
401
+
402
+ row = {
403
+ "task": task,
404
+ "index": i - 1,
405
+ "source_id": ex["source_id"],
406
+ "gold": gold,
407
+ "pred": pred,
408
+ "correct": pred == gold,
409
+ "p_gold": float(ps[gold]),
410
+ "confidence": float(ps[pred]),
411
+ "brier": float(np.sum((ps - y) ** 2)),
412
+ "temperature": float(temp),
413
+ "latency_s": latency,
414
+ "option_count": len(ps),
415
+ "probabilities": [float(x) for x in ps],
416
+ }
417
+ rows.append(row)
418
+ detail_fp.write(json.dumps(row, ensure_ascii=False) + "\n")
419
+ detail_fp.flush()
420
+
421
+ if i == 1 or i % 100 == 0 or i == total:
422
+ acc = sum(r["correct"] for r in rows) / len(rows)
423
+ elapsed = time.perf_counter() - t_task
424
+ rate = i / elapsed if elapsed else 0
425
+ eta = (total - i) / rate if rate else 0
426
+ print(
427
+ f"[{task}] {i:>6}/{total:<6} "
428
+ f"acc={acc:.4f} "
429
+ f"last={latency*1000:.0f}ms "
430
+ f"rate={rate:.2f}/s "
431
+ f"eta={eta/60:.1f}m"
432
+ )
433
+
434
+ return rows
435
+
436
+
437
+ def write_csv(summary, path):
438
+ fields = [
439
+ "task", "display", "n", "accuracy", "nll", "brier", "ece",
440
+ "mean_confidence", "latency_p50_ms", "latency_p95_ms",
441
+ ]
442
+ with open(path, "w", newline="", encoding="utf-8") as f:
443
+ w = csv.DictWriter(f, fieldnames=fields)
444
+ w.writeheader()
445
+ for task in TASK_ORDER:
446
+ if task not in summary:
447
+ continue
448
+ r = {"task": task, "display": DISPLAY[task], **summary[task]}
449
+ w.writerow(r)
450
+
451
+
452
+ def make_radar(summary, path, label):
453
+ try:
454
+ import matplotlib.pyplot as plt
455
+ except Exception as exc:
456
+ print(f"[radar] skipped: {exc}")
457
+ return
458
+
459
+ tasks = [t for t in TASK_ORDER if t in summary]
460
+ if len(tasks) < 3:
461
+ return
462
+ labels = [DISPLAY[t] for t in tasks]
463
+ ours = [summary[t]["accuracy"] * 100 for t in tasks]
464
+ base = [V11_NF4[t]["accuracy"] * 100 for t in tasks]
465
+ angles = np.linspace(0, 2 * np.pi, len(tasks), endpoint=False).tolist()
466
+ angles += angles[:1]
467
+ fig = plt.figure(figsize=(12, 10))
468
+ ax = fig.add_subplot(111, polar=True)
469
+ ax.set_theta_offset(np.pi / 2)
470
+ ax.set_theta_direction(-1)
471
+ ax.set_ylim(0, 100)
472
+ ax.set_yticks([20, 40, 60, 80, 100])
473
+ ax.set_yticklabels(["20%", "40%", "60%", "80%", "100%"] )
474
+ ax.set_xticks(angles[:-1])
475
+ ax.set_xticklabels(labels, fontsize=10)
476
+ vals = ours + ours[:1]
477
+ ax.plot(angles, vals, linewidth=3, marker="o", label=label)
478
+ ax.fill(angles, vals, alpha=0.08)
479
+ bvals = base + base[:1]
480
+ ax.plot(angles, bvals, linewidth=2, marker=".", label="JEV-E4B v1.1 NF4 measured")
481
+ ax.set_title(f"{label} canonical public benchmark\nexact same task loaders as v1.1 NF4 baseline", pad=28, fontsize=14, fontweight="bold")
482
+ ax.legend(loc="upper center", bbox_to_anchor=(0.5, -0.12), ncol=2, fontsize=9)
483
+ fig.tight_layout()
484
+ fig.savefig(path, dpi=180, bbox_inches="tight")
485
+ plt.close(fig)
486
+
487
+
488
+ def write_comparison(summary, path, label):
489
+ out = {}
490
+ for task, cur in summary.items():
491
+ base = V11_NF4.get(task)
492
+ if not base:
493
+ continue
494
+ out[task] = {
495
+ "v1.1_nf4": base,
496
+ label: {k: cur[k] for k in ("accuracy", "nll", "ece", "brier") if k in cur},
497
+ "delta_accuracy_pp": (cur["accuracy"] - base["accuracy"]) * 100.0,
498
+ "delta_nll": cur["nll"] - base["nll"],
499
+ "delta_ece": cur["ece"] - base["ece"],
500
+ }
501
+ Path(path).write_text(json.dumps(out, indent=2), encoding="utf-8")
502
+
503
+ def parse_args():
504
+ ap = argparse.ArgumentParser()
505
+ ap.add_argument("--repo", default=str(REPO_ROOT), help="Published Hugging Face repository directory")
506
+ ap.add_argument("--load-mode", choices=("nf4", "bf16"), default="nf4")
507
+ ap.add_argument("--label", default="OpenJEV E4B 1.0 public release")
508
+ ap.add_argument(
509
+ "--tasks",
510
+ default=",".join(TASK_ORDER),
511
+ help="Comma-separated tasks",
512
+ )
513
+ ap.add_argument(
514
+ "--limit",
515
+ type=int,
516
+ default=None,
517
+ help="Optional per-task cap for smoke testing. Omit for the full benchmark.",
518
+ )
519
+ ap.add_argument("--output-root", default=None)
520
+ ap.add_argument(
521
+ "--resume-latest",
522
+ action="store_true",
523
+ help="Resume the newest prior run directory by skipping tasks already present in summary.json.",
524
+ )
525
+ return ap.parse_args()
526
+
527
+
528
+ def main():
529
+ args = parse_args()
530
+ repo_dir = Path(args.repo).resolve()
531
+ decide = load_r7_decider(repo_dir, args.load_mode)
532
+
533
+ tasks = [x.strip() for x in args.tasks.split(",") if x.strip()]
534
+ unknown = [x for x in tasks if x not in LOADERS]
535
+ if unknown:
536
+ raise SystemExit(f"Unknown tasks: {unknown}")
537
+
538
+ stamp = datetime.now().strftime("%Y%m%d-%H%M%S")
539
+ out_root = Path(args.output_root) if args.output_root else Path("results/full")
540
+ out_root.mkdir(parents=True, exist_ok=True)
541
+
542
+ out_dir = None
543
+ all_summary = {}
544
+ if args.resume_latest:
545
+ prior = sorted(
546
+ [p for p in out_root.iterdir() if p.is_dir() and (p / "summary.json").exists()],
547
+ key=lambda p: p.stat().st_mtime,
548
+ reverse=True,
549
+ )
550
+ if prior:
551
+ out_dir = prior[0]
552
+ try:
553
+ all_summary = json.loads((out_dir / "summary.json").read_text(encoding="utf-8"))
554
+ except Exception:
555
+ all_summary = {}
556
+ print(f"[resume] using prior run: {out_dir}")
557
+ print(f"[resume] completed tasks: {list(all_summary)}")
558
+
559
+ if out_dir is None:
560
+ out_dir = out_root / stamp
561
+ out_dir.mkdir(parents=True, exist_ok=True)
562
+
563
+ meta = {
564
+ "created_or_resumed_at": datetime.now().isoformat(),
565
+ "repo_dir": str(repo_dir),
566
+ "load_mode": args.load_mode,
567
+ "model_label": args.label,
568
+ "runtime": ("Gemma NF4 with adapted dense FP16 layers" if args.load_mode == "nf4"
569
+ else "Gemma BF16 with adapted BF16 layers") + "; OpenJEV 1.0 adapter and FP32 decision head",
570
+ "network_model_endpoint": None,
571
+ "backbone_delta_required": True,
572
+ "baseline": "v1.1 NF4 exact same benchmark loaders",
573
+ "tasks": tasks,
574
+ "limit_per_task": args.limit,
575
+ "gpqa_source": "JingzeShi/gpqa_diamond public 198-row mirror",
576
+ "protocol": (
577
+ "openjev public benchmark rows/splits; JEV-native state/question serialization "
578
+ "for MMLU/ARC/HellaSwag and natural JEV serialization for GPQA/WinoGrande/GSM8K/chess"
579
+ ),
580
+ }
581
+ (out_dir / "run_metadata.json").write_text(json.dumps(meta, indent=2), encoding="utf-8")
582
+
583
+ detail_path = out_dir / "details.jsonl"
584
+
585
+ with detail_path.open("a", encoding="utf-8") as detail_fp:
586
+ for task in tasks:
587
+ if task in all_summary:
588
+ print(f"\n[resume] skipping completed task: {task}")
589
+ continue
590
+ print(f"\n[dataset] loading {task} ...")
591
+ rows = LOADERS[task]()
592
+ print(f"[dataset] {task}: {len(rows)} rows")
593
+ task_rows = run_task(task, rows, decide, detail_fp, limit=args.limit)
594
+ all_summary[task] = summarize(task_rows)
595
+
596
+ # Persist after every task.
597
+ (out_dir / "summary.json").write_text(
598
+ json.dumps(all_summary, indent=2), encoding="utf-8"
599
+ )
600
+ write_csv(all_summary, out_dir / "summary.csv")
601
+ write_comparison(all_summary, out_dir / "compare_v11_nf4.json", args.label)
602
+ make_radar(all_summary, out_dir / "radar.png", args.label)
603
+
604
+ m = all_summary[task]
605
+ print(
606
+ f"\nRESULT {DISPLAY[task]}: "
607
+ f"acc={m['accuracy']*100:.2f}% "
608
+ f"nll={m['nll']:.4f} "
609
+ f"brier={m['brier']:.4f} "
610
+ f"ece={m['ece']:.4f} "
611
+ f"p50={m['latency_p50_ms']:.0f}ms "
612
+ f"p95={m['latency_p95_ms']:.0f}ms"
613
+ )
614
+
615
+ print("\n\n" + "=" * 72)
616
+ print("FINAL")
617
+ print("=" * 72)
618
+ for task in tasks:
619
+ m = all_summary[task]
620
+ dpp = (m["accuracy"] - V11_NF4[task]["accuracy"]) * 100.0
621
+ print(
622
+ f"{DISPLAY[task]:<22} "
623
+ f"{m['accuracy']*100:6.2f}% "
624
+ f"NLL {m['nll']:.4f} "
625
+ f"ECE {m['ece']:.4f} "
626
+ f"delta_vs_v11_nf4={dpp:+.2f}pp"
627
+ )
628
+ print(f"\nResults: {out_dir}")
629
+ print(f"Radar: {out_dir / 'radar.png'}")
630
+ print("No model HTTP endpoint was used; scoring ran directly on the local Windows/WSL NVIDIA GPU.")
631
+
632
+
633
+ if __name__ == "__main__":
634
+ main()
eval/harness/requirements-benchmark.txt ADDED
@@ -0,0 +1,14 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ transformers>=5.5.0,<5.7.0
2
+ datasets>=4.0.0,<5.0.0
3
+ accelerate>=1.10.0
4
+ peft>=0.17.0
5
+ huggingface_hub>=0.34.0
6
+ safetensors>=0.6.2
7
+ bitsandbytes>=0.46.0
8
+ numpy>=2.1
9
+ pyarrow>=20.0
10
+ matplotlib>=3.9
11
+ python-chess>=1.999
12
+ pillow>=11.0
13
+ sentencepiece>=0.2.0
14
+ protobuf>=5.0
eval/harness/src/__init__.py ADDED
File without changes
eval/harness/src/decision_head.py ADDED
@@ -0,0 +1,73 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ from __future__ import annotations
2
+ import math
3
+ import torch
4
+ import torch.nn.functional as F
5
+ from torch import nn
6
+
7
+
8
+ class PointerDecisionHead(nn.Module):
9
+ def __init__(self, hidden_size: int, head_dim: int = 512, normalize: bool = True):
10
+ super().__init__()
11
+ self.normalize = normalize
12
+ self.q_proj = nn.Linear(hidden_size, head_dim, bias=False)
13
+ self.k_proj = nn.Linear(hidden_size, head_dim, bias=False)
14
+ self.scale = math.sqrt(head_dim)
15
+
16
+ def forward(self, decide_h: torch.Tensor, option_h: torch.Tensor, option_mask: torch.Tensor) -> torch.Tensor:
17
+ decide_h = decide_h.float()
18
+ option_h = option_h.float()
19
+ q = self.q_proj(decide_h)
20
+ k = self.k_proj(option_h)
21
+ if self.normalize:
22
+ q = F.normalize(q, dim=-1)
23
+ k = F.normalize(k, dim=-1)
24
+ logits = torch.einsum("bd,bnd->bn", q, k) * 10.0
25
+ else:
26
+ logits = torch.einsum("bd,bnd->bn", q, k) / self.scale
27
+ return logits.masked_fill(~option_mask, -1e9)
28
+
29
+
30
+ class SpanDecisionHead(nn.Module):
31
+ """Scores the mean hidden state across each option's semantic token span."""
32
+ def __init__(self, hidden_size: int, head_dim: int = 384):
33
+ super().__init__()
34
+ self.q_proj = nn.Linear(hidden_size, head_dim, bias=False)
35
+ self.k_proj = nn.Linear(hidden_size, head_dim, bias=False)
36
+ self.mlp = nn.Sequential(
37
+ nn.Linear(head_dim * 4, head_dim),
38
+ nn.GELU(),
39
+ nn.Linear(head_dim, 1),
40
+ )
41
+
42
+ def forward(self, decide_h: torch.Tensor, option_mean_h: torch.Tensor, option_mask: torch.Tensor) -> torch.Tensor:
43
+ q = F.normalize(self.q_proj(decide_h.float()), dim=-1)
44
+ k = F.normalize(self.k_proj(option_mean_h.float()), dim=-1)
45
+ qe = q[:, None, :].expand_as(k)
46
+ x = torch.cat([qe, k, qe * k, (qe - k).abs()], dim=-1)
47
+ logits = self.mlp(x).squeeze(-1)
48
+ return logits.masked_fill(~option_mask, -1e9)
49
+
50
+
51
+ class HybridDecisionHead(nn.Module):
52
+ """Pointer baseline plus a learned residual semantic-span scorer."""
53
+ def __init__(self, hidden_size: int, head_dim: int = 384, pointer_dim: int = 384, normalize: bool = True):
54
+ super().__init__()
55
+ self.pointer = PointerDecisionHead(hidden_size, pointer_dim, normalize=normalize)
56
+ self.span = SpanDecisionHead(hidden_size, head_dim)
57
+ self.logit_scale = nn.Parameter(torch.tensor(0.0))
58
+
59
+ def forward(
60
+ self,
61
+ decide_h: torch.Tensor,
62
+ option_last_h: torch.Tensor,
63
+ option_mean_h: torch.Tensor,
64
+ option_mask: torch.Tensor,
65
+ ) -> torch.Tensor:
66
+ base = self.pointer(decide_h, option_last_h, option_mask)
67
+ residual = self.span(decide_h, option_mean_h, option_mask)
68
+ return base + self.logit_scale.exp().clamp(max=4.0) * residual
69
+
70
+
71
+ def brier_loss(probs: torch.Tensor, targets: torch.Tensor, mask: torch.Tensor) -> torch.Tensor:
72
+ diff = (probs - targets).pow(2) * mask.float()
73
+ return diff.sum(dim=-1).mean()
eval/harness/src/formatting.py ADDED
@@ -0,0 +1,211 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ from __future__ import annotations
2
+ import json
3
+ import torch
4
+
5
+
6
+ def encode_piece(tok, text: str) -> list[int]:
7
+ return tok(text, add_special_tokens=False).input_ids
8
+
9
+
10
+ def state_to_text(state) -> str:
11
+ if isinstance(state, str):
12
+ return state
13
+ return json.dumps(state, ensure_ascii=False, sort_keys=True, separators=(",", ":"))
14
+
15
+
16
+ def _clip_head_tail(ids: list[int], limit: int) -> list[int]:
17
+ """Deterministically keep evidence from both ends of a long field."""
18
+ if limit <= 0:
19
+ return []
20
+ if len(ids) <= limit:
21
+ return ids
22
+ head = (limit + 1) // 2
23
+ tail = limit - head
24
+ return ids[:head] + (ids[-tail:] if tail else [])
25
+
26
+
27
+ def _assemble(tok, state_ids, q_ids, option_ids, max_length: int):
28
+ ids = [tok.bos_token_id] if tok.bos_token_id is not None else []
29
+ ids += encode_piece(tok, "State:\n")
30
+ ids += state_ids
31
+ ids += encode_piece(tok, "\n\nQuestion:\n")
32
+ ids += q_ids
33
+ ids += encode_piece(tok, "\n\nOptions:\n")
34
+
35
+ option_positions = []
36
+ option_spans = []
37
+ for idx, opt_ids in enumerate(option_ids):
38
+ ids += encode_piece(tok, f"- [{idx}] ")
39
+ span_start = len(ids)
40
+ ids += opt_ids
41
+ span_end = len(ids)
42
+ option_spans.append((span_start, span_end))
43
+ # Represent each option by its final semantic token, after it has seen
44
+ # the full option text but before the newline delimiter.
45
+ option_positions.append(len(ids) - 1)
46
+ ids += encode_piece(tok, "\n")
47
+
48
+ ids += encode_piece(tok, "\nDecision:")
49
+ decide_position = len(ids) - 1
50
+ if len(ids) > max_length:
51
+ return None
52
+ return ids, option_positions, decide_position, option_spans
53
+
54
+
55
+ def pack_question(tok, state, question: dict, max_length: int):
56
+ """Pack a decision example without dropping long states.
57
+
58
+ Priority order is structural markers/options/question first, then state.
59
+ Long state is head+tail truncated. If question/options themselves are huge,
60
+ they are bounded as a second-stage fallback. Returning None is reserved for
61
+ structurally impossible cases (e.g. too many options for max_length).
62
+ """
63
+ if max_length < 32:
64
+ return None
65
+
66
+ state_ids = encode_piece(tok, state_to_text(state))
67
+ q_ids = encode_piece(tok, str(question["instruction"]))
68
+ option_ids = [encode_piece(tok, str(opt["text"])) for opt in question["options"]]
69
+ if not option_ids or any(not x for x in option_ids):
70
+ return None
71
+
72
+ was_truncated = False
73
+
74
+ # First discover how much room remains for the state while preserving the
75
+ # complete question and option text.
76
+ probe = _assemble(tok, [], q_ids, option_ids, max_length=10**9)
77
+ if probe is None:
78
+ return None
79
+ structural_len = len(probe[0])
80
+ if structural_len <= max_length:
81
+ state_budget = max_length - structural_len
82
+ was_truncated = len(state_ids) > state_budget
83
+ packed = _assemble(tok, _clip_head_tail(state_ids, state_budget), q_ids, option_ids, max_length)
84
+ else:
85
+ was_truncated = True
86
+ # Extremely verbose question/tool schemas: cap semantic fields rather
87
+ # than dropping the sample. Typical training examples never hit this.
88
+ nopt = len(option_ids)
89
+ q_cap = min(len(q_ids), max(16, max_length // 8))
90
+ # Start modestly; then shrink until the structural representation fits.
91
+ opt_cap = max(8, min(96, max_length // max(8, nopt * 2)))
92
+ q_fit = _clip_head_tail(q_ids, q_cap)
93
+ opts_fit = [_clip_head_tail(x, opt_cap) for x in option_ids]
94
+ packed = _assemble(tok, [], q_fit, opts_fit, max_length)
95
+ while packed is None and (q_cap > 8 or opt_cap > 4):
96
+ q_cap = max(8, q_cap // 2)
97
+ opt_cap = max(4, opt_cap // 2)
98
+ q_fit = _clip_head_tail(q_ids, q_cap)
99
+ opts_fit = [_clip_head_tail(x, opt_cap) for x in option_ids]
100
+ packed = _assemble(tok, [], q_fit, opts_fit, max_length)
101
+
102
+ if packed is not None:
103
+ # If the shrunken question/options leave room, fill it with state.
104
+ base_len = len(packed[0])
105
+ state_budget = max(0, max_length - base_len)
106
+ was_truncated = was_truncated or len(state_ids) > state_budget
107
+ packed = _assemble(tok, _clip_head_tail(state_ids, state_budget), q_fit, opts_fit, max_length)
108
+
109
+ if packed is None:
110
+ return None
111
+ ids, option_positions, decide_position, option_spans = packed
112
+ return {
113
+ "input_ids": ids,
114
+ "option_positions": option_positions,
115
+ "option_spans": option_spans,
116
+ "decide_position": decide_position,
117
+ "target": question.get("target_distribution"),
118
+ "was_truncated": was_truncated,
119
+ }
120
+
121
+
122
+ def collate_packed(tok, packed: list[dict]) -> dict[str, torch.Tensor]:
123
+ if not packed:
124
+ raise RuntimeError("No packable examples")
125
+ bsz = len(packed)
126
+ max_seq = max(len(x["input_ids"]) for x in packed)
127
+ max_opts = max(len(x["option_positions"]) for x in packed)
128
+ pad_id = tok.pad_token_id if tok.pad_token_id is not None else 0
129
+ input_ids = torch.full((bsz, max_seq), pad_id, dtype=torch.long)
130
+ attention_mask = torch.zeros((bsz, max_seq), dtype=torch.long)
131
+ option_positions = torch.zeros((bsz, max_opts), dtype=torch.long)
132
+ option_mask = torch.zeros((bsz, max_opts), dtype=torch.bool)
133
+ decide_positions = torch.zeros((bsz,), dtype=torch.long)
134
+ targets = torch.zeros((bsz, max_opts), dtype=torch.float32)
135
+ option_starts = torch.zeros((bsz, max_opts), dtype=torch.long)
136
+ option_ends = torch.zeros((bsz, max_opts), dtype=torch.long)
137
+ has_targets = all(x.get("target") is not None for x in packed)
138
+ for i, x in enumerate(packed):
139
+ n = len(x["input_ids"])
140
+ m = len(x["option_positions"])
141
+ input_ids[i, :n] = torch.tensor(x["input_ids"], dtype=torch.long)
142
+ attention_mask[i, :n] = 1
143
+ option_positions[i, :m] = torch.tensor(x["option_positions"], dtype=torch.long)
144
+ spans = x.get("option_spans") or [(int(v), int(v)+1) for v in x["option_positions"]]
145
+ option_starts[i, :m] = torch.tensor([a for a, _ in spans], dtype=torch.long)
146
+ option_ends[i, :m] = torch.tensor([b for _, b in spans], dtype=torch.long)
147
+ option_mask[i, :m] = True
148
+ decide_positions[i] = x["decide_position"]
149
+ if has_targets:
150
+ targets[i, :m] = torch.tensor(x["target"], dtype=torch.float32)
151
+ out = {
152
+ "input_ids": input_ids,
153
+ "attention_mask": attention_mask,
154
+ "option_positions": option_positions,
155
+ "option_starts": option_starts,
156
+ "option_ends": option_ends,
157
+ "option_mask": option_mask,
158
+ "decide_positions": decide_positions,
159
+ }
160
+ if has_targets:
161
+ out["targets"] = targets
162
+ return out
163
+
164
+
165
+ def pack_shared_request(tok, state, questions: list[dict], max_length: int):
166
+ """Pack one common state prefix plus causal question suffixes.
167
+
168
+ Every question sees exactly the same serialized/clipped state. This is the
169
+ representation needed for safe KV-prefix reuse at inference time.
170
+ """
171
+ if max_length < 32 or not questions:
172
+ return None
173
+ bos = [tok.bos_token_id] if tok.bos_token_id is not None else []
174
+ state_marker = encode_piece(tok, "State:\n")
175
+ state_ids = encode_piece(tok, state_to_text(state))
176
+
177
+ suffixes = []
178
+ max_suffix_len = 0
179
+ for question in questions:
180
+ q_ids = encode_piece(tok, str(question["instruction"]))
181
+ option_ids = [encode_piece(tok, str(opt["text"])) for opt in question["options"]]
182
+ if not option_ids or any(not x for x in option_ids):
183
+ return None
184
+ ids = encode_piece(tok, "\n\nQuestion:\n") + q_ids + encode_piece(tok, "\n\nOptions:\n")
185
+ option_positions = []
186
+ for idx, opt_ids in enumerate(option_ids):
187
+ ids += encode_piece(tok, f"- [{idx}] ")
188
+ ids += opt_ids
189
+ option_positions.append(len(ids) - 1)
190
+ ids += encode_piece(tok, "\n")
191
+ ids += encode_piece(tok, "\nDecision:")
192
+ decide_position = len(ids) - 1
193
+ suffixes.append({
194
+ "input_ids": ids,
195
+ "option_positions": option_positions,
196
+ "decide_position": decide_position,
197
+ })
198
+ max_suffix_len = max(max_suffix_len, len(ids))
199
+
200
+ fixed_prefix_len = len(bos) + len(state_marker)
201
+ state_budget = max_length - fixed_prefix_len - max_suffix_len
202
+ if state_budget < 0:
203
+ return None
204
+ clipped = _clip_head_tail(state_ids, state_budget)
205
+ was_truncated = len(clipped) < len(state_ids)
206
+ prefix_ids = bos + state_marker + clipped
207
+ for s in suffixes:
208
+ s["was_truncated"] = was_truncated
209
+ if len(prefix_ids) + len(s["input_ids"]) > max_length:
210
+ return None
211
+ return {"prefix_ids": prefix_ids, "suffixes": suffixes, "was_truncated": was_truncated}
eval/harness/src/metrics.py ADDED
@@ -0,0 +1,223 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ from __future__ import annotations
2
+
3
+ import math
4
+ from collections import defaultdict
5
+
6
+ import torch
7
+
8
+
9
+ def ece(conf: list[float], correct: list[int], bins: int = 15) -> float | None:
10
+ if not conf:
11
+ return None
12
+ score = 0.0
13
+ total = len(conf)
14
+ for b in range(bins):
15
+ lo = b / bins
16
+ hi = (b + 1) / bins
17
+ ids = [i for i, x in enumerate(conf) if x >= lo and (x < hi or (b == bins - 1 and x <= hi))]
18
+ if not ids:
19
+ continue
20
+ acc = sum(correct[i] for i in ids) / len(ids)
21
+ cf = sum(conf[i] for i in ids) / len(ids)
22
+ score += len(ids) / total * abs(acc - cf)
23
+ return score
24
+
25
+
26
+ def option_bucket(n: int) -> str:
27
+ if n <= 2:
28
+ return "2"
29
+ if n <= 5:
30
+ return "3-5"
31
+ if n <= 10:
32
+ return "6-10"
33
+ return ">10"
34
+
35
+
36
+ def calibration_group(row: dict) -> str:
37
+ """Architecture-level calibration group, intentionally not dataset-specific."""
38
+ kind = str(row.get("kind", "choice"))
39
+ if kind == "choice":
40
+ return f"choice:{option_bucket(int(row.get('option_count', len(row.get('target', [])))))}"
41
+ return f"{kind}:*"
42
+
43
+
44
+ def temperature_for_row(calibration, row: dict) -> float:
45
+ if isinstance(calibration, (int, float)):
46
+ return max(0.05, min(20.0, float(calibration)))
47
+ if not isinstance(calibration, dict):
48
+ return 1.0
49
+ if "temperature" in calibration and "groups" not in calibration and "global_temperature" not in calibration:
50
+ return max(0.05, min(20.0, float(calibration.get("temperature", 1.0))))
51
+ global_t = float(calibration.get("global_temperature", calibration.get("temperature", 1.0)))
52
+ group = calibration_group(row)
53
+ groups = calibration.get("groups") or {}
54
+ entry = groups.get(group)
55
+ if isinstance(entry, dict):
56
+ t = float(entry.get("temperature", global_t))
57
+ elif entry is not None:
58
+ t = float(entry)
59
+ else:
60
+ t = global_t
61
+ return max(0.05, min(20.0, t))
62
+
63
+
64
+ def _score_subset(rows: list[dict], calibration=1.0) -> dict:
65
+ if not rows:
66
+ return {"n": 0, "accuracy": None, "nll": None, "brier": None, "ece": None, "mean_confidence": None}
67
+ conf: list[float] = []
68
+ correct: list[int] = []
69
+ nll: list[float] = []
70
+ brier: list[float] = []
71
+ for r in rows:
72
+ T = temperature_for_row(calibration, r)
73
+ logits = torch.tensor(r["logits"], dtype=torch.float32) / T
74
+ target = torch.tensor(r["target"], dtype=torch.float32)
75
+ probs = torch.softmax(logits, dim=-1)
76
+ pred = int(probs.argmax())
77
+ gold = int(target.argmax())
78
+ cf = float(probs[pred])
79
+ conf.append(cf)
80
+ correct.append(int(pred == gold))
81
+ nll.append(float(-(target * torch.log(probs.clamp_min(1e-9))).sum()))
82
+ brier.append(float(((probs - target) ** 2).sum()))
83
+ return {
84
+ "n": len(rows),
85
+ "accuracy": sum(correct) / len(correct),
86
+ "nll": sum(nll) / len(nll),
87
+ "brier": sum(brier) / len(brier),
88
+ "ece": ece(conf, correct),
89
+ "mean_confidence": sum(conf) / len(conf),
90
+ }
91
+
92
+
93
+ def score_rows(rows: list[dict], calibration=1.0, grouped: bool = True) -> dict:
94
+ out = _score_subset(rows, calibration)
95
+ if isinstance(calibration, (int, float)):
96
+ out["temperature"] = float(calibration)
97
+ elif isinstance(calibration, dict):
98
+ out["calibration"] = calibration
99
+ if not grouped:
100
+ return out
101
+
102
+ group_specs = {
103
+ "by_dataset": lambda r: r.get("dataset", "unknown"),
104
+ "by_kind": lambda r: r.get("kind", "choice"),
105
+ "by_transform": lambda r: r.get("transform", "unknown"),
106
+ "by_option_bucket": lambda r: option_bucket(int(r.get("option_count", len(r.get("target", []))))),
107
+ "by_truncation": lambda r: "truncated" if r.get("truncated", False) else "not_truncated",
108
+ "by_dataset_truncation": lambda r: f"{r.get('dataset','unknown')}|{'truncated' if r.get('truncated',False) else 'not_truncated'}",
109
+ "by_kind_truncation": lambda r: f"{r.get('kind','choice')}|{'truncated' if r.get('truncated',False) else 'not_truncated'}",
110
+ }
111
+ for out_key, key_fn in group_specs.items():
112
+ groups: dict[str, list[dict]] = defaultdict(list)
113
+ for r in rows:
114
+ groups[str(key_fn(r))].append(r)
115
+ out[out_key] = {k: _score_subset(v, calibration) for k, v in sorted(groups.items())}
116
+
117
+ # Fixed-label NLI confusion is especially useful in v1.3 because the
118
+ # production options are shuffled while the auxiliary semantic labels are
119
+ # always entailment/neutral/contradiction.
120
+ labels = ["entailment", "neutral", "contradiction"]
121
+ matrix = {g: {p: 0 for p in labels} for g in labels}
122
+ n_nli = 0
123
+ for r in rows:
124
+ gold_idx = int(r.get("aux_nli_label", -1))
125
+ ids = list(r.get("option_ids") or [])
126
+ if gold_idx < 0 or gold_idx >= 3 or not ids:
127
+ continue
128
+ T = temperature_for_row(calibration, r)
129
+ probs = torch.softmax(torch.tensor(r["logits"], dtype=torch.float32) / T, dim=-1)
130
+ pred_i = int(probs.argmax())
131
+ pred_id = str(ids[pred_i]) if pred_i < len(ids) else ""
132
+ if pred_id not in labels:
133
+ continue
134
+ matrix[labels[gold_idx]][pred_id] += 1
135
+ n_nli += 1
136
+ if n_nli:
137
+ out["nli_confusion"] = {"n": n_nli, "labels": labels, "matrix": matrix}
138
+ return out
139
+
140
+
141
+ def fit_temperature(rows: list[dict], max_iter: int = 50) -> float:
142
+ if not rows:
143
+ return 1.0
144
+ log_t = torch.tensor(0.0, requires_grad=True)
145
+ opt = torch.optim.LBFGS([log_t], lr=0.2, max_iter=max_iter, line_search_fn="strong_wolfe")
146
+
147
+ def closure():
148
+ opt.zero_grad()
149
+ T = log_t.exp().clamp(0.05, 20.0)
150
+ total = torch.tensor(0.0)
151
+ for r in rows:
152
+ logits = torch.tensor(r["logits"], dtype=torch.float32)
153
+ target = torch.tensor(r["target"], dtype=torch.float32)
154
+ total = total - (target * torch.log_softmax(logits / T, dim=-1)).sum()
155
+ loss = total / max(1, len(rows))
156
+ loss.backward()
157
+ return loss
158
+
159
+ opt.step(closure)
160
+ return float(log_t.exp().clamp(0.05, 20.0).detach())
161
+
162
+
163
+ def fit_calibration_policy(
164
+ rows: list[dict],
165
+ min_group: int = 40,
166
+ prior_strength: float = 100.0,
167
+ max_iter: int = 50,
168
+ ) -> dict:
169
+ """Fit shrinkage temperatures by primitive/option-count.
170
+
171
+ Dataset-specific temperatures are intentionally avoided. Small groups shrink
172
+ in log-temperature space toward the global fit to reduce calibration overfit.
173
+ """
174
+ global_t = fit_temperature(rows, max_iter=max_iter)
175
+ grouped: dict[str, list[dict]] = defaultdict(list)
176
+ for row in rows:
177
+ grouped[calibration_group(row)].append(row)
178
+ groups: dict[str, dict] = {}
179
+ for key, subset in sorted(grouped.items()):
180
+ n = len(subset)
181
+ if n < min_group:
182
+ continue
183
+ raw_t = fit_temperature(subset, max_iter=max_iter)
184
+ w = n / (n + float(prior_strength))
185
+ shrunk = math.exp(w * math.log(raw_t) + (1.0 - w) * math.log(global_t))
186
+ groups[key] = {
187
+ "temperature": float(max(0.05, min(20.0, shrunk))),
188
+ "raw_temperature": float(raw_t),
189
+ "n": n,
190
+ "shrinkage_weight": w,
191
+ }
192
+ return {
193
+ "version": 2,
194
+ "type": "primitive_option_temperature",
195
+ "global_temperature": float(global_t),
196
+ "groups": groups,
197
+ "min_group": int(min_group),
198
+ "prior_strength": float(prior_strength),
199
+ "n": len(rows),
200
+ }
201
+
202
+
203
+ def qualification_value(metrics: dict, metric: str) -> float:
204
+ """Return a lower-is-better scalar for checkpoint selection."""
205
+ if metric in {"calibrated_nll", "policy_nll"}:
206
+ return float(metrics["calibrated"]["nll"])
207
+ if metric in {"macro_dataset_calibrated_nll", "macro_dataset_policy_nll"}:
208
+ vals = [float(x["nll"]) for x in metrics["calibrated"].get("by_dataset", {}).values() if x.get("nll") is not None]
209
+ if not vals:
210
+ return float(metrics["calibrated"]["nll"])
211
+ return sum(vals) / len(vals)
212
+ if metric in {"macro_kind_calibrated_nll", "macro_kind_policy_nll"}:
213
+ vals = [float(x["nll"]) for x in metrics["calibrated"].get("by_kind", {}).values() if x.get("nll") is not None]
214
+ if not vals:
215
+ return float(metrics["calibrated"]["nll"])
216
+ return sum(vals) / len(vals)
217
+ if metric in {"calibrated_brier", "policy_brier"}:
218
+ return float(metrics["calibrated"]["brier"])
219
+ if metric == "raw_nll":
220
+ return float(metrics["raw"]["nll"])
221
+ if metric == "raw_brier":
222
+ return float(metrics["raw"]["brier"])
223
+ raise ValueError(f"Unknown selection metric: {metric}")
eval/multimodal_smoke/lines.tsv ADDED
@@ -0,0 +1,14 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ q_billing_1 billing I was charged twice for my subscription this month.
2
+ q_billing_2 billing Can I get a refund for the invoice I paid yesterday?
3
+ q_access_1 account access I forgot my password and the reset email never arrives.
4
+ q_access_2 account access My account is locked after too many login attempts.
5
+ q_tech_1 technical support The app crashes every time I open the settings page.
6
+ q_tech_2 technical support The website shows an error when I upload a file.
7
+ q_sales_1 sales I would like a price quote for fifty team licenses.
8
+ q_sales_2 sales Do you offer a discount for annual enterprise plans?
9
+ s_pos_1 positive I absolutely love this phone, the camera is amazing.
10
+ s_pos_2 positive Great service, the delivery arrived early and everything works.
11
+ s_pos_3 positive This is the best purchase I have made all year.
12
+ s_neg_1 negative The battery died after two days and support never replied.
13
+ s_neg_2 negative Terrible experience, the product broke on the first use.
14
+ s_neg_3 negative I want my money back, this was a complete waste.
eval/multimodal_smoke/make_audio_windows.ps1 ADDED
@@ -0,0 +1,17 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # Speak every line of lines.tsv with the two built-in Windows voices (16 kHz, 16-bit, mono).
2
+ # Usage: powershell -ExecutionPolicy Bypass -File make_audio_windows.ps1 -Out .\work\audio
3
+ param([string]$Out = (Join-Path $PSScriptRoot "work\audio"))
4
+ Add-Type -AssemblyName System.Speech
5
+ New-Item -ItemType Directory -Force $Out | Out-Null
6
+ $fmt = New-Object System.Speech.AudioFormat.SpeechAudioFormatInfo(16000, [System.Speech.AudioFormat.AudioBitsPerSample]::Sixteen, [System.Speech.AudioFormat.AudioChannel]::Mono)
7
+ $s = New-Object System.Speech.Synthesis.SpeechSynthesizer
8
+ $n = 0
9
+ foreach ($voice in @('Microsoft David Desktop', 'Microsoft Zira Desktop')) {
10
+ $s.SelectVoice($voice); $tag = $voice.Split(' ')[1].ToLower()
11
+ foreach ($line in Get-Content -LiteralPath (Join-Path $PSScriptRoot 'lines.tsv')) {
12
+ $parts = $line.Split("`t")
13
+ $s.SetOutputToWaveFile((Join-Path $Out ($parts[0] + "-" + $tag + ".wav")), $fmt)
14
+ $s.Speak($parts[2]); $n++
15
+ }
16
+ }
17
+ $s.SetOutputToNull(); $s.Dispose(); "wrote $n clips to $Out"
eval/multimodal_smoke/results.json ADDED
@@ -0,0 +1,1044 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ {
2
+ "release": "OpenJEV E4B 1.0 (internal v1.6 R7)",
3
+ "load_mode": "nf4",
4
+ "device": "NVIDIA GeForce RTX 3060 12 GB",
5
+ "date": "2026-09-27",
6
+ "text_vs_multimodal_path_max_prob_diff": 0.0,
7
+ "peak_vram_gib": 10.73,
8
+ "suites": {
9
+ "image: shape (3-way)": {
10
+ "n": 12,
11
+ "correct": 12,
12
+ "accuracy": 1.0,
13
+ "median_ms": 4881
14
+ },
15
+ "image: color (4-way)": {
16
+ "n": 12,
17
+ "correct": 12,
18
+ "accuracy": 1.0,
19
+ "median_ms": 4738
20
+ },
21
+ "control, no image: shape": {
22
+ "n": 12,
23
+ "correct": 4,
24
+ "accuracy": 0.3333333333333333,
25
+ "median_ms": 191
26
+ },
27
+ "control, no image: color": {
28
+ "n": 12,
29
+ "correct": 3,
30
+ "accuracy": 0.25,
31
+ "median_ms": 202
32
+ },
33
+ "image: printed message -> queue (4-way)": {
34
+ "n": 8,
35
+ "correct": 8,
36
+ "accuracy": 1.0,
37
+ "median_ms": 5284
38
+ },
39
+ "control, no image: queue": {
40
+ "n": 8,
41
+ "correct": 2,
42
+ "accuracy": 0.25,
43
+ "median_ms": 189
44
+ },
45
+ "reference, same message as text: queue": {
46
+ "n": 8,
47
+ "correct": 7,
48
+ "accuracy": 0.875,
49
+ "median_ms": 196
50
+ },
51
+ "audio: spoken message -> queue (4-way)": {
52
+ "n": 16,
53
+ "correct": 16,
54
+ "accuracy": 1.0,
55
+ "median_ms": 2262
56
+ },
57
+ "control, no audio: queue": {
58
+ "n": 8,
59
+ "correct": 2,
60
+ "accuracy": 0.25,
61
+ "median_ms": 190
62
+ },
63
+ "audio: spoken review -> sentiment (2-way)": {
64
+ "n": 12,
65
+ "correct": 12,
66
+ "accuracy": 1.0,
67
+ "median_ms": 2424
68
+ },
69
+ "control, no audio: sentiment": {
70
+ "n": 6,
71
+ "correct": 3,
72
+ "accuracy": 0.5,
73
+ "median_ms": 190
74
+ },
75
+ "reference, same review as text: sentiment": {
76
+ "n": 6,
77
+ "correct": 6,
78
+ "accuracy": 1.0,
79
+ "median_ms": 192
80
+ }
81
+ },
82
+ "rows": [
83
+ {
84
+ "suite": "image: shape (3-way)",
85
+ "kind": "image",
86
+ "gold": "square",
87
+ "pred": "square",
88
+ "p_gold": 0.9991449117660522,
89
+ "ms": 4963
90
+ },
91
+ {
92
+ "suite": "image: color (4-way)",
93
+ "kind": "image",
94
+ "gold": "red",
95
+ "pred": "red",
96
+ "p_gold": 0.999085545539856,
97
+ "ms": 4712
98
+ },
99
+ {
100
+ "suite": "control, no image: shape",
101
+ "kind": "none",
102
+ "gold": "square",
103
+ "pred": "circle",
104
+ "p_gold": 0.3252781629562378,
105
+ "ms": 191
106
+ },
107
+ {
108
+ "suite": "control, no image: color",
109
+ "kind": "none",
110
+ "gold": "red",
111
+ "pred": "red",
112
+ "p_gold": 0.34349796175956726,
113
+ "ms": 223
114
+ },
115
+ {
116
+ "suite": "image: shape (3-way)",
117
+ "kind": "image",
118
+ "gold": "square",
119
+ "pred": "square",
120
+ "p_gold": 0.9993372559547424,
121
+ "ms": 4881
122
+ },
123
+ {
124
+ "suite": "image: color (4-way)",
125
+ "kind": "image",
126
+ "gold": "blue",
127
+ "pred": "blue",
128
+ "p_gold": 0.9998289346694946,
129
+ "ms": 4711
130
+ },
131
+ {
132
+ "suite": "control, no image: shape",
133
+ "kind": "none",
134
+ "gold": "square",
135
+ "pred": "circle",
136
+ "p_gold": 0.3252781629562378,
137
+ "ms": 207
138
+ },
139
+ {
140
+ "suite": "control, no image: color",
141
+ "kind": "none",
142
+ "gold": "blue",
143
+ "pred": "red",
144
+ "p_gold": 0.22613903880119324,
145
+ "ms": 191
146
+ },
147
+ {
148
+ "suite": "image: shape (3-way)",
149
+ "kind": "image",
150
+ "gold": "square",
151
+ "pred": "square",
152
+ "p_gold": 0.9991328120231628,
153
+ "ms": 4809
154
+ },
155
+ {
156
+ "suite": "image: color (4-way)",
157
+ "kind": "image",
158
+ "gold": "green",
159
+ "pred": "green",
160
+ "p_gold": 0.9999126195907593,
161
+ "ms": 4725
162
+ },
163
+ {
164
+ "suite": "control, no image: shape",
165
+ "kind": "none",
166
+ "gold": "square",
167
+ "pred": "circle",
168
+ "p_gold": 0.3252781629562378,
169
+ "ms": 186
170
+ },
171
+ {
172
+ "suite": "control, no image: color",
173
+ "kind": "none",
174
+ "gold": "green",
175
+ "pred": "red",
176
+ "p_gold": 0.22800041735172272,
177
+ "ms": 180
178
+ },
179
+ {
180
+ "suite": "image: shape (3-way)",
181
+ "kind": "image",
182
+ "gold": "square",
183
+ "pred": "square",
184
+ "p_gold": 0.999433696269989,
185
+ "ms": 4700
186
+ },
187
+ {
188
+ "suite": "image: color (4-way)",
189
+ "kind": "image",
190
+ "gold": "yellow",
191
+ "pred": "yellow",
192
+ "p_gold": 0.9988981485366821,
193
+ "ms": 4706
194
+ },
195
+ {
196
+ "suite": "control, no image: shape",
197
+ "kind": "none",
198
+ "gold": "square",
199
+ "pred": "circle",
200
+ "p_gold": 0.3252781629562378,
201
+ "ms": 181
202
+ },
203
+ {
204
+ "suite": "control, no image: color",
205
+ "kind": "none",
206
+ "gold": "yellow",
207
+ "pred": "red",
208
+ "p_gold": 0.20236249268054962,
209
+ "ms": 200
210
+ },
211
+ {
212
+ "suite": "image: shape (3-way)",
213
+ "kind": "image",
214
+ "gold": "circle",
215
+ "pred": "circle",
216
+ "p_gold": 0.9994489550590515,
217
+ "ms": 4697
218
+ },
219
+ {
220
+ "suite": "image: color (4-way)",
221
+ "kind": "image",
222
+ "gold": "red",
223
+ "pred": "red",
224
+ "p_gold": 0.998988687992096,
225
+ "ms": 4705
226
+ },
227
+ {
228
+ "suite": "control, no image: shape",
229
+ "kind": "none",
230
+ "gold": "circle",
231
+ "pred": "circle",
232
+ "p_gold": 0.451766699552536,
233
+ "ms": 189
234
+ },
235
+ {
236
+ "suite": "control, no image: color",
237
+ "kind": "none",
238
+ "gold": "red",
239
+ "pred": "red",
240
+ "p_gold": 0.34349796175956726,
241
+ "ms": 190
242
+ },
243
+ {
244
+ "suite": "image: shape (3-way)",
245
+ "kind": "image",
246
+ "gold": "circle",
247
+ "pred": "circle",
248
+ "p_gold": 0.9999669790267944,
249
+ "ms": 4698
250
+ },
251
+ {
252
+ "suite": "image: color (4-way)",
253
+ "kind": "image",
254
+ "gold": "blue",
255
+ "pred": "blue",
256
+ "p_gold": 0.9999053478240967,
257
+ "ms": 4738
258
+ },
259
+ {
260
+ "suite": "control, no image: shape",
261
+ "kind": "none",
262
+ "gold": "circle",
263
+ "pred": "circle",
264
+ "p_gold": 0.451766699552536,
265
+ "ms": 211
266
+ },
267
+ {
268
+ "suite": "control, no image: color",
269
+ "kind": "none",
270
+ "gold": "blue",
271
+ "pred": "red",
272
+ "p_gold": 0.22613903880119324,
273
+ "ms": 179
274
+ },
275
+ {
276
+ "suite": "image: shape (3-way)",
277
+ "kind": "image",
278
+ "gold": "circle",
279
+ "pred": "circle",
280
+ "p_gold": 0.9999372959136963,
281
+ "ms": 4692
282
+ },
283
+ {
284
+ "suite": "image: color (4-way)",
285
+ "kind": "image",
286
+ "gold": "green",
287
+ "pred": "green",
288
+ "p_gold": 0.9998636245727539,
289
+ "ms": 4783
290
+ },
291
+ {
292
+ "suite": "control, no image: shape",
293
+ "kind": "none",
294
+ "gold": "circle",
295
+ "pred": "circle",
296
+ "p_gold": 0.451766699552536,
297
+ "ms": 216
298
+ },
299
+ {
300
+ "suite": "control, no image: color",
301
+ "kind": "none",
302
+ "gold": "green",
303
+ "pred": "red",
304
+ "p_gold": 0.22800041735172272,
305
+ "ms": 202
306
+ },
307
+ {
308
+ "suite": "image: shape (3-way)",
309
+ "kind": "image",
310
+ "gold": "circle",
311
+ "pred": "circle",
312
+ "p_gold": 0.9997873902320862,
313
+ "ms": 4726
314
+ },
315
+ {
316
+ "suite": "image: color (4-way)",
317
+ "kind": "image",
318
+ "gold": "yellow",
319
+ "pred": "yellow",
320
+ "p_gold": 0.9992806315422058,
321
+ "ms": 4724
322
+ },
323
+ {
324
+ "suite": "control, no image: shape",
325
+ "kind": "none",
326
+ "gold": "circle",
327
+ "pred": "circle",
328
+ "p_gold": 0.451766699552536,
329
+ "ms": 185
330
+ },
331
+ {
332
+ "suite": "control, no image: color",
333
+ "kind": "none",
334
+ "gold": "yellow",
335
+ "pred": "red",
336
+ "p_gold": 0.20236249268054962,
337
+ "ms": 202
338
+ },
339
+ {
340
+ "suite": "image: shape (3-way)",
341
+ "kind": "image",
342
+ "gold": "triangle",
343
+ "pred": "triangle",
344
+ "p_gold": 0.9999802112579346,
345
+ "ms": 4963
346
+ },
347
+ {
348
+ "suite": "image: color (4-way)",
349
+ "kind": "image",
350
+ "gold": "red",
351
+ "pred": "red",
352
+ "p_gold": 0.9987249970436096,
353
+ "ms": 4991
354
+ },
355
+ {
356
+ "suite": "control, no image: shape",
357
+ "kind": "none",
358
+ "gold": "triangle",
359
+ "pred": "circle",
360
+ "p_gold": 0.22295516729354858,
361
+ "ms": 251
362
+ },
363
+ {
364
+ "suite": "control, no image: color",
365
+ "kind": "none",
366
+ "gold": "red",
367
+ "pred": "red",
368
+ "p_gold": 0.34349796175956726,
369
+ "ms": 285
370
+ },
371
+ {
372
+ "suite": "image: shape (3-way)",
373
+ "kind": "image",
374
+ "gold": "triangle",
375
+ "pred": "triangle",
376
+ "p_gold": 0.9999686479568481,
377
+ "ms": 6177
378
+ },
379
+ {
380
+ "suite": "image: color (4-way)",
381
+ "kind": "image",
382
+ "gold": "blue",
383
+ "pred": "blue",
384
+ "p_gold": 0.9998495578765869,
385
+ "ms": 5227
386
+ },
387
+ {
388
+ "suite": "control, no image: shape",
389
+ "kind": "none",
390
+ "gold": "triangle",
391
+ "pred": "circle",
392
+ "p_gold": 0.22295516729354858,
393
+ "ms": 205
394
+ },
395
+ {
396
+ "suite": "control, no image: color",
397
+ "kind": "none",
398
+ "gold": "blue",
399
+ "pred": "red",
400
+ "p_gold": 0.22613903880119324,
401
+ "ms": 220
402
+ },
403
+ {
404
+ "suite": "image: shape (3-way)",
405
+ "kind": "image",
406
+ "gold": "triangle",
407
+ "pred": "triangle",
408
+ "p_gold": 0.9999749660491943,
409
+ "ms": 5371
410
+ },
411
+ {
412
+ "suite": "image: color (4-way)",
413
+ "kind": "image",
414
+ "gold": "green",
415
+ "pred": "green",
416
+ "p_gold": 0.9999052286148071,
417
+ "ms": 4874
418
+ },
419
+ {
420
+ "suite": "control, no image: shape",
421
+ "kind": "none",
422
+ "gold": "triangle",
423
+ "pred": "circle",
424
+ "p_gold": 0.22295516729354858,
425
+ "ms": 190
426
+ },
427
+ {
428
+ "suite": "control, no image: color",
429
+ "kind": "none",
430
+ "gold": "green",
431
+ "pred": "red",
432
+ "p_gold": 0.22800041735172272,
433
+ "ms": 208
434
+ },
435
+ {
436
+ "suite": "image: shape (3-way)",
437
+ "kind": "image",
438
+ "gold": "triangle",
439
+ "pred": "triangle",
440
+ "p_gold": 0.9999715089797974,
441
+ "ms": 5469
442
+ },
443
+ {
444
+ "suite": "image: color (4-way)",
445
+ "kind": "image",
446
+ "gold": "yellow",
447
+ "pred": "yellow",
448
+ "p_gold": 0.9982457160949707,
449
+ "ms": 5095
450
+ },
451
+ {
452
+ "suite": "control, no image: shape",
453
+ "kind": "none",
454
+ "gold": "triangle",
455
+ "pred": "circle",
456
+ "p_gold": 0.22295516729354858,
457
+ "ms": 187
458
+ },
459
+ {
460
+ "suite": "control, no image: color",
461
+ "kind": "none",
462
+ "gold": "yellow",
463
+ "pred": "red",
464
+ "p_gold": 0.20236249268054962,
465
+ "ms": 185
466
+ },
467
+ {
468
+ "suite": "image: printed message -> queue (4-way)",
469
+ "kind": "image",
470
+ "gold": "billing",
471
+ "pred": "billing",
472
+ "p_gold": 0.9587600827217102,
473
+ "ms": 5605
474
+ },
475
+ {
476
+ "suite": "control, no image: queue",
477
+ "kind": "none",
478
+ "gold": "billing",
479
+ "pred": "technical support",
480
+ "p_gold": 0.02786904200911522,
481
+ "ms": 203
482
+ },
483
+ {
484
+ "suite": "reference, same message as text: queue",
485
+ "kind": "text",
486
+ "gold": "billing",
487
+ "pred": "billing",
488
+ "p_gold": 0.9986612796783447,
489
+ "ms": 196
490
+ },
491
+ {
492
+ "suite": "image: printed message -> queue (4-way)",
493
+ "kind": "image",
494
+ "gold": "billing",
495
+ "pred": "billing",
496
+ "p_gold": 0.9929062128067017,
497
+ "ms": 5024
498
+ },
499
+ {
500
+ "suite": "control, no image: queue",
501
+ "kind": "none",
502
+ "gold": "billing",
503
+ "pred": "technical support",
504
+ "p_gold": 0.02786904200911522,
505
+ "ms": 188
506
+ },
507
+ {
508
+ "suite": "reference, same message as text: queue",
509
+ "kind": "text",
510
+ "gold": "billing",
511
+ "pred": "billing",
512
+ "p_gold": 0.9986044764518738,
513
+ "ms": 181
514
+ },
515
+ {
516
+ "suite": "image: printed message -> queue (4-way)",
517
+ "kind": "image",
518
+ "gold": "account access",
519
+ "pred": "account access",
520
+ "p_gold": 0.7705231308937073,
521
+ "ms": 5284
522
+ },
523
+ {
524
+ "suite": "control, no image: queue",
525
+ "kind": "none",
526
+ "gold": "account access",
527
+ "pred": "technical support",
528
+ "p_gold": 0.15655532479286194,
529
+ "ms": 189
530
+ },
531
+ {
532
+ "suite": "reference, same message as text: queue",
533
+ "kind": "text",
534
+ "gold": "account access",
535
+ "pred": "account access",
536
+ "p_gold": 0.8988062143325806,
537
+ "ms": 202
538
+ },
539
+ {
540
+ "suite": "image: printed message -> queue (4-way)",
541
+ "kind": "image",
542
+ "gold": "account access",
543
+ "pred": "account access",
544
+ "p_gold": 0.6306372284889221,
545
+ "ms": 5310
546
+ },
547
+ {
548
+ "suite": "control, no image: queue",
549
+ "kind": "none",
550
+ "gold": "account access",
551
+ "pred": "technical support",
552
+ "p_gold": 0.15655532479286194,
553
+ "ms": 181
554
+ },
555
+ {
556
+ "suite": "reference, same message as text: queue",
557
+ "kind": "text",
558
+ "gold": "account access",
559
+ "pred": "account access",
560
+ "p_gold": 0.8048309087753296,
561
+ "ms": 192
562
+ },
563
+ {
564
+ "suite": "image: printed message -> queue (4-way)",
565
+ "kind": "image",
566
+ "gold": "technical support",
567
+ "pred": "technical support",
568
+ "p_gold": 0.9995842576026917,
569
+ "ms": 5288
570
+ },
571
+ {
572
+ "suite": "control, no image: queue",
573
+ "kind": "none",
574
+ "gold": "technical support",
575
+ "pred": "technical support",
576
+ "p_gold": 0.8107117414474487,
577
+ "ms": 202
578
+ },
579
+ {
580
+ "suite": "reference, same message as text: queue",
581
+ "kind": "text",
582
+ "gold": "technical support",
583
+ "pred": "technical support",
584
+ "p_gold": 0.9998304843902588,
585
+ "ms": 204
586
+ },
587
+ {
588
+ "suite": "image: printed message -> queue (4-way)",
589
+ "kind": "image",
590
+ "gold": "technical support",
591
+ "pred": "technical support",
592
+ "p_gold": 0.998561441898346,
593
+ "ms": 4919
594
+ },
595
+ {
596
+ "suite": "control, no image: queue",
597
+ "kind": "none",
598
+ "gold": "technical support",
599
+ "pred": "technical support",
600
+ "p_gold": 0.8107117414474487,
601
+ "ms": 209
602
+ },
603
+ {
604
+ "suite": "reference, same message as text: queue",
605
+ "kind": "text",
606
+ "gold": "technical support",
607
+ "pred": "technical support",
608
+ "p_gold": 0.999686598777771,
609
+ "ms": 206
610
+ },
611
+ {
612
+ "suite": "image: printed message -> queue (4-way)",
613
+ "kind": "image",
614
+ "gold": "sales",
615
+ "pred": "sales",
616
+ "p_gold": 0.9524871706962585,
617
+ "ms": 5105
618
+ },
619
+ {
620
+ "suite": "control, no image: queue",
621
+ "kind": "none",
622
+ "gold": "sales",
623
+ "pred": "technical support",
624
+ "p_gold": 0.004863882903009653,
625
+ "ms": 188
626
+ },
627
+ {
628
+ "suite": "reference, same message as text: queue",
629
+ "kind": "text",
630
+ "gold": "sales",
631
+ "pred": "sales",
632
+ "p_gold": 0.9405021667480469,
633
+ "ms": 193
634
+ },
635
+ {
636
+ "suite": "image: printed message -> queue (4-way)",
637
+ "kind": "image",
638
+ "gold": "sales",
639
+ "pred": "sales",
640
+ "p_gold": 0.8253994584083557,
641
+ "ms": 5032
642
+ },
643
+ {
644
+ "suite": "control, no image: queue",
645
+ "kind": "none",
646
+ "gold": "sales",
647
+ "pred": "technical support",
648
+ "p_gold": 0.004863882903009653,
649
+ "ms": 179
650
+ },
651
+ {
652
+ "suite": "reference, same message as text: queue",
653
+ "kind": "text",
654
+ "gold": "sales",
655
+ "pred": "billing",
656
+ "p_gold": 0.4147074818611145,
657
+ "ms": 192
658
+ },
659
+ {
660
+ "suite": "audio: spoken message -> queue (4-way)",
661
+ "kind": "audio",
662
+ "gold": "billing",
663
+ "pred": "billing",
664
+ "p_gold": 0.991688072681427,
665
+ "ms": 2480
666
+ },
667
+ {
668
+ "suite": "audio: spoken message -> queue (4-way)",
669
+ "kind": "audio",
670
+ "gold": "billing",
671
+ "pred": "billing",
672
+ "p_gold": 0.9903938174247742,
673
+ "ms": 2274
674
+ },
675
+ {
676
+ "suite": "control, no audio: queue",
677
+ "kind": "none",
678
+ "gold": "billing",
679
+ "pred": "technical support",
680
+ "p_gold": 0.005660666152834892,
681
+ "ms": 184
682
+ },
683
+ {
684
+ "suite": "audio: spoken message -> queue (4-way)",
685
+ "kind": "audio",
686
+ "gold": "billing",
687
+ "pred": "billing",
688
+ "p_gold": 0.9894062876701355,
689
+ "ms": 2247
690
+ },
691
+ {
692
+ "suite": "audio: spoken message -> queue (4-way)",
693
+ "kind": "audio",
694
+ "gold": "billing",
695
+ "pred": "billing",
696
+ "p_gold": 0.9892838597297668,
697
+ "ms": 2272
698
+ },
699
+ {
700
+ "suite": "control, no audio: queue",
701
+ "kind": "none",
702
+ "gold": "billing",
703
+ "pred": "technical support",
704
+ "p_gold": 0.005660666152834892,
705
+ "ms": 193
706
+ },
707
+ {
708
+ "suite": "audio: spoken message -> queue (4-way)",
709
+ "kind": "audio",
710
+ "gold": "account access",
711
+ "pred": "account access",
712
+ "p_gold": 0.898837149143219,
713
+ "ms": 2256
714
+ },
715
+ {
716
+ "suite": "audio: spoken message -> queue (4-way)",
717
+ "kind": "audio",
718
+ "gold": "account access",
719
+ "pred": "account access",
720
+ "p_gold": 0.8590515851974487,
721
+ "ms": 2290
722
+ },
723
+ {
724
+ "suite": "control, no audio: queue",
725
+ "kind": "none",
726
+ "gold": "account access",
727
+ "pred": "technical support",
728
+ "p_gold": 0.01911778189241886,
729
+ "ms": 183
730
+ },
731
+ {
732
+ "suite": "audio: spoken message -> queue (4-way)",
733
+ "kind": "audio",
734
+ "gold": "account access",
735
+ "pred": "account access",
736
+ "p_gold": 0.7258855104446411,
737
+ "ms": 2258
738
+ },
739
+ {
740
+ "suite": "audio: spoken message -> queue (4-way)",
741
+ "kind": "audio",
742
+ "gold": "account access",
743
+ "pred": "account access",
744
+ "p_gold": 0.6762276291847229,
745
+ "ms": 2268
746
+ },
747
+ {
748
+ "suite": "control, no audio: queue",
749
+ "kind": "none",
750
+ "gold": "account access",
751
+ "pred": "technical support",
752
+ "p_gold": 0.01911778189241886,
753
+ "ms": 189
754
+ },
755
+ {
756
+ "suite": "audio: spoken message -> queue (4-way)",
757
+ "kind": "audio",
758
+ "gold": "technical support",
759
+ "pred": "technical support",
760
+ "p_gold": 0.9997541308403015,
761
+ "ms": 2246
762
+ },
763
+ {
764
+ "suite": "audio: spoken message -> queue (4-way)",
765
+ "kind": "audio",
766
+ "gold": "technical support",
767
+ "pred": "technical support",
768
+ "p_gold": 0.9997640252113342,
769
+ "ms": 2263
770
+ },
771
+ {
772
+ "suite": "control, no audio: queue",
773
+ "kind": "none",
774
+ "gold": "technical support",
775
+ "pred": "technical support",
776
+ "p_gold": 0.955605149269104,
777
+ "ms": 201
778
+ },
779
+ {
780
+ "suite": "audio: spoken message -> queue (4-way)",
781
+ "kind": "audio",
782
+ "gold": "technical support",
783
+ "pred": "technical support",
784
+ "p_gold": 0.9992349147796631,
785
+ "ms": 2257
786
+ },
787
+ {
788
+ "suite": "audio: spoken message -> queue (4-way)",
789
+ "kind": "audio",
790
+ "gold": "technical support",
791
+ "pred": "technical support",
792
+ "p_gold": 0.9991409778594971,
793
+ "ms": 2267
794
+ },
795
+ {
796
+ "suite": "control, no audio: queue",
797
+ "kind": "none",
798
+ "gold": "technical support",
799
+ "pred": "technical support",
800
+ "p_gold": 0.955605149269104,
801
+ "ms": 190
802
+ },
803
+ {
804
+ "suite": "audio: spoken message -> queue (4-way)",
805
+ "kind": "audio",
806
+ "gold": "sales",
807
+ "pred": "sales",
808
+ "p_gold": 0.9358583092689514,
809
+ "ms": 2255
810
+ },
811
+ {
812
+ "suite": "audio: spoken message -> queue (4-way)",
813
+ "kind": "audio",
814
+ "gold": "sales",
815
+ "pred": "sales",
816
+ "p_gold": 0.9297321438789368,
817
+ "ms": 2247
818
+ },
819
+ {
820
+ "suite": "control, no audio: queue",
821
+ "kind": "none",
822
+ "gold": "sales",
823
+ "pred": "technical support",
824
+ "p_gold": 0.019616365432739258,
825
+ "ms": 216
826
+ },
827
+ {
828
+ "suite": "audio: spoken message -> queue (4-way)",
829
+ "kind": "audio",
830
+ "gold": "sales",
831
+ "pred": "sales",
832
+ "p_gold": 0.6899588108062744,
833
+ "ms": 2262
834
+ },
835
+ {
836
+ "suite": "audio: spoken message -> queue (4-way)",
837
+ "kind": "audio",
838
+ "gold": "sales",
839
+ "pred": "sales",
840
+ "p_gold": 0.7804462909698486,
841
+ "ms": 2259
842
+ },
843
+ {
844
+ "suite": "control, no audio: queue",
845
+ "kind": "none",
846
+ "gold": "sales",
847
+ "pred": "technical support",
848
+ "p_gold": 0.019616365432739258,
849
+ "ms": 175
850
+ },
851
+ {
852
+ "suite": "audio: spoken review -> sentiment (2-way)",
853
+ "kind": "audio",
854
+ "gold": "positive",
855
+ "pred": "positive",
856
+ "p_gold": 0.9998955726623535,
857
+ "ms": 2278
858
+ },
859
+ {
860
+ "suite": "audio: spoken review -> sentiment (2-way)",
861
+ "kind": "audio",
862
+ "gold": "positive",
863
+ "pred": "positive",
864
+ "p_gold": 0.9998762607574463,
865
+ "ms": 2751
866
+ },
867
+ {
868
+ "suite": "control, no audio: sentiment",
869
+ "kind": "none",
870
+ "gold": "positive",
871
+ "pred": "positive",
872
+ "p_gold": 0.8081900477409363,
873
+ "ms": 202
874
+ },
875
+ {
876
+ "suite": "reference, same review as text: sentiment",
877
+ "kind": "text",
878
+ "gold": "positive",
879
+ "pred": "positive",
880
+ "p_gold": 0.999956488609314,
881
+ "ms": 200
882
+ },
883
+ {
884
+ "suite": "audio: spoken review -> sentiment (2-way)",
885
+ "kind": "audio",
886
+ "gold": "positive",
887
+ "pred": "positive",
888
+ "p_gold": 0.9998339414596558,
889
+ "ms": 2356
890
+ },
891
+ {
892
+ "suite": "audio: spoken review -> sentiment (2-way)",
893
+ "kind": "audio",
894
+ "gold": "positive",
895
+ "pred": "positive",
896
+ "p_gold": 0.9998350143432617,
897
+ "ms": 2681
898
+ },
899
+ {
900
+ "suite": "control, no audio: sentiment",
901
+ "kind": "none",
902
+ "gold": "positive",
903
+ "pred": "positive",
904
+ "p_gold": 0.8081900477409363,
905
+ "ms": 190
906
+ },
907
+ {
908
+ "suite": "reference, same review as text: sentiment",
909
+ "kind": "text",
910
+ "gold": "positive",
911
+ "pred": "positive",
912
+ "p_gold": 0.9999436140060425,
913
+ "ms": 188
914
+ },
915
+ {
916
+ "suite": "audio: spoken review -> sentiment (2-way)",
917
+ "kind": "audio",
918
+ "gold": "positive",
919
+ "pred": "positive",
920
+ "p_gold": 0.9999054670333862,
921
+ "ms": 265
922
+ },
923
+ {
924
+ "suite": "audio: spoken review -> sentiment (2-way)",
925
+ "kind": "audio",
926
+ "gold": "positive",
927
+ "pred": "positive",
928
+ "p_gold": 0.9998829364776611,
929
+ "ms": 261
930
+ },
931
+ {
932
+ "suite": "control, no audio: sentiment",
933
+ "kind": "none",
934
+ "gold": "positive",
935
+ "pred": "positive",
936
+ "p_gold": 0.8081900477409363,
937
+ "ms": 200
938
+ },
939
+ {
940
+ "suite": "reference, same review as text: sentiment",
941
+ "kind": "text",
942
+ "gold": "positive",
943
+ "pred": "positive",
944
+ "p_gold": 0.9999107122421265,
945
+ "ms": 213
946
+ },
947
+ {
948
+ "suite": "audio: spoken review -> sentiment (2-way)",
949
+ "kind": "audio",
950
+ "gold": "negative",
951
+ "pred": "negative",
952
+ "p_gold": 0.9996554851531982,
953
+ "ms": 2513
954
+ },
955
+ {
956
+ "suite": "audio: spoken review -> sentiment (2-way)",
957
+ "kind": "audio",
958
+ "gold": "negative",
959
+ "pred": "negative",
960
+ "p_gold": 0.9996836185455322,
961
+ "ms": 2424
962
+ },
963
+ {
964
+ "suite": "control, no audio: sentiment",
965
+ "kind": "none",
966
+ "gold": "negative",
967
+ "pred": "positive",
968
+ "p_gold": 0.19180989265441895,
969
+ "ms": 188
970
+ },
971
+ {
972
+ "suite": "reference, same review as text: sentiment",
973
+ "kind": "text",
974
+ "gold": "negative",
975
+ "pred": "negative",
976
+ "p_gold": 0.9992406368255615,
977
+ "ms": 189
978
+ },
979
+ {
980
+ "suite": "audio: spoken review -> sentiment (2-way)",
981
+ "kind": "audio",
982
+ "gold": "negative",
983
+ "pred": "negative",
984
+ "p_gold": 0.9999104738235474,
985
+ "ms": 2465
986
+ },
987
+ {
988
+ "suite": "audio: spoken review -> sentiment (2-way)",
989
+ "kind": "audio",
990
+ "gold": "negative",
991
+ "pred": "negative",
992
+ "p_gold": 0.9998811483383179,
993
+ "ms": 2744
994
+ },
995
+ {
996
+ "suite": "control, no audio: sentiment",
997
+ "kind": "none",
998
+ "gold": "negative",
999
+ "pred": "positive",
1000
+ "p_gold": 0.19180989265441895,
1001
+ "ms": 188
1002
+ },
1003
+ {
1004
+ "suite": "reference, same review as text: sentiment",
1005
+ "kind": "text",
1006
+ "gold": "negative",
1007
+ "pred": "negative",
1008
+ "p_gold": 0.999881386756897,
1009
+ "ms": 192
1010
+ },
1011
+ {
1012
+ "suite": "audio: spoken review -> sentiment (2-way)",
1013
+ "kind": "audio",
1014
+ "gold": "negative",
1015
+ "pred": "negative",
1016
+ "p_gold": 0.9994496703147888,
1017
+ "ms": 2155
1018
+ },
1019
+ {
1020
+ "suite": "audio: spoken review -> sentiment (2-way)",
1021
+ "kind": "audio",
1022
+ "gold": "negative",
1023
+ "pred": "negative",
1024
+ "p_gold": 0.9993879795074463,
1025
+ "ms": 2120
1026
+ },
1027
+ {
1028
+ "suite": "control, no audio: sentiment",
1029
+ "kind": "none",
1030
+ "gold": "negative",
1031
+ "pred": "positive",
1032
+ "p_gold": 0.19180989265441895,
1033
+ "ms": 190
1034
+ },
1035
+ {
1036
+ "suite": "reference, same review as text: sentiment",
1037
+ "kind": "text",
1038
+ "gold": "negative",
1039
+ "pred": "negative",
1040
+ "p_gold": 0.9989564418792725,
1041
+ "ms": 184
1042
+ }
1043
+ ]
1044
+ }
eval/multimodal_smoke/run_smoke.py ADDED
@@ -0,0 +1,139 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ """Smoke test for the experimental image/audio path (docs/MULTIMODAL_EXPERIMENTAL.md).
2
+
3
+ Run from the release root after make_audio_windows.ps1 has written work/audio:
4
+ python -m eval.multimodal_smoke.run_smoke
5
+ Images are generated here. Every question is also asked with the media removed,
6
+ which should score at chance.
7
+ """
8
+
9
+ import json
10
+ import time
11
+ from pathlib import Path
12
+
13
+ import torch
14
+ from PIL import Image, ImageDraw, ImageFont
15
+
16
+ from openjev import OpenJEV
17
+ from openjev.formatting import collate_packed, pack_question
18
+ from openjev.modeling import _temperature
19
+
20
+ HERE = Path(__file__).resolve().parent
21
+ REPO = HERE.parents[1]
22
+ WORK = HERE / "work"
23
+ QUEUES = ["billing", "technical support", "account access", "sales"]
24
+ COLORS = {"red": (220, 30, 30), "blue": (30, 70, 220), "green": (30, 160, 60), "yellow": (240, 200, 20)}
25
+ SHAPES = ["square", "circle", "triangle"]
26
+ TEXT_CASES = [
27
+ ("The battery died after two days and support never replied.", "What is the sentiment of this review?", ["positive", "negative", "neutral"]),
28
+ ("The central bank raised interest rates by half a point to curb inflation.", "What is the topic of this headline?", ["sports", "economy", "technology", "health"]),
29
+ ("I forgot my password and the reset email never arrives.", "Which support queue should handle this message?", QUEUES),
30
+ ("Premise: The store is closed on Sundays. Hypothesis: The store is open every day.", "Does the premise entail the hypothesis?", ["entailment", "neutral", "contradiction"]),
31
+ ]
32
+
33
+
34
+ def make_images(lines):
35
+ out = WORK / "images"
36
+ out.mkdir(parents=True, exist_ok=True)
37
+ shapes = []
38
+ for shape in SHAPES:
39
+ for name, rgb in COLORS.items():
40
+ im = Image.new("RGB", (512, 512), "white")
41
+ d = ImageDraw.Draw(im)
42
+ if shape == "square":
43
+ d.rectangle((110, 110, 402, 402), fill=rgb)
44
+ elif shape == "circle":
45
+ d.ellipse((100, 100, 412, 412), fill=rgb)
46
+ else:
47
+ d.polygon([(256, 80), (80, 420), (432, 420)], fill=rgb)
48
+ path = out / f"{name}-{shape}.png"
49
+ im.save(path)
50
+ shapes.append((path, shape, name))
51
+ try:
52
+ font = ImageFont.truetype("arial.ttf", 34)
53
+ except OSError:
54
+ font = ImageFont.load_default()
55
+ messages = []
56
+ for rid, label, text in lines:
57
+ if not rid.startswith("q_"):
58
+ continue
59
+ im = Image.new("RGB", (768, 384), "white")
60
+ d = ImageDraw.Draw(im)
61
+ row, y = "", 60
62
+ for word in text.split():
63
+ if d.textlength(row + " " + word, font=font) > 680:
64
+ d.text((40, y), row.strip(), fill="black", font=font)
65
+ y, row = y + 52, ""
66
+ row += " " + word
67
+ d.text((40, y), row.strip(), fill="black", font=font)
68
+ path = out / f"{rid}.png"
69
+ im.save(path)
70
+ messages.append((path, label, text))
71
+ return shapes, messages
72
+
73
+
74
+ def main():
75
+ lines = [l.split("\t") for l in (HERE / "lines.tsv").read_text(encoding="utf-8").splitlines() if l.strip()]
76
+ audio_dir = WORK / "audio"
77
+ if not any(audio_dir.glob("*.wav")):
78
+ raise SystemExit("Run make_audio_windows.ps1 first (writes eval/multimodal_smoke/work/audio).")
79
+ shapes, messages = make_images(lines)
80
+ model = OpenJEV.from_pretrained(REPO, device="cuda", load_mode="nf4", multimodal=True)
81
+ suites, rows = {}, []
82
+
83
+ def run(suite, gold, question, options, **kw):
84
+ t = time.perf_counter()
85
+ r = model.choice(instruction=question, options=options, **kw)
86
+ ms = 1000 * (time.perf_counter() - t)
87
+ s = suites.setdefault(suite, {"n": 0, "correct": 0, "ms": []})
88
+ s["n"] += 1
89
+ s["correct"] += r["selected_option"] == gold
90
+ s["ms"].append(ms)
91
+ rows.append({"suite": suite, "gold": gold, "pred": r["selected_option"],
92
+ "p_gold": r["probabilities"][options.index(gold)], "ms": round(ms)})
93
+
94
+ # Text-only inputs must score identically through the text and multimodal forward paths.
95
+ max_diff = 0.0
96
+ for state, q, opts in TEXT_CASES:
97
+ a = model.choice(state=state, instruction=q, options=opts)["probabilities"]
98
+ packed = pack_question(model.tokenizer, state, {"instruction": q, "options": [{"text": o} for o in opts]}, 8192)
99
+ batch = {k: v.to("cuda") for k, v in collate_packed(model.tokenizer, [packed]).items()}
100
+ with torch.inference_mode():
101
+ logits = model._media_logits(batch, {})[0, :len(opts)].float()
102
+ b = torch.softmax(logits / _temperature(model.calibration), -1).cpu().tolist()
103
+ max_diff = max(max_diff, max(abs(x - y) for x, y in zip(a, b)))
104
+
105
+ for path, shape, color in shapes:
106
+ run("image: shape (3-way)", shape, "What shape is shown in the image?", SHAPES, state="", image=path)
107
+ run("image: color (4-way)", color, "What color is the shape in the image?", list(COLORS), state="", image=path)
108
+ run("control, no image: shape", shape, "What shape is shown in the image?", SHAPES, state="")
109
+ run("control, no image: color", color, "What color is the shape in the image?", list(COLORS), state="")
110
+ for path, label, text in messages:
111
+ q = "Which support queue should handle the message in the image?"
112
+ run("image: printed message -> queue (4-way)", label, q, QUEUES, state="", image=path)
113
+ run("control, no image: queue", label, q, QUEUES, state="")
114
+ run("reference, same message as text: queue", label, "Which support queue should handle this message?", QUEUES, state=text)
115
+ for rid, label, text in lines:
116
+ for voice in ("david", "zira"):
117
+ clip = audio_dir / f"{rid}-{voice}.wav"
118
+ if rid.startswith("q_"):
119
+ run("audio: spoken message -> queue (4-way)", label, "Which support queue should handle the spoken message?", QUEUES, state="", audio=clip)
120
+ else:
121
+ run("audio: spoken review -> sentiment (2-way)", label, "What is the sentiment of the spoken review?", ["positive", "negative"], state="", audio=clip)
122
+ if rid.startswith("q_"):
123
+ run("control, no audio: queue", label, "Which support queue should handle the spoken message?", QUEUES, state="")
124
+ else:
125
+ run("control, no audio: sentiment", label, "What is the sentiment of the spoken review?", ["positive", "negative"], state="")
126
+ run("reference, same review as text: sentiment", label, "What is the sentiment of this review?", ["positive", "negative"], state=text)
127
+
128
+ print(f"text vs multimodal path, max probability difference: {max_diff:.2e}")
129
+ for name, s in suites.items():
130
+ s["median_ms"] = round(sorted(s.pop("ms"))[s["n"] // 2])
131
+ s["accuracy"] = s["correct"] / s["n"]
132
+ print(f"{name:45s} {s['correct']:>2}/{s['n']:<2} {100 * s['accuracy']:5.1f}% median {s['median_ms']} ms")
133
+ result = {"text_vs_multimodal_path_max_prob_diff": max_diff,
134
+ "peak_vram_gib": round(torch.cuda.max_memory_allocated() / 2**30, 2), "suites": suites, "rows": rows}
135
+ (WORK / "results.json").write_text(json.dumps(result, indent=2) + "\n", encoding="utf-8")
136
+
137
+
138
+ if __name__ == "__main__":
139
+ main()
eval/openjev_1_0_public_benchmark.csv ADDED
@@ -0,0 +1,6 @@
 
 
 
 
 
 
 
1
+ task,display,status,n,accuracy,nll,brier,ece,p50_ms,p95_ms,raw_e4b_nf4_accuracy,delta_vs_raw_pp
2
+ arc_challenge,ARC-Challenge,measured,1172,0.878839590443686,0.3434354264088279,0.18004018282065612,0.018640702693919233,173.45179200128769,241.31601209974178,0.8737,0.5139590443685949
3
+ winogrande,WinoGrande,measured,1267,0.664561957379637,0.8732282598352692,0.5079363220206212,0.1986676781891346,168.07529400102794,241.4132092009823,0.6014,6.316195737963692
4
+ arc_easy,ARC-Easy,measured,2376,0.9562289562289562,0.15331558248276225,0.07269179989034444,0.029571857402955024,172.40582649901626,231.58336125015921,0.9558,0.04289562289562232
5
+ hellaswag,HellaSwag,measured,10042,0.8977295359490142,0.2736215284007136,0.14571887516017584,0.012820075397336044,230.12878749796073,276.0439652007335,0.7651,13.262953594901417
6
+ mmlu,MMLU,measured,14042,0.6441390115368181,0.8705675106095903,0.45396635565340543,0.03314363695835977,196.04511249963252,303.28189720094076,0.6619,-1.776098846318197
eval/openjev_1_0_public_benchmark.json ADDED
@@ -0,0 +1,74 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ {
2
+ "model_line": "openjev-e4b",
3
+ "release": "1.0",
4
+ "benchmark_artifact": "local NF4 frozen base + dense adapted layers + LoRA + decision head",
5
+ "source_summary": "eval/raw_run/summary.json",
6
+ "raw_control_summary": "eval/raw_e4b_nf4_control_summary.json",
7
+ "tasks": {
8
+ "arc_challenge": {
9
+ "status": "measured",
10
+ "display": "ARC-Challenge",
11
+ "n": 1172,
12
+ "accuracy": 0.878839590443686,
13
+ "nll": 0.3434354264088279,
14
+ "brier": 0.18004018282065612,
15
+ "ece": 0.018640702693919233,
16
+ "p50_ms": 173.45179200128769,
17
+ "p95_ms": 241.31601209974178,
18
+ "raw_e4b_nf4_accuracy": 0.8737,
19
+ "delta_vs_raw_pp": 0.5139590443685949
20
+ },
21
+ "winogrande": {
22
+ "status": "measured",
23
+ "display": "WinoGrande",
24
+ "n": 1267,
25
+ "accuracy": 0.664561957379637,
26
+ "nll": 0.8732282598352692,
27
+ "brier": 0.5079363220206212,
28
+ "ece": 0.1986676781891346,
29
+ "p50_ms": 168.07529400102794,
30
+ "p95_ms": 241.4132092009823,
31
+ "raw_e4b_nf4_accuracy": 0.6014,
32
+ "delta_vs_raw_pp": 6.316195737963692
33
+ },
34
+ "arc_easy": {
35
+ "status": "measured",
36
+ "display": "ARC-Easy",
37
+ "n": 2376,
38
+ "accuracy": 0.9562289562289562,
39
+ "nll": 0.15331558248276225,
40
+ "brier": 0.07269179989034444,
41
+ "ece": 0.029571857402955024,
42
+ "p50_ms": 172.40582649901626,
43
+ "p95_ms": 231.58336125015921,
44
+ "raw_e4b_nf4_accuracy": 0.9558,
45
+ "delta_vs_raw_pp": 0.04289562289562232
46
+ },
47
+ "hellaswag": {
48
+ "status": "measured",
49
+ "display": "HellaSwag",
50
+ "n": 10042,
51
+ "accuracy": 0.8977295359490142,
52
+ "nll": 0.2736215284007136,
53
+ "brier": 0.14571887516017584,
54
+ "ece": 0.012820075397336044,
55
+ "p50_ms": 230.12878749796073,
56
+ "p95_ms": 276.0439652007335,
57
+ "raw_e4b_nf4_accuracy": 0.7651,
58
+ "delta_vs_raw_pp": 13.262953594901417
59
+ },
60
+ "mmlu": {
61
+ "status": "measured",
62
+ "display": "MMLU",
63
+ "n": 14042,
64
+ "accuracy": 0.6441390115368181,
65
+ "nll": 0.8705675106095903,
66
+ "brier": 0.45396635565340543,
67
+ "ece": 0.03314363695835977,
68
+ "p50_ms": 196.04511249963252,
69
+ "p95_ms": 303.28189720094076,
70
+ "raw_e4b_nf4_accuracy": 0.6619,
71
+ "delta_vs_raw_pp": -1.776098846318197
72
+ }
73
+ }
74
+ }
eval/raw_e4b_nf4_control_run/details.jsonl ADDED
@@ -0,0 +1,3 @@
 
 
 
 
1
+ version https://git-lfs.github.com/spec/v1
2
+ oid sha256:9cca1e4e95851c986e0f34a5ee9fb1b795feab6471ed211ae145c176dd8a3e03
3
+ size 11112822
eval/raw_e4b_nf4_control_run/summary.csv ADDED
@@ -0,0 +1,6 @@
 
 
 
 
 
 
 
1
+ task,display,n,accuracy,nll,brier,ece,mean_confidence,latency_p50_ms,latency_p95_ms
2
+ arc_challenge,ARC-Challenge,1172,0.8737201365187713,0.6831067400476731,0.21717192716282274,0.0939787680494793,0.9676989045682511,158.1707515015296,239.32236594882852
3
+ winogrande,WinoGrande,1267,0.601420678768745,1.2788768116978686,0.6614413444986983,0.3052572014015491,0.9024290672675535,154.72152499933145,223.75241669906248
4
+ arc_easy,ARC-Easy,2376,0.9558080808080808,0.22179265867232187,0.07866352502747752,0.03341212654329345,0.9868527784304538,153.7982515001204,216.1515892476018
5
+ hellaswag,HellaSwag,10042,0.7650866361282613,1.1006902005839516,0.3895498661609883,0.16545367857507678,0.9305298846809178,259.9183570000605,484.0487622521322
6
+ mmlu,MMLU,14042,0.6618715282723259,1.6791403682676391,0.5577819683925032,0.24181974065906128,0.9036912689313833,176.33997150187497,324.31165310117643
eval/raw_e4b_nf4_control_run/summary.json ADDED
@@ -0,0 +1,52 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ {
2
+ "arc_easy": {
3
+ "n": 2376,
4
+ "accuracy": 0.9558080808080808,
5
+ "nll": 0.22179265867232187,
6
+ "brier": 0.07866352502747752,
7
+ "ece": 0.03341212654329345,
8
+ "latency_p50_ms": 153.7982515001204,
9
+ "latency_p95_ms": 216.1515892476018,
10
+ "mean_confidence": 0.9868527784304538
11
+ },
12
+ "arc_challenge": {
13
+ "n": 1172,
14
+ "accuracy": 0.8737201365187713,
15
+ "nll": 0.6831067400476731,
16
+ "brier": 0.21717192716282274,
17
+ "ece": 0.0939787680494793,
18
+ "latency_p50_ms": 158.1707515015296,
19
+ "latency_p95_ms": 239.32236594882852,
20
+ "mean_confidence": 0.9676989045682511
21
+ },
22
+ "winogrande": {
23
+ "n": 1267,
24
+ "accuracy": 0.601420678768745,
25
+ "nll": 1.2788768116978686,
26
+ "brier": 0.6614413444986983,
27
+ "ece": 0.3052572014015491,
28
+ "latency_p50_ms": 154.72152499933145,
29
+ "latency_p95_ms": 223.75241669906248,
30
+ "mean_confidence": 0.9024290672675535
31
+ },
32
+ "hellaswag": {
33
+ "n": 10042,
34
+ "accuracy": 0.7650866361282613,
35
+ "nll": 1.1006902005839516,
36
+ "brier": 0.3895498661609883,
37
+ "ece": 0.16545367857507678,
38
+ "latency_p50_ms": 259.9183570000605,
39
+ "latency_p95_ms": 484.0487622521322,
40
+ "mean_confidence": 0.9305298846809178
41
+ },
42
+ "mmlu": {
43
+ "n": 14042,
44
+ "accuracy": 0.6618715282723259,
45
+ "nll": 1.6791403682676391,
46
+ "brier": 0.5577819683925032,
47
+ "ece": 0.24181974065906128,
48
+ "latency_p50_ms": 176.33997150187497,
49
+ "latency_p95_ms": 324.31165310117643,
50
+ "mean_confidence": 0.9036912689313833
51
+ }
52
+ }
eval/raw_e4b_nf4_control_summary.json ADDED
@@ -0,0 +1,46 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ {
2
+ "model": "google/gemma-4-E4B-it",
3
+ "precision": "NF4",
4
+ "readout": "native LM-head answer-label scoring",
5
+ "complete": true,
6
+ "task_count": 5,
7
+ "source": "final benchmark output recorded 2026-09-26",
8
+ "methodological_note": "Quantization-matched base control for the local path. End-to-end system comparison versus OpenJEV because the readout architecture differs.",
9
+ "results": {
10
+ "arc_easy": {
11
+ "accuracy": 0.9558,
12
+ "nll": 0.2218,
13
+ "ece": 0.0334,
14
+ "precision": "NF4",
15
+ "readout": "native_lm_head_answer_label_scoring"
16
+ },
17
+ "arc_challenge": {
18
+ "accuracy": 0.8737,
19
+ "nll": 0.6831,
20
+ "ece": 0.094,
21
+ "precision": "NF4",
22
+ "readout": "native_lm_head_answer_label_scoring"
23
+ },
24
+ "winogrande": {
25
+ "accuracy": 0.6014,
26
+ "nll": 1.2789,
27
+ "ece": 0.3053,
28
+ "precision": "NF4",
29
+ "readout": "native_lm_head_answer_label_scoring"
30
+ },
31
+ "hellaswag": {
32
+ "accuracy": 0.7651,
33
+ "nll": 1.1007,
34
+ "ece": 0.1655,
35
+ "precision": "NF4",
36
+ "readout": "native_lm_head_answer_label_scoring"
37
+ },
38
+ "mmlu": {
39
+ "accuracy": 0.6619,
40
+ "nll": 1.6791,
41
+ "ece": 0.2418,
42
+ "precision": "NF4",
43
+ "readout": "native_lm_head_answer_label_scoring"
44
+ }
45
+ }
46
+ }