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
}