code wiki / (root) / nx_conv2d_mt_gate.nx

nx_conv2d_mt_gate.nx source

↩ module page · 266 lines · 8620 B

1// nx_conv2d_mt_gate.nx -- adversarial gate for the multi-threaded 2// conv2d forward (nx_conv2d_forward_mt / nx_conv2d_forward_pool) 3// against the serial oracle nx_conv2d_forward. 4// 5// The MT path is an INDEPENDENT implementation of the same math 6// (organ policy: the serial path was left untouched), so full-buffer 7// bit-exact comparison here is a real two-implementation oracle, not 8// a self-comparison. 9// 10// Checks (10): 11// 1 serial oracle runs OK on the small shape 12// 2..6 MT bit-exact vs serial for nworkers in {1, 2, 3, 0=auto, 13// 1000=clamped-past-row-count}; output poisoned before each 14// run so unwritten rows cannot pass 15// 7 no-bias variant bit-exact (nworkers=3) 16// 8..9 caller-owned pool reused across two calls (second call has 17// different input contents) -- proves the completed-counter 18// DELTA wait, both bit-exact 19// 10 speedup on a big shape (1x64x128x128, C_out=64), auto 20// workers: bit-exact AND wall-time speedup >= floor 21// 22// Small shape uses prime-ish dims (H=17, W=13, C_out=5) so any 23// stride or band-boundary bug lands off-pattern and fails the 24// compare. 25// 26// genealogy_id: substrate_conv2d_v1_direct + nx_conv_speedup_banding 27// lineage_id: conv2d_mt_gate_v1 28 29import "nx_conv2d.nx" 30import "nx_fmt.nx" 31 32// Small asymmetric shape. 33const GT_N: i64 = 2 34const GT_CIN: i64 = 3 35const GT_COUT: i64 = 5 36const GT_H: i64 = 17 37const GT_W: i64 = 13 38 39// Big compute-bound shape for the speedup measure. 40const GB_CIN: i64 = 64 41const GB_COUT: i64 = 64 42const GB_H: i64 = 128 43const GB_W: i64 = 128 44 45// Speedup floor, x100 (200 = 2.0x). Conservative on purpose: the 46// build host has 16 hardware workers and the same banding pattern 47// measured 6.76x in nx_conv_speedup, but the gate must not flake 48// under a loaded machine. The measured number is printed above the 49// verdict for the honest record. 50const GATE_SPEEDUP_FLOOR_X100: i64 = 200 51 52func g_lcg(s: i64) -> i64 { 53 var x: i64 = s * 1103515245 + 12345 54 x = x & 2147483647 55 return x 56} 57 58// Deterministic fill in [-1024, 1023] (Q10-scale magnitudes keep the 59// i64 accumulator far from overflow: 64ch * 9taps * 1024 * 1024 ~ 6e8). 60func g_fill(p: *i64, count: i64, seed: i64) -> i64 { 61 var s: i64 = seed 62 var i: i64 = 0 63 while i < count { 64 s = g_lcg(s) 65 p[i] = (s % 2048) - 1024 66 i = i + 1 67 } 68 return 0 69} 70 71// Smaller magnitudes for bias vectors. 72func g_fill_small(p: *i64, count: i64, seed: i64) -> i64 { 73 var s: i64 = seed 74 var i: i64 = 0 75 while i < count { 76 s = g_lcg(s) 77 p[i] = (s % 512) - 256 78 i = i + 1 79 } 80 return 0 81} 82 83// Poison an output buffer so a band the MT path failed to write can 84// never compare equal. 85func g_poison(p: *i64, count: i64) -> i64 { 86 let pv: i64 = 0 - 777777 87 var i: i64 = 0 88 while i < count { 89 p[i] = pv 90 i = i + 1 91 } 92 return 0 93} 94 95func g_same(a: *i64, b: *i64, count: i64) -> i64 { 96 var i: i64 = 0 97 while i < count { 98 if a[i] != b[i] { return 0 } 99 i = i + 1 100 } 101 return 1 102} 103 104func g_t4(d0: i64, d1: i64, d2: i64, d3: i64, err: *nx_int) -> *NxTensor { 105 let sh: *nx_int = sys_mmap(4 * 8) as *nx_int 106 sh[0] = d0; sh[1] = d1; sh[2] = d2; sh[3] = d3 107 return nx_t_alloc(NX_DT_I64, sh, 4, err) 108} 109 110func g_nl() -> i64 { 111 fmt_puts("\n" as *u8) 112 return 0 113} 114 115func main() -> i64 { 116 let err: *nx_int = sys_mmap(8) as *nx_int 117 err[0] = 0 118 119 // ---- small-shape tensors ---- 120 let tin: *NxTensor = g_t4(GT_N, GT_CIN, GT_H, GT_W, err) 121 let twt: *NxTensor = g_t4(GT_COUT, GT_CIN, 3, 3, err) 122 let tref: *NxTensor = g_t4(GT_N, GT_COUT, GT_H, GT_W, err) 123 let tout: *NxTensor = g_t4(GT_N, GT_COUT, GT_H, GT_W, err) 124 if err[0] != 0 { return 9 } 125 126 let in_n: i64 = GT_N * GT_CIN * GT_H * GT_W 127 let wt_n: i64 = GT_COUT * GT_CIN * 9 128 let out_n: i64 = GT_N * GT_COUT * GT_H * GT_W 129 130 let pin: *i64 = tin.storage as *i64 131 let pwt: *i64 = twt.storage as *i64 132 let pref: *i64 = tref.storage as *i64 133 let pout: *i64 = tout.storage as *i64 134 135 g_fill(pin, in_n, 20260707) 136 g_fill(pwt, wt_n, 977) 137 let bias: *i64 = sys_mmap(GT_COUT * 8) as *i64 138 g_fill_small(bias, GT_COUT, 4242) 139 140 var pass: i64 = 0 141 142 // ---- 1: serial oracle ---- 143 let v1: nx_int = nx_conv2d_forward(tin, twt, bias, tref) 144 if v1 != NX_CV2_OK { 145 fmt_puts("CV2MT 1 SERIAL FAIL v="); fmt_putn(v1); g_nl() 146 return 11 147 } 148 fmt_puts("CV2MT 1 SERIAL OK"); g_nl() 149 pass = pass + 1 150 151 // ---- 2..6: MT exactness across worker counts ---- 152 let counts: *i64 = sys_mmap(5 * 8) as *i64 153 counts[0] = 1; counts[1] = 2; counts[2] = 3; counts[3] = 0; counts[4] = 1000 154 var k: i64 = 0 155 while k < 5 { 156 g_poison(pout, out_n) 157 let nwk: i64 = counts[k] 158 let vk: nx_int = nx_conv2d_forward_mt(tin, twt, bias, tout, nwk) 159 var ok: i64 = 0 160 if vk == NX_CV2_OK { ok = g_same(pout, pref, out_n) } 161 if ok != 1 { 162 fmt_puts("CV2MT EXACT FAIL nw="); fmt_putn(nwk); g_nl() 163 return 12 + k 164 } 165 fmt_puts("CV2MT EXACT OK nw="); fmt_putn(nwk); g_nl() 166 pass = pass + 1 167 k = k + 1 168 } 169 170 // ---- 7: no-bias variant ---- 171 let v7s: nx_int = nx_conv2d_forward(tin, twt, 0 as *i64, tref) 172 g_poison(pout, out_n) 173 let v7m: nx_int = nx_conv2d_forward_mt(tin, twt, 0 as *i64, tout, 3) 174 var ok7: i64 = 0 175 if v7s == NX_CV2_OK { if v7m == NX_CV2_OK { ok7 = g_same(pout, pref, out_n) } } 176 if ok7 != 1 { 177 fmt_puts("CV2MT 7 NOBIAS FAIL"); g_nl() 178 return 17 179 } 180 fmt_puts("CV2MT 7 NOBIAS OK"); g_nl() 181 pass = pass + 1 182 183 // ---- 8..9: caller-owned pool reused across two calls ---- 184 let v8s: nx_int = nx_conv2d_forward(tin, twt, bias, tref) 185 if v8s != NX_CV2_OK { return 18 } 186 let pool: *NxThreadPool = nx_pool_new(4, 0) 187 g_poison(pout, out_n) 188 let v8: nx_int = nx_conv2d_forward_pool(pool, tin, twt, bias, tout) 189 var ok8: i64 = 0 190 if v8 == NX_CV2_OK { ok8 = g_same(pout, pref, out_n) } 191 if ok8 != 1 { 192 fmt_puts("CV2MT 8 POOL1 FAIL"); g_nl() 193 return 18 194 } 195 fmt_puts("CV2MT 8 POOL1 OK"); g_nl() 196 pass = pass + 1 197 198 g_fill(pin, in_n, 555001) 199 let v9s: nx_int = nx_conv2d_forward(tin, twt, bias, tref) 200 if v9s != NX_CV2_OK { return 19 } 201 g_poison(pout, out_n) 202 let v9: nx_int = nx_conv2d_forward_pool(pool, tin, twt, bias, tout) 203 var ok9: i64 = 0 204 if v9 == NX_CV2_OK { ok9 = g_same(pout, pref, out_n) } 205 nx_pool_shutdown(pool) 206 if ok9 != 1 { 207 fmt_puts("CV2MT 9 POOL2 FAIL"); g_nl() 208 return 19 209 } 210 fmt_puts("CV2MT 9 POOL2 OK"); g_nl() 211 pass = pass + 1 212 213 // ---- 10: big-shape speedup, auto workers ---- 214 let bin: *NxTensor = g_t4(1, GB_CIN, GB_H, GB_W, err) 215 let bwt: *NxTensor = g_t4(GB_COUT, GB_CIN, 3, 3, err) 216 let bref: *NxTensor = g_t4(1, GB_COUT, GB_H, GB_W, err) 217 let bout: *NxTensor = g_t4(1, GB_COUT, GB_H, GB_W, err) 218 if err[0] != 0 { return 20 } 219 220 let bin_n: i64 = GB_CIN * GB_H * GB_W 221 let bwt_n: i64 = GB_COUT * GB_CIN * 9 222 let bout_n: i64 = GB_COUT * GB_H * GB_W 223 224 let bpi: *i64 = bin.storage as *i64 225 let bpw: *i64 = bwt.storage as *i64 226 let bpr: *i64 = bref.storage as *i64 227 let bpo: *i64 = bout.storage as *i64 228 229 g_fill(bpi, bin_n, 6767) 230 g_fill(bpw, bwt_n, 8181) 231 let bbias: *i64 = sys_mmap(GB_COUT * 8) as *i64 232 g_fill_small(bbias, GB_COUT, 31337) 233 234 let t0: i64 = sys_now_us() 235 let vbs: nx_int = nx_conv2d_forward(bin, bwt, bbias, bref) 236 let serial_us: i64 = sys_now_us() - t0 237 if vbs != NX_CV2_OK { return 21 } 238 239 g_poison(bpo, bout_n) 240 let t1: i64 = sys_now_us() 241 let vbm: nx_int = nx_conv2d_forward_mt(bin, bwt, bbias, bout, 0) 242 let mt_us: i64 = sys_now_us() - t1 243 if vbm != NX_CV2_OK { return 22 } 244 let okb: i64 = g_same(bpo, bpr, bout_n) 245 if okb != 1 { 246 fmt_puts("CV2MT 10 BIG-EXACT FAIL"); g_nl() 247 return 23 248 } 249 250 let hww: i64 = nx_hw_worker_count() 251 fmt_puts("hw_workers="); fmt_putn(hww); g_nl() 252 fmt_puts("serial_us="); fmt_putn(serial_us); g_nl() 253 fmt_puts("mt_us="); fmt_putn(mt_us); g_nl() 254 var sx100: i64 = 0 255 if mt_us > 0 { sx100 = serial_us * 100 / mt_us } 256 fmt_puts("speedup_x100="); fmt_putn(sx100); g_nl() 257 if sx100 < GATE_SPEEDUP_FLOOR_X100 { 258 fmt_puts("CV2MT 10 SPEEDUP FAIL"); g_nl() 259 return 24 260 } 261 fmt_puts("CV2MT 10 SPEEDUP OK"); g_nl() 262 pass = pass + 1 263 264 fmt_puts("CONV2D_MT_GATE "); fmt_putn(pass); fmt_puts("/10 GREEN"); g_nl() 265 return 0 266}