tricu

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

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