- 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>
128 lines
5.4 KiB
Python
128 lines
5.4 KiB
Python
#!/usr/bin/env python3
|
||
"""FP-Fusion extra cells 采集(10 个新增 cell:binary-* + day-of-week × en/zh)。
|
||
对齐 fp_fusion engine D 层采样;带 enable_thinking=false 禁思考 + api-key。"""
|
||
import argparse
|
||
import asyncio
|
||
import json
|
||
import math
|
||
import random
|
||
import sys
|
||
import time
|
||
from collections import Counter
|
||
from pathlib import Path
|
||
|
||
sys.path.insert(0, str(Path(__file__).resolve().parents[2])) # 仓库根/site-packages, 使 evalharness 包可导入
|
||
import httpx
|
||
from evalharness.fingerprint.battery import NEW_CELLS, D_TEMPERATURE
|
||
from evalharness.fingerprint.engine import normalize_answer
|
||
|
||
SYS_ONE_WORD = {'en': 'Answer with exactly one word. No punctuation, no explanation.',
|
||
'zh': '只回答一个词,不要标点,不要解释。'}
|
||
client_key = ''
|
||
|
||
|
||
def normalize_for_ref(cell_id, raw):
|
||
domains = {f"{c['id']}:{lang}": c['domain'] for c in NEW_CELLS for lang in ('en', 'zh')}
|
||
return normalize_answer(raw, domains[cell_id])
|
||
|
||
|
||
async def sample_cell(client, api_url, model, cell_id, pool, n, sem, retries, timeout):
|
||
samples = []
|
||
for i in range(n):
|
||
prompt = random.choice(pool)
|
||
body = {'model': model,
|
||
'messages': [{'role': 'system',
|
||
'content': SYS_ONE_WORD['zh' if cell_id.endswith(':zh') else 'en']},
|
||
{'role': 'user', 'content': prompt}],
|
||
'temperature': D_TEMPERATURE, 'max_tokens': 16, 'stream': False,
|
||
'chat_template_kwargs': {'enable_thinking': False}}
|
||
headers = {'Content-Type': 'application/json'}
|
||
if client_key:
|
||
headers['Authorization'] = f'Bearer {client_key}'
|
||
for attempt in range(retries):
|
||
try:
|
||
async with sem:
|
||
r = await client.post(f"{api_url.rstrip('/')}/chat/completions",
|
||
json=body, headers=headers, timeout=timeout)
|
||
r.raise_for_status()
|
||
data = r.json()
|
||
content = (data.get('choices') or [{}])[0].get('message', {}).get('content') or ''
|
||
samples.append(content)
|
||
break
|
||
except Exception as e:
|
||
if attempt == retries - 1:
|
||
print(f' [{cell_id}] sample {i} failed: {str(e)[:80]}', file=sys.stderr)
|
||
samples.append('')
|
||
else:
|
||
await asyncio.sleep(1.5 * (attempt + 1))
|
||
return cell_id, samples
|
||
|
||
|
||
async def main_async(args):
|
||
sem = asyncio.Semaphore(args.concurrency)
|
||
timeout = httpx.Timeout(max(args.timeout, 120))
|
||
async with httpx.AsyncClient(timeout=timeout) as client:
|
||
tasks = []
|
||
for c in NEW_CELLS:
|
||
for lang in ('en', 'zh'):
|
||
cell_id = f"{c['id']}:{lang}"
|
||
tasks.append(asyncio.create_task(sample_cell(
|
||
client, args.api_url, args.model, cell_id, c['par'][lang],
|
||
args.samples, sem, args.retries, timeout)))
|
||
results = await asyncio.gather(*tasks)
|
||
cells = {}
|
||
for cell_id, samples in results:
|
||
counts = Counter()
|
||
valid = invalid = refusal = empty = error = 0
|
||
for s in samples:
|
||
if s == '':
|
||
error += 1; continue
|
||
norm, cat = normalize_for_ref(cell_id, s)
|
||
if cat == 'valid' and norm is not None:
|
||
counts[norm] += 1; valid += 1
|
||
elif cat == 'refusal': refusal += 1
|
||
elif cat == 'empty': empty += 1
|
||
else: invalid += 1
|
||
total = len(samples)
|
||
entropy = 0.0
|
||
if total and counts:
|
||
entropy = -sum((v / total) * math.log2(v / total) for v in counts.values())
|
||
cells[cell_id] = {'cellId': cell_id, 'counts': {str(k): v for k, v in counts.items()},
|
||
'validCount': valid, 'invalidCount': invalid,
|
||
'refusalCount': refusal, 'emptyCount': empty,
|
||
'errorCount': error, 'totalCount': total,
|
||
'entropyBits': entropy, 'normalizedEntropy': 0.0,
|
||
'medianLatencyMs': None, 'meanCompletionTokens': None,
|
||
'meanReasoningTokens': None}
|
||
print(f' {cell_id}: valid={valid}/{total}', flush=True)
|
||
return cells
|
||
|
||
|
||
def main():
|
||
p = argparse.ArgumentParser(description='Collect fp_fusion extra reference cells')
|
||
p.add_argument('--api-url', required=True)
|
||
p.add_argument('--model', required=True)
|
||
p.add_argument('--api-key', default='')
|
||
p.add_argument('--out', required=True)
|
||
p.add_argument('--samples', type=int, default=25)
|
||
p.add_argument('--concurrency', type=int, default=2)
|
||
p.add_argument('--retries', type=int, default=8)
|
||
p.add_argument('--timeout', type=float, default=120)
|
||
args = p.parse_args()
|
||
|
||
global client_key
|
||
client_key = args.api_key
|
||
|
||
cells = asyncio.run(main_async(args))
|
||
out = Path(args.out)
|
||
out.parent.mkdir(parents=True, exist_ok=True)
|
||
payload = {'formatVersion': 1, 'protocol': 'one-token/v1', 'model': args.model,
|
||
'collectedAt': time.strftime('%Y-%m-%dT%H:%M:%S.000Z', time.gmtime()),
|
||
'samplesPerCell': args.samples, 'postReasoning': False,
|
||
'extraCellsOnly': True, 'cells': cells}
|
||
out.write_text(json.dumps(payload, ensure_ascii=False, indent=2), encoding='utf-8')
|
||
print(f'extra reference written -> {out} ({len(cells)} cells)')
|
||
|
||
|
||
if __name__ == '__main__':
|
||
main() |