code wiki / (root) / nx_q4k_speed_bench.nx

nx_q4k_speed_bench.nx source

↩ module page · 121 lines · 4749 B

1// nx_q4k_speed_bench.nx -- measure the CPU speedup of the integer fused-dequant-dot vs emulated-f32. 2// 3// sd-server -> Nishi migration (CPU perf payoff). Both paths now agree with ggml (dequant tech debt fixed). 4// This times, on the real blk.0.attn_q Q4_K weight row (4096), N iterations of: 5// (A) INTEGER: nx_q4k_dot_row_col (fused dequant + integer-Q34 dot; the SOTA quantized-GEMM route). 6// (B) F32: nx_q4k_to_f32 (dequant to f32) + emulated-f32 dot (the correctness route). 7// Activation is converted to f32 ONCE outside the timed loop (amortized in a real GEMM), so we compare the 8// per-output-neuron cost fairly. Reports us/op each and the speedup x100. 9// license_tier: ORIGINAL 10import "nx_syscalls.nx" 11import "nx_tier.nx" 12import "nx_le.nx" 13import "nx_strconv.nx" 14import "nx_tensor.nx" 15import "nx_gguf.nx" 16import "nx_gguf_load.nx" 17import "nx_gguf_meta.nx" 18import "nx_placement.nx" 19import "nx_gguf_load_lazy.nx" 20import "nx_q4k_matmul.nx" 21import "nx_dequant_iter.nx" 22import "nx_q4k_to_f32.nx" 23import "nx_f32.nx" 24import "nx_f32_cvt.nx" 25import "nx_clock.nx" 26 27func sb_emit(fd: i64, key: *u8, key_len: i64, value: i64) -> i64 { 28 let line: *u8 = sys_mmap(80) 29 var lo: i64 = 0 30 var ki: i64 = 0 31 while ki < key_len { line[lo] = key[ki]; lo = lo + 1; ki = ki + 1 } 32 line[lo] = 0x3D; lo = lo + 1 33 let dec: *u8 = sys_mmap(32) 34 let nd: i64 = nx_strconv_format_i64(value, dec) 35 var k: i64 = 0 36 while k < nd { line[lo] = dec[k]; lo = lo + 1; k = k + 1 } 37 line[lo] = 0x0A; lo = lo + 1 38 return sys_write(fd, line, lo) 39} 40 41func main() -> i64 { 42 let path: *u8 = "/mnt/c/Users/elder/elder-ai-platform/models/unified/text_encoder/Huihui-Qwen3-4B-Instruct-2507-abliterated-Q4_K_M.gguf" as *u8 43 let fd: i64 = sys_openat_rd(path) 44 if fd < 0 { return 30 } 45 let CAP: i64 = 1153433600 46 let buf: *u8 = sys_mmap(CAP) 47 var total: i64 = 0 48 var go: i64 = 1 49 while go == 1 { 50 let r: i64 = sys_read(fd, ((buf as i64) + total) as *u8, CAP - total) 51 if r <= 0 { go = 0 } else { total = total + r; if total >= CAP { go = 0 } } 52 } 53 sys_close(fd) 54 if total < 100000000 { return 31 } 55 let hdr: *NxGgufHeader = sys_mmap(NX_GGUF_HDR_BYTES) as *NxGgufHeader 56 if nx_gguf_parse(buf, total, hdr) != NX_GGUF_OK { return 40 } 57 let qi: nx_int = nx_gguf_find_tensor(hdr, "blk.0.attn_q.weight" as *u8, 19) 58 if qi < 0 { return 60 } 59 let ti: *NxGgufTensorInfo = nx_gguf_tensor_at(hdr, qi) 60 if ti.ggml_type != 12 { return 61 } 61 let HID: i64 = ti.dim_0 62 let w_off: i64 = hdr.data_off + ti.offset 63 let n_blocks: i64 = HID / 256 64 if w_off + n_blocks * 144 > total { return 63 } 65 66 let col: *i64 = sys_mmap(HID * 8) as *i64 67 let col_f32: *i64 = sys_mmap(HID * 8) as *i64 68 var i: i64 = 0 69 while i < HID { col[i] = 1024 + (i - (i / 5) * 5) * 256; col_f32[i] = nx_q10_to_f32(col[i]); i = i + 1 } 70 71 let it: *NxQ4KBlockIter = nx_q4k_iter_alloc() 72 let wf32: *i64 = sys_mmap(HID * 8) as *i64 73 let N: i64 = 1000 74 75 // ---- warm up both paths ---- 76 var w: i64 = 0 77 while w < 8 { 78 nx_q4k_dot_row_col(buf, w_off, n_blocks, col, it) 79 nx_q4k_to_f32(buf, w_off, HID, wf32) 80 w = w + 1 81 } 82 83 // ---- time INTEGER fused dequant+dot ---- 84 var sink_i: i64 = 0 85 let t0: i64 = nx_clock_monotonic_ns() 86 var k: i64 = 0 87 while k < N { sink_i = sink_i + nx_q4k_dot_row_col(buf, w_off, n_blocks, col, it); k = k + 1 } 88 let t1: i64 = nx_clock_monotonic_ns() 89 let int_ns: i64 = t1 - t0 90 91 // ---- time F32 dequant + emulated-f32 dot ---- 92 var sink_f: i64 = 0 93 let t2: i64 = nx_clock_monotonic_ns() 94 k = 0 95 while k < N { 96 nx_q4k_to_f32(buf, w_off, HID, wf32) 97 var acc: i64 = 0 98 i = 0 99 while i < HID { acc = nx_f32_add(acc, nx_f32_mul(wf32[i], col_f32[i])); i = i + 1 } 100 sink_f = sink_f + acc 101 k = k + 1 102 } 103 let t3: i64 = nx_clock_monotonic_ns() 104 let f32_ns: i64 = t3 - t2 105 106 let ofd: i64 = sys_openat_wr("/tmp/zimg_q4k_speed.txt" as *u8, 0x1a4) 107 if ofd >= 0 { 108 sb_emit(ofd, "N" as *u8, 1, N) 109 sb_emit(ofd, "HID" as *u8, 3, HID) 110 sb_emit(ofd, "int_total_ns" as *u8, 12, int_ns) 111 sb_emit(ofd, "f32_total_ns" as *u8, 12, f32_ns) 112 sb_emit(ofd, "int_ns_per_op" as *u8, 13, int_ns / N) 113 sb_emit(ofd, "f32_ns_per_op" as *u8, 13, f32_ns / N) 114 if int_ns > 0 { sb_emit(ofd, "speedup_x100" as *u8, 12, f32_ns * 100 / int_ns) } 115 sb_emit(ofd, "sink_guard" as *u8, 10, (sink_i & 1) + (sink_f & 1)) 116 sys_close(ofd) 117 } 118 if int_ns <= 0 { return 80 } // clock resolution failure 119 if f32_ns <= int_ns { return 81 } // integer must be faster 120 return 0 121}