## Coverage Report

#### Created: 2026-01-25 15:05

next uncovered line (L), next uncovered region (R), next uncovered branch (B)

    /home/noah/src/trueno/src/brick/quant_ops.rs

    Line
    Count
    Source
    1
    //! Q5_K and Q6_K Quantization Operations (llama.cpp compatible)
    2
    //!
    3
    //! This module provides quantization formats and compute operations
    4
    //! for llama.cpp-compatible k-quant formats.
    5
    //!
    6
    //! # Formats
    7
    //!
    8
    //! - `BlockQ5K`: 5-bit quantization with super-blocks (256 values)
    9
    //! - `BlockQ6K`: 6-bit quantization with super-blocks (256 values)
    10
    //!
    11
    //! # Operations
    12
    //!
    13
    //! - `DotQ5KOp`: Dot product with Q5_K quantized weights
    14
    //! - `DotQ6KOp`: Dot product with Q6_K quantized weights
    15
    //!
    16
    //! # SIMD Optimization
    17
    //!
    18
    //! Both operations use AVX2/FMA when available for ~4x speedup.
    19
    20
    use super::{Backend, ComputeOp};
    21
    use crate::error::TruenoError;
    22
    23
    // ============================================================================
    24
    // Q5_K Block Format
    25
    // ============================================================================
    26
    27
    /// Q5_K block format (5-bit with super-blocks).
    28
    ///
    29
    /// Matches llama.cpp's block_q5_K format:
    30
    /// - Super-block of 256 values
    31
    /// - 5-bit quantization with k-quant scales
    32
    /// - Higher precision than Q4_K, lower than Q6_K
    33
    ///
    34
    /// Memory layout:
    35
    /// ```text
    36
    /// | d (fp16) | dmin (fp16) | scales[12] | qh[32] | qs[128] |
    37
    /// ```
    38
    #[derive(Debug, Clone)]
    39
    pub struct BlockQ5K {
    40
        /// Scale factor (half precision)
    41
        pub d: f32,
    42
        /// Minimum value scale (half precision)
    43
        pub dmin: f32,
    44
        /// Scales for each 32-value block (12 bytes packed)
    45
        pub scales: [u8; 12],
    46
        /// High bits for quantized values (32 bytes)
    47
        pub qh: [u8; 32],
    48
        /// Quantized values (128 bytes, 2 values per byte)
    49
        pub qs: [u8; 128],
    50
    }
    51
    52
    impl BlockQ5K {
    53
        /// Block size in elements
    54
        pub const BLOCK_SIZE: usize = 256;
    55
    56
        /// Dequantize a Q5_K block to f32.
    57
        ///
    58
        /// # Safety
    59
        ///
    60
        /// Output buffer must have at least BLOCK_SIZE elements.
    61
    0
        pub fn dequantize(&self, output: &mut [f32]) {
    62
    0
            debug_assert!(output.len() >= Self::BLOCK_SIZE);
    63
    64
            // Decode scales from packed format
    65
    0
            let mut scales = [0i8; 8];
    66
    0
            for i in 0..8 {
    67
    0
                let low = (self.scales[i] & 0x3F) as i8;
    68
    0
                scales[i] = low - 32;
    69
    0
            }
    70
    71
            // Dequantize each sub-block
    72
    0
            for block_idx in 0..8 {
    73
    0
                let scale = scales[block_idx] as f32;
    74
    0
                let base_idx = block_idx * 32;
    75
    76
    0
                for i in 0..32 {
    77
    0
                    let out_idx = base_idx + i;
    78
    0
                    let byte_idx = base_idx / 2 + i / 2;
    79
    80
                    // Extract 4-bit low value
    81
    0
                    let q4 = if i % 2 == 0 {
    82
    0
                        self.qs[byte_idx] & 0x0F
    83
                    } else {
    84
    0
                        self.qs[byte_idx] >> 4
    85
                    };
    86
    87
                    // Extract 5th bit from qh
    88
    0
                    let qh_bit = ((self.qh[i] >> block_idx) & 1) as u8;
    89
    0
                    let q5 = q4 | (qh_bit << 4);
    90
    91
                    // Dequantize: value = d * scale * (q5 - 16) + dmin
    92
    0
                    output[out_idx] = self.d * scale * (q5 as f32 - 16.0) + self.dmin;
    93
                }
    94
            }
    95
    0
        }
    96
    }
    97
    98
    // ============================================================================
    99
    // Q6_K Block Format
    100
    // ============================================================================
    101
    102
    /// Q6_K block format (6-bit with super-blocks).
    103
    ///
    104
    /// Matches llama.cpp's block_q6_K format:
    105
    /// - Super-block of 256 values
    106
    /// - 6-bit quantization with k-quant scales
    107
    /// - Highest precision k-quant format
    108
    ///
    109
    /// Memory layout:
    110
    /// ```text
    111
    /// | ql[128] | qh[64] | scales[16] | d (fp16) |
    112
    /// ```
    113
    #[derive(Debug, Clone)]
    114
    pub struct BlockQ6K {
    115
        /// Low 4 bits of quantized values (128 bytes)
    116
        pub ql: [u8; 128],
    117
        /// High 2 bits of quantized values (64 bytes)
    118
        pub qh: [u8; 64],
    119
        /// Scales for each 16-value block (16 bytes)
    120
        pub scales: [i8; 16],
    121
        /// Scale factor (half precision)
    122
        pub d: f32,
    123
    }
    124
    125
    impl BlockQ6K {
    126
        /// Block size in elements
    127
        pub const BLOCK_SIZE: usize = 256;
    128
    129
        /// Dequantize a Q6_K block to f32.
    130
        ///
    131
        /// # Safety
    132
        ///
    133
        /// Output buffer must have at least BLOCK_SIZE elements.
    134
    0
        pub fn dequantize(&self, output: &mut [f32]) {
    135
    0
            debug_assert!(output.len() >= Self::BLOCK_SIZE);
    136
    137
            // Dequantize each sub-block of 16 values
    138
    0
            for block_idx in 0..16 {
    139
    0
                let scale = self.scales[block_idx] as f32;
    140
    0
                let base_idx = block_idx * 16;
    141
    142
    0
                for i in 0..16 {
    143
    0
                    let out_idx = base_idx + i;
    144
    0
                    let ql_idx = base_idx / 2 + i / 2;
    145
    0
                    let qh_idx = base_idx / 4 + i / 4;
    146
    147
                    // Extract 4-bit low value
    148
    0
                    let ql_val = if i % 2 == 0 {
    149
    0
                        self.ql[ql_idx] & 0x0F
    150
                    } else {
    151
    0
                        self.ql[ql_idx] >> 4
    152
                    };
    153
    154
                    // Extract 2-bit high value
    155
    0
                    let qh_shift = (i % 4) * 2;
    156
    0
                    let qh_val = ((self.qh[qh_idx] >> qh_shift) & 0x03) as u8;
    157
    158
                    // Combine to 6-bit value
    159
    0
                    let q6 = ql_val | (qh_val << 4);
    160
    161
                    // Dequantize: value = d * scale * (q6 - 32)
    162
    0
                    output[out_idx] = self.d * scale * (q6 as f32 - 32.0);
    163
                }
    164
            }
    165
    0
        }
    166
    }
    167
    168
    // ============================================================================
    169
    // Q5_K Dot Product Operation
    170
    // ============================================================================
    171
    172
    /// Q5_K dot product operation.
    173
    ///
    174
    /// Computes dot product between Q5_K quantized weights and f32 activations.
    175
    #[derive(Debug, Clone)]
    176
    pub struct DotQ5KOp {
    177
        /// Number of blocks
    178
        pub n_blocks: usize,
    179
    }
    180
    181
    impl DotQ5KOp {
    182
        /// Create a new Q5_K dot product operation.
    183
        #[must_use]
    184
    0
        pub fn new(n_elements: usize) -> Self {
    185
    0
            Self {
    186
    0
                n_blocks: n_elements / BlockQ5K::BLOCK_SIZE,
    187
    0
            }
    188
    0
        }
    189
    190
        /// Compute dot product with SIMD acceleration.
    191
        #[cfg(target_arch = "x86_64")]
    192
        #[target_feature(enable = "avx2", enable = "fma")]
    193
    0
        unsafe fn avx2_dot_block(block: &BlockQ5K, x: &[f32]) -> f32 {
    194
            use std::arch::x86_64::*;
    195
    196
    0
            let mut acc = _mm256_setzero_ps();
    197
    0
            let mut dequant = [0.0f32; BlockQ5K::BLOCK_SIZE];
    198
    0
            block.dequantize(&mut dequant);
    199
    200
    0
            let mut i = 0;
    201
    0
            while i + 8 <= BlockQ5K::BLOCK_SIZE {
    202
    0
                let vd = _mm256_loadu_ps(dequant.as_ptr().add(i));
    203
    0
                let vx = _mm256_loadu_ps(x.as_ptr().add(i));
    204
    0
                acc = _mm256_fmadd_ps(vd, vx, acc);
    205
    0
                i += 8;
    206
    0
            }
    207
    208
            // Horizontal sum
    209
    0
            let high = _mm256_extractf128_ps(acc, 1);
    210
    0
            let low = _mm256_castps256_ps128(acc);
    211
    0
            let sum128 = _mm_add_ps(high, low);
    212
    0
            let sum64 = _mm_add_ps(sum128, _mm_movehl_ps(sum128, sum128));
    213
    0
            let sum32 = _mm_add_ss(sum64, _mm_shuffle_ps(sum64, sum64, 1));
    214
    0
            _mm_cvtss_f32(sum32)
    215
    0
        }
    216
    }
    217
    218
    impl ComputeOp for DotQ5KOp {
    219
        type Input = (Vec<BlockQ5K>, Vec<f32>);
    220
        type Output = f32;
    221
    222
    0
        fn name(&self) -> &'static str {
    223
    0
            "dot_q5k"
    224
    0
        }
    225
    226
    0
        fn execute(&self, input: Self::Input, backend: Backend) -> Result<Self::Output, TruenoError> {
    227
    0
            let (blocks, x) = input;
    228
    229
    0
            if blocks.is_empty() || x.is_empty() {
    230
    0
                return Ok(0.0);
    231
    0
            }
    232
    233
    0
            let mut sum = 0.0f32;
    234
    235
            #[cfg(target_arch = "x86_64")]
    236
            {
    237
    0
                if matches!(backend, Backend::Avx2 | Backend::Auto) && is_x86_feature_detected!("avx2")
    238
                {
    239
    0
                    for (i, block) in blocks.iter().enumerate() {
    240
    0
                        let x_slice = &x[i * BlockQ5K::BLOCK_SIZE..];
    241
    0
                        sum += unsafe { Self::avx2_dot_block(block, x_slice) };
    242
    0
                    }
    243
    0
                    return Ok(sum);
    244
    0
                }
    245
            }
    246
    247
            // Scalar fallback
    248
    0
            let mut dequant = [0.0f32; BlockQ5K::BLOCK_SIZE];
    249
    0
            for (i, block) in blocks.iter().enumerate() {
    250
    0
                block.dequantize(&mut dequant);
    251
    0
                let x_slice = &x[i * BlockQ5K::BLOCK_SIZE..];
    252
    0
                for j in 0..BlockQ5K::BLOCK_SIZE {
    253
    0
                    sum += dequant[j] * x_slice[j];
    254
    0
                }
    255
            }
    256
    257
    0
            Ok(sum)
    258
    0
        }
    259
    260
    0
        fn tokens(&self, _input: &Self::Input) -> usize {
    261
    0
            self.n_blocks * BlockQ5K::BLOCK_SIZE
    262
    0
        }
    263
    }
    264
    265
    // ============================================================================
    266
    // Q6_K Dot Product Operation
    267
    // ============================================================================
    268
    269
    /// Q6_K dot product operation.
    270
    ///
    271
    /// Computes dot product between Q6_K quantized weights and f32 activations.
    272
    #[derive(Debug, Clone)]
    273
    pub struct DotQ6KOp {
    274
        /// Number of blocks
    275
        pub n_blocks: usize,
    276
    }
    277
    278
    impl DotQ6KOp {
    279
        /// Create a new Q6_K dot product operation.
    280
        #[must_use]
    281
    0
        pub fn new(n_elements: usize) -> Self {
    282
    0
            Self {
    283
    0
                n_blocks: n_elements / BlockQ6K::BLOCK_SIZE,
    284
    0
            }
    285
    0
        }
    286
    287
        /// Compute dot product with SIMD acceleration.
    288
        #[cfg(target_arch = "x86_64")]
    289
        #[target_feature(enable = "avx2", enable = "fma")]
    290
    0
        unsafe fn avx2_dot_block(block: &BlockQ6K, x: &[f32]) -> f32 {
    291
            use std::arch::x86_64::*;
    292
    293
    0
            let mut acc = _mm256_setzero_ps();
    294
    0
            let mut dequant = [0.0f32; BlockQ6K::BLOCK_SIZE];
    295
    0
            block.dequantize(&mut dequant);
    296
    297
    0
            let mut i = 0;
    298
    0
            while i + 8 <= BlockQ6K::BLOCK_SIZE {
    299
    0
                let vd = _mm256_loadu_ps(dequant.as_ptr().add(i));
    300
    0
                let vx = _mm256_loadu_ps(x.as_ptr().add(i));
    301
    0
                acc = _mm256_fmadd_ps(vd, vx, acc);
    302
    0
                i += 8;
    303
    0
            }
    304
    305
            // Horizontal sum
    306
    0
            let high = _mm256_extractf128_ps(acc, 1);
    307
    0
            let low = _mm256_castps256_ps128(acc);
    308
    0
            let sum128 = _mm_add_ps(high, low);
    309
    0
            let sum64 = _mm_add_ps(sum128, _mm_movehl_ps(sum128, sum128));
    310
    0
            let sum32 = _mm_add_ss(sum64, _mm_shuffle_ps(sum64, sum64, 1));
    311
    0
            _mm_cvtss_f32(sum32)
    312
    0
        }
    313
    }
    314
    315
    impl ComputeOp for DotQ6KOp {
    316
        type Input = (Vec<BlockQ6K>, Vec<f32>);
    317
        type Output = f32;
    318
    319
    0
        fn name(&self) -> &'static str {
    320
    0
            "dot_q6k"
    321
    0
        }
    322
    323
    0
        fn execute(&self, input: Self::Input, backend: Backend) -> Result<Self::Output, TruenoError> {
    324
    0
            let (blocks, x) = input;
    325
    326
    0
            if blocks.is_empty() || x.is_empty() {
    327
    0
                return Ok(0.0);
    328
    0
            }
    329
    330
    0
            let mut sum = 0.0f32;
    331
    332
            #[cfg(target_arch = "x86_64")]
    333
            {
    334
    0
                if matches!(backend, Backend::Avx2 | Backend::Auto) && is_x86_feature_detected!("avx2")
    335
                {
    336
    0
                    for (i, block) in blocks.iter().enumerate() {
    337
    0
                        let x_slice = &x[i * BlockQ6K::BLOCK_SIZE..];
    338
    0
                        sum += unsafe { Self::avx2_dot_block(block, x_slice) };
    339
    0
                    }
    340
    0
                    return Ok(sum);
    341
    0
                }
    342
            }
    343
    344
            // Scalar fallback
    345
    0
            let mut dequant = [0.0f32; BlockQ6K::BLOCK_SIZE];
    346
    0
            for (i, block) in blocks.iter().enumerate() {
    347
    0
                block.dequantize(&mut dequant);
    348
    0
                let x_slice = &x[i * BlockQ6K::BLOCK_SIZE..];
    349
    0
                for j in 0..BlockQ6K::BLOCK_SIZE {
    350
    0
                    sum += dequant[j] * x_slice[j];
    351
    0
                }
    352
            }
    353
    354
    0
            Ok(sum)
    355
    0
        }
    356
    357
    0
        fn tokens(&self, _input: &Self::Input) -> usize {
    358
    0
            self.n_blocks * BlockQ6K::BLOCK_SIZE
    359
    0
        }
    360
    }
    361
    362
    #[cfg(test)]
    363
    mod tests {
    364
        use super::*;
    365
    366
        // ===== BlockQ5K Tests =====
    367
    368
        #[test]
    369
        fn test_block_q5k_size() {
    370
            assert_eq!(BlockQ5K::BLOCK_SIZE, 256);
    371
        }
    372
    373
        #[test]
    374
        fn test_block_q5k_dequantize_basic() {
    375
            let block = BlockQ5K {
    376
                d: 0.1,
    377
                dmin: 0.0,
    378
                scales: [32; 12], // Neutral scales (32 - 32 = 0)
    379
                qh: [0; 32],      // All high bits 0
    380
                qs: [0x88; 128],  // 8,8 pattern (mid-range 4-bit)
    381
            };
    382
    383
            let mut output = [0.0f32; 256];
    384
            block.dequantize(&mut output);
    385
    386
            // With scale=0, all outputs should be dmin (0.0)
    387
            for val in &output {
    388
                assert!(val.abs() < 1.0, "Expected near-zero, got {}", val);
    389
            }
    390
        }
    391
    392
        #[test]
    393
        fn test_block_q5k_dequantize_with_scale() {
    394
            let block = BlockQ5K {
    395
                d: 1.0,
    396
                dmin: 0.5,
    397
                scales: [33; 12], // Scale of 1 (33 - 32 = 1)
    398
                qh: [0xFF; 32],   // All high bits set
    399
                qs: [0xFF; 128],  // All low bits set (15,15)
    400
            };
    401
    402
            let mut output = [0.0f32; 256];
    403
            block.dequantize(&mut output);
    404
    405
            // Values should be non-zero with positive scale
    406
            let non_zero_count = output.iter().filter(|&&v| v.abs() > 1e-6).count();
    407
            assert!(non_zero_count > 0, "Should have non-zero values");
    408
        }
    409
    410
        #[test]
    411
        fn test_block_q5k_dequantize_alternating() {
    412
            let block = BlockQ5K {
    413
                d: 0.5,
    414
                dmin: 0.1,
    415
                scales: [34; 12], // Scale of 2
    416
                qh: [0xAA; 32],   // Alternating bits
    417
                qs: [0x55; 128],  // Alternating nibbles (5,5)
    418
            };
    419
    420
            let mut output = [0.0f32; 256];
    421
            block.dequantize(&mut output);
    422
    423
            // All values should be finite
    424
            for val in &output {
    425
                assert!(val.is_finite(), "Value should be finite");
    426
            }
    427
        }
    428
    429
        #[test]
    430
        fn test_block_q5k_dequantize_odd_even_bytes() {
    431
            // Test both even and odd index paths in dequantization
    432
            let block = BlockQ5K {
    433
                d: 1.0,
    434
                dmin: 0.0,
    435
                scales: [48; 12], // Scale of 16 (48 - 32 = 16)
    436
                qh: [0; 32],
    437
                qs: [0x12; 128], // Low nibble = 2, high nibble = 1
    438
            };
    439
    440
            let mut output = [0.0f32; 256];
    441
            block.dequantize(&mut output);
    442
    443
            // Check that alternating values differ (even vs odd extraction)
    444
            // Since qs[i] = 0x12, even indices extract 2, odd indices extract 1
    445
            // Note: the actual dequant formula is complex, but values should differ
    446
            assert!(output[0] != output[1] || output[0].abs() < 1e-6);
    447
        }
    448
    449
        // ===== BlockQ6K Tests =====
    450
    451
        #[test]
    452
        fn test_block_q6k_size() {
    453
            assert_eq!(BlockQ6K::BLOCK_SIZE, 256);
    454
        }
    455
    456
        #[test]
    457
        fn test_block_q6k_dequantize_basic() {
    458
            let block = BlockQ6K {
    459
                ql: [0; 128],
    460
                qh: [0; 64],
    461
                scales: [0; 16], // Zero scales
    462
                d: 0.1,
    463
            };
    464
    465
            let mut output = [0.0f32; 256];
    466
            block.dequantize(&mut output);
    467
    468
            // With scale=0, all outputs should be d * 0 * (q6 - 32) = 0
    469
            for val in &output {
    470
                assert!(val.abs() < 1e-6, "Expected 0, got {}", val);
    471
            }
    472
        }
    473
    474
        #[test]
    475
        fn test_block_q6k_dequantize_with_scale() {
    476
            let block = BlockQ6K {
    477
                ql: [0xFF; 128], // Max low bits
    478
                qh: [0xFF; 64],  // Max high bits
    479
                scales: [1; 16], // Positive scale
    480
                d: 0.5,
    481
            };
    482
    483
            let mut output = [0.0f32; 256];
    484
            block.dequantize(&mut output);
    485
    486
            // Values should be non-zero
    487
            let non_zero = output.iter().any(|&v| v.abs() > 1e-6);
    488
            assert!(non_zero, "Should have non-zero values");
    489
        }
    490
    491
        #[test]
    492
        fn test_block_q6k_dequantize_negative_scale() {
    493
            let block = BlockQ6K {
    494
                ql: [0x88; 128],
    495
                qh: [0x55; 64],
    496
                scales: [-1; 16], // Negative scale
    497
                d: 1.0,
    498
            };
    499
    500
            let mut output = [0.0f32; 256];
    501
            block.dequantize(&mut output);
    502
    503
            // All values should be finite
    504
            for val in &output {
    505
                assert!(val.is_finite());
    506
            }
    507
        }
    508
    509
        #[test]
    510
        fn test_block_q6k_dequantize_all_subblocks() {
    511
            // Test that all 16 sub-blocks are processed
    512
            let block = BlockQ6K {
    513
                ql: [0x12; 128],
    514
                qh: [0x03; 64],  // Different pattern per position
    515
                scales: [1, 2, 3, 4, 5, 6, 7, 8, -1, -2, -3, -4, -5, -6, -7, -8],
    516
                d: 0.1,
    517
            };
    518
    519
            let mut output = [0.0f32; 256];
    520
            block.dequantize(&mut output);
    521
    522
            // Check values at different sub-block boundaries
    523
            assert!(output[0].is_finite());
    524
            assert!(output[15].is_finite());
    525
            assert!(output[16].is_finite());
    526
            assert!(output[127].is_finite());
    527
            assert!(output[255].is_finite());
    528
        }
    529
    530
        #[test]
    531
        fn test_block_q6k_qh_extraction() {
    532
            // Test the 2-bit high value extraction logic
    533
            // qh_shift cycles through 0, 2, 4, 6 for i % 4 = 0, 1, 2, 3
    534
            let block = BlockQ6K {
    535
                ql: [0; 128],
    536
                qh: [0b11_10_01_00; 64], // Pattern: 0,1,2,3 across 4 positions
    537
                scales: [1; 16],
    538
                d: 1.0,
    539
            };
    540
    541
            let mut output = [0.0f32; 256];
    542
            block.dequantize(&mut output);
    543
    544
            // Different qh values should produce different outputs
    545
            // Position 0: qh_val = 0, Position 1: qh_val = 1, etc.
    546
            // This tests the (i % 4) * 2 shift logic
    547
            assert!(output[0].is_finite());
    548
            assert!(output[1].is_finite());
    549
            assert!(output[2].is_finite());
    550
            assert!(output[3].is_finite());
    551
        }
    552
    553
        // ===== DotQ5KOp Tests =====
    554
    555
        #[test]
    556
        fn test_dot_q5k_new() {
    557
            let op = DotQ5KOp::new(512);
    558
            assert_eq!(op.n_blocks, 2);
    559
        }
    560
    561
        #[test]
    562
        fn test_dot_q5k_name() {
    563
            let op = DotQ5KOp::new(256);
    564
            assert_eq!(op.name(), "dot_q5k");
    565
        }
    566
    567
        #[test]
    568
        fn test_dot_q5k_empty() {
    569
            let op = DotQ5KOp::new(256);
    570
            let result = op.execute((vec![], vec![]), Backend::Scalar).unwrap();
    571
            assert!((result - 0.0).abs() < 1e-6);
    572
        }
    573
    574
        #[test]
    575
        fn test_dot_q5k_empty_activations() {
    576
            let op = DotQ5KOp::new(256);
    577
            let block = BlockQ5K {
    578
                d: 1.0,
    579
                dmin: 0.0,
    580
                scales: [32; 12],
    581
                qh: [0; 32],
    582
                qs: [0; 128],
    583
            };
    584
            let result = op.execute((vec![block], vec![]), Backend::Scalar).unwrap();
    585
            assert!((result - 0.0).abs() < 1e-6);
    586
        }
    587
    588
        #[test]
    589
        fn test_dot_q5k_tokens() {
    590
            let op = DotQ5KOp::new(512); // 2 blocks
    591
            let input = (vec![], vec![]);
    592
            assert_eq!(op.tokens(&input), 512);
    593
        }
    594
    595
        #[test]
    596
        fn test_dot_q5k_scalar_execution() {
    597
            let op = DotQ5KOp::new(256);
    598
            let block = BlockQ5K {
    599
                d: 1.0,
    600
                dmin: 0.0,
    601
                scales: [33; 12], // Scale = 1
    602
                qh: [0; 32],
    603
                qs: [0x88; 128], // Mid-range values
    604
            };
    605
            let x = vec![1.0f32; 256];
    606
            let result = op.execute((vec![block], x), Backend::Scalar).unwrap();
    607
            assert!(result.is_finite());
    608
        }
    609
    610
        #[test]
    611
        fn test_dot_q5k_multiple_blocks() {
    612
            let op = DotQ5KOp::new(512);
    613
            let block = BlockQ5K {
    614
                d: 0.5,
    615
                dmin: 0.1,
    616
                scales: [34; 12],
    617
                qh: [0; 32],
    618
                qs: [0x44; 128],
    619
            };
    620
            let x = vec![0.5f32; 512];
    621
            let result = op.execute((vec![block.clone(), block], x), Backend::Scalar).unwrap();
    622
            assert!(result.is_finite());
    623
        }
    624
    625
        #[test]
    626
        fn test_dot_q5k_auto_backend() {
    627
            let op = DotQ5KOp::new(256);
    628
            let block = BlockQ5K {
    629
                d: 1.0,
    630
                dmin: 0.0,
    631
                scales: [32; 12],
    632
                qh: [0; 32],
    633
                qs: [0; 128],
    634
            };
    635
            let x = vec![1.0f32; 256];
    636
            // Auto backend should work (may use AVX2 if available)
    637
            let result = op.execute((vec![block], x), Backend::Auto).unwrap();
    638
            assert!(result.is_finite());
    639
        }
    640
    641
        #[test]
    642
        fn test_dot_q5k_avx2_backend() {
    643
            let op = DotQ5KOp::new(256);
    644
            let block = BlockQ5K {
    645
                d: 1.0,
    646
                dmin: 0.0,
    647
                scales: [33; 12],
    648
                qh: [0; 32],
    649
                qs: [0x11; 128],
    650
            };
    651
            let x = vec![2.0f32; 256];
    652
            // Request AVX2, will fall back to scalar if not available
    653
            let result = op.execute((vec![block], x), Backend::Avx2).unwrap();
    654
            assert!(result.is_finite());
    655
        }
    656
    657
        // ===== DotQ6KOp Tests =====
    658
    659
        #[test]
    660
        fn test_dot_q6k_new() {
    661
            let op = DotQ6KOp::new(768);
    662
            assert_eq!(op.n_blocks, 3);
    663
        }
    664
    665
        #[test]
    666
        fn test_dot_q6k_name() {
    667
            let op = DotQ6KOp::new(256);
    668
            assert_eq!(op.name(), "dot_q6k");
    669
        }
    670
    671
        #[test]
    672
        fn test_dot_q6k_empty() {
    673
            let op = DotQ6KOp::new(256);
    674
            let result = op.execute((vec![], vec![]), Backend::Scalar).unwrap();
    675
            assert!((result - 0.0).abs() < 1e-6);
    676
        }
    677
    678
        #[test]
    679
        fn test_dot_q6k_empty_activations() {
    680
            let op = DotQ6KOp::new(256);
    681
            let block = BlockQ6K {
    682
                ql: [0; 128],
    683
                qh: [0; 64],
    684
                scales: [0; 16],
    685
                d: 1.0,
    686
            };
    687
            let result = op.execute((vec![block], vec![]), Backend::Scalar).unwrap();
    688
            assert!((result - 0.0).abs() < 1e-6);
    689
        }
    690
    691
        #[test]
    692
        fn test_dot_q6k_tokens() {
    693
            let op = DotQ6KOp::new(768); // 3 blocks
    694
            let input = (vec![], vec![]);
    695
            assert_eq!(op.tokens(&input), 768);
    696
        }
    697
    698
        #[test]
    699
        fn test_dot_q6k_scalar_execution() {
    700
            let op = DotQ6KOp::new(256);
    701
            let block = BlockQ6K {
    702
                ql: [0x55; 128],
    703
                qh: [0x55; 64],
    704
                scales: [1; 16],
    705
                d: 0.5,
    706
            };
    707
            let x = vec![1.0f32; 256];
    708
            let result = op.execute((vec![block], x), Backend::Scalar).unwrap();
    709
            assert!(result.is_finite());
    710
        }
    711
    712
        #[test]
    713
        fn test_dot_q6k_multiple_blocks() {
    714
            let op = DotQ6KOp::new(512);
    715
            let block = BlockQ6K {
    716
                ql: [0x33; 128],
    717
                qh: [0x33; 64],
    718
                scales: [2; 16],
    719
                d: 0.25,
    720
            };
    721
            let x = vec![0.5f32; 512];
    722
            let result = op.execute((vec![block.clone(), block], x), Backend::Scalar).unwrap();
    723
            assert!(result.is_finite());
    724
        }
    725
    726
        #[test]
    727
        fn test_dot_q6k_auto_backend() {
    728
            let op = DotQ6KOp::new(256);
    729
            let block = BlockQ6K {
    730
                ql: [0; 128],
    731
                qh: [0; 64],
    732
                scales: [1; 16],
    733
                d: 1.0,
    734
            };
    735
            let x = vec![1.0f32; 256];
    736
            let result = op.execute((vec![block], x), Backend::Auto).unwrap();
    737
            assert!(result.is_finite());
    738
        }
    739
    740
        #[test]
    741
        fn test_dot_q6k_avx2_backend() {
    742
            let op = DotQ6KOp::new(256);
    743
            let block = BlockQ6K {
    744
                ql: [0xAA; 128],
    745
                qh: [0xAA; 64],
    746
                scales: [3; 16],
    747
                d: 0.1,
    748
            };
    749
            let x = vec![2.0f32; 256];
    750
            let result = op.execute((vec![block], x), Backend::Avx2).unwrap();
    751
            assert!(result.is_finite());
    752
        }
    753
    754
        // ===== Backend Equivalence Tests =====
    755
    756
        #[test]
    757
        fn test_q5k_backend_equivalence() {
    758
            let op = DotQ5KOp::new(256);
    759
            let block = BlockQ5K {
    760
                d: 0.5,
    761
                dmin: 0.1,
    762
                scales: [35; 12],
    763
                qh: [0x55; 32],
    764
                qs: [0x77; 128],
    765
            };
    766
            let x = vec![1.5f32; 256];
    767
    768
            let scalar = op.execute((vec![block.clone()], x.clone()), Backend::Scalar).unwrap();
    769
            let auto = op.execute((vec![block], x), Backend::Auto).unwrap();
    770
    771
            // Allow small FP differences due to SIMD operation ordering
    772
            let rel_diff = (scalar - auto).abs() / scalar.abs().max(1e-6);
    773
            assert!(rel_diff < 1e-4, "scalar={scalar}, auto={auto}, rel_diff={rel_diff}");
    774
        }
    775
    776
        #[test]
    777
        fn test_q6k_backend_equivalence() {
    778
            let op = DotQ6KOp::new(256);
    779
            let block = BlockQ6K {
    780
                ql: [0x66; 128],
    781
                qh: [0x22; 64],
    782
                scales: [4; 16],
    783
                d: 0.2,
    784
            };
    785
            let x = vec![1.5f32; 256];
    786
    787
            let scalar = op.execute((vec![block.clone()], x.clone()), Backend::Scalar).unwrap();
    788
            let auto = op.execute((vec![block], x), Backend::Auto).unwrap();
    789
    790
            // Allow small FP differences due to SIMD operation ordering
    791
            let rel_diff = (scalar - auto).abs() / scalar.abs().max(1e-6);
    792
            assert!(rel_diff < 1e-4, "scalar={scalar}, auto={auto}, rel_diff={rel_diff}");
    793
        }
    794
    795
        // ===== Clone/Debug Trait Tests =====
    796
    797
        #[test]
    798
        fn test_block_q5k_clone_debug() {
    799
            let block = BlockQ5K {
    800
                d: 1.0,
    801
                dmin: 0.5,
    802
                scales: [32; 12],
    803
                qh: [0; 32],
    804
                qs: [0; 128],
    805
            };
    806
            let cloned = block.clone();
    807
            assert_eq!(format!("{:?}", block), format!("{:?}", cloned));
    808
        }
    809
    810
        #[test]
    811
        fn test_block_q6k_clone_debug() {
    812
            let block = BlockQ6K {
    813
                ql: [0; 128],
    814
                qh: [0; 64],
    815
                scales: [0; 16],
    816
                d: 1.0,
    817
            };
    818
            let cloned = block.clone();
    819
            assert_eq!(format!("{:?}", block), format!("{:?}", cloned));
    820
        }
    821
    822
        #[test]
    823
        fn test_dot_q5k_op_clone_debug() {
    824
            let op = DotQ5KOp::new(256);
    825
            let cloned = op.clone();
    826
            assert_eq!(format!("{:?}", op), format!("{:?}", cloned));
    827
        }
    828
    829
        #[test]
    830
        fn test_dot_q6k_op_clone_debug() {
    831
            let op = DotQ6KOp::new(256);
    832
            let cloned = op.clone();
    833
            assert_eq!(format!("{:?}", op), format!("{:?}", cloned));
    834
        }
    835
    }