1use crate::types::ModelArch;
10use serde::{Deserialize, Serialize};
11
12#[derive(Debug, Clone, Serialize, Deserialize)]
15pub struct Quality {
16 pub metric: String,
18 pub value: f32,
19 #[serde(default, skip_serializing_if = "Option::is_none")]
20 pub baseline_dense: Option<f32>,
21 #[serde(default, skip_serializing_if = "Option::is_none")]
22 pub n_samples: Option<u32>,
23 #[serde(default, skip_serializing_if = "Option::is_none")]
24 pub dataset_sha256: Option<String>,
25}
26
27#[derive(Debug, Clone, Serialize, Deserialize)]
29pub struct TaskMask {
30 pub task_id: u32,
32 pub name: String,
34 pub description: Option<String>,
36 pub sparsity: f32,
38 #[serde(default)]
40 pub quality: Option<Quality>,
41 pub ffn_masks: Vec<Vec<u8>>,
43 pub head_masks: Vec<Vec<u8>>,
45 pub layer_gates: Vec<bool>,
47 pub parent: Option<String>,
49 pub has_hot_pack: bool,
51 pub priority: MaskPriority,
53}
54
55#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
56pub enum MaskPriority {
57 Fallback,
58 Normal,
59 Primary,
60}
61
62impl TaskMask {
63 pub fn ffn_active_count(&self, layer_idx: usize) -> usize {
65 self.ffn_masks
66 .get(layer_idx)
67 .map(|m| m.iter().map(|b| b.count_ones() as usize).sum())
68 .unwrap_or(0)
69 }
70
71 pub fn layer_alive(&self, layer_idx: usize) -> bool {
73 self.layer_gates.get(layer_idx).copied().unwrap_or(false)
74 }
75
76 pub fn active_layer_count(&self) -> usize {
78 self.layer_gates.iter().filter(|&&alive| alive).count()
79 }
80
81 pub fn ffn_active_indices(&self, layer_idx: usize) -> Vec<u16> {
83 let Some(mask) = self.ffn_masks.get(layer_idx) else {
84 return vec![];
85 };
86 let mut indices = Vec::new();
87 for (byte_idx, &byte) in mask.iter().enumerate() {
88 for bit in 0..8 {
89 if byte & (1 << bit) != 0 {
90 indices.push((byte_idx * 8 + bit) as u16);
91 }
92 }
93 }
94 indices
95 }
96
97 pub fn active_head_count(&self, layer_idx: usize) -> usize {
99 self.head_masks
100 .get(layer_idx)
101 .map(|m| m.iter().map(|b| b.count_ones() as usize).sum())
102 .unwrap_or(0)
103 }
104
105 pub fn head_flags(&self, layer_idx: usize, num_heads: usize) -> Vec<bool> {
107 let mut flags = vec![true; num_heads];
108 if let Some(mask) = self.head_masks.get(layer_idx) {
109 for (h, flag) in flags.iter_mut().enumerate() {
110 *flag = mask
111 .get(h / 8)
112 .map(|b| b & (1 << (h % 8)) != 0)
113 .unwrap_or(false);
114 }
115 }
116 flags
117 }
118
119 pub fn avg_active_neurons(&self) -> f64 {
121 let alive_layers: Vec<_> = (0..self.layer_gates.len())
122 .filter(|&i| self.layer_alive(i))
123 .collect();
124 if alive_layers.is_empty() {
125 return 0.0;
126 }
127 let total: usize = alive_layers.iter().map(|&i| self.ffn_active_count(i)).sum();
128 total as f64 / alive_layers.len() as f64
129 }
130
131 pub fn union(&self, other: &TaskMask) -> TaskMask {
133 let mut result = self.clone();
134 result.name = format!("{}+{}", self.name, other.name);
135 result.task_id = u32::MAX; result.parent = None;
137 result.has_hot_pack = false;
138 result.quality = None; for (li, gate) in result.layer_gates.iter_mut().enumerate() {
141 *gate = self.layer_alive(li) || other.layer_alive(li);
142 }
143
144 for (li, mask) in result.ffn_masks.iter_mut().enumerate() {
145 if let Some(om) = other.ffn_masks.get(li) {
146 for (byte, &ob) in mask.iter_mut().zip(om) {
147 *byte |= ob;
148 }
149 }
150 }
151
152 for (li, mask) in result.head_masks.iter_mut().enumerate() {
153 if let Some(om) = other.head_masks.get(li) {
154 for (byte, &ob) in mask.iter_mut().zip(om) {
155 *byte |= ob;
156 }
157 }
158 }
159
160 let total_neurons: usize = result.ffn_masks.iter().map(|m| m.len() * 8).sum();
162 let active: usize = (0..result.layer_gates.len())
163 .map(|i| result.ffn_active_count(i))
164 .sum();
165 result.sparsity = 1.0 - (active as f32 / total_neurons.max(1) as f32);
166
167 result
168 }
169
170 pub fn diff(&self, other: &TaskMask) -> MaskDiff {
174 let n_layers = self.layer_gates.len().max(other.layer_gates.len());
175 let mut changed_layers = Vec::new();
176 let mut neurons_added = 0usize;
177 let mut neurons_removed = 0usize;
178 let mut ffn_delta = Vec::with_capacity(n_layers);
179
180 let empty: Vec<u8> = Vec::new();
181 for li in 0..n_layers {
182 let a = self.ffn_masks.get(li).unwrap_or(&empty);
183 let b = other.ffn_masks.get(li).unwrap_or(&empty);
184 let len = a.len().max(b.len());
185 let mut delta = vec![0u8; len];
186 let mut layer_changed = self.layer_alive(li) != other.layer_alive(li);
187
188 for bi in 0..len {
189 let av = a.get(bi).copied().unwrap_or(0);
190 let bv = b.get(bi).copied().unwrap_or(0);
191 let x = av ^ bv;
192 delta[bi] = x;
193 if x != 0 {
194 layer_changed = true;
195 neurons_added += (bv & !av).count_ones() as usize;
196 neurons_removed += (av & !bv).count_ones() as usize;
197 }
198 }
199
200 let ha = self.head_masks.get(li).unwrap_or(&empty);
202 let hb = other.head_masks.get(li).unwrap_or(&empty);
203 if ha.len() != hb.len() || ha.iter().zip(hb).any(|(x, y)| x != y) {
204 layer_changed = true;
205 }
206
207 if layer_changed {
208 changed_layers.push(li);
209 }
210 ffn_delta.push(delta);
211 }
212
213 MaskDiff {
214 changed_layers,
215 neurons_added,
216 neurons_removed,
217 ffn_delta,
218 }
219 }
220
221 pub fn normalize_tail_bits(&mut self, arch: &ModelArch) {
223 for row in &mut self.ffn_masks {
224 zero_tail_bits(row, arch.intermediate_size);
225 }
226 for row in &mut self.head_masks {
227 zero_tail_bits(row, arch.num_attention_heads);
228 }
229 }
230}
231
232pub fn zero_tail_bits(bits: &mut [u8], n_bits: usize) {
234 let full_bytes = n_bits / 8;
235 let rem = n_bits % 8;
236 if full_bytes < bits.len() {
237 if rem > 0 {
238 bits[full_bytes] &= (1u8 << rem) - 1;
239 for b in &mut bits[full_bytes + 1..] {
240 *b = 0;
241 }
242 } else {
243 for b in &mut bits[full_bytes..] {
244 *b = 0;
245 }
246 }
247 }
248}
249
250#[derive(Debug, Clone, Serialize, Deserialize)]
252pub struct MaskDiff {
253 pub changed_layers: Vec<usize>,
254 pub neurons_added: usize,
255 pub neurons_removed: usize,
256 #[serde(skip)]
258 pub ffn_delta: Vec<Vec<u8>>,
259}
260
261#[derive(Debug, Clone, Serialize, Deserialize)]
263pub struct MaskCatalog {
264 pub masks: Vec<TaskMask>,
265 pub default_task: String,
266}
267
268impl MaskCatalog {
269 pub fn empty() -> Self {
270 Self {
271 masks: vec![],
272 default_task: "general".to_string(),
273 }
274 }
275
276 pub fn get(&self, name: &str) -> Option<&TaskMask> {
278 self.masks.iter().find(|m| m.name == name)
279 }
280
281 pub fn fallback(&self) -> Option<&TaskMask> {
283 self.masks
284 .iter()
285 .find(|m| m.priority == MaskPriority::Fallback)
286 .or(self.masks.first())
287 }
288
289 pub fn task_names(&self) -> Vec<&str> {
291 self.masks.iter().map(|m| m.name.as_str()).collect()
292 }
293}
294
295#[derive(Debug, Clone, Serialize, Deserialize)]
299struct MasksMeta {
300 default_task: String,
301 masks: Vec<MaskMeta>,
302}
303
304#[derive(Debug, Clone, Serialize, Deserialize)]
305struct MaskMeta {
306 task_id: u32,
307 name: String,
308 #[serde(default)]
309 description: Option<String>,
310 sparsity: f32,
311 #[serde(default)]
312 quality: Option<Quality>,
313 #[serde(default)]
314 parent: Option<String>,
315 priority: MaskPriority,
316 #[serde(default)]
317 has_hot_pack: bool,
318 blob_off: u64,
320 blob_len: u64,
321}
322
323pub fn encode_masks_section(catalog: &MaskCatalog, arch: &ModelArch) -> Result<Vec<u8>, String> {
327 let ffn_b = arch.ffn_mask_bytes();
328 let head_b = arch.head_mask_bytes();
329 let gates_b = arch.gates_mask_bytes();
330 let blob_len = arch.mask_blob_len();
331
332 let mut blobs: Vec<Vec<u8>> = Vec::with_capacity(catalog.masks.len());
334 for m in &catalog.masks {
335 let mut blob = Vec::with_capacity(blob_len);
336 for li in 0..arch.num_layers {
337 let mut row = vec![0u8; ffn_b];
338 if let Some(src) = m.ffn_masks.get(li) {
339 let n = src.len().min(ffn_b);
340 row[..n].copy_from_slice(&src[..n]);
341 }
342 zero_tail_bits(&mut row, arch.intermediate_size);
343 blob.extend_from_slice(&row);
344 }
345 for li in 0..arch.num_layers {
346 let mut row = vec![0u8; head_b];
347 if let Some(src) = m.head_masks.get(li) {
348 let n = src.len().min(head_b);
349 row[..n].copy_from_slice(&src[..n]);
350 }
351 zero_tail_bits(&mut row, arch.num_attention_heads);
352 blob.extend_from_slice(&row);
353 }
354 let mut gates = vec![0u8; gates_b];
355 for li in 0..arch.num_layers {
356 if m.layer_alive(li) {
357 gates[li / 8] |= 1 << (li % 8);
358 }
359 }
360 blob.extend_from_slice(&gates);
361 debug_assert_eq!(blob.len(), blob_len);
362 blobs.push(blob);
363 }
364
365 let build_meta = |blobs_start: u64| -> MasksMeta {
370 let mut metas = Vec::with_capacity(catalog.masks.len());
371 let mut off = blobs_start;
372 for m in &catalog.masks {
373 off = (off + 7) / 8 * 8; metas.push(MaskMeta {
375 task_id: m.task_id,
376 name: m.name.clone(),
377 description: m.description.clone(),
378 sparsity: m.sparsity,
379 quality: m.quality.clone(),
380 parent: m.parent.clone(),
381 priority: m.priority,
382 has_hot_pack: m.has_hot_pack,
383 blob_off: off,
384 blob_len: blob_len as u64,
385 });
386 off += blob_len as u64;
387 }
388 MasksMeta {
389 default_task: catalog.default_task.clone(),
390 masks: metas,
391 }
392 };
393
394 let mut meta_len = 0usize;
396 let mut meta_json;
397 loop {
398 let blobs_start = 8 + meta_len as u64;
399 meta_json = serde_json::to_vec(&build_meta(blobs_start))
400 .map_err(|e| format!("serialize masks meta: {e}"))?;
401 if meta_json.len() == meta_len {
402 break;
403 }
404 meta_len = meta_json.len();
405 }
406
407 let meta = build_meta(8 + meta_len as u64);
408 let mut out = Vec::new();
409 out.extend_from_slice(&(catalog.masks.len() as u32).to_le_bytes());
410 out.extend_from_slice(&(meta_len as u32).to_le_bytes());
411 out.extend_from_slice(&meta_json);
412 for (mm, blob) in meta.masks.iter().zip(&blobs) {
413 while (out.len() as u64) < mm.blob_off {
414 out.push(0);
415 }
416 debug_assert_eq!(out.len() as u64, mm.blob_off);
417 out.extend_from_slice(blob);
418 }
419 Ok(out)
420}
421
422pub fn decode_masks_section(bytes: &[u8], arch: &ModelArch) -> Result<MaskCatalog, String> {
424 if bytes.len() < 8 {
425 return Err("masks section too short".into());
426 }
427 let n_masks = u32::from_le_bytes(bytes[0..4].try_into().unwrap()) as usize;
428 let meta_len = u32::from_le_bytes(bytes[4..8].try_into().unwrap()) as usize;
429 if 8 + meta_len > bytes.len() {
430 return Err("masks meta out of bounds".into());
431 }
432 let meta: MasksMeta = serde_json::from_slice(&bytes[8..8 + meta_len])
433 .map_err(|e| format!("masks meta JSON: {e}"))?;
434 if meta.masks.len() != n_masks {
435 return Err(format!(
436 "masks count mismatch: envelope {} vs meta {}",
437 n_masks,
438 meta.masks.len()
439 ));
440 }
441
442 let ffn_b = arch.ffn_mask_bytes();
443 let head_b = arch.head_mask_bytes();
444 let expected_blob = arch.mask_blob_len() as u64;
445
446 let mut masks = Vec::with_capacity(n_masks);
447 for mm in &meta.masks {
448 if mm.blob_len != expected_blob {
449 return Err(format!(
450 "mask '{}': blob_len {} != expected {} for arch",
451 mm.name, mm.blob_len, expected_blob
452 ));
453 }
454 let start = mm.blob_off as usize;
455 let end = start + mm.blob_len as usize;
456 if end > bytes.len() {
457 return Err(format!("mask '{}': blob out of bounds", mm.name));
458 }
459 let blob = &bytes[start..end];
460
461 let mut ffn_masks = Vec::with_capacity(arch.num_layers);
462 for li in 0..arch.num_layers {
463 ffn_masks.push(blob[li * ffn_b..(li + 1) * ffn_b].to_vec());
464 }
465 let heads_base = arch.num_layers * ffn_b;
466 let mut head_masks = Vec::with_capacity(arch.num_layers);
467 for li in 0..arch.num_layers {
468 head_masks.push(blob[heads_base + li * head_b..heads_base + (li + 1) * head_b].to_vec());
469 }
470 let gates_base = heads_base + arch.num_layers * head_b;
471 let gates = &blob[gates_base..];
472 let layer_gates: Vec<bool> = (0..arch.num_layers)
473 .map(|li| gates[li / 8] & (1 << (li % 8)) != 0)
474 .collect();
475
476 masks.push(TaskMask {
477 task_id: mm.task_id,
478 name: mm.name.clone(),
479 description: mm.description.clone(),
480 sparsity: mm.sparsity,
481 quality: mm.quality.clone(),
482 ffn_masks,
483 head_masks,
484 layer_gates,
485 parent: mm.parent.clone(),
486 has_hot_pack: mm.has_hot_pack,
487 priority: mm.priority,
488 });
489 }
490
491 Ok(MaskCatalog {
492 masks,
493 default_task: meta.default_task,
494 })
495}