code wiki / _hdl_build / nx_f32_bricks_hw_gate.nx

nx_f32_bricks_hw_gate.nx source

↩ module page · 133 lines · 7620 B

1// nx_f32_bricks_hw_gate.nx -- DIFFERENTIAL gate for the hardware twins of three f32 tower bricks (nx_f32_gelu, 2// nx_f32_layernorm, nx_f32_softmax, 2026-09-14): each twin must return the IDENTICAL bits to the software oracle it 3// replaced (the _sw body kept verbatim in the same file) over a dense grid and over pseudo-random rows, so "IEEE 4// round-to-nearest on both sides" is measured, never assumed. Planted controls prove the comparator can fail: one 5// input nudged by one ulp must change the answer, and a perturbed gamma must change a LayerNorm row. Declared in 6// organ_gate.conf. expect_exit: 0 license_tier: ORIGINAL 7import "nx_gate_verdict.nx" 8import "nx_f32.nx" 9import "nx_f32_cvt.nx" 10import "nx_f32_div.nx" 11import "nx_f32_gelu.nx" 12import "nx_f32_layernorm.nx" 13import "nx_f32_softmax.nx" 14 15const BH_GRID: i64 = 4096 // grid points across the range below 16const BH_RANGE: i64 = 16 // gelu/erf grid spans -8..+8, the region where erf is not saturated 17const BH_HALF_RANGE: i64 = 8 18const BH_ROWS: i64 = 16 19const BH_DIM: i64 = 64 20const BH_NOISE_MUL: i64 = 1103515245 // LCG multiplier (Knuth/glibc), a published PRNG constant, not a tunable 21const BH_NOISE_INC: i64 = 12345 22const BH_NOISE_MASK: i64 = 0x7fffffff 23const BH_NOISE_SPAN: i64 = 1000 // pseudo-random values land in -4.000 .. +4.000 (thousandths) 24const BH_NOISE_CENTER: i64 = 4000 25const BH_MILLI: i64 = 1000 26const BH_EPS_MICRO: i64 = 1 // LayerNorm eps 1e-6 as micro units 27const BH_MICRO: i64 = 1000000 28 29static g_bh_seed: i64 30func bh_noise() -> i64 { g_bh_seed = (g_bh_seed * BH_NOISE_MUL + BH_NOISE_INC) & BH_NOISE_MASK; return g_bh_seed } 31// a pseudo-random f32 in -4..+4 built from thousandths 32func bh_rand_f32() -> i64 { 33 let m: i64 = (bh_noise() - (bh_noise() / (2 * BH_NOISE_CENTER)) * (2 * BH_NOISE_CENTER)) - BH_NOISE_CENTER 34 return nx_f32_div(nx_i32_to_f32(m), nx_i32_to_f32(BH_MILLI)) 35} 36 37func main() -> i64 { 38 gv_puts("=== nx_f32 bricks hardware-twin gate (gelu, layernorm, softmax vs their software oracles, bit for bit) ===\n" as *u8) 39 let ctr: *i64 = gv_ctr() 40 g_bh_seed = 1 41 // gelu + erf over the grid 42 var gelu_mism: i64 = 0 43 var erf_mism: i64 = 0 44 var gi: i64 = 0 45 let stepf: i64 = nx_f32_div(nx_i32_to_f32(BH_RANGE), nx_i32_to_f32(BH_GRID)) 46 while gi < BH_GRID { 47 let x: i64 = nx_f32_sub(nx_f32_mul(nx_i32_to_f32(gi), stepf), nx_i32_to_f32(BH_HALF_RANGE)) 48 if nx_f32_gelu(x) != nx_f32_gelu_sw(x) { gelu_mism = gelu_mism + 1 } 49 if nx_f32_erf(x) != nx_f32_erf_sw(x) { erf_mism = erf_mism + 1 } 50 gi = gi + 1 51 } 52 gv_check("T1 gelu hardware twin equals the software oracle bit for bit on the whole grid" as *u8, (gelu_mism == 0) as i64, ctr) 53 gv_check("T2 erf hardware twin equals the software oracle bit for bit on the whole grid" as *u8, (erf_mism == 0) as i64, ctr) 54 // neg-control: one ulp of input moves the answer, so the comparator can fail 55 let xc: i64 = nx_f32_div(nx_i32_to_f32(3), nx_i32_to_f32(4)) 56 gv_check("T3 neg-control-ulp: gelu of an input nudged by one ulp differs from the oracle at the original input" as *u8, (nx_f32_gelu(xc + 1) != nx_f32_gelu_sw(xc)) as i64, ctr) 57 // layernorm rows 58 let xm: *i64 = sys_mmap(BH_ROWS * BH_DIM * 8) as *i64 59 let gamma: *i64 = sys_mmap(BH_DIM * 8) as *i64 60 let beta: *i64 = sys_mmap(BH_DIM * 8) as *i64 61 let o1: *i64 = sys_mmap(BH_ROWS * BH_DIM * 8) as *i64 62 let o2: *i64 = sys_mmap(BH_ROWS * BH_DIM * 8) as *i64 63 var k: i64 = 0 64 while k < BH_ROWS * BH_DIM { xm[k] = bh_rand_f32(); k = k + 1 } 65 k = 0 66 while k < BH_DIM { gamma[k] = bh_rand_f32(); beta[k] = bh_rand_f32(); k = k + 1 } 67 let eps: i64 = nx_f32_div(nx_i32_to_f32(BH_EPS_MICRO), nx_i32_to_f32(BH_MICRO)) 68 nx_f32_layernorm(xm, gamma, beta, BH_ROWS, BH_DIM, eps, o1) 69 nx_f32_layernorm_sw(xm, gamma, beta, BH_ROWS, BH_DIM, eps, o2) 70 var ln_mism: i64 = 0 71 k = 0 72 while k < BH_ROWS * BH_DIM { if o1[k] != o2[k] { ln_mism = ln_mism + 1 } k = k + 1 } 73 gv_check("T4 layernorm hardware twin equals the software oracle bit for bit over every row" as *u8, (ln_mism == 0) as i64, ctr) 74 let g0: i64 = gamma[0] 75 gamma[0] = g0 + 1 76 nx_f32_layernorm(xm, gamma, beta, BH_ROWS, BH_DIM, eps, o1) 77 gamma[0] = g0 78 var ln_ctrl: i64 = 0 79 k = 0 80 while k < BH_ROWS * BH_DIM { if o1[k] != o2[k] { ln_ctrl = ln_ctrl + 1 } k = k + 1 } 81 // a one-ulp gamma nudge changes a row only when the product rounds to a different float (measured 10 of 16 rows), 82 // so the control asserts the comparator CAN fail, never that every row moves 83 gv_check("T5 neg-control-gamma: a one-ulp gamma perturbation changes at least one cell, so the comparison can fail" as *u8, (ln_ctrl > 0) as i64, ctr) 84 // softmax rows 85 let s1: *i64 = sys_mmap(BH_DIM * 8) as *i64 86 let s2: *i64 = sys_mmap(BH_DIM * 8) as *i64 87 var sm_mism: i64 = 0 88 var row: i64 = 0 89 while row < BH_ROWS { 90 nx_f32_softmax((xm as i64 + row * BH_DIM * 8) as *i64, BH_DIM, s1) 91 nx_f32_softmax_sw((xm as i64 + row * BH_DIM * 8) as *i64, BH_DIM, s2) 92 k = 0 93 while k < BH_DIM { if s1[k] != s2[k] { sm_mism = sm_mism + 1 } k = k + 1 } 94 row = row + 1 95 } 96 gv_check("T6 softmax hardware twin equals the software oracle bit for bit over every row" as *u8, (sm_mism == 0) as i64, ctr) 97 // primitive-level localisation: which software brick deviates from the IEEE hardware on the same operands 98 var mul_mism: i64 = 0 99 var add_mism: i64 = 0 100 var div_mism: i64 = 0 101 var first_x: i64 = 0 102 var first_hw: i64 = 0 103 var first_sw: i64 = 0 104 var seen: i64 = 0 105 gi = 0 106 while gi < BH_GRID { 107 let a: i64 = nx_f32_sub(nx_f32_mul(nx_i32_to_f32(gi), stepf), nx_i32_to_f32(BH_HALF_RANGE)) 108 let b: i64 = bh_rand_f32() 109 if nx_f32_mul(a, b) != __f32_mul(a, b) { mul_mism = mul_mism + 1 } 110 if nx_f32_add(a, b) != __f32_add(a, b) { add_mism = add_mism + 1 } 111 if b != 0 { if nx_f32_div(a, b) != __f32_div(a, b) { div_mism = div_mism + 1 } } 112 if seen == 0 { let hg: i64 = nx_f32_gelu(a); let sg: i64 = nx_f32_gelu_sw(a); if hg != sg { first_x = a; first_hw = hg; first_sw = sg; seen = 1 } } 113 gi = gi + 1 114 } 115 gv_check("T7 primitive: software nx_f32_mul equals the hardware multiply bit for bit on the grid pairs" as *u8, (mul_mism == 0) as i64, ctr) 116 gv_check("T8 primitive: software nx_f32_add equals the hardware add bit for bit on the grid pairs" as *u8, (add_mism == 0) as i64, ctr) 117 gv_check("T9 primitive: software nx_f32_div equals the hardware divide bit for bit on the grid pairs" as *u8, (div_mism == 0) as i64, ctr) 118 gv_values_head() 119 gv_kv("mul_mismatches" as *u8, mul_mism) 120 gv_kv("add_mismatches" as *u8, add_mism) 121 gv_kv("div_mismatches" as *u8, div_mism) 122 gv_kv("first_gelu_mismatch_x_bits" as *u8, first_x) 123 gv_kv("first_gelu_mismatch_hw_bits" as *u8, first_hw) 124 gv_kv("first_gelu_mismatch_sw_bits" as *u8, first_sw) 125 gv_kv("grid_points" as *u8, BH_GRID) 126 gv_kv("gelu_mismatches" as *u8, gelu_mism) 127 gv_kv("erf_mismatches" as *u8, erf_mism) 128 gv_kv("layernorm_cells" as *u8, BH_ROWS * BH_DIM) 129 gv_kv("layernorm_mismatches" as *u8, ln_mism) 130 gv_kv("layernorm_control_changed_cells" as *u8, ln_ctrl) 131 gv_kv("softmax_mismatches" as *u8, sm_mism) 132 return gv_verdict("NX-F32-BRICKS-HW-GATE" as *u8, ctr, "the hardware twins of gelu, erf, layernorm and softmax return the identical bits to their software oracles on a dense grid and on pseudo-random rows, and planted one-ulp perturbations prove the comparison can fail" as *u8) 133}