float_adders.py (8369B)
1 #!/usr/bin/env python3 2 """Adder Boolean demand/order and local-binding source-form investigation.""" 3 import argparse 4 import hashlib 5 import json 6 import platform 7 import random 8 import statistics 9 from pathlib import Path 10 11 from float_multiply import Runner, sha 12 from base_workloads import encode as nat 13 from http_headers import LEAF, TRUE, pair, tree_list, expect 14 from bundle_dag import read 15 16 ROOT = Path(__file__).resolve().parent.parent 17 VARIANTS = ['native','originalMux','ordered','inline'] 18 19 20 def wrappers(): 21 lines = [] 22 for v in VARIANTS: 23 for suffix,consumer in [('Full',''),('Low','N.odd '),('Next','head (tail ' )]: 24 expr = f'{v}Add (head x) (tail x)' 25 if suffix == 'Next': 26 expr = f'head (tail ({expr}))' 27 elif consumer: 28 expr = f'{consumer}({expr})' 29 lines.append(f'{v}{suffix} xs = map (x : {expr}) xs') 30 probes = {'UnknownLow': f'head (tail ({v}Add 2 (t (spin x) 1)))', 31 'UnknownLowHigh': f'head (tail (tail ({v}Add 2 (t (spin x) 1))))', 32 'LowWithoutTails': f'head ({v}Add (t (t t) (spin x)) (t t (spin x)))', 33 'RightZero': f'head ({v}Add (t (t t) (spin x)) 0)', 34 'LeftZero': f'head ({v}Add 0 (t (t t) (spin x)))'} 35 lines += [f'{v}{name} x = {expr}' for name,expr in probes.items()] 36 for name,worker in [('nativeProduct','N.multiply'),('originalMuxProduct','M.mux'), 37 ('orderedProduct','orderedMultiply'),('inlineProduct','inlineMultiply')]: 38 lines.append(f'{name} xs = map (x : {worker} (head x) (tail x)) xs') 39 return '\n'.join(lines)+'\n' 40 41 42 def main(): 43 p = argparse.ArgumentParser(description=__doc__) 44 p.add_argument('tricu',type=Path) 45 p.add_argument('--directory',required=True,type=Path) 46 p.add_argument('--size',type=int,default=16) 47 p.add_argument('--repeats',type=int,default=5) 48 args = p.parse_args() 49 if not 4 <= args.size <= 64 or not 3 <= args.repeats <= 10: 50 p.error('size 4..64; repeats 3..10') 51 root = args.directory.resolve();root.mkdir(parents=True,exist_ok=False) 52 workspace = root/'workspace';workspace.mkdir() 53 sources = {'driver.tri':'bench/float-adders.tri','multiply.tri':'bench/float-multiply.tri', 54 'pipelines.tri':'bench/float-pipelines.tri','kernels.tri':'bench/float-significands.tri', 55 'layouts.tri':'bench/float-layouts.tri','native_math.tri':'lib/math/native.tri'} 56 for name,path in sources.items(): 57 (workspace/name).write_bytes((ROOT/path).read_bytes()) 58 with (workspace/'driver.tri').open('a') as f:f.write(wrappers()) 59 runner = Runner(args.tricu,root,900,memory_mib=2048) 60 report = dict(complete=False,runtime_sha256=sha(args.tricu),compiler_sha256=sha(ROOT/'bootstrap/build-stable.arboricx'), 61 harness_sha256=sha(Path(__file__)),runner_dependency_sha256=sha(ROOT/'test/float_multiply.py'), 62 platform=platform.platform(),sources={name:sha(ROOT/path) for name,path in sources.items()}, 63 staged_driver_sha256=sha(workspace/'driver.tri'),validation=[],demand=[],samples=[],summaries=[]) 64 try: 65 tool = runner.imported(ROOT/'bootstrap/build-stable.arboricx','compiler') 66 bundle = root/'adders.arboricx' 67 runner.run('compile',['compile','--file',workspace/'driver.tri','--toolchain',tool, 68 '--tool-store',runner.store,'--store',runner.store,'--output',bundle,'--compile-timeout','240'],270) 69 pin = runner.imported(bundle,'adders');report['bundle_sha256']=sha(bundle) 70 _,_,nodes,exports,_ = read(bundle) 71 report['compiled_shapes'] = [] 72 for name in [v+'Add' for v in VARIANTS]: 73 seen,todo=set(),[exports[name]] 74 while todo: 75 i=todo.pop() 76 if i not in seen:seen.add(i);todo.extend(nodes[i][1:]) 77 report['compiled_shapes'].append(dict(name=name,reachable_dag_nodes=len(seen))) 78 79 def run(label,name,raw,wanted,measure=False): 80 path=root/(label+'.input.tree');path.write_bytes(raw) 81 actual=runner.invoke(label,pin,name,['--arg-tree-file',path]+(['--profile-full-result'] if measure else [])) 82 expect(actual,wanted,label) 83 if not measure: 84 report['validation'].append(dict(label=label,input_sha256=hashlib.sha256(raw).hexdigest(), 85 output_sha256=hashlib.sha256(actual).hexdigest())) 86 return 87 log=(root/(label+'.log')).read_text() 88 points={v['profile']:v for line in log.splitlines() if line.startswith('{') for v in [json.loads(line)]} 89 before,after=points['invoke-begin-1'],points['force-end-1'] 90 return dict(wall_ns=after['wall_ns']-before['wall_ns'],allocated_bytes=after['allocated_bytes']-before['allocated_bytes']) 91 92 def material(rows,consumer): 93 values=[a*b if consumer=='Product' else a+b for a,b in rows] 94 if consumer in ['Low','Next']: 95 wanted=tree_list([TRUE if n & (1 if consumer=='Low' else 2) else LEAF for n in values]) 96 else:wanted=nat(values) 97 return tree_list([pair(nat(a),nat(b)) for a,b in rows]),wanted 98 99 rng=random.Random(53106) 100 cases=[(a,b) for a in range(16) for b in range(16)] 101 for w in [8,53,106,212]: 102 cases += [(a,b) for a in [(1 << w)-1,1 << w,(1 << w)+1] for b in [0,1,(1 << w)-1]] 103 cases += [(rng.getrandbits(w),rng.getrandbits(w)) for _ in range(8)] 104 report['addition_cases']=len(cases) 105 for v in VARIANTS: 106 for suffix in ['Full','Low','Next']: 107 raw,wanted=material(cases,suffix);run('check-'+v+suffix,v+suffix,raw,wanted) 108 # These probes inspect the actual sum trees. 109 for v in VARIANTS: 110 for suffix,wanted in [('UnknownLow',LEAF),('UnknownLowHigh',TRUE),('LowWithoutTails',TRUE), 111 ('RightZero',TRUE),('LeftZero',TRUE)]: 112 label='demand-'+v+suffix 113 try: 114 run(label,v+suffix,LEAF,wanted) 115 report['demand'].append(dict(name=v,probe=suffix,outcome='expected-result')) 116 except RuntimeError as error: 117 if v!='originalMux' or suffix not in ['UnknownLow','UnknownLowHigh'] or str(error)!='wall-time guard exceeded': 118 raise 119 report['demand'].append(dict(name=v,probe=suffix,outcome='timeout',seconds=3)) 120 schedule=[] 121 for w in [53,106]: 122 groups={'random':[(rng.getrandbits(w-1)+(1 << (w-1)),rng.getrandbits(w-1)+(1 << (w-1))) for _ in range(args.size)], 123 'carry':[((1 << w)-1,1+(i%2)) for i in range(args.size)]} 124 for group,rows in groups.items(): 125 for v in VARIANTS: 126 for suffix in ['Full','Low','Next']: 127 raw,wanted=material(rows,suffix) 128 schedule.append((f'{w}-{group}',v+suffix,raw,wanted)) 129 if w==53: 130 for v in VARIANTS: 131 raw,wanted=material(rows,'Product') 132 run('check-'+group+'-'+v+'Product',v+'Product',raw,wanted) 133 schedule.append((f'{w}-{group}',v+'Product',raw,wanted)) 134 for repeat in range(args.repeats): 135 rng.shuffle(schedule) 136 for group,name,raw,wanted in schedule: 137 sample=run(f'{group}-{name}-{repeat}',name,raw,wanted,True) 138 report['samples'].append(dict(group=group,name=name,size=args.size,repeat=repeat,warmup=repeat==0,**sample)) 139 for group,name,_,_ in sorted(schedule): 140 rows=[s for s in report['samples'] if s['group']==group and s['name']==name and not s['warmup']] 141 row=dict(group=group,name=name,size=args.size) 142 for key in ['wall_ns','allocated_bytes']: 143 row[key]=statistics.median(s[key] for s in rows) 144 row[key+'_min']=min(s[key] for s in rows);row[key+'_max']=max(s[key] for s in rows) 145 report['summaries'].append(row);print(json.dumps(row),flush=True) 146 report['complete']=True 147 finally: 148 (root/'report.json').write_text(json.dumps(report,indent=2)+'\n') 149 150 151 if __name__=='__main__':main()