tricu

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

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