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