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