team-nexus / inference.py
samadhitalagala's picture
Final system: greedy thinking only (no sampling); update predictions, README, defaults
903fb32 verified
Raw History Blame Contribute Delete
5.7 kB
"""
Sinhala MMLU Shared Task - Final inference script
Team: Nexus
The organizers will run this file exactly as it is, changing ONLY the DATA_REPO line
below to point to the final Sinhala MMLU dataset:
pip install -r requirements.txt
python inference.py
System (see README.md): Gemma 4 E4B-it, text-only (7.463B parameters, vision/audio towers removed, weights unchanged),
no fine-tuning. Every question is answered with the model's thinking mode, greedy decoding only (no sampling).
A fast one-step answer (next-token scores over the labels 1-4, averaged over 4 cyclic option orders) is used only as a
fallback if a thinking answer cannot be read. Set SELF_CONSISTENCY = True for an earlier, sampling-based variant
(not the submitted system).
Section 2 runs the exact pipeline that produced our Codabench submission (inference/predict.py with
configs/submission_base_think_greedy.yaml), on the questions loaded by section 1.
"""
# ======================= ORGANIZERS CHANGE ONLY THIS LINE =======================
DATA_REPO = "SinhalaMMLU-SharedTask/Sinhala-MMLU-SharedTask-Test-Dataset"
# ================================================================================
OUTPUT_FILE = "predictions.jsonl"
# ----------------------- TEAM SETTINGS (keep exactly as used for your final submission)
MODEL_ID = "SinhalaMMLU-SharedTask/team-nexus-gemma-4-e4b-it-text" # text-only extraction of google/gemma-4-E4B-it
MODEL_REVISION = "8f686039aad1a865c9335c17874d9b870fdec766" # exact commit of MODEL_ID used for our submission
SEED = 1234
SELF_CONSISTENCY = False # False = our submitted system (greedy thinking only, no sampling); True = earlier sampling variant, NOT submitted
import json
import os
import random
import re
import numpy as np
import torch
from huggingface_hub import HfFileSystem, list_repo_files
def set_seed(seed):
random.seed(seed)
np.random.seed(seed)
torch.manual_seed(seed)
torch.cuda.manual_seed_all(seed)
# =============== 1. LOAD THE DATASET FROM HUGGING FACE (do not change) ===============
def load_questions(data_repo):
fs = HfFileSystem()
files = sorted(f for f in list_repo_files(data_repo, repo_type="dataset") if f.endswith(".json"))
rows = []
for f in files:
with fs.open(f"datasets/{data_repo}/{f}", "r", encoding="utf-8") as fh:
raw = json.load(fh)
if isinstance(raw, dict):
raw = next(v for v in raw.values() if isinstance(v, list))
for r in raw:
r["_file"] = f
rows.append(r)
return rows
def get_level(r):
return (r.get("metadata") or {}).get("difficulty") or r["_file"].split("/")[0]
# =============== 2. OUR SYSTEM ===============
# predict_all() writes the loaded questions to a temporary folder (same file layout as the dataset repo), runs
# inference/predict.py on it offline, and maps its answers back to `rows` by (level, subject, q_no).
HERE = os.path.dirname(os.path.abspath(__file__))
CONFIG = os.path.join(HERE, "configs", "submission_base_think_gated_sc.yaml" if SELF_CONSISTENCY
else "submission_base_think_greedy.yaml")
def load_system():
from huggingface_hub import snapshot_download
return snapshot_download(MODEL_ID, revision=MODEL_REVISION) # local folder with weights + tokenizer
def predict_all(system, rows):
import subprocess
import sys
import tempfile
import zipfile
import yaml
model_dir = system
with tempfile.TemporaryDirectory() as tmp:
by_file = {}
for r in rows:
by_file.setdefault(r["_file"], []).append({k: v for k, v in r.items() if k != "_file"})
for f, recs in by_file.items():
path = os.path.join(tmp, "data", f)
os.makedirs(os.path.dirname(path), exist_ok=True)
with open(path, "w", encoding="utf-8") as fh:
json.dump(recs, fh, ensure_ascii=False)
with open(CONFIG, encoding="utf-8") as fh:
cfg = yaml.safe_load(fh)
cfg["model"] = model_dir
cfg["seed"] = SEED
cfg_path = os.path.join(tmp, "config.yaml")
with open(cfg_path, "w", encoding="utf-8") as fh:
yaml.safe_dump(cfg, fh, allow_unicode=True)
out_zip = os.path.join(tmp, "predictions.zip")
subprocess.run([sys.executable, "-m", "inference.predict", "--config", cfg_path,
"--input", os.path.join(tmp, "data"), "--output", out_zip], cwd=HERE, check=True)
with zipfile.ZipFile(out_zip) as z:
preds = [json.loads(l) for l in z.read("predictions.jsonl").decode("utf-8").splitlines() if l.strip()]
ans = {(p["level"], p["subject"], p["q_no"]): p["answer"] for p in preds}
return [ans[(get_level(r), r["subject"], r["q_no"])] for r in rows]
# =============== 3. WRITE PREDICTIONS (do not change) ===============
if __name__ == "__main__":
set_seed(SEED)
rows = load_questions(DATA_REPO)
print(len(rows), "questions loaded from", DATA_REPO)
system = load_system()
answers = predict_all(system, rows)
assert len(answers) == len(rows), "predict_all() must return one answer per question"
with open(OUTPUT_FILE, "w", encoding="utf-8") as f:
for r, a in zip(rows, answers):
a = str(a).strip()
assert a in {"1", "2", "3", "4"}, f"invalid answer {a!r} for q_no={r.get('q_no')}"
f.write(json.dumps({
"q_no": r["q_no"],
"subject": r["subject"],
"category": r["category"],
"level": get_level(r),
"answer": a,
}, ensure_ascii=False, separators=(",", ":")) + "\n")
print("\nSaved", OUTPUT_FILE)