Skip to main content

ferrox_quant/
lib.rs

1//! ferrox-quant: dequantization kernels for the block-quantized tensor
2//! formats used by GGUF files (Q4_0, Q8_0, Q4_K, Q5_K, Q6_K).
3//!
4//! These block layouts are a public, widely documented convention
5//! (originated in ggml). The functions here are independent
6//! implementations written against that public layout description, not
7//! copied from any other project's source. Q4_K and Q6_K in particular
8//! are the dominant real-world GGUF quantization formats (most
9//! published checkpoints ship as Q4_K_M or similar K-quant mixes, not
10//! the legacy Q4_0/Q8_0 formats). These are checked against independent
11//! Python cross-validation, following the same discipline as
12//! `ferrox-models`'
13//! GGUF-roundtrip tests.
14
15pub mod encode;
16pub use encode::q4_k::{encode_block_q4_k, encode_row_q4_k};
17pub use encode::q5_k::{encode_block_q5_k, encode_row_q5_k};
18pub use encode::q6_k::{encode_block_q6_k, encode_row_q6_k, probe_q6_k_group};
19pub use encode::{encode_block_q8_0, encode_row_q8_0};
20
21pub mod iq4_xs_q8;
22/// The base-3 trit formats, ggml's `TQ1_0` and PrismML's `PTQ1_0`.
23pub mod ternary;
24pub use iq4_xs_q8::{dot_iq4_xs_q8_k, dot_iq4_xs_q8_k_scalar};
25pub mod iq_tables;
26/// ggml-produced golden vectors for the IQ2_XS/IQ2_S/IQ3_S/IQ1_M
27/// kernels. Test-only: a ~60 KB data blob has no business in a release
28/// build, and nothing outside the tests reads it.
29#[cfg(test)]
30mod iq_tier_goldens;
31pub mod repack;
32
33pub use repack::{
34    batch_gemm_is_accelerated, gemm_q4_0x4_group, gemm_q4_0x4_group_x4, gemm_q4_0x4_group_x4_on,
35    gemm_q4_kx8_group, gemm_q4_kx8_group_x4, gemm_q4_kx8_group_x4_on, gemm_q5_kx8_group,
36    gemm_q5_kx8_group_x4, gemm_q5_kx8_group_x4_on, gemm_q6_kx8_group, gemm_q6_kx8_group_x4,
37    gemm_q6_kx8_group_x4_on, gemm_q8_0x4_group, gemm_q8_0x4_group_x4, gemm_q8_0x4_group_x4_on,
38    gemv_q4_0x4_group, gemv_q4_kx8_group, gemv_q4_kx8_q8_k, gemv_q5_kx8_group, gemv_q5_kx8_q8_k,
39    gemv_q6_kx8_group, gemv_q6_kx8_q8_k, gemv_q8_0x4_group, gemv_q8_0x4_q8_0,
40    interleaved_gemm_is_accelerated, make_block_q4_0x4, make_block_q4_kx8, make_block_q5_kx8,
41    make_block_q6_kx8, make_block_q8_0x4, pack_q4_0_matrix_x4, pack_q4_k_matrix_x8,
42    pack_q5_k_matrix_x8, pack_q6_k_matrix_x8, pack_q8_0_matrix_x4, preferred_interleave,
43    prepare_q8_acts_x4, prepare_q8_k_acts_x4, q4_0x4_gemm_uses_acts_x4, q4_0x4_interleave,
44    q4_kx8_gemm_uses_acts_x4, q4_kx8_interleave, q5_kx8_gemm_uses_acts_x4, q5_kx8_interleave,
45    q6_kx8_gemm_uses_acts_x4, q6_kx8_interleave, q8_0x4_gemm_uses_acts_x4, q8_0x4_interleave,
46    AccelX4, Q8ActsX4, Q8KActsX4, Q4_0X4_BLOCK_BYTES, Q4_0X4_GEMM_NC, Q4_0X4_INTERLEAVE,
47    Q4_0X4_NROWS, Q4_KX8_BLOCK_BYTES, Q4_KX8_GEMM_NC, Q4_KX8_NROWS, Q5_KX8_BLOCK_BYTES,
48    Q5_KX8_GEMM_NC, Q5_KX8_NROWS, Q6_KX8_BLOCK_BYTES, Q6_KX8_GEMM_NC, Q6_KX8_NROWS, Q8K_ACTS_X4_NC,
49    Q8_0X4_BLOCK_BYTES, Q8_0X4_GEMM_NC, Q8_0X4_INTERLEAVE, Q8_0X4_NROWS,
50};
51
52use half::f16;
53
54/// Q8_0: 32 int8 values sharing one f16 scale. 34 bytes per block.
55pub const Q8_0_BLOCK_BYTES: usize = 34;
56pub const Q8_0_BLOCK_ELEMS: usize = 32;
57
58/// Q4_0: 32 packed 4-bit values (16 bytes) sharing one f16 scale. 18 bytes per block.
59pub const Q4_0_BLOCK_BYTES: usize = 18;
60pub const Q4_0_BLOCK_ELEMS: usize = 32;
61
62/// Q4_1: like Q4_0 but asymmetric -- an f16 scale `d` *and* an f16 min
63/// `m` (value = `q*d + m`, no `-8` bias), 32 packed 4-bit values.
64/// Layout: d(2) + m(2) + qs(16) = 20 bytes. Verified against real
65/// `ggml-common.h`/`ggml-quants.c` source, not guessed.
66pub const Q4_1_BLOCK_BYTES: usize = 20;
67pub const Q4_1_BLOCK_ELEMS: usize = 32;
68
69/// Q5_0: like Q4_0 (single f16 scale `d`, symmetric `-16` bias) but
70/// each element gets a 5th bit from a 4-byte `qh` bitplane. Layout:
71/// d(2) + qh(4) + qs(16) = 22 bytes.
72pub const Q5_0_BLOCK_BYTES: usize = 22;
73pub const Q5_0_BLOCK_ELEMS: usize = 32;
74
75/// Q5_1: Q5_0's 5th-bit scheme combined with Q4_1's asymmetric `d`+`m`
76/// (no bias subtraction). Layout: d(2) + m(2) + qh(4) + qs(16) = 24
77/// bytes.
78pub const Q5_1_BLOCK_BYTES: usize = 24;
79pub const Q5_1_BLOCK_ELEMS: usize = 32;
80
81/// Q8_1: like Q8_0 (32 signed 8-bit values, one f16 scale `d`) plus an
82/// extra f16 field `s` that upstream ggml uses only as a precomputed
83/// per-block sum for its own fused SIMD dot-product kernels -- not
84/// needed for correct dequantization, since `y = qs*d` is unaffected
85/// by it. Layout: d(2) + s(2) + qs(32) = 36 bytes.
86pub const Q8_1_BLOCK_BYTES: usize = 36;
87pub const Q8_1_BLOCK_ELEMS: usize = 32;
88
89/// Metal `FERROX_CTK=turbo4` KV block: 32 elems → f16 scale + 16 nibble bytes.
90pub const TURBO4_KV_GROUP: usize = 32;
91pub const TURBO4_KV_BLOCK_BYTES: usize = 18;
92
93/// Metal `FERROX_CTK=fp8` KV block: 32 elems → f16 scale + 32 E4M3-ish bytes.
94/// Codes are absmax-scaled int8 in [-127,127] (portable stand-in for E4M3).
95pub const FP8_KV_GROUP: usize = 32;
96pub const FP8_KV_BLOCK_BYTES: usize = 34;
97
98/// Pack f32 into Metal turbo4 KV blocks (no WHT).
99pub fn pack_turbo4_kv_blocks(x: &[f32]) -> Vec<u8> {
100    assert_eq!(x.len() % TURBO4_KV_GROUP, 0);
101    let n_blocks = x.len() / TURBO4_KV_GROUP;
102    let mut out = vec![0u8; n_blocks * TURBO4_KV_BLOCK_BYTES];
103    for b in 0..n_blocks {
104        let chunk = &x[b * TURBO4_KV_GROUP..(b + 1) * TURBO4_KV_GROUP];
105        let amax = chunk.iter().fold(0f32, |m, &v| m.max(v.abs()));
106        let scale = if amax > 0.0 { amax / 7.0 } else { 0.0 };
107        let inv = if scale > 0.0 { 1.0 / scale } else { 0.0 };
108        let bits = f16::from_f32(scale).to_le_bytes();
109        let dst = &mut out[b * TURBO4_KV_BLOCK_BYTES..(b + 1) * TURBO4_KV_BLOCK_BYTES];
110        dst[0] = bits[0];
111        dst[1] = bits[1];
112        for i in 0..16 {
113            let q0 = (chunk[i * 2] * inv).round().clamp(-8.0, 7.0) as i8;
114            let q1 = (chunk[i * 2 + 1] * inv).round().clamp(-8.0, 7.0) as i8;
115            dst[2 + i] = ((q0 as u8) & 0x0f) | (((q1 as u8) & 0x0f) << 4);
116        }
117    }
118    out
119}
120
121/// Unpack [`pack_turbo4_kv_blocks`].
122pub fn unpack_turbo4_kv_blocks(bytes: &[u8]) -> Result<Vec<f32>, QuantError> {
123    if !bytes.len().is_multiple_of(TURBO4_KV_BLOCK_BYTES) {
124        return Err(QuantError::Misaligned(bytes.len(), TURBO4_KV_BLOCK_BYTES));
125    }
126    let n_blocks = bytes.len() / TURBO4_KV_BLOCK_BYTES;
127    let mut out = Vec::with_capacity(n_blocks * TURBO4_KV_GROUP);
128    for b in 0..n_blocks {
129        let block = &bytes[b * TURBO4_KV_BLOCK_BYTES..(b + 1) * TURBO4_KV_BLOCK_BYTES];
130        let scale = f16::from_le_bytes([block[0], block[1]]).to_f32();
131        for i in 0..16 {
132            let byte = block[2 + i];
133            let q0 = ((byte & 0x0f) as i8) << 4 >> 4;
134            let q1 = ((byte >> 4) as i8) << 4 >> 4;
135            out.push(q0 as f32 * scale);
136            out.push(q1 as f32 * scale);
137        }
138    }
139    Ok(out)
140}
141
142/// Pack f32 into Metal fp8-style KV blocks (scaled int8, Q8_0-compatible layout).
143pub fn pack_fp8_kv_blocks(x: &[f32]) -> Vec<u8> {
144    // Same wire layout as Q8_0 — reuse for host upload/download.
145    quantize_q8_0(x)
146}
147
148/// Unpack [`pack_fp8_kv_blocks`].
149pub fn unpack_fp8_kv_blocks(bytes: &[u8]) -> Result<Vec<f32>, QuantError> {
150    dequant_q8_0(bytes)
151}
152
153/// Q4_K: a 256-element super-block, split into 8 32-element sub-blocks,
154/// each with its own 6-bit scale and 6-bit min (packed into 12 bytes),
155/// plus one shared f16 scale-of-scales `d` and scale-of-mins `dmin`.
156/// Layout: d(2) + dmin(2) + scales(12) + qs(128) = 144 bytes.
157pub const Q4_K_BLOCK_BYTES: usize = 144;
158pub const Q4_K_BLOCK_ELEMS: usize = 256;
159const Q4_K_SCALE_BYTES: usize = 12;
160
161/// Q5_K: the same 8-sub-blocks-of-32 / 6-bit-scale-and-min layout as
162/// Q4_K (same 12-byte packed scales, same unpacking), but each element
163/// gets a 5th bit from a separate 32-byte `qh` bitplane (one bit per
164/// element, 256 bits total) instead of Q4_K's plain 4-bit nibble.
165/// Layout: d(2) + dmin(2) + scales(12) + qh(32) + qs(128) = 176 bytes.
166pub const Q5_K_BLOCK_BYTES: usize = 176;
167pub const Q5_K_BLOCK_ELEMS: usize = 256;
168
169/// Q6_K: a 256-element super-block, split into 16 16-element sub-blocks
170/// each with its own signed 8-bit scale, plus one shared f16
171/// super-block scale `d`. Layout: ql(128) + qh(64) + scales(16) + d(2)
172/// = 210 bytes.
173pub const Q6_K_BLOCK_BYTES: usize = 210;
174pub const Q6_K_BLOCK_ELEMS: usize = 256;
175
176/// Q2_K: a 256-element super-block, 16 sub-blocks of 16, each with its
177/// own 4-bit scale and 4-bit min packed one byte per sub-block (not
178/// Q4_K's cross-byte 6-bit packing -- a real, verified difference, not
179/// assumed), plus one shared f16 super-block scale `d` and f16
180/// super-block min-scale `dmin`. Layout: scales(16) + qs(64) + d(2) +
181/// dmin(2) = 84 bytes -- note `d`/`dmin` come *after* `scales`/`qs`,
182/// the opposite field order from every other K-quant format here,
183/// verified directly against real `ggml-common.h`/`ggml-quants.c`
184/// source (`block_q2_K`, `dequantize_row_q2_K`).
185pub const Q2_K_BLOCK_BYTES: usize = 84;
186pub const Q2_K_BLOCK_ELEMS: usize = 256;
187const Q2_K_SCALE_BYTES: usize = 16;
188
189/// Q3_K: a 256-element super-block, 16 sub-blocks of 16, each with its
190/// own signed 6-bit scale (packed via a byte-wise interleaving scheme
191/// across 12 bytes, verified against `dequantize_row_q3_K`'s real
192/// `aux[]` unpacking -- see `q3_k_unpack_scales`'s doc comment), a
193/// 3-bit value per element (2 low bits from `qs`, 1 high bit from
194/// `hmask`, centered by `-4` when the high bit is *clear*), scaled by
195/// one shared f16 `d`. Layout: hmask(32) + qs(64) + scales(12) + d(2)
196/// = 110 bytes.
197pub const Q3_K_BLOCK_BYTES: usize = 110;
198pub const Q3_K_BLOCK_ELEMS: usize = 256;
199const Q3_K_SCALE_BYTES: usize = 12;
200
201#[derive(Debug, thiserror::Error)]
202pub enum QuantError {
203    #[error("buffer length {0} is not a multiple of the block size {1}")]
204    Misaligned(usize, usize),
205    #[error("MXFP4 packed buffer is {0} bytes but scales buffer implies {1} bytes ({1} = scales.len() * MXFP4_GROUP_SIZE / 2)")]
206    Mxfp4RowMismatch(usize, usize),
207}
208
209/// BF16 isn't a block-quantized format at all -- it's IEEE-754 binary32
210/// truncated to its sign bit + 8 exponent bits + 7 mantissa bits (the
211/// upper 16 bits of an f32), so widening it back to f32 is an exact,
212/// lossless bit shift: `f32::from_bits((bits as u32) << 16)`, zero-
213/// padding the low 16 mantissa bits rather than any real
214/// dequantization math. Included here anyway (rather than as a one-off
215/// in `ferrox-models::loader`) so every real element type ferrox
216/// recognizes has one obvious home.
217pub fn dequant_bf16(src: &[u8]) -> Result<Vec<f32>, QuantError> {
218    if !src.len().is_multiple_of(2) {
219        return Err(QuantError::Misaligned(src.len(), 2));
220    }
221    Ok(src
222        .as_chunks::<2>()
223        .0
224        .iter()
225        .map(|c| f32::from_bits((u16::from_le_bytes([c[0], c[1]]) as u32) << 16))
226        .collect())
227}
228
229/// F16 (IEEE-754 binary16) widened to f32. Like [`dequant_bf16`] this is
230/// a plain element type, not a block format: every f16 value is exactly
231/// representable in f32, so the widening is lossless. `GgmlType::F16` is
232/// what `llama-quantize --pure`-free conversions and every `*-f16.gguf`
233/// carry, and it is also the dtype ggml uses for `token_embd` in some
234/// mixed checkpoints.
235pub fn dequant_f16(src: &[u8]) -> Result<Vec<f32>, QuantError> {
236    if !src.len().is_multiple_of(2) {
237        return Err(QuantError::Misaligned(src.len(), 2));
238    }
239    Ok(src
240        .as_chunks::<2>()
241        .0
242        .iter()
243        .map(|c| f16::from_le_bytes([c[0], c[1]]).to_f32())
244        .collect())
245}
246
247/// Dequantize a Q8_0 buffer into f32.
248pub fn dequant_q8_0(src: &[u8]) -> Result<Vec<f32>, QuantError> {
249    if !src.len().is_multiple_of(Q8_0_BLOCK_BYTES) {
250        return Err(QuantError::Misaligned(src.len(), Q8_0_BLOCK_BYTES));
251    }
252    let n_blocks = src.len() / Q8_0_BLOCK_BYTES;
253    let mut out = Vec::with_capacity(n_blocks * Q8_0_BLOCK_ELEMS);
254    for b in 0..n_blocks {
255        let block = &src[b * Q8_0_BLOCK_BYTES..(b + 1) * Q8_0_BLOCK_BYTES];
256        let scale = f16::from_le_bytes([block[0], block[1]]).to_f32();
257        for i in 0..Q8_0_BLOCK_ELEMS {
258            let q = block[2 + i] as i8;
259            out.push(q as f32 * scale);
260        }
261    }
262    Ok(out)
263}
264
265/// Dequantize a Q4_0 buffer into f32. Each byte packs two 4-bit nibbles
266/// (low nibble = element i, high nibble = element i+16), each nibble
267/// biased by -8 before scaling, matching the public Q4_0 convention.
268pub fn dequant_q4_0(src: &[u8]) -> Result<Vec<f32>, QuantError> {
269    if !src.len().is_multiple_of(Q4_0_BLOCK_BYTES) {
270        return Err(QuantError::Misaligned(src.len(), Q4_0_BLOCK_BYTES));
271    }
272    let n_blocks = src.len() / Q4_0_BLOCK_BYTES;
273    let mut out = vec![0f32; n_blocks * Q4_0_BLOCK_ELEMS];
274    for b in 0..n_blocks {
275        let block = &src[b * Q4_0_BLOCK_BYTES..(b + 1) * Q4_0_BLOCK_BYTES];
276        let scale = f16::from_le_bytes([block[0], block[1]]).to_f32();
277        let nibbles = &block[2..18];
278        let base = b * Q4_0_BLOCK_ELEMS;
279        for i in 0..16 {
280            let byte = nibbles[i];
281            let lo = (byte & 0x0F) as i32 - 8;
282            let hi = ((byte >> 4) & 0x0F) as i32 - 8;
283            out[base + i] = lo as f32 * scale;
284            out[base + i + 16] = hi as f32 * scale;
285        }
286    }
287    Ok(out)
288}
289
290/// Unpacks one Q4_K super-block's 8 (scale, min) pairs from its 12-byte
291/// packed `scales` field. ggml packs these as 6-bit values using a
292/// scheme where the first 4 sub-blocks store their scale/min directly
293/// in the low 6 bits of `scales[0..4]`/`scales[4..8]`, and the last 4
294/// borrow their low 4 bits from `scales[4..8]`'s high nibble and their
295/// high 2 bits from `scales[0..4]`'s top bits -- packing 8 six-bit
296/// scales and 8 six-bit mins (96 bits total) into 12 bytes without
297/// wasting any padding bits.
298fn q4_k_scale_min(j: usize, scales: &[u8; Q4_K_SCALE_BYTES]) -> (u8, u8) {
299    if j < 4 {
300        (scales[j] & 63, scales[j + 4] & 63)
301    } else {
302        (
303            (scales[j + 4] & 0x0F) | ((scales[j - 4] >> 6) << 4),
304            (scales[j + 4] >> 4) | ((scales[j] >> 6) << 4),
305        )
306    }
307}
308
309/// Dequantize a Q4_K buffer into f32. See the module doc comment and
310/// `Q4_K_BLOCK_BYTES` for the block layout.
311pub fn dequant_q4_k(src: &[u8]) -> Result<Vec<f32>, QuantError> {
312    if !src.len().is_multiple_of(Q4_K_BLOCK_BYTES) {
313        return Err(QuantError::Misaligned(src.len(), Q4_K_BLOCK_BYTES));
314    }
315    let n_blocks = src.len() / Q4_K_BLOCK_BYTES;
316    let mut out = Vec::with_capacity(n_blocks * Q4_K_BLOCK_ELEMS);
317    for block in src.as_chunks::<Q4_K_BLOCK_BYTES>().0 {
318        let d = f16::from_le_bytes([block[0], block[1]]).to_f32();
319        let dmin = f16::from_le_bytes([block[2], block[3]]).to_f32();
320        let scales: [u8; Q4_K_SCALE_BYTES] = block[4..16].try_into().unwrap();
321        let qs = &block[16..144];
322
323        let mut is = 0usize;
324        let mut q_off = 0usize;
325        for _ in 0..4 {
326            let (sc1, m1) = q4_k_scale_min(is, &scales);
327            let (sc2, m2) = q4_k_scale_min(is + 1, &scales);
328            let (d1, min1) = (d * sc1 as f32, dmin * m1 as f32);
329            let (d2, min2) = (d * sc2 as f32, dmin * m2 as f32);
330            for l in 0..32 {
331                out.push(d1 * (qs[q_off + l] & 0x0F) as f32 - min1);
332            }
333            for l in 0..32 {
334                out.push(d2 * (qs[q_off + l] >> 4) as f32 - min2);
335            }
336            q_off += 32;
337            is += 2;
338        }
339    }
340    Ok(out)
341}
342
343/// Fused Q4_K dequant+dot: identical math to `dequant_q4_k`, but
344/// accumulated directly against `x` instead of materializing a
345/// dequantized row. Dispatches to SIMD when the host CPU supports it,
346/// same mechanism as `dot_q8_0_f32`.
347pub fn dot_q4_k_f32(row_bytes: &[u8], x: &[f32]) -> f32 {
348    #[cfg(target_arch = "x86_64")]
349    {
350        if is_x86_feature_detected!("avx2") && is_x86_feature_detected!("fma") {
351            return unsafe { simd_x86::dot_q4_k_f32_avx2(row_bytes, x) };
352        }
353    }
354    #[cfg(target_arch = "aarch64")]
355    {
356        if std::arch::is_aarch64_feature_detected!("neon") {
357            return unsafe { simd_aarch64::dot_q4_k_f32_neon(row_bytes, x) };
358        }
359    }
360    dot_q4_k_f32_scalar(row_bytes, x)
361}
362
363pub fn dot_q4_k_f32_scalar(row_bytes: &[u8], x: &[f32]) -> f32 {
364    debug_assert_eq!(row_bytes.len() % Q4_K_BLOCK_BYTES, 0);
365    let mut acc = 0f32;
366    let mut base = 0usize;
367    for block in row_bytes.as_chunks::<Q4_K_BLOCK_BYTES>().0 {
368        let d = f16::from_le_bytes([block[0], block[1]]).to_f32();
369        let dmin = f16::from_le_bytes([block[2], block[3]]).to_f32();
370        let scales: [u8; Q4_K_SCALE_BYTES] = block[4..16].try_into().unwrap();
371        let qs = &block[16..144];
372
373        let mut is = 0usize;
374        let mut q_off = 0usize;
375        for _ in 0..4 {
376            let (sc1, m1) = q4_k_scale_min(is, &scales);
377            let (sc2, m2) = q4_k_scale_min(is + 1, &scales);
378            let (d1, min1) = (d * sc1 as f32, dmin * m1 as f32);
379            let (d2, min2) = (d * sc2 as f32, dmin * m2 as f32);
380            for l in 0..32 {
381                acc += (d1 * (qs[q_off + l] & 0x0F) as f32 - min1) * x[base + l];
382            }
383            for l in 0..32 {
384                acc += (d2 * (qs[q_off + l] >> 4) as f32 - min2) * x[base + 32 + l];
385            }
386            q_off += 32;
387            base += 64;
388            is += 2;
389        }
390    }
391    acc
392}
393
394/// Dequantize a Q5_K buffer into f32. See the module doc comment and
395/// `Q5_K_BLOCK_BYTES` for the block layout. Shares Q4_K's scale/min
396/// packing (`q4_k_scale_min`) and 4-outer-iteration structure; the only
397/// difference is each nibble gets a 5th bit from `qh`, whose 32 bytes
398/// are reused across all 4 outer iterations at different bit positions
399/// (`u1`/`u2`, doubling by 4 each iteration) rather than being consumed
400/// sequentially the way `qs` is.
401pub fn dequant_q5_k(src: &[u8]) -> Result<Vec<f32>, QuantError> {
402    if !src.len().is_multiple_of(Q5_K_BLOCK_BYTES) {
403        return Err(QuantError::Misaligned(src.len(), Q5_K_BLOCK_BYTES));
404    }
405    let n_blocks = src.len() / Q5_K_BLOCK_BYTES;
406    let mut out = Vec::with_capacity(n_blocks * Q5_K_BLOCK_ELEMS);
407    for block in src.as_chunks::<Q5_K_BLOCK_BYTES>().0 {
408        let d = f16::from_le_bytes([block[0], block[1]]).to_f32();
409        let dmin = f16::from_le_bytes([block[2], block[3]]).to_f32();
410        let scales: [u8; Q4_K_SCALE_BYTES] = block[4..16].try_into().unwrap();
411        let qh = &block[16..48];
412        let qs = &block[48..176];
413
414        let mut is = 0usize;
415        let (mut u1, mut u2) = (1u8, 2u8);
416        for oi in 0..4 {
417            let (sc1, m1) = q4_k_scale_min(is, &scales);
418            let (sc2, m2) = q4_k_scale_min(is + 1, &scales);
419            let (d1, min1) = (d * sc1 as f32, dmin * m1 as f32);
420            let (d2, min2) = (d * sc2 as f32, dmin * m2 as f32);
421            let ql = &qs[oi * 32..oi * 32 + 32];
422            for l in 0..32 {
423                let hi = if qh[l] & u1 != 0 { 16 } else { 0 };
424                out.push(d1 * ((ql[l] & 0x0F) + hi) as f32 - min1);
425            }
426            for l in 0..32 {
427                let hi = if qh[l] & u2 != 0 { 16 } else { 0 };
428                out.push(d2 * ((ql[l] >> 4) + hi) as f32 - min2);
429            }
430            is += 2;
431            u1 <<= 2;
432            u2 <<= 2;
433        }
434    }
435    Ok(out)
436}
437
438/// Fused Q5_K dequant+dot: identical math to `dequant_q5_k`, but
439/// accumulated directly against `x` instead of materializing a
440/// dequantized row. Dispatches to SIMD when available, same mechanism
441/// as `dot_q8_0_f32`.
442pub fn dot_q5_k_f32(row_bytes: &[u8], x: &[f32]) -> f32 {
443    #[cfg(target_arch = "x86_64")]
444    {
445        if is_x86_feature_detected!("avx2") && is_x86_feature_detected!("fma") {
446            return unsafe { simd_x86::dot_q5_k_f32_avx2(row_bytes, x) };
447        }
448    }
449    #[cfg(target_arch = "aarch64")]
450    {
451        if std::arch::is_aarch64_feature_detected!("neon") {
452            return unsafe { simd_aarch64::dot_q5_k_f32_neon(row_bytes, x) };
453        }
454    }
455    dot_q5_k_f32_scalar(row_bytes, x)
456}
457
458pub fn dot_q5_k_f32_scalar(row_bytes: &[u8], x: &[f32]) -> f32 {
459    debug_assert_eq!(row_bytes.len() % Q5_K_BLOCK_BYTES, 0);
460    let mut acc = 0f32;
461    let mut base = 0usize;
462    for block in row_bytes.as_chunks::<Q5_K_BLOCK_BYTES>().0 {
463        let d = f16::from_le_bytes([block[0], block[1]]).to_f32();
464        let dmin = f16::from_le_bytes([block[2], block[3]]).to_f32();
465        let scales: [u8; Q4_K_SCALE_BYTES] = block[4..16].try_into().unwrap();
466        let qh = &block[16..48];
467        let qs = &block[48..176];
468
469        let mut is = 0usize;
470        let (mut u1, mut u2) = (1u8, 2u8);
471        for oi in 0..4 {
472            let (sc1, m1) = q4_k_scale_min(is, &scales);
473            let (sc2, m2) = q4_k_scale_min(is + 1, &scales);
474            let (d1, min1) = (d * sc1 as f32, dmin * m1 as f32);
475            let (d2, min2) = (d * sc2 as f32, dmin * m2 as f32);
476            let ql = &qs[oi * 32..oi * 32 + 32];
477            for l in 0..32 {
478                let hi = if qh[l] & u1 != 0 { 16 } else { 0 };
479                acc += (d1 * ((ql[l] & 0x0F) + hi) as f32 - min1) * x[base + l];
480            }
481            for l in 0..32 {
482                let hi = if qh[l] & u2 != 0 { 16 } else { 0 };
483                acc += (d2 * ((ql[l] >> 4) + hi) as f32 - min2) * x[base + 32 + l];
484            }
485            base += 64;
486            is += 2;
487            u1 <<= 2;
488            u2 <<= 2;
489        }
490    }
491    acc
492}
493
494/// Dequantize a Q6_K buffer into f32. See the module doc comment and
495/// `Q6_K_BLOCK_BYTES` for the block layout.
496pub fn dequant_q6_k(src: &[u8]) -> Result<Vec<f32>, QuantError> {
497    if !src.len().is_multiple_of(Q6_K_BLOCK_BYTES) {
498        return Err(QuantError::Misaligned(src.len(), Q6_K_BLOCK_BYTES));
499    }
500    let n_blocks = src.len() / Q6_K_BLOCK_BYTES;
501    let mut out = vec![0f32; n_blocks * Q6_K_BLOCK_ELEMS];
502    for (b, block) in src.as_chunks::<Q6_K_BLOCK_BYTES>().0.iter().enumerate() {
503        let ql_full = &block[0..128];
504        let qh_full = &block[128..192];
505        let sc_full = &block[192..208];
506        let d = f16::from_le_bytes([block[208], block[209]]).to_f32();
507        let out_base = b * Q6_K_BLOCK_ELEMS;
508
509        for half in 0..2 {
510            let ql = &ql_full[half * 64..half * 64 + 64];
511            let qh = &qh_full[half * 32..half * 32 + 32];
512            let sc = &sc_full[half * 8..half * 8 + 8];
513            let y = &mut out[out_base + half * 128..out_base + half * 128 + 128];
514
515            for l in 0..32 {
516                let is = l / 16;
517                let q1 = ((ql[l] & 0x0F) | ((qh[l] & 3) << 4)) as i8 - 32;
518                let q2 = ((ql[l + 32] & 0x0F) | (((qh[l] >> 2) & 3) << 4)) as i8 - 32;
519                let q3 = ((ql[l] >> 4) | (((qh[l] >> 4) & 3) << 4)) as i8 - 32;
520                let q4 = ((ql[l + 32] >> 4) | (((qh[l] >> 6) & 3) << 4)) as i8 - 32;
521                y[l] = d * (sc[is] as i8 as f32) * (q1 as f32);
522                y[l + 32] = d * (sc[is + 2] as i8 as f32) * (q2 as f32);
523                y[l + 64] = d * (sc[is + 4] as i8 as f32) * (q3 as f32);
524                y[l + 96] = d * (sc[is + 6] as i8 as f32) * (q4 as f32);
525            }
526        }
527    }
528    Ok(out)
529}
530
531/// Fused Q6_K dequant+dot: identical math to `dequant_q6_k`, but
532/// accumulated directly against `x` instead of materializing a
533/// dequantized row. Dispatches to SIMD when available, same mechanism
534/// as `dot_q8_0_f32`.
535pub fn dot_q6_k_f32(row_bytes: &[u8], x: &[f32]) -> f32 {
536    #[cfg(target_arch = "x86_64")]
537    {
538        if is_x86_feature_detected!("avx2") && is_x86_feature_detected!("fma") {
539            return unsafe { simd_x86::dot_q6_k_f32_avx2(row_bytes, x) };
540        }
541    }
542    #[cfg(target_arch = "aarch64")]
543    {
544        if std::arch::is_aarch64_feature_detected!("neon") {
545            return unsafe { simd_aarch64::dot_q6_k_f32_neon(row_bytes, x) };
546        }
547    }
548    dot_q6_k_f32_scalar(row_bytes, x)
549}
550
551pub fn dot_q6_k_f32_scalar(row_bytes: &[u8], x: &[f32]) -> f32 {
552    debug_assert_eq!(row_bytes.len() % Q6_K_BLOCK_BYTES, 0);
553    let mut acc = 0f32;
554    let mut x_base = 0usize;
555    for block in row_bytes.as_chunks::<Q6_K_BLOCK_BYTES>().0 {
556        let ql_full = &block[0..128];
557        let qh_full = &block[128..192];
558        let sc_full = &block[192..208];
559        let d = f16::from_le_bytes([block[208], block[209]]).to_f32();
560
561        for half in 0..2 {
562            let ql = &ql_full[half * 64..half * 64 + 64];
563            let qh = &qh_full[half * 32..half * 32 + 32];
564            let sc = &sc_full[half * 8..half * 8 + 8];
565            let xh = &x[x_base..x_base + 128];
566
567            for l in 0..32 {
568                let is = l / 16;
569                let q1 = ((ql[l] & 0x0F) | ((qh[l] & 3) << 4)) as i8 - 32;
570                let q2 = ((ql[l + 32] & 0x0F) | (((qh[l] >> 2) & 3) << 4)) as i8 - 32;
571                let q3 = ((ql[l] >> 4) | (((qh[l] >> 4) & 3) << 4)) as i8 - 32;
572                let q4 = ((ql[l + 32] >> 4) | (((qh[l] >> 6) & 3) << 4)) as i8 - 32;
573                acc += d * (sc[is] as i8 as f32) * (q1 as f32) * xh[l];
574                acc += d * (sc[is + 2] as i8 as f32) * (q2 as f32) * xh[l + 32];
575                acc += d * (sc[is + 4] as i8 as f32) * (q3 as f32) * xh[l + 64];
576                acc += d * (sc[is + 6] as i8 as f32) * (q4 as f32) * xh[l + 96];
577            }
578            x_base += 128;
579        }
580    }
581    acc
582}
583
584/// Quantize an f32 slice into Q8_0 blocks, zero-padding a partial
585/// trailing block. Used by test fixtures and by the CPU reference
586/// "quantize activations for a symmetric int8 matmul" path, where the
587/// vector length is not guaranteed to be a whole number of blocks.
588///
589/// The per-block arithmetic is [`encode::encode_block_q8_0`], not a
590/// second spelling of it: this function used to have its own, which
591/// divided by the scale where llama.cpp multiplies by its reciprocal
592/// and stored a scale of 1.0 for an all-zero block where llama.cpp
593/// stores 0.0. Both differences are invisible to a value comparison
594/// and both produce different bytes, which is exactly the kind of
595/// silent divergence a second copy of a code path creates. The tail
596/// padding is the ONLY thing this adds.
597///
598/// A *weight* encoder wants [`encode::encode_row_q8_0`] instead, which
599/// refuses a ragged length rather than padding it: padding a weight row
600/// writes more elements than its shape declares.
601pub fn quantize_q8_0(src: &[f32]) -> Vec<u8> {
602    let mut out = Vec::with_capacity(src.len().div_ceil(Q8_0_BLOCK_ELEMS) * Q8_0_BLOCK_BYTES);
603    for chunk in src.chunks(Q8_0_BLOCK_ELEMS) {
604        let mut block = [0f32; Q8_0_BLOCK_ELEMS];
605        block[..chunk.len()].copy_from_slice(chunk);
606        encode::encode_block_q8_0(&block, &mut out);
607    }
608    out
609}
610
611/// Fused dot product between one Q8_0-quantized row (stored as raw
612/// block bytes) and an f32 activation vector, without ever
613/// materializing a dequantized f32 copy of the row. This is the
614/// memory-bandwidth-saving trick llama.cpp's quantized matmul kernels
615/// rely on: for large weight matrices, bandwidth (not FLOPs) dominates
616/// inference cost, and Q8_0 moves 4x fewer bytes than a dequant-then-
617/// matmul approach that expands every weight to f32 up front.
618///
619/// Dispatches to an AVX2+FMA SIMD kernel at runtime when the host CPU
620/// supports it (checked via `is_x86_feature_detected!`), falling back
621/// to the portable scalar loop
622/// otherwise. Both paths are tested against each other for exact
623/// numerical agreement.
624pub fn dot_q8_0_f32(row_bytes: &[u8], x: &[f32]) -> f32 {
625    #[cfg(target_arch = "x86_64")]
626    {
627        if is_x86_feature_detected!("avx2") && is_x86_feature_detected!("fma") {
628            return unsafe { simd_x86::dot_q8_0_f32_avx2(row_bytes, x) };
629        }
630    }
631    #[cfg(target_arch = "aarch64")]
632    {
633        if std::arch::is_aarch64_feature_detected!("neon") {
634            return unsafe { simd_aarch64::dot_q8_0_f32_neon(row_bytes, x) };
635        }
636    }
637    dot_q8_0_f32_scalar(row_bytes, x)
638}
639
640pub fn dot_q8_0_f32_scalar(row_bytes: &[u8], x: &[f32]) -> f32 {
641    debug_assert_eq!(row_bytes.len() % Q8_0_BLOCK_BYTES, 0);
642    debug_assert_eq!(
643        row_bytes.len() / Q8_0_BLOCK_BYTES * Q8_0_BLOCK_ELEMS,
644        x.len()
645    );
646    let mut acc = 0f32;
647    for (b, block) in row_bytes
648        .as_chunks::<Q8_0_BLOCK_BYTES>()
649        .0
650        .iter()
651        .enumerate()
652    {
653        let scale = f16::from_le_bytes([block[0], block[1]]).to_f32();
654        let base = b * Q8_0_BLOCK_ELEMS;
655        let mut block_acc = 0f32;
656        for i in 0..Q8_0_BLOCK_ELEMS {
657            let q = block[2 + i] as i8;
658            block_acc += (q as f32) * x[base + i];
659        }
660        acc += block_acc * scale;
661    }
662    acc
663}
664
665/// An activation vector quantized to signed 8-bit in 32-element blocks,
666/// each with its own f32 scale (`d`), so it can feed the integer
667/// `vec_dot` paths against Q8_0 weights. This mirrors llama.cpp's
668/// `quantize_row_q8_1` (minus the block sum, which is only needed for
669/// asymmetric weight formats): quantizing the shared activation once per
670/// matvec turns every weight-row dot into an int8×int8 → int32 reduction
671/// (`vdotq_s32` / `_mm256_maddubs`-class ops) plus a single scale, which
672/// is what lets llama.cpp's CPU matmul stay in integer SIMD.
673#[derive(Clone, Debug)]
674pub struct Q8Activations {
675    /// Signed 8-bit quantized values, `n_blocks * 32` long.
676    pub q: Vec<i8>,
677    /// Per-block scale, `n_blocks` long. `x ≈ q * d`.
678    pub d: Vec<f32>,
679}
680
681impl Q8Activations {
682    pub fn n_blocks(&self) -> usize {
683        self.d.len()
684    }
685}
686
687/// ggml `block_q8_K` activations for K-quant int-dot (`Q4_K`/`Q5_K`/`Q6_K`).
688/// Super-blocks of 256 elements with 16-wide `bsums` for the min term.
689#[derive(Clone, Debug)]
690pub struct Q8KActivations {
691    pub q: Vec<i8>,
692    pub d: Vec<f32>,
693    /// Per 16-wide group sums of `q`, `n_blocks * 16` long.
694    pub bsums: Vec<i16>,
695}
696
697impl Q8KActivations {
698    pub fn n_blocks(&self) -> usize {
699        self.d.len()
700    }
701}
702
703/// Quantize activations to ggml `Q8_K` (256-elem super-blocks). Positive
704/// scale convention (`d = amax/127`) matching our `Q8_0` path; `bsums`
705/// enable the Q4_K min correction without re-scanning `q`.
706pub fn quantize_activations_q8_k(x: &[f32]) -> Q8KActivations {
707    debug_assert_eq!(x.len() % Q4_K_BLOCK_ELEMS, 0);
708    let n_blocks = x.len() / Q4_K_BLOCK_ELEMS;
709    let mut q = vec![0i8; n_blocks * Q4_K_BLOCK_ELEMS];
710    let mut d = vec![0f32; n_blocks];
711    let mut bsums = vec![0i16; n_blocks * 16];
712    let quant_one =
713        |(q_slot, d_slot, bsum_slot, chunk): (&mut [i8], &mut f32, &mut [i16], &[f32])| {
714            let amax = chunk.iter().fold(0f32, |m, &v| m.max(v.abs()));
715            let scale = amax / 127.0;
716            let inv = if scale > 0.0 { 1.0 / scale } else { 0.0 };
717            *d_slot = scale;
718            for (i, &v) in chunk.iter().enumerate() {
719                let qi = (v * inv).round();
720                q_slot[i] = qi.clamp(-127.0, 127.0) as i8;
721            }
722            for (slot, group) in bsum_slot.iter_mut().zip(q_slot.as_chunks::<16>().0) {
723                *slot = group.iter().map(|&q| q as i32).sum::<i32>() as i16;
724            }
725        };
726    // Serial on purpose: every batch caller is already inside a Rayon
727    // region (one task per activation), so an inner region here nested
728    // ~batch_size fork-joins per matmul; and one row's blocks are far too
729    // little work to amortize one. llama quantizes serially per thread
730    // chunk too (`ggml_compute_forward_mul_mat`, `ggml-cpu.c`).
731    for (b, chunk) in x.as_chunks::<Q4_K_BLOCK_ELEMS>().0.iter().enumerate() {
732        quant_one((
733            &mut q[b * Q4_K_BLOCK_ELEMS..(b + 1) * Q4_K_BLOCK_ELEMS],
734            &mut d[b],
735            &mut bsums[b * 16..(b + 1) * 16],
736            chunk,
737        ));
738    }
739    Q8KActivations { q, d, bsums }
740}
741
742/// Quantize an activation row to [`Q8Activations`] (32-element blocks,
743/// ggml `quantize_row_q8_0` rounding: `d = amax/127`, `q = round(x/d)`).
744/// `x.len()` must be a multiple of 32.
745pub fn quantize_activations_q8(x: &[f32]) -> Q8Activations {
746    debug_assert_eq!(x.len() % Q8_0_BLOCK_ELEMS, 0);
747    let n_blocks = x.len() / Q8_0_BLOCK_ELEMS;
748    let mut q = vec![0i8; n_blocks * Q8_0_BLOCK_ELEMS];
749    let mut d = vec![0f32; n_blocks];
750    let quant_one = |(q_slot, d_slot, chunk): (&mut [i8], &mut f32, &[f32])| {
751        let amax = chunk.iter().fold(0f32, |m, &v| m.max(v.abs()));
752        let scale = amax / 127.0;
753        let inv = if scale > 0.0 { 1.0 / scale } else { 0.0 };
754        *d_slot = scale;
755        for (i, &v) in chunk.iter().enumerate() {
756            // round-half-away-from-zero, clamped to i8 range.
757            let qi = (v * inv).round();
758            q_slot[i] = qi.clamp(-127.0, 127.0) as i8;
759        }
760    };
761    // Serial on purpose — see `quantize_activations_q8_k`. The parallel
762    // split this replaces was also 32-byte `q` chunks (two per cache
763    // line) with adjacent `d` writes: false sharing on every store.
764    for (b, chunk) in x.as_chunks::<Q8_0_BLOCK_ELEMS>().0.iter().enumerate() {
765        quant_one((
766            &mut q[b * Q8_0_BLOCK_ELEMS..(b + 1) * Q8_0_BLOCK_ELEMS],
767            &mut d[b],
768            chunk,
769        ));
770    }
771    Q8Activations { q, d }
772}
773
774/// Integer `vec_dot` of a Q8_0 weight row against pre-quantized Q8
775/// activations: `Σ_blocks d_w * d_a * Σ_i (q_w · q_a)`. Dispatches to a
776/// NEON `dotprod` / AVX2 kernel when available, else the scalar loop.
777/// Numerically ≈ [`dot_q8_0_f32`] up to activation-quant error.
778pub fn dot_q8_0_q8(row_bytes: &[u8], act: &Q8Activations) -> f32 {
779    #[cfg(target_arch = "x86_64")]
780    {
781        if is_x86_feature_detected!("avx2") {
782            return unsafe { simd_x86::dot_q8_0_q8_avx2(row_bytes, act) };
783        }
784    }
785    #[cfg(target_arch = "aarch64")]
786    {
787        if std::arch::is_aarch64_feature_detected!("dotprod") {
788            return unsafe { simd_aarch64::dot_q8_0_q8_neon_sdot(row_bytes, act) };
789        }
790        if std::arch::is_aarch64_feature_detected!("neon") {
791            return unsafe { simd_aarch64::dot_q8_0_q8_neon(row_bytes, act) };
792        }
793    }
794    dot_q8_0_q8_scalar(row_bytes, act)
795}
796
797pub fn dot_q8_0_q8_scalar(row_bytes: &[u8], act: &Q8Activations) -> f32 {
798    debug_assert_eq!(row_bytes.len() % Q8_0_BLOCK_BYTES, 0);
799    let n_blocks = row_bytes.len() / Q8_0_BLOCK_BYTES;
800    debug_assert_eq!(n_blocks, act.n_blocks());
801    let mut acc = 0f32;
802    for (b, block) in row_bytes
803        .as_chunks::<Q8_0_BLOCK_BYTES>()
804        .0
805        .iter()
806        .enumerate()
807    {
808        let dw = f16::from_le_bytes([block[0], block[1]]).to_f32();
809        let base = b * Q8_0_BLOCK_ELEMS;
810        let mut isum = 0i32;
811        for i in 0..Q8_0_BLOCK_ELEMS {
812            let qw = block[2 + i] as i8 as i32;
813            let qa = act.q[base + i] as i32;
814            isum += qw * qa;
815        }
816        acc += dw * act.d[b] * isum as f32;
817    }
818    acc
819}
820
821/// Integer `vec_dot` of a Q4_0 weight row against pre-quantized Q8
822/// activations (llama.cpp `ggml_vec_dot_q4_0_q8_0`). Opt-in via
823/// `FERROX_CPU_INT_DOT` for Q4_0 matvecs.
824pub fn dot_q4_0_q8(row_bytes: &[u8], act: &Q8Activations) -> f32 {
825    #[cfg(target_arch = "x86_64")]
826    {
827        if is_x86_feature_detected!("avx2") {
828            return unsafe { simd_x86::dot_q4_0_q8_avx2(row_bytes, act) };
829        }
830    }
831    #[cfg(target_arch = "aarch64")]
832    {
833        if std::arch::is_aarch64_feature_detected!("dotprod") {
834            return unsafe { simd_aarch64::dot_q4_0_q8_neon_sdot(row_bytes, act) };
835        }
836        if std::arch::is_aarch64_feature_detected!("neon") {
837            return unsafe { simd_aarch64::dot_q4_0_q8_neon(row_bytes, act) };
838        }
839    }
840    dot_q4_0_q8_scalar(row_bytes, act)
841}
842
843/// Two contiguous Q4_0 rows × one Q8 act (shared act loads). Faster than
844/// two [`dot_q4_0_q8`] calls on Apple DotProd.
845pub fn dot_q4_0_q8_2row(row0: &[u8], row1: &[u8], act: &Q8Activations) -> (f32, f32) {
846    #[cfg(target_arch = "aarch64")]
847    {
848        if std::arch::is_aarch64_feature_detected!("dotprod")
849            && row0.len() == row1.len()
850            && row0.len().is_multiple_of(Q4_0_BLOCK_BYTES)
851        {
852            return unsafe { simd_aarch64::dot_q4_0_q8_neon_sdot_2row(row0, row1, act) };
853        }
854    }
855    (dot_q4_0_q8(row0, act), dot_q4_0_q8(row1, act))
856}
857
858pub fn dot_q4_0_q8_scalar(row_bytes: &[u8], act: &Q8Activations) -> f32 {
859    debug_assert_eq!(row_bytes.len() % Q4_0_BLOCK_BYTES, 0);
860    let n_blocks = row_bytes.len() / Q4_0_BLOCK_BYTES;
861    debug_assert_eq!(n_blocks, act.n_blocks());
862    let mut acc = 0f32;
863    for (b, block) in row_bytes
864        .as_chunks::<Q4_0_BLOCK_BYTES>()
865        .0
866        .iter()
867        .enumerate()
868    {
869        let dw = f16::from_le_bytes([block[0], block[1]]).to_f32();
870        let base = b * Q4_0_BLOCK_ELEMS;
871        let mut isum = 0i32;
872        for i in 0..16 {
873            let qs = block[2 + i];
874            let q0 = (qs & 0x0F) as i32 - 8;
875            let q1 = (qs >> 4) as i32 - 8;
876            isum += q0 * act.q[base + i] as i32;
877            isum += q1 * act.q[base + 16 + i] as i32;
878        }
879        acc += dw * act.d[b] * isum as f32;
880    }
881    acc
882}
883
884/// Integer `vec_dot` of a Q4_K weight row against [`Q8KActivations`]
885/// (llama.cpp `ggml_vec_dot_q4_K_q8_K`). Opt-in via `FERROX_CPU_INT_DOT`.
886pub fn dot_q4_k_q8(row_bytes: &[u8], act: &Q8KActivations) -> f32 {
887    #[cfg(target_arch = "x86_64")]
888    {
889        if is_x86_feature_detected!("avx2") {
890            return unsafe { simd_x86::dot_q4_k_q8_avx2(row_bytes, act) };
891        }
892    }
893    #[cfg(target_arch = "aarch64")]
894    {
895        if std::arch::is_aarch64_feature_detected!("i8mm") {
896            return unsafe { simd_aarch64::dot_q4_k_q8_neon_i8mm(row_bytes, act) };
897        }
898        if std::arch::is_aarch64_feature_detected!("dotprod") {
899            return unsafe { simd_aarch64::dot_q4_k_q8_neon_sdot(row_bytes, act) };
900        }
901        if std::arch::is_aarch64_feature_detected!("neon") {
902            return unsafe { simd_aarch64::dot_q4_k_q8_neon(row_bytes, act) };
903        }
904    }
905    dot_q4_k_q8_scalar(row_bytes, act)
906}
907
908pub fn dot_q4_k_q8_scalar(row_bytes: &[u8], act: &Q8KActivations) -> f32 {
909    debug_assert_eq!(row_bytes.len() % Q4_K_BLOCK_BYTES, 0);
910    let n_blocks = row_bytes.len() / Q4_K_BLOCK_BYTES;
911    debug_assert_eq!(n_blocks, act.n_blocks());
912    let mut acc = 0f32;
913    for (b, block) in row_bytes
914        .as_chunks::<Q4_K_BLOCK_BYTES>()
915        .0
916        .iter()
917        .enumerate()
918    {
919        let d = f16::from_le_bytes([block[0], block[1]]).to_f32();
920        let dmin = f16::from_le_bytes([block[2], block[3]]).to_f32();
921        let scales: [u8; Q4_K_SCALE_BYTES] = block[4..16].try_into().unwrap();
922        let qs = &block[16..144];
923        let da = act.d[b];
924        let q8 = &act.q[b * Q4_K_BLOCK_ELEMS..(b + 1) * Q4_K_BLOCK_ELEMS];
925        let bsums = &act.bsums[b * 16..(b + 1) * 16];
926
927        let mut sum_min = 0i32;
928        for i in 0..8 {
929            let (_, m) = q4_k_scale_min(i, &scales);
930            sum_min += m as i32 * (bsums[2 * i] as i32 + bsums[2 * i + 1] as i32);
931        }
932        acc -= dmin * da * sum_min as f32;
933
934        let mut q_off = 0usize;
935        let mut base = 0usize;
936        let mut is = 0usize;
937        for _ in 0..4 {
938            let (sc1, _) = q4_k_scale_min(is, &scales);
939            let (sc2, _) = q4_k_scale_min(is + 1, &scales);
940            let mut isum1 = 0i32;
941            let mut isum2 = 0i32;
942            for l in 0..32 {
943                isum1 += (qs[q_off + l] & 0x0F) as i32 * q8[base + l] as i32;
944            }
945            for l in 0..32 {
946                isum2 += (qs[q_off + l] >> 4) as i32 * q8[base + 32 + l] as i32;
947            }
948            acc += d * da * (sc1 as f32 * isum1 as f32 + sc2 as f32 * isum2 as f32);
949            q_off += 32;
950            base += 64;
951            is += 2;
952        }
953    }
954    acc
955}
956
957/// Integer `vec_dot` of a Q5_K weight row against [`Q8KActivations`]
958/// (llama.cpp `ggml_vec_dot_q5_K_q8_K`). Opt-in via `FERROX_CPU_INT_DOT`.
959pub fn dot_q5_k_q8(row_bytes: &[u8], act: &Q8KActivations) -> f32 {
960    #[cfg(target_arch = "aarch64")]
961    {
962        if std::arch::is_aarch64_feature_detected!("dotprod") {
963            return unsafe { simd_aarch64::dot_q5_k_q8_neon_sdot(row_bytes, act) };
964        }
965        if std::arch::is_aarch64_feature_detected!("neon") {
966            return unsafe { simd_aarch64::dot_q5_k_q8_neon(row_bytes, act) };
967        }
968    }
969    dot_q5_k_q8_scalar(row_bytes, act)
970}
971
972pub fn dot_q5_k_q8_scalar(row_bytes: &[u8], act: &Q8KActivations) -> f32 {
973    debug_assert_eq!(row_bytes.len() % Q5_K_BLOCK_BYTES, 0);
974    let n_blocks = row_bytes.len() / Q5_K_BLOCK_BYTES;
975    debug_assert_eq!(n_blocks, act.n_blocks());
976    let mut acc = 0f32;
977    for (b, block) in row_bytes
978        .as_chunks::<Q5_K_BLOCK_BYTES>()
979        .0
980        .iter()
981        .enumerate()
982    {
983        let d = f16::from_le_bytes([block[0], block[1]]).to_f32();
984        let dmin = f16::from_le_bytes([block[2], block[3]]).to_f32();
985        let scales: [u8; Q4_K_SCALE_BYTES] = block[4..16].try_into().unwrap();
986        let qh = &block[16..48];
987        let qs = &block[48..176];
988        let da = act.d[b];
989        let q8 = &act.q[b * Q5_K_BLOCK_ELEMS..(b + 1) * Q5_K_BLOCK_ELEMS];
990        let bsums = &act.bsums[b * 16..(b + 1) * 16];
991
992        let mut sum_min = 0i32;
993        for i in 0..8 {
994            let (_, m) = q4_k_scale_min(i, &scales);
995            sum_min += m as i32 * (bsums[2 * i] as i32 + bsums[2 * i + 1] as i32);
996        }
997        acc -= dmin * da * sum_min as f32;
998
999        let mut q_off = 0usize;
1000        let mut base = 0usize;
1001        let mut is = 0usize;
1002        let (mut u1, mut u2) = (1u8, 2u8);
1003        for _ in 0..4 {
1004            let (sc1, _) = q4_k_scale_min(is, &scales);
1005            let (sc2, _) = q4_k_scale_min(is + 1, &scales);
1006            let mut isum1 = 0i32;
1007            let mut isum2 = 0i32;
1008            for l in 0..32 {
1009                let hi = if qh[l] & u1 != 0 { 16 } else { 0 };
1010                isum1 += ((qs[q_off + l] & 0x0F) + hi) as i32 * q8[base + l] as i32;
1011            }
1012            for l in 0..32 {
1013                let hi = if qh[l] & u2 != 0 { 16 } else { 0 };
1014                isum2 += ((qs[q_off + l] >> 4) + hi) as i32 * q8[base + 32 + l] as i32;
1015            }
1016            acc += d * da * (sc1 as f32 * isum1 as f32 + sc2 as f32 * isum2 as f32);
1017            q_off += 32;
1018            base += 64;
1019            is += 2;
1020            u1 <<= 2;
1021            u2 <<= 2;
1022        }
1023    }
1024    acc
1025}
1026
1027/// How many activations one [`gemm_q5_k_q8_row`] / [`gemm_q6_k_q8_row`]
1028/// keeps in flight. Amortizes weight-block scale/qh/qs loads over the
1029/// batch (Phi-4 Q5_K qkv / Q6_K ffn_down) without full Kx8 repack.
1030pub const Q5_K_GEMM_NC: usize = 4;
1031pub const Q6_K_GEMM_NC: usize = 4;
1032
1033/// One Q5_K weight row × `acts.len()` Q8_K activations → `out[j]`.
1034///
1035/// Block-outer loop so each Q5_K block's scales / qh / qs are decoded once
1036/// and reused across activations (llama.cpp GEMM motivation without the
1037/// `block_q5_Kx8` interleave).
1038pub fn gemm_q5_k_q8_row(row_bytes: &[u8], acts: &[Q8KActivations], out: &mut [f32]) {
1039    assert_eq!(out.len(), acts.len());
1040    if acts.is_empty() {
1041        return;
1042    }
1043    #[cfg(target_arch = "aarch64")]
1044    {
1045        if acts.len() <= Q5_K_GEMM_NC && std::arch::is_aarch64_feature_detected!("dotprod") {
1046            unsafe {
1047                simd_aarch64::gemm_q5_k_q8_neon_sdot(row_bytes, acts, out);
1048            }
1049            return;
1050        }
1051    }
1052    gemm_q5_k_q8_row_scalar(row_bytes, acts, out);
1053}
1054
1055pub fn gemm_q5_k_q8_row_scalar(row_bytes: &[u8], acts: &[Q8KActivations], out: &mut [f32]) {
1056    debug_assert_eq!(row_bytes.len() % Q5_K_BLOCK_BYTES, 0);
1057    out.fill(0.0);
1058    let n_blocks = row_bytes.len() / Q5_K_BLOCK_BYTES;
1059    for act in acts {
1060        debug_assert_eq!(n_blocks, act.n_blocks());
1061    }
1062    for (b, block) in row_bytes
1063        .as_chunks::<Q5_K_BLOCK_BYTES>()
1064        .0
1065        .iter()
1066        .enumerate()
1067    {
1068        let d = f16::from_le_bytes([block[0], block[1]]).to_f32();
1069        let dmin = f16::from_le_bytes([block[2], block[3]]).to_f32();
1070        let scales: [u8; Q4_K_SCALE_BYTES] = block[4..16].try_into().unwrap();
1071        let qh = &block[16..48];
1072        let qs = &block[48..176];
1073        let mut mins = [0u8; 8];
1074        let mut sc_only = [0u8; 8];
1075        for i in 0..8 {
1076            let (s, m) = q4_k_scale_min(i, &scales);
1077            sc_only[i] = s;
1078            mins[i] = m;
1079        }
1080        for (j, act) in acts.iter().enumerate() {
1081            let da = act.d[b];
1082            let q8 = &act.q[b * Q5_K_BLOCK_ELEMS..(b + 1) * Q5_K_BLOCK_ELEMS];
1083            let bsums = &act.bsums[b * 16..(b + 1) * 16];
1084            let mut sum_min = 0i32;
1085            for i in 0..8 {
1086                sum_min += mins[i] as i32 * (bsums[2 * i] as i32 + bsums[2 * i + 1] as i32);
1087            }
1088            out[j] -= dmin * da * sum_min as f32;
1089
1090            let mut q_off = 0usize;
1091            let mut base = 0usize;
1092            let mut is = 0usize;
1093            let (mut u1, mut u2) = (1u8, 2u8);
1094            for _ in 0..4 {
1095                let sc1 = sc_only[is];
1096                let sc2 = sc_only[is + 1];
1097                let mut isum1 = 0i32;
1098                let mut isum2 = 0i32;
1099                for l in 0..32 {
1100                    let hi = if qh[l] & u1 != 0 { 16 } else { 0 };
1101                    isum1 += ((qs[q_off + l] & 0x0F) + hi) as i32 * q8[base + l] as i32;
1102                }
1103                for l in 0..32 {
1104                    let hi = if qh[l] & u2 != 0 { 16 } else { 0 };
1105                    isum2 += ((qs[q_off + l] >> 4) + hi) as i32 * q8[base + 32 + l] as i32;
1106                }
1107                out[j] += d * da * (sc1 as f32 * isum1 as f32 + sc2 as f32 * isum2 as f32);
1108                q_off += 32;
1109                base += 64;
1110                is += 2;
1111                u1 <<= 2;
1112                u2 <<= 2;
1113            }
1114        }
1115    }
1116}
1117
1118/// One Q6_K weight row × `acts.len()` Q8_K activations → `out[j]`.
1119pub fn gemm_q6_k_q8_row(row_bytes: &[u8], acts: &[Q8KActivations], out: &mut [f32]) {
1120    assert_eq!(out.len(), acts.len());
1121    if acts.is_empty() {
1122        return;
1123    }
1124    #[cfg(target_arch = "aarch64")]
1125    {
1126        if acts.len() <= Q6_K_GEMM_NC && std::arch::is_aarch64_feature_detected!("dotprod") {
1127            unsafe {
1128                simd_aarch64::gemm_q6_k_q8_neon_sdot(row_bytes, acts, out);
1129            }
1130            return;
1131        }
1132    }
1133    gemm_q6_k_q8_row_scalar(row_bytes, acts, out);
1134}
1135
1136pub fn gemm_q6_k_q8_row_scalar(row_bytes: &[u8], acts: &[Q8KActivations], out: &mut [f32]) {
1137    out.fill(0.0);
1138    for (j, act) in acts.iter().enumerate() {
1139        out[j] = dot_q6_k_q8_scalar(row_bytes, act);
1140    }
1141}
1142
1143/// Integer `vec_dot` of a Q6_K weight row against [`Q8KActivations`]
1144/// (llama.cpp `ggml_vec_dot_q6_K_q8_K`). Opt-in via `FERROX_CPU_INT_DOT`.
1145pub fn dot_q6_k_q8(row_bytes: &[u8], act: &Q8KActivations) -> f32 {
1146    #[cfg(target_arch = "aarch64")]
1147    {
1148        if std::arch::is_aarch64_feature_detected!("dotprod") {
1149            return unsafe { simd_aarch64::dot_q6_k_q8_neon_sdot(row_bytes, act) };
1150        }
1151    }
1152    dot_q6_k_q8_scalar(row_bytes, act)
1153}
1154
1155pub fn dot_q6_k_q8_scalar(row_bytes: &[u8], act: &Q8KActivations) -> f32 {
1156    debug_assert_eq!(row_bytes.len() % Q6_K_BLOCK_BYTES, 0);
1157    let n_blocks = row_bytes.len() / Q6_K_BLOCK_BYTES;
1158    debug_assert_eq!(n_blocks, act.n_blocks());
1159    // Q6_K uses 256-elem super-blocks; Q8_K acts share that width.
1160    debug_assert_eq!(Q6_K_BLOCK_ELEMS, Q4_K_BLOCK_ELEMS);
1161    let mut acc = 0f32;
1162    for (b, block) in row_bytes
1163        .as_chunks::<Q6_K_BLOCK_BYTES>()
1164        .0
1165        .iter()
1166        .enumerate()
1167    {
1168        let ql_full = &block[0..128];
1169        let qh_full = &block[128..192];
1170        let sc_full = &block[192..208];
1171        let d = f16::from_le_bytes([block[208], block[209]]).to_f32();
1172        let da = act.d[b];
1173        let q8 = &act.q[b * Q6_K_BLOCK_ELEMS..(b + 1) * Q6_K_BLOCK_ELEMS];
1174        let mut isum = 0i32;
1175
1176        for half in 0..2 {
1177            let ql = &ql_full[half * 64..half * 64 + 64];
1178            let qh = &qh_full[half * 32..half * 32 + 32];
1179            let sc = &sc_full[half * 8..half * 8 + 8];
1180            let q8h = &q8[half * 128..half * 128 + 128];
1181            for l in 0..32 {
1182                let is = l / 16;
1183                let q1 = ((ql[l] & 0x0F) | ((qh[l] & 3) << 4)) as i8 as i32 - 32;
1184                let q2 = ((ql[l + 32] & 0x0F) | (((qh[l] >> 2) & 3) << 4)) as i8 as i32 - 32;
1185                let q3 = ((ql[l] >> 4) | (((qh[l] >> 4) & 3) << 4)) as i8 as i32 - 32;
1186                let q4 = ((ql[l + 32] >> 4) | (((qh[l] >> 6) & 3) << 4)) as i8 as i32 - 32;
1187                isum += (sc[is] as i8 as i32) * q1 * (q8h[l] as i32);
1188                isum += (sc[is + 2] as i8 as i32) * q2 * (q8h[l + 32] as i32);
1189                isum += (sc[is + 4] as i8 as i32) * q3 * (q8h[l + 64] as i32);
1190                isum += (sc[is + 6] as i8 as i32) * q4 * (q8h[l + 96] as i32);
1191            }
1192        }
1193        acc += d * da * isum as f32;
1194    }
1195    acc
1196}
1197
1198#[cfg(target_arch = "x86_64")]
1199mod simd_x86 {
1200    use super::{
1201        e8m0_scale, q3_k_unpack_scales, q4_k_scale_min, q5_fifth_bits, Q8Activations,
1202        Q8KActivations, IQ4_NL_BLOCK_BYTES, IQ4_NL_BLOCK_ELEMS, IQ4_XS_BLOCK_BYTES, KVALUES_IQ4NL,
1203        MXFP4_GROUP_SIZE, Q2_K_BLOCK_BYTES, Q2_K_SCALE_BYTES, Q3_K_BLOCK_BYTES, Q3_K_SCALE_BYTES,
1204        Q4_0_BLOCK_BYTES, Q4_0_BLOCK_ELEMS, Q4_1_BLOCK_BYTES, Q4_1_BLOCK_ELEMS, Q4_K_BLOCK_BYTES,
1205        Q4_K_BLOCK_ELEMS, Q4_K_SCALE_BYTES, Q5_0_BLOCK_BYTES, Q5_0_BLOCK_ELEMS, Q5_1_BLOCK_BYTES,
1206        Q5_1_BLOCK_ELEMS, Q5_K_BLOCK_BYTES, Q6_K_BLOCK_BYTES, Q6_K_BLOCK_ELEMS, Q8_0_BLOCK_BYTES,
1207        Q8_0_BLOCK_ELEMS, Q8_1_BLOCK_BYTES, Q8_1_BLOCK_ELEMS,
1208    };
1209    use half::f16;
1210    use std::arch::x86_64::*;
1211
1212    /// AVX2+FMA fused Q8_0 dot product. Each 32-element block is
1213    /// processed as four 8-wide lanes: sign-extend 8 int8 quantized
1214    /// values to i32 (`_mm256_cvtepi8_epi32`), convert to f32, and
1215    /// fused-multiply-accumulate against the matching 8 activation
1216    /// values, then horizontally sum and apply the block's shared f16
1217    /// scale. Safety: caller must have already checked
1218    /// `is_x86_feature_detected!("avx2")` and `"fma"`; the function
1219    /// itself additionally asserts the buffer lengths line up, same as
1220    /// the scalar path.
1221    #[target_feature(enable = "avx2,fma")]
1222    pub unsafe fn dot_q8_0_f32_avx2(row_bytes: &[u8], x: &[f32]) -> f32 {
1223        debug_assert_eq!(row_bytes.len() % Q8_0_BLOCK_BYTES, 0);
1224        debug_assert_eq!(
1225            row_bytes.len() / Q8_0_BLOCK_BYTES * Q8_0_BLOCK_ELEMS,
1226            x.len()
1227        );
1228        let mut acc = 0f32;
1229        for (b, block) in row_bytes
1230            .as_chunks::<Q8_0_BLOCK_BYTES>()
1231            .0
1232            .iter()
1233            .enumerate()
1234        {
1235            let scale = f16::from_le_bytes([block[0], block[1]]).to_f32();
1236            let base = b * Q8_0_BLOCK_ELEMS;
1237            let qs = &block[2..34];
1238
1239            let mut block_acc = _mm256_setzero_ps();
1240            for g in 0..4 {
1241                let raw8 = _mm_loadl_epi64(qs.as_ptr().add(g * 8) as *const __m128i);
1242                let i32x8 = _mm256_cvtepi8_epi32(raw8);
1243                let f32x8 = _mm256_cvtepi32_ps(i32x8);
1244                let xv = _mm256_loadu_ps(x.as_ptr().add(base + g * 8));
1245                block_acc = _mm256_fmadd_ps(f32x8, xv, block_acc);
1246            }
1247            acc += hsum256_ps(block_acc) * scale;
1248        }
1249        acc
1250    }
1251
1252    /// AVX2 integer Q8_0 × Q8 dot: sign-extend both operands' int8 halves
1253    /// to i16, `_mm256_madd_epi16` into i32 pairs (no AVX-512 VNNI needed),
1254    /// horizontally sum, and scale by `d_w * d_a` per block. Matches
1255    /// [`super::dot_q8_0_q8_scalar`] exactly (pure integer products).
1256    /// Safety: caller checked `is_x86_feature_detected!("avx2")`.
1257    #[target_feature(enable = "avx2")]
1258    pub unsafe fn dot_q8_0_q8_avx2(row_bytes: &[u8], act: &Q8Activations) -> f32 {
1259        debug_assert_eq!(row_bytes.len() % Q8_0_BLOCK_BYTES, 0);
1260        debug_assert_eq!(row_bytes.len() / Q8_0_BLOCK_BYTES, act.n_blocks());
1261        let mut acc = 0f32;
1262        for (b, block) in row_bytes
1263            .as_chunks::<Q8_0_BLOCK_BYTES>()
1264            .0
1265            .iter()
1266            .enumerate()
1267        {
1268            let dw = f16::from_le_bytes([block[0], block[1]]).to_f32();
1269            let base = b * Q8_0_BLOCK_ELEMS;
1270            let w = _mm256_loadu_si256(block.as_ptr().add(2) as *const __m256i);
1271            let a = _mm256_loadu_si256(act.q.as_ptr().add(base) as *const __m256i);
1272            let w_lo = _mm256_cvtepi8_epi16(_mm256_castsi256_si128(w));
1273            let w_hi = _mm256_cvtepi8_epi16(_mm256_extracti128_si256(w, 1));
1274            let a_lo = _mm256_cvtepi8_epi16(_mm256_castsi256_si128(a));
1275            let a_hi = _mm256_cvtepi8_epi16(_mm256_extracti128_si256(a, 1));
1276            let prod =
1277                _mm256_add_epi32(_mm256_madd_epi16(w_lo, a_lo), _mm256_madd_epi16(w_hi, a_hi));
1278            // horizontal sum of 8 i32 lanes
1279            let hi128 = _mm256_extracti128_si256(prod, 1);
1280            let lo128 = _mm256_castsi256_si128(prod);
1281            let mut sum128 = _mm_add_epi32(lo128, hi128);
1282            sum128 = _mm_add_epi32(sum128, _mm_shuffle_epi32(sum128, 0b01_00_11_10));
1283            sum128 = _mm_add_epi32(sum128, _mm_shuffle_epi32(sum128, 0b00_00_00_01));
1284            let isum = _mm_cvtsi128_si32(sum128);
1285            acc += dw * act.d[b] * isum as f32;
1286        }
1287        acc
1288    }
1289
1290    /// AVX2 Q4_0 × Q8 int-dot. Nibble unpack + signed bias, then
1291    /// `_mm256_madd_epi16` against activation i16. Safety: caller
1292    /// checked `avx2`.
1293    #[target_feature(enable = "avx2")]
1294    pub unsafe fn dot_q4_0_q8_avx2(row_bytes: &[u8], act: &Q8Activations) -> f32 {
1295        debug_assert_eq!(row_bytes.len() % Q4_0_BLOCK_BYTES, 0);
1296        debug_assert_eq!(row_bytes.len() / Q4_0_BLOCK_BYTES, act.n_blocks());
1297        let low_mask = _mm_set1_epi8(0x0F);
1298        let bias = _mm_set1_epi8(8);
1299        let mut acc = 0f32;
1300        for (b, block) in row_bytes
1301            .as_chunks::<Q4_0_BLOCK_BYTES>()
1302            .0
1303            .iter()
1304            .enumerate()
1305        {
1306            let dw = f16::from_le_bytes([block[0], block[1]]).to_f32();
1307            let base = b * Q4_0_BLOCK_ELEMS;
1308            let qs = _mm_loadu_si128(block.as_ptr().add(2) as *const __m128i);
1309            let lo = _mm_sub_epi8(_mm_and_si128(qs, low_mask), bias);
1310            let hi = _mm_sub_epi8(_mm_and_si128(_mm_srli_epi16(qs, 4), low_mask), bias);
1311            // Interleave lo (0..15) then hi (16..31) into 32 i8 → widen to i16.
1312            let w = _mm256_set_m128i(hi, lo);
1313            let a = _mm256_loadu_si256(act.q.as_ptr().add(base) as *const __m256i);
1314            let w_lo = _mm256_cvtepi8_epi16(_mm256_castsi256_si128(w));
1315            let w_hi = _mm256_cvtepi8_epi16(_mm256_extracti128_si256(w, 1));
1316            let a_lo = _mm256_cvtepi8_epi16(_mm256_castsi256_si128(a));
1317            let a_hi = _mm256_cvtepi8_epi16(_mm256_extracti128_si256(a, 1));
1318            let prod =
1319                _mm256_add_epi32(_mm256_madd_epi16(w_lo, a_lo), _mm256_madd_epi16(w_hi, a_hi));
1320            let hi128 = _mm256_extracti128_si256(prod, 1);
1321            let lo128 = _mm256_castsi256_si128(prod);
1322            let mut sum128 = _mm_add_epi32(lo128, hi128);
1323            sum128 = _mm_add_epi32(sum128, _mm_shuffle_epi32(sum128, 0b01_00_11_10));
1324            sum128 = _mm_add_epi32(sum128, _mm_shuffle_epi32(sum128, 0b00_00_00_01));
1325            let isum = _mm_cvtsi128_si32(sum128);
1326            acc += dw * act.d[b] * isum as f32;
1327        }
1328        acc
1329    }
1330
1331    /// AVX2 Q4_K × Q8_K int-dot. Matches [`super::dot_q4_k_q8_scalar`].
1332    #[target_feature(enable = "avx2")]
1333    pub unsafe fn dot_q4_k_q8_avx2(row_bytes: &[u8], act: &Q8KActivations) -> f32 {
1334        debug_assert_eq!(row_bytes.len() % Q4_K_BLOCK_BYTES, 0);
1335        debug_assert_eq!(row_bytes.len() / Q4_K_BLOCK_BYTES, act.n_blocks());
1336        let low_mask = _mm256_set1_epi8(0x0F_u8 as i8);
1337        let mut acc = 0f32;
1338        for (b, block) in row_bytes
1339            .as_chunks::<Q4_K_BLOCK_BYTES>()
1340            .0
1341            .iter()
1342            .enumerate()
1343        {
1344            let d = f16::from_le_bytes([block[0], block[1]]).to_f32();
1345            let dmin = f16::from_le_bytes([block[2], block[3]]).to_f32();
1346            let scales: [u8; Q4_K_SCALE_BYTES] = block[4..16].try_into().unwrap();
1347            let qs = &block[16..144];
1348            let da = act.d[b];
1349            let q8 = act.q.as_ptr().add(b * Q4_K_BLOCK_ELEMS);
1350            let bsums = &act.bsums[b * 16..(b + 1) * 16];
1351
1352            let mut sum_min = 0i32;
1353            for i in 0..8 {
1354                let (_, m) = q4_k_scale_min(i, &scales);
1355                sum_min += m as i32 * (bsums[2 * i] as i32 + bsums[2 * i + 1] as i32);
1356            }
1357            acc -= dmin * da * sum_min as f32;
1358
1359            let mut q_off = 0usize;
1360            let mut base = 0usize;
1361            let mut is = 0usize;
1362            for _ in 0..4 {
1363                let (sc1, _) = q4_k_scale_min(is, &scales);
1364                let (sc2, _) = q4_k_scale_min(is + 1, &scales);
1365                let packed = _mm256_loadu_si256(qs.as_ptr().add(q_off) as *const __m256i);
1366                let lo = _mm256_and_si256(packed, low_mask);
1367                let hi = _mm256_and_si256(_mm256_srli_epi16(packed, 4), low_mask);
1368                let a0 = _mm256_loadu_si256(q8.add(base) as *const __m256i);
1369                let a1 = _mm256_loadu_si256(q8.add(base + 32) as *const __m256i);
1370                let isum1 = madd_i8_avx2(lo, a0);
1371                let isum2 = madd_i8_avx2(hi, a1);
1372                acc += d * da * (sc1 as f32 * isum1 as f32 + sc2 as f32 * isum2 as f32);
1373                q_off += 32;
1374                base += 64;
1375                is += 2;
1376            }
1377        }
1378        acc
1379    }
1380
1381    #[target_feature(enable = "avx2")]
1382    unsafe fn madd_i8_avx2(w: __m256i, a: __m256i) -> i32 {
1383        let w_lo = _mm256_cvtepi8_epi16(_mm256_castsi256_si128(w));
1384        let w_hi = _mm256_cvtepi8_epi16(_mm256_extracti128_si256(w, 1));
1385        let a_lo = _mm256_cvtepi8_epi16(_mm256_castsi256_si128(a));
1386        let a_hi = _mm256_cvtepi8_epi16(_mm256_extracti128_si256(a, 1));
1387        let prod = _mm256_add_epi32(_mm256_madd_epi16(w_lo, a_lo), _mm256_madd_epi16(w_hi, a_hi));
1388        let hi128 = _mm256_extracti128_si256(prod, 1);
1389        let lo128 = _mm256_castsi256_si128(prod);
1390        let mut sum128 = _mm_add_epi32(lo128, hi128);
1391        sum128 = _mm_add_epi32(sum128, _mm_shuffle_epi32(sum128, 0b01_00_11_10));
1392        sum128 = _mm_add_epi32(sum128, _mm_shuffle_epi32(sum128, 0b00_00_00_01));
1393        _mm_cvtsi128_si32(sum128)
1394    }
1395
1396    /// AVX2+FMA fused Q4_0 dot product. Each block packs 32 4-bit
1397    /// values into 16 bytes: byte `i`'s low nibble is element `i`,
1398    /// high nibble is element `i+16`, both biased by -8. High-nibble
1399    /// extraction uses the standard `_mm_srli_epi16(bytes, 4) & 0x0F`
1400    /// trick (shifting as 16-bit lanes, then masking per-byte, avoids
1401    /// needing a per-byte shift instruction which x86 SIMD doesn't
1402    /// have below AVX-512). Safety: same contract as
1403    /// `dot_q8_0_f32_avx2`.
1404    #[target_feature(enable = "avx2,fma")]
1405    pub unsafe fn dot_q4_0_f32_avx2(row_bytes: &[u8], x: &[f32]) -> f32 {
1406        debug_assert_eq!(row_bytes.len() % Q4_0_BLOCK_BYTES, 0);
1407        let bias = _mm_set1_epi8(8);
1408        let low_mask = _mm_set1_epi8(0x0F);
1409
1410        let mut acc = 0f32;
1411        for (b, block) in row_bytes
1412            .as_chunks::<Q4_0_BLOCK_BYTES>()
1413            .0
1414            .iter()
1415            .enumerate()
1416        {
1417            let scale = f16::from_le_bytes([block[0], block[1]]).to_f32();
1418            let base = b * Q4_0_BLOCK_ELEMS;
1419            let nibbles = _mm_loadu_si128(block.as_ptr().add(2) as *const __m128i);
1420
1421            let lo_nibbles = _mm_sub_epi8(_mm_and_si128(nibbles, low_mask), bias);
1422            let hi_nibbles =
1423                _mm_sub_epi8(_mm_and_si128(_mm_srli_epi16(nibbles, 4), low_mask), bias);
1424
1425            let mut block_acc = _mm256_setzero_ps();
1426            // elements 0..16 (lo_nibbles), two 8-wide groups
1427            for (group_idx, half) in [
1428                (0usize, lo_nibbles),
1429                (1usize, _mm_srli_si128(lo_nibbles, 8)),
1430                (2usize, hi_nibbles),
1431                (3usize, _mm_srli_si128(hi_nibbles, 8)),
1432            ] {
1433                let i32x8 = _mm256_cvtepi8_epi32(half);
1434                let f32x8 = _mm256_cvtepi32_ps(i32x8);
1435                let elem_base = base + group_idx * 8;
1436                let xv = _mm256_loadu_ps(x.as_ptr().add(elem_base));
1437                block_acc = _mm256_fmadd_ps(f32x8, xv, block_acc);
1438            }
1439            acc += hsum256_ps(block_acc) * scale;
1440        }
1441        acc
1442    }
1443
1444    #[inline]
1445    #[target_feature(enable = "avx2")]
1446    unsafe fn hsum256_ps(v: __m256) -> f32 {
1447        let hi = _mm256_extractf128_ps(v, 1);
1448        let lo = _mm256_castps256_ps128(v);
1449        let sum128 = _mm_add_ps(hi, lo);
1450        let shuf = _mm_movehdup_ps(sum128);
1451        let sums = _mm_add_ps(sum128, shuf);
1452        let shuf2 = _mm_movehl_ps(shuf, sums);
1453        let sums2 = _mm_add_ss(sums, shuf2);
1454        _mm_cvtss_f32(sums2)
1455    }
1456
1457    /// Widens 16 unsigned nibble-derived byte values (0..=15, or 0..=31
1458    /// once Q5_K has OR'd in a 5th bit) held in the low and high halves
1459    /// of `part` into 8 lanes of f32 via `_mm256_cvtepu8_epi32` (zero-
1460    /// extending unsigned widen, unlike Q8_0/Q4_0's signed
1461    /// `_mm256_cvtepi8_epi32` -- K-quant nibbles are never negative
1462    /// before the affine `d*q - min` transform is applied), then
1463    /// dequantizes as `d*q - min` and fused-multiply-accumulates
1464    /// against the matching 8 activations. Called twice per 16-byte
1465    /// group (`part` = the low 8 bytes, then the high 8 bytes via
1466    /// `_mm_srli_si128(part, 8)`) to cover all 16 lanes, mirroring the
1467    /// existing Q4_0 AVX2 kernel's `_mm_srli_si128(lo_nibbles, 8)`
1468    /// idiom for the same reason (AVX2 has no direct 16-lane u8->i32
1469    /// widen).
1470    #[inline]
1471    #[target_feature(enable = "avx2,fma")]
1472    unsafe fn fma_affine8(
1473        part: __m128i,
1474        d: f32,
1475        min: f32,
1476        x: &[f32],
1477        x_base: usize,
1478        acc: __m256,
1479    ) -> __m256 {
1480        let i32x8 = _mm256_cvtepu8_epi32(part);
1481        let f32x8 = _mm256_cvtepi32_ps(i32x8);
1482        let weight = _mm256_fmsub_ps(f32x8, _mm256_set1_ps(d), _mm256_set1_ps(min));
1483        let xv = _mm256_loadu_ps(x.as_ptr().add(x_base));
1484        _mm256_fmadd_ps(weight, xv, acc)
1485    }
1486
1487    /// AVX2+FMA fused Q4_K dot product. Mirrors `dot_q4_0_f32_avx2`'s
1488    /// nibble-splitting structure (low/high nibble of each byte are two
1489    /// independent output elements, each 16-byte load's nibbles split
1490    /// into two 8-wide `_mm256_cvtepu8_epi32` groups via
1491    /// `_mm_srli_si128(_, 8)`), scaled up from Q4_0's 16 bytes/block to
1492    /// Q4_K's 32 bytes/sub-block (two 16-byte loads instead of one),
1493    /// with the affine `d*q - min` transform (independent (scale, min)
1494    /// pairs for the low-nibble half and the high-nibble half) instead
1495    /// of Q4_0's single symmetric `d*(q-8)`. Safety: same contract as
1496    /// `dot_q8_0_f32_avx2`.
1497    #[target_feature(enable = "avx2,fma")]
1498    pub unsafe fn dot_q4_k_f32_avx2(row_bytes: &[u8], x: &[f32]) -> f32 {
1499        debug_assert_eq!(row_bytes.len() % Q4_K_BLOCK_BYTES, 0);
1500        let low_mask = _mm_set1_epi8(0x0F);
1501        let mut acc = 0f32;
1502        let mut x_base = 0usize;
1503        for block in row_bytes.as_chunks::<Q4_K_BLOCK_BYTES>().0 {
1504            let d = f16::from_le_bytes([block[0], block[1]]).to_f32();
1505            let dmin = f16::from_le_bytes([block[2], block[3]]).to_f32();
1506            let scales: [u8; Q4_K_SCALE_BYTES] = block[4..16].try_into().unwrap();
1507            let qs = &block[16..144];
1508
1509            let mut is = 0usize;
1510            let mut q_off = 0usize;
1511            for _ in 0..4 {
1512                let (sc1, m1) = q4_k_scale_min(is, &scales);
1513                let (sc2, m2) = q4_k_scale_min(is + 1, &scales);
1514                let d1 = d * sc1 as f32;
1515                let min1 = dmin * m1 as f32;
1516                let d2 = d * sc2 as f32;
1517                let min2 = dmin * m2 as f32;
1518
1519                let mut lo_acc = _mm256_setzero_ps();
1520                let mut hi_acc = _mm256_setzero_ps();
1521                for g in 0..2 {
1522                    let raw16 = _mm_loadu_si128(qs.as_ptr().add(q_off + g * 16) as *const __m128i);
1523                    let lo_nib = _mm_and_si128(raw16, low_mask);
1524                    let hi_nib = _mm_and_si128(_mm_srli_epi16(raw16, 4), low_mask);
1525
1526                    for (part_idx, part) in
1527                        [lo_nib, _mm_srli_si128(lo_nib, 8)].into_iter().enumerate()
1528                    {
1529                        lo_acc =
1530                            fma_affine8(part, d1, min1, x, x_base + g * 16 + part_idx * 8, lo_acc);
1531                    }
1532                    for (part_idx, part) in
1533                        [hi_nib, _mm_srli_si128(hi_nib, 8)].into_iter().enumerate()
1534                    {
1535                        hi_acc = fma_affine8(
1536                            part,
1537                            d2,
1538                            min2,
1539                            x,
1540                            x_base + 32 + g * 16 + part_idx * 8,
1541                            hi_acc,
1542                        );
1543                    }
1544                }
1545                acc += hsum256_ps(lo_acc) + hsum256_ps(hi_acc);
1546                q_off += 32;
1547                x_base += 64;
1548                is += 2;
1549            }
1550        }
1551        acc
1552    }
1553
1554    /// AVX2+FMA fused Q5_K dot product: identical structure to
1555    /// `dot_q4_k_f32_avx2`, but before widening, each nibble gets a 5th
1556    /// bit OR'd in from the block's `qh` bitplane. The per-lane "is bit
1557    /// `u1`/`u2` set in this byte of `qh`" test uses an equality-based
1558    /// mask (`_mm_cmpeq_epi8(masked, zero)`, inverted via
1559    /// `_mm_andnot_si128`) rather than `_mm_cmpgt_epi8`: `u1`/`u2` sweep
1560    /// up to 128 (`u2` reaches `0x80`), which as a *signed* i8 is
1561    /// negative, so a signed greater-than comparison would silently
1562    /// misclassify a set high bit as "not greater than zero" -- the
1563    /// equality test is agnostic to that sign issue since it only asks
1564    /// "is the masked byte zero or not." Safety: same contract as
1565    /// `dot_q8_0_f32_avx2`.
1566    #[target_feature(enable = "avx2,fma")]
1567    pub unsafe fn dot_q5_k_f32_avx2(row_bytes: &[u8], x: &[f32]) -> f32 {
1568        debug_assert_eq!(row_bytes.len() % Q5_K_BLOCK_BYTES, 0);
1569        let low_mask = _mm_set1_epi8(0x0F);
1570        let zero = _mm_setzero_si128();
1571        let sixteen = _mm_set1_epi8(16);
1572        let mut acc = 0f32;
1573        let mut x_base = 0usize;
1574        for block in row_bytes.as_chunks::<Q5_K_BLOCK_BYTES>().0 {
1575            let d = f16::from_le_bytes([block[0], block[1]]).to_f32();
1576            let dmin = f16::from_le_bytes([block[2], block[3]]).to_f32();
1577            let scales: [u8; Q4_K_SCALE_BYTES] = block[4..16].try_into().unwrap();
1578            let qh = &block[16..48];
1579            let qs = &block[48..176];
1580
1581            let mut is = 0usize;
1582            let (mut u1, mut u2) = (1u8, 2u8);
1583            for _oi in 0..4 {
1584                let (sc1, m1) = q4_k_scale_min(is, &scales);
1585                let (sc2, m2) = q4_k_scale_min(is + 1, &scales);
1586                let d1 = d * sc1 as f32;
1587                let min1 = dmin * m1 as f32;
1588                let d2 = d * sc2 as f32;
1589                let min2 = dmin * m2 as f32;
1590                let ql = &qs[is / 2 * 32..is / 2 * 32 + 32];
1591                let u1_vec = _mm_set1_epi8(u1 as i8);
1592                let u2_vec = _mm_set1_epi8(u2 as i8);
1593
1594                let mut lo_acc = _mm256_setzero_ps();
1595                let mut hi_acc = _mm256_setzero_ps();
1596                for g in 0..2 {
1597                    let raw16 = _mm_loadu_si128(ql.as_ptr().add(g * 16) as *const __m128i);
1598                    let qh16 = _mm_loadu_si128(qh.as_ptr().add(g * 16) as *const __m128i);
1599
1600                    let lo_nib = _mm_and_si128(raw16, low_mask);
1601                    let hi_nib = _mm_and_si128(_mm_srli_epi16(raw16, 4), low_mask);
1602
1603                    let is_zero1 = _mm_cmpeq_epi8(_mm_and_si128(qh16, u1_vec), zero);
1604                    let hi_bit1 = _mm_andnot_si128(is_zero1, sixteen);
1605                    let is_zero2 = _mm_cmpeq_epi8(_mm_and_si128(qh16, u2_vec), zero);
1606                    let hi_bit2 = _mm_andnot_si128(is_zero2, sixteen);
1607
1608                    let lo_full = _mm_or_si128(lo_nib, hi_bit1);
1609                    let hi_full = _mm_or_si128(hi_nib, hi_bit2);
1610
1611                    for (part_idx, part) in [lo_full, _mm_srli_si128(lo_full, 8)]
1612                        .into_iter()
1613                        .enumerate()
1614                    {
1615                        lo_acc =
1616                            fma_affine8(part, d1, min1, x, x_base + g * 16 + part_idx * 8, lo_acc);
1617                    }
1618                    for (part_idx, part) in [hi_full, _mm_srli_si128(hi_full, 8)]
1619                        .into_iter()
1620                        .enumerate()
1621                    {
1622                        hi_acc = fma_affine8(
1623                            part,
1624                            d2,
1625                            min2,
1626                            x,
1627                            x_base + 32 + g * 16 + part_idx * 8,
1628                            hi_acc,
1629                        );
1630                    }
1631                }
1632                acc += hsum256_ps(lo_acc) + hsum256_ps(hi_acc);
1633                x_base += 64;
1634                is += 2;
1635                u1 <<= 2;
1636                u2 <<= 2;
1637            }
1638        }
1639        acc
1640    }
1641
1642    /// AVX2+FMA fused Q6_K dot product. Each 32-element group (`q1..q4`
1643    /// in the scalar reference) is processed 16 lanes at a time: the
1644    /// 6-bit value is `(ql nibble) | (qh 2-bit field << 4)`. Unlike the
1645    /// NEON kernel (which centers by `-32` in the signed-int domain
1646    /// before converting to f32), this widens the raw *unsigned* 0..=63
1647    /// value straight to f32 via `_mm256_cvtepu8_epi32` and subtracts
1648    /// `32.0` as a float afterward (`_mm256_sub_ps`) -- simpler here
1649    /// since x86 has no cheap signed-widen-with-bias trick to match
1650    /// NEON's, and float subtraction of a small exact integer bias from
1651    /// a small exact integer value is itself exact, so the two
1652    /// approaches agree bit-for-bit on every representable input. The
1653    /// `qh` 2-bit-field shift amount (0/2/4/6) must be a compile-time
1654    /// constant at `_mm_srli_epi16`'s call site (`rustc` rejects a
1655    /// plain runtime `i32` there with "attempt to use a non-constant
1656    /// value in a constant" -- confirmed directly, not assumed), hence
1657    /// `q6_k_group_avx2`'s `const QH_SHIFT` generic, monomorphized once
1658    /// per group at its four call sites below (unlike NEON's equivalent
1659    /// split, x86's shift-by-immediate accepts N=0 fine, so no separate
1660    /// zero-shift function is needed here). Safety: same contract as
1661    /// `dot_q8_0_f32_avx2`.
1662    #[inline]
1663    #[target_feature(enable = "avx2,fma")]
1664    #[allow(clippy::too_many_arguments)]
1665    unsafe fn q6_k_group_avx2<const QH_SHIFT: i32, const HI_NIBBLE: bool>(
1666        ql: &[u8],
1667        ql_off: usize,
1668        qh: &[u8],
1669        sc: &[u8],
1670        sc_base: usize,
1671        d: f32,
1672        x: &[f32],
1673        x_base: usize,
1674        out_off: usize,
1675        low_mask: __m128i,
1676        two_bit_mask: __m128i,
1677        bias: __m256,
1678    ) -> f32 {
1679        let mut acc = 0f32;
1680        for sub in 0..2usize {
1681            let byte_off = sub * 16;
1682            let ql_raw = _mm_loadu_si128(ql.as_ptr().add(ql_off + byte_off) as *const __m128i);
1683            let qh_raw = _mm_loadu_si128(qh.as_ptr().add(byte_off) as *const __m128i);
1684
1685            let nib = if HI_NIBBLE {
1686                _mm_and_si128(_mm_srli_epi16(ql_raw, 4), low_mask)
1687            } else {
1688                _mm_and_si128(ql_raw, low_mask)
1689            };
1690            let qh_field = _mm_and_si128(_mm_srli_epi16(qh_raw, QH_SHIFT), two_bit_mask);
1691            let raw6 = _mm_or_si128(nib, _mm_slli_epi16(qh_field, 4));
1692
1693            let scale = d * (sc[sc_base + sub] as i8) as f32;
1694            let elem_base = x_base + out_off + sub * 16;
1695            for (part_idx, part) in [raw6, _mm_srli_si128(raw6, 8)].into_iter().enumerate() {
1696                let i32x8 = _mm256_cvtepu8_epi32(part);
1697                let f32x8 = _mm256_sub_ps(_mm256_cvtepi32_ps(i32x8), bias);
1698                let xv = _mm256_loadu_ps(x.as_ptr().add(elem_base + part_idx * 8));
1699                let weighted = _mm256_mul_ps(f32x8, _mm256_set1_ps(scale));
1700                acc += hsum256_ps(_mm256_mul_ps(weighted, xv));
1701            }
1702        }
1703        acc
1704    }
1705
1706    /// AVX2+FMA fused Q6_K dot product: dispatches each of the four
1707    /// 32-element groups per half-block (`q1..q4` in the scalar
1708    /// reference) to `q6_k_group_avx2`, monomorphized once per group's
1709    /// (compile-time-constant) `qh` shift amount and nibble half.
1710    /// Safety: same contract as `dot_q8_0_f32_avx2`.
1711    #[target_feature(enable = "avx2,fma")]
1712    pub unsafe fn dot_q6_k_f32_avx2(row_bytes: &[u8], x: &[f32]) -> f32 {
1713        debug_assert_eq!(row_bytes.len() % Q6_K_BLOCK_BYTES, 0);
1714        debug_assert_eq!(
1715            row_bytes.len() / Q6_K_BLOCK_BYTES * Q6_K_BLOCK_ELEMS,
1716            x.len()
1717        );
1718        let low_mask = _mm_set1_epi8(0x0F);
1719        let two_bit_mask = _mm_set1_epi8(0x03);
1720        let bias = _mm256_set1_ps(32.0);
1721
1722        let mut acc = 0f32;
1723        let mut x_base = 0usize;
1724        for block in row_bytes.as_chunks::<Q6_K_BLOCK_BYTES>().0 {
1725            let ql_full = &block[0..128];
1726            let qh_full = &block[128..192];
1727            let sc_full = &block[192..208];
1728            let d = f16::from_le_bytes([block[208], block[209]]).to_f32();
1729
1730            for half in 0..2 {
1731                let ql = &ql_full[half * 64..half * 64 + 64];
1732                let qh = &qh_full[half * 32..half * 32 + 32];
1733                let sc = &sc_full[half * 8..half * 8 + 8];
1734                let half_base = x_base + half * 128;
1735
1736                acc += q6_k_group_avx2::<0, false>(
1737                    ql,
1738                    0,
1739                    qh,
1740                    sc,
1741                    0,
1742                    d,
1743                    x,
1744                    half_base,
1745                    0,
1746                    low_mask,
1747                    two_bit_mask,
1748                    bias,
1749                );
1750                acc += q6_k_group_avx2::<2, false>(
1751                    ql,
1752                    32,
1753                    qh,
1754                    sc,
1755                    2,
1756                    d,
1757                    x,
1758                    half_base,
1759                    32,
1760                    low_mask,
1761                    two_bit_mask,
1762                    bias,
1763                );
1764                acc += q6_k_group_avx2::<4, true>(
1765                    ql,
1766                    0,
1767                    qh,
1768                    sc,
1769                    4,
1770                    d,
1771                    x,
1772                    half_base,
1773                    64,
1774                    low_mask,
1775                    two_bit_mask,
1776                    bias,
1777                );
1778                acc += q6_k_group_avx2::<6, true>(
1779                    ql,
1780                    32,
1781                    qh,
1782                    sc,
1783                    6,
1784                    d,
1785                    x,
1786                    half_base,
1787                    96,
1788                    low_mask,
1789                    two_bit_mask,
1790                    bias,
1791                );
1792            }
1793            x_base += Q6_K_BLOCK_ELEMS;
1794        }
1795        acc
1796    }
1797
1798    /// Decodes 8 real E2M1 codebook values (one nibble byte per lane,
1799    /// each 0..=15, held in the low 8 bytes of `nib`) into `__m256`,
1800    /// arithmetically rather than via a 16-entry float lookup table --
1801    /// see `simd_aarch64::mxfp4_nibbles_to_f32_quads`'s doc comment for
1802    /// the derivation (identical formula, just AVX2 intrinsics:
1803    /// `_mm_shuffle_epi8` for the 2-bit-exponent -> `{pow2,bias}` lookup
1804    /// instead of NEON's `vqtbl1q_u8`, `_mm256_cvtepu8_epi32` to widen
1805    /// instead of NEON's `widen_u8x16_to_f32_quads`).
1806    #[inline]
1807    #[target_feature(enable = "avx2,fma")]
1808    unsafe fn mxfp4_nibbles_to_f32x8(nib: __m128i) -> __m256 {
1809        let sign_bit = _mm_and_si128(nib, _mm_set1_epi8(0x8));
1810        let e = _mm_and_si128(_mm_srli_epi16(nib, 1), _mm_set1_epi8(0x3));
1811        let m = _mm_and_si128(nib, _mm_set1_epi8(0x1));
1812
1813        let pow2_table = _mm_setr_epi8(1, 1, 2, 4, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0);
1814        let bias_table = _mm_setr_epi8(0, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0);
1815        let pow2_u8 = _mm_shuffle_epi8(pow2_table, e);
1816        let bias_u8 = _mm_shuffle_epi8(bias_table, e);
1817
1818        let pow2_f = _mm256_cvtepi32_ps(_mm256_cvtepu8_epi32(pow2_u8));
1819        let bias_f = _mm256_cvtepi32_ps(_mm256_cvtepu8_epi32(bias_u8));
1820        let m_f = _mm256_cvtepi32_ps(_mm256_cvtepu8_epi32(m));
1821        let sign_f = _mm256_cvtepi32_ps(_mm256_cvtepu8_epi32(sign_bit));
1822
1823        // magnitude = pow2 * (bias + 0.5*m); value = magnitude * (1 - 0.25*sign)
1824        let magnitude = _mm256_mul_ps(pow2_f, _mm256_fmadd_ps(m_f, _mm256_set1_ps(0.5), bias_f));
1825        let sign_mul = _mm256_fnmadd_ps(sign_f, _mm256_set1_ps(0.25), _mm256_set1_ps(1.0));
1826        _mm256_mul_ps(magnitude, sign_mul)
1827    }
1828
1829    /// AVX2+FMA fused MXFP4 dequant+dot -- same real math as
1830    /// `dot_mxfp4_row_f32_scalar` (real E2M1 codebook + E8M0 scale),
1831    /// decoded via `mxfp4_nibbles_to_f32x8` instead of the scalar
1832    /// path's 16-entry `KVALUES_MXFP4` table lookup. Cross-validated
1833    /// against the scalar reference across many packed-byte patterns
1834    /// (see this module's tests) -- CI runs this on real x86_64
1835    /// hardware, matching the project's established
1836    /// verify-on-real-hardware-not-just-compile discipline for every
1837    /// other AVX2 kernel here.
1838    pub unsafe fn dot_mxfp4_row_f32_avx2(packed: &[u8], scales: &[u8], x: &[f32]) -> f32 {
1839        debug_assert_eq!(packed.len(), scales.len() * (MXFP4_GROUP_SIZE / 2));
1840        let low_mask = _mm_set1_epi8(0x0F);
1841        let mut acc = 0f32;
1842        let mut x_base = 0usize;
1843        for (g, &e_byte) in scales.iter().enumerate() {
1844            let d = e8m0_scale(e_byte);
1845            let group = &packed[g * 16..(g + 1) * 16];
1846            let bytes = _mm_loadu_si128(group.as_ptr() as *const __m128i);
1847            let lo_nib = _mm_and_si128(bytes, low_mask);
1848            let hi_nib = _mm_and_si128(_mm_srli_epi16(bytes, 4), low_mask);
1849
1850            let mut block_acc = _mm256_setzero_ps();
1851            for (half_idx, nib) in [
1852                (0usize, lo_nib),
1853                (1usize, _mm_srli_si128(lo_nib, 8)),
1854                (2usize, hi_nib),
1855                (3usize, _mm_srli_si128(hi_nib, 8)),
1856            ] {
1857                let vals = mxfp4_nibbles_to_f32x8(nib);
1858                let elem_base = x_base + half_idx * 8;
1859                let xv = _mm256_loadu_ps(x.as_ptr().add(elem_base));
1860                block_acc = _mm256_fmadd_ps(vals, xv, block_acc);
1861            }
1862            acc += hsum256_ps(block_acc) * d;
1863            x_base += MXFP4_GROUP_SIZE;
1864        }
1865        acc
1866    }
1867
1868    /// AVX2+FMA fused Q8_1 dot product. Mathematically identical to
1869    /// `dot_q8_0_f32_avx2` (`y = q*d`, no `min` term) -- Q8_1's block
1870    /// just has an extra 2-byte field between `d` and the int8 values,
1871    /// so the quantized bytes start at offset 4 instead of offset 2.
1872    /// Safety: same contract as `dot_q8_0_f32_avx2`.
1873    #[target_feature(enable = "avx2,fma")]
1874    pub unsafe fn dot_q8_1_f32_avx2(row_bytes: &[u8], x: &[f32]) -> f32 {
1875        debug_assert_eq!(row_bytes.len() % Q8_1_BLOCK_BYTES, 0);
1876        let mut acc = 0f32;
1877        for (b, block) in row_bytes
1878            .as_chunks::<Q8_1_BLOCK_BYTES>()
1879            .0
1880            .iter()
1881            .enumerate()
1882        {
1883            let d = f16::from_le_bytes([block[0], block[1]]).to_f32();
1884            let base = b * Q8_1_BLOCK_ELEMS;
1885            let qs = &block[4..36];
1886
1887            let mut block_acc = _mm256_setzero_ps();
1888            for g in 0..4 {
1889                let raw8 = _mm_loadl_epi64(qs.as_ptr().add(g * 8) as *const __m128i);
1890                let i32x8 = _mm256_cvtepi8_epi32(raw8);
1891                let f32x8 = _mm256_cvtepi32_ps(i32x8);
1892                let xv = _mm256_loadu_ps(x.as_ptr().add(base + g * 8));
1893                block_acc = _mm256_fmadd_ps(f32x8, xv, block_acc);
1894            }
1895            acc += hsum256_ps(block_acc) * d;
1896        }
1897        acc
1898    }
1899
1900    /// AVX2+FMA fused Q4_1 dot product. Same nibble-splitting structure
1901    /// as `dot_q4_0_f32_avx2`, but asymmetric (`y = nibble*d + m`, no
1902    /// bias subtraction) -- reuses `fma_affine8` (which computes `q*d -
1903    /// min`) by passing `-m` as `min`, since `q*d - (-m) == q*d + m`.
1904    /// Safety: same contract as `dot_q8_0_f32_avx2`.
1905    #[target_feature(enable = "avx2,fma")]
1906    pub unsafe fn dot_q4_1_f32_avx2(row_bytes: &[u8], x: &[f32]) -> f32 {
1907        debug_assert_eq!(row_bytes.len() % Q4_1_BLOCK_BYTES, 0);
1908        let low_mask = _mm_set1_epi8(0x0F);
1909        let mut acc = 0f32;
1910        for (b, block) in row_bytes
1911            .as_chunks::<Q4_1_BLOCK_BYTES>()
1912            .0
1913            .iter()
1914            .enumerate()
1915        {
1916            let d = f16::from_le_bytes([block[0], block[1]]).to_f32();
1917            let m = f16::from_le_bytes([block[2], block[3]]).to_f32();
1918            let base = b * Q4_1_BLOCK_ELEMS;
1919            let nibbles = _mm_loadu_si128(block.as_ptr().add(4) as *const __m128i);
1920
1921            let lo_nibbles = _mm_and_si128(nibbles, low_mask);
1922            let hi_nibbles = _mm_and_si128(_mm_srli_epi16(nibbles, 4), low_mask);
1923
1924            let mut lo_acc = _mm256_setzero_ps();
1925            let mut hi_acc = _mm256_setzero_ps();
1926            for (part_idx, part) in [lo_nibbles, _mm_srli_si128(lo_nibbles, 8)]
1927                .into_iter()
1928                .enumerate()
1929            {
1930                lo_acc = fma_affine8(part, d, -m, x, base + part_idx * 8, lo_acc);
1931            }
1932            for (part_idx, part) in [hi_nibbles, _mm_srli_si128(hi_nibbles, 8)]
1933                .into_iter()
1934                .enumerate()
1935            {
1936                hi_acc = fma_affine8(part, d, -m, x, base + 16 + part_idx * 8, hi_acc);
1937            }
1938            acc += hsum256_ps(lo_acc) + hsum256_ps(hi_acc);
1939        }
1940        acc
1941    }
1942
1943    /// AVX2+FMA fused Q5_0 dot product. The 5th-bit-per-element
1944    /// extraction (`q5_fifth_bits`) is done in scalar prep, once per
1945    /// block, into a stack-local `[i8; 32]` array (each value already
1946    /// includes the `-16` symmetric bias) -- deliberately not
1947    /// vectorized, since the real per-lane-varying bit-position test
1948    /// this needs is a correctness-sensitive detail not worth risking a
1949    /// hand-rolled SIMD mistake on for a single already-small (16-bit)
1950    /// bitplane; the actual per-element multiply-accumulate over all 32
1951    /// elements, where the real throughput cost lives, is fully
1952    /// vectorized exactly like `dot_q8_0_f32_avx2`. Safety: same
1953    /// contract as `dot_q8_0_f32_avx2`.
1954    #[target_feature(enable = "avx2,fma")]
1955    pub unsafe fn dot_q5_0_f32_avx2(row_bytes: &[u8], x: &[f32]) -> f32 {
1956        debug_assert_eq!(row_bytes.len() % Q5_0_BLOCK_BYTES, 0);
1957        let mut acc = 0f32;
1958        for (b, block) in row_bytes
1959            .as_chunks::<Q5_0_BLOCK_BYTES>()
1960            .0
1961            .iter()
1962            .enumerate()
1963        {
1964            let d = f16::from_le_bytes([block[0], block[1]]).to_f32();
1965            let qh = u32::from_le_bytes(block[2..6].try_into().unwrap());
1966            let qs = &block[6..22];
1967            let base = b * Q5_0_BLOCK_ELEMS;
1968
1969            let mut vals = [0i8; 32];
1970            for j in 0..16 {
1971                let (xh_0, xh_1) = q5_fifth_bits(qh, j);
1972                vals[j] = (((qs[j] & 0x0F) | xh_0) as i32 - 16) as i8;
1973                vals[j + 16] = (((qs[j] >> 4) | xh_1) as i32 - 16) as i8;
1974            }
1975
1976            let mut block_acc = _mm256_setzero_ps();
1977            for g in 0..4 {
1978                let raw8 = _mm_loadl_epi64(vals.as_ptr().add(g * 8) as *const __m128i);
1979                let i32x8 = _mm256_cvtepi8_epi32(raw8);
1980                let f32x8 = _mm256_cvtepi32_ps(i32x8);
1981                let xv = _mm256_loadu_ps(x.as_ptr().add(base + g * 8));
1982                block_acc = _mm256_fmadd_ps(f32x8, xv, block_acc);
1983            }
1984            acc += hsum256_ps(block_acc) * d;
1985        }
1986        acc
1987    }
1988
1989    /// AVX2+FMA fused Q5_1 dot product. Same 5th-bit scalar-prep
1990    /// approach as `dot_q5_0_f32_avx2`, but asymmetric (`y = q*d + m`,
1991    /// no `-16` bias) -- see that function's doc comment for why the
1992    /// bit extraction stays scalar. Safety: same contract as
1993    /// `dot_q8_0_f32_avx2`.
1994    #[target_feature(enable = "avx2,fma")]
1995    pub unsafe fn dot_q5_1_f32_avx2(row_bytes: &[u8], x: &[f32]) -> f32 {
1996        debug_assert_eq!(row_bytes.len() % Q5_1_BLOCK_BYTES, 0);
1997        let mut acc = 0f32;
1998        for (b, block) in row_bytes
1999            .as_chunks::<Q5_1_BLOCK_BYTES>()
2000            .0
2001            .iter()
2002            .enumerate()
2003        {
2004            let d = f16::from_le_bytes([block[0], block[1]]).to_f32();
2005            let m = f16::from_le_bytes([block[2], block[3]]).to_f32();
2006            let qh = u32::from_le_bytes(block[4..8].try_into().unwrap());
2007            let qs = &block[8..24];
2008            let base = b * Q5_1_BLOCK_ELEMS;
2009
2010            let mut vals = [0u8; 32];
2011            for j in 0..16 {
2012                let (xh_0, xh_1) = q5_fifth_bits(qh, j);
2013                vals[j] = (qs[j] & 0x0F) | xh_0;
2014                vals[j + 16] = (qs[j] >> 4) | xh_1;
2015            }
2016
2017            let mut block_acc = _mm256_setzero_ps();
2018            for g in 0..4 {
2019                let raw8 = _mm_loadl_epi64(vals.as_ptr().add(g * 8) as *const __m128i);
2020                let i32x8 = _mm256_cvtepu8_epi32(raw8);
2021                let f32x8 = _mm256_cvtepi32_ps(i32x8);
2022                let weight = _mm256_fmadd_ps(f32x8, _mm256_set1_ps(d), _mm256_set1_ps(m));
2023                let xv = _mm256_loadu_ps(x.as_ptr().add(base + g * 8));
2024                block_acc = _mm256_fmadd_ps(weight, xv, block_acc);
2025            }
2026            acc += hsum256_ps(block_acc);
2027        }
2028        acc
2029    }
2030
2031    /// AVX2+FMA fused Q2_K dot product. Mirrors `dot_q4_k_f32_avx2`'s
2032    /// sub-block loop, but each element is a 2-bit value (`(byte >>
2033    /// shift) & 3`) instead of a nibble, and each sub-block's
2034    /// (scale, min) is one plain byte (`sc & 0x0F` / `sc >> 4`), not
2035    /// Q4_K's cross-byte 6-bit packing. `shift` only ever takes the
2036    /// values 0/2/4/6, and `_mm_srli_epi16` requires a compile-time-
2037    /// constant shift amount, so the 4 shift values are unrolled as 4
2038    /// literal call sites via this macro rather than a runtime loop --
2039    /// same reason this file's `q6_k_group_avx2` takes `QH_SHIFT` as a
2040    /// const generic. The same "shift 16-bit lanes, mask per byte"
2041    /// trick `dot_q4_0_f32_avx2` uses for nibbles generalizes exactly
2042    /// to 2-bit fields: masking with `0x03` after `_mm_srli_epi16`
2043    /// discards the neighboring byte's bits that leak into the shift,
2044    /// for any of the 4 shift amounts. Safety: same contract as
2045    /// `dot_q8_0_f32_avx2`.
2046    #[target_feature(enable = "avx2,fma")]
2047    pub unsafe fn dot_q2_k_f32_avx2(row_bytes: &[u8], x: &[f32]) -> f32 {
2048        debug_assert_eq!(row_bytes.len() % Q2_K_BLOCK_BYTES, 0);
2049        let two_bit_mask = _mm_set1_epi8(3);
2050        let mut acc = 0f32;
2051        let mut x_base = 0usize;
2052
2053        macro_rules! q2_k_sub_block {
2054            ($shift:literal, $q:expr, $scales:expr, $is:expr, $d:expr, $dmin:expr, $x:expr, $x_base:expr, $acc:expr) => {{
2055                let sc1 = $scales[$is];
2056                $is += 1;
2057                let dl1 = $d * (sc1 & 0x0F) as f32;
2058                let ml1 = $dmin * (sc1 >> 4) as f32;
2059                let sc2 = $scales[$is];
2060                $is += 1;
2061                let dl2 = $d * (sc2 & 0x0F) as f32;
2062                let ml2 = $dmin * (sc2 >> 4) as f32;
2063
2064                let lo16 = _mm_loadu_si128($q.as_ptr() as *const __m128i);
2065                let hi16 = _mm_loadu_si128($q.as_ptr().add(16) as *const __m128i);
2066                let lo2 = _mm_and_si128(_mm_srli_epi16(lo16, $shift), two_bit_mask);
2067                let hi2 = _mm_and_si128(_mm_srli_epi16(hi16, $shift), two_bit_mask);
2068
2069                let mut lo_acc = _mm256_setzero_ps();
2070                let mut hi_acc = _mm256_setzero_ps();
2071                for (part_idx, part) in [lo2, _mm_srli_si128(lo2, 8)].into_iter().enumerate() {
2072                    lo_acc = fma_affine8(part, dl1, ml1, $x, $x_base + part_idx * 8, lo_acc);
2073                }
2074                for (part_idx, part) in [hi2, _mm_srli_si128(hi2, 8)].into_iter().enumerate() {
2075                    hi_acc = fma_affine8(part, dl2, ml2, $x, $x_base + 16 + part_idx * 8, hi_acc);
2076                }
2077                $acc += hsum256_ps(lo_acc) + hsum256_ps(hi_acc);
2078                $x_base += 32;
2079            }};
2080        }
2081
2082        for block in row_bytes.as_chunks::<Q2_K_BLOCK_BYTES>().0 {
2083            let scales: &[u8; Q2_K_SCALE_BYTES] = block[0..16].try_into().unwrap();
2084            let qs = &block[16..80];
2085            let d = f16::from_le_bytes([block[80], block[81]]).to_f32();
2086            let dmin = f16::from_le_bytes([block[82], block[83]]).to_f32();
2087
2088            let mut is = 0usize;
2089            for n in 0..2 {
2090                let q = &qs[n * 32..n * 32 + 32];
2091                q2_k_sub_block!(0, q, scales, is, d, dmin, x, x_base, acc);
2092                q2_k_sub_block!(2, q, scales, is, d, dmin, x, x_base, acc);
2093                q2_k_sub_block!(4, q, scales, is, d, dmin, x, x_base, acc);
2094                q2_k_sub_block!(6, q, scales, is, d, dmin, x, x_base, acc);
2095            }
2096        }
2097        acc
2098    }
2099
2100    /// AVX2+FMA fused Q3_K dot product. Same 2-bit-field extraction
2101    /// trick as `dot_q2_k_f32_avx2` (shift-then-mask, 4 literal shift
2102    /// values), plus a 3rd bit tested from `hmask` the same way
2103    /// `dot_q5_k_f32_avx2` tests Q5_K's 5th bit (`_mm_cmpeq_epi8`
2104    /// against zero, inverted, since the tested bit position `m` sweeps
2105    /// up to `0x80`, which as signed i8 would misclassify under a
2106    /// signed greater-than test). `bias` (4 or 0) is applied as a
2107    /// per-lane select between two constant vectors rather than a
2108    /// branch. The 6-bit per-sub-block scale unpacking
2109    /// (`q3_k_unpack_scales`) runs once per block on the scalar side
2110    /// (cheap, real bit-shuffling not worth vectorizing for a
2111    /// once-per-block cost), reusing the existing scalar helper exactly
2112    /// rather than re-deriving it. Safety: same contract as
2113    /// `dot_q8_0_f32_avx2`.
2114    #[target_feature(enable = "avx2,fma")]
2115    pub unsafe fn dot_q3_k_f32_avx2(row_bytes: &[u8], x: &[f32]) -> f32 {
2116        debug_assert_eq!(row_bytes.len() % Q3_K_BLOCK_BYTES, 0);
2117        let two_bit_mask = _mm_set1_epi8(3);
2118        let zero = _mm_setzero_si128();
2119        let four = _mm_set1_epi8(4);
2120        let mut acc = 0f32;
2121        let mut x_base = 0usize;
2122
2123        macro_rules! q3_k_sub_block {
2124            ($shift:literal, $q:expr, $hmask:expr, $m_vec:expr, $dl1:expr, $dl2:expr, $x:expr, $x_base:expr, $acc:expr) => {{
2125                let lo16 = _mm_loadu_si128($q.as_ptr() as *const __m128i);
2126                let hi16 = _mm_loadu_si128($q.as_ptr().add(16) as *const __m128i);
2127                let lo2 = _mm_and_si128(_mm_srli_epi16(lo16, $shift), two_bit_mask);
2128                let hi2 = _mm_and_si128(_mm_srli_epi16(hi16, $shift), two_bit_mask);
2129
2130                let hmask_lo = _mm_loadu_si128($hmask.as_ptr() as *const __m128i);
2131                let hmask_hi = _mm_loadu_si128($hmask.as_ptr().add(16) as *const __m128i);
2132                // bit_clear_* is all-ones (0xFF) per lane where the hmask bit is
2133                // CLEAR (bias=4), all-zero where it's set (bias=0) -- matching
2134                // the scalar reference's `if hmask[l] & m != 0 { 0 } else { 4 }`.
2135                let bit_clear_lo = _mm_cmpeq_epi8(_mm_and_si128(hmask_lo, $m_vec), zero);
2136                let bit_clear_hi = _mm_cmpeq_epi8(_mm_and_si128(hmask_hi, $m_vec), zero);
2137                let bias_lo = _mm_and_si128(bit_clear_lo, four);
2138                let bias_hi = _mm_and_si128(bit_clear_hi, four);
2139                let raw_lo = _mm_sub_epi8(lo2, bias_lo);
2140                let raw_hi = _mm_sub_epi8(hi2, bias_hi);
2141
2142                let mut lo_acc = _mm256_setzero_ps();
2143                let mut hi_acc = _mm256_setzero_ps();
2144                for (part_idx, part) in [raw_lo, _mm_srli_si128(raw_lo, 8)].into_iter().enumerate()
2145                {
2146                    let i32x8 = _mm256_cvtepi8_epi32(part);
2147                    let f32x8 = _mm256_cvtepi32_ps(i32x8);
2148                    let xv = _mm256_loadu_ps($x.as_ptr().add($x_base + part_idx * 8));
2149                    lo_acc = _mm256_fmadd_ps(f32x8, xv, lo_acc);
2150                }
2151                for (part_idx, part) in [raw_hi, _mm_srli_si128(raw_hi, 8)].into_iter().enumerate()
2152                {
2153                    let i32x8 = _mm256_cvtepi8_epi32(part);
2154                    let f32x8 = _mm256_cvtepi32_ps(i32x8);
2155                    let xv = _mm256_loadu_ps($x.as_ptr().add($x_base + 16 + part_idx * 8));
2156                    hi_acc = _mm256_fmadd_ps(f32x8, xv, hi_acc);
2157                }
2158                $acc += hsum256_ps(lo_acc) * $dl1 + hsum256_ps(hi_acc) * $dl2;
2159                $x_base += 32;
2160            }};
2161        }
2162
2163        for block in row_bytes.as_chunks::<Q3_K_BLOCK_BYTES>().0 {
2164            let hmask = &block[0..32];
2165            let qs = &block[32..96];
2166            let scales_raw: &[u8; Q3_K_SCALE_BYTES] = block[96..108].try_into().unwrap();
2167            let d_all = f16::from_le_bytes([block[108], block[109]]).to_f32();
2168            let scales = q3_k_unpack_scales(scales_raw);
2169
2170            let mut is = 0usize;
2171            let mut m = 1u8;
2172            for n in 0..2 {
2173                let q = &qs[n * 32..n * 32 + 32];
2174                for shift in [0u32, 2, 4, 6] {
2175                    let dl1 = d_all * (scales[is] as f32 - 32.0);
2176                    let dl2 = d_all * (scales[is + 1] as f32 - 32.0);
2177                    is += 2;
2178                    let m_vec = _mm_set1_epi8(m as i8);
2179                    match shift {
2180                        0 => q3_k_sub_block!(0, q, hmask, m_vec, dl1, dl2, x, x_base, acc),
2181                        2 => q3_k_sub_block!(2, q, hmask, m_vec, dl1, dl2, x, x_base, acc),
2182                        4 => q3_k_sub_block!(4, q, hmask, m_vec, dl1, dl2, x, x_base, acc),
2183                        6 => q3_k_sub_block!(6, q, hmask, m_vec, dl1, dl2, x, x_base, acc),
2184                        _ => unreachable!(),
2185                    }
2186                    m <<= 1;
2187                }
2188            }
2189        }
2190        acc
2191    }
2192
2193    /// AVX2 fused IQ4_NL dot product. `KVALUES_IQ4NL`'s 16 entries are
2194    /// arbitrary (non-arithmetic) signed values, so unlike MXFP4's
2195    /// bit-twiddled reconstruction, the natural AVX2 idiom is a direct
2196    /// 16-entry table lookup via `_mm_shuffle_epi8` (`pshufb`), which is
2197    /// exactly a 4-bit-index-into-16-byte-table lookup within each
2198    /// 128-bit lane -- precisely this shape. Safety: same contract as
2199    /// `dot_q8_0_f32_avx2`.
2200    #[target_feature(enable = "avx2,fma")]
2201    pub unsafe fn dot_iq4_nl_f32_avx2(row_bytes: &[u8], x: &[f32]) -> f32 {
2202        debug_assert_eq!(row_bytes.len() % IQ4_NL_BLOCK_BYTES, 0);
2203        let low_mask = _mm_set1_epi8(0x0F);
2204        let codebook = _mm_loadu_si128(KVALUES_IQ4NL.as_ptr() as *const __m128i);
2205        let mut acc = 0f32;
2206        let mut x_base = 0usize;
2207        for block in row_bytes.as_chunks::<IQ4_NL_BLOCK_BYTES>().0 {
2208            let d = f16::from_le_bytes([block[0], block[1]]).to_f32();
2209            let qs = &block[2..18];
2210            let bytes = _mm_loadu_si128(qs.as_ptr() as *const __m128i);
2211            let lo_idx = _mm_and_si128(bytes, low_mask);
2212            let hi_idx = _mm_and_si128(_mm_srli_epi16(bytes, 4), low_mask);
2213            let lo_vals = _mm_shuffle_epi8(codebook, lo_idx);
2214            let hi_vals = _mm_shuffle_epi8(codebook, hi_idx);
2215
2216            let mut block_acc = _mm256_setzero_ps();
2217            for (half_idx, vals) in [
2218                (0usize, lo_vals),
2219                (1usize, _mm_srli_si128(lo_vals, 8)),
2220                (2usize, hi_vals),
2221                (3usize, _mm_srli_si128(hi_vals, 8)),
2222            ] {
2223                let i32x8 = _mm256_cvtepi8_epi32(vals);
2224                let f32x8 = _mm256_cvtepi32_ps(i32x8);
2225                let xv = _mm256_loadu_ps(x.as_ptr().add(x_base + half_idx * 8));
2226                block_acc = _mm256_fmadd_ps(f32x8, xv, block_acc);
2227            }
2228            acc += hsum256_ps(block_acc) * d;
2229            x_base += IQ4_NL_BLOCK_ELEMS;
2230        }
2231        acc
2232    }
2233
2234    /// AVX2 fused IQ4_XS dot product. Same codebook lookup as
2235    /// `dot_iq4_nl_f32_avx2`, repeated per 32-element sub-block (8 per
2236    /// 256-element block), each with its own 6-bit scale unpacked
2237    /// exactly as the scalar reference does (once per sub-block, cheap,
2238    /// not vectorized). Safety: same contract as `dot_q8_0_f32_avx2`.
2239    #[target_feature(enable = "avx2,fma")]
2240    pub unsafe fn dot_iq4_xs_f32_avx2(row_bytes: &[u8], x: &[f32]) -> f32 {
2241        debug_assert_eq!(row_bytes.len() % IQ4_XS_BLOCK_BYTES, 0);
2242        let low_mask = _mm_set1_epi8(0x0F);
2243        let codebook = _mm_loadu_si128(KVALUES_IQ4NL.as_ptr() as *const __m128i);
2244        let mut acc = 0f32;
2245        let mut x_base = 0usize;
2246        for block in row_bytes.as_chunks::<IQ4_XS_BLOCK_BYTES>().0 {
2247            let d = f16::from_le_bytes([block[0], block[1]]).to_f32();
2248            let scales_h = u16::from_le_bytes([block[2], block[3]]);
2249            let scales_l = &block[4..8];
2250            let qs = &block[8..136];
2251
2252            for ib in 0..8 {
2253                let ls = ((scales_l[ib / 2] >> (4 * (ib % 2))) & 0xf)
2254                    | (((scales_h >> (2 * ib)) & 3) as u8) << 4;
2255                let dl = d * (ls as f32 - 32.0);
2256                let sub = &qs[ib * 16..ib * 16 + 16];
2257                let bytes = _mm_loadu_si128(sub.as_ptr() as *const __m128i);
2258                let lo_idx = _mm_and_si128(bytes, low_mask);
2259                let hi_idx = _mm_and_si128(_mm_srli_epi16(bytes, 4), low_mask);
2260                let lo_vals = _mm_shuffle_epi8(codebook, lo_idx);
2261                let hi_vals = _mm_shuffle_epi8(codebook, hi_idx);
2262
2263                let mut sub_acc = _mm256_setzero_ps();
2264                for (half_idx, vals) in [
2265                    (0usize, lo_vals),
2266                    (1usize, _mm_srli_si128(lo_vals, 8)),
2267                    (2usize, hi_vals),
2268                    (3usize, _mm_srli_si128(hi_vals, 8)),
2269                ] {
2270                    let i32x8 = _mm256_cvtepi8_epi32(vals);
2271                    let f32x8 = _mm256_cvtepi32_ps(i32x8);
2272                    let xv = _mm256_loadu_ps(x.as_ptr().add(x_base + half_idx * 8));
2273                    sub_acc = _mm256_fmadd_ps(f32x8, xv, sub_acc);
2274                }
2275                acc += hsum256_ps(sub_acc) * dl;
2276                x_base += 32;
2277            }
2278        }
2279        acc
2280    }
2281
2282    /// Expands one 8-value grid row of *unsigned* byte magnitudes into
2283    /// 8 f32 lanes with the format's per-element signs applied --
2284    /// shared by the IQ2_XXS/IQ3_XXS kernels below. `signs` is the
2285    /// 8-bit `ksigns_iq2xs` pattern for this row; a set bit `j` (the
2286    /// same `kmask_iq2xs` convention the scalar path uses) negates
2287    /// lane `j`, done here by XORing the f32 sign bit from a bit-test
2288    /// mask rather than multiplying by ±1.0.
2289    #[inline]
2290    #[target_feature(enable = "avx2", enable = "fma")]
2291    unsafe fn iq_grid_row_signed_f32(row_le: u64, signs: u8) -> __m256 {
2292        let mags = _mm256_cvtepi32_ps(_mm256_cvtepu8_epi32(_mm_set_epi64x(0, row_le as i64)));
2293        let bit_mask = _mm256_setr_epi32(1, 2, 4, 8, 16, 32, 64, 128);
2294        let bits = _mm256_and_si256(_mm256_set1_epi32(signs as i32), bit_mask);
2295        let neg = _mm256_cmpeq_epi32(bits, bit_mask);
2296        let sign_bit = _mm256_and_si256(neg, _mm256_set1_epi32(0x8000_0000_u32 as i32));
2297        _mm256_xor_ps(mags, _mm256_castsi256_ps(sign_bit))
2298    }
2299
2300    /// AVX2+FMA fused IQ1_S dot: same walk as the scalar reference
2301    /// (grid rows of signed int8, per-group scale `dl` and additive
2302    /// `delta`), vectorized 8 elements at a time. Verified directly
2303    /// against the scalar path on real x86_64 hardware (this module's
2304    /// tests), whose goldens are themselves cross-validated against
2305    /// the compiled ggml implementation.
2306    #[target_feature(enable = "avx2", enable = "fma")]
2307    pub unsafe fn dot_iq1_s_f32_avx2(row_bytes: &[u8], x: &[f32]) -> f32 {
2308        debug_assert_eq!(row_bytes.len() % crate::IQ1_S_BLOCK_BYTES, 0);
2309        let mut acc = _mm256_setzero_ps();
2310        let mut x_base = 0usize;
2311        for block in row_bytes.as_chunks::<{ crate::IQ1_S_BLOCK_BYTES }>().0 {
2312            let d = f16::from_le_bytes([block[0], block[1]]).to_f32();
2313            let qs = &block[2..34];
2314            let qh = &block[34..50];
2315            for ib in 0..8 {
2316                let h = u16::from_le_bytes([qh[2 * ib], qh[2 * ib + 1]]);
2317                let dl = d * (2.0 * ((h >> 12) & 7) as f32 + 1.0);
2318                let delta = if h & 0x8000 != 0 {
2319                    -crate::IQ1S_DELTA
2320                } else {
2321                    crate::IQ1S_DELTA
2322                };
2323                let dl_v = _mm256_set1_ps(dl);
2324                let delta_v = _mm256_set1_ps(delta);
2325                for l in 0..4 {
2326                    let idx = qs[4 * ib + l] as usize | ((((h >> (3 * l)) & 7) as usize) << 8);
2327                    let row = crate::iq_tables::IQ1S_GRID[idx];
2328                    let g = _mm256_cvtepi32_ps(_mm256_cvtepi8_epi32(_mm_set_epi64x(0, row as i64)));
2329                    let vals = _mm256_mul_ps(dl_v, _mm256_add_ps(g, delta_v));
2330                    let xv = _mm256_loadu_ps(x.as_ptr().add(x_base));
2331                    acc = _mm256_fmadd_ps(vals, xv, acc);
2332                    x_base += 8;
2333                }
2334            }
2335        }
2336        hsum256_ps(acc)
2337    }
2338
2339    /// AVX2+FMA fused IQ2_XXS dot -- same decode as the scalar
2340    /// reference (u16 codes -> grid rows + ksigns patterns + packed
2341    /// 4-bit group scale), 8 elements per FMA. Verification: see
2342    /// `dot_iq1_s_f32_avx2`'s doc comment.
2343    #[target_feature(enable = "avx2", enable = "fma")]
2344    pub unsafe fn dot_iq2_xxs_f32_avx2(row_bytes: &[u8], x: &[f32]) -> f32 {
2345        debug_assert_eq!(row_bytes.len() % crate::IQ2_XXS_BLOCK_BYTES, 0);
2346        let mut acc = _mm256_setzero_ps();
2347        let mut x_base = 0usize;
2348        for block in row_bytes.as_chunks::<{ crate::IQ2_XXS_BLOCK_BYTES }>().0 {
2349            let d = f16::from_le_bytes([block[0], block[1]]).to_f32();
2350            for ib32 in 0..8 {
2351                let g0 = u16::from_le_bytes([block[2 + 8 * ib32], block[3 + 8 * ib32]]);
2352                let g1 = u16::from_le_bytes([block[4 + 8 * ib32], block[5 + 8 * ib32]]);
2353                let g2 = u16::from_le_bytes([block[6 + 8 * ib32], block[7 + 8 * ib32]]);
2354                let g3 = u16::from_le_bytes([block[8 + 8 * ib32], block[9 + 8 * ib32]]);
2355                let aux32_1 = g2 as u32 | ((g3 as u32) << 16);
2356                let db = _mm256_set1_ps(d * (0.5 + (aux32_1 >> 28) as f32) * 0.25);
2357                let aux8 = [
2358                    (g0 & 0xFF) as usize,
2359                    (g0 >> 8) as usize,
2360                    (g1 & 0xFF) as usize,
2361                    (g1 >> 8) as usize,
2362                ];
2363                for (l, &code) in aux8.iter().enumerate() {
2364                    let signs =
2365                        crate::iq_tables::KSIGNS_IQ2XS[((aux32_1 >> (7 * l)) & 127) as usize];
2366                    let vals = iq_grid_row_signed_f32(crate::iq_tables::IQ2XXS_GRID[code], signs);
2367                    let xv = _mm256_loadu_ps(x.as_ptr().add(x_base));
2368                    acc = _mm256_fmadd_ps(_mm256_mul_ps(db, vals), xv, acc);
2369                    x_base += 8;
2370                }
2371            }
2372        }
2373        hsum256_ps(acc)
2374    }
2375
2376    /// AVX2+FMA fused IQ3_XXS dot -- two u32 grid rows per 8 elements,
2377    /// combined into one 8-byte magnitude row, then the shared
2378    /// sign/scale path. Verification: see `dot_iq1_s_f32_avx2`'s doc
2379    /// comment.
2380    #[target_feature(enable = "avx2", enable = "fma")]
2381    pub unsafe fn dot_iq3_xxs_f32_avx2(row_bytes: &[u8], x: &[f32]) -> f32 {
2382        debug_assert_eq!(row_bytes.len() % crate::IQ3_XXS_BLOCK_BYTES, 0);
2383        let mut acc = _mm256_setzero_ps();
2384        let mut x_base = 0usize;
2385        for block in row_bytes.as_chunks::<{ crate::IQ3_XXS_BLOCK_BYTES }>().0 {
2386            let d = f16::from_le_bytes([block[0], block[1]]).to_f32();
2387            let qs = &block[2..66];
2388            let sas = &block[66..98];
2389            for ib32 in 0..8 {
2390                let aux32 = u32::from_le_bytes([
2391                    sas[4 * ib32],
2392                    sas[4 * ib32 + 1],
2393                    sas[4 * ib32 + 2],
2394                    sas[4 * ib32 + 3],
2395                ]);
2396                let db = _mm256_set1_ps(d * (0.5 + (aux32 >> 28) as f32) * 0.5);
2397                for l in 0..4 {
2398                    let signs = crate::iq_tables::KSIGNS_IQ2XS[((aux32 >> (7 * l)) & 127) as usize];
2399                    let r1 = crate::iq_tables::IQ3XXS_GRID[qs[8 * ib32 + 2 * l] as usize];
2400                    let r2 = crate::iq_tables::IQ3XXS_GRID[qs[8 * ib32 + 2 * l + 1] as usize];
2401                    let row = (r1 as u64) | ((r2 as u64) << 32);
2402                    let vals = iq_grid_row_signed_f32(row, signs);
2403                    let xv = _mm256_loadu_ps(x.as_ptr().add(x_base));
2404                    acc = _mm256_fmadd_ps(_mm256_mul_ps(db, vals), xv, acc);
2405                    x_base += 8;
2406                }
2407            }
2408        }
2409        hsum256_ps(acc)
2410    }
2411}
2412
2413/// ARM NEON kernels, mirroring `simd_x86`'s structure and math exactly
2414/// (same block layouts, same bias/scale handling) but using NEON's
2415/// 128-bit vectors: 16 int8 lanes per load instead of AVX2's 32-lane
2416/// (4x8) processing, widened in two steps (int8 -> int16 -> int32) via
2417/// `vmovl_*` rather than AVX2's single-step `_mm256_cvtepi8_epi32`,
2418/// since NEON has no direct int8-to-int32 widen instruction. NEON is
2419/// part of the aarch64 baseline ISA (unlike AVX2 on x86_64, which is
2420/// optional), so `is_aarch64_feature_detected!` is expected to always
2421/// return true on real aarch64 hardware -- kept for the same "detect,
2422/// don't assume" discipline the AVX2 dispatch uses, and so this
2423/// degrades gracefully if ever compiled for a hypothetical NEON-less
2424/// aarch64 target.
2425#[cfg(target_arch = "aarch64")]
2426mod simd_aarch64 {
2427    use super::{
2428        e8m0_scale, q3_k_unpack_scales, q4_k_scale_min, q5_fifth_bits, Q8Activations,
2429        Q8KActivations, IQ4_NL_BLOCK_BYTES, IQ4_NL_BLOCK_ELEMS, IQ4_XS_BLOCK_BYTES, KVALUES_IQ4NL,
2430        MXFP4_GROUP_SIZE, Q2_K_BLOCK_BYTES, Q2_K_SCALE_BYTES, Q3_K_BLOCK_BYTES, Q3_K_SCALE_BYTES,
2431        Q4_0_BLOCK_BYTES, Q4_0_BLOCK_ELEMS, Q4_1_BLOCK_BYTES, Q4_1_BLOCK_ELEMS, Q4_K_BLOCK_BYTES,
2432        Q4_K_BLOCK_ELEMS, Q4_K_SCALE_BYTES, Q5_0_BLOCK_BYTES, Q5_0_BLOCK_ELEMS, Q5_1_BLOCK_BYTES,
2433        Q5_1_BLOCK_ELEMS, Q5_K_BLOCK_BYTES, Q5_K_BLOCK_ELEMS, Q6_K_BLOCK_BYTES, Q6_K_BLOCK_ELEMS,
2434        Q8_0_BLOCK_BYTES, Q8_0_BLOCK_ELEMS, Q8_1_BLOCK_BYTES, Q8_1_BLOCK_ELEMS,
2435    };
2436    use half::f16;
2437    use std::arch::aarch64::*;
2438
2439    /// NEON fused Q8_0 dot product. Each 32-element block is processed
2440    /// as two 16-wide loads, each widened int8 -> int16 -> int32 (via
2441    /// `vmovl_s8` then `vmovl_s16`, splitting low/high halves with
2442    /// `vget_low`/`vget_high` at each step since NEON widening
2443    /// instructions only operate on 64-bit half-registers), converted
2444    /// to f32, and fused-multiply-accumulated against the matching
2445    /// activation values with `vfmaq_f32`, then horizontally summed
2446    /// with `vaddvq_f32` (an aarch64-only reduction intrinsic) and
2447    /// scaled by the block's shared f16 scale. Safety: caller must have
2448    /// already checked `is_aarch64_feature_detected!("neon")`; the
2449    /// function itself additionally asserts the buffer lengths line up,
2450    /// same as the scalar path.
2451    #[target_feature(enable = "neon")]
2452    pub unsafe fn dot_q8_0_f32_neon(row_bytes: &[u8], x: &[f32]) -> f32 {
2453        debug_assert_eq!(row_bytes.len() % Q8_0_BLOCK_BYTES, 0);
2454        debug_assert_eq!(
2455            row_bytes.len() / Q8_0_BLOCK_BYTES * Q8_0_BLOCK_ELEMS,
2456            x.len()
2457        );
2458        let mut acc = 0f32;
2459        for (b, block) in row_bytes
2460            .as_chunks::<Q8_0_BLOCK_BYTES>()
2461            .0
2462            .iter()
2463            .enumerate()
2464        {
2465            let scale = f16::from_le_bytes([block[0], block[1]]).to_f32();
2466            let base = b * Q8_0_BLOCK_ELEMS;
2467            let qs = &block[2..34];
2468
2469            let mut block_acc = vdupq_n_f32(0.0);
2470            for g in 0..2 {
2471                let raw16 = vld1q_s8(qs.as_ptr().add(g * 16) as *const i8);
2472                let lo16 = vmovl_s8(vget_low_s8(raw16));
2473                let hi16 = vmovl_s8(vget_high_s8(raw16));
2474                for (half_idx, half16) in [lo16, hi16].into_iter().enumerate() {
2475                    let lo32 = vmovl_s16(vget_low_s16(half16));
2476                    let hi32 = vmovl_s16(vget_high_s16(half16));
2477                    let f_lo = vcvtq_f32_s32(lo32);
2478                    let f_hi = vcvtq_f32_s32(hi32);
2479                    let elem_base = base + g * 16 + half_idx * 8;
2480                    let x_lo = vld1q_f32(x.as_ptr().add(elem_base));
2481                    let x_hi = vld1q_f32(x.as_ptr().add(elem_base + 4));
2482                    block_acc = vfmaq_f32(block_acc, f_lo, x_lo);
2483                    block_acc = vfmaq_f32(block_acc, f_hi, x_hi);
2484                }
2485            }
2486            acc += vaddvq_f32(block_acc) * scale;
2487        }
2488        acc
2489    }
2490
2491    /// NEON integer Q8_0 × Q8 dot via widening multiply (no SDOT).
2492    /// Prefer [`dot_q8_0_q8_neon_sdot`] when `dotprod` is available.
2493    #[target_feature(enable = "neon")]
2494    pub unsafe fn dot_q8_0_q8_neon(row_bytes: &[u8], act: &Q8Activations) -> f32 {
2495        debug_assert_eq!(row_bytes.len() % Q8_0_BLOCK_BYTES, 0);
2496        debug_assert_eq!(row_bytes.len() / Q8_0_BLOCK_BYTES, act.n_blocks());
2497        let mut acc = 0f32;
2498        for (b, block) in row_bytes
2499            .as_chunks::<Q8_0_BLOCK_BYTES>()
2500            .0
2501            .iter()
2502            .enumerate()
2503        {
2504            let dw = f16::from_le_bytes([block[0], block[1]]).to_f32();
2505            let base = b * Q8_0_BLOCK_ELEMS;
2506            let mut isum = vdupq_n_s32(0);
2507            for g in 0..2 {
2508                let w = vld1q_s8(block.as_ptr().add(2 + g * 16) as *const i8);
2509                let a = vld1q_s8(act.q.as_ptr().add(base + g * 16));
2510                let prod_lo = vmull_s8(vget_low_s8(w), vget_low_s8(a));
2511                let prod_hi = vmull_s8(vget_high_s8(w), vget_high_s8(a));
2512                isum = vpadalq_s16(isum, prod_lo);
2513                isum = vpadalq_s16(isum, prod_hi);
2514            }
2515            acc += dw * act.d[b] * vaddvq_s32(isum) as f32;
2516        }
2517        acc
2518    }
2519
2520    /// Stable SDOT via inline asm (`vdotq_s32` is nightly-only).
2521    #[target_feature(enable = "neon,dotprod")]
2522    unsafe fn neon_sdot(mut acc: int32x4_t, a: int8x16_t, b: int8x16_t) -> int32x4_t {
2523        std::arch::asm!(
2524            "sdot {acc:v}.4s, {a:v}.16b, {b:v}.16b",
2525            acc = inout(vreg) acc,
2526            a = in(vreg) a,
2527            b = in(vreg) b,
2528            options(pure, nomem, nostack),
2529        );
2530        acc
2531    }
2532
2533    /// NEON Q8_0 × Q8 int-dot with SDOT (Apple Silicon / ARMv8.2+).
2534    /// Two-block unroll + float4 scale-accumulate (llama.cpp ARM style).
2535    #[target_feature(enable = "neon,dotprod")]
2536    pub unsafe fn dot_q8_0_q8_neon_sdot(row_bytes: &[u8], act: &Q8Activations) -> f32 {
2537        debug_assert_eq!(row_bytes.len() % Q8_0_BLOCK_BYTES, 0);
2538        debug_assert_eq!(row_bytes.len() / Q8_0_BLOCK_BYTES, act.n_blocks());
2539        let nb = row_bytes.len() / Q8_0_BLOCK_BYTES;
2540        let mut sumv0 = vdupq_n_f32(0.0);
2541        let mut sumv1 = vdupq_n_f32(0.0);
2542        let mut b = 0usize;
2543        while b + 1 < nb {
2544            let block0 = row_bytes.as_ptr().add(b * Q8_0_BLOCK_BYTES);
2545            let block1 = row_bytes.as_ptr().add((b + 1) * Q8_0_BLOCK_BYTES);
2546            let dw0 = f16::from_le_bytes([*block0, *block0.add(1)]).to_f32();
2547            let dw1 = f16::from_le_bytes([*block1, *block1.add(1)]).to_f32();
2548            let base0 = b * Q8_0_BLOCK_ELEMS;
2549            let base1 = (b + 1) * Q8_0_BLOCK_ELEMS;
2550            let mut isum0 = vdupq_n_s32(0);
2551            let mut isum1 = vdupq_n_s32(0);
2552            for g in 0..2 {
2553                let w0 = vld1q_s8(block0.add(2 + g * 16) as *const i8);
2554                let w1 = vld1q_s8(block1.add(2 + g * 16) as *const i8);
2555                let a0 = vld1q_s8(act.q.as_ptr().add(base0 + g * 16));
2556                let a1 = vld1q_s8(act.q.as_ptr().add(base1 + g * 16));
2557                isum0 = neon_sdot(isum0, w0, a0);
2558                isum1 = neon_sdot(isum1, w1, a1);
2559            }
2560            sumv0 = vmlaq_n_f32(sumv0, vcvtq_f32_s32(isum0), dw0 * act.d[b]);
2561            sumv1 = vmlaq_n_f32(sumv1, vcvtq_f32_s32(isum1), dw1 * act.d[b + 1]);
2562            b += 2;
2563        }
2564        let mut acc = vaddvq_f32(sumv0) + vaddvq_f32(sumv1);
2565        if b < nb {
2566            let block = row_bytes.as_ptr().add(b * Q8_0_BLOCK_BYTES);
2567            let dw = f16::from_le_bytes([*block, *block.add(1)]).to_f32();
2568            let base = b * Q8_0_BLOCK_ELEMS;
2569            let mut isum = vdupq_n_s32(0);
2570            for g in 0..2 {
2571                let w = vld1q_s8(block.add(2 + g * 16) as *const i8);
2572                let a = vld1q_s8(act.q.as_ptr().add(base + g * 16));
2573                isum = neon_sdot(isum, w, a);
2574            }
2575            acc += dw * act.d[b] * vaddvq_s32(isum) as f32;
2576        }
2577        acc
2578    }
2579
2580    /// NEON Q4_0 × Q8 int-dot. Unpack nibbles → signed i8, then same
2581    /// `vmull_s8`/`vpadalq_s16` reduction as Q8×Q8. Safety: caller
2582    /// checked neon.
2583    #[target_feature(enable = "neon")]
2584    pub unsafe fn dot_q4_0_q8_neon(row_bytes: &[u8], act: &Q8Activations) -> f32 {
2585        debug_assert_eq!(row_bytes.len() % Q4_0_BLOCK_BYTES, 0);
2586        debug_assert_eq!(row_bytes.len() / Q4_0_BLOCK_BYTES, act.n_blocks());
2587        let bias = vdupq_n_s8(8);
2588        let low_mask = vdupq_n_u8(0x0F);
2589        let mut acc = 0f32;
2590        for (b, block) in row_bytes
2591            .as_chunks::<Q4_0_BLOCK_BYTES>()
2592            .0
2593            .iter()
2594            .enumerate()
2595        {
2596            let dw = f16::from_le_bytes([block[0], block[1]]).to_f32();
2597            let base = b * Q4_0_BLOCK_ELEMS;
2598            let nibbles = vld1q_u8(block.as_ptr().add(2));
2599            let lo = vsubq_s8(vreinterpretq_s8_u8(vandq_u8(nibbles, low_mask)), bias);
2600            let hi = vsubq_s8(vreinterpretq_s8_u8(vshrq_n_u8(nibbles, 4)), bias);
2601            let mut isum = vdupq_n_s32(0);
2602            // lo = elems 0..15, hi = elems 16..31 — matches act layout.
2603            let a0 = vld1q_s8(act.q.as_ptr().add(base));
2604            let a1 = vld1q_s8(act.q.as_ptr().add(base + 16));
2605            let p0_lo = vmull_s8(vget_low_s8(lo), vget_low_s8(a0));
2606            let p0_hi = vmull_s8(vget_high_s8(lo), vget_high_s8(a0));
2607            let p1_lo = vmull_s8(vget_low_s8(hi), vget_low_s8(a1));
2608            let p1_hi = vmull_s8(vget_high_s8(hi), vget_high_s8(a1));
2609            isum = vpadalq_s16(isum, p0_lo);
2610            isum = vpadalq_s16(isum, p0_hi);
2611            isum = vpadalq_s16(isum, p1_lo);
2612            isum = vpadalq_s16(isum, p1_hi);
2613            acc += dw * act.d[b] * vaddvq_s32(isum) as f32;
2614        }
2615        acc
2616    }
2617
2618    /// Two weight rows × one act: share Q8 loads, dual SDOT accumulate.
2619    #[target_feature(enable = "neon,dotprod")]
2620    pub unsafe fn dot_q4_0_q8_neon_sdot_2row(
2621        row0: &[u8],
2622        row1: &[u8],
2623        act: &Q8Activations,
2624    ) -> (f32, f32) {
2625        debug_assert_eq!(row0.len(), row1.len());
2626        debug_assert_eq!(row0.len() % Q4_0_BLOCK_BYTES, 0);
2627        let bias = vdupq_n_s8(8);
2628        let low_mask = vdupq_n_u8(0x0F);
2629        let nb = row0.len() / Q4_0_BLOCK_BYTES;
2630        let mut sum0 = vdupq_n_f32(0.0);
2631        let mut sum1 = vdupq_n_f32(0.0);
2632        for b in 0..nb {
2633            let p0 = row0.as_ptr().add(b * Q4_0_BLOCK_BYTES);
2634            let p1 = row1.as_ptr().add(b * Q4_0_BLOCK_BYTES);
2635            let dw0 = f16::from_le_bytes([*p0, *p0.add(1)]).to_f32();
2636            let dw1 = f16::from_le_bytes([*p1, *p1.add(1)]).to_f32();
2637            let base = b * Q4_0_BLOCK_ELEMS;
2638            let a_lo = vld1q_s8(act.q.as_ptr().add(base));
2639            let a_hi = vld1q_s8(act.q.as_ptr().add(base + 16));
2640            let nib0 = vld1q_u8(p0.add(2));
2641            let nib1 = vld1q_u8(p1.add(2));
2642            let lo0 = vsubq_s8(vreinterpretq_s8_u8(vandq_u8(nib0, low_mask)), bias);
2643            let hi0 = vsubq_s8(vreinterpretq_s8_u8(vshrq_n_u8(nib0, 4)), bias);
2644            let lo1 = vsubq_s8(vreinterpretq_s8_u8(vandq_u8(nib1, low_mask)), bias);
2645            let hi1 = vsubq_s8(vreinterpretq_s8_u8(vshrq_n_u8(nib1, 4)), bias);
2646            let mut is0 = neon_sdot(vdupq_n_s32(0), lo0, a_lo);
2647            is0 = neon_sdot(is0, hi0, a_hi);
2648            let mut is1 = neon_sdot(vdupq_n_s32(0), lo1, a_lo);
2649            is1 = neon_sdot(is1, hi1, a_hi);
2650            let scale = act.d[b];
2651            sum0 = vmlaq_n_f32(sum0, vcvtq_f32_s32(is0), dw0 * scale);
2652            sum1 = vmlaq_n_f32(sum1, vcvtq_f32_s32(is1), dw1 * scale);
2653        }
2654        (vaddvq_f32(sum0), vaddvq_f32(sum1))
2655    }
2656
2657    /// NEON Q4_0 × Q8 with SDOT. Two-block unroll + float4 scale-accumulate.
2658    #[target_feature(enable = "neon,dotprod")]
2659    pub unsafe fn dot_q4_0_q8_neon_sdot(row_bytes: &[u8], act: &Q8Activations) -> f32 {
2660        debug_assert_eq!(row_bytes.len() % Q4_0_BLOCK_BYTES, 0);
2661        debug_assert_eq!(row_bytes.len() / Q4_0_BLOCK_BYTES, act.n_blocks());
2662        let bias = vdupq_n_s8(8);
2663        let low_mask = vdupq_n_u8(0x0F);
2664        let nb = row_bytes.len() / Q4_0_BLOCK_BYTES;
2665        let mut sumv0 = vdupq_n_f32(0.0);
2666        let mut sumv1 = vdupq_n_f32(0.0);
2667        let mut b = 0usize;
2668        while b + 1 < nb {
2669            let block0 = row_bytes.as_ptr().add(b * Q4_0_BLOCK_BYTES);
2670            let block1 = row_bytes.as_ptr().add((b + 1) * Q4_0_BLOCK_BYTES);
2671            let dw0 = f16::from_le_bytes([*block0, *block0.add(1)]).to_f32();
2672            let dw1 = f16::from_le_bytes([*block1, *block1.add(1)]).to_f32();
2673            let base0 = b * Q4_0_BLOCK_ELEMS;
2674            let base1 = (b + 1) * Q4_0_BLOCK_ELEMS;
2675            let nib0 = vld1q_u8(block0.add(2));
2676            let nib1 = vld1q_u8(block1.add(2));
2677            let lo0 = vsubq_s8(vreinterpretq_s8_u8(vandq_u8(nib0, low_mask)), bias);
2678            let hi0 = vsubq_s8(vreinterpretq_s8_u8(vshrq_n_u8(nib0, 4)), bias);
2679            let lo1 = vsubq_s8(vreinterpretq_s8_u8(vandq_u8(nib1, low_mask)), bias);
2680            let hi1 = vsubq_s8(vreinterpretq_s8_u8(vshrq_n_u8(nib1, 4)), bias);
2681            let mut isum0 = neon_sdot(vdupq_n_s32(0), lo0, vld1q_s8(act.q.as_ptr().add(base0)));
2682            isum0 = neon_sdot(isum0, hi0, vld1q_s8(act.q.as_ptr().add(base0 + 16)));
2683            let mut isum1 = neon_sdot(vdupq_n_s32(0), lo1, vld1q_s8(act.q.as_ptr().add(base1)));
2684            isum1 = neon_sdot(isum1, hi1, vld1q_s8(act.q.as_ptr().add(base1 + 16)));
2685            sumv0 = vmlaq_n_f32(sumv0, vcvtq_f32_s32(isum0), dw0 * act.d[b]);
2686            sumv1 = vmlaq_n_f32(sumv1, vcvtq_f32_s32(isum1), dw1 * act.d[b + 1]);
2687            b += 2;
2688        }
2689        let mut acc = vaddvq_f32(sumv0) + vaddvq_f32(sumv1);
2690        if b < nb {
2691            let block = &row_bytes[b * Q4_0_BLOCK_BYTES..(b + 1) * Q4_0_BLOCK_BYTES];
2692            let dw = f16::from_le_bytes([block[0], block[1]]).to_f32();
2693            let base = b * Q4_0_BLOCK_ELEMS;
2694            let nibbles = vld1q_u8(block.as_ptr().add(2));
2695            let lo = vsubq_s8(vreinterpretq_s8_u8(vandq_u8(nibbles, low_mask)), bias);
2696            let hi = vsubq_s8(vreinterpretq_s8_u8(vshrq_n_u8(nibbles, 4)), bias);
2697            let mut isum = neon_sdot(vdupq_n_s32(0), lo, vld1q_s8(act.q.as_ptr().add(base)));
2698            isum = neon_sdot(isum, hi, vld1q_s8(act.q.as_ptr().add(base + 16)));
2699            acc += dw * act.d[b] * vaddvq_s32(isum) as f32;
2700        }
2701        acc
2702    }
2703
2704    #[target_feature(enable = "neon")]
2705    unsafe fn neon_i8_dot_widen(mut isum: int32x4_t, w: int8x16_t, a: int8x16_t) -> int32x4_t {
2706        let prod_lo = vmull_s8(vget_low_s8(w), vget_low_s8(a));
2707        let prod_hi = vmull_s8(vget_high_s8(w), vget_high_s8(a));
2708        isum = vpadalq_s16(isum, prod_lo);
2709        vpadalq_s16(isum, prod_hi)
2710    }
2711
2712    /// NEON Q4_K × Q8_K int-dot (widening path).
2713    #[target_feature(enable = "neon")]
2714    pub unsafe fn dot_q4_k_q8_neon(row_bytes: &[u8], act: &Q8KActivations) -> f32 {
2715        debug_assert_eq!(row_bytes.len() % Q4_K_BLOCK_BYTES, 0);
2716        debug_assert_eq!(row_bytes.len() / Q4_K_BLOCK_BYTES, act.n_blocks());
2717        let low_mask = vdupq_n_u8(0x0F);
2718        let mut acc = 0f32;
2719        for (b, block) in row_bytes
2720            .as_chunks::<Q4_K_BLOCK_BYTES>()
2721            .0
2722            .iter()
2723            .enumerate()
2724        {
2725            let d = f16::from_le_bytes([block[0], block[1]]).to_f32();
2726            let dmin = f16::from_le_bytes([block[2], block[3]]).to_f32();
2727            let scales: [u8; Q4_K_SCALE_BYTES] = block[4..16].try_into().unwrap();
2728            let qs = &block[16..144];
2729            let da = act.d[b];
2730            let q8 = act.q.as_ptr().add(b * Q4_K_BLOCK_ELEMS);
2731            let bsums = &act.bsums[b * 16..(b + 1) * 16];
2732
2733            let mut sum_min = 0i32;
2734            for i in 0..8 {
2735                let (_, m) = q4_k_scale_min(i, &scales);
2736                sum_min += m as i32 * (bsums[2 * i] as i32 + bsums[2 * i + 1] as i32);
2737            }
2738            acc -= dmin * da * sum_min as f32;
2739
2740            let mut q_off = 0usize;
2741            let mut base = 0usize;
2742            let mut is = 0usize;
2743            for _ in 0..4 {
2744                let (sc1, _) = q4_k_scale_min(is, &scales);
2745                let (sc2, _) = q4_k_scale_min(is + 1, &scales);
2746                let mut isum1 = vdupq_n_s32(0);
2747                let mut isum2 = vdupq_n_s32(0);
2748                for g in 0..2 {
2749                    let packed = vld1q_u8(qs.as_ptr().add(q_off + g * 16));
2750                    let lo = vreinterpretq_s8_u8(vandq_u8(packed, low_mask));
2751                    let hi = vreinterpretq_s8_u8(vshrq_n_u8(packed, 4));
2752                    let a0 = vld1q_s8(q8.add(base + g * 16));
2753                    let a1 = vld1q_s8(q8.add(base + 32 + g * 16));
2754                    isum1 = neon_i8_dot_widen(isum1, lo, a0);
2755                    isum2 = neon_i8_dot_widen(isum2, hi, a1);
2756                }
2757                acc += d
2758                    * da
2759                    * (sc1 as f32 * vaddvq_s32(isum1) as f32
2760                        + sc2 as f32 * vaddvq_s32(isum2) as f32);
2761                q_off += 32;
2762                base += 64;
2763                is += 2;
2764            }
2765        }
2766        acc
2767    }
2768
2769    /// NEON Q4_K × Q8_K on i8mm hosts. llama.cpp `ggml_vec_dot_q4_K_q8_K`
2770    /// uses SMMLA only for nrc==2 / repacked GEMM tiles (see repack.cpp);
2771    /// single-row vec-dot stays on dotprod until ferrox Q4_K repack lands.
2772    /// Dispatched when `is_aarch64_feature_detected!("i8mm")` so callers
2773    /// can prefer the feature without changing numerics.
2774    #[target_feature(enable = "neon,i8mm")]
2775    pub unsafe fn dot_q4_k_q8_neon_i8mm(row_bytes: &[u8], act: &Q8KActivations) -> f32 {
2776        dot_q4_k_q8_neon_sdot(row_bytes, act)
2777    }
2778
2779    /// NEON Q4_K × Q8_K with SDOT.
2780    #[target_feature(enable = "neon,dotprod")]
2781    pub unsafe fn dot_q4_k_q8_neon_sdot(row_bytes: &[u8], act: &Q8KActivations) -> f32 {
2782        debug_assert_eq!(row_bytes.len() % Q4_K_BLOCK_BYTES, 0);
2783        debug_assert_eq!(row_bytes.len() / Q4_K_BLOCK_BYTES, act.n_blocks());
2784        let low_mask = vdupq_n_u8(0x0F);
2785        let mut acc = 0f32;
2786        for (b, block) in row_bytes
2787            .as_chunks::<Q4_K_BLOCK_BYTES>()
2788            .0
2789            .iter()
2790            .enumerate()
2791        {
2792            let d = f16::from_le_bytes([block[0], block[1]]).to_f32();
2793            let dmin = f16::from_le_bytes([block[2], block[3]]).to_f32();
2794            let scales: [u8; Q4_K_SCALE_BYTES] = block[4..16].try_into().unwrap();
2795            let qs = &block[16..144];
2796            let da = act.d[b];
2797            let q8 = act.q.as_ptr().add(b * Q4_K_BLOCK_ELEMS);
2798            let bsums = &act.bsums[b * 16..(b + 1) * 16];
2799
2800            let mut sum_min = 0i32;
2801            for i in 0..8 {
2802                let (_, m) = q4_k_scale_min(i, &scales);
2803                sum_min += m as i32 * (bsums[2 * i] as i32 + bsums[2 * i + 1] as i32);
2804            }
2805            acc -= dmin * da * sum_min as f32;
2806
2807            let mut q_off = 0usize;
2808            let mut base = 0usize;
2809            let mut is = 0usize;
2810            for _ in 0..4 {
2811                let (sc1, _) = q4_k_scale_min(is, &scales);
2812                let (sc2, _) = q4_k_scale_min(is + 1, &scales);
2813                let mut isum1 = vdupq_n_s32(0);
2814                let mut isum2 = vdupq_n_s32(0);
2815                for g in 0..2 {
2816                    let packed = vld1q_u8(qs.as_ptr().add(q_off + g * 16));
2817                    let lo = vreinterpretq_s8_u8(vandq_u8(packed, low_mask));
2818                    let hi = vreinterpretq_s8_u8(vshrq_n_u8(packed, 4));
2819                    let a0 = vld1q_s8(q8.add(base + g * 16));
2820                    let a1 = vld1q_s8(q8.add(base + 32 + g * 16));
2821                    isum1 = neon_sdot(isum1, lo, a0);
2822                    isum2 = neon_sdot(isum2, hi, a1);
2823                }
2824                acc += d
2825                    * da
2826                    * (sc1 as f32 * vaddvq_s32(isum1) as f32
2827                        + sc2 as f32 * vaddvq_s32(isum2) as f32);
2828                q_off += 32;
2829                base += 64;
2830                is += 2;
2831            }
2832        }
2833        acc
2834    }
2835
2836    /// NEON Q5_K × Q8_K int-dot (widening path).
2837    #[target_feature(enable = "neon")]
2838    pub unsafe fn dot_q5_k_q8_neon(row_bytes: &[u8], act: &Q8KActivations) -> f32 {
2839        debug_assert_eq!(row_bytes.len() % Q5_K_BLOCK_BYTES, 0);
2840        debug_assert_eq!(row_bytes.len() / Q5_K_BLOCK_BYTES, act.n_blocks());
2841        let low_mask = vdupq_n_u8(0x0F);
2842        let sixteen = vdupq_n_u8(16);
2843        let mut acc = 0f32;
2844        for (b, block) in row_bytes
2845            .as_chunks::<Q5_K_BLOCK_BYTES>()
2846            .0
2847            .iter()
2848            .enumerate()
2849        {
2850            let d = f16::from_le_bytes([block[0], block[1]]).to_f32();
2851            let dmin = f16::from_le_bytes([block[2], block[3]]).to_f32();
2852            let scales: [u8; Q4_K_SCALE_BYTES] = block[4..16].try_into().unwrap();
2853            let qh = block.as_ptr().add(16);
2854            let qs = &block[48..176];
2855            let da = act.d[b];
2856            let q8 = act.q.as_ptr().add(b * Q5_K_BLOCK_ELEMS);
2857            let bsums = &act.bsums[b * 16..(b + 1) * 16];
2858
2859            let mut sum_min = 0i32;
2860            for i in 0..8 {
2861                let (_, m) = q4_k_scale_min(i, &scales);
2862                sum_min += m as i32 * (bsums[2 * i] as i32 + bsums[2 * i + 1] as i32);
2863            }
2864            acc -= dmin * da * sum_min as f32;
2865
2866            let mut q_off = 0usize;
2867            let mut base = 0usize;
2868            let mut is = 0usize;
2869            let (mut u1, mut u2) = (1u8, 2u8);
2870            for _ in 0..4 {
2871                let (sc1, _) = q4_k_scale_min(is, &scales);
2872                let (sc2, _) = q4_k_scale_min(is + 1, &scales);
2873                let mut isum1 = vdupq_n_s32(0);
2874                let mut isum2 = vdupq_n_s32(0);
2875                let u1_vec = vdupq_n_u8(u1);
2876                let u2_vec = vdupq_n_u8(u2);
2877                for g in 0..2 {
2878                    let packed = vld1q_u8(qs.as_ptr().add(q_off + g * 16));
2879                    let qh16 = vld1q_u8(qh.add(g * 16));
2880                    let lo_nib = vandq_u8(packed, low_mask);
2881                    let hi_nib = vshrq_n_u8(packed, 4);
2882                    let hi_bit1 = vandq_u8(vtstq_u8(qh16, u1_vec), sixteen);
2883                    let hi_bit2 = vandq_u8(vtstq_u8(qh16, u2_vec), sixteen);
2884                    let lo = vreinterpretq_s8_u8(vorrq_u8(lo_nib, hi_bit1));
2885                    let hi = vreinterpretq_s8_u8(vorrq_u8(hi_nib, hi_bit2));
2886                    let a0 = vld1q_s8(q8.add(base + g * 16));
2887                    let a1 = vld1q_s8(q8.add(base + 32 + g * 16));
2888                    isum1 = neon_i8_dot_widen(isum1, lo, a0);
2889                    isum2 = neon_i8_dot_widen(isum2, hi, a1);
2890                }
2891                acc += d
2892                    * da
2893                    * (sc1 as f32 * vaddvq_s32(isum1) as f32
2894                        + sc2 as f32 * vaddvq_s32(isum2) as f32);
2895                q_off += 32;
2896                base += 64;
2897                is += 2;
2898                u1 <<= 2;
2899                u2 <<= 2;
2900            }
2901        }
2902        acc
2903    }
2904
2905    /// NEON Q5_K × Q8_K with SDOT (llama.cpp `ggml_vec_dot_q5_K_q8_K` ARM).
2906    #[target_feature(enable = "neon,dotprod")]
2907    pub unsafe fn dot_q5_k_q8_neon_sdot(row_bytes: &[u8], act: &Q8KActivations) -> f32 {
2908        debug_assert_eq!(row_bytes.len() % Q5_K_BLOCK_BYTES, 0);
2909        debug_assert_eq!(row_bytes.len() / Q5_K_BLOCK_BYTES, act.n_blocks());
2910        let low_mask = vdupq_n_u8(0x0F);
2911        let sixteen = vdupq_n_u8(16);
2912        let mut acc = 0f32;
2913        for (b, block) in row_bytes
2914            .as_chunks::<Q5_K_BLOCK_BYTES>()
2915            .0
2916            .iter()
2917            .enumerate()
2918        {
2919            let d = f16::from_le_bytes([block[0], block[1]]).to_f32();
2920            let dmin = f16::from_le_bytes([block[2], block[3]]).to_f32();
2921            let scales: [u8; Q4_K_SCALE_BYTES] = block[4..16].try_into().unwrap();
2922            let qh = block.as_ptr().add(16);
2923            let qs = &block[48..176];
2924            let da = act.d[b];
2925            let q8 = act.q.as_ptr().add(b * Q5_K_BLOCK_ELEMS);
2926            let bsums = &act.bsums[b * 16..(b + 1) * 16];
2927
2928            let mut sum_min = 0i32;
2929            for i in 0..8 {
2930                let (_, m) = q4_k_scale_min(i, &scales);
2931                sum_min += m as i32 * (bsums[2 * i] as i32 + bsums[2 * i + 1] as i32);
2932            }
2933            acc -= dmin * da * sum_min as f32;
2934
2935            let mut q_off = 0usize;
2936            let mut base = 0usize;
2937            let mut is = 0usize;
2938            let (mut u1, mut u2) = (1u8, 2u8);
2939            for _ in 0..4 {
2940                let (sc1, _) = q4_k_scale_min(is, &scales);
2941                let (sc2, _) = q4_k_scale_min(is + 1, &scales);
2942                let mut isum1 = vdupq_n_s32(0);
2943                let mut isum2 = vdupq_n_s32(0);
2944                let u1_vec = vdupq_n_u8(u1);
2945                let u2_vec = vdupq_n_u8(u2);
2946                for g in 0..2 {
2947                    let packed = vld1q_u8(qs.as_ptr().add(q_off + g * 16));
2948                    let qh16 = vld1q_u8(qh.add(g * 16));
2949                    let lo_nib = vandq_u8(packed, low_mask);
2950                    let hi_nib = vshrq_n_u8(packed, 4);
2951                    let hi_bit1 = vandq_u8(vtstq_u8(qh16, u1_vec), sixteen);
2952                    let hi_bit2 = vandq_u8(vtstq_u8(qh16, u2_vec), sixteen);
2953                    let lo = vreinterpretq_s8_u8(vorrq_u8(lo_nib, hi_bit1));
2954                    let hi = vreinterpretq_s8_u8(vorrq_u8(hi_nib, hi_bit2));
2955                    let a0 = vld1q_s8(q8.add(base + g * 16));
2956                    let a1 = vld1q_s8(q8.add(base + 32 + g * 16));
2957                    isum1 = neon_sdot(isum1, lo, a0);
2958                    isum2 = neon_sdot(isum2, hi, a1);
2959                }
2960                acc += d
2961                    * da
2962                    * (sc1 as f32 * vaddvq_s32(isum1) as f32
2963                        + sc2 as f32 * vaddvq_s32(isum2) as f32);
2964                q_off += 32;
2965                base += 64;
2966                is += 2;
2967                u1 <<= 2;
2968                u2 <<= 2;
2969            }
2970        }
2971        acc
2972    }
2973
2974    /// Q5_K row × up to [`Q5_K_GEMM_NC`] activations (weight blocks loaded once).
2975    #[target_feature(enable = "neon,dotprod")]
2976    pub unsafe fn gemm_q5_k_q8_neon_sdot(
2977        row_bytes: &[u8],
2978        acts: &[Q8KActivations],
2979        out: &mut [f32],
2980    ) {
2981        debug_assert_eq!(out.len(), acts.len());
2982        debug_assert!(acts.len() <= super::Q5_K_GEMM_NC);
2983        out.fill(0.0);
2984        if acts.is_empty() {
2985            return;
2986        }
2987        let low_mask = vdupq_n_u8(0x0F);
2988        let sixteen = vdupq_n_u8(16);
2989        let n = acts.len();
2990        for (b, block) in row_bytes
2991            .as_chunks::<Q5_K_BLOCK_BYTES>()
2992            .0
2993            .iter()
2994            .enumerate()
2995        {
2996            let d = f16::from_le_bytes([block[0], block[1]]).to_f32();
2997            let dmin = f16::from_le_bytes([block[2], block[3]]).to_f32();
2998            let scales: [u8; Q4_K_SCALE_BYTES] = block[4..16].try_into().unwrap();
2999            let qh = block.as_ptr().add(16);
3000            let qs = &block[48..176];
3001            let mut mins = [0u8; 8];
3002            let mut sc_only = [0u8; 8];
3003            for i in 0..8 {
3004                let (s, m) = q4_k_scale_min(i, &scales);
3005                sc_only[i] = s;
3006                mins[i] = m;
3007            }
3008            for j in 0..n {
3009                let act = &acts[j];
3010                let da = act.d[b];
3011                let bsums = &act.bsums[b * 16..(b + 1) * 16];
3012                let mut sum_min = 0i32;
3013                for i in 0..8 {
3014                    sum_min += mins[i] as i32 * (bsums[2 * i] as i32 + bsums[2 * i + 1] as i32);
3015                }
3016                out[j] -= dmin * da * sum_min as f32;
3017            }
3018            let mut q_off = 0usize;
3019            let mut base = 0usize;
3020            let mut is = 0usize;
3021            let (mut u1, mut u2) = (1u8, 2u8);
3022            for _ in 0..4 {
3023                let sc1 = sc_only[is];
3024                let sc2 = sc_only[is + 1];
3025                let u1_vec = vdupq_n_u8(u1);
3026                let u2_vec = vdupq_n_u8(u2);
3027                // Decode weight quants once per 32-byte group.
3028                let mut lo_cols = [vreinterpretq_s8_u8(vdupq_n_u8(0)); 2];
3029                let mut hi_cols = [vreinterpretq_s8_u8(vdupq_n_u8(0)); 2];
3030                for g in 0..2 {
3031                    let packed = vld1q_u8(qs.as_ptr().add(q_off + g * 16));
3032                    let qh16 = vld1q_u8(qh.add(g * 16));
3033                    let lo_nib = vandq_u8(packed, low_mask);
3034                    let hi_nib = vshrq_n_u8(packed, 4);
3035                    let hi_bit1 = vandq_u8(vtstq_u8(qh16, u1_vec), sixteen);
3036                    let hi_bit2 = vandq_u8(vtstq_u8(qh16, u2_vec), sixteen);
3037                    lo_cols[g] = vreinterpretq_s8_u8(vorrq_u8(lo_nib, hi_bit1));
3038                    hi_cols[g] = vreinterpretq_s8_u8(vorrq_u8(hi_nib, hi_bit2));
3039                }
3040                for j in 0..n {
3041                    let q8 = acts[j].q.as_ptr().add(b * Q5_K_BLOCK_ELEMS);
3042                    let da = acts[j].d[b];
3043                    let mut isum1 = vdupq_n_s32(0);
3044                    let mut isum2 = vdupq_n_s32(0);
3045                    for g in 0..2 {
3046                        let a0 = vld1q_s8(q8.add(base + g * 16));
3047                        let a1 = vld1q_s8(q8.add(base + 32 + g * 16));
3048                        isum1 = neon_sdot(isum1, lo_cols[g], a0);
3049                        isum2 = neon_sdot(isum2, hi_cols[g], a1);
3050                    }
3051                    out[j] += d
3052                        * da
3053                        * (sc1 as f32 * vaddvq_s32(isum1) as f32
3054                            + sc2 as f32 * vaddvq_s32(isum2) as f32);
3055                }
3056                q_off += 32;
3057                base += 64;
3058                is += 2;
3059                u1 <<= 2;
3060                u2 <<= 2;
3061            }
3062        }
3063    }
3064
3065    /// Q6_K row × up to [`Q6_K_GEMM_NC`] activations — decode ql/qh once
3066    /// per sub-block, reuse across acts (Phi-4 `ffn_down` Q6_K).
3067    #[target_feature(enable = "neon,dotprod")]
3068    pub unsafe fn gemm_q6_k_q8_neon_sdot(
3069        row_bytes: &[u8],
3070        acts: &[Q8KActivations],
3071        out: &mut [f32],
3072    ) {
3073        debug_assert_eq!(out.len(), acts.len());
3074        debug_assert!(acts.len() <= super::Q6_K_GEMM_NC);
3075        out.fill(0.0);
3076        let n = acts.len();
3077        if n == 0 {
3078            return;
3079        }
3080        let m4b = vdupq_n_u8(0x0F);
3081        let mone = vdupq_n_u8(3);
3082        for (b, block) in row_bytes
3083            .as_chunks::<Q6_K_BLOCK_BYTES>()
3084            .0
3085            .iter()
3086            .enumerate()
3087        {
3088            let d_all = f16::from_le_bytes([block[208], block[209]]).to_f32();
3089            let ql = block.as_ptr();
3090            let qh = block.as_ptr().add(128);
3091            let scale = block.as_ptr().add(192) as *const i8;
3092            let scales = vld1q_s8(scale);
3093            let q6scales0 = vmovl_s8(vget_low_s8(scales));
3094            let q6scales1 = vmovl_s8(vget_high_s8(scales));
3095
3096            let mut isum_mins = [0i32; 4];
3097            let mut isums = [0i32; 4];
3098            for j in 0..n {
3099                let bsums = acts[j].bsums.as_ptr().add(b * 16);
3100                let q8sums0 = vld1q_s16(bsums);
3101                let q8sums1 = vld1q_s16(bsums.add(8));
3102                let prod = vaddq_s32(
3103                    vaddq_s32(
3104                        vmull_s16(vget_low_s16(q8sums0), vget_low_s16(q6scales0)),
3105                        vmull_s16(vget_high_s16(q8sums0), vget_high_s16(q6scales0)),
3106                    ),
3107                    vaddq_s32(
3108                        vmull_s16(vget_low_s16(q8sums1), vget_low_s16(q6scales1)),
3109                        vmull_s16(vget_high_s16(q8sums1), vget_high_s16(q6scales1)),
3110                    ),
3111                );
3112                isum_mins[j] = vaddvq_s32(prod);
3113            }
3114
3115            for half in 0..2usize {
3116                let q6 = ql.add(half * 64);
3117                let qhp = qh.add(half * 32);
3118                let sc = scale.add(half * 8);
3119                let act_off = half * 128;
3120
3121                let qh0 = vld1q_u8(qhp);
3122                let qh1 = vld1q_u8(qhp.add(16));
3123                let q6_0 = vld1q_u8(q6);
3124                let q6_1 = vld1q_u8(q6.add(16));
3125                let q6_2 = vld1q_u8(q6.add(32));
3126                let q6_3 = vld1q_u8(q6.add(48));
3127
3128                let h0 = vshlq_n_u8(vandq_u8(mone, qh0), 4);
3129                let h1 = vshlq_n_u8(vandq_u8(mone, qh1), 4);
3130                let mut shifted = vshrq_n_u8(qh0, 2);
3131                let h2 = vshlq_n_u8(vandq_u8(mone, shifted), 4);
3132                shifted = vshrq_n_u8(qh1, 2);
3133                let h3 = vshlq_n_u8(vandq_u8(mone, shifted), 4);
3134                let wb0 = vreinterpretq_s8_u8(vorrq_u8(vandq_u8(q6_0, m4b), h0));
3135                let wb1 = vreinterpretq_s8_u8(vorrq_u8(vandq_u8(q6_1, m4b), h1));
3136                let wb2 = vreinterpretq_s8_u8(vorrq_u8(vandq_u8(q6_2, m4b), h2));
3137                let wb3 = vreinterpretq_s8_u8(vorrq_u8(vandq_u8(q6_3, m4b), h3));
3138                let sc0 = *sc.add(0) as i32;
3139                let sc1 = *sc.add(1) as i32;
3140                let sc2 = *sc.add(2) as i32;
3141                let sc3 = *sc.add(3) as i32;
3142                let z = vdupq_n_s32(0);
3143                for j in 0..n {
3144                    let q8p = acts[j].q.as_ptr().add(b * Q6_K_BLOCK_ELEMS + act_off);
3145                    isums[j] += vaddvq_s32(neon_sdot(z, wb0, vld1q_s8(q8p))) * sc0
3146                        + vaddvq_s32(neon_sdot(z, wb1, vld1q_s8(q8p.add(16)))) * sc1
3147                        + vaddvq_s32(neon_sdot(z, wb2, vld1q_s8(q8p.add(32)))) * sc2
3148                        + vaddvq_s32(neon_sdot(z, wb3, vld1q_s8(q8p.add(48)))) * sc3;
3149                }
3150
3151                shifted = vshrq_n_u8(qh0, 4);
3152                let h0 = vshlq_n_u8(vandq_u8(mone, shifted), 4);
3153                shifted = vshrq_n_u8(qh1, 4);
3154                let h1 = vshlq_n_u8(vandq_u8(mone, shifted), 4);
3155                shifted = vshrq_n_u8(qh0, 6);
3156                let h2 = vshlq_n_u8(vandq_u8(mone, shifted), 4);
3157                shifted = vshrq_n_u8(qh1, 6);
3158                let h3 = vshlq_n_u8(vandq_u8(mone, shifted), 4);
3159                let wb0 = vreinterpretq_s8_u8(vorrq_u8(vshrq_n_u8(q6_0, 4), h0));
3160                let wb1 = vreinterpretq_s8_u8(vorrq_u8(vshrq_n_u8(q6_1, 4), h1));
3161                let wb2 = vreinterpretq_s8_u8(vorrq_u8(vshrq_n_u8(q6_2, 4), h2));
3162                let wb3 = vreinterpretq_s8_u8(vorrq_u8(vshrq_n_u8(q6_3, 4), h3));
3163                let sc0 = *sc.add(4) as i32;
3164                let sc1 = *sc.add(5) as i32;
3165                let sc2 = *sc.add(6) as i32;
3166                let sc3 = *sc.add(7) as i32;
3167                for j in 0..n {
3168                    let q8p = acts[j].q.as_ptr().add(b * Q6_K_BLOCK_ELEMS + act_off + 64);
3169                    isums[j] += vaddvq_s32(neon_sdot(z, wb0, vld1q_s8(q8p))) * sc0
3170                        + vaddvq_s32(neon_sdot(z, wb1, vld1q_s8(q8p.add(16)))) * sc1
3171                        + vaddvq_s32(neon_sdot(z, wb2, vld1q_s8(q8p.add(32)))) * sc2
3172                        + vaddvq_s32(neon_sdot(z, wb3, vld1q_s8(q8p.add(48)))) * sc3;
3173                }
3174            }
3175            for j in 0..n {
3176                out[j] += d_all * acts[j].d[b] * (isums[j] - 32 * isum_mins[j]) as f32;
3177            }
3178        }
3179    }
3180
3181    /// NEON Q6_K × Q8_K with SDOT (llama.cpp `ggml_vec_dot_q6_K_q8_K` ARM).
3182    /// Quants are assembled as unsigned 0..63 then corrected with
3183    /// `isum - 32 * sum(scale * bsums)` — same as ggml's NEON path.
3184    #[target_feature(enable = "neon,dotprod")]
3185    pub unsafe fn dot_q6_k_q8_neon_sdot(row_bytes: &[u8], act: &Q8KActivations) -> f32 {
3186        debug_assert_eq!(row_bytes.len() % Q6_K_BLOCK_BYTES, 0);
3187        debug_assert_eq!(row_bytes.len() / Q6_K_BLOCK_BYTES, act.n_blocks());
3188        let m4b = vdupq_n_u8(0x0F);
3189        let mone = vdupq_n_u8(3);
3190        let mut acc = 0f32;
3191        for (b, block) in row_bytes
3192            .as_chunks::<Q6_K_BLOCK_BYTES>()
3193            .0
3194            .iter()
3195            .enumerate()
3196        {
3197            let d_all = f16::from_le_bytes([block[208], block[209]]).to_f32();
3198            let da = act.d[b];
3199            let ql = block.as_ptr();
3200            let qh = block.as_ptr().add(128);
3201            let scale = block.as_ptr().add(192) as *const i8;
3202            let q8 = act.q.as_ptr().add(b * Q6_K_BLOCK_ELEMS);
3203            let bsums = act.bsums.as_ptr().add(b * 16);
3204
3205            let scales = vld1q_s8(scale);
3206            let q6scales0 = vmovl_s8(vget_low_s8(scales));
3207            let q6scales1 = vmovl_s8(vget_high_s8(scales));
3208            let q8sums0 = vld1q_s16(bsums);
3209            let q8sums1 = vld1q_s16(bsums.add(8));
3210            let prod = vaddq_s32(
3211                vaddq_s32(
3212                    vmull_s16(vget_low_s16(q8sums0), vget_low_s16(q6scales0)),
3213                    vmull_s16(vget_high_s16(q8sums0), vget_high_s16(q6scales0)),
3214                ),
3215                vaddq_s32(
3216                    vmull_s16(vget_low_s16(q8sums1), vget_low_s16(q6scales1)),
3217                    vmull_s16(vget_high_s16(q8sums1), vget_high_s16(q6scales1)),
3218                ),
3219            );
3220            let isum_mins = vaddvq_s32(prod);
3221            let mut isum = 0i32;
3222            let mut q6 = ql;
3223            let mut qhp = qh;
3224            let mut q8p = q8;
3225            let mut sc = scale;
3226            for _ in 0..2 {
3227                let qh0 = vld1q_u8(qhp);
3228                let qh1 = vld1q_u8(qhp.add(16));
3229                qhp = qhp.add(32);
3230                let q6_0 = vld1q_u8(q6);
3231                let q6_1 = vld1q_u8(q6.add(16));
3232                let q6_2 = vld1q_u8(q6.add(32));
3233                let q6_3 = vld1q_u8(q6.add(48));
3234                q6 = q6.add(64);
3235                let q8_0 = vld1q_s8(q8p);
3236                let q8_1 = vld1q_s8(q8p.add(16));
3237                let q8_2 = vld1q_s8(q8p.add(32));
3238                let q8_3 = vld1q_s8(q8p.add(48));
3239                q8p = q8p.add(64);
3240
3241                let h0 = vshlq_n_u8(vandq_u8(mone, qh0), 4);
3242                let h1 = vshlq_n_u8(vandq_u8(mone, qh1), 4);
3243                let mut shifted = vshrq_n_u8(qh0, 2);
3244                let h2 = vshlq_n_u8(vandq_u8(mone, shifted), 4);
3245                shifted = vshrq_n_u8(qh1, 2);
3246                let h3 = vshlq_n_u8(vandq_u8(mone, shifted), 4);
3247
3248                let b0 = vreinterpretq_s8_u8(vorrq_u8(vandq_u8(q6_0, m4b), h0));
3249                let b1 = vreinterpretq_s8_u8(vorrq_u8(vandq_u8(q6_1, m4b), h1));
3250                let b2 = vreinterpretq_s8_u8(vorrq_u8(vandq_u8(q6_2, m4b), h2));
3251                let b3 = vreinterpretq_s8_u8(vorrq_u8(vandq_u8(q6_3, m4b), h3));
3252                let z = vdupq_n_s32(0);
3253                isum += vaddvq_s32(neon_sdot(z, b0, q8_0)) * (*sc.add(0) as i32)
3254                    + vaddvq_s32(neon_sdot(z, b1, q8_1)) * (*sc.add(1) as i32)
3255                    + vaddvq_s32(neon_sdot(z, b2, q8_2)) * (*sc.add(2) as i32)
3256                    + vaddvq_s32(neon_sdot(z, b3, q8_3)) * (*sc.add(3) as i32);
3257                sc = sc.add(4);
3258
3259                let q8_0 = vld1q_s8(q8p);
3260                let q8_1 = vld1q_s8(q8p.add(16));
3261                let q8_2 = vld1q_s8(q8p.add(32));
3262                let q8_3 = vld1q_s8(q8p.add(48));
3263                q8p = q8p.add(64);
3264                shifted = vshrq_n_u8(qh0, 4);
3265                let h0 = vshlq_n_u8(vandq_u8(mone, shifted), 4);
3266                shifted = vshrq_n_u8(qh1, 4);
3267                let h1 = vshlq_n_u8(vandq_u8(mone, shifted), 4);
3268                shifted = vshrq_n_u8(qh0, 6);
3269                let h2 = vshlq_n_u8(vandq_u8(mone, shifted), 4);
3270                shifted = vshrq_n_u8(qh1, 6);
3271                let h3 = vshlq_n_u8(vandq_u8(mone, shifted), 4);
3272                let b0 = vreinterpretq_s8_u8(vorrq_u8(vshrq_n_u8(q6_0, 4), h0));
3273                let b1 = vreinterpretq_s8_u8(vorrq_u8(vshrq_n_u8(q6_1, 4), h1));
3274                let b2 = vreinterpretq_s8_u8(vorrq_u8(vshrq_n_u8(q6_2, 4), h2));
3275                let b3 = vreinterpretq_s8_u8(vorrq_u8(vshrq_n_u8(q6_3, 4), h3));
3276                isum += vaddvq_s32(neon_sdot(z, b0, q8_0)) * (*sc.add(0) as i32)
3277                    + vaddvq_s32(neon_sdot(z, b1, q8_1)) * (*sc.add(1) as i32)
3278                    + vaddvq_s32(neon_sdot(z, b2, q8_2)) * (*sc.add(2) as i32)
3279                    + vaddvq_s32(neon_sdot(z, b3, q8_3)) * (*sc.add(3) as i32);
3280                sc = sc.add(4);
3281            }
3282            acc += d_all * da * (isum - 32 * isum_mins) as f32;
3283        }
3284        acc
3285    }
3286
3287    /// NEON fused Q4_0 dot product. Each block's 16 nibble-packed bytes
3288    /// are loaded once, split into low/high nibbles with
3289    /// `vandq_u8`/`vshrq_n_u8` (a per-byte shift, simpler than AVX2's
3290    /// 16-bit-lane-shift-then-mask trick since NEON shifts natively at
3291    /// byte granularity), then each 16-lane nibble group goes through
3292    /// the same unsigned-widen -> signed-bias-subtract -> widen-to-i32
3293    /// -> f32 -> FMA sequence as Q8_0 above. Safety: same contract as
3294    /// `dot_q8_0_f32_neon`.
3295    #[target_feature(enable = "neon")]
3296    pub unsafe fn dot_q4_0_f32_neon(row_bytes: &[u8], x: &[f32]) -> f32 {
3297        debug_assert_eq!(row_bytes.len() % Q4_0_BLOCK_BYTES, 0);
3298        let bias = vdupq_n_s16(8);
3299        let low_mask = vdupq_n_u8(0x0F);
3300
3301        let mut acc = 0f32;
3302        for (b, block) in row_bytes
3303            .as_chunks::<Q4_0_BLOCK_BYTES>()
3304            .0
3305            .iter()
3306            .enumerate()
3307        {
3308            let scale = f16::from_le_bytes([block[0], block[1]]).to_f32();
3309            let base = b * Q4_0_BLOCK_ELEMS;
3310            let nibbles = vld1q_u8(block.as_ptr().add(2));
3311
3312            let lo_nibbles = vandq_u8(nibbles, low_mask); // elements 0..16
3313            let hi_nibbles = vshrq_n_u8(nibbles, 4); // elements 16..32
3314
3315            let mut block_acc = vdupq_n_f32(0.0);
3316            for (group_idx, nib_u8) in [lo_nibbles, hi_nibbles].into_iter().enumerate() {
3317                let lo16 = vsubq_s16(vreinterpretq_s16_u16(vmovl_u8(vget_low_u8(nib_u8))), bias);
3318                let hi16 = vsubq_s16(vreinterpretq_s16_u16(vmovl_u8(vget_high_u8(nib_u8))), bias);
3319                for (half_idx, half16) in [lo16, hi16].into_iter().enumerate() {
3320                    let lo32 = vmovl_s16(vget_low_s16(half16));
3321                    let hi32 = vmovl_s16(vget_high_s16(half16));
3322                    let f_lo = vcvtq_f32_s32(lo32);
3323                    let f_hi = vcvtq_f32_s32(hi32);
3324                    let elem_base = base + group_idx * 16 + half_idx * 8;
3325                    let x_lo = vld1q_f32(x.as_ptr().add(elem_base));
3326                    let x_hi = vld1q_f32(x.as_ptr().add(elem_base + 4));
3327                    block_acc = vfmaq_f32(block_acc, f_lo, x_lo);
3328                    block_acc = vfmaq_f32(block_acc, f_hi, x_hi);
3329                }
3330            }
3331            acc += vaddvq_f32(block_acc) * scale;
3332        }
3333        acc
3334    }
3335
3336    /// Widens 16 unsigned nibble values (0..=15 or 0..=31 once a 5th
3337    /// bit has been OR'd in for Q5_K) into four `float32x4_t` quads, in
3338    /// lane order -- the shared u8 -> u16 -> u32 -> f32 widening step
3339    /// every K-quant NEON kernel below needs, factored out once rather
3340    /// than repeated per format.
3341    #[inline]
3342    #[target_feature(enable = "neon")]
3343    unsafe fn widen_u8x16_to_f32_quads(
3344        v: uint8x16_t,
3345    ) -> (float32x4_t, float32x4_t, float32x4_t, float32x4_t) {
3346        let u16_lo = vmovl_u8(vget_low_u8(v)); // lanes 0..8
3347        let u16_hi = vmovl_u8(vget_high_u8(v)); // lanes 8..16
3348        (
3349            vcvtq_f32_u32(vmovl_u16(vget_low_u16(u16_lo))), // lanes 0..4
3350            vcvtq_f32_u32(vmovl_u16(vget_high_u16(u16_lo))), // lanes 4..8
3351            vcvtq_f32_u32(vmovl_u16(vget_low_u16(u16_hi))), // lanes 8..12
3352            vcvtq_f32_u32(vmovl_u16(vget_high_u16(u16_hi))), // lanes 12..16
3353        )
3354    }
3355
3356    /// Dequantizes 16 nibble-derived f32 values (`quads`, in element
3357    /// order) as `d * q - min` and fused-multiply-accumulates each
3358    /// against the matching 16 activations starting at `x[x_base..]`,
3359    /// into `acc`. Shared by Q4_K's and Q5_K's NEON kernels, which both
3360    /// use this exact affine (scale, min) dequant form per 32-element
3361    /// sub-block.
3362    #[inline]
3363    #[target_feature(enable = "neon")]
3364    unsafe fn fma_affine16(
3365        quads: (float32x4_t, float32x4_t, float32x4_t, float32x4_t),
3366        d: f32,
3367        min_vec: float32x4_t,
3368        x: &[f32],
3369        x_base: usize,
3370        mut acc: float32x4_t,
3371    ) -> float32x4_t {
3372        let (q0, q1, q2, q3) = quads;
3373        let mut i = 0usize;
3374        for q in [q0, q1, q2, q3] {
3375            let w = vsubq_f32(vmulq_n_f32(q, d), min_vec);
3376            let xv = vld1q_f32(x.as_ptr().add(x_base + i));
3377            acc = vfmaq_f32(acc, w, xv);
3378            i += 4;
3379        }
3380        acc
3381    }
3382
3383    /// NEON fused Q4_K dot product. Mirrors `dot_q4_0_f32_neon`'s
3384    /// nibble-splitting structure (low/high nibble of each byte are two
3385    /// independent output elements), scaled up from Q4_0's 16
3386    /// bytes/block to Q4_K's 32 bytes/sub-block, with the affine `d*q -
3387    /// min` transform (two independent (scale, min) pairs, one for the
3388    /// low-nibble half and one for the high-nibble half) instead of
3389    /// Q4_0's single symmetric `d*(q-8)`. Safety: same contract as
3390    /// `dot_q8_0_f32_neon`.
3391    #[target_feature(enable = "neon")]
3392    pub unsafe fn dot_q4_k_f32_neon(row_bytes: &[u8], x: &[f32]) -> f32 {
3393        debug_assert_eq!(row_bytes.len() % Q4_K_BLOCK_BYTES, 0);
3394        let low_mask = vdupq_n_u8(0x0F);
3395        let mut acc = 0f32;
3396        let mut x_base = 0usize;
3397        for block in row_bytes.as_chunks::<Q4_K_BLOCK_BYTES>().0 {
3398            let d = f16::from_le_bytes([block[0], block[1]]).to_f32();
3399            let dmin = f16::from_le_bytes([block[2], block[3]]).to_f32();
3400            let scales: [u8; Q4_K_SCALE_BYTES] = block[4..16].try_into().unwrap();
3401            let qs = &block[16..144];
3402
3403            // One vector accumulator per block — avoid a horizontal
3404            // reduce on every 32-element group (4× per super-block).
3405            let mut vec_acc = vdupq_n_f32(0.0);
3406            let mut is = 0usize;
3407            let mut q_off = 0usize;
3408            for _ in 0..4 {
3409                let (sc1, m1) = q4_k_scale_min(is, &scales);
3410                let (sc2, m2) = q4_k_scale_min(is + 1, &scales);
3411                let d1 = d * sc1 as f32;
3412                let min1_vec = vdupq_n_f32(dmin * m1 as f32);
3413                let d2 = d * sc2 as f32;
3414                let min2_vec = vdupq_n_f32(dmin * m2 as f32);
3415
3416                for g in 0..2 {
3417                    let raw16 = vld1q_u8(qs.as_ptr().add(q_off + g * 16));
3418                    let lo_nib = vandq_u8(raw16, low_mask);
3419                    let hi_nib = vshrq_n_u8(raw16, 4);
3420                    vec_acc = fma_affine16(
3421                        widen_u8x16_to_f32_quads(lo_nib),
3422                        d1,
3423                        min1_vec,
3424                        x,
3425                        x_base + g * 16,
3426                        vec_acc,
3427                    );
3428                    vec_acc = fma_affine16(
3429                        widen_u8x16_to_f32_quads(hi_nib),
3430                        d2,
3431                        min2_vec,
3432                        x,
3433                        x_base + 32 + g * 16,
3434                        vec_acc,
3435                    );
3436                }
3437                q_off += 32;
3438                x_base += 64;
3439                is += 2;
3440            }
3441            acc += vaddvq_f32(vec_acc);
3442        }
3443        acc
3444    }
3445
3446    /// NEON fused Q5_K dot product: identical structure to
3447    /// `dot_q4_k_f32_neon`, but before widening, each nibble gets a 5th
3448    /// bit OR'd in from the block's `qh` bitplane. The per-lane "is bit
3449    /// `u1`/`u2` set in this byte of `qh`" test uses
3450    /// `vtstq_u8`(bitwise-AND-then-nonzero-test, giving an all-ones or
3451    /// all-zeros mask per lane) `AND`ed with a lane of `16` -- the
3452    /// standard NEON idiom for a per-lane conditional add when the
3453    /// condition is itself a bitwise test. Safety: same contract as
3454    /// `dot_q8_0_f32_neon`.
3455    #[target_feature(enable = "neon")]
3456    pub unsafe fn dot_q5_k_f32_neon(row_bytes: &[u8], x: &[f32]) -> f32 {
3457        debug_assert_eq!(row_bytes.len() % Q5_K_BLOCK_BYTES, 0);
3458        let low_mask = vdupq_n_u8(0x0F);
3459        let sixteen = vdupq_n_u8(16);
3460        let mut acc = 0f32;
3461        let mut x_base = 0usize;
3462        for block in row_bytes.as_chunks::<Q5_K_BLOCK_BYTES>().0 {
3463            let d = f16::from_le_bytes([block[0], block[1]]).to_f32();
3464            let dmin = f16::from_le_bytes([block[2], block[3]]).to_f32();
3465            let scales: [u8; Q4_K_SCALE_BYTES] = block[4..16].try_into().unwrap();
3466            let qh = &block[16..48];
3467            let qs = &block[48..176];
3468
3469            let mut is = 0usize;
3470            let (mut u1, mut u2) = (1u8, 2u8);
3471            for oi in 0..4 {
3472                let (sc1, m1) = q4_k_scale_min(is, &scales);
3473                let (sc2, m2) = q4_k_scale_min(is + 1, &scales);
3474                let d1 = d * sc1 as f32;
3475                let min1_vec = vdupq_n_f32(dmin * m1 as f32);
3476                let d2 = d * sc2 as f32;
3477                let min2_vec = vdupq_n_f32(dmin * m2 as f32);
3478                let ql = &qs[oi * 32..oi * 32 + 32];
3479                let u1_vec = vdupq_n_u8(u1);
3480                let u2_vec = vdupq_n_u8(u2);
3481
3482                let mut lo_acc = vdupq_n_f32(0.0);
3483                let mut hi_acc = vdupq_n_f32(0.0);
3484                for g in 0..2 {
3485                    let raw16 = vld1q_u8(ql.as_ptr().add(g * 16));
3486                    let qh16 = vld1q_u8(qh.as_ptr().add(g * 16));
3487
3488                    let lo_nib = vandq_u8(raw16, low_mask);
3489                    let hi_nib = vshrq_n_u8(raw16, 4);
3490                    let hi_bit1 = vandq_u8(vtstq_u8(qh16, u1_vec), sixteen);
3491                    let hi_bit2 = vandq_u8(vtstq_u8(qh16, u2_vec), sixteen);
3492
3493                    lo_acc = fma_affine16(
3494                        widen_u8x16_to_f32_quads(vorrq_u8(lo_nib, hi_bit1)),
3495                        d1,
3496                        min1_vec,
3497                        x,
3498                        x_base + g * 16,
3499                        lo_acc,
3500                    );
3501                    hi_acc = fma_affine16(
3502                        widen_u8x16_to_f32_quads(vorrq_u8(hi_nib, hi_bit2)),
3503                        d2,
3504                        min2_vec,
3505                        x,
3506                        x_base + 32 + g * 16,
3507                        hi_acc,
3508                    );
3509                }
3510                acc += vaddvq_f32(lo_acc) + vaddvq_f32(hi_acc);
3511                x_base += 64;
3512                is += 2;
3513                u1 <<= 2;
3514                u2 <<= 2;
3515            }
3516        }
3517        acc
3518    }
3519
3520    /// Widens 16 raw 6-bit values (0..=63, already `nibble | (2bit <<
3521    /// 4)`-assembled) into four `float32x4_t` quads, centered by `-32`
3522    /// (Q6_K's fixed bias -- unlike Q4_K/Q5_K's per-sub-block `min`,
3523    /// this is the same constant for every element). The 0..=63 range
3524    /// fits safely in an `i16` after a bit-cast from `u16`, so
3525    /// subtracting the bias in the signed 16-bit domain before the
3526    /// final widen-to-i32-then-f32 step is exact.
3527    #[inline]
3528    #[target_feature(enable = "neon")]
3529    unsafe fn widen_u8x16_centered_to_f32_quads(
3530        v: uint8x16_t,
3531        bias16: int16x8_t,
3532    ) -> (float32x4_t, float32x4_t, float32x4_t, float32x4_t) {
3533        let s16_lo = vsubq_s16(vreinterpretq_s16_u16(vmovl_u8(vget_low_u8(v))), bias16);
3534        let s16_hi = vsubq_s16(vreinterpretq_s16_u16(vmovl_u8(vget_high_u8(v))), bias16);
3535        (
3536            vcvtq_f32_s32(vmovl_s16(vget_low_s16(s16_lo))),
3537            vcvtq_f32_s32(vmovl_s16(vget_high_s16(s16_lo))),
3538            vcvtq_f32_s32(vmovl_s16(vget_low_s16(s16_hi))),
3539            vcvtq_f32_s32(vmovl_s16(vget_high_s16(s16_hi))),
3540        )
3541    }
3542
3543    /// Multiplies 16 f32 values (`quads`) by the single shared scalar
3544    /// `scale` and fused-multiply-accumulates each against the matching
3545    /// 16 activations starting at `x[x_base..]`. Q6_K's dequant is pure
3546    /// `scale * centered_value` (no per-element `min` subtraction, only
3547    /// a fixed bias already folded in by the caller), unlike Q4_K/Q5_K's
3548    /// `fma_affine16`.
3549    #[inline]
3550    #[target_feature(enable = "neon")]
3551    unsafe fn fma_scaled16(
3552        quads: (float32x4_t, float32x4_t, float32x4_t, float32x4_t),
3553        scale: f32,
3554        x: &[f32],
3555        x_base: usize,
3556        mut acc: float32x4_t,
3557    ) -> float32x4_t {
3558        let (q0, q1, q2, q3) = quads;
3559        let mut i = 0usize;
3560        for q in [q0, q1, q2, q3] {
3561            let xv = vld1q_f32(x.as_ptr().add(x_base + i));
3562            acc = vfmaq_f32(acc, vmulq_n_f32(q, scale), xv);
3563            i += 4;
3564        }
3565        acc
3566    }
3567
3568    /// One (q1/q2/q3/q4 in the scalar reference) 32-element group
3569    /// within a Q6_K half-block: 16 lanes at a time (`sub` selects
3570    /// which 16), the 6-bit value is `(ql nibble) | (qh 2-bit field <<
3571    /// 4)`, scaled by `sc[sc_base + sub]` (elements 0..16 of the group
3572    /// use one sub-block scale, 16..32 use the next) and `d`. The `qh`
3573    /// 2-bit field's shift amount is a NEON shift-by-immediate, which
3574    /// Rust's intrinsics require as a compile-time constant -- hence
3575    /// this being a `const QH_SHIFT` generic, monomorphized once per
3576    /// group (0/2/4/6) at its four call sites below, rather than a
3577    /// runtime loop variable. Safety: same contract as
3578    /// `dot_q8_0_f32_neon`.
3579    #[inline]
3580    #[target_feature(enable = "neon")]
3581    #[allow(clippy::too_many_arguments)]
3582    unsafe fn q6_k_group<const QH_SHIFT: i32, const HI_NIBBLE: bool>(
3583        ql: &[u8],
3584        ql_off: usize,
3585        qh: &[u8],
3586        sc: &[u8],
3587        sc_base: usize,
3588        d: f32,
3589        x: &[f32],
3590        x_base: usize,
3591        out_off: usize,
3592        low_mask: uint8x16_t,
3593        two_bit_mask: uint8x16_t,
3594        bias16: int16x8_t,
3595    ) -> f32 {
3596        let mut acc = 0f32;
3597        for sub in 0..2usize {
3598            let byte_off = sub * 16;
3599            let ql_raw = vld1q_u8(ql.as_ptr().add(ql_off + byte_off));
3600            let qh_raw = vld1q_u8(qh.as_ptr().add(byte_off));
3601
3602            let nib = if HI_NIBBLE {
3603                vshrq_n_u8::<4>(ql_raw)
3604            } else {
3605                vandq_u8(ql_raw, low_mask)
3606            };
3607            // QH_SHIFT is only ever 2, 4, or 6 here (q1's shift-0 case
3608            // is handled separately by `q6_k_group_q1` below): NEON's
3609            // shift-by-immediate intrinsics require their N in 1..=8 as
3610            // a genuine compile-time constant, and that assertion is
3611            // checked at monomorphization time even inside a dead
3612            // branch, so a runtime `if QH_SHIFT == 0` guard here would
3613            // still fail to compile for the QH_SHIFT=0 instantiation.
3614            let qh_field = vandq_u8(vshrq_n_u8::<QH_SHIFT>(qh_raw), two_bit_mask);
3615            let raw6 = vorrq_u8(nib, vshlq_n_u8::<4>(qh_field));
3616
3617            let scale = d * (sc[sc_base + sub] as i8) as f32;
3618            let quads = widen_u8x16_centered_to_f32_quads(raw6, bias16);
3619            let acc_vec = fma_scaled16(
3620                quads,
3621                scale,
3622                x,
3623                x_base + out_off + sub * 16,
3624                vdupq_n_f32(0.0),
3625            );
3626            acc += vaddvq_f32(acc_vec);
3627        }
3628        acc
3629    }
3630
3631    /// Same as `q6_k_group`, specialized for q1 (`QH_SHIFT` would be 0,
3632    /// which is out of NEON's valid shift-immediate range) -- the `qh`
3633    /// 2-bit field is already at bit position 0, so no shift is needed
3634    /// before masking. Always low-nibble (`HI_NIBBLE = false` in
3635    /// `q6_k_group`'s terms), matching the scalar reference's `q1`.
3636    #[inline]
3637    #[target_feature(enable = "neon")]
3638    #[allow(clippy::too_many_arguments)]
3639    unsafe fn q6_k_group_q1(
3640        ql: &[u8],
3641        qh: &[u8],
3642        sc: &[u8],
3643        d: f32,
3644        x: &[f32],
3645        x_base: usize,
3646        low_mask: uint8x16_t,
3647        two_bit_mask: uint8x16_t,
3648        bias16: int16x8_t,
3649    ) -> f32 {
3650        let mut acc = 0f32;
3651        // `sub` drives both the byte offset into `ql`/`qh` and the
3652        // index into `sc` -- not just the latter, so clippy's
3653        // iterator-based rewrite doesn't fit.
3654        #[allow(clippy::needless_range_loop)]
3655        for sub in 0..2usize {
3656            let byte_off = sub * 16;
3657            let ql_raw = vld1q_u8(ql.as_ptr().add(byte_off));
3658            let qh_raw = vld1q_u8(qh.as_ptr().add(byte_off));
3659
3660            let nib = vandq_u8(ql_raw, low_mask);
3661            let qh_field = vandq_u8(qh_raw, two_bit_mask);
3662            let raw6 = vorrq_u8(nib, vshlq_n_u8::<4>(qh_field));
3663
3664            let scale = d * (sc[sub] as i8) as f32;
3665            let quads = widen_u8x16_centered_to_f32_quads(raw6, bias16);
3666            let acc_vec = fma_scaled16(quads, scale, x, x_base + sub * 16, vdupq_n_f32(0.0));
3667            acc += vaddvq_f32(acc_vec);
3668        }
3669        acc
3670    }
3671
3672    /// NEON fused Q6_K dot product: dispatches each of the four
3673    /// 32-element groups per half-block (`q1..q4` in the scalar
3674    /// reference) to `q6_k_group`, monomorphized once per group's
3675    /// (compile-time-constant) `qh` shift amount and nibble half.
3676    /// Safety: same contract as `dot_q8_0_f32_neon`.
3677    #[target_feature(enable = "neon")]
3678    pub unsafe fn dot_q6_k_f32_neon(row_bytes: &[u8], x: &[f32]) -> f32 {
3679        debug_assert_eq!(row_bytes.len() % Q6_K_BLOCK_BYTES, 0);
3680        debug_assert_eq!(
3681            row_bytes.len() / Q6_K_BLOCK_BYTES * Q6_K_BLOCK_ELEMS,
3682            x.len()
3683        );
3684        let low_mask = vdupq_n_u8(0x0F);
3685        let two_bit_mask = vdupq_n_u8(0x03);
3686        let bias16 = vdupq_n_s16(32);
3687
3688        let mut acc = 0f32;
3689        let mut x_base = 0usize;
3690        for block in row_bytes.as_chunks::<Q6_K_BLOCK_BYTES>().0 {
3691            let ql_full = &block[0..128];
3692            let qh_full = &block[128..192];
3693            let sc_full = &block[192..208];
3694            let d = f16::from_le_bytes([block[208], block[209]]).to_f32();
3695
3696            for half in 0..2 {
3697                let ql = &ql_full[half * 64..half * 64 + 64];
3698                let qh = &qh_full[half * 32..half * 32 + 32];
3699                let sc = &sc_full[half * 8..half * 8 + 8];
3700                let half_base = x_base + half * 128;
3701
3702                // q1: ql[0..32] low nibble, no qh shift needed, out 0, sc[0..2]
3703                acc += q6_k_group_q1(ql, qh, sc, d, x, half_base, low_mask, two_bit_mask, bias16);
3704                // q2: ql[32..64] low nibble, qh shift 2, out 32, sc[2..4]
3705                acc += q6_k_group::<2, false>(
3706                    ql,
3707                    32,
3708                    qh,
3709                    sc,
3710                    2,
3711                    d,
3712                    x,
3713                    half_base,
3714                    32,
3715                    low_mask,
3716                    two_bit_mask,
3717                    bias16,
3718                );
3719                // q3: ql[0..32] high nibble, qh shift 4, out 64, sc[4..6]
3720                acc += q6_k_group::<4, true>(
3721                    ql,
3722                    0,
3723                    qh,
3724                    sc,
3725                    4,
3726                    d,
3727                    x,
3728                    half_base,
3729                    64,
3730                    low_mask,
3731                    two_bit_mask,
3732                    bias16,
3733                );
3734                // q4: ql[32..64] high nibble, qh shift 6, out 96, sc[6..8]
3735                acc += q6_k_group::<6, true>(
3736                    ql,
3737                    32,
3738                    qh,
3739                    sc,
3740                    6,
3741                    d,
3742                    x,
3743                    half_base,
3744                    96,
3745                    low_mask,
3746                    two_bit_mask,
3747                    bias16,
3748                );
3749            }
3750            x_base += Q6_K_BLOCK_ELEMS;
3751        }
3752        acc
3753    }
3754
3755    /// Decodes 16 real E2M1 codebook values (one nibble byte per lane,
3756    /// each 0..=15, in `nib`) into four `float32x4_t` quads --
3757    /// arithmetically, not via a 16-entry float lookup table. Real
3758    /// E2M1 bit layout: bit3=sign, bits2:1=exponent `e` (0..3),
3759    /// bit0=mantissa `m` (0 or 1). Derivation (verified by hand against
3760    /// every real `KVALUES_MXFP4` entry): for `e=0`, `magnitude = 0.5*m`;
3761    /// for `e>=1`, `magnitude = 2^(e-1) * (1 + 0.5*m)`. Both cases are one
3762    /// formula, `magnitude = pow2(e) * (bias(e) + 0.5*m)`, where
3763    /// `pow2(e) = [1,1,2,4][e]` and `bias(e) = [0,1,1,1][e]` -- looked up
3764    /// via `vqtbl1q_u8` (a real 16-entry byte-table-lookup instruction;
3765    /// `e` is always in 0..3, so this is always an exact, in-range
3766    /// lookup, never the "index >=16 -> zero" out-of-range case). Sign
3767    /// is folded in as a multiplier (`1.0 - 0.25*sign_bit`, where
3768    /// `sign_bit` is 0 or 8) to avoid a branch/select. Cross-validated
3769    /// against the scalar `KVALUES_MXFP4` table across every real
3770    /// nibble value (see this module's tests).
3771    #[inline]
3772    #[target_feature(enable = "neon")]
3773    unsafe fn mxfp4_nibbles_to_f32_quads(
3774        nib: uint8x16_t,
3775    ) -> (float32x4_t, float32x4_t, float32x4_t, float32x4_t) {
3776        let sign_bit = vandq_u8(nib, vdupq_n_u8(0x8));
3777        let e = vandq_u8(vshrq_n_u8(nib, 1), vdupq_n_u8(0x3));
3778        let m = vandq_u8(nib, vdupq_n_u8(0x1));
3779
3780        let pow2_table: [u8; 16] = [1, 1, 2, 4, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0];
3781        let bias_table: [u8; 16] = [0, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0];
3782        let pow2_u8 = vqtbl1q_u8(vld1q_u8(pow2_table.as_ptr()), e);
3783        let bias_u8 = vqtbl1q_u8(vld1q_u8(bias_table.as_ptr()), e);
3784
3785        let (p0, p1, p2, p3) = widen_u8x16_to_f32_quads(pow2_u8);
3786        let (b0, b1, b2, b3) = widen_u8x16_to_f32_quads(bias_u8);
3787        let (m0, m1, m2, m3) = widen_u8x16_to_f32_quads(m);
3788        let (s0, s1, s2, s3) = widen_u8x16_to_f32_quads(sign_bit);
3789
3790        let half = vdupq_n_f32(0.5);
3791        let quarter = vdupq_n_f32(0.25);
3792        let one = vdupq_n_f32(1.0);
3793
3794        let decode = |p: float32x4_t, b: float32x4_t, m: float32x4_t, s: float32x4_t| {
3795            let magnitude = vmulq_f32(p, vfmaq_f32(b, m, half)); // p * (b + 0.5*m)
3796            let sign_mul = vfmsq_f32(one, s, quarter); // 1.0 - 0.25*s
3797            vmulq_f32(magnitude, sign_mul)
3798        };
3799
3800        (
3801            decode(p0, b0, m0, s0),
3802            decode(p1, b1, m1, s1),
3803            decode(p2, b2, m2, s2),
3804            decode(p3, b3, m3, s3),
3805        )
3806    }
3807
3808    /// NEON fused MXFP4 dequant+dot -- same real math as
3809    /// `dot_mxfp4_row_f32_scalar` (real E2M1 codebook + E8M0 scale),
3810    /// decoded via `mxfp4_nibbles_to_f32_quads` instead of the scalar
3811    /// path's 16-entry `KVALUES_MXFP4` table lookup. Cross-validated
3812    /// against the scalar reference across many packed-byte patterns
3813    /// (see this module's tests) -- verified directly on real aarch64
3814    /// hardware (Apple M2 Pro), matching the project's established
3815    /// verify-on-real-hardware discipline for every other NEON kernel
3816    /// here.
3817    #[target_feature(enable = "neon")]
3818    pub unsafe fn dot_mxfp4_row_f32_neon(packed: &[u8], scales: &[u8], x: &[f32]) -> f32 {
3819        debug_assert_eq!(packed.len(), scales.len() * (MXFP4_GROUP_SIZE / 2));
3820        let low_mask = vdupq_n_u8(0x0F);
3821        let mut acc = 0f32;
3822        let mut x_base = 0usize;
3823        for (g, &e_byte) in scales.iter().enumerate() {
3824            let d = e8m0_scale(e_byte);
3825            let group = &packed[g * 16..(g + 1) * 16];
3826            let bytes = vld1q_u8(group.as_ptr());
3827            let lo_nib = vandq_u8(bytes, low_mask);
3828            let hi_nib = vshrq_n_u8(bytes, 4);
3829
3830            let mut block_acc = vdupq_n_f32(0.0);
3831            for (half_idx, nib) in [lo_nib, hi_nib].into_iter().enumerate() {
3832                let (v0, v1, v2, v3) = mxfp4_nibbles_to_f32_quads(nib);
3833                let elem_base = x_base + half_idx * 16;
3834                for (i, v) in [v0, v1, v2, v3].into_iter().enumerate() {
3835                    let xv = vld1q_f32(x.as_ptr().add(elem_base + i * 4));
3836                    block_acc = vfmaq_f32(block_acc, v, xv);
3837                }
3838            }
3839            acc += vaddvq_f32(block_acc) * d;
3840            x_base += MXFP4_GROUP_SIZE;
3841        }
3842        acc
3843    }
3844
3845    /// NEON fused Q8_1 dot product. Mathematically identical to
3846    /// `dot_q8_0_f32_neon` (`y = q*d`) -- see the AVX2 sibling's doc
3847    /// comment for why. Safety: same contract as `dot_q8_0_f32_neon`.
3848    #[target_feature(enable = "neon")]
3849    pub unsafe fn dot_q8_1_f32_neon(row_bytes: &[u8], x: &[f32]) -> f32 {
3850        debug_assert_eq!(row_bytes.len() % Q8_1_BLOCK_BYTES, 0);
3851        let mut acc = 0f32;
3852        for (b, block) in row_bytes
3853            .as_chunks::<Q8_1_BLOCK_BYTES>()
3854            .0
3855            .iter()
3856            .enumerate()
3857        {
3858            let scale = f16::from_le_bytes([block[0], block[1]]).to_f32();
3859            let base = b * Q8_1_BLOCK_ELEMS;
3860            let qs = &block[4..36];
3861
3862            let mut block_acc = vdupq_n_f32(0.0);
3863            for g in 0..2 {
3864                let raw16 = vld1q_s8(qs.as_ptr().add(g * 16) as *const i8);
3865                let lo16 = vmovl_s8(vget_low_s8(raw16));
3866                let hi16 = vmovl_s8(vget_high_s8(raw16));
3867                for (half_idx, half16) in [lo16, hi16].into_iter().enumerate() {
3868                    let lo32 = vmovl_s16(vget_low_s16(half16));
3869                    let hi32 = vmovl_s16(vget_high_s16(half16));
3870                    let f_lo = vcvtq_f32_s32(lo32);
3871                    let f_hi = vcvtq_f32_s32(hi32);
3872                    let elem_base = base + g * 16 + half_idx * 8;
3873                    let x_lo = vld1q_f32(x.as_ptr().add(elem_base));
3874                    let x_hi = vld1q_f32(x.as_ptr().add(elem_base + 4));
3875                    block_acc = vfmaq_f32(block_acc, f_lo, x_lo);
3876                    block_acc = vfmaq_f32(block_acc, f_hi, x_hi);
3877                }
3878            }
3879            acc += vaddvq_f32(block_acc) * scale;
3880        }
3881        acc
3882    }
3883
3884    /// NEON fused Q4_1 dot product. Same nibble-splitting structure as
3885    /// `dot_q4_0_f32_neon`, but asymmetric (`y = nibble*d + m`, no bias
3886    /// subtraction): widens each nibble as unsigned (0..=15) then
3887    /// applies `q*d + m` directly instead of `(q-8)*d`. Safety: same
3888    /// contract as `dot_q8_0_f32_neon`.
3889    #[target_feature(enable = "neon")]
3890    pub unsafe fn dot_q4_1_f32_neon(row_bytes: &[u8], x: &[f32]) -> f32 {
3891        debug_assert_eq!(row_bytes.len() % Q4_1_BLOCK_BYTES, 0);
3892        let low_mask = vdupq_n_u8(0x0F);
3893
3894        let mut acc = 0f32;
3895        for (b, block) in row_bytes
3896            .as_chunks::<Q4_1_BLOCK_BYTES>()
3897            .0
3898            .iter()
3899            .enumerate()
3900        {
3901            let d = f16::from_le_bytes([block[0], block[1]]).to_f32();
3902            let m = f16::from_le_bytes([block[2], block[3]]).to_f32();
3903            let base = b * Q4_1_BLOCK_ELEMS;
3904            let nibbles = vld1q_u8(block.as_ptr().add(4));
3905
3906            let lo_nibbles = vandq_u8(nibbles, low_mask); // elements 0..16
3907            let hi_nibbles = vshrq_n_u8(nibbles, 4); // elements 16..32
3908
3909            let mut block_acc = vdupq_n_f32(0.0);
3910            for (group_idx, nib_u8) in [lo_nibbles, hi_nibbles].into_iter().enumerate() {
3911                let lo16 = vmovl_u8(vget_low_u8(nib_u8));
3912                let hi16 = vmovl_u8(vget_high_u8(nib_u8));
3913                for (half_idx, half16) in [lo16, hi16].into_iter().enumerate() {
3914                    let lo32 = vcvtq_f32_u32(vmovl_u16(vget_low_u16(half16)));
3915                    let hi32 = vcvtq_f32_u32(vmovl_u16(vget_high_u16(half16)));
3916                    let elem_base = base + group_idx * 16 + half_idx * 8;
3917                    let x_lo = vld1q_f32(x.as_ptr().add(elem_base));
3918                    let x_hi = vld1q_f32(x.as_ptr().add(elem_base + 4));
3919                    let w_lo = vfmaq_n_f32(vdupq_n_f32(m), lo32, d);
3920                    let w_hi = vfmaq_n_f32(vdupq_n_f32(m), hi32, d);
3921                    block_acc = vfmaq_f32(block_acc, w_lo, x_lo);
3922                    block_acc = vfmaq_f32(block_acc, w_hi, x_hi);
3923                }
3924            }
3925            acc += vaddvq_f32(block_acc);
3926        }
3927        acc
3928    }
3929
3930    /// NEON fused Q5_0 dot product. Same scalar-prep-then-vectorize
3931    /// approach as `simd_x86::dot_q5_0_f32_avx2` -- see that function's
3932    /// doc comment for why the 5th-bit extraction stays scalar while
3933    /// the 32-element multiply-accumulate is fully vectorized. Safety:
3934    /// same contract as `dot_q8_0_f32_neon`.
3935    #[target_feature(enable = "neon")]
3936    pub unsafe fn dot_q5_0_f32_neon(row_bytes: &[u8], x: &[f32]) -> f32 {
3937        debug_assert_eq!(row_bytes.len() % Q5_0_BLOCK_BYTES, 0);
3938        let mut acc = 0f32;
3939        for (b, block) in row_bytes
3940            .as_chunks::<Q5_0_BLOCK_BYTES>()
3941            .0
3942            .iter()
3943            .enumerate()
3944        {
3945            let d = f16::from_le_bytes([block[0], block[1]]).to_f32();
3946            let qh = u32::from_le_bytes(block[2..6].try_into().unwrap());
3947            let qs = &block[6..22];
3948            let base = b * Q5_0_BLOCK_ELEMS;
3949
3950            let mut vals = [0i8; 32];
3951            for j in 0..16 {
3952                let (xh_0, xh_1) = q5_fifth_bits(qh, j);
3953                vals[j] = (((qs[j] & 0x0F) | xh_0) as i32 - 16) as i8;
3954                vals[j + 16] = (((qs[j] >> 4) | xh_1) as i32 - 16) as i8;
3955            }
3956
3957            let mut block_acc = vdupq_n_f32(0.0);
3958            for g in 0..2 {
3959                let raw16 = vld1q_s8(vals.as_ptr().add(g * 16));
3960                let lo16 = vmovl_s8(vget_low_s8(raw16));
3961                let hi16 = vmovl_s8(vget_high_s8(raw16));
3962                for (half_idx, half16) in [lo16, hi16].into_iter().enumerate() {
3963                    let lo32 = vcvtq_f32_s32(vmovl_s16(vget_low_s16(half16)));
3964                    let hi32 = vcvtq_f32_s32(vmovl_s16(vget_high_s16(half16)));
3965                    let elem_base = base + g * 16 + half_idx * 8;
3966                    let x_lo = vld1q_f32(x.as_ptr().add(elem_base));
3967                    let x_hi = vld1q_f32(x.as_ptr().add(elem_base + 4));
3968                    block_acc = vfmaq_f32(block_acc, lo32, x_lo);
3969                    block_acc = vfmaq_f32(block_acc, hi32, x_hi);
3970                }
3971            }
3972            acc += vaddvq_f32(block_acc) * d;
3973        }
3974        acc
3975    }
3976
3977    /// NEON fused Q5_1 dot product. Same 5th-bit scalar-prep approach
3978    /// as `dot_q5_0_f32_neon`, but asymmetric (`y = q*d + m`, no `-16`
3979    /// bias). Safety: same contract as `dot_q8_0_f32_neon`.
3980    #[target_feature(enable = "neon")]
3981    pub unsafe fn dot_q5_1_f32_neon(row_bytes: &[u8], x: &[f32]) -> f32 {
3982        debug_assert_eq!(row_bytes.len() % Q5_1_BLOCK_BYTES, 0);
3983        let mut acc = 0f32;
3984        for (b, block) in row_bytes
3985            .as_chunks::<Q5_1_BLOCK_BYTES>()
3986            .0
3987            .iter()
3988            .enumerate()
3989        {
3990            let d = f16::from_le_bytes([block[0], block[1]]).to_f32();
3991            let m = f16::from_le_bytes([block[2], block[3]]).to_f32();
3992            let qh = u32::from_le_bytes(block[4..8].try_into().unwrap());
3993            let qs = &block[8..24];
3994            let base = b * Q5_1_BLOCK_ELEMS;
3995
3996            let mut vals = [0u8; 32];
3997            for j in 0..16 {
3998                let (xh_0, xh_1) = q5_fifth_bits(qh, j);
3999                vals[j] = (qs[j] & 0x0F) | xh_0;
4000                vals[j + 16] = (qs[j] >> 4) | xh_1;
4001            }
4002
4003            let mut block_acc = vdupq_n_f32(0.0);
4004            for g in 0..2 {
4005                let raw16 = vld1q_u8(vals.as_ptr().add(g * 16));
4006                let lo16 = vmovl_u8(vget_low_u8(raw16));
4007                let hi16 = vmovl_u8(vget_high_u8(raw16));
4008                for (half_idx, half16) in [lo16, hi16].into_iter().enumerate() {
4009                    let lo32 = vcvtq_f32_u32(vmovl_u16(vget_low_u16(half16)));
4010                    let hi32 = vcvtq_f32_u32(vmovl_u16(vget_high_u16(half16)));
4011                    let elem_base = base + g * 16 + half_idx * 8;
4012                    let x_lo = vld1q_f32(x.as_ptr().add(elem_base));
4013                    let x_hi = vld1q_f32(x.as_ptr().add(elem_base + 4));
4014                    let w_lo = vfmaq_n_f32(vdupq_n_f32(m), lo32, d);
4015                    let w_hi = vfmaq_n_f32(vdupq_n_f32(m), hi32, d);
4016                    block_acc = vfmaq_f32(block_acc, w_lo, x_lo);
4017                    block_acc = vfmaq_f32(block_acc, w_hi, x_hi);
4018                }
4019            }
4020            acc += vaddvq_f32(block_acc);
4021        }
4022        acc
4023    }
4024
4025    /// NEON fused Q2_K dot product. Mirrors `dot_q4_k_f32_neon`'s
4026    /// sub-block loop with a 2-bit field (`(byte >> shift) & 3`) instead
4027    /// of a nibble, and a trivial one-byte-per-sub-block (scale, min)
4028    /// pairing. `shift` only ever takes 0/2/4/6, and NEON's
4029    /// `vshrq_n_u8` accepts a literal immediate the same way this file's
4030    /// `vshrq_n_u8::<4>`/`vshrq_n_u8(_, 4)` calls elsewhere do -- unrolled
4031    /// via a macro over the 4 literal shift values, same reasoning as
4032    /// the AVX2 sibling. Safety: same contract as `dot_q8_0_f32_neon`.
4033    #[target_feature(enable = "neon")]
4034    pub unsafe fn dot_q2_k_f32_neon(row_bytes: &[u8], x: &[f32]) -> f32 {
4035        debug_assert_eq!(row_bytes.len() % Q2_K_BLOCK_BYTES, 0);
4036        let two_bit_mask = vdupq_n_u8(3);
4037        let mut acc = 0f32;
4038        let mut x_base = 0usize;
4039
4040        // NEON's `vshrq_n_u8` requires its immediate shift in 1..=8 (a
4041        // shift of 0 fails a compile-time static assertion) -- unlike
4042        // AVX2's `_mm_srli_epi16`, which allows 0. The `0` literal
4043        // pattern below is matched before the general `$shift:literal`
4044        // arm, so the shift=0 case never generates a call to
4045        // `vshrq_n_u8` at all, just the plain mask.
4046        macro_rules! shr2 {
4047            (0, $v:expr) => {
4048                vandq_u8($v, two_bit_mask)
4049            };
4050            ($shift:literal, $v:expr) => {
4051                vandq_u8(vshrq_n_u8($v, $shift), two_bit_mask)
4052            };
4053        }
4054
4055        macro_rules! q2_k_sub_block {
4056            ($shift:tt, $q:expr, $scales:expr, $is:expr, $d:expr, $dmin:expr, $x:expr, $x_base:expr, $acc:expr) => {{
4057                let sc1 = $scales[$is];
4058                $is += 1;
4059                let dl1 = $d * (sc1 & 0x0F) as f32;
4060                let min1_vec = vdupq_n_f32($dmin * (sc1 >> 4) as f32);
4061                let sc2 = $scales[$is];
4062                $is += 1;
4063                let dl2 = $d * (sc2 & 0x0F) as f32;
4064                let min2_vec = vdupq_n_f32($dmin * (sc2 >> 4) as f32);
4065
4066                let lo16 = vld1q_u8($q.as_ptr());
4067                let hi16 = vld1q_u8($q.as_ptr().add(16));
4068                let lo2 = shr2!($shift, lo16);
4069                let hi2 = shr2!($shift, hi16);
4070
4071                let lo_acc = fma_affine16(
4072                    widen_u8x16_to_f32_quads(lo2),
4073                    dl1,
4074                    min1_vec,
4075                    $x,
4076                    $x_base,
4077                    vdupq_n_f32(0.0),
4078                );
4079                let hi_acc = fma_affine16(
4080                    widen_u8x16_to_f32_quads(hi2),
4081                    dl2,
4082                    min2_vec,
4083                    $x,
4084                    $x_base + 16,
4085                    vdupq_n_f32(0.0),
4086                );
4087                $acc += vaddvq_f32(lo_acc) + vaddvq_f32(hi_acc);
4088                $x_base += 32;
4089            }};
4090        }
4091
4092        for block in row_bytes.as_chunks::<Q2_K_BLOCK_BYTES>().0 {
4093            let scales: &[u8; Q2_K_SCALE_BYTES] = block[0..16].try_into().unwrap();
4094            let qs = &block[16..80];
4095            let d = f16::from_le_bytes([block[80], block[81]]).to_f32();
4096            let dmin = f16::from_le_bytes([block[82], block[83]]).to_f32();
4097
4098            let mut is = 0usize;
4099            for n in 0..2 {
4100                let q = &qs[n * 32..n * 32 + 32];
4101                q2_k_sub_block!(0, q, scales, is, d, dmin, x, x_base, acc);
4102                q2_k_sub_block!(2, q, scales, is, d, dmin, x, x_base, acc);
4103                q2_k_sub_block!(4, q, scales, is, d, dmin, x, x_base, acc);
4104                q2_k_sub_block!(6, q, scales, is, d, dmin, x, x_base, acc);
4105            }
4106        }
4107        acc
4108    }
4109
4110    /// NEON fused Q3_K dot product. Same 2-bit-field extraction as
4111    /// `dot_q2_k_f32_neon` (4 literal shift values), plus a 3rd bit
4112    /// tested from `hmask` via `vtstq_u8` (real bit-test intrinsic,
4113    /// all-ones per lane where the AND is nonzero) -- inverted with
4114    /// `vmvnq_u8` since Q3_K's bias is 4 when the bit is CLEAR, the
4115    /// opposite of Q5_K's "add 16 when set" convention. The 6-bit
4116    /// per-sub-block scale unpacking (`q3_k_unpack_scales`) runs once
4117    /// per block on the scalar side, same as the AVX2 sibling. Safety:
4118    /// same contract as `dot_q8_0_f32_neon`.
4119    #[target_feature(enable = "neon")]
4120    pub unsafe fn dot_q3_k_f32_neon(row_bytes: &[u8], x: &[f32]) -> f32 {
4121        debug_assert_eq!(row_bytes.len() % Q3_K_BLOCK_BYTES, 0);
4122        let two_bit_mask = vdupq_n_u8(3);
4123        let four = vdupq_n_u8(4);
4124        let mut acc = 0f32;
4125        let mut x_base = 0usize;
4126
4127        // See `dot_q2_k_f32_neon`'s `shr2!` for why shift=0 needs its
4128        // own arm: NEON's `vshrq_n_u8` requires its immediate in 1..=8.
4129        macro_rules! shr2 {
4130            (0, $v:expr) => {
4131                vandq_u8($v, two_bit_mask)
4132            };
4133            ($shift:literal, $v:expr) => {
4134                vandq_u8(vshrq_n_u8($v, $shift), two_bit_mask)
4135            };
4136        }
4137
4138        macro_rules! q3_k_sub_block {
4139            ($shift:tt, $q:expr, $hmask:expr, $m_vec:expr, $dl1:expr, $dl2:expr, $x:expr, $x_base:expr, $acc:expr) => {{
4140                let lo16 = vld1q_u8($q.as_ptr());
4141                let hi16 = vld1q_u8($q.as_ptr().add(16));
4142                let lo2 = shr2!($shift, lo16);
4143                let hi2 = shr2!($shift, hi16);
4144
4145                let hmask_lo = vld1q_u8($hmask.as_ptr());
4146                let hmask_hi = vld1q_u8($hmask.as_ptr().add(16));
4147                // bit_clear_* is all-ones per lane where the hmask bit is
4148                // CLEAR (bias=4), all-zero where it's set (bias=0) --
4149                // matching the scalar reference's `if hmask[l] & m != 0
4150                // { 0 } else { 4 }`.
4151                let bit_clear_lo = vmvnq_u8(vtstq_u8(hmask_lo, $m_vec));
4152                let bit_clear_hi = vmvnq_u8(vtstq_u8(hmask_hi, $m_vec));
4153                let bias_lo = vandq_u8(bit_clear_lo, four);
4154                let bias_hi = vandq_u8(bit_clear_hi, four);
4155
4156                let raw_lo_i16_lo = vsubq_s16(vreinterpretq_s16_u16(vmovl_u8(vget_low_u8(lo2))), {
4157                    vreinterpretq_s16_u16(vmovl_u8(vget_low_u8(bias_lo)))
4158                });
4159                let raw_lo_i16_hi =
4160                    vsubq_s16(vreinterpretq_s16_u16(vmovl_u8(vget_high_u8(lo2))), {
4161                        vreinterpretq_s16_u16(vmovl_u8(vget_high_u8(bias_lo)))
4162                    });
4163                let raw_hi_i16_lo = vsubq_s16(vreinterpretq_s16_u16(vmovl_u8(vget_low_u8(hi2))), {
4164                    vreinterpretq_s16_u16(vmovl_u8(vget_low_u8(bias_hi)))
4165                });
4166                let raw_hi_i16_hi =
4167                    vsubq_s16(vreinterpretq_s16_u16(vmovl_u8(vget_high_u8(hi2))), {
4168                        vreinterpretq_s16_u16(vmovl_u8(vget_high_u8(bias_hi)))
4169                    });
4170
4171                let mut lo_acc = vdupq_n_f32(0.0);
4172                let mut hi_acc = vdupq_n_f32(0.0);
4173                for (i, half16) in [raw_lo_i16_lo, raw_lo_i16_hi].into_iter().enumerate() {
4174                    let lo32 = vcvtq_f32_s32(vmovl_s16(vget_low_s16(half16)));
4175                    let hi32 = vcvtq_f32_s32(vmovl_s16(vget_high_s16(half16)));
4176                    let elem_base = $x_base + i * 8;
4177                    let x_lo = vld1q_f32($x.as_ptr().add(elem_base));
4178                    let x_hi = vld1q_f32($x.as_ptr().add(elem_base + 4));
4179                    lo_acc = vfmaq_f32(lo_acc, lo32, x_lo);
4180                    lo_acc = vfmaq_f32(lo_acc, hi32, x_hi);
4181                }
4182                for (i, half16) in [raw_hi_i16_lo, raw_hi_i16_hi].into_iter().enumerate() {
4183                    let lo32 = vcvtq_f32_s32(vmovl_s16(vget_low_s16(half16)));
4184                    let hi32 = vcvtq_f32_s32(vmovl_s16(vget_high_s16(half16)));
4185                    let elem_base = $x_base + 16 + i * 8;
4186                    let x_lo = vld1q_f32($x.as_ptr().add(elem_base));
4187                    let x_hi = vld1q_f32($x.as_ptr().add(elem_base + 4));
4188                    hi_acc = vfmaq_f32(hi_acc, lo32, x_lo);
4189                    hi_acc = vfmaq_f32(hi_acc, hi32, x_hi);
4190                }
4191                $acc += vaddvq_f32(lo_acc) * $dl1 + vaddvq_f32(hi_acc) * $dl2;
4192                $x_base += 32;
4193            }};
4194        }
4195
4196        for block in row_bytes.as_chunks::<Q3_K_BLOCK_BYTES>().0 {
4197            let hmask = &block[0..32];
4198            let qs = &block[32..96];
4199            let scales_raw: &[u8; Q3_K_SCALE_BYTES] = block[96..108].try_into().unwrap();
4200            let d_all = f16::from_le_bytes([block[108], block[109]]).to_f32();
4201            let scales = q3_k_unpack_scales(scales_raw);
4202
4203            let mut is = 0usize;
4204            let mut m = 1u8;
4205            for n in 0..2 {
4206                let q = &qs[n * 32..n * 32 + 32];
4207                for shift in [0u32, 2, 4, 6] {
4208                    let dl1 = d_all * (scales[is] as f32 - 32.0);
4209                    let dl2 = d_all * (scales[is + 1] as f32 - 32.0);
4210                    is += 2;
4211                    let m_vec = vdupq_n_u8(m);
4212                    match shift {
4213                        0 => q3_k_sub_block!(0, q, hmask, m_vec, dl1, dl2, x, x_base, acc),
4214                        2 => q3_k_sub_block!(2, q, hmask, m_vec, dl1, dl2, x, x_base, acc),
4215                        4 => q3_k_sub_block!(4, q, hmask, m_vec, dl1, dl2, x, x_base, acc),
4216                        6 => q3_k_sub_block!(6, q, hmask, m_vec, dl1, dl2, x, x_base, acc),
4217                        _ => unreachable!(),
4218                    }
4219                    m <<= 1;
4220                }
4221            }
4222        }
4223        acc
4224    }
4225
4226    /// NEON fused IQ4_NL dot product. `KVALUES_IQ4NL`'s 16 arbitrary
4227    /// entries are looked up via `vqtbl1q_s8` (a real 16-entry
4228    /// byte-table-lookup instruction; every index is 0..=15 via the
4229    /// `& 0x0F` mask, so this is always an in-range lookup) -- same
4230    /// idea as `mxfp4_nibbles_to_f32_quads`'s use of `vqtbl1q_u8` for
4231    /// its sub-tables, but a direct value lookup instead of an
4232    /// arithmetic reconstruction, since `KVALUES_IQ4NL` isn't a clean
4233    /// power-of-2 pattern. Safety: same contract as `dot_q8_0_f32_neon`.
4234    #[target_feature(enable = "neon")]
4235    pub unsafe fn dot_iq4_nl_f32_neon(row_bytes: &[u8], x: &[f32]) -> f32 {
4236        debug_assert_eq!(row_bytes.len() % IQ4_NL_BLOCK_BYTES, 0);
4237        let low_mask = vdupq_n_u8(0x0F);
4238        let codebook = vld1q_s8(KVALUES_IQ4NL.as_ptr());
4239        let mut acc = 0f32;
4240        let mut x_base = 0usize;
4241        for block in row_bytes.as_chunks::<IQ4_NL_BLOCK_BYTES>().0 {
4242            let d = f16::from_le_bytes([block[0], block[1]]).to_f32();
4243            let qs = &block[2..18];
4244            let bytes = vld1q_u8(qs.as_ptr());
4245            let lo_idx = vandq_u8(bytes, low_mask);
4246            let hi_idx = vshrq_n_u8(bytes, 4);
4247            let lo_vals = vqtbl1q_s8(codebook, lo_idx);
4248            let hi_vals = vqtbl1q_s8(codebook, hi_idx);
4249
4250            let mut block_acc = vdupq_n_f32(0.0);
4251            for (half_idx, vals) in [lo_vals, hi_vals].into_iter().enumerate() {
4252                let lo16 = vmovl_s8(vget_low_s8(vals));
4253                let hi16 = vmovl_s8(vget_high_s8(vals));
4254                for (i, half16) in [lo16, hi16].into_iter().enumerate() {
4255                    let lo32 = vcvtq_f32_s32(vmovl_s16(vget_low_s16(half16)));
4256                    let hi32 = vcvtq_f32_s32(vmovl_s16(vget_high_s16(half16)));
4257                    let elem_base = x_base + half_idx * 16 + i * 8;
4258                    let x_lo = vld1q_f32(x.as_ptr().add(elem_base));
4259                    let x_hi = vld1q_f32(x.as_ptr().add(elem_base + 4));
4260                    block_acc = vfmaq_f32(block_acc, lo32, x_lo);
4261                    block_acc = vfmaq_f32(block_acc, hi32, x_hi);
4262                }
4263            }
4264            acc += vaddvq_f32(block_acc) * d;
4265            x_base += IQ4_NL_BLOCK_ELEMS;
4266        }
4267        acc
4268    }
4269
4270    /// NEON fused IQ4_XS dot product. Same codebook lookup as
4271    /// `dot_iq4_nl_f32_neon`, repeated per 32-element sub-block, each
4272    /// with its own 6-bit scale unpacked exactly as the scalar
4273    /// reference does. Safety: same contract as `dot_q8_0_f32_neon`.
4274    #[target_feature(enable = "neon")]
4275    pub unsafe fn dot_iq4_xs_f32_neon(row_bytes: &[u8], x: &[f32]) -> f32 {
4276        debug_assert_eq!(row_bytes.len() % IQ4_XS_BLOCK_BYTES, 0);
4277        let low_mask = vdupq_n_u8(0x0F);
4278        let codebook = vld1q_s8(KVALUES_IQ4NL.as_ptr());
4279        let mut acc = 0f32;
4280        let mut x_base = 0usize;
4281        for block in row_bytes.as_chunks::<IQ4_XS_BLOCK_BYTES>().0 {
4282            let d = f16::from_le_bytes([block[0], block[1]]).to_f32();
4283            let scales_h = u16::from_le_bytes([block[2], block[3]]);
4284            let scales_l = &block[4..8];
4285            let qs = &block[8..136];
4286
4287            for ib in 0..8 {
4288                let ls = ((scales_l[ib / 2] >> (4 * (ib % 2))) & 0xf)
4289                    | (((scales_h >> (2 * ib)) & 3) as u8) << 4;
4290                let dl = d * (ls as f32 - 32.0);
4291                let sub = &qs[ib * 16..ib * 16 + 16];
4292                let bytes = vld1q_u8(sub.as_ptr());
4293                let lo_idx = vandq_u8(bytes, low_mask);
4294                let hi_idx = vshrq_n_u8(bytes, 4);
4295                let lo_vals = vqtbl1q_s8(codebook, lo_idx);
4296                let hi_vals = vqtbl1q_s8(codebook, hi_idx);
4297
4298                let mut sub_acc = vdupq_n_f32(0.0);
4299                for (half_idx, vals) in [lo_vals, hi_vals].into_iter().enumerate() {
4300                    let lo16 = vmovl_s8(vget_low_s8(vals));
4301                    let hi16 = vmovl_s8(vget_high_s8(vals));
4302                    for (i, half16) in [lo16, hi16].into_iter().enumerate() {
4303                        let lo32 = vcvtq_f32_s32(vmovl_s16(vget_low_s16(half16)));
4304                        let hi32 = vcvtq_f32_s32(vmovl_s16(vget_high_s16(half16)));
4305                        let elem_base = x_base + half_idx * 16 + i * 8;
4306                        let x_lo = vld1q_f32(x.as_ptr().add(elem_base));
4307                        let x_hi = vld1q_f32(x.as_ptr().add(elem_base + 4));
4308                        sub_acc = vfmaq_f32(sub_acc, lo32, x_lo);
4309                        sub_acc = vfmaq_f32(sub_acc, hi32, x_hi);
4310                    }
4311                }
4312                acc += vaddvq_f32(sub_acc) * dl;
4313                x_base += 32;
4314            }
4315        }
4316        acc
4317    }
4318}
4319
4320/// Same idea for Q4_0: fused dequant + dot, no intermediate f32 buffer.
4321/// Dispatches to AVX2+FMA when available, same mechanism as
4322/// `dot_q8_0_f32`.
4323pub fn dot_q4_0_f32(row_bytes: &[u8], x: &[f32]) -> f32 {
4324    #[cfg(target_arch = "x86_64")]
4325    {
4326        if is_x86_feature_detected!("avx2") && is_x86_feature_detected!("fma") {
4327            return unsafe { simd_x86::dot_q4_0_f32_avx2(row_bytes, x) };
4328        }
4329    }
4330    #[cfg(target_arch = "aarch64")]
4331    {
4332        if std::arch::is_aarch64_feature_detected!("neon") {
4333            return unsafe { simd_aarch64::dot_q4_0_f32_neon(row_bytes, x) };
4334        }
4335    }
4336    dot_q4_0_f32_scalar(row_bytes, x)
4337}
4338
4339pub fn dot_q4_0_f32_scalar(row_bytes: &[u8], x: &[f32]) -> f32 {
4340    debug_assert_eq!(row_bytes.len() % Q4_0_BLOCK_BYTES, 0);
4341    let mut acc = 0f32;
4342    for (b, block) in row_bytes
4343        .as_chunks::<Q4_0_BLOCK_BYTES>()
4344        .0
4345        .iter()
4346        .enumerate()
4347    {
4348        let scale = f16::from_le_bytes([block[0], block[1]]).to_f32();
4349        let nibbles = &block[2..18];
4350        let base = b * Q4_0_BLOCK_ELEMS;
4351        let mut block_acc = 0f32;
4352        for i in 0..16 {
4353            let byte = nibbles[i];
4354            let lo = (byte & 0x0F) as i32 - 8;
4355            let hi = ((byte >> 4) & 0x0F) as i32 - 8;
4356            block_acc += (lo as f32) * x[base + i];
4357            block_acc += (hi as f32) * x[base + i + 16];
4358        }
4359        acc += block_acc * scale;
4360    }
4361    acc
4362}
4363
4364/// Dequantize a Q4_1 buffer into f32. Formula verified against real
4365/// `ggml-quants.c::dequantize_row_q4_1`: `y = q*d + m`, no bias
4366/// subtraction (unlike Q4_0's symmetric `q-8`).
4367pub fn dequant_q4_1(src: &[u8]) -> Result<Vec<f32>, QuantError> {
4368    if !src.len().is_multiple_of(Q4_1_BLOCK_BYTES) {
4369        return Err(QuantError::Misaligned(src.len(), Q4_1_BLOCK_BYTES));
4370    }
4371    let n_blocks = src.len() / Q4_1_BLOCK_BYTES;
4372    let mut out = vec![0f32; n_blocks * Q4_1_BLOCK_ELEMS];
4373    for (b, block) in src.as_chunks::<Q4_1_BLOCK_BYTES>().0.iter().enumerate() {
4374        let d = f16::from_le_bytes([block[0], block[1]]).to_f32();
4375        let m = f16::from_le_bytes([block[2], block[3]]).to_f32();
4376        let nibbles = &block[4..20];
4377        let base = b * Q4_1_BLOCK_ELEMS;
4378        for i in 0..16 {
4379            let byte = nibbles[i];
4380            out[base + i] = (byte & 0x0F) as f32 * d + m;
4381            out[base + i + 16] = (byte >> 4) as f32 * d + m;
4382        }
4383    }
4384    Ok(out)
4385}
4386
4387/// Fused Q4_1 dequant+dot, same math as `dequant_q4_1`. Dispatches to
4388/// AVX2+FMA or NEON when available, same mechanism as `dot_q4_0_f32`.
4389pub fn dot_q4_1_f32(row_bytes: &[u8], x: &[f32]) -> f32 {
4390    #[cfg(target_arch = "x86_64")]
4391    {
4392        if is_x86_feature_detected!("avx2") && is_x86_feature_detected!("fma") {
4393            return unsafe { simd_x86::dot_q4_1_f32_avx2(row_bytes, x) };
4394        }
4395    }
4396    #[cfg(target_arch = "aarch64")]
4397    {
4398        if std::arch::is_aarch64_feature_detected!("neon") {
4399            return unsafe { simd_aarch64::dot_q4_1_f32_neon(row_bytes, x) };
4400        }
4401    }
4402    dot_q4_1_f32_scalar(row_bytes, x)
4403}
4404
4405pub fn dot_q4_1_f32_scalar(row_bytes: &[u8], x: &[f32]) -> f32 {
4406    debug_assert_eq!(row_bytes.len() % Q4_1_BLOCK_BYTES, 0);
4407    let mut acc = 0f32;
4408    for (b, block) in row_bytes
4409        .as_chunks::<Q4_1_BLOCK_BYTES>()
4410        .0
4411        .iter()
4412        .enumerate()
4413    {
4414        let d = f16::from_le_bytes([block[0], block[1]]).to_f32();
4415        let m = f16::from_le_bytes([block[2], block[3]]).to_f32();
4416        let nibbles = &block[4..20];
4417        let base = b * Q4_1_BLOCK_ELEMS;
4418        for i in 0..16 {
4419            let byte = nibbles[i];
4420            acc += ((byte & 0x0F) as f32 * d + m) * x[base + i];
4421            acc += ((byte >> 4) as f32 * d + m) * x[base + i + 16];
4422        }
4423    }
4424    acc
4425}
4426
4427/// Unpacks the 5th bit for element `j` (of 16, low-nibble group) and
4428/// `j+16` (high-nibble group) from Q5_0/Q5_1's shared 4-byte `qh`
4429/// bitplane, exactly matching `ggml-quants.c`'s real bit indexing:
4430/// `xh_0` reads bit `j`, `xh_1` reads bit `j+16`, both placed at bit 4
4431/// (value 0 or 16) ready to OR into the corresponding nibble.
4432#[inline]
4433fn q5_fifth_bits(qh: u32, j: usize) -> (u8, u8) {
4434    let xh_0 = ((qh >> j) << 4) as u8 & 0x10;
4435    let xh_1 = (qh >> (j + 12)) as u8 & 0x10;
4436    (xh_0, xh_1)
4437}
4438
4439/// Dequantize a Q5_0 buffer into f32. Formula verified against real
4440/// `ggml-quants.c::dequantize_row_q5_0`: symmetric, `y = (q-16)*d`
4441/// where `q` is the 4-bit nibble with the 5th bit from `qh` ORed in.
4442pub fn dequant_q5_0(src: &[u8]) -> Result<Vec<f32>, QuantError> {
4443    if !src.len().is_multiple_of(Q5_0_BLOCK_BYTES) {
4444        return Err(QuantError::Misaligned(src.len(), Q5_0_BLOCK_BYTES));
4445    }
4446    let n_blocks = src.len() / Q5_0_BLOCK_BYTES;
4447    let mut out = vec![0f32; n_blocks * Q5_0_BLOCK_ELEMS];
4448    for (b, block) in src.as_chunks::<Q5_0_BLOCK_BYTES>().0.iter().enumerate() {
4449        let d = f16::from_le_bytes([block[0], block[1]]).to_f32();
4450        let qh = u32::from_le_bytes(block[2..6].try_into().unwrap());
4451        let qs = &block[6..22];
4452        let base = b * Q5_0_BLOCK_ELEMS;
4453        for j in 0..16 {
4454            let (xh_0, xh_1) = q5_fifth_bits(qh, j);
4455            let x0 = ((qs[j] & 0x0F) | xh_0) as i32 - 16;
4456            let x1 = ((qs[j] >> 4) | xh_1) as i32 - 16;
4457            out[base + j] = x0 as f32 * d;
4458            out[base + j + 16] = x1 as f32 * d;
4459        }
4460    }
4461    Ok(out)
4462}
4463
4464/// Fused Q5_0 dequant+dot, same math as `dequant_q5_0`. Dispatches to
4465/// AVX2+FMA or NEON when available, same mechanism as `dot_q4_0_f32`.
4466pub fn dot_q5_0_f32(row_bytes: &[u8], x: &[f32]) -> f32 {
4467    #[cfg(target_arch = "x86_64")]
4468    {
4469        if is_x86_feature_detected!("avx2") && is_x86_feature_detected!("fma") {
4470            return unsafe { simd_x86::dot_q5_0_f32_avx2(row_bytes, x) };
4471        }
4472    }
4473    #[cfg(target_arch = "aarch64")]
4474    {
4475        if std::arch::is_aarch64_feature_detected!("neon") {
4476            return unsafe { simd_aarch64::dot_q5_0_f32_neon(row_bytes, x) };
4477        }
4478    }
4479    dot_q5_0_f32_scalar(row_bytes, x)
4480}
4481
4482pub fn dot_q5_0_f32_scalar(row_bytes: &[u8], x: &[f32]) -> f32 {
4483    debug_assert_eq!(row_bytes.len() % Q5_0_BLOCK_BYTES, 0);
4484    let mut acc = 0f32;
4485    for (b, block) in row_bytes
4486        .as_chunks::<Q5_0_BLOCK_BYTES>()
4487        .0
4488        .iter()
4489        .enumerate()
4490    {
4491        let d = f16::from_le_bytes([block[0], block[1]]).to_f32();
4492        let qh = u32::from_le_bytes(block[2..6].try_into().unwrap());
4493        let qs = &block[6..22];
4494        let base = b * Q5_0_BLOCK_ELEMS;
4495        for j in 0..16 {
4496            let (xh_0, xh_1) = q5_fifth_bits(qh, j);
4497            let x0 = ((qs[j] & 0x0F) | xh_0) as i32 - 16;
4498            let x1 = ((qs[j] >> 4) | xh_1) as i32 - 16;
4499            acc += (x0 as f32 * d) * x[base + j];
4500            acc += (x1 as f32 * d) * x[base + j + 16];
4501        }
4502    }
4503    acc
4504}
4505
4506/// Dequantize a Q5_1 buffer into f32. Formula verified against real
4507/// `ggml-quants.c::dequantize_row_q5_1`: Q5_0's 5th-bit scheme, but
4508/// asymmetric like Q4_1 (`y = q*d + m`, no `-16` bias).
4509pub fn dequant_q5_1(src: &[u8]) -> Result<Vec<f32>, QuantError> {
4510    if !src.len().is_multiple_of(Q5_1_BLOCK_BYTES) {
4511        return Err(QuantError::Misaligned(src.len(), Q5_1_BLOCK_BYTES));
4512    }
4513    let n_blocks = src.len() / Q5_1_BLOCK_BYTES;
4514    let mut out = vec![0f32; n_blocks * Q5_1_BLOCK_ELEMS];
4515    for (b, block) in src.as_chunks::<Q5_1_BLOCK_BYTES>().0.iter().enumerate() {
4516        let d = f16::from_le_bytes([block[0], block[1]]).to_f32();
4517        let m = f16::from_le_bytes([block[2], block[3]]).to_f32();
4518        let qh = u32::from_le_bytes(block[4..8].try_into().unwrap());
4519        let qs = &block[8..24];
4520        let base = b * Q5_1_BLOCK_ELEMS;
4521        for j in 0..16 {
4522            let (xh_0, xh_1) = q5_fifth_bits(qh, j);
4523            let x0 = (qs[j] & 0x0F) | xh_0;
4524            let x1 = (qs[j] >> 4) | xh_1;
4525            out[base + j] = x0 as f32 * d + m;
4526            out[base + j + 16] = x1 as f32 * d + m;
4527        }
4528    }
4529    Ok(out)
4530}
4531
4532/// Fused Q5_1 dequant+dot, same math as `dequant_q5_1`. Dispatches to
4533/// AVX2+FMA or NEON when available, same mechanism as `dot_q4_0_f32`.
4534pub fn dot_q5_1_f32(row_bytes: &[u8], x: &[f32]) -> f32 {
4535    #[cfg(target_arch = "x86_64")]
4536    {
4537        if is_x86_feature_detected!("avx2") && is_x86_feature_detected!("fma") {
4538            return unsafe { simd_x86::dot_q5_1_f32_avx2(row_bytes, x) };
4539        }
4540    }
4541    #[cfg(target_arch = "aarch64")]
4542    {
4543        if std::arch::is_aarch64_feature_detected!("neon") {
4544            return unsafe { simd_aarch64::dot_q5_1_f32_neon(row_bytes, x) };
4545        }
4546    }
4547    dot_q5_1_f32_scalar(row_bytes, x)
4548}
4549
4550pub fn dot_q5_1_f32_scalar(row_bytes: &[u8], x: &[f32]) -> f32 {
4551    debug_assert_eq!(row_bytes.len() % Q5_1_BLOCK_BYTES, 0);
4552    let mut acc = 0f32;
4553    for (b, block) in row_bytes
4554        .as_chunks::<Q5_1_BLOCK_BYTES>()
4555        .0
4556        .iter()
4557        .enumerate()
4558    {
4559        let d = f16::from_le_bytes([block[0], block[1]]).to_f32();
4560        let m = f16::from_le_bytes([block[2], block[3]]).to_f32();
4561        let qh = u32::from_le_bytes(block[4..8].try_into().unwrap());
4562        let qs = &block[8..24];
4563        let base = b * Q5_1_BLOCK_ELEMS;
4564        for j in 0..16 {
4565            let (xh_0, xh_1) = q5_fifth_bits(qh, j);
4566            let x0 = (qs[j] & 0x0F) | xh_0;
4567            let x1 = (qs[j] >> 4) | xh_1;
4568            acc += (x0 as f32 * d + m) * x[base + j];
4569            acc += (x1 as f32 * d + m) * x[base + j + 16];
4570        }
4571    }
4572    acc
4573}
4574
4575/// Dequantize a Q8_1 buffer into f32. Formula verified against real
4576/// `ggml-quants.c::dequantize_row_q8_1`: identical to Q8_0 (`y = q*d`)
4577/// -- the extra `s` field (upstream: a precomputed per-block sum used
4578/// only by ggml's own fused SIMD dot kernels) doesn't change the
4579/// dequantized value and is intentionally unread here.
4580pub fn dequant_q8_1(src: &[u8]) -> Result<Vec<f32>, QuantError> {
4581    if !src.len().is_multiple_of(Q8_1_BLOCK_BYTES) {
4582        return Err(QuantError::Misaligned(src.len(), Q8_1_BLOCK_BYTES));
4583    }
4584    let n_blocks = src.len() / Q8_1_BLOCK_BYTES;
4585    let mut out = Vec::with_capacity(n_blocks * Q8_1_BLOCK_ELEMS);
4586    for block in src.as_chunks::<Q8_1_BLOCK_BYTES>().0 {
4587        let d = f16::from_le_bytes([block[0], block[1]]).to_f32();
4588        for i in 0..Q8_1_BLOCK_ELEMS {
4589            let q = block[4 + i] as i8;
4590            out.push(q as f32 * d);
4591        }
4592    }
4593    Ok(out)
4594}
4595
4596/// Fused Q8_1 dequant+dot, same math as `dequant_q8_1`. Dispatches to
4597/// AVX2+FMA or NEON when available -- mathematically identical to
4598/// Q8_0 (`y = q*d`), so the SIMD kernels are Q8_0's kernels with the
4599/// quantized bytes read from offset 4 instead of offset 2 (Q8_1's
4600/// block has an extra 2-byte field between `d` and the int8 values).
4601pub fn dot_q8_1_f32(row_bytes: &[u8], x: &[f32]) -> f32 {
4602    #[cfg(target_arch = "x86_64")]
4603    {
4604        if is_x86_feature_detected!("avx2") && is_x86_feature_detected!("fma") {
4605            return unsafe { simd_x86::dot_q8_1_f32_avx2(row_bytes, x) };
4606        }
4607    }
4608    #[cfg(target_arch = "aarch64")]
4609    {
4610        if std::arch::is_aarch64_feature_detected!("neon") {
4611            return unsafe { simd_aarch64::dot_q8_1_f32_neon(row_bytes, x) };
4612        }
4613    }
4614    dot_q8_1_f32_scalar(row_bytes, x)
4615}
4616
4617pub fn dot_q8_1_f32_scalar(row_bytes: &[u8], x: &[f32]) -> f32 {
4618    debug_assert_eq!(row_bytes.len() % Q8_1_BLOCK_BYTES, 0);
4619    let mut acc = 0f32;
4620    for (b, block) in row_bytes
4621        .as_chunks::<Q8_1_BLOCK_BYTES>()
4622        .0
4623        .iter()
4624        .enumerate()
4625    {
4626        let d = f16::from_le_bytes([block[0], block[1]]).to_f32();
4627        let base = b * Q8_1_BLOCK_ELEMS;
4628        let mut block_acc = 0f32;
4629        for i in 0..Q8_1_BLOCK_ELEMS {
4630            let q = block[4 + i] as i8;
4631            block_acc += (q as f32) * x[base + i];
4632        }
4633        acc += block_acc * d;
4634    }
4635    acc
4636}
4637
4638/// Dequantize a Q2_K buffer into f32. Formula verified against real
4639/// `ggml-quants.c::dequantize_row_q2_K`: 16 sub-blocks of 16 elements,
4640/// each sub-block's `(scale, min)` packed one byte per sub-block
4641/// (`sc & 0xF` = 4-bit scale, `sc >> 4` = 4-bit min -- much simpler
4642/// than Q4_K's cross-byte 6-bit packing), value = `d*scale*raw2bit -
4643/// dmin*min`, `raw2bit` in 0..=3 (2 bits per element from `qs`, 4
4644/// elements packed per byte).
4645pub fn dequant_q2_k(src: &[u8]) -> Result<Vec<f32>, QuantError> {
4646    if !src.len().is_multiple_of(Q2_K_BLOCK_BYTES) {
4647        return Err(QuantError::Misaligned(src.len(), Q2_K_BLOCK_BYTES));
4648    }
4649    let n_blocks = src.len() / Q2_K_BLOCK_BYTES;
4650    let mut out = Vec::with_capacity(n_blocks * Q2_K_BLOCK_ELEMS);
4651    for block in src.as_chunks::<Q2_K_BLOCK_BYTES>().0 {
4652        let scales: &[u8; Q2_K_SCALE_BYTES] = block[0..16].try_into().unwrap();
4653        let qs = &block[16..80];
4654        let d = f16::from_le_bytes([block[80], block[81]]).to_f32();
4655        let dmin = f16::from_le_bytes([block[82], block[83]]).to_f32();
4656
4657        let mut is = 0usize;
4658        for n in 0..2 {
4659            let q = &qs[n * 32..n * 32 + 32];
4660            let mut shift = 0u32;
4661            for _j in 0..4 {
4662                let sc1 = scales[is];
4663                is += 1;
4664                let (dl1, ml1) = (d * (sc1 & 0x0F) as f32, dmin * (sc1 >> 4) as f32);
4665                for &byte in &q[0..16] {
4666                    let raw = (byte >> shift) & 3;
4667                    out.push(dl1 * raw as f32 - ml1);
4668                }
4669
4670                let sc2 = scales[is];
4671                is += 1;
4672                let (dl2, ml2) = (d * (sc2 & 0x0F) as f32, dmin * (sc2 >> 4) as f32);
4673                for &byte in &q[16..32] {
4674                    let raw = (byte >> shift) & 3;
4675                    out.push(dl2 * raw as f32 - ml2);
4676                }
4677                shift += 2;
4678            }
4679        }
4680    }
4681    Ok(out)
4682}
4683
4684/// Fused Q2_K dequant+dot, same math as `dequant_q2_k`. Dispatches to
4685/// AVX2+FMA or NEON when available, same mechanism as `dot_q4_k_f32`.
4686pub fn dot_q2_k_f32(row_bytes: &[u8], x: &[f32]) -> f32 {
4687    #[cfg(target_arch = "x86_64")]
4688    {
4689        if is_x86_feature_detected!("avx2") && is_x86_feature_detected!("fma") {
4690            return unsafe { simd_x86::dot_q2_k_f32_avx2(row_bytes, x) };
4691        }
4692    }
4693    #[cfg(target_arch = "aarch64")]
4694    {
4695        if std::arch::is_aarch64_feature_detected!("neon") {
4696            return unsafe { simd_aarch64::dot_q2_k_f32_neon(row_bytes, x) };
4697        }
4698    }
4699    dot_q2_k_f32_scalar(row_bytes, x)
4700}
4701
4702pub fn dot_q2_k_f32_scalar(row_bytes: &[u8], x: &[f32]) -> f32 {
4703    debug_assert_eq!(row_bytes.len() % Q2_K_BLOCK_BYTES, 0);
4704    let mut acc = 0f32;
4705    let mut x_base = 0usize;
4706    for block in row_bytes.as_chunks::<Q2_K_BLOCK_BYTES>().0 {
4707        let scales: &[u8; Q2_K_SCALE_BYTES] = block[0..16].try_into().unwrap();
4708        let qs = &block[16..80];
4709        let d = f16::from_le_bytes([block[80], block[81]]).to_f32();
4710        let dmin = f16::from_le_bytes([block[82], block[83]]).to_f32();
4711
4712        let mut is = 0usize;
4713        for n in 0..2 {
4714            let q = &qs[n * 32..n * 32 + 32];
4715            let mut shift = 0u32;
4716            for _j in 0..4 {
4717                let sc1 = scales[is];
4718                is += 1;
4719                let (dl1, ml1) = (d * (sc1 & 0x0F) as f32, dmin * (sc1 >> 4) as f32);
4720                for l in 0..16 {
4721                    let raw = (q[l] >> shift) & 3;
4722                    acc += (dl1 * raw as f32 - ml1) * x[x_base + l];
4723                }
4724
4725                let sc2 = scales[is];
4726                is += 1;
4727                let (dl2, ml2) = (d * (sc2 & 0x0F) as f32, dmin * (sc2 >> 4) as f32);
4728                for l in 0..16 {
4729                    let raw = (q[l + 16] >> shift) & 3;
4730                    acc += (dl2 * raw as f32 - ml2) * x[x_base + l + 16];
4731                }
4732                shift += 2;
4733                x_base += 32;
4734            }
4735        }
4736    }
4737    acc
4738}
4739
4740/// Unpacks Q3_K's 12-byte packed `scales` field into 16 signed 6-bit
4741/// values (range -32..=31 after the caller subtracts 32), transcribed
4742/// exactly from `dequantize_row_q3_K`'s real `aux[]` byte-wise
4743/// interleaving (four `u32`-at-a-time operations, here done per-byte
4744/// since Rust has no ambient SIMD-in-a-register trick to mirror C's
4745/// `uint32_t` shortcut) -- not reverse-engineered from the bit layout
4746/// alone, since a plausible-looking guess at this specific packing
4747/// would be easy to get wrong in a way indistinguishable from correct
4748/// without the real source.
4749fn q3_k_unpack_scales(raw: &[u8; Q3_K_SCALE_BYTES]) -> [i8; 16] {
4750    const KMASK1: u8 = 0x03;
4751    const KMASK2: u8 = 0x0F;
4752    let mut out = [0u8; 16];
4753    for j in 0..4 {
4754        let (a0, a1, tmp) = (raw[j], raw[4 + j], raw[8 + j]);
4755        // `tmp >> 0` (a no-op, dropped) kept as an explicit `>> 0` in
4756        // the real C source purely for symmetry with the `>>2`/`>>4`/
4757        // `>>6` siblings below; clippy correctly flags it as dead code
4758        // once written idiomatically in Rust.
4759        out[j] = (a0 & KMASK2) | ((tmp & KMASK1) << 4);
4760        out[4 + j] = (a1 & KMASK2) | (((tmp >> 2) & KMASK1) << 4);
4761        out[8 + j] = (a0 >> 4) | (((tmp >> 4) & KMASK1) << 4);
4762        out[12 + j] = (a1 >> 4) | (((tmp >> 6) & KMASK1) << 4);
4763    }
4764    // Values are always in 0..64 (6 significant bits, top 2 bits of
4765    // each byte never set), so this bit-cast to i8 is exactly the
4766    // `int8_t` reinterpretation the real C code performs.
4767    out.map(|b| b as i8)
4768}
4769
4770/// Dequantize a Q3_K buffer into f32. Formula verified against real
4771/// `ggml-quants.c::dequantize_row_q3_K`: 16 sub-blocks of 16 elements,
4772/// value = `d_all*(scale-32)*(raw3bit-bias)`, `raw3bit` = 2 bits from
4773/// `qs` plus 1 high bit from `hmask` (bit `m`, `m` sweeping all 8 bit
4774/// positions across the whole block -- `hmask` is indexed the same way
4775/// regardless of which half of `qs` is active, only the bit tested
4776/// changes), `bias` = 4 when the high bit is clear, 0 when set.
4777pub fn dequant_q3_k(src: &[u8]) -> Result<Vec<f32>, QuantError> {
4778    if !src.len().is_multiple_of(Q3_K_BLOCK_BYTES) {
4779        return Err(QuantError::Misaligned(src.len(), Q3_K_BLOCK_BYTES));
4780    }
4781    let n_blocks = src.len() / Q3_K_BLOCK_BYTES;
4782    let mut out = Vec::with_capacity(n_blocks * Q3_K_BLOCK_ELEMS);
4783    for block in src.as_chunks::<Q3_K_BLOCK_BYTES>().0 {
4784        let hmask = &block[0..32];
4785        let qs = &block[32..96];
4786        let scales_raw: &[u8; Q3_K_SCALE_BYTES] = block[96..108].try_into().unwrap();
4787        let d_all = f16::from_le_bytes([block[108], block[109]]).to_f32();
4788        let scales = q3_k_unpack_scales(scales_raw);
4789
4790        let mut is = 0usize;
4791        let mut m = 1u8;
4792        for n in 0..2 {
4793            let q = &qs[n * 32..n * 32 + 32];
4794            let mut shift = 0u32;
4795            for _j in 0..4 {
4796                let dl1 = d_all * (scales[is] as f32 - 32.0);
4797                is += 1;
4798                for l in 0..16 {
4799                    let raw = ((q[l] >> shift) & 3) as i32;
4800                    let bias = if hmask[l] & m != 0 { 0 } else { 4 };
4801                    out.push(dl1 * (raw - bias) as f32);
4802                }
4803
4804                let dl2 = d_all * (scales[is] as f32 - 32.0);
4805                is += 1;
4806                for l in 0..16 {
4807                    let raw = ((q[l + 16] >> shift) & 3) as i32;
4808                    let bias = if hmask[l + 16] & m != 0 { 0 } else { 4 };
4809                    out.push(dl2 * (raw - bias) as f32);
4810                }
4811                shift += 2;
4812                m <<= 1;
4813            }
4814        }
4815    }
4816    Ok(out)
4817}
4818
4819/// Fused Q3_K dequant+dot, same math as `dequant_q3_k`. Dispatches to
4820/// AVX2+FMA or NEON when available, same mechanism as `dot_q4_k_f32`.
4821pub fn dot_q3_k_f32(row_bytes: &[u8], x: &[f32]) -> f32 {
4822    #[cfg(target_arch = "x86_64")]
4823    {
4824        if is_x86_feature_detected!("avx2") && is_x86_feature_detected!("fma") {
4825            return unsafe { simd_x86::dot_q3_k_f32_avx2(row_bytes, x) };
4826        }
4827    }
4828    #[cfg(target_arch = "aarch64")]
4829    {
4830        if std::arch::is_aarch64_feature_detected!("neon") {
4831            return unsafe { simd_aarch64::dot_q3_k_f32_neon(row_bytes, x) };
4832        }
4833    }
4834    dot_q3_k_f32_scalar(row_bytes, x)
4835}
4836
4837pub fn dot_q3_k_f32_scalar(row_bytes: &[u8], x: &[f32]) -> f32 {
4838    debug_assert_eq!(row_bytes.len() % Q3_K_BLOCK_BYTES, 0);
4839    let mut acc = 0f32;
4840    let mut x_base = 0usize;
4841    for block in row_bytes.as_chunks::<Q3_K_BLOCK_BYTES>().0 {
4842        let hmask = &block[0..32];
4843        let qs = &block[32..96];
4844        let scales_raw: &[u8; Q3_K_SCALE_BYTES] = block[96..108].try_into().unwrap();
4845        let d_all = f16::from_le_bytes([block[108], block[109]]).to_f32();
4846        let scales = q3_k_unpack_scales(scales_raw);
4847
4848        let mut is = 0usize;
4849        let mut m = 1u8;
4850        for n in 0..2 {
4851            let q = &qs[n * 32..n * 32 + 32];
4852            let mut shift = 0u32;
4853            for _j in 0..4 {
4854                let dl1 = d_all * (scales[is] as f32 - 32.0);
4855                is += 1;
4856                for l in 0..16 {
4857                    let raw = ((q[l] >> shift) & 3) as i32;
4858                    let bias = if hmask[l] & m != 0 { 0 } else { 4 };
4859                    acc += (dl1 * (raw - bias) as f32) * x[x_base + l];
4860                }
4861
4862                let dl2 = d_all * (scales[is] as f32 - 32.0);
4863                is += 1;
4864                for l in 0..16 {
4865                    let raw = ((q[l + 16] >> shift) & 3) as i32;
4866                    let bias = if hmask[l + 16] & m != 0 { 0 } else { 4 };
4867                    acc += (dl2 * (raw - bias) as f32) * x[x_base + l + 16];
4868                }
4869                shift += 2;
4870                m <<= 1;
4871                x_base += 32;
4872            }
4873        }
4874    }
4875    acc
4876}
4877
4878pub const IQ4_NL_BLOCK_BYTES: usize = 18;
4879pub const IQ4_NL_BLOCK_ELEMS: usize = 32;
4880pub const IQ4_XS_BLOCK_BYTES: usize = 136;
4881pub const IQ4_XS_BLOCK_ELEMS: usize = 256;
4882
4883/// The 16-entry non-linear codebook shared by IQ4_NL and IQ4_XS: a 4-bit
4884/// index maps to one of these signed `i8` values instead of a linear
4885/// `nibble*scale` transform. Verified against real ggml-quants.c
4886/// (`kvalues_iq4nl`) rather than derived.
4887pub(crate) const KVALUES_IQ4NL: [i8; 16] = [
4888    -127, -104, -83, -65, -49, -35, -22, -10, 1, 13, 25, 38, 53, 69, 89, 113,
4889];
4890
4891pub fn dequant_iq4_nl(src: &[u8]) -> Result<Vec<f32>, QuantError> {
4892    if !src.len().is_multiple_of(IQ4_NL_BLOCK_BYTES) {
4893        return Err(QuantError::Misaligned(src.len(), IQ4_NL_BLOCK_BYTES));
4894    }
4895    let n_blocks = src.len() / IQ4_NL_BLOCK_BYTES;
4896    let mut out = Vec::with_capacity(n_blocks * IQ4_NL_BLOCK_ELEMS);
4897    for block in src.as_chunks::<IQ4_NL_BLOCK_BYTES>().0 {
4898        let d = f16::from_le_bytes([block[0], block[1]]).to_f32();
4899        let qs = &block[2..18];
4900        let mut lo = [0f32; 16];
4901        let mut hi = [0f32; 16];
4902        for (j, &byte) in qs.iter().enumerate() {
4903            lo[j] = d * KVALUES_IQ4NL[(byte & 0xf) as usize] as f32;
4904            hi[j] = d * KVALUES_IQ4NL[(byte >> 4) as usize] as f32;
4905        }
4906        out.extend_from_slice(&lo);
4907        out.extend_from_slice(&hi);
4908    }
4909    Ok(out)
4910}
4911
4912/// Fused IQ4_NL dequant+dot, same math as `dequant_iq4_nl`. Dispatches
4913/// to AVX2+FMA or NEON when available, same mechanism as `dot_q4_0_f32`.
4914pub fn dot_iq4_nl_f32(row_bytes: &[u8], x: &[f32]) -> f32 {
4915    #[cfg(target_arch = "x86_64")]
4916    {
4917        if is_x86_feature_detected!("avx2") && is_x86_feature_detected!("fma") {
4918            return unsafe { simd_x86::dot_iq4_nl_f32_avx2(row_bytes, x) };
4919        }
4920    }
4921    #[cfg(target_arch = "aarch64")]
4922    {
4923        if std::arch::is_aarch64_feature_detected!("neon") {
4924            return unsafe { simd_aarch64::dot_iq4_nl_f32_neon(row_bytes, x) };
4925        }
4926    }
4927    dot_iq4_nl_f32_scalar(row_bytes, x)
4928}
4929
4930pub fn dot_iq4_nl_f32_scalar(row_bytes: &[u8], x: &[f32]) -> f32 {
4931    debug_assert_eq!(row_bytes.len() % IQ4_NL_BLOCK_BYTES, 0);
4932    let mut acc = 0f32;
4933    let mut x_base = 0usize;
4934    for block in row_bytes.as_chunks::<IQ4_NL_BLOCK_BYTES>().0 {
4935        let d = f16::from_le_bytes([block[0], block[1]]).to_f32();
4936        let qs = &block[2..18];
4937        for (j, &byte) in qs.iter().enumerate() {
4938            acc += (d * KVALUES_IQ4NL[(byte & 0xf) as usize] as f32) * x[x_base + j];
4939            acc += (d * KVALUES_IQ4NL[(byte >> 4) as usize] as f32) * x[x_base + 16 + j];
4940        }
4941        x_base += IQ4_NL_BLOCK_ELEMS;
4942    }
4943    acc
4944}
4945
4946pub fn dequant_iq4_xs(src: &[u8]) -> Result<Vec<f32>, QuantError> {
4947    if !src.len().is_multiple_of(IQ4_XS_BLOCK_BYTES) {
4948        return Err(QuantError::Misaligned(src.len(), IQ4_XS_BLOCK_BYTES));
4949    }
4950    let n_blocks = src.len() / IQ4_XS_BLOCK_BYTES;
4951    let mut out = Vec::with_capacity(n_blocks * IQ4_XS_BLOCK_ELEMS);
4952    for block in src.as_chunks::<IQ4_XS_BLOCK_BYTES>().0 {
4953        let d = f16::from_le_bytes([block[0], block[1]]).to_f32();
4954        let scales_h = u16::from_le_bytes([block[2], block[3]]);
4955        let scales_l = &block[4..8];
4956        let qs = &block[8..136];
4957
4958        for ib in 0..8 {
4959            let ls = ((scales_l[ib / 2] >> (4 * (ib % 2))) & 0xf)
4960                | (((scales_h >> (2 * ib)) & 3) as u8) << 4;
4961            let dl = d * (ls as f32 - 32.0);
4962            let sub = &qs[ib * 16..ib * 16 + 16];
4963            let mut lo = [0f32; 16];
4964            let mut hi = [0f32; 16];
4965            for (j, &byte) in sub.iter().enumerate() {
4966                lo[j] = dl * KVALUES_IQ4NL[(byte & 0xf) as usize] as f32;
4967                hi[j] = dl * KVALUES_IQ4NL[(byte >> 4) as usize] as f32;
4968            }
4969            out.extend_from_slice(&lo);
4970            out.extend_from_slice(&hi);
4971        }
4972    }
4973    Ok(out)
4974}
4975
4976/// Fused IQ4_XS dequant+dot, same math as `dequant_iq4_xs`. Dispatches
4977/// to AVX2+FMA or NEON when available, same mechanism as `dot_q4_0_f32`.
4978pub fn dot_iq4_xs_f32(row_bytes: &[u8], x: &[f32]) -> f32 {
4979    #[cfg(target_arch = "x86_64")]
4980    {
4981        if is_x86_feature_detected!("avx2") && is_x86_feature_detected!("fma") {
4982            return unsafe { simd_x86::dot_iq4_xs_f32_avx2(row_bytes, x) };
4983        }
4984    }
4985    #[cfg(target_arch = "aarch64")]
4986    {
4987        if std::arch::is_aarch64_feature_detected!("neon") {
4988            return unsafe { simd_aarch64::dot_iq4_xs_f32_neon(row_bytes, x) };
4989        }
4990    }
4991    dot_iq4_xs_f32_scalar(row_bytes, x)
4992}
4993
4994pub fn dot_iq4_xs_f32_scalar(row_bytes: &[u8], x: &[f32]) -> f32 {
4995    debug_assert_eq!(row_bytes.len() % IQ4_XS_BLOCK_BYTES, 0);
4996    let mut acc = 0f32;
4997    let mut x_base = 0usize;
4998    for block in row_bytes.as_chunks::<IQ4_XS_BLOCK_BYTES>().0 {
4999        let d = f16::from_le_bytes([block[0], block[1]]).to_f32();
5000        let scales_h = u16::from_le_bytes([block[2], block[3]]);
5001        let scales_l = &block[4..8];
5002        let qs = &block[8..136];
5003
5004        for ib in 0..8 {
5005            let ls = ((scales_l[ib / 2] >> (4 * (ib % 2))) & 0xf)
5006                | (((scales_h >> (2 * ib)) & 3) as u8) << 4;
5007            let dl = d * (ls as f32 - 32.0);
5008            let sub = &qs[ib * 16..ib * 16 + 16];
5009            for (j, &byte) in sub.iter().enumerate() {
5010                acc += (dl * KVALUES_IQ4NL[(byte & 0xf) as usize] as f32) * x[x_base + j];
5011                acc += (dl * KVALUES_IQ4NL[(byte >> 4) as usize] as f32) * x[x_base + 16 + j];
5012            }
5013            x_base += 32;
5014        }
5015    }
5016    acc
5017}
5018
5019/// Elements per MXFP4 scale group (real, confirmed both from ggml's
5020/// `QK_MXFP4` and directly from a real Kimi K3 shard's own tensor shapes:
5021/// `*.weight_scale` is `in_dim/32` bytes, `*.weight_packed` is `in_dim/2`
5022/// bytes).
5023pub const MXFP4_GROUP_SIZE: usize = 32;
5024
5025/// Real (non-doubled) E2M1 4-bit float codebook: sign + 2 exponent bits +
5026/// 1 mantissa bit, per the OCP Microscaling Formats v1.0 spec. Verified
5027/// against real `ggml-common.h`'s `kvalues_mxfp4` table, which stores
5028/// these same 16 values pre-doubled (paired with a scale halved by
5029/// `ggml_e8m0_to_fp32_half`) purely so ggml's table can stay `int8_t`;
5030/// the two conventions multiply out identically. Ferrox uses the real,
5031/// undoubled values directly against the real (unhalved) E8M0 scale below
5032/// instead, since there's no int8-table constraint here.
5033const KVALUES_MXFP4: [f32; 16] = [
5034    0.0, 0.5, 1.0, 1.5, 2.0, 3.0, 4.0, 6.0, -0.0, -0.5, -1.0, -1.5, -2.0, -3.0, -4.0, -6.0,
5035];
5036
5037/// OCP MX E8M0 scale byte -> `2^(e-127)` (bias 127, same bias convention
5038/// as an IEEE754 f32 exponent field). Implemented by placing `e` directly
5039/// into an f32's exponent bits (mantissa zero) -- exact, not an
5040/// approximation -- exactly mirroring real `ggml_e8m0_to_fp32`. `e = 0`
5041/// is special-cased (the direct bit-shift would just produce `0.0`, not
5042/// the intended `2^-127`) using the same subnormal bit pattern the real
5043/// implementation uses. `e = 255` is reserved for NaN by the OCP spec and
5044/// is not specially handled, matching that same real implementation's own
5045/// documented limitation ("does not handle NaN").
5046fn e8m0_scale(e: u8) -> f32 {
5047    if e == 0 {
5048        f32::from_bits(0x0040_0000)
5049    } else {
5050        f32::from_bits((e as u32) << 23)
5051    }
5052}
5053
5054/// Dequantizes one row of Kimi K3's MXFP4-packed expert weights. Unlike
5055/// every other kernel in this module, MXFP4 here is NOT a single
5056/// interleaved byte stream -- Kimi K3's real safetensors checkpoint
5057/// stores the packed 4-bit codes and the per-group E8M0 scales as two
5058/// separate tensors (`*.weight_packed`, `*.weight_scale`; confirmed
5059/// directly against a real shard header's tensor shapes, not ggml's own
5060/// combined-block GGUF convention), so this takes both buffers directly
5061/// rather than one combined block stream. `packed` is `in_dim/2` bytes
5062/// (2 nibble-packed E2M1 codes per byte, low-nibble-first-half /
5063/// high-nibble-second-half within each 32-element group -- same
5064/// convention as this module's other nibble-packed formats); `scales` is
5065/// `in_dim/MXFP4_GROUP_SIZE` bytes (one E8M0 scale byte per group).
5066pub fn dequant_mxfp4_row(packed: &[u8], scales: &[u8]) -> Result<Vec<f32>, QuantError> {
5067    let expected_packed_len = scales.len() * (MXFP4_GROUP_SIZE / 2);
5068    if packed.len() != expected_packed_len {
5069        return Err(QuantError::Mxfp4RowMismatch(
5070            packed.len(),
5071            expected_packed_len,
5072        ));
5073    }
5074    let mut out = Vec::with_capacity(scales.len() * MXFP4_GROUP_SIZE);
5075    for (g, &e) in scales.iter().enumerate() {
5076        let d = e8m0_scale(e);
5077        let group = &packed[g * (MXFP4_GROUP_SIZE / 2)..(g + 1) * (MXFP4_GROUP_SIZE / 2)];
5078        let mut lo = [0f32; MXFP4_GROUP_SIZE / 2];
5079        let mut hi = [0f32; MXFP4_GROUP_SIZE / 2];
5080        for (j, &byte) in group.iter().enumerate() {
5081            lo[j] = d * KVALUES_MXFP4[(byte & 0xf) as usize];
5082            hi[j] = d * KVALUES_MXFP4[(byte >> 4) as usize];
5083        }
5084        out.extend_from_slice(&lo);
5085        out.extend_from_slice(&hi);
5086    }
5087    Ok(out)
5088}
5089
5090/// Fused MXFP4 dequant+dot, same math as `dequant_mxfp4_row`. Dispatches
5091/// to AVX2+FMA or NEON when available (see `simd_x86::dot_mxfp4_row_f32_avx2`/
5092/// `simd_aarch64::dot_mxfp4_row_f32_neon`), same mechanism as
5093/// `dot_q4_0_f32` -- this is the hot path for every routed expert's FFN
5094/// in a real Kimi K3 forward pass, so unlike Q4_0/Q8_0's optional
5095/// legacy-format status, keeping this scalar-only directly costs real
5096/// inference speed.
5097pub fn dot_mxfp4_row_f32(packed: &[u8], scales: &[u8], x: &[f32]) -> f32 {
5098    #[cfg(target_arch = "x86_64")]
5099    {
5100        if is_x86_feature_detected!("avx2") && is_x86_feature_detected!("fma") {
5101            return unsafe { simd_x86::dot_mxfp4_row_f32_avx2(packed, scales, x) };
5102        }
5103    }
5104    #[cfg(target_arch = "aarch64")]
5105    {
5106        if std::arch::is_aarch64_feature_detected!("neon") {
5107            return unsafe { simd_aarch64::dot_mxfp4_row_f32_neon(packed, scales, x) };
5108        }
5109    }
5110    dot_mxfp4_row_f32_scalar(packed, scales, x)
5111}
5112
5113pub fn dot_mxfp4_row_f32_scalar(packed: &[u8], scales: &[u8], x: &[f32]) -> f32 {
5114    debug_assert_eq!(packed.len(), scales.len() * (MXFP4_GROUP_SIZE / 2));
5115    let mut acc = 0f32;
5116    let mut x_base = 0usize;
5117    for (g, &e) in scales.iter().enumerate() {
5118        let d = e8m0_scale(e);
5119        let group = &packed[g * (MXFP4_GROUP_SIZE / 2)..(g + 1) * (MXFP4_GROUP_SIZE / 2)];
5120        for (j, &byte) in group.iter().enumerate() {
5121            acc += (d * KVALUES_MXFP4[(byte & 0xf) as usize]) * x[x_base + j];
5122            acc += (d * KVALUES_MXFP4[(byte >> 4) as usize]) * x[x_base + MXFP4_GROUP_SIZE / 2 + j];
5123        }
5124        x_base += MXFP4_GROUP_SIZE;
5125    }
5126    acc
5127}
5128
5129// ---------------------------------------------------------------------
5130// IQ1_S / IQ1_M / IQ2_XXS / IQ2_XS / IQ2_S / IQ3_XXS / IQ3_S: the
5131// codebook-grid low-bit formats used throughout published "Dynamic"
5132// low-bit GGUFs of large MoE models.
5133// Unlike every format above, an element's magnitude comes from a shared
5134// grid table (`iq_tables`) indexed by packed code bits, with signs
5135// applied from a shared 7-bit sign-pattern table (the `_XXS`/`IQ2_XS`
5136// tier) or from literal sign bytes (the `_S` tier) -- not from an
5137// arithmetic transform of the stored bits. Layouts and semantics
5138// written against ggml's published dequant reference
5139// (`dequantize_row_iq1_s`/`_iq1_m`/`_iq2_xxs`/`_iq2_xs`/`_iq2_s`/
5140// `_iq3_xxs`/`_iq3_s` in `ggml/src/ggml-quants.c`); cross-validated
5141// against the real compiled ggml implementation -- for the `_XXS` tier
5142// via an independent Python reference checked against
5143// `ggml_get_type_traits(...)->to_float`, and for IQ2_XS/IQ2_S/IQ3_S/
5144// IQ1_M by linking ggml-quants.c directly and asserting bit-exact
5145// equality with its output (see this module's tests).
5146//
5147// A wrong grid index or a wrong sign/scale unpack in these formats does
5148// not produce obviously broken numbers -- it produces plausible ones
5149// from the same codebook. So every one of them is pinned to ggml's own
5150// bytes rather than to a self-consistent re-derivation, and the pinned
5151// blocks deliberately include the all-ones pattern (maximum grid index,
5152// every sign bit, maximum scale nibbles) and the all-zeros pattern.
5153// ---------------------------------------------------------------------
5154
5155/// IQ1_S: d(f16) + 32 low-index bytes + 8 u16 (3 high index bits + 3
5156/// scale bits + sign-of-delta per 32-element group). 1.5625 bpw.
5157pub const IQ1_S_BLOCK_BYTES: usize = 50;
5158pub const IQ1_S_BLOCK_ELEMS: usize = 256;
5159/// IQ1_M: 32 low-index bytes + 16 qh bytes (3 high index bits + a
5160/// sign-of-delta bit per 8-element group) + 8 scale bytes. 1.75 bpw.
5161/// The only IQ format with no f16 scale field -- see `for_each_iq1_m`.
5162pub const IQ1_M_BLOCK_BYTES: usize = 56;
5163pub const IQ1_M_BLOCK_ELEMS: usize = 256;
5164/// IQ2_XXS: d(f16) + 32 u16 codes (grid indices + packed scale/signs).
5165/// 2.0625 bpw.
5166pub const IQ2_XXS_BLOCK_BYTES: usize = 66;
5167pub const IQ2_XXS_BLOCK_ELEMS: usize = 256;
5168/// IQ2_XS: d(f16) + 32 u16 codes (9-bit grid index + 7-bit sign index)
5169/// + 8 scale bytes (two 4-bit scales per 32-element group). 2.3125 bpw.
5170pub const IQ2_XS_BLOCK_BYTES: usize = 74;
5171pub const IQ2_XS_BLOCK_ELEMS: usize = 256;
5172/// IQ2_S: d(f16) + 32 low-index bytes + 32 literal sign bytes + 8 qh
5173/// bytes (2 high index bits per group of 8) + 8 scale bytes. 2.5625 bpw.
5174pub const IQ2_S_BLOCK_BYTES: usize = 82;
5175pub const IQ2_S_BLOCK_ELEMS: usize = 256;
5176/// IQ3_XXS: d(f16) + 64 grid-index bytes + 8 u32 scale/sign words.
5177/// 3.0625 bpw.
5178pub const IQ3_XXS_BLOCK_BYTES: usize = 98;
5179pub const IQ3_XXS_BLOCK_ELEMS: usize = 256;
5180/// IQ3_S: d(f16) + 64 low-index bytes + 8 qh bytes (one 9th index bit
5181/// per grid code) + 32 literal sign bytes + 4 scale bytes (two 4-bit
5182/// scales per pair of 32-element groups). 3.4375 bpw.
5183pub const IQ3_S_BLOCK_BYTES: usize = 110;
5184pub const IQ3_S_BLOCK_ELEMS: usize = 256;
5185
5186/// ggml's IQ1S_DELTA: the constant additive shift applied to every
5187/// IQ1_S grid value, signed per 32-element group. IQ1_M's IQ1M_DELTA is
5188/// the same 0.125 in ggml-common.h, applied per 8-element group; kept as
5189/// one constant here because the two are defined equal upstream and a
5190/// second name would only invite them to drift apart in this file.
5191const IQ1S_DELTA: f32 = 0.125;
5192
5193/// `+1.0` when the matching bit in an IQ sign byte is clear, `-1.0` when
5194/// it is set. Every IQ2/IQ3 format signs its grid magnitudes this way;
5195/// only the provenance of `signs` differs (a `KSIGNS_IQ2XS` lookup for
5196/// the `_XXS`/`IQ2_XS` tier, a literal stored byte for the `_S` tier).
5197#[inline]
5198fn iq_sign(signs: u8, j: usize) -> f32 {
5199    if signs & iq_tables::KMASK_IQ2XS[j] != 0 {
5200        -1.0
5201    } else {
5202        1.0
5203    }
5204}
5205
5206#[inline]
5207fn read_f16(bytes: &[u8]) -> f32 {
5208    f16::from_le_bytes([bytes[0], bytes[1]]).to_f32()
5209}
5210
5211/// Shared IQ1_S per-block walk: calls `emit(elem_index, value)` for all
5212/// 256 elements, so dequant and fused-dot stay one algorithm.
5213#[inline]
5214fn for_each_iq1_s(block: &[u8], mut emit: impl FnMut(usize, f32)) {
5215    let d = read_f16(block);
5216    let qs = &block[2..34];
5217    let qh = &block[34..50];
5218    let mut idx = 0usize;
5219    for ib in 0..8 {
5220        let h = u16::from_le_bytes([qh[2 * ib], qh[2 * ib + 1]]);
5221        let dl = d * (2.0 * ((h >> 12) & 7) as f32 + 1.0);
5222        let delta = if h & 0x8000 != 0 {
5223            -IQ1S_DELTA
5224        } else {
5225            IQ1S_DELTA
5226        };
5227        for l in 0..4 {
5228            let grid_index = qs[4 * ib + l] as usize | ((((h >> (3 * l)) & 7) as usize) << 8);
5229            let row = iq_tables::IQ1S_GRID[grid_index];
5230            for j in 0..8 {
5231                let v = ((row >> (8 * j)) & 0xFF) as u8 as i8;
5232                emit(idx, dl * (v as f32 + delta));
5233                idx += 1;
5234            }
5235        }
5236    }
5237}
5238
5239/// Shared IQ2_XXS per-block walk (same emit contract as IQ1_S above).
5240#[inline]
5241fn for_each_iq2_xxs(block: &[u8], mut emit: impl FnMut(usize, f32)) {
5242    let d = read_f16(block);
5243    let qs: Vec<u16> = block[2..66]
5244        .as_chunks::<2>()
5245        .0
5246        .iter()
5247        .map(|c| u16::from_le_bytes([c[0], c[1]]))
5248        .collect();
5249    let mut idx = 0usize;
5250    for ib32 in 0..8 {
5251        let g = &qs[4 * ib32..4 * ib32 + 4];
5252        let aux32_1 = g[2] as u32 | ((g[3] as u32) << 16);
5253        let db = d * (0.5 + (aux32_1 >> 28) as f32) * 0.25;
5254        let aux8 = [
5255            (g[0] & 0xFF) as usize,
5256            (g[0] >> 8) as usize,
5257            (g[1] & 0xFF) as usize,
5258            (g[1] >> 8) as usize,
5259        ];
5260        for (l, &code) in aux8.iter().enumerate() {
5261            let row = iq_tables::IQ2XXS_GRID[code];
5262            let signs = iq_tables::KSIGNS_IQ2XS[((aux32_1 >> (7 * l)) & 127) as usize];
5263            for j in 0..8 {
5264                let mag = ((row >> (8 * j)) & 0xFF) as f32;
5265                emit(idx, db * mag * iq_sign(signs, j));
5266                idx += 1;
5267            }
5268        }
5269    }
5270}
5271
5272/// Shared IQ3_XXS per-block walk (same emit contract as IQ1_S above).
5273#[inline]
5274fn for_each_iq3_xxs(block: &[u8], mut emit: impl FnMut(usize, f32)) {
5275    let d = read_f16(block);
5276    let qs = &block[2..66];
5277    let sas = &block[66..98];
5278    let mut idx = 0usize;
5279    for ib32 in 0..8 {
5280        let aux32 = u32::from_le_bytes([
5281            sas[4 * ib32],
5282            sas[4 * ib32 + 1],
5283            sas[4 * ib32 + 2],
5284            sas[4 * ib32 + 3],
5285        ]);
5286        let db = d * (0.5 + (aux32 >> 28) as f32) * 0.5;
5287        for l in 0..4 {
5288            let signs = iq_tables::KSIGNS_IQ2XS[((aux32 >> (7 * l)) & 127) as usize];
5289            let g1 = iq_tables::IQ3XXS_GRID[qs[8 * ib32 + 2 * l] as usize];
5290            let g2 = iq_tables::IQ3XXS_GRID[qs[8 * ib32 + 2 * l + 1] as usize];
5291            for j in 0..4 {
5292                emit(
5293                    idx + j,
5294                    db * ((g1 >> (8 * j)) & 0xFF) as f32 * iq_sign(signs, j),
5295                );
5296            }
5297            for j in 0..4 {
5298                emit(
5299                    idx + 4 + j,
5300                    db * ((g2 >> (8 * j)) & 0xFF) as f32 * iq_sign(signs, j + 4),
5301                );
5302            }
5303            idx += 8;
5304        }
5305    }
5306}
5307
5308/// Shared IQ2_XS per-block walk (same emit contract as IQ1_S above).
5309///
5310/// IQ2_XS is IQ2_XXS with the scales pulled out of the code words: each
5311/// u16 code now spends all 16 bits on payload (9-bit grid index + 7-bit
5312/// `KSIGNS_IQ2XS` index), and the per-group scales move into their own
5313/// 8 trailing bytes, two 4-bit scales per 32-element group. The `l/2`
5314/// split below is ggml's: within a group of 32, codes 0-1 take the low
5315/// nibble's scale and codes 2-3 the high nibble's.
5316#[inline]
5317fn for_each_iq2_xs(block: &[u8], mut emit: impl FnMut(usize, f32)) {
5318    let d = read_f16(block);
5319    let qs = &block[2..66];
5320    let scales = &block[66..74];
5321    let mut idx = 0usize;
5322    for ib32 in 0..8 {
5323        let db = [
5324            d * (0.5 + (scales[ib32] & 0xF) as f32) * 0.25,
5325            d * (0.5 + (scales[ib32] >> 4) as f32) * 0.25,
5326        ];
5327        for l in 0..4 {
5328            let code = u16::from_le_bytes([qs[8 * ib32 + 2 * l], qs[8 * ib32 + 2 * l + 1]]);
5329            let row = iq_tables::IQ2XS_GRID[(code & 511) as usize];
5330            let signs = iq_tables::KSIGNS_IQ2XS[(code >> 9) as usize];
5331            for j in 0..8 {
5332                let mag = ((row >> (8 * j)) & 0xFF) as f32;
5333                emit(idx, db[l / 2] * mag * iq_sign(signs, j));
5334                idx += 1;
5335            }
5336        }
5337    }
5338}
5339
5340/// Shared IQ2_S per-block walk (same emit contract as IQ1_S above).
5341///
5342/// IQ2_S spends its extra quarter-bit on *literal* signs: instead of a
5343/// 7-bit index into `KSIGNS_IQ2XS` (which can only express the 128 sign
5344/// patterns of even parity), each group of 8 elements gets a full sign
5345/// byte. That frees the code word of sign bits entirely, so the grid
5346/// index widens to 10 bits -- 8 from `qs` plus 2 pulled out of the
5347/// group's `qh` byte, a different 2-bit field per code (`l` selects
5348/// which). Note ggml declares `qs` as one 64-byte array and then aliases
5349/// its second half as the sign bytes; the two halves are named
5350/// separately here because they are unrelated payloads.
5351#[inline]
5352fn for_each_iq2_s(block: &[u8], mut emit: impl FnMut(usize, f32)) {
5353    let d = read_f16(block);
5354    let qs = &block[2..34];
5355    let sign_bytes = &block[34..66];
5356    let qh = &block[66..74];
5357    let scales = &block[74..82];
5358    let mut idx = 0usize;
5359    for ib32 in 0..8 {
5360        let db = [
5361            d * (0.5 + (scales[ib32] & 0xF) as f32) * 0.25,
5362            d * (0.5 + (scales[ib32] >> 4) as f32) * 0.25,
5363        ];
5364        for l in 0..4 {
5365            let hi = ((qh[ib32] as usize) << (8 - 2 * l)) & 0x300;
5366            let row = iq_tables::IQ2S_GRID[qs[4 * ib32 + l] as usize | hi];
5367            let signs = sign_bytes[4 * ib32 + l];
5368            for j in 0..8 {
5369                let mag = ((row >> (8 * j)) & 0xFF) as f32;
5370                emit(idx, db[l / 2] * mag * iq_sign(signs, j));
5371                idx += 1;
5372            }
5373        }
5374    }
5375}
5376
5377/// Shared IQ3_S per-block walk (same emit contract as IQ1_S above).
5378///
5379/// IQ3_S is to IQ3_XXS what IQ2_S is to IQ2_XXS: literal sign bytes
5380/// instead of `KSIGNS_IQ2XS` indices, and the freed bits spent widening
5381/// the grid index to 9 bits (8 from `qs`, the 9th from the group's `qh`
5382/// byte, one bit per code). Scales are the odd part: there are only 4
5383/// scale bytes for 8 groups of 32, so one byte's two nibbles cover
5384/// *two consecutive groups* -- low nibble for the even group, high
5385/// nibble for the odd one -- and the scale is `1 + 2*nibble` (an odd
5386/// integer multiplier), not the `(0.5 + nibble) * 0.25` of the IQ2 tier.
5387///
5388/// ggml writes this as a loop stepping `ib32` by 2 with pointer bumps
5389/// inside; unrolled here to a plain per-group loop with explicit
5390/// offsets, which is the same traversal with the aliasing spelled out.
5391#[inline]
5392fn for_each_iq3_s(block: &[u8], mut emit: impl FnMut(usize, f32)) {
5393    let d = read_f16(block);
5394    let qs = &block[2..66];
5395    let qh = &block[66..74];
5396    let sign_bytes = &block[74..106];
5397    let scales = &block[106..110];
5398    let mut idx = 0usize;
5399    for ib32 in 0..8 {
5400        let nibble = if ib32 % 2 == 0 {
5401            scales[ib32 / 2] & 0xF
5402        } else {
5403            scales[ib32 / 2] >> 4
5404        };
5405        let db = d * (1.0 + 2.0 * nibble as f32);
5406        for l in 0..4 {
5407            // The 9th index bit for code `2l` is qh bit `2l`, and for
5408            // code `2l+1` it is qh bit `2l+1` -- ggml expresses both as
5409            // a left shift landing that bit on 256.
5410            let h = qh[ib32] as usize;
5411            let i1 = qs[8 * ib32 + 2 * l] as usize | ((h << (8 - 2 * l)) & 256);
5412            let i2 = qs[8 * ib32 + 2 * l + 1] as usize | ((h << (7 - 2 * l)) & 256);
5413            let g1 = iq_tables::IQ3S_GRID[i1];
5414            let g2 = iq_tables::IQ3S_GRID[i2];
5415            let signs = sign_bytes[4 * ib32 + l];
5416            for j in 0..4 {
5417                emit(
5418                    idx + j,
5419                    db * ((g1 >> (8 * j)) & 0xFF) as f32 * iq_sign(signs, j),
5420                );
5421            }
5422            for j in 0..4 {
5423                emit(
5424                    idx + 4 + j,
5425                    db * ((g2 >> (8 * j)) & 0xFF) as f32 * iq_sign(signs, j + 4),
5426                );
5427            }
5428            idx += 8;
5429        }
5430    }
5431}
5432
5433/// Shared IQ1_M per-block walk (same emit contract as IQ1_S above).
5434///
5435/// IQ1_M reuses IQ1_S's 2048-entry signed grid and its `+/-delta` shift,
5436/// but restructures everything around it, and it is the one IQ format
5437/// with **no f16 scale field**: the block's 16 scale bits are scattered
5438/// as the top nibble of each of the four 16-bit scale words, and are
5439/// reassembled here into an f16 bit pattern. The remaining 12 bits of
5440/// each word carry four 3-bit sub-scales (two 32-element groups per
5441/// word, two sub-scales per group covering 16 elements each), so the
5442/// scale resolution is twice IQ1_S's.
5443///
5444/// The delta sign is also finer-grained than IQ1_S's: one bit per 8
5445/// elements (`qh` bits 3 and 7) rather than one per 32.
5446#[inline]
5447fn for_each_iq1_m(block: &[u8], mut emit: impl FnMut(usize, f32)) {
5448    let qs = &block[0..32];
5449    let qh = &block[32..48];
5450    let scales = &block[48..56];
5451    let sc: [u16; 4] =
5452        std::array::from_fn(|k| u16::from_le_bytes([scales[2 * k], scales[2 * k + 1]]));
5453    // Top nibble of sc[0]..sc[3] -> f16 bits 0-3, 4-7, 8-11, 12-15.
5454    let d = f16::from_bits(
5455        (sc[0] >> 12) | ((sc[1] >> 8) & 0x00F0) | ((sc[2] >> 4) & 0x0F00) | (sc[3] & 0xF000),
5456    )
5457    .to_f32();
5458    let mut idx = 0usize;
5459    for ib in 0..8 {
5460        let shift = 6 * (ib % 2);
5461        let dl = [
5462            d * (2.0 * ((sc[ib / 2] >> shift) & 7) as f32 + 1.0),
5463            d * (2.0 * ((sc[ib / 2] >> (shift + 3)) & 7) as f32 + 1.0),
5464        ];
5465        let (h0, h1) = (qh[2 * ib] as usize, qh[2 * ib + 1] as usize);
5466        // Grid index high bits: qh nibble bits 0-2 of each half-byte.
5467        // Bits 3 and 7 of each qh byte are the delta signs instead.
5468        let grid_idx = [
5469            qs[4 * ib] as usize | ((h0 << 8) & 0x700),
5470            qs[4 * ib + 1] as usize | ((h0 << 4) & 0x700),
5471            qs[4 * ib + 2] as usize | ((h1 << 8) & 0x700),
5472            qs[4 * ib + 3] as usize | ((h1 << 4) & 0x700),
5473        ];
5474        let delta = [
5475            if h0 & 0x08 != 0 {
5476                -IQ1S_DELTA
5477            } else {
5478                IQ1S_DELTA
5479            },
5480            if h0 & 0x80 != 0 {
5481                -IQ1S_DELTA
5482            } else {
5483                IQ1S_DELTA
5484            },
5485            if h1 & 0x08 != 0 {
5486                -IQ1S_DELTA
5487            } else {
5488                IQ1S_DELTA
5489            },
5490            if h1 & 0x80 != 0 {
5491                -IQ1S_DELTA
5492            } else {
5493                IQ1S_DELTA
5494            },
5495        ];
5496        for l in 0..4 {
5497            let row = iq_tables::IQ1S_GRID[grid_idx[l]];
5498            for j in 0..8 {
5499                let v = ((row >> (8 * j)) & 0xFF) as u8 as i8;
5500                emit(idx, dl[l / 2] * (v as f32 + delta[l]));
5501                idx += 1;
5502            }
5503        }
5504    }
5505}
5506
5507macro_rules! iq_dequant_and_dot {
5508    ($dequant:ident, $dot_scalar:ident, $walk:ident, $bytes:ident, $elems:ident) => {
5509        pub fn $dequant(src: &[u8]) -> Result<Vec<f32>, QuantError> {
5510            if !src.len().is_multiple_of($bytes) {
5511                return Err(QuantError::Misaligned(src.len(), $bytes));
5512            }
5513            let n_blocks = src.len() / $bytes;
5514            let mut out = vec![0f32; n_blocks * $elems];
5515            for (b, block) in src.chunks_exact($bytes).enumerate() {
5516                let base = b * $elems;
5517                $walk(block, |i, v| out[base + i] = v);
5518            }
5519            Ok(out)
5520        }
5521
5522        pub fn $dot_scalar(row_bytes: &[u8], x: &[f32]) -> f32 {
5523            debug_assert_eq!(row_bytes.len() % $bytes, 0);
5524            let mut acc = 0f32;
5525            let mut x_base = 0usize;
5526            for block in row_bytes.chunks_exact($bytes) {
5527                $walk(block, |i, v| acc += v * x[x_base + i]);
5528                x_base += $elems;
5529            }
5530            acc
5531        }
5532    };
5533}
5534
5535/// Hand-written dispatch for the IQ codebook formats: AVX2+FMA when the
5536/// host supports it (verified directly against the scalar reference on
5537/// real x86_64 hardware -- see this module's tests), scalar otherwise.
5538/// No NEON kernels yet for these formats (no aarch64 host was available
5539/// to verify one on; the scalar path serves ARM).
5540macro_rules! iq_dispatch {
5541    ($dot:ident, $dot_scalar:ident, $avx2:ident) => {
5542        pub fn $dot(row_bytes: &[u8], x: &[f32]) -> f32 {
5543            #[cfg(target_arch = "x86_64")]
5544            {
5545                if is_x86_feature_detected!("avx2") && is_x86_feature_detected!("fma") {
5546                    return unsafe { simd_x86::$avx2(row_bytes, x) };
5547                }
5548            }
5549            $dot_scalar(row_bytes, x)
5550        }
5551    };
5552}
5553
5554iq_dispatch!(dot_iq1_s_f32, dot_iq1_s_f32_scalar, dot_iq1_s_f32_avx2);
5555iq_dispatch!(
5556    dot_iq2_xxs_f32,
5557    dot_iq2_xxs_f32_scalar,
5558    dot_iq2_xxs_f32_avx2
5559);
5560iq_dispatch!(
5561    dot_iq3_xxs_f32,
5562    dot_iq3_xxs_f32_scalar,
5563    dot_iq3_xxs_f32_avx2
5564);
5565
5566/// IQ2_XS / IQ2_S / IQ3_S / IQ1_M dispatch: scalar only. These landed
5567/// for *coverage* -- before them, tags 17/21/22/29 fell to
5568/// `GgmlType::Other` and the tensor could not be decoded at all, which
5569/// silently ruled out 5 of the 16 published Unsloth `UD-*` variants.
5570/// They deliberately match the state of their older siblings' NEON/GPU
5571/// story (none), rather than growing a vectorized path that no golden
5572/// vector would then be able to distinguish from the scalar one.
5573pub fn dot_iq2_xs_f32(row_bytes: &[u8], x: &[f32]) -> f32 {
5574    dot_iq2_xs_f32_scalar(row_bytes, x)
5575}
5576
5577pub fn dot_iq2_s_f32(row_bytes: &[u8], x: &[f32]) -> f32 {
5578    dot_iq2_s_f32_scalar(row_bytes, x)
5579}
5580
5581pub fn dot_iq3_s_f32(row_bytes: &[u8], x: &[f32]) -> f32 {
5582    dot_iq3_s_f32_scalar(row_bytes, x)
5583}
5584
5585pub fn dot_iq1_m_f32(row_bytes: &[u8], x: &[f32]) -> f32 {
5586    dot_iq1_m_f32_scalar(row_bytes, x)
5587}
5588
5589/// GGUF block-MXFP4 dispatch: scalar only so far (the two-buffer
5590/// safetensors MXFP4 form has AVX2/NEON kernels above; this block form
5591/// hasn't needed one yet).
5592pub fn dot_mxfp4_gguf_f32(row_bytes: &[u8], x: &[f32]) -> f32 {
5593    dot_mxfp4_gguf_f32_scalar(row_bytes, x)
5594}
5595
5596iq_dequant_and_dot!(
5597    dequant_iq1_s,
5598    dot_iq1_s_f32_scalar,
5599    for_each_iq1_s,
5600    IQ1_S_BLOCK_BYTES,
5601    IQ1_S_BLOCK_ELEMS
5602);
5603iq_dequant_and_dot!(
5604    dequant_iq2_xxs,
5605    dot_iq2_xxs_f32_scalar,
5606    for_each_iq2_xxs,
5607    IQ2_XXS_BLOCK_BYTES,
5608    IQ2_XXS_BLOCK_ELEMS
5609);
5610iq_dequant_and_dot!(
5611    dequant_iq3_xxs,
5612    dot_iq3_xxs_f32_scalar,
5613    for_each_iq3_xxs,
5614    IQ3_XXS_BLOCK_BYTES,
5615    IQ3_XXS_BLOCK_ELEMS
5616);
5617iq_dequant_and_dot!(
5618    dequant_iq2_xs,
5619    dot_iq2_xs_f32_scalar,
5620    for_each_iq2_xs,
5621    IQ2_XS_BLOCK_BYTES,
5622    IQ2_XS_BLOCK_ELEMS
5623);
5624iq_dequant_and_dot!(
5625    dequant_iq2_s,
5626    dot_iq2_s_f32_scalar,
5627    for_each_iq2_s,
5628    IQ2_S_BLOCK_BYTES,
5629    IQ2_S_BLOCK_ELEMS
5630);
5631iq_dequant_and_dot!(
5632    dequant_iq3_s,
5633    dot_iq3_s_f32_scalar,
5634    for_each_iq3_s,
5635    IQ3_S_BLOCK_BYTES,
5636    IQ3_S_BLOCK_ELEMS
5637);
5638iq_dequant_and_dot!(
5639    dequant_iq1_m,
5640    dot_iq1_m_f32_scalar,
5641    for_each_iq1_m,
5642    IQ1_M_BLOCK_BYTES,
5643    IQ1_M_BLOCK_ELEMS
5644);
5645
5646/// GGUF block-MXFP4 (ggml type tag 39): one 17-byte block = 1 E8M0
5647/// scale byte + 16 nibble bytes covering 32 elements, low nibble ->
5648/// element `j`, high nibble -> element `j+16`. Same E2M1 codebook and
5649/// E8M0 scale math as the Kimi safetensors two-buffer MXFP4 path above
5650/// (`dot_mxfp4_row_f32`) -- ggml expresses it as doubled-integer
5651/// kvalues times a half scale (`2^(e-128)`), this module as true E2M1
5652/// values times the full `2^(e-127)` scale; the products are identical
5653/// across the whole E8M0 range including the `e < 2` denormal
5654/// patterns. Only the byte layout differs: interleaved 17-byte blocks
5655/// in one stream here, two separate packed/scale tensors there.
5656pub const MXFP4_GGUF_BLOCK_BYTES: usize = 17;
5657pub const MXFP4_GGUF_BLOCK_ELEMS: usize = 32;
5658
5659/// Shared GGUF-block-MXFP4 per-block walk (same emit contract as the
5660/// IQ walks above).
5661#[inline]
5662fn for_each_mxfp4_gguf(block: &[u8], mut emit: impl FnMut(usize, f32)) {
5663    let d = e8m0_scale(block[0]);
5664    for (j, &byte) in block[1..17].iter().enumerate() {
5665        emit(j, d * KVALUES_MXFP4[(byte & 0x0F) as usize]);
5666        emit(j + 16, d * KVALUES_MXFP4[(byte >> 4) as usize]);
5667    }
5668}
5669
5670iq_dequant_and_dot!(
5671    dequant_mxfp4_gguf,
5672    dot_mxfp4_gguf_f32_scalar,
5673    for_each_mxfp4_gguf,
5674    MXFP4_GGUF_BLOCK_BYTES,
5675    MXFP4_GGUF_BLOCK_ELEMS
5676);
5677
5678#[cfg(test)]
5679mod tests {
5680    use super::*;
5681
5682    #[test]
5683    fn turbo4_kv_blocks_roundtrip_reasonable() {
5684        let x: Vec<f32> = (0..64).map(|i| (i as f32 * 0.17).sin() * 2.0).collect();
5685        let packed = pack_turbo4_kv_blocks(&x);
5686        assert_eq!(packed.len(), 2 * TURBO4_KV_BLOCK_BYTES);
5687        let y = unpack_turbo4_kv_blocks(&packed).unwrap();
5688        assert_eq!(y.len(), 64);
5689        let mut err = 0.0f32;
5690        for (a, b) in x.iter().zip(y.iter()) {
5691            err += (a - b).abs();
5692        }
5693        err /= x.len() as f32;
5694        assert!(err < 0.2, "mean abs err {err}");
5695    }
5696
5697    #[test]
5698    fn q8_0_roundtrip_is_within_quantization_error() {
5699        let original: Vec<f32> = (0..32).map(|i| (i as f32 - 16.0) * 0.37).collect();
5700        let packed = quantize_q8_0(&original);
5701        assert_eq!(packed.len(), Q8_0_BLOCK_BYTES);
5702        let restored = dequant_q8_0(&packed).unwrap();
5703        assert_eq!(restored.len(), 32);
5704        for (a, b) in original.iter().zip(restored.iter()) {
5705            assert!((a - b).abs() < 0.1, "a={a} b={b}");
5706        }
5707    }
5708
5709    #[test]
5710    fn quantize_activations_q8_reconstructs_within_quant_error() {
5711        let x: Vec<f32> = (0..64)
5712            .map(|i| ((i as f32) * 0.13 - 4.0).sin() * 3.0)
5713            .collect();
5714        let act = quantize_activations_q8(&x);
5715        assert_eq!(act.n_blocks(), 2);
5716        assert_eq!(act.q.len(), 64);
5717        for (b, chunk) in x.as_chunks::<32>().0.iter().enumerate() {
5718            let amax = chunk.iter().fold(0f32, |m, &v| m.max(v.abs()));
5719            let tol = amax / 127.0 + 1e-6;
5720            for (i, &v) in chunk.iter().enumerate() {
5721                let recon = act.q[b * 32 + i] as f32 * act.d[b];
5722                assert!((recon - v).abs() <= tol, "b={b} i={i} v={v} recon={recon}");
5723            }
5724        }
5725    }
5726
5727    #[test]
5728    fn quantize_activations_q8_handles_all_zero_block() {
5729        let act = quantize_activations_q8(&[0f32; 32]);
5730        assert_eq!(act.d[0], 0.0);
5731        assert!(act.q.iter().all(|&q| q == 0));
5732    }
5733
5734    #[test]
5735    fn quantize_activations_q8_parallel_matches_serial() {
5736        let x: Vec<f32> = (0..512)
5737            .map(|i| ((i as f32) * 0.07 - 8.0).sin() * 2.5)
5738            .collect();
5739        let got = quantize_activations_q8(&x);
5740        let n_blocks = x.len() / Q8_0_BLOCK_ELEMS;
5741        let mut q = vec![0i8; n_blocks * Q8_0_BLOCK_ELEMS];
5742        let mut d = vec![0f32; n_blocks];
5743        for (b, chunk) in x.as_chunks::<Q8_0_BLOCK_ELEMS>().0.iter().enumerate() {
5744            let amax = chunk.iter().fold(0f32, |m, &v| m.max(v.abs()));
5745            let scale = amax / 127.0;
5746            let inv = if scale > 0.0 { 1.0 / scale } else { 0.0 };
5747            d[b] = scale;
5748            let base = b * Q8_0_BLOCK_ELEMS;
5749            for (i, &v) in chunk.iter().enumerate() {
5750                let qi = (v * inv).round();
5751                q[base + i] = qi.clamp(-127.0, 127.0) as i8;
5752            }
5753        }
5754        assert_eq!(got.q, q);
5755        assert_eq!(got.d, d);
5756    }
5757
5758    #[test]
5759    fn quantize_activations_q8_k_parallel_matches_serial() {
5760        let x: Vec<f32> = (0..1024)
5761            .map(|i| ((i as f32) * 0.05 - 12.0).cos() * 1.7)
5762            .collect();
5763        let got = quantize_activations_q8_k(&x);
5764        let n_blocks = x.len() / Q4_K_BLOCK_ELEMS;
5765        let mut q = vec![0i8; n_blocks * Q4_K_BLOCK_ELEMS];
5766        let mut d = vec![0f32; n_blocks];
5767        let mut bsums = vec![0i16; n_blocks * 16];
5768        for (b, chunk) in x.as_chunks::<Q4_K_BLOCK_ELEMS>().0.iter().enumerate() {
5769            let amax = chunk.iter().fold(0f32, |m, &v| m.max(v.abs()));
5770            let scale = amax / 127.0;
5771            let inv = if scale > 0.0 { 1.0 / scale } else { 0.0 };
5772            d[b] = scale;
5773            let base = b * Q4_K_BLOCK_ELEMS;
5774            for (i, &v) in chunk.iter().enumerate() {
5775                let qi = (v * inv).round();
5776                q[base + i] = qi.clamp(-127.0, 127.0) as i8;
5777            }
5778            let bsum_base = b * 16;
5779            for g in 0..16 {
5780                let mut s = 0i32;
5781                let off = base + g * 16;
5782                for i in 0..16 {
5783                    s += q[off + i] as i32;
5784                }
5785                bsums[bsum_base + g] = s as i16;
5786            }
5787        }
5788        assert_eq!(got.q, q);
5789        assert_eq!(got.d, d);
5790        assert_eq!(got.bsums, bsums);
5791    }
5792
5793    #[test]
5794    fn dot_q4_k_q8_matches_scalar_and_tracks_float_dot() {
5795        let n_blocks = 3;
5796        let cols = n_blocks * Q4_K_BLOCK_ELEMS;
5797        let x: Vec<f32> = (0..cols)
5798            .map(|i| ((i as f32) * 0.017 - 2.1).sin() * 1.8)
5799            .collect();
5800        // Build a synthetic Q4_K row via quantize then re-pack? Use dequant
5801        // round-trip: quantize floats with a simple pattern into Q4_K by
5802        // packing known nibbles (same as other K-quant tests).
5803        let mut weights = Vec::with_capacity(n_blocks * Q4_K_BLOCK_BYTES);
5804        for b in 0..n_blocks {
5805            weights.extend_from_slice(&f16::from_f32(0.05 + b as f32 * 0.01).to_le_bytes());
5806            weights.extend_from_slice(&f16::from_f32(0.01 + b as f32 * 0.002).to_le_bytes());
5807            // 12 scale bytes: simple low-6-bit pattern
5808            for i in 0..12u8 {
5809                weights.push(20 + i.wrapping_mul(3));
5810            }
5811            for i in 0..128u8 {
5812                weights.push(i.wrapping_mul(17).wrapping_add(b as u8));
5813            }
5814        }
5815        let act = quantize_activations_q8_k(&x);
5816        let dispatched = dot_q4_k_q8(&weights, &act);
5817        let scalar = dot_q4_k_q8_scalar(&weights, &act);
5818        assert_eq!(dispatched, scalar, "dispatch must match scalar");
5819        let float_dot = dot_q4_k_f32(&weights, &x);
5820        let err = (dispatched - float_dot).abs();
5821        let scale = float_dot.abs().max(1.0);
5822        assert!(
5823            err / scale < 0.05,
5824            "int-dot vs f32 relative err {err}/{scale} too large (int={dispatched} f32={float_dot})"
5825        );
5826    }
5827
5828    #[test]
5829    #[cfg(target_arch = "aarch64")]
5830    fn dot_q4_k_q8_i8mm_matches_scalar_when_available() {
5831        if !std::arch::is_aarch64_feature_detected!("i8mm") {
5832            return;
5833        }
5834        let n_blocks = 3;
5835        let cols = n_blocks * Q4_K_BLOCK_ELEMS;
5836        let x: Vec<f32> = (0..cols)
5837            .map(|i| ((i as f32) * 0.017 - 2.1).sin() * 1.8)
5838            .collect();
5839        let mut weights = Vec::with_capacity(n_blocks * Q4_K_BLOCK_BYTES);
5840        for b in 0..n_blocks {
5841            weights.extend_from_slice(&f16::from_f32(0.05 + b as f32 * 0.01).to_le_bytes());
5842            weights.extend_from_slice(&f16::from_f32(0.01 + b as f32 * 0.002).to_le_bytes());
5843            for i in 0..12u8 {
5844                weights.push(20 + i.wrapping_mul(3));
5845            }
5846            for i in 0..128u8 {
5847                weights.push(i.wrapping_mul(17).wrapping_add(b as u8));
5848            }
5849        }
5850        let act = quantize_activations_q8_k(&x);
5851        let scalar = dot_q4_k_q8_scalar(&weights, &act);
5852        let i8mm = unsafe { simd_aarch64::dot_q4_k_q8_neon_i8mm(&weights, &act) };
5853        assert_eq!(i8mm, scalar, "i8mm must match scalar");
5854        let dispatched = dot_q4_k_q8(&weights, &act);
5855        assert_eq!(
5856            dispatched, scalar,
5857            "dispatch must match scalar on i8mm host"
5858        );
5859    }
5860
5861    #[test]
5862    fn dot_q5_k_q8_matches_scalar_and_tracks_float_dot() {
5863        let x: Vec<f32> = (0..Q5_K_BLOCK_ELEMS)
5864            .map(|i| ((i as f32) * 0.013 - 1.7).sin() * 1.5)
5865            .collect();
5866        let act = quantize_activations_q8_k(&x);
5867        let dispatched = dot_q5_k_q8(&Q5_K_TEST_BLOCK, &act);
5868        let scalar = dot_q5_k_q8_scalar(&Q5_K_TEST_BLOCK, &act);
5869        assert_eq!(dispatched, scalar, "dispatch must match scalar");
5870        let float_dot = dot_q5_k_f32(&Q5_K_TEST_BLOCK, &x);
5871        let err = (dispatched - float_dot).abs();
5872        let scale = float_dot.abs().max(1.0);
5873        assert!(
5874            err / scale < 0.05,
5875            "Q5_K int-dot vs f32 relative err {err}/{scale} (int={dispatched} f32={float_dot})"
5876        );
5877    }
5878
5879    #[test]
5880    fn gemm_q5_k_q8_row_matches_per_act_dots() {
5881        let acts: Vec<_> = (0..Q5_K_GEMM_NC)
5882            .map(|j| {
5883                let x: Vec<f32> = (0..Q5_K_BLOCK_ELEMS)
5884                    .map(|i| ((i as f32) * 0.013 - 1.7 + j as f32).sin() * 1.5)
5885                    .collect();
5886                quantize_activations_q8_k(&x)
5887            })
5888            .collect();
5889        let mut out = vec![0f32; acts.len()];
5890        gemm_q5_k_q8_row(&Q5_K_TEST_BLOCK, &acts, &mut out);
5891        for (j, act) in acts.iter().enumerate() {
5892            let want = dot_q5_k_q8(&Q5_K_TEST_BLOCK, act);
5893            let err = (out[j] - want).abs();
5894            assert!(
5895                err < 1e-4,
5896                "act {j}: gemm {got} vs dot {want}",
5897                got = out[j]
5898            );
5899        }
5900    }
5901
5902    #[test]
5903    fn gemm_q6_k_q8_row_matches_per_act_dots() {
5904        let acts: Vec<_> = (0..Q6_K_GEMM_NC)
5905            .map(|j| {
5906                let x: Vec<f32> = (0..Q6_K_BLOCK_ELEMS)
5907                    .map(|i| ((i as f32) * 0.011 - 0.9 + j as f32).cos() * 1.9)
5908                    .collect();
5909                quantize_activations_q8_k(&x)
5910            })
5911            .collect();
5912        let mut out = vec![0f32; acts.len()];
5913        gemm_q6_k_q8_row(&Q6_K_TEST_BLOCK, &acts, &mut out);
5914        for (j, act) in acts.iter().enumerate() {
5915            let want = dot_q6_k_q8(&Q6_K_TEST_BLOCK, act);
5916            let err = (out[j] - want).abs();
5917            assert!(
5918                err < 1e-3,
5919                "act {j}: gemm {got} vs dot {want}",
5920                got = out[j]
5921            );
5922        }
5923    }
5924
5925    #[test]
5926    fn dot_q6_k_q8_matches_scalar_and_tracks_float_dot() {
5927        let x: Vec<f32> = (0..Q6_K_BLOCK_ELEMS)
5928            .map(|i| ((i as f32) * 0.011 - 0.9).cos() * 1.9)
5929            .collect();
5930        let act = quantize_activations_q8_k(&x);
5931        let dispatched = dot_q6_k_q8(&Q6_K_TEST_BLOCK, &act);
5932        let scalar = dot_q6_k_q8_scalar(&Q6_K_TEST_BLOCK, &act);
5933        assert_eq!(dispatched, scalar, "dispatch must match scalar");
5934        let float_dot = dot_q6_k_f32(&Q6_K_TEST_BLOCK, &x);
5935        let err = (dispatched - float_dot).abs();
5936        let scale = float_dot.abs().max(1.0);
5937        assert!(
5938            err / scale < 0.05,
5939            "Q6_K int-dot vs f32 relative err {err}/{scale} (int={dispatched} f32={float_dot})"
5940        );
5941    }
5942
5943    #[test]
5944    fn dot_q8_0_q8_dispatch_matches_scalar_and_float_dot() {
5945        // Random-ish Q8_0 weight row + activations; the integer dot must
5946        // equal its own scalar path exactly and the float dot closely.
5947        let n_blocks = 5;
5948        let cols = n_blocks * Q8_0_BLOCK_ELEMS;
5949        let x: Vec<f32> = (0..cols)
5950            .map(|i| ((i as f32) * 0.019 - 1.3).cos() * 2.7)
5951            .collect();
5952
5953        let mut weights = Vec::with_capacity(n_blocks * Q8_0_BLOCK_BYTES);
5954        for b in 0..n_blocks {
5955            weights.extend_from_slice(&f16::from_f32(0.021 + b as f32 * 0.004).to_le_bytes());
5956            for i in 0..Q8_0_BLOCK_ELEMS {
5957                weights.push(((i as i32 * 7 + b as i32 * 3) % 255 - 127) as i8 as u8);
5958            }
5959        }
5960
5961        let act = quantize_activations_q8(&x);
5962        let dispatched = dot_q8_0_q8(&weights, &act);
5963        let scalar = dot_q8_0_q8_scalar(&weights, &act);
5964        assert_eq!(
5965            dispatched.to_bits(),
5966            scalar.to_bits(),
5967            "SIMD int dot must match scalar int dot bit-for-bit"
5968        );
5969
5970        let float_dot = dot_q8_0_f32(&weights, &x);
5971        // Activation quant error is ~amax/127 per element; the aggregate
5972        // relative error stays small for this many terms.
5973        let rel = (dispatched - float_dot).abs() / float_dot.abs().max(1e-6);
5974        assert!(
5975            rel < 0.02,
5976            "int dot {dispatched} vs float {float_dot} rel={rel}"
5977        );
5978    }
5979
5980    #[test]
5981    fn dot_q4_0_q8_dispatch_matches_scalar_and_float_dot() {
5982        let n_blocks = 5;
5983        let cols = n_blocks * Q4_0_BLOCK_ELEMS;
5984        let x: Vec<f32> = (0..cols)
5985            .map(|i| ((i as f32) * 0.019 - 1.3).cos() * 2.7)
5986            .collect();
5987
5988        let mut weights = Vec::with_capacity(n_blocks * Q4_0_BLOCK_BYTES);
5989        for b in 0..n_blocks {
5990            weights.extend_from_slice(&f16::from_f32(0.021 + b as f32 * 0.004).to_le_bytes());
5991            for i in 0..16 {
5992                weights.push(((i as u32 * 13 + b as u32 * 7) % 256) as u8);
5993            }
5994        }
5995
5996        let act = quantize_activations_q8(&x);
5997        let dispatched = dot_q4_0_q8(&weights, &act);
5998        let scalar = dot_q4_0_q8_scalar(&weights, &act);
5999        assert_eq!(
6000            dispatched.to_bits(),
6001            scalar.to_bits(),
6002            "SIMD Q4_0 int dot must match scalar bit-for-bit"
6003        );
6004
6005        let float_dot = dot_q4_0_f32(&weights, &x);
6006        let rel = (dispatched - float_dot).abs() / float_dot.abs().max(1e-6);
6007        assert!(
6008            rel < 0.03,
6009            "Q4_0 int dot {dispatched} vs float {float_dot} rel={rel}"
6010        );
6011    }
6012
6013    #[test]
6014    fn q4_0_zero_nibble_maps_to_negative_bias() {
6015        // scale = 1.0, nibble 0 -> (0 - 8) * scale = -8.0
6016        let mut block = Vec::new();
6017        block.extend_from_slice(&f16::from_f32(1.0).to_le_bytes());
6018        block.extend_from_slice(&[0u8; 16]); // all nibbles zero
6019        let out = dequant_q4_0(&block).unwrap();
6020        assert_eq!(out.len(), 32);
6021        assert!(out.iter().all(|&v| v == -8.0));
6022    }
6023
6024    #[test]
6025    fn rejects_misaligned_buffers() {
6026        let bad = vec![0u8; 5];
6027        assert!(dequant_q8_0(&bad).is_err());
6028        assert!(dequant_q4_0(&bad).is_err());
6029    }
6030
6031    #[test]
6032    fn q4_1_affine_nibble_maps_to_scale_plus_min() {
6033        // d=2.0, m=5.0, nibble=1 (both halves of every byte) ->
6034        // 1*2+5 = 7.0 for every element.
6035        let mut block = Vec::new();
6036        block.extend_from_slice(&f16::from_f32(2.0).to_le_bytes());
6037        block.extend_from_slice(&f16::from_f32(5.0).to_le_bytes());
6038        block.extend_from_slice(&[0x11u8; 16]); // lo=1, hi=1
6039        let out = dequant_q4_1(&block).unwrap();
6040        assert_eq!(out.len(), 32);
6041        assert!(out.iter().all(|&v| (v - 7.0).abs() < 1e-6));
6042    }
6043
6044    #[test]
6045    fn q5_0_fifth_bit_extends_range_past_a_plain_nibble() {
6046        // d=1.0, qs nibble=0, but qh sets bit 0 (affects element 0's
6047        // low nibble): x0 = (0 | 16) - 16 = 0 still (5th bit set
6048        // brings it back to the *middle* of the 5-bit range, unlike a
6049        // 4-bit nibble's max of 15 -8=7). Pick a qh bit that's
6050        // unambiguous: set bit 1 (element j=1's low nibble) instead,
6051        // -> x = (0|16)-16 = 0... use a clearer case: nibble=15,
6052        // qh bit set -> x = (15|16)-16 = 31-16 = 15 (16|15=31 since
6053        // bits don't overlap: nibble uses bits 0-3, 5th bit is bit 4).
6054        let mut block = Vec::new();
6055        block.extend_from_slice(&f16::from_f32(1.0).to_le_bytes());
6056        let mut qh = [0u8; 4];
6057        qh[0] |= 1 << 0; // sets bit 0 of qh -> element j=0's 5th bit
6058        block.extend_from_slice(&qh);
6059        let mut qs = [0u8; 16];
6060        qs[0] = 0x0F; // low nibble = 15 for element 0
6061        block.extend_from_slice(&qs);
6062        let out = dequant_q5_0(&block).unwrap();
6063        assert_eq!(out.len(), 32);
6064        // element 0: nibble=15, 5th bit set -> q=15|16=31, x=31-16=15
6065        assert_eq!(out[0], 15.0);
6066        // every other element: nibble=0, no 5th bit -> q=0, x=0-16=-16
6067        assert_eq!(out[1], -16.0);
6068    }
6069
6070    #[test]
6071    fn q5_1_fifth_bit_without_bias_subtraction() {
6072        let mut block = Vec::new();
6073        block.extend_from_slice(&f16::from_f32(1.0).to_le_bytes());
6074        block.extend_from_slice(&f16::from_f32(0.0).to_le_bytes());
6075        let mut qh = [0u8; 4];
6076        qh[0] |= 1 << 0;
6077        block.extend_from_slice(&qh);
6078        let mut qs = [0u8; 16];
6079        qs[0] = 0x0F;
6080        block.extend_from_slice(&qs);
6081        let out = dequant_q5_1(&block).unwrap();
6082        assert_eq!(out.len(), 32);
6083        // element 0: q = 15|16 = 31, x = 31*1+0 = 31 (no -16 bias)
6084        assert_eq!(out[0], 31.0);
6085        assert_eq!(out[1], 0.0);
6086    }
6087
6088    #[test]
6089    fn q8_1_matches_q8_0_math_ignoring_the_extra_sum_field() {
6090        let mut block = Vec::new();
6091        block.extend_from_slice(&f16::from_f32(0.5).to_le_bytes());
6092        block.extend_from_slice(&f16::from_f32(999.0).to_le_bytes()); // s: must be ignored
6093        let qs: Vec<i8> = (0..32).map(|i| i - 16).collect();
6094        block.extend_from_slice(&i8_to_u8_bytes(&qs));
6095        let out = dequant_q8_1(&block).unwrap();
6096        assert_eq!(out.len(), 32);
6097        for (i, &v) in out.iter().enumerate() {
6098            assert_eq!(v, (i as f32 - 16.0) * 0.5);
6099        }
6100    }
6101
6102    /// Test-only `i8` -> `u8` byte reinterpretation; `i8`/`u8` share
6103    /// layout, so this is just a bit-pattern-preserving cast per
6104    /// element.
6105    fn i8_to_u8_bytes(src: &[i8]) -> Vec<u8> {
6106        src.iter().map(|&b| b as u8).collect()
6107    }
6108
6109    #[test]
6110    fn legacy_formats_fused_dot_matches_dequant_then_dot() {
6111        let x: Vec<f32> = (0..32).map(|i| ((i as f32) * 0.07).sin()).collect();
6112
6113        let mut q4_1 = Vec::new();
6114        q4_1.extend_from_slice(&f16::from_f32(0.3).to_le_bytes());
6115        q4_1.extend_from_slice(&f16::from_f32(-1.2).to_le_bytes());
6116        q4_1.extend_from_slice(
6117            &(0..16)
6118                .map(|i| (i as u8) | ((15 - i as u8) << 4))
6119                .collect::<Vec<u8>>(),
6120        );
6121        let expected: f32 = dequant_q4_1(&q4_1)
6122            .unwrap()
6123            .iter()
6124            .zip(x.iter())
6125            .map(|(a, b)| a * b)
6126            .sum();
6127        let fused = dot_q4_1_f32(&q4_1, &x);
6128        assert!(
6129            (fused - expected).abs() < 1e-3,
6130            "Q4_1: fused={fused} expected={expected}"
6131        );
6132
6133        let mut q5_0 = Vec::new();
6134        q5_0.extend_from_slice(&f16::from_f32(0.4).to_le_bytes());
6135        q5_0.extend_from_slice(&[0xA5, 0x3C, 0x00, 0xFF]);
6136        q5_0.extend_from_slice(
6137            &(0..16)
6138                .map(|i| (i as u8) | ((15 - i as u8) << 4))
6139                .collect::<Vec<u8>>(),
6140        );
6141        let expected: f32 = dequant_q5_0(&q5_0)
6142            .unwrap()
6143            .iter()
6144            .zip(x.iter())
6145            .map(|(a, b)| a * b)
6146            .sum();
6147        let fused = dot_q5_0_f32(&q5_0, &x);
6148        assert!(
6149            (fused - expected).abs() < 1e-3,
6150            "Q5_0: fused={fused} expected={expected}"
6151        );
6152
6153        let mut q5_1 = Vec::new();
6154        q5_1.extend_from_slice(&f16::from_f32(0.2).to_le_bytes());
6155        q5_1.extend_from_slice(&f16::from_f32(0.9).to_le_bytes());
6156        q5_1.extend_from_slice(&[0x12, 0x34, 0x56, 0x78]);
6157        q5_1.extend_from_slice(
6158            &(0..16)
6159                .map(|i| (i as u8) | ((15 - i as u8) << 4))
6160                .collect::<Vec<u8>>(),
6161        );
6162        let expected: f32 = dequant_q5_1(&q5_1)
6163            .unwrap()
6164            .iter()
6165            .zip(x.iter())
6166            .map(|(a, b)| a * b)
6167            .sum();
6168        let fused = dot_q5_1_f32(&q5_1, &x);
6169        assert!(
6170            (fused - expected).abs() < 1e-3,
6171            "Q5_1: fused={fused} expected={expected}"
6172        );
6173
6174        let mut q8_1 = Vec::new();
6175        q8_1.extend_from_slice(&f16::from_f32(0.6).to_le_bytes());
6176        q8_1.extend_from_slice(&f16::from_f32(0.0).to_le_bytes());
6177        let qs: Vec<i8> = (0..32).map(|i| ((i * 7) % 61) as i8 - 30).collect();
6178        q8_1.extend_from_slice(&i8_to_u8_bytes(&qs));
6179        let expected: f32 = dequant_q8_1(&q8_1)
6180            .unwrap()
6181            .iter()
6182            .zip(x.iter())
6183            .map(|(a, b)| a * b)
6184            .sum();
6185        let fused = dot_q8_1_f32(&q8_1, &x);
6186        assert!(
6187            (fused - expected).abs() < 1e-3,
6188            "Q8_1: fused={fused} expected={expected}"
6189        );
6190    }
6191
6192    #[test]
6193    fn legacy_formats_reject_misaligned_buffers() {
6194        let bad = vec![0u8; 5];
6195        assert!(dequant_q4_1(&bad).is_err());
6196        assert!(dequant_q5_0(&bad).is_err());
6197        assert!(dequant_q5_1(&bad).is_err());
6198        assert!(dequant_q8_1(&bad).is_err());
6199    }
6200
6201    #[test]
6202    fn bf16_widening_is_exact_for_round_values() {
6203        // Values with zero low-mantissa bits round-trip through
6204        // f32->bf16 truncation exactly, so this is a real equality
6205        // check, not an approximate one.
6206        for v in [0.0f32, 1.0, -1.0, 2.5, -0.5, 100.0, -100.0] {
6207            let bf16_bits = (v.to_bits() >> 16) as u16;
6208            let bytes = bf16_bits.to_le_bytes();
6209            let restored = dequant_bf16(&bytes).unwrap();
6210            assert_eq!(restored, vec![v], "bf16 round-trip mismatch for {v}");
6211        }
6212    }
6213
6214    #[test]
6215    fn bf16_widening_matches_hand_computed_bits() {
6216        // 1.0f32 = 0x3F800000; its bf16 truncation is the top 16 bits,
6217        // 0x3F80. Widening back must reproduce exactly 0x3F800000.
6218        let bytes = 0x3F80u16.to_le_bytes();
6219        let out = dequant_bf16(&bytes).unwrap();
6220        assert_eq!(out, vec![1.0f32]);
6221        assert_eq!(out[0].to_bits(), 0x3F800000);
6222    }
6223
6224    #[test]
6225    fn bf16_rejects_odd_length_buffers() {
6226        let bad = vec![0u8; 3];
6227        assert!(dequant_bf16(&bad).is_err());
6228    }
6229
6230    #[test]
6231    fn f16_widening_is_exact_and_covers_the_special_values() {
6232        // Every f16 is exactly representable in f32, so equality holds
6233        // for all finite inputs -- including subnormals, which a naive
6234        // shift-based widening gets wrong.
6235        let subnormal = f16::from_bits(0x0001); // 2^-24, smallest f16 subnormal
6236        let cases: Vec<f16> = [0.0f32, -0.0, 1.0, -1.0, 2.5, -0.5, 65504.0, -65504.0]
6237            .iter()
6238            .map(|&v| f16::from_f32(v))
6239            .chain(std::iter::once(subnormal))
6240            .collect();
6241        let bytes: Vec<u8> = cases.iter().flat_map(|h| h.to_le_bytes()).collect();
6242        let out = dequant_f16(&bytes).unwrap();
6243        assert_eq!(out.len(), cases.len());
6244        for (got, want) in out.iter().zip(cases.iter()) {
6245            assert_eq!(got.to_bits(), want.to_f32().to_bits());
6246        }
6247        assert_eq!(out[8], 2f32.powi(-24));
6248
6249        // Infinity survives; f16 max (65504) is not clamped.
6250        let inf = f16::INFINITY.to_le_bytes();
6251        assert!(dequant_f16(&inf).unwrap()[0].is_infinite());
6252    }
6253
6254    #[test]
6255    fn f16_rejects_odd_length_buffers() {
6256        let bad = vec![0u8; 5];
6257        assert!(dequant_f16(&bad).is_err());
6258    }
6259
6260    #[test]
6261    fn fused_q8_0_dot_matches_dequant_then_dot() {
6262        let original: Vec<f32> = (0..32).map(|i| (i as f32 - 16.0) * 0.37).collect();
6263        let packed = quantize_q8_0(&original);
6264        let x: Vec<f32> = (0..32).map(|i| (i as f32) * 0.01 - 0.16).collect();
6265
6266        let dequanted = dequant_q8_0(&packed).unwrap();
6267        let expected: f32 = dequanted.iter().zip(x.iter()).map(|(a, b)| a * b).sum();
6268
6269        let fused = dot_q8_0_f32(&packed, &x);
6270        assert!(
6271            (fused - expected).abs() < 1e-3,
6272            "fused={fused} expected={expected}"
6273        );
6274    }
6275
6276    #[test]
6277    fn dispatched_dot_matches_scalar_reference_across_many_blocks() {
6278        // 5 blocks (160 elements) so the test exercises multiple
6279        // AVX2 iterations, not just one, and uses varied values
6280        // (including negatives and zero) to catch sign-extension bugs
6281        // in the SIMD path specifically.
6282        let n_blocks = 5;
6283        let original: Vec<f32> = (0..n_blocks * 32)
6284            .map(|i| ((i as f32) - (n_blocks * 16) as f32) * 0.29)
6285            .collect();
6286        let packed = quantize_q8_0(&original);
6287        let x: Vec<f32> = (0..n_blocks * 32)
6288            .map(|i| ((i as f32) * 0.013).sin())
6289            .collect();
6290
6291        let dispatched = dot_q8_0_f32(&packed, &x);
6292        let scalar = dot_q8_0_f32_scalar(&packed, &x);
6293        assert!(
6294            (dispatched - scalar).abs() < 1e-2,
6295            "dispatched={dispatched} scalar={scalar} (should match regardless of which SIMD path the host CPU takes)"
6296        );
6297    }
6298
6299    #[cfg(target_arch = "x86_64")]
6300    #[test]
6301    fn avx2_kernel_matches_scalar_directly_when_available() {
6302        if !is_x86_feature_detected!("avx2") || !is_x86_feature_detected!("fma") {
6303            eprintln!("skipping: host CPU lacks AVX2/FMA");
6304            return;
6305        }
6306        let n_blocks = 8;
6307        let original: Vec<f32> = (0..n_blocks * 32)
6308            .map(|i| ((i % 37) as f32 - 18.0) * 0.11)
6309            .collect();
6310        let packed = quantize_q8_0(&original);
6311        let x: Vec<f32> = (0..n_blocks * 32)
6312            .map(|i| ((i as f32) * 0.07).cos())
6313            .collect();
6314
6315        let simd = unsafe { simd_x86::dot_q8_0_f32_avx2(&packed, &x) };
6316        let scalar = dot_q8_0_f32_scalar(&packed, &x);
6317        assert!(
6318            (simd - scalar).abs() < 1e-2,
6319            "AVX2 kernel diverged from scalar: simd={simd} scalar={scalar}"
6320        );
6321    }
6322
6323    #[cfg(target_arch = "x86_64")]
6324    #[test]
6325    fn avx2_q4_0_kernel_matches_scalar_directly_when_available() {
6326        if !is_x86_feature_detected!("avx2") || !is_x86_feature_detected!("fma") {
6327            eprintln!("skipping: host CPU lacks AVX2/FMA");
6328            return;
6329        }
6330        // Build several Q4_0 blocks with varied nibble patterns
6331        // (including 0x0, 0xF, and mixed) to exercise both the low-
6332        // and high-nibble extraction paths and the -8 bias at both
6333        // extremes.
6334        let n_blocks = 6;
6335        let mut packed = Vec::new();
6336        for b in 0..n_blocks {
6337            packed.extend_from_slice(&half::f16::from_f32(0.05 + b as f32 * 0.01).to_le_bytes());
6338            for i in 0..16u8 {
6339                let lo = (i + b as u8) % 16;
6340                let hi = (15 - i + b as u8) % 16;
6341                packed.push(lo | (hi << 4));
6342            }
6343        }
6344        let x: Vec<f32> = (0..n_blocks * 32)
6345            .map(|i| ((i as f32) * 0.09).sin())
6346            .collect();
6347
6348        let simd = unsafe { simd_x86::dot_q4_0_f32_avx2(&packed, &x) };
6349        let scalar = dot_q4_0_f32_scalar(&packed, &x);
6350        assert!(
6351            (simd - scalar).abs() < 1e-2,
6352            "AVX2 Q4_0 kernel diverged from scalar: simd={simd} scalar={scalar}"
6353        );
6354    }
6355
6356    #[cfg(target_arch = "aarch64")]
6357    #[test]
6358    fn neon_kernel_matches_scalar_directly_when_available() {
6359        if !std::arch::is_aarch64_feature_detected!("neon") {
6360            eprintln!("skipping: host CPU lacks NEON (unexpected on real aarch64 hardware)");
6361            return;
6362        }
6363        let n_blocks = 8;
6364        let original: Vec<f32> = (0..n_blocks * 32)
6365            .map(|i| ((i % 37) as f32 - 18.0) * 0.11)
6366            .collect();
6367        let packed = quantize_q8_0(&original);
6368        let x: Vec<f32> = (0..n_blocks * 32)
6369            .map(|i| ((i as f32) * 0.07).cos())
6370            .collect();
6371
6372        let simd = unsafe { simd_aarch64::dot_q8_0_f32_neon(&packed, &x) };
6373        let scalar = dot_q8_0_f32_scalar(&packed, &x);
6374        assert!(
6375            (simd - scalar).abs() < 1e-2,
6376            "NEON kernel diverged from scalar: simd={simd} scalar={scalar}"
6377        );
6378    }
6379
6380    #[cfg(target_arch = "aarch64")]
6381    #[test]
6382    fn neon_q4_0_kernel_matches_scalar_directly_when_available() {
6383        if !std::arch::is_aarch64_feature_detected!("neon") {
6384            eprintln!("skipping: host CPU lacks NEON (unexpected on real aarch64 hardware)");
6385            return;
6386        }
6387        // Build several Q4_0 blocks with varied nibble patterns
6388        // (including 0x0, 0xF, and mixed) to exercise both the low-
6389        // and high-nibble extraction paths and the -8 bias at both
6390        // extremes.
6391        let n_blocks = 6;
6392        let mut packed = Vec::new();
6393        for b in 0..n_blocks {
6394            packed.extend_from_slice(&half::f16::from_f32(0.05 + b as f32 * 0.01).to_le_bytes());
6395            for i in 0..16u8 {
6396                let lo = (i + b as u8) % 16;
6397                let hi = (15 - i + b as u8) % 16;
6398                packed.push(lo | (hi << 4));
6399            }
6400        }
6401        let x: Vec<f32> = (0..n_blocks * 32)
6402            .map(|i| ((i as f32) * 0.09).sin())
6403            .collect();
6404
6405        let simd = unsafe { simd_aarch64::dot_q4_0_f32_neon(&packed, &x) };
6406        let scalar = dot_q4_0_f32_scalar(&packed, &x);
6407        assert!(
6408            (simd - scalar).abs() < 1e-2,
6409            "NEON Q4_0 kernel diverged from scalar: simd={simd} scalar={scalar}"
6410        );
6411    }
6412
6413    #[test]
6414    fn dispatched_q4_0_matches_scalar_reference() {
6415        let n_blocks = 4;
6416        let mut packed = Vec::new();
6417        for b in 0..n_blocks {
6418            packed.extend_from_slice(&half::f16::from_f32(0.2).to_le_bytes());
6419            for i in 0..16u8 {
6420                packed.push((i % 16) | (((15 - i + b as u8) % 16) << 4));
6421            }
6422        }
6423        let x: Vec<f32> = (0..n_blocks * 32)
6424            .map(|i| (i as f32) * 0.02 - 1.0)
6425            .collect();
6426
6427        let dispatched = dot_q4_0_f32(&packed, &x);
6428        let scalar = dot_q4_0_f32_scalar(&packed, &x);
6429        assert!(
6430            (dispatched - scalar).abs() < 1e-2,
6431            "dispatched={dispatched} scalar={scalar}"
6432        );
6433    }
6434
6435    #[test]
6436    fn fused_q4_0_dot_matches_dequant_then_dot() {
6437        let mut block = Vec::new();
6438        block.extend_from_slice(&f16::from_f32(1.0).to_le_bytes());
6439        block.extend_from_slice(&[0x12u8; 16]); // arbitrary nibble pattern
6440        let x: Vec<f32> = (0..32).map(|i| (i as f32) * 0.1).collect();
6441
6442        let dequanted = dequant_q4_0(&block).unwrap();
6443        let expected: f32 = dequanted.iter().zip(x.iter()).map(|(a, b)| a * b).sum();
6444        let fused = dot_q4_0_f32(&block, &x);
6445        assert!(
6446            (fused - expected).abs() < 1e-3,
6447            "fused={fused} expected={expected}"
6448        );
6449    }
6450
6451    // Cross-validation data generated by an independent Python
6452    // implementation of the Q4_K/Q6_K public
6453    // block-quantization formats, written from the same public layout
6454    // description as the Rust code above but not derived from it.
6455    // Generated by an independent Python reference -- do not hand-edit.
6456    const Q4_K_TEST_BLOCK: [u8; 144] = [
6457        0x66, 0x2a, 0x66, 0x2a, 0x02, 0x02, 0x02, 0x02, 0x4f, 0x4b, 0x10, 0x12, 0x42, 0xe4, 0xc1,
6458        0xb2, 0x64, 0xa8, 0x70, 0x2d, 0x6a, 0xa6, 0x76, 0x79, 0xa6, 0xf7, 0x5a, 0xda, 0x37, 0x87,
6459        0x38, 0xd5, 0xf9, 0xfa, 0xc2, 0x98, 0x33, 0x94, 0x48, 0x59, 0x46, 0x73, 0xb2, 0x3b, 0x28,
6460        0x18, 0x2e, 0x02, 0xe4, 0x5d, 0x86, 0xa9, 0x93, 0x39, 0x51, 0x75, 0x5f, 0xb6, 0xac, 0x0a,
6461        0x17, 0x35, 0x8d, 0xf7, 0x97, 0x7a, 0x95, 0xf5, 0x51, 0xc9, 0xdd, 0xb8, 0xdf, 0x7a, 0x69,
6462        0xdb, 0xcb, 0xfe, 0xa6, 0xf0, 0x69, 0xf6, 0xf2, 0xc6, 0xad, 0xb4, 0x68, 0x9f, 0xad, 0x7f,
6463        0xd6, 0x40, 0x8f, 0x14, 0xca, 0xdb, 0xa9, 0x7d, 0x89, 0xb6, 0xad, 0x96, 0xa9, 0x69, 0x96,
6464        0xaa, 0x98, 0x79, 0x06, 0x9a, 0x86, 0x74, 0xff, 0xde, 0x8e, 0xf0, 0xf0, 0x3f, 0xcd, 0xdd,
6465        0x7d, 0x7f, 0x0c, 0x3d, 0x0e, 0x7f, 0x88, 0x8f, 0xf7, 0x95, 0x83, 0x13, 0x11, 0x85, 0x55,
6466        0x0c, 0x5c, 0x7b, 0x9e, 0x51, 0x48, 0x69, 0x67, 0x1e,
6467    ];
6468    const Q4_K_GOLDEN: [f32; 256] = [
6469        -0.349915, 0.0499878, -0.749817, 0.549866, 0.249939, -0.149963, -0.149963, 0.149963,
6470        -0.149963, -0.0499878, 0.249939, 0.249939, -0.0499878, -0.0499878, 0.0499878, -0.249939,
6471        0.149963, 0.249939, -0.549866, 0.0499878, -0.44989, -0.349915, 0.0499878, 0.149963,
6472        -0.149963, -0.44989, -0.549866, 0.349915, 0.0499878, 0.0499878, 0.649841, -0.549866,
6473        0.0499878, 0.44989, 0.149963, -0.349915, 0.0499878, 0.44989, 0.149963, 0.149963, 0.44989,
6474        0.949768, -0.0499878, 0.749817, -0.249939, 0.249939, -0.249939, 0.749817, 0.949768,
6475        0.949768, 0.649841, 0.349915, -0.249939, 0.349915, -0.149963, -0.0499878, -0.149963,
6476        0.149963, 0.549866, -0.249939, -0.349915, -0.44989, -0.349915, -0.549866, -0.399902,
6477        0.499878, -0.199951, 0.0999756, -0.499878, 0.0999756, -0.699829, -0.299927, 0.699829,
6478        -0.199951, 0.399902, 0.199951, -0.0999756, -0.299927, 0.499878, -0.0999756, -0.0999756,
6479        0.199951, -0.299927, -0.299927, -0.699829, 0.0999756, 0.499878, 0.0, 0.699829, 0.199951,
6480        0.0999756, 0.299927, 0.299927, 0.599854, -0.199951, -0.799805, 0.499878, -0.399902,
6481        -0.0999756, 0.0999756, 0.0, -0.599854, -0.399902, -0.199951, -0.399902, 0.199951,
6482        0.0999756, -0.89978, -0.799805, -0.599854, -0.0999756, 0.599854, 0.0, -0.199951, 0.0,
6483        0.599854, -0.399902, 0.299927, 0.399902, 0.199951, 0.399902, -0.199951, -0.299927,
6484        0.399902, 0.299927, 0.599854, 0.0999756, 0.599854, -0.0999756, -0.399902, -0.799805,
6485        -0.399902, 0.299927, -0.599854, -0.199951, 0.499878, 0.299927, 0.499878, -0.399902,
6486        -0.999756, 0.499878, -0.599854, 0.0, 0.0999756, -0.0999756, 0.299927, -0.0999756,
6487        -0.399902, 0.299927, -0.399902, -0.0999756, -0.0999756, -0.399902, 0.0, -0.199951,
6488        -0.0999756, -0.399902, 0.0, -0.399902, -0.599854, -0.299927, 1.49963, 1.49963, 0.89978,
6489        0.499878, 0.699829, -0.299927, 0.299927, 0.499878, -0.0999756, 1.09973, -0.699829,
6490        0.0999756, -1.29968, 0.89978, 1.09973, 0.499878, -0.0999756, 0.0999756, 0.699829, 0.499878,
6491        0.299927, 0.499878, -0.299927, 0.299927, 0.499878, 0.299927, -0.0999756, -1.49963,
6492        0.299927, 0.0999756, -0.0999756, 0.149963, 0.0999756, 0.0999756, -0.599854, -0.599854,
6493        0.149963, 0.0499878, 0.0499878, 0.0499878, 0.149963, 0.0, 0.0499878, 0.0999756, 0.149963,
6494        -0.199951, 0.149963, -0.249939, -0.349915, -0.44989, -0.44989, -0.549866, -0.349915,
6495        -0.349915, 0.0, 0.0, -0.0499878, 0.0999756, -0.549866, -0.199951, -0.149963, -0.249939,
6496        0.0999756, 0.949768, 0.749817, 0.249939, 0.949768, 0.949768, -0.249939, 0.649841, 0.749817,
6497        0.149963, 0.149963, -0.549866, -0.249939, -0.549866, 0.149963, 0.249939, 0.249939,
6498        0.949768, 0.349915, 0.249939, -0.44989, -0.44989, 0.249939, -0.0499878, -0.549866,
6499        -0.0499878, 0.149963, 0.349915, -0.0499878, -0.149963, 0.0499878, 0.0499878, -0.44989,
6500    ];
6501
6502    // Generated by an independent Python reference -- do not hand-edit.
6503    #[rustfmt::skip]
6504    const Q5_K_TEST_BLOCK: [u8; 176] = [
6505        0x66, 0x2a, 0x66, 0x2a, 0x01, 0x01, 0x01, 0x01, 0x4f, 0x4b, 0x10, 0x12, 0x41, 0xe2, 0xc1,
6506        0xb1, 0x72, 0x2f, 0x20, 0x07, 0x31, 0x0c, 0x38, 0xb3, 0x9c, 0xb8, 0xad, 0x2f, 0x9a, 0xea,
6507        0x17, 0xd0, 0xee, 0x93, 0x9e, 0x3e, 0x74, 0xbb, 0x28, 0x18, 0x39, 0x25, 0xb6, 0x09, 0x18,
6508        0x29, 0x1c, 0x1d, 0x29, 0x41, 0x40, 0x0a, 0x74, 0x7d, 0xfd, 0x21, 0xdd, 0x6d, 0x45, 0x73,
6509        0x0e, 0x1e, 0xc0, 0x4a, 0xfc, 0xf3, 0x8e, 0x24, 0x6b, 0x34, 0x7d, 0xbe, 0x94, 0xde, 0x59,
6510        0x7a, 0x35, 0x30, 0x36, 0x0a, 0xf9, 0x4a, 0x9b, 0xa2, 0x26, 0x21, 0xa2, 0xfa, 0xdf, 0x4b,
6511        0x29, 0x64, 0x6f, 0xbb, 0xca, 0x0f, 0x3c, 0xda, 0x20, 0xf4, 0x93, 0x86, 0xab, 0x6e, 0xb9,
6512        0xe5, 0xd5, 0xa0, 0x82, 0xd6, 0x41, 0xff, 0x12, 0xbc, 0x34, 0xbb, 0xab, 0xb8, 0x20, 0x2f,
6513        0xbb, 0x5f, 0x0c, 0x10, 0xcf, 0x49, 0xc5, 0x86, 0x5c, 0xdf, 0xff, 0x78, 0x44, 0x26, 0x3b,
6514        0xc2, 0x23, 0x3d, 0x2b, 0xe9, 0x00, 0x12, 0xf8, 0xea, 0xe2, 0x9e, 0x5e, 0x50, 0x20, 0x9f,
6515        0x9d, 0x8d, 0x7d, 0x7f, 0xcc, 0x1d, 0x0e, 0x13, 0xf8, 0xc2, 0xf1, 0x3d, 0x08, 0x2f, 0x23,
6516        0x13, 0xac, 0x0d, 0xa7, 0xe7, 0x20, 0xa3, 0x90, 0xb7, 0xc8, 0x28,
6517    ];
6518    const Q5_K_GOLDEN: [f32; 256] = [
6519        -0.299927, 0.0999756, -0.749817, 0.549866, 0.249939, -0.0999756, -0.0999756, 0.0999756,
6520        -0.0999756, -0.0999756, 0.299927, 0.199951, -0.0499878, -0.0499878, 0.0499878, -0.249939,
6521        -0.149963, 0.199951, -0.0499878, -0.549866, -0.199951, 0.249939, -0.0999756, -0.0499878,
6522        0.249939, 0.749817, -0.299927, 0.549866, -0.499878, 0.0499878, -0.44989, 0.549866,
6523        0.349915, 0.44989, -0.349915, 0.249939, -0.199951, -0.199951, 0.199951, 0.349915,
6524        0.0999756, -0.249939, -0.349915, 0.599854, 0.249939, 0.299927, 0.849792, -0.349915,
6525        0.999756, 0.999756, 0.649841, 0.349915, -0.249939, 0.399902, -0.199951, 0.0, -0.0999756,
6526        0.0999756, 0.499878, -0.199951, -0.399902, -0.399902, -0.399902, -0.549866, -0.349915,
6527        0.499878, -0.249939, 0.0999756, -0.499878, 0.0499878, -0.699829, -0.299927, 0.749817,
6528        -0.249939, 0.44989, 0.199951, -0.0499878, -0.249939, 0.499878, -0.0499878, 0.599854,
6529        -0.299927, 0.0, 0.199951, 0.149963, -0.499878, -0.249939, -0.0999756, -0.349915, 0.249939,
6530        0.249939, -0.799805, -0.699829, -0.499878, 0.0499878, 0.749817, -0.149963, 0.0999756,
6531        -0.44989, -0.399902, -0.799805, 0.0, 0.399902, -0.149963, 0.549866, 0.0999756, 0.0,
6532        0.199951, 0.199951, 0.44989, -0.299927, -0.89978, 0.0499878, -0.249939, 0.0, 0.649841,
6533        -0.44989, 0.299927, 0.399902, 0.199951, 0.44989, -0.199951, -0.249939, 0.399902, 0.299927,
6534        0.549866, 0.0999756, 0.649841, -0.0999756, -0.399902, -0.799805, -0.44989, 0.349915,
6535        -0.599854, -0.199951, 0.549866, 0.349915, 0.549866, -0.399902, -0.999756, 0.549866,
6536        -0.549866, 0.0499878, 0.0999756, -0.399902, 0.549866, 0.549866, 0.199951, 0.0, 0.0999756,
6537        -0.44989, -0.0999756, -0.0499878, -0.349915, 0.349915, -0.549866, -0.199951, -0.89978,
6538        0.199951, 0.299927, 0.199951, 1.19971, 0.399902, -0.399902, 1.09973, -0.399902, 0.299927,
6539        0.299927, -0.399902, 0.599854, 0.0999756, 0.199951, -0.299927, 0.499878, -0.299927,
6540        -0.699829, 0.599854, -0.199951, 0.0, 0.799805, 0.499878, 0.299927, 0.399902, -0.299927,
6541        0.299927, 0.399902, 0.299927, -0.0999756, -1.49963, 0.199951, 0.0, -0.0999756, 0.299927,
6542        0.0999756, 0.0999756, -0.599854, -0.599854, 0.149963, 0.0499878, 0.0499878, 0.0499878,
6543        0.149963, 0.0, 0.0499878, 0.0999756, 0.349915, -0.199951, 0.299927, 0.249939, 0.0499878,
6544        -0.199951, 0.149963, 0.349915, -0.44989, 0.0, 0.0499878, -0.249939, -0.249939, -0.599854,
6545        -0.44989, -0.599854, -0.249939, -0.199951, -0.199951, 0.149963, -0.0999756, -0.299927,
6546        -0.299927, -0.44989, -0.0999756, -0.0999756, 0.649841, 0.599854, 0.599854, 0.849792,
6547        -0.499878, 0.249939, 0.299927, 0.199951, 0.849792, 0.999756, 0.399902, 0.249939, -0.44989,
6548        -0.44989, 0.299927, -0.0499878, -0.549866, -0.0499878, 0.149963, 0.349915, -0.0499878,
6549        -0.0999756, 0.0, 0.0499878, -0.44989,
6550    ];
6551
6552    #[test]
6553    fn q5_k_dequant_matches_independent_python_reference() {
6554        let got = dequant_q5_k(&Q5_K_TEST_BLOCK).unwrap();
6555        assert_eq!(got.len(), Q5_K_GOLDEN.len());
6556        for (i, (a, b)) in got.iter().zip(Q5_K_GOLDEN.iter()).enumerate() {
6557            assert!(
6558                (a - b).abs() < 1e-3,
6559                "Q5_K element {i}: rust={a} python={b}"
6560            );
6561        }
6562    }
6563
6564    #[test]
6565    fn q5_k_fused_dot_matches_dequant_then_dot() {
6566        let dequanted = dequant_q5_k(&Q5_K_TEST_BLOCK).unwrap();
6567        let x: Vec<f32> = (0..256).map(|i| ((i as f32) * 0.023).sin()).collect();
6568        let expected: f32 = dequanted.iter().zip(x.iter()).map(|(a, b)| a * b).sum();
6569        let fused = dot_q5_k_f32(&Q5_K_TEST_BLOCK, &x);
6570        assert!(
6571            (fused - expected).abs() < 1e-2,
6572            "fused={fused} expected={expected}"
6573        );
6574    }
6575
6576    #[test]
6577    fn q5_k_rejects_misaligned_buffers() {
6578        let bad = vec![0u8; 5];
6579        assert!(dequant_q5_k(&bad).is_err());
6580    }
6581
6582    const Q6_K_TEST_BLOCK: [u8; 210] = [
6583        0xe0, 0xa5, 0x40, 0x5c, 0x8d, 0x3a, 0x0a, 0x26, 0xfb, 0x4b, 0x6e, 0x9a, 0xdf, 0x3e, 0xa3,
6584        0xc4, 0xf8, 0x2b, 0x1d, 0x95, 0x76, 0x7d, 0x3b, 0xcd, 0xfd, 0xef, 0xc2, 0x0b, 0x07, 0x63,
6585        0x29, 0xfb, 0x81, 0x57, 0xbe, 0xbe, 0x06, 0xf7, 0x3a, 0x92, 0xc4, 0x43, 0xff, 0xad, 0xac,
6586        0x7e, 0x0f, 0x00, 0x2a, 0x4f, 0xf0, 0xf8, 0xa9, 0xfa, 0x3c, 0x90, 0x6d, 0x73, 0x2d, 0x5a,
6587        0xe6, 0xc6, 0x46, 0xf2, 0x0d, 0x55, 0x4c, 0x25, 0x38, 0x71, 0x2b, 0x35, 0x38, 0x82, 0x16,
6588        0x37, 0x5f, 0x32, 0x61, 0x02, 0xdd, 0x2f, 0x6f, 0x7b, 0x1f, 0xb4, 0x1a, 0x1b, 0x3e, 0x4f,
6589        0x11, 0xa3, 0x17, 0x40, 0x5a, 0x5f, 0x76, 0xcd, 0x19, 0x27, 0x9b, 0xc7, 0xc8, 0xf7, 0xf7,
6590        0xee, 0xf4, 0x86, 0xd9, 0xfd, 0xa7, 0xfe, 0x9e, 0xac, 0x70, 0x53, 0x5b, 0x76, 0xfb, 0x39,
6591        0xf8, 0x4b, 0x98, 0xfe, 0xd0, 0x06, 0x21, 0x4c, 0x4d, 0xbe, 0x10, 0x2b, 0x06, 0x65, 0xc9,
6592        0x5e, 0xf9, 0x95, 0x72, 0xae, 0x99, 0xd9, 0x7e, 0x15, 0xbd, 0x5e, 0x6d, 0xe8, 0x25, 0x8a,
6593        0xd5, 0x99, 0xc6, 0x6b, 0x69, 0xc7, 0x84, 0xc6, 0xa4, 0xf7, 0xb9, 0x6d, 0x68, 0x45, 0x0e,
6594        0x65, 0x69, 0xeb, 0xe6, 0xeb, 0xe9, 0x28, 0xa6, 0xb9, 0x96, 0xf2, 0xe8, 0xa7, 0x9b, 0x6e,
6595        0x79, 0x8a, 0x68, 0x65, 0x59, 0x98, 0x8b, 0x44, 0x41, 0x98, 0x9a, 0x56, 0x01, 0x01, 0x01,
6596        0x02, 0x01, 0x01, 0x01, 0x01, 0x02, 0x01, 0x02, 0x02, 0x01, 0x01, 0x01, 0x02, 0x1f, 0x25,
6597    ];
6598    const Q6_K_GOLDEN: [f32; 256] = [
6599        -0.320068, 0.100021, -0.640137, 0.56012, 0.260056, -0.120026, -0.120026, 0.120026,
6600        -0.100021, -0.100021, 0.28006, 0.200043, -0.0200043, -0.0400085, 0.0600128, -0.240051,
6601        -0.160034, 0.220047, -0.0600128, -0.540115, -0.200043, 0.260056, -0.100021, -0.0600128,
6602        0.260056, 0.620132, -0.28006, 0.540115, -0.500107, 0.0600128, -0.460098, 0.540115,
6603        0.340073, 0.460098, -0.360077, 0.28006, -0.200043, -0.180038, 0.200043, 0.360077,
6604        0.0800171, -0.260056, -0.340073, 0.580124, 0.240051, 0.28006, 0.620132, -0.320068, 1.04022,
6605        1.24026, 0.640137, 0.320068, -0.28006, 0.400085, -0.160034, 0.0, -0.120026, 0.120026,
6606        0.520111, -0.240051, -0.400085, -0.400085, -0.400085, -0.56012, -0.360077, 0.520111,
6607        -0.240051, 0.100021, -0.480103, 0.0600128, -0.640137, -0.28006, 0.620132, -0.240051,
6608        0.440094, 0.180038, -0.0600128, -0.260056, 0.520111, -0.0800171, 0.620132, -0.28006,
6609        0.0200043, 0.180038, 0.14003, -0.500107, -0.260056, -0.0800171, -0.340073, 0.28006,
6610        0.240051, -0.640137, -0.640137, -0.520111, 0.0400085, 0.620132, -0.160034, 0.100021,
6611        -0.42009, -0.42009, -0.640137, -0.0200043, 0.380081, -0.14003, 0.56012, 0.0800171,
6612        -0.0200043, 0.200043, 0.200043, 0.460098, -0.320068, -0.640137, 0.0400085, -0.240051,
6613        -0.0200043, 0.620132, -0.440094, 0.300064, 0.380081, 0.180038, 0.440094, -0.180038,
6614        -0.28006, 0.42009, 0.28006, 0.56012, 0.0800171, 0.620132, -0.120026, -0.440094, -0.800171,
6615        -0.440094, 0.320068, -0.600128, -0.200043, 0.840179, 0.320068, 0.720154, -0.400085,
6616        -1.00021, 0.600128, -0.56012, 0.0400085, 0.0800171, -0.380081, 0.620132, 0.620132,
6617        0.220047, -0.0200043, 0.0800171, -0.440094, -0.100021, -0.0400085, -0.340073, 0.340073,
6618        -0.580124, -0.180038, -0.640137, 0.200043, 0.300064, 0.240051, 1.16025, 0.360077,
6619        -0.360077, 1.08023, -0.360077, 0.320068, 0.28006, -0.360077, 0.56012, 0.160034, 0.240051,
6620        -0.28006, 0.520111, -0.360077, -0.720154, 0.56012, -0.160034, 0.0, 0.760162, 0.440094,
6621        0.240051, 0.440094, -0.28006, 0.320068, 0.440094, 0.320068, -0.0800171, -1.28027, 0.240051,
6622        0.0400085, -0.160034, 0.320068, 0.100021, 0.0800171, -0.600128, -0.580124, 0.14003,
6623        0.0400085, 0.0600128, 0.0600128, 0.160034, 0.0200043, 0.0600128, 0.100021, 0.380081,
6624        -0.200043, 0.320068, 0.260056, 0.0400085, -0.200043, 0.14003, 0.340073, -0.42009,
6625        0.0200043, 0.0200043, -0.260056, -0.240051, -0.620132, -0.440094, -0.620132, -0.240051,
6626        -0.220047, -0.220047, 0.14003, -0.0800171, -0.300064, -0.28006, -0.460098, -0.0800171,
6627        -0.0800171, 0.620132, 0.620132, 0.600128, 0.620132, -0.480103, 0.260056, 0.300064,
6628        0.200043, 0.620132, 1.00021, 0.400085, 0.28006, -0.440094, -0.440094, 0.28006, -0.0400085,
6629        -0.520111, -0.0400085, 0.160034, 0.360077, -0.0400085, -0.120026, 0.0, 0.0800171,
6630        -0.480103,
6631    ];
6632
6633    // Generated by an independent Python reference -- do not hand-edit.
6634    // Same input values as Q6_K_TEST_BLOCK, but every odd sub-block
6635    // stores a *negative* int8 scale. Q6_K scales are signed in the
6636    // public format; this fixture is what distinguishes a correctly
6637    // signed decoder from one that reads scale bytes as unsigned
6638    // (-1 read as 255) -- the all-positive fixture above cannot.
6639    const Q6_K_SIGNED_SCALES_TEST_BLOCK: [u8; 210] = [
6640        0xe0, 0xa5, 0x40, 0x5c, 0x8d, 0x3a, 0x0a, 0x26, 0xfb, 0x4b, 0x6e, 0x9a, 0xdf, 0x3e, 0xa3,
6641        0xc4, 0x18, 0xe5, 0xf3, 0x7b, 0x9a, 0x93, 0xd5, 0x43, 0x13, 0x20, 0x4e, 0xf5, 0xf9, 0xad,
6642        0xe7, 0x05, 0x81, 0x57, 0xbe, 0xbe, 0x06, 0xf7, 0x3a, 0x92, 0xc4, 0x43, 0xff, 0xad, 0xac,
6643        0x7e, 0x0f, 0x00, 0xe6, 0xc0, 0x10, 0x08, 0x67, 0x16, 0xd4, 0x70, 0xa3, 0x9d, 0xe3, 0xb6,
6644        0x2a, 0x4a, 0xca, 0x0e, 0x0d, 0x55, 0x4c, 0x25, 0x38, 0x71, 0x2b, 0x35, 0x38, 0x82, 0x16,
6645        0x37, 0x5f, 0x32, 0x61, 0x02, 0x33, 0xe1, 0xa1, 0x95, 0xf1, 0x5c, 0xf6, 0xf5, 0xd2, 0xc1,
6646        0xff, 0x6d, 0xf9, 0xcf, 0xb6, 0xb1, 0x76, 0xcd, 0x19, 0x27, 0x9b, 0xc7, 0xc8, 0xf7, 0xf7,
6647        0xee, 0xf4, 0x86, 0xd9, 0xfd, 0xa7, 0xfe, 0x72, 0x64, 0x90, 0xbd, 0xb5, 0x9a, 0x15, 0xd7,
6648        0x18, 0xc5, 0x78, 0x12, 0x3f, 0x0a, 0xef, 0xc4, 0x4d, 0xbe, 0x10, 0x2b, 0x06, 0x65, 0xc9,
6649        0x5e, 0xf9, 0x95, 0x72, 0xae, 0x99, 0xd9, 0x7e, 0x15, 0x42, 0xa1, 0x96, 0x17, 0xda, 0x75,
6650        0x2a, 0x6a, 0x39, 0x94, 0x96, 0x38, 0x7b, 0x39, 0x5b, 0x08, 0xb9, 0x6d, 0x68, 0x45, 0x0e,
6651        0x65, 0x69, 0xeb, 0xe6, 0xeb, 0xe9, 0x28, 0xa6, 0xb9, 0x96, 0xf2, 0x17, 0x58, 0x68, 0x91,
6652        0x86, 0x75, 0x97, 0x9a, 0xa6, 0x67, 0x74, 0xbb, 0xbe, 0xa7, 0x65, 0xa9, 0x01, 0xff, 0x01,
6653        0xfe, 0x01, 0xff, 0x01, 0xff, 0x02, 0xff, 0x02, 0xfe, 0x01, 0xff, 0x01, 0xfe, 0x1f, 0x25,
6654    ];
6655    const Q6_K_SIGNED_SCALES_GOLDEN: [f32; 256] = [
6656        -0.320068, 0.100021, -0.640137, 0.56012, 0.260056, -0.120026, -0.120026, 0.120026,
6657        -0.100021, -0.100021, 0.28006, 0.200043, -0.0200043, -0.0400085, 0.0600128, -0.240051,
6658        -0.160034, 0.220047, -0.0600128, -0.540115, -0.200043, 0.260056, -0.100021, -0.0600128,
6659        0.260056, 0.640137, -0.28006, 0.540115, -0.500107, 0.0600128, -0.460098, 0.540115,
6660        0.340073, 0.460098, -0.360077, 0.28006, -0.200043, -0.180038, 0.200043, 0.360077,
6661        0.0800171, -0.260056, -0.340073, 0.580124, 0.240051, 0.28006, 0.620132, -0.320068, 1.04022,
6662        1.28027, 0.640137, 0.320068, -0.28006, 0.400085, -0.160034, -0.0, -0.120026, 0.120026,
6663        0.520111, -0.240051, -0.400085, -0.400085, -0.400085, -0.56012, -0.360077, 0.520111,
6664        -0.240051, 0.100021, -0.480103, 0.0600128, -0.640137, -0.28006, 0.620132, -0.240051,
6665        0.440094, 0.180038, -0.0600128, -0.260056, 0.520111, -0.0800171, 0.620132, -0.28006,
6666        0.0200043, 0.180038, 0.14003, -0.500107, -0.260056, -0.0800171, -0.340073, 0.28006,
6667        0.240051, -0.620132, -0.620132, -0.520111, 0.0400085, 0.640137, -0.160034, 0.100021,
6668        -0.42009, -0.42009, -0.640137, -0.0200043, 0.380081, -0.14003, 0.56012, 0.0800171,
6669        -0.0200043, 0.200043, 0.200043, 0.460098, -0.320068, -0.640137, 0.0400085, -0.240051,
6670        -0.0200043, 0.640137, -0.440094, 0.300064, 0.380081, 0.180038, 0.440094, -0.180038,
6671        -0.28006, 0.42009, 0.28006, 0.56012, 0.0800171, 0.640137, -0.120026, -0.440094, -0.800171,
6672        -0.440094, 0.320068, -0.600128, -0.200043, 0.840179, 0.320068, 0.720154, -0.400085,
6673        -1.00021, 0.600128, -0.56012, 0.0400085, 0.0800171, -0.380081, 0.620132, 0.620132,
6674        0.220047, -0.0200043, 0.0800171, -0.440094, -0.100021, -0.0400085, -0.340073, 0.340073,
6675        -0.580124, -0.180038, -0.620132, 0.200043, 0.300064, 0.240051, 1.16025, 0.360077,
6676        -0.360077, 1.08023, -0.360077, 0.320068, 0.28006, -0.360077, 0.56012, 0.160034, 0.240051,
6677        -0.28006, 0.520111, -0.360077, -0.720154, 0.56012, -0.160034, -0.0, 0.760162, 0.440094,
6678        0.240051, 0.440094, -0.28006, 0.320068, 0.440094, 0.320068, -0.0800171, -1.24026, 0.240051,
6679        0.0400085, -0.160034, 0.320068, 0.100021, 0.0800171, -0.600128, -0.580124, 0.14003,
6680        0.0400085, 0.0600128, 0.0600128, 0.160034, 0.0200043, 0.0600128, 0.100021, 0.380081,
6681        -0.200043, 0.320068, 0.260056, 0.0400085, -0.200043, 0.14003, 0.340073, -0.42009,
6682        0.0200043, 0.0200043, -0.260056, -0.240051, -0.620132, -0.440094, -0.620132, -0.240051,
6683        -0.220047, -0.220047, 0.14003, -0.0800171, -0.300064, -0.28006, -0.460098, -0.0800171,
6684        -0.0800171, 0.620132, 0.620132, 0.600128, 0.620132, -0.480103, 0.260056, 0.300064,
6685        0.200043, 0.620132, 1.00021, 0.400085, 0.28006, -0.440094, -0.440094, 0.28006, -0.0400085,
6686        -0.520111, -0.0400085, 0.160034, 0.360077, -0.0400085, -0.120026, -0.0, 0.0800171,
6687        -0.480103,
6688    ];
6689
6690    #[test]
6691    fn q4_k_dequant_matches_independent_python_reference() {
6692        let got = dequant_q4_k(&Q4_K_TEST_BLOCK).unwrap();
6693        assert_eq!(got.len(), Q4_K_GOLDEN.len());
6694        for (i, (a, b)) in got.iter().zip(Q4_K_GOLDEN.iter()).enumerate() {
6695            assert!(
6696                (a - b).abs() < 1e-3,
6697                "Q4_K element {i}: rust={a} python={b}"
6698            );
6699        }
6700    }
6701
6702    #[test]
6703    fn q4_k_fused_dot_matches_dequant_then_dot() {
6704        let dequanted = dequant_q4_k(&Q4_K_TEST_BLOCK).unwrap();
6705        let x: Vec<f32> = (0..256).map(|i| ((i as f32) * 0.017).sin()).collect();
6706        let expected: f32 = dequanted.iter().zip(x.iter()).map(|(a, b)| a * b).sum();
6707        let fused = dot_q4_k_f32(&Q4_K_TEST_BLOCK, &x);
6708        assert!(
6709            (fused - expected).abs() < 1e-2,
6710            "fused={fused} expected={expected}"
6711        );
6712    }
6713
6714    #[test]
6715    fn q6_k_dequant_matches_independent_python_reference() {
6716        let got = dequant_q6_k(&Q6_K_TEST_BLOCK).unwrap();
6717        assert_eq!(got.len(), Q6_K_GOLDEN.len());
6718        for (i, (a, b)) in got.iter().zip(Q6_K_GOLDEN.iter()).enumerate() {
6719            assert!(
6720                (a - b).abs() < 1e-3,
6721                "Q6_K element {i}: rust={a} python={b}"
6722            );
6723        }
6724    }
6725
6726    #[test]
6727    fn q6_k_fused_dot_matches_dequant_then_dot() {
6728        let dequanted = dequant_q6_k(&Q6_K_TEST_BLOCK).unwrap();
6729        let x: Vec<f32> = (0..256).map(|i| ((i as f32) * 0.021).cos()).collect();
6730        let expected: f32 = dequanted.iter().zip(x.iter()).map(|(a, b)| a * b).sum();
6731        let fused = dot_q6_k_f32(&Q6_K_TEST_BLOCK, &x);
6732        assert!(
6733            (fused - expected).abs() < 1e-2,
6734            "fused={fused} expected={expected}"
6735        );
6736    }
6737
6738    // Generated by an independent Python reference -- do not hand-edit.
6739    // Random-but-well-formed blocks (any byte pattern is structurally
6740    // valid for these formats; `d` pinned to a small non-NaN f16).
6741    // The Python reference itself is cross-validated against the real
6742    // compiled ggml implementation.
6743    // Generated by an independent Python reference -- do not hand-edit.
6744    const IQ1_S_TEST_BLOCK: [u8; 50] = [
6745        0x0a, 0x2f, 0xfa, 0x06, 0x1e, 0x37, 0x6f, 0xe3, 0x62, 0xd0, 0xb6, 0xa4, 0x25, 0xae, 0x76,
6746        0x14, 0x72, 0x5b, 0xfa, 0x05, 0xd1, 0xf1, 0x2a, 0x4c, 0xad, 0x29, 0xae, 0xf4, 0xcf, 0x0c,
6747        0x96, 0x51, 0x58, 0x03, 0x6d, 0xd3, 0x10, 0x92, 0x70, 0xff, 0x61, 0x58, 0xc8, 0x30, 0x25,
6748        0x64, 0x49, 0x85, 0xc0, 0x24,
6749    ];
6750    const IQ1_S_GOLDEN: [f32; 256] = [
6751        1.05861, 1.05861, 1.05861, -0.15123, -0.15123, -0.15123, -1.36107, 1.05861, -1.36107,
6752        -1.36107, 1.05861, -0.15123, -1.36107, -0.15123, 1.05861, -0.15123, -0.15123, -0.15123,
6753        -1.36107, -0.15123, -0.15123, -0.15123, 1.05861, -0.15123, -1.36107, -0.15123, -1.36107,
6754        -0.15123, -0.15123, -0.15123, -0.15123, -1.36107, -0.371201, 0.288712, -0.0412445,
6755        0.288712, -0.0412445, -0.0412445, -0.371201, -0.371201, 0.288712, -0.0412445, -0.0412445,
6756        -0.0412445, -0.0412445, -0.0412445, -0.371201, -0.0412445, -0.0412445, -0.371201,
6757        -0.0412445, -0.0412445, -0.0412445, -0.0412445, -0.371201, -0.371201, 0.288712, 0.288712,
6758        0.288712, 0.288712, -0.371201, -0.371201, 0.288712, -0.371201, 1.44356, 1.44356, 1.44356,
6759        -1.856, 1.44356, 1.44356, -1.856, -1.856, -1.856, 1.44356, -0.206223, -1.856, -1.856,
6760        -0.206223, -0.206223, 1.44356, 1.44356, -0.206223, -0.206223, -0.206223, -0.206223,
6761        -0.206223, 1.44356, -0.206223, -0.206223, 1.44356, -1.856, 1.44356, -0.206223, -0.206223,
6762        1.44356, 1.44356, 0.15123, 1.36107, 1.36107, 1.36107, 1.36107, 0.15123, 0.15123, -1.05861,
6763        0.15123, 0.15123, -1.05861, 1.36107, 0.15123, 0.15123, 0.15123, 0.15123, 1.36107, 1.36107,
6764        -1.05861, 1.36107, 1.36107, 0.15123, 0.15123, -1.05861, 0.15123, 1.36107, -1.05861,
6765        -1.05861, -1.05861, 1.36107, 0.15123, 0.15123, 0.866135, 0.0962372, -0.67366, 0.0962372,
6766        0.866135, -0.67366, 0.0962372, -0.67366, 0.866135, 0.0962372, 0.0962372, 0.866135,
6767        0.866135, -0.67366, 0.0962372, -0.67366, -0.67366, 0.866135, 0.0962372, 0.0962372,
6768        -0.67366, 0.0962372, 0.0962372, 0.0962372, 0.866135, -0.67366, 0.0962372, 0.866135,
6769        0.0962372, -0.67366, 0.0962372, -0.67366, 1.60854, 0.178726, 1.60854, 0.178726, 0.178726,
6770        0.178726, 1.60854, 0.178726, 1.60854, 0.178726, -1.25108, 1.60854, 1.60854, 0.178726,
6771        0.178726, 0.178726, 0.178726, 0.178726, 1.60854, 1.60854, 0.178726, 1.60854, -1.25108,
6772        -1.25108, -1.25108, -1.25108, 0.178726, -1.25108, 1.60854, 0.178726, 1.60854, -1.25108,
6773        0.0962372, -0.123734, -0.0137482, -0.0137482, 0.0962372, 0.0962372, -0.0137482, -0.123734,
6774        -0.123734, 0.0962372, -0.123734, 0.0962372, 0.0962372, -0.123734, 0.0962372, -0.123734,
6775        -0.0137482, 0.0962372, -0.0137482, -0.0137482, 0.0962372, -0.123734, -0.123734, 0.0962372,
6776        -0.123734, -0.0137482, -0.0137482, 0.0962372, -0.123734, -0.0137482, 0.0962372, -0.123734,
6777        0.618668, 0.618668, -0.481186, 0.618668, -0.481186, 0.618668, -0.481186, -0.481186,
6778        -0.481186, 0.0687408, 0.618668, 0.0687408, -0.481186, 0.0687408, -0.481186, -0.481186,
6779        0.0687408, 0.0687408, 0.618668, 0.618668, 0.618668, 0.618668, -0.481186, 0.0687408,
6780        0.618668, 0.0687408, -0.481186, 0.0687408, -0.481186, 0.0687408, 0.618668, -0.481186,
6781    ];
6782
6783    const IQ2_XXS_TEST_BLOCK: [u8; 66] = [
6784        0x29, 0x30, 0xd9, 0x33, 0x95, 0x4c, 0x08, 0x1e, 0xad, 0x79, 0x49, 0xf2, 0x8d, 0x5f, 0x93,
6785        0xea, 0x78, 0x18, 0x98, 0xb9, 0x94, 0x14, 0xad, 0xce, 0xca, 0x1d, 0xab, 0x81, 0x53, 0x4a,
6786        0x68, 0xd0, 0x59, 0x96, 0x36, 0x5d, 0xbe, 0x20, 0xc4, 0xff, 0xe4, 0x2c, 0xcd, 0x2f, 0x4f,
6787        0x4f, 0x67, 0x53, 0xc6, 0xd5, 0xa2, 0xfb, 0xc7, 0xf3, 0xe2, 0x6b, 0xf1, 0x99, 0x23, 0x1e,
6788        0x2d, 0x5e, 0x8c, 0x78, 0xc2, 0x31,
6789    ];
6790    const IQ2_XXS_GOLDEN: [f32; 256] = [
6791        1.95007, 1.95007, 1.95007, -6.09398, 6.09398, 1.95007, 1.95007, -10.4816, 1.95007, 1.95007,
6792        -1.95007, -10.4816, -6.09398, -6.09398, 1.95007, 1.95007, 6.09398, 6.09398, -1.95007,
6793        10.4816, -6.09398, -1.95007, 1.95007, -6.09398, -1.95007, 1.95007, -1.95007, -6.09398,
6794        1.95007, 1.95007, -6.09398, 1.95007, -0.390015, -1.2188, 0.390015, 0.390015, -0.390015,
6795        0.390015, 1.2188, -0.390015, -0.390015, 0.390015, -0.390015, 0.390015, -0.390015, 1.2188,
6796        -1.2188, 2.09633, -0.390015, -0.390015, 2.09633, 1.2188, 0.390015, -0.390015, -0.390015,
6797        1.2188, -0.390015, -2.09633, 1.2188, 0.390015, 1.2188, 1.2188, -1.2188, -0.390015,
6798        -0.390015, 2.09633, -0.390015, -1.2188, 2.09633, -0.390015, 0.390015, 1.2188, -0.390015,
6799        0.390015, -1.2188, -2.09633, -0.390015, 1.2188, 1.2188, 1.2188, -0.390015, -0.390015,
6800        0.390015, -2.09633, 1.2188, -0.390015, 0.390015, 1.2188, 2.09633, -0.390015, -2.09633,
6801        -2.09633, 0.390015, -0.390015, -0.390015, -0.390015, 13.2767, 2.47009, 2.47009, -13.2767,
6802        7.71904, -13.2767, -2.47009, -7.71904, 2.47009, 2.47009, 13.2767, 2.47009, 2.47009,
6803        -13.2767, 13.2767, -2.47009, -2.47009, -2.47009, -13.2767, 2.47009, 7.71904, -2.47009,
6804        -7.71904, -2.47009, 2.47009, -2.47009, 7.71904, 2.47009, -2.47009, -2.47009, 7.71904,
6805        -2.47009, 0.650024, 0.650024, -2.03133, 0.650024, 3.49388, 2.03133, -0.650024, 0.650024,
6806        -2.03133, -3.49388, -0.650024, -2.03133, -0.650024, -2.03133, -2.03133, -0.650024,
6807        -0.650024, -0.650024, 0.650024, 0.650024, -0.650024, 3.49388, 2.03133, -2.03133, -2.03133,
6808        -0.650024, -0.650024, 0.650024, 0.650024, -2.03133, -0.650024, -0.650024, -10.9692,
6809        -10.9692, -3.51013, 3.51013, 3.51013, -10.9692, -18.867, -10.9692, 3.51013, -18.867,
6810        -3.51013, 3.51013, 10.9692, -10.9692, 3.51013, -3.51013, -3.51013, 3.51013, 10.9692,
6811        -18.867, -3.51013, 3.51013, 10.9692, -3.51013, 3.51013, -3.51013, -10.9692, -18.867,
6812        3.51013, -3.51013, 10.9692, 3.51013, 2.47009, -2.47009, 2.47009, 2.47009, 13.2767,
6813        -7.71904, -2.47009, -7.71904, -7.71904, -13.2767, -2.47009, 2.47009, -7.71904, 2.47009,
6814        -13.2767, -13.2767, -2.47009, 13.2767, -13.2767, 7.71904, 2.47009, 2.47009, -13.2767,
6815        -7.71904, -13.2767, -2.47009, -13.2767, -2.47009, 2.47009, 7.71904, -7.71904, -13.2767,
6816        2.84386, 0.910034, -4.89143, -0.910034, 0.910034, 2.84386, 0.910034, 0.910034, -4.89143,
6817        0.910034, 4.89143, 0.910034, -4.89143, -0.910034, -0.910034, 0.910034, -0.910034, 0.910034,
6818        0.910034, -0.910034, 2.84386, 2.84386, 0.910034, 0.910034, 0.910034, -0.910034, -0.910034,
6819        -4.89143, 0.910034, 2.84386, 2.84386, -0.910034,
6820    ];
6821
6822    const IQ3_XXS_TEST_BLOCK: [u8; 98] = [
6823        0x71, 0x31, 0x16, 0x0a, 0x79, 0x04, 0x5d, 0x87, 0xae, 0x2a, 0x4a, 0x43, 0xfd, 0x02, 0xba,
6824        0x6c, 0x10, 0x42, 0x80, 0xe5, 0x1d, 0x08, 0x22, 0xcb, 0x21, 0x54, 0xf9, 0xaa, 0x8e, 0xc2,
6825        0xf2, 0x34, 0x66, 0x1e, 0x2a, 0xef, 0x19, 0xae, 0x48, 0x47, 0x29, 0xa0, 0x72, 0xd1, 0x31,
6826        0xc0, 0x65, 0x49, 0xde, 0x79, 0x32, 0xe6, 0x4d, 0xb6, 0x55, 0x3f, 0x4d, 0xf1, 0x18, 0xbb,
6827        0x18, 0x59, 0x4c, 0x31, 0xa3, 0xb2, 0x34, 0xdd, 0xf6, 0x4a, 0x91, 0x51, 0x3f, 0x3e, 0x40,
6828        0x69, 0xad, 0xbf, 0x1a, 0xd0, 0x05, 0xfb, 0xbe, 0x8b, 0x0b, 0xdd, 0xdf, 0x7d, 0x94, 0x74,
6829        0x92, 0x3e, 0xff, 0x04, 0x2a, 0xc4, 0xea, 0xc9,
6830    ];
6831    const IQ3_XXS_GOLDEN: [f32; 256] = [
6832        1.5304, 23.7211, -4.59119, 1.5304, -10.7128, -23.7211, 1.5304, -1.5304, 7.65198, -7.65198,
6833        7.65198, -7.65198, -10.7128, -4.59119, 1.5304, 1.5304, -4.59119, -23.7211, 16.8344,
6834        -4.59119, -13.7736, 23.7211, -16.8344, -7.65198, -13.7736, -1.5304, -1.5304, 13.7736,
6835        -23.7211, 10.7128, -13.7736, -1.5304, -3.57092, 1.19031, 5.95154, 3.57092, -18.4498,
6836        10.7128, 1.19031, 3.57092, -5.95154, -1.19031, 13.0934, 18.4498, 10.7128, -1.19031,
6837        1.19031, -1.19031, -18.4498, 1.19031, -8.33215, -10.7128, -13.0934, -1.19031, -3.57092,
6838        5.95154, -3.57092, 5.95154, 3.57092, 1.19031, -10.7128, -8.33215, -1.19031, 3.57092,
6839        3.91101, 60.6207, 27.3771, 19.5551, 35.1991, 35.1991, -35.1991, -50.8431, 11.733, -27.3771,
6840        19.5551, 3.91101, -11.733, 27.3771, -3.91101, -3.91101, -43.0211, 60.6207, -19.5551,
6841        3.91101, -50.8431, -19.5551, 11.733, 43.0211, -60.6207, 43.0211, -19.5551, -3.91101,
6842        -11.733, -27.3771, -27.3771, 11.733, 5.27136, -68.5277, 36.8995, -81.7061, -68.5277,
6843        36.8995, 68.5277, -36.8995, 26.3568, 15.8141, 5.27136, 36.8995, 57.985, -5.27136, 81.7061,
6844        -47.4423, -5.27136, -47.4423, -15.8141, 81.7061, -47.4423, 68.5277, 68.5277, 5.27136,
6845        26.3568, 26.3568, 5.27136, -26.3568, -36.8995, 36.8995, -26.3568, -5.27136, 71.1634,
6846        -32.1383, -41.3207, -4.59119, -22.9559, -32.1383, 4.59119, -71.1634, -41.3207, -4.59119,
6847        -22.9559, 4.59119, -41.3207, 4.59119, 4.59119, 41.3207, 4.59119, -22.9559, -13.7736,
6848        -13.7736, 13.7736, -13.7736, 13.7736, 13.7736, 32.1383, 13.7736, 41.3207, -4.59119,
6849        13.7736, -13.7736, -32.1383, -32.1383, -39.5352, -33.1586, -7.65198, -12.7533, -17.8546,
6850        28.0573, -17.8546, 28.0573, -12.7533, -17.8546, 28.0573, -2.55066, -17.8546, -22.9559,
6851        -28.0573, 22.9559, -2.55066, 12.7533, 2.55066, 12.7533, -12.7533, 12.7533, -7.65198,
6852        -7.65198, 22.9559, 33.1586, -2.55066, 33.1586, 12.7533, -12.7533, 12.7533, 12.7533,
6853        0.85022, -1.87048, 1.87048, 0.170044, -1.87048, 0.170044, 1.87048, 2.21057, -0.85022,
6854        0.510132, -0.85022, -0.510132, -2.63568, -1.19031, -0.85022, 1.5304, 2.21057, 1.5304,
6855        -1.19031, -0.510132, -1.19031, -0.85022, -0.170044, -0.510132, -0.85022, -0.510132,
6856        -0.85022, 0.510132, 2.21057, -0.85022, 0.510132, 2.63568, 21.2555, -4.2511, 21.2555,
6857        -4.2511, 46.7621, -38.2599, 29.7577, -38.2599, 21.2555, 4.2511, 21.2555, -4.2511, 4.2511,
6858        46.7621, 38.2599, -12.7533, -4.2511, -12.7533, 21.2555, -12.7533, 21.2555, -29.7577,
6859        46.7621, 4.2511, -65.892, -38.2599, -38.2599, -29.7577, 29.7577, 46.7621, -4.2511,
6860        -38.2599,
6861    ];
6862
6863    #[test]
6864    fn iq1_s_dequant_matches_independent_python_reference() {
6865        let got = dequant_iq1_s(&IQ1_S_TEST_BLOCK).unwrap();
6866        assert_eq!(got.len(), IQ1_S_GOLDEN.len());
6867        for (i, (a, b)) in got.iter().zip(IQ1_S_GOLDEN.iter()).enumerate() {
6868            assert!(
6869                (a - b).abs() < 1e-3,
6870                "IQ1_S element {i}: rust={a} python={b}"
6871            );
6872        }
6873    }
6874
6875    #[test]
6876    fn iq2_xxs_dequant_matches_independent_python_reference() {
6877        let got = dequant_iq2_xxs(&IQ2_XXS_TEST_BLOCK).unwrap();
6878        assert_eq!(got.len(), IQ2_XXS_GOLDEN.len());
6879        for (i, (a, b)) in got.iter().zip(IQ2_XXS_GOLDEN.iter()).enumerate() {
6880            assert!(
6881                (a - b).abs() < 1e-3,
6882                "IQ2_XXS element {i}: rust={a} python={b}"
6883            );
6884        }
6885    }
6886
6887    #[test]
6888    fn iq3_xxs_dequant_matches_independent_python_reference() {
6889        let got = dequant_iq3_xxs(&IQ3_XXS_TEST_BLOCK).unwrap();
6890        assert_eq!(got.len(), IQ3_XXS_GOLDEN.len());
6891        for (i, (a, b)) in got.iter().zip(IQ3_XXS_GOLDEN.iter()).enumerate() {
6892            assert!(
6893                (a - b).abs() < 1e-3,
6894                "IQ3_XXS element {i}: rust={a} python={b}"
6895            );
6896        }
6897    }
6898
6899    #[test]
6900    fn iq_lowbit_fused_dots_match_dequant_then_dot() {
6901        type DequantFn = fn(&[u8]) -> Result<Vec<f32>, QuantError>;
6902        type DotFn = fn(&[u8], &[f32]) -> f32;
6903        let x: Vec<f32> = (0..1024).map(|i| ((i as f32) * 0.027).sin()).collect();
6904        let cases: [(&[u8], usize, DequantFn, DotFn); 3] = [
6905            (&IQ1_S_TEST_BLOCK, 4, dequant_iq1_s, dot_iq1_s_f32),
6906            (&IQ2_XXS_TEST_BLOCK, 4, dequant_iq2_xxs, dot_iq2_xxs_f32),
6907            (&IQ3_XXS_TEST_BLOCK, 4, dequant_iq3_xxs, dot_iq3_xxs_f32),
6908        ];
6909        for (block, n, dequant, dot) in cases {
6910            let packed = repeat_block(block, n);
6911            let dequanted = dequant(&packed).unwrap();
6912            let expected: f32 = dequanted.iter().zip(x.iter()).map(|(a, b)| a * b).sum();
6913            let fused = dot(&packed, &x[..dequanted.len()]);
6914            assert!(
6915                (fused - expected).abs() < 1e-2,
6916                "fused={fused} expected={expected}"
6917            );
6918        }
6919    }
6920
6921    /// Direct AVX2-vs-scalar comparison for the three IQ kernels on
6922    /// many random blocks (fully random codes/signs/scales, `d`
6923    /// pinned non-NaN) -- run on real x86_64 hardware, not just the
6924    /// committed golden block.
6925    #[cfg(target_arch = "x86_64")]
6926    #[test]
6927    fn avx2_iq_kernels_match_scalar_directly_on_random_blocks() {
6928        if !(is_x86_feature_detected!("avx2") && is_x86_feature_detected!("fma")) {
6929            eprintln!("skipping: host CPU lacks AVX2+FMA");
6930            return;
6931        }
6932        type ScalarFn = fn(&[u8], &[f32]) -> f32;
6933        type Avx2Fn = unsafe fn(&[u8], &[f32]) -> f32;
6934        let cases: [(&str, usize, ScalarFn, Avx2Fn); 3] = [
6935            (
6936                "iq1_s",
6937                IQ1_S_BLOCK_BYTES,
6938                dot_iq1_s_f32_scalar,
6939                simd_x86::dot_iq1_s_f32_avx2,
6940            ),
6941            (
6942                "iq2_xxs",
6943                IQ2_XXS_BLOCK_BYTES,
6944                dot_iq2_xxs_f32_scalar,
6945                simd_x86::dot_iq2_xxs_f32_avx2,
6946            ),
6947            (
6948                "iq3_xxs",
6949                IQ3_XXS_BLOCK_BYTES,
6950                dot_iq3_xxs_f32_scalar,
6951                simd_x86::dot_iq3_xxs_f32_avx2,
6952            ),
6953        ];
6954        for (name, block_bytes, scalar, avx2) in cases {
6955            for trial in 0..16u32 {
6956                let n_blocks = 3;
6957                let mut bytes =
6958                    pseudo_random_bytes(trial.wrapping_mul(97) + 5, n_blocks * block_bytes);
6959                for b in 0..n_blocks {
6960                    // pin each block's f16 `d` to a safe small value
6961                    let d = half::f16::from_f32(0.05 + 0.01 * trial as f32).to_le_bytes();
6962                    bytes[b * block_bytes] = d[0];
6963                    bytes[b * block_bytes + 1] = d[1];
6964                }
6965                let x: Vec<f32> = (0..n_blocks * 256)
6966                    .map(|i| ((i as f32) * 0.017 + trial as f32).sin())
6967                    .collect();
6968                let s = scalar(&bytes, &x);
6969                let v = unsafe { avx2(&bytes, &x) };
6970                // Tolerance covers accumulation-order drift only (the
6971                // 8-lane FMA sums in a different order than scalar,
6972                // over per-term magnitudes up to ~100 here); any real
6973                // decode bug -- wrong grid row, sign, or scale --
6974                // shifts the result by orders of magnitude more than
6975                // this on random codes.
6976                let tol = 2e-3_f32.max(s.abs() * 1e-3);
6977                assert!(
6978                    (s - v).abs() < tol,
6979                    "{name} trial {trial}: scalar={s} avx2={v}"
6980                );
6981            }
6982        }
6983    }
6984
6985    // Only called from `avx2_iq_kernels_match_scalar_directly_on_random_blocks`,
6986    // which is itself `#[cfg(target_arch = "x86_64")]` -- this must carry
6987    // the same gate or it's dead code (and fails `-D warnings`) on
6988    // non-x86_64 hosts (e.g. aarch64 Apple Silicon).
6989    #[cfg(target_arch = "x86_64")]
6990    fn pseudo_random_bytes(seed: u32, len: usize) -> Vec<u8> {
6991        let mut state = seed.wrapping_mul(2654435761).wrapping_add(1);
6992        (0..len)
6993            .map(|_| {
6994                state = state.wrapping_mul(1664525).wrapping_add(1013904223);
6995                (state >> 16) as u8
6996            })
6997            .collect()
6998    }
6999
7000    #[test]
7001    fn iq_lowbit_dequant_rejects_misaligned_buffers() {
7002        let bad = vec![0u8; 7];
7003        assert!(dequant_iq1_s(&bad).is_err());
7004        assert!(dequant_iq2_xxs(&bad).is_err());
7005        assert!(dequant_iq3_xxs(&bad).is_err());
7006        assert!(dequant_iq2_xs(&bad).is_err());
7007        assert!(dequant_iq2_s(&bad).is_err());
7008        assert!(dequant_iq3_s(&bad).is_err());
7009        assert!(dequant_iq1_m(&bad).is_err());
7010    }
7011
7012    /// IQ2_XS / IQ2_S / IQ3_S / IQ1_M against the **real compiled ggml
7013    /// dequantizers**, not a second reading of the spec.
7014    ///
7015    /// This is the whole job for these four formats. They are codebook
7016    /// formats: a wrong grid index, a swapped scale nibble or an
7017    /// off-by-one in the sign unpack does not produce obviously broken
7018    /// numbers, it produces other plausible numbers out of the same
7019    /// codebook. So the goldens in `iq_tier_goldens` are ggml's own
7020    /// output (see that module's header for how they were produced and
7021    /// why those particular blocks), and the comparison is **exact** --
7022    /// every arithmetic step here is expressible in f32 without
7023    /// reassociation, so any difference at all is a decode bug, not
7024    /// rounding.
7025    #[test]
7026    fn iq_tier_dequant_matches_real_ggml_exactly() {
7027        type DequantFn = fn(&[u8]) -> Result<Vec<f32>, QuantError>;
7028        let cases: [(&str, &[u8], &[f32], DequantFn); 4] = [
7029            (
7030                "IQ2_XS",
7031                &iq_tier_goldens::IQ2_XS_TEST_BLOCKS,
7032                &iq_tier_goldens::IQ2_XS_GOLDEN,
7033                dequant_iq2_xs,
7034            ),
7035            (
7036                "IQ2_S",
7037                &iq_tier_goldens::IQ2_S_TEST_BLOCKS,
7038                &iq_tier_goldens::IQ2_S_GOLDEN,
7039                dequant_iq2_s,
7040            ),
7041            (
7042                "IQ3_S",
7043                &iq_tier_goldens::IQ3_S_TEST_BLOCKS,
7044                &iq_tier_goldens::IQ3_S_GOLDEN,
7045                dequant_iq3_s,
7046            ),
7047            (
7048                "IQ1_M",
7049                &iq_tier_goldens::IQ1_M_TEST_BLOCKS,
7050                &iq_tier_goldens::IQ1_M_GOLDEN,
7051                dequant_iq1_m,
7052            ),
7053        ];
7054        for (name, blocks, golden, dequant) in cases {
7055            let got = dequant(blocks).unwrap();
7056            assert_eq!(got.len(), golden.len(), "{name}: element count");
7057            for (i, (a, b)) in got.iter().zip(golden.iter()).enumerate() {
7058                assert_eq!(
7059                    a.to_bits(),
7060                    b.to_bits(),
7061                    "{name} element {i} (block {}, offset {}): rust={a} ggml={b}",
7062                    i / 256,
7063                    i % 256
7064                );
7065            }
7066        }
7067    }
7068
7069    /// The saturated first block of each fixture is the one that pins
7070    /// the *high* end of every packed field, so spell out what it is
7071    /// asserting: with every byte 0xff, each format must reach its
7072    /// maximum grid index -- the single most likely thing to get wrong
7073    /// when a format widens its index by stealing bits from `qh`.
7074    ///
7075    /// Derived here from the grid tables directly, so this test fails
7076    /// even if the golden fixture were regenerated from a broken
7077    /// harness.
7078    #[test]
7079    fn iq_tier_all_ones_block_reaches_the_maximum_grid_index() {
7080        // IQ2_XS: code = 0xffff -> grid index 511 (the top of a 512-row
7081        // grid), sign index 127 -> ksigns 255 -> every element negative.
7082        // Scale nibble 15 -> db = d * (0.5 + 15) * 0.25.
7083        let d = f16::from_le_bytes([
7084            iq_tier_goldens::IQ2_XS_TEST_BLOCKS[0],
7085            iq_tier_goldens::IQ2_XS_TEST_BLOCKS[1],
7086        ])
7087        .to_f32();
7088        let mag = (iq_tables::IQ2XS_GRID[511] & 0xFF) as f32;
7089        assert_eq!(
7090            iq_tier_goldens::IQ2_XS_GOLDEN[0],
7091            -(d * (0.5 + 15.0) * 0.25) * mag
7092        );
7093
7094        // IQ2_S: qs byte 0xff plus 2 high bits from qh -> grid index
7095        // 1023, the top of a 1024-row grid; sign byte 0xff.
7096        let d = f16::from_le_bytes([
7097            iq_tier_goldens::IQ2_S_TEST_BLOCKS[0],
7098            iq_tier_goldens::IQ2_S_TEST_BLOCKS[1],
7099        ])
7100        .to_f32();
7101        let mag = (iq_tables::IQ2S_GRID[1023] & 0xFF) as f32;
7102        assert_eq!(
7103            iq_tier_goldens::IQ2_S_GOLDEN[0],
7104            -(d * (0.5 + 15.0) * 0.25) * mag
7105        );
7106
7107        // IQ3_S: qs byte 0xff plus the 9th bit from qh -> grid index
7108        // 511; scale nibble 15 -> db = d * (1 + 2*15) = 31*d.
7109        let d = f16::from_le_bytes([
7110            iq_tier_goldens::IQ3_S_TEST_BLOCKS[0],
7111            iq_tier_goldens::IQ3_S_TEST_BLOCKS[1],
7112        ])
7113        .to_f32();
7114        let mag = (iq_tables::IQ3S_GRID[511] & 0xFF) as f32;
7115        assert_eq!(iq_tier_goldens::IQ3_S_GOLDEN[0], -(d * 31.0) * mag);
7116
7117        // IQ1_M: qs byte 0xff plus 3 high bits from qh -> grid index
7118        // 2047, the top of the shared 2048-row IQ1 grid. Its scale is
7119        // the f16 reassembled from the scale words' top nibbles, and
7120        // its sub-scale nibble is 7 -> 2*7+1 = 15. The grid values are
7121        // *signed*, and qh bit 3 is set so delta is negative.
7122        let sc: [u16; 4] = std::array::from_fn(|k| {
7123            u16::from_le_bytes([
7124                iq_tier_goldens::IQ1_M_TEST_BLOCKS[48 + 2 * k],
7125                iq_tier_goldens::IQ1_M_TEST_BLOCKS[48 + 2 * k + 1],
7126            ])
7127        });
7128        let d = f16::from_bits(
7129            (sc[0] >> 12) | ((sc[1] >> 8) & 0x00F0) | ((sc[2] >> 4) & 0x0F00) | (sc[3] & 0xF000),
7130        )
7131        .to_f32();
7132        let v = (iq_tables::IQ1S_GRID[2047] & 0xFF) as u8 as i8;
7133        assert_eq!(
7134            iq_tier_goldens::IQ1_M_GOLDEN[0],
7135            d * 15.0 * (v as f32 - IQ1S_DELTA)
7136        );
7137    }
7138
7139    /// The fused dots for the new tier must agree with dequant-then-dot
7140    /// on the same bytes -- the same invariant
7141    /// `iq_lowbit_fused_dots_match_dequant_then_dot` pins for the older
7142    /// formats, restated here because these four share only the macro,
7143    /// not the walk.
7144    #[test]
7145    fn iq_tier_fused_dots_match_dequant_then_dot() {
7146        type DequantFn = fn(&[u8]) -> Result<Vec<f32>, QuantError>;
7147        type DotFn = fn(&[u8], &[f32]) -> f32;
7148        let x: Vec<f32> = (0..1024).map(|i| ((i as f32) * 0.031).cos()).collect();
7149        let cases: [(&str, &[u8], DequantFn, DotFn); 4] = [
7150            (
7151                "IQ2_XS",
7152                &iq_tier_goldens::IQ2_XS_TEST_BLOCKS,
7153                dequant_iq2_xs,
7154                dot_iq2_xs_f32,
7155            ),
7156            (
7157                "IQ2_S",
7158                &iq_tier_goldens::IQ2_S_TEST_BLOCKS,
7159                dequant_iq2_s,
7160                dot_iq2_s_f32,
7161            ),
7162            (
7163                "IQ3_S",
7164                &iq_tier_goldens::IQ3_S_TEST_BLOCKS,
7165                dequant_iq3_s,
7166                dot_iq3_s_f32,
7167            ),
7168            (
7169                "IQ1_M",
7170                &iq_tier_goldens::IQ1_M_TEST_BLOCKS,
7171                dequant_iq1_m,
7172                dot_iq1_m_f32,
7173            ),
7174        ];
7175        for (name, blocks, dequant, dot) in cases {
7176            let dequanted = dequant(blocks).unwrap();
7177            let expected: f32 = dequanted.iter().zip(x.iter()).map(|(a, b)| a * b).sum();
7178            let fused = dot(blocks, &x[..dequanted.len()]);
7179            assert!(
7180                (fused - expected).abs() <= expected.abs() * 1e-5 + 1e-3,
7181                "{name}: fused={fused} expected={expected}"
7182            );
7183        }
7184    }
7185
7186    // Generated by an independent Python reference -- do not hand-edit.
7187    // 4 GGUF-block-MXFP4 blocks with distinct pinned E8M0 scale bytes;
7188    // the Python reference is cross-validated against the real compiled
7189    // ggml implementation across the FULL random E8M0 range (including
7190    // the e<2 denormal patterns).
7191    const MXFP4_GGUF_TEST_BLOCKS: [u8; 68] = [
7192        0x79, 0xb4, 0x8d, 0xe2, 0x62, 0x5d, 0xbb, 0x9d, 0x54, 0xe6, 0xdb, 0x94, 0x59, 0x7d, 0x28,
7193        0xf9, 0x79, 0x7a, 0xfc, 0xc1, 0xfa, 0x1e, 0x53, 0x5b, 0x0e, 0xc2, 0x5a, 0x2f, 0x0c, 0x82,
7194        0x4d, 0xcb, 0x11, 0x28, 0x7b, 0x7c, 0xb6, 0x45, 0xe0, 0xb0, 0x52, 0x40, 0x51, 0xec, 0x30,
7195        0x1a, 0xd2, 0x17, 0xf3, 0xbb, 0xfc, 0x7c, 0x8f, 0xf0, 0x67, 0x83, 0x88, 0x9d, 0x79, 0xdb,
7196        0xf4, 0x45, 0x29, 0x78, 0xe6, 0xf4, 0x99, 0xea,
7197    ];
7198    const MXFP4_GGUF_GOLDEN: [f32; 128] = [
7199        0.03125, -0.046875, 0.015625, 0.015625, -0.046875, -0.0234375, -0.046875, 0.03125, 0.0625,
7200        -0.0234375, 0.03125, -0.0078125, -0.046875, 0.0, -0.0078125, -0.0078125, -0.0234375, 0.0,
7201        -0.0625, 0.0625, 0.046875, -0.0234375, -0.0078125, 0.046875, -0.0625, -0.046875,
7202        -0.0078125, 0.046875, 0.09375, 0.015625, -0.09375, 0.09375, -0.0625, 0.015625, -0.03125,
7203        -0.125, 0.046875, -0.046875, -0.125, 0.03125, -0.03125, -0.1875, -0.0625, 0.03125,
7204        -0.09375, -0.046875, 0.015625, 0.0, -0.1875, -0.0625, -0.1875, 0.015625, 0.09375, 0.09375,
7205        0.0, -0.0625, 0.09375, 0.03125, 0.0, 0.0, 0.0625, -0.0625, 0.015625, 0.03125, -0.125, 0.25,
7206        0.1875, 0.0, 0.0, 0.0625, 0.0, 0.03125, -0.125, 0.0, -0.0625, 0.0625, 0.375, 0.09375,
7207        -0.09375, -0.125, 0.375, -0.09375, 0.125, -0.25, -0.09375, 0.1875, 0.125, 0.1875, -0.25,
7208        0.09375, 0.03125, -0.1875, 0.03125, -0.375, -0.09375, -0.375, -0.75, 0.0, 0.75, 0.1875,
7209        0.0, -0.375, -0.0625, -0.1875, 0.25, 0.375, -0.0625, 0.0, 0.5, 0.25, -0.0625, -0.125, 0.0,
7210        -0.75, 0.5, 0.0, 0.0, -0.0625, 0.75, -0.375, -0.75, 0.25, 0.125, 0.75, -0.5, -0.75,
7211        -0.0625, -0.5,
7212    ];
7213
7214    #[test]
7215    fn mxfp4_gguf_dequant_matches_independent_python_reference() {
7216        let got = dequant_mxfp4_gguf(&MXFP4_GGUF_TEST_BLOCKS).unwrap();
7217        assert_eq!(got.len(), MXFP4_GGUF_GOLDEN.len());
7218        for (i, (a, b)) in got.iter().zip(MXFP4_GGUF_GOLDEN.iter()).enumerate() {
7219            assert!(
7220                (a - b).abs() < 1e-3,
7221                "MXFP4-GGUF element {i}: rust={a} python={b}"
7222            );
7223        }
7224    }
7225
7226    #[test]
7227    fn mxfp4_gguf_fused_dot_matches_dequant_then_dot() {
7228        let dequanted = dequant_mxfp4_gguf(&MXFP4_GGUF_TEST_BLOCKS).unwrap();
7229        let x: Vec<f32> = (0..128).map(|i| ((i as f32) * 0.031).cos()).collect();
7230        let expected: f32 = dequanted.iter().zip(x.iter()).map(|(a, b)| a * b).sum();
7231        let fused = dot_mxfp4_gguf_f32(&MXFP4_GGUF_TEST_BLOCKS, &x);
7232        assert!(
7233            (fused - expected).abs() < 1e-2,
7234            "fused={fused} expected={expected}"
7235        );
7236    }
7237
7238    /// The GGUF block form and the Kimi two-buffer form are the same
7239    /// math in different byte layouts -- deinterleaving a block row
7240    /// into (packed, scales) buffers and running the two-buffer kernel
7241    /// must produce the same result.
7242    #[test]
7243    fn mxfp4_gguf_block_form_agrees_with_two_buffer_form() {
7244        let mut packed = Vec::new();
7245        let mut scales = Vec::new();
7246        for block in MXFP4_GGUF_TEST_BLOCKS
7247            .as_chunks::<MXFP4_GGUF_BLOCK_BYTES>()
7248            .0
7249        {
7250            scales.push(block[0]);
7251            packed.extend_from_slice(&block[1..17]);
7252        }
7253        let x: Vec<f32> = (0..128).map(|i| ((i as f32) * 0.019).sin()).collect();
7254        let a = dot_mxfp4_gguf_f32(&MXFP4_GGUF_TEST_BLOCKS, &x);
7255        let b = dot_mxfp4_row_f32(&packed, &scales, &x);
7256        assert!((a - b).abs() < 1e-4, "block={a} two-buffer={b}");
7257    }
7258
7259    // Generated by an independent Python reference -- do not hand-edit.
7260    // Q6_K block whose int8 sub-block scales include *negative* values
7261    // (9 of 16 in this draw). Q6_K is the only K-quant whose sub-block
7262    // scales are signed; every other Q6_K golden in this file happens
7263    // to have all-positive scales, which is exactly why a scalar path
7264    // that read them as unsigned passed all of those tests while
7265    // disagreeing with the format (and with the AVX2/NEON kernels) on
7266    // real checkpoints.
7267    const Q6_K_SIGNED_TEST_BLOCK: [u8; 210] = [
7268        0x10, 0x5b, 0x5f, 0x45, 0x4a, 0xa0, 0x3f, 0x10, 0xf2, 0x7f, 0xdd, 0xf5, 0x25, 0x03, 0xc3,
7269        0x12, 0x74, 0xe1, 0x4e, 0x42, 0xf1, 0x04, 0xe1, 0xad, 0xc6, 0x55, 0x59, 0x4b, 0x5a, 0xfc,
7270        0xf5, 0x3f, 0xc5, 0x0b, 0xac, 0x7b, 0x4c, 0xd4, 0x19, 0xa6, 0x27, 0xdd, 0xf4, 0x7d, 0x9c,
7271        0xfc, 0x03, 0xd2, 0x5f, 0xe3, 0xff, 0x9c, 0xa6, 0x74, 0xa0, 0xe1, 0xbe, 0xf0, 0x26, 0xdb,
7272        0x4b, 0x23, 0xa0, 0xbc, 0xb1, 0x94, 0xd7, 0x7e, 0xcf, 0xf7, 0x97, 0xb4, 0xac, 0x1f, 0xb1,
7273        0x9f, 0xb7, 0xbe, 0xa3, 0xb5, 0xd2, 0xd4, 0x6d, 0x9c, 0x3d, 0xf3, 0x5f, 0x0e, 0x64, 0xbf,
7274        0x54, 0x40, 0xc8, 0xef, 0x9d, 0xc3, 0xf3, 0x4c, 0xb0, 0xf8, 0x54, 0xcf, 0xf3, 0x12, 0xcc,
7275        0x2f, 0x0c, 0xee, 0xab, 0x5d, 0x8d, 0x0b, 0x19, 0xb2, 0x99, 0xbd, 0x4a, 0xec, 0x04, 0xb3,
7276        0xf6, 0xc1, 0xb9, 0xf8, 0x1d, 0xfe, 0x51, 0xea, 0x99, 0xe5, 0x75, 0x5b, 0x98, 0x28, 0x05,
7277        0x18, 0x8a, 0x9f, 0xda, 0xb7, 0xb6, 0xe5, 0x5b, 0x3a, 0x52, 0x49, 0xcc, 0x72, 0xff, 0x61,
7278        0x91, 0x95, 0xa2, 0xa1, 0x5d, 0xd5, 0xc4, 0x7d, 0xb1, 0x0b, 0xda, 0xa9, 0xa2, 0x97, 0x1e,
7279        0x7e, 0xe9, 0xa2, 0xd6, 0xdd, 0x0e, 0x94, 0x21, 0xa4, 0x67, 0x92, 0xad, 0x46, 0xab, 0xe1,
7280        0xe2, 0x3b, 0x21, 0x69, 0x2a, 0x1e, 0xd3, 0xea, 0xa4, 0xdf, 0xa6, 0xd2, 0xff, 0x01, 0xfe,
7281        0xff, 0x01, 0xff, 0x01, 0x01, 0x02, 0xff, 0xff, 0x01, 0xfe, 0x02, 0x01, 0xff, 0x1f, 0x25,
7282    ];
7283    const Q6_K_SIGNED_GOLDEN: [f32; 256] = [
7284        0.320068, 0.100021, 0.0200043, -0.42009, 0.440094, 0.640137, 0.0200043, 0.640137,
7285        -0.0400085, -0.620132, -0.260056, -0.42009, -0.100021, 0.260056, -0.380081, -0.0400085,
7286        0.0800171, -0.300064, -0.360077, 0.0400085, 0.340073, -0.240051, -0.300064, -0.0600128,
7287        0.120026, -0.220047, -0.14003, -0.100021, -0.440094, -0.0800171, -0.220047, 0.620132,
7288        -0.200043, 0.200043, 0.160034, -0.440094, -0.480103, -0.160034, 0.28006, -0.240051,
7289        -0.28006, -1.16025, -0.160034, 0.120026, 0.160034, 0.160034, -0.120026, -0.0800171,
7290        0.340073, -0.0600128, -0.620132, 0.400085, -0.440094, 0.56012, 0.640137, 0.300064,
7291        0.360077, 0.640137, -0.440094, 0.100021, 0.100021, -0.380081, 0.640137, -0.240051,
7292        -0.300064, 0.100021, 0.42009, -0.240051, -0.240051, 0.200043, -0.580124, -0.300064,
7293        -0.340073, -0.180038, -0.0600128, 0.620132, 0.360077, 0.0, -0.0800171, 0.340073, 0.180038,
7294        0.360077, 0.56012, -0.400085, -0.620132, -0.0, 0.0400085, 0.120026, -0.240051, -0.100021,
7295        0.220047, 0.240051, 0.540115, -0.620132, -0.620132, 0.580124, 0.240051, 0.320068,
7296        -0.120026, -0.180038, 0.0800171, -0.380081, -0.620132, -0.440094, 0.0400085, 0.260056,
7297        0.620132, 0.14003, 0.180038, 0.620132, -0.320068, -0.380081, -0.220047, -0.0400085,
7298        0.620132, -0.14003, 0.520111, -0.180038, 0.200043, 0.28006, 0.220047, 0.300064, -0.28006,
7299        0.580124, 0.400085, -0.28006, 0.200043, -0.42009, 0.0400085, -0.480103, 0.28006, 1.20026,
7300        0.600128, 0.28006, -0.360077, 0.160034, 0.480103, -0.0400085, 0.0400085, -0.680145,
7301        -0.360077, -0.720154, 0.760162, 0.200043, 0.28006, -0.0800171, -0.580124, 0.0800171,
7302        -0.260056, -0.380081, 0.0200043, 0.0400085, -0.0800171, -0.300064, -0.400085, -0.0,
7303        0.480103, -0.620132, -0.260056, -0.0600128, -0.0600128, -0.240051, 0.640137, 0.160034,
7304        -0.400085, -0.620132, -0.0600128, 0.600128, 0.0800171, -0.620132, -0.56012, 0.0400085,
7305        0.42009, 0.0600128, 0.0600128, 0.42009, 0.500107, -0.28006, 0.180038, -0.380081, -0.440094,
7306        0.240051, -0.56012, 0.0600128, 0.120026, 0.340073, -0.460098, 0.160034, -0.0600128,
7307        0.600128, -0.300064, -0.440094, 0.200043, -0.360077, -0.520111, 0.360077, 0.160034,
7308        -1.24026, -0.360077, -0.440094, 0.240051, 0.600128, 0.840179, 0.28006, -0.440094,
7309        -0.440094, -0.400085, 0.200043, 0.520111, -0.760162, 0.240051, 0.360077, 0.120026, 1.24026,
7310        0.200043, 0.0, 0.240051, -0.200043, -0.440094, 0.160034, 0.480103, -0.0800171, 0.360077,
7311        -0.160034, 0.620132, 0.0800171, 0.220047, 0.300064, -0.540115, -0.0800171, 0.620132,
7312        0.0200043, 0.56012, 0.360077, -0.640137, 0.28006, -0.440094, 0.100021, -0.160034, 0.0,
7313        -0.0200043, 0.100021, -0.180038, -0.540115, -0.400085, 0.360077, 0.640137, 0.100021,
7314        0.340073, 0.400085, -0.540115, -0.620132, -0.0200043, -0.620132, -0.100021, -0.600128,
7315    ];
7316
7317    #[test]
7318    fn q6_k_signed_scale_dequant_matches_independent_python_reference() {
7319        let got = dequant_q6_k(&Q6_K_SIGNED_TEST_BLOCK).unwrap();
7320        assert_eq!(got.len(), Q6_K_SIGNED_GOLDEN.len());
7321        for (i, (a, b)) in got.iter().zip(Q6_K_SIGNED_GOLDEN.iter()).enumerate() {
7322            assert!(
7323                (a - b).abs() < 1e-3,
7324                "Q6_K signed-scale element {i}: rust={a} python={b}"
7325            );
7326        }
7327    }
7328
7329    #[test]
7330    fn q6_k_signed_scale_fused_dot_matches_dequant_then_dot() {
7331        let dequanted = dequant_q6_k(&Q6_K_SIGNED_TEST_BLOCK).unwrap();
7332        let x: Vec<f32> = (0..256).map(|i| ((i as f32) * 0.023).sin()).collect();
7333        let expected: f32 = dequanted.iter().zip(x.iter()).map(|(a, b)| a * b).sum();
7334        let fused = dot_q6_k_f32(&Q6_K_SIGNED_TEST_BLOCK, &x);
7335        assert!(
7336            (fused - expected).abs() < 1e-2,
7337            "fused={fused} expected={expected}"
7338        );
7339    }
7340
7341    #[test]
7342    fn dispatched_q6_k_matches_scalar_on_signed_scales() {
7343        // On AVX2/NEON hosts this compares the SIMD kernel (which always
7344        // read the scales as signed) against the scalar path directly on
7345        // a negative-scale block -- the comparison that would have caught
7346        // the scalar path's unsigned-scale bug.
7347        let n_blocks = 4;
7348        let packed = repeat_block(&Q6_K_SIGNED_TEST_BLOCK, n_blocks);
7349        let x: Vec<f32> = (0..256 * n_blocks)
7350            .map(|i| ((i as f32) * 0.019).sin())
7351            .collect();
7352        let dispatched = dot_q6_k_f32(&packed, &x);
7353        let scalar = dot_q6_k_f32_scalar(&packed, &x);
7354        assert!(
7355            (dispatched - scalar).abs() < 1e-1,
7356            "dispatched={dispatched} scalar={scalar}"
7357        );
7358    }
7359
7360    #[test]
7361    fn q6_k_dequant_matches_python_reference_with_negative_scales() {
7362        // Regression test for a real bug: the scalar dequant read the
7363        // signed int8 sub-block scales as unsigned, so any negative
7364        // scale (e.g. -1 -> 255) corrupted its whole sub-block. The
7365        // all-positive-scale fixture above could never catch that.
7366        let got = dequant_q6_k(&Q6_K_SIGNED_SCALES_TEST_BLOCK).unwrap();
7367        assert_eq!(got.len(), Q6_K_SIGNED_SCALES_GOLDEN.len());
7368        for (i, (a, b)) in got.iter().zip(Q6_K_SIGNED_SCALES_GOLDEN.iter()).enumerate() {
7369            assert!(
7370                (a - b).abs() < 1e-3,
7371                "Q6_K signed-scale element {i}: rust={a} python={b}"
7372            );
7373        }
7374    }
7375
7376    #[test]
7377    fn q6_k_fused_dot_matches_dequant_then_dot_with_negative_scales() {
7378        let dequanted = dequant_q6_k(&Q6_K_SIGNED_SCALES_TEST_BLOCK).unwrap();
7379        let x: Vec<f32> = (0..256).map(|i| ((i as f32) * 0.023).sin()).collect();
7380        let expected: f32 = dequanted.iter().zip(x.iter()).map(|(a, b)| a * b).sum();
7381        let fused = dot_q6_k_f32(&Q6_K_SIGNED_SCALES_TEST_BLOCK, &x);
7382        assert!(
7383            (fused - expected).abs() < 1e-2,
7384            "fused={fused} expected={expected}"
7385        );
7386    }
7387
7388    #[test]
7389    fn q6_k_scalar_dot_matches_python_reference_with_negative_scales() {
7390        // Pins the *scalar* path specifically (not whatever SIMD path
7391        // `dot_q6_k_f32` dispatches to on this host) against the
7392        // independent Python golden, so scalar/SIMD can never again
7393        // disagree on scale signedness without a test failing.
7394        let x: Vec<f32> = (0..256).map(|i| ((i as f32) * 0.023).sin()).collect();
7395        let expected: f32 = Q6_K_SIGNED_SCALES_GOLDEN
7396            .iter()
7397            .zip(x.iter())
7398            .map(|(a, b)| a * b)
7399            .sum();
7400        let scalar = dot_q6_k_f32_scalar(&Q6_K_SIGNED_SCALES_TEST_BLOCK, &x);
7401        assert!(
7402            (scalar - expected).abs() < 1e-2,
7403            "scalar={scalar} expected={expected}"
7404        );
7405    }
7406
7407    #[test]
7408    fn q4_k_and_q6_k_reject_misaligned_buffers() {
7409        let bad = vec![0u8; 5];
7410        assert!(dequant_q4_k(&bad).is_err());
7411        assert!(dequant_q6_k(&bad).is_err());
7412    }
7413
7414    // Generated by an independent Python reference -- do not hand-edit.
7415    // Random-but-well-formed block bytes (d/dmin/d_all pinned to
7416    // realistic small scales to keep golden values readable and avoid
7417    // any risk of an f16 NaN/Inf bit pattern; scales/qs/hmask/qh fully
7418    // random) cross-validated against an independent Python
7419    // dequantizer written from the same public layout description.
7420    const Q2_K_TEST_BLOCK: [u8; 84] = [
7421        0x92, 0x32, 0xc9, 0x0e, 0x0f, 0xf8, 0x10, 0xf0, 0xd1, 0x82, 0xca, 0x81, 0x7f, 0x11, 0xdb,
7422        0xff, 0x78, 0xf8, 0xab, 0xc5, 0x60, 0x0c, 0xc0, 0xbc, 0xa6, 0x52, 0x56, 0x1b, 0xc0, 0x36,
7423        0x6b, 0x6e, 0xbb, 0x53, 0x32, 0x90, 0x0a, 0x41, 0x67, 0x97, 0x48, 0x76, 0x86, 0x23, 0xd5,
7424        0x8e, 0x9e, 0x02, 0xc1, 0x1b, 0xea, 0x9c, 0xb7, 0x55, 0xc3, 0x1b, 0xf4, 0x59, 0xc6, 0xef,
7425        0x11, 0x61, 0xbc, 0x54, 0xd7, 0x8a, 0x6d, 0xed, 0x9e, 0xe7, 0x48, 0x69, 0x8e, 0x3a, 0x30,
7426        0x6c, 0xd8, 0xdc, 0x85, 0xc1, 0xec, 0x35, 0x14, 0x32,
7427    ];
7428    const Q2_K_GOLDEN: [f32; 256] = [
7429        -1.70947, -1.70947, 0.51123, -0.969238, -1.70947, -1.70947, -1.70947, -1.70947, -0.229004,
7430        -0.229004, -0.229004, 0.51123, -1.70947, -0.229004, 0.51123, -0.229004, 1.65088, 1.65088,
7431        0.910645, -0.569824, 0.910645, 0.17041, 1.65088, 1.65088, -0.569824, 0.910645, 0.910645,
7432        1.65088, 0.17041, 0.910645, 0.910645, 0.910645, 4.38281, 4.38281, 4.38281, 1.05176,
7433        -2.2793, 7.71387, -2.2793, 7.71387, 1.05176, -2.2793, 1.05176, 4.38281, -2.2793, 1.05176,
7434        4.38281, 7.71387, 10.3633, 0.0, 0.0, 0.0, 10.3633, 0.0, 5.18164, 5.18164, 10.3633, 5.18164,
7435        5.18164, 0.0, 5.18164, 15.5449, 15.5449, 0.0, 16.6553, 16.6553, 11.1035, 0.0, 11.1035, 0.0,
7436        0.0, 16.6553, 11.1035, 5.55176, 5.55176, 5.55176, 0.0, 16.6553, 11.1035, 11.1035, 6.03369,
7437        0.111816, 6.03369, 0.111816, -2.84912, -2.84912, 3.07275, 0.111816, -2.84912, 6.03369,
7438        -2.84912, 3.07275, 0.111816, -2.84912, 0.111816, -2.84912, -0.189941, -0.189941, -0.189941,
7439        -0.189941, -0.189941, -0.189941, -0.189941, -0.189941, -0.189941, -0.189941, -0.189941,
7440        -0.189941, -0.189941, -0.189941, -0.189941, -0.189941, -2.84912, -2.84912, -2.84912,
7441        -2.84912, -2.84912, -2.84912, -2.84912, -2.84912, -2.84912, -2.84912, -2.84912, -2.84912,
7442        -2.84912, -2.84912, -2.84912, -2.84912, -2.09912, -1.35889, -1.729, -2.46924, -1.35889,
7443        -2.09912, -1.35889, -1.35889, -2.46924, -2.09912, -1.729, -1.35889, -2.09912, -2.09912,
7444        -2.46924, -2.46924, 0.701172, -0.0390625, -0.779297, -0.779297, -0.0390625, 0.701172,
7445        -1.51953, -0.779297, -0.0390625, -0.0390625, -1.51953, -1.51953, -1.51953, -1.51953,
7446        -0.779297, -0.779297, -2.2793, 5.12305, 5.12305, 8.82422, 1.42188, 1.42188, -2.2793,
7447        5.12305, 1.42188, 5.12305, 1.42188, 8.82422, -2.2793, -2.2793, 8.82422, 1.42188, -1.14941,
7448        -0.779297, -0.40918, -0.40918, -0.40918, -1.14941, -0.779297, -0.779297, -0.40918,
7449        -0.779297, -1.51953, -0.40918, -0.779297, -0.40918, -1.14941, -1.51953, -1.32959, 4.22217,
7450        9.77393, 4.22217, 15.3257, 4.22217, -1.32959, 4.22217, 15.3257, 4.22217, -1.32959, 9.77393,
7451        4.22217, 9.77393, 15.3257, 4.22217, 0.180176, -0.189941, 0.550293, 0.550293, 0.180176,
7452        0.550293, -0.189941, 0.550293, -0.189941, 0.92041, 0.92041, 0.550293, 0.180176, 0.180176,
7453        -0.189941, -0.189941, 9.74463, -2.46924, 9.74463, 5.67334, 5.67334, 1.60205, 9.74463,
7454        -2.46924, 9.74463, 1.60205, 9.74463, 9.74463, -2.46924, 1.60205, 5.67334, 1.60205, 13.8062,
7455        8.25439, 2.70264, 13.8062, 8.25439, 13.8062, 2.70264, 2.70264, 8.25439, -2.84912, -2.84912,
7456        2.70264, 13.8062, 13.8062, 8.25439, 13.8062,
7457    ];
7458
7459    const Q3_K_TEST_BLOCK: [u8; 110] = [
7460        0x56, 0xf2, 0xb4, 0x2b, 0xd5, 0x6f, 0x51, 0x71, 0x3c, 0x0a, 0xb9, 0x1d, 0xd0, 0xb9, 0x3b,
7461        0xb3, 0x0f, 0xff, 0x8c, 0xb2, 0x83, 0x3a, 0x3d, 0x24, 0xb1, 0x12, 0x56, 0xe3, 0x23, 0x54,
7462        0xf2, 0xfa, 0x7f, 0xdf, 0x31, 0xe1, 0x18, 0x26, 0x6e, 0xcd, 0x5b, 0x38, 0xee, 0xbd, 0x9f,
7463        0x8c, 0x57, 0x47, 0x0b, 0x11, 0xcb, 0xfb, 0xb4, 0x83, 0xa0, 0x4e, 0x0b, 0xd4, 0xa7, 0x85,
7464        0xe0, 0x60, 0xf3, 0xb3, 0xe3, 0x95, 0x43, 0xc6, 0x05, 0x05, 0x77, 0x53, 0xed, 0x23, 0xcc,
7465        0x6a, 0x0e, 0x89, 0xa1, 0x79, 0x85, 0xf6, 0x6e, 0x5a, 0x23, 0x63, 0xbe, 0x53, 0xfa, 0xa2,
7466        0x2b, 0xe9, 0xcd, 0xce, 0xf8, 0x3d, 0x6f, 0xd0, 0x42, 0x6e, 0x3b, 0x7f, 0x23, 0x26, 0xd3,
7467        0xb9, 0x18, 0xbf, 0xa4, 0x34,
7468    ];
7469    const Q3_K_GOLDEN: [f32; 256] = [
7470        -8.99121, -8.99121, -26.9736, 8.99121, 0.0, 17.9824, 17.9824, 8.99121, -8.99121, -35.9648,
7471        17.9824, 8.99121, -8.99121, 0.0, 26.9736, 26.9736, -13.9219, -4.64062, 4.64062, 4.64062,
7472        -0.0, 4.64062, -0.0, 9.28125, -13.9219, 18.5625, 4.64062, -4.64062, -0.0, 18.5625, 4.64062,
7473        4.64062, -26.1035, -26.1035, 34.8047, -0.0, 17.4023, -8.70117, 8.70117, 8.70117, 17.4023,
7474        -17.4023, 8.70117, 8.70117, 8.70117, 8.70117, -8.70117, -8.70117, 17.4023, 0.0, -17.4023,
7475        17.4023, 8.70117, 0.0, -34.8047, -8.70117, -17.4023, 8.70117, 8.70117, 8.70117, 0.0,
7476        -34.8047, 0.0, 0.0, -18.2725, 18.2725, -18.2725, 12.1816, -6.09082, -12.1816, 12.1816,
7477        24.3633, -6.09082, 6.09082, 12.1816, -18.2725, 18.2725, 24.3633, 18.2725, 24.3633, 0.0,
7478        4.35059, 0.0, -4.35059, -4.35059, -17.4023, 8.70117, 0.0, -17.4023, -13.0518, 8.70117,
7479        -17.4023, -8.70117, 8.70117, -4.35059, -4.35059, -2.61035, -0.870117, -3.48047, 2.61035,
7480        -3.48047, 0.0, -2.61035, -0.870117, 0.870117, 0.0, 2.61035, 1.74023, -1.74023, 1.74023,
7481        0.870117, -2.61035, 0.0, 0.0, 19.1426, -6.38086, -12.7617, 12.7617, 12.7617, -19.1426,
7482        -25.5234, -6.38086, -12.7617, -12.7617, -6.38086, -19.1426, -6.38086, 12.7617, -8.70117,
7483        -2.90039, -8.70117, 5.80078, -2.90039, 8.70117, -8.70117, -8.70117, -2.90039, 2.90039,
7484        -0.0, -5.80078, -5.80078, -2.90039, -2.90039, -2.90039, -25.2334, 16.8223, -16.8223,
7485        16.8223, -8.41113, 25.2334, 16.8223, -8.41113, 16.8223, 16.8223, 25.2334, -25.2334,
7486        -25.2334, 16.8223, 0.0, 8.41113, 13.9219, -3.48047, -0.0, -3.48047, 10.4414, -3.48047,
7487        10.4414, -0.0, -10.4414, 13.9219, -10.4414, 6.96094, 3.48047, -6.96094, -0.0, -6.96094,
7488        -19.1426, 6.38086, -6.38086, 12.7617, -25.5234, 0.0, 19.1426, 0.0, 12.7617, -25.5234,
7489        -12.7617, 12.7617, 19.1426, -6.38086, 12.7617, 19.1426, 11.0215, 5.51074, -22.043, -22.043,
7490        0.0, 0.0, 16.5322, 5.51074, -11.0215, -11.0215, -22.043, -11.0215, 0.0, -22.043, -11.0215,
7491        -5.51074, -8.12109, 6.09082, -4.06055, -6.09082, -4.06055, -4.06055, -2.03027, -6.09082,
7492        -2.03027, -4.06055, 4.06055, 4.06055, -8.12109, 0.0, 6.09082, 6.09082, 8.70117, -17.4023,
7493        -8.70117, 8.70117, -0.0, 34.8047, 26.1035, 26.1035, 8.70117, 34.8047, -26.1035, 26.1035,
7494        -0.0, -17.4023, 17.4023, -8.70117, -1.16016, 1.74023, 0.580078, 0.580078, 0.0, -1.74023,
7495        -1.16016, -1.74023, 1.74023, -1.16016, -2.32031, 1.74023, -0.580078, -0.580078, 1.74023,
7496        0.0,
7497    ];
7498
7499    #[test]
7500    fn q2_k_dequant_matches_independent_python_reference() {
7501        let got = dequant_q2_k(&Q2_K_TEST_BLOCK).unwrap();
7502        assert_eq!(got.len(), Q2_K_GOLDEN.len());
7503        for (i, (a, b)) in got.iter().zip(Q2_K_GOLDEN.iter()).enumerate() {
7504            assert!(
7505                (a - b).abs() < 1e-3,
7506                "Q2_K element {i}: rust={a} python={b}"
7507            );
7508        }
7509    }
7510
7511    #[test]
7512    fn q2_k_fused_dot_matches_dequant_then_dot() {
7513        let dequanted = dequant_q2_k(&Q2_K_TEST_BLOCK).unwrap();
7514        let x: Vec<f32> = (0..256).map(|i| ((i as f32) * 0.019).sin()).collect();
7515        let expected: f32 = dequanted.iter().zip(x.iter()).map(|(a, b)| a * b).sum();
7516        let fused = dot_q2_k_f32(&Q2_K_TEST_BLOCK, &x);
7517        assert!(
7518            (fused - expected).abs() < 1e-1,
7519            "fused={fused} expected={expected}"
7520        );
7521    }
7522
7523    #[test]
7524    fn q3_k_dequant_matches_independent_python_reference() {
7525        let got = dequant_q3_k(&Q3_K_TEST_BLOCK).unwrap();
7526        assert_eq!(got.len(), Q3_K_GOLDEN.len());
7527        for (i, (a, b)) in got.iter().zip(Q3_K_GOLDEN.iter()).enumerate() {
7528            assert!(
7529                (a - b).abs() < 1e-3,
7530                "Q3_K element {i}: rust={a} python={b}"
7531            );
7532        }
7533    }
7534
7535    #[test]
7536    fn q3_k_fused_dot_matches_dequant_then_dot() {
7537        let dequanted = dequant_q3_k(&Q3_K_TEST_BLOCK).unwrap();
7538        let x: Vec<f32> = (0..256).map(|i| ((i as f32) * 0.023).cos()).collect();
7539        let expected: f32 = dequanted.iter().zip(x.iter()).map(|(a, b)| a * b).sum();
7540        let fused = dot_q3_k_f32(&Q3_K_TEST_BLOCK, &x);
7541        assert!(
7542            (fused - expected).abs() < 1e-1,
7543            "fused={fused} expected={expected}"
7544        );
7545    }
7546
7547    #[test]
7548    fn q2_k_and_q3_k_reject_misaligned_buffers() {
7549        let bad = vec![0u8; 5];
7550        assert!(dequant_q2_k(&bad).is_err());
7551        assert!(dequant_q3_k(&bad).is_err());
7552    }
7553
7554    // Generated by an independent Python reference -- do not hand-edit.
7555    // Random-but-well-formed block bytes (d pinned to a realistic small
7556    // scale; qs/scales_l/scales_h fully random) cross-validated against
7557    // an independent Python dequantizer written from the same public
7558    // layout description (real ggml-quants.c / ggml-common.h source).
7559    const IQ4_NL_TEST_BLOCK: [u8; 18] = [
7560        0xf6, 0x34, 0x3c, 0x7f, 0x90, 0x6a, 0xdc, 0x0f, 0x77, 0xfc, 0xb9, 0x1c, 0xdf, 0x74, 0xe0,
7561        0x40, 0x5d, 0xf3,
7562    ];
7563    const IQ4_NL_GOLDEN: [f32; 32] = [
7564        16.4331, 35.0366, -39.3774, 7.75146, 16.4331, 35.0366, -3.10059, 16.4331, 4.03076, 16.4331,
7565        35.0366, -15.1929, -39.3774, -39.3774, 21.394, -20.1538, -20.1538, -3.10059, 4.03076,
7566        -6.82129, 21.394, -39.3774, -3.10059, 35.0366, 11.7822, -32.2461, 21.394, -3.10059,
7567        27.5952, -15.1929, -10.8521, 35.0366,
7568    ];
7569
7570    const IQ4_XS_TEST_BLOCK: [u8; 136] = [
7571        0x5c, 0x33, 0xb4, 0x39, 0xd1, 0x64, 0x97, 0x82, 0xcb, 0xbd, 0x88, 0x95, 0xf3, 0x60, 0x2a,
7572        0xb5, 0xe7, 0x24, 0xd3, 0xee, 0xfe, 0x71, 0x13, 0xbe, 0x70, 0x84, 0x48, 0x79, 0x7b, 0x3e,
7573        0xf0, 0x55, 0xdc, 0xb2, 0xb2, 0xde, 0x32, 0xa1, 0x5b, 0x02, 0x01, 0xdc, 0x2a, 0xbb, 0xf7,
7574        0x0b, 0x8a, 0x88, 0xdd, 0x0b, 0x02, 0x7e, 0x5e, 0x76, 0x87, 0x30, 0x1e, 0x1c, 0xcf, 0x48,
7575        0xd7, 0x61, 0xf3, 0x51, 0x52, 0x17, 0x98, 0x0a, 0x87, 0xcf, 0x02, 0x91, 0xc8, 0xee, 0xc0,
7576        0x91, 0x69, 0x2a, 0x4f, 0x64, 0x68, 0xa7, 0xb2, 0xe6, 0x98, 0x21, 0x81, 0x75, 0x53, 0x2a,
7577        0x8d, 0x12, 0xae, 0xe0, 0xea, 0x0c, 0x75, 0xff, 0x22, 0x5e, 0x25, 0x19, 0xda, 0x2e, 0x51,
7578        0x4e, 0x81, 0xdc, 0x0e, 0x78, 0x86, 0xd7, 0x58, 0xb5, 0xb7, 0xf6, 0x45, 0xa9, 0x0a, 0x83,
7579        0xfd, 0x2a, 0x12, 0x7d, 0xf0, 0x12, 0x97, 0xe2, 0xfe, 0xf4, 0xd0, 0xa2, 0x11, 0x14, 0x78,
7580        0xdb,
7581    ];
7582    const IQ4_XS_GOLDEN: [f32; 256] = [
7583        -270.917, -491.928, -7.12939, 249.529, 463.411, 905.433, -178.235, 249.529, 71.2939,
7584        349.34, 463.411, -634.516, -634.516, 741.457, 463.411, -634.516, -377.858, -270.917,
7585        -7.12939, -92.6821, -805.622, 156.847, 591.74, -270.917, -634.516, 591.74, -491.928,
7586        -634.516, -805.622, 71.2939, 741.457, -270.917, 87.6226, 33.8071, -0.689941, -8.96924,
7587        -26.2178, -61.4048, 87.6226, 24.1479, -36.5669, 57.2651, 57.2651, -61.4048, 57.2651,
7588        71.7539, -26.2178, 57.2651, 6.89941, -0.689941, 33.8071, 6.89941, 6.89941, 44.8462,
7589        -77.9634, 24.1479, -47.606, -26.2178, -26.2178, -47.606, 44.8462, -17.2485, 24.1479,
7590        87.6226, -478.359, 243.779, 114.99, 174.785, -45.9961, 174.785, 114.99, 4.59961, 317.373,
7591        174.785, -381.768, 409.365, 409.365, -101.191, -45.9961, -584.15, -584.15, 317.373,
7592        -381.768, 174.785, 519.756, -584.15, 4.59961, 4.59961, 317.373, -584.15, -584.15, -45.9961,
7593        -160.986, -45.9961, 4.59961, -298.975, 122.81, 73.1338, 155.927, 1.37988, -13.7988,
7594        -143.508, -89.6924, -143.508, -114.53, -13.7988, 1.37988, 34.4971, -13.7988, 155.927,
7595        -114.53, -143.508, -143.508, -143.508, 73.1338, -67.6143, 95.2119, -30.3574, 155.927,
7596        -48.2959, -48.2959, -143.508, 17.9385, -175.245, 1.37988, 73.1338, -175.245, 17.9385,
7597        -2.06982, -184.214, 262.868, 215.262, -26.9077, -51.7456, -233.89, 101.421, -2.06982,
7598        20.6982, 171.795, 45.5361, -2.06982, 215.262, 215.262, 72.4438, -109.701, -184.214,
7599        -109.701, -26.9077, 45.5361, 171.795, 101.421, 45.5361, 45.5361, -51.7456, -78.6533,
7600        -184.214, -26.9077, 171.795, -2.06982, 20.6982, -134.539, 51.7456, 142.818, -171.795,
7601        184.214, -262.868, 51.7456, 109.701, -72.4438, 233.89, -171.795, 184.214, -72.4438,
7602        26.9077, 51.7456, 184.214, -72.4438, -171.795, 2.06982, -215.262, 51.7456, 184.214,
7603        184.214, -262.868, -20.6982, 233.89, -171.795, -72.4438, -171.795, -215.262, 142.818,
7604        -171.795, -430.523, 368.429, -430.523, 219.401, 368.429, 4.13965, -91.0723, -41.3965,
7605        4.13965, -144.888, -41.3965, -91.0723, -144.888, 53.8154, 103.491, -269.077, -144.888,
7606        -202.843, 4.13965, 285.636, -525.735, -41.3965, 4.13965, 285.636, -144.888, 157.307,
7607        157.307, 467.78, -202.843, 103.491, -525.735, 4.13965, -380.848, -137.988, 458.121,
7608        -380.848, 700.98, 458.121, 55.1953, 458.121, -491.238, 270.457, 700.98, 458.121, 574.031,
7609        270.457, -5.51953, -209.742, -623.707, 458.121, 574.031, 55.1953, -623.707, 574.031,
7610        -71.7539, -491.238, -623.707, -623.707, -380.848, -137.988, 574.031, 574.031, 55.1953,
7611        -380.848,
7612    ];
7613
7614    #[test]
7615    fn iq4_nl_dequant_matches_independent_python_reference() {
7616        let got = dequant_iq4_nl(&IQ4_NL_TEST_BLOCK).unwrap();
7617        assert_eq!(got.len(), IQ4_NL_GOLDEN.len());
7618        for (i, (a, b)) in got.iter().zip(IQ4_NL_GOLDEN.iter()).enumerate() {
7619            assert!(
7620                (a - b).abs() < 1e-2,
7621                "IQ4_NL element {i}: rust={a} python={b}"
7622            );
7623        }
7624    }
7625
7626    #[test]
7627    fn iq4_nl_fused_dot_matches_dequant_then_dot() {
7628        let dequanted = dequant_iq4_nl(&IQ4_NL_TEST_BLOCK).unwrap();
7629        let x: Vec<f32> = (0..32).map(|i| ((i as f32) * 0.019).sin()).collect();
7630        let expected: f32 = dequanted.iter().zip(x.iter()).map(|(a, b)| a * b).sum();
7631        let fused = dot_iq4_nl_f32(&IQ4_NL_TEST_BLOCK, &x);
7632        assert!(
7633            (fused - expected).abs() < 1e-1,
7634            "fused={fused} expected={expected}"
7635        );
7636    }
7637
7638    #[test]
7639    fn iq4_xs_dequant_matches_independent_python_reference() {
7640        let got = dequant_iq4_xs(&IQ4_XS_TEST_BLOCK).unwrap();
7641        assert_eq!(got.len(), IQ4_XS_GOLDEN.len());
7642        for (i, (a, b)) in got.iter().zip(IQ4_XS_GOLDEN.iter()).enumerate() {
7643            assert!(
7644                (a - b).abs() < 1e-1,
7645                "IQ4_XS element {i}: rust={a} python={b}"
7646            );
7647        }
7648    }
7649
7650    #[test]
7651    fn iq4_xs_fused_dot_matches_dequant_then_dot() {
7652        let dequanted = dequant_iq4_xs(&IQ4_XS_TEST_BLOCK).unwrap();
7653        let x: Vec<f32> = (0..256).map(|i| ((i as f32) * 0.023).cos()).collect();
7654        let expected: f32 = dequanted.iter().zip(x.iter()).map(|(a, b)| a * b).sum();
7655        let fused = dot_iq4_xs_f32(&IQ4_XS_TEST_BLOCK, &x);
7656        assert!(
7657            (fused - expected).abs() < 1e-1,
7658            "fused={fused} expected={expected}"
7659        );
7660    }
7661
7662    #[test]
7663    fn iq4_nl_and_iq4_xs_reject_misaligned_buffers() {
7664        let bad = vec![0u8; 5];
7665        assert!(dequant_iq4_nl(&bad).is_err());
7666        assert!(dequant_iq4_xs(&bad).is_err());
7667    }
7668
7669    // Generated by an independent Python reference -- do not hand-edit. Scale
7670    // bytes deliberately span e=0 (2^-127, the special subnormal-adjacent
7671    // case) and a mid-range exponent (e=130 -> 2^3 = 8.0), packed nibbles
7672    // fully random.
7673    const MXFP4_TEST_PACKED: [u8; 32] = [
7674        0xaa, 0xf9, 0x12, 0xda, 0x04, 0xac, 0xce, 0x2d, 0xbf, 0x4c, 0xc3, 0x06, 0x67, 0x59, 0xd1,
7675        0xa3, 0xea, 0xf1, 0x8f, 0x5d, 0xe5, 0xe6, 0x9e, 0x77, 0x73, 0x9c, 0x6f, 0x14, 0x5f, 0x1f,
7676        0xd9, 0x5e,
7677    ];
7678    const MXFP4_TEST_SCALES: [u8; 2] = [0x00, 0x82];
7679    const MXFP4_GOLDEN: [f32; 64] = [
7680        -5.87747e-39,
7681        -2.93874e-39,
7682        5.87747e-39,
7683        -5.87747e-39,
7684        1.17549e-38,
7685        -1.17549e-38,
7686        -2.35099e-38,
7687        -1.76324e-38,
7688        -3.52648e-38,
7689        -1.17549e-38,
7690        8.81621e-39,
7691        2.35099e-38,
7692        3.52648e-38,
7693        -2.93874e-39,
7694        2.93874e-39,
7695        8.81621e-39,
7696        -5.87747e-39,
7697        -3.52648e-38,
7698        2.93874e-39,
7699        -1.76324e-38,
7700        0.0,
7701        -5.87747e-39,
7702        -1.17549e-38,
7703        5.87747e-39,
7704        -8.81621e-39,
7705        1.17549e-38,
7706        -1.17549e-38,
7707        0.0,
7708        2.35099e-38,
7709        1.76324e-38,
7710        -1.76324e-38,
7711        -5.87747e-39,
7712        -8.0,
7713        4.0,
7714        -48.0,
7715        -24.0,
7716        24.0,
7717        32.0,
7718        -32.0,
7719        48.0,
7720        12.0,
7721        -16.0,
7722        -48.0,
7723        16.0,
7724        -48.0,
7725        -48.0,
7726        -4.0,
7727        -32.0,
7728        -32.0,
7729        -48.0,
7730        -0.0,
7731        24.0,
7732        -32.0,
7733        -32.0,
7734        -4.0,
7735        48.0,
7736        48.0,
7737        -4.0,
7738        32.0,
7739        4.0,
7740        24.0,
7741        4.0,
7742        -24.0,
7743        24.0,
7744    ];
7745
7746    #[test]
7747    fn mxfp4_dequant_matches_independent_python_reference() {
7748        let got = dequant_mxfp4_row(&MXFP4_TEST_PACKED, &MXFP4_TEST_SCALES).unwrap();
7749        assert_eq!(got.len(), MXFP4_GOLDEN.len());
7750        for (i, (a, b)) in got.iter().zip(MXFP4_GOLDEN.iter()).enumerate() {
7751            let tol = 1e-38f32.max(b.abs() * 1e-3);
7752            assert!(
7753                (a - b).abs() < tol,
7754                "MXFP4 element {i}: rust={a} python={b}"
7755            );
7756        }
7757    }
7758
7759    #[test]
7760    fn mxfp4_fused_dot_matches_dequant_then_dot() {
7761        let dequanted = dequant_mxfp4_row(&MXFP4_TEST_PACKED, &MXFP4_TEST_SCALES).unwrap();
7762        let x: Vec<f32> = (0..64).map(|i| ((i as f32) * 0.037).sin()).collect();
7763        let expected: f32 = dequanted.iter().zip(x.iter()).map(|(a, b)| a * b).sum();
7764        let fused = dot_mxfp4_row_f32(&MXFP4_TEST_PACKED, &MXFP4_TEST_SCALES, &x);
7765        assert!(
7766            (fused - expected).abs() < 1e-3,
7767            "fused={fused} expected={expected}"
7768        );
7769    }
7770
7771    #[test]
7772    fn mxfp4_scale_byte_zero_and_max_match_the_e8m0_formula() {
7773        // e=0 is the special subnormal-adjacent case (2^-127); e=127 is
7774        // the OCP MX bias point (scale 1.0, i.e. the E2M1 values verbatim).
7775        assert!((e8m0_scale(0) - 2f32.powi(-127)).abs() < 1e-45);
7776        assert_eq!(e8m0_scale(127), 1.0);
7777        assert_eq!(e8m0_scale(128), 2.0);
7778    }
7779
7780    #[test]
7781    fn mxfp4_simd_dispatch_matches_scalar_across_every_possible_packed_byte_value() {
7782        // 16 groups of 16 bytes each = 256 total packed bytes, covering
7783        // every possible u8 value exactly once (each byte encodes 2
7784        // nibbles, so this exercises every (lo_nibble, hi_nibble) pair
7785        // the real E2M1 codebook can ever see) -- exhaustive coverage
7786        // for the SIMD decode logic (mxfp4_nibbles_to_f32_quads /
7787        // mxfp4_nibbles_to_f32x8), which is new, hand-derived
7788        // arithmetic (not a direct port of already-tested code) and so
7789        // needs its own thorough cross-validation against the scalar
7790        // KVALUES_MXFP4 table lookup, not just the one golden fixture
7791        // above.
7792        let packed: Vec<u8> = (0..=255u8).collect();
7793        let n_groups = packed.len() / (MXFP4_GROUP_SIZE / 2);
7794        // Varied scale bytes (not all identical), staying within the
7795        // realistic/non-overflowing range this module's own doc
7796        // comments already establish (0xFF reserved for NaN; very high
7797        // bytes combined with E2M1's max magnitude of 6 can legitimately
7798        // overflow f32::MAX).
7799        let scales: Vec<u8> = (0..n_groups).map(|i| ((i * 17 + 3) % 180) as u8).collect();
7800        let x: Vec<f32> = (0..n_groups * MXFP4_GROUP_SIZE)
7801            .map(|i| ((i as f32) * 0.013).cos())
7802            .collect();
7803
7804        let scalar = dot_mxfp4_row_f32_scalar(&packed, &scales, &x);
7805        let dispatched = dot_mxfp4_row_f32(&packed, &scales, &x);
7806        assert!(
7807            (scalar - dispatched).abs() < scalar.abs() * 1e-3 + 1e-3,
7808            "scalar={scalar} dispatched (SIMD)={dispatched}"
7809        );
7810
7811        #[cfg(target_arch = "aarch64")]
7812        {
7813            let neon = unsafe { simd_aarch64::dot_mxfp4_row_f32_neon(&packed, &scales, &x) };
7814            assert!(
7815                (scalar - neon).abs() < scalar.abs() * 1e-3 + 1e-3,
7816                "scalar={scalar} neon={neon}"
7817            );
7818        }
7819    }
7820
7821    #[test]
7822    fn mxfp4_rejects_a_packed_scales_length_mismatch() {
7823        let bad_packed = vec![0u8; 15]; // one byte short of 16 for a single 32-elem group
7824        let scales = [0u8; 1];
7825        assert!(matches!(
7826            dequant_mxfp4_row(&bad_packed, &scales),
7827            Err(QuantError::Mxfp4RowMismatch(15, 16))
7828        ));
7829    }
7830
7831    /// Repeats a single-block golden fixture `n` times, so multi-block
7832    /// SIMD dispatch (not just a single loop iteration) gets exercised.
7833    fn repeat_block(block: &[u8], n: usize) -> Vec<u8> {
7834        block
7835            .iter()
7836            .copied()
7837            .cycle()
7838            .take(block.len() * n)
7839            .collect()
7840    }
7841
7842    #[test]
7843    fn dispatched_q4_k_matches_scalar_reference_across_many_blocks() {
7844        let n_blocks = 4;
7845        let packed = repeat_block(&Q4_K_TEST_BLOCK, n_blocks);
7846        let x: Vec<f32> = (0..256 * n_blocks)
7847            .map(|i| ((i as f32) * 0.013).sin())
7848            .collect();
7849        let dispatched = dot_q4_k_f32(&packed, &x);
7850        let scalar = dot_q4_k_f32_scalar(&packed, &x);
7851        assert!(
7852            (dispatched - scalar).abs() < 1e-1,
7853            "dispatched={dispatched} scalar={scalar}"
7854        );
7855    }
7856
7857    #[test]
7858    fn dispatched_q5_k_matches_scalar_reference_across_many_blocks() {
7859        let n_blocks = 4;
7860        let packed = repeat_block(&Q5_K_TEST_BLOCK, n_blocks);
7861        let x: Vec<f32> = (0..256 * n_blocks)
7862            .map(|i| ((i as f32) * 0.011).cos())
7863            .collect();
7864        let dispatched = dot_q5_k_f32(&packed, &x);
7865        let scalar = dot_q5_k_f32_scalar(&packed, &x);
7866        assert!(
7867            (dispatched - scalar).abs() < 1e-1,
7868            "dispatched={dispatched} scalar={scalar}"
7869        );
7870    }
7871
7872    #[test]
7873    fn dispatched_q6_k_matches_scalar_reference_across_many_blocks() {
7874        let n_blocks = 4;
7875        let packed = repeat_block(&Q6_K_TEST_BLOCK, n_blocks);
7876        let x: Vec<f32> = (0..256 * n_blocks)
7877            .map(|i| ((i as f32) * 0.019).sin())
7878            .collect();
7879        let dispatched = dot_q6_k_f32(&packed, &x);
7880        let scalar = dot_q6_k_f32_scalar(&packed, &x);
7881        assert!(
7882            (dispatched - scalar).abs() < 1e-1,
7883            "dispatched={dispatched} scalar={scalar}"
7884        );
7885    }
7886
7887    #[test]
7888    fn dispatched_q6_k_matches_scalar_reference_with_negative_scales() {
7889        // Same shape as the test above, but on the negative-scale
7890        // fixture: this is the case where the scalar reference and the
7891        // SIMD kernels historically *disagreed* (scalar read the signed
7892        // scales as unsigned), so all-positive parity was vacuous.
7893        let n_blocks = 4;
7894        let packed = repeat_block(&Q6_K_SIGNED_SCALES_TEST_BLOCK, n_blocks);
7895        let x: Vec<f32> = (0..256 * n_blocks)
7896            .map(|i| ((i as f32) * 0.019).sin())
7897            .collect();
7898        let dispatched = dot_q6_k_f32(&packed, &x);
7899        let scalar = dot_q6_k_f32_scalar(&packed, &x);
7900        assert!(
7901            (dispatched - scalar).abs() < 1e-1,
7902            "dispatched={dispatched} scalar={scalar}"
7903        );
7904    }
7905
7906    #[cfg(target_arch = "aarch64")]
7907    #[test]
7908    fn neon_q4_k_kernel_matches_scalar_directly_when_available() {
7909        if !std::arch::is_aarch64_feature_detected!("neon") {
7910            eprintln!("skipping: host CPU lacks NEON");
7911            return;
7912        }
7913        let n_blocks = 4;
7914        let packed = repeat_block(&Q4_K_TEST_BLOCK, n_blocks);
7915        let x: Vec<f32> = (0..256 * n_blocks)
7916            .map(|i| ((i as f32) * 0.037).cos())
7917            .collect();
7918        let simd = unsafe { simd_aarch64::dot_q4_k_f32_neon(&packed, &x) };
7919        let scalar = dot_q4_k_f32_scalar(&packed, &x);
7920        assert!(
7921            (simd - scalar).abs() < 1e-1,
7922            "NEON Q4_K kernel diverged from scalar: simd={simd} scalar={scalar}"
7923        );
7924    }
7925
7926    #[cfg(target_arch = "aarch64")]
7927    #[test]
7928    fn neon_q5_k_q8_kernel_matches_scalar_directly_when_available() {
7929        if !std::arch::is_aarch64_feature_detected!("neon") {
7930            eprintln!("skipping: host CPU lacks NEON");
7931            return;
7932        }
7933        let n_blocks = 4;
7934        let packed = repeat_block(&Q5_K_TEST_BLOCK, n_blocks);
7935        let x: Vec<f32> = (0..256 * n_blocks)
7936            .map(|i| ((i as f32) * 0.029).sin())
7937            .collect();
7938        let act = quantize_activations_q8_k(&x);
7939        let dispatched = dot_q5_k_q8(&packed, &act);
7940        let scalar = dot_q5_k_q8_scalar(&packed, &act);
7941        assert_eq!(
7942            dispatched,
7943            scalar,
7944            "Q5_K×Q8_K dispatch must match scalar (dotprod={})",
7945            std::arch::is_aarch64_feature_detected!("dotprod")
7946        );
7947        if std::arch::is_aarch64_feature_detected!("dotprod") {
7948            let sdot = unsafe { simd_aarch64::dot_q5_k_q8_neon_sdot(&packed, &act) };
7949            assert_eq!(sdot, scalar, "NEON SDOT Q5_K×Q8_K diverged from scalar");
7950        }
7951        if std::arch::is_aarch64_feature_detected!("neon") {
7952            let neon = unsafe { simd_aarch64::dot_q5_k_q8_neon(&packed, &act) };
7953            assert_eq!(neon, scalar, "NEON widen Q5_K×Q8_K diverged from scalar");
7954        }
7955    }
7956
7957    #[cfg(target_arch = "aarch64")]
7958    #[test]
7959    fn neon_q5_k_kernel_matches_scalar_directly_when_available() {
7960        if !std::arch::is_aarch64_feature_detected!("neon") {
7961            eprintln!("skipping: host CPU lacks NEON");
7962            return;
7963        }
7964        let n_blocks = 4;
7965        let packed = repeat_block(&Q5_K_TEST_BLOCK, n_blocks);
7966        let x: Vec<f32> = (0..256 * n_blocks)
7967            .map(|i| ((i as f32) * 0.029).sin())
7968            .collect();
7969        let simd = unsafe { simd_aarch64::dot_q5_k_f32_neon(&packed, &x) };
7970        let scalar = dot_q5_k_f32_scalar(&packed, &x);
7971        assert!(
7972            (simd - scalar).abs() < 1e-1,
7973            "NEON Q5_K kernel diverged from scalar: simd={simd} scalar={scalar}"
7974        );
7975    }
7976
7977    #[cfg(target_arch = "aarch64")]
7978    #[test]
7979    fn neon_q6_k_kernel_matches_scalar_directly_when_available() {
7980        if !std::arch::is_aarch64_feature_detected!("neon") {
7981            eprintln!("skipping: host CPU lacks NEON");
7982            return;
7983        }
7984        let n_blocks = 4;
7985        let packed = repeat_block(&Q6_K_TEST_BLOCK, n_blocks);
7986        let x: Vec<f32> = (0..256 * n_blocks)
7987            .map(|i| ((i as f32) * 0.041).cos())
7988            .collect();
7989        let simd = unsafe { simd_aarch64::dot_q6_k_f32_neon(&packed, &x) };
7990        let scalar = dot_q6_k_f32_scalar(&packed, &x);
7991        assert!(
7992            (simd - scalar).abs() < 1e-1,
7993            "NEON Q6_K kernel diverged from scalar: simd={simd} scalar={scalar}"
7994        );
7995    }
7996
7997    #[cfg(target_arch = "aarch64")]
7998    #[test]
7999    fn neon_q6_k_kernel_matches_scalar_directly_on_negative_scales() {
8000        if !std::arch::is_aarch64_feature_detected!("neon") {
8001            eprintln!("skipping: host CPU lacks NEON");
8002            return;
8003        }
8004        let n_blocks = 4;
8005        let packed = repeat_block(&Q6_K_SIGNED_SCALES_TEST_BLOCK, n_blocks);
8006        let x: Vec<f32> = (0..256 * n_blocks)
8007            .map(|i| ((i as f32) * 0.041).cos())
8008            .collect();
8009        let simd = unsafe { simd_aarch64::dot_q6_k_f32_neon(&packed, &x) };
8010        let scalar = dot_q6_k_f32_scalar(&packed, &x);
8011        assert!(
8012            (simd - scalar).abs() < 1e-1,
8013            "NEON Q6_K kernel diverged from scalar on negative scales: simd={simd} scalar={scalar}"
8014        );
8015    }
8016
8017    #[test]
8018    fn q4_k_scalar_matches_independent_python_reference_via_dispatch_entrypoint() {
8019        // The public `dot_q4_k_f32`/`dot_q5_k_f32`/`dot_q6_k_f32`
8020        // dispatch functions must still agree with the
8021        // already-Python-cross-validated dequant golden values, not
8022        // just with themselves -- guards against a SIMD kernel and the
8023        // scalar kernel agreeing with each other while both being
8024        // wrong in the same way.
8025        let x: Vec<f32> = (0..256).map(|i| ((i as f32) * 0.017).sin()).collect();
8026        let dequanted = dequant_q4_k(&Q4_K_TEST_BLOCK).unwrap();
8027        let expected: f32 = dequanted.iter().zip(x.iter()).map(|(a, b)| a * b).sum();
8028        let dispatched = dot_q4_k_f32(&Q4_K_TEST_BLOCK, &x);
8029        assert!((dispatched - expected).abs() < 1e-2);
8030    }
8031
8032    // --- SIMD coverage for the 8 previously-scalar-only formats ---
8033
8034    fn q4_1_test_block() -> Vec<u8> {
8035        let mut b = Vec::new();
8036        b.extend_from_slice(&f16::from_f32(0.3).to_le_bytes());
8037        b.extend_from_slice(&f16::from_f32(-1.2).to_le_bytes());
8038        b.extend_from_slice(
8039            &(0..16)
8040                .map(|i| (i as u8) | ((15 - i as u8) << 4))
8041                .collect::<Vec<u8>>(),
8042        );
8043        b
8044    }
8045
8046    fn q5_0_test_block() -> Vec<u8> {
8047        let mut b = Vec::new();
8048        b.extend_from_slice(&f16::from_f32(0.4).to_le_bytes());
8049        b.extend_from_slice(&[0xA5, 0x3C, 0x00, 0xFF]);
8050        b.extend_from_slice(
8051            &(0..16)
8052                .map(|i| (i as u8) | ((15 - i as u8) << 4))
8053                .collect::<Vec<u8>>(),
8054        );
8055        b
8056    }
8057
8058    fn q5_1_test_block() -> Vec<u8> {
8059        let mut b = Vec::new();
8060        b.extend_from_slice(&f16::from_f32(0.2).to_le_bytes());
8061        b.extend_from_slice(&f16::from_f32(0.9).to_le_bytes());
8062        b.extend_from_slice(&[0x12, 0x34, 0x56, 0x78]);
8063        b.extend_from_slice(
8064            &(0..16)
8065                .map(|i| (i as u8) | ((15 - i as u8) << 4))
8066                .collect::<Vec<u8>>(),
8067        );
8068        b
8069    }
8070
8071    fn q8_1_test_block() -> Vec<u8> {
8072        let mut b = Vec::new();
8073        b.extend_from_slice(&f16::from_f32(0.6).to_le_bytes());
8074        b.extend_from_slice(&f16::from_f32(0.0).to_le_bytes());
8075        let qs: Vec<i8> = (0..32).map(|i| ((i * 7) % 61) as i8 - 30).collect();
8076        b.extend_from_slice(&i8_to_u8_bytes(&qs));
8077        b
8078    }
8079
8080    #[test]
8081    fn dispatched_matches_scalar_for_the_8_newly_simd_formats_across_many_blocks() {
8082        let n_blocks = 4;
8083
8084        let q4_1 = repeat_block(&q4_1_test_block(), n_blocks);
8085        let x32 = |seed: f32| -> Vec<f32> {
8086            (0..32 * n_blocks)
8087                .map(|i| ((i as f32) * seed).sin())
8088                .collect()
8089        };
8090        let x = x32(0.031);
8091        assert!((dot_q4_1_f32(&q4_1, &x) - dot_q4_1_f32_scalar(&q4_1, &x)).abs() < 1e-1);
8092
8093        let q5_0 = repeat_block(&q5_0_test_block(), n_blocks);
8094        let x = x32(0.037);
8095        assert!((dot_q5_0_f32(&q5_0, &x) - dot_q5_0_f32_scalar(&q5_0, &x)).abs() < 1e-1);
8096
8097        let q5_1 = repeat_block(&q5_1_test_block(), n_blocks);
8098        let x = x32(0.041);
8099        assert!((dot_q5_1_f32(&q5_1, &x) - dot_q5_1_f32_scalar(&q5_1, &x)).abs() < 1e-1);
8100
8101        let q8_1 = repeat_block(&q8_1_test_block(), n_blocks);
8102        let x = x32(0.043);
8103        assert!((dot_q8_1_f32(&q8_1, &x) - dot_q8_1_f32_scalar(&q8_1, &x)).abs() < 1e-1);
8104
8105        let q2_k = repeat_block(&Q2_K_TEST_BLOCK, n_blocks);
8106        let x256 = |seed: f32| -> Vec<f32> {
8107            (0..256 * n_blocks)
8108                .map(|i| ((i as f32) * seed).cos())
8109                .collect()
8110        };
8111        let x = x256(0.013);
8112        assert!((dot_q2_k_f32(&q2_k, &x) - dot_q2_k_f32_scalar(&q2_k, &x)).abs() < 1e-1);
8113
8114        let q3_k = repeat_block(&Q3_K_TEST_BLOCK, n_blocks);
8115        let x = x256(0.017);
8116        assert!((dot_q3_k_f32(&q3_k, &x) - dot_q3_k_f32_scalar(&q3_k, &x)).abs() < 1e-1);
8117
8118        let iq4_nl = repeat_block(&IQ4_NL_TEST_BLOCK, n_blocks);
8119        let x = x32(0.019);
8120        assert!((dot_iq4_nl_f32(&iq4_nl, &x) - dot_iq4_nl_f32_scalar(&iq4_nl, &x)).abs() < 1e-1);
8121
8122        let iq4_xs = repeat_block(&IQ4_XS_TEST_BLOCK, n_blocks);
8123        let x = x256(0.023);
8124        assert!((dot_iq4_xs_f32(&iq4_xs, &x) - dot_iq4_xs_f32_scalar(&iq4_xs, &x)).abs() < 1e-1);
8125    }
8126
8127    #[cfg(target_arch = "aarch64")]
8128    #[test]
8129    fn neon_kernels_match_scalar_directly_for_the_8_newly_simd_formats() {
8130        if !std::arch::is_aarch64_feature_detected!("neon") {
8131            eprintln!("skipping: host CPU lacks NEON");
8132            return;
8133        }
8134        let n_blocks = 4;
8135        let x32 = |seed: f32| -> Vec<f32> {
8136            (0..32 * n_blocks)
8137                .map(|i| ((i as f32) * seed).sin())
8138                .collect()
8139        };
8140        let x256 = |seed: f32| -> Vec<f32> {
8141            (0..256 * n_blocks)
8142                .map(|i| ((i as f32) * seed).cos())
8143                .collect()
8144        };
8145
8146        let q4_1 = repeat_block(&q4_1_test_block(), n_blocks);
8147        let x = x32(0.031);
8148        let simd = unsafe { simd_aarch64::dot_q4_1_f32_neon(&q4_1, &x) };
8149        assert!((simd - dot_q4_1_f32_scalar(&q4_1, &x)).abs() < 1e-1);
8150
8151        let q5_0 = repeat_block(&q5_0_test_block(), n_blocks);
8152        let x = x32(0.037);
8153        let simd = unsafe { simd_aarch64::dot_q5_0_f32_neon(&q5_0, &x) };
8154        assert!((simd - dot_q5_0_f32_scalar(&q5_0, &x)).abs() < 1e-1);
8155
8156        let q5_1 = repeat_block(&q5_1_test_block(), n_blocks);
8157        let x = x32(0.041);
8158        let simd = unsafe { simd_aarch64::dot_q5_1_f32_neon(&q5_1, &x) };
8159        assert!((simd - dot_q5_1_f32_scalar(&q5_1, &x)).abs() < 1e-1);
8160
8161        let q8_1 = repeat_block(&q8_1_test_block(), n_blocks);
8162        let x = x32(0.043);
8163        let simd = unsafe { simd_aarch64::dot_q8_1_f32_neon(&q8_1, &x) };
8164        assert!((simd - dot_q8_1_f32_scalar(&q8_1, &x)).abs() < 1e-1);
8165
8166        let q2_k = repeat_block(&Q2_K_TEST_BLOCK, n_blocks);
8167        let x = x256(0.013);
8168        let simd = unsafe { simd_aarch64::dot_q2_k_f32_neon(&q2_k, &x) };
8169        assert!((simd - dot_q2_k_f32_scalar(&q2_k, &x)).abs() < 1e-1);
8170
8171        let q3_k = repeat_block(&Q3_K_TEST_BLOCK, n_blocks);
8172        let x = x256(0.017);
8173        let simd = unsafe { simd_aarch64::dot_q3_k_f32_neon(&q3_k, &x) };
8174        assert!((simd - dot_q3_k_f32_scalar(&q3_k, &x)).abs() < 1e-1);
8175
8176        let iq4_nl = repeat_block(&IQ4_NL_TEST_BLOCK, n_blocks);
8177        let x = x32(0.019);
8178        let simd = unsafe { simd_aarch64::dot_iq4_nl_f32_neon(&iq4_nl, &x) };
8179        assert!((simd - dot_iq4_nl_f32_scalar(&iq4_nl, &x)).abs() < 1e-1);
8180
8181        let iq4_xs = repeat_block(&IQ4_XS_TEST_BLOCK, n_blocks);
8182        let x = x256(0.023);
8183        let simd = unsafe { simd_aarch64::dot_iq4_xs_f32_neon(&iq4_xs, &x) };
8184        assert!((simd - dot_iq4_xs_f32_scalar(&iq4_xs, &x)).abs() < 1e-1);
8185    }
8186
8187    #[cfg(target_arch = "x86_64")]
8188    #[test]
8189    fn avx2_kernels_match_scalar_directly_for_the_8_newly_simd_formats() {
8190        if !(is_x86_feature_detected!("avx2") && is_x86_feature_detected!("fma")) {
8191            eprintln!("skipping: host CPU lacks AVX2+FMA");
8192            return;
8193        }
8194        let n_blocks = 4;
8195        let x32 = |seed: f32| -> Vec<f32> {
8196            (0..32 * n_blocks)
8197                .map(|i| ((i as f32) * seed).sin())
8198                .collect()
8199        };
8200        let x256 = |seed: f32| -> Vec<f32> {
8201            (0..256 * n_blocks)
8202                .map(|i| ((i as f32) * seed).cos())
8203                .collect()
8204        };
8205
8206        let q4_1 = repeat_block(&q4_1_test_block(), n_blocks);
8207        let x = x32(0.031);
8208        let simd = unsafe { simd_x86::dot_q4_1_f32_avx2(&q4_1, &x) };
8209        assert!((simd - dot_q4_1_f32_scalar(&q4_1, &x)).abs() < 1e-1);
8210
8211        let q5_0 = repeat_block(&q5_0_test_block(), n_blocks);
8212        let x = x32(0.037);
8213        let simd = unsafe { simd_x86::dot_q5_0_f32_avx2(&q5_0, &x) };
8214        assert!((simd - dot_q5_0_f32_scalar(&q5_0, &x)).abs() < 1e-1);
8215
8216        let q5_1 = repeat_block(&q5_1_test_block(), n_blocks);
8217        let x = x32(0.041);
8218        let simd = unsafe { simd_x86::dot_q5_1_f32_avx2(&q5_1, &x) };
8219        assert!((simd - dot_q5_1_f32_scalar(&q5_1, &x)).abs() < 1e-1);
8220
8221        let q8_1 = repeat_block(&q8_1_test_block(), n_blocks);
8222        let x = x32(0.043);
8223        let simd = unsafe { simd_x86::dot_q8_1_f32_avx2(&q8_1, &x) };
8224        assert!((simd - dot_q8_1_f32_scalar(&q8_1, &x)).abs() < 1e-1);
8225
8226        let q2_k = repeat_block(&Q2_K_TEST_BLOCK, n_blocks);
8227        let x = x256(0.013);
8228        let simd = unsafe { simd_x86::dot_q2_k_f32_avx2(&q2_k, &x) };
8229        assert!((simd - dot_q2_k_f32_scalar(&q2_k, &x)).abs() < 1e-1);
8230
8231        let q3_k = repeat_block(&Q3_K_TEST_BLOCK, n_blocks);
8232        let x = x256(0.017);
8233        let simd = unsafe { simd_x86::dot_q3_k_f32_avx2(&q3_k, &x) };
8234        assert!((simd - dot_q3_k_f32_scalar(&q3_k, &x)).abs() < 1e-1);
8235
8236        let iq4_nl = repeat_block(&IQ4_NL_TEST_BLOCK, n_blocks);
8237        let x = x32(0.019);
8238        let simd = unsafe { simd_x86::dot_iq4_nl_f32_avx2(&iq4_nl, &x) };
8239        assert!((simd - dot_iq4_nl_f32_scalar(&iq4_nl, &x)).abs() < 1e-1);
8240
8241        let iq4_xs = repeat_block(&IQ4_XS_TEST_BLOCK, n_blocks);
8242        let x = x256(0.023);
8243        let simd = unsafe { simd_x86::dot_iq4_xs_f32_avx2(&iq4_xs, &x) };
8244        assert!((simd - dot_iq4_xs_f32_scalar(&iq4_xs, &x)).abs() < 1e-1);
8245    }
8246}