Download diffuthink/storypatch/context_evaluate.py from OthmaneW/StoryPatch-context-v3: direct link, hf CLI and curl.
- Browser
- Download file 6.79 kB
-
https://huggingface.co/OthmaneW/StoryPatch-context-v3/resolve/main/diffuthink/storypatch/context_evaluate.py
- Command line
-
hf download hf://OthmaneW/StoryPatch-context-v3/diffuthink/storypatch/context_evaluate.py
-
curl -L -o context_evaluate.py https://huggingface.co/OthmaneW/StoryPatch-context-v3/resolve/main/diffuthink/storypatch/context_evaluate.py
6.79 kB
| """Freeze a bounded NLL/compatibility blend on validation before testing.""" | |
| import copy | |
| import hashlib | |
| import json | |
| from pathlib import Path | |
| import torch | |
| from .corrections import read | |
| from .ranker import Reranker, blended_score | |
| from .evaluate_ranker import generate_rows, calibrate, evaluate, accepted, bootstrap | |
| from .train_ranker import encode_groups, validation | |
| from .benchmark import cases | |
| from diffuthink.v2.inference import load | |
| from diffuthink.v2.editor import rewrite_span | |
| def digest(path):return hashlib.sha256(Path(path).read_bytes()).hexdigest() | |
| def blend(rows,weight): | |
| output=copy.deepcopy(rows) | |
| for r in output: | |
| r['raw_original_score']=r['original_score'] | |
| r['original_score']=blended_score(r['original_nll'],r['original_score'],weight) | |
| for c in r['candidates']: | |
| c['raw_ranker_score']=c['ranker_score'] | |
| c['ranker_score']=blended_score(c['local_nll'],c['ranker_score'],weight) | |
| return output | |
| def top1(rows): | |
| errors=[r for r in rows if not r['case']['clean']] | |
| return sum(accepted(r['case'],max(r['candidates'],key=lambda c:c['ranker_score'],default={}).get('replacement','')) for r in errors)/len(errors) | |
| def choose_blend(rows): | |
| # Both domains must meet their NLL validation top1 score; zero is a valid fallback. | |
| domains={'corpus':[r for r in rows if r['case']['category']=='corpus'], | |
| 'procedural':[r for r in rows if r['case']['category']!='corpus']} | |
| baseline={name:top1(blend(group,0.)) for name,group in domains.items()} | |
| trials=[] | |
| for weight in [0.,.05,.1,.2,.4,.8,1.6]: | |
| scores={name:top1(blend(group,weight)) for name,group in domains.items()} | |
| passes=all(scores[name]+1e-12>=baseline[name] for name in domains) | |
| trials.append({'weight':weight,'top1_by_domain':scores,'passes':passes}) | |
| eligible=[t for t in trials if t['passes']] | |
| selected=max(eligible,key=lambda t:(sum(t['top1_by_domain'].values()),-t['weight'])) | |
| return {'weight':selected['weight'],'formula':'-local_nll + weight * clip(learned_score, -8, 8)', | |
| 'selection':'Validation top1 improvement with no top1 loss in either corpus or procedural domain; ties prefer lower weight.', | |
| 'baseline':baseline,'trials':trials} | |
| def main(): | |
| torch.set_num_threads(4) | |
| data=Path('data/processed/storypatch-context-v3');out=Path('reports/storypatch-context-v3');out.mkdir(parents=True,exist_ok=True) | |
| generator=Path('runs/storypatch-13m/best');directory=Path('runs/storypatch-context-ranker-v3/best') | |
| manifest=json.loads((data/'manifest.json').read_text()) | |
| binding={'generator_sha256':digest(generator/'model.safetensors'),'ranker_sha256':digest(directory/'model.safetensors'), | |
| 'corpus':manifest,'validation_contexts':240,'precision':'bf16 generator / fp32 ranker','device':'cuda','seed':301} | |
| binding_path=out/'binding.json' | |
| if binding_path.exists() and json.loads(binding_path.read_text())!=binding:raise ValueError('Cache binding mismatch') | |
| binding_path.write_text(json.dumps(binding,indent=2),encoding='utf-8') | |
| for split in ['validation','test']: | |
| if digest(data/f'{split}.jsonl')!=manifest['splits'][split]['sha256']:raise ValueError('Corpus changed') | |
| model,tokenizer=load(generator,'cuda');ranker=Reranker.load(directory,tokenizer,'cuda') | |
| valid=read(data/'validation.jsonl')[:240] | |
| validation_rows=generate_rows(model,tokenizer,ranker,valid,'cuda',out/'validation_predictions.jsonl') | |
| blending=choose_blend(validation_rows);calibration=calibrate(blend(validation_rows,blending['weight'])) | |
| ranker.info['blending']=blending;ranker.info['calibration']=calibration | |
| (directory/'ranker_info.json').write_text(json.dumps(ranker.info,indent=2),encoding='utf-8') | |
| (out/'selection.json').write_text(json.dumps({'blending':blending,'calibration':calibration},indent=2),encoding='utf-8') | |
| print(json.dumps({'blend':blending,'calibration':calibration}),flush=True) | |
| # Test is first scored only after the complete inference policy is fixed. | |
| test=read(data/'test.jsonl');raw=generate_rows(model,tokenizer,ranker,test,'cuda',out/'test_predictions.jsonl') | |
| mixed=blend(raw,blending['weight']) | |
| groups={'all':mixed,'corpus':[r for r in mixed if r['case']['category']=='corpus'], | |
| 'procedural':[r for r in mixed if r['case']['category']!='corpus']} | |
| results={name:evaluate(group,calibration) for name,group in groups.items()} | |
| legacy=[] | |
| for i,c in enumerate(cases()): | |
| r=rewrite_span(model,tokenizer,c['text'],c['start'],c['end'],seed=42+i*13,precision='bf16',ranker=ranker) | |
| base=min(r['candidates'],key=lambda x:x['local_nll'],default={}).get('replacement','') | |
| new=next(iter(r['candidates']),{}).get('replacement','') | |
| legacy.append({'case':c,'nll':base,'blended':new,'recommendation':r['recommendation'], | |
| 'candidates':[{k:x[k] for k in ['replacement','local_nll','ranker_score','raw_ranker_score']} for x in r['candidates']]}) | |
| legacy_metrics={key:sum(accepted(r['case'],r[key]) for r in legacy)/len(legacy) for key in ['nll','blended']} | |
| previous=Reranker.load('runs/storypatch-ranker-v2/best',tokenizer,'cpu');previous_correct=0 | |
| for row in legacy: | |
| c=row['case'];a,b=c['start'],c['end'];pool=row['candidates'] | |
| texts=[(c['text'][:a]+r['replacement']+c['text'][b:],a,a+len(r['replacement'])) for r in pool] | |
| scores=previous.scores(texts) if texts else [] | |
| selected=pool[max(range(len(scores)),key=lambda i:scores[i])]['replacement'] if scores else '' | |
| row['previous_ranker_top1']=selected;previous_correct+=accepted(c,selected) | |
| previous_metrics={'top1':previous_correct/len(legacy),'device':'cpu','precision':'fp32', | |
| 'weights_sha256':digest('runs/storypatch-ranker-v2/best/model.safetensors'), | |
| 'candidate_pool':'exact stored candidate pool shared with NLL and v3; no regeneration'} | |
| report={'binding':binding,'selection':{'blending':blending,'calibration':calibration},'test':results, | |
| 'bootstrap':bootstrap(mixed,calibration),'legacy':{'summary':legacy_metrics,'rows':legacy,'previous_ranker_matched_pool':previous_metrics}, | |
| 'reference_pool_test':validation(ranker.model,encode_groups(test,tokenizer,512),test,'cuda'), | |
| 'limits':['TinyStories is synthetic. No human preference labels.', 'Corpus metric is original-span reconstruction, not semantic correctness.', | |
| 'Procedural cases are the previous benchmark reused for regression tracking.', 'Legacy probes are repeated diagnostics, not a new unseen test.']} | |
| (out/'evaluation.json').write_text(json.dumps(report,indent=2),encoding='utf-8') | |
| print(json.dumps({'test':results,'legacy':legacy_metrics}),flush=True) | |
| if __name__=='__main__':main() | |