float_chains.py (9340B)
1 #!/usr/bin/env python3 2 """Chained operations: checked words, trusted words, operation-local and pooled caches.""" 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 VARIANTS=['checkedBatch','trustedBatch','localBatch','freshBatch','cachedBatch'] 21 22 23 def chain_oracle(row): 24 value,pool,steps=row 25 flags=[False]*5 26 for op,index in steps: 27 ok,(value,current)=oracle(value,pool[index],OPS[op]);assert ok 28 flags=[a or b for a,b in zip(flags,current)] 29 return value,tuple(flags) 30 31 32 def payload(result): 33 word,flags=result 34 out=TRUE if flags[-1] else LEAF 35 for flag in reversed(flags[:-1]):out=pair(TRUE if flag else LEAF,out) 36 return pair(nat(word),out) 37 38 39 def raw_rows(rows): 40 return tree_list([pair(nat(initial),pair(tree_list([nat(w) for w in pool]), 41 tree_list([pair(nat(op),nat(index)) for op,index in steps]))) for initial,pool,steps in rows]) 42 43 44 def cases(): 45 words=class_words() 46 rows=[(a,[b],[(op,0)]) for a in words for b in words for op in range(4)] 47 rng=random.Random(751) 48 for _ in range(96): 49 pool=[rng.choice(words),rng.getrandbits(64),rng.getrandbits(64)] 50 rows.append((rng.getrandbits(64),pool,[(rng.randrange(4),rng.randrange(3)) for _ in range(rng.choice([0,1,4,16]))])) 51 rows += [ 52 (MIN_NORMAL,[0x3fefffffffffffff,0x4000000000000000],[(2,0),(2,1)]), 53 (1,[0x3fe0000000000000,INF],[(2,0),(2,1)]), 54 (0x7fefffffffffffff,[0x4000000000000000,0,INF],[(2,0),(2,1),(0,2)]), 55 (0x3ff0000000000000,[0,INF],[(3,0),(3,1)]), 56 (QNAN|1,[INF|1,SIGN|QNAN|2],[(0,0),(1,1)]), 57 (0x3ff0000000000000,[0x3ca0000000000000],[(0,0),(0,0)])] 58 for op in range(4): 59 for n in [0,1,4,16,64]:rows.append((0x3ff0000000000000,[0x3ff0000000000001],[(op,0)]*n)) 60 # Observe every prefix, and each primitive step with fresh clear flags. 61 # This makes each step's word and fresh flags independently observable. 62 expanded=list(rows) 63 for initial,pool,steps in rows: 64 for stop in range(1,len(steps)): 65 expanded.append((initial,pool,steps[:stop])) 66 word=initial 67 for op,index in steps: 68 expanded.append((word,[pool[index]],[(op,0)])) 69 ok,(word,flags)=oracle(word,pool[index],OPS[op]);assert ok 70 unique={} 71 for initial,pool,steps in expanded: 72 unique.setdefault((initial,tuple(pool),tuple(steps)),(initial,pool,steps)) 73 return list(unique.values()) 74 75 76 def groups(): 77 result={} 78 for n in [1,4,16]: 79 result[f'add-reuse-{n}']=[(0x3ff0000000000000+i,[0x3ca0000000000001],[(0,0)]*n) for i in range(4)] 80 result[f'affine-reuse-{n}']=[(0x3ff0000000000000+i,[0x3fefffffffffffff,0x3ca0000000000001],[(2,0),(0,1)]*n) for i in range(4)] 81 result[f'divide-reuse-{n}']=[(0x3ff0000000000000+i,[0x3ff8123456789abc],[(3,0)]*n) for i in range(4)] 82 result['affine-unique-16']=[(0x3ff0000000000000+i, 83 [w for _ in range(16) for w in [0x3fefffffffffffff,0x3ca0000000000001]], 84 [(0 if j%2 else 2,j) for j in range(32)]) for i in range(4)] 85 # Equal-valued separate pool slots and varying operand values. 86 result['add-varying-16']=[(0x3ff0000000000000+i,[0x3ca0000000000001+j for j in range(16)],[(0,j) for j in range(16)]) for i in range(4)] 87 result['exceptional-16']=[(1,[0x3fe0000000000000,INF],[(2,0),(2,1)]*8) for _ in range(4)] 88 return result 89 90 91 def main(): 92 p=argparse.ArgumentParser(description=__doc__) 93 p.add_argument('tricu',type=Path);p.add_argument('--directory',required=True,type=Path) 94 args=p.parse_args();root=args.directory.resolve();root.mkdir(parents=True,exist_ok=False) 95 work=root/'workspace';work.mkdir() 96 sources={'driver.tri':'bench/float-chains.tri','ieee.tri':'bench/float-ieee.tri','product_quotient.tri':'bench/float-finite-product-quotient.tri', 97 'finite.tri':'bench/float-finite.tri','adders.tri':'bench/float-adders.tri', 98 'multiply.tri':'bench/float-multiply.tri','near.tri':'bench/float-near-jam.tri', 99 'aligned.tri':'bench/float-aligned-quantize.tri','fast.tri':'bench/float-quantize-fast.tri', 100 'quantize.tri':'bench/float-quantize.tri','pipelines.tri':'bench/float-pipelines.tri', 101 'kernels.tri':'bench/float-significands.tri','layouts.tri':'bench/float-layouts.tri', 102 'native_math.tri':'lib/math/native.tri'} 103 for name,path in sources.items():(work/name).write_bytes((ROOT/path).read_bytes()) 104 report=dict(complete=False,scope='chained RNE operations; valid word inputs; construction charged; no public representation', 105 runtime_sha256=sha(args.tricu),compiler_sha256=sha(ROOT/'bootstrap/build-stable.arboricx'), 106 harness_sha256=sha(Path(__file__)),sources={n:sha(ROOT/p) for n,p in sources.items()}, 107 validation=[],demand=[],samples=[],summaries=[]) 108 runner=Runner(args.tricu,root,2400,memory_mib=2048) 109 try: 110 tool=runner.imported(ROOT/'bootstrap/build-stable.arboricx','compiler');bundle=root/'ieee.arboricx' 111 runner.run('compile',['compile','--file',work/'driver.tri','--toolchain',tool,'--tool-store',runner.store, 112 '--store',runner.store,'--output',bundle,'--compile-timeout','240'],270) 113 pin=runner.imported(bundle,'ieee');report['bundle_sha256']=sha(bundle) 114 def run(label,name,raw,wanted,count,measure=False): 115 file=root/(label+'.input.tree');file.write_bytes(raw) 116 actual=runner.invoke(label,pin,name,['--arg-tree-file',file]+(['--profile-full-result'] if measure else [])) 117 expect(actual,wanted,label) 118 if not measure: 119 report['validation'].append(dict(label=label,cases=count,input_sha256=sha(file), 120 output_sha256=hashlib.sha256(actual).hexdigest()));return 121 points={v['profile']:v for line in (root/(label+'.log')).read_text().splitlines() 122 if line.startswith('{') for v in [json.loads(line)]} 123 a,b=points['invoke-begin-1'],points['force-end-1'] 124 return dict(wall_ns=b['wall_ns']-a['wall_ns'],allocated_bytes=b['allocated_bytes']-a['allocated_bytes']) 125 def material(rows): 126 return raw_rows(rows),tree_list([payload(chain_oracle(row)) for row in rows]) 127 rows=cases();report['chain_cases']=len(rows) 128 for name in VARIANTS: 129 for offset in range(0,len(rows),12): 130 batch=rows[offset:offset+12];raw,wanted=material(batch) 131 run(f'check-{name}-{offset}',name,raw,wanted,len(batch)) 132 def flag_tree(mask): 133 bits=[bool(mask&(1<<i)) for i in range(5)] 134 out=TRUE if bits[-1] else LEAF 135 for bit in reversed(bits[:-1]):out=pair(TRUE if bit else LEAF,out) 136 return out 137 flag_pairs=[(a,b) for a in range(32) for b in range(32)] 138 for offset in range(0,len(flag_pairs),32): 139 batch=flag_pairs[offset:offset+32] 140 run('flags-'+str(offset),'mergeFlags',tree_list([pair(flag_tree(a),flag_tree(b)) for a,b in batch]), 141 tree_list([flag_tree(a|b) for a,b in batch]),len(batch)) 142 actual=runner.invoke('demand-flags',pin,'flagDemand',['--arg','0']) 143 expect(actual,TRUE,'flagDemand');report['demand'].append(dict(name='flagDemand',outcome='expected-result')) 144 for name,word in [('emptyChecked',SIGN),('emptyCached',SIGN),('unusedPool',0x3ff0000000000000)]: 145 actual=runner.invoke('demand-'+name,pin,name,['--arg','0']) 146 expect(actual,payload((word,(False,)*5)),name) 147 report['demand'].append(dict(name=name,outcome='expected-result')) 148 rng=random.Random(751) 149 jobs=[] 150 for group,batch in groups().items(): 151 for name in VARIANTS: 152 raw,wanted=material(batch);jobs.append((group,name,raw,wanted,len(batch))) 153 for rep in range(5): 154 schedule=list(jobs);rng.shuffle(schedule) 155 for group,name,raw,wanted,size in schedule: 156 sample=run(f'measure-{group}-{name}-{rep}',name,raw,wanted,size,True) 157 report['samples'].append(dict(group=group,name=name,size=size,repeat=rep,warmup=rep==0,**sample)) 158 for group,name,_,_,size in jobs: 159 samples=[s for s in report['samples'] if s['group']==group and s['name']==name and not s['warmup']] 160 row=dict(group=group,name=name,size=size) 161 for key in ['wall_ns','allocated_bytes']: 162 row[key]=statistics.median(s[key] for s in samples) 163 row[key+'_min']=min(s[key] for s in samples);row[key+'_max']=max(s[key] for s in samples) 164 report['summaries'].append(row);print(json.dumps(row),flush=True) 165 report['complete']=True 166 finally:(root/'report.json').write_text(json.dumps(report,indent=2)+'\n') 167 168 169 if __name__=='__main__':main()