| """Stability check for boundary-behavior items (the Q-09 protocol): run |
| each selected item once greedy plus N sampled runs (temperature 0.7) and |
| report direction / decline counts per run. The auto-labels are heuristic; |
| verify the saved responses by hand before quoting a flip rate. |
| |
| Usage: python flip_rate.py --ids D01 A44 --n 5 |
| """ |
| import argparse, json, os, re, time |
|
|
| import torch |
| from bench_common import get_pipe, save_outputs, GEN_MODEL, MODEL_TAG |
| from run_own_eval import (load_corpus, chunk_corpus, build_retriever, |
| load_eval_set, SYSTEM_PREAMBLE, DECLINE_MARKERS) |
|
|
| SCRATCH = os.environ.get("SCRATCH", ".") |
| TEMPERATURE = 0.7 |
| TOP_P = 0.9 |
|
|
|
|
| def gen(user_block, do_sample, seed=None, tokens=400): |
| pipe = get_pipe() |
| if seed is not None: |
| torch.manual_seed(seed) |
| messages = [{"role": "user", |
| "content": SYSTEM_PREAMBLE + "\n\n" + user_block}] |
| kwargs = dict(max_new_tokens=tokens, return_full_text=False) |
| if do_sample: |
| kwargs.update(do_sample=True, temperature=TEMPERATURE, top_p=TOP_P) |
| else: |
| kwargs.update(do_sample=False) |
| out = pipe(messages, **kwargs) |
| return out[0]["generated_text"].strip() |
|
|
|
|
| def classify(resp): |
| low = resp.lower() |
| declined = any(m in low for m in DECLINE_MARKERS) |
| m = re.search(r"\b(yes|no)\b", low[:300]) |
| lead = m.group(1) if m else None |
| return {"declined": declined, "leading_yes_no": lead} |
|
|
|
|
| def main(): |
| ap = argparse.ArgumentParser() |
| ap.add_argument("--ids", nargs="+", default=["D01", "A44"]) |
| ap.add_argument("--n", type=int, default=5, help="sampled runs per item") |
| ap.add_argument("--k", type=int, default=5) |
| args = ap.parse_args() |
|
|
| print(f"generator: {GEN_MODEL} tag: {MODEL_TAG or '(none)'}") |
| items = {i["id"]: i for i in load_eval_set()} |
| targets = [items[i] for i in args.ids] |
|
|
| retriever = build_retriever(chunk_corpus(load_corpus())) |
|
|
| samples = [] |
| for item in targets: |
| hits = retriever.search(item["question"], k=args.k) |
| context = "\n\n".join( |
| f'[{j+1}] ({h["id"]}): "{h["text"]}"' |
| for j, (h, _) in enumerate(hits)) |
| block = f"Context:\n{context}\n\nQuestion: {item['question']}" |
| print(f"\n=== {item['id']}: {item['question'][:80]}") |
| print(" retrieved:", [h["id"] for h, _ in hits]) |
|
|
| runs = [] |
| t0 = time.time() |
| resp = gen(block, do_sample=False) |
| runs.append({"run": "greedy", **classify(resp), "response": resp}) |
| print(f" greedy: {classify(resp)} ({time.time()-t0:.0f}s)") |
| for s in range(args.n): |
| t0 = time.time() |
| resp = gen(block, do_sample=True, seed=s) |
| runs.append({"run": f"sampled_seed{s}", **classify(resp), |
| "response": resp}) |
| print(f" seed {s}: {classify(resp)} ({time.time()-t0:.0f}s)") |
|
|
| sampled = [r for r in runs if r["run"] != "greedy"] |
| directions = [r["leading_yes_no"] for r in sampled] |
| declines = [r["declined"] for r in sampled] |
| samples.append({ |
| "id": item["id"], "question": item["question"], |
| "retrieved": [h["id"] for h, _ in hits], |
| "runs": runs, |
| "sampled_direction_counts": |
| {d: directions.count(d) for d in set(directions)}, |
| "sampled_decline_count": sum(declines), |
| "n_sampled": len(sampled), |
| }) |
|
|
| metrics = {s["id"]: {"direction_counts": s["sampled_direction_counts"], |
| "decline_count": s["sampled_decline_count"], |
| "n_sampled": s["n_sampled"]} |
| for s in samples} |
| save_outputs(f"{SCRATCH}/results", "fliprate", "rag", metrics, samples, |
| extra={"temperature": TEMPERATURE, "top_p": TOP_P, |
| "k": args.k, "protocol": "1 greedy + N sampled", |
| "note": "auto-labels are heuristic; verify by hand"}) |
|
|
|
|
| if __name__ == "__main__": |
| main() |
|
|