float_pipelines.py (10679B)
1 #!/usr/bin/env python3 2 """Blocked significands and bounded exact arithmetic followed by 53-bit RNE.""" 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_significands import rounded, shifted, validation_cases 15 16 ROOT = Path(__file__).resolve().parent.parent 17 18 19 def sha(path): 20 return hashlib.sha256(path.read_bytes()).hexdigest() 21 22 23 def limbs(n): 24 out = [] 25 while n: 26 out.append(nat(n & 255)) 27 n >>= 8 28 return tree_list(out) 29 30 31 def finish(n): 32 cut = max(0,n.bit_length()-53) 33 m,flag = rounded(n,cut) 34 if m.bit_length() > 53: 35 m >>= 1 36 cut += 1 37 return cut,m,flag 38 39 40 def operation(name,a,b,gap=0): 41 if name == 'Chain': 42 total,flag = 0,False 43 for _ in range(3): 44 cut,a,f = finish(a*b) 45 total += cut 46 flag |= f 47 return total,a,flag 48 if name in ['Add','AlignedAdd']: 49 n = (a << gap)+b 50 elif name in ['Subtract','AlignedSubtract']: 51 n = (a << gap)-b 52 assert n >= 0 53 else: 54 n = a*b 55 return finish(n) 56 57 58 def encoded_result(result, encode): 59 cut,m,flag = result 60 return pair(nat(cut),pair(encode(m),TRUE if flag else LEAF)) 61 62 63 def job(rows,layout,op): 64 encode = nat if layout == 'lsb' else limbs 65 raw = [] 66 wanted = [] 67 for a,b,gap in rows: 68 argument = pair(encode(a),encode(b)) 69 if op.startswith('Aligned'): 70 argument = pair(nat(gap),argument) 71 raw.append(argument) 72 wanted.append(encoded_result(operation(op,a,b,gap),encode)) 73 return tree_list(raw),tree_list(wanted) 74 75 76 def main(): 77 p = argparse.ArgumentParser(description=__doc__) 78 p.add_argument('tricu',type=Path) 79 p.add_argument('--directory',required=True,type=Path) 80 p.add_argument('--size',type=int,default=16) 81 p.add_argument('--repeats',type=int,default=5) 82 p.add_argument('--seconds',type=int,default=1200) 83 args = p.parse_args() 84 if not 4 <= args.size <= 64 or not 3 <= args.repeats <= 10: 85 p.error('size 4..64; repeats 3..10') 86 root = args.directory.resolve() 87 root.mkdir(parents=True,exist_ok=False) 88 workspace = root/'workspace' 89 workspace.mkdir() 90 sources = {'driver.tri':ROOT/'bench/float-pipelines.tri', 91 'kernels.tri':ROOT/'bench/float-significands.tri', 92 'layouts.tri':ROOT/'bench/float-layouts.tri', 93 'native_math.tri':ROOT/'lib/math/native.tri'} 94 for name,path in sources.items(): 95 (workspace/name).write_bytes(path.read_bytes()) 96 runner = ProbeRunner(args.tricu,root,args.seconds,memory_mib=2048) 97 report = dict(complete=False,scope='fresh-process-invoke-through-full-force', 98 runtime_sha256=sha(args.tricu),harness_sha256=sha(Path(__file__)), 99 oracle_dependency_sha256=sha(ROOT/'test/float_significands.py'), 100 compiler_sha256=sha(ROOT/'bootstrap/build-stable.arboricx'),platform=platform.platform(), 101 sources={name:sha(path) for name,path in sources.items()},validation=[],samples=[],summaries=[]) 102 try: 103 tool = runner.imported(ROOT/'bootstrap/build-stable.arboricx','compiler') 104 bundle = root/'pipelines.arboricx' 105 runner.run('compile',['compile','--file',workspace/'driver.tri','--toolchain',tool, 106 '--tool-store',runner.store,'--store',runner.store,'--output',bundle,'--compile-timeout','240'],270) 107 pin = runner.imported(bundle,'pipelines') 108 report['bundle_sha256'] = sha(bundle) 109 110 def run(label,name,raw,wanted,measure=False): 111 path = root/(label+'.input.tree') 112 path.write_bytes(raw) 113 actual = runner.invoke(label,pin,name,['--arg-tree-file',path]+ 114 (['--profile-full-result'] if measure else [])) 115 expect(actual,wanted,label) 116 if not measure: 117 report['validation'].append(dict(label=label,input_sha256=hashlib.sha256(raw).hexdigest(), 118 output_sha256=hashlib.sha256(actual).hexdigest())) 119 return 120 log = (root/(label+'.log')).read_text() 121 points = {v['profile']:v for line in log.splitlines() if line.startswith('{') 122 for v in [json.loads(line)]} 123 before,after = points['invoke-begin-1'],points['force-end-1'] 124 return dict(wall_ns=after['wall_ns']-before['wall_ns'], 125 allocated_bytes=after['allocated_bytes']-before['allocated_bytes']) 126 127 cases = validation_cases() 128 report['shift_round_cases'] = len(cases) 129 for offset in range(0,len(cases),96): 130 rows = cases[offset:offset+96] 131 raw = tree_list([pair(nat(k),limbs(n)) for n,k in rows]) 132 for op,oracle in [('Shift',shifted),('Round',rounded)]: 133 wanted = tree_list([pair(limbs(q),TRUE if f else LEAF) 134 for q,f in [oracle(n,k) for n,k in rows]]) 135 run(f'check-limb{op}-{offset}','limb'+op,raw,wanted) 136 rng = random.Random(25653) 137 special = [0,1,2,127,128,254,255,256,257,65535,65536,(1 << 52)-1, 138 1 << 52,(1 << 53)-1,(1 << 53)-2] 139 pairs = [(max(a,b),min(a,b),0) for a in special for b in special] 140 pairs += [(max(a,b),min(a,b),0) for a,b in 141 [(rng.getrandbits(53),rng.getrandbits(53)) for _ in range(32)]] 142 report['arithmetic_cases'] = len(pairs) 143 for op in ['Add','Subtract','Multiply','Chain']: 144 for layout in ['lsb','limb']: 145 for offset in range(0,len(pairs),24): 146 raw,wanted = job(pairs[offset:offset+24],layout,op) 147 run(f'check-{layout}{op}-{offset}',layout+op,raw,wanted) 148 aligned = [(a,b,k) for a,b,_ in pairs[::8] for k in [0,1,7,8,52,53,54]] 149 aligned += [((1 << 53)-1,1,1),((1 << 53)-1,0,1),((1 << 53)-1,2,1)] 150 report['alignment_cases'] = len(aligned) 151 for op in ['AlignedAdd','AlignedSubtract']: 152 for layout in ['lsb','limb']: 153 for offset in range(0,len(aligned),24): 154 raw,wanted = job(aligned[offset:offset+24],layout,op) 155 run(f'check-{layout}{op}-{offset}',layout+op,raw,wanted) 156 # Exercise the final carry-renormalization path directly, including 157 # below/tie/above cases near the next power of two. 158 boundaries = [0,1] + [n for w in [54,55,105,106] 159 for n in [(1 << w)-1,(1 << w)-(1 << (w-54)), 160 (1 << w)-(1 << (w-54))-1,1 << w]] 161 for i,n in enumerate(boundaries): 162 for name,encode in [('finishL',nat),('finishB',limbs)]: 163 run(f'check-finish-{name}-{i}',name,encode(n),encoded_result(finish(n),encode)) 164 report['finish_boundary_cases'] = len(boundaries) 165 values = special+[rng.getrandbits(106) for _ in range(16)] 166 run('check-convert-in','naturalToLimb',nat(values),tree_list([limbs(v) for v in values])) 167 run('check-convert-out','limbToNatural',tree_list([limbs(v) for v in values]),nat(values)) 168 run('check-widths','limbWidths',tree_list([limbs(v) for v in values]),nat([v.bit_length() for v in values])) 169 170 groups = {} 171 groups['random'] = [(a,b,0) for a,b in [sorted([rng.getrandbits(52)+(1 << 52), 172 rng.getrandbits(52)+(1 << 52)],reverse=True) 173 for _ in range(args.size)]] 174 groups['carry'] = [((1 << 53)-1,(1 << 53)-1-(i%3),0) for i in range(args.size)] 175 groups['cancel'] = [(a,a-(i%4),0) for i,(a,_,_) in enumerate(groups['random'])] 176 schedule = [] 177 for group,rows in groups.items(): 178 for op in ['Add','Subtract','Multiply','Chain']: 179 for layout in ['lsb','limb']: 180 raw,wanted = job(rows,layout,op) 181 schedule.append((group,layout+op,raw,wanted)) 182 for group,gaps in [('near',[0,1,2,7]),('wide',[8,52,53,54])]: 183 rows = [(a,b,gaps[i%len(gaps)]) for i,(a,b,_) in enumerate(groups['random'])] 184 for op in ['AlignedAdd','AlignedSubtract']: 185 for layout in ['lsb','limb']: 186 raw,wanted = job(rows,layout,op) 187 schedule.append((group,layout+op,raw,wanted)) 188 for width in [53,106]: 189 values = [rng.getrandbits(width-1)+(1 << (width-1)) for _ in range(args.size)] 190 for group,counts in [('small',[1,2,3,7]),('byte',[8,16,24,32]), 191 ('boundary',[width-1,width,width+1]),('far',[2046,1 << 128])]: 192 rows = [(n,counts[i%len(counts)]) for i,n in enumerate(values)] 193 for layout,encode in [('lsb',nat),('limb',limbs)]: 194 raw = tree_list([pair(nat(k),encode(n)) for n,k in rows]) 195 wanted = tree_list([pair(encode(q),TRUE if f else LEAF) for q,f in [rounded(n,k) for n,k in rows]]) 196 name = layout+'Round' 197 schedule.append((f'{width}-{group}',name,raw,wanted)) 198 schedule += [(str(width),'naturalToLimb',nat(values),tree_list([limbs(v) for v in values])), 199 (str(width),'limbToNatural',tree_list([limbs(v) for v in values]),nat(values))] 200 rng.shuffle(schedule) 201 for repeat in range(args.repeats): 202 rng.shuffle(schedule) 203 for group,name,raw,wanted in schedule: 204 sample = run(f'{group}-{name}-{repeat}',name,raw,wanted,True) 205 report['samples'].append(dict(group=group,name=name,size=args.size,repeat=repeat, 206 warmup=repeat==0,**sample)) 207 for group,name,_,_ in sorted(schedule): 208 samples = [v for v in report['samples'] if v['group']==group and v['name']==name and not v['warmup']] 209 row = dict(group=group,name=name,size=args.size) 210 for key in ['wall_ns','allocated_bytes']: 211 row[key] = statistics.median(v[key] for v in samples) 212 row[key+'_min'] = min(v[key] for v in samples) 213 row[key+'_max'] = max(v[key] for v in samples) 214 report['summaries'].append(row) 215 print(json.dumps(row),flush=True) 216 report['complete'] = True 217 finally: 218 (root/'report.json').write_text(json.dumps(report,indent=2)+'\n') 219 220 221 if __name__ == '__main__': 222 main()