hypernet-sp-distill / hypernet_sp /composite_test5.py
baya1116's picture
Super-squash branch 'main' using huggingface_hub
b5989f0
Raw
History Blame Contribute Delete
7.92 kB
"""Composite battery v5 — LONG-HAUL: drive the generation stream to 6k, then ~12k tokens
(long-CoT multi-part problems x multi-turn) and measure what no earlier battery touched:
* mass-based EVICTION (maxD=4096): fires when absorbed-distant exceeds the bound — the
production path RESULTS.md flags as 'only activates at extreme buffer sizes'
* quality checkpoints at ~6k and ~12k: anchor-fact recall, math on a corrected value,
a gist probe ('what topics have we covered?') that leans on the SP, honest miss
* termination health of every volume turn (think must close; no runaway)
* tok/s flatness across the haul (bounded KV + bounded pooler input)
Run next to fft_hf/: python3 composite_test5.py [--target 12000] [--cap 1600]
"""
import argparse, json, os, re, sys, time
import torch
sys.path.insert(0, os.path.dirname(os.path.abspath(__file__)))
sys.path.insert(0, os.path.join(os.path.dirname(os.path.abspath(__file__)), "runtime"))
import memory_core as mc
from app_session_torch import AppSession
from decode_policy import DecodePolicy
results, vol_stats, ANSWERS = [], [], {}
# multi-part problems: long natural CoT, several DISTINCT sub-answers (so the convergence
# trigger doesn't cut the turn short), one checkable final value each.
VOLUME = [
("P1", "I invest $2,000 at 5% compound interest per year. (a) What is it worth after 1 year? (b) After 2 years? (c) After 3 years? Work each part out carefully.", ["2315.25"]),
("P2", "A train goes 60 km at 30 km/h, then 90 km at 45 km/h, then rests 30 minutes, then 100 km at 50 km/h. (a) Time for each leg? (b) Total trip time including the rest? (c) Average speed counting the rest?", ["6.5", "38"]),
("P3", "A committee of 3 is chosen from 5 women and 4 men. (a) How many total committees? (b) How many with exactly 2 women? (c) How many with at least 1 man?", ["84", "40", "74"]),
("P4", "A $1,200 paycheck is split: 30% rent, 25% food, 15% savings, the rest entertainment. (a) Dollar amount of each category? (b) If rent rises to 35%, how much LESS goes to entertainment?", ["360", "60"]),
("P5", "A rectangle is 12 cm by 8 cm. (a) Its area and perimeter? (b) A circle of radius 4 cm is cut from it - remaining area, using pi=3.14? (c) What percent of the rectangle was removed, to one decimal?", ["45.7", "52.3"]),
("P6", "Find the smallest positive number that leaves remainder 2 when divided by 3, remainder 3 when divided by 5, and remainder 2 when divided by 7. Verify each condition.", ["23"]),
("P7", "Pipe A fills a tank in 6 hours, pipe B in 4 hours, and drain C empties it in 12 hours. (a) Rate of each per hour as a fraction? (b) All three open: how long to fill? (c) A and B only: how long?", ["2.4"]),
("P8", "A bag has 5 red, 4 blue, 3 green marbles. Two are drawn without replacement. (a) P(both red)? (b) P(one red one blue)? (c) P(no green)? Give each as a fraction and verify.", ["5/33", "10/33"]),
]
def run(sess, msg, store, name, want=None, custom=None, volume=False, cap=None):
t0 = time.time()
n0 = len(sess.gen)
if volume:
ans, src, chunks = sess.turn(msg, store=store)
else:
ans, src, chunks = sess.turn(msg, store=store)
dt, ntok = time.time() - t0, len(sess.gen) - n0
checks = {}
if want is not None:
norm = ans.replace(",", "").lower() # '$2,315.25' must match gold '2315.25'
checks["answer"] = any(w.lower() in norm for w in want)
if custom is not None:
checks["custom"] = custom(ans)
ok = all(checks.values()) if checks else True
results.append((name, ok, checks))
ANSWERS[name] = {'ans': ans, 'chunks': chunks}
if volume:
body = sess.tok.decode(sess.gen[n0:])
closed = "</think>" in body
vol_stats.append((name, ntok, dt, ntok / max(dt, 1), closed))
results.append((f"{name}.terminated", closed, {}))
print(f"[{name}] {'PASS' if ok else 'FAIL'} {checks} ({dt:.0f}s, +{ntok} tok, "
f"stream={len(sess.gen)}, kept={len(sess.kept)}, evictions={sess.evictions})\n"
f" ans={ans[:120]!r}", flush=True)
return ans
def probes(s, tag):
run(s, "What's my reservation code?", "none", f"{tag}.code", want=["QX7-2291"])
run(s, "If I spend $200 from my budget, how much is left?", "none", f"{tag}.math-budget",
want=["450"])
run(s, "In one sentence each, what topics have we worked through so far?", "none",
f"{tag}.gist", custom=lambda a: sum(k in a.lower() for k in
("invest", "interest", "train", "committee", "paycheck", "rent", "rectangle",
"circle", "remainder", "pipe", "tank", "marble", "probability", "budget")) >= 2)
run(s, "What's my blood type?", "none", f"{tag}.honest-miss",
custom=lambda a: "don't have that saved" in a)
def main():
ap = argparse.ArgumentParser()
ap.add_argument("--target", type=int, default=12000)
ap.add_argument("--cap", type=int, default=1600)
a = ap.parse_args()
torch.set_num_threads(os.cpu_count())
import app_session_torch
app_session_torch.ANSCAP = 4000 # the app's 600-char UX cap truncates part (c) of a
# multi-part answer BEFORE the gold check sees it (P1: the 3-year value fell past the
# cap). Benchmarks raise it (cf. gsm8k_eval raising T.ANSCAP); so does this battery.
# App-side recommendation: make ANSCAP adaptive for multi-part questions.
import joblib
from transformers import AutoModelForCausalLM, AutoTokenizer
from rag import BGERetriever
sys.path.pop(1)
from attn_export3_torch import load_pooler
tok = AutoTokenizer.from_pretrained("fft_hf")
llm = AutoModelForCausalLM.from_pretrained("fft_hf", dtype=torch.float32).eval()
pooler, bge = load_pooler(), BGERetriever()
mem = mc.TieredMemory("/dev/null", bge=bge)
s = AppSession(llm, tok, pooler, bge, joblib.load("evals/intent_clf.joblib"),
joblib.load("evals/specificity_clf.joblib"), mem, cap=a.cap, seed=41)
print("#### anchors ####", flush=True)
for f in ["My reservation code is QX7-2291.", "I'm allergic to peanuts.",
"My gift budget is $650.", "The project is called Apollo."]:
run(s, f, "session", "anchor")
half = a.target // 2
vi = 0
print(f"\n#### volume to ~{half} tok ####", flush=True)
while len(s.gen) < half and vi < len(VOLUME):
name, q, want = VOLUME[vi]; vi += 1
run(s, q, "none", name, want=want, volume=True)
print(f"\n#### checkpoint A @ {len(s.gen)} tok ####", flush=True)
probes(s, "A")
print(f"\n#### volume to ~{a.target} tok ####", flush=True)
while len(s.gen) < a.target and vi < len(VOLUME):
name, q, want = VOLUME[vi]; vi += 1
run(s, q, "none", name, want=want, volume=True)
print(f"\n#### checkpoint B @ {len(s.gen)} tok ####", flush=True)
probes(s, "B")
run(s, "Am I allergic to anything?", "none", "B.allergy", want=["peanut"])
print("\n" + "=" * 70, flush=True)
for name, ok, _ in results:
print(f" {'PASS' if ok else 'FAIL'} {name}")
print("\nvolume turns (tok, sec, tok/s, think-closed):")
for name, ntok, dt, rate, closed in vol_stats:
print(f" {name}: {ntok:>5} tok {dt:>5.0f}s {rate:.2f} tok/s closed={closed}")
print(f"\nfinal: stream={len(s.gen)} tok | kept={len(s.kept)} | absorbed={s.absorbed} "
f"| evictions={s.evictions} | maxD={s.maxD}")
npass = sum(1 for _, ok, _ in results if ok)
print(f"\nCOMPOSITE5: {npass}/{len(results)} PASS")
json.dump({"results": [{"name": n, "ok": o} for n, o, _ in results],
"answers": ANSWERS,
"stream": len(s.gen), "evictions": s.evictions,
"vol": [{"name": n, "tok": t, "sec": d} for n, t, d, _, _ in vol_stats]},
open("composite5_results.json", "w"), indent=1)
print("HARNESS_V2")
print("COMPOSITE5_DONE")
if __name__ == "__main__":
main()