1use crate::{AttentionMask, Result, SparseError};
4use serde::{Deserialize, Serialize};
5
6#[derive(Debug, Clone, Serialize, Deserialize)]
8pub struct KernelConfig {
9 pub hidden_dim: usize,
11 pub num_heads: usize,
13 pub head_dim: usize,
15 pub seq_len: usize,
17 pub use_flash: bool,
19 pub memory_format: MemoryFormat,
21}
22
23#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
25pub enum MemoryFormat {
26 Contiguous,
28 ChannelsLast,
30 Grouped,
32}
33
34impl Default for KernelConfig {
35 fn default() -> Self {
36 Self {
37 hidden_dim: 2048,
38 num_heads: 32,
39 head_dim: 64,
40 seq_len: 4096,
41 use_flash: true,
42 memory_format: MemoryFormat::Contiguous,
43 }
44 }
45}
46
47#[derive(Debug)]
68pub struct SparseKernel {
69 config: KernelConfig,
70 index_cache: std::collections::HashMap<u64, IndexMapping>,
72}
73
74#[derive(Debug, Clone)]
76struct IndexMapping {
77 active_heads: Vec<usize>,
79 #[allow(dead_code)]
81 scatter_indices: Vec<usize>,
82 #[allow(dead_code)]
84 pattern_hash: u64,
85}
86
87impl SparseKernel {
88 pub fn new(config: KernelConfig) -> Self {
90 Self {
91 config,
92 index_cache: std::collections::HashMap::new(),
93 }
94 }
95
96 pub fn prepare(&mut self, mask: &AttentionMask, layer: usize) -> Result<()> {
98 let pattern_hash = self.compute_pattern_hash(mask, layer);
99
100 self.index_cache.entry(pattern_hash).or_insert_with(|| {
101 let active_heads = mask.active_heads(layer);
102 let scatter_indices: Vec<usize> =
103 active_heads.iter().enumerate().map(|(i, _)| i).collect();
104
105 IndexMapping {
106 active_heads,
107 scatter_indices,
108 pattern_hash,
109 }
110 });
111
112 Ok(())
113 }
114
115 pub fn execute(
122 &self,
123 mask: &AttentionMask,
124 layer: usize,
125 _q: &[f32], _k: &[f32],
127 _v: &[f32],
128 ) -> Result<Vec<f32>> {
129 let pattern_hash = self.compute_pattern_hash(mask, layer);
130
131 let mapping = self
132 .index_cache
133 .get(&pattern_hash)
134 .ok_or_else(|| SparseError::KernelError("Mask pattern not prepared".into()))?;
135
136 let batch_size = 1; let output_size =
139 batch_size * self.config.seq_len * self.config.num_heads * self.config.head_dim;
140
141 let mut output = vec![0.0f32; output_size];
147
148 for &head in &mapping.active_heads {
150 let offset = head * self.config.head_dim;
151 for d in 0..self.config.head_dim {
152 if offset + d < output.len() {
153 output[offset + d] = 1.0; }
155 }
156 }
157
158 Ok(output)
159 }
160
161 fn compute_pattern_hash(&self, mask: &AttentionMask, layer: usize) -> u64 {
163 use std::hash::{Hash, Hasher};
164 let mut hasher = std::collections::hash_map::DefaultHasher::new();
165
166 layer.hash(&mut hasher);
167 for head in 0..mask.num_heads {
168 mask.is_active(layer, head).hash(&mut hasher);
169 }
170
171 hasher.finish()
172 }
173
174 pub fn estimate_savings(&self, mask: &AttentionMask) -> ComputeEstimate {
176 let total_heads = mask.num_heads * mask.num_layers;
177 let active_heads: usize = (0..mask.num_layers).map(|l| mask.active_count(l)).sum();
178
179 let compute_ratio = active_heads as f32 / total_heads as f32;
180
181 let memory_ratio = compute_ratio * 0.9 + 0.1; let attention_ratio = compute_ratio; ComputeEstimate {
188 compute_ratio,
189 memory_ratio,
190 attention_ratio,
191 estimated_speedup: 1.0 / compute_ratio,
192 active_heads,
193 total_heads,
194 }
195 }
196
197 pub fn config(&self) -> &KernelConfig {
199 &self.config
200 }
201
202 pub fn clear_cache(&mut self) {
204 self.index_cache.clear();
205 }
206}
207
208#[derive(Debug, Clone, Serialize, Deserialize)]
210pub struct ComputeEstimate {
211 pub compute_ratio: f32,
213 pub memory_ratio: f32,
215 pub attention_ratio: f32,
217 pub estimated_speedup: f32,
219 pub active_heads: usize,
221 pub total_heads: usize,
223}
224
225#[derive(Debug, Clone, Default, Serialize, Deserialize)]
227pub struct KernelStats {
228 pub executions: u64,
230 pub cache_hits: u64,
232 pub avg_sparsity: f32,
234 pub compute_saved: f64,
236}
237
238#[cfg(test)]
239mod tests {
240 use super::*;
241
242 #[test]
243 fn test_kernel_prepare() {
244 let mut kernel = SparseKernel::new(KernelConfig::default());
245 let mask = AttentionMask::random(32, 10, 0.5);
246
247 kernel.prepare(&mask, 0).unwrap();
248 kernel.prepare(&mask, 5).unwrap();
249
250 assert!(!kernel.index_cache.is_empty());
252 }
253
254 #[test]
255 fn test_compute_estimate() {
256 let kernel = SparseKernel::new(KernelConfig::default());
257 let mask = AttentionMask::random(32, 10, 0.5);
258
259 let estimate = kernel.estimate_savings(&mask);
260
261 assert!(estimate.compute_ratio > 0.4 && estimate.compute_ratio < 0.7);
263 assert!(estimate.estimated_speedup > 1.4 && estimate.estimated_speedup < 2.5);
264 }
265
266 #[test]
267 fn test_execute() {
268 let mut kernel = SparseKernel::new(KernelConfig {
269 num_heads: 8,
270 head_dim: 64,
271 seq_len: 16,
272 ..Default::default()
273 });
274
275 let mask = AttentionMask::random(8, 4, 0.5);
276 kernel.prepare(&mask, 0).unwrap();
277
278 let q = vec![0.0f32; 16 * 8 * 64];
280 let k = vec![0.0f32; 16 * 8 * 64];
281 let v = vec![0.0f32; 16 * 8 * 64];
282
283 let output = kernel.execute(&mask, 0, &q, &k, &v).unwrap();
284 assert!(!output.is_empty());
285 }
286}