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