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}