Download training/kaggle_cells/cell4_pass3.py from SinhalaMMLU-SharedTask/team-riswan: direct link, hf CLI and curl.
- Browser
- Download file 7.39 kB
-
https://huggingface.co/SinhalaMMLU-SharedTask/team-riswan/resolve/main/training/kaggle_cells/cell4_pass3.py
- Command line
-
hf download hf://SinhalaMMLU-SharedTask/team-riswan/training/kaggle_cells/cell4_pass3.py
-
curl -L -o cell4_pass3.py https://huggingface.co/SinhalaMMLU-SharedTask/team-riswan/resolve/main/training/kaggle_cells/cell4_pass3.py
7.39 kB
| # === STANDALONE CELL 4: PASS 3 (4096 TOKENS + SANSKRIT/TECHNICAL HINT ON 720 TARGET QS) === | |
| import os, re, json, time, zipfile, shutil, subprocess, urllib.request | |
| from pathlib import Path | |
| from concurrent.futures import ThreadPoolExecutor, as_completed | |
| # 1. Ensure both Ollama servers (GPU 0 -> 11434, GPU 1 -> 11435) are running | |
| 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: | |
| 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=subprocess.DEVNULL, stderr=subprocess.DEVNULL) | |
| time.sleep(3) | |
| # 2. Locate /kaggle/working/work/data (or extract from /kaggle/input if needed) | |
| 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] = str(r["answer"]) | |
| return m | |
| # NOTE (archive edit): the original cell embedded the 2238 Pass-1 answers as a digit string (P1_DIGITS) to resume | |
| # after a Kaggle session reset. They are identical to ../passes/pass1_think_2048_orig.jsonl and are loaded from there. | |
| P1_DIGITS = ''.join(json.loads(l)['answer'] for l in open('/kaggle/working/pass1_think_2048_orig.jsonl', encoding='utf-8') if l.strip()) | |
| all_qs = load_all_questions(data_dir) | |
| p1_map = {f"{q['_level'].lower()}_{q['subject'].strip()}_{q['q_no']}": P1_DIGITS[i] for i, q in enumerate(all_qs)} | |
| p2_map = load_ckpt(Path("/kaggle/working/predictions_gemma4_think_pass2_ckpt.jsonl")) | |
| cot_map = load_ckpt(data_dir / "predictions_gemma4_cot.jsonl") | |
| dir_map = load_ckpt(data_dir / "predictions_gemma4_direct.jsonl") | |
| # Target: (A) 348 questions where P1 != P2, plus (B) 372 questions not in P2 where P1 != CoT or P1 != Direct | |
| target_qs = [] | |
| for q in all_qs: | |
| k = f"{q['_level'].lower()}_{q['subject'].strip()}_{q['q_no']}" | |
| if k in p2_map: | |
| if p1_map.get(k) != p2_map.get(k): | |
| target_qs.append(q) | |
| else: | |
| if p1_map.get(k) != cot_map.get(k) or p1_map.get(k) != dir_map.get(k): | |
| target_qs.append(q) | |
| p3_ckpt_path = Path("/kaggle/working/predictions_gemma4_think_pass3_ckpt.jsonl") | |
| done_p3 = load_ckpt(p3_ckpt_path) | |
| rem_p3 = [q for q in target_qs if f"{q['_level'].lower()}_{q['subject'].strip()}_{q['q_no']}" not in done_p3] | |
| print(f"Pass 3 Target: {len(target_qs)} questions | Already done: {len(done_p3)} | Remaining: {len(rem_p3)}") | |
| SYSTEM_THINK_PROMPT = ( | |
| "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). If the question or options contain Sanskrit, " | |
| "Hindustani, Pali, Tamil, or technical terms, carefully map them to their standard curriculum " | |
| "meaning. You may reason in English. Finish with the final answer in the form 'Answer: <number>'." | |
| ) | |
| ANSWER_THINK_RE = re.compile(r"(?:Answer|පිළිතුර)\s*[::]?\s*\**\s*\(?([1-4])\)?", re.IGNORECASE) | |
| def solve_p3(idx_q): | |
| idx, q = idx_q | |
| port = 11434 if (idx % 2 == 0) else 11435 | |
| 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)") | |
| opts = "\n".join(f"{i}. {c}" for i, c in enumerate(q["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 = { | |
| "model": "gemma4:e4b", | |
| "messages": [ | |
| {"role": "system", "content": SYSTEM_THINK_PROMPT}, | |
| {"role": "user", "content": user_msg}, | |
| ], | |
| "think": True, | |
| "stream": False, | |
| "options": {"temperature": 0.1, "top_p": 0.95, "num_predict": 4096}, | |
| } | |
| for attempt in range(3): | |
| try: | |
| req = urllib.request.Request( | |
| f"http://127.0.0.1:{port}/api/chat", | |
| data=json.dumps(payload).encode("utf-8"), | |
| headers={"Content-Type": "application/json"}, | |
| ) | |
| with urllib.request.urlopen(req, timeout=300) as resp: | |
| data = json.loads(resp.read().decode("utf-8")) | |
| content = data.get("message", {}).get("content", "") or "" | |
| thinking = data.get("message", {}).get("thinking", "") or "" | |
| m = ANSWER_THINK_RE.findall(content) or ANSWER_THINK_RE.findall(thinking) | |
| if m: | |
| ans = m[-1] | |
| else: | |
| digits = re.findall(r"\b([1-4])\b", content if content.strip() else thinking) | |
| ans = digits[-1] if digits else "1" | |
| return { | |
| "q_no": q_no, | |
| "subject": subj, | |
| "category": q.get("category", "general"), | |
| "level": lvl, | |
| "answer": str(ans), | |
| } | |
| except Exception: | |
| time.sleep(2) | |
| return { | |
| "q_no": q_no, | |
| "subject": subj, | |
| "category": q.get("category", "general"), | |
| "level": lvl, | |
| "answer": p1_map.get(q_key, "1"), | |
| } | |
| t0 = time.time() | |
| done_count = len(done_p3) | |
| with open(p3_ckpt_path, "a", encoding="utf-8") as f_ckpt: | |
| with ThreadPoolExecutor(max_workers=4) as ex: | |
| futures = {ex.submit(solve_p3, (i, q)): q for i, q in enumerate(rem_p3)} | |
| for fut in as_completed(futures): | |
| res = fut.result() | |
| f_ckpt.write(json.dumps(res, ensure_ascii=False) + "\n") | |
| f_ckpt.flush() | |
| done_count += 1 | |
| if done_count % 20 == 0 or done_count == len(target_qs): | |
| el = time.time() - t0 | |
| rate = (done_count - len(done_p3)) / max(el, 1e-5) | |
| eta = (len(target_qs) - done_count) / max(rate, 1e-5) / 60 | |
| print(f"[Pass 3: {done_count}/{len(target_qs)}] {rate:.2f} q/s | ETA: {eta:.1f} min", flush=True) | |
| print("PASS 3 COMPLETE!") | |