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