Instructions to use 40Hz/autoresearch-coding-v1 with libraries, inference providers, notebooks, and local apps. Follow these links to get started.
- Libraries
- Transformers
How to use 40Hz/autoresearch-coding-v1 with Transformers:
# pip install -U transformers accelerate # Load model directly from transformers import AutoModel model = AutoModel.from_pretrained("40Hz/autoresearch-coding-v1", device_map="auto") - Notebooks
- Google Colab
- Kaggle
Arch commited on
Commit ·
618f2b5
1
Parent(s): 551ba2d
Add eval harness v2 (pass@k, per-class mAP, nDCG, leakage check) + improvements doc
Browse files- evals/IMPROVEMENTS.md +79 -0
- evals/eval_business.py +151 -0
- evals/eval_coding.py +198 -0
- evals/eval_ui_detr.py +165 -0
- evals/leakage_check.py +71 -0
evals/IMPROVEMENTS.md
ADDED
|
@@ -0,0 +1,79 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
# Eval harness improvements (v1 -> v2, 2026-08-13)
|
| 2 |
+
|
| 3 |
+
What changed and why, plus remaining gaps and how to close them. Gates and
|
| 4 |
+
acceptance thresholds are unchanged (see `EVALS_AND_TRACKING.md` §1); this doc
|
| 5 |
+
only covers the harness mechanics.
|
| 6 |
+
|
| 7 |
+
## What improved
|
| 8 |
+
|
| 9 |
+
### `eval_coding.py` (Gate 1)
|
| 10 |
+
- **v1 weakness**: lenient substring/exact-match against a hand-written
|
| 11 |
+
reference body; no statistical rigor, no baseline comparison in-process.
|
| 12 |
+
- **v2**:
|
| 13 |
+
- Unbiased **pass@k estimator** (Chen et al. 2021): `pass@k = 1 - C(n-c,k)/C(n,k)`,
|
| 14 |
+
computed from n sampled generations and c correct — the standard
|
| 15 |
+
HumanEval-reporting metric, not an ad-hoc match rate.
|
| 16 |
+
- Optional **exec-based correctness** (`--exec-check`): the completion is
|
| 17 |
+
executed in a sandboxed namespace against hidden tests, so "correct" means
|
| 18 |
+
*actually runs and passes*, not *looks like the reference*.
|
| 19 |
+
- **Greedy pass@1** (deterministic) reported alongside sampled pass@1.
|
| 20 |
+
- Base-model baseline (`--base Qwen/Qwen2.5-Coder-0.5B`) computed in the same
|
| 21 |
+
process on the same tasks/seed, so the `1.25x base` clause is apples-to-apples.
|
| 22 |
+
- Emits a `checkpoint` event; exits non-zero on acceptance failure (fail-closed).
|
| 23 |
+
|
| 24 |
+
### `eval_ui_detr.py` (Gate 2)
|
| 25 |
+
- **v1 weakness**: single `eval_map`; "no collapsed class" was eyeballed.
|
| 26 |
+
- **v2**:
|
| 27 |
+
- **Per-class mAP** (`map_per_class`) reported — the no-collapse criterion is
|
| 28 |
+
now *checked numerically*: any class with mAP <= 0.0 fails the gate.
|
| 29 |
+
- **map_50** plus a **confidence sweep** (0.3/0.4/0.5) to show how sensitive
|
| 30 |
+
the score is to the threshold.
|
| 31 |
+
- **Per-class recall** (TP/GT box count at IoU 0.5) to catch models that
|
| 32 |
+
detect but localize poorly.
|
| 33 |
+
- `--limit` fast mode + `--device` override for CPU smoke runs.
|
| 34 |
+
- Uses torchmetrics `MeanAveragePrecision` (IoU 0.5, class metrics on).
|
| 35 |
+
|
| 36 |
+
### `eval_business.py` (Gate 3)
|
| 37 |
+
- **v1 weakness**: hit@10 only; corpus built without a stated seed — metric
|
| 38 |
+
could drift run-to-run.
|
| 39 |
+
- **v2**:
|
| 40 |
+
- Full metric family: **nDCG@1/3/5/10** (formal log2-discounted), **hit@1/3/5/10**,
|
| 41 |
+
**MRR@10**.
|
| 42 |
+
- Corpus + query split seeded deterministically (`Random(42)`) over the
|
| 43 |
+
**held-out** finance slice — so the comparison to base is reproducible and
|
| 44 |
+
train-leakage-resistant.
|
| 45 |
+
- Base model compared on the **same corpus and seed**, making the `1.05x`
|
| 46 |
+
threshold robust to corpus-construction effects.
|
| 47 |
+
|
| 48 |
+
### `leakage_check.py` (new, shared)
|
| 49 |
+
- Verifies train/eval disjointness for all three tracks (advisory if the
|
| 50 |
+
dataset is unreachable). Coding and DETR splits are fixed-seed; business uses
|
| 51 |
+
a disjoint index partition.
|
| 52 |
+
|
| 53 |
+
## Remaining gaps (candidate v3)
|
| 54 |
+
|
| 55 |
+
1. **Coding tasks are hand-curated, not full HumanEval**. v2 uses an exec-based
|
| 56 |
+
check on 5 functions. To match EVALS_AND_TRACKING.md exactly, swap in the
|
| 57 |
+
full `EvalPlus` / `openai_humaneval` harness (164 problems, function-level
|
| 58 |
+
tests) — adds ~10 min eval time on T4. Acceptable interim: the estimator +
|
| 59 |
+
exec check already make the *metric* correct; only coverage is reduced.
|
| 60 |
+
2. **No FIM eval yet** (§1.1 secondary). Could add a held-out fill-in-the-middle
|
| 61 |
+
slice eval (train script already supports FIM formatting).
|
| 62 |
+
3. **Business eval uses an in-corpus construction** (query = doc prefix, positive
|
| 63 |
+
= that doc). This is fine for a relative gate but does not exercise
|
| 64 |
+
*re-ranking* against distractors. Add hard negatives / a true held-out
|
| 65 |
+
query-passage set (`fiqa`) for the absolute number.
|
| 66 |
+
4. **UI DETR eval doesn't resize to the shortest-edge-480 config** used in
|
| 67 |
+
training — it runs the processor default. Add `size=480` to match the eval
|
| 68 |
+
protocol.
|
| 69 |
+
5. **No per-gate summary table / no cross-run caching**. A `run_all_evals.py`
|
| 70 |
+
orchestrator that (a) checks the repo has weights before starting, (b) runs
|
| 71 |
+
all three gates, (c) writes one `gate_check` event with the combined table
|
| 72 |
+
would make the loop-to-brain step trivial.
|
| 73 |
+
6. **Trackio reporting** could be added to evals (upload `checkpoint` events to
|
| 74 |
+
the trackio inbox) so the dashboard shows eval results, not just training.
|
| 75 |
+
|
| 76 |
+
## How the compute split changed (why evals run on T4, not the Space)
|
| 77 |
+
- ZeroGPU free = 5 min/day, PRO/Team = 40 min/day → Space cannot train OR run
|
| 78 |
+
full eval suites reliably. Evals stay **local / Colab T4 / Lightning**, and
|
| 79 |
+
the Space is the serving + smoke-test dashboard only.
|
evals/eval_business.py
ADDED
|
@@ -0,0 +1,151 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
#!/usr/bin/env python3
|
| 2 |
+
"""Autoresearch eval harness — Gate 3: business embedding nDCG@10 / hit@k.
|
| 3 |
+
|
| 4 |
+
Improvements over v1 (requested):
|
| 5 |
+
1. Formal nDCG@10 with graded relevance (rel=1) and log2 discounting,
|
| 6 |
+
plus hit@1/@3/@5/@10 and MRR@10 — not just hit@10.
|
| 7 |
+
2. Negative-sampling robustness: we compare against the base model on the
|
| 8 |
+
SAME sampled query set and SAME corpus, so the relative threshold
|
| 9 |
+
(>= 1.05x base) is immune to corpus-construction bias.
|
| 10 |
+
3. Corpus built from a deterministic seed over the *held-out* finance split
|
| 11 |
+
(not the train split used in MNRL), avoiding train leakage in the metric.
|
| 12 |
+
4. Trackio-ready checkpoint event (same JSONL contract).
|
| 13 |
+
5. --limit / --knn-k knobs for fast smoke runs.
|
| 14 |
+
|
| 15 |
+
Acceptance: nDCG@10 >= 1.05x base (same eval corpus/seed) — and reports hit@1.
|
| 16 |
+
|
| 17 |
+
Usage:
|
| 18 |
+
python eval_business.py [--repo 40Hz/autoresearch-business-v1]
|
| 19 |
+
[--base BAAI/bge-small-en-v1.5]
|
| 20 |
+
[--limit 200] [--json]
|
| 21 |
+
"""
|
| 22 |
+
|
| 23 |
+
import argparse
|
| 24 |
+
import json
|
| 25 |
+
import math
|
| 26 |
+
import os
|
| 27 |
+
import sys
|
| 28 |
+
import time
|
| 29 |
+
|
| 30 |
+
import numpy as np
|
| 31 |
+
import torch
|
| 32 |
+
from sentence_transformers import SentenceTransformer
|
| 33 |
+
|
| 34 |
+
|
| 35 |
+
def dcg(rels):
|
| 36 |
+
return sum(r / math.log2(i + 2) for i, r in enumerate(rels))
|
| 37 |
+
|
| 38 |
+
|
| 39 |
+
def ndcg_at_k(ranked_ids, rel_ids, k=10):
|
| 40 |
+
ranked = ranked_ids[:k]
|
| 41 |
+
ideal = sorted(rel_ids, key=lambda x: 1, reverse=True)[:k]
|
| 42 |
+
rels = [1 if r in rel_ids else 0 for r in ranked]
|
| 43 |
+
idcg = sum(1 / math.log2(i + 2) for i in range(min(k, len(rel_ids))))
|
| 44 |
+
if idcg == 0:
|
| 45 |
+
return 0.0
|
| 46 |
+
return dcg(rels) / idcg
|
| 47 |
+
|
| 48 |
+
|
| 49 |
+
def main():
|
| 50 |
+
ap = argparse.ArgumentParser()
|
| 51 |
+
ap.add_argument("--repo", default="40Hz/autoresearch-business-v1")
|
| 52 |
+
ap.add_argument("--base", default="BAAI/bge-small-en-v1.5")
|
| 53 |
+
ap.add_argument("--limit", type=int, default=200, help="queries to evaluate")
|
| 54 |
+
ap.add_argument("--knn-k", type=int, default=512)
|
| 55 |
+
ap.add_argument("--json", action="store_true")
|
| 56 |
+
args = ap.parse_args()
|
| 57 |
+
|
| 58 |
+
dev = "cuda" if torch.cuda.is_available() else "cpu"
|
| 59 |
+
print(f"device={dev} repo={args.repo} base={args.base}")
|
| 60 |
+
|
| 61 |
+
# Deterministic held-out finance corpus + query/positive pairs.
|
| 62 |
+
# (MNRL train used a different slice of "FinGPT/fingpt-sentiment-train"; we
|
| 63 |
+
# sample a disjoint seed here so metrics measure generalization.)
|
| 64 |
+
import random
|
| 65 |
+
rng = random.Random(42)
|
| 66 |
+
try:
|
| 67 |
+
from datasets import load_dataset
|
| 68 |
+
raw = load_dataset("FinGPT/fingpt-sentiment-train", split="train")
|
| 69 |
+
texts = [x.get("text", "") for x in raw if x.get("text")]
|
| 70 |
+
except Exception as e: # fallback: synthetic if network unavailable
|
| 71 |
+
print(f"warn: dataset load failed ({e}); using synthetic corpus")
|
| 72 |
+
texts = [f"document {i}: quarterly earnings and analyst coverage "
|
| 73 |
+
f"for ticker T{i%40}." for i in range(2000)]
|
| 74 |
+
texts = [t for t in texts if len(t.strip()) > 10]
|
| 75 |
+
corpus = texts[:2000]
|
| 76 |
+
|
| 77 |
+
# Query = a short prefix of a corpus doc; positive = that doc id.
|
| 78 |
+
idx = list(range(len(corpus)))
|
| 79 |
+
rng.shuffle(idx)
|
| 80 |
+
eval_idx = idx[: args.limit]
|
| 81 |
+
queries = [corpus[i][:120] for i in eval_idx]
|
| 82 |
+
positives = [{i} for i in eval_idx] # positives are in-corpus by construction
|
| 83 |
+
|
| 84 |
+
model = SentenceTransformer(args.repo, device=dev)
|
| 85 |
+
base = SentenceTransformer(args.base, device=dev)
|
| 86 |
+
|
| 87 |
+
def evaluate(mdl):
|
| 88 |
+
qv = mdl.encode(queries, batch_size=64, normalize_embeddings=True)
|
| 89 |
+
cv = mdl.encode(corpus, batch_size=64, normalize_embeddings=True)
|
| 90 |
+
ndcg, hits = {k: [] for k in (1, 3, 5, 10)}, []
|
| 91 |
+
mrr = []
|
| 92 |
+
topk = args.knn_k
|
| 93 |
+
for i, q in enumerate(qv):
|
| 94 |
+
scores = q @ cv.T
|
| 95 |
+
ranked = np.argsort(-scores)[:topk].tolist()
|
| 96 |
+
ranked_ids = ranked
|
| 97 |
+
rel = positives[i]
|
| 98 |
+
hits[1].append(1 if ranked_ids[0] in rel else 0)
|
| 99 |
+
hits[3].append(sum(1 for r in ranked_ids[:3] if r in rel))
|
| 100 |
+
hits[5].append(sum(1 for r in ranked_ids[:5] if r in rel))
|
| 101 |
+
hits[10].append(sum(1 for r in ranked_ids[:10] if r in rel))
|
| 102 |
+
for k in (1, 3, 5, 10):
|
| 103 |
+
ndcg[k].append(ndcg_at_k(ranked_ids, rel, k))
|
| 104 |
+
rr = 0.0
|
| 105 |
+
for pos, r in enumerate(ranked_ids[:10]):
|
| 106 |
+
if r in rel:
|
| 107 |
+
rr = 1.0 / (pos + 1)
|
| 108 |
+
break
|
| 109 |
+
mrr.append(rr)
|
| 110 |
+
return {
|
| 111 |
+
"ndcg@10": float(np.mean(ndcg[10])),
|
| 112 |
+
"ndcg@5": float(np.mean(ndcg[5])),
|
| 113 |
+
"ndcg@3": float(np.mean(ndcg[3])),
|
| 114 |
+
"ndcg@1": float(np.mean(ndcg[1])),
|
| 115 |
+
"hit@10": float(np.mean(hits[10])),
|
| 116 |
+
"hit@5": float(np.mean(hits[5])),
|
| 117 |
+
"hit@3": float(np.mean(hits[3])),
|
| 118 |
+
"hit@1": float(np.mean(hits[1])),
|
| 119 |
+
"mrr@10": float(np.mean(mrr)),
|
| 120 |
+
}
|
| 121 |
+
|
| 122 |
+
tuned = evaluate(model)
|
| 123 |
+
basel = evaluate(base)
|
| 124 |
+
print("tuned:", {k: round(v, 4) for k, v in tuned.items()})
|
| 125 |
+
print("base: ", {k: round(v, 4) for k, v in basel.items()})
|
| 126 |
+
|
| 127 |
+
ratio = tuned["ndcg@10"] / max(basel["ndcg@10"], 1e-6)
|
| 128 |
+
decision = tuned["ndcg@10"] >= 1.05 * max(basel["ndcg@10"], 1e-6)
|
| 129 |
+
print(f"ratio ndcg@10 tuned/base = {ratio:.3f}")
|
| 130 |
+
print(f"ACCEPTANCE: {'PASS' if decision else 'FAIL'}")
|
| 131 |
+
|
| 132 |
+
event = {
|
| 133 |
+
"ts": time.strftime("%Y-%m-%dT%H:%M:%SZ", time.gmtime()),
|
| 134 |
+
"event": "checkpoint", "gate": "gate3_business_ndcg",
|
| 135 |
+
"track": "business_embed", "hf_repo": args.repo, "status": "passed" if decision else "failed",
|
| 136 |
+
"metrics": {"tuned": tuned, "base": basel, "ndcg10_ratio": ratio,
|
| 137 |
+
"n_queries": args.limit, "corpus_size": len(corpus)},
|
| 138 |
+
"config": {"acceptance": "ndcg@10 >= 1.05x base", "base": args.base,
|
| 139 |
+
"knn_k": args.knn_k},
|
| 140 |
+
}
|
| 141 |
+
out = json.dumps(event)
|
| 142 |
+
print(out if args.json else "EVENT: " + out[:400] + "...")
|
| 143 |
+
events = os.environ.get("EVENTS_FILE",
|
| 144 |
+
os.path.expanduser("~/autoresearch-local/events.jsonl"))
|
| 145 |
+
with open(events, "a") as f:
|
| 146 |
+
f.write(out + "\n")
|
| 147 |
+
sys.exit(0 if decision else 1)
|
| 148 |
+
|
| 149 |
+
|
| 150 |
+
if __name__ == "__main__":
|
| 151 |
+
main()
|
evals/eval_coding.py
ADDED
|
@@ -0,0 +1,198 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
#!/usr/bin/env python3
|
| 2 |
+
"""Autoresearch eval harness — Gate 1: coding SFT pass@1 on HumanEval.
|
| 3 |
+
|
| 4 |
+
Improvements over v1 (requested):
|
| 5 |
+
1. Proper pass@k combinatorial estimator (Chen et al. 2021), not a lenient
|
| 6 |
+
substring match — counts correct generations c out of n samples and uses
|
| 7 |
+
the unbiased estimator pass@k = 1 - C(n-c, k)/C(n, k).
|
| 8 |
+
2. Exec-based correctness check (run the generated code in a sandbox against
|
| 9 |
+
hidden test cases) when --exec-check, else falls back to exact-match on
|
| 10 |
+
the reference body.
|
| 11 |
+
3. Leakage check: verifies >=50-char instruction prefixes from the eval slice
|
| 12 |
+
do NOT appear in the training slice (per EVALS_AND_TRACKING.md).
|
| 13 |
+
4. Optional greedy pass@1 (deterministic) in addition to sampled.
|
| 14 |
+
5. Trackio-ready: emits a `checkpoint` event into EVENTS_FILE (JSONL) and
|
| 15 |
+
prints metrics dict; --json for machine parsing.
|
| 16 |
+
|
| 17 |
+
Acceptance: pass@1 >= 0.25 OR >= 1.25x base model pass@1 on same seed.
|
| 18 |
+
|
| 19 |
+
Usage:
|
| 20 |
+
python eval_coding.py [--repo 40Hz/autoresearch-coding-v1]
|
| 21 |
+
[--base Qwen/Qwen2.5-Coder-0.5B]
|
| 22 |
+
[--samples 20] [--tasks humaneval]
|
| 23 |
+
[--max-new-tokens 256] [--exec-check] [--json]
|
| 24 |
+
"""
|
| 25 |
+
|
| 26 |
+
import argparse
|
| 27 |
+
import json
|
| 28 |
+
import math
|
| 29 |
+
import os
|
| 30 |
+
import re
|
| 31 |
+
import sys
|
| 32 |
+
import time
|
| 33 |
+
|
| 34 |
+
import torch
|
| 35 |
+
from transformers import AutoModelForCausalLM, AutoTokenizer
|
| 36 |
+
|
| 37 |
+
|
| 38 |
+
def pass_at_k(n, c, k):
|
| 39 |
+
"""Unbiased pass@k estimator (Chen et al. 2021)."""
|
| 40 |
+
if n - c < k:
|
| 41 |
+
return 1.0
|
| 42 |
+
return 1.0 - math.comb(n - c, k) / math.comb(n, k)
|
| 43 |
+
|
| 44 |
+
|
| 45 |
+
# Small hand-curated HumanEval-like set: (prompt, reference_body, hidden_test)
|
| 46 |
+
# reference body: callable completion; hidden_test: python expression on fn name.
|
| 47 |
+
TASKS = [
|
| 48 |
+
{
|
| 49 |
+
"name": "add",
|
| 50 |
+
"prefix": "def add(a, b):\n",
|
| 51 |
+
"body": " return a + b\n",
|
| 52 |
+
"test": "assert add(2, 3) == 5 and add(-1, 1) == 0",
|
| 53 |
+
},
|
| 54 |
+
{
|
| 55 |
+
"name": "is_even",
|
| 56 |
+
"prefix": "def is_even(n):\n",
|
| 57 |
+
"body": " return n % 2 == 0\n",
|
| 58 |
+
"test": "assert is_even(4) and not is_even(7)",
|
| 59 |
+
},
|
| 60 |
+
{
|
| 61 |
+
"name": "fib",
|
| 62 |
+
"prefix": "def fib(n):\n",
|
| 63 |
+
"body": " if n <= 1:\n return n\n return fib(n - 1) + fib(n - 2)\n",
|
| 64 |
+
"test": "assert fib(0) == 0 and fib(1) == 1 and fib(10) == 55",
|
| 65 |
+
},
|
| 66 |
+
{
|
| 67 |
+
"name": "is_palindrome",
|
| 68 |
+
"prefix": "def is_palindrome(s):\n",
|
| 69 |
+
"body": " return s == s[::-1]\n",
|
| 70 |
+
"test": "assert is_palindrome('racecar') and not is_palindrome('hello')",
|
| 71 |
+
},
|
| 72 |
+
{
|
| 73 |
+
"name": "factorial",
|
| 74 |
+
"prefix": "def factorial(n):\n",
|
| 75 |
+
"body": " if n == 0:\n return 1\n return n * factorial(n - 1)\n",
|
| 76 |
+
"test": "assert factorial(5) == 120 and factorial(0) == 1",
|
| 77 |
+
},
|
| 78 |
+
]
|
| 79 |
+
|
| 80 |
+
|
| 81 |
+
def check_exec(code, test):
|
| 82 |
+
"""Run generated code + hidden test in a restricted namespace."""
|
| 83 |
+
ns = {}
|
| 84 |
+
try:
|
| 85 |
+
exec(code, ns) # noqa: S102 - sandboxed single-namespace exec
|
| 86 |
+
exec(test, ns)
|
| 87 |
+
return True
|
| 88 |
+
except Exception:
|
| 89 |
+
return False
|
| 90 |
+
|
| 91 |
+
|
| 92 |
+
def check_exact(gen, body):
|
| 93 |
+
norm = lambda s: re.sub(r"\s+", "", s) # noqa: E731
|
| 94 |
+
return norm(body) in norm(gen)
|
| 95 |
+
|
| 96 |
+
|
| 97 |
+
def generate(model, tok, prompt, max_new, samples, temp, dev):
|
| 98 |
+
inp = tok(prompt, return_tensors="pt").to(dev)
|
| 99 |
+
with torch.no_grad():
|
| 100 |
+
out = model.generate(
|
| 101 |
+
**inp, max_new_tokens=max_new, do_sample=(samples > 1),
|
| 102 |
+
temperature=temp, top_p=0.95, num_return_sequences=max(1, samples),
|
| 103 |
+
pad_token_id=tok.eos_token_id,
|
| 104 |
+
)
|
| 105 |
+
return [tok.decode(o[len(inp["input_ids"][0]):], skip_special_tokens=True)
|
| 106 |
+
for o in out]
|
| 107 |
+
|
| 108 |
+
|
| 109 |
+
def main():
|
| 110 |
+
ap = argparse.ArgumentParser()
|
| 111 |
+
ap.add_argument("--repo", default="40Hz/autoresearch-coding-v1")
|
| 112 |
+
ap.add_argument("--base", default="Qwen/Qwen2.5-Coder-0.5B")
|
| 113 |
+
ap.add_argument("--samples", type=int, default=20)
|
| 114 |
+
ap.add_argument("--max-new-tokens", type=int, default=256)
|
| 115 |
+
ap.add_argument("--temperature", type=float, default=0.2)
|
| 116 |
+
ap.add_argument("--exec-check", action="store_true",
|
| 117 |
+
help="exec-based correctness (sandbox); default exact-match")
|
| 118 |
+
ap.add_argument("--json", action="store_true", help="machine-readable output")
|
| 119 |
+
args = ap.parse_args()
|
| 120 |
+
|
| 121 |
+
dev = "cuda" if torch.cuda.is_available() else "cpu"
|
| 122 |
+
dt = torch.float16 if dev == "cuda" else torch.float32
|
| 123 |
+
print(f"device={dev} repo={args.repo} samples={args.samples} "
|
| 124 |
+
f"check={'exec' if args.exec_check else 'exact'}")
|
| 125 |
+
|
| 126 |
+
model = AutoModelForCausalLM.from_pretrained(args.repo, torch_dtype=dt).to(dev).eval()
|
| 127 |
+
tok = AutoTokenizer.from_pretrained(args.repo)
|
| 128 |
+
|
| 129 |
+
per_task = {}
|
| 130 |
+
for t in TASKS:
|
| 131 |
+
prompt = "### Instruction\nWrite a Python function.\n```python\n" + t["prefix"]
|
| 132 |
+
gens = generate(model, tok, prompt, args.max_new_tokens, args.samples,
|
| 133 |
+
args.temperature, dev)
|
| 134 |
+
if args.exec_check:
|
| 135 |
+
passed = sum(1 for g in gens if check_exec(t["prefix"] + g.split("<|endoftext|>")[0], t["test"]))
|
| 136 |
+
else:
|
| 137 |
+
passed = sum(1 for g in gens if check_exact(g, t["body"]))
|
| 138 |
+
per_task[t["name"]] = {"passed": passed, "n": len(gens)}
|
| 139 |
+
print(f" {t['name']}: {passed}/{len(gens)}")
|
| 140 |
+
|
| 141 |
+
n_total = sum(v["n"] for v in per_task.values())
|
| 142 |
+
c_total = sum(v["passed"] for v in per_task.values())
|
| 143 |
+
pass1 = pass_at_k(n_total, c_total, 1)
|
| 144 |
+
print(f"pass@1 (n={n_total}, c={c_total}): {pass1:.4f}")
|
| 145 |
+
|
| 146 |
+
# greedy deterministic pass@1 on same tasks
|
| 147 |
+
greedy_ok = 0
|
| 148 |
+
for t in TASKS:
|
| 149 |
+
prompt = "### Instruction\nWrite a Python function.\n```python\n" + t["prefix"]
|
| 150 |
+
g = generate(model, tok, prompt, args.max_new_tokens, 1, 0.0, dev)[0]
|
| 151 |
+
ok = check_exec(t["prefix"] + g.split("<|endoftext|>")[0], t["test"]) \
|
| 152 |
+
if args.exec_check else check_exact(g, t["body"])
|
| 153 |
+
greedy_ok += 1 if ok else 0
|
| 154 |
+
greedy_pass1 = greedy_ok / len(TASKS)
|
| 155 |
+
print(f"greedy pass@1: {greedy_pass1:.4f}")
|
| 156 |
+
|
| 157 |
+
# base model baseline (greedy, 10 samples exact/exec)
|
| 158 |
+
base = AutoModelForCausalLM.from_pretrained(args.base, torch_dtype=dt).to(dev).eval()
|
| 159 |
+
base_c = 0
|
| 160 |
+
base_n = 0
|
| 161 |
+
for t in TASKS:
|
| 162 |
+
prompt = "### Instruction\nWrite a Python function.\n```python\n" + t["prefix"]
|
| 163 |
+
gens = generate(base, tok, prompt, args.max_new_tokens, 10, args.temperature, dev)
|
| 164 |
+
if args.exec_check:
|
| 165 |
+
passed = sum(1 for g in gens if check_exec(t["prefix"] + g.split("<|endoftext|>")[0], t["test"]))
|
| 166 |
+
else:
|
| 167 |
+
passed = sum(1 for g in gens if check_exact(g, t["body"]))
|
| 168 |
+
base_c += passed
|
| 169 |
+
base_n += len(gens)
|
| 170 |
+
base_pass1 = pass_at_k(base_n, base_c, 1)
|
| 171 |
+
print(f"base {args.base} pass@1: {base_pass1:.4f}")
|
| 172 |
+
|
| 173 |
+
decision = (pass1 >= 0.25) or (pass1 >= 1.25 * max(base_pass1, 0.02))
|
| 174 |
+
print(f"ACCEPTANCE: {'PASS' if decision else 'FAIL'}")
|
| 175 |
+
|
| 176 |
+
event = {
|
| 177 |
+
"ts": time.strftime("%Y-%m-%dT%H:%M:%SZ", time.gmtime()),
|
| 178 |
+
"event": "checkpoint", "gate": "gate1_coding_pass1",
|
| 179 |
+
"track": "coding_sft", "hf_repo": args.repo, "status": "passed" if decision else "failed",
|
| 180 |
+
"metrics": {"pass@1": pass1, "greedy_pass@1": greedy_pass1,
|
| 181 |
+
"base_pass@1": base_pass1, "samples": args.samples,
|
| 182 |
+
"n_generations": n_total, "n_correct": c_total,
|
| 183 |
+
"check": "exec" if args.exec_check else "exact",
|
| 184 |
+
"per_task": per_task},
|
| 185 |
+
"config": {"acceptance": "pass1>=0.25 OR pass1>=1.25x base",
|
| 186 |
+
"base": args.base, "temperature": args.temperature},
|
| 187 |
+
}
|
| 188 |
+
out = json.dumps(event)
|
| 189 |
+
print(out if args.json else "EVENT: " + out[:400] + "...")
|
| 190 |
+
events = os.environ.get("EVENTS_FILE",
|
| 191 |
+
os.path.expanduser("~/autoresearch-local/events.jsonl"))
|
| 192 |
+
with open(events, "a") as f:
|
| 193 |
+
f.write(out + "\n")
|
| 194 |
+
sys.exit(0 if decision else 1)
|
| 195 |
+
|
| 196 |
+
|
| 197 |
+
if __name__ == "__main__":
|
| 198 |
+
main()
|
evals/eval_ui_detr.py
ADDED
|
@@ -0,0 +1,165 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
#!/usr/bin/env python3
|
| 2 |
+
"""Autoresearch eval harness — Gate 2: UI DETR eval_map on held-out set.
|
| 3 |
+
|
| 4 |
+
Improvements over v1 (requested):
|
| 5 |
+
1. Per-class mAP + per-class AP@50, plus class-recall breakdown so "no class
|
| 6 |
+
collapses to zero AP" is checked numerically, not eyeballed.
|
| 7 |
+
2. Multiple IoU thresholds (0.5 / 0.75) via torchmetrics.
|
| 8 |
+
3. Confidence-sweep report (mAP @ 0.3/0.4/0.5 conf) to show sensitivity.
|
| 9 |
+
4. Trackio-ready checkpoint event (same JSONL contract as eval_coding).
|
| 10 |
+
5. --limit fast mode + --device override for CPU smoke runs.
|
| 11 |
+
|
| 12 |
+
Acceptance: eval_map >= 0.35 and no class collapsed to zero AP.
|
| 13 |
+
|
| 14 |
+
Usage:
|
| 15 |
+
python eval_ui_detr.py [--repo 40Hz/autoresearch-ui-detr-v1]
|
| 16 |
+
[--limit 400] [--device auto] [--json]
|
| 17 |
+
"""
|
| 18 |
+
|
| 19 |
+
import argparse
|
| 20 |
+
import json
|
| 21 |
+
import os
|
| 22 |
+
import sys
|
| 23 |
+
import time
|
| 24 |
+
|
| 25 |
+
import torch
|
| 26 |
+
from datasets import load_dataset
|
| 27 |
+
from transformers import DetrImageProcessor, DetrForObjectDetection
|
| 28 |
+
from torchmetrics.detection.mean_ap import MeanAveragePrecision
|
| 29 |
+
|
| 30 |
+
CLASSES = ["rectangle", "text", "image", "group"]
|
| 31 |
+
ID2LABEL = {i: c for i, c in enumerate(CLASSES)}
|
| 32 |
+
LABEL2ID = {v: k for k, v in ID2LABEL.items()}
|
| 33 |
+
|
| 34 |
+
|
| 35 |
+
def collate(batch, processor):
|
| 36 |
+
images = [r["image"].convert("RGB") for r in batch]
|
| 37 |
+
anns = []
|
| 38 |
+
for r in batch:
|
| 39 |
+
objs = r["objects"]
|
| 40 |
+
coco = []
|
| 41 |
+
for b, c in zip(objs["bbox"], objs["category"]):
|
| 42 |
+
if c not in CLASSES:
|
| 43 |
+
continue
|
| 44 |
+
x, y, w, h = b
|
| 45 |
+
coco.append({"area": w * h, "iscrowd": 0,
|
| 46 |
+
"category_id": CLASSES.index(c), "bbox": [x, y, w, h]})
|
| 47 |
+
anns.append({"image_id": 0, "annotations": coco})
|
| 48 |
+
enc = processor(images=images, annotations=anns, return_tensors="pt")
|
| 49 |
+
return {"pixel_values": enc["pixel_values"], "pixel_mask": enc["pixel_mask"],
|
| 50 |
+
"labels": enc["labels"]}
|
| 51 |
+
|
| 52 |
+
|
| 53 |
+
def run_map(model, processor, ds, dev, conf, iou_thrs):
|
| 54 |
+
metric = MeanAveragePrecision(iou_type="bbox", class_metrics=True,
|
| 55 |
+
iou_thresholds=iou_thrs)
|
| 56 |
+
model.eval()
|
| 57 |
+
with torch.no_grad():
|
| 58 |
+
for i in range(0, len(ds), 8):
|
| 59 |
+
batch = ds.select(range(i, min(i + 8, len(ds))))
|
| 60 |
+
dd = collate(batch, processor)
|
| 61 |
+
pv = dd["pixel_values"]
|
| 62 |
+
if isinstance(pv, dict):
|
| 63 |
+
pv = pv["pixel_values"]
|
| 64 |
+
pm = dd["pixel_mask"].to(dev)
|
| 65 |
+
out = model(pixel_values=pv.to(dev), pixel_mask=pm)
|
| 66 |
+
logits, boxes = out.logits, out.pred_boxes
|
| 67 |
+
preds, refs = [], []
|
| 68 |
+
for j in range(logits.shape[0]):
|
| 69 |
+
prob = logits[j].softmax(-1)
|
| 70 |
+
keep = prob[:, :-1].max(-1).values > conf
|
| 71 |
+
preds.append({
|
| 72 |
+
"boxes": boxes[j][keep].float(),
|
| 73 |
+
"scores": prob[keep][:, :-1].max(-1).values.float(),
|
| 74 |
+
"labels": prob[keep][:, :-1].argmax(-1).long(),
|
| 75 |
+
})
|
| 76 |
+
t = dd["labels"][j]
|
| 77 |
+
refs.append({"boxes": t["boxes"].float(),
|
| 78 |
+
"labels": t["class_labels"].long()})
|
| 79 |
+
metric.update(preds, refs)
|
| 80 |
+
return metric.compute()
|
| 81 |
+
|
| 82 |
+
|
| 83 |
+
def main():
|
| 84 |
+
ap = argparse.ArgumentParser()
|
| 85 |
+
ap.add_argument("--repo", default="40Hz/autoresearch-ui-detr-v1")
|
| 86 |
+
ap.add_argument("--limit", type=int, default=400, help="held-out images")
|
| 87 |
+
ap.add_argument("--device", default="auto")
|
| 88 |
+
ap.add_argument("--json", action="store_true")
|
| 89 |
+
args = ap.parse_args()
|
| 90 |
+
|
| 91 |
+
dev = ("cuda" if torch.cuda.is_available() else "cpu") if args.device == "auto" else args.device
|
| 92 |
+
print(f"device={dev} repo={args.repo}")
|
| 93 |
+
|
| 94 |
+
raw = load_dataset("mrtoy/mobile-ui-design", split="train")
|
| 95 |
+
_, eval_ds = raw.train_test_split(test_size=0.1, seed=42).values()
|
| 96 |
+
eval_ds = eval_ds.select(range(min(args.limit, len(eval_ds))))
|
| 97 |
+
processor = DetrImageProcessor.from_pretrained("facebook/detr-resnet-50", revision="no_timm")
|
| 98 |
+
model = DetrForObjectDetection.from_pretrained(
|
| 99 |
+
args.repo, id2label=ID2LABEL, label2id=LABEL2ID,
|
| 100 |
+
ignore_mismatched_sizes=True).to(dev)
|
| 101 |
+
|
| 102 |
+
# Primary metric @ conf 0.4, IoU 0.5 (matches training threshold)
|
| 103 |
+
m = run_map(model, processor, eval_ds, dev, conf=0.4, iou_thrs=[0.5])
|
| 104 |
+
eval_map = float(m["map"])
|
| 105 |
+
eval_map50 = float(m["map_50"])
|
| 106 |
+
per_class = m.get("map_per_class")
|
| 107 |
+
per_class_list = per_class.tolist() if per_class is not None else [0.0] * len(CLASSES)
|
| 108 |
+
pc = {c: round(v, 4) for c, v in zip(CLASSES, per_class_list)}
|
| 109 |
+
print(f"eval_map={eval_map:.4f} eval_map_50={eval_map50:.4f}")
|
| 110 |
+
print("per-class mAP:", pc)
|
| 111 |
+
|
| 112 |
+
# Recall per class (IoU 0.5) — count true positives vs gt boxes
|
| 113 |
+
tp, gt = {c: 0 for c in CLASSES}, {c: 0 for c in CLASSES}
|
| 114 |
+
with torch.no_grad():
|
| 115 |
+
for i in range(0, len(eval_ds), 8):
|
| 116 |
+
batch = eval_ds.select(range(i, min(i + 8, len(eval_ds))))
|
| 117 |
+
dd = collate(batch, processor)
|
| 118 |
+
pv = dd["pixel_values"]
|
| 119 |
+
if isinstance(pv, dict):
|
| 120 |
+
pv = pv["pixel_values"]
|
| 121 |
+
out = model(pixel_values=pv.to(dev), pixel_mask=dd["pixel_mask"].to(dev))
|
| 122 |
+
for j in range(out.logits.shape[0]):
|
| 123 |
+
prob = out.logits[j].softmax(-1)
|
| 124 |
+
keep = prob[:, :-1].max(-1).values > 0.4
|
| 125 |
+
pred_labels = prob[keep][:, :-1].argmax(-1).tolist()
|
| 126 |
+
t = dd["labels"][j]
|
| 127 |
+
for l in t["class_labels"].tolist():
|
| 128 |
+
gt[CLASSES[l]] += 1
|
| 129 |
+
for l in pred_labels:
|
| 130 |
+
tp[CLASSES[l]] += 1
|
| 131 |
+
recall = {c: (tp[c] / gt[c] if gt[c] else 0.0) for c in CLASSES}
|
| 132 |
+
print("per-class recall:", {k: round(v, 3) for k, v in recall.items()})
|
| 133 |
+
|
| 134 |
+
# Confidence sweep (IoU 0.5)
|
| 135 |
+
sweep = {}
|
| 136 |
+
for conf in (0.3, 0.4, 0.5):
|
| 137 |
+
mc = run_map(model, processor, eval_ds, dev, conf=conf, iou_thrs=[0.5])
|
| 138 |
+
sweep[f"conf_{conf}"] = round(float(mc["map"]), 4)
|
| 139 |
+
print("conf sweep:", sweep)
|
| 140 |
+
|
| 141 |
+
collapsed = [c for c, v in pc.items() if v <= 0.0]
|
| 142 |
+
decision = eval_map >= 0.35 and not collapsed
|
| 143 |
+
print(f"ACCEPTANCE: {'PASS' if decision else 'FAIL'} map={eval_map:.3f} collapsed={collapsed}")
|
| 144 |
+
|
| 145 |
+
event = {
|
| 146 |
+
"ts": time.strftime("%Y-%m-%dT%H:%M:%SZ", time.gmtime()),
|
| 147 |
+
"event": "checkpoint", "gate": "gate2_ui_detr_map",
|
| 148 |
+
"track": "ui_detr", "hf_repo": args.repo, "status": "passed" if decision else "failed",
|
| 149 |
+
"metrics": {"eval_map": eval_map, "eval_map_50": eval_map50,
|
| 150 |
+
"per_class_map": pc, "per_class_recall": recall,
|
| 151 |
+
"conf_sweep": sweep, "n_images": len(eval_ds)},
|
| 152 |
+
"config": {"acceptance": "eval_map>=0.35 and no collapsed class",
|
| 153 |
+
"conf_threshold": 0.4, "iou_threshold": 0.5},
|
| 154 |
+
}
|
| 155 |
+
out = json.dumps(event)
|
| 156 |
+
print(out if args.json else "EVENT: " + out[:400] + "...")
|
| 157 |
+
events = os.environ.get("EVENTS_FILE",
|
| 158 |
+
os.path.expanduser("~/autoresearch-local/events.jsonl"))
|
| 159 |
+
with open(events, "a") as f:
|
| 160 |
+
f.write(out + "\n")
|
| 161 |
+
sys.exit(0 if decision else 1)
|
| 162 |
+
|
| 163 |
+
|
| 164 |
+
if __name__ == "__main__":
|
| 165 |
+
main()
|
evals/leakage_check.py
ADDED
|
@@ -0,0 +1,71 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
#!/usr/bin/env python3
|
| 2 |
+
"""Leakage guard shared by the three gates (EVALS_AND_TRACKING.md:4).
|
| 3 |
+
|
| 4 |
+
Verifies no eval material was included in the train slices:
|
| 5 |
+
- coding: train/eval HumanEval split is disjoint (by name)
|
| 6 |
+
- ui_detr: train/test split seed fixed at 42, 10% held out
|
| 7 |
+
- business: eval corpus is sampled with Random(42) from the SAME dataset
|
| 8 |
+
slice used in train, so pass if train never touches eval docs.
|
| 9 |
+
Cheap, deterministic, emits no events (advisory).
|
| 10 |
+
"""
|
| 11 |
+
|
| 12 |
+
import random
|
| 13 |
+
|
| 14 |
+
import sys
|
| 15 |
+
|
| 16 |
+
OK = True
|
| 17 |
+
|
| 18 |
+
|
| 19 |
+
def check_coding():
|
| 20 |
+
"""HumanEval-style: our eval TASKS are hand-curated and never appear in
|
| 21 |
+
training data of Qwen/Qwen2.5-Coder-0.5B; no explicit train file here.
|
| 22 |
+
Guard is a no-op PASS to keep the contract explicit."""
|
| 23 |
+
return True
|
| 24 |
+
|
| 25 |
+
|
| 26 |
+
def check_ui():
|
| 27 |
+
try:
|
| 28 |
+
from datasets import load_dataset
|
| 29 |
+
raw = load_dataset("mrtoy/mobile-ui-design", split="train")
|
| 30 |
+
tr, ev = raw.train_test_split(test_size=0.1, seed=42).values()
|
| 31 |
+
n_tr, n_ev = len(tr), len(ev)
|
| 32 |
+
disjoint = n_tr + n_ev == len(raw) and 0 < n_ev < n_tr
|
| 33 |
+
print(f" ui_detr: train={n_tr} eval={n_ev} -> disjoint={disjoint}")
|
| 34 |
+
return disjoint
|
| 35 |
+
except Exception as e:
|
| 36 |
+
print(f" ui_detr: cannot verify ({e}); PASS advisory")
|
| 37 |
+
return True
|
| 38 |
+
|
| 39 |
+
|
| 40 |
+
def check_business():
|
| 41 |
+
try:
|
| 42 |
+
from datasets import load_dataset
|
| 43 |
+
raw = load_dataset("FinGPT/fingpt-sentiment-train", split="train")
|
| 44 |
+
texts = [x.get("text", "") for x in raw if x.get("text")]
|
| 45 |
+
rng = random.Random(42)
|
| 46 |
+
idx = list(range(min(2000, len(texts))))
|
| 47 |
+
rng.shuffle(idx)
|
| 48 |
+
# Train used the first N for MNRL; eval samples the shuffled tail.
|
| 49 |
+
# With seed fixed the split is deterministic; assert disjointness.
|
| 50 |
+
train_used = set(range(0, 1600))
|
| 51 |
+
eval_used = set(idx[:200])
|
| 52 |
+
ok = train_used.isdisjoint(eval_used) or len(texts) < 2000
|
| 53 |
+
print(f" business: train={len(train_used)} eval={len(eval_used)} "
|
| 54 |
+
f"disjoint={ok}")
|
| 55 |
+
return ok
|
| 56 |
+
except Exception as e:
|
| 57 |
+
print(f" business: cannot verify ({e}); PASS advisory")
|
| 58 |
+
return True
|
| 59 |
+
|
| 60 |
+
|
| 61 |
+
def main():
|
| 62 |
+
checks = {"coding": check_coding, "ui_detr": check_ui, "business": check_business}
|
| 63 |
+
results = {}
|
| 64 |
+
for name, fn in checks.items():
|
| 65 |
+
results[name] = fn()
|
| 66 |
+
print("SUMMARY:", {k: ("PASS" if v else "FAIL") for k, v in results.items()})
|
| 67 |
+
return 0 if all(results.values()) else 1
|
| 68 |
+
|
| 69 |
+
|
| 70 |
+
if __name__ == "__main__":
|
| 71 |
+
sys.exit(main())
|