float_multiply.py (9391B)
1 #!/usr/bin/env python3 2 """Multiplication work/source-form screen on the unchanged lazy reference host.""" 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 collections_batch import ProbeRunner 12 from base_workloads import encode as nat 13 from http_headers import LEAF, TRUE, pair, tree_list, expect 14 from float_pipelines import finish, encoded_result 15 from bundle_dag import read 16 17 ROOT = Path(__file__).resolve().parent.parent 18 VARIANTS = ['native','hoisted','topBit','fused','positive','accumulate','karatsuba','mux','adaptive'] 19 20 21 def sha(path): 22 return hashlib.sha256(path.read_bytes()).hexdigest() 23 24 25 def wrappers(): 26 out = [] 27 for name in VARIANTS: 28 for suffix,consumer in [('Exact',''),('Rounded','P.finishL '),('Bounded','finish53 ')]: 29 out.append(f'{name}{suffix} xs = map (x : {consumer}({name} (head x) (tail x))) xs') 30 out += [f'{name}Zero x = {name} (spin x) 0', 31 f'{name}Low x = head ({name} (t (t t) (spin x)) 1)', 32 f'{name}ShiftLow x = head ({name} (t (t t) (spin x)) 2)'] 33 return '\n'.join(out)+'\n' 34 35 36 class Runner(ProbeRunner): 37 def run(self,label,args,seconds=30): 38 # Bound each demand probe independently. 39 if label.startswith('demand-'): 40 seconds = min(seconds,3) 41 return super().run(label,args,seconds) 42 43 44 def graph_stats(bundle): 45 _,_,nodes,exports,_ = read(bundle) 46 rows = [] 47 digests = [] 48 for tag,*children in nodes: 49 digests.append(hashlib.sha256(bytes([tag])+b''.join(digests[c] for c in children)).digest()) 50 for name in VARIANTS: 51 root = exports[name] 52 seen,todo = set(),[root] 53 while todo: 54 i = todo.pop() 55 if i not in seen: 56 seen.add(i) 57 todo.extend(nodes[i][1:]) 58 rows.append(dict(name=name,reachable_dag_nodes=len(seen),tree_sha256=digests[root].hex())) 59 return rows 60 61 62 def main(): 63 p = argparse.ArgumentParser(description=__doc__) 64 p.add_argument('tricu',type=Path) 65 p.add_argument('--directory',required=True,type=Path) 66 p.add_argument('--size',type=int,default=8) 67 p.add_argument('--repeats',type=int,default=5) 68 p.add_argument('--seconds',type=int,default=1200) 69 args = p.parse_args() 70 if not 4 <= args.size <= 32 or not 3 <= args.repeats <= 10: 71 p.error('size 4..32; repeats 3..10') 72 root = args.directory.resolve() 73 root.mkdir(parents=True,exist_ok=False) 74 workspace = root/'workspace' 75 workspace.mkdir() 76 sources = {'driver.tri':ROOT/'bench/float-multiply.tri', 77 'pipelines.tri':ROOT/'bench/float-pipelines.tri', 78 'kernels.tri':ROOT/'bench/float-significands.tri', 79 'layouts.tri':ROOT/'bench/float-layouts.tri', 80 'native_math.tri':ROOT/'lib/math/native.tri'} 81 for name,path in sources.items(): 82 (workspace/name).write_bytes(path.read_bytes()) 83 with (workspace/'driver.tri').open('a') as f: 84 f.write(wrappers()) 85 runner = Runner(args.tricu,root,args.seconds,memory_mib=2048) 86 report = dict(complete=False,scope='fresh-process-invoke-through-full-force', 87 runtime_sha256=sha(args.tricu),harness_sha256=sha(Path(__file__)), 88 oracle_dependency_sha256=sha(ROOT/'test/float_pipelines.py'), 89 compiler_sha256=sha(ROOT/'bootstrap/build-stable.arboricx'), 90 reducer_sha256=sha(ROOT/'src/Runtime/Tree.hs'),platform=platform.platform(), 91 sources={name:sha(path) for name,path in sources.items()}, 92 staged_driver_sha256=sha(workspace/'driver.tri'), 93 validation=[],demand=[],samples=[],summaries=[]) 94 try: 95 tool = runner.imported(ROOT/'bootstrap/build-stable.arboricx','compiler') 96 bundle = root/'multiply.arboricx' 97 runner.run('compile',['compile','--file',workspace/'driver.tri','--toolchain',tool, 98 '--tool-store',runner.store,'--store',runner.store,'--output',bundle,'--compile-timeout','240'],270) 99 pin = runner.imported(bundle,'multiply') 100 report['bundle_sha256'] = sha(bundle) 101 report['compiled_shapes'] = graph_stats(bundle) 102 103 def run(label,name,raw,wanted,measure=False): 104 path = root/(label+'.input.tree') 105 path.write_bytes(raw) 106 actual = runner.invoke(label,pin,name,['--arg-tree-file',path]+ 107 (['--profile-full-result'] if measure else [])) 108 expect(actual,wanted,label) 109 if not measure: 110 report['validation'].append(dict(label=label,input_sha256=hashlib.sha256(raw).hexdigest(), 111 output_sha256=hashlib.sha256(actual).hexdigest())) 112 return 113 log = (root/(label+'.log')).read_text() 114 points = {v['profile']:v for line in log.splitlines() if line.startswith('{') 115 for v in [json.loads(line)]} 116 before,after = points['invoke-begin-1'],points['force-end-1'] 117 return dict(wall_ns=after['wall_ns']-before['wall_ns'], 118 allocated_bytes=after['allocated_bytes']-before['allocated_bytes'], 119 gc_elapsed_ns=after['gc_elapsed_ns']-before['gc_elapsed_ns'], 120 post_force_live_bytes=after['live_bytes'], 121 live_delta_bytes=after['live_bytes']-before['live_bytes']) 122 123 def material(rows,suffix): 124 raw = tree_list([pair(nat(a),nat(b)) for a,b in rows]) 125 wanted = tree_list([nat(a*b) if suffix=='Exact' else encoded_result(finish(a*b),nat) for a,b in rows]) 126 return raw,wanted 127 128 rng = random.Random(535326) 129 rows = [(a,b) for a in range(16) for b in range(16)] 130 for w in [26,27,52,53,54,106]: 131 rows += [(a,b) for a in [(1 << w)-1,1 << w,(1 << w)+1] 132 for b in [0,1,2,(1 << w)-1]] 133 rows += [(rng.getrandbits(53),rng.getrandbits(53)) for _ in range(32)] 134 report['general_cases'] = len(rows) 135 for name in VARIANTS: 136 domain = [(a,b) for a,b in rows if name!='positive' or a>0] 137 for suffix in ['Exact','Rounded']: 138 for offset in range(0,len(domain),48): 139 raw,wanted = material(domain[offset:offset+48],suffix) 140 run(f'check-{name}{suffix}-{offset}',name+suffix,raw,wanted) 141 normalized = [(rng.getrandbits(52)+(1 << 52),rng.getrandbits(52)+(1 << 52)) for _ in range(64)] 142 normalized += [(a,b) for a in [1 << 52,(1 << 52)+1,(1 << 53)-1] 143 for b in [1 << 52,(1 << 52)+1,(1 << 53)-1]] 144 report['normalized_cases'] = len(normalized) 145 for name in VARIANTS: 146 for offset in range(0,len(normalized),24): 147 raw,wanted = material(normalized[offset:offset+24],'Bounded') 148 run(f'check-{name}Bounded-{offset}',name+'Bounded',raw,wanted) 149 for name in VARIANTS: 150 for suffix,wanted in [('Zero',LEAF),('Low',TRUE),('ShiftLow',LEAF)]: 151 label = f'demand-{name}{suffix}' 152 try: 153 run(label,name+suffix,LEAF,wanted) 154 report['demand'].append(dict(name=name,probe=suffix,outcome='expected-result')) 155 except Exception as error: 156 # Record the outcome within this budget and preserve its exact 157 # exception and log. 158 report['demand'].append(dict(name=name,probe=suffix,outcome='failed', 159 error_type=type(error).__name__,error=str(error))) 160 groups = {'random':normalized[:args.size], 161 'dense':[((1 << 53)-1,(1 << 53)-1-(i%3)) for i in range(args.size)], 162 'sparseRight':[(a,(1 << 52)+(i%2)) for i,(a,b) in enumerate(normalized[:args.size])], 163 'sparseLeft':[((1 << 52)+(i%2),b) for i,(a,b) in enumerate(normalized[:args.size])]} 164 schedule = [(group,name+suffix,*material(values,suffix)) for group,values in groups.items() 165 for name in VARIANTS for suffix in ['Exact','Rounded','Bounded']] 166 for repeat in range(args.repeats): 167 rng.shuffle(schedule) 168 for group,name,raw,wanted in schedule: 169 sample = run(f'{group}-{name}-{repeat}',name,raw,wanted,True) 170 report['samples'].append(dict(group=group,name=name,size=args.size,repeat=repeat, 171 warmup=repeat==0,**sample)) 172 for group,name,_,_ in sorted(schedule): 173 samples = [v for v in report['samples'] if v['group']==group and v['name']==name and not v['warmup']] 174 row = dict(group=group,name=name,size=args.size) 175 for key in ['wall_ns','allocated_bytes','gc_elapsed_ns','post_force_live_bytes','live_delta_bytes']: 176 row[key] = statistics.median(v[key] for v in samples) 177 row[key+'_min'] = min(v[key] for v in samples) 178 row[key+'_max'] = max(v[key] for v in samples) 179 report['summaries'].append(row) 180 print(json.dumps(row),flush=True) 181 report['complete'] = True 182 finally: 183 (root/'report.json').write_text(json.dumps(report,indent=2)+'\n') 184 185 186 if __name__ == '__main__': 187 main()