File size: 8,312 Bytes
e03d464 a5b993b e03d464 a5b993b e03d464 a5b993b e03d464 a5b993b e03d464 a5b993b e03d464 a5b993b e03d464 a5b993b e03d464 a5b993b e03d464 a5b993b e03d464 a5b993b e03d464 a5b993b e03d464 a5b993b e03d464 a5b993b e03d464 a5b993b e03d464 a5b993b e03d464 a5b993b e03d464 a5b993b e03d464 a5b993b e03d464 a5b993b e03d464 a5b993b e03d464 a5b993b e03d464 a5b993b e03d464 a5b993b | 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 | """IOL-AI 2026 — notebook decode (prompt + force-close + T=0.6).
Matches linguini-test-iolai.ipynb cells 4–5:
- USER_INSTRUCTIONS (+ COT + /think)
- think 2048 @ T=0.6; force <|END_THINKING|><|START_RESPONSE|>
- answer 512 @ T=0.6
- marker-gated FINAL ANSWERS: parser
"""
from __future__ import annotations
import os
import subprocess
import sys
def _install_bundled_deps() -> None:
wheels_dir = os.path.join(os.path.dirname(os.path.abspath(__file__)), "wheels")
if not os.path.isdir(wheels_dir):
return
subprocess.run(
[
sys.executable,
"-m",
"pip",
"install",
"-q",
"--no-index",
f"--find-links={wheels_dir}",
"transformers==4.56.2",
],
check=True,
)
_install_bundled_deps()
os.environ["HF_HUB_OFFLINE"] = "1"
os.environ["TRANSFORMERS_OFFLINE"] = "1"
MODEL_ID = "."
import json
import re
import pandas as pd
import torch
from transformers import AutoModelForCausalLM, AutoTokenizer
SYSTEM = ""
USER_INSTRUCTIONS = """You solve International Linguistics Olympiad (IOL) problems from the data you are given.
You may see a task type you have never seen: follow the instruction and examples, and answer in the same form they use.
What to return by task type:
- translation: only the required form in the language the query asks for — do not add extra glosses or "form | meaning" unless asked
- fill_blanks: only the missing form for each blank — no extra glosses
- match_letters: ONLY the option letter (A, B, C, …), one letter per line — never copy option text, never arrows, never "A. word"
- text_to_num: the number in digits only
- num_to_text: the number written out in words, in the language asked
- kinship / sentence matching: the full required sentence or form — NOT roman numerals (i, ii, iii) and NOT an alphabet dump
- any other type: exactly what the instruction asks for, nothing else
Answer in the language and form the query asks for. Do not add glosses, translations, or explanations unless the instruction requires them.
Output rules:
- Put answers ONLY after a line that says exactly: FINAL ANSWERS:
- Never put answers before that marker.
- One answer per line; exactly as many lines as items asked in the query.
- Bare answers only: no numbering, no quotes, no commentary, no repeating the question.
Extra hard rules:
- Never refuse or apologize; always output FINAL ANSWERS: with your best guess.
- For match_letters: only bare letters (A, B, C, …) — never dump the alphabet (A B C D E F…), never option text.
- Never append tags or glosses: no "_GCY", "_NS", "form – meaning", "word - gloss", or markdown bold.
- Never write an essay or explanation of how the language works under FINAL ANSWERS: — only the answer strings.
- If the query asks for colour/color forms, output those forms only (one per line), not a linguistics write-up.
- Emit exactly as many answer lines as items asked — no more, no fewer."""
USER_INSTRUCTIONS_COT = (
USER_INSTRUCTIONS
+ "\n\nThink step by step about the rules in the examples and how they apply to the query, "
"then write FINAL ANSWERS: and the answer lines."
)
APPEND_THINK = True
THINK_BUDGET = 2048
ANSWER_BUDGET = 512
TEMPERATURE = 0.6
def build_user(context, query):
instructions = USER_INSTRUCTIONS_COT if APPEND_THINK else USER_INSTRUCTIONS
parts = [instructions.strip(), "", context.strip(), "", query.strip()]
if APPEND_THINK:
parts.append("/think")
return "\n".join(parts)
_SPECIAL = re.compile(
r"<\|START_RESPONSE\|>|<\|END_RESPONSE\|>|"
r"<\|/?END_OF_TURN_TOKEN\|>|<\|/?START_OF_TURN_TOKEN\|>|"
r"<\|CHATBOT_TOKEN\|>|<\|/?START_THINKING\|>|<\|/?END_THINKING\|>|"
r"<EOS_TOKEN>|<BOS_TOKEN>|<PAD>"
)
_MARKER = re.compile(r"(?im)\bfinal\s+answers?\b\s*:?\s*")
_TURN_NOISE = re.compile(
r"<\|/?END_OF_TURN_TOKEN\|>|<\|/?START_OF_TURN_TOKEN\|>|"
r"<\|CHATBOT_TOKEN\|>|<EOS_TOKEN>|<BOS_TOKEN>"
)
def _lines_after(region: str) -> list[str]:
answers = []
for line in region.splitlines():
line = _SPECIAL.sub("", line)
line = re.sub(r"^\s*(?:\(?\d+[.)]|[-*•])\s*", "", line).strip()
if not line:
if answers:
break
continue
if re.fullmatch(r"(?i)final\s+answers?\s*:?", line):
continue
if answers and (
len(line) > 120
or line.lower().startswith(("however", "but the", "to solve", "### ", "**"))
):
break
answers.append(line)
return answers
def parse_answers(text: str) -> list[str]:
raw = text
def _from_region(region: str) -> list[str] | None:
markers = list(_MARKER.finditer(region))
if not markers:
return None
for m in reversed(markers):
answers = _lines_after(region[m.end() :])
if answers:
return answers
return []
region = raw
if "<|END_THINKING|>" in region:
region = region.rsplit("<|END_THINKING|>", 1)[-1]
blocks = re.findall(
r"<\|START_RESPONSE\|>(.*?)<\|END_RESPONSE\|>", region, flags=re.S
)
if blocks:
region = blocks[-1]
found = _from_region(region)
if found is not None:
return found
found = _from_region(raw)
if found is not None:
return found
return []
def _build_prompt(tok, system: str, user: str):
messages = []
if system.strip():
messages.append({"role": "system", "content": system})
messages.append({"role": "user", "content": user})
try:
return tok.apply_chat_template(
messages,
add_generation_prompt=True,
return_tensors="pt",
return_dict=True,
reasoning_options={"enabled": True},
)
except TypeError:
ids = tok.apply_chat_template(
messages, add_generation_prompt=True, return_tensors="pt"
)
return {"input_ids": ids, "attention_mask": torch.ones_like(ids)}
@torch.inference_mode()
def generate_with_budgets(model, tok, enc):
device = next(model.parameters()).device
enc = {k: v.to(device) if hasattr(v, "to") else v for k, v in enc.items()}
prompt_len = enc["input_ids"].shape[-1]
pad_id = tok.pad_token_id or tok.eos_token_id
end_ids = tok.encode("<|END_THINKING|>", add_special_tokens=False)
start_ids = tok.encode("<|START_RESPONSE|>", add_special_tokens=False)
out = model.generate(
**enc,
max_new_tokens=THINK_BUDGET,
do_sample=True,
temperature=TEMPERATURE,
eos_token_id=tok.eos_token_id,
pad_token_id=pad_id,
)
gen = out[0]
new_ids = gen[prompt_len:].tolist()
forced = False
finished = bool(new_ids) and new_ids[-1] == tok.eos_token_id
if end_ids[0] not in new_ids:
forced = True
cont = torch.tensor(
end_ids + start_ids, device=gen.device, dtype=gen.dtype
)
gen = torch.cat([gen, cont], dim=0)
finished = False
if not finished:
ids = gen.unsqueeze(0)
out2 = model.generate(
input_ids=ids,
attention_mask=torch.ones_like(ids),
max_new_tokens=ANSWER_BUDGET,
do_sample=True,
temperature=TEMPERATURE,
eos_token_id=tok.eos_token_id,
pad_token_id=pad_id,
)
gen = out2[0]
text = tok.decode(gen[prompt_len:], skip_special_tokens=False).strip()
return _TURN_NOISE.sub("", text).strip(), forced
tok = AutoTokenizer.from_pretrained(MODEL_ID)
model = AutoModelForCausalLM.from_pretrained(
MODEL_ID, torch_dtype=torch.float16, device_map="auto"
).eval()
df = pd.read_csv("/tmp/data/test.csv", dtype=str).fillna("")
rows = []
for i, r in df.iterrows():
user = build_user(r["context"], r["query"])
enc = _build_prompt(tok, SYSTEM, user)
text, forced = generate_with_budgets(model, tok, enc)
answers = parse_answers(text)
rows.append({"id": r["id"], "pred": json.dumps(answers, ensure_ascii=False)})
pd.DataFrame(rows).to_csv("submission.csv", index=False)
print(f"[{i + 1}/{len(df)}] n={len(answers)} forced={forced}", flush=True)
print("wrote submission.csv", flush=True)
|