code wiki / (root) / nx_q5_0_to_f32.nx

nx_q5_0_to_f32.nx source

↩ module page · 95 lines · 3783 B

1// nx_q5_0_to_f32.nx -- bits-up Q5_0 dequantization to IEEE 754 binary32. 2// 3// Q5_0 block layout (per ggml v3 spec; clean-room from public docs): 4// 18 bytes per block of 32 values: 5// bytes [0..2] d (f16 scale) 6// bytes [2..6] qh (32-bit packed high bits, LE) 7// bytes [6..22] qs (32 4-bit values, 2 per byte) 8// 9// Dequant per ggml v3 spec (block_q5_0 in ggml-common.h): 10// For j in 0..15: 11// qs_byte = qs[j] 12// qs_low = qs_byte & 0x0F 13// qs_high = (qs_byte >> 4) & 0x0F 14// qh_bit_lo = (qh >> j) & 1 -- pairs with qs_low 15// qh_bit_hi = (qh >> (j + 16)) & 1 -- pairs with qs_high 16// q5_lo = qs_low | (qh_bit_lo << 4) // 0..31 17// q5_hi = qs_high | (qh_bit_hi << 4) // 0..31 18// value[j] = (q5_lo - 16) * d_f32 19// value[j+16] = (q5_hi - 16) * d_f32 20// 21// CRITICAL: positions 0..15 use qs[j].low+qh_bit[j], positions 16..31 22// use qs[j].high+qh_bit[j+16]. The two halves of qs are interleaved 23// against DIFFERENT bit ranges of qh (not bits [i] for all i). 24// (Bug fix 2026-05-21 after live-fire on real Qwen2.5 surfaced 25// embed-magnitude blow-up due to wrong interleaving.) 26// 27// genealogy_id: ggml_q5_0_canon 28// lineage_id: substrate_q5_0_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_Q5_0_OK: nx_int = 0 37const NX_Q5_0_ERR_BAD_DIM: nx_int = 1 38const NX_Q5_0_VPB: i64 = 32 // values per block (QK5_0) 39const NX_Q5_0_BPB: i64 = 22 // bytes per block (d:2 + qh:4 + qs:16) 40 41func _nx_q5_0_block_to_f32(buf: *u8, base_off: i64, take: i64, 42 out_f32: *i64) -> nx_int { 43 let d_f16: i64 = nx_le_read_u16(buf, base_off + 0) 44 let d_f32: i64 = nx_f16_to_f32(d_f16) 45 let qh: i64 = nx_le_read_u32(buf, base_off + 2) 46 let qs_off: i64 = base_off + 6 47 48 // Per ggml: walk j=0..15, emit value[j] (low nibble) and value[j+16] 49 // (high nibble) with paired qh bits at positions j and j+16. 50 var j: i64 = 0 51 while j < 16 { 52 let qs_byte: i64 = nx_le_read_u8(buf, qs_off + j) 53 let qs_lo: i64 = qs_byte & 0x0F 54 let qs_hi: i64 = (qs_byte >> 4) & 0x0F 55 let qh_bit_lo: i64 = (qh >> j) & 1 56 let qh_bit_hi: i64 = (qh >> (j + 16)) & 1 57 let q5_lo: i64 = qs_lo | (qh_bit_lo << 4) 58 let q5_hi: i64 = qs_hi | (qh_bit_hi << 4) 59 let s_lo: i64 = q5_lo - 16 60 let s_hi: i64 = q5_hi - 16 61 62 // HARDWARE convert+mul (2026-07-08): __f32_from_i64=cvtsi2ss (1 instr) 63 // replaces the ~40-op software nx_i32_to_f32; __f32_mul=mulss replaces 64 // software nx_f32_mul. Bit-exact for these normal finite products 65 // (proven: nx_q5_0_threaded_gate F32POOL==Q5FUSEDPOOL). Speeds the 66 // model-load dequant of the 388M Q5_0 values (79% of this model). 67 if j < take { 68 out_f32[j] = __f32_mul(d_f32, __f32_from_i64(s_lo)) 69 } 70 let j_hi: i64 = j + 16 71 if j_hi < take { 72 out_f32[j_hi] = __f32_mul(d_f32, __f32_from_i64(s_hi)) 73 } 74 j = j + 1 75 } 76 return NX_Q5_0_OK 77} 78 79// Multi-block dequant. 80 81func nx_q5_0_to_f32(buf: *u8, base_off: i64, n_values: i64, 82 out_f32: *i64) -> nx_int { 83 let n_blocks: i64 = (n_values + NX_Q5_0_VPB - 1) / NX_Q5_0_VPB 84 var b: i64 = 0 85 while b < n_blocks { 86 let block_off: i64 = base_off + b * NX_Q5_0_BPB 87 let block_n: i64 = n_values - b * NX_Q5_0_VPB 88 var take: i64 = block_n 89 if take > NX_Q5_0_VPB { take = NX_Q5_0_VPB } 90 _nx_q5_0_block_to_f32(buf, block_off, take, 91 ((out_f32 as i64) + b * NX_Q5_0_VPB * 8) as *i64) 92 b = b + 1 93 } 94 return NX_Q5_0_OK 95}