Download diffuthink/storypatch/evaluate.py from OthmaneW/StoryPatch-context-v3: direct link, hf CLI and curl.
- Browser
- Download file 5.04 kB
-
https://huggingface.co/OthmaneW/StoryPatch-context-v3/resolve/main/diffuthink/storypatch/evaluate.py
- Command line
-
hf download hf://OthmaneW/StoryPatch-context-v3/diffuthink/storypatch/evaluate.py
-
curl -L -o evaluate.py https://huggingface.co/OthmaneW/StoryPatch-context-v3/resolve/main/diffuthink/storypatch/evaluate.py
5.04 kB
| """Evaluate fixed reconstruction masks and the actual interactive editor.""" | |
| import argparse | |
| import json | |
| from pathlib import Path | |
| import time | |
| import numpy as np | |
| import torch | |
| from diffuthink.v2.data import digest | |
| from diffuthink.v2.editor import rewrite_span | |
| from diffuthink.v2.inference import load as load_model, denoise | |
| from diffuthink.v2.hybrid import causal_evaluate | |
| from diffuthink.v2.model import PAD, MASK | |
| from .data import load | |
| from .train import span_evaluate | |
| def normalize(text): | |
| return ' '.join(text.casefold().strip(' .!?\n\t').split()) | |
| def run(args): | |
| torch.set_num_threads(4) | |
| arrays, manifest = load(args.data) | |
| model, tokenizer = load_model(args.model, args.device) | |
| if digest(Path(args.model)/'tokenizer.json') != manifest['tokenizer_sha256']: | |
| raise ValueError('Benchmark requires the identical frozen tokenizer') | |
| precision = 'bf16' if args.device == 'cuda' else 'fp32' | |
| report = {'model': args.model, 'weights_sha256': digest(Path(args.model)/'model.safetensors'), | |
| 'device': args.device, 'precision': precision, 'torch_version': str(torch.__version__), | |
| 'data_manifest': manifest, 'split': args.split, 'editor_attempts': 6, | |
| 'challenge_sha256': digest(args.challenges), 'seed': 42, | |
| 'limitations': 'Exact reference metrics miss valid alternatives. Authored challenges are diagnostic, not a general grammar or coherence score.'} | |
| report['span'] = span_evaluate(model, arrays[args.split], 400, precision) | |
| report['causal'] = causal_evaluate(model, arrays[args.split][0], 400, precision=precision) | |
| print(json.dumps({'span': report['span'], 'causal': report['causal']}), flush=True) | |
| samples = [] | |
| array, spans = arrays[args.split] | |
| for index in range(min(64, len(array))): | |
| clean = [int(t) for t in array[index] if t != PAD] | |
| a, b = map(int, spans[index, 0]) | |
| masked = clean[:a]+[MASK]*(b-a)+clean[b:] | |
| result = denoise(model, tokenizer, masked, steps=12, temperature=0, seed=42, precision=precision, allow_eos=False) | |
| pred = result['tokens'][a:b] | |
| samples.append({'index': index, 'reference': tokenizer.decode(clean[a:b]), | |
| 'prediction': tokenizer.decode(pred), 'exact': pred == clean[a:b], | |
| 'context_preserved': result['tokens'][:a] == clean[:a] and result['tokens'][b:] == clean[b:]}) | |
| report['iterative_reconstruction'] = {'samples': samples, 'examples': len(samples), | |
| 'exact_match': sum(s['exact'] for s in samples)/len(samples), | |
| 'context_preservation': sum(s['context_preserved'] for s in samples)/len(samples), | |
| 'oracle_length': True, 'note': 'Reference span length supplied; this is not the editor candidate metric.'} | |
| challenges = json.loads(Path(args.challenges).read_text())['cases'] | |
| outputs = [] | |
| for case in challenges: | |
| result = rewrite_span(model, tokenizer, case['text'], case['start'], case['end'], precision=precision) | |
| accepted = {normalize(x) for x in case['accepted']} | |
| candidates = result['candidates'] | |
| outputs.append({**case, 'top1': bool(candidates and normalize(candidates[0]['replacement']) in accepted), | |
| 'top3': any(normalize(c['replacement']) in accepted for c in candidates[:3]), | |
| 'context_preserved': all(c['text'] == result['prefix']+c['replacement']+result['suffix'] for c in candidates), | |
| 'empty': not candidates, 'latency_ms': result['latency_ms'], | |
| 'candidates': [{k: v for k, v in c.items() if k != 'trace'} for c in candidates]}) | |
| print(json.dumps({'case': case['id'], 'top1': outputs[-1]['top1'], 'top': candidates[0]['replacement'] if candidates else None}), flush=True) | |
| def summarize(rows): | |
| return {key: sum(row[key] for row in rows)/len(rows) for key in ('top1','top3','context_preserved','empty')} | |
| report['editor'] = {'examples': len(outputs), **summarize(outputs), | |
| 'median_latency_ms': float(np.median([r['latency_ms'] for r in outputs])), | |
| 'by_category': {c: summarize([r for r in outputs if r['category'] == c]) for c in sorted({r['category'] for r in outputs})}, | |
| 'samples': outputs} | |
| output = Path(args.output) | |
| output.parent.mkdir(parents=True, exist_ok=True) | |
| output.write_text(json.dumps(report, indent=2, ensure_ascii=False), encoding='utf-8') | |
| print(json.dumps({k:v for k,v in report['editor'].items() if k != 'samples'}), flush=True) | |
| if __name__ == '__main__': | |
| p = argparse.ArgumentParser() | |
| p.add_argument('--model', required=True) | |
| p.add_argument('--data', default='data/processed/storypatch-v2') | |
| p.add_argument('--challenges', default='reports/storypatch-v2/challenges.json') | |
| p.add_argument('--output', required=True) | |
| p.add_argument('--device', choices=['cpu','cuda'], default='cuda') | |
| p.add_argument('--split', choices=['validation','test'], default='test') | |
| run(p.parse_args()) | |