ruoxi_sun 09b2add673 fingerprint: integrate fp_fusion model fingerprint benchmark
New evalharness/fingerprint/ package (from evalstone fp_fusion v1.1,
2026-09-07 pruning final): probe battery -> concurrent collection ->
five scoring views (verify/attribution/variant/adversarial/robustness),
bundled family aliases + 27 reference fingerprints (12 fp_fusion schema).

- CLI: 'evalharness fingerprint run ...' (REMAINDER passthrough, single
  source of arg definitions) + 'fingerprint list' for bundled references
- imports rewritten package-relative; direct 'python3 run_fp_fusion.py'
  execution kept working via package bootstrap
- offline analysis/collection scripts made path-independent (previously
  pinned to a /opt/evalscope path absent on this host)
- shell scripts: hardcoded API key -> FP_API_KEY/OPENAI_API_KEY env vars
- --reference accepts short names resolved against bundled references/
- pyproject: +httpx dependency, package-data references/*.json
- tests/test_fingerprint.py: 10 offline tests (battery definitions,
  assembly counts, normalization, signals, verdict ladder, CLI wiring)
- README: fingerprint section + architecture entry

Verified on H20-1: tests 10/10, installed CLI OK, full-protocol run vs
vectron GLM-5.3 reproduces baseline (score 0.9451, s_idn 0.846).
2026-09-11 03:52:21 +00:00

313 lines
11 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
"""Cell 信噪比(SNR)分析26 个 D 层 cell 里谁是混子?(纯离线,零 API
信号 = 9 模型两两 JSD 均值 —— cell 把不同模型拉开的能力
噪声 = 模型内 bootstrap 对分 JSD —— 采样固有波动
SNR = 信号 / 噪声
三重验证:
A. drop-one去掉单 cell 后 9 模型 top-1 归因是否仍 9/9、最弱同族间距是否恶化
B. SNR 前向贪心:按 SNR 降序加 cell达成 9/9 的最小集,再后向修剪
C. 后向消元:从全量 cell 起逐个剔除"删了不伤"的 cell
锚点glm_53 两次独立全量样本的逐 cell test-retest JSD真实噪声
产出:/tmp/bfd/cell_snr_report.txt + /tmp/bfd/cell_snr.json
"""
import itertools
import json
import random
import sys
from collections import 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, compare_cells, # noqa: E402
distributions_by_cell, 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}
WEAK_PAIRS = [("glm_51", "glm_52"), ("kimi_k2_6", "kimi_k2_7code")]
rng = random.Random(20260907)
LINES = []
def log(s=""):
print(s)
LINES.append(s)
def f3(x):
return "" if x is None else f"{x:.3f}"
# ---------- 载入 ----------
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])
with open(f"{BFD}/glm_53/rerun/raw_answers.jsonl") as f:
rerun_samples = build_d_normalized([json.loads(l) for l in f])
dists = {n: distributions_by_cell(s) for n, s in samples.items()}
rerun_dist = distributions_by_cell(rerun_samples)
refs = {n: load_reference(f"{R}/{rf}")["cells"] for n, rf, _ in MODELS}
ALL_CELLS = sorted(set().union(*[set(r) for r in refs.values()])
| set().union(*[set(d) for d in dists.values()]))
log(f"cell 总数: {len(ALL_CELLS)}(电池口径 26 cell × 25 样本 = 650 D 请求)")
log()
# ---------- 有效性 / 类目数 ----------
valid_n = {}
for n in NAMES:
cnt = defaultdict(int)
for s in samples[n]:
if s["cat"] == "valid" and s["norm"] is not None:
cnt[s["cell"]] += 1
for c in ALL_CELLS:
valid_n[(n, c)] = cnt.get(c, 0)
cats = {}
for c in ALL_CELLS:
seen = set()
for n in NAMES:
seen |= {s["norm"] for s in samples[n]
if s["cell"] == c and s["cat"] == "valid"}
cats[c] = len(seen)
# ---------- 噪声bootstrap 对分 ----------
def boot_noise(ans, rounds=30):
n = len(ans)
h = n // 2
if h < 5:
return None
vals = []
for _ in range(rounds):
perm = list(ans)
rng.shuffle(perm)
a, b = defaultdict(int), defaultdict(int)
for x in perm[:h]:
a[x] += 1
for x in perm[h:2 * h]:
b[x] += 1
vals.append(jsd_bits(dict(a), dict(b)))
return sum(vals) / len(vals)
noise = {}
for c in ALL_CELLS:
per = []
for n in NAMES:
ans = [s["norm"] for s in samples[n]
if s["cell"] == c and s["cat"] == "valid"]
j = boot_noise(ans)
if j is not None:
per.append(j)
noise[c] = (sum(per) / len(per), len(per)) if per else (None, 0)
# ---------- 信号:跨模型两两 JSD ----------
signal = {}
for c in ALL_CELLS:
js = []
for a, b in itertools.combinations(NAMES, 2):
da, db = dists[a].get(c), dists[b].get(c)
if da and db and sum(da.values()) >= 10 and sum(db.values()) >= 10:
js.append(jsd_bits(da, db))
signal[c] = (sum(js) / len(js), len(js)) if js else (None, 0)
# ---------- SNR ----------
snr = {}
for c in ALL_CELLS:
s, _ = signal[c]
nz, _ = noise[c]
if s is None or s < 1e-9:
snr[c] = 0.0
elif nz is None or nz < 1e-9:
snr[c] = 99.0
else:
snr[c] = s / nz
# ---------- 预计算 (模型, 参考) 逐 cell JSD ----------
J = {}
for m in NAMES:
for r in NAMES:
entries, _ = compare_cells(dists[m], refs[r])
J[(m, r)] = {e["cell"]: e["jsd"] for e in entries}
def state(active):
res, margins = {}, []
for m in NAMES:
vals = {}
for r in NAMES:
js = [J[(m, r)][c] for c in active if c in J[(m, r)]]
if js:
vals[r] = sum(js) / len(js)
if not vals:
res[m] = (False, None, 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]
margin = (min(sib) - own) if (sib and own is not None) else None
res[m] = (best == m, own, margin)
if margin is not None:
margins.append((margin, m))
correct = sum(1 for m in NAMES if res[m][0])
weakest = min(margins) if margins else None
return res, correct, weakest
base_res, base_correct, base_weak = state(ALL_CELLS)
log("【基线(全 cell】top-1 正确 %d/9各模型同族间距正值=安全):" % base_correct)
for m in NAMES:
_, own, mg = base_res[m]
log(f" {m:24s} own={f3(own)} 同族间距={f3(mg)}")
log(f" 最弱环节: {base_weak[1]} 间距 {base_weak[0]:+.3f}")
log()
# ---------- drop-one ----------
drop1 = {}
for c in ALL_CELLS:
_, cor, wk = state([x for x in ALL_CELLS if x != c])
drop1[c] = (cor, wk)
# ---------- 后向消元 ----------
active = list(ALL_CELLS)
removed_order = []
while True:
cands = []
for c in active:
rest = [x for x in active if x != c]
_, cor, wk = state(rest)
if cor == 9:
cands.append((wk[0], c))
if not cands:
break
cands.sort(reverse=True)
pick = cands[0][1]
removed_order.append(pick)
active = [x for x in active if x != pick]
final_res, final_correct, final_weak = state(active)
log(f"【后向消元】可安全剔除 {len(removed_order)} 个,最小充分集 {len(active)} cell"
f"top-1 {final_correct}/9最弱间距 {final_weak[1]} {final_weak[0]:+.3f}")
log(f" 剔除顺序: {', '.join(removed_order)}")
log(f" 保留集: {', '.join(active)}")
log()
# ---------- SNR 前向贪心 ----------
order = sorted(ALL_CELLS, key=lambda c: (-(snr[c] if snr[c] < 90 else 99), c))
fw_active, fw_correct = [], 0
for c in order:
fw_active.append(c)
_, fw_correct, _ = state(fw_active)
if fw_correct == 9:
break
# 后向修剪
changed = True
while changed:
changed = False
for c in sorted(fw_active, key=lambda x: snr[x]):
rest = [x for x in fw_active if x != c]
_, cor, _ = state(rest)
if cor == 9:
fw_active = rest
changed = True
break
log(f"【SNR 前向贪心】最小集 {len(fw_active)} cell: {', '.join(fw_active)}")
log()
# ---------- 弱对载荷 cell ----------
log("【弱对载荷】版本级最难的两组,哪些 cell 在真正出力(两模型间 JSD top8:")
for a, b in WEAK_PAIRS:
per = []
for c in ALL_CELLS:
da, db = dists[a].get(c), dists[b].get(c)
if da and db and sum(da.values()) >= 10 and sum(db.values()) >= 10:
per.append((jsd_bits(da, db), c))
per.sort(reverse=True)
log(f" {a} vs {b}: " + ", ".join(f"{c}({j:.3f})" for j, c in per[:8]))
log()
# ---------- 总表 ----------
log("【cell 信噪比排行】(按 SNR 降序drop1=去掉该 cell 后 top-1 是否仍 9/9 + 最弱间距变化)")
log(f"{'cell':34s} {'valid均':>7s} {'<10数':>5s} {'类目':>4s} {'对数':>4s} "
f"{'信号':>6s} {'噪声':>6s} {'SNR':>6s} drop1")
ranked = sorted(ALL_CELLS, key=lambda c: -snr[c])
table_rows = []
for c in ranked:
avg_v = sum(valid_n[(n, c)] for n in NAMES) / len(NAMES)
below = sum(1 for n in NAMES if valid_n[(n, c)] < 10)
s, pairs = signal[c]
nz, nmod = noise[c]
cor, wk = drop1[c]
d1 = f"{'✓9/9' if cor == 9 else '✗破坏'} Δ{wk[0] - base_weak[0]:+.3f}" if wk else "?"
tag = " ★核心" if c in active else (" ✂可剪" if cor == 9 else " ⚠载荷")
log(f"{c:34s} {avg_v:7.1f} {below:5d} {cats[c]:4d} {pairs:4d} "
f"{f3(s):>6s} {f3(nz):>6s} {snr[c]:6.2f} {d1}{tag}")
table_rows.append({"cell": c, "avg_valid": round(avg_v, 1), "below10": below,
"categories": cats[c], "pairs": pairs,
"signal": None if s is None else round(s, 4),
"noise": None if nz is None else round(nz, 4),
"snr": round(snr[c], 3), "drop1_correct": cor,
"drop1_min_margin": None if not wk else round(wk[0], 4),
"in_min_set": c in active})
log()
# ---------- test-retest 锚点 ----------
log("【真实噪声锚点】glm_53 两次独立全量样本的逐 cell test-retest JSD:")
tr = []
for c in ALL_CELLS:
da, db = dists["glm_53"].get(c), rerun_dist.get(c)
if da and db and sum(da.values()) >= 10 and sum(db.values()) >= 10:
tr.append((jsd_bits(da, db), c))
tr.sort(reverse=True)
log(" " + ", ".join(f"{c}({j:.3f})" for j, c in tr))
glm_boot = [noise[c][0] for c in ALL_CELLS
if noise[c][0] is not None
and valid_n[("glm_53", c)] >= 10]
log(f" 均值 {sum(j for j, _ in tr) / len(tr):.3f}(同批 cell 的 bootstrap 噪声均值 "
f"{sum(glm_boot) / len(glm_boot):.3f},两者同量级则 bootstrap 估计可信)")
log()
# ---------- 节省估算 ----------
K = len(active)
log("【节省估算】(纯 D 层剪枝,文本层 36 + 基线 5 不变)")
log(f" 保留 {K} cell → D 请求 {650}{K * 25}{K * 25 / 691 * 100:.0f}% 原量)")
for nm, old_min, d_req in [("kimi_k3", 65, 5.6), ("glm_53", 23, 2.0), ("deepseek_v4_flash", 22, 2.0)]:
d_time = old_min - 4 # 文本层+基线约 4 分钟
new_min = d_time * (K * 25) / 650 + 4
log(f" {nm:22s} verify {old_min} 分钟 → 约 {new_min:.0f} 分钟")
log()
# ---------- 落盘 ----------
with open(f"{BFD}/cell_snr_report.txt", "w") as f:
f.write("\n".join(LINES) + "\n")
with open(f"{BFD}/cell_snr.json", "w") as f:
json.dump({"baseline": {"correct": base_correct,
"weak": [base_weak[1], round(base_weak[0], 4)]},
"cells": table_rows,
"backward_removed_order": removed_order,
"minimal_set": active,
"forward_set": fw_active,
"final_margins": {m: None if base_res[m][2] is None else round(base_res[m][2], 4)
for m in NAMES},
"pruned_margins": {m: None if final_res[m][2] is None else round(final_res[m][2], 4)
for m in NAMES}}, f, ensure_ascii=False, indent=1)
print("\n已写入 /tmp/bfd/cell_snr_report.txt + /tmp/bfd/cell_snr.json")