1use crate::{CategoryMapping, HeadCategory, Result, SparseError};
4use serde::{Deserialize, Serialize};
5
6#[derive(Debug, Clone, Serialize, Deserialize)]
8pub struct HeadImportance {
9 pub layer: usize,
11 pub head: usize,
13 pub importance: f32,
15 pub variance: f32,
17 pub category: HeadCategory,
19 pub activation_rate: f32,
21}
22
23#[derive(Debug, Clone, Serialize, Deserialize)]
25pub struct HeadAnalysis {
26 pub model_id: String,
28 pub num_heads: usize,
30 pub num_layers: usize,
32 pub heads: Vec<HeadImportance>,
34 pub category_mapping: CategoryMapping,
36 pub prune_threshold: f32,
38 pub metadata: AnalysisMetadata,
40}
41
42#[derive(Debug, Clone, Serialize, Deserialize)]
44pub struct AnalysisMetadata {
45 pub num_samples: usize,
47 pub categories_analyzed: Vec<String>,
49 pub timestamp: u64,
51 pub method: AnalysisMethod,
53}
54
55#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
57pub enum AnalysisMethod {
58 Gradient,
60 Activation,
62 Ablation,
64 Distillation,
66}
67
68impl HeadAnalysis {
69 pub fn get_importance(&self, layer: usize, head: usize) -> Option<&HeadImportance> {
71 self.heads
72 .iter()
73 .find(|h| h.layer == layer && h.head == head)
74 }
75
76 pub fn important_heads(&self, threshold: f32) -> Vec<&HeadImportance> {
78 self.heads
79 .iter()
80 .filter(|h| h.importance >= threshold)
81 .collect()
82 }
83
84 pub fn heads_by_category(&self, category: HeadCategory) -> Vec<&HeadImportance> {
86 self.heads
87 .iter()
88 .filter(|h| h.category == category)
89 .collect()
90 }
91
92 pub fn layer_importance(&self) -> Vec<f32> {
94 let mut layer_sums = vec![0.0f32; self.num_layers];
95 let mut layer_counts = vec![0usize; self.num_layers];
96
97 for head in &self.heads {
98 layer_sums[head.layer] += head.importance;
99 layer_counts[head.layer] += 1;
100 }
101
102 layer_sums
103 .iter()
104 .zip(layer_counts.iter())
105 .map(|(&sum, &count)| if count > 0 { sum / count as f32 } else { 0.0 })
106 .collect()
107 }
108
109 pub fn suggested_sparsity(&self, target_overall: f32) -> Vec<f32> {
111 let layer_imp = self.layer_importance();
112 let mean_imp: f32 = layer_imp.iter().sum::<f32>() / layer_imp.len() as f32;
113
114 layer_imp
116 .iter()
117 .map(|&imp| {
118 let ratio = if mean_imp > 0.0 { imp / mean_imp } else { 1.0 };
119 (target_overall * (2.0 - ratio)).clamp(0.1, 0.9)
121 })
122 .collect()
123 }
124}
125
126pub struct ImportanceAnalyzer {
128 method: AnalysisMethod,
130 num_samples: usize,
132 activations: Vec<ActivationSample>,
134}
135
136#[derive(Debug, Clone)]
138struct ActivationSample {
139 attention_weights: Vec<Vec<f32>>,
141 category: String,
143 #[allow(dead_code)]
145 step: u32,
146}
147
148impl ImportanceAnalyzer {
149 pub fn new(method: AnalysisMethod) -> Self {
151 Self {
152 method,
153 num_samples: 0,
154 activations: Vec::new(),
155 }
156 }
157
158 pub fn with_samples(mut self, count: usize) -> Self {
160 self.num_samples = count;
161 self
162 }
163
164 pub fn record_sample(&mut self, attention_weights: Vec<Vec<f32>>, category: String, step: u32) {
166 self.activations.push(ActivationSample {
167 attention_weights,
168 category,
169 step,
170 });
171 }
172
173 pub fn analyze(&self, model_id: &str) -> Result<HeadAnalysis> {
175 if self.activations.is_empty() {
176 return Err(SparseError::AnalysisError("No samples collected".into()));
177 }
178
179 let num_layers = self.activations[0].attention_weights.len();
180 let num_heads = self.activations[0]
181 .attention_weights
182 .first()
183 .map(|l| l.len())
184 .unwrap_or(0);
185
186 let heads = match self.method {
188 AnalysisMethod::Activation => self.analyze_activation(num_layers, num_heads),
189 AnalysisMethod::Gradient => self.analyze_activation(num_layers, num_heads), AnalysisMethod::Ablation => self.analyze_activation(num_layers, num_heads), AnalysisMethod::Distillation => self.analyze_activation(num_layers, num_heads), };
193
194 let category_mapping = self.infer_categories(&heads, num_heads, num_layers);
196
197 let mut importances: Vec<f32> = heads.iter().map(|h| h.importance).collect();
199 importances.sort_by(|a, b| a.partial_cmp(b).unwrap());
200 let prune_threshold = importances
201 .get(importances.len() / 2)
202 .copied()
203 .unwrap_or(0.5);
204
205 let categories_analyzed: Vec<String> = self
206 .activations
207 .iter()
208 .map(|s| s.category.clone())
209 .collect::<std::collections::HashSet<_>>()
210 .into_iter()
211 .collect();
212
213 Ok(HeadAnalysis {
214 model_id: model_id.into(),
215 num_heads,
216 num_layers,
217 heads,
218 category_mapping,
219 prune_threshold,
220 metadata: AnalysisMetadata {
221 num_samples: self.activations.len(),
222 categories_analyzed,
223 timestamp: std::time::SystemTime::now()
224 .duration_since(std::time::UNIX_EPOCH)
225 .map(|d| d.as_secs())
226 .unwrap_or(0),
227 method: self.method,
228 },
229 })
230 }
231
232 fn analyze_activation(&self, num_layers: usize, num_heads: usize) -> Vec<HeadImportance> {
234 let mut heads = Vec::with_capacity(num_layers * num_heads);
235
236 for layer in 0..num_layers {
237 for head in 0..num_heads {
238 let values: Vec<f32> = self
240 .activations
241 .iter()
242 .filter_map(|s| {
243 s.attention_weights
244 .get(layer)
245 .and_then(|l| l.get(head))
246 .copied()
247 })
248 .collect();
249
250 if values.is_empty() {
251 continue;
252 }
253
254 let mean: f32 = values.iter().sum::<f32>() / values.len() as f32;
256 let variance: f32 =
257 values.iter().map(|v| (v - mean).powi(2)).sum::<f32>() / values.len() as f32;
258
259 let activation_rate =
261 values.iter().filter(|&&v| v > 0.1).count() as f32 / values.len() as f32;
262
263 let category = Self::infer_head_category(layer, head, num_layers, num_heads, mean);
265
266 heads.push(HeadImportance {
267 layer,
268 head,
269 importance: mean.clamp(0.0, 1.0),
270 variance,
271 category,
272 activation_rate,
273 });
274 }
275 }
276
277 heads
278 }
279
280 fn infer_head_category(
282 layer: usize,
283 head: usize,
284 num_layers: usize,
285 num_heads: usize,
286 _importance: f32,
287 ) -> HeadCategory {
288 let layer_fraction = layer as f32 / num_layers as f32;
289 let head_fraction = head as f32 / num_heads as f32;
290
291 if layer_fraction < 0.2 {
293 return if head_fraction < 0.5 {
294 HeadCategory::Composition
295 } else {
296 HeadCategory::General
297 };
298 }
299
300 if layer_fraction > 0.8 {
302 return if head_fraction < 0.3 {
303 HeadCategory::Detail
304 } else {
305 HeadCategory::Edge
306 };
307 }
308
309 if head_fraction < 0.25 {
311 HeadCategory::Face
312 } else if head_fraction < 0.5 {
313 HeadCategory::Body
314 } else if head_fraction < 0.75 {
315 HeadCategory::Background
316 } else {
317 HeadCategory::Style
318 }
319 }
320
321 fn infer_categories(
323 &self,
324 heads: &[HeadImportance],
325 num_heads: usize,
326 num_layers: usize,
327 ) -> CategoryMapping {
328 let head_categories: Vec<Vec<HeadCategory>> = (0..num_layers)
329 .map(|layer| {
330 (0..num_heads)
331 .map(|head| {
332 heads
333 .iter()
334 .find(|h| h.layer == layer && h.head == head)
335 .map(|h| h.category)
336 .unwrap_or(HeadCategory::General)
337 })
338 .collect()
339 })
340 .collect();
341
342 CategoryMapping {
343 num_heads,
344 num_layers,
345 head_categories,
346 model_id: String::new(),
347 }
348 }
349}
350
351#[derive(Debug, Clone, Serialize, Deserialize)]
353pub struct ImportanceStats {
354 pub mean: f32,
356 pub std_dev: f32,
358 pub min: f32,
360 pub max: f32,
362 pub median: f32,
364 pub percentiles: [f32; 4],
366}
367
368impl ImportanceStats {
369 pub fn from_importances(importances: &[f32]) -> Self {
371 if importances.is_empty() {
372 return Self {
373 mean: 0.0,
374 std_dev: 0.0,
375 min: 0.0,
376 max: 0.0,
377 median: 0.0,
378 percentiles: [0.0; 4],
379 };
380 }
381
382 let mut sorted = importances.to_vec();
383 sorted.sort_by(|a, b| a.partial_cmp(b).unwrap());
384
385 let n = sorted.len();
386 let mean: f32 = sorted.iter().sum::<f32>() / n as f32;
387 let variance: f32 = sorted.iter().map(|v| (v - mean).powi(2)).sum::<f32>() / n as f32;
388
389 Self {
390 mean,
391 std_dev: variance.sqrt(),
392 min: sorted[0],
393 max: sorted[n - 1],
394 median: sorted[n / 2],
395 percentiles: [
396 sorted[n / 4],
397 sorted[n / 2],
398 sorted[3 * n / 4],
399 sorted[9 * n / 10],
400 ],
401 }
402 }
403}
404
405#[cfg(test)]
406mod tests {
407 use super::*;
408
409 #[test]
410 fn test_analyzer() {
411 let mut analyzer = ImportanceAnalyzer::new(AnalysisMethod::Activation);
412
413 for _ in 0..10 {
415 let weights: Vec<Vec<f32>> = (0..10)
416 .map(|_| (0..8).map(|h| 0.5 + h as f32 * 0.05).collect())
417 .collect();
418 analyzer.record_sample(weights, "portrait".into(), 1);
419 }
420
421 let analysis = analyzer.analyze("test-model").unwrap();
422 assert_eq!(analysis.num_layers, 10);
423 assert_eq!(analysis.num_heads, 8);
424 assert_eq!(analysis.heads.len(), 80);
425 }
426
427 #[test]
428 fn test_layer_importance() {
429 let heads: Vec<HeadImportance> = (0..4)
430 .flat_map(|layer| {
431 (0..8).map(move |head| HeadImportance {
432 layer,
433 head,
434 importance: 0.5 + layer as f32 * 0.1,
435 variance: 0.01,
436 category: HeadCategory::General,
437 activation_rate: 0.8,
438 })
439 })
440 .collect();
441
442 let analysis = HeadAnalysis {
443 model_id: "test".into(),
444 num_heads: 8,
445 num_layers: 4,
446 heads,
447 category_mapping: CategoryMapping::sdxl_default(),
448 prune_threshold: 0.5,
449 metadata: AnalysisMetadata {
450 num_samples: 10,
451 categories_analyzed: vec!["test".into()],
452 timestamp: 0,
453 method: AnalysisMethod::Activation,
454 },
455 };
456
457 let layer_imp = analysis.layer_importance();
458 assert_eq!(layer_imp.len(), 4);
459 assert!(layer_imp[3] > layer_imp[0]);
461 }
462}