tricu

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

float_working.py (10609B)


      1 #!/usr/bin/env python3
      2 """Private rounded values between operations, versus word-backed chains and round-trip ablations."""
      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 nearest
     11 from float_quantize import ROOT, dyadic, value
     12 from float_multiply import Runner, sha
     13 from base_workloads import encode as nat
     14 from http_headers import LEAF, TRUE, pair, tree_list, expect
     15 from float_finite import SIGN, LIMIT, MASK, finite_value, cases as add_cases
     16 from float_finite_product_quotient import cases as product_cases
     17 
     18 from float_ieee import OPS, oracle, encoded, class_words, SIGN, INF, QNAN, MIN_NORMAL
     19 
     20 from float_chains import cases as prior_cases, groups as prior_groups, chain_oracle, payload, raw_rows
     21 from float_finite import normalized, special_words
     22 from float_ieee import TINY_BOUNDARY, direct_cases as tiny_cases
     23 from float_quantize import cases as quantizer_cases
     24 
     25 VARIANTS=['checkedBatch','cachedBatch','roundtripBatch','freshBatch','workingBatch']
     26 
     27 
     28 def flag_tree(flags):
     29     out=TRUE if flags[-1] else LEAF
     30     for bit in reversed(flags[:-1]):out=pair(TRUE if bit else LEAF,out)
     31     return out
     32 
     33 
     34 def working_parts(word):
     35     sign=bool(word&SIGN);m=word & (SIGN-1)
     36     if m==0:return 0,sign,0,0
     37     if m==INF:return 2,sign,0,0
     38     if m>INF:return 3,sign,0,m & (2**52-1)
     39     _,s,n=normalized(word)
     40     return 1,sign,s,n
     41 
     42 
     43 def working_tree(word):
     44     kind,sign,s,n=working_parts(word)
     45     return pair(nat(kind),pair(TRUE if sign else LEAF,pair(nat(s),nat(n))))
     46 
     47 
     48 def internal_result(result):
     49     word,flags=result
     50     return pair(working_tree(word),flag_tree(flags))
     51 
     52 
     53 def cached_tree(word):
     54     sign,s,n=normalized(word) # materialization probes use nonzero finite words
     55     return pair(nat(word),pair(LEAF,pair(LEAF,pair(LEAF,pair(TRUE if sign else LEAF,pair(nat(s),nat(n)))))))
     56 
     57 
     58 def cases():
     59     rows=prior_cases()
     60     rows += [(word,[],[]) for word in [SIGN,INF|1,SIGN|QNAN|123]]
     61     # Subnormal results are normalized privately, then reused by every operator.
     62     for word in [1,2,3,2**51-1,2**52-1,2**52,2**52+1]:
     63         for sign in [0,SIGN]:
     64             for op in range(4):
     65                 pool=[0x3fe0000000000000,1,0x3ff0000000000000]
     66                 steps=[(2,0),(op,1),(0,2)]
     67                 for end in range(1,4):rows.append((word|sign,pool,steps[:end]))
     68     return rows
     69 
     70 
     71 def groups():
     72     g=prior_groups()
     73     g['empty-interchange']=[(word,[],[]) for word in [1,0x3ff0000000000000,SIGN|INF|1,SIGN]]
     74     g['add-reuse-64']=[(0x3ff0000000000000+i,[0x3ca0000000000001],[(0,0)]*64) for i in range(4)]
     75     g['affine-reuse-64']=[(0x3ff0000000000000+i,[0x3fefffffffffffff,0x3ca0000000000001],[(2,0),(0,1)]*64) for i in range(4)]
     76     g['subnormal-reuse-16']=[((1<<51)+i,[0x3fe0000000000000],[(2,0)]*16) for i in range(4)]
     77     return g
     78 
     79 
     80 def main():
     81     p=argparse.ArgumentParser(description=__doc__)
     82     p.add_argument('tricu',type=Path);p.add_argument('--directory',required=True,type=Path)
     83     args=p.parse_args();root=args.directory.resolve();root.mkdir(parents=True,exist_ok=False)
     84     work=root/'workspace';work.mkdir()
     85     sources={'driver.tri':'bench/float-working.tri','chains.tri':'bench/float-chains.tri','ieee.tri':'bench/float-ieee.tri','product_quotient.tri':'bench/float-finite-product-quotient.tri',
     86              'finite.tri':'bench/float-finite.tri','adders.tri':'bench/float-adders.tri',
     87              'multiply.tri':'bench/float-multiply.tri','near.tri':'bench/float-near-jam.tri',
     88              'aligned.tri':'bench/float-aligned-quantize.tri','fast.tri':'bench/float-quantize-fast.tri',
     89              'quantize.tri':'bench/float-quantize.tri','pipelines.tri':'bench/float-pipelines.tri',
     90              'kernels.tri':'bench/float-significands.tri','layouts.tri':'bench/float-layouts.tri',
     91              'native_math.tri':'lib/math/native.tri'}
     92     for name,path in sources.items():(work/name).write_bytes((ROOT/path).read_bytes())
     93     report=dict(complete=False,scope='private rounded working values; no intermediate IEEE words; trusted valid inputs; benchmark only',
     94                 runtime_sha256=sha(args.tricu),compiler_sha256=sha(ROOT/'bootstrap/build-stable.arboricx'),
     95                 harness_sha256=sha(Path(__file__)),sources={n:sha(ROOT/p) for n,p in sources.items()},
     96                 validation=[],demand=[],samples=[],summaries=[])
     97     runner=Runner(args.tricu,root,2400,memory_mib=2048)
     98     try:
     99         tool=runner.imported(ROOT/'bootstrap/build-stable.arboricx','compiler');bundle=root/'ieee.arboricx'
    100         runner.run('compile',['compile','--file',work/'driver.tri','--toolchain',tool,'--tool-store',runner.store,
    101                              '--store',runner.store,'--output',bundle,'--compile-timeout','240'],270)
    102         pin=runner.imported(bundle,'ieee');report['bundle_sha256']=sha(bundle)
    103         def run(label,name,raw,wanted,count,measure=False):
    104             file=root/(label+'.input.tree');file.write_bytes(raw)
    105             actual=runner.invoke(label,pin,name,['--arg-tree-file',file]+(['--profile-full-result'] if measure else []))
    106             expect(actual,wanted,label)
    107             if not measure:
    108                 report['validation'].append(dict(label=label,cases=count,input_sha256=sha(file),
    109                     output_sha256=hashlib.sha256(actual).hexdigest()));return
    110             points={v['profile']:v for line in (root/(label+'.log')).read_text().splitlines()
    111                     if line.startswith('{') for v in [json.loads(line)]}
    112             a,b=points['invoke-begin-1'],points['force-end-1']
    113             return dict(wall_ns=b['wall_ns']-a['wall_ns'],allocated_bytes=b['allocated_bytes']-a['allocated_bytes'],
    114                 process_peak_kib=runner.records[-1]['peak_kib'],
    115                 process_maximum_live_bytes=runner.records[-1]['rts_process']['maximum_live_bytes'])
    116         def material(rows):
    117             return raw_rows(rows),tree_list([payload(chain_oracle(row)) for row in rows])
    118         rows=cases();report['chain_cases']=len(rows)
    119         for name in VARIANTS:
    120             for offset in range(0,len(rows),12):
    121                 batch=rows[offset:offset+12];raw,wanted=material(batch)
    122                 run(f'check-{name}-{offset}',name,raw,wanted,len(batch))
    123         # Force every working-result field to check the private normalized invariant.
    124         for offset in range(0,len(rows),12):
    125             batch=rows[offset:offset+12]
    126             run('internal-'+str(offset),'internalBatch',raw_rows(batch),
    127                 tree_list([internal_result(chain_oracle(row)) for row in batch]),len(batch))
    128         quant_rows=list(dict.fromkeys([(s,n) for n,s in quantizer_cases()]+tiny_cases()))
    129         report['direct_quantizer_cases']=len(quant_rows)
    130         for offset in range(0,len(quant_rows),12):
    131             batch=quant_rows[offset:offset+12];wanted=[]
    132             for s,n in batch:
    133                 x=dyadic(n,s-2304);bits,ix,_=nearest(x)
    134                 wanted.append(payload((bits,(False,False,bits==INF,ix and x<TINY_BOUNDARY,ix))))
    135             run('quantize-'+str(offset),'quantized',tree_list([pair(nat(s),nat(n)) for s,n in batch]),tree_list(wanted),len(batch))
    136         rng=random.Random(752)
    137         words=special_words()+class_words()+[sign|(1<<k) for sign in [0,SIGN] for k in range(52)]+[rng.getrandbits(64) for _ in range(128)]
    138         report['transport_cases']=len(words)
    139         for offset in range(0,len(words),24):
    140             batch=words[offset:offset+24];raw=tree_list([nat(w) for w in batch])
    141             run('transport-'+str(offset),'transport',raw,raw,len(batch))
    142         for name,wanted in [
    143             ('emptyDemand',payload((SIGN,(False,)*5))),
    144             ('unusedDemand',payload((0x3ff0000000000000,(False,)*5))),
    145             ('zeroScaleDemand',payload((0,(False,)*5))),
    146             ('farDemand',payload((0x3ff0000000000000,(False,False,False,False,True)))),
    147             ('nanWordDemand',nat(QNAN|1)),
    148             ('zeroProductDemand',payload((SIGN,(False,)*5)))]:
    149             actual=runner.invoke('demand-'+name,pin,name,['--arg','0'])
    150             expect(actual,wanted,name);report['demand'].append(dict(name=name,outcome='expected-result'))
    151         rng=random.Random(751)
    152         jobs=[]
    153         for group,batch in groups().items():
    154             for name in VARIANTS:
    155                 raw,wanted=material(batch);jobs.append((group,name,raw,wanted,len(batch)))
    156         # Full-materialization/retention stress: payloads deliberately differ.
    157         # Whole-process RSS/RTS maximum live bytes include load and rendering.
    158         material_rng=random.Random(755)
    159         pool=[(material_rng.randrange(1,2047)<<52)|material_rng.getrandbits(52)|(material_rng.randrange(2)<<63) for _ in range(1024)]
    160         for count in [1,16,256,1024]:
    161             batch=pool[:count];raw=tree_list([nat(w) for w in batch])
    162             for name,make in [('retainWords',nat),('retainCached',cached_tree),('retainWorking',working_tree)]:
    163                 wanted=tree_list([make(w) for w in batch])
    164                 jobs.append(('retain-'+str(count),name,raw,wanted,count))
    165         subnormal_pool=[]
    166         for _ in range(1024):
    167             width=material_rng.choice([1,2,8,26,52])
    168             subnormal_pool.append((1<<(width-1))|material_rng.getrandbits(width-1))
    169         for count in [256,1024]:
    170             batch=subnormal_pool[:count];raw=tree_list([nat(w) for w in batch])
    171             for name,make in [('retainWords',nat),('retainCached',cached_tree),('retainWorking',working_tree)]:
    172                 jobs.append(('retain-subnormal-'+str(count),name,raw,tree_list([make(w) for w in batch]),count))
    173         for rep in range(5):
    174             schedule=list(jobs);rng.shuffle(schedule)
    175             for group,name,raw,wanted,size in schedule:
    176                 sample=run(f'measure-{group}-{name}-{rep}',name,raw,wanted,size,True)
    177                 report['samples'].append(dict(group=group,name=name,size=size,repeat=rep,warmup=rep==0,**sample))
    178         for group,name,_,_,size in jobs:
    179             samples=[s for s in report['samples'] if s['group']==group and s['name']==name and not s['warmup']]
    180             row=dict(group=group,name=name,size=size)
    181             for key in ['wall_ns','allocated_bytes','process_peak_kib','process_maximum_live_bytes']:
    182                 row[key]=statistics.median(s[key] for s in samples)
    183                 row[key+'_min']=min(s[key] for s in samples);row[key+'_max']=max(s[key] for s in samples)
    184             report['summaries'].append(row);print(json.dumps(row),flush=True)
    185         report['complete']=True
    186     finally:(root/'report.json').write_text(json.dumps(report,indent=2)+'\n')
    187 
    188 
    189 if __name__=='__main__':main()