Skip to main content

ferrox_models/
kimi_decoder.rs

1//! A dedicated decoder for Kimi K3's real hybrid architecture, separate
2//! from `ferrox-models::decoder::Decoder` (which every GQA-only preset
3//! -- GLM-5.2, DeepSeek V4 Pro, the test fixtures -- uses and which
4//! `ferrox-cli`, `ferrox-server`, `prefix_cache`, and `speculative` all
5//! depend on): this module composes the already-independently-tested
6//! `kda`, `mla`, `latent_moe`, `block_residual`, and
7//! `ferrox_core::situ_and_mul` pieces into a real forward pass, without
8//! touching any of that existing, production-quality GQA code path (a
9//! shared, polymorphic `Decoder` was judged too risky to attempt
10//! without a way to verify it end to end).
11//!
12//! Real per-layer flow, transcribed from `KimiDecoderLayer.forward`'s
13//! `_forward_attn_residual` path (the one Kimi K3 actually runs, since
14//! `attn_res_block_size`=12 is set in its real config) in
15//! `modeling_kimi_linear.py`:
16//!
17//! ```text
18//! prefix_sum = hidden
19//! blended = block_residual.is_empty() ? prefix_sum : apply_attn_res(prefix_sum, block_residual, self_attn_res_*)
20//! if layer_idx % attn_res_block_size == 0 { block_residual.push(prefix_sum); prefix_sum = None }
21//! attn_out = kda_or_mla(rms_norm(blended, input_layernorm))
22//! prefix_sum = (prefix_sum is None) ? attn_out : prefix_sum + attn_out
23//! blended2 = apply_attn_res(prefix_sum, block_residual, mlp_res_*)   // block_residual may have just grown above
24//! ffn_out = dense_or_moe(rms_norm(blended2, post_attention_layernorm))
25//! hidden = prefix_sum + ffn_out
26//! ```
27//!
28//! One real, non-obvious fact confirmed by reading
29//! `KimiLinearModel.forward` (not just the per-layer code): `block_residual`
30//! is freshly re-initialized to *empty* inside `forward()`, i.e. once
31//! per forward call -- for single-token incremental decode (what this
32//! module implements), that means every decode step starts with an
33//! empty `block_residual`, not a value carried over from the previous
34//! token. `KimiDecodeState` therefore only needs to carry each layer's
35//! own attention state (`kda::KdaState` or MLA's growable K/V buffers),
36//! not any block-residual bookkeeping across positions.
37
38use ferrox_core::matmul::{rms_norm, situ_and_mul};
39use ferrox_core::tensor::Tensor;
40use ferrox_core::weight_matrix::WeightMatrix;
41
42use crate::block_residual::apply_attn_res;
43use crate::kda::{self, KdaAttnWeights, KdaState};
44use crate::latent_moe::{self, KimiLatentMoeWeights, KimiMoeConfig};
45use crate::mla::{self, MlaAttnWeights};
46
47pub enum KimiLayerAttention {
48    Kda(Box<KdaAttnWeights>),
49    Mla(Box<MlaAttnWeights>),
50}
51
52/// `KimiMLP` used directly on the full hidden dimension -- the sole
53/// dense leading layer's feed-forward block (`n_dense_leading_layers`=1
54/// for Kimi K3).
55pub struct DenseMlpWeights {
56    pub gate_proj: WeightMatrix,
57    pub up_proj: WeightMatrix,
58    pub down_proj: WeightMatrix,
59}
60
61impl DenseMlpWeights {
62    pub(crate) fn forward(&self, x: &[f32], situ_beta: f32, situ_linear_beta: f32) -> Vec<f32> {
63        let gate = self.gate_proj.apply(x);
64        let up = self.up_proj.apply(x);
65        let combined = situ_and_mul(&gate, &up, situ_beta, situ_linear_beta);
66        self.down_proj.apply(&combined)
67    }
68}
69
70pub enum KimiLayerFfn {
71    Dense(Box<DenseMlpWeights>),
72    Moe(Box<KimiLatentMoeWeights>),
73}
74
75pub struct KimiDecoderLayerWeights {
76    pub input_layernorm_weight: Vec<f32>,
77    pub attn: KimiLayerAttention,
78    pub post_attention_layernorm_weight: Vec<f32>,
79    pub ffn: KimiLayerFfn,
80    /// Block-residual weights -- present on every real layer (confirmed
81    /// against a real shard header), used twice per layer (once before
82    /// attention, once before the FFN).
83    pub self_attention_res_norm_weight: Vec<f32>,
84    pub self_attention_res_proj_weight: Vec<f32>,
85    pub mlp_res_norm_weight: Vec<f32>,
86    pub mlp_res_proj_weight: Vec<f32>,
87}
88
89pub struct KimiDecoderWeights {
90    pub embedding: Tensor, // [vocab_size, hidden_dim]
91    pub layers: Vec<KimiDecoderLayerWeights>,
92    pub output_attn_res_norm_weight: Vec<f32>,
93    pub output_attn_res_proj_weight: Vec<f32>,
94    pub final_norm_weight: Vec<f32>,
95    pub output_head: WeightMatrix, // [vocab_size, hidden_dim]
96}
97
98impl KimiDecoderWeights {
99    /// The shared expert store's live counters when this model streams
100    /// routed experts -- `None` when fully resident. Every store-backed
101    /// layer shares one store, so the first found speaks for the model.
102    pub fn expert_store_stats(&self) -> Option<ferrox_core::expert_store::ExpertStoreStats> {
103        self.layers.iter().find_map(|l| match &l.ffn {
104            KimiLayerFfn::Moe(moe) => match &moe.experts {
105                crate::latent_moe::KimiExpertBacking::Stored { store, .. } => Some(store.stats()),
106                crate::latent_moe::KimiExpertBacking::Resident(_) => None,
107            },
108            KimiLayerFfn::Dense(_) => None,
109        })
110    }
111}
112
113pub struct KimiDecoderConfig {
114    pub attn_res_block_size: usize,
115    pub rms_norm_eps: f32,
116    pub situ_beta: f32,
117    pub situ_linear_beta: f32,
118    pub moe: KimiMoeConfig,
119}
120
121pub enum KimiLayerState {
122    Kda(KdaState),
123    Mla {
124        k_cache: Vec<f32>,
125        v_cache: Vec<f32>,
126    },
127}
128
129pub struct KimiDecodeState {
130    layer_states: Vec<KimiLayerState>,
131}
132
133impl KimiDecodeState {
134    /// Builds fresh (all-zero/empty) per-layer state matching each
135    /// layer's real attention kind, as declared by `weights.layers`.
136    pub fn new(weights: &KimiDecoderWeights, kda_cfg: &crate::config::KdaConfig) -> Self {
137        let layer_states = weights
138            .layers
139            .iter()
140            .map(|l| match &l.attn {
141                KimiLayerAttention::Kda(_) => KimiLayerState::Kda(KdaState::new(kda_cfg)),
142                KimiLayerAttention::Mla(_) => KimiLayerState::Mla {
143                    k_cache: Vec::new(),
144                    v_cache: Vec::new(),
145                },
146            })
147            .collect();
148        KimiDecodeState { layer_states }
149    }
150}
151
152/// One decode step across every layer.
153pub fn kimi_forward_token(
154    weights: &KimiDecoderWeights,
155    cfg: &KimiDecoderConfig,
156    mla_cfg: &crate::config::MlaConfig,
157    kda_cfg: &crate::config::KdaConfig,
158    token_id: usize,
159    state: &mut KimiDecodeState,
160) -> Vec<f32> {
161    let hidden_dim = weights.embedding.cols();
162    let mut hidden = weights.embedding.row(token_id).to_vec();
163    // Fresh every call -- see module doc comment for why this is real,
164    // not a simplification: `KimiLinearModel.forward` re-initializes
165    // `block_residual` to empty on every forward call.
166    let mut block_residual: Vec<f32> = Vec::new();
167
168    for (layer_idx, layer) in weights.layers.iter().enumerate() {
169        let prefix_sum_pre_blend = hidden.clone();
170
171        let blended = if block_residual.is_empty() {
172            prefix_sum_pre_blend.clone()
173        } else {
174            apply_attn_res(
175                &prefix_sum_pre_blend,
176                &block_residual,
177                &layer.self_attention_res_norm_weight,
178                &layer.self_attention_res_proj_weight,
179                cfg.rms_norm_eps,
180            )
181        };
182
183        let mut prefix_sum: Option<Vec<f32>> = Some(prefix_sum_pre_blend.clone());
184        if layer_idx % cfg.attn_res_block_size == 0 {
185            block_residual.extend_from_slice(&prefix_sum_pre_blend);
186            prefix_sum = None;
187        }
188
189        let normed = rms_norm(&blended, &layer.input_layernorm_weight, cfg.rms_norm_eps);
190        let attn_out = match (&layer.attn, &mut state.layer_states[layer_idx]) {
191            (KimiLayerAttention::Kda(w), KimiLayerState::Kda(s)) => {
192                kda::kda_forward_token(w, kda_cfg, &normed, cfg.rms_norm_eps, s)
193            }
194            (KimiLayerAttention::Mla(w), KimiLayerState::Mla { k_cache, v_cache }) => {
195                mla::mla_forward_token(w, mla_cfg, &normed, cfg.rms_norm_eps, k_cache, v_cache)
196            }
197            _ => unreachable!("layer attention kind and decode state kind must always match"),
198        };
199
200        let mut prefix_sum = match prefix_sum {
201            Some(mut ps) => {
202                for (p, a) in ps.iter_mut().zip(attn_out.iter()) {
203                    *p += a;
204                }
205                ps
206            }
207            None => attn_out,
208        };
209
210        let blended2 = apply_attn_res(
211            &prefix_sum,
212            &block_residual,
213            &layer.mlp_res_norm_weight,
214            &layer.mlp_res_proj_weight,
215            cfg.rms_norm_eps,
216        );
217        let normed2 = rms_norm(
218            &blended2,
219            &layer.post_attention_layernorm_weight,
220            cfg.rms_norm_eps,
221        );
222        let ffn_out = match &layer.ffn {
223            KimiLayerFfn::Dense(w) => w.forward(&normed2, cfg.situ_beta, cfg.situ_linear_beta),
224            KimiLayerFfn::Moe(w) => latent_moe::kimi_latent_moe_forward(w, &cfg.moe, &normed2),
225        };
226
227        for (p, f) in prefix_sum.iter_mut().zip(ffn_out.iter()) {
228            *p += f;
229        }
230        hidden = prefix_sum;
231    }
232
233    if !block_residual.is_empty() {
234        hidden = apply_attn_res(
235            &hidden,
236            &block_residual,
237            &weights.output_attn_res_norm_weight,
238            &weights.output_attn_res_proj_weight,
239            cfg.rms_norm_eps,
240        );
241    }
242
243    let final_normed = rms_norm(&hidden, &weights.final_norm_weight, cfg.rms_norm_eps);
244    assert_eq!(final_normed.len(), hidden_dim);
245    weights.output_head.apply(&final_normed)
246}
247
248#[cfg(test)]
249mod tests {
250    use super::*;
251    use crate::config::{KdaConfig, MlaConfig};
252    use crate::kda::KdaAttnWeights;
253    use crate::latent_moe::KimiExpertBacking;
254    use crate::latent_moe::KimiExpertWeights;
255    use crate::mla::MlaAttnWeights;
256    use ferrox_core::tensor::Tensor;
257
258    const HIDDEN_DIM: usize = 8;
259    const EPS: f32 = 1e-5;
260    const SITU_BETA: f32 = 4.0;
261    const SITU_LINEAR_BETA: f32 = 25.0;
262    const ATTN_RES_BLOCK_SIZE: usize = 2;
263
264    const KDA_NUM_HEADS: usize = 2;
265    const KDA_HEAD_DIM: usize = 3;
266    const KDA_PROJ: usize = KDA_NUM_HEADS * KDA_HEAD_DIM;
267    const KDA_CONV_SIZE: usize = 4;
268    const KDA_GATE_LOWER_BOUND: f32 = -5.0;
269
270    const DENSE_INTERMEDIATE: usize = 5;
271
272    const MLA_NUM_HEADS: usize = 2;
273    const MLA_QK_NOPE: usize = 3;
274    const MLA_QK_ROPE: usize = 2;
275    const MLA_KV_LORA: usize = 4;
276    const MLA_Q_LORA: usize = 6;
277    const MLA_V_HEAD_DIM: usize = 3;
278    const MLA_PROJ: usize = MLA_NUM_HEADS * MLA_V_HEAD_DIM;
279
280    const MOE_HIDDEN_DIM: usize = 4;
281    const MOE_INTERMEDIATE: usize = 3;
282    const N_EXPERTS: usize = 4;
283    const TOP_K: usize = 2;
284    const SHARED_INTERMEDIATE: usize = 3;
285    const OUTPUT_VOCAB: usize = 5;
286
287    fn wm(data: &[f32], rows: usize, cols: usize) -> WeightMatrix {
288        assert_eq!(data.len(), rows * cols);
289        WeightMatrix::F32(Tensor::new(data.to_vec(), vec![rows, cols]))
290    }
291
292    // Generated by an independent Python reference -- do not hand-edit.
293    const KDA_Q_PROJ: [f32; 48] = [
294        0.0332731, -0.0251273, -0.241248, -0.645647, 0.363556, -0.144589, -0.058427, -0.264839,
295        -0.174983, -0.313658, -0.0222095, 0.0329603, -0.0773659, 0.179789, -0.428436, 0.272643,
296        -0.29953, -0.124363, -0.270533, -0.15906, -0.136326, 0.138484, 0.728104, -0.306624,
297        -0.0415019, -0.62199, -0.0334468, 0.0501355, 0.374942, 0.438693, 0.398001, 0.0660859,
298        0.146161, -0.129336, 0.701423, -0.0954704, -0.37886, -0.0410528, 0.653784, 0.193832,
299        0.451906, 0.367486, 0.0999694, -0.498877, 0.495604, -0.203314, -0.326699, -0.410713,
300    ];
301    const KDA_K_PROJ: [f32; 48] = [
302        -0.421999, -0.0844493, 0.0366557, -0.373529, -0.320309, -0.171037, -0.208623, -0.251102,
303        -0.384313, -0.242582, -0.708695, -0.0410055, -0.163735, 0.045339, 0.0596934, -0.462932,
304        0.344817, 0.314063, 0.183688, -0.215147, 0.0682644, -0.119572, 0.24693, -0.171698,
305        -0.427691, -0.311782, -0.243235, 0.168478, -0.322684, -0.0423794, 0.296734, -0.106622,
306        0.0607117, 0.900444, 0.214488, 0.390483, 0.0470901, -0.084555, -0.509912, 0.273387,
307        -0.0251134, 0.459143, 0.34484, -0.102602, -0.315908, -0.311536, -0.154464, -0.067087,
308    ];
309    const KDA_V_PROJ: [f32; 48] = [
310        0.106298, -0.296877, -0.441024, 0.346085, -0.211878, 0.208265, 0.286445, 0.254616,
311        0.527007, 0.0238059, 0.0520139, -0.0428818, 0.547271, 0.0641891, 0.406677, -0.159912,
312        0.315001, 0.090456, 0.665635, 0.0849947, 0.332725, 0.0468494, -0.340192, -0.57511,
313        -0.199206, 0.00843515, 0.307353, -0.341596, -0.188955, -0.0262392, 0.711621, -0.12957,
314        0.0589482, 0.6676, -0.0487213, 0.318632, -0.129497, 0.45287, 0.312848, 0.404807, 0.300039,
315        -0.111872, 0.00619566, 0.337312, 0.203499, -0.615727, -0.426802, 0.00709124,
316    ];
317    const KDA_Q_CONV_W: [f32; 24] = [
318        0.232949, -0.349125, -0.144423, -1.17421, 0.392014, -0.0551284, -0.188107, 0.34828,
319        0.182368, -1.15816, 0.29745, -0.163951, -0.785389, -0.0799616, 0.0853995, 0.159346,
320        0.438013, 0.16794, 0.11068, 0.222541, 0.27932, 0.584781, 0.0801223, 0.315161,
321    ];
322    const KDA_K_CONV_W: [f32; 24] = [
323        0.0811582, 0.5723, 0.275913, 0.337176, 0.30982, -0.260437, -0.368242, 0.355531, -0.940038,
324        -0.0380444, 0.559686, -0.632208, 0.0297131, -0.246906, 0.435212, -0.65012, -0.207435,
325        -0.259419, -0.219341, 0.426728, -0.0195621, -0.164165, 0.231709, 0.363266,
326    ];
327    const KDA_V_CONV_W: [f32; 24] = [
328        0.174442,
329        0.16448,
330        -0.11751,
331        -0.00693412,
332        0.22015,
333        0.1184,
334        0.59284,
335        -0.100545,
336        0.904578,
337        -0.057162,
338        -0.185486,
339        -0.0155876,
340        0.239106,
341        -0.184512,
342        0.887074,
343        0.319565,
344        0.518244,
345        0.238721,
346        -0.0932803,
347        -0.614529,
348        0.400545,
349        0.458996,
350        -0.217588,
351        0.0540083,
352    ];
353    const KDA_A_LOG: [f32; 2] = [2.57153, 1.68862];
354    const KDA_F_A_PROJ: [f32; 24] = [
355        0.293008, 0.208071, -0.0458425, 0.26919, -0.0604267, -0.414273, -0.347327, 0.435059,
356        -0.0875413, 0.260375, 0.78432, -0.0103574, 0.129559, 0.522936, 0.205997, 0.235916,
357        0.129708, -0.515258, -0.251447, 0.0114761, -0.0608938, -0.142939, 0.0391045, -0.106787,
358    ];
359    const KDA_F_B_PROJ: [f32; 18] = [
360        0.258232, 0.0799449, -0.128173, 0.374093, 0.0249372, 0.564439, 0.291732, -0.673602,
361        0.300492, -0.312056, -0.0782346, -0.560855, 0.319259, -0.194854, -0.109685, -0.295024,
362        -0.0275032, -0.287207,
363    ];
364    const KDA_DT_BIAS: [f32; 6] = [0.406797, 0.44378, -0.118682, -0.101149, 0.144153, 0.100643];
365    const KDA_B_PROJ: [f32; 16] = [
366        -0.557976, 0.101854, -0.529571, 0.437377, 0.282072, 0.0808985, 0.119817, -0.234031,
367        -0.114455, -0.182255, 0.492746, -0.213926, -0.0890158, 0.520648, -0.0288543, 0.378583,
368    ];
369    const KDA_G_PROJ: [f32; 48] = [
370        0.115836, 0.257017, -0.28702, 0.0285552, 0.0241214, -0.586101, -0.284745, -0.1296,
371        0.369078, -0.275476, 0.352622, 0.235832, -0.13984, 0.230448, 0.440173, 0.224343, 0.103446,
372        0.108157, -0.523653, -0.674568, 0.130624, 0.228334, -0.493531, 0.257466, -0.0398756,
373        -0.385554, 0.209178, -0.414277, -0.364987, 0.515843, -0.486846, -0.128941, -0.0565001,
374        0.480469, -0.0731606, 0.279074, -0.16879, -0.589812, 0.200213, -0.3698, 0.355666, 0.269551,
375        0.450168, -0.0689023, -0.234073, -0.81206, 0.250412, 0.0890739,
376    ];
377    const KDA_O_NORM_W: [f32; 3] = [0.970175, 0.902756, 1.03511];
378    const KDA_O_PROJ: [f32; 48] = [
379        -0.148986, -0.157794, 0.225606, -0.302445, -0.0587451, -0.235495, -0.541087, -0.270422,
380        0.604319, 0.371634, 0.144359, 0.409312, 0.108881, -0.352629, -0.129886, -0.258043,
381        -0.168576, -0.241786, 0.0283778, -0.097169, -0.0738384, 0.102603, 0.302548, 0.219944,
382        0.144096, -0.521365, -0.354098, -0.79218, -0.227326, -0.778447, -0.221429, -0.0319402,
383        0.377724, 0.388695, 0.560278, 0.420781, -0.440711, 0.275919, 0.189938, 0.159768, 0.341554,
384        0.594513, 0.333733, -0.0974569, -0.308295, 0.200531, 0.101979, -0.363293,
385    ];
386    const DENSE_GATE_PROJ: [f32; 40] = [
387        0.0530662,
388        -0.0224611,
389        -0.468284,
390        0.394925,
391        -0.0289055,
392        -0.138464,
393        0.302004,
394        -0.0823999,
395        0.295465,
396        0.391958,
397        0.197625,
398        0.236802,
399        -0.000834978,
400        0.539451,
401        0.230436,
402        0.0369415,
403        0.235552,
404        -0.0302823,
405        0.216731,
406        0.106894,
407        -0.0388429,
408        -0.0235654,
409        0.0683206,
410        -0.247831,
411        -0.829944,
412        -0.255354,
413        -0.652798,
414        -0.539486,
415        -0.16364,
416        0.212245,
417        -0.263695,
418        0.266948,
419        0.474532,
420        0.249119,
421        -0.0215431,
422        0.0568342,
423        -0.0727135,
424        -0.267156,
425        0.0426981,
426        -0.921182,
427    ];
428    const DENSE_UP_PROJ: [f32; 40] = [
429        -0.236304,
430        0.180069,
431        -0.2151,
432        0.222938,
433        -0.0463798,
434        -0.31709,
435        -0.00577976,
436        0.254963,
437        -0.152982,
438        0.736692,
439        0.565269,
440        -0.00259989,
441        0.22078,
442        0.0674972,
443        0.193022,
444        0.626185,
445        0.0697913,
446        0.489142,
447        -0.0709509,
448        0.135837,
449        0.0604967,
450        0.134483,
451        0.15545,
452        -0.715912,
453        0.131568,
454        0.447037,
455        0.0964134,
456        -0.568027,
457        -0.539577,
458        -0.385414,
459        -0.308715,
460        0.338456,
461        -0.589399,
462        -0.22471,
463        -0.256634,
464        -0.172552,
465        0.229815,
466        -0.232763,
467        -0.15492,
468        0.112469,
469    ];
470    const DENSE_DOWN_PROJ: [f32; 40] = [
471        0.364035, -0.722506, -0.192076, -0.513796, 0.358074, 0.187812, -0.210479, -0.190417,
472        0.0378089, 0.398016, 0.393775, 0.119131, 0.0944593, 0.160586, -0.302192, -0.0174533,
473        -0.0131564, 0.17226, -0.0443057, -0.354608, 0.221662, 0.333016, -0.0158993, -0.0357403,
474        -0.251197, 0.0164112, 0.210909, 0.292464, 0.201202, -0.121079, 0.407385, 0.275425,
475        -0.537043, -0.0175377, -0.428206, 0.253178, 0.404171, -0.231798, 0.168481, -0.222966,
476    ];
477    const MLA_Q_A_PROJ: [f32; 48] = [
478        0.451391, -0.126339, 0.514525, 0.104224, -0.482352, -0.12592, -0.140897, 0.570421,
479        0.177444, 0.0637828, -0.219946, -0.449056, -0.0832418, -0.0932411, 0.141219, -0.688717,
480        0.311497, -0.0927841, 0.0286042, 0.608332, -0.340392, 0.292946, 0.390898, -0.0359569,
481        -0.157069, -0.194805, 0.595659, -0.129717, 0.435375, 0.707992, -0.267707, -0.446994,
482        0.257943, 0.208935, 0.0124412, 0.0539018, 0.40146, -0.144905, -0.115103, -0.432397,
483        -0.205624, -0.596541, 0.485434, 0.194575, -0.132356, -0.194361, -0.361851, -0.109006,
484    ];
485    const MLA_Q_A_NORM_W: [f32; 6] = [0.969939, 0.841643, 1.00562, 1.08525, 0.926626, 0.990575];
486    const MLA_Q_B_PROJ: [f32; 60] = [
487        0.1374,
488        -0.135647,
489        0.0887308,
490        -0.0979333,
491        -0.259915,
492        -0.0779051,
493        0.115564,
494        0.228723,
495        -0.230383,
496        0.083073,
497        0.192752,
498        -0.0369039,
499        -0.286092,
500        -0.0686448,
501        -0.307404,
502        -0.0636238,
503        0.211306,
504        -0.146429,
505        -0.0687791,
506        0.250252,
507        -0.156639,
508        0.337944,
509        0.178598,
510        -0.616342,
511        -0.0385348,
512        0.382634,
513        0.671192,
514        -0.248981,
515        0.121872,
516        0.275898,
517        0.188452,
518        0.828256,
519        -0.438505,
520        0.621337,
521        0.408834,
522        -0.303079,
523        0.0797437,
524        -0.240968,
525        0.0335185,
526        0.602176,
527        0.461823,
528        -0.23303,
529        0.0937693,
530        -0.0384911,
531        0.173248,
532        -0.515262,
533        0.0811063,
534        0.543123,
535        -0.148281,
536        -0.599126,
537        0.302461,
538        -0.417772,
539        0.064963,
540        -0.000217146,
541        0.188863,
542        0.752821,
543        -0.293606,
544        0.169847,
545        0.739423,
546        0.435163,
547    ];
548    const MLA_KV_A_PROJ: [f32; 48] = [
549        -0.421765, -0.232617, 0.203753, 0.166609, -0.045513, -0.0101532, 0.0955488, -0.165861,
550        -0.0260061, -0.0220384, -0.0330121, -0.158364, -0.0111785, -0.345142, 0.755459, -0.0558389,
551        -0.277054, -0.26961, 0.240628, 0.0947741, -0.167247, -0.23363, -0.157332, 0.345719,
552        0.156487, 0.0865676, -0.192661, -0.214795, -0.567426, -0.0837593, -0.324576, 0.310762,
553        -0.314536, -0.68736, -0.0154466, 0.315627, -0.150268, -0.0287802, 0.356274, -0.246495,
554        -0.0825744, 0.204116, 0.559475, 0.640863, 0.456519, 0.173669, 0.185071, -0.115482,
555    ];
556    const MLA_KV_A_NORM_W: [f32; 4] = [1.10413, 1.0107, 0.871313, 1.16755];
557    const MLA_KV_B_PROJ: [f32; 48] = [
558        0.0866448,
559        -0.423795,
560        0.114522,
561        -0.441771,
562        -0.186988,
563        0.0366587,
564        0.114137,
565        -0.0609123,
566        0.199646,
567        0.722449,
568        0.704719,
569        -0.460906,
570        0.0629041,
571        0.485761,
572        0.328924,
573        0.0319879,
574        -0.298518,
575        0.123369,
576        -0.136789,
577        -0.256657,
578        0.131407,
579        0.435331,
580        0.0737087,
581        -0.130659,
582        0.130558,
583        0.113592,
584        -0.0490255,
585        -0.292942,
586        -0.0925113,
587        -0.412935,
588        -0.222763,
589        -0.189368,
590        0.202786,
591        -0.128307,
592        -0.215928,
593        0.0669919,
594        0.384514,
595        0.637332,
596        -0.0592113,
597        0.0464926,
598        -0.281259,
599        0.107265,
600        0.494055,
601        -0.00978826,
602        -0.398057,
603        0.593258,
604        0.0966843,
605        0.013154,
606    ];
607    const MLA_O_PROJ: [f32; 48] = [
608        -0.384195,
609        0.195792,
610        0.334816,
611        -0.101664,
612        0.103024,
613        -0.142412,
614        0.130369,
615        -0.307274,
616        -0.367605,
617        0.559024,
618        -0.128535,
619        0.0330137,
620        0.241127,
621        0.0644755,
622        -0.0776874,
623        -0.231983,
624        -0.909896,
625        -0.268289,
626        0.205435,
627        -0.268665,
628        -0.663843,
629        0.00206543,
630        -0.454986,
631        0.226469,
632        -0.539808,
633        0.34166,
634        7.24295e-05,
635        0.339034,
636        -0.244381,
637        0.168123,
638        -0.316009,
639        0.00653252,
640        0.107088,
641        0.229998,
642        0.279935,
643        0.318718,
644        -0.262585,
645        0.528549,
646        0.356373,
647        0.217004,
648        0.154669,
649        -0.193219,
650        -0.0690429,
651        0.304651,
652        0.554123,
653        0.158962,
654        0.525434,
655        -0.20413,
656    ];
657    const MLA_G_PROJ: [f32; 48] = [
658        0.378873, -0.362289, -0.172782, -0.305609, -0.15234, 0.13899, -0.358599, -0.786074,
659        -0.168818, 0.168034, -0.27184, -0.103517, 0.34245, 0.0531821, 0.254431, -0.164725,
660        -0.0816316, -0.0860996, -0.0865057, 0.0241667, 0.259761, 0.230357, -0.726831, 0.379549,
661        0.171985, -0.378427, -0.199484, 0.138928, -0.238634, -0.55968, -0.405272, -0.184117,
662        -0.184866, 0.0497178, 0.103438, 0.380376, 0.364498, -0.175124, 0.293164, 0.200305,
663        -0.0883711, -0.155664, -0.011865, 0.304442, -0.0586883, -0.269584, -0.215651, 0.0801283,
664    ];
665    const MOE_ROUTER_WEIGHT: [f32; 32] = [
666        -0.14075, 0.348724, 0.162851, 0.220414, -0.334635, -0.0441166, 0.291079, -0.16397,
667        0.104033, 0.186523, -0.0732253, -0.18498, 0.0548991, 0.0572118, -0.212319, -0.114626,
668        0.0438156, -0.291582, -0.364393, -0.456047, -0.153299, -0.175468, 0.0600189, -0.433395,
669        -0.241496, -0.195144, 0.468446, -0.0704137, 0.0423066, 0.0625186, 0.125581, -0.384111,
670    ];
671    const MOE_BIAS: [f32; 4] = [-0.287968, -0.506168, -0.0823805, -1.16076];
672    const MOE_DOWN_PROJ: [f32; 32] = [
673        0.357856, -0.419139, 0.0951717, 0.486966, 0.138318, -0.321581, 0.023647, -0.287115,
674        -0.218501, 0.466282, -0.674924, 0.358341, -0.651141, -0.190237, 0.154624, -0.131574,
675        -0.0769183, 0.0423567, 0.768159, 0.659497, -0.106854, -0.338984, -0.0425253, -0.326632,
676        -0.157781, -0.0021155, 0.259854, 0.132918, 0.305795, 0.418168, 0.0538999, 0.630383,
677    ];
678    const MOE_UP_PROJ: [f32; 32] = [
679        0.0537259, -0.222431, 0.0517936, -0.328772, -0.218991, 0.0109666, -0.66681, 0.53133,
680        -0.24925, -0.271987, -0.57234, 0.0120699, -0.288328, 0.131623, -0.132867, -0.105812,
681        -0.284854, -0.456721, -0.150868, -0.102689, -0.245856, -0.0891634, 0.389166, 0.518893,
682        -0.0414535, 0.157239, -0.0155488, 0.207036, 0.00431687, -0.261777, -0.541271, 0.211408,
683    ];
684    const MOE_ROUTED_NORM_W: [f32; 4] = [1.14805, 0.951891, 0.971543, 0.908736];
685    const MOE_SHARED_W1: [f32; 24] = [
686        -0.377193,
687        0.27978,
688        -0.0102447,
689        0.229219,
690        -0.380455,
691        0.552381,
692        -0.0980754,
693        0.56176,
694        -0.297,
695        -0.142547,
696        -0.204375,
697        -0.338748,
698        0.388348,
699        0.489957,
700        0.356719,
701        0.0574536,
702        0.110144,
703        -0.00856218,
704        0.426843,
705        0.260044,
706        0.148431,
707        -0.184063,
708        -0.342297,
709        -0.0762749,
710    ];
711    const MOE_SHARED_W2: [f32; 24] = [
712        -0.43575, 0.0521214, -0.10101, 0.072215, 0.337606, 0.0963424, -0.107468, -0.49745,
713        0.364127, 0.217258, 0.359295, -0.0454641, -0.252116, 0.0389652, 0.178223, 0.0836014,
714        0.124572, -0.252946, -0.227852, 0.0663797, 0.110113, 0.458224, 0.520268, -0.0199504,
715    ];
716    const MOE_SHARED_W3: [f32; 24] = [
717        0.368003,
718        -0.483325,
719        -0.379792,
720        0.100306,
721        0.106976,
722        -0.0896776,
723        -0.316544,
724        0.322652,
725        -0.517715,
726        0.249498,
727        0.0612062,
728        -0.601994,
729        -0.193968,
730        0.558544,
731        -0.388051,
732        -0.277637,
733        0.122792,
734        -0.185875,
735        -0.00133169,
736        0.0236871,
737        -0.296105,
738        0.0705485,
739        -0.302294,
740        -0.246982,
741    ];
742    const MOE_E0_W1: [f32; 12] = [
743        0.0440567, 0.174659, -0.355788, -0.234582, 0.238399, 0.600469, -0.130771, 0.811262,
744        0.130333, -0.0412095, -0.714084, 0.158573,
745    ];
746    const MOE_E0_W2: [f32; 12] = [
747        -0.103864, -0.115033, 0.275029, -0.0808199, -0.348501, 0.30532, 0.310035, -0.0757431,
748        -0.132877, 0.711211, 0.226243, 0.163931,
749    ];
750    const MOE_E0_W3: [f32; 12] = [
751        -0.114776, 0.141509, 0.504018, -0.115836, 0.43992, 0.131565, -0.469145, 0.0490231,
752        0.151157, -0.0375621, -0.17218, 0.231705,
753    ];
754    const MOE_E1_W1: [f32; 12] = [
755        0.0488396,
756        -0.341746,
757        0.0418606,
758        0.221871,
759        -0.722022,
760        -0.00220993,
761        -0.0916067,
762        -0.398679,
763        -0.423551,
764        -0.645019,
765        -0.27434,
766        0.0208012,
767    ];
768    const MOE_E1_W2: [f32; 12] = [
769        -0.167076, 0.231248, -0.759916, 0.352397, -0.0142662, -0.24585, -0.0941462, 0.0286856,
770        0.103257, -0.272298, 0.177667, -0.226165,
771    ];
772    const MOE_E1_W3: [f32; 12] = [
773        0.189908, -0.207244, -0.0267815, 0.0907185, 0.191764, 0.105395, 0.0162554, -0.452242,
774        0.430473, -0.291964, -0.261698, 0.0512003,
775    ];
776    const MOE_E2_W1: [f32; 12] = [
777        -0.0189576, 0.0668481, 0.214585, 0.143547, -0.459021, 0.191771, -0.386651, 0.519018,
778        -0.38456, -0.409715, 0.101121, -0.114501,
779    ];
780    const MOE_E2_W2: [f32; 12] = [
781        0.329286, -0.122696, 0.385045, -0.309553, 0.205889, -0.0737322, 0.0664255, 0.243589,
782        -0.101208, 0.615374, 0.791347, -0.288181,
783    ];
784    const MOE_E2_W3: [f32; 12] = [
785        -0.34824, -0.0969774, -0.0404219, -0.142054, -0.140162, 0.168913, 0.329251, 0.568559,
786        0.302453, 0.2389, 0.10173, 0.26265,
787    ];
788    const MOE_E3_W1: [f32; 12] = [
789        -0.270246, -0.148207, 0.301809, 0.317732, 0.216907, 0.245138, 0.390181, -0.120865,
790        0.115518, -0.398155, -0.0685247, -0.263379,
791    ];
792    const MOE_E3_W2: [f32; 12] = [
793        -0.0315493, -0.147451, -0.471437, -0.149953, -0.0759736, -0.42393, 0.500115, 0.50571,
794        -0.126137, -0.0207527, 0.0606406, -0.138175,
795    ];
796    const MOE_E3_W3: [f32; 12] = [
797        0.0539619, -0.604005, -0.229171, 0.432303, 0.240478, 0.157986, 0.482184, -0.0937539,
798        0.255604, 0.304551, -0.0564262, -0.17754,
799    ];
800    const L0_SELF_ATTN_RES_NORM_W: [f32; 8] = [
801        1.09202, 0.89273, 1.09569, 0.903039, 0.86568, 0.985966, 0.904269, 0.901858,
802    ];
803    const L0_SELF_ATTN_RES_PROJ_W: [f32; 8] = [
804        0.181373, -0.268475, -0.278623, -0.0176931, 0.15743, -0.189069, -0.0939655, 0.0588636,
805    ];
806    const L0_MLP_RES_NORM_W: [f32; 8] = [
807        1.01577, 1.04681, 0.991541, 1.00423, 0.89548, 1.09467, 1.05601, 0.968893,
808    ];
809    const L0_MLP_RES_PROJ_W: [f32; 8] = [
810        -0.388332, -0.132232, 0.21217, -0.337705, 0.246598, -0.20951, 0.154862, -0.225175,
811    ];
812    const L0_INPUT_LAYERNORM_W: [f32; 8] = [
813        0.970917, 0.970733, 0.946098, 1.23423, 0.883455, 1.12311, 0.809911, 1.12998,
814    ];
815    const L0_POST_ATTN_LAYERNORM_W: [f32; 8] = [
816        0.986205, 1.04826, 1.00761, 1.04983, 0.890071, 0.817534, 1.17858, 0.915493,
817    ];
818    const L1_SELF_ATTN_RES_NORM_W: [f32; 8] = [
819        0.985561, 0.917139, 1.10889, 1.02053, 0.951296, 1.08218, 1.05087, 0.816958,
820    ];
821    const L1_SELF_ATTN_RES_PROJ_W: [f32; 8] = [
822        -0.13164, -0.436846, -0.109316, 0.0236738, 0.0868917, 0.038431, 0.12599, 0.680195,
823    ];
824    const L1_MLP_RES_NORM_W: [f32; 8] = [
825        1.123, 1.03367, 1.15067, 1.06599, 1.10144, 0.905083, 0.911655, 1.11872,
826    ];
827    const L1_MLP_RES_PROJ_W: [f32; 8] = [
828        -0.0917493, -0.258613, 0.62677, 0.0330113, 0.0841344, -0.332195, -0.355091, 0.152981,
829    ];
830    const L1_INPUT_LAYERNORM_W: [f32; 8] = [
831        0.878093, 0.911769, 1.15303, 1.1774, 0.961136, 1.01693, 0.935918, 1.08085,
832    ];
833    const L1_POST_ATTN_LAYERNORM_W: [f32; 8] = [
834        0.982172, 0.868114, 0.876461, 1.00153, 1.10216, 1.01644, 0.784298, 1.0521,
835    ];
836    const OUTPUT_ATTN_RES_NORM_W: [f32; 8] = [
837        0.978339, 0.909439, 1.00794, 0.900698, 1.11013, 1.13568, 1.00689, 1.06737,
838    ];
839    const OUTPUT_ATTN_RES_PROJ_W: [f32; 8] = [
840        -0.0780474, 0.195513, -0.118552, 0.46788, 0.285518, -0.16824, -0.350209, -0.0767004,
841    ];
842    const FINAL_NORM_W: [f32; 8] = [
843        0.969882, 0.931832, 1.10373, 1.16478, 0.891072, 1.0772, 0.85093, 0.91487,
844    ];
845    const EMBEDDING_ROW: [f32; 8] = [
846        0.309738, 0.0378015, -0.337758, 0.323415, 0.162627, -0.685942, 0.134525, 0.42463,
847    ];
848    const OUTPUT_HEAD: [f32; 40] = [
849        0.04209, 0.261934, -0.329344, 0.0625128, 0.272985, 0.290578, 0.0418593, 0.216545, -0.63005,
850        -0.246383, 0.0668848, -0.179091, -0.277674, 0.285747, 0.0959162, -0.0944821, -0.542151,
851        0.2749, 0.299618, -0.0826765, 0.136924, -0.193359, -0.132659, 0.306145, 0.0721566,
852        0.0321218, -0.360207, -0.152256, -0.779984, 0.286993, -0.0684097, 0.142516, -0.124933,
853        0.180322, 0.00527221, 0.0710325, 0.453557, -0.152982, 0.0701835, 0.314973,
854    ];
855    const GOLDEN_LOGITS: [f32; 5] = [0.487583, -1.04021, 0.695218, -0.214018, 1.12848];
856
857    fn expert(
858        w1: &[f32],
859        w2: &[f32],
860        w3: &[f32],
861        ffn_dim: usize,
862        in_dim: usize,
863    ) -> KimiExpertWeights {
864        KimiExpertWeights {
865            w1: wm(w1, ffn_dim, in_dim),
866            w2: wm(w2, in_dim, ffn_dim),
867            w3: wm(w3, ffn_dim, in_dim),
868        }
869    }
870
871    fn make_weights() -> KimiDecoderWeights {
872        let layer0 = KimiDecoderLayerWeights {
873            input_layernorm_weight: L0_INPUT_LAYERNORM_W.to_vec(),
874            attn: KimiLayerAttention::Kda(Box::new(KdaAttnWeights {
875                q_proj: wm(&KDA_Q_PROJ, KDA_PROJ, HIDDEN_DIM),
876                k_proj: wm(&KDA_K_PROJ, KDA_PROJ, HIDDEN_DIM),
877                v_proj: wm(&KDA_V_PROJ, KDA_PROJ, HIDDEN_DIM),
878                q_conv_weight: KDA_Q_CONV_W.to_vec(),
879                k_conv_weight: KDA_K_CONV_W.to_vec(),
880                v_conv_weight: KDA_V_CONV_W.to_vec(),
881                a_log: KDA_A_LOG.to_vec(),
882                f_a_proj: wm(&KDA_F_A_PROJ, KDA_HEAD_DIM, HIDDEN_DIM),
883                f_b_proj: wm(&KDA_F_B_PROJ, KDA_PROJ, KDA_HEAD_DIM),
884                dt_bias: KDA_DT_BIAS.to_vec(),
885                b_proj: wm(&KDA_B_PROJ, KDA_NUM_HEADS, HIDDEN_DIM),
886                g_proj: wm(&KDA_G_PROJ, KDA_PROJ, HIDDEN_DIM),
887                o_norm_weight: KDA_O_NORM_W.to_vec(),
888                o_proj: wm(&KDA_O_PROJ, HIDDEN_DIM, KDA_PROJ),
889            })),
890            post_attention_layernorm_weight: L0_POST_ATTN_LAYERNORM_W.to_vec(),
891            ffn: KimiLayerFfn::Dense(Box::new(DenseMlpWeights {
892                gate_proj: wm(&DENSE_GATE_PROJ, DENSE_INTERMEDIATE, HIDDEN_DIM),
893                up_proj: wm(&DENSE_UP_PROJ, DENSE_INTERMEDIATE, HIDDEN_DIM),
894                down_proj: wm(&DENSE_DOWN_PROJ, HIDDEN_DIM, DENSE_INTERMEDIATE),
895            })),
896            self_attention_res_norm_weight: L0_SELF_ATTN_RES_NORM_W.to_vec(),
897            self_attention_res_proj_weight: L0_SELF_ATTN_RES_PROJ_W.to_vec(),
898            mlp_res_norm_weight: L0_MLP_RES_NORM_W.to_vec(),
899            mlp_res_proj_weight: L0_MLP_RES_PROJ_W.to_vec(),
900        };
901
902        let layer1 = KimiDecoderLayerWeights {
903            input_layernorm_weight: L1_INPUT_LAYERNORM_W.to_vec(),
904            attn: KimiLayerAttention::Mla(Box::new(MlaAttnWeights {
905                q_a_proj: wm(&MLA_Q_A_PROJ, MLA_Q_LORA, HIDDEN_DIM),
906                q_a_layernorm: MLA_Q_A_NORM_W.to_vec(),
907                q_b_proj: wm(
908                    &MLA_Q_B_PROJ,
909                    MLA_NUM_HEADS * (MLA_QK_NOPE + MLA_QK_ROPE),
910                    MLA_Q_LORA,
911                ),
912                kv_a_proj_with_mqa: wm(&MLA_KV_A_PROJ, MLA_KV_LORA + MLA_QK_ROPE, HIDDEN_DIM),
913                kv_a_layernorm: MLA_KV_A_NORM_W.to_vec(),
914                kv_b_proj: wm(
915                    &MLA_KV_B_PROJ,
916                    MLA_NUM_HEADS * (MLA_QK_NOPE + MLA_V_HEAD_DIM),
917                    MLA_KV_LORA,
918                ),
919                o_proj: wm(&MLA_O_PROJ, HIDDEN_DIM, MLA_PROJ),
920                g_proj: Some(wm(&MLA_G_PROJ, MLA_PROJ, HIDDEN_DIM)),
921            })),
922            post_attention_layernorm_weight: L1_POST_ATTN_LAYERNORM_W.to_vec(),
923            ffn: KimiLayerFfn::Moe(Box::new(KimiLatentMoeWeights {
924                router_weight: wm(&MOE_ROUTER_WEIGHT, N_EXPERTS, HIDDEN_DIM),
925                e_score_correction_bias: MOE_BIAS.to_vec(),
926                down_proj: wm(&MOE_DOWN_PROJ, MOE_HIDDEN_DIM, HIDDEN_DIM),
927                up_proj: wm(&MOE_UP_PROJ, HIDDEN_DIM, MOE_HIDDEN_DIM),
928                routed_expert_norm_weight: Some(MOE_ROUTED_NORM_W.to_vec()),
929                experts: KimiExpertBacking::Resident(vec![
930                    expert(
931                        &MOE_E0_W1,
932                        &MOE_E0_W2,
933                        &MOE_E0_W3,
934                        MOE_INTERMEDIATE,
935                        MOE_HIDDEN_DIM,
936                    ),
937                    expert(
938                        &MOE_E1_W1,
939                        &MOE_E1_W2,
940                        &MOE_E1_W3,
941                        MOE_INTERMEDIATE,
942                        MOE_HIDDEN_DIM,
943                    ),
944                    expert(
945                        &MOE_E2_W1,
946                        &MOE_E2_W2,
947                        &MOE_E2_W3,
948                        MOE_INTERMEDIATE,
949                        MOE_HIDDEN_DIM,
950                    ),
951                    expert(
952                        &MOE_E3_W1,
953                        &MOE_E3_W2,
954                        &MOE_E3_W3,
955                        MOE_INTERMEDIATE,
956                        MOE_HIDDEN_DIM,
957                    ),
958                ]),
959                shared_expert: expert(
960                    &MOE_SHARED_W1,
961                    &MOE_SHARED_W2,
962                    &MOE_SHARED_W3,
963                    SHARED_INTERMEDIATE,
964                    HIDDEN_DIM,
965                ),
966            })),
967            self_attention_res_norm_weight: L1_SELF_ATTN_RES_NORM_W.to_vec(),
968            self_attention_res_proj_weight: L1_SELF_ATTN_RES_PROJ_W.to_vec(),
969            mlp_res_norm_weight: L1_MLP_RES_NORM_W.to_vec(),
970            mlp_res_proj_weight: L1_MLP_RES_PROJ_W.to_vec(),
971        };
972
973        KimiDecoderWeights {
974            embedding: Tensor::new(EMBEDDING_ROW.to_vec(), vec![1, HIDDEN_DIM]),
975            layers: vec![layer0, layer1],
976            output_attn_res_norm_weight: OUTPUT_ATTN_RES_NORM_W.to_vec(),
977            output_attn_res_proj_weight: OUTPUT_ATTN_RES_PROJ_W.to_vec(),
978            final_norm_weight: FINAL_NORM_W.to_vec(),
979            output_head: wm(&OUTPUT_HEAD, OUTPUT_VOCAB, HIDDEN_DIM),
980        }
981    }
982
983    fn decoder_cfg() -> KimiDecoderConfig {
984        KimiDecoderConfig {
985            attn_res_block_size: ATTN_RES_BLOCK_SIZE,
986            rms_norm_eps: EPS,
987            situ_beta: SITU_BETA,
988            situ_linear_beta: SITU_LINEAR_BETA,
989            moe: KimiMoeConfig {
990                n_experts_active: TOP_K,
991                moe_renormalize: true,
992                routed_scaling_factor: 1.0,
993                situ_beta: SITU_BETA,
994                situ_linear_beta: SITU_LINEAR_BETA,
995                rms_norm_eps: EPS,
996            },
997        }
998    }
999
1000    fn mla_cfg() -> MlaConfig {
1001        MlaConfig {
1002            num_heads: MLA_NUM_HEADS,
1003            q_lora_rank: MLA_Q_LORA,
1004            kv_lora_rank: MLA_KV_LORA,
1005            qk_nope_head_dim: MLA_QK_NOPE,
1006            qk_rope_head_dim: MLA_QK_ROPE,
1007            v_head_dim: MLA_V_HEAD_DIM,
1008            use_output_gate: true,
1009            rope: None,
1010        }
1011    }
1012
1013    fn kda_cfg() -> KdaConfig {
1014        KdaConfig {
1015            num_heads: KDA_NUM_HEADS,
1016            head_dim: KDA_HEAD_DIM,
1017            short_conv_kernel_size: KDA_CONV_SIZE,
1018            gate_lower_bound: KDA_GATE_LOWER_BOUND,
1019            use_full_rank_gate: true,
1020        }
1021    }
1022
1023    #[test]
1024    fn two_mixed_layers_match_independent_python_reference() {
1025        let weights = make_weights();
1026        let cfg = decoder_cfg();
1027        let mla_cfg = mla_cfg();
1028        let kda_cfg = kda_cfg();
1029        let mut state = KimiDecodeState::new(&weights, &kda_cfg);
1030
1031        let logits = kimi_forward_token(&weights, &cfg, &mla_cfg, &kda_cfg, 0, &mut state);
1032        assert_eq!(logits.len(), GOLDEN_LOGITS.len());
1033        for (i, (a, b)) in logits.iter().zip(GOLDEN_LOGITS.iter()).enumerate() {
1034            assert!((a - b).abs() < 1e-3, "logit {i}: rust={a} python={b}");
1035        }
1036    }
1037}