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}