## 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/inference/cached/weights.rs

    Line
    Count
    Source
    1
    //! Dequantized weight cache for GPU GEMM operations
    2
    //!
    3
    //! Stores pre-dequantized f32 weights for GPU GEMM to avoid
    4
    //! repeated dequantization on every forward pass.
    5
    6
    7
    /// Dequantized FFN weights for a single transformer layer
    8
    ///
    9
    /// Stores pre-dequantized f32 weights for GPU GEMM operations.
    10
    /// Cache these to avoid repeated dequantization on every forward pass.
    11
    #[derive(Clone)]
    12
    pub struct DequantizedFFNWeights {
    13
        /// Up projection weights [hidden_dim, intermediate_dim]
    14
        pub up: Vec<f32>,
    15
        /// Down projection weights [intermediate_dim, hidden_dim]
    16
        pub down: Vec<f32>,
    17
        /// Optional up bias [intermediate_dim]
    18
        pub up_bias: Option<Vec<f32>>,
    19
        /// Optional down bias [hidden_dim]
    20
        pub down_bias: Option<Vec<f32>>,
    21
    }
    22
    23
    /// Cache for dequantized FFN weights (PARITY-019)
    24
    ///
    25
    /// Uses RwLock for concurrent read access during batch inference.
    26
    /// Weights are dequantized once during warmup and reused for GPU GEMM.
    27
    ///
    28
    /// # Performance Impact
    29
    /// - Eliminates per-forward dequantization overhead
    30
    /// - Enables GPU GEMM with f32 weights
    31
    /// - Memory tradeoff: ~6.4 GB for phi-2 32 layers
    32
    ///
    33
    /// # Thread Safety
    34
    /// - RwLock allows multiple concurrent readers during inference
    35
    /// - Single writer during warmup phase
    36
    #[cfg(feature = "gpu")]
    37
    pub struct DequantizedWeightCache {
    38
        /// Per-layer dequantized weights
    39
        layers: std::sync::RwLock<std::collections::HashMap<usize, DequantizedFFNWeights>>,
    40
        /// Hidden dimension for validation
    41
        hidden_dim: usize,
    42
        /// Intermediate FFN dimension
    43
        intermediate_dim: usize,
    44
        /// Number of layers to cache
    45
        num_layers: usize,
    46
    }
    47
    48
    #[cfg(feature = "gpu")]
    49
    impl DequantizedWeightCache {
    50
        /// Create a new weight cache with specified dimensions
    51
        ///
    52
        /// # Arguments
    53
        /// * `hidden_dim` - Model hidden dimension (e.g., 2560 for phi-2)
    54
        /// * `intermediate_dim` - FFN intermediate dimension (e.g., 10240 for phi-2)
    55
        /// * `num_layers` - Number of transformer layers to cache
    56
        #[must_use]
    57
    6
        pub fn new(hidden_dim: usize, intermediate_dim: usize, num_layers: usize) -> Self {
    58
    6
            Self {
    59
    6
                layers: std::sync::RwLock::new(std::collections::HashMap::with_capacity(num_layers)),
    60
    6
                hidden_dim,
    61
    6
                intermediate_dim,
    62
    6
                num_layers,
    63
    6
            }
    64
    6
        }
    65
    66
        /// Pre-warmup all layers with dequantized weights
    67
        ///
    68
        /// Call this once at startup to avoid dequantization during inference.
    69
        /// The closure receives layer index and returns (up_weights, down_weights).
    70
        ///
    71
        /// # Arguments
    72
        /// * `dequant_fn` - Closure that dequantizes weights for a given layer index
    73
        ///
    74
        /// # Panics
    75
        /// Panics if the RwLock is poisoned
    76
    4
        pub fn warmup<F>(&self, dequant_fn: F)
    77
    4
        where
    78
    4
            F: Fn(usize) -> (Vec<f32>, Vec<f32>),
    79
        {
    80
    4
            let mut cache = self.layers.write().expect("Cache lock poisoned");
    81
    11
            for layer_idx in 0..self.num_layers4 {
    82
    11
                cache.entry(layer_idx).or_insert_with(|| {
    83
    11
                    let (up, down) = dequant_fn(layer_idx);
    84
    11
                    DequantizedFFNWeights {
    85
    11
                        up,
    86
    11
                        down,
    87
    11
                        up_bias: None,
    88
    11
                        down_bias: None,
    89
    11
                    }
    90
    11
                });
    91
            }
    92
    4
        }
    93
    94
        /// Warmup with biases
    95
        ///
    96
        /// Same as `warmup` but also caches bias vectors.
    97
    1
        pub fn warmup_with_bias<F>(&self, dequant_fn: F)
    98
    1
        where
    99
    1
            F: Fn(usize) -> (Vec<f32>, Vec<f32>, Option<Vec<f32>>, Option<Vec<f32>>),
    100
        {
    101
    1
            let mut cache = self.layers.write().expect("Cache lock poisoned");
    102
    2
            for layer_idx in 0..self.num_layers1 {
    103
    2
                cache.entry(layer_idx).or_insert_with(|| {
    104
    2
                    let (up, down, up_bias, down_bias) = dequant_fn(layer_idx);
    105
    2
                    DequantizedFFNWeights {
    106
    2
                        up,
    107
    2
                        down,
    108
    2
                        up_bias,
    109
    2
                        down_bias,
    110
    2
                    }
    111
    2
                });
    112
            }
    113
    1
        }
    114
    115
        /// Get cached weights for a layer (read-only access)
    116
        ///
    117
        /// Returns None if the layer hasn't been warmed up.
    118
        /// Uses read lock for concurrent access during batch inference.
    119
    21
        pub fn get(&self, layer_idx: usize) -> Option<DequantizedFFNWeights> {
    120
    21
            let cache = self.layers.read().expect("Cache lock poisoned");
    121
    21
            cache.get(&layer_idx).cloned()
    122
    21
        }
    123
    124
        /// Check if a layer is cached
    125
    8
        pub fn is_cached(&self, layer_idx: usize) -> bool {
    126
    8
            let cache = self.layers.read().expect("Cache lock poisoned");
    127
    8
            cache.contains_key(&layer_idx)
    128
    8
        }
    129
    130
        /// Get number of cached layers
    131
    14
        pub fn cached_count(&self) -> usize {
    132
    14
            let cache = self.layers.read().expect("Cache lock poisoned");
    133
    14
            cache.len()
    134
    14
        }
    135
    136
        /// Get total memory usage in bytes
    137
    5
        pub fn memory_bytes(&self) -> usize {
    138
            // Each layer: up + down weights
    139
            // up: hidden_dim × intermediate_dim × 4 bytes
    140
            // down: intermediate_dim × hidden_dim × 4 bytes
    141
    5
            let per_layer = 2 * self.hidden_dim * self.intermediate_dim * 4;
    142
    5
            self.cached_count() * per_layer
    143
    5
        }
    144
    145
        /// Get model dimensions
    146
        #[must_use]
    147
    2
        pub fn dimensions(&self) -> (usize, usize, usize) {
    148
    2
            (self.hidden_dim, self.intermediate_dim, self.num_layers)
    149
    2
        }
    150
    }