README
Browse files
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.
|