sora 370953729b Fix perf stats (wrong import path), per-repeat checkpoints, README
- perf_stats aggregator lives in eval/, not model/: the import failed
  silently and EVERY perf column was empty (not just ttft). Now warns
  on stderr instead of swallowing.
- repeats > 1 get their own checkpoint key (:rep2, :rep3, ...): repeat 2
  previously restored repeat 1's predictions and finished instantly with
  identical scores. rep1 keeps the legacy key (existing checkpoints still
  resume).
- repeats summary: report the MEAN score and aggregate time/tokens over
  ALL runs (was: last run only).
- README: six-benchmark command as the primary example.

Co-Authored-By: Claude <noreply@anthropic.com>
2026-09-11 13:38:04 +00:00

159 lines
6.1 KiB
Python
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

#!/usr/bin/env python3
"""剪枝候选集验证(纯离线,零 API
确定性检查之外,加 bootstrap 重跑仿真:对每个模型的 cell 答案做有放回重采样,
模拟"明天再跑一遍电池",统计 top-1 归因成功率。剪枝集与全量集成功率持平才算安全。
集合定义(依据 cell_snr.py 排行 + 弱对载荷分析):
tier1 死重: binary-*-zh ×49 模型全 0 valid纯浪费 100 请求/跑)+ favorite-number:en5/9 模型不可用)
tier2 零信号: coin-flip:en/zh、random-number-1-10:en/zh、binary-season:en
(跨模型信号 ≤0.053,全体模型收敛到同一分布,构造性无区分力)
tier3 谨慎: day-of-week:zh不在任何弱对 top8移除 Δ+0.001
推荐15 = 弱对 top8 并集(11) + SNR≥2 补充(4)
"""
import json
import random
import sys
from collections import Counter, defaultdict
from pathlib import Path # noqa: E402
sys.path.insert(0, str(Path(__file__).resolve().parents[2])) # 仓库根/site-packages, 使 evalharness 包可导入
from evalharness.fingerprint.engine import (build_d_normalized, distributions_by_cell, # noqa: E402
jsd_bits, load_reference)
BFD = "/tmp/bfd"
R = str(Path(__file__).resolve().parent / 'references')
MODELS = [
("deepseek_v4_flash", "deepseek_v4_flash_fusion_reference.json", "DS"),
("deepseek_v4_flash_0731", "deepseek_v4_flash_0731_fusion_reference.json", "DS"),
("deepseek_v4_pro", "deepseek_v4_pro_fusion_reference.json", "DS"),
("glm_51", "glm_51_fusion_reference.json", "GLM"),
("glm_52", "glm52_vectron_fusion_reference.json", "GLM"),
("glm_53", "glm53_fusion_reference.json", "GLM"),
("kimi_k2_6", "kimi_k2_6_fusion_reference.json", "Kimi"),
("kimi_k2_7code", "kimi_k2_7code_fusion_reference.json", "Kimi"),
("kimi_k3", "kimi_k3_fusion_reference.json", "Kimi"),
]
NAMES = [m[0] for m in MODELS]
FAMILY = {m[0]: m[2] for m in MODELS}
samples = {}
for n, _, _ in MODELS:
with open(f"{BFD}/{n}/raw_answers.jsonl") as f:
samples[n] = build_d_normalized([json.loads(l) for l in f])
dists = {n: distributions_by_cell(s) for n, s in samples.items()}
refs = {n: load_reference(f"{R}/{rf}")["cells"] for n, rf, _ in MODELS}
ALL = sorted(set().union(*[set(r) for r in refs.values()])
| set().union(*[set(d) for d in dists.values()]))
ANS = {}
for n in NAMES:
per = defaultdict(list)
for s in samples[n]:
if s["cat"] == "valid" and s["norm"] is not None:
per[s["cell"]].append(s["norm"])
for c in ALL:
ANS[(n, c)] = per.get(c, [])
def score(dist_m, ref_cells, cells):
js = []
for c in cells:
da, db = dist_m.get(c), ref_cells.get(c)
if not da or not db:
continue
if sum(da.values()) < 10 or sum(db.values()) < 10:
continue
js.append(jsd_bits(da, db))
return sum(js) / len(js) if js else None
def state(cells, D=None):
D = dists if D is None else D
res = {}
for m in NAMES:
vals = {r: score(D[m], refs[r], cells) for r in NAMES}
vals = {r: v for r, v in vals.items() if v is not None}
if not vals:
res[m] = (None, False, False, None)
continue
best = min(vals, key=vals.get)
own = vals.get(m)
sib = [v for r, v in vals.items() if FAMILY[r] == FAMILY[m] and r != m]
mg = (min(sib) - own) if (sib and own is not None) else None
res[m] = (best, best == m, FAMILY[best] == FAMILY[m], mg)
exact = sum(1 for m in NAMES if res[m][1])
margins = [(res[m][3], m) for m in NAMES if res[m][3] is not None]
return res, exact, (min(margins) if margins else None)
KEEP15 = [
"random-animal:en", "random-animal:zh", "random-city:en", "random-city:zh",
"random-color:en", "random-color:zh", "random-letter:en", "random-letter:zh",
"random-number-1-100:en", "random-number-1-100:zh", "favorite-number:zh",
"day-of-week:en", "binary-pet:en", "binary-tea-coffee:en",
"binary-sea-mountain:en",
]
TIER12_16 = KEEP15 + ["day-of-week:zh"]
MINI3 = ["day-of-week:en", "binary-pet:en", "binary-tea-coffee:en"]
SETS = [
("全量 26 cell现状", ALL),
("推荐 15 cell", KEEP15),
("仅 tier1+2 剪16 cell", TIER12_16),
("家族分诊 mini 3 cell", MINI3),
]
out = []
def log(s=""):
print(s)
out.append(s)
B = 40
for name, cells in SETS:
res, exact, weak = state(cells)
d_req = len(cells) * 25
k3_min = (65 - 4) * d_req / 650 + 4
log(f"━━ {name} D请求 {d_req}{d_req / 691 * 100:.0f}% 原量) K3 verify 约 {k3_min:.0f} 分钟")
log(f" 确定性: 精确 top-1 {exact}/9 最弱间距 {weak[1]} {weak[0]:+.3f}")
rng = random.Random(7)
ex = {m: 0 for m in NAMES}
fam = {m: 0 for m in NAMES}
confusions = Counter()
for _ in range(B):
Dboot = {}
for m in NAMES:
d = {}
for c in cells:
ans = ANS[(m, c)]
if len(ans) >= 10:
d[c] = dict(Counter(rng.choices(ans, k=len(ans))))
Dboot[m] = d
for m in NAMES:
vals = {r: score(Dboot[m], refs[r], cells) for r in NAMES}
vals = {r: v for r, v in vals.items() if v is not None}
if not vals:
continue
best = min(vals, key=vals.get)
ex[m] += best == m
fam[m] += FAMILY[best] == FAMILY[m]
if best != m:
confusions[(m, best)] += 1
tot_ex = sum(ex.values()) / (B * len(NAMES)) * 100
tot_fam = sum(fam.values()) / (B * len(NAMES)) * 100
log(f" bootstrap 重跑仿真({B}次): 精确 {tot_ex:.0f}% 家族 {tot_fam:.0f}%")
detail = " ".join(f"{m.replace('deepseek_v4_', 'ds_').replace('kimi_', 'k')}: {ex[m] / B * 100:.0f}%"
for m in NAMES)
log(f" 逐模型精确: {detail}")
if confusions:
top = ", ".join(f"{a}{b}×{c}" for (a, b), c in confusions.most_common(4))
log(f" 混淆集中在: {top}")
log()
with open(f"{BFD}/keepset_eval.txt", "w") as f:
f.write("\n".join(out) + "\n")
print("已写入 /tmp/bfd/keepset_eval.txt")