code wiki / (root) / nx_gguf_load_q4k_test.nx

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}