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