code wiki / _hdl_build / nx_gpu_q4k_gate.nx
nx_gpu_q4k_gate.nx source
↩ module page · 341 lines · 16543 B
1// nx_gpu_q4k_gate.nx -- proves a SOVEREIGN organ can drive the GPU.
2//
3// WHY THIS EXISTS (2026-08-01): Q4_K matvec was measured correct on the RTX 5080 at 362 GB/s
4// DRAM-resident (=> ~42 tok/s for a 14B, against 1.06 tok/s for today's 0.5B CPU seat). But NishiLang
5// emits STATIC ELF with syscalls only -- it has no dlopen, so it cannot link libcuda. The GPU therefore
6// lives behind nx_gpu_q4kd (a small C daemon holding weights resident in VRAM) and we reach it over
7// TCP, which this language already speaks. This gate is the proof that the sovereign side can actually
8// drive it -- not that the daemon works in isolation.
9//
10// ★ THE COMPARISON IS BIT-EXACT, ON PURPOSE. GPU and CPU reduce in different orders, so a float
11// comparison would normally need a tolerance -- and a tolerance is a place for a wrong answer to
12// hide. Instead the test data is chosen so EVERY product is a small integer: d=1.0, dmin=0, all
13// sub-block scales=1, x integral. Integer-valued f32 addition is exact in ANY order, so the GPU
14// result must equal the expected value BIT FOR BIT. No tolerance, nothing to tune.
15//
16// EXIT CONTRACT: 0 = all teeth pass. 1 = a tooth failed. 2 = could not reach the daemon (refuses to
17// report GREEN when it never measured -- absence of findings and absence of measurement must never
18// render identically).
19// nx_gpu_q4k_gate [port] default 18140
20// expect_exit: 0 license_tier: ORIGINAL
21import "nx_syscalls.nx"
22import "nx_gate_verdict.nx"
23
24const GQ_PORT: i64 = 18140
25const GQ_N: i64 = 8 // rows; must be a multiple of 8 (one warp per row, 8 warps/block)
26const GQ_K: i64 = 256 // one Q4_K super-block per row
27const GQ_BPB: i64 = 144 // bytes per super-block -- from runtime/nx_dequant_iter.nx
28const GQ_OP_PING: i64 = 0
29const GQ_OP_REGISTER: i64 = 1
30const GQ_OP_MATVEC: i64 = 2
31
32func gq_w(s: *u8) -> i64 { var n: i64 = 0; while s[n] != (0 as u8) { n = n + 1 } sys_write(1, s, n); return 0 }
33func gq_n(v: i64) -> i64 {
34 if v == 0 { sys_write(1, "0" as *u8, 1); return 0 }
35 var m: i64 = v
36 if m < 0 { sys_write(1, "-" as *u8, 1); m = 0 - m }
37 let t: *u8 = sys_mmap(32); var k: i64 = 0
38 while m > 0 { t[k] = (48 + (m % 10)) as u8; m = m / 10; k = k + 1 }
39 while k > 0 { k = k - 1; sys_write(1, (((t as i64) + k) as *u8), 1) }
40 return 0
41}
42
43// little-endian scalar writers/readers over a byte buffer
44func gq_put32(b: *u8, off: i64, v: i64) -> i64 {
45 b[off] = (v & 255) as u8
46 b[off + 1] = ((v / 256) & 255) as u8
47 b[off + 2] = ((v / 65536) & 255) as u8
48 b[off + 3] = ((v / 16777216) & 255) as u8
49 return 0
50}
51func gq_get32(b: *u8, off: i64) -> i64 {
52 return (b[off] as i64) + (b[off + 1] as i64) * 256 + (b[off + 2] as i64) * 65536 + (b[off + 3] as i64) * 16777216
53}
54
55// IEEE-754 binary32 bit pattern of a small positive integer (exact for v < 2^24).
56// Used to build x and to predict y without ever leaving integer arithmetic.
57func gq_i2f(v: i64) -> i64 {
58 if v == 0 { return 0 }
59 var p: i64 = 0
60 var t: i64 = v
61 while t > 1 { t = t / 2; p = p + 1 }
62 var man: i64 = 0
63 if p <= 23 { man = (v * (1 << (23 - p))) & 8388607 } else { man = (v / (1 << (p - 23))) & 8388607 }
64 return ((127 + p) * 8388608) + man
65}
66
67// read exactly n bytes; short reads on a socket are NORMAL, not an error
68func gq_readn(fd: i64, buf: *u8, n: i64) -> i64 {
69 var got: i64 = 0
70 var go: i64 = 1
71 while go == 1 {
72 if got >= n { go = 0 } else {
73 let r: i64 = sys_read(fd, (((buf as i64) + got) as *u8), n - got)
74 if r <= 0 { go = 0 } else { got = got + r }
75 }
76 }
77 if got == n { return 0 }
78 return 0 - 1
79}
80func gq_writen(fd: i64, buf: *u8, n: i64) -> i64 {
81 var sent: i64 = 0
82 var go: i64 = 1
83 while go == 1 {
84 if sent >= n { go = 0 } else {
85 let r: i64 = sys_write(fd, (((buf as i64) + sent) as *u8), n - sent)
86 if r <= 0 { go = 0 } else { sent = sent + r }
87 }
88 }
89 if sent == n { return 0 }
90 return 0 - 1
91}
92
93func gq_connect(port: i64) -> i64 {
94 let fd: i64 = sys_socket(AF_INET, SOCK_STREAM, 0)
95 if fd < 0 { return 0 - 1 }
96 let sa: *u8 = sys_mmap(32)
97 var i: i64 = 0
98 while i < 16 { sa[i] = 0 as u8; i = i + 1 }
99 sa[0] = 2 as u8 // AF_INET, little-endian u16
100 sa[1] = 0 as u8
101 sa[2] = ((port / 256) & 255) as u8 // sin_port is BIG-endian
102 sa[3] = (port & 255) as u8
103 sa[4] = 127 as u8 // 127.0.0.1
104 sa[5] = 0 as u8
105 sa[6] = 0 as u8
106 sa[7] = 1 as u8
107 if sys_connect(fd, sa, 16) < 0 { sys_close(fd); return 0 - 1 }
108 return fd
109}
110
111// Set the 4-bit quant for element e (0..255) inside a 128-byte qs region.
112// Layout from nx_dequant_iter.nx: each 32-byte chunk yields 64 values -- low nibbles first, then high.
113func gq_set_q(qs: *u8, e: i64, q: i64) -> i64 {
114 let c: i64 = e / 64
115 let w: i64 = e % 64
116 if w < 32 {
117 let bi: i64 = c * 32 + w
118 qs[bi] = ((qs[bi] as i64 & 240) + (q & 15)) as u8
119 } else {
120 let bi: i64 = c * 32 + (w - 32)
121 qs[bi] = ((qs[bi] as i64 & 15) + ((q & 15) * 16)) as u8
122 }
123 return 0
124}
125
126func main(argc: i64, argv: *i64) -> i64 {
127 var port: i64 = GQ_PORT
128 if argc >= 2 {
129 let s: *u8 = argv[1] as *u8
130 var v: i64 = 0; var i: i64 = 0; var ok: i64 = 1
131 while s[i] != (0 as u8) {
132 let c: i64 = s[i] as i64
133 if c < 48 { ok = 0 } else { if c > 57 { ok = 0 } else { v = v * 10 + (c - 48) } }
134 i = i + 1
135 }
136 if ok == 1 { if v > 0 { port = v } }
137 }
138
139 gq_w("=== NX-GPU-Q4K GATE: a sovereign organ driving the GPU over TCP ===\n" as *u8)
140 var pass: i64 = 0
141 var fail: i64 = 0
142
143 let fd: i64 = gq_connect(port)
144 if fd < 0 {
145 gq_w(" CANNOT-MEASURE: no daemon on 127.0.0.1:" as *u8); gq_n(port)
146 gq_w("\n start it: NX_Q4K_PTX=<path>/q4k.ptx nx_gpu_q4kd\n" as *u8)
147 gq_w("NX-GPU-Q4K-GATE verdict=CANNOT-MEASURE (refusing to report GREEN without measuring)\n" as *u8)
148 sys_exit(2); return 2
149 }
150
151 // ---- T1: PING ----
152 let req: *u8 = sys_mmap(65536)
153 let rsp: *u8 = sys_mmap(65536)
154 gq_put32(req, 0, GQ_OP_PING)
155 if gq_writen(fd, req, 4) == 0 {
156 if gq_readn(fd, rsp, 8) == 0 {
157 let st: i64 = gq_get32(rsp, 0)
158 let ver: i64 = gq_get32(rsp, 4)
159 if st == 0 {
160 gq_w(" PASS T1 PING -> protocol version " as *u8); gq_n(ver); gq_w("\n" as *u8); pass = pass + 1
161 } else { gq_w(" FAIL T1 PING status " as *u8); gq_n(st); gq_w("\n" as *u8); fail = fail + 1 }
162 } else { gq_w(" FAIL T1 PING short read\n" as *u8); fail = fail + 1 }
163 } else { gq_w(" FAIL T1 PING write\n" as *u8); fail = fail + 1 }
164
165 // ---- build an INTEGER-EXACT Q4_K matrix ----
166 // d = 1.0 (f16 0x3C00), dmin = 0, every sub-block scale = 1 and min = 0.
167 // The 12 packed scale bytes that yield sc=1,m=0 for all 8 sub-blocks are [1,1,1,1,0,0,0,0,1,1,1,1]
168 // under the 6-bit unpack in nx_dequant_iter.nx -- derived, not guessed.
169 let wbytes: i64 = GQ_N * GQ_BPB
170 var r: i64 = 0
171 while r < GQ_N {
172 let blk: i64 = r * GQ_BPB
173 var z: i64 = 0
174 while z < GQ_BPB { req[16 + blk + z] = 0 as u8; z = z + 1 }
175 req[16 + blk + 0] = 0 as u8 // d = f16 1.0 = 0x3C00
176 req[16 + blk + 1] = 60 as u8
177 req[16 + blk + 2] = 0 as u8 // dmin = 0
178 req[16 + blk + 3] = 0 as u8
179 var j: i64 = 0
180 while j < 4 { req[16 + blk + 4 + j] = 1 as u8; j = j + 1 }
181 j = 4
182 while j < 8 { req[16 + blk + 4 + j] = 0 as u8; j = j + 1 }
183 j = 8
184 while j < 12 { req[16 + blk + 4 + j] = 1 as u8; j = j + 1 }
185 var e: i64 = 0
186 while e < GQ_K {
187 gq_set_q((((req as i64) + 16 + blk + 16) as *u8), e, (e + r) % 16)
188 e = e + 1
189 }
190 r = r + 1
191 }
192 // REGISTER header is [op u32][N u32][K u32][nbytes u64] = 20 bytes, so the payload starts at 20.
193 // Weights were staged in `req` at +16; copy them in behind a correctly-sized header.
194 let frame: *u8 = sys_mmap(65536)
195 gq_put32(frame, 0, GQ_OP_REGISTER)
196 gq_put32(frame, 4, GQ_N)
197 gq_put32(frame, 8, GQ_K)
198 gq_put32(frame, 12, wbytes)
199 gq_put32(frame, 16, 0) // high 32 bits of the u64 length
200 var cp: i64 = 0
201 while cp < wbytes { frame[20 + cp] = req[16 + cp]; cp = cp + 1 }
202
203 var handle: i64 = 0 - 1
204 if gq_writen(fd, frame, 20 + wbytes) == 0 {
205 if gq_readn(fd, rsp, 8) == 0 {
206 let st: i64 = gq_get32(rsp, 0)
207 if st == 0 {
208 handle = gq_get32(rsp, 4)
209 gq_w(" PASS T2 REGISTER " as *u8); gq_n(wbytes)
210 gq_w(" B resident in VRAM, handle=" as *u8); gq_n(handle); gq_w("\n" as *u8); pass = pass + 1
211 } else { gq_w(" FAIL T2 REGISTER status " as *u8); gq_n(st); gq_w("\n" as *u8); fail = fail + 1 }
212 } else { gq_w(" FAIL T2 REGISTER short read\n" as *u8); fail = fail + 1 }
213 } else { gq_w(" FAIL T2 REGISTER write\n" as *u8); fail = fail + 1 }
214
215 // ---- T3: MATVEC, compared BIT-EXACTLY ----
216 if handle >= 0 {
217 gq_put32(frame, 0, GQ_OP_MATVEC)
218 gq_put32(frame, 4, handle)
219 gq_put32(frame, 8, GQ_K)
220 var e: i64 = 0
221 while e < GQ_K { gq_put32(frame, 12 + e * 4, gq_i2f((e % 5) + 1)); e = e + 1 }
222
223 if gq_writen(fd, frame, 12 + GQ_K * 4) == 0 {
224 if gq_readn(fd, rsp, 8) == 0 {
225 let st: i64 = gq_get32(rsp, 0)
226 let n: i64 = gq_get32(rsp, 4)
227 if st == 0 {
228 if gq_readn(fd, (((rsp as i64) + 8) as *u8), n * 4) == 0 {
229 var bad: i64 = 0
230 var rr: i64 = 0
231 while rr < n {
232 var want: i64 = 0
233 var ee: i64 = 0
234 while ee < GQ_K { want = want + ((ee + rr) % 16) * ((ee % 5) + 1); ee = ee + 1 }
235 let wantbits: i64 = gq_i2f(want)
236 let got: i64 = gq_get32(rsp, 8 + rr * 4)
237 if got != wantbits {
238 if bad < 3 {
239 gq_w(" row " as *u8); gq_n(rr)
240 gq_w(" got bits " as *u8); gq_n(got)
241 gq_w(" want " as *u8); gq_n(wantbits)
242 gq_w(" (int " as *u8); gq_n(want); gq_w(")\n" as *u8)
243 }
244 bad = bad + 1
245 }
246 rr = rr + 1
247 }
248 if bad == 0 {
249 gq_w(" PASS T3 MATVEC " as *u8); gq_n(n)
250 gq_w(" rows BIT-EXACT vs integer prediction (no tolerance used)\n" as *u8); pass = pass + 1
251 } else {
252 gq_w(" FAIL T3 MATVEC " as *u8); gq_n(bad); gq_w(" rows wrong\n" as *u8); fail = fail + 1
253 }
254
255 // ---- T4: NEG-CONTROL. The SAME comparator must reject a deliberately wrong
256 // prediction. Without this, "0 mismatches" could just mean the check never fires.
257 var negbad: i64 = 0
258 var r2: i64 = 0
259 while r2 < n {
260 var want2: i64 = 1 // deliberately off by one
261 var e2: i64 = 0
262 while e2 < GQ_K { want2 = want2 + ((e2 + r2) % 16) * ((e2 % 5) + 1); e2 = e2 + 1 }
263 if gq_get32(rsp, 8 + r2 * 4) != gq_i2f(want2) { negbad = negbad + 1 }
264 r2 = r2 + 1
265 }
266 if negbad == n {
267 gq_w(" PASS T4 NEG-CONTROL all " as *u8); gq_n(negbad)
268 gq_w(" rows rejected against an off-by-one prediction -> T3 is real\n" as *u8); pass = pass + 1
269 } else {
270 gq_w(" FAIL T4 NEG-CONTROL only " as *u8); gq_n(negbad)
271 gq_w(" of " as *u8); gq_n(n); gq_w(" rejected -> comparator is blind\n" as *u8); fail = fail + 1
272 }
273 } else { gq_w(" FAIL T3 MATVEC short payload\n" as *u8); fail = fail + 1 }
274 } else { gq_w(" FAIL T3 MATVEC status " as *u8); gq_n(st); gq_w("\n" as *u8); fail = fail + 1 }
275 } else { gq_w(" FAIL T3 MATVEC short read\n" as *u8); fail = fail + 1 }
276 } else { gq_w(" FAIL T3 MATVEC write\n" as *u8); fail = fail + 1 }
277
278 // ---- T5: a bad handle must be REFUSED, not silently served ----
279 gq_put32(frame, 0, GQ_OP_MATVEC)
280 gq_put32(frame, 4, 999)
281 gq_put32(frame, 8, GQ_K)
282 if gq_writen(fd, frame, 12 + GQ_K * 4) == 0 {
283 if gq_readn(fd, rsp, 4) == 0 {
284 let st: i64 = gq_get32(rsp, 0)
285 if st != 0 {
286 gq_w(" PASS T5 unknown handle REFUSED with status " as *u8); gq_n(st); gq_w("\n" as *u8); pass = pass + 1
287 } else { gq_w(" FAIL T5 unknown handle was ACCEPTED\n" as *u8); fail = fail + 1 }
288 } else { gq_w(" FAIL T5 no response\n" as *u8); fail = fail + 1 }
289 } else { gq_w(" FAIL T5 write\n" as *u8); fail = fail + 1 }
290
291 // ---- T6: THE CONNECTION MUST STILL WORK AFTER A REFUSAL ----
292 // T5's rejected request still carried a K*4-byte payload. The first version of the daemon
293 // answered without consuming it, so those bytes were parsed as the NEXT opcode -- its log
294 // showed `unknown op 1065353216` (= 0x3F800000 = float 1.0), i.e. our own x vector. T5 passed
295 // anyway, because a desync only hurts the request AFTER it. This tooth is that request.
296 gq_put32(frame, 0, GQ_OP_MATVEC)
297 gq_put32(frame, 4, handle)
298 gq_put32(frame, 8, GQ_K)
299 var e6: i64 = 0
300 while e6 < GQ_K { gq_put32(frame, 12 + e6 * 4, gq_i2f((e6 % 5) + 1)); e6 = e6 + 1 }
301 if gq_writen(fd, frame, 12 + GQ_K * 4) == 0 {
302 if gq_readn(fd, rsp, 8) == 0 {
303 let st6: i64 = gq_get32(rsp, 0)
304 let n6: i64 = gq_get32(rsp, 4)
305 if st6 == 0 {
306 if n6 == GQ_N {
307 if gq_readn(fd, (((rsp as i64) + 8) as *u8), n6 * 4) == 0 {
308 var want6: i64 = 0
309 var ee6: i64 = 0
310 while ee6 < GQ_K { want6 = want6 + (ee6 % 16) * ((ee6 % 5) + 1); ee6 = ee6 + 1 }
311 if gq_get32(rsp, 8) == gq_i2f(want6) {
312 gq_w(" PASS T6 stream still framed after a refusal (no desync)\n" as *u8); pass = pass + 1
313 } else {
314 gq_w(" FAIL T6 row 0 wrong after a refusal -> stream desynchronised\n" as *u8); fail = fail + 1
315 }
316 } else { gq_w(" FAIL T6 short payload after refusal\n" as *u8); fail = fail + 1 }
317 } else {
318 gq_w(" FAIL T6 got N=" as *u8); gq_n(n6)
319 gq_w(" -> the refused request's payload was parsed as a header\n" as *u8); fail = fail + 1
320 }
321 } else {
322 gq_w(" FAIL T6 status " as *u8); gq_n(st6)
323 gq_w(" -> connection unusable after a refusal\n" as *u8); fail = fail + 1
324 }
325 } else { gq_w(" FAIL T6 no response after refusal\n" as *u8); fail = fail + 1 }
326 } else { gq_w(" FAIL T6 write\n" as *u8); fail = fail + 1 }
327 }
328
329 sys_close(fd)
330 gq_w("NX-GPU-Q4K-GATE pass=" as *u8); gq_n(pass)
331 gq_w(" fail=" as *u8); gq_n(fail)
332 // MIGRATED onto nx_gate_verdict by nx_gate_dry_apply (D001, minimal form): every check
333 // row above is untouched, so the PASS/FAIL vector cannot change; only the hand-rolled
334 // verdict emission is replaced by the ONE shared base class. Proven by nx_gate_migrate verify.
335 let ctr__dry: *i64 = gv_ctr()
336 ctr__dry[0] = pass
337 ctr__dry[1] = pass + fail
338 let rc__dry: i64 = gv_verdict("GPU-Q4K-GATE" as *u8, ctr__dry, "teeth unchanged; verdict emission migrated onto the shared base class" as *u8)
339 sys_exit(rc__dry)
340 return rc__dry
341}