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}