Coverage Report

Created: 2026-01-25 15:05

next uncovered line (L), next uncovered region (R), next uncovered branch (B)
/home/noah/src/realizar/src/gguf/test_helpers.rs
Line
Count
Source
1
//! Test helpers for GGUF module testing
2
//!
3
//! This module contains shared test utilities that are used across
4
//! multiple test files in the GGUF module shatter.
5
//!
6
//! ## Contents
7
//!
8
//! - `create_test_model_with_config`: Creates a minimal OwnedQuantizedModel for testing
9
//! - `create_q4k_test_data`: Creates Q4_K quantized tensor data for testing
10
//!
11
//! ## Usage
12
//!
13
//! ```rust,ignore
14
//! #[cfg(test)]
15
//! use crate::gguf::test_helpers::{create_test_model_with_config, create_q4k_test_data};
16
//! ```
17
18
use crate::gguf::{
19
    GGUFConfig, OwnedQKVWeights, OwnedQuantizedLayer, OwnedQuantizedModel, OwnedQuantizedTensor,
20
};
21
22
/// Create a test model with specific configuration
23
///
24
/// This helper creates a minimal `OwnedQuantizedModel` with deterministic
25
/// test weights for verifying attention, FFN, and other model operations.
26
///
27
/// # Arguments
28
///
29
/// * `config` - The model configuration to use
30
///
31
/// # Returns
32
///
33
/// An `OwnedQuantizedModel` with test weights
34
115
pub(crate) fn create_test_model_with_config(config: &GGUFConfig) -> OwnedQuantizedModel {
35
    // Create minimal model weights for testing
36
115
    let vocab_size = config.vocab_size;
37
115
    let hidden_dim = config.hidden_dim;
38
115
    let intermediate_dim = config.intermediate_dim;
39
115
    let kv_dim = config.num_kv_heads * (hidden_dim / config.num_heads);
40
41
    // QKV projection: hidden_dim -> hidden_dim + 2*kv_dim (Q + K + V)
42
115
    let qkv_out_dim = hidden_dim + 2 * kv_dim;
43
115
    let qkv_weight = create_q4k_test_data(hidden_dim, qkv_out_dim);
44
45
    // Output projection: hidden_dim -> hidden_dim
46
115
    let attn_output_weight = create_q4k_test_data(hidden_dim, hidden_dim);
47
48
    // FFN weights
49
115
    let ffn_up_weight = create_q4k_test_data(hidden_dim, intermediate_dim);
50
115
    let ffn_down_weight = create_q4k_test_data(intermediate_dim, hidden_dim);
51
52
    // Layer norm weights
53
115
    let attn_norm_weight = vec![1.0f32; hidden_dim];
54
55
115
    let layer = OwnedQuantizedLayer {
56
115
        attn_norm_weight,
57
115
        attn_norm_bias: None,
58
115
        qkv_weight: OwnedQKVWeights::Fused(qkv_weight),
59
115
        qkv_bias: None,
60
115
        attn_output_weight,
61
115
        attn_output_bias: None,
62
115
        ffn_up_weight,
63
115
        ffn_up_bias: None,
64
115
        ffn_down_weight,
65
115
        ffn_down_bias: None,
66
115
        ffn_gate_weight: None,
67
115
        ffn_gate_bias: None,
68
115
        ffn_norm_weight: None,
69
115
        ffn_norm_bias: None,
70
115
    };
71
72
115
    let token_embedding = vec![0.1f32; vocab_size * hidden_dim];
73
115
    let output_norm_weight = vec![1.0f32; hidden_dim];
74
115
    let lm_head_weight = create_q4k_test_data(hidden_dim, vocab_size);
75
76
115
    OwnedQuantizedModel {
77
115
        config: config.clone(),
78
115
        token_embedding,
79
115
        layers: vec![layer],
80
115
        output_norm_weight,
81
115
        output_norm_bias: None,
82
115
        lm_head_weight,
83
115
        lm_head_bias: None,
84
115
        #[cfg(feature = "cuda")]
85
115
        cuda_executor: None,
86
115
        #[cfg(feature = "cuda")]
87
115
        cuda_kernel_count: std::sync::atomic::AtomicU64::new(0),
88
115
        #[cfg(feature = "cuda")]
89
115
        cached_weight_names: std::sync::Mutex::new(std::collections::HashSet::new()),
90
115
    }
91
115
}
92
93
/// Create Q4_K test data for given dimensions
94
///
95
/// Q4_K uses row-major storage where each row has ceil(in_dim/256) super-blocks.
96
/// Each super-block is 144 bytes and covers 256 values.
97
///
98
/// # Arguments
99
///
100
/// * `in_dim` - Input dimension (number of columns)
101
/// * `out_dim` - Output dimension (number of rows)
102
///
103
/// # Returns
104
///
105
/// An `OwnedQuantizedTensor` with Q4_K quantized test data
106
577
pub(crate) fn create_q4k_test_data(in_dim: usize, out_dim: usize) -> OwnedQuantizedTensor {
107
    // Row-major storage: each row needs ceil(in_dim/256) super-blocks
108
577
    let super_blocks_per_row = in_dim.div_ceil(256);
109
577
    let bytes_per_row = super_blocks_per_row * 144;
110
577
    let data_size = out_dim * bytes_per_row;
111
577
    let mut data = vec![0u8; data_size];
112
113
81.3k
    for row in 0..
out_dim577
{
114
89.1k
        for sb in 0..
super_blocks_per_row81.3k
{
115
89.1k
            let offset = row * bytes_per_row + sb * 144;
116
            // d=1.0 in f16 format
117
89.1k
            data[offset..offset + 2].copy_from_slice(&0x3C00_u16.to_le_bytes());
118
            // dmin=0
119
89.1k
            data[offset + 2..offset + 4].copy_from_slice(&0x0000_u16.to_le_bytes());
120
            // Fill scales and quantized values with deterministic test pattern
121
12.5M
            for 
i12.4M
in 4..144 {
122
12.4M
                data[offset + i] = ((row + sb + i) % 16) as u8;
123
12.4M
            }
124
        }
125
    }
126
127
577
    OwnedQuantizedTensor {
128
577
        data,
129
577
        in_dim,
130
577
        out_dim,
131
577
        qtype: 12, // Q4_K
132
577
    }
133
577
}
134
135
#[cfg(test)]
136
mod tests {
137
    use super::*;
138
139
    #[test]
140
1
    fn test_create_q4k_test_data_basic() {
141
1
        let tensor = create_q4k_test_data(256, 64);
142
1
        assert_eq!(tensor.in_dim, 256);
143
1
        assert_eq!(tensor.out_dim, 64);
144
1
        assert_eq!(tensor.qtype, 12); // Q4_K
145
        // 1 super-block per row, 144 bytes each, 64 rows
146
1
        assert_eq!(tensor.data.len(), 64 * 144);
147
1
    }
148
149
    #[test]
150
1
    fn test_create_q4k_test_data_multi_superblock() {
151
        // 512 values needs 2 super-blocks per row
152
1
        let tensor = create_q4k_test_data(512, 32);
153
1
        assert_eq!(tensor.in_dim, 512);
154
1
        assert_eq!(tensor.out_dim, 32);
155
        // 2 super-blocks per row, 144 bytes each, 32 rows
156
1
        assert_eq!(tensor.data.len(), 32 * 2 * 144);
157
1
    }
158
159
    #[test]
160
1
    fn test_create_test_model_with_config_basic() {
161
1
        let config = GGUFConfig {
162
1
            architecture: "test".to_string(),
163
1
            hidden_dim: 64,
164
1
            intermediate_dim: 128,
165
1
            num_heads: 4,
166
1
            num_kv_heads: 4,
167
1
            num_layers: 1,
168
1
            vocab_size: 100,
169
1
            rope_theta: 10000.0,
170
1
            context_length: 512,
171
1
            eps: 1e-5,
172
1
            rope_type: 0,
173
1
        };
174
175
1
        let model = create_test_model_with_config(&config);
176
1
        assert_eq!(model.config.hidden_dim, 64);
177
1
        assert_eq!(model.layers.len(), 1);
178
1
        assert_eq!(model.token_embedding.len(), 100 * 64);
179
1
    }
180
}