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}