nx_q6_k_to_f32.nx source
↩ module page · 116 lines · 4888 B
1// nx_q6_k_to_f32.nx -- bits-up Q6_K dequantization to IEEE 754 binary32.
2//
3// Q6_K super-block layout (per ggml v3 spec; clean-room).
4// 210 bytes per super-block of 256 values:
5// bytes [0..128] ql 128 bytes, 4-bit packed (low/high nibble)
6// bytes [128..192] qh 64 bytes, 2-bit packed (4 pairs per byte)
7// bytes [192..208] scales 16 i8 sub-block scales
8// bytes [208..210] d f16 super-scale
9//
10// Dequant (per ggml dequantize_row_q6_K):
11// Process super-block in 2 chunks of 128 values each (c in 0..1).
12// Each chunk consumes 64 bytes of ql, 32 bytes of qh, 8 scales.
13// Inside a chunk, l loops 0..31, producing 4 output positions per l:
14// y[c*128 + l ] = d * sc[c*8 + (l/16) + 0] * q1
15// y[c*128 + l + 32] = d * sc[c*8 + (l/16) + 2] * q2
16// y[c*128 + l + 64] = d * sc[c*8 + (l/16) + 4] * q3
17// y[c*128 + l + 96] = d * sc[c*8 + (l/16) + 6] * q4
18// where:
19// q1 = (ql[c*64 + l ] & 0x0F) | ((qh[c*32 + l] >> 0) & 3) << 4 - 32
20// q2 = (ql[c*64 + l + 32] & 0x0F) | ((qh[c*32 + l] >> 2) & 3) << 4 - 32
21// q3 = (ql[c*64 + l ] >> 4 ) | ((qh[c*32 + l] >> 4) & 3) << 4 - 32
22// q4 = (ql[c*64 + l + 32] >> 4 ) | ((qh[c*32 + l] >> 6) & 3) << 4 - 32
23//
24// (Rewrite 2026-05-21 after live-fire on real Qwen2.5 showed the
25// naive per-position algorithm doesn't match ggml's interleaving.)
26//
27// genealogy_id: ggml_q6_k_canon
28// lineage_id: substrate_q6_k_to_f32_v1
29
30import "nx_syscalls.nx"
31import "nx_tier.nx"
32import "nx_le.nx"
33import "nx_f32.nx"
34import "nx_f32_cvt.nx"
35
36const NX_Q6_K_OK: nx_int = 0
37const NX_Q6_K_VPB: i64 = 256 // values per super-block
38const NX_Q6_K_BPB: i64 = 210 // bytes per super-block
39
40func _q6k_emit(d_f32: i64, scale_u8: i64,
41 q_low: i64, qh_byte: i64, qh_shift: i64,
42 out_f32: *i64, out_idx: i64, take: i64) -> nx_int {
43 if out_idx >= take { return 0 }
44 var scale_i: i64 = scale_u8
45 if scale_i >= 128 { scale_i = scale_i - 256 }
46 let scale_f32: i64 = __f32_mul(d_f32, __f32_from_i64(scale_i)) // HW cvtsi2ss+mulss (2026-07-08)
47 let q_high: i64 = (qh_byte >> qh_shift) & 0x03
48 let q6: i64 = q_low | (q_high << 4)
49 let s: i64 = q6 - 32
50 out_f32[out_idx] = __f32_mul(scale_f32, __f32_from_i64(s)) // HW cvtsi2ss+mulss (2026-07-08)
51 return 0
52}
53
54func _nx_q6_k_block_to_f32(buf: *u8, base_off: i64, take: i64,
55 out_f32: *i64) -> nx_int {
56 let ql_off: i64 = base_off + 0
57 let qh_off: i64 = base_off + 128
58 let scales_off: i64 = base_off + 192
59 let d_off: i64 = base_off + 208
60
61 let d_f16: i64 = nx_le_read_u16(buf, d_off)
62 let d_f32: i64 = nx_f16_to_f32(d_f16)
63
64 // Process 2 chunks of 128 values each (c=0 covers values 0..127,
65 // c=1 covers 128..255).
66 var c: i64 = 0
67 while c < 2 {
68 let ql_base: i64 = ql_off + c * 64
69 let qh_base: i64 = qh_off + c * 32
70 let sc_base: i64 = scales_off + c * 8
71 let y_base: i64 = c * 128
72
73 var l: i64 = 0
74 while l < 32 {
75 let is: i64 = l / 16 // 0 or 1 -- which scale subgroup
76
77 let ql0: i64 = nx_le_read_u8(buf, ql_base + l)
78 let ql1: i64 = nx_le_read_u8(buf, ql_base + l + 32)
79 let qh: i64 = nx_le_read_u8(buf, qh_base + l)
80
81 // q1 = (ql0 & 0xF) + bits01<<4 -32, scale sc[is + 0]
82 // q2 = (ql1 & 0xF) + bits23<<4 -32, scale sc[is + 2]
83 // q3 = (ql0 >> 4) + bits45<<4 -32, scale sc[is + 4]
84 // q4 = (ql1 >> 4) + bits67<<4 -32, scale sc[is + 6]
85 let sc1: i64 = nx_le_read_u8(buf, sc_base + is + 0)
86 let sc2: i64 = nx_le_read_u8(buf, sc_base + is + 2)
87 let sc3: i64 = nx_le_read_u8(buf, sc_base + is + 4)
88 let sc4: i64 = nx_le_read_u8(buf, sc_base + is + 6)
89
90 _q6k_emit(d_f32, sc1, ql0 & 0x0F, qh, 0, out_f32, y_base + l + 0, take)
91 _q6k_emit(d_f32, sc2, ql1 & 0x0F, qh, 2, out_f32, y_base + l + 32, take)
92 _q6k_emit(d_f32, sc3, (ql0 >> 4) & 0x0F, qh, 4, out_f32, y_base + l + 64, take)
93 _q6k_emit(d_f32, sc4, (ql1 >> 4) & 0x0F, qh, 6, out_f32, y_base + l + 96, take)
94
95 l = l + 1
96 }
97 c = c + 1
98 }
99 return NX_Q6_K_OK
100}
101
102func nx_q6_k_to_f32(buf: *u8, base_off: i64, n_values: i64,
103 out_f32: *i64) -> nx_int {
104 let n_super: i64 = (n_values + NX_Q6_K_VPB - 1) / NX_Q6_K_VPB
105 var sb: i64 = 0
106 while sb < n_super {
107 let super_off: i64 = base_off + sb * NX_Q6_K_BPB
108 let block_n: i64 = n_values - sb * NX_Q6_K_VPB
109 var take: i64 = block_n
110 if take > NX_Q6_K_VPB { take = NX_Q6_K_VPB }
111 _nx_q6_k_block_to_f32(buf, super_off, take,
112 ((out_f32 as i64) + sb * NX_Q6_K_VPB * 8) as *i64)
113 sb = sb + 1
114 }
115 return NX_Q6_K_OK
116}