Skip to main content

ferrox_quant/encode/
q5_k.rs

1//! The Q5_K weight encoder: a transcription of llama.cpp b7650's
2//! `quantize_row_q5_K_ref` (`ggml/src/ggml-quants.c:1467`).
3//!
4//! Q5_K is Q4_K's super-block fit with two things changed and nothing
5//! else:
6//!
7//! * the codes run `0..=31` instead of `0..=15`, and the candidate
8//!   grid handed to `make_qkx2_quants` is `(-0.5, 0.1, 15)` rather than
9//!   `(-1.0, 0.1, 20)` -- both of which are [`Q5_K_FIT`], passed to the
10//!   SAME [`super::fit::fit_qk_super_block`] Q4_K uses;
11//! * the fifth bit of each code goes into a separate 32-byte `qh`
12//!   plane, which is the only packing this module writes out itself.
13//!
14//! Two transcriptions of that shared fit is precisely the shape this
15//! repo has paid the most for: the copies agree the day they are
16//! written and drift the first time one of them is corrected. So the
17//! difference between Q4_K and Q5_K is four numbers in a struct, and if
18//! someone fixes the least-squares step it is fixed for both or for
19//! neither.
20//!
21//! The `qh` bit assignment is worth stating because it is not the
22//! obvious one: the 256 codes are walked in four groups of 64, and
23//! within a group the first 32 codes' high bits go to bit `m1` of
24//! `qh[j]` and the second 32's to bit `m2`, where `m1`/`m2` start at 1
25//! and 2 and shift LEFT BY TWO per group. So `qh[j]` holds the high
26//! bits of elements `j`, `j+32`, `j+64`, ... in bit pairs, not one
27//! contiguous run. `dequant_q5_k` in this crate reads it back with the
28//! same `u1 <<= 2` walk.
29
30use super::fit::{fit_qk_super_block, QkFit};
31use crate::{Q5_K_BLOCK_BYTES, Q5_K_BLOCK_ELEMS};
32
33/// Q5_K's half of the shared super-block fit: 5-bit codes, and the
34/// `(-0.5, 0.1, 15)` candidate grid from `ggml-quants.c:1488`.
35const Q5_K_FIT: QkFit = QkFit {
36    nmax: 31,
37    rmin: -0.5,
38    rdelta: 0.1,
39    nstep: 15,
40};
41
42/// Bytes of `qh` (one bit per element) in a Q5_K super-block.
43const QH_BYTES: usize = Q5_K_BLOCK_ELEMS / 8;
44
45/// Encodes one Q5_K super-block (exactly [`Q5_K_BLOCK_ELEMS`] values)
46/// and appends its [`Q5_K_BLOCK_BYTES`] bytes to `out`.
47pub fn encode_block_q5_k(block: &[f32; Q5_K_BLOCK_ELEMS], out: &mut Vec<u8>) {
48    let fitted = fit_qk_super_block(block, Q5_K_FIT);
49
50    let mut qh = [0u8; QH_BYTES];
51    let mut ql = [0u8; Q5_K_BLOCK_ELEMS / 2];
52    let (mut m1, mut m2) = (1u8, 2u8);
53    for (g, n) in (0..Q5_K_BLOCK_ELEMS).step_by(64).enumerate() {
54        for j in 0..32 {
55            // `l1 -= 16` where upstream tests `> 15`: the fifth bit is
56            // stripped into `qh` and the low four stay in `ql`. Writing
57            // `l1 & 0xF` instead would be the same for a code in
58            // `0..=31` and would silently keep a code above 31 -- which
59            // `fit_qk_super_block` cannot produce, but only because it
60            // clamps to `nmax`. Keeping the C's shape means the two
61            // facts stay tied together.
62            let mut l1 = fitted.l[n + j];
63            if l1 > 15 {
64                l1 -= 16;
65                qh[j] |= m1;
66            }
67            let mut l2 = fitted.l[n + j + 32];
68            if l2 > 15 {
69                l2 -= 16;
70                qh[j] |= m2;
71            }
72            ql[g * 32 + j] = l1 | (l2 << 4);
73        }
74        m1 <<= 2;
75        m2 <<= 2;
76    }
77
78    out.reserve(Q5_K_BLOCK_BYTES);
79    out.extend_from_slice(&fitted.d.to_le_bytes());
80    out.extend_from_slice(&fitted.dmin.to_le_bytes());
81    out.extend_from_slice(&fitted.packed);
82    out.extend_from_slice(&qh);
83    out.extend_from_slice(&ql);
84}
85
86/// Encodes a whole row (or any slice whose length is a multiple of
87/// [`Q5_K_BLOCK_ELEMS`]) into Q5_K super-blocks, appending to `out`.
88///
89/// Returns `None` when `src.len()` is not a multiple of the super-block
90/// size. llama.cpp answers that case by silently *changing type* --
91/// `convert_incompatible_tensor` rewrites a Q5_K tensor with an awkward
92/// row length to Q5_1, and to F16 if that does not fit either -- and
93/// ferrox has neither encoder, so this refuses instead of padding.
94/// Padding would write more elements than the tensor's shape declares
95/// and every following row would decode shifted.
96pub fn encode_row_q5_k(src: &[f32], out: &mut Vec<u8>) -> Option<()> {
97    let (blocks, rest) = src.as_chunks::<Q5_K_BLOCK_ELEMS>();
98    if !rest.is_empty() {
99        return None;
100    }
101    out.reserve(blocks.len() * Q5_K_BLOCK_BYTES);
102    for block in blocks {
103        encode_block_q5_k(block, out);
104    }
105    Some(())
106}
107
108#[cfg(test)]
109mod tests {
110    use super::*;
111    use crate::encode::testdata::k_quant_fixture;
112    use crate::{dequant_q5_k, q4_k_scale_min, Q4_K_SCALE_BYTES};
113    use half::f16;
114
115    /// llama.cpp's own bytes for [`k_quant_fixture`].
116    ///
117    /// Produced by the C harness described in the PR body: it links
118    /// llama.cpp b7650's `libggml-base` and calls the exported
119    /// `quantize_row_q5_K_ref` on the f32s
120    /// `encode::testdata::dump_the_fixture_the_c_harness_reads` writes.
121    /// The same harness also calls `ggml_quantize_chunk(GGML_TYPE_Q5_K,
122    /// ...)` -- the entry point `llama-quantize` itself goes through --
123    /// and asserts the two agree, so this is what the real tool writes
124    /// and not merely what a reference function does.
125    ///
126    /// An encoder that is within Q5_K's error bound passes any
127    /// tolerance test and still writes a different file. Only this
128    /// catches that.
129    const LLAMA_CPP_Q5_K_GOLDEN: [u8; 12 * Q5_K_BLOCK_BYTES] = [
130        0x1f, 0x0c, 0x06, 0x1c, 0x04, 0x0c, 0x59, 0xff, 0x04, 0x0c, 0x58, 0xff, 0x55, 0xcc, 0x89,
131        0xc3, 0x5b, 0xa3, 0x9b, 0x4d, 0xdb, 0x3a, 0xda, 0x4e, 0xdb, 0xbb, 0xf7, 0x3f, 0x08, 0xab,
132        0xbf, 0x41, 0x66, 0xaa, 0xa9, 0x3a, 0x1a, 0x4e, 0xab, 0xbb, 0x1b, 0xfc, 0xf0, 0x86, 0xd0,
133        0xe9, 0x67, 0x6f, 0xef, 0x94, 0x7a, 0x9f, 0xeb, 0x31, 0x2b, 0x4f, 0x4f, 0x11, 0xb5, 0x5b,
134        0xa4, 0xe6, 0x22, 0x0c, 0x77, 0x25, 0xb8, 0x86, 0x91, 0x10, 0x12, 0x51, 0x43, 0xe4, 0xfc,
135        0x4b, 0x72, 0x1a, 0x25, 0xc8, 0x7a, 0x65, 0xb9, 0xe4, 0xfd, 0xac, 0x72, 0x6b, 0xe2, 0x9f,
136        0xc7, 0x6e, 0x87, 0x41, 0xc6, 0x8c, 0xeb, 0x70, 0xf8, 0x6d, 0x3a, 0x67, 0xa2, 0xcd, 0x07,
137        0x1b, 0xfe, 0x1a, 0x02, 0xaf, 0x2f, 0xa4, 0x26, 0xfc, 0x8e, 0xc4, 0x6e, 0x7c, 0x50, 0x7e,
138        0xa6, 0x74, 0xc7, 0xe9, 0x07, 0x75, 0x64, 0x79, 0x39, 0xb0, 0xac, 0x7e, 0xec, 0x04, 0xf0,
139        0x47, 0x98, 0xb3, 0x5f, 0x41, 0x7a, 0x9f, 0xa2, 0x70, 0xc7, 0xf0, 0x65, 0x04, 0x0b, 0x5a,
140        0x79, 0x49, 0xb5, 0xaf, 0x42, 0xa2, 0x74, 0x43, 0x80, 0x9e, 0x29, 0x1d, 0x2a, 0xa7, 0x90,
141        0xdb, 0xe2, 0xe9, 0x58, 0xf1, 0x39, 0xf0, 0x3f, 0x51, 0x89, 0x82, 0x08, 0x0c, 0xcf, 0x1b,
142        0x00, 0x10, 0x48, 0xc6, 0x00, 0x00, 0x40, 0xcf, 0x45, 0xcc, 0xaa, 0xff, 0x2a, 0x0e, 0x82,
143        0xae, 0xa2, 0x56, 0xd2, 0x36, 0x06, 0x6e, 0x7a, 0x7a, 0x7e, 0xce, 0xa2, 0x0e, 0x22, 0xce,
144        0x8e, 0x0e, 0xe6, 0x8e, 0x02, 0x2e, 0x66, 0x82, 0x8e, 0x76, 0xc2, 0x66, 0xc6, 0x9a, 0xf0,
145        0xf0, 0xf0, 0xf0, 0xf0, 0xf0, 0xf0, 0xf0, 0xf0, 0xf0, 0xf0, 0xf0, 0xf0, 0xf0, 0xf0, 0xf0,
146        0xf0, 0xf0, 0xf0, 0xf0, 0xf0, 0xf0, 0xf0, 0xf0, 0xf0, 0xf0, 0xf0, 0xf0, 0xf0, 0xf0, 0xf0,
147        0xf0, 0xdb, 0x54, 0x17, 0x07, 0x8a, 0x67, 0x39, 0x7d, 0xc2, 0x76, 0x3d, 0xaf, 0x83, 0xc3,
148        0x16, 0x4b, 0xea, 0xd6, 0xc6, 0x05, 0x0c, 0x2e, 0xec, 0xf6, 0x0c, 0x2f, 0xfe, 0xa4, 0x0e,
149        0xde, 0xcb, 0x2a, 0x4a, 0x60, 0x67, 0xce, 0xf1, 0x6b, 0x33, 0xf4, 0x0f, 0x08, 0x12, 0xd4,
150        0x85, 0x0d, 0xc7, 0x5f, 0x5a, 0x70, 0x0e, 0x34, 0xf7, 0xa9, 0xe3, 0xcb, 0x41, 0x34, 0x7a,
151        0xcd, 0x0c, 0xf5, 0xa2, 0x0a, 0x21, 0xe0, 0x24, 0x91, 0xa5, 0xf8, 0x23, 0x66, 0xf7, 0x72,
152        0xce, 0xcb, 0x45, 0xa9, 0x54, 0xbe, 0x7a, 0x7d, 0xc5, 0xca, 0x67, 0xf3, 0x17, 0x0a, 0x03,
153        0xa8, 0x0f, 0x6d, 0x6f, 0x89, 0x90, 0xb2, 0x21, 0x0c, 0xe7, 0x1b, 0x05, 0x00, 0x58, 0xff,
154        0x05, 0x00, 0x59, 0xff, 0x55, 0xdd, 0x88, 0xcc, 0x0c, 0x87, 0xd2, 0xa7, 0x77, 0xae, 0x45,
155        0xe3, 0x75, 0x1c, 0x32, 0x47, 0xb0, 0x31, 0x30, 0xbd, 0x7e, 0x12, 0x2e, 0x35, 0x78, 0x2f,
156        0x1a, 0x9d, 0xb4, 0x88, 0x78, 0x64, 0xc8, 0xb6, 0xca, 0x4c, 0x87, 0x60, 0xa5, 0x38, 0xe8,
157        0x27, 0x58, 0x2f, 0xde, 0x70, 0x07, 0x6c, 0xcb, 0x47, 0x1d, 0xa5, 0xec, 0xf0, 0x4c, 0xd3,
158        0xcd, 0x2e, 0xf8, 0xa8, 0xaf, 0x48, 0x0c, 0xa5, 0xce, 0x53, 0x51, 0x97, 0x55, 0xaa, 0x96,
159        0x8b, 0xea, 0x7f, 0xe0, 0x1d, 0xb3, 0xe4, 0x51, 0xa1, 0x07, 0x35, 0xe7, 0xcb, 0x1b, 0x00,
160        0xbb, 0x84, 0xd5, 0xe3, 0x43, 0x00, 0x59, 0x02, 0x31, 0x0f, 0x66, 0x47, 0xeb, 0x94, 0xf3,
161        0xd2, 0xd5, 0x1b, 0x23, 0x87, 0x04, 0xb9, 0xa0, 0x44, 0xdc, 0x40, 0x41, 0x9b, 0x24, 0x41,
162        0xda, 0xd6, 0x3a, 0x10, 0x19, 0x3d, 0xbe, 0x1e, 0xb5, 0x35, 0xe2, 0x9f, 0xfb, 0xba, 0xab,
163        0x2e, 0xe2, 0xc9, 0x7c, 0x8a, 0xf4, 0x50, 0x5b, 0xa8, 0xf8, 0x90, 0xfe, 0x28, 0xf9, 0x09,
164        0xad, 0xb4, 0x37, 0x58, 0x88, 0x4b, 0xd1, 0x03, 0x77, 0x42, 0x23, 0xfa, 0x2f, 0xd4, 0xdc,
165        0xc2, 0x51, 0x40, 0x16, 0x0c, 0x06, 0x1c, 0x05, 0x0c, 0x57, 0xff, 0x05, 0x0c, 0x55, 0xff,
166        0x55, 0xdc, 0x97, 0xef, 0x77, 0x16, 0x2e, 0x68, 0xdb, 0x60, 0x5b, 0x8c, 0xda, 0x68, 0xc0,
167        0xe5, 0xee, 0xd1, 0x60, 0x18, 0x31, 0x38, 0x4c, 0x28, 0x10, 0xcb, 0x67, 0xbb, 0x09, 0xdd,
168        0xc5, 0xf3, 0x86, 0xfc, 0xf4, 0x12, 0x65, 0xf4, 0x9b, 0x92, 0xfd, 0x86, 0xc8, 0x8f, 0x3d,
169        0xc7, 0x27, 0xe1, 0xf5, 0xc6, 0x9c, 0xb4, 0xf1, 0x2a, 0x4b, 0x0b, 0xb0, 0x1e, 0xab, 0xc8,
170        0x06, 0x0a, 0xa5, 0x0f, 0xd1, 0x1a, 0xdb, 0x0e, 0x1c, 0xc6, 0xf1, 0xc2, 0xd5, 0x8e, 0x89,
171        0x9b, 0x21, 0x2b, 0x80, 0xb1, 0xb9, 0xe1, 0x97, 0xaa, 0xe1, 0x7a, 0x8c, 0x00, 0xa5, 0x6a,
172        0x08, 0x83, 0xbc, 0xa7, 0x20, 0x2b, 0x1f, 0x33, 0xa0, 0x36, 0x39, 0xa0, 0x96, 0xa9, 0xcd,
173        0xc5, 0xf0, 0x7e, 0xe3, 0xd4, 0x52, 0xfe, 0x16, 0xc8, 0x61, 0xd5, 0x7a, 0x7d, 0xe4, 0x59,
174        0x7a, 0xd2, 0x0c, 0xa6, 0xcb, 0x63, 0x11, 0x2b, 0x37, 0x96, 0xc9, 0x9e, 0xef, 0x3a, 0x9c,
175        0xed, 0xe1, 0xbc, 0xdd, 0xf0, 0xe6, 0x1b, 0x65, 0x0f, 0xcb, 0xc0, 0x7e, 0xa5, 0x51, 0x58,
176        0x28, 0x3b, 0xe4, 0x42, 0x05, 0xf8, 0x94, 0x39, 0xbb, 0x15, 0x8f, 0x57, 0x6e, 0xcf, 0x53,
177        0x02, 0x2f, 0x11, 0xe8, 0xe8, 0xf5, 0xf2, 0xab, 0xa8, 0xb0, 0xd6, 0xa5, 0xd3, 0x48, 0xff,
178        0xa9, 0x13, 0x06, 0x8f, 0xa1, 0x25, 0x07, 0xc6, 0x81, 0xb1, 0xa3, 0x86, 0x77, 0x36, 0xa3,
179        0xd6, 0x61, 0xa7, 0x15, 0xd3, 0x60, 0x87, 0x97, 0x96, 0x26, 0x81, 0xce, 0x85, 0x35, 0x80,
180        0xe3, 0xba, 0xe9, 0x40, 0xef, 0x07, 0x13, 0xd1, 0x17, 0x30, 0xd4, 0xcf, 0x2a, 0x60, 0x68,
181        0xbe, 0x83, 0x1f, 0xbe, 0xfe, 0xa1, 0x86, 0xbd, 0x4b, 0x52, 0x68, 0xa7, 0xd7, 0x2d, 0x07,
182        0x23, 0xe2, 0xc5, 0xb4, 0x0f, 0x7f, 0x10, 0x3d, 0x7f, 0x14, 0x07, 0x60, 0x0b, 0x54, 0x77,
183        0x46, 0xc6, 0xe6, 0x0f, 0x6c, 0xb6, 0x0f, 0x32, 0x7e, 0xaf, 0x78, 0xf8, 0xc0, 0xfa, 0xbb,
184        0x43, 0x15, 0xd9, 0x3c, 0x30, 0xff, 0x12, 0xe8, 0xb7, 0x95, 0xfb, 0xc7, 0xfe, 0x7e, 0xdb,
185        0x22, 0xae, 0x3d, 0x8f, 0x6b, 0x05, 0x90, 0xa3, 0x2d, 0x72, 0xfd, 0x1d, 0xd5, 0xca, 0xc9,
186        0x80, 0xf6, 0x09, 0xee, 0x40, 0xe4, 0xc6, 0xaa, 0x2f, 0x98, 0x7b, 0x6d, 0x7b, 0x0d, 0xfa,
187        0x8b, 0x75, 0x03, 0x5b, 0xde, 0xd0, 0xfd, 0xf1, 0x7a, 0x96, 0x5c, 0xee, 0x29, 0xe2, 0x3b,
188        0x7b, 0x2d, 0xa6, 0x3c, 0x3e, 0xa4, 0xd3, 0x0c, 0x43, 0x20, 0xea, 0x05, 0xbc, 0x16, 0xb1,
189        0xb2, 0xa7, 0xff, 0xac, 0xaa, 0xa8, 0xbf, 0x28, 0x7b, 0xcf, 0x12, 0xff, 0x2f, 0xbc, 0x5c,
190        0xdb, 0x09, 0x42, 0xaf, 0xcc, 0x7e, 0xca, 0x7c, 0x4c, 0x1e, 0x67, 0x08, 0xee, 0x34, 0x2f,
191        0x1e, 0x04, 0x0e, 0x04, 0x66, 0x29, 0xf4, 0x3e, 0x7d, 0x2c, 0xfd, 0xb9, 0xcb, 0x93, 0x87,
192        0x39, 0x95, 0x21, 0xf7, 0x7c, 0x26, 0xdc, 0x28, 0x28, 0x0a, 0xbe, 0x3e, 0x69, 0x69, 0x74,
193        0xe2, 0x92, 0x2b, 0xb7, 0x19, 0xdb, 0x20, 0xef, 0xbf, 0x07, 0xf6, 0x8b, 0xc6, 0x19, 0xf0,
194        0x79, 0x11, 0x51, 0x95, 0x04, 0x1a, 0x70, 0x64, 0x56, 0x20, 0x3f, 0x20, 0x00, 0xf9, 0xf5,
195        0x4d, 0xb2, 0x00, 0x3c, 0x31, 0xd8, 0x10, 0xf0, 0xf5, 0x44, 0x97, 0x18, 0x51, 0x8f, 0xbb,
196        0x0b, 0x78, 0x70, 0x26, 0x51, 0xdf, 0x90, 0x4d, 0xf8, 0x2d, 0xa6, 0x75, 0xa7, 0x76, 0xcf,
197        0xf4, 0x8b, 0x1d, 0xfd, 0x35, 0xc0, 0x8e, 0xf9, 0x06, 0xe9, 0x17, 0x15, 0x41, 0x35, 0x06,
198        0x6d, 0x4d, 0x69, 0xe8, 0x0f, 0x6f, 0xa0, 0x82, 0x47, 0x3c, 0xe1, 0x29, 0x32, 0x74, 0x41,
199        0x93, 0xf9, 0x5b, 0xc6, 0x57, 0x34, 0x7e, 0x9b, 0x1b, 0x07, 0x6f, 0x7b, 0x93, 0x85, 0xfc,
200        0xda, 0x37, 0x7a, 0x9b, 0x5d, 0x13, 0x3d, 0x05, 0x8f, 0x14, 0xb0, 0xe2, 0xbf, 0xae, 0xea,
201        0xe3, 0xfe, 0xfb, 0xcd, 0x22, 0x0f, 0xfe, 0x88, 0x90, 0xb2, 0xb0, 0xb8, 0x5b, 0x12, 0xa0,
202        0xb8, 0x97, 0xce, 0xc6, 0xdc, 0xfa, 0xb9, 0xcc, 0xb9, 0xb4, 0xac, 0xdc, 0x91, 0xea, 0x39,
203        0xca, 0x60, 0x9b, 0x78, 0x10, 0xde, 0xbd, 0x78, 0x89, 0xdc, 0xbb, 0x2a, 0x98, 0xd0, 0x23,
204        0x3a, 0x19, 0x01, 0xf9, 0x16, 0x46, 0x49, 0x8f, 0xe0, 0x3e, 0xa0, 0xd6, 0x99, 0x96, 0xaa,
205        0xdc, 0x65, 0x38, 0xad, 0x34, 0xc9, 0x5c, 0x07, 0xcc, 0xc5, 0xdf, 0xcc, 0xbf, 0x9d, 0xdd,
206        0x84, 0x3f, 0xa7, 0x0c, 0x0b, 0xa0, 0x6e, 0xf5, 0x22, 0xf8, 0x7d, 0x51, 0xfc, 0xe6, 0x93,
207        0x60, 0xbf, 0x2f, 0x2c, 0xa9, 0x60, 0x6f, 0x5b, 0xdd, 0x03, 0x32, 0xc7, 0x5e, 0x8d, 0xe3,
208        0x16, 0x04, 0x39, 0xf2, 0x03, 0x06, 0x24, 0x5a, 0xe9, 0xeb, 0xe2, 0x64, 0x04, 0x80, 0x01,
209        0xf5, 0x09, 0xde, 0xe9, 0x37, 0x47, 0x4e, 0x1f, 0x9f, 0x41, 0xa9, 0xc5, 0x20, 0x55, 0xaf,
210        0x45, 0x29, 0x3d, 0xc0, 0x37, 0xd8, 0x0f, 0x2c, 0x1f, 0x6a, 0x27, 0x92, 0xd3, 0x53, 0x68,
211        0x50, 0x12, 0xbc, 0x48, 0xf7, 0xd9, 0x44, 0xf8, 0x65, 0x95, 0x29, 0xbf, 0x66, 0x09, 0x2d,
212        0xa4, 0xcc, 0x98, 0x04, 0xa2, 0x15, 0xf7, 0xf7, 0xf7, 0xff, 0xa2, 0x9d, 0xa3, 0xbf, 0xc5,
213        0x89, 0x76, 0xce, 0x58, 0x09, 0x4c, 0xba, 0x08, 0x8a, 0xf0, 0xdc, 0xf5, 0xf2, 0x8c, 0x0c,
214        0x1e, 0xed, 0x68, 0xb8, 0x68, 0x9b, 0x7c, 0xa8, 0x29, 0x1c, 0x28, 0x1a, 0xb8, 0x2c, 0x2c,
215        0xe6, 0x4f, 0xa1, 0x3a, 0xd8, 0x37, 0x10, 0xc6, 0x2a, 0x30, 0x0b, 0x7e, 0x42, 0xb6, 0x82,
216        0x6e, 0x0e, 0xfe, 0x30, 0x2d, 0x24, 0x27, 0x16, 0xb2, 0xd9, 0x5f, 0x8b, 0xde, 0x07, 0x7e,
217        0xe3, 0x5a, 0x4d, 0x8c, 0xa5, 0x47, 0x98, 0x6c, 0x13, 0x31, 0x2a, 0x3d, 0x5b, 0x87, 0x65,
218        0xc4, 0x0c, 0x3b, 0x73, 0x35, 0x91, 0x99, 0xf6, 0x05, 0x49, 0xa9, 0x80, 0x7d, 0xa0, 0x89,
219        0x5c, 0x21, 0x4f, 0x77, 0xf6, 0x4b, 0xfa, 0x96, 0x3f, 0xd3, 0x56, 0xf5, 0x66, 0xf6, 0x5b,
220        0x3f, 0x9a, 0x52, 0xe2, 0xe9, 0xeb, 0xdc, 0xf4, 0x29, 0x54, 0x60, 0x8a, 0xa2, 0x2f, 0x7c,
221        0x96, 0x59, 0xcb, 0x2c, 0x2b, 0xa7, 0xc4, 0x78, 0x56, 0x10, 0x07, 0x06, 0xfb, 0x8f, 0x3e,
222        0xfe, 0x08, 0x40, 0x82, 0x91, 0x04, 0x2e, 0xdc, 0xdb, 0x37, 0xc1, 0xf8, 0xc9, 0x1d, 0x82,
223        0x00, 0xcd, 0x7b, 0xfb, 0xea, 0x75, 0xf8, 0x4c, 0x01, 0xb6, 0x47, 0xea, 0x53, 0x57, 0x05,
224        0x6d, 0x16, 0xba, 0xf5, 0xfa, 0xb8, 0xa4, 0xb2, 0xf5, 0x6e, 0x5f, 0x3f, 0xfb, 0xfd, 0xf4,
225        0x51, 0x66, 0x66, 0xde, 0x0e, 0x68, 0x0e, 0x06, 0xd6, 0xd2, 0x7d, 0x46, 0x5c, 0xde, 0x83,
226        0x55, 0xc6, 0x19, 0x52, 0x7a, 0x56, 0x5c, 0x62, 0x64, 0xb7, 0xfc, 0x8a, 0x49, 0x17, 0xd6,
227        0x4c, 0xab, 0x44, 0xff, 0x36, 0x8a, 0x69, 0xf9, 0x89, 0x6a, 0x06, 0x48, 0xf2, 0x73, 0xe8,
228        0x3c, 0x31, 0xe2, 0x2b, 0x61, 0x73, 0x9f, 0x7e, 0xbc, 0x0a, 0x9b, 0x0f, 0x88, 0x3b, 0xbb,
229        0x60, 0x5d, 0x00, 0xf7, 0x80, 0xf6, 0x90, 0x1f, 0x52, 0x8d, 0x27, 0xda, 0xcc, 0xaa, 0x09,
230        0x82, 0xb2, 0xc0, 0xd3, 0x53, 0xf6, 0x0f, 0xed, 0x3b, 0x34, 0x62, 0x9a, 0xcf, 0x0f, 0x02,
231        0xed, 0x0c, 0xba, 0xf5, 0x44, 0x1a, 0x90, 0x51, 0x5f, 0x53, 0x8f, 0x3f, 0xf8, 0xbe, 0xd2,
232        0x76, 0x30, 0xa2, 0xf0, 0xf2, 0x80, 0xc1, 0x83, 0xba, 0x03, 0x09, 0xbf, 0xe1, 0x37, 0xfc,
233        0x13, 0x30, 0xd4, 0x9c, 0xa4, 0xd0, 0x68, 0x30, 0x07, 0x94, 0x96, 0x29, 0x4f, 0xec, 0x00,
234        0xdd, 0x6a, 0x94, 0xd3, 0x8a, 0xf9, 0x13, 0x9d, 0xfe, 0x1d, 0x5e, 0x6a, 0x56, 0xdf, 0x05,
235        0xe9, 0xf2, 0x5c, 0x54, 0xfe, 0xcd, 0x1c, 0x67, 0x93, 0x9e, 0x04, 0x72, 0x14, 0xff, 0xb4,
236        0xef, 0xf7, 0xff, 0xec, 0xed, 0xec, 0xce, 0x8e, 0xca, 0xcd, 0x5b, 0xb0, 0xa1, 0x2f, 0xc6,
237        0xc4, 0x47, 0x5b, 0xa5, 0x0d, 0xfe, 0x38, 0x28, 0xc2, 0x50, 0x25, 0xa6, 0x37, 0xc4, 0x90,
238        0x43, 0x39, 0x2b, 0x6a, 0x14, 0xa0, 0xab, 0xe8, 0xe0, 0x5c, 0x2d, 0xd1, 0x70, 0x2f, 0x96,
239        0x7f, 0x18, 0x7b, 0x31, 0x20, 0xb3, 0x20, 0xd0, 0xd9, 0x9b, 0xac, 0x09, 0xb0, 0x5c, 0x48,
240        0x88, 0xcf, 0x34, 0xa3, 0x30, 0xab, 0x15, 0x88, 0xf0, 0x41, 0xd6, 0xdc, 0x11, 0xa2, 0xf4,
241        0x46, 0x74, 0x82, 0x92, 0x41, 0xdb, 0x1c, 0x94, 0xc7, 0x88, 0x6f, 0x0b, 0x74, 0x1a, 0xf5,
242        0x20, 0x79, 0x9f, 0x2d, 0x0a, 0xc0, 0x1a, 0x29, 0xb1, 0x52, 0x3c, 0x6f, 0xd5, 0x26, 0x72,
243        0xf7, 0x34, 0xf1, 0xbe, 0x07, 0xad, 0xae, 0xb6, 0xcf, 0x00, 0xbd, 0x86, 0x44, 0x2d, 0xf6,
244        0xe3, 0xfc, 0x4a, 0x60, 0x8f, 0xe0, 0x29, 0x4b, 0x4f, 0x66, 0x00, 0xba, 0x66, 0xac, 0xf9,
245        0x71, 0xe6, 0xe5, 0x92, 0x4b, 0x29, 0x5b, 0xc6, 0x30, 0xab, 0x0e, 0x05, 0xdd, 0x10, 0xee,
246        0x9c, 0x49, 0x49, 0xcf, 0x9f, 0xe5, 0x21, 0xff, 0xe0, 0xb0, 0xfb, 0xfa, 0x75, 0x24, 0x5a,
247        0x9b, 0x44, 0xa2, 0xb1, 0xf3, 0xad, 0x06, 0x0e, 0x18, 0xb0, 0xf3, 0xfe, 0xf6, 0x6b, 0x69,
248        0xb2, 0xdf, 0x8c, 0xbd, 0x6f, 0xfa, 0x92, 0x8b, 0xa2, 0xbe, 0x87, 0x87, 0x8b, 0x93, 0xbd,
249        0x80, 0x8a, 0x81, 0x81, 0x89, 0xe4, 0x95, 0x8b, 0x80, 0x44, 0x83, 0x59, 0x97, 0x49, 0x8a,
250        0xa9, 0xa6, 0x87, 0x82, 0xbb, 0x9c, 0x45, 0xc7, 0x39, 0xc1, 0x5f, 0xf0, 0x0d, 0x4f, 0x44,
251        0x33, 0xfd, 0x7b, 0x0b, 0xe9, 0xe3, 0xb4, 0xf8, 0x0b, 0x31, 0xfc, 0xc9, 0x32, 0x70, 0x28,
252        0xba, 0x09, 0xd5, 0x88, 0x2c, 0x0e, 0x18, 0xe6, 0xf5, 0x1a, 0x0d, 0x7d, 0xef, 0x00, 0x88,
253        0xc4, 0xff, 0x6a, 0x26, 0xa6, 0x16, 0x56, 0x4f, 0x89, 0x90, 0x94, 0x18, 0x89, 0x79, 0x2e,
254        0x84, 0x70, 0x4f, 0x8a, 0x30, 0x9f, 0xf8, 0x9f, 0x1c, 0x02, 0xe2, 0xb1, 0x76, 0x92, 0x33,
255        0x07, 0xa1, 0x80, 0x6a, 0xd3, 0x12, 0x54, 0x8e, 0x2b, 0x69, 0xd9, 0xfb, 0x71, 0xa4, 0x74,
256        0x64, 0xb9, 0xc5, 0x0f, 0xc4, 0xb3, 0x39, 0x68, 0x52, 0x50, 0x02, 0x69, 0x4e, 0xac, 0x44,
257        0x3c, 0x4b, 0x8b, 0x7b, 0x2e, 0x4d, 0x9a, 0x5a, 0x40, 0x1e, 0x36, 0x76, 0x64, 0x62, 0x0e,
258        0xac, 0x57, 0x07, 0x6e, 0xc4, 0x9c, 0x77, 0xfe, 0x85, 0x95, 0x0a, 0x99, 0x6e, 0x4c, 0xe6,
259        0xdf, 0x18, 0x07, 0xc6, 0x16, 0xf5, 0xf1, 0xb0, 0xed, 0xf0, 0xff, 0xa7, 0xf3, 0x98, 0x53,
260        0x9c, 0xff, 0x8a, 0x01, 0xda, 0x07, 0x39, 0x70, 0x98, 0xe8, 0xbb, 0xa6, 0xab, 0x5b, 0x7e,
261        0xc8, 0xf0, 0x79, 0x45, 0x82, 0x2b, 0xbd, 0xc3, 0xca, 0xba, 0xd6, 0x3e, 0x1e, 0xd8, 0x7a,
262        0xae, 0xe7, 0xc2, 0x15, 0x8e, 0x27, 0x05, 0x83, 0x32, 0x7d, 0xf4, 0xfa, 0x9f, 0x30, 0xa9,
263        0x02, 0xff, 0xc9, 0xa4, 0x54, 0x0c, 0x1b, 0x25, 0x30, 0x55, 0xc3, 0x5d, 0xbc, 0xc8, 0xd8,
264        0xf7, 0xb6, 0xff, 0xfa, 0x4d, 0xfa, 0x9f, 0x79, 0x1e, 0xd0, 0xce, 0xb5, 0xa5, 0x77, 0x87,
265        0x73, 0x68, 0xab, 0x98, 0x20, 0x43, 0xca, 0xa9, 0xd9, 0x17, 0x43, 0xde, 0x0d, 0x1b, 0xd5,
266        0xf5, 0x1e, 0x88, 0x96, 0x3f, 0x54, 0xc4, 0x00, 0x8f, 0x9c, 0xc2, 0xff, 0x02, 0x15, 0x90,
267        0x58, 0x19, 0x05, 0x4e, 0xd4, 0x01, 0xcc, 0x5f, 0x33, 0xc3, 0xee, 0x78, 0x71, 0x5c, 0xcb,
268        0x73, 0xe5, 0xea, 0xa3, 0x02, 0x47, 0x00, 0xfc, 0x2d, 0x56, 0x4d, 0xc4, 0x36, 0xa6, 0x2f,
269        0x98, 0x68, 0x15, 0xbe, 0x0e, 0xa1, 0x8a, 0xd2, 0x48, 0x17, 0x63, 0x0a, 0x2e, 0xb8, 0x09,
270        0x17, 0x63, 0x5f, 0xff, 0xfb, 0xd0, 0x04, 0xea, 0x7e, 0x41, 0x30, 0xbf,
271    ];
272
273    /// The property that makes `ferrox quantize --type q5_k_m`'s output
274    /// a file llama.cpp would have written, rather than one that merely
275    /// decodes to similar numbers.
276    #[test]
277    fn q5_k_matches_llama_cpp_quantize_row_q5_k_ref() {
278        let x = k_quant_fixture();
279        let mut got = Vec::new();
280        encode_row_q5_k(&x, &mut got).unwrap();
281        assert_eq!(got.len(), LLAMA_CPP_Q5_K_GOLDEN.len());
282        for (b, (g, w)) in got
283            .as_chunks::<Q5_K_BLOCK_BYTES>()
284            .0
285            .iter()
286            .zip(LLAMA_CPP_Q5_K_GOLDEN.as_chunks::<Q5_K_BLOCK_BYTES>().0)
287            .enumerate()
288        {
289            assert_eq!(g, w, "super-block {b} disagrees with llama.cpp");
290        }
291    }
292
293    /// A row that is not a whole number of super-blocks is refused, not
294    /// padded. llama.cpp answers this case by changing the tensor's
295    /// TYPE (Q5_K -> Q5_1 -> F16); ferrox has neither encoder, and
296    /// padding would shift every following row on decode.
297    #[test]
298    fn a_row_that_is_not_a_whole_number_of_super_blocks_is_refused() {
299        let mut out = Vec::new();
300        assert!(encode_row_q5_k(&[0.5; Q5_K_BLOCK_ELEMS + 1], &mut out).is_none());
301        // 32 is a Q8_0 block and a Q5_K sub-block, and still not a
302        // Q5_K row: the block size that matters here is 256.
303        assert!(encode_row_q5_k(&[0.5; 32], &mut out).is_none());
304        assert!(encode_row_q5_k(&[], &mut out).is_some());
305    }
306
307    /// Round trip through this crate's own reader, against an exact
308    /// property rather than a tolerance: for every element, **no
309    /// representable level is strictly closer** than the one the
310    /// encoder chose.
311    ///
312    /// A tolerance would have to be invented, and an invented tolerance
313    /// is what this whole issue exists to avoid. This is a fact
314    /// instead: stage 3 rounds to the nearest of the 32 levels
315    /// `d*sc*k - dmin*m`, so a fifth bit written into the wrong `qh`
316    /// bit, a nibble packed into the wrong half-byte, or an off-by-one
317    /// in the sub-block stride all move some element off its nearest
318    /// level and turn this red. In particular it is the only test here
319    /// that would catch `m1 <<= 1` in place of `m1 <<= 2`, which the
320    /// golden also catches but which nothing else would explain.
321    #[test]
322    fn every_element_lands_on_its_nearest_representable_level() {
323        let x = k_quant_fixture();
324        let mut bytes = Vec::new();
325        encode_row_q5_k(&x, &mut bytes).unwrap();
326        let back = dequant_q5_k(&bytes).unwrap();
327        assert_eq!(back.len(), x.len());
328
329        for (b, block) in bytes.as_chunks::<Q5_K_BLOCK_BYTES>().0.iter().enumerate() {
330            let d = f16::from_le_bytes([block[0], block[1]]).to_f32();
331            let dmin = f16::from_le_bytes([block[2], block[3]]).to_f32();
332            let packed: [u8; Q4_K_SCALE_BYTES] = block[4..16].try_into().unwrap();
333            for j in 0..8 {
334                let (sc, m) = q4_k_scale_min(j, &packed);
335                let (dj, dm) = (d * sc as f32, dmin * m as f32);
336                for ii in 0..32 {
337                    let idx = b * Q5_K_BLOCK_ELEMS + 32 * j + ii;
338                    let chosen = (x[idx] - back[idx]).abs();
339                    for k in 0..=31u8 {
340                        let level = dj * k as f32 - dm;
341                        assert!(
342                            (x[idx] - level).abs() >= chosen,
343                            "block {b} sub-block {j} element {ii}: {} is closer to {} than to the \
344                             chosen {}",
345                            x[idx],
346                            level,
347                            back[idx]
348                        );
349                    }
350                }
351            }
352        }
353    }
354
355    /// The fifth bit is actually used. A Q5_K encoder that packed only
356    /// the low nibble would still round-trip within Q4_K's error and
357    /// would pass any tolerance test; what it would NOT do is set a
358    /// single bit in `qh`.
359    #[test]
360    fn the_high_bit_plane_is_not_all_zero() {
361        let x = k_quant_fixture();
362        let mut bytes = Vec::new();
363        encode_row_q5_k(&x, &mut bytes).unwrap();
364        for (b, block) in bytes.as_chunks::<Q5_K_BLOCK_BYTES>().0.iter().enumerate() {
365            let qh = &block[16..16 + QH_BYTES];
366            // Super-block 2 of the fixture is the all-zero / constant
367            // one, whose codes are legitimately all low.
368            if b == 2 {
369                continue;
370            }
371            assert!(
372                qh.iter().any(|&v| v != 0),
373                "super-block {b} has an empty qh"
374            );
375        }
376    }
377}