kenga-prophet-m5 / code /corpus_eval.py
GermannM's picture
Kenga Prophet M5: same ~838K backbone as M4.2, data-scaled. Template-held-out NT 89.26%, compile/run 100%, greedy match 20%, pass@4 32.5%, zero-shot real-code 11.74%. Includes verified factory corpus splits + full pipeline code.
97a739f verified
Raw
History Blame Contribute Delete
4.22 kB
"""tools/corpus_eval.py — generation eval on Corpus Factory test split.
For each test record, prompt = first function block only; the model must
generate the rest (helper functions + main with a correct call). Programs
are verified by kenga-lite; stdout must equal the record's ground truth.
Metrics per directive point 8: compile-ok, run-ok, greedy match, pass@k.
Usage:
python tools/corpus_eval.py --model m42 --test minds/corpus_factory/split/test.jsonl
"""
import argparse
import collections
import json
import os
import sys
sys.path.insert(0, os.path.dirname(os.path.abspath(__file__)))
import kenchat
CODECS = {
'm37': 'minds/kenga_digits.pkl',
'm40': 'minds/kenga_full.pkl',
'm41': 'minds/kenga_full.pkl',
'm42': 'minds/kenga_full.pkl',
'm5': 'minds/kenga_full.pkl',
}
WEIGHTS = {m: f'minds/mid_prophet_{m}_w.txt' for m in CODECS}
def first_fn_block(src):
"""Prompt prefix: source up to and including the first top-level '}'.'
The model must continue with helper functions and main."""
depth = 0
for i, ch in enumerate(src):
if ch == '{':
depth += 1
elif ch == '}':
depth -= 1
if depth == 0:
return src[:i + 1]
return src
def main():
ap = argparse.ArgumentParser()
ap.add_argument('--model', required=True, choices=list(CODECS))
ap.add_argument('--test', default='minds/corpus_factory/split/test.jsonl')
ap.add_argument('--limit', type=int, default=100)
ap.add_argument('--max-tokens', type=int, default=200)
ap.add_argument('-k', type=int, default=8, help='pass@k sampling budget')
args = ap.parse_args()
weights = WEIGHTS[args.model]
codec = kenchat.load_codec_vocab(CODECS[args.model])
recs = []
with open(args.test, encoding='utf-8') as f:
for line in f:
line = line.strip()
if line:
recs.append(json.loads(line))
recs = recs[:args.limit]
stats = collections.defaultdict(lambda: [0, 0, 0, 0]) # compile,run,match,passk
n = 0
for r in recs:
prompt = first_fn_block(r['src'])
# greedy
_, gsrc = kenchat.gen_tokens(prompt, weights, max_tokens=args.max_tokens,
temperature=None, codec=codec)
full = kenchat.make_valid_program(prompt, gsrc)
rc, out, _ = kenchat.run_via_kenga_lite(full, timeout=10)
first = out.strip().split('\n')[0] if out else ''
ok_compile = rc == 0
ok_match = first == r['out']
passed_k = ok_match
# pass@k: sampled candidates until match
if not passed_k and args.k > 1:
for i in range(args.k - 1):
_, ssrc = kenchat.gen_tokens(prompt, weights,
max_tokens=args.max_tokens,
temperature=1.0, codec=codec, seed=i)
ffull = kenchat.make_valid_program(prompt, ssrc)
frc, fout, _ = kenchat.run_via_kenga_lite(ffull, timeout=10)
ffirst = fout.strip().split('\n')[0] if fout else ''
if frc == 0 and ffirst == r['out']:
passed_k = True
break
st = stats[r['category']]
st[0] += int(ok_compile)
st[1] += int(ok_compile and rc == 0)
st[2] += int(ok_match)
st[3] += int(passed_k)
n += 1
tot = [0, 0, 0, 0]
print(f'model={args.model} test={args.test} programs={n} pass@{args.k}')
print(f'{"category":10s} {"compile":>10s} {"run":>10s} {"match":>10s} {"pass@k":>10s}')
for cat in sorted(stats):
c, ru, m_, pk = stats[cat]
tot[0] += c; tot[1] += ru; tot[2] += m_; tot[3] += pk
print(f'{cat:10s} {c:>6d}/{n:<3d} {ru:>6d}/{n:<3d} '
f'{m_:>6d}/{n:<3d} {pk:>6d}/{n:<3d}')
print(f'{"TOTAL":10s} {tot[0]:>6d}/{n:<3d} {tot[1]:>6d}/{n:<3d} '
f'{tot[2]:>6d}/{n:<3d} {tot[3]:>6d}/{n:<3d}')
pct = lambda x: f'{100*x/n:.1f}%'
print(f'\ncompile {pct(tot[0])} run {pct(tot[1])} '
f'match(greedy) {pct(tot[2])} match(pass@{args.k}) {pct(tot[3])}')
return 0
if __name__ == '__main__':
sys.exit(main())