code wiki / (root) / nx_qwen_reader_bench_gate.nx

nx_qwen_reader_bench_gate.nx source

↩ module page · 419 lines · 18508 B

1// nx_qwen_reader_bench_gate.nx -- IN-PROCESS sovereign benchmark of the pretrained no-float Qwen2.5-0.5B as an 2// extractive SQuAD reader. Loads the model ONCE (nsv_init -> dequant-once caches), loops the real 3// (question, context, gold) rows from knowledge/index/reader_rows.bin, greedy-generates the answer IN-PROCESS 4// (NO HTTP, NO daemon, NO socket), scores integer token-F1 vs gold, prints per-row detail + the mean permille. 5// 6// WHY THIS EXISTS: the prior PowerShell->HTTP->:8032 harness was fragile (the daemon's request path could not 7// take the long SQuAD contexts -> connection dropped mid-batch) AND it violated doctrine ("sovereign gates, 8// not curl harnesses"). Rule#3: stop patching the HTTP path -- rewrite the measurement as an in-process gate. 9// 10// Baselines for context (printed with the result): from-scratch neural reader F1~81 ; mechanical reader F1=251. 11// usage: nx_qwen_reader_bench_gate [N] (default N=30 ; i32 lossless ; greedy) Sovereign: nx_nofloat_serve_core. 12import "nx_syscalls.nx" 13import "nx_nofloat_serve_core.nx" 14import "nx_qa_score_lib.nx" // qs_f1 = the CANONICAL SQuAD scorer behind the 251/148/101 baseline (strips articles) 15 16const QRB_MODEL: *u8 = "/home/elderwesto/nx_stage/nx_real_model.gguf" 17const QRB_CTXCAP: i64 = 440 // context truncation (chars); 1-shot @ 440 (440 ctx + ~370 prefix/q <= 900 guard). ⚠2-shot@380 REVERTED — the standalone +104 (722 vs 618) was a SINGLE-GOLD artifact; on-protocol multi-alias it did NOT hold (SQuAD 427 < 510). See arc memory. 18const QRB_MAXPROMPT: i64 = 900 // hard char guard: sized for the FIXED tokenizer + NSV_MAXT=384 (900/2.5-worst-bpe = 360 tok + MAXNEW 16 = 376 < 384). Was 600 = a stale relic of the old 192-tok window that spuriously SKIPPED rows 8/9 (po=607) to 0. 19const QRB_MAXNEW: i64 = 16 20const QRB_MODE: i64 = 0 // 0 = i32 lossless (faithful), not i8 fast -- measure THE model, no approximation 21const QRB_RESULTS: *u8 = "knowledge/index/qwen_reader_results.tsv" // resume-accumulate ledger: "idx<TAB>f1\n" 22 23func qrb_pn(v: i64) -> i64 { let b: *u8=sys_mmap(32); let e: i64=nsv_catn(b,0,v); sys_write(1,b,e); return 0 } 24func qrb_ps(s: *u8) -> i64 { nsv_log(s); return 0 } 25 26// SQuAD token normalization: lowercase A-Z, keep a-z/0-9, everything else -> space(32). 27func qrb_lc(c: i64) -> i64 { 28 if c >= 65 { if c <= 90 { return c + 32 } } 29 if c >= 97 { if c <= 122 { return c } } 30 if c >= 48 { if c <= 57 { return c } } 31 return 32 32} 33func qrb_norm(src: *u8, n: i64, dst: *u8) -> i64 { var i: i64=0; while i<n { dst[i]=qrb_lc(src[i] as i64) as u8; i=i+1 } return n } 34 35// extract word boundaries (maximal non-space runs) from a normalized buffer into starts[]/lens[]; cap 256. 36func qrb_words(buf: *u8, n: i64, starts: *i64, lens: *i64) -> i64 { 37 var w: i64=0; var i: i64=0; var inw: i64=0; var st: i64=0 38 while i < n { 39 if (buf[i] as i64) == 32 { 40 if inw == 1 { if w < 256 { starts[w]=st; lens[w]=i-st; w=w+1 } inw=0 } 41 } else { 42 if inw == 0 { st=i; inw=1 } 43 } 44 i = i + 1 45 } 46 if inw == 1 { if w < 256 { starts[w]=st; lens[w]=i-st; w=w+1 } } 47 return w 48} 49func qrb_weq(a: *u8, ao: i64, al: i64, b: *u8, bo: i64, bl: i64) -> i64 { 50 if al != bl { return 0 } 51 var i: i64=0 52 while i < al { if a[ao+i] != b[bo+i] { return 0 } i=i+1 } 53 return 1 54} 55 56// question-aware context selection (retrieve-then-read): split ctx into sentences, score each by question-term 57// hits (normalized exact word match, len>=3 -- keeps AFC/NFC/50-class discriminators), emit best sentence +/- 1 58// neighbor shrunk-to-cap around the best. WHY: blind truncation is answer-POSITION-dependent -- MEASURED 59// ctx=260 hid deep answers (rows 4/5/10 = 0) while ctx=440 pulled in parallel-fact distractor sentences 60// (rows 7/8/9 answered the WRONG GAME). Selection wins both sides. No term hits -> prefix-truncate fallback. 61// sel packs preallocated scratch (>6-arg clobber gotcha): sel[0]=ctx-norm(4096) sel[1]=q-norm(1024) 62// sel[2..5]=ctx/q word starts+lens (256*8 each) sel[6..8]=sent off/len/score (64*8) sel[9]=dst(512) 63// sel[10]=word->sentence map (256*8). 64func qrb_sel_ctx(cp: *u8, cn: i64, qp: *u8, qn: i64, sel: *i64) -> i64 { 65 let nb: *u8 = sel[0] as *u8 66 let qb: *u8 = sel[1] as *u8 67 let cs: *i64 = sel[2] as *i64 68 let cl: *i64 = sel[3] as *i64 69 let qs: *i64 = sel[4] as *i64 70 let ql: *i64 = sel[5] as *i64 71 let soff: *i64 = sel[6] as *i64 72 let slen: *i64 = sel[7] as *i64 73 let ssc: *i64 = sel[8] as *i64 74 let dst: *u8 = sel[9] as *u8 75 let wsent: *i64 = sel[10] as *i64 76 var cn2: i64 = cn 77 if cn2 > 4000 { cn2 = 4000 } // norm-buffer bound (sel[0]=4096) 78 var qn2: i64 = qn 79 if qn2 > 1000 { qn2 = 1000 } 80 // 1) sentence split on ./!/? followed by space (raw bytes; abbrev over-splits are noise the +/-1 window absorbs) 81 var ns: i64 = 0 82 var st: i64 = 0 83 var i: i64 = 0 84 while i < cn2 { 85 let c: i64 = cp[i] as i64 86 var isend: i64 = 0 87 if c == 46 { isend = 1 } 88 if c == 33 { isend = 1 } 89 if c == 63 { isend = 1 } 90 if isend == 1 { 91 var nxt: i64 = 32 92 if i + 1 < cn2 { nxt = cp[i+1] as i64 } 93 if nxt == 32 { 94 if ns < 64 { soff[ns]=st; slen[ns]=i+1-st; ssc[ns]=0; ns=ns+1 } 95 st = i + 2 96 } 97 } 98 i = i + 1 99 } 100 if st < cn2 { if ns < 64 { soff[ns]=st; slen[ns]=cn2-st; ssc[ns]=0; ns=ns+1 } } 101 if ns == 0 { 102 var w0: i64 = cn2 103 if w0 > QRB_CTXCAP { w0 = QRB_CTXCAP } 104 i=0 105 while i < w0 { dst[i]=cp[i]; i=i+1 } 106 return w0 107 } 108 // 2) normalized word tables (qrb_norm is position-aligned 1:1 with the raw bytes) 109 qrb_norm(cp, cn2, nb) 110 qrb_norm(qp, qn2, qb) 111 let ncw: i64 = qrb_words(nb, cn2, cs, cl) 112 let nqw: i64 = qrb_words(qb, qn2, qs, ql) 113 // 3a) map each ctx word to its sentence 114 var scur: i64 = 0 115 var w: i64 = 0 116 while w < ncw { 117 var adv: i64 = 1 118 while adv == 1 { 119 adv = 0 120 if scur + 1 < ns { if cs[w] >= soff[scur] + slen[scur] { scur = scur + 1; adv = 1 } } 121 } 122 wsent[w] = scur 123 w = w + 1 124 } 125 // 3b) per-sentence score = number of DISTINCT question terms (len>=3) PRESENT in the sentence. 126 // Term-presence (not word-frequency) scoring: MEASURED failure row 0 -- a long sentence full of 127 // the/was outscored the true MVP sentence under per-word crediting; dedup + presence kills the 128 // stopword-frequency inflation while rare terms (named/valuable/player, AFC/NFC/50) decide. 129 var t: i64 = 0 130 while t < nqw { 131 if ql[t] >= 3 { 132 var dup: i64 = 0 133 var j2: i64 = 0 134 while j2 < t { 135 if dup == 0 { if qrb_weq(qb, qs[j2], ql[j2], qb, qs[t], ql[t]) == 1 { dup = 1 } } 136 j2 = j2 + 1 137 } 138 if dup == 0 { 139 var s2: i64 = 0 140 while s2 < ns { 141 var hit: i64 = 0 142 var w2: i64 = 0 143 while w2 < ncw { 144 if hit == 0 { if wsent[w2] == s2 { if qrb_weq(nb, cs[w2], cl[w2], qb, qs[t], ql[t]) == 1 { hit = 1 } } } 145 w2 = w2 + 1 146 } 147 ssc[s2] = ssc[s2] + hit 148 s2 = s2 + 1 149 } 150 } 151 } 152 t = t + 1 153 } 154 // 4) best sentence = first max; zero hits anywhere -> prefix-truncate fallback (old behavior) 155 var best: i64 = 0 156 var bsc: i64 = 0-1 157 i = 0 158 while i < ns { if ssc[i] > bsc { bsc = ssc[i]; best = i } i = i + 1 } 159 if bsc <= 0 { 160 var w1: i64 = cn2 161 if w1 > QRB_CTXCAP { w1 = QRB_CTXCAP } 162 i=0 163 while i < w1 { dst[i]=cp[i]; i=i+1 } 164 return w1 165 } 166 // 5) window best +/- 1 neighbor, shrunk to the cap dropping neighbors before the best sentence itself 167 var lo: i64 = best - 1 168 if lo < 0 { lo = 0 } 169 var hi: i64 = best + 1 170 if hi >= ns { hi = ns - 1 } 171 var tot: i64 = 0 172 i = lo 173 while i <= hi { tot = tot + slen[i] + 1; i = i + 1 } 174 var shrink: i64 = 1 175 while shrink == 1 { 176 shrink = 0 177 if tot > QRB_CTXCAP { 178 if lo < best { tot = tot - slen[lo] - 1; lo = lo + 1; shrink = 1 } else { 179 if hi > best { tot = tot - slen[hi] - 1; hi = hi - 1; shrink = 1 } 180 } 181 } 182 } 183 var o: i64 = 0 184 i = lo 185 while i <= hi { 186 var k: i64 = 0 187 while k < slen[i] { if o < QRB_CTXCAP { dst[o] = cp[soff[i]+k]; o = o + 1 } k = k + 1 } 188 if o < QRB_CTXCAP { dst[o] = 32 as u8; o = o + 1 } 189 i = i + 1 190 } 191 return o 192} 193 194// token-F1 permille between pred[0,pn) and gold[0,gn). sc packs 7 preallocated scratch ptrs (bundled to stay 195// under the >6-arg clobber gotcha): sc[0]=pb sc[1]=gb (norm bufs) sc[2..5]=ps/pl/gs/gl (word tables) sc[6]=gu. 196func qrb_f1(pred: *u8, pn: i64, gold: *u8, gn: i64, sc: *i64) -> i64 { 197 let pb: *u8 = sc[0] as *u8 198 let gb: *u8 = sc[1] as *u8 199 let ps: *i64 = sc[2] as *i64 200 let pl: *i64 = sc[3] as *i64 201 let gs: *i64 = sc[4] as *i64 202 let gl: *i64 = sc[5] as *i64 203 let gu: *i64 = sc[6] as *i64 204 qrb_norm(pred, pn, pb) 205 qrb_norm(gold, gn, gb) 206 let np: i64 = qrb_words(pb, pn, ps, pl) 207 let ng: i64 = qrb_words(gb, gn, gs, gl) 208 if np == 0 { return 0 } 209 if ng == 0 { return 0 } 210 var k: i64=0 211 while k < ng { gu[k]=0; k=k+1 } 212 var common: i64=0 213 var i: i64=0 214 while i < np { 215 var j: i64=0 216 var hit: i64=0 217 while j < ng { 218 if hit == 0 { if gu[j]==0 { if qrb_weq(pb,ps[i],pl[i],gb,gs[j],gl[j])==1 { gu[j]=1; common=common+1; hit=1 } } } 219 j = j + 1 220 } 221 i = i + 1 222 } 223 if common == 0 { return 0 } 224 return (2000*common) / (np + ng) 225} 226 227// resume ledger: read QRB_RESULTS, count completed rows (one per '\n') and sum their f1 (digits after the tab). 228// returns done-count; writes the running sum to sumptr[0]. Absent file -> done=0, sum=0. 229func qrb_read_done(sumptr: *i64) -> i64 { 230 let fd: i64 = sys_openat_rd(QRB_RESULTS) 231 if fd < 0 { sumptr[0]=0; return 0 } 232 let cap: i64 = 1048576 233 let buf: *u8 = sys_mmap(cap) 234 var got: i64=0 235 var r: i64=1 236 while r > 0 { r=sys_read(fd,(buf as i64+got) as *u8, cap-got); if r>0 { got=got+r } } 237 sys_close(fd) 238 var done: i64=0 239 var sum: i64=0 240 var f1: i64=0 241 var seentab: i64=0 242 var i: i64=0 243 while i < got { 244 let c: i64 = buf[i] as i64 245 if c == 9 { seentab=1 } else { 246 if c == 10 { done=done+1; sum=sum+f1; f1=0; seentab=0 } else { 247 if seentab == 1 { if c>=48 { if c<=57 { f1=f1*10+(c-48) } } } 248 } 249 } 250 i = i + 1 251 } 252 sumptr[0]=sum 253 return done 254} 255// append one completed row to the ledger (open-append + write + close = durable before any later crash). 256func qrb_append_result(idx: i64, f1: i64) -> i64 { 257 let fd: i64 = sys_openat_append(QRB_RESULTS, 420) // 0644 258 if fd < 0 { return 0-1 } 259 let b: *u8 = sys_mmap(64) 260 var o: i64 = nsv_catn(b, 0, idx) 261 b[o]=9 as u8 262 o=o+1 263 o = nsv_catn(b, o, f1) 264 b[o]=10 as u8 265 o=o+1 266 sys_write(fd, b, o) 267 sys_close(fd) 268 return 0 269} 270 271func main(argc: i64, argv: *i64) -> i64 { 272 var N: i64 = 30 273 var MODE: i64 = QRB_MODE // arg2: "1" -> i8 fast, else i32 lossless (default) 274 if argc >= 2 { 275 let a: *u8 = argv[1] as *u8 276 var v: i64=0 277 var j: i64=0 278 while a[j] != (0 as u8) { if a[j]>=(48 as u8){ if a[j]<=(57 as u8){ v=v*10+(a[j] as i64 - 48) } } j=j+1 } 279 if v > 0 { N = v } 280 } 281 if argc >= 3 { let b: *u8 = argv[2] as *u8; if b[0]==(49 as u8) { MODE = 1 } } 282 var mpath: *u8 = QRB_MODEL // arg3: model path override (NAS-exec: Synology home != /home/elderwesto) 283 if argc >= 4 { mpath = argv[3] as *u8 } 284 qrb_ps("=== QWEN-AS-READER in-process benchmark (greedy, ctxcap=400) mode=" as *u8); qrb_pn(MODE); qrb_ps(" (0=i32-lossless 1=i8-fast) ===\n" as *u8) 285 qrb_ps("loading model + dequant-once caches...\n" as *u8) 286 let rc: i64 = nsv_init(mpath) 287 if rc != 0 { qrb_ps("MODEL INIT FAILED rc=" as *u8); qrb_pn(rc); qrb_ps("\n" as *u8); return 1 } 288 qrb_ps("model ready; reading rows...\n" as *u8) 289 290 let fd: i64 = sys_openat_rd("knowledge/index/reader_rows.bin" as *u8) 291 if fd < 0 { qrb_ps("reader_rows.bin open FAILED\n" as *u8); return 1 } 292 let cap: i64 = 16777216 293 let raw: *u8 = sys_mmap(cap) 294 var got: i64=0 295 var r: i64=1 296 while r > 0 { r=sys_read(fd,(raw as i64+got) as *u8, cap-got); if r>0 { got=got+r } } 297 sys_close(fd) 298 if got < 16 { qrb_ps("reader_rows.bin too small\n" as *u8); return 1 } 299 300 // preallocate ALL per-row scratch ONCE (no per-row/per-token mmap -> no leak in the loop) 301 let pr: *u8 = sys_mmap(4096) 302 let out: *u8 = sys_mmap(4096) 303 let meta: *i64 = sys_mmap(8*8) as *i64 304 let gp: *i64 = sys_mmap(12*8) as *i64 305 let sc: *i64 = sys_mmap(7*8) as *i64 306 sc[0]=sys_mmap(1024) as i64 307 sc[1]=sys_mmap(1024) as i64 308 sc[2]=sys_mmap(256*8) as i64 309 sc[3]=sys_mmap(256*8) as i64 310 sc[4]=sys_mmap(256*8) as i64 311 sc[5]=sys_mmap(256*8) as i64 312 sc[6]=sys_mmap(256*8) as i64 313 let goldbuf: *u8 = sys_mmap(4096) // NUL-terminated copy of gold for qs_f1 (gold in the mmap is not NUL-term) 314 let sel: *i64 = sys_mmap(12*8) as *i64 // qrb_sel_ctx scratch (see its header comment) 315 sel[0]=sys_mmap(4096) as i64 316 sel[1]=sys_mmap(1024) as i64 317 sel[2]=sys_mmap(256*8) as i64 318 sel[3]=sys_mmap(256*8) as i64 319 sel[4]=sys_mmap(256*8) as i64 320 sel[5]=sys_mmap(256*8) as i64 321 sel[6]=sys_mmap(64*8) as i64 322 sel[7]=sys_mmap(64*8) as i64 323 sel[8]=sys_mmap(64*8) as i64 324 sel[9]=sys_mmap(512) as i64 325 sel[10]=sys_mmap(256*8) as i64 326 let selbuf: *u8 = sel[9] as *u8 // bind-before-index (inline-cast-index gotcha) 327 328 var sumbox: *i64 = sys_mmap(8) as *i64 329 let done0: i64 = qrb_read_done(sumbox) 330 var sum: i64 = sumbox[0] 331 qrb_ps("[resume] prior rows done=" as *u8); qrb_pn(done0) 332 qrb_ps(" (sum=" as *u8); qrb_pn(sum); qrb_ps(") ; target N=" as *u8); qrb_pn(N); qrb_ps("\n" as *u8) 333 334 var off: i64 = 8 335 var idx: i64 = 0 336 var newdone: i64 = 0 337 while off + 24 < got { 338 if idx >= N { off = got } else { 339 let hp: *i64 = (raw as i64 + off) as *i64 340 let qlen: i64 = hp[0] 341 if qlen < 0 { off = got } else { if qlen > 100000 { off = got } else { 342 let qp: *u8 = (raw as i64 + off + 8) as *u8 343 let hp2: *i64 = (raw as i64 + off + 8 + qlen) as *i64 344 let clen: i64 = hp2[0] 345 let cp: *u8 = (raw as i64 + off + 16 + qlen) as *u8 346 let hp3: *i64 = (raw as i64 + off + 16 + qlen + clen) as *i64 347 let alen: i64 = hp3[0] 348 let ap: *u8 = (raw as i64 + off + 24 + qlen + clen) as *u8 349 off = off + 24 + qlen + clen + alen 350 351 if idx >= done0 { 352 let cc: i64 = qrb_sel_ctx(cp, clen, qp, qlen, sel) // question-aware window (retrieve-then-read) 353 var po: i64 = 0 354 po = nsv_cat(pr, po, "Extract the answer as the shortest exact phrase from the context, not a sentence.\n\nContext: The Eiffel Tower was completed in 1889 and stands in the city of Paris.\nQuestion: In what city is the Eiffel Tower?\nAnswer: Paris\n\nContext: " as *u8) 355 po = nsv_catb(pr, po, selbuf, cc) 356 po = nsv_cat(pr, po, "\n\nQuestion: " as *u8) 357 var qc: i64 = qlen 358 if qc > 120 { qc = 120 } 359 po = nsv_catb(pr, po, qp, qc) 360 po = nsv_cat(pr, po, "\nAnswer:" as *u8) 361 362 gp[0]=pr as i64 363 gp[1]=po 364 gp[2]=QRB_MAXNEW 365 gp[3]=MODE 366 gp[4]=out as i64 367 gp[5]=4096 368 gp[6]=meta as i64 369 gp[7]=0-1 370 gp[8]=0 371 gp[9]=0 372 gp[10]=0 373 gp[11]=0 374 var tlen: i64 = 0-9 // sentinel: -9 = skipped (prompt over the token-window guard) 375 if po <= QRB_MAXPROMPT { tlen = nsv_generate(gp) } 376 377 var f1: i64 = 0 378 var el: i64 = 0 379 if tlen > 0 { 380 el = tlen 381 var i2: i64=0 382 var nlf: i64=0 383 while i2 < tlen { if nlf==0 { if (out[i2] as i64)==10 { el=i2; nlf=1 } } i2=i2+1 } 384 out[el] = 0 as u8 // NUL-terminate the answer's first line for qs_f1 385 var gi: i64 = 0 386 while gi < alen { goldbuf[gi] = ap[gi]; gi=gi+1 } 387 goldbuf[alen] = 0 as u8 388 f1 = qs_f1(out, goldbuf) // CANONICAL SQuAD F1 (article-stripping) = exact 251-baseline metric 389 } 390 391 qrb_append_result(idx, f1) 392 sum = sum + f1 393 newdone = newdone + 1 394 395 qrb_ps("[" as *u8); qrb_pn(idx); qrb_ps("] F1=" as *u8); qrb_pn(f1) 396 if tlen == 0-9 { qrb_ps(" (skipped po=" as *u8); qrb_pn(po); qrb_ps(">guard)" as *u8) } else { if tlen <= 0 { qrb_ps(" (gen-err=" as *u8); qrb_pn(meta[5]); qrb_ps(")" as *u8) } } 397 qrb_ps(" gold=[" as *u8); sys_write(1, ap, alen); qrb_ps("] qwen=[" as *u8) 398 if el > 0 { sys_write(1, out, el) } 399 qrb_ps("]\n" as *u8) 400 } 401 idx = idx + 1 402 } } 403 } 404 } 405 406 let total: i64 = done0 + newdone 407 if total >= N { 408 var mean: i64 = 0 409 if N > 0 { mean = sum / N } 410 qrb_ps("\n=== QWEN-AS-READER: mean token-F1 = " as *u8); qrb_pn(mean) 411 qrb_ps(" permille over " as *u8); qrb_pn(N); qrb_ps(" real SQuAD rows ===\n" as *u8) 412 qrb_ps("=== baselines: from-scratch neural reader F1~81 ; mechanical reader F1=251 ===\n" as *u8) 413 qrb_ps("QRB-COMPLETE\n" as *u8) 414 } else { 415 qrb_ps("\n[progress] " as *u8); qrb_pn(total); qrb_ps("/" as *u8); qrb_pn(N) 416 qrb_ps(" rows (this session +" as *u8); qrb_pn(newdone); qrb_ps(") ; relaunch to continue\n" as *u8) 417 } 418 return 0 419}