1use crate::fcd::{FcdModel, LnFfn};
22use crate::fcd_ops as ops;
23use crate::sampler::SplitMix64;
24use cortiq_core::CmfModel;
25use std::sync::Arc;
26
27#[derive(Clone, Debug)]
29pub struct BakeHyper {
30 pub steps_a: usize,
31 pub steps_b: usize,
32 pub l1_init: f64,
33 pub l1_step: f64,
34 pub eval_every: usize,
35 pub lr_a: f64,
36 pub lr_b: f64,
37 pub tau: f32,
38 pub fcd_layers: usize,
39 pub seed: u64,
40 pub target_sparsity: f64,
44 pub l1_mult: f64,
47 pub align: usize,
51 pub uniform_inter: bool,
54 pub focus_tokens: Vec<u32>,
60 pub focus_follow_tokens: Vec<u32>,
65}
66
67impl Default for BakeHyper {
68 fn default() -> Self {
69 Self {
70 steps_a: 240,
71 steps_b: 120,
72 l1_init: 0.01,
73 l1_step: 0.005,
74 eval_every: 30,
75 lr_a: 0.1,
76 lr_b: 1e-5,
77 tau: 0.5,
78 fcd_layers: 4,
79 seed: 0,
80 target_sparsity: 0.0,
81 l1_mult: 1.0,
82 align: 32,
83 uniform_inter: false,
84 focus_tokens: Vec::new(),
85 focus_follow_tokens: Vec::new(),
86 }
87 }
88}
89
90pub struct BakeReport {
92 pub backbone: f64,
94 pub masked: f64,
96 pub overlaid: f64,
98 pub pruned_ratio: f64,
99 pub kept_per_layer: Vec<usize>,
100 pub sec: f64,
101}
102
103pub struct BakeArtifacts {
105 pub keep: Vec<Vec<bool>>,
108 pub keep_visits: Vec<Vec<bool>>,
111 pub down: Vec<Vec<f32>>,
114 pub gate_up: Vec<Option<(Vec<f32>, Vec<f32>)>>,
116 pub fcd_layers: Vec<usize>,
118 pub logits: Vec<Vec<f32>>,
123}
124
125const CLIP: f64 = 1.0;
126const B1: f64 = 0.9;
127const B2: f64 = 0.999;
128const EPS: f64 = 1e-8;
129
130struct Adam {
132 m: Vec<Vec<f64>>,
133 v: Vec<Vec<f64>>,
134 t: i32,
135 lr: f64,
136}
137
138impl Adam {
139 fn new(sizes: &[usize], lr: f64) -> Self {
140 Self {
141 m: sizes.iter().map(|&n| vec![0.0; n]).collect(),
142 v: sizes.iter().map(|&n| vec![0.0; n]).collect(),
143 t: 0,
144 lr,
145 }
146 }
147
148 fn step(&mut self, params: &mut [&mut [f32]], grads: &[Vec<f64>], lr_scale: f64) {
150 let gn: f64 = grads
151 .iter()
152 .flat_map(|g| g.iter().map(|x| x * x))
153 .sum::<f64>()
154 .sqrt();
155 let clip = if gn > CLIP { CLIP / gn } else { 1.0 };
156 self.t += 1;
157 let (bc1, bc2) = (1.0 - B1.powi(self.t), 1.0 - B2.powi(self.t));
158 for (pi, p) in params.iter_mut().enumerate() {
159 for j in 0..p.len() {
160 let g = grads[pi][j] * clip;
161 let m = &mut self.m[pi][j];
162 let v = &mut self.v[pi][j];
163 *m = B1 * *m + (1.0 - B1) * g;
164 *v = B2 * *v + (1.0 - B2) * g * g;
165 let upd = (*m / bc1) / ((*v / bc2).sqrt() + EPS);
166 p[j] -= (self.lr * lr_scale * upd) as f32;
167 }
168 }
169 }
170}
171
172pub fn mask_init_logit(loops: usize) -> f32 {
191 let base = 1.0f32 / (1.0 + (-2.0f32).exp());
192 let per_visit = base.powf(1.0 / loops.max(1) as f32);
193 (per_visit / (1.0 - per_visit)).ln()
194}
195
196pub fn mask_step_scale(loops: usize) -> f64 {
202 1.0 / loops.max(1) as f64
203}
204
205fn sigmoid(x: f32) -> f32 {
206 1.0 / (1.0 + (-x).exp())
207}
208
209fn is_scored_target(
210 ids: &[u32],
211 target_index: usize,
212 sequence_end: usize,
213 focus: &[u32],
214 follow: &[u32],
215) -> bool {
216 if focus.is_empty() {
217 return true;
218 }
219 focus.contains(&ids[target_index])
220 && (follow.is_empty()
221 || (target_index + 1 < sequence_end && follow.contains(&ids[target_index + 1])))
222}
223
224struct Pass<'a> {
227 fm: &'a FcdModel,
228 tau: f32,
229 logits: &'a [Vec<f32>],
231 hard: bool,
232 ffn: &'a [Option<(Vec<f32>, Vec<f32>, Vec<f32>)>],
234 focus_tokens: &'a [u32],
236 focus_follow_tokens: &'a [u32],
238}
239
240impl Pass<'_> {
241 fn gates(&self, li: usize) -> Vec<f32> {
242 self.logits[li]
243 .iter()
244 .map(|&l| {
245 let s = sigmoid(l);
246 if self.hard {
247 if s > self.tau { 1.0 } else { 0.0 }
248 } else {
249 s
250 }
251 })
252 .collect()
253 }
254
255 fn wts<'b>(&'b self, li: usize, mats: &'b crate::fcd::LayerMats) -> LnFfn<'b> {
256 let l = &self.fm.layers[li];
257 match &self.ffn[li] {
258 Some((g, u, d)) => LnFfn {
259 iln: &l.iln,
260 pln: &l.pln,
261 gate: g,
262 up: u,
263 down: d,
264 gu: None,
266 },
267 None => LnFfn {
268 iln: &l.iln,
269 pln: &l.pln,
270 gate: &[],
271 up: &[],
272 down: &mats.down,
273 gu: Some(&mats.gu),
274 },
275 }
276 }
277
278 #[allow(clippy::too_many_arguments)]
282 fn chunk(
283 &self,
284 ids: &[u32],
285 grad: Option<(
286 &mut [Vec<f64>],
287 &mut [Option<(Vec<f64>, Vec<f64>, Vec<f64>)>],
288 )>,
289 ) -> (f64, usize) {
290 self.chunk_batch(ids, 1, grad)
291 }
292
293 fn chunk_batch(
301 &self,
302 ids: &[u32],
303 b: usize,
304 grad: Option<(
305 &mut [Vec<f64>],
306 &mut [Option<(Vec<f64>, Vec<f64>, Vec<f64>)>],
307 )>,
308 ) -> (f64, usize) {
309 let fm = self.fm;
310 let hsz = fm.hidden;
311 debug_assert!(ids.len() % b.max(1) == 0, "ragged batch");
312 debug_assert!(grad.is_none() || b == 1, "grads are per-chunk");
315 let t = ids.len() / b.max(1);
316 let n = b * t;
317 let nl = fm.layers.len();
318 let mut h = vec![0f32; n * hsz];
320 for (r, &id) in ids.iter().enumerate() {
321 h[r * hsz..(r + 1) * hsz]
322 .copy_from_slice(&fm.embed[id as usize * hsz..(id as usize + 1) * hsz]);
323 }
324 let loops = fm.loops.max(1);
332 let vn = nl * loops;
333 let mut h_ins = Vec::with_capacity(vn);
334 let mut acts = Vec::with_capacity(vn);
335 let mut masks = Vec::with_capacity(vn);
336 let mut lnorms: Vec<Option<(Vec<f32>, Vec<f32>)>> = vec![None; vn];
338 for vl in 0..vn {
339 let li = vl % nl;
340 let g = self.gates(vl);
345 let mats_hold = fm.mats(li).expect("layer mats");
346 let wts = self.wts(li, &mats_hold);
347 let want = grad.is_some();
348 let (h2, a) = fm.layer_forward_scaled(li, &h, b, t, &wts, false, want, Some(&g));
349 h_ins.push(if want { h } else { Vec::new() });
350 acts.push(a);
351 masks.push(g);
352 h = h2;
353 if fm.loop_norm && li + 1 == nl && vl + 1 < vn {
356 let mut hn = vec![0f32; n * hsz];
357 let mut inv = vec![0f32; n];
358 ops::rmsnorm_fwd(&h, &fm.final_norm, fm.eps, fm.gemma, &mut hn, &mut inv);
359 if want {
360 lnorms[vl] = Some((h, inv));
361 }
362 h = hn;
363 }
364 }
365 let mut hn = vec![0f32; n * hsz];
367 let mut inv = vec![0f32; n];
368 ops::rmsnorm_fwd(&h, &fm.final_norm, fm.eps, fm.gemma, &mut hn, &mut inv);
369 let lm: &[f32] = fm.lm_head.as_deref().unwrap_or(&fm.embed);
370 let vocab = lm.len() / hsz;
371 let pool = fm.pool.as_deref();
372 let mut nll = 0f64;
373 let mut dh_n = vec![0f32; n * hsz]; const POS_CHUNK: usize = 64;
378 let scored = (0..b)
379 .map(|bi| {
380 let base = bi * t;
381 (base + 1..base + t)
382 .filter(|&target_index| {
383 is_scored_target(
384 ids,
385 target_index,
386 base + t,
387 self.focus_tokens,
388 self.focus_follow_tokens,
389 )
390 })
391 .count()
392 })
393 .sum::<usize>();
394 if scored == 0 {
395 return (0.0, 0);
396 }
397 for bi in 0..b {
398 let base = bi * t;
399 let mut p0 = 0usize;
400 while p0 < t - 1 {
401 let pc = POS_CHUNK.min(t - 1 - p0);
402 let mut logits = vec![0f32; pc * vocab];
403 ops::gemm_nt(
404 &hn[(base + p0) * hsz..(base + p0 + pc) * hsz],
405 lm,
406 &mut logits,
407 pc,
408 hsz,
409 vocab,
410 pool,
411 );
412 for r in 0..pc {
413 let target_index = base + p0 + r + 1;
414 let target_id = ids[target_index];
415 let row = &mut logits[r * vocab..(r + 1) * vocab];
416 if !is_scored_target(
417 ids,
418 target_index,
419 base + t,
420 self.focus_tokens,
421 self.focus_follow_tokens,
422 ) {
423 if grad.is_some() {
424 row.fill(0.0);
425 }
426 continue;
427 }
428 let target = target_id as usize;
429 if self.focus_tokens.is_empty() {
430 let mx = row.iter().cloned().fold(f32::NEG_INFINITY, f32::max) as f64;
431 let mut sum = 0f64;
432 for v in row.iter() {
433 sum += ((*v as f64) - mx).exp();
434 }
435 nll += mx + sum.ln() - row[target] as f64;
436 if grad.is_some() {
437 let inv_n = 1.0 / scored as f64;
439 for v in row.iter_mut() {
440 *v = ((((*v as f64) - mx).exp() / sum) * inv_n) as f32;
441 }
442 row[target] -= inv_n as f32;
443 }
444 } else {
445 let mx = self
452 .focus_tokens
453 .iter()
454 .map(|&id| row[id as usize])
455 .fold(f32::NEG_INFINITY, f32::max)
456 as f64;
457 let probs: Vec<(usize, f64)> = self
458 .focus_tokens
459 .iter()
460 .map(|&id| {
461 let index = id as usize;
462 (index, ((row[index] as f64) - mx).exp())
463 })
464 .collect();
465 let sum: f64 = probs.iter().map(|(_, value)| value).sum();
466 nll += mx + sum.ln() - row[target] as f64;
467 if grad.is_some() {
468 let inv_n = 1.0 / scored as f64;
469 row.fill(0.0);
470 for (index, value) in probs {
471 row[index] = (value / sum * inv_n) as f32;
472 }
473 row[target] -= inv_n as f32;
474 }
475 }
476 }
477 if grad.is_some() {
478 ops::gemm_dx(
479 &logits,
480 lm,
481 &mut dh_n[(base + p0) * hsz..(base + p0 + pc) * hsz],
482 pc,
483 hsz,
484 vocab,
485 pool,
486 );
487 }
488 p0 += pc;
489 }
490 }
491 let Some((dmask, dffn)) = grad else {
492 return (nll, scored);
493 };
494 let t_bwd = std::time::Instant::now();
496 let mut dh = vec![0f32; n * hsz];
497 ops::rmsnorm_bwd(&h, &fm.final_norm, &inv, &dh_n, fm.gemma, &mut dh, None);
498 for vl in (0..vn).rev() {
499 let li = vl % nl;
500 if let Some((hb, inv)) = lnorms[vl].as_ref() {
502 let mut dprev = vec![0f32; n * hsz];
503 ops::rmsnorm_bwd(hb, &fm.final_norm, inv, &dh, fm.gemma, &mut dprev, None);
504 dh = dprev;
505 }
506 let a = acts[vl].as_ref().expect("acts saved in grad mode");
507 let g = &masks[vl];
508 let inter = fm.layers[li].inter;
509 let mats_hold = fm.mats(li).expect("layer mats");
510 let wts = self.wts(li, &mats_hold);
511 let mut dact2 = vec![0f32; t * inter];
513 ops::gemm_dx(&dh, wts.down, &mut dact2, t, inter, hsz, fm.pool.as_deref());
514 if let Some((_, _, dd)) = dffn[li].as_mut() {
515 let mut act2 = a.act.clone();
517 for r in 0..t {
518 for (x, &gv) in act2[r * inter..(r + 1) * inter].iter_mut().zip(g) {
519 *x *= gv;
520 }
521 }
522 let mut dw = vec![0f32; hsz * inter];
523 ops::gemm_dw(&dh, &act2, &mut dw, t, inter, hsz, fm.pool.as_deref());
524 for (o, &x) in dd.iter_mut().zip(&dw) {
525 *o += x as f64;
526 }
527 }
528 {
532 let dm = &mut dmask[vl];
533 for r in 0..t {
534 let da = &dact2[r * inter..(r + 1) * inter];
535 let aa = &a.act[r * inter..(r + 1) * inter];
536 for j in 0..inter {
537 dm[j] += da[j] as f64 * aa[j] as f64;
538 }
539 }
540 for (j, d) in dm.iter_mut().enumerate() {
542 let _ = j;
543 let _ = d;
544 }
545 }
546 let mut dg_pre = vec![0f32; t * inter];
548 let mut du_pre = vec![0f32; t * inter];
549 for r in 0..t {
550 for j in 0..inter {
551 let i = r * inter + j;
552 let da = dact2[i] * g[j];
553 let sg = ops::silu(a.gpre[i]);
554 dg_pre[i] = da * a.upre[i] * ops::silu_bwd(a.gpre[i]);
555 du_pre[i] = da * sg;
556 }
557 }
558 let mut dn2 = vec![0f32; t * hsz];
561 if let Some(gu) = wts.gu {
562 let mut dgu = vec![0f32; t * 2 * inter];
563 for r in 0..t {
564 let row = &mut dgu[r * 2 * inter..(r + 1) * 2 * inter];
565 row[..inter].copy_from_slice(&dg_pre[r * inter..(r + 1) * inter]);
566 row[inter..].copy_from_slice(&du_pre[r * inter..(r + 1) * inter]);
567 }
568 ops::gemm_dx(&dgu, gu, &mut dn2, t, hsz, 2 * inter, fm.pool.as_deref());
569 } else {
570 ops::gemm_dx(
571 &dg_pre,
572 wts.gate,
573 &mut dn2,
574 t,
575 hsz,
576 inter,
577 fm.pool.as_deref(),
578 );
579 let mut dn2b = vec![0f32; t * hsz];
580 ops::gemm_dx(
581 &du_pre,
582 wts.up,
583 &mut dn2b,
584 t,
585 hsz,
586 inter,
587 fm.pool.as_deref(),
588 );
589 for (x, &y) in dn2.iter_mut().zip(&dn2b) {
590 *x += y;
591 }
592 }
593 if let Some((dgw, duw, _)) = dffn[li].as_mut() {
594 let mut dw = vec![0f32; inter * hsz];
595 ops::gemm_dw(&dg_pre, &a.n2, &mut dw, t, hsz, inter, fm.pool.as_deref());
596 for (o, &x) in dgw.iter_mut().zip(&dw) {
597 *o += x as f64;
598 }
599 dw.fill(0.0);
600 ops::gemm_dw(&du_pre, &a.n2, &mut dw, t, hsz, inter, fm.pool.as_deref());
601 for (o, &x) in duw.iter_mut().zip(&dw) {
602 *o += x as f64;
603 }
604 }
605 let mut dh1 = dh.clone(); ops::rmsnorm_bwd(&a.h1, wts.pln, &a.inv2, &dn2, fm.gemma, &mut dh1, None);
609 dh = dh1;
610 let _ = &h_ins[vl];
611 }
612 crate::fcd::prof::add(&crate::fcd::prof::BWD, t_bwd);
613 (nll, scored)
614 }
615}
616
617fn held_ppl(pass: &Pass, held: &[Vec<u32>]) -> f64 {
619 if held.is_empty() {
623 return f64::NAN;
624 }
625 let t = held[0].len();
626 if held.iter().all(|c| c.len() == t) {
627 let flat: Vec<u32> = held.iter().flatten().copied().collect();
628 let (l, k) = pass.chunk_batch(&flat, held.len(), None);
629 return (l / k.max(1) as f64).exp();
630 }
631 let mut nll = 0f64;
632 let mut n = 0usize;
633 for c in held {
634 let (l, k) = pass.chunk(c, None);
635 nll += l;
636 n += k;
637 }
638 (nll / n.max(1) as f64).exp()
639}
640
641pub fn replica_score_file_mask(
647 model: &Arc<CmfModel>,
648 chunks: &[Vec<u32>],
649) -> Result<(f64, f64), String> {
650 let o1_off = crate::nystrom::O1Cfg {
651 layers: crate::nystrom::O1Layers::List(Vec::new()),
652 m: 4,
653 w: 8,
654 sink: 1,
655 rect: crate::nystrom::O1_DEFAULT_RECT,
656 };
657 let fm = FcdModel::from_cmf(model, &o1_off, false)?;
658 let nl = fm.layers.len();
659 let loops = fm.loops.max(1);
660 let vn = nl * loops;
661 let inter = fm.layers[0].inter;
662 let ffn: Vec<Option<(Vec<f32>, Vec<f32>, Vec<f32>)>> = vec![None; nl];
663 let task = &model.masks.default_task;
665 let mask = model
666 .masks
667 .masks
668 .iter()
669 .find(|m| &m.name == task)
670 .or_else(|| model.masks.masks.first());
671 let open: Vec<Vec<f32>> = vec![vec![50.0; inter]; vn];
672 let masked_logits: Vec<Vec<f32>> = match mask {
673 Some(m) => (0..vn)
674 .map(|vl| {
675 let row = m.ffn_masks.get(vl).map(|v| v.as_slice()).unwrap_or(&[]);
676 (0..inter)
677 .map(|j| {
678 if (row.get(j >> 3).copied().unwrap_or(0) >> (j & 7)) & 1 != 0 {
679 50.0
680 } else {
681 -50.0
682 }
683 })
684 .collect()
685 })
686 .collect(),
687 None => open.clone(),
688 };
689 let score = |logits: &[Vec<f32>]| -> f64 {
690 let pass = Pass {
691 fm: &fm,
692 tau: 0.5,
693 logits,
694 hard: true,
695 ffn: &ffn,
696 focus_tokens: &[],
697 focus_follow_tokens: &[],
698 };
699 held_ppl(&pass, chunks)
700 };
701 Ok((score(&open), score(&masked_logits)))
702}
703
704pub fn skill_bake(
706 model: &Arc<CmfModel>,
707 chunks: &[Vec<u32>],
708 held_n: usize,
709 hy: &BakeHyper,
710 mut log: impl FnMut(&str),
711) -> Result<(BakeReport, BakeArtifacts), String> {
712 let t0 = std::time::Instant::now();
713 let o1_off = crate::nystrom::O1Cfg {
714 layers: crate::nystrom::O1Layers::List(Vec::new()),
715 m: 4,
716 w: 8,
717 sink: 1,
718 rect: crate::nystrom::O1_DEFAULT_RECT,
719 };
720 let fm = FcdModel::from_cmf(model, &o1_off, false)?;
721 let nl = fm.layers.len();
722 let inter = fm.layers.iter().map(|l| l.inter).max().unwrap_or(0);
723 if fm.layers.iter().any(|l| l.inter != inter) {
724 return Err("skill bake: non-uniform FFN widths".into());
725 }
726 let held: Vec<Vec<u32>> = chunks[..held_n.min(chunks.len())].to_vec();
727 let calib: Vec<Vec<u32>> = chunks[held_n.min(chunks.len())..].to_vec();
728 if calib.len() < 12 {
729 return Err(format!(
730 "skill bake: corpus too small ({} calib chunks)",
731 calib.len()
732 ));
733 }
734 let fcd: Vec<usize> = (nl.saturating_sub(hy.fcd_layers)..nl).collect();
735 let _rng = SplitMix64::new(hy.seed);
736
737 let loops = fm.loops.max(1);
765 let m0 = mask_init_logit(loops);
766 let vn = nl * loops;
768 let mut logits: Vec<Vec<f32>> = vec![vec![m0; inter]; vn];
769 let mut ffn: Vec<Option<(Vec<f32>, Vec<f32>, Vec<f32>)>> = vec![None; nl];
770
771 let open: Vec<Vec<f32>> = vec![vec![50.0; inter]; vn];
774 let base_pass = Pass {
775 fm: &fm,
776 tau: hy.tau,
777 logits: &open,
778 hard: true,
779 ffn: &ffn,
780 focus_tokens: &hy.focus_tokens,
781 focus_follow_tokens: &hy.focus_follow_tokens,
782 };
783 let backbone = held_ppl(&base_pass, &held);
784 log(&format!("baseline (full): {backbone:.3}"));
785
786 let mut adam_a = Adam::new(&vec![inter; vn], hy.lr_a);
788 let mut l1 = hy.l1_init * hy.l1_mult;
789 let l1_step_eff = hy.l1_step * hy.l1_mult;
790 let mut best: (f64, Option<Vec<Vec<f32>>>, f64) = (f64::MAX, None, 0.0);
792 let mut max_sp: (f64, Option<Vec<Vec<f32>>>, f64) = (f64::MAX, None, 0.0);
794 let mut prev_alive: Option<Vec<Vec<bool>>> = None;
795 let mut acc_chunk = 0f64;
799 let mut acc_adam = 0f64;
800 crate::gpu::bake_precision_strict(true);
808 for step in 0..hy.steps_a {
809 let t_step = std::time::Instant::now();
810 let chunk = &calib[step % calib.len()];
811 let mut dmask: Vec<Vec<f64>> = vec![vec![0.0; inter]; vn];
812 let mut dffn: Vec<Option<(Vec<f64>, Vec<f64>, Vec<f64>)>> = vec![None; nl];
813 let pass = Pass {
814 fm: &fm,
815 tau: hy.tau,
816 logits: &logits,
817 hard: false,
818 ffn: &ffn,
819 focus_tokens: &hy.focus_tokens,
820 focus_follow_tokens: &hy.focus_follow_tokens,
821 };
822 let _ = pass.chunk(chunk, Some((&mut dmask, &mut dffn)));
823 let l1_per = l1 / (inter as f64 * nl as f64);
825 for li in 0..vn {
826 for j in 0..inter {
827 let s = sigmoid(logits[li][j]) as f64;
828 dmask[li][j] = dmask[li][j] * s * (1.0 - s) + l1_per * s * (1.0 - s);
829 }
830 }
831 let t_chunk = t_step.elapsed().as_secs_f64();
844 let mut params: Vec<&mut [f32]> = logits.iter_mut().map(|v| v.as_mut_slice()).collect();
845 adam_a.step(&mut params, &dmask, 1.0);
846 acc_chunk += t_chunk;
847 acc_adam += t_step.elapsed().as_secs_f64() - t_chunk;
848 if (step + 1) % hy.eval_every == 0 {
849 l1 += l1_step_eff;
850 let pass = Pass {
851 fm: &fm,
852 tau: hy.tau,
853 logits: &logits,
854 hard: true,
855 ffn: &ffn,
856 focus_tokens: &hy.focus_tokens,
857 focus_follow_tokens: &hy.focus_follow_tokens,
858 };
859 crate::gpu::bake_precision_strict(false);
860 let hp = held_ppl(&pass, &held);
861 crate::gpu::bake_precision_strict(true);
862 let cur: Vec<Vec<bool>> = logits
868 .iter()
869 .map(|l| l.iter().map(|&x| sigmoid(x) > hy.tau).collect())
870 .collect();
871 if let Some(prev) = &prev_alive {
872 let died: Vec<String> = cur
873 .iter()
874 .zip(prev)
875 .enumerate()
876 .flat_map(|(li, (c, p))| {
877 c.iter()
878 .zip(p.iter())
879 .enumerate()
880 .filter(|&(_, (&cj, &pj))| pj && !cj)
881 .map(move |(j, _)| format!("L{li}:{j}"))
882 })
883 .collect();
884 if !died.is_empty() {
885 log(&format!(
886 " closed since last eval: {}: {}{}",
887 died.len(),
888 died.iter().take(32).cloned().collect::<Vec<_>>().join(" "),
889 if died.len() > 32 { " …" } else { "" }
890 ));
891 }
892 }
893 let alive: usize = cur.iter().map(|l| l.iter().filter(|&&b| b).count()).sum();
894 prev_alive = Some(cur);
895 let sp = 1.0 - alive as f64 / (vn * inter) as f64;
896 if sp > max_sp.2 {
898 max_sp = (hp, Some(logits.clone()), sp);
899 }
900 if hy.target_sparsity > 0.0 {
902 if sp >= hy.target_sparsity && hp < best.0 {
903 best = (hp, Some(logits.clone()), sp);
904 }
905 } else if hp < best.0 {
906 best = (hp, Some(logits.clone()), sp);
907 }
908 log(&format!(
909 " [A] step {}: L1={l1:.3} pruned={:.2}% hard-PPL={hp:.3} (bottom {}@{:.2}%) [fwd+bwd {:.1}s, adam {:.2}s per step]",
910 step + 1,
911 sp * 100.0,
912 if best.0 == f64::MAX {
913 "—".to_string()
914 } else {
915 format!("{:.3}", best.0)
916 },
917 best.2 * 100.0,
918 acc_chunk / (step + 1) as f64,
919 acc_adam / (step + 1) as f64
920 ));
921 }
922 }
923 crate::gpu::bake_precision_strict(false);
927 if hy.target_sparsity > 0.0 && best.1.is_none() {
928 log(&format!(
929 "[A] target sparsity {:.0}% not reached; using max-sparsity checkpoint ({:.0}%)",
930 hy.target_sparsity * 100.0,
931 max_sp.2 * 100.0
932 ));
933 best = max_sp;
934 }
935 {
938 use crate::fcd::prof;
939 let (a, f, bw, g, gc) = (
940 prof::take(&prof::ATTN_FWD),
941 prof::take(&prof::FFN_FWD),
942 prof::take(&prof::BWD),
943 prof::take(&prof::GEMM),
944 prof::GEMM_CALLS.swap(0, std::sync::atomic::Ordering::Relaxed),
945 );
946 log(&format!(
947 "[prof] phase A over {} step(s): attn-fwd {a:.1}s | ffn-fwd {f:.1}s | bwd {bw:.1}s | gemm total {g:.1}s in {gc} calls ({:.1} ms/call)",
948 hy.steps_a,
949 if gc > 0 { g * 1000.0 / gc as f64 } else { 0.0 }
950 ));
951 log(&format!("[prof] gemm shapes:\n{}", prof::shape_report(6)));
952 }
953 if let Some(b) = best.1.take() {
954 logits = b;
955 }
956 let pass = Pass {
957 fm: &fm,
958 tau: hy.tau,
959 logits: &logits,
960 hard: true,
961 ffn: &ffn,
962 focus_tokens: &hy.focus_tokens,
963 focus_follow_tokens: &hy.focus_follow_tokens,
964 };
965 let masked = held_ppl(&pass, &held);
966 log(&format!(
967 "[A] {:.0}s: masked-PPL {masked:.3}",
968 t0.elapsed().as_secs_f64()
969 ));
970
971 for &li in &fcd {
973 let p = format!("model.layers.{li}.");
974 ffn[li] = Some((
975 crate::fcd::deq_pub(&fm.src, &format!("{p}mlp.gate_proj.weight"))
976 .map_err(|e| format!("phase-B gate: {e}"))?,
977 crate::fcd::deq_pub(&fm.src, &format!("{p}mlp.up_proj.weight"))
978 .map_err(|e| format!("phase-B up: {e}"))?,
979 crate::fcd::deq_pub(&fm.src, &format!("{p}mlp.down_proj.weight"))
980 .map_err(|e| format!("phase-B down: {e}"))?,
981 ));
982 }
983 let sizes: Vec<usize> = fcd
984 .iter()
985 .flat_map(|&li| {
986 let (g, u, d) = ffn[li].as_ref().expect("phase-B masters");
987 [g.len(), u.len(), d.len()]
988 })
989 .collect();
990 let mut adam_b = Adam::new(&sizes, hy.lr_b);
991 let mut best_b: (f64, Option<Vec<Option<(Vec<f32>, Vec<f32>, Vec<f32>)>>>) =
995 (masked, Some(vec![None; nl]));
996 for step in 0..hy.steps_b {
997 let chunk = &calib[step % calib.len()];
998 let mut dmask: Vec<Vec<f64>> = vec![vec![0.0; inter]; vn];
999 let mut dffn: Vec<Option<(Vec<f64>, Vec<f64>, Vec<f64>)>> = (0..nl)
1000 .map(|li| {
1001 ffn[li]
1002 .as_ref()
1003 .map(|(g, u, d)| (vec![0.0; g.len()], vec![0.0; u.len()], vec![0.0; d.len()]))
1004 })
1005 .collect();
1006 let pass = Pass {
1007 fm: &fm,
1008 tau: hy.tau,
1009 logits: &logits,
1010 hard: true,
1011 ffn: &ffn,
1012 focus_tokens: &hy.focus_tokens,
1013 focus_follow_tokens: &hy.focus_follow_tokens,
1014 };
1015 let _ = pass.chunk(chunk, Some((&mut dmask, &mut dffn)));
1016 let lr_scale = 0.5 * (1.0 + (std::f64::consts::PI * step as f64 / hy.steps_b as f64).cos());
1018 let first_fcd = fcd[0];
1019 let mut params: Vec<&mut [f32]> = Vec::new();
1020 let mut grads: Vec<Vec<f64>> = Vec::new();
1021 for (off, slot) in ffn[first_fcd..].iter_mut().enumerate() {
1022 let li = first_fcd + off;
1023 let Some((g, u, d)) = slot.as_mut() else {
1024 continue;
1025 };
1026 let (dg, du, dd) = dffn[li].take().unwrap();
1027 params.push(g.as_mut_slice());
1028 grads.push(dg);
1029 params.push(u.as_mut_slice());
1030 grads.push(du);
1031 params.push(d.as_mut_slice());
1032 grads.push(dd);
1033 }
1034 adam_b.step(&mut params, &grads, lr_scale * mask_step_scale(loops));
1039 if (step + 1) % hy.eval_every == 0 {
1040 let pass = Pass {
1041 fm: &fm,
1042 tau: hy.tau,
1043 logits: &logits,
1044 hard: true,
1045 ffn: &ffn,
1046 focus_tokens: &hy.focus_tokens,
1047 focus_follow_tokens: &hy.focus_follow_tokens,
1048 };
1049 let cur = held_ppl(&pass, &held);
1050 if cur < best_b.0 {
1051 best_b = (cur, Some(ffn.clone()));
1052 }
1053 log(&format!(
1054 " [B] step {}: held-PPL {cur:.3} (best {:.3})",
1055 step + 1,
1056 best_b.0
1057 ));
1058 }
1059 }
1060 ffn = best_b.1.take().expect("phase-B always has a checkpoint");
1061 let overlaid = best_b.0;
1062
1063 let keep_visits = keep_masks(&logits, hy.tau, hy.align, hy.uniform_inter);
1068 let keep: Vec<Vec<bool>> = (0..nl)
1069 .map(|li| {
1070 (0..inter)
1071 .map(|j| (0..loops).any(|v| keep_visits[v * nl + li][j]))
1072 .collect()
1073 })
1074 .collect();
1075 if hy.align > 1 || hy.uniform_inter {
1076 let raw: usize = logits
1077 .iter()
1078 .map(|l| l.iter().filter(|&&x| sigmoid(x) > hy.tau).count())
1079 .sum();
1080 let padded: usize = keep_visits
1084 .iter()
1085 .map(|a| a.iter().filter(|&&x| x).count())
1086 .sum::<usize>()
1087 .saturating_sub(raw);
1088 log(&format!(
1089 "align: +{padded} neurons resurrected (align {}, uniform {})",
1090 hy.align, hy.uniform_inter
1091 ));
1092 }
1093 let mut down_out = Vec::with_capacity(nl);
1094 let mut gate_up = Vec::with_capacity(nl);
1095 let mut kept_per_layer = Vec::with_capacity(nl);
1096 for li in 0..nl {
1097 let alive = &keep[li];
1098 kept_per_layer.push(alive.iter().filter(|&&a| a).count());
1099 let mut down = match &ffn[li] {
1100 Some((_, _, d)) => d.clone(),
1101 None => fm.mats(li).expect("layer mats").down.clone(),
1102 };
1103 let hsz = fm.hidden;
1104 for r in 0..hsz {
1105 for (c, &a) in alive.iter().enumerate() {
1106 if !a {
1107 down[r * inter + c] = 0.0;
1108 }
1109 }
1110 }
1111 gate_up.push(ffn[li].as_ref().map(|(g, u, _)| (g.clone(), u.clone())));
1112 down_out.push(down);
1113 }
1114 let total: usize = keep_visits
1115 .iter()
1116 .map(|a| a.iter().filter(|&&x| x).count())
1117 .sum();
1118 let report = BakeReport {
1119 backbone,
1120 masked,
1121 overlaid,
1122 pruned_ratio: 1.0 - total as f64 / (vn * inter) as f64,
1123 kept_per_layer,
1124 sec: t0.elapsed().as_secs_f64(),
1125 };
1126 let arts = BakeArtifacts {
1127 keep,
1128 keep_visits,
1129 down: down_out,
1130 gate_up,
1131 fcd_layers: fcd,
1132 logits: logits.clone(),
1133 };
1134 Ok((report, arts))
1135}
1136
1137fn keep_masks(logits: &[Vec<f32>], tau: f32, align: usize, uniform: bool) -> Vec<Vec<bool>> {
1145 let inter = logits[0].len();
1146 let round = |n: usize| -> usize {
1147 let n = n.max(1);
1148 if align <= 1 {
1149 n.min(inter)
1150 } else {
1151 (n.div_ceil(align) * align).min(inter)
1152 }
1153 };
1154 let mut want: Vec<usize> = logits
1155 .iter()
1156 .map(|l| round(l.iter().filter(|&&x| sigmoid(x) > tau).count()))
1157 .collect();
1158 if uniform {
1159 let k = want.iter().copied().max().unwrap_or(inter);
1160 want = vec![k; logits.len()];
1161 }
1162 logits
1163 .iter()
1164 .zip(&want)
1165 .map(|(l, &k)| {
1166 let mut idx: Vec<usize> = (0..inter).collect();
1167 idx.sort_unstable_by(|&a, &b| l[b].total_cmp(&l[a]));
1168 let mut alive = vec![false; inter];
1169 for &i in idx.iter().take(k) {
1170 alive[i] = true;
1171 }
1172 alive
1173 })
1174 .collect()
1175}
1176
1177#[cfg(test)]
1178mod tests {
1179 use super::*;
1180
1181 fn kept(masks: &[Vec<bool>]) -> Vec<usize> {
1182 masks
1183 .iter()
1184 .map(|m| m.iter().filter(|&&a| a).count())
1185 .collect()
1186 }
1187
1188 #[test]
1189 fn terminal_focus_ignores_label_names_inside_the_prompt() {
1190 let down = 10;
1193 let up = 11;
1194 let im_end = 99;
1195 let ids = [1, down, 2, up, 3, up, im_end, 4];
1196 let focus = [down, up];
1197 let follow = [im_end];
1198 assert!(!is_scored_target(&ids, 1, ids.len(), &focus, &follow));
1199 assert!(!is_scored_target(&ids, 3, ids.len(), &focus, &follow));
1200 assert!(is_scored_target(&ids, 5, ids.len(), &focus, &follow));
1201 }
1202
1203 #[test]
1206 fn keep_masks_aligns_up_and_preserves_alive() {
1207 let inter = 96;
1208 let l0: Vec<f32> = (0..inter)
1211 .map(|i| if i < 40 { 1.0 } else { -1.0 - i as f32 * 0.01 })
1212 .collect();
1213 let l1: Vec<f32> = (0..inter)
1215 .map(|i| if i < 64 { 2.0 } else { -3.0 })
1216 .collect();
1217 let masks = keep_masks(&[l0.clone(), l1], 0.5, 32, false);
1218 assert_eq!(kept(&masks), vec![64, 64]);
1219 for i in 0..64 {
1222 assert!(masks[0][i], "neuron {i} should be kept");
1223 }
1224 for i in 64..inter {
1225 assert!(!masks[0][i], "neuron {i} should stay pruned");
1226 }
1227 }
1228
1229 #[test]
1231 fn keep_masks_uniform_takes_max() {
1232 let inter = 96;
1233 let l0: Vec<f32> = (0..inter)
1234 .map(|i| if i < 10 { 1.0 } else { -2.0 })
1235 .collect();
1236 let l1: Vec<f32> = (0..inter)
1237 .map(|i| if i < 70 { 1.0 } else { -2.0 })
1238 .collect();
1239 let masks = keep_masks(&[l0, l1], 0.5, 32, true);
1240 assert_eq!(kept(&masks), vec![96, 96]);
1241 }
1242
1243 #[test]
1246 fn keep_masks_edges() {
1247 let inter = 48;
1248 let l: Vec<f32> = (0..inter)
1249 .map(|i| if i < 47 { 1.0 } else { -2.0 })
1250 .collect();
1251 let masks = keep_masks(&[l.clone()], 0.5, 32, false);
1252 assert_eq!(kept(&masks), vec![48]); let masks = keep_masks(&[l], 0.5, 1, false);
1254 assert_eq!(kept(&masks), vec![47]);
1255 let dead: Vec<f32> = vec![-5.0; inter];
1256 let masks = keep_masks(&[dead], 0.5, 32, false);
1257 assert_eq!(kept(&masks), vec![32]); }
1259}