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

101 lines
3.9 KiB
Python
Raw Permalink 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
"""9×9 交叉混淆矩阵:每个模型实测 D 层分布 vs 全部 9 个参考(纯离线)。
混淆判定:某模型的全部 JSD 里最低者top-1若不是自己的参考 → 记一次混淆。
附带glm_53 复跑样本作为第二独立样本验证比对稳定性。
"""
import json
import sys
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, 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"),
]
FAMILY = {n: f for n, _, f in MODELS}
refs = {}
for n, rf, _ in MODELS:
refs[n] = load_reference(f"{R}/{rf}")["cells"]
def dist_of(path):
records = [json.loads(l) for l in open(path)]
d = build_d_normalized(records)
return distributions_by_cell(d)
live = {n: dist_of(f"{BFD}/{n}/raw_answers.jsonl") for n, _, _ in MODELS}
extra = {}
try:
extra["glm_53#rerun"] = dist_of(f"{BFD}/glm_53/rerun/raw_answers.jsonl")
except FileNotFoundError:
pass
names = [n for n, _, _ in MODELS]
results = {}
for lname, dist in {**live, **extra}.items():
results[lname] = {}
for rname, ref_cells in refs.items():
entries, mean_jsd = compare_cells(dist, ref_cells)
results[lname][rname] = (mean_jsd, len(entries))
print("JSD 矩阵(行=实测模型,列=参考,越低越像):")
print("live\\ref".ljust(22) + "".join(n[:13].rjust(14) for n in names))
for lname in results:
print(lname[:21].ljust(22) +
"".join(f"{results[lname][n][0]:.3f}".rjust(14)
if results[lname][n][0] is not None else "".rjust(14)
for n in names))
print()
print("=" * 100)
correct, total, confusions = 0, 0, []
print(f"{'模型':22s} {'own':>6s} {'top1 匹配':22s} {'判定':6s} {'最佳同族':22s} {'同族JSD':>8s} {'间距':>8s}")
for lname in live:
valid = {r: v[0] for r, v in results[lname].items() if v[0] is not None}
if not valid:
continue
best = min(valid, key=valid.get)
own = valid[lname]
total += 1
ok = best == lname
correct += ok
if not ok:
confusions.append((lname, best, round(own, 3), round(valid[best], 3)))
sibs = [r for r in valid if FAMILY[r] == FAMILY[lname] and r != lname]
if sibs:
bs = min(sibs, key=valid.get)
print(f"{lname:22s} {own:6.3f} {best:22s} {'' if ok else '✗混淆':6s} "
f"{bs:22s} {valid[bs]:8.3f} {valid[bs] - own:+8.3f}")
else:
print(f"{lname:22s} {own:6.3f} {best:22s} {'' if ok else '✗混淆':6s} {'(无同族)':22s}")
print()
print(f"【top-1 正确率】{correct}/{total} = {correct / total * 100:.0f}%")
print(f"【混淆数】{len(confusions)}")
for c in confusions:
print(f" 混淆: {c[0]} 被认成 {c[1]} (own={c[2]}, conf={c[3]})")
if "glm_53#rerun" in results:
valid = {r: v[0] for r, v in results["glm_53#rerun"].items() if v[0] is not None}
best = min(valid, key=valid.get)
print(f"【稳定性】glm_53 第二独立样本 top1={best} "
f"{'✓ 与首跑一致' if best == 'glm_53' else '✗ 不一致'} "
f"(own={valid['glm_53']:.3f}, 次优={sorted(valid.values())[1]:.3f})")