Download day53/scripts/bank_eval.py from td-builder/fbfhbfbfbv: direct link, hf CLI and curl.
- Browser
- Download file 7.62 kB
-
https://huggingface.co/td-builder/fbfhbfbfbv/resolve/main/day53/scripts/bank_eval.py
- Command line
-
hf download hf://td-builder/fbfhbfbfbv/day53/scripts/bank_eval.py
-
curl -L -o bank_eval.py https://huggingface.co/td-builder/fbfhbfbfbv/resolve/main/day53/scripts/bank_eval.py
7.62 kB
| #!/usr/bin/env python3 | |
| """bank_eval.py (day-52) — does the artifact bank COMPOUND solve-rate, weights FROZEN? | |
| Sealed meter = data/af/band_meter_v3.jsonl (320, DISJOINT from fuel_big). For each | |
| meter problem we RETRIEVE the top-K most similar banked (statement, solution) pairs | |
| by pure-python BM25 over tokenized statements, build a few-shot prompt (analogous | |
| solved problems from the disjoint pool — never the answer), greedy-generate with the | |
| frozen base, and grade on ALL the target's sampled tests. | |
| COMPOUNDING TEST: sweep bank size S over BANK_SIZES (first S bank records = the | |
| retrievable set; S=0 = no retrieval = frozen-base control). Compounding = solved/320 | |
| rises monotonically with S while the model never changes. | |
| Env: BASE (/workspace/RSI/newbase), METER, BANK (/workspace/RSI/outputs/bank.jsonl), | |
| BANK_SIZES ("0,100,300,600,1000"), K (3), EVAL_TC (40), MAXTOK (900), | |
| MAXLEN (16384), GPU_UTIL (0.85), N (meter subset cap, for smoke tests), | |
| OUT (/workspace/RSI/outputs/bank_eval.json). | |
| Prints: BANK_COMPOUNDING sizes=[...] solved=[...] | |
| """ | |
| import os, sys, json, math, re, tempfile, subprocess | |
| from collections import Counter | |
| from concurrent.futures import ThreadPoolExecutor | |
| sys.path.insert(0, "/workspace/RSI") | |
| from src.utils.vllm_backend import VLLMModelLoader # noqa: E402 | |
| from src.utils.external_benchmarks import _extract_code # noqa: E402 | |
| BASE = os.environ.get("BASE", "/workspace/RSI/newbase") | |
| METER = os.environ.get("METER", "/workspace/RSI/data/af/band_meter_v3.jsonl") | |
| BANK = os.environ.get("BANK", "/workspace/RSI/outputs/bank.jsonl") | |
| SIZES = [int(x) for x in os.environ.get("BANK_SIZES", "0,100,300,600,1000").split(",")] | |
| K = int(os.environ.get("K", "3")) | |
| EVAL_TC = int(os.environ.get("EVAL_TC", "40")) | |
| MAXTOK = int(os.environ.get("MAXTOK", "900")) | |
| MAXLEN = int(os.environ.get("MAXLEN", "16384")) | |
| N = int(os.environ.get("N", "100000")) | |
| OUT = os.environ.get("OUT", "/workspace/RSI/outputs/bank_eval.json") | |
| BNB = {"load_in_4bit": True, "bnb_4bit_compute_dtype": "bfloat16"} | |
| # ---- grading (identical semantics to band_ruler.py) ---- | |
| def _norm(s): return "\n".join(l.rstrip() for l in str(s).strip().splitlines()) | |
| def _run1(code, inp): | |
| d = tempfile.mkdtemp(); p = os.path.join(d, "s.py"); open(p, "w").write(code) | |
| try: return subprocess.run([sys.executable, p], input=str(inp), capture_output=True, text=True, timeout=6).stdout | |
| except Exception: return "" | |
| finally: | |
| try: os.remove(p); os.rmdir(d) | |
| except Exception: pass | |
| _pool = ThreadPoolExecutor(max_workers=96) | |
| def grade(code, ins, outs): | |
| if not code or not ins: return 0.0 | |
| tc = list(zip(ins, outs)); step = max(1, len(tc)//EVAL_TC); tc = tc[::step][:EVAL_TC] | |
| return sum(_pool.map(lambda io: _norm(_run1(code, io[0])) == _norm(io[1]), tc)) / max(1, len(tc)) | |
| # ---- pure-python BM25 (no deps) ---- | |
| _tok_re = re.compile(r"[a-z0-9]+") | |
| def toks(s): return _tok_re.findall(s.lower()) | |
| class BM25: | |
| def __init__(self, docs, k1=1.5, b=0.75): | |
| self.k1, self.b = k1, b | |
| self.docs = [Counter(toks(d)) for d in docs] | |
| self.dl = [sum(c.values()) for c in self.docs] | |
| self.avgdl = (sum(self.dl) / len(self.dl)) if self.dl else 1.0 | |
| df = Counter() | |
| for c in self.docs: df.update(c.keys()) | |
| n = len(self.docs) | |
| self.idf = {t: math.log(1 + (n - d + 0.5) / (d + 0.5)) for t, d in df.items()} | |
| def top(self, query, k): | |
| q = toks(query) | |
| scores = [] | |
| for i, c in enumerate(self.docs): | |
| s = 0.0 | |
| for t in q: | |
| f = c.get(t, 0) | |
| if not f: continue | |
| s += self.idf.get(t, 0.0) * f * (self.k1 + 1) / (f + self.k1 * (1 - self.b + self.b * self.dl[i] / self.avgdl)) | |
| scores.append((s, i)) | |
| scores.sort(reverse=True) | |
| return [i for s, i in scores[:k] if s > 0] | |
| def split_chat(p): | |
| """(prefix incl user tag, user body, suffix from user <|im_end|>).""" | |
| a = p.find("<|im_start|>user\n") | |
| if a == -1: return None | |
| a += len("<|im_start|>user\n") | |
| b = p.find("<|im_end|>", a) | |
| if b == -1: return None | |
| return p[:a], p[a:b], p[b:] | |
| def fewshot_prompt(target_prompt, exemplars): | |
| """exemplars = [(statement, solution)]; insert into the user turn, budget to MAXLEN.""" | |
| sp = split_chat(target_prompt) | |
| if sp is None: | |
| prefix, body, suffix = "", target_prompt, "" | |
| else: | |
| prefix, body, suffix = sp | |
| budget = (MAXLEN - MAXTOK - 128) * 3 # ~3 chars/token, conservative | |
| exs = list(exemplars) | |
| while True: | |
| blocks = [] | |
| for i, (st, sol) in enumerate(exs, 1): | |
| blocks.append(f"### Solved example {i}\nProblem:\n{st[:1500]}\n\nCorrect solution:\n```python\n{sol[:2500]}\n```\n") | |
| head = ("Here are previously solved problems similar to the one you must solve. " | |
| "Study their approaches, then solve the NEW problem.\n\n" + "\n".join(blocks) + | |
| "\n### NEW problem (solve THIS one)\n") if exs else "" | |
| full = prefix + head + body + suffix | |
| if len(full) <= budget or not exs: return full | |
| exs = exs[:-1] # drop least-similar exemplar until it fits | |
| meter = [json.loads(l) for l in open(METER) if l.strip()][:N] | |
| for r in meter: | |
| assert r.get("prompt") and r.get("inputs") and r.get("outputs"), f"bad meter row {r.get('pid')}" | |
| bank = [json.loads(l) for l in open(BANK) if l.strip()] if os.path.exists(BANK) else [] | |
| SIZES = sorted(set(min(s, len(bank)) for s in SIZES)) | |
| print(f"[bank_eval] meter={len(meter)} bank={len(bank)} sizes={SIZES} K={K} BASE={BASE}", flush=True) | |
| loader = VLLMModelLoader(model_path=BASE, dtype="bfloat16", max_model_len=MAXLEN, | |
| gpu_memory_utilization=float(os.environ.get("GPU_UTIL", "0.85")), | |
| allow_remote_code=True, quantization_config=BNB, enforce_eager=True) | |
| loader.load() | |
| assert getattr(loader, "_llm", None) is not None, "FATAL: vLLM did not load (no cross-backend verdicts)" | |
| print(f"[bank_eval] loaded {BASE} backend=vllm (FROZEN, no adapter)", flush=True) | |
| results = {} # size -> {"solved": int, "bits": [...], "mean_partial": float} | |
| for S in SIZES: | |
| sub = bank[:S] | |
| if S > 0: | |
| bm = BM25([b["statement"] for b in sub]) | |
| prompts = [] | |
| n_with_ex = 0 | |
| for r in meter: | |
| if S == 0: | |
| prompts.append(r["prompt"]); continue | |
| stmt = split_chat(r["prompt"]) | |
| query = stmt[1] if stmt else r["prompt"] | |
| idxs = bm.top(query, K) | |
| exs = [(sub[i]["statement"], sub[i]["solution"]) for i in idxs] | |
| if exs: n_with_ex += 1 | |
| prompts.append(fewshot_prompt(r["prompt"], exs)) | |
| print(f"[bank_eval] S={S}: {n_with_ex}/{len(meter)} meter problems got exemplars; generating (greedy)...", flush=True) | |
| outs = loader.generate_batch(prompts, max_new_tokens=MAXTOK, temperature=0.0, top_p=1.0) | |
| bits, part = [], [] | |
| for r, o in zip(meter, outs): | |
| g = grade(_extract_code(o) or o, r["inputs"], r["outputs"]) | |
| part.append(g); bits.append(1 if g >= 0.999 else 0) | |
| results[S] = {"solved": sum(bits), "bits": bits, "mean_partial": sum(part) / len(part)} | |
| print(f"[bank_eval] S={S}: solved {sum(bits)}/{len(meter)} mean_partial={sum(part)/len(part):.4f}", flush=True) | |
| os.makedirs(os.path.dirname(OUT), exist_ok=True) | |
| json.dump({"base": BASE, "meter": METER, "bank": BANK, "k": K, | |
| "pids": [r["pid"] for r in meter], | |
| "sizes": SIZES, "results": {str(s): results[s] for s in SIZES}}, open(OUT, "w")) | |
| print(f"[bank_eval] results -> {OUT}", flush=True) | |
| print(f"BANK_COMPOUNDING sizes={SIZES} solved={[results[s]['solved'] for s in SIZES]}", flush=True) | |