## 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/backends/q6k/colmajor.rs

    Line
    Count
    Source
    1
    //! Column-major Q6_K matrix-vector multiplication.
    2
    //!
    3
    //! This module implements column-major GEMV for GGML/GGUF format weights,
    4
    //! where weights are stored column-first for cache-efficient streaming.
    5
    6
    use super::{f16_to_f32, SUPER_BLOCK_BYTES, SUPER_BLOCK_SIZE};
    7
    8
    /// Fused Q6_K matrix-vector multiply for GGML column-major layout
    9
    ///
    10
    /// Computes: output = input @ Q6K_weight (GGML convention: y = x @ W)
    11
    /// where weight is stored in Q6_K format with GGML column-major super-block organization.
    12
    ///
    13
    /// # Arguments
    14
    /// * `q6k_data` - Raw Q6K bytes in GGML column-major layout [ne0, ne1]
    15
    /// * `input` - F32 input vector [ne1] (input/reduction dimension)
    16
    /// * `ne0` - Size of output dimension (rows in GGML, output size)
    17
    /// * `ne1` - Size of input/reduction dimension (columns in GGML, input size)
    18
    ///
    19
    /// # Returns
    20
    /// F32 output vector [ne0]
    21
    0
    pub fn matmul_q6k_f32_colmajor(
    22
    0
        q6k_data: &[u8],
    23
    0
        input: &[f32],
    24
    0
        ne0: usize, // output dimension (rows)
    25
    0
        ne1: usize, // input/reduction dimension (columns)
    26
    0
    ) -> Vec<f32> {
    27
    0
        assert_eq!(input.len(), ne1, "Input length must match ne1 (input dimension)");
    28
    29
        // Number of super-blocks per column (each column has ne0 elements = output_dim)
    30
    0
        let blocks_per_col = (ne0 + SUPER_BLOCK_SIZE - 1) / SUPER_BLOCK_SIZE;
    31
    0
        let col_bytes = blocks_per_col * SUPER_BLOCK_BYTES;
    32
    33
    0
        let mut output = vec![0.0f32; ne0];
    34
    35
        // Process each input column and accumulate to outputs
    36
        // Column j contains weights from input[j] to all ne0 outputs
    37
    0
        for col_idx in 0..ne1 {
    38
    0
            let col_start = col_idx * col_bytes;
    39
    0
            let x_j = input[col_idx]; // Input value for this column
    40
    41
            // Skip if input is zero (common in sparse activations)
    42
    0
            if x_j == 0.0 {
    43
    0
                continue;
    44
    0
            }
    45
    46
    0
            for sb_idx in 0..blocks_per_col {
    47
    0
                let sb_start = col_start + sb_idx * SUPER_BLOCK_BYTES;
    48
    0
                if sb_start + SUPER_BLOCK_BYTES > q6k_data.len() {
    49
    0
                    break;
    50
    0
                }
    51
    0
                let sb_data = &q6k_data[sb_start..sb_start + SUPER_BLOCK_BYTES];
    52
    53
    0
                let ql = &sb_data[0..128];
    54
    0
                let qh = &sb_data[128..192];
    55
    0
                let scales = &sb_data[192..208];
    56
    0
                let d = f16_to_f32(u16::from_le_bytes([sb_data[208], sb_data[209]]));
    57
    58
    0
                let output_offset = sb_idx * SUPER_BLOCK_SIZE;
    59
    60
    0
                for group in 0..16 {
    61
    0
                    let scale = (scales[group] as i8) as f32;
    62
    0
                    let group_offset = group * 16;
    63
    64
    0
                    for j in 0..16 {
    65
    0
                        let idx = group_offset + j;
    66
    0
                        let output_idx = output_offset + idx;
    67
    0
                        if output_idx >= ne0 {
    68
    0
                            continue;
    69
    0
                        }
    70
    71
    0
                        let ql_byte = ql[idx / 2];
    72
    0
                        let low4 = if idx % 2 == 0 {
    73
    0
                            ql_byte & 0x0F
    74
                        } else {
    75
    0
                            ql_byte >> 4
    76
                        };
    77
    78
    0
                        let qh_byte = qh[idx / 4];
    79
    0
                        let qh_shift = (idx % 4) * 2;
    80
    0
                        let high2 = (qh_byte >> qh_shift) & 0x03;
    81
    82
    0
                        let q6 = (low4 | (high2 << 4)) as i8 - 32;
    83
    0
                        let dequant = d * scale * q6 as f32;
    84
    0
                        output[output_idx] += x_j * dequant;
    85
                    }
    86
                }
    87
            }
    88
        }
    89
    90
    0
        output
    91
    0
    }
    92
    93
    /// Runtime dispatch for column-major Q6K matmul
    94
    ///
    95
    /// Uses scalar implementation for correctness.
    96
    /// Critical for lm_head which is typically 151936 x 1536 (233M elements).
    97
    #[inline]
    98
    0
    pub fn matmul_q6k_f32_colmajor_dispatch(
    99
    0
        q6k_data: &[u8],
    100
    0
        input: &[f32],
    101
    0
        ne0: usize,
    102
    0
        ne1: usize,
    103
    0
    ) -> Vec<f32> {
    104
    0
        matmul_q6k_f32_colmajor(q6k_data, input, ne0, ne1)
    105
    0
    }