arvindcr4 commited on
Commit
8d9740c
·
verified ·
1 Parent(s): 24d7d8e
Files changed (1) hide show
  1. README.md +160 -0
README.md ADDED
@@ -0,0 +1,160 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ ---
2
+ license: apache-2.0
3
+ base_model: Qwen/Qwen2.5-14B-Instruct-AWQ
4
+ tags:
5
+ - iol-ai-2026
6
+ - linguistics
7
+ - reasoning
8
+ language:
9
+ - en
10
+ ---
11
+
12
+ # IOL-AI 2026 — Qwen2.5-14B-Instruct-AWQ
13
+
14
+ Submission for the [IOL-AI 2026 Linguistics Olympiad Challenge](https://iolai.org).
15
+
16
+ The weights are an unmodified copy of
17
+ [`Qwen/Qwen2.5-14B-Instruct-AWQ`](https://huggingface.co/Qwen/Qwen2.5-14B-Instruct-AWQ)
18
+ (Apache-2.0, redistributable), shipped in-repo because the evaluation sandbox
19
+ has no internet access. **All of the work is in `script.py`.**
20
+
21
+ ## Approach
22
+
23
+ The eval budget is 30 minutes on a 16 GB T4 for a test set of only ~90
24
+ sub-questions, so compute per problem is abundant while *reliability* is
25
+ scarce. The script is built around that asymmetry.
26
+
27
+ **1. Alignment first.** Each row is a problem block with N numbered items and
28
+ `pred` must be a JSON list of exactly N answers in order. A single missing line
29
+ shifts every later answer and zeroes the whole block on both exact-match and
30
+ chrF. `detect_n_items` recovers N from the query — handling numbered lines,
31
+ `(1)` blank markers, stated ranges, lettered items, unnumbered one-per-line
32
+ lists, and the `match_letters` shape whose items live in the shared context.
33
+ Measured on the 160 public Linguini problems it puts **98.4% of items in
34
+ correctly-sized blocks**. Model output is then force-fitted to N, preferring
35
+ the model's own numbering when it supplies it.
36
+
37
+ **2. Never emit an empty answer.** The final score is a geometric mean of exact
38
+ match and chrF, so a blank scores zero on both and is strictly worse than a
39
+ wrong guess. Every path ends in a non-empty string.
40
+
41
+ **2b. Answer style: a hypothesis that was tested and rejected.** Gold answers do
42
+ follow the conventions of whatever language the answer is in (measured over the
43
+ 920 public Linguini answers, into-English golds that are full sentences are 99%
44
+ capitalised, while the 157 that are bare clauses are only 10% capitalised, and
45
+ the style matches the problem's own glosses in 36/36 measurable cases). Encoding
46
+ that as prompt guidance nevertheless *lowered* exact match on the hidden set
47
+ twice (0.0250 -> 0.0218 -> 0.0000). It is therefore not in the shipped script.
48
+ The lesson recorded here for anyone rerunning this: a correct statistical
49
+ description of the gold format did not translate into a better prompt.
50
+
51
+ **3. Monotone improvement under a hard deadline.** A complete, correctly-shaped
52
+ `submission.csv` is written *before the model is loaded*, then overwritten after
53
+ every improvement: greedy pass → each self-consistency pass → explanations.
54
+ A crash or a timeout leaves the best result reached so far on disk rather than
55
+ nothing. The script tracks its own remaining budget and stops adding passes
56
+ when one more would not fit.
57
+
58
+ **4. Greedy-anchored voting.** After the greedy pass, sampled passes (T=0.5)
59
+ run while budget remains, but the greedy answer is the default and sampled
60
+ answers may only displace it when at least two of them agree on the same
61
+ normalised form *and* that form outpolls the greedy one.
62
+
63
+ The asymmetry is empirical. A symmetric version — majority, else "most central
64
+ by chrF" — was measurably worse than not voting at all: with only a handful of
65
+ samples the centrality fallback is ill-defined (with two candidates pairwise
66
+ chrF is symmetric, so it degenerated into preferring the shorter string) and it
67
+ swapped the greedy answer for a sampled one about half the time. On the mock
68
+ set that cost 4x exact match (EM 0.044 -> 0.011). Anchoring makes the procedure
69
+ monotone: it can only fire on genuine agreement. chrF is implemented inline so
70
+ the script carries no dependency the sandbox might lack.
71
+
72
+ **5. `match_letters` as an assignment problem.** Free-form generation answers
73
+ this task type with the identity permutation (A, B, C, ...), which is a *valid*
74
+ permutation, so duplicate-repair never fires and it scores ~0. `solve_matching`
75
+ instead scores every (item, option) pair from the next-token distribution and
76
+ takes the optimal one-to-one assignment, enforcing the bijection exactly.
77
+ Duplicate-repair is retained only as a fallback for when that solver declines.
78
+
79
+ ## Human Evaluation Challenge
80
+
81
+ `submission.csv` includes an `explanation` column: a short, human-readable
82
+ statement of the rules behind each answer (not a raw reasoning trace),
83
+ generated after the answers are fixed.
84
+
85
+ ## Reproducing
86
+
87
+ ```bash
88
+ python script.py # reads /tmp/data/test.csv, writes submission.csv
89
+ ```
90
+
91
+ Environment knobs (all optional, defaults match the platform):
92
+ `IOL_TEST_CSV`, `IOL_OUT_CSV`, `IOL_MODEL`, `IOL_TIME_LIMIT`, `IOL_BATCH`,
93
+ `IOL_EXPLAIN`.
94
+
95
+
96
+ ## Revision history (measured on the hidden set, not guessed)
97
+
98
+ | submission | change | score | chrF | exact match |
99
+ |---|---|---|---|---|
100
+ | 1 | symmetric self-consistency vote | 0.0686 | 0.1882 | 0.0250 |
101
+ | 2 | greedy-anchored voting (vote no longer fires) | **0.0712** | 0.2029 | 0.0250 |
102
+ | 3 | + "capitalise English answers" style rule | 0.0679 | 0.2117 | 0.0218 |
103
+ | 4 | + mirror-gloss-style, answer normalisation, equation hints | 0.0000 | 0.1527 | 0.0000 |
104
+ | 5 | revert to 2, plus the match_letters assignment solver | | | |
105
+ | 6 | + `repetition_penalty=1.0` (the model ships 1.05) | | | |
106
+
107
+ Every layer of prompt/post-processing cleverness measurably *hurt*. Submission 5
108
+ therefore reverts to the configuration of submission 2 and adds exactly one
109
+ change, motivated by a specific measured failure:
110
+
111
+ **`match_letters` was being answered with the identity permutation.** Replaying
112
+ seven parser variants over saved raw generations gave exact match 0.0000 for all
113
+ seven, which exonerates the parser — the model simply was not solving the task,
114
+ emitting the option labels in order (A, B, C, ...). Because the identity is a
115
+ valid permutation, `repair_bijection` never fired. `solve_matching` replaces
116
+ free-form generation for this task type: it scores every (item, option) pair
117
+ from the next-token distribution and takes the optimal one-to-one assignment,
118
+ so the bijection constraint is enforced exactly rather than hoped for.
119
+
120
+
121
+ ## The silent decoding bug
122
+
123
+ `Qwen/Qwen2.5-14B-Instruct-AWQ` ships `generation_config.json` containing
124
+ `repetition_penalty: 1.05`. Greedy decoding ignores `temperature`, `top_p` and
125
+ `top_k` — and transformers emits a warning for each of those — but a repetition
126
+ penalty **is** applied under greedy decoding, with no warning at all.
127
+
128
+ That matters here specifically: 34% of the 920 public gold answers repeat some
129
+ letter three or more times, because these languages are agglutinative and the
130
+ answers look like `ɨmpʼuhurʼu` and `ɨŋɡɨrʼɨ`. A 5% penalty on repeated tokens
131
+ biases the model away from exactly the strings the task requires. The script now
132
+ passes `repetition_penalty=1.0` explicitly.
133
+
134
+ NFC normalisation of answers was considered and rejected: 98.15% of public golds
135
+ are already NFC, but 13 of them are NFD-and-not-NFC, so forcing NFC would break
136
+ those for an unmeasured gain.
137
+
138
+
139
+ ## v8 — faithful baseline replication
140
+
141
+ The organizers' reference script reaches exact match **0.0729** on the hidden set
142
+ with these exact weights. Our best is 0.0333. Before adding anything further we
143
+ need to know whether that number is reproducible by us at all, so v8 replicates
144
+ their script literally — trivial system prompt, no chain-of-thought, **batch 1 (no padding at all)**,
145
+ naive line split, and **no forcing to N answers** — changing exactly one thing:
146
+ `repetition_penalty=1.0`. Generation is EOS-limited rather than cap-limited:
147
+ without chain-of-thought the model emits a few short answer lines and stops.
148
+
149
+ **Result: 0.2245 (chrF 0.3150, exact match 0.1600) — first place of 43 teams.**
150
+
151
+ That is 2.7x our best engineered pipeline (0.0830) and +83% on the organizers'
152
+ own baseline (0.1227), the entire delta over their number being
153
+ `repetition_penalty=1.0`.
154
+
155
+ The lesson is uncomfortable and worth recording plainly: every layer we added on
156
+ top of the reference structure — chain-of-thought, an `ANSWERS:` block, answer
157
+ style rules, output normalisation, forcing exactly N answers — reduced exact
158
+ match. Submissions 2 -> 3 -> 4 fell 0.0712 -> 0.0679 -> 0.0000 as more
159
+ engineering went in. The winning move was deleting all of it and fixing one
160
+ decoding flag.