1use ferrox_core::matmul::{rms_norm, situ_and_mul};
34use ferrox_core::weight_matrix::WeightMatrix;
35use ferrox_moe::route_top_k_sigmoid_with_bias;
36
37pub struct KimiExpertWeights {
42 pub w1: WeightMatrix, pub w2: WeightMatrix, pub w3: WeightMatrix, }
46
47impl KimiExpertWeights {
48 pub fn forward(&self, x: &[f32], situ_beta: f32, situ_linear_beta: f32) -> Vec<f32> {
49 let gate = self.w1.apply(x);
50 let up = self.w3.apply(x);
51 let combined = situ_and_mul(&gate, &up, situ_beta, situ_linear_beta);
52 self.w2.apply(&combined)
53 }
54}
55
56pub struct KimiLatentMoeWeights {
57 pub router_weight: WeightMatrix, pub e_score_correction_bias: Vec<f32>, pub down_proj: WeightMatrix, pub up_proj: WeightMatrix, pub routed_expert_norm_weight: Option<Vec<f32>>, pub experts: KimiExpertBacking, pub shared_expert: KimiExpertWeights, }
67
68pub enum KimiExpertBacking {
77 Resident(Vec<KimiExpertWeights>),
78 Stored {
79 store: std::sync::Arc<
80 ferrox_core::expert_store::ExpertStore<crate::kimi_loader::KimiExpertSource>,
81 >,
82 layout: crate::kimi_loader::KimiStoredExpertLayout,
83 n_experts: usize,
84 layer: u32,
85 },
86}
87
88impl KimiExpertBacking {
89 pub fn n_experts(&self) -> usize {
90 match self {
91 KimiExpertBacking::Resident(v) => v.len(),
92 KimiExpertBacking::Stored { n_experts, .. } => *n_experts,
93 }
94 }
95
96 pub fn with_expert<R>(&self, e: usize, f: impl FnOnce(&KimiExpertWeights) -> R) -> R {
100 match self {
101 KimiExpertBacking::Resident(v) => f(&v[e]),
102 KimiExpertBacking::Stored {
103 store,
104 layout,
105 layer,
106 ..
107 } => {
108 let lease = store
109 .acquire(ferrox_core::expert_store::ExpertKey {
110 layer: *layer,
111 expert: e as u32,
112 })
113 .unwrap_or_else(|err| {
114 panic!(
115 "kimi expert store read failed for layer {layer} expert {e}: {err} \
116 (checkpoint file unreadable mid-decode)"
117 )
118 });
119 let tmp = layout.materialize(&lease);
120 f(&tmp)
121 }
122 }
123 }
124}
125
126pub struct KimiMoeConfig {
127 pub n_experts_active: usize,
128 pub moe_renormalize: bool,
129 pub routed_scaling_factor: f32,
130 pub situ_beta: f32,
131 pub situ_linear_beta: f32,
132 pub rms_norm_eps: f32,
133}
134
135pub fn kimi_latent_moe_forward(
136 weights: &KimiLatentMoeWeights,
137 cfg: &KimiMoeConfig,
138 hidden: &[f32],
139) -> Vec<f32> {
140 let router_logits = weights.router_weight.apply(hidden);
141 let decision = route_top_k_sigmoid_with_bias(
142 &router_logits,
143 &weights.e_score_correction_bias,
144 cfg.n_experts_active,
145 cfg.moe_renormalize,
146 cfg.routed_scaling_factor,
147 );
148
149 let latent = weights.down_proj.apply(hidden);
150
151 let mut combined = vec![0f32; latent.len()];
152 for (&eid, &w) in decision.expert_ids.iter().zip(decision.weights.iter()) {
153 let out = weights.experts.with_expert(eid, |ex| {
154 ex.forward(&latent, cfg.situ_beta, cfg.situ_linear_beta)
155 });
156 for (c, o) in combined.iter_mut().zip(out.iter()) {
157 *c += w * o;
158 }
159 }
160
161 let normed = match &weights.routed_expert_norm_weight {
162 Some(w) => rms_norm(&combined, w, cfg.rms_norm_eps),
163 None => combined,
164 };
165
166 let mut out = weights.up_proj.apply(&normed);
167 let shared = weights
168 .shared_expert
169 .forward(hidden, cfg.situ_beta, cfg.situ_linear_beta);
170 for (o, s) in out.iter_mut().zip(shared.iter()) {
171 *o += s;
172 }
173 out
174}
175
176#[cfg(test)]
177mod tests {
178 use super::*;
179 use ferrox_core::tensor::Tensor;
180
181 const HIDDEN_DIM: usize = 6;
182 const MOE_HIDDEN_DIM: usize = 4;
183 const MOE_INTERMEDIATE: usize = 3;
184 const N_EXPERTS: usize = 4;
185 const TOP_K: usize = 2;
186 const SHARED_INTERMEDIATE: usize = 3; const SITU_BETA: f32 = 4.0;
188 const SITU_LINEAR_BETA: f32 = 25.0;
189 const NORM_EPS: f32 = 1e-5;
190
191 fn wm(data: &[f32], rows: usize, cols: usize) -> WeightMatrix {
192 assert_eq!(data.len(), rows * cols);
193 WeightMatrix::F32(Tensor::new(data.to_vec(), vec![rows, cols]))
194 }
195
196 const LMOE_ROUTER_WEIGHT: [f32; 24] = [
198 0.0102578, 0.407924, 0.367416, -0.153092, -0.0893909, -0.158215, 0.170918, -0.0168193,
199 0.224066, -0.554197, 0.469965, -0.0289296, 0.204114, -0.0409699, -0.11373, 0.138933,
200 0.247354, -0.060759, -0.0458359, 0.20571, -0.261102, -0.454315, 0.118495, -0.20117,
201 ];
202 const LMOE_BIAS: [f32; 4] = [-0.96017, -0.407027, -0.233799, -0.596601];
203 const LMOE_DOWN_PROJ: [f32; 24] = [
204 -0.447739, 0.0109913, 0.269175, -0.0699396, -0.223079, 0.115498, 0.215171, -0.0900032,
205 0.1634, 0.312863, -0.0620869, -0.244055, 0.104295, 0.0742637, 0.329644, -0.385374,
206 -0.198484, -0.25145, -0.520204, 0.0379304, 0.158341, -0.221637, 0.415694, 0.246577,
207 ];
208 const LMOE_UP_PROJ: [f32; 24] = [
209 0.188213,
210 0.120512,
211 0.286701,
212 -0.399594,
213 0.184179,
214 0.180833,
215 -0.530316,
216 0.104109,
217 -0.0751264,
218 0.234457,
219 -0.131719,
220 -0.00547226,
221 0.102855,
222 -0.262879,
223 0.179579,
224 -0.031489,
225 0.147745,
226 -0.156551,
227 0.32586,
228 0.181561,
229 -0.0534075,
230 0.189587,
231 0.377927,
232 0.537353,
233 ];
234 const LMOE_ROUTED_NORM_W: [f32; 4] = [0.842642, 1.08831, 1.04651, 0.990614];
235 const LMOE_E0_W1: [f32; 12] = [
236 -0.302, 0.377157, -0.378521, 0.170084, 0.39056, -0.479901, -0.0907554, -0.392765,
237 0.0732162, 0.454313, 0.607067, -0.533434,
238 ];
239 const LMOE_E0_W2: [f32; 12] = [
240 -0.172485,
241 0.211064,
242 0.473812,
243 0.126363,
244 -0.223846,
245 0.0891395,
246 -0.00498576,
247 -0.0611222,
248 -0.220341,
249 0.116178,
250 0.0923639,
251 -0.0278952,
252 ];
253 const LMOE_E0_W3: [f32; 12] = [
254 -0.0665064, -0.385475, -0.145853, 0.361935, -0.0571675, -0.431909, 0.400333, 0.15908,
255 0.632426, 0.0187537, -0.138415, -0.434293,
256 ];
257 const LMOE_E1_W1: [f32; 12] = [
258 0.397152, 0.770853, -0.24628, -0.194126, 0.178857, -0.249104, -0.0811675, -0.104951,
259 0.0575877, 0.328446, 0.00662028, 0.275673,
260 ];
261 const LMOE_E1_W2: [f32; 12] = [
262 -0.125967, 0.0983396, -0.641468, -0.434984, 0.238774, -0.177045, 0.173974, 0.162703,
263 0.396684, 0.243558, 0.305097, -0.0335014,
264 ];
265 const LMOE_E1_W3: [f32; 12] = [
266 -0.209486, -0.219468, -0.146413, -0.338949, -0.164233, -0.0277707, 0.0754836, -0.101667,
267 -0.577105, -0.0216849, 0.0676037, 0.325343,
268 ];
269 const LMOE_E2_W1: [f32; 12] = [
270 0.173359, -0.193068, -0.217133, 0.603179, 0.226991, 0.549432, 0.638833, -0.245425,
271 0.115581, 0.13745, 0.167884, 0.162571,
272 ];
273 const LMOE_E2_W2: [f32; 12] = [
274 0.0606164, 0.0522388, -0.45075, -0.0496204, -0.224286, 0.0378262, -0.140264, 0.185556,
275 0.245723, 0.0926077, 0.0948504, 0.027884,
276 ];
277 const LMOE_E2_W3: [f32; 12] = [
278 -0.134339, -0.0493504, -0.148694, 0.116396, 0.00423402, 0.174387, -0.398595, 0.266334,
279 -0.228809, -0.220285, -0.0592329, -0.169015,
280 ];
281 const LMOE_E3_W1: [f32; 12] = [
282 0.0873376, -0.172249, -0.320843, -0.253741, 0.393595, 0.0132775, -0.350259, 0.00251322,
283 -0.466784, 0.53687, -0.457343, 0.143626,
284 ];
285 const LMOE_E3_W2: [f32; 12] = [
286 0.163088, -0.435002, 0.090083, 0.299152, 0.140273, 0.0783049, 0.284729, 0.0482714,
287 0.100928, -0.0380135, 0.189542, -0.282415,
288 ];
289 const LMOE_E3_W3: [f32; 12] = [
290 0.237529, -0.15159, -0.327196, 0.109575, 0.507887, 0.288503, -0.154695, 0.209418,
291 -0.136263, -0.0372058, 0.0268585, -0.695166,
292 ];
293 const LMOE_SHARED_W1: [f32; 18] = [
294 0.0574844,
295 -0.308883,
296 -0.20922,
297 -0.442317,
298 -0.454967,
299 -0.282979,
300 0.247679,
301 0.499812,
302 -0.00756153,
303 0.327537,
304 -0.0791942,
305 -0.573592,
306 0.044975,
307 0.133694,
308 -0.128585,
309 0.0906673,
310 0.171767,
311 -0.259022,
312 ];
313 const LMOE_SHARED_W2: [f32; 18] = [
314 -0.44305, -0.0663763, -0.0633182, -0.105799, 0.296174, 0.517677, -0.124994, 0.209666,
315 0.282279, 0.213947, 0.314246, -0.116773, 0.221942, 0.607345, 0.2411, -0.185968, 0.182314,
316 0.378714,
317 ];
318 const LMOE_SHARED_W3: [f32; 18] = [
319 0.110696,
320 -0.16902,
321 0.462924,
322 -0.37493,
323 0.151028,
324 -0.00485267,
325 -0.3581,
326 0.365835,
327 0.104366,
328 -0.350039,
329 0.180519,
330 -0.12948,
331 -0.570837,
332 -0.206672,
333 0.0790654,
334 0.190711,
335 0.0496695,
336 0.0135495,
337 ];
338
339 const LMOE_HIDDEN: [f32; 6] = [0.234995, -0.105488, 0.588477, 0.0743208, 0.146961, 0.270399];
340 const LMOE_GOLDEN_OUT: [f32; 6] = [
341 0.061351, 0.0969791, 0.0499038, -0.052412, -0.0592386, -0.0464002,
342 ];
343
344 fn expert(
345 w1: &[f32],
346 w2: &[f32],
347 w3: &[f32],
348 ffn_dim: usize,
349 in_dim: usize,
350 ) -> KimiExpertWeights {
351 KimiExpertWeights {
352 w1: wm(w1, ffn_dim, in_dim),
353 w2: wm(w2, in_dim, ffn_dim),
354 w3: wm(w3, ffn_dim, in_dim),
355 }
356 }
357
358 fn make_weights() -> KimiLatentMoeWeights {
359 KimiLatentMoeWeights {
360 router_weight: wm(&LMOE_ROUTER_WEIGHT, N_EXPERTS, HIDDEN_DIM),
361 e_score_correction_bias: LMOE_BIAS.to_vec(),
362 down_proj: wm(&LMOE_DOWN_PROJ, MOE_HIDDEN_DIM, HIDDEN_DIM),
363 up_proj: wm(&LMOE_UP_PROJ, HIDDEN_DIM, MOE_HIDDEN_DIM),
364 routed_expert_norm_weight: Some(LMOE_ROUTED_NORM_W.to_vec()),
365 experts: KimiExpertBacking::Resident(vec![
366 expert(
367 &LMOE_E0_W1,
368 &LMOE_E0_W2,
369 &LMOE_E0_W3,
370 MOE_INTERMEDIATE,
371 MOE_HIDDEN_DIM,
372 ),
373 expert(
374 &LMOE_E1_W1,
375 &LMOE_E1_W2,
376 &LMOE_E1_W3,
377 MOE_INTERMEDIATE,
378 MOE_HIDDEN_DIM,
379 ),
380 expert(
381 &LMOE_E2_W1,
382 &LMOE_E2_W2,
383 &LMOE_E2_W3,
384 MOE_INTERMEDIATE,
385 MOE_HIDDEN_DIM,
386 ),
387 expert(
388 &LMOE_E3_W1,
389 &LMOE_E3_W2,
390 &LMOE_E3_W3,
391 MOE_INTERMEDIATE,
392 MOE_HIDDEN_DIM,
393 ),
394 ]),
395 shared_expert: expert(
396 &LMOE_SHARED_W1,
397 &LMOE_SHARED_W2,
398 &LMOE_SHARED_W3,
399 SHARED_INTERMEDIATE,
400 HIDDEN_DIM,
401 ),
402 }
403 }
404
405 fn cfg() -> KimiMoeConfig {
406 KimiMoeConfig {
407 n_experts_active: TOP_K,
408 moe_renormalize: true,
409 routed_scaling_factor: 1.0,
410 situ_beta: SITU_BETA,
411 situ_linear_beta: SITU_LINEAR_BETA,
412 rms_norm_eps: NORM_EPS,
413 }
414 }
415
416 #[test]
417 fn matches_independent_python_reference() {
418 let weights = make_weights();
419 let cfg = cfg();
420 let out = kimi_latent_moe_forward(&weights, &cfg, &LMOE_HIDDEN);
421 assert_eq!(out.len(), LMOE_GOLDEN_OUT.len());
422 for (i, (a, b)) in out.iter().zip(LMOE_GOLDEN_OUT.iter()).enumerate() {
423 assert!((a - b).abs() < 1e-3, "element {i}: rust={a} python={b}");
424 }
425 }
426
427 #[test]
428 fn without_routed_norm_output_still_finite_and_differs() {
429 let mut weights = make_weights();
430 weights.routed_expert_norm_weight = None;
431 let cfg = cfg();
432 let out = kimi_latent_moe_forward(&weights, &cfg, &LMOE_HIDDEN);
433 assert!(out.iter().all(|v| v.is_finite()));
434 assert!((out[0] - LMOE_GOLDEN_OUT[0]).abs() > 1e-6);
435 }
436}