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