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}