1use anyhow::Result;
6
7use super::attnres::rms_norm;
8use super::cache::{HybridCache, LayerCache, MlaKv};
9use super::cpu_weights::{
10 Ablation, DenseMlp, K3CpuLayer, K3CpuModel, KdaWeights, MixerW, MlaWeights, MlpW, MoeWeights,
11};
12use super::kda::{KdaConfig, KdaState, bounded_gate, kda_decode_token};
13use super::latent_moe::{LatentMoeConfig, mix_routed_experts, sigmoid_topk};
14use super::mla::{MlaConfig, mla_decode_token};
15use super::ops::{embed_token, matvec, matvec_column_tp};
16use super::situ::sigmoid;
17
18mod dense;
19mod stream;
20pub use stream::AttnResStream;
21
22pub type HiddenReduce<'a> = &'a dyn Fn(&mut [f32]) -> Result<()>;
24
25pub type DenseMlpCore<'a> =
27 &'a dyn Fn(&DenseMlp, &[f32], usize, usize, f32, f32) -> Result<Vec<f32>>;
28
29pub struct K3LayerCtx<'a> {
35 pub kda: &'a KdaConfig,
36 pub mla: &'a MlaConfig,
37 pub moe: &'a LatentMoeConfig,
38 pub situ_beta: f32,
39 pub situ_linear_beta: f32,
40 pub hidden: usize,
41 pub dense_intermediate: usize,
42 pub eps: f32,
43 pub rope_theta: f32,
44 pub reduce_hidden: Option<HiddenReduce<'a>>,
46 pub dense_mlp: Option<DenseMlpCore<'a>>,
47}
48
49impl<'a> K3LayerCtx<'a> {
50 pub fn from_model(model: &'a K3CpuModel) -> Self {
51 Self {
52 kda: &model.kda,
53 mla: &model.mla,
54 moe: &model.moe,
55 situ_beta: model.graph.situ_beta,
56 situ_linear_beta: model.graph.situ_linear_beta,
57 hidden: model.graph.hidden,
58 dense_intermediate: model.dense_intermediate,
59 eps: model.eps,
60 rope_theta: model.rope_theta,
61 reduce_hidden: None,
62 dense_mlp: None,
63 }
64 }
65}
66
67pub fn forward_token(
70 model: &K3CpuModel,
71 token: u32,
72 pos: usize,
73 cache: &mut HybridCache,
74 ablation: Ablation,
75) -> Vec<f32> {
76 let embed = embed_token(&model.embed, token, model.graph.hidden, model.vocab);
77 let mut stream = AttnResStream::new(model.graph.hidden, model.graph.attn_res_block_size);
78 stream.partial.clone_from(&embed);
79 let ctx = K3LayerCtx::from_model(model);
80 for layer in &model.layers {
81 if ablation.skip_layer == Some(layer.spec.index) {
82 continue;
83 }
84 let mixer_state = &mut cache.layers[layer.spec.index];
85 forward_one_layer(&ctx, layer, pos, mixer_state, &mut stream, ablation);
86 }
87 let h = stream.mix(
88 &model.output_res_proj,
89 &model.output_res_norm,
90 model.eps,
91 ablation.attnres_mix,
92 );
93 rms_norm(&h, &model.final_norm, model.eps)
94}
95
96pub fn logits(model: &K3CpuModel, h: &[f32]) -> Vec<f32> {
98 matvec(&model.lm_head, h, model.vocab, model.graph.hidden)
99}
100
101pub fn forward_one_layer(
107 ctx: &K3LayerCtx<'_>,
108 layer: &K3CpuLayer,
109 pos: usize,
110 mixer_state: &mut LayerCache,
111 stream: &mut AttnResStream,
112 ablation: Ablation,
113) {
114 forward_one_layer_with_cores(
115 ctx,
116 layer,
117 pos,
118 mixer_state,
119 stream,
120 ablation,
121 cpu_kda_core,
122 cpu_mla_core,
123 cpu_moe_core,
124 )
125 .expect("K3 CPU mixer cores are infallible")
126}
127
128fn cpu_kda_core(
129 x: &[f32],
130 w: &[f32],
131 g: &[f32],
132 b: &[f32],
133 cfg: &KdaConfig,
134 st: &mut KdaState,
135) -> Result<Vec<f32>> {
136 Ok(kda_decode_token(x, w, g, b, cfg, st))
137}
138
139#[allow(clippy::too_many_arguments)]
140fn cpu_mla_core(
141 q: &mut [f32],
142 k: &mut [f32],
143 v: &[f32],
144 g: &[f32],
145 kv: &mut MlaKv,
146 cfg: &MlaConfig,
147 pos: usize,
148 theta: f32,
149) -> Result<Vec<f32>> {
150 Ok(mla_decode_token(q, k, v, g, kv, cfg, pos, theta))
151}
152
153#[allow(clippy::too_many_arguments)]
157pub fn forward_one_layer_with_kda_decode<F>(
158 ctx: &K3LayerCtx<'_>,
159 layer: &K3CpuLayer,
160 pos: usize,
161 mixer_state: &mut LayerCache,
162 stream: &mut AttnResStream,
163 ablation: Ablation,
164 kda_decode: F,
165) -> Result<()>
166where
167 F: FnMut(&[f32], &[f32], &[f32], &[f32], &KdaConfig, &mut KdaState) -> Result<Vec<f32>>,
168{
169 forward_one_layer_with_cores(
170 ctx,
171 layer,
172 pos,
173 mixer_state,
174 stream,
175 ablation,
176 kda_decode,
177 cpu_mla_core,
178 cpu_moe_core,
179 )
180}
181
182#[allow(clippy::too_many_arguments)]
186pub fn forward_one_layer_with_mla_decode<F>(
187 ctx: &K3LayerCtx<'_>,
188 layer: &K3CpuLayer,
189 pos: usize,
190 mixer_state: &mut LayerCache,
191 stream: &mut AttnResStream,
192 ablation: Ablation,
193 mla_decode: F,
194) -> Result<()>
195where
196 F: FnMut(
197 &mut [f32],
198 &mut [f32],
199 &[f32],
200 &[f32],
201 &mut MlaKv,
202 &MlaConfig,
203 usize,
204 f32,
205 ) -> Result<Vec<f32>>,
206{
207 forward_one_layer_with_cores(
208 ctx,
209 layer,
210 pos,
211 mixer_state,
212 stream,
213 ablation,
214 cpu_kda_core,
215 mla_decode,
216 cpu_moe_core,
217 )
218}
219
220fn cpu_moe_core(
221 w: &MoeWeights,
222 latent: &[f32],
223 ids: &[usize],
224 mix_w: &[f32],
225 cfg: &LatentMoeConfig,
226) -> Result<Vec<f32>> {
227 Ok(mix_routed_experts(latent, ids, mix_w, &w.experts, cfg))
228}
229
230#[allow(clippy::too_many_arguments)]
235pub fn forward_one_layer_with_cores<FK, FM, FE>(
236 ctx: &K3LayerCtx<'_>,
237 layer: &K3CpuLayer,
238 pos: usize,
239 mixer_state: &mut LayerCache,
240 stream: &mut AttnResStream,
241 ablation: Ablation,
242 mut kda_decode: FK,
243 mut mla_decode: FM,
244 mut moe_experts: FE,
245) -> Result<()>
246where
247 FK: FnMut(&[f32], &[f32], &[f32], &[f32], &KdaConfig, &mut KdaState) -> Result<Vec<f32>>,
248 FM: FnMut(
249 &mut [f32],
250 &mut [f32],
251 &[f32],
252 &[f32],
253 &mut MlaKv,
254 &MlaConfig,
255 usize,
256 f32,
257 ) -> Result<Vec<f32>>,
258 FE: FnMut(&MoeWeights, &[f32], &[usize], &[f32], &LatentMoeConfig) -> Result<Vec<f32>>,
259{
260 let eps = ctx.eps;
261 let mix = ablation.attnres_mix;
262 let h = stream.mix(&layer.attn_res_proj, &layer.attn_res_norm, eps, mix);
263 stream.archive_incoming_at_block_start(layer.spec.index);
265 let x = rms_norm(&h, &layer.input_norm, eps);
266 let mut mix_out = match (&layer.mixer, mixer_state) {
267 (MixerW::Kda(w), LayerCache::Kda(state)) => {
268 kda_mixer(w, &x, ctx.kda, state, eps, ablation, &mut kda_decode)?
269 }
270 (MixerW::Mla(w), LayerCache::Mla(kv)) => mla_mixer(
271 w,
272 &x,
273 ctx.mla,
274 kv,
275 pos,
276 ctx.rope_theta,
277 eps,
278 ablation,
279 &mut mla_decode,
280 )?,
281 _ => panic!(
282 "K3 mixer/state mismatch at layer {} (CPU fallback GPU wrapper)",
283 layer.spec.index
284 ),
285 };
286 if let Some(f) = ctx.reduce_hidden {
287 f(&mut mix_out)?;
288 }
289 stream.add(&mix_out);
290 let h = stream.mix(&layer.mlp_res_proj, &layer.mlp_res_norm, eps, mix);
291 let x = rms_norm(&h, &layer.post_norm, eps);
292 let mlp_out = match &layer.mlp {
293 MlpW::Dense(w) => {
294 let mut y = dense::run(ctx, w, &x, ctx.dense_intermediate)?;
295 if let Some(f) = ctx.reduce_hidden {
296 f(&mut y)?;
297 }
298 y
299 }
300 MlpW::Moe(w) => moe_mlp_with(w, &x, ctx, ablation.force_expert, &mut moe_experts)?,
301 };
302 stream.add(&mlp_out);
303 Ok(())
304}
305
306fn apply_o_proj(w: &[f32], x: &[f32], out: usize, inn: usize, ablation: Ablation) -> Vec<f32> {
307 matvec_column_tp(
308 w,
309 x,
310 out,
311 inn,
312 ablation.o_proj_tp,
313 ablation.drop_o_proj_rank,
314 )
315}
316
317fn kda_mixer<F>(
318 w: &KdaWeights,
319 x: &[f32],
320 cfg: &KdaConfig,
321 state: &mut KdaState,
322 eps: f32,
323 ablation: Ablation,
324 kda_decode: &mut F,
325) -> Result<Vec<f32>>
326where
327 F: FnMut(&[f32], &[f32], &[f32], &[f32], &KdaConfig, &mut KdaState) -> Result<Vec<f32>>,
328{
329 let qdim = cfg.qkv_dim();
330 let q = matvec(&w.q_proj, x, qdim, x.len());
331 let k = matvec(&w.k_proj, x, qdim, x.len());
332 let v = matvec(&w.v_proj, x, qdim, x.len());
333 let mut qkv = q;
334 qkv.extend_from_slice(&k);
335 qkv.extend_from_slice(&v);
336 let fa = matvec(&w.f_a, x, cfg.head_dim, x.len());
338 let z = matvec(&w.f_b, &fa, qdim, cfg.head_dim);
339 let gate = bounded_gate(
340 &z,
341 &w.dt_bias,
342 &w.a_log,
343 cfg.heads,
344 cfg.head_dim,
345 cfg.gate_lower_bound,
346 );
347 let beta = matvec(&w.b_proj, x, cfg.heads, x.len());
348 let g = matvec(&w.g_proj, x, qdim, x.len());
349 let core = kda_decode(&qkv, &w.conv, &gate, &beta, cfg, state)?;
350 let gated = gated_o_norm(&core, &g, &w.o_norm, cfg.head_dim, eps);
351 Ok(apply_o_proj(&w.o_proj, &gated, x.len(), qdim, ablation))
352}
353
354fn gated_o_norm(core: &[f32], g: &[f32], o_norm: &[f32], head_dim: usize, eps: f32) -> Vec<f32> {
355 let mut out = vec![0.0f32; core.len()];
356 for ((c, gg), o) in core
357 .chunks_exact(head_dim)
358 .zip(g.chunks_exact(head_dim))
359 .zip(out.chunks_exact_mut(head_dim))
360 {
361 let n = rms_norm(c, o_norm, eps);
362 for i in 0..head_dim {
363 o[i] = sigmoid(gg[i]) * n[i];
364 }
365 }
366 out
367}
368
369#[allow(clippy::too_many_arguments)]
370fn mla_mixer<F>(
371 w: &MlaWeights,
372 x: &[f32],
373 cfg: &MlaConfig,
374 kv: &mut MlaKv,
375 pos: usize,
376 theta: f32,
377 eps: f32,
378 ablation: Ablation,
379 mla_decode: &mut F,
380) -> Result<Vec<f32>>
381where
382 F: FnMut(
383 &mut [f32],
384 &mut [f32],
385 &[f32],
386 &[f32],
387 &mut MlaKv,
388 &MlaConfig,
389 usize,
390 f32,
391 ) -> Result<Vec<f32>>,
392{
393 let qk = cfg.qk_head_dim();
394 let qa = matvec(&w.q_a, x, cfg.q_lora_rank, x.len());
395 let qa = rms_norm(&qa, &w.q_a_ln, eps);
396 let mut q = matvec(&w.q_b, &qa, cfg.heads * qk, cfg.q_lora_rank);
397 let kv_in = cfg.kv_lora_rank + cfg.qk_rope_head_dim;
398 let kv_lat = matvec(&w.kv_a, x, kv_in, x.len());
399 let (c, pe) = kv_lat.split_at(cfg.kv_lora_rank);
400 let c = rms_norm(c, &w.kv_a_ln, eps);
401 let kvb_out = cfg.heads * (cfg.qk_nope_head_dim + cfg.v_head_dim);
402 let kvb = matvec(&w.kv_b, &c, kvb_out, cfg.kv_lora_rank);
403 let (k, v) = pack_mla_kv(&kvb, pe, cfg);
404 let mut k = k;
405 let g = matvec(&w.g_proj, x, cfg.heads * cfg.v_head_dim, x.len());
406 let attn = mla_decode(&mut q, &mut k, &v, &g, kv, cfg, pos, theta)?;
407 Ok(apply_o_proj(
408 &w.o_proj,
409 &attn,
410 x.len(),
411 cfg.heads * cfg.v_head_dim,
412 ablation,
413 ))
414}
415
416fn pack_mla_kv(kvb: &[f32], k_pe: &[f32], cfg: &MlaConfig) -> (Vec<f32>, Vec<f32>) {
417 let nope = cfg.qk_nope_head_dim;
418 let rope = cfg.qk_rope_head_dim;
419 let dv = cfg.v_head_dim;
420 let qk = nope + rope;
421 let mut k = vec![0.0f32; cfg.heads * qk];
422 let mut v = vec![0.0f32; cfg.heads * dv];
423 let stride = nope + dv;
424 for h in 0..cfg.heads {
425 let src = &kvb[h * stride..(h + 1) * stride];
426 let kd = &mut k[h * qk..(h + 1) * qk];
427 kd[..nope].copy_from_slice(&src[..nope]);
428 if rope > 0 {
429 kd[nope..].copy_from_slice(k_pe);
430 }
431 v[h * dv..(h + 1) * dv].copy_from_slice(&src[nope..]);
432 }
433 (k, v)
434}
435
436fn moe_mlp_with<F>(
437 w: &MoeWeights,
438 x: &[f32],
439 ctx: &K3LayerCtx<'_>,
440 force: Option<usize>,
441 experts_fn: &mut F,
442) -> Result<Vec<f32>>
443where
444 F: FnMut(&MoeWeights, &[f32], &[usize], &[f32], &LatentMoeConfig) -> Result<Vec<f32>>,
445{
446 let mut logits = matvec(&w.router, x, ctx.moe.n_routed, ctx.moe.hidden);
447 if let Some(e) = force {
448 logits.fill(0.0);
449 logits[e] = 8.0;
450 }
451 let shared = w
452 .shared
453 .as_ref()
454 .map(|s| dense::run(ctx, s, x, ctx.moe.expert_hidden))
455 .transpose()?;
456 let latent = matvec(&w.down, x, ctx.moe.latent, ctx.moe.hidden);
457 let (ids, weights) = sigmoid_topk(&logits, &w.bias, ctx.moe.top_k);
458 let mixed = experts_fn(w, &latent, &ids, &weights, ctx.moe)?;
459 let mixed = if ctx.moe.use_norm {
460 rms_norm(&mixed, &w.norm, ctx.eps)
461 } else {
462 mixed
463 };
464 let mut out = matvec(&w.up, &mixed, ctx.moe.hidden, ctx.moe.latent);
465 if let Some(s) = shared {
466 for (o, ss) in out.iter_mut().zip(&s) {
467 *o += ss;
468 }
469 }
470 Ok(out)
471}