File size: 6,758 Bytes
682bee1 | 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 | # Self-Contained Cell 3: Pass 2 (Shuffled Options + 4096 Token Budget on Hard & Disputed Questions)
import json, os, re, time, zipfile, shutil, subprocess, urllib.request
from pathlib import Path
from concurrent.futures import ThreadPoolExecutor, as_completed
# 1. Ensure both Ollama servers (11434 and 11435) are alive
for idx, port in [(0, 11434), (1, 11435)]:
try:
urllib.request.urlopen(f"http://127.0.0.1:{port}/api/tags", timeout=3)
except Exception:
print(f"Restarting Ollama server on GPU #{idx} (port {port})...")
env = {**os.environ, "CUDA_VISIBLE_DEVICES": str(idx), "OLLAMA_HOST": f"127.0.0.1:{port}", "OLLAMA_NUM_PARALLEL": "2", "OLLAMA_FLASH_ATTENTION": "1", "OLLAMA_KEEP_ALIVE": "24h"}
subprocess.Popen(["ollama", "serve"], env=env, stdout=open(f"/tmp/ollama{idx}.log", "w"), stderr=subprocess.STDOUT)
time.sleep(4)
# 2. Ensure data directory and helper functions are defined
work_dir = Path("/kaggle/working/work")
work_dir.mkdir(parents=True, exist_ok=True)
data_dir = work_dir / "data"
if not data_dir.exists():
for p in Path("/kaggle/input").rglob("*"):
if p.name == "kaggle_data.zip":
with zipfile.ZipFile(p, "r") as z:
z.extractall(work_dir)
break
elif p.name == "predictions_gemma4_cot.jsonl":
shutil.copytree(p.parent.parent, work_dir, dirs_exist_ok=True)
break
def load_all_questions(d_dir: Path):
qs = []
for lvl in ["easy", "medium", "hard"]:
for f in sorted((d_dir / lvl).glob("*.json")):
for item in json.load(open(f, encoding="utf-8")):
item["_level"] = lvl
qs.append(item)
return qs
def load_ckpt(path: Path):
m = {}
if path.exists():
for line in open(path, encoding="utf-8"):
if line.strip():
r = json.loads(line)
k = f"{str(r['level']).lower().strip()}_{str(r['subject']).strip()}_{str(r['q_no']).strip()}"
m[k] = r
return m
canonical_qs = load_all_questions(data_dir)
cot_map = load_ckpt(data_dir / "predictions_gemma4_cot.jsonl")
dir_map = load_ckpt(data_dir / "predictions_gemma4_direct.jsonl")
p1_map = load_ckpt(Path("/kaggle/working/predictions.jsonl"))
lvl_rank = {"hard": 0, "medium": 1, "easy": 2}
def sort_key(q):
k = f"{q['_level'].lower()}_{q['subject'].strip()}_{q['q_no']}"
disputed = 0 if cot_map.get(k, {}).get("answer") != dir_map.get(k, {}).get("answer") else 1
return (disputed, lvl_rank.get(q["_level"].lower(), 3))
priority_qs = sorted(canonical_qs, key=sort_key)
SYSTEM_THINK = (
"You are an expert teacher of the Sri Lankan national school curriculum (grades 6-13), "
"fluent in Sinhala. You will receive a multiple-choice question written in Sinhala with four "
"numbered options. Exactly one option is correct. Read the Sinhala carefully, including "
"negations such as 'නොවන' / 'නොවේ' (NOT). You may reason in English. "
"Finish with the final answer in the form 'Answer: <number>'."
)
ANS_RE = re.compile(r"(?:Answer|පිළිතුර)\s*[::]?\s*\**\s*\(?([1-4])\)?", re.I)
HOSTS = ["http://127.0.0.1:11434", "http://127.0.0.1:11435"]
p2_ckpt_path = Path("/kaggle/working/predictions_gemma4_think_pass2_ckpt.jsonl")
# Target the 749 Disputed questions + all remaining Hard questions (1,074 questions total)
target_qs = []
for q in priority_qs:
k = f"{q['_level'].lower()}_{q['subject'].strip()}_{q['q_no']}"
is_disp = cot_map.get(k, {}).get("answer") != dir_map.get(k, {}).get("answer")
if is_disp or q["_level"].lower() == "hard":
target_qs.append(q)
p2_map = load_ckpt(p2_ckpt_path)
todo_p2 = [(i, q) for i, q in enumerate(target_qs) if f"{q['_level'].lower()}_{q['subject'].strip()}_{q['q_no']}" not in p2_map]
print(f"Pass 2 (Shuffled + 4096 tokens) | Already done: {len(p2_map)} | Remaining: {len(todo_p2)}")
def solve_question_shuffled(idx_q):
idx, q = idx_q
host = HOSTS[idx % 2]
lvl = q["_level"].lower()
subj = q.get("subject", "General")
q_no = q["q_no"]
q_key = f"{lvl}_{subj.strip()}_{q_no}"
grade = "6-9" if lvl == "easy" else ("10-11 (O/L)" if lvl == "medium" else "12-13 (A/L)")
orig_choices = list(q["choices"])
rot_choices = orig_choices[1:] + orig_choices[:1]
opts = "\n".join(f"{i}. {c}" for i, c in enumerate(rot_choices, 1))
user_msg = (
f"විෂය (Subject): {subj} | ශ්රේණිය (Grade): {grade}\n\n"
f"ප්රශ්නය (Question):\n{q['question'].strip()}\n\n"
f"විකල්ප (Options):\n{opts}\n\n"
"Which option (1, 2, 3 or 4) is correct? End your reply with 'Answer: <number>'."
)
payload = json.dumps({
"model": "gemma4:e4b",
"messages": [{"role": "system", "content": SYSTEM_THINK}, {"role": "user", "content": user_msg}],
"stream": False,
"think": True,
"options": {"temperature": 0.0, "num_predict": 4096}
}).encode("utf-8")
try:
req = urllib.request.Request(f"{host}/api/chat", data=payload, headers={"Content-Type": "application/json"})
with urllib.request.urlopen(req, timeout=300) as resp:
res = json.loads(resp.read().decode("utf-8"))
msg = res.get("message", {})
content, thinking = msg.get("content", ""), msg.get("thinking", "")
m = ANS_RE.findall(content) or ANS_RE.findall(thinking)
if m:
rot_ans = m[-1]
else:
digits = re.findall(r"\b([1-4])\b", content if content.strip() else thinking)
rot_ans = digits[-1] if digits else "1"
rot_idx = int(rot_ans) - 1 if rot_ans in {"1", "2", "3", "4"} else 0
orig_ans = str(((rot_idx + 1) % 4) + 1)
except Exception:
orig_ans = (p1_map.get(q_key) or cot_map.get(q_key, {})).get("answer", "1")
return q_key, {
"q_no": q_no,
"subject": subj,
"category": q.get("category", "general"),
"level": lvl,
"answer": orig_ans
}
t0 = time.time()
done_p2 = 0
with ThreadPoolExecutor(max_workers=4) as ex:
futures = [ex.submit(solve_question_shuffled, item) for item in todo_p2]
for fut in as_completed(futures):
k, rec = fut.result()
p2_map[k] = rec
done_p2 += 1
if done_p2 % 20 == 0 or done_p2 == len(todo_p2):
with open(p2_ckpt_path, "w", encoding="utf-8") as f:
for r in p2_map.values():
f.write(json.dumps(r, ensure_ascii=False) + "\n")
elapsed = time.time() - t0
print(f"Pass 2 [{len(p2_map)}/{len(target_qs)}] ({elapsed/60:.1f}m elapsed, {elapsed/done_p2:.1f}s/q)")
|