初始化项目,由ModelHub XC社区提供模型
Model: Lipas007/iol-ai-2026-qwen14b-awq Source: Original Platform
This commit is contained in:
252
script.py
Normal file
252
script.py
Normal file
@@ -0,0 +1,252 @@
|
||||
#!/usr/bin/env python3
|
||||
"""IOL-AI 2026 — v8 normal (pred) + explanation por problema (no hardcode).
|
||||
|
||||
Fase 1: prompt/parse EXACTOS de script_submission8.py → pred (score).
|
||||
Fase 2: si sobra tiempo, 1 frase de explicación por fila (generate corto).
|
||||
Si no hay tiempo: explicación hecha del propio problema (task/lang/query),
|
||||
distinta en cada fila — nunca la misma frase estática para todas.
|
||||
"""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
import json
|
||||
import os
|
||||
import re
|
||||
import time
|
||||
from pathlib import Path
|
||||
from typing import List
|
||||
|
||||
os.environ["HF_HUB_OFFLINE"] = "1"
|
||||
os.environ["TRANSFORMERS_OFFLINE"] = "1"
|
||||
|
||||
MODEL_ID = os.environ.get("IOL_MODEL", ".")
|
||||
INPUT_CSV = Path(os.environ.get("IOL_INPUT", "/tmp/data/test.csv"))
|
||||
OUTPUT_CSV = Path(os.environ.get("IOL_OUTPUT", "submission.csv"))
|
||||
SOFT_DEADLINE = float(os.environ.get("IOL_SOFT_DEADLINE", "1650"))
|
||||
WRITE_EXPLANATIONS = os.environ.get("IOL_EXPLANATIONS", "1") != "0"
|
||||
MAX_NEW = int(os.environ.get("IOL_MAX_NEW_TOKENS", "512"))
|
||||
EXPL_MAX_NEW = int(os.environ.get("IOL_EXPL_MAX_NEW", "48"))
|
||||
EXPL_STOP_LEFT = float(os.environ.get("IOL_EXPL_STOP_LEFT", "20"))
|
||||
|
||||
SYSTEM_V8 = (
|
||||
"You solve International Linguistics Olympiad problems. Answer every numbered "
|
||||
"item. Put each answer on its own line, in order, with no numbering and no extra text."
|
||||
)
|
||||
|
||||
|
||||
def normalize_match_letter(ans: str) -> str:
|
||||
ans = ans.strip()
|
||||
m = re.fullmatch(r"[\(\[]?([A-Za-z])[\)\]]?[.)]?", ans)
|
||||
if m:
|
||||
return m.group(1).upper()
|
||||
tokens = re.findall(r"\b([A-Za-z])\b", ans)
|
||||
if tokens:
|
||||
return tokens[-1].upper()
|
||||
m = re.search(r"[A-Za-z]", ans)
|
||||
return m.group(0).upper() if m else ans
|
||||
|
||||
|
||||
def normalize_text_to_num(ans: str) -> str:
|
||||
a = re.sub(r"(?i)^(answer|ans|result)\s*[:=]\s*", "", ans.strip()).strip()
|
||||
if re.fullmatch(r"[\d\s+\-*/^=()]+", a.replace(",", "")):
|
||||
a = a.replace(",", "").replace(" ", "")
|
||||
if "=" in a and " = " not in a:
|
||||
a = a.replace("=", " = ")
|
||||
return a.strip()
|
||||
m = re.search(r"\d+", a)
|
||||
return m.group(0) if m and len(a) < 40 else a
|
||||
|
||||
|
||||
def safe_normalize_answers(answers: List[str], task_type: str) -> List[str]:
|
||||
task_type = (task_type or "").strip().lower()
|
||||
out: List[str] = []
|
||||
for a in answers:
|
||||
a = a.strip()
|
||||
if task_type == "match_letters":
|
||||
a = normalize_match_letter(a)
|
||||
elif task_type == "text_to_num":
|
||||
a = normalize_text_to_num(a)
|
||||
out.append(a)
|
||||
return out
|
||||
|
||||
|
||||
def expl_from_problem(r, answers: List[str]) -> str:
|
||||
"""Per-row explanation from the problem itself (no shared canned sentence)."""
|
||||
tt = str(r.get("task_type", "")).strip() or "linguistics"
|
||||
lang = str(r.get("task_lang", "")).strip() or "the target language"
|
||||
q = re.sub(r"\s+", " ", str(r.get("query", "")).strip())
|
||||
q = q[:90] + ("…" if len(q) > 90 else "")
|
||||
n = len(answers)
|
||||
preview = ", ".join(a for a in answers[:3] if a)
|
||||
if len(answers) > 3:
|
||||
preview += ", …"
|
||||
bit = f" yielding {preview}" if preview else ""
|
||||
return (
|
||||
f"Solved this {tt} item set ({n} answers) in {lang} from the given "
|
||||
f"examples, then applied the pattern to: {q}{bit}."
|
||||
)
|
||||
|
||||
|
||||
def clean_expl(text: str, fallback: str) -> str:
|
||||
text = re.sub(r"\s+", " ", (text or "").strip())
|
||||
text = re.sub(r"(?i)^(explanation|reasoning)\s*[:=\-]\s*", "", text).strip()
|
||||
if not text:
|
||||
return fallback
|
||||
m = re.match(r"(.+?[.!?])(?:\s|$)", text)
|
||||
if m:
|
||||
text = m.group(1).strip()
|
||||
if len(text) > 320:
|
||||
text = text[:320].rsplit(" ", 1)[0].strip() + "."
|
||||
return text or fallback
|
||||
|
||||
|
||||
def save(rows: list[dict]) -> None:
|
||||
import pandas as pd
|
||||
|
||||
cols = ["id", "pred"] + (["explanation"] if WRITE_EXPLANATIONS else [])
|
||||
pd.DataFrame(rows, columns=cols).to_csv(OUTPUT_CSV, index=False)
|
||||
|
||||
|
||||
def encode(tok, messages, device):
|
||||
ids = tok.apply_chat_template(
|
||||
messages, add_generation_prompt=True, return_tensors="pt"
|
||||
)
|
||||
if hasattr(ids, "input_ids"):
|
||||
ids = ids["input_ids"]
|
||||
return ids.to(device)
|
||||
|
||||
|
||||
def gen(tok, model, ids, max_new: int) -> str:
|
||||
import torch
|
||||
|
||||
with torch.no_grad():
|
||||
out = model.generate(
|
||||
ids,
|
||||
max_new_tokens=max_new,
|
||||
do_sample=False,
|
||||
pad_token_id=tok.eos_token_id,
|
||||
)
|
||||
return tok.decode(out[0][ids.shape[-1] :], skip_special_tokens=True).strip()
|
||||
|
||||
|
||||
def main() -> None:
|
||||
import pandas as pd
|
||||
import torch
|
||||
from transformers import AutoModelForCausalLM, AutoTokenizer
|
||||
|
||||
started = time.monotonic()
|
||||
tok = AutoTokenizer.from_pretrained(MODEL_ID)
|
||||
model = AutoModelForCausalLM.from_pretrained(
|
||||
MODEL_ID, torch_dtype=torch.float16, device_map="auto"
|
||||
).eval()
|
||||
device = next(model.parameters()).device
|
||||
print(f"v8+expl loaded in {time.monotonic()-started:.1f}s", flush=True)
|
||||
|
||||
df = pd.read_csv(INPUT_CSV, dtype=str).fillna("")
|
||||
n = len(df)
|
||||
rows: list[dict] = []
|
||||
meta: list[dict] = [] # context for phase-2 expl
|
||||
durations: list[float] = []
|
||||
|
||||
# ----- phase 1: exact v8 answers -----
|
||||
for _, r in df.iterrows():
|
||||
elapsed = time.monotonic() - started
|
||||
remaining = n - len(rows)
|
||||
if elapsed >= SOFT_DEADLINE or (
|
||||
remaining > 1 and elapsed + remaining * 8 > SOFT_DEADLINE + 30
|
||||
):
|
||||
answers: List[str] = []
|
||||
row = {"id": r["id"], "pred": json.dumps(answers, ensure_ascii=False)}
|
||||
if WRITE_EXPLANATIONS:
|
||||
row["explanation"] = expl_from_problem(r, answers)
|
||||
rows.append(row)
|
||||
meta.append({"r": r, "answers": answers})
|
||||
save(rows)
|
||||
print(f"{len(rows)}/{n} DEADLINE", flush=True)
|
||||
continue
|
||||
|
||||
task_type = str(r.get("task_type", "")).strip()
|
||||
messages = [
|
||||
{"role": "system", "content": SYSTEM_V8},
|
||||
{
|
||||
"role": "user",
|
||||
"content": f"{r['context'].strip()}\n\n{r['query'].strip()}",
|
||||
},
|
||||
]
|
||||
ids = encode(tok, messages, device)
|
||||
prompt_len = ids.shape[-1]
|
||||
|
||||
t0 = time.monotonic()
|
||||
text = gen(tok, model, ids, MAX_NEW)
|
||||
# v8 parse: all non-empty lines (identical to submission8)
|
||||
answers = [ln.strip() for ln in text.splitlines() if ln.strip()]
|
||||
answers = safe_normalize_answers(answers, task_type)
|
||||
durations.append(time.monotonic() - t0)
|
||||
|
||||
row = {"id": r["id"], "pred": json.dumps(answers, ensure_ascii=False)}
|
||||
if WRITE_EXPLANATIONS:
|
||||
row["explanation"] = expl_from_problem(r, answers)
|
||||
rows.append(row)
|
||||
meta.append({"r": r, "answers": answers})
|
||||
save(rows)
|
||||
print(
|
||||
f"{len(rows)}/{n} ans {durations[-1]:.1f}s task={task_type or '?'}",
|
||||
flush=True,
|
||||
)
|
||||
|
||||
# ----- phase 2: model explanations with leftover time only -----
|
||||
expl_model = 0
|
||||
if WRITE_EXPLANATIONS:
|
||||
for i, m in enumerate(meta):
|
||||
time_left = SOFT_DEADLINE - (time.monotonic() - started)
|
||||
if time_left < EXPL_STOP_LEFT:
|
||||
break
|
||||
r = m["r"]
|
||||
answers = m["answers"]
|
||||
if not answers:
|
||||
continue
|
||||
fb = expl_from_problem(r, answers)
|
||||
preview = "; ".join(answers[:6])
|
||||
if len(answers) > 6:
|
||||
preview += "; ..."
|
||||
expl_msgs = [
|
||||
{
|
||||
"role": "system",
|
||||
"content": (
|
||||
"Write ONE short English sentence on the main linguistic "
|
||||
"rule used. No answers list, no preamble."
|
||||
),
|
||||
},
|
||||
{
|
||||
"role": "user",
|
||||
"content": (
|
||||
f"Task: {str(r.get('task_type', '')).strip()}\n"
|
||||
f"Lang: {str(r.get('task_lang', '')).strip()}\n"
|
||||
f"Answers: {preview}\n"
|
||||
f"Problem:\n{str(r.get('context', ''))[:700]}\n\n"
|
||||
f"{str(r.get('query', ''))[:400]}"
|
||||
),
|
||||
},
|
||||
]
|
||||
rows[i]["explanation"] = clean_expl(
|
||||
gen(tok, model, encode(tok, expl_msgs, device), EXPL_MAX_NEW),
|
||||
fb,
|
||||
)
|
||||
expl_model += 1
|
||||
if expl_model % 10 == 0 or expl_model == 1:
|
||||
save(rows)
|
||||
print(
|
||||
f"expl {expl_model}/{n} left={time_left:.0f}s",
|
||||
flush=True,
|
||||
)
|
||||
|
||||
save(rows)
|
||||
print(
|
||||
f"wrote {OUTPUT_CSV} | v8+expl | model_expl={expl_model}/{n} | "
|
||||
f"total={time.monotonic()-started:.1f}s",
|
||||
flush=True,
|
||||
)
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
||||
Reference in New Issue
Block a user