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 ffn_active_count(&self, layer_idx: usize) -> usize {
71 self.ffn_masks
72 .get(layer_idx)
73 .map(|m| m.iter().map(|b| b.count_ones() as usize).sum())
74 .unwrap_or(0)
75 }
76
77 pub fn layer_alive(&self, layer_idx: usize) -> bool {
84 self.layer_gates.get(layer_idx).copied().unwrap_or(true)
85 }
86
87 pub fn active_layer_count(&self) -> usize {
89 self.layer_gates.iter().filter(|&&alive| alive).count()
90 }
91
92 pub fn ffn_active_indices(&self, layer_idx: usize) -> Vec<u16> {
94 let Some(mask) = self.ffn_masks.get(layer_idx) else {
95 return vec![];
96 };
97 let mut indices = Vec::new();
98 for (byte_idx, &byte) in mask.iter().enumerate() {
99 for bit in 0..8 {
100 if byte & (1 << bit) != 0 {
101 indices.push((byte_idx * 8 + bit) as u16);
102 }
103 }
104 }
105 indices
106 }
107
108 pub fn active_head_count(&self, layer_idx: usize) -> usize {
110 self.head_masks
111 .get(layer_idx)
112 .map(|m| m.iter().map(|b| b.count_ones() as usize).sum())
113 .unwrap_or(0)
114 }
115
116 pub fn expert_flags(&self, layer_idx: usize, num_experts: usize) -> Option<Vec<bool>> {
119 let mask = self.expert_masks.get(layer_idx)?;
120 if mask.is_empty() {
121 return None;
122 }
123 Some(
124 (0..num_experts)
125 .map(|e| mask.get(e / 8).map(|b| b & (1 << (e % 8)) != 0).unwrap_or(false))
126 .collect(),
127 )
128 }
129
130 pub fn head_flags(&self, layer_idx: usize, num_heads: usize) -> Vec<bool> {
132 let mut flags = vec![true; num_heads];
133 if let Some(mask) = self.head_masks.get(layer_idx) {
134 for (h, flag) in flags.iter_mut().enumerate() {
135 *flag = mask
136 .get(h / 8)
137 .map(|b| b & (1 << (h % 8)) != 0)
138 .unwrap_or(false);
139 }
140 }
141 flags
142 }
143
144 pub fn avg_active_neurons(&self) -> f64 {
146 let alive_layers: Vec<_> = (0..self.layer_gates.len())
147 .filter(|&i| self.layer_alive(i))
148 .collect();
149 if alive_layers.is_empty() {
150 return 0.0;
151 }
152 let total: usize = alive_layers.iter().map(|&i| self.ffn_active_count(i)).sum();
153 total as f64 / alive_layers.len() as f64
154 }
155
156 pub fn union(&self, other: &TaskMask) -> TaskMask {
158 let mut result = self.clone();
159 result.name = format!("{}+{}", self.name, other.name);
160 result.task_id = u32::MAX; result.parent = None;
162 result.has_hot_pack = false;
163 result.quality = None; for (li, gate) in result.layer_gates.iter_mut().enumerate() {
166 *gate = self.layer_alive(li) || other.layer_alive(li);
167 }
168
169 for (li, mask) in result.ffn_masks.iter_mut().enumerate() {
170 if let Some(om) = other.ffn_masks.get(li) {
171 for (byte, &ob) in mask.iter_mut().zip(om) {
172 *byte |= ob;
173 }
174 }
175 }
176
177 for (li, mask) in result.head_masks.iter_mut().enumerate() {
178 if let Some(om) = other.head_masks.get(li) {
179 for (byte, &ob) in mask.iter_mut().zip(om) {
180 *byte |= ob;
181 }
182 }
183 }
184
185 if result.expert_masks.is_empty() || other.expert_masks.is_empty() {
188 result.expert_masks = Vec::new();
189 } else {
190 for (li, mask) in result.expert_masks.iter_mut().enumerate() {
191 match other.expert_masks.get(li) {
192 Some(om) if !om.is_empty() && !mask.is_empty() => {
193 for (byte, &ob) in mask.iter_mut().zip(om) {
194 *byte |= ob;
195 }
196 }
197 _ => mask.clear(),
198 }
199 }
200 }
201
202 let total_neurons: usize = result.ffn_masks.iter().map(|m| m.len() * 8).sum();
204 let active: usize = (0..result.layer_gates.len())
205 .map(|i| result.ffn_active_count(i))
206 .sum();
207 result.sparsity = 1.0 - (active as f32 / total_neurons.max(1) as f32);
208
209 result
210 }
211
212 pub fn diff(&self, other: &TaskMask) -> MaskDiff {
216 let n_layers = self.layer_gates.len().max(other.layer_gates.len());
217 let mut changed_layers = Vec::new();
218 let mut neurons_added = 0usize;
219 let mut neurons_removed = 0usize;
220 let mut ffn_delta = Vec::with_capacity(n_layers);
221
222 let empty: Vec<u8> = Vec::new();
223 for li in 0..n_layers {
224 let a = self.ffn_masks.get(li).unwrap_or(&empty);
225 let b = other.ffn_masks.get(li).unwrap_or(&empty);
226 let len = a.len().max(b.len());
227 let mut delta = vec![0u8; len];
228 let mut layer_changed = self.layer_alive(li) != other.layer_alive(li);
229
230 for (bi, d) in delta.iter_mut().enumerate() {
231 let av = a.get(bi).copied().unwrap_or(0);
232 let bv = b.get(bi).copied().unwrap_or(0);
233 let x = av ^ bv;
234 *d = x;
235 if x != 0 {
236 layer_changed = true;
237 neurons_added += (bv & !av).count_ones() as usize;
238 neurons_removed += (av & !bv).count_ones() as usize;
239 }
240 }
241
242 let ha = self.head_masks.get(li).unwrap_or(&empty);
244 let hb = other.head_masks.get(li).unwrap_or(&empty);
245 if ha.len() != hb.len() || ha.iter().zip(hb).any(|(x, y)| x != y) {
246 layer_changed = true;
247 }
248
249 if layer_changed {
250 changed_layers.push(li);
251 }
252 ffn_delta.push(delta);
253 }
254
255 MaskDiff {
256 changed_layers,
257 neurons_added,
258 neurons_removed,
259 ffn_delta,
260 }
261 }
262
263 pub fn normalize_tail_bits(&mut self, arch: &ModelArch) {
265 for row in &mut self.ffn_masks {
266 zero_tail_bits(row, arch.intermediate_size);
267 }
268 for row in &mut self.head_masks {
269 zero_tail_bits(row, arch.num_attention_heads);
270 }
271 }
272}
273
274pub fn zero_tail_bits(bits: &mut [u8], n_bits: usize) {
276 let full_bytes = n_bits / 8;
277 let rem = n_bits % 8;
278 if full_bytes < bits.len() {
279 if rem > 0 {
280 bits[full_bytes] &= (1u8 << rem) - 1;
281 for b in &mut bits[full_bytes + 1..] {
282 *b = 0;
283 }
284 } else {
285 for b in &mut bits[full_bytes..] {
286 *b = 0;
287 }
288 }
289 }
290}
291
292#[derive(Debug, Clone, Serialize, Deserialize)]
294pub struct MaskDiff {
295 pub changed_layers: Vec<usize>,
296 pub neurons_added: usize,
297 pub neurons_removed: usize,
298 #[serde(skip)]
300 pub ffn_delta: Vec<Vec<u8>>,
301}
302
303#[derive(Debug, Clone, Serialize, Deserialize)]
305pub struct MaskCatalog {
306 pub masks: Vec<TaskMask>,
307 pub default_task: String,
308}
309
310impl MaskCatalog {
311 pub fn empty() -> Self {
312 Self {
313 masks: vec![],
314 default_task: "general".to_string(),
315 }
316 }
317
318 pub fn get(&self, name: &str) -> Option<&TaskMask> {
320 self.masks.iter().find(|m| m.name == name)
321 }
322
323 pub fn fallback(&self) -> Option<&TaskMask> {
325 self.masks
326 .iter()
327 .find(|m| m.priority == MaskPriority::Fallback)
328 .or(self.masks.first())
329 }
330
331 pub fn task_names(&self) -> Vec<&str> {
333 self.masks.iter().map(|m| m.name.as_str()).collect()
334 }
335}
336
337#[derive(Debug, Clone, Serialize, Deserialize)]
341struct MasksMeta {
342 default_task: String,
343 masks: Vec<MaskMeta>,
344}
345
346#[derive(Debug, Clone, Serialize, Deserialize)]
347struct MaskMeta {
348 task_id: u32,
349 name: String,
350 #[serde(default)]
351 description: Option<String>,
352 sparsity: f32,
353 #[serde(default)]
354 quality: Option<Quality>,
355 #[serde(default)]
356 parent: Option<String>,
357 priority: MaskPriority,
358 #[serde(default)]
359 has_hot_pack: bool,
360 #[serde(default)]
364 has_expert_fields: bool,
365 blob_off: u64,
367 blob_len: u64,
368}
369
370pub fn encode_masks_section(catalog: &MaskCatalog, arch: &ModelArch) -> Result<Vec<u8>, String> {
374 let ffn_b = arch.ffn_mask_bytes();
375 let head_b = arch.head_mask_bytes();
376 let gates_b = arch.gates_mask_bytes();
377 let expert_b = arch.expert_mask_bytes();
378 let ffn_rows = arch.num_layers * arch.num_loops.max(1);
385 let blob_len = ffn_rows * ffn_b + arch.num_layers * head_b + gates_b;
386
387 let mut blobs: Vec<Vec<u8>> = Vec::with_capacity(catalog.masks.len());
390 for m in &catalog.masks {
391 let has_experts = expert_b > 0 && m.expert_masks.iter().any(|e| !e.is_empty());
392 let mut blob = Vec::with_capacity(if has_experts {
393 arch.mask_blob_len_with_experts()
394 } else {
395 blob_len
396 });
397 for vl in 0..ffn_rows {
398 let src_i = if m.ffn_masks.len() >= ffn_rows {
402 vl
403 } else {
404 vl % arch.num_layers.max(1)
405 };
406 let mut row = vec![0u8; ffn_b];
407 if let Some(src) = m.ffn_masks.get(src_i) {
408 let n = src.len().min(ffn_b);
409 row[..n].copy_from_slice(&src[..n]);
410 }
411 zero_tail_bits(&mut row, arch.intermediate_size);
412 blob.extend_from_slice(&row);
413 }
414 for li in 0..arch.num_layers {
415 let mut row = vec![0u8; head_b];
416 if let Some(src) = m.head_masks.get(li) {
417 let n = src.len().min(head_b);
418 row[..n].copy_from_slice(&src[..n]);
419 }
420 zero_tail_bits(&mut row, arch.num_attention_heads);
421 blob.extend_from_slice(&row);
422 }
423 let mut gates = vec![0u8; gates_b];
424 for li in 0..arch.num_layers {
425 if m.layer_alive(li) {
426 gates[li / 8] |= 1 << (li % 8);
427 }
428 }
429 blob.extend_from_slice(&gates);
430 if has_experts {
431 let ne = arch.moe.as_ref().map(|c| c.num_experts).unwrap_or(0);
432 for li in 0..arch.num_layers {
433 let mut row = vec![0u8; expert_b];
434 match m.expert_masks.get(li) {
435 Some(src) if !src.is_empty() => {
436 let n = src.len().min(expert_b);
437 row[..n].copy_from_slice(&src[..n]);
438 zero_tail_bits(&mut row, ne);
439 }
440 _ => {
443 for e in 0..ne {
444 row[e / 8] |= 1 << (e % 8);
445 }
446 }
447 }
448 blob.extend_from_slice(&row);
449 }
450 }
451 blobs.push(blob);
452 }
453
454 let build_meta = |blobs_start: u64| -> MasksMeta {
459 let mut metas = Vec::with_capacity(catalog.masks.len());
460 let mut off = blobs_start;
461 for (m, blob) in catalog.masks.iter().zip(&blobs) {
462 off = off.div_ceil(8) * 8; metas.push(MaskMeta {
464 task_id: m.task_id,
465 name: m.name.clone(),
466 description: m.description.clone(),
467 sparsity: m.sparsity,
468 quality: m.quality.clone(),
469 parent: m.parent.clone(),
470 priority: m.priority,
471 has_hot_pack: m.has_hot_pack,
472 has_expert_fields: expert_b > 0 && blob.len() > blob_len,
473 blob_off: off,
474 blob_len: blob.len() as u64,
475 });
476 off += blob.len() as u64;
477 }
478 MasksMeta {
479 default_task: catalog.default_task.clone(),
480 masks: metas,
481 }
482 };
483
484 let mut meta_len = 0usize;
486 let mut meta_json;
487 loop {
488 let blobs_start = 8 + meta_len as u64;
489 meta_json = serde_json::to_vec(&build_meta(blobs_start))
490 .map_err(|e| format!("serialize masks meta: {e}"))?;
491 if meta_json.len() == meta_len {
492 break;
493 }
494 meta_len = meta_json.len();
495 }
496
497 let meta = build_meta(8 + meta_len as u64);
498 let mut out = Vec::new();
499 out.extend_from_slice(&(catalog.masks.len() as u32).to_le_bytes());
500 out.extend_from_slice(&(meta_len as u32).to_le_bytes());
501 out.extend_from_slice(&meta_json);
502 for (mm, blob) in meta.masks.iter().zip(&blobs) {
503 while (out.len() as u64) < mm.blob_off {
504 out.push(0);
505 }
506 debug_assert_eq!(out.len() as u64, mm.blob_off);
507 out.extend_from_slice(blob);
508 }
509 Ok(out)
510}
511
512pub fn decode_masks_section(bytes: &[u8], arch: &ModelArch) -> Result<MaskCatalog, String> {
514 if bytes.len() < 8 {
515 return Err("masks section too short".into());
516 }
517 let n_masks = u32::from_le_bytes(bytes[0..4].try_into().unwrap()) as usize;
518 let meta_len = u32::from_le_bytes(bytes[4..8].try_into().unwrap()) as usize;
519 if 8 + meta_len > bytes.len() {
520 return Err("masks meta out of bounds".into());
521 }
522 let meta: MasksMeta = serde_json::from_slice(&bytes[8..8 + meta_len])
523 .map_err(|e| format!("masks meta JSON: {e}"))?;
524 if meta.masks.len() != n_masks {
525 return Err(format!(
526 "masks count mismatch: envelope {} vs meta {}",
527 n_masks,
528 meta.masks.len()
529 ));
530 }
531
532 let ffn_b = arch.ffn_mask_bytes();
533 let head_b = arch.head_mask_bytes();
534 let expert_b = arch.expert_mask_bytes();
535 let vrows = arch.num_layers * arch.num_loops.max(1);
539 let legacy_blob = arch.mask_blob_len() as u64;
540 let legacy_with_experts = arch.mask_blob_len_with_experts() as u64;
541 let loop_blob =
542 (vrows * ffn_b + arch.num_layers * head_b + arch.gates_mask_bytes()) as u64;
543 let loop_with_experts = loop_blob + (arch.num_layers * expert_b) as u64;
544
545 let mut masks = Vec::with_capacity(n_masks);
546 for mm in &meta.masks {
547 let (expected, ffn_rows) = if mm.has_expert_fields {
548 if mm.blob_len == loop_with_experts {
549 (loop_with_experts, vrows)
550 } else {
551 (legacy_with_experts, arch.num_layers)
552 }
553 } else if mm.blob_len == loop_blob {
554 (loop_blob, vrows)
555 } else {
556 (legacy_blob, arch.num_layers)
557 };
558 if mm.has_expert_fields && expert_b == 0 {
559 return Err(format!(
560 "mask '{}': expert fields flagged but the arch has no MoE block",
561 mm.name
562 ));
563 }
564 if mm.blob_len != expected {
565 return Err(format!(
566 "mask '{}': blob_len {} != expected {} for arch",
567 mm.name, mm.blob_len, expected
568 ));
569 }
570 let start = usize::try_from(mm.blob_off)
571 .map_err(|_| format!("mask '{}': blob offset does not fit usize", mm.name))?;
572 let blob_len = usize::try_from(mm.blob_len)
573 .map_err(|_| format!("mask '{}': blob length does not fit usize", mm.name))?;
574 let end = start
575 .checked_add(blob_len)
576 .ok_or_else(|| format!("mask '{}': blob range overflows", mm.name))?;
577 if end > bytes.len() {
578 return Err(format!("mask '{}': blob out of bounds", mm.name));
579 }
580 let blob = &bytes[start..end];
581
582 let mut ffn_masks = Vec::with_capacity(vrows);
583 for li in 0..ffn_rows {
584 ffn_masks.push(blob[li * ffn_b..(li + 1) * ffn_b].to_vec());
585 }
586 if ffn_rows < vrows {
592 ffn_masks = (0..vrows)
593 .map(|vl| ffn_masks[vl % arch.num_layers.max(1)].clone())
594 .collect();
595 }
596 let heads_base = ffn_rows * ffn_b;
597 let mut head_masks = Vec::with_capacity(arch.num_layers);
598 for li in 0..arch.num_layers {
599 head_masks
600 .push(blob[heads_base + li * head_b..heads_base + (li + 1) * head_b].to_vec());
601 }
602 let gates_base = heads_base + arch.num_layers * head_b;
603 let gates = &blob[gates_base..];
604 let layer_gates: Vec<bool> = (0..vrows.max(arch.num_layers))
609 .map(|vl| {
610 let li = vl % arch.num_layers.max(1);
611 gates[li / 8] & (1 << (li % 8)) != 0
612 })
613 .collect();
614 let expert_masks: Vec<Vec<u8>> = if mm.has_expert_fields {
615 let base = gates_base + arch.gates_mask_bytes();
616 (0..arch.num_layers)
617 .map(|li| blob[base + li * expert_b..base + (li + 1) * expert_b].to_vec())
618 .collect()
619 } else {
620 Vec::new()
621 };
622
623 masks.push(TaskMask {
624 task_id: mm.task_id,
625 name: mm.name.clone(),
626 description: mm.description.clone(),
627 sparsity: mm.sparsity,
628 quality: mm.quality.clone(),
629 ffn_masks,
630 head_masks,
631 layer_gates,
632 expert_masks,
633 parent: mm.parent.clone(),
634 has_hot_pack: mm.has_hot_pack,
635 priority: mm.priority,
636 });
637 }
638
639 Ok(MaskCatalog {
640 masks,
641 default_task: meta.default_task,
642 })
643}