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 #[serde(default)]
52 pub expert_masks: Vec<Vec<u8>>,
53 pub parent: Option<String>,
55 pub has_hot_pack: bool,
57 pub priority: MaskPriority,
59}
60
61#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
62pub enum MaskPriority {
63 Fallback,
64 Normal,
65 Primary,
66}
67
68impl TaskMask {
69 pub fn fully_open(&self, inter: usize, heads: usize) -> bool {
78 if !self.layer_gates.iter().all(|&a| a) || !self.expert_masks.is_empty() {
79 return false;
80 }
81 let all_set = |bits: &[u8], n: usize| {
82 (0..n).all(|i| bits.get(i / 8).is_some_and(|b| b & (1 << (i % 8)) != 0))
83 };
84 self.ffn_masks.iter().all(|row| all_set(row, inter))
85 && self.head_masks.iter().all(|row| all_set(row, heads))
86 }
87
88 pub fn ffn_active_count(&self, layer_idx: usize) -> usize {
90 self.ffn_masks
91 .get(layer_idx)
92 .map(|m| m.iter().map(|b| b.count_ones() as usize).sum())
93 .unwrap_or(0)
94 }
95
96 pub fn layer_alive(&self, layer_idx: usize) -> bool {
103 self.layer_gates.get(layer_idx).copied().unwrap_or(true)
104 }
105
106 pub fn active_layer_count(&self) -> usize {
108 self.layer_gates.iter().filter(|&&alive| alive).count()
109 }
110
111 pub fn ffn_active_indices(&self, layer_idx: usize) -> Vec<u16> {
113 let Some(mask) = self.ffn_masks.get(layer_idx) else {
114 return vec![];
115 };
116 let mut indices = Vec::new();
117 for (byte_idx, &byte) in mask.iter().enumerate() {
118 for bit in 0..8 {
119 if byte & (1 << bit) != 0 {
120 indices.push((byte_idx * 8 + bit) as u16);
121 }
122 }
123 }
124 indices
125 }
126
127 pub fn active_head_count(&self, layer_idx: usize) -> usize {
129 self.head_masks
130 .get(layer_idx)
131 .map(|m| m.iter().map(|b| b.count_ones() as usize).sum())
132 .unwrap_or(0)
133 }
134
135 pub fn expert_flags(&self, layer_idx: usize, num_experts: usize) -> Option<Vec<bool>> {
138 let mask = self.expert_masks.get(layer_idx)?;
139 if mask.is_empty() {
140 return None;
141 }
142 Some(
143 (0..num_experts)
144 .map(|e| {
145 mask.get(e / 8)
146 .map(|b| b & (1 << (e % 8)) != 0)
147 .unwrap_or(false)
148 })
149 .collect(),
150 )
151 }
152
153 pub fn head_flags(&self, layer_idx: usize, num_heads: usize) -> Vec<bool> {
155 let mut flags = vec![true; num_heads];
156 if let Some(mask) = self.head_masks.get(layer_idx) {
157 for (h, flag) in flags.iter_mut().enumerate() {
158 *flag = mask
159 .get(h / 8)
160 .map(|b| b & (1 << (h % 8)) != 0)
161 .unwrap_or(false);
162 }
163 }
164 flags
165 }
166
167 pub fn avg_active_neurons(&self) -> f64 {
169 let alive_layers: Vec<_> = (0..self.layer_gates.len())
170 .filter(|&i| self.layer_alive(i))
171 .collect();
172 if alive_layers.is_empty() {
173 return 0.0;
174 }
175 let total: usize = alive_layers.iter().map(|&i| self.ffn_active_count(i)).sum();
176 total as f64 / alive_layers.len() as f64
177 }
178
179 pub fn union(&self, other: &TaskMask) -> TaskMask {
181 let mut result = self.clone();
182 result.name = format!("{}+{}", self.name, other.name);
183 result.task_id = u32::MAX; result.parent = None;
185 result.has_hot_pack = false;
186 result.quality = None; for (li, gate) in result.layer_gates.iter_mut().enumerate() {
189 *gate = self.layer_alive(li) || other.layer_alive(li);
190 }
191
192 for (li, mask) in result.ffn_masks.iter_mut().enumerate() {
193 if let Some(om) = other.ffn_masks.get(li) {
194 for (byte, &ob) in mask.iter_mut().zip(om) {
195 *byte |= ob;
196 }
197 }
198 }
199
200 for (li, mask) in result.head_masks.iter_mut().enumerate() {
201 if let Some(om) = other.head_masks.get(li) {
202 for (byte, &ob) in mask.iter_mut().zip(om) {
203 *byte |= ob;
204 }
205 }
206 }
207
208 if result.expert_masks.is_empty() || other.expert_masks.is_empty() {
211 result.expert_masks = Vec::new();
212 } else {
213 for (li, mask) in result.expert_masks.iter_mut().enumerate() {
214 match other.expert_masks.get(li) {
215 Some(om) if !om.is_empty() && !mask.is_empty() => {
216 for (byte, &ob) in mask.iter_mut().zip(om) {
217 *byte |= ob;
218 }
219 }
220 _ => mask.clear(),
221 }
222 }
223 }
224
225 let total_neurons: usize = result.ffn_masks.iter().map(|m| m.len() * 8).sum();
227 let active: usize = (0..result.layer_gates.len())
228 .map(|i| result.ffn_active_count(i))
229 .sum();
230 result.sparsity = 1.0 - (active as f32 / total_neurons.max(1) as f32);
231
232 result
233 }
234
235 pub fn diff(&self, other: &TaskMask) -> MaskDiff {
239 let n_layers = self.layer_gates.len().max(other.layer_gates.len());
240 let mut changed_layers = Vec::new();
241 let mut neurons_added = 0usize;
242 let mut neurons_removed = 0usize;
243 let mut ffn_delta = Vec::with_capacity(n_layers);
244
245 let empty: Vec<u8> = Vec::new();
246 for li in 0..n_layers {
247 let a = self.ffn_masks.get(li).unwrap_or(&empty);
248 let b = other.ffn_masks.get(li).unwrap_or(&empty);
249 let len = a.len().max(b.len());
250 let mut delta = vec![0u8; len];
251 let mut layer_changed = self.layer_alive(li) != other.layer_alive(li);
252
253 for (bi, d) in delta.iter_mut().enumerate() {
254 let av = a.get(bi).copied().unwrap_or(0);
255 let bv = b.get(bi).copied().unwrap_or(0);
256 let x = av ^ bv;
257 *d = x;
258 if x != 0 {
259 layer_changed = true;
260 neurons_added += (bv & !av).count_ones() as usize;
261 neurons_removed += (av & !bv).count_ones() as usize;
262 }
263 }
264
265 let ha = self.head_masks.get(li).unwrap_or(&empty);
267 let hb = other.head_masks.get(li).unwrap_or(&empty);
268 if ha.len() != hb.len() || ha.iter().zip(hb).any(|(x, y)| x != y) {
269 layer_changed = true;
270 }
271
272 if layer_changed {
273 changed_layers.push(li);
274 }
275 ffn_delta.push(delta);
276 }
277
278 MaskDiff {
279 changed_layers,
280 neurons_added,
281 neurons_removed,
282 ffn_delta,
283 }
284 }
285
286 pub fn normalize_tail_bits(&mut self, arch: &ModelArch) {
288 for row in &mut self.ffn_masks {
289 zero_tail_bits(row, arch.intermediate_size);
290 }
291 for row in &mut self.head_masks {
292 zero_tail_bits(row, arch.num_attention_heads);
293 }
294 }
295}
296
297pub fn zero_tail_bits(bits: &mut [u8], n_bits: usize) {
299 let full_bytes = n_bits / 8;
300 let rem = n_bits % 8;
301 if full_bytes < bits.len() {
302 if rem > 0 {
303 bits[full_bytes] &= (1u8 << rem) - 1;
304 for b in &mut bits[full_bytes + 1..] {
305 *b = 0;
306 }
307 } else {
308 for b in &mut bits[full_bytes..] {
309 *b = 0;
310 }
311 }
312 }
313}
314
315#[derive(Debug, Clone, Serialize, Deserialize)]
317pub struct MaskDiff {
318 pub changed_layers: Vec<usize>,
319 pub neurons_added: usize,
320 pub neurons_removed: usize,
321 #[serde(skip)]
323 pub ffn_delta: Vec<Vec<u8>>,
324}
325
326#[derive(Debug, Clone, Serialize, Deserialize)]
328pub struct MaskCatalog {
329 pub masks: Vec<TaskMask>,
330 pub default_task: String,
331}
332
333impl MaskCatalog {
334 pub fn empty() -> Self {
335 Self {
336 masks: vec![],
337 default_task: "general".to_string(),
338 }
339 }
340
341 pub fn get(&self, name: &str) -> Option<&TaskMask> {
343 self.masks.iter().find(|m| m.name == name)
344 }
345
346 pub fn fallback(&self) -> Option<&TaskMask> {
348 self.masks
349 .iter()
350 .find(|m| m.priority == MaskPriority::Fallback)
351 .or(self.masks.first())
352 }
353
354 pub fn task_names(&self) -> Vec<&str> {
356 self.masks.iter().map(|m| m.name.as_str()).collect()
357 }
358}
359
360#[derive(Debug, Clone, Serialize, Deserialize)]
364struct MasksMeta {
365 default_task: String,
366 masks: Vec<MaskMeta>,
367}
368
369#[derive(Debug, Clone, Serialize, Deserialize)]
370struct MaskMeta {
371 task_id: u32,
372 name: String,
373 #[serde(default)]
374 description: Option<String>,
375 sparsity: f32,
376 #[serde(default)]
377 quality: Option<Quality>,
378 #[serde(default)]
379 parent: Option<String>,
380 priority: MaskPriority,
381 #[serde(default)]
382 has_hot_pack: bool,
383 #[serde(default)]
387 has_expert_fields: bool,
388 blob_off: u64,
390 blob_len: u64,
391}
392
393pub fn encode_masks_section(catalog: &MaskCatalog, arch: &ModelArch) -> Result<Vec<u8>, String> {
397 let ffn_b = arch.ffn_mask_bytes();
398 let head_b = arch.head_mask_bytes();
399 let gates_b = arch.gates_mask_bytes();
400 let expert_b = arch.expert_mask_bytes();
401 let ffn_rows = arch.num_layers * arch.num_loops.max(1);
408 let blob_len = ffn_rows * ffn_b + arch.num_layers * head_b + gates_b;
409
410 let mut blobs: Vec<Vec<u8>> = Vec::with_capacity(catalog.masks.len());
413 for m in &catalog.masks {
414 let has_experts = expert_b > 0 && m.expert_masks.iter().any(|e| !e.is_empty());
415 let mut blob = Vec::with_capacity(if has_experts {
416 arch.mask_blob_len_with_experts()
417 } else {
418 blob_len
419 });
420 for vl in 0..ffn_rows {
421 let src_i = if m.ffn_masks.len() >= ffn_rows {
425 vl
426 } else {
427 vl % arch.num_layers.max(1)
428 };
429 let mut row = vec![0u8; ffn_b];
430 if let Some(src) = m.ffn_masks.get(src_i) {
431 let n = src.len().min(ffn_b);
432 row[..n].copy_from_slice(&src[..n]);
433 }
434 zero_tail_bits(&mut row, arch.intermediate_size);
435 blob.extend_from_slice(&row);
436 }
437 for li in 0..arch.num_layers {
438 let mut row = vec![0u8; head_b];
439 if let Some(src) = m.head_masks.get(li) {
440 let n = src.len().min(head_b);
441 row[..n].copy_from_slice(&src[..n]);
442 }
443 zero_tail_bits(&mut row, arch.num_attention_heads);
444 blob.extend_from_slice(&row);
445 }
446 let mut gates = vec![0u8; gates_b];
447 for li in 0..arch.num_layers {
448 if m.layer_alive(li) {
449 gates[li / 8] |= 1 << (li % 8);
450 }
451 }
452 blob.extend_from_slice(&gates);
453 if has_experts {
454 let ne = arch.moe.as_ref().map(|c| c.num_experts).unwrap_or(0);
455 for li in 0..arch.num_layers {
456 let mut row = vec![0u8; expert_b];
457 match m.expert_masks.get(li) {
458 Some(src) if !src.is_empty() => {
459 let n = src.len().min(expert_b);
460 row[..n].copy_from_slice(&src[..n]);
461 zero_tail_bits(&mut row, ne);
462 }
463 _ => {
466 for e in 0..ne {
467 row[e / 8] |= 1 << (e % 8);
468 }
469 }
470 }
471 blob.extend_from_slice(&row);
472 }
473 }
474 blobs.push(blob);
475 }
476
477 let build_meta = |blobs_start: u64| -> MasksMeta {
482 let mut metas = Vec::with_capacity(catalog.masks.len());
483 let mut off = blobs_start;
484 for (m, blob) in catalog.masks.iter().zip(&blobs) {
485 off = off.div_ceil(8) * 8; metas.push(MaskMeta {
487 task_id: m.task_id,
488 name: m.name.clone(),
489 description: m.description.clone(),
490 sparsity: m.sparsity,
491 quality: m.quality.clone(),
492 parent: m.parent.clone(),
493 priority: m.priority,
494 has_hot_pack: m.has_hot_pack,
495 has_expert_fields: expert_b > 0 && blob.len() > blob_len,
496 blob_off: off,
497 blob_len: blob.len() as u64,
498 });
499 off += blob.len() as u64;
500 }
501 MasksMeta {
502 default_task: catalog.default_task.clone(),
503 masks: metas,
504 }
505 };
506
507 let mut meta_len = 0usize;
509 let mut meta_json;
510 loop {
511 let blobs_start = 8 + meta_len as u64;
512 meta_json = serde_json::to_vec(&build_meta(blobs_start))
513 .map_err(|e| format!("serialize masks meta: {e}"))?;
514 if meta_json.len() == meta_len {
515 break;
516 }
517 meta_len = meta_json.len();
518 }
519
520 let meta = build_meta(8 + meta_len as u64);
521 let mut out = Vec::new();
522 out.extend_from_slice(&(catalog.masks.len() as u32).to_le_bytes());
523 out.extend_from_slice(&(meta_len as u32).to_le_bytes());
524 out.extend_from_slice(&meta_json);
525 for (mm, blob) in meta.masks.iter().zip(&blobs) {
526 while (out.len() as u64) < mm.blob_off {
527 out.push(0);
528 }
529 debug_assert_eq!(out.len() as u64, mm.blob_off);
530 out.extend_from_slice(blob);
531 }
532 Ok(out)
533}
534
535pub fn decode_masks_section(bytes: &[u8], arch: &ModelArch) -> Result<MaskCatalog, String> {
537 if bytes.len() < 8 {
538 return Err("masks section too short".into());
539 }
540 let n_masks = u32::from_le_bytes(bytes[0..4].try_into().unwrap()) as usize;
541 let meta_len = u32::from_le_bytes(bytes[4..8].try_into().unwrap()) as usize;
542 if 8 + meta_len > bytes.len() {
543 return Err("masks meta out of bounds".into());
544 }
545 let meta: MasksMeta = serde_json::from_slice(&bytes[8..8 + meta_len])
546 .map_err(|e| format!("masks meta JSON: {e}"))?;
547 if meta.masks.len() != n_masks {
548 return Err(format!(
549 "masks count mismatch: envelope {} vs meta {}",
550 n_masks,
551 meta.masks.len()
552 ));
553 }
554
555 let ffn_b = arch.ffn_mask_bytes();
556 let head_b = arch.head_mask_bytes();
557 let expert_b = arch.expert_mask_bytes();
558 let vrows = arch.num_layers * arch.num_loops.max(1);
562 let legacy_blob = arch.mask_blob_len() as u64;
563 let legacy_with_experts = arch.mask_blob_len_with_experts() as u64;
564 let loop_blob = (vrows * ffn_b + arch.num_layers * head_b + arch.gates_mask_bytes()) as u64;
565 let loop_with_experts = loop_blob + (arch.num_layers * expert_b) as u64;
566
567 let mut masks = Vec::with_capacity(n_masks);
568 for mm in &meta.masks {
569 let (expected, ffn_rows) = if mm.has_expert_fields {
570 if mm.blob_len == loop_with_experts {
571 (loop_with_experts, vrows)
572 } else {
573 (legacy_with_experts, arch.num_layers)
574 }
575 } else if mm.blob_len == loop_blob {
576 (loop_blob, vrows)
577 } else {
578 (legacy_blob, arch.num_layers)
579 };
580 if mm.has_expert_fields && expert_b == 0 {
581 return Err(format!(
582 "mask '{}': expert fields flagged but the arch has no MoE block",
583 mm.name
584 ));
585 }
586 if mm.blob_len != expected {
587 return Err(format!(
588 "mask '{}': blob_len {} != expected {} for arch",
589 mm.name, mm.blob_len, expected
590 ));
591 }
592 let start = usize::try_from(mm.blob_off)
593 .map_err(|_| format!("mask '{}': blob offset does not fit usize", mm.name))?;
594 let blob_len = usize::try_from(mm.blob_len)
595 .map_err(|_| format!("mask '{}': blob length does not fit usize", mm.name))?;
596 let end = start
597 .checked_add(blob_len)
598 .ok_or_else(|| format!("mask '{}': blob range overflows", mm.name))?;
599 if end > bytes.len() {
600 return Err(format!("mask '{}': blob out of bounds", mm.name));
601 }
602 let blob = &bytes[start..end];
603
604 let mut ffn_masks = Vec::with_capacity(vrows);
605 for li in 0..ffn_rows {
606 ffn_masks.push(blob[li * ffn_b..(li + 1) * ffn_b].to_vec());
607 }
608 if ffn_rows < vrows {
614 ffn_masks = (0..vrows)
615 .map(|vl| ffn_masks[vl % arch.num_layers.max(1)].clone())
616 .collect();
617 }
618 let heads_base = ffn_rows * ffn_b;
619 let mut head_masks = Vec::with_capacity(arch.num_layers);
620 for li in 0..arch.num_layers {
621 head_masks
622 .push(blob[heads_base + li * head_b..heads_base + (li + 1) * head_b].to_vec());
623 }
624 let gates_base = heads_base + arch.num_layers * head_b;
625 let gates = &blob[gates_base..];
626 let layer_gates: Vec<bool> = (0..vrows.max(arch.num_layers))
631 .map(|vl| {
632 let li = vl % arch.num_layers.max(1);
633 gates[li / 8] & (1 << (li % 8)) != 0
634 })
635 .collect();
636 let expert_masks: Vec<Vec<u8>> = if mm.has_expert_fields {
637 let base = gates_base + arch.gates_mask_bytes();
638 (0..arch.num_layers)
639 .map(|li| blob[base + li * expert_b..base + (li + 1) * expert_b].to_vec())
640 .collect()
641 } else {
642 Vec::new()
643 };
644
645 masks.push(TaskMask {
646 task_id: mm.task_id,
647 name: mm.name.clone(),
648 description: mm.description.clone(),
649 sparsity: mm.sparsity,
650 quality: mm.quality.clone(),
651 ffn_masks,
652 head_masks,
653 layer_gates,
654 expert_masks,
655 parent: mm.parent.clone(),
656 has_hot_pack: mm.has_hot_pack,
657 priority: mm.priority,
658 });
659 }
660
661 Ok(MaskCatalog {
662 masks,
663 default_task: meta.default_task,
664 })
665}