tricu

A lazy functional language based on Tree Calculus
Log | Files | Refs | README | LICENSE

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()