nx_gguf_load_q4k_test.nx source
↩ module page · 146 lines · 5248 B
1// nx_gguf_load_q4k_test.nx -- focused smoke for Q4_K dequant.
2//
3// Builds a synthetic GGUF with one Q4_K tensor (1 super-block, 256
4// values) and verifies the dequant:
5//
6// d = 1.0 (f16 0x3C00) -> d_q10 = 1024
7// dmin = 0.0 -> dmin_q10 = 0
8// scales[0..7] = {1, 1, 1, 1, 2, 0, 0, 0} (only sc[4]=2 in high half)
9// mins[0..7] = {0, 0, 0, 0, 0, 0, 0, 0}
10// nibble byte 0: 0x32 -> low nibble 2, high nibble 3
11// nibble byte 64: 0x32 -> low nibble 2, high nibble 3
12// all other nibbles = 0
13//
14// ggml layout (32-byte groups; group g -> sub-block 2g low / 2g+1 high)
15// + Q24 super-scale (d=1.0 -> d_q24 = 2^24 = 16777216). byte 0 is
16// group 0 l=0 (low -> sub-block 0 out[0]; high -> sub-block 1 out[32]);
17// byte 64 is group 2 l=0 (low -> sub-block 4 out[128]; high -> sub-block
18// 5, sc[5]=0, out[160]=0).
19//
20// Expected Q24 outputs:
21// out[0] = 16777216 * sc[0]=1 * 2 - 0 = 33554432
22// out[32] = 16777216 * sc[1]=1 * 3 - 0 = 50331648
23// out[128] = 16777216 * sc[4]=2 * 2 - 0 = 67108864
24// everywhere else: 0
25//
26// Tests both the low-half (is<4) and high-half (is>=4) scale unpacking
27// paths in nx_gguf_dequant_q4_k.
28
29import "nx_syscalls.nx"
30import "nx_tier.nx"
31import "nx_le.nx"
32import "nx_tensor.nx"
33import "nx_gguf.nx"
34import "nx_gguf_load.nx"
35
36func main() -> i64 {
37 let buf: *u8 = sys_mmap(512)
38
39 // ----- Header -----
40 buf[0] = 0x47; buf[1] = 0x47; buf[2] = 0x55; buf[3] = 0x46
41 buf[4] = 3
42 nx_le_write_u64(buf, 8, 1) // tensor_count
43 nx_le_write_u64(buf, 16, 0)
44
45 // ----- Tensor info: "q4k" Q4_K [256] -----
46 nx_le_write_u64(buf, 24, 3)
47 buf[32] = 0x71; buf[33] = 0x34; buf[34] = 0x6B // "q4k"
48 nx_le_write_u32(buf, 35, 1) // n_dims = 1
49 nx_le_write_u64(buf, 39, 256) // dim_0 = 256
50 nx_le_write_u32(buf, 47, NX_GGML_TYPE_Q4_K)
51 nx_le_write_u64(buf, 51, 0) // data offset for this tensor
52 // tensor_info ends at byte 59; data_off = align_up(59, 32) = 64.
53
54 // ----- Q4_K super-block at data_off = 64 -----
55 let q4k_off: i64 = 64
56 nx_le_write_u16(buf, q4k_off + 0, 0x3C00) // d = 1.0
57 nx_le_write_u16(buf, q4k_off + 2, 0x0000) // dmin = 0.0
58
59 // Scales/mins 12 bytes at q4k_off + 4:
60 // sc[0..3] = 1 -> q[0..3] = 0x01 (low 6 bits = 1, top 2 = 0)
61 // m[0..3] = 0 -> q[4..7] = 0x00
62 // sc[4] = 2 -> q[8] low nibble = 2, q[0]'s top 2 bits = 0
63 // sc[5..7] = 0 -> q[9..11] low nibble = 0
64 // m[4..7] = 0 -> q[8..11] high nibble = 0
65 buf[q4k_off + 4] = 0x01
66 buf[q4k_off + 5] = 0x01
67 buf[q4k_off + 6] = 0x01
68 buf[q4k_off + 7] = 0x01
69 buf[q4k_off + 8] = 0x00
70 buf[q4k_off + 9] = 0x00
71 buf[q4k_off + 10] = 0x00
72 buf[q4k_off + 11] = 0x00
73 buf[q4k_off + 12] = 0x02 // sc[4] low nibble = 2
74 buf[q4k_off + 13] = 0x00
75 buf[q4k_off + 14] = 0x00
76 buf[q4k_off + 15] = 0x00
77
78 // Nibble bytes 16..143 (128 bytes). Zero everything except:
79 // byte at qs_off + 0 (sub-block 0, l=0): 0x32
80 // byte at qs_off + 64 (sub-block 4, l=0): 0x32
81 let qs_off: i64 = q4k_off + 16
82 var zi: nx_int = 0
83 while zi < 128 {
84 buf[qs_off + zi] = 0
85 zi = zi + 1
86 }
87 buf[qs_off + 0] = 0x32
88 buf[qs_off + 64] = 0x32
89
90 // ----- Parse -----
91 let hdr: *NxGgufHeader = sys_mmap(NX_GGUF_HDR_BYTES) as *NxGgufHeader
92 let vp: nx_int = nx_gguf_parse(buf, 512, hdr)
93 if vp != NX_GGUF_OK { return 10 + vp }
94 if hdr.data_off != 64 { return 15 }
95 if hdr.n_tensors != 1 { return 16 }
96
97 // Format helpers sanity.
98 let ti0: *NxGgufTensorInfo = nx_gguf_tensor_at(hdr, 0)
99 if nx_gguf_ggml_values_per_block(ti0.ggml_type) != 256 { return 20 }
100 if nx_gguf_ggml_bytes_per_block(ti0.ggml_type) != 144 { return 21 }
101 if nx_gguf_tensor_n_values(ti0) != 256 { return 22 }
102 if nx_gguf_tensor_data_bytes(ti0) != 144 { return 23 }
103
104 // ----- Load + verify -----
105 let key: *u8 = sys_mmap(3)
106 key[0] = 0x71; key[1] = 0x34; key[2] = 0x6B
107 let err: *i64 = sys_mmap(8) as *i64
108 err[0] = 0
109 let t: *NxTensor = nx_gguf_load_tensor(buf, hdr, key, 3, err)
110 if err[0] != NX_GL_OK { return 30 + err[0] }
111 if t.numel != 256 { return 40 }
112
113 let p: *i64 = t.storage as *i64
114
115 // Sub-block 0 (low nibbles of group 0), sc=1, Q24:
116 // out[0] = 16777216 * 1 * 2 - 0 = 33554432
117 if p[0] != 33554432 { return 50 }
118 // out[1..32] all 0 (rest of sub-block 0; bytes 1..31 are 0)
119 var i2: nx_int = 1
120 while i2 < 32 {
121 if p[i2] != 0 { return 60 + i2 }
122 i2 = i2 + 1
123 }
124
125 // Sub-block 1 (high nibbles of group 0), sc=1, Q24:
126 // out[32] = 16777216 * 1 * 3 - 0 = 50331648
127 if p[32] != 50331648 { return 90 }
128 // out[33..128] all 0 (rest of sub-block 1 + sub-blocks 2,3 empty)
129 var i3: nx_int = 33
130 while i3 < 128 {
131 if p[i3] != 0 { return 100 }
132 i3 = i3 + 1
133 }
134
135 // Sub-block 4 (low nibbles of group 2), sc=2, Q24:
136 // out[128] = 16777216 * 2 * 2 - 0 = 67108864
137 if p[128] != 67108864 { return 120 }
138 // out[129..256] all 0 (sub-block 5 high nibble uses sc[5]=0)
139 var i4: nx_int = 129
140 while i4 < 256 {
141 if p[i4] != 0 { return 130 }
142 i4 = i4 + 1
143 }
144
145 return 0
146}