Skip to main content

ferrox_models/
block_residual.rs

1//! Kimi K3's cross-layer "block residual" mixing mechanism
2//! (`_apply_attn_res` in the real `modeling_kimi_linear.py`), a real
3//! architectural feature discovered by reading `KimiDecoderLayer.forward`
4//! in full rather than assuming a standard pre-norm residual: every
5//! layer blends its running residual stream with a growing set of
6//! saved checkpoints from earlier layers in the same block, using a
7//! learned RMSNorm-projected softmax score to weight the blend --
8//! structurally a tiny self-attention over `{checkpoints..., current}`.
9//!
10//! Confirmed real and active (not a dead/optional code path) via
11//! `config.json`'s `attn_res_block_size`=12: every layer calls this
12//! twice (once before self-attention using `self_attention_res_norm`/
13//! `self_attention_res_proj`, once before the FFN using
14//! `mlp_res_norm`/`mlp_res_proj`), every 12th layer (0-indexed
15//! `layer_idx % 12 == 0`) additionally commits the layer's pre-blend
16//! input as a new checkpoint into the block's growing residual set, and
17//! the whole model applies one final blend
18//! (`output_attn_res_norm`/`output_attn_res_proj`) before the final
19//! norm. Real per-layer tensor names (`self_attention_res_norm.weight`,
20//! `self_attention_res_proj.weight`, `mlp_res_norm.weight`,
21//! `mlp_res_proj.weight`) confirmed directly against a real Kimi K3
22//! shard header fetched earlier this session -- this isn't a rarely-used
23//! feature, every layer has these weights.
24
25/// One `_apply_attn_res` call: blends `prefix_sum` (`[hidden_dim]`, the
26/// layer's current running residual) with `block_residual` (`[n_blocks,
27/// hidden_dim]` flattened, the checkpoints saved so far in this block --
28/// may be empty). `norm_weight`/`proj_weight` are both `[hidden_dim]`
29/// (the real `proj` is `Linear(hidden_dim, 1, bias=false)`, so its
30/// weight is a single `[hidden_dim]` row, not a matrix).
31///
32/// Real math: normalize each candidate (`prefix_sum` and every saved
33/// checkpoint) by its own RMS scale factor only (no elementwise weight
34/// yet), score it by dotting with `norm_weight * proj_weight`
35/// (mathematically identical to `proj(rms_norm(candidate))` since `proj`
36/// has no bias), softmax the scores across candidates, then return the
37/// softmax-weighted sum of the *raw* (non-normalized) candidates.
38pub fn apply_attn_res(
39    prefix_sum: &[f32],
40    block_residual: &[f32],
41    norm_weight: &[f32],
42    proj_weight: &[f32],
43    eps: f32,
44) -> Vec<f32> {
45    let score_weight: Vec<f32> = norm_weight
46        .iter()
47        .zip(proj_weight.iter())
48        .map(|(n, p)| n * p)
49        .collect();
50    apply_attn_res_prescored(prefix_sum, block_residual, &score_weight, eps)
51}
52
53/// Same computation as `apply_attn_res`, but takes the already-fused
54/// `norm_weight * proj_weight` product directly instead of the two
55/// separate factors -- what a real Kimi K3 GGUF checkpoint actually
56/// stores (`blk.{bid}.attn_res_score`/`ffn_res_score`/
57/// `output_res_score`, real names confirmed against
58/// `ggml-org/llama.cpp#26185`'s `conversion/kimi_k3.py`, whose
59/// `_try_fuse_res` fuses this exact product at GGUF-conversion time
60/// since `_apply_attn_res` in the real `modeling_kimi_linear.py` never
61/// uses the two factors separately -- see docs/MODELS.md). The
62/// safetensors checkpoint stores the two factors separately instead, so
63/// `apply_attn_res` fuses them itself and delegates here.
64pub fn apply_attn_res_prescored(
65    prefix_sum: &[f32],
66    block_residual: &[f32],
67    score_weight: &[f32],
68    eps: f32,
69) -> Vec<f32> {
70    let hidden_dim = prefix_sum.len();
71    assert_eq!(score_weight.len(), hidden_dim);
72    assert!(block_residual.len().is_multiple_of(hidden_dim));
73    let n_blocks = block_residual.len() / hidden_dim;
74    let n_candidates = n_blocks + 1;
75
76    let candidate = |c: usize| -> &[f32] {
77        if c < n_blocks {
78            &block_residual[c * hidden_dim..(c + 1) * hidden_dim]
79        } else {
80            prefix_sum
81        }
82    };
83
84    let mut scores = vec![0f32; n_candidates];
85    for (c, score) in scores.iter_mut().enumerate() {
86        let v = candidate(c);
87        let mean_sq: f32 = v.iter().map(|x| x * x).sum::<f32>() / hidden_dim as f32;
88        let rstd = 1.0 / (mean_sq + eps).sqrt();
89        *score = v
90            .iter()
91            .zip(score_weight.iter())
92            .map(|(x, w)| (x * rstd) * w)
93            .sum();
94    }
95
96    let max = scores.iter().cloned().fold(f32::NEG_INFINITY, f32::max);
97    let mut probs = vec![0f32; n_candidates];
98    let mut sum = 0f32;
99    for (c, p) in probs.iter_mut().enumerate() {
100        *p = (scores[c] - max).exp();
101        sum += *p;
102    }
103    for p in probs.iter_mut() {
104        *p /= sum;
105    }
106
107    let mut out = vec![0f32; hidden_dim];
108    for (c, &w) in probs.iter().enumerate() {
109        let v = candidate(c);
110        for (o, x) in out.iter_mut().zip(v.iter()) {
111            *o += w * x;
112        }
113    }
114    out
115}
116
117#[cfg(test)]
118mod tests {
119    use super::*;
120
121    // Generated by an independent Python reference -- do not hand-edit.
122    // Golden values computed from the same formula
123    // transcribed from the real `_apply_attn_res` source.
124    const PREFIX_SUM: [f32; 4] = [0.00123015, 0.298746, -0.274138, -0.890592];
125    const BLOCK_RESIDUAL: [f32; 8] = [
126        -0.454671, -0.991647, 0.0601436, 1.34022, -0.492207, -0.620475, 0.489842, 0.356887,
127    ];
128    const NORM_WEIGHT: [f32; 4] = [1.01054, 0.906953, 0.997075, 1.06953];
129    const PROJ_WEIGHT: [f32; 4] = [-1.34421, -0.457616, -1.90122, -1.28954];
130    const EXPECTED: [f32; 4] = [-0.0107297, 0.271161, -0.26009, -0.847388];
131
132    #[test]
133    fn matches_independent_python_reference() {
134        let got = apply_attn_res(
135            &PREFIX_SUM,
136            &BLOCK_RESIDUAL,
137            &NORM_WEIGHT,
138            &PROJ_WEIGHT,
139            1e-5,
140        );
141        for (i, (a, b)) in got.iter().zip(EXPECTED.iter()).enumerate() {
142            assert!((a - b).abs() < 1e-4, "element {i}: rust={a} python={b}");
143        }
144    }
145
146    #[test]
147    fn with_no_saved_checkpoints_softmax_over_one_candidate_is_the_identity() {
148        // n_blocks=0 -> exactly one candidate (prefix_sum itself) ->
149        // softmax over a single score is always 1.0 -> output must equal
150        // prefix_sum unchanged, regardless of norm/proj weights.
151        let prefix_sum = [1.0f32, -2.0, 3.5];
152        let norm_weight = [0.7f32, 1.3, -0.2];
153        let proj_weight = [2.0f32, -1.0, 0.5];
154        let got = apply_attn_res(&prefix_sum, &[], &norm_weight, &proj_weight, 1e-5);
155        for (a, b) in got.iter().zip(prefix_sum.iter()) {
156            assert!((a - b).abs() < 1e-5);
157        }
158    }
159}