1use crate::deepseek_v4_budget::LayerCompressor;
34use ferrox_core::attention::apply_rope_back;
35use ferrox_core::csa_hca_compress::compress_block;
36use ferrox_core::deepseek_v4_attention::{csa_attention, hca_attention};
37use ferrox_core::matmul::rms_norm;
38use ferrox_core::tensor::Tensor;
39use ferrox_core::weight_matrix::WeightMatrix;
40use ferrox_moe::{
41 combine_expert_outputs, route_top_k, run_expert, ExpertWeights, GatingFunction, GluAct,
42};
43
44use crate::hyper_connections::{
45 head as hc_head, post as hc_post, pre as hc_pre, HyperConnectionHeadWeights,
46 HyperConnectionPreWeights, HC_MULT,
47};
48use crate::output_projection::grouped_output_projection;
49
50pub struct DeepseekV4CsaWeights {
66 pub role_proj: WeightMatrix,
69 pub role_gate: WeightMatrix,
72 pub indexer_key_proj: WeightMatrix,
81 pub indexer_q_proj: WeightMatrix,
83 pub indexer_head_weights: Vec<f32>,
85 pub indexer_top_k: usize,
87}
88
89pub struct DeepseekV4AttnWeights {
91 pub q_proj: WeightMatrix,
92 pub k_proj: WeightMatrix,
93 pub v_proj: WeightMatrix,
94 pub group_down: Vec<WeightMatrix>,
96 pub wo_b: WeightMatrix,
97 pub comp_gate: WeightMatrix,
99 pub comp_norm: Vec<f32>,
100 pub attn_sinks: Option<Vec<f32>>,
105 pub csa: Option<DeepseekV4CsaWeights>,
107}
108
109pub struct DeepseekV4MoeFfnWeights {
111 pub router_weight: WeightMatrix,
112 pub experts: Vec<ExpertWeights>,
113 pub shared_expert: ExpertWeights,
114}
115
116pub struct DeepseekV4DecoderLayerWeights {
117 pub attn_hc_pre: HyperConnectionPreWeights,
118 pub attn_norm_weight: Vec<f32>,
119 pub attn: DeepseekV4AttnWeights,
120 pub ffn_hc_pre: HyperConnectionPreWeights,
121 pub ffn_norm_weight: Vec<f32>,
122 pub ffn: DeepseekV4MoeFfnWeights,
123}
124
125pub struct DeepseekV4DecoderWeights {
126 pub embedding: Tensor,
127 pub layer: DeepseekV4DecoderLayerWeights,
128 pub hc_head: HyperConnectionHeadWeights,
129 pub final_norm_weight: Vec<f32>,
130 pub output_head: WeightMatrix,
131}
132
133pub struct DeepseekV4DecoderConfig {
134 pub rms_norm_eps: f32,
135 pub hc_sinkhorn_iters: u32,
136 pub hc_eps: f32,
137 pub n_heads: usize,
138 pub qk_head_dim: usize,
139 pub v_head_dim: usize,
140 pub qk_rope: usize,
141 pub compress_rope_theta: f32,
142 pub n_experts_active: usize,
143 pub moe_renormalize: bool,
144 pub compressor: LayerCompressor,
157}
158
159pub struct DeepseekV4LayerState {
164 hc_streams: [Vec<f32>; HC_MULT],
165 raw_k: Vec<f32>,
166 raw_v: Vec<f32>,
167 compressed_k: Vec<f32>,
168 compressed_v: Vec<f32>,
169 role_kv: Vec<f32>,
173 role_scores: Vec<f32>,
174 token_count: usize,
175}
176
177impl DeepseekV4LayerState {
178 pub fn new(hidden_dim: usize) -> Self {
179 let zero = vec![0.0; hidden_dim];
180 Self {
181 hc_streams: std::array::from_fn(|_| zero.clone()),
182 raw_k: Vec::new(),
183 raw_v: Vec::new(),
184 compressed_k: Vec::new(),
185 compressed_v: Vec::new(),
186 role_kv: Vec::new(),
187 role_scores: Vec::new(),
188 token_count: 0,
189 }
190 }
191
192 fn reset_hc_from_hidden(&mut self, hidden: &[f32]) {
193 for stream in self.hc_streams.iter_mut() {
194 stream.copy_from_slice(hidden);
195 }
196 }
197}
198
199pub struct DeepseekV4DecodeState {
200 layer: DeepseekV4LayerState,
201}
202
203impl DeepseekV4DecodeState {
204 pub fn new(hidden_dim: usize) -> Self {
205 Self {
206 layer: DeepseekV4LayerState::new(hidden_dim),
207 }
208 }
209}
210
211fn derope_attn_out(
212 attn_out: &mut [f32],
213 n_heads: usize,
214 v_head_dim: usize,
215 qk_rope: usize,
216 pos: usize,
217 theta: f32,
218) {
219 assert!(qk_rope <= v_head_dim);
220 for h in 0..n_heads {
221 let head_start = h * v_head_dim;
222 let rope_start = head_start + v_head_dim - qk_rope;
223 apply_rope_back(
224 &mut attn_out[rope_start..head_start + v_head_dim],
225 pos,
226 theta,
227 );
228 }
229}
230
231fn hca_block(
237 weights: &DeepseekV4AttnWeights,
238 cfg: &DeepseekV4DecoderConfig,
239 state: &mut DeepseekV4LayerState,
240 ratio: usize,
241) {
242 let n_raw = state.token_count;
243 let per_token_k = cfg.n_heads * cfg.qk_head_dim;
244 let per_token_v = cfg.n_heads * cfg.v_head_dim;
245 let block_index = n_raw / ratio;
246
247 let kv_block: Vec<Vec<f32>> = state.raw_k[(n_raw - ratio) * per_token_k..n_raw * per_token_k]
248 .chunks(cfg.qk_head_dim)
249 .map(|row| row.to_vec())
250 .collect();
251 let score_block: Vec<Vec<f32>> = kv_block
252 .iter()
253 .map(|row| weights.comp_gate.apply(row))
254 .collect();
255 let compressed_k = compress_block(
256 &kv_block,
257 &score_block,
258 &weights.comp_norm,
259 cfg.rms_norm_eps,
260 cfg.qk_rope,
261 block_index,
262 cfg.compress_rope_theta,
263 );
264 let v_block: Vec<Vec<f32>> = state.raw_v[(n_raw - ratio) * per_token_v..n_raw * per_token_v]
265 .chunks(cfg.v_head_dim)
266 .map(|row| row.to_vec())
267 .collect();
268 let compressed_v = compress_block(
269 &v_block,
270 &score_block,
271 &weights.comp_norm,
272 cfg.rms_norm_eps,
273 cfg.qk_rope,
274 block_index,
275 cfg.compress_rope_theta,
276 );
277 state.compressed_k.extend_from_slice(&compressed_k);
278 state.compressed_v.extend_from_slice(&compressed_v);
279}
280
281fn csa_block(
305 weights: &DeepseekV4AttnWeights,
306 cfg: &DeepseekV4DecoderConfig,
307 state: &mut DeepseekV4LayerState,
308 ratio: usize,
309) {
310 assert_eq!(
311 cfg.n_heads, 1,
312 "this skeleton compresses one head: a block is assembled as rows of qk_head_dim, so \
313 more heads would pool across them into a single entry and leave n_compressed \
314 fractional. Multi-head compression is real scope, not a silent approximation"
315 );
316 let n_raw = state.token_count;
317 let blocks_closed = n_raw / ratio;
322 let block_ord = blocks_closed - 1;
323 let role_width = 2 * cfg.qk_head_dim;
324
325 let mut kv_block: Vec<Vec<f32>> = Vec::with_capacity(2 * ratio);
326 let mut score_block: Vec<Vec<f32>> = Vec::with_capacity(2 * ratio);
327 let mut v_block: Vec<Vec<f32>> = Vec::with_capacity(2 * ratio);
328
329 for step in 0..ratio {
332 if block_ord == 0 {
333 kv_block.push(vec![0.0; cfg.qk_head_dim]);
334 score_block.push(vec![f32::NEG_INFINITY; cfg.qk_head_dim]);
335 v_block.push(vec![0.0; cfg.v_head_dim]);
336 continue;
337 }
338 let token = (block_ord - 1) * ratio + step;
339 let at = token * role_width;
340 kv_block.push(state.role_kv[at..at + cfg.qk_head_dim].to_vec());
341 score_block.push(state.role_scores[at..at + cfg.qk_head_dim].to_vec());
342 let v_at = token * cfg.v_head_dim;
343 v_block.push(state.raw_v[v_at..v_at + cfg.v_head_dim].to_vec());
344 }
345 for step in 0..ratio {
347 let token = block_ord * ratio + step;
348 let at = token * role_width + cfg.qk_head_dim;
349 kv_block.push(state.role_kv[at..at + cfg.qk_head_dim].to_vec());
350 score_block.push(state.role_scores[at..at + cfg.qk_head_dim].to_vec());
351 let v_at = token * cfg.v_head_dim;
352 v_block.push(state.raw_v[v_at..v_at + cfg.v_head_dim].to_vec());
353 }
354
355 let compressed_k = compress_block(
356 &kv_block,
357 &score_block,
358 &weights.comp_norm,
359 cfg.rms_norm_eps,
360 cfg.qk_rope,
361 blocks_closed,
362 cfg.compress_rope_theta,
363 );
364 let compressed_v = compress_block(
370 &v_block,
371 &score_block,
372 &weights.comp_norm,
373 cfg.rms_norm_eps,
374 cfg.qk_rope,
375 blocks_closed,
376 cfg.compress_rope_theta,
377 );
378 state.compressed_k.extend_from_slice(&compressed_k);
379 state.compressed_v.extend_from_slice(&compressed_v);
380}
381
382fn attn_forward_token(
383 weights: &DeepseekV4AttnWeights,
384 cfg: &DeepseekV4DecoderConfig,
385 attn_in: &[f32],
386 state: &mut DeepseekV4LayerState,
387) -> Vec<f32> {
388 let q = weights.q_proj.apply(attn_in);
389 let k = weights.k_proj.apply(attn_in);
390 let v = weights.v_proj.apply(attn_in);
391 debug_assert_eq!(q.len(), cfg.n_heads * cfg.qk_head_dim);
392 debug_assert_eq!(k.len(), cfg.n_heads * cfg.qk_head_dim);
393 debug_assert_eq!(v.len(), cfg.n_heads * cfg.v_head_dim);
394
395 state.raw_k.extend_from_slice(&k);
396 state.raw_v.extend_from_slice(&v);
397 state.token_count += 1;
398 let n_raw = state.token_count;
399
400 if let Some(csa) = weights.csa.as_ref() {
404 for head in k.chunks(cfg.qk_head_dim) {
405 state.role_kv.extend_from_slice(&csa.role_proj.apply(head));
406 state
407 .role_scores
408 .extend_from_slice(&csa.role_gate.apply(head));
409 }
410 }
411
412 let ratio = cfg.compressor.ratio() as usize;
413 if ratio > 0 && n_raw.is_multiple_of(ratio) {
414 match cfg.compressor {
415 LayerCompressor::Hca => hca_block(weights, cfg, state, ratio),
416 LayerCompressor::Csa => {
417 csa_block(weights, cfg, state, ratio);
418 }
419 LayerCompressor::None => unreachable!("ratio 0 is filtered above"),
420 }
421 }
422
423 let n_compressed = if cfg.qk_head_dim > 0 {
424 state.compressed_k.len() / (cfg.n_heads * cfg.qk_head_dim)
425 } else {
426 0
427 };
428 let sinks = weights.attn_sinks.as_deref();
429
430 let mut attn_out = match cfg.compressor {
435 LayerCompressor::None | LayerCompressor::Hca => hca_attention(
436 &q,
437 &state.raw_k,
438 &state.raw_v,
439 n_raw,
440 &state.compressed_k,
441 &state.compressed_v,
442 n_compressed,
443 cfg.n_heads,
444 cfg.qk_head_dim,
445 cfg.v_head_dim,
446 sinks,
447 ),
448 LayerCompressor::Csa => {
449 let csa = weights
450 .csa
451 .as_ref()
452 .expect("a CSA layer must carry its role projections and indexer");
453 let n_index_heads = csa.indexer_head_weights.len();
454 let projected = csa.indexer_q_proj.apply(&q[..cfg.qk_head_dim]);
459 let index_head_dim = projected.len().checked_div(n_index_heads).unwrap_or(0);
463 let indexer_q: Vec<Vec<f32>> = projected
464 .chunks(index_head_dim.max(1))
465 .map(|c| c.to_vec())
466 .collect();
467 let indexer_keys: Vec<Vec<f32>> = state
468 .compressed_k
469 .chunks(cfg.qk_head_dim)
470 .take(n_compressed)
471 .map(|entry| csa.indexer_key_proj.apply(entry))
472 .collect();
473 csa_attention(
474 &q,
475 &state.raw_k,
476 &state.raw_v,
477 n_raw,
478 &state.compressed_k,
479 &state.compressed_v,
480 n_compressed,
481 &indexer_q,
482 &indexer_keys,
483 &csa.indexer_head_weights,
484 csa.indexer_top_k,
485 cfg.n_heads,
486 cfg.qk_head_dim,
487 cfg.v_head_dim,
488 sinks,
489 )
490 }
491 };
492
493 derope_attn_out(
494 &mut attn_out,
495 cfg.n_heads,
496 cfg.v_head_dim,
497 cfg.qk_rope,
498 state.token_count.saturating_sub(1),
499 cfg.compress_rope_theta,
500 );
501
502 grouped_output_projection(&attn_out, &weights.group_down, &weights.wo_b)
503}
504
505fn moe_ffn_forward(
506 weights: &DeepseekV4MoeFfnWeights,
507 cfg: &DeepseekV4DecoderConfig,
508 x: &[f32],
509) -> Vec<f32> {
510 let router_logits = weights.router_weight.apply(x);
511 let decision = route_top_k(
512 &router_logits,
513 cfg.n_experts_active,
514 GatingFunction::SqrtSoftplus,
515 cfg.moe_renormalize,
516 );
517 let routed_outputs: Vec<(Vec<f32>, f32)> = decision
518 .expert_ids
519 .iter()
520 .zip(decision.weights.iter())
521 .map(|(&e, &w)| (run_expert(x, &weights.experts[e], GluAct::Swiglu), w))
522 .collect();
523 let shared_out = run_expert(x, &weights.shared_expert, GluAct::Swiglu);
524 combine_expert_outputs(&routed_outputs, &[shared_out], x.len())
525}
526
527pub fn deepseek_v4_forward_token(
530 weights: &DeepseekV4DecoderWeights,
531 cfg: &DeepseekV4DecoderConfig,
532 token_id: usize,
533 state: &mut DeepseekV4DecodeState,
534) -> Vec<f32> {
535 let hidden_dim = weights.embedding.cols();
536 let hidden = weights.embedding.row(token_id).to_vec();
537 state.layer.reset_hc_from_hidden(&hidden);
538 let layer = &weights.layer;
539
540 let hc_residual = state.layer.hc_streams.clone();
541 let (attn_in, attn_post, attn_comb) = hc_pre(
542 &layer.attn_hc_pre,
543 &hc_residual,
544 cfg.rms_norm_eps,
545 cfg.hc_sinkhorn_iters,
546 cfg.hc_eps,
547 );
548 let attn_normed = rms_norm(&attn_in, &layer.attn_norm_weight, cfg.rms_norm_eps);
549 let attn_out = attn_forward_token(&layer.attn, cfg, &attn_normed, &mut state.layer);
550 state.layer.hc_streams = hc_post(&attn_out, &hc_residual, &attn_post, &attn_comb);
551
552 let hc_residual = state.layer.hc_streams.clone();
553 let (ffn_in, ffn_post, ffn_comb) = hc_pre(
554 &layer.ffn_hc_pre,
555 &hc_residual,
556 cfg.rms_norm_eps,
557 cfg.hc_sinkhorn_iters,
558 cfg.hc_eps,
559 );
560 let ffn_normed = rms_norm(&ffn_in, &layer.ffn_norm_weight, cfg.rms_norm_eps);
561 let ffn_out = moe_ffn_forward(&layer.ffn, cfg, &ffn_normed);
562 state.layer.hc_streams = hc_post(&ffn_out, &hc_residual, &ffn_post, &ffn_comb);
563
564 let collapsed = hc_head(
565 &weights.hc_head,
566 &state.layer.hc_streams,
567 cfg.rms_norm_eps,
568 cfg.hc_eps,
569 );
570 let final_normed = rms_norm(&collapsed, &weights.final_norm_weight, cfg.rms_norm_eps);
571 assert_eq!(final_normed.len(), hidden_dim);
572 weights.output_head.apply(&final_normed)
573}
574
575#[cfg(test)]
576mod tests {
577 use super::*;
578 use crate::hyper_connections::HyperConnectionPreWeights;
579
580 const HIDDEN_DIM: usize = 8;
581 const EPS: f32 = 1e-5;
582 const NUM_HEADS: usize = 1;
583 const QK_HEAD_DIM: usize = 4;
584 const V_HEAD_DIM: usize = 4;
585 const QK_ROPE: usize = 2;
586 const N_GROUPS: usize = 1;
587 const O_LORA_RANK: usize = 2;
588 const O_GROUP_DIM: usize = (NUM_HEADS * V_HEAD_DIM) / N_GROUPS;
589 const N_EXPERTS: usize = 4;
590 const N_EXPERTS_ACTIVE: usize = 2;
591 const MOE_FFN_DIM: usize = 3;
592 const OUTPUT_VOCAB: usize = 5;
593 const HC_FLAT: usize = HC_MULT * HIDDEN_DIM;
594 const N_INDEX_HEADS: usize = 2;
595 const INDEX_HEAD_DIM: usize = 2;
596
597 fn wm(data: Vec<f32>, rows: usize, cols: usize) -> WeightMatrix {
598 assert_eq!(data.len(), rows * cols);
599 WeightMatrix::F32(Tensor::new(data, vec![rows, cols]))
600 }
601
602 fn synth(seed: usize, n: usize) -> Vec<f32> {
603 (0..n)
604 .map(|i| (((seed * 131 + i * 17 + 7) % 23) as f32 * 0.05) - 0.55)
605 .collect()
606 }
607
608 fn make_hc_pre(seed: usize) -> HyperConnectionPreWeights {
609 HyperConnectionPreWeights {
610 fn_proj: wm(
611 synth(seed, (2 + HC_MULT) * HC_MULT * HC_FLAT),
612 (2 + HC_MULT) * HC_MULT,
613 HC_FLAT,
614 ),
615 scale: [0.5, 0.5, 0.5],
616 base_pre: [0.1; HC_MULT],
617 base_post: [0.2; HC_MULT],
618 base_comb: [0.01; HC_MULT * HC_MULT],
619 }
620 }
621
622 fn make_hc_head(seed: usize) -> HyperConnectionHeadWeights {
623 HyperConnectionHeadWeights {
624 fn_proj: wm(synth(seed, HC_MULT * HC_FLAT), HC_MULT, HC_FLAT),
625 scale: 0.5,
626 base: [0.1; HC_MULT],
627 }
628 }
629
630 fn make_weights_for(csa: bool, sinks: Option<Vec<f32>>) -> DeepseekV4DecoderWeights {
631 let expert = |seed: usize| ExpertWeights {
632 gate: wm(
633 synth(seed, MOE_FFN_DIM * HIDDEN_DIM),
634 MOE_FFN_DIM,
635 HIDDEN_DIM,
636 ),
637 up: wm(
638 synth(seed + 1, MOE_FFN_DIM * HIDDEN_DIM),
639 MOE_FFN_DIM,
640 HIDDEN_DIM,
641 ),
642 down: wm(
643 synth(seed + 2, HIDDEN_DIM * MOE_FFN_DIM),
644 HIDDEN_DIM,
645 MOE_FFN_DIM,
646 ),
647 };
648
649 DeepseekV4DecoderWeights {
650 embedding: Tensor::new(
651 synth(1000, OUTPUT_VOCAB * HIDDEN_DIM),
652 vec![OUTPUT_VOCAB, HIDDEN_DIM],
653 ),
654 layer: DeepseekV4DecoderLayerWeights {
655 attn_hc_pre: make_hc_pre(100),
656 attn_norm_weight: vec![1.0; HIDDEN_DIM],
657 attn: DeepseekV4AttnWeights {
658 q_proj: wm(
659 synth(110, NUM_HEADS * QK_HEAD_DIM * HIDDEN_DIM),
660 NUM_HEADS * QK_HEAD_DIM,
661 HIDDEN_DIM,
662 ),
663 k_proj: wm(
664 synth(111, NUM_HEADS * QK_HEAD_DIM * HIDDEN_DIM),
665 NUM_HEADS * QK_HEAD_DIM,
666 HIDDEN_DIM,
667 ),
668 v_proj: wm(
669 synth(112, NUM_HEADS * V_HEAD_DIM * HIDDEN_DIM),
670 NUM_HEADS * V_HEAD_DIM,
671 HIDDEN_DIM,
672 ),
673 group_down: (0..N_GROUPS)
674 .map(|g| {
675 wm(
676 synth(120 + g, O_LORA_RANK * O_GROUP_DIM),
677 O_LORA_RANK,
678 O_GROUP_DIM,
679 )
680 })
681 .collect(),
682 wo_b: wm(
683 synth(130, HIDDEN_DIM * O_LORA_RANK * N_GROUPS),
684 HIDDEN_DIM,
685 O_LORA_RANK * N_GROUPS,
686 ),
687 comp_gate: wm(
688 synth(140, QK_HEAD_DIM * QK_HEAD_DIM),
689 QK_HEAD_DIM,
690 QK_HEAD_DIM,
691 ),
692 comp_norm: vec![1.0; QK_HEAD_DIM],
693 attn_sinks: sinks,
694 csa: csa.then(|| DeepseekV4CsaWeights {
695 role_proj: wm(
696 synth(150, 2 * QK_HEAD_DIM * QK_HEAD_DIM),
697 2 * QK_HEAD_DIM,
698 QK_HEAD_DIM,
699 ),
700 role_gate: wm(
701 synth(151, 2 * QK_HEAD_DIM * QK_HEAD_DIM),
702 2 * QK_HEAD_DIM,
703 QK_HEAD_DIM,
704 ),
705 indexer_key_proj: wm(
706 synth(152, INDEX_HEAD_DIM * QK_HEAD_DIM),
707 INDEX_HEAD_DIM,
708 QK_HEAD_DIM,
709 ),
710 indexer_q_proj: wm(
711 synth(153, N_INDEX_HEADS * INDEX_HEAD_DIM * QK_HEAD_DIM),
712 N_INDEX_HEADS * INDEX_HEAD_DIM,
713 QK_HEAD_DIM,
714 ),
715 indexer_head_weights: vec![1.0; N_INDEX_HEADS],
716 indexer_top_k: 1,
717 }),
718 },
719 ffn_hc_pre: make_hc_pre(200),
720 ffn_norm_weight: vec![1.0; HIDDEN_DIM],
721 ffn: DeepseekV4MoeFfnWeights {
722 router_weight: wm(synth(300, N_EXPERTS * HIDDEN_DIM), N_EXPERTS, HIDDEN_DIM),
723 experts: (0..N_EXPERTS).map(|e| expert(400 + e * 10)).collect(),
724 shared_expert: expert(900),
725 },
726 },
727 hc_head: make_hc_head(500),
728 final_norm_weight: vec![1.0; HIDDEN_DIM],
729 output_head: wm(
730 synth(1100, OUTPUT_VOCAB * HIDDEN_DIM),
731 OUTPUT_VOCAB,
732 HIDDEN_DIM,
733 ),
734 }
735 }
736
737 fn make_weights() -> DeepseekV4DecoderWeights {
738 make_weights_for(false, None)
739 }
740
741 fn decoder_cfg_for(compressor: LayerCompressor) -> DeepseekV4DecoderConfig {
742 DeepseekV4DecoderConfig {
743 rms_norm_eps: EPS,
744 hc_sinkhorn_iters: 4,
745 hc_eps: 1e-6,
746 n_heads: NUM_HEADS,
747 qk_head_dim: QK_HEAD_DIM,
748 v_head_dim: V_HEAD_DIM,
749 qk_rope: QK_ROPE,
750 compress_rope_theta: 1_000_000.0,
751 n_experts_active: N_EXPERTS_ACTIVE,
752 moe_renormalize: true,
753 compressor,
754 }
755 }
756
757 fn decoder_cfg() -> DeepseekV4DecoderConfig {
758 decoder_cfg_for(LayerCompressor::Hca)
759 }
760
761 #[test]
766 fn every_compressor_in_the_schedule_produces_finite_logits() {
767 for compressor in [
768 LayerCompressor::None,
769 LayerCompressor::Csa,
770 LayerCompressor::Hca,
771 ] {
772 let weights = make_weights_for(compressor == LayerCompressor::Csa, None);
773 let cfg = decoder_cfg_for(compressor);
774 let mut state = DeepseekV4DecodeState::new(HIDDEN_DIM);
775 for token_id in 0..9 {
779 let logits =
780 deepseek_v4_forward_token(&weights, &cfg, token_id % OUTPUT_VOCAB, &mut state);
781 assert!(
782 logits.iter().all(|v| v.is_finite()),
783 "{compressor:?} token {token_id}: {logits:?}"
784 );
785 }
786 }
787 }
788
789 #[test]
794 fn a_layer_with_no_compressor_never_builds_a_compressed_entry() {
795 let weights = make_weights_for(false, None);
796 let cfg = decoder_cfg_for(LayerCompressor::None);
797 let mut state = DeepseekV4DecodeState::new(HIDDEN_DIM);
798 for token_id in 0..20 {
799 deepseek_v4_forward_token(&weights, &cfg, token_id % OUTPUT_VOCAB, &mut state);
800 }
801 assert_eq!(state.layer.token_count, 20);
802 assert!(
803 state.layer.compressed_k.is_empty() && state.layer.compressed_v.is_empty(),
804 "a ratio-0 layer compressed something"
805 );
806 }
807
808 #[test]
813 fn one_compressed_entry_appears_per_closed_block() {
814 let weights = make_weights_for(true, None);
815 let cfg = decoder_cfg_for(LayerCompressor::Csa);
816 let mut state = DeepseekV4DecodeState::new(HIDDEN_DIM);
817 for token in 0..12 {
818 deepseek_v4_forward_token(&weights, &cfg, token % OUTPUT_VOCAB, &mut state);
819 let entries = state.layer.compressed_k.len() / (NUM_HEADS * QK_HEAD_DIM);
820 assert_eq!(
821 entries,
822 LayerCompressor::Csa.visible_compressed(token),
823 "after token {token}"
824 );
825 }
826 }
827
828 #[test]
840 fn a_csa_block_reaches_back_into_the_previous_half_block() {
841 let cfg = decoder_cfg_for(LayerCompressor::Csa);
842 let ratio = LayerCompressor::Csa.ratio() as usize;
843
844 let run = |first_tokens: &[usize]| -> Vec<f32> {
845 let weights = make_weights_for(true, None);
846 let mut state = DeepseekV4DecodeState::new(HIDDEN_DIM);
847 for &t in first_tokens {
848 deepseek_v4_forward_token(&weights, &cfg, t, &mut state);
849 }
850 for t in 0..ratio {
853 deepseek_v4_forward_token(&weights, &cfg, t % OUTPUT_VOCAB, &mut state);
854 }
855 let per_entry = NUM_HEADS * QK_HEAD_DIM;
856 assert_eq!(state.layer.compressed_k.len() / per_entry, 2);
857 state.layer.compressed_k[per_entry..].to_vec()
858 };
859
860 let a = run(&[0, 1, 2, 3]);
861 let b = run(&[4, 3, 2, 1]);
862 assert_ne!(
863 a, b,
864 "the second block must depend on the first half-block it overlaps"
865 );
866 }
867
868 #[test]
872 fn a_per_head_attention_sink_changes_the_output() {
873 let cfg = decoder_cfg_for(LayerCompressor::None);
874 let logits_for = |sinks: Option<Vec<f32>>| {
875 let weights = make_weights_for(false, sinks);
876 let mut state = DeepseekV4DecodeState::new(HIDDEN_DIM);
877 let mut last = Vec::new();
878 for token in 0..4 {
879 last = deepseek_v4_forward_token(&weights, &cfg, token % OUTPUT_VOCAB, &mut state);
880 }
881 last
882 };
883
884 let plain = logits_for(None);
885 let negligible = logits_for(Some(vec![-40.0; NUM_HEADS]));
886 let dominant = logits_for(Some(vec![40.0; NUM_HEADS]));
887
888 for (p, n) in plain.iter().zip(negligible.iter()) {
889 assert!(
890 (p - n).abs() < 1e-4,
891 "a sink far below every score: {p} vs {n}"
892 );
893 }
894 assert!(
895 plain
896 .iter()
897 .zip(dominant.iter())
898 .any(|(p, d)| (p - d).abs() > 1e-3),
899 "a dominant sink must change the answer"
900 );
901 assert!(dominant.iter().all(|v| v.is_finite()));
902 }
903
904 #[test]
905 fn one_layer_synthetic_forward_produces_finite_logits() {
906 let weights = make_weights();
907 let cfg = decoder_cfg();
908 let mut state = DeepseekV4DecodeState::new(HIDDEN_DIM);
909
910 for token_id in 0..3 {
911 let logits =
912 deepseek_v4_forward_token(&weights, &cfg, token_id % OUTPUT_VOCAB, &mut state);
913 assert_eq!(logits.len(), OUTPUT_VOCAB);
914 assert!(
915 logits.iter().all(|v| v.is_finite()),
916 "token {token_id}: logits must be finite, got {logits:?}"
917 );
918 assert!(
919 !logits.iter().any(|v| v.is_nan()),
920 "token {token_id}: logits must not contain NaN, got {logits:?}"
921 );
922 }
923 }
924}