Buckets:
| """Write notebooks/eval_qwen35_4b.ipynb with pilot.py embedded. Run after editing pilot.py.""" | |
| import json | |
| import os | |
| ROOT = os.path.dirname(os.path.dirname(os.path.abspath(__file__))) | |
| PILOT = open(os.path.join(ROOT, "scripts", "pilot.py"), encoding="utf-8").read() | |
| def md(s): | |
| return {"cell_type": "markdown", "metadata": {}, "source": s.strip("\n")} | |
| def code(s): | |
| return {"cell_type": "code", "metadata": {}, "execution_count": None, "outputs": [], "source": s.strip("\n")} | |
| cells = [ | |
| md(""" | |
| # Darija–French temporal pilot: Qwen3.5-4B | |
| 1. Runtime → Change runtime type → **T4 GPU**. | |
| 2. Upload `cases.csv` (from `data/`) to the Files pane on the left. | |
| 3. Run the cells in order. Generation is resumable: if Colab disconnects, re-run from the top and finished items are skipped. | |
| 4. Download `results.zip`, fill `manual_verdict` / `error_type` / `my_notes` in `scoring_sheet.csv`, upload it back, run the metrics cell. | |
| """), | |
| code(""" | |
| !pip install -q -U "transformers>=5.0" accelerate torchvision pillow | |
| """), | |
| code(""" | |
| MODEL_ID = "Qwen/Qwen3.5-4B" | |
| REVISION = "851bf6e806efd8d0a36b00ddf55e13ccb7b8cd0a" # main on 2026-10-21; pinned for reproducibility | |
| CASES_PATH = "cases.csv" | |
| OUT_DIR = "results" | |
| FORMATS = ["A", "B"] | |
| MAX_NEW_TOKENS = 384 # non-thinking answers are short; truncation is flagged per item | |
| THINKING_MAX_NEW_TOKENS = 4096 | |
| """), | |
| code("# --- scripts/pilot.py, embedded by scripts/build_notebook.py ---\n" + PILOT), | |
| code(""" | |
| import os, sys, json, time, platform, datetime | |
| import torch, transformers | |
| os.makedirs(OUT_DIR, exist_ok=True) | |
| gpu = torch.cuda.get_device_name(0) if torch.cuda.is_available() else None | |
| assert gpu, "No GPU: Runtime -> Change runtime type -> T4 GPU" | |
| # T4 (compute 7.5) has no native bf16; use float16 there. | |
| DTYPE = torch.bfloat16 if torch.cuda.get_device_capability(0)[0] >= 8 else torch.float16 | |
| run_config = { | |
| "model_id": MODEL_ID, "revision": REVISION, "gpu": gpu, "dtype": str(DTYPE), | |
| "python": platform.python_version(), "torch": torch.__version__, "transformers": transformers.__version__, | |
| "decoding": {"do_sample": False, "num_beams": 1, "max_new_tokens": MAX_NEW_TOKENS}, | |
| "prompt_formats": PROMPT_FORMATS, "answer_instructions": ANSWER_INSTRUCTIONS, | |
| "started_utc": datetime.datetime.utcnow().isoformat(timespec="seconds"), | |
| } | |
| json.dump(run_config, open(f"{OUT_DIR}/run_config.json", "w"), indent=2, ensure_ascii=False) | |
| print(json.dumps(run_config, indent=2, ensure_ascii=False)) | |
| """), | |
| code(""" | |
| cases = load_cases(CASES_PATH) # only APPROVED / EDITED rows | |
| print(len(cases), "approved cases") | |
| from collections import Counter | |
| print(Counter((c["language"], c["task_type"], c["variant_id"]) for c in cases)) | |
| """), | |
| code(""" | |
| from transformers import AutoProcessor, AutoModelForImageTextToText | |
| processor = AutoProcessor.from_pretrained(MODEL_ID, revision=REVISION) | |
| model = AutoModelForImageTextToText.from_pretrained(MODEL_ID, revision=REVISION, dtype=DTYPE, device_map="auto") | |
| model.eval() | |
| def render(prompt, thinking): | |
| messages = [{"role": "user", "content": [{"type": "text", "text": prompt}]}] | |
| return processor.apply_chat_template(messages, add_generation_prompt=True, tokenize=False, | |
| enable_thinking=thinking) | |
| def generate(prompt, thinking=False, max_new_tokens=MAX_NEW_TOKENS): | |
| text = render(prompt, thinking) | |
| inputs = processor(text=[text], return_tensors="pt").to(model.device) | |
| with torch.no_grad(): | |
| out = model.generate(**inputs, do_sample=False, num_beams=1, max_new_tokens=max_new_tokens) | |
| new = out[0][inputs["input_ids"].shape[1]:] | |
| return processor.decode(new, skip_special_tokens=True), len(new) | |
| # Check the chat template really switched thinking off: the prompt should end with an empty think block. | |
| sample = render(build_prompt(cases[0], "A"), thinking=False) | |
| print(sample[-300:]) | |
| assert "</think>" in sample[-60:], "enable_thinking=False did not reach the chat template; stop and check" | |
| """), | |
| code(""" | |
| RAW = f"{OUT_DIR}/raw_outputs.jsonl" | |
| done = {(r["case_id"], r["prompt_format"]) for r in read_jsonl(RAW)} | |
| todo = [(c, f) for f in FORMATS for c in cases if (c["case_id"], f) not in done] | |
| print(f"{len(done)} done, {len(todo)} to run") | |
| t0 = time.time() | |
| for k, (c, fmt) in enumerate(todo, 1): | |
| prompt = build_prompt(c, fmt) | |
| t = time.time() | |
| raw, n_tokens = generate(prompt) | |
| parsed, status = parse_answer(raw, c["task_type"]) | |
| append_jsonl(RAW, {"case_id": c["case_id"], "prompt_format": fmt, "thinking": False, "prompt": prompt, | |
| "raw_output": raw, "new_tokens": n_tokens, "truncated": n_tokens >= MAX_NEW_TOKENS, | |
| "parsed_answer": parsed, "parse_status": status, "seconds": round(time.time() - t, 1)}) | |
| print(f"[{k}/{len(todo)}] {c['case_id']} fmt={fmt} -> {parsed or status} ({time.time() - t:.0f}s, elapsed {time.time() - t0:.0f}s)") | |
| """), | |
| code(""" | |
| import csv | |
| by_case = {c["case_id"]: c for c in cases} | |
| rows = [] | |
| for r in read_jsonl(RAW): | |
| c = by_case.get(r["case_id"]) | |
| if not c or r["thinking"]: | |
| continue | |
| rows.append({**{k: c[k] for k in ("case_id", "family_id", "variant_id", "language", "task_type", "question", "gold_answer")}, | |
| "prompt_format": r["prompt_format"], "parsed_answer": r["parsed_answer"], "parse_status": r["parse_status"], | |
| "truncated": r["truncated"], "auto_correct": auto_score(c, r["parsed_answer"], r["parse_status"]), | |
| "manual_verdict": "", "error_type": "", "my_notes": ""}) | |
| SHEET = f"{OUT_DIR}/scoring_sheet.csv" | |
| with open(SHEET, "w", encoding="utf-8", newline="") as f: | |
| w = csv.DictWriter(f, fieldnames=list(rows[0])) | |
| w.writeheader(); w.writerows(rows) | |
| print("wrote", SHEET, len(rows), "rows") | |
| print(json.dumps(metrics(rows), indent=2)) | |
| import pandas as pd | |
| df = pd.DataFrame(rows) | |
| df[df["auto_correct"] != True][["case_id", "prompt_format", "gold_answer", "parsed_answer", "parse_status", "truncated"]] | |
| """), | |
| md(""" | |
| **Your review.** Read every raw output in `raw_outputs.jsonl` (not only the parsed label). In `scoring_sheet.csv` set `manual_verdict` to CORRECT or WRONG where you disagree with `auto_correct` or where it is empty (extract tasks), and `error_type` to one of: `language` (misread Darija/French), `ordering`, `inference` (facts right, conclusion wrong), `format`, `other`. Upload the edited sheet back as `results/scoring_sheet.csv`, then run the next cell. | |
| """), | |
| code(""" | |
| with open(f"{OUT_DIR}/scoring_sheet.csv", encoding="utf-8-sig", newline="") as f: | |
| reviewed = list(csv.DictReader(f)) | |
| m = metrics(reviewed) | |
| m["error_types"] = dict(Counter(r["error_type"] for r in reviewed if r["error_type"])) | |
| json.dump(m, open(f"{OUT_DIR}/metrics.json", "w"), indent=2, ensure_ascii=False) | |
| print(json.dumps(m, indent=2, ensure_ascii=False)) | |
| """), | |
| md(""" | |
| **Optional: thinking-mode rerun of failures.** Slow on a T4 (long reasoning). Greedy here; Qwen recommends sampling for thinking mode, so report this as a diagnostic, not as the model's tuned performance. | |
| """), | |
| code(""" | |
| RAW_T = f"{OUT_DIR}/raw_outputs_thinking.jsonl" | |
| failed = sorted({(r["case_id"], r["prompt_format"]) for r in reviewed if final_correct(r) is False}) | |
| done_t = {(r["case_id"], r["prompt_format"]) for r in read_jsonl(RAW_T)} | |
| todo_t = [x for x in failed if x not in done_t] | |
| print(len(failed), "failures,", len(todo_t), "to rerun with thinking") | |
| for k, (cid, fmt) in enumerate(todo_t, 1): | |
| c = by_case[cid]; prompt = build_prompt(c, fmt); t = time.time() | |
| raw, n_tokens = generate(prompt, thinking=True, max_new_tokens=THINKING_MAX_NEW_TOKENS) | |
| parsed, status = parse_answer(raw, c["task_type"]) | |
| append_jsonl(RAW_T, {"case_id": cid, "prompt_format": fmt, "thinking": True, "prompt": prompt, "raw_output": raw, | |
| "new_tokens": n_tokens, "truncated": n_tokens >= THINKING_MAX_NEW_TOKENS, | |
| "parsed_answer": parsed, "parse_status": status, | |
| "auto_correct": auto_score(c, parsed, status), "seconds": round(time.time() - t, 1)}) | |
| print(f"[{k}/{len(todo_t)}] {cid} fmt={fmt} -> {parsed or status} ({time.time() - t:.0f}s)") | |
| """), | |
| code(""" | |
| import shutil | |
| shutil.copy(CASES_PATH, f"{OUT_DIR}/cases_used.csv") | |
| shutil.make_archive("results", "zip", OUT_DIR) | |
| from google.colab import files | |
| files.download("results.zip") | |
| """), | |
| ] | |
| def write(cells, name): | |
| nb = {"cells": cells, "metadata": {"accelerator": "GPU", "colab": {"provenance": []}, | |
| "kernelspec": {"name": "python3", "display_name": "Python 3"}, | |
| "language_info": {"name": "python"}}, | |
| "nbformat": 4, "nbformat_minor": 5} | |
| for i, c in enumerate(nb["cells"]): | |
| c["id"] = f"cell-{i}" | |
| path = os.path.join(ROOT, "notebooks", name) | |
| with open(path, "w", encoding="utf-8") as f: | |
| json.dump(nb, f, indent=1, ensure_ascii=False) | |
| print("wrote", path) | |
| write(cells, "eval_qwen35_4b.ipynb") | |
| # Diagnostic: rerun the F4 Darija cases with Chamel's connector glossary in the prompt (non-thinking). | |
| # Shares the install, config, pilot.py, environment and model-loading cells with the main notebook. | |
| glossary_cells = [ | |
| md(""" | |
| # Glossary check: F4 Darija–French with a connector glossary (non-thinking) | |
| Reruns the F4 Darija–French cases (base and contrast, formats A and B) with your glossary of Darija time connectors added just before the story. Same model, revision, decoding and answer parsing as the main run; only the glossary is new. | |
| 1. Runtime → **T4 GPU**. 2. Upload `cases.csv` and `glossary.txt` (both from `data/`). 3. Run all. 4. Download `glossary_results.zip`. | |
| """), | |
| cells[1], cells[2], | |
| code('OUT_DIR = "results_glossary" # separate folder: never overwrite the main run results'), | |
| cells[3], cells[4], | |
| code(""" | |
| GLOSSARY = load_glossary("glossary.txt") | |
| print(GLOSSARY) | |
| cases = [c for c in load_cases(CASES_PATH) if c["family_id"] == "F4" and c["language"] == "darija_fr"] | |
| print(len(cases), "F4 Darija cases") | |
| """), | |
| cells[6], | |
| code(""" | |
| RAW_G = f"{OUT_DIR}/raw_outputs_glossary.jsonl" | |
| json.dump({**run_config, "glossary": GLOSSARY, "cases": [c["case_id"] for c in cases]}, | |
| open(f"{OUT_DIR}/run_config_glossary.json", "w"), indent=2, ensure_ascii=False) | |
| done = {(r["case_id"], r["prompt_format"]) for r in read_jsonl(RAW_G)} | |
| todo = [(c, f) for f in FORMATS for c in cases if (c["case_id"], f) not in done] | |
| print(f"{len(done)} done, {len(todo)} to run") | |
| for k, (c, fmt) in enumerate(todo, 1): | |
| prompt = build_prompt(c, fmt, glossary=GLOSSARY) | |
| t = time.time() | |
| raw, n_tokens = generate(prompt) | |
| parsed, status = parse_answer(raw, c["task_type"]) | |
| append_jsonl(RAW_G, {"case_id": c["case_id"], "prompt_format": fmt, "thinking": False, "glossary": True, | |
| "prompt": prompt, "raw_output": raw, "new_tokens": n_tokens, | |
| "truncated": n_tokens >= MAX_NEW_TOKENS, "parsed_answer": parsed, "parse_status": status, | |
| "auto_correct": auto_score(c, parsed, status), "seconds": round(time.time() - t, 1)}) | |
| print(f"[{k}/{len(todo)}] {c['case_id']} fmt={fmt} -> {parsed or status} gold={c['gold_answer']} ({time.time() - t:.0f}s)") | |
| """), | |
| code(""" | |
| res = read_jsonl(RAW_G) | |
| print(f"with glossary: {sum(r['auto_correct'] is True for r in res)}/{len(res)} correct (without glossary: 0/4 in the main run)") | |
| for r in res: | |
| print("=====", r["case_id"], r["prompt_format"], "->", r["parsed_answer"]) | |
| print(r["raw_output"][:1200]) | |
| """), | |
| code(""" | |
| import shutil | |
| shutil.copy("glossary.txt", f"{OUT_DIR}/glossary_used.txt") | |
| shutil.make_archive("glossary_results", "zip", OUT_DIR) | |
| from google.colab import files | |
| files.download("glossary_results.zip") | |
| """), | |
| ] | |
| write(glossary_cells, "glossary_check_f4.ipynb") | |
Xet Storage Details
- Size:
- 12.1 kB
- Xet hash:
- 4ec1275bcbf24c7bde026eefef71df0c74f0b424fbb885850fb2e903ffe6f082
·
Xet efficiently stores files, intelligently splitting them into unique chunks and accelerating uploads and downloads. More info.