float_near_jam.py (7888B)
1 #!/usr/bin/env python3 2 """Three-extra-bit near alignment versus unchanged exact near/far control.""" 3 import argparse 4 import hashlib 5 import json 6 from pathlib import Path 7 import random 8 import statistics 9 10 from float_aligned_quantize import cases as prior_cases, nearest, exact, raw_rows 11 from float_quantize import ROOT, BIAS, dyadic, encoded 12 from float_multiply import Runner, sha 13 from base_workloads import encode as nat 14 from http_headers import TRUE, pair, tree_list, expect 15 16 17 def jam(n,k): 18 q,r=divmod(n,1 << k) 19 return q | bool(r) 20 21 22 def reduced(row,sub=False): 23 scale,gap,a,b=row 24 assert 2<=gap<=54 25 aligned=b << (3-gap) if gap<=3 else jam(b,gap-3) 26 return dyadic(8*a+(-aligned if sub else aligned),scale-BIAS-3) 27 28 29 def cases(): 30 lo,hi=2**52,2**53-1 31 # All near gaps, with carries, cancellation boundaries and even/odd ties. 32 pairs=[(lo,lo),(lo,lo+1),(lo,hi),(lo+1,lo),(lo+2,lo+1),(hi,lo),(hi,hi)] 33 rows=[(s,g,max(a,b) if g==0 else a,min(a,b) if g==0 else b) 34 for s in [1177,1178,1229,1230,2252,3275,3276] 35 for g in range(55) for a,b in pairs] 36 rng=random.Random(573) 37 rows += [(rng.choice([1177,1229,1230,2252,3275]),rng.randrange(2,55), 38 rng.randrange(lo,hi+1),rng.randrange(lo,hi+1)) for _ in range(256)] 39 # Include far-range and extreme-scale cases. 40 rows += prior_cases()[::9] 41 return list(dict.fromkeys(rows)) 42 43 44 def main(): 45 p=argparse.ArgumentParser(description=__doc__) 46 p.add_argument('tricu',type=Path);p.add_argument('--directory',required=True,type=Path) 47 args=p.parse_args();root=args.directory.resolve();root.mkdir(parents=True,exist_ok=False) 48 work=root/'workspace';work.mkdir() 49 sources={'driver.tri':'bench/float-near-jam.tri','aligned.tri':'bench/float-aligned-quantize.tri', 50 'fast.tri':'bench/float-quantize-fast.tri','quantize.tri':'bench/float-quantize.tri', 51 'pipelines.tri':'bench/float-pipelines.tri','kernels.tri':'bench/float-significands.tri', 52 'layouts.tri':'bench/float-layouts.tri','native_math.tri':'lib/math/native.tri'} 53 for name,path in sources.items(): (work/name).write_bytes((ROOT/path).read_bytes()) 54 report=dict(complete=False,runtime_sha256=sha(args.tricu),compiler_sha256=sha(ROOT/'bootstrap/build-stable.arboricx'), 55 harness_sha256=sha(Path(__file__)),oracle_sha256=sha(ROOT/'test/float_aligned_quantize.py'), 56 sources={n:sha(ROOT/p) for n,p in sources.items()},validation=[],demand=[],samples=[],summaries=[]) 57 runner=Runner(args.tricu,root,2400,memory_mib=2048) 58 try: 59 tool=runner.imported(ROOT/'bootstrap/build-stable.arboricx','compiler');bundle=root/'near.arboricx' 60 runner.run('compile',['compile','--file',work/'driver.tri','--toolchain',tool,'--tool-store',runner.store, 61 '--store',runner.store,'--output',bundle,'--compile-timeout','240'],270) 62 pin=runner.imported(bundle,'near');report['bundle_sha256']=sha(bundle) 63 def run(label,name,raw,wanted,count,measure=False): 64 path=root/(label+'.input.tree');path.write_bytes(raw) 65 actual=runner.invoke(label,pin,name,['--arg-tree-file',path]+(['--profile-full-result'] if measure else [])) 66 expect(actual,wanted,label) 67 if not measure: 68 report['validation'].append(dict(label=label,cases=count,input_sha256=sha(path), 69 output_sha256=hashlib.sha256(actual).hexdigest()));return 70 points={v['profile']:v for line in (root/(label+'.log')).read_text().splitlines() 71 if line.startswith('{') for v in [json.loads(line)]} 72 a,b=points['invoke-begin-1'],points['force-end-1'] 73 return dict(wall_ns=b['wall_ns']-a['wall_ns'],allocated_bytes=b['allocated_bytes']-a['allocated_bytes']) 74 rows=cases();report['cases_per_worker']=len(rows) 75 for suffix,sub in [('Adds',False),('Subtracts',True)]: 76 for offset in range(0,len(rows),32): 77 batch=rows[offset:offset+32];raw=raw_rows(batch) 78 wanted=tree_list([encoded(nearest(exact(x,sub))) for x in batch]) 79 for name in [suffix.lower(),'control'+suffix,'hybrid'+suffix]: 80 run(f'check-{name}-{offset}',name,raw,wanted,len(batch)) 81 js=[(k,n) for k in range(54) for n in [0,1,2,3,2**52,2**52+1,2**53-1]] 82 report['jam_cases']=len(js) 83 for offset in range(0,len(js),32): 84 batch=js[offset:offset+32] 85 run('jam-'+str(offset),'jams',tree_list([pair(nat(k),nat(n)) for k,n in batch]), 86 tree_list([nat(jam(n,k)) for k,n in batch]),len(batch)) 87 negatives=[((2252,53,2**52,2**52+1),False),((2252,53,2**52+2,2**52+1),True), 88 ((2252,4,2**52,2**52+1),False)] 89 for i,(row,sub) in enumerate(negatives): 90 s,g,a,b=row;n=8*a+(-1 if sub else 1)*(b >> (g-3)) 91 wrong=nearest(dyadic(n,s-BIAS-3));correct=nearest(exact(row,sub));assert wrong!=correct 92 run('truncate-negative-'+str(i),'truncateSubtracts' if sub else 'truncateAdds',raw_rows([row]), 93 tree_list([encoded(wrong)]),1) 94 report.setdefault('truncate_counterexamples',[]).append(dict(row=row,subtract=sub,correct=correct,wrong=wrong)) 95 for name,wanted in [('jamZeroDemand',TRUE),('farDemand',encoded((0x3ff0000000000000,True,False))), 96 ('cancelDemand',encoded((0,False,False))), 97 ('hybridFarDemand',encoded((0x3ff0000000000000,True,False))), 98 ('hybridCancelDemand',encoded((0,False,False))), 99 ('jamStickyDemand',nat(1)),('jamOddDemand',nat(1))]: 100 actual=runner.invoke('demand-'+name,pin,name,['--arg','0']);expect(actual,wanted,name) 101 report['demand'].append(dict(name=name,outcome='expected-result')) 102 groups={str(g):[(2252,g,2**52+i,2**53-1-i) for i in range(16)] for g in [0,1,2,3,4,5,6,7,8,16,27,53,54,55]} 103 groups['0']=[(2252,0,2**53-1-i,2**52+i) for i in range(16)] 104 groups['cancellation']=[(2252,0,2**52+i,2**52) for i in range(16)] 105 groups['underflow']=[(1229,53,2**52+i,2**52+1+i) for i in range(16)] 106 shape_rng=random.Random(853) 107 for gap in [8,27,54]: 108 groups[f'random-{gap}']=[(2252,gap,shape_rng.randrange(2**52,2**53),shape_rng.randrange(2**52,2**53)) for _ in range(16)] 109 groups[f'dense-{gap}']=[(2252,gap,2**53-1-i,2**53-1) for i in range(16)] 110 jobs=[] 111 for group,batch in groups.items(): 112 for suffix,sub in [('Adds',False),('Subtracts',True)]: 113 raw=raw_rows(batch);wanted=tree_list([encoded(nearest(exact(x,sub))) for x in batch]) 114 jobs += [(group,name,raw,wanted,len(batch)) for name in [suffix.lower(),'control'+suffix,'hybrid'+suffix]] 115 rng=random.Random(573) 116 for rep in range(5): 117 schedule=list(jobs);rng.shuffle(schedule) 118 for group,name,raw,wanted,size in schedule: 119 sample=run(f'measure-{group}-{name}-{rep}',name,raw,wanted,size,True) 120 report['samples'].append(dict(group=group,name=name,size=size,repeat=rep,warmup=rep==0,**sample)) 121 for group,name,_,_,size in jobs: 122 samples=[s for s in report['samples'] if s['group']==group and s['name']==name and not s['warmup']] 123 row=dict(group=group,name=name,size=size) 124 for key in ['wall_ns','allocated_bytes']: 125 row[key]=statistics.median(s[key] for s in samples) 126 row[key+'_min']=min(s[key] for s in samples);row[key+'_max']=max(s[key] for s in samples) 127 report['summaries'].append(row);print(json.dumps(row),flush=True) 128 report['complete']=True 129 finally: (root/'report.json').write_text(json.dumps(report,indent=2)+'\n') 130 131 132 if __name__=='__main__':main()